[
  {
    "path": ".cargo/config.toml",
    "content": "[env]\nLIBSQLITE3_FLAGS = \"-DSQLITE_ENABLE_MATH_FUNCTIONS\" # necessary for rusqlite dependency in order to bundle SQLite with math functions included\n\n[build]\n# turso-sync package uses tokio_unstable to seed LocalRuntime and make it deterministic\n# unfortunately, cargo commands invoked from workspace root didn't capture config.toml from dependent crate\n# so, we set this cfg globally for workspace (see relevant issue build build-target: https://github.com/rust-lang/cargo/issues/7004)\nrustflags = [\n    \"--cfg=tokio_unstable\",\n]\n\n\n# without these rustflags, cargo test failing because we are using shared napi library (turso_node)\n# (these settings copied from napi-rs itself, see https://github.com/napi-rs/napi-rs/issues/2130#issuecomment-2239407809)\n\n[target.'cfg(target_vendor = \"apple\")']\nrustflags = [\n  \"--cfg=tokio_unstable\",\n  \"-C\",\n  \"link-args=-Wl,-undefined,dynamic_lookup,-no_fixup_chains\",\n]\n\n# To be able to run unit tests on Linux, support compilation to 'x86_64-unknown-linux-gnu'.\n# pthread_key_create() destructors and segfault after a DSO unloading https://sourceware.org/bugzilla/show_bug.cgi?id=21031\n[target.'cfg(all(target_os = \"linux\", target_env = \"gnu\"))']\nrustflags = [\n  \"--cfg=tokio_unstable\",\n  \"-C\",\n  \"link-args=-Wl,--warn-unresolved-symbols\",\n  \"-C\",\n  \"link-args=-Wl,-z,nodelete\",\n]\n\n# To be able to run unit tests on Windows, support compilation to 'x86_64-pc-windows-msvc'.\n[target.'cfg(target_env = \"msvc\")']\nrustflags = [\n  \"--cfg=tokio_unstable\",\n  \"-C\",\n  \"link-args=/FORCE\"\n]\n\n# Android targets need 16KB page alignment for Android 15+ compatibility\n[target.aarch64-linux-android]\nrustflags = [\n  \"--cfg=tokio_unstable\",\n  \"-C\",\n  \"link-args=-Wl,-z,max-page-size=16384\",\n]\n\n[target.armv7-linux-androideabi]\nrustflags = [\n  \"--cfg=tokio_unstable\",\n  \"-C\",\n  \"link-args=-Wl,-z,max-page-size=16384\",\n]\n\n[target.x86_64-linux-android]\nrustflags = [\n  \"--cfg=tokio_unstable\",\n  \"-C\",\n  \"link-args=-Wl,-z,max-page-size=16384\",\n]\n\n[target.i686-linux-android]\nrustflags = [\n  \"--cfg=tokio_unstable\",\n  \"-C\",\n  \"link-args=-Wl,-z,max-page-size=16384\",\n]"
  },
  {
    "path": ".claude/skills/async-io-model/SKILL.md",
    "content": "---\nname: async-io-model\ndescription: Explanations of common asynchronous patterns used in tursodb. Involves IOResult, state machines, re-entrancy pitfalls, CompletionGroup. Always use these patterns in `core` when doing anything IO\n---\n# Async I/O Model Guide\n\nTurso uses cooperative yielding with explicit state machines instead of Rust async/await.\n\n## Core Types\n\n```rust\npub enum IOCompletions {\n    Single(Completion),\n}\n\n#[must_use]\npub enum IOResult<T> {\n    Done(T),      // Operation complete, here's the result\n    IO(IOCompletions),  // Need I/O, call me again after completions finish\n}\n```\n\nFunctions returning `IOResult` must be called repeatedly until `Done`.\n\n## Completion and CompletionGroup\n\nA `Completion` tracks a single I/O operation:\n\n```rust\npub struct Completion { /* ... */ }\n\nimpl Completion {\n    pub fn finished(&self) -> bool;\n    pub fn succeeded(&self) -> bool;\n    pub fn get_error(&self) -> Option<CompletionError>;\n}\n```\n\nTo wait for multiple I/O operations, use `CompletionGroup`:\n\n```rust\nlet mut group = CompletionGroup::new(|_| {});\n\n// Add individual completions\ngroup.add(&completion1);\ngroup.add(&completion2);\n\n// Build into single completion that finishes when all complete\nlet combined = group.build();\nio_yield_one!(combined);\n```\n\n`CompletionGroup` features:\n- Aggregates multiple completions into one\n- Calls callback when all complete (or any errors)\n- Can nest groups (add a group's completion to another group)\n- Cancellable via `group.cancel()`\n\n## Helper Macros\n\n### `return_if_io!`\nUnwraps `IOResult`, propagates IO variant up the call stack:\n```rust\nlet result = return_if_io!(some_io_operation());\n// Only reaches here if operation returned Done\n```\n\n### `io_yield_one!`\nYields a single completion:\n```rust\nio_yield_one!(completion);  // Returns Ok(IOResult::IO(Single(completion)))\n```\n\n## State Machine Pattern\n\nOperations that may yield use explicit state enums:\n\n```rust\nenum MyOperationState {\n    Start,\n    WaitingForRead { page: PageRef },\n    Processing { data: Vec<u8> },\n    Done,\n}\n```\n\nThe function loops, matching on state and transitioning:\n\n```rust\nfn my_operation(&mut self) -> Result<IOResult<Output>> {\n    loop {\n        match &mut self.state {\n            MyOperationState::Start => {\n                let (page, completion) = start_read();\n                self.state = MyOperationState::WaitingForRead { page };\n                io_yield_one!(completion);\n            }\n            MyOperationState::WaitingForRead { page } => {\n                let data = page.get_contents();\n                self.state = MyOperationState::Processing { data: data.to_vec() };\n                // No yield, continue loop\n            }\n            MyOperationState::Processing { data } => {\n                let result = process(data);\n                self.state = MyOperationState::Done;\n                return Ok(IOResult::Done(result));\n            }\n            MyOperationState::Done => unreachable!(),\n        }\n    }\n}\n```\n\n## Re-Entrancy: The Critical Pitfall\n\n**State mutations before yield points cause bugs on re-entry.**\n\n### Wrong\n```rust\nfn bad_example(&mut self) -> Result<IOResult<()>> {\n    self.counter += 1;  // Mutates state\n    return_if_io!(something_that_might_yield());  // If yields, re-entry will increment again!\n    Ok(IOResult::Done(()))\n}\n```\n\nIf `something_that_might_yield()` returns `IO`, caller waits for completion, then calls `bad_example()` again. `counter` gets incremented twice (or more).\n\n### Correct: Mutate After Yield\n```rust\nfn good_example(&mut self) -> Result<IOResult<()>> {\n    return_if_io!(something_that_might_yield());\n    self.counter += 1;  // Only reached once, after IO completes\n    Ok(IOResult::Done(()))\n}\n```\n\n### Correct: Use State Machine\n```rust\nenum State { Start, AfterIO }\n\nfn good_example(&mut self) -> Result<IOResult<()>> {\n    loop {\n        match self.state {\n            State::Start => {\n                // Don't mutate shared state here\n                self.state = State::AfterIO;\n                return_if_io!(something_that_might_yield());\n            }\n            State::AfterIO => {\n                self.counter += 1;  // Safe: only entered once\n                return Ok(IOResult::Done(()));\n            }\n        }\n    }\n}\n```\n\n## Common Re-Entrancy Bugs\n\n| Pattern | Problem |\n|---------|---------|\n| `vec.push(x); return_if_io!(...)` | Vec grows on each re-entry |\n| `idx += 1; return_if_io!(...)` | Index advances multiple times |\n| `map.insert(k,v); return_if_io!(...)` | Duplicate inserts or overwrites |\n| `flag = true; return_if_io!(...)` | Usually ok, but check logic |\n\n## State Enum Design\n\nEncode progress in state variants:\n\n```rust\n// Good: index is part of state, preserved across yields\nenum ProcessState {\n    Start,\n    ProcessingItem { idx: usize, items: Vec<Item> },\n    Done,\n}\n\n// Loop advances idx only when transitioning states\nProcessingItem { idx, items } => {\n    return_if_io!(process_item(&items[idx]));\n    if idx + 1 < items.len() {\n        self.state = ProcessingItem { idx: idx + 1, items };\n    } else {\n        self.state = Done;\n    }\n}\n```\n\n## Turso Implementation\n\nKey files:\n- `core/types.rs` - `IOResult`, `IOCompletions`, `return_if_io!`, `return_and_restore_if_io!`\n- `core/io/completions.rs` - `Completion`, `CompletionGroup`\n- `core/util.rs` - `io_yield_one!` macro\n- `core/state_machine.rs` - Generic `StateMachine` wrapper\n- `core/storage/btree.rs` - Many state machine examples\n- `core/storage/pager.rs` - `CompletionGroup` usage examples\n\n## Testing Async Code\n\nRe-entrancy bugs often only manifest under specific IO timing. Use:\n- Deterministic simulation (`testing/simulator/`)\n- Whopper concurrent DST (`testing/concurrent-simulator/`)\n- Fault injection to force yields at different points\n\n## References\n\n- `docs/manual.md` section on I/O\n"
  },
  {
    "path": ".claude/skills/cdc/SKILL.md",
    "content": "---\nname: cdc\ndescription: Change Data Capture - architecture, entrypoints, bytecode emission, sync engine integration, tests\n---\n# CDC (Change Data Capture) - Internal Feature Map\n\n## Overview\n\nCDC tracks INSERT/UPDATE/DELETE changes on database tables by writing change records into a\ndedicated CDC table (`turso_cdc` by default). It is per-connection, enabled via PRAGMA, and\noperates at the bytecode generation (translate) layer. The sync engine consumes CDC records\nto push local changes to the remote.\n\n## Architecture Diagram\n\n```\nUser SQL (INSERT/UPDATE/DELETE/DDL)\n        |\n        v\n  ┌─────────────────────────────────────────────────┐\n  │  Translate layer (core/translate/)              │\n  │  ┌───────────────────────────────────────────┐  │\n  │  │ prepare_cdc_if_necessary()                │  │\n  │  │   - checks CaptureDataChangesInfo         │  │\n  │  │   - opens CDC table cursor (OpenWrite)    │  │\n  │  │   - skips if target == CDC table itself   │  │\n  │  └───────────────────────────────────────────┘  │\n  │  ┌───────────────────────────────────────────┐  │\n  │  │ emit_cdc_insns()                          │  │\n  │  │   - writes (change_id, change_time,       │  │\n  │  │     change_type, table_name, id,          │  │\n  │  │     before, after, updates) into CDC tbl  │  │\n  │  └───────────────────────────────────────────┘  │\n  │  + emit_cdc_full_record() / emit_cdc_patch_record() │\n  └─────────────────────────────────────────────────┘\n        |\n        v\n  CDC table (turso_cdc or custom name)\n        |\n        v\n  ┌─────────────────────────────────────────────────┐\n  │  Sync engine (sync/engine/)                     │\n  │  DatabaseTape reads CDC table → DatabaseChange  │\n  │  → apply/revert → push to remote                 │\n  └─────────────────────────────────────────────────┘\n```\n\n## Core Data Types\n\n### `CaptureDataChangesMode` + `CaptureDataChangesInfo` — `core/lib.rs`\n\nCDC behavior is controlled by two types:\n\n```rust\n#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]\n#[repr(u8)]\nenum CdcVersion {\n    V1 = 1,\n    V2 = 2,\n}\n\nconst CDC_VERSION_CURRENT: CdcVersion = CdcVersion::V2;\n\nenum CaptureDataChangesMode {\n    Id,          // capture only rowid\n    Before,      // capture before-image\n    After,       // capture after-image\n    Full,        // before + after + updates\n}\n\nstruct CaptureDataChangesInfo {\n    mode: CaptureDataChangesMode,\n    table: String,                  // CDC table name\n    version: Option<CdcVersion>,    // schema version (V1 or V2)\n}\n```\n\nThe connection stores `Option<CaptureDataChangesInfo>` — `None` means CDC is off.\n\nKey methods on `CdcVersion`:\n- `has_commit_record()` — `self >= V2`, gates COMMIT record emission\n- `Display`/`FromStr` — round-trips `\"v1\"` ↔ `V1`, `\"v2\"` ↔ `V2`\n\nKey methods on `CaptureDataChangesInfo`:\n- `parse(value: &str, version: Option<CdcVersion>)` — parses PRAGMA argument `\"<mode>[,<table_name>]\"`, returns `None` for \"off\"\n- `cdc_version()` — returns `CdcVersion` (panics if version is None). Single accessor replacing old `is_v1()`/`is_v2()`/`version()` methods.\n- `has_before()` / `has_after()` / `has_updates()` — mode capability checks\n- `mode_name()` — returns mode as string\n\nConvenience trait `CaptureDataChangesExt` on `Option<CaptureDataChangesInfo>` provides:\n- `has_before()` / `has_after()` / `has_updates()` — delegates to inner, returns false for None\n- `table()` — returns `Option<&str>`, None when CDC is off\n\n### CDC Table Schema v1\n\nDefault table name: `turso_cdc` (constant `TURSO_CDC_DEFAULT_TABLE_NAME`)\n\n```sql\nCREATE TABLE turso_cdc (\n    change_id   INTEGER PRIMARY KEY AUTOINCREMENT,\n    change_time INTEGER,        -- unixepoch()\n    change_type INTEGER,        -- 1=INSERT, 0=UPDATE, -1=DELETE\n    table_name  TEXT,\n    id          <untyped>,      -- rowid of changed row\n    before      BLOB,           -- binary record (before-image)\n    after       BLOB,           -- binary record (after-image)\n    updates     BLOB            -- binary record of per-column changes\n);\n```\n\n### CDC Table Schema v2 (current)\n\n```sql\nCREATE TABLE turso_cdc (\n    change_id    INTEGER PRIMARY KEY AUTOINCREMENT,\n    change_time  INTEGER,        -- unixepoch()\n    change_type  INTEGER,        -- 1=INSERT, 0=UPDATE, -1=DELETE, 2=COMMIT\n    table_name   TEXT,\n    id           <untyped>,      -- rowid of changed row\n    before       BLOB,           -- binary record (before-image)\n    after        BLOB,           -- binary record (after-image)\n    updates      BLOB,           -- binary record of per-column changes\n    change_txn_id INTEGER        -- transaction ID (groups rows into transactions)\n);\n```\n\nv2 adds:\n- `change_txn_id` column — groups CDC rows by transaction. Assigned via `conn_txn_id(candidate)` opcode which get-or-sets a per-connection transaction ID.\n- `change_type=2` (COMMIT) records — mark transaction boundaries. Emitted once per statement in autocommit mode, or on explicit `COMMIT`.\n\nThe CDC table is created at runtime by the `InitCdcVersion` opcode via `CREATE TABLE IF NOT EXISTS`.\n\n### CDC Version Table\n\nWhen CDC is first enabled, a version tracking table is created:\n\n```sql\nCREATE TABLE turso_cdc_version (\n    table_name TEXT PRIMARY KEY,\n    version TEXT NOT NULL\n);\n```\n\nCurrent version: `CDC_VERSION_CURRENT = CdcVersion::V2` (defined in `core/lib.rs`, re-exported from `core/translate/pragma.rs`)\n\n### Version Detection in InitCdcVersion\n\nThe `InitCdcVersion` opcode detects v1 vs v2 by checking whether the CDC table already exists before creating it:\n- If CDC table already exists but has no version row → v1 (pre-existing table from before version tracking)\n- If CDC table doesn't exist → create with current version (v2)\n- If version row already exists → use that version as-is\n\n### `DatabaseChange` — `sync/engine/src/types.rs:229-249`\n\nSync engine's Rust representation of a CDC row. Has `into_apply()` and `into_revert()` methods\nfor forward/backward replay.\n\n### `OperationMode` — `core/translate/emitter.rs`\n\nUsed by `emit_cdc_insns()` to determine `change_type` value:\n- `INSERT` → 1\n- `UPDATE` / `SELECT` → 0\n- `DELETE` → -1\n- `COMMIT` → 2 (v2 only, emitted by `emit_cdc_commit_insns`)\n\n## Entry Points\n\n### 1. PRAGMA — Enable/Disable CDC\n\n**Set:** `core/translate/pragma.rs`\n- Checks MVCC is not enabled (CDC and MVCC are mutually exclusive)\n- Parses mode string via `CaptureDataChangesInfo::parse()` with `CDC_VERSION_CURRENT`\n- Emits a single `InitCdcVersion` opcode — all CDC setup (table creation, version tracking, state change) happens at execution time\n\n**Get (read current mode):** `core/translate/pragma.rs`\n- Returns 3 columns: `mode`, `table`, `version`\n- When off: returns `(\"off\", NULL, NULL)`\n- When active: returns `(mode_name, table, version)`\n\n**Pragma registration:** `core/pragma.rs` — `CaptureDataChangesConn` (and deprecated alias `UnstableCaptureDataChangesConn`) with columns `[\"mode\", \"table\", \"version\"]`\n\n### 2. Connection State\n\n**Field:** `core/connection.rs` — `capture_data_changes: RwLock<Option<CaptureDataChangesInfo>>`\n**Getter:** `get_capture_data_changes_info()` — returns read guard\n**Setter:** `set_capture_data_changes_info(opts: Option<CaptureDataChangesInfo>)`\n**Default:** initialized as `None` (CDC off)\n\n### 3. ProgramBuilder Integration\n\n**Field:** `core/vdbe/builder.rs` — `capture_data_changes_info: Option<CaptureDataChangesInfo>`\n**Accessor:** `capture_data_changes_info()` — returns `&Option<CaptureDataChangesInfo>`\n**Passed from:** `core/translate/mod.rs` — read from connection when creating builder\n\n### 4. PrepareContext\n\n**Field:** `core/vdbe/mod.rs` — `capture_data_changes: Option<CaptureDataChangesInfo>`\n**Set from:** `PrepareContext::from_connection()` — clones from `connection.get_capture_data_changes_info()`\n\n### 5. InitCdcVersion Opcode — `core/vdbe/execute.rs`\n\nAlways emitted by PRAGMA SET. Handles all CDC setup at execution time:\n1. For \"off\": stores `None` in `state.pending_cdc_info`, returns early\n2. Checks if CDC table already exists (for v1 backward compatibility)\n3. Creates CDC table (`CREATE TABLE IF NOT EXISTS <cdc_table_name> ...`) — v2 schema with `change_txn_id` column\n4. Creates version table (`CREATE TABLE IF NOT EXISTS turso_cdc_version ...`)\n5. Inserts version row: if CDC table pre-existed → \"v1\", otherwise → current version (\"v2\"). Uses `INSERT OR IGNORE` to preserve existing version rows.\n6. Reads back actual version from the table\n7. Stores computed `CaptureDataChangesInfo` in `state.pending_cdc_info`\n\nThe connection's CDC state is **not applied in the opcode**. Instead, `pending_cdc_info` is applied in `halt()` only after the transaction commits successfully. This ensures atomicity: if any step fails and the transaction rolls back, the connection's CDC state remains unchanged.\n\nAll table creation is done via nested `conn.prepare()`/`run_ignore_rows()` calls rather than bytecode emission, because the PRAGMA plan can't contain DML against tables that don't exist yet in the schema.\n\n## Bytecode Emission (core/translate/emitter.rs)\n\nThese are the core CDC code generation functions:\n\n| Function | Purpose |\n|----------|---------|\n| `prepare_cdc_if_necessary()` | Opens CDC table cursor if CDC is active and target != CDC table |\n| `emit_cdc_full_record()` | Reads all columns from cursor into a MakeRecord (for before/after image) |\n| `emit_cdc_patch_record()` | Builds record from in-flight register values (for after-image of INSERT/UPDATE) |\n| `emit_cdc_insns()` | Writes a single CDC row per changed row (INSERT/UPDATE/DELETE). Called per-row inside DML loops. |\n| `emit_cdc_commit_insns()` | Writes a COMMIT record (change_type=2) into CDC table (v2 only). Raw emission, no autocommit check. |\n| `emit_cdc_autocommit_commit()` | End-of-statement COMMIT emission. Checks `is_autocommit()` at runtime — only emits COMMIT if in autocommit mode. v2 only. |\n\n### COMMIT Emission Strategy (v2)\n\nPer-row call sites use `emit_cdc_insns()` (no COMMIT). End-of-statement sites call `emit_cdc_autocommit_commit()` which checks `is_autocommit()` at runtime:\n- **Autocommit mode:** emits a COMMIT record after the statement completes\n- **Explicit transaction (`BEGIN...COMMIT`):** skips per-statement COMMIT; the explicit `COMMIT` statement emits the COMMIT record via `emit_cdc_commit_insns()`\n\nThis ensures multi-row statements like `INSERT INTO t VALUES (1),(2),(3)` produce one COMMIT at the end, not one per row.\n\n## Integration Points — Where CDC Records Are Emitted\n\n### INSERT — `core/translate/insert.rs`\n- **Per-row:** `emit_cdc_insns()` after insert, and before delete for REPLACE/conflict\n- **End-of-statement:** `emit_cdc_autocommit_commit()` in `emit_epilogue()` after the insert loop\n\n### UPDATE — `core/translate/emitter.rs`\n- **Per-row:** captures before-image, after-image via patch record, emits `emit_cdc_insns()`\n- **End-of-statement:** `emit_cdc_autocommit_commit()` after the update loop\n\n### DELETE — `core/translate/emitter.rs`\n- **Per-row:** captures before-image and emits `emit_cdc_insns()`\n- **End-of-statement:** `emit_cdc_autocommit_commit()` after the delete loop\n\n### UPSERT (ON CONFLICT DO UPDATE) — `core/translate/upsert.rs`\n- **Per-row:** `emit_cdc_insns()` for all three cases: pure insert, update after conflict, replace\n- No end-of-statement COMMIT — upsert shares INSERT's epilogue\n\n### Schema Changes (DDL) — `core/translate/schema.rs`\n- **CREATE TABLE:** `emit_cdc_insns()` (insert into `sqlite_schema`) + `emit_cdc_autocommit_commit()`\n- **DROP TABLE:** `emit_cdc_insns()` per-row in metadata loop + `emit_cdc_autocommit_commit()` after loop\n- **CREATE INDEX:** `emit_cdc_insns()` + `emit_cdc_autocommit_commit()` (`core/translate/schema.rs`)\n- **DROP INDEX:** `emit_cdc_insns()` per-row + `emit_cdc_autocommit_commit()` after loop (`core/translate/index.rs`)\n\nDDL in explicit transactions (`BEGIN; CREATE TABLE t(x); COMMIT`) does NOT emit per-statement COMMIT — the autocommit check prevents it.\n\n### ALTER TABLE — `core/translate/update.rs`\n- Sets `cdc_update_alter_statement` on the update plan when CDC has updates mode\n\n### Views/Triggers — Explicitly excluded\n- `core/translate/view.rs` — passes `None` for CDC cursor\n- `core/translate/trigger.rs` — passes `None` for CDC cursor\n\n### Subqueries — No CDC\n- `core/translate/subquery.rs` — `cdc_cursor_id: None`\n\n## Helper Functions (for reading CDC data)\n\n### `table_columns_json_array(table_name)` — `core/function.rs`, `core/vdbe/execute.rs`\nReturns JSON array of column names for a table. Used to interpret binary records.\n\n### `bin_record_json_object(columns_json, blob)` — `core/function.rs`, `core/vdbe/execute.rs`\nDecodes a binary record (from `before`/`after`/`updates` columns) into a JSON object using column names.\n\n## Sync Engine Integration\n\nThe sync engine is the primary consumer of CDC data.\n\n### DatabaseTape — `sync/engine/src/database_tape.rs`\n- **CDC config:** `DEFAULT_CDC_TABLE_NAME = \"turso_cdc\"`, `DEFAULT_CDC_MODE = \"full\"`\n- **PRAGMA name:** `CDC_PRAGMA_NAME = \"capture_data_changes_conn\"`\n- **Initialization:** `connect()` sets CDC pragma and caches `cdc_version` from `turso_cdc_version` table. Must be called before `iterate_changes()`.\n- **Version caching:** `cdc_version: RwLock<Option<CdcVersion>>` — set by `connect()`, read by `iterate_changes()`. Panics if not set.\n- **Iterator:** `DatabaseChangesIterator` reads CDC table in batches, emits `DatabaseTapeOperation`. For v2, real COMMIT records from the table are emitted. For v1, a synthetic Commit is appended at end of batch. `ignore_schema_changes: true` (default) filters out `sqlite_schema` row changes but not COMMIT records.\n\n### Sync Operations — `sync/engine/src/database_sync_operations.rs`\n- **Change counting:** `SELECT COUNT(*) FROM turso_cdc WHERE change_id > ?`\n\n### Sync Engine — `sync/engine/src/database_sync_engine.rs`\n- **Initialization:** `open_db()` calls `main_tape.connect(coro)` to ensure CDC is set up and version is cached before any `iterate_changes()` calls.\n- During `apply_changes`, checks if CDC table existed, re-creates it after sync\n\n### Replay Generator — `sync/engine/src/database_replay_generator.rs`\n- Requires `updates` column to be populated (full mode)\n\n## Bindings CDC Surface\n\nAll bindings expose `cdc_operations` as part of sync stats:\n\n| Binding | File |\n|---------|------|\n| Python | `bindings/python/src/turso_sync.rs` |\n| JavaScript | `bindings/javascript/sync/src/lib.rs` |\n| JS (generator) | `bindings/javascript/sync/src/generator.rs` |\n| Go | `bindings/go/bindings_sync.go` |\n| React Native | `bindings/react-native/src/types.ts` |\n| SDK Kit (C header) | `sync/sdk-kit/turso_sync.h` |\n| SDK Kit (Rust) | `sync/sdk-kit/src/bindings.rs` |\n\n## Tests\n\n- **Integration tests:** `tests/integration/functions/test_cdc.rs` — covers all modes, CRUD, transactions, schema changes, version table, backward compatibility. Registered in `tests/integration/functions/mod.rs`.\n- **Sync engine tests:** `sync/engine/src/database_tape.rs` — CDC table reads, tape iteration, replay of schema changes.\n- **JS binding tests:** `bindings/javascript/sync/packages/{wasm,native}/promise.test.ts`\n\nRun: `cargo test -- test_cdc` (integration) or `cargo test -p turso_sync_engine -- database_tape` (sync engine).\n\n## User-facing Documentation\n\n- **CLI manual page:** `cli/manuals/cdc.md` — accessible via `.manual cdc` in the REPL\n- **Database manual:** `docs/manual.md` — CDC section linked in TOC\n\n## Key Design Decisions\n\n1. **Per-connection, not per-database.** Each connection has its own CDC mode and can target different tables.\n2. **Bytecode-level implementation.** CDC instructions are emitted alongside the actual DML bytecode during translation — no runtime hooks or triggers.\n3. **Self-exclusion.** Changes to the CDC table and `turso_cdc_version` table are never captured (checked in `prepare_cdc_if_necessary`).\n4. **Schema changes tracked.** DDL operations are recorded as changes to `sqlite_schema` table.\n5. **Binary record format.** Before/after/updates columns use SQLite's MakeRecord format (same as B-tree payload).\n6. **Transaction-aware.** CDC writes happen within the same transaction as the DML, so rollback naturally discards CDC entries.\n7. **Version tracking.** CDC schema version is recorded in `turso_cdc_version` table and carried in `CaptureDataChangesInfo.version` for future schema evolution.\n8. **Atomic PRAGMA.** Connection CDC state is deferred via `pending_cdc_info` in `ProgramState` and applied only at Halt. If the PRAGMA's disk writes fail and the transaction rolls back, the connection state stays unchanged.\n9. **Per-statement COMMIT (v2).** COMMIT records are emitted once per statement (not per row), using `emit_cdc_autocommit_commit()` which checks `is_autocommit()` at runtime. In explicit transactions, only the final `COMMIT` emits a COMMIT CDC record.\n10. **Backward-compatible version detection.** Pre-existing v1 CDC tables (without `turso_cdc_version`) are detected by checking table existence before creation. Existing tables get `CdcVersion::V1` inserted into the version table.\n11. **Typed version enum.** `CdcVersion` enum with `#[repr(u8)]` and `Ord`/`PartialOrd` enables feature gating via integer comparison (`has_commit_record()` = `self >= V2`). `Display`/`FromStr` handles database round-trip.\n12. **CDC and MVCC mutual exclusion.** Enabling CDC when MVCC is active (or vice versa) returns an error. Checked at PRAGMA set time and journal mode switch time.\n"
  },
  {
    "path": ".claude/skills/code-quality/SKILL.md",
    "content": "---\nname: code-quality\ndescription: General Correctness rules, Rust patterns, comments, avoiding over-engineering. When writing code always take these into account\n---\n# Code Quality Guide\n\n## Core Principle\n\nProduction database. Correctness paramount. Crash > corrupt.\n\n## Correctness Rules\n\n1. **No workarounds or quick hacks.** Handle all errors, check invariants\n2. **Assert often.** Never silently fail or swallow edge cases\n3. **Crash on invalid state** if it risks data integrity. Don't continue in undefined state\n4. **Consider edge cases.** On long enough timeline, all possible bugs will happen\n\n## Rust Patterns\n\n- Make illegal states unrepresentable\n- Exhaustive pattern matching\n- Prefer enums over strings/sentinels\n- Minimize heap allocations\n- Write CPU-friendly code (microsecond = long time)\n\n## If-Statements\n\nWrong:\n```rust\nif condition {\n    // happy path\n} else {\n    // \"shouldn't happen\" - silently ignored\n}\n```\n\nRight:\n```rust\n// If only one branch should ever be hit:\nassert!(condition, \"invariant violated: ...\");\n// OR\nreturn Err(LimboError::InternalError(\"unexpected state\".into()));\n// OR\nunreachable!(\"impossible state: ...\");\n```\n\nUse if-statements only when both branches are expected paths.\n\n## Comments\n\n**Do:**\n- Document WHY, not what\n- Document functions, structs, enums, variants\n- Focus on why something is necessary\n\n**Don't:**\n- Comments that repeat code\n- References to AI conversations (\"This test should trigger the bug\")\n- Temporal markers (\"added\", \"existing code\", \"Phase 1\")\n\n## Avoid Over-Engineering\n\n- Only changes directly requested or clearly necessary\n- Don't add features beyond what's asked\n- Don't add docstrings/comments to unchanged code\n- Don't add error handling for impossible scenarios\n- Don't create abstractions for one-time operations\n- Three similar lines > premature abstraction\n\n## Index Mutations\n\nWhen code involves index inserts, deletes, or conflict resolution, double-check the ordering against SQLite. Wrong ordering causes index inconsistencies. and easy to miss.\n\n## Ensure understanding of IO model\n\n- [Async IO model](../async-io-model/SKILL.md)\n\n## Cleanup\n\n- Delete unused code completely\n- No backwards-compat hacks (renamed `_vars`, re-exports, `// removed` comments)\n"
  },
  {
    "path": ".claude/skills/debugging/SKILL.md",
    "content": "---\nname: debugging\ndescription: How to debug tursodb using Bytecode comparison, logging, ThreadSanitizer, deterministic simulation, and corruption analysis tools\n---\n# Debugging Guide\n\n## Bytecode Comparison Flow\n\nTurso aims for SQLite compatibility. When behavior differs:\n\n```\n1. EXPLAIN query in sqlite3\n2. EXPLAIN query in tursodb\n3. Compare bytecode\n   ├─ Different → bug in code generation\n   └─ Same but results differ → bug in VM or storage layer\n```\n\n### Example\n\n```bash\n# SQLite\nsqlite3 :memory: \"EXPLAIN SELECT 1 + 1;\"\n\n# Turso\ncargo run --bin tursodb :memory: \"EXPLAIN SELECT 1 + 1;\"\n```\n\n## Manual Query Inspection\n\n```bash\ncargo run --bin tursodb :memory: 'SELECT * FROM foo;'\ncargo run --bin tursodb :memory: 'EXPLAIN SELECT * FROM foo;'\n```\n\n## Logging\n\n```bash\n# Trace core during tests\nRUST_LOG=none,turso_core=trace make test\n\n# Output goes to testing/test.log\n# Warning: can be megabytes per test run\n```\n\n## Threading Issues\n\nUse stress tests with ThreadSanitizer:\n\n```bash\nrustup toolchain install nightly\nrustup override set nightly\ncargo run -Zbuild-std --target x86_64-unknown-linux-gnu \\\n  -p turso_stress -- --vfs syscall --nr-threads 4 --nr-iterations 1000\n```\n\n## Deterministic Simulation\n\nReproduce bugs with seed. Note: simulator uses legacy \"limbo\" naming.\n\n```bash\n# Simulator\nRUST_LOG=limbo_sim=debug cargo run --bin limbo_sim -- -s <seed>\n\n# Whopper (concurrent DST)\nSEED=1234 ./testing/concurrent-simulator/bin/run\n```\n\n## Architecture Reference\n\n- **Parser** → AST from SQL strings\n- **Code generator** → bytecode from AST\n- **Virtual machine** → executes SQLite-compatible bytecode\n- **Storage layer** → B-tree operations, paging\n\n## Corruption Debugging\n\nFor WAL corruption and database integrity issues, use the corruption debug tools in [scripts](./scripts).\n\nSee [references/CORRUPTION-TOOLS.md](./references/CORRUPTION-TOOLS.md) for detailed usage.\n"
  },
  {
    "path": ".claude/skills/differential-fuzzer/SKILL.md",
    "content": "---\nname: differential-fuzzer\ndescription: Information about the differential fuzzer tool, how to run it and use it catch bugs in Turso. Always load this skill when running this tool\n---\n\n# Differential Fuzzer\n\nAlways load [Debugging skill for reference](../debugging/)\n\nThe differential fuzzer compares Turso results against SQLite for generated SQL statements to find correctness bugs.\n\n## Location\n\n`testing/differential-oracle/fuzzer/`\n\n## Running the Fuzzer\n\n### Single Run\n\n```bash\n# Basic run (100 statements, random seed)\ncargo run --bin differential_fuzzer\n\n# With specific seed for reproducibility\ncargo run --bin differential_fuzzer -- --seed 12345\n\n# More statements with verbose output\ncargo run --bin differential_fuzzer -- -n 1000 --verbose\n\n# Keep database files after run (for debugging)\ncargo run --bin differential_fuzzer -- --seed 12345 --keep-files\n\n# All options\ncargo run --bin differential_fuzzer -- \\\n  --seed <SEED>           # Deterministic seed\n  -n <NUM>                # Number of statements (default: 100)\n  -t <NUM>                # Number of tables (default: 2)\n  -c <NUM>                # Columns per table (default: 5)\n  --verbose               # Print each SQL statement\n  --keep-files            # Persist .db files to disk\n```\n\n### Continuous Fuzzing (Loop Mode)\n\n```bash\n# Run forever with random seeds\ncargo run --bin differential_fuzzer -- loop\n\n# Run 50 iterations\ncargo run --bin differential_fuzzer -- loop 50\n```\n\n### Docker Runner (CI/Production)\n\n```bash\n# Build and run from repo root\ndocker build -f testing/differential-oracle/fuzzer/docker-runner/Dockerfile -t fuzzer .\ndocker run -e GITHUB_TOKEN=xxx -e SLACK_WEBHOOK_URL=xxx fuzzer\n```\n\nEnvironment variables for docker-runner:\n- `TIME_LIMIT_MINUTES` - Total runtime (default: 1440 = 24h)\n- `PER_RUN_TIMEOUT_SECONDS` - Per-run timeout (default: 1200 = 20min)\n- `NUM_STATEMENTS` - Statements per run (default: 1000)\n- `LOG_TO_STDOUT` - Print fuzzer output (default: false)\n- `GITHUB_TOKEN` - For auto-filing issues\n- `SLACK_WEBHOOK_URL` - For notifications\n\n## Output Files\n\nAll output goes to `simulator-output/` directory:\n\n| File | Description |\n|------|-------------|\n| `test.sql` | All executed SQL statements. Failed statements prefixed with `-- FAILED:`, errors with `-- ERROR:` |\n| `schema.json` | Database schema at end of run (or at failure) |\n| `test.db` | Turso database file (only with `--keep-files`) |\n| `test-sqlite.db` | SQLite database file (only with `--keep-files`) |\n\n## Reproducing Errors\n\nAlways follow these steps\n\n1. **Find the seed** in the error output:\n   ```\n   INFO: Starting differential_fuzzer with config: SimConfig { seed: 12345, ... }\n   ```\n\n2. **Re-run with that seed**:\n   ```bash\n   cargo run --bin differential_fuzzer -- --seed 12345 --verbose --keep-files\n   ```\n\n3. **Check output files**:\n   - `simulator-output/test.sql` - Find the failing statement (look for `-- FAILED:`)\n   - `simulator-output/schema.json` - Check table structure at failure time\n\n4. **Create a minimal reproducer**\n   - Create reproducer in `.sqltest` or in `.rs` always load [Debugging skill for reference](../debugging/)\n\n5. **Compare behavior manually**:\n   If needed try to compare the behaviour and produce a report in the end.\n   Always write to a tmp file first with Edit tool to test the sql and then pass it to the binaries.\n   ```bash\n   # Run failing SQL against SQLite\n   sqlite3 :memory: < simulator-output/test.sql\n\n   # Run against tursodb CLI\n   tursodb :memory: < simulator-output/test.sql\n   ```\n\n## Understanding Failures\n\n### Oracle Failure Types\n\n1. **Row set mismatch** - Turso returned different rows than SQLite\n2. **Turso errored but SQLite succeeded** - Turso rejected valid SQL\n3. **SQLite errored but Turso succeeded** - Turso accepted invalid SQL\n4. **Schema mismatch** - Tables/columns differ after DDL\n\n### Warning (non-fatal)\n\n- **Unordered LIMIT mismatch** - LIMIT without ORDER BY may return different valid rows\n\n## Key Source Files\n\n| File | Purpose |\n|------|---------|\n| `main.rs` | CLI parsing, entry point |\n| `runner.rs` | Main simulation loop, executes statements on both DBs |\n| `oracle.rs` | Compares Turso vs SQLite results |\n| `schema.rs` | Introspects schema from both databases |\n| `memory/` | In-memory IO for deterministic simulation |\n\n## Tracing\n\nSet `RUST_LOG` for more detailed output:\n\n```bash\nRUST_LOG=debug cargo run --bin differential_fuzzer -- --seed 12345\n```\n"
  },
  {
    "path": ".claude/skills/index-knowledge/SKILL.md",
    "content": "---\nname: index-knowledge\ndescription: Generate hierarchical AGENTS.md knowledge base for a codebase. Creates root + complexity-scored subdirectory documentation.\n---\n\n# index-knowledge\n\nGenerate hierarchical AGENTS.md files. Root + complexity-scored subdirectories.\n\n## Usage\n\n```\n--create-new   # Read existing → remove all → regenerate from scratch\n--max-depth=2  # Limit directory depth (default: 5)\n```\n\nDefault: Update mode (modify existing + create new where warranted)\n\n---\n\n## Workflow (High-Level)\n\n1. **Discovery + Analysis** (concurrent)\n   - Launch parallel explore agents (multiple Task calls in one message)\n   - Main session: bash structure + LSP codemap + read existing AGENTS.md\n2. **Score & Decide** - Determine AGENTS.md locations from merged findings\n3. **Generate** - Root first, then subdirs in parallel\n4. **Review** - Deduplicate, trim, validate\n\n<critical>\n**TodoWrite ALL phases. Mark in_progress → completed in real-time.**\n  \n```\nTodoWrite([\n  { id: \"discovery\", content: \"Fire explore agents + LSP codemap + read existing\", status: \"pending\", priority: \"high\" },\n  { id: \"scoring\", content: \"Score directories, determine locations\", status: \"pending\", priority: \"high\" },\n  { id: \"generate\", content: \"Generate AGENTS.md files (root + subdirs)\", status: \"pending\", priority: \"high\" },\n  { id: \"review\", content: \"Deduplicate, validate, trim\", status: \"pending\", priority: \"medium\" }\n])\n```\n</critical>\n\n---\n\n## Phase 1: Discovery + Analysis (Concurrent)\n\n**Mark \"discovery\" as in_progress.**\n\n### Launch Parallel Explore Agents\n\nMultiple Task calls in a single message execute in parallel. Results return directly.\n\n```\n// All Task calls in ONE message = parallel execution\n\nTask(\n  description=\"project structure\",\n  subagent_type=\"explore\",\n  prompt=\"Project structure: PREDICT standard patterns for detected language → REPORT deviations only\"\n)\n\nTask(\n  description=\"entry points\",\n  subagent_type=\"explore\",\n  prompt=\"Entry points: FIND main files → REPORT non-standard organization\"\n)\n\nTask(\n  description=\"conventions\",\n  subagent_type=\"explore\",\n  prompt=\"Conventions: FIND config files (.eslintrc, pyproject.toml, .editorconfig) → REPORT project-specific rules\"\n)\n\nTask(\n  description=\"anti-patterns\",\n  subagent_type=\"explore\",\n  prompt=\"Anti-patterns: FIND 'DO NOT', 'NEVER', 'ALWAYS', 'DEPRECATED' comments → LIST forbidden patterns\"\n)\n\nTask(\n  description=\"build/ci\",\n  subagent_type=\"explore\",\n  prompt=\"Build/CI: FIND .github/workflows, Makefile → REPORT non-standard patterns\"\n)\n\nTask(\n  description=\"test patterns\",\n  subagent_type=\"explore\",\n  prompt=\"Test patterns: FIND test configs, test structure → REPORT unique conventions\"\n)\n```\n\n<dynamic-agents>\n**DYNAMIC AGENT SPAWNING**: After bash analysis, spawn ADDITIONAL explore agents based on project scale:\n\n| Factor | Threshold | Additional Agents |\n|--------|-----------|-------------------|\n| **Total files** | >100 | +1 per 100 files |\n| **Total lines** | >10k | +1 per 10k lines |\n| **Directory depth** | ≥4 | +2 for deep exploration |\n| **Large files (>500 lines)** | >10 files | +1 for complexity hotspots |\n| **Monorepo** | detected | +1 per package/workspace |\n| **Multiple languages** | >1 | +1 per language |\n\n```bash\n# Measure project scale first\ntotal_files=$(find . -type f -not -path '*/node_modules/*' -not -path '*/.git/*' | wc -l)\ntotal_lines=$(find . -type f \\( -name \"*.ts\" -o -name \"*.py\" -o -name \"*.go\" \\) -not -path '*/node_modules/*' -exec wc -l {} + 2>/dev/null | tail -1 | awk '{print $1}')\nlarge_files=$(find . -type f \\( -name \"*.ts\" -o -name \"*.py\" \\) -not -path '*/node_modules/*' -exec wc -l {} + 2>/dev/null | awk '$1 > 500 {count++} END {print count+0}')\nmax_depth=$(find . -type d -not -path '*/node_modules/*' -not -path '*/.git/*' | awk -F/ '{print NF}' | sort -rn | head -1)\n```\n\nExample spawning (all in ONE message for parallel execution):\n```\n// 500 files, 50k lines, depth 6, 15 large files → spawn additional agents\nTask(\n  description=\"large files\",\n  subagent_type=\"explore\",\n  prompt=\"Large file analysis: FIND files >500 lines, REPORT complexity hotspots\"\n)\n\nTask(\n  description=\"deep modules\",\n  subagent_type=\"explore\",\n  prompt=\"Deep modules at depth 4+: FIND hidden patterns, internal conventions\"\n)\n\nTask(\n  description=\"cross-cutting\",\n  subagent_type=\"explore\",\n  prompt=\"Cross-cutting concerns: FIND shared utilities across directories\"\n)\n// ... more based on calculation\n```\n</dynamic-agents>\n\n### Main Session: Concurrent Analysis\n\n**While Task agents execute**, main session does:\n\n#### 1. Bash Structural Analysis\n```bash\n# Directory depth + file counts\nfind . -type d -not -path '*/\\.*' -not -path '*/node_modules/*' -not -path '*/venv/*' -not -path '*/dist/*' -not -path '*/build/*' | awk -F/ '{print NF-1}' | sort -n | uniq -c\n\n# Files per directory (top 30)\nfind . -type f -not -path '*/\\.*' -not -path '*/node_modules/*' | sed 's|/[^/]*$||' | sort | uniq -c | sort -rn | head -30\n\n# Code concentration by extension\nfind . -type f \\( -name \"*.py\" -o -name \"*.ts\" -o -name \"*.tsx\" -o -name \"*.js\" -o -name \"*.go\" -o -name \"*.rs\" \\) -not -path '*/node_modules/*' | sed 's|/[^/]*$||' | sort | uniq -c | sort -rn | head -20\n\n# Existing AGENTS.md / CLAUDE.md\nfind . -type f \\( -name \"AGENTS.md\" -o -name \"CLAUDE.md\" \\) -not -path '*/node_modules/*' 2>/dev/null\n```\n\n#### 2. Read Existing AGENTS.md\n```\nFor each existing file found:\n  Read(filePath=file)\n  Extract: key insights, conventions, anti-patterns\n  Store in EXISTING_AGENTS map\n```\n\nIf `--create-new`: Read all existing first (preserve context) → then delete all → regenerate.\n\n#### 3. LSP Codemap (if available)\n```\nlsp_servers()  # Check availability\n\n# Entry points (parallel)\nlsp_document_symbols(filePath=\"src/index.ts\")\nlsp_document_symbols(filePath=\"main.py\")\n\n# Key symbols (parallel)\nlsp_workspace_symbols(filePath=\".\", query=\"class\")\nlsp_workspace_symbols(filePath=\".\", query=\"interface\")\nlsp_workspace_symbols(filePath=\".\", query=\"function\")\n\n# Centrality for top exports\nlsp_find_references(filePath=\"...\", line=X, character=Y)\n```\n\n**LSP Fallback**: If unavailable, rely on explore agents + AST-grep.\n\n**Merge: bash + LSP + existing + Task agent results. Mark \"discovery\" as completed.**\n\n---\n\n## Phase 2: Scoring & Location Decision\n\n**Mark \"scoring\" as in_progress.**\n\n### Scoring Matrix\n\n| Factor | Weight | High Threshold | Source |\n|--------|--------|----------------|--------|\n| File count | 3x | >20 | bash |\n| Subdir count | 2x | >5 | bash |\n| Code ratio | 2x | >70% | bash |\n| Unique patterns | 1x | Has own config | explore |\n| Module boundary | 2x | Has index.ts/__init__.py | bash |\n| Symbol density | 2x | >30 symbols | LSP |\n| Export count | 2x | >10 exports | LSP |\n| Reference centrality | 3x | >20 refs | LSP |\n\n### Decision Rules\n\n| Score | Action |\n|-------|--------|\n| **Root (.)** | ALWAYS create |\n| **>15** | Create AGENTS.md |\n| **8-15** | Create if distinct domain |\n| **<8** | Skip (parent covers) |\n\n### Output\n```\nAGENTS_LOCATIONS = [\n  { path: \".\", type: \"root\" },\n  { path: \"src/hooks\", score: 18, reason: \"high complexity\" },\n  { path: \"src/api\", score: 12, reason: \"distinct domain\" }\n]\n```\n\n**Mark \"scoring\" as completed.**\n\n---\n\n## Phase 3: Generate AGENTS.md\n\n**Mark \"generate\" as in_progress.**\n\n### Root AGENTS.md (Full Treatment)\n\n```markdown\n# PROJECT KNOWLEDGE BASE\n\n**Generated:** {TIMESTAMP}\n**Commit:** {SHORT_SHA}\n**Branch:** {BRANCH}\n\n## OVERVIEW\n{1-2 sentences: what + core stack}\n\n## STRUCTURE\n\\`\\`\\`\n{root}/\n├── {dir}/    # {non-obvious purpose only}\n└── {entry}\n\\`\\`\\`\n\n## WHERE TO LOOK\n| Task | Location | Notes |\n|------|----------|-------|\n\n## CODE MAP\n{From LSP - skip if unavailable or project <10 files}\n\n| Symbol | Type | Location | Refs | Role |\n\n## CONVENTIONS\n{ONLY deviations from standard}\n\n## ANTI-PATTERNS (THIS PROJECT)\n{Explicitly forbidden here}\n\n## UNIQUE STYLES\n{Project-specific}\n\n## COMMANDS\n\\`\\`\\`bash\n{dev/test/build}\n\\`\\`\\`\n\n## NOTES\n{Gotchas}\n```\n\n**Quality gates**: 50-150 lines, no generic advice, no obvious info.\n\n### Subdirectory AGENTS.md (Parallel)\n\nLaunch general agents for each location in ONE message (parallel execution):\n\n```\n// All in single message = parallel\nTask(\n  description=\"AGENTS.md for src/hooks\",\n  subagent_type=\"general\",\n  prompt=\"Generate AGENTS.md for: src/hooks\n    - Reason: high complexity\n    - 30-80 lines max\n    - NEVER repeat parent content\n    - Sections: OVERVIEW (1 line), STRUCTURE (if >5 subdirs), WHERE TO LOOK, CONVENTIONS (if different), ANTI-PATTERNS\n    - Write directly to src/hooks/AGENTS.md\"\n)\n\nTask(\n  description=\"AGENTS.md for src/api\",\n  subagent_type=\"general\",\n  prompt=\"Generate AGENTS.md for: src/api\n    - Reason: distinct domain\n    - 30-80 lines max\n    - NEVER repeat parent content\n    - Sections: OVERVIEW (1 line), STRUCTURE (if >5 subdirs), WHERE TO LOOK, CONVENTIONS (if different), ANTI-PATTERNS\n    - Write directly to src/api/AGENTS.md\"\n)\n// ... one Task per AGENTS_LOCATIONS entry\n```\n\n**Results return directly. Mark \"generate\" as completed.**\n\n---\n\n## Phase 4: Review & Deduplicate\n\n**Mark \"review\" as in_progress.**\n\nFor each generated file:\n- Remove generic advice\n- Remove parent duplicates\n- Trim to size limits\n- Verify telegraphic style\n\n**Mark \"review\" as completed.**\n\n---\n\n## Final Report\n\n```\n=== index-knowledge Complete ===\n\nMode: {update | create-new}\n\nFiles:\n  ✓ ./AGENTS.md (root, {N} lines)\n  ✓ ./src/hooks/AGENTS.md ({N} lines)\n\nDirs Analyzed: {N}\nAGENTS.md Created: {N}\nAGENTS.md Updated: {N}\n\nHierarchy:\n  ./AGENTS.md\n  └── src/hooks/AGENTS.md\n```\n\n---\n\n## Anti-Patterns\n\n- **Static agent count**: MUST vary agents based on project size/depth\n- **Sequential execution**: MUST parallel (multiple Task calls in one message)\n- **Ignoring existing**: ALWAYS read existing first, even with --create-new\n- **Over-documenting**: Not every dir needs AGENTS.md\n- **Redundancy**: Child never repeats parent\n- **Generic content**: Remove anything that applies to ALL projects\n- **Verbose style**: Telegraphic or die\n"
  },
  {
    "path": ".claude/skills/mvcc/SKILL.md",
    "content": "---\nname: mvcc\ndescription: Overview of Experimental MVCC feature - snapshot isolation, versioning, limitations\n---\n# MVCC Guide (Experimental)\n\nMulti-Version Concurrency Control. **Work in progress, not production-ready.**\n\n**CRITICAL**: Ignore MVCC when debugging unless the bug is MVCC-specific.\n\n## Enabling MVCC\n\n```sql\nPRAGMA journal_mode = 'mvcc';\n```\n\nRuntime configuration, not a compile-time feature flag. Per-database setting.\n\n## How It Works\n\nStandard WAL: single version per page, readers see snapshot at read mark time.\n\nMVCC: multiple row versions, snapshot isolation. Each transaction sees consistent snapshot at begin time.\n\n### Key Differences from WAL\n\n| Aspect | WAL | MVCC |\n|--------|-----|------|\n| Write granularity | Every commit writes full pages | Affected rows only\n| Readers/Writers | Don't block each other | Don't block each other |\n| Persistence | `.db-wal` | `.db-log` (logical log) |\n| Isolation | Snapshot (page-level) | Snapshot (row-level) |\n\n### Versioning\n\nEach row version tracks:\n- `begin` - timestamp when visible\n- `end` - timestamp when deleted/replaced\n- `btree_resident` - existed before MVCC enabled\n\n## Architecture\n\n```\nDatabase\n  └─ mv_store: MvStore\n      ├─ rows: SkipMap<RowID, Vec<RowVersion>>\n      ├─ txs: SkipMap<TxID, Transaction>\n      ├─ Storage (.db-log file)\n      └─ CheckpointStateMachine\n```\n\n**Per-connection**: `mv_tx` tracks current MVCC transaction.\n\n**Shared**: `MvStore` with lock-free `crossbeam_skiplist` structures.\n\n## Key Files\n\n- `core/mvcc/mod.rs` - Module overview\n- `core/mvcc/database/mod.rs` - Main implementation (~3000 lines)\n- `core/mvcc/cursor.rs` - Merged MVCC + B-tree cursor\n- `core/mvcc/persistent_storage/logical_log.rs` - Disk format\n- `core/mvcc/database/checkpoint_state_machine.rs` - Checkpoint logic\n\n## Checkpointing\n\nFlushes row versions to B-tree periodically.\n\n```sql\nPRAGMA mvcc_checkpoint_threshold = <pages>;\n```\n\nProcess: acquire lock → begin pager txn → write rows → commit → truncate log → fsync → release.\n\n## Current Limitations\n\n**Not implemented:**\n- Garbage collection (old versions accumulate)\n- Recovery from logical log on restart\n\n**Known issues:**\n- Checkpoint blocks other transactions, even reads!\n- No spilling to disk; memory use concerns\n\n## Testing\n\n```bash\n# Run MVCC-specific tests\ncargo test mvcc\n\n# TCL tests with MVCC\nmake test-mvcc\n```\n\nUse `#[turso_macros::test(mvcc)]` attribute for MVCC-enabled tests.\n\n```rust\n#[turso_macros::test(mvcc)]\nfn test_something() {\n    // runs with MVCC enabled\n}\n```\n\n## References\n\n- `core/mvcc/mod.rs` documents data anomalies (dirty reads, lost updates, etc.)\n- Snapshot isolation vs serializability: MVCC provides the former, not the latter\n"
  },
  {
    "path": ".claude/skills/pr-workflow/SKILL.md",
    "content": "---\nname: pr-workflow\ndescription: General guidelines for Commits, formatting, CI, dependencies, security\n---\n# PR Workflow Guide\n\n## Commit Practices\n\n- **Atomic commits.** Small, focused, single purpose\n- **Don't mix:** logic + formatting, logic + refactoring\n- **Good message** = easy to write short description of intent\n\nLearn `git rebase -i` for clean history.\n\n## PR Guidelines\n\n- Keep PRs focused and small\n- Run relevant tests before submitting\n- Each commit tells part of the story\n\n## CI Environment Notes\n\nIf running as GitHub Action:\n- Max-turns limit in `.github/workflows/claude.yml`\n- OK to commit WIP state and push\n- OK to open WIP PR and continue in another action\n- Don't spiral into rabbit holes. Stay focused on key task\n\n## Security\n\nNever commit:\n- `.env` files\n- Credentials\n- Secrets\n\n## Third-Party Dependencies\n\nWhen adding:\n1. Add license file under `licenses/`\n2. Update `NOTICE.md` with dependency info\n\n## External APIs/Tools\n\n- Never guess API params or CLI args\n- Search official docs first\n- Ask for clarification if ambiguous\n"
  },
  {
    "path": ".claude/skills/storage-format/SKILL.md",
    "content": "---\nname: storage-format\ndescription: SQLite file format, B-trees, pages, cells, overflow, freelist that is used in tursodb\n---\n# Storage Format Guide\n\n## Database File Structure\n\n```\n┌─────────────────────────────┐\n│ Page 1: Header + Schema     │  ← First 100 bytes = DB header\n├─────────────────────────────┤\n│ Page 2..N: B-tree pages     │  ← Tables and indexes\n│            Overflow pages   │\n│            Freelist pages   │\n└─────────────────────────────┘\n```\n\nPage size: power of 2, 512-65536 bytes. Default 4096.\n\n## Database Header (First 100 Bytes)\n\n| Offset | Size | Field |\n|--------|------|-------|\n| 0 | 16 | Magic: `\"SQLite format 3\\0\"` |\n| 16 | 2 | Page size (big-endian) |\n| 18 | 1 | Write format version (1=rollback, 2=WAL) |\n| 19 | 1 | Read format version |\n| 24 | 4 | Change counter |\n| 28 | 4 | Database size in pages |\n| 32 | 4 | First freelist trunk page |\n| 36 | 4 | Total freelist pages |\n| 40 | 4 | Schema cookie |\n| 56 | 4 | Text encoding (1=UTF8, 2=UTF16LE, 3=UTF16BE) |\n\nAll multi-byte integers: **big-endian**.\n\n## Page Types\n\n| Flag | Type | Purpose |\n|------|------|---------|\n| 0x02 | Interior index | Index B-tree internal node |\n| 0x05 | Interior table | Table B-tree internal node |\n| 0x0a | Leaf index | Index B-tree leaf |\n| 0x0d | Leaf table | Table B-tree leaf |\n| - | Overflow | Payload exceeding cell capacity |\n| - | Freelist | Unused pages (trunk or leaf) |\n\n## B-tree Structure\n\nTwo B-tree types:\n- **Table B-tree**: 64-bit rowid keys, stores row data\n- **Index B-tree**: Arbitrary keys (index columns + rowid)\n\n```\nInterior page:  [ptr0] key1 [ptr1] key2 [ptr2] ...\n                   │         │         │\n                   ▼         ▼         ▼\n               child     child     child\n               pages     pages     pages\n\nLeaf page:     key1:data  key2:data  key3:data ...\n```\n\nPage 1 always root of `sqlite_schema` table.\n\n## Cell Format\n\n### Table Leaf Cell\n```\n[payload_size: varint] [rowid: varint] [payload] [overflow_ptr: u32?]\n```\n\n### Table Interior Cell\n```\n[left_child_page: u32] [rowid: varint]\n```\n\n### Index Cells\nSimilar but key is arbitrary (columns + rowid), not just rowid.\n\n## Record Format (Payload)\n\n```\n[header_size: varint] [type1: varint] [type2: varint] ... [data1] [data2] ...\n```\n\nSerial types:\n| Type | Meaning |\n|------|---------|\n| 0 | NULL |\n| 1-4 | 1/2/3/4 byte signed int |\n| 5 | 6 byte signed int |\n| 6 | 8 byte signed int |\n| 7 | IEEE 754 float |\n| 8 | Integer 0 |\n| 9 | Integer 1 |\n| ≥12 even | BLOB, length=(N-12)/2 |\n| ≥13 odd | Text, length=(N-13)/2 |\n\n## Overflow Pages\n\nWhen payload exceeds threshold, excess stored in overflow chain:\n```\n[next_page: u32] [data...]\n```\nLast page has next_page=0.\n\n## Freelist\n\nLinked list of trunk pages, each containing leaf page numbers:\n```\nTrunk: [next_trunk: u32] [leaf_count: u32] [leaf_pages: u32...]\n```\n\n## Turso Implementation\n\nKey files:\n- `core/storage/sqlite3_ondisk.rs` - On-disk format, `PageType` enum\n- `core/storage/btree.rs` - B-tree operations (large file)\n- `core/storage/pager.rs` - Page management\n- `core/storage/buffer_pool.rs` - Page caching\n\n## Debugging Storage\n\n```bash\n# Integrity check\ncargo run --bin tursodb test.db \"PRAGMA integrity_check;\"\n\n# Page count\ncargo run --bin tursodb test.db \"PRAGMA page_count;\"\n\n# Freelist info\ncargo run --bin tursodb test.db \"PRAGMA freelist_count;\"\n```\n\n## References\n\n- [SQLite File Format](https://sqlite.org/fileformat.html)\n- [SQLite B-Tree Module](https://sqlite.org/btreemodule.html)\n- [SQLite Internals: Pages & B-trees](https://fly.io/blog/sqlite-internals-btree/)\n"
  },
  {
    "path": ".claude/skills/testing/SKILL.md",
    "content": "---\nname: testing\ndescription: How to write tests, when to use each type of test, and how to run them. Contains information about conversion of `.test` to `.sqltest`, and how to write `.sqltest` and rust tests\n---\n# Testing Guide\n\n## Test Types & When to Use\n\n| Type | Location | Use Case |\n|------|----------|----------|\n| `.sqltest` | `testing/sqltests/tests/` | SQL compatibility. **Preferred for new tests** |\n| TCL `.test` | `testing/` | Legacy SQL compat (being phased out) |\n| Rust integration | `tests/integration/` | Regression tests, complex scenarios |\n| Fuzz | `tests/fuzz/` | Complex features, edge case discovery |\n\n**Note:** TCL tests are being phased out in favor of testing/sqltests. The `.sqltest` format allows the same test cases to run against multiple backends (CLI, Rust bindings, etc.).\n\n## Running Tests\n\n```bash\n# Main test suite (TCL compat, sqlite3 compat, Python wrappers)\nmake test\n\n# Single TCL test\nmake test-single TEST=select.test\n\n# SQL test runner\nmake -C testing/sqltests run-cli\n\n# OR\ncargo run -p test-runner -- run <test-file or directory>\n\n# Rust unit/integration tests (full workspace)\ncargo test\n```\n\n## Writing Tests\n\n### .sqltest (Preferred)\n```\n@database :default:\n\ntest example-addition {\n    SELECT 1 + 1;\n}\nexpect {\n    2\n}\n\ntest example-multiple-rows {\n    SELECT id, name FROM users WHERE id < 3;\n}\nexpect {\n    1|alice\n    2|bob\n}\n```\nLocation: `testing/sqltests/tests/*.sqltest`\n\nYou must start converting TCL tests with the `convert` command from the test runner (e.g `cargo run -- convert <TCL_test_path> -o <out_dir>`). It is not always accurate, but it will convert most of the tests. If some conversion emits a warning you will have to write by hand whatever is missing from it (e.g unroll a for each loop by hand). Then you need to verify the tests work by running them with `make -C testing/sqltests run-rust`, and adjust their output if something was wrong with the conversion. Also, we use harcoded databases in TCL, but with `.sqltest` we generate the database with a different seed, so you will probably need to change the expected test result to match the new database query output. Avoid changing the SQL statements from the test, just change the expected result \n\n### TCL\n```tcl\ndo_execsql_test_on_specific_db {:memory:} test-name {\n  SELECT 1 + 1;\n} {2}\n```\nLocation: `testing/*.test`\n\n### Rust Integration\n```rust\n// tests/integration/test_foo.rs\n#[test]\nfn test_something() {\n    let conn = Connection::open_in_memory().unwrap();\n    // ...\n}\n```\n\n## Key Rules\n\n- Every functional change needs a test\n- Test must fail without change, pass with it\n- Prefer in-memory DBs: `:memory:` (sqltest) or `{:memory:}` (TCL)\n- Don't invent new test formats. Follow existing patterns\n- Write tests first when possible\n\n## Test Database Schema\n\n`testing/system/testing.db` has `users` and `products` tables. See [docs/testing.md](../../../docs/testing.md) for schema.\n\n## Logging During Tests\n\n```bash\nRUST_LOG=none,turso_core=trace make test\n```\nOutput: `testing/system/test.log`. Warning: very verbose.\n"
  },
  {
    "path": ".claude/skills/transaction-correctness/SKILL.md",
    "content": "---\nname: transaction-correctness\ndescription: How WAL mechanics, checkpointing, concurrency rules, recovery work in tursodb\n---\n# Transaction Correctness Guide\n\nTurso uses WAL (Write-Ahead Logging) mode exclusively.\n\nFiles: `.db`, `.db-wal` (no `.db-shm` - Turso uses in-memory WAL index)\n\n## WAL Mechanics\n\n### Write Path\n1. Writer appends frames (page data) to WAL file (sequential I/O)\n2. COMMIT = frame with non-zero db_size in header (marks transaction end)\n3. Original DB unchanged until checkpoint\n\n### Read Path\n1. Reader acquires read mark (mxFrame = last valid commit frame)\n2. For each page: check WAL up to mxFrame, fall back to main DB\n3. Reader sees consistent snapshot at its read mark\n\n### Checkpointing\nTransfers WAL content back to main DB.\n\n```\nWAL grows → checkpoint triggered (default: 1000 pages) → pages copied to DB → WAL reused\n```\n\nCheckpoint types:\n- **PASSIVE**: Non-blocking, stops at pages needed by active readers\n- **FULL**: Waits for readers, checkpoints everything\n- **RESTART**: Like FULL, also resets WAL to beginning\n- **TRUNCATE**: Like RESTART, also truncates WAL file to zero length\n\n### WAL-Index\nSQLite uses a shared memory file (`-shm`) for WAL index. **Turso does not** - it uses in-memory data structures (`frame_cache` hashmap, atomic read marks) since multi-process access is not supported.\n\n## Concurrency Rules\n\n- One writer at a time\n- Readers don't block writer, writer doesn't block readers\n- Checkpoint must stop at pages needed by active readers\n\n## Recovery\n\nOn crash:\n1. First connection acquires exclusive lock\n2. Replays valid commits from WAL\n3. Releases lock, normal operation resumes\n\n## Turso Implementation\n\nKey files:\n- [WAL implementation](../../../core/storage/wal.rs) - WAL implementation\n- [Page management, transactions](../../../core/storage/pager.rs)\n\n### Connection-Private vs Shared\n\n**Per-Connection (private):**\n- `Pager` - page cache, dirty pages, savepoints, commit state\n- `WalFile` - connection's snapshot view:\n  - `max_frame` / `min_frame` - frame range for this connection's snapshot\n  - `max_frame_read_lock_index` - which read lock slot this connection holds\n  - `last_checksum` - rolling checksum state\n\n**Shared across connections:**\n- `WalFileShared` - global WAL state:\n  - `frame_cache` - page-to-frame index (replaces `.shm` file)\n  - `max_frame` / `nbackfills` - global WAL progress\n  - `read_locks[5]` - read mark slots (TursoRwLock with embedded frame values)\n  - `write_lock` - exclusive writer lock\n  - `checkpoint_lock` - checkpoint serialization\n  - `file` - WAL file handle\n- `DatabaseStorage` - main `.db` file\n- `BufferPool` - shared memory allocation\n\n## Correctness Invariants\n\n1. **Durability**: COMMIT record must be fsynced before returning success\n2. **Atomicity**: Partial transactions never visible to readers\n3. **Isolation**: Each reader sees consistent snapshot\n4. **No lost updates**: Checkpoint can't overwrite uncommitted changes\n\n## References\n\n- [SQLite WAL](https://sqlite.org/wal.html)\n- [WAL File Format](https://sqlite.org/walformat.html)\n"
  },
  {
    "path": ".config/nextest.toml",
    "content": "[profile.loom]\n# Filter to only run tests with \"loom_\" in the name\ndefault-filter = 'test(/loom_/)'\n# Loom tests are slow - model checking explores all interleavings\nslow-timeout = { period = \"120s\", terminate-after = 2 }\nfail-fast = false\nfailure-output = \"final\"\ntest-threads = 1\n\n[profile.shuttle]\n# Filter to only run tests containing \"shuttle_\" in the name\ndefault-filter = 'test(/shuttle_/)'\nslow-timeout = { period = \"60s\", terminate-after = 2 }\nfail-fast = false\nfailure-output = \"final\"\ntest-threads = 1\n"
  },
  {
    "path": ".devcontainer/Dockerfile",
    "content": "FROM node:20\n\nARG TZ\nARG CLAUDE_CODE_VERSION\nARG GIT_DELTA_VERSION\nARG ZSH_IN_DOCKER_VERSION\nARG GO_VERSION\nARG RUST_VERSION\nARG PYTHON_VERSION\n\nENV TZ=\"$TZ\"\n\n# Install build dependencies needed for further installations\nRUN apt-get update && apt-get install -y --no-install-recommends \\\n  wget \\\n  curl \\\n  ca-certificates \\\n  build-essential \\\n  pkg-config \\\n  gnupg2 \\\n  python${PYTHON_VERSION}-dev \\\n  sudo \\\n  && apt-get clean && rm -rf /var/lib/apt/lists/*\n\n# Ensure default node user has access to /usr/local/share\nRUN mkdir -p /usr/local/share/npm-global && \\\n  chown -R node:node /usr/local/share\n\nARG USERNAME=node\n\n# Persist bash history.\nRUN SNIPPET=\"export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhistory/.bash_history\" \\\n  && mkdir /commandhistory \\\n  && touch /commandhistory/.bash_history \\\n  && chown -R $USERNAME /commandhistory\n\n# Set `DEVCONTAINER` environment variable to help with orientation\nENV DEVCONTAINER=true\n\n# Create workspace and config directories and set permissions\nRUN mkdir -p /workspace /home/node/.claude && \\\n  chown -R node:node /workspace /home/node/.claude\n\nWORKDIR /workspace\n\nRUN ARCH=$(dpkg --print-architecture) && \\\n  wget \"https://github.com/dandavison/delta/releases/download/${GIT_DELTA_VERSION}/git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb\" && \\\n  sudo dpkg -i \"git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb\" && \\\n  rm \"git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb\"\n\n# Install Go\n\nENV GOROOT=/usr/local/go\nENV GOPATH=/home/node/go\nENV PATH=$PATH:/usr/local/go/bin:/home/node/go/bin\n\nRUN set -eux; \\\n    OS=\"$(uname -s)\"; \\\n    ARCH=\"$(uname -m)\"; \\\n    \\\n    case \"$OS\" in \\\n      Linux) \\\n        GOOS=linux ;; \\\n      Darwin) \\\n        GOOS=darwin ;; \\\n      *) \\\n        echo \"Unsupported OS: $OS\" && exit 1 ;; \\\n    esac; \\\n    \\\n    case \"$ARCH\" in \\\n      x86_64|amd64) \\\n        GOARCH=amd64 ;; \\\n      aarch64|arm64) \\\n        GOARCH=arm64 ;; \\\n      *) \\\n        echo \"Unsupported arch: $ARCH\" && exit 1 ;; \\\n    esac; \\\n    \\\n    curl -fsSL \"https://go.dev/dl/go${GO_VERSION}.${GOOS}-${GOARCH}.tar.gz\" \\\n      | sudo tar -C /usr/local -xz\n\nRUN go install golang.org/x/tools/gopls@latest\nRUN go install honnef.co/go/tools/cmd/staticcheck@latest\nRUN chown -R node:node /home/node/go\n\n# Set up non-root user\nUSER node\n\n# Install Rust\nENV RUSTUP_HOME=/home/node/.rustup \\\n    CARGO_HOME=/home/node/.cargo\nENV PATH=$PATH:/home/node/.cargo/bin\n\nRUN curl https://sh.rustup.rs -sSf | sh -s -- \\\n    -y \\\n    --default-toolchain ${RUST_VERSION} \\\n    --profile minimal \\\n && rustup component add rustfmt clippy\n\n# Install Python\n\nENV UV_HOME=/home/node/.uv\nENV PATH=$PATH:/home/node/.cargo/bin:/home/node/.local/bin\n\nRUN curl -Ls https://astral.sh/uv/install.sh | sh\nRUN uv python install ${PYTHON_VERSION}\n\n# Check installation\n\nRUN rustc --version \\\n && cargo --version \\\n && go version \\\n && uv --version\n\n# Install global packages\nENV NPM_CONFIG_PREFIX=/usr/local/share/npm-global\nENV PATH=$PATH:/usr/local/share/npm-global/bin\n\n# Set the default shell to zsh rather than sh\nENV SHELL=/bin/zsh\n\n# Set the default editor and visual\nENV EDITOR=vim\nENV VISUAL=vim\n\n# Switch back to root to install runtime dependencies\nUSER root\n\n# Install runtime dependencies for final image\nRUN apt-get update && apt-get install -y --no-install-recommends \\\n  traceroute \\\n  iputils-ping \\\n  sqlite3 \\\n  less \\\n  git \\\n  procps \\\n  fzf \\\n  zsh \\\n  man-db \\\n  unzip \\\n  gh \\\n  iptables \\\n  ipset \\\n  iproute2 \\\n  dnsutils \\\n  aggregate \\\n  jq \\\n  nano \\\n  vim \\\n  && apt-get clean && rm -rf /var/lib/apt/lists/*\n\n# Switch back to node user\nUSER node\n\n# Default powerline10k theme\nRUN sh -c \"$(wget -O- https://github.com/deluan/zsh-in-docker/releases/download/v${ZSH_IN_DOCKER_VERSION}/zsh-in-docker.sh)\" -- \\\n  -p git \\\n  -p fzf \\\n  -a \"source /usr/share/doc/fzf/examples/key-bindings.zsh\" \\\n  -a \"source /usr/share/doc/fzf/examples/completion.zsh\" \\\n  -a \"export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhistory/.bash_history\" \\\n  -x\n\n# Install Claude\nRUN npm install -g @anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}\n"
  },
  {
    "path": ".devcontainer/Dockerfile.squid",
    "content": "FROM ubuntu/squid:latest\nRUN apt-get update && apt-get install -y netcat-openbsd && rm -rf /var/lib/apt/lists/*\n"
  },
  {
    "path": ".devcontainer/devcontainer.json",
    "content": "{\n  \"name\": \"tursodb sandbox\",\n  \"dockerComposeFile\": [\"docker-compose.yml\"],\n  \"service\": \"tursodb\",\n  \"remoteUser\": \"node\",\n  \"workspaceFolder\": \"/workspace\",\n  \"waitFor\": \"postStartCommand\",\n  \"customizations\": {\n    \"vscode\": {\n      \"extensions\": [\n        \"anthropic.claude-code\",\n        \"eamodio.gitlens\",\n\n        // Rust\n        \"rust-lang.rust-analyzer\",\n\n        // Go\n        \"golang.Go\",\n\n        // Python\n        \"ms-python.python\",\n        \"ms-python.vscode-pylance\",\n\n        // JavaScript / TypeScript\n        \"dbaeumer.vscode-eslint\",\n        \"esbenp.prettier-vscode\",\n        \"ms-vscode.vscode-typescript-next\"\n      ],\n      \"settings\": {\n        \"editor.formatOnSave\": true,\n        \"editor.defaultFormatter\": \"esbenp.prettier-vscode\",\n        \"editor.codeActionsOnSave\": {\n          \"source.fixAll.eslint\": \"explicit\"\n        },\n        \"terminal.integrated.defaultProfile.linux\": \"zsh\",\n        \"terminal.integrated.profiles.linux\": {\n          \"bash\": {\n            \"path\": \"bash\",\n            \"icon\": \"terminal-bash\"\n          },\n          \"zsh\": {\n            \"path\": \"zsh\"\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": ".devcontainer/docker-compose.yml",
    "content": "version: \"3.9\"\n\nservices:\n  proxy:\n    build:\n      context: .\n      dockerfile: Dockerfile.squid\n    networks: [sandbox, egress]\n    volumes:\n      - ./squid.conf:/etc/squid/squid.conf:ro\n    healthcheck:\n      test: [\"CMD\", \"nc\", \"-z\", \"127.0.0.1\", \"3128\"]\n      interval: 1s\n      timeout: 5s\n      retries: 10\n      start_period: 1s\n  tursodb:\n    depends_on:\n      proxy:\n        condition: service_healthy\n    build:\n      context: .\n      dockerfile: Dockerfile\n      args:\n        TZ: ${TZ:-America/Los_Angeles}\n        CLAUDE_CODE_VERSION: \"latest\"\n        GIT_DELTA_VERSION: \"0.18.2\"\n        ZSH_IN_DOCKER_VERSION: \"1.2.0\"\n        GO_VERSION: \"1.24.10\"\n        RUST_VERSION: \"1.88.0\"\n        PYTHON_VERSION: \"3.11\"\n    environment:\n      NODE_OPTIONS: \"--max-old-space-size=4096\"\n      CLAUDE_CONFIG_DIR: \"/home/node/.claude\"\n      POWERLEVEL9K_DISABLE_GITSTATUS: \"true\"\n      HTTP_PROXY: \"http://proxy:3128\"\n      HTTPS_PROXY: \"http://proxy:3128\"\n      ALL_PROXY: \"http://proxy:3128\"\n      NO_PROXY: \"localhost,127.0.0.1\"\n    networks: [sandbox]\n    volumes:\n      - ..:/workspace:delegated\n      - claude-code-bashhistory:/commandhistory\n      - claude-code-config:/home/node/.claude\n\n    cap_add:\n      - NET_ADMIN\n      - NET_RAW\n\n    init: true\n    tty: true\n\nvolumes:\n  claude-code-bashhistory:\n  claude-code-config:\nnetworks:\n  sandbox:\n    internal: true\n  egress:\n    driver: bridge\n"
  },
  {
    "path": ".devcontainer/init-firewall.sh",
    "content": "#!/bin/bash\nset -euo pipefail  # Exit on error, undefined vars, and pipeline failures\nIFS=$'\\n\\t'       # Stricter word splitting\n\n# 1. Extract Docker DNS info BEFORE any flushing\nDOCKER_DNS_RULES=$(iptables-save -t nat | grep \"127\\.0\\.0\\.11\" || true)\n\n# Flush existing rules and delete existing ipsets\niptables -F\niptables -X\niptables -t nat -F\niptables -t nat -X\niptables -t mangle -F\niptables -t mangle -X\nipset destroy allowed-domains 2>/dev/null || true\n\n# 2. Selectively restore ONLY internal Docker DNS resolution\nif [ -n \"$DOCKER_DNS_RULES\" ]; then\n    echo \"Restoring Docker DNS rules...\"\n    iptables -t nat -N DOCKER_OUTPUT 2>/dev/null || true\n    iptables -t nat -N DOCKER_POSTROUTING 2>/dev/null || true\n    echo \"$DOCKER_DNS_RULES\" | xargs -L 1 iptables -t nat\nelse\n    echo \"No Docker DNS rules to restore\"\nfi\n\n# First allow DNS and localhost before any restrictions\n# Allow outbound DNS\niptables -A OUTPUT -p udp --dport 53 -j ACCEPT\n# Allow inbound DNS responses\niptables -A INPUT -p udp --sport 53 -j ACCEPT\n# Allow outbound SSH\niptables -A OUTPUT -p tcp --dport 22 -j ACCEPT\n# Allow inbound SSH responses\niptables -A INPUT -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT\n# Allow localhost\niptables -A INPUT -i lo -j ACCEPT\niptables -A OUTPUT -o lo -j ACCEPT\n\n# Create ipset with CIDR support\nipset create allowed-domains hash:net\n\n# Fetch GitHub meta information and aggregate + add their IP ranges\necho \"Fetching GitHub IP ranges...\"\ngh_ranges=$(curl -s https://api.github.com/meta)\nif [ -z \"$gh_ranges\" ]; then\n    echo \"ERROR: Failed to fetch GitHub IP ranges\"\n    exit 1\nfi\n\nif ! echo \"$gh_ranges\" | jq -e '.web and .api and .git' >/dev/null; then\n    echo \"ERROR: GitHub API response missing required fields\"\n    exit 1\nfi\n\necho \"Processing GitHub IPs...\"\nwhile read -r cidr; do\n    if [[ ! \"$cidr\" =~ ^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}/[0-9]{1,2}$ ]]; then\n        echo \"ERROR: Invalid CIDR range from GitHub meta: $cidr\"\n        exit 1\n    fi\n    echo \"Adding GitHub range $cidr\"\n    ipset add allowed-domains \"$cidr\"\ndone < <(echo \"$gh_ranges\" | jq -r '(.web + .api + .git)[]' | aggregate -q)\n\n# Resolve and add other allowed domains\nfor domain in \\\n    \"files.pythonhosted.org\" \\\n    \"static.crates.io\" \\\n    \"index.crates.io\" \\\n    \"proxy.golang.org\" \\\n    \"registry.npmjs.org\" \\\n    \"api.anthropic.com\" \\\n    \"sentry.io\" \\\n    \"statsig.anthropic.com\" \\\n    \"statsig.com\" \\\n    \"marketplace.visualstudio.com\" \\\n    \"vscode.blob.core.windows.net\" \\\n    \"update.code.visualstudio.com\"; do\n    echo \"Resolving $domain...\"\n    ips=$(dig +noall +answer A \"$domain\" | awk '$4 == \"A\" {print $5}')\n    if [ -z \"$ips\" ]; then\n        echo \"ERROR: Failed to resolve $domain\"\n        exit 1\n    fi\n    \n    while read -r ip; do\n        if [[ ! \"$ip\" =~ ^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$ ]]; then\n            echo \"ERROR: Invalid IP from DNS for $domain: $ip\"\n            exit 1\n        fi\n        echo \"Adding $ip for $domain\"\n        ipset add allowed-domains \"$ip\"\n    done < <(echo \"$ips\")\ndone\n\n# Get host IP from default route\nHOST_IP=$(ip route | grep default | cut -d\" \" -f3)\nif [ -z \"$HOST_IP\" ]; then\n    echo \"ERROR: Failed to detect host IP\"\n    exit 1\nfi\n\nHOST_NETWORK=$(echo \"$HOST_IP\" | sed \"s/\\.[0-9]*$/.0\\/24/\")\necho \"Host network detected as: $HOST_NETWORK\"\n\n# Set up remaining iptables rules\niptables -A INPUT -s \"$HOST_NETWORK\" -j ACCEPT\niptables -A OUTPUT -d \"$HOST_NETWORK\" -j ACCEPT\n\n# Set default policies to DROP first\niptables -P INPUT DROP\niptables -P FORWARD DROP\niptables -P OUTPUT DROP\n\n# First allow established connections for already approved traffic\niptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT\niptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT\n\n# Then allow only specific outbound traffic to allowed domains\niptables -A OUTPUT -m set --match-set allowed-domains dst -j ACCEPT\n\n# Explicitly REJECT all other outbound traffic for immediate feedback\niptables -A OUTPUT -j REJECT --reject-with icmp-admin-prohibited\n\necho \"Firewall configuration complete\"\necho \"Verifying firewall rules...\"\nif curl --connect-timeout 5 https://example.com >/dev/null 2>&1; then\n    echo \"ERROR: Firewall verification failed - was able to reach https://example.com\"\n    exit 1\nelse\n    echo \"Firewall verification passed - unable to reach https://example.com as expected\"\nfi\n\n# Verify GitHub API access\nif ! curl --connect-timeout 5 https://api.github.com/zen >/dev/null 2>&1; then\n    echo \"ERROR: Firewall verification failed - unable to reach https://api.github.com\"\n    exit 1\nelse\n    echo \"Firewall verification passed - able to reach https://api.github.com as expected\"\nfi\n"
  },
  {
    "path": ".devcontainer/squid.conf",
    "content": "http_port 3128\n\n# DNS\ndns_v4_first on\n\n# Allowed domains\nacl allowed dstdomain \\\n    .pythonhosted.org \\\n    .pypi.org \\\n    .crates.io \\\n    .static.rust-lang.org \\\n    .npmjs.org \\\n    .github.com \\\n    .githubusercontent.com \\\n    .go.dev \\\n    .golang.org \\\n    .anthropic.com \\\n    .openai.com \\\n    .sentry.io \\\n    .statsig.com \\\n    .visualstudio.com \\\n    .windows.net\n\n# Allow HTTPS CONNECT only to allowed domains\nacl SSL_ports port 443\nacl Safe_ports port 80 443\nacl CONNECT method CONNECT\n\nhttp_access deny !Safe_ports\nhttp_access deny CONNECT !SSL_ports\nhttp_access allow allowed\nhttp_access deny all\n\n# Logging\naccess_log stdio:/var/log/squid/access.log\n"
  },
  {
    "path": ".dockerignore",
    "content": "*target\n.git\nturso-test-runner\nassets\nperf/clickbench\nperf/tpc-h\ntesting/sqlright/build\n"
  },
  {
    "path": ".github/labeler.yml",
    "content": "simulator:\n  - changed-files:\n      - any-glob-to-any-file: \"testing/simulator/**/*\"\ndocs:\n  - changed-files:\n      - any-glob-to-any-file: \"docs/**/*\"\n\nextensionlib:\n  - changed-files:\n      - any-glob-to-any-file:\n          [\"extensions/core/**/*\", \"macros/src/ext/*\", \"core/ext/*\"]\n\nmacros:\n  - changed-files:\n      - any-glob-to-any-file: [\"macros/**/*\"]\n\nextensions-other:\n  - all:\n      - changed-files:\n          - any-glob-to-any-file: \"extensions/**/*\"\n          - all-globs-to-all-files: \"!extensions/core/**/*\"\n\nfuzzing:\n  - changed-files:\n      - any-glob-to-any-file: \"fuzz/**/*\"\n\nperf/benchmarks:\n  - changed-files:\n      - any-glob-to-any-file: [\"perf/**/*\", \"core/benches/*\"]\n\ngo-bindings:\n  - changed-files:\n      - any-glob-to-any-file: \"bindings/go/**/*\"\n\npython-bindings:\n  - changed-files:\n      - any-glob-to-any-file: \"bindings/python/**/*\"\n\njs-bindings:\n  - changed-files:\n      - any-glob-to-any-file: \"bindings/javascript/**/*\"\n\nrust-bindings:\n  - changed-files:\n      - any-glob-to-any-file: \"bindings/rust/**/*\"\n\njava-bindings:\n  - changed-files:\n      - any-glob-to-any-file: \"bindings/java/**/*\"\n\nparser:\n  - changed-files:\n      - any-glob-to-any-file: \"vendored/sqlite3-parser/*\"\n\ncli:\n  - changed-files:\n      - any-glob-to-any-file: \"cli/**/*\"\n\nsqlite3:\n  - changed-files:\n      - any-glob-to-any-file: \"sqlite3/**/*\"\n\ncore:\n  - changed-files:\n      - any-glob-to-any-file: \"core/**/*\"\n\noptimizer:\n  - changed-files:\n      - any-glob-to-any-file: \"core/translate/optimizer/*\"\n\ntranslation/planning:\n  - changed-files:\n      - any-glob-to-any-file: \"core/translate/*.rs\"\n\nio:\n  - changed-files:\n      - any-glob-to-any-file: \"core/io/*\"\n\nmvcc:\n  - changed-files:\n      - any-glob-to-any-file: \"core/mvcc/**/*\"\n\nvdbe:\n  - changed-files:\n      - any-glob-to-any-file: \"core/vdbe/*\"\n\njson:\n  - changed-files:\n      - any-glob-to-any-file: \"core/json/*\"\n\nstorage:\n  - changed-files:\n      - any-glob-to-any-file: \"core/storage/*\"\n\nvector:\n  - changed-files:\n      - any-glob-to-any-file: \"core/vector/*\"\n\nturso-serverless:\n  - changed-files:\n    - any-glob-to-any-file: 'packages/turso-serverless'\n\nturso-sync:\n  - changed-files:\n    - any-glob-to-any-file: 'packages/turso-sync'\n\nantithesis:\n  - changed-files:\n      - any-glob-to-any-file:\n          [\"testing/antithesis/**/*\", \"scripts/antithesis/*\", \"testing/stress/**/*\"]\n\nci-actions:\n  - changed-files:\n      - any-glob-to-any-file: \".github/workflows/*.yml\"\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "# NOTICE:\n<!-- \nIn order to streamline the contribution process, please check the \"allow edits from maintainers\" checkbox on your PR. If needed, this allows us to push tweaks to your PR and avoid a potentially lengthy back-and-forth. Your original commits will stay on the branch, and you will keep authorship of those commits.\n-->\n\n\n## Description\n\n<!-- \nPlease include a summary of the changes and the related issue. \n-->\n\n## Motivation and context\n\n<!-- \nPlease include relevant motivation and context.\nLink relevant issues here.\n-->\n\n\n## Description of AI Usage\n\n<!-- \nPlease disclose how AI was used to help create this PR. For example, you can share prompts,\nspecific tools, or ways of working that you took advantage of. You can also share whether the\ncreation of the PR was mainly driven by AI, or whether it was used for assistance.\n\nThis is a good way of sharing knowledge to other contributors about how we can work more efficiently with\nAI tools. Note that the use of AI is encouraged, but the committer is still fully responsible for understanding\nand reviewing the output.\n-->\n"
  },
  {
    "path": ".github/shared/install_sqlite/action.yml",
    "content": "name: \"Install SQLite\"\ndescription: \"Installs sqlite3 CLI for compat tests that invoke it as a subprocess (make test-compat)\"\n\nruns:\n  using: \"composite\"\n  steps:\n    - name: Install SQLite\n      env:\n        SQLITE_VERSION: \"3510100\"\n        YEAR: 2025\n      run: |\n        curl -o /tmp/sqlite.zip https://sqlite.org/$YEAR/sqlite-tools-linux-x64-$SQLITE_VERSION.zip > /dev/null\n        echo \"y\" | unzip -j /tmp/sqlite.zip sqlite3 -d /usr/local/bin/\n        sqlite3 --version\n      shell: bash\n"
  },
  {
    "path": ".github/shared/setup-mold/action.yml",
    "content": "name: \"Setup mold linker\"\ndescription: \"Installs mold linker with retry logic to handle transient GitHub download failures\"\n\ninputs:\n  mold-version:\n    description: \"Version of mold to install\"\n    required: false\n    default: \"2.40.4\"\n\nruns:\n  using: \"composite\"\n  steps:\n    - name: Install mold linker\n      shell: bash\n      run: |\n        MOLD_VERSION=\"${{ inputs.mold-version }}\"\n        ARCH=\"$(uname -m)\"\n        URL=\"https://github.com/rui314/mold/releases/download/v${MOLD_VERSION}/mold-${MOLD_VERSION}-${ARCH}-linux.tar.gz\"\n        TMPFILE=\"/tmp/mold.tar.gz\"\n\n        MAX_RETRIES=5\n        for i in $(seq 1 $MAX_RETRIES); do\n          echo \"Downloading mold v${MOLD_VERSION} (attempt $i/$MAX_RETRIES)...\"\n          if curl -fsSL --retry 3 --retry-delay 5 -o \"$TMPFILE\" \"$URL\"; then\n            echo \"Download succeeded.\"\n            break\n          fi\n          if [ \"$i\" -eq \"$MAX_RETRIES\" ]; then\n            echo \"::warning::Failed to download mold after $MAX_RETRIES attempts. Building without mold.\"\n            exit 0\n          fi\n          SLEEP=$((i * 5))\n          echo \"Download failed. Retrying in ${SLEEP}s...\"\n          sleep \"$SLEEP\"\n        done\n\n        sudo tar -C /usr/local --strip-components=1 --no-overwrite-dir -xzf \"$TMPFILE\"\n        rm -f \"$TMPFILE\"\n        echo \"mold $(mold --version) installed successfully.\"\n"
  },
  {
    "path": ".github/shared/setup-sccache/action.yml",
    "content": "name: \"Setup sccache\"\ndescription: \"Installs sccache with graceful fallback if cache service is unavailable\"\n\nruns:\n  using: \"composite\"\n  steps:\n    - name: Install sccache\n      uses: mozilla-actions/sccache-action@v0.0.9\n    - name: Enable sccache\n      shell: bash\n      run: |\n        export SCCACHE_GHA_ENABLED=true\n        if sccache -s >/dev/null 2>&1; then\n          echo \"SCCACHE_GHA_ENABLED=true\" >> \"$GITHUB_ENV\"\n          echo \"RUSTC_WRAPPER=sccache\" >> \"$GITHUB_ENV\"\n          echo \"sccache is available and configured\"\n        else\n          echo \"::warning::sccache cache service is unavailable, building without cache\"\n        fi\n"
  },
  {
    "path": ".github/turso-bot.yml",
    "content": "# Turso Bot configuration\n# Place this file in your repository at .github/turso-bot.yml\n\n# Enable or disable the bot for this repository\nenabled: true\n\n# BetterStack schedule ID to fetch on-call users\nscheduleId: \"335158\"\n\n"
  },
  {
    "path": ".github/workflows/antithesis-schedule.yml",
    "content": "name: Antithesis nightly\n\non:\n  schedule:\n    - cron: \"0 0 * * 0-5\" # Sun-Fri: 4 hour run\n    - cron: \"0 0 * * 6\"   # Saturday: 24 hour run\n\njobs:\n  test:\n    uses: ./.github/workflows/antithesis.yml\n    with:\n      duration: ${{ github.event.schedule == '0 0 * * 6' && 1440 || 240 }}\n      test_name: ${{ github.event.schedule == '0 0 * * 6' && 'weekend run' || 'scheduled run' }}\n    secrets: inherit\n"
  },
  {
    "path": ".github/workflows/antithesis.yml",
    "content": "name: Antithesis experiment\n\non:\n  # Allows the workflow to be triggered manually\n  workflow_dispatch:\n    inputs:\n      test_name:\n        description: \"Name to differentiate this run (optional)\"\n        required: false\n        default: \"\"\n        type: string\n      duration:\n        description: \"Duration in minutes (min 15, default 4 hours)\"\n        required: false\n        default: 240\n        type: number\n  workflow_call:\n    inputs:\n      test_name:\n        required: true\n        type: string\n      duration:\n        required: true\n        type: number\n\nenv:\n  ANTITHESIS_DOCKER_HOST: us-central1-docker.pkg.dev\n  ANTITHESIS_DOCKER_REPO: ${{ secrets.ANTITHESIS_DOCKER_REPO }}\n  ANTITHESIS_REGISTRY_KEY: ${{ secrets.ANTITHESIS_REGISTRY_KEY }}\n\njobs:\n  test:\n    runs-on: blacksmith\n\n    timeout-minutes: 30\n    steps:\n    - uses: actions/checkout@v3\n\n    - name: Publish workload\n      run: bash ./scripts/antithesis/publish-workload.sh\n\n    - name: Publish config\n      run: bash ./scripts/antithesis/publish-config.sh\n\n    - name: Run Antithesis Tests\n      uses: antithesishq/antithesis-trigger-action@v0.5\n      with:\n        notebook_name: limbo\n        tenant: ${{ secrets.ANTITHESIS_TENANT }}\n        username: ${{ secrets.ANTITHESIS_USER }}\n        password: ${{ secrets.ANTITHESIS_PASSWD }}\n        github_token: ${{ secrets.ANTITHESIS_GITHUB_TOKEN }}\n        config_image: ${{ secrets.ANTITHESIS_DOCKER_REPO }}/limbo-config:antithesis-latest\n        images: ${{ secrets.ANTITHESIS_DOCKER_REPO }}/limbo-workload:antithesis-latest\n        description: >-\n          Turso - ${{ github.event_name }}\n          on ${{ github.ref_name }} (${{ github.sha }})\n          by ${{ github.actor }}${{ inputs.test_name && format(' ({0})', inputs.test_name) || '' }}\n        email_recipients: ${{ secrets.ANTITHESIS_EMAIL }}\n        test_name: ${{ inputs.test_name }}\n        additional_parameters: |-\n          antithesis.duration=${{ inputs.duration }}\n          antithesis.source=${{ github.ref_name }}"
  },
  {
    "path": ".github/workflows/build-sim.yml",
    "content": "name: Build and push limbo-sim image\n\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - main\n\n# Add permissions needed for OIDC authentication with AWS\npermissions:\n  id-token: write # allow getting OIDC token\n  contents: read # allow reading repository contents\n\n# Ensure only one build runs at a time. A new push to main will cancel any in-progress build.\nconcurrency:\n  group: \"build-sim\"\n  cancel-in-progress: true\n\nenv:\n  AWS_REGION: ${{ secrets.LIMBO_SIM_AWS_REGION }}\n  IAM_ROLE: ${{ secrets.LIMBO_SIM_DEPLOYER_IAM_ROLE }}\n  ECR_URL: ${{ secrets.LIMBO_SIM_ECR_URL }}\n  ECR_IMAGE_NAME: ${{ secrets.LIMBO_SIM_IMAGE_NAME }}\n  GIT_HASH: ${{ github.sha }}\n\njobs:\n  deploy:\n    runs-on: blacksmith\n    timeout-minutes: 30\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n\n      - name: Configure AWS credentials\n        uses: aws-actions/configure-aws-credentials@v4\n        with:\n          role-to-assume: ${{ env.IAM_ROLE }}\n          aws-region: ${{ env.AWS_REGION }}\n\n      - name: Login to Amazon ECR\n        uses: aws-actions/amazon-ecr-login@v2\n\n      - name: Build and push limbo-sim docker image\n        run: |\n          docker build -f testing/simulator/simulator-docker-runner/Dockerfile.simulator -t ${{ env.ECR_URL }}/${{ env.ECR_IMAGE_NAME }} --build-arg GIT_HASH=${{ env.GIT_HASH }} .\n          docker push ${{ env.ECR_URL }}/${{ env.ECR_IMAGE_NAME }}\n"
  },
  {
    "path": ".github/workflows/build-sqlancer.yml",
    "content": "name: Build and push sqlancer-runner image\n\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - main\n    paths:\n      - \"bindings/java/**\"\n      - \"scripts/corruption-debug-tools/**\"\n      - \"testing/sqlancer/**\"\n      - \".github/workflows/build-sqlancer.yml\"\n\n# Add permissions needed for OIDC authentication with AWS\npermissions:\n  id-token: write # allow getting OIDC token\n  contents: read # allow reading repository contents\n\n# Ensure only one build runs at a time. A new push to main will cancel any in-progress build.\nconcurrency:\n  group: \"build-sqlancer\"\n  cancel-in-progress: true\n\nenv:\n  AWS_REGION: ${{ secrets.LIMBO_SIM_AWS_REGION }}\n  IAM_ROLE: ${{ secrets.SQLANCER_DEPLOYER_IAM_ROLE }}\n  ECR_URL: ${{ secrets.SQLANCER_ECR_URL }}\n  GIT_HASH: ${{ github.sha }}\n\njobs:\n  deploy:\n    runs-on: blacksmith\n    timeout-minutes: 45\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n\n      - name: Configure AWS credentials\n        uses: aws-actions/configure-aws-credentials@v4\n        with:\n          role-to-assume: ${{ env.IAM_ROLE }}\n          aws-region: ${{ env.AWS_REGION }}\n\n      - name: Login to Amazon ECR\n        uses: aws-actions/amazon-ecr-login@v2\n\n      - name: Build and push sqlancer-runner docker image\n        run: |\n          docker build -f testing/sqlancer/sqlancer-runner/Dockerfile.sqlancer -t ${{ env.ECR_URL }}:latest --build-arg GIT_HASH=${{ env.GIT_HASH }} .\n          docker push ${{ env.ECR_URL }}:latest\n"
  },
  {
    "path": ".github/workflows/c-compat.yml",
    "content": "name: C compat Tests\n\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - main\n    tags:\n      - v*\n  pull_request:\n    branches:\n      - main\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}\n  cancel-in-progress: true\n\nenv:\n  CARGO_INCREMENTAL: \"0\"\n  CARGO_NET_RETRY: 10\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    timeout-minutes: 30\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n\n      - name: Install Rust(stable)\n        uses: dtolnay/rust-toolchain@stable\n\n      - name: Setup mold linker\n        uses: rui314/setup-mold@v1\n\n      - name: Rust cache\n        uses: Swatinem/rust-cache@v2\n        with:\n          prefix-key: \"v1-rust\"\n          cache-on-failure: true\n\n      - uses: \"./.github/shared/setup-sccache\"\n\n      - name: Build Turso C bindings\n        run: cargo build -p turso_sqlite3 --features capi --locked\n\n      - name: Run C compat tests from Rust\n        working-directory: sqlite3\n        run: cargo test --locked\n\n      - name: Run C compat C tests linking against real SQLite\n        working-directory: sqlite3/tests\n        run: |\n          make clean\n          make LIBS=\"-lsqlite3\"\n          ./sqlite3-tests\n\n      - name: Run C compat C tests linking against Turso\n        working-directory: sqlite3/tests\n        run: |\n          make clean\n          make LIBS=\"-L../../target/debug -lturso_sqlite3\"\n          LD_LIBRARY_PATH=../../target/debug ./sqlite3-tests\n"
  },
  {
    "path": ".github/workflows/claude.yml",
    "content": "name: Claude Code\n\non:\n  issue_comment:\n    types: [created]\n  pull_request_review_comment:\n    types: [created]\n  pull_request_review:\n    types: [submitted]\n  issues:\n    types: [assigned]\n\njobs:\n  claude:\n    concurrency:\n      group: claude-${{ github.event.issue.number || github.event.pull_request.number }}\n      cancel-in-progress: false\n    # Only run when:\n    # 1. @claude is mentioned in a comment on an issue or PR by an authorized user\n    # 2. @claude is mentioned in a PR review by an authorized user\n    # 3. claude[bot] is assigned to an issue\n    #\n    # Authorized users: OWNER, MEMBER, COLLABORATOR (prevents abuse by external users)\n    if: |\n      (\n        contains(fromJSON('[\"issue_comment\", \"pull_request_review_comment\"]'), github.event_name) &&\n        contains(github.event.comment.body, '@claude') &&\n        contains(fromJSON('[\"OWNER\", \"MEMBER\", \"COLLABORATOR\"]'), github.event.comment.author_association)\n      ) ||\n      (\n        github.event_name == 'pull_request_review' &&\n        contains(github.event.review.body, '@claude') &&\n        contains(fromJSON('[\"OWNER\", \"MEMBER\", \"COLLABORATOR\"]'), github.event.review.author_association)\n      ) ||\n      (\n        github.event_name == 'issues' &&\n        github.event.assignee.login == 'claude[bot]'\n      )\n    runs-on: ubuntu-latest\n    timeout-minutes: 30\n    permissions:\n      contents: write\n      issues: write\n      pull-requests: write\n      actions: read\n      id-token: write\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 1\n\n      - name: Set up Rust\n        uses: dtolnay/rust-toolchain@stable\n        with:\n          components: rustfmt, clippy\n\n      - name: Cache Rust dependencies\n        uses: Swatinem/rust-cache@v2\n\n      - name: Run Claude\n        uses: anthropics/claude-code-action@v1\n        with:\n          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}\n          branch_prefix: \"claude/\"\n          additional_permissions: |\n            actions: read\n          claude_args: |\n            --model claude-opus-4-5-20251101\n            --max-turns 100\n            --allowedTools \"Bash(cargo:*),Bash(git:*),Bash(make:*),Bash(rustfmt:*),Bash(python:*),Edit,Write,Read,Glob,Grep,TodoWrite,Task,WebSearch\"\n            --disallowedTools \"Bash(rm -rf *),Bash(sudo *),Bash(curl *),Bash(wget *)\"\n"
  },
  {
    "path": ".github/workflows/codspeed.yml",
    "content": "name: CodSpeed\n\non:\n  push:\n    branches:\n      - \"main\"\n  pull_request:\n  workflow_dispatch:\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}\n  cancel-in-progress: true\n\npermissions:\n  contents: read\n  id-token: write\n\njobs:\n  benchmarks:\n    runs-on: ubuntu-24.04\n    timeout-minutes: 60\n    # Allow this job to fail without failing the entire CI run\n    continue-on-error: true\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup Rust toolchain\n        uses: dtolnay/rust-toolchain@1.88.0\n\n      - name: Setup mold linker\n        uses: rui314/setup-mold@v1\n\n      - uses: Swatinem/rust-cache@v2\n        with:\n          prefix-key: \"v1-rust-codspeed\"\n          cache-on-failure: true\n\n      - uses: \"./.github/shared/setup-sccache\"\n\n      - name: Install cargo-codspeed\n        uses: taiki-e/install-action@v2\n        with:\n          tool: cargo-codspeed\n\n      - name: Generate graph-queries DBs\n        run: |\n          python3 perf/graph-queries/generate_seed.py | sqlite3 perf/graph-queries/graph-queries.db\n          cp perf/graph-queries/graph-queries.db perf/graph-queries/graph-queries-analyzed.db\n          sqlite3 perf/graph-queries/graph-queries-analyzed.db \"ANALYZE;\"\n\n      - name: Build benchmarks (excluding TPC-H)\n        run: make codspeed-build-bench-exclude-tpc-h\n\n      - name: Run benchmarks (excluding TPC-H)\n        uses: CodSpeedHQ/action@v4.5.2\n        with:\n          mode: simulation\n          run: cargo codspeed run\n\n  # tpc-h:\n  #   runs-on: ubuntu-latest\n  #   timeout-minutes: 60\n  #   env:\n  #     DB_FILE: \"perf/tpc-h/TPC-H.db\"\n  #   steps:\n  #     - uses: actions/checkout@v4\n\n  #     - name: Setup Rust toolchain\n  #       uses: dtolnay/rust-toolchain@stable\n\n  #     - name: Setup mold linker\n  #       uses: rui314/setup-mold@v1\n\n  #     - uses: Swatinem/rust-cache@v2\n  #       with:\n  #         prefix-key: \"v1-rust-codspeed\"\n  #         cache-on-failure: true\n\n  #     - name: Setup sccache\n  #       uses: mozilla-actions/sccache-action@v0.0.9\n\n  #     - name: Install cargo-codspeed\n  #       uses: taiki-e/install-action@v2\n  #       with:\n  #         tool: cargo-codspeed\n\n  #     - name: Cache TPC-H\n  #       id: cache-tpch\n  #       uses: actions/cache@v4\n  #       with:\n  #         path: ${{ env.DB_FILE }}\n  #         key: tpc-h\n\n  #     - name: Download TPC-H\n  #       if: steps.cache-tpch.outputs.cache-hit != 'true'\n  #       env:\n  #         DB_URL: \"https://github.com/lovasoa/TPCH-sqlite/releases/download/v1.0/TPC-H.db\"\n  #       run: wget -O $DB_FILE --no-verbose $DB_URL\n\n  #     - name: Build TPC-H benchmark\n  #       env:\n  #         SCCACHE_GHA_ENABLED: \"true\"\n  #         RUSTC_WRAPPER: \"sccache\"\n  #       run: cargo codspeed build --features codspeed --bench tpc_h_benchmark \n\n  #     - name: Run TPC-H benchmark\n  #       uses: CodSpeedHQ/action@v4.5.2\n  #       with:\n  #         mode: simulation\n  #         run: cargo codspeed run --bench tpc_h_benchmark\n"
  },
  {
    "path": ".github/workflows/dotnet-publish.yml",
    "content": "name: Dotnet Publish\n\non:\n  # Manually trigger the workflow\n  workflow_dispatch:\n\nenv:\n  working-directory: bindings/dotnet\n\njobs:\n  # Build native libraries for each platform\n  build-natives:\n    strategy:\n      matrix:\n        include:\n        - os: ubuntu-latest\n          make-target: build-rust-linux64\n          artifact-name: linux64\n          target: x86_64-unknown-linux-gnu\n\n        - os: macos-latest\n          make-target: build-rust-macos64\n          artifact-name: macos64\n          target: x86_64-apple-darwin\n\n        - os: macos-latest\n          make-target: build-rust-macosarm64\n          target: aarch64-apple-darwin\n          artifact-name: macosarm64\n\n        - os: windows-latest\n          make-target: build-rust-windows64\n          target: x86_64-pc-windows-msvc\n          artifact-name: windows64\n\n    runs-on: ${{ matrix.os }}\n    timeout-minutes: 30\n    defaults:\n      run:\n        working-directory: ${{ env.working-directory }}\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n\n      - name: Install Rust\n        uses: dtolnay/rust-toolchain@stable\n        with:\n          targets: ${{ matrix.target }}\n      \n      - name: Add target\n        run: rustup target add ${{ matrix.target }}\n\n      - name: Build rust native library\n        run: make ${{ matrix.make-target }} RUST_RELEASE_OPT='--profile release-official'\n      \n      - name: Upload rust native library\n        uses: actions/upload-artifact@v4\n        with:\n          name: native-${{ matrix.artifact-name }}\n          path: |\n            ${{ env.working-directory }}/rs_compiled/**/turso_dotnet.dll\n            ${{ env.working-directory }}/rs_compiled/**/libturso_dotnet.so\n            ${{ env.working-directory }}/rs_compiled/**/libturso_dotnet.dylib\n\n          retention-days: 1\n\n  # Create Turso.Raw and Turso nuget packages\n  publish:\n    needs: build-natives\n    runs-on: ubuntu-latest\n    timeout-minutes: 30\n\n    defaults:\n      run:\n        working-directory: ${{ env.working-directory }}\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n      \n      - name: Install dotnet sdk 9.0\n        uses: actions/setup-dotnet@v4\n        with:\n          dotnet-version: '9.0.x'\n\n      - name: Download all native libraries\n        uses: actions/download-artifact@v4\n        with:\n          pattern: native-*\n          path: ${{ env.working-directory }}/rs_compiled\n          merge-multiple: true\n\n      - name: Build and pack \n        run:\n          make pack\n\n      # https://learn.microsoft.com/en-us/nuget/nuget-org/publish-a-package\n      - name: Publish to nuget\n        if: false # TODO: Get nuget api key and publish packages\n        run: |\n          dotnet nuget push ${{ env.working-directory }}/src/Turso.Raw/bin/Release/Turso.Raw.*.nupkg --api-key ... --source https://api.nuget.org/v3/index.json\n\n          dotnet nuget push ${{ env.working-directory }}/src/Turso/bin/Release/Turso.*.nupkg --api-key ... --source https://api.nuget.org/v3/index.json\n\n      - name: Upload Turso.Raw to artifacts\n        uses: actions/upload-artifact@v4\n        with:\n          name: Turso.Raw\n          path: ${{ env.working-directory }}/src/Turso.Raw/bin/Release/Turso.Raw.*.nupkg\n          retention-days: 7\n\n      - name: Upload Turso to artifacts\n        uses: actions/upload-artifact@v4\n        with:\n          name: Turso\n          path: ${{ env.working-directory }}/src/Turso/bin/Release/Turso.*.nupkg\n          retention-days: 7\n\n\n\n"
  },
  {
    "path": ".github/workflows/dotnet-test.yml",
    "content": "name: Dotnet Tests\n\non:\n  push:\n    branches:\n      - main\n    tags:\n      - v*\n  pull_request:\n    branches:\n      - main\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}\n  cancel-in-progress: true\n\nenv:\n  working-directory: bindings/dotnet\n  CARGO_INCREMENTAL: \"0\"\n  CARGO_NET_RETRY: 10\n\njobs:\n  test:\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    timeout-minutes: 30\n\n    defaults:\n      run:\n        working-directory: ${{ env.working-directory }}\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n\n      - name: Install Rust(stable)\n        uses: dtolnay/rust-toolchain@stable\n\n      - name: Setup mold linker\n        uses: rui314/setup-mold@v1\n\n      - name: Rust cache\n        uses: Swatinem/rust-cache@v2\n        with:\n          prefix-key: \"v1-rust\"\n          cache-on-failure: true\n\n      - uses: \"./.github/shared/setup-sccache\"\n\n      - name: Install dotnet sdk 9.0\n        uses: actions/setup-dotnet@v4\n        with:\n          dotnet-version: '9.0.x'\n\n      - name: Run tests\n        run: make test\n"
  },
  {
    "path": ".github/workflows/elle.yml",
    "content": "name: Elle Consistency Check\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches:\n      - main\n  workflow_dispatch:\n    inputs:\n      max_steps:\n        description: \"Maximum simulation steps\"\n        required: false\n        default: \"100000\"\n      seed:\n        description: \"Random seed (leave empty for random)\"\n        required: false\n        default: \"\"\n  schedule:\n    # Run nightly at 3am UTC\n    - cron: \"0 3 * * *\"\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}\n  cancel-in-progress: true\n\nenv:\n  CARGO_TERM_COLOR: always\n\njobs:\n  elle-check:\n    name: Elle ${{ matrix.elle_model }} (${{ matrix.mvcc && 'MVCC' || 'default' }})\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    timeout-minutes: 60\n    strategy:\n      fail-fast: false\n      matrix:\n        mvcc: [false, true]\n        elle_model: [list-append, rw-register]\n    steps:\n      - uses: actions/checkout@v4\n\n      - uses: useblacksmith/rust-cache@v3\n        with:\n          prefix-key: \"v1-rust\"\n\n      - uses: \"./.github/shared/setup-sccache\"\n\n      - name: Setup Java\n        uses: actions/setup-java@v4\n        with:\n          distribution: \"temurin\"\n          java-version: \"21\"\n\n      - name: Cache Leiningen\n        uses: actions/cache@v4\n        with:\n          path: |\n            /tmp/lein\n            ~/.lein\n          key: lein-${{ runner.os }}\n\n      - name: Cache elle-cli\n        uses: actions/cache@v4\n        with:\n          path: /tmp/elle-cli\n          key: elle-cli-${{ runner.os }}-${{ hashFiles('.github/workflows/elle.yml') }}\n\n      - name: Build simulator\n        run: cargo build -p turso_whopper\n\n      - name: Run simulation with Elle\n        env:\n          SEED: ${{ github.event.inputs.seed }}\n        run: |\n          SEED_ARG=\"\"\n          if [ -n \"$SEED\" ]; then\n            export SEED=\"$SEED\"\n          fi\n          MAX_STEPS=\"${{ github.event.inputs.max_steps }}\"\n          MAX_STEPS=\"${MAX_STEPS:-100000}\"\n          MVCC_FLAG=\"\"\n          if [ \"${{ matrix.mvcc }}\" = \"true\" ]; then\n            MVCC_FLAG=\"--enable-mvcc\"\n          fi\n          ./target/debug/turso_whopper \\\n            --elle ${{ matrix.elle_model }} \\\n            --elle-output elle-history.edn \\\n            --max-steps \"$MAX_STEPS\" \\\n            $MVCC_FLAG\n\n      - name: Setup Leiningen\n        run: |\n          if [ ! -x /tmp/lein/lein ]; then\n            mkdir -p /tmp/lein\n            curl -sL https://raw.githubusercontent.com/technomancy/leiningen/2.11.2/bin/lein -o /tmp/lein/lein\n            chmod +x /tmp/lein/lein\n            /tmp/lein/lein version\n          fi\n\n      - name: Build elle-cli\n        run: |\n          if [ ! -d /tmp/elle-cli ]; then\n            git clone --depth 1 https://github.com/ligurio/elle-cli.git /tmp/elle-cli\n          fi\n          cd /tmp/elle-cli\n          if [ ! -f target/*-standalone.jar ]; then\n            /tmp/lein/lein uberjar\n          fi\n\n      - name: Install Graphviz\n        run: sudo apt-get update && sudo apt-get install -y graphviz\n\n      - name: Run Elle analysis\n        run: |\n          ELLE_JAR=$(ls -t /tmp/elle-cli/target/*-standalone.jar | head -1)\n          mkdir -p elle-results\n          echo \"Using JAR: $ELLE_JAR\"\n          echo \"History file: $(pwd)/elle-history.edn\"\n          echo \"History size: $(wc -l < elle-history.edn) events\"\n          echo \"\"\n          CONSISTENCY_FLAG=\"--consistency-models serializable\"\n          if [ \"${{ matrix.mvcc }}\" = \"true\" ]; then\n            CONSISTENCY_FLAG=\"--consistency-models snapshot-isolation\"\n          fi\n          java -jar \"$ELLE_JAR\" --model ${{ matrix.elle_model }} $CONSISTENCY_FLAG --verbose --directory elle-results elle-history.edn\n\n      - name: Upload Elle results\n        uses: actions/upload-artifact@v4\n        if: always()\n        with:\n          name: elle-results-${{ matrix.elle_model }}-${{ matrix.mvcc && 'mvcc' || 'default' }}\n          path: |\n            elle-history.edn\n            elle-results/\n          retention-days: 30\n"
  },
  {
    "path": ".github/workflows/fuzz.yml",
    "content": "name: Run long fuzz tests and stress test\n\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - main\n    tags:\n      - v*\n  pull_request:\n    branches:\n      - main\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}\n  cancel-in-progress: true\n\nenv:\n  CARGO_INCREMENTAL: \"0\"\n  CARGO_NET_RETRY: 10\n\njobs:\n  run-fuzz-tests:\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    timeout-minutes: 30\n\n    steps:\n    - uses: actions/checkout@v4\n    - uses: dtolnay/rust-toolchain@stable\n    - name: Setup mold linker\n      uses: rui314/setup-mold@v1\n    - uses: Swatinem/rust-cache@v2\n      with:\n        prefix-key: \"v1-rust\"\n        cache-on-failure: true\n    - uses: \"./.github/shared/setup-sccache\"\n    - name: Install cargo-nextest\n      uses: taiki-e/install-action@nextest\n    - name: Set up Python 3.10\n      uses: useblacksmith/setup-python@v6\n      with:\n        python-version: \"3.10\"\n    - name: Build\n      run: cargo build --verbose --locked --all-features\n    - name: Run fuzz tests\n      env:\n        RUST_BACKTRACE: 1\n        FUZZ_MULTIPLIER: \"0.5\"\n      run: cargo nextest run --locked --no-fail-fast -E 'binary(fuzz_tests)' --failure-output=final\n\n  run-long-fuzz-tests:\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    timeout-minutes: 30\n\n    steps:\n    - uses: actions/checkout@v4\n    - uses: dtolnay/rust-toolchain@stable\n    - name: Setup mold linker\n      uses: rui314/setup-mold@v1\n    - uses: Swatinem/rust-cache@v2\n      with:\n        prefix-key: \"v1-rust\"\n        cache-on-failure: true\n    - uses: \"./.github/shared/setup-sccache\"\n    - name: Install cargo-nextest\n      uses: taiki-e/install-action@nextest\n    - name: Set up Python 3.10\n      uses: useblacksmith/setup-python@v6\n      with:\n        python-version: \"3.10\"\n    - name: Build\n      run: cargo build --verbose --locked --all-features\n    - name: Run ignored long fuzz tests\n      env:\n        RUST_BACKTRACE: 1\n        FUZZ_MULTIPLIER: \"0.5\"\n      run: cargo nextest run --locked --no-fail-fast --run-ignored ignored-only -E 'test(/fuzz_long/)' --failure-output=final\n\n  simple-stress-test:\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    timeout-minutes: 30\n    steps:\n    - uses: actions/checkout@v4\n    - uses: dtolnay/rust-toolchain@stable\n    - name: Setup mold linker\n      uses: rui314/setup-mold@v1\n    - uses: Swatinem/rust-cache@v2\n      with:\n        prefix-key: \"v1-rust\"\n        cache-on-failure: true\n    - uses: \"./.github/shared/setup-sccache\"\n    - name: Set up Python 3.10\n      uses: useblacksmith/setup-python@v6\n      with:\n        python-version: \"3.10\"\n    - name: Build\n      run: cargo build --verbose --locked --all-features\n    - name: Run ignored long tests\n      env:\n        RUST_BACKTRACE: 1\n      run: RUSTFLAGS=\"--cfg shuttle\" cargo run -p turso_stress --locked -- --nr-threads 3 --nr-iterations 300\n\n  shuttle-stress-test:\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    timeout-minutes: 30\n    steps:\n    - uses: actions/checkout@v4\n    - uses: dtolnay/rust-toolchain@stable\n    - name: Setup mold linker\n      uses: rui314/setup-mold@v1\n    - uses: Swatinem/rust-cache@v2\n      with:\n        prefix-key: \"v1-rust-shuttle\"\n        cache-on-failure: true\n    - uses: \"./.github/shared/setup-sccache\"\n    - name: Run shuttle mvcc tests\n      env:\n        RUST_BACKTRACE: \"full\"\n        RUSTFLAGS: \"--cfg=shuttle\"\n      run: cargo test -p turso_stress --locked --test shuttle_mvcc\n    - name: Run shuttle stress test\n      env:\n        RUST_BACKTRACE: 1\n        RUSTFLAGS: \"--cfg=shuttle\"\n      run: cargo run -p turso_stress --locked -- --nr-threads 2 --nr-iterations 1000\n\n  libfuzzer-scalar-func:\n    # Allow this job to fail without failing the entire CI run\n    continue-on-error: true\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    timeout-minutes: 15\n    steps:\n    - uses: actions/checkout@v4\n    - uses: dtolnay/rust-toolchain@nightly\n    - name: Setup mold linker\n      uses: rui314/setup-mold@v1\n    - uses: Swatinem/rust-cache@v2\n      with:\n        prefix-key: \"v1-rust-fuzz\"\n        cache-on-failure: true\n    - name: Install cargo-fuzz\n      run: cargo install cargo-fuzz\n    - name: Run scalar_func fuzzer\n      run: cargo +nightly fuzz run scalar_func -- -max_total_time=300\n"
  },
  {
    "path": ".github/workflows/go.yml",
    "content": "## path=../../.github/workflows/go-publish.yml\nname: Build & Publish Go Driver\n\non:\n  workflow_dispatch:\n  push:\n    branches: [\"main\"]\n    tags:\n      - \"v*\"\n  pull_request:\n    branches: [\"main\"]\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n\npermissions:\n  contents: write\n\nenv:\n  # Token with repo access to tursodatabase/turso-go-platform-libs\n  GH_TOKEN: ${{ secrets.TURSO_GO_PLATFORM_LIBS_TOKEN }}\n  CARGO_INCREMENTAL: \"0\"\n  CARGO_NET_RETRY: 10\n\njobs:\n  test:\n    name: Build Rust and run Go tests\n    runs-on: ${{ matrix.os }}\n    timeout-minutes: 30\n    strategy:\n      matrix:\n        os:\n          - blacksmith-4vcpu-ubuntu-2404\n          - macos-latest\n          - windows-latest\n    env:\n      CARGO_TERM_COLOR: always\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n\n      - name: Setup Rust toolchain\n        uses: dtolnay/rust-toolchain@stable\n\n      - name: Setup mold linker\n        if: runner.os == 'Linux'\n        uses: rui314/setup-mold@v1\n      - name: Rust cache\n        uses: Swatinem/rust-cache@v2\n        with:\n          prefix-key: \"v1-rust\"\n          cache-on-failure: true\n      - uses: \"./.github/shared/setup-sccache\"\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: \"1.24.10\"\n          cache: true\n          cache-dependency-path: bindings/go/go.sum\n\n      - name: Build Rust (debug)\n        run: cargo build --verbose --locked\n\n      # Linux: use LD_LIBRARY_PATH to point to Rust target dir\n      - name: Run Go tests (Linux)\n        if: runner.os == 'Linux'\n        working-directory: bindings/go\n        env:\n          LOCAL_SYNC_SERVER: ../../target/debug/tursodb\n          LD_LIBRARY_PATH: ${{ github.workspace }}/target/debug\n        run: go test ./... -v\n\n      # macOS: use DYLD_LIBRARY_PATH to point to Rust target dir\n      - name: Run Go tests (macOS)\n        if: runner.os == 'macOS'\n        working-directory: bindings/go\n        env:\n          LOCAL_SYNC_SERVER: ../../target/debug/tursodb\n          DYLD_LIBRARY_PATH: ${{ github.workspace }}/target/debug\n        run: go test ./... -v\n\n      # Windows: prepend Rust target dir to PATH so .dll can be found\n      - name: Run Go tests (Windows)\n        if: runner.os == 'Windows'\n        working-directory: bindings/go\n        shell: pwsh\n        run: |\n          $env:PATH = \"${{ github.workspace }}\\target\\debug;$env:PATH\"\n          $env:LOCAL_SYNC_SERVER = \"..\\..\\target\\debug\\tursodb.exe\"\n          go test ./... -v\n\n  publish:\n    name: Publish Go driver\n    needs: test\n    runs-on: ubuntu-latest\n    timeout-minutes: 60\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n\n      - name: Ensure tooling (jq, coreutils)\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y jq coreutils\n\n      - name: Compute publish variables\n        id: vars\n        shell: bash\n        run: |\n          set -euo pipefail\n\n          EVENT=\"${{ github.event_name }}\"\n          REF=\"${{ github.ref }}\"\n\n          # Determine whether to publish for real:\n          # - Manual dispatch: publish\n          # - Tag push (refs/tags/v*): publish\n          # - Everything else (including PR): dry-run\n          if [[ \"$EVENT\" == \"workflow_dispatch\" ]]; then\n            DO_PUBLISH=\"true\"\n          elif [[ \"$REF\" == refs/tags/v* ]]; then\n            DO_PUBLISH=\"true\"\n          else\n            DO_PUBLISH=\"false\"\n          fi\n\n          if [[ \"${GITHUB_REF_TYPE}\" == \"tag\" ]]; then\n            # For tag events, github.ref_name is correct\n            TURSO_REF=\"${GITHUB_REF_NAME}\"\n          elif [[ -n \"${GITHUB_HEAD_REF}\" ]]; then\n            # For PRs, github.head_ref contains the real source branch\n            TURSO_REF=\"${GITHUB_HEAD_REF}\"\n          else\n            # For push events to branches\n            TURSO_REF=\"${GITHUB_REF_NAME}\"\n          fi\n\n          # Branch to push into this repository\n          if [[ \"$EVENT\" == \"workflow_dispatch\" ]]; then\n            TARGET_TAG=\"\"\n            TARGET_BRANCH=\"go-release-${TURSO_REF}\"\n          elif [[ \"$REF\" == refs/tags/* ]]; then\n            TARGET_TAG=\"${TURSO_REF}\"\n            TARGET_BRANCH=\"go-release-${TURSO_REF}\"\n          else\n            TARGET_TAG=\"\"\n            TARGET_BRANCH=\"go-release-${TURSO_REF}\"\n          fi\n\n          # Generate a unique nonce\n          NONCE=\"$(uuidgen 2>/dev/null || true)\"\n          if [[ -z \"$NONCE\" ]]; then\n            NONCE=\"$(date +%s)-$RANDOM-$RANDOM\"\n          fi\n\n          echo \"do_publish=$DO_PUBLISH\" >> \"$GITHUB_OUTPUT\"\n          echo \"turso_ref=$TURSO_REF\" >> \"$GITHUB_OUTPUT\"\n          echo \"target_branch=$TARGET_BRANCH\" >> \"$GITHUB_OUTPUT\"\n          echo \"target_tag=$TARGET_TAG\" >> \"$GITHUB_OUTPUT\"\n          echo \"nonce=$NONCE\" >> \"$GITHUB_OUTPUT\"\n\n          echo \"Event: $EVENT\"\n          echo \"Ref: $REF\"\n          echo \"DO_PUBLISH=$DO_PUBLISH\"\n          echo \"TURSO_REF=$TURSO_REF\"\n          echo \"TARGET_BRANCH=$TARGET_BRANCH\"\n          echo \"TARGET_TAG=$TARGET_TAG\"\n          echo \"NONCE=$NONCE\"\n\n      - name: Dry-run (PRs and non-tag non-manual runs)\n        if: steps.vars.outputs.do_publish != 'true'\n        run: |\n          echo \"Dry run: not triggering turso-go-platform-libs and not pushing any commits.\"\n          echo \"Would have used:\"\n          echo \" - turso_ref=${{ steps.vars.outputs.turso_ref }}\"\n          echo \" - target_branch=${{ steps.vars.outputs.target_branch }}\"\n          echo \" - nonce=${{ steps.vars.outputs.nonce }}\"\n          echo \"Exiting.\"\n\n      - name: Check token available for external repo\n        if: steps.vars.outputs.do_publish == 'true'\n        shell: bash\n        run: |\n          if [[ -z \"${GH_TOKEN:-}\" ]]; then\n            echo \"TURSO_GO_PLATFORM_LIBS_TOKEN is not set; cannot publish.\"\n            exit 1\n          fi\n\n      # Trigger turso-go-platform-libs build+publish workflow\n      - name: Trigger external prebuilt-libs workflow\n        if: steps.vars.outputs.do_publish == 'true'\n        shell: bash\n        env:\n          TURSO_REF: ${{ steps.vars.outputs.turso_ref }}\n          NONCE: ${{ steps.vars.outputs.nonce }}\n        run: |\n          set -euo pipefail\n          echo \"Triggering tursodatabase/turso-go-platform-libs build.yml with:\"\n          echo \" - turso_ref=$TURSO_REF\"\n          echo \" - push_changes=true\"\n          echo \" - nonce=$NONCE\"\n\n          # Use gh to dispatch workflow\n          gh workflow run build.yml \\\n            -R tursodatabase/turso-go-platform-libs \\\n            -f turso_ref=\"$TURSO_REF\" \\\n            -f push_changes=true \\\n            -f nonce=\"$NONCE\"\n\n      # Wait for the external workflow to complete by locating the run that has our nonce in job names\n      - name: Wait for external workflow completion\n        if: steps.vars.outputs.do_publish == 'true'\n        id: wait_external\n        shell: bash\n        env:\n          NONCE: ${{ steps.vars.outputs.nonce }}\n        run: |\n          set -euo pipefail\n\n          REPO=\"tursodatabase/turso-go-platform-libs\"\n          WORKFLOW_FILE=\"build.yml\"\n\n          echo \"Searching for workflow run in $REPO matching nonce=$NONCE\"\n\n          # Try to find the run that includes the nonce in job names.\n          FOUND_RUN_ID=\"\"\n          ATTEMPTS=0\n          MAX_ATTEMPTS=60   # ~15 minutes (with 15s sleep)\n          while [[ -z \"$FOUND_RUN_ID\" && $ATTEMPTS -lt $MAX_ATTEMPTS ]]; do\n            ATTEMPTS=$((ATTEMPTS+1))\n            # Fetch recent runs of the workflow\n            RUN_IDS=$(gh api -H \"Accept: application/vnd.github+json\" \\\n              repos/$REPO/actions/workflows/$WORKFLOW_FILE/runs \\\n              -q '.workflow_runs[0:30][] | .id' || true)\n\n            for RID in $RUN_IDS; do\n              # List jobs and search for nonce in any job name\n              if gh api -H \"Accept: application/vnd.github+json\" repos/$REPO/actions/runs/$RID/jobs -q '.jobs[].name' | grep -q \"nonce=${NONCE}\"; then\n                FOUND_RUN_ID=\"$RID\"\n                break\n              fi\n            done\n\n            if [[ -z \"$FOUND_RUN_ID\" ]]; then\n              echo \"Nonce not found yet (attempt $ATTEMPTS/$MAX_ATTEMPTS). Sleeping 15s...\"\n              sleep 15\n            fi\n          done\n\n          if [[ -z \"$FOUND_RUN_ID\" ]]; then\n            echo \"Failed to locate external workflow run with nonce=$NONCE\"\n            exit 1\n          fi\n\n          echo \"Found external run id: $FOUND_RUN_ID\"\n          echo \"run_id=$FOUND_RUN_ID\" >> \"$GITHUB_OUTPUT\"\n\n          echo \"Waiting for external run to complete...\"\n          while true; do\n            STATUS=$(gh api repos/$REPO/actions/runs/$FOUND_RUN_ID -q '.status')\n            CONCLUSION=$(gh api repos/$REPO/actions/runs/$FOUND_RUN_ID -q '.conclusion')\n            echo \"Status: $STATUS, Conclusion: $CONCLUSION\"\n            if [[ \"$STATUS\" == \"completed\" ]]; then\n              if [[ \"$CONCLUSION\" != \"success\" ]]; then\n                echo \"External workflow did not succeed. Conclusion: $CONCLUSION\"\n                exit 1\n              fi\n              break\n            fi\n            sleep 15\n          done\n\n      - name: Determine libs branch name from turso_ref\n        if: steps.vars.outputs.do_publish == 'true'\n        id: libs_branch\n        shell: bash\n        env:\n          TURSO_REF: ${{ steps.vars.outputs.turso_ref }}\n        run: |\n          set -euo pipefail\n          BRANCH=\"turso-branch-$TURSO_REF\"\n          echo \"branch=$BRANCH\" >> \"$GITHUB_OUTPUT\"\n          echo \"Resolved libs branch: $BRANCH\"\n\n      - name: Setup Go (for dependency update)\n        if: steps.vars.outputs.do_publish == 'true'\n        uses: actions/setup-go@v5\n        with:\n          go-version: \"1.24.10\"\n\n      - name: Create branch\n        if: steps.vars.outputs.do_publish == 'true'\n        shell: bash\n        working-directory: bindings/go\n        env:\n          TARGET_TAG: ${{ steps.vars.outputs.target_tag }}\n          TARGET_BRANCH: ${{ steps.vars.outputs.target_branch }}\n          NONCE: ${{ steps.vars.outputs.nonce }}\n          RESOLVED_VERSION: ${{ steps.update_dep.outputs.resolved_version }}\n        run: |\n          set -euo pipefail\n\n          git config user.name \"github-actions[bot]\"\n          git config user.email \"github-actions[bot]@users.noreply.github.com\"\n\n          # Create/update branch\n          # If branch exists locally/remote, check it out; otherwise create\n          if git ls-remote --exit-code --heads origin \"$TARGET_BRANCH\" >/dev/null 2>&1; then\n            echo \"Branch $TARGET_BRANCH exists on remote — checking it out.\"\n            git fetch origin \"$TARGET_BRANCH\":\"$TARGET_BRANCH\"\n            git checkout \"$TARGET_BRANCH\"\n          else\n            echo \"Creating new branch $TARGET_BRANCH\"\n            git checkout -b \"$TARGET_BRANCH\"\n          fi\n\n      - name: Update dependency to freshly built libs\n        if: steps.vars.outputs.do_publish == 'true'\n        id: update_dep\n        working-directory: bindings/go\n        shell: bash\n        env:\n          LIBS_BRANCH: ${{ steps.libs_branch.outputs.branch }}\n        run: |\n          set -euo pipefail\n          echo \"Updating github.com/tursodatabase/turso-go-platform-libs to branch: $LIBS_BRANCH\"\n\n          # Update dependency and tidy\n          go get \"github.com/tursodatabase/turso-go-platform-libs@${LIBS_BRANCH}\"\n          go mod tidy\n\n          # Determine the resolved pseudo-version\n          RESOLVED_VERSION=\"$(go list -m -json github.com/tursodatabase/turso-go-platform-libs | jq -r .Version)\"\n          echo \"resolved_version=$RESOLVED_VERSION\" >> \"$GITHUB_OUTPUT\"\n          echo \"Resolved version: $RESOLVED_VERSION\"\n\n          echo \"Changed files:\"\n          git status --porcelain\n\n      - name: Push branch and tag with updated go.mod\n        if: steps.vars.outputs.do_publish == 'true'\n        shell: bash\n        working-directory: bindings/go\n        env:\n          TARGET_TAG: ${{ steps.vars.outputs.target_tag }}\n          TARGET_BRANCH: ${{ steps.vars.outputs.target_branch }}\n          NONCE: ${{ steps.vars.outputs.nonce }}\n          RESOLVED_VERSION: ${{ steps.update_dep.outputs.resolved_version }}\n        run: |\n          set -euo pipefail\n\n          git config user.name \"github-actions[bot]\"\n          git config user.email \"github-actions[bot]@users.noreply.github.com\"\n\n          # Stage changes if any\n          git add go.mod go.sum || true\n\n          if git diff --cached --quiet; then\n            echo \"No changes in go.mod/go.sum to commit; skipping push.\"\n            exit 0\n          fi\n\n          COMMIT_MSG=\"go: update turso-go-platform-libs to ${RESOLVED_VERSION} (nonce: ${NONCE})\"\n          git commit -m \"$COMMIT_MSG\"\n\n          # Push branch\n          git push --set-upstream origin \"$TARGET_BRANCH\" --force-with-lease || git push --set-upstream origin \"$TARGET_BRANCH\"\n\n          echo \"Pushed branch: $TARGET_BRANCH\"\n\n          if [[ \"$TARGET_TAG\" =~ ^v[0-9]+\\.[0-9]+\\.[0-9]+([\\-+][0-9A-Za-z\\.-]+)?$ ]]; then\n            # module is in the subdirectory - prepend path to the version tag\n            git tag \"bindings/go/$TARGET_TAG\"\n            git push origin tag \"bindings/go/$TARGET_TAG\"\n          fi\n"
  },
  {
    "path": ".github/workflows/java-publish.yml",
    "content": "name: Publish Java Bindings to Maven Central\n\non:\n  # Manually trigger the workflow\n  workflow_dispatch:\n\nenv:\n  working-directory: bindings/java\n\njobs:\n  # Build native libraries for each platform\n  build-natives:\n    strategy:\n      matrix:\n        include:\n          - os: ubuntu-latest\n            target: x86_64-unknown-linux-gnu\n            make-target: linux_x86\n            artifact-name: linux-x86_64\n          - os: macos-latest\n            target: x86_64-apple-darwin\n            make-target: macos_x86\n            artifact-name: macos-x86_64\n          - os: macos-latest\n            target: aarch64-apple-darwin\n            make-target: macos_arm64\n            artifact-name: macos-arm64\n          - os: ubuntu-latest\n            target: x86_64-pc-windows-gnu\n            make-target: windows\n            artifact-name: windows-x86_64\n\n    runs-on: ${{ matrix.os }}\n    timeout-minutes: 30\n\n    defaults:\n      run:\n        working-directory: ${{ env.working-directory }}\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n\n      - name: Install Rust\n        uses: dtolnay/rust-toolchain@stable\n        with:\n          targets: ${{ matrix.target }}\n\n      - name: Verify and install Rust target\n        run: |\n          echo \"Installing target: ${{ matrix.target }}\"\n          rustup target add ${{ matrix.target }}\n          echo \"Installed targets:\"\n          rustup target list --installed\n          echo \"Rust version:\"\n          rustc --version\n\n      - name: Install cross-compilation tools (Windows on Linux)\n        if: matrix.target == 'x86_64-pc-windows-gnu'\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y mingw-w64\n\n      - name: Build native library\n        run: make ${{ matrix.make-target }}\n\n      - name: Verify build output\n        run: |\n          echo \"Build completed for ${{ matrix.target }}\"\n          ls -lah libs/\n          find libs/ -type f\n\n      - name: Upload native library\n        uses: actions/upload-artifact@v4\n        with:\n          name: native-${{ matrix.artifact-name }}\n          path: ${{ env.working-directory }}/libs/\n          retention-days: 1\n\n  # Publish to Maven Central with all native libraries\n  publish:\n    needs: build-natives\n    runs-on: ubuntu-latest\n    timeout-minutes: 30\n\n    defaults:\n      run:\n        working-directory: ${{ env.working-directory }}\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n\n      - name: Set up JDK\n        uses: actions/setup-java@v4\n        with:\n          distribution: 'temurin'\n          java-version: '8'\n\n      - name: Setup Gradle\n        uses: gradle/actions/setup-gradle@v3\n\n      - name: Install Rust (for test builds)\n        uses: dtolnay/rust-toolchain@stable\n\n      - name: Download all native libraries\n        uses: actions/download-artifact@v4\n        with:\n          pattern: native-*\n          path: ${{ env.working-directory }}/libs-temp\n          merge-multiple: true\n\n      - name: Organize native libraries\n        run: |\n          # Move downloaded artifacts to libs directory\n          rm -rf libs\n          mv libs-temp libs\n          echo \"Native libraries collected:\"\n          ls -R libs/\n\n      - name: Build test natives\n        run: make build_test\n\n      - name: Run tests\n        run: ./gradlew test\n\n      - name: Publish to Maven Central\n        env:\n          MAVEN_UPLOAD_USERNAME: ${{ secrets.MAVEN_UPLOAD_USERNAME }}\n          MAVEN_UPLOAD_PASSWORD: ${{ secrets.MAVEN_UPLOAD_PASSWORD }}\n          MAVEN_SIGNING_KEY: ${{ secrets.MAVEN_SIGNING_KEY }}\n          MAVEN_SIGNING_PASSPHRASE: ${{ secrets.MAVEN_SIGNING_PASSPHRASE }}\n        run: |\n          echo \"Building, signing, and publishing to Maven Central...\"\n          ./gradlew clean publishToMavenCentral --no-daemon --stacktrace\n\n      - name: Upload bundle artifact\n        if: always()\n        uses: actions/upload-artifact@v4\n        with:\n          name: maven-central-bundle\n          path: ${{ env.working-directory }}/build/maven-central/*.zip\n          retention-days: 7"
  },
  {
    "path": ".github/workflows/java.yml",
    "content": "name: Java Tests\n\non:\n  push:\n    branches:\n      - main\n    tags:\n      - v*\n  pull_request:\n    branches:\n      - main\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}\n  cancel-in-progress: true\n\nenv:\n  working-directory: bindings/java\n  CARGO_INCREMENTAL: \"0\"\n  CARGO_NET_RETRY: 10\n\njobs:\n  test:\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    timeout-minutes: 30\n\n    defaults:\n      run:\n        working-directory: ${{ env.working-directory }}\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n\n      - name: Install Rust(stable)\n        uses: dtolnay/rust-toolchain@stable\n\n      - name: Setup mold linker\n        uses: rui314/setup-mold@v1\n\n      - name: Rust cache\n        uses: Swatinem/rust-cache@v2\n        with:\n          prefix-key: \"v1-rust\"\n          cache-on-failure: true\n\n      - uses: \"./.github/shared/setup-sccache\"\n\n      - name: Set up JDK\n        uses: useblacksmith/setup-java@v5\n        with:\n          distribution: 'temurin'\n          java-version: '8'\n\n      - name: Run Java tests\n        run: make test\n"
  },
  {
    "path": ".github/workflows/labeler.yml",
    "content": "name: \"Pull Request Labeler\"\non:\n  - pull_request_target\n\njobs:\n  labeler:\n    timeout-minutes: 30\n    permissions:\n      contents: read\n      issues: write\n      pull-requests: write\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/labeler@v5\n"
  },
  {
    "path": ".github/workflows/napi.yml",
    "content": "name: Build & publish @tursodatabase/database\n\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - main\n    tags:\n      - v*\n  pull_request:\n    branches:\n      - main\n\nenv:\n  DEBUG: napi:*\n  APP_NAME: turso\n  MACOSX_DEPLOYMENT_TARGET: \"10.13\"\n  CARGO_INCREMENTAL: \"0\"\n  CARGO_NET_RETRY: 10\n\ndefaults:\n  run:\n    working-directory: bindings/javascript\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n\njobs:\n  build:\n    timeout-minutes: 20\n    strategy:\n      fail-fast: false\n      matrix:\n        settings:\n          - host: windows-latest\n            target: x86_64-pc-windows-msvc\n            artifact: db-bindings-x86_64-pc-windows-msvc\n            build: yarn workspace @tursodatabase/database napi-build --target x86_64-pc-windows-msvc\n          - host: windows-latest\n            target: x86_64-pc-windows-msvc\n            artifact: sync-bindings-x86_64-pc-windows-msvc\n            build: yarn workspace @tursodatabase/sync napi-build --target x86_64-pc-windows-msvc\n          - host: ubuntu-latest\n            target: x86_64-unknown-linux-gnu\n            artifact: db-bindings-x86_64-unknown-linux-gnu\n            docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian\n            build: yarn workspace @tursodatabase/database napi-build --target x86_64-unknown-linux-gnu\n          - host: ubuntu-latest\n            target: x86_64-unknown-linux-gnu\n            artifact: sync-bindings-x86_64-unknown-linux-gnu\n            docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian\n            build: yarn workspace @tursodatabase/sync napi-build --target x86_64-unknown-linux-gnu\n          - host: macos-latest\n            target: aarch64-apple-darwin\n            artifact: db-bindings-aarch64-apple-darwin\n            build: yarn workspace @tursodatabase/database napi-build --target aarch64-apple-darwin\n          - host: macos-latest\n            target: aarch64-apple-darwin\n            artifact: sync-bindings-aarch64-apple-darwin\n            build: yarn workspace @tursodatabase/sync napi-build --target aarch64-apple-darwin\n          - host: blacksmith-2vcpu-ubuntu-2404-arm\n            target: aarch64-unknown-linux-gnu\n            artifact: db-bindings-aarch64-unknown-linux-gnu\n            build: yarn workspace @tursodatabase/database napi-build --target aarch64-unknown-linux-gnu\n          - host: blacksmith-2vcpu-ubuntu-2404-arm\n            target: aarch64-unknown-linux-gnu\n            artifact: sync-bindings-aarch64-unknown-linux-gnu\n            build: yarn workspace @tursodatabase/sync napi-build --target aarch64-unknown-linux-gnu\n          - host: ubuntu-latest\n            target: wasm32-wasip1-threads\n            artifact: db-bindings-wasm32-wasip1-threads\n            setup: |\n              rustup target add wasm32-wasip1-threads\n              wget https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-x86_64-linux.tar.gz\n              tar -xvf wasi-sdk-25.0-x86_64-linux.tar.gz\n            build: |\n              export WASI_SDK_PATH=\"$(pwd)/wasi-sdk-25.0-x86_64-linux\"\n              export CMAKE_BUILD_PARALLEL_LEVEL=$(nproc)\n              export TARGET_CXXFLAGS=\"--target=wasm32-wasi-threads --sysroot=$(pwd)/wasi-sdk-25.0-x86_64-linux/share/wasi-sysroot -pthread -mllvm -wasm-enable-sjlj -lsetjmp\"\n              export TARGET_CFLAGS=\"$TARGET_CXXFLAGS\"\n              yarn workspace @tursodatabase/database-common build\n              yarn workspace @tursodatabase/database-wasm-common build\n              yarn workspace @tursodatabase/database-wasm build\n          - host: ubuntu-latest\n            target: wasm32-wasip1-threads\n            artifact: sync-bindings-wasm32-wasip1-threads\n            setup: |\n              rustup target add wasm32-wasip1-threads\n              wget https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-x86_64-linux.tar.gz\n              tar -xvf wasi-sdk-25.0-x86_64-linux.tar.gz\n            build: |\n              export WASI_SDK_PATH=\"$(pwd)/wasi-sdk-25.0-x86_64-linux\"\n              export CMAKE_BUILD_PARALLEL_LEVEL=$(nproc)\n              export TARGET_CXXFLAGS=\"--target=wasm32-wasi-threads --sysroot=$(pwd)/wasi-sdk-25.0-x86_64-linux/share/wasi-sysroot -pthread -mllvm -wasm-enable-sjlj -lsetjmp\"\n              export TARGET_CFLAGS=\"$TARGET_CXXFLAGS\"\n              yarn workspace @tursodatabase/database-common build\n              yarn workspace @tursodatabase/database-wasm-common build\n              yarn workspace @tursodatabase/sync-common build\n              yarn workspace @tursodatabase/sync-wasm build\n    name: ${{ matrix.settings.artifact }} - node@20\n    runs-on: ${{ matrix.settings.host }}\n    steps:\n      - uses: actions/checkout@v4\n      - name: Setup node\n        uses: actions/setup-node@v4\n        if: ${{ !matrix.settings.docker }}\n        with:\n          node-version: 20\n      - name: Install\n        uses: dtolnay/rust-toolchain@stable\n        if: ${{ !matrix.settings.docker }}\n        with:\n          toolchain: stable\n          targets: ${{ matrix.settings.target }}\n      - name: Cache cargo\n        uses: actions/cache@v4\n        with:\n          path: |\n            ~/.cargo/registry/index/\n            ~/.cargo/registry/cache/\n            ~/.cargo/git/db/\n            .cargo-cache\n            target/\n          key: ${{ matrix.settings.target }}-cargo-${{ matrix.settings.host }}-${{ hashFiles('**/Cargo.lock') }}\n          restore-keys: |\n            ${{ matrix.settings.target }}-cargo-${{ matrix.settings.host }}-\n          save-always: true\n      - uses: mlugg/setup-zig@v2\n        if: ${{ matrix.settings.target == 'armv7-unknown-linux-gnueabihf' || matrix.settings.target == 'armv7-unknown-linux-musleabihf' }}\n        with:\n          version: 0.13.0\n      - name: Setup toolchain\n        run: ${{ matrix.settings.setup }}\n        if: ${{ matrix.settings.setup }}\n        shell: bash\n      - name: Install dependencies\n        run: yarn install\n      - name: Build common\n        run: yarn workspace @tursodatabase/database-common build\n      - name: Setup node x86\n        uses: actions/setup-node@v4\n        if: matrix.settings.target == 'x86_64-pc-windows-msvc'\n        with:\n          node-version: 20\n          architecture: x64\n      - name: Build in docker\n        if: ${{ matrix.settings.docker }}\n        shell: bash\n        working-directory: .\n        run: |\n          docker run --rm --user 0:0 \\\n            -v ${{ github.workspace }}/.cargo-cache/git/db:/usr/local/cargo/git/db \\\n            -v ${{ github.workspace }}/.cargo/registry/cache:/usr/local/cargo/registry/cache \\\n            -v ${{ github.workspace }}/.cargo/registry/index:/usr/local/cargo/registry/index \\\n            -v ${{ github.workspace }}:/build \\\n            -w /build/bindings/javascript \\\n            ${{ matrix.settings.docker }} \\\n            bash -c \"${{ matrix.settings.build }}\"\n      - name: Build\n        run: ${{ matrix.settings.build }}\n        if: ${{ !matrix.settings.docker }}\n        shell: bash\n      - name: Upload artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: ${{ matrix.settings.artifact }}\n          path: |\n            bindings/javascript/packages/native/turso.*.node\n            bindings/javascript/packages/wasm/turso.*.wasm\n            bindings/javascript/sync/packages/native/sync.*.node\n            bindings/javascript/sync/packages/wasm/sync.*.wasm\n          if-no-files-found: error\n  test-db-linux-x64-gnu-binding:\n    name: Test DB bindings on Linux-x64-gnu - node@${{ matrix.node }}\n    timeout-minutes: 30\n    needs:\n      - build\n    strategy:\n      fail-fast: false\n      matrix:\n        node:\n          - \"20\"\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    steps:\n      - uses: actions/checkout@v4\n      - name: Setup node\n        uses: useblacksmith/setup-node@v5\n        with:\n          node-version: ${{ matrix.node }}\n      - name: Install dependencies\n        run: yarn install\n      - name: Build common\n        run: yarn workspace @tursodatabase/database-common build\n      - name: Download all DB artifacts\n        uses: actions/download-artifact@v4\n        with:\n          path: bindings/javascript\n          merge-multiple: true\n          pattern: 'db*'\n      - name: List packages\n        run: ls -R .\n        shell: bash\n      - name: Test bindings\n        run: docker run --rm -v $(pwd):/build -w /build node:${{ matrix.node }}-slim yarn workspace @tursodatabase/database test\n  test-db-wasm-binding:\n    name: Test DB bindings on browser@${{ matrix.node }}\n    timeout-minutes: 30\n    needs:\n      - build\n    strategy:\n      fail-fast: false\n      matrix:\n        node:\n          - \"20\"\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    steps:\n      - uses: actions/checkout@v4\n      - name: Setup node\n        uses: useblacksmith/setup-node@v5\n        with:\n          node-version: ${{ matrix.node }}\n      - name: Install dependencies\n        run: yarn install\n      - name: Build common\n        run: yarn workspace @tursodatabase/database-common build\n      - name: Build wasm-common\n        run: yarn workspace @tursodatabase/database-wasm-common build\n      - name: Install playwright with deps\n        run: yarn workspace @tursodatabase/database-wasm playwright install --with-deps\n      - name: Download all DB artifacts\n        uses: actions/download-artifact@v4\n        with:\n          path: bindings/javascript\n          merge-multiple: true\n          pattern: 'db*'\n      - name: List packages\n        run: ls -R .\n        shell: bash\n      - name: Test bindings\n        run: yarn workspace @tursodatabase/database-wasm test\n  publish:\n    name: Publish\n    runs-on: ubuntu-latest\n    timeout-minutes: 30\n    permissions:\n      contents: read\n      id-token: write\n    needs:\n      - test-db-linux-x64-gnu-binding\n      - test-db-wasm-binding\n    steps:\n      - uses: actions/checkout@v4\n      - name: Setup node\n        uses: actions/setup-node@v4\n        with:\n          node-version: 20\n      - name: Update npm\n        run: npm install -g npm@11\n      - name: Download all DB artifacts\n        uses: actions/download-artifact@v4\n        with:\n          path: bindings/javascript\n          merge-multiple: true\n          pattern: 'db*'\n      - name: Download all sync artifacts\n        uses: actions/download-artifact@v4\n        with:\n          path: bindings/javascript\n          merge-multiple: true\n          pattern: 'sync*'\n      - name: List packages\n        run: ls -R .\n        shell: bash\n      - name: Install dependencies\n        run: yarn install\n      - name: Install dependencies\n        run: yarn tsc-build\n      - name: Publish\n        if: \"startsWith(github.ref, 'refs/tags/v')\"\n        run: |\n          if git log -1 --pretty=%B | grep \"^Turso [0-9]\\+\\.[0-9]\\+\\.[0-9]\\+$\";\n          then\n            npm publish --workspaces --access public --provenance\n          elif git log -1 --pretty=%B | grep \"^Turso [0-9]\\+\\.[0-9]\\+\\.[0-9]\\+\";\n          then\n            npm publish --workspaces --access public --provenance --tag next\n          else\n            echo \"git log structure is unexpected, skip publishing\"\n            npm publish --workspaces --dry-run\n          fi\n      - name: Publish (dry-run)\n        if: \"!startsWith(github.ref, 'refs/tags/v')\"\n        run: |\n          npm pack --workspaces\n"
  },
  {
    "path": ".github/workflows/perf_nightly.yml",
    "content": "name: Nightly Benchmarks on Nyrkiö Runners (stability)\n\non:\n  workflow_dispatch:\n    branches: [\"main\", \"notmain\", \"master\"]\n  schedule:\n    - cron: '24 4 * * *'\n  push:\n  #   branches: [\"main\", \"notmain\", \"master\"]\n    branches: [\"notmain\"]\n  pull_request:\n  #   branches: [\"main\", \"notmain\", \"master\"]\n    branches: [\"notmain\"]\n\nenv:\n  CARGO_TERM_COLOR: never\n\njobs:\n  bench:\n    runs-on: nyrkio_perf_server_4cpu_ubuntu2404\n    timeout-minutes: 45 # FIXME: make this run faster\n    steps:\n      - uses: actions/checkout@v3\n      - uses: useblacksmith/setup-node@v5\n        with:\n          node-version: 20\n      #     cache: 'npm'\n      # - name: Install dependencies\n      #   run: npm install && npm run build\n\n      - name: Generate graph-queries DB\n        run: |\n          python3 perf/graph-queries/generate_seed.py | sqlite3 perf/graph-queries/graph-queries.db\n          cp perf/graph-queries/graph-queries.db perf/graph-queries/graph-queries-analyzed.db\n          sqlite3 perf/graph-queries/graph-queries-analyzed.db \"ANALYZE;\"\n\n      - name: Bench\n        run: make bench-exclude-tpc-h  2>&1 | tee output.txt\n      - name: Analyze benchmark result with Nyrkiö\n        uses: nyrkio/change-detection@HEAD\n        with:\n          name: nightly/turso\n          tool: criterion\n          output-file-path: output.txt\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n\n          # What to do if a change is immediately detected by Nyrkiö.\n          # Note that smaller changes are only detected with delay, usually after a change\n          # persisted over 2-7 commits. Go to nyrkiö.com to view those or configure alerts.\n          # Note that Nyrkiö will find all changes, also improvements. This means fail-on-alert\n          # on pull events isn't compatible with this workflow being required to pass branch protection.\n          fail-on-alert: false\n          comment-on-alert: false\n          comment-always: false\n          # Nyrkiö configuration\n          # Get yours from https://nyrkio.com/docs/getting-started\n          nyrkio-token: ${{ secrets.NYRKIO_JWT_TOKEN }}\n          # HTTP requests will fail for all non-core contributors that don't have their own token.\n          # Don't want that to spoil the build, so:\n          never-fail: true\n          # Make results and change points public, so that any oss contributor can see them\n          nyrkio-public: true\n\n          # parameters of the algorithm. Note: These are global, so we only set them once and for all.\n          # Smaller p-value = less change points found. Larger p-value = more, but also more false positives.\n          nyrkio-settings-pvalue: 0.00001\n          # Ignore changes smaller than this.\n          nyrkio-settings-threshold: 0%\n\n  clickbench:\n    runs-on: nyrkio_perf_server_4cpu_ubuntu2404\n    timeout-minutes: 30\n    steps:\n      - uses: actions/checkout@v3\n      - uses: useblacksmith/setup-node@v5\n        with:\n          node-version: 20\n\n      - name: Clickbench\n        run: make clickbench\n\n      - name: Analyze TURSO result with Nyrkiö\n        uses: nyrkio/change-detection@HEAD\n        with:\n          name: nightly/clickbench/turso\n          tool: time\n          output-file-path: clickbench-tursodb.txt\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n          # What to do if a change is immediately detected by Nyrkiö.\n          # Note that smaller changes are only detected with delay, usually after a change\n          # persisted over 2-7 commits. Go to nyrkiö.com to view those or configure alerts.\n          # Note that Nyrkiö will find all changes, also improvements. This means fail-on-alert\n          # on pull events isn't compatible with this workflow being required to pass branch protection.\n          fail-on-alert: false\n          comment-on-alert: false\n          comment-always: false\n          # Nyrkiö configuration\n          # Get yours from https://nyrkio.com/docs/getting-started\n          nyrkio-token: ${{ secrets.NYRKIO_JWT_TOKEN }}\n          # HTTP requests will fail for all non-core contributors that don't have their own token.\n          # Don't want that to spoil the build, so:\n          never-fail: true\n          # Make results and change points public, so that any oss contributor can see them\n          nyrkio-public: true\n          nyrkio-settings-pvalue: 0.00001\n          nyrkio-settings-threshold: 0%\n\n      - name: Analyze SQLITE3 result with Nyrkiö\n        uses: nyrkio/change-detection@HEAD\n        with:\n          name: nightly/clickbench/sqlite3\n          tool: time\n          output-file-path: clickbench-sqlite3.txt\n          fail-on-alert: false\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n          comment-on-alert: false\n          comment-always: false\n          nyrkio-token: ${{ secrets.NYRKIO_JWT_TOKEN }}\n          never-fail: true\n          nyrkio-public: true\n          nyrkio-settings-pvalue: 0.00001\n          nyrkio-settings-threshold: 0%\n\n  tpc-h-criterion:\n    runs-on: nyrkio_perf_server_4cpu_ubuntu2404\n    timeout-minutes: 60\n    env:\n      DB_FILE: \"perf/tpc-h/TPC-H.db\"\n    steps:\n      - uses: actions/checkout@v3\n      - uses: Swatinem/rust-cache@v2\n        with:\n          prefix-key: \"v1-rust\"\n          cache-on-failure: true\n\n      - name: Cache TPC-H\n        id: cache-primes\n        uses: useblacksmith/cache@v5\n        with:\n          path: ${{ env.DB_FILE }}\n          key: tpc-h\n      - name: Download TPC-H\n        if: steps.cache-primes.outputs.cache-hit != 'true'\n        env:\n          DB_URL: \"https://github.com/lovasoa/TPCH-sqlite/releases/download/v1.0/TPC-H.db\"\n        run: wget -O $DB_FILE --no-verbose $DB_URL\n\n      - name: Bench\n        run: cargo bench --bench tpc_h_benchmark  2>&1 | tee output.txt\n      - name: Analyze benchmark result with Nyrkiö\n        uses: nyrkio/change-detection@HEAD\n        with:\n          name: nightly/tpc-h\n          tool: criterion\n          output-file-path: output.txt\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n\n          # What to do if a change is immediately detected by Nyrkiö.\n          # Note that smaller changes are only detected with delay, usually after a change\n          # persisted over 2-7 commits. Go to nyrkiö.com to view those or configure alerts.\n          # Note that Nyrkiö will find all changes, also improvements. This means fail-on-alert\n          # on pull events isn't compatible with this workflow being required to pass branch protection.\n          fail-on-alert: false\n          comment-on-alert: false\n          comment-always: false\n          # Nyrkiö configuration\n          # Get yours from https://nyrkio.com/docs/getting-started\n          nyrkio-token: ${{ secrets.NYRKIO_JWT_TOKEN }}\n          # HTTP requests will fail for all non-core contributors that don't have their own token.\n          # Don't want that to spoil the build, so:\n          never-fail: true\n          # Make results and change points public, so that any oss contributor can see them\n          nyrkio-public: true\n\n          # parameters of the algorithm. Note: These are global, so we only set them once and for all.\n          # Smaller p-value = less change points found. Larger p-value = more, but also more false positives.\n          nyrkio-settings-pvalue: 0.00001\n          nyrkio-settings-threshold: 0%\n\n\n"
  },
  {
    "path": ".github/workflows/publish-crates.yml",
    "content": "name: Publish Crates\n\non:\n  push:\n    tags:\n      - '**[0-9]+.[0-9]+.[0-9]+*'\n\njobs:\n  publish:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          submodules: recursive\n\n      - uses: dtolnay/rust-toolchain@stable\n\n      - name: Publish crates\n        env:\n          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}\n        run: ./scripts/publish-crates.sh\n"
  },
  {
    "path": ".github/workflows/python.yml",
    "content": "name: Python\n\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - main\n    tags:\n      - v*\n  pull_request:\n    branches:\n      - main\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}\n  cancel-in-progress: true\n\nenv:\n  working-directory: bindings/python\n  PIP_DISABLE_PIP_VERSION_CHECK: \"true\"\n  CARGO_INCREMENTAL: \"0\"\n  CARGO_NET_RETRY: 10\n\njobs:\n  configure-strategy:\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    timeout-minutes: 30\n    outputs:\n      python-versions: ${{ steps.gen-matrix.outputs.python-versions }}\n    steps:\n      - id: gen-matrix\n        run: |\n          echo \"python-versions=[\\\"3.9\\\",\\\"3.13\\\"]\" >> $GITHUB_OUTPUT\n\n  test:\n    needs: configure-strategy\n    timeout-minutes: 30\n    strategy:\n      matrix:\n        os:\n          - blacksmith-4vcpu-ubuntu-2404\n          - macos-latest\n          - windows-latest\n        python-version: ${{ fromJson(needs.configure-strategy.outputs.python-versions) }}\n    runs-on: ${{ matrix.os }}\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n\n      - name: Setup Rust toolchain\n        uses: dtolnay/rust-toolchain@stable\n\n      - name: Setup mold linker\n        if: runner.os == 'Linux'\n        uses: rui314/setup-mold@v1\n\n      - name: Rust cache\n        uses: Swatinem/rust-cache@v2\n        with:\n          prefix-key: \"v1-rust\"\n          cache-on-failure: true\n\n      - uses: \"./.github/shared/setup-sccache\"\n\n      - name: Build Rust (debug)\n        run: cargo build --verbose --locked\n\n      - name: Set up Python ${{ matrix.python-version }}\n        uses: useblacksmith/setup-python@v6\n        with:\n          python-version: ${{ matrix.python-version }}\n\n      - name: Install uv\n        uses: useblacksmith/setup-uv@v4\n        with:\n          enable-cache: true\n\n      - name: Install the project\n        working-directory: ${{ env.working-directory }}\n        run: uv sync --all-extras --dev --all-packages\n\n      - name: Run Pytest\n        working-directory: ${{ env.working-directory }}\n        run: uv run pytest tests/test_database.py\n\n      - name: Run Pytest (async driver)\n        working-directory: ${{ env.working-directory }}\n        run: uv run pytest tests/test_database_aio.py\n\n      - name: Run Pytest (sync)\n        working-directory: ${{ env.working-directory }}\n        if: runner.os != 'Windows'\n        env:\n          LOCAL_SYNC_SERVER: \"../../target/debug/tursodb\"\n        run: uv run pytest tests/test_database_sync.py\n\n      - name: Run Pytest (sync, async driver)\n        working-directory: ${{ env.working-directory }}\n        if: runner.os != 'Windows'\n        env:\n          LOCAL_SYNC_SERVER: \"../../target/debug/tursodb\"\n        run: uv run pytest tests/test_database_sync_aio.py\n\n      - name: Run Pytest (windows, sync)\n        working-directory: ${{ env.working-directory }}\n        if: runner.os == 'Windows'\n        env:\n          LOCAL_SYNC_SERVER: \"..\\\\..\\\\target\\\\debug\\\\tursodb.exe\"\n        run: uv run pytest tests/test_database_sync.py\n\n      - name: Run Pytest (windows, sync, async driver)\n        working-directory: ${{ env.working-directory }}\n        if: runner.os == 'Windows'\n        env:\n          LOCAL_SYNC_SERVER: \"..\\\\..\\\\target\\\\debug\\\\tursodb.exe\"\n        run: uv run pytest tests/test_database_sync_aio.py\n\n  lint:\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    timeout-minutes: 30\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n\n      - name: Set up Python\n        uses: useblacksmith/setup-python@v6\n\n      - name: Install uv\n        uses: useblacksmith/setup-uv@v4\n        with:\n          enable-cache: true\n\n      - name: Install the project\n        run: uv sync --all-extras --dev --all-packages\n\n      - name: Ruff lint\n        run: uvx ruff check\n\n  linux:\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    timeout-minutes: 30\n    defaults:\n      run:\n        working-directory: ${{ env.working-directory }}\n    strategy:\n      matrix:\n        target: [x86_64]\n    steps:\n      - uses: actions/checkout@v3\n      - uses: useblacksmith/setup-python@v6\n        with:\n          python-version: '3.10'\n      - name: Build wheels\n        uses: PyO3/maturin-action@v1\n        with:\n          working-directory: ${{ env.working-directory }}\n          target: ${{ matrix.target }}\n          args: --profile release-official --out dist --find-interpreter\n          sccache: 'true'\n          manylinux: auto\n      - name: Upload wheels\n        uses: actions/upload-artifact@v4\n        with:\n          name: wheels-linux\n          path: bindings/python/dist\n\n  macos-arm64:\n    runs-on: macos-latest\n    timeout-minutes: 30\n    defaults:\n      run:\n        working-directory: ${{ env.working-directory }}\n    strategy:\n      matrix:\n        target: [aarch64]\n    steps:\n      - uses: actions/checkout@v3\n      - uses: actions/setup-python@v4\n        env:\n          CXX: clang++\n          CC: clang\n        with:\n          python-version: '3.10'\n      - name: Build wheels\n        uses: PyO3/maturin-action@v1\n        with:\n          working-directory: ${{ env.working-directory }}\n          target: ${{ matrix.target }}\n          args: --profile release-official --out dist --find-interpreter\n          sccache: 'true'\n      - name: Upload wheels\n        uses: actions/upload-artifact@v4\n        with:\n          name: wheels-macos-arm64\n          path: bindings/python/dist\n\n  sdist:\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    timeout-minutes: 30\n    defaults:\n      run:\n        working-directory: ${{ env.working-directory }}\n    steps:\n      - uses: actions/checkout@v3\n      - name: Build sdist\n        uses: PyO3/maturin-action@v1\n        with:\n          working-directory: ${{ env.working-directory }}\n          command: sdist\n          args: --out dist\n      - name: Upload sdist\n        uses: actions/upload-artifact@v4\n        with:\n          name: wheels-sdist\n          path: bindings/python/dist\n\n  release:\n    name: Release\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    timeout-minutes: 30\n    if: \"startsWith(github.ref, 'refs/tags/')\"\n    needs: [linux, macos-arm64, sdist]\n    steps:\n      - uses: actions/download-artifact@v4\n        with:\n          path: bindings/python/dist\n          pattern: wheels-*\n          merge-multiple: true\n      - name: Publish to PyPI\n        uses: PyO3/maturin-action@v1\n        env:\n          MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}\n        with:\n          working-directory: ${{ env.working-directory }}\n          command: upload\n          args: --skip-existing dist/*\n"
  },
  {
    "path": ".github/workflows/react-native.yml",
    "content": "name: Build & publish @tursodatabase/sync-react-native\n\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - main\n    tags:\n      - v*\n  pull_request:\n    branches:\n      - main\n\nenv:\n  CARGO_INCREMENTAL: \"0\"\n  CARGO_NET_RETRY: 10\n  working-directory: bindings/react-native\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n\njobs:\n  build-ios:\n    name: Build iOS libraries\n    runs-on: macos-latest\n    timeout-minutes: 30\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Install Rust\n        uses: dtolnay/rust-toolchain@stable\n        with:\n          targets: aarch64-apple-ios,aarch64-apple-ios-sim\n\n      - name: Rust cache\n        uses: Swatinem/rust-cache@v2\n        with:\n          prefix-key: \"v1-rust-ios\"\n          cache-on-failure: true\n\n      - name: Build iOS libraries\n        run: make ios-build\n        working-directory: ${{ env.working-directory }}\n\n      - name: Package iOS xcframework\n        run: make package-dylibs\n        working-directory: ${{ env.working-directory }}\n\n      - name: Upload iOS artifacts\n        uses: actions/upload-artifact@v4\n        with:\n          name: ios-libs\n          path: |\n            bindings/react-native/libs/ios/\n          if-no-files-found: error\n\n  build-android:\n    name: Build Android libraries\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    timeout-minutes: 30\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Install Rust\n        uses: dtolnay/rust-toolchain@stable\n        with:\n          targets: aarch64-linux-android,armv7-linux-androideabi,x86_64-linux-android,i686-linux-android\n\n      - name: Rust cache\n        uses: Swatinem/rust-cache@v2\n        with:\n          prefix-key: \"v1-rust-android\"\n          cache-on-failure: true\n\n      - name: Set up Android NDK\n        uses: android-actions/setup-android@v3\n\n      - name: Install cargo-ndk\n        run: cargo install cargo-ndk\n\n      - name: Build Android libraries\n        run: make android\n        working-directory: ${{ env.working-directory }}\n\n      - name: Upload Android artifacts\n        uses: actions/upload-artifact@v4\n        with:\n          name: android-libs\n          path: |\n            bindings/react-native/libs/android/\n          if-no-files-found: error\n\n  build-typescript:\n    name: Build TypeScript\n    runs-on: ubuntu-latest\n    timeout-minutes: 10\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: 20\n\n      - name: Install dependencies\n        run: npm install\n        working-directory: ${{ env.working-directory }}\n\n      - name: Build TypeScript\n        run: npm run build\n        working-directory: ${{ env.working-directory }}\n\n      - name: Type check\n        run: npm run typescript\n        working-directory: ${{ env.working-directory }}\n\n      - name: Upload TypeScript build\n        uses: actions/upload-artifact@v4\n        with:\n          name: typescript-build\n          path: |\n            bindings/react-native/lib/\n          if-no-files-found: error\n\n  publish:\n    name: Publish to npm\n    runs-on: ubuntu-latest\n    timeout-minutes: 15\n    needs:\n      - build-ios\n      - build-android\n      - build-typescript\n    permissions:\n      contents: read\n      id-token: write\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: 20\n\n      - name: Update npm\n        run: npm install -g npm@11\n\n      - name: Download iOS artifacts\n        uses: actions/download-artifact@v4\n        with:\n          name: ios-libs\n          path: bindings/react-native/libs/ios/\n\n      - name: Download Android artifacts\n        uses: actions/download-artifact@v4\n        with:\n          name: android-libs\n          path: bindings/react-native/libs/android/\n\n      - name: Download TypeScript build\n        uses: actions/download-artifact@v4\n        with:\n          name: typescript-build\n          path: bindings/react-native/lib/\n\n      - name: Install dependencies\n        run: npm install\n        working-directory: ${{ env.working-directory }}\n\n      - name: List package contents\n        run: |\n          echo \"=== Package contents ===\"\n          ls -la\n          echo \"=== libs/ios ===\"\n          ls -laR libs/ios/ || echo \"No iOS libs\"\n          echo \"=== libs/android ===\"\n          ls -laR libs/android/ || echo \"No Android libs\"\n          echo \"=== lib (TypeScript) ===\"\n          ls -laR lib/ || echo \"No lib\"\n        working-directory: ${{ env.working-directory }}\n\n      - name: Publish\n        if: startsWith(github.ref, 'refs/tags/v')\n        run: |\n          if git log -1 --pretty=%B | grep \"^Turso [0-9]\\+\\.[0-9]\\+\\.[0-9]\\+$\";\n          then\n            npm publish --access public --provenance\n          elif git log -1 --pretty=%B | grep \"^Turso [0-9]\\+\\.[0-9]\\+\\.[0-9]\\+\";\n          then\n            npm publish --access public --provenance --tag next\n          else\n            echo \"git log structure is unexpected, skip publishing\"\n            npm publish --dry-run\n          fi\n        working-directory: ${{ env.working-directory }}\n\n      - name: Publish (dry-run)\n        if: ${{ !startsWith(github.ref, 'refs/tags/v') }}\n        run: npm pack\n        working-directory: ${{ env.working-directory }}\n\n      - name: Upload package tarball\n        if: ${{ !startsWith(github.ref, 'refs/tags/v') }}\n        uses: actions/upload-artifact@v4\n        with:\n          name: npm-package\n          path: bindings/react-native/*.tgz\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "# This file was autogenerated by dist: https://axodotdev.github.io/cargo-dist\n#\n# Copyright 2022-2024, axodotdev\n# SPDX-License-Identifier: MIT or Apache-2.0\n#\n# CI that:\n#\n# * checks for a Git Tag that looks like a release\n# * builds artifacts with dist (archives, installers, hashes)\n# * uploads those artifacts to temporary workflow zip\n# * on success, uploads the artifacts to a GitHub Release\n#\n# Note that the GitHub Release will be created with a generated\n# title/body based on your changelogs.\n\nname: Release\npermissions:\n  \"contents\": \"write\"\n\n# This task will run whenever you push a git tag that looks like a version\n# like \"1.0.0\", \"v0.1.0-prerelease.1\", \"my-app/0.1.0\", \"releases/v1.0.0\", etc.\n# Various formats will be parsed into a VERSION and an optional PACKAGE_NAME, where\n# PACKAGE_NAME must be the name of a Cargo package in your workspace, and VERSION\n# must be a Cargo-style SemVer Version (must have at least major.minor.patch).\n#\n# If PACKAGE_NAME is specified, then the announcement will be for that\n# package (erroring out if it doesn't have the given version or isn't dist-able).\n#\n# If PACKAGE_NAME isn't specified, then the announcement will be for all\n# (dist-able) packages in the workspace with that version (this mode is\n# intended for workspaces with only one dist-able package, or with all dist-able\n# packages versioned/released in lockstep).\n#\n# If you push multiple tags at once, separate instances of this workflow will\n# spin up, creating an independent announcement for each one. However, GitHub\n# will hard limit this to 3 tags per commit, as it will assume more tags is a\n# mistake.\n#\n# If there's a prerelease-style suffix to the version, then the release(s)\n# will be marked as a prerelease.\non:\n  pull_request:\n  push:\n    tags:\n      - '**[0-9]+.[0-9]+.[0-9]+*'\n\njobs:\n  # Run 'dist plan' (or host) to determine what tasks we need to do\n  plan:\n    runs-on: \"ubuntu-22.04\"\n    outputs:\n      val: ${{ steps.plan.outputs.manifest }}\n      tag: ${{ !github.event.pull_request && github.ref_name || '' }}\n      tag-flag: ${{ !github.event.pull_request && format('--tag={0}', github.ref_name) || '' }}\n      publishing: ${{ !github.event.pull_request }}\n    env:\n      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          persist-credentials: false\n          submodules: recursive\n      - name: Install dist\n        # we specify bash to get pipefail; it guards against the `curl` command\n        # failing. otherwise `sh` won't catch that `curl` returned non-0\n        shell: bash\n        run: \"curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.30.2/cargo-dist-installer.sh | sh\"\n      - name: Cache dist\n        uses: actions/upload-artifact@v4\n        with:\n          name: cargo-dist-cache\n          path: ~/.cargo/bin/dist\n      # sure would be cool if github gave us proper conditionals...\n      # so here's a doubly-nested ternary-via-truthiness to try to provide the best possible\n      # functionality based on whether this is a pull_request, and whether it's from a fork.\n      # (PRs run on the *source* but secrets are usually on the *target* -- that's *good*\n      # but also really annoying to build CI around when it needs secrets to work right.)\n      - id: plan\n        run: |\n          dist ${{ (!github.event.pull_request && format('host --steps=create --tag={0}', github.ref_name)) || 'plan' }} --output-format=json > plan-dist-manifest.json\n          echo \"dist ran successfully\"\n          cat plan-dist-manifest.json\n          echo \"manifest=$(jq -c \".\" plan-dist-manifest.json)\" >> \"$GITHUB_OUTPUT\"\n      - name: \"Upload dist-manifest.json\"\n        uses: actions/upload-artifact@v4\n        with:\n          name: artifacts-plan-dist-manifest\n          path: plan-dist-manifest.json\n\n  # Build and packages all the platform-specific things\n  build-local-artifacts:\n    name: build-local-artifacts (${{ join(matrix.targets, ', ') }})\n    # Let the initial task tell us to not run (currently very blunt)\n    needs:\n      - plan\n    if: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix.include != null && (needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload') }}\n    strategy:\n      fail-fast: false\n      # Target platforms/runners are computed by dist in create-release.\n      # Each member of the matrix has the following arguments:\n      #\n      # - runner: the github runner\n      # - dist-args: cli flags to pass to dist\n      # - install-dist: expression to run to install dist on the runner\n      #\n      # Typically there will be:\n      # - 1 \"global\" task that builds universal installers\n      # - N \"local\" tasks that build each platform's binaries and platform-specific installers\n      matrix: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix }}\n    runs-on: ${{ matrix.runner }}\n    container: ${{ matrix.container && matrix.container.image || null }}\n    env:\n      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json\n    permissions:\n      \"attestations\": \"write\"\n      \"contents\": \"read\"\n      \"id-token\": \"write\"\n    steps:\n      - name: enable windows longpaths\n        run: |\n          git config --global core.longpaths true\n      - uses: actions/checkout@v4\n        with:\n          persist-credentials: false\n          submodules: recursive\n      - name: Install Rust non-interactively if not already installed\n        if: ${{ matrix.container }}\n        run: |\n          if ! command -v cargo > /dev/null 2>&1; then\n            curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y\n            echo \"$HOME/.cargo/bin\" >> $GITHUB_PATH\n          fi\n      - name: Install dist\n        run: ${{ matrix.install_dist.run }}\n      # Get the dist-manifest\n      - name: Fetch local artifacts\n        uses: actions/download-artifact@v4\n        with:\n          pattern: artifacts-*\n          path: target/distrib/\n          merge-multiple: true\n      - name: Install dependencies\n        run: |\n          ${{ matrix.packages_install }}\n      - name: Build artifacts\n        run: |\n          # Actually do builds and make zips and whatnot\n          dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifest.json\n          echo \"dist ran successfully\"\n      - name: Attest\n        uses: actions/attest-build-provenance@v2\n        with:\n          subject-path: \"target/distrib/*${{ join(matrix.targets, ', ') }}*\"\n      - id: cargo-dist\n        name: Post-build\n        # We force bash here just because github makes it really hard to get values up\n        # to \"real\" actions without writing to env-vars, and writing to env-vars has\n        # inconsistent syntax between shell and powershell.\n        shell: bash\n        run: |\n          # Parse out what we just built and upload it to scratch storage\n          echo \"paths<<EOF\" >> \"$GITHUB_OUTPUT\"\n          dist print-upload-files-from-manifest --manifest dist-manifest.json >> \"$GITHUB_OUTPUT\"\n          echo \"EOF\" >> \"$GITHUB_OUTPUT\"\n\n          cp dist-manifest.json \"$BUILD_MANIFEST_NAME\"\n      - name: \"Upload artifacts\"\n        uses: actions/upload-artifact@v4\n        with:\n          name: artifacts-build-local-${{ join(matrix.targets, '_') }}\n          path: |\n            ${{ steps.cargo-dist.outputs.paths }}\n            ${{ env.BUILD_MANIFEST_NAME }}\n\n  # Build and package all the platform-agnostic(ish) things\n  build-global-artifacts:\n    needs:\n      - plan\n      - build-local-artifacts\n    runs-on: \"ubuntu-22.04\"\n    env:\n      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          persist-credentials: false\n          submodules: recursive\n      - name: Install cached dist\n        uses: actions/download-artifact@v4\n        with:\n          name: cargo-dist-cache\n          path: ~/.cargo/bin/\n      - run: chmod +x ~/.cargo/bin/dist\n      # Get all the local artifacts for the global tasks to use (for e.g. checksums)\n      - name: Fetch local artifacts\n        uses: actions/download-artifact@v4\n        with:\n          pattern: artifacts-*\n          path: target/distrib/\n          merge-multiple: true\n      - id: cargo-dist\n        shell: bash\n        run: |\n          dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json \"--artifacts=global\" > dist-manifest.json\n          echo \"dist ran successfully\"\n\n          # Parse out what we just built and upload it to scratch storage\n          echo \"paths<<EOF\" >> \"$GITHUB_OUTPUT\"\n          jq --raw-output \".upload_files[]\" dist-manifest.json >> \"$GITHUB_OUTPUT\"\n          echo \"EOF\" >> \"$GITHUB_OUTPUT\"\n\n          cp dist-manifest.json \"$BUILD_MANIFEST_NAME\"\n      - name: \"Upload artifacts\"\n        uses: actions/upload-artifact@v4\n        with:\n          name: artifacts-build-global\n          path: |\n            ${{ steps.cargo-dist.outputs.paths }}\n            ${{ env.BUILD_MANIFEST_NAME }}\n  # Determines if we should publish/announce\n  host:\n    needs:\n      - plan\n      - build-local-artifacts\n      - build-global-artifacts\n    # Only run if we're \"publishing\", and only if plan, local and global didn't fail (skipped is fine)\n    if: ${{ always() && needs.plan.result == 'success' && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }}\n    env:\n      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n    runs-on: \"ubuntu-22.04\"\n    outputs:\n      val: ${{ steps.host.outputs.manifest }}\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          persist-credentials: false\n          submodules: recursive\n      - name: Install cached dist\n        uses: actions/download-artifact@v4\n        with:\n          name: cargo-dist-cache\n          path: ~/.cargo/bin/\n      - run: chmod +x ~/.cargo/bin/dist\n      # Fetch artifacts from scratch-storage\n      - name: Fetch artifacts\n        uses: actions/download-artifact@v4\n        with:\n          pattern: artifacts-*\n          path: target/distrib/\n          merge-multiple: true\n      - id: host\n        shell: bash\n        run: |\n          dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json\n          echo \"artifacts uploaded and released successfully\"\n          cat dist-manifest.json\n          echo \"manifest=$(jq -c \".\" dist-manifest.json)\" >> \"$GITHUB_OUTPUT\"\n      - name: \"Upload dist-manifest.json\"\n        uses: actions/upload-artifact@v4\n        with:\n          # Overwrite the previous copy\n          name: artifacts-dist-manifest\n          path: dist-manifest.json\n      # Create a GitHub Release while uploading all files to it\n      - name: \"Download GitHub Artifacts\"\n        uses: actions/download-artifact@v4\n        with:\n          pattern: artifacts-*\n          path: artifacts\n          merge-multiple: true\n      - name: Cleanup\n        run: |\n          # Remove the granular manifests\n          rm -f artifacts/*-dist-manifest.json\n      - name: Create GitHub Release\n        env:\n          PRERELEASE_FLAG: \"${{ fromJson(steps.host.outputs.manifest).announcement_is_prerelease && '--prerelease' || '' }}\"\n          ANNOUNCEMENT_TITLE: \"${{ fromJson(steps.host.outputs.manifest).announcement_title }}\"\n          ANNOUNCEMENT_BODY: \"${{ fromJson(steps.host.outputs.manifest).announcement_github_body }}\"\n          RELEASE_COMMIT: \"${{ github.sha }}\"\n        run: |\n          # Write and read notes from a file to avoid quoting breaking things\n          echo \"$ANNOUNCEMENT_BODY\" > $RUNNER_TEMP/notes.txt\n\n          gh release create \"${{ needs.plan.outputs.tag }}\" --target \"$RELEASE_COMMIT\" $PRERELEASE_FLAG --title \"$ANNOUNCEMENT_TITLE\" --notes-file \"$RUNNER_TEMP/notes.txt\" artifacts/*\n\n  announce:\n    needs:\n      - plan\n      - host\n    # use \"always() && ...\" to allow us to wait for all publish jobs while\n    # still allowing individual publish jobs to skip themselves (for prereleases).\n    # \"host\" however must run to completion, no skipping allowed!\n    if: ${{ always() && needs.host.result == 'success' }}\n    runs-on: \"ubuntu-22.04\"\n    env:\n      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          persist-credentials: false\n          submodules: recursive\n"
  },
  {
    "path": ".github/workflows/rust.yml",
    "content": "name: Rust\n\non:\n  workflow_dispatch:\n  push:\n    branches: [\"main\"]\n  pull_request:\n    branches: [\"main\"]\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}\n  cancel-in-progress: true\n\nenv:\n  CARGO_TERM_COLOR: always\n  CARGO_INCREMENTAL: \"0\"\n  CARGO_NET_RETRY: 10\n\njobs:\n  license-check:\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    timeout-minutes: 10\n    steps:\n      - uses: actions/checkout@v4\n      - uses: dtolnay/rust-toolchain@stable\n      - uses: taiki-e/install-action@cargo-deny\n      - run: cargo deny check licenses\n\n  cargo-fmt-check:\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    timeout-minutes: 30\n    steps:\n      - uses: actions/checkout@v4\n      - uses: dtolnay/rust-toolchain@stable\n      - name: Check formatting\n        run: cargo fmt --check\n      - name: Check formatting (fuzz)\n        run: cd fuzz && cargo fmt --check\n\n  check-bindings:\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    timeout-minutes: 10\n    steps:\n      - uses: actions/checkout@v4\n      - name: Install bindgen\n        run: cargo install --version 0.71.1 bindgen-cli\n      - name: Regenerate sdk-kit bindings\n        run: cd sdk-kit && ./bindgen.sh\n      - name: Regenerate sync/sdk-kit bindings\n        run: cd sync/sdk-kit && ./bindgen.sh\n      - name: Check bindings are up to date\n        run: |\n          if ! git diff --exit-code sdk-kit/src/bindings.rs sync/sdk-kit/src/bindings.rs; then\n            echo \"Bindings are out of date. Run ./bindgen.sh in sdk-kit/ and sync/sdk-kit/ and commit the changes.\"\n            exit 1\n          fi\n\n  build-native:\n    timeout-minutes: ${{ matrix.os == 'windows-latest' && 120 || 30 }}\n    strategy:\n      matrix:\n        os: [blacksmith-4vcpu-ubuntu-2404, macos-latest, windows-latest]\n\n    runs-on: ${{ matrix.os }}\n\n    steps:\n      - uses: actions/checkout@v4\n      - uses: dtolnay/rust-toolchain@stable\n        with:\n          components: llvm-tools-preview\n      - name: Setup mold linker\n        if: runner.os == 'Linux'\n        uses: \"./.github/shared/setup-mold\"\n      - uses: Swatinem/rust-cache@v2\n        with:\n          prefix-key: \"v1-rust\"\n          cache-on-failure: true\n      - uses: \"./.github/shared/setup-sccache\"\n      - name: Install cargo-nextest\n        uses: taiki-e/install-action@nextest\n      - name: Install cargo-llvm-cov\n        if: runner.os == 'Linux'\n        uses: taiki-e/install-action@cargo-llvm-cov\n      - name: Set up Python 3.10\n        uses: useblacksmith/setup-python@v6\n        with:\n          python-version: \"3.10\"\n      - name: Build\n        run: cargo build --verbose --locked --all-features\n      - name: Test (nextest with coverage)\n        if: runner.os == 'Linux'\n        env:\n          RUST_LOG: ${{ runner.debug && 'turso_core::storage=trace' || '' }}\n          LOCAL_SYNC_SERVER: \"../../target/debug/tursodb\"\n        # for some reason, test(test_busy_snapshot_immediate) hung with coverage - so let's exclude it from the run for now\n        run: |\n          cargo llvm-cov nextest --workspace --all-features --locked --no-fail-fast -E 'not binary(fuzz_tests) and not test(test_busy_snapshot_immediate)' --failure-output=final --html --output-dir coverage\n          echo \"::group::Coverage Summary\"\n          cargo llvm-cov report | tee coverage/summary.txt\n          echo \"::endgroup::\"\n        timeout-minutes: 20\n      - name: Test (nextest)\n        if: runner.os != 'Linux'\n        env:\n          RUST_LOG: ${{ runner.debug && 'turso_core::storage=trace' || '' }}\n          LOCAL_SYNC_SERVER: \"../../target/debug/tursodb\"\n        run: cargo nextest run --workspace --all-features --locked --no-fail-fast -E 'not binary(fuzz_tests)' --failure-output=final\n        timeout-minutes: 20\n      - name: Test integrity_check corruption and short reads (no checksum feature because it interferes with the test)\n        run: cargo nextest run -p core_tester --locked --no-fail-fast -E 'test(integrity_check) | test(short_read)' --failure-output=final\n        timeout-minutes: 5\n      - name: Test doctests\n        env:\n          LOCAL_SYNC_SERVER: \"../../target/debug/tursodb\"\n        run: cargo test --workspace --all-features --doc --locked\n      - name: Upload coverage report\n        if: runner.os == 'Linux'\n        uses: actions/upload-artifact@v4\n        with:\n          name: coverage-report\n          path: coverage/\n          retention-days: 14\n\n  shuttle-tests:\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    timeout-minutes: 30\n    steps:\n      - uses: actions/checkout@v4\n      - uses: dtolnay/rust-toolchain@stable\n      - name: Setup mold linker\n        uses: \"./.github/shared/setup-mold\"\n      - uses: Swatinem/rust-cache@v2\n        with:\n          prefix-key: \"v1-rust-shuttle\"\n          cache-on-failure: true\n      - uses: \"./.github/shared/setup-sccache\"\n      - name: Install cargo-nextest\n        uses: taiki-e/install-action@nextest\n      - name: Run shuttle tests\n        run: |\n          make test-shuttle\n\n  # stress-nondeterminism-check:\n  #   runs-on: blacksmith-4vcpu-ubuntu-2404\n  #   timeout-minutes: 15\n  #   steps:\n  #     - uses: actions/checkout@v4\n  #     - uses: dtolnay/rust-toolchain@stable\n  #     - name: Setup mold linker\n  #       uses: \"./.github/shared/setup-mold\"\n  #     - uses: Swatinem/rust-cache@v2\n  #       with:\n  #         prefix-key: \"v1-rust-shuttle\"\n  #         cache-on-failure: true\n  #     - uses: \"./.github/shared/setup-sccache\"\n  #     - name: Check for uncontrolled nondeterminism\n  #       env:\n  #         RUSTFLAGS: \"--cfg tokio_unstable --cfg shuttle\"\n  #       run: |\n  #         cargo run -p turso_stress -- --check-uncontrolled-nondeterminism\n\n  clippy:\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    timeout-minutes: 30\n    steps:\n      - uses: actions/checkout@v4\n      - uses: dtolnay/rust-toolchain@stable\n        with:\n          components: clippy\n      - uses: Swatinem/rust-cache@v2\n        with:\n          prefix-key: \"v1-rust\"\n          cache-on-failure: true\n      - name: Clippy\n        run: |\n          cargo clippy --workspace --all-features --all-targets --locked -- --deny=warnings\n\n  simulator:\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    timeout-minutes: 35\n    strategy:\n      matrix:\n        profile:\n          - name: default\n            iterations: 10\n            args: \"--maximum-tests 1000 --min-tick 10 --max-tick 50 --io-backend=memory\"\n          - name: InsertHeavy\n            iterations: 5\n            args: \"--maximum-tests 1000 --min-tick 10 --max-tick 50 --profile write_heavy --io-backend=memory\"\n          - name: InsertHeavySpill\n            iterations: 5\n            args: \"--maximum-tests 1000 --min-tick 10 --max-tick 50 --profile write_heavy_spill --io-backend=memory\"\n          - name: Faultless\n            iterations: 10\n            args: \"--maximum-tests 1000 --min-tick 10 --max-tick 50 --profile faultless --io-backend=memory\"\n          - name: IOUring InsertHeavySpill\n            iterations: 5\n            args: \"--io-backend=io-uring --maximum-tests 1000 --profile=write_heavy_spill\"\n          - name: IOUring Differential\n            iterations: 10\n            args: \"--io-backend=io-uring --maximum-tests 1000 --differential\"\n          - name: Differential\n            iterations: 10\n            args: \"--maximum-tests 1000 --differential --io-backend=memory\"\n    steps:\n      - uses: actions/checkout@v4\n      - uses: dtolnay/rust-toolchain@stable\n      - name: Setup mold linker\n        uses: \"./.github/shared/setup-mold\"\n      - uses: Swatinem/rust-cache@v2\n        with:\n          prefix-key: \"v1-rust\"\n          cache-on-failure: true\n      - uses: \"./.github/shared/setup-sccache\"\n      # Run these with for loops (instead of --loop flag in sim) so that each iteration gets a separate seed -- this produces much easier-to-debug traces when there's a failure since we don't have to potentially\n      # run 10 iterations of the same seed to get to the failure\n      - name: Simulator ${{ matrix.profile.name }}\n        run: |\n          for i in $(seq 1 ${{ matrix.profile.iterations }}); do\n            ./scripts/run-sim ${{ matrix.profile.args }} || exit 1\n          done\n\n        # TODO: find out why this blows the stack so quickly every time..\n        # e.g. https://github.com/tursodatabase/turso/actions/runs/22370904813/job/64749257476?pr=5588\n  # simulator-windows:\n  #   runs-on: windows-latest\n  #   timeout-minutes: 120\n  #   strategy:\n  #     matrix:\n  #       profile:\n  #         - name: default\n  #           iterations: 10\n  #           args: \"--maximum-tests 1000 --min-tick 10 --max-tick 50 --io-backend=memory\"\n  #         - name: InsertHeavy\n  #           iterations: 5\n  #           args: \"--maximum-tests 1000 --min-tick 10 --max-tick 50 --profile write_heavy --io-backend=memory\"\n  #         - name: InsertHeavySpill\n  #           iterations: 5\n  #           args: \"--maximum-tests 1000 --min-tick 10 --max-tick 50 --profile write_heavy_spill --io-backend=memory\"\n  #         - name: Faultless\n  #           iterations: 10\n  #           args: \"--maximum-tests 1000 --min-tick 10 --max-tick 50 --profile faultless --io-backend=memory\"\n  #         - name: WindowsIOCP InsertHeavySpill\n  #           iterations: 5\n  #           args: \"--io-backend=experimental_win_iocp --maximum-tests 1000 --profile=write_heavy_spill\"\n  #         - name: WindowsIOCP Differential\n  #           iterations: 10\n  #           args: \"--io-backend=experimental_win_iocp --maximum-tests 1000 --differential\"\n  #         - name: Differential\n  #           iterations: 10\n  #           args: \"--maximum-tests 5000 --differential --io-backend=memory\"\n  #   steps:\n  #     - uses: actions/checkout@v4\n  #     - uses: dtolnay/rust-toolchain@stable\n  #     - uses: Swatinem/rust-cache@v2\n  #       with:\n  #         prefix-key: \"v1-rust\"\n  #         cache-on-failure: true\n  #     - uses: \"./.github/shared/setup-sccache\"\n  #     - name: Windows Simulator ${{ matrix.profile.name }}\n  #       shell: pwsh\n  #       run: |\n  #         for ($i = 1; $i -le ${{ matrix.profile.iterations }}; $i++) {\n  #           & ./scripts/run-sim.ps1 ${{ matrix.profile.args }}\n  #           if ($LASTEXITCODE -ne 0) {\n  #               exit $LASTEXITCODE\n  #           }\n  #         }\n\n  # TODO: for now leave this commented out, as it always fails\n  # differential-fuzzer:\n  #   runs-on: blacksmith-4vcpu-ubuntu-2404\n  #   timeout-minutes: 30\n  #   steps:\n  #     - uses: actions/checkout@v4\n  #     - uses: dtolnay/rust-toolchain@stable\n  #     - name: Setup mold linker\n  #       uses: rui314/setup-mold@v1\n  #     - uses: Swatinem/rust-cache@v2\n  #       with:\n  #         prefix-key: \"v1-rust\"\n  #         cache-on-failure: true\n  #     - name: Setup sccache\n  #       uses: mozilla-actions/sccache-action@v0.0.9\n  #     - name: Run differential_fuzzer\n  #       env:\n  #         SCCACHE_GHA_ENABLED: \"true\"\n  #         RUSTC_WRAPPER: \"sccache\"\n  #       run: |\n  #         cargo run -p differential-fuzzer -- -n 1000 loop 10\n\n  test-limbo:\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    timeout-minutes: 20\n    steps:\n      - uses: actions/checkout@v4\n      - uses: dtolnay/rust-toolchain@stable\n      - name: Setup mold linker\n        uses: \"./.github/shared/setup-mold\"\n      - uses: Swatinem/rust-cache@v2\n        with:\n          prefix-key: \"v1-rust\"\n          cache-on-failure: true\n      - uses: \"./.github/shared/setup-sccache\"\n\n      - name: Install uv\n        uses: useblacksmith/setup-uv@v4\n        with:\n          enable-cache: true\n\n      - name: Set up Python\n        run: uv python install\n\n      - uses: \"./.github/shared/install_sqlite\"\n      - name: Test\n        run: make test\n        timeout-minutes: 20\n\n  concurrent-simulator:\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    timeout-minutes: 30\n    steps:\n      - uses: actions/checkout@v4\n      - uses: dtolnay/rust-toolchain@stable\n      - name: Setup mold linker\n        uses: \"./.github/shared/setup-mold\"\n      - uses: Swatinem/rust-cache@v2\n        with:\n          prefix-key: \"v1-rust\"\n          cache-on-failure: true\n      - uses: \"./.github/shared/setup-sccache\"\n      - name: Run turso_whopper regression tests\n        run: cargo test -p turso_whopper --test regression_tests\n        timeout-minutes: 5\n      - name: Run turso_whopper\n        run: |\n          cargo build -p turso_whopper\n          for i in $(seq 1 100); do\n            ./target/debug/turso_whopper --reopen-probability 0.001 || exit 1\n          done\n\n  test-sqlite:\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    timeout-minutes: 30\n    steps:\n      - uses: actions/checkout@v4\n      - uses: \"./.github/shared/install_sqlite\"\n      - name: build SQLite test extensions\n        run: cargo build --package limbo_sqlite_test_ext --locked\n      - name: Test\n        run: SQLITE_EXEC=\"sqlite3\" make test-compat\n"
  },
  {
    "path": ".github/workflows/rust_perf.yml",
    "content": "name: Rust Benchmarks+Nyrkiö\n\non:\n  workflow_dispatch:\n  push:\n    branches: [\"main\", \"master\", \"notmain\"]\n  pull_request:\n    branches: [\"main\", \"notmain\", \"master\"]\n\nenv:\n  CARGO_TERM_COLOR: never\n  CARGO_INCREMENTAL: \"0\"\n  CARGO_NET_RETRY: 10\n\njobs:\n  bench:\n    runs-on: ubuntu-latest\n    timeout-minutes: 30\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - bench: benchmark\n            features: \"--features bench\"\n          - bench: mvcc_benchmark\n            features: \"--features bench\"\n          - bench: json_benchmark\n            features: \"--features bench\"\n          - bench: sql_functions\n            features: \"--features bench\"\n          - bench: hash_spill_benchmark\n            features: \"--features bench\"\n          - bench: write_perf_benchmark\n            features: \"--features bench\"\n          - bench: fts_benchmark\n            features: \"--features bench\"\n          - bench: parser_benchmark\n            features: \"\"\n          - bench: graph_queries_benchmark\n            features: \"--features bench\"\n    steps:\n      - uses: actions/checkout@v4\n      - uses: dtolnay/rust-toolchain@stable\n      - uses: Swatinem/rust-cache@v2\n        with:\n          prefix-key: \"v1-rust\"\n          cache-on-failure: true\n      - name: Generate graph-queries DB\n        if: matrix.bench == 'graph_queries_benchmark'\n        run: |\n          python3 perf/graph-queries/generate_seed.py | sqlite3 perf/graph-queries/graph-queries.db\n          cp perf/graph-queries/graph-queries.db perf/graph-queries/graph-queries-analyzed.db\n          sqlite3 perf/graph-queries/graph-queries-analyzed.db \"ANALYZE;\"\n      - name: Bench ${{ matrix.bench }}\n        run: cargo bench --bench ${{ matrix.bench }} ${{ matrix.features }} 2>&1 | tee output.txt\n      - uses: actions/upload-artifact@v4\n        with:\n          name: bench-output-${{ matrix.bench }}\n          path: output.txt\n\n  bench-report:\n    runs-on: ubuntu-latest\n    needs: bench\n    if: always() && !cancelled()\n    steps:\n      - uses: actions/download-artifact@v4\n        with:\n          pattern: bench-output-*\n      - name: Merge outputs\n        run: cat bench-output-*/output.txt > output.txt\n      - name: Analyze benchmark result with Nyrkiö\n        uses: nyrkio/change-detection@HEAD\n        with:\n          name: turso\n          tool: criterion\n          output-file-path: output.txt\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n\n          # What to do if a change is immediately detected by Nyrkiö.\n          # Note that smaller changes are only detected with delay, usually after a change\n          # persisted over 2-7 commits. Go to nyrkiö.com to view those or configure alerts.\n          # Note that Nyrkiö will find all changes, also improvements. This means fail-on-alert\n          # on pull events isn't compatible with this workflow being required to pass branch protection.\n          fail-on-alert: false\n          comment-on-alert: false\n          comment-always: false\n          # Nyrkiö configuration\n          # Get yours from https://nyrkio.com/docs/getting-started\n          nyrkio-token: ${{ secrets.NYRKIO_JWT_TOKEN }}\n          # HTTP requests will fail for all non-core contributors that don't have their own token.\n          # Don't want that to spoil the build, so:\n          never-fail: true\n          # Make results and change points public, so that any oss contributor can see them\n          nyrkio-public: true\n\n          # parameters of the algorithm. Note: These are global, so we only set them once and for all.\n          # Smaller p-value = less change points found. Larger p-value = more, but also more false positives.\n          nyrkio-settings-pvalue: 0.00001\n          nyrkio-settings-threshold: 0%\n\n  clickbench:\n    runs-on: ubuntu-latest\n    timeout-minutes: 30\n    steps:\n      - uses: actions/checkout@v4\n      - uses: dtolnay/rust-toolchain@stable\n      - uses: Swatinem/rust-cache@v2\n        with:\n          prefix-key: \"v1-rust\"\n          cache-on-failure: true\n      - uses: useblacksmith/setup-node@v5\n        with:\n          node-version: 20\n\n      - name: Clickbench\n        run: make clickbench\n\n      - name: Analyze TURSO result with Nyrkiö\n        uses: nyrkio/change-detection@HEAD\n        with:\n          name: clickbench/turso\n          tool: time\n          output-file-path: clickbench-tursodb.txt\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n          # What to do if a change is immediately detected by Nyrkiö.\n          # Note that smaller changes are only detected with delay, usually after a change\n          # persisted over 2-7 commits. Go to nyrkiö.com to view those or configure alerts.\n          # Note that Nyrkiö will find all changes, also improvements. This means fail-on-alert\n          # on pull events isn't compatible with this workflow being required to pass branch protection.\n          fail-on-alert: false\n          comment-on-alert: false\n          comment-always: false\n          # Nyrkiö configuration\n          # Get yours from https://nyrkio.com/docs/getting-started\n          nyrkio-token: ${{ secrets.NYRKIO_JWT_TOKEN }}\n          # HTTP requests will fail for all non-core contributors that don't have their own token.\n          # Don't want that to spoil the build, so:\n          never-fail: true\n          # Make results and change points public, so that any oss contributor can see them\n          nyrkio-public: true\n          nyrkio-settings-pvalue: 0.00001\n          nyrkio-settings-threshold: 0%\n\n      - name: Analyze SQLITE3 result with Nyrkiö\n        uses: nyrkio/change-detection@HEAD\n        with:\n          name: clickbench/sqlite3\n          tool: time\n          output-file-path: clickbench-sqlite3.txt\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n          fail-on-alert: false\n          comment-on-alert: false\n          comment-always: false\n          nyrkio-token: ${{ secrets.NYRKIO_JWT_TOKEN }}\n          never-fail: true\n          nyrkio-public: true\n          nyrkio-settings-pvalue: 0.00001\n          nyrkio-settings-threshold: 0%\n\n  tpc-h-criterion:\n    runs-on: ubuntu-latest\n    timeout-minutes: 60\n    env:\n      DB_FILE: \"perf/tpc-h/TPC-H.db\"\n    steps:\n      - uses: actions/checkout@v4\n      - uses: dtolnay/rust-toolchain@stable\n      - uses: Swatinem/rust-cache@v2\n        with:\n          prefix-key: \"v1-rust\"\n          cache-on-failure: true\n\n      - name: Cache TPC-H\n        id: cache-primes\n        uses: useblacksmith/cache@v5\n        with:\n          path: ${{ env.DB_FILE }}\n          key: tpc-h\n      - name: Download TPC-H\n        if: steps.cache-primes.outputs.cache-hit != 'true'\n        env:\n          DB_URL: \"https://github.com/lovasoa/TPCH-sqlite/releases/download/v1.0/TPC-H.db\"\n        run: wget -O $DB_FILE --no-verbose $DB_URL\n\n      - name: Bench\n        run: cargo bench --bench tpc_h_benchmark --locked  2>&1 | tee output.txt\n      - name: Analyze benchmark result with Nyrkiö\n        uses: nyrkio/change-detection@HEAD\n        with:\n          name: tpc-h\n          tool: criterion\n          output-file-path: output.txt\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n\n          # What to do if a change is immediately detected by Nyrkiö.\n          # Note that smaller changes are only detected with delay, usually after a change\n          # persisted over 2-7 commits. Go to nyrkiö.com to view those or configure alerts.\n          # Note that Nyrkiö will find all changes, also improvements. This means fail-on-alert\n          # on pull events isn't compatible with this workflow being required to pass branch protection.\n          fail-on-alert: false\n          comment-on-alert: false\n          comment-always: false\n          # Nyrkiö configuration\n          # Get yours from https://nyrkio.com/docs/getting-started\n          nyrkio-token: ${{ secrets.NYRKIO_JWT_TOKEN }}\n          # HTTP requests will fail for all non-core contributors that don't have their own token.\n          # Don't want that to spoil the build, so:\n          never-fail: true\n          # Make results and change points public, so that any oss contributor can see them\n          nyrkio-public: true\n\n          # parameters of the algorithm. Note: These are global, so we only set them once and for all.\n          # Smaller p-value = less change points found. Larger p-value = more, but also more false positives.\n          nyrkio-settings-pvalue: 0.00001\n          nyrkio-settings-threshold: 0%\n\n  tpc-h:\n    runs-on: ubuntu-latest\n    timeout-minutes: 30\n    steps:\n      - uses: actions/checkout@v4\n      - uses: dtolnay/rust-toolchain@stable\n      - uses: Swatinem/rust-cache@v2\n        with:\n          prefix-key: \"v1-rust\"\n          cache-on-failure: true\n      - name: TPC-H\n        env:\n          RUST_LOG: \"off\"\n        run: ./perf/tpc-h/benchmark.sh\n\n  vfs-bench-compile:\n    runs-on: ubuntu-latest\n    timeout-minutes: 30\n    steps:\n      - uses: actions/checkout@v4\n      - uses: dtolnay/rust-toolchain@stable\n      - uses: Swatinem/rust-cache@v2\n        with:\n          prefix-key: \"v1-rust\"\n          cache-on-failure: true\n\n      - name: Build\n        run: cargo build --release --verbose --locked\n\n      - name: Install uv\n        uses: useblacksmith/setup-uv@v4\n        with:\n          enable-cache: true\n\n      - name: Set up Python\n        run: uv python install\n\n      - name: Install the project\n        run: uv sync --package turso_test\n\n      - name: Run benchmark\n        run: uv run bench-vfs \"SELECT 1;\" 100\n"
  },
  {
    "path": ".github/workflows/sqltest.yml",
    "content": "name: SQL Tests\n\non:\n  workflow_dispatch:\n  push:\n    branches: [\"main\"]\n  pull_request:\n    branches: [\"main\"]\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}\n  cancel-in-progress: true\n\nenv:\n  CARGO_TERM_COLOR: always\n\njobs:\n  sqltest-check:\n    name: Check .sqltest syntax\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    timeout-minutes: 10\n    steps:\n      - uses: actions/checkout@v3\n      - uses: useblacksmith/rust-cache@v3\n        with:\n          prefix-key: \"v1-rust\"\n      - name: Build test runner\n        run: make -C testing/sqltests build-runner\n      - name: Check test syntax\n        run: make -C testing/sqltests check\n\n  sqltest-run-cli:\n    name: Run SQL tests (CLI backend)\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    timeout-minutes: 30\n    steps:\n      - uses: actions/checkout@v3\n      - uses: useblacksmith/rust-cache@v3\n        with:\n          prefix-key: \"v1-rust\"\n      - uses: \"./.github/shared/setup-sccache\"\n      - uses: \"./.github/shared/install_sqlite\"\n      - name: Build tursodb\n        run: make -C testing/sqltests build-tursodb\n      - name: Build test runner\n        run: make -C testing/sqltests build-runner\n      - name: Run tests\n        run: make -C testing/sqltests run-cli CROSS_CHECK_BINARY=sqlite3\n      - name: Run tests (MVCC)\n        run: make -C testing/sqltests run-cli MVCC=1\n\n  sqltest-run-rust:\n    name: Run SQL tests (Rust backend)\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    timeout-minutes: 30\n    steps:\n      - uses: actions/checkout@v3\n      - uses: useblacksmith/rust-cache@v3\n        with:\n          prefix-key: \"v1-rust\"\n      - name: Build test runner\n        run: make -C testing/sqltests build-runner\n      - name: Run tests\n        run: make -C testing/sqltests run-rust\n      - name: Run tests (MVCC)\n        run: make -C testing/sqltests run-rust MVCC=1\n\n  sqltest-run-js:\n    name: Run SQL tests (JS backend)\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    timeout-minutes: 30\n    defaults:\n      run:\n        working-directory: bindings/javascript\n    steps:\n      - uses: actions/checkout@v3\n      - uses: useblacksmith/rust-cache@v3\n        with:\n          prefix-key: \"v1-rust\"\n      - name: Setup node\n        uses: useblacksmith/setup-node@v5\n        with:\n          node-version: 20\n      - uses: \"./.github/shared/setup-sccache\"\n      - name: Install JS dependencies\n        run: yarn install\n      - name: Build common package\n        run: yarn workspace @tursodatabase/database-common build\n      - name: Build native bindings\n        run: yarn workspace @tursodatabase/database napi-build --target x86_64-unknown-linux-gnu\n      - name: Build TypeScript\n        run: yarn workspace @tursodatabase/database tsc-build\n      - name: Build test runner\n        working-directory: testing/sqltests\n        run: cargo build --release\n      - name: Run tests\n        working-directory: testing/sqltests\n        run: make run-js\n      - name: Run tests (MVCC)\n        working-directory: testing/sqltests\n        run: make run-js MVCC=1\n\n  sqltest-sqlite:\n    name: Run SQL tests (SQLite)\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    timeout-minutes: 30\n    steps:\n      - uses: actions/checkout@v3\n      - uses: useblacksmith/rust-cache@v3\n        with:\n          prefix-key: \"v1-rust\"\n      - uses: \"./.github/shared/install_sqlite\"\n      - name: Build test runner\n        run: make -C testing/sqltests build-runner\n      - name: Run tests against SQLite\n        run: make -C testing/sqltests test-sqlite\n"
  },
  {
    "path": ".github/workflows/stale.yml",
    "content": "name: Stale\n\non:\n  schedule:\n    - cron: '0 0 * * *'  # Runs every day at midnight UTC\n\npermissions:\n  issues: read\n  pull-requests: write\n\njobs:\n  stale:\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    timeout-minutes: 30\n    steps:\n      - name: Close stale pull requests\n        uses: actions/stale@v6\n        with:\n          repo-token: ${{ secrets.GH_TOKEN }}\n          operations-per-run: 1000\n          ascending: true\n          stale-pr-message: 'This pull request has been marked as stale due to inactivity. It will be closed in 7 days if no further activity occurs.'\n          close-pr-message: 'This pull request has been closed due to inactivity. Please feel free to reopen it if you have further updates.'\n          days-before-issue-stale: 365\n          days-before-stale: 30\n          days-before-close: 7\n"
  },
  {
    "path": ".github/workflows/turso-serverless.yml",
    "content": "name: turso-serverless\n\non:\n  workflow_dispatch:\n  push:\n    branches: [ main ]\n    paths:\n      - 'packages/turso-serverless/**'\n      - '.github/workflows/turso-serverless.yml'\n  pull_request:\n    branches: [ main ]\n    paths:\n      - 'packages/turso-serverless/**'\n      - '.github/workflows/turso-serverless.yml'\n\nenv:\n  working-directory: packages/turso-serverless\n\njobs:\n  build:\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    timeout-minutes: 20\n    defaults:\n      run:\n        working-directory: ${{ env.working-directory }}\n    \n    steps:\n    - uses: actions/checkout@v4\n    \n    - name: Setup Node.js\n      uses: useblacksmith/setup-node@v5\n      with:\n        node-version: '20'\n        cache: 'npm'\n        cache-dependency-path: ${{ env.working-directory }}/package-lock.json\n    \n    - name: Install dependencies\n      run: npm ci\n    \n    - name: Build\n      run: npm run build\n"
  },
  {
    "path": ".github.json",
    "content": "{\n    \"penberg\": {\n        \"name\": \"Pekka Enberg\",\n        \"email\": \"penberg@iki.fi\"\n    },\n    \"pereman2\": {\n        \"name\": \"Pere Diaz Bou\",\n        \"email\": \"pere-altea@homail.com\"\n    },\n    \"jussisaurio\": {\n        \"name\": \"Jussi Saurio\",\n        \"email\": \"jussi.saurio@gmail.com\"\n    }\n}\n"
  },
  {
    "path": ".gitignore",
    "content": "/target\n/target.noindex\n/.idea\n/.vscode\n/.sqlite3\n**/target\n\n*.so\n*.dylib\n*.ipynb\n*.o\n*.sqlite\n# Python\n.mypy_cache/\n.pytest_cache/\n.ruff_cache/\n.venv*/\n__pycache__/\n.coverage\nvenv\nenv\n.env\n.venv\ndist/\n.tmp/\n\n*.db\n**/*.db-wal\n**/*.db-shm\n**/*-wal\n# perf\n**/*/Mobibench\nperf/mobibench/plot/results.csv\nperf/mobibench/plot/mobibench.pdf\nperf/mobibench/plot/uv.lock\n# OS\n.DS_Store\n\n# Javascript\n**/node_modules/\n\n# testing\ntesting/limbo_output.txt\n**/limbo_output.txt\ntesting/*.log\n.bugbase\nlimbostress.log\nsimulator.log\n**/*.txt\nprofile.json.gz\nsimulator-output/\ntests/*.sql\nstress-go/stress-go\n&1\nbisected.sql\n*.log\n\n*.db-log\nsettings.local.json\n.claude/agents/\n.build-hash\n\n# Track empty wals for read only databases\n!testing/sqltests/database/*\ntesting/sqltests/database/integrity_*.db\n\n\n# For simulator redo\ntest.sql\n\n# for elle edn transaction history\n*.edn\nelle-history-results/\ncustom-types-fuzzer-output/\nCLAUDE.md\nbindings/python/dev.py\nbindings/python/implementation-test/\n"
  },
  {
    "path": ".python-version",
    "content": "3.13\n"
  },
  {
    "path": "AGENTS.md",
    "content": "# Turso Agent Guidelines\n\nSQLite rewrite in Rust. 40+ crate workspace.\n\n## Quick Reference\n\n```bash\ncargo build                    # build. never build with release.\ncargo test                     # rust unit/integration tests\ncargo fmt                      # format (required)\ncargo clippy --workspace --all-features --all-targets -- --deny=warnings  # lint\ncargo run -q --bin tursodb -- -q # run the interactive cli\n\nmake test                      # TCL compat + sqlite3 + extensions + MVCC\nmake test-single TEST=foo.test # single TCL test\nmake -C testing/sqltests run-rust  # sqltest runner (preferred for new tests)\n\nscripts/diff.sh \"SQL\" [label]  # compare sqlite3 vs tursodb output\n```\n\n## Structure\n\n```\nlimbo/\n├── core/           # Database engine (translate/, storage/, vdbe/, io/, mvcc/)\n├── parser/         # SQL parser (lexer, AST, grammar)\n├── cli/            # tursodb CLI (REPL, MCP server, sync server)\n├── bindings/       # Python, JS, Java, .NET, Go, Rust\n├── extensions/     # crypto, regexp, csv, fuzzy, ipaddr, percentile\n├── testing/        # simulator/, concurrent-simulator/, differential-oracle/\n├── sync/           # engine/, sdk-kit/ (Turso Cloud sync)\n├── sdk-kit/        # High-level SDK abstraction\n└── tools/          # dbhash utility\n```\n\n## Where to Look\n\n| Task | Location | Notes |\n|------|----------|-------|\n| Query execution | `core/vdbe/execute.rs` | 12k LOC bytecode interpreter |\n| SQL compilation | `core/translate/` | AST → bytecode, optimizer in `optimizer/` |\n| B-tree/pages | `core/storage/btree.rs` | 10k LOC, SQLite-compatible format |\n| WAL/durability | `core/storage/wal.rs` | Write-ahead log, checkpointing |\n| SQL parsing | `parser/src/parser.rs` | 11k LOC recursive descent |\n| Add extension | `extensions/core/` | ExtensionApi, scalar/aggregate/vtab traits |\n| Add binding | `bindings/` | PyO3, NAPI, JNI, FRB, CGO patterns |\n| Deterministic tests | `testing/simulator/` | Fault injection, differential testing |\n| New SQL tests | `testing/sqltests/tests/` | `.sqltest` format preferred |\n| Quick sqlite3 diff | `scripts/diff.sh` | Compare sqlite3 vs tursodb output for a query |\n| MVCC testing REPL | `cli/mvcc_repl.rs` | Multi-conn concurrent txn testing REPL        |\n\n## Guides\n\n- **[Testing](docs/agent-guides/testing.md)** - test types, when to use, how to write\n- **[Code Quality](docs/agent-guides/code-quality.md)** - correctness rules, Rust patterns, comments\n- **[Debugging](docs/agent-guides/debugging.md)** - bytecode comparison, logging, sanitizers\n- **[PR Workflow](docs/agent-guides/pr-workflow.md)** - commits, CI, dependencies\n- **[Transaction Correctness](docs/agent-guides/transaction-correctness.md)** - WAL, checkpointing, concurrency\n- **[Storage Format](docs/agent-guides/storage-format.md)** - file format, B-trees, pages\n- **[Async I/O Model](docs/agent-guides/async-io-model.md)** - IOResult, state machines, re-entrancy\n- **[MVCC](docs/agent-guides/mvcc.md)** - experimental multi-version concurrency (WIP)\n\n## Core Principles\n\n1. **Correctness paramount.** Production DB, not a toy. Crash > corrupt\n2. **SQLite compatibility.** Compare bytecode with `EXPLAIN`\n3. **Every change needs a test.** Must fail without change, pass with it\n4. **Assert invariants.** Don't silently fail. Don't hedge with if-statements\n5. **Own your regressions.** If tests fail after your change, they are your regressions. Debug them directly. Never stash/revert to \"check if they fail on main\" — that wastes time and is categorically banned.\n6. **Validate your hypotheses.**: If you suspect a given cause for a bug, validate it and provide incontrovertible evidence. NEVER make unearned assumptions.\n\n## CI Note\n\nRunning in GitHub Action? Max-turns limit in `.github/workflows/claude.yml`. OK to push WIP and continue in another action. Stay focused, avoid rabbit holes.\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## 0.5.0 -- 2026-03-04\n\n### Added\n* github/release: Add retry logic to network-dependent install steps (Pekka Enberg\")\n* github/release: Add retry logic to network-dependent install steps (Pekka Enberg)\n* Add JS and Python concurrent write examples (Avinash Sajjanshetty)\n* perf/react-native: add c++ getAllRows() helper (Jussi Saurio)\n* perf: add compiler branching hints (Preston Thorpe)\n* Add concurrent writes example in Rust (Avinash Sajjanshetty)\n* Add SQLAlchemy dialect for Python bindings (Orthel Toralen)\n* mvcc: add test that asserts no ghost commits after Busy errors happen (Jussi Saurio)\n* core: Support views in MVCC mode (RJ Barman)\n* add return type checking for Function calls for `CHECK` constraints (Pedro Muniz)\n* Add mobibench to gitignore (Preston Thorpe)\n* Add native TCL extension for in-process SQLite test harness (Ahmad Alhour)\n* bindings/rust: Add statement and row column inspection APIs (Pekka Enberg)\n* cli/sync: add read timeout to prevent deadlock on Windows (Orthel Toralen)\n* add SQL language reference documentation (Glauber Costa)\n* add stable PRAGMA capture_data_changes_conn name and keep old one around for compatibility (Nikita Sivukhin)\n* Improve Elle, add rw-register model and chaotic workloads (Avinash Sajjanshetty)\n* Add Mobibench evaluation: SQLite vs Turso (Pekka Enberg)\n* core/io: Add POSIX fallback for io_uring ftruncate (Pekka Enberg)\n* temporarily remove new windows simulation runs (Preston Thorpe)\n* feat: support encryption keys for attached databases via URI params (Glauber Costa)\n* Finish implementing RAISE (Preston Thorpe)\n* autofix: implement FkCheck opcode to prevent RETURNING rows after FK violations (Jussi Saurio)\n* Support custom types (Glauber Costa)\n* tests/fuzz: add fuzz_iterations helper and cut CI fuzz iters to 50% (Jussi Saurio)\n* Add STRICT table support to differential fuzzer (Glauber Costa)\n* Add support for RAISE() function (Glauber Costa)\n* Add ATTACH support to simulator (Glauber Costa)\n* partially implement FULL OUTER + LEFT and RIGHT hash joins (Preston Thorpe)\n* EXPLAIN QUERY PLAN: fix incorrect join order, add missing annotations, convert some snapshot tests to EQP-only (Jussi Saurio)\n* bindings/rust: add old multithreaded bug reproducers as mvcc regression tests (Jussi Saurio)\n* core/mvcc: Implement Hekaton commit dependency tracking (Pere Diaz Bou)\n* tests: add CTE regression tests for issue #4637 (Jussi Saurio)\n* add serial test crate to make `fuzz_pending_byte_database` serial for mvcc and wal (Pedro Muniz)\n* Add more tests to foreign_keys.sqltest (Jussi Saurio)\n* tests: add regression tests for expression index UPDATE correctness (Pedro Muniz)\n* Implement named savepoints (Preston Thorpe)\n* feat: Add materialized views support to Rust bindings and test runner (Martin Mauch)\n* Fix STRICT ADD COLUMN default type validation (Kumar Ujjawal)\n* Fix freelist_count dirty leak and add regression test (Kumar Ujjawal)\n* core: reimplement PRAGMA integrity_check as VDBE and add sqlite parity corpus (Jussi Saurio)\n* Restore new antithesis assertions (Mikaël Francoeur)\n* Use new Antithesis assertions (Mikaël Francoeur\")\n* fix: NEW.rowid_alias in UPDATE Trigger WHEN Clause Evaluates to NULL (Jussi Saurio)\n* Add MVCC Antithesis stress test configurations (Pekka Enberg)\n* add tests verifying check constraints are evaluated before ON CONFLICT clauses (Jussi Saurio)\n* Improve support for attach databases (Glauber Costa)\n* translate: support row-valued comparison expressions (Jussi Saurio)\n* fix: add `changes` support to MVCC and adjust code to always emit it in one place only (Pedro Muniz)\n* Add regression tests for correlated subqueries using MULTI-INDEX AND plans (Jussi Saurio)\n* add tests verifying INSERT RETURNING does not skip index updates (Jussi Saurio)\n* btree: add property tests & make a few functions not panic on corruption (Jussi Saurio)\n* Use new Antithesis assertions (Mikaël Francoeur\")\n* add regression tests for correlated subqueries registers fix (Pedro Muniz)\n* Use new Antithesis assertions (Mikaël Francoeur)\n* Add regression tests for concurrent commit deadlock in VDBE yield-spin loop (Avinash Sajjanshetty)\n* Add TPC-H benchmark README and plotting scripts (Pekka Enberg)\n* fix: Panic on ALTER TABLE ADD COLUMN With Invalid Collation Name #5305 (Jussi Saurio)\n* ci: add license checker to disallow GPL dependencies (Srinivas A)\n* Add some makefile commands for test runner in repo root (Preston Thorpe)\n* add exp-index-method to rust backend and for testing (Pavan Nambi)\n* Add support for FROM-clause subquery materialization and CTE materialization (Jussi Saurio)\n* Add new antithesis assertions (Mikaël Francoeur)\n* concurrent-simulator: Add UPDATE and DELETE workloads (Pekka Enberg)\n* Implement PRAGMA function_list with enum-derived function probing (Glauber Costa)\n* Add hermitage tests (Avinash Sajjanshetty)\n* Fix corruption errors caused by stale cursor positioning + add upsert support to simulator (Pavan Nambi)\n* make transactions supported in compat.md (Glauber Costa)\n* Add REGEXP operator support (Glauber Costa)\n* Add test case for MVCC dual cursor transaction isolation (Jussi Saurio)\n* Implement CHECK constraints (Glauber Costa)\n* Replace panic/todo/unimplemented with proper errors (Mikaël Francoeur)\n* testing/runner: Add @skip-if sqlite directive (Preston Thorpe)\n* Implement multi-index scan access method (Preston Thorpe)\n* add tests for ORDER BY aggregate not in SELECT with GROUP BY (Mikaël Francoeur)\n* github: Add workflow to publish crates to crates.io on tags (Pekka Enberg)\n* javascript: Add support for Statement.reader property (Pekka Enberg)\n* javascript: Fix implicit connect support (Pekka Enberg)\n* Add busy timeout and Connector pattern for go bindings (Preston Thorpe)\n* core/mvcc: add to destroyed_tables when insert+delete in same checkpoint (Pere Diaz Bou)\n* core/vdbe: fix newrowid Start state change on IO (Pere Diaz Bou)\n* add duration and fix notebook name (Mikaël Francoeur)\n* Add support for PRAGMA synchronous=NORMAL (Jussi Saurio)\n* Use new Antithesis trigger action (Mikaël Francoeur)\n* core/mvcc: update schema root pages with new allocated btree pages (Pere Diaz Bou)\n* add --experimental-attach flag to test runner backends (Mikaël Francoeur)\n* add index knowledge skill (Pedro Muniz)\n* Implement `vacuum_into` op (Avinash Sajjanshetty)\n* fix: only initialize result registers for subqueries that have not been evaluated yet (Pedro Muniz)\n* fix: add bounds checking to `cell_interior_read_left_child_page` (Avinash Sajjanshetty)\n* Add Namespace field for go-sync package (Preston Thorpe)\n* Add \"release-official\" compilation profile (Jussi Saurio)\n* Add support for local encryption in SDKs (Avinash Sajjanshetty)\n* fix build.rs rebuilding every time due to always getting new timestamp (Jussi Saurio)\n* add turso test runner to workspace member (Pedro Muniz)\n* fix dockerfile to add dbhash tool (Pedro Muniz)\n* add ReadableName strategy for differential fuzzer so that we mostly generate readable identifiers (Pedro Muniz)\n* implement `PRAGMA temp_store` optimization (Preston Thorpe)\n* Add `dbhash` tool calculate hash of logical db content (Avinash Sajjanshetty)\n* Add local encryption support for Go (Avinash Sajjanshetty)\n* mac: introduce PRAGMA fullfsync (Jussi Saurio)\n* Reintroduce record header caching (Jussi Saurio\")\n* Add new insert/write performance benchmarks (Preston Thorpe)\n* compat.md: add generated + md reformat (Pere Diaz Bou)\n* Add support for remote encryption in sync operations (Avinash Sajjanshetty)\n* core/mvcc: add savepoints to mvcc with `begin/end_statment` (Pere Diaz Bou)\n* Add benchmark for hash table/spilling performance (Preston Thorpe)\n* Reintroduce record header caching (Jussi Saurio\"' from Jussi Saurio)\n* Reintroduce record header caching (Jussi Saurio\")\n* Reintroduce record header caching (Jussi Saurio)\n* Implement remaining ON CONFLICT ResolveTypes for INSERT and UPDATE statements (Preston Thorpe)\n* feat(cli): add .read command to execute SQL from a file (i cook code)\n* add bench feature to run benchmarks in nyrkio (Pedro Muniz)\n* Implement partial ON CONFLICT support for UPDATE statements (Preston Thorpe)\n* Support CTE visibility in VALUES+RETURNING subqueries and allow explicit col names (Preston Thorpe)\n* Add ECS task to run sqlancer nightly along with simulator (Preston Thorpe)\n* implement simple strategy to restart WAL log if possible (Nikita Sivukhin)\n* Add VSCode syntax highlighter for .sqltest files (Jussi Saurio)\n* DEFAULT value handling of NON-NULL columns in ADD COLUMN (Preston Thorpe)\n* Implement Full Text Search feature (Preston Thorpe)\n* add codspeed (Pedro Muniz)\n* core/mvcc: Add support for synchronous off mode (Pekka Enberg)\n* Add support for subqueries in the WHERE position for DML statements (Preston Thorpe)\n* Add vibecoded stress-go testing tool (Jussi Saurio)\n* fix: implement IS TRUE/IS FALSE with correct SQLite semantics (Jussi Saurio)\n* Add shuttle tests for IO trait (Pedro Muniz)\n* bindings/rust: support `prepare_cached` (Pere Diaz Bou)\n* Add more comprehensive PRAGMA integrity_check, and add PRAGMA quick_check (Jussi Saurio)\n* tests: add  some shuttle tests (Pedro Muniz)\n* benchmarks: add micro benchmarks with divan (Pedro Muniz)\n* AGENTS.MD: add some magic words about max-turns (Jussi Saurio)\n* Add more instructions to AGENTS.MD and use Opus model instead of Claude for github bot (Jussi Saurio)\n* bindings/dotnet: implement IsDBNull check (Alex)\n* Implement json_object(*) to create JSON object from all columns (Martin Mauch)\n* Add some more guidelines to AGENTS.MD (Jussi Saurio)\n* Add id-token: write permission to claude action for OIDC (Jussi Saurio)\n* Add claude code github action and CLAUDE.MD file (Jussi Saurio)\n* antithesis: Add test case for WAL checkpointing (Pekka Enberg)\n* implement .dbtotxt and dbpage vtab (Pavan Nambi)\n* bindings/java: Add transaction support (Kim Seon Woo)\n\n### Updated\n* core/mvcc: disable autoincrement for MVCC (Pere Diaz Bou)\n* testing/stress: Allow --seed parameter for non-deterministic runs (Pekka Enberg)\n* Update authors in Cargo.toml (Preston Thorpe)\n* refactor: reorganize emitter.rs into its own module (Preston Thorpe)\n* core/vector: Avoid simsimd on Windows AArch64 (Marc-André Moreau)\n* core/storage: Reject non-UTF-8 encoded databases on open (Mikaël Francoeur)\n* core/mvcc: Atomic timestamps when preparing commit (Avinash Sajjanshetty)\n* Differential fuzzer: detect more false positive with limit and order by (Pedro Muniz)\n* core/mvcc:  move `payload_size` to frame header (Avinash Sajjanshetty)\n* Fast path no-op update in vdbe (Preston Thorpe)\n* Rename experimental_mvcc to mvcc (Eren Türkoğlu)\n* Update or replace ordering (Preston Thorpe)\n* Hash joins: partition spilling improvements (Preston Thorpe)\n* core/translate: Apply explicit view column aliases (Karthik Koralla)\n* core/translate: cache GROUP BY compound expressions properly (Jussi Saurio)\n* core: per-database MVCC transactions for attached databases (Glauber Costa)\n* Logical log tweaks (Jussi Saurio)\n* Assert that no two transaction timestamps are ever same (Avinash Sajjanshetty)\n* Update sqlite_sequence on ALTER TABLE ... RENAME TO (Kumar Ujjawal)\n* core/mvcc: Increase op count and tx payload size in logical log tx record format (Jussi Saurio)\n* core/translate: Use usable constraint even if preceded by unusable constraint on same column (Jussi Saurio)\n* Preserve BLOB-vs-no-affinity semantics in comparison coercion (Kumar Ujjawal)\n* compat: defer CTE column count check until its used (Pedro Muniz)\n* Improve SQLite3 tests (Pekka Enberg)\n* Accept trailing named column constraints in column defs (Kumar Ujjawal)\n* Enable index method table access for DML stmts (Preston Thorpe)\n* update unique_sets when renaming a column to prevent schema corruption (Glauber Costa)\n* Pretty print elle status progress (Avinash Sajjanshetty)\n* Decorrelate simple EXISTS / NOT EXISTS subqueries as semi/antijoins (Jussi Saurio)\n* Disable index-method access for DML (Kumar Ujjawal)\n* Avoid panic in DecrJumpZero on non-integer register (Kumar Ujjawal)\n* core/io: Track write buffer in completion (Jussi Saurio)\n* core/schema: Emit table-level UNIQUE constraints in BTreeTable::to_sql() (Jussi Saurio)\n* Avoid modulo-by-zero in WAL frame API fuzz rollback (Kumar Ujjawal)\n* enable async io in rust bindings (Pedro Muniz)\n* Make strict mode non-experimental (Glauber Costa)\n* Update misleading comment in MVCC (Avinash Sajjanshetty)\n* core/mvcc: Monotonic lockfree rowid allocation (Jussi Saurio)\n* core/mvcc: Use hash set to track commit dependencies (Avinash Sajjanshetty)\n* core/mvcc: check conflict delete btree resident tombstone (Pere Diaz Bou)\n* Adjust React Native example app instructions (Jussi Saurio)\n* Update example for VFS extensions in extensions/core/README.md (Preston Thorpe)\n* Enable partial indexes in MVCC mode (Preston Thorpe)\n* Enable Expression indexes in MVCC mode (Preston Thorpe)\n* Gate sqlite_dbpage writes behind --unsafe-testing (Kumar Ujjawal)\n* Enable FTS for JavaScript SDK (Nikita Sivukhin)\n* Sqltest cross check integrity (Jussi Saurio)\n* properly split sql statements for JS runner backend + enable triggers (Pedro Muniz)\n* Differential fuzzer improvements (Pedro Muniz)\n* core/translate: Remove spurious Affinity instruction from integrity_check (Pekka Enberg)\n* Remove some clones from MVCC code (Preston Thorpe)\n* remove `Arc<RwLock<Statement>>` from `Insn::Program` (Pedro Muniz)\n* ci: split bench into multiple jobs (Jussi Saurio)\n* Refactor core_tester fuzz module (Preston Thorpe)\n* Replace panic! and assert! with turso_macros assertions (Mikaël Francoeur)\n* SQLite compat: restrict ANY affinity special-casing to STRICT tables (Hossam Khero)\n* Enable MVCC for a bunch of fuzz tests (Preston Thorpe)\n* JS SDK: remote writes experimental (Nikita Sivukhin)\n* Reject triggers on virtual tables (Kumar Ujjawal)\n* serverless: Export Session and SessionConfig types (Nikita Sivukhin)\n* prevent scalar blob into JSONB (김선우)\n* install-sqlite: dont fail at get_download_url if sqlite3 already installed (Jussi Saurio)\n* refactor: stop using `connection.with_schema` inside `translate` module (Pedro Muniz)\n* Reject duplicate CTE names in WITH clause (Kumar Ujjawal)\n* Comment out flaky vector jaccard test (Preston Thorpe)\n* mvcc: disable cross-incompatible features (Jussi Saurio)\n* Random code cleanups to lib.rs (Pekka Enberg)\n* Reject column-level multi-column FK refs in ALTER TABLE (Kumar Ujjawal)\n* core: Make version functions non-deterministic (ratnesh mishra)\n* Experimental SQLRight setup (Mikaël Francoeur)\n* Reject FILTER clauses in view definitions (Kumar Ujjawal)\n* test/mvcc: re-enable database reopen logic in MVCC transaction fuzzer (Jussi Saurio)\n* perf: optimizations for FTS (Preston Thorpe)\n* CDC v2: transaction IDs and per-statement COMMIT records (Nikita Sivukhin)\n* Enable using Search for WHERE rowid= predicate instead of full scan (Preston Thorpe)\n* Reject reserved JSONB element types (Kumar Ujjawal)\n* core/mvcc: reparse schema on get_index_info (Pere Diaz Bou)\n* refactor: change translate functions to accept `&mut ProgramBuilder` (Pedro Muniz)\n* testing/stress: Remove multi-threading warning (Pekka Enberg)\n* github: Improve license check (Pekka Enberg)\n* core/mvcc: check for unique conflicts (Pere Diaz Bou)\n* Btree dump vtab (Nikita Sivukhin)\n* Cdc version table (Nikita Sivukhin)\n* vector8 + vector1bit (Nikita Sivukhin)\n* stress: Use single Database object (Pekka Enberg)\n* continue execution if IO was completed immediately (Nikita Sivukhin)\n* Lower Windows CI test timeout from 60 to 20 minutes (Pekka Enberg)\n* Multi index operator constraint (Pedro Muniz)\n* bench: gate nanosecond-level tests behind nanosecond-bench feature (Jussi Saurio)\n* use full link in JS SDK README so that links will be correct at npm (Nikita Sivukhin)\n* adjust is_constant check for `IN` and `emit_no_constant_insn` to avoid panic in VDBE cursor (Pedro Muniz)\n* concurrent-simulator: Explore in fast mode (Pekka Enberg)\n* Rewrite printf for full SQLite compatibility (Glauber Costa)\n* Pragmas (Glauber Costa)\n* testing/stress: Re-enable reopen and reconnect in stress tester (Pekka Enberg)\n* make `Numeric` the source of truth for number comparisons (Pedro Muniz)\n* Mvcc garbage collection (Jussi Saurio)\n* Remove unmaintained dart bindings (Avinash Sajjanshetty)\n* Convert antithesis from feature to cfg flag (Mikaël Francoeur)\n* core/storage: Remove unused FxHashSet import in btree.rs (Pekka Enberg)\n* core/mvcc: check integer primary key conflicts (Pere Diaz Bou)\n* use blocking IO for local databases for now (Nikita Sivukhin)\n* Make Github actions even more robust (Pekka Enberg)\n* bindings/wasm: Switch to notification-based I/O completion (Pekka Enberg)\n* Clippy more rules (Nikita Sivukhin)\n* properly handle url callback in react native bindings (Nikita Sivukhin)\n* Reduce antithesis stdout logging and log sql/tracing to files (Mikaël Francoeur)\n* Make Github workflows more robust (Pekka Enberg)\n* Change crate types in Dockerfile to restore Antithesis instrumentation (Mikaël Francoeur)\n* Whopper elle (Pedro Muniz)\n* allow url as function in react native bindings (Nikita Sivukhin)\n* JavaScript serverless driver improvements (Pekka Enberg)\n* MVCC: enable autocheckpoint at 1000 * 4120 bytes logical log size (Jussi Saurio)\n* Validate header (Nikita Sivukhin)\n* avoid unnecessary slow worker tasks in JS (Nikita Sivukhin)\n* do not cache chunks for .tantivy-meta.lock file (Nikita Sivukhin)\n* Destroy any custom index methods while dropping parent table (Preston Thorpe)\n* Stress progress bars (Pedro Muniz)\n* More snapshot tests (Pedro Muniz)\n* pin compiler and runner versions for codspeed benchmark (Pedro Muniz)\n* Move test runner to `testing/runner` (Pedro Muniz)\n* remove flaky test (Preston Thorpe)\n* Updates to COMPAT.MD (Jussi Saurio)\n* mark attach as experimental (Jussi Saurio)\n* run test with llvm-cov at linux (Nikita Sivukhin)\n* sim: generate unique constraints and verify statement rolls back correctly on failure (Pavan Nambi)\n* Bump urllib3 from 2.5.0 to 2.6.3 (app/dependabot)\n* Bump crossbeam-channel from 0.5.14 to 0.5.15 (app/dependabot)\n* JIT query generation in turso_stress (Mikaël Francoeur)\n* update determinism to match SQLite (Mikaël Francoeur)\n* core: database open is now async (Nikita Sivukhin)\n* bump claude code max turns (Pedro Muniz)\n* return NULL when there is no record in `op_column` (Pedro Muniz)\n* `strftime` should accept any kind of input not only text (Pedro Muniz)\n* React native example more tests (Nikita Sivukhin)\n* Document Rust sync API (Mikaël Francoeur)\n* Tpch snapshot testing (Pedro Muniz)\n* test runner: Snapshot testing (Pedro Muniz)\n* agressively black box benchmarks (Pedro Muniz)\n* Go driver time (Nikita Sivukhin)\n* `CHAR` should return `\\0` for `NULL` (Pedro Muniz)\n* core/mvcc: use dual_peek in exists (Pere Diaz Bou)\n* update npm for trusting publishing features (Nikita Sivukhin)\n* Separate Connection from a prepared statement to enable proper caching (Preston Thorpe)\n* substitute HashMap for FxHashMap in core (Pedro Muniz)\n* adjust agents.md to call tursodb cli correctly (Pedro Muniz)\n* remove NODE_AUTH_TOKEN (Nikita Sivukhin)\n* React native (Nikita Sivukhin)\n* sql_generation: reduce likelihood of index name collisions (Jussi Saurio)\n* retry on reader contention for `TursoRwLock::read` (Pedro Muniz)\n* differential fuzzer + general purpose sql generation (Pedro Muniz)\n* cleanup: centralize optimizer params and make them loadable from json (Jussi Saurio)\n* remove accesses_db flag (Nikita Sivukhin)\n* Migrate more tcl tests (Pedro Muniz)\n* testing/system: allow specifying rowcounts for gen-bigass-database.py (Jussi Saurio)\n* tests/fuzz/cte_fuzz: try to prevent pathological queries (Jussi Saurio)\n* improvements of the whopper (Nikita Sivukhin)\n* Some Insert performance improvements (Preston Thorpe)\n* Performance improvements for hash joins (Preston Thorpe)\n* WAL auto truncation: increase epoch to prevent stale pages reuse (Nikita Sivukhin)\n* core/mvcc: reparse schema after stream of schema changes (Pere Diaz Bou)\n* Reduce CLI release build time by ~60% (Jussi Saurio)\n* cleanup/vdbe: remove unused IndexKeyOwned and remove unused IndexKeyUnpacked::cursor_id (Jussi Saurio)\n* Refactor: remove encryption key field from Database struct (Avinash Sajjanshetty)\n* Migrate more TCL tests (Pedro Muniz)\n* convert agent docs to skills (Pedro Muniz)\n* Some optimizations for tpc-h queries (Pavan Nambi)\n* Refactor AggContext to use flat payload representation (Jussi Saurio)\n* more testing cleanups - move simulators (Pedro Muniz)\n* more testing cleanups (Pedro Muniz)\n* react-native: prepare (Nikita Sivukhin)\n* Move stress to `testing/stress` (Pedro Muniz)\n* Move tcl test to `testing/system` (Pedro Muniz)\n* ci/bench: increase timeout to 45mins as workaround (Jussi Saurio)\n* Remove fts/tantivy as default dependency in turso-core (Preston Thorpe)\n* Move antithesis test to `testing/antithesis` (Pedro Muniz)\n* feat/perf: use HashTable machinery for DISTINCT instead of ephemeral btree (Jussi Saurio)\n* ci: split simulator into separate jobs (Jussi Saurio)\n* CI: Reduce iterations in cte fuzzer (Preston Thorpe)\n* ci: longer timeout for cargo nextest on windows (Jussi Saurio)\n* fuzz/cte: reduce iterations (Jussi Saurio)\n* docs: split AGENTS.MD into separate agent-guides files (Jussi Saurio)\n* Js test runner (Jussi Saurio)\n* refactor/pager: small cleanups (Jussi Saurio)\n* sim: only run turso integrity check 10% of the time (Jussi Saurio)\n* Rewrite BETWEEN statements for partial indexes in DML paths (Preston Thorpe)\n* Create global directive for skipping mvcc in test runner (Preston Thorpe)\n* stress: run stress with shuttle (Pedro Muniz)\n* Validate Send + Sync for `ProgramState` + change registers to hold a `Box<[Register]>` (Pedro Muniz)\n* test-runner: rust backend (Pedro Muniz)\n* test-runner: start migrating TCL tests (Pedro Muniz)\n* Give Antithesis tests better names (Mikaël Francoeur)\n* core/vdbe: Allow leading + sign in numeric string parsing (Pere Diaz Bou)\n* core/vdbe: trim return text with integer and float (Pere Diaz Bou)\n* Reset ProgramState::once on statement reset (Mikaël Francoeur)\n* return blob if we concat blobs (Nikita Sivukhin)\n* Replace GPL-2.0 bloom crate with MIT-licensed fastbloom (Jussi Saurio)\n* Refactor core/lib.rs Connection impl out to its own file (Preston Thorpe)\n* Disallow WITHOUT ROWID tables (Jussi Saurio)\n* core: Use fast monotonic time when possible (Pekka Enberg)\n* core/vdbe: move mutable fields from Program to ProgramState (Pere Diaz Bou)\n* replace custom SmallVec with smallvec crate (Ankush)\n* Open autovacuum databases in readonly mode when experimental flag not set (app/copilot-swe-agent)\n* remove unused lints, variables and functions (Pedro Muniz)\n* separate Sync module (Pedro Muniz)\n* Btree: optimize defragment_page (Khashayar Fereidani)\n* avoid recursive read locks (Nikita Sivukhin)\n* Track WAL state in savepoints for proper rollback (Pavan Nambi)\n* vdbe: Resize deferred_seeks on program state reset (Pekka Enberg)\n* Reset checkpoint state when PRAGMA wal_checkpoint fails (Jussi Saurio)\n* Increase test_eval_param_only_once timeout threshold for CI reliability (app/copilot-swe-agent)\n* stress: switch to WAL mode before SQLite integrity check (Pekka Enberg)\n* planner: hash-join planning with safe build-side materialization (Preston Thorpe)\n* core/mvcc: rollback index rows (Pere Diaz Bou)\n* core/vdbe: check negative root pages with `integrity_check` (Pere Diaz Bou)\n* antithesis/stress: run in verbose mode (Jussi Saurio)\n* docs: change test build instructions (Mason Hall)\n* btree: `get_prev_record` and `get_next_record` automatically invalidate_record (Pedro Muniz)\n* bindings/dotnet: allow setting TursoParameter.Direction (Alex)\n* VDBE micro-optimizations for read path (Jussi Saurio)\n\n### Fixed\n* concurrent-simulator: Fix DDL inside BEGIN CONCURRENT transactions (Pekka Enberg)\n* core/json: Fix stale cursor crash in json_tree correlated subqueries (Pekka Enberg)\n* Avoid panic on unresolved qualified column in DO UPDATE (Kumar Ujjawal)\n* Fix unsound schema access when loading extensions (Preston Thorpe)\n* Fix unresolved IfNot label in scalar count(*) subqueries (Kumar Ujjawal)\n* core/translate: Fix non-literal defaults in Column instruction (Jussi Saurio)\n* core/mvcc: Fix header-only write durability (Jussi Saurio)\n* fix: IN(subquery) data corruption in ungrouped aggregates with NOT NULL columns (Vimal Prakash)\n* temporarily remove big sqltest to fix all the CI errors (Preston Thorpe)\n* Fix regression improperly consuming hash join WHERE terms (Preston Thorpe)\n* fix: UPDATE OR REPLACE does not fire FK CASCADE on implicitly-deleted row (Pedro Muniz)\n* Fix wal truncate on shutdown even after restart (Kumar Ujjawal)\n* fix: UPDATE RETURNING correlated subquery uses pre-UPDATE values (Pedro Muniz)\n* Make MVCC Elle check errors fail CI build (Pekka Enberg)\n* Overflow fix (Nikita Sivukhin)\n* fix: Window function with GROUP BY and ORDER BY causes optimizer assertion failure #5552 (Pedro Muniz)\n* Fix sync replay generator (Nikita Sivukhin)\n* fix: always reset pager state for attached databases (Pedro Muniz)\n* Fix three FULL OUTER JOIN bugs (Jussi Saurio)\n* fix snapshot isolation in MVCC (Preston Thorpe)\n* fix: clean up db-log and fix WAL extension for attached DBs in simulator (Glauber Costa)\n* fix: Expression index with correlated EXISTS subquery causes panic #5551 (Pedro Muniz)\n* Fix --experimental-strict incorrectly enabling custom types (Glauber Costa)\n* autofix: Aggregate COLLATE leaks to subsequent aggregates in same query (Jussi Saurio)\n* fix/mvcc: prevent persistent \"Database is busy\" after failed IO during checkpoint (Jussi Saurio)\n* autofix: CTE with UNION ALL corrupts first-column literals when any branch has aggregates (Jussi Saurio)\n* autofix: Triggers during FK CASCADE see pre-delete state of parent table (Jussi Saurio)\n* autofix: Correlated subquery with explicit JOIN returns wrong results after first row (Jussi Saurio)\n* autofix: LEFT JOIN incorrectly converted to INNER JOIN when WHERE uses CASE/IIF (Jussi Saurio)\n* autofix: Row value IN expression crashes with register allocation error (Jussi Saurio)\n* fix: BEFORE UPDATE trigger corrupts outer cursor position for correlated subqueries (Jussi Saurio)\n* autofix: DELETE with LIMIT ignores OFFSET clause (Preston Thorpe)\n* fix: UPSERT ON CONFLICT fails to match expression indexes #5550 (Preston Thorpe)\n* fix/optimizer: enable multi-index OR scans more widely and fix bad CTE query plans (Jussi Saurio)\n* Fix REAL affinity to preserve non-numeric text (Kumar Ujjawal)\n* Fix rowid resolution for views/WITHOUT ROWID (Kumar Ujjawal)\n* fix: Use pure-rust for macos for aegis to prevent macos compilation issues (Amodh Dhakal)\n* fix RETURNING, Statement::reset() and Program::abort() (Jussi Saurio)\n* Fix DEFAULT affinity handling (Jussi Saurio)\n* Improve I/O error message (Pekka Enberg)\n* fix: Handle IVM old record capture when REQUIRE_SEEK is set (Martin Mauch)\n* fix: MVCC cursor ignored left join null flag (Jussi Saurio)\n* fix: adjust `jump_if_null` branch offset as incorrectly failed NULL in check constraints in integrity checks (Pedro Muniz)\n* fix: emit RealAffinity when reading subquery columns from ephemeral indexes (Jussi Saurio)\n* fix: default `generate_series` STOP to SQLite Default (4294967295) when omitted (Damian Melia)\n* fix: mvcc: DDL statement inside BEGIN CONCURRENT returns a confusing error #3380 (Pedro Muniz)\n* fix: do not do unchecked indexing to non-static page offsets #4736 (Pedro Muniz)\n* fix: use consistent M_PI constant in degrees/radians for test parity with SQLite (Pedro Muniz)\n* fix: Register DBSP state index in schema for integrity_check (Martin Mauch)\n* fix: Panic on correlated subquery in IN clause referencing outer table alias #5213 (Pedro Muniz)\n* fix(wal): release WAL read lock on rollback to prevent checkpoint busy (Ahmad Alhour)\n* sqlite3: Fix segfault in sqlite3_free_table() (Pekka Enberg)\n* fix: OR in Partial Index WHERE Clause Causes Unresolved Label Panic (Jussi Saurio)\n* fix: No-op UPDATE causes datatype mismatch #5410 (Jussi Saurio)\n* fix: Expression Index Key Computed From Pre-Affinity Value During UPDATE (Jussi Saurio)\n* fix: ALTER TABLE RENAME COLUMN Does Not Update Partial Index WHERE Clauses (Jussi Saurio)\n* fix: Create materialized view in same transaction as source (Martin Mauch)\n* fix: \"Cursor not found\" when delete has a correlated subquery #5434 (Jussi Saurio)\n* fix: INSERT INTO With CTE + Compound SELECT Produces Garbled Data #5425 (Jussi Saurio)\n* fix: DELETE with correlated subquery sees in-flight deletions #5426 (Jussi Saurio)\n* fix: Panic When CTE Is Used in UPDATE SET Clause on Keyed Table (Jussi Saurio)\n* Fix FTS LIMIT 0/-1 handling to avoid Tantivy overflow (Kumar Ujjawal)\n* fix/correctness: use ephemeral table for UPDATE when expression index refers to updated columns (Mikaël Francoeur)\n* fix: update changes() and total_changes() inside explicit transactions (Damian Melia)\n* Fix: Fully enable foreign keys for MVCC (Preston Thorpe)\n* fix(cli): prevent extra space in multiline readline input (Karthik Koralla)\n* Fix materialized view re-entrancy bug (Martin Mauch)\n* fix affinity handling in check constraint evaluation (Jussi Saurio)\n* fix affinity calculation for index seeks, subqueries and ctes (Jussi Saurio)\n* fix: COLLATE Specification Lost in Concatenation with CHECK #5171 (Jussi Saurio)\n* fix: INSERT OR IGNORE + CHECK Failure Does Not Update sqlite_sequence (Jussi Saurio)\n* fix: Subquery in Trigger Body UPDATE SET Creates Poison Trigger (Jussi Saurio)\n* fix: WHERE clause returns wrong result for INT64_MAX compared against overflowed float (Jussi Saurio)\n* Fix prepare panic on comment/whitespace-only SQL (Kumar Ujjawal)\n* fix: Panic on Scalar Subquery Referencing CTE by Original Name After Alias (Jussi Saurio)\n* Fix SUM() type for BLOB inputs to return REAL (Kumar Ujjawal)\n* Unify error codes in LibsqlError for serverless package (Pekka Enberg)\n* Fix panic with duplicate expressions in window PARTITION BY (Mikaël Francoeur)\n* Fix panic and wrong results for window function in EXISTS subquery (Mikaël Francoeur)\n* fix: handle NULL properly in composite index seek/termination (Jussi Saurio)\n* fix: panic in PRAGMA integrity_check with expression indexes #5117 (Sanjeev)\n* fix: avoid cursor panic in SUM(TRUE IS TRUE) (Jussi Saurio)\n* Fix CLI exit code on query failure (Ahmad Alhour)\n* Fix MVCC trigger recovery rootpage handling (Jussi Saurio)\n* Fix collation with reordered hash joins (Preston Thorpe)\n* fix/btree: return error instead of panic if parent is unexpectedly a leaf (Jussi Saurio)\n* Fix panic on duplicate ORDER BY expressions with GROUP BY (Jussi Saurio)\n* fix: Align REAL→INTEGER cast with SQLite clamp semantics (Kumar Ujjawal)\n* fix: INSERT with auto-assigned rowid skips explicit index on IPK column (Jussi Saurio)\n* Fix affinity conversion of nan/inf text in numeric columns (Kumar Ujjawal)\n* fix: Correlated Subquery + Window Function in WHERE Hoisted Before Scan Loop (Jussi Saurio)\n* fix: more recursive read locks detected by shuttle with mvcc (Pedro Muniz)\n* fix/mvcc: dropped_root_pages tracking must survive restart (Jussi Saurio)\n* core/mvcc: Fix MVCC checkpoint panic when deleting index keys in inte… (Pekka Enberg)\n* fix: backward table scan skips rows on deep B-trees (ORDER BY DESC) (Pedro Muniz)\n* Phantom row fix (Pedro Muniz)\n* Insert or fix (Pedro Muniz)\n* fix: Panic on SELECT with 32767+ Columns (No SQLITE_MAX_COLUMN Limit) (Jussi Saurio)\n* fix: GROUP BY With Constant Expression Causes Infinite Loop #5300 (Jussi Saurio)\n* fix: propagate affinity for `IN` Subquery (Pedro Muniz)\n* fix(optimizer): fix correctness bugs in LEFT->INNER join optimization (Jussi Saurio)\n* fix/mvcc: fix test_logical_log_header_reserved_bytes_rejected test (Jussi Saurio)\n* Fix livelock in MVCC commit when pager_commit_lock is contended (Jussi Saurio)\n* Fix: handle multiplication overflow in time_date (Zohaib)\n* Fix panic on ROLLBACK TO by replacing assert! with bail_parse_error (codingbot24-s)\n* fix: Panic on CREATE TABLE with UNIQUE ... ON CONFLICT IGNORE #5221 (Jussi Saurio)\n* mvcc: define crash recovery / correctness semantics, rewrite logical log (Jussi Saurio)\n* Fix UPDATE incorrectly changing last_insert_rowid() (Kumar Ujjawal)\n* Fix panic in sum() with large floating-point values (Jussi Saurio)\n* fix(core): Handle PRAGMA cache_size overflow (fixes #5250) (ratnesh mishra)\n* fix: allow CTEs to shadow schema objects (Jussi Saurio)\n* fix: Panic on INSERT INTO t(nonexistent) SELECT ... FROM t #5226 (Jussi Saurio)\n* fix: Panic on Unresolved Table Reference in UPSERT DO UPDATE SET #5281 (Jussi Saurio)\n* core/storage: Fix pager commit completion (Pekka Enberg)\n* Fix throughput plot colors and hatching to match TPC-H plot (Pekka Enberg)\n* fix: Panic on 3+ Way MULTI-INDEX AND Query (RowSet State Violation) #… (Jussi Saurio)\n* fix: u16 Overflow in page_free_array Panics on 65536-Byte Pages #5276 (Jussi Saurio)\n* fix: handle PRAGMA database_list with argument without panicking (creativcoder)\n* fix: INSERT ... RETURNING panics with multi-column scalar subquery #5243 (Jussi Saurio)\n* fix: Panic When IN Subquery Used in LIMIT Expression #5247 (Jussi Saurio)\n* fix: ALTER TABLE RENAME COLUMN Fails on Case Mismatch #5246 (Jussi Saurio)\n* fix: read non-aggregate columns during AggStep for mixed expressions (Glauber Costa)\n* fix: null coroutine registers in ungrouped aggregate empty-result path (Glauber Costa)\n* fix: json_valid() With Zero Arguments Panics Instead of Returning Error (Jussi Saurio)\n* fix: LIMIT With i64::MIN Panics Due to Subtract Overflow in DecrJumpZ… (Jussi Saurio)\n* Fix missing dot in NOT NULL constraint error (Eren Türkoğlu)\n* fix: Materialized View Panics on Arithmetic With Text Column Values #… (Jussi Saurio)\n* fix: Panic on Scalar Subquery Referencing Rowid of Derived Table #5249 (Jussi Saurio)\n* Fix MVCC savepoint rollback leaving stale write set entries (Pekka Enberg)\n* fix: varint_len returned 10 for values with 58-64 significant bits (Jussi Saurio)\n* fix: do not apply numeric affinity of converted float is a NaN (Pedro Muniz)\n* try to fix js ci (Nikita Sivukhin)\n* fix: QUOTE should emit float in scientific notation (Pedro Muniz)\n* fix: GROUP BY sort order for extra columns (Pedro Muniz)\n* fix(core): fix ColumnUsedMask::is_only for overflow indices beyond vector length (Pedro Muniz)\n* Clear bloomfilters on rewind + hash table fix (Preston Thorpe)\n* fix: correctly jump to second OR expression if first expression is NULL (Pedro Muniz)\n* bindings/go: Fix spurious test failures from port conflicts (Pekka Enberg)\n* Fix changes() value for DELETE (Preston Thorpe)\n* core/mvcc: Fix transaction end version visibility check (Pere Diaz Bou)\n* Rn locking fix (Nikita Sivukhin)\n* fix prevent AUTOINCREMENT on non-INTEGER PKs with mixed-case names (김선우)\n* fix: cannot use index seek if the constraining expression references the same table being scanned (Pedro Muniz)\n* fix: evaluate result rows on exists subquery with DISTINCT clause (Pedro Muniz)\n* Fix multiple index corruption bugs involving INSERT OR <conflict_resolution_mode> (Jussi Saurio)\n* scripts/release-status.py: Classify issues as bugs by default (Pekka Enberg)\n* Fix broken build (Pavan Nambi)\n* Fix segfault in uuid_str/uuid_ts with no arguments (Preston Thorpe)\n* fix build (Mikaël Francoeur)\n* Strict fixes (Glauber Costa)\n* fix: prevent overflow panic in JsonArrayLength with malformed JSONB (Jussi Saurio)\n* Fix LEFT JOIN with virtual tables producing wrong results (Glauber Costa)\n* Fix user-defined columns named oid/rowid/_rowid_ resolving to internal rowid (Glauber Costa)\n* Rn fixes 2 (Nikita Sivukhin)\n* fix: emit offset after Distinct deduplication (Pedro Muniz)\n* fix: increase tolerance in vector L2 distance test to match simsimd vs rust precision (Pedro Muniz)\n* fix/encryption: do not panic if accessing db with missing or incorrect keys (Avinash Sajjanshetty)\n* fix: overflow handling in `cast_real_to_integer` (Pedro Muniz)\n* fix: convert text and blobs into bytes for correct parsing of strings into numbers (Pedro Muniz)\n* fix: `handle_text_sum` treat certain text values as approximate (Pedro Muniz)\n* Fix ruff check errors (Avinash Sajjanshetty)\n* Fix panic when CTE with indexed subquery is referenced twice (Mikaël Francoeur)\n* Fix STRICT tables incorrectly rejecting NULL values (Glauber Costa)\n* fix: allow qualified column references in partial index WHERE clause functions (Jussi Saurio)\n* Fix deadlock in MVCC mode when loading extensions on file databases (Jussi Saurio)\n* fix: WHERE clause treats string literals with numeric prefixes correctly (Jussi Saurio)\n* fix(update): apply affinity before MakeRecord to prevent index corruption (Pedro Muniz)\n* fix: JSON path UTF-8 char boundary panic (Jussi Saurio)\n* MVCC: fix checkpoint consistency and durability bugs (Jussi Saurio)\n* Fix CLI \".open\" command to preserve experimental flags (Jussi Saurio)\n* fix/pager: properly reuse last empty trunk page for page allocation (Jussi Saurio)\n* Fix Antithesis CI config (Mikaël Francoeur)\n* Fix missing STRICT keyword in BTreeTable::to_sql() (Glauber Costa)\n* fix/connection: release wal locks on connection drop to prevent DB from being permalocked (Jussi Saurio)\n* Fix JavaScript driver error message to match libSQL (Pekka Enberg)\n* MVCC fixes found using whopper (Jussi Saurio)\n* Fix bug when empty namespace is provided in go sync driver (Preston Thorpe)\n* Fix passing namespace in to config in Go sync driver (Preston Thorpe)\n* fix: -0.0 and 0.0 should have the same hash (Pedro Muniz)\n* core/mvcc: fix btree row verification (Pere Diaz Bou)\n* fix: group concat did not convert values to text on output (Pedro Muniz)\n* Fix datetime modifier UTF-8 slicing panic (Mikaël Francoeur)\n* fix `is_nonnull` check for primary key columns (Pedro Muniz)\n* page cache: fix aggressive spilling logic (Preston Thorpe)\n* Fix bug in hash join table mask calculations (Jussi Saurio)\n* Fix broken encryption SDK tests (Avinash Sajjanshetty)\n* Fix correlated subquery issues (Jussi Saurio)\n* fix: avoid exponential CTE re-planning during query compilation (Jussi Saurio)\n* ivm: Fix comparison of Integer <-> Float (Martin Mauch)\n* Fix cross-link in Claude skills (Ofek Lev)\n* mvcc: fix for schema conflict on COMMIT (Nikita Sivukhin)\n* Fix bugs in hash-join materialization subplans (Jussi Saurio)\n* fix: prevent integer overflow panic in JSONB serialization (Jussi Saurio)\n* sqlancer dockerfile fix (Jussi Saurio)\n* fix: infer subquery result list column types (Jussi Saurio)\n* fix/vdbe: fix QUOTE() - should convert ints&floats to text (Jussi Saurio)\n* fix/optimizer: fix output cardinality estimation for index seeks (Jussi Saurio)\n* fix: truncate ORDER BY after rowid when rowid is first (Jussi Saurio)\n* fix: PRINTF converts non-text format argument to string (Jussi Saurio)\n* fix: substr returns NULL when provided NULL length (Jussi Saurio)\n* fix docker builds (Pedro Muniz)\n* fix: handle EXISTS in expr_vector_size instead of panicing with todo!() (Jussi Saurio)\n* fix: move subqueries to ephemeral plan when WHERE clause is moved (Jussi Saurio)\n* fix: eagerly remove ORDER BY if single aggregate, no groups/windows (Jussi Saurio)\n* fix: IN/NOT IN subqueries return NULL when value not found and subquery contains NULLs (Jussi Saurio)\n* fix/optimizer/stats: fix incorrectly named distinct_per_prefix field (Jussi Saurio)\n* fix antithesis dockerfile: download libvoidstar.so in Dockerfile instead of COPY (Jussi Saurio)\n* Optimizer: fix bugs, improve cost model (Jussi Saurio)\n* Fix wal checkpoint (Nikita Sivukhin)\n* Busy snapshot bugfix (Nikita Sivukhin)\n* fix/translate: revert change that allowed index cursor with stale position to be read (Jussi Saurio)\n* try_for_float: use Dekker algorithm to avoid precision error (Jussi Saurio)\n* fix: type affinity comparison for TEXT columns with numeric literals (#4745) (Srinivas A)\n* fix sync-wasm package to properly handle case when no url is provided (Nikita Sivukhin)\n* Fix JSON panics and incompatibility issues (Jussi Saurio)\n* Fix misuse of encryption key in the database manager cache (Avinash Sajjanshetty)\n* Run cargo clippy --fix --all in turso-test-runner (Preston Thorpe)\n* Fix ON CONFLICT resolution propagation to trigger statements (Preston Thorpe)\n* Fix sqlancer-runner image name in GH action build (Preston Thorpe)\n* Fix misleading turso-test-runner comments regarding mvcc&readonly (Jussi Saurio)\n* turso-test-runner: fix flakiness with Rust bindings + MVCC combo (Jussi Saurio)\n* Fix datetime parse_modifier() panicing on multibyte utf-8 (Jussi Saurio)\n* Ci fixes (Jussi Saurio)\n* Scalarfunc fuzzer + fixes (Jussi Saurio)\n* bugfix: fix corruption by setting `reserved_bytes` if requested (Avinash Sajjanshetty)\n* fix(vdbe): CHAR() function should handle full Unicode range (Mikaël Francoeur)\n* do not call unlock_after_restart in case of error during wal truncation - because we already released these locks earlier (Nikita Sivukhin)\n* WAL: ignore error from auto-checkpoint and fix bug (Nikita Sivukhin)\n* fix: dont create nested aggregates in cte_fuzz (Jussi Saurio)\n* properly unlock WriteLock if restart failed and ignore Busy errors when attempt to restart WAL file failed (Nikita Sivukhin)\n* fix(parser): reject duplicate PRIMARY KEY clauses on a single column (Mikaël Francoeur)\n* Cte fixes (Jussi Saurio)\n* fix/connect: read page1 in transaction to prevent illegal WAL read (Jussi Saurio)\n* fix: dont panic if decryption or checksum verification fails (Jussi Saurio)\n* fix: fsync DB file before truncating WAL after checkpoint (Jussi Saurio)\n* Return error instead of panicing when 1. short read occurs 2. page type is invalid (Jussi Saurio)\n* Checkpoint restart fix (Nikita Sivukhin)\n* fix: nullif/instr/concat should alloc regs for both args upfront (Jussi Saurio)\n* core/vdbe/sorter: Propagate write errors instead of corrupting data (Preston Thorpe)\n* fix(parser): BETWEEN and LIKE operator precedence with IS NOT NULL (Jussi Saurio)\n* fix: allow CTEs to be referenced multiple times (Jussi Saurio)\n* fix(parser): reject ?0 parameter with proper error instead of panicking (Jussi Saurio)\n* more recursive read lock fixes (Pedro Muniz)\n* fix: allow float literals in ORDER BY clause (Jussi Saurio)\n* fix: correct operator precedence in replace() argument check (Jussi Saurio)\n* fix: reject literal-only index expressions and fix JNI panic (Jussi Saurio)\n* fix: handle CAST without type name for SQLite compatibility (Jussi Saurio)\n* fix/vdbe: convert BusySnapshot to Busy if conn rolled back (Jussi Saurio)\n* sdk-kit: Export busy snapshot error to callers (Pekka Enberg)\n* fix: return NULL instead of panicking in uuid7_timestamp_ms for invalid blobs (Jussi Saurio)\n* fix: handle parse errors gracefully in LIMIT clause (Jussi Saurio)\n* fix: prevent panic on multi-byte UTF-8 in datetime functions (Jussi Saurio)\n* fix: INSERT INTO ... SELECT ... UNION ... inserts all rows (Jussi Saurio)\n* Fix DEFAULT value handling in integrity check (Jussi Saurio)\n* Fix stale overflow page read bug and improper re-entrancy handling in integrity_check (Jussi Saurio)\n* Fix integrity_check NOT NULL validation false positive (Jussi Saurio)\n* Fix compilation after cross-pollution with unused vars PR and integrity check PR (Jussi Saurio)\n* fix: use `checked_cast_text_to_numeric` for `Numeric` cast in `exec_cast` (Pedro Muniz)\n* fix: resolve label in ungrouped aggregation with SELECT DISTINCT (Jussi Saurio)\n* fix/checkpoint: always read page1 from db file when truncating (Jussi Saurio)\n* whopper: Handle BusySnapshot error gracefully (Pekka Enberg)\n* Fix WAL truncate checkpoint discarding uncheckpointed frames (Jussi Saurio)\n* btree.rs: make #[instrument] conditional on debug_assertions (Jussi Saurio)\n* Fix path not fsyncing the DB file after truncate checkpoint (Preston Thorpe)\n* core: make datetime functions faster and fix bugs (Jussi Saurio)\n* Valueiterator with fixes (Jussi Saurio)\n* stress: handle I/O error gracefully instead of panicking (Pekka Enberg)\n* stress: dont panic if get BusySnapshot error (Jussi Saurio)\n* bindings/rust: return error on out of bounds Row::get access (Jussi Saurio)\n* vector: increase fuzz error tolerance on windows (Jussi Saurio)\n* sim: fix yet another apply_snapshot() edge case (Jussi Saurio)\n\n## 0.4.0 -- 2026-01-05\n\n### Added\n* stress: Add support for BEGIN CONCURRENT transactions (Pekka Enberg)\n* stress: add semi colon to transaction statements when printing to log file (Pedro Muniz)\n* Add scripts/corruption-debug-tools (Jussi Saurio)\n* add more asserts in balance operation (Pedro Muniz)\n* bindings/java: implement JDBC4 CharacterStream binding methods (Orange banana)\n* add --db-ref optional arg to run turso-stress against \"template\" database (Nikita Sivukhin)\n* support `format()` function (Fahd Ashour)\n* feat(extensions): add stddev aggregate function to percentile module (Kelvin)\n* simulator: add proper handling for deffered transactions in shadow state (Pedro Muniz)\n* feat: Add support for HAVING without GROUP BY (Nuno Gonçalves)\n* Implement foreign key actions (Preston Thorpe)\n* implement pragma cache spill (Preston Thorpe)\n* Add multiverse debugging instructions (Mikaël Francoeur)\n* Implement busy handlers/callbacks (Preston Thorpe)\n* add readonly checks to ensure we do not change the header (Pedro Muniz)\n* implement state machine for `op_journal_mode` (Pedro Muniz)\n* Add io_uring option for IO backend to simulator (Preston Thorpe)\n* core/storage: implement Cache Spilling (Preston Thorpe)\n* Add dotnet bindings to Turso (Kopylov Dmitriy)\n* fix bug in the sync engine wasm implementation (Nikita Sivukhin)\n* feat(hash-join): add hash matching for equivalent integer and real values (Nuno Gonçalves)\n* implement state machine for parsing input in CLI (Pedro Muniz)\n* add rust-analyzer component to toolchain (Pedro Muniz)\n* Add script to run SQLancer against turso + fix some bugs found by doing so (Jussi Saurio)\n* Add greedy join ordering for large queries (>12 tables) (Jussi Saurio)\n* core/mvcc/cursor: add missing reset state in `next` (Pere Diaz Bou)\n* Add PR template (Preston Thorpe)\n* fix: JSON_INSERT now correctly inserts new keys in nested objects (Mikaël Francoeur)\n* Remove unused parameter in `limbo_exec_rows` and add ergonomic ExecRows trait for testing (Pedro Muniz)\n* translate/optimizer: Finish implementing ANALYZE (Preston Thorpe)\n* core/mvcc/cursor: implement count (Pere Diaz Bou)\n* support libsql:// protocol as a sync url in python driver (Nikita Sivukhin)\n* initialize global header on bootstrap (Pedro Muniz)\n* Rust binding add prepare to transaction (Dave Warnock)\n* add turso bot config (Pedro Muniz)\n* feat: adding check for unquoted literals in values() (Rohith Suresh)\n* Improved Python driver with opt-in asyncio support (Nikita Sivukhin)\n* tcl,makefile: add tcl test infraestructure for mvcc (Pere Diaz Bou)\n* core/mvcc: fix bounds check new rowid (Pere Diaz Bou)\n* Added dot product vector distance (Tejas)\n* sim: add binary tool that converts plan.sql to rust test file (Jussi Saurio)\n* Add explanation for concurrent transactions (Pekka Enberg)\n* testing/fuzz: Add new fuzzer for joins (Preston Thorpe)\n* add lib-release profile (Nikita Sivukhin)\n* planner/vdbe: implement Hash Joins as an alternative to Ephemeral Indexes (Preston Thorpe)\n* Add sync support to the SDK kit (Nikita Sivukhin)\n* fix/mvcc: always reinitialize index iterator on seek (Jussi Saurio)\n* translate/vdbe: add bloom filter (Preston Thorpe)\n* mvcc: implement logical log recovery for indexes + checkpointing of indexes (Jussi Saurio)\n* Docs: add table of contents to CONTRIBUTING.md (Fahd Ashour)\n* finish implementing \"quote\" scalar function for blob types (Preston Thorpe)\n* add alias to colnames explicitly as column-N (Pavan Nambi)\n* Add `#[turso_macros::test]` to automatically create tests that can run MVCC with minimal code changes (Pedro Muniz)\n* mvcc: introduce stateful \"dual cursor\" (Jussi Saurio)\n* introduce program execution state in order to run stmt to completion in case of finalize or reset (Nikita Sivukhin)\n* mvcc: add some plumbing for index support (Jussi Saurio)\n* core/mvcc/tests: add fuzz test for mvcc with checkpoint and with CRUD ops (Pere Diaz Bou)\n* translate/planner: Implement Index creation on arbitrary expressions (Preston Thorpe)\n* Support table xinfo (Nikita Sivukhin)\n* core/mvcc/cursor: implement prev and last (Pere Diaz Bou)\n* Trigger support (Jussi Saurio)\n* translate/insert: Implement INSERT OR REPLACE (Preston Thorpe)\n* Add ColDef struct to make schema::Column creation more ergonomic (Preston Thorpe)\n* Support DELETE ... RETURNING (Jussi Saurio)\n* Refactor RETURNING to support arbitrary expressions (Jussi Saurio)\n* bindings/java: implement JDBC4 InputStream binding methods (ASCII/Binary, no-length and long overloads) (Orange banana)\n* Add RowSet<Add/Read/Test> instructions and rowset implementation (Jussi Saurio)\n* bindings/java: implement stream binding methods (int, InputStream, int) in JDBC4PreparedStatement (Orange banana)\n* workflows: Add GITHUB_TOKEN to all Nyrkiö steps (Henrik Ingo)\n* extensions/vtabs: implement remaining opcodes (Preston Thorpe)\n* Throw an error when adding generated columns via an alter table (Rohith Suresh)\n* add some docs for index method (Nikita Sivukhin)\n* bindings/java: Implement setObject(int, Object) in JDBC4PreparedStatement (Orange banana)\n\n### Updated\n* Minor cleanups in function.rs and refactoring allocations (Preston Thorpe)\n* WAL: Stop copying page buffers during checkpoint (Preston Thorpe)\n* test runner: Test Converter (Pedro Muniz)\n* core/storage: Eliminate buffer copy in begin_write_btree_page() (Pekka Enberg)\n* Test runner Foundation (Pedro Muniz)\n* show failure output in the end with cargo nextest (Pedro Muniz)\n* core/storage: Zero remaining buffer bytes in begin_write_btree_page() (Pekka Enberg)\n* core/storage: Make PagerInner::buffer use Arc<Buffer> (Pekka Enberg)\n* Adjust merge script to truncate the PR template (Preston Thorpe)\n* Improvements to turso_stress (Mikaël Francoeur)\n* Page management cleanups (Pekka Enberg)\n* btree: reset `AdvanceState` after last state transition (Pedro Muniz)\n* Make BTree and MVCC cursor `Send + Sync` (Pedro Muniz)\n* Raise log level polling frequency (Mikaël Francoeur)\n* btree/pager: performance tuning (Preston Thorpe)\n* chore/btree: remove unused code (Jussi Saurio)\n* Modify bench-profile to allow generating better flamegraphs (Jussi Saurio)\n* all-mvcc: uncomment working tests (Pere Diaz Bou)\n* perf/sorter: sort pointers instead of records, use arena allocation (Jussi Saurio)\n* Remove TursoDBFactory (Mikaël Francoeur)\n* Save sync configuration (Nikita Sivukhin)\n* Optimized RecordCursor, Remove read_varint_fast (Khashayar Fereidani)\n* Run statements to completion on reset (Martin Mauch)\n* adjust tpc-h bench script to more easily compare results (Preston Thorpe)\n* stress: use multithreaded runtime (Jussi Saurio)\n* Conflict end txn (Nikita Sivukhin)\n* Accept SQL query using `AsRef<str>` instead of `&str` (Arto Bendiken)\n* core: remove mutex from ImmutableRecord::cursor (Jussi Saurio)\n* Enable tokio-unstable in Antithesis image (Mikaël Francoeur)\n* Prevent dropping columns that contain fk references (Preston Thorpe)\n* lint/perf: deny eager fallback function calls (ok_or, map_or, unwrap_or) (Jussi Saurio)\n* reset statement in query() (Mikaël Francoeur)\n* User rust-gdb instead of gdb (Mikaël Francoeur)\n* Antithesis observability improvements (Mikaël Francoeur)\n* Rust bindings sync (Nikita Sivukhin)\n* Refactor/improve performance of commit path (Preston Thorpe)\n* Sdk kit rust bindings (Nikita Sivukhin)\n* Yet another refactor of INSERT translation (Preston Thorpe)\n* stress: Make SQLite integrity check more explicit (Pekka Enberg)\n* Sdk kit refactoring (Nikita Sivukhin)\n* perf/vdbe: reuse&clear ephemeral cursor on repeat invocations (Jussi Saurio)\n* perf/prepare: various optimizations (Jussi Saurio)\n* Improve lexer performance by using SIMD (Khashayar Fereidani)\n* Remove unnecessary `Cell` and `RefCell` for better multithreaded safety (Pedro Muniz)\n* Partial sync experimental (Nikita Sivukhin)\n* remove unneeded Result in exec unixepoch (Juan V. García)\n* Lexer/Parser Optimization and refactoring (Khashayar Fereidani)\n* tcl: run PRAGMA journal_mode=experimental_mvcc with mvcc (Pere Diaz Bou)\n* SDK tweaks (Nikita Sivukhin)\n* core/mvcc: set_null_flag(false) when seek is called (Pere Diaz Bou)\n* Mark triggers as experimental (Jussi Saurio)\n* Set all testing dbs to WAL journal mode (Preston Thorpe)\n* Remove run once from `Statement` (Pedro Muniz)\n* Use u64::from instead of .into() (Elina)\n* remove the warning directive to allow environment filter to work (Pedro Muniz)\n* core/execute: use same code for generating rowid in mvcc as in btree (Pere Diaz Bou)\n* Improve MVCC DX by dropping `--experimental-mvcc` flag (Pekka Enberg)\n* aws/sim: disable io-uring (Jussi Saurio)\n* Local sync server (Nikita Sivukhin)\n* Simplify slot bitmap to remove complex unused optimizations (Preston Thorpe)\n* clean up core tester to use `conn.execute` and `conn.exec_rows` for parsing correctly the expected values from select queries (Pedro Muniz)\n* Connection small refactor (Dave Warnock)\n* Enable MVCC with `PRAGMA journal_mode` (Pedro Muniz)\n* propagate partial sync settings in the web (Nikita Sivukhin)\n* Consider Order by expressions collation when deciding candidate index for iteration (Pedro Muniz)\n* Checkpoint cleanup (Jussi Saurio)\n* use cmath from system libraries only in tests in order to be more portable (Nikita Sivukhin)\n* No tempfiles on wasm (Nikita Sivukhin)\n* core: Make Pager thread-safe (Pekka Enberg)\n* update go mod name as we will serve module through custom proxy (Nikita Sivukhin)\n* Install sqlite locally to run tests and other scripts (Pedro Muniz)\n* rename speculative load to prefetch (docs already uses this terminology) (Nikita Sivukhin)\n* docs: update clippy command in CONTRIBUTING.md to match CI job (Nuno Gonçalves)\n* stress: Make random seed configurable (Pekka Enberg)\n* Devcontainer setup (Nikita Sivukhin)\n* core/mvcc/cursor: return previous max id (Pere Diaz Bou)\n* Update wording of AI section of PR template (Jussi Saurio)\n* whopper: Simulate time (Pekka Enberg)\n* increase lantency check for flaky test in test_read_path.rs (Preston Thorpe)\n* run get(...) to completion - otherwise INSERT ... RETURNING will be executed incorrectly (Nikita Sivukhin)\n* ci: run TCL tests for MVCC under CI (Pere Diaz Bou)\n* Get mutable reference to table in Schema so we can modify it with `Arc::make_mut` (Pedro Muniz)\n* also check for None `checkpointed_txid_max_old` when determining if `RowVersion` exists in the Db (Pedro Muniz)\n* Go driver (Nikita Sivukhin)\n* Minor improvements and refactoring in btree.rs (Preston Thorpe)\n* revert change in index_scan_compound_key_fuzz (Pedro Muniz)\n* Make `checkpointed_txid_max_old` be an `Optional<NonZeroU64>` (Pedro Muniz)\n* Remove some useless clones in pager.rs (Preston Thorpe)\n* upgrade cargo dist to 0.30.2 (Nikita Sivukhin)\n* Partial sync improvements (Nikita Sivukhin)\n* Prevent concurrent tx ctrl and write (Nikita Sivukhin)\n* core/mvcc/cursor: ignore non visible rows on \"last\" (Pere Diaz Bou)\n* core/mvcc/tests: un-ignore seek tests (Pere Diaz Bou)\n* CI: simulator tweaks (Jussi Saurio)\n* chore: remove experimental_indexes feature flags (Jussi Saurio)\n* do not propagate the MvStore to opcodes (Pedro Muniz)\n* Run BEFORE and AFTER update triggers on upserts (Mikaël Francoeur)\n* Ignore SQLITE_BUSY during auto-checkpoint (Mikaël Francoeur)\n* Allocate Page 1 in pager on open (Pedro Muniz)\n* Prevent creating index on rowid pseudo-column (Mikaël Francoeur)\n* Improve Android compatibility (Martin Mauch)\n* Simulator Roadmap (Alperen Keleş)\n* guard subjournal access within single connection (Nikita Sivukhin)\n* Turso sdk kit version (Nikita Sivukhin)\n* Automatically Propagate Encryption options (Pedro Muniz)\n* core/mvcc: state machines for `prev`, `next`, `exists`, `rewind`, `last` (Pere Diaz Bou)\n* btree/balance: assert that if multiple overflow cells, they are adjacent sequential (Jussi Saurio)\n* simulator: generate more INSERT INTO ... SELECT self-inserts (Mikaël Francoeur)\n* Arc swap MvStore + centralize MvStore acquisition (Pedro Muniz)\n* mvcc: do not store index data twice in Row (Jussi Saurio)\n* Col name in trigger subquery (Rohith Suresh)\n* Update AEGIS crate version (Avinash Sajjanshetty)\n* SDK kit (Nikita Sivukhin)\n* Ensure LIKE is case-sensitive for non-ASCII characters (Tejas)\n* sim/aws: memory IO 100% of the time, differential 50% of the time (Jussi Saurio)\n* mvcc: reconstruct index rows on logical log recovery (Jussi Saurio)\n* More mvcc index stuff (Jussi Saurio)\n* mvcc: make more MvccLazyCursor ops compatible with indexes (Jussi Saurio)\n* drop triggers if table drops (Pavan Nambi)\n* Kill unwrap() calls in MVCC module (Pekka Enberg)\n* Kill unwrap() in vector module (Pekka Enberg)\n* Kill unwrap() calls in VDBE module (Pekka Enberg)\n* Tidied imports in Rust binding example without unwrap (Dave Warnock)\n* Rust binding example without unwrap (Dave Warnock)\n* Kill unwrap() calls in JSON module (Pekka Enberg)\n* use i64 for registers p1,p2,p3,p5 in EXPLAIN output (Mikaël Francoeur)\n* Kill unwrap() in macros (Pekka Enberg)\n* Kill unwrap in incremental module (Pekka Enberg)\n* mvcc: refactor RowID.row_id to be either i64 or a record (Jussi Saurio)\n* Kill unwrap() calls in extensions (Pekka Enberg)\n* SQLite C API improvements (Nikita Sivukhin)\n* simulator: only check all tables if we have any tables to check (Pedro Muniz)\n* core: Switch to parking_lot::Mutex (Pekka Enberg)\n* Simulator: refactor and simplify `InteractionPlan` (Pedro Muniz)\n* Enable nested self-inserts in simulator (Mikaël Francoeur)\n* correct order in column creation in join  tests (Pavan Nambi)\n* Nyrkiö: Set all comment-on to false (Henrik Ingo)\n* Partial sync basic (Nikita Sivukhin)\n* Use `AsValueRef` in more functions (Pedro Muniz)\n* treat parameters as \"constant\" within a query (Nikita Sivukhin)\n* Completion: make it Send + Sync (Nikita Sivukhin)\n* core/mvcc: use btree cursor to navigate rows (Pere Diaz Bou)\n* core: update aegis (Daeho Ro)\n* Refactor affinity conversions for reusability (Pedro Muniz)\n* Create `AsValueRef` trait to allow us to be agnostic over ownership of `Value` or `ValueRef` (Pedro Muniz)\n* Move value functions to separate file (Pedro Muniz)\n* Avoid heavy macro (Nikita Sivukhin)\n* Stop blob json parsing at null terminator (Duy Dang)\n* core/translate: Remove unused ParamState (Preston Thorpe)\n* Toy index improvements (Nikita Sivukhin)\n* use dyn DatabaseStorage instead of DatabaseFile (Nikita Sivukhin)\n* Prevent DROP TABLE when table is referenced by foreign keys (Joao Faria)\n* core/vdbe Handle renaming child FK definitions in rename table stmt (Preston Thorpe)\n* Prevent misuse of subqueries that return multiple columns (Jussi Saurio)\n* Optimize and refactor schema::Column type (Preston Thorpe)\n* Clean up Connection::from_uri() by using DatabaseOpts (Rohith Suresh)\n* Select correct collation sequence for compound select (Pedro Muniz)\n* core: Disable autovacuum by default (Pekka Enberg)\n* Make mimalloc dependency optional (Pekka Enberg)\n* Update Java package version in scripts/update-version.py (Pekka Enberg)\n\n### Fixed\n* stress: Keep going on I/O errors instead of panicking (Pekka Enberg)\n* fix: lint warnings unused variable/import in release build (Khashayar Fereidani)\n* integrity check: do not throw errors if pending byte page is never used (Pedro Muniz)\n* fix(storage): improve error message for truncated database files (Srinivas A)\n* fix/pager&wal: ensure wal write lock held when rolling back frame_cache (Jussi Saurio)\n* Enable debug_assertions for antithesis profile (Pekka Enberg)\n* General improvements, micro-optimizations and bug fix for core/functions (Khashayar Fereidani)\n* fix dockerfiles (Jussi Saurio)\n* Fix DROP TABLE to properly handle FK actions and allow for orphaned/NULL FK refs (Preston Thorpe)\n* core/mvcc/logical_log: off by one error reading logical log encrypted (Pere Diaz Bou)\n* Fix squeue overflow issue in io_uring (Preston Thorpe)\n* Affinity fixes (Pedro Muniz)\n* Read only fixes (Pedro Muniz)\n* slightly adjust fixed unstable test (Nikita Sivukhin)\n* CI test setup fixes + fix GroupCompletion bug (Pedro Muniz)\n* pyturso: fix panic (Nikita Sivukhin)\n* fix(core/translate): apply affinity conversion to hash join build and probe keys (Nuno Gonçalves)\n* fix/core: fix transaction issues (Jussi Saurio)\n* sim: fix apply_snapshot for create table, create index, drop column (Jussi Saurio)\n* fix stack overflow in long unary expressions (\"' from Jussi Saurio)\n* Fix RTRIM ignoring trailing tabs (Krishna Vishal)\n* Fix incorrect conversion from TEXT to INTEGER when text is a number followed by a trailing non-breaking space (Krishna Vishal)\n* fix stack overflow in long unary expressions (\")\n* Sync fixes (Nikita Sivukhin)\n* fix(core): prevent ALTER COLUMN from resulting in tables with only generated columns (Nuno Gonçalves)\n* core/storage: fixes for the commit path and `io_uring` (Preston Thorpe)\n* aws/sim: fixes and tweaks (Jussi Saurio)\n* fix succeeded check (Pedro Muniz)\n* fix/sim: all alter table ops must be recorded and applied in order (Jussi Saurio)\n* fix coroutine panic: replace ended_coroutine Bitfield with vec (Jussi Saurio)\n* Fix: update schema if DDL commit succeeded but checkpoint failed (Jussi Saurio)\n* Fix race condition in WAL frame_cache update with io_uring (Mikaël Francoeur)\n* fix(json): properly serialize infinite values (Nuno Gonçalves)\n* fix(core/util): reject integer primary key underflow (Nuno Gonçalves)\n* fix/core: decouple autocheckpoint result from transaction durability (Jussi Saurio)\n* Fix StreamingWalReader behavior with checksums of uncommitted frames (Preston Thorpe)\n* Fix ignored completion in free_page (Preston Thorpe)\n* Fix more instances of marking pages dirty after modification (Jussi Saurio)\n* fix/pager: mark freelist trunk page dirty BEFORE modifying it (Jussi Saurio)\n* fix/sim: modify rows in ALTER TABLE properly (Jussi Saurio)\n* core: Fix integrity_check pragma code generation (Pekka Enberg)\n* antithesis: Fix unique constraint exception handling in stress-composer tests (Pekka Enberg)\n* Fix Github go workflow (Nikita Sivukhin)\n* sim: fix bug in apply_snapshot (Jussi Saurio)\n* Sync better error messages (Nikita Sivukhin)\n* Fix CTE scope propagation for compound SELECTs (Martin Mauch)\n* Sqlite3 compat fix (Nikita Sivukhin)\n* Sim transaction fixes (Jussi Saurio)\n* sim/aws: comment on existing issues instead of skipping duplicates (Jussi Saurio)\n* Fix complex unique sqlite3 compat (Nikita Sivukhin)\n* fix/btree: disable move_to_rightmost optimization with triggers (Jussi Saurio)\n* Fix descending index scan returning rows when seek key is NULL (Jussi Saurio)\n* Fix external sorter losing rows when chunks need async IO (Jussi Saurio)\n* sim: stop ignoring sql execution errors (Jussi Saurio)\n* Fix two bugs with compound selects (Jussi Saurio)\n* Fix IN operator translation logic (Nikita Sivukhin)\n* fix/mvcc: seek() must seek from both mv store and btree (Jussi Saurio)\n* Fix panic in optimizer when usable constraint refs is empty (Preston Thorpe)\n* optimizer: fix incorrect index_col_pos assigned when multiple constraints ref same join key (Preston Thorpe)\n* Bloom filter fixes (Preston Thorpe)\n* fix: escape backslashes in json_object string values (Martin Mauch)\n* fix/mvcc: use existing schema object in mvcc bootstrap (Jussi Saurio)\n* Mvcc bugfixes (Jussi Saurio)\n* Fix vtab memory leak (Nikita Sivukhin)\n* Fix comparison of large numbers (Mikaël Francoeur)\n* Fix to Google Books link in CONTRIBUTING (Juan V. García)\n* core/mvcc: fix `exists` to use `BTreeCursor` as fallback (Pere Diaz Bou)\n* core/io: Improve error handling (Pekka Enberg)\n* core/index_method: Improve error handling in toy_vector_spare_ivf.rs (Pekka Enberg)\n* Triggers: fix issues with ALTER TABLE (Jussi Saurio)\n* Return parse error if NULLS LAST used in ORDER BY (Jussi Saurio)\n* Fix: Drop internal DBSP table when dropping materialized view (Martin Mauch)\n* Fix seek not applying correct affinity to seek expr (Pedro Muniz)\n* Fix EXISTS on LEFT JOIN null rows (Duy Dang)\n* Fix error handling on provided insert column count mismatch (Jussi Saurio)\n* core/vdbe: Fix incorrect `unreachable` condition in op_seek_rowid (Preston Thorpe)\n* Update and fix nix build (Alexander Hirner)\n* Fix INSERT UNION ALL (Duy Dang)\n* Fix LEFT JOIN subqueries reusing stale right-side values (Duy Dang)\n* Throw an error in case duplicate CTE names are found (Rohith Suresh)\n* Fix self-insert SUM when table uses INTEGER PRIMARY KEY (Duy Dang)\n\n# Changelog\n\n## 0.3.0 -- 2025-10-30\n\n### Added\n* Implement wasNull tracking in ResultSet getter methods (김민석)\n* Support subqueries in all positions of a SELECT statement (Jussi Saurio)\n* Initialize LIMIT after after ORDER BY / GROUP BY initialization (Jussi Saurio)\n* index_method: implement basic trait and simple toy index (Nikita Sivukhin)\n* Where clause subquery support (Jussi Saurio)\n* sqlite3: Add multi-statement support for sqlite3_exec() (Preston Thorpe)\n* Add DISTINCT support to aggregate operator (Glauber Costa)\n* docs: Add vector search section to database manual (Pekka Enberg)\n* Support statement-level rollback via anonymous savepoints (Jussi Saurio)\n* Add AtomicEnum proc macro to generate atomic wrappers to replace RwLocks (Preston Thorpe)\n* Fix git directory resolution in simulator to support worktrees (Jussi Saurio)\n* Add Miri support for turso_stress, with bash scripts to run (Bob Peterson)\n* tests: Add rowid alias fuzz test case (Pekka Enberg)\n* core/translate: throw parse error on unsupported GENERATED column constraints (Preston Thorpe)\n* translate/insert: more refactoring and support INSERT OR IGNORE (Preston Thorpe)\n* sql_generation: Fix implementation of LTValue and GTValue for Text types (Jussi Saurio)\n* core/mvcc: implement CursorTrait on MVCC cursor (Pere Diaz Bou)\n* Add test case for vector() format crash (Pedro Muniz)\n* Add correct  unique constraint test for tcl (Pedro Muniz)\n* stress: Add busy timeout support with 5 second default (Pekka Enberg)\n* Add WINDOW functions to COMPAT.md (Jussi Saurio)\n* core/translate: Add if alias and allow iff to have more arguments (Pavan Nambi)\n* core/btree: try to introduce trait for cursors (Pere Diaz Bou)\n* bindings/java: Add support for publishing to Maven Central (Kim Seon Woo)\n* add Calendar-based timezone conversion support in JDBC4ResultSet (김민석)\n* Add Nightly versions of benchmarks that run on Nyrkiö runners (Henrik Ingo)\n* Add support for sqlite_version() star syntax (Glauber Costa)\n* core/translate: implement basic foreign key constraint support (Preston Thorpe)\n* Simulator: Add Drop and pave the way for Schema changes (Pedro Muniz)\n* core/io: remove new_dummy in place of new_yield (Pere Diaz Bou)\n* Add MVCC checkpoint threshold pragma (bit-aloo)\n* core/incremental: Implement \"is null\" and \"is not null\" tests for view filter (Glauber Costa)\n* core/mvcc: implement PartialOrd for RowId (Pere Diaz Bou)\n* core/io: Add completion group API for managing multiple I/O operations (Pekka Enberg)\n* Add short writes to unreliable-libc (FamHaggs)\n* add basic examples for database-wasm package (Nikita Sivukhin)\n* core/wal: introduce transaction_count, same as iChange in sqlite (Pere Diaz Bou)\n\n### Updated\n* antithesis: Upload config image in GitHub Actions workflow (Pekka Enberg)\n* perf/throughput: Improve reproducibility (Pekka Enberg)\n* translate: disallow correlated subqueries in HAVING and ORDER BY (Jussi Saurio)\n* Stmt reset cursors (Nikita Sivukhin)\n* reset move_to_right_state cached state in case of quick balancing (Nikita Sivukhin)\n* index_method: fully integrate into query planner (Nikita Sivukhin)\n* core: Switch to FxHash to improve performance (Pekka Enberg)\n* bindings/rust: Enable mimalloc as global allocator (Pekka Enberg)\n* index method syntax extension (Nikita Sivukhin)\n* Tighten Nyrkio p-value to 0.00001 (Henrik Ingo)\n* Strict numeric cast for op_must_be_int (bit-aloo)\n* core/vdbe: Reuse cursor in op_open_write() (Pekka Enberg)\n* core: Switch RwLock<Arc<Pager>> to ArcSwap<Pager> (Pekka Enberg)\n* Always returns Floats for sum and avg on DBSP aggregations (Glauber Costa)\n* perf/throughput: Use connection per transaction in rusqlite benchmark (Pekka Enberg)\n* Return null terminated strings from sqlite3_column_text (Preston Thorpe)\n* Order by heap sort (Nikita Sivukhin)\n* core/storage: Cache schema cookie in Pager (Pekka Enberg)\n* github: Run fuzz tests in a separate workflow (Pekka Enberg)\n* tests: Separate integration and fuzz tests (Pekka Enberg)\n* Vector speedup (Nikita Sivukhin)\n* parser: translate boolean values to literal when parsing column constraints (Preston Thorpe)\n* core/io: Make random generation deterministically simulated (Pedro Muniz)\n* core: move BTreeCursor under MVCC cursor (Pere Diaz Bou)\n* Move completion code to separate file (Pedro Muniz)\n* avoid unnecessary allocations (Nikita Sivukhin)\n* Make sure explicit column aliases have binding precedence in orderby (Pavan Nambi)\n* tests/integration: Reduce collation fuzz test iterations (Pekka Enberg)\n* Switch random blob creation to `get_random` (Pedro Muniz)\n* Shared WAL lock scoping (Pedro Muniz)\n* Remove tests that alter testing.db from views.test (Preston Thorpe)\n* tests/integration: Disable rowid alias differential fuzz test case (Pekka Enberg)\n* core/storage: Reduce logging level (Pekka Enberg)\n* cli: Improve manual page display (Pavan Nambi)\n* stress: prevent thread from holding write lock and then stopping (Jussi Saurio)\n* translate/select: prevent multiple identical non-aliased table references (Preston Thorpe)\n* Prevent on conflict column definitions on CREATE TABLE or opening DB (Preston Thorpe)\n* cli: .tables and .indexes to show data from attached tables aswell (Konstantinos Artopoulos)\n* bindings/rust: propagate the DropBehavior of a dropped tx to next access of DB, instead of panicing (Jussi Saurio)\n* Replace io_yield_many with completion groups (Pekka Enberg)\n* core/bree: remove duplicated code in BTreeCursor (Pere Diaz Bou)\n* core: Unsafe Send and Sync pushdown (Pekka Enberg)\n* Run SQLite integrity check after stress test run (Pedro Muniz)\n* Document ThreadSanitizer in CONTRIBUTING.md (Pekka Enberg)\n* tests/fuzz: Accept SEED env var for all fuzz tests (Preston Thorpe)\n* Make Rust bindings actually async (Pedro Muniz)\n* perf/throughput: force sqlite to use fullfsync (Pedro Muniz)\n* relax check in the vector test (Nikita Sivukhin)\n* Allow using indexes to iterate rows in UPDATE statements (Jussi Saurio)\n* Refactor INSERT translation to a modular setup with emitter context (Preston Thorpe)\n* increment Changes() only once conditionally (Pavan Nambi)\n* make comparison case sensitive (Pavan Nambi)\n* bindings/rust: Bump version recommendation to 0.2 (Kyle Kelley)\n* Run simulator under Miri (Bob Peterson)\n* Import workspace crates by name and not path (Pedro Muniz)\n* names shall not be shared between tables,indexs,vtabs,views (Pavan Nambi)\n* Get aliases to where shall they be used (Pavan Nambi)\n* remove cfg for `MAP_ANONYMOUS` (Pedro Muniz)\n* Simulator: `Drop Index` (Pedro Muniz)\n* Restrict joins to max 63 tables and allow arbitrary number of table columns (Jussi Saurio)\n* Simulator: persist files in sim memory IO for integrity check (Pedro Muniz)\n* Simulator: `ALTER TABLE` (Pedro Muniz)\n* Move all checksum tests behind the feature flag (Avinash Sajjanshetty)\n* Nyrkiö nightly: Reduce frequency to 1 per 24h (Henrik Ingo)\n* Vector improvements (Nikita Sivukhin)\n* Optimize sorter (Jussi Saurio)\n* Make sqlite_version() compatible with SQLite (Glauber Costa)\n* optimizer: optimize range scans to use upper and lower bounds more efficiently (Jussi Saurio)\n* translate: make bind_and_rewrite_expr() reject unbound identifiers if no referenced tables exist (Jussi Saurio)\n* Simulator: ignore `Property::AllTableHaveExpectedContent` when counting stats (Pedro Muniz)\n* When pwritev fails, clear the dirty pages (Pedro Muniz)\n* mvcc: Disable automatic checkpointing by default (Pekka Enberg)\n* Integrity check enhancements (Jussi Saurio)\n* Remove unsafe pointers (`RawSlice`) from `RefValue` (Levy A.)\n* Make table name not repeat in simulator (bit-aloo)\n* perf/throughput: Delete database before benchmark run (Pekka Enberg)\n* emit proper column information for explain prepared statements (Nikita Sivukhin)\n* core/mvcc/logical-log: switch RwLock to parking_lot (Pere Diaz Bou)\n* Modify Interactions Generation to only generate possible queries (Pedro Muniz)\n* eliminate the need for another `Once` in Completion (Pedro Muniz)\n* Rename Completion methods (Pedro Muniz)\n* Top level examples (Nikita Sivukhin)\n* docs: Explain BEGIN CONCURRENT (Pekka Enberg)\n* MVCC: do checkpoint writes in ascending order of rowid (Jussi Saurio)\n* core/mvcc: filter out seek results where is not same table_id (Pere Diaz Bou)\n* Simulator diff print (Pedro Muniz)\n* Improve simulator cli (bit-aloo)\n* core/mvcc: automatic logical log checkpointing on commit (Pere Diaz Bou)\n* remove `dyn DatabaseStorage` replace it with `DatabaseFile` (Pedro Muniz)\n* Actually enforce uniqueness in create unique index (Jussi Saurio)\n* core/wal: check index header on begin_write_tx (Pere Diaz Bou)\n* Disallow unexpected interop between WAL mode and MVCC mode (Jussi Saurio)\n\n### Fixed\n* Fix database state going back in time after sync (Nikita Sivukhin)\n* Fix foreign key constraint enforcement on UNIQUE indexes (Jussi Saurio)\n* bindings/javascript: Improve open error messages (Pekka Enberg)\n* core/storage: Fix WAL already enabled issue (Pekka Enberg)\n* Return better syntax error messages (Diego Reis)\n* core/vdbe: fix ALTER COLUMN to propagate constraints to other table references (Preston Thorpe)\n* core/translate: fix ALTER COLUMN to propagate other constraint references (Preston Thorpe\")\n* core/translate: fix ALTER COLUMN to propagate other constraint references (Preston Thorpe)\n* Fix deferred FK violations check before committing to WAL (Jussi Saurio)\n* translate/select: Fix rewriting Rowid expression when no btree table exists in joined table refs (Preston Thorpe)\n* Throw parse error on CHECK constraint in create table (Preston Thorpe)\n* Fix: rolling back tx on Error should set autocommit to true (Jussi Saurio)\n* Fix: outer CTEs should be available in subqueries (Jussi Saurio)\n* Fix change counter incrementation (Jussi Saurio)\n* Fix another \"should have been rewritten\" translation panic (Jussi Saurio)\n* Simulator: fix alter table shadowing to modify index column name (Pedro Muniz)\n* fix backwards compatible rowid alias behaviour (Pedro Muniz)\n* Fix typo in manual.md (Yevhen Kostryka)\n* core/vdbe: Improve IdxDelete error messages with context (Pekka Enberg)\n* Fix disallow reserved prefixes in ALTER TABLE RENAME TO (xmchx)\n* Fix incorrectly using an equality constraint twice for index seek (Jussi Saurio)\n* Fix IN operator NULL handling (Diego Reis)\n* Cleanup Simulator + Fix Column constraints in sql generation (Pedro Muniz)\n* Fix rusqlite compatibility claim (Dave Warnock)\n* Fix re-entrancy of op_destroy (used by DROP TABLE) (Jussi Saurio)\n* Fix VDBE program abort (Nikita Sivukhin)\n* Fix attach I/O error with in-memory databases (Preston Thorpe)\n* core/incremental: Fix re-insertion of data with same key (Glauber Costa)\n* core/io/unix: Fix short writes in try_pwritev_raw() (FamHaggs)\n\n## 0.2.0 -- 2025-10-03\n\n### Added\n\n* docs: Add section on MVCC limitations (Pekka Enberg)\n* sim: add Profile::SimpleMvcc (Jussi Saurio)\n* Add encryption internals docs (Avinash Sajjanshetty)\n* core/storage: Apple platforms support (Charly Delaroche)\n* Reject unsupported FROM clauses in UPDATE (Mikaël Francoeur)\n* Add Database::indexes_enabled() (Jussi Saurio)\n* Add Mold linker setup to CONTRIBUTING.md (Pekka Enberg)\n* support multiple conflict clauses in upsert (Nikita Sivukhin)\n* stress: add option to choose how many tables to generate (Pere Diaz Bou)\n* add manual page about materialized views (Glauber Costa)\n* support mixed integer and float expressions in the expr_compiler (Glauber Costa)\n* github: Add 30 minute timeout to all jobs (Pekka Enberg)\n* Add `CAST` to fuzzer (Levy A.)\n* Implement json_tree (Mikaël Francoeur)\n* translate: disable support for UPDATE ... ORDER BY (Jussi Saurio)\n* MVCC: support alter table (Jussi Saurio)\n* mvcc: add blocking checkpoint (Jussi Saurio)\n* Add built-in manual pages for Turso (Glauber Costa)\n* Support referring to rowid as _rowid_ or oid (Jussi Saurio)\n* translate: implement Sequence opcode and fix sort order (Preston Thorpe)\n* mvcc: add blocking checkpoint lock (Jussi Saurio)\n* translate/emitter: Implement partial indexes (Preston Thorpe)\n* Support UNION queries in DBSP-based Materialized Views (Glauber Costa)\n* Add encryption throughput test (Avinash Sajjanshetty)\n* Support JOINs in DBSP materialized views (Glauber Costa)\n* Fix C API compatibility tests and add a minimal CI (Andrea Peruffo)\n* Introduce instruction VTABLE (Lâm Hoàng Phúc)\n* core/mvcc: introduce with_header for MVCC header update tracking (Pere Diaz Bou)\n* Inital support for window functions (Piotr Rżysko)\n* Implement Min/Max aggregators (Glauber Costa)\n* Add quoted identifier test cases for `ALTER TABLE` (Levy A.)\n* add perf/throughput/rusqlite to workspace (Pedro Muniz)\n* add perf/throughput/turso to workspace (Pedro Muniz)\n* Add per page checksums (Avinash Sajjanshetty)\n* core/throughput: Add per transaction think time support (Pekka Enberg)\n* Fix simulator docker build chef by adding whopper directory (Preston Thorpe)\n* Implement the balance_quick algorithm (Jussi Saurio)\n* benchmark: introduce simple 1 thread concurrent benchmark for mvcc/sq… (Pere Diaz Bou)\n* perf: Add simple throughput benchmark (Pekka Enberg)\n* Add BEGIN CONCURRENT support for MVCC mode (Pekka Enberg)\n* add explicit usize type annotation to range iterator in test (Denizhan Dakılır)\n* whopper: A new DST with concurrency (Pekka Enberg)\n* serverless: Add Connection.reconnect() method (Mayank)\n* Return parse error for unsupported exprs (Jussi Saurio)\n* translate: return parse error for unsupported join types (Jussi Saurio)\n* Add scripts that help debug bugs from simulator (Jussi Saurio)\n* core: Support ceiling modifier in datetime (Ceferino Patino)\n\n### Updated\n\n* CI: run long fuzz tests and stress on every PR, use 2 threads for stress (Jussi Saurio)\n* Disallow INDEXED BY / NOT INDEXED in select (Jussi Saurio)\n* core/mvcc: Rename \"-lg\" to \"-log\" (Pekka Enberg)\n* docs: Document more CLI command line options (Pekka Enberg)\n* Update man pages for encryption (Avinash Sajjanshetty)\n* core/mvcc: Return completions from logical log methods (Pedro Muniz)\n* Improve MCP configuration docs (Jamie Barton)\n* Enable encryption properly in Rust bindings, whopper, and throughput tests (Avinash Sajjanshetty)\n* Enable checksums only if its opted in via feature flag (Avinash Sajjanshetty)\n* Sync engine defered sync (Nikita Sivukhin)\n* core/vdbe: Avoid cloning Arc<MvStore> on every VDBE step (Pekka Enberg)\n* mvcc: dont try to end pager tx on connection close (Jussi Saurio)\n* Allow workflow_dispatch for all CI to allow for re-running jobs (Preston Thorpe)\n* printf should truncates floats (Pavan Nambi)\n* simulator: reopen database with mvcc and indexes when necessary (Pedro Muniz)\n* core/storage: Switch page cache queue to linked list (Pekka Enberg)\n* Improve throughput benchmarks (Pekka Enberg)\n* Beta (Pekka Enberg)\n* make connect() method optional and call it implicitly on first query execution (Nikita Sivukhin)\n* mvcc: dont use mv store for ephemeral tables (Jussi Saurio)\n* Simulator: Concurrent transactions (Pedro Muniz)\n* Measure read/write latencies in encryption benchmarks (Avinash Sajjanshetty)\n* simplify `exec_trim` code + only pattern match on whitespace char (Pedro Muniz)\n* MVCC: Handle table ID / rootpages properly for both checkpointed and non-checkpointed tables (Jussi Saurio)\n* Make encryption opt in via flag (Avinash Sajjanshetty)\n* core/mvcc: Optimize exclusive transaction check (Pekka Enberg)\n* core: change root_page to i64 (Pere Diaz Bou)\n* core/storage: Remove unused import from encryption.rs (Pekka Enberg)\n* small improvement of stress testing tool (Nikita Sivukhin)\n* sum() can throw integer overflow (Duy Dang)\n* correct span in ParseUnexpectedToken (Lâm Hoàng Phúc)\n* Remove double-quoted identifier assert (Diego Reis)\n* substr scalar should also work with non-text values (Diego Reis)\n* core: Disallow CREATE INDEX when MVCC is enabled (Pekka Enberg)\n* javascript: Rename \"browser\" packages to \"wasm\" (Pekka Enberg)\n* core/vdbe: Wrap Program::n_change with AtomicI64 (Pekka Enberg)\n* use explicit null if it set instead of column default value (Nikita Sivukhin)\n* Improve encryption module (Avinash Sajjanshetty)\n* Remove vendored parser now that we have our own (Preston Thorpe)\n* core/storage: Wrap Pager::commit_info with RwLock (Pekka Enberg)\n* core/storage: Wrap WalFile::{max,min}_frame with AtomicU64 (Pekka Enberg)\n* core/storage: Wrap WalFile::max_frame_read_lock_index with AtomicUsize (Pekka Enberg)\n* core/storage: Mark Page as Send and Sync (Pekka Enberg)\n* Move turso.png image to assets directory (Preston Thorpe)\n* core/translate: rewrite default column value from identifier to string literal (Preston Thorpe)\n* core/translate: Persist NOT NULL column constraint to schema table (Preston Thorpe)\n* Sqlean fuzzy string (Danawan Bimantoro)\n* Make Sorter Send and Sync (Pekka Enberg)\n* length shall not count when it sees nullc (Pavan Nambi)\n* core/storage: Wrap WalFile::syncing with AtomicBool (Pekka Enberg)\n* Make MVCC code Send and Sync (Pekka Enberg)\n* core: recover logical log on `Database::connect` (Pere Diaz Bou)\n* core/storage: Display page category for rowid integrity check failure (Pekka Enberg)\n* translate: refactor arguments and centralize parameter context (Preston Thorpe)\n* translate: disallow creating/dropping internal tables (Jussi Saurio)\n* Improve DBSP view serialization (Glauber Costa)\n* Disallow multiple primary keys in table definition (Jussi Saurio)\n* Display nothing for .schema command when table not found (Preston Thorpe)\n* Make Connection Send (Pekka Enberg)\n* core/mvcc/logical-log: refactor get log path in tests (Pere Diaz Bou)\n* Disallow ORDER BY and LIMIT in a non-compound VALUES() statement (Jussi Saurio)\n* core: Wrap Connection::mv_tx with RwLock (Pekka Enberg)\n* Autoincrement (Pavan Nambi)\n* core/mvcc/logical-log: load logical log from disk (Pere Diaz Bou)\n* Normalize target table name identifier on table or column rename (Iaroslav Zeigerman)\n* use a different seed for `gen_rng` (Pedro Muniz)\n* core: Wrap Connection::attached_databases with RwLock (Pekka Enberg)\n* core/mvcc/logical-log: on disk format for logical log (Pere Diaz Bou)\n* Wrap more Connection fields with atomics (Pekka Enberg)\n* core/mvcc: Wrap Transaction::database_header with RwLock (Pekka Enberg)\n* core: Wrap Connection::capture_data_changes in RwLock (Pekka Enberg)\n* antithesis-tests: Rename \"utils.py\" to \"helper_utils.py\" (Pekka Enberg)\n* Make some Connection fields atomic (Pekka Enberg)\n* Simulator Runtime generation (Pedro Muniz)\n* Use SQL over HTTP batch statements for sync push (Nikita Sivukhin)\n* JavaScript bindings browser tests (Nikita Sivukhin)\n* core: Wrap Connection::transaction_state with RwLock (Pekka Enberg)\n* core: Wrap Connection::autocommit in AtomicBool (Pekka Enberg)\n* use wasm-runtime from NPM instead of patched sources (Nikita Sivukhin)\n* core: Wrap Connection::database_schemas in RwLock (Pekka Enberg)\n* core: Wrap Connection::schema in RwLock (Pekka Enberg)\n* Stop incrementing n_changes for idx delete (Preston Thorpe)\n* core: Wrap Connection::pager in RwLock (Pekka Enberg)\n* Disable extension loading at runtime (Preston Thorpe)\n* mvcc: simplify StateMachine (Jussi Saurio)\n* core: Wrap Pager::io_ctx in RwLock (Pekka Enberg)\n* Enable checksum tests if checksum feature is on (Kacper Kołodziej)\n* Wrap Pager vacuum state in RwLock (Pekka Enberg)\n* Enhancement to Sim Snapshot isolation code (Pedro Muniz)\n* core/io: Ensure callbacks are invoked once (Pedro Muniz)\n* Upgrade dist to 0.30.0 (Pekka Enberg)\n* Sync improvements (Nikita Sivukhin)\n* Remove some unnecessary unsafe impls (Pedro Muniz)\n* Pragma busy timeout (Nikita Sivukhin)\n* translate/optimize: centralize AST/expr traversal (Preston Thorpe)\n* prevent alter table with materialized views (Glauber Costa)\n* core/mvcc: Wrap LogicalLog in RwLock (Pekka Enberg)\n* mvcc: remove unused code related to is_logical_log() (Jussi Saurio)\n* Put the unused variable behind a flag as intended (Avinash Sajjanshetty)\n* whopper: Gracefully handle file size limits in simulator (Avinash Sajjanshetty)\n* core/storage: Wrap Pager::header_ref_state in RwLock (Pekka Enberg)\n* core/mvcc: Kill noop storage (Pekka Enberg)\n* core/mvcc: LogicalLog simple append serializer (Pere Diaz Bou)\n* core/storage: Wrap Pager::free_page_state with RwLock (Pekka Enberg)\n* core: Rename Connection::_db to db (Pekka Enberg)\n* core/storage: Switch Pager::max_page_count to AtomicU32 (Pekka Enberg)\n* core/storage: Use AtomicU16 for Pager::reserved_space (Pekka Enberg)\n* remove io.blocks from btree balancing code (Nikita Sivukhin)\n* core: Use sequential consistency for atomics by default (Pekka Enberg)\n* core/storage: Use AtomicU32 for Pager::page_size (Pekka Enberg)\n* Convert more Pager fields towards being Send (Pekka Enberg)\n* More async (Nikita Sivukhin)\n* Enable encryption option in Whopper (Avinash Sajjanshetty)\n* Compat: Translate the 2nd argument of group_concat / string_agg (Iaroslav Zeigerman)\n* Reduce allocations needed for `break_predicate_at_and_boundaries` (Lâm Hoàng Phúc)\n* Simulator Multiple Connections (Pedro Muniz)\n* translation: rewrite expressions and properly handle quoted identifiers in UPSERT (Preston Thorpe)\n* Remove serialization of normal write/commit path (Preston Thorpe)\n* core/vtab: Wrap InternalVirtualTable with RwLock (Pekka Enberg)\n* Clean up encryption feature flag usage (Avinash Sajjanshetty)\n* core/storage: Wrap Pager::checkpoint_state in RwLock (Pekka Enberg)\n* core: Wrap Pager dirty_pages in RwLock (Pekka Enberg)\n* core: Wrap MvCursor in Arc<RwLock<>> (Pekka Enberg)\n* core/incremental: Wrap ViewTransactionState in Arc (Pekka Enberg)\n* core/function: Wrap ExtFunc in Arc (Pekka Enberg)\n* core/vtab: Mark VTabModuleImpl as Send and Sync (Pekka Enberg)\n* core/vtab: Use AtomicPtr for table_ptr (Pekka Enberg)\n* Remove LimboResult enum and InsnFunctionStepResult::Busy variant (Jussi Saurio)\n* core: Wrap symbol table with RwLock (Pekka Enberg)\n* core/ext: Switch vtab_modules from Rc to Arc (Pekka Enberg)\n* core/storage: Clean up unused import warning in encryption.rs (Pekka Enberg)\n* core: Convert Rc<Pager> to Arc<Pager> (Pekka Enberg)\n* whopper: Handle write-write conflict (Pekka Enberg)\n* mvcc: handle properly the case where starting pager read tx fails with busy (Jussi Saurio)\n* core/mvcc: Specify level for tracing (Pekka Enberg)\n* Switch to GitHub runners for performance workflows (Diego Reis)\n* Move common dependencies to workspace (Pedro Muniz)\n* avoid unnecessary cloning when formatting Txn for Display (Avinash Sajjanshetty)\n* Busy handler (Pedro Muniz)\n* test/fuzz: improve maintainability/usability of tx isolation test (Jussi Saurio)\n* mvcc: Complete commit state machine early if write set is empty (Jussi Saurio)\n* make whopper run with checksums (Avinash Sajjanshetty)\n* Whopper + MVCC (Pekka Enberg)\n* Dont grab page cache write lock in a loop (Preston Thorpe)\n* perf/throughput/turso: Async transactions with concurrent mode (Pekka Enberg)\n* Handle partial writes in unix IO for pwrite and pwritev (Preston Thorpe)\n* remove Stmt clone (Lâm Hoàng Phúc)\n* core/storage: Remove unused import warning (Pekka Enberg)\n* Handle `EXPLAIN QUERY PLAN` like SQLite (Lâm Hoàng Phúc)\n* Update epoch on each checkpoint to prevent using stale pages for backfilling (Preston Thorpe)\n* MVCC: remove reliance on BTreeCursor::has_record() (Jussi Saurio)\n* Refactor UPSERT to use wal_expr_mut to walk AST. (Preston Thorpe)\n* Commit uncommitted whopper lockfile (Jussi Saurio)\n* core/schema: Optimize get_dependent_materialized_views() when no views (Pekka Enberg)\n* core/mvcc: Eliminate RwLock wrapping Transaction (Pekka Enberg)\n* bindings/java: PreparedStatement executeUpdate (zongkx)\n* handle `EXPLAIN` like sqlite (Lâm Hoàng Phúc)\n* Document DEFERRED and IMMEDIATE transaction modes (Pekka Enberg)\n* Refactor parseschema (Jussi Saurio)\n* Remove some traces in super hot paths in btree (Preston Thorpe)\n* Sync package opfs (Nikita Sivukhin)\n* Ensure that Connection::query() checks whether its schema is up to date (Jussi Saurio)\n* refactor cli: `readline` will write to `input_buf` (Lâm Hoàng Phúc)\n* check freelist count in integrity check (Jussi Saurio)\n* Enable the use of indexes in DELETE statements (Jussi Saurio)\n* core: Rename IO::run_once() to IO::step() (Pekka Enberg)\n* simulator: Clean up code to use extract_if() (Pavan Nambi)\n\n### Fixed\n\n* fix sync-engine bug when auth token is provided as dynamic function (Nikita Sivukhin)\n* Fix COLLATE (Jussi Saurio)\n* core/translate: fix rowid affinity (Preston Thorpe)\n* Improve error handling for cyclic views (Duy Dang)\n* Fix MVCC drop table (Jussi Saurio)\n* Fix MVCC startup infinite loop when using existing DB (Jussi Saurio)\n* Fix: JOIN USING should pick columns from left table, not right (Jussi Saurio)\n* fix/vdbe: reset op_transaction state properly (Jussi Saurio)\n* Resolve appropriate column name for rowid alias/PK (Preston Thorpe)\n* fix/mvcc: deserialize table_id as i64 (Jussi Saurio)\n* Substr fix UTF-8 (Pedro Muniz)\n* fix/mvcc: set log offset to end of file after recovery finishes (Jussi Saurio)\n* Fix index bookkeeping in DROP COLUMN (Jussi Saurio)\n* Fix self-insert with nested subquery (Mikaël Francoeur)\n* Fix SQLite database file pending byte page (Pedro Muniz)\n* Index search fixes (Nikita Sivukhin)\n* Anonymous params fix (Nikita Sivukhin)\n* core/vdbe: Fix BEGIN after BEGIN CONCURRENT check (Pekka Enberg)\n* sum should identify if there is num in strings/prefix of strings (Pavan Nambi)\n* remove UnterminatedBlockComment error (Lâm Hoàng Phúc)\n* core/printf: Compatibility tests and fixes for printf() (Luiz Gustavo)\n* Fix materialized views with complex expressions (Glauber Costa)\n* Fix column fetch in joins (Glauber Costa)\n* quoting fix (Nikita Sivukhin)\n* translate/upsert: fix explicit conflict target of non-rowid primary key in UPSERT (Preston Thorpe)\n* Fix zero limit (Nikita Sivukhin)\n* Correct spelling issue in ForeignKey ast node (Preston Thorpe)\n* resolve column alias after rewritting column access in the expression in returning insert clause (Nikita Sivukhin)\n* Fix materialized views where clause issues (Glauber Costa)\n* Fix various ALTER TABLE bugs (Jussi Saurio)\n* Fix offset variable handling (Nikita Sivukhin)\n* fix encryption config in the sync-client (Nikita Sivukhin)\n* fix avg aggregation (Nikita Sivukhin)\n* Fix CREATE INDEX with quoted identifiers (Iaroslav Zeigerman)\n* Fix ungrouped aggregate with offset clause (Preston Thorpe)\n* Fix incorrect \"column is ambiguous\" error with USING clause (Jussi Saurio)\n* parser: fix incorrect LIMIT/OFFSET parsing of form LIMIT x,y (Jussi Saurio)\n* Fix .schema command for empty databases (Diego Reis)\n* Fix JavaScript bindings (Nikita Sivukhin)\n* Fix result columns binding precedence (Jussi Saurio)\n* Fix program counter update in sequence test op (Preston Thorpe)\n* Fix INSERT INTO t DEFAULT VALUES (Jussi Saurio)\n* fix: CTE alias resolution in planner (Mayank)\n* Differential testing fixes (Pedro Muniz)\n* Fix busy handler (Lâm Hoàng Phúc)\n* DBSP: Return a parse error for a non-equality join (Glauber Costa)\n* sqlite3: Fix compatibility test error by canonicalizing path (Samuel Marks)\n* bugfix: clear reserved space for a reused page (Avinash Sajjanshetty)\n* Fix MVCC concurrency bugs (Jussi Saurio)\n* Fix math functions compatibility issues (Levy A.)\n* simulator: Fix shrinking (Pedro Muniz)\n* Fix some Rust compilation warnings (Samuel Marks)\n* translate/insert: fix `program.result_columns` when inserting multiple rows (Preston Thorpe)\n* stress: Retry sync on error to avoid a panic, take 2 (Pekka Enberg)\n* translate: couple fixes from testing with Gorm (Preston Thorpe)\n* Fix is_nonnull returns true on 1 / 0 (Lâm Hoàng Phúc)\n* Fix 3 different MVCC bugs (Jussi Saurio)\n* fix re-entrancy issue in Pager::free_page (Jussi Saurio)\n* stress: Retry sync on error to avoid a panic (Pekka Enberg)\n* move `divider_cell_is_overflow_cell` to debug assertions (Pedro Muniz)\n* Fix SharedWalFile deadlock in multithreaded context (Jussi Saurio)\n* Fix MVCC update (Jussi Saurio)\n* Various fixes to sync (Nikita Sivukhin)\n* mvcc: fix hang when CONCURRENT tx tries to commit and non-CONCURRENT tx is active (Jussi Saurio)\n* mvcc: fix two sources of panic (Jussi Saurio)\n* Fix MVCC rollback (Jussi Saurio)\n* Random fixes for MVCC (Jussi Saurio)\n* core: Panic on fsync() error by default (Pekka Enberg)\n* fix(btree): advance cursor after interior node replacement in delete (Jussi Saurio)\n* core/vdbe: Fix BEGIN CONCURRENT transactions (Pekka Enberg)\n* Fix incompatible math functions (Levy A.)\n* fix wasm-runtime package.json (Nikita Sivukhin)\n* fix CI for apple builds (Nikita Sivukhin)\n* hack imports of wasm due to the issues in Vite and Next.js build systems (Nikita Sivukhin)\n* Fix tests for views (Preston Thorpe)\n* Fixes views (Glauber Costa)\n* core: Fix reprepare to properly reset statement cursors and registers (Pedro Muniz)\n* Fix automatic indexes (Jussi Saurio)\n* Fix tx isolation test semantics after #3023 (Jussi Saurio)\n* Fix: read transaction cannot be allowed to start with a stale max frame (Jussi Saurio)\n* Fix value conversion for function parameters (Levy A.)\n* IO: handle errors properly in io_uring (Preston Thorpe)\n* core: Fix integer/float comparison (Pavan Nambi)\n* pager: fix incorrect freelist page count bookkeeping (Jussi Saurio)\n\n## 0.1.5 -- 2025-09-10\n\n### Added\n\n* add missing module type for browser package (Nikita Sivukhin)\n* Implement 2-args json_each (Mikaël Francoeur)\n* Add OPFS support to JavaScript bindings (Nikita Sivukhin)\n* test/fuzz: add UPDATE/DELETE fuzz test (Jussi Saurio)\n* add gen-bigass-database.py (Jussi Saurio)\n* Add assertion: we read a page with the correct id (Jussi Saurio)\n* support float without fractional part (Lâm Hoàng Phúc)\n* expr: use more efficient implementation for binary condition exprs (Jussi Saurio)\n* Add json_each table-valued function (1-arg only) (Mikaël Francoeur)\n* Add io_uring support to stress (Pekka Enberg)\n* Refactor LIMIT/OFFSET handling to support expressions (bit-aloo)\n* Encryption: add support for other AEGIS and AES-GCM cipher variants (Frank Denis)\n* introduce package.json for separate *-browser package (both database and sync) (Nikita Sivukhin)\n* introduce `eq/contains/starts_with/ends_with_ignore_ascii_case` macros (Lâm Hoàng Phúc)\n* introduce `match_ignore_ascii_case` macro (Lâm Hoàng Phúc)\n* core: Make strict schema support experimental (Pekka Enberg)\n* core/printf: support for more basic substitution types (Luiz Gustavo)\n* Return sqlite_version() without being initialized (Preston Thorpe)\n* Support encryption for raw WAL frames (Gaurav Sarma)\n* bindings/java: Implement date, time related methods under JDBC4PreparedStatement (Kim Seon Woo)\n* Support cipher and encryption key URI options (William Souza)\n* Implement UPSERT (Preston Thorpe)\n* CLI: implement `Line` output .mode (Andrey Oskin)\n* add sqlite integrity check back (Pedro Muniz)\n* core: Initial pass on synchronous pragma (Pekka Enberg)\n* Introduce and propagate `IOContext` as required (Avinash Sajjanshetty)\n* Add some docs on encryption (Avinash Sajjanshetty)\n* sqlite3: Implement sqlite3_malloc() and sqlite3_free() (Pekka Enberg)\n* sqlite3: Implement sqlite3_next_stmt() (Pekka Enberg)\n* core/translate: Add  support (Pekka Enberg)\n* sqlite3: Implement sqlite3_db_filename() (Pekka Enberg)\n* flake.nix: add uv dependency to nativeBuildInputs (Ceferino Patino)\n* sqlite3: Implement sqlite3_bind_parameter_index() (Pekka Enberg)\n* sqlite3: Implement sqlite3_clear_bindings() (Pekka Enberg)\n* sqlite3: Implement sqlite3_get_autocommit() (Pekka Enberg)\n* Add support for AEGIS encryption algorithm (Avinash Sajjanshetty)\n* bindings/java: Implement batch operations for JDBC4Statement (Kim Seon Woo)\n* Add syntax highlighting for EXPLAIN and ANALYZE (Alex Miller)\n* Add basic support for ANALYZE statement (Alex Miller)\n* correctly implement offset() in parser (Lâm Hoàng Phúc)\n* Switch to new parser in core (Levy A.)\n* github: Remove Intel Mac support (Pekka Enberg)\n* add remove_file method to the IO (Nikita Sivukhin)\n* Add libc fault injection to Antithesis (Pekka Enberg)\n* core/mvcc:  support for MVCC (Pere Diaz Bou)\n* SQLite C API improvements: add column type and column decltype (Danawan Bimantoro)\n* Initial pass to support per page encryption (Avinash Sajjanshetty)\n\n### Updated\n* clean `print_query_result` (Lâm Hoàng Phúc)\n* update update-script to properly handle JS workspace (Nikita Sivukhin)\n* no need `QueryStatistics` if `self.opts.timer` is not set (Lâm Hoàng Phúc)\n* optimizer: convert outer join to inner join if possible (Jussi Saurio)\n* Handle case where null flag is set in op_column (Jussi Saurio)\n* remove &1 (Lâm Hoàng Phúc)\n* reduce cloning `Arc<Page>` (Lâm Hoàng Phúc)\n* Evaluate left join seek key condition again after null row (Jussi Saurio)\n* use mlugg/setup-zig instead of unmaintained action (Kingsword)\n* Prevent setting of encryption keys if already set (Gaurav Sarma)\n* Remove RefCell from Cursor (Pedro Muniz)\n* Page Cache: optimize and use sieve/Gclock hybird algorithm in place of LRU (Preston Thorpe)\n* core: handle edge cases for read_varint (Sonny)\n* Persistence for DBSP-based materialized views (Glauber Costa)\n* io_uring: prevent out of order operations that could interfere with durability (Preston Thorpe)\n* core: Simplify WalFileShared life cycle (Pekka Enberg)\n* prevent modification to system tables. (Glauber Costa)\n* mark completion as done only after callback will be executed (Nikita Sivukhin)\n* core/mvcc: make commit_txn return on I/O (Pere Diaz Bou)\n* windows iterator returns no values for shorter slice (Lâm Hoàng Phúc)\n* Unify resolution of aggregate functions (Piotr Rżysko)\n* replace some matches with `match_ignore_ascii_case` macro (Lâm Hoàng Phúc)\n* Make io_uring sound for connections on multiple threads (Preston Thorpe)\n* build native package for ARM64 (Nikita Sivukhin)\n* refactor parser fmt (Lâm Hoàng Phúc)\n* string sometimes used as identifier quoting (Lâm Hoàng Phúc)\n* CURRENT_TIMESTAMP can fallback TK_ID (Lâm Hoàng Phúc)\n* remove `turso_sqlite3_parser` from `turso_parser` (Lâm Hoàng Phúc)\n* Simulate I/O in memory (Pedro)\n* Simulate I/O in memory (Pedro Muniz)\n* Refactor encryption to manage authentication tag internally (bit-aloo)\n* Unify handling of grouped and ungrouped aggregations (Piotr Rżysko)\n* Evict page from cache if page is unlocked and unloaded (Pedro Muniz)\n* Use u64 for file offsets in I/O and calculate such offsets in u64 (Preston Thorpe)\n* Document how to use CDC (Pavan Nambi)\n* Upgrade Rust version in simulator build Dockerfile (Preston Thorpe)\n* Parse booleans to integer literals in expressions (Preston Thorpe)\n* Simulator Profiles (Pedro Muniz)\n* Change views to use DBSP circuits (Glauber Costa)\n* core/wal: cache file size (Pere Diaz Bou)\n* Remove some code duplication in the CLI (Preston Thorpe)\n* core/translate: parse_table remove unnecessary clone of table name (Pere Diaz Bou)\n* Update COMPAT.md to remove CREATE INDEX default disabled (Preston Thorpe)\n* core/translate: remove unneessary agg clones (Pere Diaz Bou)\n* core/vdbe: Micro-optimize \"zero_or_null\" opcode (Pekka Enberg)\n* translate: with_capacity insns (Pere Diaz Bou)\n* perf: avoid constructing PageType in helper methods (Jussi Saurio)\n* refactor/perf: remove BTreePageInner (Jussi Saurio)\n* Improve integrity check (Nikita Sivukhin)\n* translate/insert: Improve string format performance (Pere Diaz Bou)\n* core/schema: get_dependent_materialized_views_unnormalized (Pere Diaz Bou)\n* core/util: emit literal, cow instead of replace (Pere Diaz Bou)\n* core/translate: sanize_string fast path improvement (Pere Diaz Bou)\n* core/io: Switch Unix I/O to use libc::pwrite() (Pekka Enberg)\n* Update README.md for Go documentation (Preston Thorpe)\n* improve sync engine (Nikita Sivukhin)\n* Remove Go bindings (Preston Thorpe)\n* core/storage: Micro-optimize Pager::commit_dirty_pages() (Pekka Enberg)\n* Rename Go driver to `turso` to not conflict with sqlite3 (Preston Thorpe)\n* Refactor: `Cell` instead of `RefCell` to store `CipherMode` in connection (Avinash Sajjanshetty)\n* Improve documentation of page pinning (Jussi Saurio)\n* Remove double indirection in the Parser (Pedro Muniz\")\n* Fail CI run if Turso output differs from SQLite in TPC-H queries (Jussi Saurio)\n* Decouple SQL generation from Simulator crate (Pedro Muniz)\n* Make fill_cell_payload() safe for async IO and cache spilling (Jussi Saurio)\n* Remove Windows IO in place of Generic IO (Preston Thorpe)\n* Improve encryption API (Avinash Sajjanshetty)\n* Remove double indirection in the Parser (Pedro Muniz)\n* Update TPC-H running instructions in PERF.md (Alex Miller)\n* Truncate the WAL on last connection close (Preston Thorpe)\n* DBSP projection (Pekka Enberg)\n* Use vectored I/O for appending WAL frames (Preston Thorpe)\n* Remove unnecessary argument from Pager::end_tx() (Nikita Sivukhin)\n* refactor/btree: rewrite the find_free_cell() function (Jussi Saurio)\n* refactor/btree: rewrite the free_cell_range() function (Jussi Saurio)\n* Remove Result from signature (Mikaël Francoeur)\n* Remove duplicated attribute in (bit-aloo)\n* reduce cloning Token in parser (Lâm Hoàng Phúc)\n* refactor encryption module and make it configurable (Avinash Sajjanshetty)\n* Replace a couple refcells for types that trivially impl Copy (Preston Thorpe)\n* wal-api: allow to mix frames insert with SQL execution (Nikita Sivukhin)\n* move check code into parser (Lâm Hoàng Phúc)\n* Serialize compat tests and use Mutex::lock() instead of Mutex::try_lock() in UnixIO (Jussi Saurio)\n* sim: remove \"run_once faults\" (Jussi Saurio)\n* should not return a Completion when there is a page cache hit (Pedro Muniz)\n* github: Reduce Python build matrix (Pekka Enberg)\n* Page cache truncate (Nikita Sivukhin)\n* Wal api checkpoint seq (Nikita Sivukhin)\n* Use more structured approach in translate_insert (Jussi Saurio)\n* Remove hardcoded flag usage in DBHeader for encryption (Avinash Sajjanshetty)\n* properly execute pragmas - they may require some IO (Nikita Sivukhin)\n* Wal checkpoint upper bound (Nikita Sivukhin)\n* Improve WAL checkpointing performance (Preston Thorpe)\n* core/mvcc: store txid in conn and reset transaction state on commit (Pere Diaz Bou)\n* core/mvcc: start first rowid at 1 (Pere Diaz Bou)\n* refactor/vdbe: move insert-related seeking to VDBE from BTreeCursor (Jussi Saurio)\n\n### Fixed\n* Fix clear_page_cache method and rollback (Preston Thorpe)\n* Fix read_entire_wal_dumb: incrementally build the frame cache (Preston Thorpe)\n* Fix merge script to prompt if tests are still in progress (Preston Thorpe)\n* SQL generation fixes (Pekka Enberg)\n* Fix affinity handling in MakeRecord (Pekka Enberg)\n* Fix infinite loop when IO failure happens on allocating first page (Preston Thorpe)\n* Fix crash in Next opcode if cursor stack has no pages (Jussi Saurio)\n* cli: Fix dump compatibility in \"PRAGMA foreign_keys\" (Pekka Enberg)\n* Small fixes (Nikita Sivukhin)\n* Avoid allocating and then immediately fallbacking errors in affinity (Jussi Saurio)\n* Fix float formatting and comparison + Blob concat (Levy A.)\n* Fix infinite loop when query starts comment token (\"--\") (Lâm Hoàng Phúc)\n* Fix sqlite3 test cases (Pekka Enberg)\n* Fix non-determinism in simulator (Pedro Muniz)\n* Fix column count in ImmutableRow (Glauber Costa)\n* Fix memory leak in page cache during balancing (Preston Thorpe)\n* Fix `sim-schema` command (Pedro Muniz)\n* Propagate decryption error from the callback (Avinash Sajjanshetty)\n* Fix sorter column deduplication (Piotr Rżysko)\n* Fix missing functions after revert (Pedro Muniz)\n* ci: fix merge-pr issue to escape command-line backticks (Ceferino Patino)\n* Fix several issues with integrity_check (Jussi Saurio)\n* core/io: Fix build on Android and iOS (Pekka Enberg)\n* WAL txn: fix reads from DB file (Nikita Sivukhin)\n* Fix blob type handling in JavaScript (Pekka Enberg)\n* Fix: all indexes need to be updated if the rowid changes (Jussi Saurio)\n* Fix: in UPDATE, insert rowid into index instead of NULL (Jussi Saurio)\n* Fix: normalize table name in DELETE (Jussi Saurio)\n\n## 0.1.4 -- 2025-08-20\n\n### Added\n* bindings/rust: Add  method (Pekka Enberg)\n* Add helper to convert io::clock::Instant to useable format (Preston Thorpe)\n* bindings/javascript: Add TypeScript declarations to package (Pekka Enberg)\n* add missing closing tag (Glauber Costa)\n* add metrics and implement the .stats command (Glauber Costa)\n* Add bench-sqlite script and makefile command (Preston Thorpe)\n* Fix simulator docker build by adding new sync directory (Preston Thorpe)\n* core/mvcc: schema_did_change support and find last valid version (Pere Diaz Bou)\n* Add list databases and open database commands to the MCP server (Glauber Costa)\n* Add documentation and rename functions (Mikaël Francoeur)\n* Add io_yield macros to reduce boilerplate (Preston Thorpe)\n* Add --keep-files flag to allow for inspection of files for successful simulator runs (Preston Thorpe)\n* core/printf: support for the %i operand (Luiz Gustavo)\n* Add parser to Dockerfiles for cargo chef (Jussi Saurio)\n* Add framework for testing extensions in TCL (Piotr Rżysko)\n* Fix WAL initialization to last committed frame (Nikita Sivukhin)\n* sim: add Property::TableHasExpectedContent (Jussi Saurio)\n* Properly implement  CLI command (Preston Thorpe)\n* docs: add Claude Code MCP integration guide (Braden Wong)\n* Add assertion for expected write amount in writev callback (Preston Thorpe)\n* sim: add Property::ReadYourUpdatesBack (Jussi Saurio)\n* Add support for unlikely(X) (bit-aloo)\n* Implement normal views (Glauber Costa)\n* turso-sync: support checkpoint (Nikita Sivukhin)\n* bindings/javascript: Add async connect() function (Pekka Enberg)\n* Direct schema mutation – add RenameColumn instruction (Levy A.)\n* Implement Aggregations for DBSP views (Glauber Costa)\n* stop silently ignoring unsupported features in incremental view where clauses (Jussi Saurio)\n* turso-sync: support updates and schema changes (Nikita Sivukhin)\n* turso-cdc: add updates column for cdc table (Nikita Sivukhin)\n* docs: improve README initialization section clarity (Braden Wong)\n* Add support for Full checkpoint mode in the WAL (Preston Thorpe)\n* Add support for PRAGMA freelist_count (bit-aloo)\n* Initial pass on incremental view maintenance with DBSP (Glauber Costa)\n* SQLite C API improvements: add bind_text and bind_blob (Danawan Bimantoro)\n* perf/clickbench: enable rest of queries since we support DISTINCT and REGEXP_REPLACE (Jussi Saurio)\n* Add table name to the delete bytecode (Glauber Costa)\n* Initial pass on incremental view maintenance with DBSP (Glauber Costa)\n* docs: fix CLI command and add homebrew install instructions for MacOS (Mattia)\n* Reimplement LimboRwLock in the WAL (Preston Thorpe)\n* BufferPool: add arena backed pool to support fixed opcodes and coalescing (Preston Thorpe)\n* translate: return parse errors for unsupported features instead of silently ignoring (Jussi Saurio)\n* Add query only pragma (bit-aloo)\n* Direct schema mutation – add  instruction (Levy A.)\n* Add .clone CLI command to copy database files (Preston Thorpe)\n* PageContent: make read_x/write_x methods private and add dedicated methods (Jussi Saurio)\n* SQLite C API improvements: add basic bind and column functions (Danawan Bimantoro)\n* javascript: Implement Statement.iterate() (Pekka Enberg)\n* Fix panic on loading extension on brand new connection (Preston Thorpe)\n* implement the MaxPgCount opcode (Glauber Costa)\n* Direct schema mutation – add  instruction (Levy A.)\n* Add regexp capture (bit-aloo)\n* Add load_insn macro for compiler hint in vdbe::execute hot path (Preston Thorpe)\n* test/fuzz: add ALTER TABLE column ops to tx isolation fuzz test (Jussi Saurio)\n* core/mvcc: implement exists (Pere Diaz Bou)\n* tests/fuzz_transactions: add tests for fuzzing transactions with MVCC (Pere Diaz Bou)\n* perf/btree: implement fast algorithm for defragment_page (Jussi Saurio)\n* bindings/rust: add with_mvcc option, open with path too! (Pere Diaz Bou)\n* core/mvcc: implement seeking operations with rowid (Pere Diaz Bou)\n* bindings/rust: add with_mvcc option (Pere Diaz Bou)\n* Add bitmap to track pages in arenas (Preston Thorpe)\n* Direct schema mutation – add  instruction (Levy A.)\n* core/mvcc: fix new rowid on restart (Pere Diaz Bou)\n* Implement JavaScript bindings with minimal Rust core (Pekka Enberg)\n* test/fuzz/transactions: add \"PRAGMA wal_checkpoint\" to txn isolation fuzz test (Jussi Saurio)\n* Support the OFFSET clause for Compound select (meteorgan)\n* Introduce some state machines in preparation for IO Completions refactor (Pedro Muniz)\n* Add cli Dockerfile (Pere Diaz Bou)\n* Implement the Cast opcode (Glauber Costa)\n* Support VALUES clauses for compound select (meteorgan)\n* fix: add packages to sim/antithesis dockerfiles for cargo-chef (Jussi Saurio)\n* Add vector_concat and vector_slice support (bit-aloo)\n* bindings/rust: Add Connection::execute_batch() (Rohith Suresh)\n* bindings/java: Throw UnsupportedOperationException for unimplemented … (Pekka Enberg)\n* turso-sync package: initial commit (Nikita Sivukhin)\n\n### Updated\n* JavaScript improvements (Pekka Enberg)\n* bindings/javascript: Rename  to (Pekka Enberg)\n* Small pager cleanups (Jussi Saurio)\n* Do not begin or end transactions in nested statement (Jussi Saurio)\n* make the MCP server instructions more visible on the README (Glauber Costa)\n* FaultyQuery enabled by default (Pedro Muniz)\n* hide our age (Glauber Costa)\n* make sure our responses are compliant with MCP (Glauber Costa)\n* Move sync code to own directory (Pekka Enberg)\n* Refactor: use regular save/restore context mechanism for delete balancing (Jussi Saurio)\n* Improve handling of inserts with column names (Wallys Ferreira)\n* emit SetCookie when creating a view (Glauber Costa)\n* unify halts (Glauber Costa)\n* Update stale in memory wal header after restarting log (Preston Thorpe)\n* sync-engine: Use SQL over HTTP instead of WAL push (Nikita Sivukhin)\n* Convert SQLite parser in Rust by hand (Lâm Hoàng Phúc)\n* Ensure we fsync the db file in all paths that we checkpoint (Preston Thorpe)\n* Revive async io extension PR (Preston Thorpe)\n* sim: reduce frequency of compound selects and complex joins (Jussi Saurio)\n* Use BufferPool owned by Database instead of a static global (Jussi Saurio)\n* sync-engine: avoid unnecessary WAL push (Nikita Sivukhin)\n* use virtual root page for sqlite_schema (Mikaël Francoeur)\n* io_uring: Gracefully handle submission queue overflow (Preston Thorpe)\n* Disable unused variables in cargo clippy for CI (Pedro Muniz)\n* Refactor: atomic ordering (Preston Thorpe)\n* Document the I/O model (Pedro Muniz)\n* disable checkpoint: adjust semantic (Nikita Sivukhin)\n* SDK: enable indices everywhere (Nikita Sivukhin)\n* Manual updates (Pekka Enberg)\n* Wait for I/O completions (Pedro Muniz)\n* IO Cleanups to use  and (Pedro Muniz)\n* move our dbsp-based views to materialized views (Glauber Costa)\n* More State machines (Pedro Muniz)\n* Rename page -> slot for arenas + buffer pool (Preston Thorpe)\n* Unify JavaScript package README files (Pekka Enberg)\n* simple README with warning (Nikita Sivukhin)\n* core/wal: Minor checkpointing cleanups and optimizations (Preston Thorpe)\n* perf/btree: optimize op_column (Jussi Saurio)\n* Update PERF.md with mobibench instructions (Preston Thorpe)\n* Handle single, double and unquoted strings in values clause (Mikaël Francoeur)\n* Use rusqlite 0.37 with bundled SQLite everywhere (Jussi Saurio)\n* Feat/pragma module list (Lucas Forato)\n* remove turso-sync as now we have turso-sync-engine (Nikita Sivukhin)\n* Sorter IO Completions (Pedro Muniz)\n* Simulator should delete files after a successful run (Pedro Muniz)\n* turso-sync: js package (Nikita Sivukhin)\n* global allocator should not be set for library, only for executables (Pedro Muniz)\n* Evaluate WHERE conditions after LEFT JOIN (Piotr Rżysko)\n* Btree cache usable space (Jussi Saurio)\n* Rename JavaScript package to (Pekka Enberg)\n* perf: a few small insert optimizations (Jussi Saurio)\n* javascript: Organize test cases better (Pekka Enberg)\n* only allow multiples of 64 for performance in arena bitmap (Preston Thorpe)\n* btree: Use correct byte offsets for page 1 in defragmentation (Jussi Saurio)\n* bench/insert: use PRAGMA synchronous=full (Jussi Saurio)\n* JavaScript improvements (Pekka Enberg)\n* turso-sync: rewrite (Nikita Sivukhin)\n* bench/insert: use locking_mode EXCLUSIVE and journal_mode=WAL for sqlite (Jussi Saurio)\n* IO More State Machine (Pedro Muniz)\n* refactor/btree: cleanup write/delete/balancing states (Jussi Saurio)\n* cdc: emit entries for schema changes (Nikita Sivukhin)\n* Coll seq (Glauber Costa)\n* Remove RefCell from Buffer in IO trait methods and PageContents (Preston Thorpe)\n* Remove Clone impl for Buffer and PageContent (Preston Thorpe)\n* More state machine + Return IO in places where completions are created (Pedro Muniz)\n* cleanup: remove unused page uptodate flag (Jussi Saurio)\n* Relax I/O configuration attribute to cover all Unixes (Pedro Muniz)\n* Update defragment page to defragment in-place (João Severo)\n* Integrate virtual tables with optimizer (Piotr Rżysko)\n* Reprepare Statements when Schema changes (Pedro Muniz)\n* More State Machines in preparation for tracking IO Completions (Pedro Muniz)\n* coalesce any adjacent buffers from writev calls into fewer iovecs (Preston Thorpe)\n* extend raw WAL API with few more methods (Nikita Sivukhin)\n* Use pwrite for single buffer pwritev call in unix IO (Preston Thorpe)\n* hide dangerous methods behind conn_raw_api feature (Nikita Sivukhin)\n* preserve files in IO memory backend (Nikita Sivukhin)\n* Improve SQLite3 TCL test suite (Pekka Enberg)\n* Make completions idempotent (Preston Thorpe)\n* perf/btree: skip seek in move_to_rightmost() if we are already on rightmost page (Jussi Saurio)\n* perf/pager: dont clear page cache on commit (Jussi Saurio)\n* Rename liblimbo_sqlite3 to libturso_sqlite3 (Pekka Enberg)\n* core: Fold HeaderRef to pager module (Pekka Enberg)\n* perf/vdbe: remove eager cloning in op_comparison (Jussi Saurio)\n* Update cargo-dist to the latest official version (Hiroaki Yutani)\n* bindings/rust: Enhance API by removing verbosity (Diego Reis)\n* use state machine for NoConflict opcode (Mikaël Francoeur)\n* state_machine: remove State associated type (Pere Diaz Bou)\n* Single quotes inside a string literal have to be doubled in (Diego Reis)\n* core/mvcc: Move commit_txn() to generic state machinery (Pere Diaz Bou)\n* bindings/javascript: Reduce VM/native crossing overhead (Pekka Enberg)\n* Enable indexes by default (Jussi Saurio)\n* perf/btree: improve performance of rowid() function (Jussi Saurio)\n* core/mvcc: Persist changes through pager on commit (Pere Diaz Bou)\n* Open a temporary on-disk file for ephemeral tables (Jussi Saurio)\n* Force Sqlite to parse schema on connection benchmark (Levy A.)\n* more compat police (Glauber Costa)\n* Bury limbo-wasm (Diego Reis)\n* IN queries (Glauber Costa)\n* IN queries (Glauber Costa)\n* vdbe: Disallow checkpointing in transaction (Jussi Saurio)\n* Serverless JavaScript driver improvements (Pekka Enberg)\n* Direct `DatabaseHeader` reads and writes – `with_header` and `with_header_mut` (Levy A.)\n* refactor/btree: simplify get_next_record()/get_prev_record() (Jussi Saurio)\n* Accumulate/batch vectored writes when backfilling during checkpoint (Preston Thorpe)\n* remove non-existent opcode (Glauber Costa)\n* skip invalid inputs in cosine distance prop test (bit-aloo)\n* core/mvcc: Switch to parking_lot RwLock (Pekka Enberg)\n* Rewrite the WAL (Preston Thorpe)\n* turso-sync: bidirectional sync for local db (Nikita Sivukhin)\n* Change more function signatures to return Completions (Pedro Muniz)\n* Clean up conversion between InsnFunctionStepResult and StepResult (Diego Reis)\n* core/mvcc: simplify mvcc cursor types (Pere Diaz Bou)\n* bindings/javascript: Run tests serially (Diego Reis)\n* Javascript testing cleanups (Pekka Enberg)\n* Javascript API improvements (Pekka Enberg)\n* Change function signatures to return IO Completions (Pedro Muniz)\n\n### Fixed\n* Fix page locked panic (Pedro Muniz)\n* Remove assertions from Completion::complete() and Completion::error() (Jussi Saurio)\n* Completion Error (Pedro Muniz)\n* fix/sim: prevent sim from trying to create an existing table or index (Jussi Saurio)\n* Fail simulator on parse errors (Jussi Saurio)\n* fix pragma table_info for views (Glauber Costa)\n* Fix two issues in simulator (Jussi Saurio)\n* Fix distinct order by (Jussi Saurio)\n* Fix UPDATE: Do not use an index for iteration if that index is going to be updated (Jussi Saurio)\n* Fix non-4096 page sizes (Jussi Saurio)\n* fix: Handle fresh INSERTs in materialized view incremental maintenance (Glauber Costa)\n* Fix: do computations on usable_space as usize, not as u16 (Jussi Saurio)\n* Sync engine fixes (Nikita Sivukhin)\n* Fix tables renaming to existing index and rename indexed columns on (Levy A.)\n* Fix max_frame determination and comments in WAL checkpointing (Preston Thorpe)\n* turso-sync: fix schema bug (Nikita Sivukhin)\n* Fix: Rename clickbench output file limbo/turso (Henrik Ingo)\n* Reprepare fix on write statement (Pedro Muniz)\n* Fix view processing in the VDBE (Jussi Saurio)\n* Fix performance regression in prepare (Piotr Rżysko)\n* Fix JavaScript bindings packaging (Nikita Sivukhin)\n* bench/insert: fix expected return value from pragma (Jussi Saurio)\n* Fix segfault on schema update for virtual tables (Preston Thorpe)\n* core/btree: fix re-entrancy bug in insert_into_page() (Jussi Saurio)\n* Fix  performance regression (Jussi Saurio)\n* fix/wal: remove start_pages_in_frames_hack to prevent checkpoint data loss (Jussi Saurio)\n* fix/core/translate: ALTER TABLE DROP COLUMN: ensure schema cookie is updated even when target table is empty (Jussi Saurio)\n* io_uring: setup plumbing for Fixed opcodes (Preston Thorpe)\n* JavaScript serverless driver fixes (Pekka Enberg)\n* Fix vector deserialization alignment and blob/text empty mismatch (bit-aloo)\n* fix/wal: reset ongoing checkpoint state when checkpoint fails (Jussi Saurio)\n* Fix parser error for repetition in row values (Diego Reis)\n* fix/wal: only rollback WAL if txn was write + fix start state for WalFile (Jussi Saurio)\n* fix/bindings/rust: return errors instead of swallowing them and returning None (Jussi Saurio)\n* fix/wal: make db_changed check detect cases where max frame happens to be the same (Jussi Saurio)\n* fix/wal: reset page cache when another connection checkpointed in between (Jussi Saurio)\n* Fix merge script to prevent incorrectly marking merged contributor PRs as closed (Preston Thorpe)\n* Fix concat_ws to match sqlite behavior (bit-aloo)\n* bindings/rust: return errors instead of vibecoded numbers (Jussi Saurio)\n\n## 0.1.3 -- 2025-07-29\n\n### Added\n* bindings/rust: Add WAL API support (Nikita Sivukhin)\n* Implement the Returning statement for inserts and updates (Glauber Costa)\n* implement the pragma encoding (Glauber Costa)\n* support doubly qualified identifiers (Glauber Costa)\n* mark detach as supported (Glauber Costa)\n* Support ATTACH (read only) (Glauber Costa)\n* serverless: Add DatabasError type (Pekka Enberg)\n* btree/balance: support case where immediate parent page of unbalanced child page also overflows (Jussi Saurio)\n* serverless: Add Statement.run() method (Pekka Enberg)\n* make add dirty to change flag and also add page to the dirty list (Nikita Sivukhin)\n* types: less noisy Debug implementation for ImmutableRecord (Jussi Saurio)\n* Safe `AtomicUsize` wrapper for `db_state`: add `DbState` and `AtomicDbState` (Levy A.)\n* Add Github workflow for Turso serverless package (Pekka Enberg)\n* Add Rickrolling Turso blog post to contrib (Avinash Sajjanshetty)\n* Add `@tursodatabase/serverless` package (Pekka Enberg)\n* Implement pragma database_list (Glauber Costa)\n* fix/test: fix and unignore incorrectly implemented test (Jussi Saurio)\n* implement Debug for Database (Glauber Costa)\n* implement pragma application_id (Glauber Costa)\n* implement write side of pragma schema_version (Glauber Costa)\n* Simplify blocking operations – add `io.block(fn)` for IO trait implementors (Levy A.)\n* bindings/rust: Initial support for transactions API (Diego Reis)\n* claude sonnet forgot to run clippy when implementing mcp server (Jussi Saurio)\n* bindings/js: support iterator, and more kinds of params (Mikaël Francoeur)\n* Add a native MCP server (Glauber Costa)\n* sim: add order by to some queries (Jussi Saurio)\n* Core: Introduce external sorting (Iaroslav Zeigerman)\n* forgot to set the state to NewTrunk if we have more leaf pages than free entries (Pedro Muniz)\n* Implement IO latency correctly in simulator (Pedro Muniz)\n* page cache: temporarily increase default size until WAL spill is implemented (Jussi Saurio)\n* compat: add integrity_check (Pere Diaz Bou)\n* sim: provide additional context in assertion failures (Jussi Saurio)\n* btree: add some assertions related to #2106 (Jussi Saurio)\n* bench: add insert benchmark (batch sizes: 1,10,100) (Jussi Saurio)\n* Support page_size pragma setting (meteorgan)\n\n### Updated\n* bindings/javascript: Generate native npm packages at publish (Pekka Enberg)\n* Replace custom wasm bindings with napi-rs (Diego Reis)\n* core: Enforce shared database object per database file (Pekka Enberg)\n* btree/pager: Improve update performance by reusing freelist pages in allocate_page() (Jussi Saurio)\n* VDBE/op_column: use references to cursor payload instead of cloning (Jussi Saurio)\n* io/unix: wrap file with Mutex (Pere Diaz Bou)\n* core: Clone everything in schema (Pere Diaz Bou)\n* core/translate: Handle Expr::Id in `CREATE INDEX` (Kristofer)\n* Thread-safe `WindowsFile` (Levy A.)\n* io_uring: use Arc pointer for user data of entries (Preston Thorpe)\n* Stop checkpointing the entire WAL after every write when wal frame size > threshold (Preston Thorpe)\n* compat police (Glauber Costa)\n* remove upsert statement from COMPAT.md (Glauber Costa)\n* Update limbo -> turso in manual.md (stano)\n* bindings/javascript: Switch to napi v3 (Diego Reis)\n* Simplify sum() aggregation logic (bit-aloo)\n* Ignore WAL frames after bad checksum (Pere Diaz Bou)\n* parser: Distinguish quoted identifiers and unify Id into Name enum (bit-aloo)\n* btree: clear overflow pages when insert overwrites a cell (= UPDATE) (Jussi Saurio)\n* Append WAL frames one by one (Pere Diaz Bou)\n* pager: Clear stale page cache if database changed (Jussi Saurio)\n* sim/aws: ignore child process exits with code 137 (Jussi Saurio)\n* WAL insert API: force schema re-parse if necessary after WAL sync session end (Nikita Sivukhin)\n* WAL insert: mark pages as dirty (Nikita Sivukhin)\n* emit SetCookie after DropTable (Glauber Costa)\n* Bail early for read-only virtual tables (Preston Thorpe)\n* measure only the time it takes to open the actual connection (Glauber Costa)\n* Explicit rowid insert (Nikita Sivukhin)\n* Deserialize keys only once when sorting immutable records (Iaroslav Zeigerman)\n* WAL insert API (Nikita Sivukhin)\n* Pager: clear overflow cells when freeing page (Jussi Saurio)\n* make readonly a property of the database (Glauber Costa)\n* use default hasher for the sake of determinism (Nikita Sivukhin)\n* Load static extensions once and store on Database instead of once per connection (Preston Thorpe)\n* wal: write txn fail in case max_frame change midway (Pere Diaz Bou)\n* Improve Antithesis tests (Pekka Enberg)\n* core/lib: init_pager lock shared wal until filled (Pere Diaz Bou)\n* test/stress&sim: enable indexes by default (Jussi Saurio)\n* Usable space unwrap (Pedro Muniz)\n* BTreeTable::to_sql: wrap special column names in brackets (Nils Koch)\n* Avoid redundant decoding of record headers when reading sorted chunk files (Iaroslav Zeigerman)\n* compat: change page_size pragma and RowData opcode to yes (meteorgan)\n* Explain the Turso challenge (Glauber Costa)\n* gh workflow for dart (test, precompile, publish), only test is activated (Andika Tanuwijaya)\n* bindings/rust: Return number of rows changed from Connection::execute() (Rohith Suresh)\n* bindings/java: Make TursoDB and TursoDB factory thread-safe (Mikaël Francoeur)\n* use `wasm32-wasip1` target instead of `wasm32-wasi` (Nils Koch)\n* improve handling of double quotes (Glauber Costa)\n* Remove `find_cell`, allow overwrite of index interior cell (Jussi Saurio)\n* sim: change --disable-create-index flag to --enable-create-index (default false) (Jussi Saurio)\n* Reanimate MVCC (Pekka Enberg)\n* Replace verbose IO Completion methods with helpers (Preston Thorpe)\n* Use pread and pwrite in run_once (Ihor Andrianov)\n* `make_from_btree` should wait for IO to complete (Pedro Muniz)\n* chore: update rust to version 1.88.0 (Nils Koch)\n* refactor/btree&vdbe: fold index key info (sort order, collations) into a single struct (Jussi Saurio)\n* core: Copy-on-write for in-memory schema (Levy A.)\n* simulator: Disable `INSERT INTO .. SELECT` for being slow (Pekka Enberg)\n* Async IO: registration of file descriptors (Preston Thorpe)\n* Clean up AST unparsing, remove `ToSqlString` (Levy A.)\n* rename operation_xxx to change_xxx to make naming more consistent (Nikita Sivukhin)\n* Separate user-callable cacheflush from internal cacheflush logic (Diego Reis)\n* make unixepoch to return i64 (Nikita Sivukhin)\n* sim: ignore fsync faults (Jussi Saurio)\n* Updates to the simulator (Alperen Keleş)\n* small refactor: rename \"amount\" to \"extra_amount\" (Nikita Sivukhin)\n* refactor: Changes CursorResult to IOResult (Diego Reis)\n* btree: unify table&index seek page boundary handling (Jussi Saurio)\n* Treat table-valued functions as tables (Piotr Rżysko)\n\n### Fixed\n* perf: fix logic error in is_simple_count() (Jussi Saurio)\n* Fix writing wal header for async IO (Preston Thorpe)\n* bindings/javascript: Fix tracing and `SqliteError` message not populated (Levy A.)\n* Fix sum() to follow the SQLite semantics (FamHaggs)\n* Fix error handling when binding column references while translating the UPDATE statement (Iaroslav Zeigerman)\n* Fix schema reparse logic (Nikita Sivukhin)\n* Fix page_count pragma (meteorgan)\n* sqlite3: Improve SQLite error handling and fix C-string safety (Ceferino Patino)\n* fix: SUM returns correct float for mixed numeric/non-numeric types & return value on empty set (Axel Tobieson Rova)\n* silence clippy errors with features disabled (Glauber Costa)\n* fix raw read frame WAL API (Nikita Sivukhin)\n* Work around CREATE TABLE whitespace issue (Jussi Saurio)\n* Fix issues in alter table tests and fix a small error in BTreeTable::to_sql (Jussi Saurio)\n* Fix duplicate SET statement compatibility with SQLite (Ihor Andrianov)\n* test/clickbench: fix clickbench (Jussi Saurio)\n* fix/sim: actually enable indexes by default (Jussi Saurio)\n* fix/btree/balance: interior cell insertion can leave page overfull (Jussi Saurio)\n* Fix `error: Stable 1.88.0 is not available` in Nix flake (Levy A.)\n* Fix column order for multi-row insertion (Nikita Sivukhin)\n* doc: fix intra-repo links for contribution guide and license, and expand binding links (bit-aloo)\n* fix opcodes missing a database register (Glauber Costa)\n* sorter: fix sorter panic on SortedChunkIOState::WaitingForRead (Jussi Saurio)\n* Fix parent page stack location after interior node replacement (Jussi Saurio)\n* Fix rollback for TxErrors (Diego Reis)\n* make most instrumentation levels to be Debug or Trace instead (Pedro Muniz)\n* Property `FaultyQuery` should fail if we encounter an error that is not expected (Pedro Muniz)\n* translate/create index: fix wrong collations (Jussi Saurio)\n* bind/java: Fix Linux x86 build release (Diego Reis)\n* fix/btree: fix insert_into_cell() logic (Jussi Saurio)\n* cli: fix not being able to redirect traces to a file from inline query (Jussi Saurio)\n* bind/javascript: Fix presentation mode disabling logic (Diego Reis)\n* btree: fix post-balancing seek bug in delete path (Jussi Saurio)\n* btree: fix trying to go upwards when we are already at the end of the entire btree (Jussi Saurio)\n* btree: fix interior cell replacement in btrees with depth >=3 (Jussi Saurio)\n* extensions/vtab: fix i32 being passed as i64 across FFI boundary (Jussi Saurio)\n* Fix CSV import in the shell (Jussi Saurio)\n* fix record header size calculations and incorrect assumptions (Jussi Saurio)\n* bindings/js: fix more tests (Mikaël Francoeur)\n\n## 0.1.2 -- 2025-07-15\n\n### Added\n\n* Add `fuzz` to CI checks (Levy A.)\n* Add async header accessor functionality (Zaid Humayun)\n* core/vector: Euclidean distance support for vector search (KarinaMilet)\n* Fix: OP_NewRowId to generate semi random rowid when largest rowid is `i64::MAX` (Krishna Vishal)\n* vdbe: fix some issues with min() and max() and add ignored fuzz test (Jussi Saurio)\n* github: Update to newest Nyrkiö Github action (Henrik Ingo)\n* Add Nyrkiö to partners section in README (Henrik Ingo)\n* Support except operator for Compound select (meteorgan)\n* btree: fix incorrect comparison implementation in key_exists_in_index() (Jussi Saurio)\n* bindings/java: Implement required methods to run on JetBrains Datagrip (Kim Seon Woo)\n* add interactive transaction to property insert-values-select (Pere Diaz Bou)\n* add pere to antithesis (Pere Diaz Bou)\n* cli: Add support for `.headers` command (Pekka Enberg)\n* stress: add a way to run stress with indexes enabled (Jussi Saurio)\n* sim: add feature flags (indexes,mvcc) to CLI args (Jussi Saurio)\n* Add multi select test in JDBC4StatementTest (Kim Seon Woo)\n* bindings/dart initial implementation (Andika Tanuwijaya)\n* bindings/javascript: Implement Database.open (Lucas Forato)\n* add `libsql_disable_wal_checkpoint` (Pedro Muniz)\n* Add a threshold for large page cache values (Krishna Vishal)\n* Rollback schema support (Pere Diaz Bou)\n* add a README for the rust bindings (Glauber Costa)\n* add a basic readme for the typescript binding (Glauber Costa)\n* add a benchmark for connection time versus number of tables (Glauber Costa)\n* Add opening new connection from a sqlite compatible URI, read-only connections (Preston Thorpe)\n* Simplify `PseudoCursor` implementation (Levy A.)\n* bind/js: add tests for expand (Mikaël Francoeur)\n\n### Updated\n\n* Gopher is biologically closer to beavers than hamsters (David Shekunts)\n* build: Update cargo-dist to 0.28.6 (Pekka Enberg)\n* cli: Fail import command if table does not exists (Pekka Enberg)\n* Assert I/O read and write sizes (Pere Diaz Bou)\n* do not check rowid alias for null (Nikita Sivukhin)\n* CDC functions (Nikita Sivukhin)\n* Ignore double quotes around table names (Zaid Humayun)\n* Efficient Record Comparison and Incremental Record Parsing (Krishna Vishal)\n* `parse_schema_rows` optimizations (Levy A.)\n* Simulator - only output color on terminal (Mikaël Francoeur)\n* Btree: more balance docs (Jussi Saurio)\n* btree: Improve balance non root docs (Jussi Saurio)\n* properly set last_checksum after recovering wal (Pere Diaz Bou)\n* btree/chore: remove unnecessary parameters to .cell_get() (Jussi Saurio)\n* core/btree: Make cell field names consistent (Jussi Saurio)\n* Enforce TCL 8.6+ in compatibility tests (Mikaël Francoeur)\n* Minor refactoring of btree (meteorgan)\n* bindings/python: Start transaction implicitly in execute() (Pekka Enberg)\n* sqlite3_ondisk: generalize left-child-pointer reading function to both index/table btrees (Jussi Saurio)\n* sim: post summary to slack (Jussi Saurio)\n* stress clippy (Jussi Saurio)\n* Synchronize WAL checkpointing (Pere Diaz Bou)\n* Reachable assertions in Antithesis Python Test for better logging (Pedro Muniz\")\n* bindings/python: close connection only when reference count is one (Pere Diaz Bou)\n* parser: use YYSTACKDEPTH (Lâm Hoàng Phúc)\n* CI: remove duplicate fuzz run (Jussi Saurio)\n* Use binary search in find_cell() (Ihor Andrianov)\n* Use `str_to_f64` on float conversion (Levy A.)\n* parser: replace KEYWORDS with matching (Lâm Hoàng Phúc)\n* Reachable assertions in Antithesis Python Test for better logging (Pedro Muniz)\n* treat ImmutableRecord as Value::Blob (Nikita Sivukhin)\n* remove experimental_flag from script + remove -q flag default flag from `TestTursoShell` (Pedro Muniz)\n* Change data capture (Nikita Sivukhin)\n* Import subset of SQLite TCL tests (Pekka Enberg)\n* bindings/java: Merge JavaScript test suites (Mikaël Francoeur)\n* Import JavaScript bindings test suite from libSQL (Mikaël Francoeur)\n* bindings/java: Rename to Turso (Diego Reis)\n* Antithesis schema rollback tests (Pekka Enberg)\n* Disable adaptive colors when output_mode is list (meteorgan)\n* core/storage: Switch to turso_assert in btree.rs (Pekka Enberg)\n* core: Disable `ROLLBACK` statement (Pekka Enberg\")\n* Rust binding improvements (Pedro Muniz\")\n* `from_uri` was not passing mvcc and indexes flag to database creation for memory path (Pedro Muniz)\n* Turso, not Limbo, in pyproject.toml (Simon Willison)\n* Rename Limbo -> Turso in python tests (Preston Thorpe)\n* clarify discord situation (Glauber Costa)\n* automatically select terminal colors for pretty mode (Glauber Costa)\n* remote query_mode from ProgramBuilderOpts and from function arguments (Nikita Sivukhin)\n* limbo -> turso (Glauber Costa)\n* Rust binding improvements (Pedro Muniz)\n\n### Fixed\n\n* test/fuzz: fix rowid_seek_fuzz not being a proper fuzz test (Jussi Saurio)\n* b-tree: fix bug in case when no matching rows was found in seek in the leaf page (Nikita Sivukhin)\n* Fix clippy errors for Rust 1.88.0 (Nils Koch)\n* sim: return LimboError::Busy when busy, instead of looping forever (Jussi Saurio)\n* btree/balance/validation: fix divider cell insert validation (Jussi Saurio)\n* btree/balance/validation: fix use-after-free in rightmost ptr validation (Jussi Saurio)\n* core/translate: Fix \"misuse of aggregate function\" error message (Pekka Enberg)\n* core/translate: Return error if SELECT needs tables and there are none (Mikaël Francoeur)\n* antithesis: Fix transaction management (Pekka Enberg)\n* core: Fix resolve_function() error messages (Pekka Enberg)\n* VDBE: fix op_insert re-entrancy (Jussi Saurio)\n* VDBE: fix op_idx_insert re-entrancy (Jussi Saurio)\n* bindings/javascript: Improve error handling compatibility with `better-sqlite3` (Mikaël Francoeur)\n* uv run ruff format && uv run ruff check --fix (Jussi Saurio)\n* vdbe: fix compilation (Pere Diaz Bou)\n* core/translate: Fix aggregate star error handling in prepare_one_sele… (Pekka Enberg)\n* Fix infinite loops, rollback problems, and other bugs found by I/O fault injection (Pedro Muniz)\n* core/translate: Unify no such table error messages (Pekka Enberg)\n* Fix `ScalarFunc::Glob` to handle NULL and other value types (Krishna Vishal)\n* fix: buffer pool is not thread safe problem (KaguraMilet)\n* Fix index update when INTEGER PRIMARY KEY (rowid alias) (Adrian-Ryan Acala)\n* Fix Python test import naming (Pedro Muniz)\n* Fix boxed memory leaks (Ihor Andrianov)\n* bindings/javascript: Formatting and typos (Mikaël Francoeur)\n\n## 0.1.1 -- 2025-06-30\n\n### Fixed\n\n* JavaScript packaging (Pekka Enberg)\n\n### Updated\n\n* simulator: FsyncNoWait + Faulty Query (Pedro Muniz)\n\n## 0.1.0 -- 2025-06-30\n\n### Added\n\n* bindings/rust: Add feature flag to enable indexes (Pekka Enberg)\n* core: Add Antithesis-aware `turso_assert` (Pekka Enberg)\n* Fix database header contents on initialization (Pere Diaz Bou)\n* Support insersect operator for compound select (meteorgan)\n* Simulator: add latency to File IO (Pedro Muniz)\n* write page1 on database initialization (Pere Diaz Bou)\n* `Rollback` simple support (Pere Diaz Bou)\n* core/db&pager: fix locking for initializing empty database (Jussi Saurio)\n* sim: add Fault::ReopenDatabase (Jussi Saurio)\n* Fix database header initialization (Diego Reis)\n* Add Pedro to email recipients for antithesis (Pedro Muniz)\n* bindings/rust: Implement Debug for Connection (Charlie)\n* Fix: add uv sync to all packages for pytest github action (Pedro Muniz)\n* Implement RowData opcode (meteorgan)\n* Support indent for Goto opcode when executing explain (meteorgan)\n\n### Updated\n\n* core: Disable `ROLLBACK` statement (Pekka Enberg)\n* WAL record db_size frame on commit last frame (Pere Diaz Bou)\n* Eliminate core extension dependencies (Pekka Enberg)\n* Move completion extension dependency to CLI (Pekka Enberg)\n* Rename `limbo` crate to `turso` (Pekka Enberg)\n* Rename `limbo_sqlite3_parser` crate to `turso_sqlite3_parser` (Pekka Enberg)\n* Rename `limbo_ext` crate to `turso_ext` (Pekka Enberg)\n* Rename `limbo_macros` to `turso_macros` (Pekka Enberg)\n* stress: Log reopen and reconnect (Pekka Enberg)\n* Rename `limbo_core` crate to `turso_core` (Pekka Enberg)\n* github: Run simulator on pull requests (Pekka Enberg)\n* bindings/rust: Named params (Andika Tanuwijaya)\n* Rename Limbo to Turso in the README and other files (Glauber Costa)\n* Remove dependency on test extension pkg (Preston Thorpe)\n* Cache `reserved_space` and `page_size` values at Pager init to prevent doing redundant IO (Krishna Vishal)\n* cli: Rename CLI to Turso (Pekka Enberg)\n* bindings/javascript: Rename package to `@tursodatabase/turso` (Pekka Enberg)\n* bindings/python: Rename package to `pyturso` (Pekka Enberg)\n* Rename Limbo to Turso Database (Pekka Enberg)\n* Bring back TPC-H benchmarks (Pekka Enberg)\n* Switch to runtime flag for enabling indexes (Pekka Enberg)\n* stress: reopen db / reconnect to db every now and then (Jussi Saurio)\n* Bring back some merge conflicts code (Pedro Muniz)\n* simulator: integrity check per query (Pedro Muniz)\n* stress: Improve progress reporting (Pekka Enberg)\n* Improve extension compatibility testing (Piotr Rżysko)\n* Ephemeral Table in Update (Pedro Muniz)\n* Use UV more in python related scripts and actions (Pedro Muniz)\n* Copy instrumented image and symbols in Dockerfile.antithesis (eric-dinh-antithesis)\n* ` op_transaction` `end_read_tx` in case of `begin_write_tx` is busy (Pere Diaz Bou)\n* antithesis-tests: Make test drivers robust when database is locked (Pekka Enberg)\n\n### Fixed\n\n* tests/integration: Fix write path test on Windows (Pekka Enberg)\n* Fix deleting previous rowid when rowid is in the Set Clause (Pedro Muniz)\n* Fix executing multiple statements (Pere Diaz Bou)\n* Fix evaluation of ISNULL/NOTNULL in OR expressions (Piotr Rżysko)\n* bindings/javascript: Fix StepResult:IO handling (Pekka Enberg)\n* fix: use uv run instead of uvx for Pytest (Pedro Muniz)\n* sim: when loading bug, dont panic if there are no runs (Jussi Saurio)\n* sim: fix singlequote escaping and unescaping (Jussi Saurio)\n* Fix btree balance and seek after overwritten cell overflows (Jussi Saurio)\n* chore: fix clippy warnings (Nils Koch)\n* Fix CI errors (Piotr Rżysko)\n* Fix infinite aggregation loop when sorting is not required (Piotr Rżysko)\n* Fix DELETE not emitting constant `WhereTerms` (Pedro Muniz)\n* Fix handling of non-aggregate expressions (Piotr Rżysko)\n* Fix fuzz issue #1763 by using the `log2` & `log10` functions where applicable (Luca Muscat)\n\n## 0.0.22 -- 2025-06-19\n\n### Added\n* Implement pragma wal_checkpoint(<MODE>) (Pedro Muniz)\n* Add abbreviated alias for `.quit` and `.exit` (Krishna Vishal)\n* Add manual WAL sync before checkpoint in con.close, fix async bug in checkpoint (Preston Thorpe)\n* Complete ALTER TABLE implementation (Levy A.)\n* Add affinity-based type coercion for seek and comparison operation (Krishna Vishal)\n* bindings/javascript: Add pragma() support (Anton Harniakou)\n* Add sleep between write tests to avoid database locking issues (Pedro Muniz)\n* bindings/java: Implement JDBC4DatabaseMetadata getTables (Kim Seon Woo)\n* bindings/javascript: Add source property to Statement (Anton Harniakou)\n* Support `sqlite_master` schema table name alias (Anton Harniakou)\n* js-bindings/implement .name property (Anton Harniakou)\n* bindings/java: Add support for Linux build (Diego Reis)\n* bindings/javascript: Add database property to Statement (Anton Harniakou)\n* simulator: add CREATE INDEX to interactions (Jussi Saurio)\n* Add support for pragma table-valued functions (Piotr Rżysko)\n* bindings/javascript: Add proper exec() method and raw() mode (Diego Reis)\n* Add simulator-docker-runner for running limbo-sim in a loop on AWS (Jussi Saurio)\n* simulator: add option to disable BugBase (Jussi Saurio)\n* simulator: switch to tracing, run io.run_once and add update queries (Pere Diaz Bou)\n* Fix: aggregate regs must be initialized as NULL at the start (Jussi Saurio)\n* add stress test with 1 thread 10k iterations to ci (Pere Diaz Bou)\n\n### Updated\n\n* antithesis: Build Python package from sources (Pekka Enberg)\n* overwrite sqlite3 in install_sqlite (Pere Diaz Bou)\n* stress: Run integrity check for every iteration (Pekka Enberg)\n* core: Clean up `integrity_check()` (Pekka Enberg)\n* Simple integrity check on btree (Pere Diaz Bou)\n* Make SQLite bindings thread-safe (Pekka Enberg)\n* Switch Connection to use Arc instead of Rc (Pekka Enberg)\n* Drop unused code in op_column (meteorgan)\n* NOT NULL constraint (Anton Harniakou)\n* Simulator Ast Generation + Simulator Unary Operator + Refactor to use `limbo_core::Value` in Simulator for massive code reuse (Pedro Muniz)\n* Refactor compound select (meteorgan)\n* Disable index usage in DELETE because it does not work safely (Jussi Saurio)\n* Simulator: Better Shrinking (Pedro Muniz)\n* Simulator integrity_check (Pedro Muniz)\n* Remove leftover info trace (Jussi Saurio)\n* Namespace functions that operate on `Value` (Pedro Muniz)\n* Remove plan.to_sql_string() from optimize_plan() as it panics on TODOs (Jussi Saurio)\n* Minor: use use_eq_ignore_ascii_case in some places (Anton Harniakou)\n* Remove the FromValue trait (Anton Harniakou)\n* bindings/javascript: Refactor presentation mode and enhance test suite (Diego Reis)\n* Beginnings of AUTOVACUUM (Zaid Humayun)\n* Reverse Parse Limbo `ast` and Plans (Pedro Muniz)\n* simulator: log the interaction about to be executed with INFO (Jussi Saurio)\n* stress: Use temporary file unless one explicitly specified (Jussi Saurio)\n* Write database header via normal pager route (meteorgan)\n* simulator: options to disable certain query types (Pedro Muniz)\n* Make cursor seek reentrant (Pedro Muniz)\n* Pass input string to `translate` function (Pedro Muniz)\n* Small tracing enhancement (Pedro Muniz)\n* Adjust write cursors for delete to avoid opening more than once. (Pedro Muniz)\n* Convert u64 rowid to i64 (Pere Diaz Bou)\n* Use tempfile in constraint test (Jussi Saurio)\n* Remove frame id from key (Pere Diaz Bou)\n* clear page cache on transaction failure (Pere Diaz Bou)\n\n### Fixed\n\n* Fix incorrect lossy conversion of `Value::Blob` to a utf-8 `String` (Luca Muscat)\n* Fix update queries to set `n_changes` (Kim Seon Woo)\n* bindings/rust: Fix Rows::next() I/O dispatcher handling (Pekka Enberg)\n* cli: fix panic of queries with less than 7 chars (Nils Koch)\n* Return parse error instead of corrupt error for `no such table` (Pedro Muniz)\n* simulator: disable all ansi encodings for debug print log file (Pedro Muniz)\n* Fix large inserts to unique indexes hanging (Jussi Saurio)\n* betters instrumentation for btree related operations + cleaner debug for `RefValue` (Pedro Muniz)\n* sim/aws: fix vibecoding errors in logic (Jussi Saurio)\n* Fix incorrect handling of OR clauses in HAVING (Jussi Saurio)\n* fix: Incorrect placeholder label in where clause translation (Pedro Muniz)\n* Fix rowid to_sql_string (Pedro Muniz)\n* Fix incorrect usage of indexes with non-contiguous columns (Jussi Saurio)\n* BTree traversal refactor and bugfixes (Pere Diaz Bou)\n* `LimboRwLock` write and read lock fixes (Pere Diaz Bou)\n* fix: make keyword_token safe by validating UTF-8 input (ankit)\n* Fix UPDATE straight up not working on non-unique indexes (Jussi Saurio)\n* Fix:  ensure `PRAGMA cache_size` changes persist only for current session (meteorgan)\n* sim/aws: fix sim timeout handling (Jussi Saurio)\n* Fix WAL frame checksum mismatch (Diego Reis)\n* Set maximum open simulator-created issues (Jussi Saurio)\n* Fix cursors not being opened for indexes in DELETE (Jussi Saurio)\n* Fix: allow DeferredSeek on more than one cursor per program (Jussi Saurio)\n* Fix stress test to ignore unique constraint violation (krishna sindhur)\n* Fix ProgramBuilder::cursor_ref not having unique keys (Jussi Saurio)\n* Fix `serialize()` unreachable panic (Krishna Vishal)\n* Btree: fix cursor record state not being updated in insert_into_page() (Jussi Saurio)\n\n## 0.0.21 - 2025-05-28\n\n### Added\n\n* Add Schema reference to Resolver - needed for adhoc subquery planning (Jussi Saurio)\n* Use the SetCookie opcode to implement user_version pragma (meteorgan)\n* Add libsql_wal_get_frame() API (Pekka Enberg)\n* Fix bug: op_vopen should replace cursor slot, not add new one (Jussi Saurio)\n* bind/js: Add support for bind() method and reduce boilerplate (Diego Reis)\n* Add PThorpe92 to codeowners file for extensions + go bindings (Preston Thorpe)\n* Refactor: add stable internal_id property to TableReference (Jussi Saurio)\n* refactor: introduce walk_expr() and walk_expr_mut() to reduce repetitive pattern matching (Jussi Saurio)\n* Add some comments for values statement (meteorgan)\n* fix bindings/wasm wal file creation by implementing `generate_random_number` (오웬)\n* core/pragma: Add support for update user_version (Diego Reis)\n* Support values statement and values in select (meteorgan)\n* Initial Support for Nested Translation (Pedro Muniz)\n* bindings/rust: Add pragma methods (Diego Reis)\n* Add collation column to Index struct (Jussi Saurio)\n* Add support for DISTINCT aggregate functions (Jussi Saurio)\n* bindings/javascript: Add Statement.iterate() method (Diego Reis)\n* (btree): Implement support for handling offset-based payload access with overflow support (Krishna Vishal)\n* Add labeler workflow and reorganize macros (Preston Thorpe)\n* Update Nyrkiö change detection to newest version (Henrik Ingo)\n* perf/ci: add basic tpc-h benchmark (Jussi Saurio)\n* Add `libsql_wal_frame_count()` API (Pekka Enberg)\n* Restructure optimizer to support join reordering (Jussi Saurio)\n* Add `rustfmt` to rust-toolchain.toml (Pekka Enberg)\n\n### Updated\n\n* Make WhereTerm::consumed a Cell<bool> (Jussi Saurio)\n* Use lifetimes in walk_expr() to guarantee that child expr has same lifetime as parent expr (Jussi Saurio)\n* Small VDBE insn tweaks (Jussi Saurio)\n* Reset idx delete state after successful finish (Pere Diaz Bou)\n* feature: `INSERT INTO <table> SELECT` (Pedro Muniz)\n* Small cleanups to pager/wal/vdbe - mostly naming (Jussi Saurio)\n* bindings/javascript: API enhancements (Diego Reis)\n* github: Migrate workflows to Blacksmith runners (blacksmith-sh[bot])\n* UNION (Jussi Saurio)\n* xConnect for virtual tables to query core db connection (Preston Thorpe)\n* Reconstruct WAL frame cache when WAL is opened (Jussi Saurio)\n* set non-shared cache by default (Pere Diaz Bou)\n* TPC-H with criterion and nyrkio (Pedro Muniz)\n* UNION ALL (Jussi Saurio)\n* Drop Table OpCodes Use Ephemeral Table As Scratch Table (Zaid Humayun)\n* sqlite3-parser: Remove scanner trace-logging (Pekka Enberg)\n* sqlite3: Switch to tracing logger (Pekka Enberg)\n* CSV virtual table extension (Piotr Rżysko)\n* remove detection of comments in the middle of query in cli (Pedro Muniz)\n* btree: Remove assumption that all btrees have a rowid (Jussi Saurio)\n* Output rust backtrace in python tests (Preston Thorpe)\n* Optimization: lift common subexpressions from OR terms (Jussi Saurio)\n* refactor: replace Operation::Subquery with Table::FromClauseSubquery (Jussi Saurio)\n* Feature: Collate (Pedro Muniz)\n* Update README.md (Yusheng Guo)\n* Mark WHERE terms as consumed instead of deleting them (Jussi Saurio)\n* Cli config 2 (Pedro Muniz)\n* pager: bump default page cache size from 10 to 2000 pages (Jussi Saurio)\n* long fuzz tests ci on btree changes (Pere Diaz Bou)\n* Document how to run `cargo test` on Ubuntu (Zaid Humayun)\n* test page_free_array (Pere Diaz Bou)\n* Rename OwnedValue -> Value (Pekka Enberg)\n* Improve SQLite3 C API tests (Pekka Enberg)\n* github: Disable setup-node yarn cache (Pekka Enberg)\n* Update Unique constraint for Primary Keys and Indexes (Pedro Muniz)\n\n### Fixed\n\n* Fix LIMIT handling (Jussi Saurio)\n* Fix off-by-one error in max_frame after WAL load (Jussi Saurio)\n* btree: fix infinite looping in backwards iteration of btree table (Jussi Saurio)\n* Fix labeler labeling everything as Extensions-Other (Jussi Saurio)\n* Fix bug in op_decr_jump_zero() (Jussi Saurio)\n* Page cache fixes (Pere Diaz Bou)\n* cli/fix: Apply default config for app (Diego Reis)\n* Fix labeler (Jussi Saurio)\n* Improve debug build validation speed (Pere Diaz Bou)\n* optimizer: fix order by removal logic (Jussi Saurio)\n* Fix updating single value (Pedro Muniz)\n* Autoindex fix (Pedro Muniz)\n* use temporary db in sqlite3 wal tests to fix later tests failing (Preston Thorpe)\n* fix labeler correct file name extension use .yml instead of .yaml (Mohamed A. Salah)\n* Fix autoindex of primary key marked as unique (Pere Diaz Bou)\n* Fix: unique contraint in auto index creation (Pedro Muniz)\n\n## 0.0.20 - 2025-05-14\n\n### Added\n\n* Support isnull and notnull expr (meteorgan)\n* Add drop index (Anton Harniakou)\n* bindings/wasm: add types property for typescript setting (오병진)\n* Implement transaction support in Go adapter (Jonathan Ness)\n* Initial implementation of `ALTER TABLE RENAME` (Levy A.)\n* Add time.Time and bool data types support in Go adapter (Jonathan Ness)\n* Add tests for INSERT with specified column-name list (Anton Harniakou)\n* GROUP BY: refactor logic to support cases where no sorting is needed (Jussi Saurio)\n* Add embedded library support to Go adapter (Jonathan Ness)\n* Add time.Time support to Go driver parameter binding (Jonathan Ness)\n* Show explanation for the NewRowid opcode (Anton Harniakou)\n* Add notion of join ordering to plan (Jussi Saurio)\n* Add static feature to Cargo.toml to support extensions written inside core (Pedro Muniz)\n* implement Clone for Arc<Mutex> types (Pete Hayman)\n* Add PRAGMA schema_version (Anton Harniakou)\n* Support literal-value current_time, current_date and current_timestamp (meteorgan)\n* Add state machine for op_idx_delete + DeleteState simplification (Pere Diaz Bou)\n* Add the .indexes command (Anton Harniakou)\n* Optimization: only initialize `Rustyline` if we are in a tty (Pedro Muniz)\n* Add Antithesis Tests (eric-dinh-antithesis)\n* core/types: remove duplicate serialtype implementation (Jussi Saurio)\n* bindings/rust: Add Statement.columns() support (Timo Kösters)\n* docs: add Rust to \"Getting Started\" section (Timo Kösters)\n* Support xBestIndex in vtab API (Preston Thorpe)\n* Feat: add support for descending indexes (Jussi Saurio)\n\n### Updated\n\n* github: Ensure rustmft is installed (Pekka Enberg)\n* btree: Coalesce free blocks in `page_free_array()` (Mohamed Hossam)\n* Count optimization (Pedro Muniz)\n* bindings/java: Remove disabled annotation for UPDATE and DELETE (Kim Seon Woo)\n* Refactor numeric literal (meteorgan)\n* EXPLAIN should show a comment for the Insert opcode (Anton Harniakou)\n* bindings/javascript: Improve compatibility with better-sqlite (Diego Reis)\n* bindings/go: Upgrade ebitengine/purego to allow for use with go 1.23.9 (Preston Thorpe)\n* Adjust vtab schema creation to display the underlying columns (Preston Thorpe)\n* Read only mode (Pedro Muniz)\n* Test that DROP TABLE also deletes the related indices (Anton Harniakou)\n* reset statement before executing in rust binding (Pedro Muniz)\n* Bump assorted dependencies (Preston Thorpe)\n* Eliminate a superfluous read transaction when doing PRAGMA user_version (Anton Harniakou)\n* update index on updated indexed columns (Pere Diaz Bou)\n* Save history on exit (Piotr Rżysko)\n* btree/tablebtree_move_to: micro-optimizations (Jussi Saurio)\n* refactor database open_file and open (meteorgan)\n* Give name to hard-coded page_size values (Anton Harniakou)\n* Performance: hoist entire expressions out of hot loops if they are constant (Jussi Saurio)\n* Feature: Composite Primary key constraint (Pedro Muniz)\n* types: refactor serialtype again to make it faster (Jussi Saurio)\n* replace vec with array in btree balancing (Lâm Hoàng Phúc)\n* Pragma page size reading (Anton Harniakou)\n* perf/btree: use binary search for Index seek operations (Jussi Saurio)\n* expr.is_nonnull(): return true if col.primary_key || col.notnull (Jussi Saurio)\n* Numeric Types Overhaul (Levy A.)\n* Python script to compare vfs performance (Preston Thorpe)\n* Create an automatic ephemeral index when a nested table scan would otherwise be selected (Jussi Saurio)\n* Bump julian_day_converter to 0.4.5 (meteorgan)\n* btree: avoid reading entire cell when only rowid needed (Jussi Saurio)\n* btree: use binary search in seek/move_to for table btrees (Jussi Saurio)\n* Feat: Covering indexes (Jussi Saurio)\n* allow index entry delete (Pere Diaz Bou)\n\n### Fixed\n\n* testing/py: rename debug_print() to run_debug() (Jussi Saurio)\n* Fix handling of empty strings in prepared statements (Diego Reis)\n* CREATE VIRTUAL TABLE fixes (Piotr Rżysko)\n* Bindings/Go: Fix symbols for FFI calls (Preston Thorpe)\n* Fix bound parameters on insert statements with out of order column indexes (Preston Thorpe)\n* Fix memory leak caused by unclosed virtual table cursors (Piotr Rżysko)\n* Fix panic on async io due to reading locked page (Preston Thorpe)\n* Fix bug: we cant remove order by terms from the head of the list (Jussi Saurio)\n* Fix setting default value for primary key on UPDATE (Pere Diaz Bou)\n* Fix: allow page_size=65536 (meteorgan)\n* Fix `page_count`  pragma (meteorgan)\n* Fix broken fuzz target due to old name (Levy A.)\n* Emit `IdxDelete` instruction and some fixes on seek after deletion (Pere Diaz Bou)\n* Bugfix: Explain command should display syntax errors in CLI (Anton Harniakou)\n* Fix incorrect between expression documentation (Pedro Muniz)\n* Fix bug: left join null flag not being cleared (Jussi Saurio)\n* Fix out of bounds access on `parse_numeric_str` (Levy A.)\n* Fix post balance validation (Pere Diaz Bou)\n\n## 0.0.19 - 2025-04-16\n\n### Added\n\n* Add `BeginSubrtn`, `NotFound` and `Affinity` bytecodes (Diego Reis)\n* Add Ansi Colors to tcl test runner (Pedro Muniz)\n* support modifiers for julianday() (meteorgan)\n* Implement Once and OpenAutoindex opcodes (Jussi Saurio)\n* Add support for OpenEphemeral bytecode (Diego Reis)\n* simulator: Add Bug Database(BugBase) (Alperen Keleş)\n* feat: Add timediff data and time function (Sachin Kumar Singh)\n* core/btree: Add PageContent::new() helper (Pekka Enberg)\n* Add support to load log file with stress test (Pere Diaz Bou)\n* Support UPDATE for virtual tables (Preston Thorpe)\n* Add `.timer` command to print SQL execution statistics (Pere Diaz Bou)\n* Strict table support (Ihor Andrianov)\n* Support backwards index scan and seeks + utilize indexes in removing ORDER BY (Jussi Saurio)\n* Add deterministic Clock (Avinash Sajjanshetty)\n* Support offset clause in Update queries (Preston Thorpe)\n* Support Create Index (Preston Thorpe)\n* Support insert default values syntax (Preston Thorpe)\n* Add support for default values in INSERT statements (Diego Reis)\n\n### Updated\n\n* Test: write tests for file backed db (Pedro Muniz)\n* btree: move some blocks of code to more reasonable places (Jussi Saurio)\n* Parse hex integers 2 (Anton Harniakou)\n* More index utils (Jussi Saurio)\n* Index utils (Jussi Saurio)\n* Feature: VDestroy for Dropping Virtual Tables (Pedro Muniz)\n* Feat balance shallower (Lâm Hoàng Phúc)\n* Parse hexidecimal integers (Anton Harniakou)\n* Code clean-ups (Diego Reis)\n* Return null when parameter is unbound (Levy A.)\n* Enhance robusteness of optimization for Binary expressions (Diego Reis)\n* Check that index seek key members are not null (Jussi Saurio)\n* Better diagnostics (Pedro Muniz)\n* simulator: provide high level commands on top of a single runner (Alperen Keleş)\n* build(deps-dev): bump vite from 6.0.7 to 6.2.6 in /bindings/wasm/test-limbo-pkg (dependabot[bot])\n* btree: remove IterationState (Jussi Saurio)\n* build(deps): bump pyo3 from 0.24.0 to 0.24.1 (dependabot[bot])\n* Multi column indexes + index seek refactor (Jussi Saurio)\n* Emit ANSI codes only when tracing is outputting to terminal (Preston Thorpe)\n* B-Tree code cleanups (Pekka Enberg)\n* btree index selection on rightmost pointer in `balance_non_root` (Pere Diaz Bou)\n* io/linux: make syscallio the default (io_uring is really slow) (Jussi Saurio)\n* Stress improvements (Pekka Enberg)\n* VDBE code cleanups (Pekka Enberg)\n* Memory tests to track large blob insertions (Pedro Muniz)\n* Setup tracing to allow output during test runs (Preston Thorpe)\n* allow insertion of multiple overflow cells (Pere Diaz Bou)\n* Properly handle insertion of indexed columns (Preston Thorpe)\n* VTabs: Proper handling of re-opened db files without the relevant extensions loaded (Preston Thorpe)\n* Account divider cell in size while distributing cells (Pere Diaz Bou)\n* Format infinite float as \"Inf\"/\"-Inf\" (jachewz)\n* update sqlite download version to 2025 + remove www. (Pere Diaz Bou)\n* Improve validation of btree balancing (Pere Diaz Bou)\n* Aggregation without group by produces incorrect results for scalars (Ihor Andrianov)\n* Dot command completion (Pedro Muniz)\n* Allow reading altered tables by defaulting to null in Column insn (Preston Thorpe)\n* docs(readme): update discord link (Jamie Barton)\n* More VDBE cleanups (Pekka Enberg)\n* Request load page on `insert_into_page` (Pere Diaz Bou)\n* core/vdbe: Rename execute_insn_* to op_* (Pekka Enberg)\n* Remove RWLock from Shared wal state (Pere Diaz Bou)\n* VDBE with indirect function dispatch (Pere Diaz Bou)\n\n### Fixed\n\n* Fix truncation of error output in tests (Pedro Muniz)\n* Fix Unary Negate Operation on Blobs (Pedro Muniz)\n* Fix incompatibility `AND` Operation (Pedro Muniz)\n* Fix: comment out incorrect assert in fuzz (Pedro Muniz)\n* Fix two issues with indexes (Jussi Saurio)\n* Fuzz fix some operations (Pedro Muniz)\n* simulator: updates to bug base, refactors (Alperen Keleş)\n* Fix overwrite cell with size less than cell size (Pere Diaz Bou)\n* Fix `EXPLAIN` to be case insensitive (Pedro Muniz)\n* core: Fix syscall VFS on Linux (Pekka Enberg)\n* Index insert fixes (Pere Diaz Bou)\n* Decrease page count on balancing fixes (Pere Diaz Bou)\n* Remainder fixes (jachewz)\n* Fix virtual table translation issues (Preston Thorpe)\n* Fix overflow position in write_page() (Lâm Hoàng Phúc)\n\n## 0.0.18 - 2025-04-02\n\n### Added\n\n* Jsonb support update (Ihor Andrianov)\n* Add BTree balancing after `delete` (Krishna Vishal)\n* Introduce Register struct (Pere Diaz Bou)\n* Introduce immutable record (Pere Diaz Bou)\n* Introduce libFuzzer (Levy A.)\n* WAL frame checksum support (Daniel Boll)\n* Initial JavaScript bindings with napi-rs (Pekka Enberg)\n* Initial pass at `UPDATE` support (Preston Thorpe)\n* Add `commit()` and placeholding insert to Python binding (Diego Reis)\n\n### Updated\n\n* Create plan for Update queries (Preston Thorpe)\n* Validate cells inside a page after each operation (Pere Diaz Bou)\n* Refactor Cli Repl Commands to use clap (Pedro Muniz)\n* Allow balance_root to balance with interior pages (Pere Diaz Bou)\n* Let remainder (%) accept textual arguments (Anton Harniakou)\n* JSON code cleanups (Pekka Enberg)\n* Allocation improvements with ImmutableRecord, OwnedRecord and read_record (Pere Diaz Bou)\n* JavaScript binding improvements (Pekka Enberg)\n* Kill test environment (Pekka Enberg)\n* Remove public unlock method from `SpinLock` to prevent unsafe aliasing (Krishna Vishal)\n* Handle limit zero case in query plan emitter (Preston Thorpe)\n* Reduce MVCC cursor memory consumption (Ihor Andrianov)\n* Unary `+` is a noop (Levy A.)\n* JSON cache (Ihor Andrianov)\n* Bump `rusqlite` to 0.34 (Pere Diaz Bou)\n* core: Rename FileStorage to DatabaseFile (Pekka Enberg)\n* Improve Python bindings (Diego Reis)\n* Schema translation cleanups (Pekka Enberg)\n* Various JSON improvements (Ihor Andrianov)\n* Enable pretty mode in shell by default (Pekka Enberg)\n* Improve CLI color scheme (Pekka Enberg)\n* Impl Copy on some types in the pager to prevent explicit clones (Preston Thorpe)\n* Syntax highlighting and hinting (Pedro Muniz)\n* chore: gitignore files with an extension *.db (Anton Harniakou)\n* Organize extension library and feature gate VFS (Preston Thorpe)\n* fragment bench functions (Pere Diaz Bou)\n\n### Fixed\n\n* Remove unnecessary balance code that crashes (Pere Diaz Bou)\n* Fix propagation of divider cell balancing interior page (Pere Diaz Bou)\n* Fuzz test btree fix seeking. (Pere Diaz Bou)\n* Fix IdxCmp insn comparisons (Jussi Saurio)\n* Fixes probably all floating point math issues and floating point display issues. (Ihor Andrianov)\n* Make BTreeCell/read_payload  not allocate any data + overflow fixes (Pere Diaz Bou)\n* Fix `compute_shl` negate with overflow (Krishna Vishal)\n* Fix a typo in README.md (Tshepang Mbambo)\n* Fix platform specific FFI C pointer type casts (Preston Thorpe)\n* core: Fix Destroy opcode root page handling (Pekka Enberg)\n* Fix `SELECT 0.0 = 0` returning false (lgualtieri75)\n* bindings/python: Fix flaky tests (Diego Reis)\n* Fix io_uring WAL write corruption by ensuring buffer lifetime (Daniel Boll)\n\n## 0.0.17 - 2025-03-19\n\n### Added\n\n* `BEGIN DEFERRED` support (Diego Reis)\n* Experimental MVCC integration (Pekka Enberg)\n* `DROP TABLE` support (Zaid Humayun)\n* Initial pass on Virtual FileSystem extension module (Preston Thorpe)\n* JSONB support (Ihor Andrianov)\n* Shell command completion (Pedro Muniz)\n\n### Updated\n\n### Fixed\n\n* Fixes and improvements to Rust bindings (yirt grek and 南宫茜)\n* Transaction management fixes (Pere Diaz Bou and Diego Reis)\n* JSON function fixes (Ihor Andrianov)\n\n\n## 0.0.16 - 2025-03-05\n\n### Added\n\n* Virtual table support (Preston Thorpe)\n* Improvements to Java bindings (Kim Seon Woo)\n* Improvements to Rust bindings (Pekka Enberg)\n* Add sqlean ipaddr extension (EmNudge)\n* Add \"dump\" and \"load\" to the help menu (EmNudge)\n* Initial Antithesis testing tool (Pekka Enberg)\n\n### Fixed\n\n* SQLite B-Tree balancing algorithm (Pere Diaz Bou)\n* B-Tree improves and fixes (Pere Diaz Bou and Perston Thorpe)\n* Display blobs as blob literals in `.dump` (from Mohamed Hossam)\n* Fix wrong count() result if the column specified contains a NULL (lgualtieri75)\n* Fix casting text to integer to match SQLite' (Preston Thorpe)\n* Improve `SELECT 1` performance to be on par with SQLite (Pekka Enberg)\n* Fix offset_sec normalization in extensions/time (meteorgan)\n* Handle parsing URI according to SQLite specification (Preston Thorpe)\n* Escape character is ignored in LIKE function (lgualtieri75)\n* Fix cast_text_to_number compatibility (Pedro Muniz)\n* Modify the LIKE function to work with all types (Mohamed Hossam)\n\n## 0.0.15 - 2025-02-18\n\n### Added\n\n**Core:**\n\n* Initial pass on virtual tables (Preston Thorpe)\n* Import MVCC code to the source tree -- not enabled (Pekka Enberg, Piotr Sarna, Avinash Sajjanshetty)\n* Implement `json_set` (Marcus Nilsson)\n* Initial support for WITH clauses (common table expressions) (Jussi Saurio)\n* `BEGIN IMMEDIATE` + `COMMIT` support (Pekka Enberg)\n* `BEGIN EXCLUSIVE` support (Pekka Enberg)\n* Add Printf Support (Zaid Humayun)\n* Add support for `delete` row (Krishna Vishal)\n* Implement json_quote (Pedro Muniz)\n* Add read implementation of user_version pragma with ReadCookie opcode (Jonathan Webb)\n* Json path refine (Ihor Andrianov)\n* cli: Basic dump support (Glauber Costa)\n* Support numeric column references in GROUP BY (Jussi Saurio)\n* Implement the legacy_file_format pragma (Glauber Costa)\n* Added IdxLE and IdxLT opcodes (Omolola Olamide)\n\n**Java bindings:*\n\n* Improve JDBC support with, for example, prepared statements (Kim Seon Woo)\n* Rename package name `tech.turso` (Kim Seon Woo)\n\n**Extensions:**\n\n* Sqlean Crypto extension (Diego Reis)\n* Sqlean Time extension (Pedro Muniz)\n* Add support for `regexp_replace()` (lgualtieri75)\n\n**Simulator:**\n\n* Add NoREC testing property (Alperen Keleş)\n* Add `--differential` mode against SQLite (Alperen Keleş)\n\n### Fixed\n\n**Core:**\n\n* Fix 24/48 bit width serial types parsing (Nikita Sivukhin)\n* Fix substr (Nikita Sivukhin)\n* Fix math binary (Nikita Sivukhin)\n* Fix and predicate (Nikita Sivukhin)\n* Fix IdxGt, IdxGe, IdxLt, and IdxLe instructions (Jussi Saurio)\n* Fix not evaling constant conditions when no tables in query (Jussi Saurio)\n* Fix remainder panic on zero right-hand-side (Jussi Saurio)\n* Fix invalid text columns generated by dump (Kingsley Yung)\n* Fix incorrect CAST text->numeric if valid prefix is 1 char long (Jussi Saurio)\n* Improve SQL statement prepare performance (Jussi Saurio)\n* Fix VCC write conflict handling (Jussi Saurio)\n* Fix various bugs in B-Tree handling (Nikita Sivukhin)\n* Fix case and emit (Nikita Sivukhin)\n* Fix coalesce (Nikita Sivukhin)\n* Fix cast (Nikita Sivukhin)\n* Fix string funcs (Nikita Sivukhin)\n* Fix floating point truncation in JSON #877 (lgualtieri75)\n* Fix bug with `SELECT` referring to a mixed-case alias (Jussi Saurio)\n\n## 0.0.14 - 2025-02-04\n\n### Added\n\n**Core:**\n\n* Improve changes() and total_changes() functions and add tests (Ben Li)\n* Add support for `json_object` function (Jorge Hermo)\n* Implemented json_valid function (Harin)\n* Implement Not (Vrishabh)\n* Initial support for wal_checkpoint pragma (Sonny)\n* Implement Or and And bytecodes (Diego Reis)\n* Implement strftime function (Pedro Muniz)\n* implement sqlite_source_id function (Glauber Costa)\n* json_patch() function implementation (Ihor Andrianov)\n* json_remove() function implementation (Ihor Andrianov)\n* Implement isnull / not null for filter expressions (Glauber Costa)\n* Add support for offset in select queries (Ben Li)\n* Support returning column names from prepared statement (Preston Thorpe)\n* Implement Concat opcode (Harin)\n* Table info (Glauber Costa)\n* Pragma list (Glauber Costa)\n* Implement Noop bytecode (Pedro Muniz)\n* implement is and is not where constraints (Glauber Costa)\n* Pagecount (Glauber Costa)\n* Support column aliases in GROUP BY, ORDER BY and HAVING (Jussi Saurio)\n* Implement json_pretty (Pedro Muniz)\n\n**Extensions:**\n\n* Initial pass on vector extension (Pekka Enberg)\n* Enable static linking for 'built-in' extensions (Preston Thorpe)\n\n**Go Bindings:**\n\n* Initial support for Go database/sql driver (Preston Thorpe)\n* Avoid potentially expensive operations on prepare' (Glauber Costa)\n\n**Java Bindings:**\n\n* Implement JDBC `ResultSet` (Kim Seon Woo)\n* Implement LimboConnection `close()` (Kim Seon Woo)\n* Implement close() for `LimboStatement` and `LimboResultSet` (Kim Seon Woo)\n* Implement methods in `JDBC4ResultSet` (Kim Seon Woo)\n* Load native library from Jar (Kim Seon Woo)\n* Change logger dependency (Kim Seon Woo)\n* Log driver loading error (Pekka Enberg)\n\n**Simulator:**\n\n* Implement `--load` and `--watch` flags (Alperen Keleş)\n\n**Build system and CI:**\n\n* Add Nyrkiö change point detection to 'cargo bench' workflow (Henrik Ingo)\n\n### Fixed\n\n* Fix `select X'1';` causes limbo to go in infinite loop (Krishna Vishal)\n* Fix rowid search codegen (Nikita Sivukhin)\n* Fix logical codegen (Nikita Sivukhin)\n* Fix parser panic when duplicate column names are given to `CREATE TABLE` (Krishna Vishal)\n* Fix panic when double quoted strings are used for column names. (Krishna Vishal)\n* Fix `SELECT -9223372036854775808` result differs from SQLite (Krishna Vishal)\n* Fix `SELECT ABS(-9223372036854775808)` causes limbo to panic.  (Krishna Vishal)\n* Fix memory leaks, make extension types more efficient (Preston Thorpe)\n* Fix table with single column PRIMARY KEY to not create extra btree (Krishna Vishal)\n* Fix null cmp codegen (Nikita Sivukhin)\n* Fix null expr codegen (Nikita Sivukhin)\n* Fix rowid generation (Nikita Sivukhin)\n* Fix shr instruction (Nikita Sivukhin)\n* Fix strftime function compatibility problems (Pedro Muniz)\n* Dont fsync the WAL on read queries (Jussi Saurio)\n\n## 0.0.13 - 2025-01-19\n\n### Added\n\n* Initial support for native Limbo extensions (Preston Thorpe)\n      \n* npm packaging for node and web (Elijah Morgan)\n\n* Add support for `rowid` keyword' (Kould)\n\n* Add support for shift left, shift right, is and is not operators (Vrishabh)\n\n* Add regexp extension (Vrishabh)\n      \n* Add counterexample minimization to simulator (Alperen Keleş)\n\n* Initial support for binding values to prepared statements (Levy A.)\n\n### Updated\n\n* Java binding improvements (Kim Seon Woo)\n\n* Reduce `liblimbo_sqlite3.a` size' (Pekka Enberg)\n\n### Fixed\n\n* Fix panics on invalid aggregate function arguments (Krishna Vishal)\n \n* Fix null compare operations not giving null (Vrishabh)\n\n* Run all statements from SQL argument in CLI (Vrishabh)\n\n* Fix MustBeInt opcode semantics (Vrishabh)\n\n* Fix recursive binary operation logic (Jussi Saurio)\n\n* Fix SQL comment parsing in Limbo shell (Diego Reis and Clyde)\n\n## 0.0.12 - 2025-01-14\n\n### Added\n\n**Core:**\n\n* Improve JSON function support (Kacper Madej, Peter Sooley)\n\n* Support nested parenthesized conditional expressions (Preston Thorpe)\n\n* Add support for changes() and total_changes() functions (Lemon-Peppermint)\n\n* Auto-create index in CREATE TABLE when necessary (Jussi Saurio)\n\n* Add partial support for datetime() function (Preston Thorpe)\n\n* SQL parser performance improvements (Jussi Saurio)\n\n**Shell:**\n\n* Show pretty parse errors in the shell (Samyak Sarnayak)\n\n* Add CSV import support to shell (Vrishabh)\n\n* Selectable IO backend with --io={syscall,io-uring} argument (Jorge López Tello)\n\n**Bindings:**\n\n* Initial version of Java bindings (Kim Seon Woo)\n\n* Initial version of Rust bindings (Pekka Enberg)\n\n* Add OPFS support to Wasm bindings (Elijah Morgan)\n\n* Support uncorrelated FROM clause subqueries (Jussi Saurio)\n\n* In-memory support to `sqlite3_open()` (Pekka Enberg)\n\n### Fixed\n\n* Make iterate() lazy in JavaScript bindings (Diego Reis)\n\n* Fix integer overflow output to be same as sqlite3 (Vrishabh)\n\n* Fix 8-bit serial type to encoding (Preston Thorpe)\n\n* Query plan optimizer bug fixes (Jussi Saurio)\n\n* B-Tree balancing fixes (Pere Diaz Bou)\n\n* Fix index seek wrong on `SeekOp::LT`\\`SeekOp::GT` (Kould)\n\n* Fix arithmetic operations for text values' from Vrishabh\n\n* Fix quote escape in SQL literals (Vrishabh)\n\n## 0.0.11 - 2024-12-31\n\n### Added\n\n* Add in-memory mode to Python bindings (Jean Arhancet)\n\n* Add json_array_length function (Peter Sooley)\n\n* Add support for the UUID extension (Preston Thorpe)\n\n### Changed\n\n* Enable sqpoll by default in io_uring (Preston Thorpe)\n\n* Simulator improvements (Alperen Keleş)\n\n### Fixed\n\n* Fix escaping issues with like and glob functions (Vrishabh)\n\n* Fix `sqlite_version()` out of bound panics' (Diego Reis)\n\n* Fix on-disk file format bugs (Jussi Saurio)\n\n## 0.0.10 - 2024-12-18\n\n### Added\n\n* In-memory mode (Preston Thorpe)\n\n* More CLI improvements (Preston Thorpe)\n\n* Add support for replace() function (Alperen Keleş)\n\n* Unary operator improvements (Jean Arhancet)\n\n* Add support for unex(x, y) function (Kacper Kołodziej)\n\n### Fixed\n\n* Fix primary key handling when there's rowid and PK is not alias (Jussi Saurio)\n\n## 0.0.9 - 2024-12-12\n\n### Added\n\n* Improve CLI (Preston Thorpe)\n\n* Add support for iif() function (Alex Miller)\n\n* Add support for last_insert_rowid() function (Krishna Vishal)\n\n* Add support JOIN USING and NATURAL JOIN (Jussi Saurio)\n\n* Add support for more scalar functions (Kacper Kołodziej)\n\n* Add support for `HAVING` clause (Jussi Saurio)\n\n* Add `get()` and `iterate()` to JavaScript/Wasm API (Jean Arhancet)\n\n## 0.0.8 - 2024-11-20\n\n### Added\n\n* Python package build and example usage (Pekka Enberg)\n\n## 0.0.7 - 2024-11-20\n\n### Added\n\n* Minor improvements to JavaScript API (Pekka Enberg)\n* `CAST` support (Jussi Saurio)\n\n### Fixed\n\n* Fix issues found in-btree code with the DST (Pere Diaz Bou)\n\n## 0.0.6 - 2024-11-18\n\n### Fixed\n\n- Fix database truncation caused by `limbo-wasm` opening file in wrong mode (Pere Diaz Bou)\n\n## 0.0.5 - 2024-11-18\n\n### Added\n\n- `CREATE TABLE` support (Pere Diaz Bou)\n\n- Add Add Database.prepare() and Statement.all() to Wasm bindings (Pekka Enberg)\n\n- WAL improvements (Pere Diaz Bou)\n\n- Primary key index scans and single-column secondary index scans (Jussi Saurio)\n\n- `GROUP BY` support (Jussi Saurio)\n\n- Overflow page support (Pere Diaz Bou)\n\n- Improvements to Python bindings (Jean Arhancet and Lauri Virtanen)\n\n- Improve scalar function support (Lauri Virtanen)\n\n### Fixed\n\n- Panic in codegen with `COUNT(*)` (Jussi Saurio)\n\n- Fix `LIKE` to be case insensitive (RJ Barman)\n\n## 0.0.4 - 2024-08-22\n\n- Query planner rewrite (Jussi Saurio)\n\n- Initial pass on Python bindings (Jean Arhancet)\n\n- Improve scalar function support (Kim Seon Woo and Jean Arhancet)\n\n### Added\n\n- Partial support for `json()` function (Jean Arhancet)\n\n## 0.0.3 - 2024-08-01\n\n### Added\n\n- Initial pass on the write path. Note that the write path is not transactional yet. (Pere Diaz Bou)\n\n- More scalar functions: `unicode()` (Ethan Niser)\n\n- Optimize point queries with integer keys (Jussi Saurio)\n\n### Fixed\n\n- `ORDER BY` support for nullable sorting columns and qualified identifiers (Jussi Saurio)\n\n- Fix `.schema` command crash in the CLI ([#212](https://github.com/tursodatabase/limbo/issues/212) (Jussi Saurio)\n\n## 0.0.2 - 2024-07-24\n\n### Added\n\n- Partial `LEFT JOIN` support.\n\n- Partial `ORDER BY` support.\n\n- Partial scalar function support.\n\n### Fixed\n\n- Lock database file with POSIX filesystem advisory lock when database\n  is opened to prevent concurrent processes from corrupting a file.\n  Please note that the locking scheme differs from SQLite, which uses\n  POSIX advisory locks for every transaction. We're defaulting to\n  locking on open because it's faster. (Issue #94)\n\n### Changed\n\n- Install to `~/.limbo/` instead of `CARGO_HOME`.\n\n## 0.0.1 - 2024-07-17\n\n### Added\n\n- Partial `SELECT` statement support, including `WHERE`, `LIKE`,\n  `LIMIT`, `CROSS JOIN`, and `INNER JOIN`.\n\n- Aggregate function support.\n\n- `EXPLAIN` statement support.\n\n- Partial `PRAGMA` statement support, including `cache_size`.\n\n- Asynchronous I/O support with Linux io_uring using direct I/O and\n  Darwin kqueue.\n\n- Initial pass on command line shell with following commands:\n"
  },
  {
    "path": "COMPAT.md",
    "content": "# Turso compatibility with SQLite\n\nThis document describes the compatibility of Turso with SQLite.\n\n## Table of contents\n\n- [Turso compatibility with SQLite](#turso-compatibility-with-sqlite)\n  - [Table of contents](#table-of-contents)\n  - [Overview](#overview)\n    - [Features](#features)\n    - [Limitations](#limitations)\n  - [SQLite query language](#sqlite-query-language)\n    - [Statements](#statements)\n      - [PRAGMA](#pragma)\n    - [Expressions](#expressions)\n    - [SQL functions](#sql-functions)\n      - [Scalar functions](#scalar-functions)\n      - [Mathematical functions](#mathematical-functions)\n      - [Aggregate functions](#aggregate-functions)\n      - [Date and time functions](#date-and-time-functions)\n      - [JSON functions](#json-functions)\n  - [SQLite C API](#sqlite-c-api)\n    - [Database Connection](#database-connection)\n    - [Prepared Statements](#prepared-statements)\n    - [Binding Parameters](#binding-parameters)\n    - [Result Columns](#result-columns)\n    - [Result Values](#result-values-sqlite3_value)\n    - [Error Handling](#error-handling)\n    - [Changes and Row IDs](#changes-and-row-ids)\n    - [Memory Management](#memory-management)\n    - [Callback Functions](#callback-functions)\n    - [User-Defined Functions](#user-defined-functions)\n    - [Collation Functions](#collation-functions)\n    - [Backup API](#backup-api)\n    - [BLOB I/O](#blob-io)\n    - [WAL Functions](#wal-functions)\n    - [Utility Functions](#utility-functions)\n    - [Table Metadata](#table-metadata)\n    - [Virtual Tables](#virtual-tables)\n    - [Loadable Extensions](#loadable-extensions)\n    - [Serialization](#serialization)\n    - [Miscellaneous](#miscellaneous)\n    - [Turso-specific Extensions](#turso-specific-extensions)\n  - [SQLite VDBE opcodes](#sqlite-vdbe-opcodes)\n  - [SQLite journaling modes](#sqlite-journaling-modes)\n  - [Extensions](#extensions)\n    - [UUID](#uuid)\n    - [regexp](#regexp)\n    - [Vector](#vector)\n    - [Time](#time)\n    - [Full-Text Search (FTS)](#full-text-search-fts)\n    - [CSV](#csv)\n    - [Percentile](#percentile)\n    - [Table-Valued Functions](#table-valued-functions)\n    - [Internal Virtual Tables](#internal-virtual-tables)\n\n## Overview\n\nTurso aims to be fully compatible with SQLite, with opt-in features not supported by SQLite.\n\n### Features\n\n* ✅ SQLite file format is fully supported\n* 🚧 SQLite query language [[status](#sqlite-query-language)] is partially supported\n* 🚧 SQLite C API [[status](#sqlite-c-api)] is partially supported\n\n### Limitations\n\n* ⛔️ Concurrent access from multiple processes is not supported.\n* ⛔️ Vacuum is not supported.\n\n## SQLite query language\n\n### Statements\n\n| Statement                 | Status  | Comment                                                                           |\n|---------------------------|---------|-----------------------------------------------------------------------------------|\n| ALTER TABLE               | ✅ Yes     |                                                                                   |\n| ANALYZE                   | ✅ Yes     |                                                                                   |\n| ATTACH DATABASE           | ✅ Yes     |                                                                                   |\n| BEGIN TRANSACTION         | ✅ Yes     |                                                                                   |\n| COMMIT TRANSACTION        | ✅ Yes     |                                                                                   |\n| CHECK                     | ✅ Yes     |                                                                                   |\n| CREATE INDEX              | ✅ Yes     |                                                                                   |\n| CREATE TABLE              | ✅ Yes     |                                                                                   |\n| CREATE TABLE ... STRICT   | 🚧 Partial | Strict schema mode is experimental.                                               |\n| CREATE TRIGGER            | ✅ Yes     |                                                                                   |\n| CREATE VIEW               | ✅ Yes     |                                                                                   |\n| CREATE VIRTUAL TABLE      | ✅ Yes     |                                                                                   |\n| DELETE                    | ✅ Yes     |                                                                                   |\n| DETACH DATABASE           | ✅ Yes     |                                                                                   |\n| DROP INDEX                | 🚧 Partial | Disabled by default.                                                              |\n| DROP TABLE                | ✅ Yes     |                                                                                   |\n| DROP TRIGGER              | ✅ Yes     |                                                                                   |\n| DROP VIEW                 | ✅ Yes     |                                                                                   |\n| END TRANSACTION           | ✅ Yes     |                                                                                   |\n| EXPLAIN                   | ✅ Yes     |                                                                                   |\n| INDEXED BY                | ❌ No      |                                                                                   |\n| INSERT                    | ✅ Yes     |                                                                                   |\n| INSERT ... ON CONFLICT (UPSERT) | ✅ Yes |                                                                                   |\n| ON CONFLICT clause        | ✅ Yes     |                                                                                   |\n| REINDEX                   | ❌ No      |                                                                                   |\n| RELEASE SAVEPOINT         | ✅ No      |                                                                                   |\n| REPLACE                   | ✅ Yes     |                                                                                   |\n| RETURNING clause          | ✅ Yes     |                                                                                   |\n| ROLLBACK TRANSACTION      | ✅ Yes     |                                                                                   |\n| SAVEPOINT                 | ✅ No      |                                                                                   |\n| SELECT                    | ✅ Yes     |                                                                                   |\n| SELECT ... WHERE          | ✅ Yes     |                                                                                   |\n| SELECT ... WHERE ... LIKE | ✅ Yes     |                                                                                   |\n| SELECT ... LIMIT          | ✅ Yes     |                                                                                   |\n| SELECT ... ORDER BY       | ✅ Yes     |                                                                                   |\n| SELECT ... GROUP BY       | ✅ Yes     |                                                                                   |\n| SELECT ... HAVING         | ✅ Yes     |                                                                                   |\n| SELECT ... JOIN           | ✅ Yes     |                                                                                   |\n| SELECT ... CROSS JOIN     | ❌ No     | SQLite CROSS JOIN means \"do not reorder joins\". |\n| SELECT ... INNER JOIN     | ✅ Yes     |                                                                                   |\n| SELECT ... OUTER JOIN     | 🚧 Partial | no RIGHT JOIN                                                                     |\n| SELECT ... JOIN USING     | ✅ Yes     |                                                                                   |\n| SELECT ... NATURAL JOIN   | ✅ Yes     |                                                                                   |\n| UPDATE                    | ✅ Yes     |                                                                                   |\n| VACUUM                    | ❌ No      |                                                                                   |\n| WITH clause               | 🚧 Partial | ❌ No RECURSIVE, no MATERIALIZED, only SELECT supported in CTEs                      |\n| WINDOW functions             | 🚧 Partial | only default frame definition, no window-specific functions (rank() etc)         |\n| GENERATED                 | ❌ No      |                                                                                   |\n\n#### [PRAGMA](https://www.sqlite.org/pragma.html)\n\n\n| Statement                        | Status     | Comment                                      |\n|----------------------------------|------------|----------------------------------------------|\n| PRAGMA analysis_limit            | ❌ No         |                                              |\n| PRAGMA application_id            | ✅ Yes        |                                              |\n| PRAGMA auto_vacuum               | ❌ No         |                                              |\n| PRAGMA automatic_index           | ❌ No         |                                              |\n| PRAGMA busy_timeout              | ✅ Yes         |                                              |\n| PRAGMA cache_size                | ✅ Yes        |                                              |\n| PRAGMA cache_spill               | 🚧 Partial    | Enabled/Disabled only                        |\n| PRAGMA case_sensitive_like       | Not Needed | deprecated in SQLite                         |\n| PRAGMA cell_size_check           | ❌ No         |                                              |\n| PRAGMA checkpoint_fullsync       | ❌ No         |                                              |\n| PRAGMA collation_list            | ❌ No         |                                              |\n| PRAGMA compile_options           | ❌ No         |                                              |\n| PRAGMA count_changes             | Not Needed | deprecated in SQLite                         |\n| PRAGMA data_store_directory      | Not Needed | deprecated in SQLite                         |\n| PRAGMA data_version              | ❌ No         |                                              |\n| PRAGMA database_list             | ✅ Yes        |                                              |\n| PRAGMA default_cache_size        | Not Needed | deprecated in SQLite                         |\n| PRAGMA defer_foreign_keys        | ❌ No         |                                              |\n| PRAGMA empty_result_callbacks    | Not Needed | deprecated in SQLite                         |\n| PRAGMA encoding                  | ✅ Yes        |                                              |\n| PRAGMA foreign_key_check         | ❌ No         |                                              |\n| PRAGMA foreign_key_list          | ❌ No         |                                              |\n| PRAGMA foreign_keys              | ✅ Yes         |                                              |\n| PRAGMA freelist_count            | ✅ Yes        |                                              |\n| PRAGMA full_column_names         | Not Needed | deprecated in SQLite                         |\n| PRAGMA fullsync                  | ❌ No         |                                              |\n| PRAGMA function_list             | ✅ Yes        |                                              |\n| PRAGMA hard_heap_limit           | ❌ No         |                                              |\n| PRAGMA ignore_check_constraints  | ✅ Yes        |                                              |\n| PRAGMA incremental_vacuum        | ❌ No         |                                              |\n| PRAGMA index_info                | ✅ Yes        |                                              |\n| PRAGMA index_list                | ✅ Yes        |                                              |\n| PRAGMA index_xinfo               | ✅ Yes        |                                              |\n| PRAGMA integrity_check           | ✅ Yes        |                                              |\n| PRAGMA journal_mode              | ✅ Yes        |                                              |\n| PRAGMA journal_size_limit        | ❌ No         |                                              |\n| PRAGMA legacy_alter_table        | ❌ No         |                                              |\n| PRAGMA legacy_file_format        | ✅ Yes        |                                              |\n| PRAGMA locking_mode              | ❌ No         |                                              |\n| PRAGMA max_page_count            | ✅ Yes        |                                              |\n| PRAGMA mmap_size                 | ❌ No         |                                              |\n| PRAGMA module_list               | ❌ No         |                                              |\n| PRAGMA optimize                  | ❌ No         |                                              |\n| PRAGMA page_count                | ✅ Yes        |                                              |\n| PRAGMA page_size                 | ✅ Yes        |                                              |\n| PRAGMA parser_trace              | ❌ No         |                                              |\n| PRAGMA pragma_list               | ✅ Yes        |                                              |\n| PRAGMA query_only                | ✅ Yes        |                                              |\n| PRAGMA quick_check               | ✅ Yes        |                                              |\n| PRAGMA read_uncommitted          | ❌ No         |                                              |\n| PRAGMA recursive_triggers        | ❌ No         |                                              |\n| PRAGMA reverse_unordered_selects | ❌ No         |                                              |\n| PRAGMA schema_version            | ✅ Yes        | For writes, emulate defensive mode (always noop)|\n| PRAGMA secure_delete             | ❌ No         |                                              |\n| PRAGMA short_column_names        | Not Needed | deprecated in SQLite                         |\n| PRAGMA shrink_memory             | ❌ No         |                                              |\n| PRAGMA soft_heap_limit           | ❌ No         |                                              |\n| PRAGMA stats                     | ❌ No         | Used for testing in SQLite                   |\n| PRAGMA synchronous               | 🚧 Partial    | `OFF` and `FULL` supported                   |\n| PRAGMA table_info                | ✅ Yes        |                                              |\n| PRAGMA table_list                | ✅ Yes        |                                              |\n| PRAGMA table_xinfo               | ✅ Yes        |                                              |\n| PRAGMA temp_store                | ✅ Yes        |                                              |\n| PRAGMA temp_store_directory      | Not Needed | deprecated in SQLite                         |\n| PRAGMA threads                   | ❌ No         |                                              |\n| PRAGMA trusted_schema            | ❌ No         |                                              |\n| PRAGMA user_version              | ✅ Yes        |                                              |\n| PRAGMA vdbe_addoptrace           | ❌ No         |                                              |\n| PRAGMA vdbe_debug                | ❌ No         |                                              |\n| PRAGMA vdbe_listing              | ❌ No         |                                              |\n| PRAGMA vdbe_trace                | ❌ No         |                                              |\n| PRAGMA wal_autocheckpoint        | ❌ No         |                                              |\n| PRAGMA wal_checkpoint            | 🚧 Partial    | Not Needed calling with param (pragma-value) |\n| PRAGMA writable_schema           | ❌ No         |                                              |\n\n### Expressions\n\nFeature support of [sqlite expr syntax](https://www.sqlite.org/lang_expr.html).\n\n| Syntax                    | Status  | Comment                                  |\n|---------------------------|---------|------------------------------------------|\n| literals                  | ✅ Yes     |                                          |\n| schema.table.column       | 🚧 Partial | Schemas aren't supported                 |\n| unary operator            | ✅ Yes     |                                          |\n| binary operator           | 🚧 Partial | Only `%`, `!<`, and `!>` are unsupported |\n| agg() FILTER (WHERE ...)  | ❌ No      | Is incorrectly ignored                   |\n| ... OVER (...)            | ❌ No      | Is incorrectly ignored                   |\n| (expr)                    | ✅ Yes     |                                          |\n| CAST (expr AS type)       | ✅ Yes     |                                          |\n| COLLATE                   | 🚧 Partial | Custom Collations not supported          |\n| (NOT) LIKE                | ✅ Yes     |                                          |\n| (NOT) GLOB                | ✅ Yes     |                                          |\n| (NOT) REGEXP              | ✅ Yes     |                                          |\n| (NOT) MATCH               | ❌ No      |                                          |\n| IS (NOT)                  | ✅ Yes     |                                          |\n| IS (NOT) DISTINCT FROM    | ✅ Yes     |                                          |\n| (NOT) BETWEEN ... AND ... | ✅ Yes     | Expression is rewritten in the optimizer |\n| (NOT) IN (SELECT...)       | ✅ Yes      |                                          |\n| (NOT) EXISTS (SELECT...)   | ✅ Yes      |                                          |\n| x <operator> (SELECT...))   | 🚧 Partial  | Only scalar subqueries supported, i.e. not (x,y) = (SELECT...)\n| CASE WHEN THEN ELSE END   | ✅ Yes     |                                          |\n| RAISE                     | ✅ Yes | `RAISE('msg')` and `RAISE(ABORT, 'msg')` also work outside triggers. |\n\n### SQL functions\n\n#### Scalar functions\n\n| Function                     | Status  | Comment                                              |\n|------------------------------|---------|------------------------------------------------------|\n| abs(X)                       | ✅ Yes     |                                                      |\n| changes()                    | 🚧 Partial | Still need to support update statements and triggers |\n| char(X1,X2,...,XN)           | ✅ Yes     |                                                      |\n| coalesce(X,Y,...)            | ✅ Yes     |                                                      |\n| concat(X,...)                | ✅ Yes     |                                                      |\n| concat_ws(SEP,X,...)         | ✅ Yes     |                                                      |\n| format(FORMAT,...)           | ✅ Yes     |                                                      |\n| glob(X,Y)                    | ✅ Yes     |                                                      |\n| hex(X)                       | ✅ Yes     |                                                      |\n| ifnull(X,Y)                  | ✅ Yes     |                                                      |\n| if(X,Y,Z)                    | ✅ Yes     | Alias of iif                                         |\n| iif(X,Y,Z)                   | ✅ Yes     |                                                      |\n| instr(X,Y)                   | ✅ Yes     |                                                      |\n| last_insert_rowid()          | ✅ Yes     |                                                      |\n| length(X)                    | ✅ Yes     |                                                      |\n| like(X,Y)                    | ✅ Yes     |                                                      |\n| like(X,Y,Z)                  | ✅ Yes     |                                                      |\n| likelihood(X,Y)              | ✅ Yes     |                                                      |\n| likely(X)                    | ✅ Yes     |                                                      |\n| load_extension(X)            | 🚧 Partial | Only Turso-native extensions, not SQLite .so/.dll    |\n| load_extension(X,Y)          | ❌ No      |                                                      |\n| lower(X)                     | ✅ Yes     |                                                      |\n| ltrim(X)                     | ✅ Yes     |                                                      |\n| ltrim(X,Y)                   | ✅ Yes     |                                                      |\n| max(X,Y,...)                 | ✅ Yes     |                                                      |\n| min(X,Y,...)                 | ✅ Yes     |                                                      |\n| nullif(X,Y)                  | ✅ Yes     |                                                      |\n| octet_length(X)              | ✅ Yes     |                                                      |\n| printf(FORMAT,...)           | ✅ Yes     |                                                      |\n| quote(X)                     | ✅ Yes     |                                                      |\n| random()                     | ✅ Yes     |                                                      |\n| randomblob(N)                | ✅ Yes     |                                                      |\n| replace(X,Y,Z)               | ✅ Yes     |                                                      |\n| round(X)                     | ✅ Yes     |                                                      |\n| round(X,Y)                   | ✅ Yes     |                                                      |\n| rtrim(X)                     | ✅ Yes     |                                                      |\n| rtrim(X,Y)                   | ✅ Yes     |                                                      |\n| sign(X)                      | ✅ Yes     |                                                      |\n| soundex(X)                   | ✅ Yes     |                                                      |\n| sqlite_compileoption_get(N)  | ❌ No      |                                                      |\n| sqlite_compileoption_used(X) | ❌ No      |                                                      |\n| sqlite_offset(X)             | ❌ No      |                                                      |\n| sqlite_source_id()           | ✅ Yes     |                                                      |\n| sqlite_version()             | ✅ Yes     |                                                      |\n| substr(X,Y,Z)                | ✅ Yes     |                                                      |\n| substr(X,Y)                  | ✅ Yes     |                                                      |\n| substring(X,Y,Z)             | ✅ Yes     |                                                      |\n| substring(X,Y)               | ✅ Yes     |                                                      |\n| total_changes()              | 🚧 Partial | Still need to support update statements and triggers |\n| trim(X)                      | ✅ Yes     |                                                      |\n| trim(X,Y)                    | ✅ Yes     |                                                      |\n| typeof(X)                    | ✅ Yes     |                                                      |\n| unhex(X)                     | ✅ Yes     |                                                      |\n| unhex(X,Y)                   | ✅ Yes     |                                                      |\n| unicode(X)                   | ✅ Yes     |                                                      |\n| unlikely(X)                  | ✅ Yes     |                                                      |\n| upper(X)                     | ✅ Yes     |                                                      |\n| unistr(X)                    | ❌ No      |                                                      |\n| zeroblob(N)                  | ✅ Yes     |                                                      |\n\n#### Mathematical functions\n\n| Function   | Status | Comment |\n|------------|--------|---------|\n| acos(X)    | ✅ Yes    |         |\n| acosh(X)   | ✅ Yes    |         |\n| asin(X)    | ✅ Yes    |         |\n| asinh(X)   | ✅ Yes    |         |\n| atan(X)    | ✅ Yes    |         |\n| atan2(Y,X) | ✅ Yes    |         |\n| atanh(X)   | ✅ Yes    |         |\n| ceil(X)    | ✅ Yes    |         |\n| ceiling(X) | ✅ Yes    |         |\n| cos(X)     | ✅ Yes    |         |\n| cosh(X)    | ✅ Yes    |         |\n| degrees(X) | ✅ Yes    |         |\n| exp(X)     | ✅ Yes    |         |\n| floor(X)   | ✅ Yes    |         |\n| ln(X)      | ✅ Yes    |         |\n| log(B,X)   | ✅ Yes    |         |\n| log(X)     | ✅ Yes    |         |\n| log10(X)   | ✅ Yes    |         |\n| log2(X)    | ✅ Yes    |         |\n| mod(X,Y)   | ✅ Yes    |         |\n| pi()       | ✅ Yes    |         |\n| pow(X,Y)   | ✅ Yes    |         |\n| power(X,Y) | ✅ Yes    |         |\n| radians(X) | ✅ Yes    |         |\n| sin(X)     | ✅ Yes    |         |\n| sinh(X)    | ✅ Yes    |         |\n| sqrt(X)    | ✅ Yes    |         |\n| tan(X)     | ✅ Yes    |         |\n| tanh(X)    | ✅ Yes    |         |\n| trunc(X)   | ✅ Yes    |         |\n\n#### Aggregate functions\n\n| Function                     | Status  | Comment |\n|------------------------------|---------|---------|\n| avg(X)                       | ✅ Yes     |         |\n| count(X)                     | ✅ Yes     |         |\n| count(*)                     | ✅ Yes     |         |\n| group_concat(X)              | ✅ Yes     |         |\n| group_concat(X,Y)            | ✅ Yes     |         |\n| string_agg(X,Y)              | ✅ Yes     |         |\n| max(X)                       | ✅ Yes     |         |\n| min(X)                       | ✅ Yes     |         |\n| sum(X)                       | ✅ Yes     |         |\n| total(X)                     | ✅ Yes     |         |\n| median(X)                    | ✅ Yes     | Requires percentile extension                        |\n| percentile(Y,P)              | ✅ Yes     | Requires percentile extension                        |\n| percentile_cont(Y,P)         | ✅ Yes     | Requires percentile extension                        |\n| percentile_disc(Y,P)         | ✅ Yes     | Requires percentile extension                        |\n| stddev(X)                    | ✅ Yes     | Turso extension                                      |\n\n#### Date and time functions\n\n| Function    | Status  | Comment                      |\n|-------------|---------|------------------------------|\n| date()      | ✅ Yes     |                              |\n| time()      | ✅ Yes     |                              |\n| datetime()  | ✅ Yes     |                              |\n| julianday() | ✅ Yes     |                              |\n| unixepoch() | ✅ Yes     |                              |\n| strftime()  | ✅ Yes     |                              |\n| timediff()  | ✅ Yes     |                              |\n\nModifiers:\n\n|  Modifier      | Status|  Comment                        |\n|----------------|-------|---------------------------------|\n| Days           | ✅ Yes \t |                                 |\n| Hours          | ✅ Yes\t |                                 |\n| Minutes        | ✅ Yes\t |                                 |\n| Seconds        | ✅ Yes\t |                                 |\n| Months         | ✅ Yes\t |                                 |\n| Years          | ✅ Yes\t |                                 |\n| TimeOffset     | ✅ Yes\t |                                 |\n| DateOffset\t | ✅ Yes   |                                 |\n| DateTimeOffset | ✅ Yes   |                                 |\n| Ceiling\t     | ✅ Yes   |                                 |\n| Floor          | ✅ Yes   |                                 |\n| StartOfMonth\t | ✅ Yes\t |                                 |\n| StartOfYear\t | ✅ Yes\t |                                 |\n| StartOfDay\t | ✅ Yes\t |                                 |\n| Weekday(N)\t | ✅ Yes   |                                 |\n| Auto           | ✅ Yes   |                                 |\n| UnixEpoch      | ✅ Yes   |                                 |\n| JulianDay      | ✅ Yes   |                                 |\n| Localtime      | ✅ Yes   |                                 |\n| Utc            | ✅ Yes   |                                 |\n| Subsec         | ✅ Yes   |                                 |\n\n#### JSON functions\n\n| Function                           | Status  | Comment                                                                                                                                      |\n| ---------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------- |\n| json(json)                         | ✅ Yes     |                                                                                                                                              |\n| jsonb(json)                        | ✅ Yes     |                                                                                                                                              |\n| json_array(value1,value2,...)      | ✅ Yes     |                                                                                                                                              |\n| jsonb_array(value1,value2,...)     | ✅ Yes     |                                                                                                                                              |\n| json_array_length(json)            | ✅ Yes     |                                                                                                                                              |\n| json_array_length(json,path)       | ✅ Yes     |                                                                                                                                              |\n| json_error_position(json)          | ✅ Yes     |                                                                                                                                              |\n| json_extract(json,path,...)        | ✅ Yes     |                                                                                                                                              |\n| jsonb_extract(json,path,...)       | ✅ Yes     |                                                                                                                                              |\n| json -> path                       | ✅ Yes     |                                                                                                                                              |\n| json ->> path                      | ✅ Yes     |                                                                                                                                              |\n| json_insert(json,path,value,...)   | ✅ Yes     |                                                                                                                                              |\n| jsonb_insert(json,path,value,...)  | ✅ Yes     |                                                                                                                                              |\n| json_object(label1,value1,...)     | ✅ Yes     |                                                                                                                                              |\n| jsonb_object(label1,value1,...)    | ✅ Yes     |                                                                                                                                              |\n| json_patch(json1,json2)            | ✅ Yes     |                                                                                                                                              |\n| jsonb_patch(json1,json2)           | ✅ Yes     |                                                                                                                                              |\n| json_pretty(json)                  | ✅ Yes     |                                                                                                                                              |\n| json_remove(json,path,...)         | ✅ Yes     |                                                                                                                                              |\n| jsonb_remove(json,path,...)        | ✅ Yes     |                                                                                                                                              |\n| json_replace(json,path,value,...)  | ✅ Yes     |                                                                                                                                              |\n| jsonb_replace(json,path,value,...) | ✅ Yes     |                                                                                                                                              |\n| json_set(json,path,value,...)      | ✅ Yes     |                                                                                                                                              |\n| jsonb_set(json,path,value,...)     | ✅ Yes     |                                                                                                                                              |\n| json_type(json)                    | ✅ Yes     |                                                                                                                                              |\n| json_type(json,path)               | ✅ Yes     |                                                                                                                                              |\n| json_valid(json)                   | ✅ Yes     |                                                                                                                                              |\n| json_valid(json,flags)             | ✅ Yes     |                                                                                                                                              |\n| json_quote(value)                  | ✅ Yes     |                                                                                                                                              |\n| json_group_array(value)            | ✅ Yes     |                                                                                                                                              |\n| jsonb_group_array(value)           | ✅ Yes     |                                                                                                                                              |\n| json_group_object(label,value)     | ✅ Yes     |                                                                                                                                              |\n| jsonb_group_object(name,value)     | ✅ Yes     |                                                                                                                                              |\n| json_each(json)                    | ✅ Yes     |                                                                                                                                              |\n| json_each(json,path)               | ✅ Yes     |                                                                                                                                              |\n| json_tree(json)                    | 🚧 Partial | see commented-out tests in json.test                                                                                                         |\n| json_tree(json,path)               | 🚧 Partial | see commented-out tests in json.test                                                                                                         |\n\n## SQLite C API\n\n### Database Connection\n\n| Interface              | Status  | Comment |\n|------------------------|---------|---------|\n| sqlite3_open           | ✅ Yes     |         |\n| sqlite3_open_v2        | 🚧 Partial | Delegates to sqlite3_open, flags/VFS ignored |\n| sqlite3_open16         | ❌ No      |         |\n| sqlite3_close          | ✅ Yes     |         |\n| sqlite3_close_v2       | ✅ Yes     | Same as sqlite3_close |\n| sqlite3_db_filename    | ✅ Yes     |         |\n| sqlite3_db_config      | ❌ No      | Stub    |\n| sqlite3_db_handle      | ✅ Yes     |         |\n| sqlite3_db_readonly    | ❌ No      |         |\n| sqlite3_db_status      | ❌ No      |         |\n| sqlite3_db_cacheflush  | ❌ No      |         |\n| sqlite3_db_release_memory | ❌ No   |         |\n| sqlite3_db_name        | ❌ No      |         |\n| sqlite3_db_mutex       | ❌ No      |         |\n| sqlite3_get_autocommit | ✅ Yes     |         |\n| sqlite3_limit          | ❌ No      | Stub    |\n| sqlite3_initialize     | ✅ Yes     |         |\n| sqlite3_shutdown       | ✅ Yes     |         |\n| sqlite3_config         | ❌ No      |         |\n\n### Prepared Statements\n\n| Interface                   | Status  | Comment |\n|-----------------------------|---------|---------|\n| sqlite3_prepare             | ❌ No      |         |\n| sqlite3_prepare_v2          | ✅ Yes     |         |\n| sqlite3_prepare_v3          | ✅ Yes     | Delegates to prepare_v2, prepFlags ignored |\n| sqlite3_prepare16           | ❌ No      |         |\n| sqlite3_prepare16_v2        | ❌ No      |         |\n| sqlite3_finalize            | ✅ Yes     |         |\n| sqlite3_step                | ✅ Yes     |         |\n| sqlite3_reset               | ✅ Yes     |         |\n| sqlite3_exec                | ✅ Yes     |         |\n| sqlite3_stmt_readonly       | ❌ No      | Stub    |\n| sqlite3_stmt_busy           | ❌ No      | Stub    |\n| sqlite3_stmt_status         | ❌ No      |         |\n| sqlite3_sql                 | ❌ No      |         |\n| sqlite3_expanded_sql        | ❌ No      | Stub    |\n| sqlite3_normalized_sql      | ❌ No      |         |\n| sqlite3_next_stmt           | ✅ Yes     |         |\n\n### Binding Parameters\n\n| Interface                    | Status  | Comment |\n|------------------------------|---------|---------|\n| sqlite3_bind_parameter_count | ✅ Yes     |         |\n| sqlite3_bind_parameter_name  | ✅ Yes     |         |\n| sqlite3_bind_parameter_index | ✅ Yes     |         |\n| sqlite3_bind_null            | ✅ Yes     |         |\n| sqlite3_bind_int             | ✅ Yes     |         |\n| sqlite3_bind_int64           | ✅ Yes     |         |\n| sqlite3_bind_double          | ✅ Yes     |         |\n| sqlite3_bind_text            | ✅ Yes     |         |\n| sqlite3_bind_text16          | ❌ No      |         |\n| sqlite3_bind_text64          | ❌ No      |         |\n| sqlite3_bind_blob            | ✅ Yes     |         |\n| sqlite3_bind_blob64          | ❌ No      |         |\n| sqlite3_bind_value           | ❌ No      |         |\n| sqlite3_bind_pointer         | ❌ No      |         |\n| sqlite3_bind_zeroblob        | ❌ No      |         |\n| sqlite3_bind_zeroblob64      | ❌ No      |         |\n| sqlite3_clear_bindings       | ✅ Yes     |         |\n\n### Result Columns\n\n| Interface                | Status  | Comment |\n|--------------------------|---------|---------|\n| sqlite3_column_count     | ✅ Yes     |         |\n| sqlite3_column_name      | ✅ Yes     |         |\n| sqlite3_column_name16    | ❌ No      |         |\n| sqlite3_column_decltype  | ✅ Yes     |         |\n| sqlite3_column_decltype16| ❌ No      |         |\n| sqlite3_column_type      | ✅ Yes     |         |\n| sqlite3_column_int       | ✅ Yes     |         |\n| sqlite3_column_int64     | ✅ Yes     |         |\n| sqlite3_column_double    | ✅ Yes     |         |\n| sqlite3_column_text      | ✅ Yes     |         |\n| sqlite3_column_text16    | ❌ No      |         |\n| sqlite3_column_blob      | ✅ Yes     |         |\n| sqlite3_column_bytes     | ✅ Yes     |         |\n| sqlite3_column_bytes16   | ❌ No      |         |\n| sqlite3_column_value     | ❌ No      |         |\n| sqlite3_column_table_name| ✅ Yes     |         |\n| sqlite3_column_database_name | ❌ No  |         |\n| sqlite3_column_origin_name | ❌ No    |         |\n| sqlite3_data_count       | ✅ Yes     |         |\n\n### Result Values (sqlite3_value)\n\n| Interface              | Status  | Comment |\n|------------------------|---------|---------|\n| sqlite3_value_type     | ✅ Yes     |         |\n| sqlite3_value_int      | ✅ Yes     |         |\n| sqlite3_value_int64    | ✅ Yes     |         |\n| sqlite3_value_double   | ✅ Yes     |         |\n| sqlite3_value_text     | ✅ Yes     |         |\n| sqlite3_value_text16   | ❌ No      |         |\n| sqlite3_value_blob     | ✅ Yes     |         |\n| sqlite3_value_bytes    | ✅ Yes     |         |\n| sqlite3_value_bytes16  | ❌ No      |         |\n| sqlite3_value_dup      | ❌ No      |         |\n| sqlite3_value_free     | ❌ No      |         |\n| sqlite3_value_nochange | ❌ No      |         |\n| sqlite3_value_frombind | ❌ No      |         |\n| sqlite3_value_subtype  | ❌ No      |         |\n| sqlite3_value_pointer  | ❌ No      |         |\n| sqlite3_value_encoding | ❌ No      |         |\n| sqlite3_value_numeric_type | ❌ No  |         |\n\n### Error Handling\n\n| Interface              | Status  | Comment |\n|------------------------|---------|---------|\n| sqlite3_errcode        | ✅ Yes     |         |\n| sqlite3_errmsg         | ✅ Yes     |         |\n| sqlite3_errmsg16       | ❌ No      |         |\n| sqlite3_errstr         | ✅ Yes     |         |\n| sqlite3_extended_errcode | ✅ Yes   |         |\n| sqlite3_extended_result_codes | ❌ No |        |\n| sqlite3_error_offset   | ❌ No      |         |\n| sqlite3_system_errno   | ❌ No      |         |\n\n### Changes and Row IDs\n\n| Interface              | Status  | Comment |\n|------------------------|---------|---------|\n| sqlite3_changes        | ✅ Yes     |         |\n| sqlite3_changes64      | ✅ Yes     |         |\n| sqlite3_total_changes  | ✅ Yes     |         |\n| sqlite3_total_changes64| ❌ No      |         |\n| sqlite3_last_insert_rowid | ✅ Yes  |         |\n| sqlite3_set_last_insert_rowid | ❌ No |       |\n\n### Memory Management\n\n| Interface              | Status  | Comment |\n|------------------------|---------|---------|\n| sqlite3_malloc         | ✅ Yes     |         |\n| sqlite3_malloc64       | ✅ Yes     |         |\n| sqlite3_free           | ✅ Yes     |         |\n| sqlite3_realloc        | ❌ No      |         |\n| sqlite3_realloc64      | ❌ No      |         |\n| sqlite3_msize          | ❌ No      |         |\n| sqlite3_memory_used    | ❌ No      |         |\n| sqlite3_memory_highwater | ❌ No    |         |\n| sqlite3_soft_heap_limit64 | ❌ No   |         |\n| sqlite3_hard_heap_limit64 | ❌ No   |         |\n| sqlite3_release_memory | ❌ No      |         |\n\n### Callback Functions\n\n| Interface                | Status  | Comment |\n|--------------------------|---------|---------|\n| sqlite3_busy_handler     | ✅ Yes     |         |\n| sqlite3_busy_timeout     | ✅ Yes     |         |\n| sqlite3_trace_v2         | ❌ No      | Stub    |\n| sqlite3_progress_handler | ❌ No      | Stub    |\n| sqlite3_set_authorizer   | ❌ No      | Stub    |\n| sqlite3_commit_hook      | ❌ No      |         |\n| sqlite3_rollback_hook    | ❌ No      |         |\n| sqlite3_update_hook      | ❌ No      |         |\n| sqlite3_preupdate_hook   | ❌ No      |         |\n| sqlite3_unlock_notify    | ❌ No      |         |\n| sqlite3_wal_hook         | ❌ No      |         |\n\n### User-Defined Functions\n\n| Interface                    | Status  | Comment |\n|------------------------------|---------|---------|\n| sqlite3_create_function      | ❌ No      |         |\n| sqlite3_create_function_v2   | ❌ No      | Stub    |\n| sqlite3_create_function16    | ❌ No      |         |\n| sqlite3_create_window_function | ❌ No    | Stub    |\n| sqlite3_aggregate_context    | ❌ No      | Stub    |\n| sqlite3_user_data            | ❌ No      | Stub    |\n| sqlite3_context_db_handle    | ❌ No      | Stub    |\n| sqlite3_get_auxdata          | ❌ No      |         |\n| sqlite3_set_auxdata          | ❌ No      |         |\n| sqlite3_result_null          | ❌ No      | Stub    |\n| sqlite3_result_int           | ❌ No      |         |\n| sqlite3_result_int64         | ❌ No      | Stub    |\n| sqlite3_result_double        | ❌ No      | Stub    |\n| sqlite3_result_text          | ❌ No      | Stub    |\n| sqlite3_result_text16        | ❌ No      |         |\n| sqlite3_result_text64        | ❌ No      |         |\n| sqlite3_result_blob          | ❌ No      | Stub    |\n| sqlite3_result_blob64        | ❌ No      |         |\n| sqlite3_result_value         | ❌ No      |         |\n| sqlite3_result_pointer       | ❌ No      |         |\n| sqlite3_result_zeroblob      | ❌ No      |         |\n| sqlite3_result_zeroblob64    | ❌ No      |         |\n| sqlite3_result_error         | ❌ No      | Stub    |\n| sqlite3_result_error16       | ❌ No      |         |\n| sqlite3_result_error_code    | ❌ No      |         |\n| sqlite3_result_error_nomem   | ❌ No      | Stub    |\n| sqlite3_result_error_toobig  | ❌ No      | Stub    |\n| sqlite3_result_subtype       | ❌ No      |         |\n\n### Collation Functions\n\n| Interface                   | Status  | Comment |\n|-----------------------------|---------|---------|\n| sqlite3_create_collation    | ❌ No      |         |\n| sqlite3_create_collation_v2 | ❌ No      | Stub    |\n| sqlite3_create_collation16  | ❌ No      |         |\n| sqlite3_collation_needed    | ❌ No      |         |\n| sqlite3_collation_needed16  | ❌ No      |         |\n| sqlite3_stricmp             | ❌ No      | Stub    |\n| sqlite3_strnicmp            | ❌ No      |         |\n\n### Backup API\n\n| Interface                | Status  | Comment |\n|--------------------------|---------|---------|\n| sqlite3_backup_init      | ❌ No      | Stub    |\n| sqlite3_backup_step      | ❌ No      | Stub    |\n| sqlite3_backup_finish    | ❌ No      | Stub    |\n| sqlite3_backup_remaining | ❌ No      | Stub    |\n| sqlite3_backup_pagecount | ❌ No      | Stub    |\n\n### BLOB I/O\n\n| Interface              | Status  | Comment |\n|------------------------|---------|---------|\n| sqlite3_blob_open      | ❌ No      | Stub    |\n| sqlite3_blob_close     | ❌ No      | Stub    |\n| sqlite3_blob_bytes     | ❌ No      | Stub    |\n| sqlite3_blob_read      | ❌ No      | Stub    |\n| sqlite3_blob_write     | ❌ No      | Stub    |\n| sqlite3_blob_reopen    | ❌ No      |         |\n\n### WAL Functions\n\n| Interface                  | Status  | Comment |\n|----------------------------|---------|---------|\n| sqlite3_wal_checkpoint     | ✅ Yes     |         |\n| sqlite3_wal_checkpoint_v2  | ✅ Yes     |         |\n| sqlite3_wal_autocheckpoint | ❌ No      |         |\n| sqlite3_wal_hook           | ❌ No      |         |\n\n### Utility Functions\n\n| Interface              | Status  | Comment |\n|------------------------|---------|---------|\n| sqlite3_libversion     | ✅ Yes     | Returns \"3.42.0\" |\n| sqlite3_libversion_number | ✅ Yes  | Returns 3042000 |\n| sqlite3_sourceid       | ❌ No      |         |\n| sqlite3_threadsafe     | ✅ Yes     | Returns 1 |\n| sqlite3_complete       | ❌ No      | Stub    |\n| sqlite3_interrupt      | ❌ No      | Stub    |\n| sqlite3_sleep          | ❌ No      | Stub    |\n| sqlite3_randomness     | ❌ No      |         |\n| sqlite3_get_table      | ✅ Yes     |         |\n| sqlite3_free_table     | ✅ Yes     |         |\n| sqlite3_mprintf        | ❌ No      |         |\n| sqlite3_vmprintf       | ❌ No      |         |\n| sqlite3_snprintf       | ❌ No      |         |\n| sqlite3_vsnprintf      | ❌ No      |         |\n| sqlite3_strglob        | ❌ No      |         |\n| sqlite3_strlike        | ❌ No      |         |\n\n### Table Metadata\n\n| Interface                    | Status  | Comment |\n|------------------------------|---------|---------|\n| sqlite3_table_column_metadata | ✅ Yes    |         |\n\n### Virtual Tables\n\n| Interface                | Status  | Comment |\n|--------------------------|---------|---------|\n| sqlite3_create_module    | ❌ No      |         |\n| sqlite3_create_module_v2 | ❌ No      |         |\n| sqlite3_drop_modules     | ❌ No      |         |\n| sqlite3_declare_vtab     | ❌ No      |         |\n| sqlite3_overload_function| ❌ No      |         |\n| sqlite3_vtab_config      | ❌ No      |         |\n| sqlite3_vtab_on_conflict | ❌ No      |         |\n| sqlite3_vtab_nochange    | ❌ No      |         |\n| sqlite3_vtab_collation   | ❌ No      |         |\n| sqlite3_vtab_distinct    | ❌ No      |         |\n| sqlite3_vtab_in          | ❌ No      |         |\n| sqlite3_vtab_in_first    | ❌ No      |         |\n| sqlite3_vtab_in_next     | ❌ No      |         |\n| sqlite3_vtab_rhs_value   | ❌ No      |         |\n\n### Loadable Extensions\n\n| Interface                    | Status  | Comment |\n|------------------------------|---------|---------|\n| sqlite3_load_extension       | ❌ No      |         |\n| sqlite3_enable_load_extension| ❌ No      |         |\n| sqlite3_auto_extension       | ❌ No      |         |\n| sqlite3_cancel_auto_extension| ❌ No      |         |\n| sqlite3_reset_auto_extension | ❌ No      |         |\n\n### Serialization\n\n| Interface              | Status  | Comment |\n|------------------------|---------|---------|\n| sqlite3_serialize      | ❌ No      | Stub    |\n| sqlite3_deserialize    | ❌ No      | Stub    |\n\n### Miscellaneous\n\n| Interface                | Status  | Comment |\n|--------------------------|---------|---------|\n| sqlite3_keyword_count    | ❌ No      |         |\n| sqlite3_keyword_name     | ❌ No      |         |\n| sqlite3_keyword_check    | ❌ No      |         |\n| sqlite3_txn_state        | ❌ No      |         |\n| sqlite3_file_control     | ❌ No      |         |\n| sqlite3_status           | ❌ No      |         |\n| sqlite3_status64         | ❌ No      |         |\n| sqlite3_test_control     | ❌ No      | Testing only |\n| sqlite3_log              | ❌ No      |         |\n\n### Turso-specific Extensions\n\n| Interface                      | Status  | Comment |\n|--------------------------------|---------|---------|\n| libsql_wal_frame_count         | ✅ Yes     | Get WAL frame count |\n| libsql_wal_get_frame           | ✅ Yes     | Extract frame from WAL |\n| libsql_wal_insert_frame        | ✅ Yes     | Insert frame into WAL |\n| libsql_wal_disable_checkpoint  | ✅ Yes     | Disable checkpointing |\n\n## SQLite VDBE opcodes\n\n| Opcode         | Status | Comment |\n|----------------|--------|---------|\n| Add            | ✅ Yes    |         |\n| AddImm         | ✅ Yes    |         |\n| Affinity       | ✅ Yes    |         |\n| AggFinal       | ✅ Yes    |         |\n| AggStep        | ✅ Yes    |         |\n| AggValue       | ✅ Yes    |         |\n| And            | ✅ Yes    |         |\n| AutoCommit     | ✅ Yes    |         |\n| BitAnd         | ✅ Yes    |         |\n| BitNot         | ✅ Yes    |         |\n| BitOr          | ✅ Yes    |         |\n| Blob           | ✅ Yes    |         |\n| BeginSubrtn    | ✅ Yes    |         |\n| Cast           | ✅ Yes    |         |\n| Checkpoint     | ✅ Yes    |         |\n| Clear          | ❌ No     |         |\n| Close          | ✅ Yes    |         |\n| CollSeq        | ✅ Yes    |         |\n| Column         | ✅ Yes    |         |\n| Compare        | ✅ Yes    |         |\n| Concat         | ✅ Yes    |         |\n| Copy           | ✅ Yes    |         |\n| Count          | ✅ Yes    |         |\n| CreateBTree    | 🚧 Partial| no temp databases |\n| DecrJumpZero   | ✅ Yes    |         |\n| Delete         | ✅ Yes    |         |\n| Destroy        | ✅ Yes    |         |\n| Divide         | ✅ Yes    |         |\n| DropIndex      | ✅ Yes    |         |\n| DropTable      | ✅ Yes    |         |\n| DropTrigger    | ✅ Yes     |         |\n| EndCoroutine   | ✅ Yes    |         |\n| Eq             | ✅ Yes    |         |\n| Expire         | ❌ No     |         |\n| Explain        | ❌ No     |         |\n| FkCheck        | ✅ Yes    |         |\n| FkCounter      | ✅ Yes    |         |\n| FkIfZero       | ✅ Yes    |         |\n| Found          | ✅ Yes    |         |\n| Filter         | ✅ Yes    |         |\n| FilterAdd      | ✅ Yes    |         |\n| Function       | ✅ Yes    |         |\n| Ge             | ✅ Yes    |         |\n| Gosub          | ✅ Yes    |         |\n| Goto           | ✅ Yes    |         |\n| Gt             | ✅ Yes    |         |\n| Halt           | ✅ Yes    |         |\n| HaltIfNull     | ✅ Yes    |         |\n| IdxDelete      | ✅ Yes    |         |\n| IdxGE          | ✅ Yes    |         |\n| IdxInsert      | ✅ Yes    |         |\n| IdxLE          | ✅ Yes    |         |\n| IdxLT          | ✅ Yes    |         |\n| IdxRowid       | ✅ Yes    |         |\n| If             | ✅ Yes    |         |\n| IfNeg          | ✅ Yes     |         |\n| IfNot          | ✅ Yes    |         |\n| IfPos          | ✅ Yes    |         |\n| IfZero         | ❌ No     |         |\n| IncrVacuum     | ❌ No     |         |\n| Init           | ✅ Yes    |         |\n| InitCoroutine  | ✅ Yes    |         |\n| Insert         | ✅ Yes    |         |\n| Int64          | ✅ Yes    |         |\n| Integer        | ✅ Yes    |         |\n| IntegrityCk    | ✅ Yes    |         |\n| IsNull         | ✅ Yes    |         |\n| IsUnique       | ❌ No     |         |\n| JournalMode    | ✅ Yes    |         |\n| Jump           | ✅ Yes    |         |\n| Last           | ✅ Yes    |         |\n| Le             | ✅ Yes    |         |\n| LoadAnalysis   | ❌ No     |         |\n| Lt             | ✅ Yes    |         |\n| MakeRecord     | ✅ Yes    |         |\n| MaxPgcnt       | ✅ Yes    |         |\n| MemMax         | ✅ Yes     |         |\n| Move           | ✅ Yes    |         |\n| Multiply       | ✅ Yes    |         |\n| MustBeInt      | ✅ Yes    |         |\n| Ne             | ✅ Yes    |         |\n| NewRowid       | ✅ Yes    |         |\n| Next           | ✅ Yes     |         |\n| Noop           | ✅ Yes     |         |\n| Not            | ✅ Yes    |         |\n| NotExists      | ✅ Yes    |         |\n| NotFound       | ✅ Yes    |         |\n| NotNull        | ✅ Yes    |         |\n| Null           | ✅ Yes    |         |\n| NullRow        | ✅ Yes    |         |\n| Once           | ✅ Yes     |         |\n| OpenAutoindex  | ✅ Yes     |         |\n| OpenDup        | ✅ Yes     |         |\n| OpenEphemeral  | ✅ Yes     |         |\n| OpenPseudo     | ✅ Yes    |         |\n| OpenRead       | ✅ Yes    |         |\n| OpenWrite      | ✅ Yes     |         |\n| Or             | ✅ Yes    |         |\n| Pagecount      | 🚧 Partial| no temp databases |\n| Param          | ❌ No     |         |\n| ParseSchema    | ✅ Yes    |         |\n| Permutation    | ❌ No     |         |\n| Prev           | ✅ Yes     |         |\n| Program        | ✅ Yes     |         |\n| ReadCookie     | 🚧 Partial| no temp databases, only user_version supported |\n| Real           | ✅ Yes    |         |\n| RealAffinity   | ✅ Yes    |         |\n| Remainder      | ✅ Yes    |         |\n| ResetCount     | ❌ No     |         |\n| ResetSorter    | 🚧 Partial| sorter cursors are not supported yet; only ephemeral tables are |\n| ResultRow      | ✅ Yes    |         |\n| Return         | ✅ Yes    |         |\n| Rewind         | ✅ Yes    |         |\n| RowData        | ✅ Yes     |         |\n| RowId          | ✅ Yes    |         |\n| RowKey         | ❌ No     |         |\n| RowSetAdd      | ✅ Yes     |         |\n| RowSetRead     | ✅ Yes    |         |\n| RowSetTest     | ✅ Yes     |         |\n| Rowid          | ✅ Yes    |         |\n| SCopy          | ❌ No     |         |\n| Savepoint      | ✅ No     |         |\n| Seek           | ❌ No     |         |\n| SeekGe         | ✅ Yes    |         |\n| SeekGt         | ✅ Yes    |         |\n| SeekLe         | ✅ Yes    |         |\n| SeekLt         | ✅ Yes    |         |\n| SeekRowid      | ✅ Yes    |         |\n| SeekEnd        | ✅ Yes    |         |\n| Sequence       | ✅ Yes    |         |\n| SequenceTest   | ✅ Yes    |         |\n| SetCookie      | ✅ Yes    |         |\n| ShiftLeft      | ✅ Yes    |         |\n| ShiftRight     | ✅ Yes    |         |\n| SoftNull       | ✅ Yes    |         |\n| Sort           | ❌ No     |         |\n| SorterCompare  | ✅ Yes     |         |\n| SorterData     | ✅ Yes    |         |\n| SorterInsert   | ✅ Yes    |         |\n| SorterNext     | ✅ Yes    |         |\n| SorterOpen     | ✅ Yes    |         |\n| SorterSort     | ✅ Yes    |         |\n| String         | NotNeeded | SQLite uses String for sized strings and String8 for null-terminated. All our strings are sized |\n| String8        | ✅ Yes    |         |\n| Subtract       | ✅ Yes    |         |\n| TableLock      | ❌ No     |         |\n| Trace          | ❌ No     |         |\n| Transaction    | ✅ Yes    |         |\n| VBegin         | ✅ Yes    |         |\n| VColumn        | ✅ Yes    |         |\n| VCreate        | ✅ Yes    |         |\n| VDestroy       | ✅ Yes    |         |\n| VFilter        | ✅ Yes    |         |\n| VNext          | ✅ Yes    |         |\n| VOpen          | ✅ Yes    |         |\n| VRename        | ✅ Yes    |         |\n| VUpdate        | ✅ Yes    |         |\n| Vacuum         | ❌ No     |         |\n| Variable       | ✅ Yes    |         |\n| Yield          | ✅ Yes    |         |\n| ZeroOrNull     | ✅ Yes    |         |\n\n##  [SQLite journaling modes](https://www.sqlite.org/pragma.html#pragma_journal_mode)\n\nWe currently don't have plan to support the rollback journal mode as it locks the database file during writes.\nTherefore, all rollback-type modes (delete, truncate, persist, memory) are marked are `Not Needed` below.\n\n| Journal mode | Status     | Comment                        |\n|--------------|------------|--------------------------------|\n| wal          | ✅ Yes        |                                |\n| wal2         | ❌ No         | experimental feature in sqlite |\n| delete       | Not Needed |                                |\n| truncate     | Not Needed |                                |\n| persist      | Not Needed |                                |\n| memory       | Not Needed |                                |\n\n##  Extensions\n\nTurso has in-tree extensions.\n\n### UUID\n\nUUID's in Turso are `blobs` by default.\n\n| Function              | Status | Comment                                                       |\n|-----------------------|--------|---------------------------------------------------------------|\n| uuid4()               | ✅ Yes    | UUID version 4                                                |\n| uuid4_str()           | ✅ Yes    | UUID v4 string alias `gen_random_uuid()` for PG compatibility |\n| uuid7(X?)             | ✅ Yes    | UUID version 7 (optional parameter for seconds since epoch)   |\n| uuid7_timestamp_ms(X) | ✅ Yes    | Convert a UUID v7 to milliseconds since epoch                 |\n| uuid_str(X)           | ✅ Yes    | Convert a valid UUID to string                                |\n| uuid_blob(X)          | ✅ Yes    | Convert a valid UUID to blob                                  |\n\n### regexp\n\nThe `regexp` extension is compatible with [sqlean-regexp](https://github.com/nalgeon/sqlean/blob/main/docs/regexp.md).\n\n| Function                                       | Status | Comment |\n|------------------------------------------------|--------|---------|\n| regexp(pattern, source)                        | ✅ Yes    |         |\n| regexp_like(source, pattern)                   | ✅ Yes    |         |\n| regexp_substr(source, pattern)                 | ✅ Yes    |         |\n| regexp_capture(source, pattern[, n])           | ✅ Yes    |         |\n| regexp_replace(source, pattern, replacement)   | ✅ Yes    |         |\n\n### Vector\n\nThe `vector` extension is compatible with libSQL native vector search.\n\n| Function                                       | Status | Comment |\n|------------------------------------------------|--------|---------|\n| vector(x)                                      | ✅ Yes    |         |\n| vector32(x)                                    | ✅ Yes    |         |\n| vector64(x)                                    | ✅ Yes    |         |\n| vector_extract(x)                              | ✅ Yes    |         |\n| vector_distance_cos(x, y)                      | ✅ Yes    |         |\n| vector_distance_l2(x, y)                              | ✅ Yes    |Euclidean distance|\n| vector_concat(x, y)                            | ✅ Yes    |         |\n| vector_slice(x, start_index, end_index)        | ✅ Yes    |         |\n\n### Time\n\nThe `time` extension is compatible with [sqlean-time](https://github.com/nalgeon/sqlean/blob/main/docs/time.md).\n\n\n| Function                                                            | Status | Comment |\n| ------------------------------------------------------------------- | ------ |---------|\n| time_now()                                                          | ✅ Yes    |         |\n| time_date(year, month, day[, hour, min, sec[, nsec[, offset_sec]]]) | ✅ Yes    |         |\n| time_get_year(t)                                                    | ✅ Yes    |         |\n| time_get_month(t)                                                   | ✅ Yes    |         |\n| time_get_day(t)                                                     | ✅ Yes    |         |\n| time_get_hour(t)                                                    | ✅ Yes    |         |\n| time_get_minute(t)                                                  | ✅ Yes    |         |\n| time_get_second(t)                                                  | ✅ Yes    |         |\n| time_get_nano(t)                                                    | ✅ Yes    |         |\n| time_get_weekday(t)                                                 | ✅ Yes    |         |\n| time_get_yearday(t)                                                 | ✅ Yes    |         |\n| time_get_isoyear(t)                                                 | ✅ Yes    |         |\n| time_get_isoweek(t)                                                 | ✅ Yes    |         |\n| time_get(t, field)                                                  | ✅ Yes    |         |\n| time_unix(sec[, nsec])                                              | ✅ Yes    |         |\n| time_milli(msec)                                                    | ✅ Yes    |         |\n| time_micro(usec)                                                    | ✅ Yes    |         |\n| time_nano(nsec)                                                     | ✅ Yes    |         |\n| time_to_unix(t)                                                     | ✅ Yes    |         |\n| time_to_milli(t)                                                    | ✅ Yes    |         |\n| time_to_micro(t)                                                    | ✅ Yes    |         |\n| time_to_nano(t)                                                     | ✅ Yes    |         |\n| time_after(t, u)                                                    | ✅ Yes    |         |\n| time_before(t, u)                                                   | ✅ Yes    |         |\n| time_compare(t, u)                                                  | ✅ Yes    |         |\n| time_equal(t, u)                                                    | ✅ Yes    |         |\n| time_add(t, d)                                                      | ✅ Yes    |         |\n| time_add_date(t, years[, months[, days]])                           | ✅ Yes    |         |\n| time_sub(t, u)                                                      | ✅ Yes    |         |\n| time_since(t)                                                       | ✅ Yes    |         |\n| time_until(t)                                                       | ✅ Yes    |         |\n| time_trunc(t, field)                                                | ✅ Yes    |         |\n| time_trunc(t, d)                                                    | ✅ Yes    |         |\n| time_round(t, d)                                                    | ✅ Yes    |         |\n| time_fmt_iso(t[, offset_sec])                                       | ✅ Yes    |         |\n| time_fmt_datetime(t[, offset_sec])                                  | ✅ Yes    |         |\n| time_fmt_date(t[, offset_sec])                                      | ✅ Yes    |         |\n| time_fmt_time(t[, offset_sec])                                      | ✅ Yes    |         |\n| time_parse(s)                                                       | ✅ Yes    |         |\n| dur_ns()                                                            | ✅ Yes    |         |\n| dur_us()                                                            | ✅ Yes    |         |\n| dur_ms()                                                            | ✅ Yes    |         |\n| dur_s()                                                             | ✅ Yes    |         |\n| dur_m()                                                             | ✅ Yes    |         |\n| dur_h()                                                             | ✅ Yes    |         |\n\n### Full-Text Search (FTS)\n\nTurso implements FTS using Tantivy instead of SQLite's FTS3/FTS4/FTS5.\n\n| Feature | Status | Comment |\n|---------|--------|---------|\n| CREATE INDEX ... USING fts | ✅ Yes | Turso-specific syntax |\n| fts_match() | ✅ Yes | |\n| fts_score() | ✅ Yes | BM25 relevance scoring |\n| fts_highlight() | ✅ Yes | |\n| MATCH operator | ✅ Yes | |\n| SQLite FTS3/FTS4/FTS5 | ❌ No | Use Turso FTS instead |\n| snippet() | ❌ No | |\n\n### CSV\n\nThe CSV extension provides RFC 4180 compliant CSV file reading.\n\n| Feature | Status | Comment |\n|---------|--------|---------|\n| CSV virtual table | ✅ Yes | `CREATE VIRTUAL TABLE ... USING csv(...)` |\n\n### Percentile\n\nStatistical aggregate functions.\n\n| Function | Status | Comment |\n|----------|--------|---------|\n| median(X) | ✅ Yes | |\n| percentile(Y,P) | ✅ Yes | |\n| percentile_cont(Y,P) | ✅ Yes | |\n| percentile_disc(Y,P) | ✅ Yes | |\n\n### Table-Valued Functions\n\n| Function | Status | Comment |\n|----------|--------|---------|\n| generate_series(start, stop[, step]) | ✅ Yes | All parameters supported |\n| carray() | ❌ No | C-API specific |\n\n### Internal Virtual Tables\n\n| Virtual Table | Status | Comment |\n|---------------|--------|---------|\n| sqlite_dbpage | 🚧 Partial | readonly, no attach support |\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to Turso\n\nWe'd love to have you contribute to Turso!\n\nThis document is a quick helper to get you going.\n\n<!--toc:start-->\n- [Contributing to Turso](#contributing-to-turso)\n  - [Getting Started](#getting-started)\n    - [Configuring `mold` Linker](#configuring-mold-linker)\n    - [Running Tests On Linux](#running-tests-on-linux)\n  - [Debugging bugs](#debugging-bugs)\n    - [Query execution debugging](#query-execution-debugging)\n    - [Stress testing with sanitizers](#stress-testing-with-sanitizers)\n  - [Finding things to work on](#finding-things-to-work-on)\n  - [Submitting your work](#submitting-your-work)\n  - [Compatibility tests](#compatibility-tests)\n    - [Prerequisites](#prerequisites)\n    - [Running the tests](#running-the-tests)\n  - [SQL Test Runner](#sql-test-runner)\n  - [TPC-H](#tpc-h)\n  - [Deterministic simulation tests](#deterministic-simulation-tests)\n    - [Whopper](#whopper)\n  - [Python Bindings](#python-bindings)\n  - [Fault injection with unreliable libc](#fault-injection-with-unreliable-libc)\n  - [Antithesis](#antithesis)\n  - [Adding Third Party Dependencies](#adding-third-party-dependencies)\n<!--toc:end-->\n\n## Getting Started\n\nTurso is a rewrite of SQLite in Rust. If you are new to SQLite, the following articles and books are a good starting point:\n\n* [Architecture of SQLite](https://www.sqlite.org/arch.html)\n* Sibsankar Haldar. [SQLite Database System Design and Implementation (2nd Edition)](https://books.google.com/books/?id=yWzwCwAAQBAJ&redir_esc=y). 2016\n* Jay Kreibich. [Using SQLite: Small. Fast. Reliable. Choose Any Three. 1st Edition](https://www.oreilly.com/library/view/using-sqlite/9781449394592/). 2010\n\nIf you are new to Rust, the following books are recommended reading:\n\n* Jim Blandy et al. [Programming Rust, 2nd Edition](https://www.oreilly.com/library/view/programming-rust-2nd/9781492052586/). 2021\n* Steve Klabnik and Carol Nichols. [The Rust Programming Language](https://doc.rust-lang.org/book/#the-rust-programming-language). 2022\n\nExamples of contributing\n\n* [How to contribute a SQL function implementation](docs/contributing/contributing_functions.md)\n* [Rickrolling Turso DB](https://avi.im/blag/2025/rickrolling-turso)\n\nTo build and run `tursodb` CLI:\n\n```shell\ncargo run --package turso_cli --bin tursodb database.db\n```\n\nRun tests:\n```console\ncargo build -p turso_sqlite3 --features capi\ncargo test\n```\n\n### Configuring `mold` Linker\n\nThe `mold` linker can reduce your build time from a minute to just few seconds.\n\nFirst, install `mold`:\n\n```console\n# Fedora/RHEL\nsudo dnf install mold\n\n# Ubuntu/Debian\nsudo apt install mold\n```\n\nThen configure Cargo to use mold by creating `.cargo/config.toml`:\n\n**For Linux:**\n\n```toml\n[target.x86_64-unknown-linux-gnu]\nlinker = \"clang\"\nrustflags = [\"-C\", \"link-arg=-fuse-ld=mold\"]\n```\n\n### Running Tests On Linux\n\n> [!NOTE]\n> These steps have been tested on Ubuntu Noble 24.04.2 LTS\n\nRunning tests on Linux and getting them pass requires a few additional steps\n\n1. Install [SQLite](https://www.sqlite.org/index.html) headers\n```console\nsudo apt install sqlite3 libsqlite3-dev\n```\n2. Install Python3 dev files\n```console\nsudo apt install python3.12 python3.12-dev\n```\n3. Set env var for Maturin\n```console\nexport PYO3_PYTHON=$(which python3)\n```\n4. Build Cargo\n```console\ncargo build -p turso_sqlite3 --features capi\n```\n5. Run tests\n```console\ncargo test\n```\n\nTest coverage report:\n\n```\ncargo tarpaulin -o html\n```\n\n> [!NOTE]\n> Generation of coverage report requires [tarpaulin](https://github.com/xd009642/tarpaulin) binary to be installed.\n> You can install it with `cargo install cargo-tarpaulin`\n\n[//]: # (TODO remove the below tip when the bug is solved)\n\n> [!TIP]\n> If coverage fails with \"Test failed during run\" error and all of the tests passed it might be the result of tarpaulin [bug](https://github.com/xd009642/tarpaulin/issues/1642). You can temporarily set [dynamic libraries linking manually](https://doc.rust-lang.org/cargo/reference/environment-variables.html#dynamic-library-paths) as a workaround, e.g. for linux  `LD_LIBRARY_PATH=\"$(rustc --print=target-libdir)\" cargo tarpaulin -o html`.\n\nRun benchmarks:\n\n```console\ncargo bench --profile bench-profile --bench benchmark\n```\n\nRun benchmarks and generate flamegraphs:\n\n```console\necho -1 | sudo tee /proc/sys/kernel/perf_event_paranoid\ncargo bench --profile bench-profile --bench benchmark -- --profile-time=5\n```\n\n## Debugging bugs\n\n### Query execution debugging\n\nTurso aims towards SQLite compatibility. If you find a query that has different behavior than SQLite, the first step is to check what the generated bytecode looks like.\n\nTo do that, first run the `EXPLAIN` command in `sqlite3` shell:\n\n```\nsqlite> EXPLAIN SELECT first_name FROM users;\naddr  opcode         p1    p2    p3    p4             p5  comment\n----  -------------  ----  ----  ----  -------------  --  -------------\n0     Init           0     7     0                    0   Start at 7\n1     OpenRead       0     2     0     2              0   root=2 iDb=0; users\n2     Rewind         0     6     0                    0\n3       Column         0     1     1                    0   r[1]= cursor 0 column 1\n4       ResultRow      1     1     0                    0   output=r[1]\n5     Next           0     3     0                    1\n6     Halt           0     0     0                    0\n7     Transaction    0     0     1     0              1   usesStmtJournal=0\n8     Goto           0     1     0                    0\n```\n\nand then run the same command in Turso's shell.\n\nIf the bytecode is different, that's the bug -- work towards fixing code generation.\nIf the bytecode is the same, but query results are different, then the bug is somewhere in the virtual machine interpreter or storage layer.\n\n### Stress testing with sanitizers\n\nIf you suspect a multi-threading issue, you can run the stress test with ThreadSanitizer enabled as follows:\n\n```console\nrustup toolchain install nightly\nrustup override set nightly\ncargo run -Zbuild-std --target x86_64-unknown-linux-gnu -p turso_stress -- --vfs syscall --nr-threads 4 --nr-iterations 1000\n```\n\n## Finding things to work on\n\nThe issue tracker has issues tagged with [good first issue](https://github.com/tursodatabase/limbo/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22),\nwhich are considered to be things to work on to get going. If you're interested in working on one of them, comment on the issue tracker, and we're happy to help you get going.\n\n## Submitting your work\n\nFork the repository and open a pull request to submit your work.\n\nThe CI checks for formatting, Clippy warnings, and test failures so remember to run the following before submitting your pull request:\n\n* `cargo fmt` and `cargo clippy --workspace --all-features --all-targets -- --deny=warnings` to keep the code formatting in check.\n* `make test` to run the test suite.\n\n**Keep your pull requests focused and as small as possible, but not smaller.** IOW, when preparing a pull request, ensure it focuses on a single thing and that your commits align with that. For example, a good pull request might fix a specific bug or a group of related bugs. Or a good pull request might add a new feature and test for it. Conversely, a bad pull request might fix a bug, add a new feature, and refactor some code.\n\n**The commits in your pull request tell the story of your change.** Break your pull request into multiple commits when needed to make it easier to review and ensure that future developers can also understand the change as they are in the middle of a `git bisect` run to debug a nasty bug. A developer should be able to reconstruct the intent of your change and how you got to the end-result by reading the commits. To keep a clean commit history, make sure the commits are _atomic_:\n\n* **Keep commits as small as possible**. The smaller the commit, the easier it is to review, but also easier `git revert` when things go bad.\n* **Don't mix logic and cleanups in same commit**. If you need to refactor the code, do it in a commit of its own. Mixing refactoring with logic changes makes it very hard to review a commit.\n* **Don't mix logic and formatting changes in same commit**. Resist the urge to fix random formatting issues in the same commit as your logic changes, because it only makes it harder to review the commit.\n* **Write a good commit message**. You know your commit is atomic when it's easy to write a short commit message that describes the intent of the change.\n\nTo produce pull requests like this, you should learn how to use Git's interactive rebase (`git rebase -i`).\n\nFor a longer discussion on good commits, see Al Tenhundfeld's [What makes a good git commit](https://www.simplethread.com/what-makes-a-good-git-commit/), for example.\n\n## Compatibility tests\n\nThe `testing/test.all` is a starting point for adding functional tests using a similar syntax to SQLite.\nThe purpose of these tests is to verify behavior matches with SQLite and Turso.\n\n### Prerequisites\n\n1. [Cargo-c](https://github.com/lu-zero/cargo-c) is needed for building C-ABI compatible library. You can get it via:\n```console\ncargo install cargo-c\n```\n2. [SQLite](https://www.sqlite.org/index.html) is needed for compatibility checking. You can install it using `brew` on macOS/Linux:\n```console\nbrew install sqlite\n```\nOr using `choco` on Windows:\n```console\nchoco install sqlite\n```\n\n### Running the tests\nTo run the test suite with Turso, simply run:\n\n```\nmake test\n```\n\nTo run the test suite with SQLite, type:\n\n```\nSQLITE_EXEC=sqlite3 SQLITE_FLAGS=\"\" make test\n```\n\nWhen working on a new feature, please consider adding a test case for it.\n\n## SQL Test Runner\n\nThe `test-runner` crate provides a dedicated test runner with a custom DSL for writing SQL tests.\nTests should be added to `testing/sqltests/tests/` using the `.sqltest` format.\n\nTo run tests:\n\n```console\nmake -C testing/sqltests run\n```\n\nFor full documentation on the DSL syntax and CLI usage, see the [test-runner docs](testing/sqltests/docs/).\n\n## TPC-H\n\n[TPC-H](https://www.tpc.org/tpch/) is a standard benchmark for testing database performance. To try out Turso's performance against a TPC-H compatible workload,\nyou can generate or download a TPC-H compatible SQLite database e.g. [here](https://github.com/lovasoa/TPCH-sqlite).\n\n## Deterministic simulation tests\n\nThe `simulator` directory contains a deterministic simulator for testing.\nWhat this means is that the behavior of a test run is deterministic based on the seed value.\nIf the simulator catches a bug, you can always reproduce the exact same sequence of events by passing the same seed.\nThe simulator also performs fault injection to discover interesting bugs.\n\n### Whopper\n\nWhopper is a DST that, unlike `simulator`, performs concurrent query execution.\n\nTo run Whopper for your local changes, run:\n\n```console\n./testing/concurrent-simulator/bin/run\n```\n\nThe output of the simulation run looks as follows:\n\n```\nmode = fast\nseed = 11621338508193870992\n       .             I/U/D/C\n       .             22/17/15/0\n       .             41/34/20/3\n       |             62/43/27/4\n       |             88/55/30/5\n      ╱|╲            97/58/30/6\n     ╱╲|╱╲           108/62/30/7\n    ╱╲╱|╲╱╲          115/67/32/7\n   ╱╲╱╲|╱╲╱╲         121/74/35/7\n  ╱╲╱╲╱|╲╱╲╱╲        125/80/38/7\n ╱╲╱╲╱╲|╱╲╱╲╱╲       141/94/43/8\n\nreal    0m1.250s\nuser    0m0.843s\nsys     0m0.043s\n```\n\nThe simulator prints ten progress indication lines, regardless of how long a run takes. The progress indicator line shows the following stats:\n\n* `I` -- the number of `INSERT` statements executed\n* `U` -- the number of `UPDATE` statements executed\n* `D` -- the number of `DELETE` statements executed\n* `C` -- the number of `PRAGMA integrity_check` statements executed\n\nThis will do a short sanity check run in using the `fast` mode.\n\nIf you need to reproduce a run, just defined the `SEED` environment variable as follows:\n\n```console\nSEED=1234 ./testing/concurrent-simulator/bin/run\n```\n\nYou can also run Whopper in exploration mode to find more serious bugs:\n\n```console\n./testing/concurrent-simulator/bin/explore\n```\n\nNote that exploration uses the `chaos` mode so if you need to reproduce a run, use:\n\n```console\nSEED=1234 ./testing/concurrent-simulator/bin/run --mode chaos\n```\n\nBoth `explore` and `run` accept the `--enable-checksums` and `--enable-encryption` flags for per page checksums and encryption respectively.\n\n## Python Bindings\n\nTurso provides Python bindings built on top of the [PyO3](https://pyo3.rs) project.\nTo compile the Python bindings locally, you first need to create and activate a Python virtual environment (for example, with Python `3.12`):\n\n```bash\npython3.12 -m venv venv\nsource venv/bin/activate\n```\n\nThen, install [Maturin](https://pypi.org/project/maturin/):\n\n```bash\npip install maturin\n```\n\nOnce Maturin is installed, you can build the crate and install it as a Python module directly into the current virtual environment by running:\n\n```bash\ncd bindings/python && maturin develop\n```\n\n## Fault injection with unreliable libc\n\nFirst, build the unreliable libc:\n\n```\ncd testing/unreliable-libc\nmake\n```\n\nThe run the stress testing tool with fault injection enabled:\n\n```\nRUST_BACKTRACE=1 LD_PRELOAD=./testing/unreliable-libc/unreliable-libc.so cargo run -p turso_stress -- --nr-iterations 10000\n```\n\n## Antithesis\n\nAntithesis is a testing platform for finding bugs with reproducibility. In\nTurso, we use Antithesis in addition to our own deterministic simulation\ntesting (DST) tool for the following:\n\n- Discovering bugs that the DST did not catch (and improve the DST)\n- Discovering bugs that the DST does not cover (for example, non-simulated I/O)\n\nIf you have an Antithesis account, you first need to configure some\nenvironment variables:\n\n```bash\nexport ANTITHESIS_USER=\nexport ANTITHESIS_TENANT=\nexport ANTITHESIS_PASSWD=\nexport ANTITHESIS_DOCKER_HOST=\nexport ANTITHESIS_DOCKER_REPO=\nexport ANTITHESIS_EMAIL=\n```\n\nYou can then publish a new Antithesis workflow with:\n\n```bash\nscripts/antithesis/publish-workload.sh\n```\n\nAnd launch an Antithesis test run with:\n\n```bash\nscripts/antithesis/launch.sh\n```\n\n## Adding Third Party Dependencies\n\nWhen you want to add third party dependencies, please follow these steps:\n\n1. Add Licenses: Place the appropriate licenses for the third-party dependencies under the licenses directory. Ensure\n   that each license is in a separate file and named appropriately.\n2. Update NOTICE.md: Specify the licenses for the third-party dependencies in the NOTICE.md file. Include the name of\n   the dependency, the license file path, and the homepage of the dependency.\n\nBy following these steps, you ensure that all third-party dependencies are properly documented and their licenses are\nincluded in the project.\n"
  },
  {
    "path": "Cargo.toml",
    "content": "# Copyright 2023-2026 the Turso authors. All rights reserved. MIT license.\n\n[workspace]\nresolver = \"2\"\nmembers = [\n    \"bindings/java\",\n    \"bindings/javascript\",\n    \"bindings/javascript/sync\",\n    \"bindings/python\",\n    \"bindings/dotnet\",\n    \"bindings/rust\",\n    \"cli\",\n    \"core\",\n    \"extensions/completion\",\n    \"extensions/core\",\n    \"extensions/crypto\",\n    \"extensions/csv\",\n    \"extensions/ipaddr\",\n    \"extensions/percentile\",\n    \"extensions/regexp\",\n    \"extensions/tests\",\n    \"extensions/fuzzy\",\n    \"macros\",\n    \"testing/simulator\",\n    \"sqlite3\",\n    \"testing/stress\",\n    \"testing/sqlite_test_ext\",\n    \"tests\",\n    \"parser\",\n    \"sync/engine\",\n    \"sync/sdk-kit\",\n    \"sql_generation\",\n    \"testing/concurrent-simulator\",\n    \"perf/throughput/turso\",\n    \"perf/throughput/rusqlite\",\n    \"perf/encryption\",\n    \"tools/dbhash\",\n    \"sdk-kit\",\n    \"sdk-kit-macros\", \n    \"testing/differential-oracle/sql_gen_prop\",\n    \"testing/differential-oracle/sql_gen\",\n    \"testing/differential-oracle/fuzzer\",\n    \"testing/sqltests\"\n]\nexclude = [\"perf/latency/limbo\"]\n\n[workspace.package]\nversion = \"0.6.0-pre.4\"\nauthors = [\"the Turso authors\"]\nedition = \"2021\"\nlicense = \"MIT\"\nrepository = \"https://github.com/tursodatabase/turso\"\n\n[workspace.dependencies]\nturso = { path = \"bindings/rust\", version = \"0.6.0-pre.4\" }\nturso_node = { path = \"bindings/javascript\", version = \"0.6.0-pre.4\" }\nturso_sdk_kit = { path = \"sdk-kit\", version = \"0.6.0-pre.4\" }\nturso_sdk_kit_macros = { path = \"sdk-kit-macros\", version = \"0.6.0-pre.4\" }\nturso_sync_sdk_kit = { path = \"sync/sdk-kit\", version = \"0.6.0-pre.4\" }\nlimbo_completion = { path = \"extensions/completion\", version = \"0.6.0-pre.4\" }\nturso_core = { path = \"core\", version = \"0.6.0-pre.4\" }\nturso_sync_engine = { path = \"sync/engine\", version = \"0.6.0-pre.4\" }\nturso_ext = { path = \"extensions/core\", version = \"0.6.0-pre.4\" }\nturso_macros = { path = \"macros\", version = \"0.6.0-pre.4\" }\nturso_parser = { path = \"parser\", version = \"0.6.0-pre.4\" }\nsql_generation = { path = \"sql_generation\" }\nturso-dbhash = { path = \"tools/dbhash\" }\nstrum = { version = \"0.26\", features = [\"derive\"] }\nstrum_macros = \"0.26\"\nserde = \"1.0\"\nserde_json = \"1.0\"\nanyhow = \"1.0.98\"\nmimalloc = { version = \"0.1.47\", default-features = false }\nrusqlite = { version = \"0.37.0\", features = [\"bundled\"] }\nitertools = \"0.14.0\"\nrand = \"0.9.2\"\nrand_chacha = \"0.9.0\"\ntracing = \"0.1.41\"\nschemars = \"1.0.4\"\ngarde = \"0.22\"\nparking_lot = \"0.12.4\"\ntokio = { version = \"1.0\", default-features = false }\ntracing-subscriber = \"0.3.20\"\nfutures = \"0.3\"\nclap = \"4.5.47\"\nthiserror = \"2.0.16\"\ntempfile = \"3.20.0\"\nindexmap = \"2.11.1\"\nindicatif = \"0.17\"\nmiette = \"7.6.0\"\nbitflags = \"2.9.4\"\nfallible-iterator = \"0.3.0\"\n\ncriterion = \"0.5.0\"\ncodspeed-criterion-compat = \"4.2.1\"\ndivan = { package = \"codspeed-divan-compat\", version = \"4.2.1\" }\n\nchrono = { version = \"0.4.42\", default-features = false }\nhex = \"0.4\"\nantithesis_sdk = { version = \"0.2\", default-features = false }\ncfg-if = \"1.0.0\"\ntracing-appender = \"0.2.3\"\nenv_logger = { version = \"0.11.6\", default-features = false }\nregex = \"1.11.1\"\nregex-syntax = { version = \"0.8.5\", default-features = false }\nsimilar = { version = \"2.7.0\" }\nsimilar-asserts = { version = \"1.7.0\" }\nbitmaps = { version = \"3.2.1\", default-features = false }\nconsole-subscriber = { version = \"0.4.1\" }\neither = { version = \"1.15\" }\n\n# Multi threading testing\nloom = { version = \"0.7\" }\nshuttle = { version = \"0.8.1\" }\n\n\n[profile.dev.package.similar]\nopt-level = 3\n\n[profile.release]\ndebug = \"line-tables-only\"\ncodegen-units = 4\npanic = \"abort\"\nlto = \"thin\"\n\n# Official release profile - maximum optimization for published binaries\n# Use for CLI releases, PyPI, npm, Maven, NuGet, etc.\n[profile.release-official]\ninherits = \"release\"\ncodegen-units = 1\nlto = true\n\n# override settings for sdk-kit release profiles in order to reduce size of produced binaries\n# otherwise, some platforms will have enormous libs (150MB+)\n[profile.lib-release]\ninherits = \"release\"\ndebug = false\ncodegen-units = 16\nlto = false\n\n[profile.fuzzing]\ninherits = \"dev\"\nopt-level = 3\ndebug = \"line-tables-only\"\npanic = \"abort\"\n\n[profile.antithesis]\ninherits = \"release\"\ndebug = true\ncodegen-units = 1\npanic = \"abort\"\nlto = true\n\n[profile.bench-profile]\ninherits = \"release\"\ndebug = true\nlto = false               # LTO hides function boundaries\ncodegen-units = 16        # Less cross-unit inlining\n\n[profile.dist]\ninherits = \"release-official\"\n\n[workspace.lints.rust]\nunexpected_cfgs = { level = \"warn\", check-cfg = ['cfg(loom)', 'cfg(shuttle)', 'cfg(antithesis)'] }\n\n[workspace.lints.clippy]\nor_fun_call = \"deny\"\nclear_with_drain = \"deny\"\ncollection_is_never_read = \"deny\"\nnaive_bytecount = \"deny\"\nstable_sort_primitive = \"deny\"\nlarge_stack_frames = \"deny\"\nlarge_types_passed_by_value = \"deny\"\nredundant_clone = \"deny\"\nassigning_clones = \"deny\"\n"
  },
  {
    "path": "Dockerfile.antithesis",
    "content": "FROM lukemathwalker/cargo-chef:0.1.72-rust-1.88.0-slim-bullseye AS chef\nRUN apt update \\\n    && apt install -y git libssl-dev pkg-config\\\n    && apt clean \\\n    && rm -rf /var/lib/apt/lists/*\nWORKDIR /app\n\n#\n# Cache dependencies\n#\n\nFROM chef AS planner\nCOPY ./Cargo.lock ./Cargo.lock\nCOPY ./Cargo.toml ./Cargo.toml\nCOPY ./bindings/dotnet ./bindings/dotnet/\nCOPY ./bindings/java ./bindings/java/\nCOPY ./bindings/javascript ./bindings/javascript/\nCOPY ./bindings/python ./bindings/python/\nCOPY ./bindings/rust ./bindings/rust/\nCOPY ./cli ./cli/\nCOPY ./core ./core/\nCOPY ./extensions ./extensions/\nCOPY ./macros ./macros/\nCOPY ./packages ./packages/\nCOPY ./parser ./parser/\nCOPY ./perf/encryption ./perf/encryption\nCOPY ./perf/throughput/rusqlite ./perf/throughput/rusqlite/\nCOPY ./perf/throughput/turso ./perf/throughput/turso/\nCOPY ./testing/simulator ./testing/simulator/\nCOPY ./sql_generation ./sql_generation\nCOPY ./sqlite3 ./sqlite3/\nCOPY ./testing/stress ./testing/stress/\nCOPY ./sync ./sync/\nCOPY ./testing/sqlite_test_ext ./testing/sqlite_test_ext/\nCOPY ./testing/unreliable-libc ./testing/unreliable-libc/\nCOPY ./tests ./tests/\nCOPY ./testing/sqltests ./testing/sqltests/\nCOPY ./testing/concurrent-simulator ./testing/concurrent-simulator/\nCOPY ./testing/differential-oracle ./testing/differential-oracle/\nCOPY ./sdk-kit ./sdk-kit/\nCOPY ./sdk-kit-macros ./sdk-kit-macros/\nCOPY ./tools ./tools/\nRUN cargo chef prepare --bin turso_stress --recipe-path recipe.json\n\n#\n# Build the project.\n#\n\nFROM chef AS builder\n\nARG antithesis=true\n\nRUN apt-get update && apt-get install -y pip && rm -rf /var/lib/apt/lists/*\nRUN pip install maturin\n\n# Antithesis instrumentation library\nADD https://antithesis.com/assets/instrumentation/libvoidstar.so /opt/antithesis/libvoidstar.so\n\nCOPY --from=planner /app/recipe.json recipe.json\nRUN cargo chef cook --bin turso_stress --release --recipe-path recipe.json\nCOPY --from=planner /app/Cargo.toml /app/Cargo.lock ./\nCOPY --from=planner /app/bindings/dotnet ./bindings/dotnet/\nCOPY --from=planner /app/bindings/java ./bindings/java/\nCOPY --from=planner /app/bindings/javascript ./bindings/javascript/\nCOPY --from=planner /app/bindings/python ./bindings/python/\nCOPY --from=planner /app/bindings/rust ./bindings/rust/\nCOPY --from=planner /app/cli ./cli/\nCOPY --from=planner /app/core ./core/\nCOPY --from=planner /app/extensions ./extensions/\nCOPY --from=planner /app/macros ./macros/\nCOPY --from=planner /app/packages ./packages/\nCOPY --from=planner /app/parser ./parser/\nCOPY --from=planner /app/perf/encryption ./perf/encryption\nCOPY --from=planner /app/perf/throughput/rusqlite ./perf/throughput/rusqlite/\nCOPY --from=planner /app/perf/throughput/turso ./perf/throughput/turso/\nCOPY --from=planner /app/testing/simulator ./testing/simulator/\nCOPY --from=planner /app/sql_generation ./sql_generation\nCOPY --from=planner /app/sqlite3 ./sqlite3/\nCOPY --from=planner /app/testing/stress ./testing/stress/\nCOPY --from=planner /app/sync ./sync/\nCOPY --from=planner /app/testing/sqlite_test_ext ./testing/sqlite_test_ext/\nCOPY --from=planner /app/testing/unreliable-libc ./testing/unreliable-libc/\nCOPY --from=planner /app/tests ./tests/\nCOPY --from=planner /app/testing/sqltests ./testing/sqltests/\nCOPY --from=planner /app/testing/concurrent-simulator ./testing/concurrent-simulator/\nCOPY --from=planner /app/testing/differential-oracle ./testing/differential-oracle\nCOPY --from=planner /app/sdk-kit ./sdk-kit/\nCOPY --from=planner /app/sdk-kit-macros ./sdk-kit-macros/\nCOPY --from=planner /app/tools ./tools/\n\n# Remove cdylib and staticlib from this line: `crate-type = [\"lib\", \"cdylib\", \"staticlib\"]`\n# This is because somehow we lose llvm sanitizer coverage if the crate is not just a lib.\nRUN for f in sdk-kit/Cargo.toml sync/sdk-kit/Cargo.toml; do \\\n        grep -qF 'crate-type = [\"lib\", \"cdylib\", \"staticlib\"]' \"$f\" \\\n        || { echo \"ERROR: expected crate-type not found in $f\"; exit 1; }; \\\n    done \\\n    && sed -i 's/crate-type = \\[\"lib\", \"cdylib\", \"staticlib\"\\]/crate-type = [\"lib\"]/' \\\n        sdk-kit/Cargo.toml sync/sdk-kit/Cargo.toml\n\nRUN if [ \"$antithesis\" = \"true\" ]; then \\\n        cp /opt/antithesis/libvoidstar.so /usr/lib/libvoidstar.so && \\\n        export RUSTFLAGS=\"--cfg=tokio_unstable --cfg=antithesis -Ccodegen-units=1 -Cpasses=sancov-module -Cllvm-args=-sanitizer-coverage-level=3 -Cllvm-args=-sanitizer-coverage-trace-pc-guard -Clink-args=-Wl,--build-id -L/usr/lib/ -lvoidstar\" && \\\n        cargo build --bin turso_stress --profile antithesis; \\\n    else \\\n        cargo build --bin turso_stress --release; \\\n    fi\n\nWORKDIR /app/bindings/python\nRUN maturin build\n\nWORKDIR /app/testing/unreliable-libc\nRUN make\n\n#\n# The final image.\n#\n\nFROM debian:bullseye-slim AS runtime\nRUN apt-get update && apt-get install -y bash curl xz-utils python3 procps sqlite3 bc binutils pip rust-gdb && rm -rf /var/lib/apt/lists/*\nRUN pip install antithesis\n\nWORKDIR /app\nEXPOSE 8080\nCOPY --from=builder /usr/lib/libvoidstar.so* /usr/lib/\nCOPY --from=builder /app/testing/unreliable-libc/unreliable-libc.so /usr/lib/\nCOPY --from=builder /app/target/antithesis/turso_stress /bin/turso_stress\nCOPY --from=builder /app/target/antithesis/turso_stress /symbols\nCOPY testing/stress/docker-entrypoint.sh /bin\nRUN chmod +x /bin/docker-entrypoint.sh\n\nCOPY --from=builder /app/target/wheels/* /tmp\nRUN pip install /tmp/*.whl\n\nWORKDIR /app\nCOPY ./testing/antithesis/bank-test/*.py /opt/antithesis/test/v1/bank-test/\nCOPY ./testing/antithesis/stress-composer/*.py /opt/antithesis/test/v1/stress-composer/\nCOPY ./testing/antithesis/stress /opt/antithesis/test/v1/stress\nCOPY ./testing/antithesis/stress-io_uring /opt/antithesis/test/v1/stress-io_uring\nCOPY ./testing/antithesis/stress-mvcc /opt/antithesis/test/v1/stress-mvcc\nCOPY ./testing/antithesis/stress-io_uring-mvcc /opt/antithesis/test/v1/stress-io_uring-mvcc\nCOPY ./testing/antithesis/stress-unreliable /opt/antithesis/test/v1/stress-unreliable\nRUN chmod 777 -R /opt/antithesis/test/v1\n\nRUN mkdir /opt/antithesis/catalog\nRUN ln -s /opt/antithesis/test/v1/bank-test/*.py /opt/antithesis/catalog\n\nENV RUST_BACKTRACE=1\n\nENTRYPOINT [\"/bin/docker-entrypoint.sh\"]\n"
  },
  {
    "path": "Dockerfile.cli",
    "content": "FROM rust:1.88.0 as builder\n\nWORKDIR /app\n\n# Copy the actual source code\nCOPY . .\n\n# Build the CLI binary\nRUN cargo build --release --package turso_cli\n\n# Runtime stage\nFROM rust:1.88.0-slim\n\nWORKDIR /app\n\n# Copy the built binary\nCOPY --from=builder /app/target/release/tursodb /usr/local/bin/\n\n# Set the entrypoint\nENTRYPOINT [\"tursodb\"]\n"
  },
  {
    "path": "LICENSE.md",
    "content": "MIT License\n\nCopyright 2024 the Turso authors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "MINIMUM_RUST_VERSION := 1.73.0\nCURRENT_RUST_VERSION := $(shell rustc -V | sed -E 's/rustc ([0-9]+\\.[0-9]+\\.[0-9]+).*/\\1/')\nCURRENT_RUST_TARGET := $(shell rustc -vV | grep host | cut -d ' ' -f 2)\nRUSTUP := $(shell command -v rustup 2> /dev/null)\nUNAME_S := $(shell uname -s)\nMINIMUM_TCL_VERSION := 8.6\n\n# Executable used to execute the compatibility tests.\nSQLITE_EXEC ?= scripts/limbo-sqlite3\nRUST_LOG := off\n\nall: check-rust-version build \n.PHONY: all\n\ninstall-sqlite:\n\t./scripts/install-sqlite3.sh\n.PHONY: install-sqlite\n\ncheck-rust-version:\n\t@echo \"Checking Rust version...\"\n\t@if [ \"$(shell printf '%s\\n' \"$(MINIMUM_RUST_VERSION)\" \"$(CURRENT_RUST_VERSION)\" | sort -V | head -n1)\" = \"$(CURRENT_RUST_VERSION)\" ]; then \\\n\t\techo \"Rust version greater than $(MINIMUM_RUST_VERSION) is required. Current version is $(CURRENT_RUST_VERSION).\"; \\\n\t\tif [ -n \"$(RUSTUP)\" ]; then \\\n\t\t\techo \"Updating Rust...\"; \\\n\t\t\trustup update stable; \\\n\t\telse \\\n\t\t\techo \"Please update Rust manually to a version greater than $(MINIMUM_RUST_VERSION).\"; \\\n\t\t\texit 1; \\\n\t\tfi; \\\n\telse \\\n\t\techo \"Rust version $(CURRENT_RUST_VERSION) is acceptable.\"; \\\n\tfi\n.PHONY: check-rust-version\n\ncheck-tcl-version:\n\t@printf '%s\\n' \\\n\t\t'set need \"$(MINIMUM_TCL_VERSION)\"' \\\n\t\t'set have [info patchlevel]' \\\n\t\t'if {[package vcompare $$have $$need] < 0} {' \\\n\t\t'    puts stderr \"tclsh $$have found — need $$need+\"' \\\n\t\t'    exit 1' \\\n\t\t'}' \\\n\t| tclsh\n.PHONY: check-tcl-version\n\nbuild: check-rust-version\n\tcargo build\n.PHONY: build\n\nturso-c:\n\tcargo cbuild\n.PHONY: turso-c\n\nuv-sync:\n\tuv sync --all-packages\n.PHONE: uv-sync\n\nuv-sync-test:\n\tuv sync --all-extras --dev --package turso_test\n.PHONE: uv-sync\n\ntest: build uv-sync-test test-compat test-sqlite3 test-shell test-memory test-write test-update test-constraint test-collate test-extensions test-runner test-runner-js test-runner-cli\n.PHONY: test\n\ntest-runner:\n\t@make -C testing/sqltests run\n.PHONY: test-runner\n\ntest-runner-js:\n\t@make -C testing/sqltests run-js\n.PHONY: test-runner-js\n\ntest-runner-cli:\n\t@make -C testing/sqltests run-cli\n.PHONY: test-runner-cli\n\ntest-extensions: build uv-sync-test\n\tRUST_LOG=$(RUST_LOG) uv run --project limbo_test test-extensions\n.PHONY: test-extensions\n\ntest-shell: build uv-sync-test\n\tRUST_LOG=$(RUST_LOG) SQLITE_EXEC=$(SQLITE_EXEC) uv run --project limbo_test test-shell\n.PHONY: test-shell\n\ntest-compat: check-tcl-version\n\tRUST_LOG=$(RUST_LOG) SQLITE_EXEC=$(SQLITE_EXEC) ./testing/system/all.test\n\ntest-single: check-tcl-version\n\t@if [ -z \"$(TEST)\" ]; then \\\n\t\techo \"Usage: make test-single TEST=path/to/test.test\"; \\\n\t\texit 1; \\\n\tfi\n\tRUST_LOG=$(RUST_LOG) SQLITE_EXEC=$(SQLITE_EXEC) ./testing/system/$(TEST)\n.PHONY: test-single\n.PHONY: test-compat\n\nreset-db:\n\t./scripts/clone_test_db.sh\n.PHONY: reset-db\n\ntest-fuzz:\n\tRUST_LOG=$(RUST_LOG) cargo test -p core_tester --release -- fuzz\n\tRUST_LOG=$(RUST_LOG) cargo run -p differential-fuzzer --bin custom_types_fuzzer -- --seed $(or $(SEED), $$(date +%s)) -n $(or $(N),200) -t $(or $(TABLES),2)\n.PHONY: test-fuzz\n\ntest-sqlite3: reset-db\n\tcargo test -p turso_sqlite3 --test compat -- --test-threads=1\n\t./scripts/clone_test_db.sh\n\tcargo test -p turso_sqlite3 --test compat --features sqlite3 -- --test-threads=1\n.PHONY: test-sqlite3\n\ntest-memory: build uv-sync-test\n\tRUST_LOG=$(RUST_LOG) SQLITE_EXEC=$(SQLITE_EXEC) uv run --project limbo_test test-memory\n.PHONY: test-memory\n\ntest-write: build uv-sync-test\n\t@if [ \"$(SQLITE_EXEC)\" != \"scripts/limbo-sqlite3\" ]; then \\\n\t\tRUST_LOG=$(RUST_LOG) SQLITE_EXEC=$(SQLITE_EXEC) uv run --project limbo_test test-write; \\\n\telse \\\n\t\techo \"Skipping test-write: SQLITE_EXEC does not have indexes scripts/limbo-sqlite3\"; \\\n\tfi\n.PHONY: test-write\n\ntest-update: build uv-sync-test\n\t@if [ \"$(SQLITE_EXEC)\" != \"scripts/limbo-sqlite3\" ]; then \\\n\t\tRUST_LOG=$(RUST_LOG) SQLITE_EXEC=$(SQLITE_EXEC) uv run --project limbo_test test-update; \\\n\telse \\\n\t\techo \"Skipping test-update: SQLITE_EXEC does not have indexes scripts/limbo-sqlite3\"; \\\n\tfi\n.PHONY: test-update\n\ntest-collate: build uv-sync-test\n\t@if [ \"$(SQLITE_EXEC)\" != \"scripts/limbo-sqlite3\" ]; then \\\n\t\tRUST_LOG=$(RUST_LOG) SQLITE_EXEC=$(SQLITE_EXEC) uv run --project limbo_test test-collate; \\\n\telse \\\n\t\techo \"Skipping test-collate: SQLITE_EXEC does not have indexes scripts/limbo-sqlite3\"; \\\n\tfi\n.PHONY: test-collate\n\ntest-constraint: build uv-sync-test\n\t@if [ \"$(SQLITE_EXEC)\" != \"scripts/limbo-sqlite3\" ]; then \\\n\t\tRUST_LOG=$(RUST_LOG) SQLITE_EXEC=$(SQLITE_EXEC) uv run --project limbo_test test-constraint; \\\n\telse \\\n\t\techo \"Skipping test-constraint: SQLITE_EXEC does not have indexes scripts/limbo-sqlite3\"; \\\n\tfi\n.PHONY: test-constraint\n\nbench-vfs: uv-sync-test build-release\n\tRUST_LOG=$(RUST_LOG) uv run --project limbo_test bench-vfs \"$(SQL)\" \"$(N)\"\n\nbench-sqlite: uv-sync-test build-release\n\tRUST_LOG=$(RUST_LOG) uv run --project limbo_test bench-sqlite \"$(VFS)\" \"$(SQL)\" \"$(N)\"\n\nclickbench:\n\t./perf/clickbench/benchmark.sh\n.PHONY: clickbench\n\nbuild-release: check-rust-version\n\tcargo build --bin tursodb --release --features=tracing_release\n\nbench-exclude-tpc-h:\n\t@benchmarks=$$(cargo bench --bench 2>&1 | grep -A 1000 '^Available bench targets:' | grep -v '^Available bench targets:' | grep -v '^ *$$' | grep -v 'tpc_h_benchmark' | xargs -I {} printf -- \"--bench %s \" {}); \\\n\tif [ -z \"$$benchmarks\" ]; then \\\n\t\techo \"No benchmarks found (excluding tpc_h_benchmark).\"; \\\n\t\texit 1; \\\n\telse \\\n\t\tcargo bench $$benchmarks --features bench; \\\n\tfi\n.PHONY: bench-exclude-tpc-h\n\ncodspeed-build-bench-exclude-tpc-h:\n\t@benchmarks=$$(cargo bench --bench 2>&1 | grep -A 1000 '^Available bench targets:' | grep -v '^Available bench targets:' | grep -v '^ *$$' | grep -v 'tpc_h_benchmark' | xargs -I {} printf -- \"--bench %s \" {}); \\\n\tif [ -z \"$$benchmarks\" ]; then \\\n\t\techo \"No benchmarks found (excluding tpc_h_benchmark).\"; \\\n\t\texit 1; \\\n\telse \\\n\t\tcargo codspeed build $$benchmarks --features codspeed; \\\n\tfi\n.PHONY: codspeed-build-bench-exclude-tpc-h\n\ndocker-cli-build:\n\tdocker build -f Dockerfile.cli -t turso-cli .\n\ndocker-cli-run:\n\tdocker run -it -v ./:/app turso-cli\n\nmerge-pr:\nifndef PR\n\t$(error PR is required. Usage: make merge-pr PR=123)\nendif\n\t@echo \"Setting up environment for PR merge...\"\n\t@if [ -z \"$(GITHUB_REPOSITORY)\" ]; then \\\n\t\tREPO=$$(git remote get-url origin | sed -E 's|.*github\\.com[:/]([^/]+/[^/]+?)(\\.git)?$$|\\1|'); \\\n\t\tif [ -z \"$$REPO\" ]; then \\\n\t\t\techo \"Error: Could not detect repository from git remote\"; \\\n\t\t\texit 1; \\\n\t\tfi; \\\n\t\texport GITHUB_REPOSITORY=\"$$REPO\"; \\\n\telse \\\n\t\texport GITHUB_REPOSITORY=\"$(GITHUB_REPOSITORY)\"; \\\n\tfi; \\\n\techo \"Repository: $$REPO\"; \\\n\tAUTH=$$(gh auth status); \\\n\tif [ -z \"$$AUTH\" ]; then \\\n\t\techo \"auth: $$AUTH\"; \\\n\t\techo \"GitHub CLI not authenticated. Starting login process...\"; \\\n\t\tgh auth login --scopes repo,workflow; \\\n\telse \\\n\t\tif ! echo \"$$AUTH\" | grep -q \"workflow\"; then \\\n\t\t\techo \"Warning: 'workflow' scope not detected. You may need to re-authenticate if merging PRs with workflow changes.\"; \\\n\t\t\techo \"Run: gh auth refresh -s repo,workflow\"; \\\n\t\tfi; \\\n\tfi; \\\n\tif [ \"$(LOCAL)\" = \"1\" ]; then \\\n\t    echo \"merging PR #$(PR) locally\"; \\\n\t\tuv run scripts/merge-pr.py $(PR) --local; \\\n\telse \\\n\t    echo \"merging PR #$(PR) on GitHub\"; \\\n\t\tuv run scripts/merge-pr.py $(PR); \\\n\tfi\n\n.PHONY: merge-pr\n\nsim-schema: \n\tmkdir -p  simulator/configs/custom\n\tcargo run -p limbo_sim -- print-schema > simulator/configs/custom/profile-schema.json\n\ntest-shuttle:\n\tRUSTFLAGS='--cfg tokio_unstable --cfg shuttle' cargo nextest run --profile shuttle --package turso_core\n\ntest-loom:\n\tRUSTFLAGS='--cfg tokio_unstable --cfg loom' cargo nextest run --profile loom --package turso_core\n"
  },
  {
    "path": "NOTICE.md",
    "content": "Limbo\n=======\n\nPlease visit our GitHub for more information:\n\n* https://github.com/tursodatabase/turso\n\nDependencies\n============\n\nThis product depends on Error Prone, distributed by the Error Prone project:\n\n* License: licenses/bindings/java/assertj-license.md (Apache License v2.0)\n* Homepage: https://github.com/google/error-prone\n\nThis product depends on AssertJ, distributed by the AssertJ authors:\n\n* License: licenses/bindings/java/errorprone-license.md (Apache License v2.0)\n* Homepage: https://joel-costigliola.github.io/assertj/\n\nThis product depends on logback, distributed by the logback authors:\n\n* License: licenses/bindings/java/logback-license.md (Apache License v2.0)\n* Homepage: https://github.com/qos-ch/logback?tab=License-1-ov-file\n\nThis product depends on spotless, distributed by the diffplug authors:\n\n* License: licenses/bindings/java/spotless-license.md (Apache License v2.0)\n* Homepage: https://github.com/diffplug/spotless\n\n\nThis project depends on ipnetwork, distributed by the ipnetwork project:\n\n* License: licenses/extensions/ipnetwork-apache-license.md (Apache License v2.0)\n* License: licenses/extensions/ipnetwork-mit-license.md (MIT License)\n* Homepage: https://github.com/achanda/ipnetwork\n\nThis project depends on libm, distributed by the rust-lang project:\n\n* License: licenses/extensions/libm-apache-license.md (Apache License v2.0)\n* License: licenses/extensions/libm-mit-license.md (MIT License)\n* Homepage: https://github.com/rust-lang/libm\n\nThis project depends on pastey, distributed by the pastey authors:\n\n* License: licenses/core/pastey-apache-license.md (Apache License v2.0)\n* License: licenses/core/pastey-mit-license.md (MIT License)\n* Homepage: https://github.com/AS1100K/pastey\n\nThis project depends on windows-sys, distributed by the Microsoft:\n\n* License: licenses/core/windows-apache.license.md (Apache License v2.0)\n* License: licenses/core/windows-mit-license.md (MIT License)\n* Homepage: https://github.com/microsoft/windows-rs\n\nThis project depends on SQLAlchemy, distributed by the SQLAlchemy authors:\n\n* License: licenses/bindings/python/sqlalchemy-mit-license.md (MIT License)\n* Homepage: https://github.com/sqlalchemy/sqlalchemy\n"
  },
  {
    "path": "PERF.md",
    "content": "# Performance Testing\n\n## Mobibench\n\n1. Clone the source repository of Mobibench fork for Turso:\n\n```console\ngit clone git@github.com:penberg/Mobibench.git\n```\n\n2. Build Mobibench:\n\n```console\ncd Mobibench/shell\nLIBS=\"../../target/release/libturso_sqlite3.a -lm\" make\nmv mobibench mobibench-turso\n```\n\n3. Run Mobibench:\n\n(easiest way is to `cd` into `target/release`)\n\n```console\n# with strace, from target/release\n\nstrace -f -c ../../Mobibench/shell/mobibench-turso -f 1024 -r 4 -a 0 -y 0 -t 1 -d 0 -n 10000 -j 3 -s 2 -T 3 -D 1\n\n\n./mobibench -p <benchmark-directory> -n 1000 -d 0 -j 4\n```\n\n\n## Clickbench\n\nWe have a modified version of the Clickbench benchmark script that can be run with:\n\n```shell\nmake clickbench\n```\n\nThis will build Turso in release mode, create a database, and run the benchmarks with a small subset of the Clickbench dataset.\nIt will run the queries for both Turso and SQLite, and print the results.\n\n\n## Comparing VFS's/IO Back-ends (io_uring | syscall)\n\n```shell\nmake bench-vfs SQL=\"select * from users;\" N=500\n```\n\nThe naive script will build and run limbo in release mode and execute the given SQL (against a copy of the `testing/testing.db` file)\n`N` times with each `vfs`. This is not meant to be a definitive or thorough performance benchmark but serves to compare the two.\n\n\n## TPC-H\n\non linux if you are using `tlp` to manage power settings, you may want to disable it while running the TPC-H benchmark as it can affect performance. consider changing swap to `swapoff -a`\n\nRun the benchmark script:\n\n```shell\n./perf/tpc-h/benchmark.sh\n```\n\n"
  },
  {
    "path": "Pipfile",
    "content": "[[source]]\nurl = \"https://pypi.org/simple\"\nverify_ssl = true\nname = \"pypi\"\n\n[packages]\nfaker = \"26.0.0\"\n\n[dev-packages]\n\n[requires]\npython_version = \"3.11\"\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n  <img src=\"assets/turso.png\" alt=\"Turso Database\" width=\"800\"/>\n  <h1 align=\"center\">Turso Database</h1>\n</p>\n\n<p align=\"center\">\n  An in-process SQL database, compatible with SQLite.\n</p>\n\n<p align=\"center\">\n  <a title=\"Build Status\" target=\"_blank\" href=\"https://github.com/tursodatabase/turso/actions/workflows/rust.yml\"><img src=\"https://img.shields.io/github/actions/workflow/status/tursodatabase/turso/rust.yml?style=flat-square\"></a>\n  <a title=\"Releases\" target=\"_blank\" href=\"https://github.com/tursodatabase/turso/releases\"><img src=\"https://img.shields.io/github/release/tursodatabase/turso?style=flat-square&color=9CF\"></a>\n  <a title=\"Rust\" target=\"_blank\" href=\"https://crates.io/crates/turso\"><img alt=\"Crate\" src=\"https://img.shields.io/crates/v/turso\"></a>\n  <a title=\"JavaScript\" target=\"_blank\" href=\"https://www.npmjs.com/package/@tursodatabase/database\"><img alt=\"NPM\" src=\"https://img.shields.io/npm/v/@tursodatabase/database\"></a>\n  <a title=\"Python\" target=\"_blank\" href=\"https://pypi.org/project/pyturso/\"><img alt=\"PyPI\" src=\"https://img.shields.io/pypi/v/pyturso\"></a>\n  <a title=\"Java\" target=\"_blank\" href=\"https://central.sonatype.com/artifact/tech.turso/turso\"><img alt=\"Maven Central\" src=\"https://img.shields.io/maven-central/v/tech.turso/turso\"></a>\n  <a title=\"MIT\" target=\"_blank\" href=\"https://github.com/tursodatabase/turso/blob/main/LICENSE.md\"><img src=\"http://img.shields.io/badge/license-MIT-orange.svg?style=flat-square\"></a>\n  <br>\n  <a title=\"GitHub Pull Requests\" target=\"_blank\" href=\"https://github.com/tursodatabase/turso/pulls\"><img src=\"https://img.shields.io/github/issues-pr-closed/tursodatabase/turso.svg?style=flat-square&color=FF9966\"></a>\n  <a title=\"GitHub Commits\" target=\"_blank\" href=\"https://github.com/tursodatabase/turso/commits/main\"><img src=\"https://img.shields.io/github/commit-activity/m/tursodatabase/turso.svg?style=flat-square\"></a>\n  <a title=\"Last Commit\" target=\"_blank\" href=\"https://github.com/tursodatabase/turso/commits/main\"><img src=\"https://img.shields.io/github/last-commit/tursodatabase/turso.svg?style=flat-square&color=FF9900\"></a>\n</p>\n<p align=\"center\">\n  <a title=\"Developer's Discord\" target=\"_blank\" href=\"https://discord.gg/jgjmyYgHwB\"><img alt=\"Chat with the Core Developers on Discord\" src=\"https://img.shields.io/discord/1258658826257961020?label=Discord&logo=Discord&style=social&label=Core%20Developers\"></a>\n</p>\n<p align=\"center\">\n  <a title=\"Users's Discord\" target=\"_blank\" href=\"https://tur.so/discord\"><img alt=\"Chat with other users of Turso (and Turso Cloud) on Discord\" src=\"https://img.shields.io/discord/933071162680958986?label=Discord&logo=Discord&style=social&label=Users\"></a>\n</p>\n\n---\n\n## About\n\nTurso Database is an in-process SQL database written in Rust, compatible with SQLite.\n\n> **⚠️ Warning:** This software is in BETA. It may still contain bugs and unexpected behavior. Use caution with production data and ensure you have backups.\n\n## Features and Roadmap\n\n* **SQLite compatibility** for SQL dialect, file formats, and the C API [see [document](COMPAT.md) for details]\n* **Change data capture (CDC)** for real-time tracking of database changes.\n* **Multi-language support** for\n  * [Go](bindings/go)\n  * [JavaScript](bindings/javascript)\n  * [Java](bindings/java)\n  * [Python](bindings/python)\n  * [Rust](bindings/rust)\n  * [WebAssembly](bindings/javascript)\n* **Asynchronous I/O** support on Linux with `io_uring`\n* **Cross-platform** support for Linux, macOS, Windows and browsers (through WebAssembly)\n* **Vector support** support including exact search and vector manipulation\n* **Improved schema management** including extended `ALTER` support and faster schema changes.\n\nThe database has the following experimental features:\n\n* **`BEGIN CONCURRENT`** for improved write throughput using multi-version concurrency control (MVCC).\n* **Encryption at rest** for protecting the data locally.\n* **Incremental computation** using DBSP for incremental view maintenance and query subscriptions.\n* **Full-Text-Search** powered by the awesome [tantivy](https://github.com/quickwit-oss/tantivy) library\n\nThe following features are on our current roadmap:\n\n* **Vector indexing** for fast approximate vector search, similar to [libSQL vector search](https://turso.tech/vector).\n\n## Getting Started\n\nPlease see the [Turso Database Manual](docs/manual.md) for more information.\n\n<details>\n<summary>💻 Command Line</summary>\n<br>\nYou can install the latest `turso` release with:\n\n```shell\ncurl --proto '=https' --tlsv1.2 -LsSf \\\n  https://github.com/tursodatabase/turso/releases/latest/download/turso_cli-installer.sh | sh\n```\n\nThen launch the interactive shell:\n\n```shell\n$ tursodb\n```\n\nThis will start the Turso interactive shell where you can execute SQL statements:\n\n```console\nTurso\nEnter \".help\" for usage hints.\nConnected to a transient in-memory database.\nUse \".open FILENAME\" to reopen on a persistent database\nturso> CREATE TABLE users (id INT, username TEXT);\nturso> INSERT INTO users VALUES (1, 'alice');\nturso> INSERT INTO users VALUES (2, 'bob');\nturso> SELECT * FROM users;\n1|alice\n2|bob\n```\n\nYou can also build and run the latest development version with:\n\n```shell\ncargo run\n```\n\nIf you like docker, we got you covered. Simply run this in the root folder:\n\n```bash\nmake docker-cli-build && \\\nmake docker-cli-run\n```\n\n</details>\n\n<details>\n<summary>🦀 Rust</summary>\n<br>\n\n```console\ncargo add turso\n```\n\nExample usage:\n\n```rust\nlet db = Builder::new_local(\"sqlite.db\").build().await?;\nlet conn = db.connect()?;\n\nlet res = conn.query(\"SELECT * FROM users\", ()).await?;\n```\n</details>\n\n<details>\n<summary>✨ JavaScript</summary>\n<br>\n\n```console\nnpm i @tursodatabase/database\n```\n\nExample usage:\n\n```js\nimport { connect } from '@tursodatabase/database';\n\nconst db = await connect('sqlite.db');\nconst stmt = db.prepare('SELECT * FROM users');\nconst users = stmt.all();\nconsole.log(users);\n```\n</details>\n\n<details>\n<summary>🐍 Python</summary>\n<br>\n\n```console\nuv pip install pyturso\n```\n\nExample usage:\n\n```python\nimport turso\n\ncon = turso.connect(\"sqlite.db\")\ncur = con.cursor()\nres = cur.execute(\"SELECT * FROM users\")\nprint(res.fetchone())\n```\n</details>\n\n<details>\n<summary>🦫 Go</summary>\n<br>\n\n```console\ngo get turso.tech/database/tursogo\ngo install turso.tech/database/tursogo\n```\n\nExample usage:\n```go\nimport (\n    \"database/sql\"\n    _ \"turso.tech/database/tursogo\"\n)\n\nconn, _ = sql.Open(\"turso\", \"sqlite.db\")\ndefer conn.Close()\n\nstmt, _ := conn.Prepare(\"select * from users\")\ndefer stmt.Close()\n\nrows, _ = stmt.Query()\nfor rows.Next() {\n    var id int\n    var username string\n    _ := rows.Scan(&id, &username)\n    fmt.Printf(\"User: ID: %d, Username: %s\\n\", id, username)\n}\n```\n</details>\n\n<details>\n\n<summary>☕️ Java</summary>\n<br>\n\nWe integrated Turso Database into JDBC. For detailed instructions on how to use Turso Database with java, please refer to\nthe [README.md under bindings/java](bindings/java/README.md).\n</details>\n\n<details>\n<summary>🤖 MCP Server Mode</summary>\n<br>\n\n\nThe Turso CLI includes a built-in [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that allows AI assistants to interact with your databases.\n\nStart the MCP server with:\n\n```shell\ntursodb your_database.db --mcp\n```\n\n### Configuration\n\nAdd Turso to your MCP client configuration:\n\n```json\n{\n  \"mcpServers\": {\n    \"turso\": {\n      \"command\": \"/path/to/.turso/tursodb\",\n      \"args\": [\"/path/to/your/database.db\", \"--mcp\"]\n    }\n  }\n}\n```\n\n### Available Tools\n\nThe MCP server provides nine tools for database interaction:\n\n1. **`open_database`** - Open a new database\n2. **`current_database`** - Describe the current database\n3. **`list_tables`** - List all tables in the database\n4. **`describe_table`** - Describe the structure of a specific table\n5. **`execute_query`** - Execute read-only SELECT queries\n6. **`insert_data`** - Insert new data into tables\n7. **`update_data`** - Update existing data in tables\n8. **`delete_data`** - Delete data from tables\n9. **`schema_change`** - Execute schema modification statements (CREATE TABLE, ALTER TABLE, DROP TABLE)\n\nOnce connected, you can ask your AI assistant:\n\n- \"Show me all tables in the database\"\n- \"What's the schema for the users table?\"\n- \"Find all posts with more than 100 upvotes\"\n- \"Insert a new user with name 'Alice' and email 'alice@example.com'\"\n\n### MCP Clients\n\n<details>\n<summary>Claude Code</summary>\n\nIf you're using [Claude Code](https://claude.ai/code), you can easily connect to your Turso MCP server using the built-in MCP management commands:\n\n#### Quick Setup\n\n1. **Add the MCP server** to Claude Code:\n\n   ```bash\n   claude mcp add my-database -- tursodb ./path/to/your/database.db --mcp\n   ```\n\n2. **Restart Claude Code** to activate the connection\n\n3. **Start querying** your database through natural language!\n\n#### Command Breakdown\n\n```bash\nclaude mcp add my-database -- tursodb ./path/to/your/database.db --mcp\n#              ↑            ↑       ↑                           ↑\n#              |            |       |                           |\n#              Name         |       Database path               MCP flag\n#                          Separator\n```\n\n- **`my-database`** - Choose any name for your MCP server\n- **`--`** - Required separator between Claude options and your command\n- **`tursodb`** - The Turso database CLI\n- **`./path/to/your/database.db`** - Path to your SQLite database file\n- **`--mcp`** - Enables MCP server mode\n\n#### Example Usage\n\n```bash\n# For a local project database\ncd /your/project\nclaude mcp add my-project-db -- tursodb ./data/app.db --mcp\n\n# For an absolute path\nclaude mcp add analytics-db -- tursodb /Users/you/databases/analytics.db --mcp\n\n# For a specific project (local scope)\nclaude mcp add project-db --local -- tursodb ./database.db --mcp\n```\n\n#### Managing MCP Servers\n\n```bash\n# List all configured MCP servers\nclaude mcp list\n\n# Get details about a specific server\nclaude mcp get my-database\n\n# Remove an MCP server\nclaude mcp remove my-database\n```\n\n</details>\n\n<details>\n<summary>Claude Desktop</summary>\n\nFor Claude Desktop, add the configuration to your `claude_desktop_config.json` file:\n\n```json\n{\n  \"mcpServers\": {\n    \"turso\": {\n      \"command\": \"/path/to/.turso/tursodb\",\n      \"args\": [\"./path/to/your/database.db.db\", \"--mcp\"]\n    }\n  }\n}\n```\n\n</details>\n\n<details>\n<summary>Cursor</summary>\n\nFor Cursor, configure MCP in your settings:\n\n1. Open Cursor settings\n2. Navigate to Extensions → MCP\n3. Add a new server with:\n   - **Name**: `turso`\n   - **Command**: `/path/to/.turso/tursodb`\n   - **Args**: `[\"./path/to/your/database.db.db\", \"--mcp\"]`\n\nAlternatively, you can add it to your Cursor configuration file directly.\n\n</details>\n\n### Direct JSON-RPC Usage\n\nThe MCP server runs as a single process that handles multiple JSON-RPC requests over stdin/stdout. Here's how to interact with it directly:\n\n#### Example with In-Memory Database\n\n```bash\ncat << 'EOF' | tursodb --mcp\n{\"jsonrpc\": \"2.0\", \"id\": 1, \"method\": \"initialize\", \"params\": {\"protocolVersion\": \"2024-11-05\", \"capabilities\": {}, \"clientInfo\": {\"name\": \"client\", \"version\": \"1.0\"}}}\n{\"jsonrpc\": \"2.0\", \"id\": 2, \"method\": \"tools/call\", \"params\": {\"name\": \"schema_change\", \"arguments\": {\"query\": \"CREATE TABLE users (id INTEGER, name TEXT, email TEXT)\"}}}\n{\"jsonrpc\": \"2.0\", \"id\": 3, \"method\": \"tools/call\", \"params\": {\"name\": \"list_tables\", \"arguments\": {}}}\n{\"jsonrpc\": \"2.0\", \"id\": 4, \"method\": \"tools/call\", \"params\": {\"name\": \"insert_data\", \"arguments\": {\"query\": \"INSERT INTO users VALUES (1, 'Alice', 'alice@example.com')\"}}}\n{\"jsonrpc\": \"2.0\", \"id\": 5, \"method\": \"tools/call\", \"params\": {\"name\": \"execute_query\", \"arguments\": {\"query\": \"SELECT * FROM users\"}}}\nEOF\n```\n\n#### Example with Existing Database\n\n```bash\n# Working with an existing database file\ncat << 'EOF' | tursodb mydb.db --mcp\n{\"jsonrpc\": \"2.0\", \"id\": 1, \"method\": \"initialize\", \"params\": {\"protocolVersion\": \"2024-11-05\", \"capabilities\": {}, \"clientInfo\": {\"name\": \"client\", \"version\": \"1.0\"}}}\n{\"jsonrpc\": \"2.0\", \"id\": 2, \"method\": \"tools/call\", \"params\": {\"name\": \"list_tables\", \"arguments\": {}}}\nEOF\n```\n\n</details>\n\n## Contributing\n\nWe'd love to have you contribute to Turso Database! Please check out the [contribution guide] to get started.\n\n### Found a data corruption bug? Get up to $1,000.00\n\nSQLite is loved because it is the most reliable database in the world. The next evolution of SQLite has\nto match or surpass this level of reliability. Turso is built with [Deterministic Simulation Testing](testing/simulator/README.md/)\nfrom the ground up, and is also tested by [Antithesis](https://antithesis.com).\n\nEven during Alpha, if you find a bug that leads to a data corruption and demonstrate\nhow our simulator failed to catch it, you can get up to $1,000.00. As the project matures we will\nincrease the size of the prize, and the scope of the bugs.\n\nList of rewarded cases:\n\n* B-Tree interior cell replacement issue in btrees with depth >=3 ([#2106](https://github.com/tursodatabase/turso/issues/2106))\n* Don't allow autovacuum to be flipped on non-empty databases ([#3830](https://github.com/tursodatabase/turso/pull/3830))\n* Self-insert with nested subquery generates corrupt data ([#3436](https://github.com/tursodatabase/turso/pull/3436))\n* Ptrmap data corruption with pre-initialized autovacuum database ([#3894](https://github.com/tursodatabase/turso/pull/3894))\n* WAL corruption on statement rollback with constraint violation ([#4493](https://github.com/tursodatabase/turso/pull/4493))\n\nMore details [here](https://turso.algora.io).\n\nTurso core staff are not eligible.\n\n## FAQ\n\n### Is Turso Database ready for production use?\n\nTurso Database is currently under heavy development and is **not** ready for production use.\n\n### How is Turso Database different from Turso's libSQL?\n\nTurso Database is a project to build the next evolution of SQLite in Rust, with a strong open contribution focus and features like native async support, vector search, and more. The libSQL project is also an attempt to evolve SQLite in a similar direction, but through a fork rather than a rewrite.\n\nRewriting SQLite in Rust started as an unassuming experiment, and due to its incredible success, replaces libSQL as our intended direction. At this point, libSQL is production ready, Turso Database is not - although it is evolving rapidly. More details [here](https://turso.tech/blog/we-will-rewrite-sqlite-and-we-are-going-all-in).\n\n## Publications\n\n* Pekka Enberg, Sasu Tarkoma, Jon Crowcroft Ashwin Rao (2024). Serverless Runtime / Database Co-Design With Asynchronous I/O. In _EdgeSys ‘24_. [[PDF]](https://penberg.org/papers/penberg-edgesys24.pdf)\n* Pekka Enberg, Sasu Tarkoma, and Ashwin Rao (2023). Towards Database and Serverless Runtime Co-Design. In _CoNEXT-SW ’23_. [[PDF](https://penberg.org/papers/penberg-conext-sw-23.pdf)] [[Slides](https://penberg.org/papers/penberg-conext-sw-23-slides.pdf)]\n\n## License\n\nThis project is licensed under the [MIT license].\n\n### Contribution\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in Turso Database by you, shall be licensed as MIT, without any additional\nterms or conditions.\n\n[contribution guide]: CONTRIBUTING.md\n[MIT license]: LICENSE.md\n\n## Partners\n\nThanks to all the partners of Turso!\n\n<a href=\"https://antithesis.com/\"><img src=\"assets/antithesis.jpg\" width=\"400\"></a>\n\n<a href=\"https://blacksmith.sh\"><img src=\"assets/blacksmith.svg\" width=\"400\"></a>\n\n<a href=\"https://nyrkio.com/\"><img src=\"assets/turso-nyrkio.png\" width=\"400\"></a>\n\n## Contributors\n\nThanks to all the contributors to Turso Database!\n\n<a href=\"https://github.com/tursodatabase/turso/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=tursodatabase/turso\" />\n</a>\n"
  },
  {
    "path": "bindings/dotnet/.gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n##\n## Get latest from `dotnet new gitignore`\n\n# dotenv files\n.env\n\n# User-specific files\n*.rsuser\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Mono auto generated files\nmono_crash.*\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\n[Ww][Ii][Nn]32/\n[Aa][Rr][Mm]/\n[Aa][Rr][Mm]64/\nbld/\n[Bb]in/\n[Oo]bj/\n[Ll]og/\n[Ll]ogs/\n\n# Visual Studio 2015/2017 cache/options directory\n.vs/\n# Uncomment if you have tasks that create the project's static files in wwwroot\n#wwwroot/\n\n# Visual Studio 2017 auto generated files\nGenerated\\ Files/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUnit\n*.VisualState.xml\nTestResult.xml\nnunit-*.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n# Benchmark Results\nBenchmarkDotNet.Artifacts/\n\n# .NET\nproject.lock.json\nproject.fragment.lock.json\nartifacts/\n\n# Tye\n.tye/\n\n# ASP.NET Scaffolding\nScaffoldingReadMe.txt\n\n# StyleCop\nStyleCopReport.xml\n\n# Files built by Visual Studio\n*_i.c\n*_p.c\n*_h.h\n*.ilk\n*.meta\n*.obj\n*.iobj\n*.pch\n*.pdb\n*.ipdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*_wpftmp.csproj\n*.log\n*.tlog\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n*.VC.db\n*.VC.VC.opendb\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# Visual Studio Trace Files\n*.e2e\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# AxoCover is a Code Coverage Tool\n.axoCover/*\n!.axoCover/settings.json\n\n# Coverlet is a free, cross platform Code Coverage Tool\ncoverage*.json\ncoverage*.xml\ncoverage*.info\n\n# Visual Studio code coverage results\n*.coverage\n*.coveragexml\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# Note: Comment the next line if you want to checkin your web deploy settings,\n# but database connection strings (with potential passwords) will be unencrypted\n*.pubxml\n*.publishproj\n\n# Microsoft Azure Web App publish settings. Comment the next line if you want to\n# checkin your Azure Web App publish settings, but sensitive information contained\n# in these scripts will be unencrypted\nPublishScripts/\n\n# NuGet Packages\n*.nupkg\n# NuGet Symbol Packages\n*.snupkg\n# The packages folder can be ignored because of Package Restore\n**/[Pp]ackages/*\n# except build/, which is used as an MSBuild target.\n!**/[Pp]ackages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/[Pp]ackages/repositories.config\n# NuGet v3's project.json files produces more ignorable files\n*.nuget.props\n*.nuget.targets\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Windows Store app package directories and files\nAppPackages/\nBundleArtifacts/\nPackage.StoreAssociation.xml\n_pkginfo.txt\n*.appx\n*.appxbundle\n*.appxupload\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!?*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.jfm\n*.pfx\n*.publishsettings\norleans.codegen.cs\n\n# Including strong name files can present a security risk\n# (https://github.com/github/gitignore/pull/2483#issue-259490424)\n#*.snk\n\n# Since there are multiple workflows, uncomment next line to ignore bower_components\n# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)\n#bower_components/\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\nServiceFabricBackup/\n*.rptproj.bak\n\n# SQL Server files\n*.mdf\n*.ldf\n*.ndf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n*.rptproj.rsuser\n*- [Bb]ackup.rdl\n*- [Bb]ackup ([0-9]).rdl\n*- [Bb]ackup ([0-9][0-9]).rdl\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\nnode_modules/\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)\n*.vbw\n\n# Visual Studio 6 auto-generated project file (contains which files were open etc.)\n*.vbp\n\n# Visual Studio 6 workspace and project file (working project files containing files to include in project)\n*.dsw\n*.dsp\n\n# Visual Studio 6 technical files\n*.ncb\n*.aps\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\npaket-files/\n\n# FAKE - F# Make\n.fake/\n\n# CodeRush personal settings\n.cr/personal\n\n# Python Tools for Visual Studio (PTVS)\n__pycache__/\n*.pyc\n\n# Cake - Uncomment if you are using it\n# tools/**\n# !tools/packages.config\n\n# Tabs Studio\n*.tss\n\n# Telerik's JustMock configuration file\n*.jmconfig\n\n# BizTalk build output\n*.btp.cs\n*.btm.cs\n*.odx.cs\n*.xsd.cs\n\n# OpenCover UI analysis results\nOpenCover/\n\n# Azure Stream Analytics local run output\nASALocalRun/\n\n# MSBuild Binary and Structured Log\n*.binlog\n\n# NVidia Nsight GPU debugger configuration file\n*.nvuser\n\n# MFractors (Xamarin productivity tool) working folder\n.mfractor/\n\n# Local History for Visual Studio\n.localhistory/\n\n# Visual Studio History (VSHistory) files\n.vshistory/\n\n# BeatPulse healthcheck temp database\nhealthchecksdb\n\n# Backup folder for Package Reference Convert tool in Visual Studio 2017\nMigrationBackup/\n\n# Ionide (cross platform F# VS Code tools) working folder\n.ionide/\n\n# Fody - auto-generated XML schema\nFodyWeavers.xsd\n\n# VS Code files for those working on multiple tools\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n*.code-workspace\n\n# Local History for Visual Studio Code\n.history/\n\n# Windows Installer files from build outputs\n*.cab\n*.msi\n*.msix\n*.msm\n*.msp\n\n# JetBrains Rider\n*.sln.iml\n.idea/\n\n##\n## Visual studio for Mac\n##\n\n\n# globs\nMakefile.in\n*.userprefs\n*.usertasks\nconfig.make\nconfig.status\naclocal.m4\ninstall-sh\nautom4te.cache/\n*.tar.gz\ntarballs/\ntest-results/\n\n# Mac bundle stuff\n*.dmg\n*.app\n\n# content below from: https://github.com/github/gitignore/blob/main/Global/macOS.gitignore\n# General\n.DS_Store\n.AppleDouble\n.LSOverride\n\n# Icon must end with two \\r\nIcon\n\n\n# Thumbnails\n._*\n\n# Files that might appear in the root of a volume\n.DocumentRevisions-V100\n.fseventsd\n.Spotlight-V100\n.TemporaryItems\n.Trashes\n.VolumeIcon.icns\n.com.apple.timemachine.donotpresent\n\n# Directories potentially created on remote AFP share\n.AppleDB\n.AppleDesktop\nNetwork Trash Folder\nTemporary Items\n.apdisk\n\n# content below from: https://github.com/github/gitignore/blob/main/Global/Windows.gitignore\n# Windows thumbnail cache files\nThumbs.db\nehthumbs.db\nehthumbs_vista.db\n\n# Dump file\n*.stackdump\n\n# Folder config file\n[Dd]esktop.ini\n\n# Recycle Bin used on file shares\n$RECYCLE.BIN/\n\n# Windows Installer files\n*.cab\n*.msi\n*.msix\n*.msm\n*.msp\n\n# Windows shortcuts\n*.lnk\n\n# Vim temporary swap files\n*.swp\n\nsrc/Turso.Native/*\nrs_compiled/*"
  },
  {
    "path": "bindings/dotnet/Cargo.toml",
    "content": "[package]\nname = \"turso-dotnet\"\nversion.workspace = true\nauthors.workspace = true\nedition.workspace = true\nlicense.workspace = true\nrepository.workspace = true\npublish = false\n\n[lints]\nworkspace = true\n\n[lib]\nname = \"turso_dotnet\"\ncrate-type = [\"cdylib\"]\npath = \"rs_src/lib.rs\"\n\n[dependencies]\nturso_core = { workspace = true }\n"
  },
  {
    "path": "bindings/dotnet/Makefile",
    "content": "ifeq ($(OS),Windows_NT)\n\tOS_TARGET = windows64\nelse \n    UNAME_S := $(shell uname -s)\n\tifeq ($(UNAME_S),Linux)\n\t\tOS_TARGET = linux64\n\tendif\n\tifeq ($(UNAME_S),Darwin)\n\t\tUNAME_P := $(shell uname -p)\n\t\tifeq ($(UNAME_P),x86_64)\n\t\t\tOS_TARGET = macos64\n\t\telse\n\t\t\tOS_TARGET = macosarm64\n\t\tendif\n\tendif\nendif\n\nRUST_RELEASE_OPT ?= \nDOTNET_RELEASE_OPT ?=\n\nall:\n\techo \"make [build|test|benchmark|build-production]\"\n\nbuild-rust-windows64:\n\tcargo build --target-dir ./rs_compiled --target x86_64-pc-windows-msvc $(RUST_RELEASE_OPT)\n\nbuild-rust-linux64:\n\tcargo build --target-dir ./rs_compiled --target x86_64-unknown-linux-gnu $(RUST_RELEASE_OPT)\n\nbuild-rust-macos64:\n\tcargo build --target-dir ./rs_compiled --target x86_64-apple-darwin $(RUST_RELEASE_OPT)\n\nbuild-rust-macosarm64:\n\tcargo build --target-dir ./rs_compiled --target aarch64-apple-darwin $(RUST_RELEASE_OPT)\n\nbuild-rust: \n\techo \"Building for $(OS_TARGET)\"\n\t$(MAKE) build-rust-$(OS_TARGET)\n\nbuild-dotnet:\n\tdotnet build $(DOTNET_RELEASE_OPT) ./src/Turso/Turso.csproj\n\nbuild: build-rust build-dotnet\n\nbuild-production:\n\t$(MAKE) build RUST_RELEASE_OPT='--release' DOTNET_RELEASE_OPT='-c Release'\n\ntest: build\n\tdotnet test ./src/Turso.Tests/Turso.Tests.csproj\n\nbenchmark: build-production\n\tdotnet run -c Release --project ./src/Benchmarks/Benchmarks.csproj\n\npack:\n\tdotnet build -c Release ./src/Turso.Raw/Turso.Raw.csproj\n\tdotnet build -c Release ./src/Turso.Raw/Turso.Raw.csproj\n\t\n\tdotnet pack -c Release ./src/Turso.Raw/Turso.Raw.csproj\n\tdotnet pack -c Release ./src/Turso/Turso.csproj\n"
  },
  {
    "path": "bindings/dotnet/Readme.md",
    "content": "# Turso_dotnet\n\nDotnet binding for turso database.\n\n## Getting Started\n\n```C#\nusing Turso;\n\nusing var connection = new TursoConnection(\"Data Source=:memory:\");\nconnection.Open();\n\nconnection.ExecuteNonQuery(\"CREATE TABLE t(a, b)\");\nvar rowsAffected = connection.ExecuteNonQuery(\"INSERT INTO t(a, b) VALUES (1, 2), (3, 4)\");\nConsole.WriteLine($\"RowsAffected: {rowsAffected}\");\n\nusing var command = connection.CreateCommand();\ncommand.CommandText = \"SELECT * FROM t\";\nusing var reader = command.ExecuteReader();\nwhile (reader.Read())\n{\n    var a = reader.GetInt32(0);\n    var b = reader.GetInt32(1);\n    Console.WriteLine($\"Value1: {a}, Value2: {b}\");\n}\n```"
  },
  {
    "path": "bindings/dotnet/Turso.slnx",
    "content": "<Solution>\n  <Configurations>\n    <Platform Name=\"Any CPU\" />\n    <Platform Name=\"x64\" />\n    <Platform Name=\"x86\" />\n  </Configurations>\n  <Project Path=\"src\\Benchmarks\\Benchmarks.csproj\" Type=\"Classic C#\">\n    <Configuration Solution=\"Debug|x64\" Project=\"Debug|Any CPU\" />\n    <Configuration Solution=\"Debug|x86\" Project=\"Debug|Any CPU\" />\n    <Configuration Solution=\"Release|x64\" Project=\"Release|Any CPU\" />\n    <Configuration Solution=\"Release|x86\" Project=\"Release|Any CPU\" />\n  </Project>\n  <Project Path=\"src\\Turso.Tests\\Turso.Tests.csproj\" Type=\"Classic C#\">\n    <Configuration Solution=\"Debug|x64\" Project=\"Debug|Any CPU\" />\n    <Configuration Solution=\"Debug|x86\" Project=\"Debug|Any CPU\" />\n    <Configuration Solution=\"Release|x64\" Project=\"Release|Any CPU\" />\n    <Configuration Solution=\"Release|x86\" Project=\"Release|Any CPU\" />\n  </Project>\n  <Project Path=\"src\\Turso\\Turso.csproj\" />\n  <Project Path=\"src\\Turso.Raw\\Turso.Raw.csproj\" Type=\"Classic C#\" />\n</Solution>"
  },
  {
    "path": "bindings/dotnet/rs_src/lib.rs",
    "content": "use std::borrow::Cow;\nuse std::ffi::CStr;\nuse std::num::NonZero;\nuse std::os::raw::c_char;\nuse std::ptr::null;\nuse std::slice;\nuse std::sync::Arc;\nuse turso_core::types::Text;\nuse turso_core::{\n    self, Connection, DatabaseOpts, EncryptionOpts, LimboError, OpenFlags, Statement, Value, IO,\n};\n\ntype Error = *const std::ffi::c_char;\n\n#[repr(C)]\npub struct Database {\n    io: Arc<dyn IO>,\n    connection: Arc<Connection>,\n}\n\n#[repr(C)]\n#[derive(Copy, Clone)]\npub enum ValueType {\n    Empty = 0,\n    Null = 1,\n    Integer = 2,\n    Float = 3,\n    Text = 4,\n    Blob = 5,\n}\n\n#[repr(C)]\n#[derive(Clone, Copy)]\npub struct Array {\n    ptr: *const u8,\n    len: usize,\n}\n\n#[repr(C)]\n#[derive(Copy, Clone)]\npub union TursoValueUnion {\n    int_val: i64,\n    real_val: f64,\n    text: Array,\n    blob: Array,\n}\n\n#[repr(C)]\n#[derive(Copy, Clone)]\npub struct TursoValue {\n    value_type: ValueType,\n    value: TursoValueUnion,\n}\n\npub fn allocate<T>(value: T) -> *const T {\n    Box::into_raw(Box::new(value))\n}\n\npub fn allocate_string(str: &str) -> *const c_char {\n    std::ffi::CString::new(str).unwrap().into_raw()\n}\n\npub fn to_vec(array: Array) -> Vec<u8> {\n    unsafe {\n        let slice = slice::from_raw_parts(array.ptr, array.len);\n        slice.to_vec()\n    }\n}\n\npub fn to_value(value: TursoValue) -> Value {\n    match value.value_type {\n        ValueType::Empty => Value::Null,\n        ValueType::Null => Value::Null,\n        ValueType::Integer => Value::from_i64(unsafe { value.value.int_val }),\n        ValueType::Float => Value::from_f64(unsafe { value.value.real_val }),\n        ValueType::Blob => Value::Blob(to_vec(unsafe { value.value.blob })),\n        ValueType::Text => {\n            let slice =\n                unsafe { slice::from_raw_parts(value.value.text.ptr, value.value.text.len) };\n            match str::from_utf8(slice) {\n                Ok(value) => Value::Text(Text::new(value)),\n                Err(_) => Value::Null,\n            }\n        }\n    }\n}\n\n/// Opens a database at the specified path and returns a pointer to the database.\n/// If an error occurred, returns null and writes a pointer to a null-terminated string into `error_ptr`.\n///\n/// # Safety\n///\n/// - The returned database pointer must be freed with `db_close`.\n/// - Any error string written to `error_ptr` must be freed with `free_string`.\n/// - `path_ptr` must not be null and must point to a valid null-terminated UTF-8 string.\n/// - `error_ptr` must not be null and must point to a valid writable location.\n#[no_mangle]\npub unsafe extern \"C\" fn db_open(\n    path_ptr: *const c_char,\n    error_ptr: *mut Error,\n) -> *const Database {\n    let path_cstr: &CStr = unsafe { CStr::from_ptr(path_ptr) };\n    let path_str = path_cstr.to_str();\n\n    let connection_result = Connection::from_uri(path_str.unwrap(), DatabaseOpts::new());\n    match connection_result {\n        Ok((io, val)) => allocate(Database {\n            io,\n            connection: val,\n        }),\n        Err(err) => {\n            unsafe {\n                *error_ptr =\n                    allocate_string(format!(\"Error while opening database: {err}\").as_str())\n            }\n            null()\n        }\n    }\n}\n\n/// Opens a database with encryption at the specified path.\n/// If cipher_ptr or hexkey_ptr is null, opens without encryption.\n/// If an error occurred, returns null and writes a pointer to a null-terminated string into `error_ptr`.\n///\n/// # Safety\n///\n/// - The returned database pointer must be freed with `db_close`.\n/// - Any error string written to `error_ptr` must be freed with `free_string`.\n/// - `path_ptr` must not be null and must point to a valid null-terminated UTF-8 string.\n/// - `cipher_ptr` and `hexkey_ptr` must either both be null or both point to valid null-terminated UTF-8 strings.\n/// - `error_ptr` must not be null and must point to a valid writable location.\n#[no_mangle]\npub unsafe extern \"C\" fn db_open_with_encryption(\n    path_ptr: *const c_char,\n    cipher_ptr: *const c_char,\n    hexkey_ptr: *const c_char,\n    error_ptr: *mut Error,\n) -> *const Database {\n    let path_cstr: &CStr = unsafe { CStr::from_ptr(path_ptr) };\n    let path_str = match path_cstr.to_str() {\n        Ok(s) => s,\n        Err(err) => {\n            unsafe {\n                *error_ptr = allocate_string(format!(\"Invalid path encoding: {err}\").as_str())\n            }\n            return null();\n        }\n    };\n\n    // Parse encryption options if both cipher and hexkey are provided\n    let encryption_opts = if !cipher_ptr.is_null() && !hexkey_ptr.is_null() {\n        let cipher_cstr: &CStr = unsafe { CStr::from_ptr(cipher_ptr) };\n        let hexkey_cstr: &CStr = unsafe { CStr::from_ptr(hexkey_ptr) };\n\n        let cipher_str = match cipher_cstr.to_str() {\n            Ok(s) => s,\n            Err(err) => {\n                unsafe {\n                    *error_ptr = allocate_string(format!(\"Invalid cipher encoding: {err}\").as_str())\n                }\n                return null();\n            }\n        };\n        let hexkey_str = match hexkey_cstr.to_str() {\n            Ok(s) => s,\n            Err(err) => {\n                unsafe {\n                    *error_ptr = allocate_string(format!(\"Invalid hexkey encoding: {err}\").as_str())\n                }\n                return null();\n            }\n        };\n\n        Some(EncryptionOpts {\n            cipher: cipher_str.to_string(),\n            hexkey: hexkey_str.to_string(),\n        })\n    } else {\n        None\n    };\n\n    let db_opts = if encryption_opts.is_some() {\n        DatabaseOpts::new().with_encryption(true)\n    } else {\n        DatabaseOpts::new()\n    };\n\n    let io: Arc<dyn IO> = match turso_core::PlatformIO::new() {\n        Ok(io) => Arc::new(io),\n        Err(err) => {\n            unsafe { *error_ptr = allocate_string(format!(\"Failed to create IO: {err}\").as_str()) }\n            return null();\n        }\n    };\n\n    // Parse encryption key before opening database\n    let encryption_key = if let Some(ref opts) = encryption_opts {\n        match turso_core::EncryptionKey::from_hex_string(&opts.hexkey) {\n            Ok(key) => Some(key),\n            Err(err) => {\n                unsafe {\n                    *error_ptr = allocate_string(format!(\"Invalid encryption key: {err}\").as_str())\n                }\n                return null();\n            }\n        }\n    } else {\n        None\n    };\n\n    let db = match turso_core::Database::open_file_with_flags(\n        io.clone(),\n        path_str,\n        OpenFlags::Create,\n        db_opts,\n        encryption_opts,\n    ) {\n        Ok(db) => db,\n        Err(err) => {\n            unsafe {\n                *error_ptr =\n                    allocate_string(format!(\"Error while opening database: {err}\").as_str())\n            }\n            return null();\n        }\n    };\n\n    // Use connect_with_encryption to properly set up encryption context before reading pages\n    let connection = match db.connect_with_encryption(encryption_key) {\n        Ok(conn) => conn,\n        Err(err) => {\n            unsafe {\n                *error_ptr = allocate_string(format!(\"Error while connecting: {err}\").as_str())\n            }\n            return null();\n        }\n    };\n\n    allocate(Database { io, connection })\n}\n\n/// Disposes the database pointer.\n///\n/// # Safety\n///\n/// - `db_ptr` must be a pointer allocated by `db_open` or `db_open_with_encryption`.\n/// - Call `db_close` only once per `db_ptr`.\n#[no_mangle]\npub unsafe extern \"C\" fn db_close(db_ptr: *mut Database) {\n    let _ = unsafe { Box::from_raw(db_ptr) };\n}\n\n/// Frees a null-terminated string previously allocated by this library.\n///\n/// # Safety\n///\n/// - `string_ptr` must be a pointer returned by this library (e.g., error messages, column names).\n/// - Call `free_string` only once per `string_ptr`.\n#[no_mangle]\npub unsafe extern \"C\" fn free_string(string_ptr: *mut c_char) {\n    unsafe { drop(std::ffi::CString::from_raw(string_ptr)) };\n}\n\n/// Prepares an SQL statement and returns a pointer to the prepared statement.\n/// If an error occurred, returns null and writes a pointer to a null-terminated string into `error_ptr`.\n///\n/// # Safety\n///\n/// - `db_ptr` must not be null.\n/// - `sql_ptr` must not be null and must point to a valid null-terminated UTF-8 string.\n/// - `error_ptr` must not be null and must point to a valid writable location.\n/// - When not null, the statement pointer must be freed with `free_statement` and any error string with `free_string`.\n#[no_mangle]\npub unsafe extern \"C\" fn db_prepare_statement(\n    db_ptr: *mut Database,\n    sql_ptr: *const c_char,\n    error_ptr: *mut Error,\n) -> *const Statement {\n    let sql = unsafe { CStr::from_ptr(sql_ptr) }.to_str();\n    let db = unsafe { &mut (*db_ptr) };\n\n    let prepare_result = db.connection.prepare(sql.unwrap());\n    match prepare_result {\n        Ok(statement) => allocate(statement),\n        Err(e) => {\n            unsafe {\n                *error_ptr = allocate_string(format!(\"Unable to prepare statement: {e}\").as_str())\n            }\n            null()\n        }\n    }\n}\n\n/// Binds a parameter to the statement by index.\n///\n/// # Safety\n///\n/// - `statement_ptr` must be a pointer returned by `db_prepare_statement`.\n/// - `index` must be >= 1.\n/// - `parameter_value` must be a valid pointer to a `TursoValue`.\n#[no_mangle]\npub unsafe extern \"C\" fn bind_parameter(\n    statement_ptr: *mut Statement,\n    index: i32,\n    parameter_value: *const TursoValue,\n) {\n    let statement = unsafe { &mut (*statement_ptr) };\n    statement.bind_at(\n        NonZero::new(index.try_into().unwrap()).unwrap(),\n        to_value(*parameter_value),\n    );\n}\n\n/// Binds a parameter to the statement by name.\n///\n/// # Safety\n///\n/// - `statement_ptr` must be a pointer returned by `db_prepare_statement`.\n/// - `parameter_name` must not be null and must point to a valid null-terminated UTF-8 string.\n/// - `parameter_value` must be a valid pointer to a `TursoValue`.\n#[no_mangle]\npub unsafe extern \"C\" fn bind_named_parameter(\n    statement_ptr: *mut Statement,\n    parameter_name: *const c_char,\n    parameter_value: *const TursoValue,\n) {\n    let statement = unsafe { &mut (*statement_ptr) };\n    let parameter_name = unsafe { CStr::from_ptr(parameter_name) }.to_str().unwrap();\n\n    for idx in 1..statement.parameters_count() + 1 {\n        let non_zero_idx = NonZero::new(idx).unwrap();\n        let param = statement.parameters().name(non_zero_idx);\n        let Some(name) = param else {\n            continue;\n        };\n        if parameter_name == name {\n            statement.bind_at(non_zero_idx, to_value(*parameter_value));\n            return;\n        }\n    }\n}\n\n/// Returns the number of rows changed by the statement.\n///\n/// # Safety\n///\n/// - `statement_ptr` must not be null.\n#[no_mangle]\npub unsafe extern \"C\" fn db_statement_nchange(statement_ptr: *mut Statement) -> i64 {\n    let statement = unsafe { &mut (*statement_ptr) };\n    statement.n_change()\n}\n\n/// Executes the statement, advancing it by one step.\n/// If an error occurred, sets `error_ptr` to a pointer to a null-terminated string.\n///\n/// # Safety\n///\n/// - `statement_ptr` must not be null.\n/// - `error_ptr` must not be null and must point to a location that is valid for writing.\n/// - If set, the error string must be freed with `free_string`.\n#[no_mangle]\npub unsafe extern \"C\" fn db_statement_execute_step(\n    statement_ptr: *mut Statement,\n    error_ptr: *mut Error,\n) -> bool {\n    let statement = unsafe { &mut (*statement_ptr) };\n    let result = statement.run_one_step_blocking(|| Ok(()), || Ok(()));\n\n    match result {\n        Ok(Some(_)) => true,\n        Ok(None) => {\n            // Done\n            false\n        }\n        Err(LimboError::Interrupt) => {\n            unsafe { *error_ptr = allocate_string(\"Interrupted\") };\n            false\n        }\n        Err(LimboError::Busy) => {\n            unsafe { *error_ptr = allocate_string(\"Database is busy\") };\n            false\n        }\n        Err(err) => {\n            unsafe { *error_ptr = allocate_string(err.to_string().as_str()) };\n            false\n        }\n    }\n}\n\n/// Frees the statement pointer.\n///\n/// # Safety\n///\n/// - `statement_ptr` must not be null.\n/// - Call `free_statement` only once per `statement_ptr`.\n#[no_mangle]\npub unsafe extern \"C\" fn free_statement(statement_ptr: *mut Statement) {\n    let mut statement = unsafe { Box::from_raw(statement_ptr) };\n    statement.reset_best_effort();\n}\n\n/// Gets the current value from the row at the specified column index.\n///\n/// # Safety\n///\n/// - `statement_ptr` must not be null.\n/// - `col_idx` must be >= 0.\n#[no_mangle]\npub unsafe extern \"C\" fn db_statement_get_value(\n    statement_ptr: *mut Statement,\n    col_idx: i32,\n) -> TursoValue {\n    let statement = unsafe { &mut (*statement_ptr) };\n    if let Some(row) = statement.row() {\n        let value = match row.get_value(col_idx.try_into().unwrap()) {\n            Value::Null => TursoValue {\n                value_type: ValueType::Null,\n                value: TursoValueUnion { int_val: 0 },\n            },\n            Value::Numeric(turso_core::Numeric::Integer(int_val)) => TursoValue {\n                value_type: ValueType::Integer,\n                value: TursoValueUnion { int_val: *int_val },\n            },\n            Value::Numeric(turso_core::Numeric::Float(float_value)) => TursoValue {\n                value_type: ValueType::Float,\n                value: TursoValueUnion {\n                    real_val: f64::from(*float_value),\n                },\n            },\n            Value::Text(text) => {\n                let array = Array {\n                    ptr: text.value.as_ptr(),\n                    len: text.value.len(),\n                };\n                TursoValue {\n                    value_type: ValueType::Text,\n                    value: TursoValueUnion { text: array },\n                }\n            }\n            Value::Blob(blob) => {\n                let bytes = blob.as_ptr();\n                let array = Array {\n                    ptr: bytes,\n                    len: blob.len(),\n                };\n                TursoValue {\n                    value_type: ValueType::Blob,\n                    value: TursoValueUnion { blob: array },\n                }\n            }\n        };\n\n        return value;\n    }\n\n    TursoValue {\n        value_type: ValueType::Empty,\n        value: TursoValueUnion { int_val: 0 },\n    }\n}\n\n/// Gets the number of columns in the current statement.\n///\n/// # Safety\n///\n/// - `statement_ptr` must not be null.\n#[no_mangle]\npub unsafe extern \"C\" fn db_statement_num_columns(statement_ptr: *mut Statement) -> i32 {\n    let statement = unsafe { &mut (*statement_ptr) };\n    statement.num_columns().try_into().unwrap()\n}\n\n/// Gets the column name for the specified index.\n/// The returned string is heap-allocated; free it with `free_string` when no longer needed.\n///\n/// # Safety\n///\n/// - `statement_ptr` must not be null.\n/// - `index` must be >= 0.\n#[no_mangle]\npub unsafe extern \"C\" fn db_statement_column_name(\n    statement_ptr: *mut Statement,\n    index: i32,\n) -> *const std::ffi::c_char {\n    let statement = unsafe { &mut (*statement_ptr) };\n    let col_name = statement.get_column_name(index.try_into().unwrap());\n    match col_name {\n        Cow::Borrowed(value) => allocate_string(value),\n        Cow::Owned(value) => allocate_string(value.as_str()),\n    }\n}\n\n/// Checks whether the statement currently points to a row.\n///\n/// # Safety\n///\n/// - `statement_ptr` must not be null.\n#[no_mangle]\npub unsafe extern \"C\" fn db_statement_has_rows(statement_ptr: *mut Statement) -> bool {\n    let statement = unsafe { &mut (*statement_ptr) };\n    match statement.row() {\n        Some(_val) => true,\n        None => false,\n    }\n}\n"
  },
  {
    "path": "bindings/dotnet/src/Benchmarks/Benchmarks.cs",
    "content": "using System.Data;\nusing System.Data.SQLite;\nusing System.Runtime.CompilerServices;\nusing BenchmarkDotNet.Attributes;\nusing Microsoft.Data.Sqlite;\nusing SQLitePCL;\nusing Turso;\n\nnamespace Benchmarks;\n\n[MemoryDiagnoser]\npublic class Benchmarks\n{\n    private SQLiteConnection _systemDataSqliteConnection;\n    private SqliteConnection _microsoftDataSqliteConnection;\n    private TursoConnection _tursoConnection;\n\n    [GlobalSetup]\n    public void Setup()\n    {\n        _systemDataSqliteConnection = new SQLiteConnection(\"Data Source=:memory:\");\n        _systemDataSqliteConnection.Open();\n\n        _microsoftDataSqliteConnection = new SqliteConnection(\"Data Source=:memory:\");\n        _microsoftDataSqliteConnection.Open();\n\n        _tursoConnection = new TursoConnection(\"Data Source=:memory:\");\n        _tursoConnection.Open();\n        CreateTable(_systemDataSqliteConnection);\n        CreateTable(_microsoftDataSqliteConnection);\n        CreateTable(_tursoConnection);\n    }\n\n    [Benchmark]\n    public void TursoSelect() => Select(_tursoConnection);\n\n    [Benchmark]\n    public void SystemSqliteSelect() => Select(_systemDataSqliteConnection);\n\n    [Benchmark]\n    public void MicrososftSqliteSelect() => Select(_microsoftDataSqliteConnection);\n\n    private void CreateTable(IDbConnection connection)\n    {\n        using var createTableCommand = connection.CreateCommand();\n        createTableCommand.CommandText = \"CREATE TABLE t(a, b)\";\n        createTableCommand.ExecuteNonQuery();\n\n        using var insertCommand = connection.CreateCommand();\n        insertCommand.CommandText = @\"INSERT INTO t(a, b) VALUES (1, 2), (3, 4);\";\n        insertCommand.ExecuteNonQuery();\n    }\n\n    [MethodImpl(MethodImplOptions.AggressiveInlining)]\n    private static void Select(IDbConnection connection)\n    {\n        using var command = connection.CreateCommand();\n        command.CommandText = \"SELECT * FROM t;\";\n        using var reader = command.ExecuteReader();\n        var sum = 0;\n        while (reader.Read()) \n            sum += reader.GetInt32(0);\n        \n        GC.KeepAlive(sum);\n    }\n}"
  },
  {
    "path": "bindings/dotnet/src/Benchmarks/Benchmarks.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n    <PropertyGroup>\n        <OutputType>Exe</OutputType>\n        <TargetFramework>net9.0</TargetFramework>\n        <ImplicitUsings>enable</ImplicitUsings>\n        <Nullable>enable</Nullable>\n    </PropertyGroup>\n\n    <ItemGroup>\n      <PackageReference Include=\"BenchmarkDotNet\" Version=\"0.15.4\" />\n      <PackageReference Include=\"Microsoft.Data.Sqlite.Core\" Version=\"9.0.9\" />\n      <PackageReference Include=\"System.Data.SQLite\" Version=\"2.0.2\" />\n      <PackageReference Include=\"SourceGear.sqlite3\" version=\"3.50.4.2\"/>\n      <PackageReference Include=\"Microsoft.EntityFrameworkCore.Sqlite\" Version=\"9.0.9\" />\n    </ItemGroup>\n\n    <ItemGroup>\n      <ProjectReference Include=\"..\\Turso\\Turso.csproj\" />\n    </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "bindings/dotnet/src/Benchmarks/Program.cs",
    "content": "﻿\nusing BenchmarkDotNet.Running;\n\nBenchmarkRunner.Run<Benchmarks.Benchmarks>();"
  },
  {
    "path": "bindings/dotnet/src/Turso/Turso.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <OutputType>Library</OutputType>\n    <TargetFramework>net9.0</TargetFramework>\n    <ImplicitUsings>enable</ImplicitUsings>\n    <Nullable>enable</Nullable>\n    <RootNamespace>Turso</RootNamespace>\n    \n    <PackageId>Turso</PackageId>\n    <Description>ADO.NET provider for turso bindings</Description>\n    <Version>0.0.1</Version>\n  </PropertyGroup>\n  \n  <ItemGroup>\n    <PackageReference Include=\"Turso.Raw\" Version=\"0.0.1\" />\n  </ItemGroup>\n  \n  <ItemGroup>\n    <ProjectReference Include=\"..\\Turso.Raw\\Turso.Raw.csproj\" />\n  </ItemGroup>\n</Project>\n"
  },
  {
    "path": "bindings/dotnet/src/Turso/TursoCommand.cs",
    "content": "﻿using System.Data;\nusing System.Data.Common;\nusing Turso.Raw.Public;\nusing Turso.Raw.Public.Handles;\n\nnamespace Turso;\n\npublic class TursoCommand : DbCommand\n{\n    private TursoConnection _connection;\n    private TursoParameterCollection _parameterCollection = new();\n\n    private TursoTransaction? _transaction;\n    private TursoStatementHandle? _statement;\n\n    public TursoCommand(TursoConnection connection, TursoTransaction? transaction = null)\n    {\n        _connection = connection;\n        _transaction = transaction;\n    }\n\n    public TursoCommand(TursoConnection connection, string command)\n    {\n        _connection = connection;\n        _transaction = null;\n        CommandText = command;\n    }\n\n\n    public override string CommandText { get; set; } = \"\";\n    public override int CommandTimeout { get; set; } = 30;\n\n    public override CommandType CommandType\n    {\n        get => CommandType.Text;\n        set => throw new NotSupportedException();\n    }\n\n    public override bool DesignTimeVisible { get; set; }\n    public override UpdateRowSource UpdatedRowSource { get; set; }\n\n    protected override DbConnection? DbConnection\n    {\n        get => _connection;\n        set => _connection = value as TursoConnection ?? throw new ArgumentException();\n    }\n\n    protected override DbParameterCollection DbParameterCollection => _parameterCollection;\n\n    public new virtual TursoParameterCollection Parameters => _parameterCollection;\n\n\n    protected override DbTransaction? DbTransaction\n    {\n        get => _transaction;\n        set => _transaction = value as TursoTransaction ?? throw new ArgumentException();\n    }\n    \n    protected override void Dispose(bool disposing)\n    {\n        base.Dispose(disposing);\n        _statement?.Dispose();\n    }\n\n    public override void Cancel()\n    {\n    }\n\n    public override int ExecuteNonQuery()\n    {\n        var reader = Execute();\n        reader.NextResult();\n        return reader.RecordsAffected;\n    }\n\n    public override object? ExecuteScalar()\n    {\n        using var reader = Execute();\n        return reader.Read()\n            ? reader.GetValue(0)\n            : null;\n    }\n\n    public override void Prepare()\n    {\n        _statement = TursoBindings.PrepareStatement(_connection.Turso, CommandText);\n        for (var i = 0; i < _parameterCollection.Count; i++)\n        {\n            var parameter = _parameterCollection[i] as TursoParameter;\n            if (parameter == null)\n                throw new ArgumentException(\"Parameter must be of type TursoParameter\");\n\n            if (!string.IsNullOrEmpty(parameter.ParameterName))\n            {\n                TursoBindings.BindNamedParameter(_statement, parameter.ParameterName, parameter.ToValue());\n            }\n            else\n            {\n                TursoBindings.BindParameter(_statement, i + 1, parameter.ToValue());\n            }\n        }\n    }\n\n    protected override DbParameter CreateDbParameter()\n    {\n        return new TursoParameter();\n    }\n\n\n    protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior)\n    {\n        return Execute(behavior);\n    }\n\n    private DbDataReader Execute(CommandBehavior behavior = CommandBehavior.Default)\n    {\n        if (_statement is null)\n            Prepare();\n\n        var reader = new TursoDataReader(this, _statement);\n        return reader;\n    }\n}"
  },
  {
    "path": "bindings/dotnet/src/Turso/TursoConnection.cs",
    "content": "﻿using System.Data;\nusing System.Data.Common;\nusing Turso.Raw.Public;\nusing Turso.Raw.Public.Handles;\n\nnamespace Turso;\n\npublic class TursoConnection : DbConnection\n{\n    private TursoDatabaseHandle? _turso = null;\n\n    private TursoConnectionOptions _connectionOptions;\n\n    public override string ConnectionString\n    {\n        get => _connectionOptions.GetConnectionString();\n        set => _connectionOptions = TursoConnectionOptions.Parse(value);\n    }\n\n    public override string Database => \"main\";\n\n    public override string DataSource => _connectionOptions[\"Data Source\"] ?? \"\";\n\n    public override string ServerVersion => throw new NotImplementedException();\n\n    public override ConnectionState State => _turso is not null ? ConnectionState.Open : ConnectionState.Closed;\n\n    public TursoConnection() : this(\"\")\n    {\n    }\n\n    public TursoConnection(string connectionString)\n    {\n        _connectionOptions = TursoConnectionOptions.Parse(connectionString);\n    }\n\n    public override void Open()\n    {\n        var filename = _connectionOptions[\"Data Source\"] ?? \":memory:\";\n        var cipher = _connectionOptions.GetEncryptionCipher();\n        var hexkey = _connectionOptions[\"Encryption Key\"];\n\n        if (cipher.HasValue && hexkey is not null)\n        {\n            _turso = TursoBindings.OpenDatabaseWithEncryption(filename, cipher.Value, hexkey);\n        }\n        else\n        {\n            _turso = TursoBindings.OpenDatabase(filename);\n        }\n    }\n\n    public override void Close()\n    {\n        _turso?.Dispose();\n        _turso = null;\n    }\n    \n    protected override void Dispose(bool disposing)\n    {\n        base.Dispose(disposing);\n        _turso?.Dispose();\n    }\n\n    protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel)\n    {\n        if (_turso is null)\n        {\n            throw new Exception(\"Turso database is closed\");\n        }\n\n        return new TursoTransaction(this, isolationLevel);\n    }\n\n    protected override DbCommand CreateDbCommand()\n    {\n        if (_turso is null)\n        {\n            throw new Exception(\"Turso database is closed\");\n        }\n\n        return new TursoCommand(this);\n    }\n\n    public int ExecuteNonQuery(string sql)\n    {\n        using var command = CreateCommand();\n        command.CommandText = sql;\n\n        return command.ExecuteNonQuery();\n    }\n\n    public override void ChangeDatabase(string databaseName)\n    {\n        throw new NotSupportedException();\n    }\n    \n    internal TursoDatabaseHandle Turso => _turso;\n}"
  },
  {
    "path": "bindings/dotnet/src/Turso/TursoConnectionOptions.cs",
    "content": "﻿using Turso.Raw.Public.Value;\n\nnamespace Turso;\n\npublic class TursoConnectionOptions\n{\n    private Dictionary<string, string> _options = new();\n\n\n    private void AddOption(string keyword, string value)\n    {\n        if (!_valid_keywords.Contains(keyword))\n        {\n            throw new InvalidOperationException($\"Unsupported keyword: {keyword}\");\n        }\n\n        _options[keyword] = value;\n    }\n\n    public string GetConnectionString()\n    {\n        var parts = new List<string>();\n        foreach (var keyword in _valid_keywords)\n        {\n            var option = GetOption(keyword);\n            if (option is not null)\n            {\n                parts.Add($\"{keyword}={option}\");\n            }    \n        }\n\n        return string.Join(\";\", parts);\n    }\n\n    private string? GetOption(string keyword)\n    {\n        return _options.GetValueOrDefault(keyword);\n    }\n\n    public string? this[string keyword]\n    {\n        get => GetOption(keyword);\n        set => AddOption(keyword, value  ?? \"\");\n    }\n\n    /// <summary>\n    /// Gets the encryption cipher from the connection options.\n    /// </summary>\n    /// <returns>The cipher enum value, or null if not specified or invalid.</returns>\n    public TursoEncryptionCipher? GetEncryptionCipher()\n    {\n        var cipherStr = GetOption(\"Encryption Cipher\");\n        if (cipherStr is null) return null;\n\n        return cipherStr.ToLowerInvariant() switch\n        {\n            \"aes128gcm\" => TursoEncryptionCipher.Aes128Gcm,\n            \"aes256gcm\" => TursoEncryptionCipher.Aes256Gcm,\n            \"aegis256\" => TursoEncryptionCipher.Aegis256,\n            \"aegis256x2\" => TursoEncryptionCipher.Aegis256x2,\n            \"aegis128l\" => TursoEncryptionCipher.Aegis128l,\n            \"aegis128x2\" => TursoEncryptionCipher.Aegis128x2,\n            \"aegis128x4\" => TursoEncryptionCipher.Aegis128x4,\n            _ => throw new InvalidOperationException($\"Unknown encryption cipher: {cipherStr}\")\n        };\n    }\n\n    private readonly string[] _valid_keywords = [\n        \"Data Source\",\n        \"Mode\",\n        \"Cache\",\n        \"Password\",\n        \"Foreign Keys\",\n        \"Recursive Triggers\",\n        \"Default Timeout\",\n        \"Pooling\",\n        \"Vfs\",\n        \"Encryption Cipher\",\n        \"Encryption Key\"\n    ];\n\n    public static TursoConnectionOptions Parse(string connectionString)\n    {\n        var options = new TursoConnectionOptions();\n\n\n\n        foreach (var optionPart in connectionString.Split(\";\"))\n        {\n            var separatorIndex = optionPart.IndexOf('=');\n            if (separatorIndex == -1)\n                continue;\n\n            var keyword = optionPart.Substring(0, separatorIndex);\n            var value = optionPart.Substring(separatorIndex + 1);\n\n            options.AddOption(keyword, value);\n        }\n\n        return options;\n    }\n}\n"
  },
  {
    "path": "bindings/dotnet/src/Turso/TursoDataReader.cs",
    "content": "﻿using System.Collections;\nusing System.ComponentModel;\nusing System.Data.Common;\nusing System.Globalization;\nusing System.Runtime.CompilerServices;\nusing Turso.Raw.Public;\nusing Turso.Raw.Public.Handles;\nusing Turso.Raw.Public.Value;\n\nnamespace Turso;\n\npublic class TursoDataReader : DbDataReader\n{\n    private readonly TursoCommand _command;\n    private readonly TursoStatementHandle _statement;\n\n    public TursoDataReader(TursoCommand command, TursoStatementHandle statement)\n    {\n        _command = command;\n        _statement = statement;\n    }\n\n    public override bool GetBoolean(int ordinal)\n    {\n        return TursoBindings.GetValue(_statement, ordinal).IntValue != 0;\n    }\n\n    public override byte GetByte(int ordinal)\n    {\n        return (byte)TursoBindings.GetValue(_statement, ordinal).IntValue;\n    }\n\n    public override long GetBytes(int ordinal, long dataOffset, byte[]? buffer, int bufferOffset, int length)\n    {\n        return GetArray(ordinal, dataOffset, buffer, bufferOffset, length);\n    }\n\n    public override char GetChar(int ordinal)\n    {\n        var value = TursoBindings.GetValue(_statement, ordinal);\n        if (value.ValueType == TursoValueType.Text && value.StringValue.Length == 1)\n        {\n            return value.StringValue[0];\n        }\n\n        return (char)TursoBindings.GetValue(_statement, ordinal).IntValue;\n    }\n\n    public override long GetChars(int ordinal, long dataOffset, char[]? buffer, int bufferOffset, int length)\n    {\n        return GetArray(ordinal, dataOffset, buffer, bufferOffset, length);\n    }\n\n    public override string GetDataTypeName(int ordinal)\n    {\n        var value = TursoBindings.GetValue(_statement, ordinal);\n        return GetTypeName(value.ValueType);\n    }\n\n    public override DateTime GetDateTime(int ordinal)\n    {\n        var value = TursoBindings.GetValue(_statement, ordinal);\n        switch (value.ValueType)\n        {\n            case TursoValueType.Text:\n                return DateTime.Parse(GetString(ordinal), CultureInfo.InvariantCulture);\n            default:\n                return DateTime.MinValue;\n        }\n    }\n\n    public override decimal GetDecimal(int ordinal)\n    {\n        return (decimal)TursoBindings.GetValue(_statement, ordinal).RealValue;\n    }\n\n    public override double GetDouble(int ordinal)\n    {\n        return TursoBindings.GetValue(_statement, ordinal).RealValue;\n    }\n\n    public override Type GetFieldType(int ordinal)\n    {\n        var value = TursoBindings.GetValue(_statement, ordinal);\n        return value.ValueType switch\n        {\n            TursoValueType.Integer => typeof(long),\n            TursoValueType.Real => typeof(double),\n            TursoValueType.Text => typeof(string),\n            TursoValueType.Blob => typeof(byte[]),\n            _ => typeof(object)\n        };\n    }\n\n    public override float GetFloat(int ordinal)\n    {\n        return (float)TursoBindings.GetValue(_statement, ordinal).RealValue;\n    }\n\n    public override Guid GetGuid(int ordinal)\n    {\n        return Guid.Parse(TursoBindings.GetValue(_statement, ordinal).StringValue);\n    }\n\n    public override short GetInt16(int ordinal)\n    {\n        return (short)TursoBindings.GetValue(_statement, ordinal).IntValue;\n    }\n\n    public override int GetInt32(int ordinal)\n    {\n        return (int)TursoBindings.GetValue(_statement, ordinal).IntValue;\n    }\n\n    public override long GetInt64(int ordinal)\n    {\n        return TursoBindings.GetValue(_statement, ordinal).IntValue;\n    }\n\n    public override string GetName(int ordinal)\n    {\n        return TursoBindings.GetName(_statement, ordinal);\n    }\n\n    public override int GetOrdinal(string name)\n    {\n        var fields = TursoBindings.GetFieldCount(_statement);\n        for (var i = 0; i < fields; i++)\n        {\n            var columnName = TursoBindings.GetName(_statement, i);\n            if (columnName == name)\n                return i;\n        }\n\n        throw new IndexOutOfRangeException($\"column {name} not found\");\n    }\n\n    public override string GetString(int ordinal)\n    {\n        return TursoBindings.GetValue(_statement, ordinal).StringValue;\n    }\n\n    public override object? GetValue(int ordinal)\n    {\n        var value = TursoBindings.GetValue(_statement, ordinal);\n        return value.ValueType switch\n        {\n            TursoValueType.Null or TursoValueType.Empty => null,\n            TursoValueType.Integer => value.IntValue,\n            TursoValueType.Real => value.RealValue,\n            TursoValueType.Text => value.StringValue,\n            TursoValueType.Blob => value.BlobValue,\n            _ => throw new ArgumentOutOfRangeException()\n        };\n    }\n\n    public override int GetValues(object[] values)\n    {\n        var i = 0;\n        for (; i < FieldCount; i++)\n        {\n            values[i] = GetValue(i)!;\n        }\n\n        return i;\n    }\n\n    public override bool IsDBNull(int ordinal)\n    {\n        var valueType = TursoBindings.GetValue(_statement, ordinal).ValueType;\n        return valueType == TursoValueType.Null;\n    }\n\n    public override int FieldCount => TursoBindings.GetFieldCount(_statement);\n\n    public override object this[int ordinal] => GetValue(ordinal)!;\n\n    public override object this[string name]\n    {\n        get\n        {\n            var ordinal = GetOrdinal(name);\n            return GetValue(ordinal)!;\n        }\n    }\n\n    public override int RecordsAffected => TursoBindings.RowsAffected(_statement);\n    public override bool HasRows => TursoBindings.HasRows(_statement);\n    public override bool IsClosed => _statement.IsInvalid;\n\n    public override bool NextResult()\n    {\n        while (TursoBindings.Read(_statement)) ;\n        return true;\n    }\n\n    protected override void Dispose(bool disposing)\n    {\n        base.Dispose(disposing);\n        _command.Dispose();\n    }\n\n    public override bool Read()\n    {\n        return TursoBindings.Read(_statement);\n    }\n\n    public override int Depth => 0;\n\n    public override IEnumerator GetEnumerator()\n    {\n        return new DbEnumerator(this, closeReader: false);\n    }\n\n    private long GetArray<T>(int ordinal, long dataOffset, T[]? buffer, int bufferOffset, int length)\n        where T : struct\n    {\n        var bytes = TursoBindings.GetValue(_statement, ordinal).BlobValue;\n        if (buffer is null)\n        {\n            return Math.Min(bytes.Length - dataOffset, length);\n        }\n\n        var position = 0;\n        for (; position < length; position++)\n        {\n            if (bufferOffset + position >= buffer.Length || position + dataOffset >= bytes.Length)\n                break;\n\n            buffer[bufferOffset + position] = Unsafe.As<byte, T>(ref bytes[position + dataOffset]);\n        }\n\n        return position;\n    }\n\n    private static string GetTypeName(TursoValueType valueType)\n    {\n        return valueType switch\n        {\n            TursoValueType.Empty => \"\",\n            TursoValueType.Null => \"NULL\",\n            TursoValueType.Integer => \"INTEGER\",\n            TursoValueType.Real => \"REAL\",\n            TursoValueType.Text => \"TEXT\",\n            TursoValueType.Blob => \"BLOB\",\n            _ => throw new InvalidEnumArgumentException(nameof(valueType))\n        };\n    }\n}"
  },
  {
    "path": "bindings/dotnet/src/Turso/TursoParameter.cs",
    "content": "using System.Data;\nusing System.Data.Common;\nusing Turso.Raw.Public.Value;\n\nnamespace Turso;\n\npublic class TursoParameter : DbParameter\n{\n    private static readonly Dictionary<Type, TursoValueType> TursoTypeMapping =\n        new()\n        {\n            { typeof(bool), TursoValueType.Integer },\n            { typeof(byte), TursoValueType.Integer },\n            { typeof(byte[]), TursoValueType.Blob },\n            { typeof(char), TursoValueType.Text },\n            { typeof(DateTime), TursoValueType.Text },\n            { typeof(DateTimeOffset), TursoValueType.Text },\n            { typeof(DateOnly), TursoValueType.Text },\n            { typeof(TimeOnly), TursoValueType.Text },\n            { typeof(DBNull), TursoValueType.Null },\n            { typeof(decimal), TursoValueType.Text },\n            { typeof(double), TursoValueType.Real },\n            { typeof(float), TursoValueType.Real },\n            { typeof(Guid), TursoValueType.Text },\n            { typeof(int), TursoValueType.Integer },\n            { typeof(long), TursoValueType.Integer },\n            { typeof(sbyte), TursoValueType.Integer },\n            { typeof(short), TursoValueType.Integer },\n            { typeof(string), TursoValueType.Text },\n            { typeof(TimeSpan), TursoValueType.Text },\n            { typeof(uint), TursoValueType.Integer },\n            { typeof(ulong), TursoValueType.Integer },\n            { typeof(ushort), TursoValueType.Integer }\n        };\n    \n    public TursoParameter()\n    {\n    }\n    \n    public TursoParameter(object value)\n    {\n        Value = value;\n    }\n\n    public TursoParameter(string parameterName, object value)\n    {\n        ParameterName = parameterName;\n        Value = value;\n    }\n\n    public TursoParameter(string parameterName, DbType dbType, object value)\n    {\n        ParameterName = parameterName;\n        DbType = dbType;\n        Value = value;\n    }\n    \n    public override void ResetDbType()\n    {\n        DbType = DbType.String;\n    }\n\n    public override DbType DbType { get; set; } = DbType.String;\n\n    public override ParameterDirection Direction\n    {\n        get => ParameterDirection.Input; \n        set\n        {\n            if (value != ParameterDirection.Input)\n            {\n                throw new ArgumentException(\"Only input parameters are supported\");\n            }\n        }\n    }\n    public override bool IsNullable { get; set; }\n    public override string? ParameterName { get; set; }\n    public override string? SourceColumn { get; set; }\n    public override object? Value { get; set; }\n    public override bool SourceColumnNullMapping { get; set; }\n\n    public TursoValue ToValue()\n    {\n        if (Value is null)\n            return new TursoValue { ValueType = TursoValueType.Null };\n        \n        var valueType = Value.GetType();\n        if (!TursoTypeMapping.TryGetValue(valueType, out var tursoValueType))\n        {\n            throw new ArgumentException($\"Parameter type {valueType} is not supported\");\n        }\n        \n        return GetTursoValue(Value, tursoValueType);\n    }\n\n    public override int Size\n    {\n        get => Value is string s \n            ? s.Length \n            : Value is byte[] bytes \n                ? bytes.Length \n                : 0;\n        set => throw new NotImplementedException();\n    }\n\n    private TursoValue GetTursoValue(object value, TursoValueType tursoValueType)\n    {\n        return tursoValueType switch\n        {\n            TursoValueType.Empty => new TursoValue() { ValueType = TursoValueType.Empty },\n            TursoValueType.Null => new TursoValue() { ValueType = TursoValueType.Null },\n            TursoValueType.Integer => new TursoValue() { ValueType = TursoValueType.Integer, IntValue = Convert.ToInt64(value) },\n            TursoValueType.Real => new TursoValue() { ValueType = TursoValueType.Real, RealValue = Convert.ToDouble(value) },\n            TursoValueType.Text => new TursoValue() { ValueType = TursoValueType.Text, StringValue = value.ToString()! },\n            TursoValueType.Blob => new TursoValue() { ValueType = TursoValueType.Blob, BlobValue = (byte[])value },\n            _ => throw new ArgumentOutOfRangeException(nameof(tursoValueType), tursoValueType, null)\n        };\n    }\n}"
  },
  {
    "path": "bindings/dotnet/src/Turso/TursoParameterCollection.cs",
    "content": "using System.Collections;\nusing System.Data.Common;\n\nnamespace Turso;\n\npublic class TursoParameterCollection : DbParameterCollection\n{\n    private readonly List<TursoParameter> _parameters = new();\n    public override int Count => _parameters.Count;\n\n    public override object SyncRoot => throw new NotImplementedException();\n\n    public override int Add(object value)\n    {\n        _parameters.Add(value as TursoParameter ?? new TursoParameter(value));\n        return _parameters.Count - 1;\n    }\n\n    public int AddWithValue(string parameterName, object value)\n    {\n        _parameters.Add(new TursoParameter(parameterName, value));\n        return _parameters.Count - 1;\n    }\n    \n    public override void AddRange(Array values)\n    {\n        for (var i = 0; i < values.Length; i++)\n        {\n            var value = values.GetValue(i)!;\n            var parameter = value as TursoParameter ?? new TursoParameter(value);\n            _parameters.Add(parameter);\n        }\n    }\n\n    public override void Clear()\n    {\n        _parameters.Clear();\n    }\n\n    public override bool Contains(object value)\n    {\n        return _parameters.Any(p => value is TursoParameter ? p == value : p.Value == value);\n    }\n\n    public override bool Contains(string value)\n    {\n        return _parameters.Any(p => p.ParameterName == value);\n    }\n\n    public override void CopyTo(Array array, int index)\n    {\n        _parameters.CopyTo((TursoParameter[])array, index);\n    }\n\n    public override IEnumerator GetEnumerator()\n    {\n        return _parameters.GetEnumerator();\n    }\n\n    public override int IndexOf(object value)\n    {\n        return _parameters.FindIndex(p => value is TursoParameter ? p == value : p.Value == value);\n    }\n\n    public override int IndexOf(string parameterName)\n    {\n        return _parameters.FindIndex(p => p.ParameterName == parameterName);\n    }\n\n    public override void Insert(int index, object value)\n    {\n        _parameters.Insert(index, value as TursoParameter ?? new TursoParameter(value));\n    }\n\n    public override void Remove(object value)\n    {\n        var index = IndexOf(value);\n        if (index == -1)\n            throw new ArgumentException($\"Parameter {value} not found\");\n        _parameters.RemoveAt(index);\n    }\n\n    public override void RemoveAt(int index)\n    {\n        _parameters.RemoveAt(index);\n    }\n\n    public override void RemoveAt(string parameterName)\n    {\n        var index = IndexOf(parameterName);\n        if (index == -1)\n            throw new ArgumentException($\"Parameter {parameterName} not found\");\n\n        _parameters.RemoveAt(index);\n    }\n\n    protected override DbParameter GetParameter(int index)\n    {\n        return _parameters[index];\n    }\n\n    protected override DbParameter GetParameter(string parameterName)\n    {\n        return _parameters.Find(p => p.ParameterName == parameterName)\n               ?? throw new ArgumentException($\"Parameter {parameterName} not found\");\n    }\n\n    protected override void SetParameter(int index, DbParameter value)\n    {\n        _parameters[index] = value as TursoParameter\n                             ?? throw new ArgumentException($\"Parameter {value} is not a TursoParameter\");\n    }\n\n    protected override void SetParameter(string parameterName, DbParameter value)\n    {\n        var index = IndexOf(parameterName);\n        if (index == -1)\n            throw new ArgumentException($\"Parameter {parameterName} not found\");\n        _parameters[index] = value as TursoParameter\n                             ?? throw new ArgumentException($\"Parameter {value} is not a TursoParameter\");\n    }\n}"
  },
  {
    "path": "bindings/dotnet/src/Turso/TursoTransaction.cs",
    "content": "using System.Data.Common;\nusing IsolationLevel = System.Data.IsolationLevel;\n\nnamespace Turso;\n\npublic class TursoTransaction : DbTransaction\n{\n    private TursoConnection _connection;\n    private IsolationLevel _isolationLevel;\n    private bool _completed = false;\n\n    public TursoTransaction(TursoConnection connection, IsolationLevel isolationLevel)\n    {\n        _connection = connection;\n        _isolationLevel = isolationLevel;\n\n        if (isolationLevel == IsolationLevel.ReadUncommitted)\n            connection.ExecuteNonQuery(\"PRAGMA read_uncommitted = 1;\");\n\n        connection.ExecuteNonQuery(\"BEGIN\");\n    }\n\n    protected override void Dispose(bool disposing)\n    {\n        if (!_completed)\n        {\n            Rollback();\n        }\n    }\n\n    public override IsolationLevel IsolationLevel => _isolationLevel;\n\n    protected override DbConnection? DbConnection => _connection;\n\n    public override void Commit()\n    {\n        _connection.ExecuteNonQuery(\"COMMIT;\");\n        CompleteTransaction();\n    }\n\n    public override void Rollback()\n    {\n        try\n        {\n            _connection.ExecuteNonQuery(\"ROLLBACK;\");\n        }\n        finally\n        {\n            CompleteTransaction();\n        }\n    }\n\n    private void CompleteTransaction()\n    {\n        if (_isolationLevel == IsolationLevel.ReadUncommitted)\n            _connection.ExecuteNonQuery(\"PRAGMA read_uncommitted = 0;\");\n        _completed = true;\n    }\n}"
  },
  {
    "path": "bindings/dotnet/src/Turso.Raw/Data/TursoNativeArray.cs",
    "content": "using System.Runtime.InteropServices;\n\nnamespace Turso.Raw.Data;\n\n[StructLayout(LayoutKind.Sequential)]\ninternal struct TursoNativeArray\n{\n    public IntPtr Data;\n    public UInt64 Length;\n}"
  },
  {
    "path": "bindings/dotnet/src/Turso.Raw/Data/TursoNativeRowValueUnion.cs",
    "content": "using System.Runtime.InteropServices;\n\nnamespace Turso.Raw.Data;\n\n[StructLayout(LayoutKind.Explicit)]\ninternal struct TursoNativeRowValueUnion\n{\n    [FieldOffset(0)]\n    public Int64 IntValue;\n    \n    [FieldOffset(0)]\n    public Double RealValue;\n    \n    [FieldOffset(0)]\n    public TursoNativeArray StringValue;\n    \n    [FieldOffset(0)]\n    public TursoNativeArray BlobValue;\n}"
  },
  {
    "path": "bindings/dotnet/src/Turso.Raw/Data/TursoNativeValue.cs",
    "content": "using System.Runtime.InteropServices;\nusing Turso.Raw.Public.Value;\n\nnamespace Turso.Raw.Data;\n\n[StructLayout(LayoutKind.Explicit)]\ninternal ref struct TursoNativeValue\n{\n    [FieldOffset(0)] \n    public TursoValueType ValueType;\n\n    [FieldOffset(8)] \n    public TursoNativeRowValueUnion RowValueUnion;\n}"
  },
  {
    "path": "bindings/dotnet/src/Turso.Raw/Public/Handles/TursoDatabaseHandle.cs",
    "content": "using System.Runtime.InteropServices;\n\nnamespace Turso.Raw.Public.Handles;\n\npublic class TursoDatabaseHandle() : SafeHandle(IntPtr.Zero, true)\n{\n    protected override bool ReleaseHandle()\n    {\n        TursoInterop.CloseDatabase(handle);\n        return true;\n    }\n\n    public void ThrowIfInvalid()\n    {\n        if (IsInvalid)\n            throw new NullReferenceException(\"database is invalid\");\n    }\n\n    public static TursoDatabaseHandle FromPtr(IntPtr ptr)\n    {\n        var handle = new TursoDatabaseHandle();\n        handle.SetHandle(ptr);\n        return handle;\n    }\n\n    public override bool IsInvalid => handle == IntPtr.Zero;\n}"
  },
  {
    "path": "bindings/dotnet/src/Turso.Raw/Public/Handles/TursoStatementHandle.cs",
    "content": "using System.Runtime.InteropServices;\n\nnamespace Turso.Raw.Public.Handles;\n\npublic class TursoStatementHandle() : SafeHandle(IntPtr.Zero, true)\n{\n    protected override bool ReleaseHandle()\n    {\n        TursoInterop.FreeStatement(handle);\n        return true;\n    }\n\n    public void ThrowIfInvalid()\n    {\n        if (IsInvalid)\n            throw new NullReferenceException(\"statement is invalid\");\n    }\n\n    public static TursoStatementHandle FromPtr(IntPtr ptr)\n    {\n        var handle = new TursoStatementHandle();\n        handle.SetHandle(ptr);\n        return handle;\n    }\n\n    public override bool IsInvalid => handle == IntPtr.Zero;\n\n}"
  },
  {
    "path": "bindings/dotnet/src/Turso.Raw/Public/TursoBindings.cs",
    "content": "using System.Runtime.InteropServices;\nusing System.Text;\nusing Turso.Raw.Data;\nusing Turso.Raw.Public.Handles;\nusing Turso.Raw.Public.Value;\n\nnamespace Turso.Raw.Public;\n\npublic static class TursoBindings\n{\n    public static TursoDatabaseHandle OpenDatabase(string path)\n    {\n        ArgumentNullException.ThrowIfNull(path);\n\n        var dbPtr = TursoInterop.OpenDatabase(path, out var errorPtr);\n        if (errorPtr != IntPtr.Zero)\n            ThrowException(errorPtr);\n\n        return TursoDatabaseHandle.FromPtr(dbPtr);\n    }\n\n    /// <summary>\n    /// Opens a database with local encryption.\n    /// </summary>\n    /// <param name=\"path\">The path to the database file.</param>\n    /// <param name=\"cipher\">The encryption cipher to use.</param>\n    /// <param name=\"hexkey\">The hex-encoded encryption key.</param>\n    /// <returns>A handle to the opened database.</returns>\n    public static TursoDatabaseHandle OpenDatabaseWithEncryption(string path, TursoEncryptionCipher cipher, string hexkey)\n    {\n        ArgumentNullException.ThrowIfNull(path);\n        ArgumentNullException.ThrowIfNull(hexkey);\n\n        var cipherStr = cipher.ToRustString();\n        var dbPtr = TursoInterop.OpenDatabaseWithEncryption(path, cipherStr, hexkey, out var errorPtr);\n        if (errorPtr != IntPtr.Zero)\n            ThrowException(errorPtr);\n\n        return TursoDatabaseHandle.FromPtr(dbPtr);\n    }\n\n    public static TursoStatementHandle PrepareStatement(TursoDatabaseHandle db, string sql)\n    {\n        db.ThrowIfInvalid();\n        ArgumentNullException.ThrowIfNull(sql);\n\n        var statementPtr = TursoInterop.PrepareStatement(db, sql, out var errorPtr);\n        if (errorPtr != IntPtr.Zero)\n            ThrowException(errorPtr);\n\n        return TursoStatementHandle.FromPtr(statementPtr);\n    }\n\n    public static void BindParameter(TursoStatementHandle statement, int index, TursoValue parameter)\n    {\n        statement.ThrowIfInvalid();\n        ArgumentOutOfRangeException.ThrowIfNegativeOrZero(index);\n\n        var nativeValue = FromValue(parameter, out var handle);\n        try\n        {\n            unsafe\n            {\n                var ptr = &nativeValue;\n                TursoInterop.BindParameter(statement, index, (IntPtr)ptr);\n            }\n        }\n        finally\n        {\n            if (handle.HasValue)\n                handle.Value.Free();\n        }\n    }\n\n    public static void BindNamedParameter(TursoStatementHandle statement, string name, TursoValue parameter)\n    {\n        statement.ThrowIfInvalid();\n        ArgumentNullException.ThrowIfNull(name);\n\n        var nativeValue = FromValue(parameter, out var handle);\n        try\n        {\n            unsafe\n            {\n                var ptr = &nativeValue;\n                TursoInterop.BindNamedParameter(statement, name, (IntPtr)ptr);\n            }\n        }\n        finally\n        {\n            if (handle.HasValue)\n                handle.Value.Free();\n        }\n    }\n\n    public static bool Read(TursoStatementHandle statement)\n    {\n        statement.ThrowIfInvalid();\n\n        var hasData = TursoInterop.StatementExecuteStep(statement, out var errorPtr);\n        if (errorPtr != IntPtr.Zero)\n            ThrowException(errorPtr);\n        return hasData;\n    }\n\n    public static TursoValue GetValue(TursoStatementHandle statement, int columnIndex)\n    {\n        statement.ThrowIfInvalid();\n        ArgumentOutOfRangeException.ThrowIfNegative(columnIndex);\n\n        var rowValue = TursoInterop.GetValueFromStatement(statement, columnIndex);\n        return rowValue.ValueType switch\n        {\n            TursoValueType.Empty => TursoValue.Empty(),\n            TursoValueType.Null => TursoValue.Null(),\n            TursoValueType.Integer => TursoValue.Int(rowValue.RowValueUnion.IntValue),\n            TursoValueType.Real => TursoValue.Real(rowValue.RowValueUnion.RealValue),\n            TursoValueType.Text => TursoValue.String(\n                Encoding.UTF8.GetString(ToArray(rowValue.RowValueUnion.StringValue))),\n            TursoValueType.Blob => TursoValue.Blob(ToArray(rowValue.RowValueUnion.BlobValue)),\n            _ => throw new ArgumentOutOfRangeException()\n        };\n    }\n\n    public static string GetName(TursoStatementHandle statement, int ordinal)\n    {\n        statement.ThrowIfInvalid();\n        ArgumentOutOfRangeException.ThrowIfNegative(ordinal);\n\n        var cname = TursoInterop.StatementColumnName(statement, ordinal);\n        try\n        {\n            return Marshal.PtrToStringUTF8(cname) ?? \"\";\n        }\n        finally\n        {\n            TursoInterop.FreeString(cname);\n        }\n    }\n\n    public static int GetFieldCount(TursoStatementHandle statement)\n    {\n        statement.ThrowIfInvalid();\n\n        return TursoInterop.StatementNumColumns(statement);\n    }\n\n    public static int RowsAffected(TursoStatementHandle statement)\n    {\n        statement.ThrowIfInvalid();\n\n        return (int)TursoInterop.StatementRowsAffected(statement);\n    }\n\n\n    public static bool HasRows(TursoStatementHandle statement)\n    {\n        statement.ThrowIfInvalid();\n\n        return TursoInterop.StatementHasRows(statement);\n    }\n\n\n    private static TursoNativeValue FromValue(TursoValue value, out GCHandle? handle)\n    {\n        handle = null;\n        var union = new TursoNativeRowValueUnion();\n        if (value.ValueType == TursoValueType.Integer)\n            union.IntValue = value.IntValue;\n        if (value.ValueType == TursoValueType.Real)\n            union.RealValue = value.RealValue;\n        if (value.ValueType == TursoValueType.Text)\n        {\n            var bytes = Encoding.UTF8.GetBytes(value.StringValue);\n            handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);\n            union.StringValue = new TursoNativeArray\n                { Data = handle.Value.AddrOfPinnedObject(), Length = (ulong)bytes.Length };\n        }\n\n        if (value.ValueType == TursoValueType.Blob)\n        {\n            handle = GCHandle.Alloc(value.BlobValue, GCHandleType.Pinned);\n            union.BlobValue = new TursoNativeArray\n                { Data = handle.Value.AddrOfPinnedObject(), Length = (ulong)value.BlobValue.Length };\n        }\n\n        return new TursoNativeValue\n        {\n            ValueType = value.ValueType,\n            RowValueUnion = union,\n        };\n    }\n\n    private static byte[] ToArray(TursoNativeArray array)\n    {\n        unsafe\n        {\n            var data = new Span<byte>((void*)array.Data, (int)array.Length);\n            return data.ToArray();\n        }\n    }\n\n    private static void ThrowException(IntPtr errorPtr)\n    {\n        var errorMessage = Marshal.PtrToStringUTF8(errorPtr);\n        var exception = new TursoException(errorMessage ?? \"Internal error\");\n        TursoInterop.FreeString(errorPtr);\n        throw exception;\n    }\n}"
  },
  {
    "path": "bindings/dotnet/src/Turso.Raw/Public/TursoException.cs",
    "content": "namespace Turso.Raw.Public;\n\npublic class TursoException(string message) : Exception(message);"
  },
  {
    "path": "bindings/dotnet/src/Turso.Raw/Public/Value/TursoEncryptionCipher.cs",
    "content": "namespace Turso.Raw.Public.Value;\n\n/// <summary>\n/// Supported encryption ciphers for local database encryption.\n/// </summary>\npublic enum TursoEncryptionCipher\n{\n    /// <summary>AES-128-GCM cipher</summary>\n    Aes128Gcm,\n    /// <summary>AES-256-GCM cipher</summary>\n    Aes256Gcm,\n    /// <summary>AEGIS-256 cipher</summary>\n    Aegis256,\n    /// <summary>AEGIS-256X2 cipher</summary>\n    Aegis256x2,\n    /// <summary>AEGIS-128L cipher</summary>\n    Aegis128l,\n    /// <summary>AEGIS-128X2 cipher</summary>\n    Aegis128x2,\n    /// <summary>AEGIS-128X4 cipher</summary>\n    Aegis128x4,\n}\n\ninternal static class TursoEncryptionCipherExtensions\n{\n    public static string ToRustString(this TursoEncryptionCipher cipher)\n    {\n        return cipher switch\n        {\n            TursoEncryptionCipher.Aes128Gcm => \"aes128gcm\",\n            TursoEncryptionCipher.Aes256Gcm => \"aes256gcm\",\n            TursoEncryptionCipher.Aegis256 => \"aegis256\",\n            TursoEncryptionCipher.Aegis256x2 => \"aegis256x2\",\n            TursoEncryptionCipher.Aegis128l => \"aegis128l\",\n            TursoEncryptionCipher.Aegis128x2 => \"aegis128x2\",\n            TursoEncryptionCipher.Aegis128x4 => \"aegis128x4\",\n            _ => throw new ArgumentOutOfRangeException(nameof(cipher), cipher, null)\n        };\n    }\n}\n"
  },
  {
    "path": "bindings/dotnet/src/Turso.Raw/Public/Value/TursoValue.cs",
    "content": "namespace Turso.Raw.Public.Value;\n\npublic struct TursoValue\n{\n    public TursoValueType ValueType;\n    public long IntValue;\n    public double RealValue;\n    public string StringValue;\n    public byte[] BlobValue;\n\n    public static TursoValue Empty() => new() { ValueType = TursoValueType.Empty };\n    public static TursoValue Null() => new() { ValueType = TursoValueType.Null };\n    public static TursoValue Int(Int64 value) => new() { ValueType = TursoValueType.Integer, IntValue = value };\n    public static TursoValue Real(Double value) => new() { ValueType = TursoValueType.Real, RealValue = value };\n    public static TursoValue String(string value) => new() { ValueType = TursoValueType.Text, StringValue = value };\n    public static TursoValue Blob(byte[] value) => new() { ValueType = TursoValueType.Blob, BlobValue = value };\n}"
  },
  {
    "path": "bindings/dotnet/src/Turso.Raw/Public/Value/TursoValueType.cs",
    "content": "namespace Turso.Raw.Public.Value;\n\npublic enum TursoValueType\n{\n    Empty = 0,\n    Null = 1,\n    Integer = 2,\n    Real = 3,\n    Text = 4,\n    Blob = 5,\n}"
  },
  {
    "path": "bindings/dotnet/src/Turso.Raw/Turso.Raw.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n    <PropertyGroup>\n        <TargetFramework>net9.0</TargetFramework>\n        <ImplicitUsings>enable</ImplicitUsings>\n        <Nullable>enable</Nullable>\n        <AllowUnsafeBlocks>true</AllowUnsafeBlocks>\n\n        <PackageId>Turso.Raw</PackageId>\n        <Description>Dotnet bindings for turso database</Description>\n        <Version>0.0.1</Version>\n    </PropertyGroup>\n\n    <ItemGroup Condition=\"'$(Configuration)'=='Debug'\">\n        <None Include=\"..\\..\\rs_compiled\\x86_64-pc-windows-msvc\\debug\\turso_dotnet.dll\" \n              Visible=\"false\" \n              Condition=\"Exists('..\\..\\rs_compiled\\x86_64-pc-windows-msvc\\debug\\turso_dotnet.dll')\">\n            <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n            <TargetPath>runtimes\\win-x64\\native\\turso_dotnet.dll</TargetPath>\n        </None>\n        <None Include=\"..\\..\\rs_compiled\\x86_64-unknown-linux-gnu\\debug\\libturso_dotnet.so\"\n              Visible=\"false\"\n              Condition=\"Exists('..\\..\\rs_compiled\\x86_64-unknown-linux-gnu\\debug\\libturso_dotnet.so')\">\n            <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n            <TargetPath>runtimes\\linux-x64\\native\\turso_dotnet.so</TargetPath>\n        </None>\n        <None Include=\"..\\..\\rs_compiled\\x86_64-apple-darwin\\debug\\libturso_dotnet.dylib\"\n              Visible=\"false\"\n              Condition=\"Exists('..\\..\\rs_compiled\\x86_64-apple-darwin\\debug\\libturso_dotnet.dylib')\">\n            <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n            <TargetPath>runtimes\\osx-x64\\native\\turso_dotnet.dylib</TargetPath>\n        </None>\n        <None Include=\"..\\..\\rs_compiled\\aarch64-apple-darwin\\debug\\libturso_dotnet.dylib\"\n              Visible=\"false\"\n              Condition=\"Exists('..\\..\\rs_compiled\\aarch64-apple-darwin\\debug\\libturso_dotnet.dylib')\">\n            <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n            <TargetPath>runtimes\\osx-arm\\native\\turso_dotnet.dylib</TargetPath>\n        </None>\n    </ItemGroup>\n    <ItemGroup Condition=\"'$(Configuration)'=='Release'\">\n        <None Include=\"..\\..\\rs_compiled\\x86_64-pc-windows-msvc\\release\\turso_dotnet.dll\"\n              Visible=\"false\"\n              Condition=\"Exists('..\\..\\rs_compiled\\x86_64-pc-windows-msvc\\release\\turso_dotnet.dll')\">\n            <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n            <TargetPath>runtimes\\win-x64\\native\\turso_dotnet.dll</TargetPath>\n            <Pack>true</Pack>\n            <PackagePath>runtimes\\win-x64\\native\\turso_dotnet.dll</PackagePath>\n        </None>\n        <None Include=\"..\\..\\rs_compiled\\x86_64-unknown-linux-gnu\\release\\libturso_dotnet.so\"\n              Visible=\"false\"\n              Condition=\"Exists('..\\..\\rs_compiled\\x86_64-unknown-linux-gnu\\release\\libturso_dotnet.so')\">\n            <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n            <TargetPath>runtimes\\linux-x64\\native\\turso_dotnet.so</TargetPath>\n            <Pack>true</Pack>\n            <PackagePath>runtimes\\linux-x64\\native\\turso_dotnet.so</PackagePath>\n        </None>\n        <None Include=\"..\\..\\rs_compiled\\x86_64-apple-darwin\\release\\libturso_dotnet.dylib\"\n              Visible=\"false\"\n              Condition=\"Exists('..\\..\\rs_compiled\\x86_64-apple-darwin\\release\\libturso_dotnet.dylib')\">\n            <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n            <TargetPath>runtimes\\osx-x64\\native\\turso_dotnet.dylib</TargetPath>\n            <Pack>true</Pack>\n            <PackagePath>runtimes\\osx-x64\\native\\turso_dotnet.dylib</PackagePath>\n        </None>\n        <None Include=\"..\\..\\rs_compiled\\aarch64-apple-darwin\\release\\libturso_dotnet.dylib\"\n              Visible=\"false\"\n              Condition=\"Exists('..\\..\\rs_compiled\\aarch64-apple-darwin\\release\\libturso_dotnet.dylib')\">\n            <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n            <TargetPath>runtimes\\osx-arm64\\native\\turso_dotnet.dylib</TargetPath>\n            <Pack>true</Pack>\n            <PackagePath>runtimes\\osx-arm64\\native\\turso_dotnet.dylib</PackagePath>\n        </None>\n    </ItemGroup>\n</Project>\n"
  },
  {
    "path": "bindings/dotnet/src/Turso.Raw/TursoInterop.cs",
    "content": "﻿using System.Runtime.InteropServices;\nusing Turso.Raw.Data;\nusing Turso.Raw.Public.Handles;\n\nnamespace Turso.Raw;\n\ninternal static class TursoInterop\n{\n    private const string DllName = \"turso_dotnet\";\n    \n    [DllImport(DllName, EntryPoint = \"db_open\", CallingConvention = CallingConvention.Cdecl)]\n    public static extern IntPtr OpenDatabase(string path, out IntPtr errorPtr);\n\n    [DllImport(DllName, EntryPoint = \"db_open_with_encryption\", CallingConvention = CallingConvention.Cdecl)]\n    public static extern IntPtr OpenDatabaseWithEncryption(string path, string? cipher, string? hexkey, out IntPtr errorPtr);\n\n    [DllImport(DllName, EntryPoint = \"db_close\", CallingConvention = CallingConvention.Cdecl)]\n    public static extern void CloseDatabase(IntPtr db);\n\n    [DllImport(DllName, EntryPoint = \"free_string\", CallingConvention = CallingConvention.Cdecl)]\n    public static extern void FreeString(IntPtr stringPtr);\n    \n    [DllImport(DllName, EntryPoint = \"db_prepare_statement\", CallingConvention = CallingConvention.Cdecl)]\n    public static extern IntPtr PrepareStatement(TursoDatabaseHandle db, string sql, out IntPtr errorPtr);\n    \n    [DllImport(DllName, EntryPoint = \"free_statement\", CallingConvention = CallingConvention.Cdecl)]\n    public static extern void FreeStatement(IntPtr statement);\n    \n    [DllImport(DllName, EntryPoint = \"bind_parameter\", CallingConvention = CallingConvention.Cdecl)]\n    public static extern void BindParameter(TursoStatementHandle statement, int index, IntPtr tursoValue);\n\n    [DllImport(DllName, EntryPoint = \"bind_named_parameter\", CallingConvention = CallingConvention.Cdecl)]\n    public static extern void BindNamedParameter(TursoStatementHandle statement, string parameterName, IntPtr tursoValue);\n\n    [DllImport(DllName, EntryPoint = \"db_statement_execute_step\", CallingConvention = CallingConvention.Cdecl)]\n    [return: MarshalAs(UnmanagedType.I1)]\n    public static extern bool StatementExecuteStep(TursoStatementHandle statement, out IntPtr errorPtr);\n    \n    [DllImport(DllName, EntryPoint = \"db_statement_nchange\", CallingConvention = CallingConvention.Cdecl)]\n    public static extern long StatementRowsAffected(TursoStatementHandle statement);\n\n    [DllImport(DllName, EntryPoint = \"db_statement_get_value\", CallingConvention = CallingConvention.Cdecl)]\n    public static extern TursoNativeValue GetValueFromStatement(TursoStatementHandle statement, int columnIndex);\n    \n    [DllImport(DllName, EntryPoint = \"db_statement_num_columns\", CallingConvention = CallingConvention.Cdecl)]\n    public static extern int StatementNumColumns(TursoStatementHandle statement);\n    \n    [DllImport(DllName, EntryPoint = \"db_statement_column_name\", CallingConvention = CallingConvention.Cdecl)]\n    public static extern IntPtr StatementColumnName(TursoStatementHandle statement, int index);\n    \n    [DllImport(DllName, EntryPoint = \"db_statement_has_rows\", CallingConvention = CallingConvention.Cdecl)]\n    [return: MarshalAs(UnmanagedType.I1)]\n    public static extern bool StatementHasRows(TursoStatementHandle statement);\n\n}"
  },
  {
    "path": "bindings/dotnet/src/Turso.Tests/Turso.Tests.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n    <PropertyGroup>\n        <TargetFramework>net9.0</TargetFramework>\n        <LangVersion>latest</LangVersion>\n        <ImplicitUsings>enable</ImplicitUsings>\n        <Nullable>enable</Nullable>\n        <IsPackable>false</IsPackable>\n    </PropertyGroup>\n\n    <ItemGroup>\n        <PackageReference Include=\"AwesomeAssertions\" Version=\"9.1.0\" />\n        <PackageReference Include=\"coverlet.collector\" Version=\"6.0.2\"/>\n        <PackageReference Include=\"Microsoft.Data.Sqlite\" Version=\"9.0.8\" />\n        <PackageReference Include=\"Microsoft.NET.Test.Sdk\" Version=\"17.12.0\"/>\n        <PackageReference Include=\"NUnit\" Version=\"4.2.2\"/>\n        <PackageReference Include=\"NUnit.Analyzers\" Version=\"4.4.0\"/>\n        <PackageReference Include=\"NUnit3TestAdapter\" Version=\"4.6.0\"/>\n    </ItemGroup>\n\n    <ItemGroup>\n        <Using Include=\"NUnit.Framework\"/>\n    </ItemGroup>\n    \n    <ItemGroup>\n        <ProjectReference Include=\"..\\Turso.Raw\\Turso.Raw.csproj\" />\n        <ProjectReference Include=\"..\\Turso\\Turso.csproj\" />\n    </ItemGroup>\n</Project>\n"
  },
  {
    "path": "bindings/dotnet/src/Turso.Tests/TursoTests.cs",
    "content": "﻿using System.Data.Common;\nusing AwesomeAssertions;\nusing Turso.Raw.Public;\nusing Turso.Raw.Public.Value;\n\nnamespace Turso.Tests;\n\npublic class TursoTests\n{\n    [Test]\n    public void TestSimpleQuery()\n    {\n        using var connection = new TursoConnection();\n        connection.Open();\n\n        using var cmd = new TursoCommand(connection, \"SELECT * FROM generate_series(1,2,1)\");\n\n        using var reader = cmd.ExecuteReader();\n\n        reader.Read().Should().BeTrue();\n        reader.GetInt32(0).Should().Be(1);\n\n        reader.Read().Should().BeTrue();\n        reader.GetInt32(0).Should().Be(2);\n\n        reader.Read().Should().BeFalse();\n    }\n\n    [Test]\n    public void TestPrepareStatement()\n    {\n        using var connection = new TursoConnection();\n        connection.Open();\n\n        using var cmd = new TursoCommand(connection, \"SELECT * FROM generate_series(?,?,?)\");\n        cmd.Parameters.Add(1);\n        cmd.Parameters.Add(2);\n        cmd.Parameters.Add(1);\n        cmd.Prepare();\n\n        using var reader = cmd.ExecuteReader();\n\n        reader.Read().Should().BeTrue();\n        reader.GetInt32(0).Should().Be(1);\n\n        reader.Read().Should().BeTrue();\n        reader.GetInt32(0).Should().Be(2);\n\n        reader.Read().Should().BeFalse();\n    }\n\n    [TestCase(\"stringValue\", TestName = \"TestStringValue\")]\n    [TestCase(new byte[] { 1, 2, 3, 4, 5 }, TestName = \"TestBlobValue\")]\n    [TestCase(1, TestName = \"TestIntValue\")]\n    [TestCase(2.5, TestName = \"TestRealValue\")]\n    public void TestDifferentTypes(object typedValue)\n    {\n        using var connection = new TursoConnection();\n        connection.Open();\n\n        using (var create = new TursoCommand(connection, \"CREATE TABLE t(v)\"))\n        {\n            create.ExecuteNonQuery().Should().Be(0);\n        }\n\n        using (var insert = new TursoCommand(connection, \"INSERT INTO t VALUES (?)\"))\n        {\n            insert.Parameters.Add(typedValue);\n            insert.ExecuteNonQuery().Should().Be(1);\n        }\n\n        using var select = new TursoCommand(connection, \"SELECT v FROM t\");\n        using var reader = select.ExecuteReader();\n\n        reader.Read().Should().BeTrue();\n\n        switch (typedValue)\n        {\n            case string s:\n                reader.GetString(0).Should().Be(s);\n                break;\n            case byte[] bytes:\n                ((byte[])reader.GetValue(0)).SequenceEqual(bytes).Should().BeTrue();\n                break;\n            case int i:\n                reader.GetInt32(0).Should().Be(i);\n                break;\n            case double d:\n                reader.GetDouble(0).Should().Be(d);\n                break;\n            default:\n                throw new AssertionException($\"Unsupported test type: {typedValue.GetType()}\");\n        }\n\n        reader.Read().Should().BeFalse();\n    }\n\n\n    [Test]\n    public void TestBindNamedParameter()\n    {\n        using var connection = new TursoConnection();\n        connection.Open();\n\n        using var cmd = new TursoCommand(connection, \"SELECT * FROM generate_series(?,:stop,?1)\");\n        cmd.Parameters.Add(1);\n        cmd.Parameters.AddWithValue(\":stop\", 2);\n        cmd.Prepare();\n\n        using var reader = cmd.ExecuteReader();\n\n        reader.Read().Should().BeTrue();\n        reader.GetInt32(0).Should().Be(1);\n\n        reader.Read().Should().BeTrue();\n        reader.GetInt32(0).Should().Be(2);\n\n        reader.Read().Should().BeFalse();\n    }\n\n    [Test]\n    public void TestInsertData()\n    {\n        using var connection = new TursoConnection();\n        connection.Open();\n\n        using var create = new TursoCommand(connection, \"CREATE TABLE t(id INTEGER, name TEXT)\");\n        create.ExecuteNonQuery().Should().Be(0);\n\n        using var insert = new TursoCommand(connection, \"INSERT INTO t(id, name) VALUES (1, 'alice'), (2, 'bob')\");\n        insert.ExecuteNonQuery().Should().Be(2);\n\n        using var countCmd = new TursoCommand(connection, \"SELECT COUNT(*) FROM t\");\n        using var reader = countCmd.ExecuteReader();\n        reader.Read().Should().BeTrue();\n        reader.GetInt32(0).Should().Be(2);\n        reader.Read().Should().BeFalse();\n    }\n\n    [Test]\n    public void TestFetchSpecificColumns()\n    {\n        using var connection = new TursoConnection();\n        connection.Open();\n\n        using var create = new TursoCommand(connection, \"CREATE TABLE t(id INTEGER, name TEXT, age INTEGER)\");\n        create.ExecuteNonQuery().Should().Be(0);\n\n        using var insert = new TursoCommand(connection, \"INSERT INTO t VALUES (1,'alice',30),(2,'bob',40)\");\n        insert.ExecuteNonQuery().Should().Be(2);\n\n        using var select = new TursoCommand(connection, \"SELECT name, age FROM t WHERE id = 2\");\n        using var reader = select.ExecuteReader();\n        reader.Read().Should().BeTrue();\n        reader.GetString(0).Should().Be(\"bob\");\n        reader.GetInt32(1).Should().Be(40);\n        reader.Read().Should().BeFalse();\n    }\n\n    [Test]\n    public void TestQueryError()\n    {\n        using var connection = new TursoConnection();\n        connection.Open();\n\n        using var cmd = new TursoCommand(connection, \"SELECT * FROM table_that_does_not_exist\");\n        cmd.Invoking(x => x.ExecuteReader()).Should().Throw<TursoException>()\n            .WithMessage(\"Unable to prepare statement: Parse error: no such table: table_that_does_not_exist\");\n    }\n\n    [Test]\n    [Ignore(\"https://github.com/tursodatabase/turso/pull/3591\")]\n    public void TestCommitTransaction()\n    {\n        using var connection = new TursoConnection(\"Data Source=./turso.db\");\n        connection.Open();\n\n        using var connection2 = new TursoConnection(\"Data Source=./turso.db\");\n        connection2.Open();\n\n        connection.ExecuteNonQuery(\"CREATE TABLE IF NOT EXISTS t(id INTEGER)\");\n        connection.ExecuteNonQuery(\"DELETE FROM t\");\n\n        using var tx = connection.BeginTransaction();\n\n        using var insert = new TursoCommand(connection, \"INSERT INTO t VALUES (1),(2)\");\n        insert.ExecuteNonQuery().Should().Be(2);\n\n        using var selectBefore = new TursoCommand(connection2, \"SELECT COUNT(*) FROM t\");\n        using (var readerBefore = selectBefore.ExecuteReader())\n        {\n            readerBefore.Read().Should().BeTrue();\n            readerBefore.GetInt32(0).Should().Be(0);\n        }\n\n        tx.Commit();\n\n        using var selectAfter = new TursoCommand(connection2, \"SELECT COUNT(*) FROM t\");\n        using var readerAfter = selectAfter.ExecuteReader();\n        readerAfter.Read().Should().BeTrue();\n        readerAfter.GetInt32(0).Should().Be(2);\n    }\n\n    [Test]\n    public void TestRollbackTransaction()\n    {\n        using var connection = new TursoConnection();\n        connection.Open();\n\n\n        using var create = new TursoCommand(connection, \"CREATE TABLE t(id INTEGER)\");\n        create.ExecuteNonQuery().Should().Be(0);\n\n        using var tx = connection.BeginTransaction();\n        using var insert = new TursoCommand(connection, \"INSERT INTO t VALUES (1),(2)\");\n        insert.ExecuteNonQuery().Should().Be(2);\n\n        using var select = new TursoCommand(connection, \"SELECT COUNT(*) FROM t\");\n        using var reader = select.ExecuteReader();\n        reader.Read().Should().BeTrue();\n        reader.GetInt32(0).Should().Be(2);\n\n        tx.Rollback();\n\n        using var select2 = new TursoCommand(connection, \"SELECT COUNT(*) FROM t\");\n        using var reader2 = select2.ExecuteReader();\n        reader2.Read().Should().BeTrue();\n        reader2.GetInt32(0).Should().Be(0);\n    }\n\n    [Test]\n    public void TestDataReaderEnumerable()\n    {\n        using var connection = new TursoConnection();\n        connection.Open();\n\n        using var create = new TursoCommand(connection, \"CREATE TABLE t(id INTEGER, name TEXT, age INTEGER)\");\n        create.ExecuteNonQuery().Should().Be(0);\n\n        using var insert = new TursoCommand(connection, \"INSERT INTO t VALUES (1,'alice',30),(2,'bob',40),(3,'charlie',50)\");\n        insert.ExecuteNonQuery().Should().Be(3);\n\n        using var select = new TursoCommand(connection, \"SELECT id, name, age FROM t ORDER BY id\");\n        using var reader = select.ExecuteReader();\n\n        var results = new List<(long id, string name, long age)>();\n\n        foreach (DbDataRecord record in reader)\n        {\n            var id = record.GetInt64(0);\n            var name = record.GetString(1);\n            var age = record.GetInt64(2);\n            results.Add((id, name, age));\n        }\n\n        results.Should().HaveCount(3);\n        results[0].Should().Be((1, \"alice\", 30));\n        results[1].Should().Be((2, \"bob\", 40));\n        results[2].Should().Be((3, \"charlie\", 50));\n    }\n\n    [Test]\n    public void TestEncryption()\n    {\n        var tempPath = Path.Combine(Path.GetTempPath(), $\"turso_test_encrypted_{Guid.NewGuid()}.db\");\n        var hexkey = \"b1bbfda4f589dc9daaf004fe21111e00dc00c98237102f5c7002a5669fc76327\";\n        var wrongKey = \"aaaaaaa4f589dc9daaf004fe21111e00dc00c98237102f5c7002a5669fc76327\";\n\n        try\n        {\n            // Create encrypted database\n            using (var connection = new TursoConnection($\"Data Source={tempPath};Encryption Cipher=aegis256;Encryption Key={hexkey}\"))\n            {\n                connection.Open();\n\n                using var create = new TursoCommand(connection, \"CREATE TABLE t(x TEXT)\");\n                create.ExecuteNonQuery();\n\n                using var insert = new TursoCommand(connection, \"INSERT INTO t VALUES ('secret')\");\n                insert.ExecuteNonQuery();\n\n                using var checkpoint = new TursoCommand(connection, \"PRAGMA wal_checkpoint(truncate)\");\n                checkpoint.ExecuteNonQuery();\n            }\n\n            // Verify data is encrypted on disk\n            var content = File.ReadAllBytes(tempPath);\n            content.Length.Should().BeGreaterThan(1024);\n            var contentStr = System.Text.Encoding.UTF8.GetString(content);\n            contentStr.Should().NotContain(\"secret\");\n\n            // Verify we can re-open with the same key\n            using (var connection2 = new TursoConnection($\"Data Source={tempPath};Encryption Cipher=aegis256;Encryption Key={hexkey}\"))\n            {\n                connection2.Open();\n\n                using var select = new TursoCommand(connection2, \"SELECT * FROM t\");\n                using var reader = select.ExecuteReader();\n                reader.Read().Should().BeTrue();\n                reader.GetString(0).Should().Be(\"secret\");\n            }\n\n            // Verify opening with wrong key fails\n            Action openWithWrongKey = () =>\n            {\n                using var conn = new TursoConnection($\"Data Source={tempPath};Encryption Cipher=aegis256;Encryption Key={wrongKey}\");\n                conn.Open();\n                using var select = new TursoCommand(conn, \"SELECT * FROM t\");\n                using var reader = select.ExecuteReader();\n                reader.Read();\n            };\n            openWithWrongKey.Should().Throw<Exception>();\n\n            // Verify opening without encryption fails\n            Action openWithoutEncryption = () =>\n            {\n                using var conn = new TursoConnection($\"Data Source={tempPath}\");\n                conn.Open();\n                using var select = new TursoCommand(conn, \"SELECT * FROM t\");\n                using var reader = select.ExecuteReader();\n                reader.Read();\n            };\n            openWithoutEncryption.Should().Throw<Exception>();\n        }\n        finally\n        {\n            if (File.Exists(tempPath)) File.Delete(tempPath);\n        }\n    }\n}"
  },
  {
    "path": "bindings/go/LICENSE.md",
    "content": "MIT License\n\nCopyright 2025 the Turso authors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n"
  },
  {
    "path": "bindings/go/README.md",
    "content": "<p align=\"center\">\n  <h1 align=\"center\">Turso Database for Go</h1>\n</p>\n\n<p align=\"center\">\n  <a title=\"Go\" target=\"_blank\" href=\"https://pkg.go.dev/turso.tech/database/tursogo\"><img src=\"https://pkg.go.dev/badge/turso.tech/database/tursogo.svg\" alt=\"Go Reference\"></a>\n  <a title=\"MIT\" target=\"_blank\" href=\"https://github.com/tursodatabase/turso/blob/main/LICENSE.md\"><img src=\"http://img.shields.io/badge/license-MIT-orange.svg?style=flat-square\"></a>\n</p>\n<p align=\"center\">\n  <a title=\"Users Discord\" target=\"_blank\" href=\"https://tur.so/discord\"><img alt=\"Chat with other users of Turso on Discord\" src=\"https://img.shields.io/discord/933071162680958986?label=Discord&logo=Discord&style=social\"></a>\n</p>\n\n---\n\n## About\n\n> **⚠️ Warning:** This software is in BETA. It may still contain bugs and unexpected behavior. Use caution with production data and ensure you have backups.\n\n## Features\n\n- **SQLite compatible:** SQLite query language and file format support ([status](https://github.com/tursodatabase/turso/blob/main/COMPAT.md)).\n- **In-process**: No network overhead, runs directly in your Go process\n- **Cross-platform**: Supports Linux, macOS, Windows\n- **Remote partial sync**: Bootstrap from a remote database, pull remote changes, and push local changes when online &mdash; all while enjoying a fully operational database offline.\n- **No CGO**: This driver uses the awesome [purego](https://github.com/ebitengine/purego) library to call C (in this case Rust with C ABI) functions from Go.\n\n## Installation\n\n```bash\ngo get turso.tech/database/tursogo\n```\n\n## Get Started\n\n```go\npackage main\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n\t\"os\"\n\n\t_ \"turso.tech/database/tursogo\"\n)\n\nfunc main() {\n\tconn, err := sql.Open(\"turso\", \":memory:\")\n\tif err != nil {\n\t\tfmt.Printf(\"Error: %v\\n\", err)\n\t\tos.Exit(1)\n\t}\n\tsql := \"CREATE table go_turso (foo INTEGER, bar TEXT)\"\n\t_, _ = conn.Exec(sql)\n\n\tsql = \"INSERT INTO go_turso (foo, bar) values (?, ?)\"\n\tstmt, _ := conn.Prepare(sql)\n\tdefer stmt.Close()\n\t_, _ = stmt.Exec(42, \"turso\")\n\trows, _ := conn.Query(\"SELECT * from go_turso\")\n\tdefer rows.Close()\n\tfor rows.Next() {\n\t\tvar a int\n\t\tvar b string\n\t\t_ = rows.Scan(&a, &b)\n\t\tfmt.Printf(\"%d, %s\\n\", a, b) // 42, turso\n\t}\n}\n```\n\n## Sync Driver\nUse a remote Turso database while working locally. You can bootstrap local state from the remote, pull remote changes, and push local commits.\n\nNote: You need a Turso remote URL. See the Turso docs for provisioning and authentication.\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\n\tturso \"turso.tech/database/tursogo\"\n)\n\nfunc main() {\n\tctx := context.Background()\n\n\t// Connect a local database to a remote Turso database\n\tdb, err := turso.NewTursoSyncDb(ctx, turso.TursoSyncDbConfig{\n\t\tPath:      \":memory:\", // local db path (or a file path)\n\t\tRemoteUrl: \"https://<db>.<region>.turso.io\",\n\t\tAuthToken: \"<authToken>\",\n\t})\n\tif err != nil {\n\t\tfmt.Printf(\"Error: %v\\n\", err)\n\t\tos.Exit(1)\n\t}\n\n\tconn, err := db.Connect(ctx)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tdefer conn.Close()\n\n\tsql := \"CREATE table go_turso (foo INTEGER, bar TEXT)\"\n\t_, _ = conn.ExecContext(ctx, sql)\n\n\tsql = \"INSERT INTO go_turso (foo, bar) values (?, ?)\"\n\tstmt, _ := conn.PrepareContext(ctx, sql)\n\tdefer stmt.Close()\n\t_, _ = stmt.ExecContext(ctx, 42, \"turso\")\n\n\t// Push local commits to remote\n\t_ = db.Push(ctx)\n\n\t// Pull new changes from remote into local\n\t_, _ = db.Pull(ctx)\n\n\trows, _ := conn.QueryContext(ctx, \"SELECT * from go_turso\")\n\tdefer rows.Close()\n\tfor rows.Next() {\n\t\tvar a int\n\t\tvar b string\n\t\t_ = rows.Scan(&a, &b)\n\t\tfmt.Printf(\"%d, %s\\n\", a, b) // 42, turso\n\t}\n\n\t// Optional: inspect and manage sync state\n\tstats, err := db.Stats(ctx)\n\tif err != nil {\n\t\tlog.Println(\"Stats unavailable:\", err)\n\t} else {\n\t\tlog.Println(\"Current revision:\", stats.NetworkReceivedBytes)\n\t}\n\n\t_ = db.Checkpoint(ctx) // compact local WAL after many writes\n}\n\n```\n\n## License\n\nThis project is licensed under the [MIT license](../../LICENSE.md).\n\n## Support\n\n- [GitHub Issues](https://github.com/tursodatabase/turso/issues)\n- [Documentation](https://docs.turso.tech)\n- [Discord Community](https://tur.so/discord)\n"
  },
  {
    "path": "bindings/go/VERSION",
    "content": "6119c6f0"
  },
  {
    "path": "bindings/go/bindings.go",
    "content": "package turso\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\n\tturso_libs \"github.com/tursodatabase/turso-go-platform-libs\"\n)\n\nvar initLibrary sync.Once\n\nfunc InitLibrary(strategy turso_libs.LoadTursoLibraryConfig) {\n\tinitLibrary.Do(func() {\n\t\tlibrary, err := turso_libs.LoadTursoLibrary(strategy)\n\t\tif err != nil {\n\t\t\tpanic(fmt.Errorf(\"unable to load turso library: %w\", err))\n\t\t}\n\t\tregisterTursoDb(library)\n\t\tregisterTursoSync(library)\n\t})\n}\n"
  },
  {
    "path": "bindings/go/bindings_db.go",
    "content": "package turso // import \"github.com/tursodatabase/turso/go\"\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"runtime\"\n\t\"unsafe\"\n\n\t\"github.com/ebitengine/purego\"\n)\n\n// package-level errors\nvar (\n\tErrTursoBusy         = errors.New(\"turso: database is busy\")\n\tErrTursoInterrupt    = errors.New(\"turso: interrupted\")\n\tErrTursoGeneric      = errors.New(\"turso: error\")\n\tErrTursoMisuse       = errors.New(\"turso: API misuse\")\n\tErrTursoConstraint   = errors.New(\"turso: constraint failed\")\n\tErrTursoReadOnly     = errors.New(\"turso: database is readonly\")\n\tErrTursoDatabaseFull = errors.New(\"turso: database is full\")\n\tErrTursoNotADb       = errors.New(\"turso: not a database\")\n\tErrTursoCorrupt      = errors.New(\"turso: database is corrupt\")\n)\n\n// DefaultBusyTimeout is the default busy timeout in milliseconds (5 seconds).\n// This matches common SQLite production recommendations. Set _busy_timeout=-1\n// in the DSN to disable the busy handler completely.\nconst DefaultBusyTimeout = 5000\n\n// define all necessary constants first\ntype TursoStatusCode int32\n\nconst (\n\tTURSO_OK            TursoStatusCode = 0\n\tTURSO_DONE          TursoStatusCode = 1\n\tTURSO_ROW           TursoStatusCode = 2\n\tTURSO_IO            TursoStatusCode = 3\n\tTURSO_BUSY          TursoStatusCode = 4\n\tTURSO_INTERRUPT     TursoStatusCode = 5\n\tTURSO_ERROR         TursoStatusCode = 127\n\tTURSO_MISUSE        TursoStatusCode = 128\n\tTURSO_CONSTRAINT    TursoStatusCode = 129\n\tTURSO_READONLY      TursoStatusCode = 130\n\tTURSO_DATABASE_FULL TursoStatusCode = 131\n\tTURSO_NOTADB        TursoStatusCode = 132\n\tTURSO_CORRUPT       TursoStatusCode = 133\n)\n\ntype TursoType int32\n\nconst (\n\tTURSO_TYPE_UNKNOWN TursoType = 0\n\tTURSO_TYPE_INTEGER TursoType = 1\n\tTURSO_TYPE_REAL    TursoType = 2\n\tTURSO_TYPE_TEXT    TursoType = 3\n\tTURSO_TYPE_BLOB    TursoType = 4\n\tTURSO_TYPE_NULL    TursoType = 5\n)\n\ntype TursoTracingLevel int32\n\nconst (\n\tTURSO_TRACING_LEVEL_ERROR TursoTracingLevel = 1\n\tTURSO_TRACING_LEVEL_WARN  TursoTracingLevel = 2\n\tTURSO_TRACING_LEVEL_INFO  TursoTracingLevel = 3\n\tTURSO_TRACING_LEVEL_DEBUG TursoTracingLevel = 4\n\tTURSO_TRACING_LEVEL_TRACE TursoTracingLevel = 5\n)\n\n// define opaque pointers as-is and accept them as exact arguments\ntype turso_database_t struct{}\ntype turso_connection_t struct{}\ntype turso_statement_t struct{}\n\ntype TursoDatabase *turso_database_t\ntype TursoConnection *turso_connection_t\ntype TursoStatement *turso_statement_t\n\n// define all public binding types\ntype TursoLog struct {\n\tMessage   string\n\tTarget    string\n\tFile      string\n\tTimestamp uint64\n\tLine      uint\n\tLevel     TursoTracingLevel\n}\n\ntype TursoConfig struct {\n\t// Logger is an optional callback invoked by the library.\n\tLogger   func(log TursoLog)\n\tLogLevel string // zero-terminated C string expected by C; wrapper converts\n}\n\ntype TursoDatabaseEncryptionOpts struct {\n\tCipher string\n\tHexkey string\n}\n\ntype TursoDatabaseConfig struct {\n\t// Path to the database file or \":memory:\"\n\tPath string\n\t// Optional comma separated list of experimental features to enable\n\tExperimentalFeatures string\n\t// Parameter which defines who drives the IO - callee or the caller\n\tAsyncIO bool\n\t// optional VFS parameter explicitly specifying FS backend for the database.\n\t// Available options are:\n\t// - \"memory\": in-memory backend\n\t// - \"syscall\": generic syscall backend\n\t// - \"io_uring\": IO uring (supported only on Linux)\n\t// - \"experimental_win_iocp\": Windows IOCP [experimental](supported only on Windows)\n\tVfs string\n\t// optional encryption parameters\n\t// as encryption is experimental - ExperimentalFeatures must have \"encryption\" in the list\n\tEncryption TursoDatabaseEncryptionOpts\n\t// BusyTimeout in milliseconds (0 = no timeout, immediate SQLITE_BUSY)\n\tBusyTimeout int\n}\n\n// define all necessary private C structs\ntype turso_slice_ref_t struct {\n\tptr uintptr\n\tlen uintptr\n}\n\ntype turso_log_t struct {\n\tmessage   uintptr // const char*\n\ttarget    uintptr // const char*\n\tfile      uintptr // const char*\n\ttimestamp uint64\n\tline      uint  // size_t\n\tlevel     int32 // turso_tracing_level_t\n}\n\ntype turso_config_t struct {\n\tlogger    uintptr // void (*logger)(const turso_log_t *log)\n\tlog_level uintptr\n}\n\ntype turso_database_config_t struct {\n\tasync_io              uint64  // non-zero value interpreted as async IO\n\tpath                  uintptr // const char*\n\texperimental_features uintptr // const char* or null\n\tvfs                   uintptr // const char* or null\n\tencryption_cipher     uintptr // const char* or null\n\tencryption_hexkey     uintptr // const char* or null\n}\n\n// C extern method types\ntype turso_status_code_t = int32\n\n// then, define C extern methods\nvar (\n\tc_turso_setup                            func(config *turso_config_t, error_opt_out **byte) turso_status_code_t\n\tc_turso_database_new                     func(config *turso_database_config_t, database **turso_database_t, error_opt_out **byte) turso_status_code_t\n\tc_turso_database_open                    func(database TursoDatabase, error_opt_out **byte) turso_status_code_t\n\tc_turso_database_connect                 func(self TursoDatabase, connection **turso_connection_t, error_opt_out **byte) turso_status_code_t\n\tc_turso_connection_get_autocommit        func(self TursoConnection) bool\n\tc_turso_connection_set_busy_timeout_ms   func(self TursoConnection, timeout_ms int64)\n\tc_turso_connection_last_insert_rowid     func(self TursoConnection) int64\n\tc_turso_connection_prepare_single        func(self TursoConnection, sql string, statement **turso_statement_t, error_opt_out **byte) turso_status_code_t\n\tc_turso_connection_prepare_first         func(self TursoConnection, sql string, statement **turso_statement_t, tail_idx *uintptr, error_opt_out **byte) turso_status_code_t\n\tc_turso_connection_close                 func(self TursoConnection, error_opt_out **byte) turso_status_code_t\n\tc_turso_statement_execute                func(self TursoStatement, rows_changes *uint64, error_opt_out **byte) turso_status_code_t\n\tc_turso_statement_step                   func(self TursoStatement, error_opt_out **byte) turso_status_code_t\n\tc_turso_statement_run_io                 func(self TursoStatement, error_opt_out **byte) turso_status_code_t\n\tc_turso_statement_reset                  func(self TursoStatement, error_opt_out **byte) turso_status_code_t\n\tc_turso_statement_finalize               func(self TursoStatement, error_opt_out **byte) turso_status_code_t\n\tc_turso_statement_n_change               func(self TursoStatement) int64\n\tc_turso_statement_column_count           func(self TursoStatement) int64\n\tc_turso_statement_column_name            func(self TursoStatement, index uintptr) uintptr\n\tc_turso_statement_column_decltype        func(self TursoStatement, index uintptr) uintptr\n\tc_turso_statement_row_value_kind         func(self TursoStatement, index uintptr) int32\n\tc_turso_statement_row_value_bytes_count  func(self TursoStatement, index uintptr) int64\n\tc_turso_statement_row_value_bytes_ptr    func(self TursoStatement, index uintptr) uintptr\n\tc_turso_statement_row_value_int          func(self TursoStatement, index uintptr) int64\n\tc_turso_statement_row_value_double       func(self TursoStatement, index uintptr) float64\n\tc_turso_statement_named_position         func(self TursoStatement, name string) int64\n\tc_turso_statement_parameters_count       func(self TursoStatement) int64\n\tc_turso_statement_bind_positional_null   func(self TursoStatement, position uintptr) turso_status_code_t\n\tc_turso_statement_bind_positional_int    func(self TursoStatement, position uintptr, value int64) turso_status_code_t\n\tc_turso_statement_bind_positional_double func(self TursoStatement, position uintptr, value float64) turso_status_code_t\n\tc_turso_statement_bind_positional_blob   func(self TursoStatement, position uintptr, ptr *byte, len uintptr) turso_status_code_t\n\tc_turso_statement_bind_positional_text   func(self TursoStatement, position uintptr, ptr *byte, len uintptr) turso_status_code_t\n\tc_turso_str_deinit                       func(self uintptr)\n\tc_turso_database_deinit                  func(self TursoDatabase)\n\tc_turso_connection_deinit                func(self TursoConnection)\n\tc_turso_statement_deinit                 func(self TursoStatement)\n)\n\n// implement a function to register extern methods from loaded lib\n// DO NOT load lib - as it will be done externally\nfunc registerTursoDb(handle uintptr) error {\n\tpurego.RegisterLibFunc(&c_turso_setup, handle, \"turso_setup\")\n\tpurego.RegisterLibFunc(&c_turso_database_new, handle, \"turso_database_new\")\n\tpurego.RegisterLibFunc(&c_turso_database_open, handle, \"turso_database_open\")\n\tpurego.RegisterLibFunc(&c_turso_database_connect, handle, \"turso_database_connect\")\n\tpurego.RegisterLibFunc(&c_turso_connection_get_autocommit, handle, \"turso_connection_get_autocommit\")\n\tpurego.RegisterLibFunc(&c_turso_connection_set_busy_timeout_ms, handle, \"turso_connection_set_busy_timeout_ms\")\n\tpurego.RegisterLibFunc(&c_turso_connection_last_insert_rowid, handle, \"turso_connection_last_insert_rowid\")\n\tpurego.RegisterLibFunc(&c_turso_connection_prepare_single, handle, \"turso_connection_prepare_single\")\n\tpurego.RegisterLibFunc(&c_turso_connection_prepare_first, handle, \"turso_connection_prepare_first\")\n\tpurego.RegisterLibFunc(&c_turso_connection_close, handle, \"turso_connection_close\")\n\tpurego.RegisterLibFunc(&c_turso_statement_execute, handle, \"turso_statement_execute\")\n\tpurego.RegisterLibFunc(&c_turso_statement_step, handle, \"turso_statement_step\")\n\tpurego.RegisterLibFunc(&c_turso_statement_run_io, handle, \"turso_statement_run_io\")\n\tpurego.RegisterLibFunc(&c_turso_statement_reset, handle, \"turso_statement_reset\")\n\tpurego.RegisterLibFunc(&c_turso_statement_finalize, handle, \"turso_statement_finalize\")\n\tpurego.RegisterLibFunc(&c_turso_statement_n_change, handle, \"turso_statement_n_change\")\n\tpurego.RegisterLibFunc(&c_turso_statement_column_count, handle, \"turso_statement_column_count\")\n\tpurego.RegisterLibFunc(&c_turso_statement_column_name, handle, \"turso_statement_column_name\")\n\tpurego.RegisterLibFunc(&c_turso_statement_column_decltype, handle, \"turso_statement_column_decltype\")\n\tpurego.RegisterLibFunc(&c_turso_statement_row_value_kind, handle, \"turso_statement_row_value_kind\")\n\tpurego.RegisterLibFunc(&c_turso_statement_row_value_bytes_count, handle, \"turso_statement_row_value_bytes_count\")\n\tpurego.RegisterLibFunc(&c_turso_statement_row_value_bytes_ptr, handle, \"turso_statement_row_value_bytes_ptr\")\n\tpurego.RegisterLibFunc(&c_turso_statement_row_value_int, handle, \"turso_statement_row_value_int\")\n\tpurego.RegisterLibFunc(&c_turso_statement_row_value_double, handle, \"turso_statement_row_value_double\")\n\tpurego.RegisterLibFunc(&c_turso_statement_named_position, handle, \"turso_statement_named_position\")\n\tpurego.RegisterLibFunc(&c_turso_statement_parameters_count, handle, \"turso_statement_parameters_count\")\n\tpurego.RegisterLibFunc(&c_turso_statement_bind_positional_null, handle, \"turso_statement_bind_positional_null\")\n\tpurego.RegisterLibFunc(&c_turso_statement_bind_positional_int, handle, \"turso_statement_bind_positional_int\")\n\tpurego.RegisterLibFunc(&c_turso_statement_bind_positional_double, handle, \"turso_statement_bind_positional_double\")\n\tpurego.RegisterLibFunc(&c_turso_statement_bind_positional_blob, handle, \"turso_statement_bind_positional_blob\")\n\tpurego.RegisterLibFunc(&c_turso_statement_bind_positional_text, handle, \"turso_statement_bind_positional_text\")\n\tpurego.RegisterLibFunc(&c_turso_str_deinit, handle, \"turso_str_deinit\")\n\tpurego.RegisterLibFunc(&c_turso_database_deinit, handle, \"turso_database_deinit\")\n\tpurego.RegisterLibFunc(&c_turso_connection_deinit, handle, \"turso_connection_deinit\")\n\tpurego.RegisterLibFunc(&c_turso_statement_deinit, handle, \"turso_statement_deinit\")\n\treturn nil\n}\n\n// Helper: map status code to default error kind\nfunc statusToError(status TursoStatusCode, msg string) error {\n\tvar base error\n\tswitch status {\n\tcase TURSO_BUSY:\n\t\tbase = ErrTursoBusy\n\tcase TURSO_INTERRUPT:\n\t\tbase = ErrTursoInterrupt\n\tcase TURSO_ERROR:\n\t\tbase = ErrTursoGeneric\n\tcase TURSO_MISUSE:\n\t\tbase = ErrTursoMisuse\n\tcase TURSO_CONSTRAINT:\n\t\tbase = ErrTursoConstraint\n\tcase TURSO_READONLY:\n\t\tbase = ErrTursoReadOnly\n\tcase TURSO_DATABASE_FULL:\n\t\tbase = ErrTursoDatabaseFull\n\tcase TURSO_NOTADB:\n\t\tbase = ErrTursoNotADb\n\tcase TURSO_CORRUPT:\n\t\tbase = ErrTursoCorrupt\n\tdefault:\n\t\t// for unknown error codes, fallback to generic\n\t\tbase = ErrTursoGeneric\n\t}\n\tif msg != \"\" {\n\t\treturn fmt.Errorf(\"%w: %s\", base, msg)\n\t}\n\treturn base\n}\n\nfunc decodeAndFreeCString(p *byte) string {\n\treturn decodeAndFreeCStringRaw(uintptr(unsafe.Pointer(p)))\n}\n\nfunc decodeAndFreeCStringRaw(p uintptr) string {\n\tif p == 0 {\n\t\treturn \"\"\n\t}\n\t// determine length\n\tvar n uintptr\n\tfor {\n\t\tb := *(*byte)(unsafe.Pointer(p + n))\n\t\tif b == 0 {\n\t\t\tbreak\n\t\t}\n\t\tn++\n\t}\n\ts := string(unsafe.Slice((*byte)(unsafe.Pointer(p)), n))\n\t// free C-allocated string\n\tc_turso_str_deinit(p)\n\treturn s\n}\n\nfunc decodeCStringNoFree(p uintptr) string {\n\tif p == 0 {\n\t\treturn \"\"\n\t}\n\tcur := (*byte)(unsafe.Pointer(p))\n\t// determine length\n\tvar n uintptr\n\tfor {\n\t\tb := *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(cur)) + n))\n\t\tif b == 0 {\n\t\t\tbreak\n\t\t}\n\t\tn++\n\t}\n\treturn string(unsafe.Slice(cur, n))\n}\n\nfunc makeCStringBytes(s string) ([]byte, uintptr) {\n\tif s == \"\" {\n\t\treturn nil, 0\n\t}\n\tb := make([]byte, 0, len(s)+1)\n\tb = append(b, s...)\n\tb = append(b, 0)\n\treturn b, uintptr(unsafe.Pointer(&b[0]))\n}\n\n// ------- Logger callback plumbing --------\nvar (\n\tloggerCallback uintptr\n\tloggerHandler  func(TursoLog)\n)\n\nfunc init() {\n\tloggerCallback = purego.NewCallback(func(p uintptr) uintptr {\n\t\tif p == 0 || loggerHandler == nil {\n\t\t\treturn 0\n\t\t}\n\t\tcl := (*turso_log_t)(unsafe.Pointer(p))\n\t\tlog := TursoLog{\n\t\t\tMessage:   decodeCStringNoFree(cl.message),\n\t\t\tTarget:    decodeCStringNoFree(cl.target),\n\t\t\tFile:      decodeCStringNoFree(cl.file),\n\t\t\tTimestamp: cl.timestamp,\n\t\t\tLine:      uint(cl.line),\n\t\t\tLevel:     TursoTracingLevel(cl.level),\n\t\t}\n\t\t// SAFETY: strings are copied above; no freeing needed.\n\t\tloggerHandler(log)\n\t\treturn 0\n\t})\n}\n\n// Go wrappers over imported C bindings\n\n// turso_setup sets up global database info.\nfunc turso_setup(config TursoConfig) error {\n\tvar cconf turso_config_t\n\t// Set logger callback\n\tif config.Logger != nil {\n\t\tloggerHandler = config.Logger\n\t\tcconf.logger = loggerCallback\n\t}\n\t// Log level C-string pointer\n\tvar levelBytes []byte\n\tlevelBytes, cconf.log_level = makeCStringBytes(config.LogLevel)\n\tvar errPtr *byte\n\tstatus := c_turso_setup(&cconf, &errPtr)\n\t// Keep Go memory alive during C call\n\truntime.KeepAlive(levelBytes)\n\tif status == int32(TURSO_OK) {\n\t\treturn nil\n\t}\n\tmsg := decodeAndFreeCString(errPtr)\n\treturn statusToError(TursoStatusCode(status), msg)\n}\n\n// turso_database_new creates database holder but do not open it.\nfunc turso_database_new(config TursoDatabaseConfig) (TursoDatabase, error) {\n\tvar cconf turso_database_config_t\n\tvar pathBytes []byte\n\tvar expBytes []byte\n\tvar vfsBytes []byte\n\tvar encryptionCipherBytes []byte\n\tvar encryptionHexkeyBytes []byte\n\tpathBytes, cconf.path = makeCStringBytes(config.Path)\n\tif config.ExperimentalFeatures != \"\" {\n\t\texpBytes, cconf.experimental_features = makeCStringBytes(config.ExperimentalFeatures)\n\t}\n\tif config.Vfs != \"\" {\n\t\tvfsBytes, cconf.vfs = makeCStringBytes(config.Vfs)\n\t}\n\tif config.Encryption.Cipher != \"\" {\n\t\tencryptionCipherBytes, cconf.encryption_cipher = makeCStringBytes(config.Encryption.Cipher)\n\t}\n\tif config.Encryption.Hexkey != \"\" {\n\t\tencryptionHexkeyBytes, cconf.encryption_hexkey = makeCStringBytes(config.Encryption.Hexkey)\n\t}\n\tcconf.async_io = 0\n\tif config.AsyncIO {\n\t\tcconf.async_io = 1\n\t}\n\n\tvar db *turso_database_t\n\tvar errPtr *byte\n\tstatus := c_turso_database_new(&cconf, &db, &errPtr)\n\truntime.KeepAlive(pathBytes)\n\truntime.KeepAlive(expBytes)\n\truntime.KeepAlive(vfsBytes)\n\truntime.KeepAlive(encryptionCipherBytes)\n\truntime.KeepAlive(encryptionHexkeyBytes)\n\tif status == int32(TURSO_OK) {\n\t\treturn TursoDatabase(db), nil\n\t}\n\tmsg := decodeAndFreeCString(errPtr)\n\treturn nil, statusToError(TursoStatusCode(status), msg)\n}\n\n// turso_database_open opens the database.\nfunc turso_database_open(database TursoDatabase) error {\n\tvar errPtr *byte\n\tstatus := c_turso_database_open(database, &errPtr)\n\tif status == int32(TURSO_OK) {\n\t\treturn nil\n\t}\n\tmsg := decodeAndFreeCString(errPtr)\n\treturn statusToError(TursoStatusCode(status), msg)\n}\n\n// turso_database_connect connects to the database and returns a connection.\nfunc turso_database_connect(self TursoDatabase) (TursoConnection, error) {\n\tvar conn *turso_connection_t\n\tvar errPtr *byte\n\tstatus := c_turso_database_connect(self, &conn, &errPtr)\n\tif status == int32(TURSO_OK) {\n\t\treturn TursoConnection(conn), nil\n\t}\n\tmsg := decodeAndFreeCString(errPtr)\n\treturn nil, statusToError(TursoStatusCode(status), msg)\n}\n\n// turso_connection_get_autocommit returns the autocommit state of the connection.\nfunc turso_connection_get_autocommit(self TursoConnection) bool {\n\treturn c_turso_connection_get_autocommit(self)\n}\n\n// turso_connection_set_busy_timeout_ms sets busy timeout for the connection\nfunc turso_connection_set_busy_timeout_ms(self TursoConnection, timeoutMs int64) {\n\tc_turso_connection_set_busy_timeout_ms(self, timeoutMs)\n}\n\n// turso_connection_last_insert_rowid returns last insert rowid.\nfunc turso_connection_last_insert_rowid(self TursoConnection) int64 {\n\treturn c_turso_connection_last_insert_rowid(self)\n}\n\n// turso_connection_prepare_single prepares a single statement in a connection.\nfunc turso_connection_prepare_single(self TursoConnection, sql string) (TursoStatement, error) {\n\tvar stmt *turso_statement_t\n\tvar errPtr *byte\n\tstatus := c_turso_connection_prepare_single(self, sql, &stmt, &errPtr)\n\tif status == int32(TURSO_OK) {\n\t\treturn TursoStatement(stmt), nil\n\t}\n\tmsg := decodeAndFreeCString(errPtr)\n\treturn nil, statusToError(TursoStatusCode(status), msg)\n}\n\n// turso_connection_prepare_first prepares the first statement from a string containing multiple statements.\nfunc turso_connection_prepare_first(self TursoConnection, sql string) (TursoStatement, int, error) {\n\tvar stmt *turso_statement_t\n\tvar tail uintptr\n\tvar errPtr *byte\n\tstatus := c_turso_connection_prepare_first(self, sql, &stmt, &tail, &errPtr)\n\tif status == int32(TURSO_OK) {\n\t\treturn TursoStatement(stmt), int(tail), nil\n\t}\n\tmsg := decodeAndFreeCString(errPtr)\n\treturn nil, 0, statusToError(TursoStatusCode(status), msg)\n}\n\n// turso_connection_close closes the connection preventing any further operations.\nfunc turso_connection_close(self TursoConnection) error {\n\tvar errPtr *byte\n\tstatus := c_turso_connection_close(self, &errPtr)\n\tif status == int32(TURSO_OK) {\n\t\treturn nil\n\t}\n\tmsg := decodeAndFreeCString(errPtr)\n\treturn statusToError(TursoStatusCode(status), msg)\n}\n\n// turso_statement_execute executes a single statement\n// * execute returns TURSO_DONE if execution completed\n// * execute returns TURSO_IO if async_io was set and execution needs IO in order to make progress\nfunc turso_statement_execute(self TursoStatement) (TursoStatusCode, uint64, error) {\n\tvar changes uint64\n\tvar errPtr *byte\n\tstatus := c_turso_statement_execute(self, &changes, &errPtr)\n\tswitch TursoStatusCode(status) {\n\tcase TURSO_OK, TURSO_DONE, TURSO_ROW, TURSO_IO:\n\t\treturn TursoStatusCode(status), changes, nil\n\tdefault:\n\t\tmsg := decodeAndFreeCString(errPtr)\n\t\treturn TursoStatusCode(status), 0, statusToError(TursoStatusCode(status), msg)\n\t}\n}\n\n// turso_statement_step steps statement execution once. Returns DONE, ROW, IO, or error.\nfunc turso_statement_step(self TursoStatement) (TursoStatusCode, error) {\n\tvar errPtr *byte\n\tstatus := c_turso_statement_step(self, &errPtr)\n\tswitch TursoStatusCode(status) {\n\tcase TURSO_OK, TURSO_DONE, TURSO_ROW, TURSO_IO:\n\t\treturn TursoStatusCode(status), nil\n\tdefault:\n\t\tmsg := decodeAndFreeCString(errPtr)\n\t\treturn TursoStatusCode(status), statusToError(TursoStatusCode(status), msg)\n\t}\n}\n\n// turso_statement_run_io executes one iteration of underlying IO backend after TURSO_IO.\nfunc turso_statement_run_io(self TursoStatement) error {\n\tvar errPtr *byte\n\tstatus := c_turso_statement_run_io(self, &errPtr)\n\tif status == int32(TURSO_OK) {\n\t\treturn nil\n\t}\n\tmsg := decodeAndFreeCString(errPtr)\n\treturn statusToError(TursoStatusCode(status), msg)\n}\n\n// turso_statement_reset resets a statement.\n// this method must be called in order to cleanup statement resources and prepare it for re-execution\n// any pending execution will be aborted - be careful and in certain cases ensure that turso_statement_finalize called before turso_statement_reset\nfunc turso_statement_reset(self TursoStatement) error {\n\tvar errPtr *byte\n\tstatus := c_turso_statement_reset(self, &errPtr)\n\tif status == int32(TURSO_OK) {\n\t\treturn nil\n\t}\n\tmsg := decodeAndFreeCString(errPtr)\n\treturn statusToError(TursoStatusCode(status), msg)\n}\n\n// turso_statement_finalize finalizes a statement.\nfunc turso_statement_finalize(self TursoStatement) error {\n\tvar errPtr *byte\n\tstatus := c_turso_statement_finalize(self, &errPtr)\n\tif status == int32(TURSO_OK) {\n\t\treturn nil\n\t}\n\tmsg := decodeAndFreeCString(errPtr)\n\treturn statusToError(TursoStatusCode(status), msg)\n}\n\n// turso_statement_n_change returns amount of row modifications (insert/delete operations) made by the most recent executed statement.\nfunc turso_statement_n_change(self TursoStatement) int64 {\n\treturn c_turso_statement_n_change(self)\n}\n\n// turso_statement_column_count returns the number of columns.\nfunc turso_statement_column_count(self TursoStatement) int64 {\n\treturn c_turso_statement_column_count(self)\n}\n\n// turso_statement_column_name returns the column name at the index.\n// The underlying C string is freed automatically.\nfunc turso_statement_column_name(self TursoStatement, index int) string {\n\tptr := c_turso_statement_column_name(self, uintptr(index))\n\treturn decodeAndFreeCStringRaw(ptr)\n}\n\n// turso_statement_column_decltype returns the column declared type at the index\n// (e.g. \"INTEGER\", \"TEXT\", \"DATETIME\", etc.). Returns empty string if not available.\n// The underlying C string is freed automatically.\nfunc turso_statement_column_decltype(self TursoStatement, index int) string {\n\tptr := c_turso_statement_column_decltype(self, uintptr(index))\n\tif ptr == 0 {\n\t\treturn \"\"\n\t}\n\treturn decodeAndFreeCStringRaw(ptr)\n}\n\n// turso_statement_row_value_kind returns the row value kind at index.\nfunc turso_statement_row_value_kind(self TursoStatement, index int) TursoType {\n\treturn TursoType(c_turso_statement_row_value_kind(self, uintptr(index)))\n}\n\n// turso_statement_row_value_bytes_count returns number of bytes for BLOB or TEXT, -1 otherwise.\nfunc turso_statement_row_value_bytes_count(self TursoStatement, index int) int64 {\n\treturn c_turso_statement_row_value_bytes_count(self, uintptr(index))\n}\n\n// turso_statement_row_value_bytes_ptr returns pointer to start of BLOB/TEXT slice, or nil otherwise.\nfunc turso_statement_row_value_bytes_ptr(self TursoStatement, index int) uintptr {\n\treturn c_turso_statement_row_value_bytes_ptr(self, uintptr(index))\n}\n\n// turso_statement_row_value_int returns INTEGER value at index, or 0 otherwise.\nfunc turso_statement_row_value_int(self TursoStatement, index int) int64 {\n\treturn c_turso_statement_row_value_int(self, uintptr(index))\n}\n\n// turso_statement_row_value_double returns REAL value at index, or 0 otherwise.\nfunc turso_statement_row_value_double(self TursoStatement, index int) float64 {\n\treturn c_turso_statement_row_value_double(self, uintptr(index))\n}\n\n// turso_statement_named_position returns named argument position in a statement.\nfunc turso_statement_named_position(self TursoStatement, name string) int64 {\n\treturn c_turso_statement_named_position(self, name)\n}\n\n// turso_statement_parameters_count returns parameters count for the statement.\nfunc turso_statement_parameters_count(self TursoStatement) int64 {\n\treturn c_turso_statement_parameters_count(self)\n}\n\n// turso_statement_bind_positional_null binds a positional argument as NULL.\nfunc turso_statement_bind_positional_null(self TursoStatement, position int) error {\n\tstatus := c_turso_statement_bind_positional_null(self, uintptr(position))\n\tif status == int32(TURSO_OK) {\n\t\treturn nil\n\t}\n\treturn statusToError(TursoStatusCode(status), \"\")\n}\n\n// turso_statement_bind_positional_int binds a positional argument as INTEGER.\nfunc turso_statement_bind_positional_int(self TursoStatement, position int, value int64) error {\n\tstatus := c_turso_statement_bind_positional_int(self, uintptr(position), value)\n\tif status == int32(TURSO_OK) {\n\t\treturn nil\n\t}\n\treturn statusToError(TursoStatusCode(status), \"\")\n}\n\n// turso_statement_bind_positional_double binds a positional argument as REAL.\nfunc turso_statement_bind_positional_double(self TursoStatement, position int, value float64) error {\n\tstatus := c_turso_statement_bind_positional_double(self, uintptr(position), value)\n\tif status == int32(TURSO_OK) {\n\t\treturn nil\n\t}\n\treturn statusToError(TursoStatusCode(status), \"\")\n}\n\n// turso_statement_bind_positional_blob binds a positional argument as BLOB.\nfunc turso_statement_bind_positional_blob(self TursoStatement, position int, value []byte) error {\n\tvar ptr *byte\n\tvar length uintptr\n\tif len(value) > 0 {\n\t\tptr = &value[0]\n\t\tlength = uintptr(len(value))\n\t}\n\tstatus := c_turso_statement_bind_positional_blob(self, uintptr(position), ptr, length)\n\tif status == int32(TURSO_OK) {\n\t\treturn nil\n\t}\n\treturn statusToError(TursoStatusCode(status), \"\")\n}\n\n// turso_statement_bind_positional_text binds a positional argument as TEXT.\n// Note: underlying C API expects a pointer and length, not a zero-terminated string.\nfunc turso_statement_bind_positional_text(self TursoStatement, position int, value string) error {\n\tvar ptr *byte\n\tvar length uintptr\n\tif value != \"\" {\n\t\t// Point directly to string data; valid for the duration of the call.\n\t\tptr = (*byte)(unsafe.Pointer(unsafe.StringData(value)))\n\t\tlength = uintptr(len(value))\n\t}\n\tstatus := c_turso_statement_bind_positional_text(self, uintptr(position), ptr, length)\n\tif status == int32(TURSO_OK) {\n\t\treturn nil\n\t}\n\treturn statusToError(TursoStatusCode(status), \"\")\n}\n\n// turso_database_deinit deallocates and closes a database.\n// SAFETY: caller must ensure that no other code can concurrently or later call methods over deinited database.\nfunc turso_database_deinit(self TursoDatabase) {\n\tc_turso_database_deinit(self)\n}\n\n// turso_connection_deinit deallocates and closes a connection.\n// SAFETY: caller must ensure that no other code can concurrently or later call methods over deinited connection.\nfunc turso_connection_deinit(self TursoConnection) {\n\tc_turso_connection_deinit(self)\n}\n\n// turso_statement_deinit deallocates and closes a statement.\n// SAFETY: caller must ensure that no other code can concurrently or later call methods over deinited statement.\nfunc turso_statement_deinit(self TursoStatement) {\n\tc_turso_statement_deinit(self)\n}\n\n// Additional ergonomic helpers (the only non-direct translations):\n// turso_statement_row_value_bytes returns a copy of bytes for BLOB or TEXT values, nil otherwise.\nfunc turso_statement_row_value_bytes(self TursoStatement, index int) []byte {\n\tn := c_turso_statement_row_value_bytes_count(self, uintptr(index))\n\tif n <= 0 {\n\t\treturn nil\n\t}\n\tptr := c_turso_statement_row_value_bytes_ptr(self, uintptr(index))\n\tif ptr == 0 {\n\t\treturn nil\n\t}\n\tsrc := unsafe.Slice((*byte)(unsafe.Pointer(ptr)), n)\n\tdst := make([]byte, n)\n\tcopy(dst, src)\n\treturn dst\n}\n\n// turso_statement_row_value_text returns a copy of text for TEXT values, \"\" otherwise.\nfunc turso_statement_row_value_text(self TursoStatement, index int) string {\n\tn := c_turso_statement_row_value_bytes_count(self, uintptr(index))\n\tif n <= 0 {\n\t\treturn \"\"\n\t}\n\tptr := c_turso_statement_row_value_bytes_ptr(self, uintptr(index))\n\tif ptr == 0 {\n\t\treturn \"\"\n\t}\n\tbs := unsafe.Slice((*byte)(unsafe.Pointer(ptr)), n)\n\t// converting []byte to string copies\n\treturn string(bs)\n}\n"
  },
  {
    "path": "bindings/go/bindings_db_test.go",
    "content": "package turso\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype dbConn struct {\n\tdb   TursoDatabase\n\tconn TursoConnection\n}\n\nfunc openInMemory(t *testing.T) (*dbConn, func()) {\n\tt.Helper()\n\n\tdb, err := turso_database_new(TursoDatabaseConfig{\n\t\tPath:                 \":memory:\",\n\t\tExperimentalFeatures: \"\",\n\t\tAsyncIO:              false,\n\t})\n\trequire.NoError(t, err)\n\trequire.NotNil(t, db)\n\n\trequire.NoError(t, turso_database_open(db))\n\n\tconn, err := turso_database_connect(db)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, conn)\n\n\tcleanup := func() {\n\t\t_ = turso_connection_close(conn)\n\t\tturso_connection_deinit(conn)\n\t\tturso_database_deinit(db)\n\t}\n\treturn &dbConn{db: db, conn: conn}, cleanup\n}\n\nfunc prepExec(t *testing.T, conn TursoConnection, sql string) uint64 {\n\tt.Helper()\n\tstmt, err := turso_connection_prepare_single(conn, sql)\n\trequire.NoError(t, err)\n\tdefer func() {\n\t\t_ = turso_statement_finalize(stmt)\n\t\tturso_statement_deinit(stmt)\n\t}()\n\t_, changes, err := turso_statement_execute(stmt)\n\trequire.NoError(t, err)\n\treturn changes\n}\n\nfunc prepStmt(t *testing.T, conn TursoConnection, sql string) TursoStatement {\n\tt.Helper()\n\tstmt, err := turso_connection_prepare_single(conn, sql)\n\trequire.NoError(t, err)\n\treturn stmt\n}\n\nfunc stepRow(t *testing.T, stmt TursoStatement) bool {\n\tt.Helper()\n\tcode, err := turso_statement_step(stmt)\n\trequire.NoError(t, err)\n\tif code == TURSO_ROW {\n\t\treturn true\n\t}\n\trequire.Equal(t, TURSO_DONE, code)\n\treturn false\n}\n\nfunc TestSetupAndOpenMemory(t *testing.T) {\n\tconn, cleanup := openInMemory(t)\n\tdefer cleanup()\n\n\tac := turso_connection_get_autocommit(conn.conn)\n\tassert.True(t, ac, \"autocommit should be true for new connection\")\n\n\t// simple sanity DDL\n\tchanges := prepExec(t, conn.conn, \"CREATE TABLE t(id INTEGER PRIMARY KEY, a INTEGER)\")\n\tassert.Equal(t, uint64(0), changes)\n}\n\nfunc TestPrepareFirstMultipleStatements(t *testing.T) {\n\tconn, cleanup := openInMemory(t)\n\tdefer cleanup()\n\n\tsql := \"CREATE TABLE t(a INT); INSERT INTO t(a) VALUES(1); SELECT a FROM t;\"\n\tstart := 0\n\tfor {\n\t\tsub := sql[start:]\n\t\tstmt, tail, err := turso_connection_prepare_first(conn.conn, sub)\n\t\trequire.NoError(t, err)\n\t\tif stmt == nil {\n\t\t\tbreak\n\t\t}\n\t\tif tail <= 0 {\n\t\t\tbreak\n\t\t}\n\n\t\t// Execute or step depending on statement type\n\t\tcolCount := turso_statement_column_count(stmt)\n\t\tif colCount == 0 {\n\t\t\t_, _, err := turso_statement_execute(stmt)\n\t\t\trequire.NoError(t, err)\n\t\t} else {\n\t\t\trequire.True(t, stepRow(t, stmt))\n\t\t\tv := turso_statement_row_value_int(stmt, 0)\n\t\t\tassert.Equal(t, int64(1), v)\n\t\t\trequire.False(t, stepRow(t, stmt))\n\t\t}\n\t\t_ = turso_statement_finalize(stmt)\n\t\tturso_statement_deinit(stmt)\n\t\tstart += tail\n\t}\n\t// Verify result\n\tstmt := prepStmt(t, conn.conn, \"SELECT a FROM t\")\n\tdefer func() {\n\t\t_ = turso_statement_finalize(stmt)\n\t\tturso_statement_deinit(stmt)\n\t}()\n\trequire.True(t, stepRow(t, stmt))\n\tassert.Equal(t, int64(1), turso_statement_row_value_int(stmt, 0))\n\trequire.False(t, stepRow(t, stmt))\n}\n\nfunc TestInsertReturningMultiplePartialFetchCommits(t *testing.T) {\n\tconn, cleanup := openInMemory(t)\n\tdefer cleanup()\n\n\tprepExec(t, conn.conn, \"CREATE TABLE t(a INT)\")\n\n\tstmt := prepStmt(t, conn.conn, \"INSERT INTO t(a) VALUES (1),(2) RETURNING a\")\n\trequire.True(t, stepRow(t, stmt))\n\tfirst := turso_statement_row_value_int(stmt, 0)\n\tassert.Equal(t, int64(1), first)\n\n\t// Do not consume all rows; finalize early\n\t_ = turso_statement_finalize(stmt)\n\tturso_statement_deinit(stmt)\n\n\t// Ensure both rows were inserted\n\tstmt2 := prepStmt(t, conn.conn, \"SELECT COUNT(*) FROM t\")\n\tdefer func() {\n\t\t_ = turso_statement_finalize(stmt2)\n\t\tturso_statement_deinit(stmt2)\n\t}()\n\trequire.True(t, stepRow(t, stmt2))\n\tcnt := turso_statement_row_value_int(stmt2, 0)\n\tassert.Equal(t, int64(2), cnt)\n\trequire.False(t, stepRow(t, stmt2))\n}\n\nfunc TestInsertReturningWithExplicitTransactionAndPartialFetch(t *testing.T) {\n\tconn, cleanup := openInMemory(t)\n\tdefer cleanup()\n\n\tprepExec(t, conn.conn, \"CREATE TABLE t(a INT)\")\n\tprepExec(t, conn.conn, \"BEGIN\")\n\tstmt := prepStmt(t, conn.conn, \"INSERT INTO t(a) VALUES (10),(20) RETURNING a\")\n\trequire.True(t, stepRow(t, stmt))\n\tv := turso_statement_row_value_int(stmt, 0)\n\tassert.Equal(t, int64(10), v)\n\t// finalize without consuming all rows\n\t_ = turso_statement_finalize(stmt)\n\tturso_statement_deinit(stmt)\n\t// Commit should still succeed\n\tprepExec(t, conn.conn, \"COMMIT\")\n\n\t// Verify data\n\tstmt2 := prepStmt(t, conn.conn, \"SELECT COUNT(*) FROM t\")\n\tdefer func() {\n\t\t_ = turso_statement_finalize(stmt2)\n\t\tturso_statement_deinit(stmt2)\n\t}()\n\trequire.True(t, stepRow(t, stmt2))\n\tassert.Equal(t, int64(2), turso_statement_row_value_int(stmt2, 0))\n}\n\nfunc TestOnConflictDoNothingReturning(t *testing.T) {\n\tconn, cleanup := openInMemory(t)\n\tdefer cleanup()\n\n\tprepExec(t, conn.conn, \"CREATE TABLE t(a INT PRIMARY KEY)\")\n\tprepExec(t, conn.conn, \"INSERT INTO t(a) VALUES(1)\")\n\n\tstmt := prepStmt(t, conn.conn, \"INSERT INTO t(a) VALUES(1) ON CONFLICT(a) DO NOTHING RETURNING a\")\n\tdefer func() {\n\t\t_ = turso_statement_finalize(stmt)\n\t\tturso_statement_deinit(stmt)\n\t}()\n\t// Should produce no rows and be done\n\tcode, err := turso_statement_step(stmt)\n\trequire.NoError(t, err)\n\tassert.Equal(t, TURSO_DONE, code)\n\n\t// Ensure count unchanged\n\tstmt2 := prepStmt(t, conn.conn, \"SELECT COUNT(*) FROM t\")\n\tdefer func() {\n\t\t_ = turso_statement_finalize(stmt2)\n\t\tturso_statement_deinit(stmt2)\n\t}()\n\trequire.True(t, stepRow(t, stmt2))\n\tassert.Equal(t, int64(1), turso_statement_row_value_int(stmt2, 0))\n}\n\nfunc TestSubqueries(t *testing.T) {\n\tconn, cleanup := openInMemory(t)\n\tdefer cleanup()\n\n\tprepExec(t, conn.conn, \"CREATE TABLE t(a INT)\")\n\tprepExec(t, conn.conn, \"INSERT INTO t(a) VALUES (1),(2),(3),(4)\")\n\n\tstmt := prepStmt(t, conn.conn, \"SELECT a FROM (SELECT a FROM t WHERE a > 1) WHERE a < 4 ORDER BY a\")\n\tdefer func() {\n\t\t_ = turso_statement_finalize(stmt)\n\t\tturso_statement_deinit(stmt)\n\t}()\n\n\tvar got []int64\n\tfor {\n\t\tif !stepRow(t, stmt) {\n\t\t\tbreak\n\t\t}\n\t\tgot = append(got, turso_statement_row_value_int(stmt, 0))\n\t}\n\tassert.Equal(t, []int64{2, 3}, got)\n}\n\nfunc TestJoin(t *testing.T) {\n\tconn, cleanup := openInMemory(t)\n\tdefer cleanup()\n\n\tprepExec(t, conn.conn, \"CREATE TABLE t1(id INT PRIMARY KEY, name TEXT)\")\n\tprepExec(t, conn.conn, \"CREATE TABLE t2(id INT PRIMARY KEY, age INT)\")\n\tprepExec(t, conn.conn, \"INSERT INTO t1(id, name) VALUES (1,'a'),(2,'b'),(3,'c')\")\n\tprepExec(t, conn.conn, \"INSERT INTO t2(id, age) VALUES (1,10),(3,30)\")\n\n\tstmt := prepStmt(t, conn.conn, \"SELECT t1.id, t1.name, t2.age FROM t1 JOIN t2 ON t1.id = t2.id ORDER BY t1.id\")\n\tdefer func() {\n\t\t_ = turso_statement_finalize(stmt)\n\t\tturso_statement_deinit(stmt)\n\t}()\n\n\tvar rows [][3]int64\n\tvar names []string\n\tfor {\n\t\tif !stepRow(t, stmt) {\n\t\t\tbreak\n\t\t}\n\t\tid := turso_statement_row_value_int(stmt, 0)\n\t\t// name as TEXT\n\t\tname := turso_statement_row_value_text(stmt, 1)\n\t\tage := turso_statement_row_value_int(stmt, 2)\n\t\trows = append(rows, [3]int64{id, int64(len(name)), age})\n\t\tnames = append(names, name)\n\t}\n\tassert.Equal(t, [][3]int64{{1, 1, 10}, {3, 1, 30}}, rows)\n\tassert.Equal(t, []string{\"a\", \"c\"}, names)\n}\n\nfunc TestAlterTable(t *testing.T) {\n\tconn, cleanup := openInMemory(t)\n\tdefer cleanup()\n\n\tprepExec(t, conn.conn, \"CREATE TABLE t(id INT PRIMARY KEY)\")\n\tprepExec(t, conn.conn, \"ALTER TABLE t ADD COLUMN name TEXT\")\n\t// Insert with new column present\n\tprepExec(t, conn.conn, \"INSERT INTO t(id, name) VALUES(1, 'hello')\")\n\n\tstmt := prepStmt(t, conn.conn, \"SELECT name FROM t WHERE id = 1\")\n\tdefer func() {\n\t\t_ = turso_statement_finalize(stmt)\n\t\tturso_statement_deinit(stmt)\n\t}()\n\trequire.True(t, stepRow(t, stmt))\n\tassert.Equal(t, \"hello\", turso_statement_row_value_text(stmt, 0))\n\trequire.False(t, stepRow(t, stmt))\n}\n\nfunc TestGenerateSeries(t *testing.T) {\n\tconn, cleanup := openInMemory(t)\n\tdefer cleanup()\n\n\tstmt := prepStmt(t, conn.conn, \"SELECT value FROM generate_series(1,5)\")\n\tdefer func() {\n\t\t_ = turso_statement_finalize(stmt)\n\t\tturso_statement_deinit(stmt)\n\t}()\n\n\tvar got []int64\n\tfor {\n\t\tif !stepRow(t, stmt) {\n\t\t\tbreak\n\t\t}\n\t\tgot = append(got, turso_statement_row_value_int(stmt, 0))\n\t}\n\tassert.Equal(t, []int64{1, 2, 3, 4, 5}, got)\n}\n\nfunc TestJSONFunctionsBindings(t *testing.T) {\n\tconn, cleanup := openInMemory(t)\n\tdefer cleanup()\n\n\tstmt := prepStmt(t, conn.conn, \"SELECT json_extract('{\\\"x\\\": [1,2,3]}', '$.x[1]'), json_array_length('[1,2,3]')\")\n\tdefer func() {\n\t\t_ = turso_statement_finalize(stmt)\n\t\tturso_statement_deinit(stmt)\n\t}()\n\n\trequire.True(t, stepRow(t, stmt))\n\tkind0 := turso_statement_row_value_kind(stmt, 0)\n\tkind1 := turso_statement_row_value_kind(stmt, 1)\n\tassert.Equal(t, TURSO_TYPE_INTEGER, kind0)\n\tassert.Equal(t, TURSO_TYPE_INTEGER, kind1)\n\tassert.Equal(t, int64(2), turso_statement_row_value_int(stmt, 0))\n\tassert.Equal(t, int64(3), turso_statement_row_value_int(stmt, 1))\n\trequire.False(t, stepRow(t, stmt))\n}\n\nfunc TestBindingsPositionalAndNamed(t *testing.T) {\n\tconn, cleanup := openInMemory(t)\n\tdefer cleanup()\n\n\tprepExec(t, conn.conn, \"CREATE TABLE t(i INTEGER, r REAL, s TEXT, b BLOB, n NULL)\")\n\n\t// Positional parameters: ?1..?\n\tstmt := prepStmt(t, conn.conn, \"INSERT INTO t(i,r,s,b,n) VALUES (?1,?2,?3,?4,?5)\")\n\trequire.NoError(t, turso_statement_bind_positional_int(stmt, 1, 42))\n\trequire.NoError(t, turso_statement_bind_positional_double(stmt, 2, 3.14))\n\trequire.NoError(t, turso_statement_bind_positional_text(stmt, 3, \"hello\"))\n\trequire.NoError(t, turso_statement_bind_positional_blob(stmt, 4, []byte{0xde, 0xad, 0xbe, 0xef}))\n\trequire.NoError(t, turso_statement_bind_positional_null(stmt, 5))\n\t_, _, err := turso_statement_execute(stmt)\n\trequire.NoError(t, err)\n\t_ = turso_statement_finalize(stmt)\n\tturso_statement_deinit(stmt)\n\n\t// Named parameters mapped to positional via named_position\n\tstmt2 := prepStmt(t, conn.conn, \"INSERT INTO t(i,r,s,b,n) VALUES (:i,:r,:s,:b,:n)\")\n\tdefer func() {\n\t\t_ = turso_statement_finalize(stmt2)\n\t\tturso_statement_deinit(stmt2)\n\t}()\n\tposI := turso_statement_named_position(stmt2, \":i\")\n\tposR := turso_statement_named_position(stmt2, \":r\")\n\tposS := turso_statement_named_position(stmt2, \":s\")\n\tposB := turso_statement_named_position(stmt2, \":b\")\n\tposN := turso_statement_named_position(stmt2, \":n\")\n\trequire.Equal(t, posI, int64(1))\n\trequire.Equal(t, posR, int64(2))\n\trequire.Equal(t, posS, int64(3))\n\trequire.Equal(t, posB, int64(4))\n\trequire.Equal(t, posN, int64(5))\n\n\trequire.NoError(t, turso_statement_bind_positional_int(stmt2, int(posI), 7))\n\trequire.NoError(t, turso_statement_bind_positional_double(stmt2, int(posR), -1.5))\n\trequire.NoError(t, turso_statement_bind_positional_text(stmt2, int(posS), \"world\"))\n\trequire.NoError(t, turso_statement_bind_positional_blob(stmt2, int(posB), []byte{})) // empty blob\n\trequire.NoError(t, turso_statement_bind_positional_null(stmt2, int(posN)))\n\t_, _, err = turso_statement_execute(stmt2)\n\trequire.NoError(t, err)\n\n\t// Verify retrieved values using row value helpers\n\tstmt3 := prepStmt(t, conn.conn, \"SELECT i,r,s,b,n FROM t\")\n\tdefer func() {\n\t\t_ = turso_statement_finalize(stmt3)\n\t\tturso_statement_deinit(stmt3)\n\t}()\n\n\t// first row\n\trequire.True(t, stepRow(t, stmt3))\n\tassert.Equal(t, TURSO_TYPE_INTEGER, turso_statement_row_value_kind(stmt3, 0))\n\tassert.Equal(t, int64(42), turso_statement_row_value_int(stmt3, 0))\n\tassert.Equal(t, TURSO_TYPE_REAL, turso_statement_row_value_kind(stmt3, 1))\n\tassert.InDelta(t, 3.14, turso_statement_row_value_double(stmt3, 1), 1e-9)\n\tassert.Equal(t, TURSO_TYPE_TEXT, turso_statement_row_value_kind(stmt3, 2))\n\tassert.Equal(t, \"hello\", turso_statement_row_value_text(stmt3, 2))\n\tassert.Equal(t, TURSO_TYPE_BLOB, turso_statement_row_value_kind(stmt3, 3))\n\tassert.Equal(t, []byte{0xde, 0xad, 0xbe, 0xef}, turso_statement_row_value_bytes(stmt3, 3))\n\tassert.Equal(t, TURSO_TYPE_NULL, turso_statement_row_value_kind(stmt3, 4))\n\n\t// second row\n\trequire.True(t, stepRow(t, stmt3))\n\tassert.Equal(t, int64(7), turso_statement_row_value_int(stmt3, 0))\n\tassert.InDelta(t, -1.5, turso_statement_row_value_double(stmt3, 1), 1e-9)\n\tassert.Equal(t, \"world\", turso_statement_row_value_text(stmt3, 2))\n\t// empty blob\n\tassert.Nil(t, turso_statement_row_value_bytes(stmt3, 3))\n\trequire.False(t, stepRow(t, stmt3))\n}\n\nfunc TestColumnMetadata(t *testing.T) {\n\tconn, cleanup := openInMemory(t)\n\tdefer cleanup()\n\n\tprepExec(t, conn.conn, \"CREATE TABLE t(id INT, name TEXT)\")\n\tprepExec(t, conn.conn, \"INSERT INTO t(id, name) VALUES (1, 'alice')\")\n\n\tstmt := prepStmt(t, conn.conn, \"SELECT id, name FROM t\")\n\tdefer func() {\n\t\t_ = turso_statement_finalize(stmt)\n\t\tturso_statement_deinit(stmt)\n\t}()\n\tcc := turso_statement_column_count(stmt)\n\trequire.Equal(t, int64(2), cc)\n\tn0 := turso_statement_column_name(stmt, 0)\n\tn1 := turso_statement_column_name(stmt, 1)\n\tassert.Equal(t, \"id\", n0)\n\tassert.Equal(t, \"name\", n1)\n\n\trequire.True(t, stepRow(t, stmt))\n\tassert.Equal(t, int64(1), turso_statement_row_value_int(stmt, 0))\n\tassert.Equal(t, \"alice\", turso_statement_row_value_text(stmt, 1))\n\trequire.False(t, stepRow(t, stmt))\n}\n"
  },
  {
    "path": "bindings/go/bindings_sync.go",
    "content": "package turso // import \"github.com/tursodatabase/turso/go\"\n\nimport (\n\t\"runtime\"\n\t\"unsafe\"\n\n\t\"github.com/ebitengine/purego\"\n)\n\n// ------------- Opaque types for sync engine -------------\n\ntype turso_sync_database_t struct{}\ntype turso_sync_operation_t struct{}\ntype turso_sync_io_item_t struct{}\ntype turso_sync_changes_t struct{}\n\ntype TursoSyncDatabase *turso_sync_database_t\ntype TursoSyncOperation *turso_sync_operation_t\ntype TursoSyncIoItem *turso_sync_io_item_t\ntype TursoSyncChanges *turso_sync_changes_t\n\n// ------------- Enums -------------\n\ntype TursoSyncIoRequestType int32\n\nconst (\n\tTURSO_SYNC_IO_NONE       TursoSyncIoRequestType = 0\n\tTURSO_SYNC_IO_HTTP       TursoSyncIoRequestType = 1\n\tTURSO_SYNC_IO_FULL_READ  TursoSyncIoRequestType = 2\n\tTURSO_SYNC_IO_FULL_WRITE TursoSyncIoRequestType = 3\n)\n\ntype TursoSyncOperationResultType int32\n\nconst (\n\tTURSO_ASYNC_RESULT_NONE       TursoSyncOperationResultType = 0\n\tTURSO_ASYNC_RESULT_CONNECTION TursoSyncOperationResultType = 1\n\tTURSO_ASYNC_RESULT_CHANGES    TursoSyncOperationResultType = 2\n\tTURSO_ASYNC_RESULT_STATS      TursoSyncOperationResultType = 3\n)\n\n// ------------- Public binding types -------------\n\n// TursoSyncDatabaseConfig describes database sync configuration.\ntype TursoSyncDatabaseConfig struct {\n\t// Path to the main database file (auxiliary files will derive names from this path)\n\tPath string\n\t// optional remote url (libsql://..., https://... or http://...)\n\t// this URL will be saved in the database metadata file in order to be able to reuse it if later client will be constructed without explicit remote url\n\tRemoteUrl string\n\t// Namespace for remote host\n\tNamespace string\n\t// Arbitrary client name used as a prefix for unique client id\n\tClientName string\n\t// Long poll timeout for pull method in milliseconds\n\tLongPollTimeoutMs int\n\t// Bootstrap db if empty; if set - client will be able to connect to fresh db only when network is online\n\tBootstrapIfEmpty bool\n\t// Reserved bytes set for the database - necessary if remote encryption is set for the db in cloud\n\tReservedBytes int\n\t// Prefix bootstrap strategy enabling partial sync that lazily pulls pages on demand and bootstraps db with first N bytes\n\tPartialBootstrapStrategyPrefix int\n\t// Query bootstrap strategy enabling partial sync - bootstraps db with pages touched by the server with given SQL query\n\tPartialBootstrapStrategyQuery string\n\t// optional parameter which defines segment size for lazy loading from remote server\n\t// one of valid PartialBootstrapStrategy* values MUST be set in order for this setting to have some effect\n\tPartialBootstrapSegmentSize int\n\t// optional parameter which defines if pages prefetch must be enabled\n\t// one of valid PartialBootstrapStrategy* values MUST be set in order for this setting to have some effect\n\tPartialBootstrapPrefetch bool\n\t// optional base64-encoded encryption key for remote encrypted databases\n\tRemoteEncryptionKey string\n\t// optional encryption cipher name (e.g. \"aes256gcm\", \"chacha20poly1305\")\n\tRemoteEncryptionCipher string\n}\n\n// TursoSyncStats holds sync engine stats.\ntype TursoSyncStats struct {\n\tCDcOperations        int64\n\tMainWalSize          int64\n\tRevertWalSize        int64\n\tLastPullUnixTime     int64\n\tLastPushUnixTime     int64\n\tNetworkSentBytes     int64\n\tNetworkReceivedBytes int64\n\tRevision             string\n}\n\n// HTTP request description used by IO layer.\ntype TursoSyncIoHttpRequest struct {\n\tUrl     string\n\tMethod  string\n\tPath    string\n\tBody    []byte\n\tHeaders int\n}\n\n// HTTP header key-value pair.\ntype TursoSyncIoHttpHeader struct {\n\tKey   string\n\tValue string\n}\n\n// Atomic read request description.\ntype TursoSyncIoFullReadRequest struct {\n\tPath string\n}\n\n// Atomic write request description.\ntype TursoSyncIoFullWriteRequest struct {\n\tPath    string\n\tContent []byte\n}\n\n// ------------- Private C-compatible structs -------------\n\ntype turso_sync_database_config_t struct {\n\tpath                              uintptr // const char*\n\tremote_url                        uintptr // const char*\n\tclient_name                       uintptr // const char*\n\tlong_poll_timeout_ms              int32\n\tbootstrap_if_empty                bool\n\treserved_bytes                    int32\n\tpartial_bootstrap_strategy_prefix int32\n\tpartial_bootstrap_strategy_query  uintptr // const char*\n\tpartial_bootstrap_segment_size    uintptr\n\tpartial_bootstrap_prefetch        bool\n\tremote_encryption_key             uintptr // const char*\n\tremote_encryption_cipher          uintptr // const char*\n}\n\ntype turso_sync_io_http_request_t struct {\n\turl     turso_slice_ref_t\n\tmethod  turso_slice_ref_t\n\tpath    turso_slice_ref_t\n\tbody    turso_slice_ref_t\n\theaders int32\n}\n\ntype turso_sync_io_http_header_t struct {\n\tkey   turso_slice_ref_t\n\tvalue turso_slice_ref_t\n}\n\ntype turso_sync_io_full_read_request_t struct {\n\tpath turso_slice_ref_t\n}\n\ntype turso_sync_io_full_write_request_t struct {\n\tpath    turso_slice_ref_t\n\tcontent turso_slice_ref_t\n}\n\ntype turso_sync_stats_t struct {\n\tcdc_operations         int64\n\tmain_wal_size          int64\n\trevert_wal_size        int64\n\tlast_pull_unix_time    int64\n\tlast_push_unix_time    int64\n\tnetwork_sent_bytes     int64\n\tnetwork_received_bytes int64\n\trevision               turso_slice_ref_t\n}\n\n// ------------- C extern function vars -------------\n\nvar (\n\tc_turso_sync_database_new func(\n\t\tdbConfig *turso_database_config_t,\n\t\tsyncConfig *turso_sync_database_config_t,\n\t\tdatabase **turso_sync_database_t,\n\t\terrorOptOut **byte,\n\t) int32\n\n\tc_turso_sync_database_open func(\n\t\tself TursoSyncDatabase,\n\t\toperation **turso_sync_operation_t,\n\t\terrorOptOut **byte,\n\t) int32\n\n\tc_turso_sync_database_create func(\n\t\tself TursoSyncDatabase,\n\t\toperation **turso_sync_operation_t,\n\t\terrorOptOut **byte,\n\t) int32\n\n\tc_turso_sync_database_connect func(\n\t\tself TursoSyncDatabase,\n\t\toperation **turso_sync_operation_t,\n\t\terrorOptOut **byte,\n\t) int32\n\n\tc_turso_sync_database_stats func(\n\t\tself TursoSyncDatabase,\n\t\toperation **turso_sync_operation_t,\n\t\terrorOptOut **byte,\n\t) int32\n\n\tc_turso_sync_database_checkpoint func(\n\t\tself TursoSyncDatabase,\n\t\toperation **turso_sync_operation_t,\n\t\terrorOptOut **byte,\n\t) int32\n\n\tc_turso_sync_database_push_changes func(\n\t\tself TursoSyncDatabase,\n\t\toperation **turso_sync_operation_t,\n\t\terrorOptOut **byte,\n\t) int32\n\n\tc_turso_sync_database_wait_changes func(\n\t\tself TursoSyncDatabase,\n\t\toperation **turso_sync_operation_t,\n\t\terrorOptOut **byte,\n\t) int32\n\n\tc_turso_sync_database_apply_changes func(\n\t\tself TursoSyncDatabase,\n\t\tchanges TursoSyncChanges,\n\t\toperation **turso_sync_operation_t,\n\t\terrorOptOut **byte,\n\t) int32\n\n\tc_turso_sync_operation_resume func(\n\t\tself TursoSyncOperation,\n\t\terrorOptOut **byte,\n\t) int32\n\n\tc_turso_sync_operation_result_kind func(\n\t\tself TursoSyncOperation,\n\t) int32\n\n\tc_turso_sync_operation_result_extract_connection func(\n\t\tself TursoSyncOperation,\n\t\tconnection **turso_connection_t,\n\t) int32\n\n\tc_turso_sync_operation_result_extract_changes func(\n\t\tself TursoSyncOperation,\n\t\tchanges **turso_sync_changes_t,\n\t) int32\n\n\tc_turso_sync_operation_result_extract_stats func(\n\t\tself TursoSyncOperation,\n\t\tstats *turso_sync_stats_t,\n\t) int32\n\n\tc_turso_sync_database_io_take_item func(\n\t\tself TursoSyncDatabase,\n\t\titem **turso_sync_io_item_t,\n\t\terrorOptOut **byte,\n\t) int32\n\n\tc_turso_sync_database_io_step_callbacks func(\n\t\tself TursoSyncDatabase,\n\t\terrorOptOut **byte,\n\t) int32\n\n\tc_turso_sync_database_io_request_kind func(\n\t\tself TursoSyncIoItem,\n\t) int32\n\n\tc_turso_sync_database_io_request_http func(\n\t\tself TursoSyncIoItem,\n\t\trequest *turso_sync_io_http_request_t,\n\t) int32\n\n\tc_turso_sync_database_io_request_http_header func(\n\t\tself TursoSyncIoItem,\n\t\tindex uintptr,\n\t\theader *turso_sync_io_http_header_t,\n\t) int32\n\n\tc_turso_sync_database_io_request_full_read func(\n\t\tself TursoSyncIoItem,\n\t\trequest *turso_sync_io_full_read_request_t,\n\t) int32\n\n\tc_turso_sync_database_io_request_full_write func(\n\t\tself TursoSyncIoItem,\n\t\trequest *turso_sync_io_full_write_request_t,\n\t) int32\n\n\tc_turso_sync_database_io_poison func(\n\t\tself TursoSyncIoItem,\n\t\terr *turso_slice_ref_t,\n\t) int32\n\n\tc_turso_sync_database_io_status func(\n\t\tself TursoSyncIoItem,\n\t\tstatus int32,\n\t) int32\n\n\tc_turso_sync_database_io_push_buffer func(\n\t\tself TursoSyncIoItem,\n\t\tbuffer *turso_slice_ref_t,\n\t) int32\n\n\tc_turso_sync_database_io_done func(\n\t\tself TursoSyncIoItem,\n\t) int32\n\n\tc_turso_sync_database_deinit func(\n\t\tself TursoSyncDatabase,\n\t)\n\n\tc_turso_sync_operation_deinit func(\n\t\tself TursoSyncOperation,\n\t)\n\n\tc_turso_sync_database_io_item_deinit func(\n\t\tself TursoSyncIoItem,\n\t)\n\n\tc_turso_sync_changes_deinit func(\n\t\tself TursoSyncChanges,\n\t)\n)\n\n// ------------- Registration -------------\n\n// registerTursoSync registers Turso Sync C API function pointers from the given library handle.\n// Do not load library here; it is done externally.\nfunc registerTursoSync(handle uintptr) error {\n\tpurego.RegisterLibFunc(&c_turso_sync_database_new, handle, \"turso_sync_database_new\")\n\tpurego.RegisterLibFunc(&c_turso_sync_database_open, handle, \"turso_sync_database_open\")\n\tpurego.RegisterLibFunc(&c_turso_sync_database_create, handle, \"turso_sync_database_create\")\n\tpurego.RegisterLibFunc(&c_turso_sync_database_connect, handle, \"turso_sync_database_connect\")\n\tpurego.RegisterLibFunc(&c_turso_sync_database_stats, handle, \"turso_sync_database_stats\")\n\tpurego.RegisterLibFunc(&c_turso_sync_database_checkpoint, handle, \"turso_sync_database_checkpoint\")\n\tpurego.RegisterLibFunc(&c_turso_sync_database_push_changes, handle, \"turso_sync_database_push_changes\")\n\tpurego.RegisterLibFunc(&c_turso_sync_database_wait_changes, handle, \"turso_sync_database_wait_changes\")\n\tpurego.RegisterLibFunc(&c_turso_sync_database_apply_changes, handle, \"turso_sync_database_apply_changes\")\n\tpurego.RegisterLibFunc(&c_turso_sync_operation_resume, handle, \"turso_sync_operation_resume\")\n\tpurego.RegisterLibFunc(&c_turso_sync_operation_result_kind, handle, \"turso_sync_operation_result_kind\")\n\tpurego.RegisterLibFunc(&c_turso_sync_operation_result_extract_connection, handle, \"turso_sync_operation_result_extract_connection\")\n\tpurego.RegisterLibFunc(&c_turso_sync_operation_result_extract_changes, handle, \"turso_sync_operation_result_extract_changes\")\n\tpurego.RegisterLibFunc(&c_turso_sync_operation_result_extract_stats, handle, \"turso_sync_operation_result_extract_stats\")\n\tpurego.RegisterLibFunc(&c_turso_sync_database_io_take_item, handle, \"turso_sync_database_io_take_item\")\n\tpurego.RegisterLibFunc(&c_turso_sync_database_io_step_callbacks, handle, \"turso_sync_database_io_step_callbacks\")\n\tpurego.RegisterLibFunc(&c_turso_sync_database_io_request_kind, handle, \"turso_sync_database_io_request_kind\")\n\tpurego.RegisterLibFunc(&c_turso_sync_database_io_request_http, handle, \"turso_sync_database_io_request_http\")\n\tpurego.RegisterLibFunc(&c_turso_sync_database_io_request_http_header, handle, \"turso_sync_database_io_request_http_header\")\n\tpurego.RegisterLibFunc(&c_turso_sync_database_io_request_full_read, handle, \"turso_sync_database_io_request_full_read\")\n\tpurego.RegisterLibFunc(&c_turso_sync_database_io_request_full_write, handle, \"turso_sync_database_io_request_full_write\")\n\tpurego.RegisterLibFunc(&c_turso_sync_database_io_poison, handle, \"turso_sync_database_io_poison\")\n\tpurego.RegisterLibFunc(&c_turso_sync_database_io_status, handle, \"turso_sync_database_io_status\")\n\tpurego.RegisterLibFunc(&c_turso_sync_database_io_push_buffer, handle, \"turso_sync_database_io_push_buffer\")\n\tpurego.RegisterLibFunc(&c_turso_sync_database_io_done, handle, \"turso_sync_database_io_done\")\n\tpurego.RegisterLibFunc(&c_turso_sync_database_deinit, handle, \"turso_sync_database_deinit\")\n\tpurego.RegisterLibFunc(&c_turso_sync_operation_deinit, handle, \"turso_sync_operation_deinit\")\n\tpurego.RegisterLibFunc(&c_turso_sync_database_io_item_deinit, handle, \"turso_sync_database_io_item_deinit\")\n\tpurego.RegisterLibFunc(&c_turso_sync_changes_deinit, handle, \"turso_sync_changes_deinit\")\n\treturn nil\n}\n\n// ------------- Helpers -------------\n\nfunc sliceRefToBytesCopy(s turso_slice_ref_t) []byte {\n\tif s.ptr == 0 || s.len == 0 {\n\t\treturn nil\n\t}\n\tp := (*byte)(unsafe.Pointer(s.ptr))\n\tn := int(s.len)\n\tsrc := unsafe.Slice(p, n)\n\tdst := make([]byte, n)\n\tcopy(dst, src)\n\treturn dst\n}\n\nfunc sliceRefToStringCopy(s turso_slice_ref_t) string {\n\tb := sliceRefToBytesCopy(s)\n\tif len(b) == 0 {\n\t\treturn \"\"\n\t}\n\treturn string(b)\n}\n\n// ------------- Go wrappers over C API -------------\n\n// turso_sync_database_new creates the database sync holder but does not open it.\nfunc turso_sync_database_new(dbConfig TursoDatabaseConfig, syncConfig TursoSyncDatabaseConfig) (TursoSyncDatabase, error) {\n\t// Build C database config\n\tvar cdb turso_database_config_t\n\tvar pathBytes, expBytes []byte\n\tpathBytes, cdb.path = makeCStringBytes(dbConfig.Path)\n\tif dbConfig.ExperimentalFeatures != \"\" {\n\t\texpBytes, cdb.experimental_features = makeCStringBytes(dbConfig.ExperimentalFeatures)\n\t}\n\tcdb.async_io = 0\n\tif dbConfig.AsyncIO {\n\t\tcdb.async_io = 1\n\t}\n\n\t// Build C sync config\n\tvar csync turso_sync_database_config_t\n\tvar syncPathBytes, remoteUrlBytes, clientNameBytes, queryBytes []byte\n\tvar encryptionKeyBytes, encryptionCipherBytes []byte\n\tsyncPathBytes, csync.path = makeCStringBytes(syncConfig.Path)\n\tremoteUrlBytes, csync.remote_url = makeCStringBytes(syncConfig.RemoteUrl)\n\tclientNameBytes, csync.client_name = makeCStringBytes(syncConfig.ClientName)\n\tcsync.long_poll_timeout_ms = int32(syncConfig.LongPollTimeoutMs)\n\tcsync.bootstrap_if_empty = syncConfig.BootstrapIfEmpty\n\tcsync.reserved_bytes = int32(syncConfig.ReservedBytes)\n\tcsync.partial_bootstrap_strategy_prefix = int32(syncConfig.PartialBootstrapStrategyPrefix)\n\tcsync.partial_bootstrap_segment_size = uintptr(syncConfig.PartialBootstrapSegmentSize)\n\tcsync.partial_bootstrap_prefetch = syncConfig.PartialBootstrapPrefetch\n\tif syncConfig.PartialBootstrapStrategyQuery != \"\" {\n\t\tqueryBytes, csync.partial_bootstrap_strategy_query = makeCStringBytes(syncConfig.PartialBootstrapStrategyQuery)\n\t}\n\tif syncConfig.RemoteEncryptionKey != \"\" {\n\t\tencryptionKeyBytes, csync.remote_encryption_key = makeCStringBytes(syncConfig.RemoteEncryptionKey)\n\t}\n\tif syncConfig.RemoteEncryptionCipher != \"\" {\n\t\tencryptionCipherBytes, csync.remote_encryption_cipher = makeCStringBytes(syncConfig.RemoteEncryptionCipher)\n\t}\n\n\tvar db *turso_sync_database_t\n\tvar errPtr *byte\n\tstatus := c_turso_sync_database_new(&cdb, &csync, &db, &errPtr)\n\n\t// Keep Go memory alive during C call\n\truntime.KeepAlive(pathBytes)\n\truntime.KeepAlive(remoteUrlBytes)\n\truntime.KeepAlive(expBytes)\n\truntime.KeepAlive(syncPathBytes)\n\truntime.KeepAlive(clientNameBytes)\n\truntime.KeepAlive(queryBytes)\n\truntime.KeepAlive(encryptionKeyBytes)\n\truntime.KeepAlive(encryptionCipherBytes)\n\n\tif status == int32(TURSO_OK) {\n\t\treturn TursoSyncDatabase(db), nil\n\t}\n\tmsg := decodeAndFreeCString(errPtr)\n\treturn nil, statusToError(TursoStatusCode(status), msg)\n}\n\n// turso_sync_database_open opens prepared synced database. Fails if no properly setup database exists.\n// AsyncOperation returns None.\nfunc turso_sync_database_open(self TursoSyncDatabase) (TursoSyncOperation, error) {\n\tvar op *turso_sync_operation_t\n\tvar errPtr *byte\n\tstatus := c_turso_sync_database_open(self, &op, &errPtr)\n\tif status == int32(TURSO_OK) {\n\t\treturn TursoSyncOperation(op), nil\n\t}\n\tmsg := decodeAndFreeCString(errPtr)\n\treturn nil, statusToError(TursoStatusCode(status), msg)\n}\n\n// turso_sync_database_create opens or prepares synced database or creates it if needed.\n// AsyncOperation returns None.\nfunc turso_sync_database_create(self TursoSyncDatabase) (TursoSyncOperation, error) {\n\tvar op *turso_sync_operation_t\n\tvar errPtr *byte\n\tstatus := c_turso_sync_database_create(self, &op, &errPtr)\n\tif status == int32(TURSO_OK) {\n\t\treturn TursoSyncOperation(op), nil\n\t}\n\tmsg := decodeAndFreeCString(errPtr)\n\treturn nil, statusToError(TursoStatusCode(status), msg)\n}\n\n// turso_sync_database_connect creates a turso database connection.\n// SAFETY: synced database must be opened before this operation.\n// AsyncOperation returns Connection.\nfunc turso_sync_database_connect(self TursoSyncDatabase) (TursoSyncOperation, error) {\n\tvar op *turso_sync_operation_t\n\tvar errPtr *byte\n\tstatus := c_turso_sync_database_connect(self, &op, &errPtr)\n\tif status == int32(TURSO_OK) {\n\t\treturn TursoSyncOperation(op), nil\n\t}\n\tmsg := decodeAndFreeCString(errPtr)\n\treturn nil, statusToError(TursoStatusCode(status), msg)\n}\n\n// turso_sync_database_stats collects stats about synced database.\n// AsyncOperation returns Stats.\nfunc turso_sync_database_stats(self TursoSyncDatabase) (TursoSyncOperation, error) {\n\tvar op *turso_sync_operation_t\n\tvar errPtr *byte\n\tstatus := c_turso_sync_database_stats(self, &op, &errPtr)\n\tif status == int32(TURSO_OK) {\n\t\treturn TursoSyncOperation(op), nil\n\t}\n\tmsg := decodeAndFreeCString(errPtr)\n\treturn nil, statusToError(TursoStatusCode(status), msg)\n}\n\n// turso_sync_database_checkpoint performs WAL checkpoint for the synced database.\n// AsyncOperation returns None.\nfunc turso_sync_database_checkpoint(self TursoSyncDatabase) (TursoSyncOperation, error) {\n\tvar op *turso_sync_operation_t\n\tvar errPtr *byte\n\tstatus := c_turso_sync_database_checkpoint(self, &op, &errPtr)\n\tif status == int32(TURSO_OK) {\n\t\treturn TursoSyncOperation(op), nil\n\t}\n\tmsg := decodeAndFreeCString(errPtr)\n\treturn nil, statusToError(TursoStatusCode(status), msg)\n}\n\n// turso_sync_database_push_changes pushes local changes to remote.\n// AsyncOperation returns None.\nfunc turso_sync_database_push_changes(self TursoSyncDatabase) (TursoSyncOperation, error) {\n\tvar op *turso_sync_operation_t\n\tvar errPtr *byte\n\tstatus := c_turso_sync_database_push_changes(self, &op, &errPtr)\n\tif status == int32(TURSO_OK) {\n\t\treturn TursoSyncOperation(op), nil\n\t}\n\tmsg := decodeAndFreeCString(errPtr)\n\treturn nil, statusToError(TursoStatusCode(status), msg)\n}\n\n// turso_sync_database_wait_changes waits for remote changes.\n// AsyncOperation returns Changes.\nfunc turso_sync_database_wait_changes(self TursoSyncDatabase) (TursoSyncOperation, error) {\n\tvar op *turso_sync_operation_t\n\tvar errPtr *byte\n\tstatus := c_turso_sync_database_wait_changes(self, &op, &errPtr)\n\tif status == int32(TURSO_OK) {\n\t\treturn TursoSyncOperation(op), nil\n\t}\n\tmsg := decodeAndFreeCString(errPtr)\n\treturn nil, statusToError(TursoStatusCode(status), msg)\n}\n\n// turso_sync_database_apply_changes applies remote changes locally.\n// SAFETY: caller must ensure that no other methods are executing concurrently (push/wait/checkpoint).\n//\n// the method CONSUMES turso_sync_changes_t instance and caller no longer owns it after the call\n// So, the changes MUST NOT be explicitly deallocated after the method call (either successful or not)\n//\n// AsyncOperation returns None.\nfunc turso_sync_database_apply_changes(self TursoSyncDatabase, changes TursoSyncChanges) (TursoSyncOperation, error) {\n\tvar op *turso_sync_operation_t\n\tvar errPtr *byte\n\tstatus := c_turso_sync_database_apply_changes(self, changes, &op, &errPtr)\n\tif status == int32(TURSO_OK) {\n\t\treturn TursoSyncOperation(op), nil\n\t}\n\tmsg := decodeAndFreeCString(errPtr)\n\treturn nil, statusToError(TursoStatusCode(status), msg)\n}\n\n// turso_sync_operation_resume resumes async operation.\n// Returns status code (OK/IO/DONE or error code) and error if any.\nfunc turso_sync_operation_resume(self TursoSyncOperation) (TursoStatusCode, error) {\n\tvar errPtr *byte\n\tstatus := c_turso_sync_operation_resume(self, &errPtr)\n\tswitch TursoStatusCode(status) {\n\tcase TURSO_OK, TURSO_IO, TURSO_DONE:\n\t\treturn TursoStatusCode(status), nil\n\tdefault:\n\t\tmsg := decodeAndFreeCString(errPtr)\n\t\treturn TursoStatusCode(status), statusToError(TursoStatusCode(status), msg)\n\t}\n}\n\n// turso_sync_operation_result_kind extracts operation result kind.\nfunc turso_sync_operation_result_kind(self TursoSyncOperation) TursoSyncOperationResultType {\n\treturn TursoSyncOperationResultType(c_turso_sync_operation_result_kind(self))\n}\n\n// turso_sync_operation_result_extract_connection extracts Connection result from finished operation.\nfunc turso_sync_operation_result_extract_connection(self TursoSyncOperation) (TursoConnection, error) {\n\tvar conn *turso_connection_t\n\tstatus := c_turso_sync_operation_result_extract_connection(self, &conn)\n\tif status == int32(TURSO_OK) {\n\t\treturn TursoConnection(conn), nil\n\t}\n\treturn nil, statusToError(TursoStatusCode(status), \"\")\n}\n\n// turso_sync_operation_result_extract_changes extracts Changes result from finished operation.\n// If no changes were fetched - return TURSO_OK and set changes to null pointer\nfunc turso_sync_operation_result_extract_changes(self TursoSyncOperation) (TursoSyncChanges, error) {\n\tvar ch *turso_sync_changes_t\n\tstatus := c_turso_sync_operation_result_extract_changes(self, &ch)\n\tif status == int32(TURSO_OK) {\n\t\treturn TursoSyncChanges(ch), nil\n\t}\n\treturn nil, statusToError(TursoStatusCode(status), \"\")\n}\n\n// turso_sync_operation_result_extract_stats extracts Stats result from finished operation.\nfunc turso_sync_operation_result_extract_stats(self TursoSyncOperation) (TursoSyncStats, error) {\n\tvar cstats turso_sync_stats_t\n\tstatus := c_turso_sync_operation_result_extract_stats(self, &cstats)\n\tif status != int32(TURSO_OK) {\n\t\treturn TursoSyncStats{}, statusToError(TursoStatusCode(status), \"\")\n\t}\n\treturn TursoSyncStats{\n\t\tCDcOperations:        cstats.cdc_operations,\n\t\tMainWalSize:          cstats.main_wal_size,\n\t\tRevertWalSize:        cstats.revert_wal_size,\n\t\tLastPullUnixTime:     cstats.last_pull_unix_time,\n\t\tLastPushUnixTime:     cstats.last_push_unix_time,\n\t\tNetworkSentBytes:     cstats.network_sent_bytes,\n\t\tNetworkReceivedBytes: cstats.network_received_bytes,\n\t\tRevision:             sliceRefToStringCopy(cstats.revision),\n\t}, nil\n}\n\n// turso_sync_database_io_take_item tries to take IO request from the sync engine IO queue.\n// if queue is empty - returns nil pointer to the TursoSyncIoItem\nfunc turso_sync_database_io_take_item(self TursoSyncDatabase) (TursoSyncIoItem, error) {\n\tvar item *turso_sync_io_item_t\n\tvar errPtr *byte\n\tstatus := c_turso_sync_database_io_take_item(self, &item, &errPtr)\n\tif status == int32(TURSO_OK) {\n\t\treturn TursoSyncIoItem(item), nil\n\t}\n\tmsg := decodeAndFreeCString(errPtr)\n\treturn nil, statusToError(TursoStatusCode(status), msg)\n}\n\n// turso_sync_database_io_step_callbacks runs extra database callbacks after IO execution.\nfunc turso_sync_database_io_step_callbacks(self TursoSyncDatabase) error {\n\tvar errPtr *byte\n\tstatus := c_turso_sync_database_io_step_callbacks(self, &errPtr)\n\tif status == int32(TURSO_OK) {\n\t\treturn nil\n\t}\n\tmsg := decodeAndFreeCString(errPtr)\n\treturn statusToError(TursoStatusCode(status), msg)\n}\n\n// turso_sync_database_io_request_kind returns the IO request kind.\nfunc turso_sync_database_io_request_kind(self TursoSyncIoItem) TursoSyncIoRequestType {\n\treturn TursoSyncIoRequestType(c_turso_sync_database_io_request_kind(self))\n}\n\n// turso_sync_database_io_request_http gets HTTP request fields for an IO item.\nfunc turso_sync_database_io_request_http(self TursoSyncIoItem) (TursoSyncIoHttpRequest, error) {\n\tvar creq turso_sync_io_http_request_t\n\tstatus := c_turso_sync_database_io_request_http(self, &creq)\n\tif status != int32(TURSO_OK) {\n\t\treturn TursoSyncIoHttpRequest{}, statusToError(TursoStatusCode(status), \"\")\n\t}\n\treturn TursoSyncIoHttpRequest{\n\t\tUrl:     sliceRefToStringCopy(creq.url),\n\t\tMethod:  sliceRefToStringCopy(creq.method),\n\t\tPath:    sliceRefToStringCopy(creq.path),\n\t\tBody:    sliceRefToBytesCopy(creq.body),\n\t\tHeaders: int(creq.headers),\n\t}, nil\n}\n\n// turso_sync_database_io_request_http_header returns HTTP header key-value pair at index.\nfunc turso_sync_database_io_request_http_header(self TursoSyncIoItem, index int) (TursoSyncIoHttpHeader, error) {\n\tvar ch turso_sync_io_http_header_t\n\tstatus := c_turso_sync_database_io_request_http_header(self, uintptr(index), &ch)\n\tif status != int32(TURSO_OK) {\n\t\treturn TursoSyncIoHttpHeader{}, statusToError(TursoStatusCode(status), \"\")\n\t}\n\treturn TursoSyncIoHttpHeader{\n\t\tKey:   sliceRefToStringCopy(ch.key),\n\t\tValue: sliceRefToStringCopy(ch.value),\n\t}, nil\n}\n\n// turso_sync_database_io_request_full_read returns atomic read request fields.\nfunc turso_sync_database_io_request_full_read(self TursoSyncIoItem) (TursoSyncIoFullReadRequest, error) {\n\tvar r turso_sync_io_full_read_request_t\n\tstatus := c_turso_sync_database_io_request_full_read(self, &r)\n\tif status != int32(TURSO_OK) {\n\t\treturn TursoSyncIoFullReadRequest{}, statusToError(TursoStatusCode(status), \"\")\n\t}\n\treturn TursoSyncIoFullReadRequest{Path: sliceRefToStringCopy(r.path)}, nil\n}\n\n// turso_sync_database_io_request_full_write returns atomic write request fields.\nfunc turso_sync_database_io_request_full_write(self TursoSyncIoItem) (TursoSyncIoFullWriteRequest, error) {\n\tvar r turso_sync_io_full_write_request_t\n\tstatus := c_turso_sync_database_io_request_full_write(self, &r)\n\tif status != int32(TURSO_OK) {\n\t\treturn TursoSyncIoFullWriteRequest{}, statusToError(TursoStatusCode(status), \"\")\n\t}\n\treturn TursoSyncIoFullWriteRequest{\n\t\tPath:    sliceRefToStringCopy(r.path),\n\t\tContent: sliceRefToBytesCopy(r.content),\n\t}, nil\n}\n\n// turso_sync_database_io_poison marks IO request completion with error.\nfunc turso_sync_database_io_poison(self TursoSyncIoItem, errMsg string) error {\n\tvar ref turso_slice_ref_t\n\tvar bytes []byte\n\tif errMsg != \"\" {\n\t\tbytes = []byte(errMsg)\n\t\tref.ptr = uintptr(unsafe.Pointer(&bytes[0]))\n\t\tref.len = uintptr(len(bytes))\n\t}\n\tstatus := c_turso_sync_database_io_poison(self, &ref)\n\t// Keep Go memory alive during call\n\truntime.KeepAlive(bytes)\n\tif status == int32(TURSO_OK) {\n\t\treturn nil\n\t}\n\treturn statusToError(TursoStatusCode(status), \"\")\n}\n\n// turso_sync_database_io_status sets IO request completion status (e.g. HTTP status).\nfunc turso_sync_database_io_status(self TursoSyncIoItem, statusCode int) error {\n\tstatus := c_turso_sync_database_io_status(self, int32(statusCode))\n\tif status == int32(TURSO_OK) {\n\t\treturn nil\n\t}\n\treturn statusToError(TursoStatusCode(status), \"\")\n}\n\n// turso_sync_database_io_push_buffer pushes bytes to the IO completion buffer.\nfunc turso_sync_database_io_push_buffer(self TursoSyncIoItem, buffer []byte) error {\n\tvar ref turso_slice_ref_t\n\tif len(buffer) > 0 {\n\t\tref.ptr = uintptr(unsafe.Pointer(&buffer[0]))\n\t\tref.len = uintptr(len(buffer))\n\t}\n\tstatus := c_turso_sync_database_io_push_buffer(self, &ref)\n\t// Keep Go memory alive during call\n\truntime.KeepAlive(buffer)\n\tif status == int32(TURSO_OK) {\n\t\treturn nil\n\t}\n\treturn statusToError(TursoStatusCode(status), \"\")\n}\n\n// turso_sync_database_io_done sets IO request completion as done.\nfunc turso_sync_database_io_done(self TursoSyncIoItem) error {\n\tstatus := c_turso_sync_database_io_done(self)\n\tif status == int32(TURSO_OK) {\n\t\treturn nil\n\t}\n\treturn statusToError(TursoStatusCode(status), \"\")\n}\n\n// turso_sync_database_deinit deallocates a TursoDatabaseSync.\nfunc turso_sync_database_deinit(self TursoSyncDatabase) {\n\tc_turso_sync_database_deinit(self)\n}\n\n// turso_sync_operation_deinit deallocates a TursoAsyncOperation.\nfunc turso_sync_operation_deinit(self TursoSyncOperation) {\n\tc_turso_sync_operation_deinit(self)\n}\n\n// turso_sync_database_io_item_deinit deallocates a SyncEngineIoQueueItem.\nfunc turso_sync_database_io_item_deinit(self TursoSyncIoItem) {\n\tc_turso_sync_database_io_item_deinit(self)\n}\n\n// turso_sync_changes_deinit deallocates a TursoDatabaseSyncChanges.\nfunc turso_sync_changes_deinit(self TursoSyncChanges) {\n\tc_turso_sync_changes_deinit(self)\n}\n"
  },
  {
    "path": "bindings/go/driver_db.go",
    "content": "package turso\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"database/sql/driver\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"math\"\n\t\"net/url\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tturso_libs \"github.com/tursodatabase/turso-go-platform-libs\"\n)\n\n// define all package level errors here\nvar (\n\tErrTursoStmtClosed = errors.New(\"turso: statement closed\")\n\tErrTursoConnClosed = errors.New(\"turso: connection closed\")\n\tErrTursoRowsClosed = errors.New(\"turso: rows closed\")\n\tErrTursoTxDone     = errors.New(\"turso: transaction done\")\n)\n\n// define all package level structs here\n\ntype tursoDbDriver struct{}\n\ntype tursoDbConnection struct {\n\tdb      TursoDatabase\n\tconn    TursoConnection\n\textraIo func() error\n\n\tmu          sync.Mutex\n\tclosed      bool\n\tbusyTimeout int // current busy timeout in milliseconds\n\t// keep flags for configuration if needed\n\tasync bool\n}\n\ntype tursoDbStatement struct {\n\tconn      *tursoDbConnection\n\tsql       string\n\tnumInputs int\n\tclosed    bool\n}\n\ntype tursoDbRows struct {\n\tconn      *tursoDbConnection\n\tstmt      TursoStatement\n\tcolumns   []string\n\tdecltypes []string\n\n\tclosed bool\n\terr    error\n}\n\ntype tursoDbResult struct {\n\tlastInsertId int64\n\trowsAffected int64\n}\n\ntype tursoDbTx struct {\n\tconn *tursoDbConnection\n\tdone bool\n}\n\n// register driver\nfunc init() {\n\tsql.Register(\"turso\", &tursoDbDriver{})\n}\n\n// Extra constructor for *tursoDbConnection instance which can be used to intergrate with turso Db driver\n// extr_io parameter is the arbitrary IO function which will be executed together with turso_statement_run_io\nfunc NewConnection(conn TursoConnection, extraIo func() error) *tursoDbConnection {\n\treturn &tursoDbConnection{\n\t\tconn:    conn,\n\t\textraIo: extraIo,\n\t}\n}\n\n// Optional helper to run global setup (logger and log level).\nfunc Setup(config TursoConfig) error {\n\tInitLibrary(turso_libs.LoadTursoLibraryConfig{})\n\treturn turso_setup(config)\n}\n\n// Implement sql.Driver methods\nfunc (d *tursoDbDriver) Open(dsn string) (driver.Conn, error) {\n\tInitLibrary(turso_libs.LoadTursoLibraryConfig{})\n\tconfig, err := parseDSN(dsn)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdb, err := turso_database_new(config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := turso_database_open(db); err != nil {\n\t\tturso_database_deinit(db)\n\t\treturn nil, err\n\t}\n\tc, err := turso_database_connect(db)\n\tif err != nil {\n\t\tturso_database_deinit(db)\n\t\treturn nil, err\n\t}\n\t// Apply busy timeout - use default if not explicitly set\n\t// A value of -1 in config means explicitly disabled (no timeout)\n\t// A value of 0 means use the default timeout\n\t// A positive value is used as-is\n\ttimeout := config.BusyTimeout\n\tif timeout == 0 {\n\t\ttimeout = DefaultBusyTimeout // Apply sensible default\n\t} else if timeout < 0 {\n\t\ttimeout = 0 // -1 means explicitly disable\n\t}\n\tif timeout > 0 {\n\t\tturso_connection_set_busy_timeout_ms(c, int64(timeout))\n\t}\n\treturn &tursoDbConnection{\n\t\tdb:          db,\n\t\tconn:        c,\n\t\tbusyTimeout: timeout,\n\t\tasync:       config.AsyncIO,\n\t}, nil\n}\n\n// --- driver.Conn and friends ---\n\n// Ensure tursoDbConnection implements required interfaces.\nvar (\n\t_ driver.Conn               = (*tursoDbConnection)(nil)\n\t_ driver.ConnPrepareContext = (*tursoDbConnection)(nil)\n\t_ driver.ExecerContext      = (*tursoDbConnection)(nil)\n\t_ driver.QueryerContext     = (*tursoDbConnection)(nil)\n\t_ driver.Pinger             = (*tursoDbConnection)(nil)\n\t_ driver.ConnBeginTx        = (*tursoDbConnection)(nil)\n)\n\nfunc (c *tursoDbConnection) Prepare(query string) (driver.Stmt, error) {\n\treturn c.PrepareContext(context.Background(), query)\n}\n\nfunc (c *tursoDbConnection) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {\n\tif err := c.checkOpen(); err != nil {\n\t\treturn nil, err\n\t}\n\t// PREPARE in Prepare - do not delay that\n\tc.mu.Lock()\n\tdefer c.mu.Unlock()\n\tif ctx.Err() != nil {\n\t\treturn nil, ctx.Err()\n\t}\n\tstmt, err := turso_connection_prepare_single(c.conn, query)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// determine number of inputs and then finalize immediately to avoid keeping state\n\tnum := int(turso_statement_parameters_count(stmt))\n\t_ = turso_statement_finalize(stmt)\n\tturso_statement_deinit(stmt)\n\n\treturn &tursoDbStatement{\n\t\tconn:      c,\n\t\tsql:       query,\n\t\tnumInputs: num,\n\t}, nil\n}\n\nfunc (c *tursoDbConnection) Close() error {\n\tc.mu.Lock()\n\tdefer c.mu.Unlock()\n\tif c.closed {\n\t\treturn nil\n\t}\n\t// Close connection and deinit resources\n\tif c.conn != nil {\n\t\t_ = turso_connection_close(c.conn)\n\t\tturso_connection_deinit(c.conn)\n\t\tc.conn = nil\n\t}\n\tif c.db != nil {\n\t\tturso_database_deinit(c.db)\n\t\tc.db = nil\n\t}\n\tc.closed = true\n\treturn nil\n}\n\nfunc (c *tursoDbConnection) Begin() (driver.Tx, error) {\n\treturn c.BeginTx(context.Background(), driver.TxOptions{})\n}\n\nfunc (c *tursoDbConnection) BeginTx(ctx context.Context, _ driver.TxOptions) (driver.Tx, error) {\n\tif err := c.checkOpen(); err != nil {\n\t\treturn nil, err\n\t}\n\t// Use BEGIN (snapshot isolation)\n\t_, err := c.ExecContext(ctx, \"BEGIN\", nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &tursoDbTx{conn: c}, nil\n}\n\nfunc (c *tursoDbConnection) Ping(ctx context.Context) error {\n\tif err := c.checkOpen(); err != nil {\n\t\treturn err\n\t}\n\t// trivial ping: simple select constant\n\t_, err := c.QueryContext(ctx, \"SELECT 1\", nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (c *tursoDbConnection) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {\n\tif err := c.checkOpen(); err != nil {\n\t\treturn nil, err\n\t}\n\t// Multi-statement support for Exec-family\n\tvar totalAffected int64\n\tc.mu.Lock()\n\tdefer c.mu.Unlock()\n\n\toffset := 0\n\tfirst := true\n\tvar lastInsert int64 = 0\n\tfor {\n\t\tif ctx.Err() != nil {\n\t\t\treturn nil, ctx.Err()\n\t\t}\n\t\trest := query[offset:]\n\t\tif strings.TrimSpace(rest) == \"\" {\n\t\t\tbreak\n\t\t}\n\t\tstmt, tail, err := turso_connection_prepare_first(c.conn, rest)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\t// Calculate absolute offset advance\n\t\toffset += tail\n\n\t\t// Bind only for the first statement\n\t\tif first && len(args) > 0 {\n\t\t\tif err := bindArgs(stmt, args); err != nil {\n\t\t\t\t_ = turso_statement_finalize(stmt)\n\t\t\t\tturso_statement_deinit(stmt)\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\t// Execute statement fully\n\t\taffected, err := c.executeFully(ctx, stmt)\n\t\t// finalize and deinit regardless of status\n\t\t_ = turso_statement_finalize(stmt)\n\t\tturso_statement_deinit(stmt)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\t// rows affected is capped at MaxInt64\n\t\tif affected > uint64(math.MaxInt64-totalAffected) {\n\t\t\ttotalAffected = math.MaxInt64\n\t\t} else {\n\t\t\ttotalAffected += int64(affected)\n\t\t}\n\t\tlastInsert = turso_connection_last_insert_rowid(c.conn)\n\t\tfirst = false\n\t\t// continue with the rest of the query string\n\t}\n\treturn &tursoDbResult{\n\t\tlastInsertId: lastInsert,\n\t\trowsAffected: totalAffected,\n\t}, nil\n}\n\nfunc (c *tursoDbConnection) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {\n\tif err := c.checkOpen(); err != nil {\n\t\treturn nil, err\n\t}\n\tc.mu.Lock()\n\tdefer c.mu.Unlock()\n\tif ctx.Err() != nil {\n\t\treturn nil, ctx.Err()\n\t}\n\t// Only single-statement queries supported here\n\tstmt, err := turso_connection_prepare_single(c.conn, query)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(args) > 0 {\n\t\tif err := bindArgs(stmt, args); err != nil {\n\t\t\t_ = turso_statement_finalize(stmt)\n\t\t\tturso_statement_deinit(stmt)\n\t\t\treturn nil, err\n\t\t}\n\t}\n\t// Return rows wrapper; do not step yet, leave cursor before first row\n\treturn &tursoDbRows{\n\t\tconn: c,\n\t\tstmt: stmt,\n\t}, nil\n}\n\nfunc (c *tursoDbConnection) checkOpen() error {\n\tc.mu.Lock()\n\tdefer c.mu.Unlock()\n\tif c.closed || c.conn == nil {\n\t\treturn ErrTursoConnClosed\n\t}\n\treturn nil\n}\n\n// SetBusyTimeout sets the busy timeout for this connection in milliseconds.\n// Pass 0 to disable the busy handler (immediate SQLITE_BUSY on contention).\n// This method is thread-safe.\nfunc (c *tursoDbConnection) SetBusyTimeout(timeoutMs int) error {\n\tif err := c.checkOpen(); err != nil {\n\t\treturn err\n\t}\n\tc.mu.Lock()\n\tdefer c.mu.Unlock()\n\tif timeoutMs < 0 {\n\t\ttimeoutMs = 0\n\t}\n\tturso_connection_set_busy_timeout_ms(c.conn, int64(timeoutMs))\n\tc.busyTimeout = timeoutMs\n\treturn nil\n}\n\n// GetBusyTimeout returns the current busy timeout in milliseconds.\n// Returns 0 if the busy handler is disabled.\nfunc (c *tursoDbConnection) GetBusyTimeout() int {\n\tc.mu.Lock()\n\tdefer c.mu.Unlock()\n\treturn c.busyTimeout\n}\n\n// --- Connector Pattern ---\n\n// ConnectorOption configures a TursoConnector.\ntype ConnectorOption func(*TursoConnector)\n\n// WithBusyTimeout sets the busy timeout in milliseconds.\n// Use 0 to disable the busy handler, -1 to use the default (5000ms).\nfunc WithBusyTimeout(ms int) ConnectorOption {\n\treturn func(c *TursoConnector) {\n\t\tc.busyTimeout = ms\n\t}\n}\n\n// TursoConnector implements driver.Connector for programmatic configuration.\ntype TursoConnector struct {\n\tdsn         string\n\tbusyTimeout int // -1 = use default, 0 = disabled, >0 = custom\n}\n\n// NewConnector creates a new TursoConnector with the given DSN and options.\n// By default, uses the DefaultBusyTimeout (5000ms).\nfunc NewConnector(dsn string, opts ...ConnectorOption) (*TursoConnector, error) {\n\tc := &TursoConnector{\n\t\tdsn:         dsn,\n\t\tbusyTimeout: -1, // -1 means use default\n\t}\n\tfor _, opt := range opts {\n\t\topt(c)\n\t}\n\treturn c, nil\n}\n\n// Connect implements driver.Connector.\nfunc (c *TursoConnector) Connect(ctx context.Context) (driver.Conn, error) {\n\tInitLibrary(turso_libs.LoadTursoLibraryConfig{})\n\tconfig, err := parseDSN(c.dsn)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// Override busy timeout from connector if set\n\tif c.busyTimeout >= 0 {\n\t\t// If connector explicitly sets 0, that means disabled\n\t\t// We use -1 internally to signal \"disabled\" to Open logic\n\t\tif c.busyTimeout == 0 {\n\t\t\tconfig.BusyTimeout = -1 // Will be converted to 0 in Open\n\t\t} else {\n\t\t\tconfig.BusyTimeout = c.busyTimeout\n\t\t}\n\t}\n\t// If busyTimeout is -1 (use default) and DSN didn't set one, leave it as 0\n\t// which will trigger the default in Open()\n\n\tdb, err := turso_database_new(config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := turso_database_open(db); err != nil {\n\t\tturso_database_deinit(db)\n\t\treturn nil, err\n\t}\n\tconn, err := turso_database_connect(db)\n\tif err != nil {\n\t\tturso_database_deinit(db)\n\t\treturn nil, err\n\t}\n\n\t// Apply busy timeout - same logic as Open()\n\ttimeout := config.BusyTimeout\n\tif timeout == 0 {\n\t\ttimeout = DefaultBusyTimeout\n\t} else if timeout < 0 {\n\t\ttimeout = 0\n\t}\n\tif timeout > 0 {\n\t\tturso_connection_set_busy_timeout_ms(conn, int64(timeout))\n\t}\n\n\treturn &tursoDbConnection{\n\t\tdb:          db,\n\t\tconn:        conn,\n\t\tbusyTimeout: timeout,\n\t\tasync:       config.AsyncIO,\n\t}, nil\n}\n\n// Driver implements driver.Connector.\nfunc (c *TursoConnector) Driver() driver.Driver {\n\treturn &tursoDbDriver{}\n}\n\n// Ensure TursoConnector implements driver.Connector\nvar _ driver.Connector = (*TursoConnector)(nil)\n\n// --- driver.Stmt and friends ---\n\n// Ensure tursoDbStatement implements required interfaces.\nvar (\n\t_ driver.Stmt             = (*tursoDbStatement)(nil)\n\t_ driver.StmtExecContext  = (*tursoDbStatement)(nil)\n\t_ driver.StmtQueryContext = (*tursoDbStatement)(nil)\n)\n\nfunc (s *tursoDbStatement) Close() error {\n\ts.closed = true\n\treturn nil\n}\n\nfunc (s *tursoDbStatement) NumInput() int {\n\treturn s.numInputs\n}\n\nfunc (s *tursoDbStatement) Exec(args []driver.Value) (driver.Result, error) {\n\tnamed := make([]driver.NamedValue, len(args))\n\tfor i, v := range args {\n\t\tnamed[i] = driver.NamedValue{Ordinal: i + 1, Value: v}\n\t}\n\treturn s.ExecContext(context.Background(), named)\n}\n\nfunc (s *tursoDbStatement) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {\n\tif s.closed {\n\t\treturn nil, ErrTursoStmtClosed\n\t}\n\treturn s.conn.ExecContext(ctx, s.sql, args)\n}\n\nfunc (s *tursoDbStatement) Query(args []driver.Value) (driver.Rows, error) {\n\tnamed := make([]driver.NamedValue, len(args))\n\tfor i, v := range args {\n\t\tnamed[i] = driver.NamedValue{Ordinal: i + 1, Value: v}\n\t}\n\treturn s.QueryContext(context.Background(), named)\n}\n\nfunc (s *tursoDbStatement) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {\n\tif s.closed {\n\t\treturn nil, ErrTursoStmtClosed\n\t}\n\treturn s.conn.QueryContext(ctx, s.sql, args)\n}\n\n// --- driver.Rows ---\n\n// Ensure tursoDbRows implements the required interface.\nvar _ driver.Rows = (*tursoDbRows)(nil)\n\nfunc (r *tursoDbRows) Columns() []string {\n\tif r.columns != nil {\n\t\treturn r.columns\n\t}\n\tn := int(turso_statement_column_count(r.stmt))\n\tnames := make([]string, n)\n\tdecltypes := make([]string, n)\n\tfor i := 0; i < n; i++ {\n\t\tnames[i] = turso_statement_column_name(r.stmt, i)\n\t\tdecltypes[i] = turso_statement_column_decltype(r.stmt, i)\n\t}\n\tr.columns = names\n\tr.decltypes = decltypes\n\treturn r.columns\n}\n\nfunc (r *tursoDbRows) Close() error {\n\tif r.closed {\n\t\treturn nil\n\t}\n\tr.closed = true\n\t_ = turso_statement_finalize(r.stmt)\n\tturso_statement_deinit(r.stmt)\n\treturn nil\n}\n\nfunc (r *tursoDbRows) Next(dest []driver.Value) error {\n\tif r.closed {\n\t\treturn io.EOF\n\t}\n\t// Ensure decltypes are populated\n\t_ = r.Columns()\n\tfor {\n\t\tstatus, err := turso_statement_step(r.stmt)\n\t\tif err != nil {\n\t\t\tr.err = err\n\t\t\treturn err\n\t\t}\n\t\tswitch status {\n\t\tcase TURSO_ROW:\n\t\t\t// Fill destination\n\t\t\tn := int(turso_statement_column_count(r.stmt))\n\t\t\tif len(dest) != n {\n\t\t\t\treturn fmt.Errorf(\"turso: expected %d dests, got %d\", n, len(dest))\n\t\t\t}\n\t\t\tfor i := 0; i < n; i++ {\n\t\t\t\tkind := turso_statement_row_value_kind(r.stmt, i)\n\t\t\t\tswitch kind {\n\t\t\t\tcase TURSO_TYPE_NULL:\n\t\t\t\t\tdest[i] = nil\n\t\t\t\tcase TURSO_TYPE_INTEGER:\n\t\t\t\t\tdest[i] = turso_statement_row_value_int(r.stmt, i)\n\t\t\t\tcase TURSO_TYPE_REAL:\n\t\t\t\t\tdest[i] = turso_statement_row_value_double(r.stmt, i)\n\t\t\t\tcase TURSO_TYPE_TEXT:\n\t\t\t\t\ttext := turso_statement_row_value_text(r.stmt, i)\n\t\t\t\t\t// Check if column type indicates a time value\n\t\t\t\t\tif i < len(r.decltypes) && isTimeColumn(r.decltypes[i]) {\n\t\t\t\t\t\tif t, err := parseTimeString(text); err == nil {\n\t\t\t\t\t\t\tdest[i] = t\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tdest[i] = text\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdest[i] = text\n\t\t\t\t\t}\n\t\t\t\tcase TURSO_TYPE_BLOB:\n\t\t\t\t\tdest[i] = turso_statement_row_value_bytes(r.stmt, i)\n\t\t\t\tdefault:\n\t\t\t\t\tdest[i] = nil\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\tcase TURSO_DONE:\n\t\t\treturn io.EOF\n\t\tcase TURSO_IO:\n\t\t\t// Run IO iteration\n\t\t\tif r.conn.extraIo != nil {\n\t\t\t\tif err := r.conn.extraIo(); err != nil {\n\t\t\t\t\tr.err = err\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t\tif err := turso_statement_run_io(r.stmt); err != nil {\n\t\t\t\tr.err = err\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tcontinue\n\t\tcase TURSO_OK:\n\t\t\t// Continue stepping\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn ErrTursoGeneric\n\t\t}\n\t}\n}\n\n// --- driver.Result ---\n\nvar _ driver.Result = (*tursoDbResult)(nil)\n\nfunc (r *tursoDbResult) LastInsertId() (int64, error) {\n\treturn r.lastInsertId, nil\n}\n\nfunc (r *tursoDbResult) RowsAffected() (int64, error) {\n\treturn r.rowsAffected, nil\n}\n\n// --- driver.Tx ---\n\nvar _ driver.Tx = (*tursoDbTx)(nil)\n\nfunc (tx *tursoDbTx) Commit() error {\n\tif tx.done {\n\t\treturn ErrTursoTxDone\n\t}\n\t_, err := tx.conn.ExecContext(context.Background(), \"COMMIT\", nil)\n\ttx.done = true\n\treturn err\n}\n\nfunc (tx *tursoDbTx) Rollback() error {\n\tif tx.done {\n\t\treturn ErrTursoTxDone\n\t}\n\t_, err := tx.conn.ExecContext(context.Background(), \"ROLLBACK\", nil)\n\ttx.done = true\n\treturn err\n}\n\n// Helpers\n\n// parseDSN supports format: <path>[?experimental=<string>&async=0|1&vfs=<string>&encryption_cipher=<string>&encryption_hexkey=<string>&_busy_timeout=<int>]\nfunc parseDSN(dsn string) (TursoDatabaseConfig, error) {\n\tconfig := TursoDatabaseConfig{Path: dsn}\n\tqMark := strings.IndexByte(dsn, '?')\n\tif qMark >= 0 {\n\t\tconfig.Path = dsn[:qMark]\n\t\trawQuery := dsn[qMark+1:]\n\t\tvals, err := url.ParseQuery(rawQuery)\n\t\tif err != nil {\n\t\t\treturn TursoDatabaseConfig{}, err\n\t\t}\n\t\tif v := vals.Get(\"experimental\"); v != \"\" {\n\t\t\tconfig.ExperimentalFeatures = v\n\t\t}\n\t\tif v := vals.Get(\"async\"); v != \"\" {\n\t\t\tconfig.AsyncIO = v == \"1\" || strings.EqualFold(v, \"true\") || strings.EqualFold(v, \"yes\")\n\t\t}\n\t\tif v := vals.Get(\"vfs\"); v != \"\" {\n\t\t\tconfig.Vfs = v\n\t\t}\n\t\tif v := vals.Get(\"encryption_cipher\"); v != \"\" {\n\t\t\tconfig.Encryption.Cipher = v\n\t\t}\n\t\tif v := vals.Get(\"encryption_hexkey\"); v != \"\" {\n\t\t\tconfig.Encryption.Hexkey = v\n\t\t}\n\t\tif v := vals.Get(\"_busy_timeout\"); v != \"\" {\n\t\t\tvar timeout int\n\t\t\tif _, err := fmt.Sscanf(v, \"%d\", &timeout); err == nil {\n\t\t\t\tconfig.BusyTimeout = timeout\n\t\t\t}\n\t\t}\n\t}\n\treturn config, nil\n}\n\nfunc (c *tursoDbConnection) executeFully(ctx context.Context, stmt TursoStatement) (uint64, error) {\n\tvar latest uint64\n\tfor {\n\t\tif ctx != nil && ctx.Err() != nil {\n\t\t\treturn 0, ctx.Err()\n\t\t}\n\t\tstatus, changes, err := turso_statement_execute(stmt)\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t\tlatest = changes\n\t\tswitch status {\n\t\tcase TURSO_DONE:\n\t\t\treturn latest, nil\n\t\tcase TURSO_IO:\n\t\t\t// perform one IO iteration and retry\n\t\t\tif c.extraIo != nil {\n\t\t\t\tif err := c.extraIo(); err != nil {\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\t\t\t}\n\t\t\tif err := turso_statement_run_io(stmt); err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\tcontinue\n\t\tcase TURSO_ROW:\n\t\t\t// Exhaust rows until DONE\n\t\t\tfor {\n\t\t\t\tif ctx != nil && ctx.Err() != nil {\n\t\t\t\t\treturn 0, ctx.Err()\n\t\t\t\t}\n\t\t\t\tst, err := turso_statement_step(stmt)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\t\t\t\tif st == TURSO_ROW {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif st == TURSO_DONE {\n\t\t\t\t\treturn latest, nil\n\t\t\t\t}\n\t\t\t\tif st == TURSO_IO {\n\t\t\t\t\tif c.extraIo != nil {\n\t\t\t\t\t\tif err := c.extraIo(); err != nil {\n\t\t\t\t\t\t\treturn 0, err\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif err := turso_statement_run_io(stmt); err != nil {\n\t\t\t\t\t\treturn 0, err\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\t// Continue on OK or others\n\t\t\t}\n\t\tcase TURSO_OK:\n\t\t\t// keep going; step to progress\n\t\t\tst, err := turso_statement_step(stmt)\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\tif st == TURSO_DONE {\n\t\t\t\treturn latest, nil\n\t\t\t}\n\t\t\tif st == TURSO_IO {\n\t\t\t\tif c.extraIo != nil {\n\t\t\t\t\tif err := c.extraIo(); err != nil {\n\t\t\t\t\t\treturn 0, err\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif err := turso_statement_run_io(stmt); err != nil {\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\t\t\t}\n\t\t\t// and loop again\n\t\tdefault:\n\t\t\treturn 0, statusToError(status, \"\")\n\t\t}\n\t}\n}\n\n// bindArgs binds ordered and named values to a statement.\n// Named values are resolved via turso_statement_named_position, otherwise ordinal positions are used (1-based).\nfunc bindArgs(stmt TursoStatement, args []driver.NamedValue) error {\n\t// Validate number of inputs if no named args present\n\tif len(args) > 0 {\n\t\thasNamed := false\n\t\tfor _, nv := range args {\n\t\t\tif nv.Name != \"\" {\n\t\t\t\thasNamed = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !hasNamed {\n\t\t\tparamCount := int(turso_statement_parameters_count(stmt))\n\t\t\tif paramCount >= 0 && len(args) != paramCount {\n\t\t\t\treturn fmt.Errorf(\"turso: got %d args, want %d\", len(args), paramCount)\n\t\t\t}\n\t\t}\n\t}\n\tfor idx, nv := range args {\n\t\tpos := idx + 1\n\t\tif nv.Name != \"\" {\n\t\t\tnp := int(turso_statement_named_position(stmt, nv.Name))\n\t\t\tif np <= 0 {\n\t\t\t\treturn fmt.Errorf(\"turso: unknown named parameter %q\", nv.Name)\n\t\t\t}\n\t\t\tpos = np\n\t\t} else if nv.Ordinal > 0 {\n\t\t\tpos = nv.Ordinal\n\t\t}\n\t\tif err := bindOne(stmt, pos, nv.Value); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc bindOne(stmt TursoStatement, position int, v any) error {\n\tif v == nil {\n\t\treturn turso_statement_bind_positional_null(stmt, position)\n\t}\n\tswitch x := v.(type) {\n\tcase int:\n\t\treturn turso_statement_bind_positional_int(stmt, position, int64(x))\n\tcase int8:\n\t\treturn turso_statement_bind_positional_int(stmt, position, int64(x))\n\tcase int16:\n\t\treturn turso_statement_bind_positional_int(stmt, position, int64(x))\n\tcase int32:\n\t\treturn turso_statement_bind_positional_int(stmt, position, int64(x))\n\tcase int64:\n\t\treturn turso_statement_bind_positional_int(stmt, position, x)\n\tcase uint:\n\t\treturn turso_statement_bind_positional_int(stmt, position, int64(x))\n\tcase uint8:\n\t\treturn turso_statement_bind_positional_int(stmt, position, int64(x))\n\tcase uint16:\n\t\treturn turso_statement_bind_positional_int(stmt, position, int64(x))\n\tcase uint32:\n\t\treturn turso_statement_bind_positional_int(stmt, position, int64(x))\n\tcase uint64:\n\t\t// cap at MaxInt64 to avoid overflow\n\t\ti := int64(0)\n\t\tif x > uint64(math.MaxInt64) {\n\t\t\ti = math.MaxInt64\n\t\t} else {\n\t\t\ti = int64(x)\n\t\t}\n\t\treturn turso_statement_bind_positional_int(stmt, position, i)\n\tcase float32:\n\t\treturn turso_statement_bind_positional_double(stmt, position, float64(x))\n\tcase float64:\n\t\treturn turso_statement_bind_positional_double(stmt, position, x)\n\tcase bool:\n\t\tif x {\n\t\t\treturn turso_statement_bind_positional_int(stmt, position, 1)\n\t\t}\n\t\treturn turso_statement_bind_positional_int(stmt, position, 0)\n\tcase []byte:\n\t\treturn turso_statement_bind_positional_blob(stmt, position, x)\n\tcase string:\n\t\treturn turso_statement_bind_positional_text(stmt, position, x)\n\tcase time.Time:\n\t\t// encode as RFC3339Nano string\n\t\treturn turso_statement_bind_positional_text(stmt, position, x.Format(time.RFC3339Nano))\n\tdefault:\n\t\t// Fallback to fmt to string\n\t\treturn turso_statement_bind_positional_text(stmt, position, fmt.Sprint(v))\n\t}\n}\n\n// isTimeColumn checks if the column declared type indicates a time/date column.\n// This matches the behavior of github.com/mattn/go-sqlite3.\nfunc isTimeColumn(decltype string) bool {\n\tif decltype == \"\" {\n\t\treturn false\n\t}\n\t// Case-insensitive exact match for TIMESTAMP, DATETIME, DATE\n\t// Matches go-sqlite3 behavior: https://github.com/mattn/go-sqlite3/blob/master/sqlite3_type.go\n\tupper := strings.ToUpper(decltype)\n\treturn upper == \"TIMESTAMP\" || upper == \"DATETIME\" || upper == \"DATE\"\n}\n\n// SQLiteTimestampFormats are the timestamp formats supported by go-sqlite3.\n// https://github.com/mattn/go-sqlite3/blob/master/sqlite3.go\nvar SQLiteTimestampFormats = []string{\n\t\"2006-01-02 15:04:05.999999999-07:00\",\n\t\"2006-01-02T15:04:05.999999999-07:00\",\n\t\"2006-01-02 15:04:05.999999999\",\n\t\"2006-01-02T15:04:05.999999999\",\n\t\"2006-01-02 15:04:05\",\n\t\"2006-01-02T15:04:05\",\n\t\"2006-01-02 15:04\",\n\t\"2006-01-02T15:04\",\n\t\"2006-01-02\",\n}\n\n// parseTimeString attempts to parse a string as a time.Time value.\n// This matches the behavior of github.com/mattn/go-sqlite3.\nfunc parseTimeString(s string) (time.Time, error) {\n\t// Strip trailing \"Z\" suffix before parsing (go-sqlite3 behavior)\n\ts = strings.TrimSuffix(s, \"Z\")\n\tfor _, format := range SQLiteTimestampFormats {\n\t\tif t, err := time.ParseInLocation(format, s, time.UTC); err == nil {\n\t\t\treturn t, nil\n\t\t}\n\t}\n\treturn time.Time{}, fmt.Errorf(\"cannot parse %q as time\", s)\n}\n"
  },
  {
    "path": "bindings/go/driver_db_test.go",
    "content": "package turso\n\nimport (\n\t\"bytes\"\n\t\"database/sql\"\n\t\"fmt\"\n\t\"log\"\n\t\"math\"\n\t\"os\"\n\t\"path\"\n\t\"runtime\"\n\t\"slices\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n\tturso_libs \"github.com/tursodatabase/turso-go-platform-libs\"\n)\n\nvar (\n\tconn *sql.DB\n)\n\nfunc openMem(t *testing.T) *sql.DB {\n\tt.Helper()\n\tdb, err := sql.Open(\"turso\", \":memory:\")\n\tif err != nil {\n\t\tt.Fatalf(\"open: %v\", err)\n\t}\n\tt.Cleanup(func() { _ = db.Close() })\n\treturn db\n}\n\nfunc TestMain(m *testing.M) {\n\tInitLibrary(turso_libs.LoadTursoLibraryConfig{LoadStrategy: \"mixed\"})\n\tvar err error\n\tconn, err = sql.Open(\"turso\", \":memory:\")\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to create database: %v\", err)\n\t}\n\terr = conn.Ping()\n\tif err != nil {\n\t\tlog.Fatalf(\"Error pinging database: %v\", err)\n\t}\n\tdefer conn.Close()\n\terr = createTable(conn)\n\tif err != nil {\n\t\tlog.Fatalf(\"Error creating table: %v\", err)\n\t}\n\tm.Run()\n}\n\nfunc TestEncryption(t *testing.T) {\n\thexkey := \"b1bbfda4f589dc9daaf004fe21111e00dc00c98237102f5c7002a5669fc76327\"\n\twrongKey := \"aaaaaaa4f589dc9daaf004fe21111e00dc00c98237102f5c7002a5669fc76327\"\n\n\tt.Run(\"encryption=disabled\", func(t *testing.T) {\n\t\ttmp := t.TempDir()\n\t\tdbPath := path.Join(tmp, \"local.db\")\n\t\tconn, err := sql.Open(\"turso\", dbPath)\n\t\trequire.Nil(t, err)\n\t\trequire.Nil(t, conn.Ping())\n\t\t_, err = conn.Exec(\"CREATE TABLE t(x)\")\n\t\trequire.Nil(t, err)\n\t\t_, err = conn.Exec(\"INSERT INTO t SELECT 'secret' FROM generate_series(1, 1024)\")\n\t\trequire.Nil(t, err)\n\t\t_, err = conn.Exec(\"PRAGMA wal_checkpoint(TRUNCATE)\")\n\t\trequire.Nil(t, err)\n\t\tcontent, err := os.ReadFile(dbPath)\n\t\trequire.Nil(t, err)\n\t\trequire.True(t, bytes.Contains(content, []byte(\"secret\")))\n\t})\n\n\tt.Run(\"encryption=enabled\", func(t *testing.T) {\n\t\ttmp := t.TempDir()\n\t\tdbPath := path.Join(tmp, \"local.db\")\n\t\tdsn := fmt.Sprintf(\"%v?experimental=encryption&encryption_cipher=aegis256&encryption_hexkey=%s\", dbPath, hexkey)\n\t\tconn, err := sql.Open(\"turso\", dsn)\n\t\trequire.Nil(t, err)\n\t\trequire.Nil(t, conn.Ping())\n\t\t_, err = conn.Exec(\"CREATE TABLE t(x)\")\n\t\trequire.Nil(t, err)\n\t\t_, err = conn.Exec(\"INSERT INTO t SELECT 'secret' FROM generate_series(1, 1024)\")\n\t\trequire.Nil(t, err)\n\t\t_, err = conn.Exec(\"PRAGMA wal_checkpoint(TRUNCATE)\")\n\t\trequire.Nil(t, err)\n\t\tcontent, err := os.ReadFile(dbPath)\n\t\trequire.Nil(t, err)\n\t\trequire.False(t, bytes.Contains(content, []byte(\"secret\")))\n\t\tconn.Close()\n\t})\n\n\tt.Run(\"encryption=full_test\", func(t *testing.T) {\n\t\ttmp := t.TempDir()\n\t\tdbPath := path.Join(tmp, \"encrypted.db\")\n\n\t\tdsn := fmt.Sprintf(\"%v?experimental=encryption&encryption_cipher=aegis256&encryption_hexkey=%s\", dbPath, hexkey)\n\t\tconn, err := sql.Open(\"turso\", dsn)\n\t\trequire.Nil(t, err)\n\t\trequire.Nil(t, conn.Ping())\n\t\t_, err = conn.Exec(\"CREATE TABLE t(x)\")\n\t\trequire.Nil(t, err)\n\t\t_, err = conn.Exec(\"INSERT INTO t SELECT 'secret' FROM generate_series(1, 1024)\")\n\t\trequire.Nil(t, err)\n\t\t_, err = conn.Exec(\"PRAGMA wal_checkpoint(TRUNCATE)\")\n\t\trequire.Nil(t, err)\n\t\tconn.Close()\n\n\t\tcontent, err := os.ReadFile(dbPath)\n\t\trequire.Nil(t, err)\n\t\trequire.Greater(t, len(content), 16*1024)\n\t\trequire.False(t, bytes.Contains(content, []byte(\"secret\")))\n\n\t\t// verify we can re-open with the same key\n\t\tconn2, err := sql.Open(\"turso\", dsn)\n\t\trequire.Nil(t, err)\n\t\tvar count int\n\t\terr = conn2.QueryRow(\"SELECT count(*) FROM t\").Scan(&count)\n\t\trequire.Nil(t, err)\n\t\trequire.Equal(t, 1024, count)\n\t\tconn2.Close()\n\n\t\t// verify opening with wrong key fails\n\t\twrongDsn := fmt.Sprintf(\"%v?experimental=encryption&encryption_cipher=aegis256&encryption_hexkey=%s\", dbPath, wrongKey)\n\t\tconn3, err := sql.Open(\"turso\", wrongDsn)\n\t\trequire.Nil(t, err) // open succeeds but query should fail\n\t\t_, err = conn3.Exec(\"SELECT * FROM t\")\n\t\trequire.NotNil(t, err)\n\t\tconn3.Close()\n\n\t\t// verify opening without encryption fails\n\t\tconn4, err := sql.Open(\"turso\", dbPath)\n\t\trequire.Nil(t, err) // Open succeeds but query should fail\n\t\t_, err = conn4.Exec(\"SELECT * FROM t\")\n\t\trequire.NotNil(t, err)\n\t\tconn4.Close()\n\t})\n}\n\nfunc TestInsertData(t *testing.T) {\n\terr := insertData(conn)\n\tif err != nil {\n\t\tt.Fatalf(\"Error inserting data: %v\", err)\n\t}\n}\n\nfunc TestQuery(t *testing.T) {\n\tquery := \"SELECT * FROM test;\"\n\tstmt, err := conn.Prepare(query)\n\tif err != nil {\n\t\tt.Fatalf(\"Error preparing query: %v\", err)\n\t}\n\tdefer stmt.Close()\n\n\trows, err := stmt.Query()\n\tif err != nil {\n\t\tt.Fatalf(\"Error executing query: %v\", err)\n\t}\n\tdefer rows.Close()\n\n\texpectedCols := []string{\"foo\", \"bar\", \"baz\"}\n\tcols, err := rows.Columns()\n\tif err != nil {\n\t\tt.Fatalf(\"Error getting columns: %v\", err)\n\t}\n\tif len(cols) != len(expectedCols) {\n\t\tt.Fatalf(\"Expected %d columns, got %d\", len(expectedCols), len(cols))\n\t}\n\tfor i, col := range cols {\n\t\tif col != expectedCols[i] {\n\t\t\tt.Errorf(\"Expected column %d to be %s, got %s\", i, expectedCols[i], col)\n\t\t}\n\t}\n\ti := 1\n\tfor rows.Next() {\n\t\tvar a int\n\t\tvar b string\n\t\tvar c []byte\n\t\terr = rows.Scan(&a, &b, &c)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Error scanning row: %v\", err)\n\t\t}\n\t\tif a != i || b != rowsMap[i] || !slicesAreEq(c, []byte(rowsMap[i])) {\n\t\t\tt.Fatalf(\"Expected %d, %s, %s, got %d, %s, %s\", i, rowsMap[i], rowsMap[i], a, b, string(c))\n\t\t}\n\t\tfmt.Println(\"RESULTS: \", a, b, string(c))\n\t\ti++\n\t}\n\n\tif err = rows.Err(); err != nil {\n\t\tt.Fatalf(\"Row iteration error: %v\", err)\n\t}\n}\n\nfunc TestFunctions(t *testing.T) {\n\tinsert := \"INSERT INTO test (foo, bar, baz) VALUES (?, ?, zeroblob(?));\"\n\tstmt, err := conn.Prepare(insert)\n\tif err != nil {\n\t\tt.Fatalf(\"Error preparing statement: %v\", err)\n\t}\n\t_, err = stmt.Exec(60, \"TestFunction\", 400)\n\tif err != nil {\n\t\tt.Fatalf(\"Error executing statement with arguments: %v\", err)\n\t}\n\tstmt.Close()\n\tstmt, err = conn.Prepare(\"SELECT baz FROM test where foo = ?\")\n\tif err != nil {\n\t\tt.Fatalf(\"Error preparing select stmt: %v\", err)\n\t}\n\tdefer stmt.Close()\n\trows, err := stmt.Query(60)\n\tif err != nil {\n\t\tt.Fatalf(\"Error executing select stmt: %v\", err)\n\t}\n\tdefer rows.Close()\n\tfor rows.Next() {\n\t\tvar b []byte\n\t\terr = rows.Scan(&b)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Error scanning row: %v\", err)\n\t\t}\n\t\tif len(b) != 400 {\n\t\t\tt.Fatalf(\"Expected 100 bytes, got %d\", len(b))\n\t\t}\n\t}\n\tsql := \"SELECT uuid4_str();\"\n\tstmt, err = conn.Prepare(sql)\n\tif err != nil {\n\t\tt.Fatalf(\"Error preparing statement: %v\", err)\n\t}\n\tdefer stmt.Close()\n\trows, err = stmt.Query()\n\tif err != nil {\n\t\tt.Fatalf(\"Error executing query: %v\", err)\n\t}\n\tdefer rows.Close()\n\tvar i int\n\tfor rows.Next() {\n\t\tvar b string\n\t\terr = rows.Scan(&b)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Error scanning row: %v\", err)\n\t\t}\n\t\tif len(b) != 36 {\n\t\t\tt.Fatalf(\"Expected 36 bytes, got %d\", len(b))\n\t\t}\n\t\ti++\n\t\tfmt.Printf(\"uuid: %s\\n\", b)\n\t}\n\tif i != 1 {\n\t\tt.Fatalf(\"Expected 1 row, got %d\", i)\n\t}\n\tfmt.Println(\"zeroblob + uuid functions passed\")\n}\n\nfunc TestDuplicateConnection(t *testing.T) {\n\tnewConn := openMem(t)\n\terr := createTable(newConn)\n\tif err != nil {\n\t\tt.Fatalf(\"Error creating table: %v\", err)\n\t}\n\terr = insertData(newConn)\n\tif err != nil {\n\t\tt.Fatalf(\"Error inserting data: %v\", err)\n\t}\n\tquery := \"SELECT * FROM test;\"\n\trows, err := newConn.Query(query)\n\tif err != nil {\n\t\tt.Fatalf(\"Error executing query: %v\", err)\n\t}\n\tdefer rows.Close()\n\tfor rows.Next() {\n\t\tvar a int\n\t\tvar b string\n\t\tvar c []byte\n\t\terr = rows.Scan(&a, &b, &c)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Error scanning row: %v\", err)\n\t\t}\n\t\tfmt.Println(\"RESULTS: \", a, b, string(c))\n\t}\n}\n\nfunc TestDuplicateConnection2(t *testing.T) {\n\tnewConn := openMem(t)\n\tsql := \"CREATE TABLE test (foo INTEGER, bar INTEGER, baz BLOB);\"\n\tnewConn.Exec(sql)\n\tsql = \"INSERT INTO test (foo, bar, baz) VALUES (?, ?, uuid4());\"\n\tstmt, err := newConn.Prepare(sql)\n\trequire.Nil(t, err)\n\tstmt.Exec(242345, 2342434)\n\tdefer stmt.Close()\n\tquery := \"SELECT * FROM test;\"\n\trows, err := newConn.Query(query)\n\tif err != nil {\n\t\tt.Fatalf(\"Error executing query: %v\", err)\n\t}\n\tdefer rows.Close()\n\tfor rows.Next() {\n\t\tvar a int\n\t\tvar b int\n\t\tvar c []byte\n\t\terr = rows.Scan(&a, &b, &c)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Error scanning row: %v\", err)\n\t\t}\n\t\tfmt.Println(\"RESULTS: \", a, b, string(c))\n\t\tif len(c) != 16 {\n\t\t\tt.Fatalf(\"Expected 16 bytes, got %d\", len(c))\n\t\t}\n\t}\n}\n\nfunc TestConnectionError(t *testing.T) {\n\tnewConn := openMem(t)\n\tsql := \"CREATE TABLE test (foo INTEGER, bar INTEGER, baz BLOB);\"\n\tnewConn.Exec(sql)\n\tsql = \"INSERT INTO test (foo, bar, baz) VALUES (?, ?, notafunction(?));\"\n\t_, err := newConn.Prepare(sql)\n\tif err == nil {\n\t\tt.Fatalf(\"Expected error, got nil\")\n\t}\n\texpectedErr := \"turso: error: Parse error: unknown function notafunction\"\n\tif err.Error() != expectedErr {\n\t\tt.Fatalf(\"Error test failed, expected: %s, found: %v\", expectedErr, err)\n\t}\n\tfmt.Println(\"Connection error test passed\")\n}\n\nfunc TestStatementError(t *testing.T) {\n\tnewConn := openMem(t)\n\tsql := \"CREATE TABLE test (foo INTEGER, bar INTEGER, baz BLOB);\"\n\tnewConn.Exec(sql)\n\tsql = \"INSERT INTO test (foo, bar, baz) VALUES (?, ?, ?);\"\n\tstmt, err := newConn.Prepare(sql)\n\tif err != nil {\n\t\tt.Fatalf(\"Error preparing statement: %v\", err)\n\t}\n\t_, err = stmt.Exec(1, 2)\n\tif err == nil {\n\t\tt.Fatalf(\"Expected error, got nil\")\n\t}\n\tif err.Error() != \"sql: expected 3 arguments, got 2\" {\n\t\tt.Fatalf(\"Unexpected : %v\\n\", err)\n\t}\n\tfmt.Println(\"Statement error test passed\")\n}\n\nfunc TestDriverRowsErrorMessages(t *testing.T) {\n\tdb := openMem(t)\n\t_, err := db.Exec(\"CREATE TABLE test (id INTEGER, name TEXT)\")\n\tif err != nil {\n\t\tt.Fatalf(\"failed to create table: %v\", err)\n\t}\n\n\t_, err = db.Exec(\"INSERT INTO test (id, name) VALUES (?, ?)\", 1, \"Alice\")\n\tif err != nil {\n\t\tt.Fatalf(\"failed to insert row: %v\", err)\n\t}\n\n\trows, err := db.Query(\"SELECT id, name FROM test\")\n\tif err != nil {\n\t\tt.Fatalf(\"failed to query table: %v\", err)\n\t}\n\n\tif !rows.Next() {\n\t\tt.Fatalf(\"expected at least one row\")\n\t}\n\tvar id int\n\tvar name string\n\terr = rows.Scan(&name, &id)\n\tif err == nil {\n\t\tt.Fatalf(\"expected error scanning wrong type: %v\", err)\n\t}\n\tt.Log(\"Rows error behavior test passed\")\n}\n\nfunc TestTransaction(t *testing.T) {\n\t// Open database connection\n\tdb := openMem(t)\n\t// Create a test table\n\t_, err := db.Exec(\"CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)\")\n\tif err != nil {\n\t\tt.Fatalf(\"Error creating table: %v\", err)\n\t}\n\n\t// Insert initial data\n\t_, err = db.Exec(\"INSERT INTO test (id, name) VALUES (1, 'Initial')\")\n\tif err != nil {\n\t\tt.Fatalf(\"Error inserting initial data: %v\", err)\n\t}\n\n\t// Begin a transaction\n\ttx, err := db.Begin()\n\tif err != nil {\n\t\tt.Fatalf(\"Error starting transaction: %v\", err)\n\t}\n\n\t// Insert data within the transaction\n\t_, err = tx.Exec(\"INSERT INTO test (id, name) VALUES (2, 'Transaction')\")\n\tif err != nil {\n\t\tt.Fatalf(\"Error inserting data in transaction: %v\", err)\n\t}\n\n\t// Commit the transaction\n\terr = tx.Commit()\n\tif err != nil {\n\t\tt.Fatalf(\"Error committing transaction: %v\", err)\n\t}\n\n\t// Verify both rows are visible after commit\n\trows, err := db.Query(\"SELECT id, name FROM test ORDER BY id\")\n\tif err != nil {\n\t\tt.Fatalf(\"Error querying data after commit: %v\", err)\n\t}\n\tdefer rows.Close()\n\n\texpected := []struct {\n\t\tid   int\n\t\tname string\n\t}{\n\t\t{1, \"Initial\"},\n\t\t{2, \"Transaction\"},\n\t}\n\n\ti := 0\n\tfor rows.Next() {\n\t\tvar id int\n\t\tvar name string\n\t\tif err := rows.Scan(&id, &name); err != nil {\n\t\t\tt.Fatalf(\"Error scanning row: %v\", err)\n\t\t}\n\n\t\tif id != expected[i].id || name != expected[i].name {\n\t\t\tt.Errorf(\"Row %d: expected (%d, %s), got (%d, %s)\",\n\t\t\t\ti, expected[i].id, expected[i].name, id, name)\n\t\t}\n\t\ti++\n\t}\n\n\tif i != 2 {\n\t\tt.Fatalf(\"Expected 2 rows, got %d\", i)\n\t}\n\n\tt.Log(\"Transaction test passed\")\n}\n\nfunc TestVectorOperations(t *testing.T) {\n\tdb := openMem(t)\n\t// Test creating table with vector columns\n\t_, err := db.Exec(`CREATE TABLE vector_test (id INTEGER PRIMARY KEY, embedding F32_BLOB(64))`)\n\tif err != nil {\n\t\tt.Fatalf(\"Error creating vector table: %v\", err)\n\t}\n\n\t// Test vector insertion\n\t_, err = db.Exec(`INSERT INTO vector_test VALUES (1, vector('[0.1, 0.2, 0.3, 0.4, 0.5]'))`)\n\tif err != nil {\n\t\tt.Fatalf(\"Error inserting vector: %v\", err)\n\t}\n\n\t// Test vector similarity calculation\n\tvar similarity float64\n\terr = db.QueryRow(`SELECT vector_distance_cos(embedding, vector('[0.2, 0.3, 0.4, 0.5, 0.6]')) FROM vector_test WHERE id = 1`).Scan(&similarity)\n\tif err != nil {\n\t\tt.Fatalf(\"Error calculating vector similarity: %v\", err)\n\t}\n\tif similarity <= 0 || similarity > 1 {\n\t\tt.Fatalf(\"Expected similarity between 0 and 1, got %f\", similarity)\n\t}\n\n\t// Test vector extraction\n\tvar extracted string\n\terr = db.QueryRow(`SELECT vector_extract(embedding) FROM vector_test WHERE id = 1`).Scan(&extracted)\n\tif err != nil {\n\t\tt.Fatalf(\"Error extracting vector: %v\", err)\n\t}\n\tfmt.Printf(\"Extracted vector: %s\\n\", extracted)\n}\n\nfunc TestSQLFeatures(t *testing.T) {\n\tdb := openMem(t)\n\n\t// Create test tables\n\t_, err := db.Exec(`\n        CREATE TABLE customers (\n            id INTEGER PRIMARY KEY,\n            name TEXT,\n            age INTEGER\n        )`)\n\tif err != nil {\n\t\tt.Fatalf(\"Error creating customers table: %v\", err)\n\t}\n\n\t_, err = db.Exec(`\n        CREATE TABLE orders (\n            id INTEGER PRIMARY KEY,\n            customer_id INTEGER,\n            amount REAL,\n            date TEXT\n        )`)\n\tif err != nil {\n\t\tt.Fatalf(\"Error creating orders table: %v\", err)\n\t}\n\n\t// Insert test data\n\t_, err = db.Exec(`\n        INSERT INTO customers VALUES\n            (1, 'Alice', 30),\n            (2, 'Bob', 25),\n            (3, 'Charlie', 40)`)\n\tif err != nil {\n\t\tt.Fatalf(\"Error inserting customers: %v\", err)\n\t}\n\n\t_, err = db.Exec(`\n        INSERT INTO orders VALUES\n            (1, 1, 100.50, '2024-01-01'),\n            (2, 1, 200.75, '2024-02-01'),\n            (3, 2, 50.25, '2024-01-15'),\n            (4, 3, 300.00, '2024-02-10')`)\n\tif err != nil {\n\t\tt.Fatalf(\"Error inserting orders: %v\", err)\n\t}\n\n\t// Test JOIN\n\trows, err := db.Query(`\n        SELECT c.name, o.amount\n        FROM customers c\n        INNER JOIN orders o ON c.id = o.customer_id\n        ORDER BY o.amount DESC`)\n\tif err != nil {\n\t\tt.Fatalf(\"Error executing JOIN: %v\", err)\n\t}\n\tdefer rows.Close()\n\n\t// Check JOIN results\n\texpectedResults := []struct {\n\t\tname   string\n\t\tamount float64\n\t}{\n\t\t{\"Charlie\", 300.00},\n\t\t{\"Alice\", 200.75},\n\t\t{\"Alice\", 100.50},\n\t\t{\"Bob\", 50.25},\n\t}\n\n\ti := 0\n\tfor rows.Next() {\n\t\tvar name string\n\t\tvar amount float64\n\t\tif err := rows.Scan(&name, &amount); err != nil {\n\t\t\tt.Fatalf(\"Error scanning JOIN result: %v\", err)\n\t\t}\n\t\tif i >= len(expectedResults) {\n\t\t\tt.Fatalf(\"Too many rows returned from JOIN\")\n\t\t}\n\t\tif name != expectedResults[i].name || amount != expectedResults[i].amount {\n\t\t\tt.Fatalf(\"Row %d: expected (%s, %.2f), got (%s, %.2f)\",\n\t\t\t\ti, expectedResults[i].name, expectedResults[i].amount, name, amount)\n\t\t}\n\t\ti++\n\t}\n\n\t// Test GROUP BY with aggregation\n\tvar count int\n\tvar total float64\n\terr = db.QueryRow(`\n        SELECT COUNT(*), SUM(amount)\n        FROM orders\n        WHERE customer_id = 1\n        GROUP BY customer_id`).Scan(&count, &total)\n\tif err != nil {\n\t\tt.Fatalf(\"Error executing GROUP BY: %v\", err)\n\t}\n\tif count != 2 || total != 301.25 {\n\t\tt.Fatalf(\"GROUP BY gave wrong results: count=%d, total=%.2f\", count, total)\n\t}\n}\n\nfunc TestDateTimeFunctions(t *testing.T) {\n\tdb := openMem(t)\n\t// Test date()\n\tvar dateStr string\n\terr := db.QueryRow(`SELECT date('now')`).Scan(&dateStr)\n\tif err != nil {\n\t\tt.Fatalf(\"Error with date() function: %v\", err)\n\t}\n\tfmt.Printf(\"Current date: %s\\n\", dateStr)\n\n\t// Test date arithmetic\n\terr = db.QueryRow(`SELECT date('2024-01-01', '+1 month')`).Scan(&dateStr)\n\tif err != nil {\n\t\tt.Fatalf(\"Error with date arithmetic: %v\", err)\n\t}\n\tif dateStr != \"2024-02-01\" {\n\t\tt.Fatalf(\"Expected '2024-02-01', got '%s'\", dateStr)\n\t}\n\n\t// Test strftime\n\tvar formatted string\n\terr = db.QueryRow(`SELECT strftime('%Y-%m-%d', '2024-01-01')`).Scan(&formatted)\n\tif err != nil {\n\t\tt.Fatalf(\"Error with strftime function: %v\", err)\n\t}\n\tif formatted != \"2024-01-01\" {\n\t\tt.Fatalf(\"Expected '2024-01-01', got '%s'\", formatted)\n\t}\n}\n\nfunc TestMathFunctions(t *testing.T) {\n\tdb := openMem(t)\n\t// Test basic math functions\n\tvar result float64\n\terr := db.QueryRow(`SELECT abs(-15.5)`).Scan(&result)\n\tif err != nil {\n\t\tt.Fatalf(\"Error with abs function: %v\", err)\n\t}\n\tif result != 15.5 {\n\t\tt.Fatalf(\"abs(-15.5) should be 15.5, got %f\", result)\n\t}\n\n\t// Test trigonometric functions\n\terr = db.QueryRow(`SELECT round(sin(radians(30)), 4)`).Scan(&result)\n\tif err != nil {\n\t\tt.Fatalf(\"Error with sin function: %v\", err)\n\t}\n\tif math.Abs(result-0.5) > 0.0001 {\n\t\tt.Fatalf(\"sin(30 degrees) should be about 0.5, got %f\", result)\n\t}\n\n\t// Test power functions\n\terr = db.QueryRow(`SELECT pow(2, 3)`).Scan(&result)\n\tif err != nil {\n\t\tt.Fatalf(\"Error with pow function: %v\", err)\n\t}\n\tif result != 8 {\n\t\tt.Fatalf(\"2^3 should be 8, got %f\", result)\n\t}\n}\n\nfunc TestJSONFunctions(t *testing.T) {\n\tdb := openMem(t)\n\t// Test json function\n\tvar valid int\n\terr := db.QueryRow(`SELECT json_valid('{\"name\":\"John\",\"age\":30}')`).Scan(&valid)\n\tif err != nil {\n\t\tt.Fatalf(\"Error with json_valid function: %v\", err)\n\t}\n\tif valid != 1 {\n\t\tt.Fatalf(\"Expected valid JSON to return 1, got %d\", valid)\n\t}\n\n\t// Test json_extract\n\tvar name string\n\terr = db.QueryRow(`SELECT json_extract('{\"name\":\"John\",\"age\":30}', '$.name')`).Scan(&name)\n\tif err != nil {\n\t\tt.Fatalf(\"Error with json_extract function: %v\", err)\n\t}\n\tif name != \"John\" {\n\t\tt.Fatalf(\"Expected 'John', got '%s'\", name)\n\t}\n\n\t// Test JSON shorthand\n\tvar age int\n\terr = db.QueryRow(`SELECT '{\"name\":\"John\",\"age\":30}' -> '$.age'`).Scan(&age)\n\tif err != nil {\n\t\tt.Fatalf(\"Error with JSON shorthand: %v\", err)\n\t}\n\tif age != 30 {\n\t\tt.Fatalf(\"Expected 30, got %d\", age)\n\t}\n}\n\nfunc TestParameterOrdering(t *testing.T) {\n\tnewConn := openMem(t)\n\tsql := \"CREATE TABLE test (a,b,c);\"\n\tnewConn.Exec(sql)\n\n\t// Test inserting with parameters in a different order than\n\t// the table definition.\n\tsql = \"INSERT INTO test (b, c ,a) VALUES (?, ?, ?);\"\n\texpectedValues := []int{1, 2, 3}\n\tstmt, err := newConn.Prepare(sql)\n\trequire.Nil(t, err)\n\t_, err = stmt.Exec(expectedValues[1], expectedValues[2], expectedValues[0])\n\tif err != nil {\n\t\tt.Fatalf(\"Error preparing statement: %v\", err)\n\t}\n\t// check that the values are in the correct order\n\tquery := \"SELECT a,b,c FROM test;\"\n\trows, err := newConn.Query(query)\n\tif err != nil {\n\t\tt.Fatalf(\"Error executing query: %v\", err)\n\t}\n\tfor rows.Next() {\n\t\tvar a, b, c int\n\t\terr := rows.Scan(&a, &b, &c)\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Error scanning row: \", err)\n\t\t}\n\t\tresult := []int{a, b, c}\n\t\tfor i := range 3 {\n\t\t\tif result[i] != expectedValues[i] {\n\t\t\t\tfmt.Printf(\"RESULTS: %d, %d, %d\\n\", a, b, c)\n\t\t\t\tfmt.Printf(\"EXPECTED: %d, %d, %d\\n\", expectedValues[0], expectedValues[1], expectedValues[2])\n\t\t\t}\n\t\t}\n\t}\n\n\t// -- part 2 --\n\t// mixed parameters and regular values\n\tsql2 := \"CREATE TABLE test2 (a,b,c);\"\n\tnewConn.Exec(sql2)\n\texpectedValues2 := []int{1, 2, 3}\n\n\t// Test inserting with parameters in a different order than\n\t// the table definition, with a mixed regular parameter included\n\tsql2 = \"INSERT INTO test2 (a, b ,c) VALUES (1, ?, ?);\"\n\t_, err = newConn.Exec(sql2, expectedValues2[1], expectedValues2[2])\n\tif err != nil {\n\t\tt.Fatalf(\"Error preparing statement: %v\", err)\n\t}\n\t// check that the values are in the correct order\n\tquery2 := \"SELECT a,b,c FROM test2;\"\n\trows2, err := newConn.Query(query2)\n\tif err != nil {\n\t\tt.Fatalf(\"Error executing query: %v\", err)\n\t}\n\tfor rows2.Next() {\n\t\tvar a, b, c int\n\t\terr := rows2.Scan(&a, &b, &c)\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Error scanning row: \", err)\n\t\t}\n\t\tresult := []int{a, b, c}\n\n\t\tfmt.Printf(\"RESULTS: %d, %d, %d\\n\", a, b, c)\n\t\tfmt.Printf(\"EXPECTED: %d, %d, %d\\n\", expectedValues[0], expectedValues[1], expectedValues[2])\n\t\tfor i := range 3 {\n\t\t\tif result[i] != expectedValues[i] {\n\t\t\t\tt.Fatalf(\"Expected %d, got %d\", expectedValues[i], result[i])\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestLimitOffsetParameters(t *testing.T) {\n\tnewConn := openMem(t)\n\tsql := \"CREATE TABLE test (a, b);\"\n\t_, err := newConn.Exec(sql)\n\tif err != nil {\n\t\tt.Fatal(\"Error creating table\")\n\t}\n\tsql = \"INSERT INTO test (a, b) VALUES (1, 'a'), (2,'b'), (3,'c'), (4,'c'), (5,'d');\"\n\t_, err = newConn.Exec(sql)\n\tif err != nil {\n\t\tt.Fatal(\"Error inserting data\")\n\t}\n\tsql = \"SELECT a, b FROM test ORDER BY b DESC LIMIT ? OFFSET ?;\"\n\tquery, err := newConn.Prepare(sql)\n\tif err != nil {\n\t\tt.Fatalf(\"Error preparing statement: %v\", err)\n\t}\n\tlimit := 2\n\toffset := 1\n\texpected := []int{4, 3}\n\trows, err := query.Query(limit, offset)\n\tif err != nil {\n\t\tt.Fatalf(\"Error executing query: %v\", err)\n\t}\n\tvar a int\n\tvar b string\n\tfor rows.Next() {\n\t\trows.Scan(&a, &b)\n\t\tif a != expected[0] && a != expected[1] {\n\t\t\tt.Fatalf(\"Expected %d or %d, got %d\", expected[0], expected[1], a)\n\t\t}\n\t}\n}\n\nfunc TestIndex(t *testing.T) {\n\tnewConn := openMem(t)\n\tsql := \"CREATE TABLE users (name TEXT PRIMARY KEY, email TEXT)\"\n\t_, err := newConn.Exec(sql)\n\tif err != nil {\n\t\tt.Fatalf(\"Error creating table: %v\", err)\n\t}\n\tsql = \"CREATE INDEX email_idx ON users(email)\"\n\t_, err = newConn.Exec(sql)\n\tif err != nil {\n\t\tt.Fatalf(\"Error creating index: %v\", err)\n\t}\n\n\t// Test inserting with parameters in a different order than\n\t// the table definition.\n\tsql = \"INSERT INTO users VALUES ('alice', 'a@b.c'), ('bob', 'b@d.e')\"\n\t_, err = newConn.Exec(sql)\n\tif err != nil {\n\t\tt.Fatalf(\"Error inserting data: %v\", err)\n\t}\n\n\tfor filter, row := range map[string][]string{\n\t\t\"a@b.c\": {\"alice\", \"a@b.c\"},\n\t\t\"b@d.e\": {\"bob\", \"b@d.e\"},\n\t} {\n\t\tquery := \"SELECT * FROM users WHERE email = ?\"\n\t\trows, err := newConn.Query(query, filter)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Error executing query: %v\", err)\n\t\t}\n\t\tfor rows.Next() {\n\t\t\tvar name, email string\n\t\t\terr := rows.Scan(&name, &email)\n\t\t\tt.Log(\"name,email:\", name, email)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(\"Error scanning row: \", err)\n\t\t\t}\n\t\t\tif !slices.Equal([]string{name, email}, row) {\n\t\t\t\tt.Fatal(\"Unexpected result\", row, []string{name, email})\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc slicesAreEq(a, b []byte) bool {\n\tif len(a) != len(b) {\n\t\tfmt.Printf(\"LENGTHS NOT EQUAL: %d != %d\\n\", len(a), len(b))\n\t\treturn false\n\t}\n\tfor i := range a {\n\t\tif a[i] != b[i] {\n\t\t\tfmt.Printf(\"SLICES NOT EQUAL: %v != %v\\n\", a, b)\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nvar rowsMap = map[int]string{1: \"hello\", 2: \"world\", 3: \"foo\", 4: \"bar\", 5: \"baz\"}\n\nfunc createTable(conn *sql.DB) error {\n\tinsert := \"CREATE TABLE test (foo INT, bar TEXT, baz BLOB);\"\n\tstmt, err := conn.Prepare(insert)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer stmt.Close()\n\t_, err = stmt.Exec()\n\treturn err\n}\n\nfunc insertData(conn *sql.DB) error {\n\tfor i := 1; i <= 5; i++ {\n\t\tinsert := \"INSERT INTO test (foo, bar, baz) VALUES (?, ?, ?);\"\n\t\tstmt, err := conn.Prepare(insert)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer stmt.Close()\n\t\tif _, err = stmt.Exec(i, rowsMap[i], []byte(rowsMap[i])); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc TestNullHandling(t *testing.T) {\n\tdb := openMem(t)\n\t_, err := db.Exec(`\n\t\tCREATE TABLE null_test (\n\t\t\tid INTEGER PRIMARY KEY,\n\t\t\ttext_val TEXT,\n\t\t\tint_val INTEGER,\n\t\t\treal_val REAL,\n\t\t\tblob_val BLOB\n\t\t)`)\n\tif err != nil {\n\t\tt.Fatalf(\"Error creating table: %v\", err)\n\t}\n\n\ttestCases := []struct {\n\t\tname     string\n\t\tquery    string\n\t\targs     []any\n\t\texpected []any\n\t}{\n\t\t{\"all nulls\", \"INSERT INTO null_test (id) VALUES (?)\", []any{1}, []any{1, nil, nil, nil, nil}},\n\t\t{\"mixed nulls\", \"INSERT INTO null_test VALUES (?, ?, ?, ?, ?)\", []any{2, \"text\", nil, 3.14, nil}, []any{2, \"text\", nil, 3.14, nil}},\n\t\t{\"no nulls\", \"INSERT INTO null_test VALUES (?, ?, ?, ?, ?)\", []any{3, \"full\", 42, 2.718, []byte(\"data\")}, []any{3, \"full\", 42, 2.718, []byte(\"data\")}},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t_, err := db.Exec(tc.query, tc.args...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Error inserting: %v\", err)\n\t\t\t}\n\t\t})\n\t}\n\n\trows, err := db.Query(\"SELECT * FROM null_test ORDER BY id\")\n\tif err != nil {\n\t\tt.Fatalf(\"Error querying: %v\", err)\n\t}\n\tdefer rows.Close()\n\n\ti := 0\n\tfor rows.Next() {\n\t\tvar id sql.NullInt64\n\t\tvar textVal sql.NullString\n\t\tvar intVal sql.NullInt64\n\t\tvar realVal sql.NullFloat64\n\t\tvar blobVal []byte\n\n\t\terr := rows.Scan(&id, &textVal, &intVal, &realVal, &blobVal)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Error scanning: %v\", err)\n\t\t}\n\n\t\tif !id.Valid {\n\t\t\tt.Errorf(\"ID should always be valid\")\n\t\t}\n\t\ti++\n\t}\n\n\tif i != 3 {\n\t\tt.Fatalf(\"Expected 3 rows, got %d\", i)\n\t}\n}\n\nfunc mustExec(t *testing.T, db *sql.DB, q string, args ...any) sql.Result {\n\tt.Helper()\n\tres, err := db.Exec(q, args...)\n\tif err != nil {\n\t\tt.Fatalf(\"exec %q: %v\", q, err)\n\t}\n\treturn res\n}\n\nfunc TestLastInsertIDAndRowsAffected(t *testing.T) {\n\tdb := openMem(t)\n\tmustExec(t, db, `CREATE TABLE t(id INTEGER PRIMARY KEY, name TEXT)`)\n\tres := mustExec(t, db, `INSERT INTO t(name) VALUES ('alice')`)\n\tid, err := res.LastInsertId()\n\tif err != nil {\n\t\tt.Fatalf(\"LastInsertId: %v\", err)\n\t}\n\tif id == 0 {\n\t\tt.Fatalf(\"expected non-zero last insert id\")\n\t}\n\tres = mustExec(t, db, `UPDATE t SET name='ALICE' WHERE id = ?`, id)\n\tra, err := res.RowsAffected()\n\tif err != nil {\n\t\tt.Fatalf(\"RowsAffected: %v\", err)\n\t}\n\tif ra != 1 {\n\t\tt.Fatalf(\"expected 1 row affected, got %d\", ra)\n\t}\n}\n\nfunc TestDataTypes(t *testing.T) {\n\tdb, err := sql.Open(\"turso\", \":memory:\")\n\tif err != nil {\n\t\tt.Fatalf(\"Error opening connection: %v\", err)\n\t}\n\tdefer db.Close()\n\n\t_, err = db.Exec(`\n\t\tCREATE TABLE types_test (\n\t\t\tcol_integer INTEGER,\n\t\t\tcol_real REAL,\n\t\t\tcol_text TEXT,\n\t\t\tcol_blob BLOB,\n\t\t\tcol_numeric NUMERIC,\n\t\t\tcol_boolean BOOLEAN,\n\t\t\tcol_date DATE,\n\t\t\tcol_datetime DATETIME,\n\t\t\tcol_timestamp TIMESTAMP\n\t\t)`)\n\tif err != nil {\n\t\tt.Fatalf(\"Error creating table: %v\", err)\n\t}\n\n\t// Insert test data\n\tnow := time.Now()\n\t_, err = db.Exec(`\n\t\tINSERT INTO types_test VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n\t\t42,\n\t\t3.14159,\n\t\t\"Hello, 世界\",\n\t\t[]byte{0x01, 0x02, 0x03},\n\t\t\"123.456\",\n\t\ttrue,\n\t\tnow.Format(\"2006-01-02\"),\n\t\tnow.Format(\"2006-01-02 15:04:05\"),\n\t\tnow.Unix(),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Error inserting: %v\", err)\n\t}\n\n\t// Query and verify each type\n\tvar (\n\t\tcolInt       int\n\t\tcolReal      float64\n\t\tcolText      string\n\t\tcolBlob      []byte\n\t\tcolNumeric   string\n\t\tcolBool      bool\n\t\tcolDate      string\n\t\tcolDateTime  string\n\t\tcolTimestamp int64\n\t)\n\n\terr = db.QueryRow(\"SELECT * FROM types_test\").Scan(\n\t\t&colInt, &colReal, &colText, &colBlob, &colNumeric,\n\t\t&colBool, &colDate, &colDateTime, &colTimestamp,\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Error scanning: %v\", err)\n\t}\n\n\t// Verify values\n\tif colInt != 42 {\n\t\tt.Errorf(\"Integer mismatch: got %d\", colInt)\n\t}\n\tif math.Abs(colReal-3.14159) > 0.00001 {\n\t\tt.Errorf(\"Real mismatch: got %f\", colReal)\n\t}\n\tif colText != \"Hello, 世界\" {\n\t\tt.Errorf(\"Text mismatch: got %s\", colText)\n\t}\n\tif !slices.Equal(colBlob, []byte{0x01, 0x02, 0x03}) {\n\t\tt.Errorf(\"Blob mismatch: got %v\", colBlob)\n\t}\n}\n\nfunc createDatabasesTable(t *testing.T, db *sql.DB) {\n\tt.Helper()\n\t_, err := db.Exec(`\n\tCREATE TABLE IF NOT EXISTS databases (\n\t\tid INTEGER PRIMARY KEY,\n\t\tcreated_at TEXT,\n\t\tupdated_at TEXT,\n\t\tdeleted_at TEXT,\n\t\thostname TEXT UNIQUE,\n\t\tnamespace TEXT,\n\t\tfly_app TEXT,\n\t\taddress TEXT,\n\t\tprimary_address TEXT,\n\t\tcloud_cluster_name TEXT,\n\t\tlocal BOOLEAN,\n\t\tallowed_ips TEXT\n\t)`)\n\tif err != nil {\n\t\tt.Fatalf(\"create table: %v\", err)\n\t}\n}\n\nfunc TestUpsertReturning_databaseSQL_Prepared(t *testing.T) {\n\tdb := openMem(t)\n\tcreateDatabasesTable(t, db)\n\n\tconst stmtText = `\n\tINSERT INTO databases\n\t\t(created_at,updated_at,deleted_at,hostname,namespace,fly_app,address,primary_address,cloud_cluster_name,local,allowed_ips)\n\tVALUES (?,?,?,?,?,?,?,?,?,?,?)\n\tON CONFLICT (hostname) DO UPDATE SET\n\t\tupdated_at=excluded.updated_at,\n\t\tdeleted_at=excluded.deleted_at,\n\t\thostname=excluded.hostname,\n\t\tnamespace=excluded.namespace,\n\t\tfly_app=excluded.fly_app,\n\t\taddress=excluded.address,\n\t\tprimary_address=excluded.primary_address,\n\t\tcloud_cluster_name=excluded.cloud_cluster_name,\n\t\tlocal=excluded.local,\n\t\tallowed_ips=excluded.allowed_ips\n\tRETURNING id`\n\n\tnow := time.Now()\n\targs := []any{\n\t\tnow,                      // created_at (driver will send RFC3339 string)\n\t\tnow,                      // updated_at\n\t\tnil,                      // deleted_at\n\t\t\"host-1.local\",           // hostname (unique)\n\t\t\"ns-123\",                 // namespace\n\t\t\"app-xyz\",                // fly_app\n\t\t\"http://127.0.0.1:10000\", // address\n\t\t\"\",                       // primary_address\n\t\t\"local\",                  // cloud_cluster_name\n\t\tfalse,                    // local (bool -> int 0/1 in your marshaler)\n\t\tnil,                      // allowed_ips (NULL)\n\t}\n\n\tstmt, err := db.Prepare(stmtText)\n\tif err != nil {\n\t\tt.Fatalf(\"prepare: %v\", err)\n\t}\n\tdefer stmt.Close()\n\n\tvar returnedID int64\n\tif err := stmt.QueryRow(args...).Scan(&returnedID); err != nil {\n\t\tt.Fatalf(\"queryrow/scan: %v\", err)\n\t}\n\tt.Logf(\"returned id: %d\", returnedID)\n\tcpy := returnedID\n\n\t// Re-run to trigger ON CONFLICT path and ensure still binds 12 args and returns id\n\targs[1] = time.Now() // updated_at\n\tif err := stmt.QueryRow(args...).Scan(&returnedID); err != nil {\n\t\tt.Fatalf(\"queryrow/scan (conflict): %v\", err)\n\t}\n\tif returnedID != cpy {\n\t\tt.Fatalf(\"expected same id on conflict, got %d then %d\", cpy, returnedID)\n\t}\n}\n\nfunc TestInsertReturning(t *testing.T) {\n\tdb := openMem(t)\n\t_, err := db.Exec(`CREATE TABLE IF NOT EXISTS t (x)`)\n\tif err != nil {\n\t\tt.Fatalf(\"create table: %v\", err)\n\t}\n\n\tvar returnedID int64\n\tif err := db.QueryRow(\"INSERT INTO t VALUES (1) RETURNING x\").Scan(&returnedID); err != nil {\n\t\tt.Fatalf(\"queryrow/scan: %v\", err)\n\t}\n\tif returnedID != 1 {\n\t\tt.Fatalf(\"unexpected returnedId: %v\", err)\n\t}\n\tt.Log(returnedID)\n\tif err := db.QueryRow(\"SELECT * FROM t\").Scan(&returnedID); err != nil {\n\t\tt.Fatalf(\"queryrow/scan (conflict): %v\", err)\n\t}\n\tif returnedID != 1 {\n\t\tt.Fatalf(\"unexpected returnedId: %v\", err)\n\t}\n\tt.Log(returnedID)\n}\n\nfunc TestUpsertReturning_databaseSQL_Prepared_ArgCountMismatch(t *testing.T) {\n\tdb := openMem(t)\n\tcreateDatabasesTable(t, db)\n\n\tconst stmtText = `\n\tINSERT INTO databases\n\t\t(created_at,updated_at,deleted_at,hostname,namespace,fly_app,address,primary_address,cloud_cluster_name,local,allowed_ips,id)\n\tVALUES (?,?,?,?,?,?,?,?,?,?,?,?)\n\tON CONFLICT (hostname) DO UPDATE SET\n\t\tupdated_at=excluded.updated_at,\n\t\tdeleted_at=excluded.deleted_at,\n\t\thostname=excluded.hostname,\n\t\tnamespace=excluded.namespace,\n\t\tfly_app=excluded.fly_app,\n\t\taddress=excluded.address,\n\t\tprimary_address=excluded.primary_address,\n\t\tcloud_cluster_name=excluded.cloud_cluster_name,\n\t\tlocal=excluded.local,\n\t\tallowed_ips=excluded.allowed_ips\n\tRETURNING id`\n\n\tstmt, err := db.Prepare(stmtText)\n\tif err != nil {\n\t\tt.Fatalf(\"prepare: %v\", err)\n\t}\n\tdefer stmt.Close()\n\n\tnow := time.Now()\n\targs := []any{\n\t\tnow, now, nil, \"host-2.local\", \"ns\", \"app\", \"addr\", \"\", \"local\", false, nil, 22,\n\t}\n\t// Append a bogus 13th arg to force the exact database/sql error you saw\n\targs = append(args, 999)\n\n\tvar id int64\n\tif err := stmt.QueryRow(args...).Scan(&id); err == nil {\n\t\tt.Fatal(\"expected argument count error, got nil\")\n\t}\n}\n\nfunc TestMultiStatementExecution(t *testing.T) {\n\tdb := openMem(t)\n\n\tt.Run(\"BasicMultiStatement\", func(t *testing.T) {\n\t\t_, err := db.Exec(`\n\t\t\tCREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT);\n\t\t\tINSERT INTO users (name) VALUES ('Alice');\n\t\t\tINSERT INTO users (name) VALUES ('Bob');\n\t\t`)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed to execute multi-statement: %v\", err)\n\t\t}\n\n\t\tvar count int\n\t\terr = db.QueryRow(\"SELECT COUNT(*) FROM users\").Scan(&count)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed to query count: %v\", err)\n\t\t}\n\t\tif count != 2 {\n\t\t\tt.Errorf(\"Expected 2 rows, got %d\", count)\n\t\t}\n\t})\n\n\tt.Run(\"StringsWithSemicolons\", func(t *testing.T) {\n\t\t_, err := db.Exec(`\n\t\t\tCREATE TABLE messages (id INTEGER PRIMARY KEY, text TEXT);\n\t\t\tINSERT INTO messages (text) VALUES ('Hello; World');\n\t\t\tINSERT INTO messages (text) VALUES ('Test; Message; Multiple');\n\t\t`)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed to execute with semicolons in strings: %v\", err)\n\t\t}\n\n\t\tvar count int\n\t\terr = db.QueryRow(\"SELECT COUNT(*) FROM messages\").Scan(&count)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed to query count: %v\", err)\n\t\t}\n\t\tif count != 2 {\n\t\t\tt.Errorf(\"Expected 2 rows, got %d\", count)\n\t\t}\n\n\t\trows, err := db.Query(\"SELECT text FROM messages ORDER BY id\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed to query messages: %v\", err)\n\t\t}\n\t\tdefer rows.Close()\n\n\t\texpected := []string{\"Hello; World\", \"Test; Message; Multiple\"}\n\t\ti := 0\n\t\tfor rows.Next() {\n\t\t\tvar text string\n\t\t\tif err := rows.Scan(&text); err != nil {\n\t\t\t\tt.Fatalf(\"Failed to scan: %v\", err)\n\t\t\t}\n\t\t\tif text != expected[i] {\n\t\t\t\tt.Errorf(\"Row %d: expected %q, got %q\", i, expected[i], text)\n\t\t\t}\n\t\t\ti++\n\t\t}\n\t})\n\n\tt.Run(\"EscapedQuotes\", func(t *testing.T) {\n\t\t_, err := db.Exec(`\n\t\t\tCREATE TABLE names (id INTEGER PRIMARY KEY, name TEXT);\n\t\t\tINSERT INTO names (name) VALUES ('O''Brien');\n\t\t\tINSERT INTO names (name) VALUES ('It''s working');\n\t\t`)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed to execute with escaped quotes: %v\", err)\n\t\t}\n\n\t\tvar count int\n\t\terr = db.QueryRow(\"SELECT COUNT(*) FROM names\").Scan(&count)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed to query count: %v\", err)\n\t\t}\n\t\tif count != 2 {\n\t\t\tt.Errorf(\"Expected 2 rows, got %d\", count)\n\t\t}\n\n\t\tvar name string\n\t\terr = db.QueryRow(\"SELECT name FROM names WHERE id = 1\").Scan(&name)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed to query name: %v\", err)\n\t\t}\n\t\tif name != \"O'Brien\" {\n\t\t\tt.Errorf(\"Expected \\\"O'Brien\\\", got %q\", name)\n\t\t}\n\t})\n\n\tt.Run(\"EmptyStatements\", func(t *testing.T) {\n\t\t_, err := db.Exec(`\n\t\t\tCREATE TABLE test_empty (id INTEGER);;;\n\t\t\tINSERT INTO test_empty (id) VALUES (1);;\n\t\t`)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed to execute with empty statements: %v\", err)\n\t\t}\n\n\t\tvar count int\n\t\terr = db.QueryRow(\"SELECT COUNT(*) FROM test_empty\").Scan(&count)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed to query count: %v\", err)\n\t\t}\n\t\tif count != 1 {\n\t\t\tt.Errorf(\"Expected 1 row, got %d\", count)\n\t\t}\n\t})\n\n\tt.Run(\"FailureInMiddle\", func(t *testing.T) {\n\t\t_, err := db.Exec(`\n\t\t\tCREATE TABLE partial (id INTEGER PRIMARY KEY);\n\t\t\tINSERT INTO partial (id) VALUES (1);\n\t\t\tINSERT INTO partial (id) VALUES (1);\n\t\t`)\n\t\tif err == nil {\n\t\t\tt.Fatal(\"Expected error for duplicate key, got nil\")\n\t\t}\n\n\t\tvar count int\n\t\terr = db.QueryRow(\"SELECT COUNT(*) FROM partial\").Scan(&count)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed to query count: %v\", err)\n\t\t}\n\t\tif count != 1 {\n\t\t\tt.Errorf(\"Expected 1 row (from first INSERT before failure), got %d\", count)\n\t\t}\n\t})\n\n\tt.Run(\"WithParameters\", func(t *testing.T) {\n\t\t_, err := db.Exec(`CREATE TABLE param_test (id INTEGER, name TEXT);`)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed to create table: %v\", err)\n\t\t}\n\n\t\t_, err = db.Exec(\"INSERT INTO param_test (id, name) VALUES (?, ?)\", 1, \"Test\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed to insert with parameters: %v\", err)\n\t\t}\n\n\t\tvar count int\n\t\terr = db.QueryRow(\"SELECT COUNT(*) FROM param_test\").Scan(&count)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed to query count: %v\", err)\n\t\t}\n\t\tif count != 1 {\n\t\t\tt.Errorf(\"Expected 1 row, got %d\", count)\n\t\t}\n\t})\n}\n\nfunc TestTimeValueRoundtrip(t *testing.T) {\n\tdb := openMem(t)\n\n\t_, err := db.Exec(`CREATE TABLE time_test (\n\t\tid INTEGER PRIMARY KEY,\n\t\tcreated_at DATETIME,\n\t\tupdated_at DATETIME,\n\t\tdeleted_at TIMESTAMP\n\t)`)\n\trequire.NoError(t, err)\n\n\t// Use a fixed time for deterministic testing\n\t// time.Time values are stored as RFC3339Nano strings\n\toriginalTime := time.Date(2024, 6, 15, 14, 30, 45, 123456789, time.UTC)\n\tlaterTime := originalTime.Add(24 * time.Hour)\n\n\t// Insert using time.Time values\n\t_, err = db.Exec(\n\t\t`INSERT INTO time_test (id, created_at, updated_at, deleted_at) VALUES (?, ?, ?, ?)`,\n\t\t1, originalTime, laterTime, nil,\n\t)\n\trequire.NoError(t, err)\n\n\tt.Run(\"scan into time.Time\", func(t *testing.T) {\n\t\tvar id int\n\t\tvar createdAt, updatedAt time.Time\n\t\tvar deletedAt sql.NullTime\n\n\t\terr := db.QueryRow(`SELECT id, created_at, updated_at, deleted_at FROM time_test WHERE id = 1`).\n\t\t\tScan(&id, &createdAt, &updatedAt, &deletedAt)\n\t\trequire.NoError(t, err)\n\n\t\trequire.Equal(t, 1, id)\n\t\trequire.True(t, originalTime.Equal(createdAt), \"createdAt mismatch: expected %v, got %v\", originalTime, createdAt)\n\t\trequire.True(t, laterTime.Equal(updatedAt), \"updatedAt mismatch: expected %v, got %v\", laterTime, updatedAt)\n\t\trequire.False(t, deletedAt.Valid, \"deletedAt should be NULL\")\n\t})\n\n\tt.Run(\"scan into string then parse\", func(t *testing.T) {\n\t\tvar createdAtStr string\n\t\terr := db.QueryRow(`SELECT created_at FROM time_test WHERE id = 1`).Scan(&createdAtStr)\n\t\trequire.NoError(t, err)\n\n\t\t// Verify the stored format is RFC3339Nano\n\t\tparsed, err := time.Parse(time.RFC3339Nano, createdAtStr)\n\t\trequire.NoError(t, err)\n\t\trequire.True(t, originalTime.Equal(parsed), \"parsed time mismatch\")\n\t})\n\n\tt.Run(\"update with time.Time\", func(t *testing.T) {\n\t\tnewTime := originalTime.Add(48 * time.Hour)\n\t\t_, err := db.Exec(`UPDATE time_test SET updated_at = ? WHERE id = ?`, newTime, 1)\n\t\trequire.NoError(t, err)\n\n\t\tvar updatedAt time.Time\n\t\terr = db.QueryRow(`SELECT updated_at FROM time_test WHERE id = 1`).Scan(&updatedAt)\n\t\trequire.NoError(t, err)\n\t\trequire.True(t, newTime.Equal(updatedAt), \"updated time mismatch\")\n\t})\n\n\tt.Run(\"query with time.Time parameter\", func(t *testing.T) {\n\t\t// Insert another row\n\t\tanotherTime := originalTime.Add(72 * time.Hour)\n\t\t_, err := db.Exec(`INSERT INTO time_test (id, created_at) VALUES (?, ?)`, 2, anotherTime)\n\t\trequire.NoError(t, err)\n\n\t\t// Query using time as parameter\n\t\tvar id int\n\t\terr = db.QueryRow(`SELECT id FROM time_test WHERE created_at = ?`, originalTime).Scan(&id)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 1, id)\n\t})\n\n\tt.Run(\"prepared statement with time.Time\", func(t *testing.T) {\n\t\tstmt, err := db.Prepare(`SELECT id, created_at FROM time_test WHERE created_at < ?`)\n\t\trequire.NoError(t, err)\n\t\tdefer stmt.Close()\n\n\t\tcutoff := originalTime.Add(1 * time.Hour)\n\t\tvar id int\n\t\tvar createdAt time.Time\n\t\terr = stmt.QueryRow(cutoff).Scan(&id, &createdAt)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 1, id)\n\t\trequire.True(t, originalTime.Equal(createdAt))\n\t})\n\n\t// expected behaviour - similar to the sqlite3 go driver as it uses decltype\n\tt.Run(\"transform datetime column\", func(t *testing.T) {\n\t\tstmt, err := db.Prepare(`SELECT concat(created_at || '') FROM time_test`)\n\t\trequire.NoError(t, err)\n\t\tdefer stmt.Close()\n\n\t\tvar createdAt string\n\t\terr = stmt.QueryRow().Scan(&createdAt)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, createdAt, originalTime.Format(time.RFC3339Nano))\n\t})\n}\n\n// --- Busy Timeout Tests ---\n\nfunc TestBusyTimeoutDefault(t *testing.T) {\n\t// Open a database without specifying busy timeout - should use default (5000ms)\n\tdb, err := sql.Open(\"turso\", \":memory:\")\n\trequire.NoError(t, err)\n\tdefer db.Close()\n\n\t// Get the underlying connection and verify the timeout\n\tconn, err := db.Conn(t.Context())\n\trequire.NoError(t, err)\n\tdefer conn.Close()\n\n\terr = conn.Raw(func(driverConn any) error {\n\t\ttc, ok := driverConn.(*tursoDbConnection)\n\t\trequire.True(t, ok, \"expected *tursoDbConnection\")\n\t\trequire.Equal(t, DefaultBusyTimeout, tc.GetBusyTimeout(),\n\t\t\t\"expected default busy timeout of %d, got %d\", DefaultBusyTimeout, tc.GetBusyTimeout())\n\t\treturn nil\n\t})\n\trequire.NoError(t, err)\n\tfmt.Println(\"Default busy timeout test passed\")\n}\n\nfunc TestBusyTimeoutDSN(t *testing.T) {\n\t// Test that _busy_timeout in DSN overrides the default\n\tdb, err := sql.Open(\"turso\", \":memory:?_busy_timeout=10000\")\n\trequire.NoError(t, err)\n\tdefer db.Close()\n\n\tconn, err := db.Conn(t.Context())\n\trequire.NoError(t, err)\n\tdefer conn.Close()\n\n\terr = conn.Raw(func(driverConn any) error {\n\t\ttc, ok := driverConn.(*tursoDbConnection)\n\t\trequire.True(t, ok)\n\t\trequire.Equal(t, 10000, tc.GetBusyTimeout(),\n\t\t\t\"expected busy timeout of 10000, got %d\", tc.GetBusyTimeout())\n\t\treturn nil\n\t})\n\trequire.NoError(t, err)\n\tfmt.Println(\"Busy timeout DSN test passed\")\n}\n\nfunc TestBusyTimeoutDisabled(t *testing.T) {\n\t// Test that _busy_timeout=-1 disables the timeout\n\tdb, err := sql.Open(\"turso\", \":memory:?_busy_timeout=-1\")\n\trequire.NoError(t, err)\n\tdefer db.Close()\n\n\tconn, err := db.Conn(t.Context())\n\trequire.NoError(t, err)\n\tdefer conn.Close()\n\n\terr = conn.Raw(func(driverConn any) error {\n\t\ttc, ok := driverConn.(*tursoDbConnection)\n\t\trequire.True(t, ok)\n\t\trequire.Equal(t, 0, tc.GetBusyTimeout(),\n\t\t\t\"expected busy timeout of 0 (disabled), got %d\", tc.GetBusyTimeout())\n\t\treturn nil\n\t})\n\tfmt.Println(\"Busy timeout disabled test passed\")\n\trequire.NoError(t, err)\n}\n\nfunc TestBusyTimeoutRuntimeChange(t *testing.T) {\n\tdb, err := sql.Open(\"turso\", \":memory:\")\n\trequire.NoError(t, err)\n\tdefer db.Close()\n\n\tconn, err := db.Conn(t.Context())\n\trequire.NoError(t, err)\n\tdefer conn.Close()\n\n\terr = conn.Raw(func(driverConn any) error {\n\t\ttc, ok := driverConn.(*tursoDbConnection)\n\t\trequire.True(t, ok)\n\n\t\t// Check initial default\n\t\trequire.Equal(t, DefaultBusyTimeout, tc.GetBusyTimeout())\n\n\t\t// Change to custom value\n\t\terr := tc.SetBusyTimeout(15000)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 15000, tc.GetBusyTimeout())\n\n\t\t// Disable timeout\n\t\terr = tc.SetBusyTimeout(0)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 0, tc.GetBusyTimeout())\n\n\t\treturn nil\n\t})\n\tfmt.Println(\"Busy timeout runtime change test passed\")\n\trequire.NoError(t, err)\n}\n\nfunc TestBusyTimeoutConnector(t *testing.T) {\n\tt.Run(\"default timeout via connector\", func(t *testing.T) {\n\t\tconnector, err := NewConnector(\":memory:\")\n\t\trequire.NoError(t, err)\n\n\t\tdb := sql.OpenDB(connector)\n\t\tdefer db.Close()\n\n\t\tconn, err := db.Conn(t.Context())\n\t\trequire.NoError(t, err)\n\t\tdefer conn.Close()\n\n\t\terr = conn.Raw(func(driverConn any) error {\n\t\t\ttc, ok := driverConn.(*tursoDbConnection)\n\t\t\trequire.True(t, ok)\n\t\t\trequire.Equal(t, DefaultBusyTimeout, tc.GetBusyTimeout())\n\t\t\treturn nil\n\t\t})\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"custom timeout via connector\", func(t *testing.T) {\n\t\tconnector, err := NewConnector(\":memory:\", WithBusyTimeout(20000))\n\t\trequire.NoError(t, err)\n\n\t\tdb := sql.OpenDB(connector)\n\t\tdefer db.Close()\n\n\t\tconn, err := db.Conn(t.Context())\n\t\trequire.NoError(t, err)\n\t\tdefer conn.Close()\n\n\t\terr = conn.Raw(func(driverConn any) error {\n\t\t\ttc, ok := driverConn.(*tursoDbConnection)\n\t\t\trequire.True(t, ok)\n\t\t\trequire.Equal(t, 20000, tc.GetBusyTimeout())\n\t\t\treturn nil\n\t\t})\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"disabled timeout via connector\", func(t *testing.T) {\n\t\tconnector, err := NewConnector(\":memory:\", WithBusyTimeout(0))\n\t\trequire.NoError(t, err)\n\n\t\tdb := sql.OpenDB(connector)\n\t\tdefer db.Close()\n\n\t\tconn, err := db.Conn(t.Context())\n\t\trequire.NoError(t, err)\n\t\tdefer conn.Close()\n\n\t\terr = conn.Raw(func(driverConn any) error {\n\t\t\ttc, ok := driverConn.(*tursoDbConnection)\n\t\t\trequire.True(t, ok)\n\t\t\trequire.Equal(t, 0, tc.GetBusyTimeout())\n\t\t\treturn nil\n\t\t})\n\t\trequire.NoError(t, err)\n\t})\n\tfmt.Println(\"Busy timeout connector test passed\")\n}\n\nfunc TestBusyTimeoutConcurrentWrites(t *testing.T) {\n\t// This test verifies that with a busy timeout, concurrent writers succeed\n\t// instead of immediately failing with SQLITE_BUSY\n\ttmp := t.TempDir()\n\tdbPath := path.Join(tmp, \"concurrent.db\")\n\n\t// Open with default timeout\n\tdb, err := sql.Open(\"turso\", dbPath)\n\trequire.NoError(t, err)\n\tdefer db.Close()\n\n\t// Create table\n\t_, err = db.Exec(\"CREATE TABLE counter (id INTEGER PRIMARY KEY, value INTEGER)\")\n\trequire.NoError(t, err)\n\t_, err = db.Exec(\"INSERT INTO counter (id, value) VALUES (1, 0)\")\n\trequire.NoError(t, err)\n\n\t// Run concurrent updates\n\tconst numGoroutines = 5\n\tconst numUpdates = 10\n\tdone := make(chan error, numGoroutines)\n\n\tfor i := range numGoroutines {\n\t\tgo func(workerID int) {\n\t\t\tfor j := range numUpdates {\n\t\t\t\t_, err := db.Exec(\"UPDATE counter SET value = value + 1 WHERE id = 1\")\n\t\t\t\tif err != nil {\n\t\t\t\t\tdone <- fmt.Errorf(\"worker %d, update %d: %w\", workerID, j, err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\tdone <- nil\n\t\t}(i)\n\t}\n\n\t// Collect results\n\tfor range numGoroutines {\n\t\terr := <-done\n\t\trequire.NoError(t, err, \"concurrent update should succeed with busy timeout\")\n\t}\n\n\t// Verify final count\n\tvar finalValue int\n\terr = db.QueryRow(\"SELECT value FROM counter WHERE id = 1\").Scan(&finalValue)\n\trequire.NoError(t, err)\n\trequire.Equal(t, numGoroutines*numUpdates, finalValue,\n\t\t\"expected %d updates, got %d\", numGoroutines*numUpdates, finalValue)\n\tfmt.Println(\"Busy timeout concurrent writes test passed\")\n}\n\nfunc TestParallelSelectColumnsConcurrency(t *testing.T) {\n\ttmp := t.TempDir()\n\tdbPath := path.Join(tmp, \"parallel_select_columns.db\")\n\n\tdb, err := sql.Open(\"turso\", dbPath)\n\trequire.NoError(t, err)\n\tdefer db.Close()\n\n\tmaxConns := runtime.GOMAXPROCS(0)\n\tif maxConns < 16 {\n\t\tmaxConns = 16\n\t}\n\tdb.SetMaxOpenConns(maxConns)\n\tdb.SetMaxIdleConns(maxConns)\n\n\t_, err = db.Exec(\"CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)\")\n\trequire.NoError(t, err)\n\t_, err = db.Exec(\"INSERT INTO users (id, name, email) VALUES (1, 'alice', 'alice@example.com')\")\n\trequire.NoError(t, err)\n\n\tworkers := runtime.GOMAXPROCS(0) * 4\n\tif workers < 32 {\n\t\tworkers = 32\n\t}\n\tconst roundsPerWorker = 12\n\tconst selectsPerRound = 300\n\n\tstart := make(chan struct{})\n\tvar wg sync.WaitGroup\n\terrCh := make(chan error, workers)\n\tfor g := range workers {\n\t\twg.Add(1)\n\t\tgo func(workerID int) {\n\t\t\tdefer wg.Done()\n\t\t\t<-start\n\t\t\tfor round := 0; round < roundsPerWorker; round++ {\n\t\t\t\tfor i := 0; i < selectsPerRound; i++ {\n\t\t\t\t\trows, qerr := db.Query(\"SELECT name, email, name FROM users WHERE id = 1\")\n\t\t\t\t\tif qerr != nil {\n\t\t\t\t\t\terrCh <- fmt.Errorf(\"worker %d query: %w\", workerID, qerr)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\t// Force column metadata fetch path on every query.\n\t\t\t\t\tcols, cerr := rows.Columns()\n\t\t\t\t\tif cerr != nil {\n\t\t\t\t\t\t_ = rows.Close()\n\t\t\t\t\t\terrCh <- fmt.Errorf(\"worker %d columns: %w\", workerID, cerr)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tif len(cols) != 3 {\n\t\t\t\t\t\t_ = rows.Close()\n\t\t\t\t\t\terrCh <- fmt.Errorf(\"worker %d unexpected columns len=%d\", workerID, len(cols))\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tfor rows.Next() {\n\t\t\t\t\t\tvar a, b, c string\n\t\t\t\t\t\tif serr := rows.Scan(&a, &b, &c); serr != nil {\n\t\t\t\t\t\t\t_ = rows.Close()\n\t\t\t\t\t\t\terrCh <- fmt.Errorf(\"worker %d scan: %w\", workerID, serr)\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif rerr := rows.Err(); rerr != nil {\n\t\t\t\t\t\t_ = rows.Close()\n\t\t\t\t\t\terrCh <- fmt.Errorf(\"worker %d rows err: %w\", workerID, rerr)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tif closeErr := rows.Close(); closeErr != nil {\n\t\t\t\t\t\terrCh <- fmt.Errorf(\"worker %d close: %w\", workerID, closeErr)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\t// Perturb scheduling/memory often to surface pointer lifetime bugs faster.\n\t\t\t\t\tif i%128 == 0 {\n\t\t\t\t\t\truntime.GC()\n\t\t\t\t\t\truntime.Gosched()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}(g)\n\t}\n\n\tclose(start)\n\twg.Wait()\n\tclose(errCh)\n\tfor runErr := range errCh {\n\t\trequire.NoError(t, runErr)\n\t}\n}\n"
  },
  {
    "path": "bindings/go/driver_sync.go",
    "content": "package turso\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"database/sql\"\n\t\"database/sql/driver\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tturso_libs \"github.com/tursodatabase/turso-go-platform-libs\"\n)\n\n// TursoPartialSyncConfig configures partial sync behavior.\ntype TursoPartialSyncConfig struct {\n\t// if positive, prefix partial bootstrap strategy will be used\n\tBootstrapStrategyPrefix int\n\t// if not empty, query partial bootstrap strategy will be used\n\tBootstrapStrategyQuery string\n\t// optional parameter which defines segment size for lazy loading from remote server\n\t// one of valid BootstrapStrategy* values MUST be set in order for this setting to have some effect\n\tSegmentSize int\n\t// optional parameter which defines if pages prefetch must be enabled\n\t// one of valid BootstrapStrategy* values MUST be set in order for this setting to have some effect\n\tPrefetch bool\n}\n\n// Public configuration for a synced database.\ntype TursoSyncDbConfig struct {\n\t// Path to the main database file locally.\n\t// Supports DSN-style options: \"mydb.db?_busy_timeout=5000\"\n\t// Supported options:\n\t//   - _busy_timeout: busy timeout in milliseconds (default: 5000, use -1 to disable)\n\tPath string\n\n\t// remote url for the sync\n\t// remote_url MUST be used in all sync engine operations: during bootstrap and all further operations\n\tRemoteUrl string\n\n\t// remote namespace for the sync client (optional)\n\tNamespace string\n\n\t// token for remote authentication\n\t// auth token value WILL not have any prefix and must be used as \"Authorization\" header prepended with \"Bearer \" prefix\n\tAuthToken string\n\n\t// optional unique client name (library MUST use `turso-sync-go` if omitted)\n\tClientName string\n\n\t// long polling timeout\n\tLongPollTimeoutMs int\n\n\t// if not set, initial bootstrap phase will be skipped and caller must call .pull(...) explicitly in order to get initial state from remote\n\t// default value is true\n\tBootstrapIfEmpty *bool\n\n\t// configuration for partial sync (disabled by default)\n\t// WARNING: This feature is EXPERIMENTAL\n\tPartialSyncExperimental TursoPartialSyncConfig\n\n\t// pass it as-is to the underlying connection\n\tExperimentalFeatures string\n\n\t// BusyTimeout in milliseconds for database connections.\n\t// Default is 5000ms (5 seconds). Set to -1 to disable busy handler.\n\t// Can also be specified via Path DSN: \"mydb.db?_busy_timeout=10000\"\n\t// If both are set, this field takes precedence.\n\tBusyTimeout int\n}\n\n// statistics for the synced database.\ntype TursoSyncDbStats struct {\n\t// amount of local operations written since last Pull(...) call\n\tCdcOperations int64\n\t// size of the main WAL file\n\tMainWalSize int64\n\t// size of the revert WAL file\n\tRevertWalSize int64\n\t// last successful pull time\n\tLastPullUnixTime int64\n\t// last successful push time\n\tLastPushUnixTime int64\n\t// total amount of bytes sent over the network (both Push and Pull operations are tracked together)\n\tNetworkSentBytes int64\n\t// total amount of bytes received over the network (both Push and Pull operations are tracked together)\n\tNetworkReceivedBytes int64\n\t// opaque server revision - it MUST NOT be interpreted/parsed in any way\n\tRevision string\n}\n\n// define public structs here\n\n// TursoSyncDb is a high-level synced database wrapper built over driver_db.go and bindings_sync.go.\n// It provides push/pull sync operations and creates SQL connections backed by the sync engine.\ntype TursoSyncDb struct {\n\tdb          TursoSyncDatabase\n\tbaseURL     string\n\tauthToken   string\n\tnamespace   string\n\tclient      *http.Client\n\tbusyTimeout int // busy timeout in milliseconds (0 = disabled)\n\n\tmu sync.Mutex\n}\n\n// main constructor to create synced database\nfunc NewTursoSyncDb(ctx context.Context, config TursoSyncDbConfig) (*TursoSyncDb, error) {\n\tInitLibrary(turso_libs.LoadTursoLibraryConfig{})\n\tif strings.TrimSpace(config.Path) == \"\" {\n\t\treturn nil, errors.New(\"turso: empty Path in TursoSyncDbConfig\")\n\t}\n\tclientName := config.ClientName\n\tif clientName == \"\" {\n\t\tclientName = \"turso-sync-go\"\n\t}\n\tbootstrap := true\n\tif config.BootstrapIfEmpty != nil {\n\t\tbootstrap = *config.BootstrapIfEmpty\n\t}\n\n\t// Parse DSN-style options from Path (e.g., \"mydb.db?_busy_timeout=5000\")\n\tdbPath, dsnOpts := parseSyncDSN(config.Path)\n\n\t// Determine busy timeout: explicit config field takes precedence over DSN\n\tbusyTimeout := config.BusyTimeout\n\tif busyTimeout == 0 {\n\t\t// Not explicitly set in config, check DSN\n\t\tif dsnOpts.BusyTimeout != 0 {\n\t\t\tbusyTimeout = dsnOpts.BusyTimeout\n\t\t}\n\t\t// If still 0, will use default when creating connections\n\t}\n\n\tremoteUrl := normalizeUrl(config.RemoteUrl)\n\t// Create sync database holder\n\tdbCfg := TursoDatabaseConfig{\n\t\tPath:                 dbPath,\n\t\tExperimentalFeatures: config.ExperimentalFeatures,\n\t\tAsyncIO:              true, // MUST be true for external IO handling\n\t}\n\tsyncCfg := TursoSyncDatabaseConfig{\n\t\tPath:                           dbPath,\n\t\tRemoteUrl:                      remoteUrl,\n\t\tNamespace:                      config.Namespace,\n\t\tClientName:                     clientName,\n\t\tLongPollTimeoutMs:              config.LongPollTimeoutMs,\n\t\tBootstrapIfEmpty:               bootstrap,\n\t\tReservedBytes:                  0,\n\t\tPartialBootstrapStrategyPrefix: config.PartialSyncExperimental.BootstrapStrategyPrefix,\n\t\tPartialBootstrapStrategyQuery:  config.PartialSyncExperimental.BootstrapStrategyQuery,\n\t\tPartialBootstrapSegmentSize:    config.PartialSyncExperimental.SegmentSize,\n\t\tPartialBootstrapPrefetch:       config.PartialSyncExperimental.Prefetch,\n\t}\n\tsdb, err := turso_sync_database_new(dbCfg, syncCfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\td := &TursoSyncDb{\n\t\tdb:          sdb,\n\t\tbaseURL:     strings.TrimRight(remoteUrl, \"/\"),\n\t\tauthToken:   strings.TrimSpace(config.AuthToken),\n\t\tnamespace:   config.Namespace,\n\t\tbusyTimeout: busyTimeout,\n\t\tclient: &http.Client{\n\t\t\t// No global timeout to allow long-poll; rely on request context.\n\t\t\tTransport: &http.Transport{\n\t\t\t\tProxy:               http.ProxyFromEnvironment,\n\t\t\t\tMaxIdleConns:        32,\n\t\t\t\tMaxIdleConnsPerHost: 32,\n\t\t\t\tIdleConnTimeout:     90 * time.Second,\n\t\t\t\tDisableCompression:  false,\n\t\t\t},\n\t\t},\n\t}\n\n\t// Create/open database with bootstrap logic as needed.\n\top, err := turso_sync_database_create(d.db)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t_, _, err = d.driveOpUntilDone(ctx, op)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn d, nil\n}\n\n// create turso db local connnection\n\n// internal connector to integrate with database/sql pool\ntype tursoSyncConnector struct{ db *TursoSyncDb }\n\nfunc (c *tursoSyncConnector) Connect(ctx context.Context) (driver.Conn, error) {\n\tc.db.mu.Lock()\n\tdefer c.db.mu.Unlock()\n\n\top, err := turso_sync_database_connect(c.db.db)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tkind, opFinal, err := c.db.driveOpUntilDone(ctx, op)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer turso_sync_operation_deinit(opFinal)\n\tif kind != TURSO_ASYNC_RESULT_CONNECTION {\n\t\treturn nil, errors.New(\"turso: unexpected result kind when connecting\")\n\t}\n\tconn, err := turso_sync_operation_result_extract_connection(opFinal)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// Integrate with the base driver; provide extra IO hook to process one IO item per iteration.\n\textra := func() error { return c.db.processOneIo() }\n\tdbConn := NewConnection(conn, extra)\n\n\t// Apply busy timeout - use default if not explicitly set\n\ttimeout := c.db.busyTimeout\n\tif timeout == 0 {\n\t\ttimeout = DefaultBusyTimeout // Apply sensible default\n\t} else if timeout < 0 {\n\t\ttimeout = 0 // -1 means explicitly disable\n\t}\n\tif timeout > 0 {\n\t\tturso_connection_set_busy_timeout_ms(conn, int64(timeout))\n\t}\n\tdbConn.busyTimeout = timeout\n\n\treturn dbConn, nil\n}\n\nfunc (c *tursoSyncConnector) Driver() driver.Driver { return &tursoDbDriver{} }\n\n// create tursodb connection using NewConnection(...) from driver_db.go and tursoSyncConnector helper\nfunc (d *TursoSyncDb) Connect(ctx context.Context) (*sql.DB, error) {\n\treturn sql.OpenDB(&tursoSyncConnector{db: d}), nil\n}\n\n// implement EXTRA sync methods\n\n// Pull fresh data from the remote\n// Pull DO NOT sent any local changes to the remote and instead \"rebase\" them on top of new changes from remote\n// Return true, if new changes were applied locally - otherwise return false\nfunc (d *TursoSyncDb) Pull(ctx context.Context) (bool, error) {\n\td.mu.Lock()\n\tdefer d.mu.Unlock()\n\n\t// 1) Wait for remote changes\n\twaitOp, err := turso_sync_database_wait_changes(d.db)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tkind, waitFinal, err := d.driveOpUntilDone(ctx, waitOp)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer turso_sync_operation_deinit(waitFinal)\n\tif kind != TURSO_ASYNC_RESULT_CHANGES {\n\t\treturn false, errors.New(\"turso: unexpected result kind for wait_changes\")\n\t}\n\tchanges, err := turso_sync_operation_result_extract_changes(waitFinal)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\t// No changes available\n\tif changes == nil {\n\t\treturn false, nil\n\t}\n\n\t// 2) Apply fetched changes locally\n\tapplyOp, err := turso_sync_database_apply_changes(d.db, changes)\n\tif err != nil {\n\t\t// changes ownership is transferred to apply_changes even in case of error\n\t\treturn false, err\n\t}\n\t_, applyFinal, err := d.driveOpUntilDone(ctx, applyOp)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tturso_sync_operation_deinit(applyFinal)\n\treturn true, nil\n}\n\n// Push local changes to the remote\n// Push DO NOT fetch any remote changes\nfunc (d *TursoSyncDb) Push(ctx context.Context) error {\n\td.mu.Lock()\n\tdefer d.mu.Unlock()\n\n\top, err := turso_sync_database_push_changes(d.db)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, opFinal, err := d.driveOpUntilDone(ctx, op)\n\tif err != nil {\n\t\treturn err\n\t}\n\tturso_sync_operation_deinit(opFinal)\n\treturn nil\n}\n\n// Get stats for the synced database\nfunc (d *TursoSyncDb) Stats(ctx context.Context) (TursoSyncDbStats, error) {\n\td.mu.Lock()\n\tdefer d.mu.Unlock()\n\n\top, err := turso_sync_database_stats(d.db)\n\tif err != nil {\n\t\treturn TursoSyncDbStats{}, err\n\t}\n\tkind, opFinal, err := d.driveOpUntilDone(ctx, op)\n\tif err != nil {\n\t\treturn TursoSyncDbStats{}, err\n\t}\n\tdefer turso_sync_operation_deinit(opFinal)\n\tif kind != TURSO_ASYNC_RESULT_STATS {\n\t\treturn TursoSyncDbStats{}, errors.New(\"turso: unexpected result kind for stats\")\n\t}\n\tstats, err := turso_sync_operation_result_extract_stats(opFinal)\n\tif err != nil {\n\t\treturn TursoSyncDbStats{}, err\n\t}\n\treturn TursoSyncDbStats{\n\t\tCdcOperations:        stats.CDcOperations,\n\t\tMainWalSize:          stats.MainWalSize,\n\t\tRevertWalSize:        stats.RevertWalSize,\n\t\tLastPullUnixTime:     stats.LastPullUnixTime,\n\t\tLastPushUnixTime:     stats.LastPushUnixTime,\n\t\tNetworkSentBytes:     stats.NetworkSentBytes,\n\t\tNetworkReceivedBytes: stats.NetworkReceivedBytes,\n\t\tRevision:             stats.Revision,\n\t}, nil\n}\n\n// Checkpoint local WAL of the database\nfunc (d *TursoSyncDb) Checkpoint(ctx context.Context) error {\n\td.mu.Lock()\n\tdefer d.mu.Unlock()\n\n\top, err := turso_sync_database_checkpoint(d.db)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, opFinal, err := d.driveOpUntilDone(ctx, op)\n\tif err != nil {\n\t\treturn err\n\t}\n\tturso_sync_operation_deinit(opFinal)\n\treturn nil\n}\n\n// driveOpUntilDone resumes an async operation until completion, serving IO requests as needed.\n// It returns the final result kind and the operation handle that must be deinitialized by the caller.\nfunc (d *TursoSyncDb) driveOpUntilDone(ctx context.Context, op TursoSyncOperation) (TursoSyncOperationResultType, TursoSyncOperation, error) {\n\tfor {\n\t\tif ctx != nil && ctx.Err() != nil {\n\t\t\treturn TURSO_ASYNC_RESULT_NONE, op, ctx.Err()\n\t\t}\n\t\tcode, err := turso_sync_operation_resume(op)\n\t\tif err != nil {\n\t\t\treturn TURSO_ASYNC_RESULT_NONE, op, err\n\t\t}\n\t\tswitch code {\n\t\tcase TURSO_DONE:\n\t\t\treturn turso_sync_operation_result_kind(op), op, nil\n\t\tcase TURSO_IO:\n\t\t\tif err := d.processIoQueue(ctx); err != nil {\n\t\t\t\treturn TURSO_ASYNC_RESULT_NONE, op, err\n\t\t\t}\n\t\t\tcontinue\n\t\tcase TURSO_OK:\n\t\t\t// Just continue\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn TURSO_ASYNC_RESULT_NONE, op, statusToError(code, \"\")\n\t\t}\n\t}\n}\n\n// processOneIo handles at most one IO item (used as extra IO iteration inside SQL driver).\nfunc (d *TursoSyncDb) processOneIo() error {\n\titem, err := turso_sync_database_io_take_item(d.db)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif item == nil {\n\t\t// Still run callbacks to allow engine to progress timers/state.\n\t\treturn turso_sync_database_io_step_callbacks(d.db)\n\t}\n\t_ = d.handleIoItem(context.Background(), item)\n\tturso_sync_database_io_item_deinit(item)\n\treturn turso_sync_database_io_step_callbacks(d.db)\n}\n\n// processIoQueue drains IO queue until it's empty.\nfunc (d *TursoSyncDb) processIoQueue(ctx context.Context) error {\n\tfor {\n\t\tif ctx != nil && ctx.Err() != nil {\n\t\t\treturn ctx.Err()\n\t\t}\n\t\titem, err := turso_sync_database_io_take_item(d.db)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif item == nil {\n\t\t\tbreak\n\t\t}\n\t\t_ = d.handleIoItem(ctx, item)\n\t\tturso_sync_database_io_item_deinit(item)\n\t}\n\treturn turso_sync_database_io_step_callbacks(d.db)\n}\n\nfunc buildHostname(baseURL, namespace string) (string, error) {\n\tu, err := url.Parse(baseURL)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif namespace == \"\" {\n\t\treturn u.Host, nil\n\t}\n\treturn namespace + \".\" + u.Host, nil\n}\n\n// handleIoItem performs execution of a single IO item.\n// It streams data in chunks for HTTP and file operations to avoid loading whole payloads in memory.\nfunc (d *TursoSyncDb) handleIoItem(ctx context.Context, item TursoSyncIoItem) error {\n\tswitch turso_sync_database_io_request_kind(item) {\n\tcase TURSO_SYNC_IO_HTTP:\n\t\treq, err := turso_sync_database_io_request_http(item)\n\t\tif err != nil {\n\t\t\t_ = turso_sync_database_io_poison(item, err.Error())\n\t\t\t_ = turso_sync_database_io_done(item)\n\t\t\treturn err\n\t\t}\n\t\t// Build URL\n\t\tbuildUrl := joinUrl(d.baseURL, req.Path)\n\n\t\t// Build headers\n\t\thdr := make(http.Header, req.Headers+2)\n\t\tfor i := 0; i < req.Headers; i++ {\n\t\t\th, err := turso_sync_database_io_request_http_header(item, i)\n\t\t\tif err != nil {\n\t\t\t\t_ = turso_sync_database_io_poison(item, err.Error())\n\t\t\t\t_ = turso_sync_database_io_done(item)\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif h.Key != \"\" {\n\t\t\t\thdr.Add(h.Key, h.Value)\n\t\t\t}\n\t\t}\n\t\tif d.authToken != \"\" {\n\t\t\thdr.Set(\"Authorization\", \"Bearer \"+d.authToken)\n\t\t}\n\t\t// Propagate sensible defaults\n\t\tif hdr.Get(\"User-Agent\") == \"\" {\n\t\t\thdr.Set(\"User-Agent\", \"turso-sync-go\")\n\t\t}\n\n\t\t// Prepare request body reader\n\t\tvar body io.Reader\n\t\tif len(req.Body) > 0 {\n\t\t\tbody = bytes.NewReader(req.Body)\n\t\t}\n\t\thttpReq, err := http.NewRequestWithContext(ctx, req.Method, buildUrl, body)\n\t\tif err != nil {\n\t\t\t_ = turso_sync_database_io_poison(item, err.Error())\n\t\t\t_ = turso_sync_database_io_done(item)\n\t\t\treturn err\n\t\t}\n\t\thost, err := buildHostname(d.baseURL, d.namespace)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\thttpReq.Host = host\n\t\thttpReq.Header = hdr\n\n\t\tresp, err := d.client.Do(httpReq)\n\t\tif err != nil {\n\t\t\t_ = turso_sync_database_io_poison(item, err.Error())\n\t\t\t_ = turso_sync_database_io_done(item)\n\t\t\treturn err\n\t\t}\n\t\tdefer resp.Body.Close()\n\n\t\t// Send status\n\t\t_ = turso_sync_database_io_status(item, resp.StatusCode)\n\n\t\t// Stream body\n\t\tbuf := make([]byte, 64*1024)\n\t\tfor {\n\t\t\tif ctx != nil && ctx.Err() != nil {\n\t\t\t\t_ = turso_sync_database_io_poison(item, ctx.Err().Error())\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tn, rerr := resp.Body.Read(buf)\n\t\t\tif n > 0 {\n\t\t\t\t// push the exact slice view; underlying call copies bytes synchronously\n\t\t\t\t_ = turso_sync_database_io_push_buffer(item, buf[:n])\n\t\t\t}\n\t\t\tif rerr == io.EOF {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif rerr != nil {\n\t\t\t\t_ = turso_sync_database_io_poison(item, rerr.Error())\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\t_ = turso_sync_database_io_done(item)\n\t\treturn nil\n\n\tcase TURSO_SYNC_IO_FULL_READ:\n\t\tr, err := turso_sync_database_io_request_full_read(item)\n\t\tif err != nil {\n\t\t\t_ = turso_sync_database_io_poison(item, err.Error())\n\t\t\t_ = turso_sync_database_io_done(item)\n\t\t\treturn err\n\t\t}\n\t\tf, ferr := os.Open(r.Path)\n\t\tif errors.Is(ferr, os.ErrNotExist) {\n\t\t\t_ = turso_sync_database_io_done(item)\n\t\t\treturn nil\n\t\t} else if ferr != nil {\n\t\t\t_ = turso_sync_database_io_poison(item, ferr.Error())\n\t\t\t_ = turso_sync_database_io_done(item)\n\t\t\treturn ferr\n\t\t}\n\t\tdefer f.Close()\n\t\tbuf := make([]byte, 64*1024)\n\t\tfor {\n\t\t\tif ctx != nil && ctx.Err() != nil {\n\t\t\t\t_ = turso_sync_database_io_poison(item, ctx.Err().Error())\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tn, rerr := f.Read(buf)\n\t\t\tif n > 0 {\n\t\t\t\t_ = turso_sync_database_io_push_buffer(item, buf[:n])\n\t\t\t}\n\t\t\tif rerr == io.EOF {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif rerr != nil {\n\t\t\t\t_ = turso_sync_database_io_poison(item, rerr.Error())\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\t_ = turso_sync_database_io_done(item)\n\t\treturn nil\n\n\tcase TURSO_SYNC_IO_FULL_WRITE:\n\t\tr, err := turso_sync_database_io_request_full_write(item)\n\t\tif err != nil {\n\t\t\t_ = turso_sync_database_io_poison(item, err.Error())\n\t\t\t_ = turso_sync_database_io_done(item)\n\t\t\treturn err\n\t\t}\n\t\t// Ensure directory exists\n\t\tif dir := filepath.Dir(r.Path); dir != \"\" && dir != \".\" {\n\t\t\t_ = os.MkdirAll(dir, 0o755)\n\t\t}\n\t\t// Write file atomically-ish by writing to a temp and renaming\n\t\ttmp := r.Path + \".tmp\"\n\t\tif werr := os.WriteFile(tmp, r.Content, 0o644); werr != nil {\n\t\t\t_ = turso_sync_database_io_poison(item, werr.Error())\n\t\t\t_ = turso_sync_database_io_done(item)\n\t\t\treturn werr\n\t\t}\n\t\tif rerr := os.Rename(tmp, r.Path); rerr != nil {\n\t\t\t_ = turso_sync_database_io_poison(item, rerr.Error())\n\t\t\t_ = turso_sync_database_io_done(item)\n\t\t\treturn rerr\n\t\t}\n\t\t_ = turso_sync_database_io_done(item)\n\t\treturn nil\n\n\tdefault:\n\t\t// Unknown or none; mark done\n\t\t_ = turso_sync_database_io_done(item)\n\t\treturn nil\n\t}\n}\n\nfunc joinUrl(base, p string) string {\n\tif !strings.HasPrefix(p, \"/\") {\n\t\tp = \"/\" + p\n\t}\n\treturn strings.TrimRight(base, \"/\") + p\n}\n\nfunc normalizeUrl(base string) string {\n\tif cut, ok := strings.CutPrefix(base, \"libsql://\"); ok {\n\t\treturn \"https://\" + cut\n\t}\n\treturn base\n}\n\n// syncDSNOptions holds options parsed from DSN-style path\ntype syncDSNOptions struct {\n\tBusyTimeout int // 0 = not set, >0 = custom, <0 = disabled\n}\n\n// parseSyncDSN parses a DSN-style path like \"mydb.db?_busy_timeout=5000\"\n// and returns the clean path and parsed options.\nfunc parseSyncDSN(dsn string) (string, syncDSNOptions) {\n\tvar opts syncDSNOptions\n\tqMark := strings.IndexByte(dsn, '?')\n\tif qMark < 0 {\n\t\treturn dsn, opts\n\t}\n\tpath := dsn[:qMark]\n\trawQuery := dsn[qMark+1:]\n\tvals, err := url.ParseQuery(rawQuery)\n\tif err != nil {\n\t\treturn dsn, opts // Return original on parse error\n\t}\n\tif v := vals.Get(\"_busy_timeout\"); v != \"\" {\n\t\tvar timeout int\n\t\tif _, err := fmt.Sscanf(v, \"%d\", &timeout); err == nil {\n\t\t\topts.BusyTimeout = timeout\n\t\t}\n\t}\n\treturn path, opts\n}\n"
  },
  {
    "path": "bindings/go/driver_sync_test.go",
    "content": "package turso\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"math/rand\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\n// --- Busy Timeout DSN Parsing Tests (no server required) ---\n\nfunc TestSyncDSNParsing(t *testing.T) {\n\ttests := []struct {\n\t\tname            string\n\t\tdsn             string\n\t\texpectedPath    string\n\t\texpectedTimeout int\n\t}{\n\t\t{\n\t\t\tname:            \"simple path\",\n\t\t\tdsn:             \"mydb.db\",\n\t\t\texpectedPath:    \"mydb.db\",\n\t\t\texpectedTimeout: 0,\n\t\t},\n\t\t{\n\t\t\tname:            \"path with busy timeout\",\n\t\t\tdsn:             \"mydb.db?_busy_timeout=10000\",\n\t\t\texpectedPath:    \"mydb.db\",\n\t\t\texpectedTimeout: 10000,\n\t\t},\n\t\t{\n\t\t\tname:            \"path with negative busy timeout\",\n\t\t\tdsn:             \"mydb.db?_busy_timeout=-1\",\n\t\t\texpectedPath:    \"mydb.db\",\n\t\t\texpectedTimeout: -1,\n\t\t},\n\t\t{\n\t\t\tname:            \"memory db with timeout\",\n\t\t\tdsn:             \":memory:?_busy_timeout=5000\",\n\t\t\texpectedPath:    \":memory:\",\n\t\t\texpectedTimeout: 5000,\n\t\t},\n\t\t{\n\t\t\tname:            \"path with other options\",\n\t\t\tdsn:             \"/path/to/db.db?other=value&_busy_timeout=3000\",\n\t\t\texpectedPath:    \"/path/to/db.db\",\n\t\t\texpectedTimeout: 3000,\n\t\t},\n\t\t{\n\t\t\tname:            \"path with zero timeout\",\n\t\t\tdsn:             \"test.db?_busy_timeout=0\",\n\t\t\texpectedPath:    \"test.db\",\n\t\t\texpectedTimeout: 0,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tpath, opts := parseSyncDSN(tt.dsn)\n\t\t\trequire.Equal(t, tt.expectedPath, path)\n\t\t\trequire.Equal(t, tt.expectedTimeout, opts.BusyTimeout)\n\t\t})\n\t}\n}\n\nfunc TestSyncBusyTimeoutConfigPrecedence(t *testing.T) {\n\t// Test that explicit BusyTimeout in config takes precedence over DSN\n\tt.Run(\"config overrides DSN\", func(t *testing.T) {\n\t\t// This test verifies the logic without actually creating a database\n\t\tconfig := TursoSyncDbConfig{\n\t\t\tPath:        \"test.db?_busy_timeout=1000\",\n\t\t\tBusyTimeout: 5000, // Explicit config should win\n\t\t}\n\n\t\t// Parse DSN like NewTursoSyncDb does\n\t\t_, dsnOpts := parseSyncDSN(config.Path)\n\t\tbusyTimeout := config.BusyTimeout\n\t\tif busyTimeout == 0 {\n\t\t\tif dsnOpts.BusyTimeout != 0 {\n\t\t\t\tbusyTimeout = dsnOpts.BusyTimeout\n\t\t\t}\n\t\t}\n\n\t\t// Explicit config should take precedence\n\t\trequire.Equal(t, 5000, busyTimeout)\n\t})\n\n\tt.Run(\"DSN used when config not set\", func(t *testing.T) {\n\t\tconfig := TursoSyncDbConfig{\n\t\t\tPath:        \"test.db?_busy_timeout=3000\",\n\t\t\tBusyTimeout: 0, // Not set\n\t\t}\n\n\t\t_, dsnOpts := parseSyncDSN(config.Path)\n\t\tbusyTimeout := config.BusyTimeout\n\t\tif busyTimeout == 0 {\n\t\t\tif dsnOpts.BusyTimeout != 0 {\n\t\t\t\tbusyTimeout = dsnOpts.BusyTimeout\n\t\t\t}\n\t\t}\n\n\t\t// DSN timeout should be used\n\t\trequire.Equal(t, 3000, busyTimeout)\n\t})\n\n\tt.Run(\"default used when neither set\", func(t *testing.T) {\n\t\tconfig := TursoSyncDbConfig{\n\t\t\tPath:        \"test.db\",\n\t\t\tBusyTimeout: 0,\n\t\t}\n\n\t\t_, dsnOpts := parseSyncDSN(config.Path)\n\t\tbusyTimeout := config.BusyTimeout\n\t\tif busyTimeout == 0 {\n\t\t\tif dsnOpts.BusyTimeout != 0 {\n\t\t\t\tbusyTimeout = dsnOpts.BusyTimeout\n\t\t\t}\n\t\t}\n\n\t\t// Should be 0 at this point, default applied in Connect()\n\t\trequire.Equal(t, 0, busyTimeout)\n\t})\n}\n\nfunc randomString() string {\n\treturn fmt.Sprintf(\"r-%v\", rand.Intn(1000_000_000))\n}\n\nvar (\n\tAdminUrl = \"http://localhost:8081\"\n\tUserUrl  = \"http://localhost:8080\"\n)\n\ntype TursoServer struct {\n\tDbUrl   string\n\tuserUrl string\n\thost    string\n\tserver  *os.Process\n}\n\n// getFreePort asks the OS for a free port by binding to port 0.\nfunc getFreePort() (int, error) {\n\tl, err := net.Listen(\"tcp\", \"127.0.0.1:0\")\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tport := l.Addr().(*net.TCPAddr).Port\n\tl.Close()\n\treturn port, nil\n}\n\nfunc NewTursoServer() (*TursoServer, error) {\n\tif localSyncServer, ok := os.LookupEnv(\"LOCAL_SYNC_SERVER\"); ok {\n\t\tconst maxRetries = 5\n\t\tvar lastErr error\n\t\tfor attempt := 0; attempt < maxRetries; attempt++ {\n\t\t\tport, err := getFreePort()\n\t\t\tif err != nil {\n\t\t\t\tlastErr = err\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tturso, err := startLocalServer(localSyncServer, port)\n\t\t\tif err != nil {\n\t\t\t\tlastErr = err\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn turso, nil\n\t\t}\n\t\treturn nil, fmt.Errorf(\"failed to start server after %d attempts: %v\", maxRetries, lastErr)\n\t} else {\n\t\tname := randomString()\n\t\terr := handleResponse(http.Post(fmt.Sprintf(\"%v/v1/tenants/%v\", AdminUrl, name), \"application/json\", nil))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\terr = handleResponse(http.Post(fmt.Sprintf(\"%v/v1/tenants/%v/groups/%v\", AdminUrl, name, name), \"application/json\", nil))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\terr = handleResponse(http.Post(fmt.Sprintf(\"%v/v1/tenants/%v/groups/%v/databases/%v\", AdminUrl, name, name, name), \"application/json\", nil))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tuserUrl := strings.Split(UserUrl, \"://\")\n\t\tturso := &TursoServer{\n\t\t\tuserUrl: UserUrl,\n\t\t\tDbUrl:   fmt.Sprintf(\"%v://%v--%v--%v.%v\", userUrl[0], name, name, name, userUrl[1]),\n\t\t\thost:    fmt.Sprintf(\"%v--%v--%v.localhost\", name, name, name),\n\t\t}\n\t\treturn turso, nil\n\t}\n}\n\nfunc startLocalServer(localSyncServer string, port int) (*TursoServer, error) {\n\tserver, err := os.StartProcess(\n\t\tlocalSyncServer,\n\t\t[]string{localSyncServer, \"--sync-server\", fmt.Sprintf(\"0.0.0.0:%v\", port)},\n\t\t&os.ProcAttr{Files: []*os.File{\n\t\t\tos.Stdin,\n\t\t\tos.Stdout,\n\t\t\tos.Stderr,\n\t\t}},\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tturso := &TursoServer{\n\t\tuserUrl: fmt.Sprintf(\"http://localhost:%v\", port),\n\t\tDbUrl:   fmt.Sprintf(\"http://localhost:%v\", port),\n\t\thost:    \"\",\n\t\tserver:  server,\n\t}\n\t// Wait for server to become ready, with timeout and process health check.\n\tdeadline := time.Now().Add(30 * time.Second)\n\texitCh := make(chan error, 1)\n\tgo func() {\n\t\t_, err := server.Wait()\n\t\texitCh <- err\n\t}()\n\tfor {\n\t\tselect {\n\t\tcase exitErr := <-exitCh:\n\t\t\treturn nil, fmt.Errorf(\"server process exited before becoming ready: %v\", exitErr)\n\t\tdefault:\n\t\t}\n\t\tif time.Now().After(deadline) {\n\t\t\tserver.Kill()\n\t\t\treturn nil, fmt.Errorf(\"timed out waiting for server to become ready on port %d\", port)\n\t\t}\n\t\tresp, err := http.Get(turso.userUrl)\n\t\tif err == nil {\n\t\t\tresp.Body.Close()\n\t\t\tbreak\n\t\t}\n\t\ttime.Sleep(10 * time.Millisecond)\n\t}\n\treturn turso, nil\n}\n\nfunc (s *TursoServer) Close() {\n\tif s.server != nil {\n\t\ts.server.Kill()\n\t\ts.server.Wait()\n\t}\n}\n\nfunc handleResponse(response *http.Response, err error) error {\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer response.Body.Close()\n\tbody, err := io.ReadAll(response.Body)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttext := string(body)\n\tif response.StatusCode == 200 || response.StatusCode == 400 && strings.Contains(text, \"already exists\") {\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"http failed: %v %v\", response.StatusCode, text)\n}\n\nfunc (s *TursoServer) DbSql(sql string) ([][]any, error) {\n\tdata := map[string]any{\n\t\t\"requests\": []map[string]any{\n\t\t\t{\"type\": \"execute\", \"stmt\": map[string]any{\"sql\": sql}},\n\t\t},\n\t}\n\tpayload, err := json.Marshal(data)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tr, err := http.NewRequest(\"POST\", fmt.Sprintf(\"%v/v2/pipeline\", s.userUrl), bytes.NewReader(payload))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tr.Host = s.host\n\n\tresponse, err := http.DefaultClient.Do(r)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer response.Body.Close()\n\tif response.StatusCode != 200 {\n\t\treturn nil, fmt.Errorf(\"bad response: %v\", response.StatusCode)\n\t}\n\n\tbody, err := io.ReadAll(response.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar result map[string]any\n\terr = json.Unmarshal(body, &result)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif result[\"results\"].([]any)[0].(map[string]any)[\"type\"] != \"ok\" {\n\t\treturn nil, fmt.Errorf(\"bad response: %+v\", result)\n\t}\n\n\tinner := result[\"results\"].([]any)[0].(map[string]any)[\"response\"].(map[string]any)[\"result\"].(map[string]any)[\"rows\"].([]any)\n\trows := make([][]any, 0)\n\tfor _, innerRow := range inner {\n\t\trow := make([]any, 0)\n\t\tfor _, cell := range innerRow.([]any) {\n\t\t\trow = append(row, cell.(map[string]any)[\"value\"])\n\t\t}\n\t\trows = append(rows, row)\n\t}\n\treturn rows, nil\n}\n\nvar (\n\tSYNC_TEST_RUN = os.Getenv(\"SYNC_TEST_RUN\") == \"true\"\n)\n\nfunc TestSyncBootstrap(t *testing.T) {\n\tserver, err := NewTursoServer()\n\trequire.Nil(t, err)\n\tt.Cleanup(func() { server.Close() })\n\n\t_, err = server.DbSql(\"CREATE TABLE t(x)\")\n\trequire.Nil(t, err)\n\t_, err = server.DbSql(\"INSERT INTO t VALUES ('hello'), ('turso'), ('sync-go')\")\n\trequire.Nil(t, err)\n\n\tdb, err := NewTursoSyncDb(context.Background(), TursoSyncDbConfig{\n\t\tPath:       \":memory:\",\n\t\tClientName: \"turso-sync-go\",\n\t\tRemoteUrl:  server.DbUrl,\n\t})\n\trequire.Nil(t, err)\n\tconn, err := db.Connect(context.Background())\n\trequire.Nil(t, err)\n\trows, err := conn.QueryContext(context.Background(), \"SELECT * FROM t\")\n\trequire.Nil(t, err)\n\tvalues := make([]string, 0)\n\tfor rows.Next() {\n\t\tvar value string\n\t\trequire.Nil(t, rows.Scan(&value))\n\t\tvalues = append(values, value)\n\t}\n\trequire.Equal(t, values, []string{\"hello\", \"turso\", \"sync-go\"})\n}\n\nfunc TestSyncConfigPersistence(t *testing.T) {\n\tserver, err := NewTursoServer()\n\trequire.Nil(t, err)\n\tt.Cleanup(func() { server.Close() })\n\n\t_, err = server.DbSql(\"CREATE TABLE t(x)\")\n\trequire.Nil(t, err)\n\t_, err = server.DbSql(\"INSERT INTO t VALUES (42)\")\n\trequire.Nil(t, err)\n\n\tdir := t.TempDir()\n\tlocal := path.Join(dir, \"local.db\")\n\n\tdb1, err := NewTursoSyncDb(context.Background(), TursoSyncDbConfig{\n\t\tPath:       local,\n\t\tClientName: \"turso-sync-go\",\n\t\tRemoteUrl:  server.DbUrl,\n\t})\n\trequire.Nil(t, err)\n\tconn, err := db1.Connect(context.Background())\n\trequire.Nil(t, err)\n\trows, err := conn.QueryContext(context.Background(), \"SELECT * FROM t\")\n\trequire.Nil(t, err)\n\tvalues := make([]int64, 0)\n\tfor rows.Next() {\n\t\tvar value int64\n\t\trequire.Nil(t, rows.Scan(&value))\n\t\tvalues = append(values, value)\n\t}\n\trequire.Equal(t, values, []int64{42})\n\trows.Close()\n\tconn.Close()\n\n\t_, err = server.DbSql(\"INSERT INTO t VALUES (41)\")\n\trequire.Nil(t, err)\n\n\tdb2, err := NewTursoSyncDb(context.Background(), TursoSyncDbConfig{\n\t\tPath:      local,\n\t\tRemoteUrl: server.DbUrl,\n\t})\n\trequire.Nil(t, err)\n\n\t_, err = db2.Pull(context.Background())\n\trequire.Nil(t, err)\n\n\tconn, err = db2.Connect(context.Background())\n\trequire.Nil(t, err)\n\trows, err = conn.QueryContext(context.Background(), \"SELECT * FROM t\")\n\trequire.Nil(t, err)\n\tvalues = make([]int64, 0)\n\tfor rows.Next() {\n\t\tvar value int64\n\t\trequire.Nil(t, rows.Scan(&value))\n\t\tvalues = append(values, value)\n\t}\n\trequire.Equal(t, values, []int64{42, 41})\n}\n\nfunc TestSyncBootstrapPersistent(t *testing.T) {\n\tserver, err := NewTursoServer()\n\trequire.Nil(t, err)\n\tt.Cleanup(func() { server.Close() })\n\n\t_, err = server.DbSql(\"CREATE TABLE t(x)\")\n\trequire.Nil(t, err)\n\t_, err = server.DbSql(\"INSERT INTO t VALUES ('hello'), ('turso'), ('sync-go')\")\n\trequire.Nil(t, err)\n\n\tdir, err := os.MkdirTemp(\".\", \"test-sync-\")\n\trequire.Nil(t, err)\n\tt.Cleanup(func() { os.RemoveAll(dir) })\n\n\tdb, err := NewTursoSyncDb(context.Background(), TursoSyncDbConfig{\n\t\tPath:      filepath.Join(dir, \"local.db\"),\n\t\tRemoteUrl: server.DbUrl,\n\t})\n\trequire.Nil(t, err)\n\tconn, err := db.Connect(context.Background())\n\trequire.Nil(t, err)\n\trows, err := conn.QueryContext(context.Background(), \"SELECT * FROM t\")\n\trequire.Nil(t, err)\n\tvalues := make([]string, 0)\n\tfor rows.Next() {\n\t\tvar value string\n\t\trequire.Nil(t, rows.Scan(&value))\n\t\tvalues = append(values, value)\n\t}\n\trequire.Equal(t, values, []string{\"hello\", \"turso\", \"sync-go\"})\n}\n\nfunc TestSyncPull(t *testing.T) {\n\tserver, err := NewTursoServer()\n\trequire.Nil(t, err)\n\tt.Cleanup(func() { server.Close() })\n\n\t_, err = server.DbSql(\"CREATE TABLE t(x)\")\n\trequire.Nil(t, err)\n\t_, err = server.DbSql(\"INSERT INTO t VALUES ('hello'), ('turso'), ('sync-go')\")\n\trequire.Nil(t, err)\n\n\tdb, err := NewTursoSyncDb(context.Background(), TursoSyncDbConfig{\n\t\tPath:       \":memory:\",\n\t\tClientName: \"turso-sync-go\",\n\t\tRemoteUrl:  server.DbUrl,\n\t})\n\trequire.Nil(t, err)\n\tconn, err := db.Connect(context.Background())\n\trequire.Nil(t, err)\n\n\t_, err = server.DbSql(\"INSERT INTO t VALUES ('pull-works')\")\n\trequire.Nil(t, err)\n\n\trows, err := conn.QueryContext(context.Background(), \"SELECT * FROM t\")\n\trequire.Nil(t, err)\n\tvalues := make([]string, 0)\n\tfor rows.Next() {\n\t\tvar value string\n\t\trequire.Nil(t, rows.Scan(&value))\n\t\tvalues = append(values, value)\n\t}\n\trequire.Equal(t, values, []string{\"hello\", \"turso\", \"sync-go\"})\n\trows.Close()\n\n\tchanges, err := db.Pull(context.Background())\n\trequire.Nil(t, err)\n\trequire.True(t, changes)\n\n\tchanges, err = db.Pull(context.Background())\n\trequire.Nil(t, err)\n\trequire.False(t, changes)\n\n\trows, err = conn.QueryContext(context.Background(), \"SELECT * FROM t\")\n\trequire.Nil(t, err)\n\tvalues = make([]string, 0)\n\tfor rows.Next() {\n\t\tvar value string\n\t\trequire.Nil(t, rows.Scan(&value))\n\t\tvalues = append(values, value)\n\t}\n\trequire.Equal(t, values, []string{\"hello\", \"turso\", \"sync-go\", \"pull-works\"})\n\trows.Close()\n}\n\nfunc TestSyncPullDoNotPush(t *testing.T) {\n\tserver, err := NewTursoServer()\n\trequire.Nil(t, err)\n\tt.Cleanup(func() { server.Close() })\n\n\t_, err = server.DbSql(\"CREATE TABLE t(x)\")\n\trequire.Nil(t, err)\n\t_, err = server.DbSql(\"INSERT INTO t VALUES ('hello'), ('turso'), ('sync-go')\")\n\trequire.Nil(t, err)\n\n\tdb, err := NewTursoSyncDb(context.Background(), TursoSyncDbConfig{\n\t\tPath:       \":memory:\",\n\t\tClientName: \"turso-sync-go\",\n\t\tRemoteUrl:  server.DbUrl,\n\t})\n\trequire.Nil(t, err)\n\tconn, err := db.Connect(context.Background())\n\trequire.Nil(t, err)\n\n\t_, err = server.DbSql(\"INSERT INTO t VALUES ('pull-works')\")\n\trequire.Nil(t, err)\n\n\trows, err := conn.QueryContext(context.Background(), \"SELECT * FROM t\")\n\trequire.Nil(t, err)\n\tvalues := make([]string, 0)\n\tfor rows.Next() {\n\t\tvar value string\n\t\trequire.Nil(t, rows.Scan(&value))\n\t\tvalues = append(values, value)\n\t}\n\trequire.Equal(t, values, []string{\"hello\", \"turso\", \"sync-go\"})\n\trows.Close()\n\n\t_, err = conn.Exec(\"INSERT INTO t VALUES ('push-is-local')\")\n\trequire.Nil(t, err)\n\n\tchanges, err := db.Pull(context.Background())\n\trequire.Nil(t, err)\n\trequire.True(t, changes)\n\n\tchanges, err = db.Pull(context.Background())\n\trequire.Nil(t, err)\n\trequire.False(t, changes)\n\n\trows, err = conn.QueryContext(context.Background(), \"SELECT * FROM t\")\n\trequire.Nil(t, err)\n\tvalues = make([]string, 0)\n\tfor rows.Next() {\n\t\tvar value string\n\t\trequire.Nil(t, rows.Scan(&value))\n\t\tvalues = append(values, value)\n\t}\n\trequire.Equal(t, values, []string{\"hello\", \"turso\", \"sync-go\", \"pull-works\", \"push-is-local\"})\n\trows.Close()\n\n\tremote, err := server.DbSql(\"SELECT * FROM t\")\n\trequire.Nil(t, err)\n\trequire.Equal(t, remote, [][]any{{\"hello\"}, {\"turso\"}, {\"sync-go\"}, {\"pull-works\"}})\n}\n\nfunc TestSyncPush(t *testing.T) {\n\tserver, err := NewTursoServer()\n\trequire.Nil(t, err)\n\tt.Cleanup(func() { server.Close() })\n\n\t_, err = server.DbSql(\"CREATE TABLE t(x)\")\n\trequire.Nil(t, err)\n\t_, err = server.DbSql(\"INSERT INTO t VALUES ('hello'), ('turso'), ('sync-go')\")\n\trequire.Nil(t, err)\n\n\tdb, err := NewTursoSyncDb(context.Background(), TursoSyncDbConfig{\n\t\tPath:       \":memory:\",\n\t\tClientName: \"turso-sync-go\",\n\t\tRemoteUrl:  server.DbUrl,\n\t})\n\trequire.Nil(t, err)\n\tconn, err := db.Connect(context.Background())\n\trequire.Nil(t, err)\n\n\t_, err = conn.Exec(\"INSERT INTO t VALUES ('push-works')\")\n\trequire.Nil(t, err)\n\n\trows, err := server.DbSql(\"SELECT * FROM t\")\n\trequire.Nil(t, err)\n\trequire.Equal(t, rows, [][]any{{\"hello\"}, {\"turso\"}, {\"sync-go\"}})\n\n\trequire.Nil(t, db.Push(context.Background()))\n\n\trows, err = server.DbSql(\"SELECT * FROM t\")\n\trequire.Nil(t, err)\n\trequire.Equal(t, rows, [][]any{{\"hello\"}, {\"turso\"}, {\"sync-go\"}, {\"push-works\"}})\n}\n\nfunc TestSyncCheckpoint(t *testing.T) {\n\tserver, err := NewTursoServer()\n\trequire.Nil(t, err)\n\tt.Cleanup(func() { server.Close() })\n\n\tdb, err := NewTursoSyncDb(context.Background(), TursoSyncDbConfig{\n\t\tPath:       \":memory:\",\n\t\tClientName: \"turso-sync-go\",\n\t\tRemoteUrl:  server.DbUrl,\n\t})\n\trequire.Nil(t, err)\n\tconn, err := db.Connect(context.Background())\n\trequire.Nil(t, err)\n\n\t_, err = conn.Exec(\"CREATE TABLE t(x)\")\n\trequire.Nil(t, err)\n\n\tfor i := 0; i < 1024; i++ {\n\t\t_, err = conn.Exec(fmt.Sprintf(\"INSERT INTO t VALUES (%v)\", i))\n\t\trequire.Nil(t, err)\n\t}\n\n\tstats1, err := db.Stats(context.Background())\n\trequire.Nil(t, err)\n\trequire.Nil(t, db.Checkpoint(context.Background()))\n\tstats2, err := db.Stats(context.Background())\n\trequire.Nil(t, err)\n\n\trequire.Greater(t, stats1.MainWalSize, int64(1024*1024))\n\trequire.Equal(t, stats1.RevertWalSize, int64(0))\n\n\trequire.Equal(t, stats2.MainWalSize, int64(0))\n\trequire.Less(t, stats2.RevertWalSize, int64(8*1024))\n\n\trequire.Nil(t, db.Push(context.Background()))\n\n\trows, err := server.DbSql(\"SELECT SUM(x) FROM t\")\n\trequire.Nil(t, err)\n\trequire.Equal(t, rows, [][]any{{fmt.Sprintf(\"%v\", 1024*1023/2)}})\n}\n\nfunc TestSyncPartial(t *testing.T) {\n\tserver, err := NewTursoServer()\n\trequire.Nil(t, err)\n\tt.Cleanup(func() { server.Close() })\n\n\t_, err = server.DbSql(\"CREATE TABLE t(x)\")\n\trequire.Nil(t, err)\n\t_, err = server.DbSql(\"INSERT INTO t SELECT randomblob(1024) FROM generate_series(1, 1024)\")\n\trequire.Nil(t, err)\n\n\tdb, err := NewTursoSyncDb(context.Background(), TursoSyncDbConfig{\n\t\tPath:       \":memory:\",\n\t\tClientName: \"turso-sync-go\",\n\t\tRemoteUrl:  server.DbUrl,\n\t\tPartialSyncExperimental: TursoPartialSyncConfig{\n\t\t\tBootstrapStrategyPrefix: 128 * 1024,\n\t\t},\n\t})\n\trequire.Nil(t, err)\n\tconn, err := db.Connect(context.Background())\n\trequire.Nil(t, err)\n\n\trows, err := conn.QueryContext(context.Background(), \"SELECT LENGTH(x) FROM t LIMIT 1\")\n\trequire.Nil(t, err)\n\tvalues := make([]int, 0)\n\tfor rows.Next() {\n\t\tvar value int\n\t\trequire.Nil(t, rows.Scan(&value))\n\t\tvalues = append(values, value)\n\t}\n\trequire.Equal(t, values, []int{1024})\n\trows.Close()\n\n\tstats1, err := db.Stats(context.Background())\n\trequire.Nil(t, err)\n\trequire.Less(t, stats1.NetworkReceivedBytes, int64(256*1024))\n\n\trows, err = conn.QueryContext(context.Background(), \"SELECT SUM(LENGTH(x)) FROM t\")\n\trequire.Nil(t, err)\n\tvalues = make([]int, 0)\n\tfor rows.Next() {\n\t\tvar value int\n\t\trequire.Nil(t, rows.Scan(&value))\n\t\tvalues = append(values, value)\n\t}\n\trequire.Equal(t, values, []int{1024 * 1024})\n\trows.Close()\n\n\tstats2, err := db.Stats(context.Background())\n\trequire.Nil(t, err)\n\trequire.Greater(t, stats2.NetworkReceivedBytes, int64(1024*1024))\n}\n\n// TestSyncLargeSchema tests syncing a database where the schema table spans multiple pages.\n// This reproduces a bug where make_from_btree does blocking IO during database opening,\n// but the caller (sync engine) has no way to spin the external IO loop to fetch\n// overflow pages that aren't loaded yet.\nfunc TestSyncLargeSchema(t *testing.T) {\n\tserver, err := NewTursoServer()\n\trequire.Nil(t, err)\n\tt.Cleanup(func() { server.Close() })\n\n\t// Create many tables with long column definitions to make sqlite_schema span multiple pages.\n\t// Each CREATE TABLE statement will be stored in the schema table.\n\t// SQLite page usable space is ~4000 bytes, so creating tables with ~500 byte definitions\n\t// means we need ~8+ tables to overflow to another page.\n\tnumTables := 50\n\tfor i := 0; i < numTables; i++ {\n\t\t// Create a table with many columns to make a long CREATE statement (~1KB each)\n\t\tcolumns := \"\"\n\t\tfor j := 0; j < 20; j++ {\n\t\t\tif j > 0 {\n\t\t\t\tcolumns += \", \"\n\t\t\t}\n\t\t\tcolumns += fmt.Sprintf(\"column_%d_%d_with_a_very_long_name_to_increase_size INTEGER DEFAULT 0\", i, j)\n\t\t}\n\t\tsql := fmt.Sprintf(\"CREATE TABLE large_schema_table_%d (%s)\", i, columns)\n\t\t_, err = server.DbSql(sql)\n\t\trequire.Nil(t, err)\n\t}\n\n\t// Insert some data into the first table\n\t_, err = server.DbSql(\"INSERT INTO large_schema_table_0 (column_0_0_with_a_very_long_name_to_increase_size) VALUES (42)\")\n\trequire.Nil(t, err)\n\n\t// Use partial sync with a small bootstrap prefix to ensure not all schema pages are fetched upfront.\n\t// The bug manifests when opening the database: make_from_btree needs to read the full schema,\n\t// but overflow pages for the schema table aren't loaded yet, and blocking IO can't be driven\n\t// by the sync engine's coroutine-based IO loop.\n\tdb, err := NewTursoSyncDb(context.Background(), TursoSyncDbConfig{\n\t\tPath:       \":memory:\",\n\t\tClientName: \"turso-sync-go\",\n\t\tRemoteUrl:  server.DbUrl,\n\t\tPartialSyncExperimental: TursoPartialSyncConfig{\n\t\t\t// Use a small prefix to ensure we don't fetch all schema pages upfront\n\t\t\tBootstrapStrategyPrefix: 8 * 1024,\n\t\t},\n\t})\n\trequire.Nil(t, err)\n\n\tconn, err := db.Connect(context.Background())\n\trequire.Nil(t, err)\n\n\t// Verify we can query one of the tables created with the large schema\n\trows, err := conn.QueryContext(context.Background(), \"SELECT column_0_0_with_a_very_long_name_to_increase_size FROM large_schema_table_0\")\n\trequire.Nil(t, err)\n\tvalues := make([]int, 0)\n\tfor rows.Next() {\n\t\tvar value int\n\t\trequire.Nil(t, rows.Scan(&value))\n\t\tvalues = append(values, value)\n\t}\n\trequire.Equal(t, values, []int{42})\n\trows.Close()\n\n\t// Also verify that all tables are accessible in the schema\n\trows, err = conn.QueryContext(context.Background(), \"SELECT COUNT(*) FROM sqlite_schema WHERE type='table' AND name LIKE 'large_schema_table_%'\")\n\trequire.Nil(t, err)\n\tvar count int\n\trequire.True(t, rows.Next())\n\trequire.Nil(t, rows.Scan(&count))\n\trequire.Equal(t, count, numTables)\n\trows.Close()\n}\n"
  },
  {
    "path": "bindings/go/go-bindings-db-tests.mdx",
    "content": "---\nname: 2025-11-26-go-bindings-test\n---\n\n<Output path=\"./bindings_db_test.go\">\n\n<Code model=\"openai/gpt-5\" language=\"go\">\n\nGenerate tests for Go functions bindings for the Turso - SQLite-compatible embedded database written in Rust.\n\n# Bindings\n\nYou must generate tests for the bindings translated from C interface defined in turso.h file:\n\n<File path=\"../../sdk-kit/turso.h\" />\n\n<File path=\"./bindings_db.go\" />\n\n# Implemention\n\n- Assert conditions with \"github.com/stretchr/testify\"\n- Make tests short, concise and independent. Explicitly assert results - do not create any \"mocks\"\n- Prefer to use \":memory:\" db in order to not manage files\n\n# Test cases\n\nGenerate tests which will cover generic use of SQL. \n**Non exhaustive** list of things to check:\n- Subqueries\n- INSERT ... RETURNING ...\n    * Make additional test case for scenario, where multiple values were inserted, but only one row were fetch\n    * Make sure that in this case transaction will be properly commited even when not all rows were consumed\n- CONFLICT clauses (and how driver inform caller about conflict)\n- Basic DDL statements (CREATE/DELETE)\n- More complex DDL statements (ALTER TABLE)\n- Builtin virtual tables (generate_series)\n- JOIN\n- JSON functions\n\n</Code>\n\n</Output>"
  },
  {
    "path": "bindings/go/go-bindings-db.mdx",
    "content": "---\nname: 2025-11-26-go-bindings\n---\n\n<Output path=\"./bindings_db.go\">\n\n<Code model=\"openai/gpt-5\" language=\"go\">\n\nGenerate Go functions bindings for the Turso - SQLite-compatible embedded database written in Rust.\nYou must use C API bridge implemented in Rust which expose ABI compatible functions to interact with the database.\n\nThe main goal is to translate C code to Go-friendly bindings which will have no additional logic, but more ergonomic API:\n1. Return (err error) when possible\n2. Manipulate with strings accordingly\n\n# Bindings\n\nYou must generate bindings for C interface defined in turso.h file:\n\n<File path=\"../../sdk-kit/turso.h\" />\n\n# Rules\n\nGeneral rules for driver implementation you **MUST** follow and never go against these rules:\n- DO NOT USE cgo, USE purego instead\n- DO NOT introduce any new public methods - export turso.h content as-is\n- DO NOT register library - this will be done externally\n- AVOID unnecessary FFI calls as their cost is non zero\n- AVOID unnecessary strings transformations - replace them with more efficient alternatives if possible\n- DO NOT ever mix `turso_statement_execute` and `turso_statement_step` methods: every statement must be either \"stepped\" or \"executed\"\n    * This is because `execute` ignores all rows\n- SQL query can be arbitrary, be very careful writing the code which relies on properties derived from the simple string analysis\n- FOCUS on code readability: if possible extract helper function but make sure that it will be used more than once and that it really contribute to the code readability\n- WATCH OUT for variables scopes and do not use variables which are no longer accessible\n- WATCH OUT for string representations: in some cases library expected zero terminated C-string and in some cases library operates with string/byte slices\n- BE AWARE, that purego marshal only explicit parameters of the function calls. If you have C struct passed by reference - you need to do marshalling by yourself (e.g. convert strings to zero-terminated C strings, etc)\n- STRUCTURE of the implementation \n    * Declaration order of elements and semantic blocks MUST be exsactly the same\n    * (details and full enumerations omited in the example for brevity but you must generate full code)\n```go\npackage turso\n\n// define all package level errors here\n\n// note, that OK, DONE, ROW, IO are statuses - so you don't need to create errors for them\nvar (\n    TursoBusyErr = errors.New(\"turso: database is busy\") // provide short but concise error message\n    ...\n)\n\n// define all necessary constants first\ntype TursoStatusCode int32\n\n// note, that the only real statuses are OK, DONE, ROW, IO - everything else is errors\nconst (\n\tTURSO_OK TursoStatusCode = 0\n    ...\n)\n\ntype TursoType int32\n\nconst (\n\tTURSO_TYPE_INTEGER TursoType = 1\n    ...\n)\n\n// define opaque pointers as-is and accept them as exact arguments (e.g. func turso_database_connect(self TursoDatabase) ... - DO NOT add extra indirection)\ntype TursoDatabase *turso_database_t\n\n// define all public binding types\n// the public binding types MUST have fields with native safe go types \ntype TursoConfig struct { ... }\n\n// define all necessary private C structs\n// private C structs MUST have fields with low level types (e.g. uintptr, numbers)\ntype turso_config_t struct { ... }\n\n// then, define C extern methods\nvar (\n    // always use c_ structs here - never mix them with exported public types\n\tc_turso_setup                                 func(config turso_config_t) turso_status_code_t\n    ...\n)\n\n// implement a function to register extern methods from loaded lib\n// DO NOT load lib - as it will be done externally\nfunc register_turso_db(handle uintptr) error {\n    purego.RegisterLibFunc(&c_turso_setup, handle, \"turso_setup\")\n    ...\n}\n\n// Go wrappers over imported C bindings\n// always use exported public types here - never mix them with c_ structs\nfunc turso_setup(config TursoConig) error { ... }\n```\n\n\n# Implementation\n\n- The package name is \"github.com/tursodatabase/turso/go\"\n- Use exactly same names as in turso.h for Go method names and prepend `c_` prefix for C extern functions\n- Separate \"public\" types from \"private\" types:\n    * public types MUST be used ONLY in Go bindings methods (`turso_.*`) and have fields with native safe go types (string, slice, etc)\n    * public types MUST NOT use low level types explicitly (e.g. unintptr) - they must be either replaced with wrapper public type or native alternative must be used instead (e.g. int instead of uinptrt if this is size_t)\n    * private types MUST be used ONLY in C wrapper functions (`c_turso_.*`) and have fields with low-level C-compatible go types (uintptr, numbers, etc)\n- Document generated methods with docstrings\n- Replace `turso_status_code_t` and out error parameter with proper native golang `error`\n    * If function returns only OK or error code, do not return status explicitly from the Go binding - just return error (and any value if it was returned as out parameter)\n- Convert C-strings back and forth appropriately\n    * Remember, that Golang strings are not null-terminated - so you will need to convert them to zero-terminated strings by copying data in some cases\n- Support callback in turso_setup with purego.NewCallback\n- Make one exception and implement `turso_statement_row_value_bytes` and `turso_statement_row_value_text` methods which will use `c_turso_statement_row_value_bytes_ptr` and `c_turso_statement_row_value_bytes_count` extern methods under the hood\n    * This MUST be the only non-direct translation of C extern functions\n\n# Purego\n\nInspect following docstring from the official purego repository in order to understand how marshalling works:\n\n<Link url=\"https://raw.githubusercontent.com/ebitengine/purego/refs/heads/main/func.go\" selector=\"RegisterFunc#comment\" />\n<Link url=\"https://raw.githubusercontent.com/ebitengine/purego/refs/heads/main/func.go\" selector=\"RegisterLibFunc#comment\" />\n<Link url=\"https://raw.githubusercontent.com/ebitengine/purego/refs/heads/main/syscall_sysv.go\" selector=\"NewCallback#comment\" />\n\n</Code>\n\n</Output>"
  },
  {
    "path": "bindings/go/go-bindings-sync.mdx",
    "content": "---\nname: 2025-11-26-go-bindings-sync\n---\n\n<Output path=\"./bindings_sync.go\">\n\n<Code model=\"openai/gpt-5\" language=\"go\">\n\nGenerate Go functions bindings for the sync engine built on top of Turso - SQLite-compatible embedded database written in Rust.\nYou must use C API bridge implemented in Rust which expose ABI compatible functions to interact with the database.\n\nThe main goal is to translate C code to Go-friendly bindings which will have no additional logic, but more ergonomic API:\n1. Return (err error) when possible\n2. Manipulate with strings accordingly\n\n# Bindings\n\nYou must generate bindings for C interface defined in turso_sync.h file.\n\n<File path=\"../../sync/sdk-kit/turso_sync.h\" />\n\nNote, that this header file depends on the turso.h definitions for which Go bindings already exists.\nYou MUST just reuse them. DO NOT copy or reimplement these bindings:\n\n<File path=\"../../sdk-kit/turso.h\" />\n<File path=\"./bindings_db.go\" />\n\n# Rules\n\nGeneral rules for driver implementation you **MUST** follow and never go against these rules:\n- DO NOT USE cgo, USE purego instead\n- DO NOT introduce any new public methods - export turso.h content as-is\n- DO NOT register library - this will be done externally\n- AVOID unnecessary FFI calls as their cost is non zero\n- AVOID unnecessary strings transformations - replace them with more efficient alternatives if possible\n- FOCUS on code readability: if possible extract helper function but make sure that it will be used more than once and that it really contribute to the code readability\n- WATCH OUT for variables scopes and do not use variables which are no longer accessible\n- WATCH OUT for string representations: in some cases library expected zero terminated C-string and in some cases library operates with string/byte slices\n- BE AWARE, that purego marshal only explicit parameters of the function calls. If you have C struct passed by reference - you need to do marshalling by yourself (e.g. convert strings to zero-terminated C strings, etc)\n- STRUCTURE of the implementation \n    * Declaration order of elements and semantic blocks MUST be exsactly the same\n    * (details and full enumerations omited in the example for brevity but you must generate full code)\n```go\npackage turso\n\n// define all package level errors here\n\n// define opaque pointers as-is and accept them as exact arguments (e.g. func turso_database_connect(self TursoDatabase) ... - DO NOT add extra indirection)\ntype TursoSyncDatabase *turso_sync_database_t\n\n// define all public binding types\n// the public binding types MUST have fields with native safe go types \ntype TursoSyncDatabaseConfig struct { ... }\n\n// define all necessary private C structs\n// private C structs MUST have fields with low level types (e.g. uintptr, numbers)\ntype turso_sync_database_config_t struct { ... }\n\n// then, define C extern methods\nvar (\n    // always use c_ structs here - never mix them with exported public types\n\tc_turso_sync_database_new func(\n\t\tdbConfig *c_turso_database_config_t,\n\t\tsyncConfig *c_turso_sync_database_config_t,\n\t\tdatabase unsafe.Pointer,\n\t\terrorOptOut unsafe.Pointer,\n\t) c_turso_status_code_t\n    ...\n)\n\n// imiplement a function to register extern methods from loaded lib\n// DO NOT load lib - as it will be done externally\nfunc register_turso_sync(handle uintptr) error {\n    purego.RegisterLibFunc(&c_turso_sync_database_new, handle, \"turso_sync_database_new\")\n    ...\n}\n\n// Go wrappers over imported C bindings\n// always use exported public types here - never mix them with c_ structs\nfunc turso_sync_database_new(...) ... { ... }\n```\n\n\n# Implementation\n\n- The package name is \"github.com/tursodatabase/turso/go\"\n- Use exactly same names as in turso_sync.h for Go method names and prepend `c_` prefix for C extern functions\n- Separate \"public\" types from \"private\" types:\n    * public types MUST be used ONLY in Go bindings methods (`turso_sync_.*`) and have fields with native safe go types (string, slice, etc)\n    * public types MUST NOT use low level types explicitly (e.g. unintptr) - they must be either replaced with wrapper public type or native alternative must be used instead (e.g. int instead of uinptrt if this is size_t)\n    * private types MUST be used ONLY in C wrapper functions (`c_turso_sync_.*`) and have fields with low-level C-compatible go types (uintptr, numbers, etc)\n- Document generated methods with docstrings\n- Replace `turso_status_code_t` and out error parameter with proper native golang `error`\n- Convert C-strings back and forth appropriately\n    * Remember, that Golang strings are not null-terminated - so you will need to convert them to zero-terminated strings by copying data in some cases\n\n\n# Purego\n\nInspect following docstring from the official purego repository in order to understand how marshalling works:\n\n<Link url=\"https://raw.githubusercontent.com/ebitengine/purego/refs/heads/main/func.go\" selector=\"RegisterFunc#comment\" />\n<Link url=\"https://raw.githubusercontent.com/ebitengine/purego/refs/heads/main/func.go\" selector=\"RegisterLibFunc#comment\" />\n\n</Code>\n\n</Output>"
  },
  {
    "path": "bindings/go/go-driver-db.mdx",
    "content": "---\nname: 2025-11-26-go-bindings\n---\n\n<Output path=\"./driver_db.go\">\n\n<Code model=\"openai/gpt-5\" language=\"go\">\n\nGenerate Go SDK for the Turso - SQLite-compatible embedded database written in Rust.\nYou must use bindings_db.go bridge from Go to C API.\n\n# Bindings\n\n<File path=\"./bindings_db.go\" />\n\n# Rules\n\n- PREPARE statement in Prepare - do not delay that\n    * Consequently, you MUST properly implement NumInputs() method for statement using `turso_statement_parameters_count` method\n- NEVER reset non-failed statement which wasn't finalized before that - this will abort all progress which is wrong semantic\n- USE `BEGIN` always as SQLite and Turso doesn't have other transaction modes except from SNAPSHOT ISOLATION\n- WATCH OUT for locks and BE CAREFUL with methods calling each other and locking same mutex\n- DO NOT USE `uint64(^int64(0))` - use `math.MaxInt64` instead\n- DO NOT USE `_ interface{ Done() <-chan struct{} }` - use `ctx context.Context` instead\n- USE simple control flow - carefully manage state and locks - but try to avoid complex wrapper high-order functions (withLock, etc)\n- STRUCTURE of the implementation \n    * Declaration order of elements and semantic blocks MUST be exsactly the same\n    * (details and full enumerations omited in the example for brevity but you must generate full code)\n```go\npackage turso\n\n// define all package level errors here\nvar (\n    TursoStmtClosedErr = errors.New(\"turso: statement closed\") // provide short but concise error message\n    ...\n)\n\n// define all package level structs here\nstruct tursoDbDriver { ... }\n\n// register driver\nfunc init() {\n\tsql.Register(\"turso\", &tursoDbDriver{})\n}\n\n// Extra constructor for *tursoDbConnection instance which can be used to intergrate with turso Db driver\n// extr_io parameter is the arbitrary IO function which will be executed together with turso_statement_run_io\nfunc NewConnection(conn TursoConnection, extraIo func() error) *tursoDbConnection {\n    \n}\n\n// Implement sql.Driver methods\n...\n\n```\n\n\n# Implementation\n\nThe SDK should has following components under the hood:\n* `type tursoDbConnection struct { }` - wrapper which holds connection to the turso and protect it against concurrent use\n* `type tursoDbStatement struct { }` - wrapper which holds prepared statement to the turso and protect it against concurrent use\n* `type tursoDbRows struct { }` - wrapper which holds prepared statement and provide sqld/database compatible methods to iterate over rows of the statement\n* `type tursoDbDriver struct { }` - type to register in the sql/database as driver\n* `type tursoDbResult struct { }` - type implementing `driver.Result` interface\n* `type tursoDbTx struct { }` - type implementing `driver.Tx` interface\n* Support following DSN format: `<path>[?experimental=<string>&async=0|1]`\n* Implement multi-statement support for `Exec*` family of methods. Use `turso_connection_prepare_first` method for that\n\n\n# Go driver API\n\nInspect go sql driver documentation and follow it during implementation:\n\n<Shell cmd=\"go doc -all sql\" />\n\n</Code>\n\n</Output>"
  },
  {
    "path": "bindings/go/go-driver-sync.mdx",
    "content": "---\nname: 2025-11-26-go-bindings-sync\n---\n\n<Output path=\"./driver_sync.go\">\n\n<Code model=\"openai/gpt-5\" language=\"go\">\n\nTurso - is the SQLite compatible database written in Rust.\nOne of the important features of the Turso - is native ability to sync database with the Cloud in both directions (push local changes and pull remote changes).\n\nYour task is to generate EXTRA functionality on top of the existing Golang driver which will extend regular embedded with sync capability.\nDo not modify existing driver - its already implemented in the driver_db.go (based on bindings_db.go)\nYour task is to write extra code which will use abstractions driver_db.go + bindings_db.go and build sync support in the Gollang on top of it in the driver_sync.go file.\n\n# Rules\n\nGeneral rules for driver implementation you **MUST** follow and never go against these rules:\n- USE already implemented driver - DO NOT copy it\n- SET async_io=True for the driver database configuration - because partial sync support requires TURSO_IO to handled externally from the bindings\n- ADD context.Context in the methods when this make sense and we have control over method API\n- STRUCTURE of the implementation \n    * Declaration order of elements and semantic blocks MUST be exsactly the same\n    * (details and full enumerations omited in the example for brevity but you must generate full code)\n```go\npackage turso\n\nstruct TursoSyncDbConfig {\n    // path to the main database file locally\n    Path string\n    \n    // remote url for the sync\n    // remote_url MUST be used in all sync engine operations: during bootstrap and all further operations\n    RemoteUrl string\n    \n    // token for remote authentication\n    // auth token value WILL not have any prefix and must be used as \"Authorization\" header prepended with \"Bearer \" prefix\n    AuthToken string\n    \n    // optional unique client name (library MUST use `turso-sync-go` if omitted)\n    ClientName string\n\n    // long polling timeout\n    LongPollTimeoutMs int\n    \n    // if not set, initial bootstrap phase will be skipped and caller must call .pull(...) explicitly in order to get initial state from remote\n    // default value is true\n    BootstrapIfEmpty *bool\n    \n    // if positive, prefix partial bootstrap strategy will be used\n    PartialBootstrapStrategyPrefix int\n    // if not empty, query partial bootstrap strategy will be used\n    PartialBootstrapStrategyQuery string\n    \n    // pass it as-is to the underlying connection\n    ExperimentalFeatures string\n}\n\n// statistics for the synced database.\ntype TursoSyncDbStats struct {\n    // amount of local operations written since last Pull(...) call\n\tCdcOperations        int64\n    // size of the main WAL file\n\tMainWalSize          int64\n    // size of the revert WAL file\n\tRevertWalSize        int64\n    // last successful pull time\n\tLastPullUnixTime     int64\n    // last successful push time\n\tLastPushUnixTime     int64\n    // total amount of bytes sent over the network (both Push and Pull operations are tracked together)\n\tNetworkSentBytes     int64\n    // total amount of bytes received over the network (both Push and Pull operations are tracked together)\n\tNetworkReceivedBytes int64\n    // opaque server revision - it MUST NOT be interpreted/parsed in any way\n\tRevision             string\n}\n\n// define public structs here\nstruct TursoSyncDb { ... }\n\n// main constructor to create synced database\nfunc NewTursoSyncDb(ctx context.Context, config TursoSyncDbConfig) (*TursoSyncDb, error) { ... }\n\n// create turso db local connnection\n\n// internal connector to integrate with database/sql pool\ntype tursoSyncConnector struct { db *TursoSyncDb }\nfunc (c *tursoSyncConnector) Connect(ctx context.Context) (driver.Conn, error) { ... }\nfunc (c *tursoSyncConnector) Driver() driver.Driver { return &tursoDbDriver{} }\n\n// create tursodb connection using NewConnection(...) from driver_db.go and tursoSyncConnector helper\nfunc (d *TursoSyncDb) Connect(ctx context.Context) (*sql.DB, error) { ... }\n\n// implement EXTRA sync methods\n\n// Pull fresh data from the remote\n// Pull DO NOT sent any local changes to the remote and instead \"rebase\" them on top of new changes from remote\n// Return true, if new changes were applied locally - otherwise return false\nfunc (d *TursoSyncDb) Pull(ctx context.Context) (bool, error) { ... }\n\n// Push local changes to the remote\n// Push DO NOT fetch any remote changes\nfunc (d *TursoSyncDb) Push(ctx context.Context) error { ... }\n\n// Get stats for the synced database\nfunc (d *TursoSyncDb) Stats(ctx context.Context) (TursoSyncDbStats, error) { ... }\n\n// Checkpoint local WAL of the database\nfunc (d *TursoSyncDb) Checkpoint(ctx context.Context) error { ... }\n```\n- STREAM data from the http request to the completion in chunks and spin async operation in between in order to prevent loading whole response in memory\n- AVOID unnecessary FFI calls as their cost is non zero\n- AVOID unnecessary strings transformations - replace them with more efficient alternatives if possible\n- AVOID cryptic names - prefer short but concise names (wr is BAD, full_write is GOOD)\n- FOCUS on code readability: extract helper functions if it will contribute to the code readability (do not overoptimize - it's fine to have some logic inlined especially if it is not repeated anywhere else)\n- WATCH OUT for variables scopes and do not use variables which are no longer accessible\n\n# Implementation\n\n- Annotate public API with types\n- Add comments about public API fields/functions to clarify meaning and usage scenarios\n- Use `turso_sync_database_create()` method for creation of the synced database for now - DO NOT use init + open pair\n\n# Bindings\n\nYou must use bindings in the `bindings_sync.go` and intergrate with `driver_db.go` code (use `NewConnection` function for that)\nInspect `bindings_db.go` as you will reuse some abstractions from there.\n\n<File path=\"./bindings_sync.go\" />\n<File path=\"./bindings_db.go\" />\n<File path=\"./driver_db.go\" />\n\n</Code>\n\n</Output>"
  },
  {
    "path": "bindings/go/go.mod",
    "content": "module turso.tech/database/tursogo\n\ngo 1.24.0\n\ntoolchain go1.24.10\n\nrequire (\n\tgithub.com/ebitengine/purego v0.9.1\n\tgithub.com/stretchr/testify v1.11.1\n\tgithub.com/tursodatabase/turso-go-platform-libs v0.0.0-20251210190052-57d6c2f7db38\n)\n\nrequire (\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgolang.org/x/sys v0.38.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n)\n"
  },
  {
    "path": "bindings/go/go.sum",
    "content": "github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=\ngithub.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/tursodatabase/turso-go-platform-libs v0.0.0-20251210075532-b81eb47907db h1:BwgDmz73+tIagiVlbbZTJVWehMVMnxmN0kHWgL0lvQ0=\ngithub.com/tursodatabase/turso-go-platform-libs v0.0.0-20251210075532-b81eb47907db/go.mod h1:bo+Lpv5OYOX1gRV9L5DLKMsYxmDs56SkZwnCOLEFcxU=\ngithub.com/tursodatabase/turso-go-platform-libs v0.0.0-20251210190052-57d6c2f7db38 h1:W+4IfZunKyz6NtCVovUsZ0ni/xsd9iworxbQs+3usF4=\ngithub.com/tursodatabase/turso-go-platform-libs v0.0.0-20251210190052-57d6c2f7db38/go.mod h1:bo+Lpv5OYOX1gRV9L5DLKMsYxmDs56SkZwnCOLEFcxU=\ngolang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=\ngolang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "bindings/java/.editorconfig",
    "content": "root = true\n\n[*.java]\nindent_size = 2\nij_continuation_indent_size = 4\n"
  },
  {
    "path": "bindings/java/.gitignore",
    "content": ".gradle\nbuild/\n!gradle/wrapper/gradle-wrapper.jar\n!**/src/main/**/build/\n!**/src/test/**/build/\n\n### IntelliJ IDEA ###\n.idea/\n*.iws\n*.iml\n*.ipr\nout/\n!**/src/main/**/out/\n!**/src/test/**/out/\n\n### Eclipse ###\n.apt_generated\n.classpath\n.factorypath\n.project\n.settings\n.springBeans\n.sts4-cache\nbin/\n!**/src/main/**/bin/\n!**/src/test/**/bin/\n**/debug/**\n\n### NetBeans ###\n/nbproject/private/\n/nbbuild/\n/dist/\n/nbdist/\n/.nb-gradle/\n\n### VS Code ###\n.vscode/\n\n### Mac OS ###\n.DS_Store\n\n### turso builds ###\nlibs\ntemp\n\n### Rust build artifacts ###\n.rustc_info.json"
  },
  {
    "path": "bindings/java/.sdkmanrc",
    "content": "java=8.0.452-amzn\n"
  },
  {
    "path": "bindings/java/Cargo.toml",
    "content": "[package]\nname = \"turso-java\"\nversion.workspace = true\nauthors.workspace = true\nedition.workspace = true\nlicense.workspace = true\nrepository.workspace = true\npublish = false\n\n[lints]\nworkspace = true\n\n[features]\ntracing_release = [\"turso_core/tracing_release\"]\n[lib]\nname = \"_turso_java\"\ncrate-type = [\"cdylib\"]\npath = \"rs_src/lib.rs\"\n\n[dependencies]\nturso_core = { workspace = true, features = [\"io_uring\"] }\njni = \"0.21.1\"\nthiserror = { workspace = true }\n"
  },
  {
    "path": "bindings/java/Makefile",
    "content": "RELEASE_DIR := libs\nTEMP_DIR := temp\n\nCARGO_BUILD := cargo build --profile release-official\n\nMACOS_X86_DIR := $(RELEASE_DIR)/macos_x86\nMACOS_ARM64_DIR := $(RELEASE_DIR)/macos_arm64\nWINDOWS_DIR := $(RELEASE_DIR)/windows\nLINUX_X86_DIR := $(RELEASE_DIR)/linux_x86\n\n.PHONY: libs macos_x86 macos_arm64 windows lint lint_apply test build_test\n\nlibs: macos_x86 macos_arm64 windows linux_x86\n\nmacos_x86:\n\t@echo \"Building release version for macOS x86_64...\"\n\t@mkdir -p $(TEMP_DIR) $(MACOS_X86_DIR)\n\t@CARGO_TARGET_DIR=$(TEMP_DIR) $(CARGO_BUILD) --target x86_64-apple-darwin\n\t@cp $(TEMP_DIR)/x86_64-apple-darwin/release-official/lib_turso_java.dylib $(MACOS_X86_DIR)\n\t@rm -rf $(TEMP_DIR)\n\nmacos_arm64:\n\t@echo \"Building release version for macOS ARM64...\"\n\t@mkdir -p $(TEMP_DIR) $(MACOS_ARM64_DIR)\n\t@CARGO_TARGET_DIR=$(TEMP_DIR) $(CARGO_BUILD) --target aarch64-apple-darwin\n\t@cp $(TEMP_DIR)/aarch64-apple-darwin/release-official/lib_turso_java.dylib $(MACOS_ARM64_DIR)\n\t@rm -rf $(TEMP_DIR)\n\n# windows generates file with name `_turso_java.dll` unlike others, so we manually add prefix\nwindows:\n\t@echo \"Building release version for Windows...\"\n\t@mkdir -p $(TEMP_DIR) $(WINDOWS_DIR)\n\t@CARGO_TARGET_DIR=$(TEMP_DIR) $(CARGO_BUILD) --target x86_64-pc-windows-gnu\n\t@cp $(TEMP_DIR)/x86_64-pc-windows-gnu/release-official/_turso_java.dll $(WINDOWS_DIR)/lib_turso_java.dll\n\t@rm -rf $(TEMP_DIR)\n\nlinux_x86:\n\t@echo \"Building release version for linux x86_64...\"\n\t@mkdir -p $(TEMP_DIR) $(LINUX_X86_DIR)\n\t@CARGO_TARGET_DIR=$(TEMP_DIR) $(CARGO_BUILD) --target x86_64-unknown-linux-gnu\n\t@cp $(TEMP_DIR)/x86_64-unknown-linux-gnu/release-official/lib_turso_java.so $(LINUX_X86_DIR)\n\t@rm -rf $(TEMP_DIR)\n\nlint:\n\t./gradlew spotlessCheck\n\nlint_apply:\n\t./gradlew spotlessApply\n\ntest: lint build_test\n\t./gradlew test\n\nbuild_test:\n\tCARGO_TARGET_DIR=src/test/resources/turso cargo build\n\npublish_local:\n\t./gradlew clean publishToMavenLocal\n"
  },
  {
    "path": "bindings/java/README.md",
    "content": "# Turso JDBC Driver\n\nThe Turso JDBC driver is a library for accessing and creating Turso database files using Java.\n\n## Project Status\n\nThe project is actively developed. Feel free to open issues and contribute.\n\nTo view related works, visit this [issue](https://github.com/tursodatabase/turso/issues/615).\n\n## How to use\n\nCurrently, we have not published to the maven central. Instead, you can locally build the jar and deploy it to\nmaven local to use it.\n\n### Build jar and publish to maven local\n\n```shell\n$ cd bindings/java\n\n# Please select the appropriate target platform, currently supports `macos_x86`, `macos_arm64`, `windows` and `linux_x86`\n$ make macos_x86\n\n# deploy to maven local\n$ make publish_local\n```\n\nNow you can use the dependency as follows:\n\n```kotlin\ndependencies {\n    implementation(\"tech.turso:turso:0.0.1-SNAPSHOT\")\n}\n```\n\n## Code style\n\n- Favor composition over inheritance. For example, `JDBC4Connection` doesn't implement `TursoConnection`. Instead,\n  it includes `TursoConnection` as a field. This approach allows us to preserve the characteristics of Turso using\n  `TursoConnection` easily while maintaining interoperability with the Java world using `JDBC4Connection`.\n"
  },
  {
    "path": "bindings/java/build.gradle.kts",
    "content": "import net.ltgt.gradle.errorprone.CheckSeverity\nimport net.ltgt.gradle.errorprone.errorprone\nimport org.gradle.api.tasks.testing.logging.TestExceptionFormat\nimport org.gradle.api.tasks.testing.logging.TestLogEvent\n\nplugins {\n    java\n    application\n    `java-library`\n    `maven-publish`\n    signing\n    id(\"net.ltgt.errorprone\") version \"3.1.0\"\n\n    // If you're stuck on JRE 8, use id 'com.diffplug.spotless' version '6.13.0' or older.\n    id(\"com.diffplug.spotless\") version \"6.13.0\"\n}\n\n// Apply publishing configuration\napply(from = \"gradle/publish.gradle.kts\")\n\n// Helper function to read properties with defaults\nfun prop(key: String, default: String? = null): String? =\n    findProperty(key)?.toString() ?: default\n\ngroup = prop(\"projectGroup\") ?: error(\"projectGroup must be set in gradle.properties\")\nversion = prop(\"projectVersion\") ?: error(\"projectVersion must be set in gradle.properties\")\n\njava {\n    sourceCompatibility = JavaVersion.VERSION_1_8\n    targetCompatibility = JavaVersion.VERSION_1_8\n    withJavadocJar()\n    withSourcesJar()\n}\n\n// TODO: Add javadoc to required class and methods. After that, let's remove this settings\ntasks.withType<Javadoc> {\n    options {\n        (this as StandardJavadocDocletOptions).apply {\n            addStringOption(\"Xdoclint:none\", \"-quiet\")\n        }\n    }\n}\n\nrepositories {\n    mavenCentral()\n}\n\ndependencies {\n    compileOnly(\"org.slf4j:slf4j-api:1.7.32\")\n\n    errorprone(\"com.uber.nullaway:nullaway:0.10.26\") // maximum version which supports java 8\n    errorprone(\"com.google.errorprone:error_prone_core:2.10.0\") // maximum version which supports java 8\n\n    testImplementation(platform(\"org.junit:junit-bom:5.10.0\"))\n    testImplementation(\"org.junit.jupiter:junit-jupiter\")\n    testImplementation(\"org.assertj:assertj-core:3.27.0\")\n}\n\napplication {\n    val tursoSystemLibraryPath = System.getenv(\"TURSO_LIBRARY_PATH\")\n    if (tursoSystemLibraryPath != null) {\n        applicationDefaultJvmArgs = listOf(\n            \"-Djava.library.path=${System.getProperty(\"java.library.path\")}:$tursoSystemLibraryPath\"\n        )\n    }\n}\n\ntasks.jar {\n    from(\"libs\") {\n        into(\"libs\")\n    }\n}\n\nsourceSets {\n    test {\n        resources {\n            file(\"src/main/resource/turso-jdbc.properties\")\n        }\n    }\n}\n\ntasks.test {\n    useJUnitPlatform()\n    // In order to find rust built file under resources, we need to set it as system path\n    systemProperty(\n        \"java.library.path\",\n        \"${System.getProperty(\"java.library.path\")}:$projectDir/src/test/resources/turso/debug\"\n    )\n\n    // For our fancy test logging\n    testLogging {\n        // set options for log level LIFECYCLE\n        events(\n            TestLogEvent.FAILED,\n            TestLogEvent.PASSED,\n            TestLogEvent.SKIPPED,\n            TestLogEvent.STANDARD_OUT\n        )\n        exceptionFormat = TestExceptionFormat.FULL\n        showExceptions = true\n        showCauses = true\n        showStackTraces = true\n\n        // set options for log level DEBUG and INFO\n        debug {\n            events(\n                TestLogEvent.STARTED,\n                TestLogEvent.FAILED,\n                TestLogEvent.PASSED,\n                TestLogEvent.SKIPPED,\n                TestLogEvent.STANDARD_ERROR,\n                TestLogEvent.STANDARD_OUT\n            )\n            exceptionFormat = TestExceptionFormat.FULL\n        }\n        info.events = debug.events\n        info.exceptionFormat = debug.exceptionFormat\n\n        afterSuite(KotlinClosure2<TestDescriptor, TestResult, Unit>({ desc, result ->\n            if (desc.parent == null) { // will match the outermost suite\n                val output = \"Results: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} passed, ${result.failedTestCount} failed, ${result.skippedTestCount} skipped)\"\n                val startItem = \"|  \"\n                val endItem = \"  |\"\n                val repeatLength = startItem.length + output.length + endItem.length\n                println(\"\\n\" + \"-\".repeat(repeatLength) + \"\\n\" + startItem + output + endItem + \"\\n\" + \"-\".repeat(repeatLength))\n            }\n        }))\n    }\n}\n\ntasks.withType<JavaCompile> {\n    options.errorprone {\n        // Let's select which checks to perform. NullAway is enough for now.\n        disableAllChecks = true\n        check(\"NullAway\", CheckSeverity.ERROR)\n\n        option(\"NullAway:AnnotatedPackages\", \"tech.turso\")\n        option(\n            \"NullAway:CustomNullableAnnotations\",\n            \"tech.turso.annotations.Nullable,tech.turso.annotations.SkipNullableCheck\"\n        )\n    }\n    if (name.lowercase().contains(\"test\")) {\n        options.errorprone {\n            disable(\"NullAway\")\n        }\n    }\n}\n\nspotless {\n    java {\n        target(\"**/*.java\")\n        targetExclude(layout.buildDirectory.dir(\"**/*.java\").get().asFile)\n        targetExclude(\"example/**/*.java\")\n        targetExclude(\"src/main/java/examples/**/*.java\")\n        removeUnusedImports()\n        googleJavaFormat(\"1.7\") // or use eclipse().configFile(\"path/to/eclipse-format.xml\")\n    }\n}\n\n// Task to run the encryption example\ntasks.register<JavaExec>(\"runEncryptionExample\") {\n    group = \"examples\"\n    description = \"Run the local database encryption example\"\n    classpath = sourceSets[\"main\"].runtimeClasspath\n    mainClass.set(\"examples.EncryptionExample\")\n\n    // Set the library path for native bindings\n    // Check multiple possible locations for the native library\n    val limboRoot = projectDir.parentFile.parentFile\n    val tursoLibraryPath = System.getenv(\"TURSO_LIBRARY_PATH\")\n        ?: listOf(\n            \"${projectDir}/src/main/resources/libs/macos_arm64\",\n            \"${projectDir}/src/main/resources/libs/linux_x64\",\n            \"${projectDir}/build/resources/main/libs/macos_arm64\",\n            \"${projectDir}/build/resources/main/libs/linux_x64\",\n            \"${limboRoot}/target/debug\",\n            \"${limboRoot}/target/release\"\n        ).joinToString(\":\")\n    jvmArgs = listOf(\"-Djava.library.path=${System.getProperty(\"java.library.path\")}:$tursoLibraryPath\")\n}\n"
  },
  {
    "path": "bindings/java/example/.gitignore",
    "content": ".gradle\nbuild/\n!gradle/wrapper/gradle-wrapper.jar\n!**/src/main/**/build/\n!**/src/test/**/build/\nsample.db\nsample.db-wal\n\n### IntelliJ IDEA ###\n.idea\n"
  },
  {
    "path": "bindings/java/example/build.gradle.kts",
    "content": "plugins {\n    id(\"java\")\n}\n\ngroup = \"tech.turso\"\nversion = \"1.0-SNAPSHOT\"\n\nrepositories {\n    mavenLocal()\n    mavenCentral()\n}\n\ndependencies {\n    implementation(\"tech.turso:turso:0.0.1-SNAPSHOT\")\n    testImplementation(platform(\"org.junit:junit-bom:5.10.0\"))\n    testImplementation(\"org.junit.jupiter:junit-jupiter\")\n}\n\ntasks.test {\n    useJUnitPlatform()\n}\n\ntasks.register<JavaExec>(\"run\") {\n    group = \"application\"\n    classpath = sourceSets[\"main\"].runtimeClasspath\n    mainClass.set(\"tech.turso.Main\")\n}\n"
  },
  {
    "path": "bindings/java/example/gradle/wrapper/gradle-wrapper.properties",
    "content": "#Sun Feb 02 20:06:51 KST 2025\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.10-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "bindings/java/example/gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\nAPP_HOME=$( cd \"${APP_HOME:-./}\" && pwd -P ) || exit\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=${0##*/}\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n# Collect all arguments for the java command;\n#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of\n#     shell script including quotes and variable substitutions, so put them in\n#     double quotes to make sure that they get re-expanded; and\n#   * put everything else in single quotes, so that it's not re-expanded.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        org.gradle.wrapper.GradleWrapperMain \\\n        \"$@\"\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "bindings/java/example/gradlew.bat",
    "content": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\n@rem you may not use this file except in compliance with the License.\n@rem You may obtain a copy of the License at\n@rem\n@rem      https://www.apache.org/licenses/LICENSE-2.0\n@rem\n@rem Unless required by applicable law or agreed to in writing, software\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n@rem See the License for the specific language governing permissions and\n@rem limitations under the License.\n@rem\n\n@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto execute\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto execute\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "bindings/java/example/settings.gradle.kts",
    "content": "rootProject.name = \"example\"\n\n"
  },
  {
    "path": "bindings/java/example/src/main/java/tech.turso/Main.java",
    "content": "package tech.turso;\n\nimport java.sql.Connection;\nimport java.sql.DriverManager;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\n\npublic class Main {\n\n  public static void main(String[] args) throws SQLException {\n    try (Connection conn = DriverManager.getConnection(\"jdbc:turso:sample.db\")) {\n      try (Statement stmt =\n          conn.createStatement(\n              ResultSet.TYPE_FORWARD_ONLY,\n              ResultSet.CONCUR_READ_ONLY,\n              ResultSet.CLOSE_CURSORS_AT_COMMIT)) {\n        stmt.execute(\"CREATE TABLE users (id INT PRIMARY KEY, username TEXT);\");\n        stmt.execute(\"INSERT INTO users VALUES (1, 'turso');\");\n        stmt.execute(\"INSERT INTO users VALUES (2, 'turso');\");\n        stmt.execute(\"INSERT INTO users VALUES (3, 'who knows');\");\n        stmt.execute(\"SELECT * FROM users\");\n        System.out.println(\n            \"result: \" + stmt.getResultSet().getInt(1) + \", \" + stmt.getResultSet().getString(2));\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "bindings/java/gradle/publish.gradle.kts",
    "content": "import java.security.MessageDigest\nimport org.gradle.api.publish.PublishingExtension\nimport org.gradle.api.publish.maven.MavenPublication\nimport org.gradle.plugins.signing.SigningExtension\n\n// Helper function to read properties with defaults\nfun prop(key: String, default: String? = null): String? =\n    project.findProperty(key)?.toString() ?: default\n\n// Maven Publishing Configuration\nconfigure<PublishingExtension> {\n    publications {\n        create<MavenPublication>(\"mavenJava\") {\n            from(components[\"java\"])\n            groupId = prop(\"projectGroup\")!!\n            artifactId = prop(\"projectArtifactId\")!!\n            version = prop(\"projectVersion\")!!\n\n            pom {\n                name.set(prop(\"pomName\"))\n                description.set(prop(\"pomDescription\"))\n                url.set(prop(\"pomUrl\"))\n\n                licenses {\n                    license {\n                        name.set(prop(\"pomLicenseName\"))\n                        url.set(prop(\"pomLicenseUrl\"))\n                    }\n                }\n\n                developers {\n                    developer {\n                        id.set(prop(\"pomDeveloperId\"))\n                        name.set(prop(\"pomDeveloperName\"))\n                        email.set(prop(\"pomDeveloperEmail\"))\n                    }\n                }\n\n                scm {\n                    connection.set(prop(\"pomScmConnection\"))\n                    developerConnection.set(prop(\"pomScmDeveloperConnection\"))\n                    url.set(prop(\"pomScmUrl\"))\n                }\n            }\n        }\n    }\n}\n\n// GPG Signing Configuration\nconfigure<SigningExtension> {\n    // Make signing required for publishing\n    setRequired(true)\n\n    // For CI/GitHub Actions: use in-memory keys\n    val signingKey = providers.environmentVariable(\"MAVEN_SIGNING_KEY\").orNull\n    val signingPassword = providers.environmentVariable(\"MAVEN_SIGNING_PASSPHRASE\").orNull\n\n    if (signingKey != null && signingPassword != null) {\n        // CI mode: use in-memory keys\n        useInMemoryPgpKeys(signingKey, signingPassword)\n    } else {\n        // Local mode: use GPG command from system\n        useGpgCmd()\n    }\n\n    sign(the<PublishingExtension>().publications[\"mavenJava\"])\n}\n\n// Helper task to generate checksums\nval generateChecksums by tasks.registering {\n    dependsOn(\"jar\", \"sourcesJar\", \"javadocJar\", \"generatePomFileForMavenJavaPublication\")\n\n    val checksumDir = layout.buildDirectory.dir(\"checksums\")\n\n    doLast {\n        val files = listOf(\n            tasks.named(\"jar\").get().outputs.files.singleFile,\n            tasks.named(\"sourcesJar\").get().outputs.files.singleFile,\n            tasks.named(\"javadocJar\").get().outputs.files.singleFile,\n            layout.buildDirectory.file(\"publications/mavenJava/pom-default.xml\").get().asFile\n        )\n\n        checksumDir.get().asFile.mkdirs()\n\n        files.forEach { file ->\n            if (file.exists()) {\n                // MD5\n                val md5 = MessageDigest.getInstance(\"MD5\")\n                    .digest(file.readBytes())\n                    .joinToString(\"\") { \"%02x\".format(it) }\n                file(\"${file.absolutePath}.md5\").writeText(md5)\n\n                // SHA1\n                val sha1 = MessageDigest.getInstance(\"SHA-1\")\n                    .digest(file.readBytes())\n                    .joinToString(\"\") { \"%02x\".format(it) }\n                file(\"${file.absolutePath}.sha1\").writeText(sha1)\n            }\n        }\n    }\n}\n\n// Task to create a bundle zip for Maven Central Portal\nval createMavenCentralBundle by tasks.registering(Zip::class) {\n    group = \"publishing\"\n    description = \"Creates a bundle zip for Maven Central Portal upload\"\n\n    dependsOn(\"generatePomFileForMavenJavaPublication\", \"jar\", \"sourcesJar\", \"javadocJar\", \"signMavenJavaPublication\", generateChecksums)\n\n    // Ensure signing happens before bundle creation\n    mustRunAfter(\"signMavenJavaPublication\")\n\n    val groupId = prop(\"projectGroup\")!!.replace(\".\", \"/\")\n    val artifactId = prop(\"projectArtifactId\")!!\n    val projectVer = project.version.toString()\n\n    // Validate version is not SNAPSHOT for Maven Central\n    doFirst {\n        if (projectVer.contains(\"SNAPSHOT\")) {\n            throw GradleException(\n                \"Cannot publish SNAPSHOT version to Maven Central. \" +\n                \"Please change projectVersion in gradle.properties to a release version (e.g., 0.0.1)\"\n            )\n        }\n    }\n\n    archiveFileName.set(\"$artifactId-$projectVer-bundle.zip\")\n    destinationDirectory.set(layout.buildDirectory.dir(\"maven-central\"))\n\n    // Maven Central expects files in groupId/artifactId/version/ structure\n    val basePath = \"$groupId/$artifactId/$projectVer\"\n\n    // Main JAR + checksums + signature\n    from(tasks.named(\"jar\").get().outputs.files) {\n        into(basePath)\n        rename { \"$artifactId-$projectVer.jar\" }\n    }\n    from(tasks.named(\"jar\").get().outputs.files.singleFile.absolutePath + \".md5\") {\n        into(basePath)\n        rename { \"$artifactId-$projectVer.jar.md5\" }\n    }\n    from(tasks.named(\"jar\").get().outputs.files.singleFile.absolutePath + \".sha1\") {\n        into(basePath)\n        rename { \"$artifactId-$projectVer.jar.sha1\" }\n    }\n\n    // Sources JAR + checksums + signature\n    from(tasks.named(\"sourcesJar\").get().outputs.files) {\n        into(basePath)\n        rename { \"$artifactId-$projectVer-sources.jar\" }\n    }\n    from(tasks.named(\"sourcesJar\").get().outputs.files.singleFile.absolutePath + \".md5\") {\n        into(basePath)\n        rename { \"$artifactId-$projectVer-sources.jar.md5\" }\n    }\n    from(tasks.named(\"sourcesJar\").get().outputs.files.singleFile.absolutePath + \".sha1\") {\n        into(basePath)\n        rename { \"$artifactId-$projectVer-sources.jar.sha1\" }\n    }\n\n    // Javadoc JAR + checksums + signature\n    from(tasks.named(\"javadocJar\").get().outputs.files) {\n        into(basePath)\n        rename { \"$artifactId-$projectVer-javadoc.jar\" }\n    }\n    from(tasks.named(\"javadocJar\").get().outputs.files.singleFile.absolutePath + \".md5\") {\n        into(basePath)\n        rename { \"$artifactId-$projectVer-javadoc.jar.md5\" }\n    }\n    from(tasks.named(\"javadocJar\").get().outputs.files.singleFile.absolutePath + \".sha1\") {\n        into(basePath)\n        rename { \"$artifactId-$projectVer-javadoc.jar.sha1\" }\n    }\n\n    // POM + checksums + signature\n    from(layout.buildDirectory.file(\"publications/mavenJava/pom-default.xml\")) {\n        into(basePath)\n        rename { \"$artifactId-$projectVer.pom\" }\n    }\n    from(layout.buildDirectory.file(\"publications/mavenJava/pom-default.xml\").get().asFile.absolutePath + \".md5\") {\n        into(basePath)\n        rename { \"$artifactId-$projectVer.pom.md5\" }\n    }\n    from(layout.buildDirectory.file(\"publications/mavenJava/pom-default.xml\").get().asFile.absolutePath + \".sha1\") {\n        into(basePath)\n        rename { \"$artifactId-$projectVer.pom.sha1\" }\n    }\n\n    // Signature files - get them from the signing task outputs\n    doFirst {\n        val signingTask = tasks.named(\"signMavenJavaPublication\").get()\n        logger.lifecycle(\"Signing task outputs: ${signingTask.outputs.files.files}\")\n    }\n\n    // Include signature files generated by the signing plugin\n    from(tasks.named(\"signMavenJavaPublication\").get().outputs.files) {\n        into(basePath)\n        include(\"*.jar.asc\", \"pom-default.xml.asc\")\n        exclude(\"module.json.asc\") // Exclude gradle module metadata signature\n        rename { name ->\n            // Only rename the POM signature file\n            // JAR signatures are already correctly named by the signing plugin\n            if (name == \"pom-default.xml.asc\") {\n                \"$artifactId-$projectVer.pom.asc\"\n            } else {\n                name // Keep original name (already correct)\n            }\n        }\n    }\n}\n\n// Task to upload bundle to Maven Central Portal\ntasks.register(\"publishToMavenCentral\") {\n    group = \"publishing\"\n    description = \"Publishes artifacts to Maven Central Portal\"\n\n    // Run publish first to generate signatures, then create bundle\n    dependsOn(\"publish\")\n    dependsOn(createMavenCentralBundle)\n\n    // Make sure bundle creation happens after publish\n    createMavenCentralBundle.get().mustRunAfter(\"publish\")\n\n    doLast {\n        val username = providers.environmentVariable(\"MAVEN_UPLOAD_USERNAME\").orNull\n        val password = providers.environmentVariable(\"MAVEN_UPLOAD_PASSWORD\").orNull\n        val bundleFile = createMavenCentralBundle.get().archiveFile.get().asFile\n\n        require(username != null) { \"MAVEN_UPLOAD_USERNAME environment variable must be set\" }\n        require(password != null) { \"MAVEN_UPLOAD_PASSWORD environment variable must be set\" }\n        require(bundleFile.exists()) { \"Bundle file does not exist: ${bundleFile.absolutePath}\" }\n\n        logger.lifecycle(\"Uploading bundle to Maven Central Portal...\")\n        logger.lifecycle(\"Bundle: ${bundleFile.absolutePath}\")\n        logger.lifecycle(\"Size: ${bundleFile.length() / 1024} KB\")\n\n        // Use curl for uploading (simple and available on most systems)\n        exec {\n            commandLine(\n                \"curl\",\n                \"-X\", \"POST\",\n                \"-u\", \"$username:$password\",\n                \"-F\", \"bundle=@${bundleFile.absolutePath}\",\n                \"https://central.sonatype.com/api/v1/publisher/upload?name=${bundleFile.name}&publishingType=AUTOMATIC\"\n            )\n        }\n\n        logger.lifecycle(\"Upload completed. Check https://central.sonatype.com/publishing for status.\")\n    }\n}\n"
  },
  {
    "path": "bindings/java/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.10-bin.zip\nnetworkTimeout=10000\nvalidateDistributionUrl=true\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "bindings/java/gradle.properties",
    "content": "projectGroup=tech.turso\nprojectVersion=0.6.0-pre.4\nprojectArtifactId=turso\n\n# POM metadata\npomName=Turso JDBC Driver\npomDescription=Turso JDBC driver for Java applications\npomUrl=https://github.com/tursodatabase/turso\npomLicenseName=MIT License\npomLicenseUrl=https://opensource.org/licenses/MIT\npomDeveloperId=turso\npomDeveloperName=Turso\npomDeveloperEmail=penberg@iki.fi\npomScmConnection=scm:git:git://github.com/tursodatabase/turso.git\npomScmDeveloperConnection=scm:git:ssh://github.com:tursodatabase/turso.git\npomScmUrl=https://github.com/tursodatabase/turso"
  },
  {
    "path": "bindings/java/gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n# SPDX-License-Identifier: Apache-2.0\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\n# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)\nAPP_HOME=$( cd -P \"${APP_HOME:-./}\" > /dev/null && printf '%s\n' \"$PWD\" ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    if ! command -v java >/dev/null 2>&1\n    then\n        die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command:\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,\n#     and any embedded shellness will be escaped.\n#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be\n#     treated as '${Hostname}' itself on the command line.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        org.gradle.wrapper.GradleWrapperMain \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "bindings/java/gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n@rem SPDX-License-Identifier: Apache-2.0\r\n@rem\r\n\r\n@if \"%DEBUG%\"==\"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\r\n@rem This is normally unused\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif %ERRORLEVEL% equ 0 goto execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\r\n\r\ngoto fail\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif %ERRORLEVEL% equ 0 goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nset EXIT_CODE=%ERRORLEVEL%\r\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\r\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\r\nexit /b %EXIT_CODE%\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "bindings/java/rs_src/errors.rs",
    "content": "use jni::errors::{Error, JniError};\nuse thiserror::Error;\n\n#[derive(Debug, Error)]\npub enum TursoError {\n    #[error(\"Custom error: `{0}`\")]\n    CustomError(String),\n\n    #[error(\"Invalid database pointer\")]\n    InvalidDatabasePointer,\n\n    #[error(\"Invalid connection pointer\")]\n    InvalidConnectionPointer,\n\n    #[error(\"JNI Errors: `{0}`\")]\n    JNIErrors(Error),\n}\n\nimpl From<turso_core::LimboError> for TursoError {\n    fn from(_value: turso_core::LimboError) -> Self {\n        todo!()\n    }\n}\n\nimpl From<TursoError> for JniError {\n    fn from(value: TursoError) -> Self {\n        match value {\n            TursoError::CustomError(_)\n            | TursoError::InvalidDatabasePointer\n            | TursoError::InvalidConnectionPointer\n            | TursoError::JNIErrors(_) => {\n                eprintln!(\"Error occurred: {value:?}\");\n                JniError::Other(-1)\n            }\n        }\n    }\n}\n\nimpl From<jni::errors::Error> for TursoError {\n    fn from(value: jni::errors::Error) -> Self {\n        TursoError::JNIErrors(value)\n    }\n}\n\npub type Result<T> = std::result::Result<T, TursoError>;\n\npub const SQLITE_OK: i32 = 0; // Successful result\npub const SQLITE_ERROR: i32 = 1; // Generic error\n#[allow(dead_code)]\npub const SQLITE_INTERNAL: i32 = 2; // Internal logic error in SQLite\n#[allow(dead_code)]\npub const SQLITE_PERM: i32 = 3; // Access permission denied\n#[allow(dead_code)]\npub const SQLITE_ABORT: i32 = 4; // Callback routine requested an abort\n#[allow(dead_code)]\npub const SQLITE_BUSY: i32 = 5; // The database file is locked\n#[allow(dead_code)]\npub const SQLITE_LOCKED: i32 = 6; // A table in the database is locked\n#[allow(dead_code)]\npub const SQLITE_NOMEM: i32 = 7; // A malloc() failed\n#[allow(dead_code)]\npub const SQLITE_READONLY: i32 = 8; // Attempt to write a readonly database\n#[allow(dead_code)]\npub const SQLITE_INTERRUPT: i32 = 9; // Operation terminated by sqlite3_interrupt()\n#[allow(dead_code)]\npub const SQLITE_IOERR: i32 = 10; // Some kind of disk I/O error occurred\n#[allow(dead_code)]\npub const SQLITE_CORRUPT: i32 = 11; // The database disk image is malformed\n#[allow(dead_code)]\npub const SQLITE_NOTFOUND: i32 = 12; // Unknown opcode in sqlite3_file_control()\n#[allow(dead_code)]\npub const SQLITE_FULL: i32 = 13; // Insertion failed because database is full\n#[allow(dead_code)]\npub const SQLITE_CANTOPEN: i32 = 14; // Unable to open the database file\n#[allow(dead_code)]\npub const SQLITE_PROTOCOL: i32 = 15; // Database lock protocol error\n#[allow(dead_code)]\npub const SQLITE_EMPTY: i32 = 16; // Internal use only\n#[allow(dead_code)]\npub const SQLITE_SCHEMA: i32 = 17; // The database schema changed\n#[allow(dead_code)]\npub const SQLITE_TOOBIG: i32 = 18; // String or BLOB exceeds size limit\n#[allow(dead_code)]\npub const SQLITE_CONSTRAINT: i32 = 19; // Abort due to constraint violation\n#[allow(dead_code)]\npub const SQLITE_MISMATCH: i32 = 20; // Data type mismatch\n#[allow(dead_code)]\npub const SQLITE_MISUSE: i32 = 21; // Library used incorrectly\n#[allow(dead_code)]\npub const SQLITE_NOLFS: i32 = 22; // Uses OS features not supported on host\n#[allow(dead_code)]\npub const SQLITE_AUTH: i32 = 23; // Authorization denied\n#[allow(dead_code)]\npub const SQLITE_ROW: i32 = 100; // sqlite3_step() has another row ready\n#[allow(dead_code)]\npub const SQLITE_DONE: i32 = 101; // sqlite3_step() has finished executing\n\n#[allow(dead_code)]\npub const SQLITE_INTEGER: i32 = 1;\n#[allow(dead_code)]\npub const SQLITE_FLOAT: i32 = 2;\n#[allow(dead_code)]\npub const SQLITE_TEXT: i32 = 3;\n#[allow(dead_code)]\npub const SQLITE_BLOB: i32 = 4;\n#[allow(dead_code)]\npub const SQLITE_NULL: i32 = 5;\n\npub const TURSO_FAILED_TO_PARSE_BYTE_ARRAY: i32 = 1100;\npub const TURSO_FAILED_TO_PREPARE_STATEMENT: i32 = 1200;\npub const TURSO_ETC: i32 = 9999;\n"
  },
  {
    "path": "bindings/java/rs_src/lib.rs",
    "content": "mod errors;\nmod turso_connection;\nmod turso_db;\nmod turso_statement;\nmod utils;\n"
  },
  {
    "path": "bindings/java/rs_src/turso_connection.rs",
    "content": "use crate::errors::{\n    Result, TursoError, TURSO_ETC, TURSO_FAILED_TO_PARSE_BYTE_ARRAY,\n    TURSO_FAILED_TO_PREPARE_STATEMENT,\n};\nuse crate::turso_statement::TursoStatement;\nuse crate::utils::{set_err_msg_and_throw_exception, utf8_byte_arr_to_str};\nuse jni::objects::{JByteArray, JObject};\nuse jni::sys::jlong;\nuse jni::JNIEnv;\nuse std::sync::Arc;\nuse turso_core::Connection;\n\n#[derive(Clone)]\npub struct TursoConnection {\n    pub(crate) conn: Arc<Connection>,\n    pub(crate) _io: Arc<dyn turso_core::IO>,\n}\n\nimpl TursoConnection {\n    pub fn new(conn: Arc<Connection>, io: Arc<dyn turso_core::IO>) -> Self {\n        TursoConnection { conn, _io: io }\n    }\n\n    #[allow(clippy::wrong_self_convention)]\n    pub fn to_ptr(self) -> jlong {\n        Box::into_raw(Box::new(self)) as jlong\n    }\n\n    pub fn drop(ptr: jlong) {\n        let _boxed = unsafe { Box::from_raw(ptr as *mut TursoConnection) };\n    }\n}\n\npub fn to_turso_connection(ptr: jlong) -> Result<&'static mut TursoConnection> {\n    if ptr == 0 {\n        Err(TursoError::InvalidConnectionPointer)\n    } else {\n        unsafe { Ok(&mut *(ptr as *mut TursoConnection)) }\n    }\n}\n\n#[no_mangle]\npub extern \"system\" fn Java_tech_turso_core_TursoConnection__1close<'local>(\n    _env: JNIEnv<'local>,\n    _obj: JObject<'local>,\n    connection_ptr: jlong,\n) {\n    TursoConnection::drop(connection_ptr);\n}\n\n#[no_mangle]\npub extern \"system\" fn Java_tech_turso_core_TursoConnection_prepareUtf8<'local>(\n    mut env: JNIEnv<'local>,\n    obj: JObject<'local>,\n    connection_ptr: jlong,\n    sql_bytes: JByteArray<'local>,\n) -> jlong {\n    let connection = match to_turso_connection(connection_ptr) {\n        Ok(conn) => conn,\n        Err(e) => {\n            set_err_msg_and_throw_exception(&mut env, obj, TURSO_ETC, e.to_string());\n            return 0;\n        }\n    };\n\n    let sql = match utf8_byte_arr_to_str(&env, sql_bytes) {\n        Ok(sql) => sql,\n        Err(e) => {\n            set_err_msg_and_throw_exception(\n                &mut env,\n                obj,\n                TURSO_FAILED_TO_PARSE_BYTE_ARRAY,\n                e.to_string(),\n            );\n            return 0;\n        }\n    };\n\n    match connection.conn.prepare(sql) {\n        Ok(stmt) => TursoStatement::new(stmt, connection.clone()).to_ptr(),\n        Err(e) => {\n            set_err_msg_and_throw_exception(\n                &mut env,\n                obj,\n                TURSO_FAILED_TO_PREPARE_STATEMENT,\n                e.to_string(),\n            );\n            0\n        }\n    }\n}\n\n#[no_mangle]\npub extern \"system\" fn Java_tech_turso_core_TursoConnection__1getAutoCommit<'local>(\n    mut env: JNIEnv<'local>,\n    obj: JObject<'local>,\n    connection_ptr: jlong,\n) -> jni::sys::jboolean {\n    let connection = match to_turso_connection(connection_ptr) {\n        Ok(conn) => conn,\n        Err(e) => {\n            set_err_msg_and_throw_exception(&mut env, obj, TURSO_ETC, e.to_string());\n            return 0; // equivalent to false, but exception is thrown\n        }\n    };\n\n    if connection.conn.get_auto_commit() {\n        1\n    } else {\n        0\n    }\n}\n"
  },
  {
    "path": "bindings/java/rs_src/turso_db.rs",
    "content": "use crate::errors::{Result, TursoError, TURSO_ETC};\nuse crate::turso_connection::TursoConnection;\nuse crate::utils::set_err_msg_and_throw_exception;\nuse jni::objects::{JByteArray, JObject, JString};\nuse jni::sys::{jint, jlong};\nuse jni::JNIEnv;\nuse std::sync::Arc;\nuse turso_core::{Database, DatabaseOpts, EncryptionKey, EncryptionOpts, OpenFlags};\n\nstruct TursoDB {\n    db: Arc<Database>,\n    io: Arc<dyn turso_core::IO>,\n    /// Encryption info: (cipher, hexkey) - stored as strings for lazy parsing\n    encryption_info: Option<(String, String)>,\n}\n\nimpl TursoDB {\n    pub fn new(\n        db: Arc<Database>,\n        io: Arc<dyn turso_core::IO>,\n        encryption_info: Option<(String, String)>,\n    ) -> Self {\n        TursoDB {\n            db,\n            io,\n            encryption_info,\n        }\n    }\n\n    #[allow(clippy::wrong_self_convention)]\n    pub fn to_ptr(self) -> jlong {\n        Box::into_raw(Box::new(self)) as jlong\n    }\n\n    pub fn drop(ptr: jlong) {\n        let _boxed = unsafe { Box::from_raw(ptr as *mut TursoDB) };\n    }\n}\n\nfn to_turso_db(ptr: jlong) -> Result<&'static mut TursoDB> {\n    if ptr == 0 {\n        Err(TursoError::InvalidDatabasePointer)\n    } else {\n        unsafe { Ok(&mut *(ptr as *mut TursoDB)) }\n    }\n}\n\n#[no_mangle]\n#[allow(clippy::arc_with_non_send_sync)]\npub extern \"system\" fn Java_tech_turso_core_TursoDB_openUtf8<'local>(\n    mut env: JNIEnv<'local>,\n    obj: JObject<'local>,\n    file_path_byte_arr: JByteArray<'local>,\n    _open_flags: jint,\n) -> jlong {\n    let io = match turso_core::PlatformIO::new() {\n        Ok(io) => Arc::new(io),\n        Err(e) => {\n            set_err_msg_and_throw_exception(&mut env, obj, TURSO_ETC, e.to_string());\n            return -1;\n        }\n    };\n\n    let path = match env\n        .convert_byte_array(file_path_byte_arr)\n        .map_err(|e| e.to_string())\n    {\n        Ok(bytes) => match String::from_utf8(bytes) {\n            Ok(s) => s,\n            Err(e) => {\n                set_err_msg_and_throw_exception(&mut env, obj, TURSO_ETC, e.to_string());\n                return -1;\n            }\n        },\n        Err(e) => {\n            set_err_msg_and_throw_exception(&mut env, obj, TURSO_ETC, e);\n            return -1;\n        }\n    };\n\n    let db = match Database::open_file(io.clone(), &path) {\n        Ok(db) => db,\n        Err(e) => {\n            set_err_msg_and_throw_exception(&mut env, obj, TURSO_ETC, e.to_string());\n            return -1;\n        }\n    };\n\n    TursoDB::new(db, io, None).to_ptr()\n}\n\n/// Opens a database with encryption support.\n/// cipher and hexkey can be null for unencrypted databases.\n#[no_mangle]\n#[allow(clippy::arc_with_non_send_sync)]\npub extern \"system\" fn Java_tech_turso_core_TursoDB_openWithEncryptionUtf8<'local>(\n    mut env: JNIEnv<'local>,\n    obj: JObject<'local>,\n    file_path_byte_arr: JByteArray<'local>,\n    _open_flags: jint,\n    cipher: JString<'local>,\n    hexkey: JString<'local>,\n) -> jlong {\n    let io = match turso_core::PlatformIO::new() {\n        Ok(io) => Arc::new(io),\n        Err(e) => {\n            set_err_msg_and_throw_exception(&mut env, obj, TURSO_ETC, e.to_string());\n            return -1;\n        }\n    };\n\n    let path = match env\n        .convert_byte_array(file_path_byte_arr)\n        .map_err(|e| e.to_string())\n    {\n        Ok(bytes) => match String::from_utf8(bytes) {\n            Ok(s) => s,\n            Err(e) => {\n                set_err_msg_and_throw_exception(&mut env, obj, TURSO_ETC, e.to_string());\n                return -1;\n            }\n        },\n        Err(e) => {\n            set_err_msg_and_throw_exception(&mut env, obj, TURSO_ETC, e);\n            return -1;\n        }\n    };\n\n    // Parse encryption options if provided\n    let encryption_opts = if !cipher.is_null() && !hexkey.is_null() {\n        let cipher_str: String = match env.get_string(&cipher) {\n            Ok(s) => s.into(),\n            Err(e) => {\n                set_err_msg_and_throw_exception(&mut env, obj, TURSO_ETC, e.to_string());\n                return -1;\n            }\n        };\n        let hexkey_str: String = match env.get_string(&hexkey) {\n            Ok(s) => s.into(),\n            Err(e) => {\n                set_err_msg_and_throw_exception(&mut env, obj, TURSO_ETC, e.to_string());\n                return -1;\n            }\n        };\n        Some(EncryptionOpts {\n            cipher: cipher_str,\n            hexkey: hexkey_str,\n        })\n    } else {\n        None\n    };\n\n    // Clone encryption info before encryption_opts is consumed\n    let encryption_info = encryption_opts\n        .as_ref()\n        .map(|opts| (opts.cipher.clone(), opts.hexkey.clone()));\n\n    let db_opts = if encryption_opts.is_some() {\n        DatabaseOpts::new().with_encryption(true)\n    } else {\n        DatabaseOpts::new()\n    };\n\n    let db = match Database::open_file_with_flags(\n        io.clone(),\n        &path,\n        OpenFlags::Create,\n        db_opts,\n        encryption_opts,\n    ) {\n        Ok(db) => db,\n        Err(e) => {\n            set_err_msg_and_throw_exception(&mut env, obj, TURSO_ETC, e.to_string());\n            return -1;\n        }\n    };\n\n    TursoDB::new(db, io, encryption_info).to_ptr()\n}\n\n#[no_mangle]\npub extern \"system\" fn Java_tech_turso_core_TursoDB_connect0<'local>(\n    mut env: JNIEnv<'local>,\n    obj: JObject<'local>,\n    db_pointer: jlong,\n) -> jlong {\n    let db = match to_turso_db(db_pointer) {\n        Ok(db) => db,\n        Err(e) => {\n            set_err_msg_and_throw_exception(&mut env, obj, TURSO_ETC, e.to_string());\n            return 0;\n        }\n    };\n\n    // Parse encryption key if encryption info is present\n    let encryption_key = if let Some((_cipher, hexkey)) = &db.encryption_info {\n        match EncryptionKey::from_hex_string(hexkey) {\n            Ok(key) => Some(key),\n            Err(e) => {\n                set_err_msg_and_throw_exception(&mut env, obj, TURSO_ETC, e.to_string());\n                return 0;\n            }\n        }\n    } else {\n        None\n    };\n\n    // Use connect_with_encryption to properly set up encryption context\n    // before the pager reads page 1. This is required for encrypted databases.\n    let conn = match db.db.connect_with_encryption(encryption_key) {\n        Ok(conn) => conn,\n        Err(e) => {\n            set_err_msg_and_throw_exception(&mut env, obj, TURSO_ETC, e.to_string());\n            return 0;\n        }\n    };\n\n    TursoConnection::new(conn, db.io.clone()).to_ptr()\n}\n\n#[no_mangle]\npub extern \"system\" fn Java_tech_turso_core_TursoDB_close0<'local>(\n    _env: JNIEnv<'local>,\n    _obj: JObject<'local>,\n    db_pointer: jlong,\n) {\n    TursoDB::drop(db_pointer);\n}\n\n#[no_mangle]\npub extern \"system\" fn Java_tech_turso_core_TursoDB_throwJavaException<'local>(\n    mut env: JNIEnv<'local>,\n    obj: JObject<'local>,\n    error_code: jint,\n) {\n    set_err_msg_and_throw_exception(\n        &mut env,\n        obj,\n        error_code,\n        \"throw java exception\".to_string(),\n    );\n}\n"
  },
  {
    "path": "bindings/java/rs_src/turso_statement.rs",
    "content": "use crate::errors::{Result, SQLITE_ERROR, SQLITE_OK};\nuse crate::errors::{TursoError, TURSO_ETC};\nuse crate::turso_connection::TursoConnection;\nuse crate::utils::set_err_msg_and_throw_exception;\nuse jni::objects::{JByteArray, JObject, JObjectArray, JString, JValue};\nuse jni::sys::{jdouble, jint, jlong};\nuse jni::JNIEnv;\nuse std::num::NonZero;\nuse turso_core::{LimboError, Statement, Value};\n\npub const STEP_RESULT_ID_ROW: i32 = 10;\n#[allow(dead_code)]\npub const STEP_RESULT_ID_IO: i32 = 20;\npub const STEP_RESULT_ID_DONE: i32 = 30;\npub const STEP_RESULT_ID_INTERRUPT: i32 = 40;\npub const STEP_RESULT_ID_BUSY: i32 = 50;\npub const STEP_RESULT_ID_ERROR: i32 = 60;\n\npub struct TursoStatement {\n    pub(crate) stmt: Statement,\n    pub(crate) connection: TursoConnection,\n}\n\nimpl TursoStatement {\n    pub fn new(stmt: Statement, connection: TursoConnection) -> Self {\n        TursoStatement { stmt, connection }\n    }\n\n    #[allow(clippy::wrong_self_convention)]\n    pub fn to_ptr(self) -> jlong {\n        Box::into_raw(Box::new(self)) as jlong\n    }\n\n    pub fn drop(ptr: jlong) {\n        let _boxed = unsafe { Box::from_raw(ptr as *mut TursoStatement) };\n    }\n}\n\npub fn to_turso_statement(ptr: jlong) -> Result<&'static mut TursoStatement> {\n    if ptr == 0 {\n        Err(TursoError::InvalidConnectionPointer)\n    } else {\n        unsafe { Ok(&mut *(ptr as *mut TursoStatement)) }\n    }\n}\n\n#[no_mangle]\npub extern \"system\" fn Java_tech_turso_core_TursoStatement_step<'local>(\n    mut env: JNIEnv<'local>,\n    obj: JObject<'local>,\n    stmt_ptr: jlong,\n) -> JObject<'local> {\n    let stmt = match to_turso_statement(stmt_ptr) {\n        Ok(stmt) => stmt,\n        Err(e) => {\n            let err_msg = e.to_string();\n            set_err_msg_and_throw_exception(&mut env, obj, TURSO_ETC, err_msg.clone());\n            return to_turso_step_result_error(&mut env, &err_msg);\n        }\n    };\n\n    let result = stmt.stmt.run_one_step_blocking(|| Ok(()), || Ok(()));\n\n    match result {\n        Ok(Some(row)) => match row_to_obj_array(&mut env, row) {\n            Ok(row) => to_turso_step_result(&mut env, STEP_RESULT_ID_ROW, Some(row)),\n            Err(e) => {\n                let err_msg = e.to_string();\n                set_err_msg_and_throw_exception(&mut env, obj, TURSO_ETC, err_msg.clone());\n                to_turso_step_result_error(&mut env, &err_msg)\n            }\n        },\n        Ok(None) => {\n            // Done\n            to_turso_step_result(&mut env, STEP_RESULT_ID_DONE, None)\n        }\n        Err(LimboError::Interrupt) => {\n            to_turso_step_result(&mut env, STEP_RESULT_ID_INTERRUPT, None)\n        }\n        Err(LimboError::Busy) => to_turso_step_result(&mut env, STEP_RESULT_ID_BUSY, None),\n        Err(err) => {\n            let err_msg = err.to_string();\n            set_err_msg_and_throw_exception(&mut env, obj, TURSO_ETC, err_msg.clone());\n            to_turso_step_result_error(&mut env, &err_msg)\n        }\n    }\n}\n\n#[no_mangle]\npub extern \"system\" fn Java_tech_turso_core_TursoStatement__1close<'local>(\n    _env: JNIEnv<'local>,\n    _obj: JObject<'local>,\n    stmt_ptr: jlong,\n) {\n    TursoStatement::drop(stmt_ptr);\n}\n\nfn row_to_obj_array<'local>(\n    env: &mut JNIEnv<'local>,\n    row: &turso_core::Row,\n) -> Result<JObject<'local>> {\n    let obj_array = env.new_object_array(row.len() as i32, \"java/lang/Object\", JObject::null())?;\n\n    for (i, value) in row.get_values().enumerate() {\n        let obj = match value {\n            turso_core::Value::Null => JObject::null(),\n            turso_core::Value::Numeric(turso_core::Numeric::Integer(i)) => {\n                env.new_object(\"java/lang/Long\", \"(J)V\", &[JValue::Long(*i)])?\n            }\n            turso_core::Value::Numeric(turso_core::Numeric::Float(f)) => {\n                env.new_object(\"java/lang/Double\", \"(D)V\", &[JValue::Double(f64::from(*f))])?\n            }\n            turso_core::Value::Text(s) => env.new_string(s.as_str())?.into(),\n            turso_core::Value::Blob(b) => env.byte_array_from_slice(b.as_slice())?.into(),\n        };\n        if let Err(e) = env.set_object_array_element(&obj_array, i as i32, obj) {\n            eprintln!(\"Error on parsing row: {e:?}\");\n        }\n    }\n\n    Ok(obj_array.into())\n}\n\n#[no_mangle]\npub extern \"system\" fn Java_tech_turso_core_TursoStatement_columns<'local>(\n    mut env: JNIEnv<'local>,\n    _obj: JObject<'local>,\n    stmt_ptr: jlong,\n) -> JObject<'local> {\n    let stmt = to_turso_statement(stmt_ptr).unwrap();\n    let num_columns = stmt.stmt.num_columns();\n    let obj_arr: JObjectArray = env\n        .new_object_array(num_columns as i32, \"java/lang/String\", JObject::null())\n        .unwrap();\n\n    for i in 0..num_columns {\n        let column_name = stmt.stmt.get_column_name(i);\n        let str = env.new_string(column_name.into_owned()).unwrap();\n        env.set_object_array_element(&obj_arr, i as i32, str)\n            .unwrap();\n    }\n\n    obj_arr.into()\n}\n\n#[no_mangle]\npub extern \"system\" fn Java_tech_turso_core_TursoStatement_bindNull<'local>(\n    mut env: JNIEnv<'local>,\n    obj: JObject<'local>,\n    stmt_ptr: jlong,\n    position: jint,\n) -> jint {\n    let stmt = match to_turso_statement(stmt_ptr) {\n        Ok(stmt) => stmt,\n        Err(e) => {\n            set_err_msg_and_throw_exception(&mut env, obj, SQLITE_ERROR, e.to_string());\n            return SQLITE_ERROR;\n        }\n    };\n\n    stmt.stmt\n        .bind_at(NonZero::new(position as usize).unwrap(), Value::Null);\n    SQLITE_OK\n}\n\n#[no_mangle]\npub extern \"system\" fn Java_tech_turso_core_TursoStatement_bindLong<'local>(\n    mut env: JNIEnv<'local>,\n    obj: JObject<'local>,\n    stmt_ptr: jlong,\n    position: jint,\n    value: jlong,\n) -> jint {\n    let stmt = match to_turso_statement(stmt_ptr) {\n        Ok(stmt) => stmt,\n        Err(e) => {\n            set_err_msg_and_throw_exception(&mut env, obj, SQLITE_ERROR, e.to_string());\n            return SQLITE_ERROR;\n        }\n    };\n\n    stmt.stmt.bind_at(\n        NonZero::new(position as usize).unwrap(),\n        Value::from_i64(value),\n    );\n    SQLITE_OK\n}\n\n#[no_mangle]\npub extern \"system\" fn Java_tech_turso_core_TursoStatement_bindDouble<'local>(\n    mut env: JNIEnv<'local>,\n    obj: JObject<'local>,\n    stmt_ptr: jlong,\n    position: jint,\n    value: jdouble,\n) -> jint {\n    let stmt = match to_turso_statement(stmt_ptr) {\n        Ok(stmt) => stmt,\n        Err(e) => {\n            set_err_msg_and_throw_exception(&mut env, obj, SQLITE_ERROR, e.to_string());\n            return SQLITE_ERROR;\n        }\n    };\n\n    stmt.stmt.bind_at(\n        NonZero::new(position as usize).unwrap(),\n        Value::from_f64(value),\n    );\n    SQLITE_OK\n}\n\n#[no_mangle]\npub extern \"system\" fn Java_tech_turso_core_TursoStatement_bindText<'local>(\n    mut env: JNIEnv<'local>,\n    obj: JObject<'local>,\n    stmt_ptr: jlong,\n    position: jint,\n    value: JString<'local>,\n) -> jint {\n    let stmt = match to_turso_statement(stmt_ptr) {\n        Ok(stmt) => stmt,\n        Err(e) => {\n            set_err_msg_and_throw_exception(&mut env, obj, SQLITE_ERROR, e.to_string());\n            return SQLITE_ERROR;\n        }\n    };\n\n    let text: String = match env.get_string(&value) {\n        Ok(s) => s.into(),\n        Err(_) => return SQLITE_ERROR,\n    };\n\n    stmt.stmt.bind_at(\n        NonZero::new(position as usize).unwrap(),\n        Value::build_text(text),\n    );\n    SQLITE_OK\n}\n\n#[no_mangle]\npub extern \"system\" fn Java_tech_turso_core_TursoStatement_bindBlob<'local>(\n    mut env: JNIEnv<'local>,\n    obj: JObject<'local>,\n    stmt_ptr: jlong,\n    position: jint,\n    value: JByteArray<'local>,\n) -> jint {\n    let stmt = match to_turso_statement(stmt_ptr) {\n        Ok(stmt) => stmt,\n        Err(e) => {\n            set_err_msg_and_throw_exception(&mut env, obj, SQLITE_ERROR, e.to_string());\n            return SQLITE_ERROR;\n        }\n    };\n\n    let blob: Vec<u8> = match env.convert_byte_array(value) {\n        Ok(b) => b,\n        Err(_) => return SQLITE_ERROR,\n    };\n\n    stmt.stmt\n        .bind_at(NonZero::new(position as usize).unwrap(), Value::Blob(blob));\n    SQLITE_OK\n}\n\n#[no_mangle]\npub extern \"system\" fn Java_tech_turso_core_TursoStatement_totalChanges<'local>(\n    mut env: JNIEnv<'local>,\n    obj: JObject<'local>,\n    stmt_ptr: jlong,\n) -> jlong {\n    let stmt = match to_turso_statement(stmt_ptr) {\n        Ok(stmt) => stmt,\n        Err(e) => {\n            set_err_msg_and_throw_exception(&mut env, obj, SQLITE_ERROR, e.to_string());\n            return -1;\n        }\n    };\n\n    stmt.connection.conn.total_changes()\n}\n\n#[no_mangle]\npub extern \"system\" fn Java_tech_turso_core_TursoStatement_changes<'local>(\n    mut env: JNIEnv<'local>,\n    obj: JObject<'local>,\n    stmt_ptr: jlong,\n) -> jlong {\n    let stmt = match to_turso_statement(stmt_ptr) {\n        Ok(stmt) => stmt,\n        Err(e) => {\n            set_err_msg_and_throw_exception(&mut env, obj, SQLITE_ERROR, e.to_string());\n            return -1;\n        }\n    };\n\n    stmt.connection.conn.changes()\n}\n\n#[no_mangle]\npub extern \"system\" fn Java_tech_turso_core_TursoStatement_parameterCount<'local>(\n    mut env: JNIEnv<'local>,\n    obj: JObject<'local>,\n    stmt_ptr: jlong,\n) -> jint {\n    let stmt = match to_turso_statement(stmt_ptr) {\n        Ok(stmt) => stmt,\n        Err(e) => {\n            set_err_msg_and_throw_exception(&mut env, obj, SQLITE_ERROR, e.to_string());\n            return -1;\n        }\n    };\n\n    stmt.stmt.parameters_count() as jint\n}\n\n#[no_mangle]\npub extern \"system\" fn Java_tech_turso_core_TursoStatement_reset<'local>(\n    mut env: JNIEnv<'local>,\n    obj: JObject<'local>,\n    stmt_ptr: jlong,\n) -> jint {\n    let stmt = match to_turso_statement(stmt_ptr) {\n        Ok(stmt) => stmt,\n        Err(e) => {\n            set_err_msg_and_throw_exception(&mut env, obj, SQLITE_ERROR, e.to_string());\n            return -1;\n        }\n    };\n\n    match stmt.stmt.reset() {\n        Ok(_) => 0,\n        Err(e) => {\n            set_err_msg_and_throw_exception(&mut env, obj, SQLITE_ERROR, e.to_string());\n            -1\n        }\n    }\n}\n\n/// Converts an optional `JObject` into Java's `TursoStepResult`.\n///\n/// This function takes an optional `JObject` and converts it into a Java object\n/// of type `TursoStepResult`. The conversion is done by creating a new Java object with the\n/// appropriate constructor arguments.\n///\n/// # Arguments\n///\n/// * `env` - A mutable reference to the JNI environment.\n/// * `id` - An integer representing the type of `StepResult`.\n/// * `result` - An optional `JObject` that contains the result data.\n///\n/// # Returns\n///\n/// A `JObject` representing the `TursoStepResult` in Java. If the object creation fails,\n/// a null `JObject` is returned\nfn to_turso_step_result<'local>(\n    env: &mut JNIEnv<'local>,\n    id: i32,\n    result: Option<JObject<'local>>,\n) -> JObject<'local> {\n    let mut ctor_args = vec![JValue::Int(id)];\n    if let Some(res) = result {\n        ctor_args.push(JValue::Object(&res));\n        env.new_object(\n            \"tech/turso/core/TursoStepResult\",\n            \"(I[Ljava/lang/Object;)V\",\n            &ctor_args,\n        )\n    } else {\n        env.new_object(\"tech/turso/core/TursoStepResult\", \"(I)V\", &ctor_args)\n    }\n    .unwrap_or_else(|_| JObject::null())\n}\n\n/// Creates an error `TursoStepResult` with an error message.\nfn to_turso_step_result_error<'local>(\n    env: &mut JNIEnv<'local>,\n    error_message: &str,\n) -> JObject<'local> {\n    // Clear any pending exception first, as JNI calls won't work with a pending exception.\n    // This can happen if set_err_msg_and_throw_exception was called before this function.\n    if env.exception_check().unwrap_or(false) {\n        let _ = env.exception_clear();\n    }\n\n    let error_str = match env.new_string(error_message) {\n        Ok(s) => s,\n        Err(_) => return JObject::null(),\n    };\n    let error_obj: JObject = error_str.into();\n    let ctor_args = vec![\n        JValue::Int(STEP_RESULT_ID_ERROR),\n        JValue::Object(&error_obj),\n    ];\n    env.new_object(\n        \"tech/turso/core/TursoStepResult\",\n        \"(ILjava/lang/String;)V\",\n        &ctor_args,\n    )\n    .unwrap_or_else(|_| JObject::null())\n}\n"
  },
  {
    "path": "bindings/java/rs_src/utils.rs",
    "content": "use crate::errors::TursoError;\nuse jni::objects::{JByteArray, JObject};\nuse jni::JNIEnv;\n\npub(crate) fn utf8_byte_arr_to_str(\n    env: &JNIEnv,\n    bytes: JByteArray,\n) -> crate::errors::Result<String> {\n    let bytes = env\n        .convert_byte_array(bytes)\n        .map_err(|_| TursoError::CustomError(\"Failed to retrieve bytes\".to_string()))?;\n    let str = String::from_utf8(bytes).map_err(|_| {\n        TursoError::CustomError(\"Failed to convert utf8 byte array into string\".to_string())\n    })?;\n    Ok(str)\n}\n\n/// Sets the error message and throws a Java exception.\n///\n/// This function converts the provided error message to a byte array and calls the\n/// `throwTursoException` method on the provided Java object to throw an exception.\n///\n/// # Parameters\n/// - `env`: The JNI environment.\n/// - `obj`: The Java object on which the exception will be thrown.\n/// - `err_code`: The error code corresponding to the exception. Refer to `tech.turso.core.Codes` for the list of error codes.\n/// - `err_msg`: The error message to be included in the exception.\n///\n/// # Example\n/// ```rust\n/// set_err_msg_and_throw_exception(env, obj, Codes::SQLITE_ERROR, \"An error occurred\".to_string());\n/// ```\npub fn set_err_msg_and_throw_exception<'local>(\n    env: &mut JNIEnv<'local>,\n    obj: JObject<'local>,\n    err_code: i32,\n    err_msg: String,\n) {\n    let error_message_bytes = env\n        .byte_array_from_slice(err_msg.as_bytes())\n        .expect(\"Failed to convert to byte array\");\n    match env.call_method(\n        obj,\n        \"throwTursoException\",\n        \"(I[B)V\",\n        &[err_code.into(), (&error_message_bytes).into()],\n    ) {\n        Ok(_) => {\n            // do nothing because above method will always return Err\n        }\n        Err(_e) => {\n            // do nothing because our java app will handle Err\n        }\n    }\n}\n"
  },
  {
    "path": "bindings/java/settings.gradle.kts",
    "content": "rootProject.name = \"turso\"\n"
  },
  {
    "path": "bindings/java/src/main/java/examples/EncryptionExample.java",
    "content": "package examples;\n\nimport tech.turso.core.TursoConnection;\nimport tech.turso.core.TursoDB;\nimport tech.turso.core.TursoEncryptionCipher;\nimport tech.turso.core.TursoResultSet;\nimport tech.turso.core.TursoStatement;\n\nimport java.io.File;\nimport java.nio.file.Files;\n\n/**\n * Local Database Encryption Example\n *\n * This example demonstrates how to use local database encryption\n * with the Turso Java SDK.\n *\n * Supported ciphers:\n *   - AES_128_GCM\n *   - AES_256_GCM\n *   - AEGIS_256\n *   - AEGIS_256X2\n *   - AEGIS_128L\n *   - AEGIS_128X2\n *   - AEGIS_128X4\n */\npublic class EncryptionExample {\n    private static final String DB_PATH = \"encrypted.db\";\n    // 32-byte hex key for aegis256 (256 bits = 32 bytes = 64 hex chars)\n    private static final String ENCRYPTION_KEY =\n        \"b1bbfda4f589dc9daaf004fe21111e00dc00c98237102f5c7002a5669fc76327\";\n\n    public static void main(String[] args) throws Exception {\n        System.out.println(\"=== Turso Local Encryption Example ===\\n\");\n\n        // Create an encrypted database\n        System.out.println(\"1. Creating encrypted database...\");\n        TursoDB db = TursoDB.createWithEncryption(\n            \"jdbc:turso:\" + DB_PATH,\n            DB_PATH,\n            TursoEncryptionCipher.AEGIS_256,\n            ENCRYPTION_KEY\n        );\n        TursoConnection conn = new TursoConnection(\"jdbc:turso:\" + DB_PATH, db);\n\n        // Create a table and insert sensitive data\n        System.out.println(\"2. Creating table and inserting data...\");\n        TursoStatement stmt = conn.prepare(\"CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, ssn TEXT)\");\n        stmt.execute();\n        stmt.close();\n\n        stmt = conn.prepare(\"INSERT INTO users (name, ssn) VALUES ('Alice', '123-45-6789')\");\n        stmt.execute();\n        stmt.close();\n\n        stmt = conn.prepare(\"INSERT INTO users (name, ssn) VALUES ('Bob', '987-65-4321')\");\n        stmt.execute();\n        stmt.close();\n\n        // Checkpoint to flush data to disk\n        stmt = conn.prepare(\"PRAGMA wal_checkpoint(truncate)\");\n        stmt.execute();\n        stmt.close();\n\n        // Query the data\n        System.out.println(\"3. Querying data...\");\n        stmt = conn.prepare(\"SELECT * FROM users\");\n        stmt.execute();\n        TursoResultSet rs = stmt.getResultSet();\n        while (rs.next()) {\n            System.out.println(\"   User: id=\" + rs.get(1) +\n                             \", name=\" + rs.get(2) +\n                             \", ssn=\" + rs.get(3));\n        }\n        stmt.close();\n        conn.close();\n        db.close();\n\n        // Verify the data is encrypted on disk\n        System.out.println(\"\\n4. Verifying encryption...\");\n        byte[] rawContent = Files.readAllBytes(new File(DB_PATH).toPath());\n        String contentStr = new String(rawContent);\n        boolean containsPlaintext = contentStr.contains(\"Alice\") || contentStr.contains(\"123-45-6789\");\n\n        if (containsPlaintext) {\n            System.out.println(\"   WARNING: Data appears to be unencrypted!\");\n        } else {\n            System.out.println(\"   Data is encrypted on disk (plaintext not found)\");\n        }\n\n        // Reopen with the same key\n        System.out.println(\"\\n5. Reopening database with correct key...\");\n        TursoDB db2 = TursoDB.createWithEncryption(\n            \"jdbc:turso:\" + DB_PATH,\n            DB_PATH,\n            TursoEncryptionCipher.AEGIS_256,\n            ENCRYPTION_KEY\n        );\n        TursoConnection conn2 = new TursoConnection(\"jdbc:turso:\" + DB_PATH, db2);\n        TursoStatement stmt2 = conn2.prepare(\"SELECT name FROM users\");\n        stmt2.execute();\n        TursoResultSet rs2 = stmt2.getResultSet();\n        System.out.print(\"   Successfully read users: \");\n        while (rs2.next()) {\n            System.out.print(rs2.get(1) + \" \");\n        }\n        System.out.println();\n        stmt2.close();\n        conn2.close();\n        db2.close();\n\n        // Demonstrate that wrong key fails\n        System.out.println(\"\\n6. Attempting to open with wrong key (should fail)...\");\n        try {\n            TursoDB db3 = TursoDB.createWithEncryption(\n                \"jdbc:turso:\" + DB_PATH,\n                DB_PATH,\n                TursoEncryptionCipher.AEGIS_256,\n                \"aaaaaaa4f589dc9daaf004fe21111e00dc00c98237102f5c7002a5669fc76327\"\n            );\n            TursoConnection conn3 = new TursoConnection(\"jdbc:turso:\" + DB_PATH, db3);\n            TursoStatement stmt3 = conn3.prepare(\"SELECT * FROM users\");\n            stmt3.execute();\n            System.out.println(\"   ERROR: Should have failed with wrong key!\");\n        } catch (Exception e) {\n            System.out.println(\"   Correctly failed: \" + e.getMessage());\n        }\n\n        // Cleanup\n        new File(DB_PATH).delete();\n        System.out.println(\"\\n=== Example completed successfully ===\");\n    }\n}\n"
  },
  {
    "path": "bindings/java/src/main/java/tech/turso/JDBC.java",
    "content": "package tech.turso;\n\nimport java.sql.*;\nimport java.util.Locale;\nimport java.util.Properties;\nimport tech.turso.annotations.Nullable;\nimport tech.turso.annotations.SkipNullableCheck;\nimport tech.turso.core.TursoPropertiesHolder;\nimport tech.turso.jdbc4.JDBC4Connection;\nimport tech.turso.utils.Logger;\nimport tech.turso.utils.LoggerFactory;\n\n/** Turso JDBC driver implementation. */\npublic final class JDBC implements Driver {\n\n  private static final Logger logger = LoggerFactory.getLogger(JDBC.class);\n\n  private static final String VALID_URL_PREFIX = \"jdbc:turso:\";\n\n  static {\n    try {\n      DriverManager.registerDriver(new JDBC());\n    } catch (Exception e) {\n      logger.error(\"Failed to register driver\", e);\n    }\n  }\n\n  /**\n   * Creates a new Turso JDBC connection.\n   *\n   * @param url the database URL\n   * @param properties connection properties\n   * @return a new connection instance, or null if the URL is not valid\n   * @throws SQLException if a database access error occurs\n   */\n  @Nullable\n  public static JDBC4Connection createConnection(String url, Properties properties)\n      throws SQLException {\n    if (!isValidURL(url)) return null;\n\n    url = url.trim();\n    return new JDBC4Connection(url, extractAddress(url), properties);\n  }\n\n  private static boolean isValidURL(String url) {\n    return (url != null && url.toLowerCase(Locale.ROOT).startsWith(VALID_URL_PREFIX));\n  }\n\n  private static String extractAddress(String url) {\n    return url.substring(VALID_URL_PREFIX.length());\n  }\n\n  @Nullable\n  @Override\n  public Connection connect(String url, Properties info) throws SQLException {\n    return createConnection(url, info);\n  }\n\n  @Override\n  public boolean acceptsURL(String url) throws SQLException {\n    return isValidURL(url);\n  }\n\n  @Override\n  public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException {\n    return TursoConfig.getDriverPropertyInfo();\n  }\n\n  @Override\n  public int getMajorVersion() {\n    return Integer.parseInt(TursoPropertiesHolder.getDriverVersion().split(\"\\\\.\")[0]);\n  }\n\n  @Override\n  public int getMinorVersion() {\n    return Integer.parseInt(TursoPropertiesHolder.getDriverVersion().split(\"\\\\.\")[1]);\n  }\n\n  @Override\n  public boolean jdbcCompliant() {\n    return false;\n  }\n\n  @Override\n  @SkipNullableCheck\n  public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException {\n    // TODO\n    return null;\n  }\n}\n"
  },
  {
    "path": "bindings/java/src/main/java/tech/turso/TursoConfig.java",
    "content": "package tech.turso;\n\nimport java.sql.DriverPropertyInfo;\nimport java.util.Arrays;\nimport java.util.Properties;\n\n/** Turso Configuration. */\npublic final class TursoConfig {\n\n  private static final DriverPropertyInfo[] driverPropertyInfo = driverPropertyInfo();\n\n  private final Properties pragma;\n\n  public TursoConfig(Properties properties) {\n    this.pragma = properties;\n  }\n\n  public static DriverPropertyInfo[] getDriverPropertyInfo() {\n    return driverPropertyInfo;\n  }\n\n  public Properties toProperties() {\n    Properties copy = new Properties();\n    copy.putAll(pragma);\n    return copy;\n  }\n\n  public enum Pragma {\n    ;\n\n    private final String pragmaName;\n    private final String description;\n    private final String[] choices;\n\n    Pragma(String pragmaName, String description, String[] choices) {\n      this.pragmaName = pragmaName;\n      this.description = description;\n      this.choices = choices;\n    }\n\n    public String getPragmaName() {\n      return pragmaName;\n    }\n  }\n\n  private static DriverPropertyInfo[] driverPropertyInfo() {\n    return Arrays.stream(Pragma.values())\n        .map(\n            p -> {\n              DriverPropertyInfo info = new DriverPropertyInfo(p.pragmaName, null);\n              info.description = p.description;\n              info.choices = p.choices;\n              info.required = false;\n              return info;\n            })\n        .toArray(DriverPropertyInfo[]::new);\n  }\n}\n"
  },
  {
    "path": "bindings/java/src/main/java/tech/turso/TursoDataSource.java",
    "content": "package tech.turso;\n\nimport java.io.PrintWriter;\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport java.sql.SQLFeatureNotSupportedException;\nimport java.util.Properties;\nimport java.util.logging.Logger;\nimport javax.sql.DataSource;\nimport tech.turso.annotations.Nullable;\nimport tech.turso.annotations.SkipNullableCheck;\n\n/** Provides {@link DataSource} API for configuring Turso database connection. */\npublic final class TursoDataSource implements DataSource {\n\n  private final TursoConfig tursoConfig;\n  private final String url;\n\n  /**\n   * Creates a datasource based on the provided configuration.\n   *\n   * @param tursoConfig The configuration for the datasource.\n   */\n  public TursoDataSource(TursoConfig tursoConfig, String url) {\n    this.tursoConfig = tursoConfig;\n    this.url = url;\n  }\n\n  @Override\n  @Nullable\n  public Connection getConnection() throws SQLException {\n    return getConnection(null, null);\n  }\n\n  @Override\n  @Nullable\n  public Connection getConnection(@Nullable String username, @Nullable String password)\n      throws SQLException {\n    Properties properties = tursoConfig.toProperties();\n    if (username != null) properties.put(\"user\", username);\n    if (password != null) properties.put(\"pass\", password);\n    return JDBC.createConnection(url, properties);\n  }\n\n  @Override\n  @SkipNullableCheck\n  public PrintWriter getLogWriter() throws SQLException {\n    // TODO\n    return null;\n  }\n\n  @Override\n  public void setLogWriter(PrintWriter out) throws SQLException {\n    // TODO\n  }\n\n  @Override\n  public void setLoginTimeout(int seconds) throws SQLException {\n    // TODO\n  }\n\n  @Override\n  public int getLoginTimeout() throws SQLException {\n    // TODO\n    return 0;\n  }\n\n  @Override\n  @SkipNullableCheck\n  public Logger getParentLogger() throws SQLFeatureNotSupportedException {\n    // TODO\n    return null;\n  }\n\n  @Override\n  @SkipNullableCheck\n  public <T> T unwrap(Class<T> iface) throws SQLException {\n    // TODO\n    return null;\n  }\n\n  @Override\n  public boolean isWrapperFor(Class<?> iface) throws SQLException {\n    // TODO\n    return false;\n  }\n}\n"
  },
  {
    "path": "bindings/java/src/main/java/tech/turso/TursoErrorCode.java",
    "content": "package tech.turso;\n\nimport tech.turso.core.SqliteCode;\n\n/** Turso error code. Superset of SQLite3 error code. */\npublic enum TursoErrorCode {\n  SQLITE_OK(SqliteCode.SQLITE_OK, \"Successful result\"),\n  SQLITE_ERROR(SqliteCode.SQLITE_ERROR, \"SQL error or missing database\"),\n  SQLITE_INTERNAL(SqliteCode.SQLITE_INTERNAL, \"An internal logic error in SQLite\"),\n  SQLITE_PERM(SqliteCode.SQLITE_PERM, \"Access permission denied\"),\n  SQLITE_ABORT(SqliteCode.SQLITE_ABORT, \"Callback routine requested an abort\"),\n  SQLITE_BUSY(SqliteCode.SQLITE_BUSY, \"The database file is locked\"),\n  SQLITE_LOCKED(SqliteCode.SQLITE_LOCKED, \"A table in the database is locked\"),\n  SQLITE_NOMEM(SqliteCode.SQLITE_NOMEM, \"A malloc() failed\"),\n  SQLITE_READONLY(SqliteCode.SQLITE_READONLY, \"Attempt to write a readonly database\"),\n  SQLITE_INTERRUPT(SqliteCode.SQLITE_INTERRUPT, \"Operation terminated by sqlite_interrupt()\"),\n  SQLITE_IOERR(SqliteCode.SQLITE_IOERR, \"Some kind of disk I/O error occurred\"),\n  SQLITE_CORRUPT(SqliteCode.SQLITE_CORRUPT, \"The database disk image is malformed\"),\n  SQLITE_NOTFOUND(SqliteCode.SQLITE_NOTFOUND, \"(Internal Only) Table or record not found\"),\n  SQLITE_FULL(SqliteCode.SQLITE_FULL, \"Insertion failed because database is full\"),\n  SQLITE_CANTOPEN(SqliteCode.SQLITE_CANTOPEN, \"Unable to open the database file\"),\n  SQLITE_PROTOCOL(SqliteCode.SQLITE_PROTOCOL, \"Database lock protocol error\"),\n  SQLITE_EMPTY(SqliteCode.SQLITE_EMPTY, \"(Internal Only) Database table is empty\"),\n  SQLITE_SCHEMA(SqliteCode.SQLITE_SCHEMA, \"The database schema changed\"),\n  SQLITE_TOOBIG(SqliteCode.SQLITE_TOOBIG, \"Too much data for one row of a table\"),\n  SQLITE_CONSTRAINT(SqliteCode.SQLITE_CONSTRAINT, \"Abort due to constraint violation\"),\n  SQLITE_MISMATCH(SqliteCode.SQLITE_MISMATCH, \"Data type mismatch\"),\n  SQLITE_MISUSE(SqliteCode.SQLITE_MISUSE, \"Library used incorrectly\"),\n  SQLITE_NOLFS(SqliteCode.SQLITE_NOLFS, \"Uses OS features not supported on host\"),\n  SQLITE_AUTH(SqliteCode.SQLITE_AUTH, \"Authorization denied\"),\n  SQLITE_ROW(SqliteCode.SQLITE_ROW, \"sqlite_step() has another row ready\"),\n  SQLITE_DONE(SqliteCode.SQLITE_DONE, \"sqlite_step() has finished executing\"),\n  SQLITE_INTEGER(SqliteCode.SQLITE_INTEGER, \"Integer type\"),\n  SQLITE_FLOAT(SqliteCode.SQLITE_FLOAT, \"Float type\"),\n  SQLITE_TEXT(SqliteCode.SQLITE_TEXT, \"Text type\"),\n  SQLITE_BLOB(SqliteCode.SQLITE_BLOB, \"Blob type\"),\n  SQLITE_NULL(SqliteCode.SQLITE_NULL, \"Null type\"),\n\n  UNKNOWN_ERROR(-1, \"Unknown error\"),\n  TURSO_FAILED_TO_PARSE_BYTE_ARRAY(1100, \"Failed to parse ut8 byte array\"),\n  TURSO_FAILED_TO_PREPARE_STATEMENT(1200, \"Failed to prepare statement\"),\n  TURSO_ETC(9999, \"Unclassified error\");\n\n  public final int code;\n  public final String message;\n\n  /**\n   * @param code Error code\n   * @param message Message for the error.\n   */\n  TursoErrorCode(int code, String message) {\n    this.code = code;\n    this.message = message;\n  }\n\n  public static TursoErrorCode getErrorCode(int errorCode) {\n    for (TursoErrorCode tursoErrorCode : TursoErrorCode.values()) {\n      if (errorCode == tursoErrorCode.code) return tursoErrorCode;\n    }\n\n    return UNKNOWN_ERROR;\n  }\n\n  @Override\n  public String toString() {\n    return (\"tursoErrorCode{\" + \"code=\" + code + \", message='\" + message + '\\'' + '}');\n  }\n}\n"
  },
  {
    "path": "bindings/java/src/main/java/tech/turso/annotations/NativeInvocation.java",
    "content": "package tech.turso.annotations;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * Annotation to mark methods that are called by native functions. For example, throwing exceptions\n * or creating java objects.\n */\n@Retention(RetentionPolicy.SOURCE)\n@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})\npublic @interface NativeInvocation {\n  String invokedFrom() default \"\";\n}\n"
  },
  {
    "path": "bindings/java/src/main/java/tech/turso/annotations/Nullable.java",
    "content": "package tech.turso.annotations;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * Annotation to mark nullable types.\n *\n * <p>This annotation is used to indicate that a method, field, or parameter can be null. It helps\n * in identifying potential nullability issues and improving code quality.\n */\n@Retention(RetentionPolicy.SOURCE)\n@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})\npublic @interface Nullable {}\n"
  },
  {
    "path": "bindings/java/src/main/java/tech/turso/annotations/SkipNullableCheck.java",
    "content": "package tech.turso.annotations;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * Marker annotation to skip nullable checks.\n *\n * <p>This annotation is used to mark methods, fields, or parameters that should be excluded from\n * nullable checks. It is typically applied to code that is still under development or requires\n * special handling.\n */\n@Retention(RetentionPolicy.SOURCE)\n@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})\npublic @interface SkipNullableCheck {}\n"
  },
  {
    "path": "bindings/java/src/main/java/tech/turso/annotations/VisibleForTesting.java",
    "content": "package tech.turso.annotations;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/** Annotation to mark methods that use larger visibility for testing purposes. */\n@Retention(RetentionPolicy.SOURCE)\n@Target(ElementType.METHOD)\npublic @interface VisibleForTesting {}\n"
  },
  {
    "path": "bindings/java/src/main/java/tech/turso/core/SqliteCode.java",
    "content": "/*\n * Copyright (c) 2007 David Crawshaw <david@zentus.com>\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\npackage tech.turso.core;\n\n/** Sqlite error codes. */\npublic final class SqliteCode {\n  /** Successful result */\n  public static final int SQLITE_OK = 0;\n\n  /** SQL error or missing database */\n  public static final int SQLITE_ERROR = 1;\n\n  /** An internal logic error in SQLite */\n  public static final int SQLITE_INTERNAL = 2;\n\n  /** Access permission denied */\n  public static final int SQLITE_PERM = 3;\n\n  /** Callback routine requested an abort */\n  public static final int SQLITE_ABORT = 4;\n\n  /** The database file is locked */\n  public static final int SQLITE_BUSY = 5;\n\n  /** A table in the database is locked */\n  public static final int SQLITE_LOCKED = 6;\n\n  /** A malloc() failed */\n  public static final int SQLITE_NOMEM = 7;\n\n  /** Attempt to write a readonly database */\n  public static final int SQLITE_READONLY = 8;\n\n  /** Operation terminated by sqlite_interrupt() */\n  public static final int SQLITE_INTERRUPT = 9;\n\n  /** Some kind of disk I/O error occurred */\n  public static final int SQLITE_IOERR = 10;\n\n  /** The database disk image is malformed */\n  public static final int SQLITE_CORRUPT = 11;\n\n  /** (Internal Only) Table or record not found */\n  public static final int SQLITE_NOTFOUND = 12;\n\n  /** Insertion failed because database is full */\n  public static final int SQLITE_FULL = 13;\n\n  /** Unable to open the database file */\n  public static final int SQLITE_CANTOPEN = 14;\n\n  /** Database lock protocol error */\n  public static final int SQLITE_PROTOCOL = 15;\n\n  /** (Internal Only) Database table is empty */\n  public static final int SQLITE_EMPTY = 16;\n\n  /** The database schema changed */\n  public static final int SQLITE_SCHEMA = 17;\n\n  /** Too much data for one row of a table */\n  public static final int SQLITE_TOOBIG = 18;\n\n  /** Abort due to constraint violation */\n  public static final int SQLITE_CONSTRAINT = 19;\n\n  /** Data type mismatch */\n  public static final int SQLITE_MISMATCH = 20;\n\n  /** Library used incorrectly */\n  public static final int SQLITE_MISUSE = 21;\n\n  /** Uses OS features not supported on host */\n  public static final int SQLITE_NOLFS = 22;\n\n  /** Authorization denied */\n  public static final int SQLITE_AUTH = 23;\n\n  /** sqlite_step() has another row ready */\n  public static final int SQLITE_ROW = 100;\n\n  /** sqlite_step() has finished executing */\n  public static final int SQLITE_DONE = 101;\n\n  // types returned by sqlite3_column_type()\n\n  public static final int SQLITE_INTEGER = 1;\n  public static final int SQLITE_FLOAT = 2;\n  public static final int SQLITE_TEXT = 3;\n  public static final int SQLITE_BLOB = 4;\n  public static final int SQLITE_NULL = 5;\n}\n"
  },
  {
    "path": "bindings/java/src/main/java/tech/turso/core/TursoConnection.java",
    "content": "package tech.turso.core;\n\nimport static tech.turso.utils.ByteArrayUtils.stringToUtf8ByteArray;\n\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.util.Properties;\nimport tech.turso.annotations.NativeInvocation;\nimport tech.turso.utils.Logger;\nimport tech.turso.utils.LoggerFactory;\nimport tech.turso.utils.TursoExceptionUtils;\n\npublic final class TursoConnection {\n\n  private static final Logger logger = LoggerFactory.getLogger(TursoConnection.class);\n\n  private final String url;\n  private final long connectionPtr;\n  private final TursoDB database;\n  private boolean closed;\n\n  // Transaction state fields\n  private boolean autoCommit = true;\n  private int transactionIsolation = Connection.TRANSACTION_SERIALIZABLE;\n  private boolean inTransaction = false;\n  private final Object transactionLock = new Object();\n\n  public TursoConnection(String url, String filePath) throws SQLException {\n    this(url, filePath, new Properties());\n  }\n\n  /**\n   * Creates a connection to turso database\n   *\n   * @param url e.g. \"jdbc:turso:fileName\"\n   * @param filePath path to file\n   */\n  public TursoConnection(String url, String filePath, Properties properties) throws SQLException {\n    this.url = url;\n    this.database = open(url, filePath, properties);\n    this.connectionPtr = this.database.connect();\n  }\n\n  /**\n   * Creates a connection using an existing TursoDB instance. This is useful for encrypted databases\n   * created with TursoDB.createWithEncryption().\n   *\n   * @param url e.g. \"jdbc:turso:fileName\"\n   * @param database an existing TursoDB instance\n   */\n  public TursoConnection(String url, TursoDB database) throws SQLException {\n    this.url = url;\n    this.database = database;\n    this.connectionPtr = this.database.connect();\n  }\n\n  private static TursoDB open(String url, String filePath, Properties properties)\n      throws SQLException {\n    return TursoDB.create(url, filePath);\n  }\n\n  public void checkOpen() throws SQLException {\n    if (isClosed()) throw new SQLException(\"database connection closed\");\n  }\n\n  public String getUrl() {\n    return url;\n  }\n\n  public void close() throws SQLException {\n    if (isClosed()) {\n      return;\n    }\n\n    // Roll back any pending transaction before closing\n    synchronized (transactionLock) {\n      if (inTransaction) {\n        try {\n          executeInternal(\"ROLLBACK\");\n        } catch (SQLException e) {\n          // Log but don't throw - we're closing anyway\n          logger.warn(\"Failed to rollback transaction during close\", e);\n        } finally {\n          inTransaction = false;\n        }\n      }\n    }\n\n    this._close(this.connectionPtr);\n    this.closed = true;\n  }\n\n  private native void _close(long connectionPtr);\n\n  private native boolean _getAutoCommit(long connectionPtr);\n\n  public boolean isClosed() throws SQLException {\n    return closed;\n  }\n\n  public TursoDB getDatabase() {\n    return database;\n  }\n\n  /**\n   * Compiles an SQL statement.\n   *\n   * @param sql An SQL statement.\n   * @return Pointer to statement.\n   * @throws SQLException if a database access error occurs.\n   */\n  public TursoStatement prepare(String sql) throws SQLException {\n    return prepare(sql, true);\n  }\n\n  /**\n   * Compiles an SQL statement with optional transaction start check.\n   *\n   * @param sql An SQL statement.\n   * @param checkTransaction Whether to check and start transaction if needed.\n   * @return Pointer to statement.\n   * @throws SQLException if a database access error occurs.\n   */\n  private TursoStatement prepare(String sql, boolean checkTransaction) throws SQLException {\n    logger.trace(\"DriverManager [{}] [SQLite EXEC] {}\", Thread.currentThread().getName(), sql);\n\n    // Ensure transaction is started if needed (lazy transaction start)\n    if (checkTransaction) {\n      ensureTransactionStarted(sql);\n    }\n\n    byte[] sqlBytes = stringToUtf8ByteArray(sql);\n    if (sqlBytes == null) {\n      throw new SQLException(\"Failed to convert \" + sql + \" into bytes\");\n    }\n    return new TursoStatement(sql, prepareUtf8(connectionPtr, sqlBytes));\n  }\n\n  private native long prepareUtf8(long connectionPtr, byte[] sqlUtf8) throws SQLException;\n\n  // TODO: check whether this is still valid for turso\n  /**\n   * Checks whether the type, concurrency, and holdability settings for a {@link ResultSet} are\n   * supported by the SQLite interface. Supported settings are:\n   *\n   * <ul>\n   *   <li>type: {@link ResultSet#TYPE_FORWARD_ONLY}\n   *   <li>concurrency: {@link ResultSet#CONCUR_READ_ONLY})\n   *   <li>holdability: {@link ResultSet#CLOSE_CURSORS_AT_COMMIT}\n   * </ul>\n   *\n   * @param resultSetType the type setting.\n   * @param resultSetConcurrency the concurrency setting.\n   * @param resultSetHoldability the holdability setting.\n   */\n  public void checkCursor(int resultSetType, int resultSetConcurrency, int resultSetHoldability)\n      throws SQLException {\n    if (resultSetType != ResultSet.TYPE_FORWARD_ONLY) {\n      throw new SQLException(\"SQLite only supports TYPE_FORWARD_ONLY cursors\");\n    }\n    if (resultSetConcurrency != ResultSet.CONCUR_READ_ONLY) {\n      throw new SQLException(\"SQLite only supports CONCUR_READ_ONLY cursors\");\n    }\n    if (resultSetHoldability != ResultSet.CLOSE_CURSORS_AT_COMMIT) {\n      throw new SQLException(\"SQLite only supports closing cursors at commit\");\n    }\n  }\n\n  /**\n   * Sets the auto-commit mode for this connection.\n   *\n   * <p>When auto-commit is enabled (the default), each SQL statement is committed automatically\n   * upon completion. When auto-commit is disabled, statements are grouped into transactions that\n   * must be explicitly committed or rolled back.\n   *\n   * <p>If this method is called to enable auto-commit while a transaction is active, the current\n   * transaction is committed first.\n   *\n   * @param autoCommit true to enable auto-commit mode; false to disable it\n   * @throws SQLException if a database access error occurs or the connection is closed\n   */\n  public void setAutoCommit(boolean autoCommit) throws SQLException {\n    synchronized (transactionLock) {\n      checkOpen();\n\n      if (this.autoCommit == autoCommit) {\n        return; // No-op if already in desired mode\n      }\n\n      // If enabling autocommit and there's a pending transaction, commit it\n      if (autoCommit && inTransaction) {\n        commit();\n      }\n\n      this.autoCommit = autoCommit;\n    }\n  }\n\n  /**\n   * Gets the current auto-commit mode for this connection.\n   *\n   * @return true if auto-commit mode is enabled; false otherwise\n   * @throws SQLException if a database access error occurs or the connection is closed\n   */\n  public boolean getAutoCommit() throws SQLException {\n    checkOpen();\n    return autoCommit;\n  }\n\n  /**\n   * Commits the current transaction.\n   *\n   * @throws SQLException if in auto-commit mode, closed, or database error occurs.\n   */\n  public void commit() throws SQLException {\n    synchronized (transactionLock) {\n      checkOpen();\n      if (autoCommit) {\n        throw new SQLException(\"Cannot commit in autocommit mode.\");\n      }\n\n      if (inTransaction) {\n        executeInternal(\"COMMIT\");\n        inTransaction = false;\n      }\n    }\n  }\n\n  /**\n   * Rolls back the current transaction.\n   *\n   * @throws SQLException if in auto-commit mode, closed, or database error occurs.\n   */\n  public void rollback() throws SQLException {\n    synchronized (transactionLock) {\n      checkOpen();\n      if (autoCommit) {\n        throw new SQLException(\"Cannot rollback in autocommit mode.\");\n      }\n\n      if (inTransaction) {\n        executeInternal(\"ROLLBACK\");\n        inTransaction = false;\n      }\n    }\n  }\n\n  /**\n   * Lazy transaction starter. Starts a transaction if one isn't active, auto-commit is disabled,\n   * and the statement isn't a control command.\n   */\n  private void ensureTransactionStarted(String sql) throws SQLException {\n    if (autoCommit || inTransaction) return;\n\n    // Avoid recursive start for control statements\n    String trimmed = sql.trim().toUpperCase();\n    if (trimmed.startsWith(\"BEGIN\")\n        || trimmed.startsWith(\"COMMIT\")\n        || trimmed.startsWith(\"ROLLBACK\")) {\n      return;\n    }\n\n    String beginMode = TursoTransactionMode.fromIsolationLevel(transactionIsolation).getSql();\n    executeInternal(beginMode);\n    inTransaction = true;\n  }\n\n  /**\n   * Internal helper to execute transaction control statements without triggering recursive\n   * transaction checks.\n   */\n  private void executeInternal(String sql) throws SQLException {\n    try (TursoStatement stmt = prepare(sql, false)) {\n      stmt.execute();\n    }\n  }\n\n  /**\n   * Sets the transaction isolation level.\n   *\n   * @param level one of the following {@code Connection} constants: {@code\n   *     Connection.TRANSACTION_READ_UNCOMMITTED}, {@code Connection.TRANSACTION_READ_COMMITTED},\n   *     {@code Connection.TRANSACTION_REPEATABLE_READ}, or {@code\n   *     Connection.TRANSACTION_SERIALIZABLE}.\n   * @throws SQLException if a database access error occurs, this method is called on a closed\n   *     connection or the given parameter is not one of the {@code Connection} constants\n   */\n  public void setTransactionIsolation(int level) throws SQLException {\n    synchronized (transactionLock) {\n      checkOpen();\n\n      if (inTransaction) {\n        throw new SQLException(\"Cannot change isolation level while transaction is active.\");\n      }\n\n      if (level != Connection.TRANSACTION_READ_UNCOMMITTED\n          && level != Connection.TRANSACTION_READ_COMMITTED\n          && level != Connection.TRANSACTION_REPEATABLE_READ\n          && level != Connection.TRANSACTION_SERIALIZABLE) {\n        throw new SQLException(\"Invalid transaction isolation level: \" + level);\n      }\n\n      this.transactionIsolation = level;\n    }\n  }\n\n  /**\n   * Retrieves the current transaction isolation level.\n   *\n   * @return the current transaction isolation level\n   * @throws SQLException if a database access error occurs or the connection is closed\n   */\n  public int getTransactionIsolation() throws SQLException {\n    checkOpen();\n    return transactionIsolation;\n  }\n\n  /**\n   * Throws formatted SQLException with error code and message.\n   *\n   * @param errorCode Error code.\n   * @param errorMessageBytes Error message.\n   */\n  @NativeInvocation(invokedFrom = \"turso_connection.rs\")\n  private void throwTursoException(int errorCode, byte[] errorMessageBytes) throws SQLException {\n    TursoExceptionUtils.throwTursoException(errorCode, errorMessageBytes);\n  }\n}\n"
  },
  {
    "path": "bindings/java/src/main/java/tech/turso/core/TursoDB.java",
    "content": "package tech.turso.core;\n\nimport static tech.turso.utils.ByteArrayUtils.stringToUtf8ByteArray;\n\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.sql.SQLException;\nimport tech.turso.TursoErrorCode;\nimport tech.turso.annotations.NativeInvocation;\nimport tech.turso.annotations.Nullable;\nimport tech.turso.annotations.VisibleForTesting;\nimport tech.turso.utils.Logger;\nimport tech.turso.utils.LoggerFactory;\nimport tech.turso.utils.TursoExceptionUtils;\n\n/** This class provides a thin JNI layer over the SQLite3 C API. */\npublic final class TursoDB implements AutoCloseable {\n\n  private static final Logger logger = LoggerFactory.getLogger(TursoDB.class);\n  // Pointer to database instance\n  private long dbPointer;\n  private boolean isOpen;\n\n  private final String url;\n  private final String filePath;\n\n  static {\n    if (\"The Android Project\".equals(System.getProperty(\"java.vm.vendor\"))) {\n      // TODO\n    } else {\n      // continue with non Android execution path\n    }\n  }\n\n  /**\n   * Enum representing different architectures and their corresponding library paths and file\n   * extensions.\n   */\n  private enum Architecture {\n    MACOS_ARM64(\"libs/macos_arm64/lib_turso_java.dylib\", \".dylib\"),\n    MACOS_X86(\"libs/macos_x86/lib_turso_java.dylib\", \".dylib\"),\n    LINUX_X86(\"libs/linux_x86/lib_turso_java.so\", \".so\"),\n    WINDOWS(\"libs/windows/lib_turso_java.dll\", \".dll\"),\n    UNSUPPORTED(\"\", \"\");\n\n    private final String libPath;\n    private final String fileExtension;\n\n    Architecture(String libPath, String fileExtension) {\n      this.libPath = libPath;\n      this.fileExtension = fileExtension;\n    }\n\n    public String getLibPath() {\n      return libPath;\n    }\n\n    public String getFileExtension() {\n      return fileExtension;\n    }\n\n    public static Architecture detect() {\n      String osName = System.getProperty(\"os.name\").toLowerCase();\n      String osArch = System.getProperty(\"os.arch\").toLowerCase();\n\n      // TODO: add support for arm64 on Linux\n      if (osName.contains(\"linux\")) {\n        if (osArch.contains(\"aarch64\") || osArch.contains(\"arm64\")) {\n          throw new UnsupportedOperationException(\n              \"ARM64 architecture is not supported on Linux yet\");\n        } else if (osArch.contains(\"x86_64\") || osArch.contains(\"amd64\")) {\n          return LINUX_X86;\n        }\n      }\n\n      if (osName.contains(\"mac\")) {\n        if (osArch.contains(\"aarch64\") || osArch.contains(\"arm64\")) {\n          return MACOS_ARM64;\n        } else if (osArch.contains(\"x86_64\") || osArch.contains(\"amd64\")) {\n          return MACOS_X86;\n        }\n      } else if (osName.contains(\"win\")) {\n        return WINDOWS;\n      }\n\n      return UNSUPPORTED;\n    }\n  }\n\n  /**\n   * This method attempts to load the native library required for turso operations. It first tries\n   * to load the library from the system's library path using {@link #loadFromSystemPath()}. If that\n   * fails, it attempts to load the library from the JAR file using {@link #loadFromJar()}. If\n   * either method succeeds, the `isLoaded` flag is set to true. If both methods fail, an {@link\n   * InternalError} is thrown indicating that the necessary native library could not be loaded.\n   *\n   * @throws InternalError if the native library cannot be loaded from either the system path or the\n   *     JAR file.\n   */\n  private static void load() {\n    new SingletonHolder();\n  }\n\n  // \"lazy initialization holder class idiom\" (Effective Java #83)\n  private static class SingletonHolder {\n    static {\n      if (!loadFromSystemPath() && !loadFromJar()) {\n        throw new InternalError(\"Unable to load necessary native library\");\n      }\n    }\n  }\n\n  /**\n   * Load the native library from the system path.\n   *\n   * <p>This method attempts to load the native library named \"_turso_java\" from the system's\n   * library path. If the library is successfully loaded, the `isLoaded` flag is set to true.\n   *\n   * @return true if the library was successfully loaded, false otherwise.\n   */\n  private static boolean loadFromSystemPath() {\n    try {\n      System.loadLibrary(\"_turso_java\");\n      return true;\n    } catch (Throwable t) {\n      logger.info(\"Unable to load from default path: {}\", String.valueOf(t));\n    }\n\n    return false;\n  }\n\n  /**\n   * Load the native library from the JAR file.\n   *\n   * <p>By default, native libraries are packaged within the JAR file. This method extracts the\n   * appropriate native library for the current operating system and architecture from the JAR and\n   * loads it.\n   *\n   * @return true if the library was successfully loaded, false otherwise.\n   */\n  private static boolean loadFromJar() {\n    Architecture arch = Architecture.detect();\n    if (arch == Architecture.UNSUPPORTED) {\n      logger.info(\"Unsupported OS or architecture\");\n      return false;\n    }\n\n    try {\n      InputStream is = TursoDB.class.getClassLoader().getResourceAsStream(arch.getLibPath());\n      assert is != null;\n      File file = convertInputStreamToFile(is, arch);\n      System.load(file.getPath());\n      return true;\n    } catch (Throwable t) {\n      logger.info(\"Unable to load from jar: {}\", String.valueOf(t));\n    }\n\n    return false;\n  }\n\n  private static File convertInputStreamToFile(InputStream is, Architecture arch)\n      throws IOException {\n    File tempFile = File.createTempFile(\"lib\", arch.getFileExtension());\n    tempFile.deleteOnExit();\n\n    try (FileOutputStream os = new FileOutputStream(tempFile)) {\n      int read;\n      byte[] bytes = new byte[1024];\n\n      while ((read = is.read(bytes)) != -1) {\n        os.write(bytes, 0, read);\n      }\n    }\n\n    return tempFile;\n  }\n\n  /**\n   * @param url eTurso.gTursoTurso. \"jdbc:turso:fileName\n   * @param filePath e.g. path to file\n   */\n  public static TursoDB create(String url, String filePath) throws SQLException {\n    return new TursoDB(url, filePath, null, null);\n  }\n\n  /**\n   * Creates a TursoDB instance with encryption support.\n   *\n   * @param url e.g. \"jdbc:turso:fileName\"\n   * @param filePath e.g. path to file\n   * @param cipher the encryption cipher to use\n   * @param hexkey the hex-encoded encryption key\n   */\n  public static TursoDB createWithEncryption(\n      String url, String filePath, TursoEncryptionCipher cipher, String hexkey)\n      throws SQLException {\n    return new TursoDB(url, filePath, cipher, hexkey);\n  }\n\n  // TODO: receive config as argument\n  private TursoDB(\n      String url, String filePath, @Nullable TursoEncryptionCipher cipher, @Nullable String hexkey)\n      throws SQLException {\n    this.url = url;\n    this.filePath = filePath;\n    load();\n    open(0, cipher != null ? cipher.getValue() : null, hexkey);\n  }\n\n  // TODO: add support for JNI\n  public native void interrupt();\n\n  public boolean isClosed() {\n    return !this.isOpen;\n  }\n\n  public boolean isOpen() {\n    return this.isOpen;\n  }\n\n  private void open(int openFlags, @Nullable String cipher, @Nullable String hexkey)\n      throws SQLException {\n    open0(filePath, openFlags, cipher, hexkey);\n  }\n\n  private void open0(\n      String filePath, int openFlags, @Nullable String cipher, @Nullable String hexkey)\n      throws SQLException {\n    byte[] filePathBytes = stringToUtf8ByteArray(filePath);\n    if (filePathBytes == null) {\n      throw TursoExceptionUtils.buildTursoException(\n          TursoErrorCode.TURSO_ETC.code,\n          \"File path cannot be converted to byteArray. File name: \" + filePath);\n    }\n\n    if (cipher != null && hexkey != null) {\n      dbPointer = openWithEncryptionUtf8(filePathBytes, openFlags, cipher, hexkey);\n    } else {\n      dbPointer = openUtf8(filePathBytes, openFlags);\n    }\n    isOpen = true;\n  }\n\n  private native long openUtf8(byte[] file, int openFlags) throws SQLException;\n\n  private native long openWithEncryptionUtf8(\n      byte[] file, int openFlags, String cipher, String hexkey) throws SQLException;\n\n  public long connect() throws SQLException {\n    return connect0(dbPointer);\n  }\n\n  private native long connect0(long databasePtr) throws SQLException;\n\n  @Override\n  public void close() throws Exception {\n    if (!isOpen) return;\n\n    close0(dbPointer);\n    isOpen = false;\n  }\n\n  private native void close0(long databasePtr) throws SQLException;\n\n  @VisibleForTesting\n  native void throwJavaException(int errorCode) throws SQLException;\n\n  /**\n   * Throws formatted SQLException with error code and message.\n   *\n   * @param errorCode Error code.\n   * @param errorMessageBytes Error message.\n   */\n  @NativeInvocation(invokedFrom = \"turso_db.rs\")\n  private void throwTursoException(int errorCode, byte[] errorMessageBytes) throws SQLException {\n    TursoExceptionUtils.throwTursoException(errorCode, errorMessageBytes);\n  }\n}\n"
  },
  {
    "path": "bindings/java/src/main/java/tech/turso/core/TursoEncryptionCipher.java",
    "content": "package tech.turso.core;\n\n/** Supported encryption ciphers for local database encryption. */\npublic enum TursoEncryptionCipher {\n  /** AES-128-GCM cipher */\n  AES_128_GCM(\"aes128gcm\"),\n  /** AES-256-GCM cipher */\n  AES_256_GCM(\"aes256gcm\"),\n  /** AEGIS-256 cipher */\n  AEGIS_256(\"aegis256\"),\n  /** AEGIS-256X2 cipher */\n  AEGIS_256X2(\"aegis256x2\"),\n  /** AEGIS-128L cipher */\n  AEGIS_128L(\"aegis128l\"),\n  /** AEGIS-128X2 cipher */\n  AEGIS_128X2(\"aegis128x2\"),\n  /** AEGIS-128X4 cipher */\n  AEGIS_128X4(\"aegis128x4\");\n\n  private final String value;\n\n  TursoEncryptionCipher(String value) {\n    this.value = value;\n  }\n\n  /** Returns the string value of the cipher for internal use. */\n  public String getValue() {\n    return value;\n  }\n}\n"
  },
  {
    "path": "bindings/java/src/main/java/tech/turso/core/TursoPropertiesHolder.java",
    "content": "package tech.turso.core;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.Properties;\nimport tech.turso.jdbc4.JDBC4DatabaseMetaData;\nimport tech.turso.utils.Logger;\nimport tech.turso.utils.LoggerFactory;\n\npublic class TursoPropertiesHolder {\n\n  private static final Logger logger = LoggerFactory.getLogger(TursoPropertiesHolder.class);\n\n  private static String driverName = \"\";\n  private static String driverVersion = \"\";\n\n  static {\n    try (InputStream tursoJdbcPropStream =\n        JDBC4DatabaseMetaData.class\n            .getClassLoader()\n            .getResourceAsStream(\"turso-jdbc.properties\"); ) {\n      if (tursoJdbcPropStream == null) {\n        throw new IOException(\"Cannot load turso-jdbc.properties from jar\");\n      }\n\n      final Properties properties = new Properties();\n      properties.load(tursoJdbcPropStream);\n      driverName = properties.getProperty(\"driverName\");\n      driverVersion = properties.getProperty(\"driverVersion\");\n    } catch (IOException e) {\n      logger.error(\"Failed to load driverName and driverVersion\");\n    }\n  }\n\n  public static String getDriverName() {\n    return driverName;\n  }\n\n  public static String getDriverVersion() {\n    return driverVersion;\n  }\n}\n"
  },
  {
    "path": "bindings/java/src/main/java/tech/turso/core/TursoResultSet.java",
    "content": "package tech.turso.core;\n\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport tech.turso.annotations.Nullable;\nimport tech.turso.utils.Logger;\nimport tech.turso.utils.LoggerFactory;\n\n/**\n * A table of data representing turso database result set, which is generated by executing a\n * statement that queries the database.\n *\n * <p>A {@link TursoResultSet} object is automatically closed when the {@link TursoStatement} object\n * that generated it is closed or re-executed.\n */\npublic final class TursoResultSet {\n\n  private static final Logger log = LoggerFactory.getLogger(TursoResultSet.class);\n\n  private final TursoStatement statement;\n\n  // Name of the columns\n  private String[] columnNames = new String[0];\n  // Whether the result set does not have any rows.\n  private boolean isEmptyResultSet = false;\n  // If the result set is open. Doesn't mean it has results.\n  private boolean open;\n  // Maximum number of rows as set by the statement\n  private long maxRows;\n  // number of current row, starts at 1 (0 is used to represent loading data)\n  private int row = 0;\n  private boolean pastLastRow = false;\n\n  @Nullable private TursoStepResult lastStepResult;\n\n  public static TursoResultSet of(TursoStatement statement) {\n    return new TursoResultSet(statement);\n  }\n\n  private TursoResultSet(TursoStatement statement) {\n    this.open = true;\n    this.statement = statement;\n  }\n\n  /**\n   * Consumes all the rows in this {@link ResultSet} until the {@link #next()} method returns\n   * `false`.\n   *\n   * @throws SQLException if the result set is not open or if an error occurs while iterating.\n   */\n  public void consumeAll() throws SQLException {\n    if (!open) {\n      throw new SQLException(\"The result set is not open\");\n    }\n\n    while (next()) {}\n  }\n\n  /**\n   * Moves the cursor forward one row from its current position. A {@link TursoResultSet} cursor is\n   * initially positioned before the first fow; the first call to the method <code>next</code> makes\n   * the first row the current row; the second call makes the second row the current row, and so on.\n   * When a call to the <code>next</code> method returns <code>false</code>, the cursor is\n   * positioned after the last row.\n   *\n   * <p>Note that turso only supports <code>ResultSet.TYPE_FORWARD_ONLY</code>, which means that the\n   * cursor can only move forward.\n   *\n   * @return true if the new current row is valid; false if there are no more rows\n   * @throws SQLException if a database access error occurs\n   */\n  public boolean next() throws SQLException {\n    if (!open) {\n      throw new SQLException(\"The resultSet is not open\");\n    }\n\n    if (isEmptyResultSet || pastLastRow) {\n      return false; // completed ResultSet\n    }\n\n    if (maxRows != 0 && row == maxRows) {\n      return false;\n    }\n\n    lastStepResult = this.statement.step();\n    log.debug(\"lastStepResult: {}\", lastStepResult);\n    if (lastStepResult.isRow()) {\n      row++;\n    }\n\n    if (lastStepResult.isInInvalidState()) {\n      open = false;\n      String errorMessage = lastStepResult.getErrorMessage();\n      if (errorMessage != null && !errorMessage.isEmpty()) {\n        throw new SQLException(\"step() returned invalid result: \" + errorMessage);\n      } else {\n        throw new SQLException(\"step() returned invalid result: \" + lastStepResult);\n      }\n    }\n\n    pastLastRow = lastStepResult.isDone();\n    if (pastLastRow && row == 0) {\n      isEmptyResultSet = true;\n    }\n    return !pastLastRow;\n  }\n\n  /** Checks whether the last step result has returned row result. */\n  public boolean hasLastStepReturnedRow() {\n    return lastStepResult != null && lastStepResult.isRow();\n  }\n\n  /** Checks whether the cursor is positioned after the last row. */\n  public boolean isPastLastRow() {\n    return pastLastRow;\n  }\n\n  /** Checks whether the result set is empty (has no rows). */\n  public boolean isEmpty() {\n    return isEmptyResultSet;\n  }\n\n  /** Gets the current row number (0-based, 0 means before first row). */\n  public int getRow() {\n    return row;\n  }\n\n  /**\n   * Checks the status of the result set.\n   *\n   * @return true if it's ready to iterate over the result set; false otherwise.\n   */\n  public boolean isOpen() {\n    return open;\n  }\n\n  /** @throws SQLException if not {@link #open} */\n  public void checkOpen() throws SQLException {\n    if (!open) {\n      throw new SQLException(\"ResultSet closed\");\n    }\n  }\n\n  public void close() throws SQLException {\n    this.open = false;\n  }\n\n  public Object get(String columnName) throws SQLException {\n    final int columnsLength = this.columnNames.length;\n    for (int i = 0; i < columnsLength; i++) {\n      if (this.columnNames[i].equals(columnName)) {\n        return get(i + 1);\n      }\n    }\n\n    throw new SQLException(\"column name \" + columnName + \" not found\");\n  }\n\n  public Object get(int columnIndex) throws SQLException {\n    if (!this.isOpen()) {\n      throw new SQLException(\"ResultSet is not open\");\n    }\n\n    if (this.lastStepResult == null || this.lastStepResult.getResult() == null) {\n      throw new SQLException(\"ResultSet is null\");\n    }\n\n    final Object[] resultSet = this.lastStepResult.getResult();\n    if (columnIndex > resultSet.length || columnIndex < 0) {\n      throw new SQLException(\"columnIndex out of bound\");\n    }\n\n    return resultSet[columnIndex - 1];\n  }\n\n  public String[] getColumnNames() {\n    return this.columnNames;\n  }\n\n  public void setColumnNames(String[] columnNames) {\n    this.columnNames = columnNames;\n  }\n\n  @Override\n  public String toString() {\n    return (\"tursoResultSet{\"\n        + \"statement=\"\n        + statement\n        + \", isEmptyResultSet=\"\n        + isEmptyResultSet\n        + \", open=\"\n        + open\n        + \", maxRows=\"\n        + maxRows\n        + \", row=\"\n        + row\n        + \", pastLastRow=\"\n        + pastLastRow\n        + \", lastResult=\"\n        + lastStepResult\n        + '}');\n  }\n}\n"
  },
  {
    "path": "bindings/java/src/main/java/tech/turso/core/TursoStatement.java",
    "content": "package tech.turso.core;\n\nimport java.sql.SQLException;\nimport tech.turso.annotations.NativeInvocation;\nimport tech.turso.annotations.Nullable;\nimport tech.turso.utils.Logger;\nimport tech.turso.utils.LoggerFactory;\nimport tech.turso.utils.TursoExceptionUtils;\n\n/**\n * By default, only one <code>resultSet</code> object per <code>TursoStatement</code> can be open at\n * the same time. Therefore, if the reading of one <code>resultSet</code> object is interleaved with\n * the reading of another, each must have been generated by different <code>TursoStatement</code>\n * objects. All execution method in the <code>TursoStatement</code> implicitly close the current\n * <code>resultSet</code> object of the statement if an open one exists.\n */\npublic final class TursoStatement implements AutoCloseable {\n\n  private static final Logger log = LoggerFactory.getLogger(TursoStatement.class);\n\n  private final String sql;\n  private final long statementPointer;\n  private TursoResultSet resultSet;\n\n  private boolean closed;\n\n  // TODO: what if the statement we ran was DDL, update queries and etc. Should we still create a\n  // resultSet?\n  public TursoStatement(String sql, long statementPointer) {\n    this.sql = sql;\n    this.statementPointer = statementPointer;\n    this.resultSet = TursoResultSet.of(this);\n    log.debug(\"Creating statement with sql: {}\", this.sql);\n  }\n\n  public TursoResultSet getResultSet() {\n    return resultSet;\n  }\n\n  /**\n   * Expects a clean statement created right after prepare method is called.\n   *\n   * @return true if the ResultSet has at least one row; false otherwise.\n   */\n  public boolean execute() throws SQLException {\n    resultSet.next();\n    return resultSet.hasLastStepReturnedRow();\n  }\n\n  TursoStepResult step() throws SQLException {\n    final TursoStepResult result = step(this.statementPointer);\n    if (result == null) {\n      throw new SQLException(\"step() returned null, which is only returned when an error occurs\");\n    }\n\n    return result;\n  }\n\n  /**\n   * Because turso supports async I/O, it is possible to return a {@link TursoStepResult} with\n   * {@link TursoStepResult#STEP_RESULT_ID_ROW}. However, this is handled by the native side, so you\n   * can expect that this method will not return a {@link TursoStepResult#STEP_RESULT_ID_ROW}.\n   */\n  @Nullable\n  private native TursoStepResult step(long stmtPointer) throws SQLException;\n\n  /**\n   * Throws formatted SQLException with error code and message.\n   *\n   * @param errorCode Error code.\n   * @param errorMessageBytes Error message.\n   */\n  @NativeInvocation(invokedFrom = \"turso_statement.rs\")\n  private void throwTursoException(int errorCode, byte[] errorMessageBytes) throws SQLException {\n    TursoExceptionUtils.throwTursoException(errorCode, errorMessageBytes);\n  }\n\n  /**\n   * Closes the current statement and releases any resources associated with it. This method calls\n   * the native `_close` method to perform the actual closing operation.\n   */\n  public void close() throws SQLException {\n    if (closed) {\n      return;\n    }\n    this.resultSet.close();\n    _close(statementPointer);\n    closed = true;\n  }\n\n  private native void _close(long statementPointer);\n\n  /**\n   * Initializes the column metadata, such as the names of the columns. Since {@link TursoStatement}\n   * can only have a single {@link TursoResultSet}, it is appropriate to place the initialization of\n   * column metadata here.\n   *\n   * @throws SQLException if a database access error occurs while retrieving column names\n   */\n  public void initializeColumnMetadata() throws SQLException {\n    final String[] columnNames = this.columns(statementPointer);\n    if (columnNames != null) {\n      this.resultSet.setColumnNames(columnNames);\n    }\n  }\n\n  @Nullable\n  private native String[] columns(long statementPointer) throws SQLException;\n\n  /**\n   * Binds a NULL value to the prepared statement at the specified position.\n   *\n   * @param position The index of the SQL parameter to be set to NULL.\n   * @return <a href=\"https://www.sqlite.org/c3ref/c_abort.html\">Result Codes</a>\n   * @throws SQLException If a database access error occurs.\n   */\n  public int bindNull(int position) throws SQLException {\n    final int result = bindNull(statementPointer, position);\n    if (result != 0) {\n      throw new SQLException(\"Exception while binding NULL value at position \" + position);\n    }\n    return result;\n  }\n\n  private native int bindNull(long statementPointer, int position) throws SQLException;\n\n  /**\n   * Binds an integer value to the prepared statement at the specified position. This function calls\n   * bindLong because turso treats all integers as long (as well as SQLite).\n   *\n   * <p>According to SQLite documentation, the value is a signed integer, stored in 0, 1, 2, 3, 4,\n   * 6, or 8 bytes depending on the magnitude of the value.\n   *\n   * @param position The index of the SQL parameter to be set.\n   * @param value The integer value to bind to the parameter.\n   * @return A result code indicating the success or failure of the operation.\n   * @throws SQLException If a database access error occurs.\n   */\n  public int bindInt(int position, int value) throws SQLException {\n    return bindLong(position, value);\n  }\n\n  /**\n   * Binds a long value to the prepared statement at the specified position.\n   *\n   * @param position The index of the SQL parameter to be set.\n   * @param value The value to bind to the parameter.\n   * @return <a href=\"https://www.sqlite.org/c3ref/c_abort.html\">Result Codes</a>\n   * @throws SQLException If a database access error occurs.\n   */\n  public int bindLong(int position, long value) throws SQLException {\n    final int result = bindLong(statementPointer, position, value);\n    if (result != 0) {\n      throw new SQLException(\"Exception while binding long value at position \" + position);\n    }\n    return result;\n  }\n\n  private native int bindLong(long statementPointer, int position, long value) throws SQLException;\n\n  /**\n   * Binds a double value to the prepared statement at the specified position.\n   *\n   * @param position The index of the SQL parameter to be set.\n   * @param value The value to bind to the parameter.\n   * @return <a href=\"https://www.sqlite.org/c3ref/c_abort.html\">Result Codes</a>\n   * @throws SQLException If a database access error occurs.\n   */\n  public int bindDouble(int position, double value) throws SQLException {\n    final int result = bindDouble(statementPointer, position, value);\n    if (result != 0) {\n      throw new SQLException(\"Exception while binding double value at position \" + position);\n    }\n    return result;\n  }\n\n  private native int bindDouble(long statementPointer, int position, double value)\n      throws SQLException;\n\n  /**\n   * Binds a text value to the prepared statement at the specified position.\n   *\n   * @param position The index of the SQL parameter to be set.\n   * @param value The value to bind to the parameter.\n   * @return <a href=\"https://www.sqlite.org/c3ref/c_abort.html\">Result Codes</a>\n   * @throws SQLException If a database access error occurs.\n   */\n  public int bindText(int position, String value) throws SQLException {\n    final int result = bindText(statementPointer, position, value);\n    if (result != 0) {\n      throw new SQLException(\"Exception while binding text value at position \" + position);\n    }\n    return result;\n  }\n\n  private native int bindText(long statementPointer, int position, String value)\n      throws SQLException;\n\n  /**\n   * Binds a blob value to the prepared statement at the specified position.\n   *\n   * @param position The index of the SQL parameter to be set.\n   * @param value The value to bind to the parameter.\n   * @return <a href=\"https://www.sqlite.org/c3ref/c_abort.html\">Result Codes</a>\n   * @throws SQLException If a database access error occurs.\n   */\n  public int bindBlob(int position, byte[] value) throws SQLException {\n    final int result = bindBlob(statementPointer, position, value);\n    if (result != 0) {\n      throw new SQLException(\"Exception while binding blob value at position \" + position);\n    }\n    return result;\n  }\n\n  private native int bindBlob(long statementPointer, int position, byte[] value)\n      throws SQLException;\n\n  public void bindObject(int parameterIndex, Object x) throws SQLException {\n    if (x == null) {\n      this.bindNull(parameterIndex);\n      return;\n    }\n    if (x instanceof Byte) {\n      this.bindInt(parameterIndex, (Byte) x);\n    } else if (x instanceof Short) {\n      this.bindInt(parameterIndex, (Short) x);\n    } else if (x instanceof Integer) {\n      this.bindInt(parameterIndex, (Integer) x);\n    } else if (x instanceof Long) {\n      this.bindLong(parameterIndex, (Long) x);\n    } else if (x instanceof String) {\n      bindText(parameterIndex, (String) x);\n    } else if (x instanceof Float) {\n      bindDouble(parameterIndex, (Float) x);\n    } else if (x instanceof Double) {\n      bindDouble(parameterIndex, (Double) x);\n    } else if (x instanceof byte[]) {\n      bindBlob(parameterIndex, (byte[]) x);\n    } else {\n      throw new SQLException(\"Unsupported object type in bindObject: \" + x.getClass().getName());\n    }\n  }\n\n  /**\n   * Returns total number of changes.\n   *\n   * @throws SQLException If a database access error occurs\n   */\n  public long totalChanges() throws SQLException {\n    final long result = totalChanges(statementPointer);\n    if (result == -1) {\n      throw new SQLException(\"Exception while retrieving total number of changes\");\n    }\n\n    return result;\n  }\n\n  private native long totalChanges(long statementPointer) throws SQLException;\n\n  /**\n   * Returns number of changes.\n   *\n   * @throws SQLException If a database access error occurs\n   */\n  public long changes() throws SQLException {\n    final long result = changes(statementPointer);\n    if (result == -1) {\n      throw new SQLException(\"Exception while retrieving number of changes\");\n    }\n\n    return result;\n  }\n\n  private native long changes(long statementPointer) throws SQLException;\n\n  /**\n   * Returns the number of parameters in this statement. Parameters are the `?`'s that get replaced\n   * by the provided arguments.\n   *\n   * @throws SQLException If a database access error occurs\n   */\n  public int parameterCount() throws SQLException {\n    final int result = parameterCount(statementPointer);\n    if (result == -1) {\n      throw new SQLException(\"Exception while retrieving parameter count\");\n    }\n\n    return result;\n  }\n\n  private native int parameterCount(long statementPointer) throws SQLException;\n\n  /** Resets this statement so it's ready for re-execution */\n  public void reset() throws SQLException {\n    final int result = reset(statementPointer);\n    if (result == -1) {\n      throw new SQLException(\"Exception while resetting statement\");\n    }\n    this.resultSet = TursoResultSet.of(this);\n  }\n\n  private native int reset(long statementPointer) throws SQLException;\n\n  /**\n   * Checks if the statement is closed.\n   *\n   * @return true if the statement is closed, false otherwise.\n   */\n  public boolean isClosed() {\n    return closed;\n  }\n\n  @Override\n  public String toString() {\n    return (\"tursoStatement{\"\n        + \"statementPointer=\"\n        + statementPointer\n        + \", sql='\"\n        + sql\n        + '\\''\n        + '}');\n  }\n}\n"
  },
  {
    "path": "bindings/java/src/main/java/tech/turso/core/TursoStepResult.java",
    "content": "package tech.turso.core;\n\nimport java.util.Arrays;\nimport tech.turso.annotations.NativeInvocation;\nimport tech.turso.annotations.Nullable;\n\n/** Represents the step result of turso's statement's step function. */\npublic final class TursoStepResult {\n\n  private static final int STEP_RESULT_ID_ROW = 10;\n  private static final int STEP_RESULT_ID_IO = 20;\n  private static final int STEP_RESULT_ID_DONE = 30;\n  private static final int STEP_RESULT_ID_INTERRUPT = 40;\n  // Indicates that the database file could not be written because of concurrent activity by some\n  // other connection\n  private static final int STEP_RESULT_ID_BUSY = 50;\n  private static final int STEP_RESULT_ID_ERROR = 60;\n\n  // Identifier for Turso's StepResult\n  private final int stepResultId;\n\n  @Nullable private final Object[] result;\n\n  @Nullable private final String errorMessage;\n\n  @NativeInvocation(invokedFrom = \"turso_statement.rs\")\n  public TursoStepResult(int stepResultId) {\n    this.stepResultId = stepResultId;\n    this.result = null;\n    this.errorMessage = null;\n  }\n\n  @NativeInvocation(invokedFrom = \"turso_statement.rs\")\n  public TursoStepResult(int stepResultId, Object[] result) {\n    this.stepResultId = stepResultId;\n    this.result = result;\n    this.errorMessage = null;\n  }\n\n  @NativeInvocation(invokedFrom = \"turso_statement.rs\")\n  public TursoStepResult(int stepResultId, String errorMessage) {\n    this.stepResultId = stepResultId;\n    this.result = null;\n    this.errorMessage = errorMessage;\n  }\n\n  public boolean isRow() {\n    return stepResultId == STEP_RESULT_ID_ROW;\n  }\n\n  public boolean isDone() {\n    return stepResultId == STEP_RESULT_ID_DONE;\n  }\n\n  public boolean isInInvalidState() {\n    // current implementation doesn't allow STEP_RESULT_ID_IO to be returned\n    return (stepResultId == STEP_RESULT_ID_IO\n        || stepResultId == STEP_RESULT_ID_INTERRUPT\n        || stepResultId == STEP_RESULT_ID_BUSY\n        || stepResultId == STEP_RESULT_ID_ERROR);\n  }\n\n  @Nullable\n  public Object[] getResult() {\n    return result;\n  }\n\n  @Nullable\n  public String getErrorMessage() {\n    return errorMessage;\n  }\n\n  @Override\n  public String toString() {\n    return (\"tursoStepResult{\"\n        + \"stepResultName=\"\n        + getStepResultName()\n        + \", result=\"\n        + Arrays.toString(result)\n        + '}');\n  }\n\n  private String getStepResultName() {\n    switch (stepResultId) {\n      case STEP_RESULT_ID_ROW:\n        return \"ROW\";\n      case STEP_RESULT_ID_IO:\n        return \"IO\";\n      case STEP_RESULT_ID_DONE:\n        return \"DONE\";\n      case STEP_RESULT_ID_INTERRUPT:\n        return \"INTERRUPT\";\n      case STEP_RESULT_ID_BUSY:\n        return \"BUSY\";\n      case STEP_RESULT_ID_ERROR:\n        return \"ERROR\";\n      default:\n        return \"UNKNOWN\";\n    }\n  }\n}\n"
  },
  {
    "path": "bindings/java/src/main/java/tech/turso/core/TursoTransactionMode.java",
    "content": "package tech.turso.core;\n\nimport java.sql.Connection;\n\n/**\n * Defines the transaction modes supported by Turso (SQLite).\n *\n * <p>Transactions can be DEFERRED, IMMEDIATE, or EXCLUSIVE. The default behavior is DEFERRED.\n */\npublic enum TursoTransactionMode {\n\n  /**\n   * The transaction does not actually start until the database is first accessed.\n   *\n   * <p>Internally, the {@code BEGIN DEFERRED} statement merely sets a flag on the database\n   * connection that turns off the automatic commit that would normally occur when the last\n   * statement finishes.\n   *\n   * <p>If the first statement after {@code BEGIN DEFERRED} is a {@code SELECT}, then a read\n   * transaction is started. Subsequent write statements will upgrade the transaction to a write\n   * transaction if possible, or return {@code SQLITE_BUSY}. This maximizes concurrency for\n   * read-heavy workloads.\n   */\n  DEFERRED(\"BEGIN DEFERRED\"),\n\n  /**\n   * Causes the database connection to start a new write immediately, without waiting for a write\n   * statement.\n   *\n   * <p>The {@code BEGIN IMMEDIATE} might fail with {@code SQLITE_BUSY} if another write transaction\n   * is already active on another database connection.\n   *\n   * <p>This prevents other connections from writing to the database, effectively serializing write\n   * transactions and ensuring a stable snapshot for the duration of this transaction.\n   */\n  IMMEDIATE(\"BEGIN IMMEDIATE\"),\n\n  /**\n   * Causes the database connection to start a new write immediately and prevents other database\n   * connections from reading out of the database while the transaction is underway.\n   */\n  EXCLUSIVE(\"BEGIN EXCLUSIVE\");\n\n  private final String sql;\n\n  TursoTransactionMode(String sql) {\n    this.sql = sql;\n  }\n\n  public String getSql() {\n    return sql;\n  }\n\n  /**\n   * Determine the transaction mode based on the JDBC transaction isolation level.\n   *\n   * <p>\n   *\n   * <ul>\n   *   <li>{@link Connection#TRANSACTION_READ_UNCOMMITTED} -> {@link #DEFERRED}\n   *   <li>{@link Connection#TRANSACTION_READ_COMMITTED} -> {@link #DEFERRED}\n   *   <li>{@link Connection#TRANSACTION_REPEATABLE_READ} -> {@link #IMMEDIATE}\n   *   <li>{@link Connection#TRANSACTION_SERIALIZABLE} -> {@link #IMMEDIATE}\n   * </ul>\n   *\n   * @param level the JDBC transaction isolation level\n   * @return the corresponding TursoTransactionMode\n   */\n  public static TursoTransactionMode fromIsolationLevel(int level) {\n    if (level == Connection.TRANSACTION_READ_UNCOMMITTED\n        || level == Connection.TRANSACTION_READ_COMMITTED) {\n      return DEFERRED;\n    }\n    return IMMEDIATE;\n  }\n}\n"
  },
  {
    "path": "bindings/java/src/main/java/tech/turso/exceptions/TursoException.java",
    "content": "package tech.turso.exceptions;\n\nimport java.sql.SQLException;\nimport tech.turso.TursoErrorCode;\n\npublic final class TursoException extends SQLException {\n\n  private TursoErrorCode resultCode;\n\n  public TursoException(String message, TursoErrorCode resultCode) {\n    super(message, null, resultCode.code & 0xff);\n    this.resultCode = resultCode;\n  }\n\n  public TursoErrorCode getResultCode() {\n    return resultCode;\n  }\n}\n"
  },
  {
    "path": "bindings/java/src/main/java/tech/turso/jdbc4/JDBC4Connection.java",
    "content": "package tech.turso.jdbc4;\n\nimport java.sql.*;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Properties;\nimport java.util.concurrent.Executor;\nimport tech.turso.annotations.SkipNullableCheck;\nimport tech.turso.core.TursoConnection;\nimport tech.turso.core.TursoStatement;\n\n/** JDBC 4 Connection implementation for Turso databases. */\npublic final class JDBC4Connection implements Connection {\n\n  private final TursoConnection connection;\n\n  private Map<String, Class<?>> typeMap = new HashMap<>();\n\n  /**\n   * Creates a new JDBC4 connection.\n   *\n   * @param url the database URL\n   * @param filePath the database file path\n   * @throws SQLException if a database access error occurs\n   */\n  public JDBC4Connection(String url, String filePath) throws SQLException {\n    this.connection = new TursoConnection(url, filePath);\n  }\n\n  /**\n   * Creates a new JDBC4 connection with properties.\n   *\n   * @param url the database URL\n   * @param filePath the database file path\n   * @param properties connection properties\n   * @throws SQLException if a database access error occurs\n   */\n  public JDBC4Connection(String url, String filePath, Properties properties) throws SQLException {\n    this.connection = new TursoConnection(url, filePath, properties);\n  }\n\n  /**\n   * Prepares a SQL statement for execution.\n   *\n   * @param sql the SQL statement to prepare\n   * @return the prepared statement\n   * @throws SQLException if a database access error occurs\n   */\n  public TursoStatement prepare(String sql) throws SQLException {\n    final TursoStatement statement = connection.prepare(sql);\n    statement.initializeColumnMetadata();\n    return statement;\n  }\n\n  @Override\n  public Statement createStatement() throws SQLException {\n    return createStatement(\n        ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT);\n  }\n\n  @Override\n  public Statement createStatement(int resultSetType, int resultSetConcurrency)\n      throws SQLException {\n    return createStatement(resultSetType, resultSetConcurrency, ResultSet.CLOSE_CURSORS_AT_COMMIT);\n  }\n\n  @Override\n  public Statement createStatement(\n      int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {\n    connection.checkOpen();\n    connection.checkCursor(resultSetType, resultSetConcurrency, resultSetHoldability);\n\n    return new JDBC4Statement(this);\n  }\n\n  @Override\n  public String nativeSQL(String sql) throws SQLException {\n    return sql;\n  }\n\n  @Override\n  public void setAutoCommit(boolean autoCommit) throws SQLException {\n    connection.setAutoCommit(autoCommit);\n  }\n\n  @Override\n  public boolean getAutoCommit() throws SQLException {\n    return connection.getAutoCommit();\n  }\n\n  @Override\n  public void commit() throws SQLException {\n    connection.commit();\n  }\n\n  @Override\n  public void rollback() throws SQLException {\n    connection.rollback();\n  }\n\n  @Override\n  public void close() throws SQLException {\n    connection.close();\n  }\n\n  @Override\n  public boolean isClosed() throws SQLException {\n    return connection.isClosed();\n  }\n\n  @Override\n  @SkipNullableCheck\n  public DatabaseMetaData getMetaData() throws SQLException {\n    return new JDBC4DatabaseMetaData(this);\n  }\n\n  @Override\n  public void setReadOnly(boolean readOnly) throws SQLException {\n    // TODO\n  }\n\n  @Override\n  public boolean isReadOnly() throws SQLException {\n    // TODO\n    return false;\n  }\n\n  @Override\n  public void setCatalog(String catalog) throws SQLException {}\n\n  @Override\n  public String getCatalog() throws SQLException {\n    return \"\";\n  }\n\n  @Override\n  public void setTransactionIsolation(int level) throws SQLException {\n    connection.setTransactionIsolation(level);\n  }\n\n  @Override\n  public int getTransactionIsolation() throws SQLException {\n    return connection.getTransactionIsolation();\n  }\n\n  @Override\n  @SkipNullableCheck\n  public SQLWarning getWarnings() throws SQLException {\n    // TODO\n    return null;\n  }\n\n  @Override\n  public void clearWarnings() throws SQLException {\n    // TODO\n  }\n\n  @Override\n  public Map<String, Class<?>> getTypeMap() throws SQLException {\n    return this.typeMap;\n  }\n\n  @Override\n  public void setTypeMap(Map<String, Class<?>> map) throws SQLException {\n    synchronized (this) {\n      this.typeMap = map;\n    }\n  }\n\n  @Override\n  public int getHoldability() throws SQLException {\n    connection.checkOpen();\n    return ResultSet.CLOSE_CURSORS_AT_COMMIT;\n  }\n\n  @Override\n  public void setHoldability(int holdability) throws SQLException {\n    connection.checkOpen();\n    if (holdability != ResultSet.CLOSE_CURSORS_AT_COMMIT) {\n      throw new SQLException(\"turso only supports CLOSE_CURSORS_AT_COMMIT\");\n    }\n  }\n\n  @Override\n  @SkipNullableCheck\n  public Savepoint setSavepoint() throws SQLException {\n    throw new SQLFeatureNotSupportedException(\"Savepoints are not supported by Turso\");\n  }\n\n  @Override\n  @SkipNullableCheck\n  public Savepoint setSavepoint(String name) throws SQLException {\n    throw new SQLFeatureNotSupportedException(\"Savepoints are not supported by Turso\");\n  }\n\n  @Override\n  public void rollback(Savepoint savepoint) throws SQLException {\n    throw new SQLFeatureNotSupportedException(\"Savepoints are not supported by Turso\");\n  }\n\n  @Override\n  public void releaseSavepoint(Savepoint savepoint) throws SQLException {\n    throw new SQLFeatureNotSupportedException(\"Savepoints are not supported by Turso\");\n  }\n\n  @Override\n  public CallableStatement prepareCall(String sql) throws SQLException {\n    return prepareCall(\n        sql,\n        ResultSet.TYPE_FORWARD_ONLY,\n        ResultSet.CONCUR_READ_ONLY,\n        ResultSet.CLOSE_CURSORS_AT_COMMIT);\n  }\n\n  @Override\n  public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency)\n      throws SQLException {\n    return prepareCall(sql, resultSetType, resultSetConcurrency, ResultSet.CLOSE_CURSORS_AT_COMMIT);\n  }\n\n  @Override\n  public CallableStatement prepareCall(\n      String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability)\n      throws SQLException {\n    throw new SQLException(\"turso does not support stored procedures\");\n  }\n\n  @Override\n  public PreparedStatement prepareStatement(String sql) throws SQLException {\n    return prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);\n  }\n\n  @Override\n  public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)\n      throws SQLException {\n    return prepareStatement(\n        sql, resultSetType, resultSetConcurrency, ResultSet.CLOSE_CURSORS_AT_COMMIT);\n  }\n\n  @Override\n  public PreparedStatement prepareStatement(\n      String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability)\n      throws SQLException {\n    connection.checkOpen();\n    connection.checkCursor(resultSetType, resultSetConcurrency, resultSetHoldability);\n    return new JDBC4PreparedStatement(this, sql);\n  }\n\n  @Override\n  public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {\n    return prepareStatement(sql);\n  }\n\n  @Override\n  public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {\n    // TODO: maybe we can enhance this functionality by using columnIndexes\n    return prepareStatement(sql);\n  }\n\n  @Override\n  public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {\n    // TODO: maybe we can enhance this functionality by using columnNames\n    return prepareStatement(sql);\n  }\n\n  @Override\n  public Clob createClob() throws SQLException {\n    throw new SQLFeatureNotSupportedException(\"createClob not supported\");\n  }\n\n  @Override\n  public Blob createBlob() throws SQLException {\n    throw new SQLFeatureNotSupportedException(\"createBlob not supported\");\n  }\n\n  @Override\n  public NClob createNClob() throws SQLException {\n    throw new SQLFeatureNotSupportedException(\"createNClob not supported\");\n  }\n\n  @Override\n  @SkipNullableCheck\n  public SQLXML createSQLXML() throws SQLException {\n    throw new SQLFeatureNotSupportedException(\"createSQLXML not supported\");\n  }\n\n  @Override\n  public boolean isValid(int timeout) throws SQLException {\n    if (isClosed()) {\n      return false;\n    }\n\n    try (Statement statement = createStatement()) {\n      return statement.execute(\"select 1;\");\n    }\n  }\n\n  @Override\n  public void setClientInfo(String name, String value) throws SQLClientInfoException {\n    // TODO\n  }\n\n  @Override\n  public void setClientInfo(Properties properties) throws SQLClientInfoException {\n    // TODO\n  }\n\n  @Override\n  public String getClientInfo(String name) throws SQLException {\n    // TODO\n    return \"\";\n  }\n\n  @Override\n  @SkipNullableCheck\n  public Properties getClientInfo() throws SQLException {\n    // TODO\n    return null;\n  }\n\n  @Override\n  @SkipNullableCheck\n  public Array createArrayOf(String typeName, Object[] elements) throws SQLException {\n    // TODO\n    return null;\n  }\n\n  @Override\n  @SkipNullableCheck\n  public Struct createStruct(String typeName, Object[] attributes) throws SQLException {\n    // TODO\n    return null;\n  }\n\n  @Override\n  public void setSchema(String schema) throws SQLException {\n    // TODO\n  }\n\n  @Override\n  @SkipNullableCheck\n  public String getSchema() throws SQLException {\n    // TODO\n    return \"\";\n  }\n\n  @Override\n  public void abort(Executor executor) throws SQLException {\n    if (isClosed()) {\n      return;\n    }\n\n    close();\n  }\n\n  @Override\n  public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {\n    // TODO\n  }\n\n  @Override\n  public int getNetworkTimeout() throws SQLException {\n    // TODO\n    return 0;\n  }\n\n  @Override\n  @SkipNullableCheck\n  public <T> T unwrap(Class<T> iface) throws SQLException {\n    return null;\n  }\n\n  @Override\n  public boolean isWrapperFor(Class<?> iface) throws SQLException {\n    // TODO\n    return false;\n  }\n\n  /**\n   * Sets the busy timeout for the connection.\n   *\n   * @param busyTimeout the timeout in milliseconds\n   */\n  public void setBusyTimeout(int busyTimeout) {\n    // TODO: add support for busy timeout\n  }\n\n  /** @return busy timeout in milliseconds. */\n  public int getBusyTimeout() {\n    // TODO: add support for busyTimeout\n    return 0;\n  }\n\n  /**\n   * Gets the database URL.\n   *\n   * @return the database URL\n   */\n  public String getUrl() {\n    return this.connection.getUrl();\n  }\n\n  /**\n   * Checks if the connection is open.\n   *\n   * @throws SQLException if the connection is closed\n   */\n  public void checkOpen() throws SQLException {\n    connection.checkOpen();\n  }\n}\n"
  },
  {
    "path": "bindings/java/src/main/java/tech/turso/jdbc4/JDBC4DatabaseMetaData.java",
    "content": "package tech.turso.jdbc4;\n\nimport java.sql.Connection;\nimport java.sql.DatabaseMetaData;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.RowIdLifetime;\nimport java.sql.SQLException;\nimport java.sql.SQLFeatureNotSupportedException;\nimport tech.turso.annotations.Nullable;\nimport tech.turso.annotations.SkipNullableCheck;\nimport tech.turso.core.TursoPropertiesHolder;\nimport tech.turso.utils.Logger;\nimport tech.turso.utils.LoggerFactory;\n\n/** JDBC 4 DatabaseMetaData implementation for Turso databases. */\npublic final class JDBC4DatabaseMetaData implements DatabaseMetaData {\n\n  private static final Logger logger = LoggerFactory.getLogger(JDBC4DatabaseMetaData.class);\n\n  private final JDBC4Connection connection;\n\n  @Nullable private PreparedStatement getTables = null;\n\n  @Nullable private PreparedStatement getTableTypes = null;\n\n  @Nullable private PreparedStatement getTypeInfo = null;\n\n  @Nullable private PreparedStatement getCatalogs = null;\n\n  @Nullable private PreparedStatement getSchemas = null;\n\n  @Nullable private PreparedStatement getUDTs = null;\n\n  @Nullable private PreparedStatement getColumnsTblName = null;\n\n  @Nullable private PreparedStatement getSuperTypes = null;\n\n  @Nullable private PreparedStatement getSuperTables = null;\n\n  @Nullable private PreparedStatement getTablePrivileges = null;\n\n  @Nullable private PreparedStatement getIndexInfo = null;\n\n  @Nullable private PreparedStatement getProcedures = null;\n\n  @Nullable private PreparedStatement getAttributes = null;\n\n  @Nullable private PreparedStatement getBestRowIdentifier = null;\n\n  @Nullable private PreparedStatement getVersionColumns = null;\n\n  @Nullable private PreparedStatement getColumnPrivileges = null;\n\n  /**\n   * Creates a new JDBC4DatabaseMetaData instance.\n   *\n   * @param connection the database connection\n   */\n  public JDBC4DatabaseMetaData(JDBC4Connection connection) {\n    this.connection = connection;\n  }\n\n  @Override\n  public boolean allProceduresAreCallable() {\n    return false;\n  }\n\n  @Override\n  public boolean allTablesAreSelectable() {\n    return true;\n  }\n\n  @Override\n  public String getURL() {\n    return connection.getUrl();\n  }\n\n  @Override\n  @Nullable\n  public String getUserName() {\n    return null;\n  }\n\n  @Override\n  public boolean isReadOnly() throws SQLException {\n    return connection.isReadOnly();\n  }\n\n  @Override\n  public boolean nullsAreSortedHigh() {\n    return true;\n  }\n\n  @Override\n  public boolean nullsAreSortedLow() {\n    return !nullsAreSortedHigh();\n  }\n\n  @Override\n  public boolean nullsAreSortedAtStart() {\n    return true;\n  }\n\n  @Override\n  public boolean nullsAreSortedAtEnd() {\n    return !nullsAreSortedAtStart();\n  }\n\n  @Override\n  public String getDatabaseProductName() {\n    return \"turso\";\n  }\n\n  @Override\n  public String getDatabaseProductVersion() {\n    // TODO\n    return \"\";\n  }\n\n  @Override\n  public String getDriverName() {\n    return TursoPropertiesHolder.getDriverName();\n  }\n\n  @Override\n  public String getDriverVersion() {\n    return TursoPropertiesHolder.getDriverVersion();\n  }\n\n  @Override\n  public int getDriverMajorVersion() {\n    return Integer.parseInt(getDriverVersion().split(\"\\\\.\")[0]);\n  }\n\n  @Override\n  public int getDriverMinorVersion() {\n    return Integer.parseInt(getDriverVersion().split(\"\\\\.\")[1]);\n  }\n\n  @Override\n  public boolean usesLocalFiles() {\n    return true;\n  }\n\n  @Override\n  public boolean usesLocalFilePerTable() {\n    return false;\n  }\n\n  @Override\n  public boolean supportsMixedCaseIdentifiers() {\n    return true;\n  }\n\n  @Override\n  public boolean storesUpperCaseIdentifiers() {\n    return false;\n  }\n\n  @Override\n  public boolean storesLowerCaseIdentifiers() {\n    return false;\n  }\n\n  @Override\n  public boolean storesMixedCaseIdentifiers() {\n    return true;\n  }\n\n  @Override\n  public boolean supportsMixedCaseQuotedIdentifiers() {\n    return false;\n  }\n\n  @Override\n  public boolean storesUpperCaseQuotedIdentifiers() {\n    return false;\n  }\n\n  @Override\n  public boolean storesLowerCaseQuotedIdentifiers() {\n    return false;\n  }\n\n  @Override\n  public boolean storesMixedCaseQuotedIdentifiers() {\n    return false;\n  }\n\n  @Override\n  public String getIdentifierQuoteString() {\n    return \"\\\"\";\n  }\n\n  @Override\n  public String getSQLKeywords() {\n    // TODO: add more turso supported keywords\n    return (\"ABORT,ACTION,AFTER,ANALYZE,ATTACH,AUTOINCREMENT,BEFORE,\"\n        + \"CASCADE,CONFLICT,DATABASE,DEFERRABLE,DEFERRED,DESC,DETACH,\"\n        + \"EXCLUSIVE,EXPLAIN,FAIL,GLOB,IGNORE,INDEX,INDEXED,INITIALLY,INSTEAD,ISNULL,\"\n        + \"KEY,LIMIT,NOTNULL,OFFSET,PLAN,PRAGMA,QUERY,\"\n        + \"RAISE,REGEXP,REINDEX,RENAME,REPLACE,RESTRICT,\"\n        + \"TEMP,TEMPORARY,TRANSACTION,VACUUM,VIEW,VIRTUAL\");\n  }\n\n  @Override\n  public String getNumericFunctions() {\n    // TODO\n    return \"\";\n  }\n\n  @Override\n  public String getStringFunctions() {\n    // TOOD\n    return \"\";\n  }\n\n  @Override\n  public String getSystemFunctions() {\n    // TODO\n    return \"\";\n  }\n\n  @Override\n  public String getTimeDateFunctions() {\n    // TODO\n    return \"\";\n  }\n\n  @Override\n  public String getSearchStringEscape() {\n    return \"\\\\\";\n  }\n\n  @Override\n  public String getExtraNameCharacters() {\n    return \"\";\n  }\n\n  @Override\n  public boolean supportsAlterTableWithAddColumn() {\n    return false;\n  }\n\n  @Override\n  public boolean supportsAlterTableWithDropColumn() {\n    return false;\n  }\n\n  @Override\n  public boolean supportsColumnAliasing() {\n    return true;\n  }\n\n  @Override\n  public boolean nullPlusNonNullIsNull() {\n    return true;\n  }\n\n  @Override\n  public boolean supportsConvert() {\n    return false;\n  }\n\n  @Override\n  public boolean supportsConvert(int fromType, int toType) {\n    return false;\n  }\n\n  @Override\n  public boolean supportsTableCorrelationNames() {\n    return false;\n  }\n\n  @Override\n  public boolean supportsDifferentTableCorrelationNames() {\n    return false;\n  }\n\n  @Override\n  public boolean supportsExpressionsInOrderBy() {\n    return true;\n  }\n\n  @Override\n  public boolean supportsOrderByUnrelated() {\n    return false;\n  }\n\n  @Override\n  public boolean supportsGroupBy() {\n    return true;\n  }\n\n  @Override\n  public boolean supportsGroupByUnrelated() {\n    return false;\n  }\n\n  @Override\n  public boolean supportsGroupByBeyondSelect() {\n    return false;\n  }\n\n  @Override\n  public boolean supportsLikeEscapeClause() {\n    return false;\n  }\n\n  @Override\n  public boolean supportsMultipleResultSets() {\n    return false;\n  }\n\n  @Override\n  public boolean supportsMultipleTransactions() {\n    return true;\n  }\n\n  @Override\n  public boolean supportsNonNullableColumns() {\n    return true;\n  }\n\n  @Override\n  public boolean supportsMinimumSQLGrammar() {\n    // ODBC minimum grammar should be supported, which are the followings:\n    //   SELECT\n    //   INSERT\n    //   UPDATE\n    //   DELETE\n    //   CREATE TABLE\n    //   DROP TABLE\n    //   GRANT\n    //   REVOKE\n    //   COMMIT\n    //   ROLLBACK\n    //   DECLARE CURSOR\n    //   FETCH\n    //   CLOSE CURSOR\n    // TODO: Let's return true when turso supports them all\n    return false;\n  }\n\n  @Override\n  public boolean supportsCoreSQLGrammar() {\n    // supportsMinimumSQLGrammar() and some additional such as:\n    //   Joins (INNER JOIN, OUTER JOIN, LEFT JOIN, RIGHT JOIN)\n    //   Set operations (UNION, INTERSECT, EXCEPT)\n    //   Subqueries (e.g., SELECT * FROM table WHERE column IN (SELECT column FROM another_table))\n    //   Table expressions (SELECT column FROM (SELECT * FROM table) AS subquery)\n    //   Data type support (includes more SQL data types like FLOAT, NUMERIC, DECIMAL)\n    //   Basic string functions (e.g., LENGTH(), SUBSTRING(), CONCAT())\n    // TODO: Let's return true when turso supports them all\n    return false;\n  }\n\n  @Override\n  public boolean supportsExtendedSQLGrammar() {\n    return false;\n  }\n\n  @Override\n  public boolean supportsANSI92EntryLevelSQL() {\n    return false;\n  }\n\n  @Override\n  public boolean supportsANSI92IntermediateSQL() {\n    return false;\n  }\n\n  @Override\n  public boolean supportsANSI92FullSQL() {\n    return false;\n  }\n\n  @Override\n  public boolean supportsIntegrityEnhancementFacility() {\n    return false;\n  }\n\n  @Override\n  public boolean supportsOuterJoins() {\n    return true;\n  }\n\n  @Override\n  public boolean supportsFullOuterJoins() {\n    return true;\n  }\n\n  @Override\n  public boolean supportsLimitedOuterJoins() {\n    return true;\n  }\n\n  @Override\n  public String getSchemaTerm() {\n    return \"schema\";\n  }\n\n  @Override\n  public String getProcedureTerm() {\n    return \"not_implemented\";\n  }\n\n  @Override\n  public String getCatalogTerm() {\n    return \"catalog\";\n  }\n\n  @Override\n  public boolean isCatalogAtStart() {\n    // sqlite and turso doesn't use catalog\n    return false;\n  }\n\n  @Override\n  public String getCatalogSeparator() {\n    return \".\";\n  }\n\n  @Override\n  public boolean supportsSchemasInDataManipulation() {\n    return false;\n  }\n\n  @Override\n  public boolean supportsSchemasInProcedureCalls() {\n    return false;\n  }\n\n  @Override\n  public boolean supportsSchemasInTableDefinitions() {\n    return false;\n  }\n\n  @Override\n  public boolean supportsSchemasInIndexDefinitions() {\n    return false;\n  }\n\n  @Override\n  public boolean supportsSchemasInPrivilegeDefinitions() {\n    return false;\n  }\n\n  @Override\n  public boolean supportsCatalogsInDataManipulation() {\n    return false;\n  }\n\n  @Override\n  public boolean supportsCatalogsInProcedureCalls() {\n    return false;\n  }\n\n  @Override\n  public boolean supportsCatalogsInTableDefinitions() {\n    return false;\n  }\n\n  @Override\n  public boolean supportsCatalogsInIndexDefinitions() {\n    return false;\n  }\n\n  @Override\n  public boolean supportsCatalogsInPrivilegeDefinitions() {\n    return false;\n  }\n\n  @Override\n  public boolean supportsPositionedDelete() {\n    return false;\n  }\n\n  @Override\n  public boolean supportsPositionedUpdate() {\n    return false;\n  }\n\n  @Override\n  public boolean supportsSelectForUpdate() {\n    return false;\n  }\n\n  @Override\n  public boolean supportsStoredProcedures() {\n    return false;\n  }\n\n  @Override\n  public boolean supportsSubqueriesInComparisons() {\n    return false;\n  }\n\n  @Override\n  public boolean supportsSubqueriesInExists() {\n    return true;\n  }\n\n  @Override\n  public boolean supportsSubqueriesInIns() {\n    return true;\n  }\n\n  @Override\n  public boolean supportsSubqueriesInQuantifieds() {\n    return false;\n  }\n\n  @Override\n  public boolean supportsCorrelatedSubqueries() {\n    return false;\n  }\n\n  @Override\n  public boolean supportsUnion() {\n    // TODO: return true when turso supports\n    return false;\n  }\n\n  @Override\n  public boolean supportsUnionAll() {\n    // TODO: return true when turso supports\n    return false;\n  }\n\n  @Override\n  public boolean supportsOpenCursorsAcrossCommit() {\n    return false;\n  }\n\n  @Override\n  public boolean supportsOpenCursorsAcrossRollback() {\n    return false;\n  }\n\n  @Override\n  public boolean supportsOpenStatementsAcrossCommit() {\n    return false;\n  }\n\n  @Override\n  public boolean supportsOpenStatementsAcrossRollback() {\n    return false;\n  }\n\n  @Override\n  public int getMaxBinaryLiteralLength() {\n    return 0;\n  }\n\n  @Override\n  public int getMaxCharLiteralLength() {\n    return 0;\n  }\n\n  @Override\n  public int getMaxColumnNameLength() {\n    return 0;\n  }\n\n  @Override\n  public int getMaxColumnsInGroupBy() {\n    return 0;\n  }\n\n  @Override\n  public int getMaxColumnsInIndex() {\n    return 0;\n  }\n\n  @Override\n  public int getMaxColumnsInOrderBy() {\n    return 0;\n  }\n\n  @Override\n  public int getMaxColumnsInSelect() {\n    return 0;\n  }\n\n  @Override\n  public int getMaxColumnsInTable() {\n    return 0;\n  }\n\n  @Override\n  public int getMaxConnections() {\n    return 0;\n  }\n\n  @Override\n  public int getMaxCursorNameLength() {\n    return 0;\n  }\n\n  @Override\n  public int getMaxIndexLength() {\n    return 0;\n  }\n\n  @Override\n  public int getMaxSchemaNameLength() {\n    return 0;\n  }\n\n  @Override\n  public int getMaxProcedureNameLength() {\n    return 0;\n  }\n\n  @Override\n  public int getMaxCatalogNameLength() {\n    return 0;\n  }\n\n  @Override\n  public int getMaxRowSize() {\n    return 0;\n  }\n\n  @Override\n  public boolean doesMaxRowSizeIncludeBlobs() {\n    return false;\n  }\n\n  @Override\n  public int getMaxStatementLength() {\n    return 0;\n  }\n\n  @Override\n  public int getMaxStatements() {\n    return 0;\n  }\n\n  @Override\n  public int getMaxTableNameLength() {\n    return 0;\n  }\n\n  @Override\n  public int getMaxTablesInSelect() {\n    return 0;\n  }\n\n  @Override\n  public int getMaxUserNameLength() {\n    return 0;\n  }\n\n  @Override\n  public int getDefaultTransactionIsolation() {\n    // TODO: after turso introduces Hekaton MVCC, what should we return?\n    return Connection.TRANSACTION_SERIALIZABLE;\n  }\n\n  @Override\n  public boolean supportsTransactions() {\n    // TODO: turso doesn't support transactions fully, let's return true when supported\n    return false;\n  }\n\n  @Override\n  public boolean supportsTransactionIsolationLevel(int level) {\n    return Connection.TRANSACTION_SERIALIZABLE == level;\n  }\n\n  @Override\n  public boolean supportsDataDefinitionAndDataManipulationTransactions() {\n    // TODO: return true when supported\n    return false;\n  }\n\n  @Override\n  public boolean supportsDataManipulationTransactionsOnly() {\n    return false;\n  }\n\n  @Override\n  public boolean dataDefinitionCausesTransactionCommit() {\n    return false;\n  }\n\n  @Override\n  public boolean dataDefinitionIgnoredInTransactions() {\n    return false;\n  }\n\n  @Override\n  @SkipNullableCheck\n  public ResultSet getProcedures(\n      String catalog, String schemaPattern, String procedureNamePattern) {\n    // TODO\n    return null;\n  }\n\n  @Override\n  @SkipNullableCheck\n  public ResultSet getProcedureColumns(\n      String catalog, String schemaPattern, String procedureNamePattern, String columnNamePattern) {\n    // TODO\n    return null;\n  }\n\n  // TODO: make use of getSearchStringEscape\n  @Override\n  public ResultSet getTables(\n      @Nullable String catalog,\n      @Nullable String schemaPattern,\n      String tableNamePattern,\n      @Nullable String[] types)\n      throws SQLException {\n    // SQLite doesn't support catalogs or schemas — reject if non-empty values provided\n    if (catalog != null && !catalog.isEmpty()) {\n      return connection.prepareStatement(\"SELECT * FROM sqlite_schema WHERE 1=0\").executeQuery();\n    }\n\n    if (schemaPattern != null && !schemaPattern.isEmpty()) {\n      return connection.prepareStatement(\"SELECT * FROM sqlite_schema WHERE 1=0\").executeQuery();\n    }\n\n    // Start building query\n    StringBuilder sql =\n        new StringBuilder(\n            \"SELECT \"\n                + \"NULL AS TABLE_CAT, \"\n                + \"NULL AS TABLE_SCHEM, \"\n                + \"name AS TABLE_NAME, \"\n                + \"CASE type \"\n                + \"  WHEN 'table' THEN 'TABLE' \"\n                + \"  WHEN 'view' THEN 'VIEW' \"\n                + \"  ELSE UPPER(type) \"\n                + \"END AS TABLE_TYPE, \"\n                + \"NULL AS REMARKS, \"\n                + \"NULL AS TYPE_CAT, \"\n                + \"NULL AS TYPE_SCHEM, \"\n                + \"NULL AS TYPE_NAME, \"\n                + \"NULL AS SELF_REFERENCING_COL_NAME, \"\n                + \"NULL AS REF_GENERATION \"\n                + \"FROM sqlite_schema \"\n                + \"WHERE 1=1\");\n\n    // Apply type filtering if needed\n    if (types != null && types.length > 0) {\n      sql.append(\" AND type IN (\");\n      for (int i = 0; i < types.length; i++) {\n        if (i > 0) sql.append(\", \");\n        sql.append(\"?\");\n      }\n      sql.append(\")\");\n    }\n\n    // Apply table name pattern filtering\n    if (tableNamePattern != null) {\n      sql.append(\" AND name LIKE ?\");\n    }\n\n    // Comply with spec: sort by TABLE_TYPE, TABLE_CAT, TABLE_SCHEM, TABLE_NAME\n    sql.append(\" ORDER BY TABLE_TYPE, TABLE_CAT, TABLE_SCHEM, TABLE_NAME\");\n\n    // Prepare and bind statement\n    PreparedStatement stmt = connection.prepareStatement(sql.toString());\n    int paramIndex = 1;\n\n    if (types != null && types.length > 0) {\n      for (String type : types) {\n        String sqliteType;\n        if (\"TABLE\".equalsIgnoreCase(type)) {\n          sqliteType = \"table\";\n        } else if (\"VIEW\".equalsIgnoreCase(type)) {\n          sqliteType = \"view\";\n        } else {\n          sqliteType = type.toLowerCase();\n        }\n        stmt.setString(paramIndex++, sqliteType);\n      }\n    }\n\n    if (tableNamePattern != null) {\n      stmt.setString(paramIndex, tableNamePattern);\n    }\n\n    return stmt.executeQuery();\n  }\n\n  @Override\n  public ResultSet getSchemas() throws SQLException {\n    if (getSchemas == null) {\n      connection.checkOpen();\n      getSchemas =\n          connection.prepareStatement(\"select null as TABLE_SCHEM, null as TABLE_CATALOG limit 0;\");\n    }\n\n    return getSchemas.executeQuery();\n  }\n\n  @Override\n  public ResultSet getCatalogs() throws SQLException {\n    if (getCatalogs == null) {\n      connection.checkOpen();\n      getCatalogs = connection.prepareStatement(\"select null as TABLE_CAT limit 0;\");\n    }\n\n    return getCatalogs.executeQuery();\n  }\n\n  @Override\n  @SkipNullableCheck\n  public ResultSet getTableTypes() {\n    // TODO\n    return null;\n  }\n\n  @Override\n  @SkipNullableCheck\n  public ResultSet getColumns(\n      String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) {\n    // TODO - important\n    return null;\n  }\n\n  @Override\n  @SkipNullableCheck\n  public ResultSet getColumnPrivileges(\n      String catalog, String schema, String table, String columnNamePattern) throws SQLException {\n    if (getColumnPrivileges == null) {\n      connection.close();\n      getColumnPrivileges =\n          connection.prepareStatement(\n              \"select null as TABLE_CAT, null as TABLE_SCHEM, \"\n                  + \"null as TABLE_NAME, null as COLUMN_NAME, null as GRANTOR, null as GRANTEE, \"\n                  + \"null as PRIVILEGE, null as IS_GRANTABLE limit 0;\");\n    }\n\n    return getColumnPrivileges.executeQuery();\n  }\n\n  @Override\n  @SkipNullableCheck\n  public ResultSet getTablePrivileges(String catalog, String schemaPattern, String tableNamePattern)\n      throws SQLException {\n    if (getTablePrivileges == null) {\n      connection.checkOpen();\n      getTablePrivileges =\n          connection.prepareStatement(\n              \"select  null as TABLE_CAT, \"\n                  + \"null as TABLE_SCHEM, null as TABLE_NAME, null as GRANTOR, null \"\n                  + \"GRANTEE,  null as PRIVILEGE, null as IS_GRANTABLE limit 0;\");\n    }\n    return getTablePrivileges.executeQuery();\n  }\n\n  @Override\n  @SkipNullableCheck\n  public ResultSet getBestRowIdentifier(\n      String catalog, String schema, String table, int scope, boolean nullable)\n      throws SQLException {\n    if (getBestRowIdentifier == null) {\n      connection.checkOpen();\n      getBestRowIdentifier =\n          connection.prepareStatement(\n              \"select null as SCOPE, null as COLUMN_NAME, \"\n                  + \"null as DATA_TYPE, null as TYPE_NAME, null as COLUMN_SIZE, \"\n                  + \"null as BUFFER_LENGTH, null as DECIMAL_DIGITS, null as PSEUDO_COLUMN limit 0;\");\n    }\n\n    return getBestRowIdentifier.executeQuery();\n  }\n\n  @Override\n  @SkipNullableCheck\n  public ResultSet getVersionColumns(String catalog, String schema, String table)\n      throws SQLException {\n    if (getVersionColumns == null) {\n      connection.close();\n      getVersionColumns =\n          connection.prepareStatement(\n              \"select null as SCOPE, null as COLUMN_NAME, \"\n                  + \"null as DATA_TYPE, null as TYPE_NAME, null as COLUMN_SIZE, \"\n                  + \"null as BUFFER_LENGTH, null as DECIMAL_DIGITS, null as PSEUDO_COLUMN limit 0;\");\n    }\n    return getVersionColumns.executeQuery();\n  }\n\n  @Override\n  @SkipNullableCheck\n  public ResultSet getPrimaryKeys(String catalog, String schema, String table) {\n    // TODO - important\n    return null;\n  }\n\n  @Override\n  @SkipNullableCheck\n  public ResultSet getImportedKeys(String catalog, String schema, String table) {\n    // TODO\n    return null;\n  }\n\n  @Override\n  @SkipNullableCheck\n  public ResultSet getExportedKeys(String catalog, String schema, String table) {\n    return null;\n  }\n\n  @Override\n  @SkipNullableCheck\n  public ResultSet getCrossReference(\n      String parentCatalog,\n      String parentSchema,\n      String parentTable,\n      String foreignCatalog,\n      String foreignSchema,\n      String foreignTable) {\n    // TODO\n    return null;\n  }\n\n  @Override\n  @SkipNullableCheck\n  public ResultSet getTypeInfo() {\n    // TODO\n    return null;\n  }\n\n  @Override\n  @SkipNullableCheck\n  public ResultSet getIndexInfo(\n      String catalog, String schema, String table, boolean unique, boolean approximate) {\n    // TODO\n    return null;\n  }\n\n  @Override\n  public boolean supportsResultSetType(int type) {\n    return type == ResultSet.TYPE_FORWARD_ONLY;\n  }\n\n  @Override\n  public boolean supportsResultSetConcurrency(int type, int concurrency) {\n    return (type == ResultSet.TYPE_FORWARD_ONLY && concurrency == ResultSet.CONCUR_READ_ONLY);\n  }\n\n  @Override\n  public boolean ownUpdatesAreVisible(int type) {\n    return false;\n  }\n\n  @Override\n  public boolean ownDeletesAreVisible(int type) {\n    return false;\n  }\n\n  @Override\n  public boolean ownInsertsAreVisible(int type) {\n    return false;\n  }\n\n  @Override\n  public boolean othersUpdatesAreVisible(int type) {\n    return false;\n  }\n\n  @Override\n  public boolean othersDeletesAreVisible(int type) {\n    return false;\n  }\n\n  @Override\n  public boolean othersInsertsAreVisible(int type) {\n    return false;\n  }\n\n  @Override\n  public boolean updatesAreDetected(int type) {\n    return false;\n  }\n\n  @Override\n  public boolean deletesAreDetected(int type) {\n    return false;\n  }\n\n  @Override\n  public boolean insertsAreDetected(int type) {\n    return false;\n  }\n\n  @Override\n  public boolean supportsBatchUpdates() {\n    // TODO - let's add support for batch updates in the future and let this method return true\n    return false;\n  }\n\n  @Override\n  public ResultSet getUDTs(\n      String catalog, String schemaPattern, String typeNamePattern, int[] types)\n      throws SQLException {\n    if (getUDTs == null) {\n      connection.close();\n      getUDTs =\n          connection.prepareStatement(\n              \"select  null as TYPE_CAT, null as TYPE_SCHEM, \"\n                  + \"null as TYPE_NAME,  null as CLASS_NAME,  null as DATA_TYPE, null as REMARKS, \"\n                  + \"null as BASE_TYPE \"\n                  + \"limit 0;\");\n    }\n\n    getUDTs.clearParameters();\n    return getUDTs.executeQuery();\n  }\n\n  @Override\n  public Connection getConnection() {\n    return connection;\n  }\n\n  @Override\n  public boolean supportsSavepoints() {\n    // TODO: return true when turso supports save points\n    return false;\n  }\n\n  @Override\n  public boolean supportsNamedParameters() {\n    // TODO: return true when both turso and jdbc supports named parameters\n    return false;\n  }\n\n  @Override\n  public boolean supportsMultipleOpenResults() {\n    return false;\n  }\n\n  @Override\n  public boolean supportsGetGeneratedKeys() {\n    // TODO: check\n    return false;\n  }\n\n  @Override\n  @SkipNullableCheck\n  public ResultSet getSuperTypes(String catalog, String schemaPattern, String typeNamePattern)\n      throws SQLException {\n    if (getSuperTypes == null) {\n      connection.checkOpen();\n      getSuperTypes =\n          connection.prepareStatement(\n              \"select null as TYPE_CAT, null as TYPE_SCHEM, \"\n                  + \"null as TYPE_NAME, null as SUPERTYPE_CAT, null as SUPERTYPE_SCHEM, \"\n                  + \"null as SUPERTYPE_NAME limit 0;\");\n    }\n    return getSuperTypes.executeQuery();\n  }\n\n  @Override\n  @SkipNullableCheck\n  public ResultSet getSuperTables(String catalog, String schemaPattern, String tableNamePattern)\n      throws SQLException {\n    if (getSuperTables == null) {\n      connection.checkOpen();\n      getSuperTables =\n          connection.prepareStatement(\n              \"select null as TABLE_CAT, null as TABLE_SCHEM, \"\n                  + \"null as TABLE_NAME, null as SUPERTABLE_NAME limit 0;\");\n    }\n    return getSuperTables.executeQuery();\n  }\n\n  @Override\n  @SkipNullableCheck\n  public ResultSet getAttributes(\n      String catalog, String schemaPattern, String typeNamePattern, String attributeNamePattern)\n      throws SQLException {\n    if (getAttributes == null) {\n      connection.checkOpen();\n      getAttributes =\n          connection.prepareStatement(\n              \"select null as TYPE_CAT, null as TYPE_SCHEM, \"\n                  + \"null as TYPE_NAME, null as ATTR_NAME, null as DATA_TYPE, \"\n                  + \"null as ATTR_TYPE_NAME, null as ATTR_SIZE, null as DECIMAL_DIGITS, \"\n                  + \"null as NUM_PREC_RADIX, null as NULLABLE, null as REMARKS, null as ATTR_DEF, \"\n                  + \"null as SQL_DATA_TYPE, null as SQL_DATETIME_SUB, null as CHAR_OCTET_LENGTH, \"\n                  + \"null as ORDINAL_POSITION, null as IS_NULLABLE, null as SCOPE_CATALOG, \"\n                  + \"null as SCOPE_SCHEMA, null as SCOPE_TABLE, null as SOURCE_DATA_TYPE limit 0;\");\n    }\n\n    return getAttributes.executeQuery();\n  }\n\n  @Override\n  public boolean supportsResultSetHoldability(int holdability) {\n    return holdability == ResultSet.CLOSE_CURSORS_AT_COMMIT;\n  }\n\n  @Override\n  public int getResultSetHoldability() {\n    return ResultSet.CLOSE_CURSORS_AT_COMMIT;\n  }\n\n  @Override\n  public int getDatabaseMajorVersion() {\n    // TODO - important\n    return 0;\n  }\n\n  @Override\n  public int getDatabaseMinorVersion() {\n    // TODO - important\n    return 0;\n  }\n\n  @Override\n  public int getJDBCMajorVersion() {\n    return 4;\n  }\n\n  @Override\n  public int getJDBCMinorVersion() {\n    return 2;\n  }\n\n  @Override\n  public int getSQLStateType() {\n    return DatabaseMetaData.sqlStateSQL99;\n  }\n\n  @Override\n  public boolean locatorsUpdateCopy() {\n    return false;\n  }\n\n  @Override\n  public boolean supportsStatementPooling() {\n    return false;\n  }\n\n  @Override\n  public RowIdLifetime getRowIdLifetime() throws SQLException {\n    throw new SQLFeatureNotSupportedException();\n  }\n\n  @Override\n  public ResultSet getSchemas(String catalog, String schemaPattern) throws SQLException {\n    if (getSchemas == null) {\n      connection.checkOpen();\n      getSchemas =\n          connection.prepareStatement(\"select null as TABLE_SCHEM, null as TABLE_CATALOG limit 0;\");\n    }\n\n    return getSchemas.executeQuery();\n  }\n\n  @Override\n  public boolean supportsStoredFunctionsUsingCallSyntax() throws SQLException {\n    throw new SQLFeatureNotSupportedException();\n  }\n\n  @Override\n  public boolean autoCommitFailureClosesAllResultSets() throws SQLException {\n    throw new SQLFeatureNotSupportedException();\n  }\n\n  @Override\n  public ResultSet getClientInfoProperties() throws SQLException {\n    throw new SQLFeatureNotSupportedException();\n  }\n\n  @Override\n  @SkipNullableCheck\n  public ResultSet getFunctions(String catalog, String schemaPattern, String functionNamePattern)\n      throws SQLException {\n    throw new SQLFeatureNotSupportedException();\n  }\n\n  @Override\n  @SkipNullableCheck\n  public ResultSet getFunctionColumns(\n      String catalog, String schemaPattern, String functionNamePattern, String columnNamePattern)\n      throws SQLException {\n    throw new SQLFeatureNotSupportedException(\"Not yet implemented by SQLite JDBC driver\");\n  }\n\n  @Override\n  @SkipNullableCheck\n  public ResultSet getPseudoColumns(\n      String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern)\n      throws SQLException {\n    throw new SQLFeatureNotSupportedException();\n  }\n\n  @Override\n  public boolean generatedKeyAlwaysReturned() throws SQLException {\n    throw new SQLFeatureNotSupportedException();\n  }\n\n  @Override\n  @SkipNullableCheck\n  public <T> T unwrap(Class<T> iface) throws SQLException {\n    return null;\n  }\n\n  @Override\n  public boolean isWrapperFor(Class<?> iface) throws SQLException {\n    return false;\n  }\n}\n"
  },
  {
    "path": "bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java",
    "content": "package tech.turso.jdbc4;\n\nimport static java.util.Objects.requireNonNull;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.Reader;\nimport java.math.BigDecimal;\nimport java.net.URL;\nimport java.nio.ByteBuffer;\nimport java.nio.charset.StandardCharsets;\nimport java.sql.*;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Calendar;\nimport tech.turso.annotations.Nullable;\nimport tech.turso.annotations.SkipNullableCheck;\nimport tech.turso.core.TursoResultSet;\n\n/** JDBC 4 PreparedStatement implementation for Turso databases. */\npublic final class JDBC4PreparedStatement extends JDBC4Statement implements PreparedStatement {\n\n  private final String sql;\n  private final JDBC4ResultSet resultSet;\n\n  private final int paramCount;\n  private Object[] currentBatchParams;\n  private final ArrayList<Object[]> batchQueryParams = new ArrayList<>();\n\n  /**\n   * Creates a new JDBC4PreparedStatement.\n   *\n   * @param connection the database connection\n   * @param sql the SQL statement to prepare\n   * @throws SQLException if a database access error occurs\n   */\n  public JDBC4PreparedStatement(JDBC4Connection connection, String sql) throws SQLException {\n    super(connection);\n    this.sql = sql;\n    this.statement = connection.prepare(sql);\n    this.resultSet = new JDBC4ResultSet(this.statement.getResultSet(), this);\n    this.paramCount = statement.parameterCount();\n    this.currentBatchParams = new Object[paramCount];\n  }\n\n  @Override\n  public ResultSet executeQuery() throws SQLException {\n    // TODO: check bindings etc\n    bindParams(currentBatchParams);\n    return this.resultSet;\n  }\n\n  @Override\n  public int executeUpdate() throws SQLException {\n    requireNonNull(this.statement);\n    bindParams(currentBatchParams);\n    final TursoResultSet resultSet = statement.getResultSet();\n    resultSet.consumeAll();\n    return Math.toIntExact(statement.changes());\n  }\n\n  /**\n   * This helper method saves a parameter locally without binding it to the underlying native\n   * statement. We have to do this so we are able to switch between different sets of parameters\n   * when batching queries.\n   */\n  private void setParam(int parameterIndex, @Nullable Object object) {\n    requireNonNull(this.statement);\n    currentBatchParams[parameterIndex - 1] = object;\n  }\n\n  @Override\n  public void setNull(int parameterIndex, int sqlType) throws SQLException {\n    requireNonNull(this.statement);\n    setParam(parameterIndex, null);\n  }\n\n  @Override\n  public void setBoolean(int parameterIndex, boolean x) throws SQLException {\n    requireNonNull(this.statement);\n    setParam(parameterIndex, x ? 1 : 0);\n  }\n\n  @Override\n  public void setByte(int parameterIndex, byte x) throws SQLException {\n    requireNonNull(this.statement);\n    setParam(parameterIndex, x);\n  }\n\n  @Override\n  public void setShort(int parameterIndex, short x) throws SQLException {\n    requireNonNull(this.statement);\n    setParam(parameterIndex, x);\n  }\n\n  @Override\n  public void setInt(int parameterIndex, int x) throws SQLException {\n    requireNonNull(this.statement);\n    setParam(parameterIndex, x);\n  }\n\n  @Override\n  public void setLong(int parameterIndex, long x) throws SQLException {\n    requireNonNull(this.statement);\n    setParam(parameterIndex, x);\n  }\n\n  @Override\n  public void setFloat(int parameterIndex, float x) throws SQLException {\n    requireNonNull(this.statement);\n    setParam(parameterIndex, x);\n  }\n\n  @Override\n  public void setDouble(int parameterIndex, double x) throws SQLException {\n    requireNonNull(this.statement);\n    setParam(parameterIndex, x);\n  }\n\n  @Override\n  public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException {\n    requireNonNull(this.statement);\n    setParam(parameterIndex, x.toString());\n  }\n\n  @Override\n  public void setString(int parameterIndex, String x) throws SQLException {\n    requireNonNull(this.statement);\n    setParam(parameterIndex, x);\n  }\n\n  @Override\n  public void setBytes(int parameterIndex, byte[] x) throws SQLException {\n    requireNonNull(this.statement);\n    setParam(parameterIndex, x);\n  }\n\n  @Override\n  public void setDate(int parameterIndex, Date x) throws SQLException {\n    requireNonNull(this.statement);\n    if (x == null) {\n      setParam(parameterIndex, null);\n    } else {\n      long time = x.getTime();\n      setParam(parameterIndex, ByteBuffer.allocate(Long.BYTES).putLong(time).array());\n    }\n  }\n\n  @Override\n  public void setTime(int parameterIndex, Time x) throws SQLException {\n    requireNonNull(this.statement);\n    if (x == null) {\n      setParam(parameterIndex, null);\n    } else {\n      long time = x.getTime();\n      setParam(parameterIndex, ByteBuffer.allocate(Long.BYTES).putLong(time).array());\n    }\n  }\n\n  @Override\n  public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException {\n    requireNonNull(this.statement);\n    if (x == null) {\n      setParam(parameterIndex, null);\n    } else {\n      long time = x.getTime();\n      setParam(parameterIndex, ByteBuffer.allocate(Long.BYTES).putLong(time).array());\n    }\n  }\n\n  @Override\n  public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException {\n    requireNonNull(this.statement);\n    if (x == null) {\n      setParam(parameterIndex, null);\n      return;\n    }\n    if (length < 0) {\n      throw new SQLException(\"setAsciiStream length must be non-negative\");\n    }\n    if (length == 0) {\n      setParam(parameterIndex, \"\");\n      return;\n    }\n    try {\n      byte[] buffer = new byte[length];\n      int offset = 0;\n      int read;\n      while (offset < length && (read = x.read(buffer, offset, length - offset)) > 0) {\n        offset += read;\n      }\n      String ascii = new String(buffer, 0, offset, StandardCharsets.US_ASCII);\n      setParam(parameterIndex, ascii);\n    } catch (IOException e) {\n      throw new SQLException(\"Error reading ASCII stream\", e);\n    }\n  }\n\n  @Override\n  public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException {\n    requireNonNull(this.statement);\n    if (x == null) {\n      setParam(parameterIndex, null);\n      return;\n    }\n    if (length < 0) {\n      throw new SQLException(\"setUnicodeStream length must be non-negative\");\n    }\n    if (length == 0) {\n      setParam(parameterIndex, \"\");\n      return;\n    }\n    try {\n      byte[] buffer = new byte[length];\n      int offset = 0;\n      int read;\n      while (offset < length && (read = x.read(buffer, offset, length - offset)) > 0) {\n        offset += read;\n      }\n      String text = new String(buffer, 0, offset, StandardCharsets.UTF_8);\n      setParam(parameterIndex, text);\n    } catch (IOException e) {\n      throw new SQLException(\"Error reading Unicode stream\", e);\n    }\n  }\n\n  @Override\n  public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException {\n    requireNonNull(this.statement);\n    if (x == null) {\n      setParam(parameterIndex, null);\n      return;\n    }\n    if (length < 0) {\n      throw new SQLException(\"setBinaryStream length must be non-negative\");\n    }\n    if (length == 0) {\n      setParam(parameterIndex, new byte[0]);\n      return;\n    }\n    try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {\n      byte[] buffer = new byte[8192];\n      int bytesRead;\n      int totalRead = 0;\n      while (totalRead < length\n          && (bytesRead = x.read(buffer, 0, Math.min(buffer.length, length - totalRead))) > 0) {\n        baos.write(buffer, 0, bytesRead);\n        totalRead += bytesRead;\n      }\n      byte[] data = baos.toByteArray();\n      setParam(parameterIndex, data);\n    } catch (IOException e) {\n      throw new SQLException(\"Error reading binary stream\", e);\n    }\n  }\n\n  @Override\n  public void clearParameters() {\n    this.currentBatchParams = new Object[paramCount];\n  }\n\n  @Override\n  public void clearBatch() throws SQLException {\n    this.batchQueryParams.clear();\n    this.currentBatchParams = new Object[paramCount];\n  }\n\n  @Override\n  public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException {\n    // TODO\n  }\n\n  @Override\n  public void setObject(int parameterIndex, Object x) throws SQLException {\n    requireNonNull(this.statement);\n    if (x == null) {\n      setParam(parameterIndex, null);\n      return;\n    }\n    if (x instanceof String) {\n      setString(parameterIndex, (String) x);\n    } else if (x instanceof Integer) {\n      setInt(parameterIndex, (Integer) x);\n    } else if (x instanceof Long) {\n      setLong(parameterIndex, (Long) x);\n    } else if (x instanceof Boolean) {\n      setBoolean(parameterIndex, (Boolean) x);\n    } else if (x instanceof Double) {\n      setDouble(parameterIndex, (Double) x);\n    } else if (x instanceof Float) {\n      setFloat(parameterIndex, (Float) x);\n    } else if (x instanceof Byte) {\n      setByte(parameterIndex, (Byte) x);\n    } else if (x instanceof Short) {\n      setShort(parameterIndex, (Short) x);\n    } else if (x instanceof byte[]) {\n      setBytes(parameterIndex, (byte[]) x);\n    } else if (x instanceof Timestamp) {\n      setTimestamp(parameterIndex, (Timestamp) x);\n    } else if (x instanceof Date) {\n      setDate(parameterIndex, (Date) x);\n    } else if (x instanceof Time) {\n      setTime(parameterIndex, (Time) x);\n    } else if (x instanceof BigDecimal) {\n      setBigDecimal(parameterIndex, (BigDecimal) x);\n    } else if (x instanceof Blob\n        || x instanceof Clob\n        || x instanceof InputStream\n        || x instanceof Reader) {\n      throw new SQLException(\n          \"setObject does not yet support LOB or Stream types because the corresponding set methods are unimplemented. Type found: \"\n              + x.getClass().getName());\n    } else {\n      throw new SQLException(\"Unsupported object type in setObject: \" + x.getClass().getName());\n    }\n  }\n\n  @Override\n  public boolean execute() throws SQLException {\n    return execute(currentBatchParams);\n  }\n\n  /** This helper method runs the statement using the provided parameter values. */\n  private boolean execute(Object[] params) throws SQLException {\n    // TODO: check whether this is sufficient\n    requireNonNull(statement);\n    bindParams(params);\n    boolean result = statement.execute();\n    updateCount = statement.changes();\n    return result;\n  }\n\n  @Override\n  public int[] executeBatch() throws SQLException {\n    return Arrays.stream(executeLargeBatch()).mapToInt(l -> (int) l).toArray();\n  }\n\n  @Override\n  public long[] executeLargeBatch() throws SQLException {\n    requireNonNull(this.statement);\n    if (batchQueryParams.isEmpty()) {\n      return new long[0];\n    }\n    long[] updateCounts = new long[batchQueryParams.size()];\n    if (!isBatchCompatibleStatement(sql)) {\n      updateCounts[0] = EXECUTE_FAILED;\n      BatchUpdateException bue =\n          new BatchUpdateException(\n              \"Batch commands cannot return result sets.\",\n              \"HY000\", // General error SQL state\n              0,\n              Arrays.stream(updateCounts).mapToInt(l -> (int) l).toArray());\n      // Clear the batch after failure\n      clearBatch();\n      throw bue;\n    }\n    for (int i = 0; i < batchQueryParams.size(); i++) {\n      try {\n        statement.reset();\n        execute(batchQueryParams.get(i));\n        updateCounts[i] = getUpdateCount();\n      } catch (SQLException e) {\n        BatchUpdateException bue =\n            new BatchUpdateException(\n                \"Batch entry \" + i + \" (\" + sql + \") failed: \" + e.getMessage(),\n                e.getSQLState(),\n                e.getErrorCode(),\n                updateCounts,\n                e.getCause());\n        // Clear the batch after failure\n        clearBatch();\n        throw bue;\n      }\n    }\n    clearBatch();\n    return updateCounts;\n  }\n\n  /** Takes the given set of parameters and binds it to the underlying statement. */\n  private void bindParams(Object[] params) throws SQLException {\n    requireNonNull(statement);\n    for (int paramIndex = 1; paramIndex <= params.length; paramIndex++) {\n      statement.bindObject(paramIndex, params[paramIndex - 1]);\n    }\n  }\n\n  @Override\n  public void addBatch() {\n    batchQueryParams.add(currentBatchParams);\n    currentBatchParams = new Object[paramCount];\n  }\n\n  @Override\n  public void addBatch(String sql) throws SQLException {\n    throw new SQLException(\"addBatch(String) cannot be called on a PreparedStatement\");\n  }\n\n  @Override\n  public void setCharacterStream(int parameterIndex, @Nullable Reader reader, int length)\n      throws SQLException {\n    requireNonNull(this.statement);\n    if (reader == null) {\n      setParam(parameterIndex, null);\n      return;\n    }\n    if (length < 0) {\n      throw new SQLException(\"setCharacterStream length must be non-negative\");\n    }\n    if (length == 0) {\n      setParam(parameterIndex, \"\");\n      return;\n    }\n    try {\n      char[] buffer = new char[length];\n      int offset = 0;\n      int read;\n      while (offset < length && (read = reader.read(buffer, offset, length - offset)) > 0) {\n        offset += read;\n      }\n      String value = new String(buffer, 0, offset);\n      setParam(parameterIndex, value);\n    } catch (IOException e) {\n      throw new SQLException(\"Error reading character stream\", e);\n    }\n  }\n\n  @Override\n  public void setRef(int parameterIndex, Ref x) throws SQLException {\n    // TODO\n  }\n\n  @Override\n  public void setBlob(int parameterIndex, Blob x) throws SQLException {\n    // TODO\n  }\n\n  @Override\n  public void setClob(int parameterIndex, Clob x) throws SQLException {\n    // TODO\n  }\n\n  @Override\n  public void setArray(int parameterIndex, Array x) throws SQLException {\n    // TODO\n  }\n\n  @Override\n  public ResultSetMetaData getMetaData() throws SQLException {\n    return this.resultSet;\n  }\n\n  @Override\n  public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException {\n    setDate(parameterIndex, x);\n  }\n\n  @Override\n  public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException {\n    setTime(parameterIndex, x);\n  }\n\n  @Override\n  public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException {\n    // TODO: Apply calendar timezone conversion\n    setTimestamp(parameterIndex, x);\n  }\n\n  @Override\n  public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException {\n    // TODO\n  }\n\n  @Override\n  public void setURL(int parameterIndex, URL x) throws SQLException {\n    // TODO\n  }\n\n  @Override\n  @SkipNullableCheck\n  public ParameterMetaData getParameterMetaData() throws SQLException {\n    // TODO\n    return null;\n  }\n\n  @Override\n  public void setRowId(int parameterIndex, RowId x) throws SQLException {\n    // TODO\n  }\n\n  @Override\n  public void setNString(int parameterIndex, String value) throws SQLException {\n    // TODO\n  }\n\n  @Override\n  public void setNCharacterStream(int parameterIndex, Reader value, long length)\n      throws SQLException {\n    // TODO\n  }\n\n  @Override\n  public void setNClob(int parameterIndex, NClob value) throws SQLException {\n    // TODO\n  }\n\n  @Override\n  public void setClob(int parameterIndex, Reader reader, long length) throws SQLException {\n    // TODO\n  }\n\n  @Override\n  public void setBlob(int parameterIndex, InputStream inputStream, long length)\n      throws SQLException {\n    // TODO\n  }\n\n  @Override\n  public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException {\n    // TODO\n  }\n\n  @Override\n  public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException {\n    // TODO\n  }\n\n  @Override\n  public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength)\n      throws SQLException {\n    // TODO\n  }\n\n  @Override\n  public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException {\n    requireLengthIsPositiveInt(length);\n    setAsciiStream(parameterIndex, x, (int) length);\n  }\n\n  @Override\n  public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException {\n    requireLengthIsPositiveInt(length);\n    setBinaryStream(parameterIndex, x, (int) length);\n  }\n\n  @Override\n  public void setCharacterStream(int parameterIndex, @Nullable Reader reader, long length)\n      throws SQLException {\n    requireLengthIsPositiveInt(length);\n    setCharacterStream(parameterIndex, reader, (int) length);\n  }\n\n  private void requireLengthIsPositiveInt(long length) throws SQLFeatureNotSupportedException {\n    if (length > Integer.MAX_VALUE || length < 0) {\n      throw new SQLFeatureNotSupportedException(\n          \"Data must have a length between 0 and Integer.MAX_VALUE\");\n    }\n  }\n\n  @Override\n  public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException {\n    requireNonNull(this.statement);\n    if (x == null) {\n      setParam(parameterIndex, null);\n      return;\n    }\n    byte[] data = readBytes(x);\n    String ascii = new String(data, StandardCharsets.US_ASCII);\n    setParam(parameterIndex, ascii);\n  }\n\n  @Override\n  public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException {\n    requireNonNull(this.statement);\n    if (x == null) {\n      setParam(parameterIndex, null);\n      return;\n    }\n    byte[] data = readBytes(x);\n    setParam(parameterIndex, data);\n  }\n\n  /**\n   * Reads all bytes from the given input stream.\n   *\n   * @param x the input stream to read\n   * @return a byte array containing the data\n   * @throws SQLException if an I/O error occurs while reading\n   */\n  private byte[] readBytes(InputStream x) throws SQLException {\n    try {\n      int firstByte = x.read();\n      if (firstByte == -1) {\n        return new byte[0];\n      }\n      ByteArrayOutputStream baos = new ByteArrayOutputStream();\n      baos.write(firstByte);\n      byte[] buffer = new byte[8192];\n      int bytesRead;\n      while ((bytesRead = x.read(buffer)) > 0) {\n        baos.write(buffer, 0, bytesRead);\n      }\n      return baos.toByteArray();\n    } catch (IOException e) {\n      throw new SQLException(\"Error reading InputStream\", e);\n    }\n  }\n\n  @Override\n  public void setCharacterStream(int parameterIndex, @Nullable Reader reader) throws SQLException {\n    requireNonNull(this.statement);\n    if (reader == null) {\n      setParam(parameterIndex, null);\n      return;\n    }\n    try {\n      StringBuilder sb = new StringBuilder();\n      char[] buffer = new char[8192];\n      int read;\n      while ((read = reader.read(buffer)) != -1) {\n        sb.append(buffer, 0, read);\n      }\n      setParam(parameterIndex, sb.toString());\n    } catch (IOException e) {\n      throw new SQLException(\"Error reading character stream\", e);\n    }\n  }\n\n  @Override\n  public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException {\n    // TODO\n  }\n\n  @Override\n  public void setClob(int parameterIndex, Reader reader) throws SQLException {\n    // TODO\n  }\n\n  @Override\n  public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException {\n    // TODO\n  }\n\n  @Override\n  public void setNClob(int parameterIndex, Reader reader) throws SQLException {\n    // TODO\n  }\n}\n"
  },
  {
    "path": "bindings/java/src/main/java/tech/turso/jdbc4/JDBC4ResultSet.java",
    "content": "package tech.turso.jdbc4;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.InputStream;\nimport java.io.Reader;\nimport java.io.StringReader;\nimport java.math.BigDecimal;\nimport java.math.RoundingMode;\nimport java.net.URL;\nimport java.nio.ByteBuffer;\nimport java.sql.Array;\nimport java.sql.Blob;\nimport java.sql.Clob;\nimport java.sql.Date;\nimport java.sql.NClob;\nimport java.sql.Ref;\nimport java.sql.ResultSet;\nimport java.sql.ResultSetMetaData;\nimport java.sql.RowId;\nimport java.sql.SQLException;\nimport java.sql.SQLWarning;\nimport java.sql.SQLXML;\nimport java.sql.Statement;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.util.Calendar;\nimport java.util.Map;\nimport tech.turso.annotations.Nullable;\nimport tech.turso.annotations.SkipNullableCheck;\nimport tech.turso.core.TursoResultSet;\n\n/** JDBC 4 ResultSet implementation for Turso databases. */\npublic final class JDBC4ResultSet implements ResultSet, ResultSetMetaData {\n\n  private final TursoResultSet resultSet;\n  @Nullable private final Statement statement;\n  private boolean wasNull = false;\n\n  /**\n   * Creates a new JDBC4ResultSet.\n   *\n   * @param resultSet the underlying Turso result set\n   * @param statement the statement that created this result set\n   */\n  public JDBC4ResultSet(TursoResultSet resultSet, @Nullable Statement statement) {\n    this.resultSet = resultSet;\n    this.statement = statement;\n  }\n\n  @Override\n  public boolean next() throws SQLException {\n    return resultSet.next();\n  }\n\n  @Override\n  public void close() throws SQLException {\n    resultSet.close();\n  }\n\n  @Override\n  public boolean wasNull() throws SQLException {\n    return wasNull;\n  }\n\n  @Override\n  @Nullable\n  public String getString(int columnIndex) throws SQLException {\n    final Object result = resultSet.get(columnIndex);\n    wasNull = result == null;\n    if (result == null) {\n      return null;\n    }\n    return wrapTypeConversion(() -> (String) result);\n  }\n\n  @Override\n  public boolean getBoolean(int columnIndex) throws SQLException {\n    final Object result = resultSet.get(columnIndex);\n    wasNull = result == null;\n    if (result == null) {\n      return false;\n    }\n    return wrapTypeConversion(() -> (Long) result != 0);\n  }\n\n  @Override\n  public byte getByte(int columnIndex) throws SQLException {\n    final Object result = resultSet.get(columnIndex);\n    wasNull = result == null;\n    if (result == null) {\n      return 0;\n    }\n    return wrapTypeConversion(() -> ((Long) result).byteValue());\n  }\n\n  @Override\n  public short getShort(int columnIndex) throws SQLException {\n    final Object result = resultSet.get(columnIndex);\n    wasNull = result == null;\n    if (result == null) {\n      return 0;\n    }\n    return wrapTypeConversion(() -> ((Long) result).shortValue());\n  }\n\n  @Override\n  public int getInt(int columnIndex) throws SQLException {\n    final Object result = resultSet.get(columnIndex);\n    wasNull = result == null;\n    if (result == null) {\n      return 0;\n    }\n    return wrapTypeConversion(() -> ((Long) result).intValue());\n  }\n\n  @Override\n  public long getLong(int columnIndex) throws SQLException {\n    final Object result = resultSet.get(columnIndex);\n    wasNull = result == null;\n    if (result == null) {\n      return 0;\n    }\n    return wrapTypeConversion(() -> (long) result);\n  }\n\n  @Override\n  public float getFloat(int columnIndex) throws SQLException {\n    final Object result = resultSet.get(columnIndex);\n    wasNull = result == null;\n    if (result == null) {\n      return 0;\n    }\n    return wrapTypeConversion(() -> ((Double) result).floatValue());\n  }\n\n  @Override\n  public double getDouble(int columnIndex) throws SQLException {\n    final Object result = resultSet.get(columnIndex);\n    wasNull = result == null;\n    if (result == null) {\n      return 0;\n    }\n    return wrapTypeConversion(() -> (double) result);\n  }\n\n  // TODO: customize rounding mode?\n  @Override\n  @Nullable\n  public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException {\n    final Object result = resultSet.get(columnIndex);\n    wasNull = result == null;\n    if (result == null) {\n      return null;\n    }\n    final double doubleResult = wrapTypeConversion(() -> (double) result);\n    final BigDecimal bigDecimalResult = BigDecimal.valueOf(doubleResult);\n    return bigDecimalResult.setScale(scale, RoundingMode.HALF_UP);\n  }\n\n  @Override\n  @Nullable\n  public byte[] getBytes(int columnIndex) throws SQLException {\n    final Object result = resultSet.get(columnIndex);\n    wasNull = result == null;\n    if (result == null) {\n      return null;\n    }\n    return wrapTypeConversion(() -> (byte[]) result);\n  }\n\n  @Override\n  @Nullable\n  public Date getDate(int columnIndex) throws SQLException {\n    final Object result = resultSet.get(columnIndex);\n    wasNull = result == null;\n    if (result == null) {\n      return null;\n    }\n    return wrapTypeConversion(\n        () -> {\n          if (result instanceof byte[]) {\n            byte[] bytes = (byte[]) result;\n            if (bytes.length == Long.BYTES) {\n              long time = ByteBuffer.wrap(bytes).getLong();\n              return new Date(time);\n            }\n          }\n          throw new SQLException(\"Cannot convert value to Date: \" + result.getClass());\n        });\n  }\n\n  @Override\n  @SkipNullableCheck\n  public Time getTime(int columnIndex) throws SQLException {\n    final Object result = resultSet.get(columnIndex);\n    wasNull = result == null;\n    if (result == null) {\n      return null;\n    }\n    return wrapTypeConversion(\n        () -> {\n          if (result instanceof byte[]) {\n            byte[] bytes = (byte[]) result;\n            if (bytes.length == Long.BYTES) {\n              long time = ByteBuffer.wrap(bytes).getLong();\n              return new Time(time);\n            }\n          }\n          throw new SQLException(\"Cannot convert value to Date: \" + result.getClass());\n        });\n  }\n\n  @Override\n  @SkipNullableCheck\n  public Timestamp getTimestamp(int columnIndex) throws SQLException {\n    final Object result = resultSet.get(columnIndex);\n    wasNull = result == null;\n    if (result == null) {\n      return null;\n    }\n    return wrapTypeConversion(\n        () -> {\n          if (result instanceof byte[]) {\n            byte[] bytes = (byte[]) result;\n            if (bytes.length == Long.BYTES) {\n              long time = ByteBuffer.wrap(bytes).getLong();\n              return new Timestamp(time);\n            }\n          }\n          throw new SQLException(\"Cannot convert value to Timestamp: \" + result.getClass());\n        });\n  }\n\n  @Override\n  @SkipNullableCheck\n  public InputStream getAsciiStream(int columnIndex) throws SQLException {\n    final Object result = resultSet.get(columnIndex);\n    wasNull = result == null;\n    if (result == null) {\n      return null;\n    }\n    return wrapTypeConversion(\n        () -> {\n          if (result instanceof String) {\n            return new ByteArrayInputStream(((String) result).getBytes(\"US-ASCII\"));\n          } else if (result instanceof byte[]) {\n            return new ByteArrayInputStream((byte[]) result);\n          }\n          throw new SQLException(\"Cannot convert to ASCII stream: \" + result.getClass());\n        });\n  }\n\n  @Override\n  @SkipNullableCheck\n  public InputStream getUnicodeStream(int columnIndex) throws SQLException {\n    final Object result = resultSet.get(columnIndex);\n    wasNull = result == null;\n    if (result == null) {\n      return null;\n    }\n    return wrapTypeConversion(\n        () -> {\n          if (result instanceof String) {\n            return new ByteArrayInputStream(((String) result).getBytes(\"UTF-8\"));\n          } else if (result instanceof byte[]) {\n            return new ByteArrayInputStream((byte[]) result);\n          }\n          throw new SQLException(\"Cannot convert to Unicode stream: \" + result.getClass());\n        });\n  }\n\n  @Override\n  @SkipNullableCheck\n  public InputStream getBinaryStream(int columnIndex) throws SQLException {\n    final Object result = resultSet.get(columnIndex);\n    wasNull = result == null;\n    if (result == null) {\n      return null;\n    }\n    return wrapTypeConversion(\n        () -> {\n          if (result instanceof byte[]) {\n            return new ByteArrayInputStream((byte[]) result);\n          }\n          throw new SQLException(\"Cannot convert to binary stream: \" + result.getClass());\n        });\n  }\n\n  @Override\n  @Nullable\n  public String getString(String columnLabel) throws SQLException {\n    return getString(findColumn(columnLabel));\n  }\n\n  @Override\n  public boolean getBoolean(String columnLabel) throws SQLException {\n    return getBoolean(findColumn(columnLabel));\n  }\n\n  @Override\n  public byte getByte(String columnLabel) throws SQLException {\n    return getByte(findColumn(columnLabel));\n  }\n\n  @Override\n  public short getShort(String columnLabel) throws SQLException {\n    return getShort(findColumn(columnLabel));\n  }\n\n  @Override\n  public int getInt(String columnLabel) throws SQLException {\n    return getInt(findColumn(columnLabel));\n  }\n\n  @Override\n  public long getLong(String columnLabel) throws SQLException {\n    return getLong(findColumn(columnLabel));\n  }\n\n  @Override\n  public float getFloat(String columnLabel) throws SQLException {\n    return getFloat(findColumn(columnLabel));\n  }\n\n  @Override\n  public double getDouble(String columnLabel) throws SQLException {\n    return getDouble(findColumn(columnLabel));\n  }\n\n  @Override\n  @SkipNullableCheck\n  public BigDecimal getBigDecimal(String columnLabel, int scale) throws SQLException {\n    return getBigDecimal(findColumn(columnLabel), scale);\n  }\n\n  @Override\n  @Nullable\n  public byte[] getBytes(String columnLabel) throws SQLException {\n    return getBytes(findColumn(columnLabel));\n  }\n\n  @Override\n  @Nullable\n  public Date getDate(String columnLabel) throws SQLException {\n    final Object result = resultSet.get(columnLabel);\n    wasNull = result == null;\n    if (result == null) {\n      return null;\n    }\n    return wrapTypeConversion(\n        () -> {\n          if (result instanceof byte[]) {\n            byte[] bytes = (byte[]) result;\n            if (bytes.length == Long.BYTES) {\n              long time = ByteBuffer.wrap(bytes).getLong();\n              return new Date(time);\n            }\n          }\n          // Try to parse as string if it's stored as TEXT\n          if (result instanceof String) {\n            return Date.valueOf((String) result);\n          }\n          throw new SQLException(\"Cannot convert value to Date: \" + result.getClass());\n        });\n  }\n\n  @Override\n  @SkipNullableCheck\n  public Time getTime(String columnLabel) throws SQLException {\n    return getTime(findColumn(columnLabel));\n  }\n\n  @Override\n  @SkipNullableCheck\n  public Timestamp getTimestamp(String columnLabel) throws SQLException {\n    return getTimestamp(findColumn(columnLabel));\n  }\n\n  @Override\n  @SkipNullableCheck\n  public InputStream getAsciiStream(String columnLabel) throws SQLException {\n    return getAsciiStream(findColumn(columnLabel));\n  }\n\n  @Override\n  @SkipNullableCheck\n  public InputStream getUnicodeStream(String columnLabel) throws SQLException {\n    return getUnicodeStream(findColumn(columnLabel));\n  }\n\n  @Override\n  @SkipNullableCheck\n  public InputStream getBinaryStream(String columnLabel) throws SQLException {\n    return getBinaryStream(findColumn(columnLabel));\n  }\n\n  @Override\n  @SkipNullableCheck\n  public SQLWarning getWarnings() throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void clearWarnings() throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public String getCursorName() throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public ResultSetMetaData getMetaData() throws SQLException {\n    return this;\n  }\n\n  @Override\n  public Object getObject(int columnIndex) throws SQLException {\n    final Object result = resultSet.get(columnIndex);\n    wasNull = result == null;\n    return result;\n  }\n\n  @Override\n  @SkipNullableCheck\n  public Object getObject(String columnLabel) throws SQLException {\n    return getObject(findColumn(columnLabel));\n  }\n\n  @Override\n  public int findColumn(String columnLabel) throws SQLException {\n    if (columnLabel == null || columnLabel.isEmpty()) {\n      throw new SQLException(\"column name not found\");\n    }\n\n    final String[] columnNames = resultSet.getColumnNames();\n    for (int i = 0; i < columnNames.length; i++) {\n      if (columnNames[i].equals(columnLabel)) {\n        return i + 1;\n      }\n    }\n    throw new SQLException(\"column name \" + columnLabel + \" not found\");\n  }\n\n  @Override\n  @SkipNullableCheck\n  public Reader getCharacterStream(int columnIndex) throws SQLException {\n    final Object result = resultSet.get(columnIndex);\n    wasNull = result == null;\n    if (result == null) {\n      return null;\n    }\n    return wrapTypeConversion(() -> new StringReader((String) result));\n  }\n\n  @Override\n  @Nullable\n  public Reader getCharacterStream(String columnLabel) throws SQLException {\n    return getCharacterStream(findColumn(columnLabel));\n  }\n\n  @Override\n  @Nullable\n  public BigDecimal getBigDecimal(int columnIndex) throws SQLException {\n    final Object result = resultSet.get(columnIndex);\n    wasNull = result == null;\n    if (result == null) {\n      return null;\n    }\n    final double doubleResult = wrapTypeConversion(() -> (double) result);\n    return BigDecimal.valueOf(doubleResult);\n  }\n\n  @Override\n  @SkipNullableCheck\n  public BigDecimal getBigDecimal(String columnLabel) throws SQLException {\n    return getBigDecimal(findColumn(columnLabel));\n  }\n\n  @Override\n  public boolean isBeforeFirst() throws SQLException {\n    // Empty ResultSet should return false per JDBC spec\n    if (resultSet.isEmpty()) {\n      return false;\n    }\n    return resultSet.isOpen() && resultSet.getRow() == 0 && !resultSet.isPastLastRow();\n  }\n\n  @Override\n  public boolean isAfterLast() throws SQLException {\n    return resultSet.isOpen() && resultSet.isPastLastRow();\n  }\n\n  @Override\n  public boolean isFirst() throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public boolean isLast() throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void beforeFirst() throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void afterLast() throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public boolean first() throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public boolean last() throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public int getRow() throws SQLException {\n    return resultSet.getRow();\n  }\n\n  @Override\n  public boolean absolute(int row) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public boolean relative(int rows) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public boolean previous() throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void setFetchDirection(int direction) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public int getFetchDirection() throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void setFetchSize(int rows) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public int getFetchSize() throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public int getType() throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public int getConcurrency() throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public boolean rowUpdated() throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public boolean rowInserted() throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public boolean rowDeleted() throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateNull(int columnIndex) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateBoolean(int columnIndex, boolean x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateByte(int columnIndex, byte x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateShort(int columnIndex, short x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateInt(int columnIndex, int x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateLong(int columnIndex, long x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateFloat(int columnIndex, float x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateDouble(int columnIndex, double x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateBigDecimal(int columnIndex, BigDecimal x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateString(int columnIndex, String x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateBytes(int columnIndex, byte[] x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateDate(int columnIndex, Date x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateTime(int columnIndex, Time x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateTimestamp(int columnIndex, Timestamp x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateAsciiStream(int columnIndex, InputStream x, int length) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateBinaryStream(int columnIndex, InputStream x, int length) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateCharacterStream(int columnIndex, Reader x, int length) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateObject(int columnIndex, Object x, int scaleOrLength) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateObject(int columnIndex, Object x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateNull(String columnLabel) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateBoolean(String columnLabel, boolean x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateByte(String columnLabel, byte x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateShort(String columnLabel, short x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateInt(String columnLabel, int x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateLong(String columnLabel, long x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateFloat(String columnLabel, float x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateDouble(String columnLabel, double x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateBigDecimal(String columnLabel, BigDecimal x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateString(String columnLabel, String x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateBytes(String columnLabel, byte[] x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateDate(String columnLabel, Date x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateTime(String columnLabel, Time x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateTimestamp(String columnLabel, Timestamp x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateAsciiStream(String columnLabel, InputStream x, int length) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateBinaryStream(String columnLabel, InputStream x, int length)\n      throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateCharacterStream(String columnLabel, Reader reader, int length)\n      throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateObject(String columnLabel, Object x, int scaleOrLength) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateObject(String columnLabel, Object x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void insertRow() throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateRow() throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void deleteRow() throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void refreshRow() throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void cancelRowUpdates() throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void moveToInsertRow() throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void moveToCurrentRow() throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  @Nullable\n  public Statement getStatement() throws SQLException {\n    return statement;\n  }\n\n  @Override\n  @SkipNullableCheck\n  public Object getObject(int columnIndex, Map<String, Class<?>> map) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  @SkipNullableCheck\n  public Ref getRef(int columnIndex) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  @SkipNullableCheck\n  public Blob getBlob(int columnIndex) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  @SkipNullableCheck\n  public Clob getClob(int columnIndex) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  @SkipNullableCheck\n  public Array getArray(int columnIndex) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  @SkipNullableCheck\n  public Object getObject(String columnLabel, Map<String, Class<?>> map) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  @SkipNullableCheck\n  public Ref getRef(String columnLabel) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  @SkipNullableCheck\n  public Blob getBlob(String columnLabel) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  @SkipNullableCheck\n  public Clob getClob(String columnLabel) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  @SkipNullableCheck\n  public Array getArray(String columnLabel) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  @Nullable\n  public Date getDate(int columnIndex, Calendar cal) throws SQLException {\n    final Date date = getDate(columnIndex);\n    if (date == null || cal == null) {\n      return date;\n    }\n    return new Date(date.getTime() + calculateTimezoneOffset(date.getTime(), cal));\n  }\n\n  @Override\n  @Nullable\n  public Date getDate(String columnLabel, Calendar cal) throws SQLException {\n    return getDate(findColumn(columnLabel), cal);\n  }\n\n  @Override\n  @Nullable\n  public Time getTime(int columnIndex, Calendar cal) throws SQLException {\n    final Time time = getTime(columnIndex);\n    if (time == null || cal == null) {\n      return time;\n    }\n    return new Time(time.getTime() + calculateTimezoneOffset(time.getTime(), cal));\n  }\n\n  @Override\n  @SkipNullableCheck\n  public Time getTime(String columnLabel, Calendar cal) throws SQLException {\n    return getTime(findColumn(columnLabel), cal);\n  }\n\n  @Override\n  @SkipNullableCheck\n  public Timestamp getTimestamp(int columnIndex, Calendar cal) throws SQLException {\n    final Timestamp timestamp = getTimestamp(columnIndex);\n    if (timestamp == null || cal == null) {\n      return timestamp;\n    }\n    return new Timestamp(timestamp.getTime() + calculateTimezoneOffset(timestamp.getTime(), cal));\n  }\n\n  @Override\n  @SkipNullableCheck\n  public Timestamp getTimestamp(String columnLabel, Calendar cal) throws SQLException {\n    return getTimestamp(findColumn(columnLabel), cal);\n  }\n\n  @Override\n  @SkipNullableCheck\n  public URL getURL(int columnIndex) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  @SkipNullableCheck\n  public URL getURL(String columnLabel) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateRef(int columnIndex, Ref x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateRef(String columnLabel, Ref x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateBlob(int columnIndex, Blob x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateBlob(String columnLabel, Blob x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateClob(int columnIndex, Clob x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateClob(String columnLabel, Clob x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateArray(int columnIndex, Array x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateArray(String columnLabel, Array x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  @SkipNullableCheck\n  public RowId getRowId(int columnIndex) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  @SkipNullableCheck\n  public RowId getRowId(String columnLabel) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateRowId(int columnIndex, RowId x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateRowId(String columnLabel, RowId x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public int getHoldability() throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public boolean isClosed() throws SQLException {\n    return !resultSet.isOpen();\n  }\n\n  @Override\n  public void updateNString(int columnIndex, String nString) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateNString(String columnLabel, String nString) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateNClob(int columnIndex, NClob nClob) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateNClob(String columnLabel, NClob nClob) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  @SkipNullableCheck\n  public NClob getNClob(int columnIndex) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  @SkipNullableCheck\n  public NClob getNClob(String columnLabel) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  @SkipNullableCheck\n  public SQLXML getSQLXML(int columnIndex) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  @SkipNullableCheck\n  public SQLXML getSQLXML(String columnLabel) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateSQLXML(int columnIndex, SQLXML xmlObject) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateSQLXML(String columnLabel, SQLXML xmlObject) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public String getNString(int columnIndex) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public String getNString(String columnLabel) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  @SkipNullableCheck\n  public Reader getNCharacterStream(int columnIndex) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  @SkipNullableCheck\n  public Reader getNCharacterStream(String columnLabel) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateNCharacterStream(int columnIndex, Reader x, long length) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateNCharacterStream(String columnLabel, Reader reader, long length)\n      throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateAsciiStream(int columnIndex, InputStream x, long length) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateBinaryStream(int columnIndex, InputStream x, long length) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateCharacterStream(int columnIndex, Reader x, long length) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateAsciiStream(String columnLabel, InputStream x, long length)\n      throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateBinaryStream(String columnLabel, InputStream x, long length)\n      throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateCharacterStream(String columnLabel, Reader reader, long length)\n      throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateBlob(int columnIndex, InputStream inputStream, long length)\n      throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateBlob(String columnLabel, InputStream inputStream, long length)\n      throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateClob(int columnIndex, Reader reader, long length) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateClob(String columnLabel, Reader reader, long length) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateNClob(int columnIndex, Reader reader, long length) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateNClob(String columnLabel, Reader reader, long length) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateNCharacterStream(int columnIndex, Reader x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateNCharacterStream(String columnLabel, Reader reader) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateAsciiStream(int columnIndex, InputStream x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateBinaryStream(int columnIndex, InputStream x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateCharacterStream(int columnIndex, Reader x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateAsciiStream(String columnLabel, InputStream x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateBinaryStream(String columnLabel, InputStream x) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateCharacterStream(String columnLabel, Reader reader) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateBlob(int columnIndex, InputStream inputStream) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateBlob(String columnLabel, InputStream inputStream) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateClob(int columnIndex, Reader reader) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateClob(String columnLabel, Reader reader) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateNClob(int columnIndex, Reader reader) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public void updateNClob(String columnLabel, Reader reader) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  @SkipNullableCheck\n  public <T> T getObject(int columnIndex, Class<T> type) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  @SkipNullableCheck\n  public <T> T getObject(String columnLabel, Class<T> type) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  @SkipNullableCheck\n  public <T> T unwrap(Class<T> iface) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public boolean isWrapperFor(Class<?> iface) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public int getColumnCount() throws SQLException {\n    return this.resultSet.getColumnNames().length;\n  }\n\n  @Override\n  public boolean isAutoIncrement(int column) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public boolean isCaseSensitive(int column) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public boolean isSearchable(int column) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public boolean isCurrency(int column) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public int isNullable(int column) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public boolean isSigned(int column) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public int getColumnDisplaySize(int column) throws SQLException {\n    return Integer.MAX_VALUE;\n  }\n\n  @Override\n  public String getColumnLabel(int column) throws SQLException {\n    // TODO: should consider \"AS\" keyword\n    return getColumnName(column);\n  }\n\n  @Override\n  public String getColumnName(int column) throws SQLException {\n    if (column > 0 && column <= resultSet.getColumnNames().length) {\n      return resultSet.getColumnNames()[column - 1];\n    }\n\n    throw new SQLException(\"Index out of bound: \" + column);\n  }\n\n  @Override\n  public String getSchemaName(int column) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public int getPrecision(int column) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public int getScale(int column) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public String getTableName(int column) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public String getCatalogName(int column) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public int getColumnType(int column) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public String getColumnTypeName(int column) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public boolean isReadOnly(int column) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public boolean isWritable(int column) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public boolean isDefinitelyWritable(int column) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  @Override\n  public String getColumnClassName(int column) throws SQLException {\n    throw new UnsupportedOperationException(\"not implemented\");\n  }\n\n  private long calculateTimezoneOffset(long timeMillis, Calendar targetCal) {\n    Calendar localCal = Calendar.getInstance();\n    return targetCal.getTimeZone().getOffset(timeMillis)\n        - localCal.getTimeZone().getOffset(timeMillis);\n  }\n\n  /**\n   * Functional interface for result set value suppliers.\n   *\n   * @param <T> the type of value to supply\n   */\n  @FunctionalInterface\n  public interface ResultSetSupplier<T> {\n    /**\n     * Gets a result from the result set.\n     *\n     * @return the result value\n     * @throws Exception if an error occurs\n     */\n    T get() throws Exception;\n  }\n\n  private <T> T wrapTypeConversion(ResultSetSupplier<T> supplier) throws SQLException {\n    try {\n      return supplier.get();\n    } catch (Exception e) {\n      throw new SQLException(\"Type conversion failed: \" + e);\n    }\n  }\n}\n"
  },
  {
    "path": "bindings/java/src/main/java/tech/turso/jdbc4/JDBC4Statement.java",
    "content": "package tech.turso.jdbc4;\n\nimport static java.util.Objects.requireNonNull;\n\nimport java.sql.BatchUpdateException;\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.SQLWarning;\nimport java.sql.Statement;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.locks.ReentrantLock;\nimport java.util.regex.Pattern;\nimport tech.turso.annotations.Nullable;\nimport tech.turso.annotations.SkipNullableCheck;\nimport tech.turso.core.TursoResultSet;\nimport tech.turso.core.TursoStatement;\n\n/** JDBC 4 Statement implementation for Turso databases. */\npublic class JDBC4Statement implements Statement {\n\n  private static final Pattern BATCH_COMPATIBLE_PATTERN =\n      Pattern.compile(\n          \"^\\\\s*\"\n              + // Leading whitespace\n              \"(?:/\\\\*.*?\\\\*/\\\\s*)*\"\n              + // Optional C-style comments\n              \"(?:--[^\\\\n]*\\\\n\\\\s*)*\"\n              + // Optional SQL line comments\n              \"(?:\"\n              + // Start of keywords group\n              \"INSERT|UPDATE|DELETE\"\n              + \")\\\\b\",\n          Pattern.CASE_INSENSITIVE | Pattern.DOTALL);\n\n  protected final JDBC4Connection connection;\n\n  /** The underlying Turso statement. */\n  @Nullable protected TursoStatement statement = null;\n\n  /** The number of rows affected by the last update operation. */\n  protected long updateCount;\n\n  // Because JDBC4Statement has different life cycle in compared to tursoStatement, let's use this\n  // field to manage JDBC4Statement lifecycle\n  private boolean closed;\n  private boolean closeOnCompletion;\n\n  private final int resultSetType;\n  private final int resultSetConcurrency;\n  private final int resultSetHoldability;\n\n  private int queryTimeoutSeconds;\n\n  private ReentrantLock connectionLock = new ReentrantLock();\n\n  /**\n   * List of SQL statements to be executed as a batch. Used for batch processing as per JDBC\n   * specification.\n   */\n  private List<String> batchCommands = new ArrayList<>();\n\n  public JDBC4Statement(JDBC4Connection connection) {\n    this(\n        connection,\n        ResultSet.TYPE_FORWARD_ONLY,\n        ResultSet.CONCUR_READ_ONLY,\n        ResultSet.CLOSE_CURSORS_AT_COMMIT);\n  }\n\n  public JDBC4Statement(\n      JDBC4Connection connection,\n      int resultSetType,\n      int resultSetConcurrency,\n      int resultSetHoldability) {\n    this.connection = connection;\n    this.resultSetType = resultSetType;\n    this.resultSetConcurrency = resultSetConcurrency;\n    this.resultSetHoldability = resultSetHoldability;\n  }\n\n  // TODO: should executeQuery run execute right after preparing the statement?\n  @Override\n  public ResultSet executeQuery(String sql) throws SQLException {\n    ensureOpen();\n    statement =\n        this.withConnectionTimeout(\n            () -> {\n              try {\n                // TODO: if sql is a readOnly query, do we still need the locks?\n                connectionLock.lock();\n                return connection.prepare(sql);\n              } finally {\n                connectionLock.unlock();\n              }\n            });\n\n    requireNonNull(statement, \"statement should not be null after running execute method\");\n    return new JDBC4ResultSet(statement.getResultSet(), this);\n  }\n\n  @Override\n  public int executeUpdate(String sql) throws SQLException {\n    final long previousTotalChanges = statement == null ? 0L : statement.totalChanges();\n\n    execute(sql);\n    requireNonNull(statement, \"statement should not be null after running execute method\");\n    final TursoResultSet resultSet = statement.getResultSet();\n    resultSet.consumeAll();\n\n    return (int) (statement.totalChanges() - previousTotalChanges);\n  }\n\n  @Override\n  public void close() throws SQLException {\n    if (closed) {\n      return;\n    }\n\n    if (this.statement != null) {\n      this.statement.close();\n    }\n\n    closed = true;\n  }\n\n  @Override\n  public int getMaxFieldSize() throws SQLException {\n    // TODO\n    return 0;\n  }\n\n  @Override\n  public void setMaxFieldSize(int max) throws SQLException {\n    // TODO\n  }\n\n  @Override\n  public int getMaxRows() throws SQLException {\n    // TODO\n    return 0;\n  }\n\n  @Override\n  public void setMaxRows(int max) throws SQLException {\n    // TODO\n  }\n\n  @Override\n  public void setEscapeProcessing(boolean enable) throws SQLException {\n    // TODO\n  }\n\n  @Override\n  public int getQueryTimeout() throws SQLException {\n    // TODO\n    return 0;\n  }\n\n  @Override\n  public void setQueryTimeout(int seconds) throws SQLException {\n    if (seconds < 0) {\n      throw new SQLException(\"Query timeout must be greater than 0\");\n    }\n    this.queryTimeoutSeconds = seconds;\n  }\n\n  @Override\n  public void cancel() throws SQLException {\n    // TODO\n  }\n\n  @Override\n  @SkipNullableCheck\n  public SQLWarning getWarnings() throws SQLException {\n    // TODO\n    return null;\n  }\n\n  @Override\n  public void clearWarnings() throws SQLException {\n    // TODO\n  }\n\n  @Override\n  public void setCursorName(String name) throws SQLException {\n    // TODO\n  }\n\n  /**\n   * The <code>execute</code> method executes an SQL statement and indicates the form of the first\n   * result. You must then use the methods <code>getResultSet</code> or <code>getUpdateCount</code>\n   * to retrieve the result, and <code>getMoreResults</code> to move to any subsequent result(s).\n   */\n  @Override\n  public boolean execute(String sql) throws SQLException {\n    ensureOpen();\n    return this.withConnectionTimeout(\n        () -> {\n          try {\n            // TODO: if sql is a readOnly query, do we still need the locks?\n            connectionLock.lock();\n            statement = connection.prepare(sql);\n            final long previousChanges = statement.totalChanges();\n            final boolean result = statement.execute();\n            updateGeneratedKeys();\n            updateCount = statement.totalChanges() - previousChanges;\n\n            return result;\n          } finally {\n            connectionLock.unlock();\n          }\n        });\n  }\n\n  @Override\n  public ResultSet getResultSet() throws SQLException {\n    requireNonNull(statement, \"statement is null\");\n    ensureOpen();\n    return new JDBC4ResultSet(statement.getResultSet(), this);\n  }\n\n  @Override\n  public int getUpdateCount() throws SQLException {\n    return (int) updateCount;\n  }\n\n  @Override\n  public void setFetchDirection(int direction) throws SQLException {\n    // TODO\n  }\n\n  @Override\n  public int getFetchDirection() throws SQLException {\n    // TODO\n    return 0;\n  }\n\n  @Override\n  public void setFetchSize(int rows) throws SQLException {\n    // TODO\n  }\n\n  @Override\n  public int getFetchSize() throws SQLException {\n    // TODO\n    return 0;\n  }\n\n  @Override\n  public int getResultSetConcurrency() {\n    return resultSetConcurrency;\n  }\n\n  @Override\n  public int getResultSetType() {\n    return resultSetType;\n  }\n\n  @Override\n  public void addBatch(String sql) throws SQLException {\n    ensureOpen();\n    if (sql == null) {\n      throw new SQLException(\"SQL command cannot be null\");\n    }\n    batchCommands.add(sql);\n  }\n\n  @Override\n  public void clearBatch() throws SQLException {\n    ensureOpen();\n    batchCommands.clear();\n  }\n\n  // TODO: let's make this batch operation atomic\n  @Override\n  public int[] executeBatch() throws SQLException {\n    ensureOpen();\n\n    int[] updateCounts = new int[batchCommands.size()];\n    List<String> failedCommands = new ArrayList<>();\n\n    // Execute each command in the batch\n    for (int i = 0; i < batchCommands.size(); i++) {\n      String sql = batchCommands.get(i);\n      try {\n        if (!isBatchCompatibleStatement(sql)) {\n          failedCommands.add(sql);\n          updateCounts[i] = EXECUTE_FAILED;\n          BatchUpdateException bue =\n              new BatchUpdateException(\n                  \"Batch entry \"\n                      + i\n                      + \" (\"\n                      + sql\n                      + \") was aborted. \"\n                      + \"Batch commands cannot return result sets.\",\n                  \"HY000\", // General error SQL state\n                  0,\n                  updateCounts);\n          // Clear the batch after failure\n          clearBatch();\n          throw bue;\n        }\n\n        execute(sql);\n        // For DML statements, get the update count\n        updateCounts[i] = getUpdateCount();\n      } catch (SQLException e) {\n        failedCommands.add(sql);\n        updateCounts[i] = EXECUTE_FAILED;\n\n        // Create a BatchUpdateException with the partial results\n        BatchUpdateException bue =\n            new BatchUpdateException(\n                \"Batch entry \" + i + \" (\" + sql + \") failed: \" + e.getMessage(),\n                e.getSQLState(),\n                e.getErrorCode(),\n                updateCounts,\n                e.getCause());\n        // Clear the batch after failure\n        clearBatch();\n        throw bue;\n      }\n    }\n\n    // Clear the batch after successful execution\n    clearBatch();\n    return updateCounts;\n  }\n\n  protected boolean isBatchCompatibleStatement(String sql) {\n    if (sql == null || sql.trim().isEmpty()) {\n      return false;\n    }\n\n    return BATCH_COMPATIBLE_PATTERN.matcher(sql).find();\n  }\n\n  @Override\n  public Connection getConnection() {\n    return connection;\n  }\n\n  @Override\n  public boolean getMoreResults() throws SQLException {\n    return getMoreResults(Statement.CLOSE_CURRENT_RESULT);\n  }\n\n  @Override\n  public boolean getMoreResults(int current) throws SQLException {\n    requireNonNull(statement, \"statement should not be null\");\n\n    if (current != Statement.CLOSE_CURRENT_RESULT) {\n      throw new SQLException(\"Invalid argument\");\n    }\n\n    statement.getResultSet().close();\n    updateCount = -1;\n\n    return false;\n  }\n\n  @Override\n  @SkipNullableCheck\n  public ResultSet getGeneratedKeys() throws SQLException {\n    // TODO\n    return null;\n  }\n\n  @Override\n  public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {\n    // TODO: enhance\n    return executeUpdate(sql);\n  }\n\n  @Override\n  public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {\n    // TODO: enhance\n    return executeUpdate(sql);\n  }\n\n  @Override\n  public int executeUpdate(String sql, String[] columnNames) throws SQLException {\n    // TODO: enhance\n    return executeUpdate(sql);\n  }\n\n  @Override\n  public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {\n    // TODO: enhance\n    return execute(sql);\n  }\n\n  @Override\n  public boolean execute(String sql, int[] columnIndexes) throws SQLException {\n    // TODO: enhance\n    return execute(sql);\n  }\n\n  @Override\n  public boolean execute(String sql, String[] columnNames) throws SQLException {\n    // TODO\n    return false;\n  }\n\n  @Override\n  public int getResultSetHoldability() {\n    return resultSetHoldability;\n  }\n\n  @Override\n  public boolean isClosed() throws SQLException {\n    return this.closed;\n  }\n\n  @Override\n  public void setPoolable(boolean poolable) throws SQLException {\n    // TODO\n  }\n\n  @Override\n  public boolean isPoolable() throws SQLException {\n    // TODO\n    return false;\n  }\n\n  @Override\n  public void closeOnCompletion() throws SQLException {\n    if (closed) {\n      throw new SQLException(\"statement is closed\");\n    }\n    closeOnCompletion = true;\n  }\n\n  /**\n   * Indicates whether the statement should be closed automatically when all its dependent result\n   * sets are closed.\n   */\n  @Override\n  public boolean isCloseOnCompletion() throws SQLException {\n    if (closed) {\n      throw new SQLException(\"statement is closed\");\n    }\n    return closeOnCompletion;\n  }\n\n  @Override\n  @SkipNullableCheck\n  public <T> T unwrap(Class<T> iface) throws SQLException {\n    // TODO\n    return null;\n  }\n\n  @Override\n  public boolean isWrapperFor(Class<?> iface) throws SQLException {\n    // TODO\n    return false;\n  }\n\n  protected void updateGeneratedKeys() throws SQLException {\n    // TODO\n  }\n\n  private <T> T withConnectionTimeout(SQLCallable<T> callable) throws SQLException {\n    final int originalBusyTimeoutMillis = connection.getBusyTimeout();\n    if (queryTimeoutSeconds > 0) {\n      // TODO: set busy timeout\n      connection.setBusyTimeout(1000 * queryTimeoutSeconds);\n    }\n\n    try {\n      return callable.call();\n    } finally {\n      if (queryTimeoutSeconds > 0) {\n        connection.setBusyTimeout(originalBusyTimeoutMillis);\n      }\n    }\n  }\n\n  /**\n   * Functional interface for SQL callable operations.\n   *\n   * @param <T> the return type\n   */\n  @FunctionalInterface\n  protected interface SQLCallable<T> {\n    /**\n     * Executes the SQL operation.\n     *\n     * @return the result of the operation\n     * @throws SQLException if a database access error occurs\n     */\n    T call() throws SQLException;\n  }\n\n  private void ensureOpen() throws SQLException {\n    if (closed) {\n      throw new SQLException(\"Statement is closed\");\n    }\n  }\n}\n"
  },
  {
    "path": "bindings/java/src/main/java/tech/turso/utils/ByteArrayUtils.java",
    "content": "package tech.turso.utils;\n\nimport java.nio.charset.StandardCharsets;\nimport tech.turso.annotations.Nullable;\n\n/** Utility class for converting between byte arrays and strings using UTF-8 encoding. */\npublic final class ByteArrayUtils {\n  /**\n   * Converts a UTF-8 encoded byte array to a string.\n   *\n   * @param buffer the byte array to convert, may be null\n   * @return the string representation, or null if the input is null\n   */\n  @Nullable\n  public static String utf8ByteBufferToString(@Nullable byte[] buffer) {\n    if (buffer == null) {\n      return null;\n    }\n\n    return new String(buffer, StandardCharsets.UTF_8);\n  }\n\n  /**\n   * Converts a string to a UTF-8 encoded byte array.\n   *\n   * @param str the string to convert, may be null\n   * @return the byte array representation, or null if the input is null\n   */\n  @Nullable\n  public static byte[] stringToUtf8ByteArray(@Nullable String str) {\n    if (str == null) {\n      return null;\n    }\n    return str.getBytes(StandardCharsets.UTF_8);\n  }\n}\n"
  },
  {
    "path": "bindings/java/src/main/java/tech/turso/utils/Logger.java",
    "content": "package tech.turso.utils;\n\n/** A simple internal Logger interface. */\npublic interface Logger {\n  void trace(String message, Object... params);\n\n  void debug(String message, Object... params);\n\n  void info(String message, Object... params);\n\n  void warn(String message, Object... params);\n\n  void error(String message, Object... params);\n}\n"
  },
  {
    "path": "bindings/java/src/main/java/tech/turso/utils/LoggerFactory.java",
    "content": "package tech.turso.utils;\n\nimport java.util.logging.Level;\n\n/**\n * A factory for {@link Logger} instances that uses SLF4J if present, falling back on a\n * java.util.logging implementation otherwise.\n */\npublic final class LoggerFactory {\n  static final boolean USE_SLF4J;\n\n  static {\n    boolean useSLF4J;\n    try {\n      Class.forName(\"org.slf4j.Logger\");\n      useSLF4J = true;\n    } catch (Exception e) {\n      useSLF4J = false;\n    }\n    USE_SLF4J = useSLF4J;\n  }\n\n  /**\n   * Get a {@link Logger} instance for the given host class.\n   *\n   * @param hostClass the host class from which log messages will be issued\n   * @return a Logger\n   */\n  public static Logger getLogger(Class<?> hostClass) {\n    if (USE_SLF4J) {\n      return new SLF4JLogger(hostClass);\n    }\n\n    return new JDKLogger(hostClass);\n  }\n\n  private static class JDKLogger implements Logger {\n    final java.util.logging.Logger logger;\n\n    public JDKLogger(Class<?> hostClass) {\n      logger = java.util.logging.Logger.getLogger(hostClass.getCanonicalName());\n    }\n\n    @Override\n    public void trace(String message, Object... params) {\n      if (logger.isLoggable(Level.FINEST)) {\n        logger.log(Level.FINEST, String.format(message, params));\n      }\n    }\n\n    @Override\n    public void debug(String message, Object... params) {\n      if (logger.isLoggable(Level.FINE)) {\n        logger.log(Level.FINE, String.format(message, params));\n      }\n    }\n\n    @Override\n    public void info(String message, Object... params) {\n      if (logger.isLoggable(Level.INFO)) {\n        logger.log(Level.INFO, String.format(message, params));\n      }\n    }\n\n    @Override\n    public void warn(String message, Object... params) {\n      if (logger.isLoggable(Level.WARNING)) {\n        logger.log(Level.WARNING, String.format(message, params));\n      }\n    }\n\n    @Override\n    public void error(String message, Object... params) {\n      if (logger.isLoggable(Level.SEVERE)) {\n        logger.log(Level.SEVERE, String.format(message, params));\n      }\n    }\n  }\n\n  private static class SLF4JLogger implements Logger {\n    final org.slf4j.Logger logger;\n\n    SLF4JLogger(Class<?> hostClass) {\n      logger = org.slf4j.LoggerFactory.getLogger(hostClass);\n    }\n\n    @Override\n    public void trace(String message, Object... params) {\n      if (logger.isTraceEnabled()) {\n        logger.trace(message, String.format(message, params));\n      }\n    }\n\n    @Override\n    public void debug(String message, Object... params) {\n      if (logger.isDebugEnabled()) {\n        logger.debug(message, String.format(message, params));\n      }\n    }\n\n    @Override\n    public void info(String message, Object... params) {\n      if (logger.isInfoEnabled()) {\n        logger.info(message, String.format(message, params));\n      }\n    }\n\n    @Override\n    public void warn(String message, Object... params) {\n      if (logger.isWarnEnabled()) {\n        logger.warn(message, String.format(message, params));\n      }\n    }\n\n    @Override\n    public void error(String message, Object... params) {\n      if (logger.isErrorEnabled()) {\n        logger.error(message, String.format(message, params));\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "bindings/java/src/main/java/tech/turso/utils/TursoExceptionUtils.java",
    "content": "package tech.turso.utils;\n\nimport static tech.turso.utils.ByteArrayUtils.utf8ByteBufferToString;\n\nimport java.sql.SQLException;\nimport tech.turso.TursoErrorCode;\nimport tech.turso.annotations.Nullable;\nimport tech.turso.exceptions.TursoException;\n\npublic final class TursoExceptionUtils {\n\n  /**\n   * Throws formatted SQLException with error code and message.\n   *\n   * @param errorCode Error code.\n   * @param errorMessageBytes Error message.\n   */\n  public static void throwTursoException(int errorCode, byte[] errorMessageBytes)\n      throws SQLException {\n    String errorMessage = utf8ByteBufferToString(errorMessageBytes);\n    throw buildTursoException(errorCode, errorMessage);\n  }\n\n  /**\n   * Throws formatted SQLException with error code and message.\n   *\n   * @param errorCode Error code.\n   * @param errorMessage Error message.\n   */\n  public static TursoException buildTursoException(int errorCode, @Nullable String errorMessage)\n      throws SQLException {\n    TursoErrorCode code = TursoErrorCode.getErrorCode(errorCode);\n    String msg;\n    if (code == TursoErrorCode.UNKNOWN_ERROR) {\n      msg = String.format(\"%s:%s (%s)\", code, errorCode, errorMessage);\n    } else {\n      msg = String.format(\"%s (%s)\", code, errorMessage);\n    }\n\n    return new TursoException(msg, code);\n  }\n}\n"
  },
  {
    "path": "bindings/java/src/main/resources/META-INF/services/java.sql.Driver",
    "content": "tech.turso.JDBC\n"
  },
  {
    "path": "bindings/java/src/main/resources/turso-jdbc.properties",
    "content": "driverName=turso-jdbc\n# find a way to relate driverVersion with project.version defined in gradle.properties\ndriverVersion=0.0.1-SNAPSHOT\n"
  },
  {
    "path": "bindings/java/src/test/java/tech/turso/IntegrationTest.java",
    "content": "package tech.turso;\n\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.Properties;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport tech.turso.jdbc4.JDBC4Connection;\n\nclass IntegrationTest {\n\n  private JDBC4Connection connection;\n\n  @BeforeEach\n  void setUp() throws Exception {\n    String filePath = TestUtils.createTempFile();\n    String url = \"jdbc:turso:\" + filePath;\n    connection = new JDBC4Connection(url, filePath, new Properties());\n  }\n\n  @Test\n  void create_table_multi_inserts_select() throws Exception {\n    try (Statement stmt = createDefaultStatement()) {\n      stmt.execute(\"CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT);\");\n      stmt.execute(\"INSERT INTO users VALUES (1, 'seonwoo');\");\n      stmt.execute(\"INSERT INTO users VALUES (2, 'seonwoo');\");\n      stmt.execute(\"INSERT INTO users VALUES (3, 'seonwoo');\");\n      stmt.execute(\"SELECT * FROM users\");\n    }\n  }\n\n  private Statement createDefaultStatement() throws SQLException {\n    return connection.createStatement(\n        ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT);\n  }\n}\n"
  },
  {
    "path": "bindings/java/src/test/java/tech/turso/JDBCTest.java",
    "content": "package tech.turso;\n\nimport static org.assertj.core.api.AssertionsForClassTypes.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertDoesNotThrow;\n\nimport java.sql.Connection;\nimport java.sql.Driver;\nimport java.sql.DriverManager;\nimport java.sql.SQLException;\nimport java.util.Properties;\nimport org.junit.jupiter.api.Test;\nimport tech.turso.jdbc4.JDBC4Connection;\n\nclass JDBCTest {\n\n  @Test\n  void null_is_returned_when_invalid_url_is_passed() throws Exception {\n    JDBC4Connection connection = JDBC.createConnection(\"jdbc:invalid:xxx\", new Properties());\n    assertThat(connection).isNull();\n  }\n\n  @Test\n  void non_null_connection_is_returned_when_valid_url_is_passed() throws Exception {\n    String fileUrl = TestUtils.createTempFile();\n    JDBC4Connection connection = JDBC.createConnection(\"jdbc:turso:\" + fileUrl, new Properties());\n    assertThat(connection).isNotNull();\n  }\n\n  @Test\n  void connection_can_be_retrieved_from_DriverManager() throws SQLException {\n    try (Connection connection = DriverManager.getConnection(\"jdbc:turso:sample.db\")) {\n      assertThat(connection).isNotNull();\n    }\n  }\n\n  @Test\n  void retrieve_version() {\n    assertDoesNotThrow(() -> DriverManager.getDriver(\"jdbc:turso:\").getMajorVersion());\n    assertDoesNotThrow(() -> DriverManager.getDriver(\"jdbc:turso:\").getMinorVersion());\n  }\n\n  @Test\n  void all_driver_property_info_should_have_a_description() throws Exception {\n    Driver driver = DriverManager.getDriver(\"jdbc:turso:\");\n    assertThat(driver.getPropertyInfo(null, null))\n        .allSatisfy((info) -> assertThat(info.description).isNotNull());\n  }\n\n  @Test\n  void return_null_when_protocol_can_not_be_handled() throws Exception {\n    assertThat(JDBC.createConnection(\"jdbc:unknownprotocol:\", null)).isNull();\n  }\n}\n"
  },
  {
    "path": "bindings/java/src/test/java/tech/turso/TestUtils.java",
    "content": "package tech.turso;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\n\npublic class TestUtils {\n  /** Create temporary file and returns the path. */\n  public static String createTempFile() throws IOException {\n    return Files.createTempFile(\"turso_test_db\", null).toAbsolutePath().toString();\n  }\n}\n"
  },
  {
    "path": "bindings/java/src/test/java/tech/turso/core/TursoDBTest.java",
    "content": "package tech.turso.core;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nimport java.io.File;\nimport java.nio.file.Files;\nimport org.junit.jupiter.api.Test;\nimport tech.turso.TestUtils;\nimport tech.turso.TursoErrorCode;\nimport tech.turso.exceptions.TursoException;\n\npublic class TursoDBTest {\n\n  @Test\n  void db_should_open_and_close_normally() throws Exception {\n    String dbPath = TestUtils.createTempFile();\n    TursoDB db = TursoDB.create(\"jdbc:turso\" + dbPath, dbPath);\n\n    db.close();\n\n    assertFalse(db.isOpen());\n  }\n\n  @Test\n  void throwJavaException_should_throw_appropriate_java_exception() throws Exception {\n    String dbPath = TestUtils.createTempFile();\n    TursoDB db = TursoDB.create(\"jdbc:turso:\" + dbPath, dbPath);\n\n    final int tursoExceptionCode = TursoErrorCode.TURSO_ETC.code;\n    try {\n      db.throwJavaException(tursoExceptionCode);\n    } catch (Exception e) {\n      assertThat(e).isInstanceOf(TursoException.class);\n      TursoException tursoException = (TursoException) e;\n      assertThat(tursoException.getResultCode().code).isEqualTo(tursoExceptionCode);\n    }\n  }\n\n  @Test\n  void db_should_open_with_encryption() throws Exception {\n    String dbPath = TestUtils.createTempFile();\n    String hexkey = \"b1bbfda4f589dc9daaf004fe21111e00dc00c98237102f5c7002a5669fc76327\";\n    String wrongKey = \"aaaaaaa4f589dc9daaf004fe21111e00dc00c98237102f5c7002a5669fc76327\";\n\n    // Create encrypted database\n    TursoDB db =\n        TursoDB.createWithEncryption(\n            \"jdbc:turso:\" + dbPath, dbPath, TursoEncryptionCipher.AEGIS_256, hexkey);\n    TursoConnection conn = new TursoConnection(\"jdbc:turso:\" + dbPath, db);\n\n    TursoStatement stmt = conn.prepare(\"CREATE TABLE t(x TEXT)\");\n    stmt.execute();\n    stmt.close();\n\n    stmt = conn.prepare(\"INSERT INTO t VALUES ('secret')\");\n    stmt.execute();\n    stmt.close();\n\n    stmt = conn.prepare(\"PRAGMA wal_checkpoint(truncate)\");\n    stmt.execute();\n    stmt.close();\n\n    conn.close();\n    db.close();\n\n    // Verify data is encrypted on disk\n    byte[] content = Files.readAllBytes(new File(dbPath).toPath());\n    assertThat(content.length).isGreaterThan(1024);\n    String contentStr = new String(content);\n    assertThat(contentStr).doesNotContain(\"secret\");\n\n    // Verify we can re-open with the same key\n    TursoDB db2 =\n        TursoDB.createWithEncryption(\n            \"jdbc:turso:\" + dbPath, dbPath, TursoEncryptionCipher.AEGIS_256, hexkey);\n    TursoConnection conn2 = new TursoConnection(\"jdbc:turso:\" + dbPath, db2);\n\n    TursoStatement stmt2 = conn2.prepare(\"SELECT * FROM t\");\n    boolean hasResult = stmt2.execute();\n    assertThat(hasResult).isTrue();\n    TursoResultSet rs = stmt2.getResultSet();\n    assertThat(rs.get(1)).isEqualTo(\"secret\");\n    stmt2.close();\n\n    conn2.close();\n    db2.close();\n\n    // Verify opening with wrong key fails\n    assertThrows(\n        Exception.class,\n        () -> {\n          TursoDB dbWrongKey =\n              TursoDB.createWithEncryption(\n                  \"jdbc:turso:\" + dbPath, dbPath, TursoEncryptionCipher.AEGIS_256, wrongKey);\n          TursoConnection connWrongKey = new TursoConnection(\"jdbc:turso:\" + dbPath, dbWrongKey);\n          TursoStatement stmtWrongKey = connWrongKey.prepare(\"SELECT * FROM t\");\n          stmtWrongKey.execute();\n        });\n\n    // Verify opening without encryption fails\n    assertThrows(\n        Exception.class,\n        () -> {\n          TursoDB dbNoEncryption = TursoDB.create(\"jdbc:turso:\" + dbPath, dbPath);\n          TursoConnection connNoEncryption =\n              new TursoConnection(\"jdbc:turso:\" + dbPath, dbNoEncryption);\n          TursoStatement stmtNoEncryption = connNoEncryption.prepare(\"SELECT * FROM t\");\n          stmtNoEncryption.execute();\n        });\n  }\n}\n"
  },
  {
    "path": "bindings/java/src/test/java/tech/turso/core/TursoStatementTest.java",
    "content": "package tech.turso.core;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\nimport java.util.Properties;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport tech.turso.TestUtils;\nimport tech.turso.jdbc4.JDBC4Connection;\n\nclass TursoStatementTest {\n\n  private JDBC4Connection connection;\n\n  @BeforeEach\n  void setUp() throws Exception {\n    String filePath = TestUtils.createTempFile();\n    String url = \"jdbc:turso:\" + filePath;\n    connection = new JDBC4Connection(url, filePath, new Properties());\n  }\n\n  @Test\n  void closing_statement_closes_related_resources() throws Exception {\n    TursoStatement stmt = connection.prepare(\"SELECT 1;\");\n    stmt.execute();\n\n    stmt.close();\n    assertTrue(stmt.isClosed());\n    assertFalse(stmt.getResultSet().isOpen());\n  }\n\n  @Test\n  void test_initializeColumnMetadata() throws Exception {\n    runSql(\"CREATE TABLE users (name TEXT, age INT, country TEXT);\");\n    runSql(\"INSERT INTO users VALUES ('seonwoo', 30, 'KR');\");\n\n    final TursoStatement stmt = connection.prepare(\"SELECT * FROM users\");\n    stmt.initializeColumnMetadata();\n    final TursoResultSet rs = stmt.getResultSet();\n    final String[] columnNames = rs.getColumnNames();\n\n    assertEquals(\"name\", columnNames[0]);\n    assertEquals(\"age\", columnNames[1]);\n    assertEquals(\"country\", columnNames[2]);\n  }\n\n  @Test\n  void test_bindNull() throws Exception {\n    runSql(\"CREATE TABLE test (col1 TEXT);\");\n    TursoStatement stmt = connection.prepare(\"INSERT INTO test (col1) VALUES (?);\");\n    stmt.bindNull(1);\n    stmt.execute();\n    stmt.close();\n\n    TursoStatement selectStmt = connection.prepare(\"SELECT col1 FROM test;\");\n    selectStmt.execute();\n    assertNull(selectStmt.getResultSet().get(1));\n    selectStmt.close();\n  }\n\n  @Test\n  void test_bindLong() throws Exception {\n    runSql(\"CREATE TABLE test (col1 BIGINT);\");\n    TursoStatement stmt = connection.prepare(\"INSERT INTO test (col1) VALUES (?);\");\n    stmt.bindLong(1, 123456789L);\n    stmt.execute();\n    stmt.close();\n\n    TursoStatement selectStmt = connection.prepare(\"SELECT col1 FROM test;\");\n    selectStmt.execute();\n    assertEquals(123456789L, selectStmt.getResultSet().get(1));\n    selectStmt.close();\n  }\n\n  @Test\n  void test_bindDouble() throws Exception {\n    runSql(\"CREATE TABLE test (col1 DOUBLE);\");\n    TursoStatement stmt = connection.prepare(\"INSERT INTO test (col1) VALUES (?);\");\n    stmt.bindDouble(1, 3.14);\n    stmt.execute();\n    stmt.close();\n\n    TursoStatement selectStmt = connection.prepare(\"SELECT col1 FROM test;\");\n    selectStmt.execute();\n    assertEquals(3.14, selectStmt.getResultSet().get(1));\n    selectStmt.close();\n  }\n\n  @Test\n  void test_bindText() throws Exception {\n    runSql(\"CREATE TABLE test (col1 TEXT);\");\n    TursoStatement stmt = connection.prepare(\"INSERT INTO test (col1) VALUES (?);\");\n    stmt.bindText(1, \"hello\");\n    stmt.execute();\n    stmt.close();\n\n    TursoStatement selectStmt = connection.prepare(\"SELECT col1 FROM test;\");\n    selectStmt.execute();\n    assertEquals(\"hello\", selectStmt.getResultSet().get(1));\n    selectStmt.close();\n  }\n\n  @Test\n  void test_bindBlob() throws Exception {\n    runSql(\"CREATE TABLE test (col1 BLOB);\");\n    TursoStatement stmt = connection.prepare(\"INSERT INTO test (col1) VALUES (?);\");\n    byte[] blob = {1, 2, 3, 4, 5};\n    stmt.bindBlob(1, blob);\n    stmt.execute();\n    stmt.close();\n\n    TursoStatement selectStmt = connection.prepare(\"SELECT col1 FROM test;\");\n    selectStmt.execute();\n    assertArrayEquals(blob, (byte[]) selectStmt.getResultSet().get(1));\n    selectStmt.close();\n  }\n\n  @Test\n  void test_parameterCount() throws Exception {\n    runSql(\"CREATE TABLE test (col1 INT);\");\n    assertEquals(0, connection.prepare(\"INSERT INTO test VALUES (1)\").parameterCount());\n    assertEquals(1, connection.prepare(\"INSERT INTO test VALUES (?)\").parameterCount());\n    assertEquals(2, connection.prepare(\"INSERT INTO test VALUES (?), (?)\").parameterCount());\n  }\n\n  private void runSql(String sql) throws Exception {\n    TursoStatement stmt = connection.prepare(sql);\n    while (stmt.execute()) {\n      stmt.execute();\n    }\n  }\n}\n"
  },
  {
    "path": "bindings/java/src/test/java/tech/turso/jdbc4/JDBC4ConnectionTest.java",
    "content": "package tech.turso.jdbc4;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.Properties;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport tech.turso.TestUtils;\n\nclass JDBC4ConnectionTest {\n\n  private JDBC4Connection connection;\n\n  @BeforeEach\n  void setUp() throws Exception {\n    String filePath = TestUtils.createTempFile();\n    String url = \"jdbc:turso:\" + filePath;\n    connection = new JDBC4Connection(url, filePath, new Properties());\n  }\n\n  @Test\n  void test_create_statement_valid() throws SQLException {\n    Statement stmt = connection.createStatement();\n    assertNotNull(stmt);\n    assertEquals(ResultSet.TYPE_FORWARD_ONLY, stmt.getResultSetType());\n    assertEquals(ResultSet.CONCUR_READ_ONLY, stmt.getResultSetConcurrency());\n    assertEquals(ResultSet.CLOSE_CURSORS_AT_COMMIT, stmt.getResultSetHoldability());\n  }\n\n  @Test\n  void test_create_statement_with_type_and_concurrency_valid() throws SQLException {\n    Statement stmt =\n        connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);\n    assertNotNull(stmt);\n    assertEquals(ResultSet.TYPE_FORWARD_ONLY, stmt.getResultSetType());\n    assertEquals(ResultSet.CONCUR_READ_ONLY, stmt.getResultSetConcurrency());\n  }\n\n  @Test\n  void test_create_statement_with_all_params_valid() throws SQLException {\n    Statement stmt =\n        connection.createStatement(\n            ResultSet.TYPE_FORWARD_ONLY,\n            ResultSet.CONCUR_READ_ONLY,\n            ResultSet.CLOSE_CURSORS_AT_COMMIT);\n    assertNotNull(stmt);\n    assertEquals(ResultSet.TYPE_FORWARD_ONLY, stmt.getResultSetType());\n    assertEquals(ResultSet.CONCUR_READ_ONLY, stmt.getResultSetConcurrency());\n    assertEquals(ResultSet.CLOSE_CURSORS_AT_COMMIT, stmt.getResultSetHoldability());\n  }\n\n  @Test\n  void test_create_statement_invalid() {\n    assertThrows(\n        SQLException.class,\n        () -> {\n          connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, -1);\n        });\n  }\n\n  @Test\n  void prepare_simple_create_table() throws Exception {\n    connection.prepare(\"CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT)\");\n  }\n\n  @Test\n  void calling_close_multiple_times_throws_no_exception() throws Exception {\n    assertFalse(connection.isClosed());\n    connection.close();\n    assertTrue(connection.isClosed());\n    connection.close();\n  }\n\n  @Test\n  void calling_methods_on_closed_connection_should_throw_exception() throws Exception {\n    connection.close();\n    assertTrue(connection.isClosed());\n    assertThrows(\n        SQLException.class,\n        () ->\n            connection.createStatement(\n                ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, -1));\n  }\n\n  @Test\n  void isValid_should_return_true_on_open_connection() throws SQLException {\n    assertTrue(connection.isValid(10));\n  }\n\n  @Test\n  void isValid_should_return_false_on_closed_connection() throws SQLException {\n    connection.close();\n    assertFalse(connection.isValid(10));\n  }\n}\n"
  },
  {
    "path": "bindings/java/src/test/java/tech/turso/jdbc4/JDBC4DatabaseMetaDataTest.java",
    "content": "package tech.turso.jdbc4;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.Properties;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport tech.turso.TestUtils;\n\nclass JDBC4DatabaseMetaDataTest {\n\n  private JDBC4Connection connection;\n  private JDBC4DatabaseMetaData metaData;\n\n  @BeforeEach\n  void set_up() throws Exception {\n    String filePath = TestUtils.createTempFile();\n    String url = \"jdbc:turso:\" + filePath;\n    connection = new JDBC4Connection(url, filePath, new Properties());\n    metaData = new JDBC4DatabaseMetaData(connection);\n  }\n\n  @Test\n  void getURL_should_return_non_empty_string() {\n    assertFalse(metaData.getURL().isEmpty());\n  }\n\n  @Test\n  void getDriverName_should_not_return_empty_string() {\n    assertFalse(metaData.getDriverName().isEmpty());\n  }\n\n  @Test\n  void test_getDriverMajorVersion() {\n    metaData.getDriverMajorVersion();\n  }\n\n  @Test\n  void test_getDriverMinorVersion() {\n    metaData.getDriverMinorVersion();\n  }\n\n  @Test\n  void getDriverVersion_should_not_return_empty_string() {\n    assertFalse(metaData.getDriverVersion().isEmpty());\n  }\n\n  @Test\n  void getTables_with_non_empty_catalog_should_return_empty() throws SQLException {\n    ResultSet rs = metaData.getTables(\"nonexistent\", null, null, null);\n    assertNotNull(rs);\n    assertFalse(rs.next());\n    rs.close();\n  }\n\n  @Test\n  void getTables_with_non_empty_schema_should_return_empty() throws SQLException {\n    ResultSet rs = metaData.getTables(null, \"schema\", null, null);\n    assertNotNull(rs);\n    assertFalse(rs.next());\n    rs.close();\n  }\n\n  @Test\n  void getTables_should_return_correct_table_info() throws SQLException {\n    try (Statement stmt = connection.createStatement()) {\n      stmt.execute(\"CREATE TABLE test_table (id INTEGER PRIMARY KEY)\");\n    }\n\n    ResultSet rs = metaData.getTables(null, null, null, null);\n\n    assertNotNull(rs);\n\n    assertTrue(rs.next());\n\n    assertEquals(\"test_table\", rs.getString(\"TABLE_NAME\"));\n    assertEquals(\"TABLE\", rs.getString(\"TABLE_TYPE\"));\n\n    assertFalse(rs.next());\n\n    rs.close();\n  }\n\n  @Test\n  void getTables_with_pattern_should_filter_results() throws SQLException {\n    // Create test tables\n    try (Statement stmt = connection.createStatement()) {\n      stmt.execute(\"CREATE TABLE test1 (id INTEGER PRIMARY KEY)\");\n      stmt.execute(\"CREATE TABLE test2 (id INTEGER PRIMARY KEY)\");\n      stmt.execute(\"CREATE TABLE other (id INTEGER PRIMARY KEY)\");\n    }\n\n    ResultSet rs = metaData.getTables(null, null, \"test%\", null);\n\n    assertNotNull(rs);\n\n    int tableCount = 0;\n    while (rs.next()) {\n      String tableName = rs.getString(\"TABLE_NAME\");\n      assertTrue(tableName.startsWith(\"test\"));\n      tableCount++;\n    }\n\n    assertEquals(2, tableCount);\n\n    rs.close();\n  }\n\n  @Test\n  @Disabled(\"CREATE VIEW not supported yet\")\n  void getTables_with_type_filter_should_return_only_views() throws SQLException {\n    try (Statement stmt = connection.createStatement()) {\n      stmt.execute(\"CREATE TABLE my_table (id INTEGER PRIMARY KEY)\");\n      stmt.execute(\"CREATE VIEW my_view AS SELECT * FROM my_table\");\n    }\n\n    ResultSet rs = metaData.getTables(null, null, null, new String[] {\"VIEW\"});\n\n    assertNotNull(rs);\n\n    assertTrue(rs.next());\n    assertEquals(\"my_view\", rs.getString(\"TABLE_NAME\"));\n    assertEquals(\"VIEW\", rs.getString(\"TABLE_TYPE\"));\n\n    assertFalse(rs.next());\n\n    rs.close();\n  }\n\n  @Test\n  @Disabled(\"CREATE VIEW not supported yet\")\n  void getTables_with_pattern_and_type_filter_should_work_together() throws SQLException {\n    try (Statement stmt = connection.createStatement()) {\n      stmt.execute(\"CREATE TABLE alpha (id INTEGER)\");\n      stmt.execute(\"CREATE TABLE beta (id INTEGER)\");\n      stmt.execute(\"CREATE VIEW alpha_view AS SELECT * FROM alpha\");\n    }\n\n    ResultSet rs = metaData.getTables(null, null, \"alpha%\", new String[] {\"VIEW\"});\n\n    assertNotNull(rs);\n\n    assertTrue(rs.next());\n    assertEquals(\"alpha_view\", rs.getString(\"TABLE_NAME\"));\n    assertEquals(\"VIEW\", rs.getString(\"TABLE_TYPE\"));\n\n    assertFalse(rs.next());\n\n    rs.close();\n  }\n}\n"
  },
  {
    "path": "bindings/java/src/test/java/tech/turso/jdbc4/JDBC4PreparedStatementTest.java",
    "content": "package tech.turso.jdbc4;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.InputStream;\nimport java.io.Reader;\nimport java.io.StringReader;\nimport java.math.BigDecimal;\nimport java.nio.charset.StandardCharsets;\nimport java.sql.*;\nimport java.util.Properties;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport tech.turso.TestUtils;\n\nclass JDBC4PreparedStatementTest {\n\n  private JDBC4Connection connection;\n\n  @BeforeEach\n  void setUp() throws Exception {\n    String filePath = TestUtils.createTempFile();\n    String url = \"jdbc:turso:\" + filePath;\n    connection = new JDBC4Connection(url, filePath, new Properties());\n  }\n\n  @Test\n  void testSetBoolean() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col INTEGER)\").execute();\n    PreparedStatement stmt =\n        connection.prepareStatement(\"INSERT INTO test (col) VALUES (?), (?), (?)\");\n    stmt.setBoolean(1, true);\n    stmt.setBoolean(2, false);\n    stmt.setBoolean(3, true);\n    stmt.execute();\n\n    PreparedStatement stmt2 = connection.prepareStatement(\"SELECT * FROM test;\");\n    ResultSet rs = stmt2.executeQuery();\n    assertTrue(rs.next());\n    assertTrue(rs.getBoolean(1));\n    assertTrue(rs.next());\n    assertFalse(rs.getBoolean(1));\n    assertTrue(rs.next());\n    assertTrue(rs.getBoolean(1));\n  }\n\n  @Test\n  void testSetByte() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col INTEGER)\").execute();\n    PreparedStatement stmt =\n        connection.prepareStatement(\"INSERT INTO test (col) VALUES (?), (?), (?)\");\n    stmt.setByte(1, (byte) 1);\n    stmt.setByte(2, (byte) 2);\n    stmt.setByte(3, (byte) 3);\n    stmt.execute();\n\n    PreparedStatement stmt2 = connection.prepareStatement(\"SELECT * FROM test;\");\n    ResultSet rs = stmt2.executeQuery();\n    assertTrue(rs.next());\n    assertEquals(1, rs.getByte(1));\n    assertTrue(rs.next());\n    assertEquals(2, rs.getByte(1));\n    assertTrue(rs.next());\n    assertEquals(3, rs.getByte(1));\n  }\n\n  @Test\n  void testSetShort() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col INTEGER)\").execute();\n    PreparedStatement stmt =\n        connection.prepareStatement(\"INSERT INTO test (col) VALUES (?), (?), (?)\");\n    stmt.setShort(1, (short) 1);\n    stmt.setShort(2, (short) 2);\n    stmt.setShort(3, (short) 3);\n    stmt.execute();\n\n    PreparedStatement stmt2 = connection.prepareStatement(\"SELECT * FROM test;\");\n    ResultSet rs = stmt2.executeQuery();\n    assertTrue(rs.next());\n    assertEquals(1, rs.getShort(1));\n    assertTrue(rs.next());\n    assertEquals(2, rs.getShort(1));\n    assertTrue(rs.next());\n    assertEquals(3, rs.getShort(1));\n  }\n\n  @Test\n  void testSetInt() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col INTEGER)\").execute();\n    PreparedStatement stmt =\n        connection.prepareStatement(\"INSERT INTO test (col) VALUES (?), (?), (?)\");\n    stmt.setInt(1, 1);\n    stmt.setInt(2, 2);\n    stmt.setInt(3, 3);\n    stmt.execute();\n\n    PreparedStatement stmt2 = connection.prepareStatement(\"SELECT * FROM test;\");\n    ResultSet rs = stmt2.executeQuery();\n    assertTrue(rs.next());\n    assertEquals(1, rs.getInt(1));\n    assertTrue(rs.next());\n    assertEquals(2, rs.getInt(1));\n    assertTrue(rs.next());\n    assertEquals(3, rs.getInt(1));\n  }\n\n  @Test\n  void testSetLong() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col INTEGER)\").execute();\n    PreparedStatement stmt =\n        connection.prepareStatement(\"INSERT INTO test (col) VALUES (?), (?), (?)\");\n    stmt.setLong(1, 1L);\n    stmt.setLong(2, 2L);\n    stmt.setLong(3, 3L);\n    stmt.execute();\n\n    PreparedStatement stmt2 = connection.prepareStatement(\"SELECT * FROM test;\");\n    ResultSet rs = stmt2.executeQuery();\n    assertTrue(rs.next());\n    assertEquals(1L, rs.getLong(1));\n    assertTrue(rs.next());\n    assertEquals(2L, rs.getLong(1));\n    assertTrue(rs.next());\n    assertEquals(3L, rs.getLong(1));\n  }\n\n  @Test\n  void testSetFloat() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col REAL)\").execute();\n    PreparedStatement stmt =\n        connection.prepareStatement(\"INSERT INTO test (col) VALUES (?), (?), (?)\");\n    stmt.setFloat(1, 1.0f);\n    stmt.setFloat(2, 2.0f);\n    stmt.setFloat(3, 3.0f);\n    stmt.execute();\n\n    PreparedStatement stmt2 = connection.prepareStatement(\"SELECT * FROM test;\");\n    ResultSet rs = stmt2.executeQuery();\n    assertTrue(rs.next());\n    assertEquals(1.0f, rs.getFloat(1));\n    assertTrue(rs.next());\n    assertEquals(2.0f, rs.getFloat(1));\n    assertTrue(rs.next());\n    assertEquals(3.0f, rs.getFloat(1));\n  }\n\n  @Test\n  void testSetDouble() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col REAL)\").execute();\n    PreparedStatement stmt =\n        connection.prepareStatement(\"INSERT INTO test (col) VALUES (?), (?), (?)\");\n    stmt.setDouble(1, 1.0);\n    stmt.setDouble(2, 2.0);\n    stmt.setDouble(3, 3.0);\n    stmt.execute();\n\n    PreparedStatement stmt2 = connection.prepareStatement(\"SELECT * FROM test;\");\n    ResultSet rs = stmt2.executeQuery();\n    assertTrue(rs.next());\n    assertEquals(1.0, rs.getDouble(1));\n    assertTrue(rs.next());\n    assertEquals(2.0, rs.getDouble(1));\n    assertTrue(rs.next());\n    assertEquals(3.0, rs.getDouble(1));\n  }\n\n  @Test\n  void testSetBigDecimal() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col TEXT)\").execute();\n    PreparedStatement stmt =\n        connection.prepareStatement(\"INSERT INTO test (col) VALUES (?), (?), (?)\");\n    stmt.setBigDecimal(1, new BigDecimal(\"1.0\"));\n    stmt.setBigDecimal(2, new BigDecimal(\"2.0\"));\n    stmt.setBigDecimal(3, new BigDecimal(\"3.0\"));\n    stmt.execute();\n\n    PreparedStatement stmt2 = connection.prepareStatement(\"SELECT * FROM test;\");\n    ResultSet rs = stmt2.executeQuery();\n    assertTrue(rs.next());\n    assertEquals(\"1.0\", rs.getString(1));\n    assertTrue(rs.next());\n    assertEquals(\"2.0\", rs.getString(1));\n    assertTrue(rs.next());\n    assertEquals(\"3.0\", rs.getString(1));\n  }\n\n  @Test\n  void testSetString() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col TEXT)\").execute();\n    PreparedStatement stmt =\n        connection.prepareStatement(\"INSERT INTO test (col) VALUES (?), (?), (?)\");\n    stmt.setString(1, \"test1\");\n    stmt.setString(2, \"test2\");\n    stmt.setString(3, \"test3\");\n    stmt.execute();\n\n    PreparedStatement stmt2 = connection.prepareStatement(\"SELECT * FROM test;\");\n    ResultSet rs = stmt2.executeQuery();\n    assertTrue(rs.next());\n    assertEquals(\"test1\", rs.getString(1));\n    assertTrue(rs.next());\n    assertEquals(\"test2\", rs.getString(1));\n    assertTrue(rs.next());\n    assertEquals(\"test3\", rs.getString(1));\n  }\n\n  @Test\n  void testSetBytes() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col BLOB)\").execute();\n    PreparedStatement stmt =\n        connection.prepareStatement(\"INSERT INTO test (col) VALUES (?), (?), (?)\");\n    stmt.setBytes(1, new byte[] {1, 2, 3});\n    stmt.setBytes(2, new byte[] {4, 5, 6});\n    stmt.setBytes(3, new byte[] {7, 8, 9});\n    stmt.execute();\n\n    PreparedStatement stmt2 = connection.prepareStatement(\"SELECT * FROM test;\");\n    ResultSet rs = stmt2.executeQuery();\n    assertTrue(rs.next());\n    assertArrayEquals(new byte[] {1, 2, 3}, rs.getBytes(1));\n    assertTrue(rs.next());\n    assertArrayEquals(new byte[] {4, 5, 6}, rs.getBytes(1));\n    assertTrue(rs.next());\n    assertArrayEquals(new byte[] {7, 8, 9}, rs.getBytes(1));\n  }\n\n  @Test\n  void testSetDate() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col BLOB)\").execute();\n    PreparedStatement stmt =\n        connection.prepareStatement(\"INSERT INTO test (col) VALUES (?), (?), (?)\");\n\n    Date date1 = new Date(1000000000000L);\n    Date date2 = new Date(1500000000000L);\n    Date date3 = new Date(2000000000000L);\n\n    stmt.setDate(1, date1);\n    stmt.setDate(2, date2);\n    stmt.setDate(3, date3);\n    stmt.execute();\n\n    PreparedStatement stmt2 = connection.prepareStatement(\"SELECT * FROM test;\");\n    JDBC4ResultSet rs = (JDBC4ResultSet) stmt2.executeQuery();\n\n    assertTrue(rs.next());\n    assertEquals(date1, rs.getDate(1));\n    assertTrue(rs.next());\n    assertEquals(date2, rs.getDate(1));\n    assertTrue(rs.next());\n    assertEquals(date3, rs.getDate(1));\n  }\n\n  @Test\n  void testSetTime() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col BLOB)\").execute();\n    PreparedStatement stmt =\n        connection.prepareStatement(\"INSERT INTO test (col) VALUES (?), (?), (?)\");\n\n    Time time1 = new Time(1000000000000L);\n    Time time2 = new Time(1500000000000L);\n    Time time3 = new Time(2000000000000L);\n\n    stmt.setTime(1, time1);\n    stmt.setTime(2, time2);\n    stmt.setTime(3, time3);\n    stmt.execute();\n\n    PreparedStatement stmt2 = connection.prepareStatement(\"SELECT * FROM test;\");\n    JDBC4ResultSet rs = (JDBC4ResultSet) stmt2.executeQuery();\n\n    assertTrue(rs.next());\n    assertEquals(time1, rs.getTime(1));\n    assertTrue(rs.next());\n    assertEquals(time2, rs.getTime(1));\n    assertTrue(rs.next());\n    assertEquals(time3, rs.getTime(1));\n  }\n\n  @Test\n  void testSetTimestamp() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col BLOB)\").execute();\n    PreparedStatement stmt =\n        connection.prepareStatement(\"INSERT INTO test (col) VALUES (?), (?), (?)\");\n\n    Timestamp timestamp1 = new Timestamp(1000000000000L);\n    Timestamp timestamp2 = new Timestamp(1500000000000L);\n    Timestamp timestamp3 = new Timestamp(2000000000000L);\n\n    stmt.setTimestamp(1, timestamp1);\n    stmt.setTimestamp(2, timestamp2);\n    stmt.setTimestamp(3, timestamp3);\n    stmt.execute();\n\n    PreparedStatement stmt2 = connection.prepareStatement(\"SELECT * FROM test;\");\n    JDBC4ResultSet rs = (JDBC4ResultSet) stmt2.executeQuery();\n\n    assertTrue(rs.next());\n    assertEquals(timestamp1, rs.getTimestamp(1));\n    assertTrue(rs.next());\n    assertEquals(timestamp2, rs.getTimestamp(1));\n    assertTrue(rs.next());\n    assertEquals(timestamp3, rs.getTimestamp(1));\n  }\n\n  @Test\n  void testInsertMultipleTypes() throws SQLException {\n    connection\n        .prepareStatement(\"CREATE TABLE test (col1 INTEGER, col2 REAL, col3 TEXT, col4 BLOB)\")\n        .execute();\n    PreparedStatement stmt =\n        connection.prepareStatement(\n            \"INSERT INTO test (col1, col2, col3, col4) VALUES (?, ?, ?, ?), (?, ?, ?, ?)\");\n\n    stmt.setInt(1, 1);\n    stmt.setFloat(2, 1.1f);\n    stmt.setString(3, \"row1\");\n    stmt.setBytes(4, new byte[] {1, 2, 3});\n\n    stmt.setInt(5, 2);\n    stmt.setFloat(6, 2.2f);\n    stmt.setString(7, \"row2\");\n    stmt.setBytes(8, new byte[] {4, 5, 6});\n\n    stmt.execute();\n\n    PreparedStatement stmt2 = connection.prepareStatement(\"SELECT * FROM test;\");\n    ResultSet rs = stmt2.executeQuery();\n\n    assertTrue(rs.next());\n    assertEquals(1, rs.getInt(1));\n    assertEquals(1.1f, rs.getFloat(2));\n    assertEquals(\"row1\", rs.getString(3));\n    assertArrayEquals(new byte[] {1, 2, 3}, rs.getBytes(4));\n\n    assertTrue(rs.next());\n    assertEquals(2, rs.getInt(1));\n    assertEquals(2.2f, rs.getFloat(2));\n    assertEquals(\"row2\", rs.getString(3));\n    assertArrayEquals(new byte[] {4, 5, 6}, rs.getBytes(4));\n  }\n\n  @Test\n  void testSetObjectCoversAllSupportedTypes() throws SQLException {\n    connection\n        .prepareStatement(\n            \"CREATE TABLE test (\"\n                + \"col1 INTEGER, \"\n                + \"col2 REAL, \"\n                + \"col3 TEXT, \"\n                + \"col4 BLOB, \"\n                + \"col5 INTEGER, \"\n                + \"col6 TEXT, \"\n                + \"col7 TEXT, \"\n                + \"col8 TEXT, \"\n                + \"col9 TEXT\"\n                + \")\")\n        .execute();\n\n    PreparedStatement stmt =\n        connection.prepareStatement(\"INSERT INTO test VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\");\n\n    stmt.setObject(1, 42);\n    stmt.setObject(2, 3.141592d);\n    stmt.setObject(3, \"string_value\");\n    stmt.setObject(4, new byte[] {1, 2, 3});\n    stmt.setObject(5, 1L);\n    stmt.setObject(6, java.sql.Date.valueOf(\"2025-10-30\"));\n    stmt.setObject(7, java.sql.Time.valueOf(\"10:45:00\"));\n    stmt.setObject(8, java.sql.Timestamp.valueOf(\"2025-10-30 10:45:00\"));\n    stmt.setObject(9, new java.math.BigDecimal(\"12345.6789\"));\n\n    stmt.execute();\n\n    PreparedStatement stmt2 = connection.prepareStatement(\"SELECT * FROM test;\");\n    ResultSet rs = stmt2.executeQuery();\n\n    assertTrue(rs.next());\n    assertEquals(42, rs.getInt(1));\n    assertEquals(3.141592d, rs.getDouble(2), 0.000001);\n    assertEquals(\"string_value\", rs.getString(3));\n    assertArrayEquals(new byte[] {1, 2, 3}, rs.getBytes(4));\n    assertTrue(rs.getBoolean(5));\n    assertEquals(java.sql.Date.valueOf(\"2025-10-30\"), rs.getDate(6));\n    assertEquals(java.sql.Time.valueOf(\"10:45:00\"), rs.getTime(7));\n    assertEquals(java.sql.Timestamp.valueOf(\"2025-10-30 10:45:00\"), rs.getTimestamp(8));\n    String decimalText = rs.getString(9);\n    assertEquals(\n        new java.math.BigDecimal(\"12345.6789\").stripTrailingZeros(),\n        new java.math.BigDecimal(decimalText).stripTrailingZeros());\n  }\n\n  @Test\n  void testSetAsciiStream_intLength_insert_and_select() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col TEXT)\").execute();\n\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n\n    String text = \"test\";\n    byte[] bytes = text.getBytes(StandardCharsets.US_ASCII);\n    InputStream stream = new ByteArrayInputStream(bytes);\n\n    stmt.setAsciiStream(1, stream, bytes.length);\n    stmt.execute();\n\n    PreparedStatement stmt2 = connection.prepareStatement(\"SELECT col FROM test\");\n    ResultSet rs = stmt2.executeQuery();\n\n    assertTrue(rs.next());\n    assertEquals(text, rs.getString(1));\n  }\n\n  @Test\n  void testSetAsciiStream_intLength_nullStream() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col TEXT)\").execute();\n\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n\n    stmt.setAsciiStream(1, null, 0);\n    stmt.execute();\n\n    PreparedStatement stmt2 = connection.prepareStatement(\"SELECT col FROM test\");\n    ResultSet rs = stmt2.executeQuery();\n\n    assertTrue(rs.next());\n    assertNull(rs.getString(1));\n  }\n\n  @Test\n  void testSetAsciiStream_intLength_emptyStream() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col TEXT)\").execute();\n\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n    InputStream empty = new ByteArrayInputStream(new byte[0]);\n\n    stmt.setAsciiStream(1, empty, 10);\n    stmt.execute();\n\n    PreparedStatement stmt2 = connection.prepareStatement(\"SELECT col FROM test\");\n    ResultSet rs = stmt2.executeQuery();\n\n    assertTrue(rs.next());\n    assertEquals(\"\", rs.getString(1));\n  }\n\n  @Test\n  void testSetAsciiStream_intLength_negativeLength() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col TEXT)\").execute();\n\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n\n    String text = \"test\";\n    byte[] bytes = text.getBytes(StandardCharsets.US_ASCII);\n    InputStream stream = new ByteArrayInputStream(bytes);\n\n    assertThrows(SQLException.class, () -> stmt.setAsciiStream(1, stream, -1));\n  }\n\n  @Test\n  void testSetBinaryStream_intLength_insert_and_select() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col BLOB)\").execute();\n\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n\n    byte[] data = {1, 2, 3, 4, 5};\n    InputStream stream = new ByteArrayInputStream(data);\n\n    stmt.setBinaryStream(1, stream, data.length);\n    stmt.execute();\n\n    PreparedStatement stmt2 = connection.prepareStatement(\"SELECT col FROM test\");\n    ResultSet rs = stmt2.executeQuery();\n\n    assertTrue(rs.next());\n    assertArrayEquals(data, rs.getBytes(1));\n  }\n\n  @Test\n  void testSetBinaryStream_intLength_nullStream() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col BLOB)\").execute();\n\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n\n    stmt.setBinaryStream(1, null, 0);\n    stmt.execute();\n\n    PreparedStatement stmt2 = connection.prepareStatement(\"SELECT col FROM test\");\n    ResultSet rs = stmt2.executeQuery();\n\n    assertTrue(rs.next());\n    assertNull(rs.getBytes(1));\n  }\n\n  @Test\n  void testSetBinaryStream_intLength_emptyStream() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col BLOB)\").execute();\n\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n\n    InputStream empty = new ByteArrayInputStream(new byte[0]);\n    stmt.setBinaryStream(1, empty, 10);\n    stmt.execute();\n\n    PreparedStatement stmt2 = connection.prepareStatement(\"SELECT col FROM test\");\n    ResultSet rs = stmt2.executeQuery();\n\n    assertTrue(rs.next());\n\n    byte[] result = rs.getBytes(1);\n    assertNotNull(result);\n    assertEquals(0, result.length);\n    assertArrayEquals(new byte[0], result);\n  }\n\n  @Test\n  void testSetBinaryStream_intLength_negativeLength() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col BLOB)\").execute();\n\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n\n    byte[] data = {1, 2, 3};\n    InputStream stream = new ByteArrayInputStream(data);\n\n    assertThrows(SQLException.class, () -> stmt.setBinaryStream(1, stream, -1));\n  }\n\n  @Test\n  void testSetUnicodeStream_intLength_insert_and_select() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col TEXT)\").execute();\n\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n\n    String text = \"안녕하세요😊 Hello🌏\";\n    byte[] bytes = text.getBytes(StandardCharsets.UTF_8);\n    InputStream stream = new ByteArrayInputStream(bytes);\n\n    stmt.setUnicodeStream(1, stream, bytes.length);\n    stmt.execute();\n\n    PreparedStatement stmt2 = connection.prepareStatement(\"SELECT col FROM test\");\n    ResultSet rs = stmt2.executeQuery();\n\n    assertTrue(rs.next());\n    assertEquals(text, rs.getString(1));\n  }\n\n  @Test\n  void testSetUnicodeStream_intLength_nullStream() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col TEXT)\").execute();\n\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n\n    stmt.setUnicodeStream(1, null, 0);\n    stmt.execute();\n\n    PreparedStatement stmt2 = connection.prepareStatement(\"SELECT col FROM test\");\n    ResultSet rs = stmt2.executeQuery();\n\n    assertTrue(rs.next());\n    assertNull(rs.getString(1));\n  }\n\n  @Test\n  void testSetUnicodeStream_intLength_emptyStream() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col TEXT)\").execute();\n\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n    InputStream empty = new ByteArrayInputStream(new byte[0]);\n\n    stmt.setUnicodeStream(1, empty, 10);\n    stmt.execute();\n\n    PreparedStatement stmt2 = connection.prepareStatement(\"SELECT col FROM test\");\n    ResultSet rs = stmt2.executeQuery();\n\n    assertTrue(rs.next());\n    assertEquals(\"\", rs.getString(1));\n  }\n\n  @Test\n  void testSetUnicodeStream_intLength_negativeLength() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col TEXT)\").execute();\n\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n\n    String text = \"테스트\";\n    byte[] bytes = text.getBytes(StandardCharsets.UTF_8);\n    InputStream stream = new ByteArrayInputStream(bytes);\n\n    assertThrows(SQLException.class, () -> stmt.setUnicodeStream(1, stream, -5));\n  }\n\n  @Test\n  void testSetAsciiStream_longLength_insert_and_select() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col TEXT)\").execute();\n\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n\n    String text = \"test\";\n    byte[] bytes = text.getBytes(StandardCharsets.US_ASCII);\n    InputStream stream = new ByteArrayInputStream(bytes);\n\n    stmt.setAsciiStream(1, stream, (long) bytes.length);\n    stmt.execute();\n\n    PreparedStatement stmt2 = connection.prepareStatement(\"SELECT col FROM test\");\n    ResultSet rs = stmt2.executeQuery();\n\n    assertTrue(rs.next());\n    assertEquals(text, rs.getString(1));\n  }\n\n  @Test\n  void testSetAsciiStream_longLength_nullStream() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col TEXT)\").execute();\n\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n\n    stmt.setAsciiStream(1, null, 0L);\n    stmt.execute();\n\n    PreparedStatement stmt2 = connection.prepareStatement(\"SELECT col FROM test\");\n    ResultSet rs = stmt2.executeQuery();\n\n    assertTrue(rs.next());\n    assertNull(rs.getString(1));\n  }\n\n  @Test\n  void testSetAsciiStream_longLength_emptyStream() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col TEXT)\").execute();\n\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n    InputStream empty = new ByteArrayInputStream(new byte[0]);\n\n    stmt.setAsciiStream(1, empty, 0L);\n    stmt.execute();\n\n    PreparedStatement stmt2 = connection.prepareStatement(\"SELECT col FROM test\");\n    ResultSet rs = stmt2.executeQuery();\n\n    assertTrue(rs.next());\n    assertEquals(\"\", rs.getString(1));\n  }\n\n  @Test\n  void testSetAsciiStream_longLength_negative() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col TEXT)\").execute();\n\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n    InputStream stream = new ByteArrayInputStream(\"test\".getBytes(StandardCharsets.US_ASCII));\n\n    assertThrows(SQLFeatureNotSupportedException.class, () -> stmt.setAsciiStream(1, stream, -1L));\n  }\n\n  @Test\n  void testSetAsciiStream_longLength_overflow() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col TEXT)\").execute();\n\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n    InputStream stream = new ByteArrayInputStream(\"test\".getBytes(StandardCharsets.US_ASCII));\n\n    assertThrows(\n        SQLFeatureNotSupportedException.class,\n        () -> stmt.setAsciiStream(1, stream, (long) Integer.MAX_VALUE + 1));\n  }\n\n  @Test\n  void testSetBinaryStream_longLength_insert_and_select() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col BLOB)\").execute();\n\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n\n    byte[] data = {1, 2, 3, 4, 5};\n    InputStream stream = new ByteArrayInputStream(data);\n\n    stmt.setBinaryStream(1, stream, (long) data.length);\n    stmt.execute();\n\n    PreparedStatement stmt2 = connection.prepareStatement(\"SELECT col FROM test\");\n    ResultSet rs = stmt2.executeQuery();\n\n    assertTrue(rs.next());\n    assertArrayEquals(data, rs.getBytes(1));\n  }\n\n  @Test\n  void testSetBinaryStream_longLength_nullStream() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col BLOB)\").execute();\n\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n\n    stmt.setBinaryStream(1, null, 0L);\n    stmt.execute();\n\n    PreparedStatement stmt2 = connection.prepareStatement(\"SELECT col FROM test\");\n    ResultSet rs = stmt2.executeQuery();\n\n    assertTrue(rs.next());\n    assertNull(rs.getBytes(1));\n  }\n\n  @Test\n  void testSetBinaryStream_longLength_emptyStream() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col BLOB)\").execute();\n\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n    InputStream empty = new ByteArrayInputStream(new byte[0]);\n\n    stmt.setBinaryStream(1, empty, 0L);\n    stmt.execute();\n\n    PreparedStatement stmt2 = connection.prepareStatement(\"SELECT col FROM test\");\n    ResultSet rs = stmt2.executeQuery();\n\n    assertTrue(rs.next());\n\n    byte[] result = rs.getBytes(1);\n    assertNotNull(result);\n    assertEquals(0, result.length);\n    assertArrayEquals(new byte[0], result);\n  }\n\n  @Test\n  void testSetBinaryStream_longLength_negative() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col BLOB)\").execute();\n\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n    InputStream stream = new ByteArrayInputStream(new byte[] {1, 2, 3});\n\n    assertThrows(SQLFeatureNotSupportedException.class, () -> stmt.setBinaryStream(1, stream, -1L));\n  }\n\n  @Test\n  void testSetBinaryStream_longLength_overflow() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col BLOB)\").execute();\n\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n    InputStream stream = new ByteArrayInputStream(new byte[] {1, 2, 3});\n\n    assertThrows(\n        SQLFeatureNotSupportedException.class,\n        () -> stmt.setBinaryStream(1, stream, (long) Integer.MAX_VALUE + 1));\n  }\n\n  @Test\n  void testSetAsciiStream_noLength_insert_and_select() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col TEXT)\").execute();\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n\n    String text = \"test\";\n    byte[] bytes = text.getBytes(StandardCharsets.US_ASCII);\n    InputStream stream = new ByteArrayInputStream(bytes);\n\n    stmt.setAsciiStream(1, stream);\n    stmt.execute();\n\n    PreparedStatement stmt2 = connection.prepareStatement(\"SELECT col FROM test\");\n    ResultSet rs = stmt2.executeQuery();\n\n    assertTrue(rs.next());\n    assertEquals(text, rs.getString(1));\n  }\n\n  @Test\n  void testSetAsciiStream_noLength_nullStream() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col TEXT)\").execute();\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n\n    stmt.setAsciiStream(1, null);\n    stmt.execute();\n\n    ResultSet rs = connection.prepareStatement(\"SELECT col FROM test\").executeQuery();\n    assertTrue(rs.next());\n    assertNull(rs.getString(1));\n  }\n\n  @Test\n  void testSetAsciiStream_noLength_emptyStream() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col TEXT)\").execute();\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n\n    InputStream empty = new ByteArrayInputStream(new byte[0]);\n    stmt.setAsciiStream(1, empty);\n    stmt.execute();\n\n    ResultSet rs = connection.prepareStatement(\"SELECT col FROM test\").executeQuery();\n    assertTrue(rs.next());\n    assertEquals(\"\", rs.getString(1));\n  }\n\n  @Test\n  void testSetBinaryStream_noLength_insert_and_select() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col BLOB)\").execute();\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n\n    byte[] data = {1, 2, 3, 4, 5};\n    InputStream stream = new ByteArrayInputStream(data);\n\n    stmt.setBinaryStream(1, stream);\n    stmt.execute();\n\n    PreparedStatement stmt2 = connection.prepareStatement(\"SELECT col FROM test\");\n    ResultSet rs = stmt2.executeQuery();\n\n    assertTrue(rs.next());\n    assertArrayEquals(data, rs.getBytes(1));\n  }\n\n  @Test\n  void testSetBinaryStream_noLength_nullStream() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col BLOB)\").execute();\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n\n    stmt.setBinaryStream(1, null);\n    stmt.execute();\n\n    ResultSet rs = connection.prepareStatement(\"SELECT col FROM test\").executeQuery();\n    assertTrue(rs.next());\n    assertNull(rs.getBytes(1));\n  }\n\n  @Test\n  void testSetBinaryStream_noLength_emptyStream() throws SQLException {\n    connection.prepareStatement(\"CREATE TABLE test (col BLOB)\").execute();\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n\n    InputStream empty = new ByteArrayInputStream(new byte[0]);\n    stmt.setBinaryStream(1, empty);\n    stmt.execute();\n\n    ResultSet rs = connection.prepareStatement(\"SELECT col FROM test\").executeQuery();\n    assertTrue(rs.next());\n\n    byte[] result = rs.getBytes(1);\n    assertNotNull(result);\n    assertEquals(0, result.length);\n  }\n\n  @Test\n  void testSetCharacterStream_intLength_insert_and_select() throws Exception {\n    connection.prepareStatement(\"CREATE TABLE test (col TEXT)\").execute();\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n\n    String text = \"test\";\n    Reader reader = new StringReader(text);\n\n    stmt.setCharacterStream(1, reader, 4);\n    stmt.execute();\n\n    ResultSet rs = connection.prepareStatement(\"SELECT col FROM test\").executeQuery();\n\n    assertTrue(rs.next());\n    assertEquals(\"test\", rs.getString(1));\n  }\n\n  @Test\n  void testSetCharacterStream_intLength_shorterThanLength() throws Exception {\n    connection.prepareStatement(\"CREATE TABLE test (col TEXT)\").execute();\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n\n    Reader reader = new StringReader(\"test\");\n    stmt.setCharacterStream(1, reader, 5);\n\n    stmt.execute();\n    ResultSet rs = connection.prepareStatement(\"SELECT col FROM test\").executeQuery();\n\n    assertTrue(rs.next());\n    assertEquals(\"test\", rs.getString(1));\n  }\n\n  @Test\n  void testSetCharacterStream_intLength_zero() throws Exception {\n    connection.prepareStatement(\"CREATE TABLE test (col TEXT)\").execute();\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n\n    Reader reader = new StringReader(\"test\");\n    stmt.setCharacterStream(1, reader, 0);\n\n    stmt.execute();\n    ResultSet rs = connection.prepareStatement(\"SELECT col FROM test\").executeQuery();\n\n    assertTrue(rs.next());\n    assertEquals(\"\", rs.getString(1));\n  }\n\n  @Test\n  void testSetCharacterStream_intLength_nullReader() throws Exception {\n    connection.prepareStatement(\"CREATE TABLE test (col TEXT)\").execute();\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n\n    stmt.setCharacterStream(1, null, 10);\n\n    stmt.execute();\n    ResultSet rs = connection.prepareStatement(\"SELECT col FROM test\").executeQuery();\n\n    assertTrue(rs.next());\n    assertNull(rs.getString(1));\n  }\n\n  @Test\n  void testSetCharacterStream_intLength_negativeLength() throws Exception {\n    connection.prepareStatement(\"CREATE TABLE test (col TEXT)\").execute();\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n\n    Reader reader = new StringReader(\"text\");\n\n    assertThrows(SQLException.class, () -> stmt.setCharacterStream(1, reader, -1));\n  }\n\n  @Test\n  void testSetCharacterStream_noLength_insert_and_select() throws Exception {\n    connection.prepareStatement(\"CREATE TABLE test (col TEXT)\").execute();\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n\n    String text = \"test\";\n    Reader reader = new StringReader(text);\n\n    stmt.setCharacterStream(1, reader);\n    stmt.execute();\n\n    PreparedStatement stmt2 = connection.prepareStatement(\"SELECT col FROM test\");\n    ResultSet rs = stmt2.executeQuery();\n\n    assertTrue(rs.next());\n    assertEquals(text, rs.getString(1));\n  }\n\n  @Test\n  void testSetCharacterStream_noLength_nullReader() throws Exception {\n    connection.prepareStatement(\"CREATE TABLE test (col TEXT)\").execute();\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n\n    stmt.setCharacterStream(1, null);\n    stmt.execute();\n\n    ResultSet rs = connection.prepareStatement(\"SELECT col FROM test\").executeQuery();\n\n    assertTrue(rs.next());\n    assertNull(rs.getString(1));\n  }\n\n  @Test\n  void testSetCharacterStream_noLength_emptyReader() throws Exception {\n    connection.prepareStatement(\"CREATE TABLE test (col TEXT)\").execute();\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n\n    Reader empty = new StringReader(\"\");\n    stmt.setCharacterStream(1, empty);\n    stmt.execute();\n\n    ResultSet rs = connection.prepareStatement(\"SELECT col FROM test\").executeQuery();\n\n    assertTrue(rs.next());\n    assertEquals(\"\", rs.getString(1));\n  }\n\n  @Test\n  void testSetCharacterStream_longLength_insert_and_select() throws Exception {\n    connection.prepareStatement(\"CREATE TABLE test (col TEXT)\").execute();\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n\n    String text = \"test\";\n    Reader reader = new StringReader(text);\n\n    stmt.setCharacterStream(1, reader, (long) text.length());\n    stmt.execute();\n\n    PreparedStatement stmt2 = connection.prepareStatement(\"SELECT col FROM test\");\n    ResultSet rs = stmt2.executeQuery();\n\n    assertTrue(rs.next());\n    assertEquals(text, rs.getString(1));\n  }\n\n  @Test\n  void testSetCharacterStream_longLength_nullReader() throws Exception {\n    connection.prepareStatement(\"CREATE TABLE test (col TEXT)\").execute();\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n\n    stmt.setCharacterStream(1, null, 0L);\n    stmt.execute();\n\n    PreparedStatement stmt2 = connection.prepareStatement(\"SELECT col FROM test\");\n    ResultSet rs = stmt2.executeQuery();\n\n    assertTrue(rs.next());\n    assertNull(rs.getString(1));\n  }\n\n  @Test\n  void testSetCharacterStream_longLength_emptyReader() throws Exception {\n    connection.prepareStatement(\"CREATE TABLE test (col TEXT)\").execute();\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n\n    Reader empty = new StringReader(\"\");\n\n    stmt.setCharacterStream(1, empty, 0L);\n    stmt.execute();\n\n    PreparedStatement stmt2 = connection.prepareStatement(\"SELECT col FROM test\");\n    ResultSet rs = stmt2.executeQuery();\n\n    assertTrue(rs.next());\n    assertEquals(\"\", rs.getString(1));\n  }\n\n  @Test\n  void testSetCharacterStream_longLength_shorterThanLength() throws Exception {\n    connection.prepareStatement(\"CREATE TABLE test (col TEXT)\").execute();\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n\n    Reader reader = new StringReader(\"test\");\n\n    stmt.setCharacterStream(1, reader, 10L);\n    stmt.execute();\n\n    PreparedStatement stmt2 = connection.prepareStatement(\"SELECT col FROM test\");\n    ResultSet rs = stmt2.executeQuery();\n\n    assertTrue(rs.next());\n    assertEquals(\"test\", rs.getString(1));\n  }\n\n  @Test\n  void testSetCharacterStream_longLength_zero() throws Exception {\n    connection.prepareStatement(\"CREATE TABLE test (col TEXT)\").execute();\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n\n    Reader reader = new StringReader(\"test\");\n\n    stmt.setCharacterStream(1, reader, 0L);\n    stmt.execute();\n\n    PreparedStatement stmt2 = connection.prepareStatement(\"SELECT col FROM test\");\n    ResultSet rs = stmt2.executeQuery();\n\n    assertTrue(rs.next());\n    assertEquals(\"\", rs.getString(1));\n  }\n\n  @Test\n  void testSetCharacterStream_longLength_negative() throws Exception {\n    connection.prepareStatement(\"CREATE TABLE test (col TEXT)\").execute();\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n\n    Reader reader = new StringReader(\"test\");\n\n    assertThrows(\n        SQLFeatureNotSupportedException.class, () -> stmt.setCharacterStream(1, reader, -1L));\n  }\n\n  @Test\n  void testSetCharacterStream_longLength_overflow() throws Exception {\n    connection.prepareStatement(\"CREATE TABLE test (col TEXT)\").execute();\n    PreparedStatement stmt = connection.prepareStatement(\"INSERT INTO test (col) VALUES (?)\");\n\n    Reader reader = new StringReader(\"test\");\n\n    assertThrows(\n        SQLFeatureNotSupportedException.class,\n        () -> stmt.setCharacterStream(1, reader, (long) Integer.MAX_VALUE + 1));\n  }\n\n  @Test\n  void execute_insert_should_return_number_of_inserted_elements() throws Exception {\n    connection.prepareStatement(\"CREATE TABLE test (col INTEGER)\").execute();\n    PreparedStatement prepareStatement =\n        connection.prepareStatement(\"INSERT INTO test (col) VALUES (?), (?), (?)\");\n    prepareStatement.setInt(1, 1);\n    prepareStatement.setInt(2, 2);\n    prepareStatement.setInt(3, 3);\n    assertEquals(prepareStatement.executeUpdate(), 3);\n  }\n\n  @Test\n  void execute_update_should_return_number_of_updated_elements() throws Exception {\n    connection.prepareStatement(\"CREATE TABLE test (col INTEGER)\").execute();\n    connection.prepareStatement(\"INSERT INTO test (col) VALUES (1), (2), (3)\").execute();\n    PreparedStatement preparedStatement =\n        connection.prepareStatement(\"UPDATE test SET col = ? where col = 1 \");\n    preparedStatement.setInt(1, 4);\n    assertEquals(preparedStatement.executeUpdate(), 1);\n  }\n\n  @Test\n  void execute_delete_should_return_number_of_deleted_elements() throws Exception {\n    connection.prepareStatement(\"CREATE TABLE test (col INTEGER)\").execute();\n    connection.prepareStatement(\"INSERT INTO test (col) VALUES (1), (2), (3)\").execute();\n    PreparedStatement preparedStatement =\n        connection.prepareStatement(\"DELETE  FROM test   where col = ? \");\n    preparedStatement.setInt(1, 1);\n    assertEquals(preparedStatement.executeUpdate(), 1);\n  }\n\n  @Test\n  void testBatchInsert() throws Exception {\n    connection.prepareStatement(\"CREATE TABLE test (col1 INTEGER, col2 INTEGER)\").execute();\n    PreparedStatement preparedStatement =\n        connection.prepareStatement(\"INSERT INTO test (col1, col2) VALUES (?, ?)\");\n\n    preparedStatement.setInt(1, 1);\n    preparedStatement.setInt(2, 2);\n    preparedStatement.addBatch();\n    preparedStatement.setInt(1, 3);\n    preparedStatement.setInt(2, 4);\n    preparedStatement.addBatch();\n\n    assertArrayEquals(new int[] {1, 1}, preparedStatement.executeBatch());\n\n    ResultSet rs = connection.prepareStatement(\"SELECT * FROM test\").executeQuery();\n    assertTrue(rs.next());\n    assertEquals(1, rs.getInt(1));\n    assertEquals(2, rs.getInt(2));\n    assertTrue(rs.next());\n    assertEquals(3, rs.getInt(1));\n    assertEquals(4, rs.getInt(2));\n    assertFalse(rs.next());\n  }\n\n  @Test\n  void testBatchUpdate() throws Exception {\n    connection.prepareStatement(\"CREATE TABLE test (col1 INTEGER, col2 INTEGER)\").execute();\n    connection.prepareStatement(\"INSERT INTO test (col1, col2) VALUES (1, 1), (2, 2)\").execute();\n\n    PreparedStatement preparedStatement =\n        connection.prepareStatement(\"UPDATE test SET col2=? WHERE col1=?\");\n\n    preparedStatement.setInt(1, 5);\n    preparedStatement.setInt(2, 1);\n    preparedStatement.addBatch();\n    preparedStatement.setInt(1, 6);\n    preparedStatement.setInt(2, 2);\n    preparedStatement.addBatch();\n    preparedStatement.setInt(1, 7);\n    preparedStatement.setInt(2, 3);\n    preparedStatement.addBatch();\n\n    assertArrayEquals(new int[] {1, 1, 0}, preparedStatement.executeBatch());\n\n    ResultSet rs = connection.prepareStatement(\"SELECT * FROM test\").executeQuery();\n    assertTrue(rs.next());\n    assertEquals(1, rs.getInt(1));\n    assertEquals(5, rs.getInt(2));\n    assertTrue(rs.next());\n    assertEquals(2, rs.getInt(1));\n    assertEquals(6, rs.getInt(2));\n    assertFalse(rs.next());\n  }\n\n  @Test\n  void testBatchDelete() throws Exception {\n    connection.prepareStatement(\"CREATE TABLE test (col1 INTEGER, col2 INTEGER)\").execute();\n    connection.prepareStatement(\"INSERT INTO test (col1, col2) VALUES (1, 1), (2, 2)\").execute();\n\n    PreparedStatement preparedStatement =\n        connection.prepareStatement(\"DELETE FROM test WHERE col1=?\");\n\n    preparedStatement.setInt(1, 1);\n    preparedStatement.addBatch();\n    preparedStatement.setInt(1, 4);\n    preparedStatement.addBatch();\n\n    assertArrayEquals(new int[] {1, 0}, preparedStatement.executeBatch());\n\n    ResultSet rs = connection.prepareStatement(\"SELECT * FROM test\").executeQuery();\n    assertTrue(rs.next());\n    assertEquals(2, rs.getInt(1));\n    assertEquals(2, rs.getInt(2));\n    assertFalse(rs.next());\n  }\n\n  @Test\n  void testBatch_implicitAddBatch_shouldIgnore() throws Exception {\n    connection.prepareStatement(\"CREATE TABLE test (col1 INTEGER, col2 INTEGER)\").execute();\n    PreparedStatement preparedStatement =\n        connection.prepareStatement(\"INSERT INTO test (col1, col2) VALUES (?, ?)\");\n\n    preparedStatement.setInt(1, 1);\n    preparedStatement.setInt(2, 2);\n    preparedStatement.addBatch();\n    // we set parameters but don't call addBatch afterward\n    // we should only get a result for the first insert statement to match sqlite-jdbc behavior\n    preparedStatement.setInt(1, 3);\n    preparedStatement.setInt(2, 4);\n\n    assertArrayEquals(new int[] {1}, preparedStatement.executeBatch());\n\n    ResultSet rs = connection.prepareStatement(\"SELECT * FROM test\").executeQuery();\n    assertTrue(rs.next());\n    assertEquals(1, rs.getInt(1));\n    assertEquals(2, rs.getInt(2));\n    assertFalse(rs.next());\n  }\n\n  @Test\n  void testBatch_select_shouldFail() throws Exception {\n    connection.prepareStatement(\"CREATE TABLE test (col1 INTEGER, col2 INTEGER)\").execute();\n    PreparedStatement preparedStatement =\n        connection.prepareStatement(\"SELECT * FROM test WHERE col1=?\");\n    preparedStatement.setInt(1, 1);\n    preparedStatement.addBatch();\n    assertThrows(BatchUpdateException.class, preparedStatement::executeBatch);\n  }\n}\n"
  },
  {
    "path": "bindings/java/src/test/java/tech/turso/jdbc4/JDBC4ResultSetTest.java",
    "content": "package tech.turso.jdbc4;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\nimport java.io.InputStream;\nimport java.io.Reader;\nimport java.math.BigDecimal;\nimport java.math.RoundingMode;\nimport java.nio.ByteBuffer;\nimport java.sql.*;\nimport java.util.Calendar;\nimport java.util.Date;\nimport java.util.Properties;\nimport java.util.stream.Stream;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport tech.turso.TestUtils;\n\nclass JDBC4ResultSetTest {\n\n  private Statement stmt;\n\n  @BeforeEach\n  void setUp() throws Exception {\n    String filePath = TestUtils.createTempFile();\n    String url = \"jdbc:turso:\" + filePath;\n    final JDBC4Connection connection = new JDBC4Connection(url, filePath, new Properties());\n    stmt =\n        connection.createStatement(\n            ResultSet.TYPE_FORWARD_ONLY,\n            ResultSet.CONCUR_READ_ONLY,\n            ResultSet.CLOSE_CURSORS_AT_COMMIT);\n  }\n\n  @Test\n  void invoking_next_before_the_last_row_should_return_true() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT);\");\n    stmt.executeUpdate(\"INSERT INTO users VALUES (1, 'sinwoo');\");\n    stmt.executeUpdate(\"INSERT INTO users VALUES (2, 'seonwoo');\");\n\n    // first call to next occur internally\n    stmt.executeQuery(\"SELECT * FROM users\");\n    ResultSet resultSet = stmt.getResultSet();\n\n    assertTrue(resultSet.next());\n  }\n\n  @Test\n  void invoking_next_after_the_last_row_should_return_false() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT);\");\n    stmt.executeUpdate(\"INSERT INTO users VALUES (1, 'sinwoo');\");\n    stmt.executeUpdate(\"INSERT INTO users VALUES (2, 'seonwoo');\");\n\n    // first call to next occur internally\n    stmt.executeQuery(\"SELECT * FROM users\");\n    ResultSet resultSet = stmt.getResultSet();\n\n    while (resultSet.next()) {\n      // run until next() returns false\n    }\n\n    // if the previous call to next() returned false, consecutive call to next() should return false\n    // as well\n    assertFalse(resultSet.next());\n  }\n\n  @Test\n  void close_resultSet_test() throws Exception {\n    stmt.executeQuery(\"SELECT 1;\");\n    ResultSet resultSet = stmt.getResultSet();\n\n    assertFalse(resultSet.isClosed());\n    resultSet.close();\n    assertTrue(resultSet.isClosed());\n  }\n\n  @Test\n  void calling_methods_on_closed_resultSet_should_throw_exception() throws Exception {\n    stmt.executeQuery(\"SELECT 1;\");\n    ResultSet resultSet = stmt.getResultSet();\n    resultSet.close();\n    assertTrue(resultSet.isClosed());\n\n    assertThrows(SQLException.class, resultSet::next);\n  }\n\n  @Test\n  void test_getString() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_string (string_col TEXT);\");\n    stmt.executeUpdate(\"INSERT INTO test_string (string_col) VALUES ('test');\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_string\");\n    assertTrue(resultSet.next());\n    assertEquals(\"test\", resultSet.getString(1));\n  }\n\n  @Test\n  void test_getString_returns_null_on_null() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_null (string_col TEXT);\");\n    stmt.executeUpdate(\"INSERT INTO test_null (string_col) VALUES (NULL);\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_null\");\n    assertTrue(resultSet.next());\n    assertNull(resultSet.getString(1));\n  }\n\n  @Test\n  void test_getBoolean_true() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_boolean (boolean_col INTEGER);\");\n    stmt.executeUpdate(\"INSERT INTO test_boolean (boolean_col) VALUES (1);\");\n    stmt.executeUpdate(\"INSERT INTO test_boolean (boolean_col) VALUES (2);\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_boolean\");\n\n    assertTrue(resultSet.next());\n    assertTrue(resultSet.getBoolean(1));\n\n    resultSet.next();\n    assertTrue(resultSet.getBoolean(1));\n  }\n\n  @Test\n  void test_getBoolean_false() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_boolean (boolean_col INTEGER);\");\n    stmt.executeUpdate(\"INSERT INTO test_boolean (boolean_col) VALUES (0);\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_boolean\");\n    assertTrue(resultSet.next());\n    assertFalse(resultSet.getBoolean(1));\n  }\n\n  @Test\n  void test_getBoolean_returns_false_on_null() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_null (boolean_col INTEGER);\");\n    stmt.executeUpdate(\"INSERT INTO test_null (boolean_col) VALUES (NULL);\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_null\");\n    assertTrue(resultSet.next());\n    assertFalse(resultSet.getBoolean(1));\n  }\n\n  @Test\n  void test_getByte() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_byte (byte_col INTEGER);\");\n    stmt.executeUpdate(\"INSERT INTO test_byte (byte_col) VALUES (1);\");\n    stmt.executeUpdate(\"INSERT INTO test_byte (byte_col) VALUES (128);\"); // Exceeds byte size\n    stmt.executeUpdate(\"INSERT INTO test_byte (byte_col) VALUES (-129);\"); // Exceeds byte size\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_byte\");\n\n    // Test value that fits within byte size\n    assertTrue(resultSet.next());\n    assertEquals(1, resultSet.getByte(1));\n\n    // Test value that exceeds byte size (positive overflow)\n    assertTrue(resultSet.next());\n    assertEquals(-128, resultSet.getByte(1)); // 128 overflows to -128\n\n    // Test value that exceeds byte size (negative overflow)\n    assertTrue(resultSet.next());\n    assertEquals(127, resultSet.getByte(1)); // -129 overflows to 127\n  }\n\n  @Test\n  void test_getByte_returns_zero_on_null() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_null (byte_col INTEGER);\");\n    stmt.executeUpdate(\"INSERT INTO test_null (byte_col) VALUES (NULL);\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_null\");\n    assertTrue(resultSet.next());\n    assertEquals(0, resultSet.getByte(1));\n  }\n\n  @Test\n  void test_getShort() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_short (short_col INTEGER);\");\n    stmt.executeUpdate(\"INSERT INTO test_short (short_col) VALUES (123);\");\n    stmt.executeUpdate(\"INSERT INTO test_short (short_col) VALUES (32767);\"); // Max short value\n    stmt.executeUpdate(\"INSERT INTO test_short (short_col) VALUES (-32768);\"); // Min short value\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_short\");\n\n    // Test typical short value\n    assertTrue(resultSet.next());\n    assertEquals(123, resultSet.getShort(1));\n\n    // Test maximum short value\n    assertTrue(resultSet.next());\n    assertEquals(32767, resultSet.getShort(1));\n\n    // Test minimum short value\n    assertTrue(resultSet.next());\n    assertEquals(-32768, resultSet.getShort(1));\n  }\n\n  @Test\n  void test_getShort_returns_zero_on_null() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_null (short_col INTEGER);\");\n    stmt.executeUpdate(\"INSERT INTO test_null (short_col) VALUES (NULL);\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_null\");\n    assertTrue(resultSet.next());\n    assertEquals(0, resultSet.getShort(1));\n  }\n\n  @Test\n  void test_getInt() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_int (int_col INT);\");\n    stmt.executeUpdate(\"INSERT INTO test_int (int_col) VALUES (12345);\");\n    stmt.executeUpdate(\"INSERT INTO test_int (int_col) VALUES (2147483647);\"); // Max int value\n    stmt.executeUpdate(\"INSERT INTO test_int (int_col) VALUES (-2147483648);\"); // Min int value\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_int\");\n\n    // Test typical int value\n    assertTrue(resultSet.next());\n    assertEquals(12345, resultSet.getInt(1));\n\n    // Test maximum int value\n    assertTrue(resultSet.next());\n    assertEquals(2147483647, resultSet.getInt(1));\n\n    // Test minimum int value\n    assertTrue(resultSet.next());\n    assertEquals(-2147483648, resultSet.getInt(1));\n  }\n\n  @Test\n  void test_getInt_returns_zero_on_null() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_null (int_col INTEGER);\");\n    stmt.executeUpdate(\"INSERT INTO test_null (int_col) VALUES (NULL);\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_null\");\n    assertTrue(resultSet.next());\n    assertEquals(0, resultSet.getInt(1));\n  }\n\n  @Test\n  void test_getLong() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_long (long_col BIGINT);\");\n    stmt.executeUpdate(\"INSERT INTO test_long (long_col) VALUES (1234567890);\");\n    stmt.executeUpdate(\n        \"INSERT INTO test_long (long_col) VALUES (9223372036854775807);\"); // Max long value\n    stmt.executeUpdate(\n        \"INSERT INTO test_long (long_col) VALUES (-9223372036854775808);\"); // Min long value\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_long\");\n\n    // Test typical long value\n    assertTrue(resultSet.next());\n    assertEquals(1234567890L, resultSet.getLong(1));\n\n    // Test maximum long value\n    assertTrue(resultSet.next());\n    assertEquals(9223372036854775807L, resultSet.getLong(1));\n\n    // Test minimum long value\n    assertTrue(resultSet.next());\n    assertEquals(-9223372036854775808L, resultSet.getLong(1));\n  }\n\n  @Test\n  void test_getLong_returns_zero_no_null() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_null (long_col INTEGER);\");\n    stmt.executeUpdate(\"INSERT INTO test_null (long_col) VALUES (NULL);\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_null\");\n    assertTrue(resultSet.next());\n    assertEquals(0L, resultSet.getLong(1));\n  }\n\n  @Test\n  void test_getFloat() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_float (float_col REAL);\");\n    stmt.executeUpdate(\"INSERT INTO test_float (float_col) VALUES (1.23);\");\n    stmt.executeUpdate(\n        \"INSERT INTO test_float (float_col) VALUES (3.4028235E38);\"); // Max float value\n    stmt.executeUpdate(\n        \"INSERT INTO test_float (float_col) VALUES (1.4E-45);\"); // Min positive float value\n    stmt.executeUpdate(\n        \"INSERT INTO test_float (float_col) VALUES (-3.4028235E38);\"); // Min negative float value\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_float\");\n\n    // Test typical float value\n    assertTrue(resultSet.next());\n    assertEquals(1.23f, resultSet.getFloat(1), 0.0001);\n\n    // Test maximum float value\n    assertTrue(resultSet.next());\n    assertEquals(3.4028235E38f, resultSet.getFloat(1), 0.0001);\n\n    // Test minimum positive float value\n    assertTrue(resultSet.next());\n    assertEquals(1.4E-45f, resultSet.getFloat(1), 0.0001);\n\n    // Test minimum negative float value\n    assertTrue(resultSet.next());\n    assertEquals(-3.4028235E38f, resultSet.getFloat(1), 0.0001);\n  }\n\n  @Test\n  void test_getFloat_returns_zero_on_null() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_null (float_col REAL);\");\n    stmt.executeUpdate(\"INSERT INTO test_null (float_col) VALUES (NULL);\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_null\");\n    assertTrue(resultSet.next());\n    assertEquals(0.0f, resultSet.getFloat(1), 0.0001);\n  }\n\n  @Test\n  void test_getDouble() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_double (double_col REAL);\");\n    stmt.executeUpdate(\"INSERT INTO test_double (double_col) VALUES (1.234567);\");\n    stmt.executeUpdate(\n        \"INSERT INTO test_double (double_col) VALUES (1.7976931348623157E308);\"); // Max double\n    // value\n    stmt.executeUpdate(\n        \"INSERT INTO test_double (double_col) VALUES (4.9E-324);\"); // Min positive double value\n    stmt.executeUpdate(\n        \"INSERT INTO test_double (double_col) VALUES (-1.7976931348623157E308);\"); // Min negative\n    // double value\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_double\");\n\n    // Test typical double value\n    assertTrue(resultSet.next());\n    assertEquals(1.234567, resultSet.getDouble(1), 0.0001);\n\n    // Test maximum double value\n    assertTrue(resultSet.next());\n    assertEquals(1.7976931348623157E308, resultSet.getDouble(1), 0.0001);\n\n    // Test minimum positive double value\n    assertTrue(resultSet.next());\n    assertEquals(4.9E-324, resultSet.getDouble(1), 0.0001);\n\n    // Test minimum negative double value\n    assertTrue(resultSet.next());\n    assertEquals(-1.7976931348623157E308, resultSet.getDouble(1), 0.0001);\n  }\n\n  @Test\n  void test_getDouble_returns_zero_on_null() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_null (double_col REAL);\");\n    stmt.executeUpdate(\"INSERT INTO test_null (double_col) VALUES (NULL);\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_null\");\n    assertTrue(resultSet.next());\n    assertEquals(0.0, resultSet.getDouble(1), 0.0001);\n  }\n\n  @Test\n  void test_getBigDecimal() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_bigdecimal (bigdecimal_col REAL);\");\n    stmt.executeUpdate(\"INSERT INTO test_bigdecimal (bigdecimal_col) VALUES (12345.67);\");\n    stmt.executeUpdate(\n        \"INSERT INTO test_bigdecimal (bigdecimal_col) VALUES (1.7976931348623157E308);\"); // Max\n    // double\n    // value\n    stmt.executeUpdate(\n        \"INSERT INTO test_bigdecimal (bigdecimal_col) VALUES (4.9E-324);\"); // Min positive double\n    // value\n    stmt.executeUpdate(\n        \"INSERT INTO test_bigdecimal (bigdecimal_col) VALUES (-12345.67);\"); // Negative value\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_bigdecimal\");\n\n    // Test typical BigDecimal value\n    assertTrue(resultSet.next());\n    assertEquals(\n        new BigDecimal(\"12345.67\").setScale(2, RoundingMode.HALF_UP),\n        resultSet.getBigDecimal(1, 2));\n\n    // Test maximum double value\n    assertTrue(resultSet.next());\n    assertEquals(\n        new BigDecimal(\"1.7976931348623157E308\").setScale(10, RoundingMode.HALF_UP),\n        resultSet.getBigDecimal(1, 10));\n\n    // Test minimum positive double value\n    assertTrue(resultSet.next());\n    assertEquals(\n        new BigDecimal(\"4.9E-324\").setScale(10, RoundingMode.HALF_UP),\n        resultSet.getBigDecimal(1, 10));\n\n    // Test negative BigDecimal value\n    assertTrue(resultSet.next());\n    assertEquals(\n        new BigDecimal(\"-12345.67\").setScale(2, RoundingMode.HALF_UP),\n        resultSet.getBigDecimal(1, 2));\n  }\n\n  @Test\n  void test_getBigDecimal_returns_null_on_null() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_null (bigdecimal_col REAL);\");\n    stmt.executeUpdate(\"INSERT INTO test_null (bigdecimal_col) VALUES (NULL);\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_null\");\n    assertTrue(resultSet.next());\n    assertNull(resultSet.getBigDecimal(1, 2));\n  }\n\n  @ParameterizedTest\n  @MethodSource(\"byteArrayProvider\")\n  void test_getBytes(byte[] data) throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_bytes (bytes_col BLOB);\");\n    executeDMLAndAssert(data);\n  }\n\n  private static Stream<byte[]> byteArrayProvider() {\n    return Stream.of(\n        \"Hello\".getBytes(), \"world\".getBytes(), new byte[0], new byte[] {0x00, (byte) 0xFF});\n  }\n\n  private void executeDMLAndAssert(byte[] data) throws SQLException {\n    // Convert byte array to hexadecimal string\n    StringBuilder hexString = new StringBuilder();\n    for (byte b : data) {\n      hexString.append(String.format(\"%02X\", b));\n    }\n    // Execute DML statement\n    stmt.executeUpdate(\"INSERT INTO test_bytes (bytes_col) VALUES (X'\" + hexString + \"');\");\n\n    // Assert the inserted data\n    ResultSet resultSet = stmt.executeQuery(\"SELECT bytes_col FROM test_bytes\");\n    assertTrue(resultSet.next());\n    assertArrayEquals(data, resultSet.getBytes(1));\n  }\n\n  @Test\n  void test_getBytes_returns_null_on_null() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_null (bytes_col BLOB);\");\n    stmt.executeUpdate(\"INSERT INTO test_null (bytes_col) VALUES (NULL);\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_null\");\n    assertTrue(resultSet.next());\n    assertNull(resultSet.getBytes(1));\n  }\n\n  @Test\n  void test_getXXX_methods_on_multiple_columns() throws Exception {\n    stmt.executeUpdate(\n        \"CREATE TABLE test_integration (\"\n            + \"string_col TEXT, \"\n            + \"boolean_col INTEGER, \"\n            + \"byte_col INTEGER, \"\n            + \"short_col INTEGER, \"\n            + \"int_col INTEGER, \"\n            + \"long_col BIGINT, \"\n            + \"float_col REAL, \"\n            + \"double_col REAL, \"\n            + \"bigdecimal_col REAL, \"\n            + \"bytes_col BLOB);\");\n\n    stmt.executeUpdate(\n        \"INSERT INTO test_integration VALUES (\"\n            + \"'test', \"\n            + \"1, \"\n            + \"1, \"\n            + \"123, \"\n            + \"12345, \"\n            + \"1234567890, \"\n            + \"1.23, \"\n            + \"1.234567, \"\n            + \"12345.67, \"\n            + \"X'48656C6C6F');\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_integration\");\n    assertTrue(resultSet.next());\n\n    // Verify each column\n    assertEquals(\"test\", resultSet.getString(1));\n    assertTrue(resultSet.getBoolean(2));\n    assertEquals(1, resultSet.getByte(3));\n    assertEquals(123, resultSet.getShort(4));\n    assertEquals(12345, resultSet.getInt(5));\n    assertEquals(1234567890L, resultSet.getLong(6));\n    assertEquals(1.23f, resultSet.getFloat(7), 0.0001);\n    assertEquals(1.234567, resultSet.getDouble(8), 0.0001);\n    assertEquals(\n        new BigDecimal(\"12345.67\").setScale(2, RoundingMode.HALF_UP),\n        resultSet.getBigDecimal(9, 2));\n    assertArrayEquals(\"Hello\".getBytes(), resultSet.getBytes(10));\n  }\n\n  @Test\n  void test_invalidColumnIndex_outOfBounds() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_invalid (col INTEGER);\");\n    stmt.executeUpdate(\"INSERT INTO test_invalid (col) VALUES (1);\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_invalid\");\n    assertTrue(resultSet.next());\n\n    // Test out-of-bounds column index\n    assertThrows(SQLException.class, () -> resultSet.getInt(2));\n  }\n\n  @Test\n  void test_invalidColumnIndex_negative() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_invalid (col INTEGER);\");\n    stmt.executeUpdate(\"INSERT INTO test_invalid (col) VALUES (1);\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_invalid\");\n    assertTrue(resultSet.next());\n\n    // Test negative column index\n    assertThrows(SQLException.class, () -> resultSet.getInt(-1));\n  }\n\n  @Test\n  void test_findColumn_with_exact_name() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE users (id INTEGER, username TEXT, age INTEGER);\");\n    stmt.executeUpdate(\"INSERT INTO users VALUES (1, 'minseok', 30);\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM users\");\n    assertTrue(resultSet.next());\n\n    assertEquals(1, resultSet.findColumn(\"id\"));\n    assertEquals(2, resultSet.findColumn(\"username\"));\n    assertEquals(3, resultSet.findColumn(\"age\"));\n  }\n\n  @Test\n  void test_findColumn_with_duplicate_names() throws Exception {\n    // SQLite allows duplicate column names in SELECT\n    stmt.executeUpdate(\"CREATE TABLE test (a INTEGER, b INTEGER);\");\n    stmt.executeUpdate(\"INSERT INTO test VALUES (1, 2);\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT a, a FROM test\");\n    assertTrue(resultSet.next());\n\n    // Should return the FIRST occurrence\n    assertEquals(1, resultSet.findColumn(\"a\"));\n  }\n\n  @Test\n  void test_findColumn_with_nonexistent_column() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE users (id INTEGER);\");\n    stmt.executeUpdate(\"INSERT INTO users VALUES (1);\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM users\");\n    assertTrue(resultSet.next());\n\n    SQLException exception =\n        assertThrows(SQLException.class, () -> resultSet.findColumn(\"nonexistent\"));\n    assertEquals(\"column name nonexistent not found\", exception.getMessage());\n  }\n\n  @Test\n  void test_findColumn_with_alias() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE users (id INTEGER);\");\n    stmt.executeUpdate(\"INSERT INTO users VALUES (1);\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT id AS user_id FROM users\");\n    assertTrue(resultSet.next());\n\n    // Should find by alias, not original column name\n    assertEquals(1, resultSet.findColumn(\"user_id\"));\n    SQLException exception = assertThrows(SQLException.class, () -> resultSet.findColumn(\"id\"));\n    assertEquals(\"column name id not found\", exception.getMessage());\n  }\n\n  @Test\n  void test_findColumn_with_empty_string() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test (col INTEGER);\");\n    stmt.executeUpdate(\"INSERT INTO test VALUES (1);\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test\");\n    assertTrue(resultSet.next());\n\n    SQLException exception = assertThrows(SQLException.class, () -> resultSet.findColumn(\"\"));\n    assertEquals(\"column name not found\", exception.getMessage());\n  }\n\n  @Test\n  void test_findColumn_with_special_characters() throws Exception {\n    // SQLite allows spaces and special chars in column names if quoted\n    stmt.executeUpdate(\"CREATE TABLE test ([user name] TEXT, [user-id] INTEGER);\");\n    stmt.executeUpdate(\"INSERT INTO test VALUES ('minseok', 1);\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test\");\n    assertTrue(resultSet.next());\n\n    assertEquals(1, resultSet.findColumn(\"user name\"));\n    assertEquals(2, resultSet.findColumn(\"user-id\"));\n  }\n\n  @Test\n  void test_getCharacterStream() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_char_stream (text_col TEXT);\");\n    stmt.executeUpdate(\"INSERT INTO test_char_stream (text_col) VALUES ('Hello World');\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_char_stream\");\n    assertTrue(resultSet.next());\n\n    Reader reader = resultSet.getCharacterStream(1);\n    char[] buffer = new char[11];\n    int charsRead = reader.read(buffer);\n\n    assertEquals(11, charsRead);\n    assertEquals(\"Hello World\", new String(buffer));\n  }\n\n  @Test\n  void test_getCharacterStream_with_columnLabel() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_char_stream (text_col TEXT);\");\n    stmt.executeUpdate(\"INSERT INTO test_char_stream (text_col) VALUES ('Test Data');\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_char_stream\");\n    assertTrue(resultSet.next());\n\n    Reader reader = resultSet.getCharacterStream(\"text_col\");\n    char[] buffer = new char[9];\n    reader.read(buffer);\n\n    assertEquals(\"Test Data\", new String(buffer));\n  }\n\n  @Test\n  void test_getCharacterStream_returns_null_on_null() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_null (text_col TEXT);\");\n    stmt.executeUpdate(\"INSERT INTO test_null (text_col) VALUES (NULL);\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_null\");\n    assertTrue(resultSet.next());\n    assertNull(resultSet.getCharacterStream(1));\n  }\n\n  @Test\n  void test_getBigDecimal_with_columnLabel() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_bigdecimal (amount REAL);\");\n    stmt.executeUpdate(\"INSERT INTO test_bigdecimal (amount) VALUES (12345.67);\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_bigdecimal\");\n    assertTrue(resultSet.next());\n\n    assertEquals(BigDecimal.valueOf(12345.67), resultSet.getBigDecimal(\"amount\"));\n  }\n\n  @Test\n  void test_isBeforeFirst_and_isAfterLast() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_position (id INTEGER);\");\n    stmt.executeUpdate(\"INSERT INTO test_position VALUES (1);\");\n    stmt.executeUpdate(\"INSERT INTO test_position VALUES (2);\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_position\");\n\n    // Before first row\n    assertTrue(resultSet.isBeforeFirst());\n    assertFalse(resultSet.isAfterLast());\n\n    // First row\n    resultSet.next();\n    assertFalse(resultSet.isBeforeFirst());\n    assertFalse(resultSet.isAfterLast());\n\n    // Second row\n    resultSet.next();\n    assertFalse(resultSet.isBeforeFirst());\n    assertFalse(resultSet.isAfterLast());\n\n    // After last row\n    resultSet.next();\n    assertFalse(resultSet.isBeforeFirst());\n    assertTrue(resultSet.isAfterLast());\n  }\n\n  @Test\n  void test_isBeforeFirst_with_empty_resultSet() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_empty (id INTEGER);\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_empty\");\n\n    // After calling next() on empty ResultSet\n    assertFalse(resultSet.next());\n    assertFalse(resultSet.isBeforeFirst());\n    assertTrue(resultSet.isAfterLast());\n  }\n\n  @Test\n  void test_getRow() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_row (id INTEGER);\");\n    stmt.executeUpdate(\"INSERT INTO test_row VALUES (1);\");\n    stmt.executeUpdate(\"INSERT INTO test_row VALUES (2);\");\n    stmt.executeUpdate(\"INSERT INTO test_row VALUES (3);\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_row\");\n\n    // Before first row\n    assertEquals(0, resultSet.getRow());\n\n    // First row\n    resultSet.next();\n    assertEquals(1, resultSet.getRow());\n\n    // Second row\n    resultSet.next();\n    assertEquals(2, resultSet.getRow());\n\n    // Third row\n    resultSet.next();\n    assertEquals(3, resultSet.getRow());\n\n    // After last row\n    resultSet.next();\n    assertEquals(3, resultSet.getRow());\n  }\n\n  @Test\n  void test_getDate_with_calendar() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_date_cal (date_col BLOB);\");\n\n    // 2025-10-07 03:00:00 UTC in milliseconds\n    long utcTime = 1728270000000L;\n    byte[] timeBytes = ByteBuffer.allocate(Long.BYTES).putLong(utcTime).array();\n\n    StringBuilder hexString = new StringBuilder();\n    for (byte b : timeBytes) {\n      hexString.append(String.format(\"%02X\", b));\n    }\n    stmt.executeUpdate(\"INSERT INTO test_date_cal (date_col) VALUES (X'\" + hexString + \"');\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_date_cal\");\n    assertTrue(resultSet.next());\n\n    // Get date with UTC calendar\n    Calendar utcCal = Calendar.getInstance(java.util.TimeZone.getTimeZone(\"UTC\"));\n    Date utcDate = resultSet.getDate(1, utcCal);\n\n    // Get date with Seoul calendar (UTC+9)\n    Calendar seoulCal = Calendar.getInstance(java.util.TimeZone.getTimeZone(\"Asia/Seoul\"));\n    Date seoulDate = resultSet.getDate(1, seoulCal);\n\n    // Seoul time should be 9 hours ahead\n    long timeDiff = seoulDate.getTime() - utcDate.getTime();\n    assertEquals(9 * 60 * 60 * 1000, timeDiff);\n  }\n\n  @Test\n  void test_getDate_with_calendar_columnLabel() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_date_cal (created_at BLOB);\");\n\n    // 2025-10-07 03:00:00 UTC in milliseconds\n    long utcTime = 1728270000000L;\n    byte[] timeBytes = ByteBuffer.allocate(Long.BYTES).putLong(utcTime).array();\n\n    StringBuilder hexString = new StringBuilder();\n    for (byte b : timeBytes) {\n      hexString.append(String.format(\"%02X\", b));\n    }\n    stmt.executeUpdate(\"INSERT INTO test_date_cal (created_at) VALUES (X'\" + hexString + \"');\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_date_cal\");\n    assertTrue(resultSet.next());\n\n    Calendar utcCal = Calendar.getInstance(java.util.TimeZone.getTimeZone(\"UTC\"));\n    Date date = resultSet.getDate(\"created_at\", utcCal);\n\n    assertNotNull(date);\n  }\n\n  @Test\n  void test_getTime_with_calendar() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_time_cal (time_col BLOB);\");\n\n    // 2025-10-07 03:00:00 UTC in milliseconds\n    long utcTime = 1728270000000L;\n    byte[] timeBytes = ByteBuffer.allocate(Long.BYTES).putLong(utcTime).array();\n\n    StringBuilder hexString = new StringBuilder();\n    for (byte b : timeBytes) {\n      hexString.append(String.format(\"%02X\", b));\n    }\n    stmt.executeUpdate(\"INSERT INTO test_time_cal (time_col) VALUES (X'\" + hexString + \"');\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_time_cal\");\n    assertTrue(resultSet.next());\n\n    // Get time with UTC calendar\n    Calendar utcCal = Calendar.getInstance(java.util.TimeZone.getTimeZone(\"UTC\"));\n    Time utcTime2 = resultSet.getTime(1, utcCal);\n\n    // Get time with Seoul calendar (UTC+9)\n    Calendar seoulCal = Calendar.getInstance(java.util.TimeZone.getTimeZone(\"Asia/Seoul\"));\n    Time seoulTime = resultSet.getTime(1, seoulCal);\n\n    // Seoul time should be 9 hours ahead\n    long timeDiff = seoulTime.getTime() - utcTime2.getTime();\n    assertEquals(9 * 60 * 60 * 1000, timeDiff);\n  }\n\n  @Test\n  void test_getTime_with_calendar_columnLabel() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_time_cal (created_at BLOB);\");\n\n    // 2025-10-07 03:00:00 UTC in milliseconds\n    long utcTime = 1728270000000L;\n    byte[] timeBytes = ByteBuffer.allocate(Long.BYTES).putLong(utcTime).array();\n\n    StringBuilder hexString = new StringBuilder();\n    for (byte b : timeBytes) {\n      hexString.append(String.format(\"%02X\", b));\n    }\n    stmt.executeUpdate(\"INSERT INTO test_time_cal (created_at) VALUES (X'\" + hexString + \"');\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_time_cal\");\n    assertTrue(resultSet.next());\n\n    Calendar utcCal = Calendar.getInstance(java.util.TimeZone.getTimeZone(\"UTC\"));\n    Time time = resultSet.getTime(\"created_at\", utcCal);\n\n    assertNotNull(time);\n  }\n\n  @Test\n  void test_getTimestamp_with_calendar() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_timestamp_cal (timestamp_col BLOB);\");\n\n    // 2025-10-07 03:00:00 UTC in milliseconds\n    long utcTime = 1728270000000L;\n    byte[] timeBytes = ByteBuffer.allocate(Long.BYTES).putLong(utcTime).array();\n\n    StringBuilder hexString = new StringBuilder();\n    for (byte b : timeBytes) {\n      hexString.append(String.format(\"%02X\", b));\n    }\n    stmt.executeUpdate(\n        \"INSERT INTO test_timestamp_cal (timestamp_col) VALUES (X'\" + hexString + \"');\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_timestamp_cal\");\n    assertTrue(resultSet.next());\n\n    // Get timestamp with UTC calendar\n    Calendar utcCal = Calendar.getInstance(java.util.TimeZone.getTimeZone(\"UTC\"));\n    Timestamp utcTimestamp = resultSet.getTimestamp(1, utcCal);\n\n    // Get timestamp with Seoul calendar (UTC+9)\n    Calendar seoulCal = Calendar.getInstance(java.util.TimeZone.getTimeZone(\"Asia/Seoul\"));\n    Timestamp seoulTimestamp = resultSet.getTimestamp(1, seoulCal);\n\n    // Seoul time should be 9 hours ahead\n    long timeDiff = seoulTimestamp.getTime() - utcTimestamp.getTime();\n    assertEquals(9 * 60 * 60 * 1000, timeDiff);\n  }\n\n  @Test\n  void test_getTimestamp_with_calendar_columnLabel() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_timestamp_cal (created_at BLOB);\");\n\n    // 2025-10-07 03:00:00 UTC in milliseconds\n    long utcTime = 1728270000000L;\n    byte[] timeBytes = ByteBuffer.allocate(Long.BYTES).putLong(utcTime).array();\n\n    StringBuilder hexString = new StringBuilder();\n    for (byte b : timeBytes) {\n      hexString.append(String.format(\"%02X\", b));\n    }\n    stmt.executeUpdate(\n        \"INSERT INTO test_timestamp_cal (created_at) VALUES (X'\" + hexString + \"');\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_timestamp_cal\");\n    assertTrue(resultSet.next());\n\n    Calendar utcCal = Calendar.getInstance(java.util.TimeZone.getTimeZone(\"UTC\"));\n    Timestamp timestamp = resultSet.getTimestamp(\"created_at\", utcCal);\n\n    assertNotNull(timestamp);\n  }\n\n  @Test\n  void test_wasNull() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_was_null (id INTEGER, name TEXT);\");\n    stmt.executeUpdate(\"INSERT INTO test_was_null VALUES (1, 'test');\");\n    stmt.executeUpdate(\"INSERT INTO test_was_null VALUES (NULL, NULL);\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_was_null\");\n\n    // First row - non-null values\n    assertTrue(resultSet.next());\n    int id = resultSet.getInt(1);\n    assertFalse(resultSet.wasNull());\n    String name = resultSet.getString(2);\n    assertFalse(resultSet.wasNull());\n\n    // Second row - null values\n    assertTrue(resultSet.next());\n    int nullInt = resultSet.getInt(1);\n    assertTrue(resultSet.wasNull());\n    assertEquals(0, nullInt);\n    String nullString = resultSet.getString(2);\n    assertTrue(resultSet.wasNull());\n    assertNull(nullString);\n  }\n\n  @Test\n  void test_columnLabel_getters() throws Exception {\n    stmt.executeUpdate(\n        \"CREATE TABLE test_column_label (\"\n            + \"bool_col INTEGER, \"\n            + \"byte_col INTEGER, \"\n            + \"short_col INTEGER, \"\n            + \"int_col INTEGER, \"\n            + \"long_col BIGINT, \"\n            + \"float_col REAL, \"\n            + \"double_col REAL, \"\n            + \"bytes_col BLOB);\");\n\n    stmt.executeUpdate(\n        \"INSERT INTO test_column_label VALUES (\"\n            + \"1, 127, 32767, 2147483647, 9223372036854775807, 3.14, 2.718281828, X'48656C6C6F');\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_column_label\");\n    assertTrue(resultSet.next());\n\n    // Test columnLabel-based getters\n    assertTrue(resultSet.getBoolean(\"bool_col\"));\n    assertEquals(127, resultSet.getByte(\"byte_col\"));\n    assertEquals(32767, resultSet.getShort(\"short_col\"));\n    assertEquals(2147483647, resultSet.getInt(\"int_col\"));\n    assertEquals(9223372036854775807L, resultSet.getLong(\"long_col\"));\n    assertEquals(3.14f, resultSet.getFloat(\"float_col\"), 0.001);\n    assertEquals(2.718281828, resultSet.getDouble(\"double_col\"), 0.000001);\n    assertArrayEquals(\"Hello\".getBytes(), resultSet.getBytes(\"bytes_col\"));\n  }\n\n  @Test\n  void test_getObject_with_columnLabel() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_object (id INTEGER, name TEXT);\");\n    stmt.executeUpdate(\"INSERT INTO test_object VALUES (42, 'test');\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_object\");\n    assertTrue(resultSet.next());\n\n    Object idObj = resultSet.getObject(\"id\");\n    assertEquals(42L, idObj);\n    assertFalse(resultSet.wasNull());\n\n    Object nameObj = resultSet.getObject(\"name\");\n    assertEquals(\"test\", nameObj);\n    assertFalse(resultSet.wasNull());\n  }\n\n  @Test\n  void test_getBigDecimal_with_scale_columnLabel() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_decimal (amount REAL);\");\n    stmt.executeUpdate(\"INSERT INTO test_decimal VALUES (123.456789);\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_decimal\");\n    assertTrue(resultSet.next());\n\n    BigDecimal result = resultSet.getBigDecimal(\"amount\", 2);\n    assertEquals(new BigDecimal(\"123.46\"), result); // Should be rounded to 2 decimal places\n  }\n\n  @Test\n  void test_getTime_with_columnLabel() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_time (time_col BLOB);\");\n\n    long timeMillis = System.currentTimeMillis();\n    byte[] timeBytes = ByteBuffer.allocate(Long.BYTES).putLong(timeMillis).array();\n    StringBuilder hexString = new StringBuilder();\n    for (byte b : timeBytes) {\n      hexString.append(String.format(\"%02X\", b));\n    }\n    stmt.executeUpdate(\"INSERT INTO test_time VALUES (X'\" + hexString + \"');\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_time\");\n    assertTrue(resultSet.next());\n\n    Time time = resultSet.getTime(\"time_col\");\n    assertNotNull(time);\n    assertEquals(timeMillis, time.getTime());\n  }\n\n  @Test\n  void test_getAsciiStream() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_ascii (text_col TEXT);\");\n    stmt.executeUpdate(\"INSERT INTO test_ascii VALUES ('Hello ASCII');\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_ascii\");\n    assertTrue(resultSet.next());\n\n    InputStream stream = resultSet.getAsciiStream(1);\n    assertNotNull(stream);\n    byte[] buffer = new byte[11];\n    int bytesRead = stream.read(buffer);\n    assertEquals(11, bytesRead);\n    assertEquals(\"Hello ASCII\", new String(buffer, \"US-ASCII\"));\n    assertFalse(resultSet.wasNull());\n  }\n\n  @Test\n  void test_getAsciiStream_with_columnLabel() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_ascii (text_col TEXT);\");\n    stmt.executeUpdate(\"INSERT INTO test_ascii VALUES ('Test');\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_ascii\");\n    assertTrue(resultSet.next());\n\n    InputStream stream = resultSet.getAsciiStream(\"text_col\");\n    assertNotNull(stream);\n    byte[] buffer = new byte[4];\n    stream.read(buffer);\n    assertEquals(\"Test\", new String(buffer, \"US-ASCII\"));\n  }\n\n  @Test\n  void test_getBinaryStream() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_binary (binary_col BLOB);\");\n    byte[] data = {0x01, 0x02, 0x03, 0x04, 0x05};\n\n    StringBuilder hexString = new StringBuilder();\n    for (byte b : data) {\n      hexString.append(String.format(\"%02X\", b));\n    }\n    stmt.executeUpdate(\"INSERT INTO test_binary VALUES (X'\" + hexString + \"');\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_binary\");\n    assertTrue(resultSet.next());\n\n    InputStream stream = resultSet.getBinaryStream(1);\n    assertNotNull(stream);\n    byte[] buffer = new byte[5];\n    int bytesRead = stream.read(buffer);\n    assertEquals(5, bytesRead);\n    assertArrayEquals(data, buffer);\n    assertFalse(resultSet.wasNull());\n  }\n\n  @Test\n  void test_getBinaryStream_with_columnLabel() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_binary (data BLOB);\");\n    byte[] data = {0x0A, 0x0B, 0x0C};\n\n    StringBuilder hexString = new StringBuilder();\n    for (byte b : data) {\n      hexString.append(String.format(\"%02X\", b));\n    }\n    stmt.executeUpdate(\"INSERT INTO test_binary VALUES (X'\" + hexString + \"');\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_binary\");\n    assertTrue(resultSet.next());\n\n    InputStream stream = resultSet.getBinaryStream(\"data\");\n    assertNotNull(stream);\n    byte[] buffer = new byte[3];\n    stream.read(buffer);\n    assertArrayEquals(data, buffer);\n  }\n\n  @Test\n  void test_getUnicodeStream() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_unicode (text_col TEXT);\");\n    stmt.executeUpdate(\"INSERT INTO test_unicode VALUES ('Hello minseok');\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_unicode\");\n    assertTrue(resultSet.next());\n\n    InputStream stream = resultSet.getUnicodeStream(1);\n    assertNotNull(stream);\n    byte[] buffer = new byte[1024];\n    int bytesRead = stream.read(buffer);\n    String result = new String(buffer, 0, bytesRead, \"UTF-8\");\n    assertEquals(\"Hello minseok\", result);\n    assertFalse(resultSet.wasNull());\n  }\n\n  @Test\n  void test_getUnicodeStream_with_columnLabel() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_unicode (text_col TEXT);\");\n    stmt.executeUpdate(\"INSERT INTO test_unicode VALUES ('Unicode 테스트');\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_unicode\");\n    assertTrue(resultSet.next());\n\n    InputStream stream = resultSet.getUnicodeStream(\"text_col\");\n    assertNotNull(stream);\n    byte[] buffer = new byte[1024];\n    int bytesRead = stream.read(buffer);\n    String result = new String(buffer, 0, bytesRead, \"UTF-8\");\n    assertEquals(\"Unicode 테스트\", result);\n  }\n\n  @Test\n  void test_stream_methods_return_null_on_null() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_null_stream (text_col TEXT, binary_col BLOB);\");\n    stmt.executeUpdate(\"INSERT INTO test_null_stream VALUES (NULL, NULL);\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_null_stream\");\n    assertTrue(resultSet.next());\n\n    assertNull(resultSet.getAsciiStream(1));\n    assertTrue(resultSet.wasNull());\n\n    assertNull(resultSet.getUnicodeStream(1));\n    assertTrue(resultSet.wasNull());\n\n    assertNull(resultSet.getBinaryStream(2));\n    assertTrue(resultSet.wasNull());\n  }\n\n  @Test\n  void test_getMetaData_column_count() throws Exception {\n    stmt.executeUpdate(\"CREATE TABLE test_meta (col1 INTEGER, col2 TEXT, col3 REAL);\");\n    stmt.executeUpdate(\"INSERT INTO test_meta VALUES (1, 'test', 3.14);\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_meta\");\n    ResultSetMetaData metaData = resultSet.getMetaData();\n\n    assertEquals(3, metaData.getColumnCount());\n    assertEquals(\"col1\", metaData.getColumnName(1));\n    assertEquals(\"col2\", metaData.getColumnName(2));\n    assertEquals(\"col3\", metaData.getColumnName(3));\n    assertEquals(\"col1\", metaData.getColumnLabel(1));\n    assertEquals(Integer.MAX_VALUE, metaData.getColumnDisplaySize(1));\n  }\n\n  @Test\n  void test_wasNull_consistency_across_types() throws Exception {\n    stmt.executeUpdate(\n        \"CREATE TABLE test_null_types (\"\n            + \"int_col INTEGER, \"\n            + \"text_col TEXT, \"\n            + \"real_col REAL, \"\n            + \"blob_col BLOB);\");\n    stmt.executeUpdate(\"INSERT INTO test_null_types VALUES (NULL, NULL, NULL, NULL);\");\n\n    ResultSet resultSet = stmt.executeQuery(\"SELECT * FROM test_null_types\");\n    assertTrue(resultSet.next());\n\n    // Test wasNull for various getter methods\n    resultSet.getInt(1);\n    assertTrue(resultSet.wasNull());\n\n    resultSet.getString(2);\n    assertTrue(resultSet.wasNull());\n\n    resultSet.getDouble(3);\n    assertTrue(resultSet.wasNull());\n\n    resultSet.getBytes(4);\n    assertTrue(resultSet.wasNull());\n\n    resultSet.getObject(1);\n    assertTrue(resultSet.wasNull());\n\n    resultSet.getBigDecimal(3);\n    assertTrue(resultSet.wasNull());\n  }\n}\n"
  },
  {
    "path": "bindings/java/src/test/java/tech/turso/jdbc4/JDBC4StatementTest.java",
    "content": "package tech.turso.jdbc4;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertDoesNotThrow;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.BatchUpdateException;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.Properties;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport tech.turso.TestUtils;\n\nclass JDBC4StatementTest {\n\n  private Statement stmt;\n\n  @BeforeEach\n  void setUp() throws Exception {\n    String filePath = TestUtils.createTempFile();\n    String url = \"jdbc:turso:\" + filePath;\n    final JDBC4Connection connection = new JDBC4Connection(url, filePath, new Properties());\n    stmt =\n        connection.createStatement(\n            ResultSet.TYPE_FORWARD_ONLY,\n            ResultSet.CONCUR_READ_ONLY,\n            ResultSet.CLOSE_CURSORS_AT_COMMIT);\n  }\n\n  @Test\n  void execute_ddl_should_return_false() throws Exception {\n    assertFalse(stmt.execute(\"CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT);\"));\n  }\n\n  @Test\n  void execute_insert_should_return_false() throws Exception {\n    stmt.execute(\"CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT);\");\n    assertFalse(stmt.execute(\"INSERT INTO users VALUES (1, 'turso');\"));\n  }\n\n  @Test\n  void execute_update_should_return_false() throws Exception {\n    stmt.execute(\"CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT);\");\n    stmt.execute(\"INSERT INTO users VALUES (1, 'turso');\");\n    assertFalse(stmt.execute(\"UPDATE users SET username = 'seonwoo' WHERE id = 1;\"));\n  }\n\n  @Test\n  void execute_select_should_return_true() throws Exception {\n    stmt.execute(\"CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT);\");\n    stmt.execute(\"INSERT INTO users VALUES (1, 'turso');\");\n    assertTrue(stmt.execute(\"SELECT * FROM users;\"));\n  }\n\n  @Test\n  void execute_select() throws Exception {\n    stmt.execute(\"CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT);\");\n    stmt.execute(\"INSERT INTO users VALUES (1, 'turso 1')\");\n    stmt.execute(\"INSERT INTO users VALUES (2, 'turso 2')\");\n    stmt.execute(\"INSERT INTO users VALUES (3, 'turso 3')\");\n\n    ResultSet rs = stmt.executeQuery(\"SELECT * FROM users;\");\n    rs.next();\n    int rowCount = 0;\n\n    do {\n      rowCount++;\n      int id = rs.getInt(1);\n      String username = rs.getString(2);\n\n      assertEquals(id, rowCount);\n      assertEquals(username, \"turso \" + rowCount);\n    } while (rs.next());\n\n    assertEquals(rowCount, 3);\n    assertFalse(rs.next());\n  }\n\n  @Test\n  void close_statement_test() throws Exception {\n    stmt.close();\n    assertTrue(stmt.isClosed());\n  }\n\n  @Test\n  void double_close_is_no_op() throws SQLException {\n    stmt.close();\n    assertDoesNotThrow(() -> stmt.close());\n  }\n\n  @Test\n  void operations_on_closed_statement_should_throw_exception() throws Exception {\n    stmt.close();\n    assertThrows(SQLException.class, () -> stmt.execute(\"SELECT 1;\"));\n  }\n\n  @Test\n  void execute_update_should_return_number_of_inserted_elements() throws Exception {\n    assertThat(stmt.executeUpdate(\"CREATE TABLE s1 (c1);\")).isEqualTo(0);\n    assertThat(stmt.executeUpdate(\"INSERT INTO s1 VALUES (0);\")).isEqualTo(1);\n    assertThat(stmt.executeUpdate(\"INSERT INTO s1 VALUES (1), (2);\")).isEqualTo(2);\n    assertThat(stmt.executeUpdate(\"INSERT INTO s1 VALUES (3), (4), (5);\")).isEqualTo(3);\n  }\n\n  @Test\n  void execute_update_should_return_number_of_updated_elements() throws Exception {\n    assertThat(stmt.executeUpdate(\"CREATE TABLE s1 (c1 INT);\")).isEqualTo(0);\n    assertThat(stmt.executeUpdate(\"INSERT INTO s1 VALUES (1), (2), (3);\")).isEqualTo(3);\n    assertThat(stmt.executeUpdate(\"UPDATE s1 SET c1 = 0;\")).isEqualTo(3);\n  }\n\n  @Test\n  void execute_update_should_return_number_of_deleted_elements() throws Exception {\n    assertThat(stmt.executeUpdate(\"CREATE TABLE s1 (c1);\")).isEqualTo(0);\n    assertThat(stmt.executeUpdate(\"INSERT INTO s1 VALUES (1), (2), (3);\")).isEqualTo(3);\n\n    assertThat(stmt.executeUpdate(\"DELETE FROM s1\")).isEqualTo(3);\n  }\n\n  /** Tests for batch processing functionality */\n  @Test\n  void testAddBatch_single_statement() throws SQLException {\n    stmt.execute(\"CREATE TABLE batch_test (id INTEGER PRIMARY KEY, value TEXT);\");\n\n    stmt.addBatch(\"INSERT INTO batch_test VALUES (1, 'test1');\");\n\n    int[] updateCounts = stmt.executeBatch();\n\n    assertThat(updateCounts).hasSize(1);\n    assertThat(updateCounts[0]).isEqualTo(1);\n\n    ResultSet rs = stmt.executeQuery(\"SELECT COUNT(*) FROM batch_test;\");\n    assertTrue(rs.next());\n    assertThat(rs.getInt(1)).isEqualTo(1);\n  }\n\n  @Test\n  void testAddBatch_multiple_statements() throws SQLException {\n    stmt.execute(\"CREATE TABLE batch_test (id INTEGER PRIMARY KEY, value TEXT);\");\n\n    stmt.addBatch(\"INSERT INTO batch_test VALUES (1, 'test1');\");\n    stmt.addBatch(\"INSERT INTO batch_test VALUES (2, 'test2');\");\n    stmt.addBatch(\"INSERT INTO batch_test VALUES (3, 'test3');\");\n\n    int[] updateCounts = stmt.executeBatch();\n\n    assertThat(updateCounts).hasSize(3);\n    assertThat(updateCounts[0]).isEqualTo(1);\n    assertThat(updateCounts[1]).isEqualTo(1);\n    assertThat(updateCounts[2]).isEqualTo(1);\n\n    ResultSet rs = stmt.executeQuery(\"SELECT COUNT(*) FROM batch_test;\");\n    assertTrue(rs.next());\n    assertThat(rs.getInt(1)).isEqualTo(3);\n  }\n\n  @Test\n  void testAddBatch_with_updates_and_deletes() throws SQLException {\n    stmt.execute(\"CREATE TABLE batch_test (id INTEGER PRIMARY KEY, value TEXT);\");\n\n    stmt.execute(\n        \"INSERT INTO batch_test VALUES (1, 'initial1'), (2, 'initial2'), (3, 'initial3');\");\n\n    stmt.addBatch(\"UPDATE batch_test SET value = 'updated';\");\n    stmt.addBatch(\"DELETE FROM batch_test WHERE id = 2;\");\n    stmt.addBatch(\"INSERT INTO batch_test VALUES (4, 'new');\");\n\n    int[] updateCounts = stmt.executeBatch();\n\n    assertThat(updateCounts).hasSize(3);\n    assertThat(updateCounts[0]).isEqualTo(3); // UPDATE affected 3 row\n    assertThat(updateCounts[1]).isEqualTo(1); // DELETE affected 1 row\n    assertThat(updateCounts[2]).isEqualTo(1); // INSERT affected 1 row\n\n    // Verify final state\n    ResultSet rs = stmt.executeQuery(\"SELECT COUNT(*) FROM batch_test;\");\n    assertTrue(rs.next());\n    assertThat(rs.getInt(1)).isEqualTo(3); // 3 initial - 1 deleted + 1 inserted = 3\n  }\n\n  @Test\n  void testClearBatch() throws SQLException {\n    stmt.execute(\"CREATE TABLE batch_test (id INTEGER PRIMARY KEY, value TEXT);\");\n\n    stmt.addBatch(\"INSERT INTO batch_test VALUES (1, 'test1');\");\n    stmt.addBatch(\"INSERT INTO batch_test VALUES (2, 'test2');\");\n\n    stmt.clearBatch();\n\n    int[] updateCounts = stmt.executeBatch();\n    assertThat(updateCounts).isEmpty();\n\n    ResultSet rs = stmt.executeQuery(\"SELECT COUNT(*) FROM batch_test;\");\n    assertTrue(rs.next());\n    assertThat(rs.getInt(1)).isEqualTo(0);\n  }\n\n  @Test\n  void testBatch_with_SELECT_should_throw_exception() throws SQLException {\n    stmt.execute(\"CREATE TABLE batch_test (id INTEGER PRIMARY KEY, value TEXT);\");\n    stmt.execute(\"INSERT INTO batch_test VALUES (1, 'test1');\");\n\n    stmt.addBatch(\"INSERT INTO batch_test VALUES (2, 'test2');\");\n    stmt.addBatch(\"SELECT * FROM batch_test;\"); // This should cause an exception\n    stmt.addBatch(\"INSERT INTO batch_test VALUES (3, 'test3');\");\n\n    BatchUpdateException exception =\n        assertThrows(BatchUpdateException.class, () -> stmt.executeBatch());\n\n    assertTrue(exception.getMessage().contains(\"Batch commands cannot return result sets\"));\n\n    int[] updateCounts = exception.getUpdateCounts();\n    assertThat(updateCounts).hasSize(3);\n    assertThat(updateCounts[0]).isEqualTo(1); // First INSERT succeeded\n    assertThat(updateCounts[1]).isEqualTo(Statement.EXECUTE_FAILED); // SELECT failed\n  }\n\n  @Test\n  void testBatch_with_null_command_should_throw_exception() {\n    assertThrows(SQLException.class, () -> stmt.addBatch(null));\n  }\n\n  @Test\n  void testBatch_operations_on_closed_statement_should_throw_exception() throws SQLException {\n    stmt.close();\n\n    assertThrows(SQLException.class, () -> stmt.addBatch(\"INSERT INTO test VALUES (1);\"));\n    assertThrows(SQLException.class, () -> stmt.clearBatch());\n    assertThrows(SQLException.class, () -> stmt.executeBatch());\n  }\n\n  @Test\n  void testBatch_with_syntax_error_should_throw_exception() throws SQLException {\n    stmt.execute(\"CREATE TABLE batch_test (id INTEGER PRIMARY KEY, value TEXT);\");\n\n    stmt.addBatch(\"INSERT INTO batch_test VALUES (1, 'test1');\");\n    stmt.addBatch(\"INVALID SQL SYNTAX;\"); // This should cause an exception\n    stmt.addBatch(\"INSERT INTO batch_test VALUES (3, 'test3');\");\n\n    BatchUpdateException exception =\n        assertThrows(BatchUpdateException.class, () -> stmt.executeBatch());\n\n    int[] updateCounts = exception.getUpdateCounts();\n    assertThat(updateCounts).hasSize(3);\n    assertThat(updateCounts[0]).isEqualTo(1); // First INSERT succeeded\n    assertThat(updateCounts[1]).isEqualTo(Statement.EXECUTE_FAILED); // Invalid SQL failed\n  }\n\n  @Test\n  void testBatch_empty_batch_returns_empty_array() throws SQLException {\n    int[] updateCounts = stmt.executeBatch();\n    assertThat(updateCounts).isEmpty();\n  }\n\n  @Test\n  void testBatch_clears_after_successful_execution() throws SQLException {\n    stmt.execute(\"CREATE TABLE batch_test (id INTEGER PRIMARY KEY, value TEXT);\");\n\n    stmt.addBatch(\"INSERT INTO batch_test VALUES (1, 'test1');\");\n    stmt.executeBatch();\n\n    int[] updateCounts = stmt.executeBatch();\n    assertThat(updateCounts).isEmpty();\n  }\n\n  @Test\n  void testBatch_clears_after_failed_execution() throws SQLException {\n    stmt.execute(\"CREATE TABLE batch_test (id INTEGER PRIMARY KEY, value TEXT);\");\n\n    stmt.addBatch(\"SELECT * FROM batch_test;\");\n\n    assertThrows(BatchUpdateException.class, () -> stmt.executeBatch());\n\n    int[] updateCounts = stmt.executeBatch();\n    assertThat(updateCounts).isEmpty();\n  }\n\n  /** Tests for isBatchCompatibleStatement method */\n  @Test\n  void testIsBatchCompatibleStatement_compatible_statements() {\n    JDBC4Statement jdbc4Stmt = (JDBC4Statement) stmt;\n\n    assertTrue(jdbc4Stmt.isBatchCompatibleStatement(\"INSERT INTO table VALUES (1, 2);\"));\n    assertTrue(jdbc4Stmt.isBatchCompatibleStatement(\"insert into table values (1, 2);\"));\n    assertTrue(\n        jdbc4Stmt.isBatchCompatibleStatement(\"INSERT INTO table (col1, col2) VALUES (1, 2);\"));\n    assertTrue(jdbc4Stmt.isBatchCompatibleStatement(\"INSERT OR REPLACE INTO table VALUES (1);\"));\n    assertTrue(jdbc4Stmt.isBatchCompatibleStatement(\"INSERT OR IGNORE INTO table VALUES (1);\"));\n\n    assertTrue(jdbc4Stmt.isBatchCompatibleStatement(\"  INSERT INTO table VALUES (1);\"));\n    assertTrue(jdbc4Stmt.isBatchCompatibleStatement(\"\\t\\nINSERT INTO table VALUES (1);\"));\n    assertTrue(jdbc4Stmt.isBatchCompatibleStatement(\"   \\n\\t  INSERT INTO table VALUES (1);\"));\n\n    assertTrue(jdbc4Stmt.isBatchCompatibleStatement(\"/* comment */ INSERT INTO table VALUES (1);\"));\n    assertTrue(\n        jdbc4Stmt.isBatchCompatibleStatement(\n            \"/* multi\\nline\\ncomment */ INSERT INTO table VALUES (1);\"));\n    assertTrue(\n        jdbc4Stmt.isBatchCompatibleStatement(\"-- line comment\\nINSERT INTO table VALUES (1);\"));\n    assertTrue(\n        jdbc4Stmt.isBatchCompatibleStatement(\n            \"-- comment 1\\n-- comment 2\\nINSERT INTO table VALUES (1);\"));\n\n    assertTrue(\n        jdbc4Stmt.isBatchCompatibleStatement(\n            \"  /* comment */ -- another\\n  INSERT INTO table VALUES (1);\"));\n\n    assertTrue(jdbc4Stmt.isBatchCompatibleStatement(\"UPDATE table SET col = 1;\"));\n    assertTrue(jdbc4Stmt.isBatchCompatibleStatement(\"update table set col = 1;\"));\n    assertTrue(\n        jdbc4Stmt.isBatchCompatibleStatement(\"UPDATE table SET col1 = 1, col2 = 2 WHERE id = 3;\"));\n    assertTrue(jdbc4Stmt.isBatchCompatibleStatement(\"UPDATE OR REPLACE table SET col = 1;\"));\n\n    assertTrue(jdbc4Stmt.isBatchCompatibleStatement(\"  UPDATE table SET col = 1;\"));\n    assertTrue(jdbc4Stmt.isBatchCompatibleStatement(\"\\t\\nUPDATE table SET col = 1;\"));\n\n    assertTrue(jdbc4Stmt.isBatchCompatibleStatement(\"/* comment */ UPDATE table SET col = 1;\"));\n    assertTrue(jdbc4Stmt.isBatchCompatibleStatement(\"-- comment\\nUPDATE table SET col = 1;\"));\n\n    assertTrue(jdbc4Stmt.isBatchCompatibleStatement(\"DELETE FROM table;\"));\n    assertTrue(jdbc4Stmt.isBatchCompatibleStatement(\"delete from table;\"));\n    assertTrue(jdbc4Stmt.isBatchCompatibleStatement(\"DELETE FROM table WHERE id = 1;\"));\n\n    assertTrue(jdbc4Stmt.isBatchCompatibleStatement(\"  DELETE FROM table;\"));\n    assertTrue(jdbc4Stmt.isBatchCompatibleStatement(\"\\t\\nDELETE FROM table;\"));\n\n    assertTrue(jdbc4Stmt.isBatchCompatibleStatement(\"/* comment */ DELETE FROM table;\"));\n    assertTrue(jdbc4Stmt.isBatchCompatibleStatement(\"-- comment\\nDELETE FROM table;\"));\n  }\n\n  @Test\n  void testIsBatchCompatibleStatement_non_compatible_statements() {\n    JDBC4Statement jdbc4Stmt = (JDBC4Statement) stmt;\n\n    assertFalse(jdbc4Stmt.isBatchCompatibleStatement(\"SELECT * FROM table;\"));\n    assertFalse(jdbc4Stmt.isBatchCompatibleStatement(\"select * from table;\"));\n    assertFalse(jdbc4Stmt.isBatchCompatibleStatement(\"  SELECT * FROM table;\"));\n    assertFalse(jdbc4Stmt.isBatchCompatibleStatement(\"/* comment */ SELECT * FROM table;\"));\n    assertFalse(jdbc4Stmt.isBatchCompatibleStatement(\"-- comment\\nSELECT * FROM table;\"));\n\n    assertFalse(jdbc4Stmt.isBatchCompatibleStatement(\"EXPLAIN SELECT * FROM table;\"));\n    assertFalse(jdbc4Stmt.isBatchCompatibleStatement(\"EXPLAIN QUERY PLAN SELECT * FROM table;\"));\n\n    assertFalse(jdbc4Stmt.isBatchCompatibleStatement(\"PRAGMA table_info(table);\"));\n    assertFalse(jdbc4Stmt.isBatchCompatibleStatement(\"PRAGMA foreign_keys = ON;\"));\n\n    assertFalse(jdbc4Stmt.isBatchCompatibleStatement(\"ANALYZE;\"));\n    assertFalse(jdbc4Stmt.isBatchCompatibleStatement(\"ANALYZE table;\"));\n\n    assertFalse(\n        jdbc4Stmt.isBatchCompatibleStatement(\n            \"WITH cte AS (SELECT * FROM table) SELECT * FROM cte;\"));\n\n    assertFalse(jdbc4Stmt.isBatchCompatibleStatement(\"VACUUM;\"));\n\n    assertFalse(jdbc4Stmt.isBatchCompatibleStatement(\"VALUES (1, 2), (3, 4);\"));\n  }\n\n  @Test\n  void testIsBatchCompatibleStatement_edge_cases() {\n    JDBC4Statement jdbc4Stmt = (JDBC4Statement) stmt;\n\n    assertFalse(jdbc4Stmt.isBatchCompatibleStatement(null));\n    assertFalse(jdbc4Stmt.isBatchCompatibleStatement(\"\"));\n    assertFalse(jdbc4Stmt.isBatchCompatibleStatement(\"   \"));\n    assertFalse(jdbc4Stmt.isBatchCompatibleStatement(\"\\t\\n\"));\n\n    assertFalse(jdbc4Stmt.isBatchCompatibleStatement(\"/* comment only */\"));\n    assertFalse(jdbc4Stmt.isBatchCompatibleStatement(\"-- comment only\"));\n    assertFalse(jdbc4Stmt.isBatchCompatibleStatement(\"/* comment */ -- another comment\"));\n\n    assertFalse(jdbc4Stmt.isBatchCompatibleStatement(\"SELECT * FROM table WHERE name = 'INSERT';\"));\n    assertFalse(\n        jdbc4Stmt.isBatchCompatibleStatement(\"SELECT * FROM table WHERE action = 'DELETE';\"));\n\n    assertFalse(jdbc4Stmt.isBatchCompatibleStatement(\"INSER INTO table VALUES (1);\"));\n    assertFalse(jdbc4Stmt.isBatchCompatibleStatement(\"UPDAT table SET col = 1;\"));\n    assertFalse(jdbc4Stmt.isBatchCompatibleStatement(\"DELET FROM table;\"));\n  }\n\n  @Test\n  void testIsBatchCompatibleStatement_case_insensitive() {\n    JDBC4Statement jdbc4Stmt = (JDBC4Statement) stmt;\n\n    assertTrue(jdbc4Stmt.isBatchCompatibleStatement(\"Insert INTO table VALUES (1);\"));\n    assertTrue(jdbc4Stmt.isBatchCompatibleStatement(\"InSeRt INTO table VALUES (1);\"));\n    assertTrue(jdbc4Stmt.isBatchCompatibleStatement(\"UPDATE table SET col = 1;\"));\n    assertTrue(jdbc4Stmt.isBatchCompatibleStatement(\"UpDaTe table SET col = 1;\"));\n    assertTrue(jdbc4Stmt.isBatchCompatibleStatement(\"Delete FROM table;\"));\n    assertTrue(jdbc4Stmt.isBatchCompatibleStatement(\"DeLeTe FROM table;\"));\n  }\n}\n"
  },
  {
    "path": "bindings/java/src/test/java/tech/turso/jdbc4/TransactionTest.java",
    "content": "package tech.turso.jdbc4;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\nimport java.sql.*;\nimport java.util.Properties;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport tech.turso.TestUtils;\n\npublic class TransactionTest {\n\n  private Connection connection;\n  private String url;\n\n  @BeforeEach\n  public void setUp() throws Exception {\n    String filePath = TestUtils.createTempFile();\n    url = \"jdbc:turso:\" + filePath;\n    connection = DriverManager.getConnection(url, new Properties());\n  }\n\n  @AfterEach\n  public void tearDown() throws Exception {\n    if (connection != null && !connection.isClosed()) {\n      connection.close();\n    }\n  }\n\n  @Test\n  public void test_default_auto_commit() throws SQLException {\n    assertTrue(connection.getAutoCommit());\n  }\n\n  @Test\n  public void test_commit_rollback_in_auto_commit_mode() {\n    SQLException e1 = assertThrows(SQLException.class, () -> connection.commit());\n    assertTrue(e1.getMessage().contains(\"Cannot commit in autocommit mode\"));\n\n    SQLException e2 = assertThrows(SQLException.class, () -> connection.rollback());\n    assertTrue(e2.getMessage().contains(\"Cannot rollback in autocommit mode\"));\n  }\n\n  @Test\n  public void test_basic_commit() throws SQLException {\n    connection.setAutoCommit(false);\n    assertFalse(connection.getAutoCommit());\n\n    try (Statement stmt = connection.createStatement()) {\n      stmt.execute(\"CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)\");\n      stmt.execute(\"INSERT INTO test (name) VALUES ('Alice')\");\n      connection.commit();\n    }\n\n    // verify data persists in new connection or same connection\n    try (Statement stmt = connection.createStatement();\n        ResultSet rs = stmt.executeQuery(\"SELECT count(*) FROM test\")) {\n      assertTrue(rs.next());\n      assertEquals(1, rs.getInt(1));\n    }\n  }\n\n  @Test\n  public void test_basic_rollback() throws SQLException {\n    try (Statement stmt = connection.createStatement()) {\n      stmt.execute(\"CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)\");\n    }\n\n    connection.setAutoCommit(false);\n\n    try (Statement stmt = connection.createStatement()) {\n      stmt.execute(\"INSERT INTO test (name) VALUES ('Bob')\");\n      connection.rollback();\n    }\n\n    // verify data is gone\n    try (Statement stmt = connection.createStatement();\n        ResultSet rs = stmt.executeQuery(\"SELECT count(*) FROM test\")) {\n      assertTrue(rs.next());\n      assertEquals(0, rs.getInt(1));\n    }\n  }\n\n  @Test\n  public void test_auto_commit_toggle_commits() throws SQLException {\n    try (Statement stmt = connection.createStatement()) {\n      stmt.execute(\"CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)\");\n    }\n\n    connection.setAutoCommit(false);\n    try (Statement stmt = connection.createStatement()) {\n      stmt.execute(\"INSERT INTO test (name) VALUES ('Charlie')\");\n    }\n\n    // Setting autoCommit to true should commit the pending transaction\n    connection.setAutoCommit(true);\n    assertTrue(connection.getAutoCommit());\n\n    try (Statement stmt = connection.createStatement();\n        ResultSet rs = stmt.executeQuery(\"SELECT count(*) FROM test\")) {\n      assertTrue(rs.next());\n      assertEquals(1, rs.getInt(1));\n    }\n  }\n\n  @Test\n  public void test_isolation_level() throws SQLException {\n    int[] levels = {\n      Connection.TRANSACTION_SERIALIZABLE,\n      Connection.TRANSACTION_READ_COMMITTED,\n      Connection.TRANSACTION_READ_UNCOMMITTED,\n      Connection.TRANSACTION_REPEATABLE_READ\n    };\n\n    for (int level : levels) {\n      connection.setTransactionIsolation(level);\n      assertEquals(level, connection.getTransactionIsolation());\n    }\n\n    // Should throw if changing during transaction\n    connection.setAutoCommit(false);\n    connection.setTransactionIsolation(\n        Connection.TRANSACTION_SERIALIZABLE); // Valid (not started yet)\n\n    try (Statement stmt = connection.createStatement()) {\n      stmt.execute(\"CREATE TABLE test (id INTEGER PRIMARY KEY)\"); // starts transaction\n    }\n\n    SQLException e =\n        assertThrows(\n            SQLException.class,\n            () -> connection.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE));\n    assertTrue(\n        e.getMessage().contains(\"Cannot change isolation level while transaction is active\"));\n\n    connection.rollback();\n  }\n\n  @Test\n  public void test_no_op_commit_rollback() throws SQLException {\n    connection.setAutoCommit(false);\n\n    // No transaction active yet (lazy start)\n    // Should be no-op (no exception)\n    connection.commit();\n    connection.rollback();\n\n    // Start transaction\n    try (Statement stmt = connection.createStatement()) {\n      stmt.execute(\"CREATE TABLE test (id INTEGER PRIMARY KEY)\");\n    }\n\n    // Now active, commit it\n    connection.commit();\n\n    // Back to inactive\n    connection.rollback(); // no-op\n  }\n\n  @Test\n  public void test_savepoints_not_supported() throws SQLException {\n    assertThrows(SQLException.class, () -> connection.setSavepoint());\n    assertThrows(SQLException.class, () -> connection.setSavepoint(\"foo\"));\n    assertThrows(SQLException.class, () -> connection.rollback(null));\n    assertThrows(SQLException.class, () -> connection.releaseSavepoint(null));\n  }\n\n  @Test\n  public void test_close_without_commit_rolls_back() throws SQLException {\n    connection.setAutoCommit(false);\n\n    // Create table and insert data\n    try (Statement stmt = connection.createStatement()) {\n      stmt.execute(\"CREATE TABLE test_rollback (id INTEGER PRIMARY KEY, name TEXT)\");\n      stmt.execute(\"INSERT INTO test_rollback (name) VALUES ('rollback_me')\");\n    }\n\n    // Close connection without commit\n    connection.close();\n\n    // Reopen connection to check data\n    try (Connection newConn = DriverManager.getConnection(url, new Properties());\n        Statement stmt = newConn.createStatement()) {\n\n      // Let's expect the table might not exist.\n      SQLException e =\n          assertThrows(SQLException.class, () -> stmt.executeQuery(\"SELECT * FROM test_rollback\"));\n      assertTrue(\n          e.getMessage().contains(\"no such table\"),\n          \"Expected 'no such table' but got: \" + e.getMessage());\n    }\n  }\n\n  @Test\n  public void test_mixed_commit_rollback_scenario() throws SQLException {\n    connection.setAutoCommit(false);\n\n    // Initial schema setup - commit\n    try (Statement stmt = connection.createStatement()) {\n      stmt.execute(\"CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)\");\n    }\n    connection.commit();\n\n    // Insert generic data - commit\n    try (Statement stmt = connection.createStatement()) {\n      stmt.execute(\"INSERT INTO test (name) VALUES ('item_1')\");\n      stmt.execute(\"INSERT INTO test (name) VALUES ('item_2')\");\n    }\n    connection.commit();\n\n    // Insert more data - rollback\n    try (Statement stmt = connection.createStatement()) {\n      stmt.execute(\"INSERT INTO test (name) VALUES ('item_to_rollback')\");\n    }\n    connection.rollback();\n\n    // Verify only committed data exists\n    try (Statement stmt = connection.createStatement();\n        ResultSet rs = stmt.executeQuery(\"SELECT * FROM test ORDER BY id\")) {\n      assertTrue(rs.next());\n      assertEquals(1, rs.getInt(1));\n      assertEquals(\"item_1\", rs.getString(2));\n\n      assertTrue(rs.next());\n      assertEquals(2, rs.getInt(1));\n      assertEquals(\"item_2\", rs.getString(2));\n\n      assertFalse(rs.next(), \"Should only have 2 committed rows\");\n    }\n  }\n\n  @Test\n  public void test_double_close_is_safe() throws SQLException {\n    connection.close();\n    // Second close should be no-op or safe\n    assertDoesNotThrow(() -> connection.close());\n    assertTrue(connection.isClosed());\n  }\n\n  @Test\n  public void test_idempotent_set_auto_commit() throws SQLException {\n    assertTrue(connection.getAutoCommit());\n\n    // Setting same value multiple times\n    connection.setAutoCommit(true);\n    assertTrue(connection.getAutoCommit());\n\n    connection.setAutoCommit(false);\n    assertFalse(connection.getAutoCommit());\n\n    connection.setAutoCommit(false); // Second call\n    assertFalse(connection.getAutoCommit());\n  }\n\n  @Test\n  public void test_manual_transaction_control_passthrough() throws SQLException {\n    // Even if managed by driver, manual BEGIN/COMMIT should pass through\n    // Note: Use with caution as it might desync abstract state\n    try (Statement stmt = connection.createStatement()) {\n      stmt.execute(\"BEGIN\");\n      stmt.execute(\"CREATE TABLE manual_txn (id INT)\");\n      stmt.execute(\"COMMIT\");\n    }\n\n    try (Statement stmt = connection.createStatement();\n        ResultSet rs =\n            stmt.executeQuery(\n                \"SELECT name FROM sqlite_master WHERE type='table' AND name='manual_txn'\")) {\n      assertTrue(rs.next());\n    }\n  }\n}\n"
  },
  {
    "path": "bindings/java/src/test/resources/turso/CACHEDIR.TAG",
    "content": "Signature: 8a477f597d28d172789f06886806bc55\n# This file is a cache directory tag created by cargo.\n# For information about cache directory tags see https://bford.info/cachedir/\n"
  },
  {
    "path": "bindings/javascript/.gitignore",
    "content": "# Created by https://www.toptal.com/developers/gitignore/api/node\n# Edit at https://www.toptal.com/developers/gitignore?templates=node\n\n### Node ###\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n\n# Diagnostic reports (https://nodejs.org/api/report.html)\nreport.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n*.lcov\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# TypeScript v1 declaration files\ntypings/\n\n# TypeScript cache\n*.tsbuildinfo\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Microbundle cache\n.rpt2_cache/\n.rts2_cache_cjs/\n.rts2_cache_es/\n.rts2_cache_umd/\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variables file\n.env\n.env.test\n\n# parcel-bundler cache (https://parceljs.org/)\n.cache\n\n# Next.js build output\n.next\n\n# Nuxt.js build / generate output\n.nuxt\ndist\n\n# Gatsby files\n.cache/\n# Comment in the public line in if your project uses Gatsby and not Next.js\n# https://nextjs.org/blog/next-9-1#public-directory-support\n# public\n\n# vuepress build output\n.vuepress/dist\n\n# Serverless directories\n.serverless/\n\n# FuseBox cache\n.fusebox/\n\n# DynamoDB Local files\n.dynamodb/\n\n# TernJS port file\n.tern-port\n\n# Stores VSCode versions used for testing VSCode extensions\n.vscode-test\n\n# End of https://www.toptal.com/developers/gitignore/api/node\n\n# Created by https://www.toptal.com/developers/gitignore/api/macos\n# Edit at https://www.toptal.com/developers/gitignore?templates=macos\n\n### macOS ###\n# General\n.DS_Store\n.AppleDouble\n.LSOverride\n\n# Icon must end with two\nIcon\n\n\n# Thumbnails\n._*\n\n# Files that might appear in the root of a volume\n.DocumentRevisions-V100\n.fseventsd\n.Spotlight-V100\n.TemporaryItems\n.Trashes\n.VolumeIcon.icns\n.com.apple.timemachine.donotpresent\n\n# Directories potentially created on remote AFP share\n.AppleDB\n.AppleDesktop\nNetwork Trash Folder\nTemporary Items\n.apdisk\n\n### macOS Patch ###\n# iCloud generated files\n*.icloud\n\n# End of https://www.toptal.com/developers/gitignore/api/macos\n\n# Created by https://www.toptal.com/developers/gitignore/api/windows\n# Edit at https://www.toptal.com/developers/gitignore?templates=windows\n\n### Windows ###\n# Windows thumbnail cache files\nThumbs.db\nThumbs.db:encryptable\nehthumbs.db\nehthumbs_vista.db\n\n# Dump file\n*.stackdump\n\n# Folder config file\n[Dd]esktop.ini\n\n# Recycle Bin used on file shares\n$RECYCLE.BIN/\n\n# Windows Installer files\n*.cab\n*.msi\n*.msix\n*.msm\n*.msp\n\n# Windows shortcuts\n*.lnk\n\n# End of https://www.toptal.com/developers/gitignore/api/windows\n\n#Added by cargo\n\n/target\nCargo.lock\n\n.pnp.*\n.yarn/*\n!.yarn/patches\n!.yarn/plugins\n!.yarn/releases\n!.yarn/sdks\n!.yarn/versions\n\n*.node\n*.wasm\n\nnpm\nbundle\n\n*.db\n*.db-wal\n*.db-changes\n*.db-wal-revert\n*.db-info\n\n__screenshots__\n"
  },
  {
    "path": "bindings/javascript/.npmignore",
    "content": "target\nCargo.lock\n.cargo\n.github\nnpm\n.eslintrc\n.prettierignore\nrustfmt.toml\nyarn.lock\n*.node\n.yarn\n__test__\nrenovate.json\nexamples\nperf\n"
  },
  {
    "path": "bindings/javascript/.yarn/releases/yarn-4.9.2.cjs",
    "content": "#!/usr/bin/env node\n/* eslint-disable */\n//prettier-ignore\n(()=>{var UVe=Object.create;var E_=Object.defineProperty;var HVe=Object.getOwnPropertyDescriptor;var jVe=Object.getOwnPropertyNames;var qVe=Object.getPrototypeOf,GVe=Object.prototype.hasOwnProperty;var Ie=(t=>typeof require<\"u\"?require:typeof Proxy<\"u\"?new Proxy(t,{get:(e,r)=>(typeof require<\"u\"?require:e)[r]}):t)(function(t){if(typeof require<\"u\")return require.apply(this,arguments);throw Error('Dynamic require of \"'+t+'\" is not supported')});var Ct=(t,e)=>()=>(t&&(e=t(t=0)),e);var L=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports),Vt=(t,e)=>{for(var r in e)E_(t,r,{get:e[r],enumerable:!0})},WVe=(t,e,r,s)=>{if(e&&typeof e==\"object\"||typeof e==\"function\")for(let a of jVe(e))!GVe.call(t,a)&&a!==r&&E_(t,a,{get:()=>e[a],enumerable:!(s=HVe(e,a))||s.enumerable});return t};var et=(t,e,r)=>(r=t!=null?UVe(qVe(t)):{},WVe(e||!t||!t.__esModule?E_(r,\"default\",{value:t,enumerable:!0}):r,t));var fi={};Vt(fi,{SAFE_TIME:()=>d$,S_IFDIR:()=>rx,S_IFLNK:()=>nx,S_IFMT:()=>_f,S_IFREG:()=>N2});var _f,rx,N2,nx,d$,m$=Ct(()=>{_f=61440,rx=16384,N2=32768,nx=40960,d$=456789e3});var or={};Vt(or,{EBADF:()=>Uo,EBUSY:()=>YVe,EEXIST:()=>XVe,EINVAL:()=>KVe,EISDIR:()=>ZVe,ENOENT:()=>JVe,ENOSYS:()=>VVe,ENOTDIR:()=>zVe,ENOTEMPTY:()=>e7e,EOPNOTSUPP:()=>t7e,EROFS:()=>$Ve,ERR_DIR_CLOSED:()=>I_});function wc(t,e){return Object.assign(new Error(`${t}: ${e}`),{code:t})}function YVe(t){return wc(\"EBUSY\",t)}function VVe(t,e){return wc(\"ENOSYS\",`${t}, ${e}`)}function KVe(t){return wc(\"EINVAL\",`invalid argument, ${t}`)}function Uo(t){return wc(\"EBADF\",`bad file descriptor, ${t}`)}function JVe(t){return wc(\"ENOENT\",`no such file or directory, ${t}`)}function zVe(t){return wc(\"ENOTDIR\",`not a directory, ${t}`)}function ZVe(t){return wc(\"EISDIR\",`illegal operation on a directory, ${t}`)}function XVe(t){return wc(\"EEXIST\",`file already exists, ${t}`)}function $Ve(t){return wc(\"EROFS\",`read-only filesystem, ${t}`)}function e7e(t){return wc(\"ENOTEMPTY\",`directory not empty, ${t}`)}function t7e(t){return wc(\"EOPNOTSUPP\",`operation not supported, ${t}`)}function I_(){return wc(\"ERR_DIR_CLOSED\",\"Directory handle was closed\")}var ix=Ct(()=>{});var el={};Vt(el,{BigIntStatsEntry:()=>rE,DEFAULT_MODE:()=>B_,DirEntry:()=>C_,StatEntry:()=>tE,areStatsEqual:()=>v_,clearStats:()=>sx,convertToBigIntStats:()=>n7e,makeDefaultStats:()=>y$,makeEmptyStats:()=>r7e});function y$(){return new tE}function r7e(){return sx(y$())}function sx(t){for(let e in t)if(Object.hasOwn(t,e)){let r=t[e];typeof r==\"number\"?t[e]=0:typeof r==\"bigint\"?t[e]=BigInt(0):w_.types.isDate(r)&&(t[e]=new Date(0))}return t}function n7e(t){let e=new rE;for(let r in t)if(Object.hasOwn(t,r)){let s=t[r];typeof s==\"number\"?e[r]=BigInt(s):w_.types.isDate(s)&&(e[r]=new Date(s))}return e.atimeNs=e.atimeMs*BigInt(1e6),e.mtimeNs=e.mtimeMs*BigInt(1e6),e.ctimeNs=e.ctimeMs*BigInt(1e6),e.birthtimeNs=e.birthtimeMs*BigInt(1e6),e}function v_(t,e){if(t.atimeMs!==e.atimeMs||t.birthtimeMs!==e.birthtimeMs||t.blksize!==e.blksize||t.blocks!==e.blocks||t.ctimeMs!==e.ctimeMs||t.dev!==e.dev||t.gid!==e.gid||t.ino!==e.ino||t.isBlockDevice()!==e.isBlockDevice()||t.isCharacterDevice()!==e.isCharacterDevice()||t.isDirectory()!==e.isDirectory()||t.isFIFO()!==e.isFIFO()||t.isFile()!==e.isFile()||t.isSocket()!==e.isSocket()||t.isSymbolicLink()!==e.isSymbolicLink()||t.mode!==e.mode||t.mtimeMs!==e.mtimeMs||t.nlink!==e.nlink||t.rdev!==e.rdev||t.size!==e.size||t.uid!==e.uid)return!1;let r=t,s=e;return!(r.atimeNs!==s.atimeNs||r.mtimeNs!==s.mtimeNs||r.ctimeNs!==s.ctimeNs||r.birthtimeNs!==s.birthtimeNs)}var w_,B_,C_,tE,rE,S_=Ct(()=>{w_=et(Ie(\"util\")),B_=33188,C_=class{constructor(){this.name=\"\";this.path=\"\";this.mode=0}isBlockDevice(){return!1}isCharacterDevice(){return!1}isDirectory(){return(this.mode&61440)===16384}isFIFO(){return!1}isFile(){return(this.mode&61440)===32768}isSocket(){return!1}isSymbolicLink(){return(this.mode&61440)===40960}},tE=class{constructor(){this.uid=0;this.gid=0;this.size=0;this.blksize=0;this.atimeMs=0;this.mtimeMs=0;this.ctimeMs=0;this.birthtimeMs=0;this.atime=new Date(0);this.mtime=new Date(0);this.ctime=new Date(0);this.birthtime=new Date(0);this.dev=0;this.ino=0;this.mode=B_;this.nlink=1;this.rdev=0;this.blocks=1}isBlockDevice(){return!1}isCharacterDevice(){return!1}isDirectory(){return(this.mode&61440)===16384}isFIFO(){return!1}isFile(){return(this.mode&61440)===32768}isSocket(){return!1}isSymbolicLink(){return(this.mode&61440)===40960}},rE=class{constructor(){this.uid=BigInt(0);this.gid=BigInt(0);this.size=BigInt(0);this.blksize=BigInt(0);this.atimeMs=BigInt(0);this.mtimeMs=BigInt(0);this.ctimeMs=BigInt(0);this.birthtimeMs=BigInt(0);this.atimeNs=BigInt(0);this.mtimeNs=BigInt(0);this.ctimeNs=BigInt(0);this.birthtimeNs=BigInt(0);this.atime=new Date(0);this.mtime=new Date(0);this.ctime=new Date(0);this.birthtime=new Date(0);this.dev=BigInt(0);this.ino=BigInt(0);this.mode=BigInt(B_);this.nlink=BigInt(1);this.rdev=BigInt(0);this.blocks=BigInt(1)}isBlockDevice(){return!1}isCharacterDevice(){return!1}isDirectory(){return(this.mode&BigInt(61440))===BigInt(16384)}isFIFO(){return!1}isFile(){return(this.mode&BigInt(61440))===BigInt(32768)}isSocket(){return!1}isSymbolicLink(){return(this.mode&BigInt(61440))===BigInt(40960)}}});function l7e(t){let e,r;if(e=t.match(o7e))t=e[1];else if(r=t.match(a7e))t=`\\\\\\\\${r[1]?\".\\\\\":\"\"}${r[2]}`;else return t;return t.replace(/\\//g,\"\\\\\")}function c7e(t){t=t.replace(/\\\\/g,\"/\");let e,r;return(e=t.match(i7e))?t=`/${e[1]}`:(r=t.match(s7e))&&(t=`/unc/${r[1]?\".dot/\":\"\"}${r[2]}`),t}function ox(t,e){return t===ue?I$(e):D_(e)}var O2,vt,Er,ue,K,E$,i7e,s7e,o7e,a7e,D_,I$,tl=Ct(()=>{O2=et(Ie(\"path\")),vt={root:\"/\",dot:\".\",parent:\"..\"},Er={home:\"~\",nodeModules:\"node_modules\",manifest:\"package.json\",lockfile:\"yarn.lock\",virtual:\"__virtual__\",pnpJs:\".pnp.js\",pnpCjs:\".pnp.cjs\",pnpData:\".pnp.data.json\",pnpEsmLoader:\".pnp.loader.mjs\",rc:\".yarnrc.yml\",env:\".env\"},ue=Object.create(O2.default),K=Object.create(O2.default.posix);ue.cwd=()=>process.cwd();K.cwd=process.platform===\"win32\"?()=>D_(process.cwd()):process.cwd;process.platform===\"win32\"&&(K.resolve=(...t)=>t.length>0&&K.isAbsolute(t[0])?O2.default.posix.resolve(...t):O2.default.posix.resolve(K.cwd(),...t));E$=function(t,e,r){return e=t.normalize(e),r=t.normalize(r),e===r?\".\":(e.endsWith(t.sep)||(e=e+t.sep),r.startsWith(e)?r.slice(e.length):null)};ue.contains=(t,e)=>E$(ue,t,e);K.contains=(t,e)=>E$(K,t,e);i7e=/^([a-zA-Z]:.*)$/,s7e=/^\\/\\/(\\.\\/)?(.*)$/,o7e=/^\\/([a-zA-Z]:.*)$/,a7e=/^\\/unc\\/(\\.dot\\/)?(.*)$/;D_=process.platform===\"win32\"?c7e:t=>t,I$=process.platform===\"win32\"?l7e:t=>t;ue.fromPortablePath=I$;ue.toPortablePath=D_});async function ax(t,e){let r=\"0123456789abcdef\";await t.mkdirPromise(e.indexPath,{recursive:!0});let s=[];for(let a of r)for(let n of r)s.push(t.mkdirPromise(t.pathUtils.join(e.indexPath,`${a}${n}`),{recursive:!0}));return await Promise.all(s),e.indexPath}async function C$(t,e,r,s,a){let n=t.pathUtils.normalize(e),c=r.pathUtils.normalize(s),f=[],p=[],{atime:h,mtime:E}=a.stableTime?{atime:gd,mtime:gd}:await r.lstatPromise(c);await t.mkdirpPromise(t.pathUtils.dirname(e),{utimes:[h,E]}),await b_(f,p,t,n,r,c,{...a,didParentExist:!0});for(let C of f)await C();await Promise.all(p.map(C=>C()))}async function b_(t,e,r,s,a,n,c){let f=c.didParentExist?await w$(r,s):null,p=await a.lstatPromise(n),{atime:h,mtime:E}=c.stableTime?{atime:gd,mtime:gd}:p,C;switch(!0){case p.isDirectory():C=await f7e(t,e,r,s,f,a,n,p,c);break;case p.isFile():C=await h7e(t,e,r,s,f,a,n,p,c);break;case p.isSymbolicLink():C=await g7e(t,e,r,s,f,a,n,p,c);break;default:throw new Error(`Unsupported file type (${p.mode})`)}return(c.linkStrategy?.type!==\"HardlinkFromIndex\"||!p.isFile())&&((C||f?.mtime?.getTime()!==E.getTime()||f?.atime?.getTime()!==h.getTime())&&(e.push(()=>r.lutimesPromise(s,h,E)),C=!0),(f===null||(f.mode&511)!==(p.mode&511))&&(e.push(()=>r.chmodPromise(s,p.mode&511)),C=!0)),C}async function w$(t,e){try{return await t.lstatPromise(e)}catch{return null}}async function f7e(t,e,r,s,a,n,c,f,p){if(a!==null&&!a.isDirectory())if(p.overwrite)t.push(async()=>r.removePromise(s)),a=null;else return!1;let h=!1;a===null&&(t.push(async()=>{try{await r.mkdirPromise(s,{mode:f.mode})}catch(S){if(S.code!==\"EEXIST\")throw S}}),h=!0);let E=await n.readdirPromise(c),C=p.didParentExist&&!a?{...p,didParentExist:!1}:p;if(p.stableSort)for(let S of E.sort())await b_(t,e,r,r.pathUtils.join(s,S),n,n.pathUtils.join(c,S),C)&&(h=!0);else(await Promise.all(E.map(async P=>{await b_(t,e,r,r.pathUtils.join(s,P),n,n.pathUtils.join(c,P),C)}))).some(P=>P)&&(h=!0);return h}async function A7e(t,e,r,s,a,n,c,f,p,h){let E=await n.checksumFilePromise(c,{algorithm:\"sha1\"}),C=420,S=f.mode&511,P=`${E}${S!==C?S.toString(8):\"\"}`,I=r.pathUtils.join(h.indexPath,E.slice(0,2),`${P}.dat`),R;(ce=>(ce[ce.Lock=0]=\"Lock\",ce[ce.Rename=1]=\"Rename\"))(R||={});let N=1,U=await w$(r,I);if(a){let ie=U&&a.dev===U.dev&&a.ino===U.ino,Ae=U?.mtimeMs!==u7e;if(ie&&Ae&&h.autoRepair&&(N=0,U=null),!ie)if(p.overwrite)t.push(async()=>r.removePromise(s)),a=null;else return!1}let W=!U&&N===1?`${I}.${Math.floor(Math.random()*4294967296).toString(16).padStart(8,\"0\")}`:null,te=!1;return t.push(async()=>{if(!U&&(N===0&&await r.lockPromise(I,async()=>{let ie=await n.readFilePromise(c);await r.writeFilePromise(I,ie)}),N===1&&W)){let ie=await n.readFilePromise(c);await r.writeFilePromise(W,ie);try{await r.linkPromise(W,I)}catch(Ae){if(Ae.code===\"EEXIST\")te=!0,await r.unlinkPromise(W);else throw Ae}}a||await r.linkPromise(I,s)}),e.push(async()=>{U||(await r.lutimesPromise(I,gd,gd),S!==C&&await r.chmodPromise(I,S)),W&&!te&&await r.unlinkPromise(W)}),!1}async function p7e(t,e,r,s,a,n,c,f,p){if(a!==null)if(p.overwrite)t.push(async()=>r.removePromise(s)),a=null;else return!1;return t.push(async()=>{let h=await n.readFilePromise(c);await r.writeFilePromise(s,h)}),!0}async function h7e(t,e,r,s,a,n,c,f,p){return p.linkStrategy?.type===\"HardlinkFromIndex\"?A7e(t,e,r,s,a,n,c,f,p,p.linkStrategy):p7e(t,e,r,s,a,n,c,f,p)}async function g7e(t,e,r,s,a,n,c,f,p){if(a!==null)if(p.overwrite)t.push(async()=>r.removePromise(s)),a=null;else return!1;return t.push(async()=>{await r.symlinkPromise(ox(r.pathUtils,await n.readlinkPromise(c)),s)}),!0}var gd,u7e,P_=Ct(()=>{tl();gd=new Date(456789e3*1e3),u7e=gd.getTime()});function lx(t,e,r,s){let a=()=>{let n=r.shift();if(typeof n>\"u\")return null;let c=t.pathUtils.join(e,n);return Object.assign(t.statSync(c),{name:n,path:void 0})};return new L2(e,a,s)}var L2,B$=Ct(()=>{ix();L2=class{constructor(e,r,s={}){this.path=e;this.nextDirent=r;this.opts=s;this.closed=!1}throwIfClosed(){if(this.closed)throw I_()}async*[Symbol.asyncIterator](){try{let e;for(;(e=await this.read())!==null;)yield e}finally{await this.close()}}read(e){let r=this.readSync();return typeof e<\"u\"?e(null,r):Promise.resolve(r)}readSync(){return this.throwIfClosed(),this.nextDirent()}close(e){return this.closeSync(),typeof e<\"u\"?e(null):Promise.resolve()}closeSync(){this.throwIfClosed(),this.opts.onClose?.(),this.closed=!0}}});function v$(t,e){if(t!==e)throw new Error(`Invalid StatWatcher status: expected '${e}', got '${t}'`)}var S$,cx,D$=Ct(()=>{S$=Ie(\"events\");S_();cx=class t extends S$.EventEmitter{constructor(r,s,{bigint:a=!1}={}){super();this.status=\"ready\";this.changeListeners=new Map;this.startTimeout=null;this.fakeFs=r,this.path=s,this.bigint=a,this.lastStats=this.stat()}static create(r,s,a){let n=new t(r,s,a);return n.start(),n}start(){v$(this.status,\"ready\"),this.status=\"running\",this.startTimeout=setTimeout(()=>{this.startTimeout=null,this.fakeFs.existsSync(this.path)||this.emit(\"change\",this.lastStats,this.lastStats)},3)}stop(){v$(this.status,\"running\"),this.status=\"stopped\",this.startTimeout!==null&&(clearTimeout(this.startTimeout),this.startTimeout=null),this.emit(\"stop\")}stat(){try{return this.fakeFs.statSync(this.path,{bigint:this.bigint})}catch{let r=this.bigint?new rE:new tE;return sx(r)}}makeInterval(r){let s=setInterval(()=>{let a=this.stat(),n=this.lastStats;v_(a,n)||(this.lastStats=a,this.emit(\"change\",a,n))},r.interval);return r.persistent?s:s.unref()}registerChangeListener(r,s){this.addListener(\"change\",r),this.changeListeners.set(r,this.makeInterval(s))}unregisterChangeListener(r){this.removeListener(\"change\",r);let s=this.changeListeners.get(r);typeof s<\"u\"&&clearInterval(s),this.changeListeners.delete(r)}unregisterAllChangeListeners(){for(let r of this.changeListeners.keys())this.unregisterChangeListener(r)}hasChangeListeners(){return this.changeListeners.size>0}ref(){for(let r of this.changeListeners.values())r.ref();return this}unref(){for(let r of this.changeListeners.values())r.unref();return this}}});function nE(t,e,r,s){let a,n,c,f;switch(typeof r){case\"function\":a=!1,n=!0,c=5007,f=r;break;default:({bigint:a=!1,persistent:n=!0,interval:c=5007}=r),f=s;break}let p=ux.get(t);typeof p>\"u\"&&ux.set(t,p=new Map);let h=p.get(e);return typeof h>\"u\"&&(h=cx.create(t,e,{bigint:a}),p.set(e,h)),h.registerChangeListener(f,{persistent:n,interval:c}),h}function dd(t,e,r){let s=ux.get(t);if(typeof s>\"u\")return;let a=s.get(e);typeof a>\"u\"||(typeof r>\"u\"?a.unregisterAllChangeListeners():a.unregisterChangeListener(r),a.hasChangeListeners()||(a.stop(),s.delete(e)))}function md(t){let e=ux.get(t);if(!(typeof e>\"u\"))for(let r of e.keys())dd(t,r)}var ux,x_=Ct(()=>{D$();ux=new WeakMap});function d7e(t){let e=t.match(/\\r?\\n/g);if(e===null)return P$.EOL;let r=e.filter(a=>a===`\\r\n`).length,s=e.length-r;return r>s?`\\r\n`:`\n`}function yd(t,e){return e.replace(/\\r?\\n/g,d7e(t))}var b$,P$,Ep,Uf,Ed=Ct(()=>{b$=Ie(\"crypto\"),P$=Ie(\"os\");P_();tl();Ep=class{constructor(e){this.pathUtils=e}async*genTraversePromise(e,{stableSort:r=!1}={}){let s=[e];for(;s.length>0;){let a=s.shift();if((await this.lstatPromise(a)).isDirectory()){let c=await this.readdirPromise(a);if(r)for(let f of c.sort())s.push(this.pathUtils.join(a,f));else throw new Error(\"Not supported\")}else yield a}}async checksumFilePromise(e,{algorithm:r=\"sha512\"}={}){let s=await this.openPromise(e,\"r\");try{let n=Buffer.allocUnsafeSlow(65536),c=(0,b$.createHash)(r),f=0;for(;(f=await this.readPromise(s,n,0,65536))!==0;)c.update(f===65536?n:n.slice(0,f));return c.digest(\"hex\")}finally{await this.closePromise(s)}}async removePromise(e,{recursive:r=!0,maxRetries:s=5}={}){let a;try{a=await this.lstatPromise(e)}catch(n){if(n.code===\"ENOENT\")return;throw n}if(a.isDirectory()){if(r){let n=await this.readdirPromise(e);await Promise.all(n.map(c=>this.removePromise(this.pathUtils.resolve(e,c))))}for(let n=0;n<=s;n++)try{await this.rmdirPromise(e);break}catch(c){if(c.code!==\"EBUSY\"&&c.code!==\"ENOTEMPTY\")throw c;n<s&&await new Promise(f=>setTimeout(f,n*100))}}else await this.unlinkPromise(e)}removeSync(e,{recursive:r=!0}={}){let s;try{s=this.lstatSync(e)}catch(a){if(a.code===\"ENOENT\")return;throw a}if(s.isDirectory()){if(r)for(let a of this.readdirSync(e))this.removeSync(this.pathUtils.resolve(e,a));this.rmdirSync(e)}else this.unlinkSync(e)}async mkdirpPromise(e,{chmod:r,utimes:s}={}){if(e=this.resolve(e),e===this.pathUtils.dirname(e))return;let a=e.split(this.pathUtils.sep),n;for(let c=2;c<=a.length;++c){let f=a.slice(0,c).join(this.pathUtils.sep);if(!this.existsSync(f)){try{await this.mkdirPromise(f)}catch(p){if(p.code===\"EEXIST\")continue;throw p}if(n??=f,r!=null&&await this.chmodPromise(f,r),s!=null)await this.utimesPromise(f,s[0],s[1]);else{let p=await this.statPromise(this.pathUtils.dirname(f));await this.utimesPromise(f,p.atime,p.mtime)}}}return n}mkdirpSync(e,{chmod:r,utimes:s}={}){if(e=this.resolve(e),e===this.pathUtils.dirname(e))return;let a=e.split(this.pathUtils.sep),n;for(let c=2;c<=a.length;++c){let f=a.slice(0,c).join(this.pathUtils.sep);if(!this.existsSync(f)){try{this.mkdirSync(f)}catch(p){if(p.code===\"EEXIST\")continue;throw p}if(n??=f,r!=null&&this.chmodSync(f,r),s!=null)this.utimesSync(f,s[0],s[1]);else{let p=this.statSync(this.pathUtils.dirname(f));this.utimesSync(f,p.atime,p.mtime)}}}return n}async copyPromise(e,r,{baseFs:s=this,overwrite:a=!0,stableSort:n=!1,stableTime:c=!1,linkStrategy:f=null}={}){return await C$(this,e,s,r,{overwrite:a,stableSort:n,stableTime:c,linkStrategy:f})}copySync(e,r,{baseFs:s=this,overwrite:a=!0}={}){let n=s.lstatSync(r),c=this.existsSync(e);if(n.isDirectory()){this.mkdirpSync(e);let p=s.readdirSync(r);for(let h of p)this.copySync(this.pathUtils.join(e,h),s.pathUtils.join(r,h),{baseFs:s,overwrite:a})}else if(n.isFile()){if(!c||a){c&&this.removeSync(e);let p=s.readFileSync(r);this.writeFileSync(e,p)}}else if(n.isSymbolicLink()){if(!c||a){c&&this.removeSync(e);let p=s.readlinkSync(r);this.symlinkSync(ox(this.pathUtils,p),e)}}else throw new Error(`Unsupported file type (file: ${r}, mode: 0o${n.mode.toString(8).padStart(6,\"0\")})`);let f=n.mode&511;this.chmodSync(e,f)}async changeFilePromise(e,r,s={}){return Buffer.isBuffer(r)?this.changeFileBufferPromise(e,r,s):this.changeFileTextPromise(e,r,s)}async changeFileBufferPromise(e,r,{mode:s}={}){let a=Buffer.alloc(0);try{a=await this.readFilePromise(e)}catch{}Buffer.compare(a,r)!==0&&await this.writeFilePromise(e,r,{mode:s})}async changeFileTextPromise(e,r,{automaticNewlines:s,mode:a}={}){let n=\"\";try{n=await this.readFilePromise(e,\"utf8\")}catch{}let c=s?yd(n,r):r;n!==c&&await this.writeFilePromise(e,c,{mode:a})}changeFileSync(e,r,s={}){return Buffer.isBuffer(r)?this.changeFileBufferSync(e,r,s):this.changeFileTextSync(e,r,s)}changeFileBufferSync(e,r,{mode:s}={}){let a=Buffer.alloc(0);try{a=this.readFileSync(e)}catch{}Buffer.compare(a,r)!==0&&this.writeFileSync(e,r,{mode:s})}changeFileTextSync(e,r,{automaticNewlines:s=!1,mode:a}={}){let n=\"\";try{n=this.readFileSync(e,\"utf8\")}catch{}let c=s?yd(n,r):r;n!==c&&this.writeFileSync(e,c,{mode:a})}async movePromise(e,r){try{await this.renamePromise(e,r)}catch(s){if(s.code===\"EXDEV\")await this.copyPromise(r,e),await this.removePromise(e);else throw s}}moveSync(e,r){try{this.renameSync(e,r)}catch(s){if(s.code===\"EXDEV\")this.copySync(r,e),this.removeSync(e);else throw s}}async lockPromise(e,r){let s=`${e}.flock`,a=1e3/60,n=Date.now(),c=null,f=async()=>{let p;try{[p]=await this.readJsonPromise(s)}catch{return Date.now()-n<500}try{return process.kill(p,0),!0}catch{return!1}};for(;c===null;)try{c=await this.openPromise(s,\"wx\")}catch(p){if(p.code===\"EEXIST\"){if(!await f())try{await this.unlinkPromise(s);continue}catch{}if(Date.now()-n<60*1e3)await new Promise(h=>setTimeout(h,a));else throw new Error(`Couldn't acquire a lock in a reasonable time (via ${s})`)}else throw p}await this.writePromise(c,JSON.stringify([process.pid]));try{return await r()}finally{try{await this.closePromise(c),await this.unlinkPromise(s)}catch{}}}async readJsonPromise(e){let r=await this.readFilePromise(e,\"utf8\");try{return JSON.parse(r)}catch(s){throw s.message+=` (in ${e})`,s}}readJsonSync(e){let r=this.readFileSync(e,\"utf8\");try{return JSON.parse(r)}catch(s){throw s.message+=` (in ${e})`,s}}async writeJsonPromise(e,r,{compact:s=!1}={}){let a=s?0:2;return await this.writeFilePromise(e,`${JSON.stringify(r,null,a)}\n`)}writeJsonSync(e,r,{compact:s=!1}={}){let a=s?0:2;return this.writeFileSync(e,`${JSON.stringify(r,null,a)}\n`)}async preserveTimePromise(e,r){let s=await this.lstatPromise(e),a=await r();typeof a<\"u\"&&(e=a),await this.lutimesPromise(e,s.atime,s.mtime)}async preserveTimeSync(e,r){let s=this.lstatSync(e),a=r();typeof a<\"u\"&&(e=a),this.lutimesSync(e,s.atime,s.mtime)}},Uf=class extends Ep{constructor(){super(K)}}});var js,Ip=Ct(()=>{Ed();js=class extends Ep{getExtractHint(e){return this.baseFs.getExtractHint(e)}resolve(e){return this.mapFromBase(this.baseFs.resolve(this.mapToBase(e)))}getRealPath(){return this.mapFromBase(this.baseFs.getRealPath())}async openPromise(e,r,s){return this.baseFs.openPromise(this.mapToBase(e),r,s)}openSync(e,r,s){return this.baseFs.openSync(this.mapToBase(e),r,s)}async opendirPromise(e,r){return Object.assign(await this.baseFs.opendirPromise(this.mapToBase(e),r),{path:e})}opendirSync(e,r){return Object.assign(this.baseFs.opendirSync(this.mapToBase(e),r),{path:e})}async readPromise(e,r,s,a,n){return await this.baseFs.readPromise(e,r,s,a,n)}readSync(e,r,s,a,n){return this.baseFs.readSync(e,r,s,a,n)}async writePromise(e,r,s,a,n){return typeof r==\"string\"?await this.baseFs.writePromise(e,r,s):await this.baseFs.writePromise(e,r,s,a,n)}writeSync(e,r,s,a,n){return typeof r==\"string\"?this.baseFs.writeSync(e,r,s):this.baseFs.writeSync(e,r,s,a,n)}async closePromise(e){return this.baseFs.closePromise(e)}closeSync(e){this.baseFs.closeSync(e)}createReadStream(e,r){return this.baseFs.createReadStream(e!==null?this.mapToBase(e):e,r)}createWriteStream(e,r){return this.baseFs.createWriteStream(e!==null?this.mapToBase(e):e,r)}async realpathPromise(e){return this.mapFromBase(await this.baseFs.realpathPromise(this.mapToBase(e)))}realpathSync(e){return this.mapFromBase(this.baseFs.realpathSync(this.mapToBase(e)))}async existsPromise(e){return this.baseFs.existsPromise(this.mapToBase(e))}existsSync(e){return this.baseFs.existsSync(this.mapToBase(e))}accessSync(e,r){return this.baseFs.accessSync(this.mapToBase(e),r)}async accessPromise(e,r){return this.baseFs.accessPromise(this.mapToBase(e),r)}async statPromise(e,r){return this.baseFs.statPromise(this.mapToBase(e),r)}statSync(e,r){return this.baseFs.statSync(this.mapToBase(e),r)}async fstatPromise(e,r){return this.baseFs.fstatPromise(e,r)}fstatSync(e,r){return this.baseFs.fstatSync(e,r)}lstatPromise(e,r){return this.baseFs.lstatPromise(this.mapToBase(e),r)}lstatSync(e,r){return this.baseFs.lstatSync(this.mapToBase(e),r)}async fchmodPromise(e,r){return this.baseFs.fchmodPromise(e,r)}fchmodSync(e,r){return this.baseFs.fchmodSync(e,r)}async chmodPromise(e,r){return this.baseFs.chmodPromise(this.mapToBase(e),r)}chmodSync(e,r){return this.baseFs.chmodSync(this.mapToBase(e),r)}async fchownPromise(e,r,s){return this.baseFs.fchownPromise(e,r,s)}fchownSync(e,r,s){return this.baseFs.fchownSync(e,r,s)}async chownPromise(e,r,s){return this.baseFs.chownPromise(this.mapToBase(e),r,s)}chownSync(e,r,s){return this.baseFs.chownSync(this.mapToBase(e),r,s)}async renamePromise(e,r){return this.baseFs.renamePromise(this.mapToBase(e),this.mapToBase(r))}renameSync(e,r){return this.baseFs.renameSync(this.mapToBase(e),this.mapToBase(r))}async copyFilePromise(e,r,s=0){return this.baseFs.copyFilePromise(this.mapToBase(e),this.mapToBase(r),s)}copyFileSync(e,r,s=0){return this.baseFs.copyFileSync(this.mapToBase(e),this.mapToBase(r),s)}async appendFilePromise(e,r,s){return this.baseFs.appendFilePromise(this.fsMapToBase(e),r,s)}appendFileSync(e,r,s){return this.baseFs.appendFileSync(this.fsMapToBase(e),r,s)}async writeFilePromise(e,r,s){return this.baseFs.writeFilePromise(this.fsMapToBase(e),r,s)}writeFileSync(e,r,s){return this.baseFs.writeFileSync(this.fsMapToBase(e),r,s)}async unlinkPromise(e){return this.baseFs.unlinkPromise(this.mapToBase(e))}unlinkSync(e){return this.baseFs.unlinkSync(this.mapToBase(e))}async utimesPromise(e,r,s){return this.baseFs.utimesPromise(this.mapToBase(e),r,s)}utimesSync(e,r,s){return this.baseFs.utimesSync(this.mapToBase(e),r,s)}async lutimesPromise(e,r,s){return this.baseFs.lutimesPromise(this.mapToBase(e),r,s)}lutimesSync(e,r,s){return this.baseFs.lutimesSync(this.mapToBase(e),r,s)}async mkdirPromise(e,r){return this.baseFs.mkdirPromise(this.mapToBase(e),r)}mkdirSync(e,r){return this.baseFs.mkdirSync(this.mapToBase(e),r)}async rmdirPromise(e,r){return this.baseFs.rmdirPromise(this.mapToBase(e),r)}rmdirSync(e,r){return this.baseFs.rmdirSync(this.mapToBase(e),r)}async rmPromise(e,r){return this.baseFs.rmPromise(this.mapToBase(e),r)}rmSync(e,r){return this.baseFs.rmSync(this.mapToBase(e),r)}async linkPromise(e,r){return this.baseFs.linkPromise(this.mapToBase(e),this.mapToBase(r))}linkSync(e,r){return this.baseFs.linkSync(this.mapToBase(e),this.mapToBase(r))}async symlinkPromise(e,r,s){let a=this.mapToBase(r);if(this.pathUtils.isAbsolute(e))return this.baseFs.symlinkPromise(this.mapToBase(e),a,s);let n=this.mapToBase(this.pathUtils.join(this.pathUtils.dirname(r),e)),c=this.baseFs.pathUtils.relative(this.baseFs.pathUtils.dirname(a),n);return this.baseFs.symlinkPromise(c,a,s)}symlinkSync(e,r,s){let a=this.mapToBase(r);if(this.pathUtils.isAbsolute(e))return this.baseFs.symlinkSync(this.mapToBase(e),a,s);let n=this.mapToBase(this.pathUtils.join(this.pathUtils.dirname(r),e)),c=this.baseFs.pathUtils.relative(this.baseFs.pathUtils.dirname(a),n);return this.baseFs.symlinkSync(c,a,s)}async readFilePromise(e,r){return this.baseFs.readFilePromise(this.fsMapToBase(e),r)}readFileSync(e,r){return this.baseFs.readFileSync(this.fsMapToBase(e),r)}readdirPromise(e,r){return this.baseFs.readdirPromise(this.mapToBase(e),r)}readdirSync(e,r){return this.baseFs.readdirSync(this.mapToBase(e),r)}async readlinkPromise(e){return this.mapFromBase(await this.baseFs.readlinkPromise(this.mapToBase(e)))}readlinkSync(e){return this.mapFromBase(this.baseFs.readlinkSync(this.mapToBase(e)))}async truncatePromise(e,r){return this.baseFs.truncatePromise(this.mapToBase(e),r)}truncateSync(e,r){return this.baseFs.truncateSync(this.mapToBase(e),r)}async ftruncatePromise(e,r){return this.baseFs.ftruncatePromise(e,r)}ftruncateSync(e,r){return this.baseFs.ftruncateSync(e,r)}watch(e,r,s){return this.baseFs.watch(this.mapToBase(e),r,s)}watchFile(e,r,s){return this.baseFs.watchFile(this.mapToBase(e),r,s)}unwatchFile(e,r){return this.baseFs.unwatchFile(this.mapToBase(e),r)}fsMapToBase(e){return typeof e==\"number\"?e:this.mapToBase(e)}}});var Hf,x$=Ct(()=>{Ip();Hf=class extends js{constructor(e,{baseFs:r,pathUtils:s}){super(s),this.target=e,this.baseFs=r}getRealPath(){return this.target}getBaseFs(){return this.baseFs}mapFromBase(e){return e}mapToBase(e){return e}}});function k$(t){let e=t;return typeof t.path==\"string\"&&(e.path=ue.toPortablePath(t.path)),e}var Q$,Yn,Id=Ct(()=>{Q$=et(Ie(\"fs\"));Ed();tl();Yn=class extends Uf{constructor(e=Q$.default){super(),this.realFs=e}getExtractHint(){return!1}getRealPath(){return vt.root}resolve(e){return K.resolve(e)}async openPromise(e,r,s){return await new Promise((a,n)=>{this.realFs.open(ue.fromPortablePath(e),r,s,this.makeCallback(a,n))})}openSync(e,r,s){return this.realFs.openSync(ue.fromPortablePath(e),r,s)}async opendirPromise(e,r){return await new Promise((s,a)=>{typeof r<\"u\"?this.realFs.opendir(ue.fromPortablePath(e),r,this.makeCallback(s,a)):this.realFs.opendir(ue.fromPortablePath(e),this.makeCallback(s,a))}).then(s=>{let a=s;return Object.defineProperty(a,\"path\",{value:e,configurable:!0,writable:!0}),a})}opendirSync(e,r){let a=typeof r<\"u\"?this.realFs.opendirSync(ue.fromPortablePath(e),r):this.realFs.opendirSync(ue.fromPortablePath(e));return Object.defineProperty(a,\"path\",{value:e,configurable:!0,writable:!0}),a}async readPromise(e,r,s=0,a=0,n=-1){return await new Promise((c,f)=>{this.realFs.read(e,r,s,a,n,(p,h)=>{p?f(p):c(h)})})}readSync(e,r,s,a,n){return this.realFs.readSync(e,r,s,a,n)}async writePromise(e,r,s,a,n){return await new Promise((c,f)=>typeof r==\"string\"?this.realFs.write(e,r,s,this.makeCallback(c,f)):this.realFs.write(e,r,s,a,n,this.makeCallback(c,f)))}writeSync(e,r,s,a,n){return typeof r==\"string\"?this.realFs.writeSync(e,r,s):this.realFs.writeSync(e,r,s,a,n)}async closePromise(e){await new Promise((r,s)=>{this.realFs.close(e,this.makeCallback(r,s))})}closeSync(e){this.realFs.closeSync(e)}createReadStream(e,r){let s=e!==null?ue.fromPortablePath(e):e;return this.realFs.createReadStream(s,r)}createWriteStream(e,r){let s=e!==null?ue.fromPortablePath(e):e;return this.realFs.createWriteStream(s,r)}async realpathPromise(e){return await new Promise((r,s)=>{this.realFs.realpath(ue.fromPortablePath(e),{},this.makeCallback(r,s))}).then(r=>ue.toPortablePath(r))}realpathSync(e){return ue.toPortablePath(this.realFs.realpathSync(ue.fromPortablePath(e),{}))}async existsPromise(e){return await new Promise(r=>{this.realFs.exists(ue.fromPortablePath(e),r)})}accessSync(e,r){return this.realFs.accessSync(ue.fromPortablePath(e),r)}async accessPromise(e,r){return await new Promise((s,a)=>{this.realFs.access(ue.fromPortablePath(e),r,this.makeCallback(s,a))})}existsSync(e){return this.realFs.existsSync(ue.fromPortablePath(e))}async statPromise(e,r){return await new Promise((s,a)=>{r?this.realFs.stat(ue.fromPortablePath(e),r,this.makeCallback(s,a)):this.realFs.stat(ue.fromPortablePath(e),this.makeCallback(s,a))})}statSync(e,r){return r?this.realFs.statSync(ue.fromPortablePath(e),r):this.realFs.statSync(ue.fromPortablePath(e))}async fstatPromise(e,r){return await new Promise((s,a)=>{r?this.realFs.fstat(e,r,this.makeCallback(s,a)):this.realFs.fstat(e,this.makeCallback(s,a))})}fstatSync(e,r){return r?this.realFs.fstatSync(e,r):this.realFs.fstatSync(e)}async lstatPromise(e,r){return await new Promise((s,a)=>{r?this.realFs.lstat(ue.fromPortablePath(e),r,this.makeCallback(s,a)):this.realFs.lstat(ue.fromPortablePath(e),this.makeCallback(s,a))})}lstatSync(e,r){return r?this.realFs.lstatSync(ue.fromPortablePath(e),r):this.realFs.lstatSync(ue.fromPortablePath(e))}async fchmodPromise(e,r){return await new Promise((s,a)=>{this.realFs.fchmod(e,r,this.makeCallback(s,a))})}fchmodSync(e,r){return this.realFs.fchmodSync(e,r)}async chmodPromise(e,r){return await new Promise((s,a)=>{this.realFs.chmod(ue.fromPortablePath(e),r,this.makeCallback(s,a))})}chmodSync(e,r){return this.realFs.chmodSync(ue.fromPortablePath(e),r)}async fchownPromise(e,r,s){return await new Promise((a,n)=>{this.realFs.fchown(e,r,s,this.makeCallback(a,n))})}fchownSync(e,r,s){return this.realFs.fchownSync(e,r,s)}async chownPromise(e,r,s){return await new Promise((a,n)=>{this.realFs.chown(ue.fromPortablePath(e),r,s,this.makeCallback(a,n))})}chownSync(e,r,s){return this.realFs.chownSync(ue.fromPortablePath(e),r,s)}async renamePromise(e,r){return await new Promise((s,a)=>{this.realFs.rename(ue.fromPortablePath(e),ue.fromPortablePath(r),this.makeCallback(s,a))})}renameSync(e,r){return this.realFs.renameSync(ue.fromPortablePath(e),ue.fromPortablePath(r))}async copyFilePromise(e,r,s=0){return await new Promise((a,n)=>{this.realFs.copyFile(ue.fromPortablePath(e),ue.fromPortablePath(r),s,this.makeCallback(a,n))})}copyFileSync(e,r,s=0){return this.realFs.copyFileSync(ue.fromPortablePath(e),ue.fromPortablePath(r),s)}async appendFilePromise(e,r,s){return await new Promise((a,n)=>{let c=typeof e==\"string\"?ue.fromPortablePath(e):e;s?this.realFs.appendFile(c,r,s,this.makeCallback(a,n)):this.realFs.appendFile(c,r,this.makeCallback(a,n))})}appendFileSync(e,r,s){let a=typeof e==\"string\"?ue.fromPortablePath(e):e;s?this.realFs.appendFileSync(a,r,s):this.realFs.appendFileSync(a,r)}async writeFilePromise(e,r,s){return await new Promise((a,n)=>{let c=typeof e==\"string\"?ue.fromPortablePath(e):e;s?this.realFs.writeFile(c,r,s,this.makeCallback(a,n)):this.realFs.writeFile(c,r,this.makeCallback(a,n))})}writeFileSync(e,r,s){let a=typeof e==\"string\"?ue.fromPortablePath(e):e;s?this.realFs.writeFileSync(a,r,s):this.realFs.writeFileSync(a,r)}async unlinkPromise(e){return await new Promise((r,s)=>{this.realFs.unlink(ue.fromPortablePath(e),this.makeCallback(r,s))})}unlinkSync(e){return this.realFs.unlinkSync(ue.fromPortablePath(e))}async utimesPromise(e,r,s){return await new Promise((a,n)=>{this.realFs.utimes(ue.fromPortablePath(e),r,s,this.makeCallback(a,n))})}utimesSync(e,r,s){this.realFs.utimesSync(ue.fromPortablePath(e),r,s)}async lutimesPromise(e,r,s){return await new Promise((a,n)=>{this.realFs.lutimes(ue.fromPortablePath(e),r,s,this.makeCallback(a,n))})}lutimesSync(e,r,s){this.realFs.lutimesSync(ue.fromPortablePath(e),r,s)}async mkdirPromise(e,r){return await new Promise((s,a)=>{this.realFs.mkdir(ue.fromPortablePath(e),r,this.makeCallback(s,a))})}mkdirSync(e,r){return this.realFs.mkdirSync(ue.fromPortablePath(e),r)}async rmdirPromise(e,r){return await new Promise((s,a)=>{r?this.realFs.rmdir(ue.fromPortablePath(e),r,this.makeCallback(s,a)):this.realFs.rmdir(ue.fromPortablePath(e),this.makeCallback(s,a))})}rmdirSync(e,r){return this.realFs.rmdirSync(ue.fromPortablePath(e),r)}async rmPromise(e,r){return await new Promise((s,a)=>{r?this.realFs.rm(ue.fromPortablePath(e),r,this.makeCallback(s,a)):this.realFs.rm(ue.fromPortablePath(e),this.makeCallback(s,a))})}rmSync(e,r){return this.realFs.rmSync(ue.fromPortablePath(e),r)}async linkPromise(e,r){return await new Promise((s,a)=>{this.realFs.link(ue.fromPortablePath(e),ue.fromPortablePath(r),this.makeCallback(s,a))})}linkSync(e,r){return this.realFs.linkSync(ue.fromPortablePath(e),ue.fromPortablePath(r))}async symlinkPromise(e,r,s){return await new Promise((a,n)=>{this.realFs.symlink(ue.fromPortablePath(e.replace(/\\/+$/,\"\")),ue.fromPortablePath(r),s,this.makeCallback(a,n))})}symlinkSync(e,r,s){return this.realFs.symlinkSync(ue.fromPortablePath(e.replace(/\\/+$/,\"\")),ue.fromPortablePath(r),s)}async readFilePromise(e,r){return await new Promise((s,a)=>{let n=typeof e==\"string\"?ue.fromPortablePath(e):e;this.realFs.readFile(n,r,this.makeCallback(s,a))})}readFileSync(e,r){let s=typeof e==\"string\"?ue.fromPortablePath(e):e;return this.realFs.readFileSync(s,r)}async readdirPromise(e,r){return await new Promise((s,a)=>{r?r.recursive&&process.platform===\"win32\"?r.withFileTypes?this.realFs.readdir(ue.fromPortablePath(e),r,this.makeCallback(n=>s(n.map(k$)),a)):this.realFs.readdir(ue.fromPortablePath(e),r,this.makeCallback(n=>s(n.map(ue.toPortablePath)),a)):this.realFs.readdir(ue.fromPortablePath(e),r,this.makeCallback(s,a)):this.realFs.readdir(ue.fromPortablePath(e),this.makeCallback(s,a))})}readdirSync(e,r){return r?r.recursive&&process.platform===\"win32\"?r.withFileTypes?this.realFs.readdirSync(ue.fromPortablePath(e),r).map(k$):this.realFs.readdirSync(ue.fromPortablePath(e),r).map(ue.toPortablePath):this.realFs.readdirSync(ue.fromPortablePath(e),r):this.realFs.readdirSync(ue.fromPortablePath(e))}async readlinkPromise(e){return await new Promise((r,s)=>{this.realFs.readlink(ue.fromPortablePath(e),this.makeCallback(r,s))}).then(r=>ue.toPortablePath(r))}readlinkSync(e){return ue.toPortablePath(this.realFs.readlinkSync(ue.fromPortablePath(e)))}async truncatePromise(e,r){return await new Promise((s,a)=>{this.realFs.truncate(ue.fromPortablePath(e),r,this.makeCallback(s,a))})}truncateSync(e,r){return this.realFs.truncateSync(ue.fromPortablePath(e),r)}async ftruncatePromise(e,r){return await new Promise((s,a)=>{this.realFs.ftruncate(e,r,this.makeCallback(s,a))})}ftruncateSync(e,r){return this.realFs.ftruncateSync(e,r)}watch(e,r,s){return this.realFs.watch(ue.fromPortablePath(e),r,s)}watchFile(e,r,s){return this.realFs.watchFile(ue.fromPortablePath(e),r,s)}unwatchFile(e,r){return this.realFs.unwatchFile(ue.fromPortablePath(e),r)}makeCallback(e,r){return(s,a)=>{s?r(s):e(a)}}}});var Sn,T$=Ct(()=>{Id();Ip();tl();Sn=class extends js{constructor(e,{baseFs:r=new Yn}={}){super(K),this.target=this.pathUtils.normalize(e),this.baseFs=r}getRealPath(){return this.pathUtils.resolve(this.baseFs.getRealPath(),this.target)}resolve(e){return this.pathUtils.isAbsolute(e)?K.normalize(e):this.baseFs.resolve(K.join(this.target,e))}mapFromBase(e){return e}mapToBase(e){return this.pathUtils.isAbsolute(e)?e:this.pathUtils.join(this.target,e)}}});var R$,jf,F$=Ct(()=>{Id();Ip();tl();R$=vt.root,jf=class extends js{constructor(e,{baseFs:r=new Yn}={}){super(K),this.target=this.pathUtils.resolve(vt.root,e),this.baseFs=r}getRealPath(){return this.pathUtils.resolve(this.baseFs.getRealPath(),this.pathUtils.relative(vt.root,this.target))}getTarget(){return this.target}getBaseFs(){return this.baseFs}mapToBase(e){let r=this.pathUtils.normalize(e);if(this.pathUtils.isAbsolute(e))return this.pathUtils.resolve(this.target,this.pathUtils.relative(R$,e));if(r.match(/^\\.\\.\\/?/))throw new Error(`Resolving this path (${e}) would escape the jail`);return this.pathUtils.resolve(this.target,e)}mapFromBase(e){return this.pathUtils.resolve(R$,this.pathUtils.relative(this.target,e))}}});var iE,N$=Ct(()=>{Ip();iE=class extends js{constructor(r,s){super(s);this.instance=null;this.factory=r}get baseFs(){return this.instance||(this.instance=this.factory()),this.instance}set baseFs(r){this.instance=r}mapFromBase(r){return r}mapToBase(r){return r}}});var Cd,rl,r0,O$=Ct(()=>{Cd=Ie(\"fs\");Ed();Id();x_();ix();tl();rl=4278190080,r0=class extends Uf{constructor({baseFs:r=new Yn,filter:s=null,magicByte:a=42,maxOpenFiles:n=1/0,useCache:c=!0,maxAge:f=5e3,typeCheck:p=Cd.constants.S_IFREG,getMountPoint:h,factoryPromise:E,factorySync:C}){if(Math.floor(a)!==a||!(a>1&&a<=127))throw new Error(\"The magic byte must be set to a round value between 1 and 127 included\");super();this.fdMap=new Map;this.nextFd=3;this.isMount=new Set;this.notMount=new Set;this.realPaths=new Map;this.limitOpenFilesTimeout=null;this.baseFs=r,this.mountInstances=c?new Map:null,this.factoryPromise=E,this.factorySync=C,this.filter=s,this.getMountPoint=h,this.magic=a<<24,this.maxAge=f,this.maxOpenFiles=n,this.typeCheck=p}getExtractHint(r){return this.baseFs.getExtractHint(r)}getRealPath(){return this.baseFs.getRealPath()}saveAndClose(){if(md(this),this.mountInstances)for(let[r,{childFs:s}]of this.mountInstances.entries())s.saveAndClose?.(),this.mountInstances.delete(r)}discardAndClose(){if(md(this),this.mountInstances)for(let[r,{childFs:s}]of this.mountInstances.entries())s.discardAndClose?.(),this.mountInstances.delete(r)}resolve(r){return this.baseFs.resolve(r)}remapFd(r,s){let a=this.nextFd++|this.magic;return this.fdMap.set(a,[r,s]),a}async openPromise(r,s,a){return await this.makeCallPromise(r,async()=>await this.baseFs.openPromise(r,s,a),async(n,{subPath:c})=>this.remapFd(n,await n.openPromise(c,s,a)))}openSync(r,s,a){return this.makeCallSync(r,()=>this.baseFs.openSync(r,s,a),(n,{subPath:c})=>this.remapFd(n,n.openSync(c,s,a)))}async opendirPromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.opendirPromise(r,s),async(a,{subPath:n})=>await a.opendirPromise(n,s),{requireSubpath:!1})}opendirSync(r,s){return this.makeCallSync(r,()=>this.baseFs.opendirSync(r,s),(a,{subPath:n})=>a.opendirSync(n,s),{requireSubpath:!1})}async readPromise(r,s,a,n,c){if((r&rl)!==this.magic)return await this.baseFs.readPromise(r,s,a,n,c);let f=this.fdMap.get(r);if(typeof f>\"u\")throw Uo(\"read\");let[p,h]=f;return await p.readPromise(h,s,a,n,c)}readSync(r,s,a,n,c){if((r&rl)!==this.magic)return this.baseFs.readSync(r,s,a,n,c);let f=this.fdMap.get(r);if(typeof f>\"u\")throw Uo(\"readSync\");let[p,h]=f;return p.readSync(h,s,a,n,c)}async writePromise(r,s,a,n,c){if((r&rl)!==this.magic)return typeof s==\"string\"?await this.baseFs.writePromise(r,s,a):await this.baseFs.writePromise(r,s,a,n,c);let f=this.fdMap.get(r);if(typeof f>\"u\")throw Uo(\"write\");let[p,h]=f;return typeof s==\"string\"?await p.writePromise(h,s,a):await p.writePromise(h,s,a,n,c)}writeSync(r,s,a,n,c){if((r&rl)!==this.magic)return typeof s==\"string\"?this.baseFs.writeSync(r,s,a):this.baseFs.writeSync(r,s,a,n,c);let f=this.fdMap.get(r);if(typeof f>\"u\")throw Uo(\"writeSync\");let[p,h]=f;return typeof s==\"string\"?p.writeSync(h,s,a):p.writeSync(h,s,a,n,c)}async closePromise(r){if((r&rl)!==this.magic)return await this.baseFs.closePromise(r);let s=this.fdMap.get(r);if(typeof s>\"u\")throw Uo(\"close\");this.fdMap.delete(r);let[a,n]=s;return await a.closePromise(n)}closeSync(r){if((r&rl)!==this.magic)return this.baseFs.closeSync(r);let s=this.fdMap.get(r);if(typeof s>\"u\")throw Uo(\"closeSync\");this.fdMap.delete(r);let[a,n]=s;return a.closeSync(n)}createReadStream(r,s){return r===null?this.baseFs.createReadStream(r,s):this.makeCallSync(r,()=>this.baseFs.createReadStream(r,s),(a,{archivePath:n,subPath:c})=>{let f=a.createReadStream(c,s);return f.path=ue.fromPortablePath(this.pathUtils.join(n,c)),f})}createWriteStream(r,s){return r===null?this.baseFs.createWriteStream(r,s):this.makeCallSync(r,()=>this.baseFs.createWriteStream(r,s),(a,{subPath:n})=>a.createWriteStream(n,s))}async realpathPromise(r){return await this.makeCallPromise(r,async()=>await this.baseFs.realpathPromise(r),async(s,{archivePath:a,subPath:n})=>{let c=this.realPaths.get(a);return typeof c>\"u\"&&(c=await this.baseFs.realpathPromise(a),this.realPaths.set(a,c)),this.pathUtils.join(c,this.pathUtils.relative(vt.root,await s.realpathPromise(n)))})}realpathSync(r){return this.makeCallSync(r,()=>this.baseFs.realpathSync(r),(s,{archivePath:a,subPath:n})=>{let c=this.realPaths.get(a);return typeof c>\"u\"&&(c=this.baseFs.realpathSync(a),this.realPaths.set(a,c)),this.pathUtils.join(c,this.pathUtils.relative(vt.root,s.realpathSync(n)))})}async existsPromise(r){return await this.makeCallPromise(r,async()=>await this.baseFs.existsPromise(r),async(s,{subPath:a})=>await s.existsPromise(a))}existsSync(r){return this.makeCallSync(r,()=>this.baseFs.existsSync(r),(s,{subPath:a})=>s.existsSync(a))}async accessPromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.accessPromise(r,s),async(a,{subPath:n})=>await a.accessPromise(n,s))}accessSync(r,s){return this.makeCallSync(r,()=>this.baseFs.accessSync(r,s),(a,{subPath:n})=>a.accessSync(n,s))}async statPromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.statPromise(r,s),async(a,{subPath:n})=>await a.statPromise(n,s))}statSync(r,s){return this.makeCallSync(r,()=>this.baseFs.statSync(r,s),(a,{subPath:n})=>a.statSync(n,s))}async fstatPromise(r,s){if((r&rl)!==this.magic)return this.baseFs.fstatPromise(r,s);let a=this.fdMap.get(r);if(typeof a>\"u\")throw Uo(\"fstat\");let[n,c]=a;return n.fstatPromise(c,s)}fstatSync(r,s){if((r&rl)!==this.magic)return this.baseFs.fstatSync(r,s);let a=this.fdMap.get(r);if(typeof a>\"u\")throw Uo(\"fstatSync\");let[n,c]=a;return n.fstatSync(c,s)}async lstatPromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.lstatPromise(r,s),async(a,{subPath:n})=>await a.lstatPromise(n,s))}lstatSync(r,s){return this.makeCallSync(r,()=>this.baseFs.lstatSync(r,s),(a,{subPath:n})=>a.lstatSync(n,s))}async fchmodPromise(r,s){if((r&rl)!==this.magic)return this.baseFs.fchmodPromise(r,s);let a=this.fdMap.get(r);if(typeof a>\"u\")throw Uo(\"fchmod\");let[n,c]=a;return n.fchmodPromise(c,s)}fchmodSync(r,s){if((r&rl)!==this.magic)return this.baseFs.fchmodSync(r,s);let a=this.fdMap.get(r);if(typeof a>\"u\")throw Uo(\"fchmodSync\");let[n,c]=a;return n.fchmodSync(c,s)}async chmodPromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.chmodPromise(r,s),async(a,{subPath:n})=>await a.chmodPromise(n,s))}chmodSync(r,s){return this.makeCallSync(r,()=>this.baseFs.chmodSync(r,s),(a,{subPath:n})=>a.chmodSync(n,s))}async fchownPromise(r,s,a){if((r&rl)!==this.magic)return this.baseFs.fchownPromise(r,s,a);let n=this.fdMap.get(r);if(typeof n>\"u\")throw Uo(\"fchown\");let[c,f]=n;return c.fchownPromise(f,s,a)}fchownSync(r,s,a){if((r&rl)!==this.magic)return this.baseFs.fchownSync(r,s,a);let n=this.fdMap.get(r);if(typeof n>\"u\")throw Uo(\"fchownSync\");let[c,f]=n;return c.fchownSync(f,s,a)}async chownPromise(r,s,a){return await this.makeCallPromise(r,async()=>await this.baseFs.chownPromise(r,s,a),async(n,{subPath:c})=>await n.chownPromise(c,s,a))}chownSync(r,s,a){return this.makeCallSync(r,()=>this.baseFs.chownSync(r,s,a),(n,{subPath:c})=>n.chownSync(c,s,a))}async renamePromise(r,s){return await this.makeCallPromise(r,async()=>await this.makeCallPromise(s,async()=>await this.baseFs.renamePromise(r,s),async()=>{throw Object.assign(new Error(\"EEXDEV: cross-device link not permitted\"),{code:\"EEXDEV\"})}),async(a,{subPath:n})=>await this.makeCallPromise(s,async()=>{throw Object.assign(new Error(\"EEXDEV: cross-device link not permitted\"),{code:\"EEXDEV\"})},async(c,{subPath:f})=>{if(a!==c)throw Object.assign(new Error(\"EEXDEV: cross-device link not permitted\"),{code:\"EEXDEV\"});return await a.renamePromise(n,f)}))}renameSync(r,s){return this.makeCallSync(r,()=>this.makeCallSync(s,()=>this.baseFs.renameSync(r,s),()=>{throw Object.assign(new Error(\"EEXDEV: cross-device link not permitted\"),{code:\"EEXDEV\"})}),(a,{subPath:n})=>this.makeCallSync(s,()=>{throw Object.assign(new Error(\"EEXDEV: cross-device link not permitted\"),{code:\"EEXDEV\"})},(c,{subPath:f})=>{if(a!==c)throw Object.assign(new Error(\"EEXDEV: cross-device link not permitted\"),{code:\"EEXDEV\"});return a.renameSync(n,f)}))}async copyFilePromise(r,s,a=0){let n=async(c,f,p,h)=>{if(a&Cd.constants.COPYFILE_FICLONE_FORCE)throw Object.assign(new Error(`EXDEV: cross-device clone not permitted, copyfile '${f}' -> ${h}'`),{code:\"EXDEV\"});if(a&Cd.constants.COPYFILE_EXCL&&await this.existsPromise(f))throw Object.assign(new Error(`EEXIST: file already exists, copyfile '${f}' -> '${h}'`),{code:\"EEXIST\"});let E;try{E=await c.readFilePromise(f)}catch{throw Object.assign(new Error(`EINVAL: invalid argument, copyfile '${f}' -> '${h}'`),{code:\"EINVAL\"})}await p.writeFilePromise(h,E)};return await this.makeCallPromise(r,async()=>await this.makeCallPromise(s,async()=>await this.baseFs.copyFilePromise(r,s,a),async(c,{subPath:f})=>await n(this.baseFs,r,c,f)),async(c,{subPath:f})=>await this.makeCallPromise(s,async()=>await n(c,f,this.baseFs,s),async(p,{subPath:h})=>c!==p?await n(c,f,p,h):await c.copyFilePromise(f,h,a)))}copyFileSync(r,s,a=0){let n=(c,f,p,h)=>{if(a&Cd.constants.COPYFILE_FICLONE_FORCE)throw Object.assign(new Error(`EXDEV: cross-device clone not permitted, copyfile '${f}' -> ${h}'`),{code:\"EXDEV\"});if(a&Cd.constants.COPYFILE_EXCL&&this.existsSync(f))throw Object.assign(new Error(`EEXIST: file already exists, copyfile '${f}' -> '${h}'`),{code:\"EEXIST\"});let E;try{E=c.readFileSync(f)}catch{throw Object.assign(new Error(`EINVAL: invalid argument, copyfile '${f}' -> '${h}'`),{code:\"EINVAL\"})}p.writeFileSync(h,E)};return this.makeCallSync(r,()=>this.makeCallSync(s,()=>this.baseFs.copyFileSync(r,s,a),(c,{subPath:f})=>n(this.baseFs,r,c,f)),(c,{subPath:f})=>this.makeCallSync(s,()=>n(c,f,this.baseFs,s),(p,{subPath:h})=>c!==p?n(c,f,p,h):c.copyFileSync(f,h,a)))}async appendFilePromise(r,s,a){return await this.makeCallPromise(r,async()=>await this.baseFs.appendFilePromise(r,s,a),async(n,{subPath:c})=>await n.appendFilePromise(c,s,a))}appendFileSync(r,s,a){return this.makeCallSync(r,()=>this.baseFs.appendFileSync(r,s,a),(n,{subPath:c})=>n.appendFileSync(c,s,a))}async writeFilePromise(r,s,a){return await this.makeCallPromise(r,async()=>await this.baseFs.writeFilePromise(r,s,a),async(n,{subPath:c})=>await n.writeFilePromise(c,s,a))}writeFileSync(r,s,a){return this.makeCallSync(r,()=>this.baseFs.writeFileSync(r,s,a),(n,{subPath:c})=>n.writeFileSync(c,s,a))}async unlinkPromise(r){return await this.makeCallPromise(r,async()=>await this.baseFs.unlinkPromise(r),async(s,{subPath:a})=>await s.unlinkPromise(a))}unlinkSync(r){return this.makeCallSync(r,()=>this.baseFs.unlinkSync(r),(s,{subPath:a})=>s.unlinkSync(a))}async utimesPromise(r,s,a){return await this.makeCallPromise(r,async()=>await this.baseFs.utimesPromise(r,s,a),async(n,{subPath:c})=>await n.utimesPromise(c,s,a))}utimesSync(r,s,a){return this.makeCallSync(r,()=>this.baseFs.utimesSync(r,s,a),(n,{subPath:c})=>n.utimesSync(c,s,a))}async lutimesPromise(r,s,a){return await this.makeCallPromise(r,async()=>await this.baseFs.lutimesPromise(r,s,a),async(n,{subPath:c})=>await n.lutimesPromise(c,s,a))}lutimesSync(r,s,a){return this.makeCallSync(r,()=>this.baseFs.lutimesSync(r,s,a),(n,{subPath:c})=>n.lutimesSync(c,s,a))}async mkdirPromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.mkdirPromise(r,s),async(a,{subPath:n})=>await a.mkdirPromise(n,s))}mkdirSync(r,s){return this.makeCallSync(r,()=>this.baseFs.mkdirSync(r,s),(a,{subPath:n})=>a.mkdirSync(n,s))}async rmdirPromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.rmdirPromise(r,s),async(a,{subPath:n})=>await a.rmdirPromise(n,s))}rmdirSync(r,s){return this.makeCallSync(r,()=>this.baseFs.rmdirSync(r,s),(a,{subPath:n})=>a.rmdirSync(n,s))}async rmPromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.rmPromise(r,s),async(a,{subPath:n})=>await a.rmPromise(n,s))}rmSync(r,s){return this.makeCallSync(r,()=>this.baseFs.rmSync(r,s),(a,{subPath:n})=>a.rmSync(n,s))}async linkPromise(r,s){return await this.makeCallPromise(s,async()=>await this.baseFs.linkPromise(r,s),async(a,{subPath:n})=>await a.linkPromise(r,n))}linkSync(r,s){return this.makeCallSync(s,()=>this.baseFs.linkSync(r,s),(a,{subPath:n})=>a.linkSync(r,n))}async symlinkPromise(r,s,a){return await this.makeCallPromise(s,async()=>await this.baseFs.symlinkPromise(r,s,a),async(n,{subPath:c})=>await n.symlinkPromise(r,c))}symlinkSync(r,s,a){return this.makeCallSync(s,()=>this.baseFs.symlinkSync(r,s,a),(n,{subPath:c})=>n.symlinkSync(r,c))}async readFilePromise(r,s){return this.makeCallPromise(r,async()=>await this.baseFs.readFilePromise(r,s),async(a,{subPath:n})=>await a.readFilePromise(n,s))}readFileSync(r,s){return this.makeCallSync(r,()=>this.baseFs.readFileSync(r,s),(a,{subPath:n})=>a.readFileSync(n,s))}async readdirPromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.readdirPromise(r,s),async(a,{subPath:n})=>await a.readdirPromise(n,s),{requireSubpath:!1})}readdirSync(r,s){return this.makeCallSync(r,()=>this.baseFs.readdirSync(r,s),(a,{subPath:n})=>a.readdirSync(n,s),{requireSubpath:!1})}async readlinkPromise(r){return await this.makeCallPromise(r,async()=>await this.baseFs.readlinkPromise(r),async(s,{subPath:a})=>await s.readlinkPromise(a))}readlinkSync(r){return this.makeCallSync(r,()=>this.baseFs.readlinkSync(r),(s,{subPath:a})=>s.readlinkSync(a))}async truncatePromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.truncatePromise(r,s),async(a,{subPath:n})=>await a.truncatePromise(n,s))}truncateSync(r,s){return this.makeCallSync(r,()=>this.baseFs.truncateSync(r,s),(a,{subPath:n})=>a.truncateSync(n,s))}async ftruncatePromise(r,s){if((r&rl)!==this.magic)return this.baseFs.ftruncatePromise(r,s);let a=this.fdMap.get(r);if(typeof a>\"u\")throw Uo(\"ftruncate\");let[n,c]=a;return n.ftruncatePromise(c,s)}ftruncateSync(r,s){if((r&rl)!==this.magic)return this.baseFs.ftruncateSync(r,s);let a=this.fdMap.get(r);if(typeof a>\"u\")throw Uo(\"ftruncateSync\");let[n,c]=a;return n.ftruncateSync(c,s)}watch(r,s,a){return this.makeCallSync(r,()=>this.baseFs.watch(r,s,a),(n,{subPath:c})=>n.watch(c,s,a))}watchFile(r,s,a){return this.makeCallSync(r,()=>this.baseFs.watchFile(r,s,a),()=>nE(this,r,s,a))}unwatchFile(r,s){return this.makeCallSync(r,()=>this.baseFs.unwatchFile(r,s),()=>dd(this,r,s))}async makeCallPromise(r,s,a,{requireSubpath:n=!0}={}){if(typeof r!=\"string\")return await s();let c=this.resolve(r),f=this.findMount(c);return f?n&&f.subPath===\"/\"?await s():await this.getMountPromise(f.archivePath,async p=>await a(p,f)):await s()}makeCallSync(r,s,a,{requireSubpath:n=!0}={}){if(typeof r!=\"string\")return s();let c=this.resolve(r),f=this.findMount(c);return!f||n&&f.subPath===\"/\"?s():this.getMountSync(f.archivePath,p=>a(p,f))}findMount(r){if(this.filter&&!this.filter.test(r))return null;let s=\"\";for(;;){let a=r.substring(s.length),n=this.getMountPoint(a,s);if(!n)return null;if(s=this.pathUtils.join(s,n),!this.isMount.has(s)){if(this.notMount.has(s))continue;try{if(this.typeCheck!==null&&(this.baseFs.statSync(s).mode&Cd.constants.S_IFMT)!==this.typeCheck){this.notMount.add(s);continue}}catch{return null}this.isMount.add(s)}return{archivePath:s,subPath:this.pathUtils.join(vt.root,r.substring(s.length))}}}limitOpenFiles(r){if(this.mountInstances===null)return;let s=Date.now(),a=s+this.maxAge,n=r===null?0:this.mountInstances.size-r;for(let[c,{childFs:f,expiresAt:p,refCount:h}]of this.mountInstances.entries())if(!(h!==0||f.hasOpenFileHandles?.())){if(s>=p){f.saveAndClose?.(),this.mountInstances.delete(c),n-=1;continue}else if(r===null||n<=0){a=p;break}f.saveAndClose?.(),this.mountInstances.delete(c),n-=1}this.limitOpenFilesTimeout===null&&(r===null&&this.mountInstances.size>0||r!==null)&&isFinite(a)&&(this.limitOpenFilesTimeout=setTimeout(()=>{this.limitOpenFilesTimeout=null,this.limitOpenFiles(null)},a-s).unref())}async getMountPromise(r,s){if(this.mountInstances){let a=this.mountInstances.get(r);if(!a){let n=await this.factoryPromise(this.baseFs,r);a=this.mountInstances.get(r),a||(a={childFs:n(),expiresAt:0,refCount:0})}this.mountInstances.delete(r),this.limitOpenFiles(this.maxOpenFiles-1),this.mountInstances.set(r,a),a.expiresAt=Date.now()+this.maxAge,a.refCount+=1;try{return await s(a.childFs)}finally{a.refCount-=1}}else{let a=(await this.factoryPromise(this.baseFs,r))();try{return await s(a)}finally{a.saveAndClose?.()}}}getMountSync(r,s){if(this.mountInstances){let a=this.mountInstances.get(r);return a||(a={childFs:this.factorySync(this.baseFs,r),expiresAt:0,refCount:0}),this.mountInstances.delete(r),this.limitOpenFiles(this.maxOpenFiles-1),this.mountInstances.set(r,a),a.expiresAt=Date.now()+this.maxAge,s(a.childFs)}else{let a=this.factorySync(this.baseFs,r);try{return s(a)}finally{a.saveAndClose?.()}}}}});var er,fx,L$=Ct(()=>{Ed();tl();er=()=>Object.assign(new Error(\"ENOSYS: unsupported filesystem access\"),{code:\"ENOSYS\"}),fx=class t extends Ep{static{this.instance=new t}constructor(){super(K)}getExtractHint(){throw er()}getRealPath(){throw er()}resolve(){throw er()}async openPromise(){throw er()}openSync(){throw er()}async opendirPromise(){throw er()}opendirSync(){throw er()}async readPromise(){throw er()}readSync(){throw er()}async writePromise(){throw er()}writeSync(){throw er()}async closePromise(){throw er()}closeSync(){throw er()}createWriteStream(){throw er()}createReadStream(){throw er()}async realpathPromise(){throw er()}realpathSync(){throw er()}async readdirPromise(){throw er()}readdirSync(){throw er()}async existsPromise(e){throw er()}existsSync(e){throw er()}async accessPromise(){throw er()}accessSync(){throw er()}async statPromise(){throw er()}statSync(){throw er()}async fstatPromise(e){throw er()}fstatSync(e){throw er()}async lstatPromise(e){throw er()}lstatSync(e){throw er()}async fchmodPromise(){throw er()}fchmodSync(){throw er()}async chmodPromise(){throw er()}chmodSync(){throw er()}async fchownPromise(){throw er()}fchownSync(){throw er()}async chownPromise(){throw er()}chownSync(){throw er()}async mkdirPromise(){throw er()}mkdirSync(){throw er()}async rmdirPromise(){throw er()}rmdirSync(){throw er()}async rmPromise(){throw er()}rmSync(){throw er()}async linkPromise(){throw er()}linkSync(){throw er()}async symlinkPromise(){throw er()}symlinkSync(){throw er()}async renamePromise(){throw er()}renameSync(){throw er()}async copyFilePromise(){throw er()}copyFileSync(){throw er()}async appendFilePromise(){throw er()}appendFileSync(){throw er()}async writeFilePromise(){throw er()}writeFileSync(){throw er()}async unlinkPromise(){throw er()}unlinkSync(){throw er()}async utimesPromise(){throw er()}utimesSync(){throw er()}async lutimesPromise(){throw er()}lutimesSync(){throw er()}async readFilePromise(){throw er()}readFileSync(){throw er()}async readlinkPromise(){throw er()}readlinkSync(){throw er()}async truncatePromise(){throw er()}truncateSync(){throw er()}async ftruncatePromise(e,r){throw er()}ftruncateSync(e,r){throw er()}watch(){throw er()}watchFile(){throw er()}unwatchFile(){throw er()}}});var n0,M$=Ct(()=>{Ip();tl();n0=class extends js{constructor(e){super(ue),this.baseFs=e}mapFromBase(e){return ue.fromPortablePath(e)}mapToBase(e){return ue.toPortablePath(e)}}});var m7e,k_,y7e,Ao,_$=Ct(()=>{Id();Ip();tl();m7e=/^[0-9]+$/,k_=/^(\\/(?:[^/]+\\/)*?(?:\\$\\$virtual|__virtual__))((?:\\/((?:[^/]+-)?[a-f0-9]+)(?:\\/([^/]+))?)?((?:\\/.*)?))$/,y7e=/^([^/]+-)?[a-f0-9]+$/,Ao=class t extends js{static makeVirtualPath(e,r,s){if(K.basename(e)!==\"__virtual__\")throw new Error('Assertion failed: Virtual folders must be named \"__virtual__\"');if(!K.basename(r).match(y7e))throw new Error(\"Assertion failed: Virtual components must be ended by an hexadecimal hash\");let n=K.relative(K.dirname(e),s).split(\"/\"),c=0;for(;c<n.length&&n[c]===\"..\";)c+=1;let f=n.slice(c);return K.join(e,r,String(c),...f)}static resolveVirtual(e){let r=e.match(k_);if(!r||!r[3]&&r[5])return e;let s=K.dirname(r[1]);if(!r[3]||!r[4])return s;if(!m7e.test(r[4]))return e;let n=Number(r[4]),c=\"../\".repeat(n),f=r[5]||\".\";return t.resolveVirtual(K.join(s,c,f))}constructor({baseFs:e=new Yn}={}){super(K),this.baseFs=e}getExtractHint(e){return this.baseFs.getExtractHint(e)}getRealPath(){return this.baseFs.getRealPath()}realpathSync(e){let r=e.match(k_);if(!r)return this.baseFs.realpathSync(e);if(!r[5])return e;let s=this.baseFs.realpathSync(this.mapToBase(e));return t.makeVirtualPath(r[1],r[3],s)}async realpathPromise(e){let r=e.match(k_);if(!r)return await this.baseFs.realpathPromise(e);if(!r[5])return e;let s=await this.baseFs.realpathPromise(this.mapToBase(e));return t.makeVirtualPath(r[1],r[3],s)}mapToBase(e){if(e===\"\")return e;if(this.pathUtils.isAbsolute(e))return t.resolveVirtual(e);let r=t.resolveVirtual(this.baseFs.resolve(vt.dot)),s=t.resolveVirtual(this.baseFs.resolve(e));return K.relative(r,s)||vt.dot}mapFromBase(e){return e}}});function E7e(t,e){return typeof Q_.default.isUtf8<\"u\"?Q_.default.isUtf8(t):Buffer.byteLength(e)===t.byteLength}var Q_,U$,H$,Ax,j$=Ct(()=>{Q_=et(Ie(\"buffer\")),U$=Ie(\"url\"),H$=Ie(\"util\");Ip();tl();Ax=class extends js{constructor(e){super(ue),this.baseFs=e}mapFromBase(e){return e}mapToBase(e){if(typeof e==\"string\")return e;if(e instanceof URL)return(0,U$.fileURLToPath)(e);if(Buffer.isBuffer(e)){let r=e.toString();if(!E7e(e,r))throw new Error(\"Non-utf8 buffers are not supported at the moment. Please upvote the following issue if you encounter this error: https://github.com/yarnpkg/berry/issues/4942\");return r}throw new Error(`Unsupported path type: ${(0,H$.inspect)(e)}`)}}});var V$,Ho,Cp,i0,px,hx,sE,Nu,Ou,q$,G$,W$,Y$,M2,K$=Ct(()=>{V$=Ie(\"readline\"),Ho=Symbol(\"kBaseFs\"),Cp=Symbol(\"kFd\"),i0=Symbol(\"kClosePromise\"),px=Symbol(\"kCloseResolve\"),hx=Symbol(\"kCloseReject\"),sE=Symbol(\"kRefs\"),Nu=Symbol(\"kRef\"),Ou=Symbol(\"kUnref\"),M2=class{constructor(e,r){this[Y$]=1;this[W$]=void 0;this[G$]=void 0;this[q$]=void 0;this[Ho]=r,this[Cp]=e}get fd(){return this[Cp]}async appendFile(e,r){try{this[Nu](this.appendFile);let s=(typeof r==\"string\"?r:r?.encoding)??void 0;return await this[Ho].appendFilePromise(this.fd,e,s?{encoding:s}:void 0)}finally{this[Ou]()}}async chown(e,r){try{return this[Nu](this.chown),await this[Ho].fchownPromise(this.fd,e,r)}finally{this[Ou]()}}async chmod(e){try{return this[Nu](this.chmod),await this[Ho].fchmodPromise(this.fd,e)}finally{this[Ou]()}}createReadStream(e){return this[Ho].createReadStream(null,{...e,fd:this.fd})}createWriteStream(e){return this[Ho].createWriteStream(null,{...e,fd:this.fd})}datasync(){throw new Error(\"Method not implemented.\")}sync(){throw new Error(\"Method not implemented.\")}async read(e,r,s,a){try{this[Nu](this.read);let n;return Buffer.isBuffer(e)?n=e:(e??={},n=e.buffer??Buffer.alloc(16384),r=e.offset||0,s=e.length??n.byteLength,a=e.position??null),r??=0,s??=0,s===0?{bytesRead:s,buffer:n}:{bytesRead:await this[Ho].readPromise(this.fd,n,r,s,a),buffer:n}}finally{this[Ou]()}}async readFile(e){try{this[Nu](this.readFile);let r=(typeof e==\"string\"?e:e?.encoding)??void 0;return await this[Ho].readFilePromise(this.fd,r)}finally{this[Ou]()}}readLines(e){return(0,V$.createInterface)({input:this.createReadStream(e),crlfDelay:1/0})}async stat(e){try{return this[Nu](this.stat),await this[Ho].fstatPromise(this.fd,e)}finally{this[Ou]()}}async truncate(e){try{return this[Nu](this.truncate),await this[Ho].ftruncatePromise(this.fd,e)}finally{this[Ou]()}}utimes(e,r){throw new Error(\"Method not implemented.\")}async writeFile(e,r){try{this[Nu](this.writeFile);let s=(typeof r==\"string\"?r:r?.encoding)??void 0;await this[Ho].writeFilePromise(this.fd,e,s)}finally{this[Ou]()}}async write(...e){try{if(this[Nu](this.write),ArrayBuffer.isView(e[0])){let[r,s,a,n]=e;return{bytesWritten:await this[Ho].writePromise(this.fd,r,s??void 0,a??void 0,n??void 0),buffer:r}}else{let[r,s,a]=e;return{bytesWritten:await this[Ho].writePromise(this.fd,r,s,a),buffer:r}}}finally{this[Ou]()}}async writev(e,r){try{this[Nu](this.writev);let s=0;if(typeof r<\"u\")for(let a of e){let n=await this.write(a,void 0,void 0,r);s+=n.bytesWritten,r+=n.bytesWritten}else for(let a of e){let n=await this.write(a);s+=n.bytesWritten}return{buffers:e,bytesWritten:s}}finally{this[Ou]()}}readv(e,r){throw new Error(\"Method not implemented.\")}close(){if(this[Cp]===-1)return Promise.resolve();if(this[i0])return this[i0];if(this[sE]--,this[sE]===0){let e=this[Cp];this[Cp]=-1,this[i0]=this[Ho].closePromise(e).finally(()=>{this[i0]=void 0})}else this[i0]=new Promise((e,r)=>{this[px]=e,this[hx]=r}).finally(()=>{this[i0]=void 0,this[hx]=void 0,this[px]=void 0});return this[i0]}[(Ho,Cp,Y$=sE,W$=i0,G$=px,q$=hx,Nu)](e){if(this[Cp]===-1){let r=new Error(\"file closed\");throw r.code=\"EBADF\",r.syscall=e.name,r}this[sE]++}[Ou](){if(this[sE]--,this[sE]===0){let e=this[Cp];this[Cp]=-1,this[Ho].closePromise(e).then(this[px],this[hx])}}}});function _2(t,e){e=new Ax(e);let r=(s,a,n)=>{let c=s[a];s[a]=n,typeof c?.[oE.promisify.custom]<\"u\"&&(n[oE.promisify.custom]=c[oE.promisify.custom])};{r(t,\"exists\",(s,...a)=>{let c=typeof a[a.length-1]==\"function\"?a.pop():()=>{};process.nextTick(()=>{e.existsPromise(s).then(f=>{c(f)},()=>{c(!1)})})}),r(t,\"read\",(...s)=>{let[a,n,c,f,p,h]=s;if(s.length<=3){let E={};s.length<3?h=s[1]:(E=s[1],h=s[2]),{buffer:n=Buffer.alloc(16384),offset:c=0,length:f=n.byteLength,position:p}=E}if(c==null&&(c=0),f|=0,f===0){process.nextTick(()=>{h(null,0,n)});return}p==null&&(p=-1),process.nextTick(()=>{e.readPromise(a,n,c,f,p).then(E=>{h(null,E,n)},E=>{h(E,0,n)})})});for(let s of J$){let a=s.replace(/Promise$/,\"\");if(typeof t[a]>\"u\")continue;let n=e[s];if(typeof n>\"u\")continue;r(t,a,(...f)=>{let h=typeof f[f.length-1]==\"function\"?f.pop():()=>{};process.nextTick(()=>{n.apply(e,f).then(E=>{h(null,E)},E=>{h(E)})})})}t.realpath.native=t.realpath}{r(t,\"existsSync\",s=>{try{return e.existsSync(s)}catch{return!1}}),r(t,\"readSync\",(...s)=>{let[a,n,c,f,p]=s;return s.length<=3&&({offset:c=0,length:f=n.byteLength,position:p}=s[2]||{}),c==null&&(c=0),f|=0,f===0?0:(p==null&&(p=-1),e.readSync(a,n,c,f,p))});for(let s of I7e){let a=s;if(typeof t[a]>\"u\")continue;let n=e[s];typeof n>\"u\"||r(t,a,n.bind(e))}t.realpathSync.native=t.realpathSync}{let s=t.promises;for(let a of J$){let n=a.replace(/Promise$/,\"\");if(typeof s[n]>\"u\")continue;let c=e[a];typeof c>\"u\"||a!==\"open\"&&r(s,n,(f,...p)=>f instanceof M2?f[n].apply(f,p):c.call(e,f,...p))}r(s,\"open\",async(...a)=>{let n=await e.openPromise(...a);return new M2(n,e)})}t.read[oE.promisify.custom]=async(s,a,...n)=>({bytesRead:await e.readPromise(s,a,...n),buffer:a}),t.write[oE.promisify.custom]=async(s,a,...n)=>({bytesWritten:await e.writePromise(s,a,...n),buffer:a})}function gx(t,e){let r=Object.create(t);return _2(r,e),r}var oE,I7e,J$,z$=Ct(()=>{oE=Ie(\"util\");j$();K$();I7e=new Set([\"accessSync\",\"appendFileSync\",\"createReadStream\",\"createWriteStream\",\"chmodSync\",\"fchmodSync\",\"chownSync\",\"fchownSync\",\"closeSync\",\"copyFileSync\",\"linkSync\",\"lstatSync\",\"fstatSync\",\"lutimesSync\",\"mkdirSync\",\"openSync\",\"opendirSync\",\"readlinkSync\",\"readFileSync\",\"readdirSync\",\"readlinkSync\",\"realpathSync\",\"renameSync\",\"rmdirSync\",\"rmSync\",\"statSync\",\"symlinkSync\",\"truncateSync\",\"ftruncateSync\",\"unlinkSync\",\"unwatchFile\",\"utimesSync\",\"watch\",\"watchFile\",\"writeFileSync\",\"writeSync\"]),J$=new Set([\"accessPromise\",\"appendFilePromise\",\"fchmodPromise\",\"chmodPromise\",\"fchownPromise\",\"chownPromise\",\"closePromise\",\"copyFilePromise\",\"linkPromise\",\"fstatPromise\",\"lstatPromise\",\"lutimesPromise\",\"mkdirPromise\",\"openPromise\",\"opendirPromise\",\"readdirPromise\",\"realpathPromise\",\"readFilePromise\",\"readdirPromise\",\"readlinkPromise\",\"renamePromise\",\"rmdirPromise\",\"rmPromise\",\"statPromise\",\"symlinkPromise\",\"truncatePromise\",\"ftruncatePromise\",\"unlinkPromise\",\"utimesPromise\",\"writeFilePromise\",\"writeSync\"])});function Z$(t){let e=Math.ceil(Math.random()*4294967296).toString(16).padStart(8,\"0\");return`${t}${e}`}function X$(){if(T_)return T_;let t=ue.toPortablePath($$.default.tmpdir()),e=le.realpathSync(t);return process.once(\"exit\",()=>{le.rmtempSync()}),T_={tmpdir:t,realTmpdir:e}}var $$,Lu,T_,le,eee=Ct(()=>{$$=et(Ie(\"os\"));Id();tl();Lu=new Set,T_=null;le=Object.assign(new Yn,{detachTemp(t){Lu.delete(t)},mktempSync(t){let{tmpdir:e,realTmpdir:r}=X$();for(;;){let s=Z$(\"xfs-\");try{this.mkdirSync(K.join(e,s))}catch(n){if(n.code===\"EEXIST\")continue;throw n}let a=K.join(r,s);if(Lu.add(a),typeof t>\"u\")return a;try{return t(a)}finally{if(Lu.has(a)){Lu.delete(a);try{this.removeSync(a)}catch{}}}}},async mktempPromise(t){let{tmpdir:e,realTmpdir:r}=X$();for(;;){let s=Z$(\"xfs-\");try{await this.mkdirPromise(K.join(e,s))}catch(n){if(n.code===\"EEXIST\")continue;throw n}let a=K.join(r,s);if(Lu.add(a),typeof t>\"u\")return a;try{return await t(a)}finally{if(Lu.has(a)){Lu.delete(a);try{await this.removePromise(a)}catch{}}}}},async rmtempPromise(){await Promise.all(Array.from(Lu.values()).map(async t=>{try{await le.removePromise(t,{maxRetries:0}),Lu.delete(t)}catch{}}))},rmtempSync(){for(let t of Lu)try{le.removeSync(t),Lu.delete(t)}catch{}}})});var U2={};Vt(U2,{AliasFS:()=>Hf,BasePortableFakeFS:()=>Uf,CustomDir:()=>L2,CwdFS:()=>Sn,FakeFS:()=>Ep,Filename:()=>Er,JailFS:()=>jf,LazyFS:()=>iE,MountFS:()=>r0,NoFS:()=>fx,NodeFS:()=>Yn,PortablePath:()=>vt,PosixFS:()=>n0,ProxiedFS:()=>js,VirtualFS:()=>Ao,constants:()=>fi,errors:()=>or,extendFs:()=>gx,normalizeLineEndings:()=>yd,npath:()=>ue,opendir:()=>lx,patchFs:()=>_2,ppath:()=>K,setupCopyIndex:()=>ax,statUtils:()=>el,unwatchAllFiles:()=>md,unwatchFile:()=>dd,watchFile:()=>nE,xfs:()=>le});var bt=Ct(()=>{m$();ix();S_();P_();B$();x_();Ed();tl();tl();x$();Ed();T$();F$();N$();O$();L$();Id();M$();Ip();_$();z$();eee()});var see=L((wGt,iee)=>{iee.exports=nee;nee.sync=w7e;var tee=Ie(\"fs\");function C7e(t,e){var r=e.pathExt!==void 0?e.pathExt:process.env.PATHEXT;if(!r||(r=r.split(\";\"),r.indexOf(\"\")!==-1))return!0;for(var s=0;s<r.length;s++){var a=r[s].toLowerCase();if(a&&t.substr(-a.length).toLowerCase()===a)return!0}return!1}function ree(t,e,r){return!t.isSymbolicLink()&&!t.isFile()?!1:C7e(e,r)}function nee(t,e,r){tee.stat(t,function(s,a){r(s,s?!1:ree(a,t,e))})}function w7e(t,e){return ree(tee.statSync(t),t,e)}});var uee=L((BGt,cee)=>{cee.exports=aee;aee.sync=B7e;var oee=Ie(\"fs\");function aee(t,e,r){oee.stat(t,function(s,a){r(s,s?!1:lee(a,e))})}function B7e(t,e){return lee(oee.statSync(t),e)}function lee(t,e){return t.isFile()&&v7e(t,e)}function v7e(t,e){var r=t.mode,s=t.uid,a=t.gid,n=e.uid!==void 0?e.uid:process.getuid&&process.getuid(),c=e.gid!==void 0?e.gid:process.getgid&&process.getgid(),f=parseInt(\"100\",8),p=parseInt(\"010\",8),h=parseInt(\"001\",8),E=f|p,C=r&h||r&p&&a===c||r&f&&s===n||r&E&&n===0;return C}});var Aee=L((SGt,fee)=>{var vGt=Ie(\"fs\"),dx;process.platform===\"win32\"||global.TESTING_WINDOWS?dx=see():dx=uee();fee.exports=R_;R_.sync=S7e;function R_(t,e,r){if(typeof e==\"function\"&&(r=e,e={}),!r){if(typeof Promise!=\"function\")throw new TypeError(\"callback not provided\");return new Promise(function(s,a){R_(t,e||{},function(n,c){n?a(n):s(c)})})}dx(t,e||{},function(s,a){s&&(s.code===\"EACCES\"||e&&e.ignoreErrors)&&(s=null,a=!1),r(s,a)})}function S7e(t,e){try{return dx.sync(t,e||{})}catch(r){if(e&&e.ignoreErrors||r.code===\"EACCES\")return!1;throw r}}});var Eee=L((DGt,yee)=>{var aE=process.platform===\"win32\"||process.env.OSTYPE===\"cygwin\"||process.env.OSTYPE===\"msys\",pee=Ie(\"path\"),D7e=aE?\";\":\":\",hee=Aee(),gee=t=>Object.assign(new Error(`not found: ${t}`),{code:\"ENOENT\"}),dee=(t,e)=>{let r=e.colon||D7e,s=t.match(/\\//)||aE&&t.match(/\\\\/)?[\"\"]:[...aE?[process.cwd()]:[],...(e.path||process.env.PATH||\"\").split(r)],a=aE?e.pathExt||process.env.PATHEXT||\".EXE;.CMD;.BAT;.COM\":\"\",n=aE?a.split(r):[\"\"];return aE&&t.indexOf(\".\")!==-1&&n[0]!==\"\"&&n.unshift(\"\"),{pathEnv:s,pathExt:n,pathExtExe:a}},mee=(t,e,r)=>{typeof e==\"function\"&&(r=e,e={}),e||(e={});let{pathEnv:s,pathExt:a,pathExtExe:n}=dee(t,e),c=[],f=h=>new Promise((E,C)=>{if(h===s.length)return e.all&&c.length?E(c):C(gee(t));let S=s[h],P=/^\".*\"$/.test(S)?S.slice(1,-1):S,I=pee.join(P,t),R=!P&&/^\\.[\\\\\\/]/.test(t)?t.slice(0,2)+I:I;E(p(R,h,0))}),p=(h,E,C)=>new Promise((S,P)=>{if(C===a.length)return S(f(E+1));let I=a[C];hee(h+I,{pathExt:n},(R,N)=>{if(!R&&N)if(e.all)c.push(h+I);else return S(h+I);return S(p(h,E,C+1))})});return r?f(0).then(h=>r(null,h),r):f(0)},b7e=(t,e)=>{e=e||{};let{pathEnv:r,pathExt:s,pathExtExe:a}=dee(t,e),n=[];for(let c=0;c<r.length;c++){let f=r[c],p=/^\".*\"$/.test(f)?f.slice(1,-1):f,h=pee.join(p,t),E=!p&&/^\\.[\\\\\\/]/.test(t)?t.slice(0,2)+h:h;for(let C=0;C<s.length;C++){let S=E+s[C];try{if(hee.sync(S,{pathExt:a}))if(e.all)n.push(S);else return S}catch{}}}if(e.all&&n.length)return n;if(e.nothrow)return null;throw gee(t)};yee.exports=mee;mee.sync=b7e});var Cee=L((bGt,F_)=>{\"use strict\";var Iee=(t={})=>{let e=t.env||process.env;return(t.platform||process.platform)!==\"win32\"?\"PATH\":Object.keys(e).reverse().find(s=>s.toUpperCase()===\"PATH\")||\"Path\"};F_.exports=Iee;F_.exports.default=Iee});var See=L((PGt,vee)=>{\"use strict\";var wee=Ie(\"path\"),P7e=Eee(),x7e=Cee();function Bee(t,e){let r=t.options.env||process.env,s=process.cwd(),a=t.options.cwd!=null,n=a&&process.chdir!==void 0&&!process.chdir.disabled;if(n)try{process.chdir(t.options.cwd)}catch{}let c;try{c=P7e.sync(t.command,{path:r[x7e({env:r})],pathExt:e?wee.delimiter:void 0})}catch{}finally{n&&process.chdir(s)}return c&&(c=wee.resolve(a?t.options.cwd:\"\",c)),c}function k7e(t){return Bee(t)||Bee(t,!0)}vee.exports=k7e});var Dee=L((xGt,O_)=>{\"use strict\";var N_=/([()\\][%!^\"`<>&|;, *?])/g;function Q7e(t){return t=t.replace(N_,\"^$1\"),t}function T7e(t,e){return t=`${t}`,t=t.replace(/(?=(\\\\+?)?)\\1\"/g,'$1$1\\\\\"'),t=t.replace(/(?=(\\\\+?)?)\\1$/,\"$1$1\"),t=`\"${t}\"`,t=t.replace(N_,\"^$1\"),e&&(t=t.replace(N_,\"^$1\")),t}O_.exports.command=Q7e;O_.exports.argument=T7e});var Pee=L((kGt,bee)=>{\"use strict\";bee.exports=/^#!(.*)/});var kee=L((QGt,xee)=>{\"use strict\";var R7e=Pee();xee.exports=(t=\"\")=>{let e=t.match(R7e);if(!e)return null;let[r,s]=e[0].replace(/#! ?/,\"\").split(\" \"),a=r.split(\"/\").pop();return a===\"env\"?s:s?`${a} ${s}`:a}});var Tee=L((TGt,Qee)=>{\"use strict\";var L_=Ie(\"fs\"),F7e=kee();function N7e(t){let r=Buffer.alloc(150),s;try{s=L_.openSync(t,\"r\"),L_.readSync(s,r,0,150,0),L_.closeSync(s)}catch{}return F7e(r.toString())}Qee.exports=N7e});var Oee=L((RGt,Nee)=>{\"use strict\";var O7e=Ie(\"path\"),Ree=See(),Fee=Dee(),L7e=Tee(),M7e=process.platform===\"win32\",_7e=/\\.(?:com|exe)$/i,U7e=/node_modules[\\\\/].bin[\\\\/][^\\\\/]+\\.cmd$/i;function H7e(t){t.file=Ree(t);let e=t.file&&L7e(t.file);return e?(t.args.unshift(t.file),t.command=e,Ree(t)):t.file}function j7e(t){if(!M7e)return t;let e=H7e(t),r=!_7e.test(e);if(t.options.forceShell||r){let s=U7e.test(e);t.command=O7e.normalize(t.command),t.command=Fee.command(t.command),t.args=t.args.map(n=>Fee.argument(n,s));let a=[t.command].concat(t.args).join(\" \");t.args=[\"/d\",\"/s\",\"/c\",`\"${a}\"`],t.command=process.env.comspec||\"cmd.exe\",t.options.windowsVerbatimArguments=!0}return t}function q7e(t,e,r){e&&!Array.isArray(e)&&(r=e,e=null),e=e?e.slice(0):[],r=Object.assign({},r);let s={command:t,args:e,options:r,file:void 0,original:{command:t,args:e}};return r.shell?s:j7e(s)}Nee.exports=q7e});var _ee=L((FGt,Mee)=>{\"use strict\";var M_=process.platform===\"win32\";function __(t,e){return Object.assign(new Error(`${e} ${t.command} ENOENT`),{code:\"ENOENT\",errno:\"ENOENT\",syscall:`${e} ${t.command}`,path:t.command,spawnargs:t.args})}function G7e(t,e){if(!M_)return;let r=t.emit;t.emit=function(s,a){if(s===\"exit\"){let n=Lee(a,e);if(n)return r.call(t,\"error\",n)}return r.apply(t,arguments)}}function Lee(t,e){return M_&&t===1&&!e.file?__(e.original,\"spawn\"):null}function W7e(t,e){return M_&&t===1&&!e.file?__(e.original,\"spawnSync\"):null}Mee.exports={hookChildProcess:G7e,verifyENOENT:Lee,verifyENOENTSync:W7e,notFoundError:__}});var j_=L((NGt,lE)=>{\"use strict\";var Uee=Ie(\"child_process\"),U_=Oee(),H_=_ee();function Hee(t,e,r){let s=U_(t,e,r),a=Uee.spawn(s.command,s.args,s.options);return H_.hookChildProcess(a,s),a}function Y7e(t,e,r){let s=U_(t,e,r),a=Uee.spawnSync(s.command,s.args,s.options);return a.error=a.error||H_.verifyENOENTSync(a.status,s),a}lE.exports=Hee;lE.exports.spawn=Hee;lE.exports.sync=Y7e;lE.exports._parse=U_;lE.exports._enoent=H_});var qee=L((OGt,jee)=>{\"use strict\";function V7e(t,e){function r(){this.constructor=t}r.prototype=e.prototype,t.prototype=new r}function wd(t,e,r,s){this.message=t,this.expected=e,this.found=r,this.location=s,this.name=\"SyntaxError\",typeof Error.captureStackTrace==\"function\"&&Error.captureStackTrace(this,wd)}V7e(wd,Error);wd.buildMessage=function(t,e){var r={literal:function(h){return'\"'+a(h.text)+'\"'},class:function(h){var E=\"\",C;for(C=0;C<h.parts.length;C++)E+=h.parts[C]instanceof Array?n(h.parts[C][0])+\"-\"+n(h.parts[C][1]):n(h.parts[C]);return\"[\"+(h.inverted?\"^\":\"\")+E+\"]\"},any:function(h){return\"any character\"},end:function(h){return\"end of input\"},other:function(h){return h.description}};function s(h){return h.charCodeAt(0).toString(16).toUpperCase()}function a(h){return h.replace(/\\\\/g,\"\\\\\\\\\").replace(/\"/g,'\\\\\"').replace(/\\0/g,\"\\\\0\").replace(/\\t/g,\"\\\\t\").replace(/\\n/g,\"\\\\n\").replace(/\\r/g,\"\\\\r\").replace(/[\\x00-\\x0F]/g,function(E){return\"\\\\x0\"+s(E)}).replace(/[\\x10-\\x1F\\x7F-\\x9F]/g,function(E){return\"\\\\x\"+s(E)})}function n(h){return h.replace(/\\\\/g,\"\\\\\\\\\").replace(/\\]/g,\"\\\\]\").replace(/\\^/g,\"\\\\^\").replace(/-/g,\"\\\\-\").replace(/\\0/g,\"\\\\0\").replace(/\\t/g,\"\\\\t\").replace(/\\n/g,\"\\\\n\").replace(/\\r/g,\"\\\\r\").replace(/[\\x00-\\x0F]/g,function(E){return\"\\\\x0\"+s(E)}).replace(/[\\x10-\\x1F\\x7F-\\x9F]/g,function(E){return\"\\\\x\"+s(E)})}function c(h){return r[h.type](h)}function f(h){var E=new Array(h.length),C,S;for(C=0;C<h.length;C++)E[C]=c(h[C]);if(E.sort(),E.length>0){for(C=1,S=1;C<E.length;C++)E[C-1]!==E[C]&&(E[S]=E[C],S++);E.length=S}switch(E.length){case 1:return E[0];case 2:return E[0]+\" or \"+E[1];default:return E.slice(0,-1).join(\", \")+\", or \"+E[E.length-1]}}function p(h){return h?'\"'+a(h)+'\"':\"end of input\"}return\"Expected \"+f(t)+\" but \"+p(e)+\" found.\"};function K7e(t,e){e=e!==void 0?e:{};var r={},s={Start:Ya},a=Ya,n=function(O){return O||[]},c=function(O,J,re){return[{command:O,type:J}].concat(re||[])},f=function(O,J){return[{command:O,type:J||\";\"}]},p=function(O){return O},h=\";\",E=ur(\";\",!1),C=\"&\",S=ur(\"&\",!1),P=function(O,J){return J?{chain:O,then:J}:{chain:O}},I=function(O,J){return{type:O,line:J}},R=\"&&\",N=ur(\"&&\",!1),U=\"||\",W=ur(\"||\",!1),te=function(O,J){return J?{...O,then:J}:O},ie=function(O,J){return{type:O,chain:J}},Ae=\"|&\",ce=ur(\"|&\",!1),me=\"|\",pe=ur(\"|\",!1),Be=\"=\",Ce=ur(\"=\",!1),g=function(O,J){return{name:O,args:[J]}},we=function(O){return{name:O,args:[]}},ye=\"(\",fe=ur(\"(\",!1),se=\")\",X=ur(\")\",!1),De=function(O,J){return{type:\"subshell\",subshell:O,args:J}},Re=\"{\",dt=ur(\"{\",!1),j=\"}\",rt=ur(\"}\",!1),Fe=function(O,J){return{type:\"group\",group:O,args:J}},Ne=function(O,J){return{type:\"command\",args:J,envs:O}},Pe=function(O){return{type:\"envs\",envs:O}},Ye=function(O){return O},ke=function(O){return O},it=/^[0-9]/,_e=Zi([[\"0\",\"9\"]],!1,!1),x=function(O,J,re){return{type:\"redirection\",subtype:J,fd:O!==null?parseInt(O):null,args:[re]}},w=\">>\",b=ur(\">>\",!1),y=\">&\",F=ur(\">&\",!1),z=\">\",Z=ur(\">\",!1),$=\"<<<\",oe=ur(\"<<<\",!1),xe=\"<&\",Te=ur(\"<&\",!1),lt=\"<\",It=ur(\"<\",!1),qt=function(O){return{type:\"argument\",segments:[].concat(...O)}},ir=function(O){return O},Pt=\"$'\",gn=ur(\"$'\",!1),Pr=\"'\",Ir=ur(\"'\",!1),Nr=function(O){return[{type:\"text\",text:O}]},nn='\"\"',ai=ur('\"\"',!1),wo=function(){return{type:\"text\",text:\"\"}},ns='\"',to=ur('\"',!1),Bo=function(O){return O},ji=function(O){return{type:\"arithmetic\",arithmetic:O,quoted:!0}},ro=function(O){return{type:\"shell\",shell:O,quoted:!0}},vo=function(O){return{type:\"variable\",...O,quoted:!0}},RA=function(O){return{type:\"text\",text:O}},pf=function(O){return{type:\"arithmetic\",arithmetic:O,quoted:!1}},yh=function(O){return{type:\"shell\",shell:O,quoted:!1}},Eh=function(O){return{type:\"variable\",...O,quoted:!1}},no=function(O){return{type:\"glob\",pattern:O}},jn=/^[^']/,Fs=Zi([\"'\"],!0,!1),io=function(O){return O.join(\"\")},lu=/^[^$\"]/,cu=Zi([\"$\",'\"'],!0,!1),uu=`\\\\\n`,FA=ur(`\\\\\n`,!1),NA=function(){return\"\"},aa=\"\\\\\",la=ur(\"\\\\\",!1),OA=/^[\\\\$\"`]/,gr=Zi([\"\\\\\",\"$\",'\"',\"`\"],!1,!1),So=function(O){return O},Me=\"\\\\a\",fu=ur(\"\\\\a\",!1),Cr=function(){return\"a\"},hf=\"\\\\b\",LA=ur(\"\\\\b\",!1),MA=function(){return\"\\b\"},Au=/^[Ee]/,pu=Zi([\"E\",\"e\"],!1,!1),ac=function(){return\"\\x1B\"},ve=\"\\\\f\",Nt=ur(\"\\\\f\",!1),lc=function(){return\"\\f\"},Li=\"\\\\n\",so=ur(\"\\\\n\",!1),Rt=function(){return`\n`},xn=\"\\\\r\",ca=ur(\"\\\\r\",!1),qi=function(){return\"\\r\"},Mi=\"\\\\t\",Oa=ur(\"\\\\t\",!1),dn=function(){return\"\t\"},Jn=\"\\\\v\",hu=ur(\"\\\\v\",!1),Ih=function(){return\"\\v\"},La=/^[\\\\'\"?]/,Ma=Zi([\"\\\\\",\"'\",'\"',\"?\"],!1,!1),Ua=function(O){return String.fromCharCode(parseInt(O,16))},Xe=\"\\\\x\",Ha=ur(\"\\\\x\",!1),gf=\"\\\\u\",cc=ur(\"\\\\u\",!1),wn=\"\\\\U\",ua=ur(\"\\\\U\",!1),_A=function(O){return String.fromCodePoint(parseInt(O,16))},UA=/^[0-7]/,fa=Zi([[\"0\",\"7\"]],!1,!1),vl=/^[0-9a-fA-f]/,Mt=Zi([[\"0\",\"9\"],[\"a\",\"f\"],[\"A\",\"f\"]],!1,!1),kn=Ef(),Aa=\"{}\",ja=ur(\"{}\",!1),is=function(){return\"{}\"},uc=\"-\",gu=ur(\"-\",!1),fc=\"+\",qa=ur(\"+\",!1),_i=\".\",ws=ur(\".\",!1),Sl=function(O,J,re){return{type:\"number\",value:(O===\"-\"?-1:1)*parseFloat(J.join(\"\")+\".\"+re.join(\"\"))}},df=function(O,J){return{type:\"number\",value:(O===\"-\"?-1:1)*parseInt(J.join(\"\"))}},Ac=function(O){return{type:\"variable\",...O}},Bi=function(O){return{type:\"variable\",name:O}},Qn=function(O){return O},pc=\"*\",Je=ur(\"*\",!1),st=\"/\",St=ur(\"/\",!1),lr=function(O,J,re){return{type:J===\"*\"?\"multiplication\":\"division\",right:re}},ee=function(O,J){return J.reduce((re,de)=>({left:re,...de}),O)},Ee=function(O,J,re){return{type:J===\"+\"?\"addition\":\"subtraction\",right:re}},Oe=\"$((\",gt=ur(\"$((\",!1),yt=\"))\",Dt=ur(\"))\",!1),tr=function(O){return O},fn=\"$(\",li=ur(\"$(\",!1),Gi=function(O){return O},Tn=\"${\",Ga=ur(\"${\",!1),gy=\":-\",X1=ur(\":-\",!1),Do=function(O,J){return{name:O,defaultValue:J}},dy=\":-}\",Ch=ur(\":-}\",!1),$1=function(O){return{name:O,defaultValue:[]}},bo=\":+\",wh=ur(\":+\",!1),Bh=function(O,J){return{name:O,alternativeValue:J}},du=\":+}\",vh=ur(\":+}\",!1),Rg=function(O){return{name:O,alternativeValue:[]}},Fg=function(O){return{name:O}},Ng=\"$\",my=ur(\"$\",!1),mf=function(O){return e.isGlobPattern(O)},Po=function(O){return O},Dl=/^[a-zA-Z0-9_]/,Sh=Zi([[\"a\",\"z\"],[\"A\",\"Z\"],[\"0\",\"9\"],\"_\"],!1,!1),Og=function(){return Cy()},bl=/^[$@*?#a-zA-Z0-9_\\-]/,Pl=Zi([\"$\",\"@\",\"*\",\"?\",\"#\",[\"a\",\"z\"],[\"A\",\"Z\"],[\"0\",\"9\"],\"_\",\"-\"],!1,!1),yy=/^[()}<>$|&; \\t\"']/,HA=Zi([\"(\",\")\",\"}\",\"<\",\">\",\"$\",\"|\",\"&\",\";\",\" \",\"\t\",'\"',\"'\"],!1,!1),Ey=/^[<>&; \\t\"']/,Iy=Zi([\"<\",\">\",\"&\",\";\",\" \",\"\t\",'\"',\"'\"],!1,!1),jA=/^[ \\t]/,qA=Zi([\" \",\"\t\"],!1,!1),Y=0,xt=0,GA=[{line:1,column:1}],xo=0,yf=[],mt=0,mu;if(\"startRule\"in e){if(!(e.startRule in s))throw new Error(`Can't start parsing from rule \"`+e.startRule+'\".');a=s[e.startRule]}function Cy(){return t.substring(xt,Y)}function Lg(){return If(xt,Y)}function e2(O,J){throw J=J!==void 0?J:If(xt,Y),WA([Mg(O)],t.substring(xt,Y),J)}function Dh(O,J){throw J=J!==void 0?J:If(xt,Y),di(O,J)}function ur(O,J){return{type:\"literal\",text:O,ignoreCase:J}}function Zi(O,J,re){return{type:\"class\",parts:O,inverted:J,ignoreCase:re}}function Ef(){return{type:\"any\"}}function Wa(){return{type:\"end\"}}function Mg(O){return{type:\"other\",description:O}}function yu(O){var J=GA[O],re;if(J)return J;for(re=O-1;!GA[re];)re--;for(J=GA[re],J={line:J.line,column:J.column};re<O;)t.charCodeAt(re)===10?(J.line++,J.column=1):J.column++,re++;return GA[O]=J,J}function If(O,J){var re=yu(O),de=yu(J);return{start:{offset:O,line:re.line,column:re.column},end:{offset:J,line:de.line,column:de.column}}}function wt(O){Y<xo||(Y>xo&&(xo=Y,yf=[]),yf.push(O))}function di(O,J){return new wd(O,null,null,J)}function WA(O,J,re){return new wd(wd.buildMessage(O,J),O,J,re)}function Ya(){var O,J,re;for(O=Y,J=[],re=kt();re!==r;)J.push(re),re=kt();return J!==r?(re=pa(),re===r&&(re=null),re!==r?(xt=O,J=n(re),O=J):(Y=O,O=r)):(Y=O,O=r),O}function pa(){var O,J,re,de,Ke;if(O=Y,J=bh(),J!==r){for(re=[],de=kt();de!==r;)re.push(de),de=kt();re!==r?(de=_g(),de!==r?(Ke=Va(),Ke===r&&(Ke=null),Ke!==r?(xt=O,J=c(J,de,Ke),O=J):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r)}else Y=O,O=r;if(O===r)if(O=Y,J=bh(),J!==r){for(re=[],de=kt();de!==r;)re.push(de),de=kt();re!==r?(de=_g(),de===r&&(de=null),de!==r?(xt=O,J=f(J,de),O=J):(Y=O,O=r)):(Y=O,O=r)}else Y=O,O=r;return O}function Va(){var O,J,re,de,Ke;for(O=Y,J=[],re=kt();re!==r;)J.push(re),re=kt();if(J!==r)if(re=pa(),re!==r){for(de=[],Ke=kt();Ke!==r;)de.push(Ke),Ke=kt();de!==r?(xt=O,J=p(re),O=J):(Y=O,O=r)}else Y=O,O=r;else Y=O,O=r;return O}function _g(){var O;return t.charCodeAt(Y)===59?(O=h,Y++):(O=r,mt===0&&wt(E)),O===r&&(t.charCodeAt(Y)===38?(O=C,Y++):(O=r,mt===0&&wt(S))),O}function bh(){var O,J,re;return O=Y,J=YA(),J!==r?(re=Ug(),re===r&&(re=null),re!==r?(xt=O,J=P(J,re),O=J):(Y=O,O=r)):(Y=O,O=r),O}function Ug(){var O,J,re,de,Ke,ft,dr;for(O=Y,J=[],re=kt();re!==r;)J.push(re),re=kt();if(J!==r)if(re=wy(),re!==r){for(de=[],Ke=kt();Ke!==r;)de.push(Ke),Ke=kt();if(de!==r)if(Ke=bh(),Ke!==r){for(ft=[],dr=kt();dr!==r;)ft.push(dr),dr=kt();ft!==r?(xt=O,J=I(re,Ke),O=J):(Y=O,O=r)}else Y=O,O=r;else Y=O,O=r}else Y=O,O=r;else Y=O,O=r;return O}function wy(){var O;return t.substr(Y,2)===R?(O=R,Y+=2):(O=r,mt===0&&wt(N)),O===r&&(t.substr(Y,2)===U?(O=U,Y+=2):(O=r,mt===0&&wt(W))),O}function YA(){var O,J,re;return O=Y,J=Cf(),J!==r?(re=Hg(),re===r&&(re=null),re!==r?(xt=O,J=te(J,re),O=J):(Y=O,O=r)):(Y=O,O=r),O}function Hg(){var O,J,re,de,Ke,ft,dr;for(O=Y,J=[],re=kt();re!==r;)J.push(re),re=kt();if(J!==r)if(re=Eu(),re!==r){for(de=[],Ke=kt();Ke!==r;)de.push(Ke),Ke=kt();if(de!==r)if(Ke=YA(),Ke!==r){for(ft=[],dr=kt();dr!==r;)ft.push(dr),dr=kt();ft!==r?(xt=O,J=ie(re,Ke),O=J):(Y=O,O=r)}else Y=O,O=r;else Y=O,O=r}else Y=O,O=r;else Y=O,O=r;return O}function Eu(){var O;return t.substr(Y,2)===Ae?(O=Ae,Y+=2):(O=r,mt===0&&wt(ce)),O===r&&(t.charCodeAt(Y)===124?(O=me,Y++):(O=r,mt===0&&wt(pe))),O}function Iu(){var O,J,re,de,Ke,ft;if(O=Y,J=kh(),J!==r)if(t.charCodeAt(Y)===61?(re=Be,Y++):(re=r,mt===0&&wt(Ce)),re!==r)if(de=VA(),de!==r){for(Ke=[],ft=kt();ft!==r;)Ke.push(ft),ft=kt();Ke!==r?(xt=O,J=g(J,de),O=J):(Y=O,O=r)}else Y=O,O=r;else Y=O,O=r;else Y=O,O=r;if(O===r)if(O=Y,J=kh(),J!==r)if(t.charCodeAt(Y)===61?(re=Be,Y++):(re=r,mt===0&&wt(Ce)),re!==r){for(de=[],Ke=kt();Ke!==r;)de.push(Ke),Ke=kt();de!==r?(xt=O,J=we(J),O=J):(Y=O,O=r)}else Y=O,O=r;else Y=O,O=r;return O}function Cf(){var O,J,re,de,Ke,ft,dr,Br,_n,mi,Bs;for(O=Y,J=[],re=kt();re!==r;)J.push(re),re=kt();if(J!==r)if(t.charCodeAt(Y)===40?(re=ye,Y++):(re=r,mt===0&&wt(fe)),re!==r){for(de=[],Ke=kt();Ke!==r;)de.push(Ke),Ke=kt();if(de!==r)if(Ke=pa(),Ke!==r){for(ft=[],dr=kt();dr!==r;)ft.push(dr),dr=kt();if(ft!==r)if(t.charCodeAt(Y)===41?(dr=se,Y++):(dr=r,mt===0&&wt(X)),dr!==r){for(Br=[],_n=kt();_n!==r;)Br.push(_n),_n=kt();if(Br!==r){for(_n=[],mi=qn();mi!==r;)_n.push(mi),mi=qn();if(_n!==r){for(mi=[],Bs=kt();Bs!==r;)mi.push(Bs),Bs=kt();mi!==r?(xt=O,J=De(Ke,_n),O=J):(Y=O,O=r)}else Y=O,O=r}else Y=O,O=r}else Y=O,O=r;else Y=O,O=r}else Y=O,O=r;else Y=O,O=r}else Y=O,O=r;else Y=O,O=r;if(O===r){for(O=Y,J=[],re=kt();re!==r;)J.push(re),re=kt();if(J!==r)if(t.charCodeAt(Y)===123?(re=Re,Y++):(re=r,mt===0&&wt(dt)),re!==r){for(de=[],Ke=kt();Ke!==r;)de.push(Ke),Ke=kt();if(de!==r)if(Ke=pa(),Ke!==r){for(ft=[],dr=kt();dr!==r;)ft.push(dr),dr=kt();if(ft!==r)if(t.charCodeAt(Y)===125?(dr=j,Y++):(dr=r,mt===0&&wt(rt)),dr!==r){for(Br=[],_n=kt();_n!==r;)Br.push(_n),_n=kt();if(Br!==r){for(_n=[],mi=qn();mi!==r;)_n.push(mi),mi=qn();if(_n!==r){for(mi=[],Bs=kt();Bs!==r;)mi.push(Bs),Bs=kt();mi!==r?(xt=O,J=Fe(Ke,_n),O=J):(Y=O,O=r)}else Y=O,O=r}else Y=O,O=r}else Y=O,O=r;else Y=O,O=r}else Y=O,O=r;else Y=O,O=r}else Y=O,O=r;else Y=O,O=r;if(O===r){for(O=Y,J=[],re=kt();re!==r;)J.push(re),re=kt();if(J!==r){for(re=[],de=Iu();de!==r;)re.push(de),de=Iu();if(re!==r){for(de=[],Ke=kt();Ke!==r;)de.push(Ke),Ke=kt();if(de!==r){if(Ke=[],ft=Cu(),ft!==r)for(;ft!==r;)Ke.push(ft),ft=Cu();else Ke=r;if(Ke!==r){for(ft=[],dr=kt();dr!==r;)ft.push(dr),dr=kt();ft!==r?(xt=O,J=Ne(re,Ke),O=J):(Y=O,O=r)}else Y=O,O=r}else Y=O,O=r}else Y=O,O=r}else Y=O,O=r;if(O===r){for(O=Y,J=[],re=kt();re!==r;)J.push(re),re=kt();if(J!==r){if(re=[],de=Iu(),de!==r)for(;de!==r;)re.push(de),de=Iu();else re=r;if(re!==r){for(de=[],Ke=kt();Ke!==r;)de.push(Ke),Ke=kt();de!==r?(xt=O,J=Pe(re),O=J):(Y=O,O=r)}else Y=O,O=r}else Y=O,O=r}}}return O}function Ns(){var O,J,re,de,Ke;for(O=Y,J=[],re=kt();re!==r;)J.push(re),re=kt();if(J!==r){if(re=[],de=ki(),de!==r)for(;de!==r;)re.push(de),de=ki();else re=r;if(re!==r){for(de=[],Ke=kt();Ke!==r;)de.push(Ke),Ke=kt();de!==r?(xt=O,J=Ye(re),O=J):(Y=O,O=r)}else Y=O,O=r}else Y=O,O=r;return O}function Cu(){var O,J,re;for(O=Y,J=[],re=kt();re!==r;)J.push(re),re=kt();if(J!==r?(re=qn(),re!==r?(xt=O,J=ke(re),O=J):(Y=O,O=r)):(Y=O,O=r),O===r){for(O=Y,J=[],re=kt();re!==r;)J.push(re),re=kt();J!==r?(re=ki(),re!==r?(xt=O,J=ke(re),O=J):(Y=O,O=r)):(Y=O,O=r)}return O}function qn(){var O,J,re,de,Ke;for(O=Y,J=[],re=kt();re!==r;)J.push(re),re=kt();return J!==r?(it.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,mt===0&&wt(_e)),re===r&&(re=null),re!==r?(de=ss(),de!==r?(Ke=ki(),Ke!==r?(xt=O,J=x(re,de,Ke),O=J):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r),O}function ss(){var O;return t.substr(Y,2)===w?(O=w,Y+=2):(O=r,mt===0&&wt(b)),O===r&&(t.substr(Y,2)===y?(O=y,Y+=2):(O=r,mt===0&&wt(F)),O===r&&(t.charCodeAt(Y)===62?(O=z,Y++):(O=r,mt===0&&wt(Z)),O===r&&(t.substr(Y,3)===$?(O=$,Y+=3):(O=r,mt===0&&wt(oe)),O===r&&(t.substr(Y,2)===xe?(O=xe,Y+=2):(O=r,mt===0&&wt(Te)),O===r&&(t.charCodeAt(Y)===60?(O=lt,Y++):(O=r,mt===0&&wt(It))))))),O}function ki(){var O,J,re;for(O=Y,J=[],re=kt();re!==r;)J.push(re),re=kt();return J!==r?(re=VA(),re!==r?(xt=O,J=ke(re),O=J):(Y=O,O=r)):(Y=O,O=r),O}function VA(){var O,J,re;if(O=Y,J=[],re=wf(),re!==r)for(;re!==r;)J.push(re),re=wf();else J=r;return J!==r&&(xt=O,J=qt(J)),O=J,O}function wf(){var O,J;return O=Y,J=mn(),J!==r&&(xt=O,J=ir(J)),O=J,O===r&&(O=Y,J=jg(),J!==r&&(xt=O,J=ir(J)),O=J,O===r&&(O=Y,J=qg(),J!==r&&(xt=O,J=ir(J)),O=J,O===r&&(O=Y,J=os(),J!==r&&(xt=O,J=ir(J)),O=J))),O}function mn(){var O,J,re,de;return O=Y,t.substr(Y,2)===Pt?(J=Pt,Y+=2):(J=r,mt===0&&wt(gn)),J!==r?(re=yn(),re!==r?(t.charCodeAt(Y)===39?(de=Pr,Y++):(de=r,mt===0&&wt(Ir)),de!==r?(xt=O,J=Nr(re),O=J):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r),O}function jg(){var O,J,re,de;return O=Y,t.charCodeAt(Y)===39?(J=Pr,Y++):(J=r,mt===0&&wt(Ir)),J!==r?(re=Bf(),re!==r?(t.charCodeAt(Y)===39?(de=Pr,Y++):(de=r,mt===0&&wt(Ir)),de!==r?(xt=O,J=Nr(re),O=J):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r),O}function qg(){var O,J,re,de;if(O=Y,t.substr(Y,2)===nn?(J=nn,Y+=2):(J=r,mt===0&&wt(ai)),J!==r&&(xt=O,J=wo()),O=J,O===r)if(O=Y,t.charCodeAt(Y)===34?(J=ns,Y++):(J=r,mt===0&&wt(to)),J!==r){for(re=[],de=xl();de!==r;)re.push(de),de=xl();re!==r?(t.charCodeAt(Y)===34?(de=ns,Y++):(de=r,mt===0&&wt(to)),de!==r?(xt=O,J=Bo(re),O=J):(Y=O,O=r)):(Y=O,O=r)}else Y=O,O=r;return O}function os(){var O,J,re;if(O=Y,J=[],re=ko(),re!==r)for(;re!==r;)J.push(re),re=ko();else J=r;return J!==r&&(xt=O,J=Bo(J)),O=J,O}function xl(){var O,J;return O=Y,J=Xr(),J!==r&&(xt=O,J=ji(J)),O=J,O===r&&(O=Y,J=xh(),J!==r&&(xt=O,J=ro(J)),O=J,O===r&&(O=Y,J=JA(),J!==r&&(xt=O,J=vo(J)),O=J,O===r&&(O=Y,J=vf(),J!==r&&(xt=O,J=RA(J)),O=J))),O}function ko(){var O,J;return O=Y,J=Xr(),J!==r&&(xt=O,J=pf(J)),O=J,O===r&&(O=Y,J=xh(),J!==r&&(xt=O,J=yh(J)),O=J,O===r&&(O=Y,J=JA(),J!==r&&(xt=O,J=Eh(J)),O=J,O===r&&(O=Y,J=By(),J!==r&&(xt=O,J=no(J)),O=J,O===r&&(O=Y,J=Ph(),J!==r&&(xt=O,J=RA(J)),O=J)))),O}function Bf(){var O,J,re;for(O=Y,J=[],jn.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,mt===0&&wt(Fs));re!==r;)J.push(re),jn.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,mt===0&&wt(Fs));return J!==r&&(xt=O,J=io(J)),O=J,O}function vf(){var O,J,re;if(O=Y,J=[],re=kl(),re===r&&(lu.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,mt===0&&wt(cu))),re!==r)for(;re!==r;)J.push(re),re=kl(),re===r&&(lu.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,mt===0&&wt(cu)));else J=r;return J!==r&&(xt=O,J=io(J)),O=J,O}function kl(){var O,J,re;return O=Y,t.substr(Y,2)===uu?(J=uu,Y+=2):(J=r,mt===0&&wt(FA)),J!==r&&(xt=O,J=NA()),O=J,O===r&&(O=Y,t.charCodeAt(Y)===92?(J=aa,Y++):(J=r,mt===0&&wt(la)),J!==r?(OA.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,mt===0&&wt(gr)),re!==r?(xt=O,J=So(re),O=J):(Y=O,O=r)):(Y=O,O=r)),O}function yn(){var O,J,re;for(O=Y,J=[],re=Qo(),re===r&&(jn.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,mt===0&&wt(Fs)));re!==r;)J.push(re),re=Qo(),re===r&&(jn.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,mt===0&&wt(Fs)));return J!==r&&(xt=O,J=io(J)),O=J,O}function Qo(){var O,J,re;return O=Y,t.substr(Y,2)===Me?(J=Me,Y+=2):(J=r,mt===0&&wt(fu)),J!==r&&(xt=O,J=Cr()),O=J,O===r&&(O=Y,t.substr(Y,2)===hf?(J=hf,Y+=2):(J=r,mt===0&&wt(LA)),J!==r&&(xt=O,J=MA()),O=J,O===r&&(O=Y,t.charCodeAt(Y)===92?(J=aa,Y++):(J=r,mt===0&&wt(la)),J!==r?(Au.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,mt===0&&wt(pu)),re!==r?(xt=O,J=ac(),O=J):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=Y,t.substr(Y,2)===ve?(J=ve,Y+=2):(J=r,mt===0&&wt(Nt)),J!==r&&(xt=O,J=lc()),O=J,O===r&&(O=Y,t.substr(Y,2)===Li?(J=Li,Y+=2):(J=r,mt===0&&wt(so)),J!==r&&(xt=O,J=Rt()),O=J,O===r&&(O=Y,t.substr(Y,2)===xn?(J=xn,Y+=2):(J=r,mt===0&&wt(ca)),J!==r&&(xt=O,J=qi()),O=J,O===r&&(O=Y,t.substr(Y,2)===Mi?(J=Mi,Y+=2):(J=r,mt===0&&wt(Oa)),J!==r&&(xt=O,J=dn()),O=J,O===r&&(O=Y,t.substr(Y,2)===Jn?(J=Jn,Y+=2):(J=r,mt===0&&wt(hu)),J!==r&&(xt=O,J=Ih()),O=J,O===r&&(O=Y,t.charCodeAt(Y)===92?(J=aa,Y++):(J=r,mt===0&&wt(la)),J!==r?(La.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,mt===0&&wt(Ma)),re!==r?(xt=O,J=So(re),O=J):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=wu()))))))))),O}function wu(){var O,J,re,de,Ke,ft,dr,Br,_n,mi,Bs,zA;return O=Y,t.charCodeAt(Y)===92?(J=aa,Y++):(J=r,mt===0&&wt(la)),J!==r?(re=ha(),re!==r?(xt=O,J=Ua(re),O=J):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=Y,t.substr(Y,2)===Xe?(J=Xe,Y+=2):(J=r,mt===0&&wt(Ha)),J!==r?(re=Y,de=Y,Ke=ha(),Ke!==r?(ft=Os(),ft!==r?(Ke=[Ke,ft],de=Ke):(Y=de,de=r)):(Y=de,de=r),de===r&&(de=ha()),de!==r?re=t.substring(re,Y):re=de,re!==r?(xt=O,J=Ua(re),O=J):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=Y,t.substr(Y,2)===gf?(J=gf,Y+=2):(J=r,mt===0&&wt(cc)),J!==r?(re=Y,de=Y,Ke=Os(),Ke!==r?(ft=Os(),ft!==r?(dr=Os(),dr!==r?(Br=Os(),Br!==r?(Ke=[Ke,ft,dr,Br],de=Ke):(Y=de,de=r)):(Y=de,de=r)):(Y=de,de=r)):(Y=de,de=r),de!==r?re=t.substring(re,Y):re=de,re!==r?(xt=O,J=Ua(re),O=J):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=Y,t.substr(Y,2)===wn?(J=wn,Y+=2):(J=r,mt===0&&wt(ua)),J!==r?(re=Y,de=Y,Ke=Os(),Ke!==r?(ft=Os(),ft!==r?(dr=Os(),dr!==r?(Br=Os(),Br!==r?(_n=Os(),_n!==r?(mi=Os(),mi!==r?(Bs=Os(),Bs!==r?(zA=Os(),zA!==r?(Ke=[Ke,ft,dr,Br,_n,mi,Bs,zA],de=Ke):(Y=de,de=r)):(Y=de,de=r)):(Y=de,de=r)):(Y=de,de=r)):(Y=de,de=r)):(Y=de,de=r)):(Y=de,de=r)):(Y=de,de=r),de!==r?re=t.substring(re,Y):re=de,re!==r?(xt=O,J=_A(re),O=J):(Y=O,O=r)):(Y=O,O=r)))),O}function ha(){var O;return UA.test(t.charAt(Y))?(O=t.charAt(Y),Y++):(O=r,mt===0&&wt(fa)),O}function Os(){var O;return vl.test(t.charAt(Y))?(O=t.charAt(Y),Y++):(O=r,mt===0&&wt(Mt)),O}function Ph(){var O,J,re,de,Ke;if(O=Y,J=[],re=Y,t.charCodeAt(Y)===92?(de=aa,Y++):(de=r,mt===0&&wt(la)),de!==r?(t.length>Y?(Ke=t.charAt(Y),Y++):(Ke=r,mt===0&&wt(kn)),Ke!==r?(xt=re,de=So(Ke),re=de):(Y=re,re=r)):(Y=re,re=r),re===r&&(re=Y,t.substr(Y,2)===Aa?(de=Aa,Y+=2):(de=r,mt===0&&wt(ja)),de!==r&&(xt=re,de=is()),re=de,re===r&&(re=Y,de=Y,mt++,Ke=vy(),mt--,Ke===r?de=void 0:(Y=de,de=r),de!==r?(t.length>Y?(Ke=t.charAt(Y),Y++):(Ke=r,mt===0&&wt(kn)),Ke!==r?(xt=re,de=So(Ke),re=de):(Y=re,re=r)):(Y=re,re=r))),re!==r)for(;re!==r;)J.push(re),re=Y,t.charCodeAt(Y)===92?(de=aa,Y++):(de=r,mt===0&&wt(la)),de!==r?(t.length>Y?(Ke=t.charAt(Y),Y++):(Ke=r,mt===0&&wt(kn)),Ke!==r?(xt=re,de=So(Ke),re=de):(Y=re,re=r)):(Y=re,re=r),re===r&&(re=Y,t.substr(Y,2)===Aa?(de=Aa,Y+=2):(de=r,mt===0&&wt(ja)),de!==r&&(xt=re,de=is()),re=de,re===r&&(re=Y,de=Y,mt++,Ke=vy(),mt--,Ke===r?de=void 0:(Y=de,de=r),de!==r?(t.length>Y?(Ke=t.charAt(Y),Y++):(Ke=r,mt===0&&wt(kn)),Ke!==r?(xt=re,de=So(Ke),re=de):(Y=re,re=r)):(Y=re,re=r)));else J=r;return J!==r&&(xt=O,J=io(J)),O=J,O}function KA(){var O,J,re,de,Ke,ft;if(O=Y,t.charCodeAt(Y)===45?(J=uc,Y++):(J=r,mt===0&&wt(gu)),J===r&&(t.charCodeAt(Y)===43?(J=fc,Y++):(J=r,mt===0&&wt(qa))),J===r&&(J=null),J!==r){if(re=[],it.test(t.charAt(Y))?(de=t.charAt(Y),Y++):(de=r,mt===0&&wt(_e)),de!==r)for(;de!==r;)re.push(de),it.test(t.charAt(Y))?(de=t.charAt(Y),Y++):(de=r,mt===0&&wt(_e));else re=r;if(re!==r)if(t.charCodeAt(Y)===46?(de=_i,Y++):(de=r,mt===0&&wt(ws)),de!==r){if(Ke=[],it.test(t.charAt(Y))?(ft=t.charAt(Y),Y++):(ft=r,mt===0&&wt(_e)),ft!==r)for(;ft!==r;)Ke.push(ft),it.test(t.charAt(Y))?(ft=t.charAt(Y),Y++):(ft=r,mt===0&&wt(_e));else Ke=r;Ke!==r?(xt=O,J=Sl(J,re,Ke),O=J):(Y=O,O=r)}else Y=O,O=r;else Y=O,O=r}else Y=O,O=r;if(O===r){if(O=Y,t.charCodeAt(Y)===45?(J=uc,Y++):(J=r,mt===0&&wt(gu)),J===r&&(t.charCodeAt(Y)===43?(J=fc,Y++):(J=r,mt===0&&wt(qa))),J===r&&(J=null),J!==r){if(re=[],it.test(t.charAt(Y))?(de=t.charAt(Y),Y++):(de=r,mt===0&&wt(_e)),de!==r)for(;de!==r;)re.push(de),it.test(t.charAt(Y))?(de=t.charAt(Y),Y++):(de=r,mt===0&&wt(_e));else re=r;re!==r?(xt=O,J=df(J,re),O=J):(Y=O,O=r)}else Y=O,O=r;if(O===r&&(O=Y,J=JA(),J!==r&&(xt=O,J=Ac(J)),O=J,O===r&&(O=Y,J=hc(),J!==r&&(xt=O,J=Bi(J)),O=J,O===r)))if(O=Y,t.charCodeAt(Y)===40?(J=ye,Y++):(J=r,mt===0&&wt(fe)),J!==r){for(re=[],de=kt();de!==r;)re.push(de),de=kt();if(re!==r)if(de=oo(),de!==r){for(Ke=[],ft=kt();ft!==r;)Ke.push(ft),ft=kt();Ke!==r?(t.charCodeAt(Y)===41?(ft=se,Y++):(ft=r,mt===0&&wt(X)),ft!==r?(xt=O,J=Qn(de),O=J):(Y=O,O=r)):(Y=O,O=r)}else Y=O,O=r;else Y=O,O=r}else Y=O,O=r}return O}function Sf(){var O,J,re,de,Ke,ft,dr,Br;if(O=Y,J=KA(),J!==r){for(re=[],de=Y,Ke=[],ft=kt();ft!==r;)Ke.push(ft),ft=kt();if(Ke!==r)if(t.charCodeAt(Y)===42?(ft=pc,Y++):(ft=r,mt===0&&wt(Je)),ft===r&&(t.charCodeAt(Y)===47?(ft=st,Y++):(ft=r,mt===0&&wt(St))),ft!==r){for(dr=[],Br=kt();Br!==r;)dr.push(Br),Br=kt();dr!==r?(Br=KA(),Br!==r?(xt=de,Ke=lr(J,ft,Br),de=Ke):(Y=de,de=r)):(Y=de,de=r)}else Y=de,de=r;else Y=de,de=r;for(;de!==r;){for(re.push(de),de=Y,Ke=[],ft=kt();ft!==r;)Ke.push(ft),ft=kt();if(Ke!==r)if(t.charCodeAt(Y)===42?(ft=pc,Y++):(ft=r,mt===0&&wt(Je)),ft===r&&(t.charCodeAt(Y)===47?(ft=st,Y++):(ft=r,mt===0&&wt(St))),ft!==r){for(dr=[],Br=kt();Br!==r;)dr.push(Br),Br=kt();dr!==r?(Br=KA(),Br!==r?(xt=de,Ke=lr(J,ft,Br),de=Ke):(Y=de,de=r)):(Y=de,de=r)}else Y=de,de=r;else Y=de,de=r}re!==r?(xt=O,J=ee(J,re),O=J):(Y=O,O=r)}else Y=O,O=r;return O}function oo(){var O,J,re,de,Ke,ft,dr,Br;if(O=Y,J=Sf(),J!==r){for(re=[],de=Y,Ke=[],ft=kt();ft!==r;)Ke.push(ft),ft=kt();if(Ke!==r)if(t.charCodeAt(Y)===43?(ft=fc,Y++):(ft=r,mt===0&&wt(qa)),ft===r&&(t.charCodeAt(Y)===45?(ft=uc,Y++):(ft=r,mt===0&&wt(gu))),ft!==r){for(dr=[],Br=kt();Br!==r;)dr.push(Br),Br=kt();dr!==r?(Br=Sf(),Br!==r?(xt=de,Ke=Ee(J,ft,Br),de=Ke):(Y=de,de=r)):(Y=de,de=r)}else Y=de,de=r;else Y=de,de=r;for(;de!==r;){for(re.push(de),de=Y,Ke=[],ft=kt();ft!==r;)Ke.push(ft),ft=kt();if(Ke!==r)if(t.charCodeAt(Y)===43?(ft=fc,Y++):(ft=r,mt===0&&wt(qa)),ft===r&&(t.charCodeAt(Y)===45?(ft=uc,Y++):(ft=r,mt===0&&wt(gu))),ft!==r){for(dr=[],Br=kt();Br!==r;)dr.push(Br),Br=kt();dr!==r?(Br=Sf(),Br!==r?(xt=de,Ke=Ee(J,ft,Br),de=Ke):(Y=de,de=r)):(Y=de,de=r)}else Y=de,de=r;else Y=de,de=r}re!==r?(xt=O,J=ee(J,re),O=J):(Y=O,O=r)}else Y=O,O=r;return O}function Xr(){var O,J,re,de,Ke,ft;if(O=Y,t.substr(Y,3)===Oe?(J=Oe,Y+=3):(J=r,mt===0&&wt(gt)),J!==r){for(re=[],de=kt();de!==r;)re.push(de),de=kt();if(re!==r)if(de=oo(),de!==r){for(Ke=[],ft=kt();ft!==r;)Ke.push(ft),ft=kt();Ke!==r?(t.substr(Y,2)===yt?(ft=yt,Y+=2):(ft=r,mt===0&&wt(Dt)),ft!==r?(xt=O,J=tr(de),O=J):(Y=O,O=r)):(Y=O,O=r)}else Y=O,O=r;else Y=O,O=r}else Y=O,O=r;return O}function xh(){var O,J,re,de;return O=Y,t.substr(Y,2)===fn?(J=fn,Y+=2):(J=r,mt===0&&wt(li)),J!==r?(re=pa(),re!==r?(t.charCodeAt(Y)===41?(de=se,Y++):(de=r,mt===0&&wt(X)),de!==r?(xt=O,J=Gi(re),O=J):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r),O}function JA(){var O,J,re,de,Ke,ft;return O=Y,t.substr(Y,2)===Tn?(J=Tn,Y+=2):(J=r,mt===0&&wt(Ga)),J!==r?(re=hc(),re!==r?(t.substr(Y,2)===gy?(de=gy,Y+=2):(de=r,mt===0&&wt(X1)),de!==r?(Ke=Ns(),Ke!==r?(t.charCodeAt(Y)===125?(ft=j,Y++):(ft=r,mt===0&&wt(rt)),ft!==r?(xt=O,J=Do(re,Ke),O=J):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=Y,t.substr(Y,2)===Tn?(J=Tn,Y+=2):(J=r,mt===0&&wt(Ga)),J!==r?(re=hc(),re!==r?(t.substr(Y,3)===dy?(de=dy,Y+=3):(de=r,mt===0&&wt(Ch)),de!==r?(xt=O,J=$1(re),O=J):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=Y,t.substr(Y,2)===Tn?(J=Tn,Y+=2):(J=r,mt===0&&wt(Ga)),J!==r?(re=hc(),re!==r?(t.substr(Y,2)===bo?(de=bo,Y+=2):(de=r,mt===0&&wt(wh)),de!==r?(Ke=Ns(),Ke!==r?(t.charCodeAt(Y)===125?(ft=j,Y++):(ft=r,mt===0&&wt(rt)),ft!==r?(xt=O,J=Bh(re,Ke),O=J):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=Y,t.substr(Y,2)===Tn?(J=Tn,Y+=2):(J=r,mt===0&&wt(Ga)),J!==r?(re=hc(),re!==r?(t.substr(Y,3)===du?(de=du,Y+=3):(de=r,mt===0&&wt(vh)),de!==r?(xt=O,J=Rg(re),O=J):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=Y,t.substr(Y,2)===Tn?(J=Tn,Y+=2):(J=r,mt===0&&wt(Ga)),J!==r?(re=hc(),re!==r?(t.charCodeAt(Y)===125?(de=j,Y++):(de=r,mt===0&&wt(rt)),de!==r?(xt=O,J=Fg(re),O=J):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=Y,t.charCodeAt(Y)===36?(J=Ng,Y++):(J=r,mt===0&&wt(my)),J!==r?(re=hc(),re!==r?(xt=O,J=Fg(re),O=J):(Y=O,O=r)):(Y=O,O=r)))))),O}function By(){var O,J,re;return O=Y,J=Gg(),J!==r?(xt=Y,re=mf(J),re?re=void 0:re=r,re!==r?(xt=O,J=Po(J),O=J):(Y=O,O=r)):(Y=O,O=r),O}function Gg(){var O,J,re,de,Ke;if(O=Y,J=[],re=Y,de=Y,mt++,Ke=Qh(),mt--,Ke===r?de=void 0:(Y=de,de=r),de!==r?(t.length>Y?(Ke=t.charAt(Y),Y++):(Ke=r,mt===0&&wt(kn)),Ke!==r?(xt=re,de=So(Ke),re=de):(Y=re,re=r)):(Y=re,re=r),re!==r)for(;re!==r;)J.push(re),re=Y,de=Y,mt++,Ke=Qh(),mt--,Ke===r?de=void 0:(Y=de,de=r),de!==r?(t.length>Y?(Ke=t.charAt(Y),Y++):(Ke=r,mt===0&&wt(kn)),Ke!==r?(xt=re,de=So(Ke),re=de):(Y=re,re=r)):(Y=re,re=r);else J=r;return J!==r&&(xt=O,J=io(J)),O=J,O}function kh(){var O,J,re;if(O=Y,J=[],Dl.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,mt===0&&wt(Sh)),re!==r)for(;re!==r;)J.push(re),Dl.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,mt===0&&wt(Sh));else J=r;return J!==r&&(xt=O,J=Og()),O=J,O}function hc(){var O,J,re;if(O=Y,J=[],bl.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,mt===0&&wt(Pl)),re!==r)for(;re!==r;)J.push(re),bl.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,mt===0&&wt(Pl));else J=r;return J!==r&&(xt=O,J=Og()),O=J,O}function vy(){var O;return yy.test(t.charAt(Y))?(O=t.charAt(Y),Y++):(O=r,mt===0&&wt(HA)),O}function Qh(){var O;return Ey.test(t.charAt(Y))?(O=t.charAt(Y),Y++):(O=r,mt===0&&wt(Iy)),O}function kt(){var O,J;if(O=[],jA.test(t.charAt(Y))?(J=t.charAt(Y),Y++):(J=r,mt===0&&wt(qA)),J!==r)for(;J!==r;)O.push(J),jA.test(t.charAt(Y))?(J=t.charAt(Y),Y++):(J=r,mt===0&&wt(qA));else O=r;return O}if(mu=a(),mu!==r&&Y===t.length)return mu;throw mu!==r&&Y<t.length&&wt(Wa()),WA(yf,xo<t.length?t.charAt(xo):null,xo<t.length?If(xo,xo+1):If(xo,xo))}jee.exports={SyntaxError:wd,parse:K7e}});function yx(t,e={isGlobPattern:()=>!1}){try{return(0,Gee.parse)(t,e)}catch(r){throw r.location&&(r.message=r.message.replace(/(\\.)?$/,` (line ${r.location.start.line}, column ${r.location.start.column})$1`)),r}}function cE(t,{endSemicolon:e=!1}={}){return t.map(({command:r,type:s},a)=>`${Ex(r)}${s===\";\"?a!==t.length-1||e?\";\":\"\":\" &\"}`).join(\" \")}function Ex(t){return`${uE(t.chain)}${t.then?` ${q_(t.then)}`:\"\"}`}function q_(t){return`${t.type} ${Ex(t.line)}`}function uE(t){return`${W_(t)}${t.then?` ${G_(t.then)}`:\"\"}`}function G_(t){return`${t.type} ${uE(t.chain)}`}function W_(t){switch(t.type){case\"command\":return`${t.envs.length>0?`${t.envs.map(e=>mx(e)).join(\" \")} `:\"\"}${t.args.map(e=>Y_(e)).join(\" \")}`;case\"subshell\":return`(${cE(t.subshell)})${t.args.length>0?` ${t.args.map(e=>H2(e)).join(\" \")}`:\"\"}`;case\"group\":return`{ ${cE(t.group,{endSemicolon:!0})} }${t.args.length>0?` ${t.args.map(e=>H2(e)).join(\" \")}`:\"\"}`;case\"envs\":return t.envs.map(e=>mx(e)).join(\" \");default:throw new Error(`Unsupported command type:  \"${t.type}\"`)}}function mx(t){return`${t.name}=${t.args[0]?Bd(t.args[0]):\"\"}`}function Y_(t){switch(t.type){case\"redirection\":return H2(t);case\"argument\":return Bd(t);default:throw new Error(`Unsupported argument type: \"${t.type}\"`)}}function H2(t){return`${t.subtype} ${t.args.map(e=>Bd(e)).join(\" \")}`}function Bd(t){return t.segments.map(e=>V_(e)).join(\"\")}function V_(t){let e=(s,a)=>a?`\"${s}\"`:s,r=s=>s===\"\"?\"''\":s.match(/[()}<>$|&;\"'\\n\\t ]/)?s.match(/['\\t\\p{C}]/u)?s.match(/'/)?`\"${s.replace(/[\"$\\t\\p{C}]/u,z7e)}\"`:`$'${s.replace(/[\\t\\p{C}]/u,Yee)}'`:`'${s}'`:s;switch(t.type){case\"text\":return r(t.text);case\"glob\":return t.pattern;case\"shell\":return e(`$(${cE(t.shell)})`,t.quoted);case\"variable\":return e(typeof t.defaultValue>\"u\"?typeof t.alternativeValue>\"u\"?`\\${${t.name}}`:t.alternativeValue.length===0?`\\${${t.name}:+}`:`\\${${t.name}:+${t.alternativeValue.map(s=>Bd(s)).join(\" \")}}`:t.defaultValue.length===0?`\\${${t.name}:-}`:`\\${${t.name}:-${t.defaultValue.map(s=>Bd(s)).join(\" \")}}`,t.quoted);case\"arithmetic\":return`$(( ${Ix(t.arithmetic)} ))`;default:throw new Error(`Unsupported argument segment type: \"${t.type}\"`)}}function Ix(t){let e=a=>{switch(a){case\"addition\":return\"+\";case\"subtraction\":return\"-\";case\"multiplication\":return\"*\";case\"division\":return\"/\";default:throw new Error(`Can't extract operator from arithmetic expression of type \"${a}\"`)}},r=(a,n)=>n?`( ${a} )`:a,s=a=>r(Ix(a),![\"number\",\"variable\"].includes(a.type));switch(t.type){case\"number\":return String(t.value);case\"variable\":return t.name;default:return`${s(t.left)} ${e(t.type)} ${s(t.right)}`}}var Gee,Wee,J7e,Yee,z7e,Vee=Ct(()=>{Gee=et(qee());Wee=new Map([[\"\\f\",\"\\\\f\"],[`\n`,\"\\\\n\"],[\"\\r\",\"\\\\r\"],[\"\t\",\"\\\\t\"],[\"\\v\",\"\\\\v\"],[\"\\0\",\"\\\\0\"]]),J7e=new Map([[\"\\\\\",\"\\\\\\\\\"],[\"$\",\"\\\\$\"],['\"','\\\\\"'],...Array.from(Wee,([t,e])=>[t,`\"$'${e}'\"`])]),Yee=t=>Wee.get(t)??`\\\\x${t.charCodeAt(0).toString(16).padStart(2,\"0\")}`,z7e=t=>J7e.get(t)??`\"$'${Yee(t)}'\"`});var Jee=L((zGt,Kee)=>{\"use strict\";function Z7e(t,e){function r(){this.constructor=t}r.prototype=e.prototype,t.prototype=new r}function vd(t,e,r,s){this.message=t,this.expected=e,this.found=r,this.location=s,this.name=\"SyntaxError\",typeof Error.captureStackTrace==\"function\"&&Error.captureStackTrace(this,vd)}Z7e(vd,Error);vd.buildMessage=function(t,e){var r={literal:function(h){return'\"'+a(h.text)+'\"'},class:function(h){var E=\"\",C;for(C=0;C<h.parts.length;C++)E+=h.parts[C]instanceof Array?n(h.parts[C][0])+\"-\"+n(h.parts[C][1]):n(h.parts[C]);return\"[\"+(h.inverted?\"^\":\"\")+E+\"]\"},any:function(h){return\"any character\"},end:function(h){return\"end of input\"},other:function(h){return h.description}};function s(h){return h.charCodeAt(0).toString(16).toUpperCase()}function a(h){return h.replace(/\\\\/g,\"\\\\\\\\\").replace(/\"/g,'\\\\\"').replace(/\\0/g,\"\\\\0\").replace(/\\t/g,\"\\\\t\").replace(/\\n/g,\"\\\\n\").replace(/\\r/g,\"\\\\r\").replace(/[\\x00-\\x0F]/g,function(E){return\"\\\\x0\"+s(E)}).replace(/[\\x10-\\x1F\\x7F-\\x9F]/g,function(E){return\"\\\\x\"+s(E)})}function n(h){return h.replace(/\\\\/g,\"\\\\\\\\\").replace(/\\]/g,\"\\\\]\").replace(/\\^/g,\"\\\\^\").replace(/-/g,\"\\\\-\").replace(/\\0/g,\"\\\\0\").replace(/\\t/g,\"\\\\t\").replace(/\\n/g,\"\\\\n\").replace(/\\r/g,\"\\\\r\").replace(/[\\x00-\\x0F]/g,function(E){return\"\\\\x0\"+s(E)}).replace(/[\\x10-\\x1F\\x7F-\\x9F]/g,function(E){return\"\\\\x\"+s(E)})}function c(h){return r[h.type](h)}function f(h){var E=new Array(h.length),C,S;for(C=0;C<h.length;C++)E[C]=c(h[C]);if(E.sort(),E.length>0){for(C=1,S=1;C<E.length;C++)E[C-1]!==E[C]&&(E[S]=E[C],S++);E.length=S}switch(E.length){case 1:return E[0];case 2:return E[0]+\" or \"+E[1];default:return E.slice(0,-1).join(\", \")+\", or \"+E[E.length-1]}}function p(h){return h?'\"'+a(h)+'\"':\"end of input\"}return\"Expected \"+f(t)+\" but \"+p(e)+\" found.\"};function X7e(t,e){e=e!==void 0?e:{};var r={},s={resolution:Ne},a=Ne,n=\"/\",c=ye(\"/\",!1),f=function(_e,x){return{from:_e,descriptor:x}},p=function(_e){return{descriptor:_e}},h=\"@\",E=ye(\"@\",!1),C=function(_e,x){return{fullName:_e,description:x}},S=function(_e){return{fullName:_e}},P=function(){return Be()},I=/^[^\\/@]/,R=fe([\"/\",\"@\"],!0,!1),N=/^[^\\/]/,U=fe([\"/\"],!0,!1),W=0,te=0,ie=[{line:1,column:1}],Ae=0,ce=[],me=0,pe;if(\"startRule\"in e){if(!(e.startRule in s))throw new Error(`Can't start parsing from rule \"`+e.startRule+'\".');a=s[e.startRule]}function Be(){return t.substring(te,W)}function Ce(){return dt(te,W)}function g(_e,x){throw x=x!==void 0?x:dt(te,W),Fe([De(_e)],t.substring(te,W),x)}function we(_e,x){throw x=x!==void 0?x:dt(te,W),rt(_e,x)}function ye(_e,x){return{type:\"literal\",text:_e,ignoreCase:x}}function fe(_e,x,w){return{type:\"class\",parts:_e,inverted:x,ignoreCase:w}}function se(){return{type:\"any\"}}function X(){return{type:\"end\"}}function De(_e){return{type:\"other\",description:_e}}function Re(_e){var x=ie[_e],w;if(x)return x;for(w=_e-1;!ie[w];)w--;for(x=ie[w],x={line:x.line,column:x.column};w<_e;)t.charCodeAt(w)===10?(x.line++,x.column=1):x.column++,w++;return ie[_e]=x,x}function dt(_e,x){var w=Re(_e),b=Re(x);return{start:{offset:_e,line:w.line,column:w.column},end:{offset:x,line:b.line,column:b.column}}}function j(_e){W<Ae||(W>Ae&&(Ae=W,ce=[]),ce.push(_e))}function rt(_e,x){return new vd(_e,null,null,x)}function Fe(_e,x,w){return new vd(vd.buildMessage(_e,x),_e,x,w)}function Ne(){var _e,x,w,b;return _e=W,x=Pe(),x!==r?(t.charCodeAt(W)===47?(w=n,W++):(w=r,me===0&&j(c)),w!==r?(b=Pe(),b!==r?(te=_e,x=f(x,b),_e=x):(W=_e,_e=r)):(W=_e,_e=r)):(W=_e,_e=r),_e===r&&(_e=W,x=Pe(),x!==r&&(te=_e,x=p(x)),_e=x),_e}function Pe(){var _e,x,w,b;return _e=W,x=Ye(),x!==r?(t.charCodeAt(W)===64?(w=h,W++):(w=r,me===0&&j(E)),w!==r?(b=it(),b!==r?(te=_e,x=C(x,b),_e=x):(W=_e,_e=r)):(W=_e,_e=r)):(W=_e,_e=r),_e===r&&(_e=W,x=Ye(),x!==r&&(te=_e,x=S(x)),_e=x),_e}function Ye(){var _e,x,w,b,y;return _e=W,t.charCodeAt(W)===64?(x=h,W++):(x=r,me===0&&j(E)),x!==r?(w=ke(),w!==r?(t.charCodeAt(W)===47?(b=n,W++):(b=r,me===0&&j(c)),b!==r?(y=ke(),y!==r?(te=_e,x=P(),_e=x):(W=_e,_e=r)):(W=_e,_e=r)):(W=_e,_e=r)):(W=_e,_e=r),_e===r&&(_e=W,x=ke(),x!==r&&(te=_e,x=P()),_e=x),_e}function ke(){var _e,x,w;if(_e=W,x=[],I.test(t.charAt(W))?(w=t.charAt(W),W++):(w=r,me===0&&j(R)),w!==r)for(;w!==r;)x.push(w),I.test(t.charAt(W))?(w=t.charAt(W),W++):(w=r,me===0&&j(R));else x=r;return x!==r&&(te=_e,x=P()),_e=x,_e}function it(){var _e,x,w;if(_e=W,x=[],N.test(t.charAt(W))?(w=t.charAt(W),W++):(w=r,me===0&&j(U)),w!==r)for(;w!==r;)x.push(w),N.test(t.charAt(W))?(w=t.charAt(W),W++):(w=r,me===0&&j(U));else x=r;return x!==r&&(te=_e,x=P()),_e=x,_e}if(pe=a(),pe!==r&&W===t.length)return pe;throw pe!==r&&W<t.length&&j(X()),Fe(ce,Ae<t.length?t.charAt(Ae):null,Ae<t.length?dt(Ae,Ae+1):dt(Ae,Ae))}Kee.exports={SyntaxError:vd,parse:X7e}});function Cx(t){let e=t.match(/^\\*{1,2}\\/(.*)/);if(e)throw new Error(`The override for '${t}' includes a glob pattern. Glob patterns have been removed since their behaviours don't match what you'd expect. Set the override to '${e[1]}' instead.`);try{return(0,zee.parse)(t)}catch(r){throw r.location&&(r.message=r.message.replace(/(\\.)?$/,` (line ${r.location.start.line}, column ${r.location.start.column})$1`)),r}}function wx(t){let e=\"\";return t.from&&(e+=t.from.fullName,t.from.description&&(e+=`@${t.from.description}`),e+=\"/\"),e+=t.descriptor.fullName,t.descriptor.description&&(e+=`@${t.descriptor.description}`),e}var zee,Zee=Ct(()=>{zee=et(Jee())});var Dd=L((XGt,Sd)=>{\"use strict\";function Xee(t){return typeof t>\"u\"||t===null}function $7e(t){return typeof t==\"object\"&&t!==null}function eKe(t){return Array.isArray(t)?t:Xee(t)?[]:[t]}function tKe(t,e){var r,s,a,n;if(e)for(n=Object.keys(e),r=0,s=n.length;r<s;r+=1)a=n[r],t[a]=e[a];return t}function rKe(t,e){var r=\"\",s;for(s=0;s<e;s+=1)r+=t;return r}function nKe(t){return t===0&&Number.NEGATIVE_INFINITY===1/t}Sd.exports.isNothing=Xee;Sd.exports.isObject=$7e;Sd.exports.toArray=eKe;Sd.exports.repeat=rKe;Sd.exports.isNegativeZero=nKe;Sd.exports.extend=tKe});var fE=L(($Gt,$ee)=>{\"use strict\";function j2(t,e){Error.call(this),this.name=\"YAMLException\",this.reason=t,this.mark=e,this.message=(this.reason||\"(unknown reason)\")+(this.mark?\" \"+this.mark.toString():\"\"),Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=new Error().stack||\"\"}j2.prototype=Object.create(Error.prototype);j2.prototype.constructor=j2;j2.prototype.toString=function(e){var r=this.name+\": \";return r+=this.reason||\"(unknown reason)\",!e&&this.mark&&(r+=\" \"+this.mark.toString()),r};$ee.exports=j2});var rte=L((e5t,tte)=>{\"use strict\";var ete=Dd();function K_(t,e,r,s,a){this.name=t,this.buffer=e,this.position=r,this.line=s,this.column=a}K_.prototype.getSnippet=function(e,r){var s,a,n,c,f;if(!this.buffer)return null;for(e=e||4,r=r||75,s=\"\",a=this.position;a>0&&`\\0\\r\n\\x85\\u2028\\u2029`.indexOf(this.buffer.charAt(a-1))===-1;)if(a-=1,this.position-a>r/2-1){s=\" ... \",a+=5;break}for(n=\"\",c=this.position;c<this.buffer.length&&`\\0\\r\n\\x85\\u2028\\u2029`.indexOf(this.buffer.charAt(c))===-1;)if(c+=1,c-this.position>r/2-1){n=\" ... \",c-=5;break}return f=this.buffer.slice(a,c),ete.repeat(\" \",e)+s+f+n+`\n`+ete.repeat(\" \",e+this.position-a+s.length)+\"^\"};K_.prototype.toString=function(e){var r,s=\"\";return this.name&&(s+='in \"'+this.name+'\" '),s+=\"at line \"+(this.line+1)+\", column \"+(this.column+1),e||(r=this.getSnippet(),r&&(s+=`:\n`+r)),s};tte.exports=K_});var bs=L((t5t,ite)=>{\"use strict\";var nte=fE(),iKe=[\"kind\",\"resolve\",\"construct\",\"instanceOf\",\"predicate\",\"represent\",\"defaultStyle\",\"styleAliases\"],sKe=[\"scalar\",\"sequence\",\"mapping\"];function oKe(t){var e={};return t!==null&&Object.keys(t).forEach(function(r){t[r].forEach(function(s){e[String(s)]=r})}),e}function aKe(t,e){if(e=e||{},Object.keys(e).forEach(function(r){if(iKe.indexOf(r)===-1)throw new nte('Unknown option \"'+r+'\" is met in definition of \"'+t+'\" YAML type.')}),this.tag=t,this.kind=e.kind||null,this.resolve=e.resolve||function(){return!0},this.construct=e.construct||function(r){return r},this.instanceOf=e.instanceOf||null,this.predicate=e.predicate||null,this.represent=e.represent||null,this.defaultStyle=e.defaultStyle||null,this.styleAliases=oKe(e.styleAliases||null),sKe.indexOf(this.kind)===-1)throw new nte('Unknown kind \"'+this.kind+'\" is specified for \"'+t+'\" YAML type.')}ite.exports=aKe});var bd=L((r5t,ote)=>{\"use strict\";var ste=Dd(),Bx=fE(),lKe=bs();function J_(t,e,r){var s=[];return t.include.forEach(function(a){r=J_(a,e,r)}),t[e].forEach(function(a){r.forEach(function(n,c){n.tag===a.tag&&n.kind===a.kind&&s.push(c)}),r.push(a)}),r.filter(function(a,n){return s.indexOf(n)===-1})}function cKe(){var t={scalar:{},sequence:{},mapping:{},fallback:{}},e,r;function s(a){t[a.kind][a.tag]=t.fallback[a.tag]=a}for(e=0,r=arguments.length;e<r;e+=1)arguments[e].forEach(s);return t}function AE(t){this.include=t.include||[],this.implicit=t.implicit||[],this.explicit=t.explicit||[],this.implicit.forEach(function(e){if(e.loadKind&&e.loadKind!==\"scalar\")throw new Bx(\"There is a non-scalar type in the implicit list of a schema. Implicit resolving of such types is not supported.\")}),this.compiledImplicit=J_(this,\"implicit\",[]),this.compiledExplicit=J_(this,\"explicit\",[]),this.compiledTypeMap=cKe(this.compiledImplicit,this.compiledExplicit)}AE.DEFAULT=null;AE.create=function(){var e,r;switch(arguments.length){case 1:e=AE.DEFAULT,r=arguments[0];break;case 2:e=arguments[0],r=arguments[1];break;default:throw new Bx(\"Wrong number of arguments for Schema.create function\")}if(e=ste.toArray(e),r=ste.toArray(r),!e.every(function(s){return s instanceof AE}))throw new Bx(\"Specified list of super schemas (or a single Schema object) contains a non-Schema object.\");if(!r.every(function(s){return s instanceof lKe}))throw new Bx(\"Specified list of YAML types (or a single Type object) contains a non-Type object.\");return new AE({include:e,explicit:r})};ote.exports=AE});var lte=L((n5t,ate)=>{\"use strict\";var uKe=bs();ate.exports=new uKe(\"tag:yaml.org,2002:str\",{kind:\"scalar\",construct:function(t){return t!==null?t:\"\"}})});var ute=L((i5t,cte)=>{\"use strict\";var fKe=bs();cte.exports=new fKe(\"tag:yaml.org,2002:seq\",{kind:\"sequence\",construct:function(t){return t!==null?t:[]}})});var Ate=L((s5t,fte)=>{\"use strict\";var AKe=bs();fte.exports=new AKe(\"tag:yaml.org,2002:map\",{kind:\"mapping\",construct:function(t){return t!==null?t:{}}})});var vx=L((o5t,pte)=>{\"use strict\";var pKe=bd();pte.exports=new pKe({explicit:[lte(),ute(),Ate()]})});var gte=L((a5t,hte)=>{\"use strict\";var hKe=bs();function gKe(t){if(t===null)return!0;var e=t.length;return e===1&&t===\"~\"||e===4&&(t===\"null\"||t===\"Null\"||t===\"NULL\")}function dKe(){return null}function mKe(t){return t===null}hte.exports=new hKe(\"tag:yaml.org,2002:null\",{kind:\"scalar\",resolve:gKe,construct:dKe,predicate:mKe,represent:{canonical:function(){return\"~\"},lowercase:function(){return\"null\"},uppercase:function(){return\"NULL\"},camelcase:function(){return\"Null\"}},defaultStyle:\"lowercase\"})});var mte=L((l5t,dte)=>{\"use strict\";var yKe=bs();function EKe(t){if(t===null)return!1;var e=t.length;return e===4&&(t===\"true\"||t===\"True\"||t===\"TRUE\")||e===5&&(t===\"false\"||t===\"False\"||t===\"FALSE\")}function IKe(t){return t===\"true\"||t===\"True\"||t===\"TRUE\"}function CKe(t){return Object.prototype.toString.call(t)===\"[object Boolean]\"}dte.exports=new yKe(\"tag:yaml.org,2002:bool\",{kind:\"scalar\",resolve:EKe,construct:IKe,predicate:CKe,represent:{lowercase:function(t){return t?\"true\":\"false\"},uppercase:function(t){return t?\"TRUE\":\"FALSE\"},camelcase:function(t){return t?\"True\":\"False\"}},defaultStyle:\"lowercase\"})});var Ete=L((c5t,yte)=>{\"use strict\";var wKe=Dd(),BKe=bs();function vKe(t){return 48<=t&&t<=57||65<=t&&t<=70||97<=t&&t<=102}function SKe(t){return 48<=t&&t<=55}function DKe(t){return 48<=t&&t<=57}function bKe(t){if(t===null)return!1;var e=t.length,r=0,s=!1,a;if(!e)return!1;if(a=t[r],(a===\"-\"||a===\"+\")&&(a=t[++r]),a===\"0\"){if(r+1===e)return!0;if(a=t[++r],a===\"b\"){for(r++;r<e;r++)if(a=t[r],a!==\"_\"){if(a!==\"0\"&&a!==\"1\")return!1;s=!0}return s&&a!==\"_\"}if(a===\"x\"){for(r++;r<e;r++)if(a=t[r],a!==\"_\"){if(!vKe(t.charCodeAt(r)))return!1;s=!0}return s&&a!==\"_\"}for(;r<e;r++)if(a=t[r],a!==\"_\"){if(!SKe(t.charCodeAt(r)))return!1;s=!0}return s&&a!==\"_\"}if(a===\"_\")return!1;for(;r<e;r++)if(a=t[r],a!==\"_\"){if(a===\":\")break;if(!DKe(t.charCodeAt(r)))return!1;s=!0}return!s||a===\"_\"?!1:a!==\":\"?!0:/^(:[0-5]?[0-9])+$/.test(t.slice(r))}function PKe(t){var e=t,r=1,s,a,n=[];return e.indexOf(\"_\")!==-1&&(e=e.replace(/_/g,\"\")),s=e[0],(s===\"-\"||s===\"+\")&&(s===\"-\"&&(r=-1),e=e.slice(1),s=e[0]),e===\"0\"?0:s===\"0\"?e[1]===\"b\"?r*parseInt(e.slice(2),2):e[1]===\"x\"?r*parseInt(e,16):r*parseInt(e,8):e.indexOf(\":\")!==-1?(e.split(\":\").forEach(function(c){n.unshift(parseInt(c,10))}),e=0,a=1,n.forEach(function(c){e+=c*a,a*=60}),r*e):r*parseInt(e,10)}function xKe(t){return Object.prototype.toString.call(t)===\"[object Number]\"&&t%1===0&&!wKe.isNegativeZero(t)}yte.exports=new BKe(\"tag:yaml.org,2002:int\",{kind:\"scalar\",resolve:bKe,construct:PKe,predicate:xKe,represent:{binary:function(t){return t>=0?\"0b\"+t.toString(2):\"-0b\"+t.toString(2).slice(1)},octal:function(t){return t>=0?\"0\"+t.toString(8):\"-0\"+t.toString(8).slice(1)},decimal:function(t){return t.toString(10)},hexadecimal:function(t){return t>=0?\"0x\"+t.toString(16).toUpperCase():\"-0x\"+t.toString(16).toUpperCase().slice(1)}},defaultStyle:\"decimal\",styleAliases:{binary:[2,\"bin\"],octal:[8,\"oct\"],decimal:[10,\"dec\"],hexadecimal:[16,\"hex\"]}})});var wte=L((u5t,Cte)=>{\"use strict\";var Ite=Dd(),kKe=bs(),QKe=new RegExp(\"^(?:[-+]?(?:0|[1-9][0-9_]*)(?:\\\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?|\\\\.[0-9_]+(?:[eE][-+]?[0-9]+)?|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\\\.[0-9_]*|[-+]?\\\\.(?:inf|Inf|INF)|\\\\.(?:nan|NaN|NAN))$\");function TKe(t){return!(t===null||!QKe.test(t)||t[t.length-1]===\"_\")}function RKe(t){var e,r,s,a;return e=t.replace(/_/g,\"\").toLowerCase(),r=e[0]===\"-\"?-1:1,a=[],\"+-\".indexOf(e[0])>=0&&(e=e.slice(1)),e===\".inf\"?r===1?Number.POSITIVE_INFINITY:Number.NEGATIVE_INFINITY:e===\".nan\"?NaN:e.indexOf(\":\")>=0?(e.split(\":\").forEach(function(n){a.unshift(parseFloat(n,10))}),e=0,s=1,a.forEach(function(n){e+=n*s,s*=60}),r*e):r*parseFloat(e,10)}var FKe=/^[-+]?[0-9]+e/;function NKe(t,e){var r;if(isNaN(t))switch(e){case\"lowercase\":return\".nan\";case\"uppercase\":return\".NAN\";case\"camelcase\":return\".NaN\"}else if(Number.POSITIVE_INFINITY===t)switch(e){case\"lowercase\":return\".inf\";case\"uppercase\":return\".INF\";case\"camelcase\":return\".Inf\"}else if(Number.NEGATIVE_INFINITY===t)switch(e){case\"lowercase\":return\"-.inf\";case\"uppercase\":return\"-.INF\";case\"camelcase\":return\"-.Inf\"}else if(Ite.isNegativeZero(t))return\"-0.0\";return r=t.toString(10),FKe.test(r)?r.replace(\"e\",\".e\"):r}function OKe(t){return Object.prototype.toString.call(t)===\"[object Number]\"&&(t%1!==0||Ite.isNegativeZero(t))}Cte.exports=new kKe(\"tag:yaml.org,2002:float\",{kind:\"scalar\",resolve:TKe,construct:RKe,predicate:OKe,represent:NKe,defaultStyle:\"lowercase\"})});var z_=L((f5t,Bte)=>{\"use strict\";var LKe=bd();Bte.exports=new LKe({include:[vx()],implicit:[gte(),mte(),Ete(),wte()]})});var Z_=L((A5t,vte)=>{\"use strict\";var MKe=bd();vte.exports=new MKe({include:[z_()]})});var Pte=L((p5t,bte)=>{\"use strict\";var _Ke=bs(),Ste=new RegExp(\"^([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])$\"),Dte=new RegExp(\"^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)(?:[Tt]|[ \\\\t]+)([0-9][0-9]?):([0-9][0-9]):([0-9][0-9])(?:\\\\.([0-9]*))?(?:[ \\\\t]*(Z|([-+])([0-9][0-9]?)(?::([0-9][0-9]))?))?$\");function UKe(t){return t===null?!1:Ste.exec(t)!==null||Dte.exec(t)!==null}function HKe(t){var e,r,s,a,n,c,f,p=0,h=null,E,C,S;if(e=Ste.exec(t),e===null&&(e=Dte.exec(t)),e===null)throw new Error(\"Date resolve error\");if(r=+e[1],s=+e[2]-1,a=+e[3],!e[4])return new Date(Date.UTC(r,s,a));if(n=+e[4],c=+e[5],f=+e[6],e[7]){for(p=e[7].slice(0,3);p.length<3;)p+=\"0\";p=+p}return e[9]&&(E=+e[10],C=+(e[11]||0),h=(E*60+C)*6e4,e[9]===\"-\"&&(h=-h)),S=new Date(Date.UTC(r,s,a,n,c,f,p)),h&&S.setTime(S.getTime()-h),S}function jKe(t){return t.toISOString()}bte.exports=new _Ke(\"tag:yaml.org,2002:timestamp\",{kind:\"scalar\",resolve:UKe,construct:HKe,instanceOf:Date,represent:jKe})});var kte=L((h5t,xte)=>{\"use strict\";var qKe=bs();function GKe(t){return t===\"<<\"||t===null}xte.exports=new qKe(\"tag:yaml.org,2002:merge\",{kind:\"scalar\",resolve:GKe})});var Rte=L((g5t,Tte)=>{\"use strict\";var Pd;try{Qte=Ie,Pd=Qte(\"buffer\").Buffer}catch{}var Qte,WKe=bs(),X_=`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n\\r`;function YKe(t){if(t===null)return!1;var e,r,s=0,a=t.length,n=X_;for(r=0;r<a;r++)if(e=n.indexOf(t.charAt(r)),!(e>64)){if(e<0)return!1;s+=6}return s%8===0}function VKe(t){var e,r,s=t.replace(/[\\r\\n=]/g,\"\"),a=s.length,n=X_,c=0,f=[];for(e=0;e<a;e++)e%4===0&&e&&(f.push(c>>16&255),f.push(c>>8&255),f.push(c&255)),c=c<<6|n.indexOf(s.charAt(e));return r=a%4*6,r===0?(f.push(c>>16&255),f.push(c>>8&255),f.push(c&255)):r===18?(f.push(c>>10&255),f.push(c>>2&255)):r===12&&f.push(c>>4&255),Pd?Pd.from?Pd.from(f):new Pd(f):f}function KKe(t){var e=\"\",r=0,s,a,n=t.length,c=X_;for(s=0;s<n;s++)s%3===0&&s&&(e+=c[r>>18&63],e+=c[r>>12&63],e+=c[r>>6&63],e+=c[r&63]),r=(r<<8)+t[s];return a=n%3,a===0?(e+=c[r>>18&63],e+=c[r>>12&63],e+=c[r>>6&63],e+=c[r&63]):a===2?(e+=c[r>>10&63],e+=c[r>>4&63],e+=c[r<<2&63],e+=c[64]):a===1&&(e+=c[r>>2&63],e+=c[r<<4&63],e+=c[64],e+=c[64]),e}function JKe(t){return Pd&&Pd.isBuffer(t)}Tte.exports=new WKe(\"tag:yaml.org,2002:binary\",{kind:\"scalar\",resolve:YKe,construct:VKe,predicate:JKe,represent:KKe})});var Nte=L((m5t,Fte)=>{\"use strict\";var zKe=bs(),ZKe=Object.prototype.hasOwnProperty,XKe=Object.prototype.toString;function $Ke(t){if(t===null)return!0;var e=[],r,s,a,n,c,f=t;for(r=0,s=f.length;r<s;r+=1){if(a=f[r],c=!1,XKe.call(a)!==\"[object Object]\")return!1;for(n in a)if(ZKe.call(a,n))if(!c)c=!0;else return!1;if(!c)return!1;if(e.indexOf(n)===-1)e.push(n);else return!1}return!0}function eJe(t){return t!==null?t:[]}Fte.exports=new zKe(\"tag:yaml.org,2002:omap\",{kind:\"sequence\",resolve:$Ke,construct:eJe})});var Lte=L((y5t,Ote)=>{\"use strict\";var tJe=bs(),rJe=Object.prototype.toString;function nJe(t){if(t===null)return!0;var e,r,s,a,n,c=t;for(n=new Array(c.length),e=0,r=c.length;e<r;e+=1){if(s=c[e],rJe.call(s)!==\"[object Object]\"||(a=Object.keys(s),a.length!==1))return!1;n[e]=[a[0],s[a[0]]]}return!0}function iJe(t){if(t===null)return[];var e,r,s,a,n,c=t;for(n=new Array(c.length),e=0,r=c.length;e<r;e+=1)s=c[e],a=Object.keys(s),n[e]=[a[0],s[a[0]]];return n}Ote.exports=new tJe(\"tag:yaml.org,2002:pairs\",{kind:\"sequence\",resolve:nJe,construct:iJe})});var _te=L((E5t,Mte)=>{\"use strict\";var sJe=bs(),oJe=Object.prototype.hasOwnProperty;function aJe(t){if(t===null)return!0;var e,r=t;for(e in r)if(oJe.call(r,e)&&r[e]!==null)return!1;return!0}function lJe(t){return t!==null?t:{}}Mte.exports=new sJe(\"tag:yaml.org,2002:set\",{kind:\"mapping\",resolve:aJe,construct:lJe})});var pE=L((I5t,Ute)=>{\"use strict\";var cJe=bd();Ute.exports=new cJe({include:[Z_()],implicit:[Pte(),kte()],explicit:[Rte(),Nte(),Lte(),_te()]})});var jte=L((C5t,Hte)=>{\"use strict\";var uJe=bs();function fJe(){return!0}function AJe(){}function pJe(){return\"\"}function hJe(t){return typeof t>\"u\"}Hte.exports=new uJe(\"tag:yaml.org,2002:js/undefined\",{kind:\"scalar\",resolve:fJe,construct:AJe,predicate:hJe,represent:pJe})});var Gte=L((w5t,qte)=>{\"use strict\";var gJe=bs();function dJe(t){if(t===null||t.length===0)return!1;var e=t,r=/\\/([gim]*)$/.exec(t),s=\"\";return!(e[0]===\"/\"&&(r&&(s=r[1]),s.length>3||e[e.length-s.length-1]!==\"/\"))}function mJe(t){var e=t,r=/\\/([gim]*)$/.exec(t),s=\"\";return e[0]===\"/\"&&(r&&(s=r[1]),e=e.slice(1,e.length-s.length-1)),new RegExp(e,s)}function yJe(t){var e=\"/\"+t.source+\"/\";return t.global&&(e+=\"g\"),t.multiline&&(e+=\"m\"),t.ignoreCase&&(e+=\"i\"),e}function EJe(t){return Object.prototype.toString.call(t)===\"[object RegExp]\"}qte.exports=new gJe(\"tag:yaml.org,2002:js/regexp\",{kind:\"scalar\",resolve:dJe,construct:mJe,predicate:EJe,represent:yJe})});var Vte=L((B5t,Yte)=>{\"use strict\";var Sx;try{Wte=Ie,Sx=Wte(\"esprima\")}catch{typeof window<\"u\"&&(Sx=window.esprima)}var Wte,IJe=bs();function CJe(t){if(t===null)return!1;try{var e=\"(\"+t+\")\",r=Sx.parse(e,{range:!0});return!(r.type!==\"Program\"||r.body.length!==1||r.body[0].type!==\"ExpressionStatement\"||r.body[0].expression.type!==\"ArrowFunctionExpression\"&&r.body[0].expression.type!==\"FunctionExpression\")}catch{return!1}}function wJe(t){var e=\"(\"+t+\")\",r=Sx.parse(e,{range:!0}),s=[],a;if(r.type!==\"Program\"||r.body.length!==1||r.body[0].type!==\"ExpressionStatement\"||r.body[0].expression.type!==\"ArrowFunctionExpression\"&&r.body[0].expression.type!==\"FunctionExpression\")throw new Error(\"Failed to resolve function\");return r.body[0].expression.params.forEach(function(n){s.push(n.name)}),a=r.body[0].expression.body.range,r.body[0].expression.body.type===\"BlockStatement\"?new Function(s,e.slice(a[0]+1,a[1]-1)):new Function(s,\"return \"+e.slice(a[0],a[1]))}function BJe(t){return t.toString()}function vJe(t){return Object.prototype.toString.call(t)===\"[object Function]\"}Yte.exports=new IJe(\"tag:yaml.org,2002:js/function\",{kind:\"scalar\",resolve:CJe,construct:wJe,predicate:vJe,represent:BJe})});var q2=L((S5t,Jte)=>{\"use strict\";var Kte=bd();Jte.exports=Kte.DEFAULT=new Kte({include:[pE()],explicit:[jte(),Gte(),Vte()]})});var hre=L((D5t,G2)=>{\"use strict\";var wp=Dd(),rre=fE(),SJe=rte(),nre=pE(),DJe=q2(),o0=Object.prototype.hasOwnProperty,Dx=1,ire=2,sre=3,bx=4,$_=1,bJe=2,zte=3,PJe=/[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F-\\x84\\x86-\\x9F\\uFFFE\\uFFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF]/,xJe=/[\\x85\\u2028\\u2029]/,kJe=/[,\\[\\]\\{\\}]/,ore=/^(?:!|!!|![a-z\\-]+!)$/i,are=/^(?:!|[^,\\[\\]\\{\\}])(?:%[0-9a-f]{2}|[0-9a-z\\-#;\\/\\?:@&=\\+\\$,_\\.!~\\*'\\(\\)\\[\\]])*$/i;function Zte(t){return Object.prototype.toString.call(t)}function qf(t){return t===10||t===13}function kd(t){return t===9||t===32}function nl(t){return t===9||t===32||t===10||t===13}function hE(t){return t===44||t===91||t===93||t===123||t===125}function QJe(t){var e;return 48<=t&&t<=57?t-48:(e=t|32,97<=e&&e<=102?e-97+10:-1)}function TJe(t){return t===120?2:t===117?4:t===85?8:0}function RJe(t){return 48<=t&&t<=57?t-48:-1}function Xte(t){return t===48?\"\\0\":t===97?\"\\x07\":t===98?\"\\b\":t===116||t===9?\"\t\":t===110?`\n`:t===118?\"\\v\":t===102?\"\\f\":t===114?\"\\r\":t===101?\"\\x1B\":t===32?\" \":t===34?'\"':t===47?\"/\":t===92?\"\\\\\":t===78?\"\\x85\":t===95?\"\\xA0\":t===76?\"\\u2028\":t===80?\"\\u2029\":\"\"}function FJe(t){return t<=65535?String.fromCharCode(t):String.fromCharCode((t-65536>>10)+55296,(t-65536&1023)+56320)}var lre=new Array(256),cre=new Array(256);for(xd=0;xd<256;xd++)lre[xd]=Xte(xd)?1:0,cre[xd]=Xte(xd);var xd;function NJe(t,e){this.input=t,this.filename=e.filename||null,this.schema=e.schema||DJe,this.onWarning=e.onWarning||null,this.legacy=e.legacy||!1,this.json=e.json||!1,this.listener=e.listener||null,this.implicitTypes=this.schema.compiledImplicit,this.typeMap=this.schema.compiledTypeMap,this.length=t.length,this.position=0,this.line=0,this.lineStart=0,this.lineIndent=0,this.documents=[]}function ure(t,e){return new rre(e,new SJe(t.filename,t.input,t.position,t.line,t.position-t.lineStart))}function Rr(t,e){throw ure(t,e)}function Px(t,e){t.onWarning&&t.onWarning.call(null,ure(t,e))}var $te={YAML:function(e,r,s){var a,n,c;e.version!==null&&Rr(e,\"duplication of %YAML directive\"),s.length!==1&&Rr(e,\"YAML directive accepts exactly one argument\"),a=/^([0-9]+)\\.([0-9]+)$/.exec(s[0]),a===null&&Rr(e,\"ill-formed argument of the YAML directive\"),n=parseInt(a[1],10),c=parseInt(a[2],10),n!==1&&Rr(e,\"unacceptable YAML version of the document\"),e.version=s[0],e.checkLineBreaks=c<2,c!==1&&c!==2&&Px(e,\"unsupported YAML version of the document\")},TAG:function(e,r,s){var a,n;s.length!==2&&Rr(e,\"TAG directive accepts exactly two arguments\"),a=s[0],n=s[1],ore.test(a)||Rr(e,\"ill-formed tag handle (first argument) of the TAG directive\"),o0.call(e.tagMap,a)&&Rr(e,'there is a previously declared suffix for \"'+a+'\" tag handle'),are.test(n)||Rr(e,\"ill-formed tag prefix (second argument) of the TAG directive\"),e.tagMap[a]=n}};function s0(t,e,r,s){var a,n,c,f;if(e<r){if(f=t.input.slice(e,r),s)for(a=0,n=f.length;a<n;a+=1)c=f.charCodeAt(a),c===9||32<=c&&c<=1114111||Rr(t,\"expected valid JSON character\");else PJe.test(f)&&Rr(t,\"the stream contains non-printable characters\");t.result+=f}}function ere(t,e,r,s){var a,n,c,f;for(wp.isObject(r)||Rr(t,\"cannot merge mappings; the provided source object is unacceptable\"),a=Object.keys(r),c=0,f=a.length;c<f;c+=1)n=a[c],o0.call(e,n)||(e[n]=r[n],s[n]=!0)}function gE(t,e,r,s,a,n,c,f){var p,h;if(Array.isArray(a))for(a=Array.prototype.slice.call(a),p=0,h=a.length;p<h;p+=1)Array.isArray(a[p])&&Rr(t,\"nested arrays are not supported inside keys\"),typeof a==\"object\"&&Zte(a[p])===\"[object Object]\"&&(a[p]=\"[object Object]\");if(typeof a==\"object\"&&Zte(a)===\"[object Object]\"&&(a=\"[object Object]\"),a=String(a),e===null&&(e={}),s===\"tag:yaml.org,2002:merge\")if(Array.isArray(n))for(p=0,h=n.length;p<h;p+=1)ere(t,e,n[p],r);else ere(t,e,n,r);else!t.json&&!o0.call(r,a)&&o0.call(e,a)&&(t.line=c||t.line,t.position=f||t.position,Rr(t,\"duplicated mapping key\")),e[a]=n,delete r[a];return e}function eU(t){var e;e=t.input.charCodeAt(t.position),e===10?t.position++:e===13?(t.position++,t.input.charCodeAt(t.position)===10&&t.position++):Rr(t,\"a line break is expected\"),t.line+=1,t.lineStart=t.position}function ls(t,e,r){for(var s=0,a=t.input.charCodeAt(t.position);a!==0;){for(;kd(a);)a=t.input.charCodeAt(++t.position);if(e&&a===35)do a=t.input.charCodeAt(++t.position);while(a!==10&&a!==13&&a!==0);if(qf(a))for(eU(t),a=t.input.charCodeAt(t.position),s++,t.lineIndent=0;a===32;)t.lineIndent++,a=t.input.charCodeAt(++t.position);else break}return r!==-1&&s!==0&&t.lineIndent<r&&Px(t,\"deficient indentation\"),s}function xx(t){var e=t.position,r;return r=t.input.charCodeAt(e),!!((r===45||r===46)&&r===t.input.charCodeAt(e+1)&&r===t.input.charCodeAt(e+2)&&(e+=3,r=t.input.charCodeAt(e),r===0||nl(r)))}function tU(t,e){e===1?t.result+=\" \":e>1&&(t.result+=wp.repeat(`\n`,e-1))}function OJe(t,e,r){var s,a,n,c,f,p,h,E,C=t.kind,S=t.result,P;if(P=t.input.charCodeAt(t.position),nl(P)||hE(P)||P===35||P===38||P===42||P===33||P===124||P===62||P===39||P===34||P===37||P===64||P===96||(P===63||P===45)&&(a=t.input.charCodeAt(t.position+1),nl(a)||r&&hE(a)))return!1;for(t.kind=\"scalar\",t.result=\"\",n=c=t.position,f=!1;P!==0;){if(P===58){if(a=t.input.charCodeAt(t.position+1),nl(a)||r&&hE(a))break}else if(P===35){if(s=t.input.charCodeAt(t.position-1),nl(s))break}else{if(t.position===t.lineStart&&xx(t)||r&&hE(P))break;if(qf(P))if(p=t.line,h=t.lineStart,E=t.lineIndent,ls(t,!1,-1),t.lineIndent>=e){f=!0,P=t.input.charCodeAt(t.position);continue}else{t.position=c,t.line=p,t.lineStart=h,t.lineIndent=E;break}}f&&(s0(t,n,c,!1),tU(t,t.line-p),n=c=t.position,f=!1),kd(P)||(c=t.position+1),P=t.input.charCodeAt(++t.position)}return s0(t,n,c,!1),t.result?!0:(t.kind=C,t.result=S,!1)}function LJe(t,e){var r,s,a;if(r=t.input.charCodeAt(t.position),r!==39)return!1;for(t.kind=\"scalar\",t.result=\"\",t.position++,s=a=t.position;(r=t.input.charCodeAt(t.position))!==0;)if(r===39)if(s0(t,s,t.position,!0),r=t.input.charCodeAt(++t.position),r===39)s=t.position,t.position++,a=t.position;else return!0;else qf(r)?(s0(t,s,a,!0),tU(t,ls(t,!1,e)),s=a=t.position):t.position===t.lineStart&&xx(t)?Rr(t,\"unexpected end of the document within a single quoted scalar\"):(t.position++,a=t.position);Rr(t,\"unexpected end of the stream within a single quoted scalar\")}function MJe(t,e){var r,s,a,n,c,f;if(f=t.input.charCodeAt(t.position),f!==34)return!1;for(t.kind=\"scalar\",t.result=\"\",t.position++,r=s=t.position;(f=t.input.charCodeAt(t.position))!==0;){if(f===34)return s0(t,r,t.position,!0),t.position++,!0;if(f===92){if(s0(t,r,t.position,!0),f=t.input.charCodeAt(++t.position),qf(f))ls(t,!1,e);else if(f<256&&lre[f])t.result+=cre[f],t.position++;else if((c=TJe(f))>0){for(a=c,n=0;a>0;a--)f=t.input.charCodeAt(++t.position),(c=QJe(f))>=0?n=(n<<4)+c:Rr(t,\"expected hexadecimal character\");t.result+=FJe(n),t.position++}else Rr(t,\"unknown escape sequence\");r=s=t.position}else qf(f)?(s0(t,r,s,!0),tU(t,ls(t,!1,e)),r=s=t.position):t.position===t.lineStart&&xx(t)?Rr(t,\"unexpected end of the document within a double quoted scalar\"):(t.position++,s=t.position)}Rr(t,\"unexpected end of the stream within a double quoted scalar\")}function _Je(t,e){var r=!0,s,a=t.tag,n,c=t.anchor,f,p,h,E,C,S={},P,I,R,N;if(N=t.input.charCodeAt(t.position),N===91)p=93,C=!1,n=[];else if(N===123)p=125,C=!0,n={};else return!1;for(t.anchor!==null&&(t.anchorMap[t.anchor]=n),N=t.input.charCodeAt(++t.position);N!==0;){if(ls(t,!0,e),N=t.input.charCodeAt(t.position),N===p)return t.position++,t.tag=a,t.anchor=c,t.kind=C?\"mapping\":\"sequence\",t.result=n,!0;r||Rr(t,\"missed comma between flow collection entries\"),I=P=R=null,h=E=!1,N===63&&(f=t.input.charCodeAt(t.position+1),nl(f)&&(h=E=!0,t.position++,ls(t,!0,e))),s=t.line,dE(t,e,Dx,!1,!0),I=t.tag,P=t.result,ls(t,!0,e),N=t.input.charCodeAt(t.position),(E||t.line===s)&&N===58&&(h=!0,N=t.input.charCodeAt(++t.position),ls(t,!0,e),dE(t,e,Dx,!1,!0),R=t.result),C?gE(t,n,S,I,P,R):h?n.push(gE(t,null,S,I,P,R)):n.push(P),ls(t,!0,e),N=t.input.charCodeAt(t.position),N===44?(r=!0,N=t.input.charCodeAt(++t.position)):r=!1}Rr(t,\"unexpected end of the stream within a flow collection\")}function UJe(t,e){var r,s,a=$_,n=!1,c=!1,f=e,p=0,h=!1,E,C;if(C=t.input.charCodeAt(t.position),C===124)s=!1;else if(C===62)s=!0;else return!1;for(t.kind=\"scalar\",t.result=\"\";C!==0;)if(C=t.input.charCodeAt(++t.position),C===43||C===45)$_===a?a=C===43?zte:bJe:Rr(t,\"repeat of a chomping mode identifier\");else if((E=RJe(C))>=0)E===0?Rr(t,\"bad explicit indentation width of a block scalar; it cannot be less than one\"):c?Rr(t,\"repeat of an indentation width identifier\"):(f=e+E-1,c=!0);else break;if(kd(C)){do C=t.input.charCodeAt(++t.position);while(kd(C));if(C===35)do C=t.input.charCodeAt(++t.position);while(!qf(C)&&C!==0)}for(;C!==0;){for(eU(t),t.lineIndent=0,C=t.input.charCodeAt(t.position);(!c||t.lineIndent<f)&&C===32;)t.lineIndent++,C=t.input.charCodeAt(++t.position);if(!c&&t.lineIndent>f&&(f=t.lineIndent),qf(C)){p++;continue}if(t.lineIndent<f){a===zte?t.result+=wp.repeat(`\n`,n?1+p:p):a===$_&&n&&(t.result+=`\n`);break}for(s?kd(C)?(h=!0,t.result+=wp.repeat(`\n`,n?1+p:p)):h?(h=!1,t.result+=wp.repeat(`\n`,p+1)):p===0?n&&(t.result+=\" \"):t.result+=wp.repeat(`\n`,p):t.result+=wp.repeat(`\n`,n?1+p:p),n=!0,c=!0,p=0,r=t.position;!qf(C)&&C!==0;)C=t.input.charCodeAt(++t.position);s0(t,r,t.position,!1)}return!0}function tre(t,e){var r,s=t.tag,a=t.anchor,n=[],c,f=!1,p;for(t.anchor!==null&&(t.anchorMap[t.anchor]=n),p=t.input.charCodeAt(t.position);p!==0&&!(p!==45||(c=t.input.charCodeAt(t.position+1),!nl(c)));){if(f=!0,t.position++,ls(t,!0,-1)&&t.lineIndent<=e){n.push(null),p=t.input.charCodeAt(t.position);continue}if(r=t.line,dE(t,e,sre,!1,!0),n.push(t.result),ls(t,!0,-1),p=t.input.charCodeAt(t.position),(t.line===r||t.lineIndent>e)&&p!==0)Rr(t,\"bad indentation of a sequence entry\");else if(t.lineIndent<e)break}return f?(t.tag=s,t.anchor=a,t.kind=\"sequence\",t.result=n,!0):!1}function HJe(t,e,r){var s,a,n,c,f=t.tag,p=t.anchor,h={},E={},C=null,S=null,P=null,I=!1,R=!1,N;for(t.anchor!==null&&(t.anchorMap[t.anchor]=h),N=t.input.charCodeAt(t.position);N!==0;){if(s=t.input.charCodeAt(t.position+1),n=t.line,c=t.position,(N===63||N===58)&&nl(s))N===63?(I&&(gE(t,h,E,C,S,null),C=S=P=null),R=!0,I=!0,a=!0):I?(I=!1,a=!0):Rr(t,\"incomplete explicit mapping pair; a key node is missed; or followed by a non-tabulated empty line\"),t.position+=1,N=s;else if(dE(t,r,ire,!1,!0))if(t.line===n){for(N=t.input.charCodeAt(t.position);kd(N);)N=t.input.charCodeAt(++t.position);if(N===58)N=t.input.charCodeAt(++t.position),nl(N)||Rr(t,\"a whitespace character is expected after the key-value separator within a block mapping\"),I&&(gE(t,h,E,C,S,null),C=S=P=null),R=!0,I=!1,a=!1,C=t.tag,S=t.result;else if(R)Rr(t,\"can not read an implicit mapping pair; a colon is missed\");else return t.tag=f,t.anchor=p,!0}else if(R)Rr(t,\"can not read a block mapping entry; a multiline key may not be an implicit key\");else return t.tag=f,t.anchor=p,!0;else break;if((t.line===n||t.lineIndent>e)&&(dE(t,e,bx,!0,a)&&(I?S=t.result:P=t.result),I||(gE(t,h,E,C,S,P,n,c),C=S=P=null),ls(t,!0,-1),N=t.input.charCodeAt(t.position)),t.lineIndent>e&&N!==0)Rr(t,\"bad indentation of a mapping entry\");else if(t.lineIndent<e)break}return I&&gE(t,h,E,C,S,null),R&&(t.tag=f,t.anchor=p,t.kind=\"mapping\",t.result=h),R}function jJe(t){var e,r=!1,s=!1,a,n,c;if(c=t.input.charCodeAt(t.position),c!==33)return!1;if(t.tag!==null&&Rr(t,\"duplication of a tag property\"),c=t.input.charCodeAt(++t.position),c===60?(r=!0,c=t.input.charCodeAt(++t.position)):c===33?(s=!0,a=\"!!\",c=t.input.charCodeAt(++t.position)):a=\"!\",e=t.position,r){do c=t.input.charCodeAt(++t.position);while(c!==0&&c!==62);t.position<t.length?(n=t.input.slice(e,t.position),c=t.input.charCodeAt(++t.position)):Rr(t,\"unexpected end of the stream within a verbatim tag\")}else{for(;c!==0&&!nl(c);)c===33&&(s?Rr(t,\"tag suffix cannot contain exclamation marks\"):(a=t.input.slice(e-1,t.position+1),ore.test(a)||Rr(t,\"named tag handle cannot contain such characters\"),s=!0,e=t.position+1)),c=t.input.charCodeAt(++t.position);n=t.input.slice(e,t.position),kJe.test(n)&&Rr(t,\"tag suffix cannot contain flow indicator characters\")}return n&&!are.test(n)&&Rr(t,\"tag name cannot contain such characters: \"+n),r?t.tag=n:o0.call(t.tagMap,a)?t.tag=t.tagMap[a]+n:a===\"!\"?t.tag=\"!\"+n:a===\"!!\"?t.tag=\"tag:yaml.org,2002:\"+n:Rr(t,'undeclared tag handle \"'+a+'\"'),!0}function qJe(t){var e,r;if(r=t.input.charCodeAt(t.position),r!==38)return!1;for(t.anchor!==null&&Rr(t,\"duplication of an anchor property\"),r=t.input.charCodeAt(++t.position),e=t.position;r!==0&&!nl(r)&&!hE(r);)r=t.input.charCodeAt(++t.position);return t.position===e&&Rr(t,\"name of an anchor node must contain at least one character\"),t.anchor=t.input.slice(e,t.position),!0}function GJe(t){var e,r,s;if(s=t.input.charCodeAt(t.position),s!==42)return!1;for(s=t.input.charCodeAt(++t.position),e=t.position;s!==0&&!nl(s)&&!hE(s);)s=t.input.charCodeAt(++t.position);return t.position===e&&Rr(t,\"name of an alias node must contain at least one character\"),r=t.input.slice(e,t.position),o0.call(t.anchorMap,r)||Rr(t,'unidentified alias \"'+r+'\"'),t.result=t.anchorMap[r],ls(t,!0,-1),!0}function dE(t,e,r,s,a){var n,c,f,p=1,h=!1,E=!1,C,S,P,I,R;if(t.listener!==null&&t.listener(\"open\",t),t.tag=null,t.anchor=null,t.kind=null,t.result=null,n=c=f=bx===r||sre===r,s&&ls(t,!0,-1)&&(h=!0,t.lineIndent>e?p=1:t.lineIndent===e?p=0:t.lineIndent<e&&(p=-1)),p===1)for(;jJe(t)||qJe(t);)ls(t,!0,-1)?(h=!0,f=n,t.lineIndent>e?p=1:t.lineIndent===e?p=0:t.lineIndent<e&&(p=-1)):f=!1;if(f&&(f=h||a),(p===1||bx===r)&&(Dx===r||ire===r?I=e:I=e+1,R=t.position-t.lineStart,p===1?f&&(tre(t,R)||HJe(t,R,I))||_Je(t,I)?E=!0:(c&&UJe(t,I)||LJe(t,I)||MJe(t,I)?E=!0:GJe(t)?(E=!0,(t.tag!==null||t.anchor!==null)&&Rr(t,\"alias node should not have any properties\")):OJe(t,I,Dx===r)&&(E=!0,t.tag===null&&(t.tag=\"?\")),t.anchor!==null&&(t.anchorMap[t.anchor]=t.result)):p===0&&(E=f&&tre(t,R))),t.tag!==null&&t.tag!==\"!\")if(t.tag===\"?\"){for(t.result!==null&&t.kind!==\"scalar\"&&Rr(t,'unacceptable node kind for !<?> tag; it should be \"scalar\", not \"'+t.kind+'\"'),C=0,S=t.implicitTypes.length;C<S;C+=1)if(P=t.implicitTypes[C],P.resolve(t.result)){t.result=P.construct(t.result),t.tag=P.tag,t.anchor!==null&&(t.anchorMap[t.anchor]=t.result);break}}else o0.call(t.typeMap[t.kind||\"fallback\"],t.tag)?(P=t.typeMap[t.kind||\"fallback\"][t.tag],t.result!==null&&P.kind!==t.kind&&Rr(t,\"unacceptable node kind for !<\"+t.tag+'> tag; it should be \"'+P.kind+'\", not \"'+t.kind+'\"'),P.resolve(t.result)?(t.result=P.construct(t.result),t.anchor!==null&&(t.anchorMap[t.anchor]=t.result)):Rr(t,\"cannot resolve a node with !<\"+t.tag+\"> explicit tag\")):Rr(t,\"unknown tag !<\"+t.tag+\">\");return t.listener!==null&&t.listener(\"close\",t),t.tag!==null||t.anchor!==null||E}function WJe(t){var e=t.position,r,s,a,n=!1,c;for(t.version=null,t.checkLineBreaks=t.legacy,t.tagMap={},t.anchorMap={};(c=t.input.charCodeAt(t.position))!==0&&(ls(t,!0,-1),c=t.input.charCodeAt(t.position),!(t.lineIndent>0||c!==37));){for(n=!0,c=t.input.charCodeAt(++t.position),r=t.position;c!==0&&!nl(c);)c=t.input.charCodeAt(++t.position);for(s=t.input.slice(r,t.position),a=[],s.length<1&&Rr(t,\"directive name must not be less than one character in length\");c!==0;){for(;kd(c);)c=t.input.charCodeAt(++t.position);if(c===35){do c=t.input.charCodeAt(++t.position);while(c!==0&&!qf(c));break}if(qf(c))break;for(r=t.position;c!==0&&!nl(c);)c=t.input.charCodeAt(++t.position);a.push(t.input.slice(r,t.position))}c!==0&&eU(t),o0.call($te,s)?$te[s](t,s,a):Px(t,'unknown document directive \"'+s+'\"')}if(ls(t,!0,-1),t.lineIndent===0&&t.input.charCodeAt(t.position)===45&&t.input.charCodeAt(t.position+1)===45&&t.input.charCodeAt(t.position+2)===45?(t.position+=3,ls(t,!0,-1)):n&&Rr(t,\"directives end mark is expected\"),dE(t,t.lineIndent-1,bx,!1,!0),ls(t,!0,-1),t.checkLineBreaks&&xJe.test(t.input.slice(e,t.position))&&Px(t,\"non-ASCII line breaks are interpreted as content\"),t.documents.push(t.result),t.position===t.lineStart&&xx(t)){t.input.charCodeAt(t.position)===46&&(t.position+=3,ls(t,!0,-1));return}if(t.position<t.length-1)Rr(t,\"end of the stream or a document separator is expected\");else return}function fre(t,e){t=String(t),e=e||{},t.length!==0&&(t.charCodeAt(t.length-1)!==10&&t.charCodeAt(t.length-1)!==13&&(t+=`\n`),t.charCodeAt(0)===65279&&(t=t.slice(1)));var r=new NJe(t,e),s=t.indexOf(\"\\0\");for(s!==-1&&(r.position=s,Rr(r,\"null byte is not allowed in input\")),r.input+=\"\\0\";r.input.charCodeAt(r.position)===32;)r.lineIndent+=1,r.position+=1;for(;r.position<r.length-1;)WJe(r);return r.documents}function Are(t,e,r){e!==null&&typeof e==\"object\"&&typeof r>\"u\"&&(r=e,e=null);var s=fre(t,r);if(typeof e!=\"function\")return s;for(var a=0,n=s.length;a<n;a+=1)e(s[a])}function pre(t,e){var r=fre(t,e);if(r.length!==0){if(r.length===1)return r[0];throw new rre(\"expected a single document in the stream, but found more\")}}function YJe(t,e,r){return typeof e==\"object\"&&e!==null&&typeof r>\"u\"&&(r=e,e=null),Are(t,e,wp.extend({schema:nre},r))}function VJe(t,e){return pre(t,wp.extend({schema:nre},e))}G2.exports.loadAll=Are;G2.exports.load=pre;G2.exports.safeLoadAll=YJe;G2.exports.safeLoad=VJe});var Lre=L((b5t,sU)=>{\"use strict\";var Y2=Dd(),V2=fE(),KJe=q2(),JJe=pE(),wre=Object.prototype.toString,Bre=Object.prototype.hasOwnProperty,zJe=9,W2=10,ZJe=13,XJe=32,$Je=33,eze=34,vre=35,tze=37,rze=38,nze=39,ize=42,Sre=44,sze=45,Dre=58,oze=61,aze=62,lze=63,cze=64,bre=91,Pre=93,uze=96,xre=123,fze=124,kre=125,jo={};jo[0]=\"\\\\0\";jo[7]=\"\\\\a\";jo[8]=\"\\\\b\";jo[9]=\"\\\\t\";jo[10]=\"\\\\n\";jo[11]=\"\\\\v\";jo[12]=\"\\\\f\";jo[13]=\"\\\\r\";jo[27]=\"\\\\e\";jo[34]='\\\\\"';jo[92]=\"\\\\\\\\\";jo[133]=\"\\\\N\";jo[160]=\"\\\\_\";jo[8232]=\"\\\\L\";jo[8233]=\"\\\\P\";var Aze=[\"y\",\"Y\",\"yes\",\"Yes\",\"YES\",\"on\",\"On\",\"ON\",\"n\",\"N\",\"no\",\"No\",\"NO\",\"off\",\"Off\",\"OFF\"];function pze(t,e){var r,s,a,n,c,f,p;if(e===null)return{};for(r={},s=Object.keys(e),a=0,n=s.length;a<n;a+=1)c=s[a],f=String(e[c]),c.slice(0,2)===\"!!\"&&(c=\"tag:yaml.org,2002:\"+c.slice(2)),p=t.compiledTypeMap.fallback[c],p&&Bre.call(p.styleAliases,f)&&(f=p.styleAliases[f]),r[c]=f;return r}function gre(t){var e,r,s;if(e=t.toString(16).toUpperCase(),t<=255)r=\"x\",s=2;else if(t<=65535)r=\"u\",s=4;else if(t<=4294967295)r=\"U\",s=8;else throw new V2(\"code point within a string may not be greater than 0xFFFFFFFF\");return\"\\\\\"+r+Y2.repeat(\"0\",s-e.length)+e}function hze(t){this.schema=t.schema||KJe,this.indent=Math.max(1,t.indent||2),this.noArrayIndent=t.noArrayIndent||!1,this.skipInvalid=t.skipInvalid||!1,this.flowLevel=Y2.isNothing(t.flowLevel)?-1:t.flowLevel,this.styleMap=pze(this.schema,t.styles||null),this.sortKeys=t.sortKeys||!1,this.lineWidth=t.lineWidth||80,this.noRefs=t.noRefs||!1,this.noCompatMode=t.noCompatMode||!1,this.condenseFlow=t.condenseFlow||!1,this.implicitTypes=this.schema.compiledImplicit,this.explicitTypes=this.schema.compiledExplicit,this.tag=null,this.result=\"\",this.duplicates=[],this.usedDuplicates=null}function dre(t,e){for(var r=Y2.repeat(\" \",e),s=0,a=-1,n=\"\",c,f=t.length;s<f;)a=t.indexOf(`\n`,s),a===-1?(c=t.slice(s),s=f):(c=t.slice(s,a+1),s=a+1),c.length&&c!==`\n`&&(n+=r),n+=c;return n}function rU(t,e){return`\n`+Y2.repeat(\" \",t.indent*e)}function gze(t,e){var r,s,a;for(r=0,s=t.implicitTypes.length;r<s;r+=1)if(a=t.implicitTypes[r],a.resolve(e))return!0;return!1}function iU(t){return t===XJe||t===zJe}function mE(t){return 32<=t&&t<=126||161<=t&&t<=55295&&t!==8232&&t!==8233||57344<=t&&t<=65533&&t!==65279||65536<=t&&t<=1114111}function dze(t){return mE(t)&&!iU(t)&&t!==65279&&t!==ZJe&&t!==W2}function mre(t,e){return mE(t)&&t!==65279&&t!==Sre&&t!==bre&&t!==Pre&&t!==xre&&t!==kre&&t!==Dre&&(t!==vre||e&&dze(e))}function mze(t){return mE(t)&&t!==65279&&!iU(t)&&t!==sze&&t!==lze&&t!==Dre&&t!==Sre&&t!==bre&&t!==Pre&&t!==xre&&t!==kre&&t!==vre&&t!==rze&&t!==ize&&t!==$Je&&t!==fze&&t!==oze&&t!==aze&&t!==nze&&t!==eze&&t!==tze&&t!==cze&&t!==uze}function Qre(t){var e=/^\\n* /;return e.test(t)}var Tre=1,Rre=2,Fre=3,Nre=4,kx=5;function yze(t,e,r,s,a){var n,c,f,p=!1,h=!1,E=s!==-1,C=-1,S=mze(t.charCodeAt(0))&&!iU(t.charCodeAt(t.length-1));if(e)for(n=0;n<t.length;n++){if(c=t.charCodeAt(n),!mE(c))return kx;f=n>0?t.charCodeAt(n-1):null,S=S&&mre(c,f)}else{for(n=0;n<t.length;n++){if(c=t.charCodeAt(n),c===W2)p=!0,E&&(h=h||n-C-1>s&&t[C+1]!==\" \",C=n);else if(!mE(c))return kx;f=n>0?t.charCodeAt(n-1):null,S=S&&mre(c,f)}h=h||E&&n-C-1>s&&t[C+1]!==\" \"}return!p&&!h?S&&!a(t)?Tre:Rre:r>9&&Qre(t)?kx:h?Nre:Fre}function Eze(t,e,r,s){t.dump=function(){if(e.length===0)return\"''\";if(!t.noCompatMode&&Aze.indexOf(e)!==-1)return\"'\"+e+\"'\";var a=t.indent*Math.max(1,r),n=t.lineWidth===-1?-1:Math.max(Math.min(t.lineWidth,40),t.lineWidth-a),c=s||t.flowLevel>-1&&r>=t.flowLevel;function f(p){return gze(t,p)}switch(yze(e,c,t.indent,n,f)){case Tre:return e;case Rre:return\"'\"+e.replace(/'/g,\"''\")+\"'\";case Fre:return\"|\"+yre(e,t.indent)+Ere(dre(e,a));case Nre:return\">\"+yre(e,t.indent)+Ere(dre(Ize(e,n),a));case kx:return'\"'+Cze(e,n)+'\"';default:throw new V2(\"impossible error: invalid scalar style\")}}()}function yre(t,e){var r=Qre(t)?String(e):\"\",s=t[t.length-1]===`\n`,a=s&&(t[t.length-2]===`\n`||t===`\n`),n=a?\"+\":s?\"\":\"-\";return r+n+`\n`}function Ere(t){return t[t.length-1]===`\n`?t.slice(0,-1):t}function Ize(t,e){for(var r=/(\\n+)([^\\n]*)/g,s=function(){var h=t.indexOf(`\n`);return h=h!==-1?h:t.length,r.lastIndex=h,Ire(t.slice(0,h),e)}(),a=t[0]===`\n`||t[0]===\" \",n,c;c=r.exec(t);){var f=c[1],p=c[2];n=p[0]===\" \",s+=f+(!a&&!n&&p!==\"\"?`\n`:\"\")+Ire(p,e),a=n}return s}function Ire(t,e){if(t===\"\"||t[0]===\" \")return t;for(var r=/ [^ ]/g,s,a=0,n,c=0,f=0,p=\"\";s=r.exec(t);)f=s.index,f-a>e&&(n=c>a?c:f,p+=`\n`+t.slice(a,n),a=n+1),c=f;return p+=`\n`,t.length-a>e&&c>a?p+=t.slice(a,c)+`\n`+t.slice(c+1):p+=t.slice(a),p.slice(1)}function Cze(t){for(var e=\"\",r,s,a,n=0;n<t.length;n++){if(r=t.charCodeAt(n),r>=55296&&r<=56319&&(s=t.charCodeAt(n+1),s>=56320&&s<=57343)){e+=gre((r-55296)*1024+s-56320+65536),n++;continue}a=jo[r],e+=!a&&mE(r)?t[n]:a||gre(r)}return e}function wze(t,e,r){var s=\"\",a=t.tag,n,c;for(n=0,c=r.length;n<c;n+=1)Qd(t,e,r[n],!1,!1)&&(n!==0&&(s+=\",\"+(t.condenseFlow?\"\":\" \")),s+=t.dump);t.tag=a,t.dump=\"[\"+s+\"]\"}function Bze(t,e,r,s){var a=\"\",n=t.tag,c,f;for(c=0,f=r.length;c<f;c+=1)Qd(t,e+1,r[c],!0,!0)&&((!s||c!==0)&&(a+=rU(t,e)),t.dump&&W2===t.dump.charCodeAt(0)?a+=\"-\":a+=\"- \",a+=t.dump);t.tag=n,t.dump=a||\"[]\"}function vze(t,e,r){var s=\"\",a=t.tag,n=Object.keys(r),c,f,p,h,E;for(c=0,f=n.length;c<f;c+=1)E=\"\",c!==0&&(E+=\", \"),t.condenseFlow&&(E+='\"'),p=n[c],h=r[p],Qd(t,e,p,!1,!1)&&(t.dump.length>1024&&(E+=\"? \"),E+=t.dump+(t.condenseFlow?'\"':\"\")+\":\"+(t.condenseFlow?\"\":\" \"),Qd(t,e,h,!1,!1)&&(E+=t.dump,s+=E));t.tag=a,t.dump=\"{\"+s+\"}\"}function Sze(t,e,r,s){var a=\"\",n=t.tag,c=Object.keys(r),f,p,h,E,C,S;if(t.sortKeys===!0)c.sort();else if(typeof t.sortKeys==\"function\")c.sort(t.sortKeys);else if(t.sortKeys)throw new V2(\"sortKeys must be a boolean or a function\");for(f=0,p=c.length;f<p;f+=1)S=\"\",(!s||f!==0)&&(S+=rU(t,e)),h=c[f],E=r[h],Qd(t,e+1,h,!0,!0,!0)&&(C=t.tag!==null&&t.tag!==\"?\"||t.dump&&t.dump.length>1024,C&&(t.dump&&W2===t.dump.charCodeAt(0)?S+=\"?\":S+=\"? \"),S+=t.dump,C&&(S+=rU(t,e)),Qd(t,e+1,E,!0,C)&&(t.dump&&W2===t.dump.charCodeAt(0)?S+=\":\":S+=\": \",S+=t.dump,a+=S));t.tag=n,t.dump=a||\"{}\"}function Cre(t,e,r){var s,a,n,c,f,p;for(a=r?t.explicitTypes:t.implicitTypes,n=0,c=a.length;n<c;n+=1)if(f=a[n],(f.instanceOf||f.predicate)&&(!f.instanceOf||typeof e==\"object\"&&e instanceof f.instanceOf)&&(!f.predicate||f.predicate(e))){if(t.tag=r?f.tag:\"?\",f.represent){if(p=t.styleMap[f.tag]||f.defaultStyle,wre.call(f.represent)===\"[object Function]\")s=f.represent(e,p);else if(Bre.call(f.represent,p))s=f.represent[p](e,p);else throw new V2(\"!<\"+f.tag+'> tag resolver accepts not \"'+p+'\" style');t.dump=s}return!0}return!1}function Qd(t,e,r,s,a,n){t.tag=null,t.dump=r,Cre(t,r,!1)||Cre(t,r,!0);var c=wre.call(t.dump);s&&(s=t.flowLevel<0||t.flowLevel>e);var f=c===\"[object Object]\"||c===\"[object Array]\",p,h;if(f&&(p=t.duplicates.indexOf(r),h=p!==-1),(t.tag!==null&&t.tag!==\"?\"||h||t.indent!==2&&e>0)&&(a=!1),h&&t.usedDuplicates[p])t.dump=\"*ref_\"+p;else{if(f&&h&&!t.usedDuplicates[p]&&(t.usedDuplicates[p]=!0),c===\"[object Object]\")s&&Object.keys(t.dump).length!==0?(Sze(t,e,t.dump,a),h&&(t.dump=\"&ref_\"+p+t.dump)):(vze(t,e,t.dump),h&&(t.dump=\"&ref_\"+p+\" \"+t.dump));else if(c===\"[object Array]\"){var E=t.noArrayIndent&&e>0?e-1:e;s&&t.dump.length!==0?(Bze(t,E,t.dump,a),h&&(t.dump=\"&ref_\"+p+t.dump)):(wze(t,E,t.dump),h&&(t.dump=\"&ref_\"+p+\" \"+t.dump))}else if(c===\"[object String]\")t.tag!==\"?\"&&Eze(t,t.dump,e,n);else{if(t.skipInvalid)return!1;throw new V2(\"unacceptable kind of an object to dump \"+c)}t.tag!==null&&t.tag!==\"?\"&&(t.dump=\"!<\"+t.tag+\"> \"+t.dump)}return!0}function Dze(t,e){var r=[],s=[],a,n;for(nU(t,r,s),a=0,n=s.length;a<n;a+=1)e.duplicates.push(r[s[a]]);e.usedDuplicates=new Array(n)}function nU(t,e,r){var s,a,n;if(t!==null&&typeof t==\"object\")if(a=e.indexOf(t),a!==-1)r.indexOf(a)===-1&&r.push(a);else if(e.push(t),Array.isArray(t))for(a=0,n=t.length;a<n;a+=1)nU(t[a],e,r);else for(s=Object.keys(t),a=0,n=s.length;a<n;a+=1)nU(t[s[a]],e,r)}function Ore(t,e){e=e||{};var r=new hze(e);return r.noRefs||Dze(t,r),Qd(r,0,t,!0,!0)?r.dump+`\n`:\"\"}function bze(t,e){return Ore(t,Y2.extend({schema:JJe},e))}sU.exports.dump=Ore;sU.exports.safeDump=bze});var _re=L((P5t,Wi)=>{\"use strict\";var Qx=hre(),Mre=Lre();function Tx(t){return function(){throw new Error(\"Function \"+t+\" is deprecated and cannot be used.\")}}Wi.exports.Type=bs();Wi.exports.Schema=bd();Wi.exports.FAILSAFE_SCHEMA=vx();Wi.exports.JSON_SCHEMA=z_();Wi.exports.CORE_SCHEMA=Z_();Wi.exports.DEFAULT_SAFE_SCHEMA=pE();Wi.exports.DEFAULT_FULL_SCHEMA=q2();Wi.exports.load=Qx.load;Wi.exports.loadAll=Qx.loadAll;Wi.exports.safeLoad=Qx.safeLoad;Wi.exports.safeLoadAll=Qx.safeLoadAll;Wi.exports.dump=Mre.dump;Wi.exports.safeDump=Mre.safeDump;Wi.exports.YAMLException=fE();Wi.exports.MINIMAL_SCHEMA=vx();Wi.exports.SAFE_SCHEMA=pE();Wi.exports.DEFAULT_SCHEMA=q2();Wi.exports.scan=Tx(\"scan\");Wi.exports.parse=Tx(\"parse\");Wi.exports.compose=Tx(\"compose\");Wi.exports.addConstructor=Tx(\"addConstructor\")});var Hre=L((x5t,Ure)=>{\"use strict\";var Pze=_re();Ure.exports=Pze});var qre=L((k5t,jre)=>{\"use strict\";function xze(t,e){function r(){this.constructor=t}r.prototype=e.prototype,t.prototype=new r}function Td(t,e,r,s){this.message=t,this.expected=e,this.found=r,this.location=s,this.name=\"SyntaxError\",typeof Error.captureStackTrace==\"function\"&&Error.captureStackTrace(this,Td)}xze(Td,Error);Td.buildMessage=function(t,e){var r={literal:function(h){return'\"'+a(h.text)+'\"'},class:function(h){var E=\"\",C;for(C=0;C<h.parts.length;C++)E+=h.parts[C]instanceof Array?n(h.parts[C][0])+\"-\"+n(h.parts[C][1]):n(h.parts[C]);return\"[\"+(h.inverted?\"^\":\"\")+E+\"]\"},any:function(h){return\"any character\"},end:function(h){return\"end of input\"},other:function(h){return h.description}};function s(h){return h.charCodeAt(0).toString(16).toUpperCase()}function a(h){return h.replace(/\\\\/g,\"\\\\\\\\\").replace(/\"/g,'\\\\\"').replace(/\\0/g,\"\\\\0\").replace(/\\t/g,\"\\\\t\").replace(/\\n/g,\"\\\\n\").replace(/\\r/g,\"\\\\r\").replace(/[\\x00-\\x0F]/g,function(E){return\"\\\\x0\"+s(E)}).replace(/[\\x10-\\x1F\\x7F-\\x9F]/g,function(E){return\"\\\\x\"+s(E)})}function n(h){return h.replace(/\\\\/g,\"\\\\\\\\\").replace(/\\]/g,\"\\\\]\").replace(/\\^/g,\"\\\\^\").replace(/-/g,\"\\\\-\").replace(/\\0/g,\"\\\\0\").replace(/\\t/g,\"\\\\t\").replace(/\\n/g,\"\\\\n\").replace(/\\r/g,\"\\\\r\").replace(/[\\x00-\\x0F]/g,function(E){return\"\\\\x0\"+s(E)}).replace(/[\\x10-\\x1F\\x7F-\\x9F]/g,function(E){return\"\\\\x\"+s(E)})}function c(h){return r[h.type](h)}function f(h){var E=new Array(h.length),C,S;for(C=0;C<h.length;C++)E[C]=c(h[C]);if(E.sort(),E.length>0){for(C=1,S=1;C<E.length;C++)E[C-1]!==E[C]&&(E[S]=E[C],S++);E.length=S}switch(E.length){case 1:return E[0];case 2:return E[0]+\" or \"+E[1];default:return E.slice(0,-1).join(\", \")+\", or \"+E[E.length-1]}}function p(h){return h?'\"'+a(h)+'\"':\"end of input\"}return\"Expected \"+f(t)+\" but \"+p(e)+\" found.\"};function kze(t,e){e=e!==void 0?e:{};var r={},s={Start:cc},a=cc,n=function(ee){return[].concat(...ee)},c=\"-\",f=dn(\"-\",!1),p=function(ee){return ee},h=function(ee){return Object.assign({},...ee)},E=\"#\",C=dn(\"#\",!1),S=hu(),P=function(){return{}},I=\":\",R=dn(\":\",!1),N=function(ee,Ee){return{[ee]:Ee}},U=\",\",W=dn(\",\",!1),te=function(ee,Ee){return Ee},ie=function(ee,Ee,Oe){return Object.assign({},...[ee].concat(Ee).map(gt=>({[gt]:Oe})))},Ae=function(ee){return ee},ce=function(ee){return ee},me=La(\"correct indentation\"),pe=\" \",Be=dn(\" \",!1),Ce=function(ee){return ee.length===lr*St},g=function(ee){return ee.length===(lr+1)*St},we=function(){return lr++,!0},ye=function(){return lr--,!0},fe=function(){return ca()},se=La(\"pseudostring\"),X=/^[^\\r\\n\\t ?:,\\][{}#&*!|>'\"%@`\\-]/,De=Jn([\"\\r\",`\n`,\"\t\",\" \",\"?\",\":\",\",\",\"]\",\"[\",\"{\",\"}\",\"#\",\"&\",\"*\",\"!\",\"|\",\">\",\"'\",'\"',\"%\",\"@\",\"`\",\"-\"],!0,!1),Re=/^[^\\r\\n\\t ,\\][{}:#\"']/,dt=Jn([\"\\r\",`\n`,\"\t\",\" \",\",\",\"]\",\"[\",\"{\",\"}\",\":\",\"#\",'\"',\"'\"],!0,!1),j=function(){return ca().replace(/^ *| *$/g,\"\")},rt=\"--\",Fe=dn(\"--\",!1),Ne=/^[a-zA-Z\\/0-9]/,Pe=Jn([[\"a\",\"z\"],[\"A\",\"Z\"],\"/\",[\"0\",\"9\"]],!1,!1),Ye=/^[^\\r\\n\\t :,]/,ke=Jn([\"\\r\",`\n`,\"\t\",\" \",\":\",\",\"],!0,!1),it=\"null\",_e=dn(\"null\",!1),x=function(){return null},w=\"true\",b=dn(\"true\",!1),y=function(){return!0},F=\"false\",z=dn(\"false\",!1),Z=function(){return!1},$=La(\"string\"),oe='\"',xe=dn('\"',!1),Te=function(){return\"\"},lt=function(ee){return ee},It=function(ee){return ee.join(\"\")},qt=/^[^\"\\\\\\0-\\x1F\\x7F]/,ir=Jn(['\"',\"\\\\\",[\"\\0\",\"\u001f\"],\"\\x7F\"],!0,!1),Pt='\\\\\"',gn=dn('\\\\\"',!1),Pr=function(){return'\"'},Ir=\"\\\\\\\\\",Nr=dn(\"\\\\\\\\\",!1),nn=function(){return\"\\\\\"},ai=\"\\\\/\",wo=dn(\"\\\\/\",!1),ns=function(){return\"/\"},to=\"\\\\b\",Bo=dn(\"\\\\b\",!1),ji=function(){return\"\\b\"},ro=\"\\\\f\",vo=dn(\"\\\\f\",!1),RA=function(){return\"\\f\"},pf=\"\\\\n\",yh=dn(\"\\\\n\",!1),Eh=function(){return`\n`},no=\"\\\\r\",jn=dn(\"\\\\r\",!1),Fs=function(){return\"\\r\"},io=\"\\\\t\",lu=dn(\"\\\\t\",!1),cu=function(){return\"\t\"},uu=\"\\\\u\",FA=dn(\"\\\\u\",!1),NA=function(ee,Ee,Oe,gt){return String.fromCharCode(parseInt(`0x${ee}${Ee}${Oe}${gt}`))},aa=/^[0-9a-fA-F]/,la=Jn([[\"0\",\"9\"],[\"a\",\"f\"],[\"A\",\"F\"]],!1,!1),OA=La(\"blank space\"),gr=/^[ \\t]/,So=Jn([\" \",\"\t\"],!1,!1),Me=La(\"white space\"),fu=/^[ \\t\\n\\r]/,Cr=Jn([\" \",\"\t\",`\n`,\"\\r\"],!1,!1),hf=`\\r\n`,LA=dn(`\\r\n`,!1),MA=`\n`,Au=dn(`\n`,!1),pu=\"\\r\",ac=dn(\"\\r\",!1),ve=0,Nt=0,lc=[{line:1,column:1}],Li=0,so=[],Rt=0,xn;if(\"startRule\"in e){if(!(e.startRule in s))throw new Error(`Can't start parsing from rule \"`+e.startRule+'\".');a=s[e.startRule]}function ca(){return t.substring(Nt,ve)}function qi(){return Ua(Nt,ve)}function Mi(ee,Ee){throw Ee=Ee!==void 0?Ee:Ua(Nt,ve),gf([La(ee)],t.substring(Nt,ve),Ee)}function Oa(ee,Ee){throw Ee=Ee!==void 0?Ee:Ua(Nt,ve),Ha(ee,Ee)}function dn(ee,Ee){return{type:\"literal\",text:ee,ignoreCase:Ee}}function Jn(ee,Ee,Oe){return{type:\"class\",parts:ee,inverted:Ee,ignoreCase:Oe}}function hu(){return{type:\"any\"}}function Ih(){return{type:\"end\"}}function La(ee){return{type:\"other\",description:ee}}function Ma(ee){var Ee=lc[ee],Oe;if(Ee)return Ee;for(Oe=ee-1;!lc[Oe];)Oe--;for(Ee=lc[Oe],Ee={line:Ee.line,column:Ee.column};Oe<ee;)t.charCodeAt(Oe)===10?(Ee.line++,Ee.column=1):Ee.column++,Oe++;return lc[ee]=Ee,Ee}function Ua(ee,Ee){var Oe=Ma(ee),gt=Ma(Ee);return{start:{offset:ee,line:Oe.line,column:Oe.column},end:{offset:Ee,line:gt.line,column:gt.column}}}function Xe(ee){ve<Li||(ve>Li&&(Li=ve,so=[]),so.push(ee))}function Ha(ee,Ee){return new Td(ee,null,null,Ee)}function gf(ee,Ee,Oe){return new Td(Td.buildMessage(ee,Ee),ee,Ee,Oe)}function cc(){var ee;return ee=_A(),ee}function wn(){var ee,Ee,Oe;for(ee=ve,Ee=[],Oe=ua();Oe!==r;)Ee.push(Oe),Oe=ua();return Ee!==r&&(Nt=ee,Ee=n(Ee)),ee=Ee,ee}function ua(){var ee,Ee,Oe,gt,yt;return ee=ve,Ee=vl(),Ee!==r?(t.charCodeAt(ve)===45?(Oe=c,ve++):(Oe=r,Rt===0&&Xe(f)),Oe!==r?(gt=Qn(),gt!==r?(yt=fa(),yt!==r?(Nt=ee,Ee=p(yt),ee=Ee):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r),ee}function _A(){var ee,Ee,Oe;for(ee=ve,Ee=[],Oe=UA();Oe!==r;)Ee.push(Oe),Oe=UA();return Ee!==r&&(Nt=ee,Ee=h(Ee)),ee=Ee,ee}function UA(){var ee,Ee,Oe,gt,yt,Dt,tr,fn,li;if(ee=ve,Ee=Qn(),Ee===r&&(Ee=null),Ee!==r){if(Oe=ve,t.charCodeAt(ve)===35?(gt=E,ve++):(gt=r,Rt===0&&Xe(C)),gt!==r){if(yt=[],Dt=ve,tr=ve,Rt++,fn=st(),Rt--,fn===r?tr=void 0:(ve=tr,tr=r),tr!==r?(t.length>ve?(fn=t.charAt(ve),ve++):(fn=r,Rt===0&&Xe(S)),fn!==r?(tr=[tr,fn],Dt=tr):(ve=Dt,Dt=r)):(ve=Dt,Dt=r),Dt!==r)for(;Dt!==r;)yt.push(Dt),Dt=ve,tr=ve,Rt++,fn=st(),Rt--,fn===r?tr=void 0:(ve=tr,tr=r),tr!==r?(t.length>ve?(fn=t.charAt(ve),ve++):(fn=r,Rt===0&&Xe(S)),fn!==r?(tr=[tr,fn],Dt=tr):(ve=Dt,Dt=r)):(ve=Dt,Dt=r);else yt=r;yt!==r?(gt=[gt,yt],Oe=gt):(ve=Oe,Oe=r)}else ve=Oe,Oe=r;if(Oe===r&&(Oe=null),Oe!==r){if(gt=[],yt=Je(),yt!==r)for(;yt!==r;)gt.push(yt),yt=Je();else gt=r;gt!==r?(Nt=ee,Ee=P(),ee=Ee):(ve=ee,ee=r)}else ve=ee,ee=r}else ve=ee,ee=r;if(ee===r&&(ee=ve,Ee=vl(),Ee!==r?(Oe=ja(),Oe!==r?(gt=Qn(),gt===r&&(gt=null),gt!==r?(t.charCodeAt(ve)===58?(yt=I,ve++):(yt=r,Rt===0&&Xe(R)),yt!==r?(Dt=Qn(),Dt===r&&(Dt=null),Dt!==r?(tr=fa(),tr!==r?(Nt=ee,Ee=N(Oe,tr),ee=Ee):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r),ee===r&&(ee=ve,Ee=vl(),Ee!==r?(Oe=is(),Oe!==r?(gt=Qn(),gt===r&&(gt=null),gt!==r?(t.charCodeAt(ve)===58?(yt=I,ve++):(yt=r,Rt===0&&Xe(R)),yt!==r?(Dt=Qn(),Dt===r&&(Dt=null),Dt!==r?(tr=fa(),tr!==r?(Nt=ee,Ee=N(Oe,tr),ee=Ee):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r),ee===r))){if(ee=ve,Ee=vl(),Ee!==r)if(Oe=is(),Oe!==r)if(gt=Qn(),gt!==r)if(yt=gu(),yt!==r){if(Dt=[],tr=Je(),tr!==r)for(;tr!==r;)Dt.push(tr),tr=Je();else Dt=r;Dt!==r?(Nt=ee,Ee=N(Oe,yt),ee=Ee):(ve=ee,ee=r)}else ve=ee,ee=r;else ve=ee,ee=r;else ve=ee,ee=r;else ve=ee,ee=r;if(ee===r)if(ee=ve,Ee=vl(),Ee!==r)if(Oe=is(),Oe!==r){if(gt=[],yt=ve,Dt=Qn(),Dt===r&&(Dt=null),Dt!==r?(t.charCodeAt(ve)===44?(tr=U,ve++):(tr=r,Rt===0&&Xe(W)),tr!==r?(fn=Qn(),fn===r&&(fn=null),fn!==r?(li=is(),li!==r?(Nt=yt,Dt=te(Oe,li),yt=Dt):(ve=yt,yt=r)):(ve=yt,yt=r)):(ve=yt,yt=r)):(ve=yt,yt=r),yt!==r)for(;yt!==r;)gt.push(yt),yt=ve,Dt=Qn(),Dt===r&&(Dt=null),Dt!==r?(t.charCodeAt(ve)===44?(tr=U,ve++):(tr=r,Rt===0&&Xe(W)),tr!==r?(fn=Qn(),fn===r&&(fn=null),fn!==r?(li=is(),li!==r?(Nt=yt,Dt=te(Oe,li),yt=Dt):(ve=yt,yt=r)):(ve=yt,yt=r)):(ve=yt,yt=r)):(ve=yt,yt=r);else gt=r;gt!==r?(yt=Qn(),yt===r&&(yt=null),yt!==r?(t.charCodeAt(ve)===58?(Dt=I,ve++):(Dt=r,Rt===0&&Xe(R)),Dt!==r?(tr=Qn(),tr===r&&(tr=null),tr!==r?(fn=fa(),fn!==r?(Nt=ee,Ee=ie(Oe,gt,fn),ee=Ee):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r)}else ve=ee,ee=r;else ve=ee,ee=r}return ee}function fa(){var ee,Ee,Oe,gt,yt,Dt,tr;if(ee=ve,Ee=ve,Rt++,Oe=ve,gt=st(),gt!==r?(yt=Mt(),yt!==r?(t.charCodeAt(ve)===45?(Dt=c,ve++):(Dt=r,Rt===0&&Xe(f)),Dt!==r?(tr=Qn(),tr!==r?(gt=[gt,yt,Dt,tr],Oe=gt):(ve=Oe,Oe=r)):(ve=Oe,Oe=r)):(ve=Oe,Oe=r)):(ve=Oe,Oe=r),Rt--,Oe!==r?(ve=Ee,Ee=void 0):Ee=r,Ee!==r?(Oe=Je(),Oe!==r?(gt=kn(),gt!==r?(yt=wn(),yt!==r?(Dt=Aa(),Dt!==r?(Nt=ee,Ee=Ae(yt),ee=Ee):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r),ee===r&&(ee=ve,Ee=st(),Ee!==r?(Oe=kn(),Oe!==r?(gt=_A(),gt!==r?(yt=Aa(),yt!==r?(Nt=ee,Ee=Ae(gt),ee=Ee):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r),ee===r))if(ee=ve,Ee=uc(),Ee!==r){if(Oe=[],gt=Je(),gt!==r)for(;gt!==r;)Oe.push(gt),gt=Je();else Oe=r;Oe!==r?(Nt=ee,Ee=ce(Ee),ee=Ee):(ve=ee,ee=r)}else ve=ee,ee=r;return ee}function vl(){var ee,Ee,Oe;for(Rt++,ee=ve,Ee=[],t.charCodeAt(ve)===32?(Oe=pe,ve++):(Oe=r,Rt===0&&Xe(Be));Oe!==r;)Ee.push(Oe),t.charCodeAt(ve)===32?(Oe=pe,ve++):(Oe=r,Rt===0&&Xe(Be));return Ee!==r?(Nt=ve,Oe=Ce(Ee),Oe?Oe=void 0:Oe=r,Oe!==r?(Ee=[Ee,Oe],ee=Ee):(ve=ee,ee=r)):(ve=ee,ee=r),Rt--,ee===r&&(Ee=r,Rt===0&&Xe(me)),ee}function Mt(){var ee,Ee,Oe;for(ee=ve,Ee=[],t.charCodeAt(ve)===32?(Oe=pe,ve++):(Oe=r,Rt===0&&Xe(Be));Oe!==r;)Ee.push(Oe),t.charCodeAt(ve)===32?(Oe=pe,ve++):(Oe=r,Rt===0&&Xe(Be));return Ee!==r?(Nt=ve,Oe=g(Ee),Oe?Oe=void 0:Oe=r,Oe!==r?(Ee=[Ee,Oe],ee=Ee):(ve=ee,ee=r)):(ve=ee,ee=r),ee}function kn(){var ee;return Nt=ve,ee=we(),ee?ee=void 0:ee=r,ee}function Aa(){var ee;return Nt=ve,ee=ye(),ee?ee=void 0:ee=r,ee}function ja(){var ee;return ee=Sl(),ee===r&&(ee=fc()),ee}function is(){var ee,Ee,Oe;if(ee=Sl(),ee===r){if(ee=ve,Ee=[],Oe=qa(),Oe!==r)for(;Oe!==r;)Ee.push(Oe),Oe=qa();else Ee=r;Ee!==r&&(Nt=ee,Ee=fe()),ee=Ee}return ee}function uc(){var ee;return ee=_i(),ee===r&&(ee=ws(),ee===r&&(ee=Sl(),ee===r&&(ee=fc()))),ee}function gu(){var ee;return ee=_i(),ee===r&&(ee=Sl(),ee===r&&(ee=qa())),ee}function fc(){var ee,Ee,Oe,gt,yt,Dt;if(Rt++,ee=ve,X.test(t.charAt(ve))?(Ee=t.charAt(ve),ve++):(Ee=r,Rt===0&&Xe(De)),Ee!==r){for(Oe=[],gt=ve,yt=Qn(),yt===r&&(yt=null),yt!==r?(Re.test(t.charAt(ve))?(Dt=t.charAt(ve),ve++):(Dt=r,Rt===0&&Xe(dt)),Dt!==r?(yt=[yt,Dt],gt=yt):(ve=gt,gt=r)):(ve=gt,gt=r);gt!==r;)Oe.push(gt),gt=ve,yt=Qn(),yt===r&&(yt=null),yt!==r?(Re.test(t.charAt(ve))?(Dt=t.charAt(ve),ve++):(Dt=r,Rt===0&&Xe(dt)),Dt!==r?(yt=[yt,Dt],gt=yt):(ve=gt,gt=r)):(ve=gt,gt=r);Oe!==r?(Nt=ee,Ee=j(),ee=Ee):(ve=ee,ee=r)}else ve=ee,ee=r;return Rt--,ee===r&&(Ee=r,Rt===0&&Xe(se)),ee}function qa(){var ee,Ee,Oe,gt,yt;if(ee=ve,t.substr(ve,2)===rt?(Ee=rt,ve+=2):(Ee=r,Rt===0&&Xe(Fe)),Ee===r&&(Ee=null),Ee!==r)if(Ne.test(t.charAt(ve))?(Oe=t.charAt(ve),ve++):(Oe=r,Rt===0&&Xe(Pe)),Oe!==r){for(gt=[],Ye.test(t.charAt(ve))?(yt=t.charAt(ve),ve++):(yt=r,Rt===0&&Xe(ke));yt!==r;)gt.push(yt),Ye.test(t.charAt(ve))?(yt=t.charAt(ve),ve++):(yt=r,Rt===0&&Xe(ke));gt!==r?(Nt=ee,Ee=j(),ee=Ee):(ve=ee,ee=r)}else ve=ee,ee=r;else ve=ee,ee=r;return ee}function _i(){var ee,Ee;return ee=ve,t.substr(ve,4)===it?(Ee=it,ve+=4):(Ee=r,Rt===0&&Xe(_e)),Ee!==r&&(Nt=ee,Ee=x()),ee=Ee,ee}function ws(){var ee,Ee;return ee=ve,t.substr(ve,4)===w?(Ee=w,ve+=4):(Ee=r,Rt===0&&Xe(b)),Ee!==r&&(Nt=ee,Ee=y()),ee=Ee,ee===r&&(ee=ve,t.substr(ve,5)===F?(Ee=F,ve+=5):(Ee=r,Rt===0&&Xe(z)),Ee!==r&&(Nt=ee,Ee=Z()),ee=Ee),ee}function Sl(){var ee,Ee,Oe,gt;return Rt++,ee=ve,t.charCodeAt(ve)===34?(Ee=oe,ve++):(Ee=r,Rt===0&&Xe(xe)),Ee!==r?(t.charCodeAt(ve)===34?(Oe=oe,ve++):(Oe=r,Rt===0&&Xe(xe)),Oe!==r?(Nt=ee,Ee=Te(),ee=Ee):(ve=ee,ee=r)):(ve=ee,ee=r),ee===r&&(ee=ve,t.charCodeAt(ve)===34?(Ee=oe,ve++):(Ee=r,Rt===0&&Xe(xe)),Ee!==r?(Oe=df(),Oe!==r?(t.charCodeAt(ve)===34?(gt=oe,ve++):(gt=r,Rt===0&&Xe(xe)),gt!==r?(Nt=ee,Ee=lt(Oe),ee=Ee):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r)),Rt--,ee===r&&(Ee=r,Rt===0&&Xe($)),ee}function df(){var ee,Ee,Oe;if(ee=ve,Ee=[],Oe=Ac(),Oe!==r)for(;Oe!==r;)Ee.push(Oe),Oe=Ac();else Ee=r;return Ee!==r&&(Nt=ee,Ee=It(Ee)),ee=Ee,ee}function Ac(){var ee,Ee,Oe,gt,yt,Dt;return qt.test(t.charAt(ve))?(ee=t.charAt(ve),ve++):(ee=r,Rt===0&&Xe(ir)),ee===r&&(ee=ve,t.substr(ve,2)===Pt?(Ee=Pt,ve+=2):(Ee=r,Rt===0&&Xe(gn)),Ee!==r&&(Nt=ee,Ee=Pr()),ee=Ee,ee===r&&(ee=ve,t.substr(ve,2)===Ir?(Ee=Ir,ve+=2):(Ee=r,Rt===0&&Xe(Nr)),Ee!==r&&(Nt=ee,Ee=nn()),ee=Ee,ee===r&&(ee=ve,t.substr(ve,2)===ai?(Ee=ai,ve+=2):(Ee=r,Rt===0&&Xe(wo)),Ee!==r&&(Nt=ee,Ee=ns()),ee=Ee,ee===r&&(ee=ve,t.substr(ve,2)===to?(Ee=to,ve+=2):(Ee=r,Rt===0&&Xe(Bo)),Ee!==r&&(Nt=ee,Ee=ji()),ee=Ee,ee===r&&(ee=ve,t.substr(ve,2)===ro?(Ee=ro,ve+=2):(Ee=r,Rt===0&&Xe(vo)),Ee!==r&&(Nt=ee,Ee=RA()),ee=Ee,ee===r&&(ee=ve,t.substr(ve,2)===pf?(Ee=pf,ve+=2):(Ee=r,Rt===0&&Xe(yh)),Ee!==r&&(Nt=ee,Ee=Eh()),ee=Ee,ee===r&&(ee=ve,t.substr(ve,2)===no?(Ee=no,ve+=2):(Ee=r,Rt===0&&Xe(jn)),Ee!==r&&(Nt=ee,Ee=Fs()),ee=Ee,ee===r&&(ee=ve,t.substr(ve,2)===io?(Ee=io,ve+=2):(Ee=r,Rt===0&&Xe(lu)),Ee!==r&&(Nt=ee,Ee=cu()),ee=Ee,ee===r&&(ee=ve,t.substr(ve,2)===uu?(Ee=uu,ve+=2):(Ee=r,Rt===0&&Xe(FA)),Ee!==r?(Oe=Bi(),Oe!==r?(gt=Bi(),gt!==r?(yt=Bi(),yt!==r?(Dt=Bi(),Dt!==r?(Nt=ee,Ee=NA(Oe,gt,yt,Dt),ee=Ee):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r)))))))))),ee}function Bi(){var ee;return aa.test(t.charAt(ve))?(ee=t.charAt(ve),ve++):(ee=r,Rt===0&&Xe(la)),ee}function Qn(){var ee,Ee;if(Rt++,ee=[],gr.test(t.charAt(ve))?(Ee=t.charAt(ve),ve++):(Ee=r,Rt===0&&Xe(So)),Ee!==r)for(;Ee!==r;)ee.push(Ee),gr.test(t.charAt(ve))?(Ee=t.charAt(ve),ve++):(Ee=r,Rt===0&&Xe(So));else ee=r;return Rt--,ee===r&&(Ee=r,Rt===0&&Xe(OA)),ee}function pc(){var ee,Ee;if(Rt++,ee=[],fu.test(t.charAt(ve))?(Ee=t.charAt(ve),ve++):(Ee=r,Rt===0&&Xe(Cr)),Ee!==r)for(;Ee!==r;)ee.push(Ee),fu.test(t.charAt(ve))?(Ee=t.charAt(ve),ve++):(Ee=r,Rt===0&&Xe(Cr));else ee=r;return Rt--,ee===r&&(Ee=r,Rt===0&&Xe(Me)),ee}function Je(){var ee,Ee,Oe,gt,yt,Dt;if(ee=ve,Ee=st(),Ee!==r){for(Oe=[],gt=ve,yt=Qn(),yt===r&&(yt=null),yt!==r?(Dt=st(),Dt!==r?(yt=[yt,Dt],gt=yt):(ve=gt,gt=r)):(ve=gt,gt=r);gt!==r;)Oe.push(gt),gt=ve,yt=Qn(),yt===r&&(yt=null),yt!==r?(Dt=st(),Dt!==r?(yt=[yt,Dt],gt=yt):(ve=gt,gt=r)):(ve=gt,gt=r);Oe!==r?(Ee=[Ee,Oe],ee=Ee):(ve=ee,ee=r)}else ve=ee,ee=r;return ee}function st(){var ee;return t.substr(ve,2)===hf?(ee=hf,ve+=2):(ee=r,Rt===0&&Xe(LA)),ee===r&&(t.charCodeAt(ve)===10?(ee=MA,ve++):(ee=r,Rt===0&&Xe(Au)),ee===r&&(t.charCodeAt(ve)===13?(ee=pu,ve++):(ee=r,Rt===0&&Xe(ac)))),ee}let St=2,lr=0;if(xn=a(),xn!==r&&ve===t.length)return xn;throw xn!==r&&ve<t.length&&Xe(Ih()),gf(so,Li<t.length?t.charAt(Li):null,Li<t.length?Ua(Li,Li+1):Ua(Li,Li))}jre.exports={SyntaxError:Td,parse:kze}});function Wre(t){return t.match(Qze)?t:JSON.stringify(t)}function Vre(t){return typeof t>\"u\"?!0:typeof t==\"object\"&&t!==null&&!Array.isArray(t)?Object.keys(t).every(e=>Vre(t[e])):!1}function oU(t,e,r){if(t===null)return`null\n`;if(typeof t==\"number\"||typeof t==\"boolean\")return`${t.toString()}\n`;if(typeof t==\"string\")return`${Wre(t)}\n`;if(Array.isArray(t)){if(t.length===0)return`[]\n`;let s=\"  \".repeat(e);return`\n${t.map(n=>`${s}- ${oU(n,e+1,!1)}`).join(\"\")}`}if(typeof t==\"object\"&&t){let[s,a]=t instanceof Rx?[t.data,!1]:[t,!0],n=\"  \".repeat(e),c=Object.keys(s);a&&c.sort((p,h)=>{let E=Gre.indexOf(p),C=Gre.indexOf(h);return E===-1&&C===-1?p<h?-1:p>h?1:0:E!==-1&&C===-1?-1:E===-1&&C!==-1?1:E-C});let f=c.filter(p=>!Vre(s[p])).map((p,h)=>{let E=s[p],C=Wre(p),S=oU(E,e+1,!0),P=h>0||r?n:\"\",I=C.length>1024?`? ${C}\n${P}:`:`${C}:`,R=S.startsWith(`\n`)?S:` ${S}`;return`${P}${I}${R}`}).join(e===0?`\n`:\"\")||`\n`;return r?`\n${f}`:`${f}`}throw new Error(`Unsupported value type (${t})`)}function il(t){try{let e=oU(t,0,!1);return e!==`\n`?e:\"\"}catch(e){throw e.location&&(e.message=e.message.replace(/(\\.)?$/,` (line ${e.location.start.line}, column ${e.location.start.column})$1`)),e}}function Tze(t){return t.endsWith(`\n`)||(t+=`\n`),(0,Yre.parse)(t)}function Fze(t){if(Rze.test(t))return Tze(t);let e=(0,Fx.safeLoad)(t,{schema:Fx.FAILSAFE_SCHEMA,json:!0});if(e==null)return{};if(typeof e!=\"object\")throw new Error(`Expected an indexed object, got a ${typeof e} instead. Does your file follow Yaml's rules?`);if(Array.isArray(e))throw new Error(\"Expected an indexed object, got an array instead. Does your file follow Yaml's rules?\");return e}function cs(t){return Fze(t)}var Fx,Yre,Qze,Gre,Rx,Rze,Kre=Ct(()=>{Fx=et(Hre()),Yre=et(qre()),Qze=/^(?![-?:,\\][{}#&*!|>'\"%@` \\t\\r\\n]).([ \\t]*(?![,\\][{}:# \\t\\r\\n]).)*$/,Gre=[\"__metadata\",\"version\",\"resolution\",\"dependencies\",\"peerDependencies\",\"dependenciesMeta\",\"peerDependenciesMeta\",\"binaries\"],Rx=class{constructor(e){this.data=e}};il.PreserveOrdering=Rx;Rze=/^(#.*(\\r?\\n))*?#\\s+yarn\\s+lockfile\\s+v1\\r?\\n/i});var K2={};Vt(K2,{parseResolution:()=>Cx,parseShell:()=>yx,parseSyml:()=>cs,stringifyArgument:()=>Y_,stringifyArgumentSegment:()=>V_,stringifyArithmeticExpression:()=>Ix,stringifyCommand:()=>W_,stringifyCommandChain:()=>uE,stringifyCommandChainThen:()=>G_,stringifyCommandLine:()=>Ex,stringifyCommandLineThen:()=>q_,stringifyEnvSegment:()=>mx,stringifyRedirectArgument:()=>H2,stringifyResolution:()=>wx,stringifyShell:()=>cE,stringifyShellLine:()=>cE,stringifySyml:()=>il,stringifyValueArgument:()=>Bd});var Bc=Ct(()=>{Vee();Zee();Kre()});var zre=L((N5t,aU)=>{\"use strict\";var Nze=t=>{let e=!1,r=!1,s=!1;for(let a=0;a<t.length;a++){let n=t[a];e&&/[a-zA-Z]/.test(n)&&n.toUpperCase()===n?(t=t.slice(0,a)+\"-\"+t.slice(a),e=!1,s=r,r=!0,a++):r&&s&&/[a-zA-Z]/.test(n)&&n.toLowerCase()===n?(t=t.slice(0,a-1)+\"-\"+t.slice(a-1),s=r,r=!1,e=!0):(e=n.toLowerCase()===n&&n.toUpperCase()!==n,s=r,r=n.toUpperCase()===n&&n.toLowerCase()!==n)}return t},Jre=(t,e)=>{if(!(typeof t==\"string\"||Array.isArray(t)))throw new TypeError(\"Expected the input to be `string | string[]`\");e=Object.assign({pascalCase:!1},e);let r=a=>e.pascalCase?a.charAt(0).toUpperCase()+a.slice(1):a;return Array.isArray(t)?t=t.map(a=>a.trim()).filter(a=>a.length).join(\"-\"):t=t.trim(),t.length===0?\"\":t.length===1?e.pascalCase?t.toUpperCase():t.toLowerCase():(t!==t.toLowerCase()&&(t=Nze(t)),t=t.replace(/^[_.\\- ]+/,\"\").toLowerCase().replace(/[_.\\- ]+(\\w|$)/g,(a,n)=>n.toUpperCase()).replace(/\\d+(\\w|$)/g,a=>a.toUpperCase()),r(t))};aU.exports=Jre;aU.exports.default=Jre});var Zre=L((O5t,Oze)=>{Oze.exports=[{name:\"Agola CI\",constant:\"AGOLA\",env:\"AGOLA_GIT_REF\",pr:\"AGOLA_PULL_REQUEST_ID\"},{name:\"Appcircle\",constant:\"APPCIRCLE\",env:\"AC_APPCIRCLE\"},{name:\"AppVeyor\",constant:\"APPVEYOR\",env:\"APPVEYOR\",pr:\"APPVEYOR_PULL_REQUEST_NUMBER\"},{name:\"AWS CodeBuild\",constant:\"CODEBUILD\",env:\"CODEBUILD_BUILD_ARN\"},{name:\"Azure Pipelines\",constant:\"AZURE_PIPELINES\",env:\"TF_BUILD\",pr:{BUILD_REASON:\"PullRequest\"}},{name:\"Bamboo\",constant:\"BAMBOO\",env:\"bamboo_planKey\"},{name:\"Bitbucket Pipelines\",constant:\"BITBUCKET\",env:\"BITBUCKET_COMMIT\",pr:\"BITBUCKET_PR_ID\"},{name:\"Bitrise\",constant:\"BITRISE\",env:\"BITRISE_IO\",pr:\"BITRISE_PULL_REQUEST\"},{name:\"Buddy\",constant:\"BUDDY\",env:\"BUDDY_WORKSPACE_ID\",pr:\"BUDDY_EXECUTION_PULL_REQUEST_ID\"},{name:\"Buildkite\",constant:\"BUILDKITE\",env:\"BUILDKITE\",pr:{env:\"BUILDKITE_PULL_REQUEST\",ne:\"false\"}},{name:\"CircleCI\",constant:\"CIRCLE\",env:\"CIRCLECI\",pr:\"CIRCLE_PULL_REQUEST\"},{name:\"Cirrus CI\",constant:\"CIRRUS\",env:\"CIRRUS_CI\",pr:\"CIRRUS_PR\"},{name:\"Codefresh\",constant:\"CODEFRESH\",env:\"CF_BUILD_ID\",pr:{any:[\"CF_PULL_REQUEST_NUMBER\",\"CF_PULL_REQUEST_ID\"]}},{name:\"Codemagic\",constant:\"CODEMAGIC\",env:\"CM_BUILD_ID\",pr:\"CM_PULL_REQUEST\"},{name:\"Codeship\",constant:\"CODESHIP\",env:{CI_NAME:\"codeship\"}},{name:\"Drone\",constant:\"DRONE\",env:\"DRONE\",pr:{DRONE_BUILD_EVENT:\"pull_request\"}},{name:\"dsari\",constant:\"DSARI\",env:\"DSARI\"},{name:\"Earthly\",constant:\"EARTHLY\",env:\"EARTHLY_CI\"},{name:\"Expo Application Services\",constant:\"EAS\",env:\"EAS_BUILD\"},{name:\"Gerrit\",constant:\"GERRIT\",env:\"GERRIT_PROJECT\"},{name:\"Gitea Actions\",constant:\"GITEA_ACTIONS\",env:\"GITEA_ACTIONS\"},{name:\"GitHub Actions\",constant:\"GITHUB_ACTIONS\",env:\"GITHUB_ACTIONS\",pr:{GITHUB_EVENT_NAME:\"pull_request\"}},{name:\"GitLab CI\",constant:\"GITLAB\",env:\"GITLAB_CI\",pr:\"CI_MERGE_REQUEST_ID\"},{name:\"GoCD\",constant:\"GOCD\",env:\"GO_PIPELINE_LABEL\"},{name:\"Google Cloud Build\",constant:\"GOOGLE_CLOUD_BUILD\",env:\"BUILDER_OUTPUT\"},{name:\"Harness CI\",constant:\"HARNESS\",env:\"HARNESS_BUILD_ID\"},{name:\"Heroku\",constant:\"HEROKU\",env:{env:\"NODE\",includes:\"/app/.heroku/node/bin/node\"}},{name:\"Hudson\",constant:\"HUDSON\",env:\"HUDSON_URL\"},{name:\"Jenkins\",constant:\"JENKINS\",env:[\"JENKINS_URL\",\"BUILD_ID\"],pr:{any:[\"ghprbPullId\",\"CHANGE_ID\"]}},{name:\"LayerCI\",constant:\"LAYERCI\",env:\"LAYERCI\",pr:\"LAYERCI_PULL_REQUEST\"},{name:\"Magnum CI\",constant:\"MAGNUM\",env:\"MAGNUM\"},{name:\"Netlify CI\",constant:\"NETLIFY\",env:\"NETLIFY\",pr:{env:\"PULL_REQUEST\",ne:\"false\"}},{name:\"Nevercode\",constant:\"NEVERCODE\",env:\"NEVERCODE\",pr:{env:\"NEVERCODE_PULL_REQUEST\",ne:\"false\"}},{name:\"Prow\",constant:\"PROW\",env:\"PROW_JOB_ID\"},{name:\"ReleaseHub\",constant:\"RELEASEHUB\",env:\"RELEASE_BUILD_ID\"},{name:\"Render\",constant:\"RENDER\",env:\"RENDER\",pr:{IS_PULL_REQUEST:\"true\"}},{name:\"Sail CI\",constant:\"SAIL\",env:\"SAILCI\",pr:\"SAIL_PULL_REQUEST_NUMBER\"},{name:\"Screwdriver\",constant:\"SCREWDRIVER\",env:\"SCREWDRIVER\",pr:{env:\"SD_PULL_REQUEST\",ne:\"false\"}},{name:\"Semaphore\",constant:\"SEMAPHORE\",env:\"SEMAPHORE\",pr:\"PULL_REQUEST_NUMBER\"},{name:\"Sourcehut\",constant:\"SOURCEHUT\",env:{CI_NAME:\"sourcehut\"}},{name:\"Strider CD\",constant:\"STRIDER\",env:\"STRIDER\"},{name:\"TaskCluster\",constant:\"TASKCLUSTER\",env:[\"TASK_ID\",\"RUN_ID\"]},{name:\"TeamCity\",constant:\"TEAMCITY\",env:\"TEAMCITY_VERSION\"},{name:\"Travis CI\",constant:\"TRAVIS\",env:\"TRAVIS\",pr:{env:\"TRAVIS_PULL_REQUEST\",ne:\"false\"}},{name:\"Vela\",constant:\"VELA\",env:\"VELA\",pr:{VELA_PULL_REQUEST:\"1\"}},{name:\"Vercel\",constant:\"VERCEL\",env:{any:[\"NOW_BUILDER\",\"VERCEL\"]},pr:\"VERCEL_GIT_PULL_REQUEST_ID\"},{name:\"Visual Studio App Center\",constant:\"APPCENTER\",env:\"APPCENTER_BUILD_ID\"},{name:\"Woodpecker\",constant:\"WOODPECKER\",env:{CI:\"woodpecker\"},pr:{CI_BUILD_EVENT:\"pull_request\"}},{name:\"Xcode Cloud\",constant:\"XCODE_CLOUD\",env:\"CI_XCODE_PROJECT\",pr:\"CI_PULL_REQUEST_NUMBER\"},{name:\"Xcode Server\",constant:\"XCODE_SERVER\",env:\"XCS\"}]});var Rd=L(_l=>{\"use strict\";var $re=Zre(),Ps=process.env;Object.defineProperty(_l,\"_vendors\",{value:$re.map(function(t){return t.constant})});_l.name=null;_l.isPR=null;$re.forEach(function(t){let r=(Array.isArray(t.env)?t.env:[t.env]).every(function(s){return Xre(s)});if(_l[t.constant]=r,!!r)switch(_l.name=t.name,typeof t.pr){case\"string\":_l.isPR=!!Ps[t.pr];break;case\"object\":\"env\"in t.pr?_l.isPR=t.pr.env in Ps&&Ps[t.pr.env]!==t.pr.ne:\"any\"in t.pr?_l.isPR=t.pr.any.some(function(s){return!!Ps[s]}):_l.isPR=Xre(t.pr);break;default:_l.isPR=null}});_l.isCI=!!(Ps.CI!==\"false\"&&(Ps.BUILD_ID||Ps.BUILD_NUMBER||Ps.CI||Ps.CI_APP_ID||Ps.CI_BUILD_ID||Ps.CI_BUILD_NUMBER||Ps.CI_NAME||Ps.CONTINUOUS_INTEGRATION||Ps.RUN_ID||_l.name));function Xre(t){return typeof t==\"string\"?!!Ps[t]:\"env\"in t?Ps[t.env]&&Ps[t.env].includes(t.includes):\"any\"in t?t.any.some(function(e){return!!Ps[e]}):Object.keys(t).every(function(e){return Ps[e]===t[e]})}});var ei,En,Fd,lU,Nx,ene,cU,uU,Ox=Ct(()=>{(function(t){t.StartOfInput=\"\\0\",t.EndOfInput=\"\u0001\",t.EndOfPartialInput=\"\u0002\"})(ei||(ei={}));(function(t){t[t.InitialNode=0]=\"InitialNode\",t[t.SuccessNode=1]=\"SuccessNode\",t[t.ErrorNode=2]=\"ErrorNode\",t[t.CustomNode=3]=\"CustomNode\"})(En||(En={}));Fd=-1,lU=/^(-h|--help)(?:=([0-9]+))?$/,Nx=/^(--[a-z]+(?:-[a-z]+)*|-[a-zA-Z]+)$/,ene=/^-[a-zA-Z]{2,}$/,cU=/^([^=]+)=([\\s\\S]*)$/,uU=process.env.DEBUG_CLI===\"1\"});var nt,yE,Lx,fU,Mx=Ct(()=>{Ox();nt=class extends Error{constructor(e){super(e),this.clipanion={type:\"usage\"},this.name=\"UsageError\"}},yE=class extends Error{constructor(e,r){if(super(),this.input=e,this.candidates=r,this.clipanion={type:\"none\"},this.name=\"UnknownSyntaxError\",this.candidates.length===0)this.message=\"Command not found, but we're not sure what's the alternative.\";else if(this.candidates.every(s=>s.reason!==null&&s.reason===r[0].reason)){let[{reason:s}]=this.candidates;this.message=`${s}\n\n${this.candidates.map(({usage:a})=>`$ ${a}`).join(`\n`)}`}else if(this.candidates.length===1){let[{usage:s}]=this.candidates;this.message=`Command not found; did you mean:\n\n$ ${s}\n${fU(e)}`}else this.message=`Command not found; did you mean one of:\n\n${this.candidates.map(({usage:s},a)=>`${`${a}.`.padStart(4)} ${s}`).join(`\n`)}\n\n${fU(e)}`}},Lx=class extends Error{constructor(e,r){super(),this.input=e,this.usages=r,this.clipanion={type:\"none\"},this.name=\"AmbiguousSyntaxError\",this.message=`Cannot find which to pick amongst the following alternatives:\n\n${this.usages.map((s,a)=>`${`${a}.`.padStart(4)} ${s}`).join(`\n`)}\n\n${fU(e)}`}},fU=t=>`While running ${t.filter(e=>e!==ei.EndOfInput&&e!==ei.EndOfPartialInput).map(e=>{let r=JSON.stringify(e);return e.match(/\\s/)||e.length===0||r!==`\"${e}\"`?r:e}).join(\" \")}`});function Lze(t){let e=t.split(`\n`),r=e.filter(a=>a.match(/\\S/)),s=r.length>0?r.reduce((a,n)=>Math.min(a,n.length-n.trimStart().length),Number.MAX_VALUE):0;return e.map(a=>a.slice(s).trimRight()).join(`\n`)}function qo(t,{format:e,paragraphs:r}){return t=t.replace(/\\r\\n?/g,`\n`),t=Lze(t),t=t.replace(/^\\n+|\\n+$/g,\"\"),t=t.replace(/^(\\s*)-([^\\n]*?)\\n+/gm,`$1-$2\n\n`),t=t.replace(/\\n(\\n)?\\n*/g,(s,a)=>a||\" \"),r&&(t=t.split(/\\n/).map(s=>{let a=s.match(/^\\s*[*-][\\t ]+(.*)/);if(!a)return s.match(/(.{1,80})(?: |$)/g).join(`\n`);let n=s.length-s.trimStart().length;return a[1].match(new RegExp(`(.{1,${78-n}})(?: |$)`,\"g\")).map((c,f)=>\" \".repeat(n)+(f===0?\"- \":\"  \")+c).join(`\n`)}).join(`\n\n`)),t=t.replace(/(`+)((?:.|[\\n])*?)\\1/g,(s,a,n)=>e.code(a+n+a)),t=t.replace(/(\\*\\*)((?:.|[\\n])*?)\\1/g,(s,a,n)=>e.bold(a+n+a)),t?`${t}\n`:\"\"}var AU,tne,rne,pU=Ct(()=>{AU=Array(80).fill(\"\\u2501\");for(let t=0;t<=24;++t)AU[AU.length-t]=`\\x1B[38;5;${232+t}m\\u2501`;tne={header:t=>`\\x1B[1m\\u2501\\u2501\\u2501 ${t}${t.length<75?` ${AU.slice(t.length+5).join(\"\")}`:\":\"}\\x1B[0m`,bold:t=>`\\x1B[1m${t}\\x1B[22m`,error:t=>`\\x1B[31m\\x1B[1m${t}\\x1B[22m\\x1B[39m`,code:t=>`\\x1B[36m${t}\\x1B[39m`},rne={header:t=>t,bold:t=>t,error:t=>t,code:t=>t}});function Ea(t){return{...t,[J2]:!0}}function Gf(t,e){return typeof t>\"u\"?[t,e]:typeof t==\"object\"&&t!==null&&!Array.isArray(t)?[void 0,t]:[t,e]}function _x(t,{mergeName:e=!1}={}){let r=t.match(/^([^:]+): (.*)$/m);if(!r)return\"validation failed\";let[,s,a]=r;return e&&(a=a[0].toLowerCase()+a.slice(1)),a=s!==\".\"||!e?`${s.replace(/^\\.(\\[|$)/,\"$1\")}: ${a}`:`: ${a}`,a}function z2(t,e){return e.length===1?new nt(`${t}${_x(e[0],{mergeName:!0})}`):new nt(`${t}:\n${e.map(r=>`\n- ${_x(r)}`).join(\"\")}`)}function Nd(t,e,r){if(typeof r>\"u\")return e;let s=[],a=[],n=f=>{let p=e;return e=f,n.bind(null,p)};if(!r(e,{errors:s,coercions:a,coercion:n}))throw z2(`Invalid value for ${t}`,s);for(let[,f]of a)f();return e}var J2,Bp=Ct(()=>{Mx();J2=Symbol(\"clipanion/isOption\")});var Ia={};Vt(Ia,{KeyRelationship:()=>Wf,TypeAssertionError:()=>l0,applyCascade:()=>$2,as:()=>rZe,assert:()=>$ze,assertWithErrors:()=>eZe,cascade:()=>qx,fn:()=>nZe,hasAtLeastOneKey:()=>IU,hasExactLength:()=>ane,hasForbiddenKeys:()=>wZe,hasKeyRelationship:()=>tB,hasMaxLength:()=>sZe,hasMinLength:()=>iZe,hasMutuallyExclusiveKeys:()=>BZe,hasRequiredKeys:()=>CZe,hasUniqueItems:()=>oZe,isArray:()=>Ux,isAtLeast:()=>yU,isAtMost:()=>cZe,isBase64:()=>mZe,isBoolean:()=>Wze,isDate:()=>Vze,isDict:()=>zze,isEnum:()=>po,isHexColor:()=>dZe,isISO8601:()=>gZe,isInExclusiveRange:()=>fZe,isInInclusiveRange:()=>uZe,isInstanceOf:()=>Xze,isInteger:()=>EU,isJSON:()=>yZe,isLiteral:()=>ine,isLowerCase:()=>AZe,isMap:()=>Jze,isNegative:()=>aZe,isNullable:()=>IZe,isNumber:()=>dU,isObject:()=>sne,isOneOf:()=>mU,isOptional:()=>EZe,isPartial:()=>Zze,isPayload:()=>Yze,isPositive:()=>lZe,isRecord:()=>jx,isSet:()=>Kze,isString:()=>IE,isTuple:()=>Hx,isUUID4:()=>hZe,isUnknown:()=>gU,isUpperCase:()=>pZe,makeTrait:()=>one,makeValidator:()=>Wr,matchesRegExp:()=>X2,softAssert:()=>tZe});function ti(t){return t===null?\"null\":t===void 0?\"undefined\":t===\"\"?\"an empty string\":typeof t==\"symbol\"?`<${t.toString()}>`:Array.isArray(t)?\"an array\":JSON.stringify(t)}function EE(t,e){if(t.length===0)return\"nothing\";if(t.length===1)return ti(t[0]);let r=t.slice(0,-1),s=t[t.length-1],a=t.length>2?`, ${e} `:` ${e} `;return`${r.map(n=>ti(n)).join(\", \")}${a}${ti(s)}`}function a0(t,e){var r,s,a;return typeof e==\"number\"?`${(r=t?.p)!==null&&r!==void 0?r:\".\"}[${e}]`:Mze.test(e)?`${(s=t?.p)!==null&&s!==void 0?s:\"\"}.${e}`:`${(a=t?.p)!==null&&a!==void 0?a:\".\"}[${JSON.stringify(e)}]`}function hU(t,e,r){return t===1?e:r}function mr({errors:t,p:e}={},r){return t?.push(`${e??\".\"}: ${r}`),!1}function qze(t,e){return r=>{t[e]=r}}function Yf(t,e){return r=>{let s=t[e];return t[e]=r,Yf(t,e).bind(null,s)}}function Z2(t,e,r){let s=()=>(t(r()),a),a=()=>(t(e),s);return s}function gU(){return Wr({test:(t,e)=>!0})}function ine(t){return Wr({test:(e,r)=>e!==t?mr(r,`Expected ${ti(t)} (got ${ti(e)})`):!0})}function IE(){return Wr({test:(t,e)=>typeof t!=\"string\"?mr(e,`Expected a string (got ${ti(t)})`):!0})}function po(t){let e=Array.isArray(t)?t:Object.values(t),r=e.every(a=>typeof a==\"string\"||typeof a==\"number\"),s=new Set(e);return s.size===1?ine([...s][0]):Wr({test:(a,n)=>s.has(a)?!0:r?mr(n,`Expected one of ${EE(e,\"or\")} (got ${ti(a)})`):mr(n,`Expected a valid enumeration value (got ${ti(a)})`)})}function Wze(){return Wr({test:(t,e)=>{var r;if(typeof t!=\"boolean\"){if(typeof e?.coercions<\"u\"){if(typeof e?.coercion>\"u\")return mr(e,\"Unbound coercion result\");let s=Gze.get(t);if(typeof s<\"u\")return e.coercions.push([(r=e.p)!==null&&r!==void 0?r:\".\",e.coercion.bind(null,s)]),!0}return mr(e,`Expected a boolean (got ${ti(t)})`)}return!0}})}function dU(){return Wr({test:(t,e)=>{var r;if(typeof t!=\"number\"){if(typeof e?.coercions<\"u\"){if(typeof e?.coercion>\"u\")return mr(e,\"Unbound coercion result\");let s;if(typeof t==\"string\"){let a;try{a=JSON.parse(t)}catch{}if(typeof a==\"number\")if(JSON.stringify(a)===t)s=a;else return mr(e,`Received a number that can't be safely represented by the runtime (${t})`)}if(typeof s<\"u\")return e.coercions.push([(r=e.p)!==null&&r!==void 0?r:\".\",e.coercion.bind(null,s)]),!0}return mr(e,`Expected a number (got ${ti(t)})`)}return!0}})}function Yze(t){return Wr({test:(e,r)=>{var s;if(typeof r?.coercions>\"u\")return mr(r,\"The isPayload predicate can only be used with coercion enabled\");if(typeof r.coercion>\"u\")return mr(r,\"Unbound coercion result\");if(typeof e!=\"string\")return mr(r,`Expected a string (got ${ti(e)})`);let a;try{a=JSON.parse(e)}catch{return mr(r,`Expected a JSON string (got ${ti(e)})`)}let n={value:a};return t(a,Object.assign(Object.assign({},r),{coercion:Yf(n,\"value\")}))?(r.coercions.push([(s=r.p)!==null&&s!==void 0?s:\".\",r.coercion.bind(null,n.value)]),!0):!1}})}function Vze(){return Wr({test:(t,e)=>{var r;if(!(t instanceof Date)){if(typeof e?.coercions<\"u\"){if(typeof e?.coercion>\"u\")return mr(e,\"Unbound coercion result\");let s;if(typeof t==\"string\"&&nne.test(t))s=new Date(t);else{let a;if(typeof t==\"string\"){let n;try{n=JSON.parse(t)}catch{}typeof n==\"number\"&&(a=n)}else typeof t==\"number\"&&(a=t);if(typeof a<\"u\")if(Number.isSafeInteger(a)||!Number.isSafeInteger(a*1e3))s=new Date(a*1e3);else return mr(e,`Received a timestamp that can't be safely represented by the runtime (${t})`)}if(typeof s<\"u\")return e.coercions.push([(r=e.p)!==null&&r!==void 0?r:\".\",e.coercion.bind(null,s)]),!0}return mr(e,`Expected a date (got ${ti(t)})`)}return!0}})}function Ux(t,{delimiter:e}={}){return Wr({test:(r,s)=>{var a;let n=r;if(typeof r==\"string\"&&typeof e<\"u\"&&typeof s?.coercions<\"u\"){if(typeof s?.coercion>\"u\")return mr(s,\"Unbound coercion result\");r=r.split(e)}if(!Array.isArray(r))return mr(s,`Expected an array (got ${ti(r)})`);let c=!0;for(let f=0,p=r.length;f<p&&(c=t(r[f],Object.assign(Object.assign({},s),{p:a0(s,f),coercion:Yf(r,f)}))&&c,!(!c&&s?.errors==null));++f);return r!==n&&s.coercions.push([(a=s.p)!==null&&a!==void 0?a:\".\",s.coercion.bind(null,r)]),c}})}function Kze(t,{delimiter:e}={}){let r=Ux(t,{delimiter:e});return Wr({test:(s,a)=>{var n,c;if(Object.getPrototypeOf(s).toString()===\"[object Set]\")if(typeof a?.coercions<\"u\"){if(typeof a?.coercion>\"u\")return mr(a,\"Unbound coercion result\");let f=[...s],p=[...s];if(!r(p,Object.assign(Object.assign({},a),{coercion:void 0})))return!1;let h=()=>p.some((E,C)=>E!==f[C])?new Set(p):s;return a.coercions.push([(n=a.p)!==null&&n!==void 0?n:\".\",Z2(a.coercion,s,h)]),!0}else{let f=!0;for(let p of s)if(f=t(p,Object.assign({},a))&&f,!f&&a?.errors==null)break;return f}if(typeof a?.coercions<\"u\"){if(typeof a?.coercion>\"u\")return mr(a,\"Unbound coercion result\");let f={value:s};return r(s,Object.assign(Object.assign({},a),{coercion:Yf(f,\"value\")}))?(a.coercions.push([(c=a.p)!==null&&c!==void 0?c:\".\",Z2(a.coercion,s,()=>new Set(f.value))]),!0):!1}return mr(a,`Expected a set (got ${ti(s)})`)}})}function Jze(t,e){let r=Ux(Hx([t,e])),s=jx(e,{keys:t});return Wr({test:(a,n)=>{var c,f,p;if(Object.getPrototypeOf(a).toString()===\"[object Map]\")if(typeof n?.coercions<\"u\"){if(typeof n?.coercion>\"u\")return mr(n,\"Unbound coercion result\");let h=[...a],E=[...a];if(!r(E,Object.assign(Object.assign({},n),{coercion:void 0})))return!1;let C=()=>E.some((S,P)=>S[0]!==h[P][0]||S[1]!==h[P][1])?new Map(E):a;return n.coercions.push([(c=n.p)!==null&&c!==void 0?c:\".\",Z2(n.coercion,a,C)]),!0}else{let h=!0;for(let[E,C]of a)if(h=t(E,Object.assign({},n))&&h,!h&&n?.errors==null||(h=e(C,Object.assign(Object.assign({},n),{p:a0(n,E)}))&&h,!h&&n?.errors==null))break;return h}if(typeof n?.coercions<\"u\"){if(typeof n?.coercion>\"u\")return mr(n,\"Unbound coercion result\");let h={value:a};return Array.isArray(a)?r(a,Object.assign(Object.assign({},n),{coercion:void 0}))?(n.coercions.push([(f=n.p)!==null&&f!==void 0?f:\".\",Z2(n.coercion,a,()=>new Map(h.value))]),!0):!1:s(a,Object.assign(Object.assign({},n),{coercion:Yf(h,\"value\")}))?(n.coercions.push([(p=n.p)!==null&&p!==void 0?p:\".\",Z2(n.coercion,a,()=>new Map(Object.entries(h.value)))]),!0):!1}return mr(n,`Expected a map (got ${ti(a)})`)}})}function Hx(t,{delimiter:e}={}){let r=ane(t.length);return Wr({test:(s,a)=>{var n;if(typeof s==\"string\"&&typeof e<\"u\"&&typeof a?.coercions<\"u\"){if(typeof a?.coercion>\"u\")return mr(a,\"Unbound coercion result\");s=s.split(e),a.coercions.push([(n=a.p)!==null&&n!==void 0?n:\".\",a.coercion.bind(null,s)])}if(!Array.isArray(s))return mr(a,`Expected a tuple (got ${ti(s)})`);let c=r(s,Object.assign({},a));for(let f=0,p=s.length;f<p&&f<t.length&&(c=t[f](s[f],Object.assign(Object.assign({},a),{p:a0(a,f),coercion:Yf(s,f)}))&&c,!(!c&&a?.errors==null));++f);return c}})}function jx(t,{keys:e=null}={}){let r=Ux(Hx([e??IE(),t]));return Wr({test:(s,a)=>{var n;if(Array.isArray(s)&&typeof a?.coercions<\"u\")return typeof a?.coercion>\"u\"?mr(a,\"Unbound coercion result\"):r(s,Object.assign(Object.assign({},a),{coercion:void 0}))?(s=Object.fromEntries(s),a.coercions.push([(n=a.p)!==null&&n!==void 0?n:\".\",a.coercion.bind(null,s)]),!0):!1;if(typeof s!=\"object\"||s===null)return mr(a,`Expected an object (got ${ti(s)})`);let c=Object.keys(s),f=!0;for(let p=0,h=c.length;p<h&&(f||a?.errors!=null);++p){let E=c[p],C=s[E];if(E===\"__proto__\"||E===\"constructor\"){f=mr(Object.assign(Object.assign({},a),{p:a0(a,E)}),\"Unsafe property name\");continue}if(e!==null&&!e(E,a)){f=!1;continue}if(!t(C,Object.assign(Object.assign({},a),{p:a0(a,E),coercion:Yf(s,E)}))){f=!1;continue}}return f}})}function zze(t,e={}){return jx(t,e)}function sne(t,{extra:e=null}={}){let r=Object.keys(t),s=Wr({test:(a,n)=>{if(typeof a!=\"object\"||a===null)return mr(n,`Expected an object (got ${ti(a)})`);let c=new Set([...r,...Object.keys(a)]),f={},p=!0;for(let h of c){if(h===\"constructor\"||h===\"__proto__\")p=mr(Object.assign(Object.assign({},n),{p:a0(n,h)}),\"Unsafe property name\");else{let E=Object.prototype.hasOwnProperty.call(t,h)?t[h]:void 0,C=Object.prototype.hasOwnProperty.call(a,h)?a[h]:void 0;typeof E<\"u\"?p=E(C,Object.assign(Object.assign({},n),{p:a0(n,h),coercion:Yf(a,h)}))&&p:e===null?p=mr(Object.assign(Object.assign({},n),{p:a0(n,h)}),`Extraneous property (got ${ti(C)})`):Object.defineProperty(f,h,{enumerable:!0,get:()=>C,set:qze(a,h)})}if(!p&&n?.errors==null)break}return e!==null&&(p||n?.errors!=null)&&(p=e(f,n)&&p),p}});return Object.assign(s,{properties:t})}function Zze(t){return sne(t,{extra:jx(gU())})}function one(t){return()=>t}function Wr({test:t}){return one(t)()}function $ze(t,e){if(!e(t))throw new l0}function eZe(t,e){let r=[];if(!e(t,{errors:r}))throw new l0({errors:r})}function tZe(t,e){}function rZe(t,e,{coerce:r=!1,errors:s,throw:a}={}){let n=s?[]:void 0;if(!r){if(e(t,{errors:n}))return a?t:{value:t,errors:void 0};if(a)throw new l0({errors:n});return{value:void 0,errors:n??!0}}let c={value:t},f=Yf(c,\"value\"),p=[];if(!e(t,{errors:n,coercion:f,coercions:p})){if(a)throw new l0({errors:n});return{value:void 0,errors:n??!0}}for(let[,h]of p)h();return a?c.value:{value:c.value,errors:void 0}}function nZe(t,e){let r=Hx(t);return(...s)=>{if(!r(s))throw new l0;return e(...s)}}function iZe(t){return Wr({test:(e,r)=>e.length>=t?!0:mr(r,`Expected to have a length of at least ${t} elements (got ${e.length})`)})}function sZe(t){return Wr({test:(e,r)=>e.length<=t?!0:mr(r,`Expected to have a length of at most ${t} elements (got ${e.length})`)})}function ane(t){return Wr({test:(e,r)=>e.length!==t?mr(r,`Expected to have a length of exactly ${t} elements (got ${e.length})`):!0})}function oZe({map:t}={}){return Wr({test:(e,r)=>{let s=new Set,a=new Set;for(let n=0,c=e.length;n<c;++n){let f=e[n],p=typeof t<\"u\"?t(f):f;if(s.has(p)){if(a.has(p))continue;mr(r,`Expected to contain unique elements; got a duplicate with ${ti(e)}`),a.add(p)}else s.add(p)}return a.size===0}})}function aZe(){return Wr({test:(t,e)=>t<=0?!0:mr(e,`Expected to be negative (got ${t})`)})}function lZe(){return Wr({test:(t,e)=>t>=0?!0:mr(e,`Expected to be positive (got ${t})`)})}function yU(t){return Wr({test:(e,r)=>e>=t?!0:mr(r,`Expected to be at least ${t} (got ${e})`)})}function cZe(t){return Wr({test:(e,r)=>e<=t?!0:mr(r,`Expected to be at most ${t} (got ${e})`)})}function uZe(t,e){return Wr({test:(r,s)=>r>=t&&r<=e?!0:mr(s,`Expected to be in the [${t}; ${e}] range (got ${r})`)})}function fZe(t,e){return Wr({test:(r,s)=>r>=t&&r<e?!0:mr(s,`Expected to be in the [${t}; ${e}[ range (got ${r})`)})}function EU({unsafe:t=!1}={}){return Wr({test:(e,r)=>e!==Math.round(e)?mr(r,`Expected to be an integer (got ${e})`):!t&&!Number.isSafeInteger(e)?mr(r,`Expected to be a safe integer (got ${e})`):!0})}function X2(t){return Wr({test:(e,r)=>t.test(e)?!0:mr(r,`Expected to match the pattern ${t.toString()} (got ${ti(e)})`)})}function AZe(){return Wr({test:(t,e)=>t!==t.toLowerCase()?mr(e,`Expected to be all-lowercase (got ${t})`):!0})}function pZe(){return Wr({test:(t,e)=>t!==t.toUpperCase()?mr(e,`Expected to be all-uppercase (got ${t})`):!0})}function hZe(){return Wr({test:(t,e)=>jze.test(t)?!0:mr(e,`Expected to be a valid UUID v4 (got ${ti(t)})`)})}function gZe(){return Wr({test:(t,e)=>nne.test(t)?!0:mr(e,`Expected to be a valid ISO 8601 date string (got ${ti(t)})`)})}function dZe({alpha:t=!1}){return Wr({test:(e,r)=>(t?_ze.test(e):Uze.test(e))?!0:mr(r,`Expected to be a valid hexadecimal color string (got ${ti(e)})`)})}function mZe(){return Wr({test:(t,e)=>Hze.test(t)?!0:mr(e,`Expected to be a valid base 64 string (got ${ti(t)})`)})}function yZe(t=gU()){return Wr({test:(e,r)=>{let s;try{s=JSON.parse(e)}catch{return mr(r,`Expected to be a valid JSON string (got ${ti(e)})`)}return t(s,r)}})}function qx(t,...e){let r=Array.isArray(e[0])?e[0]:e;return Wr({test:(s,a)=>{var n,c;let f={value:s},p=typeof a?.coercions<\"u\"?Yf(f,\"value\"):void 0,h=typeof a?.coercions<\"u\"?[]:void 0;if(!t(s,Object.assign(Object.assign({},a),{coercion:p,coercions:h})))return!1;let E=[];if(typeof h<\"u\")for(let[,C]of h)E.push(C());try{if(typeof a?.coercions<\"u\"){if(f.value!==s){if(typeof a?.coercion>\"u\")return mr(a,\"Unbound coercion result\");a.coercions.push([(n=a.p)!==null&&n!==void 0?n:\".\",a.coercion.bind(null,f.value)])}(c=a?.coercions)===null||c===void 0||c.push(...h)}return r.every(C=>C(f.value,a))}finally{for(let C of E)C()}}})}function $2(t,...e){let r=Array.isArray(e[0])?e[0]:e;return qx(t,r)}function EZe(t){return Wr({test:(e,r)=>typeof e>\"u\"?!0:t(e,r)})}function IZe(t){return Wr({test:(e,r)=>e===null?!0:t(e,r)})}function CZe(t,e){var r;let s=new Set(t),a=eB[(r=e?.missingIf)!==null&&r!==void 0?r:\"missing\"];return Wr({test:(n,c)=>{let f=new Set(Object.keys(n)),p=[];for(let h of s)a(f,h,n)||p.push(h);return p.length>0?mr(c,`Missing required ${hU(p.length,\"property\",\"properties\")} ${EE(p,\"and\")}`):!0}})}function IU(t,e){var r;let s=new Set(t),a=eB[(r=e?.missingIf)!==null&&r!==void 0?r:\"missing\"];return Wr({test:(n,c)=>Object.keys(n).some(h=>a(s,h,n))?!0:mr(c,`Missing at least one property from ${EE(Array.from(s),\"or\")}`)})}function wZe(t,e){var r;let s=new Set(t),a=eB[(r=e?.missingIf)!==null&&r!==void 0?r:\"missing\"];return Wr({test:(n,c)=>{let f=new Set(Object.keys(n)),p=[];for(let h of s)a(f,h,n)&&p.push(h);return p.length>0?mr(c,`Forbidden ${hU(p.length,\"property\",\"properties\")} ${EE(p,\"and\")}`):!0}})}function BZe(t,e){var r;let s=new Set(t),a=eB[(r=e?.missingIf)!==null&&r!==void 0?r:\"missing\"];return Wr({test:(n,c)=>{let f=new Set(Object.keys(n)),p=[];for(let h of s)a(f,h,n)&&p.push(h);return p.length>1?mr(c,`Mutually exclusive properties ${EE(p,\"and\")}`):!0}})}function tB(t,e,r,s){var a,n;let c=new Set((a=s?.ignore)!==null&&a!==void 0?a:[]),f=eB[(n=s?.missingIf)!==null&&n!==void 0?n:\"missing\"],p=new Set(r),h=vZe[e],E=e===Wf.Forbids?\"or\":\"and\";return Wr({test:(C,S)=>{let P=new Set(Object.keys(C));if(!f(P,t,C)||c.has(C[t]))return!0;let I=[];for(let R of p)(f(P,R,C)&&!c.has(C[R]))!==h.expect&&I.push(R);return I.length>=1?mr(S,`Property \"${t}\" ${h.message} ${hU(I.length,\"property\",\"properties\")} ${EE(I,E)}`):!0}})}var Mze,_ze,Uze,Hze,jze,nne,Gze,Xze,mU,l0,eB,Wf,vZe,Ul=Ct(()=>{Mze=/^[a-zA-Z_][a-zA-Z0-9_]*$/;_ze=/^#[0-9a-f]{6}$/i,Uze=/^#[0-9a-f]{6}([0-9a-f]{2})?$/i,Hze=/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/,jze=/^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}$/i,nne=/^(?:[1-9]\\d{3}(-?)(?:(?:0[1-9]|1[0-2])\\1(?:0[1-9]|1\\d|2[0-8])|(?:0[13-9]|1[0-2])\\1(?:29|30)|(?:0[13578]|1[02])(?:\\1)31|00[1-9]|0[1-9]\\d|[12]\\d{2}|3(?:[0-5]\\d|6[0-5]))|(?:[1-9]\\d(?:0[48]|[2468][048]|[13579][26])|(?:[2468][048]|[13579][26])00)(?:(-?)02(?:\\2)29|-?366))T(?:[01]\\d|2[0-3])(:?)[0-5]\\d(?:\\3[0-5]\\d)?(?:Z|[+-][01]\\d(?:\\3[0-5]\\d)?)$/;Gze=new Map([[\"true\",!0],[\"True\",!0],[\"1\",!0],[1,!0],[\"false\",!1],[\"False\",!1],[\"0\",!1],[0,!1]]);Xze=t=>Wr({test:(e,r)=>e instanceof t?!0:mr(r,`Expected an instance of ${t.name} (got ${ti(e)})`)}),mU=(t,{exclusive:e=!1}={})=>Wr({test:(r,s)=>{var a,n,c;let f=[],p=typeof s?.errors<\"u\"?[]:void 0;for(let h=0,E=t.length;h<E;++h){let C=typeof s?.errors<\"u\"?[]:void 0,S=typeof s?.coercions<\"u\"?[]:void 0;if(t[h](r,Object.assign(Object.assign({},s),{errors:C,coercions:S,p:`${(a=s?.p)!==null&&a!==void 0?a:\".\"}#${h+1}`}))){if(f.push([`#${h+1}`,S]),!e)break}else p?.push(C[0])}if(f.length===1){let[,h]=f[0];return typeof h<\"u\"&&((n=s?.coercions)===null||n===void 0||n.push(...h)),!0}return f.length>1?mr(s,`Expected to match exactly a single predicate (matched ${f.join(\", \")})`):(c=s?.errors)===null||c===void 0||c.push(...p),!1}});l0=class extends Error{constructor({errors:e}={}){let r=\"Type mismatch\";if(e&&e.length>0){r+=`\n`;for(let s of e)r+=`\n- ${s}`}super(r)}};eB={missing:(t,e)=>t.has(e),undefined:(t,e,r)=>t.has(e)&&typeof r[e]<\"u\",nil:(t,e,r)=>t.has(e)&&r[e]!=null,falsy:(t,e,r)=>t.has(e)&&!!r[e]};(function(t){t.Forbids=\"Forbids\",t.Requires=\"Requires\"})(Wf||(Wf={}));vZe={[Wf.Forbids]:{expect:!1,message:\"forbids using\"},[Wf.Requires]:{expect:!0,message:\"requires using\"}}});var ot,c0=Ct(()=>{Bp();ot=class{constructor(){this.help=!1}static Usage(e){return e}async catch(e){throw e}async validateAndExecute(){let r=this.constructor.schema;if(Array.isArray(r)){let{isDict:a,isUnknown:n,applyCascade:c}=await Promise.resolve().then(()=>(Ul(),Ia)),f=c(a(n()),r),p=[],h=[];if(!f(this,{errors:p,coercions:h}))throw z2(\"Invalid option schema\",p);for(let[,C]of h)C()}else if(r!=null)throw new Error(\"Invalid command schema\");let s=await this.execute();return typeof s<\"u\"?s:0}};ot.isOption=J2;ot.Default=[]});function sl(t){uU&&console.log(t)}function cne(){let t={nodes:[]};for(let e=0;e<En.CustomNode;++e)t.nodes.push(Hl());return t}function SZe(t){let e=cne(),r=[],s=e.nodes.length;for(let a of t){r.push(s);for(let n=0;n<a.nodes.length;++n)fne(n)||e.nodes.push(RZe(a.nodes[n],s));s+=a.nodes.length-En.CustomNode+1}for(let a of r)CE(e,En.InitialNode,a);return e}function Mu(t,e){return t.nodes.push(e),t.nodes.length-1}function DZe(t){let e=new Set,r=s=>{if(e.has(s))return;e.add(s);let a=t.nodes[s];for(let c of Object.values(a.statics))for(let{to:f}of c)r(f);for(let[,{to:c}]of a.dynamics)r(c);for(let{to:c}of a.shortcuts)r(c);let n=new Set(a.shortcuts.map(({to:c})=>c));for(;a.shortcuts.length>0;){let{to:c}=a.shortcuts.shift(),f=t.nodes[c];for(let[p,h]of Object.entries(f.statics)){let E=Object.prototype.hasOwnProperty.call(a.statics,p)?a.statics[p]:a.statics[p]=[];for(let C of h)E.some(({to:S})=>C.to===S)||E.push(C)}for(let[p,h]of f.dynamics)a.dynamics.some(([E,{to:C}])=>p===E&&h.to===C)||a.dynamics.push([p,h]);for(let p of f.shortcuts)n.has(p.to)||(a.shortcuts.push(p),n.add(p.to))}};r(En.InitialNode)}function bZe(t,{prefix:e=\"\"}={}){if(uU){sl(`${e}Nodes are:`);for(let r=0;r<t.nodes.length;++r)sl(`${e}  ${r}: ${JSON.stringify(t.nodes[r])}`)}}function PZe(t,e,r=!1){sl(`Running a vm on ${JSON.stringify(e)}`);let s=[{node:En.InitialNode,state:{candidateUsage:null,requiredOptions:[],errorMessage:null,ignoreOptions:!1,options:[],path:[],positionals:[],remainder:null,selectedIndex:null,partial:!1,tokens:[]}}];bZe(t,{prefix:\"  \"});let a=[ei.StartOfInput,...e];for(let n=0;n<a.length;++n){let c=a[n],f=c===ei.EndOfInput||c===ei.EndOfPartialInput,p=n-1;sl(`  Processing ${JSON.stringify(c)}`);let h=[];for(let{node:E,state:C}of s){sl(`    Current node is ${E}`);let S=t.nodes[E];if(E===En.ErrorNode){h.push({node:E,state:C});continue}console.assert(S.shortcuts.length===0,\"Shortcuts should have been eliminated by now\");let P=Object.prototype.hasOwnProperty.call(S.statics,c);if(!r||n<a.length-1||P)if(P){let I=S.statics[c];for(let{to:R,reducer:N}of I)h.push({node:R,state:typeof N<\"u\"?Gx(wU,N,C,c,p):C}),sl(`      Static transition to ${R} found`)}else sl(\"      No static transition found\");else{let I=!1;for(let R of Object.keys(S.statics))if(R.startsWith(c)){if(c===R)for(let{to:N,reducer:U}of S.statics[R])h.push({node:N,state:typeof U<\"u\"?Gx(wU,U,C,c,p):C}),sl(`      Static transition to ${N} found`);else for(let{to:N}of S.statics[R])h.push({node:N,state:{...C,remainder:R.slice(c.length)}}),sl(`      Static transition to ${N} found (partial match)`);I=!0}I||sl(\"      No partial static transition found\")}if(!f)for(let[I,{to:R,reducer:N}]of S.dynamics)Gx(FZe,I,C,c,p)&&(h.push({node:R,state:typeof N<\"u\"?Gx(wU,N,C,c,p):C}),sl(`      Dynamic transition to ${R} found (via ${I})`))}if(h.length===0&&f&&e.length===1)return[{node:En.InitialNode,state:lne}];if(h.length===0)throw new yE(e,s.filter(({node:E})=>E!==En.ErrorNode).map(({state:E})=>({usage:E.candidateUsage,reason:null})));if(h.every(({node:E})=>E===En.ErrorNode))throw new yE(e,h.map(({state:E})=>({usage:E.candidateUsage,reason:E.errorMessage})));s=kZe(h)}if(s.length>0){sl(\"  Results:\");for(let n of s)sl(`    - ${n.node} -> ${JSON.stringify(n.state)}`)}else sl(\"  No results\");return s}function xZe(t,e,{endToken:r=ei.EndOfInput}={}){let s=PZe(t,[...e,r]);return QZe(e,s.map(({state:a})=>a))}function kZe(t){let e=0;for(let{state:r}of t)r.path.length>e&&(e=r.path.length);return t.filter(({state:r})=>r.path.length===e)}function QZe(t,e){let r=e.filter(S=>S.selectedIndex!==null),s=r.filter(S=>!S.partial);if(s.length>0&&(r=s),r.length===0)throw new Error;let a=r.filter(S=>S.selectedIndex===Fd||S.requiredOptions.every(P=>P.some(I=>S.options.find(R=>R.name===I))));if(a.length===0)throw new yE(t,r.map(S=>({usage:S.candidateUsage,reason:null})));let n=0;for(let S of a)S.path.length>n&&(n=S.path.length);let c=a.filter(S=>S.path.length===n),f=S=>S.positionals.filter(({extra:P})=>!P).length+S.options.length,p=c.map(S=>({state:S,positionalCount:f(S)})),h=0;for(let{positionalCount:S}of p)S>h&&(h=S);let E=p.filter(({positionalCount:S})=>S===h).map(({state:S})=>S),C=TZe(E);if(C.length>1)throw new Lx(t,C.map(S=>S.candidateUsage));return C[0]}function TZe(t){let e=[],r=[];for(let s of t)s.selectedIndex===Fd?r.push(s):e.push(s);return r.length>0&&e.push({...lne,path:une(...r.map(s=>s.path)),options:r.reduce((s,a)=>s.concat(a.options),[])}),e}function une(t,e,...r){return e===void 0?Array.from(t):une(t.filter((s,a)=>s===e[a]),...r)}function Hl(){return{dynamics:[],shortcuts:[],statics:{}}}function fne(t){return t===En.SuccessNode||t===En.ErrorNode}function CU(t,e=0){return{to:fne(t.to)?t.to:t.to>=En.CustomNode?t.to+e-En.CustomNode+1:t.to+e,reducer:t.reducer}}function RZe(t,e=0){let r=Hl();for(let[s,a]of t.dynamics)r.dynamics.push([s,CU(a,e)]);for(let s of t.shortcuts)r.shortcuts.push(CU(s,e));for(let[s,a]of Object.entries(t.statics))r.statics[s]=a.map(n=>CU(n,e));return r}function qs(t,e,r,s,a){t.nodes[e].dynamics.push([r,{to:s,reducer:a}])}function CE(t,e,r,s){t.nodes[e].shortcuts.push({to:r,reducer:s})}function Ca(t,e,r,s,a){(Object.prototype.hasOwnProperty.call(t.nodes[e].statics,r)?t.nodes[e].statics[r]:t.nodes[e].statics[r]=[]).push({to:s,reducer:a})}function Gx(t,e,r,s,a){if(Array.isArray(e)){let[n,...c]=e;return t[n](r,s,a,...c)}else return t[e](r,s,a)}var lne,FZe,wU,jl,BU,Wx,Yx=Ct(()=>{Ox();Mx();lne={candidateUsage:null,requiredOptions:[],errorMessage:null,ignoreOptions:!1,path:[],positionals:[],options:[],remainder:null,selectedIndex:Fd,partial:!1,tokens:[]};FZe={always:()=>!0,isOptionLike:(t,e)=>!t.ignoreOptions&&e!==\"-\"&&e.startsWith(\"-\"),isNotOptionLike:(t,e)=>t.ignoreOptions||e===\"-\"||!e.startsWith(\"-\"),isOption:(t,e,r,s)=>!t.ignoreOptions&&e===s,isBatchOption:(t,e,r,s)=>!t.ignoreOptions&&ene.test(e)&&[...e.slice(1)].every(a=>s.has(`-${a}`)),isBoundOption:(t,e,r,s,a)=>{let n=e.match(cU);return!t.ignoreOptions&&!!n&&Nx.test(n[1])&&s.has(n[1])&&a.filter(c=>c.nameSet.includes(n[1])).every(c=>c.allowBinding)},isNegatedOption:(t,e,r,s)=>!t.ignoreOptions&&e===`--no-${s.slice(2)}`,isHelp:(t,e)=>!t.ignoreOptions&&lU.test(e),isUnsupportedOption:(t,e,r,s)=>!t.ignoreOptions&&e.startsWith(\"-\")&&Nx.test(e)&&!s.has(e),isInvalidOption:(t,e)=>!t.ignoreOptions&&e.startsWith(\"-\")&&!Nx.test(e)},wU={setCandidateState:(t,e,r,s)=>({...t,...s}),setSelectedIndex:(t,e,r,s)=>({...t,selectedIndex:s}),setPartialIndex:(t,e,r,s)=>({...t,selectedIndex:s,partial:!0}),pushBatch:(t,e,r,s)=>{let a=t.options.slice(),n=t.tokens.slice();for(let c=1;c<e.length;++c){let f=s.get(`-${e[c]}`),p=c===1?[0,2]:[c,c+1];a.push({name:f,value:!0}),n.push({segmentIndex:r,type:\"option\",option:f,slice:p})}return{...t,options:a,tokens:n}},pushBound:(t,e,r)=>{let[,s,a]=e.match(cU),n=t.options.concat({name:s,value:a}),c=t.tokens.concat([{segmentIndex:r,type:\"option\",slice:[0,s.length],option:s},{segmentIndex:r,type:\"assign\",slice:[s.length,s.length+1]},{segmentIndex:r,type:\"value\",slice:[s.length+1,s.length+a.length+1]}]);return{...t,options:n,tokens:c}},pushPath:(t,e,r)=>{let s=t.path.concat(e),a=t.tokens.concat({segmentIndex:r,type:\"path\"});return{...t,path:s,tokens:a}},pushPositional:(t,e,r)=>{let s=t.positionals.concat({value:e,extra:!1}),a=t.tokens.concat({segmentIndex:r,type:\"positional\"});return{...t,positionals:s,tokens:a}},pushExtra:(t,e,r)=>{let s=t.positionals.concat({value:e,extra:!0}),a=t.tokens.concat({segmentIndex:r,type:\"positional\"});return{...t,positionals:s,tokens:a}},pushExtraNoLimits:(t,e,r)=>{let s=t.positionals.concat({value:e,extra:jl}),a=t.tokens.concat({segmentIndex:r,type:\"positional\"});return{...t,positionals:s,tokens:a}},pushTrue:(t,e,r,s)=>{let a=t.options.concat({name:s,value:!0}),n=t.tokens.concat({segmentIndex:r,type:\"option\",option:s});return{...t,options:a,tokens:n}},pushFalse:(t,e,r,s)=>{let a=t.options.concat({name:s,value:!1}),n=t.tokens.concat({segmentIndex:r,type:\"option\",option:s});return{...t,options:a,tokens:n}},pushUndefined:(t,e,r,s)=>{let a=t.options.concat({name:e,value:void 0}),n=t.tokens.concat({segmentIndex:r,type:\"option\",option:e});return{...t,options:a,tokens:n}},pushStringValue:(t,e,r)=>{var s;let a=t.options[t.options.length-1],n=t.options.slice(),c=t.tokens.concat({segmentIndex:r,type:\"value\"});return a.value=((s=a.value)!==null&&s!==void 0?s:[]).concat([e]),{...t,options:n,tokens:c}},setStringValue:(t,e,r)=>{let s=t.options[t.options.length-1],a=t.options.slice(),n=t.tokens.concat({segmentIndex:r,type:\"value\"});return s.value=e,{...t,options:a,tokens:n}},inhibateOptions:t=>({...t,ignoreOptions:!0}),useHelp:(t,e,r,s)=>{let[,,a]=e.match(lU);return typeof a<\"u\"?{...t,options:[{name:\"-c\",value:String(s)},{name:\"-i\",value:a}]}:{...t,options:[{name:\"-c\",value:String(s)}]}},setError:(t,e,r,s)=>e===ei.EndOfInput||e===ei.EndOfPartialInput?{...t,errorMessage:`${s}.`}:{...t,errorMessage:`${s} (\"${e}\").`},setOptionArityError:(t,e)=>{let r=t.options[t.options.length-1];return{...t,errorMessage:`Not enough arguments to option ${r.name}.`}}},jl=Symbol(),BU=class{constructor(e,r){this.allOptionNames=new Map,this.arity={leading:[],trailing:[],extra:[],proxy:!1},this.options=[],this.paths=[],this.cliIndex=e,this.cliOpts=r}addPath(e){this.paths.push(e)}setArity({leading:e=this.arity.leading,trailing:r=this.arity.trailing,extra:s=this.arity.extra,proxy:a=this.arity.proxy}){Object.assign(this.arity,{leading:e,trailing:r,extra:s,proxy:a})}addPositional({name:e=\"arg\",required:r=!0}={}){if(!r&&this.arity.extra===jl)throw new Error(\"Optional parameters cannot be declared when using .rest() or .proxy()\");if(!r&&this.arity.trailing.length>0)throw new Error(\"Optional parameters cannot be declared after the required trailing positional arguments\");!r&&this.arity.extra!==jl?this.arity.extra.push(e):this.arity.extra!==jl&&this.arity.extra.length===0?this.arity.leading.push(e):this.arity.trailing.push(e)}addRest({name:e=\"arg\",required:r=0}={}){if(this.arity.extra===jl)throw new Error(\"Infinite lists cannot be declared multiple times in the same command\");if(this.arity.trailing.length>0)throw new Error(\"Infinite lists cannot be declared after the required trailing positional arguments\");for(let s=0;s<r;++s)this.addPositional({name:e});this.arity.extra=jl}addProxy({required:e=0}={}){this.addRest({required:e}),this.arity.proxy=!0}addOption({names:e,description:r,arity:s=0,hidden:a=!1,required:n=!1,allowBinding:c=!0}){if(!c&&s>1)throw new Error(\"The arity cannot be higher than 1 when the option only supports the --arg=value syntax\");if(!Number.isInteger(s))throw new Error(`The arity must be an integer, got ${s}`);if(s<0)throw new Error(`The arity must be positive, got ${s}`);let f=e.reduce((p,h)=>h.length>p.length?h:p,\"\");for(let p of e)this.allOptionNames.set(p,f);this.options.push({preferredName:f,nameSet:e,description:r,arity:s,hidden:a,required:n,allowBinding:c})}setContext(e){this.context=e}usage({detailed:e=!0,inlineOptions:r=!0}={}){let s=[this.cliOpts.binaryName],a=[];if(this.paths.length>0&&s.push(...this.paths[0]),e){for(let{preferredName:c,nameSet:f,arity:p,hidden:h,description:E,required:C}of this.options){if(h)continue;let S=[];for(let I=0;I<p;++I)S.push(` #${I}`);let P=`${f.join(\",\")}${S.join(\"\")}`;!r&&E?a.push({preferredName:c,nameSet:f,definition:P,description:E,required:C}):s.push(C?`<${P}>`:`[${P}]`)}s.push(...this.arity.leading.map(c=>`<${c}>`)),this.arity.extra===jl?s.push(\"...\"):s.push(...this.arity.extra.map(c=>`[${c}]`)),s.push(...this.arity.trailing.map(c=>`<${c}>`))}return{usage:s.join(\" \"),options:a}}compile(){if(typeof this.context>\"u\")throw new Error(\"Assertion failed: No context attached\");let e=cne(),r=En.InitialNode,s=this.usage().usage,a=this.options.filter(f=>f.required).map(f=>f.nameSet);r=Mu(e,Hl()),Ca(e,En.InitialNode,ei.StartOfInput,r,[\"setCandidateState\",{candidateUsage:s,requiredOptions:a}]);let n=this.arity.proxy?\"always\":\"isNotOptionLike\",c=this.paths.length>0?this.paths:[[]];for(let f of c){let p=r;if(f.length>0){let S=Mu(e,Hl());CE(e,p,S),this.registerOptions(e,S),p=S}for(let S=0;S<f.length;++S){let P=Mu(e,Hl());Ca(e,p,f[S],P,\"pushPath\"),p=P}if(this.arity.leading.length>0||!this.arity.proxy){let S=Mu(e,Hl());qs(e,p,\"isHelp\",S,[\"useHelp\",this.cliIndex]),qs(e,S,\"always\",S,\"pushExtra\"),Ca(e,S,ei.EndOfInput,En.SuccessNode,[\"setSelectedIndex\",Fd]),this.registerOptions(e,p)}this.arity.leading.length>0&&(Ca(e,p,ei.EndOfInput,En.ErrorNode,[\"setError\",\"Not enough positional arguments\"]),Ca(e,p,ei.EndOfPartialInput,En.SuccessNode,[\"setPartialIndex\",this.cliIndex]));let h=p;for(let S=0;S<this.arity.leading.length;++S){let P=Mu(e,Hl());(!this.arity.proxy||S+1!==this.arity.leading.length)&&this.registerOptions(e,P),(this.arity.trailing.length>0||S+1!==this.arity.leading.length)&&(Ca(e,P,ei.EndOfInput,En.ErrorNode,[\"setError\",\"Not enough positional arguments\"]),Ca(e,P,ei.EndOfPartialInput,En.SuccessNode,[\"setPartialIndex\",this.cliIndex])),qs(e,h,\"isNotOptionLike\",P,\"pushPositional\"),h=P}let E=h;if(this.arity.extra===jl||this.arity.extra.length>0){let S=Mu(e,Hl());if(CE(e,h,S),this.arity.extra===jl){let P=Mu(e,Hl());this.arity.proxy||this.registerOptions(e,P),qs(e,h,n,P,\"pushExtraNoLimits\"),qs(e,P,n,P,\"pushExtraNoLimits\"),CE(e,P,S)}else for(let P=0;P<this.arity.extra.length;++P){let I=Mu(e,Hl());(!this.arity.proxy||P>0)&&this.registerOptions(e,I),qs(e,E,n,I,\"pushExtra\"),CE(e,I,S),E=I}E=S}this.arity.trailing.length>0&&(Ca(e,E,ei.EndOfInput,En.ErrorNode,[\"setError\",\"Not enough positional arguments\"]),Ca(e,E,ei.EndOfPartialInput,En.SuccessNode,[\"setPartialIndex\",this.cliIndex]));let C=E;for(let S=0;S<this.arity.trailing.length;++S){let P=Mu(e,Hl());this.arity.proxy||this.registerOptions(e,P),S+1<this.arity.trailing.length&&(Ca(e,P,ei.EndOfInput,En.ErrorNode,[\"setError\",\"Not enough positional arguments\"]),Ca(e,P,ei.EndOfPartialInput,En.SuccessNode,[\"setPartialIndex\",this.cliIndex])),qs(e,C,\"isNotOptionLike\",P,\"pushPositional\"),C=P}qs(e,C,n,En.ErrorNode,[\"setError\",\"Extraneous positional argument\"]),Ca(e,C,ei.EndOfInput,En.SuccessNode,[\"setSelectedIndex\",this.cliIndex]),Ca(e,C,ei.EndOfPartialInput,En.SuccessNode,[\"setSelectedIndex\",this.cliIndex])}return{machine:e,context:this.context}}registerOptions(e,r){qs(e,r,[\"isOption\",\"--\"],r,\"inhibateOptions\"),qs(e,r,[\"isBatchOption\",this.allOptionNames],r,[\"pushBatch\",this.allOptionNames]),qs(e,r,[\"isBoundOption\",this.allOptionNames,this.options],r,\"pushBound\"),qs(e,r,[\"isUnsupportedOption\",this.allOptionNames],En.ErrorNode,[\"setError\",\"Unsupported option name\"]),qs(e,r,[\"isInvalidOption\"],En.ErrorNode,[\"setError\",\"Invalid option name\"]);for(let s of this.options)if(s.arity===0)for(let a of s.nameSet)qs(e,r,[\"isOption\",a],r,[\"pushTrue\",s.preferredName]),a.startsWith(\"--\")&&!a.startsWith(\"--no-\")&&qs(e,r,[\"isNegatedOption\",a],r,[\"pushFalse\",s.preferredName]);else{let a=Mu(e,Hl());for(let n of s.nameSet)qs(e,r,[\"isOption\",n],a,[\"pushUndefined\",s.preferredName]);for(let n=0;n<s.arity;++n){let c=Mu(e,Hl());Ca(e,a,ei.EndOfInput,En.ErrorNode,\"setOptionArityError\"),Ca(e,a,ei.EndOfPartialInput,En.ErrorNode,\"setOptionArityError\"),qs(e,a,\"isOptionLike\",En.ErrorNode,\"setOptionArityError\");let f=s.arity===1?\"setStringValue\":\"pushStringValue\";qs(e,a,\"isNotOptionLike\",c,f),a=c}CE(e,a,r)}}},Wx=class t{constructor({binaryName:e=\"...\"}={}){this.builders=[],this.opts={binaryName:e}}static build(e,r={}){return new t(r).commands(e).compile()}getBuilderByIndex(e){if(!(e>=0&&e<this.builders.length))throw new Error(`Assertion failed: Out-of-bound command index (${e})`);return this.builders[e]}commands(e){for(let r of e)r(this.command());return this}command(){let e=new BU(this.builders.length,this.opts);return this.builders.push(e),e}compile(){let e=[],r=[];for(let a of this.builders){let{machine:n,context:c}=a.compile();e.push(n),r.push(c)}let s=SZe(e);return DZe(s),{machine:s,contexts:r,process:(a,{partial:n}={})=>{let c=n?ei.EndOfPartialInput:ei.EndOfInput;return xZe(s,a,{endToken:c})}}}}});function pne(){return Vx.default&&\"getColorDepth\"in Vx.default.WriteStream.prototype?Vx.default.WriteStream.prototype.getColorDepth():process.env.FORCE_COLOR===\"0\"?1:process.env.FORCE_COLOR===\"1\"||typeof process.stdout<\"u\"&&process.stdout.isTTY?8:1}function hne(t){let e=Ane;if(typeof e>\"u\"){if(t.stdout===process.stdout&&t.stderr===process.stderr)return null;let{AsyncLocalStorage:r}=Ie(\"async_hooks\");e=Ane=new r;let s=process.stdout._write;process.stdout._write=function(n,c,f){let p=e.getStore();return typeof p>\"u\"?s.call(this,n,c,f):p.stdout.write(n,c,f)};let a=process.stderr._write;process.stderr._write=function(n,c,f){let p=e.getStore();return typeof p>\"u\"?a.call(this,n,c,f):p.stderr.write(n,c,f)}}return r=>e.run(t,r)}var Vx,Ane,gne=Ct(()=>{Vx=et(Ie(\"tty\"),1)});var Kx,dne=Ct(()=>{c0();Kx=class t extends ot{constructor(e){super(),this.contexts=e,this.commands=[]}static from(e,r){let s=new t(r);s.path=e.path;for(let a of e.options)switch(a.name){case\"-c\":s.commands.push(Number(a.value));break;case\"-i\":s.index=Number(a.value);break}return s}async execute(){let e=this.commands;if(typeof this.index<\"u\"&&this.index>=0&&this.index<e.length&&(e=[e[this.index]]),e.length===0)this.context.stdout.write(this.cli.usage());else if(e.length===1)this.context.stdout.write(this.cli.usage(this.contexts[e[0]].commandClass,{detailed:!0}));else if(e.length>1){this.context.stdout.write(`Multiple commands match your selection:\n`),this.context.stdout.write(`\n`);let r=0;for(let s of this.commands)this.context.stdout.write(this.cli.usage(this.contexts[s].commandClass,{prefix:`${r++}. `.padStart(5)}));this.context.stdout.write(`\n`),this.context.stdout.write(`Run again with -h=<index> to see the longer details of any of those commands.\n`)}}}});async function Ene(...t){let{resolvedOptions:e,resolvedCommandClasses:r,resolvedArgv:s,resolvedContext:a}=Cne(t);return wa.from(r,e).runExit(s,a)}async function Ine(...t){let{resolvedOptions:e,resolvedCommandClasses:r,resolvedArgv:s,resolvedContext:a}=Cne(t);return wa.from(r,e).run(s,a)}function Cne(t){let e,r,s,a;switch(typeof process<\"u\"&&typeof process.argv<\"u\"&&(s=process.argv.slice(2)),t.length){case 1:r=t[0];break;case 2:t[0]&&t[0].prototype instanceof ot||Array.isArray(t[0])?(r=t[0],Array.isArray(t[1])?s=t[1]:a=t[1]):(e=t[0],r=t[1]);break;case 3:Array.isArray(t[2])?(e=t[0],r=t[1],s=t[2]):t[0]&&t[0].prototype instanceof ot||Array.isArray(t[0])?(r=t[0],s=t[1],a=t[2]):(e=t[0],r=t[1],a=t[2]);break;default:e=t[0],r=t[1],s=t[2],a=t[3];break}if(typeof s>\"u\")throw new Error(\"The argv parameter must be provided when running Clipanion outside of a Node context\");return{resolvedOptions:e,resolvedCommandClasses:r,resolvedArgv:s,resolvedContext:a}}function yne(t){return t()}var mne,wa,wne=Ct(()=>{Ox();Yx();pU();gne();c0();dne();mne=Symbol(\"clipanion/errorCommand\");wa=class t{constructor({binaryLabel:e,binaryName:r=\"...\",binaryVersion:s,enableCapture:a=!1,enableColors:n}={}){this.registrations=new Map,this.builder=new Wx({binaryName:r}),this.binaryLabel=e,this.binaryName=r,this.binaryVersion=s,this.enableCapture=a,this.enableColors=n}static from(e,r={}){let s=new t(r),a=Array.isArray(e)?e:[e];for(let n of a)s.register(n);return s}register(e){var r;let s=new Map,a=new e;for(let p in a){let h=a[p];typeof h==\"object\"&&h!==null&&h[ot.isOption]&&s.set(p,h)}let n=this.builder.command(),c=n.cliIndex,f=(r=e.paths)!==null&&r!==void 0?r:a.paths;if(typeof f<\"u\")for(let p of f)n.addPath(p);this.registrations.set(e,{specs:s,builder:n,index:c});for(let[p,{definition:h}]of s.entries())h(n,p);n.setContext({commandClass:e})}process(e,r){let{input:s,context:a,partial:n}=typeof e==\"object\"&&Array.isArray(e)?{input:e,context:r}:e,{contexts:c,process:f}=this.builder.compile(),p=f(s,{partial:n}),h={...t.defaultContext,...a};switch(p.selectedIndex){case Fd:{let E=Kx.from(p,c);return E.context=h,E.tokens=p.tokens,E}default:{let{commandClass:E}=c[p.selectedIndex],C=this.registrations.get(E);if(typeof C>\"u\")throw new Error(\"Assertion failed: Expected the command class to have been registered.\");let S=new E;S.context=h,S.tokens=p.tokens,S.path=p.path;try{for(let[P,{transformer:I}]of C.specs.entries())S[P]=I(C.builder,P,p,h);return S}catch(P){throw P[mne]=S,P}}break}}async run(e,r){var s,a;let n,c={...t.defaultContext,...r},f=(s=this.enableColors)!==null&&s!==void 0?s:c.colorDepth>1;if(!Array.isArray(e))n=e;else try{n=this.process(e,c)}catch(E){return c.stdout.write(this.error(E,{colored:f})),1}if(n.help)return c.stdout.write(this.usage(n,{colored:f,detailed:!0})),0;n.context=c,n.cli={binaryLabel:this.binaryLabel,binaryName:this.binaryName,binaryVersion:this.binaryVersion,enableCapture:this.enableCapture,enableColors:this.enableColors,definitions:()=>this.definitions(),definition:E=>this.definition(E),error:(E,C)=>this.error(E,C),format:E=>this.format(E),process:(E,C)=>this.process(E,{...c,...C}),run:(E,C)=>this.run(E,{...c,...C}),usage:(E,C)=>this.usage(E,C)};let p=this.enableCapture&&(a=hne(c))!==null&&a!==void 0?a:yne,h;try{h=await p(()=>n.validateAndExecute().catch(E=>n.catch(E).then(()=>0)))}catch(E){return c.stdout.write(this.error(E,{colored:f,command:n})),1}return h}async runExit(e,r){process.exitCode=await this.run(e,r)}definition(e,{colored:r=!1}={}){if(!e.usage)return null;let{usage:s}=this.getUsageByRegistration(e,{detailed:!1}),{usage:a,options:n}=this.getUsageByRegistration(e,{detailed:!0,inlineOptions:!1}),c=typeof e.usage.category<\"u\"?qo(e.usage.category,{format:this.format(r),paragraphs:!1}):void 0,f=typeof e.usage.description<\"u\"?qo(e.usage.description,{format:this.format(r),paragraphs:!1}):void 0,p=typeof e.usage.details<\"u\"?qo(e.usage.details,{format:this.format(r),paragraphs:!0}):void 0,h=typeof e.usage.examples<\"u\"?e.usage.examples.map(([E,C])=>[qo(E,{format:this.format(r),paragraphs:!1}),C.replace(/\\$0/g,this.binaryName)]):void 0;return{path:s,usage:a,category:c,description:f,details:p,examples:h,options:n}}definitions({colored:e=!1}={}){let r=[];for(let s of this.registrations.keys()){let a=this.definition(s,{colored:e});a&&r.push(a)}return r}usage(e=null,{colored:r,detailed:s=!1,prefix:a=\"$ \"}={}){var n;if(e===null){for(let p of this.registrations.keys()){let h=p.paths,E=typeof p.usage<\"u\";if(!h||h.length===0||h.length===1&&h[0].length===0||((n=h?.some(P=>P.length===0))!==null&&n!==void 0?n:!1))if(e){e=null;break}else e=p;else if(E){e=null;continue}}e&&(s=!0)}let c=e!==null&&e instanceof ot?e.constructor:e,f=\"\";if(c)if(s){let{description:p=\"\",details:h=\"\",examples:E=[]}=c.usage||{};p!==\"\"&&(f+=qo(p,{format:this.format(r),paragraphs:!1}).replace(/^./,P=>P.toUpperCase()),f+=`\n`),(h!==\"\"||E.length>0)&&(f+=`${this.format(r).header(\"Usage\")}\n`,f+=`\n`);let{usage:C,options:S}=this.getUsageByRegistration(c,{inlineOptions:!1});if(f+=`${this.format(r).bold(a)}${C}\n`,S.length>0){f+=`\n`,f+=`${this.format(r).header(\"Options\")}\n`;let P=S.reduce((I,R)=>Math.max(I,R.definition.length),0);f+=`\n`;for(let{definition:I,description:R}of S)f+=`  ${this.format(r).bold(I.padEnd(P))}    ${qo(R,{format:this.format(r),paragraphs:!1})}`}if(h!==\"\"&&(f+=`\n`,f+=`${this.format(r).header(\"Details\")}\n`,f+=`\n`,f+=qo(h,{format:this.format(r),paragraphs:!0})),E.length>0){f+=`\n`,f+=`${this.format(r).header(\"Examples\")}\n`;for(let[P,I]of E)f+=`\n`,f+=qo(P,{format:this.format(r),paragraphs:!1}),f+=`${I.replace(/^/m,`  ${this.format(r).bold(a)}`).replace(/\\$0/g,this.binaryName)}\n`}}else{let{usage:p}=this.getUsageByRegistration(c);f+=`${this.format(r).bold(a)}${p}\n`}else{let p=new Map;for(let[S,{index:P}]of this.registrations.entries()){if(typeof S.usage>\"u\")continue;let I=typeof S.usage.category<\"u\"?qo(S.usage.category,{format:this.format(r),paragraphs:!1}):null,R=p.get(I);typeof R>\"u\"&&p.set(I,R=[]);let{usage:N}=this.getUsageByIndex(P);R.push({commandClass:S,usage:N})}let h=Array.from(p.keys()).sort((S,P)=>S===null?-1:P===null?1:S.localeCompare(P,\"en\",{usage:\"sort\",caseFirst:\"upper\"})),E=typeof this.binaryLabel<\"u\",C=typeof this.binaryVersion<\"u\";E||C?(E&&C?f+=`${this.format(r).header(`${this.binaryLabel} - ${this.binaryVersion}`)}\n\n`:E?f+=`${this.format(r).header(`${this.binaryLabel}`)}\n`:f+=`${this.format(r).header(`${this.binaryVersion}`)}\n`,f+=`  ${this.format(r).bold(a)}${this.binaryName} <command>\n`):f+=`${this.format(r).bold(a)}${this.binaryName} <command>\n`;for(let S of h){let P=p.get(S).slice().sort((R,N)=>R.usage.localeCompare(N.usage,\"en\",{usage:\"sort\",caseFirst:\"upper\"})),I=S!==null?S.trim():\"General commands\";f+=`\n`,f+=`${this.format(r).header(`${I}`)}\n`;for(let{commandClass:R,usage:N}of P){let U=R.usage.description||\"undocumented\";f+=`\n`,f+=`  ${this.format(r).bold(N)}\n`,f+=`    ${qo(U,{format:this.format(r),paragraphs:!1})}`}}f+=`\n`,f+=qo(\"You can also print more details about any of these commands by calling them with the `-h,--help` flag right after the command name.\",{format:this.format(r),paragraphs:!0})}return f}error(e,r){var s,{colored:a,command:n=(s=e[mne])!==null&&s!==void 0?s:null}=r===void 0?{}:r;(!e||typeof e!=\"object\"||!(\"stack\"in e))&&(e=new Error(`Execution failed with a non-error rejection (rejected value: ${JSON.stringify(e)})`));let c=\"\",f=e.name.replace(/([a-z])([A-Z])/g,\"$1 $2\");f===\"Error\"&&(f=\"Internal Error\"),c+=`${this.format(a).error(f)}: ${e.message}\n`;let p=e.clipanion;return typeof p<\"u\"?p.type===\"usage\"&&(c+=`\n`,c+=this.usage(n)):e.stack&&(c+=`${e.stack.replace(/^.*\\n/,\"\")}\n`),c}format(e){var r;return((r=e??this.enableColors)!==null&&r!==void 0?r:t.defaultContext.colorDepth>1)?tne:rne}getUsageByRegistration(e,r){let s=this.registrations.get(e);if(typeof s>\"u\")throw new Error(\"Assertion failed: Unregistered command\");return this.getUsageByIndex(s.index,r)}getUsageByIndex(e,r){return this.builder.getBuilderByIndex(e).usage(r)}};wa.defaultContext={env:process.env,stdin:process.stdin,stdout:process.stdout,stderr:process.stderr,colorDepth:pne()}});var rB,Bne=Ct(()=>{c0();rB=class extends ot{async execute(){this.context.stdout.write(`${JSON.stringify(this.cli.definitions(),null,2)}\n`)}};rB.paths=[[\"--clipanion=definitions\"]]});var nB,vne=Ct(()=>{c0();nB=class extends ot{async execute(){this.context.stdout.write(this.cli.usage())}};nB.paths=[[\"-h\"],[\"--help\"]]});function Jx(t={}){return Ea({definition(e,r){var s;e.addProxy({name:(s=t.name)!==null&&s!==void 0?s:r,required:t.required})},transformer(e,r,s){return s.positionals.map(({value:a})=>a)}})}var vU=Ct(()=>{Bp()});var iB,Sne=Ct(()=>{c0();vU();iB=class extends ot{constructor(){super(...arguments),this.args=Jx()}async execute(){this.context.stdout.write(`${JSON.stringify(this.cli.process(this.args).tokens,null,2)}\n`)}};iB.paths=[[\"--clipanion=tokens\"]]});var sB,Dne=Ct(()=>{c0();sB=class extends ot{async execute(){var e;this.context.stdout.write(`${(e=this.cli.binaryVersion)!==null&&e!==void 0?e:\"<unknown>\"}\n`)}};sB.paths=[[\"-v\"],[\"--version\"]]});var SU={};Vt(SU,{DefinitionsCommand:()=>rB,HelpCommand:()=>nB,TokensCommand:()=>iB,VersionCommand:()=>sB});var bne=Ct(()=>{Bne();vne();Sne();Dne()});function Pne(t,e,r){let[s,a]=Gf(e,r??{}),{arity:n=1}=a,c=t.split(\",\"),f=new Set(c);return Ea({definition(p){p.addOption({names:c,arity:n,hidden:a?.hidden,description:a?.description,required:a.required})},transformer(p,h,E){let C,S=typeof s<\"u\"?[...s]:void 0;for(let{name:P,value:I}of E.options)f.has(P)&&(C=P,S=S??[],S.push(I));return typeof S<\"u\"?Nd(C??h,S,a.validator):S}})}var xne=Ct(()=>{Bp()});function kne(t,e,r){let[s,a]=Gf(e,r??{}),n=t.split(\",\"),c=new Set(n);return Ea({definition(f){f.addOption({names:n,allowBinding:!1,arity:0,hidden:a.hidden,description:a.description,required:a.required})},transformer(f,p,h){let E=s;for(let{name:C,value:S}of h.options)c.has(C)&&(E=S);return E}})}var Qne=Ct(()=>{Bp()});function Tne(t,e,r){let[s,a]=Gf(e,r??{}),n=t.split(\",\"),c=new Set(n);return Ea({definition(f){f.addOption({names:n,allowBinding:!1,arity:0,hidden:a.hidden,description:a.description,required:a.required})},transformer(f,p,h){let E=s;for(let{name:C,value:S}of h.options)c.has(C)&&(E??(E=0),S?E+=1:E=0);return E}})}var Rne=Ct(()=>{Bp()});function Fne(t={}){return Ea({definition(e,r){var s;e.addRest({name:(s=t.name)!==null&&s!==void 0?s:r,required:t.required})},transformer(e,r,s){let a=c=>{let f=s.positionals[c];return f.extra===jl||f.extra===!1&&c<e.arity.leading.length},n=0;for(;n<s.positionals.length&&a(n);)n+=1;return s.positionals.splice(0,n).map(({value:c})=>c)}})}var Nne=Ct(()=>{Yx();Bp()});function NZe(t,e,r){let[s,a]=Gf(e,r??{}),{arity:n=1}=a,c=t.split(\",\"),f=new Set(c);return Ea({definition(p){p.addOption({names:c,arity:a.tolerateBoolean?0:n,hidden:a.hidden,description:a.description,required:a.required})},transformer(p,h,E,C){let S,P=s;typeof a.env<\"u\"&&C.env[a.env]&&(S=a.env,P=C.env[a.env]);for(let{name:I,value:R}of E.options)f.has(I)&&(S=I,P=R);return typeof P==\"string\"?Nd(S??h,P,a.validator):P}})}function OZe(t={}){let{required:e=!0}=t;return Ea({definition(r,s){var a;r.addPositional({name:(a=t.name)!==null&&a!==void 0?a:s,required:t.required})},transformer(r,s,a){var n;for(let c=0;c<a.positionals.length;++c){if(a.positionals[c].extra===jl||e&&a.positionals[c].extra===!0||!e&&a.positionals[c].extra===!1)continue;let[f]=a.positionals.splice(c,1);return Nd((n=t.name)!==null&&n!==void 0?n:s,f.value,t.validator)}}})}function One(t,...e){return typeof t==\"string\"?NZe(t,...e):OZe(t)}var Lne=Ct(()=>{Yx();Bp()});var ge={};Vt(ge,{Array:()=>Pne,Boolean:()=>kne,Counter:()=>Tne,Proxy:()=>Jx,Rest:()=>Fne,String:()=>One,applyValidator:()=>Nd,cleanValidationError:()=>_x,formatError:()=>z2,isOptionSymbol:()=>J2,makeCommandOption:()=>Ea,rerouteArguments:()=>Gf});var Mne=Ct(()=>{Bp();vU();xne();Qne();Rne();Nne();Lne()});var oB={};Vt(oB,{Builtins:()=>SU,Cli:()=>wa,Command:()=>ot,Option:()=>ge,UsageError:()=>nt,formatMarkdownish:()=>qo,run:()=>Ine,runExit:()=>Ene});var Wt=Ct(()=>{Mx();pU();c0();wne();bne();Mne()});var _ne=L((q9t,LZe)=>{LZe.exports={name:\"dotenv\",version:\"16.3.1\",description:\"Loads environment variables from .env file\",main:\"lib/main.js\",types:\"lib/main.d.ts\",exports:{\".\":{types:\"./lib/main.d.ts\",require:\"./lib/main.js\",default:\"./lib/main.js\"},\"./config\":\"./config.js\",\"./config.js\":\"./config.js\",\"./lib/env-options\":\"./lib/env-options.js\",\"./lib/env-options.js\":\"./lib/env-options.js\",\"./lib/cli-options\":\"./lib/cli-options.js\",\"./lib/cli-options.js\":\"./lib/cli-options.js\",\"./package.json\":\"./package.json\"},scripts:{\"dts-check\":\"tsc --project tests/types/tsconfig.json\",lint:\"standard\",\"lint-readme\":\"standard-markdown\",pretest:\"npm run lint && npm run dts-check\",test:\"tap tests/*.js --100 -Rspec\",prerelease:\"npm test\",release:\"standard-version\"},repository:{type:\"git\",url:\"git://github.com/motdotla/dotenv.git\"},funding:\"https://github.com/motdotla/dotenv?sponsor=1\",keywords:[\"dotenv\",\"env\",\".env\",\"environment\",\"variables\",\"config\",\"settings\"],readmeFilename:\"README.md\",license:\"BSD-2-Clause\",devDependencies:{\"@definitelytyped/dtslint\":\"^0.0.133\",\"@types/node\":\"^18.11.3\",decache:\"^4.6.1\",sinon:\"^14.0.1\",standard:\"^17.0.0\",\"standard-markdown\":\"^7.1.0\",\"standard-version\":\"^9.5.0\",tap:\"^16.3.0\",tar:\"^6.1.11\",typescript:\"^4.8.4\"},engines:{node:\">=12\"},browser:{fs:!1}}});var qne=L((G9t,vp)=>{var Une=Ie(\"fs\"),bU=Ie(\"path\"),MZe=Ie(\"os\"),_Ze=Ie(\"crypto\"),UZe=_ne(),PU=UZe.version,HZe=/(?:^|^)\\s*(?:export\\s+)?([\\w.-]+)(?:\\s*=\\s*?|:\\s+?)(\\s*'(?:\\\\'|[^'])*'|\\s*\"(?:\\\\\"|[^\"])*\"|\\s*`(?:\\\\`|[^`])*`|[^#\\r\\n]+)?\\s*(?:#.*)?(?:$|$)/mg;function jZe(t){let e={},r=t.toString();r=r.replace(/\\r\\n?/mg,`\n`);let s;for(;(s=HZe.exec(r))!=null;){let a=s[1],n=s[2]||\"\";n=n.trim();let c=n[0];n=n.replace(/^(['\"`])([\\s\\S]*)\\1$/mg,\"$2\"),c==='\"'&&(n=n.replace(/\\\\n/g,`\n`),n=n.replace(/\\\\r/g,\"\\r\")),e[a]=n}return e}function qZe(t){let e=jne(t),r=Gs.configDotenv({path:e});if(!r.parsed)throw new Error(`MISSING_DATA: Cannot parse ${e} for an unknown reason`);let s=Hne(t).split(\",\"),a=s.length,n;for(let c=0;c<a;c++)try{let f=s[c].trim(),p=YZe(r,f);n=Gs.decrypt(p.ciphertext,p.key);break}catch(f){if(c+1>=a)throw f}return Gs.parse(n)}function GZe(t){console.log(`[dotenv@${PU}][INFO] ${t}`)}function WZe(t){console.log(`[dotenv@${PU}][WARN] ${t}`)}function DU(t){console.log(`[dotenv@${PU}][DEBUG] ${t}`)}function Hne(t){return t&&t.DOTENV_KEY&&t.DOTENV_KEY.length>0?t.DOTENV_KEY:process.env.DOTENV_KEY&&process.env.DOTENV_KEY.length>0?process.env.DOTENV_KEY:\"\"}function YZe(t,e){let r;try{r=new URL(e)}catch(f){throw f.code===\"ERR_INVALID_URL\"?new Error(\"INVALID_DOTENV_KEY: Wrong format. Must be in valid uri format like dotenv://:key_1234@dotenv.org/vault/.env.vault?environment=development\"):f}let s=r.password;if(!s)throw new Error(\"INVALID_DOTENV_KEY: Missing key part\");let a=r.searchParams.get(\"environment\");if(!a)throw new Error(\"INVALID_DOTENV_KEY: Missing environment part\");let n=`DOTENV_VAULT_${a.toUpperCase()}`,c=t.parsed[n];if(!c)throw new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment ${n} in your .env.vault file.`);return{ciphertext:c,key:s}}function jne(t){let e=bU.resolve(process.cwd(),\".env\");return t&&t.path&&t.path.length>0&&(e=t.path),e.endsWith(\".vault\")?e:`${e}.vault`}function VZe(t){return t[0]===\"~\"?bU.join(MZe.homedir(),t.slice(1)):t}function KZe(t){GZe(\"Loading env from encrypted .env.vault\");let e=Gs._parseVault(t),r=process.env;return t&&t.processEnv!=null&&(r=t.processEnv),Gs.populate(r,e,t),{parsed:e}}function JZe(t){let e=bU.resolve(process.cwd(),\".env\"),r=\"utf8\",s=!!(t&&t.debug);t&&(t.path!=null&&(e=VZe(t.path)),t.encoding!=null&&(r=t.encoding));try{let a=Gs.parse(Une.readFileSync(e,{encoding:r})),n=process.env;return t&&t.processEnv!=null&&(n=t.processEnv),Gs.populate(n,a,t),{parsed:a}}catch(a){return s&&DU(`Failed to load ${e} ${a.message}`),{error:a}}}function zZe(t){let e=jne(t);return Hne(t).length===0?Gs.configDotenv(t):Une.existsSync(e)?Gs._configVault(t):(WZe(`You set DOTENV_KEY but you are missing a .env.vault file at ${e}. Did you forget to build it?`),Gs.configDotenv(t))}function ZZe(t,e){let r=Buffer.from(e.slice(-64),\"hex\"),s=Buffer.from(t,\"base64\"),a=s.slice(0,12),n=s.slice(-16);s=s.slice(12,-16);try{let c=_Ze.createDecipheriv(\"aes-256-gcm\",r,a);return c.setAuthTag(n),`${c.update(s)}${c.final()}`}catch(c){let f=c instanceof RangeError,p=c.message===\"Invalid key length\",h=c.message===\"Unsupported state or unable to authenticate data\";if(f||p){let E=\"INVALID_DOTENV_KEY: It must be 64 characters long (or more)\";throw new Error(E)}else if(h){let E=\"DECRYPTION_FAILED: Please check your DOTENV_KEY\";throw new Error(E)}else throw console.error(\"Error: \",c.code),console.error(\"Error: \",c.message),c}}function XZe(t,e,r={}){let s=!!(r&&r.debug),a=!!(r&&r.override);if(typeof e!=\"object\")throw new Error(\"OBJECT_REQUIRED: Please check the processEnv argument being passed to populate\");for(let n of Object.keys(e))Object.prototype.hasOwnProperty.call(t,n)?(a===!0&&(t[n]=e[n]),s&&DU(a===!0?`\"${n}\" is already defined and WAS overwritten`:`\"${n}\" is already defined and was NOT overwritten`)):t[n]=e[n]}var Gs={configDotenv:JZe,_configVault:KZe,_parseVault:qZe,config:zZe,decrypt:ZZe,parse:jZe,populate:XZe};vp.exports.configDotenv=Gs.configDotenv;vp.exports._configVault=Gs._configVault;vp.exports._parseVault=Gs._parseVault;vp.exports.config=Gs.config;vp.exports.decrypt=Gs.decrypt;vp.exports.parse=Gs.parse;vp.exports.populate=Gs.populate;vp.exports=Gs});var Wne=L((W9t,Gne)=>{\"use strict\";Gne.exports=(t,...e)=>new Promise(r=>{r(t(...e))})});var Od=L((Y9t,xU)=>{\"use strict\";var $Ze=Wne(),Yne=t=>{if(t<1)throw new TypeError(\"Expected `concurrency` to be a number from 1 and up\");let e=[],r=0,s=()=>{r--,e.length>0&&e.shift()()},a=(f,p,...h)=>{r++;let E=$Ze(f,...h);p(E),E.then(s,s)},n=(f,p,...h)=>{r<t?a(f,p,...h):e.push(a.bind(null,f,p,...h))},c=(f,...p)=>new Promise(h=>n(f,h,...p));return Object.defineProperties(c,{activeCount:{get:()=>r},pendingCount:{get:()=>e.length}}),c};xU.exports=Yne;xU.exports.default=Yne});function Vf(t){return`YN${t.toString(10).padStart(4,\"0\")}`}function zx(t){let e=Number(t.slice(2));if(typeof Dr[e]>\"u\")throw new Error(`Unknown message name: \"${t}\"`);return e}var Dr,Zx=Ct(()=>{Dr=(Me=>(Me[Me.UNNAMED=0]=\"UNNAMED\",Me[Me.EXCEPTION=1]=\"EXCEPTION\",Me[Me.MISSING_PEER_DEPENDENCY=2]=\"MISSING_PEER_DEPENDENCY\",Me[Me.CYCLIC_DEPENDENCIES=3]=\"CYCLIC_DEPENDENCIES\",Me[Me.DISABLED_BUILD_SCRIPTS=4]=\"DISABLED_BUILD_SCRIPTS\",Me[Me.BUILD_DISABLED=5]=\"BUILD_DISABLED\",Me[Me.SOFT_LINK_BUILD=6]=\"SOFT_LINK_BUILD\",Me[Me.MUST_BUILD=7]=\"MUST_BUILD\",Me[Me.MUST_REBUILD=8]=\"MUST_REBUILD\",Me[Me.BUILD_FAILED=9]=\"BUILD_FAILED\",Me[Me.RESOLVER_NOT_FOUND=10]=\"RESOLVER_NOT_FOUND\",Me[Me.FETCHER_NOT_FOUND=11]=\"FETCHER_NOT_FOUND\",Me[Me.LINKER_NOT_FOUND=12]=\"LINKER_NOT_FOUND\",Me[Me.FETCH_NOT_CACHED=13]=\"FETCH_NOT_CACHED\",Me[Me.YARN_IMPORT_FAILED=14]=\"YARN_IMPORT_FAILED\",Me[Me.REMOTE_INVALID=15]=\"REMOTE_INVALID\",Me[Me.REMOTE_NOT_FOUND=16]=\"REMOTE_NOT_FOUND\",Me[Me.RESOLUTION_PACK=17]=\"RESOLUTION_PACK\",Me[Me.CACHE_CHECKSUM_MISMATCH=18]=\"CACHE_CHECKSUM_MISMATCH\",Me[Me.UNUSED_CACHE_ENTRY=19]=\"UNUSED_CACHE_ENTRY\",Me[Me.MISSING_LOCKFILE_ENTRY=20]=\"MISSING_LOCKFILE_ENTRY\",Me[Me.WORKSPACE_NOT_FOUND=21]=\"WORKSPACE_NOT_FOUND\",Me[Me.TOO_MANY_MATCHING_WORKSPACES=22]=\"TOO_MANY_MATCHING_WORKSPACES\",Me[Me.CONSTRAINTS_MISSING_DEPENDENCY=23]=\"CONSTRAINTS_MISSING_DEPENDENCY\",Me[Me.CONSTRAINTS_INCOMPATIBLE_DEPENDENCY=24]=\"CONSTRAINTS_INCOMPATIBLE_DEPENDENCY\",Me[Me.CONSTRAINTS_EXTRANEOUS_DEPENDENCY=25]=\"CONSTRAINTS_EXTRANEOUS_DEPENDENCY\",Me[Me.CONSTRAINTS_INVALID_DEPENDENCY=26]=\"CONSTRAINTS_INVALID_DEPENDENCY\",Me[Me.CANT_SUGGEST_RESOLUTIONS=27]=\"CANT_SUGGEST_RESOLUTIONS\",Me[Me.FROZEN_LOCKFILE_EXCEPTION=28]=\"FROZEN_LOCKFILE_EXCEPTION\",Me[Me.CROSS_DRIVE_VIRTUAL_LOCAL=29]=\"CROSS_DRIVE_VIRTUAL_LOCAL\",Me[Me.FETCH_FAILED=30]=\"FETCH_FAILED\",Me[Me.DANGEROUS_NODE_MODULES=31]=\"DANGEROUS_NODE_MODULES\",Me[Me.NODE_GYP_INJECTED=32]=\"NODE_GYP_INJECTED\",Me[Me.AUTHENTICATION_NOT_FOUND=33]=\"AUTHENTICATION_NOT_FOUND\",Me[Me.INVALID_CONFIGURATION_KEY=34]=\"INVALID_CONFIGURATION_KEY\",Me[Me.NETWORK_ERROR=35]=\"NETWORK_ERROR\",Me[Me.LIFECYCLE_SCRIPT=36]=\"LIFECYCLE_SCRIPT\",Me[Me.CONSTRAINTS_MISSING_FIELD=37]=\"CONSTRAINTS_MISSING_FIELD\",Me[Me.CONSTRAINTS_INCOMPATIBLE_FIELD=38]=\"CONSTRAINTS_INCOMPATIBLE_FIELD\",Me[Me.CONSTRAINTS_EXTRANEOUS_FIELD=39]=\"CONSTRAINTS_EXTRANEOUS_FIELD\",Me[Me.CONSTRAINTS_INVALID_FIELD=40]=\"CONSTRAINTS_INVALID_FIELD\",Me[Me.AUTHENTICATION_INVALID=41]=\"AUTHENTICATION_INVALID\",Me[Me.PROLOG_UNKNOWN_ERROR=42]=\"PROLOG_UNKNOWN_ERROR\",Me[Me.PROLOG_SYNTAX_ERROR=43]=\"PROLOG_SYNTAX_ERROR\",Me[Me.PROLOG_EXISTENCE_ERROR=44]=\"PROLOG_EXISTENCE_ERROR\",Me[Me.STACK_OVERFLOW_RESOLUTION=45]=\"STACK_OVERFLOW_RESOLUTION\",Me[Me.AUTOMERGE_FAILED_TO_PARSE=46]=\"AUTOMERGE_FAILED_TO_PARSE\",Me[Me.AUTOMERGE_IMMUTABLE=47]=\"AUTOMERGE_IMMUTABLE\",Me[Me.AUTOMERGE_SUCCESS=48]=\"AUTOMERGE_SUCCESS\",Me[Me.AUTOMERGE_REQUIRED=49]=\"AUTOMERGE_REQUIRED\",Me[Me.DEPRECATED_CLI_SETTINGS=50]=\"DEPRECATED_CLI_SETTINGS\",Me[Me.PLUGIN_NAME_NOT_FOUND=51]=\"PLUGIN_NAME_NOT_FOUND\",Me[Me.INVALID_PLUGIN_REFERENCE=52]=\"INVALID_PLUGIN_REFERENCE\",Me[Me.CONSTRAINTS_AMBIGUITY=53]=\"CONSTRAINTS_AMBIGUITY\",Me[Me.CACHE_OUTSIDE_PROJECT=54]=\"CACHE_OUTSIDE_PROJECT\",Me[Me.IMMUTABLE_INSTALL=55]=\"IMMUTABLE_INSTALL\",Me[Me.IMMUTABLE_CACHE=56]=\"IMMUTABLE_CACHE\",Me[Me.INVALID_MANIFEST=57]=\"INVALID_MANIFEST\",Me[Me.PACKAGE_PREPARATION_FAILED=58]=\"PACKAGE_PREPARATION_FAILED\",Me[Me.INVALID_RANGE_PEER_DEPENDENCY=59]=\"INVALID_RANGE_PEER_DEPENDENCY\",Me[Me.INCOMPATIBLE_PEER_DEPENDENCY=60]=\"INCOMPATIBLE_PEER_DEPENDENCY\",Me[Me.DEPRECATED_PACKAGE=61]=\"DEPRECATED_PACKAGE\",Me[Me.INCOMPATIBLE_OS=62]=\"INCOMPATIBLE_OS\",Me[Me.INCOMPATIBLE_CPU=63]=\"INCOMPATIBLE_CPU\",Me[Me.FROZEN_ARTIFACT_EXCEPTION=64]=\"FROZEN_ARTIFACT_EXCEPTION\",Me[Me.TELEMETRY_NOTICE=65]=\"TELEMETRY_NOTICE\",Me[Me.PATCH_HUNK_FAILED=66]=\"PATCH_HUNK_FAILED\",Me[Me.INVALID_CONFIGURATION_VALUE=67]=\"INVALID_CONFIGURATION_VALUE\",Me[Me.UNUSED_PACKAGE_EXTENSION=68]=\"UNUSED_PACKAGE_EXTENSION\",Me[Me.REDUNDANT_PACKAGE_EXTENSION=69]=\"REDUNDANT_PACKAGE_EXTENSION\",Me[Me.AUTO_NM_SUCCESS=70]=\"AUTO_NM_SUCCESS\",Me[Me.NM_CANT_INSTALL_EXTERNAL_SOFT_LINK=71]=\"NM_CANT_INSTALL_EXTERNAL_SOFT_LINK\",Me[Me.NM_PRESERVE_SYMLINKS_REQUIRED=72]=\"NM_PRESERVE_SYMLINKS_REQUIRED\",Me[Me.UPDATE_LOCKFILE_ONLY_SKIP_LINK=73]=\"UPDATE_LOCKFILE_ONLY_SKIP_LINK\",Me[Me.NM_HARDLINKS_MODE_DOWNGRADED=74]=\"NM_HARDLINKS_MODE_DOWNGRADED\",Me[Me.PROLOG_INSTANTIATION_ERROR=75]=\"PROLOG_INSTANTIATION_ERROR\",Me[Me.INCOMPATIBLE_ARCHITECTURE=76]=\"INCOMPATIBLE_ARCHITECTURE\",Me[Me.GHOST_ARCHITECTURE=77]=\"GHOST_ARCHITECTURE\",Me[Me.RESOLUTION_MISMATCH=78]=\"RESOLUTION_MISMATCH\",Me[Me.PROLOG_LIMIT_EXCEEDED=79]=\"PROLOG_LIMIT_EXCEEDED\",Me[Me.NETWORK_DISABLED=80]=\"NETWORK_DISABLED\",Me[Me.NETWORK_UNSAFE_HTTP=81]=\"NETWORK_UNSAFE_HTTP\",Me[Me.RESOLUTION_FAILED=82]=\"RESOLUTION_FAILED\",Me[Me.AUTOMERGE_GIT_ERROR=83]=\"AUTOMERGE_GIT_ERROR\",Me[Me.CONSTRAINTS_CHECK_FAILED=84]=\"CONSTRAINTS_CHECK_FAILED\",Me[Me.UPDATED_RESOLUTION_RECORD=85]=\"UPDATED_RESOLUTION_RECORD\",Me[Me.EXPLAIN_PEER_DEPENDENCIES_CTA=86]=\"EXPLAIN_PEER_DEPENDENCIES_CTA\",Me[Me.MIGRATION_SUCCESS=87]=\"MIGRATION_SUCCESS\",Me[Me.VERSION_NOTICE=88]=\"VERSION_NOTICE\",Me[Me.TIPS_NOTICE=89]=\"TIPS_NOTICE\",Me[Me.OFFLINE_MODE_ENABLED=90]=\"OFFLINE_MODE_ENABLED\",Me[Me.INVALID_PROVENANCE_ENVIRONMENT=91]=\"INVALID_PROVENANCE_ENVIRONMENT\",Me))(Dr||{})});var aB=L((K9t,Vne)=>{var eXe=\"2.0.0\",tXe=Number.MAX_SAFE_INTEGER||9007199254740991,rXe=16,nXe=250,iXe=[\"major\",\"premajor\",\"minor\",\"preminor\",\"patch\",\"prepatch\",\"prerelease\"];Vne.exports={MAX_LENGTH:256,MAX_SAFE_COMPONENT_LENGTH:rXe,MAX_SAFE_BUILD_LENGTH:nXe,MAX_SAFE_INTEGER:tXe,RELEASE_TYPES:iXe,SEMVER_SPEC_VERSION:eXe,FLAG_INCLUDE_PRERELEASE:1,FLAG_LOOSE:2}});var lB=L((J9t,Kne)=>{var sXe=typeof process==\"object\"&&process.env&&process.env.NODE_DEBUG&&/\\bsemver\\b/i.test(process.env.NODE_DEBUG)?(...t)=>console.error(\"SEMVER\",...t):()=>{};Kne.exports=sXe});var wE=L((Sp,Jne)=>{var{MAX_SAFE_COMPONENT_LENGTH:kU,MAX_SAFE_BUILD_LENGTH:oXe,MAX_LENGTH:aXe}=aB(),lXe=lB();Sp=Jne.exports={};var cXe=Sp.re=[],uXe=Sp.safeRe=[],rr=Sp.src=[],nr=Sp.t={},fXe=0,QU=\"[a-zA-Z0-9-]\",AXe=[[\"\\\\s\",1],[\"\\\\d\",aXe],[QU,oXe]],pXe=t=>{for(let[e,r]of AXe)t=t.split(`${e}*`).join(`${e}{0,${r}}`).split(`${e}+`).join(`${e}{1,${r}}`);return t},Kr=(t,e,r)=>{let s=pXe(e),a=fXe++;lXe(t,a,e),nr[t]=a,rr[a]=e,cXe[a]=new RegExp(e,r?\"g\":void 0),uXe[a]=new RegExp(s,r?\"g\":void 0)};Kr(\"NUMERICIDENTIFIER\",\"0|[1-9]\\\\d*\");Kr(\"NUMERICIDENTIFIERLOOSE\",\"\\\\d+\");Kr(\"NONNUMERICIDENTIFIER\",`\\\\d*[a-zA-Z-]${QU}*`);Kr(\"MAINVERSION\",`(${rr[nr.NUMERICIDENTIFIER]})\\\\.(${rr[nr.NUMERICIDENTIFIER]})\\\\.(${rr[nr.NUMERICIDENTIFIER]})`);Kr(\"MAINVERSIONLOOSE\",`(${rr[nr.NUMERICIDENTIFIERLOOSE]})\\\\.(${rr[nr.NUMERICIDENTIFIERLOOSE]})\\\\.(${rr[nr.NUMERICIDENTIFIERLOOSE]})`);Kr(\"PRERELEASEIDENTIFIER\",`(?:${rr[nr.NUMERICIDENTIFIER]}|${rr[nr.NONNUMERICIDENTIFIER]})`);Kr(\"PRERELEASEIDENTIFIERLOOSE\",`(?:${rr[nr.NUMERICIDENTIFIERLOOSE]}|${rr[nr.NONNUMERICIDENTIFIER]})`);Kr(\"PRERELEASE\",`(?:-(${rr[nr.PRERELEASEIDENTIFIER]}(?:\\\\.${rr[nr.PRERELEASEIDENTIFIER]})*))`);Kr(\"PRERELEASELOOSE\",`(?:-?(${rr[nr.PRERELEASEIDENTIFIERLOOSE]}(?:\\\\.${rr[nr.PRERELEASEIDENTIFIERLOOSE]})*))`);Kr(\"BUILDIDENTIFIER\",`${QU}+`);Kr(\"BUILD\",`(?:\\\\+(${rr[nr.BUILDIDENTIFIER]}(?:\\\\.${rr[nr.BUILDIDENTIFIER]})*))`);Kr(\"FULLPLAIN\",`v?${rr[nr.MAINVERSION]}${rr[nr.PRERELEASE]}?${rr[nr.BUILD]}?`);Kr(\"FULL\",`^${rr[nr.FULLPLAIN]}$`);Kr(\"LOOSEPLAIN\",`[v=\\\\s]*${rr[nr.MAINVERSIONLOOSE]}${rr[nr.PRERELEASELOOSE]}?${rr[nr.BUILD]}?`);Kr(\"LOOSE\",`^${rr[nr.LOOSEPLAIN]}$`);Kr(\"GTLT\",\"((?:<|>)?=?)\");Kr(\"XRANGEIDENTIFIERLOOSE\",`${rr[nr.NUMERICIDENTIFIERLOOSE]}|x|X|\\\\*`);Kr(\"XRANGEIDENTIFIER\",`${rr[nr.NUMERICIDENTIFIER]}|x|X|\\\\*`);Kr(\"XRANGEPLAIN\",`[v=\\\\s]*(${rr[nr.XRANGEIDENTIFIER]})(?:\\\\.(${rr[nr.XRANGEIDENTIFIER]})(?:\\\\.(${rr[nr.XRANGEIDENTIFIER]})(?:${rr[nr.PRERELEASE]})?${rr[nr.BUILD]}?)?)?`);Kr(\"XRANGEPLAINLOOSE\",`[v=\\\\s]*(${rr[nr.XRANGEIDENTIFIERLOOSE]})(?:\\\\.(${rr[nr.XRANGEIDENTIFIERLOOSE]})(?:\\\\.(${rr[nr.XRANGEIDENTIFIERLOOSE]})(?:${rr[nr.PRERELEASELOOSE]})?${rr[nr.BUILD]}?)?)?`);Kr(\"XRANGE\",`^${rr[nr.GTLT]}\\\\s*${rr[nr.XRANGEPLAIN]}$`);Kr(\"XRANGELOOSE\",`^${rr[nr.GTLT]}\\\\s*${rr[nr.XRANGEPLAINLOOSE]}$`);Kr(\"COERCEPLAIN\",`(^|[^\\\\d])(\\\\d{1,${kU}})(?:\\\\.(\\\\d{1,${kU}}))?(?:\\\\.(\\\\d{1,${kU}}))?`);Kr(\"COERCE\",`${rr[nr.COERCEPLAIN]}(?:$|[^\\\\d])`);Kr(\"COERCEFULL\",rr[nr.COERCEPLAIN]+`(?:${rr[nr.PRERELEASE]})?(?:${rr[nr.BUILD]})?(?:$|[^\\\\d])`);Kr(\"COERCERTL\",rr[nr.COERCE],!0);Kr(\"COERCERTLFULL\",rr[nr.COERCEFULL],!0);Kr(\"LONETILDE\",\"(?:~>?)\");Kr(\"TILDETRIM\",`(\\\\s*)${rr[nr.LONETILDE]}\\\\s+`,!0);Sp.tildeTrimReplace=\"$1~\";Kr(\"TILDE\",`^${rr[nr.LONETILDE]}${rr[nr.XRANGEPLAIN]}$`);Kr(\"TILDELOOSE\",`^${rr[nr.LONETILDE]}${rr[nr.XRANGEPLAINLOOSE]}$`);Kr(\"LONECARET\",\"(?:\\\\^)\");Kr(\"CARETTRIM\",`(\\\\s*)${rr[nr.LONECARET]}\\\\s+`,!0);Sp.caretTrimReplace=\"$1^\";Kr(\"CARET\",`^${rr[nr.LONECARET]}${rr[nr.XRANGEPLAIN]}$`);Kr(\"CARETLOOSE\",`^${rr[nr.LONECARET]}${rr[nr.XRANGEPLAINLOOSE]}$`);Kr(\"COMPARATORLOOSE\",`^${rr[nr.GTLT]}\\\\s*(${rr[nr.LOOSEPLAIN]})$|^$`);Kr(\"COMPARATOR\",`^${rr[nr.GTLT]}\\\\s*(${rr[nr.FULLPLAIN]})$|^$`);Kr(\"COMPARATORTRIM\",`(\\\\s*)${rr[nr.GTLT]}\\\\s*(${rr[nr.LOOSEPLAIN]}|${rr[nr.XRANGEPLAIN]})`,!0);Sp.comparatorTrimReplace=\"$1$2$3\";Kr(\"HYPHENRANGE\",`^\\\\s*(${rr[nr.XRANGEPLAIN]})\\\\s+-\\\\s+(${rr[nr.XRANGEPLAIN]})\\\\s*$`);Kr(\"HYPHENRANGELOOSE\",`^\\\\s*(${rr[nr.XRANGEPLAINLOOSE]})\\\\s+-\\\\s+(${rr[nr.XRANGEPLAINLOOSE]})\\\\s*$`);Kr(\"STAR\",\"(<|>)?=?\\\\s*\\\\*\");Kr(\"GTE0\",\"^\\\\s*>=\\\\s*0\\\\.0\\\\.0\\\\s*$\");Kr(\"GTE0PRE\",\"^\\\\s*>=\\\\s*0\\\\.0\\\\.0-0\\\\s*$\")});var Xx=L((z9t,zne)=>{var hXe=Object.freeze({loose:!0}),gXe=Object.freeze({}),dXe=t=>t?typeof t!=\"object\"?hXe:t:gXe;zne.exports=dXe});var TU=L((Z9t,$ne)=>{var Zne=/^[0-9]+$/,Xne=(t,e)=>{let r=Zne.test(t),s=Zne.test(e);return r&&s&&(t=+t,e=+e),t===e?0:r&&!s?-1:s&&!r?1:t<e?-1:1},mXe=(t,e)=>Xne(e,t);$ne.exports={compareIdentifiers:Xne,rcompareIdentifiers:mXe}});var Go=L((X9t,nie)=>{var $x=lB(),{MAX_LENGTH:eie,MAX_SAFE_INTEGER:ek}=aB(),{safeRe:tie,t:rie}=wE(),yXe=Xx(),{compareIdentifiers:BE}=TU(),RU=class t{constructor(e,r){if(r=yXe(r),e instanceof t){if(e.loose===!!r.loose&&e.includePrerelease===!!r.includePrerelease)return e;e=e.version}else if(typeof e!=\"string\")throw new TypeError(`Invalid version. Must be a string. Got type \"${typeof e}\".`);if(e.length>eie)throw new TypeError(`version is longer than ${eie} characters`);$x(\"SemVer\",e,r),this.options=r,this.loose=!!r.loose,this.includePrerelease=!!r.includePrerelease;let s=e.trim().match(r.loose?tie[rie.LOOSE]:tie[rie.FULL]);if(!s)throw new TypeError(`Invalid Version: ${e}`);if(this.raw=e,this.major=+s[1],this.minor=+s[2],this.patch=+s[3],this.major>ek||this.major<0)throw new TypeError(\"Invalid major version\");if(this.minor>ek||this.minor<0)throw new TypeError(\"Invalid minor version\");if(this.patch>ek||this.patch<0)throw new TypeError(\"Invalid patch version\");s[4]?this.prerelease=s[4].split(\".\").map(a=>{if(/^[0-9]+$/.test(a)){let n=+a;if(n>=0&&n<ek)return n}return a}):this.prerelease=[],this.build=s[5]?s[5].split(\".\"):[],this.format()}format(){return this.version=`${this.major}.${this.minor}.${this.patch}`,this.prerelease.length&&(this.version+=`-${this.prerelease.join(\".\")}`),this.version}toString(){return this.version}compare(e){if($x(\"SemVer.compare\",this.version,this.options,e),!(e instanceof t)){if(typeof e==\"string\"&&e===this.version)return 0;e=new t(e,this.options)}return e.version===this.version?0:this.compareMain(e)||this.comparePre(e)}compareMain(e){return e instanceof t||(e=new t(e,this.options)),BE(this.major,e.major)||BE(this.minor,e.minor)||BE(this.patch,e.patch)}comparePre(e){if(e instanceof t||(e=new t(e,this.options)),this.prerelease.length&&!e.prerelease.length)return-1;if(!this.prerelease.length&&e.prerelease.length)return 1;if(!this.prerelease.length&&!e.prerelease.length)return 0;let r=0;do{let s=this.prerelease[r],a=e.prerelease[r];if($x(\"prerelease compare\",r,s,a),s===void 0&&a===void 0)return 0;if(a===void 0)return 1;if(s===void 0)return-1;if(s===a)continue;return BE(s,a)}while(++r)}compareBuild(e){e instanceof t||(e=new t(e,this.options));let r=0;do{let s=this.build[r],a=e.build[r];if($x(\"prerelease compare\",r,s,a),s===void 0&&a===void 0)return 0;if(a===void 0)return 1;if(s===void 0)return-1;if(s===a)continue;return BE(s,a)}while(++r)}inc(e,r,s){switch(e){case\"premajor\":this.prerelease.length=0,this.patch=0,this.minor=0,this.major++,this.inc(\"pre\",r,s);break;case\"preminor\":this.prerelease.length=0,this.patch=0,this.minor++,this.inc(\"pre\",r,s);break;case\"prepatch\":this.prerelease.length=0,this.inc(\"patch\",r,s),this.inc(\"pre\",r,s);break;case\"prerelease\":this.prerelease.length===0&&this.inc(\"patch\",r,s),this.inc(\"pre\",r,s);break;case\"major\":(this.minor!==0||this.patch!==0||this.prerelease.length===0)&&this.major++,this.minor=0,this.patch=0,this.prerelease=[];break;case\"minor\":(this.patch!==0||this.prerelease.length===0)&&this.minor++,this.patch=0,this.prerelease=[];break;case\"patch\":this.prerelease.length===0&&this.patch++,this.prerelease=[];break;case\"pre\":{let a=Number(s)?1:0;if(!r&&s===!1)throw new Error(\"invalid increment argument: identifier is empty\");if(this.prerelease.length===0)this.prerelease=[a];else{let n=this.prerelease.length;for(;--n>=0;)typeof this.prerelease[n]==\"number\"&&(this.prerelease[n]++,n=-2);if(n===-1){if(r===this.prerelease.join(\".\")&&s===!1)throw new Error(\"invalid increment argument: identifier already exists\");this.prerelease.push(a)}}if(r){let n=[r,a];s===!1&&(n=[r]),BE(this.prerelease[0],r)===0?isNaN(this.prerelease[1])&&(this.prerelease=n):this.prerelease=n}break}default:throw new Error(`invalid increment argument: ${e}`)}return this.raw=this.format(),this.build.length&&(this.raw+=`+${this.build.join(\".\")}`),this}};nie.exports=RU});var Ld=L(($9t,sie)=>{var iie=Go(),EXe=(t,e,r=!1)=>{if(t instanceof iie)return t;try{return new iie(t,e)}catch(s){if(!r)return null;throw s}};sie.exports=EXe});var aie=L((eWt,oie)=>{var IXe=Ld(),CXe=(t,e)=>{let r=IXe(t,e);return r?r.version:null};oie.exports=CXe});var cie=L((tWt,lie)=>{var wXe=Ld(),BXe=(t,e)=>{let r=wXe(t.trim().replace(/^[=v]+/,\"\"),e);return r?r.version:null};lie.exports=BXe});var Aie=L((rWt,fie)=>{var uie=Go(),vXe=(t,e,r,s,a)=>{typeof r==\"string\"&&(a=s,s=r,r=void 0);try{return new uie(t instanceof uie?t.version:t,r).inc(e,s,a).version}catch{return null}};fie.exports=vXe});var gie=L((nWt,hie)=>{var pie=Ld(),SXe=(t,e)=>{let r=pie(t,null,!0),s=pie(e,null,!0),a=r.compare(s);if(a===0)return null;let n=a>0,c=n?r:s,f=n?s:r,p=!!c.prerelease.length;if(!!f.prerelease.length&&!p)return!f.patch&&!f.minor?\"major\":c.patch?\"patch\":c.minor?\"minor\":\"major\";let E=p?\"pre\":\"\";return r.major!==s.major?E+\"major\":r.minor!==s.minor?E+\"minor\":r.patch!==s.patch?E+\"patch\":\"prerelease\"};hie.exports=SXe});var mie=L((iWt,die)=>{var DXe=Go(),bXe=(t,e)=>new DXe(t,e).major;die.exports=bXe});var Eie=L((sWt,yie)=>{var PXe=Go(),xXe=(t,e)=>new PXe(t,e).minor;yie.exports=xXe});var Cie=L((oWt,Iie)=>{var kXe=Go(),QXe=(t,e)=>new kXe(t,e).patch;Iie.exports=QXe});var Bie=L((aWt,wie)=>{var TXe=Ld(),RXe=(t,e)=>{let r=TXe(t,e);return r&&r.prerelease.length?r.prerelease:null};wie.exports=RXe});var vc=L((lWt,Sie)=>{var vie=Go(),FXe=(t,e,r)=>new vie(t,r).compare(new vie(e,r));Sie.exports=FXe});var bie=L((cWt,Die)=>{var NXe=vc(),OXe=(t,e,r)=>NXe(e,t,r);Die.exports=OXe});var xie=L((uWt,Pie)=>{var LXe=vc(),MXe=(t,e)=>LXe(t,e,!0);Pie.exports=MXe});var tk=L((fWt,Qie)=>{var kie=Go(),_Xe=(t,e,r)=>{let s=new kie(t,r),a=new kie(e,r);return s.compare(a)||s.compareBuild(a)};Qie.exports=_Xe});var Rie=L((AWt,Tie)=>{var UXe=tk(),HXe=(t,e)=>t.sort((r,s)=>UXe(r,s,e));Tie.exports=HXe});var Nie=L((pWt,Fie)=>{var jXe=tk(),qXe=(t,e)=>t.sort((r,s)=>jXe(s,r,e));Fie.exports=qXe});var cB=L((hWt,Oie)=>{var GXe=vc(),WXe=(t,e,r)=>GXe(t,e,r)>0;Oie.exports=WXe});var rk=L((gWt,Lie)=>{var YXe=vc(),VXe=(t,e,r)=>YXe(t,e,r)<0;Lie.exports=VXe});var FU=L((dWt,Mie)=>{var KXe=vc(),JXe=(t,e,r)=>KXe(t,e,r)===0;Mie.exports=JXe});var NU=L((mWt,_ie)=>{var zXe=vc(),ZXe=(t,e,r)=>zXe(t,e,r)!==0;_ie.exports=ZXe});var nk=L((yWt,Uie)=>{var XXe=vc(),$Xe=(t,e,r)=>XXe(t,e,r)>=0;Uie.exports=$Xe});var ik=L((EWt,Hie)=>{var e$e=vc(),t$e=(t,e,r)=>e$e(t,e,r)<=0;Hie.exports=t$e});var OU=L((IWt,jie)=>{var r$e=FU(),n$e=NU(),i$e=cB(),s$e=nk(),o$e=rk(),a$e=ik(),l$e=(t,e,r,s)=>{switch(e){case\"===\":return typeof t==\"object\"&&(t=t.version),typeof r==\"object\"&&(r=r.version),t===r;case\"!==\":return typeof t==\"object\"&&(t=t.version),typeof r==\"object\"&&(r=r.version),t!==r;case\"\":case\"=\":case\"==\":return r$e(t,r,s);case\"!=\":return n$e(t,r,s);case\">\":return i$e(t,r,s);case\">=\":return s$e(t,r,s);case\"<\":return o$e(t,r,s);case\"<=\":return a$e(t,r,s);default:throw new TypeError(`Invalid operator: ${e}`)}};jie.exports=l$e});var Gie=L((CWt,qie)=>{var c$e=Go(),u$e=Ld(),{safeRe:sk,t:ok}=wE(),f$e=(t,e)=>{if(t instanceof c$e)return t;if(typeof t==\"number\"&&(t=String(t)),typeof t!=\"string\")return null;e=e||{};let r=null;if(!e.rtl)r=t.match(e.includePrerelease?sk[ok.COERCEFULL]:sk[ok.COERCE]);else{let p=e.includePrerelease?sk[ok.COERCERTLFULL]:sk[ok.COERCERTL],h;for(;(h=p.exec(t))&&(!r||r.index+r[0].length!==t.length);)(!r||h.index+h[0].length!==r.index+r[0].length)&&(r=h),p.lastIndex=h.index+h[1].length+h[2].length;p.lastIndex=-1}if(r===null)return null;let s=r[2],a=r[3]||\"0\",n=r[4]||\"0\",c=e.includePrerelease&&r[5]?`-${r[5]}`:\"\",f=e.includePrerelease&&r[6]?`+${r[6]}`:\"\";return u$e(`${s}.${a}.${n}${c}${f}`,e)};qie.exports=f$e});var Yie=L((wWt,Wie)=>{\"use strict\";Wie.exports=function(t){t.prototype[Symbol.iterator]=function*(){for(let e=this.head;e;e=e.next)yield e.value}}});var ak=L((BWt,Vie)=>{\"use strict\";Vie.exports=Fn;Fn.Node=Md;Fn.create=Fn;function Fn(t){var e=this;if(e instanceof Fn||(e=new Fn),e.tail=null,e.head=null,e.length=0,t&&typeof t.forEach==\"function\")t.forEach(function(a){e.push(a)});else if(arguments.length>0)for(var r=0,s=arguments.length;r<s;r++)e.push(arguments[r]);return e}Fn.prototype.removeNode=function(t){if(t.list!==this)throw new Error(\"removing node which does not belong to this list\");var e=t.next,r=t.prev;return e&&(e.prev=r),r&&(r.next=e),t===this.head&&(this.head=e),t===this.tail&&(this.tail=r),t.list.length--,t.next=null,t.prev=null,t.list=null,e};Fn.prototype.unshiftNode=function(t){if(t!==this.head){t.list&&t.list.removeNode(t);var e=this.head;t.list=this,t.next=e,e&&(e.prev=t),this.head=t,this.tail||(this.tail=t),this.length++}};Fn.prototype.pushNode=function(t){if(t!==this.tail){t.list&&t.list.removeNode(t);var e=this.tail;t.list=this,t.prev=e,e&&(e.next=t),this.tail=t,this.head||(this.head=t),this.length++}};Fn.prototype.push=function(){for(var t=0,e=arguments.length;t<e;t++)p$e(this,arguments[t]);return this.length};Fn.prototype.unshift=function(){for(var t=0,e=arguments.length;t<e;t++)h$e(this,arguments[t]);return this.length};Fn.prototype.pop=function(){if(this.tail){var t=this.tail.value;return this.tail=this.tail.prev,this.tail?this.tail.next=null:this.head=null,this.length--,t}};Fn.prototype.shift=function(){if(this.head){var t=this.head.value;return this.head=this.head.next,this.head?this.head.prev=null:this.tail=null,this.length--,t}};Fn.prototype.forEach=function(t,e){e=e||this;for(var r=this.head,s=0;r!==null;s++)t.call(e,r.value,s,this),r=r.next};Fn.prototype.forEachReverse=function(t,e){e=e||this;for(var r=this.tail,s=this.length-1;r!==null;s--)t.call(e,r.value,s,this),r=r.prev};Fn.prototype.get=function(t){for(var e=0,r=this.head;r!==null&&e<t;e++)r=r.next;if(e===t&&r!==null)return r.value};Fn.prototype.getReverse=function(t){for(var e=0,r=this.tail;r!==null&&e<t;e++)r=r.prev;if(e===t&&r!==null)return r.value};Fn.prototype.map=function(t,e){e=e||this;for(var r=new Fn,s=this.head;s!==null;)r.push(t.call(e,s.value,this)),s=s.next;return r};Fn.prototype.mapReverse=function(t,e){e=e||this;for(var r=new Fn,s=this.tail;s!==null;)r.push(t.call(e,s.value,this)),s=s.prev;return r};Fn.prototype.reduce=function(t,e){var r,s=this.head;if(arguments.length>1)r=e;else if(this.head)s=this.head.next,r=this.head.value;else throw new TypeError(\"Reduce of empty list with no initial value\");for(var a=0;s!==null;a++)r=t(r,s.value,a),s=s.next;return r};Fn.prototype.reduceReverse=function(t,e){var r,s=this.tail;if(arguments.length>1)r=e;else if(this.tail)s=this.tail.prev,r=this.tail.value;else throw new TypeError(\"Reduce of empty list with no initial value\");for(var a=this.length-1;s!==null;a--)r=t(r,s.value,a),s=s.prev;return r};Fn.prototype.toArray=function(){for(var t=new Array(this.length),e=0,r=this.head;r!==null;e++)t[e]=r.value,r=r.next;return t};Fn.prototype.toArrayReverse=function(){for(var t=new Array(this.length),e=0,r=this.tail;r!==null;e++)t[e]=r.value,r=r.prev;return t};Fn.prototype.slice=function(t,e){e=e||this.length,e<0&&(e+=this.length),t=t||0,t<0&&(t+=this.length);var r=new Fn;if(e<t||e<0)return r;t<0&&(t=0),e>this.length&&(e=this.length);for(var s=0,a=this.head;a!==null&&s<t;s++)a=a.next;for(;a!==null&&s<e;s++,a=a.next)r.push(a.value);return r};Fn.prototype.sliceReverse=function(t,e){e=e||this.length,e<0&&(e+=this.length),t=t||0,t<0&&(t+=this.length);var r=new Fn;if(e<t||e<0)return r;t<0&&(t=0),e>this.length&&(e=this.length);for(var s=this.length,a=this.tail;a!==null&&s>e;s--)a=a.prev;for(;a!==null&&s>t;s--,a=a.prev)r.push(a.value);return r};Fn.prototype.splice=function(t,e,...r){t>this.length&&(t=this.length-1),t<0&&(t=this.length+t);for(var s=0,a=this.head;a!==null&&s<t;s++)a=a.next;for(var n=[],s=0;a&&s<e;s++)n.push(a.value),a=this.removeNode(a);a===null&&(a=this.tail),a!==this.head&&a!==this.tail&&(a=a.prev);for(var s=0;s<r.length;s++)a=A$e(this,a,r[s]);return n};Fn.prototype.reverse=function(){for(var t=this.head,e=this.tail,r=t;r!==null;r=r.prev){var s=r.prev;r.prev=r.next,r.next=s}return this.head=e,this.tail=t,this};function A$e(t,e,r){var s=e===t.head?new Md(r,null,e,t):new Md(r,e,e.next,t);return s.next===null&&(t.tail=s),s.prev===null&&(t.head=s),t.length++,s}function p$e(t,e){t.tail=new Md(e,t.tail,null,t),t.head||(t.head=t.tail),t.length++}function h$e(t,e){t.head=new Md(e,null,t.head,t),t.tail||(t.tail=t.head),t.length++}function Md(t,e,r,s){if(!(this instanceof Md))return new Md(t,e,r,s);this.list=s,this.value=t,e?(e.next=this,this.prev=e):this.prev=null,r?(r.prev=this,this.next=r):this.next=null}try{Yie()(Fn)}catch{}});var Xie=L((vWt,Zie)=>{\"use strict\";var g$e=ak(),_d=Symbol(\"max\"),bp=Symbol(\"length\"),vE=Symbol(\"lengthCalculator\"),fB=Symbol(\"allowStale\"),Ud=Symbol(\"maxAge\"),Dp=Symbol(\"dispose\"),Kie=Symbol(\"noDisposeOnSet\"),Ws=Symbol(\"lruList\"),_u=Symbol(\"cache\"),zie=Symbol(\"updateAgeOnGet\"),LU=()=>1,_U=class{constructor(e){if(typeof e==\"number\"&&(e={max:e}),e||(e={}),e.max&&(typeof e.max!=\"number\"||e.max<0))throw new TypeError(\"max must be a non-negative number\");let r=this[_d]=e.max||1/0,s=e.length||LU;if(this[vE]=typeof s!=\"function\"?LU:s,this[fB]=e.stale||!1,e.maxAge&&typeof e.maxAge!=\"number\")throw new TypeError(\"maxAge must be a number\");this[Ud]=e.maxAge||0,this[Dp]=e.dispose,this[Kie]=e.noDisposeOnSet||!1,this[zie]=e.updateAgeOnGet||!1,this.reset()}set max(e){if(typeof e!=\"number\"||e<0)throw new TypeError(\"max must be a non-negative number\");this[_d]=e||1/0,uB(this)}get max(){return this[_d]}set allowStale(e){this[fB]=!!e}get allowStale(){return this[fB]}set maxAge(e){if(typeof e!=\"number\")throw new TypeError(\"maxAge must be a non-negative number\");this[Ud]=e,uB(this)}get maxAge(){return this[Ud]}set lengthCalculator(e){typeof e!=\"function\"&&(e=LU),e!==this[vE]&&(this[vE]=e,this[bp]=0,this[Ws].forEach(r=>{r.length=this[vE](r.value,r.key),this[bp]+=r.length})),uB(this)}get lengthCalculator(){return this[vE]}get length(){return this[bp]}get itemCount(){return this[Ws].length}rforEach(e,r){r=r||this;for(let s=this[Ws].tail;s!==null;){let a=s.prev;Jie(this,e,s,r),s=a}}forEach(e,r){r=r||this;for(let s=this[Ws].head;s!==null;){let a=s.next;Jie(this,e,s,r),s=a}}keys(){return this[Ws].toArray().map(e=>e.key)}values(){return this[Ws].toArray().map(e=>e.value)}reset(){this[Dp]&&this[Ws]&&this[Ws].length&&this[Ws].forEach(e=>this[Dp](e.key,e.value)),this[_u]=new Map,this[Ws]=new g$e,this[bp]=0}dump(){return this[Ws].map(e=>lk(this,e)?!1:{k:e.key,v:e.value,e:e.now+(e.maxAge||0)}).toArray().filter(e=>e)}dumpLru(){return this[Ws]}set(e,r,s){if(s=s||this[Ud],s&&typeof s!=\"number\")throw new TypeError(\"maxAge must be a number\");let a=s?Date.now():0,n=this[vE](r,e);if(this[_u].has(e)){if(n>this[_d])return SE(this,this[_u].get(e)),!1;let p=this[_u].get(e).value;return this[Dp]&&(this[Kie]||this[Dp](e,p.value)),p.now=a,p.maxAge=s,p.value=r,this[bp]+=n-p.length,p.length=n,this.get(e),uB(this),!0}let c=new UU(e,r,n,a,s);return c.length>this[_d]?(this[Dp]&&this[Dp](e,r),!1):(this[bp]+=c.length,this[Ws].unshift(c),this[_u].set(e,this[Ws].head),uB(this),!0)}has(e){if(!this[_u].has(e))return!1;let r=this[_u].get(e).value;return!lk(this,r)}get(e){return MU(this,e,!0)}peek(e){return MU(this,e,!1)}pop(){let e=this[Ws].tail;return e?(SE(this,e),e.value):null}del(e){SE(this,this[_u].get(e))}load(e){this.reset();let r=Date.now();for(let s=e.length-1;s>=0;s--){let a=e[s],n=a.e||0;if(n===0)this.set(a.k,a.v);else{let c=n-r;c>0&&this.set(a.k,a.v,c)}}}prune(){this[_u].forEach((e,r)=>MU(this,r,!1))}},MU=(t,e,r)=>{let s=t[_u].get(e);if(s){let a=s.value;if(lk(t,a)){if(SE(t,s),!t[fB])return}else r&&(t[zie]&&(s.value.now=Date.now()),t[Ws].unshiftNode(s));return a.value}},lk=(t,e)=>{if(!e||!e.maxAge&&!t[Ud])return!1;let r=Date.now()-e.now;return e.maxAge?r>e.maxAge:t[Ud]&&r>t[Ud]},uB=t=>{if(t[bp]>t[_d])for(let e=t[Ws].tail;t[bp]>t[_d]&&e!==null;){let r=e.prev;SE(t,e),e=r}},SE=(t,e)=>{if(e){let r=e.value;t[Dp]&&t[Dp](r.key,r.value),t[bp]-=r.length,t[_u].delete(r.key),t[Ws].removeNode(e)}},UU=class{constructor(e,r,s,a,n){this.key=e,this.value=r,this.length=s,this.now=a,this.maxAge=n||0}},Jie=(t,e,r,s)=>{let a=r.value;lk(t,a)&&(SE(t,r),t[fB]||(a=void 0)),a&&e.call(s,a.value,a.key,t)};Zie.exports=_U});var Sc=L((SWt,rse)=>{var HU=class t{constructor(e,r){if(r=m$e(r),e instanceof t)return e.loose===!!r.loose&&e.includePrerelease===!!r.includePrerelease?e:new t(e.raw,r);if(e instanceof jU)return this.raw=e.value,this.set=[[e]],this.format(),this;if(this.options=r,this.loose=!!r.loose,this.includePrerelease=!!r.includePrerelease,this.raw=e.trim().split(/\\s+/).join(\" \"),this.set=this.raw.split(\"||\").map(s=>this.parseRange(s.trim())).filter(s=>s.length),!this.set.length)throw new TypeError(`Invalid SemVer Range: ${this.raw}`);if(this.set.length>1){let s=this.set[0];if(this.set=this.set.filter(a=>!ese(a[0])),this.set.length===0)this.set=[s];else if(this.set.length>1){for(let a of this.set)if(a.length===1&&v$e(a[0])){this.set=[a];break}}}this.format()}format(){return this.range=this.set.map(e=>e.join(\" \").trim()).join(\"||\").trim(),this.range}toString(){return this.range}parseRange(e){let s=((this.options.includePrerelease&&w$e)|(this.options.loose&&B$e))+\":\"+e,a=$ie.get(s);if(a)return a;let n=this.options.loose,c=n?ol[Ba.HYPHENRANGELOOSE]:ol[Ba.HYPHENRANGE];e=e.replace(c,F$e(this.options.includePrerelease)),Si(\"hyphen replace\",e),e=e.replace(ol[Ba.COMPARATORTRIM],E$e),Si(\"comparator trim\",e),e=e.replace(ol[Ba.TILDETRIM],I$e),Si(\"tilde trim\",e),e=e.replace(ol[Ba.CARETTRIM],C$e),Si(\"caret trim\",e);let f=e.split(\" \").map(C=>S$e(C,this.options)).join(\" \").split(/\\s+/).map(C=>R$e(C,this.options));n&&(f=f.filter(C=>(Si(\"loose invalid filter\",C,this.options),!!C.match(ol[Ba.COMPARATORLOOSE])))),Si(\"range list\",f);let p=new Map,h=f.map(C=>new jU(C,this.options));for(let C of h){if(ese(C))return[C];p.set(C.value,C)}p.size>1&&p.has(\"\")&&p.delete(\"\");let E=[...p.values()];return $ie.set(s,E),E}intersects(e,r){if(!(e instanceof t))throw new TypeError(\"a Range is required\");return this.set.some(s=>tse(s,r)&&e.set.some(a=>tse(a,r)&&s.every(n=>a.every(c=>n.intersects(c,r)))))}test(e){if(!e)return!1;if(typeof e==\"string\")try{e=new y$e(e,this.options)}catch{return!1}for(let r=0;r<this.set.length;r++)if(N$e(this.set[r],e,this.options))return!0;return!1}};rse.exports=HU;var d$e=Xie(),$ie=new d$e({max:1e3}),m$e=Xx(),jU=AB(),Si=lB(),y$e=Go(),{safeRe:ol,t:Ba,comparatorTrimReplace:E$e,tildeTrimReplace:I$e,caretTrimReplace:C$e}=wE(),{FLAG_INCLUDE_PRERELEASE:w$e,FLAG_LOOSE:B$e}=aB(),ese=t=>t.value===\"<0.0.0-0\",v$e=t=>t.value===\"\",tse=(t,e)=>{let r=!0,s=t.slice(),a=s.pop();for(;r&&s.length;)r=s.every(n=>a.intersects(n,e)),a=s.pop();return r},S$e=(t,e)=>(Si(\"comp\",t,e),t=P$e(t,e),Si(\"caret\",t),t=D$e(t,e),Si(\"tildes\",t),t=k$e(t,e),Si(\"xrange\",t),t=T$e(t,e),Si(\"stars\",t),t),va=t=>!t||t.toLowerCase()===\"x\"||t===\"*\",D$e=(t,e)=>t.trim().split(/\\s+/).map(r=>b$e(r,e)).join(\" \"),b$e=(t,e)=>{let r=e.loose?ol[Ba.TILDELOOSE]:ol[Ba.TILDE];return t.replace(r,(s,a,n,c,f)=>{Si(\"tilde\",t,s,a,n,c,f);let p;return va(a)?p=\"\":va(n)?p=`>=${a}.0.0 <${+a+1}.0.0-0`:va(c)?p=`>=${a}.${n}.0 <${a}.${+n+1}.0-0`:f?(Si(\"replaceTilde pr\",f),p=`>=${a}.${n}.${c}-${f} <${a}.${+n+1}.0-0`):p=`>=${a}.${n}.${c} <${a}.${+n+1}.0-0`,Si(\"tilde return\",p),p})},P$e=(t,e)=>t.trim().split(/\\s+/).map(r=>x$e(r,e)).join(\" \"),x$e=(t,e)=>{Si(\"caret\",t,e);let r=e.loose?ol[Ba.CARETLOOSE]:ol[Ba.CARET],s=e.includePrerelease?\"-0\":\"\";return t.replace(r,(a,n,c,f,p)=>{Si(\"caret\",t,a,n,c,f,p);let h;return va(n)?h=\"\":va(c)?h=`>=${n}.0.0${s} <${+n+1}.0.0-0`:va(f)?n===\"0\"?h=`>=${n}.${c}.0${s} <${n}.${+c+1}.0-0`:h=`>=${n}.${c}.0${s} <${+n+1}.0.0-0`:p?(Si(\"replaceCaret pr\",p),n===\"0\"?c===\"0\"?h=`>=${n}.${c}.${f}-${p} <${n}.${c}.${+f+1}-0`:h=`>=${n}.${c}.${f}-${p} <${n}.${+c+1}.0-0`:h=`>=${n}.${c}.${f}-${p} <${+n+1}.0.0-0`):(Si(\"no pr\"),n===\"0\"?c===\"0\"?h=`>=${n}.${c}.${f}${s} <${n}.${c}.${+f+1}-0`:h=`>=${n}.${c}.${f}${s} <${n}.${+c+1}.0-0`:h=`>=${n}.${c}.${f} <${+n+1}.0.0-0`),Si(\"caret return\",h),h})},k$e=(t,e)=>(Si(\"replaceXRanges\",t,e),t.split(/\\s+/).map(r=>Q$e(r,e)).join(\" \")),Q$e=(t,e)=>{t=t.trim();let r=e.loose?ol[Ba.XRANGELOOSE]:ol[Ba.XRANGE];return t.replace(r,(s,a,n,c,f,p)=>{Si(\"xRange\",t,s,a,n,c,f,p);let h=va(n),E=h||va(c),C=E||va(f),S=C;return a===\"=\"&&S&&(a=\"\"),p=e.includePrerelease?\"-0\":\"\",h?a===\">\"||a===\"<\"?s=\"<0.0.0-0\":s=\"*\":a&&S?(E&&(c=0),f=0,a===\">\"?(a=\">=\",E?(n=+n+1,c=0,f=0):(c=+c+1,f=0)):a===\"<=\"&&(a=\"<\",E?n=+n+1:c=+c+1),a===\"<\"&&(p=\"-0\"),s=`${a+n}.${c}.${f}${p}`):E?s=`>=${n}.0.0${p} <${+n+1}.0.0-0`:C&&(s=`>=${n}.${c}.0${p} <${n}.${+c+1}.0-0`),Si(\"xRange return\",s),s})},T$e=(t,e)=>(Si(\"replaceStars\",t,e),t.trim().replace(ol[Ba.STAR],\"\")),R$e=(t,e)=>(Si(\"replaceGTE0\",t,e),t.trim().replace(ol[e.includePrerelease?Ba.GTE0PRE:Ba.GTE0],\"\")),F$e=t=>(e,r,s,a,n,c,f,p,h,E,C,S,P)=>(va(s)?r=\"\":va(a)?r=`>=${s}.0.0${t?\"-0\":\"\"}`:va(n)?r=`>=${s}.${a}.0${t?\"-0\":\"\"}`:c?r=`>=${r}`:r=`>=${r}${t?\"-0\":\"\"}`,va(h)?p=\"\":va(E)?p=`<${+h+1}.0.0-0`:va(C)?p=`<${h}.${+E+1}.0-0`:S?p=`<=${h}.${E}.${C}-${S}`:t?p=`<${h}.${E}.${+C+1}-0`:p=`<=${p}`,`${r} ${p}`.trim()),N$e=(t,e,r)=>{for(let s=0;s<t.length;s++)if(!t[s].test(e))return!1;if(e.prerelease.length&&!r.includePrerelease){for(let s=0;s<t.length;s++)if(Si(t[s].semver),t[s].semver!==jU.ANY&&t[s].semver.prerelease.length>0){let a=t[s].semver;if(a.major===e.major&&a.minor===e.minor&&a.patch===e.patch)return!0}return!1}return!0}});var AB=L((DWt,lse)=>{var pB=Symbol(\"SemVer ANY\"),WU=class t{static get ANY(){return pB}constructor(e,r){if(r=nse(r),e instanceof t){if(e.loose===!!r.loose)return e;e=e.value}e=e.trim().split(/\\s+/).join(\" \"),GU(\"comparator\",e,r),this.options=r,this.loose=!!r.loose,this.parse(e),this.semver===pB?this.value=\"\":this.value=this.operator+this.semver.version,GU(\"comp\",this)}parse(e){let r=this.options.loose?ise[sse.COMPARATORLOOSE]:ise[sse.COMPARATOR],s=e.match(r);if(!s)throw new TypeError(`Invalid comparator: ${e}`);this.operator=s[1]!==void 0?s[1]:\"\",this.operator===\"=\"&&(this.operator=\"\"),s[2]?this.semver=new ose(s[2],this.options.loose):this.semver=pB}toString(){return this.value}test(e){if(GU(\"Comparator.test\",e,this.options.loose),this.semver===pB||e===pB)return!0;if(typeof e==\"string\")try{e=new ose(e,this.options)}catch{return!1}return qU(e,this.operator,this.semver,this.options)}intersects(e,r){if(!(e instanceof t))throw new TypeError(\"a Comparator is required\");return this.operator===\"\"?this.value===\"\"?!0:new ase(e.value,r).test(this.value):e.operator===\"\"?e.value===\"\"?!0:new ase(this.value,r).test(e.semver):(r=nse(r),r.includePrerelease&&(this.value===\"<0.0.0-0\"||e.value===\"<0.0.0-0\")||!r.includePrerelease&&(this.value.startsWith(\"<0.0.0\")||e.value.startsWith(\"<0.0.0\"))?!1:!!(this.operator.startsWith(\">\")&&e.operator.startsWith(\">\")||this.operator.startsWith(\"<\")&&e.operator.startsWith(\"<\")||this.semver.version===e.semver.version&&this.operator.includes(\"=\")&&e.operator.includes(\"=\")||qU(this.semver,\"<\",e.semver,r)&&this.operator.startsWith(\">\")&&e.operator.startsWith(\"<\")||qU(this.semver,\">\",e.semver,r)&&this.operator.startsWith(\"<\")&&e.operator.startsWith(\">\")))}};lse.exports=WU;var nse=Xx(),{safeRe:ise,t:sse}=wE(),qU=OU(),GU=lB(),ose=Go(),ase=Sc()});var hB=L((bWt,cse)=>{var O$e=Sc(),L$e=(t,e,r)=>{try{e=new O$e(e,r)}catch{return!1}return e.test(t)};cse.exports=L$e});var fse=L((PWt,use)=>{var M$e=Sc(),_$e=(t,e)=>new M$e(t,e).set.map(r=>r.map(s=>s.value).join(\" \").trim().split(\" \"));use.exports=_$e});var pse=L((xWt,Ase)=>{var U$e=Go(),H$e=Sc(),j$e=(t,e,r)=>{let s=null,a=null,n=null;try{n=new H$e(e,r)}catch{return null}return t.forEach(c=>{n.test(c)&&(!s||a.compare(c)===-1)&&(s=c,a=new U$e(s,r))}),s};Ase.exports=j$e});var gse=L((kWt,hse)=>{var q$e=Go(),G$e=Sc(),W$e=(t,e,r)=>{let s=null,a=null,n=null;try{n=new G$e(e,r)}catch{return null}return t.forEach(c=>{n.test(c)&&(!s||a.compare(c)===1)&&(s=c,a=new q$e(s,r))}),s};hse.exports=W$e});var yse=L((QWt,mse)=>{var YU=Go(),Y$e=Sc(),dse=cB(),V$e=(t,e)=>{t=new Y$e(t,e);let r=new YU(\"0.0.0\");if(t.test(r)||(r=new YU(\"0.0.0-0\"),t.test(r)))return r;r=null;for(let s=0;s<t.set.length;++s){let a=t.set[s],n=null;a.forEach(c=>{let f=new YU(c.semver.version);switch(c.operator){case\">\":f.prerelease.length===0?f.patch++:f.prerelease.push(0),f.raw=f.format();case\"\":case\">=\":(!n||dse(f,n))&&(n=f);break;case\"<\":case\"<=\":break;default:throw new Error(`Unexpected operation: ${c.operator}`)}}),n&&(!r||dse(r,n))&&(r=n)}return r&&t.test(r)?r:null};mse.exports=V$e});var Ise=L((TWt,Ese)=>{var K$e=Sc(),J$e=(t,e)=>{try{return new K$e(t,e).range||\"*\"}catch{return null}};Ese.exports=J$e});var ck=L((RWt,vse)=>{var z$e=Go(),Bse=AB(),{ANY:Z$e}=Bse,X$e=Sc(),$$e=hB(),Cse=cB(),wse=rk(),eet=ik(),tet=nk(),ret=(t,e,r,s)=>{t=new z$e(t,s),e=new X$e(e,s);let a,n,c,f,p;switch(r){case\">\":a=Cse,n=eet,c=wse,f=\">\",p=\">=\";break;case\"<\":a=wse,n=tet,c=Cse,f=\"<\",p=\"<=\";break;default:throw new TypeError('Must provide a hilo val of \"<\" or \">\"')}if($$e(t,e,s))return!1;for(let h=0;h<e.set.length;++h){let E=e.set[h],C=null,S=null;if(E.forEach(P=>{P.semver===Z$e&&(P=new Bse(\">=0.0.0\")),C=C||P,S=S||P,a(P.semver,C.semver,s)?C=P:c(P.semver,S.semver,s)&&(S=P)}),C.operator===f||C.operator===p||(!S.operator||S.operator===f)&&n(t,S.semver))return!1;if(S.operator===p&&c(t,S.semver))return!1}return!0};vse.exports=ret});var Dse=L((FWt,Sse)=>{var net=ck(),iet=(t,e,r)=>net(t,e,\">\",r);Sse.exports=iet});var Pse=L((NWt,bse)=>{var set=ck(),oet=(t,e,r)=>set(t,e,\"<\",r);bse.exports=oet});var Qse=L((OWt,kse)=>{var xse=Sc(),aet=(t,e,r)=>(t=new xse(t,r),e=new xse(e,r),t.intersects(e,r));kse.exports=aet});var Rse=L((LWt,Tse)=>{var cet=hB(),uet=vc();Tse.exports=(t,e,r)=>{let s=[],a=null,n=null,c=t.sort((E,C)=>uet(E,C,r));for(let E of c)cet(E,e,r)?(n=E,a||(a=E)):(n&&s.push([a,n]),n=null,a=null);a&&s.push([a,null]);let f=[];for(let[E,C]of s)E===C?f.push(E):!C&&E===c[0]?f.push(\"*\"):C?E===c[0]?f.push(`<=${C}`):f.push(`${E} - ${C}`):f.push(`>=${E}`);let p=f.join(\" || \"),h=typeof e.raw==\"string\"?e.raw:String(e);return p.length<h.length?p:e}});var _se=L((MWt,Mse)=>{var Fse=Sc(),KU=AB(),{ANY:VU}=KU,gB=hB(),JU=vc(),fet=(t,e,r={})=>{if(t===e)return!0;t=new Fse(t,r),e=new Fse(e,r);let s=!1;e:for(let a of t.set){for(let n of e.set){let c=pet(a,n,r);if(s=s||c!==null,c)continue e}if(s)return!1}return!0},Aet=[new KU(\">=0.0.0-0\")],Nse=[new KU(\">=0.0.0\")],pet=(t,e,r)=>{if(t===e)return!0;if(t.length===1&&t[0].semver===VU){if(e.length===1&&e[0].semver===VU)return!0;r.includePrerelease?t=Aet:t=Nse}if(e.length===1&&e[0].semver===VU){if(r.includePrerelease)return!0;e=Nse}let s=new Set,a,n;for(let P of t)P.operator===\">\"||P.operator===\">=\"?a=Ose(a,P,r):P.operator===\"<\"||P.operator===\"<=\"?n=Lse(n,P,r):s.add(P.semver);if(s.size>1)return null;let c;if(a&&n){if(c=JU(a.semver,n.semver,r),c>0)return null;if(c===0&&(a.operator!==\">=\"||n.operator!==\"<=\"))return null}for(let P of s){if(a&&!gB(P,String(a),r)||n&&!gB(P,String(n),r))return null;for(let I of e)if(!gB(P,String(I),r))return!1;return!0}let f,p,h,E,C=n&&!r.includePrerelease&&n.semver.prerelease.length?n.semver:!1,S=a&&!r.includePrerelease&&a.semver.prerelease.length?a.semver:!1;C&&C.prerelease.length===1&&n.operator===\"<\"&&C.prerelease[0]===0&&(C=!1);for(let P of e){if(E=E||P.operator===\">\"||P.operator===\">=\",h=h||P.operator===\"<\"||P.operator===\"<=\",a){if(S&&P.semver.prerelease&&P.semver.prerelease.length&&P.semver.major===S.major&&P.semver.minor===S.minor&&P.semver.patch===S.patch&&(S=!1),P.operator===\">\"||P.operator===\">=\"){if(f=Ose(a,P,r),f===P&&f!==a)return!1}else if(a.operator===\">=\"&&!gB(a.semver,String(P),r))return!1}if(n){if(C&&P.semver.prerelease&&P.semver.prerelease.length&&P.semver.major===C.major&&P.semver.minor===C.minor&&P.semver.patch===C.patch&&(C=!1),P.operator===\"<\"||P.operator===\"<=\"){if(p=Lse(n,P,r),p===P&&p!==n)return!1}else if(n.operator===\"<=\"&&!gB(n.semver,String(P),r))return!1}if(!P.operator&&(n||a)&&c!==0)return!1}return!(a&&h&&!n&&c!==0||n&&E&&!a&&c!==0||S||C)},Ose=(t,e,r)=>{if(!t)return e;let s=JU(t.semver,e.semver,r);return s>0?t:s<0||e.operator===\">\"&&t.operator===\">=\"?e:t},Lse=(t,e,r)=>{if(!t)return e;let s=JU(t.semver,e.semver,r);return s<0?t:s>0||e.operator===\"<\"&&t.operator===\"<=\"?e:t};Mse.exports=fet});var Ai=L((_Wt,jse)=>{var zU=wE(),Use=aB(),het=Go(),Hse=TU(),get=Ld(),det=aie(),met=cie(),yet=Aie(),Eet=gie(),Iet=mie(),Cet=Eie(),wet=Cie(),Bet=Bie(),vet=vc(),Det=bie(),bet=xie(),Pet=tk(),xet=Rie(),ket=Nie(),Qet=cB(),Tet=rk(),Ret=FU(),Fet=NU(),Net=nk(),Oet=ik(),Let=OU(),Met=Gie(),_et=AB(),Uet=Sc(),Het=hB(),jet=fse(),qet=pse(),Get=gse(),Wet=yse(),Yet=Ise(),Vet=ck(),Ket=Dse(),Jet=Pse(),zet=Qse(),Zet=Rse(),Xet=_se();jse.exports={parse:get,valid:det,clean:met,inc:yet,diff:Eet,major:Iet,minor:Cet,patch:wet,prerelease:Bet,compare:vet,rcompare:Det,compareLoose:bet,compareBuild:Pet,sort:xet,rsort:ket,gt:Qet,lt:Tet,eq:Ret,neq:Fet,gte:Net,lte:Oet,cmp:Let,coerce:Met,Comparator:_et,Range:Uet,satisfies:Het,toComparators:jet,maxSatisfying:qet,minSatisfying:Get,minVersion:Wet,validRange:Yet,outside:Vet,gtr:Ket,ltr:Jet,intersects:zet,simplifyRange:Zet,subset:Xet,SemVer:het,re:zU.re,src:zU.src,tokens:zU.t,SEMVER_SPEC_VERSION:Use.SEMVER_SPEC_VERSION,RELEASE_TYPES:Use.RELEASE_TYPES,compareIdentifiers:Hse.compareIdentifiers,rcompareIdentifiers:Hse.rcompareIdentifiers}});var Gse=L((UWt,qse)=>{\"use strict\";function $et(t,e){function r(){this.constructor=t}r.prototype=e.prototype,t.prototype=new r}function Hd(t,e,r,s){this.message=t,this.expected=e,this.found=r,this.location=s,this.name=\"SyntaxError\",typeof Error.captureStackTrace==\"function\"&&Error.captureStackTrace(this,Hd)}$et(Hd,Error);Hd.buildMessage=function(t,e){var r={literal:function(h){return'\"'+a(h.text)+'\"'},class:function(h){var E=\"\",C;for(C=0;C<h.parts.length;C++)E+=h.parts[C]instanceof Array?n(h.parts[C][0])+\"-\"+n(h.parts[C][1]):n(h.parts[C]);return\"[\"+(h.inverted?\"^\":\"\")+E+\"]\"},any:function(h){return\"any character\"},end:function(h){return\"end of input\"},other:function(h){return h.description}};function s(h){return h.charCodeAt(0).toString(16).toUpperCase()}function a(h){return h.replace(/\\\\/g,\"\\\\\\\\\").replace(/\"/g,'\\\\\"').replace(/\\0/g,\"\\\\0\").replace(/\\t/g,\"\\\\t\").replace(/\\n/g,\"\\\\n\").replace(/\\r/g,\"\\\\r\").replace(/[\\x00-\\x0F]/g,function(E){return\"\\\\x0\"+s(E)}).replace(/[\\x10-\\x1F\\x7F-\\x9F]/g,function(E){return\"\\\\x\"+s(E)})}function n(h){return h.replace(/\\\\/g,\"\\\\\\\\\").replace(/\\]/g,\"\\\\]\").replace(/\\^/g,\"\\\\^\").replace(/-/g,\"\\\\-\").replace(/\\0/g,\"\\\\0\").replace(/\\t/g,\"\\\\t\").replace(/\\n/g,\"\\\\n\").replace(/\\r/g,\"\\\\r\").replace(/[\\x00-\\x0F]/g,function(E){return\"\\\\x0\"+s(E)}).replace(/[\\x10-\\x1F\\x7F-\\x9F]/g,function(E){return\"\\\\x\"+s(E)})}function c(h){return r[h.type](h)}function f(h){var E=new Array(h.length),C,S;for(C=0;C<h.length;C++)E[C]=c(h[C]);if(E.sort(),E.length>0){for(C=1,S=1;C<E.length;C++)E[C-1]!==E[C]&&(E[S]=E[C],S++);E.length=S}switch(E.length){case 1:return E[0];case 2:return E[0]+\" or \"+E[1];default:return E.slice(0,-1).join(\", \")+\", or \"+E[E.length-1]}}function p(h){return h?'\"'+a(h)+'\"':\"end of input\"}return\"Expected \"+f(t)+\" but \"+p(e)+\" found.\"};function ett(t,e){e=e!==void 0?e:{};var r={},s={Expression:y},a=y,n=\"|\",c=Fe(\"|\",!1),f=\"&\",p=Fe(\"&\",!1),h=\"^\",E=Fe(\"^\",!1),C=function($,oe){return!!oe.reduce((xe,Te)=>{switch(Te[1]){case\"|\":return xe|Te[3];case\"&\":return xe&Te[3];case\"^\":return xe^Te[3]}},$)},S=\"!\",P=Fe(\"!\",!1),I=function($){return!$},R=\"(\",N=Fe(\"(\",!1),U=\")\",W=Fe(\")\",!1),te=function($){return $},ie=/^[^ \\t\\n\\r()!|&\\^]/,Ae=Ne([\" \",\"\t\",`\n`,\"\\r\",\"(\",\")\",\"!\",\"|\",\"&\",\"^\"],!0,!1),ce=function($){return e.queryPattern.test($)},me=function($){return e.checkFn($)},pe=ke(\"whitespace\"),Be=/^[ \\t\\n\\r]/,Ce=Ne([\" \",\"\t\",`\n`,\"\\r\"],!1,!1),g=0,we=0,ye=[{line:1,column:1}],fe=0,se=[],X=0,De;if(\"startRule\"in e){if(!(e.startRule in s))throw new Error(`Can't start parsing from rule \"`+e.startRule+'\".');a=s[e.startRule]}function Re(){return t.substring(we,g)}function dt(){return _e(we,g)}function j($,oe){throw oe=oe!==void 0?oe:_e(we,g),b([ke($)],t.substring(we,g),oe)}function rt($,oe){throw oe=oe!==void 0?oe:_e(we,g),w($,oe)}function Fe($,oe){return{type:\"literal\",text:$,ignoreCase:oe}}function Ne($,oe,xe){return{type:\"class\",parts:$,inverted:oe,ignoreCase:xe}}function Pe(){return{type:\"any\"}}function Ye(){return{type:\"end\"}}function ke($){return{type:\"other\",description:$}}function it($){var oe=ye[$],xe;if(oe)return oe;for(xe=$-1;!ye[xe];)xe--;for(oe=ye[xe],oe={line:oe.line,column:oe.column};xe<$;)t.charCodeAt(xe)===10?(oe.line++,oe.column=1):oe.column++,xe++;return ye[$]=oe,oe}function _e($,oe){var xe=it($),Te=it(oe);return{start:{offset:$,line:xe.line,column:xe.column},end:{offset:oe,line:Te.line,column:Te.column}}}function x($){g<fe||(g>fe&&(fe=g,se=[]),se.push($))}function w($,oe){return new Hd($,null,null,oe)}function b($,oe,xe){return new Hd(Hd.buildMessage($,oe),$,oe,xe)}function y(){var $,oe,xe,Te,lt,It,qt,ir;if($=g,oe=F(),oe!==r){for(xe=[],Te=g,lt=Z(),lt!==r?(t.charCodeAt(g)===124?(It=n,g++):(It=r,X===0&&x(c)),It===r&&(t.charCodeAt(g)===38?(It=f,g++):(It=r,X===0&&x(p)),It===r&&(t.charCodeAt(g)===94?(It=h,g++):(It=r,X===0&&x(E)))),It!==r?(qt=Z(),qt!==r?(ir=F(),ir!==r?(lt=[lt,It,qt,ir],Te=lt):(g=Te,Te=r)):(g=Te,Te=r)):(g=Te,Te=r)):(g=Te,Te=r);Te!==r;)xe.push(Te),Te=g,lt=Z(),lt!==r?(t.charCodeAt(g)===124?(It=n,g++):(It=r,X===0&&x(c)),It===r&&(t.charCodeAt(g)===38?(It=f,g++):(It=r,X===0&&x(p)),It===r&&(t.charCodeAt(g)===94?(It=h,g++):(It=r,X===0&&x(E)))),It!==r?(qt=Z(),qt!==r?(ir=F(),ir!==r?(lt=[lt,It,qt,ir],Te=lt):(g=Te,Te=r)):(g=Te,Te=r)):(g=Te,Te=r)):(g=Te,Te=r);xe!==r?(we=$,oe=C(oe,xe),$=oe):(g=$,$=r)}else g=$,$=r;return $}function F(){var $,oe,xe,Te,lt,It;return $=g,t.charCodeAt(g)===33?(oe=S,g++):(oe=r,X===0&&x(P)),oe!==r?(xe=F(),xe!==r?(we=$,oe=I(xe),$=oe):(g=$,$=r)):(g=$,$=r),$===r&&($=g,t.charCodeAt(g)===40?(oe=R,g++):(oe=r,X===0&&x(N)),oe!==r?(xe=Z(),xe!==r?(Te=y(),Te!==r?(lt=Z(),lt!==r?(t.charCodeAt(g)===41?(It=U,g++):(It=r,X===0&&x(W)),It!==r?(we=$,oe=te(Te),$=oe):(g=$,$=r)):(g=$,$=r)):(g=$,$=r)):(g=$,$=r)):(g=$,$=r),$===r&&($=z())),$}function z(){var $,oe,xe,Te,lt;if($=g,oe=Z(),oe!==r){if(xe=g,Te=[],ie.test(t.charAt(g))?(lt=t.charAt(g),g++):(lt=r,X===0&&x(Ae)),lt!==r)for(;lt!==r;)Te.push(lt),ie.test(t.charAt(g))?(lt=t.charAt(g),g++):(lt=r,X===0&&x(Ae));else Te=r;Te!==r?xe=t.substring(xe,g):xe=Te,xe!==r?(we=g,Te=ce(xe),Te?Te=void 0:Te=r,Te!==r?(we=$,oe=me(xe),$=oe):(g=$,$=r)):(g=$,$=r)}else g=$,$=r;return $}function Z(){var $,oe;for(X++,$=[],Be.test(t.charAt(g))?(oe=t.charAt(g),g++):(oe=r,X===0&&x(Ce));oe!==r;)$.push(oe),Be.test(t.charAt(g))?(oe=t.charAt(g),g++):(oe=r,X===0&&x(Ce));return X--,$===r&&(oe=r,X===0&&x(pe)),$}if(De=a(),De!==r&&g===t.length)return De;throw De!==r&&g<t.length&&x(Ye()),b(se,fe<t.length?t.charAt(fe):null,fe<t.length?_e(fe,fe+1):_e(fe,fe))}qse.exports={SyntaxError:Hd,parse:ett}});var Wse=L(uk=>{var{parse:ttt}=Gse();uk.makeParser=(t=/[a-z]+/)=>(e,r)=>ttt(e,{queryPattern:t,checkFn:r});uk.parse=uk.makeParser()});var Vse=L((jWt,Yse)=>{\"use strict\";Yse.exports={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]}});var ZU=L((qWt,Jse)=>{var dB=Vse(),Kse={};for(let t of Object.keys(dB))Kse[dB[t]]=t;var hr={rgb:{channels:3,labels:\"rgb\"},hsl:{channels:3,labels:\"hsl\"},hsv:{channels:3,labels:\"hsv\"},hwb:{channels:3,labels:\"hwb\"},cmyk:{channels:4,labels:\"cmyk\"},xyz:{channels:3,labels:\"xyz\"},lab:{channels:3,labels:\"lab\"},lch:{channels:3,labels:\"lch\"},hex:{channels:1,labels:[\"hex\"]},keyword:{channels:1,labels:[\"keyword\"]},ansi16:{channels:1,labels:[\"ansi16\"]},ansi256:{channels:1,labels:[\"ansi256\"]},hcg:{channels:3,labels:[\"h\",\"c\",\"g\"]},apple:{channels:3,labels:[\"r16\",\"g16\",\"b16\"]},gray:{channels:1,labels:[\"gray\"]}};Jse.exports=hr;for(let t of Object.keys(hr)){if(!(\"channels\"in hr[t]))throw new Error(\"missing channels property: \"+t);if(!(\"labels\"in hr[t]))throw new Error(\"missing channel labels property: \"+t);if(hr[t].labels.length!==hr[t].channels)throw new Error(\"channel and label counts mismatch: \"+t);let{channels:e,labels:r}=hr[t];delete hr[t].channels,delete hr[t].labels,Object.defineProperty(hr[t],\"channels\",{value:e}),Object.defineProperty(hr[t],\"labels\",{value:r})}hr.rgb.hsl=function(t){let e=t[0]/255,r=t[1]/255,s=t[2]/255,a=Math.min(e,r,s),n=Math.max(e,r,s),c=n-a,f,p;n===a?f=0:e===n?f=(r-s)/c:r===n?f=2+(s-e)/c:s===n&&(f=4+(e-r)/c),f=Math.min(f*60,360),f<0&&(f+=360);let h=(a+n)/2;return n===a?p=0:h<=.5?p=c/(n+a):p=c/(2-n-a),[f,p*100,h*100]};hr.rgb.hsv=function(t){let e,r,s,a,n,c=t[0]/255,f=t[1]/255,p=t[2]/255,h=Math.max(c,f,p),E=h-Math.min(c,f,p),C=function(S){return(h-S)/6/E+1/2};return E===0?(a=0,n=0):(n=E/h,e=C(c),r=C(f),s=C(p),c===h?a=s-r:f===h?a=1/3+e-s:p===h&&(a=2/3+r-e),a<0?a+=1:a>1&&(a-=1)),[a*360,n*100,h*100]};hr.rgb.hwb=function(t){let e=t[0],r=t[1],s=t[2],a=hr.rgb.hsl(t)[0],n=1/255*Math.min(e,Math.min(r,s));return s=1-1/255*Math.max(e,Math.max(r,s)),[a,n*100,s*100]};hr.rgb.cmyk=function(t){let e=t[0]/255,r=t[1]/255,s=t[2]/255,a=Math.min(1-e,1-r,1-s),n=(1-e-a)/(1-a)||0,c=(1-r-a)/(1-a)||0,f=(1-s-a)/(1-a)||0;return[n*100,c*100,f*100,a*100]};function rtt(t,e){return(t[0]-e[0])**2+(t[1]-e[1])**2+(t[2]-e[2])**2}hr.rgb.keyword=function(t){let e=Kse[t];if(e)return e;let r=1/0,s;for(let a of Object.keys(dB)){let n=dB[a],c=rtt(t,n);c<r&&(r=c,s=a)}return s};hr.keyword.rgb=function(t){return dB[t]};hr.rgb.xyz=function(t){let e=t[0]/255,r=t[1]/255,s=t[2]/255;e=e>.04045?((e+.055)/1.055)**2.4:e/12.92,r=r>.04045?((r+.055)/1.055)**2.4:r/12.92,s=s>.04045?((s+.055)/1.055)**2.4:s/12.92;let a=e*.4124+r*.3576+s*.1805,n=e*.2126+r*.7152+s*.0722,c=e*.0193+r*.1192+s*.9505;return[a*100,n*100,c*100]};hr.rgb.lab=function(t){let e=hr.rgb.xyz(t),r=e[0],s=e[1],a=e[2];r/=95.047,s/=100,a/=108.883,r=r>.008856?r**(1/3):7.787*r+16/116,s=s>.008856?s**(1/3):7.787*s+16/116,a=a>.008856?a**(1/3):7.787*a+16/116;let n=116*s-16,c=500*(r-s),f=200*(s-a);return[n,c,f]};hr.hsl.rgb=function(t){let e=t[0]/360,r=t[1]/100,s=t[2]/100,a,n,c;if(r===0)return c=s*255,[c,c,c];s<.5?a=s*(1+r):a=s+r-s*r;let f=2*s-a,p=[0,0,0];for(let h=0;h<3;h++)n=e+1/3*-(h-1),n<0&&n++,n>1&&n--,6*n<1?c=f+(a-f)*6*n:2*n<1?c=a:3*n<2?c=f+(a-f)*(2/3-n)*6:c=f,p[h]=c*255;return p};hr.hsl.hsv=function(t){let e=t[0],r=t[1]/100,s=t[2]/100,a=r,n=Math.max(s,.01);s*=2,r*=s<=1?s:2-s,a*=n<=1?n:2-n;let c=(s+r)/2,f=s===0?2*a/(n+a):2*r/(s+r);return[e,f*100,c*100]};hr.hsv.rgb=function(t){let e=t[0]/60,r=t[1]/100,s=t[2]/100,a=Math.floor(e)%6,n=e-Math.floor(e),c=255*s*(1-r),f=255*s*(1-r*n),p=255*s*(1-r*(1-n));switch(s*=255,a){case 0:return[s,p,c];case 1:return[f,s,c];case 2:return[c,s,p];case 3:return[c,f,s];case 4:return[p,c,s];case 5:return[s,c,f]}};hr.hsv.hsl=function(t){let e=t[0],r=t[1]/100,s=t[2]/100,a=Math.max(s,.01),n,c;c=(2-r)*s;let f=(2-r)*a;return n=r*a,n/=f<=1?f:2-f,n=n||0,c/=2,[e,n*100,c*100]};hr.hwb.rgb=function(t){let e=t[0]/360,r=t[1]/100,s=t[2]/100,a=r+s,n;a>1&&(r/=a,s/=a);let c=Math.floor(6*e),f=1-s;n=6*e-c,c&1&&(n=1-n);let p=r+n*(f-r),h,E,C;switch(c){default:case 6:case 0:h=f,E=p,C=r;break;case 1:h=p,E=f,C=r;break;case 2:h=r,E=f,C=p;break;case 3:h=r,E=p,C=f;break;case 4:h=p,E=r,C=f;break;case 5:h=f,E=r,C=p;break}return[h*255,E*255,C*255]};hr.cmyk.rgb=function(t){let e=t[0]/100,r=t[1]/100,s=t[2]/100,a=t[3]/100,n=1-Math.min(1,e*(1-a)+a),c=1-Math.min(1,r*(1-a)+a),f=1-Math.min(1,s*(1-a)+a);return[n*255,c*255,f*255]};hr.xyz.rgb=function(t){let e=t[0]/100,r=t[1]/100,s=t[2]/100,a,n,c;return a=e*3.2406+r*-1.5372+s*-.4986,n=e*-.9689+r*1.8758+s*.0415,c=e*.0557+r*-.204+s*1.057,a=a>.0031308?1.055*a**(1/2.4)-.055:a*12.92,n=n>.0031308?1.055*n**(1/2.4)-.055:n*12.92,c=c>.0031308?1.055*c**(1/2.4)-.055:c*12.92,a=Math.min(Math.max(0,a),1),n=Math.min(Math.max(0,n),1),c=Math.min(Math.max(0,c),1),[a*255,n*255,c*255]};hr.xyz.lab=function(t){let e=t[0],r=t[1],s=t[2];e/=95.047,r/=100,s/=108.883,e=e>.008856?e**(1/3):7.787*e+16/116,r=r>.008856?r**(1/3):7.787*r+16/116,s=s>.008856?s**(1/3):7.787*s+16/116;let a=116*r-16,n=500*(e-r),c=200*(r-s);return[a,n,c]};hr.lab.xyz=function(t){let e=t[0],r=t[1],s=t[2],a,n,c;n=(e+16)/116,a=r/500+n,c=n-s/200;let f=n**3,p=a**3,h=c**3;return n=f>.008856?f:(n-16/116)/7.787,a=p>.008856?p:(a-16/116)/7.787,c=h>.008856?h:(c-16/116)/7.787,a*=95.047,n*=100,c*=108.883,[a,n,c]};hr.lab.lch=function(t){let e=t[0],r=t[1],s=t[2],a;a=Math.atan2(s,r)*360/2/Math.PI,a<0&&(a+=360);let c=Math.sqrt(r*r+s*s);return[e,c,a]};hr.lch.lab=function(t){let e=t[0],r=t[1],a=t[2]/360*2*Math.PI,n=r*Math.cos(a),c=r*Math.sin(a);return[e,n,c]};hr.rgb.ansi16=function(t,e=null){let[r,s,a]=t,n=e===null?hr.rgb.hsv(t)[2]:e;if(n=Math.round(n/50),n===0)return 30;let c=30+(Math.round(a/255)<<2|Math.round(s/255)<<1|Math.round(r/255));return n===2&&(c+=60),c};hr.hsv.ansi16=function(t){return hr.rgb.ansi16(hr.hsv.rgb(t),t[2])};hr.rgb.ansi256=function(t){let e=t[0],r=t[1],s=t[2];return e===r&&r===s?e<8?16:e>248?231:Math.round((e-8)/247*24)+232:16+36*Math.round(e/255*5)+6*Math.round(r/255*5)+Math.round(s/255*5)};hr.ansi16.rgb=function(t){let e=t%10;if(e===0||e===7)return t>50&&(e+=3.5),e=e/10.5*255,[e,e,e];let r=(~~(t>50)+1)*.5,s=(e&1)*r*255,a=(e>>1&1)*r*255,n=(e>>2&1)*r*255;return[s,a,n]};hr.ansi256.rgb=function(t){if(t>=232){let n=(t-232)*10+8;return[n,n,n]}t-=16;let e,r=Math.floor(t/36)/5*255,s=Math.floor((e=t%36)/6)/5*255,a=e%6/5*255;return[r,s,a]};hr.rgb.hex=function(t){let r=(((Math.round(t[0])&255)<<16)+((Math.round(t[1])&255)<<8)+(Math.round(t[2])&255)).toString(16).toUpperCase();return\"000000\".substring(r.length)+r};hr.hex.rgb=function(t){let e=t.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i);if(!e)return[0,0,0];let r=e[0];e[0].length===3&&(r=r.split(\"\").map(f=>f+f).join(\"\"));let s=parseInt(r,16),a=s>>16&255,n=s>>8&255,c=s&255;return[a,n,c]};hr.rgb.hcg=function(t){let e=t[0]/255,r=t[1]/255,s=t[2]/255,a=Math.max(Math.max(e,r),s),n=Math.min(Math.min(e,r),s),c=a-n,f,p;return c<1?f=n/(1-c):f=0,c<=0?p=0:a===e?p=(r-s)/c%6:a===r?p=2+(s-e)/c:p=4+(e-r)/c,p/=6,p%=1,[p*360,c*100,f*100]};hr.hsl.hcg=function(t){let e=t[1]/100,r=t[2]/100,s=r<.5?2*e*r:2*e*(1-r),a=0;return s<1&&(a=(r-.5*s)/(1-s)),[t[0],s*100,a*100]};hr.hsv.hcg=function(t){let e=t[1]/100,r=t[2]/100,s=e*r,a=0;return s<1&&(a=(r-s)/(1-s)),[t[0],s*100,a*100]};hr.hcg.rgb=function(t){let e=t[0]/360,r=t[1]/100,s=t[2]/100;if(r===0)return[s*255,s*255,s*255];let a=[0,0,0],n=e%1*6,c=n%1,f=1-c,p=0;switch(Math.floor(n)){case 0:a[0]=1,a[1]=c,a[2]=0;break;case 1:a[0]=f,a[1]=1,a[2]=0;break;case 2:a[0]=0,a[1]=1,a[2]=c;break;case 3:a[0]=0,a[1]=f,a[2]=1;break;case 4:a[0]=c,a[1]=0,a[2]=1;break;default:a[0]=1,a[1]=0,a[2]=f}return p=(1-r)*s,[(r*a[0]+p)*255,(r*a[1]+p)*255,(r*a[2]+p)*255]};hr.hcg.hsv=function(t){let e=t[1]/100,r=t[2]/100,s=e+r*(1-e),a=0;return s>0&&(a=e/s),[t[0],a*100,s*100]};hr.hcg.hsl=function(t){let e=t[1]/100,s=t[2]/100*(1-e)+.5*e,a=0;return s>0&&s<.5?a=e/(2*s):s>=.5&&s<1&&(a=e/(2*(1-s))),[t[0],a*100,s*100]};hr.hcg.hwb=function(t){let e=t[1]/100,r=t[2]/100,s=e+r*(1-e);return[t[0],(s-e)*100,(1-s)*100]};hr.hwb.hcg=function(t){let e=t[1]/100,s=1-t[2]/100,a=s-e,n=0;return a<1&&(n=(s-a)/(1-a)),[t[0],a*100,n*100]};hr.apple.rgb=function(t){return[t[0]/65535*255,t[1]/65535*255,t[2]/65535*255]};hr.rgb.apple=function(t){return[t[0]/255*65535,t[1]/255*65535,t[2]/255*65535]};hr.gray.rgb=function(t){return[t[0]/100*255,t[0]/100*255,t[0]/100*255]};hr.gray.hsl=function(t){return[0,0,t[0]]};hr.gray.hsv=hr.gray.hsl;hr.gray.hwb=function(t){return[0,100,t[0]]};hr.gray.cmyk=function(t){return[0,0,0,t[0]]};hr.gray.lab=function(t){return[t[0],0,0]};hr.gray.hex=function(t){let e=Math.round(t[0]/100*255)&255,s=((e<<16)+(e<<8)+e).toString(16).toUpperCase();return\"000000\".substring(s.length)+s};hr.rgb.gray=function(t){return[(t[0]+t[1]+t[2])/3/255*100]}});var Zse=L((GWt,zse)=>{var fk=ZU();function ntt(){let t={},e=Object.keys(fk);for(let r=e.length,s=0;s<r;s++)t[e[s]]={distance:-1,parent:null};return t}function itt(t){let e=ntt(),r=[t];for(e[t].distance=0;r.length;){let s=r.pop(),a=Object.keys(fk[s]);for(let n=a.length,c=0;c<n;c++){let f=a[c],p=e[f];p.distance===-1&&(p.distance=e[s].distance+1,p.parent=s,r.unshift(f))}}return e}function stt(t,e){return function(r){return e(t(r))}}function ott(t,e){let r=[e[t].parent,t],s=fk[e[t].parent][t],a=e[t].parent;for(;e[a].parent;)r.unshift(e[a].parent),s=stt(fk[e[a].parent][a],s),a=e[a].parent;return s.conversion=r,s}zse.exports=function(t){let e=itt(t),r={},s=Object.keys(e);for(let a=s.length,n=0;n<a;n++){let c=s[n];e[c].parent!==null&&(r[c]=ott(c,e))}return r}});var $se=L((WWt,Xse)=>{var XU=ZU(),att=Zse(),DE={},ltt=Object.keys(XU);function ctt(t){let e=function(...r){let s=r[0];return s==null?s:(s.length>1&&(r=s),t(r))};return\"conversion\"in t&&(e.conversion=t.conversion),e}function utt(t){let e=function(...r){let s=r[0];if(s==null)return s;s.length>1&&(r=s);let a=t(r);if(typeof a==\"object\")for(let n=a.length,c=0;c<n;c++)a[c]=Math.round(a[c]);return a};return\"conversion\"in t&&(e.conversion=t.conversion),e}ltt.forEach(t=>{DE[t]={},Object.defineProperty(DE[t],\"channels\",{value:XU[t].channels}),Object.defineProperty(DE[t],\"labels\",{value:XU[t].labels});let e=att(t);Object.keys(e).forEach(s=>{let a=e[s];DE[t][s]=utt(a),DE[t][s].raw=ctt(a)})});Xse.exports=DE});var pk=L((YWt,ioe)=>{\"use strict\";var eoe=(t,e)=>(...r)=>`\\x1B[${t(...r)+e}m`,toe=(t,e)=>(...r)=>{let s=t(...r);return`\\x1B[${38+e};5;${s}m`},roe=(t,e)=>(...r)=>{let s=t(...r);return`\\x1B[${38+e};2;${s[0]};${s[1]};${s[2]}m`},Ak=t=>t,noe=(t,e,r)=>[t,e,r],bE=(t,e,r)=>{Object.defineProperty(t,e,{get:()=>{let s=r();return Object.defineProperty(t,e,{value:s,enumerable:!0,configurable:!0}),s},enumerable:!0,configurable:!0})},$U,PE=(t,e,r,s)=>{$U===void 0&&($U=$se());let a=s?10:0,n={};for(let[c,f]of Object.entries($U)){let p=c===\"ansi16\"?\"ansi\":c;c===e?n[p]=t(r,a):typeof f==\"object\"&&(n[p]=t(f[e],a))}return n};function ftt(){let t=new Map,e={modifier:{reset:[0,0],bold:[1,22],dim:[2,22],italic:[3,23],underline:[4,24],inverse:[7,27],hidden:[8,28],strikethrough:[9,29]},color:{black:[30,39],red:[31,39],green:[32,39],yellow:[33,39],blue:[34,39],magenta:[35,39],cyan:[36,39],white:[37,39],blackBright:[90,39],redBright:[91,39],greenBright:[92,39],yellowBright:[93,39],blueBright:[94,39],magentaBright:[95,39],cyanBright:[96,39],whiteBright:[97,39]},bgColor:{bgBlack:[40,49],bgRed:[41,49],bgGreen:[42,49],bgYellow:[43,49],bgBlue:[44,49],bgMagenta:[45,49],bgCyan:[46,49],bgWhite:[47,49],bgBlackBright:[100,49],bgRedBright:[101,49],bgGreenBright:[102,49],bgYellowBright:[103,49],bgBlueBright:[104,49],bgMagentaBright:[105,49],bgCyanBright:[106,49],bgWhiteBright:[107,49]}};e.color.gray=e.color.blackBright,e.bgColor.bgGray=e.bgColor.bgBlackBright,e.color.grey=e.color.blackBright,e.bgColor.bgGrey=e.bgColor.bgBlackBright;for(let[r,s]of Object.entries(e)){for(let[a,n]of Object.entries(s))e[a]={open:`\\x1B[${n[0]}m`,close:`\\x1B[${n[1]}m`},s[a]=e[a],t.set(n[0],n[1]);Object.defineProperty(e,r,{value:s,enumerable:!1})}return Object.defineProperty(e,\"codes\",{value:t,enumerable:!1}),e.color.close=\"\\x1B[39m\",e.bgColor.close=\"\\x1B[49m\",bE(e.color,\"ansi\",()=>PE(eoe,\"ansi16\",Ak,!1)),bE(e.color,\"ansi256\",()=>PE(toe,\"ansi256\",Ak,!1)),bE(e.color,\"ansi16m\",()=>PE(roe,\"rgb\",noe,!1)),bE(e.bgColor,\"ansi\",()=>PE(eoe,\"ansi16\",Ak,!0)),bE(e.bgColor,\"ansi256\",()=>PE(toe,\"ansi256\",Ak,!0)),bE(e.bgColor,\"ansi16m\",()=>PE(roe,\"rgb\",noe,!0)),e}Object.defineProperty(ioe,\"exports\",{enumerable:!0,get:ftt})});var ooe=L((VWt,soe)=>{\"use strict\";soe.exports=(t,e=process.argv)=>{let r=t.startsWith(\"-\")?\"\":t.length===1?\"-\":\"--\",s=e.indexOf(r+t),a=e.indexOf(\"--\");return s!==-1&&(a===-1||s<a)}});var coe=L((KWt,loe)=>{\"use strict\";var Att=Ie(\"os\"),aoe=Ie(\"tty\"),Dc=ooe(),{env:xs}=process,u0;Dc(\"no-color\")||Dc(\"no-colors\")||Dc(\"color=false\")||Dc(\"color=never\")?u0=0:(Dc(\"color\")||Dc(\"colors\")||Dc(\"color=true\")||Dc(\"color=always\"))&&(u0=1);\"FORCE_COLOR\"in xs&&(xs.FORCE_COLOR===\"true\"?u0=1:xs.FORCE_COLOR===\"false\"?u0=0:u0=xs.FORCE_COLOR.length===0?1:Math.min(parseInt(xs.FORCE_COLOR,10),3));function e4(t){return t===0?!1:{level:t,hasBasic:!0,has256:t>=2,has16m:t>=3}}function t4(t,e){if(u0===0)return 0;if(Dc(\"color=16m\")||Dc(\"color=full\")||Dc(\"color=truecolor\"))return 3;if(Dc(\"color=256\"))return 2;if(t&&!e&&u0===void 0)return 0;let r=u0||0;if(xs.TERM===\"dumb\")return r;if(process.platform===\"win32\"){let s=Att.release().split(\".\");return Number(s[0])>=10&&Number(s[2])>=10586?Number(s[2])>=14931?3:2:1}if(\"CI\"in xs)return[\"TRAVIS\",\"CIRCLECI\",\"APPVEYOR\",\"GITLAB_CI\"].some(s=>s in xs)||xs.CI_NAME===\"codeship\"?1:r;if(\"TEAMCITY_VERSION\"in xs)return/^(9\\.(0*[1-9]\\d*)\\.|\\d{2,}\\.)/.test(xs.TEAMCITY_VERSION)?1:0;if(\"GITHUB_ACTIONS\"in xs)return 1;if(xs.COLORTERM===\"truecolor\")return 3;if(\"TERM_PROGRAM\"in xs){let s=parseInt((xs.TERM_PROGRAM_VERSION||\"\").split(\".\")[0],10);switch(xs.TERM_PROGRAM){case\"iTerm.app\":return s>=3?3:2;case\"Apple_Terminal\":return 2}}return/-256(color)?$/i.test(xs.TERM)?2:/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(xs.TERM)||\"COLORTERM\"in xs?1:r}function ptt(t){let e=t4(t,t&&t.isTTY);return e4(e)}loe.exports={supportsColor:ptt,stdout:e4(t4(!0,aoe.isatty(1))),stderr:e4(t4(!0,aoe.isatty(2)))}});var foe=L((JWt,uoe)=>{\"use strict\";var htt=(t,e,r)=>{let s=t.indexOf(e);if(s===-1)return t;let a=e.length,n=0,c=\"\";do c+=t.substr(n,s-n)+e+r,n=s+a,s=t.indexOf(e,n);while(s!==-1);return c+=t.substr(n),c},gtt=(t,e,r,s)=>{let a=0,n=\"\";do{let c=t[s-1]===\"\\r\";n+=t.substr(a,(c?s-1:s)-a)+e+(c?`\\r\n`:`\n`)+r,a=s+1,s=t.indexOf(`\n`,a)}while(s!==-1);return n+=t.substr(a),n};uoe.exports={stringReplaceAll:htt,stringEncaseCRLFWithFirstIndex:gtt}});var doe=L((zWt,goe)=>{\"use strict\";var dtt=/(?:\\\\(u(?:[a-f\\d]{4}|\\{[a-f\\d]{1,6}\\})|x[a-f\\d]{2}|.))|(?:\\{(~)?(\\w+(?:\\([^)]*\\))?(?:\\.\\w+(?:\\([^)]*\\))?)*)(?:[ \\t]|(?=\\r?\\n)))|(\\})|((?:.|[\\r\\n\\f])+?)/gi,Aoe=/(?:^|\\.)(\\w+)(?:\\(([^)]*)\\))?/g,mtt=/^(['\"])((?:\\\\.|(?!\\1)[^\\\\])*)\\1$/,ytt=/\\\\(u(?:[a-f\\d]{4}|{[a-f\\d]{1,6}})|x[a-f\\d]{2}|.)|([^\\\\])/gi,Ett=new Map([[\"n\",`\n`],[\"r\",\"\\r\"],[\"t\",\"\t\"],[\"b\",\"\\b\"],[\"f\",\"\\f\"],[\"v\",\"\\v\"],[\"0\",\"\\0\"],[\"\\\\\",\"\\\\\"],[\"e\",\"\\x1B\"],[\"a\",\"\\x07\"]]);function hoe(t){let e=t[0]===\"u\",r=t[1]===\"{\";return e&&!r&&t.length===5||t[0]===\"x\"&&t.length===3?String.fromCharCode(parseInt(t.slice(1),16)):e&&r?String.fromCodePoint(parseInt(t.slice(2,-1),16)):Ett.get(t)||t}function Itt(t,e){let r=[],s=e.trim().split(/\\s*,\\s*/g),a;for(let n of s){let c=Number(n);if(!Number.isNaN(c))r.push(c);else if(a=n.match(mtt))r.push(a[2].replace(ytt,(f,p,h)=>p?hoe(p):h));else throw new Error(`Invalid Chalk template style argument: ${n} (in style '${t}')`)}return r}function Ctt(t){Aoe.lastIndex=0;let e=[],r;for(;(r=Aoe.exec(t))!==null;){let s=r[1];if(r[2]){let a=Itt(s,r[2]);e.push([s].concat(a))}else e.push([s])}return e}function poe(t,e){let r={};for(let a of e)for(let n of a.styles)r[n[0]]=a.inverse?null:n.slice(1);let s=t;for(let[a,n]of Object.entries(r))if(Array.isArray(n)){if(!(a in s))throw new Error(`Unknown Chalk style: ${a}`);s=n.length>0?s[a](...n):s[a]}return s}goe.exports=(t,e)=>{let r=[],s=[],a=[];if(e.replace(dtt,(n,c,f,p,h,E)=>{if(c)a.push(hoe(c));else if(p){let C=a.join(\"\");a=[],s.push(r.length===0?C:poe(t,r)(C)),r.push({inverse:f,styles:Ctt(p)})}else if(h){if(r.length===0)throw new Error(\"Found extraneous } in Chalk template literal\");s.push(poe(t,r)(a.join(\"\"))),a=[],r.pop()}else a.push(E)}),s.push(a.join(\"\")),r.length>0){let n=`Chalk template literal is missing ${r.length} closing bracket${r.length===1?\"\":\"s\"} (\\`}\\`)`;throw new Error(n)}return s.join(\"\")}});var kE=L((ZWt,woe)=>{\"use strict\";var mB=pk(),{stdout:n4,stderr:i4}=coe(),{stringReplaceAll:wtt,stringEncaseCRLFWithFirstIndex:Btt}=foe(),{isArray:hk}=Array,yoe=[\"ansi\",\"ansi\",\"ansi256\",\"ansi16m\"],xE=Object.create(null),vtt=(t,e={})=>{if(e.level&&!(Number.isInteger(e.level)&&e.level>=0&&e.level<=3))throw new Error(\"The `level` option should be an integer from 0 to 3\");let r=n4?n4.level:0;t.level=e.level===void 0?r:e.level},s4=class{constructor(e){return Eoe(e)}},Eoe=t=>{let e={};return vtt(e,t),e.template=(...r)=>Coe(e.template,...r),Object.setPrototypeOf(e,gk.prototype),Object.setPrototypeOf(e.template,e),e.template.constructor=()=>{throw new Error(\"`chalk.constructor()` is deprecated. Use `new chalk.Instance()` instead.\")},e.template.Instance=s4,e.template};function gk(t){return Eoe(t)}for(let[t,e]of Object.entries(mB))xE[t]={get(){let r=dk(this,o4(e.open,e.close,this._styler),this._isEmpty);return Object.defineProperty(this,t,{value:r}),r}};xE.visible={get(){let t=dk(this,this._styler,!0);return Object.defineProperty(this,\"visible\",{value:t}),t}};var Ioe=[\"rgb\",\"hex\",\"keyword\",\"hsl\",\"hsv\",\"hwb\",\"ansi\",\"ansi256\"];for(let t of Ioe)xE[t]={get(){let{level:e}=this;return function(...r){let s=o4(mB.color[yoe[e]][t](...r),mB.color.close,this._styler);return dk(this,s,this._isEmpty)}}};for(let t of Ioe){let e=\"bg\"+t[0].toUpperCase()+t.slice(1);xE[e]={get(){let{level:r}=this;return function(...s){let a=o4(mB.bgColor[yoe[r]][t](...s),mB.bgColor.close,this._styler);return dk(this,a,this._isEmpty)}}}}var Stt=Object.defineProperties(()=>{},{...xE,level:{enumerable:!0,get(){return this._generator.level},set(t){this._generator.level=t}}}),o4=(t,e,r)=>{let s,a;return r===void 0?(s=t,a=e):(s=r.openAll+t,a=e+r.closeAll),{open:t,close:e,openAll:s,closeAll:a,parent:r}},dk=(t,e,r)=>{let s=(...a)=>hk(a[0])&&hk(a[0].raw)?moe(s,Coe(s,...a)):moe(s,a.length===1?\"\"+a[0]:a.join(\" \"));return Object.setPrototypeOf(s,Stt),s._generator=t,s._styler=e,s._isEmpty=r,s},moe=(t,e)=>{if(t.level<=0||!e)return t._isEmpty?\"\":e;let r=t._styler;if(r===void 0)return e;let{openAll:s,closeAll:a}=r;if(e.indexOf(\"\\x1B\")!==-1)for(;r!==void 0;)e=wtt(e,r.close,r.open),r=r.parent;let n=e.indexOf(`\n`);return n!==-1&&(e=Btt(e,a,s,n)),s+e+a},r4,Coe=(t,...e)=>{let[r]=e;if(!hk(r)||!hk(r.raw))return e.join(\" \");let s=e.slice(1),a=[r.raw[0]];for(let n=1;n<r.length;n++)a.push(String(s[n-1]).replace(/[{}\\\\]/g,\"\\\\$&\"),String(r.raw[n]));return r4===void 0&&(r4=doe()),r4(t,a.join(\"\"))};Object.defineProperties(gk.prototype,xE);var mk=gk();mk.supportsColor=n4;mk.stderr=gk({level:i4?i4.level:0});mk.stderr.supportsColor=i4;woe.exports=mk});var yk=L(bc=>{\"use strict\";bc.isInteger=t=>typeof t==\"number\"?Number.isInteger(t):typeof t==\"string\"&&t.trim()!==\"\"?Number.isInteger(Number(t)):!1;bc.find=(t,e)=>t.nodes.find(r=>r.type===e);bc.exceedsLimit=(t,e,r=1,s)=>s===!1||!bc.isInteger(t)||!bc.isInteger(e)?!1:(Number(e)-Number(t))/Number(r)>=s;bc.escapeNode=(t,e=0,r)=>{let s=t.nodes[e];s&&(r&&s.type===r||s.type===\"open\"||s.type===\"close\")&&s.escaped!==!0&&(s.value=\"\\\\\"+s.value,s.escaped=!0)};bc.encloseBrace=t=>t.type!==\"brace\"||t.commas>>0+t.ranges>>0?!1:(t.invalid=!0,!0);bc.isInvalidBrace=t=>t.type!==\"brace\"?!1:t.invalid===!0||t.dollar?!0:!(t.commas>>0+t.ranges>>0)||t.open!==!0||t.close!==!0?(t.invalid=!0,!0):!1;bc.isOpenOrClose=t=>t.type===\"open\"||t.type===\"close\"?!0:t.open===!0||t.close===!0;bc.reduce=t=>t.reduce((e,r)=>(r.type===\"text\"&&e.push(r.value),r.type===\"range\"&&(r.type=\"text\"),e),[]);bc.flatten=(...t)=>{let e=[],r=s=>{for(let a=0;a<s.length;a++){let n=s[a];Array.isArray(n)?r(n,e):n!==void 0&&e.push(n)}return e};return r(t),e}});var Ek=L(($Wt,voe)=>{\"use strict\";var Boe=yk();voe.exports=(t,e={})=>{let r=(s,a={})=>{let n=e.escapeInvalid&&Boe.isInvalidBrace(a),c=s.invalid===!0&&e.escapeInvalid===!0,f=\"\";if(s.value)return(n||c)&&Boe.isOpenOrClose(s)?\"\\\\\"+s.value:s.value;if(s.value)return s.value;if(s.nodes)for(let p of s.nodes)f+=r(p);return f};return r(t)}});var Doe=L((eYt,Soe)=>{\"use strict\";Soe.exports=function(t){return typeof t==\"number\"?t-t===0:typeof t==\"string\"&&t.trim()!==\"\"?Number.isFinite?Number.isFinite(+t):isFinite(+t):!1}});var Noe=L((tYt,Foe)=>{\"use strict\";var boe=Doe(),jd=(t,e,r)=>{if(boe(t)===!1)throw new TypeError(\"toRegexRange: expected the first argument to be a number\");if(e===void 0||t===e)return String(t);if(boe(e)===!1)throw new TypeError(\"toRegexRange: expected the second argument to be a number.\");let s={relaxZeros:!0,...r};typeof s.strictZeros==\"boolean\"&&(s.relaxZeros=s.strictZeros===!1);let a=String(s.relaxZeros),n=String(s.shorthand),c=String(s.capture),f=String(s.wrap),p=t+\":\"+e+\"=\"+a+n+c+f;if(jd.cache.hasOwnProperty(p))return jd.cache[p].result;let h=Math.min(t,e),E=Math.max(t,e);if(Math.abs(h-E)===1){let R=t+\"|\"+e;return s.capture?`(${R})`:s.wrap===!1?R:`(?:${R})`}let C=Roe(t)||Roe(e),S={min:t,max:e,a:h,b:E},P=[],I=[];if(C&&(S.isPadded=C,S.maxLen=String(S.max).length),h<0){let R=E<0?Math.abs(E):1;I=Poe(R,Math.abs(h),S,s),h=S.a=0}return E>=0&&(P=Poe(h,E,S,s)),S.negatives=I,S.positives=P,S.result=Dtt(I,P,s),s.capture===!0?S.result=`(${S.result})`:s.wrap!==!1&&P.length+I.length>1&&(S.result=`(?:${S.result})`),jd.cache[p]=S,S.result};function Dtt(t,e,r){let s=a4(t,e,\"-\",!1,r)||[],a=a4(e,t,\"\",!1,r)||[],n=a4(t,e,\"-?\",!0,r)||[];return s.concat(n).concat(a).join(\"|\")}function btt(t,e){let r=1,s=1,a=koe(t,r),n=new Set([e]);for(;t<=a&&a<=e;)n.add(a),r+=1,a=koe(t,r);for(a=Qoe(e+1,s)-1;t<a&&a<=e;)n.add(a),s+=1,a=Qoe(e+1,s)-1;return n=[...n],n.sort(ktt),n}function Ptt(t,e,r){if(t===e)return{pattern:t,count:[],digits:0};let s=xtt(t,e),a=s.length,n=\"\",c=0;for(let f=0;f<a;f++){let[p,h]=s[f];p===h?n+=p:p!==\"0\"||h!==\"9\"?n+=Qtt(p,h,r):c++}return c&&(n+=r.shorthand===!0?\"\\\\d\":\"[0-9]\"),{pattern:n,count:[c],digits:a}}function Poe(t,e,r,s){let a=btt(t,e),n=[],c=t,f;for(let p=0;p<a.length;p++){let h=a[p],E=Ptt(String(c),String(h),s),C=\"\";if(!r.isPadded&&f&&f.pattern===E.pattern){f.count.length>1&&f.count.pop(),f.count.push(E.count[0]),f.string=f.pattern+Toe(f.count),c=h+1;continue}r.isPadded&&(C=Ttt(h,r,s)),E.string=C+E.pattern+Toe(E.count),n.push(E),c=h+1,f=E}return n}function a4(t,e,r,s,a){let n=[];for(let c of t){let{string:f}=c;!s&&!xoe(e,\"string\",f)&&n.push(r+f),s&&xoe(e,\"string\",f)&&n.push(r+f)}return n}function xtt(t,e){let r=[];for(let s=0;s<t.length;s++)r.push([t[s],e[s]]);return r}function ktt(t,e){return t>e?1:e>t?-1:0}function xoe(t,e,r){return t.some(s=>s[e]===r)}function koe(t,e){return Number(String(t).slice(0,-e)+\"9\".repeat(e))}function Qoe(t,e){return t-t%Math.pow(10,e)}function Toe(t){let[e=0,r=\"\"]=t;return r||e>1?`{${e+(r?\",\"+r:\"\")}}`:\"\"}function Qtt(t,e,r){return`[${t}${e-t===1?\"\":\"-\"}${e}]`}function Roe(t){return/^-?(0+)\\d/.test(t)}function Ttt(t,e,r){if(!e.isPadded)return t;let s=Math.abs(e.maxLen-String(t).length),a=r.relaxZeros!==!1;switch(s){case 0:return\"\";case 1:return a?\"0?\":\"0\";case 2:return a?\"0{0,2}\":\"00\";default:return a?`0{0,${s}}`:`0{${s}}`}}jd.cache={};jd.clearCache=()=>jd.cache={};Foe.exports=jd});var u4=L((rYt,qoe)=>{\"use strict\";var Rtt=Ie(\"util\"),Moe=Noe(),Ooe=t=>t!==null&&typeof t==\"object\"&&!Array.isArray(t),Ftt=t=>e=>t===!0?Number(e):String(e),l4=t=>typeof t==\"number\"||typeof t==\"string\"&&t!==\"\",yB=t=>Number.isInteger(+t),c4=t=>{let e=`${t}`,r=-1;if(e[0]===\"-\"&&(e=e.slice(1)),e===\"0\")return!1;for(;e[++r]===\"0\";);return r>0},Ntt=(t,e,r)=>typeof t==\"string\"||typeof e==\"string\"?!0:r.stringify===!0,Ott=(t,e,r)=>{if(e>0){let s=t[0]===\"-\"?\"-\":\"\";s&&(t=t.slice(1)),t=s+t.padStart(s?e-1:e,\"0\")}return r===!1?String(t):t},Loe=(t,e)=>{let r=t[0]===\"-\"?\"-\":\"\";for(r&&(t=t.slice(1),e--);t.length<e;)t=\"0\"+t;return r?\"-\"+t:t},Ltt=(t,e)=>{t.negatives.sort((c,f)=>c<f?-1:c>f?1:0),t.positives.sort((c,f)=>c<f?-1:c>f?1:0);let r=e.capture?\"\":\"?:\",s=\"\",a=\"\",n;return t.positives.length&&(s=t.positives.join(\"|\")),t.negatives.length&&(a=`-(${r}${t.negatives.join(\"|\")})`),s&&a?n=`${s}|${a}`:n=s||a,e.wrap?`(${r}${n})`:n},_oe=(t,e,r,s)=>{if(r)return Moe(t,e,{wrap:!1,...s});let a=String.fromCharCode(t);if(t===e)return a;let n=String.fromCharCode(e);return`[${a}-${n}]`},Uoe=(t,e,r)=>{if(Array.isArray(t)){let s=r.wrap===!0,a=r.capture?\"\":\"?:\";return s?`(${a}${t.join(\"|\")})`:t.join(\"|\")}return Moe(t,e,r)},Hoe=(...t)=>new RangeError(\"Invalid range arguments: \"+Rtt.inspect(...t)),joe=(t,e,r)=>{if(r.strictRanges===!0)throw Hoe([t,e]);return[]},Mtt=(t,e)=>{if(e.strictRanges===!0)throw new TypeError(`Expected step \"${t}\" to be a number`);return[]},_tt=(t,e,r=1,s={})=>{let a=Number(t),n=Number(e);if(!Number.isInteger(a)||!Number.isInteger(n)){if(s.strictRanges===!0)throw Hoe([t,e]);return[]}a===0&&(a=0),n===0&&(n=0);let c=a>n,f=String(t),p=String(e),h=String(r);r=Math.max(Math.abs(r),1);let E=c4(f)||c4(p)||c4(h),C=E?Math.max(f.length,p.length,h.length):0,S=E===!1&&Ntt(t,e,s)===!1,P=s.transform||Ftt(S);if(s.toRegex&&r===1)return _oe(Loe(t,C),Loe(e,C),!0,s);let I={negatives:[],positives:[]},R=W=>I[W<0?\"negatives\":\"positives\"].push(Math.abs(W)),N=[],U=0;for(;c?a>=n:a<=n;)s.toRegex===!0&&r>1?R(a):N.push(Ott(P(a,U),C,S)),a=c?a-r:a+r,U++;return s.toRegex===!0?r>1?Ltt(I,s):Uoe(N,null,{wrap:!1,...s}):N},Utt=(t,e,r=1,s={})=>{if(!yB(t)&&t.length>1||!yB(e)&&e.length>1)return joe(t,e,s);let a=s.transform||(S=>String.fromCharCode(S)),n=`${t}`.charCodeAt(0),c=`${e}`.charCodeAt(0),f=n>c,p=Math.min(n,c),h=Math.max(n,c);if(s.toRegex&&r===1)return _oe(p,h,!1,s);let E=[],C=0;for(;f?n>=c:n<=c;)E.push(a(n,C)),n=f?n-r:n+r,C++;return s.toRegex===!0?Uoe(E,null,{wrap:!1,options:s}):E},Ik=(t,e,r,s={})=>{if(e==null&&l4(t))return[t];if(!l4(t)||!l4(e))return joe(t,e,s);if(typeof r==\"function\")return Ik(t,e,1,{transform:r});if(Ooe(r))return Ik(t,e,0,r);let a={...s};return a.capture===!0&&(a.wrap=!0),r=r||a.step||1,yB(r)?yB(t)&&yB(e)?_tt(t,e,r,a):Utt(t,e,Math.max(Math.abs(r),1),a):r!=null&&!Ooe(r)?Mtt(r,a):Ik(t,e,1,r)};qoe.exports=Ik});var Yoe=L((nYt,Woe)=>{\"use strict\";var Htt=u4(),Goe=yk(),jtt=(t,e={})=>{let r=(s,a={})=>{let n=Goe.isInvalidBrace(a),c=s.invalid===!0&&e.escapeInvalid===!0,f=n===!0||c===!0,p=e.escapeInvalid===!0?\"\\\\\":\"\",h=\"\";if(s.isOpen===!0||s.isClose===!0)return p+s.value;if(s.type===\"open\")return f?p+s.value:\"(\";if(s.type===\"close\")return f?p+s.value:\")\";if(s.type===\"comma\")return s.prev.type===\"comma\"?\"\":f?s.value:\"|\";if(s.value)return s.value;if(s.nodes&&s.ranges>0){let E=Goe.reduce(s.nodes),C=Htt(...E,{...e,wrap:!1,toRegex:!0});if(C.length!==0)return E.length>1&&C.length>1?`(${C})`:C}if(s.nodes)for(let E of s.nodes)h+=r(E,s);return h};return r(t)};Woe.exports=jtt});var Joe=L((iYt,Koe)=>{\"use strict\";var qtt=u4(),Voe=Ek(),QE=yk(),qd=(t=\"\",e=\"\",r=!1)=>{let s=[];if(t=[].concat(t),e=[].concat(e),!e.length)return t;if(!t.length)return r?QE.flatten(e).map(a=>`{${a}}`):e;for(let a of t)if(Array.isArray(a))for(let n of a)s.push(qd(n,e,r));else for(let n of e)r===!0&&typeof n==\"string\"&&(n=`{${n}}`),s.push(Array.isArray(n)?qd(a,n,r):a+n);return QE.flatten(s)},Gtt=(t,e={})=>{let r=e.rangeLimit===void 0?1e3:e.rangeLimit,s=(a,n={})=>{a.queue=[];let c=n,f=n.queue;for(;c.type!==\"brace\"&&c.type!==\"root\"&&c.parent;)c=c.parent,f=c.queue;if(a.invalid||a.dollar){f.push(qd(f.pop(),Voe(a,e)));return}if(a.type===\"brace\"&&a.invalid!==!0&&a.nodes.length===2){f.push(qd(f.pop(),[\"{}\"]));return}if(a.nodes&&a.ranges>0){let C=QE.reduce(a.nodes);if(QE.exceedsLimit(...C,e.step,r))throw new RangeError(\"expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.\");let S=qtt(...C,e);S.length===0&&(S=Voe(a,e)),f.push(qd(f.pop(),S)),a.nodes=[];return}let p=QE.encloseBrace(a),h=a.queue,E=a;for(;E.type!==\"brace\"&&E.type!==\"root\"&&E.parent;)E=E.parent,h=E.queue;for(let C=0;C<a.nodes.length;C++){let S=a.nodes[C];if(S.type===\"comma\"&&a.type===\"brace\"){C===1&&h.push(\"\"),h.push(\"\");continue}if(S.type===\"close\"){f.push(qd(f.pop(),h,p));continue}if(S.value&&S.type!==\"open\"){h.push(qd(h.pop(),S.value));continue}S.nodes&&s(S,a)}return h};return QE.flatten(s(t))};Koe.exports=Gtt});var Zoe=L((sYt,zoe)=>{\"use strict\";zoe.exports={MAX_LENGTH:1024*64,CHAR_0:\"0\",CHAR_9:\"9\",CHAR_UPPERCASE_A:\"A\",CHAR_LOWERCASE_A:\"a\",CHAR_UPPERCASE_Z:\"Z\",CHAR_LOWERCASE_Z:\"z\",CHAR_LEFT_PARENTHESES:\"(\",CHAR_RIGHT_PARENTHESES:\")\",CHAR_ASTERISK:\"*\",CHAR_AMPERSAND:\"&\",CHAR_AT:\"@\",CHAR_BACKSLASH:\"\\\\\",CHAR_BACKTICK:\"`\",CHAR_CARRIAGE_RETURN:\"\\r\",CHAR_CIRCUMFLEX_ACCENT:\"^\",CHAR_COLON:\":\",CHAR_COMMA:\",\",CHAR_DOLLAR:\"$\",CHAR_DOT:\".\",CHAR_DOUBLE_QUOTE:'\"',CHAR_EQUAL:\"=\",CHAR_EXCLAMATION_MARK:\"!\",CHAR_FORM_FEED:\"\\f\",CHAR_FORWARD_SLASH:\"/\",CHAR_HASH:\"#\",CHAR_HYPHEN_MINUS:\"-\",CHAR_LEFT_ANGLE_BRACKET:\"<\",CHAR_LEFT_CURLY_BRACE:\"{\",CHAR_LEFT_SQUARE_BRACKET:\"[\",CHAR_LINE_FEED:`\n`,CHAR_NO_BREAK_SPACE:\"\\xA0\",CHAR_PERCENT:\"%\",CHAR_PLUS:\"+\",CHAR_QUESTION_MARK:\"?\",CHAR_RIGHT_ANGLE_BRACKET:\">\",CHAR_RIGHT_CURLY_BRACE:\"}\",CHAR_RIGHT_SQUARE_BRACKET:\"]\",CHAR_SEMICOLON:\";\",CHAR_SINGLE_QUOTE:\"'\",CHAR_SPACE:\" \",CHAR_TAB:\"\t\",CHAR_UNDERSCORE:\"_\",CHAR_VERTICAL_LINE:\"|\",CHAR_ZERO_WIDTH_NOBREAK_SPACE:\"\\uFEFF\"}});var rae=L((oYt,tae)=>{\"use strict\";var Wtt=Ek(),{MAX_LENGTH:Xoe,CHAR_BACKSLASH:f4,CHAR_BACKTICK:Ytt,CHAR_COMMA:Vtt,CHAR_DOT:Ktt,CHAR_LEFT_PARENTHESES:Jtt,CHAR_RIGHT_PARENTHESES:ztt,CHAR_LEFT_CURLY_BRACE:Ztt,CHAR_RIGHT_CURLY_BRACE:Xtt,CHAR_LEFT_SQUARE_BRACKET:$oe,CHAR_RIGHT_SQUARE_BRACKET:eae,CHAR_DOUBLE_QUOTE:$tt,CHAR_SINGLE_QUOTE:ert,CHAR_NO_BREAK_SPACE:trt,CHAR_ZERO_WIDTH_NOBREAK_SPACE:rrt}=Zoe(),nrt=(t,e={})=>{if(typeof t!=\"string\")throw new TypeError(\"Expected a string\");let r=e||{},s=typeof r.maxLength==\"number\"?Math.min(Xoe,r.maxLength):Xoe;if(t.length>s)throw new SyntaxError(`Input length (${t.length}), exceeds max characters (${s})`);let a={type:\"root\",input:t,nodes:[]},n=[a],c=a,f=a,p=0,h=t.length,E=0,C=0,S,P={},I=()=>t[E++],R=N=>{if(N.type===\"text\"&&f.type===\"dot\"&&(f.type=\"text\"),f&&f.type===\"text\"&&N.type===\"text\"){f.value+=N.value;return}return c.nodes.push(N),N.parent=c,N.prev=f,f=N,N};for(R({type:\"bos\"});E<h;)if(c=n[n.length-1],S=I(),!(S===rrt||S===trt)){if(S===f4){R({type:\"text\",value:(e.keepEscaping?S:\"\")+I()});continue}if(S===eae){R({type:\"text\",value:\"\\\\\"+S});continue}if(S===$oe){p++;let N=!0,U;for(;E<h&&(U=I());){if(S+=U,U===$oe){p++;continue}if(U===f4){S+=I();continue}if(U===eae&&(p--,p===0))break}R({type:\"text\",value:S});continue}if(S===Jtt){c=R({type:\"paren\",nodes:[]}),n.push(c),R({type:\"text\",value:S});continue}if(S===ztt){if(c.type!==\"paren\"){R({type:\"text\",value:S});continue}c=n.pop(),R({type:\"text\",value:S}),c=n[n.length-1];continue}if(S===$tt||S===ert||S===Ytt){let N=S,U;for(e.keepQuotes!==!0&&(S=\"\");E<h&&(U=I());){if(U===f4){S+=U+I();continue}if(U===N){e.keepQuotes===!0&&(S+=U);break}S+=U}R({type:\"text\",value:S});continue}if(S===Ztt){C++;let U={type:\"brace\",open:!0,close:!1,dollar:f.value&&f.value.slice(-1)===\"$\"||c.dollar===!0,depth:C,commas:0,ranges:0,nodes:[]};c=R(U),n.push(c),R({type:\"open\",value:S});continue}if(S===Xtt){if(c.type!==\"brace\"){R({type:\"text\",value:S});continue}let N=\"close\";c=n.pop(),c.close=!0,R({type:N,value:S}),C--,c=n[n.length-1];continue}if(S===Vtt&&C>0){if(c.ranges>0){c.ranges=0;let N=c.nodes.shift();c.nodes=[N,{type:\"text\",value:Wtt(c)}]}R({type:\"comma\",value:S}),c.commas++;continue}if(S===Ktt&&C>0&&c.commas===0){let N=c.nodes;if(C===0||N.length===0){R({type:\"text\",value:S});continue}if(f.type===\"dot\"){if(c.range=[],f.value+=S,f.type=\"range\",c.nodes.length!==3&&c.nodes.length!==5){c.invalid=!0,c.ranges=0,f.type=\"text\";continue}c.ranges++,c.args=[];continue}if(f.type===\"range\"){N.pop();let U=N[N.length-1];U.value+=f.value+S,f=U,c.ranges--;continue}R({type:\"dot\",value:S});continue}R({type:\"text\",value:S})}do if(c=n.pop(),c.type!==\"root\"){c.nodes.forEach(W=>{W.nodes||(W.type===\"open\"&&(W.isOpen=!0),W.type===\"close\"&&(W.isClose=!0),W.nodes||(W.type=\"text\"),W.invalid=!0)});let N=n[n.length-1],U=N.nodes.indexOf(c);N.nodes.splice(U,1,...c.nodes)}while(n.length>0);return R({type:\"eos\"}),a};tae.exports=nrt});var sae=L((aYt,iae)=>{\"use strict\";var nae=Ek(),irt=Yoe(),srt=Joe(),ort=rae(),ql=(t,e={})=>{let r=[];if(Array.isArray(t))for(let s of t){let a=ql.create(s,e);Array.isArray(a)?r.push(...a):r.push(a)}else r=[].concat(ql.create(t,e));return e&&e.expand===!0&&e.nodupes===!0&&(r=[...new Set(r)]),r};ql.parse=(t,e={})=>ort(t,e);ql.stringify=(t,e={})=>nae(typeof t==\"string\"?ql.parse(t,e):t,e);ql.compile=(t,e={})=>(typeof t==\"string\"&&(t=ql.parse(t,e)),irt(t,e));ql.expand=(t,e={})=>{typeof t==\"string\"&&(t=ql.parse(t,e));let r=srt(t,e);return e.noempty===!0&&(r=r.filter(Boolean)),e.nodupes===!0&&(r=[...new Set(r)]),r};ql.create=(t,e={})=>t===\"\"||t.length<3?[t]:e.expand!==!0?ql.compile(t,e):ql.expand(t,e);iae.exports=ql});var EB=L((lYt,uae)=>{\"use strict\";var art=Ie(\"path\"),Kf=\"\\\\\\\\/\",oae=`[^${Kf}]`,Pp=\"\\\\.\",lrt=\"\\\\+\",crt=\"\\\\?\",Ck=\"\\\\/\",urt=\"(?=.)\",aae=\"[^/]\",A4=`(?:${Ck}|$)`,lae=`(?:^|${Ck})`,p4=`${Pp}{1,2}${A4}`,frt=`(?!${Pp})`,Art=`(?!${lae}${p4})`,prt=`(?!${Pp}{0,1}${A4})`,hrt=`(?!${p4})`,grt=`[^.${Ck}]`,drt=`${aae}*?`,cae={DOT_LITERAL:Pp,PLUS_LITERAL:lrt,QMARK_LITERAL:crt,SLASH_LITERAL:Ck,ONE_CHAR:urt,QMARK:aae,END_ANCHOR:A4,DOTS_SLASH:p4,NO_DOT:frt,NO_DOTS:Art,NO_DOT_SLASH:prt,NO_DOTS_SLASH:hrt,QMARK_NO_DOT:grt,STAR:drt,START_ANCHOR:lae},mrt={...cae,SLASH_LITERAL:`[${Kf}]`,QMARK:oae,STAR:`${oae}*?`,DOTS_SLASH:`${Pp}{1,2}(?:[${Kf}]|$)`,NO_DOT:`(?!${Pp})`,NO_DOTS:`(?!(?:^|[${Kf}])${Pp}{1,2}(?:[${Kf}]|$))`,NO_DOT_SLASH:`(?!${Pp}{0,1}(?:[${Kf}]|$))`,NO_DOTS_SLASH:`(?!${Pp}{1,2}(?:[${Kf}]|$))`,QMARK_NO_DOT:`[^.${Kf}]`,START_ANCHOR:`(?:^|[${Kf}])`,END_ANCHOR:`(?:[${Kf}]|$)`},yrt={alnum:\"a-zA-Z0-9\",alpha:\"a-zA-Z\",ascii:\"\\\\x00-\\\\x7F\",blank:\" \\\\t\",cntrl:\"\\\\x00-\\\\x1F\\\\x7F\",digit:\"0-9\",graph:\"\\\\x21-\\\\x7E\",lower:\"a-z\",print:\"\\\\x20-\\\\x7E \",punct:\"\\\\-!\\\"#$%&'()\\\\*+,./:;<=>?@[\\\\]^_`{|}~\",space:\" \\\\t\\\\r\\\\n\\\\v\\\\f\",upper:\"A-Z\",word:\"A-Za-z0-9_\",xdigit:\"A-Fa-f0-9\"};uae.exports={MAX_LENGTH:1024*64,POSIX_REGEX_SOURCE:yrt,REGEX_BACKSLASH:/\\\\(?![*+?^${}(|)[\\]])/g,REGEX_NON_SPECIAL_CHARS:/^[^@![\\].,$*+?^{}()|\\\\/]+/,REGEX_SPECIAL_CHARS:/[-*+?.^${}(|)[\\]]/,REGEX_SPECIAL_CHARS_BACKREF:/(\\\\?)((\\W)(\\3*))/g,REGEX_SPECIAL_CHARS_GLOBAL:/([-*+?.^${}(|)[\\]])/g,REGEX_REMOVE_BACKSLASH:/(?:\\[.*?[^\\\\]\\]|\\\\(?=.))/g,REPLACEMENTS:{\"***\":\"*\",\"**/**\":\"**\",\"**/**/**\":\"**\"},CHAR_0:48,CHAR_9:57,CHAR_UPPERCASE_A:65,CHAR_LOWERCASE_A:97,CHAR_UPPERCASE_Z:90,CHAR_LOWERCASE_Z:122,CHAR_LEFT_PARENTHESES:40,CHAR_RIGHT_PARENTHESES:41,CHAR_ASTERISK:42,CHAR_AMPERSAND:38,CHAR_AT:64,CHAR_BACKWARD_SLASH:92,CHAR_CARRIAGE_RETURN:13,CHAR_CIRCUMFLEX_ACCENT:94,CHAR_COLON:58,CHAR_COMMA:44,CHAR_DOT:46,CHAR_DOUBLE_QUOTE:34,CHAR_EQUAL:61,CHAR_EXCLAMATION_MARK:33,CHAR_FORM_FEED:12,CHAR_FORWARD_SLASH:47,CHAR_GRAVE_ACCENT:96,CHAR_HASH:35,CHAR_HYPHEN_MINUS:45,CHAR_LEFT_ANGLE_BRACKET:60,CHAR_LEFT_CURLY_BRACE:123,CHAR_LEFT_SQUARE_BRACKET:91,CHAR_LINE_FEED:10,CHAR_NO_BREAK_SPACE:160,CHAR_PERCENT:37,CHAR_PLUS:43,CHAR_QUESTION_MARK:63,CHAR_RIGHT_ANGLE_BRACKET:62,CHAR_RIGHT_CURLY_BRACE:125,CHAR_RIGHT_SQUARE_BRACKET:93,CHAR_SEMICOLON:59,CHAR_SINGLE_QUOTE:39,CHAR_SPACE:32,CHAR_TAB:9,CHAR_UNDERSCORE:95,CHAR_VERTICAL_LINE:124,CHAR_ZERO_WIDTH_NOBREAK_SPACE:65279,SEP:art.sep,extglobChars(t){return{\"!\":{type:\"negate\",open:\"(?:(?!(?:\",close:`))${t.STAR})`},\"?\":{type:\"qmark\",open:\"(?:\",close:\")?\"},\"+\":{type:\"plus\",open:\"(?:\",close:\")+\"},\"*\":{type:\"star\",open:\"(?:\",close:\")*\"},\"@\":{type:\"at\",open:\"(?:\",close:\")\"}}},globChars(t){return t===!0?mrt:cae}}});var IB=L(al=>{\"use strict\";var Ert=Ie(\"path\"),Irt=process.platform===\"win32\",{REGEX_BACKSLASH:Crt,REGEX_REMOVE_BACKSLASH:wrt,REGEX_SPECIAL_CHARS:Brt,REGEX_SPECIAL_CHARS_GLOBAL:vrt}=EB();al.isObject=t=>t!==null&&typeof t==\"object\"&&!Array.isArray(t);al.hasRegexChars=t=>Brt.test(t);al.isRegexChar=t=>t.length===1&&al.hasRegexChars(t);al.escapeRegex=t=>t.replace(vrt,\"\\\\$1\");al.toPosixSlashes=t=>t.replace(Crt,\"/\");al.removeBackslashes=t=>t.replace(wrt,e=>e===\"\\\\\"?\"\":e);al.supportsLookbehinds=()=>{let t=process.version.slice(1).split(\".\").map(Number);return t.length===3&&t[0]>=9||t[0]===8&&t[1]>=10};al.isWindows=t=>t&&typeof t.windows==\"boolean\"?t.windows:Irt===!0||Ert.sep===\"\\\\\";al.escapeLast=(t,e,r)=>{let s=t.lastIndexOf(e,r);return s===-1?t:t[s-1]===\"\\\\\"?al.escapeLast(t,e,s-1):`${t.slice(0,s)}\\\\${t.slice(s)}`};al.removePrefix=(t,e={})=>{let r=t;return r.startsWith(\"./\")&&(r=r.slice(2),e.prefix=\"./\"),r};al.wrapOutput=(t,e={},r={})=>{let s=r.contains?\"\":\"^\",a=r.contains?\"\":\"$\",n=`${s}(?:${t})${a}`;return e.negated===!0&&(n=`(?:^(?!${n}).*$)`),n}});var yae=L((uYt,mae)=>{\"use strict\";var fae=IB(),{CHAR_ASTERISK:h4,CHAR_AT:Srt,CHAR_BACKWARD_SLASH:CB,CHAR_COMMA:Drt,CHAR_DOT:g4,CHAR_EXCLAMATION_MARK:d4,CHAR_FORWARD_SLASH:dae,CHAR_LEFT_CURLY_BRACE:m4,CHAR_LEFT_PARENTHESES:y4,CHAR_LEFT_SQUARE_BRACKET:brt,CHAR_PLUS:Prt,CHAR_QUESTION_MARK:Aae,CHAR_RIGHT_CURLY_BRACE:xrt,CHAR_RIGHT_PARENTHESES:pae,CHAR_RIGHT_SQUARE_BRACKET:krt}=EB(),hae=t=>t===dae||t===CB,gae=t=>{t.isPrefix!==!0&&(t.depth=t.isGlobstar?1/0:1)},Qrt=(t,e)=>{let r=e||{},s=t.length-1,a=r.parts===!0||r.scanToEnd===!0,n=[],c=[],f=[],p=t,h=-1,E=0,C=0,S=!1,P=!1,I=!1,R=!1,N=!1,U=!1,W=!1,te=!1,ie=!1,Ae=!1,ce=0,me,pe,Be={value:\"\",depth:0,isGlob:!1},Ce=()=>h>=s,g=()=>p.charCodeAt(h+1),we=()=>(me=pe,p.charCodeAt(++h));for(;h<s;){pe=we();let De;if(pe===CB){W=Be.backslashes=!0,pe=we(),pe===m4&&(U=!0);continue}if(U===!0||pe===m4){for(ce++;Ce()!==!0&&(pe=we());){if(pe===CB){W=Be.backslashes=!0,we();continue}if(pe===m4){ce++;continue}if(U!==!0&&pe===g4&&(pe=we())===g4){if(S=Be.isBrace=!0,I=Be.isGlob=!0,Ae=!0,a===!0)continue;break}if(U!==!0&&pe===Drt){if(S=Be.isBrace=!0,I=Be.isGlob=!0,Ae=!0,a===!0)continue;break}if(pe===xrt&&(ce--,ce===0)){U=!1,S=Be.isBrace=!0,Ae=!0;break}}if(a===!0)continue;break}if(pe===dae){if(n.push(h),c.push(Be),Be={value:\"\",depth:0,isGlob:!1},Ae===!0)continue;if(me===g4&&h===E+1){E+=2;continue}C=h+1;continue}if(r.noext!==!0&&(pe===Prt||pe===Srt||pe===h4||pe===Aae||pe===d4)===!0&&g()===y4){if(I=Be.isGlob=!0,R=Be.isExtglob=!0,Ae=!0,pe===d4&&h===E&&(ie=!0),a===!0){for(;Ce()!==!0&&(pe=we());){if(pe===CB){W=Be.backslashes=!0,pe=we();continue}if(pe===pae){I=Be.isGlob=!0,Ae=!0;break}}continue}break}if(pe===h4){if(me===h4&&(N=Be.isGlobstar=!0),I=Be.isGlob=!0,Ae=!0,a===!0)continue;break}if(pe===Aae){if(I=Be.isGlob=!0,Ae=!0,a===!0)continue;break}if(pe===brt){for(;Ce()!==!0&&(De=we());){if(De===CB){W=Be.backslashes=!0,we();continue}if(De===krt){P=Be.isBracket=!0,I=Be.isGlob=!0,Ae=!0;break}}if(a===!0)continue;break}if(r.nonegate!==!0&&pe===d4&&h===E){te=Be.negated=!0,E++;continue}if(r.noparen!==!0&&pe===y4){if(I=Be.isGlob=!0,a===!0){for(;Ce()!==!0&&(pe=we());){if(pe===y4){W=Be.backslashes=!0,pe=we();continue}if(pe===pae){Ae=!0;break}}continue}break}if(I===!0){if(Ae=!0,a===!0)continue;break}}r.noext===!0&&(R=!1,I=!1);let ye=p,fe=\"\",se=\"\";E>0&&(fe=p.slice(0,E),p=p.slice(E),C-=E),ye&&I===!0&&C>0?(ye=p.slice(0,C),se=p.slice(C)):I===!0?(ye=\"\",se=p):ye=p,ye&&ye!==\"\"&&ye!==\"/\"&&ye!==p&&hae(ye.charCodeAt(ye.length-1))&&(ye=ye.slice(0,-1)),r.unescape===!0&&(se&&(se=fae.removeBackslashes(se)),ye&&W===!0&&(ye=fae.removeBackslashes(ye)));let X={prefix:fe,input:t,start:E,base:ye,glob:se,isBrace:S,isBracket:P,isGlob:I,isExtglob:R,isGlobstar:N,negated:te,negatedExtglob:ie};if(r.tokens===!0&&(X.maxDepth=0,hae(pe)||c.push(Be),X.tokens=c),r.parts===!0||r.tokens===!0){let De;for(let Re=0;Re<n.length;Re++){let dt=De?De+1:E,j=n[Re],rt=t.slice(dt,j);r.tokens&&(Re===0&&E!==0?(c[Re].isPrefix=!0,c[Re].value=fe):c[Re].value=rt,gae(c[Re]),X.maxDepth+=c[Re].depth),(Re!==0||rt!==\"\")&&f.push(rt),De=j}if(De&&De+1<t.length){let Re=t.slice(De+1);f.push(Re),r.tokens&&(c[c.length-1].value=Re,gae(c[c.length-1]),X.maxDepth+=c[c.length-1].depth)}X.slashes=n,X.parts=f}return X};mae.exports=Qrt});var Cae=L((fYt,Iae)=>{\"use strict\";var wk=EB(),Gl=IB(),{MAX_LENGTH:Bk,POSIX_REGEX_SOURCE:Trt,REGEX_NON_SPECIAL_CHARS:Rrt,REGEX_SPECIAL_CHARS_BACKREF:Frt,REPLACEMENTS:Eae}=wk,Nrt=(t,e)=>{if(typeof e.expandRange==\"function\")return e.expandRange(...t,e);t.sort();let r=`[${t.join(\"-\")}]`;try{new RegExp(r)}catch{return t.map(a=>Gl.escapeRegex(a)).join(\"..\")}return r},TE=(t,e)=>`Missing ${t}: \"${e}\" - use \"\\\\\\\\${e}\" to match literal characters`,E4=(t,e)=>{if(typeof t!=\"string\")throw new TypeError(\"Expected a string\");t=Eae[t]||t;let r={...e},s=typeof r.maxLength==\"number\"?Math.min(Bk,r.maxLength):Bk,a=t.length;if(a>s)throw new SyntaxError(`Input length: ${a}, exceeds maximum allowed length: ${s}`);let n={type:\"bos\",value:\"\",output:r.prepend||\"\"},c=[n],f=r.capture?\"\":\"?:\",p=Gl.isWindows(e),h=wk.globChars(p),E=wk.extglobChars(h),{DOT_LITERAL:C,PLUS_LITERAL:S,SLASH_LITERAL:P,ONE_CHAR:I,DOTS_SLASH:R,NO_DOT:N,NO_DOT_SLASH:U,NO_DOTS_SLASH:W,QMARK:te,QMARK_NO_DOT:ie,STAR:Ae,START_ANCHOR:ce}=h,me=x=>`(${f}(?:(?!${ce}${x.dot?R:C}).)*?)`,pe=r.dot?\"\":N,Be=r.dot?te:ie,Ce=r.bash===!0?me(r):Ae;r.capture&&(Ce=`(${Ce})`),typeof r.noext==\"boolean\"&&(r.noextglob=r.noext);let g={input:t,index:-1,start:0,dot:r.dot===!0,consumed:\"\",output:\"\",prefix:\"\",backtrack:!1,negated:!1,brackets:0,braces:0,parens:0,quotes:0,globstar:!1,tokens:c};t=Gl.removePrefix(t,g),a=t.length;let we=[],ye=[],fe=[],se=n,X,De=()=>g.index===a-1,Re=g.peek=(x=1)=>t[g.index+x],dt=g.advance=()=>t[++g.index]||\"\",j=()=>t.slice(g.index+1),rt=(x=\"\",w=0)=>{g.consumed+=x,g.index+=w},Fe=x=>{g.output+=x.output!=null?x.output:x.value,rt(x.value)},Ne=()=>{let x=1;for(;Re()===\"!\"&&(Re(2)!==\"(\"||Re(3)===\"?\");)dt(),g.start++,x++;return x%2===0?!1:(g.negated=!0,g.start++,!0)},Pe=x=>{g[x]++,fe.push(x)},Ye=x=>{g[x]--,fe.pop()},ke=x=>{if(se.type===\"globstar\"){let w=g.braces>0&&(x.type===\"comma\"||x.type===\"brace\"),b=x.extglob===!0||we.length&&(x.type===\"pipe\"||x.type===\"paren\");x.type!==\"slash\"&&x.type!==\"paren\"&&!w&&!b&&(g.output=g.output.slice(0,-se.output.length),se.type=\"star\",se.value=\"*\",se.output=Ce,g.output+=se.output)}if(we.length&&x.type!==\"paren\"&&(we[we.length-1].inner+=x.value),(x.value||x.output)&&Fe(x),se&&se.type===\"text\"&&x.type===\"text\"){se.value+=x.value,se.output=(se.output||\"\")+x.value;return}x.prev=se,c.push(x),se=x},it=(x,w)=>{let b={...E[w],conditions:1,inner:\"\"};b.prev=se,b.parens=g.parens,b.output=g.output;let y=(r.capture?\"(\":\"\")+b.open;Pe(\"parens\"),ke({type:x,value:w,output:g.output?\"\":I}),ke({type:\"paren\",extglob:!0,value:dt(),output:y}),we.push(b)},_e=x=>{let w=x.close+(r.capture?\")\":\"\"),b;if(x.type===\"negate\"){let y=Ce;if(x.inner&&x.inner.length>1&&x.inner.includes(\"/\")&&(y=me(r)),(y!==Ce||De()||/^\\)+$/.test(j()))&&(w=x.close=`)$))${y}`),x.inner.includes(\"*\")&&(b=j())&&/^\\.[^\\\\/.]+$/.test(b)){let F=E4(b,{...e,fastpaths:!1}).output;w=x.close=`)${F})${y})`}x.prev.type===\"bos\"&&(g.negatedExtglob=!0)}ke({type:\"paren\",extglob:!0,value:X,output:w}),Ye(\"parens\")};if(r.fastpaths!==!1&&!/(^[*!]|[/()[\\]{}\"])/.test(t)){let x=!1,w=t.replace(Frt,(b,y,F,z,Z,$)=>z===\"\\\\\"?(x=!0,b):z===\"?\"?y?y+z+(Z?te.repeat(Z.length):\"\"):$===0?Be+(Z?te.repeat(Z.length):\"\"):te.repeat(F.length):z===\".\"?C.repeat(F.length):z===\"*\"?y?y+z+(Z?Ce:\"\"):Ce:y?b:`\\\\${b}`);return x===!0&&(r.unescape===!0?w=w.replace(/\\\\/g,\"\"):w=w.replace(/\\\\+/g,b=>b.length%2===0?\"\\\\\\\\\":b?\"\\\\\":\"\")),w===t&&r.contains===!0?(g.output=t,g):(g.output=Gl.wrapOutput(w,g,e),g)}for(;!De();){if(X=dt(),X===\"\\0\")continue;if(X===\"\\\\\"){let b=Re();if(b===\"/\"&&r.bash!==!0||b===\".\"||b===\";\")continue;if(!b){X+=\"\\\\\",ke({type:\"text\",value:X});continue}let y=/^\\\\+/.exec(j()),F=0;if(y&&y[0].length>2&&(F=y[0].length,g.index+=F,F%2!==0&&(X+=\"\\\\\")),r.unescape===!0?X=dt():X+=dt(),g.brackets===0){ke({type:\"text\",value:X});continue}}if(g.brackets>0&&(X!==\"]\"||se.value===\"[\"||se.value===\"[^\")){if(r.posix!==!1&&X===\":\"){let b=se.value.slice(1);if(b.includes(\"[\")&&(se.posix=!0,b.includes(\":\"))){let y=se.value.lastIndexOf(\"[\"),F=se.value.slice(0,y),z=se.value.slice(y+2),Z=Trt[z];if(Z){se.value=F+Z,g.backtrack=!0,dt(),!n.output&&c.indexOf(se)===1&&(n.output=I);continue}}}(X===\"[\"&&Re()!==\":\"||X===\"-\"&&Re()===\"]\")&&(X=`\\\\${X}`),X===\"]\"&&(se.value===\"[\"||se.value===\"[^\")&&(X=`\\\\${X}`),r.posix===!0&&X===\"!\"&&se.value===\"[\"&&(X=\"^\"),se.value+=X,Fe({value:X});continue}if(g.quotes===1&&X!=='\"'){X=Gl.escapeRegex(X),se.value+=X,Fe({value:X});continue}if(X==='\"'){g.quotes=g.quotes===1?0:1,r.keepQuotes===!0&&ke({type:\"text\",value:X});continue}if(X===\"(\"){Pe(\"parens\"),ke({type:\"paren\",value:X});continue}if(X===\")\"){if(g.parens===0&&r.strictBrackets===!0)throw new SyntaxError(TE(\"opening\",\"(\"));let b=we[we.length-1];if(b&&g.parens===b.parens+1){_e(we.pop());continue}ke({type:\"paren\",value:X,output:g.parens?\")\":\"\\\\)\"}),Ye(\"parens\");continue}if(X===\"[\"){if(r.nobracket===!0||!j().includes(\"]\")){if(r.nobracket!==!0&&r.strictBrackets===!0)throw new SyntaxError(TE(\"closing\",\"]\"));X=`\\\\${X}`}else Pe(\"brackets\");ke({type:\"bracket\",value:X});continue}if(X===\"]\"){if(r.nobracket===!0||se&&se.type===\"bracket\"&&se.value.length===1){ke({type:\"text\",value:X,output:`\\\\${X}`});continue}if(g.brackets===0){if(r.strictBrackets===!0)throw new SyntaxError(TE(\"opening\",\"[\"));ke({type:\"text\",value:X,output:`\\\\${X}`});continue}Ye(\"brackets\");let b=se.value.slice(1);if(se.posix!==!0&&b[0]===\"^\"&&!b.includes(\"/\")&&(X=`/${X}`),se.value+=X,Fe({value:X}),r.literalBrackets===!1||Gl.hasRegexChars(b))continue;let y=Gl.escapeRegex(se.value);if(g.output=g.output.slice(0,-se.value.length),r.literalBrackets===!0){g.output+=y,se.value=y;continue}se.value=`(${f}${y}|${se.value})`,g.output+=se.value;continue}if(X===\"{\"&&r.nobrace!==!0){Pe(\"braces\");let b={type:\"brace\",value:X,output:\"(\",outputIndex:g.output.length,tokensIndex:g.tokens.length};ye.push(b),ke(b);continue}if(X===\"}\"){let b=ye[ye.length-1];if(r.nobrace===!0||!b){ke({type:\"text\",value:X,output:X});continue}let y=\")\";if(b.dots===!0){let F=c.slice(),z=[];for(let Z=F.length-1;Z>=0&&(c.pop(),F[Z].type!==\"brace\");Z--)F[Z].type!==\"dots\"&&z.unshift(F[Z].value);y=Nrt(z,r),g.backtrack=!0}if(b.comma!==!0&&b.dots!==!0){let F=g.output.slice(0,b.outputIndex),z=g.tokens.slice(b.tokensIndex);b.value=b.output=\"\\\\{\",X=y=\"\\\\}\",g.output=F;for(let Z of z)g.output+=Z.output||Z.value}ke({type:\"brace\",value:X,output:y}),Ye(\"braces\"),ye.pop();continue}if(X===\"|\"){we.length>0&&we[we.length-1].conditions++,ke({type:\"text\",value:X});continue}if(X===\",\"){let b=X,y=ye[ye.length-1];y&&fe[fe.length-1]===\"braces\"&&(y.comma=!0,b=\"|\"),ke({type:\"comma\",value:X,output:b});continue}if(X===\"/\"){if(se.type===\"dot\"&&g.index===g.start+1){g.start=g.index+1,g.consumed=\"\",g.output=\"\",c.pop(),se=n;continue}ke({type:\"slash\",value:X,output:P});continue}if(X===\".\"){if(g.braces>0&&se.type===\"dot\"){se.value===\".\"&&(se.output=C);let b=ye[ye.length-1];se.type=\"dots\",se.output+=X,se.value+=X,b.dots=!0;continue}if(g.braces+g.parens===0&&se.type!==\"bos\"&&se.type!==\"slash\"){ke({type:\"text\",value:X,output:C});continue}ke({type:\"dot\",value:X,output:C});continue}if(X===\"?\"){if(!(se&&se.value===\"(\")&&r.noextglob!==!0&&Re()===\"(\"&&Re(2)!==\"?\"){it(\"qmark\",X);continue}if(se&&se.type===\"paren\"){let y=Re(),F=X;if(y===\"<\"&&!Gl.supportsLookbehinds())throw new Error(\"Node.js v10 or higher is required for regex lookbehinds\");(se.value===\"(\"&&!/[!=<:]/.test(y)||y===\"<\"&&!/<([!=]|\\w+>)/.test(j()))&&(F=`\\\\${X}`),ke({type:\"text\",value:X,output:F});continue}if(r.dot!==!0&&(se.type===\"slash\"||se.type===\"bos\")){ke({type:\"qmark\",value:X,output:ie});continue}ke({type:\"qmark\",value:X,output:te});continue}if(X===\"!\"){if(r.noextglob!==!0&&Re()===\"(\"&&(Re(2)!==\"?\"||!/[!=<:]/.test(Re(3)))){it(\"negate\",X);continue}if(r.nonegate!==!0&&g.index===0){Ne();continue}}if(X===\"+\"){if(r.noextglob!==!0&&Re()===\"(\"&&Re(2)!==\"?\"){it(\"plus\",X);continue}if(se&&se.value===\"(\"||r.regex===!1){ke({type:\"plus\",value:X,output:S});continue}if(se&&(se.type===\"bracket\"||se.type===\"paren\"||se.type===\"brace\")||g.parens>0){ke({type:\"plus\",value:X});continue}ke({type:\"plus\",value:S});continue}if(X===\"@\"){if(r.noextglob!==!0&&Re()===\"(\"&&Re(2)!==\"?\"){ke({type:\"at\",extglob:!0,value:X,output:\"\"});continue}ke({type:\"text\",value:X});continue}if(X!==\"*\"){(X===\"$\"||X===\"^\")&&(X=`\\\\${X}`);let b=Rrt.exec(j());b&&(X+=b[0],g.index+=b[0].length),ke({type:\"text\",value:X});continue}if(se&&(se.type===\"globstar\"||se.star===!0)){se.type=\"star\",se.star=!0,se.value+=X,se.output=Ce,g.backtrack=!0,g.globstar=!0,rt(X);continue}let x=j();if(r.noextglob!==!0&&/^\\([^?]/.test(x)){it(\"star\",X);continue}if(se.type===\"star\"){if(r.noglobstar===!0){rt(X);continue}let b=se.prev,y=b.prev,F=b.type===\"slash\"||b.type===\"bos\",z=y&&(y.type===\"star\"||y.type===\"globstar\");if(r.bash===!0&&(!F||x[0]&&x[0]!==\"/\")){ke({type:\"star\",value:X,output:\"\"});continue}let Z=g.braces>0&&(b.type===\"comma\"||b.type===\"brace\"),$=we.length&&(b.type===\"pipe\"||b.type===\"paren\");if(!F&&b.type!==\"paren\"&&!Z&&!$){ke({type:\"star\",value:X,output:\"\"});continue}for(;x.slice(0,3)===\"/**\";){let oe=t[g.index+4];if(oe&&oe!==\"/\")break;x=x.slice(3),rt(\"/**\",3)}if(b.type===\"bos\"&&De()){se.type=\"globstar\",se.value+=X,se.output=me(r),g.output=se.output,g.globstar=!0,rt(X);continue}if(b.type===\"slash\"&&b.prev.type!==\"bos\"&&!z&&De()){g.output=g.output.slice(0,-(b.output+se.output).length),b.output=`(?:${b.output}`,se.type=\"globstar\",se.output=me(r)+(r.strictSlashes?\")\":\"|$)\"),se.value+=X,g.globstar=!0,g.output+=b.output+se.output,rt(X);continue}if(b.type===\"slash\"&&b.prev.type!==\"bos\"&&x[0]===\"/\"){let oe=x[1]!==void 0?\"|$\":\"\";g.output=g.output.slice(0,-(b.output+se.output).length),b.output=`(?:${b.output}`,se.type=\"globstar\",se.output=`${me(r)}${P}|${P}${oe})`,se.value+=X,g.output+=b.output+se.output,g.globstar=!0,rt(X+dt()),ke({type:\"slash\",value:\"/\",output:\"\"});continue}if(b.type===\"bos\"&&x[0]===\"/\"){se.type=\"globstar\",se.value+=X,se.output=`(?:^|${P}|${me(r)}${P})`,g.output=se.output,g.globstar=!0,rt(X+dt()),ke({type:\"slash\",value:\"/\",output:\"\"});continue}g.output=g.output.slice(0,-se.output.length),se.type=\"globstar\",se.output=me(r),se.value+=X,g.output+=se.output,g.globstar=!0,rt(X);continue}let w={type:\"star\",value:X,output:Ce};if(r.bash===!0){w.output=\".*?\",(se.type===\"bos\"||se.type===\"slash\")&&(w.output=pe+w.output),ke(w);continue}if(se&&(se.type===\"bracket\"||se.type===\"paren\")&&r.regex===!0){w.output=X,ke(w);continue}(g.index===g.start||se.type===\"slash\"||se.type===\"dot\")&&(se.type===\"dot\"?(g.output+=U,se.output+=U):r.dot===!0?(g.output+=W,se.output+=W):(g.output+=pe,se.output+=pe),Re()!==\"*\"&&(g.output+=I,se.output+=I)),ke(w)}for(;g.brackets>0;){if(r.strictBrackets===!0)throw new SyntaxError(TE(\"closing\",\"]\"));g.output=Gl.escapeLast(g.output,\"[\"),Ye(\"brackets\")}for(;g.parens>0;){if(r.strictBrackets===!0)throw new SyntaxError(TE(\"closing\",\")\"));g.output=Gl.escapeLast(g.output,\"(\"),Ye(\"parens\")}for(;g.braces>0;){if(r.strictBrackets===!0)throw new SyntaxError(TE(\"closing\",\"}\"));g.output=Gl.escapeLast(g.output,\"{\"),Ye(\"braces\")}if(r.strictSlashes!==!0&&(se.type===\"star\"||se.type===\"bracket\")&&ke({type:\"maybe_slash\",value:\"\",output:`${P}?`}),g.backtrack===!0){g.output=\"\";for(let x of g.tokens)g.output+=x.output!=null?x.output:x.value,x.suffix&&(g.output+=x.suffix)}return g};E4.fastpaths=(t,e)=>{let r={...e},s=typeof r.maxLength==\"number\"?Math.min(Bk,r.maxLength):Bk,a=t.length;if(a>s)throw new SyntaxError(`Input length: ${a}, exceeds maximum allowed length: ${s}`);t=Eae[t]||t;let n=Gl.isWindows(e),{DOT_LITERAL:c,SLASH_LITERAL:f,ONE_CHAR:p,DOTS_SLASH:h,NO_DOT:E,NO_DOTS:C,NO_DOTS_SLASH:S,STAR:P,START_ANCHOR:I}=wk.globChars(n),R=r.dot?C:E,N=r.dot?S:E,U=r.capture?\"\":\"?:\",W={negated:!1,prefix:\"\"},te=r.bash===!0?\".*?\":P;r.capture&&(te=`(${te})`);let ie=pe=>pe.noglobstar===!0?te:`(${U}(?:(?!${I}${pe.dot?h:c}).)*?)`,Ae=pe=>{switch(pe){case\"*\":return`${R}${p}${te}`;case\".*\":return`${c}${p}${te}`;case\"*.*\":return`${R}${te}${c}${p}${te}`;case\"*/*\":return`${R}${te}${f}${p}${N}${te}`;case\"**\":return R+ie(r);case\"**/*\":return`(?:${R}${ie(r)}${f})?${N}${p}${te}`;case\"**/*.*\":return`(?:${R}${ie(r)}${f})?${N}${te}${c}${p}${te}`;case\"**/.*\":return`(?:${R}${ie(r)}${f})?${c}${p}${te}`;default:{let Be=/^(.*?)\\.(\\w+)$/.exec(pe);if(!Be)return;let Ce=Ae(Be[1]);return Ce?Ce+c+Be[2]:void 0}}},ce=Gl.removePrefix(t,W),me=Ae(ce);return me&&r.strictSlashes!==!0&&(me+=`${f}?`),me};Iae.exports=E4});var Bae=L((AYt,wae)=>{\"use strict\";var Ort=Ie(\"path\"),Lrt=yae(),I4=Cae(),C4=IB(),Mrt=EB(),_rt=t=>t&&typeof t==\"object\"&&!Array.isArray(t),$i=(t,e,r=!1)=>{if(Array.isArray(t)){let E=t.map(S=>$i(S,e,r));return S=>{for(let P of E){let I=P(S);if(I)return I}return!1}}let s=_rt(t)&&t.tokens&&t.input;if(t===\"\"||typeof t!=\"string\"&&!s)throw new TypeError(\"Expected pattern to be a non-empty string\");let a=e||{},n=C4.isWindows(e),c=s?$i.compileRe(t,e):$i.makeRe(t,e,!1,!0),f=c.state;delete c.state;let p=()=>!1;if(a.ignore){let E={...e,ignore:null,onMatch:null,onResult:null};p=$i(a.ignore,E,r)}let h=(E,C=!1)=>{let{isMatch:S,match:P,output:I}=$i.test(E,c,e,{glob:t,posix:n}),R={glob:t,state:f,regex:c,posix:n,input:E,output:I,match:P,isMatch:S};return typeof a.onResult==\"function\"&&a.onResult(R),S===!1?(R.isMatch=!1,C?R:!1):p(E)?(typeof a.onIgnore==\"function\"&&a.onIgnore(R),R.isMatch=!1,C?R:!1):(typeof a.onMatch==\"function\"&&a.onMatch(R),C?R:!0)};return r&&(h.state=f),h};$i.test=(t,e,r,{glob:s,posix:a}={})=>{if(typeof t!=\"string\")throw new TypeError(\"Expected input to be a string\");if(t===\"\")return{isMatch:!1,output:\"\"};let n=r||{},c=n.format||(a?C4.toPosixSlashes:null),f=t===s,p=f&&c?c(t):t;return f===!1&&(p=c?c(t):t,f=p===s),(f===!1||n.capture===!0)&&(n.matchBase===!0||n.basename===!0?f=$i.matchBase(t,e,r,a):f=e.exec(p)),{isMatch:!!f,match:f,output:p}};$i.matchBase=(t,e,r,s=C4.isWindows(r))=>(e instanceof RegExp?e:$i.makeRe(e,r)).test(Ort.basename(t));$i.isMatch=(t,e,r)=>$i(e,r)(t);$i.parse=(t,e)=>Array.isArray(t)?t.map(r=>$i.parse(r,e)):I4(t,{...e,fastpaths:!1});$i.scan=(t,e)=>Lrt(t,e);$i.compileRe=(t,e,r=!1,s=!1)=>{if(r===!0)return t.output;let a=e||{},n=a.contains?\"\":\"^\",c=a.contains?\"\":\"$\",f=`${n}(?:${t.output})${c}`;t&&t.negated===!0&&(f=`^(?!${f}).*$`);let p=$i.toRegex(f,e);return s===!0&&(p.state=t),p};$i.makeRe=(t,e={},r=!1,s=!1)=>{if(!t||typeof t!=\"string\")throw new TypeError(\"Expected a non-empty string\");let a={negated:!1,fastpaths:!0};return e.fastpaths!==!1&&(t[0]===\".\"||t[0]===\"*\")&&(a.output=I4.fastpaths(t,e)),a.output||(a=I4(t,e)),$i.compileRe(a,e,r,s)};$i.toRegex=(t,e)=>{try{let r=e||{};return new RegExp(t,r.flags||(r.nocase?\"i\":\"\"))}catch(r){if(e&&e.debug===!0)throw r;return/$^/}};$i.constants=Mrt;wae.exports=$i});var Sae=L((pYt,vae)=>{\"use strict\";vae.exports=Bae()});var Sa=L((hYt,xae)=>{\"use strict\";var bae=Ie(\"util\"),Pae=sae(),Jf=Sae(),w4=IB(),Dae=t=>t===\"\"||t===\"./\",Qi=(t,e,r)=>{e=[].concat(e),t=[].concat(t);let s=new Set,a=new Set,n=new Set,c=0,f=E=>{n.add(E.output),r&&r.onResult&&r.onResult(E)};for(let E=0;E<e.length;E++){let C=Jf(String(e[E]),{...r,onResult:f},!0),S=C.state.negated||C.state.negatedExtglob;S&&c++;for(let P of t){let I=C(P,!0);(S?!I.isMatch:I.isMatch)&&(S?s.add(I.output):(s.delete(I.output),a.add(I.output)))}}let h=(c===e.length?[...n]:[...a]).filter(E=>!s.has(E));if(r&&h.length===0){if(r.failglob===!0)throw new Error(`No matches found for \"${e.join(\", \")}\"`);if(r.nonull===!0||r.nullglob===!0)return r.unescape?e.map(E=>E.replace(/\\\\/g,\"\")):e}return h};Qi.match=Qi;Qi.matcher=(t,e)=>Jf(t,e);Qi.isMatch=(t,e,r)=>Jf(e,r)(t);Qi.any=Qi.isMatch;Qi.not=(t,e,r={})=>{e=[].concat(e).map(String);let s=new Set,a=[],n=f=>{r.onResult&&r.onResult(f),a.push(f.output)},c=new Set(Qi(t,e,{...r,onResult:n}));for(let f of a)c.has(f)||s.add(f);return[...s]};Qi.contains=(t,e,r)=>{if(typeof t!=\"string\")throw new TypeError(`Expected a string: \"${bae.inspect(t)}\"`);if(Array.isArray(e))return e.some(s=>Qi.contains(t,s,r));if(typeof e==\"string\"){if(Dae(t)||Dae(e))return!1;if(t.includes(e)||t.startsWith(\"./\")&&t.slice(2).includes(e))return!0}return Qi.isMatch(t,e,{...r,contains:!0})};Qi.matchKeys=(t,e,r)=>{if(!w4.isObject(t))throw new TypeError(\"Expected the first argument to be an object\");let s=Qi(Object.keys(t),e,r),a={};for(let n of s)a[n]=t[n];return a};Qi.some=(t,e,r)=>{let s=[].concat(t);for(let a of[].concat(e)){let n=Jf(String(a),r);if(s.some(c=>n(c)))return!0}return!1};Qi.every=(t,e,r)=>{let s=[].concat(t);for(let a of[].concat(e)){let n=Jf(String(a),r);if(!s.every(c=>n(c)))return!1}return!0};Qi.all=(t,e,r)=>{if(typeof t!=\"string\")throw new TypeError(`Expected a string: \"${bae.inspect(t)}\"`);return[].concat(e).every(s=>Jf(s,r)(t))};Qi.capture=(t,e,r)=>{let s=w4.isWindows(r),n=Jf.makeRe(String(t),{...r,capture:!0}).exec(s?w4.toPosixSlashes(e):e);if(n)return n.slice(1).map(c=>c===void 0?\"\":c)};Qi.makeRe=(...t)=>Jf.makeRe(...t);Qi.scan=(...t)=>Jf.scan(...t);Qi.parse=(t,e)=>{let r=[];for(let s of[].concat(t||[]))for(let a of Pae(String(s),e))r.push(Jf.parse(a,e));return r};Qi.braces=(t,e)=>{if(typeof t!=\"string\")throw new TypeError(\"Expected a string\");return e&&e.nobrace===!0||!/\\{.*\\}/.test(t)?[t]:Pae(t,e)};Qi.braceExpand=(t,e)=>{if(typeof t!=\"string\")throw new TypeError(\"Expected a string\");return Qi.braces(t,{...e,expand:!0})};xae.exports=Qi});var Qae=L((gYt,kae)=>{\"use strict\";kae.exports=({onlyFirst:t=!1}={})=>{let e=[\"[\\\\u001B\\\\u009B][[\\\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\\\d\\\\/#&.:=?%@~_]+)*|[a-zA-Z\\\\d]+(?:;[-a-zA-Z\\\\d\\\\/#&.:=?%@~_]*)*)?\\\\u0007)\",\"(?:(?:\\\\d{1,4}(?:;\\\\d{0,4})*)?[\\\\dA-PR-TZcf-ntqry=><~]))\"].join(\"|\");return new RegExp(e,t?void 0:\"g\")}});var vk=L((dYt,Tae)=>{\"use strict\";var Urt=Qae();Tae.exports=t=>typeof t==\"string\"?t.replace(Urt(),\"\"):t});var Fae=L((mYt,Rae)=>{function Hrt(){this.__data__=[],this.size=0}Rae.exports=Hrt});var RE=L((yYt,Nae)=>{function jrt(t,e){return t===e||t!==t&&e!==e}Nae.exports=jrt});var wB=L((EYt,Oae)=>{var qrt=RE();function Grt(t,e){for(var r=t.length;r--;)if(qrt(t[r][0],e))return r;return-1}Oae.exports=Grt});var Mae=L((IYt,Lae)=>{var Wrt=wB(),Yrt=Array.prototype,Vrt=Yrt.splice;function Krt(t){var e=this.__data__,r=Wrt(e,t);if(r<0)return!1;var s=e.length-1;return r==s?e.pop():Vrt.call(e,r,1),--this.size,!0}Lae.exports=Krt});var Uae=L((CYt,_ae)=>{var Jrt=wB();function zrt(t){var e=this.__data__,r=Jrt(e,t);return r<0?void 0:e[r][1]}_ae.exports=zrt});var jae=L((wYt,Hae)=>{var Zrt=wB();function Xrt(t){return Zrt(this.__data__,t)>-1}Hae.exports=Xrt});var Gae=L((BYt,qae)=>{var $rt=wB();function ent(t,e){var r=this.__data__,s=$rt(r,t);return s<0?(++this.size,r.push([t,e])):r[s][1]=e,this}qae.exports=ent});var BB=L((vYt,Wae)=>{var tnt=Fae(),rnt=Mae(),nnt=Uae(),int=jae(),snt=Gae();function FE(t){var e=-1,r=t==null?0:t.length;for(this.clear();++e<r;){var s=t[e];this.set(s[0],s[1])}}FE.prototype.clear=tnt;FE.prototype.delete=rnt;FE.prototype.get=nnt;FE.prototype.has=int;FE.prototype.set=snt;Wae.exports=FE});var Vae=L((SYt,Yae)=>{var ont=BB();function ant(){this.__data__=new ont,this.size=0}Yae.exports=ant});var Jae=L((DYt,Kae)=>{function lnt(t){var e=this.__data__,r=e.delete(t);return this.size=e.size,r}Kae.exports=lnt});var Zae=L((bYt,zae)=>{function cnt(t){return this.__data__.get(t)}zae.exports=cnt});var $ae=L((PYt,Xae)=>{function unt(t){return this.__data__.has(t)}Xae.exports=unt});var B4=L((xYt,ele)=>{var fnt=typeof global==\"object\"&&global&&global.Object===Object&&global;ele.exports=fnt});var Pc=L((kYt,tle)=>{var Ant=B4(),pnt=typeof self==\"object\"&&self&&self.Object===Object&&self,hnt=Ant||pnt||Function(\"return this\")();tle.exports=hnt});var Gd=L((QYt,rle)=>{var gnt=Pc(),dnt=gnt.Symbol;rle.exports=dnt});var ole=L((TYt,sle)=>{var nle=Gd(),ile=Object.prototype,mnt=ile.hasOwnProperty,ynt=ile.toString,vB=nle?nle.toStringTag:void 0;function Ent(t){var e=mnt.call(t,vB),r=t[vB];try{t[vB]=void 0;var s=!0}catch{}var a=ynt.call(t);return s&&(e?t[vB]=r:delete t[vB]),a}sle.exports=Ent});var lle=L((RYt,ale)=>{var Int=Object.prototype,Cnt=Int.toString;function wnt(t){return Cnt.call(t)}ale.exports=wnt});var Wd=L((FYt,fle)=>{var cle=Gd(),Bnt=ole(),vnt=lle(),Snt=\"[object Null]\",Dnt=\"[object Undefined]\",ule=cle?cle.toStringTag:void 0;function bnt(t){return t==null?t===void 0?Dnt:Snt:ule&&ule in Object(t)?Bnt(t):vnt(t)}fle.exports=bnt});var Wl=L((NYt,Ale)=>{function Pnt(t){var e=typeof t;return t!=null&&(e==\"object\"||e==\"function\")}Ale.exports=Pnt});var Sk=L((OYt,ple)=>{var xnt=Wd(),knt=Wl(),Qnt=\"[object AsyncFunction]\",Tnt=\"[object Function]\",Rnt=\"[object GeneratorFunction]\",Fnt=\"[object Proxy]\";function Nnt(t){if(!knt(t))return!1;var e=xnt(t);return e==Tnt||e==Rnt||e==Qnt||e==Fnt}ple.exports=Nnt});var gle=L((LYt,hle)=>{var Ont=Pc(),Lnt=Ont[\"__core-js_shared__\"];hle.exports=Lnt});var yle=L((MYt,mle)=>{var v4=gle(),dle=function(){var t=/[^.]+$/.exec(v4&&v4.keys&&v4.keys.IE_PROTO||\"\");return t?\"Symbol(src)_1.\"+t:\"\"}();function Mnt(t){return!!dle&&dle in t}mle.exports=Mnt});var S4=L((_Yt,Ele)=>{var _nt=Function.prototype,Unt=_nt.toString;function Hnt(t){if(t!=null){try{return Unt.call(t)}catch{}try{return t+\"\"}catch{}}return\"\"}Ele.exports=Hnt});var Cle=L((UYt,Ile)=>{var jnt=Sk(),qnt=yle(),Gnt=Wl(),Wnt=S4(),Ynt=/[\\\\^$.*+?()[\\]{}|]/g,Vnt=/^\\[object .+?Constructor\\]$/,Knt=Function.prototype,Jnt=Object.prototype,znt=Knt.toString,Znt=Jnt.hasOwnProperty,Xnt=RegExp(\"^\"+znt.call(Znt).replace(Ynt,\"\\\\$&\").replace(/hasOwnProperty|(function).*?(?=\\\\\\()| for .+?(?=\\\\\\])/g,\"$1.*?\")+\"$\");function $nt(t){if(!Gnt(t)||qnt(t))return!1;var e=jnt(t)?Xnt:Vnt;return e.test(Wnt(t))}Ile.exports=$nt});var Ble=L((HYt,wle)=>{function eit(t,e){return t?.[e]}wle.exports=eit});var f0=L((jYt,vle)=>{var tit=Cle(),rit=Ble();function nit(t,e){var r=rit(t,e);return tit(r)?r:void 0}vle.exports=nit});var Dk=L((qYt,Sle)=>{var iit=f0(),sit=Pc(),oit=iit(sit,\"Map\");Sle.exports=oit});var SB=L((GYt,Dle)=>{var ait=f0(),lit=ait(Object,\"create\");Dle.exports=lit});var xle=L((WYt,Ple)=>{var ble=SB();function cit(){this.__data__=ble?ble(null):{},this.size=0}Ple.exports=cit});var Qle=L((YYt,kle)=>{function uit(t){var e=this.has(t)&&delete this.__data__[t];return this.size-=e?1:0,e}kle.exports=uit});var Rle=L((VYt,Tle)=>{var fit=SB(),Ait=\"__lodash_hash_undefined__\",pit=Object.prototype,hit=pit.hasOwnProperty;function git(t){var e=this.__data__;if(fit){var r=e[t];return r===Ait?void 0:r}return hit.call(e,t)?e[t]:void 0}Tle.exports=git});var Nle=L((KYt,Fle)=>{var dit=SB(),mit=Object.prototype,yit=mit.hasOwnProperty;function Eit(t){var e=this.__data__;return dit?e[t]!==void 0:yit.call(e,t)}Fle.exports=Eit});var Lle=L((JYt,Ole)=>{var Iit=SB(),Cit=\"__lodash_hash_undefined__\";function wit(t,e){var r=this.__data__;return this.size+=this.has(t)?0:1,r[t]=Iit&&e===void 0?Cit:e,this}Ole.exports=wit});var _le=L((zYt,Mle)=>{var Bit=xle(),vit=Qle(),Sit=Rle(),Dit=Nle(),bit=Lle();function NE(t){var e=-1,r=t==null?0:t.length;for(this.clear();++e<r;){var s=t[e];this.set(s[0],s[1])}}NE.prototype.clear=Bit;NE.prototype.delete=vit;NE.prototype.get=Sit;NE.prototype.has=Dit;NE.prototype.set=bit;Mle.exports=NE});var jle=L((ZYt,Hle)=>{var Ule=_le(),Pit=BB(),xit=Dk();function kit(){this.size=0,this.__data__={hash:new Ule,map:new(xit||Pit),string:new Ule}}Hle.exports=kit});var Gle=L((XYt,qle)=>{function Qit(t){var e=typeof t;return e==\"string\"||e==\"number\"||e==\"symbol\"||e==\"boolean\"?t!==\"__proto__\":t===null}qle.exports=Qit});var DB=L(($Yt,Wle)=>{var Tit=Gle();function Rit(t,e){var r=t.__data__;return Tit(e)?r[typeof e==\"string\"?\"string\":\"hash\"]:r.map}Wle.exports=Rit});var Vle=L((eVt,Yle)=>{var Fit=DB();function Nit(t){var e=Fit(this,t).delete(t);return this.size-=e?1:0,e}Yle.exports=Nit});var Jle=L((tVt,Kle)=>{var Oit=DB();function Lit(t){return Oit(this,t).get(t)}Kle.exports=Lit});var Zle=L((rVt,zle)=>{var Mit=DB();function _it(t){return Mit(this,t).has(t)}zle.exports=_it});var $le=L((nVt,Xle)=>{var Uit=DB();function Hit(t,e){var r=Uit(this,t),s=r.size;return r.set(t,e),this.size+=r.size==s?0:1,this}Xle.exports=Hit});var bk=L((iVt,ece)=>{var jit=jle(),qit=Vle(),Git=Jle(),Wit=Zle(),Yit=$le();function OE(t){var e=-1,r=t==null?0:t.length;for(this.clear();++e<r;){var s=t[e];this.set(s[0],s[1])}}OE.prototype.clear=jit;OE.prototype.delete=qit;OE.prototype.get=Git;OE.prototype.has=Wit;OE.prototype.set=Yit;ece.exports=OE});var rce=L((sVt,tce)=>{var Vit=BB(),Kit=Dk(),Jit=bk(),zit=200;function Zit(t,e){var r=this.__data__;if(r instanceof Vit){var s=r.__data__;if(!Kit||s.length<zit-1)return s.push([t,e]),this.size=++r.size,this;r=this.__data__=new Jit(s)}return r.set(t,e),this.size=r.size,this}tce.exports=Zit});var Pk=L((oVt,nce)=>{var Xit=BB(),$it=Vae(),est=Jae(),tst=Zae(),rst=$ae(),nst=rce();function LE(t){var e=this.__data__=new Xit(t);this.size=e.size}LE.prototype.clear=$it;LE.prototype.delete=est;LE.prototype.get=tst;LE.prototype.has=rst;LE.prototype.set=nst;nce.exports=LE});var sce=L((aVt,ice)=>{var ist=\"__lodash_hash_undefined__\";function sst(t){return this.__data__.set(t,ist),this}ice.exports=sst});var ace=L((lVt,oce)=>{function ost(t){return this.__data__.has(t)}oce.exports=ost});var cce=L((cVt,lce)=>{var ast=bk(),lst=sce(),cst=ace();function xk(t){var e=-1,r=t==null?0:t.length;for(this.__data__=new ast;++e<r;)this.add(t[e])}xk.prototype.add=xk.prototype.push=lst;xk.prototype.has=cst;lce.exports=xk});var fce=L((uVt,uce)=>{function ust(t,e){for(var r=-1,s=t==null?0:t.length;++r<s;)if(e(t[r],r,t))return!0;return!1}uce.exports=ust});var pce=L((fVt,Ace)=>{function fst(t,e){return t.has(e)}Ace.exports=fst});var D4=L((AVt,hce)=>{var Ast=cce(),pst=fce(),hst=pce(),gst=1,dst=2;function mst(t,e,r,s,a,n){var c=r&gst,f=t.length,p=e.length;if(f!=p&&!(c&&p>f))return!1;var h=n.get(t),E=n.get(e);if(h&&E)return h==e&&E==t;var C=-1,S=!0,P=r&dst?new Ast:void 0;for(n.set(t,e),n.set(e,t);++C<f;){var I=t[C],R=e[C];if(s)var N=c?s(R,I,C,e,t,n):s(I,R,C,t,e,n);if(N!==void 0){if(N)continue;S=!1;break}if(P){if(!pst(e,function(U,W){if(!hst(P,W)&&(I===U||a(I,U,r,s,n)))return P.push(W)})){S=!1;break}}else if(!(I===R||a(I,R,r,s,n))){S=!1;break}}return n.delete(t),n.delete(e),S}hce.exports=mst});var b4=L((pVt,gce)=>{var yst=Pc(),Est=yst.Uint8Array;gce.exports=Est});var mce=L((hVt,dce)=>{function Ist(t){var e=-1,r=Array(t.size);return t.forEach(function(s,a){r[++e]=[a,s]}),r}dce.exports=Ist});var Ece=L((gVt,yce)=>{function Cst(t){var e=-1,r=Array(t.size);return t.forEach(function(s){r[++e]=s}),r}yce.exports=Cst});var vce=L((dVt,Bce)=>{var Ice=Gd(),Cce=b4(),wst=RE(),Bst=D4(),vst=mce(),Sst=Ece(),Dst=1,bst=2,Pst=\"[object Boolean]\",xst=\"[object Date]\",kst=\"[object Error]\",Qst=\"[object Map]\",Tst=\"[object Number]\",Rst=\"[object RegExp]\",Fst=\"[object Set]\",Nst=\"[object String]\",Ost=\"[object Symbol]\",Lst=\"[object ArrayBuffer]\",Mst=\"[object DataView]\",wce=Ice?Ice.prototype:void 0,P4=wce?wce.valueOf:void 0;function _st(t,e,r,s,a,n,c){switch(r){case Mst:if(t.byteLength!=e.byteLength||t.byteOffset!=e.byteOffset)return!1;t=t.buffer,e=e.buffer;case Lst:return!(t.byteLength!=e.byteLength||!n(new Cce(t),new Cce(e)));case Pst:case xst:case Tst:return wst(+t,+e);case kst:return t.name==e.name&&t.message==e.message;case Rst:case Nst:return t==e+\"\";case Qst:var f=vst;case Fst:var p=s&Dst;if(f||(f=Sst),t.size!=e.size&&!p)return!1;var h=c.get(t);if(h)return h==e;s|=bst,c.set(t,e);var E=Bst(f(t),f(e),s,a,n,c);return c.delete(t),E;case Ost:if(P4)return P4.call(t)==P4.call(e)}return!1}Bce.exports=_st});var kk=L((mVt,Sce)=>{function Ust(t,e){for(var r=-1,s=e.length,a=t.length;++r<s;)t[a+r]=e[r];return t}Sce.exports=Ust});var xc=L((yVt,Dce)=>{var Hst=Array.isArray;Dce.exports=Hst});var x4=L((EVt,bce)=>{var jst=kk(),qst=xc();function Gst(t,e,r){var s=e(t);return qst(t)?s:jst(s,r(t))}bce.exports=Gst});var xce=L((IVt,Pce)=>{function Wst(t,e){for(var r=-1,s=t==null?0:t.length,a=0,n=[];++r<s;){var c=t[r];e(c,r,t)&&(n[a++]=c)}return n}Pce.exports=Wst});var k4=L((CVt,kce)=>{function Yst(){return[]}kce.exports=Yst});var Qk=L((wVt,Tce)=>{var Vst=xce(),Kst=k4(),Jst=Object.prototype,zst=Jst.propertyIsEnumerable,Qce=Object.getOwnPropertySymbols,Zst=Qce?function(t){return t==null?[]:(t=Object(t),Vst(Qce(t),function(e){return zst.call(t,e)}))}:Kst;Tce.exports=Zst});var Fce=L((BVt,Rce)=>{function Xst(t,e){for(var r=-1,s=Array(t);++r<t;)s[r]=e(r);return s}Rce.exports=Xst});var zf=L((vVt,Nce)=>{function $st(t){return t!=null&&typeof t==\"object\"}Nce.exports=$st});var Lce=L((SVt,Oce)=>{var eot=Wd(),tot=zf(),rot=\"[object Arguments]\";function not(t){return tot(t)&&eot(t)==rot}Oce.exports=not});var bB=L((DVt,Uce)=>{var Mce=Lce(),iot=zf(),_ce=Object.prototype,sot=_ce.hasOwnProperty,oot=_ce.propertyIsEnumerable,aot=Mce(function(){return arguments}())?Mce:function(t){return iot(t)&&sot.call(t,\"callee\")&&!oot.call(t,\"callee\")};Uce.exports=aot});var jce=L((bVt,Hce)=>{function lot(){return!1}Hce.exports=lot});var xB=L((PB,ME)=>{var cot=Pc(),uot=jce(),Wce=typeof PB==\"object\"&&PB&&!PB.nodeType&&PB,qce=Wce&&typeof ME==\"object\"&&ME&&!ME.nodeType&&ME,fot=qce&&qce.exports===Wce,Gce=fot?cot.Buffer:void 0,Aot=Gce?Gce.isBuffer:void 0,pot=Aot||uot;ME.exports=pot});var kB=L((PVt,Yce)=>{var hot=9007199254740991,got=/^(?:0|[1-9]\\d*)$/;function dot(t,e){var r=typeof t;return e=e??hot,!!e&&(r==\"number\"||r!=\"symbol\"&&got.test(t))&&t>-1&&t%1==0&&t<e}Yce.exports=dot});var Tk=L((xVt,Vce)=>{var mot=9007199254740991;function yot(t){return typeof t==\"number\"&&t>-1&&t%1==0&&t<=mot}Vce.exports=yot});var Jce=L((kVt,Kce)=>{var Eot=Wd(),Iot=Tk(),Cot=zf(),wot=\"[object Arguments]\",Bot=\"[object Array]\",vot=\"[object Boolean]\",Sot=\"[object Date]\",Dot=\"[object Error]\",bot=\"[object Function]\",Pot=\"[object Map]\",xot=\"[object Number]\",kot=\"[object Object]\",Qot=\"[object RegExp]\",Tot=\"[object Set]\",Rot=\"[object String]\",Fot=\"[object WeakMap]\",Not=\"[object ArrayBuffer]\",Oot=\"[object DataView]\",Lot=\"[object Float32Array]\",Mot=\"[object Float64Array]\",_ot=\"[object Int8Array]\",Uot=\"[object Int16Array]\",Hot=\"[object Int32Array]\",jot=\"[object Uint8Array]\",qot=\"[object Uint8ClampedArray]\",Got=\"[object Uint16Array]\",Wot=\"[object Uint32Array]\",Di={};Di[Lot]=Di[Mot]=Di[_ot]=Di[Uot]=Di[Hot]=Di[jot]=Di[qot]=Di[Got]=Di[Wot]=!0;Di[wot]=Di[Bot]=Di[Not]=Di[vot]=Di[Oot]=Di[Sot]=Di[Dot]=Di[bot]=Di[Pot]=Di[xot]=Di[kot]=Di[Qot]=Di[Tot]=Di[Rot]=Di[Fot]=!1;function Yot(t){return Cot(t)&&Iot(t.length)&&!!Di[Eot(t)]}Kce.exports=Yot});var Rk=L((QVt,zce)=>{function Vot(t){return function(e){return t(e)}}zce.exports=Vot});var Fk=L((QB,_E)=>{var Kot=B4(),Zce=typeof QB==\"object\"&&QB&&!QB.nodeType&&QB,TB=Zce&&typeof _E==\"object\"&&_E&&!_E.nodeType&&_E,Jot=TB&&TB.exports===Zce,Q4=Jot&&Kot.process,zot=function(){try{var t=TB&&TB.require&&TB.require(\"util\").types;return t||Q4&&Q4.binding&&Q4.binding(\"util\")}catch{}}();_E.exports=zot});var Nk=L((TVt,eue)=>{var Zot=Jce(),Xot=Rk(),Xce=Fk(),$ce=Xce&&Xce.isTypedArray,$ot=$ce?Xot($ce):Zot;eue.exports=$ot});var T4=L((RVt,tue)=>{var eat=Fce(),tat=bB(),rat=xc(),nat=xB(),iat=kB(),sat=Nk(),oat=Object.prototype,aat=oat.hasOwnProperty;function lat(t,e){var r=rat(t),s=!r&&tat(t),a=!r&&!s&&nat(t),n=!r&&!s&&!a&&sat(t),c=r||s||a||n,f=c?eat(t.length,String):[],p=f.length;for(var h in t)(e||aat.call(t,h))&&!(c&&(h==\"length\"||a&&(h==\"offset\"||h==\"parent\")||n&&(h==\"buffer\"||h==\"byteLength\"||h==\"byteOffset\")||iat(h,p)))&&f.push(h);return f}tue.exports=lat});var Ok=L((FVt,rue)=>{var cat=Object.prototype;function uat(t){var e=t&&t.constructor,r=typeof e==\"function\"&&e.prototype||cat;return t===r}rue.exports=uat});var R4=L((NVt,nue)=>{function fat(t,e){return function(r){return t(e(r))}}nue.exports=fat});var sue=L((OVt,iue)=>{var Aat=R4(),pat=Aat(Object.keys,Object);iue.exports=pat});var aue=L((LVt,oue)=>{var hat=Ok(),gat=sue(),dat=Object.prototype,mat=dat.hasOwnProperty;function yat(t){if(!hat(t))return gat(t);var e=[];for(var r in Object(t))mat.call(t,r)&&r!=\"constructor\"&&e.push(r);return e}oue.exports=yat});var RB=L((MVt,lue)=>{var Eat=Sk(),Iat=Tk();function Cat(t){return t!=null&&Iat(t.length)&&!Eat(t)}lue.exports=Cat});var Lk=L((_Vt,cue)=>{var wat=T4(),Bat=aue(),vat=RB();function Sat(t){return vat(t)?wat(t):Bat(t)}cue.exports=Sat});var F4=L((UVt,uue)=>{var Dat=x4(),bat=Qk(),Pat=Lk();function xat(t){return Dat(t,Pat,bat)}uue.exports=xat});var pue=L((HVt,Aue)=>{var fue=F4(),kat=1,Qat=Object.prototype,Tat=Qat.hasOwnProperty;function Rat(t,e,r,s,a,n){var c=r&kat,f=fue(t),p=f.length,h=fue(e),E=h.length;if(p!=E&&!c)return!1;for(var C=p;C--;){var S=f[C];if(!(c?S in e:Tat.call(e,S)))return!1}var P=n.get(t),I=n.get(e);if(P&&I)return P==e&&I==t;var R=!0;n.set(t,e),n.set(e,t);for(var N=c;++C<p;){S=f[C];var U=t[S],W=e[S];if(s)var te=c?s(W,U,S,e,t,n):s(U,W,S,t,e,n);if(!(te===void 0?U===W||a(U,W,r,s,n):te)){R=!1;break}N||(N=S==\"constructor\")}if(R&&!N){var ie=t.constructor,Ae=e.constructor;ie!=Ae&&\"constructor\"in t&&\"constructor\"in e&&!(typeof ie==\"function\"&&ie instanceof ie&&typeof Ae==\"function\"&&Ae instanceof Ae)&&(R=!1)}return n.delete(t),n.delete(e),R}Aue.exports=Rat});var gue=L((jVt,hue)=>{var Fat=f0(),Nat=Pc(),Oat=Fat(Nat,\"DataView\");hue.exports=Oat});var mue=L((qVt,due)=>{var Lat=f0(),Mat=Pc(),_at=Lat(Mat,\"Promise\");due.exports=_at});var Eue=L((GVt,yue)=>{var Uat=f0(),Hat=Pc(),jat=Uat(Hat,\"Set\");yue.exports=jat});var Cue=L((WVt,Iue)=>{var qat=f0(),Gat=Pc(),Wat=qat(Gat,\"WeakMap\");Iue.exports=Wat});var FB=L((YVt,Pue)=>{var N4=gue(),O4=Dk(),L4=mue(),M4=Eue(),_4=Cue(),bue=Wd(),UE=S4(),wue=\"[object Map]\",Yat=\"[object Object]\",Bue=\"[object Promise]\",vue=\"[object Set]\",Sue=\"[object WeakMap]\",Due=\"[object DataView]\",Vat=UE(N4),Kat=UE(O4),Jat=UE(L4),zat=UE(M4),Zat=UE(_4),Yd=bue;(N4&&Yd(new N4(new ArrayBuffer(1)))!=Due||O4&&Yd(new O4)!=wue||L4&&Yd(L4.resolve())!=Bue||M4&&Yd(new M4)!=vue||_4&&Yd(new _4)!=Sue)&&(Yd=function(t){var e=bue(t),r=e==Yat?t.constructor:void 0,s=r?UE(r):\"\";if(s)switch(s){case Vat:return Due;case Kat:return wue;case Jat:return Bue;case zat:return vue;case Zat:return Sue}return e});Pue.exports=Yd});var Oue=L((VVt,Nue)=>{var U4=Pk(),Xat=D4(),$at=vce(),elt=pue(),xue=FB(),kue=xc(),Que=xB(),tlt=Nk(),rlt=1,Tue=\"[object Arguments]\",Rue=\"[object Array]\",Mk=\"[object Object]\",nlt=Object.prototype,Fue=nlt.hasOwnProperty;function ilt(t,e,r,s,a,n){var c=kue(t),f=kue(e),p=c?Rue:xue(t),h=f?Rue:xue(e);p=p==Tue?Mk:p,h=h==Tue?Mk:h;var E=p==Mk,C=h==Mk,S=p==h;if(S&&Que(t)){if(!Que(e))return!1;c=!0,E=!1}if(S&&!E)return n||(n=new U4),c||tlt(t)?Xat(t,e,r,s,a,n):$at(t,e,p,r,s,a,n);if(!(r&rlt)){var P=E&&Fue.call(t,\"__wrapped__\"),I=C&&Fue.call(e,\"__wrapped__\");if(P||I){var R=P?t.value():t,N=I?e.value():e;return n||(n=new U4),a(R,N,r,s,n)}}return S?(n||(n=new U4),elt(t,e,r,s,a,n)):!1}Nue.exports=ilt});var Uue=L((KVt,_ue)=>{var slt=Oue(),Lue=zf();function Mue(t,e,r,s,a){return t===e?!0:t==null||e==null||!Lue(t)&&!Lue(e)?t!==t&&e!==e:slt(t,e,r,s,Mue,a)}_ue.exports=Mue});var jue=L((JVt,Hue)=>{var olt=Uue();function alt(t,e){return olt(t,e)}Hue.exports=alt});var H4=L((zVt,que)=>{var llt=f0(),clt=function(){try{var t=llt(Object,\"defineProperty\");return t({},\"\",{}),t}catch{}}();que.exports=clt});var _k=L((ZVt,Wue)=>{var Gue=H4();function ult(t,e,r){e==\"__proto__\"&&Gue?Gue(t,e,{configurable:!0,enumerable:!0,value:r,writable:!0}):t[e]=r}Wue.exports=ult});var j4=L((XVt,Yue)=>{var flt=_k(),Alt=RE();function plt(t,e,r){(r!==void 0&&!Alt(t[e],r)||r===void 0&&!(e in t))&&flt(t,e,r)}Yue.exports=plt});var Kue=L(($Vt,Vue)=>{function hlt(t){return function(e,r,s){for(var a=-1,n=Object(e),c=s(e),f=c.length;f--;){var p=c[t?f:++a];if(r(n[p],p,n)===!1)break}return e}}Vue.exports=hlt});var zue=L((e7t,Jue)=>{var glt=Kue(),dlt=glt();Jue.exports=dlt});var q4=L((NB,HE)=>{var mlt=Pc(),efe=typeof NB==\"object\"&&NB&&!NB.nodeType&&NB,Zue=efe&&typeof HE==\"object\"&&HE&&!HE.nodeType&&HE,ylt=Zue&&Zue.exports===efe,Xue=ylt?mlt.Buffer:void 0,$ue=Xue?Xue.allocUnsafe:void 0;function Elt(t,e){if(e)return t.slice();var r=t.length,s=$ue?$ue(r):new t.constructor(r);return t.copy(s),s}HE.exports=Elt});var Uk=L((t7t,rfe)=>{var tfe=b4();function Ilt(t){var e=new t.constructor(t.byteLength);return new tfe(e).set(new tfe(t)),e}rfe.exports=Ilt});var G4=L((r7t,nfe)=>{var Clt=Uk();function wlt(t,e){var r=e?Clt(t.buffer):t.buffer;return new t.constructor(r,t.byteOffset,t.length)}nfe.exports=wlt});var Hk=L((n7t,ife)=>{function Blt(t,e){var r=-1,s=t.length;for(e||(e=Array(s));++r<s;)e[r]=t[r];return e}ife.exports=Blt});var afe=L((i7t,ofe)=>{var vlt=Wl(),sfe=Object.create,Slt=function(){function t(){}return function(e){if(!vlt(e))return{};if(sfe)return sfe(e);t.prototype=e;var r=new t;return t.prototype=void 0,r}}();ofe.exports=Slt});var jk=L((s7t,lfe)=>{var Dlt=R4(),blt=Dlt(Object.getPrototypeOf,Object);lfe.exports=blt});var W4=L((o7t,cfe)=>{var Plt=afe(),xlt=jk(),klt=Ok();function Qlt(t){return typeof t.constructor==\"function\"&&!klt(t)?Plt(xlt(t)):{}}cfe.exports=Qlt});var ffe=L((a7t,ufe)=>{var Tlt=RB(),Rlt=zf();function Flt(t){return Rlt(t)&&Tlt(t)}ufe.exports=Flt});var Y4=L((l7t,pfe)=>{var Nlt=Wd(),Olt=jk(),Llt=zf(),Mlt=\"[object Object]\",_lt=Function.prototype,Ult=Object.prototype,Afe=_lt.toString,Hlt=Ult.hasOwnProperty,jlt=Afe.call(Object);function qlt(t){if(!Llt(t)||Nlt(t)!=Mlt)return!1;var e=Olt(t);if(e===null)return!0;var r=Hlt.call(e,\"constructor\")&&e.constructor;return typeof r==\"function\"&&r instanceof r&&Afe.call(r)==jlt}pfe.exports=qlt});var V4=L((c7t,hfe)=>{function Glt(t,e){if(!(e===\"constructor\"&&typeof t[e]==\"function\")&&e!=\"__proto__\")return t[e]}hfe.exports=Glt});var qk=L((u7t,gfe)=>{var Wlt=_k(),Ylt=RE(),Vlt=Object.prototype,Klt=Vlt.hasOwnProperty;function Jlt(t,e,r){var s=t[e];(!(Klt.call(t,e)&&Ylt(s,r))||r===void 0&&!(e in t))&&Wlt(t,e,r)}gfe.exports=Jlt});var Vd=L((f7t,dfe)=>{var zlt=qk(),Zlt=_k();function Xlt(t,e,r,s){var a=!r;r||(r={});for(var n=-1,c=e.length;++n<c;){var f=e[n],p=s?s(r[f],t[f],f,r,t):void 0;p===void 0&&(p=t[f]),a?Zlt(r,f,p):zlt(r,f,p)}return r}dfe.exports=Xlt});var yfe=L((A7t,mfe)=>{function $lt(t){var e=[];if(t!=null)for(var r in Object(t))e.push(r);return e}mfe.exports=$lt});var Ife=L((p7t,Efe)=>{var ect=Wl(),tct=Ok(),rct=yfe(),nct=Object.prototype,ict=nct.hasOwnProperty;function sct(t){if(!ect(t))return rct(t);var e=tct(t),r=[];for(var s in t)s==\"constructor\"&&(e||!ict.call(t,s))||r.push(s);return r}Efe.exports=sct});var jE=L((h7t,Cfe)=>{var oct=T4(),act=Ife(),lct=RB();function cct(t){return lct(t)?oct(t,!0):act(t)}Cfe.exports=cct});var Bfe=L((g7t,wfe)=>{var uct=Vd(),fct=jE();function Act(t){return uct(t,fct(t))}wfe.exports=Act});var xfe=L((d7t,Pfe)=>{var vfe=j4(),pct=q4(),hct=G4(),gct=Hk(),dct=W4(),Sfe=bB(),Dfe=xc(),mct=ffe(),yct=xB(),Ect=Sk(),Ict=Wl(),Cct=Y4(),wct=Nk(),bfe=V4(),Bct=Bfe();function vct(t,e,r,s,a,n,c){var f=bfe(t,r),p=bfe(e,r),h=c.get(p);if(h){vfe(t,r,h);return}var E=n?n(f,p,r+\"\",t,e,c):void 0,C=E===void 0;if(C){var S=Dfe(p),P=!S&&yct(p),I=!S&&!P&&wct(p);E=p,S||P||I?Dfe(f)?E=f:mct(f)?E=gct(f):P?(C=!1,E=pct(p,!0)):I?(C=!1,E=hct(p,!0)):E=[]:Cct(p)||Sfe(p)?(E=f,Sfe(f)?E=Bct(f):(!Ict(f)||Ect(f))&&(E=dct(p))):C=!1}C&&(c.set(p,E),a(E,p,s,n,c),c.delete(p)),vfe(t,r,E)}Pfe.exports=vct});var Tfe=L((m7t,Qfe)=>{var Sct=Pk(),Dct=j4(),bct=zue(),Pct=xfe(),xct=Wl(),kct=jE(),Qct=V4();function kfe(t,e,r,s,a){t!==e&&bct(e,function(n,c){if(a||(a=new Sct),xct(n))Pct(t,e,c,r,kfe,s,a);else{var f=s?s(Qct(t,c),n,c+\"\",t,e,a):void 0;f===void 0&&(f=n),Dct(t,c,f)}},kct)}Qfe.exports=kfe});var K4=L((y7t,Rfe)=>{function Tct(t){return t}Rfe.exports=Tct});var Nfe=L((E7t,Ffe)=>{function Rct(t,e,r){switch(r.length){case 0:return t.call(e);case 1:return t.call(e,r[0]);case 2:return t.call(e,r[0],r[1]);case 3:return t.call(e,r[0],r[1],r[2])}return t.apply(e,r)}Ffe.exports=Rct});var J4=L((I7t,Lfe)=>{var Fct=Nfe(),Ofe=Math.max;function Nct(t,e,r){return e=Ofe(e===void 0?t.length-1:e,0),function(){for(var s=arguments,a=-1,n=Ofe(s.length-e,0),c=Array(n);++a<n;)c[a]=s[e+a];a=-1;for(var f=Array(e+1);++a<e;)f[a]=s[a];return f[e]=r(c),Fct(t,this,f)}}Lfe.exports=Nct});var _fe=L((C7t,Mfe)=>{function Oct(t){return function(){return t}}Mfe.exports=Oct});var jfe=L((w7t,Hfe)=>{var Lct=_fe(),Ufe=H4(),Mct=K4(),_ct=Ufe?function(t,e){return Ufe(t,\"toString\",{configurable:!0,enumerable:!1,value:Lct(e),writable:!0})}:Mct;Hfe.exports=_ct});var Gfe=L((B7t,qfe)=>{var Uct=800,Hct=16,jct=Date.now;function qct(t){var e=0,r=0;return function(){var s=jct(),a=Hct-(s-r);if(r=s,a>0){if(++e>=Uct)return arguments[0]}else e=0;return t.apply(void 0,arguments)}}qfe.exports=qct});var z4=L((v7t,Wfe)=>{var Gct=jfe(),Wct=Gfe(),Yct=Wct(Gct);Wfe.exports=Yct});var Vfe=L((S7t,Yfe)=>{var Vct=K4(),Kct=J4(),Jct=z4();function zct(t,e){return Jct(Kct(t,e,Vct),t+\"\")}Yfe.exports=zct});var Jfe=L((D7t,Kfe)=>{var Zct=RE(),Xct=RB(),$ct=kB(),eut=Wl();function tut(t,e,r){if(!eut(r))return!1;var s=typeof e;return(s==\"number\"?Xct(r)&&$ct(e,r.length):s==\"string\"&&e in r)?Zct(r[e],t):!1}Kfe.exports=tut});var Zfe=L((b7t,zfe)=>{var rut=Vfe(),nut=Jfe();function iut(t){return rut(function(e,r){var s=-1,a=r.length,n=a>1?r[a-1]:void 0,c=a>2?r[2]:void 0;for(n=t.length>3&&typeof n==\"function\"?(a--,n):void 0,c&&nut(r[0],r[1],c)&&(n=a<3?void 0:n,a=1),e=Object(e);++s<a;){var f=r[s];f&&t(e,f,s,n)}return e})}zfe.exports=iut});var $fe=L((P7t,Xfe)=>{var sut=Tfe(),out=Zfe(),aut=out(function(t,e,r,s){sut(t,e,r,s)});Xfe.exports=aut});var je={};Vt(je,{AsyncActions:()=>$4,BufferStream:()=>X4,CachingStrategy:()=>fAe,DefaultStream:()=>e3,allSettledSafe:()=>Uu,assertNever:()=>r3,bufferStream:()=>GE,buildIgnorePattern:()=>hut,convertMapsToIndexableObjects:()=>Wk,dynamicRequire:()=>kp,escapeRegExp:()=>cut,getArrayWithDefault:()=>LB,getFactoryWithDefault:()=>Vl,getMapWithDefault:()=>n3,getSetWithDefault:()=>xp,groupBy:()=>mut,isIndexableObject:()=>Z4,isPathLike:()=>gut,isTaggedYarnVersion:()=>lut,makeDeferred:()=>lAe,mapAndFilter:()=>Yl,mapAndFind:()=>A0,mergeIntoTarget:()=>pAe,overrideType:()=>uut,parseBoolean:()=>MB,parseInt:()=>WE,parseOptionalBoolean:()=>AAe,plural:()=>Gk,prettifyAsyncErrors:()=>qE,prettifySyncErrors:()=>i3,releaseAfterUseAsync:()=>Aut,replaceEnvVariables:()=>Yk,sortMap:()=>Ys,toMerged:()=>dut,tryParseOptionalBoolean:()=>s3,validateEnum:()=>fut});function lut(t){return!!(sAe.default.valid(t)&&t.match(/^[^-]+(-rc\\.[0-9]+)?$/))}function Gk(t,{one:e,more:r,zero:s=r}){return t===0?s:t===1?e:r}function cut(t){return t.replace(/[.*+?^${}()|[\\]\\\\]/g,\"\\\\$&\")}function uut(t){}function r3(t){throw new Error(`Assertion failed: Unexpected object '${t}'`)}function fut(t,e){let r=Object.values(t);if(!r.includes(e))throw new nt(`Invalid value for enumeration: ${JSON.stringify(e)} (expected one of ${r.map(s=>JSON.stringify(s)).join(\", \")})`);return e}function Yl(t,e){let r=[];for(let s of t){let a=e(s);a!==oAe&&r.push(a)}return r}function A0(t,e){for(let r of t){let s=e(r);if(s!==aAe)return s}}function Z4(t){return typeof t==\"object\"&&t!==null}async function Uu(t){let e=await Promise.allSettled(t),r=[];for(let s of e){if(s.status===\"rejected\")throw s.reason;r.push(s.value)}return r}function Wk(t){if(t instanceof Map&&(t=Object.fromEntries(t)),Z4(t))for(let e of Object.keys(t)){let r=t[e];Z4(r)&&(t[e]=Wk(r))}return t}function Vl(t,e,r){let s=t.get(e);return typeof s>\"u\"&&t.set(e,s=r()),s}function LB(t,e){let r=t.get(e);return typeof r>\"u\"&&t.set(e,r=[]),r}function xp(t,e){let r=t.get(e);return typeof r>\"u\"&&t.set(e,r=new Set),r}function n3(t,e){let r=t.get(e);return typeof r>\"u\"&&t.set(e,r=new Map),r}async function Aut(t,e){if(e==null)return await t();try{return await t()}finally{await e()}}async function qE(t,e){try{return await t()}catch(r){throw r.message=e(r.message),r}}function i3(t,e){try{return t()}catch(r){throw r.message=e(r.message),r}}async function GE(t){return await new Promise((e,r)=>{let s=[];t.on(\"error\",a=>{r(a)}),t.on(\"data\",a=>{s.push(a)}),t.on(\"end\",()=>{e(Buffer.concat(s))})})}function lAe(){let t,e;return{promise:new Promise((s,a)=>{t=s,e=a}),resolve:t,reject:e}}function cAe(t){return OB(ue.fromPortablePath(t))}function uAe(path){let physicalPath=ue.fromPortablePath(path),currentCacheEntry=OB.cache[physicalPath];delete OB.cache[physicalPath];let result;try{result=cAe(physicalPath);let freshCacheEntry=OB.cache[physicalPath],dynamicModule=eval(\"module\"),freshCacheIndex=dynamicModule.children.indexOf(freshCacheEntry);freshCacheIndex!==-1&&dynamicModule.children.splice(freshCacheIndex,1)}finally{OB.cache[physicalPath]=currentCacheEntry}return result}function put(t){let e=eAe.get(t),r=le.statSync(t);if(e?.mtime===r.mtimeMs)return e.instance;let s=uAe(t);return eAe.set(t,{mtime:r.mtimeMs,instance:s}),s}function kp(t,{cachingStrategy:e=2}={}){switch(e){case 0:return uAe(t);case 1:return put(t);case 2:return cAe(t);default:throw new Error(\"Unsupported caching strategy\")}}function Ys(t,e){let r=Array.from(t);Array.isArray(e)||(e=[e]);let s=[];for(let n of e)s.push(r.map(c=>n(c)));let a=r.map((n,c)=>c);return a.sort((n,c)=>{for(let f of s){let p=f[n]<f[c]?-1:f[n]>f[c]?1:0;if(p!==0)return p}return 0}),a.map(n=>r[n])}function hut(t){return t.length===0?null:t.map(e=>`(${nAe.default.makeRe(e,{windows:!1,dot:!0}).source})`).join(\"|\")}function Yk(t,{env:e}){let r=/\\${(?<variableName>[\\d\\w_]+)(?<colon>:)?(?:-(?<fallback>[^}]*))?}/g;return t.replace(r,(...s)=>{let{variableName:a,colon:n,fallback:c}=s[s.length-1],f=Object.hasOwn(e,a),p=e[a];if(p||f&&!n)return p;if(c!=null)return c;throw new nt(`Environment variable not found (${a})`)})}function MB(t){switch(t){case\"true\":case\"1\":case 1:case!0:return!0;case\"false\":case\"0\":case 0:case!1:return!1;default:throw new Error(`Couldn't parse \"${t}\" as a boolean`)}}function AAe(t){return typeof t>\"u\"?t:MB(t)}function s3(t){try{return AAe(t)}catch{return null}}function gut(t){return!!(ue.isAbsolute(t)||t.match(/^(\\.{1,2}|~)\\//))}function pAe(t,...e){let r=c=>({value:c}),s=r(t),a=e.map(c=>r(c)),{value:n}=(0,rAe.default)(s,...a,(c,f)=>{if(Array.isArray(c)&&Array.isArray(f)){for(let p of f)c.find(h=>(0,tAe.default)(h,p))||c.push(p);return c}});return n}function dut(...t){return pAe({},...t)}function mut(t,e){let r=Object.create(null);for(let s of t){let a=s[e];r[a]??=[],r[a].push(s)}return r}function WE(t){return typeof t==\"string\"?Number.parseInt(t,10):t}var tAe,rAe,nAe,iAe,sAe,t3,oAe,aAe,X4,$4,e3,OB,eAe,fAe,kc=Ct(()=>{bt();Wt();tAe=et(jue()),rAe=et($fe()),nAe=et(Sa()),iAe=et(Od()),sAe=et(Ai()),t3=Ie(\"stream\");oAe=Symbol();Yl.skip=oAe;aAe=Symbol();A0.skip=aAe;X4=class extends t3.Transform{constructor(){super(...arguments);this.chunks=[]}_transform(r,s,a){if(s!==\"buffer\"||!Buffer.isBuffer(r))throw new Error(\"Assertion failed: BufferStream only accept buffers\");this.chunks.push(r),a(null,null)}_flush(r){r(null,Buffer.concat(this.chunks))}};$4=class{constructor(e){this.deferred=new Map;this.promises=new Map;this.limit=(0,iAe.default)(e)}set(e,r){let s=this.deferred.get(e);typeof s>\"u\"&&this.deferred.set(e,s=lAe());let a=this.limit(()=>r());return this.promises.set(e,a),a.then(()=>{this.promises.get(e)===a&&s.resolve()},n=>{this.promises.get(e)===a&&s.reject(n)}),s.promise}reduce(e,r){let s=this.promises.get(e)??Promise.resolve();this.set(e,()=>r(s))}async wait(){await Promise.all(this.promises.values())}},e3=class extends t3.Transform{constructor(r=Buffer.alloc(0)){super();this.active=!0;this.ifEmpty=r}_transform(r,s,a){if(s!==\"buffer\"||!Buffer.isBuffer(r))throw new Error(\"Assertion failed: DefaultStream only accept buffers\");this.active=!1,a(null,r)}_flush(r){this.active&&this.ifEmpty.length>0?r(null,this.ifEmpty):r(null)}},OB=eval(\"require\");eAe=new Map;fAe=(s=>(s[s.NoCache=0]=\"NoCache\",s[s.FsTime=1]=\"FsTime\",s[s.Node=2]=\"Node\",s))(fAe||{})});var YE,o3,a3,hAe=Ct(()=>{YE=(r=>(r.HARD=\"HARD\",r.SOFT=\"SOFT\",r))(YE||{}),o3=(s=>(s.Dependency=\"Dependency\",s.PeerDependency=\"PeerDependency\",s.PeerDependencyMeta=\"PeerDependencyMeta\",s))(o3||{}),a3=(s=>(s.Inactive=\"inactive\",s.Redundant=\"redundant\",s.Active=\"active\",s))(a3||{})});var he={};Vt(he,{LogLevel:()=>Xk,Style:()=>Jk,Type:()=>pt,addLogFilterSupport:()=>HB,applyColor:()=>ri,applyHyperlink:()=>KE,applyStyle:()=>Kd,json:()=>Jd,jsonOrPretty:()=>Iut,mark:()=>A3,pretty:()=>Ut,prettyField:()=>Zf,prettyList:()=>f3,prettyTruncatedLocatorList:()=>Zk,stripAnsi:()=>VE.default,supportsColor:()=>zk,supportsHyperlinks:()=>u3,tuple:()=>Hu});function gAe(t){let e=[\"KiB\",\"MiB\",\"GiB\",\"TiB\"],r=e.length;for(;r>1&&t<1024**r;)r-=1;let s=1024**r;return`${Math.floor(t*100/s)/100} ${e[r-1]}`}function Vk(t,e){if(Array.isArray(e))return e.length===0?ri(t,\"[]\",pt.CODE):ri(t,\"[ \",pt.CODE)+e.map(r=>Vk(t,r)).join(\", \")+ri(t,\" ]\",pt.CODE);if(typeof e==\"string\")return ri(t,JSON.stringify(e),pt.STRING);if(typeof e==\"number\")return ri(t,JSON.stringify(e),pt.NUMBER);if(typeof e==\"boolean\")return ri(t,JSON.stringify(e),pt.BOOLEAN);if(e===null)return ri(t,\"null\",pt.NULL);if(typeof e==\"object\"&&Object.getPrototypeOf(e)===Object.prototype){let r=Object.entries(e);return r.length===0?ri(t,\"{}\",pt.CODE):ri(t,\"{ \",pt.CODE)+r.map(([s,a])=>`${Vk(t,s)}: ${Vk(t,a)}`).join(\", \")+ri(t,\" }\",pt.CODE)}if(typeof e>\"u\")return ri(t,\"undefined\",pt.NULL);throw new Error(\"Assertion failed: The value doesn't seem to be a valid JSON object\")}function Hu(t,e){return[e,t]}function Kd(t,e,r){return t.get(\"enableColors\")&&r&2&&(e=UB.default.bold(e)),e}function ri(t,e,r){if(!t.get(\"enableColors\"))return e;let s=yut.get(r);if(s===null)return e;let a=typeof s>\"u\"?r:c3.level>=3?s[0]:s[1],n=typeof a==\"number\"?l3.ansi256(a):a.startsWith(\"#\")?l3.hex(a):l3[a];if(typeof n!=\"function\")throw new Error(`Invalid format type ${a}`);return n(e)}function KE(t,e,r){return t.get(\"enableHyperlinks\")?Eut?`\\x1B]8;;${r}\\x1B\\\\${e}\\x1B]8;;\\x1B\\\\`:`\\x1B]8;;${r}\\x07${e}\\x1B]8;;\\x07`:e}function Ut(t,e,r){if(e===null)return ri(t,\"null\",pt.NULL);if(Object.hasOwn(Kk,r))return Kk[r].pretty(t,e);if(typeof e!=\"string\")throw new Error(`Assertion failed: Expected the value to be a string, got ${typeof e}`);return ri(t,e,r)}function f3(t,e,r,{separator:s=\", \"}={}){return[...e].map(a=>Ut(t,a,r)).join(s)}function Jd(t,e){if(t===null)return null;if(Object.hasOwn(Kk,e))return Kk[e].json(t);if(typeof t!=\"string\")throw new Error(`Assertion failed: Expected the value to be a string, got ${typeof t}`);return t}function Iut(t,e,[r,s]){return t?Jd(r,s):Ut(e,r,s)}function A3(t){return{Check:ri(t,\"\\u2713\",\"green\"),Cross:ri(t,\"\\u2718\",\"red\"),Question:ri(t,\"?\",\"cyan\")}}function Zf(t,{label:e,value:[r,s]}){return`${Ut(t,e,pt.CODE)}: ${Ut(t,r,s)}`}function Zk(t,e,r){let s=[],a=[...e],n=r;for(;a.length>0;){let h=a[0],E=`${Yr(t,h)}, `,C=p3(h).length+2;if(s.length>0&&n<C)break;s.push([E,C]),n-=C,a.shift()}if(a.length===0)return s.map(([h])=>h).join(\"\").slice(0,-2);let c=\"X\".repeat(a.length.toString().length),f=`and ${c} more.`,p=a.length;for(;s.length>1&&n<f.length;)n+=s[s.length-1][1],p+=1,s.pop();return[s.map(([h])=>h).join(\"\"),f.replace(c,Ut(t,p,pt.NUMBER))].join(\"\")}function HB(t,{configuration:e}){let r=e.get(\"logFilters\"),s=new Map,a=new Map,n=[];for(let C of r){let S=C.get(\"level\");if(typeof S>\"u\")continue;let P=C.get(\"code\");typeof P<\"u\"&&s.set(P,S);let I=C.get(\"text\");typeof I<\"u\"&&a.set(I,S);let R=C.get(\"pattern\");typeof R<\"u\"&&n.push([dAe.default.matcher(R,{contains:!0}),S])}n.reverse();let c=(C,S,P)=>{if(C===null||C===0)return P;let I=a.size>0||n.length>0?(0,VE.default)(S):S;if(a.size>0){let R=a.get(I);if(typeof R<\"u\")return R??P}if(n.length>0){for(let[R,N]of n)if(R(I))return N??P}if(s.size>0){let R=s.get(Vf(C));if(typeof R<\"u\")return R??P}return P},f=t.reportInfo,p=t.reportWarning,h=t.reportError,E=function(C,S,P,I){switch(c(S,P,I)){case\"info\":f.call(C,S,P);break;case\"warning\":p.call(C,S??0,P);break;case\"error\":h.call(C,S??0,P);break}};t.reportInfo=function(...C){return E(this,...C,\"info\")},t.reportWarning=function(...C){return E(this,...C,\"warning\")},t.reportError=function(...C){return E(this,...C,\"error\")}}var UB,_B,dAe,VE,pt,Jk,c3,zk,u3,l3,yut,Wo,Kk,Eut,Xk,Qc=Ct(()=>{bt();UB=et(kE()),_B=et(Rd());Wt();dAe=et(Sa()),VE=et(vk());Zx();Yo();pt={NO_HINT:\"NO_HINT\",ID:\"ID\",NULL:\"NULL\",SCOPE:\"SCOPE\",NAME:\"NAME\",RANGE:\"RANGE\",REFERENCE:\"REFERENCE\",NUMBER:\"NUMBER\",STRING:\"STRING\",BOOLEAN:\"BOOLEAN\",PATH:\"PATH\",URL:\"URL\",ADDED:\"ADDED\",REMOVED:\"REMOVED\",CODE:\"CODE\",INSPECT:\"INSPECT\",DURATION:\"DURATION\",SIZE:\"SIZE\",SIZE_DIFF:\"SIZE_DIFF\",IDENT:\"IDENT\",DESCRIPTOR:\"DESCRIPTOR\",LOCATOR:\"LOCATOR\",RESOLUTION:\"RESOLUTION\",DEPENDENT:\"DEPENDENT\",PACKAGE_EXTENSION:\"PACKAGE_EXTENSION\",SETTING:\"SETTING\",MARKDOWN:\"MARKDOWN\",MARKDOWN_INLINE:\"MARKDOWN_INLINE\"},Jk=(e=>(e[e.BOLD=2]=\"BOLD\",e))(Jk||{}),c3=_B.default.GITHUB_ACTIONS?{level:2}:UB.default.supportsColor?{level:UB.default.supportsColor.level}:{level:0},zk=c3.level!==0,u3=zk&&!_B.default.GITHUB_ACTIONS&&!_B.default.CIRCLE&&!_B.default.GITLAB,l3=new UB.default.Instance(c3),yut=new Map([[pt.NO_HINT,null],[pt.NULL,[\"#a853b5\",129]],[pt.SCOPE,[\"#d75f00\",166]],[pt.NAME,[\"#d7875f\",173]],[pt.RANGE,[\"#00afaf\",37]],[pt.REFERENCE,[\"#87afff\",111]],[pt.NUMBER,[\"#ffd700\",220]],[pt.STRING,[\"#b4bd68\",32]],[pt.BOOLEAN,[\"#faa023\",209]],[pt.PATH,[\"#d75fd7\",170]],[pt.URL,[\"#d75fd7\",170]],[pt.ADDED,[\"#5faf00\",70]],[pt.REMOVED,[\"#ff3131\",160]],[pt.CODE,[\"#87afff\",111]],[pt.SIZE,[\"#ffd700\",220]]]),Wo=t=>t;Kk={[pt.ID]:Wo({pretty:(t,e)=>typeof e==\"number\"?ri(t,`${e}`,pt.NUMBER):ri(t,e,pt.CODE),json:t=>t}),[pt.INSPECT]:Wo({pretty:(t,e)=>Vk(t,e),json:t=>t}),[pt.NUMBER]:Wo({pretty:(t,e)=>ri(t,`${e}`,pt.NUMBER),json:t=>t}),[pt.IDENT]:Wo({pretty:(t,e)=>es(t,e),json:t=>cn(t)}),[pt.LOCATOR]:Wo({pretty:(t,e)=>Yr(t,e),json:t=>cl(t)}),[pt.DESCRIPTOR]:Wo({pretty:(t,e)=>ni(t,e),json:t=>ll(t)}),[pt.RESOLUTION]:Wo({pretty:(t,{descriptor:e,locator:r})=>jB(t,e,r),json:({descriptor:t,locator:e})=>({descriptor:ll(t),locator:e!==null?cl(e):null})}),[pt.DEPENDENT]:Wo({pretty:(t,{locator:e,descriptor:r})=>h3(t,e,r),json:({locator:t,descriptor:e})=>({locator:cl(t),descriptor:ll(e)})}),[pt.PACKAGE_EXTENSION]:Wo({pretty:(t,e)=>{switch(e.type){case\"Dependency\":return`${es(t,e.parentDescriptor)} \\u27A4 ${ri(t,\"dependencies\",pt.CODE)} \\u27A4 ${es(t,e.descriptor)}`;case\"PeerDependency\":return`${es(t,e.parentDescriptor)} \\u27A4 ${ri(t,\"peerDependencies\",pt.CODE)} \\u27A4 ${es(t,e.descriptor)}`;case\"PeerDependencyMeta\":return`${es(t,e.parentDescriptor)} \\u27A4 ${ri(t,\"peerDependenciesMeta\",pt.CODE)} \\u27A4 ${es(t,Da(e.selector))} \\u27A4 ${ri(t,e.key,pt.CODE)}`;default:throw new Error(`Assertion failed: Unsupported package extension type: ${e.type}`)}},json:t=>{switch(t.type){case\"Dependency\":return`${cn(t.parentDescriptor)} > ${cn(t.descriptor)}`;case\"PeerDependency\":return`${cn(t.parentDescriptor)} >> ${cn(t.descriptor)}`;case\"PeerDependencyMeta\":return`${cn(t.parentDescriptor)} >> ${t.selector} / ${t.key}`;default:throw new Error(`Assertion failed: Unsupported package extension type: ${t.type}`)}}}),[pt.SETTING]:Wo({pretty:(t,e)=>(t.get(e),KE(t,ri(t,e,pt.CODE),`https://yarnpkg.com/configuration/yarnrc#${e}`)),json:t=>t}),[pt.DURATION]:Wo({pretty:(t,e)=>{if(e>1e3*60){let r=Math.floor(e/1e3/60),s=Math.ceil((e-r*60*1e3)/1e3);return s===0?`${r}m`:`${r}m ${s}s`}else{let r=Math.floor(e/1e3),s=e-r*1e3;return s===0?`${r}s`:`${r}s ${s}ms`}},json:t=>t}),[pt.SIZE]:Wo({pretty:(t,e)=>ri(t,gAe(e),pt.NUMBER),json:t=>t}),[pt.SIZE_DIFF]:Wo({pretty:(t,e)=>{let r=e>=0?\"+\":\"-\",s=r===\"+\"?pt.REMOVED:pt.ADDED;return ri(t,`${r} ${gAe(Math.max(Math.abs(e),1))}`,s)},json:t=>t}),[pt.PATH]:Wo({pretty:(t,e)=>ri(t,ue.fromPortablePath(e),pt.PATH),json:t=>ue.fromPortablePath(t)}),[pt.MARKDOWN]:Wo({pretty:(t,{text:e,format:r,paragraphs:s})=>qo(e,{format:r,paragraphs:s}),json:({text:t})=>t}),[pt.MARKDOWN_INLINE]:Wo({pretty:(t,e)=>(e=e.replace(/(`+)((?:.|[\\n])*?)\\1/g,(r,s,a)=>Ut(t,s+a+s,pt.CODE)),e=e.replace(/(\\*\\*)((?:.|[\\n])*?)\\1/g,(r,s,a)=>Kd(t,a,2)),e),json:t=>t})};Eut=!!process.env.KONSOLE_VERSION;Xk=(a=>(a.Error=\"error\",a.Warning=\"warning\",a.Info=\"info\",a.Discard=\"discard\",a))(Xk||{})});var mAe=L(JE=>{\"use strict\";Object.defineProperty(JE,\"__esModule\",{value:!0});JE.splitWhen=JE.flatten=void 0;function Cut(t){return t.reduce((e,r)=>[].concat(e,r),[])}JE.flatten=Cut;function wut(t,e){let r=[[]],s=0;for(let a of t)e(a)?(s++,r[s]=[]):r[s].push(a);return r}JE.splitWhen=wut});var yAe=L($k=>{\"use strict\";Object.defineProperty($k,\"__esModule\",{value:!0});$k.isEnoentCodeError=void 0;function But(t){return t.code===\"ENOENT\"}$k.isEnoentCodeError=But});var EAe=L(eQ=>{\"use strict\";Object.defineProperty(eQ,\"__esModule\",{value:!0});eQ.createDirentFromStats=void 0;var g3=class{constructor(e,r){this.name=e,this.isBlockDevice=r.isBlockDevice.bind(r),this.isCharacterDevice=r.isCharacterDevice.bind(r),this.isDirectory=r.isDirectory.bind(r),this.isFIFO=r.isFIFO.bind(r),this.isFile=r.isFile.bind(r),this.isSocket=r.isSocket.bind(r),this.isSymbolicLink=r.isSymbolicLink.bind(r)}};function vut(t,e){return new g3(t,e)}eQ.createDirentFromStats=vut});var BAe=L(us=>{\"use strict\";Object.defineProperty(us,\"__esModule\",{value:!0});us.convertPosixPathToPattern=us.convertWindowsPathToPattern=us.convertPathToPattern=us.escapePosixPath=us.escapeWindowsPath=us.escape=us.removeLeadingDotSegment=us.makeAbsolute=us.unixify=void 0;var Sut=Ie(\"os\"),Dut=Ie(\"path\"),IAe=Sut.platform()===\"win32\",but=2,Put=/(\\\\?)([()*?[\\]{|}]|^!|[!+@](?=\\()|\\\\(?![!()*+?@[\\]{|}]))/g,xut=/(\\\\?)([()[\\]{}]|^!|[!+@](?=\\())/g,kut=/^\\\\\\\\([.?])/,Qut=/\\\\(?![!()+@[\\]{}])/g;function Tut(t){return t.replace(/\\\\/g,\"/\")}us.unixify=Tut;function Rut(t,e){return Dut.resolve(t,e)}us.makeAbsolute=Rut;function Fut(t){if(t.charAt(0)===\".\"){let e=t.charAt(1);if(e===\"/\"||e===\"\\\\\")return t.slice(but)}return t}us.removeLeadingDotSegment=Fut;us.escape=IAe?d3:m3;function d3(t){return t.replace(xut,\"\\\\$2\")}us.escapeWindowsPath=d3;function m3(t){return t.replace(Put,\"\\\\$2\")}us.escapePosixPath=m3;us.convertPathToPattern=IAe?CAe:wAe;function CAe(t){return d3(t).replace(kut,\"//$1\").replace(Qut,\"/\")}us.convertWindowsPathToPattern=CAe;function wAe(t){return m3(t)}us.convertPosixPathToPattern=wAe});var SAe=L((q7t,vAe)=>{vAe.exports=function(e){if(typeof e!=\"string\"||e===\"\")return!1;for(var r;r=/(\\\\).|([@?!+*]\\(.*\\))/g.exec(e);){if(r[2])return!0;e=e.slice(r.index+r[0].length)}return!1}});var PAe=L((G7t,bAe)=>{var Nut=SAe(),DAe={\"{\":\"}\",\"(\":\")\",\"[\":\"]\"},Out=function(t){if(t[0]===\"!\")return!0;for(var e=0,r=-2,s=-2,a=-2,n=-2,c=-2;e<t.length;){if(t[e]===\"*\"||t[e+1]===\"?\"&&/[\\].+)]/.test(t[e])||s!==-1&&t[e]===\"[\"&&t[e+1]!==\"]\"&&(s<e&&(s=t.indexOf(\"]\",e)),s>e&&(c===-1||c>s||(c=t.indexOf(\"\\\\\",e),c===-1||c>s)))||a!==-1&&t[e]===\"{\"&&t[e+1]!==\"}\"&&(a=t.indexOf(\"}\",e),a>e&&(c=t.indexOf(\"\\\\\",e),c===-1||c>a))||n!==-1&&t[e]===\"(\"&&t[e+1]===\"?\"&&/[:!=]/.test(t[e+2])&&t[e+3]!==\")\"&&(n=t.indexOf(\")\",e),n>e&&(c=t.indexOf(\"\\\\\",e),c===-1||c>n))||r!==-1&&t[e]===\"(\"&&t[e+1]!==\"|\"&&(r<e&&(r=t.indexOf(\"|\",e)),r!==-1&&t[r+1]!==\")\"&&(n=t.indexOf(\")\",r),n>r&&(c=t.indexOf(\"\\\\\",r),c===-1||c>n))))return!0;if(t[e]===\"\\\\\"){var f=t[e+1];e+=2;var p=DAe[f];if(p){var h=t.indexOf(p,e);h!==-1&&(e=h+1)}if(t[e]===\"!\")return!0}else e++}return!1},Lut=function(t){if(t[0]===\"!\")return!0;for(var e=0;e<t.length;){if(/[*?{}()[\\]]/.test(t[e]))return!0;if(t[e]===\"\\\\\"){var r=t[e+1];e+=2;var s=DAe[r];if(s){var a=t.indexOf(s,e);a!==-1&&(e=a+1)}if(t[e]===\"!\")return!0}else e++}return!1};bAe.exports=function(e,r){if(typeof e!=\"string\"||e===\"\")return!1;if(Nut(e))return!0;var s=Out;return r&&r.strict===!1&&(s=Lut),s(e)}});var kAe=L((W7t,xAe)=>{\"use strict\";var Mut=PAe(),_ut=Ie(\"path\").posix.dirname,Uut=Ie(\"os\").platform()===\"win32\",y3=\"/\",Hut=/\\\\/g,jut=/[\\{\\[].*[\\}\\]]$/,qut=/(^|[^\\\\])([\\{\\[]|\\([^\\)]+$)/,Gut=/\\\\([\\!\\*\\?\\|\\[\\]\\(\\)\\{\\}])/g;xAe.exports=function(e,r){var s=Object.assign({flipBackslashes:!0},r);s.flipBackslashes&&Uut&&e.indexOf(y3)<0&&(e=e.replace(Hut,y3)),jut.test(e)&&(e+=y3),e+=\"a\";do e=_ut(e);while(Mut(e)||qut.test(e));return e.replace(Gut,\"$1\")}});var MAe=L(jr=>{\"use strict\";Object.defineProperty(jr,\"__esModule\",{value:!0});jr.removeDuplicateSlashes=jr.matchAny=jr.convertPatternsToRe=jr.makeRe=jr.getPatternParts=jr.expandBraceExpansion=jr.expandPatternsWithBraceExpansion=jr.isAffectDepthOfReadingPattern=jr.endsWithSlashGlobStar=jr.hasGlobStar=jr.getBaseDirectory=jr.isPatternRelatedToParentDirectory=jr.getPatternsOutsideCurrentDirectory=jr.getPatternsInsideCurrentDirectory=jr.getPositivePatterns=jr.getNegativePatterns=jr.isPositivePattern=jr.isNegativePattern=jr.convertToNegativePattern=jr.convertToPositivePattern=jr.isDynamicPattern=jr.isStaticPattern=void 0;var Wut=Ie(\"path\"),Yut=kAe(),E3=Sa(),QAe=\"**\",Vut=\"\\\\\",Kut=/[*?]|^!/,Jut=/\\[[^[]*]/,zut=/(?:^|[^!*+?@])\\([^(]*\\|[^|]*\\)/,Zut=/[!*+?@]\\([^(]*\\)/,Xut=/,|\\.\\./,$ut=/(?!^)\\/{2,}/g;function TAe(t,e={}){return!RAe(t,e)}jr.isStaticPattern=TAe;function RAe(t,e={}){return t===\"\"?!1:!!(e.caseSensitiveMatch===!1||t.includes(Vut)||Kut.test(t)||Jut.test(t)||zut.test(t)||e.extglob!==!1&&Zut.test(t)||e.braceExpansion!==!1&&eft(t))}jr.isDynamicPattern=RAe;function eft(t){let e=t.indexOf(\"{\");if(e===-1)return!1;let r=t.indexOf(\"}\",e+1);if(r===-1)return!1;let s=t.slice(e,r);return Xut.test(s)}function tft(t){return tQ(t)?t.slice(1):t}jr.convertToPositivePattern=tft;function rft(t){return\"!\"+t}jr.convertToNegativePattern=rft;function tQ(t){return t.startsWith(\"!\")&&t[1]!==\"(\"}jr.isNegativePattern=tQ;function FAe(t){return!tQ(t)}jr.isPositivePattern=FAe;function nft(t){return t.filter(tQ)}jr.getNegativePatterns=nft;function ift(t){return t.filter(FAe)}jr.getPositivePatterns=ift;function sft(t){return t.filter(e=>!I3(e))}jr.getPatternsInsideCurrentDirectory=sft;function oft(t){return t.filter(I3)}jr.getPatternsOutsideCurrentDirectory=oft;function I3(t){return t.startsWith(\"..\")||t.startsWith(\"./..\")}jr.isPatternRelatedToParentDirectory=I3;function aft(t){return Yut(t,{flipBackslashes:!1})}jr.getBaseDirectory=aft;function lft(t){return t.includes(QAe)}jr.hasGlobStar=lft;function NAe(t){return t.endsWith(\"/\"+QAe)}jr.endsWithSlashGlobStar=NAe;function cft(t){let e=Wut.basename(t);return NAe(t)||TAe(e)}jr.isAffectDepthOfReadingPattern=cft;function uft(t){return t.reduce((e,r)=>e.concat(OAe(r)),[])}jr.expandPatternsWithBraceExpansion=uft;function OAe(t){let e=E3.braces(t,{expand:!0,nodupes:!0,keepEscaping:!0});return e.sort((r,s)=>r.length-s.length),e.filter(r=>r!==\"\")}jr.expandBraceExpansion=OAe;function fft(t,e){let{parts:r}=E3.scan(t,Object.assign(Object.assign({},e),{parts:!0}));return r.length===0&&(r=[t]),r[0].startsWith(\"/\")&&(r[0]=r[0].slice(1),r.unshift(\"\")),r}jr.getPatternParts=fft;function LAe(t,e){return E3.makeRe(t,e)}jr.makeRe=LAe;function Aft(t,e){return t.map(r=>LAe(r,e))}jr.convertPatternsToRe=Aft;function pft(t,e){return e.some(r=>r.test(t))}jr.matchAny=pft;function hft(t){return t.replace($ut,\"/\")}jr.removeDuplicateSlashes=hft});var jAe=L((V7t,HAe)=>{\"use strict\";var gft=Ie(\"stream\"),_Ae=gft.PassThrough,dft=Array.prototype.slice;HAe.exports=mft;function mft(){let t=[],e=dft.call(arguments),r=!1,s=e[e.length-1];s&&!Array.isArray(s)&&s.pipe==null?e.pop():s={};let a=s.end!==!1,n=s.pipeError===!0;s.objectMode==null&&(s.objectMode=!0),s.highWaterMark==null&&(s.highWaterMark=64*1024);let c=_Ae(s);function f(){for(let E=0,C=arguments.length;E<C;E++)t.push(UAe(arguments[E],s));return p(),this}function p(){if(r)return;r=!0;let E=t.shift();if(!E){process.nextTick(h);return}Array.isArray(E)||(E=[E]);let C=E.length+1;function S(){--C>0||(r=!1,p())}function P(I){function R(){I.removeListener(\"merge2UnpipeEnd\",R),I.removeListener(\"end\",R),n&&I.removeListener(\"error\",N),S()}function N(U){c.emit(\"error\",U)}if(I._readableState.endEmitted)return S();I.on(\"merge2UnpipeEnd\",R),I.on(\"end\",R),n&&I.on(\"error\",N),I.pipe(c,{end:!1}),I.resume()}for(let I=0;I<E.length;I++)P(E[I]);S()}function h(){r=!1,c.emit(\"queueDrain\"),a&&c.end()}return c.setMaxListeners(0),c.add=f,c.on(\"unpipe\",function(E){E.emit(\"merge2UnpipeEnd\")}),e.length&&f.apply(null,e),c}function UAe(t,e){if(Array.isArray(t))for(let r=0,s=t.length;r<s;r++)t[r]=UAe(t[r],e);else{if(!t._readableState&&t.pipe&&(t=t.pipe(_Ae(e))),!t._readableState||!t.pause||!t.pipe)throw new Error(\"Only readable stream can be merged.\");t.pause()}return t}});var GAe=L(rQ=>{\"use strict\";Object.defineProperty(rQ,\"__esModule\",{value:!0});rQ.merge=void 0;var yft=jAe();function Eft(t){let e=yft(t);return t.forEach(r=>{r.once(\"error\",s=>e.emit(\"error\",s))}),e.once(\"close\",()=>qAe(t)),e.once(\"end\",()=>qAe(t)),e}rQ.merge=Eft;function qAe(t){t.forEach(e=>e.emit(\"close\"))}});var WAe=L(zE=>{\"use strict\";Object.defineProperty(zE,\"__esModule\",{value:!0});zE.isEmpty=zE.isString=void 0;function Ift(t){return typeof t==\"string\"}zE.isString=Ift;function Cft(t){return t===\"\"}zE.isEmpty=Cft});var Qp=L(Vo=>{\"use strict\";Object.defineProperty(Vo,\"__esModule\",{value:!0});Vo.string=Vo.stream=Vo.pattern=Vo.path=Vo.fs=Vo.errno=Vo.array=void 0;var wft=mAe();Vo.array=wft;var Bft=yAe();Vo.errno=Bft;var vft=EAe();Vo.fs=vft;var Sft=BAe();Vo.path=Sft;var Dft=MAe();Vo.pattern=Dft;var bft=GAe();Vo.stream=bft;var Pft=WAe();Vo.string=Pft});var JAe=L(Ko=>{\"use strict\";Object.defineProperty(Ko,\"__esModule\",{value:!0});Ko.convertPatternGroupToTask=Ko.convertPatternGroupsToTasks=Ko.groupPatternsByBaseDirectory=Ko.getNegativePatternsAsPositive=Ko.getPositivePatterns=Ko.convertPatternsToTasks=Ko.generate=void 0;var ju=Qp();function xft(t,e){let r=YAe(t,e),s=YAe(e.ignore,e),a=VAe(r),n=KAe(r,s),c=a.filter(E=>ju.pattern.isStaticPattern(E,e)),f=a.filter(E=>ju.pattern.isDynamicPattern(E,e)),p=C3(c,n,!1),h=C3(f,n,!0);return p.concat(h)}Ko.generate=xft;function YAe(t,e){let r=t;return e.braceExpansion&&(r=ju.pattern.expandPatternsWithBraceExpansion(r)),e.baseNameMatch&&(r=r.map(s=>s.includes(\"/\")?s:`**/${s}`)),r.map(s=>ju.pattern.removeDuplicateSlashes(s))}function C3(t,e,r){let s=[],a=ju.pattern.getPatternsOutsideCurrentDirectory(t),n=ju.pattern.getPatternsInsideCurrentDirectory(t),c=w3(a),f=w3(n);return s.push(...B3(c,e,r)),\".\"in f?s.push(v3(\".\",n,e,r)):s.push(...B3(f,e,r)),s}Ko.convertPatternsToTasks=C3;function VAe(t){return ju.pattern.getPositivePatterns(t)}Ko.getPositivePatterns=VAe;function KAe(t,e){return ju.pattern.getNegativePatterns(t).concat(e).map(ju.pattern.convertToPositivePattern)}Ko.getNegativePatternsAsPositive=KAe;function w3(t){let e={};return t.reduce((r,s)=>{let a=ju.pattern.getBaseDirectory(s);return a in r?r[a].push(s):r[a]=[s],r},e)}Ko.groupPatternsByBaseDirectory=w3;function B3(t,e,r){return Object.keys(t).map(s=>v3(s,t[s],e,r))}Ko.convertPatternGroupsToTasks=B3;function v3(t,e,r,s){return{dynamic:s,positive:e,negative:r,base:t,patterns:[].concat(e,r.map(ju.pattern.convertToNegativePattern))}}Ko.convertPatternGroupToTask=v3});var ZAe=L(nQ=>{\"use strict\";Object.defineProperty(nQ,\"__esModule\",{value:!0});nQ.read=void 0;function kft(t,e,r){e.fs.lstat(t,(s,a)=>{if(s!==null){zAe(r,s);return}if(!a.isSymbolicLink()||!e.followSymbolicLink){S3(r,a);return}e.fs.stat(t,(n,c)=>{if(n!==null){if(e.throwErrorOnBrokenSymbolicLink){zAe(r,n);return}S3(r,a);return}e.markSymbolicLink&&(c.isSymbolicLink=()=>!0),S3(r,c)})})}nQ.read=kft;function zAe(t,e){t(e)}function S3(t,e){t(null,e)}});var XAe=L(iQ=>{\"use strict\";Object.defineProperty(iQ,\"__esModule\",{value:!0});iQ.read=void 0;function Qft(t,e){let r=e.fs.lstatSync(t);if(!r.isSymbolicLink()||!e.followSymbolicLink)return r;try{let s=e.fs.statSync(t);return e.markSymbolicLink&&(s.isSymbolicLink=()=>!0),s}catch(s){if(!e.throwErrorOnBrokenSymbolicLink)return r;throw s}}iQ.read=Qft});var $Ae=L(p0=>{\"use strict\";Object.defineProperty(p0,\"__esModule\",{value:!0});p0.createFileSystemAdapter=p0.FILE_SYSTEM_ADAPTER=void 0;var sQ=Ie(\"fs\");p0.FILE_SYSTEM_ADAPTER={lstat:sQ.lstat,stat:sQ.stat,lstatSync:sQ.lstatSync,statSync:sQ.statSync};function Tft(t){return t===void 0?p0.FILE_SYSTEM_ADAPTER:Object.assign(Object.assign({},p0.FILE_SYSTEM_ADAPTER),t)}p0.createFileSystemAdapter=Tft});var epe=L(b3=>{\"use strict\";Object.defineProperty(b3,\"__esModule\",{value:!0});var Rft=$Ae(),D3=class{constructor(e={}){this._options=e,this.followSymbolicLink=this._getValue(this._options.followSymbolicLink,!0),this.fs=Rft.createFileSystemAdapter(this._options.fs),this.markSymbolicLink=this._getValue(this._options.markSymbolicLink,!1),this.throwErrorOnBrokenSymbolicLink=this._getValue(this._options.throwErrorOnBrokenSymbolicLink,!0)}_getValue(e,r){return e??r}};b3.default=D3});var zd=L(h0=>{\"use strict\";Object.defineProperty(h0,\"__esModule\",{value:!0});h0.statSync=h0.stat=h0.Settings=void 0;var tpe=ZAe(),Fft=XAe(),P3=epe();h0.Settings=P3.default;function Nft(t,e,r){if(typeof e==\"function\"){tpe.read(t,x3(),e);return}tpe.read(t,x3(e),r)}h0.stat=Nft;function Oft(t,e){let r=x3(e);return Fft.read(t,r)}h0.statSync=Oft;function x3(t={}){return t instanceof P3.default?t:new P3.default(t)}});var ipe=L((nKt,npe)=>{var rpe;npe.exports=typeof queueMicrotask==\"function\"?queueMicrotask.bind(typeof window<\"u\"?window:global):t=>(rpe||(rpe=Promise.resolve())).then(t).catch(e=>setTimeout(()=>{throw e},0))});var ope=L((iKt,spe)=>{spe.exports=Mft;var Lft=ipe();function Mft(t,e){let r,s,a,n=!0;Array.isArray(t)?(r=[],s=t.length):(a=Object.keys(t),r={},s=a.length);function c(p){function h(){e&&e(p,r),e=null}n?Lft(h):h()}function f(p,h,E){r[p]=E,(--s===0||h)&&c(h)}s?a?a.forEach(function(p){t[p](function(h,E){f(p,h,E)})}):t.forEach(function(p,h){p(function(E,C){f(h,E,C)})}):c(null),n=!1}});var k3=L(aQ=>{\"use strict\";Object.defineProperty(aQ,\"__esModule\",{value:!0});aQ.IS_SUPPORT_READDIR_WITH_FILE_TYPES=void 0;var oQ=process.versions.node.split(\".\");if(oQ[0]===void 0||oQ[1]===void 0)throw new Error(`Unexpected behavior. The 'process.versions.node' variable has invalid value: ${process.versions.node}`);var ape=Number.parseInt(oQ[0],10),_ft=Number.parseInt(oQ[1],10),lpe=10,Uft=10,Hft=ape>lpe,jft=ape===lpe&&_ft>=Uft;aQ.IS_SUPPORT_READDIR_WITH_FILE_TYPES=Hft||jft});var cpe=L(lQ=>{\"use strict\";Object.defineProperty(lQ,\"__esModule\",{value:!0});lQ.createDirentFromStats=void 0;var Q3=class{constructor(e,r){this.name=e,this.isBlockDevice=r.isBlockDevice.bind(r),this.isCharacterDevice=r.isCharacterDevice.bind(r),this.isDirectory=r.isDirectory.bind(r),this.isFIFO=r.isFIFO.bind(r),this.isFile=r.isFile.bind(r),this.isSocket=r.isSocket.bind(r),this.isSymbolicLink=r.isSymbolicLink.bind(r)}};function qft(t,e){return new Q3(t,e)}lQ.createDirentFromStats=qft});var T3=L(cQ=>{\"use strict\";Object.defineProperty(cQ,\"__esModule\",{value:!0});cQ.fs=void 0;var Gft=cpe();cQ.fs=Gft});var R3=L(uQ=>{\"use strict\";Object.defineProperty(uQ,\"__esModule\",{value:!0});uQ.joinPathSegments=void 0;function Wft(t,e,r){return t.endsWith(r)?t+e:t+r+e}uQ.joinPathSegments=Wft});var gpe=L(g0=>{\"use strict\";Object.defineProperty(g0,\"__esModule\",{value:!0});g0.readdir=g0.readdirWithFileTypes=g0.read=void 0;var Yft=zd(),upe=ope(),Vft=k3(),fpe=T3(),Ape=R3();function Kft(t,e,r){if(!e.stats&&Vft.IS_SUPPORT_READDIR_WITH_FILE_TYPES){ppe(t,e,r);return}hpe(t,e,r)}g0.read=Kft;function ppe(t,e,r){e.fs.readdir(t,{withFileTypes:!0},(s,a)=>{if(s!==null){fQ(r,s);return}let n=a.map(f=>({dirent:f,name:f.name,path:Ape.joinPathSegments(t,f.name,e.pathSegmentSeparator)}));if(!e.followSymbolicLinks){F3(r,n);return}let c=n.map(f=>Jft(f,e));upe(c,(f,p)=>{if(f!==null){fQ(r,f);return}F3(r,p)})})}g0.readdirWithFileTypes=ppe;function Jft(t,e){return r=>{if(!t.dirent.isSymbolicLink()){r(null,t);return}e.fs.stat(t.path,(s,a)=>{if(s!==null){if(e.throwErrorOnBrokenSymbolicLink){r(s);return}r(null,t);return}t.dirent=fpe.fs.createDirentFromStats(t.name,a),r(null,t)})}}function hpe(t,e,r){e.fs.readdir(t,(s,a)=>{if(s!==null){fQ(r,s);return}let n=a.map(c=>{let f=Ape.joinPathSegments(t,c,e.pathSegmentSeparator);return p=>{Yft.stat(f,e.fsStatSettings,(h,E)=>{if(h!==null){p(h);return}let C={name:c,path:f,dirent:fpe.fs.createDirentFromStats(c,E)};e.stats&&(C.stats=E),p(null,C)})}});upe(n,(c,f)=>{if(c!==null){fQ(r,c);return}F3(r,f)})})}g0.readdir=hpe;function fQ(t,e){t(e)}function F3(t,e){t(null,e)}});var Ipe=L(d0=>{\"use strict\";Object.defineProperty(d0,\"__esModule\",{value:!0});d0.readdir=d0.readdirWithFileTypes=d0.read=void 0;var zft=zd(),Zft=k3(),dpe=T3(),mpe=R3();function Xft(t,e){return!e.stats&&Zft.IS_SUPPORT_READDIR_WITH_FILE_TYPES?ype(t,e):Epe(t,e)}d0.read=Xft;function ype(t,e){return e.fs.readdirSync(t,{withFileTypes:!0}).map(s=>{let a={dirent:s,name:s.name,path:mpe.joinPathSegments(t,s.name,e.pathSegmentSeparator)};if(a.dirent.isSymbolicLink()&&e.followSymbolicLinks)try{let n=e.fs.statSync(a.path);a.dirent=dpe.fs.createDirentFromStats(a.name,n)}catch(n){if(e.throwErrorOnBrokenSymbolicLink)throw n}return a})}d0.readdirWithFileTypes=ype;function Epe(t,e){return e.fs.readdirSync(t).map(s=>{let a=mpe.joinPathSegments(t,s,e.pathSegmentSeparator),n=zft.statSync(a,e.fsStatSettings),c={name:s,path:a,dirent:dpe.fs.createDirentFromStats(s,n)};return e.stats&&(c.stats=n),c})}d0.readdir=Epe});var Cpe=L(m0=>{\"use strict\";Object.defineProperty(m0,\"__esModule\",{value:!0});m0.createFileSystemAdapter=m0.FILE_SYSTEM_ADAPTER=void 0;var ZE=Ie(\"fs\");m0.FILE_SYSTEM_ADAPTER={lstat:ZE.lstat,stat:ZE.stat,lstatSync:ZE.lstatSync,statSync:ZE.statSync,readdir:ZE.readdir,readdirSync:ZE.readdirSync};function $ft(t){return t===void 0?m0.FILE_SYSTEM_ADAPTER:Object.assign(Object.assign({},m0.FILE_SYSTEM_ADAPTER),t)}m0.createFileSystemAdapter=$ft});var wpe=L(O3=>{\"use strict\";Object.defineProperty(O3,\"__esModule\",{value:!0});var eAt=Ie(\"path\"),tAt=zd(),rAt=Cpe(),N3=class{constructor(e={}){this._options=e,this.followSymbolicLinks=this._getValue(this._options.followSymbolicLinks,!1),this.fs=rAt.createFileSystemAdapter(this._options.fs),this.pathSegmentSeparator=this._getValue(this._options.pathSegmentSeparator,eAt.sep),this.stats=this._getValue(this._options.stats,!1),this.throwErrorOnBrokenSymbolicLink=this._getValue(this._options.throwErrorOnBrokenSymbolicLink,!0),this.fsStatSettings=new tAt.Settings({followSymbolicLink:this.followSymbolicLinks,fs:this.fs,throwErrorOnBrokenSymbolicLink:this.throwErrorOnBrokenSymbolicLink})}_getValue(e,r){return e??r}};O3.default=N3});var AQ=L(y0=>{\"use strict\";Object.defineProperty(y0,\"__esModule\",{value:!0});y0.Settings=y0.scandirSync=y0.scandir=void 0;var Bpe=gpe(),nAt=Ipe(),L3=wpe();y0.Settings=L3.default;function iAt(t,e,r){if(typeof e==\"function\"){Bpe.read(t,M3(),e);return}Bpe.read(t,M3(e),r)}y0.scandir=iAt;function sAt(t,e){let r=M3(e);return nAt.read(t,r)}y0.scandirSync=sAt;function M3(t={}){return t instanceof L3.default?t:new L3.default(t)}});var Spe=L((hKt,vpe)=>{\"use strict\";function oAt(t){var e=new t,r=e;function s(){var n=e;return n.next?e=n.next:(e=new t,r=e),n.next=null,n}function a(n){r.next=n,r=n}return{get:s,release:a}}vpe.exports=oAt});var bpe=L((gKt,_3)=>{\"use strict\";var aAt=Spe();function Dpe(t,e,r){if(typeof t==\"function\"&&(r=e,e=t,t=null),!(r>=1))throw new Error(\"fastqueue concurrency must be equal to or greater than 1\");var s=aAt(lAt),a=null,n=null,c=0,f=null,p={push:R,drain:Tc,saturated:Tc,pause:E,paused:!1,get concurrency(){return r},set concurrency(Ae){if(!(Ae>=1))throw new Error(\"fastqueue concurrency must be equal to or greater than 1\");if(r=Ae,!p.paused)for(;a&&c<r;)c++,U()},running:h,resume:P,idle:I,length:C,getQueue:S,unshift:N,empty:Tc,kill:W,killAndDrain:te,error:ie};return p;function h(){return c}function E(){p.paused=!0}function C(){for(var Ae=a,ce=0;Ae;)Ae=Ae.next,ce++;return ce}function S(){for(var Ae=a,ce=[];Ae;)ce.push(Ae.value),Ae=Ae.next;return ce}function P(){if(p.paused){if(p.paused=!1,a===null){c++,U();return}for(;a&&c<r;)c++,U()}}function I(){return c===0&&p.length()===0}function R(Ae,ce){var me=s.get();me.context=t,me.release=U,me.value=Ae,me.callback=ce||Tc,me.errorHandler=f,c>=r||p.paused?n?(n.next=me,n=me):(a=me,n=me,p.saturated()):(c++,e.call(t,me.value,me.worked))}function N(Ae,ce){var me=s.get();me.context=t,me.release=U,me.value=Ae,me.callback=ce||Tc,me.errorHandler=f,c>=r||p.paused?a?(me.next=a,a=me):(a=me,n=me,p.saturated()):(c++,e.call(t,me.value,me.worked))}function U(Ae){Ae&&s.release(Ae);var ce=a;ce&&c<=r?p.paused?c--:(n===a&&(n=null),a=ce.next,ce.next=null,e.call(t,ce.value,ce.worked),n===null&&p.empty()):--c===0&&p.drain()}function W(){a=null,n=null,p.drain=Tc}function te(){a=null,n=null,p.drain(),p.drain=Tc}function ie(Ae){f=Ae}}function Tc(){}function lAt(){this.value=null,this.callback=Tc,this.next=null,this.release=Tc,this.context=null,this.errorHandler=null;var t=this;this.worked=function(r,s){var a=t.callback,n=t.errorHandler,c=t.value;t.value=null,t.callback=Tc,t.errorHandler&&n(r,c),a.call(t.context,r,s),t.release(t)}}function cAt(t,e,r){typeof t==\"function\"&&(r=e,e=t,t=null);function s(E,C){e.call(this,E).then(function(S){C(null,S)},C)}var a=Dpe(t,s,r),n=a.push,c=a.unshift;return a.push=f,a.unshift=p,a.drained=h,a;function f(E){var C=new Promise(function(S,P){n(E,function(I,R){if(I){P(I);return}S(R)})});return C.catch(Tc),C}function p(E){var C=new Promise(function(S,P){c(E,function(I,R){if(I){P(I);return}S(R)})});return C.catch(Tc),C}function h(){if(a.idle())return new Promise(function(S){S()});var E=a.drain,C=new Promise(function(S){a.drain=function(){E(),S()}});return C}}_3.exports=Dpe;_3.exports.promise=cAt});var pQ=L(Xf=>{\"use strict\";Object.defineProperty(Xf,\"__esModule\",{value:!0});Xf.joinPathSegments=Xf.replacePathSegmentSeparator=Xf.isAppliedFilter=Xf.isFatalError=void 0;function uAt(t,e){return t.errorFilter===null?!0:!t.errorFilter(e)}Xf.isFatalError=uAt;function fAt(t,e){return t===null||t(e)}Xf.isAppliedFilter=fAt;function AAt(t,e){return t.split(/[/\\\\]/).join(e)}Xf.replacePathSegmentSeparator=AAt;function pAt(t,e,r){return t===\"\"?e:t.endsWith(r)?t+e:t+r+e}Xf.joinPathSegments=pAt});var j3=L(H3=>{\"use strict\";Object.defineProperty(H3,\"__esModule\",{value:!0});var hAt=pQ(),U3=class{constructor(e,r){this._root=e,this._settings=r,this._root=hAt.replacePathSegmentSeparator(e,r.pathSegmentSeparator)}};H3.default=U3});var W3=L(G3=>{\"use strict\";Object.defineProperty(G3,\"__esModule\",{value:!0});var gAt=Ie(\"events\"),dAt=AQ(),mAt=bpe(),hQ=pQ(),yAt=j3(),q3=class extends yAt.default{constructor(e,r){super(e,r),this._settings=r,this._scandir=dAt.scandir,this._emitter=new gAt.EventEmitter,this._queue=mAt(this._worker.bind(this),this._settings.concurrency),this._isFatalError=!1,this._isDestroyed=!1,this._queue.drain=()=>{this._isFatalError||this._emitter.emit(\"end\")}}read(){return this._isFatalError=!1,this._isDestroyed=!1,setImmediate(()=>{this._pushToQueue(this._root,this._settings.basePath)}),this._emitter}get isDestroyed(){return this._isDestroyed}destroy(){if(this._isDestroyed)throw new Error(\"The reader is already destroyed\");this._isDestroyed=!0,this._queue.killAndDrain()}onEntry(e){this._emitter.on(\"entry\",e)}onError(e){this._emitter.once(\"error\",e)}onEnd(e){this._emitter.once(\"end\",e)}_pushToQueue(e,r){let s={directory:e,base:r};this._queue.push(s,a=>{a!==null&&this._handleError(a)})}_worker(e,r){this._scandir(e.directory,this._settings.fsScandirSettings,(s,a)=>{if(s!==null){r(s,void 0);return}for(let n of a)this._handleEntry(n,e.base);r(null,void 0)})}_handleError(e){this._isDestroyed||!hQ.isFatalError(this._settings,e)||(this._isFatalError=!0,this._isDestroyed=!0,this._emitter.emit(\"error\",e))}_handleEntry(e,r){if(this._isDestroyed||this._isFatalError)return;let s=e.path;r!==void 0&&(e.path=hQ.joinPathSegments(r,e.name,this._settings.pathSegmentSeparator)),hQ.isAppliedFilter(this._settings.entryFilter,e)&&this._emitEntry(e),e.dirent.isDirectory()&&hQ.isAppliedFilter(this._settings.deepFilter,e)&&this._pushToQueue(s,r===void 0?void 0:e.path)}_emitEntry(e){this._emitter.emit(\"entry\",e)}};G3.default=q3});var Ppe=L(V3=>{\"use strict\";Object.defineProperty(V3,\"__esModule\",{value:!0});var EAt=W3(),Y3=class{constructor(e,r){this._root=e,this._settings=r,this._reader=new EAt.default(this._root,this._settings),this._storage=[]}read(e){this._reader.onError(r=>{IAt(e,r)}),this._reader.onEntry(r=>{this._storage.push(r)}),this._reader.onEnd(()=>{CAt(e,this._storage)}),this._reader.read()}};V3.default=Y3;function IAt(t,e){t(e)}function CAt(t,e){t(null,e)}});var xpe=L(J3=>{\"use strict\";Object.defineProperty(J3,\"__esModule\",{value:!0});var wAt=Ie(\"stream\"),BAt=W3(),K3=class{constructor(e,r){this._root=e,this._settings=r,this._reader=new BAt.default(this._root,this._settings),this._stream=new wAt.Readable({objectMode:!0,read:()=>{},destroy:()=>{this._reader.isDestroyed||this._reader.destroy()}})}read(){return this._reader.onError(e=>{this._stream.emit(\"error\",e)}),this._reader.onEntry(e=>{this._stream.push(e)}),this._reader.onEnd(()=>{this._stream.push(null)}),this._reader.read(),this._stream}};J3.default=K3});var kpe=L(Z3=>{\"use strict\";Object.defineProperty(Z3,\"__esModule\",{value:!0});var vAt=AQ(),gQ=pQ(),SAt=j3(),z3=class extends SAt.default{constructor(){super(...arguments),this._scandir=vAt.scandirSync,this._storage=[],this._queue=new Set}read(){return this._pushToQueue(this._root,this._settings.basePath),this._handleQueue(),this._storage}_pushToQueue(e,r){this._queue.add({directory:e,base:r})}_handleQueue(){for(let e of this._queue.values())this._handleDirectory(e.directory,e.base)}_handleDirectory(e,r){try{let s=this._scandir(e,this._settings.fsScandirSettings);for(let a of s)this._handleEntry(a,r)}catch(s){this._handleError(s)}}_handleError(e){if(gQ.isFatalError(this._settings,e))throw e}_handleEntry(e,r){let s=e.path;r!==void 0&&(e.path=gQ.joinPathSegments(r,e.name,this._settings.pathSegmentSeparator)),gQ.isAppliedFilter(this._settings.entryFilter,e)&&this._pushToStorage(e),e.dirent.isDirectory()&&gQ.isAppliedFilter(this._settings.deepFilter,e)&&this._pushToQueue(s,r===void 0?void 0:e.path)}_pushToStorage(e){this._storage.push(e)}};Z3.default=z3});var Qpe=L($3=>{\"use strict\";Object.defineProperty($3,\"__esModule\",{value:!0});var DAt=kpe(),X3=class{constructor(e,r){this._root=e,this._settings=r,this._reader=new DAt.default(this._root,this._settings)}read(){return this._reader.read()}};$3.default=X3});var Tpe=L(t8=>{\"use strict\";Object.defineProperty(t8,\"__esModule\",{value:!0});var bAt=Ie(\"path\"),PAt=AQ(),e8=class{constructor(e={}){this._options=e,this.basePath=this._getValue(this._options.basePath,void 0),this.concurrency=this._getValue(this._options.concurrency,Number.POSITIVE_INFINITY),this.deepFilter=this._getValue(this._options.deepFilter,null),this.entryFilter=this._getValue(this._options.entryFilter,null),this.errorFilter=this._getValue(this._options.errorFilter,null),this.pathSegmentSeparator=this._getValue(this._options.pathSegmentSeparator,bAt.sep),this.fsScandirSettings=new PAt.Settings({followSymbolicLinks:this._options.followSymbolicLinks,fs:this._options.fs,pathSegmentSeparator:this._options.pathSegmentSeparator,stats:this._options.stats,throwErrorOnBrokenSymbolicLink:this._options.throwErrorOnBrokenSymbolicLink})}_getValue(e,r){return e??r}};t8.default=e8});var mQ=L($f=>{\"use strict\";Object.defineProperty($f,\"__esModule\",{value:!0});$f.Settings=$f.walkStream=$f.walkSync=$f.walk=void 0;var Rpe=Ppe(),xAt=xpe(),kAt=Qpe(),r8=Tpe();$f.Settings=r8.default;function QAt(t,e,r){if(typeof e==\"function\"){new Rpe.default(t,dQ()).read(e);return}new Rpe.default(t,dQ(e)).read(r)}$f.walk=QAt;function TAt(t,e){let r=dQ(e);return new kAt.default(t,r).read()}$f.walkSync=TAt;function RAt(t,e){let r=dQ(e);return new xAt.default(t,r).read()}$f.walkStream=RAt;function dQ(t={}){return t instanceof r8.default?t:new r8.default(t)}});var yQ=L(i8=>{\"use strict\";Object.defineProperty(i8,\"__esModule\",{value:!0});var FAt=Ie(\"path\"),NAt=zd(),Fpe=Qp(),n8=class{constructor(e){this._settings=e,this._fsStatSettings=new NAt.Settings({followSymbolicLink:this._settings.followSymbolicLinks,fs:this._settings.fs,throwErrorOnBrokenSymbolicLink:this._settings.followSymbolicLinks})}_getFullEntryPath(e){return FAt.resolve(this._settings.cwd,e)}_makeEntry(e,r){let s={name:r,path:r,dirent:Fpe.fs.createDirentFromStats(r,e)};return this._settings.stats&&(s.stats=e),s}_isFatalError(e){return!Fpe.errno.isEnoentCodeError(e)&&!this._settings.suppressErrors}};i8.default=n8});var a8=L(o8=>{\"use strict\";Object.defineProperty(o8,\"__esModule\",{value:!0});var OAt=Ie(\"stream\"),LAt=zd(),MAt=mQ(),_At=yQ(),s8=class extends _At.default{constructor(){super(...arguments),this._walkStream=MAt.walkStream,this._stat=LAt.stat}dynamic(e,r){return this._walkStream(e,r)}static(e,r){let s=e.map(this._getFullEntryPath,this),a=new OAt.PassThrough({objectMode:!0});a._write=(n,c,f)=>this._getEntry(s[n],e[n],r).then(p=>{p!==null&&r.entryFilter(p)&&a.push(p),n===s.length-1&&a.end(),f()}).catch(f);for(let n=0;n<s.length;n++)a.write(n);return a}_getEntry(e,r,s){return this._getStat(e).then(a=>this._makeEntry(a,r)).catch(a=>{if(s.errorFilter(a))return null;throw a})}_getStat(e){return new Promise((r,s)=>{this._stat(e,this._fsStatSettings,(a,n)=>a===null?r(n):s(a))})}};o8.default=s8});var Npe=L(c8=>{\"use strict\";Object.defineProperty(c8,\"__esModule\",{value:!0});var UAt=mQ(),HAt=yQ(),jAt=a8(),l8=class extends HAt.default{constructor(){super(...arguments),this._walkAsync=UAt.walk,this._readerStream=new jAt.default(this._settings)}dynamic(e,r){return new Promise((s,a)=>{this._walkAsync(e,r,(n,c)=>{n===null?s(c):a(n)})})}async static(e,r){let s=[],a=this._readerStream.static(e,r);return new Promise((n,c)=>{a.once(\"error\",c),a.on(\"data\",f=>s.push(f)),a.once(\"end\",()=>n(s))})}};c8.default=l8});var Ope=L(f8=>{\"use strict\";Object.defineProperty(f8,\"__esModule\",{value:!0});var qB=Qp(),u8=class{constructor(e,r,s){this._patterns=e,this._settings=r,this._micromatchOptions=s,this._storage=[],this._fillStorage()}_fillStorage(){for(let e of this._patterns){let r=this._getPatternSegments(e),s=this._splitSegmentsIntoSections(r);this._storage.push({complete:s.length<=1,pattern:e,segments:r,sections:s})}}_getPatternSegments(e){return qB.pattern.getPatternParts(e,this._micromatchOptions).map(s=>qB.pattern.isDynamicPattern(s,this._settings)?{dynamic:!0,pattern:s,patternRe:qB.pattern.makeRe(s,this._micromatchOptions)}:{dynamic:!1,pattern:s})}_splitSegmentsIntoSections(e){return qB.array.splitWhen(e,r=>r.dynamic&&qB.pattern.hasGlobStar(r.pattern))}};f8.default=u8});var Lpe=L(p8=>{\"use strict\";Object.defineProperty(p8,\"__esModule\",{value:!0});var qAt=Ope(),A8=class extends qAt.default{match(e){let r=e.split(\"/\"),s=r.length,a=this._storage.filter(n=>!n.complete||n.segments.length>s);for(let n of a){let c=n.sections[0];if(!n.complete&&s>c.length||r.every((p,h)=>{let E=n.segments[h];return!!(E.dynamic&&E.patternRe.test(p)||!E.dynamic&&E.pattern===p)}))return!0}return!1}};p8.default=A8});var Mpe=L(g8=>{\"use strict\";Object.defineProperty(g8,\"__esModule\",{value:!0});var EQ=Qp(),GAt=Lpe(),h8=class{constructor(e,r){this._settings=e,this._micromatchOptions=r}getFilter(e,r,s){let a=this._getMatcher(r),n=this._getNegativePatternsRe(s);return c=>this._filter(e,c,a,n)}_getMatcher(e){return new GAt.default(e,this._settings,this._micromatchOptions)}_getNegativePatternsRe(e){let r=e.filter(EQ.pattern.isAffectDepthOfReadingPattern);return EQ.pattern.convertPatternsToRe(r,this._micromatchOptions)}_filter(e,r,s,a){if(this._isSkippedByDeep(e,r.path)||this._isSkippedSymbolicLink(r))return!1;let n=EQ.path.removeLeadingDotSegment(r.path);return this._isSkippedByPositivePatterns(n,s)?!1:this._isSkippedByNegativePatterns(n,a)}_isSkippedByDeep(e,r){return this._settings.deep===1/0?!1:this._getEntryLevel(e,r)>=this._settings.deep}_getEntryLevel(e,r){let s=r.split(\"/\").length;if(e===\"\")return s;let a=e.split(\"/\").length;return s-a}_isSkippedSymbolicLink(e){return!this._settings.followSymbolicLinks&&e.dirent.isSymbolicLink()}_isSkippedByPositivePatterns(e,r){return!this._settings.baseNameMatch&&!r.match(e)}_isSkippedByNegativePatterns(e,r){return!EQ.pattern.matchAny(e,r)}};g8.default=h8});var _pe=L(m8=>{\"use strict\";Object.defineProperty(m8,\"__esModule\",{value:!0});var Zd=Qp(),d8=class{constructor(e,r){this._settings=e,this._micromatchOptions=r,this.index=new Map}getFilter(e,r){let s=Zd.pattern.convertPatternsToRe(e,this._micromatchOptions),a=Zd.pattern.convertPatternsToRe(r,Object.assign(Object.assign({},this._micromatchOptions),{dot:!0}));return n=>this._filter(n,s,a)}_filter(e,r,s){let a=Zd.path.removeLeadingDotSegment(e.path);if(this._settings.unique&&this._isDuplicateEntry(a)||this._onlyFileFilter(e)||this._onlyDirectoryFilter(e)||this._isSkippedByAbsoluteNegativePatterns(a,s))return!1;let n=e.dirent.isDirectory(),c=this._isMatchToPatterns(a,r,n)&&!this._isMatchToPatterns(a,s,n);return this._settings.unique&&c&&this._createIndexRecord(a),c}_isDuplicateEntry(e){return this.index.has(e)}_createIndexRecord(e){this.index.set(e,void 0)}_onlyFileFilter(e){return this._settings.onlyFiles&&!e.dirent.isFile()}_onlyDirectoryFilter(e){return this._settings.onlyDirectories&&!e.dirent.isDirectory()}_isSkippedByAbsoluteNegativePatterns(e,r){if(!this._settings.absolute)return!1;let s=Zd.path.makeAbsolute(this._settings.cwd,e);return Zd.pattern.matchAny(s,r)}_isMatchToPatterns(e,r,s){let a=Zd.pattern.matchAny(e,r);return!a&&s?Zd.pattern.matchAny(e+\"/\",r):a}};m8.default=d8});var Upe=L(E8=>{\"use strict\";Object.defineProperty(E8,\"__esModule\",{value:!0});var WAt=Qp(),y8=class{constructor(e){this._settings=e}getFilter(){return e=>this._isNonFatalError(e)}_isNonFatalError(e){return WAt.errno.isEnoentCodeError(e)||this._settings.suppressErrors}};E8.default=y8});var jpe=L(C8=>{\"use strict\";Object.defineProperty(C8,\"__esModule\",{value:!0});var Hpe=Qp(),I8=class{constructor(e){this._settings=e}getTransformer(){return e=>this._transform(e)}_transform(e){let r=e.path;return this._settings.absolute&&(r=Hpe.path.makeAbsolute(this._settings.cwd,r),r=Hpe.path.unixify(r)),this._settings.markDirectories&&e.dirent.isDirectory()&&(r+=\"/\"),this._settings.objectMode?Object.assign(Object.assign({},e),{path:r}):r}};C8.default=I8});var IQ=L(B8=>{\"use strict\";Object.defineProperty(B8,\"__esModule\",{value:!0});var YAt=Ie(\"path\"),VAt=Mpe(),KAt=_pe(),JAt=Upe(),zAt=jpe(),w8=class{constructor(e){this._settings=e,this.errorFilter=new JAt.default(this._settings),this.entryFilter=new KAt.default(this._settings,this._getMicromatchOptions()),this.deepFilter=new VAt.default(this._settings,this._getMicromatchOptions()),this.entryTransformer=new zAt.default(this._settings)}_getRootDirectory(e){return YAt.resolve(this._settings.cwd,e.base)}_getReaderOptions(e){let r=e.base===\".\"?\"\":e.base;return{basePath:r,pathSegmentSeparator:\"/\",concurrency:this._settings.concurrency,deepFilter:this.deepFilter.getFilter(r,e.positive,e.negative),entryFilter:this.entryFilter.getFilter(e.positive,e.negative),errorFilter:this.errorFilter.getFilter(),followSymbolicLinks:this._settings.followSymbolicLinks,fs:this._settings.fs,stats:this._settings.stats,throwErrorOnBrokenSymbolicLink:this._settings.throwErrorOnBrokenSymbolicLink,transform:this.entryTransformer.getTransformer()}}_getMicromatchOptions(){return{dot:this._settings.dot,matchBase:this._settings.baseNameMatch,nobrace:!this._settings.braceExpansion,nocase:!this._settings.caseSensitiveMatch,noext:!this._settings.extglob,noglobstar:!this._settings.globstar,posix:!0,strictSlashes:!1}}};B8.default=w8});var qpe=L(S8=>{\"use strict\";Object.defineProperty(S8,\"__esModule\",{value:!0});var ZAt=Npe(),XAt=IQ(),v8=class extends XAt.default{constructor(){super(...arguments),this._reader=new ZAt.default(this._settings)}async read(e){let r=this._getRootDirectory(e),s=this._getReaderOptions(e);return(await this.api(r,e,s)).map(n=>s.transform(n))}api(e,r,s){return r.dynamic?this._reader.dynamic(e,s):this._reader.static(r.patterns,s)}};S8.default=v8});var Gpe=L(b8=>{\"use strict\";Object.defineProperty(b8,\"__esModule\",{value:!0});var $At=Ie(\"stream\"),ept=a8(),tpt=IQ(),D8=class extends tpt.default{constructor(){super(...arguments),this._reader=new ept.default(this._settings)}read(e){let r=this._getRootDirectory(e),s=this._getReaderOptions(e),a=this.api(r,e,s),n=new $At.Readable({objectMode:!0,read:()=>{}});return a.once(\"error\",c=>n.emit(\"error\",c)).on(\"data\",c=>n.emit(\"data\",s.transform(c))).once(\"end\",()=>n.emit(\"end\")),n.once(\"close\",()=>a.destroy()),n}api(e,r,s){return r.dynamic?this._reader.dynamic(e,s):this._reader.static(r.patterns,s)}};b8.default=D8});var Wpe=L(x8=>{\"use strict\";Object.defineProperty(x8,\"__esModule\",{value:!0});var rpt=zd(),npt=mQ(),ipt=yQ(),P8=class extends ipt.default{constructor(){super(...arguments),this._walkSync=npt.walkSync,this._statSync=rpt.statSync}dynamic(e,r){return this._walkSync(e,r)}static(e,r){let s=[];for(let a of e){let n=this._getFullEntryPath(a),c=this._getEntry(n,a,r);c===null||!r.entryFilter(c)||s.push(c)}return s}_getEntry(e,r,s){try{let a=this._getStat(e);return this._makeEntry(a,r)}catch(a){if(s.errorFilter(a))return null;throw a}}_getStat(e){return this._statSync(e,this._fsStatSettings)}};x8.default=P8});var Ype=L(Q8=>{\"use strict\";Object.defineProperty(Q8,\"__esModule\",{value:!0});var spt=Wpe(),opt=IQ(),k8=class extends opt.default{constructor(){super(...arguments),this._reader=new spt.default(this._settings)}read(e){let r=this._getRootDirectory(e),s=this._getReaderOptions(e);return this.api(r,e,s).map(s.transform)}api(e,r,s){return r.dynamic?this._reader.dynamic(e,s):this._reader.static(r.patterns,s)}};Q8.default=k8});var Vpe=L($E=>{\"use strict\";Object.defineProperty($E,\"__esModule\",{value:!0});$E.DEFAULT_FILE_SYSTEM_ADAPTER=void 0;var XE=Ie(\"fs\"),apt=Ie(\"os\"),lpt=Math.max(apt.cpus().length,1);$E.DEFAULT_FILE_SYSTEM_ADAPTER={lstat:XE.lstat,lstatSync:XE.lstatSync,stat:XE.stat,statSync:XE.statSync,readdir:XE.readdir,readdirSync:XE.readdirSync};var T8=class{constructor(e={}){this._options=e,this.absolute=this._getValue(this._options.absolute,!1),this.baseNameMatch=this._getValue(this._options.baseNameMatch,!1),this.braceExpansion=this._getValue(this._options.braceExpansion,!0),this.caseSensitiveMatch=this._getValue(this._options.caseSensitiveMatch,!0),this.concurrency=this._getValue(this._options.concurrency,lpt),this.cwd=this._getValue(this._options.cwd,process.cwd()),this.deep=this._getValue(this._options.deep,1/0),this.dot=this._getValue(this._options.dot,!1),this.extglob=this._getValue(this._options.extglob,!0),this.followSymbolicLinks=this._getValue(this._options.followSymbolicLinks,!0),this.fs=this._getFileSystemMethods(this._options.fs),this.globstar=this._getValue(this._options.globstar,!0),this.ignore=this._getValue(this._options.ignore,[]),this.markDirectories=this._getValue(this._options.markDirectories,!1),this.objectMode=this._getValue(this._options.objectMode,!1),this.onlyDirectories=this._getValue(this._options.onlyDirectories,!1),this.onlyFiles=this._getValue(this._options.onlyFiles,!0),this.stats=this._getValue(this._options.stats,!1),this.suppressErrors=this._getValue(this._options.suppressErrors,!1),this.throwErrorOnBrokenSymbolicLink=this._getValue(this._options.throwErrorOnBrokenSymbolicLink,!1),this.unique=this._getValue(this._options.unique,!0),this.onlyDirectories&&(this.onlyFiles=!1),this.stats&&(this.objectMode=!0),this.ignore=[].concat(this.ignore)}_getValue(e,r){return e===void 0?r:e}_getFileSystemMethods(e={}){return Object.assign(Object.assign({},$E.DEFAULT_FILE_SYSTEM_ADAPTER),e)}};$E.default=T8});var CQ=L((UKt,Jpe)=>{\"use strict\";var Kpe=JAe(),cpt=qpe(),upt=Gpe(),fpt=Ype(),R8=Vpe(),Rc=Qp();async function F8(t,e){qu(t);let r=N8(t,cpt.default,e),s=await Promise.all(r);return Rc.array.flatten(s)}(function(t){t.glob=t,t.globSync=e,t.globStream=r,t.async=t;function e(h,E){qu(h);let C=N8(h,fpt.default,E);return Rc.array.flatten(C)}t.sync=e;function r(h,E){qu(h);let C=N8(h,upt.default,E);return Rc.stream.merge(C)}t.stream=r;function s(h,E){qu(h);let C=[].concat(h),S=new R8.default(E);return Kpe.generate(C,S)}t.generateTasks=s;function a(h,E){qu(h);let C=new R8.default(E);return Rc.pattern.isDynamicPattern(h,C)}t.isDynamicPattern=a;function n(h){return qu(h),Rc.path.escape(h)}t.escapePath=n;function c(h){return qu(h),Rc.path.convertPathToPattern(h)}t.convertPathToPattern=c;let f;(function(h){function E(S){return qu(S),Rc.path.escapePosixPath(S)}h.escapePath=E;function C(S){return qu(S),Rc.path.convertPosixPathToPattern(S)}h.convertPathToPattern=C})(f=t.posix||(t.posix={}));let p;(function(h){function E(S){return qu(S),Rc.path.escapeWindowsPath(S)}h.escapePath=E;function C(S){return qu(S),Rc.path.convertWindowsPathToPattern(S)}h.convertPathToPattern=C})(p=t.win32||(t.win32={}))})(F8||(F8={}));function N8(t,e,r){let s=[].concat(t),a=new R8.default(r),n=Kpe.generate(s,a),c=new e(a);return n.map(c.read,c)}function qu(t){if(![].concat(t).every(s=>Rc.string.isString(s)&&!Rc.string.isEmpty(s)))throw new TypeError(\"Patterns must be a string (non empty) or an array of strings\")}Jpe.exports=F8});var Nn={};Vt(Nn,{checksumFile:()=>BQ,checksumPattern:()=>vQ,makeHash:()=>fs});function fs(...t){let e=(0,wQ.createHash)(\"sha512\"),r=\"\";for(let s of t)typeof s==\"string\"?r+=s:s&&(r&&(e.update(r),r=\"\"),e.update(s));return r&&e.update(r),e.digest(\"hex\")}async function BQ(t,{baseFs:e,algorithm:r}={baseFs:le,algorithm:\"sha512\"}){let s=await e.openPromise(t,\"r\");try{let n=Buffer.allocUnsafeSlow(65536),c=(0,wQ.createHash)(r),f=0;for(;(f=await e.readPromise(s,n,0,65536))!==0;)c.update(f===65536?n:n.slice(0,f));return c.digest(\"hex\")}finally{await e.closePromise(s)}}async function vQ(t,{cwd:e}){let s=(await(0,O8.default)(t,{cwd:ue.fromPortablePath(e),onlyDirectories:!0})).map(f=>`${f}/**/*`),a=await(0,O8.default)([t,...s],{cwd:ue.fromPortablePath(e),onlyFiles:!1});a.sort();let n=await Promise.all(a.map(async f=>{let p=[Buffer.from(f)],h=K.join(e,ue.toPortablePath(f)),E=await le.lstatPromise(h);return E.isSymbolicLink()?p.push(Buffer.from(await le.readlinkPromise(h))):E.isFile()&&p.push(await le.readFilePromise(h)),p.join(\"\\0\")})),c=(0,wQ.createHash)(\"sha512\");for(let f of n)c.update(f);return c.digest(\"hex\")}var wQ,O8,E0=Ct(()=>{bt();wQ=Ie(\"crypto\"),O8=et(CQ())});var q={};Vt(q,{allPeerRequests:()=>XB,areDescriptorsEqual:()=>ehe,areIdentsEqual:()=>VB,areLocatorsEqual:()=>KB,areVirtualPackagesEquivalent:()=>Ipt,bindDescriptor:()=>ypt,bindLocator:()=>Ept,convertDescriptorToLocator:()=>SQ,convertLocatorToDescriptor:()=>M8,convertPackageToLocator:()=>gpt,convertToIdent:()=>hpt,convertToManifestRange:()=>kpt,copyPackage:()=>WB,devirtualizeDescriptor:()=>YB,devirtualizeLocator:()=>tI,ensureDevirtualizedDescriptor:()=>dpt,ensureDevirtualizedLocator:()=>mpt,getIdentVendorPath:()=>j8,isPackageCompatible:()=>kQ,isVirtualDescriptor:()=>Tp,isVirtualLocator:()=>Gu,makeDescriptor:()=>On,makeIdent:()=>ba,makeLocator:()=>Vs,makeRange:()=>PQ,parseDescriptor:()=>I0,parseFileStyleRange:()=>Ppt,parseIdent:()=>Da,parseLocator:()=>Rp,parseRange:()=>Xd,prettyDependent:()=>h3,prettyDescriptor:()=>ni,prettyIdent:()=>es,prettyLocator:()=>Yr,prettyLocatorNoColors:()=>p3,prettyRange:()=>nI,prettyReference:()=>zB,prettyResolution:()=>jB,prettyWorkspace:()=>ZB,renamePackage:()=>_8,slugifyIdent:()=>L8,slugifyLocator:()=>rI,sortDescriptors:()=>iI,stringifyDescriptor:()=>ll,stringifyIdent:()=>cn,stringifyLocator:()=>cl,tryParseDescriptor:()=>JB,tryParseIdent:()=>the,tryParseLocator:()=>bQ,tryParseRange:()=>bpt,unwrapIdentFromScope:()=>Tpt,virtualizeDescriptor:()=>U8,virtualizePackage:()=>H8,wrapIdentIntoScope:()=>Qpt});function ba(t,e){if(t?.startsWith(\"@\"))throw new Error(\"Invalid scope: don't prefix it with '@'\");return{identHash:fs(t,e),scope:t,name:e}}function On(t,e){return{identHash:t.identHash,scope:t.scope,name:t.name,descriptorHash:fs(t.identHash,e),range:e}}function Vs(t,e){return{identHash:t.identHash,scope:t.scope,name:t.name,locatorHash:fs(t.identHash,e),reference:e}}function hpt(t){return{identHash:t.identHash,scope:t.scope,name:t.name}}function SQ(t){return{identHash:t.identHash,scope:t.scope,name:t.name,locatorHash:t.descriptorHash,reference:t.range}}function M8(t){return{identHash:t.identHash,scope:t.scope,name:t.name,descriptorHash:t.locatorHash,range:t.reference}}function gpt(t){return{identHash:t.identHash,scope:t.scope,name:t.name,locatorHash:t.locatorHash,reference:t.reference}}function _8(t,e){return{identHash:e.identHash,scope:e.scope,name:e.name,locatorHash:e.locatorHash,reference:e.reference,version:t.version,languageName:t.languageName,linkType:t.linkType,conditions:t.conditions,dependencies:new Map(t.dependencies),peerDependencies:new Map(t.peerDependencies),dependenciesMeta:new Map(t.dependenciesMeta),peerDependenciesMeta:new Map(t.peerDependenciesMeta),bin:new Map(t.bin)}}function WB(t){return _8(t,t)}function U8(t,e){if(e.includes(\"#\"))throw new Error(\"Invalid entropy\");return On(t,`virtual:${e}#${t.range}`)}function H8(t,e){if(e.includes(\"#\"))throw new Error(\"Invalid entropy\");return _8(t,Vs(t,`virtual:${e}#${t.reference}`))}function Tp(t){return t.range.startsWith(GB)}function Gu(t){return t.reference.startsWith(GB)}function YB(t){if(!Tp(t))throw new Error(\"Not a virtual descriptor\");return On(t,t.range.replace(DQ,\"\"))}function tI(t){if(!Gu(t))throw new Error(\"Not a virtual descriptor\");return Vs(t,t.reference.replace(DQ,\"\"))}function dpt(t){return Tp(t)?On(t,t.range.replace(DQ,\"\")):t}function mpt(t){return Gu(t)?Vs(t,t.reference.replace(DQ,\"\")):t}function ypt(t,e){return t.range.includes(\"::\")?t:On(t,`${t.range}::${eI.default.stringify(e)}`)}function Ept(t,e){return t.reference.includes(\"::\")?t:Vs(t,`${t.reference}::${eI.default.stringify(e)}`)}function VB(t,e){return t.identHash===e.identHash}function ehe(t,e){return t.descriptorHash===e.descriptorHash}function KB(t,e){return t.locatorHash===e.locatorHash}function Ipt(t,e){if(!Gu(t))throw new Error(\"Invalid package type\");if(!Gu(e))throw new Error(\"Invalid package type\");if(!VB(t,e)||t.dependencies.size!==e.dependencies.size)return!1;for(let r of t.dependencies.values()){let s=e.dependencies.get(r.identHash);if(!s||!ehe(r,s))return!1}return!0}function Da(t){let e=the(t);if(!e)throw new Error(`Invalid ident (${t})`);return e}function the(t){let e=t.match(Cpt);if(!e)return null;let[,r,s]=e;return ba(typeof r<\"u\"?r:null,s)}function I0(t,e=!1){let r=JB(t,e);if(!r)throw new Error(`Invalid descriptor (${t})`);return r}function JB(t,e=!1){let r=e?t.match(wpt):t.match(Bpt);if(!r)return null;let[,s,a,n]=r;if(n===\"unknown\")throw new Error(`Invalid range (${t})`);let c=typeof s<\"u\"?s:null,f=typeof n<\"u\"?n:\"unknown\";return On(ba(c,a),f)}function Rp(t,e=!1){let r=bQ(t,e);if(!r)throw new Error(`Invalid locator (${t})`);return r}function bQ(t,e=!1){let r=e?t.match(vpt):t.match(Spt);if(!r)return null;let[,s,a,n]=r;if(n===\"unknown\")throw new Error(`Invalid reference (${t})`);let c=typeof s<\"u\"?s:null,f=typeof n<\"u\"?n:\"unknown\";return Vs(ba(c,a),f)}function Xd(t,e){let r=t.match(Dpt);if(r===null)throw new Error(`Invalid range (${t})`);let s=typeof r[1]<\"u\"?r[1]:null;if(typeof e?.requireProtocol==\"string\"&&s!==e.requireProtocol)throw new Error(`Invalid protocol (${s})`);if(e?.requireProtocol&&s===null)throw new Error(`Missing protocol (${s})`);let a=typeof r[3]<\"u\"?decodeURIComponent(r[2]):null;if(e?.requireSource&&a===null)throw new Error(`Missing source (${t})`);let n=typeof r[3]<\"u\"?decodeURIComponent(r[3]):decodeURIComponent(r[2]),c=e?.parseSelector?eI.default.parse(n):n,f=typeof r[4]<\"u\"?eI.default.parse(r[4]):null;return{protocol:s,source:a,selector:c,params:f}}function bpt(t,e){try{return Xd(t,e)}catch{return null}}function Ppt(t,{protocol:e}){let{selector:r,params:s}=Xd(t,{requireProtocol:e,requireBindings:!0});if(typeof s.locator!=\"string\")throw new Error(`Assertion failed: Invalid bindings for ${t}`);return{parentLocator:Rp(s.locator,!0),path:r}}function zpe(t){return t=t.replaceAll(\"%\",\"%25\"),t=t.replaceAll(\":\",\"%3A\"),t=t.replaceAll(\"#\",\"%23\"),t}function xpt(t){return t===null?!1:Object.entries(t).length>0}function PQ({protocol:t,source:e,selector:r,params:s}){let a=\"\";return t!==null&&(a+=`${t}`),e!==null&&(a+=`${zpe(e)}#`),a+=zpe(r),xpt(s)&&(a+=`::${eI.default.stringify(s)}`),a}function kpt(t){let{params:e,protocol:r,source:s,selector:a}=Xd(t);for(let n in e)n.startsWith(\"__\")&&delete e[n];return PQ({protocol:r,source:s,params:e,selector:a})}function cn(t){return t.scope?`@${t.scope}/${t.name}`:`${t.name}`}function Qpt(t,e){return t.scope?ba(e,`${t.scope}__${t.name}`):ba(e,t.name)}function Tpt(t,e){if(t.scope!==e)return t;let r=t.name.indexOf(\"__\");if(r===-1)return ba(null,t.name);let s=t.name.slice(0,r),a=t.name.slice(r+2);return ba(s,a)}function ll(t){return t.scope?`@${t.scope}/${t.name}@${t.range}`:`${t.name}@${t.range}`}function cl(t){return t.scope?`@${t.scope}/${t.name}@${t.reference}`:`${t.name}@${t.reference}`}function L8(t){return t.scope!==null?`@${t.scope}-${t.name}`:t.name}function rI(t){let{protocol:e,selector:r}=Xd(t.reference),s=e!==null?e.replace(Rpt,\"\"):\"exotic\",a=Zpe.default.valid(r),n=a!==null?`${s}-${a}`:`${s}`,c=10;return t.scope?`${L8(t)}-${n}-${t.locatorHash.slice(0,c)}`:`${L8(t)}-${n}-${t.locatorHash.slice(0,c)}`}function es(t,e){return e.scope?`${Ut(t,`@${e.scope}/`,pt.SCOPE)}${Ut(t,e.name,pt.NAME)}`:`${Ut(t,e.name,pt.NAME)}`}function xQ(t){if(t.startsWith(GB)){let e=xQ(t.substring(t.indexOf(\"#\")+1)),r=t.substring(GB.length,GB.length+Apt);return`${e} [${r}]`}else return t.replace(Fpt,\"?[...]\")}function nI(t,e){return`${Ut(t,xQ(e),pt.RANGE)}`}function ni(t,e){return`${es(t,e)}${Ut(t,\"@\",pt.RANGE)}${nI(t,e.range)}`}function zB(t,e){return`${Ut(t,xQ(e),pt.REFERENCE)}`}function Yr(t,e){return`${es(t,e)}${Ut(t,\"@\",pt.REFERENCE)}${zB(t,e.reference)}`}function p3(t){return`${cn(t)}@${xQ(t.reference)}`}function iI(t){return Ys(t,[e=>cn(e),e=>e.range])}function ZB(t,e){return es(t,e.anchoredLocator)}function jB(t,e,r){let s=Tp(e)?YB(e):e;return r===null?`${ni(t,s)} \\u2192 ${A3(t).Cross}`:s.identHash===r.identHash?`${ni(t,s)} \\u2192 ${zB(t,r.reference)}`:`${ni(t,s)} \\u2192 ${Yr(t,r)}`}function h3(t,e,r){return r===null?`${Yr(t,e)}`:`${Yr(t,e)} (via ${nI(t,r.range)})`}function j8(t){return`node_modules/${cn(t)}`}function kQ(t,e){return t.conditions?ppt(t.conditions,r=>{let[,s,a]=r.match($pe),n=e[s];return n?n.includes(a):!0}):!0}function XB(t){let e=new Set;if(\"children\"in t)e.add(t);else for(let r of t.requests.values())e.add(r);for(let r of e)for(let s of r.children.values())e.add(s);return e}var eI,Zpe,Xpe,GB,Apt,$pe,ppt,DQ,Cpt,wpt,Bpt,vpt,Spt,Dpt,Rpt,Fpt,Yo=Ct(()=>{eI=et(Ie(\"querystring\")),Zpe=et(Ai()),Xpe=et(Wse());Qc();E0();kc();Yo();GB=\"virtual:\",Apt=5,$pe=/(os|cpu|libc)=([a-z0-9_-]+)/,ppt=(0,Xpe.makeParser)($pe);DQ=/^[^#]*#/;Cpt=/^(?:@([^/]+?)\\/)?([^@/]+)$/;wpt=/^(?:@([^/]+?)\\/)?([^@/]+?)(?:@(.+))$/,Bpt=/^(?:@([^/]+?)\\/)?([^@/]+?)(?:@(.+))?$/;vpt=/^(?:@([^/]+?)\\/)?([^@/]+?)(?:@(.+))$/,Spt=/^(?:@([^/]+?)\\/)?([^@/]+?)(?:@(.+))?$/;Dpt=/^([^#:]*:)?((?:(?!::)[^#])*)(?:#((?:(?!::).)*))?(?:::(.*))?$/;Rpt=/:$/;Fpt=/\\?.*/});var rhe,nhe=Ct(()=>{Yo();rhe={hooks:{reduceDependency:(t,e,r,s,{resolver:a,resolveOptions:n})=>{for(let{pattern:c,reference:f}of e.topLevelWorkspace.manifest.resolutions){if(c.from&&(c.from.fullName!==cn(r)||e.configuration.normalizeLocator(Vs(Da(c.from.fullName),c.from.description??r.reference)).locatorHash!==r.locatorHash)||c.descriptor.fullName!==cn(t)||e.configuration.normalizeDependency(On(Rp(c.descriptor.fullName),c.descriptor.description??t.range)).descriptorHash!==t.descriptorHash)continue;return a.bindDescriptor(e.configuration.normalizeDependency(On(t,f)),e.topLevelWorkspace.anchoredLocator,n)}return t},validateProject:async(t,e)=>{for(let r of t.workspaces){let s=ZB(t.configuration,r);await t.configuration.triggerHook(a=>a.validateWorkspace,r,{reportWarning:(a,n)=>e.reportWarning(a,`${s}: ${n}`),reportError:(a,n)=>e.reportError(a,`${s}: ${n}`)})}},validateWorkspace:async(t,e)=>{let{manifest:r}=t;r.resolutions.length&&t.cwd!==t.project.cwd&&r.errors.push(new Error(\"Resolutions field will be ignored\"));for(let s of r.errors)e.reportWarning(57,s.message)}}}});var Ei,$d=Ct(()=>{Ei=class t{static{this.protocol=\"workspace:\"}supportsDescriptor(e,r){return!!(e.range.startsWith(t.protocol)||r.project.tryWorkspaceByDescriptor(e)!==null)}supportsLocator(e,r){return!!e.reference.startsWith(t.protocol)}shouldPersistResolution(e,r){return!1}bindDescriptor(e,r,s){return e}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){return[s.project.getWorkspaceByDescriptor(e).anchoredLocator]}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){let s=r.project.getWorkspaceByCwd(e.reference.slice(t.protocol.length));return{...e,version:s.manifest.version||\"0.0.0\",languageName:\"unknown\",linkType:\"SOFT\",conditions:null,dependencies:r.project.configuration.normalizeDependencyMap(new Map([...s.manifest.dependencies,...s.manifest.devDependencies])),peerDependencies:new Map([...s.manifest.peerDependencies]),dependenciesMeta:s.manifest.dependenciesMeta,peerDependenciesMeta:s.manifest.peerDependenciesMeta,bin:s.manifest.bin}}}});var Or={};Vt(Or,{SemVer:()=>lhe.SemVer,clean:()=>Opt,getComparator:()=>ohe,mergeComparators:()=>q8,satisfiesWithPrereleases:()=>eA,simplifyRanges:()=>G8,stringifyComparator:()=>ahe,validRange:()=>ul});function eA(t,e,r=!1){if(!t)return!1;let s=`${e}${r}`,a=ihe.get(s);if(typeof a>\"u\")try{a=new Fp.default.Range(e,{includePrerelease:!0,loose:r})}catch{return!1}finally{ihe.set(s,a||null)}else if(a===null)return!1;let n;try{n=new Fp.default.SemVer(t,a)}catch{return!1}return a.test(n)?!0:(n.prerelease&&(n.prerelease=[]),a.set.some(c=>{for(let f of c)f.semver.prerelease&&(f.semver.prerelease=[]);return c.every(f=>f.test(n))}))}function ul(t){if(t.indexOf(\":\")!==-1)return null;let e=she.get(t);if(typeof e<\"u\")return e;try{e=new Fp.default.Range(t)}catch{e=null}return she.set(t,e),e}function Opt(t){let e=Npt.exec(t);return e?e[1]:null}function ohe(t){if(t.semver===Fp.default.Comparator.ANY)return{gt:null,lt:null};switch(t.operator){case\"\":return{gt:[\">=\",t.semver],lt:[\"<=\",t.semver]};case\">\":case\">=\":return{gt:[t.operator,t.semver],lt:null};case\"<\":case\"<=\":return{gt:null,lt:[t.operator,t.semver]};default:throw new Error(`Assertion failed: Unexpected comparator operator (${t.operator})`)}}function q8(t){if(t.length===0)return null;let e=null,r=null;for(let s of t){if(s.gt){let a=e!==null?Fp.default.compare(s.gt[1],e[1]):null;(a===null||a>0||a===0&&s.gt[0]===\">\")&&(e=s.gt)}if(s.lt){let a=r!==null?Fp.default.compare(s.lt[1],r[1]):null;(a===null||a<0||a===0&&s.lt[0]===\"<\")&&(r=s.lt)}}if(e&&r){let s=Fp.default.compare(e[1],r[1]);if(s===0&&(e[0]===\">\"||r[0]===\"<\")||s>0)return null}return{gt:e,lt:r}}function ahe(t){if(t.gt&&t.lt){if(t.gt[0]===\">=\"&&t.lt[0]===\"<=\"&&t.gt[1].version===t.lt[1].version)return t.gt[1].version;if(t.gt[0]===\">=\"&&t.lt[0]===\"<\"){if(t.lt[1].version===`${t.gt[1].major+1}.0.0-0`)return`^${t.gt[1].version}`;if(t.lt[1].version===`${t.gt[1].major}.${t.gt[1].minor+1}.0-0`)return`~${t.gt[1].version}`}}let e=[];return t.gt&&e.push(t.gt[0]+t.gt[1].version),t.lt&&e.push(t.lt[0]+t.lt[1].version),e.length?e.join(\" \"):\"*\"}function G8(t){let e=t.map(Lpt).map(s=>ul(s).set.map(a=>a.map(n=>ohe(n)))),r=e.shift().map(s=>q8(s)).filter(s=>s!==null);for(let s of e){let a=[];for(let n of r)for(let c of s){let f=q8([n,...c]);f!==null&&a.push(f)}r=a}return r.length===0?null:r.map(s=>ahe(s)).join(\" || \")}function Lpt(t){let e=t.split(\"||\");if(e.length>1){let r=new Set;for(let s of e)e.some(a=>a!==s&&Fp.default.subset(s,a))||r.add(s);if(r.size<e.length)return[...r].join(\" || \")}return t}var Fp,lhe,ihe,she,Npt,Np=Ct(()=>{Fp=et(Ai()),lhe=et(Ai()),ihe=new Map;she=new Map;Npt=/^(?:[\\sv=]*?)((0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)(?:\\s*)$/});function che(t){let e=t.match(/^[ \\t]+/m);return e?e[0]:\"  \"}function uhe(t){return t.charCodeAt(0)===65279?t.slice(1):t}function Pa(t){return t.replace(/\\\\/g,\"/\")}function QQ(t,{yamlCompatibilityMode:e}){return e?s3(t):typeof t>\"u\"||typeof t==\"boolean\"?t:null}function fhe(t,e){let r=e.search(/[^!]/);if(r===-1)return\"invalid\";let s=r%2===0?\"\":\"!\",a=e.slice(r);return`${s}${t}=${a}`}function W8(t,e){return e.length===1?fhe(t,e[0]):`(${e.map(r=>fhe(t,r)).join(\" | \")})`}var Ahe,Ht,sI=Ct(()=>{bt();Bc();Ahe=et(Ai());$d();kc();Np();Yo();Ht=class t{constructor(){this.indent=\"  \";this.name=null;this.version=null;this.os=null;this.cpu=null;this.libc=null;this.type=null;this.packageManager=null;this.private=!1;this.license=null;this.main=null;this.module=null;this.browser=null;this.languageName=null;this.bin=new Map;this.scripts=new Map;this.dependencies=new Map;this.devDependencies=new Map;this.peerDependencies=new Map;this.workspaceDefinitions=[];this.dependenciesMeta=new Map;this.peerDependenciesMeta=new Map;this.resolutions=[];this.files=null;this.publishConfig=null;this.installConfig=null;this.preferUnplugged=null;this.raw={};this.errors=[]}static{this.fileName=\"package.json\"}static{this.allDependencies=[\"dependencies\",\"devDependencies\",\"peerDependencies\"]}static{this.hardDependencies=[\"dependencies\",\"devDependencies\"]}static async tryFind(e,{baseFs:r=new Yn}={}){let s=K.join(e,\"package.json\");try{return await t.fromFile(s,{baseFs:r})}catch(a){if(a.code===\"ENOENT\")return null;throw a}}static async find(e,{baseFs:r}={}){let s=await t.tryFind(e,{baseFs:r});if(s===null)throw new Error(\"Manifest not found\");return s}static async fromFile(e,{baseFs:r=new Yn}={}){let s=new t;return await s.loadFile(e,{baseFs:r}),s}static fromText(e){let r=new t;return r.loadFromText(e),r}loadFromText(e){let r;try{r=JSON.parse(uhe(e)||\"{}\")}catch(s){throw s.message+=` (when parsing ${e})`,s}this.load(r),this.indent=che(e)}async loadFile(e,{baseFs:r=new Yn}){let s=await r.readFilePromise(e,\"utf8\"),a;try{a=JSON.parse(uhe(s)||\"{}\")}catch(n){throw n.message+=` (when parsing ${e})`,n}this.load(a),this.indent=che(s)}load(e,{yamlCompatibilityMode:r=!1}={}){if(typeof e!=\"object\"||e===null)throw new Error(`Utterly invalid manifest data (${e})`);this.raw=e;let s=[];if(this.name=null,typeof e.name==\"string\")try{this.name=Da(e.name)}catch{s.push(new Error(\"Parsing failed for the 'name' field\"))}if(typeof e.version==\"string\"?this.version=e.version:this.version=null,Array.isArray(e.os)){let n=[];this.os=n;for(let c of e.os)typeof c!=\"string\"?s.push(new Error(\"Parsing failed for the 'os' field\")):n.push(c)}else this.os=null;if(Array.isArray(e.cpu)){let n=[];this.cpu=n;for(let c of e.cpu)typeof c!=\"string\"?s.push(new Error(\"Parsing failed for the 'cpu' field\")):n.push(c)}else this.cpu=null;if(Array.isArray(e.libc)){let n=[];this.libc=n;for(let c of e.libc)typeof c!=\"string\"?s.push(new Error(\"Parsing failed for the 'libc' field\")):n.push(c)}else this.libc=null;if(typeof e.type==\"string\"?this.type=e.type:this.type=null,typeof e.packageManager==\"string\"?this.packageManager=e.packageManager:this.packageManager=null,typeof e.private==\"boolean\"?this.private=e.private:this.private=!1,typeof e.license==\"string\"?this.license=e.license:this.license=null,typeof e.languageName==\"string\"?this.languageName=e.languageName:this.languageName=null,typeof e.main==\"string\"?this.main=Pa(e.main):this.main=null,typeof e.module==\"string\"?this.module=Pa(e.module):this.module=null,e.browser!=null)if(typeof e.browser==\"string\")this.browser=Pa(e.browser);else{this.browser=new Map;for(let[n,c]of Object.entries(e.browser))this.browser.set(Pa(n),typeof c==\"string\"?Pa(c):c)}else this.browser=null;if(this.bin=new Map,typeof e.bin==\"string\")e.bin.trim()===\"\"?s.push(new Error(\"Invalid bin field\")):this.name!==null?this.bin.set(this.name.name,Pa(e.bin)):s.push(new Error(\"String bin field, but no attached package name\"));else if(typeof e.bin==\"object\"&&e.bin!==null)for(let[n,c]of Object.entries(e.bin)){if(typeof c!=\"string\"||c.trim()===\"\"){s.push(new Error(`Invalid bin definition for '${n}'`));continue}let f=Da(n);this.bin.set(f.name,Pa(c))}if(this.scripts=new Map,typeof e.scripts==\"object\"&&e.scripts!==null)for(let[n,c]of Object.entries(e.scripts)){if(typeof c!=\"string\"){s.push(new Error(`Invalid script definition for '${n}'`));continue}this.scripts.set(n,c)}if(this.dependencies=new Map,typeof e.dependencies==\"object\"&&e.dependencies!==null)for(let[n,c]of Object.entries(e.dependencies)){if(typeof c!=\"string\"){s.push(new Error(`Invalid dependency range for '${n}'`));continue}let f;try{f=Da(n)}catch{s.push(new Error(`Parsing failed for the dependency name '${n}'`));continue}let p=On(f,c);this.dependencies.set(p.identHash,p)}if(this.devDependencies=new Map,typeof e.devDependencies==\"object\"&&e.devDependencies!==null)for(let[n,c]of Object.entries(e.devDependencies)){if(typeof c!=\"string\"){s.push(new Error(`Invalid dependency range for '${n}'`));continue}let f;try{f=Da(n)}catch{s.push(new Error(`Parsing failed for the dependency name '${n}'`));continue}let p=On(f,c);this.devDependencies.set(p.identHash,p)}if(this.peerDependencies=new Map,typeof e.peerDependencies==\"object\"&&e.peerDependencies!==null)for(let[n,c]of Object.entries(e.peerDependencies)){let f;try{f=Da(n)}catch{s.push(new Error(`Parsing failed for the dependency name '${n}'`));continue}(typeof c!=\"string\"||!c.startsWith(Ei.protocol)&&!ul(c))&&(s.push(new Error(`Invalid dependency range for '${n}'`)),c=\"*\");let p=On(f,c);this.peerDependencies.set(p.identHash,p)}typeof e.workspaces==\"object\"&&e.workspaces!==null&&e.workspaces.nohoist&&s.push(new Error(\"'nohoist' is deprecated, please use 'installConfig.hoistingLimits' instead\"));let a=Array.isArray(e.workspaces)?e.workspaces:typeof e.workspaces==\"object\"&&e.workspaces!==null&&Array.isArray(e.workspaces.packages)?e.workspaces.packages:[];this.workspaceDefinitions=[];for(let n of a){if(typeof n!=\"string\"){s.push(new Error(`Invalid workspace definition for '${n}'`));continue}this.workspaceDefinitions.push({pattern:n})}if(this.dependenciesMeta=new Map,typeof e.dependenciesMeta==\"object\"&&e.dependenciesMeta!==null)for(let[n,c]of Object.entries(e.dependenciesMeta)){if(typeof c!=\"object\"||c===null){s.push(new Error(`Invalid meta field for '${n}`));continue}let f=I0(n),p=this.ensureDependencyMeta(f),h=QQ(c.built,{yamlCompatibilityMode:r});if(h===null){s.push(new Error(`Invalid built meta field for '${n}'`));continue}let E=QQ(c.optional,{yamlCompatibilityMode:r});if(E===null){s.push(new Error(`Invalid optional meta field for '${n}'`));continue}let C=QQ(c.unplugged,{yamlCompatibilityMode:r});if(C===null){s.push(new Error(`Invalid unplugged meta field for '${n}'`));continue}Object.assign(p,{built:h,optional:E,unplugged:C})}if(this.peerDependenciesMeta=new Map,typeof e.peerDependenciesMeta==\"object\"&&e.peerDependenciesMeta!==null)for(let[n,c]of Object.entries(e.peerDependenciesMeta)){if(typeof c!=\"object\"||c===null){s.push(new Error(`Invalid meta field for '${n}'`));continue}let f=I0(n),p=this.ensurePeerDependencyMeta(f),h=QQ(c.optional,{yamlCompatibilityMode:r});if(h===null){s.push(new Error(`Invalid optional meta field for '${n}'`));continue}Object.assign(p,{optional:h})}if(this.resolutions=[],typeof e.resolutions==\"object\"&&e.resolutions!==null)for(let[n,c]of Object.entries(e.resolutions)){if(typeof c!=\"string\"){s.push(new Error(`Invalid resolution entry for '${n}'`));continue}try{this.resolutions.push({pattern:Cx(n),reference:c})}catch(f){s.push(f);continue}}if(Array.isArray(e.files)){this.files=new Set;for(let n of e.files){if(typeof n!=\"string\"){s.push(new Error(`Invalid files entry for '${n}'`));continue}this.files.add(n)}}else this.files=null;if(typeof e.publishConfig==\"object\"&&e.publishConfig!==null){if(this.publishConfig={},typeof e.publishConfig.access==\"string\"&&(this.publishConfig.access=e.publishConfig.access),typeof e.publishConfig.main==\"string\"&&(this.publishConfig.main=Pa(e.publishConfig.main)),typeof e.publishConfig.module==\"string\"&&(this.publishConfig.module=Pa(e.publishConfig.module)),e.publishConfig.browser!=null)if(typeof e.publishConfig.browser==\"string\")this.publishConfig.browser=Pa(e.publishConfig.browser);else{this.publishConfig.browser=new Map;for(let[n,c]of Object.entries(e.publishConfig.browser))this.publishConfig.browser.set(Pa(n),typeof c==\"string\"?Pa(c):c)}if(typeof e.publishConfig.registry==\"string\"&&(this.publishConfig.registry=e.publishConfig.registry),typeof e.publishConfig.provenance==\"boolean\"&&(this.publishConfig.provenance=e.publishConfig.provenance),typeof e.publishConfig.bin==\"string\")this.name!==null?this.publishConfig.bin=new Map([[this.name.name,Pa(e.publishConfig.bin)]]):s.push(new Error(\"String bin field, but no attached package name\"));else if(typeof e.publishConfig.bin==\"object\"&&e.publishConfig.bin!==null){this.publishConfig.bin=new Map;for(let[n,c]of Object.entries(e.publishConfig.bin)){if(typeof c!=\"string\"){s.push(new Error(`Invalid bin definition for '${n}'`));continue}this.publishConfig.bin.set(n,Pa(c))}}if(Array.isArray(e.publishConfig.executableFiles)){this.publishConfig.executableFiles=new Set;for(let n of e.publishConfig.executableFiles){if(typeof n!=\"string\"){s.push(new Error(\"Invalid executable file definition\"));continue}this.publishConfig.executableFiles.add(Pa(n))}}}else this.publishConfig=null;if(typeof e.installConfig==\"object\"&&e.installConfig!==null){this.installConfig={};for(let n of Object.keys(e.installConfig))n===\"hoistingLimits\"?typeof e.installConfig.hoistingLimits==\"string\"?this.installConfig.hoistingLimits=e.installConfig.hoistingLimits:s.push(new Error(\"Invalid hoisting limits definition\")):n==\"selfReferences\"?typeof e.installConfig.selfReferences==\"boolean\"?this.installConfig.selfReferences=e.installConfig.selfReferences:s.push(new Error(\"Invalid selfReferences definition, must be a boolean value\")):s.push(new Error(`Unrecognized installConfig key: ${n}`))}else this.installConfig=null;if(typeof e.optionalDependencies==\"object\"&&e.optionalDependencies!==null)for(let[n,c]of Object.entries(e.optionalDependencies)){if(typeof c!=\"string\"){s.push(new Error(`Invalid dependency range for '${n}'`));continue}let f;try{f=Da(n)}catch{s.push(new Error(`Parsing failed for the dependency name '${n}'`));continue}let p=On(f,c);this.dependencies.set(p.identHash,p);let h=On(f,\"unknown\"),E=this.ensureDependencyMeta(h);Object.assign(E,{optional:!0})}typeof e.preferUnplugged==\"boolean\"?this.preferUnplugged=e.preferUnplugged:this.preferUnplugged=null,this.errors=s}getForScope(e){switch(e){case\"dependencies\":return this.dependencies;case\"devDependencies\":return this.devDependencies;case\"peerDependencies\":return this.peerDependencies;default:throw new Error(`Unsupported value (\"${e}\")`)}}hasConsumerDependency(e){return!!(this.dependencies.has(e.identHash)||this.peerDependencies.has(e.identHash))}hasHardDependency(e){return!!(this.dependencies.has(e.identHash)||this.devDependencies.has(e.identHash))}hasSoftDependency(e){return!!this.peerDependencies.has(e.identHash)}hasDependency(e){return!!(this.hasHardDependency(e)||this.hasSoftDependency(e))}getConditions(){let e=[];return this.os&&this.os.length>0&&e.push(W8(\"os\",this.os)),this.cpu&&this.cpu.length>0&&e.push(W8(\"cpu\",this.cpu)),this.libc&&this.libc.length>0&&e.push(W8(\"libc\",this.libc)),e.length>0?e.join(\" & \"):null}ensureDependencyMeta(e){if(e.range!==\"unknown\"&&!Ahe.default.valid(e.range))throw new Error(`Invalid meta field range for '${ll(e)}'`);let r=cn(e),s=e.range!==\"unknown\"?e.range:null,a=this.dependenciesMeta.get(r);a||this.dependenciesMeta.set(r,a=new Map);let n=a.get(s);return n||a.set(s,n={}),n}ensurePeerDependencyMeta(e){if(e.range!==\"unknown\")throw new Error(`Invalid meta field range for '${ll(e)}'`);let r=cn(e),s=this.peerDependenciesMeta.get(r);return s||this.peerDependenciesMeta.set(r,s={}),s}setRawField(e,r,{after:s=[]}={}){let a=new Set(s.filter(n=>Object.hasOwn(this.raw,n)));if(a.size===0||Object.hasOwn(this.raw,e))this.raw[e]=r;else{let n=this.raw,c=this.raw={},f=!1;for(let p of Object.keys(n))c[p]=n[p],f||(a.delete(p),a.size===0&&(c[e]=r,f=!0))}}exportTo(e,{compatibilityMode:r=!0}={}){if(Object.assign(e,this.raw),this.name!==null?e.name=cn(this.name):delete e.name,this.version!==null?e.version=this.version:delete e.version,this.os!==null?e.os=this.os:delete e.os,this.cpu!==null?e.cpu=this.cpu:delete e.cpu,this.type!==null?e.type=this.type:delete e.type,this.packageManager!==null?e.packageManager=this.packageManager:delete e.packageManager,this.private?e.private=!0:delete e.private,this.license!==null?e.license=this.license:delete e.license,this.languageName!==null?e.languageName=this.languageName:delete e.languageName,this.main!==null?e.main=this.main:delete e.main,this.module!==null?e.module=this.module:delete e.module,this.browser!==null){let n=this.browser;typeof n==\"string\"?e.browser=n:n instanceof Map&&(e.browser=Object.assign({},...Array.from(n.keys()).sort().map(c=>({[c]:n.get(c)}))))}else delete e.browser;this.bin.size===1&&this.name!==null&&this.bin.has(this.name.name)?e.bin=this.bin.get(this.name.name):this.bin.size>0?e.bin=Object.assign({},...Array.from(this.bin.keys()).sort().map(n=>({[n]:this.bin.get(n)}))):delete e.bin,this.workspaceDefinitions.length>0?this.raw.workspaces&&!Array.isArray(this.raw.workspaces)?e.workspaces={...this.raw.workspaces,packages:this.workspaceDefinitions.map(({pattern:n})=>n)}:e.workspaces=this.workspaceDefinitions.map(({pattern:n})=>n):this.raw.workspaces&&!Array.isArray(this.raw.workspaces)&&Object.keys(this.raw.workspaces).length>0?e.workspaces=this.raw.workspaces:delete e.workspaces;let s=[],a=[];for(let n of this.dependencies.values()){let c=this.dependenciesMeta.get(cn(n)),f=!1;if(r&&c){let p=c.get(null);p&&p.optional&&(f=!0)}f?a.push(n):s.push(n)}s.length>0?e.dependencies=Object.assign({},...iI(s).map(n=>({[cn(n)]:n.range}))):delete e.dependencies,a.length>0?e.optionalDependencies=Object.assign({},...iI(a).map(n=>({[cn(n)]:n.range}))):delete e.optionalDependencies,this.devDependencies.size>0?e.devDependencies=Object.assign({},...iI(this.devDependencies.values()).map(n=>({[cn(n)]:n.range}))):delete e.devDependencies,this.peerDependencies.size>0?e.peerDependencies=Object.assign({},...iI(this.peerDependencies.values()).map(n=>({[cn(n)]:n.range}))):delete e.peerDependencies,e.dependenciesMeta={};for(let[n,c]of Ys(this.dependenciesMeta.entries(),([f,p])=>f))for(let[f,p]of Ys(c.entries(),([h,E])=>h!==null?`0${h}`:\"1\")){let h=f!==null?ll(On(Da(n),f)):n,E={...p};r&&f===null&&delete E.optional,Object.keys(E).length!==0&&(e.dependenciesMeta[h]=E)}if(Object.keys(e.dependenciesMeta).length===0&&delete e.dependenciesMeta,this.peerDependenciesMeta.size>0?e.peerDependenciesMeta=Object.assign({},...Ys(this.peerDependenciesMeta.entries(),([n,c])=>n).map(([n,c])=>({[n]:c}))):delete e.peerDependenciesMeta,this.resolutions.length>0?e.resolutions=Object.assign({},...this.resolutions.map(({pattern:n,reference:c})=>({[wx(n)]:c}))):delete e.resolutions,this.files!==null?e.files=Array.from(this.files):delete e.files,this.preferUnplugged!==null?e.preferUnplugged=this.preferUnplugged:delete e.preferUnplugged,this.scripts!==null&&this.scripts.size>0){e.scripts??={};for(let n of Object.keys(e.scripts))this.scripts.has(n)||delete e.scripts[n];for(let[n,c]of this.scripts.entries())e.scripts[n]=c}else delete e.scripts;return e}}});var hhe=L((tJt,phe)=>{var Mpt=Pc(),_pt=function(){return Mpt.Date.now()};phe.exports=_pt});var dhe=L((rJt,ghe)=>{var Upt=/\\s/;function Hpt(t){for(var e=t.length;e--&&Upt.test(t.charAt(e)););return e}ghe.exports=Hpt});var yhe=L((nJt,mhe)=>{var jpt=dhe(),qpt=/^\\s+/;function Gpt(t){return t&&t.slice(0,jpt(t)+1).replace(qpt,\"\")}mhe.exports=Gpt});var oI=L((iJt,Ehe)=>{var Wpt=Wd(),Ypt=zf(),Vpt=\"[object Symbol]\";function Kpt(t){return typeof t==\"symbol\"||Ypt(t)&&Wpt(t)==Vpt}Ehe.exports=Kpt});var Bhe=L((sJt,whe)=>{var Jpt=yhe(),Ihe=Wl(),zpt=oI(),Che=NaN,Zpt=/^[-+]0x[0-9a-f]+$/i,Xpt=/^0b[01]+$/i,$pt=/^0o[0-7]+$/i,eht=parseInt;function tht(t){if(typeof t==\"number\")return t;if(zpt(t))return Che;if(Ihe(t)){var e=typeof t.valueOf==\"function\"?t.valueOf():t;t=Ihe(e)?e+\"\":e}if(typeof t!=\"string\")return t===0?t:+t;t=Jpt(t);var r=Xpt.test(t);return r||$pt.test(t)?eht(t.slice(2),r?2:8):Zpt.test(t)?Che:+t}whe.exports=tht});var Dhe=L((oJt,She)=>{var rht=Wl(),Y8=hhe(),vhe=Bhe(),nht=\"Expected a function\",iht=Math.max,sht=Math.min;function oht(t,e,r){var s,a,n,c,f,p,h=0,E=!1,C=!1,S=!0;if(typeof t!=\"function\")throw new TypeError(nht);e=vhe(e)||0,rht(r)&&(E=!!r.leading,C=\"maxWait\"in r,n=C?iht(vhe(r.maxWait)||0,e):n,S=\"trailing\"in r?!!r.trailing:S);function P(ce){var me=s,pe=a;return s=a=void 0,h=ce,c=t.apply(pe,me),c}function I(ce){return h=ce,f=setTimeout(U,e),E?P(ce):c}function R(ce){var me=ce-p,pe=ce-h,Be=e-me;return C?sht(Be,n-pe):Be}function N(ce){var me=ce-p,pe=ce-h;return p===void 0||me>=e||me<0||C&&pe>=n}function U(){var ce=Y8();if(N(ce))return W(ce);f=setTimeout(U,R(ce))}function W(ce){return f=void 0,S&&s?P(ce):(s=a=void 0,c)}function te(){f!==void 0&&clearTimeout(f),h=0,s=p=a=f=void 0}function ie(){return f===void 0?c:W(Y8())}function Ae(){var ce=Y8(),me=N(ce);if(s=arguments,a=this,p=ce,me){if(f===void 0)return I(p);if(C)return clearTimeout(f),f=setTimeout(U,e),P(p)}return f===void 0&&(f=setTimeout(U,e)),c}return Ae.cancel=te,Ae.flush=ie,Ae}She.exports=oht});var V8=L((aJt,bhe)=>{var aht=Dhe(),lht=Wl(),cht=\"Expected a function\";function uht(t,e,r){var s=!0,a=!0;if(typeof t!=\"function\")throw new TypeError(cht);return lht(r)&&(s=\"leading\"in r?!!r.leading:s,a=\"trailing\"in r?!!r.trailing:a),aht(t,e,{leading:s,maxWait:e,trailing:a})}bhe.exports=uht});function Aht(t){return typeof t.reportCode<\"u\"}var Phe,xhe,khe,fht,Yt,ho,Fc=Ct(()=>{Phe=et(V8()),xhe=Ie(\"stream\"),khe=Ie(\"string_decoder\"),fht=15,Yt=class extends Error{constructor(r,s,a){super(s);this.reportExtra=a;this.reportCode=r}};ho=class{constructor(){this.cacheHits=new Set;this.cacheMisses=new Set;this.reportedInfos=new Set;this.reportedWarnings=new Set;this.reportedErrors=new Set}getRecommendedLength(){return 180}reportCacheHit(e){this.cacheHits.add(e.locatorHash)}reportCacheMiss(e,r){this.cacheMisses.add(e.locatorHash)}static progressViaCounter(e){let r=0,s,a=new Promise(p=>{s=p}),n=p=>{let h=s;a=new Promise(E=>{s=E}),r=p,h()},c=(p=0)=>{n(r+1)},f=async function*(){for(;r<e;)await a,yield{progress:r/e}}();return{[Symbol.asyncIterator](){return f},hasProgress:!0,hasTitle:!1,set:n,tick:c}}static progressViaTitle(){let e,r,s=new Promise(c=>{r=c}),a=(0,Phe.default)(c=>{let f=r;s=new Promise(p=>{r=p}),e=c,f()},1e3/fht),n=async function*(){for(;;)await s,yield{title:e}}();return{[Symbol.asyncIterator](){return n},hasProgress:!1,hasTitle:!0,setTitle:a}}async startProgressPromise(e,r){let s=this.reportProgress(e);try{return await r(e)}finally{s.stop()}}startProgressSync(e,r){let s=this.reportProgress(e);try{return r(e)}finally{s.stop()}}reportInfoOnce(e,r,s){let a=s&&s.key?s.key:r;this.reportedInfos.has(a)||(this.reportedInfos.add(a),this.reportInfo(e,r),s?.reportExtra?.(this))}reportWarningOnce(e,r,s){let a=s&&s.key?s.key:r;this.reportedWarnings.has(a)||(this.reportedWarnings.add(a),this.reportWarning(e,r),s?.reportExtra?.(this))}reportErrorOnce(e,r,s){let a=s&&s.key?s.key:r;this.reportedErrors.has(a)||(this.reportedErrors.add(a),this.reportError(e,r),s?.reportExtra?.(this))}reportExceptionOnce(e){Aht(e)?this.reportErrorOnce(e.reportCode,e.message,{key:e,reportExtra:e.reportExtra}):this.reportErrorOnce(1,e.stack||e.message,{key:e})}createStreamReporter(e=null){let r=new xhe.PassThrough,s=new khe.StringDecoder,a=\"\";return r.on(\"data\",n=>{let c=s.write(n),f;do if(f=c.indexOf(`\n`),f!==-1){let p=a+c.substring(0,f);c=c.substring(f+1),a=\"\",e!==null?this.reportInfo(null,`${e} ${p}`):this.reportInfo(null,p)}while(f!==-1);a+=c}),r.on(\"end\",()=>{let n=s.end();n!==\"\"&&(e!==null?this.reportInfo(null,`${e} ${n}`):this.reportInfo(null,n))}),r}}});var aI,K8=Ct(()=>{Fc();Yo();aI=class{constructor(e){this.fetchers=e}supports(e,r){return!!this.tryFetcher(e,r)}getLocalPath(e,r){return this.getFetcher(e,r).getLocalPath(e,r)}async fetch(e,r){return await this.getFetcher(e,r).fetch(e,r)}tryFetcher(e,r){let s=this.fetchers.find(a=>a.supports(e,r));return s||null}getFetcher(e,r){let s=this.fetchers.find(a=>a.supports(e,r));if(!s)throw new Yt(11,`${Yr(r.project.configuration,e)} isn't supported by any available fetcher`);return s}}});var em,J8=Ct(()=>{Yo();em=class{constructor(e){this.resolvers=e.filter(r=>r)}supportsDescriptor(e,r){return!!this.tryResolverByDescriptor(e,r)}supportsLocator(e,r){return!!this.tryResolverByLocator(e,r)}shouldPersistResolution(e,r){return this.getResolverByLocator(e,r).shouldPersistResolution(e,r)}bindDescriptor(e,r,s){return this.getResolverByDescriptor(e,s).bindDescriptor(e,r,s)}getResolutionDependencies(e,r){return this.getResolverByDescriptor(e,r).getResolutionDependencies(e,r)}async getCandidates(e,r,s){return await this.getResolverByDescriptor(e,s).getCandidates(e,r,s)}async getSatisfying(e,r,s,a){return this.getResolverByDescriptor(e,a).getSatisfying(e,r,s,a)}async resolve(e,r){return await this.getResolverByLocator(e,r).resolve(e,r)}tryResolverByDescriptor(e,r){let s=this.resolvers.find(a=>a.supportsDescriptor(e,r));return s||null}getResolverByDescriptor(e,r){let s=this.resolvers.find(a=>a.supportsDescriptor(e,r));if(!s)throw new Error(`${ni(r.project.configuration,e)} isn't supported by any available resolver`);return s}tryResolverByLocator(e,r){let s=this.resolvers.find(a=>a.supportsLocator(e,r));return s||null}getResolverByLocator(e,r){let s=this.resolvers.find(a=>a.supportsLocator(e,r));if(!s)throw new Error(`${Yr(r.project.configuration,e)} isn't supported by any available resolver`);return s}}});var lI,z8=Ct(()=>{bt();Yo();lI=class{supports(e){return!!e.reference.startsWith(\"virtual:\")}getLocalPath(e,r){let s=e.reference.indexOf(\"#\");if(s===-1)throw new Error(\"Invalid virtual package reference\");let a=e.reference.slice(s+1),n=Vs(e,a);return r.fetcher.getLocalPath(n,r)}async fetch(e,r){let s=e.reference.indexOf(\"#\");if(s===-1)throw new Error(\"Invalid virtual package reference\");let a=e.reference.slice(s+1),n=Vs(e,a),c=await r.fetcher.fetch(n,r);return await this.ensureVirtualLink(e,c,r)}getLocatorFilename(e){return rI(e)}async ensureVirtualLink(e,r,s){let a=r.packageFs.getRealPath(),n=s.project.configuration.get(\"virtualFolder\"),c=this.getLocatorFilename(e),f=Ao.makeVirtualPath(n,c,a),p=new Hf(f,{baseFs:r.packageFs,pathUtils:K});return{...r,packageFs:p}}}});var TQ,Qhe=Ct(()=>{TQ=class t{static{this.protocol=\"virtual:\"}static isVirtualDescriptor(e){return!!e.range.startsWith(t.protocol)}static isVirtualLocator(e){return!!e.reference.startsWith(t.protocol)}supportsDescriptor(e,r){return t.isVirtualDescriptor(e)}supportsLocator(e,r){return t.isVirtualLocator(e)}shouldPersistResolution(e,r){return!1}bindDescriptor(e,r,s){throw new Error('Assertion failed: calling \"bindDescriptor\" on a virtual descriptor is unsupported')}getResolutionDependencies(e,r){throw new Error('Assertion failed: calling \"getResolutionDependencies\" on a virtual descriptor is unsupported')}async getCandidates(e,r,s){throw new Error('Assertion failed: calling \"getCandidates\" on a virtual descriptor is unsupported')}async getSatisfying(e,r,s,a){throw new Error('Assertion failed: calling \"getSatisfying\" on a virtual descriptor is unsupported')}async resolve(e,r){throw new Error('Assertion failed: calling \"resolve\" on a virtual locator is unsupported')}}});var cI,Z8=Ct(()=>{bt();$d();cI=class{supports(e){return!!e.reference.startsWith(Ei.protocol)}getLocalPath(e,r){return this.getWorkspace(e,r).cwd}async fetch(e,r){let s=this.getWorkspace(e,r).cwd;return{packageFs:new Sn(s),prefixPath:vt.dot,localPath:s}}getWorkspace(e,r){return r.project.getWorkspaceByCwd(e.reference.slice(Ei.protocol.length))}}});function $B(t){return typeof t==\"object\"&&t!==null&&!Array.isArray(t)}function The(t){return typeof t>\"u\"?3:$B(t)?0:Array.isArray(t)?1:2}function eH(t,e){return Object.hasOwn(t,e)}function hht(t){return $B(t)&&eH(t,\"onConflict\")&&typeof t.onConflict==\"string\"}function ght(t){if(typeof t>\"u\")return{onConflict:\"default\",value:t};if(!hht(t))return{onConflict:\"default\",value:t};if(eH(t,\"value\"))return t;let{onConflict:e,...r}=t;return{onConflict:e,value:r}}function Rhe(t,e){let r=$B(t)&&eH(t,e)?t[e]:void 0;return ght(r)}function uI(t,e){return[t,e,Fhe]}function tH(t){return Array.isArray(t)?t[2]===Fhe:!1}function X8(t,e){if($B(t)){let r={};for(let s of Object.keys(t))r[s]=X8(t[s],e);return uI(e,r)}return Array.isArray(t)?uI(e,t.map(r=>X8(r,e))):uI(e,t)}function $8(t,e,r,s,a){let n,c=[],f=a,p=0;for(let E=a-1;E>=s;--E){let[C,S]=t[E],{onConflict:P,value:I}=Rhe(S,r),R=The(I);if(R!==3){if(n??=R,R!==n||P===\"hardReset\"){p=f;break}if(R===2)return uI(C,I);if(c.unshift([C,I]),P===\"reset\"){p=E;break}P===\"extend\"&&E===s&&(s=0),f=E}}if(typeof n>\"u\")return null;let h=c.map(([E])=>E).join(\", \");switch(n){case 1:return uI(h,new Array().concat(...c.map(([E,C])=>C.map(S=>X8(S,E)))));case 0:{let E=Object.assign({},...c.map(([,R])=>R)),C=Object.keys(E),S={},P=t.map(([R,N])=>[R,Rhe(N,r).value]),I=pht(P,([R,N])=>{let U=The(N);return U!==0&&U!==3});if(I!==-1){let R=P.slice(I+1);for(let N of C)S[N]=$8(R,e,N,0,R.length)}else for(let R of C)S[R]=$8(P,e,R,p,P.length);return uI(h,S)}default:throw new Error(\"Assertion failed: Non-extendable value type\")}}function Nhe(t){return $8(t.map(([e,r])=>[e,{\".\":r}]),[],\".\",0,t.length)}function ev(t){return tH(t)?t[1]:t}function RQ(t){let e=tH(t)?t[1]:t;if(Array.isArray(e))return e.map(r=>RQ(r));if($B(e)){let r={};for(let[s,a]of Object.entries(e))r[s]=RQ(a);return r}return e}function rH(t){return tH(t)?t[0]:null}var pht,Fhe,Ohe=Ct(()=>{pht=(t,e,r)=>{let s=[...t];return s.reverse(),s.findIndex(e,r)};Fhe=Symbol()});var FQ={};Vt(FQ,{getDefaultGlobalFolder:()=>iH,getHomeFolder:()=>fI,isFolderInside:()=>sH});function iH(){if(process.platform===\"win32\"){let t=ue.toPortablePath(process.env.LOCALAPPDATA||ue.join((0,nH.homedir)(),\"AppData\",\"Local\"));return K.resolve(t,\"Yarn/Berry\")}if(process.env.XDG_DATA_HOME){let t=ue.toPortablePath(process.env.XDG_DATA_HOME);return K.resolve(t,\"yarn/berry\")}return K.resolve(fI(),\".yarn/berry\")}function fI(){return ue.toPortablePath((0,nH.homedir)()||\"/usr/local/share\")}function sH(t,e){let r=K.relative(e,t);return r&&!r.startsWith(\"..\")&&!K.isAbsolute(r)}var nH,NQ=Ct(()=>{bt();nH=Ie(\"os\")});var _he=L((IJt,Mhe)=>{\"use strict\";var oH=Ie(\"https\"),aH=Ie(\"http\"),{URL:Lhe}=Ie(\"url\"),lH=class extends aH.Agent{constructor(e){let{proxy:r,proxyRequestOptions:s,...a}=e;super(a),this.proxy=typeof r==\"string\"?new Lhe(r):r,this.proxyRequestOptions=s||{}}createConnection(e,r){let s={...this.proxyRequestOptions,method:\"CONNECT\",host:this.proxy.hostname,port:this.proxy.port,path:`${e.host}:${e.port}`,setHost:!1,headers:{...this.proxyRequestOptions.headers,connection:this.keepAlive?\"keep-alive\":\"close\",host:`${e.host}:${e.port}`},agent:!1,timeout:e.timeout||0};if(this.proxy.username||this.proxy.password){let n=Buffer.from(`${decodeURIComponent(this.proxy.username||\"\")}:${decodeURIComponent(this.proxy.password||\"\")}`).toString(\"base64\");s.headers[\"proxy-authorization\"]=`Basic ${n}`}this.proxy.protocol===\"https:\"&&(s.servername=this.proxy.hostname);let a=(this.proxy.protocol===\"http:\"?aH:oH).request(s);a.once(\"connect\",(n,c,f)=>{a.removeAllListeners(),c.removeAllListeners(),n.statusCode===200?r(null,c):(c.destroy(),r(new Error(`Bad response: ${n.statusCode}`),null))}),a.once(\"timeout\",()=>{a.destroy(new Error(\"Proxy timeout\"))}),a.once(\"error\",n=>{a.removeAllListeners(),r(n,null)}),a.end()}},cH=class extends oH.Agent{constructor(e){let{proxy:r,proxyRequestOptions:s,...a}=e;super(a),this.proxy=typeof r==\"string\"?new Lhe(r):r,this.proxyRequestOptions=s||{}}createConnection(e,r){let s={...this.proxyRequestOptions,method:\"CONNECT\",host:this.proxy.hostname,port:this.proxy.port,path:`${e.host}:${e.port}`,setHost:!1,headers:{...this.proxyRequestOptions.headers,connection:this.keepAlive?\"keep-alive\":\"close\",host:`${e.host}:${e.port}`},agent:!1,timeout:e.timeout||0};if(this.proxy.username||this.proxy.password){let n=Buffer.from(`${decodeURIComponent(this.proxy.username||\"\")}:${decodeURIComponent(this.proxy.password||\"\")}`).toString(\"base64\");s.headers[\"proxy-authorization\"]=`Basic ${n}`}this.proxy.protocol===\"https:\"&&(s.servername=this.proxy.hostname);let a=(this.proxy.protocol===\"http:\"?aH:oH).request(s);a.once(\"connect\",(n,c,f)=>{if(a.removeAllListeners(),c.removeAllListeners(),n.statusCode===200){let p=super.createConnection({...e,socket:c});r(null,p)}else c.destroy(),r(new Error(`Bad response: ${n.statusCode}`),null)}),a.once(\"timeout\",()=>{a.destroy(new Error(\"Proxy timeout\"))}),a.once(\"error\",n=>{a.removeAllListeners(),r(n,null)}),a.end()}};Mhe.exports={HttpProxyAgent:lH,HttpsProxyAgent:cH}});var uH,Uhe,Hhe,jhe=Ct(()=>{uH=et(_he(),1),Uhe=uH.default.HttpProxyAgent,Hhe=uH.default.HttpsProxyAgent});var Lp=L((Op,OQ)=>{\"use strict\";Object.defineProperty(Op,\"__esModule\",{value:!0});var qhe=[\"Int8Array\",\"Uint8Array\",\"Uint8ClampedArray\",\"Int16Array\",\"Uint16Array\",\"Int32Array\",\"Uint32Array\",\"Float32Array\",\"Float64Array\",\"BigInt64Array\",\"BigUint64Array\"];function mht(t){return qhe.includes(t)}var yht=[\"Function\",\"Generator\",\"AsyncGenerator\",\"GeneratorFunction\",\"AsyncGeneratorFunction\",\"AsyncFunction\",\"Observable\",\"Array\",\"Buffer\",\"Blob\",\"Object\",\"RegExp\",\"Date\",\"Error\",\"Map\",\"Set\",\"WeakMap\",\"WeakSet\",\"ArrayBuffer\",\"SharedArrayBuffer\",\"DataView\",\"Promise\",\"URL\",\"FormData\",\"URLSearchParams\",\"HTMLElement\",...qhe];function Eht(t){return yht.includes(t)}var Iht=[\"null\",\"undefined\",\"string\",\"number\",\"bigint\",\"boolean\",\"symbol\"];function Cht(t){return Iht.includes(t)}function AI(t){return e=>typeof e===t}var{toString:Ghe}=Object.prototype,tv=t=>{let e=Ghe.call(t).slice(8,-1);if(/HTML\\w+Element/.test(e)&&be.domElement(t))return\"HTMLElement\";if(Eht(e))return e},pi=t=>e=>tv(e)===t;function be(t){if(t===null)return\"null\";switch(typeof t){case\"undefined\":return\"undefined\";case\"string\":return\"string\";case\"number\":return\"number\";case\"boolean\":return\"boolean\";case\"function\":return\"Function\";case\"bigint\":return\"bigint\";case\"symbol\":return\"symbol\";default:}if(be.observable(t))return\"Observable\";if(be.array(t))return\"Array\";if(be.buffer(t))return\"Buffer\";let e=tv(t);if(e)return e;if(t instanceof String||t instanceof Boolean||t instanceof Number)throw new TypeError(\"Please don't use object wrappers for primitive types\");return\"Object\"}be.undefined=AI(\"undefined\");be.string=AI(\"string\");var wht=AI(\"number\");be.number=t=>wht(t)&&!be.nan(t);be.bigint=AI(\"bigint\");be.function_=AI(\"function\");be.null_=t=>t===null;be.class_=t=>be.function_(t)&&t.toString().startsWith(\"class \");be.boolean=t=>t===!0||t===!1;be.symbol=AI(\"symbol\");be.numericString=t=>be.string(t)&&!be.emptyStringOrWhitespace(t)&&!Number.isNaN(Number(t));be.array=(t,e)=>Array.isArray(t)?be.function_(e)?t.every(e):!0:!1;be.buffer=t=>{var e,r,s,a;return(a=(s=(r=(e=t)===null||e===void 0?void 0:e.constructor)===null||r===void 0?void 0:r.isBuffer)===null||s===void 0?void 0:s.call(r,t))!==null&&a!==void 0?a:!1};be.blob=t=>pi(\"Blob\")(t);be.nullOrUndefined=t=>be.null_(t)||be.undefined(t);be.object=t=>!be.null_(t)&&(typeof t==\"object\"||be.function_(t));be.iterable=t=>{var e;return be.function_((e=t)===null||e===void 0?void 0:e[Symbol.iterator])};be.asyncIterable=t=>{var e;return be.function_((e=t)===null||e===void 0?void 0:e[Symbol.asyncIterator])};be.generator=t=>{var e,r;return be.iterable(t)&&be.function_((e=t)===null||e===void 0?void 0:e.next)&&be.function_((r=t)===null||r===void 0?void 0:r.throw)};be.asyncGenerator=t=>be.asyncIterable(t)&&be.function_(t.next)&&be.function_(t.throw);be.nativePromise=t=>pi(\"Promise\")(t);var Bht=t=>{var e,r;return be.function_((e=t)===null||e===void 0?void 0:e.then)&&be.function_((r=t)===null||r===void 0?void 0:r.catch)};be.promise=t=>be.nativePromise(t)||Bht(t);be.generatorFunction=pi(\"GeneratorFunction\");be.asyncGeneratorFunction=t=>tv(t)===\"AsyncGeneratorFunction\";be.asyncFunction=t=>tv(t)===\"AsyncFunction\";be.boundFunction=t=>be.function_(t)&&!t.hasOwnProperty(\"prototype\");be.regExp=pi(\"RegExp\");be.date=pi(\"Date\");be.error=pi(\"Error\");be.map=t=>pi(\"Map\")(t);be.set=t=>pi(\"Set\")(t);be.weakMap=t=>pi(\"WeakMap\")(t);be.weakSet=t=>pi(\"WeakSet\")(t);be.int8Array=pi(\"Int8Array\");be.uint8Array=pi(\"Uint8Array\");be.uint8ClampedArray=pi(\"Uint8ClampedArray\");be.int16Array=pi(\"Int16Array\");be.uint16Array=pi(\"Uint16Array\");be.int32Array=pi(\"Int32Array\");be.uint32Array=pi(\"Uint32Array\");be.float32Array=pi(\"Float32Array\");be.float64Array=pi(\"Float64Array\");be.bigInt64Array=pi(\"BigInt64Array\");be.bigUint64Array=pi(\"BigUint64Array\");be.arrayBuffer=pi(\"ArrayBuffer\");be.sharedArrayBuffer=pi(\"SharedArrayBuffer\");be.dataView=pi(\"DataView\");be.enumCase=(t,e)=>Object.values(e).includes(t);be.directInstanceOf=(t,e)=>Object.getPrototypeOf(t)===e.prototype;be.urlInstance=t=>pi(\"URL\")(t);be.urlString=t=>{if(!be.string(t))return!1;try{return new URL(t),!0}catch{return!1}};be.truthy=t=>!!t;be.falsy=t=>!t;be.nan=t=>Number.isNaN(t);be.primitive=t=>be.null_(t)||Cht(typeof t);be.integer=t=>Number.isInteger(t);be.safeInteger=t=>Number.isSafeInteger(t);be.plainObject=t=>{if(Ghe.call(t)!==\"[object Object]\")return!1;let e=Object.getPrototypeOf(t);return e===null||e===Object.getPrototypeOf({})};be.typedArray=t=>mht(tv(t));var vht=t=>be.safeInteger(t)&&t>=0;be.arrayLike=t=>!be.nullOrUndefined(t)&&!be.function_(t)&&vht(t.length);be.inRange=(t,e)=>{if(be.number(e))return t>=Math.min(0,e)&&t<=Math.max(e,0);if(be.array(e)&&e.length===2)return t>=Math.min(...e)&&t<=Math.max(...e);throw new TypeError(`Invalid range: ${JSON.stringify(e)}`)};var Sht=1,Dht=[\"innerHTML\",\"ownerDocument\",\"style\",\"attributes\",\"nodeValue\"];be.domElement=t=>be.object(t)&&t.nodeType===Sht&&be.string(t.nodeName)&&!be.plainObject(t)&&Dht.every(e=>e in t);be.observable=t=>{var e,r,s,a;return t?t===((r=(e=t)[Symbol.observable])===null||r===void 0?void 0:r.call(e))||t===((a=(s=t)[\"@@observable\"])===null||a===void 0?void 0:a.call(s)):!1};be.nodeStream=t=>be.object(t)&&be.function_(t.pipe)&&!be.observable(t);be.infinite=t=>t===1/0||t===-1/0;var Whe=t=>e=>be.integer(e)&&Math.abs(e%2)===t;be.evenInteger=Whe(0);be.oddInteger=Whe(1);be.emptyArray=t=>be.array(t)&&t.length===0;be.nonEmptyArray=t=>be.array(t)&&t.length>0;be.emptyString=t=>be.string(t)&&t.length===0;var bht=t=>be.string(t)&&!/\\S/.test(t);be.emptyStringOrWhitespace=t=>be.emptyString(t)||bht(t);be.nonEmptyString=t=>be.string(t)&&t.length>0;be.nonEmptyStringAndNotWhitespace=t=>be.string(t)&&!be.emptyStringOrWhitespace(t);be.emptyObject=t=>be.object(t)&&!be.map(t)&&!be.set(t)&&Object.keys(t).length===0;be.nonEmptyObject=t=>be.object(t)&&!be.map(t)&&!be.set(t)&&Object.keys(t).length>0;be.emptySet=t=>be.set(t)&&t.size===0;be.nonEmptySet=t=>be.set(t)&&t.size>0;be.emptyMap=t=>be.map(t)&&t.size===0;be.nonEmptyMap=t=>be.map(t)&&t.size>0;be.propertyKey=t=>be.any([be.string,be.number,be.symbol],t);be.formData=t=>pi(\"FormData\")(t);be.urlSearchParams=t=>pi(\"URLSearchParams\")(t);var Yhe=(t,e,r)=>{if(!be.function_(e))throw new TypeError(`Invalid predicate: ${JSON.stringify(e)}`);if(r.length===0)throw new TypeError(\"Invalid number of values\");return t.call(r,e)};be.any=(t,...e)=>(be.array(t)?t:[t]).some(s=>Yhe(Array.prototype.some,s,e));be.all=(t,...e)=>Yhe(Array.prototype.every,t,e);var _t=(t,e,r,s={})=>{if(!t){let{multipleValues:a}=s,n=a?`received values of types ${[...new Set(r.map(c=>`\\`${be(c)}\\``))].join(\", \")}`:`received value of type \\`${be(r)}\\``;throw new TypeError(`Expected value which is \\`${e}\\`, ${n}.`)}};Op.assert={undefined:t=>_t(be.undefined(t),\"undefined\",t),string:t=>_t(be.string(t),\"string\",t),number:t=>_t(be.number(t),\"number\",t),bigint:t=>_t(be.bigint(t),\"bigint\",t),function_:t=>_t(be.function_(t),\"Function\",t),null_:t=>_t(be.null_(t),\"null\",t),class_:t=>_t(be.class_(t),\"Class\",t),boolean:t=>_t(be.boolean(t),\"boolean\",t),symbol:t=>_t(be.symbol(t),\"symbol\",t),numericString:t=>_t(be.numericString(t),\"string with a number\",t),array:(t,e)=>{_t(be.array(t),\"Array\",t),e&&t.forEach(e)},buffer:t=>_t(be.buffer(t),\"Buffer\",t),blob:t=>_t(be.blob(t),\"Blob\",t),nullOrUndefined:t=>_t(be.nullOrUndefined(t),\"null or undefined\",t),object:t=>_t(be.object(t),\"Object\",t),iterable:t=>_t(be.iterable(t),\"Iterable\",t),asyncIterable:t=>_t(be.asyncIterable(t),\"AsyncIterable\",t),generator:t=>_t(be.generator(t),\"Generator\",t),asyncGenerator:t=>_t(be.asyncGenerator(t),\"AsyncGenerator\",t),nativePromise:t=>_t(be.nativePromise(t),\"native Promise\",t),promise:t=>_t(be.promise(t),\"Promise\",t),generatorFunction:t=>_t(be.generatorFunction(t),\"GeneratorFunction\",t),asyncGeneratorFunction:t=>_t(be.asyncGeneratorFunction(t),\"AsyncGeneratorFunction\",t),asyncFunction:t=>_t(be.asyncFunction(t),\"AsyncFunction\",t),boundFunction:t=>_t(be.boundFunction(t),\"Function\",t),regExp:t=>_t(be.regExp(t),\"RegExp\",t),date:t=>_t(be.date(t),\"Date\",t),error:t=>_t(be.error(t),\"Error\",t),map:t=>_t(be.map(t),\"Map\",t),set:t=>_t(be.set(t),\"Set\",t),weakMap:t=>_t(be.weakMap(t),\"WeakMap\",t),weakSet:t=>_t(be.weakSet(t),\"WeakSet\",t),int8Array:t=>_t(be.int8Array(t),\"Int8Array\",t),uint8Array:t=>_t(be.uint8Array(t),\"Uint8Array\",t),uint8ClampedArray:t=>_t(be.uint8ClampedArray(t),\"Uint8ClampedArray\",t),int16Array:t=>_t(be.int16Array(t),\"Int16Array\",t),uint16Array:t=>_t(be.uint16Array(t),\"Uint16Array\",t),int32Array:t=>_t(be.int32Array(t),\"Int32Array\",t),uint32Array:t=>_t(be.uint32Array(t),\"Uint32Array\",t),float32Array:t=>_t(be.float32Array(t),\"Float32Array\",t),float64Array:t=>_t(be.float64Array(t),\"Float64Array\",t),bigInt64Array:t=>_t(be.bigInt64Array(t),\"BigInt64Array\",t),bigUint64Array:t=>_t(be.bigUint64Array(t),\"BigUint64Array\",t),arrayBuffer:t=>_t(be.arrayBuffer(t),\"ArrayBuffer\",t),sharedArrayBuffer:t=>_t(be.sharedArrayBuffer(t),\"SharedArrayBuffer\",t),dataView:t=>_t(be.dataView(t),\"DataView\",t),enumCase:(t,e)=>_t(be.enumCase(t,e),\"EnumCase\",t),urlInstance:t=>_t(be.urlInstance(t),\"URL\",t),urlString:t=>_t(be.urlString(t),\"string with a URL\",t),truthy:t=>_t(be.truthy(t),\"truthy\",t),falsy:t=>_t(be.falsy(t),\"falsy\",t),nan:t=>_t(be.nan(t),\"NaN\",t),primitive:t=>_t(be.primitive(t),\"primitive\",t),integer:t=>_t(be.integer(t),\"integer\",t),safeInteger:t=>_t(be.safeInteger(t),\"integer\",t),plainObject:t=>_t(be.plainObject(t),\"plain object\",t),typedArray:t=>_t(be.typedArray(t),\"TypedArray\",t),arrayLike:t=>_t(be.arrayLike(t),\"array-like\",t),domElement:t=>_t(be.domElement(t),\"HTMLElement\",t),observable:t=>_t(be.observable(t),\"Observable\",t),nodeStream:t=>_t(be.nodeStream(t),\"Node.js Stream\",t),infinite:t=>_t(be.infinite(t),\"infinite number\",t),emptyArray:t=>_t(be.emptyArray(t),\"empty array\",t),nonEmptyArray:t=>_t(be.nonEmptyArray(t),\"non-empty array\",t),emptyString:t=>_t(be.emptyString(t),\"empty string\",t),emptyStringOrWhitespace:t=>_t(be.emptyStringOrWhitespace(t),\"empty string or whitespace\",t),nonEmptyString:t=>_t(be.nonEmptyString(t),\"non-empty string\",t),nonEmptyStringAndNotWhitespace:t=>_t(be.nonEmptyStringAndNotWhitespace(t),\"non-empty string and not whitespace\",t),emptyObject:t=>_t(be.emptyObject(t),\"empty object\",t),nonEmptyObject:t=>_t(be.nonEmptyObject(t),\"non-empty object\",t),emptySet:t=>_t(be.emptySet(t),\"empty set\",t),nonEmptySet:t=>_t(be.nonEmptySet(t),\"non-empty set\",t),emptyMap:t=>_t(be.emptyMap(t),\"empty map\",t),nonEmptyMap:t=>_t(be.nonEmptyMap(t),\"non-empty map\",t),propertyKey:t=>_t(be.propertyKey(t),\"PropertyKey\",t),formData:t=>_t(be.formData(t),\"FormData\",t),urlSearchParams:t=>_t(be.urlSearchParams(t),\"URLSearchParams\",t),evenInteger:t=>_t(be.evenInteger(t),\"even integer\",t),oddInteger:t=>_t(be.oddInteger(t),\"odd integer\",t),directInstanceOf:(t,e)=>_t(be.directInstanceOf(t,e),\"T\",t),inRange:(t,e)=>_t(be.inRange(t,e),\"in range\",t),any:(t,...e)=>_t(be.any(t,...e),\"predicate returns truthy for any value\",e,{multipleValues:!0}),all:(t,...e)=>_t(be.all(t,...e),\"predicate returns truthy for all values\",e,{multipleValues:!0})};Object.defineProperties(be,{class:{value:be.class_},function:{value:be.function_},null:{value:be.null_}});Object.defineProperties(Op.assert,{class:{value:Op.assert.class_},function:{value:Op.assert.function_},null:{value:Op.assert.null_}});Op.default=be;OQ.exports=be;OQ.exports.default=be;OQ.exports.assert=Op.assert});var Vhe=L((wJt,fH)=>{\"use strict\";var LQ=class extends Error{constructor(e){super(e||\"Promise was canceled\"),this.name=\"CancelError\"}get isCanceled(){return!0}},MQ=class t{static fn(e){return(...r)=>new t((s,a,n)=>{r.push(n),e(...r).then(s,a)})}constructor(e){this._cancelHandlers=[],this._isPending=!0,this._isCanceled=!1,this._rejectOnCancel=!0,this._promise=new Promise((r,s)=>{this._reject=s;let a=f=>{this._isPending=!1,r(f)},n=f=>{this._isPending=!1,s(f)},c=f=>{if(!this._isPending)throw new Error(\"The `onCancel` handler was attached after the promise settled.\");this._cancelHandlers.push(f)};return Object.defineProperties(c,{shouldReject:{get:()=>this._rejectOnCancel,set:f=>{this._rejectOnCancel=f}}}),e(a,n,c)})}then(e,r){return this._promise.then(e,r)}catch(e){return this._promise.catch(e)}finally(e){return this._promise.finally(e)}cancel(e){if(!(!this._isPending||this._isCanceled)){if(this._cancelHandlers.length>0)try{for(let r of this._cancelHandlers)r()}catch(r){this._reject(r)}this._isCanceled=!0,this._rejectOnCancel&&this._reject(new LQ(e))}}get isCanceled(){return this._isCanceled}};Object.setPrototypeOf(MQ.prototype,Promise.prototype);fH.exports=MQ;fH.exports.CancelError=LQ});var Khe=L((pH,hH)=>{\"use strict\";Object.defineProperty(pH,\"__esModule\",{value:!0});function Pht(t){return t.encrypted}var AH=(t,e)=>{let r;typeof e==\"function\"?r={connect:e}:r=e;let s=typeof r.connect==\"function\",a=typeof r.secureConnect==\"function\",n=typeof r.close==\"function\",c=()=>{s&&r.connect(),Pht(t)&&a&&(t.authorized?r.secureConnect():t.authorizationError||t.once(\"secureConnect\",r.secureConnect)),n&&t.once(\"close\",r.close)};t.writable&&!t.connecting?c():t.connecting?t.once(\"connect\",c):t.destroyed&&n&&r.close(t._hadError)};pH.default=AH;hH.exports=AH;hH.exports.default=AH});var Jhe=L((dH,mH)=>{\"use strict\";Object.defineProperty(dH,\"__esModule\",{value:!0});var xht=Khe(),kht=Number(process.versions.node.split(\".\")[0]),gH=t=>{let e={start:Date.now(),socket:void 0,lookup:void 0,connect:void 0,secureConnect:void 0,upload:void 0,response:void 0,end:void 0,error:void 0,abort:void 0,phases:{wait:void 0,dns:void 0,tcp:void 0,tls:void 0,request:void 0,firstByte:void 0,download:void 0,total:void 0}};t.timings=e;let r=c=>{let f=c.emit.bind(c);c.emit=(p,...h)=>(p===\"error\"&&(e.error=Date.now(),e.phases.total=e.error-e.start,c.emit=f),f(p,...h))};r(t),t.prependOnceListener(\"abort\",()=>{e.abort=Date.now(),(!e.response||kht>=13)&&(e.phases.total=Date.now()-e.start)});let s=c=>{e.socket=Date.now(),e.phases.wait=e.socket-e.start;let f=()=>{e.lookup=Date.now(),e.phases.dns=e.lookup-e.socket};c.prependOnceListener(\"lookup\",f),xht.default(c,{connect:()=>{e.connect=Date.now(),e.lookup===void 0&&(c.removeListener(\"lookup\",f),e.lookup=e.connect,e.phases.dns=e.lookup-e.socket),e.phases.tcp=e.connect-e.lookup},secureConnect:()=>{e.secureConnect=Date.now(),e.phases.tls=e.secureConnect-e.connect}})};t.socket?s(t.socket):t.prependOnceListener(\"socket\",s);let a=()=>{var c;e.upload=Date.now(),e.phases.request=e.upload-(c=e.secureConnect,c??e.connect)};return(typeof t.writableFinished==\"boolean\"?t.writableFinished:t.finished&&t.outputSize===0&&(!t.socket||t.socket.writableLength===0))?a():t.prependOnceListener(\"finish\",a),t.prependOnceListener(\"response\",c=>{e.response=Date.now(),e.phases.firstByte=e.response-e.upload,c.timings=e,r(c),c.prependOnceListener(\"end\",()=>{e.end=Date.now(),e.phases.download=e.end-e.response,e.phases.total=e.end-e.start})}),e};dH.default=gH;mH.exports=gH;mH.exports.default=gH});var r0e=L((BJt,IH)=>{\"use strict\";var{V4MAPPED:Qht,ADDRCONFIG:Tht,ALL:t0e,promises:{Resolver:zhe},lookup:Rht}=Ie(\"dns\"),{promisify:yH}=Ie(\"util\"),Fht=Ie(\"os\"),pI=Symbol(\"cacheableLookupCreateConnection\"),EH=Symbol(\"cacheableLookupInstance\"),Zhe=Symbol(\"expires\"),Nht=typeof t0e==\"number\",Xhe=t=>{if(!(t&&typeof t.createConnection==\"function\"))throw new Error(\"Expected an Agent instance as the first argument\")},Oht=t=>{for(let e of t)e.family!==6&&(e.address=`::ffff:${e.address}`,e.family=6)},$he=()=>{let t=!1,e=!1;for(let r of Object.values(Fht.networkInterfaces()))for(let s of r)if(!s.internal&&(s.family===\"IPv6\"?e=!0:t=!0,t&&e))return{has4:t,has6:e};return{has4:t,has6:e}},Lht=t=>Symbol.iterator in t,e0e={ttl:!0},Mht={all:!0},_Q=class{constructor({cache:e=new Map,maxTtl:r=1/0,fallbackDuration:s=3600,errorTtl:a=.15,resolver:n=new zhe,lookup:c=Rht}={}){if(this.maxTtl=r,this.errorTtl=a,this._cache=e,this._resolver=n,this._dnsLookup=yH(c),this._resolver instanceof zhe?(this._resolve4=this._resolver.resolve4.bind(this._resolver),this._resolve6=this._resolver.resolve6.bind(this._resolver)):(this._resolve4=yH(this._resolver.resolve4.bind(this._resolver)),this._resolve6=yH(this._resolver.resolve6.bind(this._resolver))),this._iface=$he(),this._pending={},this._nextRemovalTime=!1,this._hostnamesToFallback=new Set,s<1)this._fallback=!1;else{this._fallback=!0;let f=setInterval(()=>{this._hostnamesToFallback.clear()},s*1e3);f.unref&&f.unref()}this.lookup=this.lookup.bind(this),this.lookupAsync=this.lookupAsync.bind(this)}set servers(e){this.clear(),this._resolver.setServers(e)}get servers(){return this._resolver.getServers()}lookup(e,r,s){if(typeof r==\"function\"?(s=r,r={}):typeof r==\"number\"&&(r={family:r}),!s)throw new Error(\"Callback must be a function.\");this.lookupAsync(e,r).then(a=>{r.all?s(null,a):s(null,a.address,a.family,a.expires,a.ttl)},s)}async lookupAsync(e,r={}){typeof r==\"number\"&&(r={family:r});let s=await this.query(e);if(r.family===6){let a=s.filter(n=>n.family===6);r.hints&Qht&&(Nht&&r.hints&t0e||a.length===0)?Oht(s):s=a}else r.family===4&&(s=s.filter(a=>a.family===4));if(r.hints&Tht){let{_iface:a}=this;s=s.filter(n=>n.family===6?a.has6:a.has4)}if(s.length===0){let a=new Error(`cacheableLookup ENOTFOUND ${e}`);throw a.code=\"ENOTFOUND\",a.hostname=e,a}return r.all?s:s[0]}async query(e){let r=await this._cache.get(e);if(!r){let s=this._pending[e];if(s)r=await s;else{let a=this.queryAndCache(e);this._pending[e]=a,r=await a}}return r=r.map(s=>({...s})),r}async _resolve(e){let r=async h=>{try{return await h}catch(E){if(E.code===\"ENODATA\"||E.code===\"ENOTFOUND\")return[];throw E}},[s,a]=await Promise.all([this._resolve4(e,e0e),this._resolve6(e,e0e)].map(h=>r(h))),n=0,c=0,f=0,p=Date.now();for(let h of s)h.family=4,h.expires=p+h.ttl*1e3,n=Math.max(n,h.ttl);for(let h of a)h.family=6,h.expires=p+h.ttl*1e3,c=Math.max(c,h.ttl);return s.length>0?a.length>0?f=Math.min(n,c):f=n:f=c,{entries:[...s,...a],cacheTtl:f}}async _lookup(e){try{return{entries:await this._dnsLookup(e,{all:!0}),cacheTtl:0}}catch{return{entries:[],cacheTtl:0}}}async _set(e,r,s){if(this.maxTtl>0&&s>0){s=Math.min(s,this.maxTtl)*1e3,r[Zhe]=Date.now()+s;try{await this._cache.set(e,r,s)}catch(a){this.lookupAsync=async()=>{let n=new Error(\"Cache Error. Please recreate the CacheableLookup instance.\");throw n.cause=a,n}}Lht(this._cache)&&this._tick(s)}}async queryAndCache(e){if(this._hostnamesToFallback.has(e))return this._dnsLookup(e,Mht);try{let r=await this._resolve(e);r.entries.length===0&&this._fallback&&(r=await this._lookup(e),r.entries.length!==0&&this._hostnamesToFallback.add(e));let s=r.entries.length===0?this.errorTtl:r.cacheTtl;return await this._set(e,r.entries,s),delete this._pending[e],r.entries}catch(r){throw delete this._pending[e],r}}_tick(e){let r=this._nextRemovalTime;(!r||e<r)&&(clearTimeout(this._removalTimeout),this._nextRemovalTime=e,this._removalTimeout=setTimeout(()=>{this._nextRemovalTime=!1;let s=1/0,a=Date.now();for(let[n,c]of this._cache){let f=c[Zhe];a>=f?this._cache.delete(n):f<s&&(s=f)}s!==1/0&&this._tick(s-a)},e),this._removalTimeout.unref&&this._removalTimeout.unref())}install(e){if(Xhe(e),pI in e)throw new Error(\"CacheableLookup has been already installed\");e[pI]=e.createConnection,e[EH]=this,e.createConnection=(r,s)=>(\"lookup\"in r||(r.lookup=this.lookup),e[pI](r,s))}uninstall(e){if(Xhe(e),e[pI]){if(e[EH]!==this)throw new Error(\"The agent is not owned by this CacheableLookup instance\");e.createConnection=e[pI],delete e[pI],delete e[EH]}}updateInterfaceInfo(){let{_iface:e}=this;this._iface=$he(),(e.has4&&!this._iface.has4||e.has6&&!this._iface.has6)&&this._cache.clear()}clear(e){if(e){this._cache.delete(e);return}this._cache.clear()}};IH.exports=_Q;IH.exports.default=_Q});var s0e=L((vJt,CH)=>{\"use strict\";var _ht=typeof URL>\"u\"?Ie(\"url\").URL:URL,Uht=\"text/plain\",Hht=\"us-ascii\",n0e=(t,e)=>e.some(r=>r instanceof RegExp?r.test(t):r===t),jht=(t,{stripHash:e})=>{let r=t.match(/^data:([^,]*?),([^#]*?)(?:#(.*))?$/);if(!r)throw new Error(`Invalid URL: ${t}`);let s=r[1].split(\";\"),a=r[2],n=e?\"\":r[3],c=!1;s[s.length-1]===\"base64\"&&(s.pop(),c=!0);let f=(s.shift()||\"\").toLowerCase(),h=[...s.map(E=>{let[C,S=\"\"]=E.split(\"=\").map(P=>P.trim());return C===\"charset\"&&(S=S.toLowerCase(),S===Hht)?\"\":`${C}${S?`=${S}`:\"\"}`}).filter(Boolean)];return c&&h.push(\"base64\"),(h.length!==0||f&&f!==Uht)&&h.unshift(f),`data:${h.join(\";\")},${c?a.trim():a}${n?`#${n}`:\"\"}`},i0e=(t,e)=>{if(e={defaultProtocol:\"http:\",normalizeProtocol:!0,forceHttp:!1,forceHttps:!1,stripAuthentication:!0,stripHash:!1,stripWWW:!0,removeQueryParameters:[/^utm_\\w+/i],removeTrailingSlash:!0,removeDirectoryIndex:!1,sortQueryParameters:!0,...e},Reflect.has(e,\"normalizeHttps\"))throw new Error(\"options.normalizeHttps is renamed to options.forceHttp\");if(Reflect.has(e,\"normalizeHttp\"))throw new Error(\"options.normalizeHttp is renamed to options.forceHttps\");if(Reflect.has(e,\"stripFragment\"))throw new Error(\"options.stripFragment is renamed to options.stripHash\");if(t=t.trim(),/^data:/i.test(t))return jht(t,e);let r=t.startsWith(\"//\");!r&&/^\\.*\\//.test(t)||(t=t.replace(/^(?!(?:\\w+:)?\\/\\/)|^\\/\\//,e.defaultProtocol));let a=new _ht(t);if(e.forceHttp&&e.forceHttps)throw new Error(\"The `forceHttp` and `forceHttps` options cannot be used together\");if(e.forceHttp&&a.protocol===\"https:\"&&(a.protocol=\"http:\"),e.forceHttps&&a.protocol===\"http:\"&&(a.protocol=\"https:\"),e.stripAuthentication&&(a.username=\"\",a.password=\"\"),e.stripHash&&(a.hash=\"\"),a.pathname&&(a.pathname=a.pathname.replace(/((?!:).|^)\\/{2,}/g,(n,c)=>/^(?!\\/)/g.test(c)?`${c}/`:\"/\")),a.pathname&&(a.pathname=decodeURI(a.pathname)),e.removeDirectoryIndex===!0&&(e.removeDirectoryIndex=[/^index\\.[a-z]+$/]),Array.isArray(e.removeDirectoryIndex)&&e.removeDirectoryIndex.length>0){let n=a.pathname.split(\"/\"),c=n[n.length-1];n0e(c,e.removeDirectoryIndex)&&(n=n.slice(0,n.length-1),a.pathname=n.slice(1).join(\"/\")+\"/\")}if(a.hostname&&(a.hostname=a.hostname.replace(/\\.$/,\"\"),e.stripWWW&&/^www\\.([a-z\\-\\d]{2,63})\\.([a-z.]{2,5})$/.test(a.hostname)&&(a.hostname=a.hostname.replace(/^www\\./,\"\"))),Array.isArray(e.removeQueryParameters))for(let n of[...a.searchParams.keys()])n0e(n,e.removeQueryParameters)&&a.searchParams.delete(n);return e.sortQueryParameters&&a.searchParams.sort(),e.removeTrailingSlash&&(a.pathname=a.pathname.replace(/\\/$/,\"\")),t=a.toString(),(e.removeTrailingSlash||a.pathname===\"/\")&&a.hash===\"\"&&(t=t.replace(/\\/$/,\"\")),r&&!e.normalizeProtocol&&(t=t.replace(/^http:\\/\\//,\"//\")),e.stripProtocol&&(t=t.replace(/^(?:https?:)?\\/\\//,\"\")),t};CH.exports=i0e;CH.exports.default=i0e});var l0e=L((SJt,a0e)=>{a0e.exports=o0e;function o0e(t,e){if(t&&e)return o0e(t)(e);if(typeof t!=\"function\")throw new TypeError(\"need wrapper function\");return Object.keys(t).forEach(function(s){r[s]=t[s]}),r;function r(){for(var s=new Array(arguments.length),a=0;a<s.length;a++)s[a]=arguments[a];var n=t.apply(this,s),c=s[s.length-1];return typeof n==\"function\"&&n!==c&&Object.keys(c).forEach(function(f){n[f]=c[f]}),n}}});var BH=L((DJt,wH)=>{var c0e=l0e();wH.exports=c0e(UQ);wH.exports.strict=c0e(u0e);UQ.proto=UQ(function(){Object.defineProperty(Function.prototype,\"once\",{value:function(){return UQ(this)},configurable:!0}),Object.defineProperty(Function.prototype,\"onceStrict\",{value:function(){return u0e(this)},configurable:!0})});function UQ(t){var e=function(){return e.called?e.value:(e.called=!0,e.value=t.apply(this,arguments))};return e.called=!1,e}function u0e(t){var e=function(){if(e.called)throw new Error(e.onceError);return e.called=!0,e.value=t.apply(this,arguments)},r=t.name||\"Function wrapped with `once`\";return e.onceError=r+\" shouldn't be called more than once\",e.called=!1,e}});var vH=L((bJt,A0e)=>{var qht=BH(),Ght=function(){},Wht=function(t){return t.setHeader&&typeof t.abort==\"function\"},Yht=function(t){return t.stdio&&Array.isArray(t.stdio)&&t.stdio.length===3},f0e=function(t,e,r){if(typeof e==\"function\")return f0e(t,null,e);e||(e={}),r=qht(r||Ght);var s=t._writableState,a=t._readableState,n=e.readable||e.readable!==!1&&t.readable,c=e.writable||e.writable!==!1&&t.writable,f=function(){t.writable||p()},p=function(){c=!1,n||r.call(t)},h=function(){n=!1,c||r.call(t)},E=function(I){r.call(t,I?new Error(\"exited with error code: \"+I):null)},C=function(I){r.call(t,I)},S=function(){if(n&&!(a&&a.ended))return r.call(t,new Error(\"premature close\"));if(c&&!(s&&s.ended))return r.call(t,new Error(\"premature close\"))},P=function(){t.req.on(\"finish\",p)};return Wht(t)?(t.on(\"complete\",p),t.on(\"abort\",S),t.req?P():t.on(\"request\",P)):c&&!s&&(t.on(\"end\",f),t.on(\"close\",f)),Yht(t)&&t.on(\"exit\",E),t.on(\"end\",h),t.on(\"finish\",p),e.error!==!1&&t.on(\"error\",C),t.on(\"close\",S),function(){t.removeListener(\"complete\",p),t.removeListener(\"abort\",S),t.removeListener(\"request\",P),t.req&&t.req.removeListener(\"finish\",p),t.removeListener(\"end\",f),t.removeListener(\"close\",f),t.removeListener(\"finish\",p),t.removeListener(\"exit\",E),t.removeListener(\"end\",h),t.removeListener(\"error\",C),t.removeListener(\"close\",S)}};A0e.exports=f0e});var g0e=L((PJt,h0e)=>{var Vht=BH(),Kht=vH(),SH=Ie(\"fs\"),rv=function(){},Jht=/^v?\\.0/.test(process.version),HQ=function(t){return typeof t==\"function\"},zht=function(t){return!Jht||!SH?!1:(t instanceof(SH.ReadStream||rv)||t instanceof(SH.WriteStream||rv))&&HQ(t.close)},Zht=function(t){return t.setHeader&&HQ(t.abort)},Xht=function(t,e,r,s){s=Vht(s);var a=!1;t.on(\"close\",function(){a=!0}),Kht(t,{readable:e,writable:r},function(c){if(c)return s(c);a=!0,s()});var n=!1;return function(c){if(!a&&!n){if(n=!0,zht(t))return t.close(rv);if(Zht(t))return t.abort();if(HQ(t.destroy))return t.destroy();s(c||new Error(\"stream was destroyed\"))}}},p0e=function(t){t()},$ht=function(t,e){return t.pipe(e)},e0t=function(){var t=Array.prototype.slice.call(arguments),e=HQ(t[t.length-1]||rv)&&t.pop()||rv;if(Array.isArray(t[0])&&(t=t[0]),t.length<2)throw new Error(\"pump requires two streams per minimum\");var r,s=t.map(function(a,n){var c=n<t.length-1,f=n>0;return Xht(a,c,f,function(p){r||(r=p),p&&s.forEach(p0e),!c&&(s.forEach(p0e),e(r))})});return t.reduce($ht)};h0e.exports=e0t});var m0e=L((xJt,d0e)=>{\"use strict\";var{PassThrough:t0t}=Ie(\"stream\");d0e.exports=t=>{t={...t};let{array:e}=t,{encoding:r}=t,s=r===\"buffer\",a=!1;e?a=!(r||s):r=r||\"utf8\",s&&(r=null);let n=new t0t({objectMode:a});r&&n.setEncoding(r);let c=0,f=[];return n.on(\"data\",p=>{f.push(p),a?c=f.length:c+=p.length}),n.getBufferedValue=()=>e?f:s?Buffer.concat(f,c):f.join(\"\"),n.getBufferedLength=()=>c,n}});var y0e=L((kJt,hI)=>{\"use strict\";var r0t=g0e(),n0t=m0e(),jQ=class extends Error{constructor(){super(\"maxBuffer exceeded\"),this.name=\"MaxBufferError\"}};async function qQ(t,e){if(!t)return Promise.reject(new Error(\"Expected a stream\"));e={maxBuffer:1/0,...e};let{maxBuffer:r}=e,s;return await new Promise((a,n)=>{let c=f=>{f&&(f.bufferedData=s.getBufferedValue()),n(f)};s=r0t(t,n0t(e),f=>{if(f){c(f);return}a()}),s.on(\"data\",()=>{s.getBufferedLength()>r&&c(new jQ)})}),s.getBufferedValue()}hI.exports=qQ;hI.exports.default=qQ;hI.exports.buffer=(t,e)=>qQ(t,{...e,encoding:\"buffer\"});hI.exports.array=(t,e)=>qQ(t,{...e,array:!0});hI.exports.MaxBufferError=jQ});var I0e=L((TJt,E0e)=>{\"use strict\";var i0t=new Set([200,203,204,206,300,301,308,404,405,410,414,501]),s0t=new Set([200,203,204,300,301,302,303,307,308,404,405,410,414,501]),o0t=new Set([500,502,503,504]),a0t={date:!0,connection:!0,\"keep-alive\":!0,\"proxy-authenticate\":!0,\"proxy-authorization\":!0,te:!0,trailer:!0,\"transfer-encoding\":!0,upgrade:!0},l0t={\"content-length\":!0,\"content-encoding\":!0,\"transfer-encoding\":!0,\"content-range\":!0};function tm(t){let e=parseInt(t,10);return isFinite(e)?e:0}function c0t(t){return t?o0t.has(t.status):!0}function DH(t){let e={};if(!t)return e;let r=t.trim().split(/,/);for(let s of r){let[a,n]=s.split(/=/,2);e[a.trim()]=n===void 0?!0:n.trim().replace(/^\"|\"$/g,\"\")}return e}function u0t(t){let e=[];for(let r in t){let s=t[r];e.push(s===!0?r:r+\"=\"+s)}if(e.length)return e.join(\", \")}E0e.exports=class{constructor(e,r,{shared:s,cacheHeuristic:a,immutableMinTimeToLive:n,ignoreCargoCult:c,_fromObject:f}={}){if(f){this._fromObject(f);return}if(!r||!r.headers)throw Error(\"Response headers missing\");this._assertRequestHasHeaders(e),this._responseTime=this.now(),this._isShared=s!==!1,this._cacheHeuristic=a!==void 0?a:.1,this._immutableMinTtl=n!==void 0?n:24*3600*1e3,this._status=\"status\"in r?r.status:200,this._resHeaders=r.headers,this._rescc=DH(r.headers[\"cache-control\"]),this._method=\"method\"in e?e.method:\"GET\",this._url=e.url,this._host=e.headers.host,this._noAuthorization=!e.headers.authorization,this._reqHeaders=r.headers.vary?e.headers:null,this._reqcc=DH(e.headers[\"cache-control\"]),c&&\"pre-check\"in this._rescc&&\"post-check\"in this._rescc&&(delete this._rescc[\"pre-check\"],delete this._rescc[\"post-check\"],delete this._rescc[\"no-cache\"],delete this._rescc[\"no-store\"],delete this._rescc[\"must-revalidate\"],this._resHeaders=Object.assign({},this._resHeaders,{\"cache-control\":u0t(this._rescc)}),delete this._resHeaders.expires,delete this._resHeaders.pragma),r.headers[\"cache-control\"]==null&&/no-cache/.test(r.headers.pragma)&&(this._rescc[\"no-cache\"]=!0)}now(){return Date.now()}storable(){return!!(!this._reqcc[\"no-store\"]&&(this._method===\"GET\"||this._method===\"HEAD\"||this._method===\"POST\"&&this._hasExplicitExpiration())&&s0t.has(this._status)&&!this._rescc[\"no-store\"]&&(!this._isShared||!this._rescc.private)&&(!this._isShared||this._noAuthorization||this._allowsStoringAuthenticated())&&(this._resHeaders.expires||this._rescc[\"max-age\"]||this._isShared&&this._rescc[\"s-maxage\"]||this._rescc.public||i0t.has(this._status)))}_hasExplicitExpiration(){return this._isShared&&this._rescc[\"s-maxage\"]||this._rescc[\"max-age\"]||this._resHeaders.expires}_assertRequestHasHeaders(e){if(!e||!e.headers)throw Error(\"Request headers missing\")}satisfiesWithoutRevalidation(e){this._assertRequestHasHeaders(e);let r=DH(e.headers[\"cache-control\"]);return r[\"no-cache\"]||/no-cache/.test(e.headers.pragma)||r[\"max-age\"]&&this.age()>r[\"max-age\"]||r[\"min-fresh\"]&&this.timeToLive()<1e3*r[\"min-fresh\"]||this.stale()&&!(r[\"max-stale\"]&&!this._rescc[\"must-revalidate\"]&&(r[\"max-stale\"]===!0||r[\"max-stale\"]>this.age()-this.maxAge()))?!1:this._requestMatches(e,!1)}_requestMatches(e,r){return(!this._url||this._url===e.url)&&this._host===e.headers.host&&(!e.method||this._method===e.method||r&&e.method===\"HEAD\")&&this._varyMatches(e)}_allowsStoringAuthenticated(){return this._rescc[\"must-revalidate\"]||this._rescc.public||this._rescc[\"s-maxage\"]}_varyMatches(e){if(!this._resHeaders.vary)return!0;if(this._resHeaders.vary===\"*\")return!1;let r=this._resHeaders.vary.trim().toLowerCase().split(/\\s*,\\s*/);for(let s of r)if(e.headers[s]!==this._reqHeaders[s])return!1;return!0}_copyWithoutHopByHopHeaders(e){let r={};for(let s in e)a0t[s]||(r[s]=e[s]);if(e.connection){let s=e.connection.trim().split(/\\s*,\\s*/);for(let a of s)delete r[a]}if(r.warning){let s=r.warning.split(/,/).filter(a=>!/^\\s*1[0-9][0-9]/.test(a));s.length?r.warning=s.join(\",\").trim():delete r.warning}return r}responseHeaders(){let e=this._copyWithoutHopByHopHeaders(this._resHeaders),r=this.age();return r>3600*24&&!this._hasExplicitExpiration()&&this.maxAge()>3600*24&&(e.warning=(e.warning?`${e.warning}, `:\"\")+'113 - \"rfc7234 5.5.4\"'),e.age=`${Math.round(r)}`,e.date=new Date(this.now()).toUTCString(),e}date(){let e=Date.parse(this._resHeaders.date);return isFinite(e)?e:this._responseTime}age(){let e=this._ageValue(),r=(this.now()-this._responseTime)/1e3;return e+r}_ageValue(){return tm(this._resHeaders.age)}maxAge(){if(!this.storable()||this._rescc[\"no-cache\"]||this._isShared&&this._resHeaders[\"set-cookie\"]&&!this._rescc.public&&!this._rescc.immutable||this._resHeaders.vary===\"*\")return 0;if(this._isShared){if(this._rescc[\"proxy-revalidate\"])return 0;if(this._rescc[\"s-maxage\"])return tm(this._rescc[\"s-maxage\"])}if(this._rescc[\"max-age\"])return tm(this._rescc[\"max-age\"]);let e=this._rescc.immutable?this._immutableMinTtl:0,r=this.date();if(this._resHeaders.expires){let s=Date.parse(this._resHeaders.expires);return Number.isNaN(s)||s<r?0:Math.max(e,(s-r)/1e3)}if(this._resHeaders[\"last-modified\"]){let s=Date.parse(this._resHeaders[\"last-modified\"]);if(isFinite(s)&&r>s)return Math.max(e,(r-s)/1e3*this._cacheHeuristic)}return e}timeToLive(){let e=this.maxAge()-this.age(),r=e+tm(this._rescc[\"stale-if-error\"]),s=e+tm(this._rescc[\"stale-while-revalidate\"]);return Math.max(0,e,r,s)*1e3}stale(){return this.maxAge()<=this.age()}_useStaleIfError(){return this.maxAge()+tm(this._rescc[\"stale-if-error\"])>this.age()}useStaleWhileRevalidate(){return this.maxAge()+tm(this._rescc[\"stale-while-revalidate\"])>this.age()}static fromObject(e){return new this(void 0,void 0,{_fromObject:e})}_fromObject(e){if(this._responseTime)throw Error(\"Reinitialized\");if(!e||e.v!==1)throw Error(\"Invalid serialization\");this._responseTime=e.t,this._isShared=e.sh,this._cacheHeuristic=e.ch,this._immutableMinTtl=e.imm!==void 0?e.imm:24*3600*1e3,this._status=e.st,this._resHeaders=e.resh,this._rescc=e.rescc,this._method=e.m,this._url=e.u,this._host=e.h,this._noAuthorization=e.a,this._reqHeaders=e.reqh,this._reqcc=e.reqcc}toObject(){return{v:1,t:this._responseTime,sh:this._isShared,ch:this._cacheHeuristic,imm:this._immutableMinTtl,st:this._status,resh:this._resHeaders,rescc:this._rescc,m:this._method,u:this._url,h:this._host,a:this._noAuthorization,reqh:this._reqHeaders,reqcc:this._reqcc}}revalidationHeaders(e){this._assertRequestHasHeaders(e);let r=this._copyWithoutHopByHopHeaders(e.headers);if(delete r[\"if-range\"],!this._requestMatches(e,!0)||!this.storable())return delete r[\"if-none-match\"],delete r[\"if-modified-since\"],r;if(this._resHeaders.etag&&(r[\"if-none-match\"]=r[\"if-none-match\"]?`${r[\"if-none-match\"]}, ${this._resHeaders.etag}`:this._resHeaders.etag),r[\"accept-ranges\"]||r[\"if-match\"]||r[\"if-unmodified-since\"]||this._method&&this._method!=\"GET\"){if(delete r[\"if-modified-since\"],r[\"if-none-match\"]){let a=r[\"if-none-match\"].split(/,/).filter(n=>!/^\\s*W\\//.test(n));a.length?r[\"if-none-match\"]=a.join(\",\").trim():delete r[\"if-none-match\"]}}else this._resHeaders[\"last-modified\"]&&!r[\"if-modified-since\"]&&(r[\"if-modified-since\"]=this._resHeaders[\"last-modified\"]);return r}revalidatedPolicy(e,r){if(this._assertRequestHasHeaders(e),this._useStaleIfError()&&c0t(r))return{modified:!1,matches:!1,policy:this};if(!r||!r.headers)throw Error(\"Response headers missing\");let s=!1;if(r.status!==void 0&&r.status!=304?s=!1:r.headers.etag&&!/^\\s*W\\//.test(r.headers.etag)?s=this._resHeaders.etag&&this._resHeaders.etag.replace(/^\\s*W\\//,\"\")===r.headers.etag:this._resHeaders.etag&&r.headers.etag?s=this._resHeaders.etag.replace(/^\\s*W\\//,\"\")===r.headers.etag.replace(/^\\s*W\\//,\"\"):this._resHeaders[\"last-modified\"]?s=this._resHeaders[\"last-modified\"]===r.headers[\"last-modified\"]:!this._resHeaders.etag&&!this._resHeaders[\"last-modified\"]&&!r.headers.etag&&!r.headers[\"last-modified\"]&&(s=!0),!s)return{policy:new this.constructor(e,r),modified:r.status!=304,matches:!1};let a={};for(let c in this._resHeaders)a[c]=c in r.headers&&!l0t[c]?r.headers[c]:this._resHeaders[c];let n=Object.assign({},r,{status:this._status,method:this._method,headers:a});return{policy:new this.constructor(e,n,{shared:this._isShared,cacheHeuristic:this._cacheHeuristic,immutableMinTimeToLive:this._immutableMinTtl}),modified:!1,matches:!0}}}});var GQ=L((RJt,C0e)=>{\"use strict\";C0e.exports=t=>{let e={};for(let[r,s]of Object.entries(t))e[r.toLowerCase()]=s;return e}});var B0e=L((FJt,w0e)=>{\"use strict\";var f0t=Ie(\"stream\").Readable,A0t=GQ(),bH=class extends f0t{constructor(e,r,s,a){if(typeof e!=\"number\")throw new TypeError(\"Argument `statusCode` should be a number\");if(typeof r!=\"object\")throw new TypeError(\"Argument `headers` should be an object\");if(!(s instanceof Buffer))throw new TypeError(\"Argument `body` should be a buffer\");if(typeof a!=\"string\")throw new TypeError(\"Argument `url` should be a string\");super(),this.statusCode=e,this.headers=A0t(r),this.body=s,this.url=a}_read(){this.push(this.body),this.push(null)}};w0e.exports=bH});var S0e=L((NJt,v0e)=>{\"use strict\";var p0t=[\"destroy\",\"setTimeout\",\"socket\",\"headers\",\"trailers\",\"rawHeaders\",\"statusCode\",\"httpVersion\",\"httpVersionMinor\",\"httpVersionMajor\",\"rawTrailers\",\"statusMessage\"];v0e.exports=(t,e)=>{let r=new Set(Object.keys(t).concat(p0t));for(let s of r)s in e||(e[s]=typeof t[s]==\"function\"?t[s].bind(t):t[s])}});var b0e=L((OJt,D0e)=>{\"use strict\";var h0t=Ie(\"stream\").PassThrough,g0t=S0e(),d0t=t=>{if(!(t&&t.pipe))throw new TypeError(\"Parameter `response` must be a response stream.\");let e=new h0t;return g0t(t,e),t.pipe(e)};D0e.exports=d0t});var P0e=L(PH=>{PH.stringify=function t(e){if(typeof e>\"u\")return e;if(e&&Buffer.isBuffer(e))return JSON.stringify(\":base64:\"+e.toString(\"base64\"));if(e&&e.toJSON&&(e=e.toJSON()),e&&typeof e==\"object\"){var r=\"\",s=Array.isArray(e);r=s?\"[\":\"{\";var a=!0;for(var n in e){var c=typeof e[n]==\"function\"||!s&&typeof e[n]>\"u\";Object.hasOwnProperty.call(e,n)&&!c&&(a||(r+=\",\"),a=!1,s?e[n]==null?r+=\"null\":r+=t(e[n]):e[n]!==void 0&&(r+=t(n)+\":\"+t(e[n])))}return r+=s?\"]\":\"}\",r}else return typeof e==\"string\"?JSON.stringify(/^:/.test(e)?\":\"+e:e):typeof e>\"u\"?\"null\":JSON.stringify(e)};PH.parse=function(t){return JSON.parse(t,function(e,r){return typeof r==\"string\"?/^:base64:/.test(r)?Buffer.from(r.substring(8),\"base64\"):/^:/.test(r)?r.substring(1):r:r})}});var T0e=L((MJt,Q0e)=>{\"use strict\";var m0t=Ie(\"events\"),x0e=P0e(),y0t=t=>{let e={redis:\"@keyv/redis\",rediss:\"@keyv/redis\",mongodb:\"@keyv/mongo\",mongo:\"@keyv/mongo\",sqlite:\"@keyv/sqlite\",postgresql:\"@keyv/postgres\",postgres:\"@keyv/postgres\",mysql:\"@keyv/mysql\",etcd:\"@keyv/etcd\",offline:\"@keyv/offline\",tiered:\"@keyv/tiered\"};if(t.adapter||t.uri){let r=t.adapter||/^[^:+]*/.exec(t.uri)[0];return new(Ie(e[r]))(t)}return new Map},k0e=[\"sqlite\",\"postgres\",\"mysql\",\"mongo\",\"redis\",\"tiered\"],xH=class extends m0t{constructor(e,{emitErrors:r=!0,...s}={}){if(super(),this.opts={namespace:\"keyv\",serialize:x0e.stringify,deserialize:x0e.parse,...typeof e==\"string\"?{uri:e}:e,...s},!this.opts.store){let n={...this.opts};this.opts.store=y0t(n)}if(this.opts.compression){let n=this.opts.compression;this.opts.serialize=n.serialize.bind(n),this.opts.deserialize=n.deserialize.bind(n)}typeof this.opts.store.on==\"function\"&&r&&this.opts.store.on(\"error\",n=>this.emit(\"error\",n)),this.opts.store.namespace=this.opts.namespace;let a=n=>async function*(){for await(let[c,f]of typeof n==\"function\"?n(this.opts.store.namespace):n){let p=await this.opts.deserialize(f);if(!(this.opts.store.namespace&&!c.includes(this.opts.store.namespace))){if(typeof p.expires==\"number\"&&Date.now()>p.expires){this.delete(c);continue}yield[this._getKeyUnprefix(c),p.value]}}};typeof this.opts.store[Symbol.iterator]==\"function\"&&this.opts.store instanceof Map?this.iterator=a(this.opts.store):typeof this.opts.store.iterator==\"function\"&&this.opts.store.opts&&this._checkIterableAdaptar()&&(this.iterator=a(this.opts.store.iterator.bind(this.opts.store)))}_checkIterableAdaptar(){return k0e.includes(this.opts.store.opts.dialect)||k0e.findIndex(e=>this.opts.store.opts.url.includes(e))>=0}_getKeyPrefix(e){return`${this.opts.namespace}:${e}`}_getKeyPrefixArray(e){return e.map(r=>`${this.opts.namespace}:${r}`)}_getKeyUnprefix(e){return e.split(\":\").splice(1).join(\":\")}get(e,r){let{store:s}=this.opts,a=Array.isArray(e),n=a?this._getKeyPrefixArray(e):this._getKeyPrefix(e);if(a&&s.getMany===void 0){let c=[];for(let f of n)c.push(Promise.resolve().then(()=>s.get(f)).then(p=>typeof p==\"string\"?this.opts.deserialize(p):this.opts.compression?this.opts.deserialize(p):p).then(p=>{if(p!=null)return typeof p.expires==\"number\"&&Date.now()>p.expires?this.delete(f).then(()=>{}):r&&r.raw?p:p.value}));return Promise.allSettled(c).then(f=>{let p=[];for(let h of f)p.push(h.value);return p})}return Promise.resolve().then(()=>a?s.getMany(n):s.get(n)).then(c=>typeof c==\"string\"?this.opts.deserialize(c):this.opts.compression?this.opts.deserialize(c):c).then(c=>{if(c!=null)return a?c.map((f,p)=>{if(typeof f==\"string\"&&(f=this.opts.deserialize(f)),f!=null){if(typeof f.expires==\"number\"&&Date.now()>f.expires){this.delete(e[p]).then(()=>{});return}return r&&r.raw?f:f.value}}):typeof c.expires==\"number\"&&Date.now()>c.expires?this.delete(e).then(()=>{}):r&&r.raw?c:c.value})}set(e,r,s){let a=this._getKeyPrefix(e);typeof s>\"u\"&&(s=this.opts.ttl),s===0&&(s=void 0);let{store:n}=this.opts;return Promise.resolve().then(()=>{let c=typeof s==\"number\"?Date.now()+s:null;return typeof r==\"symbol\"&&this.emit(\"error\",\"symbol cannot be serialized\"),r={value:r,expires:c},this.opts.serialize(r)}).then(c=>n.set(a,c,s)).then(()=>!0)}delete(e){let{store:r}=this.opts;if(Array.isArray(e)){let a=this._getKeyPrefixArray(e);if(r.deleteMany===void 0){let n=[];for(let c of a)n.push(r.delete(c));return Promise.allSettled(n).then(c=>c.every(f=>f.value===!0))}return Promise.resolve().then(()=>r.deleteMany(a))}let s=this._getKeyPrefix(e);return Promise.resolve().then(()=>r.delete(s))}clear(){let{store:e}=this.opts;return Promise.resolve().then(()=>e.clear())}has(e){let r=this._getKeyPrefix(e),{store:s}=this.opts;return Promise.resolve().then(async()=>typeof s.has==\"function\"?s.has(r):await s.get(r)!==void 0)}disconnect(){let{store:e}=this.opts;if(typeof e.disconnect==\"function\")return e.disconnect()}};Q0e.exports=xH});var N0e=L((UJt,F0e)=>{\"use strict\";var E0t=Ie(\"events\"),WQ=Ie(\"url\"),I0t=s0e(),C0t=y0e(),kH=I0e(),R0e=B0e(),w0t=GQ(),B0t=b0e(),v0t=T0e(),nv=class t{constructor(e,r){if(typeof e!=\"function\")throw new TypeError(\"Parameter `request` must be a function\");return this.cache=new v0t({uri:typeof r==\"string\"&&r,store:typeof r!=\"string\"&&r,namespace:\"cacheable-request\"}),this.createCacheableRequest(e)}createCacheableRequest(e){return(r,s)=>{let a;if(typeof r==\"string\")a=QH(WQ.parse(r)),r={};else if(r instanceof WQ.URL)a=QH(WQ.parse(r.toString())),r={};else{let[C,...S]=(r.path||\"\").split(\"?\"),P=S.length>0?`?${S.join(\"?\")}`:\"\";a=QH({...r,pathname:C,search:P})}r={headers:{},method:\"GET\",cache:!0,strictTtl:!1,automaticFailover:!1,...r,...S0t(a)},r.headers=w0t(r.headers);let n=new E0t,c=I0t(WQ.format(a),{stripWWW:!1,removeTrailingSlash:!1,stripAuthentication:!1}),f=`${r.method}:${c}`,p=!1,h=!1,E=C=>{h=!0;let S=!1,P,I=new Promise(N=>{P=()=>{S||(S=!0,N())}}),R=N=>{if(p&&!C.forceRefresh){N.status=N.statusCode;let W=kH.fromObject(p.cachePolicy).revalidatedPolicy(C,N);if(!W.modified){let te=W.policy.responseHeaders();N=new R0e(p.statusCode,te,p.body,p.url),N.cachePolicy=W.policy,N.fromCache=!0}}N.fromCache||(N.cachePolicy=new kH(C,N,C),N.fromCache=!1);let U;C.cache&&N.cachePolicy.storable()?(U=B0t(N),(async()=>{try{let W=C0t.buffer(N);if(await Promise.race([I,new Promise(ce=>N.once(\"end\",ce))]),S)return;let te=await W,ie={cachePolicy:N.cachePolicy.toObject(),url:N.url,statusCode:N.fromCache?p.statusCode:N.statusCode,body:te},Ae=C.strictTtl?N.cachePolicy.timeToLive():void 0;C.maxTtl&&(Ae=Ae?Math.min(Ae,C.maxTtl):C.maxTtl),await this.cache.set(f,ie,Ae)}catch(W){n.emit(\"error\",new t.CacheError(W))}})()):C.cache&&p&&(async()=>{try{await this.cache.delete(f)}catch(W){n.emit(\"error\",new t.CacheError(W))}})(),n.emit(\"response\",U||N),typeof s==\"function\"&&s(U||N)};try{let N=e(C,R);N.once(\"error\",P),N.once(\"abort\",P),n.emit(\"request\",N)}catch(N){n.emit(\"error\",new t.RequestError(N))}};return(async()=>{let C=async P=>{await Promise.resolve();let I=P.cache?await this.cache.get(f):void 0;if(typeof I>\"u\")return E(P);let R=kH.fromObject(I.cachePolicy);if(R.satisfiesWithoutRevalidation(P)&&!P.forceRefresh){let N=R.responseHeaders(),U=new R0e(I.statusCode,N,I.body,I.url);U.cachePolicy=R,U.fromCache=!0,n.emit(\"response\",U),typeof s==\"function\"&&s(U)}else p=I,P.headers=R.revalidationHeaders(P),E(P)},S=P=>n.emit(\"error\",new t.CacheError(P));this.cache.once(\"error\",S),n.on(\"response\",()=>this.cache.removeListener(\"error\",S));try{await C(r)}catch(P){r.automaticFailover&&!h&&E(r),n.emit(\"error\",new t.CacheError(P))}})(),n}}};function S0t(t){let e={...t};return e.path=`${t.pathname||\"/\"}${t.search||\"\"}`,delete e.pathname,delete e.search,e}function QH(t){return{protocol:t.protocol,auth:t.auth,hostname:t.hostname||t.host||\"localhost\",port:t.port,pathname:t.pathname,search:t.search}}nv.RequestError=class extends Error{constructor(t){super(t.message),this.name=\"RequestError\",Object.assign(this,t)}};nv.CacheError=class extends Error{constructor(t){super(t.message),this.name=\"CacheError\",Object.assign(this,t)}};F0e.exports=nv});var L0e=L((qJt,O0e)=>{\"use strict\";var D0t=[\"aborted\",\"complete\",\"headers\",\"httpVersion\",\"httpVersionMinor\",\"httpVersionMajor\",\"method\",\"rawHeaders\",\"rawTrailers\",\"setTimeout\",\"socket\",\"statusCode\",\"statusMessage\",\"trailers\",\"url\"];O0e.exports=(t,e)=>{if(e._readableState.autoDestroy)throw new Error(\"The second stream must have the `autoDestroy` option set to `false`\");let r=new Set(Object.keys(t).concat(D0t)),s={};for(let a of r)a in e||(s[a]={get(){let n=t[a];return typeof n==\"function\"?n.bind(t):n},set(n){t[a]=n},enumerable:!0,configurable:!1});return Object.defineProperties(e,s),t.once(\"aborted\",()=>{e.destroy(),e.emit(\"aborted\")}),t.once(\"close\",()=>{t.complete&&e.readable?e.once(\"end\",()=>{e.emit(\"close\")}):e.emit(\"close\")}),e}});var _0e=L((GJt,M0e)=>{\"use strict\";var{Transform:b0t,PassThrough:P0t}=Ie(\"stream\"),TH=Ie(\"zlib\"),x0t=L0e();M0e.exports=t=>{let e=(t.headers[\"content-encoding\"]||\"\").toLowerCase();if(![\"gzip\",\"deflate\",\"br\"].includes(e))return t;let r=e===\"br\";if(r&&typeof TH.createBrotliDecompress!=\"function\")return t.destroy(new Error(\"Brotli is not supported on Node.js < 12\")),t;let s=!0,a=new b0t({transform(f,p,h){s=!1,h(null,f)},flush(f){f()}}),n=new P0t({autoDestroy:!1,destroy(f,p){t.destroy(),p(f)}}),c=r?TH.createBrotliDecompress():TH.createUnzip();return c.once(\"error\",f=>{if(s&&!t.readable){n.end();return}n.destroy(f)}),x0t(t,n),t.pipe(a).pipe(c).pipe(n),n}});var FH=L((WJt,U0e)=>{\"use strict\";var RH=class{constructor(e={}){if(!(e.maxSize&&e.maxSize>0))throw new TypeError(\"`maxSize` must be a number greater than 0\");this.maxSize=e.maxSize,this.onEviction=e.onEviction,this.cache=new Map,this.oldCache=new Map,this._size=0}_set(e,r){if(this.cache.set(e,r),this._size++,this._size>=this.maxSize){if(this._size=0,typeof this.onEviction==\"function\")for(let[s,a]of this.oldCache.entries())this.onEviction(s,a);this.oldCache=this.cache,this.cache=new Map}}get(e){if(this.cache.has(e))return this.cache.get(e);if(this.oldCache.has(e)){let r=this.oldCache.get(e);return this.oldCache.delete(e),this._set(e,r),r}}set(e,r){return this.cache.has(e)?this.cache.set(e,r):this._set(e,r),this}has(e){return this.cache.has(e)||this.oldCache.has(e)}peek(e){if(this.cache.has(e))return this.cache.get(e);if(this.oldCache.has(e))return this.oldCache.get(e)}delete(e){let r=this.cache.delete(e);return r&&this._size--,this.oldCache.delete(e)||r}clear(){this.cache.clear(),this.oldCache.clear(),this._size=0}*keys(){for(let[e]of this)yield e}*values(){for(let[,e]of this)yield e}*[Symbol.iterator](){for(let e of this.cache)yield e;for(let e of this.oldCache){let[r]=e;this.cache.has(r)||(yield e)}}get size(){let e=0;for(let r of this.oldCache.keys())this.cache.has(r)||e++;return Math.min(this._size+e,this.maxSize)}};U0e.exports=RH});var OH=L((YJt,G0e)=>{\"use strict\";var k0t=Ie(\"events\"),Q0t=Ie(\"tls\"),T0t=Ie(\"http2\"),R0t=FH(),xa=Symbol(\"currentStreamsCount\"),H0e=Symbol(\"request\"),Nc=Symbol(\"cachedOriginSet\"),gI=Symbol(\"gracefullyClosing\"),F0t=[\"maxDeflateDynamicTableSize\",\"maxSessionMemory\",\"maxHeaderListPairs\",\"maxOutstandingPings\",\"maxReservedRemoteStreams\",\"maxSendHeaderBlockLength\",\"paddingStrategy\",\"localAddress\",\"path\",\"rejectUnauthorized\",\"minDHSize\",\"ca\",\"cert\",\"clientCertEngine\",\"ciphers\",\"key\",\"pfx\",\"servername\",\"minVersion\",\"maxVersion\",\"secureProtocol\",\"crl\",\"honorCipherOrder\",\"ecdhCurve\",\"dhparam\",\"secureOptions\",\"sessionIdContext\"],N0t=(t,e,r)=>{let s=0,a=t.length;for(;s<a;){let n=s+a>>>1;r(t[n],e)?s=n+1:a=n}return s},O0t=(t,e)=>t.remoteSettings.maxConcurrentStreams>e.remoteSettings.maxConcurrentStreams,NH=(t,e)=>{for(let r of t)r[Nc].length<e[Nc].length&&r[Nc].every(s=>e[Nc].includes(s))&&r[xa]+e[xa]<=e.remoteSettings.maxConcurrentStreams&&q0e(r)},L0t=(t,e)=>{for(let r of t)e[Nc].length<r[Nc].length&&e[Nc].every(s=>r[Nc].includes(s))&&e[xa]+r[xa]<=r.remoteSettings.maxConcurrentStreams&&q0e(e)},j0e=({agent:t,isFree:e})=>{let r={};for(let s in t.sessions){let n=t.sessions[s].filter(c=>{let f=c[rm.kCurrentStreamsCount]<c.remoteSettings.maxConcurrentStreams;return e?f:!f});n.length!==0&&(r[s]=n)}return r},q0e=t=>{t[gI]=!0,t[xa]===0&&t.close()},rm=class t extends k0t{constructor({timeout:e=6e4,maxSessions:r=1/0,maxFreeSessions:s=10,maxCachedTlsSessions:a=100}={}){super(),this.sessions={},this.queue={},this.timeout=e,this.maxSessions=r,this.maxFreeSessions=s,this._freeSessionsCount=0,this._sessionsCount=0,this.settings={enablePush:!1},this.tlsSessionCache=new R0t({maxSize:a})}static normalizeOrigin(e,r){return typeof e==\"string\"&&(e=new URL(e)),r&&e.hostname!==r&&(e.hostname=r),e.origin}normalizeOptions(e){let r=\"\";if(e)for(let s of F0t)e[s]&&(r+=`:${e[s]}`);return r}_tryToCreateNewSession(e,r){if(!(e in this.queue)||!(r in this.queue[e]))return;let s=this.queue[e][r];this._sessionsCount<this.maxSessions&&!s.completed&&(s.completed=!0,s())}getSession(e,r,s){return new Promise((a,n)=>{Array.isArray(s)?(s=[...s],a()):s=[{resolve:a,reject:n}];let c=this.normalizeOptions(r),f=t.normalizeOrigin(e,r&&r.servername);if(f===void 0){for(let{reject:E}of s)E(new TypeError(\"The `origin` argument needs to be a string or an URL object\"));return}if(c in this.sessions){let E=this.sessions[c],C=-1,S=-1,P;for(let I of E){let R=I.remoteSettings.maxConcurrentStreams;if(R<C)break;if(I[Nc].includes(f)){let N=I[xa];if(N>=R||I[gI]||I.destroyed)continue;P||(C=R),N>S&&(P=I,S=N)}}if(P){if(s.length!==1){for(let{reject:I}of s){let R=new Error(`Expected the length of listeners to be 1, got ${s.length}.\nPlease report this to https://github.com/szmarczak/http2-wrapper/`);I(R)}return}s[0].resolve(P);return}}if(c in this.queue){if(f in this.queue[c]){this.queue[c][f].listeners.push(...s),this._tryToCreateNewSession(c,f);return}}else this.queue[c]={};let p=()=>{c in this.queue&&this.queue[c][f]===h&&(delete this.queue[c][f],Object.keys(this.queue[c]).length===0&&delete this.queue[c])},h=()=>{let E=`${f}:${c}`,C=!1;try{let S=T0t.connect(e,{createConnection:this.createConnection,settings:this.settings,session:this.tlsSessionCache.get(E),...r});S[xa]=0,S[gI]=!1;let P=()=>S[xa]<S.remoteSettings.maxConcurrentStreams,I=!0;S.socket.once(\"session\",N=>{this.tlsSessionCache.set(E,N)}),S.once(\"error\",N=>{for(let{reject:U}of s)U(N);this.tlsSessionCache.delete(E)}),S.setTimeout(this.timeout,()=>{S.destroy()}),S.once(\"close\",()=>{if(C){I&&this._freeSessionsCount--,this._sessionsCount--;let N=this.sessions[c];N.splice(N.indexOf(S),1),N.length===0&&delete this.sessions[c]}else{let N=new Error(\"Session closed without receiving a SETTINGS frame\");N.code=\"HTTP2WRAPPER_NOSETTINGS\";for(let{reject:U}of s)U(N);p()}this._tryToCreateNewSession(c,f)});let R=()=>{if(!(!(c in this.queue)||!P())){for(let N of S[Nc])if(N in this.queue[c]){let{listeners:U}=this.queue[c][N];for(;U.length!==0&&P();)U.shift().resolve(S);let W=this.queue[c];if(W[N].listeners.length===0&&(delete W[N],Object.keys(W).length===0)){delete this.queue[c];break}if(!P())break}}};S.on(\"origin\",()=>{S[Nc]=S.originSet,P()&&(R(),NH(this.sessions[c],S))}),S.once(\"remoteSettings\",()=>{if(S.ref(),S.unref(),this._sessionsCount++,h.destroyed){let N=new Error(\"Agent has been destroyed\");for(let U of s)U.reject(N);S.destroy();return}S[Nc]=S.originSet;{let N=this.sessions;if(c in N){let U=N[c];U.splice(N0t(U,S,O0t),0,S)}else N[c]=[S]}this._freeSessionsCount+=1,C=!0,this.emit(\"session\",S),R(),p(),S[xa]===0&&this._freeSessionsCount>this.maxFreeSessions&&S.close(),s.length!==0&&(this.getSession(f,r,s),s.length=0),S.on(\"remoteSettings\",()=>{R(),NH(this.sessions[c],S)})}),S[H0e]=S.request,S.request=(N,U)=>{if(S[gI])throw new Error(\"The session is gracefully closing. No new streams are allowed.\");let W=S[H0e](N,U);return S.ref(),++S[xa],S[xa]===S.remoteSettings.maxConcurrentStreams&&this._freeSessionsCount--,W.once(\"close\",()=>{if(I=P(),--S[xa],!S.destroyed&&!S.closed&&(L0t(this.sessions[c],S),P()&&!S.closed)){I||(this._freeSessionsCount++,I=!0);let te=S[xa]===0;te&&S.unref(),te&&(this._freeSessionsCount>this.maxFreeSessions||S[gI])?S.close():(NH(this.sessions[c],S),R())}}),W}}catch(S){for(let P of s)P.reject(S);p()}};h.listeners=s,h.completed=!1,h.destroyed=!1,this.queue[c][f]=h,this._tryToCreateNewSession(c,f)})}request(e,r,s,a){return new Promise((n,c)=>{this.getSession(e,r,[{reject:c,resolve:f=>{try{n(f.request(s,a))}catch(p){c(p)}}}])})}createConnection(e,r){return t.connect(e,r)}static connect(e,r){r.ALPNProtocols=[\"h2\"];let s=e.port||443,a=e.hostname||e.host;return typeof r.servername>\"u\"&&(r.servername=a),Q0t.connect(s,a,r)}closeFreeSessions(){for(let e of Object.values(this.sessions))for(let r of e)r[xa]===0&&r.close()}destroy(e){for(let r of Object.values(this.sessions))for(let s of r)s.destroy(e);for(let r of Object.values(this.queue))for(let s of Object.values(r))s.destroyed=!0;this.queue={}}get freeSessions(){return j0e({agent:this,isFree:!0})}get busySessions(){return j0e({agent:this,isFree:!1})}};rm.kCurrentStreamsCount=xa;rm.kGracefullyClosing=gI;G0e.exports={Agent:rm,globalAgent:new rm}});var MH=L((VJt,W0e)=>{\"use strict\";var{Readable:M0t}=Ie(\"stream\"),LH=class extends M0t{constructor(e,r){super({highWaterMark:r,autoDestroy:!1}),this.statusCode=null,this.statusMessage=\"\",this.httpVersion=\"2.0\",this.httpVersionMajor=2,this.httpVersionMinor=0,this.headers={},this.trailers={},this.req=null,this.aborted=!1,this.complete=!1,this.upgrade=null,this.rawHeaders=[],this.rawTrailers=[],this.socket=e,this.connection=e,this._dumped=!1}_destroy(e){this.req._request.destroy(e)}setTimeout(e,r){return this.req.setTimeout(e,r),this}_dump(){this._dumped||(this._dumped=!0,this.removeAllListeners(\"data\"),this.resume())}_read(){this.req&&this.req._request.resume()}};W0e.exports=LH});var _H=L((KJt,Y0e)=>{\"use strict\";Y0e.exports=t=>{let e={protocol:t.protocol,hostname:typeof t.hostname==\"string\"&&t.hostname.startsWith(\"[\")?t.hostname.slice(1,-1):t.hostname,host:t.host,hash:t.hash,search:t.search,pathname:t.pathname,href:t.href,path:`${t.pathname||\"\"}${t.search||\"\"}`};return typeof t.port==\"string\"&&t.port.length!==0&&(e.port=Number(t.port)),(t.username||t.password)&&(e.auth=`${t.username||\"\"}:${t.password||\"\"}`),e}});var K0e=L((JJt,V0e)=>{\"use strict\";V0e.exports=(t,e,r)=>{for(let s of r)t.on(s,(...a)=>e.emit(s,...a))}});var z0e=L((zJt,J0e)=>{\"use strict\";J0e.exports=t=>{switch(t){case\":method\":case\":scheme\":case\":authority\":case\":path\":return!0;default:return!1}}});var X0e=L((XJt,Z0e)=>{\"use strict\";var dI=(t,e,r)=>{Z0e.exports[e]=class extends t{constructor(...a){super(typeof r==\"string\"?r:r(a)),this.name=`${super.name} [${e}]`,this.code=e}}};dI(TypeError,\"ERR_INVALID_ARG_TYPE\",t=>{let e=t[0].includes(\".\")?\"property\":\"argument\",r=t[1],s=Array.isArray(r);return s&&(r=`${r.slice(0,-1).join(\", \")} or ${r.slice(-1)}`),`The \"${t[0]}\" ${e} must be ${s?\"one of\":\"of\"} type ${r}. Received ${typeof t[2]}`});dI(TypeError,\"ERR_INVALID_PROTOCOL\",t=>`Protocol \"${t[0]}\" not supported. Expected \"${t[1]}\"`);dI(Error,\"ERR_HTTP_HEADERS_SENT\",t=>`Cannot ${t[0]} headers after they are sent to the client`);dI(TypeError,\"ERR_INVALID_HTTP_TOKEN\",t=>`${t[0]} must be a valid HTTP token [${t[1]}]`);dI(TypeError,\"ERR_HTTP_INVALID_HEADER_VALUE\",t=>`Invalid value \"${t[0]} for header \"${t[1]}\"`);dI(TypeError,\"ERR_INVALID_CHAR\",t=>`Invalid character in ${t[0]} [${t[1]}]`)});var GH=L(($Jt,sge)=>{\"use strict\";var _0t=Ie(\"http2\"),{Writable:U0t}=Ie(\"stream\"),{Agent:$0e,globalAgent:H0t}=OH(),j0t=MH(),q0t=_H(),G0t=K0e(),W0t=z0e(),{ERR_INVALID_ARG_TYPE:UH,ERR_INVALID_PROTOCOL:Y0t,ERR_HTTP_HEADERS_SENT:ege,ERR_INVALID_HTTP_TOKEN:V0t,ERR_HTTP_INVALID_HEADER_VALUE:K0t,ERR_INVALID_CHAR:J0t}=X0e(),{HTTP2_HEADER_STATUS:tge,HTTP2_HEADER_METHOD:rge,HTTP2_HEADER_PATH:nge,HTTP2_METHOD_CONNECT:z0t}=_0t.constants,Jo=Symbol(\"headers\"),HH=Symbol(\"origin\"),jH=Symbol(\"session\"),ige=Symbol(\"options\"),YQ=Symbol(\"flushedHeaders\"),iv=Symbol(\"jobs\"),Z0t=/^[\\^`\\-\\w!#$%&*+.|~]+$/,X0t=/[^\\t\\u0020-\\u007E\\u0080-\\u00FF]/,qH=class extends U0t{constructor(e,r,s){super({autoDestroy:!1});let a=typeof e==\"string\"||e instanceof URL;if(a&&(e=q0t(e instanceof URL?e:new URL(e))),typeof r==\"function\"||r===void 0?(s=r,r=a?e:{...e}):r={...e,...r},r.h2session)this[jH]=r.h2session;else if(r.agent===!1)this.agent=new $0e({maxFreeSessions:0});else if(typeof r.agent>\"u\"||r.agent===null)typeof r.createConnection==\"function\"?(this.agent=new $0e({maxFreeSessions:0}),this.agent.createConnection=r.createConnection):this.agent=H0t;else if(typeof r.agent.request==\"function\")this.agent=r.agent;else throw new UH(\"options.agent\",[\"Agent-like Object\",\"undefined\",\"false\"],r.agent);if(r.protocol&&r.protocol!==\"https:\")throw new Y0t(r.protocol,\"https:\");let n=r.port||r.defaultPort||this.agent&&this.agent.defaultPort||443,c=r.hostname||r.host||\"localhost\";delete r.hostname,delete r.host,delete r.port;let{timeout:f}=r;if(r.timeout=void 0,this[Jo]=Object.create(null),this[iv]=[],this.socket=null,this.connection=null,this.method=r.method||\"GET\",this.path=r.path,this.res=null,this.aborted=!1,this.reusedSocket=!1,r.headers)for(let[p,h]of Object.entries(r.headers))this.setHeader(p,h);r.auth&&!(\"authorization\"in this[Jo])&&(this[Jo].authorization=\"Basic \"+Buffer.from(r.auth).toString(\"base64\")),r.session=r.tlsSession,r.path=r.socketPath,this[ige]=r,n===443?(this[HH]=`https://${c}`,\":authority\"in this[Jo]||(this[Jo][\":authority\"]=c)):(this[HH]=`https://${c}:${n}`,\":authority\"in this[Jo]||(this[Jo][\":authority\"]=`${c}:${n}`)),f&&this.setTimeout(f),s&&this.once(\"response\",s),this[YQ]=!1}get method(){return this[Jo][rge]}set method(e){e&&(this[Jo][rge]=e.toUpperCase())}get path(){return this[Jo][nge]}set path(e){e&&(this[Jo][nge]=e)}get _mustNotHaveABody(){return this.method===\"GET\"||this.method===\"HEAD\"||this.method===\"DELETE\"}_write(e,r,s){if(this._mustNotHaveABody){s(new Error(\"The GET, HEAD and DELETE methods must NOT have a body\"));return}this.flushHeaders();let a=()=>this._request.write(e,r,s);this._request?a():this[iv].push(a)}_final(e){if(this.destroyed)return;this.flushHeaders();let r=()=>{if(this._mustNotHaveABody){e();return}this._request.end(e)};this._request?r():this[iv].push(r)}abort(){this.res&&this.res.complete||(this.aborted||process.nextTick(()=>this.emit(\"abort\")),this.aborted=!0,this.destroy())}_destroy(e,r){this.res&&this.res._dump(),this._request&&this._request.destroy(),r(e)}async flushHeaders(){if(this[YQ]||this.destroyed)return;this[YQ]=!0;let e=this.method===z0t,r=s=>{if(this._request=s,this.destroyed){s.destroy();return}e||G0t(s,this,[\"timeout\",\"continue\",\"close\",\"error\"]);let a=c=>(...f)=>{!this.writable&&!this.destroyed?c(...f):this.once(\"finish\",()=>{c(...f)})};s.once(\"response\",a((c,f,p)=>{let h=new j0t(this.socket,s.readableHighWaterMark);this.res=h,h.req=this,h.statusCode=c[tge],h.headers=c,h.rawHeaders=p,h.once(\"end\",()=>{this.aborted?(h.aborted=!0,h.emit(\"aborted\")):(h.complete=!0,h.socket=null,h.connection=null)}),e?(h.upgrade=!0,this.emit(\"connect\",h,s,Buffer.alloc(0))?this.emit(\"close\"):s.destroy()):(s.on(\"data\",E=>{!h._dumped&&!h.push(E)&&s.pause()}),s.once(\"end\",()=>{h.push(null)}),this.emit(\"response\",h)||h._dump())})),s.once(\"headers\",a(c=>this.emit(\"information\",{statusCode:c[tge]}))),s.once(\"trailers\",a((c,f,p)=>{let{res:h}=this;h.trailers=c,h.rawTrailers=p}));let{socket:n}=s.session;this.socket=n,this.connection=n;for(let c of this[iv])c();this.emit(\"socket\",this.socket)};if(this[jH])try{r(this[jH].request(this[Jo]))}catch(s){this.emit(\"error\",s)}else{this.reusedSocket=!0;try{r(await this.agent.request(this[HH],this[ige],this[Jo]))}catch(s){this.emit(\"error\",s)}}}getHeader(e){if(typeof e!=\"string\")throw new UH(\"name\",\"string\",e);return this[Jo][e.toLowerCase()]}get headersSent(){return this[YQ]}removeHeader(e){if(typeof e!=\"string\")throw new UH(\"name\",\"string\",e);if(this.headersSent)throw new ege(\"remove\");delete this[Jo][e.toLowerCase()]}setHeader(e,r){if(this.headersSent)throw new ege(\"set\");if(typeof e!=\"string\"||!Z0t.test(e)&&!W0t(e))throw new V0t(\"Header name\",e);if(typeof r>\"u\")throw new K0t(r,e);if(X0t.test(r))throw new J0t(\"header content\",e);this[Jo][e.toLowerCase()]=r}setNoDelay(){}setSocketKeepAlive(){}setTimeout(e,r){let s=()=>this._request.setTimeout(e,r);return this._request?s():this[iv].push(s),this}get maxHeadersCount(){if(!this.destroyed&&this._request)return this._request.session.localSettings.maxHeaderListSize}set maxHeadersCount(e){}};sge.exports=qH});var age=L((ezt,oge)=>{\"use strict\";var $0t=Ie(\"tls\");oge.exports=(t={},e=$0t.connect)=>new Promise((r,s)=>{let a=!1,n,c=async()=>{await p,n.off(\"timeout\",f),n.off(\"error\",s),t.resolveSocket?(r({alpnProtocol:n.alpnProtocol,socket:n,timeout:a}),a&&(await Promise.resolve(),n.emit(\"timeout\"))):(n.destroy(),r({alpnProtocol:n.alpnProtocol,timeout:a}))},f=async()=>{a=!0,c()},p=(async()=>{try{n=await e(t,c),n.on(\"error\",s),n.once(\"timeout\",f)}catch(h){s(h)}})()})});var cge=L((tzt,lge)=>{\"use strict\";var egt=Ie(\"net\");lge.exports=t=>{let e=t.host,r=t.headers&&t.headers.host;return r&&(r.startsWith(\"[\")?r.indexOf(\"]\")===-1?e=r:e=r.slice(1,-1):e=r.split(\":\",1)[0]),egt.isIP(e)?\"\":e}});var Age=L((rzt,YH)=>{\"use strict\";var uge=Ie(\"http\"),WH=Ie(\"https\"),tgt=age(),rgt=FH(),ngt=GH(),igt=cge(),sgt=_H(),VQ=new rgt({maxSize:100}),sv=new Map,fge=(t,e,r)=>{e._httpMessage={shouldKeepAlive:!0};let s=()=>{t.emit(\"free\",e,r)};e.on(\"free\",s);let a=()=>{t.removeSocket(e,r)};e.on(\"close\",a);let n=()=>{t.removeSocket(e,r),e.off(\"close\",a),e.off(\"free\",s),e.off(\"agentRemove\",n)};e.on(\"agentRemove\",n),t.emit(\"free\",e,r)},ogt=async t=>{let e=`${t.host}:${t.port}:${t.ALPNProtocols.sort()}`;if(!VQ.has(e)){if(sv.has(e))return(await sv.get(e)).alpnProtocol;let{path:r,agent:s}=t;t.path=t.socketPath;let a=tgt(t);sv.set(e,a);try{let{socket:n,alpnProtocol:c}=await a;if(VQ.set(e,c),t.path=r,c===\"h2\")n.destroy();else{let{globalAgent:f}=WH,p=WH.Agent.prototype.createConnection;s?s.createConnection===p?fge(s,n,t):n.destroy():f.createConnection===p?fge(f,n,t):n.destroy()}return sv.delete(e),c}catch(n){throw sv.delete(e),n}}return VQ.get(e)};YH.exports=async(t,e,r)=>{if((typeof t==\"string\"||t instanceof URL)&&(t=sgt(new URL(t))),typeof e==\"function\"&&(r=e,e=void 0),e={ALPNProtocols:[\"h2\",\"http/1.1\"],...t,...e,resolveSocket:!0},!Array.isArray(e.ALPNProtocols)||e.ALPNProtocols.length===0)throw new Error(\"The `ALPNProtocols` option must be an Array with at least one entry\");e.protocol=e.protocol||\"https:\";let s=e.protocol===\"https:\";e.host=e.hostname||e.host||\"localhost\",e.session=e.tlsSession,e.servername=e.servername||igt(e),e.port=e.port||(s?443:80),e._defaultAgent=s?WH.globalAgent:uge.globalAgent;let a=e.agent;if(a){if(a.addRequest)throw new Error(\"The `options.agent` object can contain only `http`, `https` or `http2` properties\");e.agent=a[s?\"https\":\"http\"]}return s&&await ogt(e)===\"h2\"?(a&&(e.agent=a.http2),new ngt(e,r)):uge.request(e,r)};YH.exports.protocolCache=VQ});var hge=L((nzt,pge)=>{\"use strict\";var agt=Ie(\"http2\"),lgt=OH(),VH=GH(),cgt=MH(),ugt=Age(),fgt=(t,e,r)=>new VH(t,e,r),Agt=(t,e,r)=>{let s=new VH(t,e,r);return s.end(),s};pge.exports={...agt,ClientRequest:VH,IncomingMessage:cgt,...lgt,request:fgt,get:Agt,auto:ugt}});var JH=L(KH=>{\"use strict\";Object.defineProperty(KH,\"__esModule\",{value:!0});var gge=Lp();KH.default=t=>gge.default.nodeStream(t)&&gge.default.function_(t.getBoundary)});var Ege=L(zH=>{\"use strict\";Object.defineProperty(zH,\"__esModule\",{value:!0});var mge=Ie(\"fs\"),yge=Ie(\"util\"),dge=Lp(),pgt=JH(),hgt=yge.promisify(mge.stat);zH.default=async(t,e)=>{if(e&&\"content-length\"in e)return Number(e[\"content-length\"]);if(!t)return 0;if(dge.default.string(t))return Buffer.byteLength(t);if(dge.default.buffer(t))return t.length;if(pgt.default(t))return yge.promisify(t.getLength.bind(t))();if(t instanceof mge.ReadStream){let{size:r}=await hgt(t.path);return r===0?void 0:r}}});var XH=L(ZH=>{\"use strict\";Object.defineProperty(ZH,\"__esModule\",{value:!0});function ggt(t,e,r){let s={};for(let a of r)s[a]=(...n)=>{e.emit(a,...n)},t.on(a,s[a]);return()=>{for(let a of r)t.off(a,s[a])}}ZH.default=ggt});var Ige=L($H=>{\"use strict\";Object.defineProperty($H,\"__esModule\",{value:!0});$H.default=()=>{let t=[];return{once(e,r,s){e.once(r,s),t.push({origin:e,event:r,fn:s})},unhandleAll(){for(let e of t){let{origin:r,event:s,fn:a}=e;r.removeListener(s,a)}t.length=0}}}});var wge=L(ov=>{\"use strict\";Object.defineProperty(ov,\"__esModule\",{value:!0});ov.TimeoutError=void 0;var dgt=Ie(\"net\"),mgt=Ige(),Cge=Symbol(\"reentry\"),ygt=()=>{},KQ=class extends Error{constructor(e,r){super(`Timeout awaiting '${r}' for ${e}ms`),this.event=r,this.name=\"TimeoutError\",this.code=\"ETIMEDOUT\"}};ov.TimeoutError=KQ;ov.default=(t,e,r)=>{if(Cge in t)return ygt;t[Cge]=!0;let s=[],{once:a,unhandleAll:n}=mgt.default(),c=(C,S,P)=>{var I;let R=setTimeout(S,C,C,P);(I=R.unref)===null||I===void 0||I.call(R);let N=()=>{clearTimeout(R)};return s.push(N),N},{host:f,hostname:p}=r,h=(C,S)=>{t.destroy(new KQ(C,S))},E=()=>{for(let C of s)C();n()};if(t.once(\"error\",C=>{if(E(),t.listenerCount(\"error\")===0)throw C}),t.once(\"close\",E),a(t,\"response\",C=>{a(C,\"end\",E)}),typeof e.request<\"u\"&&c(e.request,h,\"request\"),typeof e.socket<\"u\"){let C=()=>{h(e.socket,\"socket\")};t.setTimeout(e.socket,C),s.push(()=>{t.removeListener(\"timeout\",C)})}return a(t,\"socket\",C=>{var S;let{socketPath:P}=t;if(C.connecting){let I=!!(P??dgt.isIP((S=p??f)!==null&&S!==void 0?S:\"\")!==0);if(typeof e.lookup<\"u\"&&!I&&typeof C.address().address>\"u\"){let R=c(e.lookup,h,\"lookup\");a(C,\"lookup\",R)}if(typeof e.connect<\"u\"){let R=()=>c(e.connect,h,\"connect\");I?a(C,\"connect\",R()):a(C,\"lookup\",N=>{N===null&&a(C,\"connect\",R())})}typeof e.secureConnect<\"u\"&&r.protocol===\"https:\"&&a(C,\"connect\",()=>{let R=c(e.secureConnect,h,\"secureConnect\");a(C,\"secureConnect\",R)})}if(typeof e.send<\"u\"){let I=()=>c(e.send,h,\"send\");C.connecting?a(C,\"connect\",()=>{a(t,\"upload-complete\",I())}):a(t,\"upload-complete\",I())}}),typeof e.response<\"u\"&&a(t,\"upload-complete\",()=>{let C=c(e.response,h,\"response\");a(t,\"response\",C)}),E}});var vge=L(ej=>{\"use strict\";Object.defineProperty(ej,\"__esModule\",{value:!0});var Bge=Lp();ej.default=t=>{t=t;let e={protocol:t.protocol,hostname:Bge.default.string(t.hostname)&&t.hostname.startsWith(\"[\")?t.hostname.slice(1,-1):t.hostname,host:t.host,hash:t.hash,search:t.search,pathname:t.pathname,href:t.href,path:`${t.pathname||\"\"}${t.search||\"\"}`};return Bge.default.string(t.port)&&t.port.length>0&&(e.port=Number(t.port)),(t.username||t.password)&&(e.auth=`${t.username||\"\"}:${t.password||\"\"}`),e}});var Sge=L(tj=>{\"use strict\";Object.defineProperty(tj,\"__esModule\",{value:!0});var Egt=Ie(\"url\"),Igt=[\"protocol\",\"host\",\"hostname\",\"port\",\"pathname\",\"search\"];tj.default=(t,e)=>{var r,s;if(e.path){if(e.pathname)throw new TypeError(\"Parameters `path` and `pathname` are mutually exclusive.\");if(e.search)throw new TypeError(\"Parameters `path` and `search` are mutually exclusive.\");if(e.searchParams)throw new TypeError(\"Parameters `path` and `searchParams` are mutually exclusive.\")}if(e.search&&e.searchParams)throw new TypeError(\"Parameters `search` and `searchParams` are mutually exclusive.\");if(!t){if(!e.protocol)throw new TypeError(\"No URL protocol specified\");t=`${e.protocol}//${(s=(r=e.hostname)!==null&&r!==void 0?r:e.host)!==null&&s!==void 0?s:\"\"}`}let a=new Egt.URL(t);if(e.path){let n=e.path.indexOf(\"?\");n===-1?e.pathname=e.path:(e.pathname=e.path.slice(0,n),e.search=e.path.slice(n+1)),delete e.path}for(let n of Igt)e[n]&&(a[n]=e[n].toString());return a}});var Dge=L(nj=>{\"use strict\";Object.defineProperty(nj,\"__esModule\",{value:!0});var rj=class{constructor(){this.weakMap=new WeakMap,this.map=new Map}set(e,r){typeof e==\"object\"?this.weakMap.set(e,r):this.map.set(e,r)}get(e){return typeof e==\"object\"?this.weakMap.get(e):this.map.get(e)}has(e){return typeof e==\"object\"?this.weakMap.has(e):this.map.has(e)}};nj.default=rj});var sj=L(ij=>{\"use strict\";Object.defineProperty(ij,\"__esModule\",{value:!0});var Cgt=async t=>{let e=[],r=0;for await(let s of t)e.push(s),r+=Buffer.byteLength(s);return Buffer.isBuffer(e[0])?Buffer.concat(e,r):Buffer.from(e.join(\"\"))};ij.default=Cgt});var Pge=L(nm=>{\"use strict\";Object.defineProperty(nm,\"__esModule\",{value:!0});nm.dnsLookupIpVersionToFamily=nm.isDnsLookupIpVersion=void 0;var bge={auto:0,ipv4:4,ipv6:6};nm.isDnsLookupIpVersion=t=>t in bge;nm.dnsLookupIpVersionToFamily=t=>{if(nm.isDnsLookupIpVersion(t))return bge[t];throw new Error(\"Invalid DNS lookup IP version\")}});var oj=L(JQ=>{\"use strict\";Object.defineProperty(JQ,\"__esModule\",{value:!0});JQ.isResponseOk=void 0;JQ.isResponseOk=t=>{let{statusCode:e}=t,r=t.request.options.followRedirect?299:399;return e>=200&&e<=r||e===304}});var kge=L(aj=>{\"use strict\";Object.defineProperty(aj,\"__esModule\",{value:!0});var xge=new Set;aj.default=t=>{xge.has(t)||(xge.add(t),process.emitWarning(`Got: ${t}`,{type:\"DeprecationWarning\"}))}});var Qge=L(lj=>{\"use strict\";Object.defineProperty(lj,\"__esModule\",{value:!0});var bi=Lp(),wgt=(t,e)=>{if(bi.default.null_(t.encoding))throw new TypeError(\"To get a Buffer, set `options.responseType` to `buffer` instead\");bi.assert.any([bi.default.string,bi.default.undefined],t.encoding),bi.assert.any([bi.default.boolean,bi.default.undefined],t.resolveBodyOnly),bi.assert.any([bi.default.boolean,bi.default.undefined],t.methodRewriting),bi.assert.any([bi.default.boolean,bi.default.undefined],t.isStream),bi.assert.any([bi.default.string,bi.default.undefined],t.responseType),t.responseType===void 0&&(t.responseType=\"text\");let{retry:r}=t;if(e?t.retry={...e.retry}:t.retry={calculateDelay:s=>s.computedValue,limit:0,methods:[],statusCodes:[],errorCodes:[],maxRetryAfter:void 0},bi.default.object(r)?(t.retry={...t.retry,...r},t.retry.methods=[...new Set(t.retry.methods.map(s=>s.toUpperCase()))],t.retry.statusCodes=[...new Set(t.retry.statusCodes)],t.retry.errorCodes=[...new Set(t.retry.errorCodes)]):bi.default.number(r)&&(t.retry.limit=r),bi.default.undefined(t.retry.maxRetryAfter)&&(t.retry.maxRetryAfter=Math.min(...[t.timeout.request,t.timeout.connect].filter(bi.default.number))),bi.default.object(t.pagination)){e&&(t.pagination={...e.pagination,...t.pagination});let{pagination:s}=t;if(!bi.default.function_(s.transform))throw new Error(\"`options.pagination.transform` must be implemented\");if(!bi.default.function_(s.shouldContinue))throw new Error(\"`options.pagination.shouldContinue` must be implemented\");if(!bi.default.function_(s.filter))throw new TypeError(\"`options.pagination.filter` must be implemented\");if(!bi.default.function_(s.paginate))throw new Error(\"`options.pagination.paginate` must be implemented\")}return t.responseType===\"json\"&&t.headers.accept===void 0&&(t.headers.accept=\"application/json\"),t};lj.default=wgt});var Tge=L(av=>{\"use strict\";Object.defineProperty(av,\"__esModule\",{value:!0});av.retryAfterStatusCodes=void 0;av.retryAfterStatusCodes=new Set([413,429,503]);var Bgt=({attemptCount:t,retryOptions:e,error:r,retryAfter:s})=>{if(t>e.limit)return 0;let a=e.methods.includes(r.options.method),n=e.errorCodes.includes(r.code),c=r.response&&e.statusCodes.includes(r.response.statusCode);if(!a||!n&&!c)return 0;if(r.response){if(s)return e.maxRetryAfter===void 0||s>e.maxRetryAfter?0:s;if(r.response.statusCode===413)return 0}let f=Math.random()*100;return 2**(t-1)*1e3+f};av.default=Bgt});var uv=L(Ln=>{\"use strict\";Object.defineProperty(Ln,\"__esModule\",{value:!0});Ln.UnsupportedProtocolError=Ln.ReadError=Ln.TimeoutError=Ln.UploadError=Ln.CacheError=Ln.HTTPError=Ln.MaxRedirectsError=Ln.RequestError=Ln.setNonEnumerableProperties=Ln.knownHookEvents=Ln.withoutBody=Ln.kIsNormalizedAlready=void 0;var Rge=Ie(\"util\"),Fge=Ie(\"stream\"),vgt=Ie(\"fs\"),C0=Ie(\"url\"),Nge=Ie(\"http\"),cj=Ie(\"http\"),Sgt=Ie(\"https\"),Dgt=Jhe(),bgt=r0e(),Oge=N0e(),Pgt=_0e(),xgt=hge(),kgt=GQ(),at=Lp(),Qgt=Ege(),Lge=JH(),Tgt=XH(),Mge=wge(),Rgt=vge(),_ge=Sge(),Fgt=Dge(),Ngt=sj(),Uge=Pge(),Ogt=oj(),w0=kge(),Lgt=Qge(),Mgt=Tge(),uj,go=Symbol(\"request\"),XQ=Symbol(\"response\"),mI=Symbol(\"responseSize\"),yI=Symbol(\"downloadedSize\"),EI=Symbol(\"bodySize\"),II=Symbol(\"uploadedSize\"),zQ=Symbol(\"serverResponsesPiped\"),Hge=Symbol(\"unproxyEvents\"),jge=Symbol(\"isFromCache\"),fj=Symbol(\"cancelTimeouts\"),qge=Symbol(\"startedReading\"),CI=Symbol(\"stopReading\"),ZQ=Symbol(\"triggerRead\"),B0=Symbol(\"body\"),lv=Symbol(\"jobs\"),Gge=Symbol(\"originalResponse\"),Wge=Symbol(\"retryTimeout\");Ln.kIsNormalizedAlready=Symbol(\"isNormalizedAlready\");var _gt=at.default.string(process.versions.brotli);Ln.withoutBody=new Set([\"GET\",\"HEAD\"]);Ln.knownHookEvents=[\"init\",\"beforeRequest\",\"beforeRedirect\",\"beforeError\",\"beforeRetry\",\"afterResponse\"];function Ugt(t){for(let e in t){let r=t[e];if(!at.default.string(r)&&!at.default.number(r)&&!at.default.boolean(r)&&!at.default.null_(r)&&!at.default.undefined(r))throw new TypeError(`The \\`searchParams\\` value '${String(r)}' must be a string, number, boolean or null`)}}function Hgt(t){return at.default.object(t)&&!(\"statusCode\"in t)}var Aj=new Fgt.default,jgt=async t=>new Promise((e,r)=>{let s=a=>{r(a)};t.pending||e(),t.once(\"error\",s),t.once(\"ready\",()=>{t.off(\"error\",s),e()})}),qgt=new Set([300,301,302,303,304,307,308]),Ggt=[\"context\",\"body\",\"json\",\"form\"];Ln.setNonEnumerableProperties=(t,e)=>{let r={};for(let s of t)if(s)for(let a of Ggt)a in s&&(r[a]={writable:!0,configurable:!0,enumerable:!1,value:s[a]});Object.defineProperties(e,r)};var As=class extends Error{constructor(e,r,s){var a;if(super(e),Error.captureStackTrace(this,this.constructor),this.name=\"RequestError\",this.code=r.code,s instanceof sT?(Object.defineProperty(this,\"request\",{enumerable:!1,value:s}),Object.defineProperty(this,\"response\",{enumerable:!1,value:s[XQ]}),Object.defineProperty(this,\"options\",{enumerable:!1,value:s.options})):Object.defineProperty(this,\"options\",{enumerable:!1,value:s}),this.timings=(a=this.request)===null||a===void 0?void 0:a.timings,at.default.string(r.stack)&&at.default.string(this.stack)){let n=this.stack.indexOf(this.message)+this.message.length,c=this.stack.slice(n).split(`\n`).reverse(),f=r.stack.slice(r.stack.indexOf(r.message)+r.message.length).split(`\n`).reverse();for(;f.length!==0&&f[0]===c[0];)c.shift();this.stack=`${this.stack.slice(0,n)}${c.reverse().join(`\n`)}${f.reverse().join(`\n`)}`}}};Ln.RequestError=As;var $Q=class extends As{constructor(e){super(`Redirected ${e.options.maxRedirects} times. Aborting.`,{},e),this.name=\"MaxRedirectsError\"}};Ln.MaxRedirectsError=$Q;var eT=class extends As{constructor(e){super(`Response code ${e.statusCode} (${e.statusMessage})`,{},e.request),this.name=\"HTTPError\"}};Ln.HTTPError=eT;var tT=class extends As{constructor(e,r){super(e.message,e,r),this.name=\"CacheError\"}};Ln.CacheError=tT;var rT=class extends As{constructor(e,r){super(e.message,e,r),this.name=\"UploadError\"}};Ln.UploadError=rT;var nT=class extends As{constructor(e,r,s){super(e.message,e,s),this.name=\"TimeoutError\",this.event=e.event,this.timings=r}};Ln.TimeoutError=nT;var cv=class extends As{constructor(e,r){super(e.message,e,r),this.name=\"ReadError\"}};Ln.ReadError=cv;var iT=class extends As{constructor(e){super(`Unsupported protocol \"${e.url.protocol}\"`,{},e),this.name=\"UnsupportedProtocolError\"}};Ln.UnsupportedProtocolError=iT;var Wgt=[\"socket\",\"connect\",\"continue\",\"information\",\"upgrade\",\"timeout\"],sT=class extends Fge.Duplex{constructor(e,r={},s){super({autoDestroy:!1,highWaterMark:0}),this[yI]=0,this[II]=0,this.requestInitialized=!1,this[zQ]=new Set,this.redirects=[],this[CI]=!1,this[ZQ]=!1,this[lv]=[],this.retryCount=0,this._progressCallbacks=[];let a=()=>this._unlockWrite(),n=()=>this._lockWrite();this.on(\"pipe\",h=>{h.prependListener(\"data\",a),h.on(\"data\",n),h.prependListener(\"end\",a),h.on(\"end\",n)}),this.on(\"unpipe\",h=>{h.off(\"data\",a),h.off(\"data\",n),h.off(\"end\",a),h.off(\"end\",n)}),this.on(\"pipe\",h=>{h instanceof cj.IncomingMessage&&(this.options.headers={...h.headers,...this.options.headers})});let{json:c,body:f,form:p}=r;if((c||f||p)&&this._lockWrite(),Ln.kIsNormalizedAlready in r)this.options=r;else try{this.options=this.constructor.normalizeArguments(e,r,s)}catch(h){at.default.nodeStream(r.body)&&r.body.destroy(),this.destroy(h);return}(async()=>{var h;try{this.options.body instanceof vgt.ReadStream&&await jgt(this.options.body);let{url:E}=this.options;if(!E)throw new TypeError(\"Missing `url` property\");if(this.requestUrl=E.toString(),decodeURI(this.requestUrl),await this._finalizeBody(),await this._makeRequest(),this.destroyed){(h=this[go])===null||h===void 0||h.destroy();return}for(let C of this[lv])C();this[lv].length=0,this.requestInitialized=!0}catch(E){if(E instanceof As){this._beforeError(E);return}this.destroyed||this.destroy(E)}})()}static normalizeArguments(e,r,s){var a,n,c,f,p;let h=r;if(at.default.object(e)&&!at.default.urlInstance(e))r={...s,...e,...r};else{if(e&&r&&r.url!==void 0)throw new TypeError(\"The `url` option is mutually exclusive with the `input` argument\");r={...s,...r},e!==void 0&&(r.url=e),at.default.urlInstance(r.url)&&(r.url=new C0.URL(r.url.toString()))}if(r.cache===!1&&(r.cache=void 0),r.dnsCache===!1&&(r.dnsCache=void 0),at.assert.any([at.default.string,at.default.undefined],r.method),at.assert.any([at.default.object,at.default.undefined],r.headers),at.assert.any([at.default.string,at.default.urlInstance,at.default.undefined],r.prefixUrl),at.assert.any([at.default.object,at.default.undefined],r.cookieJar),at.assert.any([at.default.object,at.default.string,at.default.undefined],r.searchParams),at.assert.any([at.default.object,at.default.string,at.default.undefined],r.cache),at.assert.any([at.default.object,at.default.number,at.default.undefined],r.timeout),at.assert.any([at.default.object,at.default.undefined],r.context),at.assert.any([at.default.object,at.default.undefined],r.hooks),at.assert.any([at.default.boolean,at.default.undefined],r.decompress),at.assert.any([at.default.boolean,at.default.undefined],r.ignoreInvalidCookies),at.assert.any([at.default.boolean,at.default.undefined],r.followRedirect),at.assert.any([at.default.number,at.default.undefined],r.maxRedirects),at.assert.any([at.default.boolean,at.default.undefined],r.throwHttpErrors),at.assert.any([at.default.boolean,at.default.undefined],r.http2),at.assert.any([at.default.boolean,at.default.undefined],r.allowGetBody),at.assert.any([at.default.string,at.default.undefined],r.localAddress),at.assert.any([Uge.isDnsLookupIpVersion,at.default.undefined],r.dnsLookupIpVersion),at.assert.any([at.default.object,at.default.undefined],r.https),at.assert.any([at.default.boolean,at.default.undefined],r.rejectUnauthorized),r.https&&(at.assert.any([at.default.boolean,at.default.undefined],r.https.rejectUnauthorized),at.assert.any([at.default.function_,at.default.undefined],r.https.checkServerIdentity),at.assert.any([at.default.string,at.default.object,at.default.array,at.default.undefined],r.https.certificateAuthority),at.assert.any([at.default.string,at.default.object,at.default.array,at.default.undefined],r.https.key),at.assert.any([at.default.string,at.default.object,at.default.array,at.default.undefined],r.https.certificate),at.assert.any([at.default.string,at.default.undefined],r.https.passphrase),at.assert.any([at.default.string,at.default.buffer,at.default.array,at.default.undefined],r.https.pfx)),at.assert.any([at.default.object,at.default.undefined],r.cacheOptions),at.default.string(r.method)?r.method=r.method.toUpperCase():r.method=\"GET\",r.headers===s?.headers?r.headers={...r.headers}:r.headers=kgt({...s?.headers,...r.headers}),\"slashes\"in r)throw new TypeError(\"The legacy `url.Url` has been deprecated. Use `URL` instead.\");if(\"auth\"in r)throw new TypeError(\"Parameter `auth` is deprecated. Use `username` / `password` instead.\");if(\"searchParams\"in r&&r.searchParams&&r.searchParams!==s?.searchParams){let P;if(at.default.string(r.searchParams)||r.searchParams instanceof C0.URLSearchParams)P=new C0.URLSearchParams(r.searchParams);else{Ugt(r.searchParams),P=new C0.URLSearchParams;for(let I in r.searchParams){let R=r.searchParams[I];R===null?P.append(I,\"\"):R!==void 0&&P.append(I,R)}}(a=s?.searchParams)===null||a===void 0||a.forEach((I,R)=>{P.has(R)||P.append(R,I)}),r.searchParams=P}if(r.username=(n=r.username)!==null&&n!==void 0?n:\"\",r.password=(c=r.password)!==null&&c!==void 0?c:\"\",at.default.undefined(r.prefixUrl)?r.prefixUrl=(f=s?.prefixUrl)!==null&&f!==void 0?f:\"\":(r.prefixUrl=r.prefixUrl.toString(),r.prefixUrl!==\"\"&&!r.prefixUrl.endsWith(\"/\")&&(r.prefixUrl+=\"/\")),at.default.string(r.url)){if(r.url.startsWith(\"/\"))throw new Error(\"`input` must not start with a slash when using `prefixUrl`\");r.url=_ge.default(r.prefixUrl+r.url,r)}else(at.default.undefined(r.url)&&r.prefixUrl!==\"\"||r.protocol)&&(r.url=_ge.default(r.prefixUrl,r));if(r.url){\"port\"in r&&delete r.port;let{prefixUrl:P}=r;Object.defineProperty(r,\"prefixUrl\",{set:R=>{let N=r.url;if(!N.href.startsWith(R))throw new Error(`Cannot change \\`prefixUrl\\` from ${P} to ${R}: ${N.href}`);r.url=new C0.URL(R+N.href.slice(P.length)),P=R},get:()=>P});let{protocol:I}=r.url;if(I===\"unix:\"&&(I=\"http:\",r.url=new C0.URL(`http://unix${r.url.pathname}${r.url.search}`)),r.searchParams&&(r.url.search=r.searchParams.toString()),I!==\"http:\"&&I!==\"https:\")throw new iT(r);r.username===\"\"?r.username=r.url.username:r.url.username=r.username,r.password===\"\"?r.password=r.url.password:r.url.password=r.password}let{cookieJar:E}=r;if(E){let{setCookie:P,getCookieString:I}=E;at.assert.function_(P),at.assert.function_(I),P.length===4&&I.length===0&&(P=Rge.promisify(P.bind(r.cookieJar)),I=Rge.promisify(I.bind(r.cookieJar)),r.cookieJar={setCookie:P,getCookieString:I})}let{cache:C}=r;if(C&&(Aj.has(C)||Aj.set(C,new Oge((P,I)=>{let R=P[go](P,I);return at.default.promise(R)&&(R.once=(N,U)=>{if(N===\"error\")R.catch(U);else if(N===\"abort\")(async()=>{try{(await R).once(\"abort\",U)}catch{}})();else throw new Error(`Unknown HTTP2 promise event: ${N}`);return R}),R},C))),r.cacheOptions={...r.cacheOptions},r.dnsCache===!0)uj||(uj=new bgt.default),r.dnsCache=uj;else if(!at.default.undefined(r.dnsCache)&&!r.dnsCache.lookup)throw new TypeError(`Parameter \\`dnsCache\\` must be a CacheableLookup instance or a boolean, got ${at.default(r.dnsCache)}`);at.default.number(r.timeout)?r.timeout={request:r.timeout}:s&&r.timeout!==s.timeout?r.timeout={...s.timeout,...r.timeout}:r.timeout={...r.timeout},r.context||(r.context={});let S=r.hooks===s?.hooks;r.hooks={...r.hooks};for(let P of Ln.knownHookEvents)if(P in r.hooks)if(at.default.array(r.hooks[P]))r.hooks[P]=[...r.hooks[P]];else throw new TypeError(`Parameter \\`${P}\\` must be an Array, got ${at.default(r.hooks[P])}`);else r.hooks[P]=[];if(s&&!S)for(let P of Ln.knownHookEvents)s.hooks[P].length>0&&(r.hooks[P]=[...s.hooks[P],...r.hooks[P]]);if(\"family\"in r&&w0.default('\"options.family\" was never documented, please use \"options.dnsLookupIpVersion\"'),s?.https&&(r.https={...s.https,...r.https}),\"rejectUnauthorized\"in r&&w0.default('\"options.rejectUnauthorized\" is now deprecated, please use \"options.https.rejectUnauthorized\"'),\"checkServerIdentity\"in r&&w0.default('\"options.checkServerIdentity\" was never documented, please use \"options.https.checkServerIdentity\"'),\"ca\"in r&&w0.default('\"options.ca\" was never documented, please use \"options.https.certificateAuthority\"'),\"key\"in r&&w0.default('\"options.key\" was never documented, please use \"options.https.key\"'),\"cert\"in r&&w0.default('\"options.cert\" was never documented, please use \"options.https.certificate\"'),\"passphrase\"in r&&w0.default('\"options.passphrase\" was never documented, please use \"options.https.passphrase\"'),\"pfx\"in r&&w0.default('\"options.pfx\" was never documented, please use \"options.https.pfx\"'),\"followRedirects\"in r)throw new TypeError(\"The `followRedirects` option does not exist. Use `followRedirect` instead.\");if(r.agent){for(let P in r.agent)if(P!==\"http\"&&P!==\"https\"&&P!==\"http2\")throw new TypeError(`Expected the \\`options.agent\\` properties to be \\`http\\`, \\`https\\` or \\`http2\\`, got \\`${P}\\``)}return r.maxRedirects=(p=r.maxRedirects)!==null&&p!==void 0?p:0,Ln.setNonEnumerableProperties([s,h],r),Lgt.default(r,s)}_lockWrite(){let e=()=>{throw new TypeError(\"The payload has been already provided\")};this.write=e,this.end=e}_unlockWrite(){this.write=super.write,this.end=super.end}async _finalizeBody(){let{options:e}=this,{headers:r}=e,s=!at.default.undefined(e.form),a=!at.default.undefined(e.json),n=!at.default.undefined(e.body),c=s||a||n,f=Ln.withoutBody.has(e.method)&&!(e.method===\"GET\"&&e.allowGetBody);if(this._cannotHaveBody=f,c){if(f)throw new TypeError(`The \\`${e.method}\\` method cannot be used with a body`);if([n,s,a].filter(p=>p).length>1)throw new TypeError(\"The `body`, `json` and `form` options are mutually exclusive\");if(n&&!(e.body instanceof Fge.Readable)&&!at.default.string(e.body)&&!at.default.buffer(e.body)&&!Lge.default(e.body))throw new TypeError(\"The `body` option must be a stream.Readable, string or Buffer\");if(s&&!at.default.object(e.form))throw new TypeError(\"The `form` option must be an Object\");{let p=!at.default.string(r[\"content-type\"]);n?(Lge.default(e.body)&&p&&(r[\"content-type\"]=`multipart/form-data; boundary=${e.body.getBoundary()}`),this[B0]=e.body):s?(p&&(r[\"content-type\"]=\"application/x-www-form-urlencoded\"),this[B0]=new C0.URLSearchParams(e.form).toString()):(p&&(r[\"content-type\"]=\"application/json\"),this[B0]=e.stringifyJson(e.json));let h=await Qgt.default(this[B0],e.headers);at.default.undefined(r[\"content-length\"])&&at.default.undefined(r[\"transfer-encoding\"])&&!f&&!at.default.undefined(h)&&(r[\"content-length\"]=String(h))}}else f?this._lockWrite():this._unlockWrite();this[EI]=Number(r[\"content-length\"])||void 0}async _onResponseBase(e){let{options:r}=this,{url:s}=r;this[Gge]=e,r.decompress&&(e=Pgt(e));let a=e.statusCode,n=e;n.statusMessage=n.statusMessage?n.statusMessage:Nge.STATUS_CODES[a],n.url=r.url.toString(),n.requestUrl=this.requestUrl,n.redirectUrls=this.redirects,n.request=this,n.isFromCache=e.fromCache||!1,n.ip=this.ip,n.retryCount=this.retryCount,this[jge]=n.isFromCache,this[mI]=Number(e.headers[\"content-length\"])||void 0,this[XQ]=e,e.once(\"end\",()=>{this[mI]=this[yI],this.emit(\"downloadProgress\",this.downloadProgress)}),e.once(\"error\",f=>{e.destroy(),this._beforeError(new cv(f,this))}),e.once(\"aborted\",()=>{this._beforeError(new cv({name:\"Error\",message:\"The server aborted pending request\",code:\"ECONNRESET\"},this))}),this.emit(\"downloadProgress\",this.downloadProgress);let c=e.headers[\"set-cookie\"];if(at.default.object(r.cookieJar)&&c){let f=c.map(async p=>r.cookieJar.setCookie(p,s.toString()));r.ignoreInvalidCookies&&(f=f.map(async p=>p.catch(()=>{})));try{await Promise.all(f)}catch(p){this._beforeError(p);return}}if(r.followRedirect&&e.headers.location&&qgt.has(a)){if(e.resume(),this[go]&&(this[fj](),delete this[go],this[Hge]()),(a===303&&r.method!==\"GET\"&&r.method!==\"HEAD\"||!r.methodRewriting)&&(r.method=\"GET\",\"body\"in r&&delete r.body,\"json\"in r&&delete r.json,\"form\"in r&&delete r.form,this[B0]=void 0,delete r.headers[\"content-length\"]),this.redirects.length>=r.maxRedirects){this._beforeError(new $Q(this));return}try{let p=Buffer.from(e.headers.location,\"binary\").toString(),h=new C0.URL(p,s),E=h.toString();decodeURI(E),h.hostname!==s.hostname||h.port!==s.port?(\"host\"in r.headers&&delete r.headers.host,\"cookie\"in r.headers&&delete r.headers.cookie,\"authorization\"in r.headers&&delete r.headers.authorization,(r.username||r.password)&&(r.username=\"\",r.password=\"\")):(h.username=r.username,h.password=r.password),this.redirects.push(E),r.url=h;for(let C of r.hooks.beforeRedirect)await C(r,n);this.emit(\"redirect\",n,r),await this._makeRequest()}catch(p){this._beforeError(p);return}return}if(r.isStream&&r.throwHttpErrors&&!Ogt.isResponseOk(n)){this._beforeError(new eT(n));return}e.on(\"readable\",()=>{this[ZQ]&&this._read()}),this.on(\"resume\",()=>{e.resume()}),this.on(\"pause\",()=>{e.pause()}),e.once(\"end\",()=>{this.push(null)}),this.emit(\"response\",e);for(let f of this[zQ])if(!f.headersSent){for(let p in e.headers){let h=r.decompress?p!==\"content-encoding\":!0,E=e.headers[p];h&&f.setHeader(p,E)}f.statusCode=a}}async _onResponse(e){try{await this._onResponseBase(e)}catch(r){this._beforeError(r)}}_onRequest(e){let{options:r}=this,{timeout:s,url:a}=r;Dgt.default(e),this[fj]=Mge.default(e,s,a);let n=r.cache?\"cacheableResponse\":\"response\";e.once(n,p=>{this._onResponse(p)}),e.once(\"error\",p=>{var h;e.destroy(),(h=e.res)===null||h===void 0||h.removeAllListeners(\"end\"),p=p instanceof Mge.TimeoutError?new nT(p,this.timings,this):new As(p.message,p,this),this._beforeError(p)}),this[Hge]=Tgt.default(e,this,Wgt),this[go]=e,this.emit(\"uploadProgress\",this.uploadProgress);let c=this[B0],f=this.redirects.length===0?this:e;at.default.nodeStream(c)?(c.pipe(f),c.once(\"error\",p=>{this._beforeError(new rT(p,this))})):(this._unlockWrite(),at.default.undefined(c)?(this._cannotHaveBody||this._noPipe)&&(f.end(),this._lockWrite()):(this._writeRequest(c,void 0,()=>{}),f.end(),this._lockWrite())),this.emit(\"request\",e)}async _createCacheableRequest(e,r){return new Promise((s,a)=>{Object.assign(r,Rgt.default(e)),delete r.url;let n,c=Aj.get(r.cache)(r,async f=>{f._readableState.autoDestroy=!1,n&&(await n).emit(\"cacheableResponse\",f),s(f)});r.url=e,c.once(\"error\",a),c.once(\"request\",async f=>{n=f,s(n)})})}async _makeRequest(){var e,r,s,a,n;let{options:c}=this,{headers:f}=c;for(let U in f)if(at.default.undefined(f[U]))delete f[U];else if(at.default.null_(f[U]))throw new TypeError(`Use \\`undefined\\` instead of \\`null\\` to delete the \\`${U}\\` header`);if(c.decompress&&at.default.undefined(f[\"accept-encoding\"])&&(f[\"accept-encoding\"]=_gt?\"gzip, deflate, br\":\"gzip, deflate\"),c.cookieJar){let U=await c.cookieJar.getCookieString(c.url.toString());at.default.nonEmptyString(U)&&(c.headers.cookie=U)}for(let U of c.hooks.beforeRequest){let W=await U(c);if(!at.default.undefined(W)){c.request=()=>W;break}}c.body&&this[B0]!==c.body&&(this[B0]=c.body);let{agent:p,request:h,timeout:E,url:C}=c;if(c.dnsCache&&!(\"lookup\"in c)&&(c.lookup=c.dnsCache.lookup),C.hostname===\"unix\"){let U=/(?<socketPath>.+?):(?<path>.+)/.exec(`${C.pathname}${C.search}`);if(U?.groups){let{socketPath:W,path:te}=U.groups;Object.assign(c,{socketPath:W,path:te,host:\"\"})}}let S=C.protocol===\"https:\",P;c.http2?P=xgt.auto:P=S?Sgt.request:Nge.request;let I=(e=c.request)!==null&&e!==void 0?e:P,R=c.cache?this._createCacheableRequest:I;p&&!c.http2&&(c.agent=p[S?\"https\":\"http\"]),c[go]=I,delete c.request,delete c.timeout;let N=c;if(N.shared=(r=c.cacheOptions)===null||r===void 0?void 0:r.shared,N.cacheHeuristic=(s=c.cacheOptions)===null||s===void 0?void 0:s.cacheHeuristic,N.immutableMinTimeToLive=(a=c.cacheOptions)===null||a===void 0?void 0:a.immutableMinTimeToLive,N.ignoreCargoCult=(n=c.cacheOptions)===null||n===void 0?void 0:n.ignoreCargoCult,c.dnsLookupIpVersion!==void 0)try{N.family=Uge.dnsLookupIpVersionToFamily(c.dnsLookupIpVersion)}catch{throw new Error(\"Invalid `dnsLookupIpVersion` option value\")}c.https&&(\"rejectUnauthorized\"in c.https&&(N.rejectUnauthorized=c.https.rejectUnauthorized),c.https.checkServerIdentity&&(N.checkServerIdentity=c.https.checkServerIdentity),c.https.certificateAuthority&&(N.ca=c.https.certificateAuthority),c.https.certificate&&(N.cert=c.https.certificate),c.https.key&&(N.key=c.https.key),c.https.passphrase&&(N.passphrase=c.https.passphrase),c.https.pfx&&(N.pfx=c.https.pfx));try{let U=await R(C,N);at.default.undefined(U)&&(U=P(C,N)),c.request=h,c.timeout=E,c.agent=p,c.https&&(\"rejectUnauthorized\"in c.https&&delete N.rejectUnauthorized,c.https.checkServerIdentity&&delete N.checkServerIdentity,c.https.certificateAuthority&&delete N.ca,c.https.certificate&&delete N.cert,c.https.key&&delete N.key,c.https.passphrase&&delete N.passphrase,c.https.pfx&&delete N.pfx),Hgt(U)?this._onRequest(U):this.writable?(this.once(\"finish\",()=>{this._onResponse(U)}),this._unlockWrite(),this.end(),this._lockWrite()):this._onResponse(U)}catch(U){throw U instanceof Oge.CacheError?new tT(U,this):new As(U.message,U,this)}}async _error(e){try{for(let r of this.options.hooks.beforeError)e=await r(e)}catch(r){e=new As(r.message,r,this)}this.destroy(e)}_beforeError(e){if(this[CI])return;let{options:r}=this,s=this.retryCount+1;this[CI]=!0,e instanceof As||(e=new As(e.message,e,this));let a=e,{response:n}=a;(async()=>{if(n&&!n.body){n.setEncoding(this._readableState.encoding);try{n.rawBody=await Ngt.default(n),n.body=n.rawBody.toString()}catch{}}if(this.listenerCount(\"retry\")!==0){let c;try{let f;n&&\"retry-after\"in n.headers&&(f=Number(n.headers[\"retry-after\"]),Number.isNaN(f)?(f=Date.parse(n.headers[\"retry-after\"])-Date.now(),f<=0&&(f=1)):f*=1e3),c=await r.retry.calculateDelay({attemptCount:s,retryOptions:r.retry,error:a,retryAfter:f,computedValue:Mgt.default({attemptCount:s,retryOptions:r.retry,error:a,retryAfter:f,computedValue:0})})}catch(f){this._error(new As(f.message,f,this));return}if(c){let f=async()=>{try{for(let p of this.options.hooks.beforeRetry)await p(this.options,a,s)}catch(p){this._error(new As(p.message,e,this));return}this.destroyed||(this.destroy(),this.emit(\"retry\",s,e))};this[Wge]=setTimeout(f,c);return}}this._error(a)})()}_read(){this[ZQ]=!0;let e=this[XQ];if(e&&!this[CI]){e.readableLength&&(this[ZQ]=!1);let r;for(;(r=e.read())!==null;){this[yI]+=r.length,this[qge]=!0;let s=this.downloadProgress;s.percent<1&&this.emit(\"downloadProgress\",s),this.push(r)}}}_write(e,r,s){let a=()=>{this._writeRequest(e,r,s)};this.requestInitialized?a():this[lv].push(a)}_writeRequest(e,r,s){this[go].destroyed||(this._progressCallbacks.push(()=>{this[II]+=Buffer.byteLength(e,r);let a=this.uploadProgress;a.percent<1&&this.emit(\"uploadProgress\",a)}),this[go].write(e,r,a=>{!a&&this._progressCallbacks.length>0&&this._progressCallbacks.shift()(),s(a)}))}_final(e){let r=()=>{for(;this._progressCallbacks.length!==0;)this._progressCallbacks.shift()();if(!(go in this)){e();return}if(this[go].destroyed){e();return}this[go].end(s=>{s||(this[EI]=this[II],this.emit(\"uploadProgress\",this.uploadProgress),this[go].emit(\"upload-complete\")),e(s)})};this.requestInitialized?r():this[lv].push(r)}_destroy(e,r){var s;this[CI]=!0,clearTimeout(this[Wge]),go in this&&(this[fj](),!((s=this[XQ])===null||s===void 0)&&s.complete||this[go].destroy()),e!==null&&!at.default.undefined(e)&&!(e instanceof As)&&(e=new As(e.message,e,this)),r(e)}get _isAboutToError(){return this[CI]}get ip(){var e;return(e=this.socket)===null||e===void 0?void 0:e.remoteAddress}get aborted(){var e,r,s;return((r=(e=this[go])===null||e===void 0?void 0:e.destroyed)!==null&&r!==void 0?r:this.destroyed)&&!(!((s=this[Gge])===null||s===void 0)&&s.complete)}get socket(){var e,r;return(r=(e=this[go])===null||e===void 0?void 0:e.socket)!==null&&r!==void 0?r:void 0}get downloadProgress(){let e;return this[mI]?e=this[yI]/this[mI]:this[mI]===this[yI]?e=1:e=0,{percent:e,transferred:this[yI],total:this[mI]}}get uploadProgress(){let e;return this[EI]?e=this[II]/this[EI]:this[EI]===this[II]?e=1:e=0,{percent:e,transferred:this[II],total:this[EI]}}get timings(){var e;return(e=this[go])===null||e===void 0?void 0:e.timings}get isFromCache(){return this[jge]}pipe(e,r){if(this[qge])throw new Error(\"Failed to pipe. The response has been emitted already.\");return e instanceof cj.ServerResponse&&this[zQ].add(e),super.pipe(e,r)}unpipe(e){return e instanceof cj.ServerResponse&&this[zQ].delete(e),super.unpipe(e),this}};Ln.default=sT});var fv=L(Wu=>{\"use strict\";var Ygt=Wu&&Wu.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r),Object.defineProperty(t,s,{enumerable:!0,get:function(){return e[r]}})}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),Vgt=Wu&&Wu.__exportStar||function(t,e){for(var r in t)r!==\"default\"&&!Object.prototype.hasOwnProperty.call(e,r)&&Ygt(e,t,r)};Object.defineProperty(Wu,\"__esModule\",{value:!0});Wu.CancelError=Wu.ParseError=void 0;var Yge=uv(),pj=class extends Yge.RequestError{constructor(e,r){let{options:s}=r.request;super(`${e.message} in \"${s.url.toString()}\"`,e,r.request),this.name=\"ParseError\"}};Wu.ParseError=pj;var hj=class extends Yge.RequestError{constructor(e){super(\"Promise was canceled\",{},e),this.name=\"CancelError\"}get isCanceled(){return!0}};Wu.CancelError=hj;Vgt(uv(),Wu)});var Kge=L(gj=>{\"use strict\";Object.defineProperty(gj,\"__esModule\",{value:!0});var Vge=fv(),Kgt=(t,e,r,s)=>{let{rawBody:a}=t;try{if(e===\"text\")return a.toString(s);if(e===\"json\")return a.length===0?\"\":r(a.toString());if(e===\"buffer\")return a;throw new Vge.ParseError({message:`Unknown body type '${e}'`,name:\"Error\"},t)}catch(n){throw new Vge.ParseError(n,t)}};gj.default=Kgt});var dj=L(v0=>{\"use strict\";var Jgt=v0&&v0.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r),Object.defineProperty(t,s,{enumerable:!0,get:function(){return e[r]}})}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),zgt=v0&&v0.__exportStar||function(t,e){for(var r in t)r!==\"default\"&&!Object.prototype.hasOwnProperty.call(e,r)&&Jgt(e,t,r)};Object.defineProperty(v0,\"__esModule\",{value:!0});var Zgt=Ie(\"events\"),Xgt=Lp(),$gt=Vhe(),oT=fv(),Jge=Kge(),zge=uv(),edt=XH(),tdt=sj(),Zge=oj(),rdt=[\"request\",\"response\",\"redirect\",\"uploadProgress\",\"downloadProgress\"];function Xge(t){let e,r,s=new Zgt.EventEmitter,a=new $gt((c,f,p)=>{let h=E=>{let C=new zge.default(void 0,t);C.retryCount=E,C._noPipe=!0,p(()=>C.destroy()),p.shouldReject=!1,p(()=>f(new oT.CancelError(C))),e=C,C.once(\"response\",async I=>{var R;if(I.retryCount=E,I.request.aborted)return;let N;try{N=await tdt.default(C),I.rawBody=N}catch{return}if(C._isAboutToError)return;let U=((R=I.headers[\"content-encoding\"])!==null&&R!==void 0?R:\"\").toLowerCase(),W=[\"gzip\",\"deflate\",\"br\"].includes(U),{options:te}=C;if(W&&!te.decompress)I.body=N;else try{I.body=Jge.default(I,te.responseType,te.parseJson,te.encoding)}catch(ie){if(I.body=N.toString(),Zge.isResponseOk(I)){C._beforeError(ie);return}}try{for(let[ie,Ae]of te.hooks.afterResponse.entries())I=await Ae(I,async ce=>{let me=zge.default.normalizeArguments(void 0,{...ce,retry:{calculateDelay:()=>0},throwHttpErrors:!1,resolveBodyOnly:!1},te);me.hooks.afterResponse=me.hooks.afterResponse.slice(0,ie);for(let Be of me.hooks.beforeRetry)await Be(me);let pe=Xge(me);return p(()=>{pe.catch(()=>{}),pe.cancel()}),pe})}catch(ie){C._beforeError(new oT.RequestError(ie.message,ie,C));return}if(!Zge.isResponseOk(I)){C._beforeError(new oT.HTTPError(I));return}r=I,c(C.options.resolveBodyOnly?I.body:I)});let S=I=>{if(a.isCanceled)return;let{options:R}=C;if(I instanceof oT.HTTPError&&!R.throwHttpErrors){let{response:N}=I;c(C.options.resolveBodyOnly?N.body:N);return}f(I)};C.once(\"error\",S);let P=C.options.body;C.once(\"retry\",(I,R)=>{var N,U;if(P===((N=R.request)===null||N===void 0?void 0:N.options.body)&&Xgt.default.nodeStream((U=R.request)===null||U===void 0?void 0:U.options.body)){S(R);return}h(I)}),edt.default(C,s,rdt)};h(0)});a.on=(c,f)=>(s.on(c,f),a);let n=c=>{let f=(async()=>{await a;let{options:p}=r.request;return Jge.default(r,c,p.parseJson,p.encoding)})();return Object.defineProperties(f,Object.getOwnPropertyDescriptors(a)),f};return a.json=()=>{let{headers:c}=e.options;return!e.writableFinished&&c.accept===void 0&&(c.accept=\"application/json\"),n(\"json\")},a.buffer=()=>n(\"buffer\"),a.text=()=>n(\"text\"),a}v0.default=Xge;zgt(fv(),v0)});var $ge=L(mj=>{\"use strict\";Object.defineProperty(mj,\"__esModule\",{value:!0});var ndt=fv();function idt(t,...e){let r=(async()=>{if(t instanceof ndt.RequestError)try{for(let a of e)if(a)for(let n of a)t=await n(t)}catch(a){t=a}throw t})(),s=()=>r;return r.json=s,r.text=s,r.buffer=s,r.on=s,r}mj.default=idt});var rde=L(yj=>{\"use strict\";Object.defineProperty(yj,\"__esModule\",{value:!0});var ede=Lp();function tde(t){for(let e of Object.values(t))(ede.default.plainObject(e)||ede.default.array(e))&&tde(e);return Object.freeze(t)}yj.default=tde});var ide=L(nde=>{\"use strict\";Object.defineProperty(nde,\"__esModule\",{value:!0})});var Ej=L(Lc=>{\"use strict\";var sdt=Lc&&Lc.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r),Object.defineProperty(t,s,{enumerable:!0,get:function(){return e[r]}})}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),odt=Lc&&Lc.__exportStar||function(t,e){for(var r in t)r!==\"default\"&&!Object.prototype.hasOwnProperty.call(e,r)&&sdt(e,t,r)};Object.defineProperty(Lc,\"__esModule\",{value:!0});Lc.defaultHandler=void 0;var sde=Lp(),Oc=dj(),adt=$ge(),lT=uv(),ldt=rde(),cdt={RequestError:Oc.RequestError,CacheError:Oc.CacheError,ReadError:Oc.ReadError,HTTPError:Oc.HTTPError,MaxRedirectsError:Oc.MaxRedirectsError,TimeoutError:Oc.TimeoutError,ParseError:Oc.ParseError,CancelError:Oc.CancelError,UnsupportedProtocolError:Oc.UnsupportedProtocolError,UploadError:Oc.UploadError},udt=async t=>new Promise(e=>{setTimeout(e,t)}),{normalizeArguments:aT}=lT.default,ode=(...t)=>{let e;for(let r of t)e=aT(void 0,r,e);return e},fdt=t=>t.isStream?new lT.default(void 0,t):Oc.default(t),Adt=t=>\"defaults\"in t&&\"options\"in t.defaults,pdt=[\"get\",\"post\",\"put\",\"patch\",\"head\",\"delete\"];Lc.defaultHandler=(t,e)=>e(t);var ade=(t,e)=>{if(t)for(let r of t)r(e)},lde=t=>{t._rawHandlers=t.handlers,t.handlers=t.handlers.map(s=>(a,n)=>{let c,f=s(a,p=>(c=n(p),c));if(f!==c&&!a.isStream&&c){let p=f,{then:h,catch:E,finally:C}=p;Object.setPrototypeOf(p,Object.getPrototypeOf(c)),Object.defineProperties(p,Object.getOwnPropertyDescriptors(c)),p.then=h,p.catch=E,p.finally=C}return f});let e=(s,a={},n)=>{var c,f;let p=0,h=E=>t.handlers[p++](E,p===t.handlers.length?fdt:h);if(sde.default.plainObject(s)){let E={...s,...a};lT.setNonEnumerableProperties([s,a],E),a=E,s=void 0}try{let E;try{ade(t.options.hooks.init,a),ade((c=a.hooks)===null||c===void 0?void 0:c.init,a)}catch(S){E=S}let C=aT(s,a,n??t.options);if(C[lT.kIsNormalizedAlready]=!0,E)throw new Oc.RequestError(E.message,E,C);return h(C)}catch(E){if(a.isStream)throw E;return adt.default(E,t.options.hooks.beforeError,(f=a.hooks)===null||f===void 0?void 0:f.beforeError)}};e.extend=(...s)=>{let a=[t.options],n=[...t._rawHandlers],c;for(let f of s)Adt(f)?(a.push(f.defaults.options),n.push(...f.defaults._rawHandlers),c=f.defaults.mutableDefaults):(a.push(f),\"handlers\"in f&&n.push(...f.handlers),c=f.mutableDefaults);return n=n.filter(f=>f!==Lc.defaultHandler),n.length===0&&n.push(Lc.defaultHandler),lde({options:ode(...a),handlers:n,mutableDefaults:!!c})};let r=async function*(s,a){let n=aT(s,a,t.options);n.resolveBodyOnly=!1;let c=n.pagination;if(!sde.default.object(c))throw new TypeError(\"`options.pagination` must be implemented\");let f=[],{countLimit:p}=c,h=0;for(;h<c.requestLimit;){h!==0&&await udt(c.backoff);let E=await e(void 0,void 0,n),C=await c.transform(E),S=[];for(let I of C)if(c.filter(I,f,S)&&(!c.shouldContinue(I,f,S)||(yield I,c.stackAllItems&&f.push(I),S.push(I),--p<=0)))return;let P=c.paginate(E,f,S);if(P===!1)return;P===E.request.options?n=E.request.options:P!==void 0&&(n=aT(void 0,P,n)),h++}};e.paginate=r,e.paginate.all=async(s,a)=>{let n=[];for await(let c of r(s,a))n.push(c);return n},e.paginate.each=r,e.stream=(s,a)=>e(s,{...a,isStream:!0});for(let s of pdt)e[s]=(a,n)=>e(a,{...n,method:s}),e.stream[s]=(a,n)=>e(a,{...n,method:s,isStream:!0});return Object.assign(e,cdt),Object.defineProperty(e,\"defaults\",{value:t.mutableDefaults?t:ldt.default(t),writable:t.mutableDefaults,configurable:t.mutableDefaults,enumerable:!0}),e.mergeOptions=ode,e};Lc.default=lde;odt(ide(),Lc)});var fde=L((Mp,cT)=>{\"use strict\";var hdt=Mp&&Mp.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r),Object.defineProperty(t,s,{enumerable:!0,get:function(){return e[r]}})}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),cde=Mp&&Mp.__exportStar||function(t,e){for(var r in t)r!==\"default\"&&!Object.prototype.hasOwnProperty.call(e,r)&&hdt(e,t,r)};Object.defineProperty(Mp,\"__esModule\",{value:!0});var gdt=Ie(\"url\"),ude=Ej(),ddt={options:{method:\"GET\",retry:{limit:2,methods:[\"GET\",\"PUT\",\"HEAD\",\"DELETE\",\"OPTIONS\",\"TRACE\"],statusCodes:[408,413,429,500,502,503,504,521,522,524],errorCodes:[\"ETIMEDOUT\",\"ECONNRESET\",\"EADDRINUSE\",\"ECONNREFUSED\",\"EPIPE\",\"ENOTFOUND\",\"ENETUNREACH\",\"EAI_AGAIN\"],maxRetryAfter:void 0,calculateDelay:({computedValue:t})=>t},timeout:{},headers:{\"user-agent\":\"got (https://github.com/sindresorhus/got)\"},hooks:{init:[],beforeRequest:[],beforeRedirect:[],beforeRetry:[],beforeError:[],afterResponse:[]},cache:void 0,dnsCache:void 0,decompress:!0,throwHttpErrors:!0,followRedirect:!0,isStream:!1,responseType:\"text\",resolveBodyOnly:!1,maxRedirects:10,prefixUrl:\"\",methodRewriting:!0,ignoreInvalidCookies:!1,context:{},http2:!1,allowGetBody:!1,https:void 0,pagination:{transform:t=>t.request.options.responseType===\"json\"?t.body:JSON.parse(t.body),paginate:t=>{if(!Reflect.has(t.headers,\"link\"))return!1;let e=t.headers.link.split(\",\"),r;for(let s of e){let a=s.split(\";\");if(a[1].includes(\"next\")){r=a[0].trimStart().trim(),r=r.slice(1,-1);break}}return r?{url:new gdt.URL(r)}:!1},filter:()=>!0,shouldContinue:()=>!0,countLimit:1/0,backoff:0,requestLimit:1e4,stackAllItems:!0},parseJson:t=>JSON.parse(t),stringifyJson:t=>JSON.stringify(t),cacheOptions:{}},handlers:[ude.defaultHandler],mutableDefaults:!1},Ij=ude.default(ddt);Mp.default=Ij;cT.exports=Ij;cT.exports.default=Ij;cT.exports.__esModule=!0;cde(Ej(),Mp);cde(dj(),Mp)});var An={};Vt(An,{Method:()=>mde,del:()=>Cdt,get:()=>Bj,getNetworkSettings:()=>dde,post:()=>vj,put:()=>Idt,request:()=>Av});async function Cj(t){return Vl(pde,t,()=>le.readFilePromise(t).then(e=>(pde.set(t,e),e)))}function Edt({statusCode:t,statusMessage:e},r){let s=Ut(r,t,pt.NUMBER),a=`https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/${t}`;return KE(r,`${s}${e?` (${e})`:\"\"}`,a)}async function uT(t,{configuration:e,customErrorMessage:r}){try{return await t}catch(s){if(s.name!==\"HTTPError\")throw s;let a=r?.(s,e)??s.response.body?.error;a==null&&(s.message.startsWith(\"Response code\")?a=\"The remote server failed to provide the requested resource\":a=s.message),s.code===\"ETIMEDOUT\"&&s.event===\"socket\"&&(a+=`(can be increased via ${Ut(e,\"httpTimeout\",pt.SETTING)})`);let n=new Yt(35,a,c=>{s.response&&c.reportError(35,`  ${Zf(e,{label:\"Response Code\",value:Hu(pt.NO_HINT,Edt(s.response,e))})}`),s.request&&(c.reportError(35,`  ${Zf(e,{label:\"Request Method\",value:Hu(pt.NO_HINT,s.request.options.method)})}`),c.reportError(35,`  ${Zf(e,{label:\"Request URL\",value:Hu(pt.URL,s.request.requestUrl)})}`)),s.request.redirects.length>0&&c.reportError(35,`  ${Zf(e,{label:\"Request Redirects\",value:Hu(pt.NO_HINT,f3(e,s.request.redirects,pt.URL))})}`),s.request.retryCount===s.request.options.retry.limit&&c.reportError(35,`  ${Zf(e,{label:\"Request Retry Count\",value:Hu(pt.NO_HINT,`${Ut(e,s.request.retryCount,pt.NUMBER)} (can be increased via ${Ut(e,\"httpRetry\",pt.SETTING)})`)})}`)});throw n.originalError=s,n}}function dde(t,e){let r=[...e.configuration.get(\"networkSettings\")].sort(([c],[f])=>f.length-c.length),s={enableNetwork:void 0,httpsCaFilePath:void 0,httpProxy:void 0,httpsProxy:void 0,httpsKeyFilePath:void 0,httpsCertFilePath:void 0},a=Object.keys(s),n=typeof t==\"string\"?new URL(t):t;for(let[c,f]of r)if(wj.default.isMatch(n.hostname,c))for(let p of a){let h=f.get(p);h!==null&&typeof s[p]>\"u\"&&(s[p]=h)}for(let c of a)typeof s[c]>\"u\"&&(s[c]=e.configuration.get(c));return s}async function Av(t,e,{configuration:r,headers:s,jsonRequest:a,jsonResponse:n,method:c=\"GET\",wrapNetworkRequest:f}){let p={target:t,body:e,configuration:r,headers:s,jsonRequest:a,jsonResponse:n,method:c},h=async()=>await wdt(t,e,p),E=typeof f<\"u\"?await f(h,p):h;return await(await r.reduceHook(S=>S.wrapNetworkRequest,E,p))()}async function Bj(t,{configuration:e,jsonResponse:r,customErrorMessage:s,wrapNetworkRequest:a,...n}){let c=()=>uT(Av(t,null,{configuration:e,wrapNetworkRequest:a,...n}),{configuration:e,customErrorMessage:s}).then(p=>p.body),f=await(typeof a<\"u\"?c():Vl(Ade,t,()=>c().then(p=>(Ade.set(t,p),p))));return r?JSON.parse(f.toString()):f}async function Idt(t,e,{customErrorMessage:r,...s}){return(await uT(Av(t,e,{...s,method:\"PUT\"}),{customErrorMessage:r,configuration:s.configuration})).body}async function vj(t,e,{customErrorMessage:r,...s}){return(await uT(Av(t,e,{...s,method:\"POST\"}),{customErrorMessage:r,configuration:s.configuration})).body}async function Cdt(t,{customErrorMessage:e,...r}){return(await uT(Av(t,null,{...r,method:\"DELETE\"}),{customErrorMessage:e,configuration:r.configuration})).body}async function wdt(t,e,{configuration:r,headers:s,jsonRequest:a,jsonResponse:n,method:c=\"GET\"}){let f=typeof t==\"string\"?new URL(t):t,p=dde(f,{configuration:r});if(p.enableNetwork===!1)throw new Yt(80,`Request to '${f.href}' has been blocked because of your configuration settings`);if(f.protocol===\"http:\"&&!wj.default.isMatch(f.hostname,r.get(\"unsafeHttpWhitelist\")))throw new Yt(81,`Unsafe http requests must be explicitly whitelisted in your configuration (${f.hostname})`);let h={headers:s,method:c};h.responseType=n?\"json\":\"buffer\",e!==null&&(Buffer.isBuffer(e)||!a&&typeof e==\"string\"?h.body=e:h.json=e);let E=r.get(\"httpTimeout\"),C=r.get(\"httpRetry\"),S=r.get(\"enableStrictSsl\"),P=p.httpsCaFilePath,I=p.httpsCertFilePath,R=p.httpsKeyFilePath,{default:N}=await Promise.resolve().then(()=>et(fde())),U=P?await Cj(P):void 0,W=I?await Cj(I):void 0,te=R?await Cj(R):void 0,ie={rejectUnauthorized:S,ca:U,cert:W,key:te},Ae={http:p.httpProxy?new Uhe({proxy:p.httpProxy,proxyRequestOptions:ie}):mdt,https:p.httpsProxy?new Hhe({proxy:p.httpsProxy,proxyRequestOptions:ie}):ydt},ce=N.extend({timeout:{socket:E},retry:C,agent:Ae,https:{rejectUnauthorized:S,certificateAuthority:U,certificate:W,key:te},...h});return r.getLimit(\"networkConcurrency\")(()=>ce(f))}var hde,gde,wj,Ade,pde,mdt,ydt,mde,fT=Ct(()=>{bt();jhe();hde=Ie(\"https\"),gde=Ie(\"http\"),wj=et(Sa());Fc();Qc();kc();Ade=new Map,pde=new Map,mdt=new gde.Agent({keepAlive:!0}),ydt=new hde.Agent({keepAlive:!0});mde=(a=>(a.GET=\"GET\",a.PUT=\"PUT\",a.POST=\"POST\",a.DELETE=\"DELETE\",a))(mde||{})});var ps={};Vt(ps,{availableParallelism:()=>Dj,getArchitecture:()=>pv,getArchitectureName:()=>bdt,getArchitectureSet:()=>Sj,getCaller:()=>Qdt,major:()=>Bdt,openUrl:()=>vdt});function Ddt(){if(process.platform===\"darwin\"||process.platform===\"win32\")return null;let t;try{t=le.readFileSync(Sdt)}catch{}if(typeof t<\"u\"){if(t&&(t.includes(\"GLIBC\")||t.includes(\"libc\")))return\"glibc\";if(t&&t.includes(\"musl\"))return\"musl\"}let r=(process.report?.getReport()??{}).sharedObjects??[],s=/\\/(?:(ld-linux-|[^/]+-linux-gnu\\/)|(libc.musl-|ld-musl-))/;return A0(r,a=>{let n=a.match(s);if(!n)return A0.skip;if(n[1])return\"glibc\";if(n[2])return\"musl\";throw new Error(\"Assertion failed: Expected the libc variant to have been detected\")})??null}function pv(){return Ede=Ede??{os:process.platform,cpu:process.arch,libc:Ddt()}}function bdt(t=pv()){return t.libc?`${t.os}-${t.cpu}-${t.libc}`:`${t.os}-${t.cpu}`}function Sj(){let t=pv();return Ide=Ide??{os:[t.os],cpu:[t.cpu],libc:t.libc?[t.libc]:[]}}function kdt(t){let e=Pdt.exec(t);if(!e)return null;let r=e[2]&&e[2].indexOf(\"native\")===0,s=e[2]&&e[2].indexOf(\"eval\")===0,a=xdt.exec(e[2]);return s&&a!=null&&(e[2]=a[1],e[3]=a[2],e[4]=a[3]),{file:r?null:e[2],methodName:e[1]||\"<unknown>\",arguments:r?[e[2]]:[],line:e[3]?+e[3]:null,column:e[4]?+e[4]:null}}function Qdt(){let e=new Error().stack.split(`\n`)[3];return kdt(e)}function Dj(){return typeof AT.default.availableParallelism<\"u\"?AT.default.availableParallelism():Math.max(1,AT.default.cpus().length)}var AT,Bdt,yde,vdt,Sdt,Ede,Ide,Pdt,xdt,pT=Ct(()=>{bt();AT=et(Ie(\"os\"));hT();kc();Bdt=Number(process.versions.node.split(\".\")[0]),yde=new Map([[\"darwin\",\"open\"],[\"linux\",\"xdg-open\"],[\"win32\",\"explorer.exe\"]]).get(process.platform),vdt=typeof yde<\"u\"?async t=>{try{return await bj(yde,[t],{cwd:K.cwd()}),!0}catch{return!1}}:void 0,Sdt=\"/usr/bin/ldd\";Pdt=/^\\s*at (.*?) ?\\(((?:file|https?|blob|chrome-extension|native|eval|webpack|<anonymous>|\\/|[a-z]:\\\\|\\\\\\\\).*?)(?::(\\d+))?(?::(\\d+))?\\)?\\s*$/i,xdt=/\\((\\S*)(?::(\\d+))(?::(\\d+))\\)/});function Tj(t,e,r,s,a){let n=ev(r);if(s.isArray||s.type===\"ANY\"&&Array.isArray(n))return Array.isArray(n)?n.map((c,f)=>Pj(t,`${e}[${f}]`,c,s,a)):String(n).split(/,/).map(c=>Pj(t,e,c,s,a));if(Array.isArray(n))throw new Error(`Non-array configuration settings \"${e}\" cannot be an array`);return Pj(t,e,r,s,a)}function Pj(t,e,r,s,a){let n=ev(r);switch(s.type){case\"ANY\":return RQ(n);case\"SHAPE\":return Ndt(t,e,r,s,a);case\"MAP\":return Odt(t,e,r,s,a)}if(n===null&&!s.isNullable&&s.default!==null)throw new Error(`Non-nullable configuration settings \"${e}\" cannot be set to null`);if(s.values?.includes(n))return n;let f=(()=>{if(s.type===\"BOOLEAN\"&&typeof n!=\"string\")return MB(n);if(typeof n!=\"string\")throw new Error(`Expected configuration setting \"${e}\" to be a string, got ${typeof n}`);let p=Yk(n,{env:t.env});switch(s.type){case\"ABSOLUTE_PATH\":{let h=a,E=rH(r);return E&&E[0]!==\"<\"&&(h=K.dirname(E)),K.resolve(h,ue.toPortablePath(p))}case\"LOCATOR_LOOSE\":return Rp(p,!1);case\"NUMBER\":return parseInt(p);case\"LOCATOR\":return Rp(p);case\"BOOLEAN\":return MB(p);default:return p}})();if(s.values&&!s.values.includes(f))throw new Error(`Invalid value, expected one of ${s.values.join(\", \")}`);return f}function Ndt(t,e,r,s,a){let n=ev(r);if(typeof n!=\"object\"||Array.isArray(n))throw new nt(`Object configuration settings \"${e}\" must be an object`);let c=Rj(t,s,{ignoreArrays:!0});if(n===null)return c;for(let[f,p]of Object.entries(n)){let h=`${e}.${f}`;if(!s.properties[f])throw new nt(`Unrecognized configuration settings found: ${e}.${f} - run \"yarn config -v\" to see the list of settings supported in Yarn`);c.set(f,Tj(t,h,p,s.properties[f],a))}return c}function Odt(t,e,r,s,a){let n=ev(r),c=new Map;if(typeof n!=\"object\"||Array.isArray(n))throw new nt(`Map configuration settings \"${e}\" must be an object`);if(n===null)return c;for(let[f,p]of Object.entries(n)){let h=s.normalizeKeys?s.normalizeKeys(f):f,E=`${e}['${h}']`,C=s.valueDefinition;c.set(h,Tj(t,E,p,C,a))}return c}function Rj(t,e,{ignoreArrays:r=!1}={}){switch(e.type){case\"SHAPE\":{if(e.isArray&&!r)return[];let s=new Map;for(let[a,n]of Object.entries(e.properties))s.set(a,Rj(t,n));return s}case\"MAP\":return e.isArray&&!r?[]:new Map;case\"ABSOLUTE_PATH\":return e.default===null?null:t.projectCwd===null?Array.isArray(e.default)?e.default.map(s=>K.normalize(s)):K.isAbsolute(e.default)?K.normalize(e.default):e.isNullable?null:void 0:Array.isArray(e.default)?e.default.map(s=>K.resolve(t.projectCwd,s)):K.resolve(t.projectCwd,e.default);default:return e.default}}function dT(t,e,r){if(e.type===\"SECRET\"&&typeof t==\"string\"&&r.hideSecrets)return Fdt;if(e.type===\"ABSOLUTE_PATH\"&&typeof t==\"string\"&&r.getNativePaths)return ue.fromPortablePath(t);if(e.isArray&&Array.isArray(t)){let s=[];for(let a of t)s.push(dT(a,e,r));return s}if(e.type===\"MAP\"&&t instanceof Map){if(t.size===0)return;let s=new Map;for(let[a,n]of t.entries()){let c=dT(n,e.valueDefinition,r);typeof c<\"u\"&&s.set(a,c)}return s}if(e.type===\"SHAPE\"&&t instanceof Map){if(t.size===0)return;let s=new Map;for(let[a,n]of t.entries()){let c=e.properties[a],f=dT(n,c,r);typeof f<\"u\"&&s.set(a,f)}return s}return t}function Ldt(){let t={};for(let[e,r]of Object.entries(process.env))e=e.toLowerCase(),e.startsWith(mT)&&(e=(0,wde.default)(e.slice(mT.length)),t[e]=r);return t}function kj(){let t=`${mT}rc_filename`;for(let[e,r]of Object.entries(process.env))if(e.toLowerCase()===t&&typeof r==\"string\")return r;return Qj}async function Cde(t){try{return await le.readFilePromise(t)}catch{return Buffer.of()}}async function Mdt(t,e){return Buffer.compare(...await Promise.all([Cde(t),Cde(e)]))===0}async function _dt(t,e){let[r,s]=await Promise.all([le.statPromise(t),le.statPromise(e)]);return r.dev===s.dev&&r.ino===s.ino}async function Hdt({configuration:t,selfPath:e}){let r=t.get(\"yarnPath\");return t.get(\"ignorePath\")||r===null||r===e||await Udt(r,e)?null:r}var wde,_p,Bde,vde,Sde,xj,Tdt,hv,Rdt,Up,mT,Qj,Fdt,gv,Dde,yT,gT,Udt,ze,dv=Ct(()=>{bt();Bc();wde=et(zre()),_p=et(Rd());Wt();Bde=et(qne()),vde=Ie(\"module\"),Sde=et(Od()),xj=Ie(\"stream\");nhe();sI();K8();J8();z8();Qhe();Z8();$d();Ohe();NQ();Qc();E0();fT();kc();pT();Np();Yo();Tdt=function(){if(!_p.GITHUB_ACTIONS||!process.env.GITHUB_EVENT_PATH)return!1;let t=ue.toPortablePath(process.env.GITHUB_EVENT_PATH),e;try{e=le.readJsonSync(t)}catch{return!1}return!(!(\"repository\"in e)||!e.repository||(e.repository.private??!0))}(),hv=new Set([\"@yarnpkg/plugin-constraints\",\"@yarnpkg/plugin-exec\",\"@yarnpkg/plugin-interactive-tools\",\"@yarnpkg/plugin-stage\",\"@yarnpkg/plugin-typescript\",\"@yarnpkg/plugin-version\",\"@yarnpkg/plugin-workspace-tools\"]),Rdt=new Set([\"isTestEnv\",\"injectNpmUser\",\"injectNpmPassword\",\"injectNpm2FaToken\",\"zipDataEpilogue\",\"cacheCheckpointOverride\",\"cacheVersionOverride\",\"lockfileVersionOverride\",\"binFolder\",\"version\",\"flags\",\"profile\",\"gpg\",\"ignoreNode\",\"wrapOutput\",\"home\",\"confDir\",\"registry\",\"ignoreCwd\"]),Up=/^(?!v)[a-z0-9._-]+$/i,mT=\"yarn_\",Qj=\".yarnrc.yml\",Fdt=\"********\",gv=(E=>(E.ANY=\"ANY\",E.BOOLEAN=\"BOOLEAN\",E.ABSOLUTE_PATH=\"ABSOLUTE_PATH\",E.LOCATOR=\"LOCATOR\",E.LOCATOR_LOOSE=\"LOCATOR_LOOSE\",E.NUMBER=\"NUMBER\",E.STRING=\"STRING\",E.SECRET=\"SECRET\",E.SHAPE=\"SHAPE\",E.MAP=\"MAP\",E))(gv||{}),Dde=pt,yT=(r=>(r.JUNCTIONS=\"junctions\",r.SYMLINKS=\"symlinks\",r))(yT||{}),gT={lastUpdateCheck:{description:\"Last timestamp we checked whether new Yarn versions were available\",type:\"STRING\",default:null},yarnPath:{description:\"Path to the local executable that must be used over the global one\",type:\"ABSOLUTE_PATH\",default:null},ignorePath:{description:\"If true, the local executable will be ignored when using the global one\",type:\"BOOLEAN\",default:!1},globalFolder:{description:\"Folder where all system-global files are stored\",type:\"ABSOLUTE_PATH\",default:iH()},cacheFolder:{description:\"Folder where the cache files must be written\",type:\"ABSOLUTE_PATH\",default:\"./.yarn/cache\"},compressionLevel:{description:\"Zip files compression level, from 0 to 9 or mixed (a variant of 9, which stores some files uncompressed, when compression doesn't yield good results)\",type:\"NUMBER\",values:[\"mixed\",0,1,2,3,4,5,6,7,8,9],default:0},virtualFolder:{description:\"Folder where the virtual packages (cf doc) will be mapped on the disk (must be named __virtual__)\",type:\"ABSOLUTE_PATH\",default:\"./.yarn/__virtual__\"},installStatePath:{description:\"Path of the file where the install state will be persisted\",type:\"ABSOLUTE_PATH\",default:\"./.yarn/install-state.gz\"},immutablePatterns:{description:\"Array of glob patterns; files matching them won't be allowed to change during immutable installs\",type:\"STRING\",default:[],isArray:!0},rcFilename:{description:\"Name of the files where the configuration can be found\",type:\"STRING\",default:kj()},enableGlobalCache:{description:\"If true, the system-wide cache folder will be used regardless of `cache-folder`\",type:\"BOOLEAN\",default:!0},cacheMigrationMode:{description:\"Defines the conditions under which Yarn upgrades should cause the cache archives to be regenerated.\",type:\"STRING\",values:[\"always\",\"match-spec\",\"required-only\"],default:\"always\"},enableColors:{description:\"If true, the CLI is allowed to use colors in its output\",type:\"BOOLEAN\",default:zk,defaultText:\"<dynamic>\"},enableHyperlinks:{description:\"If true, the CLI is allowed to use hyperlinks in its output\",type:\"BOOLEAN\",default:u3,defaultText:\"<dynamic>\"},enableInlineBuilds:{description:\"If true, the CLI will print the build output on the command line\",type:\"BOOLEAN\",default:_p.isCI,defaultText:\"<dynamic>\"},enableMessageNames:{description:\"If true, the CLI will prefix most messages with codes suitable for search engines\",type:\"BOOLEAN\",default:!0},enableProgressBars:{description:\"If true, the CLI is allowed to show a progress bar for long-running events\",type:\"BOOLEAN\",default:!_p.isCI,defaultText:\"<dynamic>\"},enableTimers:{description:\"If true, the CLI is allowed to print the time spent executing commands\",type:\"BOOLEAN\",default:!0},enableTips:{description:\"If true, installs will print a helpful message every day of the week\",type:\"BOOLEAN\",default:!_p.isCI,defaultText:\"<dynamic>\"},preferInteractive:{description:\"If true, the CLI will automatically use the interactive mode when called from a TTY\",type:\"BOOLEAN\",default:!1},preferTruncatedLines:{description:\"If true, the CLI will truncate lines that would go beyond the size of the terminal\",type:\"BOOLEAN\",default:!1},progressBarStyle:{description:\"Which style of progress bar should be used (only when progress bars are enabled)\",type:\"STRING\",default:void 0,defaultText:\"<dynamic>\"},defaultLanguageName:{description:\"Default language mode that should be used when a package doesn't offer any insight\",type:\"STRING\",default:\"node\"},defaultProtocol:{description:\"Default resolution protocol used when resolving pure semver and tag ranges\",type:\"STRING\",default:\"npm:\"},enableTransparentWorkspaces:{description:\"If false, Yarn won't automatically resolve workspace dependencies unless they use the `workspace:` protocol\",type:\"BOOLEAN\",default:!0},supportedArchitectures:{description:\"Architectures that Yarn will fetch and inject into the resolver\",type:\"SHAPE\",properties:{os:{description:\"Array of supported process.platform strings, or null to target them all\",type:\"STRING\",isArray:!0,isNullable:!0,default:[\"current\"]},cpu:{description:\"Array of supported process.arch strings, or null to target them all\",type:\"STRING\",isArray:!0,isNullable:!0,default:[\"current\"]},libc:{description:\"Array of supported libc libraries, or null to target them all\",type:\"STRING\",isArray:!0,isNullable:!0,default:[\"current\"]}}},enableMirror:{description:\"If true, the downloaded packages will be retrieved and stored in both the local and global folders\",type:\"BOOLEAN\",default:!0},enableNetwork:{description:\"If false, Yarn will refuse to use the network if required to\",type:\"BOOLEAN\",default:!0},enableOfflineMode:{description:\"If true, Yarn will attempt to retrieve files and metadata from the global cache rather than the network\",type:\"BOOLEAN\",default:!1},httpProxy:{description:\"URL of the http proxy that must be used for outgoing http requests\",type:\"STRING\",default:null},httpsProxy:{description:\"URL of the http proxy that must be used for outgoing https requests\",type:\"STRING\",default:null},unsafeHttpWhitelist:{description:\"List of the hostnames for which http queries are allowed (glob patterns are supported)\",type:\"STRING\",default:[],isArray:!0},httpTimeout:{description:\"Timeout of each http request in milliseconds\",type:\"NUMBER\",default:6e4},httpRetry:{description:\"Retry times on http failure\",type:\"NUMBER\",default:3},networkConcurrency:{description:\"Maximal number of concurrent requests\",type:\"NUMBER\",default:50},taskPoolConcurrency:{description:\"Maximal amount of concurrent heavy task processing\",type:\"NUMBER\",default:Dj()},taskPoolMode:{description:\"Execution strategy for heavy tasks\",type:\"STRING\",values:[\"async\",\"workers\"],default:\"workers\"},networkSettings:{description:\"Network settings per hostname (glob patterns are supported)\",type:\"MAP\",valueDefinition:{description:\"\",type:\"SHAPE\",properties:{httpsCaFilePath:{description:\"Path to file containing one or multiple Certificate Authority signing certificates\",type:\"ABSOLUTE_PATH\",default:null},enableNetwork:{description:\"If false, the package manager will refuse to use the network if required to\",type:\"BOOLEAN\",default:null},httpProxy:{description:\"URL of the http proxy that must be used for outgoing http requests\",type:\"STRING\",default:null},httpsProxy:{description:\"URL of the http proxy that must be used for outgoing https requests\",type:\"STRING\",default:null},httpsKeyFilePath:{description:\"Path to file containing private key in PEM format\",type:\"ABSOLUTE_PATH\",default:null},httpsCertFilePath:{description:\"Path to file containing certificate chain in PEM format\",type:\"ABSOLUTE_PATH\",default:null}}}},httpsCaFilePath:{description:\"A path to a file containing one or multiple Certificate Authority signing certificates\",type:\"ABSOLUTE_PATH\",default:null},httpsKeyFilePath:{description:\"Path to file containing private key in PEM format\",type:\"ABSOLUTE_PATH\",default:null},httpsCertFilePath:{description:\"Path to file containing certificate chain in PEM format\",type:\"ABSOLUTE_PATH\",default:null},enableStrictSsl:{description:\"If false, SSL certificate errors will be ignored\",type:\"BOOLEAN\",default:!0},logFilters:{description:\"Overrides for log levels\",type:\"SHAPE\",isArray:!0,concatenateValues:!0,properties:{code:{description:\"Code of the messages covered by this override\",type:\"STRING\",default:void 0},text:{description:\"Code of the texts covered by this override\",type:\"STRING\",default:void 0},pattern:{description:\"Code of the patterns covered by this override\",type:\"STRING\",default:void 0},level:{description:\"Log level override, set to null to remove override\",type:\"STRING\",values:Object.values(Xk),isNullable:!0,default:void 0}}},enableTelemetry:{description:\"If true, telemetry will be periodically sent, following the rules in https://yarnpkg.com/advanced/telemetry\",type:\"BOOLEAN\",default:!0},telemetryInterval:{description:\"Minimal amount of time between two telemetry uploads, in days\",type:\"NUMBER\",default:7},telemetryUserId:{description:\"If you desire to tell us which project you are, you can set this field. Completely optional and opt-in.\",type:\"STRING\",default:null},enableHardenedMode:{description:\"If true, automatically enable --check-resolutions --refresh-lockfile on installs\",type:\"BOOLEAN\",default:_p.isPR&&Tdt,defaultText:\"<true on public PRs>\"},enableScripts:{description:\"If true, packages are allowed to have install scripts by default\",type:\"BOOLEAN\",default:!0},enableStrictSettings:{description:\"If true, unknown settings will cause Yarn to abort\",type:\"BOOLEAN\",default:!0},enableImmutableCache:{description:\"If true, the cache is reputed immutable and actions that would modify it will throw\",type:\"BOOLEAN\",default:!1},enableCacheClean:{description:\"If false, disallows the `cache clean` command\",type:\"BOOLEAN\",default:!0},checksumBehavior:{description:\"Enumeration defining what to do when a checksum doesn't match expectations\",type:\"STRING\",default:\"throw\"},injectEnvironmentFiles:{description:\"List of all the environment files that Yarn should inject inside the process when it starts\",type:\"ABSOLUTE_PATH\",default:[\".env.yarn?\"],isArray:!0},packageExtensions:{description:\"Map of package corrections to apply on the dependency tree\",type:\"MAP\",valueDefinition:{description:\"The extension that will be applied to any package whose version matches the specified range\",type:\"SHAPE\",properties:{dependencies:{description:\"The set of dependencies that must be made available to the current package in order for it to work properly\",type:\"MAP\",valueDefinition:{description:\"A range\",type:\"STRING\"}},peerDependencies:{description:\"Inherited dependencies - the consumer of the package will be tasked to provide them\",type:\"MAP\",valueDefinition:{description:\"A semver range\",type:\"STRING\"}},peerDependenciesMeta:{description:\"Extra information related to the dependencies listed in the peerDependencies field\",type:\"MAP\",valueDefinition:{description:\"The peerDependency meta\",type:\"SHAPE\",properties:{optional:{description:\"If true, the selected peer dependency will be marked as optional by the package manager and the consumer omitting it won't be reported as an error\",type:\"BOOLEAN\",default:!1}}}}}}}};Udt=process.platform===\"win32\"?Mdt:_dt;ze=class t{constructor(e){this.isCI=_p.isCI;this.projectCwd=null;this.plugins=new Map;this.settings=new Map;this.values=new Map;this.sources=new Map;this.invalid=new Map;this.env={};this.limits=new Map;this.packageExtensions=null;this.startingCwd=e}static{this.deleteProperty=Symbol()}static{this.telemetry=null}static create(e,r,s){let a=new t(e);typeof r<\"u\"&&!(r instanceof Map)&&(a.projectCwd=r),a.importSettings(gT);let n=typeof s<\"u\"?s:r instanceof Map?r:new Map;for(let[c,f]of n)a.activatePlugin(c,f);return a}static async find(e,r,{strict:s=!0,usePathCheck:a=null,useRc:n=!0}={}){let c=Ldt();delete c.rcFilename;let f=new t(e),p=await t.findRcFiles(e),h=await t.findFolderRcFile(fI());h&&(p.find(me=>me.path===h.path)||p.unshift(h));let E=Nhe(p.map(ce=>[ce.path,ce.data])),C=vt.dot,S=new Set(Object.keys(gT)),P=({yarnPath:ce,ignorePath:me,injectEnvironmentFiles:pe})=>({yarnPath:ce,ignorePath:me,injectEnvironmentFiles:pe}),I=({yarnPath:ce,ignorePath:me,injectEnvironmentFiles:pe,...Be})=>{let Ce={};for(let[g,we]of Object.entries(Be))S.has(g)&&(Ce[g]=we);return Ce},R=({yarnPath:ce,ignorePath:me,...pe})=>{let Be={};for(let[Ce,g]of Object.entries(pe))S.has(Ce)||(Be[Ce]=g);return Be};if(f.importSettings(P(gT)),f.useWithSource(\"<environment>\",P(c),e,{strict:!1}),E){let[ce,me]=E;f.useWithSource(ce,P(me),C,{strict:!1})}if(a){if(await Hdt({configuration:f,selfPath:a})!==null)return f;f.useWithSource(\"<override>\",{ignorePath:!0},e,{strict:!1,overwrite:!0})}let N=await t.findProjectCwd(e);f.startingCwd=e,f.projectCwd=N;let U=Object.assign(Object.create(null),process.env);f.env=U;let W=await Promise.all(f.get(\"injectEnvironmentFiles\").map(async ce=>{let me=ce.endsWith(\"?\")?await le.readFilePromise(ce.slice(0,-1),\"utf8\").catch(()=>\"\"):await le.readFilePromise(ce,\"utf8\");return(0,Bde.parse)(me)}));for(let ce of W)for(let[me,pe]of Object.entries(ce))f.env[me]=Yk(pe,{env:U});if(f.importSettings(I(gT)),f.useWithSource(\"<environment>\",I(c),e,{strict:s}),E){let[ce,me]=E;f.useWithSource(ce,I(me),C,{strict:s})}let te=ce=>\"default\"in ce?ce.default:ce,ie=new Map([[\"@@core\",rhe]]);if(r!==null)for(let ce of r.plugins.keys())ie.set(ce,te(r.modules.get(ce)));for(let[ce,me]of ie)f.activatePlugin(ce,me);let Ae=new Map([]);if(r!==null){let ce=new Map;for(let[Be,Ce]of r.modules)ce.set(Be,()=>Ce);let me=new Set,pe=async(Be,Ce)=>{let{factory:g,name:we}=kp(Be);if(!g||me.has(we))return;let ye=new Map(ce),fe=X=>{if((0,vde.isBuiltin)(X))return kp(X);if(ye.has(X))return ye.get(X)();throw new nt(`This plugin cannot access the package referenced via ${X} which is neither a builtin, nor an exposed entry`)},se=await qE(async()=>te(await g(fe)),X=>`${X} (when initializing ${we}, defined in ${Ce})`);ce.set(we,()=>se),me.add(we),Ae.set(we,se)};if(c.plugins)for(let Be of c.plugins.split(\";\")){let Ce=K.resolve(e,ue.toPortablePath(Be));await pe(Ce,\"<environment>\")}for(let{path:Be,cwd:Ce,data:g}of p)if(n&&Array.isArray(g.plugins))for(let we of g.plugins){let ye=typeof we!=\"string\"?we.path:we,fe=we?.spec??\"\",se=we?.checksum??\"\";if(hv.has(fe))continue;let X=K.resolve(Ce,ue.toPortablePath(ye));if(!await le.existsPromise(X)){if(!fe){let dt=Ut(f,K.basename(X,\".cjs\"),pt.NAME),j=Ut(f,\".gitignore\",pt.NAME),rt=Ut(f,f.values.get(\"rcFilename\"),pt.NAME),Fe=Ut(f,\"https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored\",pt.URL);throw new nt(`Missing source for the ${dt} plugin - please try to remove the plugin from ${rt} then reinstall it manually. This error usually occurs because ${j} is incorrect, check ${Fe} to make sure your plugin folder isn't gitignored.`)}if(!fe.match(/^https?:/)){let dt=Ut(f,K.basename(X,\".cjs\"),pt.NAME),j=Ut(f,f.values.get(\"rcFilename\"),pt.NAME);throw new nt(`Failed to recognize the source for the ${dt} plugin - please try to delete the plugin from ${j} then reinstall it manually.`)}let De=await Bj(fe,{configuration:f}),Re=fs(De);if(se&&se!==Re){let dt=Ut(f,K.basename(X,\".cjs\"),pt.NAME),j=Ut(f,f.values.get(\"rcFilename\"),pt.NAME),rt=Ut(f,`yarn plugin import ${fe}`,pt.CODE);throw new nt(`Failed to fetch the ${dt} plugin from its remote location: its checksum seems to have changed. If this is expected, please remove the plugin from ${j} then run ${rt} to reimport it.`)}await le.mkdirPromise(K.dirname(X),{recursive:!0}),await le.writeFilePromise(X,De)}await pe(X,Be)}}for(let[ce,me]of Ae)f.activatePlugin(ce,me);if(f.useWithSource(\"<environment>\",R(c),e,{strict:s}),E){let[ce,me]=E;f.useWithSource(ce,R(me),C,{strict:s})}return f.get(\"enableGlobalCache\")&&(f.values.set(\"cacheFolder\",`${f.get(\"globalFolder\")}/cache`),f.sources.set(\"cacheFolder\",\"<internal>\")),f}static async findRcFiles(e){let r=kj(),s=[],a=e,n=null;for(;a!==n;){n=a;let c=K.join(n,r);if(le.existsSync(c)){let f,p;try{p=await le.readFilePromise(c,\"utf8\"),f=cs(p)}catch{let h=\"\";throw p?.match(/^\\s+(?!-)[^:]+\\s+\\S+/m)&&(h=\" (in particular, make sure you list the colons after each key name)\"),new nt(`Parse error when loading ${c}; please check it's proper Yaml${h}`)}s.unshift({path:c,cwd:n,data:f})}a=K.dirname(n)}return s}static async findFolderRcFile(e){let r=K.join(e,Er.rc),s;try{s=await le.readFilePromise(r,\"utf8\")}catch(n){if(n.code===\"ENOENT\")return null;throw n}let a=cs(s);return{path:r,cwd:e,data:a}}static async findProjectCwd(e){let r=null,s=e,a=null;for(;s!==a;){if(a=s,le.existsSync(K.join(a,Er.lockfile)))return a;le.existsSync(K.join(a,Er.manifest))&&(r=a),s=K.dirname(a)}return r}static async updateConfiguration(e,r,s={}){let a=kj(),n=K.join(e,a),c=le.existsSync(n)?cs(await le.readFilePromise(n,\"utf8\")):{},f=!1,p;if(typeof r==\"function\"){try{p=r(c)}catch{p=r({})}if(p===c)return!1}else{p=c;for(let h of Object.keys(r)){let E=c[h],C=r[h],S;if(typeof C==\"function\")try{S=C(E)}catch{S=C(void 0)}else S=C;E!==S&&(S===t.deleteProperty?delete p[h]:p[h]=S,f=!0)}if(!f)return!1}return await le.changeFilePromise(n,il(p),{automaticNewlines:!0}),!0}static async addPlugin(e,r){r.length!==0&&await t.updateConfiguration(e,s=>{let a=s.plugins??[];if(a.length===0)return{...s,plugins:r};let n=[],c=[...r];for(let f of a){let p=typeof f!=\"string\"?f.path:f,h=c.find(E=>E.path===p);h?(n.push(h),c=c.filter(E=>E!==h)):n.push(f)}return n.push(...c),{...s,plugins:n}})}static async updateHomeConfiguration(e){let r=fI();return await t.updateConfiguration(r,e)}activatePlugin(e,r){this.plugins.set(e,r),typeof r.configuration<\"u\"&&this.importSettings(r.configuration)}importSettings(e){for(let[r,s]of Object.entries(e))if(s!=null){if(this.settings.has(r))throw new Error(`Cannot redefine settings \"${r}\"`);this.settings.set(r,s),this.values.set(r,Rj(this,s))}}useWithSource(e,r,s,a){try{this.use(e,r,s,a)}catch(n){throw n.message+=` (in ${Ut(this,e,pt.PATH)})`,n}}use(e,r,s,{strict:a=!0,overwrite:n=!1}={}){a=a&&this.get(\"enableStrictSettings\");for(let c of[\"enableStrictSettings\",...Object.keys(r)]){let f=r[c],p=rH(f);if(p&&(e=p),typeof f>\"u\"||c===\"plugins\"||e===\"<environment>\"&&Rdt.has(c))continue;if(c===\"rcFilename\")throw new nt(`The rcFilename settings can only be set via ${`${mT}RC_FILENAME`.toUpperCase()}, not via a rc file`);let h=this.settings.get(c);if(!h){let C=fI(),S=e[0]!==\"<\"?K.dirname(e):null;if(a&&!(S!==null?C===S:!1))throw new nt(`Unrecognized or legacy configuration settings found: ${c} - run \"yarn config -v\" to see the list of settings supported in Yarn`);this.invalid.set(c,e);continue}if(this.sources.has(c)&&!(n||h.type===\"MAP\"||h.isArray&&h.concatenateValues))continue;let E;try{E=Tj(this,c,f,h,s)}catch(C){throw C.message+=` in ${Ut(this,e,pt.PATH)}`,C}if(c===\"enableStrictSettings\"&&e!==\"<environment>\"){a=E;continue}if(h.type===\"MAP\"){let C=this.values.get(c);this.values.set(c,new Map(n?[...C,...E]:[...E,...C])),this.sources.set(c,`${this.sources.get(c)}, ${e}`)}else if(h.isArray&&h.concatenateValues){let C=this.values.get(c);this.values.set(c,n?[...C,...E]:[...E,...C]),this.sources.set(c,`${this.sources.get(c)}, ${e}`)}else this.values.set(c,E),this.sources.set(c,e)}}get(e){if(!this.values.has(e))throw new Error(`Invalid configuration key \"${e}\"`);return this.values.get(e)}getSpecial(e,{hideSecrets:r=!1,getNativePaths:s=!1}){let a=this.get(e),n=this.settings.get(e);if(typeof n>\"u\")throw new nt(`Couldn't find a configuration settings named \"${e}\"`);return dT(a,n,{hideSecrets:r,getNativePaths:s})}getSubprocessStreams(e,{header:r,prefix:s,report:a}){let n,c,f=le.createWriteStream(e);if(this.get(\"enableInlineBuilds\")){let p=a.createStreamReporter(`${s} ${Ut(this,\"STDOUT\",\"green\")}`),h=a.createStreamReporter(`${s} ${Ut(this,\"STDERR\",\"red\")}`);n=new xj.PassThrough,n.pipe(p),n.pipe(f),c=new xj.PassThrough,c.pipe(h),c.pipe(f)}else n=f,c=f,typeof r<\"u\"&&n.write(`${r}\n`);return{stdout:n,stderr:c}}makeResolver(){let e=[];for(let r of this.plugins.values())for(let s of r.resolvers||[])e.push(new s);return new em([new TQ,new Ei,...e])}makeFetcher(){let e=[];for(let r of this.plugins.values())for(let s of r.fetchers||[])e.push(new s);return new aI([new lI,new cI,...e])}getLinkers(){let e=[];for(let r of this.plugins.values())for(let s of r.linkers||[])e.push(new s);return e}getSupportedArchitectures(){let e=pv(),r=this.get(\"supportedArchitectures\"),s=r.get(\"os\");s!==null&&(s=s.map(c=>c===\"current\"?e.os:c));let a=r.get(\"cpu\");a!==null&&(a=a.map(c=>c===\"current\"?e.cpu:c));let n=r.get(\"libc\");return n!==null&&(n=Yl(n,c=>c===\"current\"?e.libc??Yl.skip:c)),{os:s,cpu:a,libc:n}}isInteractive({interactive:e,stdout:r}){return r.isTTY?e??this.get(\"preferInteractive\"):!1}async getPackageExtensions(){if(this.packageExtensions!==null)return this.packageExtensions;this.packageExtensions=new Map;let e=this.packageExtensions,r=(s,a,{userProvided:n=!1}={})=>{if(!ul(s.range))throw new Error(\"Only semver ranges are allowed as keys for the packageExtensions setting\");let c=new Ht;c.load(a,{yamlCompatibilityMode:!0});let f=LB(e,s.identHash),p=[];f.push([s.range,p]);let h={status:\"inactive\",userProvided:n,parentDescriptor:s};for(let E of c.dependencies.values())p.push({...h,type:\"Dependency\",descriptor:E});for(let E of c.peerDependencies.values())p.push({...h,type:\"PeerDependency\",descriptor:E});for(let[E,C]of c.peerDependenciesMeta)for(let[S,P]of Object.entries(C))p.push({...h,type:\"PeerDependencyMeta\",selector:E,key:S,value:P})};await this.triggerHook(s=>s.registerPackageExtensions,this,r);for(let[s,a]of this.get(\"packageExtensions\"))r(I0(s,!0),Wk(a),{userProvided:!0});return e}normalizeLocator(e){return ul(e.reference)?Vs(e,`${this.get(\"defaultProtocol\")}${e.reference}`):Up.test(e.reference)?Vs(e,`${this.get(\"defaultProtocol\")}${e.reference}`):e}normalizeDependency(e){return ul(e.range)?On(e,`${this.get(\"defaultProtocol\")}${e.range}`):Up.test(e.range)?On(e,`${this.get(\"defaultProtocol\")}${e.range}`):e}normalizeDependencyMap(e){return new Map([...e].map(([r,s])=>[r,this.normalizeDependency(s)]))}normalizePackage(e,{packageExtensions:r}){let s=WB(e),a=r.get(e.identHash);if(typeof a<\"u\"){let c=e.version;if(c!==null){for(let[f,p]of a)if(eA(c,f))for(let h of p)switch(h.status===\"inactive\"&&(h.status=\"redundant\"),h.type){case\"Dependency\":typeof s.dependencies.get(h.descriptor.identHash)>\"u\"&&(h.status=\"active\",s.dependencies.set(h.descriptor.identHash,this.normalizeDependency(h.descriptor)));break;case\"PeerDependency\":typeof s.peerDependencies.get(h.descriptor.identHash)>\"u\"&&(h.status=\"active\",s.peerDependencies.set(h.descriptor.identHash,h.descriptor));break;case\"PeerDependencyMeta\":{let E=s.peerDependenciesMeta.get(h.selector);(typeof E>\"u\"||!Object.hasOwn(E,h.key)||E[h.key]!==h.value)&&(h.status=\"active\",Vl(s.peerDependenciesMeta,h.selector,()=>({}))[h.key]=h.value)}break;default:r3(h)}}}let n=c=>c.scope?`${c.scope}__${c.name}`:`${c.name}`;for(let c of s.peerDependenciesMeta.keys()){let f=Da(c);s.peerDependencies.has(f.identHash)||s.peerDependencies.set(f.identHash,On(f,\"*\"))}for(let c of s.peerDependencies.values()){if(c.scope===\"types\")continue;let f=n(c),p=ba(\"types\",f),h=cn(p);s.peerDependencies.has(p.identHash)||s.peerDependenciesMeta.has(h)||s.dependencies.has(p.identHash)||(s.peerDependencies.set(p.identHash,On(p,\"*\")),s.peerDependenciesMeta.set(h,{optional:!0}))}return s.dependencies=new Map(Ys(s.dependencies,([,c])=>ll(c))),s.peerDependencies=new Map(Ys(s.peerDependencies,([,c])=>ll(c))),s}getLimit(e){return Vl(this.limits,e,()=>(0,Sde.default)(this.get(e)))}async triggerHook(e,...r){for(let s of this.plugins.values()){let a=s.hooks;if(!a)continue;let n=e(a);n&&await n(...r)}}async triggerMultipleHooks(e,r){for(let s of r)await this.triggerHook(e,...s)}async reduceHook(e,r,...s){let a=r;for(let n of this.plugins.values()){let c=n.hooks;if(!c)continue;let f=e(c);f&&(a=await f(a,...s))}return a}async firstHook(e,...r){for(let s of this.plugins.values()){let a=s.hooks;if(!a)continue;let n=e(a);if(!n)continue;let c=await n(...r);if(typeof c<\"u\")return c}return null}}});var Gr={};Vt(Gr,{EndStrategy:()=>Lj,ExecError:()=>ET,PipeError:()=>mv,execvp:()=>bj,pipevp:()=>Yu});function im(t){return t!==null&&typeof t.fd==\"number\"}function Fj(){}function Nj(){for(let t of sm)t.kill()}async function Yu(t,e,{cwd:r,env:s=process.env,strict:a=!1,stdin:n=null,stdout:c,stderr:f,end:p=2}){let h=[\"pipe\",\"pipe\",\"pipe\"];n===null?h[0]=\"ignore\":im(n)&&(h[0]=n),im(c)&&(h[1]=c),im(f)&&(h[2]=f);let E=(0,Oj.default)(t,e,{cwd:ue.fromPortablePath(r),env:{...s,PWD:ue.fromPortablePath(r)},stdio:h});sm.add(E),sm.size===1&&(process.on(\"SIGINT\",Fj),process.on(\"SIGTERM\",Nj)),!im(n)&&n!==null&&n.pipe(E.stdin),im(c)||E.stdout.pipe(c,{end:!1}),im(f)||E.stderr.pipe(f,{end:!1});let C=()=>{for(let S of new Set([c,f]))im(S)||S.end()};return new Promise((S,P)=>{E.on(\"error\",I=>{sm.delete(E),sm.size===0&&(process.off(\"SIGINT\",Fj),process.off(\"SIGTERM\",Nj)),(p===2||p===1)&&C(),P(I)}),E.on(\"close\",(I,R)=>{sm.delete(E),sm.size===0&&(process.off(\"SIGINT\",Fj),process.off(\"SIGTERM\",Nj)),(p===2||p===1&&I!==0)&&C(),I===0||!a?S({code:Mj(I,R)}):P(new mv({fileName:t,code:I,signal:R}))})})}async function bj(t,e,{cwd:r,env:s=process.env,encoding:a=\"utf8\",strict:n=!1}){let c=[\"ignore\",\"pipe\",\"pipe\"],f=[],p=[],h=ue.fromPortablePath(r);typeof s.PWD<\"u\"&&(s={...s,PWD:h});let E=(0,Oj.default)(t,e,{cwd:h,env:s,stdio:c});return E.stdout.on(\"data\",C=>{f.push(C)}),E.stderr.on(\"data\",C=>{p.push(C)}),await new Promise((C,S)=>{E.on(\"error\",P=>{let I=ze.create(r),R=Ut(I,t,pt.PATH);S(new Yt(1,`Process ${R} failed to spawn`,N=>{N.reportError(1,`  ${Zf(I,{label:\"Thrown Error\",value:Hu(pt.NO_HINT,P.message)})}`)}))}),E.on(\"close\",(P,I)=>{let R=a===\"buffer\"?Buffer.concat(f):Buffer.concat(f).toString(a),N=a===\"buffer\"?Buffer.concat(p):Buffer.concat(p).toString(a);P===0||!n?C({code:Mj(P,I),stdout:R,stderr:N}):S(new ET({fileName:t,code:P,signal:I,stdout:R,stderr:N}))})})}function Mj(t,e){let r=jdt.get(e);return typeof r<\"u\"?128+r:t??1}function qdt(t,e,{configuration:r,report:s}){s.reportError(1,`  ${Zf(r,t!==null?{label:\"Exit Code\",value:Hu(pt.NUMBER,t)}:{label:\"Exit Signal\",value:Hu(pt.CODE,e)})}`)}var Oj,Lj,mv,ET,sm,jdt,hT=Ct(()=>{bt();Oj=et(j_());dv();Fc();Qc();Lj=(s=>(s[s.Never=0]=\"Never\",s[s.ErrorCode=1]=\"ErrorCode\",s[s.Always=2]=\"Always\",s))(Lj||{}),mv=class extends Yt{constructor({fileName:e,code:r,signal:s}){let a=ze.create(K.cwd()),n=Ut(a,e,pt.PATH);super(1,`Child ${n} reported an error`,c=>{qdt(r,s,{configuration:a,report:c})}),this.code=Mj(r,s)}},ET=class extends mv{constructor({fileName:e,code:r,signal:s,stdout:a,stderr:n}){super({fileName:e,code:r,signal:s}),this.stdout=a,this.stderr=n}};sm=new Set;jdt=new Map([[\"SIGINT\",2],[\"SIGQUIT\",3],[\"SIGKILL\",9],[\"SIGTERM\",15]])});function Pde(t){bde=t}function yv(){return typeof _j>\"u\"&&(_j=bde()),_j}var _j,bde,Uj=Ct(()=>{bde=()=>{throw new Error(\"Assertion failed: No libzip instance is available, and no factory was configured\")}});var xde=L((IT,jj)=>{var Gdt=Object.assign({},Ie(\"fs\")),Hj=function(){var t=typeof document<\"u\"&&document.currentScript?document.currentScript.src:void 0;return typeof __filename<\"u\"&&(t=t||__filename),function(e){e=e||{};var r=typeof e<\"u\"?e:{},s,a;r.ready=new Promise(function(Je,st){s=Je,a=st});var n={},c;for(c in r)r.hasOwnProperty(c)&&(n[c]=r[c]);var f=[],p=\"./this.program\",h=function(Je,st){throw st},E=!1,C=!0,S=\"\";function P(Je){return r.locateFile?r.locateFile(Je,S):S+Je}var I,R,N,U;C&&(E?S=Ie(\"path\").dirname(S)+\"/\":S=__dirname+\"/\",I=function(st,St){var lr=Me(st);return lr?St?lr:lr.toString():(N||(N=Gdt),U||(U=Ie(\"path\")),st=U.normalize(st),N.readFileSync(st,St?null:\"utf8\"))},R=function(st){var St=I(st,!0);return St.buffer||(St=new Uint8Array(St)),we(St.buffer),St},process.argv.length>1&&(p=process.argv[1].replace(/\\\\/g,\"/\")),f=process.argv.slice(2),h=function(Je){process.exit(Je)},r.inspect=function(){return\"[Emscripten Module object]\"});var W=r.print||console.log.bind(console),te=r.printErr||console.warn.bind(console);for(c in n)n.hasOwnProperty(c)&&(r[c]=n[c]);n=null,r.arguments&&(f=r.arguments),r.thisProgram&&(p=r.thisProgram),r.quit&&(h=r.quit);var ie=0,Ae=function(Je){ie=Je},ce;r.wasmBinary&&(ce=r.wasmBinary);var me=r.noExitRuntime||!0;typeof WebAssembly!=\"object\"&&ns(\"no native wasm support detected\");function pe(Je,st,St){switch(st=st||\"i8\",st.charAt(st.length-1)===\"*\"&&(st=\"i32\"),st){case\"i1\":return Ye[Je>>0];case\"i8\":return Ye[Je>>0];case\"i16\":return Eh((Je>>1)*2);case\"i32\":return no((Je>>2)*4);case\"i64\":return no((Je>>2)*4);case\"float\":return pf((Je>>2)*4);case\"double\":return yh((Je>>3)*8);default:ns(\"invalid type for getValue: \"+st)}return null}var Be,Ce=!1,g;function we(Je,st){Je||ns(\"Assertion failed: \"+st)}function ye(Je){var st=r[\"_\"+Je];return we(st,\"Cannot call unknown function \"+Je+\", make sure it is exported\"),st}function fe(Je,st,St,lr,ee){var Ee={string:function(Gi){var Tn=0;if(Gi!=null&&Gi!==0){var Ga=(Gi.length<<2)+1;Tn=Bi(Ga),dt(Gi,Tn,Ga)}return Tn},array:function(Gi){var Tn=Bi(Gi.length);return Fe(Gi,Tn),Tn}};function Oe(Gi){return st===\"string\"?De(Gi):st===\"boolean\"?!!Gi:Gi}var gt=ye(Je),yt=[],Dt=0;if(lr)for(var tr=0;tr<lr.length;tr++){var fn=Ee[St[tr]];fn?(Dt===0&&(Dt=df()),yt[tr]=fn(lr[tr])):yt[tr]=lr[tr]}var li=gt.apply(null,yt);return li=Oe(li),Dt!==0&&Ac(Dt),li}function se(Je,st,St,lr){St=St||[];var ee=St.every(function(Oe){return Oe===\"number\"}),Ee=st!==\"string\";return Ee&&ee&&!lr?ye(Je):function(){return fe(Je,st,St,arguments,lr)}}var X=new TextDecoder(\"utf8\");function De(Je,st){if(!Je)return\"\";for(var St=Je+st,lr=Je;!(lr>=St)&&ke[lr];)++lr;return X.decode(ke.subarray(Je,lr))}function Re(Je,st,St,lr){if(!(lr>0))return 0;for(var ee=St,Ee=St+lr-1,Oe=0;Oe<Je.length;++Oe){var gt=Je.charCodeAt(Oe);if(gt>=55296&&gt<=57343){var yt=Je.charCodeAt(++Oe);gt=65536+((gt&1023)<<10)|yt&1023}if(gt<=127){if(St>=Ee)break;st[St++]=gt}else if(gt<=2047){if(St+1>=Ee)break;st[St++]=192|gt>>6,st[St++]=128|gt&63}else if(gt<=65535){if(St+2>=Ee)break;st[St++]=224|gt>>12,st[St++]=128|gt>>6&63,st[St++]=128|gt&63}else{if(St+3>=Ee)break;st[St++]=240|gt>>18,st[St++]=128|gt>>12&63,st[St++]=128|gt>>6&63,st[St++]=128|gt&63}}return st[St]=0,St-ee}function dt(Je,st,St){return Re(Je,ke,st,St)}function j(Je){for(var st=0,St=0;St<Je.length;++St){var lr=Je.charCodeAt(St);lr>=55296&&lr<=57343&&(lr=65536+((lr&1023)<<10)|Je.charCodeAt(++St)&1023),lr<=127?++st:lr<=2047?st+=2:lr<=65535?st+=3:st+=4}return st}function rt(Je){var st=j(Je)+1,St=Ma(st);return St&&Re(Je,Ye,St,st),St}function Fe(Je,st){Ye.set(Je,st)}function Ne(Je,st){return Je%st>0&&(Je+=st-Je%st),Je}var Pe,Ye,ke,it,_e,x,w,b,y,F;function z(Je){Pe=Je,r.HEAP_DATA_VIEW=F=new DataView(Je),r.HEAP8=Ye=new Int8Array(Je),r.HEAP16=it=new Int16Array(Je),r.HEAP32=x=new Int32Array(Je),r.HEAPU8=ke=new Uint8Array(Je),r.HEAPU16=_e=new Uint16Array(Je),r.HEAPU32=w=new Uint32Array(Je),r.HEAPF32=b=new Float32Array(Je),r.HEAPF64=y=new Float64Array(Je)}var Z=r.INITIAL_MEMORY||16777216,$,oe=[],xe=[],Te=[],lt=!1;function It(){if(r.preRun)for(typeof r.preRun==\"function\"&&(r.preRun=[r.preRun]);r.preRun.length;)Pt(r.preRun.shift());Fs(oe)}function qt(){lt=!0,Fs(xe)}function ir(){if(r.postRun)for(typeof r.postRun==\"function\"&&(r.postRun=[r.postRun]);r.postRun.length;)Pr(r.postRun.shift());Fs(Te)}function Pt(Je){oe.unshift(Je)}function gn(Je){xe.unshift(Je)}function Pr(Je){Te.unshift(Je)}var Ir=0,Nr=null,nn=null;function ai(Je){Ir++,r.monitorRunDependencies&&r.monitorRunDependencies(Ir)}function wo(Je){if(Ir--,r.monitorRunDependencies&&r.monitorRunDependencies(Ir),Ir==0&&(Nr!==null&&(clearInterval(Nr),Nr=null),nn)){var st=nn;nn=null,st()}}r.preloadedImages={},r.preloadedAudios={};function ns(Je){r.onAbort&&r.onAbort(Je),Je+=\"\",te(Je),Ce=!0,g=1,Je=\"abort(\"+Je+\"). Build with -s ASSERTIONS=1 for more info.\";var st=new WebAssembly.RuntimeError(Je);throw a(st),st}var to=\"data:application/octet-stream;base64,\";function Bo(Je){return Je.startsWith(to)}var ji=\"data:application/octet-stream;base64,AGFzbQEAAAAB/wEkYAN/f38Bf2ABfwF/YAJ/fwF/YAF/AGAEf39/fwF/YAN/f38AYAV/f39/fwF/YAJ/fwBgBH9/f38AYAABf2AFf39/fn8BfmAEf35/fwF/YAR/f35/AX5gAn9+AX9gA398fwBgA39/fgF/YAF/AX5gBn9/f39/fwF/YAN/fn8Bf2AEf39/fwF+YAV/f35/fwF/YAR/f35/AX9gA39/fgF+YAJ/fgBgAn9/AX5gBX9/f39/AGADf35/AX5gBX5+f35/AX5gA39/fwF+YAZ/fH9/f38Bf2AAAGAHf35/f39+fwF/YAV/fn9/fwF/YAV/f39/fwF+YAJ+fwF/YAJ/fAACJQYBYQFhAAMBYQFiAAEBYQFjAAABYQFkAAEBYQFlAAIBYQFmAAED5wHlAQMAAwEDAwEHDAgDFgcNEgEDDRcFAQ8DEAUQAwIBAhgECxkEAQMBBQsFAwMDARACBAMAAggLBwEAAwADGgQDGwYGABwBBgMTFBEHBwcVCx4ABAgHBAICAgAfAQICAgIGFSAAIQAiAAIBBgIHAg0LEw0FAQUCACMDAQAUAAAGBQECBQUDCwsSAgEDBQIHAQEICAACCQQEAQABCAEBCQoBAwkBAQEBBgEGBgYABAIEBAQGEQQEAAARAAEDCQEJAQAJCQkBAQECCgoAAAMPAQEBAwACAgICBQIABwAKBgwHAAADAgICBQEEBQFwAT8/BQcBAYACgIACBgkBfwFBgInBAgsH+gEzAWcCAAFoAFQBaQDqAQFqALsBAWsAwQEBbACpAQFtAKgBAW4ApwEBbwClAQFwAKMBAXEAoAEBcgCbAQFzAMABAXQAugEBdQC5AQF2AEsBdwDiAQF4AMgBAXkAxwEBegDCAQFBAMkBAUIAuAEBQwAGAUQACQFFAKYBAUYAtwEBRwC2AQFIALUBAUkAtAEBSgCzAQFLALIBAUwAsQEBTQCwAQFOAK8BAU8AvAEBUACuAQFRAK0BAVIArAEBUwAaAVQACwFVAKQBAVYAMgFXAQABWACrAQFZAKoBAVoAxgEBXwDFAQEkAMQBAmFhAL8BAmJhAL4BAmNhAL0BCXgBAEEBCz6iAeMBjgGQAVpbjwFYnwGdAVeeAV1coQFZVlWcAZoBmQGYAZcBlgGVAZQBkwGSAZEB6QHoAecB5gHlAeQB4QHfAeAB3gHdAdwB2gHbAYUB2QHYAdcB1gHVAdQB0wHSAdEB0AHPAc4BzQHMAcsBygE4wwEK1N8G5QHMDAEHfwJAIABFDQAgAEEIayIDIABBBGsoAgAiAUF4cSIAaiEFAkAgAUEBcQ0AIAFBA3FFDQEgAyADKAIAIgFrIgNBxIQBKAIASQ0BIAAgAWohACADQciEASgCAEcEQCABQf8BTQRAIAMoAggiAiABQQN2IgRBA3RB3IQBakYaIAIgAygCDCIBRgRAQbSEAUG0hAEoAgBBfiAEd3E2AgAMAwsgAiABNgIMIAEgAjYCCAwCCyADKAIYIQYCQCADIAMoAgwiAUcEQCADKAIIIgIgATYCDCABIAI2AggMAQsCQCADQRRqIgIoAgAiBA0AIANBEGoiAigCACIEDQBBACEBDAELA0AgAiEHIAQiAUEUaiICKAIAIgQNACABQRBqIQIgASgCECIEDQALIAdBADYCAAsgBkUNAQJAIAMgAygCHCICQQJ0QeSGAWoiBCgCAEYEQCAEIAE2AgAgAQ0BQbiEAUG4hAEoAgBBfiACd3E2AgAMAwsgBkEQQRQgBigCECADRhtqIAE2AgAgAUUNAgsgASAGNgIYIAMoAhAiAgRAIAEgAjYCECACIAE2AhgLIAMoAhQiAkUNASABIAI2AhQgAiABNgIYDAELIAUoAgQiAUEDcUEDRw0AQbyEASAANgIAIAUgAUF+cTYCBCADIABBAXI2AgQgACADaiAANgIADwsgAyAFTw0AIAUoAgQiAUEBcUUNAAJAIAFBAnFFBEAgBUHMhAEoAgBGBEBBzIQBIAM2AgBBwIQBQcCEASgCACAAaiIANgIAIAMgAEEBcjYCBCADQciEASgCAEcNA0G8hAFBADYCAEHIhAFBADYCAA8LIAVByIQBKAIARgRAQciEASADNgIAQbyEAUG8hAEoAgAgAGoiADYCACADIABBAXI2AgQgACADaiAANgIADwsgAUF4cSAAaiEAAkAgAUH/AU0EQCAFKAIIIgIgAUEDdiIEQQN0QdyEAWpGGiACIAUoAgwiAUYEQEG0hAFBtIQBKAIAQX4gBHdxNgIADAILIAIgATYCDCABIAI2AggMAQsgBSgCGCEGAkAgBSAFKAIMIgFHBEAgBSgCCCICQcSEASgCAEkaIAIgATYCDCABIAI2AggMAQsCQCAFQRRqIgIoAgAiBA0AIAVBEGoiAigCACIEDQBBACEBDAELA0AgAiEHIAQiAUEUaiICKAIAIgQNACABQRBqIQIgASgCECIEDQALIAdBADYCAAsgBkUNAAJAIAUgBSgCHCICQQJ0QeSGAWoiBCgCAEYEQCAEIAE2AgAgAQ0BQbiEAUG4hAEoAgBBfiACd3E2AgAMAgsgBkEQQRQgBigCECAFRhtqIAE2AgAgAUUNAQsgASAGNgIYIAUoAhAiAgRAIAEgAjYCECACIAE2AhgLIAUoAhQiAkUNACABIAI2AhQgAiABNgIYCyADIABBAXI2AgQgACADaiAANgIAIANByIQBKAIARw0BQbyEASAANgIADwsgBSABQX5xNgIEIAMgAEEBcjYCBCAAIANqIAA2AgALIABB/wFNBEAgAEEDdiIBQQN0QdyEAWohAAJ/QbSEASgCACICQQEgAXQiAXFFBEBBtIQBIAEgAnI2AgAgAAwBCyAAKAIICyECIAAgAzYCCCACIAM2AgwgAyAANgIMIAMgAjYCCA8LQR8hAiADQgA3AhAgAEH///8HTQRAIABBCHYiASABQYD+P2pBEHZBCHEiAXQiAiACQYDgH2pBEHZBBHEiAnQiBCAEQYCAD2pBEHZBAnEiBHRBD3YgASACciAEcmsiAUEBdCAAIAFBFWp2QQFxckEcaiECCyADIAI2AhwgAkECdEHkhgFqIQECQAJAAkBBuIQBKAIAIgRBASACdCIHcUUEQEG4hAEgBCAHcjYCACABIAM2AgAgAyABNgIYDAELIABBAEEZIAJBAXZrIAJBH0YbdCECIAEoAgAhAQNAIAEiBCgCBEF4cSAARg0CIAJBHXYhASACQQF0IQIgBCABQQRxaiIHQRBqKAIAIgENAAsgByADNgIQIAMgBDYCGAsgAyADNgIMIAMgAzYCCAwBCyAEKAIIIgAgAzYCDCAEIAM2AgggA0EANgIYIAMgBDYCDCADIAA2AggLQdSEAUHUhAEoAgBBAWsiAEF/IAAbNgIACwuDBAEDfyACQYAETwRAIAAgASACEAIaIAAPCyAAIAJqIQMCQCAAIAFzQQNxRQRAAkAgAEEDcUUEQCAAIQIMAQsgAkEBSARAIAAhAgwBCyAAIQIDQCACIAEtAAA6AAAgAUEBaiEBIAJBAWoiAkEDcUUNASACIANJDQALCwJAIANBfHEiBEHAAEkNACACIARBQGoiBUsNAANAIAIgASgCADYCACACIAEoAgQ2AgQgAiABKAIINgIIIAIgASgCDDYCDCACIAEoAhA2AhAgAiABKAIUNgIUIAIgASgCGDYCGCACIAEoAhw2AhwgAiABKAIgNgIgIAIgASgCJDYCJCACIAEoAig2AiggAiABKAIsNgIsIAIgASgCMDYCMCACIAEoAjQ2AjQgAiABKAI4NgI4IAIgASgCPDYCPCABQUBrIQEgAkFAayICIAVNDQALCyACIARPDQEDQCACIAEoAgA2AgAgAUEEaiEBIAJBBGoiAiAESQ0ACwwBCyADQQRJBEAgACECDAELIAAgA0EEayIESwRAIAAhAgwBCyAAIQIDQCACIAEtAAA6AAAgAiABLQABOgABIAIgAS0AAjoAAiACIAEtAAM6AAMgAUEEaiEBIAJBBGoiAiAETQ0ACwsgAiADSQRAA0AgAiABLQAAOgAAIAFBAWohASACQQFqIgIgA0cNAAsLIAALGgAgAARAIAAtAAEEQCAAKAIEEAYLIAAQBgsLoi4BDH8jAEEQayIMJAACQAJAAkACQAJAAkACQAJAAkACQAJAAkAgAEH0AU0EQEG0hAEoAgAiBUEQIABBC2pBeHEgAEELSRsiCEEDdiICdiIBQQNxBEAgAUF/c0EBcSACaiIDQQN0IgFB5IQBaigCACIEQQhqIQACQCAEKAIIIgIgAUHchAFqIgFGBEBBtIQBIAVBfiADd3E2AgAMAQsgAiABNgIMIAEgAjYCCAsgBCADQQN0IgFBA3I2AgQgASAEaiIBIAEoAgRBAXI2AgQMDQsgCEG8hAEoAgAiCk0NASABBEACQEECIAJ0IgBBACAAa3IgASACdHEiAEEAIABrcUEBayIAIABBDHZBEHEiAnYiAUEFdkEIcSIAIAJyIAEgAHYiAUECdkEEcSIAciABIAB2IgFBAXZBAnEiAHIgASAAdiIBQQF2QQFxIgByIAEgAHZqIgNBA3QiAEHkhAFqKAIAIgQoAggiASAAQdyEAWoiAEYEQEG0hAEgBUF+IAN3cSIFNgIADAELIAEgADYCDCAAIAE2AggLIARBCGohACAEIAhBA3I2AgQgBCAIaiICIANBA3QiASAIayIDQQFyNgIEIAEgBGogAzYCACAKBEAgCkEDdiIBQQN0QdyEAWohB0HIhAEoAgAhBAJ/IAVBASABdCIBcUUEQEG0hAEgASAFcjYCACAHDAELIAcoAggLIQEgByAENgIIIAEgBDYCDCAEIAc2AgwgBCABNgIIC0HIhAEgAjYCAEG8hAEgAzYCAAwNC0G4hAEoAgAiBkUNASAGQQAgBmtxQQFrIgAgAEEMdkEQcSICdiIBQQV2QQhxIgAgAnIgASAAdiIBQQJ2QQRxIgByIAEgAHYiAUEBdkECcSIAciABIAB2IgFBAXZBAXEiAHIgASAAdmpBAnRB5IYBaigCACIBKAIEQXhxIAhrIQMgASECA0ACQCACKAIQIgBFBEAgAigCFCIARQ0BCyAAKAIEQXhxIAhrIgIgAyACIANJIgIbIQMgACABIAIbIQEgACECDAELCyABIAhqIgkgAU0NAiABKAIYIQsgASABKAIMIgRHBEAgASgCCCIAQcSEASgCAEkaIAAgBDYCDCAEIAA2AggMDAsgAUEUaiICKAIAIgBFBEAgASgCECIARQ0EIAFBEGohAgsDQCACIQcgACIEQRRqIgIoAgAiAA0AIARBEGohAiAEKAIQIgANAAsgB0EANgIADAsLQX8hCCAAQb9/Sw0AIABBC2oiAEF4cSEIQbiEASgCACIJRQ0AQQAgCGshAwJAAkACQAJ/QQAgCEGAAkkNABpBHyAIQf///wdLDQAaIABBCHYiACAAQYD+P2pBEHZBCHEiAnQiACAAQYDgH2pBEHZBBHEiAXQiACAAQYCAD2pBEHZBAnEiAHRBD3YgASACciAAcmsiAEEBdCAIIABBFWp2QQFxckEcagsiBUECdEHkhgFqKAIAIgJFBEBBACEADAELQQAhACAIQQBBGSAFQQF2ayAFQR9GG3QhAQNAAkAgAigCBEF4cSAIayIHIANPDQAgAiEEIAciAw0AQQAhAyACIQAMAwsgACACKAIUIgcgByACIAFBHXZBBHFqKAIQIgJGGyAAIAcbIQAgAUEBdCEBIAINAAsLIAAgBHJFBEBBAiAFdCIAQQAgAGtyIAlxIgBFDQMgAEEAIABrcUEBayIAIABBDHZBEHEiAnYiAUEFdkEIcSIAIAJyIAEgAHYiAUECdkEEcSIAciABIAB2IgFBAXZBAnEiAHIgASAAdiIBQQF2QQFxIgByIAEgAHZqQQJ0QeSGAWooAgAhAAsgAEUNAQsDQCAAKAIEQXhxIAhrIgEgA0khAiABIAMgAhshAyAAIAQgAhshBCAAKAIQIgEEfyABBSAAKAIUCyIADQALCyAERQ0AIANBvIQBKAIAIAhrTw0AIAQgCGoiBiAETQ0BIAQoAhghBSAEIAQoAgwiAUcEQCAEKAIIIgBBxIQBKAIASRogACABNgIMIAEgADYCCAwKCyAEQRRqIgIoAgAiAEUEQCAEKAIQIgBFDQQgBEEQaiECCwNAIAIhByAAIgFBFGoiAigCACIADQAgAUEQaiECIAEoAhAiAA0ACyAHQQA2AgAMCQsgCEG8hAEoAgAiAk0EQEHIhAEoAgAhAwJAIAIgCGsiAUEQTwRAQbyEASABNgIAQciEASADIAhqIgA2AgAgACABQQFyNgIEIAIgA2ogATYCACADIAhBA3I2AgQMAQtByIQBQQA2AgBBvIQBQQA2AgAgAyACQQNyNgIEIAIgA2oiACAAKAIEQQFyNgIECyADQQhqIQAMCwsgCEHAhAEoAgAiBkkEQEHAhAEgBiAIayIBNgIAQcyEAUHMhAEoAgAiAiAIaiIANgIAIAAgAUEBcjYCBCACIAhBA3I2AgQgAkEIaiEADAsLQQAhACAIQS9qIgkCf0GMiAEoAgAEQEGUiAEoAgAMAQtBmIgBQn83AgBBkIgBQoCggICAgAQ3AgBBjIgBIAxBDGpBcHFB2KrVqgVzNgIAQaCIAUEANgIAQfCHAUEANgIAQYAgCyIBaiIFQQAgAWsiB3EiAiAITQ0KQeyHASgCACIEBEBB5IcBKAIAIgMgAmoiASADTQ0LIAEgBEsNCwtB8IcBLQAAQQRxDQUCQAJAQcyEASgCACIDBEBB9IcBIQADQCADIAAoAgAiAU8EQCABIAAoAgRqIANLDQMLIAAoAggiAA0ACwtBABApIgFBf0YNBiACIQVBkIgBKAIAIgNBAWsiACABcQRAIAIgAWsgACABakEAIANrcWohBQsgBSAITQ0GIAVB/v///wdLDQZB7IcBKAIAIgQEQEHkhwEoAgAiAyAFaiIAIANNDQcgACAESw0HCyAFECkiACABRw0BDAgLIAUgBmsgB3EiBUH+////B0sNBSAFECkiASAAKAIAIAAoAgRqRg0EIAEhAAsCQCAAQX9GDQAgCEEwaiAFTQ0AQZSIASgCACIBIAkgBWtqQQAgAWtxIgFB/v///wdLBEAgACEBDAgLIAEQKUF/RwRAIAEgBWohBSAAIQEMCAtBACAFaxApGgwFCyAAIgFBf0cNBgwECwALQQAhBAwHC0EAIQEMBQsgAUF/Rw0CC0HwhwFB8IcBKAIAQQRyNgIACyACQf7///8HSw0BIAIQKSEBQQAQKSEAIAFBf0YNASAAQX9GDQEgACABTQ0BIAAgAWsiBSAIQShqTQ0BC0HkhwFB5IcBKAIAIAVqIgA2AgBB6IcBKAIAIABJBEBB6IcBIAA2AgALAkACQAJAQcyEASgCACIHBEBB9IcBIQADQCABIAAoAgAiAyAAKAIEIgJqRg0CIAAoAggiAA0ACwwCC0HEhAEoAgAiAEEAIAAgAU0bRQRAQcSEASABNgIAC0EAIQBB+IcBIAU2AgBB9IcBIAE2AgBB1IQBQX82AgBB2IQBQYyIASgCADYCAEGAiAFBADYCAANAIABBA3QiA0HkhAFqIANB3IQBaiICNgIAIANB6IQBaiACNgIAIABBAWoiAEEgRw0AC0HAhAEgBUEoayIDQXggAWtBB3FBACABQQhqQQdxGyIAayICNgIAQcyEASAAIAFqIgA2AgAgACACQQFyNgIEIAEgA2pBKDYCBEHQhAFBnIgBKAIANgIADAILIAAtAAxBCHENACADIAdLDQAgASAHTQ0AIAAgAiAFajYCBEHMhAEgB0F4IAdrQQdxQQAgB0EIakEHcRsiAGoiAjYCAEHAhAFBwIQBKAIAIAVqIgEgAGsiADYCACACIABBAXI2AgQgASAHakEoNgIEQdCEAUGciAEoAgA2AgAMAQtBxIQBKAIAIAFLBEBBxIQBIAE2AgALIAEgBWohAkH0hwEhAAJAAkACQAJAAkACQANAIAIgACgCAEcEQCAAKAIIIgANAQwCCwsgAC0ADEEIcUUNAQtB9IcBIQADQCAHIAAoAgAiAk8EQCACIAAoAgRqIgQgB0sNAwsgACgCCCEADAALAAsgACABNgIAIAAgACgCBCAFajYCBCABQXggAWtBB3FBACABQQhqQQdxG2oiCSAIQQNyNgIEIAJBeCACa0EHcUEAIAJBCGpBB3EbaiIFIAggCWoiBmshAiAFIAdGBEBBzIQBIAY2AgBBwIQBQcCEASgCACACaiIANgIAIAYgAEEBcjYCBAwDCyAFQciEASgCAEYEQEHIhAEgBjYCAEG8hAFBvIQBKAIAIAJqIgA2AgAgBiAAQQFyNgIEIAAgBmogADYCAAwDCyAFKAIEIgBBA3FBAUYEQCAAQXhxIQcCQCAAQf8BTQRAIAUoAggiAyAAQQN2IgBBA3RB3IQBakYaIAMgBSgCDCIBRgRAQbSEAUG0hAEoAgBBfiAAd3E2AgAMAgsgAyABNgIMIAEgAzYCCAwBCyAFKAIYIQgCQCAFIAUoAgwiAUcEQCAFKAIIIgAgATYCDCABIAA2AggMAQsCQCAFQRRqIgAoAgAiAw0AIAVBEGoiACgCACIDDQBBACEBDAELA0AgACEEIAMiAUEUaiIAKAIAIgMNACABQRBqIQAgASgCECIDDQALIARBADYCAAsgCEUNAAJAIAUgBSgCHCIDQQJ0QeSGAWoiACgCAEYEQCAAIAE2AgAgAQ0BQbiEAUG4hAEoAgBBfiADd3E2AgAMAgsgCEEQQRQgCCgCECAFRhtqIAE2AgAgAUUNAQsgASAINgIYIAUoAhAiAARAIAEgADYCECAAIAE2AhgLIAUoAhQiAEUNACABIAA2AhQgACABNgIYCyAFIAdqIQUgAiAHaiECCyAFIAUoAgRBfnE2AgQgBiACQQFyNgIEIAIgBmogAjYCACACQf8BTQRAIAJBA3YiAEEDdEHchAFqIQICf0G0hAEoAgAiAUEBIAB0IgBxRQRAQbSEASAAIAFyNgIAIAIMAQsgAigCCAshACACIAY2AgggACAGNgIMIAYgAjYCDCAGIAA2AggMAwtBHyEAIAJB////B00EQCACQQh2IgAgAEGA/j9qQRB2QQhxIgN0IgAgAEGA4B9qQRB2QQRxIgF0IgAgAEGAgA9qQRB2QQJxIgB0QQ92IAEgA3IgAHJrIgBBAXQgAiAAQRVqdkEBcXJBHGohAAsgBiAANgIcIAZCADcCECAAQQJ0QeSGAWohBAJAQbiEASgCACIDQQEgAHQiAXFFBEBBuIQBIAEgA3I2AgAgBCAGNgIAIAYgBDYCGAwBCyACQQBBGSAAQQF2ayAAQR9GG3QhACAEKAIAIQEDQCABIgMoAgRBeHEgAkYNAyAAQR12IQEgAEEBdCEAIAMgAUEEcWoiBCgCECIBDQALIAQgBjYCECAGIAM2AhgLIAYgBjYCDCAGIAY2AggMAgtBwIQBIAVBKGsiA0F4IAFrQQdxQQAgAUEIakEHcRsiAGsiAjYCAEHMhAEgACABaiIANgIAIAAgAkEBcjYCBCABIANqQSg2AgRB0IQBQZyIASgCADYCACAHIARBJyAEa0EHcUEAIARBJ2tBB3EbakEvayIAIAAgB0EQakkbIgJBGzYCBCACQfyHASkCADcCECACQfSHASkCADcCCEH8hwEgAkEIajYCAEH4hwEgBTYCAEH0hwEgATYCAEGAiAFBADYCACACQRhqIQADQCAAQQc2AgQgAEEIaiEBIABBBGohACABIARJDQALIAIgB0YNAyACIAIoAgRBfnE2AgQgByACIAdrIgRBAXI2AgQgAiAENgIAIARB/wFNBEAgBEEDdiIAQQN0QdyEAWohAgJ/QbSEASgCACIBQQEgAHQiAHFFBEBBtIQBIAAgAXI2AgAgAgwBCyACKAIICyEAIAIgBzYCCCAAIAc2AgwgByACNgIMIAcgADYCCAwEC0EfIQAgB0IANwIQIARB////B00EQCAEQQh2IgAgAEGA/j9qQRB2QQhxIgJ0IgAgAEGA4B9qQRB2QQRxIgF0IgAgAEGAgA9qQRB2QQJxIgB0QQ92IAEgAnIgAHJrIgBBAXQgBCAAQRVqdkEBcXJBHGohAAsgByAANgIcIABBAnRB5IYBaiEDAkBBuIQBKAIAIgJBASAAdCIBcUUEQEG4hAEgASACcjYCACADIAc2AgAgByADNgIYDAELIARBAEEZIABBAXZrIABBH0YbdCEAIAMoAgAhAQNAIAEiAigCBEF4cSAERg0EIABBHXYhASAAQQF0IQAgAiABQQRxaiIDKAIQIgENAAsgAyAHNgIQIAcgAjYCGAsgByAHNgIMIAcgBzYCCAwDCyADKAIIIgAgBjYCDCADIAY2AgggBkEANgIYIAYgAzYCDCAGIAA2AggLIAlBCGohAAwFCyACKAIIIgAgBzYCDCACIAc2AgggB0EANgIYIAcgAjYCDCAHIAA2AggLQcCEASgCACIAIAhNDQBBwIQBIAAgCGsiATYCAEHMhAFBzIQBKAIAIgIgCGoiADYCACAAIAFBAXI2AgQgAiAIQQNyNgIEIAJBCGohAAwDC0GEhAFBMDYCAEEAIQAMAgsCQCAFRQ0AAkAgBCgCHCICQQJ0QeSGAWoiACgCACAERgRAIAAgATYCACABDQFBuIQBIAlBfiACd3EiCTYCAAwCCyAFQRBBFCAFKAIQIARGG2ogATYCACABRQ0BCyABIAU2AhggBCgCECIABEAgASAANgIQIAAgATYCGAsgBCgCFCIARQ0AIAEgADYCFCAAIAE2AhgLAkAgA0EPTQRAIAQgAyAIaiIAQQNyNgIEIAAgBGoiACAAKAIEQQFyNgIEDAELIAQgCEEDcjYCBCAGIANBAXI2AgQgAyAGaiADNgIAIANB/wFNBEAgA0EDdiIAQQN0QdyEAWohAgJ/QbSEASgCACIBQQEgAHQiAHFFBEBBtIQBIAAgAXI2AgAgAgwBCyACKAIICyEAIAIgBjYCCCAAIAY2AgwgBiACNgIMIAYgADYCCAwBC0EfIQAgA0H///8HTQRAIANBCHYiACAAQYD+P2pBEHZBCHEiAnQiACAAQYDgH2pBEHZBBHEiAXQiACAAQYCAD2pBEHZBAnEiAHRBD3YgASACciAAcmsiAEEBdCADIABBFWp2QQFxckEcaiEACyAGIAA2AhwgBkIANwIQIABBAnRB5IYBaiECAkACQCAJQQEgAHQiAXFFBEBBuIQBIAEgCXI2AgAgAiAGNgIAIAYgAjYCGAwBCyADQQBBGSAAQQF2ayAAQR9GG3QhACACKAIAIQgDQCAIIgEoAgRBeHEgA0YNAiAAQR12IQIgAEEBdCEAIAEgAkEEcWoiAigCECIIDQALIAIgBjYCECAGIAE2AhgLIAYgBjYCDCAGIAY2AggMAQsgASgCCCIAIAY2AgwgASAGNgIIIAZBADYCGCAGIAE2AgwgBiAANgIICyAEQQhqIQAMAQsCQCALRQ0AAkAgASgCHCICQQJ0QeSGAWoiACgCACABRgRAIAAgBDYCACAEDQFBuIQBIAZBfiACd3E2AgAMAgsgC0EQQRQgCygCECABRhtqIAQ2AgAgBEUNAQsgBCALNgIYIAEoAhAiAARAIAQgADYCECAAIAQ2AhgLIAEoAhQiAEUNACAEIAA2AhQgACAENgIYCwJAIANBD00EQCABIAMgCGoiAEEDcjYCBCAAIAFqIgAgACgCBEEBcjYCBAwBCyABIAhBA3I2AgQgCSADQQFyNgIEIAMgCWogAzYCACAKBEAgCkEDdiIAQQN0QdyEAWohBEHIhAEoAgAhAgJ/QQEgAHQiACAFcUUEQEG0hAEgACAFcjYCACAEDAELIAQoAggLIQAgBCACNgIIIAAgAjYCDCACIAQ2AgwgAiAANgIIC0HIhAEgCTYCAEG8hAEgAzYCAAsgAUEIaiEACyAMQRBqJAAgAAuJAQEDfyAAKAIcIgEQMAJAIAAoAhAiAiABKAIQIgMgAiADSRsiAkUNACAAKAIMIAEoAgggAhAHGiAAIAAoAgwgAmo2AgwgASABKAIIIAJqNgIIIAAgACgCFCACajYCFCAAIAAoAhAgAms2AhAgASABKAIQIAJrIgA2AhAgAA0AIAEgASgCBDYCCAsLzgEBBX8CQCAARQ0AIAAoAjAiAQRAIAAgAUEBayIBNgIwIAENAQsgACgCIARAIABBATYCICAAEBoaCyAAKAIkQQFGBEAgABBDCwJAIAAoAiwiAUUNACAALQAoDQACQCABKAJEIgNFDQAgASgCTCEEA0AgACAEIAJBAnRqIgUoAgBHBEAgAyACQQFqIgJHDQEMAgsLIAUgBCADQQFrIgJBAnRqKAIANgIAIAEgAjYCRAsLIABBAEIAQQUQDhogACgCACIBBEAgARALCyAAEAYLC1oCAn4BfwJ/AkACQCAALQAARQ0AIAApAxAiAUJ9Vg0AIAFCAnwiAiAAKQMIWA0BCyAAQQA6AABBAAwBC0EAIAAoAgQiA0UNABogACACNwMQIAMgAadqLwAACwthAgJ+AX8CQAJAIAAtAABFDQAgACkDECICQn1WDQAgAkICfCIDIAApAwhYDQELIABBADoAAA8LIAAoAgQiBEUEQA8LIAAgAzcDECAEIAKnaiIAIAFBCHY6AAEgACABOgAAC8wCAQJ/IwBBEGsiBCQAAkAgACkDGCADrYinQQFxRQRAIABBDGoiAARAIABBADYCBCAAQRw2AgALQn8hAgwBCwJ+IAAoAgAiBUUEQCAAKAIIIAEgAiADIAAoAgQRDAAMAQsgBSAAKAIIIAEgAiADIAAoAgQRCgALIgJCf1UNAAJAIANBBGsOCwEAAAAAAAAAAAABAAsCQAJAIAAtABhBEHFFBEAgAEEMaiIBBEAgAUEANgIEIAFBHDYCAAsMAQsCfiAAKAIAIgFFBEAgACgCCCAEQQhqQghBBCAAKAIEEQwADAELIAEgACgCCCAEQQhqQghBBCAAKAIEEQoAC0J/VQ0BCyAAQQxqIgAEQCAAQQA2AgQgAEEUNgIACwwBCyAEKAIIIQEgBCgCDCEDIABBDGoiAARAIAAgAzYCBCAAIAE2AgALCyAEQRBqJAAgAguTFQIOfwN+AkACQAJAAkACQAJAAkACQAJAAkACQCAAKALwLQRAIAAoAogBQQFIDQEgACgCACIEKAIsQQJHDQQgAC8B5AENAyAALwHoAQ0DIAAvAewBDQMgAC8B8AENAyAALwH0AQ0DIAAvAfgBDQMgAC8B/AENAyAALwGcAg0DIAAvAaACDQMgAC8BpAINAyAALwGoAg0DIAAvAawCDQMgAC8BsAINAyAALwG0Ag0DIAAvAbgCDQMgAC8BvAINAyAALwHAAg0DIAAvAcQCDQMgAC8ByAINAyAALwHUAg0DIAAvAdgCDQMgAC8B3AINAyAALwHgAg0DIAAvAYgCDQIgAC8BjAINAiAALwGYAg0CQSAhBgNAIAAgBkECdCIFai8B5AENAyAAIAVBBHJqLwHkAQ0DIAAgBUEIcmovAeQBDQMgACAFQQxyai8B5AENAyAGQQRqIgZBgAJHDQALDAMLIABBBzYC/C0gAkF8Rw0FIAFFDQUMBgsgAkEFaiIEIQcMAwtBASEHCyAEIAc2AiwLIAAgAEHoFmoQUSAAIABB9BZqEFEgAC8B5gEhBCAAIABB7BZqKAIAIgxBAnRqQf//AzsB6gEgAEGQFmohECAAQZQWaiERIABBjBZqIQdBACEGIAxBAE4EQEEHQYoBIAQbIQ1BBEEDIAQbIQpBfyEJA0AgBCEIIAAgCyIOQQFqIgtBAnRqLwHmASEEAkACQCAGQQFqIgVB//8DcSIPIA1B//8DcU8NACAEIAhHDQAgBSEGDAELAn8gACAIQQJ0akHMFWogCkH//wNxIA9LDQAaIAgEQEEBIQUgByAIIAlGDQEaIAAgCEECdGpBzBVqIgYgBi8BAEEBajsBACAHDAELQQEhBSAQIBEgBkH//wNxQQpJGwsiBiAGLwEAIAVqOwEAQQAhBgJ/IARFBEBBAyEKQYoBDAELQQNBBCAEIAhGIgUbIQpBBkEHIAUbCyENIAghCQsgDCAORw0ACwsgAEHaE2ovAQAhBCAAIABB+BZqKAIAIgxBAnRqQd4TakH//wM7AQBBACEGIAxBAE4EQEEHQYoBIAQbIQ1BBEEDIAQbIQpBfyEJQQAhCwNAIAQhCCAAIAsiDkEBaiILQQJ0akHaE2ovAQAhBAJAAkAgBkEBaiIFQf//A3EiDyANQf//A3FPDQAgBCAIRw0AIAUhBgwBCwJ/IAAgCEECdGpBzBVqIApB//8DcSAPSw0AGiAIBEBBASEFIAcgCCAJRg0BGiAAIAhBAnRqQcwVaiIGIAYvAQBBAWo7AQAgBwwBC0EBIQUgECARIAZB//8DcUEKSRsLIgYgBi8BACAFajsBAEEAIQYCfyAERQRAQQMhCkGKAQwBC0EDQQQgBCAIRiIFGyEKQQZBByAFGwshDSAIIQkLIAwgDkcNAAsLIAAgAEGAF2oQUSAAIAAoAvgtAn9BEiAAQYoWai8BAA0AGkERIABB0hVqLwEADQAaQRAgAEGGFmovAQANABpBDyAAQdYVai8BAA0AGkEOIABBghZqLwEADQAaQQ0gAEHaFWovAQANABpBDCAAQf4Vai8BAA0AGkELIABB3hVqLwEADQAaQQogAEH6FWovAQANABpBCSAAQeIVai8BAA0AGkEIIABB9hVqLwEADQAaQQcgAEHmFWovAQANABpBBiAAQfIVai8BAA0AGkEFIABB6hVqLwEADQAaQQQgAEHuFWovAQANABpBA0ECIABBzhVqLwEAGwsiBkEDbGoiBEERajYC+C0gACgC/C1BCmpBA3YiByAEQRtqQQN2IgRNBEAgByEEDAELIAAoAowBQQRHDQAgByEECyAEIAJBBGpPQQAgARsNASAEIAdHDQQLIANBAmqtIRIgACkDmC4hFCAAKAKgLiIBQQNqIgdBP0sNASASIAGthiAUhCESDAILIAAgASACIAMQOQwDCyABQcAARgRAIAAoAgQgACgCEGogFDcAACAAIAAoAhBBCGo2AhBBAyEHDAELIAAoAgQgACgCEGogEiABrYYgFIQ3AAAgACAAKAIQQQhqNgIQIAFBPWshByASQcAAIAFrrYghEgsgACASNwOYLiAAIAc2AqAuIABBgMEAQYDKABCHAQwBCyADQQRqrSESIAApA5guIRQCQCAAKAKgLiIBQQNqIgRBP00EQCASIAGthiAUhCESDAELIAFBwABGBEAgACgCBCAAKAIQaiAUNwAAIAAgACgCEEEIajYCEEEDIQQMAQsgACgCBCAAKAIQaiASIAGthiAUhDcAACAAIAAoAhBBCGo2AhAgAUE9ayEEIBJBwAAgAWutiCESCyAAIBI3A5guIAAgBDYCoC4gAEHsFmooAgAiC6xCgAJ9IRMgAEH4FmooAgAhCQJAAkACfwJ+AkACfwJ/IARBOk0EQCATIASthiAShCETIARBBWoMAQsgBEHAAEYEQCAAKAIEIAAoAhBqIBI3AAAgACAAKAIQQQhqNgIQIAmsIRJCBSEUQQoMAgsgACgCBCAAKAIQaiATIASthiAShDcAACAAIAAoAhBBCGo2AhAgE0HAACAEa62IIRMgBEE7awshBSAJrCESIAVBOksNASAFrSEUIAVBBWoLIQcgEiAUhiAThAwBCyAFQcAARgRAIAAoAgQgACgCEGogEzcAACAAIAAoAhBBCGo2AhAgBq1CA30hE0IFIRRBCQwCCyAAKAIEIAAoAhBqIBIgBa2GIBOENwAAIAAgACgCEEEIajYCECAFQTtrIQcgEkHAACAFa62ICyESIAatQgN9IRMgB0E7Sw0BIAetIRQgB0EEagshBCATIBSGIBKEIRMMAQsgB0HAAEYEQCAAKAIEIAAoAhBqIBI3AAAgACAAKAIQQQhqNgIQQQQhBAwBCyAAKAIEIAAoAhBqIBMgB62GIBKENwAAIAAgACgCEEEIajYCECAHQTxrIQQgE0HAACAHa62IIRMLQQAhBQNAIAAgBSIBQZDWAGotAABBAnRqQc4VajMBACEUAn8gBEE8TQRAIBQgBK2GIBOEIRMgBEEDagwBCyAEQcAARgRAIAAoAgQgACgCEGogEzcAACAAIAAoAhBBCGo2AhAgFCETQQMMAQsgACgCBCAAKAIQaiAUIASthiAThDcAACAAIAAoAhBBCGo2AhAgFEHAACAEa62IIRMgBEE9awshBCABQQFqIQUgASAGRw0ACyAAIAQ2AqAuIAAgEzcDmC4gACAAQeQBaiICIAsQhgEgACAAQdgTaiIBIAkQhgEgACACIAEQhwELIAAQiAEgAwRAAkAgACgCoC4iBEE5TgRAIAAoAgQgACgCEGogACkDmC43AAAgACAAKAIQQQhqNgIQDAELIARBGU4EQCAAKAIEIAAoAhBqIAApA5guPgAAIAAgAEGcLmo1AgA3A5guIAAgACgCEEEEajYCECAAIAAoAqAuQSBrIgQ2AqAuCyAEQQlOBH8gACgCBCAAKAIQaiAAKQOYLj0AACAAIAAoAhBBAmo2AhAgACAAKQOYLkIQiDcDmC4gACgCoC5BEGsFIAQLQQFIDQAgACAAKAIQIgFBAWo2AhAgASAAKAIEaiAAKQOYLjwAAAsgAEEANgKgLiAAQgA3A5guCwsZACAABEAgACgCABAGIAAoAgwQBiAAEAYLC6wBAQJ+Qn8hAwJAIAAtACgNAAJAAkAgACgCIEUNACACQgBTDQAgAlANASABDQELIABBDGoiAARAIABBADYCBCAAQRI2AgALQn8PCyAALQA1DQBCACEDIAAtADQNACACUA0AA0AgACABIAOnaiACIAN9QQEQDiIEQn9XBEAgAEEBOgA1Qn8gAyADUBsPCyAEUEUEQCADIAR8IgMgAloNAgwBCwsgAEEBOgA0CyADC3UCAn4BfwJAAkAgAC0AAEUNACAAKQMQIgJCe1YNACACQgR8IgMgACkDCFgNAQsgAEEAOgAADwsgACgCBCIERQRADwsgACADNwMQIAQgAqdqIgAgAUEYdjoAAyAAIAFBEHY6AAIgACABQQh2OgABIAAgAToAAAtUAgF+AX8CQAJAIAAtAABFDQAgASAAKQMQIgF8IgIgAVQNACACIAApAwhYDQELIABBADoAAEEADwsgACgCBCIDRQRAQQAPCyAAIAI3AxAgAyABp2oLdwECfyMAQRBrIgMkAEF/IQQCQCAALQAoDQAgACgCIEEAIAJBA0kbRQRAIABBDGoiAARAIABBADYCBCAAQRI2AgALDAELIAMgAjYCCCADIAE3AwAgACADQhBBBhAOQgBTDQBBACEEIABBADoANAsgA0EQaiQAIAQLVwICfgF/AkACQCAALQAARQ0AIAApAxAiAUJ7Vg0AIAFCBHwiAiAAKQMIWA0BCyAAQQA6AABBAA8LIAAoAgQiA0UEQEEADwsgACACNwMQIAMgAadqKAAAC1UCAX4BfyAABEACQCAAKQMIUA0AQgEhAQNAIAAoAgAgAkEEdGoQPiABIAApAwhaDQEgAachAiABQgF8IQEMAAsACyAAKAIAEAYgACgCKBAQIAAQBgsLZAECfwJAAkACQCAARQRAIAGnEAkiA0UNAkEYEAkiAkUNAQwDCyAAIQNBGBAJIgINAkEADwsgAxAGC0EADwsgAkIANwMQIAIgATcDCCACIAM2AgQgAkEBOgAAIAIgAEU6AAEgAgudAQICfgF/AkACQCAALQAARQ0AIAApAxAiAkJ3Vg0AIAJCCHwiAyAAKQMIWA0BCyAAQQA6AAAPCyAAKAIEIgRFBEAPCyAAIAM3AxAgBCACp2oiACABQjiIPAAHIAAgAUIwiDwABiAAIAFCKIg8AAUgACABQiCIPAAEIAAgAUIYiDwAAyAAIAFCEIg8AAIgACABQgiIPAABIAAgATwAAAvwAgICfwF+AkAgAkUNACAAIAJqIgNBAWsgAToAACAAIAE6AAAgAkEDSQ0AIANBAmsgAToAACAAIAE6AAEgA0EDayABOgAAIAAgAToAAiACQQdJDQAgA0EEayABOgAAIAAgAToAAyACQQlJDQAgAEEAIABrQQNxIgRqIgMgAUH/AXFBgYKECGwiADYCACADIAIgBGtBfHEiAmoiAUEEayAANgIAIAJBCUkNACADIAA2AgggAyAANgIEIAFBCGsgADYCACABQQxrIAA2AgAgAkEZSQ0AIAMgADYCGCADIAA2AhQgAyAANgIQIAMgADYCDCABQRBrIAA2AgAgAUEUayAANgIAIAFBGGsgADYCACABQRxrIAA2AgAgAiADQQRxQRhyIgFrIgJBIEkNACAArUKBgICAEH4hBSABIANqIQEDQCABIAU3AxggASAFNwMQIAEgBTcDCCABIAU3AwAgAUEgaiEBIAJBIGsiAkEfSw0ACwsLbwEDfyAAQQxqIQICQAJ/IAAoAiAiAUUEQEF/IQFBEgwBCyAAIAFBAWsiAzYCIEEAIQEgAw0BIABBAEIAQQIQDhogACgCACIARQ0BIAAQGkF/Sg0BQRQLIQAgAgRAIAJBADYCBCACIAA2AgALCyABC58BAgF/AX4CfwJAAn4gACgCACIDKAIkQQFGQQAgAkJ/VRtFBEAgA0EMaiIBBEAgAUEANgIEIAFBEjYCAAtCfwwBCyADIAEgAkELEA4LIgRCf1cEQCAAKAIAIQEgAEEIaiIABEAgACABKAIMNgIAIAAgASgCEDYCBAsMAQtBACACIARRDQEaIABBCGoEQCAAQRs2AgwgAEEGNgIICwtBfwsLJAEBfyAABEADQCAAKAIAIQEgACgCDBAGIAAQBiABIgANAAsLC5gBAgJ+AX8CQAJAIAAtAABFDQAgACkDECIBQndWDQAgAUIIfCICIAApAwhYDQELIABBADoAAEIADwsgACgCBCIDRQRAQgAPCyAAIAI3AxAgAyABp2oiADEABkIwhiAAMQAHQjiGhCAAMQAFQiiGhCAAMQAEQiCGhCAAMQADQhiGhCAAMQACQhCGhCAAMQABQgiGhCAAMQAAfAsjACAAQShGBEAgAhAGDwsgAgRAIAEgAkEEaygCACAAEQcACwsyACAAKAIkQQFHBEAgAEEMaiIABEAgAEEANgIEIABBEjYCAAtCfw8LIABBAEIAQQ0QDgsPACAABEAgABA2IAAQBgsLgAEBAX8gAC0AKAR/QX8FIAFFBEAgAEEMagRAIABBADYCECAAQRI2AgwLQX8PCyABECoCQCAAKAIAIgJFDQAgAiABECFBf0oNACAAKAIAIQEgAEEMaiIABEAgACABKAIMNgIAIAAgASgCEDYCBAtBfw8LIAAgAUI4QQMQDkI/h6cLC38BA38gACEBAkAgAEEDcQRAA0AgAS0AAEUNAiABQQFqIgFBA3ENAAsLA0AgASICQQRqIQEgAigCACIDQX9zIANBgYKECGtxQYCBgoR4cUUNAAsgA0H/AXFFBEAgAiAAaw8LA0AgAi0AASEDIAJBAWoiASECIAMNAAsLIAEgAGsL3wIBCH8gAEUEQEEBDwsCQCAAKAIIIgINAEEBIQQgAC8BBCIHRQRAQQEhAgwBCyAAKAIAIQgDQAJAIAMgCGoiBS0AACICQSBPBEAgAkEYdEEYdUF/Sg0BCyACQQ1NQQBBASACdEGAzABxGw0AAn8CfyACQeABcUHAAUYEQEEBIQYgA0EBagwBCyACQfABcUHgAUYEQCADQQJqIQNBACEGQQEMAgsgAkH4AXFB8AFHBEBBBCECDAULQQAhBiADQQNqCyEDQQALIQlBBCECIAMgB08NAiAFLQABQcABcUGAAUcNAkEDIQQgBg0AIAUtAAJBwAFxQYABRw0CIAkNACAFLQADQcABcUGAAUcNAgsgBCECIANBAWoiAyAHSQ0ACwsgACACNgIIAn8CQCABRQ0AAkAgAUECRw0AIAJBA0cNAEECIQIgAEECNgIICyABIAJGDQBBBSACQQFHDQEaCyACCwtIAgJ+An8jAEEQayIEIAE2AgxCASAArYYhAgNAIAQgAUEEaiIANgIMIAIiA0IBIAEoAgAiBa2GhCECIAAhASAFQX9KDQALIAMLhwUBB38CQAJAIABFBEBBxRQhAiABRQ0BIAFBADYCAEHFFA8LIAJBwABxDQEgACgCCEUEQCAAQQAQIxoLIAAoAgghBAJAIAJBgAFxBEAgBEEBa0ECTw0BDAMLIARBBEcNAgsCQCAAKAIMIgINACAAAn8gACgCACEIIABBEGohCUEAIQICQAJAAkACQCAALwEEIgUEQEEBIQQgBUEBcSEHIAVBAUcNAQwCCyAJRQ0CIAlBADYCAEEADAQLIAVBfnEhBgNAIARBAUECQQMgAiAIai0AAEEBdEHQFGovAQAiCkGAEEkbIApBgAFJG2pBAUECQQMgCCACQQFyai0AAEEBdEHQFGovAQAiBEGAEEkbIARBgAFJG2ohBCACQQJqIQIgBkECayIGDQALCwJ/IAcEQCAEQQFBAkEDIAIgCGotAABBAXRB0BRqLwEAIgJBgBBJGyACQYABSRtqIQQLIAQLEAkiB0UNASAFQQEgBUEBSxshCkEAIQVBACEGA0AgBSAHaiEDAn8gBiAIai0AAEEBdEHQFGovAQAiAkH/AE0EQCADIAI6AAAgBUEBagwBCyACQf8PTQRAIAMgAkE/cUGAAXI6AAEgAyACQQZ2QcABcjoAACAFQQJqDAELIAMgAkE/cUGAAXI6AAIgAyACQQx2QeABcjoAACADIAJBBnZBP3FBgAFyOgABIAVBA2oLIQUgBkEBaiIGIApHDQALIAcgBEEBayICakEAOgAAIAlFDQAgCSACNgIACyAHDAELIAMEQCADQQA2AgQgA0EONgIAC0EACyICNgIMIAINAEEADwsgAUUNACABIAAoAhA2AgALIAIPCyABBEAgASAALwEENgIACyAAKAIAC4MBAQR/QRIhBQJAAkAgACkDMCABWA0AIAGnIQYgACgCQCEEIAJBCHEiB0UEQCAEIAZBBHRqKAIEIgINAgsgBCAGQQR0aiIEKAIAIgJFDQAgBC0ADEUNAUEXIQUgBw0BC0EAIQIgAyAAQQhqIAMbIgAEQCAAQQA2AgQgACAFNgIACwsgAgtuAQF/IwBBgAJrIgUkAAJAIARBgMAEcQ0AIAIgA0wNACAFIAFB/wFxIAIgA2siAkGAAiACQYACSSIBGxAZIAFFBEADQCAAIAVBgAIQLiACQYACayICQf8BSw0ACwsgACAFIAIQLgsgBUGAAmokAAuBAQEBfyMAQRBrIgQkACACIANsIQICQCAAQSdGBEAgBEEMaiACEIwBIQBBACAEKAIMIAAbIQAMAQsgAUEBIAJBxABqIAARAAAiAUUEQEEAIQAMAQtBwAAgAUE/cWsiACABakHAAEEAIABBBEkbaiIAQQRrIAE2AAALIARBEGokACAAC1IBAn9BhIEBKAIAIgEgAEEDakF8cSICaiEAAkAgAkEAIAAgAU0bDQAgAD8AQRB0SwRAIAAQA0UNAQtBhIEBIAA2AgAgAQ8LQYSEAUEwNgIAQX8LNwAgAEJ/NwMQIABBADYCCCAAQgA3AwAgAEEANgIwIABC/////w83AyggAEIANwMYIABCADcDIAulAQEBf0HYABAJIgFFBEBBAA8LAkAgAARAIAEgAEHYABAHGgwBCyABQgA3AyAgAUEANgIYIAFC/////w83AxAgAUEAOwEMIAFBv4YoNgIIIAFBAToABiABQQA6AAQgAUIANwNIIAFBgIDYjXg2AkQgAUIANwMoIAFCADcDMCABQgA3AzggAUFAa0EAOwEAIAFCADcDUAsgAUEBOgAFIAFBADYCACABC1gCAn4BfwJAAkAgAC0AAEUNACAAKQMQIgMgAq18IgQgA1QNACAEIAApAwhYDQELIABBADoAAA8LIAAoAgQiBUUEQA8LIAAgBDcDECAFIAOnaiABIAIQBxoLlgEBAn8CQAJAIAJFBEAgAacQCSIFRQ0BQRgQCSIEDQIgBRAGDAELIAIhBUEYEAkiBA0BCyADBEAgA0EANgIEIANBDjYCAAtBAA8LIARCADcDECAEIAE3AwggBCAFNgIEIARBAToAACAEIAJFOgABIAAgBSABIAMQZUEASAR/IAQtAAEEQCAEKAIEEAYLIAQQBkEABSAECwubAgEDfyAALQAAQSBxRQRAAkAgASEDAkAgAiAAIgEoAhAiAAR/IAAFAn8gASABLQBKIgBBAWsgAHI6AEogASgCACIAQQhxBEAgASAAQSByNgIAQX8MAQsgAUIANwIEIAEgASgCLCIANgIcIAEgADYCFCABIAAgASgCMGo2AhBBAAsNASABKAIQCyABKAIUIgVrSwRAIAEgAyACIAEoAiQRAAAaDAILAn8gASwAS0F/SgRAIAIhAANAIAIgACIERQ0CGiADIARBAWsiAGotAABBCkcNAAsgASADIAQgASgCJBEAACAESQ0CIAMgBGohAyABKAIUIQUgAiAEawwBCyACCyEAIAUgAyAAEAcaIAEgASgCFCAAajYCFAsLCwvNBQEGfyAAKAIwIgNBhgJrIQYgACgCPCECIAMhAQNAIAAoAkQgAiAAKAJoIgRqayECIAEgBmogBE0EQCAAKAJIIgEgASADaiADEAcaAkAgAyAAKAJsIgFNBEAgACABIANrNgJsDAELIABCADcCbAsgACAAKAJoIANrIgE2AmggACAAKAJYIANrNgJYIAEgACgChC5JBEAgACABNgKELgsgAEH8gAEoAgARAwAgAiADaiECCwJAIAAoAgAiASgCBCIERQ0AIAAoAjwhBSAAIAIgBCACIARJGyICBH8gACgCSCAAKAJoaiAFaiEFIAEgBCACazYCBAJAAkACQAJAIAEoAhwiBCgCFEEBaw4CAQACCyAEQaABaiAFIAEoAgAgAkHcgAEoAgARCAAMAgsgASABKAIwIAUgASgCACACQcSAASgCABEEADYCMAwBCyAFIAEoAgAgAhAHGgsgASABKAIAIAJqNgIAIAEgASgCCCACajYCCCAAKAI8BSAFCyACaiICNgI8AkAgACgChC4iASACakEDSQ0AIAAoAmggAWshAQJAIAAoAnRBgQhPBEAgACAAIAAoAkggAWoiAi0AACACLQABIAAoAnwRAAA2AlQMAQsgAUUNACAAIAFBAWsgACgChAERAgAaCyAAKAKELiAAKAI8IgJBAUZrIgRFDQAgACABIAQgACgCgAERBQAgACAAKAKELiAEazYChC4gACgCPCECCyACQYUCSw0AIAAoAgAoAgRFDQAgACgCMCEBDAELCwJAIAAoAkQiAiAAKAJAIgNNDQAgAAJ/IAAoAjwgACgCaGoiASADSwRAIAAoAkggAWpBACACIAFrIgNBggIgA0GCAkkbIgMQGSABIANqDAELIAFBggJqIgEgA00NASAAKAJIIANqQQAgAiADayICIAEgA2siAyACIANJGyIDEBkgACgCQCADags2AkALC50CAQF/AkAgAAJ/IAAoAqAuIgFBwABGBEAgACgCBCAAKAIQaiAAKQOYLjcAACAAQgA3A5guIAAgACgCEEEIajYCEEEADAELIAFBIE4EQCAAKAIEIAAoAhBqIAApA5guPgAAIAAgAEGcLmo1AgA3A5guIAAgACgCEEEEajYCECAAIAAoAqAuQSBrIgE2AqAuCyABQRBOBEAgACgCBCAAKAIQaiAAKQOYLj0AACAAIAAoAhBBAmo2AhAgACAAKQOYLkIQiDcDmC4gACAAKAKgLkEQayIBNgKgLgsgAUEISA0BIAAgACgCECIBQQFqNgIQIAEgACgCBGogACkDmC48AAAgACAAKQOYLkIIiDcDmC4gACgCoC5BCGsLNgKgLgsLEAAgACgCCBAGIABBADYCCAvwAQECf0F/IQECQCAALQAoDQAgACgCJEEDRgRAIABBDGoEQCAAQQA2AhAgAEEXNgIMC0F/DwsCQCAAKAIgBEAgACkDGELAAINCAFINASAAQQxqBEAgAEEANgIQIABBHTYCDAtBfw8LAkAgACgCACICRQ0AIAIQMkF/Sg0AIAAoAgAhASAAQQxqIgAEQCAAIAEoAgw2AgAgACABKAIQNgIEC0F/DwsgAEEAQgBBABAOQn9VDQAgACgCACIARQ0BIAAQGhpBfw8LQQAhASAAQQA7ATQgAEEMagRAIABCADcCDAsgACAAKAIgQQFqNgIgCyABCzsAIAAtACgEfkJ/BSAAKAIgRQRAIABBDGoiAARAIABBADYCBCAAQRI2AgALQn8PCyAAQQBCAEEHEA4LC5oIAQt/IABFBEAgARAJDwsgAUFATwRAQYSEAUEwNgIAQQAPCwJ/QRAgAUELakF4cSABQQtJGyEGIABBCGsiBSgCBCIJQXhxIQQCQCAJQQNxRQRAQQAgBkGAAkkNAhogBkEEaiAETQRAIAUhAiAEIAZrQZSIASgCAEEBdE0NAgtBAAwCCyAEIAVqIQcCQCAEIAZPBEAgBCAGayIDQRBJDQEgBSAJQQFxIAZyQQJyNgIEIAUgBmoiAiADQQNyNgIEIAcgBygCBEEBcjYCBCACIAMQOwwBCyAHQcyEASgCAEYEQEHAhAEoAgAgBGoiBCAGTQ0CIAUgCUEBcSAGckECcjYCBCAFIAZqIgMgBCAGayICQQFyNgIEQcCEASACNgIAQcyEASADNgIADAELIAdByIQBKAIARgRAQbyEASgCACAEaiIDIAZJDQICQCADIAZrIgJBEE8EQCAFIAlBAXEgBnJBAnI2AgQgBSAGaiIEIAJBAXI2AgQgAyAFaiIDIAI2AgAgAyADKAIEQX5xNgIEDAELIAUgCUEBcSADckECcjYCBCADIAVqIgIgAigCBEEBcjYCBEEAIQJBACEEC0HIhAEgBDYCAEG8hAEgAjYCAAwBCyAHKAIEIgNBAnENASADQXhxIARqIgogBkkNASAKIAZrIQwCQCADQf8BTQRAIAcoAggiBCADQQN2IgJBA3RB3IQBakYaIAQgBygCDCIDRgRAQbSEAUG0hAEoAgBBfiACd3E2AgAMAgsgBCADNgIMIAMgBDYCCAwBCyAHKAIYIQsCQCAHIAcoAgwiCEcEQCAHKAIIIgJBxIQBKAIASRogAiAINgIMIAggAjYCCAwBCwJAIAdBFGoiBCgCACICDQAgB0EQaiIEKAIAIgINAEEAIQgMAQsDQCAEIQMgAiIIQRRqIgQoAgAiAg0AIAhBEGohBCAIKAIQIgINAAsgA0EANgIACyALRQ0AAkAgByAHKAIcIgNBAnRB5IYBaiICKAIARgRAIAIgCDYCACAIDQFBuIQBQbiEASgCAEF+IAN3cTYCAAwCCyALQRBBFCALKAIQIAdGG2ogCDYCACAIRQ0BCyAIIAs2AhggBygCECICBEAgCCACNgIQIAIgCDYCGAsgBygCFCICRQ0AIAggAjYCFCACIAg2AhgLIAxBD00EQCAFIAlBAXEgCnJBAnI2AgQgBSAKaiICIAIoAgRBAXI2AgQMAQsgBSAJQQFxIAZyQQJyNgIEIAUgBmoiAyAMQQNyNgIEIAUgCmoiAiACKAIEQQFyNgIEIAMgDBA7CyAFIQILIAILIgIEQCACQQhqDwsgARAJIgVFBEBBAA8LIAUgAEF8QXggAEEEaygCACICQQNxGyACQXhxaiICIAEgASACSxsQBxogABAGIAUL6QEBA38CQCABRQ0AIAJBgDBxIgIEfwJ/IAJBgCBHBEBBAiACQYAQRg0BGiADBEAgA0EANgIEIANBEjYCAAtBAA8LQQQLIQJBAAVBAQshBkEUEAkiBEUEQCADBEAgA0EANgIEIANBDjYCAAtBAA8LIAQgAUEBahAJIgU2AgAgBUUEQCAEEAZBAA8LIAUgACABEAcgAWpBADoAACAEQQA2AhAgBEIANwMIIAQgATsBBCAGDQAgBCACECNBBUcNACAEKAIAEAYgBCgCDBAGIAQQBkEAIQQgAwRAIANBADYCBCADQRI2AgALCyAEC7UBAQJ/AkACQAJAAkACQAJAAkAgAC0ABQRAIAAtAABBAnFFDQELIAAoAjAQECAAQQA2AjAgAC0ABUUNAQsgAC0AAEEIcUUNAQsgACgCNBAcIABBADYCNCAALQAFRQ0BCyAALQAAQQRxRQ0BCyAAKAI4EBAgAEEANgI4IAAtAAVFDQELIAAtAABBgAFxRQ0BCyAAKAJUIgEEfyABQQAgARAiEBkgACgCVAVBAAsQBiAAQQA2AlQLC9wMAgl/AX4jAEFAaiIGJAACQAJAAkACQAJAIAEoAjBBABAjIgVBAkZBACABKAI4QQAQIyIEQQFGGw0AIAVBAUZBACAEQQJGGw0AIAVBAkciAw0BIARBAkcNAQsgASABLwEMQYAQcjsBDEEAIQMMAQsgASABLwEMQf/vA3E7AQxBACEFIANFBEBB9eABIAEoAjAgAEEIahBpIgVFDQILIAJBgAJxBEAgBSEDDAELIARBAkcEQCAFIQMMAQtB9cYBIAEoAjggAEEIahBpIgNFBEAgBRAcDAILIAMgBTYCAAsgASABLwEMQf7/A3EgAS8BUiIFQQBHcjsBDAJAAkACQAJAAn8CQAJAIAEpAyhC/v///w9WDQAgASkDIEL+////D1YNACACQYAEcUUNASABKQNIQv////8PVA0BCyAFQYECa0H//wNxQQNJIQdBAQwBCyAFQYECa0H//wNxIQQgAkGACnFBgApHDQEgBEEDSSEHQQALIQkgBkIcEBciBEUEQCAAQQhqIgAEQCAAQQA2AgQgAEEONgIACyADEBwMBQsgAkGACHEhBQJAAkAgAkGAAnEEQAJAIAUNACABKQMgQv////8PVg0AIAEpAyhCgICAgBBUDQMLIAQgASkDKBAYIAEpAyAhDAwBCwJAAkACQCAFDQAgASkDIEL/////D1YNACABKQMoIgxC/////w9WDQEgASkDSEKAgICAEFQNBAsgASkDKCIMQv////8PVA0BCyAEIAwQGAsgASkDICIMQv////8PWgRAIAQgDBAYCyABKQNIIgxC/////w9UDQELIAQgDBAYCyAELQAARQRAIABBCGoiAARAIABBADYCBCAAQRQ2AgALIAQQCCADEBwMBQtBASEKQQEgBC0AAAR+IAQpAxAFQgALp0H//wNxIAYQRyEFIAQQCCAFIAM2AgAgBw0BDAILIAMhBSAEQQJLDQELIAZCBxAXIgRFBEAgAEEIaiIABEAgAEEANgIEIABBDjYCAAsgBRAcDAMLIARBAhANIARBhxJBAhAsIAQgAS0AUhBwIAQgAS8BEBANIAQtAABFBEAgAEEIaiIABEAgAEEANgIEIABBFDYCAAsgBBAIDAILQYGyAkEHIAYQRyEDIAQQCCADIAU2AgBBASELIAMhBQsgBkIuEBciA0UEQCAAQQhqIgAEQCAAQQA2AgQgAEEONgIACyAFEBwMAgsgA0GjEkGoEiACQYACcSIHG0EEECwgB0UEQCADIAkEf0EtBSABLwEIC0H//wNxEA0LIAMgCQR/QS0FIAEvAQoLQf//A3EQDSADIAEvAQwQDSADIAsEf0HjAAUgASgCEAtB//8DcRANIAYgASgCFDYCPAJ/IAZBPGoQjQEiCEUEQEEAIQlBIQwBCwJ/IAgoAhQiBEHQAE4EQCAEQQl0DAELIAhB0AA2AhRBgMACCyEEIAgoAgRBBXQgCCgCCEELdGogCCgCAEEBdmohCSAIKAIMIAQgCCgCEEEFdGpqQaDAAWoLIQQgAyAJQf//A3EQDSADIARB//8DcRANIAMCfyALBEBBACABKQMoQhRUDQEaCyABKAIYCxASIAEpAyAhDCADAn8gAwJ/AkAgBwRAIAxC/v///w9YBEAgASkDKEL/////D1QNAgsgA0F/EBJBfwwDC0F/IAxC/v///w9WDQEaCyAMpwsQEiABKQMoIgxC/////w8gDEL/////D1QbpwsQEiADIAEoAjAiBAR/IAQvAQQFQQALQf//A3EQDSADIAEoAjQgAhBsIAVBgAYQbGpB//8DcRANIAdFBEAgAyABKAI4IgQEfyAELwEEBUEAC0H//wNxEA0gAyABLwE8EA0gAyABLwFAEA0gAyABKAJEEBIgAyABKQNIIgxC/////w8gDEL/////D1QbpxASCyADLQAARQRAIABBCGoiAARAIABBADYCBCAAQRQ2AgALIAMQCCAFEBwMAgsgACAGIAMtAAAEfiADKQMQBUIACxAbIQQgAxAIIARBf0wNACABKAIwIgMEQCAAIAMQYUF/TA0BCyAFBEAgACAFQYAGEGtBf0wNAQsgBRAcIAEoAjQiBQRAIAAgBSACEGtBAEgNAgsgBw0CIAEoAjgiAUUNAiAAIAEQYUEATg0CDAELIAUQHAtBfyEKCyAGQUBrJAAgCgtNAQJ/IAEtAAAhAgJAIAAtAAAiA0UNACACIANHDQADQCABLQABIQIgAC0AASIDRQ0BIAFBAWohASAAQQFqIQAgAiADRg0ACwsgAyACawvcAwICfgF/IAOtIQQgACkDmC4hBQJAIAACfyAAAn4gACgCoC4iBkEDaiIDQT9NBEAgBCAGrYYgBYQMAQsgBkHAAEYEQCAAKAIEIAAoAhBqIAU3AAAgACgCEEEIagwCCyAAKAIEIAAoAhBqIAQgBq2GIAWENwAAIAAgACgCEEEIajYCECAGQT1rIQMgBEHAACAGa62ICyIENwOYLiAAIAM2AqAuIANBOU4EQCAAKAIEIAAoAhBqIAQ3AAAgACAAKAIQQQhqNgIQDAILIANBGU4EQCAAKAIEIAAoAhBqIAQ+AAAgACAAKAIQQQRqNgIQIAAgACkDmC5CIIgiBDcDmC4gACAAKAKgLkEgayIDNgKgLgsgA0EJTgR/IAAoAgQgACgCEGogBD0AACAAIAAoAhBBAmo2AhAgACkDmC5CEIghBCAAKAKgLkEQawUgAwtBAUgNASAAKAIQCyIDQQFqNgIQIAAoAgQgA2ogBDwAAAsgAEEANgKgLiAAQgA3A5guIAAoAgQgACgCEGogAjsAACAAIAAoAhBBAmoiAzYCECAAKAIEIANqIAJBf3M7AAAgACAAKAIQQQJqIgM2AhAgAgRAIAAoAgQgA2ogASACEAcaIAAgACgCECACajYCEAsLrAQCAX8BfgJAIAANACABUA0AIAMEQCADQQA2AgQgA0ESNgIAC0EADwsCQAJAIAAgASACIAMQiQEiBEUNAEEYEAkiAkUEQCADBEAgA0EANgIEIANBDjYCAAsCQCAEKAIoIgBFBEAgBCkDGCEBDAELIABBADYCKCAEKAIoQgA3AyAgBCAEKQMYIgUgBCkDICIBIAEgBVQbIgE3AxgLIAQpAwggAVYEQANAIAQoAgAgAadBBHRqKAIAEAYgAUIBfCIBIAQpAwhUDQALCyAEKAIAEAYgBCgCBBAGIAQQBgwBCyACQQA2AhQgAiAENgIQIAJBABABNgIMIAJBADYCCCACQgA3AgACf0E4EAkiAEUEQCADBEAgA0EANgIEIANBDjYCAAtBAAwBCyAAQQA2AgggAEIANwMAIABCADcDICAAQoCAgIAQNwIsIABBADoAKCAAQQA2AhQgAEIANwIMIABBADsBNCAAIAI2AgggAEEkNgIEIABCPyACQQBCAEEOQSQRDAAiASABQgBTGzcDGCAACyIADQEgAigCECIDBEACQCADKAIoIgBFBEAgAykDGCEBDAELIABBADYCKCADKAIoQgA3AyAgAyADKQMYIgUgAykDICIBIAEgBVQbIgE3AxgLIAMpAwggAVYEQANAIAMoAgAgAadBBHRqKAIAEAYgAUIBfCIBIAMpAwhUDQALCyADKAIAEAYgAygCBBAGIAMQBgsgAhAGC0EAIQALIAALiwwBBn8gACABaiEFAkACQCAAKAIEIgJBAXENACACQQNxRQ0BIAAoAgAiAiABaiEBAkAgACACayIAQciEASgCAEcEQCACQf8BTQRAIAAoAggiBCACQQN2IgJBA3RB3IQBakYaIAAoAgwiAyAERw0CQbSEAUG0hAEoAgBBfiACd3E2AgAMAwsgACgCGCEGAkAgACAAKAIMIgNHBEAgACgCCCICQcSEASgCAEkaIAIgAzYCDCADIAI2AggMAQsCQCAAQRRqIgIoAgAiBA0AIABBEGoiAigCACIEDQBBACEDDAELA0AgAiEHIAQiA0EUaiICKAIAIgQNACADQRBqIQIgAygCECIEDQALIAdBADYCAAsgBkUNAgJAIAAgACgCHCIEQQJ0QeSGAWoiAigCAEYEQCACIAM2AgAgAw0BQbiEAUG4hAEoAgBBfiAEd3E2AgAMBAsgBkEQQRQgBigCECAARhtqIAM2AgAgA0UNAwsgAyAGNgIYIAAoAhAiAgRAIAMgAjYCECACIAM2AhgLIAAoAhQiAkUNAiADIAI2AhQgAiADNgIYDAILIAUoAgQiAkEDcUEDRw0BQbyEASABNgIAIAUgAkF+cTYCBCAAIAFBAXI2AgQgBSABNgIADwsgBCADNgIMIAMgBDYCCAsCQCAFKAIEIgJBAnFFBEAgBUHMhAEoAgBGBEBBzIQBIAA2AgBBwIQBQcCEASgCACABaiIBNgIAIAAgAUEBcjYCBCAAQciEASgCAEcNA0G8hAFBADYCAEHIhAFBADYCAA8LIAVByIQBKAIARgRAQciEASAANgIAQbyEAUG8hAEoAgAgAWoiATYCACAAIAFBAXI2AgQgACABaiABNgIADwsgAkF4cSABaiEBAkAgAkH/AU0EQCAFKAIIIgQgAkEDdiICQQN0QdyEAWpGGiAEIAUoAgwiA0YEQEG0hAFBtIQBKAIAQX4gAndxNgIADAILIAQgAzYCDCADIAQ2AggMAQsgBSgCGCEGAkAgBSAFKAIMIgNHBEAgBSgCCCICQcSEASgCAEkaIAIgAzYCDCADIAI2AggMAQsCQCAFQRRqIgQoAgAiAg0AIAVBEGoiBCgCACICDQBBACEDDAELA0AgBCEHIAIiA0EUaiIEKAIAIgINACADQRBqIQQgAygCECICDQALIAdBADYCAAsgBkUNAAJAIAUgBSgCHCIEQQJ0QeSGAWoiAigCAEYEQCACIAM2AgAgAw0BQbiEAUG4hAEoAgBBfiAEd3E2AgAMAgsgBkEQQRQgBigCECAFRhtqIAM2AgAgA0UNAQsgAyAGNgIYIAUoAhAiAgRAIAMgAjYCECACIAM2AhgLIAUoAhQiAkUNACADIAI2AhQgAiADNgIYCyAAIAFBAXI2AgQgACABaiABNgIAIABByIQBKAIARw0BQbyEASABNgIADwsgBSACQX5xNgIEIAAgAUEBcjYCBCAAIAFqIAE2AgALIAFB/wFNBEAgAUEDdiICQQN0QdyEAWohAQJ/QbSEASgCACIDQQEgAnQiAnFFBEBBtIQBIAIgA3I2AgAgAQwBCyABKAIICyECIAEgADYCCCACIAA2AgwgACABNgIMIAAgAjYCCA8LQR8hAiAAQgA3AhAgAUH///8HTQRAIAFBCHYiAiACQYD+P2pBEHZBCHEiBHQiAiACQYDgH2pBEHZBBHEiA3QiAiACQYCAD2pBEHZBAnEiAnRBD3YgAyAEciACcmsiAkEBdCABIAJBFWp2QQFxckEcaiECCyAAIAI2AhwgAkECdEHkhgFqIQcCQAJAQbiEASgCACIEQQEgAnQiA3FFBEBBuIQBIAMgBHI2AgAgByAANgIAIAAgBzYCGAwBCyABQQBBGSACQQF2ayACQR9GG3QhAiAHKAIAIQMDQCADIgQoAgRBeHEgAUYNAiACQR12IQMgAkEBdCECIAQgA0EEcWoiB0EQaigCACIDDQALIAcgADYCECAAIAQ2AhgLIAAgADYCDCAAIAA2AggPCyAEKAIIIgEgADYCDCAEIAA2AgggAEEANgIYIAAgBDYCDCAAIAE2AggLC1gCAX8BfgJAAn9BACAARQ0AGiAArUIChiICpyIBIABBBHJBgIAESQ0AGkF/IAEgAkIgiKcbCyIBEAkiAEUNACAAQQRrLQAAQQNxRQ0AIABBACABEBkLIAALQwEDfwJAIAJFDQADQCAALQAAIgQgAS0AACIFRgRAIAFBAWohASAAQQFqIQAgAkEBayICDQEMAgsLIAQgBWshAwsgAwsUACAAEEAgACgCABAgIAAoAgQQIAutBAIBfgV/IwBBEGsiBCQAIAAgAWshBgJAAkAgAUEBRgRAIAAgBi0AACACEBkMAQsgAUEJTwRAIAAgBikAADcAACAAIAJBAWtBB3FBAWoiBWohACACIAVrIgFFDQIgBSAGaiECA0AgACACKQAANwAAIAJBCGohAiAAQQhqIQAgAUEIayIBDQALDAILAkACQAJAAkAgAUEEaw4FAAICAgECCyAEIAYoAAAiATYCBCAEIAE2AgAMAgsgBCAGKQAANwMADAELQQghByAEQQhqIQgDQCAIIAYgByABIAEgB0sbIgUQByAFaiEIIAcgBWsiBw0ACyAEIAQpAwg3AwALAkAgBQ0AIAJBEEkNACAEKQMAIQMgAkEQayIGQQR2QQFqQQdxIgEEQANAIAAgAzcACCAAIAM3AAAgAkEQayECIABBEGohACABQQFrIgENAAsLIAZB8ABJDQADQCAAIAM3AHggACADNwBwIAAgAzcAaCAAIAM3AGAgACADNwBYIAAgAzcAUCAAIAM3AEggACADNwBAIAAgAzcAOCAAIAM3ADAgACADNwAoIAAgAzcAICAAIAM3ABggACADNwAQIAAgAzcACCAAIAM3AAAgAEGAAWohACACQYABayICQQ9LDQALCyACQQhPBEBBCCAFayEBA0AgACAEKQMANwAAIAAgAWohACACIAFrIgJBB0sNAAsLIAJFDQEgACAEIAIQBxoLIAAgAmohAAsgBEEQaiQAIAALXwECfyAAKAIIIgEEQCABEAsgAEEANgIICwJAIAAoAgQiAUUNACABKAIAIgJBAXFFDQAgASgCEEF+Rw0AIAEgAkF+cSICNgIAIAINACABECAgAEEANgIECyAAQQA6AAwL1wICBH8BfgJAAkAgACgCQCABp0EEdGooAgAiA0UEQCACBEAgAkEANgIEIAJBFDYCAAsMAQsgACgCACADKQNIIgdBABAUIQMgACgCACEAIANBf0wEQCACBEAgAiAAKAIMNgIAIAIgACgCEDYCBAsMAQtCACEBIwBBEGsiBiQAQX8hAwJAIABCGkEBEBRBf0wEQCACBEAgAiAAKAIMNgIAIAIgACgCEDYCBAsMAQsgAEIEIAZBCmogAhAtIgRFDQBBHiEAQQEhBQNAIAQQDCAAaiEAIAVBAkcEQCAFQQFqIQUMAQsLIAQtAAAEfyAEKQMQIAQpAwhRBUEAC0UEQCACBEAgAkEANgIEIAJBFDYCAAsgBBAIDAELIAQQCCAAIQMLIAZBEGokACADIgBBAEgNASAHIACtfCIBQn9VDQEgAgRAIAJBFjYCBCACQQQ2AgALC0IAIQELIAELYAIBfgF/AkAgAEUNACAAQQhqEF8iAEUNACABIAEoAjBBAWo2AjAgACADNgIIIAAgAjYCBCAAIAE2AgAgAEI/IAEgA0EAQgBBDiACEQoAIgQgBEIAUxs3AxggACEFCyAFCyIAIAAoAiRBAWtBAU0EQCAAQQBCAEEKEA4aIABBADYCJAsLbgACQAJAAkAgA0IQVA0AIAJFDQECfgJAAkACQCACKAIIDgMCAAEECyACKQMAIAB8DAILIAIpAwAgAXwMAQsgAikDAAsiA0IAUw0AIAEgA1oNAgsgBARAIARBADYCBCAEQRI2AgALC0J/IQMLIAMLggICAX8CfgJAQQEgAiADGwRAIAIgA2oQCSIFRQRAIAQEQCAEQQA2AgQgBEEONgIAC0EADwsgAq0hBgJAAkAgAARAIAAgBhATIgBFBEAgBARAIARBADYCBCAEQQ42AgALDAULIAUgACACEAcaIAMNAQwCCyABIAUgBhARIgdCf1cEQCAEBEAgBCABKAIMNgIAIAQgASgCEDYCBAsMBAsgBiAHVQRAIAQEQCAEQQA2AgQgBEERNgIACwwECyADRQ0BCyACIAVqIgBBADoAACACQQFIDQAgBSECA0AgAi0AAEUEQCACQSA6AAALIAJBAWoiAiAASQ0ACwsLIAUPCyAFEAZBAAuBAQEBfwJAIAAEQCADQYAGcSEFQQAhAwNAAkAgAC8BCCACRw0AIAUgACgCBHFFDQAgA0EATg0DIANBAWohAwsgACgCACIADQALCyAEBEAgBEEANgIEIARBCTYCAAtBAA8LIAEEQCABIAAvAQo7AQALIAAvAQpFBEBBwBQPCyAAKAIMC1cBAX9BEBAJIgNFBEBBAA8LIAMgATsBCiADIAA7AQggA0GABjYCBCADQQA2AgACQCABBEAgAyACIAEQYyIANgIMIAANASADEAZBAA8LIANBADYCDAsgAwvuBQIEfwV+IwBB4ABrIgQkACAEQQhqIgNCADcDICADQQA2AhggA0L/////DzcDECADQQA7AQwgA0G/hig2AgggA0EBOgAGIANBADsBBCADQQA2AgAgA0IANwNIIANBgIDYjXg2AkQgA0IANwMoIANCADcDMCADQgA3AzggA0FAa0EAOwEAIANCADcDUCABKQMIUCIDRQRAIAEoAgAoAgApA0ghBwsCfgJAIAMEQCAHIQkMAQsgByEJA0AgCqdBBHQiBSABKAIAaigCACIDKQNIIgggCSAIIAlUGyIJIAEpAyBWBEAgAgRAIAJBADYCBCACQRM2AgALQn8MAwsgAygCMCIGBH8gBi8BBAVBAAtB//8Dca0gCCADKQMgfHxCHnwiCCAHIAcgCFQbIgcgASkDIFYEQCACBEAgAkEANgIEIAJBEzYCAAtCfwwDCyAAKAIAIAEoAgAgBWooAgApA0hBABAUIQYgACgCACEDIAZBf0wEQCACBEAgAiADKAIMNgIAIAIgAygCEDYCBAtCfwwDCyAEQQhqIANBAEEBIAIQaEJ/UQRAIARBCGoQNkJ/DAMLAkACQCABKAIAIAVqKAIAIgMvAQogBC8BEkkNACADKAIQIAQoAhhHDQAgAygCFCAEKAIcRw0AIAMoAjAgBCgCOBBiRQ0AAkAgBCgCICIGIAMoAhhHBEAgBCkDKCEIDAELIAMpAyAiCyAEKQMoIghSDQAgCyEIIAMpAyggBCkDMFENAgsgBC0AFEEIcUUNACAGDQAgCEIAUg0AIAQpAzBQDQELIAIEQCACQQA2AgQgAkEVNgIACyAEQQhqEDZCfwwDCyABKAIAIAVqKAIAKAI0IAQoAjwQbyEDIAEoAgAgBWooAgAiBUEBOgAEIAUgAzYCNCAEQQA2AjwgBEEIahA2IApCAXwiCiABKQMIVA0ACwsgByAJfSIHQv///////////wAgB0L///////////8AVBsLIQcgBEHgAGokACAHC8YBAQJ/QdgAEAkiAUUEQCAABEAgAEEANgIEIABBDjYCAAtBAA8LIAECf0EYEAkiAkUEQCAABEAgAEEANgIEIABBDjYCAAtBAAwBCyACQQA2AhAgAkIANwMIIAJBADYCACACCyIANgJQIABFBEAgARAGQQAPCyABQgA3AwAgAUEANgIQIAFCADcCCCABQgA3AhQgAUEANgJUIAFCADcCHCABQgA3ACEgAUIANwMwIAFCADcDOCABQUBrQgA3AwAgAUIANwNIIAELgBMCD38CfiMAQdAAayIFJAAgBSABNgJMIAVBN2ohEyAFQThqIRBBACEBA0ACQCAOQQBIDQBB/////wcgDmsgAUgEQEGEhAFBPTYCAEF/IQ4MAQsgASAOaiEOCyAFKAJMIgchAQJAAkACQAJAAkACQAJAAkAgBQJ/AkAgBy0AACIGBEADQAJAAkAgBkH/AXEiBkUEQCABIQYMAQsgBkElRw0BIAEhBgNAIAEtAAFBJUcNASAFIAFBAmoiCDYCTCAGQQFqIQYgAS0AAiEMIAghASAMQSVGDQALCyAGIAdrIQEgAARAIAAgByABEC4LIAENDSAFKAJMIQEgBSgCTCwAAUEwa0EKTw0DIAEtAAJBJEcNAyABLAABQTBrIQ9BASERIAFBA2oMBAsgBSABQQFqIgg2AkwgAS0AASEGIAghAQwACwALIA4hDSAADQggEUUNAkEBIQEDQCAEIAFBAnRqKAIAIgAEQCADIAFBA3RqIAAgAhB4QQEhDSABQQFqIgFBCkcNAQwKCwtBASENIAFBCk8NCANAIAQgAUECdGooAgANCCABQQFqIgFBCkcNAAsMCAtBfyEPIAFBAWoLIgE2AkxBACEIAkAgASwAACIKQSBrIgZBH0sNAEEBIAZ0IgZBidEEcUUNAANAAkAgBSABQQFqIgg2AkwgASwAASIKQSBrIgFBIE8NAEEBIAF0IgFBidEEcUUNACABIAZyIQYgCCEBDAELCyAIIQEgBiEICwJAIApBKkYEQCAFAn8CQCABLAABQTBrQQpPDQAgBSgCTCIBLQACQSRHDQAgASwAAUECdCAEakHAAWtBCjYCACABLAABQQN0IANqQYADaygCACELQQEhESABQQNqDAELIBENCEEAIRFBACELIAAEQCACIAIoAgAiAUEEajYCACABKAIAIQsLIAUoAkxBAWoLIgE2AkwgC0F/Sg0BQQAgC2shCyAIQYDAAHIhCAwBCyAFQcwAahB3IgtBAEgNBiAFKAJMIQELQX8hCQJAIAEtAABBLkcNACABLQABQSpGBEACQCABLAACQTBrQQpPDQAgBSgCTCIBLQADQSRHDQAgASwAAkECdCAEakHAAWtBCjYCACABLAACQQN0IANqQYADaygCACEJIAUgAUEEaiIBNgJMDAILIBENByAABH8gAiACKAIAIgFBBGo2AgAgASgCAAVBAAshCSAFIAUoAkxBAmoiATYCTAwBCyAFIAFBAWo2AkwgBUHMAGoQdyEJIAUoAkwhAQtBACEGA0AgBiESQX8hDSABLAAAQcEAa0E5Sw0HIAUgAUEBaiIKNgJMIAEsAAAhBiAKIQEgBiASQTpsakGf7ABqLQAAIgZBAWtBCEkNAAsgBkETRg0CIAZFDQYgD0EATgRAIAQgD0ECdGogBjYCACAFIAMgD0EDdGopAwA3A0AMBAsgAA0BC0EAIQ0MBQsgBUFAayAGIAIQeCAFKAJMIQoMAgsgD0F/Sg0DC0EAIQEgAEUNBAsgCEH//3txIgwgCCAIQYDAAHEbIQZBACENQaQIIQ8gECEIAkACQAJAAn8CQAJAAkACQAJ/AkACQAJAAkACQAJAAkAgCkEBaywAACIBQV9xIAEgAUEPcUEDRhsgASASGyIBQdgAaw4hBBISEhISEhISDhIPBg4ODhIGEhISEgIFAxISCRIBEhIEAAsCQCABQcEAaw4HDhILEg4ODgALIAFB0wBGDQkMEQsgBSkDQCEUQaQIDAULQQAhAQJAAkACQAJAAkACQAJAIBJB/wFxDggAAQIDBBcFBhcLIAUoAkAgDjYCAAwWCyAFKAJAIA42AgAMFQsgBSgCQCAOrDcDAAwUCyAFKAJAIA47AQAMEwsgBSgCQCAOOgAADBILIAUoAkAgDjYCAAwRCyAFKAJAIA6sNwMADBALIAlBCCAJQQhLGyEJIAZBCHIhBkH4ACEBCyAQIQcgAUEgcSEMIAUpA0AiFFBFBEADQCAHQQFrIgcgFKdBD3FBsPAAai0AACAMcjoAACAUQg9WIQogFEIEiCEUIAoNAAsLIAUpA0BQDQMgBkEIcUUNAyABQQR2QaQIaiEPQQIhDQwDCyAQIQEgBSkDQCIUUEUEQANAIAFBAWsiASAUp0EHcUEwcjoAACAUQgdWIQcgFEIDiCEUIAcNAAsLIAEhByAGQQhxRQ0CIAkgECAHayIBQQFqIAEgCUgbIQkMAgsgBSkDQCIUQn9XBEAgBUIAIBR9IhQ3A0BBASENQaQIDAELIAZBgBBxBEBBASENQaUIDAELQaYIQaQIIAZBAXEiDRsLIQ8gECEBAkAgFEKAgICAEFQEQCAUIRUMAQsDQCABQQFrIgEgFCAUQgqAIhVCCn59p0EwcjoAACAUQv////+fAVYhByAVIRQgBw0ACwsgFaciBwRAA0AgAUEBayIBIAcgB0EKbiIMQQpsa0EwcjoAACAHQQlLIQogDCEHIAoNAAsLIAEhBwsgBkH//3txIAYgCUF/ShshBgJAIAUpA0AiFEIAUg0AIAkNAEEAIQkgECEHDAoLIAkgFFAgECAHa2oiASABIAlIGyEJDAkLIAUoAkAiAUGKEiABGyIHQQAgCRB6IgEgByAJaiABGyEIIAwhBiABIAdrIAkgARshCQwICyAJBEAgBSgCQAwCC0EAIQEgAEEgIAtBACAGECcMAgsgBUEANgIMIAUgBSkDQD4CCCAFIAVBCGo2AkBBfyEJIAVBCGoLIQhBACEBAkADQCAIKAIAIgdFDQECQCAFQQRqIAcQeSIHQQBIIgwNACAHIAkgAWtLDQAgCEEEaiEIIAkgASAHaiIBSw0BDAILC0F/IQ0gDA0FCyAAQSAgCyABIAYQJyABRQRAQQAhAQwBC0EAIQggBSgCQCEKA0AgCigCACIHRQ0BIAVBBGogBxB5IgcgCGoiCCABSg0BIAAgBUEEaiAHEC4gCkEEaiEKIAEgCEsNAAsLIABBICALIAEgBkGAwABzECcgCyABIAEgC0gbIQEMBQsgACAFKwNAIAsgCSAGIAFBABEdACEBDAQLIAUgBSkDQDwAN0EBIQkgEyEHIAwhBgwCC0F/IQ0LIAVB0ABqJAAgDQ8LIABBICANIAggB2siDCAJIAkgDEgbIgpqIgggCyAIIAtKGyIBIAggBhAnIAAgDyANEC4gAEEwIAEgCCAGQYCABHMQJyAAQTAgCiAMQQAQJyAAIAcgDBAuIABBICABIAggBkGAwABzECcMAAsAC54DAgR/AX4gAARAIAAoAgAiAQRAIAEQGhogACgCABALCyAAKAIcEAYgACgCIBAQIAAoAiQQECAAKAJQIgMEQCADKAIQIgIEQCADKAIAIgEEfwNAIAIgBEECdGooAgAiAgRAA0AgAigCGCEBIAIQBiABIgINAAsgAygCACEBCyABIARBAWoiBEsEQCADKAIQIQIMAQsLIAMoAhAFIAILEAYLIAMQBgsgACgCQCIBBEAgACkDMFAEfyABBSABED5CAiEFAkAgACkDMEICVA0AQQEhAgNAIAAoAkAgAkEEdGoQPiAFIAApAzBaDQEgBachAiAFQgF8IQUMAAsACyAAKAJACxAGCwJAIAAoAkRFDQBBACECQgEhBQNAIAAoAkwgAkECdGooAgAiAUEBOgAoIAFBDGoiASgCAEUEQCABBEAgAUEANgIEIAFBCDYCAAsLIAUgADUCRFoNASAFpyECIAVCAXwhBQwACwALIAAoAkwQBiAAKAJUIgIEQCACKAIIIgEEQCACKAIMIAERAwALIAIQBgsgAEEIahAxIAAQBgsL6gMCAX4EfwJAIAAEfiABRQRAIAMEQCADQQA2AgQgA0ESNgIAC0J/DwsgAkGDIHEEQAJAIAApAzBQDQBBPEE9IAJBAXEbIQcgAkECcUUEQANAIAAgBCACIAMQUyIFBEAgASAFIAcRAgBFDQYLIARCAXwiBCAAKQMwVA0ADAILAAsDQCAAIAQgAiADEFMiBQRAIAECfyAFECJBAWohBgNAQQAgBkUNARogBSAGQQFrIgZqIggtAABBL0cNAAsgCAsiBkEBaiAFIAYbIAcRAgBFDQULIARCAXwiBCAAKQMwVA0ACwsgAwRAIANBADYCBCADQQk2AgALQn8PC0ESIQYCQAJAIAAoAlAiBUUNACABRQ0AQQkhBiAFKQMIUA0AIAUoAhAgAS0AACIHBH9CpesKIQQgASEAA0AgBCAHrUL/AYN8IQQgAC0AASIHBEAgAEEBaiEAIARC/////w+DQiF+IQQMAQsLIASnBUGFKgsgBSgCAHBBAnRqKAIAIgBFDQADQCABIAAoAgAQOEUEQCACQQhxBEAgACkDCCIEQn9RDQMMBAsgACkDECIEQn9RDQIMAwsgACgCGCIADQALCyADBEAgA0EANgIEIAMgBjYCAAtCfyEECyAEBUJ/Cw8LIAMEQCADQgA3AgALIAQL3AQCB38BfgJAAkAgAEUNACABRQ0AIAJCf1UNAQsgBARAIARBADYCBCAEQRI2AgALQQAPCwJAIAAoAgAiB0UEQEGAAiEHQYACEDwiBkUNASAAKAIQEAYgAEGAAjYCACAAIAY2AhALAkACQCAAKAIQIAEtAAAiBQR/QqXrCiEMIAEhBgNAIAwgBa1C/wGDfCEMIAYtAAEiBQRAIAZBAWohBiAMQv////8Pg0IhfiEMDAELCyAMpwVBhSoLIgYgB3BBAnRqIggoAgAiBQRAA0ACQCAFKAIcIAZHDQAgASAFKAIAEDgNAAJAIANBCHEEQCAFKQMIQn9SDQELIAUpAxBCf1ENBAsgBARAIARBADYCBCAEQQo2AgALQQAPCyAFKAIYIgUNAAsLQSAQCSIFRQ0CIAUgATYCACAFIAgoAgA2AhggCCAFNgIAIAVCfzcDCCAFIAY2AhwgACAAKQMIQgF8Igw3AwggDLogB7hEAAAAAAAA6D+iZEUNACAHQQBIDQAgByAHQQF0IghGDQAgCBA8IgpFDQECQCAMQgAgBxtQBEAgACgCECEJDAELIAAoAhAhCUEAIQQDQCAJIARBAnRqKAIAIgYEQANAIAYoAhghASAGIAogBigCHCAIcEECdGoiCygCADYCGCALIAY2AgAgASIGDQALCyAEQQFqIgQgB0cNAAsLIAkQBiAAIAg2AgAgACAKNgIQCyADQQhxBEAgBSACNwMICyAFIAI3AxBBAQ8LIAQEQCAEQQA2AgQgBEEONgIAC0EADwsgBARAIARBADYCBCAEQQ42AgALQQAL3Q8BF38jAEFAaiIHQgA3AzAgB0IANwM4IAdCADcDICAHQgA3AygCQAJAAkACQAJAIAIEQCACQQNxIQggAkEBa0EDTwRAIAJBfHEhBgNAIAdBIGogASAJQQF0IgxqLwEAQQF0aiIKIAovAQBBAWo7AQAgB0EgaiABIAxBAnJqLwEAQQF0aiIKIAovAQBBAWo7AQAgB0EgaiABIAxBBHJqLwEAQQF0aiIKIAovAQBBAWo7AQAgB0EgaiABIAxBBnJqLwEAQQF0aiIKIAovAQBBAWo7AQAgCUEEaiEJIAZBBGsiBg0ACwsgCARAA0AgB0EgaiABIAlBAXRqLwEAQQF0aiIGIAYvAQBBAWo7AQAgCUEBaiEJIAhBAWsiCA0ACwsgBCgCACEJQQ8hCyAHLwE+IhENAgwBCyAEKAIAIQkLQQ4hC0EAIREgBy8BPA0AQQ0hCyAHLwE6DQBBDCELIAcvATgNAEELIQsgBy8BNg0AQQohCyAHLwE0DQBBCSELIAcvATINAEEIIQsgBy8BMA0AQQchCyAHLwEuDQBBBiELIAcvASwNAEEFIQsgBy8BKg0AQQQhCyAHLwEoDQBBAyELIAcvASYNAEECIQsgBy8BJA0AIAcvASJFBEAgAyADKAIAIgBBBGo2AgAgAEHAAjYBACADIAMoAgAiAEEEajYCACAAQcACNgEAQQEhDQwDCyAJQQBHIRtBASELQQEhCQwBCyALIAkgCSALSxshG0EBIQ5BASEJA0AgB0EgaiAJQQF0ai8BAA0BIAlBAWoiCSALRw0ACyALIQkLQX8hCCAHLwEiIg9BAksNAUEEIAcvASQiECAPQQF0amsiBkEASA0BIAZBAXQgBy8BJiISayIGQQBIDQEgBkEBdCAHLwEoIhNrIgZBAEgNASAGQQF0IAcvASoiFGsiBkEASA0BIAZBAXQgBy8BLCIVayIGQQBIDQEgBkEBdCAHLwEuIhZrIgZBAEgNASAGQQF0IAcvATAiF2siBkEASA0BIAZBAXQgBy8BMiIZayIGQQBIDQEgBkEBdCAHLwE0IhxrIgZBAEgNASAGQQF0IAcvATYiDWsiBkEASA0BIAZBAXQgBy8BOCIYayIGQQBIDQEgBkEBdCAHLwE6IgxrIgZBAEgNASAGQQF0IAcvATwiCmsiBkEASA0BIAZBAXQgEWsiBkEASA0BIAZBACAARSAOchsNASAJIBtLIRpBACEIIAdBADsBAiAHIA87AQQgByAPIBBqIgY7AQYgByAGIBJqIgY7AQggByAGIBNqIgY7AQogByAGIBRqIgY7AQwgByAGIBVqIgY7AQ4gByAGIBZqIgY7ARAgByAGIBdqIgY7ARIgByAGIBlqIgY7ARQgByAGIBxqIgY7ARYgByAGIA1qIgY7ARggByAGIBhqIgY7ARogByAGIAxqIgY7ARwgByAGIApqOwEeAkAgAkUNACACQQFHBEAgAkF+cSEGA0AgASAIQQF0ai8BACIKBEAgByAKQQF0aiIKIAovAQAiCkEBajsBACAFIApBAXRqIAg7AQALIAEgCEEBciIMQQF0ai8BACIKBEAgByAKQQF0aiIKIAovAQAiCkEBajsBACAFIApBAXRqIAw7AQALIAhBAmohCCAGQQJrIgYNAAsLIAJBAXFFDQAgASAIQQF0ai8BACICRQ0AIAcgAkEBdGoiAiACLwEAIgJBAWo7AQAgBSACQQF0aiAIOwEACyAJIBsgGhshDUEUIRBBACEWIAUiCiEYQQAhEgJAAkACQCAADgICAAELQQEhCCANQQpLDQNBgQIhEEHw2QAhGEGw2QAhCkEBIRIMAQsgAEECRiEWQQAhEEHw2gAhGEGw2gAhCiAAQQJHBEAMAQtBASEIIA1BCUsNAgtBASANdCITQQFrIRwgAygCACEUQQAhFSANIQZBACEPQQAhDkF/IQIDQEEBIAZ0IRoCQANAIAkgD2shFwJAIAUgFUEBdGovAQAiCCAQTwRAIAogCCAQa0EBdCIAai8BACERIAAgGGotAAAhAAwBC0EAQeAAIAhBAWogEEkiBhshACAIQQAgBhshEQsgDiAPdiEMQX8gF3QhBiAaIQgDQCAUIAYgCGoiCCAMakECdGoiGSAROwECIBkgFzoAASAZIAA6AAAgCA0AC0EBIAlBAWt0IQYDQCAGIgBBAXYhBiAAIA5xDQALIAdBIGogCUEBdGoiBiAGLwEAQQFrIgY7AQAgAEEBayAOcSAAakEAIAAbIQ4gFUEBaiEVIAZB//8DcUUEQCAJIAtGDQIgASAFIBVBAXRqLwEAQQF0ai8BACEJCyAJIA1NDQAgDiAccSIAIAJGDQALQQEgCSAPIA0gDxsiD2siBnQhAiAJIAtJBEAgCyAPayEMIAkhCAJAA0AgAiAHQSBqIAhBAXRqLwEAayICQQFIDQEgAkEBdCECIAZBAWoiBiAPaiIIIAtJDQALIAwhBgtBASAGdCECC0EBIQggEiACIBNqIhNBtApLcQ0DIBYgE0HQBEtxDQMgAygCACICIABBAnRqIgggDToAASAIIAY6AAAgCCAUIBpBAnRqIhQgAmtBAnY7AQIgACECDAELCyAOBEAgFCAOQQJ0aiIAQQA7AQIgACAXOgABIABBwAA6AAALIAMgAygCACATQQJ0ajYCAAsgBCANNgIAQQAhCAsgCAusAQICfgF/IAFBAmqtIQIgACkDmC4hAwJAIAAoAqAuIgFBA2oiBEE/TQRAIAIgAa2GIAOEIQIMAQsgAUHAAEYEQCAAKAIEIAAoAhBqIAM3AAAgACAAKAIQQQhqNgIQQQMhBAwBCyAAKAIEIAAoAhBqIAIgAa2GIAOENwAAIAAgACgCEEEIajYCECABQT1rIQQgAkHAACABa62IIQILIAAgAjcDmC4gACAENgKgLguXAwICfgN/QYDJADMBACECIAApA5guIQMCQCAAKAKgLiIFQYLJAC8BACIGaiIEQT9NBEAgAiAFrYYgA4QhAgwBCyAFQcAARgRAIAAoAgQgACgCEGogAzcAACAAIAAoAhBBCGo2AhAgBiEEDAELIAAoAgQgACgCEGogAiAFrYYgA4Q3AAAgACAAKAIQQQhqNgIQIARBQGohBCACQcAAIAVrrYghAgsgACACNwOYLiAAIAQ2AqAuIAEEQAJAIARBOU4EQCAAKAIEIAAoAhBqIAI3AAAgACAAKAIQQQhqNgIQDAELIARBGU4EQCAAKAIEIAAoAhBqIAI+AAAgACAAKAIQQQRqNgIQIAAgACkDmC5CIIgiAjcDmC4gACAAKAKgLkEgayIENgKgLgsgBEEJTgR/IAAoAgQgACgCEGogAj0AACAAIAAoAhBBAmo2AhAgACkDmC5CEIghAiAAKAKgLkEQawUgBAtBAUgNACAAIAAoAhAiAUEBajYCECABIAAoAgRqIAI8AAALIABBADYCoC4gAEIANwOYLgsL8hQBEn8gASgCCCICKAIAIQUgAigCDCEHIAEoAgAhCCAAQoCAgIDQxwA3A6ApQQAhAgJAAkAgB0EASgRAQX8hDANAAkAgCCACQQJ0aiIDLwEABEAgACAAKAKgKUEBaiIDNgKgKSAAIANBAnRqQawXaiACNgIAIAAgAmpBqClqQQA6AAAgAiEMDAELIANBADsBAgsgAkEBaiICIAdHDQALIABB/C1qIQ8gAEH4LWohESAAKAKgKSIEQQFKDQIMAQsgAEH8LWohDyAAQfgtaiERQX8hDAsDQCAAIARBAWoiAjYCoCkgACACQQJ0akGsF2ogDEEBaiIDQQAgDEECSCIGGyICNgIAIAggAkECdCIEakEBOwEAIAAgAmpBqClqQQA6AAAgACAAKAL4LUEBazYC+C0gBQRAIA8gDygCACAEIAVqLwECazYCAAsgAyAMIAYbIQwgACgCoCkiBEECSA0ACwsgASAMNgIEIARBAXYhBgNAIAAgBkECdGpBrBdqKAIAIQkCQCAGIgJBAXQiAyAESg0AIAggCUECdGohCiAAIAlqQagpaiENIAYhBQNAAkAgAyAETgRAIAMhAgwBCyAIIABBrBdqIgIgA0EBciIEQQJ0aigCACILQQJ0ai8BACIOIAggAiADQQJ0aigCACIQQQJ0ai8BACICTwRAIAIgDkcEQCADIQIMAgsgAyECIABBqClqIgMgC2otAAAgAyAQai0AAEsNAQsgBCECCyAKLwEAIgQgCCAAIAJBAnRqQawXaigCACIDQQJ0ai8BACILSQRAIAUhAgwCCwJAIAQgC0cNACANLQAAIAAgA2pBqClqLQAASw0AIAUhAgwCCyAAIAVBAnRqQawXaiADNgIAIAIhBSACQQF0IgMgACgCoCkiBEwNAAsLIAAgAkECdGpBrBdqIAk2AgAgBkECTgRAIAZBAWshBiAAKAKgKSEEDAELCyAAKAKgKSEDA0AgByEGIAAgA0EBayIENgKgKSAAKAKwFyEKIAAgACADQQJ0akGsF2ooAgAiCTYCsBdBASECAkAgA0EDSA0AIAggCUECdGohDSAAIAlqQagpaiELQQIhA0EBIQUDQAJAIAMgBE4EQCADIQIMAQsgCCAAQawXaiICIANBAXIiB0ECdGooAgAiBEECdGovAQAiDiAIIAIgA0ECdGooAgAiEEECdGovAQAiAk8EQCACIA5HBEAgAyECDAILIAMhAiAAQagpaiIDIARqLQAAIAMgEGotAABLDQELIAchAgsgDS8BACIHIAggACACQQJ0akGsF2ooAgAiA0ECdGovAQAiBEkEQCAFIQIMAgsCQCAEIAdHDQAgCy0AACAAIANqQagpai0AAEsNACAFIQIMAgsgACAFQQJ0akGsF2ogAzYCACACIQUgAkEBdCIDIAAoAqApIgRMDQALC0ECIQMgAEGsF2oiByACQQJ0aiAJNgIAIAAgACgCpClBAWsiBTYCpCkgACgCsBchAiAHIAVBAnRqIAo2AgAgACAAKAKkKUEBayIFNgKkKSAHIAVBAnRqIAI2AgAgCCAGQQJ0aiINIAggAkECdGoiBS8BACAIIApBAnRqIgQvAQBqOwEAIABBqClqIgkgBmoiCyACIAlqLQAAIgIgCSAKai0AACIKIAIgCksbQQFqOgAAIAUgBjsBAiAEIAY7AQIgACAGNgKwF0EBIQVBASECAkAgACgCoCkiBEECSA0AA0AgDS8BACIKIAggAAJ/IAMgAyAETg0AGiAIIAcgA0EBciICQQJ0aigCACIEQQJ0ai8BACIOIAggByADQQJ0aigCACIQQQJ0ai8BACISTwRAIAMgDiASRw0BGiADIAQgCWotAAAgCSAQai0AAEsNARoLIAILIgJBAnRqQawXaigCACIDQQJ0ai8BACIESQRAIAUhAgwCCwJAIAQgCkcNACALLQAAIAAgA2pBqClqLQAASw0AIAUhAgwCCyAAIAVBAnRqQawXaiADNgIAIAIhBSACQQF0IgMgACgCoCkiBEwNAAsLIAZBAWohByAAIAJBAnRqQawXaiAGNgIAIAAoAqApIgNBAUoNAAsgACAAKAKkKUEBayICNgKkKSAAQawXaiIDIAJBAnRqIAAoArAXNgIAIAEoAgQhCSABKAIIIgIoAhAhBiACKAIIIQogAigCBCEQIAIoAgAhDSABKAIAIQcgAEGkF2pCADcBACAAQZwXakIANwEAIABBlBdqQgA3AQAgAEGMF2oiAUIANwEAQQAhBSAHIAMgACgCpClBAnRqKAIAQQJ0akEAOwECAkAgACgCpCkiAkG7BEoNACACQQFqIQIDQCAHIAAgAkECdGpBrBdqKAIAIgRBAnQiEmoiCyAHIAsvAQJBAnRqLwECIgNBAWogBiADIAZJGyIOOwECIAMgBk8hEwJAIAQgCUoNACAAIA5BAXRqQYwXaiIDIAMvAQBBAWo7AQBBACEDIAQgCk4EQCAQIAQgCmtBAnRqKAIAIQMLIBEgESgCACALLwEAIgQgAyAOamxqNgIAIA1FDQAgDyAPKAIAIAMgDSASai8BAmogBGxqNgIACyAFIBNqIQUgAkEBaiICQb0ERw0ACyAFRQ0AIAAgBkEBdGpBjBdqIQQDQCAGIQIDQCAAIAIiA0EBayICQQF0akGMF2oiDy8BACIKRQ0ACyAPIApBAWs7AQAgACADQQF0akGMF2oiAiACLwEAQQJqOwEAIAQgBC8BAEEBayIDOwEAIAVBAkohAiAFQQJrIQUgAg0ACyAGRQ0AQb0EIQIDQCADQf//A3EiBQRAA0AgACACQQFrIgJBAnRqQawXaigCACIDIAlKDQAgByADQQJ0aiIDLwECIAZHBEAgESARKAIAIAYgAy8BAGxqIgQ2AgAgESAEIAMvAQAgAy8BAmxrNgIAIAMgBjsBAgsgBUEBayIFDQALCyAGQQFrIgZFDQEgACAGQQF0akGMF2ovAQAhAwwACwALIwBBIGsiAiABIgAvAQBBAXQiATsBAiACIAEgAC8BAmpBAXQiATsBBCACIAEgAC8BBGpBAXQiATsBBiACIAEgAC8BBmpBAXQiATsBCCACIAEgAC8BCGpBAXQiATsBCiACIAEgAC8BCmpBAXQiATsBDCACIAEgAC8BDGpBAXQiATsBDiACIAEgAC8BDmpBAXQiATsBECACIAEgAC8BEGpBAXQiATsBEiACIAEgAC8BEmpBAXQiATsBFCACIAEgAC8BFGpBAXQiATsBFiACIAEgAC8BFmpBAXQiATsBGCACIAEgAC8BGGpBAXQiATsBGiACIAEgAC8BGmpBAXQiATsBHCACIAAvARwgAWpBAXQ7AR5BACEAIAxBAE4EQANAIAggAEECdGoiAy8BAiIBBEAgAiABQQF0aiIFIAUvAQAiBUEBajsBACADIAWtQoD+A4NCCIhCgpCAgQh+QpDCiKKIAYNCgYKEiBB+QiCIp0H/AXEgBUH/AXGtQoKQgIEIfkKQwoiiiAGDQoGChIgQfkIYiKdBgP4DcXJBECABa3Y7AQALIAAgDEchASAAQQFqIQAgAQ0ACwsLcgEBfyMAQRBrIgQkAAJ/QQAgAEUNABogAEEIaiEAIAFFBEAgAlBFBEAgAARAIABBADYCBCAAQRI2AgALQQAMAgtBAEIAIAMgABA6DAELIAQgAjcDCCAEIAE2AgAgBEIBIAMgABA6CyEAIARBEGokACAACyIAIAAgASACIAMQJiIARQRAQQAPCyAAKAIwQQAgAiADECULAwABC8gFAQR/IABB//8DcSEDIABBEHYhBEEBIQAgAkEBRgRAIAMgAS0AAGpB8f8DcCIAIARqQfH/A3BBEHQgAHIPCwJAIAEEfyACQRBJDQECQCACQa8rSwRAA0AgAkGwK2shAkG1BSEFIAEhAANAIAMgAC0AAGoiAyAEaiADIAAtAAFqIgNqIAMgAC0AAmoiA2ogAyAALQADaiIDaiADIAAtAARqIgNqIAMgAC0ABWoiA2ogAyAALQAGaiIDaiADIAAtAAdqIgNqIQQgBQRAIABBCGohACAFQQFrIQUMAQsLIARB8f8DcCEEIANB8f8DcCEDIAFBsCtqIQEgAkGvK0sNAAsgAkEISQ0BCwNAIAMgAS0AAGoiACAEaiAAIAEtAAFqIgBqIAAgAS0AAmoiAGogACABLQADaiIAaiAAIAEtAARqIgBqIAAgAS0ABWoiAGogACABLQAGaiIAaiAAIAEtAAdqIgNqIQQgAUEIaiEBIAJBCGsiAkEHSw0ACwsCQCACRQ0AIAJBAWshBiACQQNxIgUEQCABIQADQCACQQFrIQIgAyAALQAAaiIDIARqIQQgAEEBaiIBIQAgBUEBayIFDQALCyAGQQNJDQADQCADIAEtAABqIgAgAS0AAWoiBSABLQACaiIGIAEtAANqIgMgBiAFIAAgBGpqamohBCABQQRqIQEgAkEEayICDQALCyADQfH/A3AgBEHx/wNwQRB0cgVBAQsPCwJAIAJFDQAgAkEBayEGIAJBA3EiBQRAIAEhAANAIAJBAWshAiADIAAtAABqIgMgBGohBCAAQQFqIgEhACAFQQFrIgUNAAsLIAZBA0kNAANAIAMgAS0AAGoiACABLQABaiIFIAEtAAJqIgYgAS0AA2oiAyAGIAUgACAEampqaiEEIAFBBGohASACQQRrIgINAAsLIANB8f8DcCAEQfH/A3BBEHRyCx8AIAAgAiADQcCAASgCABEAACEAIAEgAiADEAcaIAALIwAgACAAKAJAIAIgA0HUgAEoAgARAAA2AkAgASACIAMQBxoLzSoCGH8HfiAAKAIMIgIgACgCECIDaiEQIAMgAWshASAAKAIAIgUgACgCBGohA0F/IAAoAhwiBygCpAF0IQRBfyAHKAKgAXQhCyAHKAI4IQwCf0EAIAcoAiwiEUUNABpBACACIAxJDQAaIAJBhAJqIAwgEWpNCyEWIBBBgwJrIRMgASACaiEXIANBDmshFCAEQX9zIRggC0F/cyESIAcoApwBIRUgBygCmAEhDSAHKAKIASEIIAc1AoQBIR0gBygCNCEOIAcoAjAhGSAQQQFqIQ8DQCAIQThyIQYgBSAIQQN2QQdxayELAn8gAiANIAUpAAAgCK2GIB2EIh2nIBJxQQJ0IgFqIgMtAAAiBA0AGiACIAEgDWoiAS0AAjoAACAGIAEtAAEiAWshBiACQQFqIA0gHSABrYgiHacgEnFBAnQiAWoiAy0AACIEDQAaIAIgASANaiIDLQACOgABIAYgAy0AASIDayEGIA0gHSADrYgiHacgEnFBAnRqIgMtAAAhBCACQQJqCyEBIAtBB2ohBSAGIAMtAAEiAmshCCAdIAKtiCEdAkACQAJAIARB/wFxRQ0AAkACQAJAAkACQANAIARBEHEEQCAVIB0gBK1CD4OIIhqnIBhxQQJ0aiECAn8gCCAEQQ9xIgZrIgRBG0sEQCAEIQggBQwBCyAEQThyIQggBSkAACAErYYgGoQhGiAFIARBA3ZrQQdqCyELIAMzAQIhGyAIIAItAAEiA2shCCAaIAOtiCEaIAItAAAiBEEQcQ0CA0AgBEHAAHFFBEAgCCAVIAIvAQJBAnRqIBqnQX8gBHRBf3NxQQJ0aiICLQABIgNrIQggGiADrYghGiACLQAAIgRBEHFFDQEMBAsLIAdB0f4ANgIEIABB7A42AhggGiEdDAMLIARB/wFxIgJBwABxRQRAIAggDSADLwECQQJ0aiAdp0F/IAJ0QX9zcUECdGoiAy0AASICayEIIB0gAq2IIR0gAy0AACIERQ0HDAELCyAEQSBxBEAgB0G//gA2AgQgASECDAgLIAdB0f4ANgIEIABB0A42AhggASECDAcLIB1BfyAGdEF/c62DIBt8IhunIQUgCCAEQQ9xIgNrIQggGiAErUIPg4ghHSABIBdrIgYgAjMBAiAaQX8gA3RBf3Otg3ynIgRPDQIgBCAGayIGIBlNDQEgBygCjEdFDQEgB0HR/gA2AgQgAEG5DDYCGAsgASECIAshBQwFCwJAIA5FBEAgDCARIAZraiEDDAELIAYgDk0EQCAMIA4gBmtqIQMMAQsgDCARIAYgDmsiBmtqIQMgBSAGTQ0AIAUgBmshBQJAAkAgASADTSABIA8gAWusIhogBq0iGyAaIBtUGyIapyIGaiICIANLcQ0AIAMgBmogAUsgASADT3ENACABIAMgBhAHGiACIQEMAQsgASADIAMgAWsiASABQR91IgFqIAFzIgIQByACaiEBIBogAq0iHn0iHFANACACIANqIQIDQAJAIBwgHiAcIB5UGyIbQiBUBEAgGyEaDAELIBsiGkIgfSIgQgWIQgF8QgODIh9QRQRAA0AgASACKQAANwAAIAEgAikAGDcAGCABIAIpABA3ABAgASACKQAINwAIIBpCIH0hGiACQSBqIQIgAUEgaiEBIB9CAX0iH0IAUg0ACwsgIELgAFQNAANAIAEgAikAADcAACABIAIpABg3ABggASACKQAQNwAQIAEgAikACDcACCABIAIpADg3ADggASACKQAwNwAwIAEgAikAKDcAKCABIAIpACA3ACAgASACKQBYNwBYIAEgAikAUDcAUCABIAIpAEg3AEggASACKQBANwBAIAEgAikAYDcAYCABIAIpAGg3AGggASACKQBwNwBwIAEgAikAeDcAeCACQYABaiECIAFBgAFqIQEgGkKAAX0iGkIfVg0ACwsgGkIQWgRAIAEgAikAADcAACABIAIpAAg3AAggGkIQfSEaIAJBEGohAiABQRBqIQELIBpCCFoEQCABIAIpAAA3AAAgGkIIfSEaIAJBCGohAiABQQhqIQELIBpCBFoEQCABIAIoAAA2AAAgGkIEfSEaIAJBBGohAiABQQRqIQELIBpCAloEQCABIAIvAAA7AAAgGkICfSEaIAJBAmohAiABQQJqIQELIBwgG30hHCAaUEUEQCABIAItAAA6AAAgAkEBaiECIAFBAWohAQsgHEIAUg0ACwsgDiEGIAwhAwsgBSAGSwRAAkACQCABIANNIAEgDyABa6wiGiAGrSIbIBogG1QbIhqnIglqIgIgA0txDQAgAyAJaiABSyABIANPcQ0AIAEgAyAJEAcaDAELIAEgAyADIAFrIgEgAUEfdSIBaiABcyIBEAcgAWohAiAaIAGtIh59IhxQDQAgASADaiEBA0ACQCAcIB4gHCAeVBsiG0IgVARAIBshGgwBCyAbIhpCIH0iIEIFiEIBfEIDgyIfUEUEQANAIAIgASkAADcAACACIAEpABg3ABggAiABKQAQNwAQIAIgASkACDcACCAaQiB9IRogAUEgaiEBIAJBIGohAiAfQgF9Ih9CAFINAAsLICBC4ABUDQADQCACIAEpAAA3AAAgAiABKQAYNwAYIAIgASkAEDcAECACIAEpAAg3AAggAiABKQA4NwA4IAIgASkAMDcAMCACIAEpACg3ACggAiABKQAgNwAgIAIgASkAWDcAWCACIAEpAFA3AFAgAiABKQBINwBIIAIgASkAQDcAQCACIAEpAGA3AGAgAiABKQBoNwBoIAIgASkAcDcAcCACIAEpAHg3AHggAUGAAWohASACQYABaiECIBpCgAF9IhpCH1YNAAsLIBpCEFoEQCACIAEpAAA3AAAgAiABKQAINwAIIBpCEH0hGiACQRBqIQIgAUEQaiEBCyAaQghaBEAgAiABKQAANwAAIBpCCH0hGiACQQhqIQIgAUEIaiEBCyAaQgRaBEAgAiABKAAANgAAIBpCBH0hGiACQQRqIQIgAUEEaiEBCyAaQgJaBEAgAiABLwAAOwAAIBpCAn0hGiACQQJqIQIgAUECaiEBCyAcIBt9IRwgGlBFBEAgAiABLQAAOgAAIAJBAWohAiABQQFqIQELIBxCAFINAAsLIAUgBmshAUEAIARrIQUCQCAEQQdLBEAgBCEDDAELIAEgBE0EQCAEIQMMAQsgAiAEayEFA0ACQCACIAUpAAA3AAAgBEEBdCEDIAEgBGshASACIARqIQIgBEEDSw0AIAMhBCABIANLDQELC0EAIANrIQULIAIgBWohBAJAIAUgDyACa6wiGiABrSIbIBogG1QbIhqnIgFIIAVBf0pxDQAgBUEBSCABIARqIAJLcQ0AIAIgBCABEAcgAWohAgwDCyACIAQgAyADQR91IgFqIAFzIgEQByABaiECIBogAa0iHn0iHFANAiABIARqIQEDQAJAIBwgHiAcIB5UGyIbQiBUBEAgGyEaDAELIBsiGkIgfSIgQgWIQgF8QgODIh9QRQRAA0AgAiABKQAANwAAIAIgASkAGDcAGCACIAEpABA3ABAgAiABKQAINwAIIBpCIH0hGiABQSBqIQEgAkEgaiECIB9CAX0iH0IAUg0ACwsgIELgAFQNAANAIAIgASkAADcAACACIAEpABg3ABggAiABKQAQNwAQIAIgASkACDcACCACIAEpADg3ADggAiABKQAwNwAwIAIgASkAKDcAKCACIAEpACA3ACAgAiABKQBYNwBYIAIgASkAUDcAUCACIAEpAEg3AEggAiABKQBANwBAIAIgASkAYDcAYCACIAEpAGg3AGggAiABKQBwNwBwIAIgASkAeDcAeCABQYABaiEBIAJBgAFqIQIgGkKAAX0iGkIfVg0ACwsgGkIQWgRAIAIgASkAADcAACACIAEpAAg3AAggGkIQfSEaIAJBEGohAiABQRBqIQELIBpCCFoEQCACIAEpAAA3AAAgGkIIfSEaIAJBCGohAiABQQhqIQELIBpCBFoEQCACIAEoAAA2AAAgGkIEfSEaIAJBBGohAiABQQRqIQELIBpCAloEQCACIAEvAAA7AAAgGkICfSEaIAJBAmohAiABQQJqIQELIBwgG30hHCAaUEUEQCACIAEtAAA6AAAgAkEBaiECIAFBAWohAQsgHFBFDQALDAILAkAgASADTSABIA8gAWusIhogBa0iGyAaIBtUGyIapyIEaiICIANLcQ0AIAMgBGogAUsgASADT3ENACABIAMgBBAHGgwCCyABIAMgAyABayIBIAFBH3UiAWogAXMiARAHIAFqIQIgGiABrSIefSIcUA0BIAEgA2ohAQNAAkAgHCAeIBwgHlQbIhtCIFQEQCAbIRoMAQsgGyIaQiB9IiBCBYhCAXxCA4MiH1BFBEADQCACIAEpAAA3AAAgAiABKQAYNwAYIAIgASkAEDcAECACIAEpAAg3AAggGkIgfSEaIAFBIGohASACQSBqIQIgH0IBfSIfQgBSDQALCyAgQuAAVA0AA0AgAiABKQAANwAAIAIgASkAGDcAGCACIAEpABA3ABAgAiABKQAINwAIIAIgASkAODcAOCACIAEpADA3ADAgAiABKQAoNwAoIAIgASkAIDcAICACIAEpAFg3AFggAiABKQBQNwBQIAIgASkASDcASCACIAEpAEA3AEAgAiABKQBgNwBgIAIgASkAaDcAaCACIAEpAHA3AHAgAiABKQB4NwB4IAFBgAFqIQEgAkGAAWohAiAaQoABfSIaQh9WDQALCyAaQhBaBEAgAiABKQAANwAAIAIgASkACDcACCAaQhB9IRogAkEQaiECIAFBEGohAQsgGkIIWgRAIAIgASkAADcAACAaQgh9IRogAkEIaiECIAFBCGohAQsgGkIEWgRAIAIgASgAADYAACAaQgR9IRogAkEEaiECIAFBBGohAQsgGkICWgRAIAIgAS8AADsAACAaQgJ9IRogAkECaiECIAFBAmohAQsgHCAbfSEcIBpQRQRAIAIgAS0AADoAACACQQFqIQIgAUEBaiEBCyAcUEUNAAsMAQsCQAJAIBYEQAJAIAQgBUkEQCAHKAKYRyAESw0BCyABIARrIQMCQEEAIARrIgVBf0ogDyABa6wiGiAbIBogG1QbIhqnIgIgBUpxDQAgBUEBSCACIANqIAFLcQ0AIAEgAyACEAcgAmohAgwFCyABIAMgBCAEQR91IgFqIAFzIgEQByABaiECIBogAa0iHn0iHFANBCABIANqIQEDQAJAIBwgHiAcIB5UGyIbQiBUBEAgGyEaDAELIBsiGkIgfSIgQgWIQgF8QgODIh9QRQRAA0AgAiABKQAANwAAIAIgASkAGDcAGCACIAEpABA3ABAgAiABKQAINwAIIBpCIH0hGiABQSBqIQEgAkEgaiECIB9CAX0iH0IAUg0ACwsgIELgAFQNAANAIAIgASkAADcAACACIAEpABg3ABggAiABKQAQNwAQIAIgASkACDcACCACIAEpADg3ADggAiABKQAwNwAwIAIgASkAKDcAKCACIAEpACA3ACAgAiABKQBYNwBYIAIgASkAUDcAUCACIAEpAEg3AEggAiABKQBANwBAIAIgASkAYDcAYCACIAEpAGg3AGggAiABKQBwNwBwIAIgASkAeDcAeCABQYABaiEBIAJBgAFqIQIgGkKAAX0iGkIfVg0ACwsgGkIQWgRAIAIgASkAADcAACACIAEpAAg3AAggGkIQfSEaIAJBEGohAiABQRBqIQELIBpCCFoEQCACIAEpAAA3AAAgGkIIfSEaIAJBCGohAiABQQhqIQELIBpCBFoEQCACIAEoAAA2AAAgGkIEfSEaIAJBBGohAiABQQRqIQELIBpCAloEQCACIAEvAAA7AAAgGkICfSEaIAJBAmohAiABQQJqIQELIBwgG30hHCAaUEUEQCACIAEtAAA6AAAgAkEBaiECIAFBAWohAQsgHFBFDQALDAQLIBAgAWsiCUEBaiIGIAUgBSAGSxshAyABIARrIQIgAUEHcUUNAiADRQ0CIAEgAi0AADoAACACQQFqIQIgAUEBaiIGQQdxQQAgA0EBayIFGw0BIAYhASAFIQMgCSEGDAILAkAgBCAFSQRAIAcoAphHIARLDQELIAEgASAEayIGKQAANwAAIAEgBUEBa0EHcUEBaiIDaiECIAUgA2siBEUNAyADIAZqIQEDQCACIAEpAAA3AAAgAUEIaiEBIAJBCGohAiAEQQhrIgQNAAsMAwsgASAEIAUQPyECDAILIAEgAi0AADoAASAJQQFrIQYgA0ECayEFIAJBAWohAgJAIAFBAmoiCkEHcUUNACAFRQ0AIAEgAi0AADoAAiAJQQJrIQYgA0EDayEFIAJBAWohAgJAIAFBA2oiCkEHcUUNACAFRQ0AIAEgAi0AADoAAyAJQQNrIQYgA0EEayEFIAJBAWohAgJAIAFBBGoiCkEHcUUNACAFRQ0AIAEgAi0AADoABCAJQQRrIQYgA0EFayEFIAJBAWohAgJAIAFBBWoiCkEHcUUNACAFRQ0AIAEgAi0AADoABSAJQQVrIQYgA0EGayEFIAJBAWohAgJAIAFBBmoiCkEHcUUNACAFRQ0AIAEgAi0AADoABiAJQQZrIQYgA0EHayEFIAJBAWohAgJAIAFBB2oiCkEHcUUNACAFRQ0AIAEgAi0AADoAByAJQQdrIQYgA0EIayEDIAFBCGohASACQQFqIQIMBgsgCiEBIAUhAwwFCyAKIQEgBSEDDAQLIAohASAFIQMMAwsgCiEBIAUhAwwCCyAKIQEgBSEDDAELIAohASAFIQMLAkACQCAGQRdNBEAgA0UNASADQQFrIQUgA0EHcSIEBEADQCABIAItAAA6AAAgA0EBayEDIAFBAWohASACQQFqIQIgBEEBayIEDQALCyAFQQdJDQEDQCABIAItAAA6AAAgASACLQABOgABIAEgAi0AAjoAAiABIAItAAM6AAMgASACLQAEOgAEIAEgAi0ABToABSABIAItAAY6AAYgASACLQAHOgAHIAFBCGohASACQQhqIQIgA0EIayIDDQALDAELIAMNAQsgASECDAELIAEgBCADED8hAgsgCyEFDAELIAEgAy0AAjoAACABQQFqIQILIAUgFE8NACACIBNJDQELCyAAIAI2AgwgACAFIAhBA3ZrIgE2AgAgACATIAJrQYMCajYCECAAIBQgAWtBDmo2AgQgByAIQQdxIgA2AogBIAcgHUJ/IACthkJ/hYM+AoQBC+cFAQR/IAMgAiACIANLGyEEIAAgAWshAgJAIABBB3FFDQAgBEUNACAAIAItAAA6AAAgA0EBayEGIAJBAWohAiAAQQFqIgdBB3FBACAEQQFrIgUbRQRAIAchACAFIQQgBiEDDAELIAAgAi0AADoAASADQQJrIQYgBEECayEFIAJBAWohAgJAIABBAmoiB0EHcUUNACAFRQ0AIAAgAi0AADoAAiADQQNrIQYgBEEDayEFIAJBAWohAgJAIABBA2oiB0EHcUUNACAFRQ0AIAAgAi0AADoAAyADQQRrIQYgBEEEayEFIAJBAWohAgJAIABBBGoiB0EHcUUNACAFRQ0AIAAgAi0AADoABCADQQVrIQYgBEEFayEFIAJBAWohAgJAIABBBWoiB0EHcUUNACAFRQ0AIAAgAi0AADoABSADQQZrIQYgBEEGayEFIAJBAWohAgJAIABBBmoiB0EHcUUNACAFRQ0AIAAgAi0AADoABiADQQdrIQYgBEEHayEFIAJBAWohAgJAIABBB2oiB0EHcUUNACAFRQ0AIAAgAi0AADoAByADQQhrIQMgBEEIayEEIABBCGohACACQQFqIQIMBgsgByEAIAUhBCAGIQMMBQsgByEAIAUhBCAGIQMMBAsgByEAIAUhBCAGIQMMAwsgByEAIAUhBCAGIQMMAgsgByEAIAUhBCAGIQMMAQsgByEAIAUhBCAGIQMLAkAgA0EXTQRAIARFDQEgBEEBayEBIARBB3EiAwRAA0AgACACLQAAOgAAIARBAWshBCAAQQFqIQAgAkEBaiECIANBAWsiAw0ACwsgAUEHSQ0BA0AgACACLQAAOgAAIAAgAi0AAToAASAAIAItAAI6AAIgACACLQADOgADIAAgAi0ABDoABCAAIAItAAU6AAUgACACLQAGOgAGIAAgAi0ABzoAByAAQQhqIQAgAkEIaiECIARBCGsiBA0ACwwBCyAERQ0AIAAgASAEED8hAAsgAAvyCAEXfyAAKAJoIgwgACgCMEGGAmsiBWtBACAFIAxJGyENIAAoAnQhAiAAKAKQASEPIAAoAkgiDiAMaiIJIAAoAnAiBUECIAUbIgVBAWsiBmoiAy0AASESIAMtAAAhEyAGIA5qIQZBAyEDIAAoApQBIRYgACgCPCEUIAAoAkwhECAAKAI4IRECQAJ/IAVBA0kEQCANIQggDgwBCyAAIABBACAJLQABIAAoAnwRAAAgCS0AAiAAKAJ8EQAAIQoDQCAAIAogAyAJai0AACAAKAJ8EQAAIQogACgCUCAKQQF0ai8BACIIIAEgCCABQf//A3FJIggbIQEgA0ECayAHIAgbIQcgA0EBaiIDIAVNDQALIAFB//8DcSAHIA1qIghB//8DcU0NASAGIAdB//8DcSIDayEGIA4gA2sLIQMCQAJAIAwgAUH//wNxTQ0AIAIgAkECdiAFIA9JGyEKIA1B//8DcSEVIAlBAmohDyAJQQRrIRcDQAJAAkAgBiABQf//A3EiC2otAAAgE0cNACAGIAtBAWoiAWotAAAgEkcNACADIAtqIgItAAAgCS0AAEcNACABIANqLQAAIAktAAFGDQELIApBAWsiCkUNAiAQIAsgEXFBAXRqLwEAIgEgCEH//wNxSw0BDAILIAJBAmohAUEAIQQgDyECAkADQCACLQAAIAEtAABHDQEgAi0AASABLQABRwRAIARBAXIhBAwCCyACLQACIAEtAAJHBEAgBEECciEEDAILIAItAAMgAS0AA0cEQCAEQQNyIQQMAgsgAi0ABCABLQAERwRAIARBBHIhBAwCCyACLQAFIAEtAAVHBEAgBEEFciEEDAILIAItAAYgAS0ABkcEQCAEQQZyIQQMAgsgAi0AByABLQAHRwRAIARBB3IhBAwCCyABQQhqIQEgAkEIaiECIARB+AFJIRggBEEIaiEEIBgNAAtBgAIhBAsCQAJAIAUgBEECaiICSQRAIAAgCyAHQf//A3FrIgY2AmwgAiAUSwRAIBQPCyACIBZPBEAgAg8LIAkgBEEBaiIFaiIBLQABIRIgAS0AACETAkAgAkEESQ0AIAIgBmogDE8NACAGQf//A3EhCCAEQQFrIQtBACEDQQAhBwNAIBAgAyAIaiARcUEBdGovAQAiASAGQf//A3FJBEAgAyAVaiABTw0IIAMhByABIQYLIANBAWoiAyALTQ0ACyAAIAAgAEEAIAIgF2oiAS0AACAAKAJ8EQAAIAEtAAEgACgCfBEAACABLQACIAAoAnwRAAAhASAAKAJQIAFBAXRqLwEAIgEgBkH//wNxTwRAIAdB//8DcSEDIAYhAQwDCyAEQQJrIgdB//8DcSIDIBVqIAFPDQYMAgsgAyAFaiEGIAIhBQsgCkEBayIKRQ0DIBAgCyARcUEBdGovAQAiASAIQf//A3FNDQMMAQsgByANaiEIIA4gA2siAyAFaiEGIAIhBQsgDCABQf//A3FLDQALCyAFDwsgAiEFCyAFIAAoAjwiACAAIAVLGwuGBQETfyAAKAJ0IgMgA0ECdiAAKAJwIgNBAiADGyIDIAAoApABSRshByAAKAJoIgogACgCMEGGAmsiBWtB//8DcUEAIAUgCkkbIQwgACgCSCIIIApqIgkgA0EBayICaiIFLQABIQ0gBS0AACEOIAlBAmohBSACIAhqIQsgACgClAEhEiAAKAI8IQ8gACgCTCEQIAAoAjghESAAKAKIAUEFSCETA0ACQCAKIAFB//8DcU0NAANAAkACQCALIAFB//8DcSIGai0AACAORw0AIAsgBkEBaiIBai0AACANRw0AIAYgCGoiAi0AACAJLQAARw0AIAEgCGotAAAgCS0AAUYNAQsgB0EBayIHRQ0CIAwgECAGIBFxQQF0ai8BACIBSQ0BDAILCyACQQJqIQRBACECIAUhAQJAA0AgAS0AACAELQAARw0BIAEtAAEgBC0AAUcEQCACQQFyIQIMAgsgAS0AAiAELQACRwRAIAJBAnIhAgwCCyABLQADIAQtAANHBEAgAkEDciECDAILIAEtAAQgBC0ABEcEQCACQQRyIQIMAgsgAS0ABSAELQAFRwRAIAJBBXIhAgwCCyABLQAGIAQtAAZHBEAgAkEGciECDAILIAEtAAcgBC0AB0cEQCACQQdyIQIMAgsgBEEIaiEEIAFBCGohASACQfgBSSEUIAJBCGohAiAUDQALQYACIQILAkAgAyACQQJqIgFJBEAgACAGNgJsIAEgD0sEQCAPDwsgASASTwRAIAEPCyAIIAJBAWoiA2ohCyADIAlqIgMtAAEhDSADLQAAIQ4gASEDDAELIBMNAQsgB0EBayIHRQ0AIAwgECAGIBFxQQF0ai8BACIBSQ0BCwsgAwvLAQECfwJAA0AgAC0AACABLQAARw0BIAAtAAEgAS0AAUcEQCACQQFyDwsgAC0AAiABLQACRwRAIAJBAnIPCyAALQADIAEtAANHBEAgAkEDcg8LIAAtAAQgAS0ABEcEQCACQQRyDwsgAC0ABSABLQAFRwRAIAJBBXIPCyAALQAGIAEtAAZHBEAgAkEGcg8LIAAtAAcgAS0AB0cEQCACQQdyDwsgAUEIaiEBIABBCGohACACQfgBSSEDIAJBCGohAiADDQALQYACIQILIAIL5wwBB38gAEF/cyEAIAJBF08EQAJAIAFBA3FFDQAgAS0AACAAQf8BcXNBAnRB0BhqKAIAIABBCHZzIQAgAkEBayIEQQAgAUEBaiIDQQNxG0UEQCAEIQIgAyEBDAELIAEtAAEgAEH/AXFzQQJ0QdAYaigCACAAQQh2cyEAIAFBAmohAwJAIAJBAmsiBEUNACADQQNxRQ0AIAEtAAIgAEH/AXFzQQJ0QdAYaigCACAAQQh2cyEAIAFBA2ohAwJAIAJBA2siBEUNACADQQNxRQ0AIAEtAAMgAEH/AXFzQQJ0QdAYaigCACAAQQh2cyEAIAFBBGohASACQQRrIQIMAgsgBCECIAMhAQwBCyAEIQIgAyEBCyACQRRuIgNBbGwhCQJAIANBAWsiCEUEQEEAIQQMAQsgA0EUbCABakEUayEDQQAhBANAIAEoAhAgB3MiB0EWdkH8B3FB0DhqKAIAIAdBDnZB/AdxQdAwaigCACAHQQZ2QfwHcUHQKGooAgAgB0H/AXFBAnRB0CBqKAIAc3NzIQcgASgCDCAGcyIGQRZ2QfwHcUHQOGooAgAgBkEOdkH8B3FB0DBqKAIAIAZBBnZB/AdxQdAoaigCACAGQf8BcUECdEHQIGooAgBzc3MhBiABKAIIIAVzIgVBFnZB/AdxQdA4aigCACAFQQ52QfwHcUHQMGooAgAgBUEGdkH8B3FB0ChqKAIAIAVB/wFxQQJ0QdAgaigCAHNzcyEFIAEoAgQgBHMiBEEWdkH8B3FB0DhqKAIAIARBDnZB/AdxQdAwaigCACAEQQZ2QfwHcUHQKGooAgAgBEH/AXFBAnRB0CBqKAIAc3NzIQQgASgCACAAcyIAQRZ2QfwHcUHQOGooAgAgAEEOdkH8B3FB0DBqKAIAIABBBnZB/AdxQdAoaigCACAAQf8BcUECdEHQIGooAgBzc3MhACABQRRqIQEgCEEBayIIDQALIAMhAQsgAiAJaiECIAEoAhAgASgCDCABKAIIIAEoAgQgASgCACAAcyIAQQh2IABB/wFxQQJ0QdAYaigCAHMiAEEIdiAAQf8BcUECdEHQGGooAgBzIgBBCHYgAEH/AXFBAnRB0BhqKAIAcyIAQf8BcUECdEHQGGooAgAgBHNzIABBCHZzIgBBCHYgAEH/AXFBAnRB0BhqKAIAcyIAQQh2IABB/wFxQQJ0QdAYaigCAHMiAEEIdiAAQf8BcUECdEHQGGooAgBzIgBB/wFxQQJ0QdAYaigCACAFc3MgAEEIdnMiAEEIdiAAQf8BcUECdEHQGGooAgBzIgBBCHYgAEH/AXFBAnRB0BhqKAIAcyIAQQh2IABB/wFxQQJ0QdAYaigCAHMiAEH/AXFBAnRB0BhqKAIAIAZzcyAAQQh2cyIAQQh2IABB/wFxQQJ0QdAYaigCAHMiAEEIdiAAQf8BcUECdEHQGGooAgBzIgBBCHYgAEH/AXFBAnRB0BhqKAIAcyIAQf8BcUECdEHQGGooAgAgB3NzIABBCHZzIgBBCHYgAEH/AXFBAnRB0BhqKAIAcyIAQQh2IABB/wFxQQJ0QdAYaigCAHMiAEEIdiAAQf8BcUECdEHQGGooAgBzIgBBCHYgAEH/AXFBAnRB0BhqKAIAcyEAIAFBFGohAQsgAkEHSwRAA0AgAS0AByABLQAGIAEtAAUgAS0ABCABLQADIAEtAAIgAS0AASABLQAAIABB/wFxc0ECdEHQGGooAgAgAEEIdnMiAEH/AXFzQQJ0QdAYaigCACAAQQh2cyIAQf8BcXNBAnRB0BhqKAIAIABBCHZzIgBB/wFxc0ECdEHQGGooAgAgAEEIdnMiAEH/AXFzQQJ0QdAYaigCACAAQQh2cyIAQf8BcXNBAnRB0BhqKAIAIABBCHZzIgBB/wFxc0ECdEHQGGooAgAgAEEIdnMiAEH/AXFzQQJ0QdAYaigCACAAQQh2cyEAIAFBCGohASACQQhrIgJBB0sNAAsLAkAgAkUNACACQQFxBH8gAS0AACAAQf8BcXNBAnRB0BhqKAIAIABBCHZzIQAgAUEBaiEBIAJBAWsFIAILIQMgAkEBRg0AA0AgAS0AASABLQAAIABB/wFxc0ECdEHQGGooAgAgAEEIdnMiAEH/AXFzQQJ0QdAYaigCACAAQQh2cyEAIAFBAmohASADQQJrIgMNAAsLIABBf3MLwgIBA38jAEEQayIIJAACfwJAIAAEQCAEDQEgBVANAQsgBgRAIAZBADYCBCAGQRI2AgALQQAMAQtBgAEQCSIHRQRAIAYEQCAGQQA2AgQgBkEONgIAC0EADAELIAcgATcDCCAHQgA3AwAgB0EoaiIJECogByAFNwMYIAcgBDYCECAHIAM6AGAgB0EANgJsIAdCADcCZCAAKQMYIQEgCEF/NgIIIAhCjoCAgPAANwMAIAdBECAIECQgAUL/gQGDhCIBNwNwIAcgAadBBnZBAXE6AHgCQCACRQ0AIAkgAhBgQX9KDQAgBxAGQQAMAQsgBhBfIgIEQCAAIAAoAjBBAWo2AjAgAiAHNgIIIAJBATYCBCACIAA2AgAgAkI/IAAgB0EAQgBBDkEBEQoAIgEgAUIAUxs3AxgLIAILIQAgCEEQaiQAIAALYgEBf0E4EAkiAUUEQCAABEAgAEEANgIEIABBDjYCAAtBAA8LIAFBADYCCCABQgA3AwAgAUIANwMgIAFCgICAgBA3AiwgAUEAOgAoIAFBADYCFCABQgA3AgwgAUEAOwE0IAELuwEBAX4gASkDACICQgKDUEUEQCAAIAEpAxA3AxALIAJCBINQRQRAIAAgASkDGDcDGAsgAkIIg1BFBEAgACABKQMgNwMgCyACQhCDUEUEQCAAIAEoAig2AigLIAJCIINQRQRAIAAgASgCLDYCLAsgAkLAAINQRQRAIAAgAS8BMDsBMAsgAkKAAYNQRQRAIAAgAS8BMjsBMgsgAkKAAoNQRQRAIAAgASgCNDYCNAsgACAAKQMAIAKENwMAQQALGQAgAUUEQEEADwsgACABKAIAIAEzAQQQGws3AQJ/IABBACABG0UEQCAAIAFGDwsgAC8BBCIDIAEvAQRGBH8gACgCACABKAIAIAMQPQVBAQtFCyIBAX8gAUUEQEEADwsgARAJIgJFBEBBAA8LIAIgACABEAcLKQAgACABIAIgAyAEEEUiAEUEQEEADwsgACACQQAgBBA1IQEgABAGIAELcQEBfgJ/AkAgAkJ/VwRAIAMEQCADQQA2AgQgA0EUNgIACwwBCyAAIAEgAhARIgRCf1cEQCADBEAgAyAAKAIMNgIAIAMgACgCEDYCBAsMAQtBACACIARXDQEaIAMEQCADQQA2AgQgA0ERNgIACwtBfwsLNQAgACABIAJBABAmIgBFBEBBfw8LIAMEQCADIAAtAAk6AAALIAQEQCAEIAAoAkQ2AgALQQAL/AECAn8BfiMAQRBrIgMkAAJAIAAgA0EOaiABQYAGQQAQRiIARQRAIAIhAAwBCyADLwEOIgFBBUkEQCACIQAMAQsgAC0AAEEBRwRAIAIhAAwBCyAAIAGtQv//A4MQFyIBRQRAIAIhAAwBCyABEH0aAkAgARAVIAIEfwJ/IAIvAQQhAEEAIAIoAgAiBEUNABpBACAEIABB1IABKAIAEQAACwVBAAtHBEAgAiEADAELIAEgAS0AAAR+IAEpAwggASkDEH0FQgALIgVC//8DgxATIAWnQf//A3FBgBBBABA1IgBFBEAgAiEADAELIAIQEAsgARAICyADQRBqJAAgAAvmDwIIfwJ+IwBB4ABrIgckAEEeQS4gAxshCwJAAkAgAgRAIAIiBSIGLQAABH4gBikDCCAGKQMQfQVCAAsgC61aDQEgBARAIARBADYCBCAEQRM2AgALQn8hDQwCCyABIAutIAcgBBAtIgUNAEJ/IQ0MAQsgBUIEEBMoAABBoxJBqBIgAxsoAABHBEAgBARAIARBADYCBCAEQRM2AgALQn8hDSACDQEgBRAIDAELIABCADcDICAAQQA2AhggAEL/////DzcDECAAQQA7AQwgAEG/hig2AgggAEEBOgAGIABBADsBBCAAQQA2AgAgAEIANwNIIABBgIDYjXg2AkQgAEIANwMoIABCADcDMCAAQgA3AzggAEFAa0EAOwEAIABCADcDUCAAIAMEf0EABSAFEAwLOwEIIAAgBRAMOwEKIAAgBRAMOwEMIAAgBRAMNgIQIAUQDCEGIAUQDCEJIAdBADYCWCAHQgA3A1AgB0IANwNIIAcgCUEfcTYCPCAHIAZBC3Y2AjggByAGQQV2QT9xNgI0IAcgBkEBdEE+cTYCMCAHIAlBCXZB0ABqNgJEIAcgCUEFdkEPcUEBazYCQCAAIAdBMGoQBTYCFCAAIAUQFTYCGCAAIAUQFa03AyAgACAFEBWtNwMoIAUQDCEIIAUQDCEGIAACfiADBEBBACEJIABBADYCRCAAQQA7AUAgAEEANgI8QgAMAQsgBRAMIQkgACAFEAw2AjwgACAFEAw7AUAgACAFEBU2AkQgBRAVrQs3A0ggBS0AAEUEQCAEBEAgBEEANgIEIARBFDYCAAtCfyENIAINASAFEAgMAQsCQCAALwEMIgpBAXEEQCAKQcAAcQRAIABB//8DOwFSDAILIABBATsBUgwBCyAAQQA7AVILIABBADYCOCAAQgA3AzAgBiAIaiAJaiEKAkAgAgRAIAUtAAAEfiAFKQMIIAUpAxB9BUIACyAKrVoNASAEBEAgBEEANgIEIARBFTYCAAtCfyENDAILIAUQCCABIAqtQQAgBBAtIgUNAEJ/IQ0MAQsCQCAIRQ0AIAAgBSABIAhBASAEEGQiCDYCMCAIRQRAIAQoAgBBEUYEQCAEBEAgBEEANgIEIARBFTYCAAsLQn8hDSACDQIgBRAIDAILIAAtAA1BCHFFDQAgCEECECNBBUcNACAEBEAgBEEANgIEIARBFTYCAAtCfyENIAINASAFEAgMAQsgAEE0aiEIAkAgBkUNACAFIAEgBkEAIAQQRSIMRQRAQn8hDSACDQIgBRAIDAILIAwgBkGAAkGABCADGyAIIAQQbiEGIAwQBiAGRQRAQn8hDSACDQIgBRAIDAILIANFDQAgAEEBOgAECwJAIAlFDQAgACAFIAEgCUEAIAQQZCIBNgI4IAFFBEBCfyENIAINAiAFEAgMAgsgAC0ADUEIcUUNACABQQIQI0EFRw0AIAQEQCAEQQA2AgQgBEEVNgIAC0J/IQ0gAg0BIAUQCAwBCyAAIAAoAjRB9eABIAAoAjAQZzYCMCAAIAAoAjRB9cYBIAAoAjgQZzYCOAJAAkAgACkDKEL/////D1ENACAAKQMgQv////8PUQ0AIAApA0hC/////w9SDQELAkACQAJAIAgoAgAgB0EwakEBQYACQYAEIAMbIAQQRiIBRQRAIAJFDQEMAgsgASAHMwEwEBciAUUEQCAEBEAgBEEANgIEIARBDjYCAAsgAkUNAQwCCwJAIAApAyhC/////w9RBEAgACABEB03AygMAQsgA0UNAEEAIQYCQCABKQMQIg5CCHwiDSAOVA0AIAEpAwggDVQNACABIA03AxBBASEGCyABIAY6AAALIAApAyBC/////w9RBEAgACABEB03AyALAkAgAw0AIAApA0hC/////w9RBEAgACABEB03A0gLIAAoAjxB//8DRw0AIAAgARAVNgI8CyABLQAABH8gASkDECABKQMIUQVBAAsNAiAEBEAgBEEANgIEIARBFTYCAAsgARAIIAINAQsgBRAIC0J/IQ0MAgsgARAICyAFLQAARQRAIAQEQCAEQQA2AgQgBEEUNgIAC0J/IQ0gAg0BIAUQCAwBCyACRQRAIAUQCAtCfyENIAApA0hCf1cEQCAEBEAgBEEWNgIEIARBBDYCAAsMAQsjAEEQayIDJABBASEBAkAgACgCEEHjAEcNAEEAIQECQCAAKAI0IANBDmpBgbICQYAGQQAQRiICBEAgAy8BDiIFQQZLDQELIAQEQCAEQQA2AgQgBEEVNgIACwwBCyACIAWtQv//A4MQFyICRQRAIAQEQCAEQQA2AgQgBEEUNgIACwwBC0EBIQECQAJAAkAgAhAMQQFrDgICAQALQQAhASAEBEAgBEEANgIEIARBGDYCAAsgAhAIDAILIAApAyhCE1YhAQsgAkICEBMvAABBwYoBRwRAQQAhASAEBEAgBEEANgIEIARBGDYCAAsgAhAIDAELIAIQfUEBayIFQf8BcUEDTwRAQQAhASAEBEAgBEEANgIEIARBGDYCAAsgAhAIDAELIAMvAQ5BB0cEQEEAIQEgBARAIARBADYCBCAEQRU2AgALIAIQCAwBCyAAIAE6AAYgACAFQf8BcUGBAmo7AVIgACACEAw2AhAgAhAIQQEhAQsgA0EQaiQAIAFFDQAgCCAIKAIAEG02AgAgCiALaq0hDQsgB0HgAGokACANC4ECAQR/IwBBEGsiBCQAAkAgASAEQQxqQcAAQQAQJSIGRQ0AIAQoAgxBBWoiA0GAgARPBEAgAgRAIAJBADYCBCACQRI2AgALDAELQQAgA60QFyIDRQRAIAIEQCACQQA2AgQgAkEONgIACwwBCyADQQEQcCADIAEEfwJ/IAEvAQQhBUEAIAEoAgAiAUUNABpBACABIAVB1IABKAIAEQAACwVBAAsQEiADIAYgBCgCDBAsAn8gAy0AAEUEQCACBEAgAkEANgIEIAJBFDYCAAtBAAwBCyAAIAMtAAAEfiADKQMQBUIAC6dB//8DcSADKAIEEEcLIQUgAxAICyAEQRBqJAAgBQvgAQICfwF+QTAQCSICRQRAIAEEQCABQQA2AgQgAUEONgIAC0EADwsgAkIANwMIIAJBADYCACACQgA3AxAgAkIANwMYIAJCADcDICACQgA3ACUgAFAEQCACDwsCQCAAQv////8AVg0AIACnQQR0EAkiA0UNACACIAM2AgBBACEBQgEhBANAIAMgAUEEdGoiAUIANwIAIAFCADcABSAAIARSBEAgBKchASAEQgF8IQQMAQsLIAIgADcDCCACIAA3AxAgAg8LIAEEQCABQQA2AgQgAUEONgIAC0EAEBAgAhAGQQAL7gECA38BfiMAQRBrIgQkAAJAIARBDGpCBBAXIgNFBEBBfyECDAELAkAgAQRAIAJBgAZxIQUDQAJAIAUgASgCBHFFDQACQCADKQMIQgBUBEAgA0EAOgAADAELIANCADcDECADQQE6AAALIAMgAS8BCBANIAMgAS8BChANIAMtAABFBEAgAEEIaiIABEAgAEEANgIEIABBFDYCAAtBfyECDAQLQX8hAiAAIARBDGpCBBAbQQBIDQMgATMBCiIGUA0AIAAgASgCDCAGEBtBAEgNAwsgASgCACIBDQALC0EAIQILIAMQCAsgBEEQaiQAIAILPAEBfyAABEAgAUGABnEhAQNAIAEgACgCBHEEQCACIAAvAQpqQQRqIQILIAAoAgAiAA0ACwsgAkH//wNxC5wBAQN/IABFBEBBAA8LIAAhAwNAAn8CQAJAIAAvAQgiAUH04AFNBEAgAUEBRg0BIAFB9cYBRg0BDAILIAFBgbICRg0AIAFB9eABRw0BCyAAKAIAIQEgAEEANgIAIAAoAgwQBiAAEAYgASADIAAgA0YbIQMCQCACRQRAQQAhAgwBCyACIAE2AgALIAEMAQsgACICKAIACyIADQALIAMLsgQCBX8BfgJAAkACQCAAIAGtEBciAQRAIAEtAAANAUEAIQAMAgsgBARAIARBADYCBCAEQQ42AgALQQAPC0EAIQADQCABLQAABH4gASkDCCABKQMQfQVCAAtCBFQNASABEAwhByABIAEQDCIGrRATIghFBEBBACECIAQEQCAEQQA2AgQgBEEVNgIACyABEAggAEUNAwNAIAAoAgAhASAAKAIMEAYgABAGIAEiAA0ACwwDCwJAAkBBEBAJIgUEQCAFIAY7AQogBSAHOwEIIAUgAjYCBCAFQQA2AgAgBkUNASAFIAggBhBjIgY2AgwgBg0CIAUQBgtBACECIAQEQCAEQQA2AgQgBEEONgIACyABEAggAEUNBANAIAAoAgAhASAAKAIMEAYgABAGIAEiAA0ACwwECyAFQQA2AgwLAkAgAEUEQCAFIQAMAQsgCSAFNgIACyAFIQkgAS0AAA0ACwsCQCABLQAABH8gASkDECABKQMIUQVBAAsNACABIAEtAAAEfiABKQMIIAEpAxB9BUIACyIKQv////8PgxATIQICQCAKpyIFQQNLDQAgAkUNACACQcEUIAUQPUUNAQtBACECIAQEQCAEQQA2AgQgBEEVNgIACyABEAggAEUNAQNAIAAoAgAhASAAKAIMEAYgABAGIAEiAA0ACwwBCyABEAggAwRAIAMgADYCAEEBDwtBASECIABFDQADQCAAKAIAIQEgACgCDBAGIAAQBiABIgANAAsLIAILvgEBBX8gAAR/IAAhAgNAIAIiBCgCACICDQALIAEEQANAIAEiAy8BCCEGIAMoAgAhASAAIQICQAJAA0ACQCACLwEIIAZHDQAgAi8BCiIFIAMvAQpHDQAgBUUNAiACKAIMIAMoAgwgBRA9RQ0CCyACKAIAIgINAAsgA0EANgIAIAQgAzYCACADIQQMAQsgAiACKAIEIAMoAgRBgAZxcjYCBCADQQA2AgAgAygCDBAGIAMQBgsgAQ0ACwsgAAUgAQsLVQICfgF/AkACQCAALQAARQ0AIAApAxAiAkIBfCIDIAJUDQAgAyAAKQMIWA0BCyAAQQA6AAAPCyAAKAIEIgRFBEAPCyAAIAM3AxAgBCACp2ogAToAAAt9AQN/IwBBEGsiAiQAIAIgATYCDEF/IQMCQCAALQAoDQACQCAAKAIAIgRFDQAgBCABEHFBf0oNACAAKAIAIQEgAEEMaiIABEAgACABKAIMNgIAIAAgASgCEDYCBAsMAQsgACACQQxqQgRBExAOQj+HpyEDCyACQRBqJAAgAwvdAQEDfyABIAApAzBaBEAgAEEIagRAIABBADYCDCAAQRI2AggLQX8PCyAAQQhqIQIgAC0AGEECcQRAIAIEQCACQQA2AgQgAkEZNgIAC0F/DwtBfyEDAkAgACABQQAgAhBTIgRFDQAgACgCUCAEIAIQfkUNAAJ/IAEgACkDMFoEQCAAQQhqBEAgAEEANgIMIABBEjYCCAtBfwwBCyABp0EEdCICIAAoAkBqKAIEECAgACgCQCACaiICQQA2AgQgAhBAQQALDQAgACgCQCABp0EEdGpBAToADEEAIQMLIAMLpgIBBX9BfyEFAkAgACABQQBBABAmRQ0AIAAtABhBAnEEQCAAQQhqIgAEQCAAQQA2AgQgAEEZNgIAC0F/DwsCfyAAKAJAIgQgAaciBkEEdGooAgAiBUUEQCADQYCA2I14RyEHQQMMAQsgBSgCRCADRyEHIAUtAAkLIQggBCAGQQR0aiIEIQYgBCgCBCEEQQAgAiAIRiAHG0UEQAJAIAQNACAGIAUQKyIENgIEIAQNACAAQQhqIgAEQCAAQQA2AgQgAEEONgIAC0F/DwsgBCADNgJEIAQgAjoACSAEIAQoAgBBEHI2AgBBAA8LQQAhBSAERQ0AIAQgBCgCAEFvcSIANgIAIABFBEAgBBAgIAZBADYCBEEADwsgBCADNgJEIAQgCDoACQsgBQvjCAIFfwR+IAAtABhBAnEEQCAAQQhqBEAgAEEANgIMIABBGTYCCAtCfw8LIAApAzAhCwJAIANBgMAAcQRAIAAgASADQQAQTCIJQn9SDQELAn4CQAJAIAApAzAiCUIBfCIMIAApAzgiClQEQCAAKAJAIQQMAQsgCkIBhiIJQoAIIAlCgAhUGyIJQhAgCUIQVhsgCnwiCadBBHQiBK0gCkIEhkLw////D4NUDQEgACgCQCAEEDQiBEUNASAAIAk3AzggACAENgJAIAApAzAiCUIBfCEMCyAAIAw3AzAgBCAJp0EEdGoiBEIANwIAIARCADcABSAJDAELIABBCGoEQCAAQQA2AgwgAEEONgIIC0J/CyIJQgBZDQBCfw8LAkAgAUUNAAJ/QQAhBCAJIAApAzBaBEAgAEEIagRAIABBADYCDCAAQRI2AggLQX8MAQsgAC0AGEECcQRAIABBCGoEQCAAQQA2AgwgAEEZNgIIC0F/DAELAkAgAUUNACABLQAARQ0AQX8gASABECJB//8DcSADIABBCGoQNSIERQ0BGiADQYAwcQ0AIARBABAjQQNHDQAgBEECNgIICwJAIAAgAUEAQQAQTCIKQgBTIgENACAJIApRDQAgBBAQIABBCGoEQCAAQQA2AgwgAEEKNgIIC0F/DAELAkAgAUEBIAkgClEbRQ0AAkACfwJAIAAoAkAiASAJpyIFQQR0aiIGKAIAIgMEQCADKAIwIAQQYg0BCyAEIAYoAgQNARogBiAGKAIAECsiAzYCBCAEIAMNARogAEEIagRAIABBADYCDCAAQQ42AggLDAILQQEhByAGKAIAKAIwC0EAQQAgAEEIaiIDECUiCEUNAAJAAkAgASAFQQR0aiIFKAIEIgENACAGKAIAIgENAEEAIQEMAQsgASgCMCIBRQRAQQAhAQwBCyABQQBBACADECUiAUUNAQsgACgCUCAIIAlBACADEE1FDQAgAQRAIAAoAlAgAUEAEH4aCyAFKAIEIQMgBwRAIANFDQIgAy0AAEECcUUNAiADKAIwEBAgBSgCBCIBIAEoAgBBfXEiAzYCACADRQRAIAEQICAFQQA2AgQgBBAQQQAMBAsgASAGKAIAKAIwNgIwIAQQEEEADAMLIAMoAgAiAUECcQRAIAMoAjAQECAFKAIEIgMoAgAhAQsgAyAENgIwIAMgAUECcjYCAEEADAILIAQQEEF/DAELIAQQEEEAC0UNACALIAApAzBRBEBCfw8LIAAoAkAgCadBBHRqED4gACALNwMwQn8PCyAJpyIGQQR0IgEgACgCQGoQQAJAAkAgACgCQCIEIAFqIgMoAgAiBUUNAAJAIAMoAgQiAwRAIAMoAgAiAEEBcUUNAQwCCyAFECshAyAAKAJAIgQgBkEEdGogAzYCBCADRQ0CIAMoAgAhAAsgA0F+NgIQIAMgAEEBcjYCAAsgASAEaiACNgIIIAkPCyAAQQhqBEAgAEEANgIMIABBDjYCCAtCfwteAQF/IwBBEGsiAiQAAn8gACgCJEEBRwRAIABBDGoiAARAIABBADYCBCAAQRI2AgALQX8MAQsgAkEANgIIIAIgATcDACAAIAJCEEEMEA5CP4enCyEAIAJBEGokACAAC9oDAQZ/IwBBEGsiBSQAIAUgAjYCDCMAQaABayIEJAAgBEEIakHA8ABBkAEQBxogBCAANgI0IAQgADYCHCAEQX4gAGsiA0H/////ByADQf////8HSRsiBjYCOCAEIAAgBmoiADYCJCAEIAA2AhggBEEIaiEAIwBB0AFrIgMkACADIAI2AswBIANBoAFqQQBBKBAZIAMgAygCzAE2AsgBAkBBACABIANByAFqIANB0ABqIANBoAFqEEpBAEgNACAAKAJMQQBOIQcgACgCACECIAAsAEpBAEwEQCAAIAJBX3E2AgALIAJBIHEhCAJ/IAAoAjAEQCAAIAEgA0HIAWogA0HQAGogA0GgAWoQSgwBCyAAQdAANgIwIAAgA0HQAGo2AhAgACADNgIcIAAgAzYCFCAAKAIsIQIgACADNgIsIAAgASADQcgBaiADQdAAaiADQaABahBKIAJFDQAaIABBAEEAIAAoAiQRAAAaIABBADYCMCAAIAI2AiwgAEEANgIcIABBADYCECAAKAIUGiAAQQA2AhRBAAsaIAAgACgCACAIcjYCACAHRQ0ACyADQdABaiQAIAYEQCAEKAIcIgAgACAEKAIYRmtBADoAAAsgBEGgAWokACAFQRBqJAALUwEDfwJAIAAoAgAsAABBMGtBCk8NAANAIAAoAgAiAiwAACEDIAAgAkEBajYCACABIANqQTBrIQEgAiwAAUEwa0EKTw0BIAFBCmwhAQwACwALIAELuwIAAkAgAUEUSw0AAkACQAJAAkACQAJAAkACQAJAAkAgAUEJaw4KAAECAwQFBgcICQoLIAIgAigCACIBQQRqNgIAIAAgASgCADYCAA8LIAIgAigCACIBQQRqNgIAIAAgATQCADcDAA8LIAIgAigCACIBQQRqNgIAIAAgATUCADcDAA8LIAIgAigCAEEHakF4cSIBQQhqNgIAIAAgASkDADcDAA8LIAIgAigCACIBQQRqNgIAIAAgATIBADcDAA8LIAIgAigCACIBQQRqNgIAIAAgATMBADcDAA8LIAIgAigCACIBQQRqNgIAIAAgATAAADcDAA8LIAIgAigCACIBQQRqNgIAIAAgATEAADcDAA8LIAIgAigCAEEHakF4cSIBQQhqNgIAIAAgASsDADkDAA8LIAAgAkEAEQcACwubAgAgAEUEQEEADwsCfwJAIAAEfyABQf8ATQ0BAkBB9IIBKAIAKAIARQRAIAFBgH9xQYC/A0YNAwwBCyABQf8PTQRAIAAgAUE/cUGAAXI6AAEgACABQQZ2QcABcjoAAEECDAQLIAFBgLADT0EAIAFBgEBxQYDAA0cbRQRAIAAgAUE/cUGAAXI6AAIgACABQQx2QeABcjoAACAAIAFBBnZBP3FBgAFyOgABQQMMBAsgAUGAgARrQf//P00EQCAAIAFBP3FBgAFyOgADIAAgAUESdkHwAXI6AAAgACABQQZ2QT9xQYABcjoAAiAAIAFBDHZBP3FBgAFyOgABQQQMBAsLQYSEAUEZNgIAQX8FQQELDAELIAAgAToAAEEBCwvjAQECfyACQQBHIQMCQAJAAkAgAEEDcUUNACACRQ0AIAFB/wFxIQQDQCAALQAAIARGDQIgAkEBayICQQBHIQMgAEEBaiIAQQNxRQ0BIAINAAsLIANFDQELAkAgAC0AACABQf8BcUYNACACQQRJDQAgAUH/AXFBgYKECGwhAwNAIAAoAgAgA3MiBEF/cyAEQYGChAhrcUGAgYKEeHENASAAQQRqIQAgAkEEayICQQNLDQALCyACRQ0AIAFB/wFxIQEDQCABIAAtAABGBEAgAA8LIABBAWohACACQQFrIgINAAsLQQALeQEBfAJAIABFDQAgACsDECAAKwMgIgIgAUQAAAAAAAAAACABRAAAAAAAAAAAZBsiAUQAAAAAAADwPyABRAAAAAAAAPA/YxsgACsDKCACoaKgIgEgACsDGKFjRQ0AIAAoAgAgASAAKAIMIAAoAgQRDgAgACABOQMYCwtIAQF8AkAgAEUNACAAKwMQIAArAyAiASAAKwMoIAGhoCIBIAArAxihY0UNACAAKAIAIAEgACgCDCAAKAIEEQ4AIAAgATkDGAsLWgICfgF/An8CQAJAIAAtAABFDQAgACkDECIBQgF8IgIgAVQNACACIAApAwhYDQELIABBADoAAEEADAELQQAgACgCBCIDRQ0AGiAAIAI3AxAgAyABp2otAAALC4IEAgZ/AX4gAEEAIAEbRQRAIAIEQCACQQA2AgQgAkESNgIAC0EADwsCQAJAIAApAwhQDQAgACgCECABLQAAIgQEf0Kl6wohCSABIQMDQCAJIAStQv8Bg3whCSADLQABIgQEQCADQQFqIQMgCUL/////D4NCIX4hCQwBCwsgCacFQYUqCyIEIAAoAgBwQQJ0aiIGKAIAIgNFDQADQAJAIAMoAhwgBEcNACABIAMoAgAQOA0AAkAgAykDCEJ/UQRAIAMoAhghAQJAIAUEQCAFIAE2AhgMAQsgBiABNgIACyADEAYgACAAKQMIQgF9Igk3AwggCbogACgCACIBuER7FK5H4XqEP6JjRQ0BIAFBgQJJDQECf0EAIQMgACgCACIGIAFBAXYiBUcEQCAFEDwiB0UEQCACBEAgAkEANgIEIAJBDjYCAAtBAAwCCwJAIAApAwhCACAGG1AEQCAAKAIQIQQMAQsgACgCECEEA0AgBCADQQJ0aigCACIBBEADQCABKAIYIQIgASAHIAEoAhwgBXBBAnRqIggoAgA2AhggCCABNgIAIAIiAQ0ACwsgA0EBaiIDIAZHDQALCyAEEAYgACAFNgIAIAAgBzYCEAtBAQsNAQwFCyADQn83AxALQQEPCyADIgUoAhgiAw0ACwsgAgRAIAJBADYCBCACQQk2AgALC0EAC6UGAgl/AX4jAEHwAGsiBSQAAkACQCAARQ0AAkAgAQRAIAEpAzAgAlYNAQtBACEDIABBCGoEQCAAQQA2AgwgAEESNgIICwwCCwJAIANBCHENACABKAJAIAKnQQR0aiIGKAIIRQRAIAYtAAxFDQELQQAhAyAAQQhqBEAgAEEANgIMIABBDzYCCAsMAgsgASACIANBCHIgBUE4ahCKAUF/TARAQQAhAyAAQQhqBEAgAEEANgIMIABBFDYCCAsMAgsgA0EDdkEEcSADciIGQQRxIQcgBSkDUCEOIAUvAWghCQJAIANBIHFFIAUvAWpBAEdxIgtFDQAgBA0AIAAoAhwiBA0AQQAhAyAAQQhqBEAgAEEANgIMIABBGjYCCAsMAgsgBSkDWFAEQCAAQQBCAEEAEFIhAwwCCwJAIAdFIgwgCUEAR3EiDUEBckUEQEEAIQMgBUEAOwEwIAUgDjcDICAFIA43AxggBSAFKAJgNgIoIAVC3AA3AwAgASgCACAOIAVBACABIAIgAEEIahBeIgYNAQwDC0EAIQMgASACIAYgAEEIaiIGECYiB0UNAiABKAIAIAUpA1ggBUE4aiAHLwEMQQF2QQNxIAEgAiAGEF4iBkUNAgsCfyAGIAE2AiwCQCABKAJEIghBAWoiCiABKAJIIgdJBEAgASgCTCEHDAELIAEoAkwgB0EKaiIIQQJ0EDQiB0UEQCABQQhqBEAgAUEANgIMIAFBDjYCCAtBfwwCCyABIAc2AkwgASAINgJIIAEoAkQiCEEBaiEKCyABIAo2AkQgByAIQQJ0aiAGNgIAQQALQX9MBEAgBhALDAELAkAgC0UEQCAGIQEMAQtBJkEAIAUvAWpBAUYbIgFFBEAgAEEIagRAIABBADYCDCAAQRg2AggLDAMLIAAgBiAFLwFqQQAgBCABEQYAIQEgBhALIAFFDQILAkAgDUUEQCABIQMMAQsgACABIAUvAWgQgQEhAyABEAsgA0UNAQsCQCAJRSAMckUEQCADIQEMAQsgACADQQEQgAEhASADEAsgAUUNAQsgASEDDAELQQAhAwsgBUHwAGokACADC4UBAQF/IAFFBEAgAEEIaiIABEAgAEEANgIEIABBEjYCAAtBAA8LQTgQCSIDRQRAIABBCGoiAARAIABBADYCBCAAQQ42AgALQQAPCyADQQA2AhAgA0IANwIIIANCADcDKCADQQA2AgQgAyACNgIAIANCADcDGCADQQA2AjAgACABQTsgAxBCCw8AIAAgASACQQBBABCCAQusAgECfyABRQRAIABBCGoiAARAIABBADYCBCAAQRI2AgALQQAPCwJAIAJBfUsNACACQf//A3FBCEYNACAAQQhqIgAEQCAAQQA2AgQgAEEQNgIAC0EADwsCQEGwwAAQCSIFBEAgBUEANgIIIAVCADcCACAFQYiBAUGogQEgAxs2AqhAIAUgAjYCFCAFIAM6ABAgBUEAOgAPIAVBADsBDCAFIAMgAkF9SyIGcToADiAFQQggAiAGG0H//wNxIAQgBUGIgQFBqIEBIAMbKAIAEQAAIgI2AqxAIAINASAFEDEgBRAGCyAAQQhqIgAEQCAAQQA2AgQgAEEONgIAC0EADwsgACABQTogBRBCIgAEfyAABSAFKAKsQCAFKAKoQCgCBBEDACAFEDEgBRAGQQALC6ABAQF/IAIgACgCBCIDIAIgA0kbIgIEQCAAIAMgAms2AgQCQAJAAkACQCAAKAIcIgMoAhRBAWsOAgEAAgsgA0GgAWogASAAKAIAIAJB3IABKAIAEQgADAILIAAgACgCMCABIAAoAgAgAkHEgAEoAgARBAA2AjAMAQsgASAAKAIAIAIQBxoLIAAgACgCACACajYCACAAIAAoAgggAmo2AggLC7cCAQR/QX4hAgJAIABFDQAgACgCIEUNACAAKAIkIgRFDQAgACgCHCIBRQ0AIAEoAgAgAEcNAAJAAkAgASgCICIDQTlrDjkBAgICAgICAgICAgIBAgICAQICAgICAgICAgICAgICAgICAQICAgICAgICAgICAQICAgICAgICAgEACyADQZoFRg0AIANBKkcNAQsCfwJ/An8gASgCBCICBEAgBCAAKAIoIAIQHiAAKAIcIQELIAEoAlAiAgsEQCAAKAIkIAAoAiggAhAeIAAoAhwhAQsgASgCTCICCwRAIAAoAiQgACgCKCACEB4gACgCHCEBCyABKAJIIgILBEAgACgCJCAAKAIoIAIQHiAAKAIcIQELIAAoAiQgACgCKCABEB4gAEEANgIcQX1BACADQfEARhshAgsgAgvrCQEIfyAAKAIwIgMgACgCDEEFayICIAIgA0sbIQggACgCACIEKAIEIQkgAUEERiEHAkADQCAEKAIQIgMgACgCoC5BKmpBA3UiAkkEQEEBIQYMAgsgCCADIAJrIgMgACgCaCAAKAJYayICIAQoAgRqIgVB//8DIAVB//8DSRsiBiADIAZJGyIDSwRAQQEhBiADQQBHIAdyRQ0CIAFFDQIgAyAFRw0CCyAAQQBBACAHIAMgBUZxIgUQOSAAIAAoAhBBBGsiBDYCECAAKAIEIARqIAM7AAAgACAAKAIQQQJqIgQ2AhAgACgCBCAEaiADQX9zOwAAIAAgACgCEEECajYCECAAKAIAEAoCfyACBEAgACgCACgCDCAAKAJIIAAoAlhqIAMgAiACIANLGyICEAcaIAAoAgAiBCAEKAIMIAJqNgIMIAQgBCgCECACazYCECAEIAQoAhQgAmo2AhQgACAAKAJYIAJqNgJYIAMgAmshAwsgAwsEQCAAKAIAIgIgAigCDCADEIMBIAAoAgAiAiACKAIMIANqNgIMIAIgAigCECADazYCECACIAIoAhQgA2o2AhQLIAAoAgAhBCAFRQ0AC0EAIQYLAkAgCSAEKAIEayICRQRAIAAoAmghAwwBCwJAIAAoAjAiAyACTQRAIABBAjYCgC4gACgCSCAEKAIAIANrIAMQBxogACAAKAIwIgM2AoQuIAAgAzYCaAwBCyACIAAoAkQgACgCaCIFa08EQCAAIAUgA2siBDYCaCAAKAJIIgUgAyAFaiAEEAcaIAAoAoAuIgNBAU0EQCAAIANBAWo2AoAuCyAAIAAoAmgiBSAAKAKELiIDIAMgBUsbNgKELiAAKAIAIQQLIAAoAkggBWogBCgCACACayACEAcaIAAgACgCaCACaiIDNgJoIAAgACgCMCAAKAKELiIEayIFIAIgAiAFSxsgBGo2AoQuCyAAIAM2AlgLIAAgAyAAKAJAIgIgAiADSRs2AkBBAyECAkAgBkUNACAAKAIAIgUoAgQhAgJAAkAgAUF7cUUNACACDQBBASECIAMgACgCWEYNAiAAKAJEIANrIQRBACECDAELIAIgACgCRCADayIETQ0AIAAoAlgiByAAKAIwIgZIDQAgACADIAZrIgM2AmggACAHIAZrNgJYIAAoAkgiAiACIAZqIAMQBxogACgCgC4iA0EBTQRAIAAgA0EBajYCgC4LIAAgACgCaCIDIAAoAoQuIgIgAiADSxs2AoQuIAAoAjAgBGohBCAAKAIAIgUoAgQhAgsCQCACIAQgAiAESRsiAkUEQCAAKAIwIQUMAQsgBSAAKAJIIANqIAIQgwEgACAAKAJoIAJqIgM2AmggACAAKAIwIgUgACgChC4iBGsiBiACIAIgBksbIARqNgKELgsgACADIAAoAkAiAiACIANJGzYCQCADIAAoAlgiBmsiAyAFIAAoAgwgACgCoC5BKmpBA3VrIgJB//8DIAJB//8DSRsiBCAEIAVLG0kEQEEAIQIgAUEERiADQQBHckUNASABRQ0BIAAoAgAoAgQNASADIARLDQELQQAhAiABQQRGBEAgACgCACgCBEUgAyAETXEhAgsgACAAKAJIIAZqIAQgAyADIARLGyIBIAIQOSAAIAAoAlggAWo2AlggACgCABAKQQJBACACGw8LIAIL/woCCn8DfiAAKQOYLiENIAAoAqAuIQQgAkEATgRAQQRBAyABLwECIggbIQlBB0GKASAIGyEFQX8hCgNAIAghByABIAsiDEEBaiILQQJ0ai8BAiEIAkACQCAGQQFqIgMgBU4NACAHIAhHDQAgAyEGDAELAkAgAyAJSARAIAAgB0ECdGoiBkHOFWohCSAGQcwVaiEKA0AgCjMBACEPAn8gBCAJLwEAIgZqIgVBP00EQCAPIASthiANhCENIAUMAQsgBEHAAEYEQCAAKAIEIAAoAhBqIA03AAAgACAAKAIQQQhqNgIQIA8hDSAGDAELIAAoAgQgACgCEGogDyAErYYgDYQ3AAAgACAAKAIQQQhqNgIQIA9BwAAgBGutiCENIAVBQGoLIQQgA0EBayIDDQALDAELIAcEQAJAIAcgCkYEQCANIQ8gBCEFIAMhBgwBCyAAIAdBAnRqIgNBzBVqMwEAIQ8gBCADQc4Vai8BACIDaiIFQT9NBEAgDyAErYYgDYQhDwwBCyAEQcAARgRAIAAoAgQgACgCEGogDTcAACAAIAAoAhBBCGo2AhAgAyEFDAELIAAoAgQgACgCEGogDyAErYYgDYQ3AAAgACAAKAIQQQhqNgIQIAVBQGohBSAPQcAAIARrrYghDwsgADMBjBYhDgJAIAUgAC8BjhYiBGoiA0E/TQRAIA4gBa2GIA+EIQ4MAQsgBUHAAEYEQCAAKAIEIAAoAhBqIA83AAAgACAAKAIQQQhqNgIQIAQhAwwBCyAAKAIEIAAoAhBqIA4gBa2GIA+ENwAAIAAgACgCEEEIajYCECADQUBqIQMgDkHAACAFa62IIQ4LIAasQgN9IQ0gA0E9TQRAIANBAmohBCANIAOthiAOhCENDAILIANBwABGBEAgACgCBCAAKAIQaiAONwAAIAAgACgCEEEIajYCEEECIQQMAgsgACgCBCAAKAIQaiANIAOthiAOhDcAACAAIAAoAhBBCGo2AhAgA0E+ayEEIA1BwAAgA2utiCENDAELIAZBCUwEQCAAMwGQFiEOAkAgBCAALwGSFiIFaiIDQT9NBEAgDiAErYYgDYQhDgwBCyAEQcAARgRAIAAoAgQgACgCEGogDTcAACAAIAAoAhBBCGo2AhAgBSEDDAELIAAoAgQgACgCEGogDiAErYYgDYQ3AAAgACAAKAIQQQhqNgIQIANBQGohAyAOQcAAIARrrYghDgsgBqxCAn0hDSADQTxNBEAgA0EDaiEEIA0gA62GIA6EIQ0MAgsgA0HAAEYEQCAAKAIEIAAoAhBqIA43AAAgACAAKAIQQQhqNgIQQQMhBAwCCyAAKAIEIAAoAhBqIA0gA62GIA6ENwAAIAAgACgCEEEIajYCECADQT1rIQQgDUHAACADa62IIQ0MAQsgADMBlBYhDgJAIAQgAC8BlhYiBWoiA0E/TQRAIA4gBK2GIA2EIQ4MAQsgBEHAAEYEQCAAKAIEIAAoAhBqIA03AAAgACAAKAIQQQhqNgIQIAUhAwwBCyAAKAIEIAAoAhBqIA4gBK2GIA2ENwAAIAAgACgCEEEIajYCECADQUBqIQMgDkHAACAEa62IIQ4LIAatQgp9IQ0gA0E4TQRAIANBB2ohBCANIAOthiAOhCENDAELIANBwABGBEAgACgCBCAAKAIQaiAONwAAIAAgACgCEEEIajYCEEEHIQQMAQsgACgCBCAAKAIQaiANIAOthiAOhDcAACAAIAAoAhBBCGo2AhAgA0E5ayEEIA1BwAAgA2utiCENC0EAIQYCfyAIRQRAQYoBIQVBAwwBC0EGQQcgByAIRiIDGyEFQQNBBCADGwshCSAHIQoLIAIgDEcNAAsLIAAgBDYCoC4gACANNwOYLgv5BQIIfwJ+AkAgACgC8C1FBEAgACkDmC4hCyAAKAKgLiEDDAELA0AgCSIDQQNqIQkgAyAAKALsLWoiAy0AAiEFIAApA5guIQwgACgCoC4hBAJAIAMvAAAiB0UEQCABIAVBAnRqIgMzAQAhCyAEIAMvAQIiBWoiA0E/TQRAIAsgBK2GIAyEIQsMAgsgBEHAAEYEQCAAKAIEIAAoAhBqIAw3AAAgACAAKAIQQQhqNgIQIAUhAwwCCyAAKAIEIAAoAhBqIAsgBK2GIAyENwAAIAAgACgCEEEIajYCECADQUBqIQMgC0HAACAEa62IIQsMAQsgBUGAzwBqLQAAIghBAnQiBiABaiIDQYQIajMBACELIANBhghqLwEAIQMgCEEIa0ETTQRAIAUgBkGA0QBqKAIAa60gA62GIAuEIQsgBkHA0wBqKAIAIANqIQMLIAMgAiAHQQFrIgcgB0EHdkGAAmogB0GAAkkbQYDLAGotAAAiBUECdCIIaiIKLwECaiEGIAozAQAgA62GIAuEIQsgBCAFQQRJBH8gBgUgByAIQYDSAGooAgBrrSAGrYYgC4QhCyAIQcDUAGooAgAgBmoLIgVqIgNBP00EQCALIASthiAMhCELDAELIARBwABGBEAgACgCBCAAKAIQaiAMNwAAIAAgACgCEEEIajYCECAFIQMMAQsgACgCBCAAKAIQaiALIASthiAMhDcAACAAIAAoAhBBCGo2AhAgA0FAaiEDIAtBwAAgBGutiCELCyAAIAs3A5guIAAgAzYCoC4gCSAAKALwLUkNAAsLIAFBgAhqMwEAIQwCQCADIAFBgghqLwEAIgJqIgFBP00EQCAMIAOthiALhCEMDAELIANBwABGBEAgACgCBCAAKAIQaiALNwAAIAAgACgCEEEIajYCECACIQEMAQsgACgCBCAAKAIQaiAMIAOthiALhDcAACAAIAAoAhBBCGo2AhAgAUFAaiEBIAxBwAAgA2utiCEMCyAAIAw3A5guIAAgATYCoC4L8AQBA38gAEHkAWohAgNAIAIgAUECdCIDakEAOwEAIAIgA0EEcmpBADsBACABQQJqIgFBngJHDQALIABBADsBzBUgAEEAOwHYEyAAQZQWakEAOwEAIABBkBZqQQA7AQAgAEGMFmpBADsBACAAQYgWakEAOwEAIABBhBZqQQA7AQAgAEGAFmpBADsBACAAQfwVakEAOwEAIABB+BVqQQA7AQAgAEH0FWpBADsBACAAQfAVakEAOwEAIABB7BVqQQA7AQAgAEHoFWpBADsBACAAQeQVakEAOwEAIABB4BVqQQA7AQAgAEHcFWpBADsBACAAQdgVakEAOwEAIABB1BVqQQA7AQAgAEHQFWpBADsBACAAQcwUakEAOwEAIABByBRqQQA7AQAgAEHEFGpBADsBACAAQcAUakEAOwEAIABBvBRqQQA7AQAgAEG4FGpBADsBACAAQbQUakEAOwEAIABBsBRqQQA7AQAgAEGsFGpBADsBACAAQagUakEAOwEAIABBpBRqQQA7AQAgAEGgFGpBADsBACAAQZwUakEAOwEAIABBmBRqQQA7AQAgAEGUFGpBADsBACAAQZAUakEAOwEAIABBjBRqQQA7AQAgAEGIFGpBADsBACAAQYQUakEAOwEAIABBgBRqQQA7AQAgAEH8E2pBADsBACAAQfgTakEAOwEAIABB9BNqQQA7AQAgAEHwE2pBADsBACAAQewTakEAOwEAIABB6BNqQQA7AQAgAEHkE2pBADsBACAAQeATakEAOwEAIABB3BNqQQA7AQAgAEIANwL8LSAAQeQJakEBOwEAIABBADYC+C0gAEEANgLwLQuKAwIGfwR+QcgAEAkiBEUEQEEADwsgBEIANwMAIARCADcDMCAEQQA2AiggBEIANwMgIARCADcDGCAEQgA3AxAgBEIANwMIIARCADcDOCABUARAIARBCBAJIgA2AgQgAEUEQCAEEAYgAwRAIANBADYCBCADQQ42AgALQQAPCyAAQgA3AwAgBA8LAkAgAaciBUEEdBAJIgZFDQAgBCAGNgIAIAVBA3RBCGoQCSIFRQ0AIAQgATcDECAEIAU2AgQDQCAAIAynIghBBHRqIgcpAwgiDVBFBEAgBygCACIHRQRAIAMEQCADQQA2AgQgA0ESNgIACyAGEAYgBRAGIAQQBkEADwsgBiAKp0EEdGoiCSANNwMIIAkgBzYCACAFIAhBA3RqIAs3AwAgCyANfCELIApCAXwhCgsgDEIBfCIMIAFSDQALIAQgCjcDCCAEQgAgCiACGzcDGCAFIAqnQQN0aiALNwMAIAQgCzcDMCAEDwsgAwRAIANBADYCBCADQQ42AgALIAYQBiAEEAZBAAvlAQIDfwF+QX8hBQJAIAAgASACQQAQJiIERQ0AIAAgASACEIsBIgZFDQACfgJAIAJBCHENACAAKAJAIAGnQQR0aigCCCICRQ0AIAIgAxAhQQBOBEAgAykDAAwCCyAAQQhqIgAEQCAAQQA2AgQgAEEPNgIAC0F/DwsgAxAqIAMgBCgCGDYCLCADIAQpAyg3AxggAyAEKAIUNgIoIAMgBCkDIDcDICADIAQoAhA7ATAgAyAELwFSOwEyQvwBQtwBIAQtAAYbCyEHIAMgBjYCCCADIAE3AxAgAyAHQgOENwMAQQAhBQsgBQspAQF/IAAgASACIABBCGoiABAmIgNFBEBBAA8LIAMoAjBBACACIAAQJQuAAwEGfwJ/An9BMCABQYB/Sw0BGgJ/IAFBgH9PBEBBhIQBQTA2AgBBAAwBC0EAQRAgAUELakF4cSABQQtJGyIFQcwAahAJIgFFDQAaIAFBCGshAgJAIAFBP3FFBEAgAiEBDAELIAFBBGsiBigCACIHQXhxIAFBP2pBQHFBCGsiASABQUBrIAEgAmtBD0sbIgEgAmsiA2shBCAHQQNxRQRAIAIoAgAhAiABIAQ2AgQgASACIANqNgIADAELIAEgBCABKAIEQQFxckECcjYCBCABIARqIgQgBCgCBEEBcjYCBCAGIAMgBigCAEEBcXJBAnI2AgAgAiADaiIEIAQoAgRBAXI2AgQgAiADEDsLAkAgASgCBCICQQNxRQ0AIAJBeHEiAyAFQRBqTQ0AIAEgBSACQQFxckECcjYCBCABIAVqIgIgAyAFayIFQQNyNgIEIAEgA2oiAyADKAIEQQFyNgIEIAIgBRA7CyABQQhqCyIBRQsEQEEwDwsgACABNgIAQQALCwoAIABBiIQBEAQL6AIBBX8gACgCUCEBIAAvATAhBEEEIQUDQCABQQAgAS8BACICIARrIgMgAiADSRs7AQAgAUEAIAEvAQIiAiAEayIDIAIgA0kbOwECIAFBACABLwEEIgIgBGsiAyACIANJGzsBBCABQQAgAS8BBiICIARrIgMgAiADSRs7AQYgBUGAgARGRQRAIAFBCGohASAFQQRqIQUMAQsLAkAgBEUNACAEQQNxIQUgACgCTCEBIARBAWtBA08EQCAEIAVrIQADQCABQQAgAS8BACICIARrIgMgAiADSRs7AQAgAUEAIAEvAQIiAiAEayIDIAIgA0kbOwECIAFBACABLwEEIgIgBGsiAyACIANJGzsBBCABQQAgAS8BBiICIARrIgMgAiADSRs7AQYgAUEIaiEBIABBBGsiAA0ACwsgBUUNAANAIAFBACABLwEAIgAgBGsiAiAAIAJJGzsBACABQQJqIQEgBUEBayIFDQALCwuDAQEEfyACQQFOBEAgAiAAKAJIIAFqIgJqIQMgACgCUCEEA0AgBCACKAAAQbHz3fF5bEEPdkH+/wdxaiIFLwEAIgYgAUH//wNxRwRAIAAoAkwgASAAKAI4cUH//wNxQQF0aiAGOwEAIAUgATsBAAsgAUEBaiEBIAJBAWoiAiADSQ0ACwsLUAECfyABIAAoAlAgACgCSCABaigAAEGx893xeWxBD3ZB/v8HcWoiAy8BACICRwRAIAAoAkwgACgCOCABcUEBdGogAjsBACADIAE7AQALIAILugEBAX8jAEEQayICJAAgAkEAOgAIQYCBAUECNgIAQfyAAUEDNgIAQfiAAUEENgIAQfSAAUEFNgIAQfCAAUEGNgIAQeyAAUEHNgIAQeiAAUEINgIAQeSAAUEJNgIAQeCAAUEKNgIAQdyAAUELNgIAQdiAAUEMNgIAQdSAAUENNgIAQdCAAUEONgIAQcyAAUEPNgIAQciAAUEQNgIAQcSAAUERNgIAQcCAAUESNgIAIAAgARBYIAJBEGokAAu9AQEBfyMAQRBrIgEkACABQQA6AAhBgIEBQQI2AgBB/IABQQM2AgBB+IABQQQ2AgBB9IABQQU2AgBB8IABQQY2AgBB7IABQQc2AgBB6IABQQg2AgBB5IABQQk2AgBB4IABQQo2AgBB3IABQQs2AgBB2IABQQw2AgBB1IABQQ02AgBB0IABQQ42AgBBzIABQQ82AgBByIABQRA2AgBBxIABQRE2AgBBwIABQRI2AgAgAEEANgJAIAFBEGokAEEAC70BAQF/IwBBEGsiASQAIAFBADoACEGAgQFBAjYCAEH8gAFBAzYCAEH4gAFBBDYCAEH0gAFBBTYCAEHwgAFBBjYCAEHsgAFBBzYCAEHogAFBCDYCAEHkgAFBCTYCAEHggAFBCjYCAEHcgAFBCzYCAEHYgAFBDDYCAEHUgAFBDTYCAEHQgAFBDjYCAEHMgAFBDzYCAEHIgAFBEDYCAEHEgAFBETYCAEHAgAFBEjYCACAAKAJAIQAgAUEQaiQAIAALvgEBAX8jAEEQayIEJAAgBEEAOgAIQYCBAUECNgIAQfyAAUEDNgIAQfiAAUEENgIAQfSAAUEFNgIAQfCAAUEGNgIAQeyAAUEHNgIAQeiAAUEINgIAQeSAAUEJNgIAQeCAAUEKNgIAQdyAAUELNgIAQdiAAUEMNgIAQdSAAUENNgIAQdCAAUEONgIAQcyAAUEPNgIAQciAAUEQNgIAQcSAAUERNgIAQcCAAUESNgIAIAAgASACIAMQVyAEQRBqJAALygEAIwBBEGsiAyQAIANBADoACEGAgQFBAjYCAEH8gAFBAzYCAEH4gAFBBDYCAEH0gAFBBTYCAEHwgAFBBjYCAEHsgAFBBzYCAEHogAFBCDYCAEHkgAFBCTYCAEHggAFBCjYCAEHcgAFBCzYCAEHYgAFBDDYCAEHUgAFBDTYCAEHQgAFBDjYCAEHMgAFBDzYCAEHIgAFBEDYCAEHEgAFBETYCAEHAgAFBEjYCACAAIAAoAkAgASACQdSAASgCABEAADYCQCADQRBqJAALwAEBAX8jAEEQayIDJAAgA0EAOgAIQYCBAUECNgIAQfyAAUEDNgIAQfiAAUEENgIAQfSAAUEFNgIAQfCAAUEGNgIAQeyAAUEHNgIAQeiAAUEINgIAQeSAAUEJNgIAQeCAAUEKNgIAQdyAAUELNgIAQdiAAUEMNgIAQdSAAUENNgIAQdCAAUEONgIAQcyAAUEPNgIAQciAAUEQNgIAQcSAAUERNgIAQcCAAUESNgIAIAAgASACEF0hACADQRBqJAAgAAu+AQEBfyMAQRBrIgIkACACQQA6AAhBgIEBQQI2AgBB/IABQQM2AgBB+IABQQQ2AgBB9IABQQU2AgBB8IABQQY2AgBB7IABQQc2AgBB6IABQQg2AgBB5IABQQk2AgBB4IABQQo2AgBB3IABQQs2AgBB2IABQQw2AgBB1IABQQ02AgBB0IABQQ42AgBBzIABQQ82AgBByIABQRA2AgBBxIABQRE2AgBBwIABQRI2AgAgACABEFwhACACQRBqJAAgAAu2AQEBfyMAQRBrIgAkACAAQQA6AAhBgIEBQQI2AgBB/IABQQM2AgBB+IABQQQ2AgBB9IABQQU2AgBB8IABQQY2AgBB7IABQQc2AgBB6IABQQg2AgBB5IABQQk2AgBB4IABQQo2AgBB3IABQQs2AgBB2IABQQw2AgBB1IABQQ02AgBB0IABQQ42AgBBzIABQQ82AgBByIABQRA2AgBBxIABQRE2AgBBwIABQRI2AgAgAEEQaiQAQQgLwgEBAX8jAEEQayIEJAAgBEEAOgAIQYCBAUECNgIAQfyAAUEDNgIAQfiAAUEENgIAQfSAAUEFNgIAQfCAAUEGNgIAQeyAAUEHNgIAQeiAAUEINgIAQeSAAUEJNgIAQeCAAUEKNgIAQdyAAUELNgIAQdiAAUEMNgIAQdSAAUENNgIAQdCAAUEONgIAQcyAAUEPNgIAQciAAUEQNgIAQcSAAUERNgIAQcCAAUESNgIAIAAgASACIAMQWSEAIARBEGokACAAC8IBAQF/IwBBEGsiBCQAIARBADoACEGAgQFBAjYCAEH8gAFBAzYCAEH4gAFBBDYCAEH0gAFBBTYCAEHwgAFBBjYCAEHsgAFBBzYCAEHogAFBCDYCAEHkgAFBCTYCAEHggAFBCjYCAEHcgAFBCzYCAEHYgAFBDDYCAEHUgAFBDTYCAEHQgAFBDjYCAEHMgAFBDzYCAEHIgAFBEDYCAEHEgAFBETYCAEHAgAFBEjYCACAAIAEgAiADEFYhACAEQRBqJAAgAAsHACAALwEwC8ABAQF/IwBBEGsiAyQAIANBADoACEGAgQFBAjYCAEH8gAFBAzYCAEH4gAFBBDYCAEH0gAFBBTYCAEHwgAFBBjYCAEHsgAFBBzYCAEHogAFBCDYCAEHkgAFBCTYCAEHggAFBCjYCAEHcgAFBCzYCAEHYgAFBDDYCAEHUgAFBDTYCAEHQgAFBDjYCAEHMgAFBDzYCAEHIgAFBEDYCAEHEgAFBETYCAEHAgAFBEjYCACAAIAEgAhBVIQAgA0EQaiQAIAALBwAgACgCQAsaACAAIAAoAkAgASACQdSAASgCABEAADYCQAsLACAAQQA2AkBBAAsHACAAKAIgCwQAQQgLzgUCA34BfyMAQYBAaiIIJAACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQCAEDhECAwwFAAEECAkJCQkJCQcJBgkLIANCCFoEfiACIAEoAmQ2AgAgAiABKAJoNgIEQggFQn8LIQYMCwsgARAGDAoLIAEoAhAiAgRAIAIgASkDGCABQeQAaiICEEEiA1ANCCABKQMIIgVCf4UgA1QEQCACBEAgAkEANgIEIAJBFTYCAAsMCQsgAUEANgIQIAEgAyAFfDcDCCABIAEpAwAgA3w3AwALIAEtAHgEQCABKQMAIQUMCQtCACEDIAEpAwAiBVAEQCABQgA3AyAMCgsDQCAAIAggBSADfSIFQoDAACAFQoDAAFQbEBEiB0J/VwRAIAFB5ABqIgEEQCABIAAoAgw2AgAgASAAKAIQNgIECwwJCyAHUEUEQCABKQMAIgUgAyAHfCIDWA0KDAELCyABQeQAagRAIAFBADYCaCABQRE2AmQLDAcLIAEpAwggASkDICIFfSIHIAMgAyAHVhsiA1ANCAJAIAEtAHhFDQAgACAFQQAQFEF/Sg0AIAFB5ABqIgEEQCABIAAoAgw2AgAgASAAKAIQNgIECwwHCyAAIAIgAxARIgZCf1cEQCABQeQAagRAIAFBADYCaCABQRE2AmQLDAcLIAEgASkDICAGfCIDNwMgIAZCAFINCEIAIQYgAyABKQMIWg0IIAFB5ABqBEAgAUEANgJoIAFBETYCZAsMBgsgASkDICABKQMAIgV9IAEpAwggBX0gAiADIAFB5ABqEEQiA0IAUw0FIAEgASkDACADfDcDIAwHCyACIAFBKGoQYEEfdawhBgwGCyABMABgIQYMBQsgASkDcCEGDAQLIAEpAyAgASkDAH0hBgwDCyABQeQAagRAIAFBADYCaCABQRw2AmQLC0J/IQYMAQsgASAFNwMgCyAIQYBAayQAIAYLBwAgACgCAAsPACAAIAAoAjBBAWo2AjALGABB+IMBQgA3AgBBgIQBQQA2AgBB+IMBCwcAIABBDGoLBwAgACgCLAsHACAAKAIoCwcAIAAoAhgLFQAgACABrSACrUIghoQgAyAEEIoBCxMBAX4gABAzIgFCIIinEAAgAacLbwEBfiABrSACrUIghoQhBSMAQRBrIgEkAAJ/IABFBEAgBVBFBEAgBARAIARBADYCBCAEQRI2AgALQQAMAgtBAEIAIAMgBBA6DAELIAEgBTcDCCABIAA2AgAgAUIBIAMgBBA6CyEAIAFBEGokACAACxQAIAAgASACrSADrUIghoQgBBBSC9oCAgJ/AX4CfyABrSACrUIghoQiByAAKQMwVEEAIARBCkkbRQRAIABBCGoEQCAAQQA2AgwgAEESNgIIC0F/DAELIAAtABhBAnEEQCAAQQhqBEAgAEEANgIMIABBGTYCCAtBfwwBCyADBH8gA0H//wNxQQhGIANBfUtyBUEBC0UEQCAAQQhqBEAgAEEANgIMIABBEDYCCAtBfwwBCyAAKAJAIgEgB6ciBUEEdGooAgAiAgR/IAIoAhAgA0YFIANBf0YLIQYgASAFQQR0aiIBIQUgASgCBCEBAkAgBgRAIAFFDQEgAUEAOwFQIAEgASgCAEF+cSIANgIAIAANASABECAgBUEANgIEQQAMAgsCQCABDQAgBSACECsiATYCBCABDQAgAEEIagRAIABBADYCDCAAQQ42AggLQX8MAgsgASAEOwFQIAEgAzYCECABIAEoAgBBAXI2AgALQQALCxwBAX4gACABIAIgAEEIahBMIgNCIIinEAAgA6cLHwEBfiAAIAEgAq0gA61CIIaEEBEiBEIgiKcQACAEpwteAQF+An5CfyAARQ0AGiAAKQMwIgIgAUEIcUUNABpCACACUA0AGiAAKAJAIQADQCACIAKnQQR0IABqQRBrKAIADQEaIAJCAX0iAkIAUg0AC0IACyICQiCIpxAAIAKnCxMAIAAgAa0gAq1CIIaEIAMQiwELnwEBAn4CfiACrSADrUIghoQhBUJ/IQQCQCAARQ0AIAAoAgQNACAAQQRqIQIgBUJ/VwRAIAIEQCACQQA2AgQgAkESNgIAC0J/DAILQgAhBCAALQAQDQAgBVANACAAKAIUIAEgBRARIgRCf1UNACAAKAIUIQAgAgRAIAIgACgCDDYCACACIAAoAhA2AgQLQn8hBAsgBAsiBEIgiKcQACAEpwueAQEBfwJ/IAAgACABrSACrUIghoQgAyAAKAIcEH8iAQRAIAEQMkF/TARAIABBCGoEQCAAIAEoAgw2AgggACABKAIQNgIMCyABEAtBAAwCC0EYEAkiBEUEQCAAQQhqBEAgAEEANgIMIABBDjYCCAsgARALQQAMAgsgBCAANgIAIARBADYCDCAEQgA3AgQgBCABNgIUIARBADoAEAsgBAsLsQICAX8BfgJ/QX8hBAJAIAAgAa0gAq1CIIaEIgZBAEEAECZFDQAgAC0AGEECcQRAIABBCGoEQCAAQQA2AgwgAEEZNgIIC0F/DAILIAAoAkAiASAGpyICQQR0aiIEKAIIIgUEQEEAIQQgBSADEHFBf0oNASAAQQhqBEAgAEEANgIMIABBDzYCCAtBfwwCCwJAIAQoAgAiBQRAIAUoAhQgA0YNAQsCQCABIAJBBHRqIgEoAgQiBA0AIAEgBRArIgQ2AgQgBA0AIABBCGoEQCAAQQA2AgwgAEEONgIIC0F/DAMLIAQgAzYCFCAEIAQoAgBBIHI2AgBBAAwCC0EAIQQgASACQQR0aiIBKAIEIgBFDQAgACAAKAIAQV9xIgI2AgAgAg0AIAAQICABQQA2AgQLIAQLCxQAIAAgAa0gAq1CIIaEIAQgBRBzCxIAIAAgAa0gAq1CIIaEIAMQFAtBAQF+An4gAUEAIAIbRQRAIABBCGoEQCAAQQA2AgwgAEESNgIIC0J/DAELIAAgASACIAMQdAsiBEIgiKcQACAEpwvGAwIFfwF+An4CQAJAIAAiBC0AGEECcQRAIARBCGoEQCAEQQA2AgwgBEEZNgIICwwBCyABRQRAIARBCGoEQCAEQQA2AgwgBEESNgIICwwBCyABECIiByABakEBay0AAEEvRwRAIAdBAmoQCSIARQRAIARBCGoEQCAEQQA2AgwgBEEONgIICwwCCwJAAkAgACIGIAEiBXNBA3ENACAFQQNxBEADQCAGIAUtAAAiAzoAACADRQ0DIAZBAWohBiAFQQFqIgVBA3ENAAsLIAUoAgAiA0F/cyADQYGChAhrcUGAgYKEeHENAANAIAYgAzYCACAFKAIEIQMgBkEEaiEGIAVBBGohBSADQYGChAhrIANBf3NxQYCBgoR4cUUNAAsLIAYgBS0AACIDOgAAIANFDQADQCAGIAUtAAEiAzoAASAGQQFqIQYgBUEBaiEFIAMNAAsLIAcgACIDakEvOwAACyAEQQBCAEEAEFIiAEUEQCADEAYMAQsgBCADIAEgAxsgACACEHQhCCADEAYgCEJ/VwRAIAAQCyAIDAMLIAQgCEEDQYCA/I8EEHNBf0oNASAEIAgQchoLQn8hCAsgCAsiCEIgiKcQACAIpwsQACAAIAGtIAKtQiCGhBByCxYAIAAgAa0gAq1CIIaEIAMgBCAFEGYL3iMDD38IfgF8IwBB8ABrIgkkAAJAIAFBAE5BACAAG0UEQCACBEAgAkEANgIEIAJBEjYCAAsMAQsgACkDGCISAn5BsIMBKQMAIhNCf1EEQCAJQoOAgIBwNwMwIAlChoCAgPAANwMoIAlCgYCAgCA3AyBBsIMBQQAgCUEgahAkNwMAIAlCj4CAgHA3AxAgCUKJgICAoAE3AwAgCUKMgICA0AE3AwhBuIMBQQggCRAkNwMAQbCDASkDACETCyATC4MgE1IEQCACBEAgAkEANgIEIAJBHDYCAAsMAQsgASABQRByQbiDASkDACITIBKDIBNRGyIKQRhxQRhGBEAgAgRAIAJBADYCBCACQRk2AgALDAELIAlBOGoQKgJAIAAgCUE4ahAhBEACQCAAKAIMQQVGBEAgACgCEEEsRg0BCyACBEAgAiAAKAIMNgIAIAIgACgCEDYCBAsMAgsgCkEBcUUEQCACBEAgAkEANgIEIAJBCTYCAAsMAwsgAhBJIgVFDQEgBSAKNgIEIAUgADYCACAKQRBxRQ0CIAUgBSgCFEECcjYCFCAFIAUoAhhBAnI2AhgMAgsgCkECcQRAIAIEQCACQQA2AgQgAkEKNgIACwwCCyAAEDJBf0wEQCACBEAgAiAAKAIMNgIAIAIgACgCEDYCBAsMAQsCfyAKQQhxBEACQCACEEkiAUUNACABIAo2AgQgASAANgIAIApBEHFFDQAgASABKAIUQQJyNgIUIAEgASgCGEECcjYCGAsgAQwBCyMAQUBqIg4kACAOQQhqECoCQCAAIA5BCGoQIUF/TARAIAIEQCACIAAoAgw2AgAgAiAAKAIQNgIECwwBCyAOLQAIQQRxRQRAIAIEQCACQYoBNgIEIAJBBDYCAAsMAQsgDikDICETIAIQSSIFRQRAQQAhBQwBCyAFIAo2AgQgBSAANgIAIApBEHEEQCAFIAUoAhRBAnI2AhQgBSAFKAIYQQJyNgIYCwJAAkACQCATUARAAn8gACEBAkADQCABKQMYQoCAEINCAFINASABKAIAIgENAAtBAQwBCyABQQBCAEESEA6nCw0EIAVBCGoEQCAFQQA2AgwgBUETNgIICwwBCyMAQdAAayIBJAACQCATQhVYBEAgBUEIagRAIAVBADYCDCAFQRM2AggLDAELAkACQCAFKAIAQgAgE0KqgAQgE0KqgARUGyISfUECEBRBf0oNACAFKAIAIgMoAgxBBEYEQCADKAIQQRZGDQELIAVBCGoEQCAFIAMoAgw2AgggBSADKAIQNgIMCwwBCyAFKAIAEDMiE0J/VwRAIAUoAgAhAyAFQQhqIggEQCAIIAMoAgw2AgAgCCADKAIQNgIECwwBCyAFKAIAIBJBACAFQQhqIg8QLSIERQ0BIBJCqoAEWgRAAkAgBCkDCEIUVARAIARBADoAAAwBCyAEQhQ3AxAgBEEBOgAACwsgAQRAIAFBADYCBCABQRM2AgALIARCABATIQwCQCAELQAABH4gBCkDCCAEKQMQfQVCAAunIgdBEmtBA0sEQEJ/IRcDQCAMQQFrIQMgByAMakEVayEGAkADQCADQQFqIgNB0AAgBiADaxB6IgNFDQEgA0EBaiIMQZ8SQQMQPQ0ACwJAIAMgBCgCBGusIhIgBCkDCFYEQCAEQQA6AAAMAQsgBCASNwMQIARBAToAAAsgBC0AAAR+IAQpAxAFQgALIRICQCAELQAABH4gBCkDCCAEKQMQfQVCAAtCFVgEQCABBEAgAUEANgIEIAFBEzYCAAsMAQsgBEIEEBMoAABB0JaVMEcEQCABBEAgAUEANgIEIAFBEzYCAAsMAQsCQAJAAkAgEkIUVA0AIAQoAgQgEqdqQRRrKAAAQdCWmThHDQACQCASQhR9IhQgBCIDKQMIVgRAIANBADoAAAwBCyADIBQ3AxAgA0EBOgAACyAFKAIUIRAgBSgCACEGIAMtAAAEfiAEKQMQBUIACyEWIARCBBATGiAEEAwhCyAEEAwhDSAEEB0iFEJ/VwRAIAEEQCABQRY2AgQgAUEENgIACwwECyAUQjh8IhUgEyAWfCIWVgRAIAEEQCABQQA2AgQgAUEVNgIACwwECwJAAkAgEyAUVg0AIBUgEyAEKQMIfFYNAAJAIBQgE30iFSAEKQMIVgRAIANBADoAAAwBCyADIBU3AxAgA0EBOgAAC0EAIQcMAQsgBiAUQQAQFEF/TARAIAEEQCABIAYoAgw2AgAgASAGKAIQNgIECwwFC0EBIQcgBkI4IAFBEGogARAtIgNFDQQLIANCBBATKAAAQdCWmTBHBEAgAQRAIAFBADYCBCABQRU2AgALIAdFDQQgAxAIDAQLIAMQHSEVAkAgEEEEcSIGRQ0AIBQgFXxCDHwgFlENACABBEAgAUEANgIEIAFBFTYCAAsgB0UNBCADEAgMBAsgA0IEEBMaIAMQFSIQIAsgC0H//wNGGyELIAMQFSIRIA0gDUH//wNGGyENAkAgBkUNACANIBFGQQAgCyAQRhsNACABBEAgAUEANgIEIAFBFTYCAAsgB0UNBCADEAgMBAsgCyANcgRAIAEEQCABQQA2AgQgAUEBNgIACyAHRQ0EIAMQCAwECyADEB0iGCADEB1SBEAgAQRAIAFBADYCBCABQQE2AgALIAdFDQQgAxAIDAQLIAMQHSEVIAMQHSEWIAMtAABFBEAgAQRAIAFBADYCBCABQRQ2AgALIAdFDQQgAxAIDAQLIAcEQCADEAgLAkAgFkIAWQRAIBUgFnwiGSAWWg0BCyABBEAgAUEWNgIEIAFBBDYCAAsMBAsgEyAUfCIUIBlUBEAgAQRAIAFBADYCBCABQRU2AgALDAQLAkAgBkUNACAUIBlRDQAgAQRAIAFBADYCBCABQRU2AgALDAQLIBggFUIugFgNASABBEAgAUEANgIEIAFBFTYCAAsMAwsCQCASIAQpAwhWBEAgBEEAOgAADAELIAQgEjcDECAEQQE6AAALIAUoAhQhAyAELQAABH4gBCkDCCAEKQMQfQVCAAtCFVgEQCABBEAgAUEANgIEIAFBFTYCAAsMAwsgBC0AAAR+IAQpAxAFQgALIRQgBEIEEBMaIAQQFQRAIAEEQCABQQA2AgQgAUEBNgIACwwDCyAEEAwgBBAMIgZHBEAgAQRAIAFBADYCBCABQRM2AgALDAMLIAQQFSEHIAQQFa0iFiAHrSIVfCIYIBMgFHwiFFYEQCABBEAgAUEANgIEIAFBFTYCAAsMAwsCQCADQQRxRQ0AIBQgGFENACABBEAgAUEANgIEIAFBFTYCAAsMAwsgBq0gARBqIgNFDQIgAyAWNwMgIAMgFTcDGCADQQA6ACwMAQsgGCABEGoiA0UNASADIBY3AyAgAyAVNwMYIANBAToALAsCQCASQhR8IhQgBCkDCFYEQCAEQQA6AAAMAQsgBCAUNwMQIARBAToAAAsgBBAMIQYCQCADKQMYIAMpAyB8IBIgE3xWDQACQCAGRQRAIAUtAARBBHFFDQELAkAgEkIWfCISIAQpAwhWBEAgBEEAOgAADAELIAQgEjcDECAEQQE6AAALIAQtAAAEfiAEKQMIIAQpAxB9BUIACyIUIAatIhJUDQEgBS0ABEEEcUEAIBIgFFIbDQEgBkUNACADIAQgEhATIAZBACABEDUiBjYCKCAGDQAgAxAWDAILAkAgEyADKQMgIhJYBEACQCASIBN9IhIgBCkDCFYEQCAEQQA6AAAMAQsgBCASNwMQIARBAToAAAsgBCADKQMYEBMiBkUNAiAGIAMpAxgQFyIHDQEgAQRAIAFBADYCBCABQQ42AgALIAMQFgwDCyAFKAIAIBJBABAUIQcgBSgCACEGIAdBf0wEQCABBEAgASAGKAIMNgIAIAEgBigCEDYCBAsgAxAWDAMLQQAhByAGEDMgAykDIFENACABBEAgAUEANgIEIAFBEzYCAAsgAxAWDAILQgAhFAJAAkAgAykDGCIWUEUEQANAIBQgAykDCFIiC0UEQCADLQAsDQMgFkIuVA0DAn8CQCADKQMQIhVCgIAEfCISIBVaQQAgEkKAgICAAVQbRQ0AIAMoAgAgEqdBBHQQNCIGRQ0AIAMgBjYCAAJAIAMpAwgiFSASWg0AIAYgFadBBHRqIgZCADcCACAGQgA3AAUgFUIBfCIVIBJRDQADQCADKAIAIBWnQQR0aiIGQgA3AgAgBkIANwAFIBVCAXwiFSASUg0ACwsgAyASNwMIIAMgEjcDEEEBDAELIAEEQCABQQA2AgQgAUEONgIAC0EAC0UNBAtB2AAQCSIGBH8gBkIANwMgIAZBADYCGCAGQv////8PNwMQIAZBADsBDCAGQb+GKDYCCCAGQQE6AAYgBkEAOwEEIAZBADYCACAGQgA3A0ggBkGAgNiNeDYCRCAGQgA3AyggBkIANwMwIAZCADcDOCAGQUBrQQA7AQAgBkIANwNQIAYFQQALIQYgAygCACAUp0EEdGogBjYCAAJAIAYEQCAGIAUoAgAgB0EAIAEQaCISQn9VDQELIAsNBCABKAIAQRNHDQQgAQRAIAFBADYCBCABQRU2AgALDAQLIBRCAXwhFCAWIBJ9IhZCAFINAAsLIBQgAykDCFINAAJAIAUtAARBBHFFDQAgBwRAIActAAAEfyAHKQMQIAcpAwhRBUEAC0UNAgwBCyAFKAIAEDMiEkJ/VwRAIAUoAgAhBiABBEAgASAGKAIMNgIAIAEgBigCEDYCBAsgAxAWDAULIBIgAykDGCADKQMgfFINAQsgBxAIAn4gCARAAn8gF0IAVwRAIAUgCCABEEghFwsgBSADIAEQSCISIBdVCwRAIAgQFiASDAILIAMQFgwFC0IAIAUtAARBBHFFDQAaIAUgAyABEEgLIRcgAyEIDAMLIAEEQCABQQA2AgQgAUEVNgIACyAHEAggAxAWDAILIAMQFiAHEAgMAQsgAQRAIAFBADYCBCABQRU2AgALIAMQFgsCQCAMIAQoAgRrrCISIAQpAwhWBEAgBEEAOgAADAELIAQgEjcDECAEQQE6AAALIAQtAAAEfiAEKQMIIAQpAxB9BUIAC6ciB0ESa0EDSw0BCwsgBBAIIBdCf1UNAwwBCyAEEAgLIA8iAwRAIAMgASgCADYCACADIAEoAgQ2AgQLIAgQFgtBACEICyABQdAAaiQAIAgNAQsgAgRAIAIgBSgCCDYCACACIAUoAgw2AgQLDAELIAUgCCgCADYCQCAFIAgpAwg3AzAgBSAIKQMQNwM4IAUgCCgCKDYCICAIEAYgBSgCUCEIIAVBCGoiBCEBQQAhBwJAIAUpAzAiE1ANAEGAgICAeCEGAn8gE7pEAAAAAAAA6D+jRAAA4P///+9BpCIaRAAAAAAAAPBBYyAaRAAAAAAAAAAAZnEEQCAaqwwBC0EACyIDQYCAgIB4TQRAIANBAWsiA0EBdiADciIDQQJ2IANyIgNBBHYgA3IiA0EIdiADciIDQRB2IANyQQFqIQYLIAYgCCgCACIMTQ0AIAYQPCILRQRAIAEEQCABQQA2AgQgAUEONgIACwwBCwJAIAgpAwhCACAMG1AEQCAIKAIQIQ8MAQsgCCgCECEPA0AgDyAHQQJ0aigCACIBBEADQCABKAIYIQMgASALIAEoAhwgBnBBAnRqIg0oAgA2AhggDSABNgIAIAMiAQ0ACwsgB0EBaiIHIAxHDQALCyAPEAYgCCAGNgIAIAggCzYCEAsCQCAFKQMwUA0AQgAhEwJAIApBBHFFBEADQCAFKAJAIBOnQQR0aigCACgCMEEAQQAgAhAlIgFFDQQgBSgCUCABIBNBCCAEEE1FBEAgBCgCAEEKRw0DCyATQgF8IhMgBSkDMFQNAAwDCwALA0AgBSgCQCATp0EEdGooAgAoAjBBAEEAIAIQJSIBRQ0DIAUoAlAgASATQQggBBBNRQ0BIBNCAXwiEyAFKQMwVA0ACwwBCyACBEAgAiAEKAIANgIAIAIgBCgCBDYCBAsMAQsgBSAFKAIUNgIYDAELIAAgACgCMEEBajYCMCAFEEtBACEFCyAOQUBrJAAgBQsiBQ0BIAAQGhoLQQAhBQsgCUHwAGokACAFCxAAIwAgAGtBcHEiACQAIAALBgAgACQACwQAIwAL4CoDEX8IfgN8IwBBwMAAayIHJABBfyECAkAgAEUNAAJ/IAAtAChFBEBBACAAKAIYIAAoAhRGDQEaC0EBCyEBAkACQCAAKQMwIhRQRQRAIAAoAkAhCgNAIAogEqdBBHRqIgMtAAwhCwJAAkAgAygCCA0AIAsNACADKAIEIgNFDQEgAygCAEUNAQtBASEBCyAXIAtBAXOtQv8Bg3whFyASQgF8IhIgFFINAAsgF0IAUg0BCyAAKAIEQQhxIAFyRQ0BAn8gACgCACIDKAIkIgFBA0cEQCADKAIgBH9BfyADEBpBAEgNAhogAygCJAUgAQsEQCADEEMLQX8gA0EAQgBBDxAOQgBTDQEaIANBAzYCJAtBAAtBf0oNASAAKAIAKAIMQRZGBEAgACgCACgCEEEsRg0CCyAAKAIAIQEgAEEIagRAIAAgASgCDDYCCCAAIAEoAhA2AgwLDAILIAFFDQAgFCAXVARAIABBCGoEQCAAQQA2AgwgAEEUNgIICwwCCyAXp0EDdBAJIgtFDQFCfyEWQgAhEgNAAkAgCiASp0EEdGoiBigCACIDRQ0AAkAgBigCCA0AIAYtAAwNACAGKAIEIgFFDQEgASgCAEUNAQsgFiADKQNIIhMgEyAWVhshFgsgBi0ADEUEQCAXIBlYBEAgCxAGIABBCGoEQCAAQQA2AgwgAEEUNgIICwwECyALIBmnQQN0aiASNwMAIBlCAXwhGQsgEkIBfCISIBRSDQALIBcgGVYEQCALEAYgAEEIagRAIABBADYCDCAAQRQ2AggLDAILAkACQCAAKAIAKQMYQoCACINQDQACQAJAIBZCf1INACAAKQMwIhNQDQIgE0IBgyEVIAAoAkAhAwJAIBNCAVEEQEJ/IRRCACESQgAhFgwBCyATQn6DIRlCfyEUQgAhEkIAIRYDQCADIBKnQQR0aigCACIBBEAgFiABKQNIIhMgEyAWVCIBGyEWIBQgEiABGyEUCyADIBJCAYQiGKdBBHRqKAIAIgEEQCAWIAEpA0giEyATIBZUIgEbIRYgFCAYIAEbIRQLIBJCAnwhEiAZQgJ9IhlQRQ0ACwsCQCAVUA0AIAMgEqdBBHRqKAIAIgFFDQAgFiABKQNIIhMgEyAWVCIBGyEWIBQgEiABGyEUCyAUQn9RDQBCACETIwBBEGsiBiQAAkAgACAUIABBCGoiCBBBIhVQDQAgFSAAKAJAIBSnQQR0aigCACIKKQMgIhh8IhQgGFpBACAUQn9VG0UEQCAIBEAgCEEWNgIEIAhBBDYCAAsMAQsgCi0ADEEIcUUEQCAUIRMMAQsgACgCACAUQQAQFCEBIAAoAgAhAyABQX9MBEAgCARAIAggAygCDDYCACAIIAMoAhA2AgQLDAELIAMgBkEMakIEEBFCBFIEQCAAKAIAIQEgCARAIAggASgCDDYCACAIIAEoAhA2AgQLDAELIBRCBHwgFCAGKAAMQdCWncAARhtCFEIMAn9BASEBAkAgCikDKEL+////D1YNACAKKQMgQv7///8PVg0AQQAhAQsgAQsbfCIUQn9XBEAgCARAIAhBFjYCBCAIQQQ2AgALDAELIBQhEwsgBkEQaiQAIBMiFkIAUg0BIAsQBgwFCyAWUA0BCwJ/IAAoAgAiASgCJEEBRgRAIAFBDGoEQCABQQA2AhAgAUESNgIMC0F/DAELQX8gAUEAIBZBERAOQgBTDQAaIAFBATYCJEEAC0F/Sg0BC0IAIRYCfyAAKAIAIgEoAiRBAUYEQCABQQxqBEAgAUEANgIQIAFBEjYCDAtBfwwBC0F/IAFBAEIAQQgQDkIAUw0AGiABQQE2AiRBAAtBf0oNACAAKAIAIQEgAEEIagRAIAAgASgCDDYCCCAAIAEoAhA2AgwLIAsQBgwCCyAAKAJUIgIEQCACQgA3AxggAigCAEQAAAAAAAAAACACKAIMIAIoAgQRDgALIABBCGohBCAXuiEcQgAhFAJAAkACQANAIBcgFCITUgRAIBO6IByjIRsgE0IBfCIUuiAcoyEaAkAgACgCVCICRQ0AIAIgGjkDKCACIBs5AyAgAisDECAaIBuhRAAAAAAAAAAAoiAboCIaIAIrAxihY0UNACACKAIAIBogAigCDCACKAIEEQ4AIAIgGjkDGAsCfwJAIAAoAkAgCyATp0EDdGopAwAiE6dBBHRqIg0oAgAiAQRAIAEpA0ggFlQNAQsgDSgCBCEFAkACfwJAIA0oAggiAkUEQCAFRQ0BQQEgBSgCACICQQFxDQIaIAJBwABxQQZ2DAILQQEgBQ0BGgsgDSABECsiBTYCBCAFRQ0BIAJBAEcLIQZBACEJIwBBEGsiDCQAAkAgEyAAKQMwWgRAIABBCGoEQCAAQQA2AgwgAEESNgIIC0F/IQkMAQsgACgCQCIKIBOnIgNBBHRqIg8oAgAiAkUNACACLQAEDQACQCACKQNIQhp8IhhCf1cEQCAAQQhqBEAgAEEWNgIMIABBBDYCCAsMAQtBfyEJIAAoAgAgGEEAEBRBf0wEQCAAKAIAIQIgAEEIagRAIAAgAigCDDYCCCAAIAIoAhA2AgwLDAILIAAoAgBCBCAMQQxqIABBCGoiDhAtIhBFDQEgEBAMIQEgEBAMIQggEC0AAAR/IBApAxAgECkDCFEFQQALIQIgEBAIIAJFBEAgDgRAIA5BADYCBCAOQRQ2AgALDAILAkAgCEUNACAAKAIAIAGtQQEQFEF/TARAQYSEASgCACECIA4EQCAOIAI2AgQgDkEENgIACwwDC0EAIAAoAgAgCEEAIA4QRSIBRQ0BIAEgCEGAAiAMQQhqIA4QbiECIAEQBiACRQ0BIAwoAggiAkUNACAMIAIQbSICNgIIIA8oAgAoAjQgAhBvIQIgDygCACACNgI0CyAPKAIAIgJBAToABEEAIQkgCiADQQR0aigCBCIBRQ0BIAEtAAQNASACKAI0IQIgAUEBOgAEIAEgAjYCNAwBC0F/IQkLIAxBEGokACAJQQBIDQUgACgCABAfIhhCAFMNBSAFIBg3A0ggBgRAQQAhDCANKAIIIg0hASANRQRAIAAgACATQQhBABB/IgwhASAMRQ0HCwJAAkAgASAHQQhqECFBf0wEQCAEBEAgBCABKAIMNgIAIAQgASgCEDYCBAsMAQsgBykDCCISQsAAg1AEQCAHQQA7ATggByASQsAAhCISNwMICwJAAkAgBSgCECICQX5PBEAgBy8BOCIDRQ0BIAUgAzYCECADIQIMAgsgAg0AIBJCBINQDQAgByAHKQMgNwMoIAcgEkIIhCISNwMIQQAhAgwBCyAHIBJC9////w+DIhI3AwgLIBJCgAGDUARAIAdBADsBOiAHIBJCgAGEIhI3AwgLAn8gEkIEg1AEQEJ/IRVBgAoMAQsgBSAHKQMgIhU3AyggEkIIg1AEQAJAAkACQAJAQQggAiACQX1LG0H//wNxDg0CAwMDAwMDAwEDAwMAAwtBgApBgAIgFUKUwuTzD1YbDAQLQYAKQYACIBVCg4Ow/w9WGwwDC0GACkGAAiAVQv////8PVhsMAgtBgApBgAIgFUIAUhsMAQsgBSAHKQMoNwMgQYACCyEPIAAoAgAQHyITQn9XBEAgACgCACECIAQEQCAEIAIoAgw2AgAgBCACKAIQNgIECwwBCyAFIAUvAQxB9/8DcTsBDCAAIAUgDxA3IgpBAEgNACAHLwE4IghBCCAFKAIQIgMgA0F9SxtB//8DcSICRyEGAkACQAJAAkACQAJAAkAgAiAIRwRAIANBAEchAwwBC0EAIQMgBS0AAEGAAXFFDQELIAUvAVIhCSAHLwE6IQIMAQsgBS8BUiIJIAcvAToiAkYNAQsgASABKAIwQQFqNgIwIAJB//8DcQ0BIAEhAgwCCyABIAEoAjBBAWo2AjBBACEJDAILQSZBACAHLwE6QQFGGyICRQRAIAQEQCAEQQA2AgQgBEEYNgIACyABEAsMAwsgACABIAcvATpBACAAKAIcIAIRBgAhAiABEAsgAkUNAgsgCUEARyEJIAhBAEcgBnFFBEAgAiEBDAELIAAgAiAHLwE4EIEBIQEgAhALIAFFDQELAkAgCEUgBnJFBEAgASECDAELIAAgAUEAEIABIQIgARALIAJFDQELAkAgA0UEQCACIQMMAQsgACACIAUoAhBBASAFLwFQEIIBIQMgAhALIANFDQELAkAgCUUEQCADIQEMAQsgBSgCVCIBRQRAIAAoAhwhAQsCfyAFLwFSGkEBCwRAIAQEQCAEQQA2AgQgBEEYNgIACyADEAsMAgsgACADIAUvAVJBASABQQARBgAhASADEAsgAUUNAQsgACgCABAfIhhCf1cEQCAAKAIAIQIgBARAIAQgAigCDDYCACAEIAIoAhA2AgQLDAELAkAgARAyQQBOBEACfwJAAkAgASAHQUBrQoDAABARIhJCAVMNAEIAIRkgFUIAVQRAIBW5IRoDQCAAIAdBQGsgEhAbQQBIDQMCQCASQoDAAFINACAAKAJUIgJFDQAgAiAZQoBAfSIZuSAaoxB7CyABIAdBQGtCgMAAEBEiEkIAVQ0ACwwBCwNAIAAgB0FAayASEBtBAEgNAiABIAdBQGtCgMAAEBEiEkIAVQ0ACwtBACASQn9VDQEaIAQEQCAEIAEoAgw2AgAgBCABKAIQNgIECwtBfwshAiABEBoaDAELIAQEQCAEIAEoAgw2AgAgBCABKAIQNgIEC0F/IQILIAEgB0EIahAhQX9MBEAgBARAIAQgASgCDDYCACAEIAEoAhA2AgQLQX8hAgsCf0EAIQkCQCABIgNFDQADQCADLQAaQQFxBEBB/wEhCSADQQBCAEEQEA4iFUIAUw0CIBVCBFkEQCADQQxqBEAgA0EANgIQIANBFDYCDAsMAwsgFachCQwCCyADKAIAIgMNAAsLIAlBGHRBGHUiA0F/TAsEQCAEBEAgBCABKAIMNgIAIAQgASgCEDYCBAsgARALDAELIAEQCyACQQBIDQAgACgCABAfIRUgACgCACECIBVCf1cEQCAEBEAgBCACKAIMNgIAIAQgAigCEDYCBAsMAQsgAiATEHVBf0wEQCAAKAIAIQIgBARAIAQgAigCDDYCACAEIAIoAhA2AgQLDAELIAcpAwgiE0LkAINC5ABSBEAgBARAIARBADYCBCAEQRQ2AgALDAELAkAgBS0AAEEgcQ0AIBNCEINQRQRAIAUgBygCMDYCFAwBCyAFQRRqEAEaCyAFIAcvATg2AhAgBSAHKAI0NgIYIAcpAyAhEyAFIBUgGH03AyAgBSATNwMoIAUgBS8BDEH5/wNxIANB/wFxQQF0cjsBDCAPQQp2IQNBPyEBAkACQAJAAkAgBSgCECICQQxrDgMAAQIBCyAFQS47AQoMAgtBLSEBIAMNACAFKQMoQv7///8PVg0AIAUpAyBC/v///w9WDQBBFCEBIAJBCEYNACAFLwFSQQFGDQAgBSgCMCICBH8gAi8BBAVBAAtB//8DcSICBEAgAiAFKAIwKAIAakEBay0AAEEvRg0BC0EKIQELIAUgATsBCgsgACAFIA8QNyICQQBIDQAgAiAKRwRAIAQEQCAEQQA2AgQgBEEUNgIACwwBCyAAKAIAIBUQdUF/Sg0BIAAoAgAhAiAEBEAgBCACKAIMNgIAIAQgAigCEDYCBAsLIA0NByAMEAsMBwsgDQ0CIAwQCwwCCyAFIAUvAQxB9/8DcTsBDCAAIAVBgAIQN0EASA0FIAAgEyAEEEEiE1ANBSAAKAIAIBNBABAUQX9MBEAgACgCACECIAQEQCAEIAIoAgw2AgAgBCACKAIQNgIECwwGCyAFKQMgIRIjAEGAQGoiAyQAAkAgElBFBEAgAEEIaiECIBK6IRoDQEF/IQEgACgCACADIBJCgMAAIBJCgMAAVBsiEyACEGVBAEgNAiAAIAMgExAbQQBIDQIgACgCVCAaIBIgE30iErqhIBqjEHsgEkIAUg0ACwtBACEBCyADQYBAayQAIAFBf0oNAUEBIREgAUEcdkEIcUEIRgwCCyAEBEAgBEEANgIEIARBDjYCAAsMBAtBAAtFDQELCyARDQBBfyECAkAgACgCABAfQgBTDQAgFyEUQQAhCkIAIRcjAEHwAGsiESQAAkAgACgCABAfIhVCAFkEQCAUUEUEQANAIAAgACgCQCALIBenQQN0aigCAEEEdGoiAygCBCIBBH8gAQUgAygCAAtBgAQQNyIBQQBIBEBCfyEXDAQLIAFBAEcgCnIhCiAXQgF8IhcgFFINAAsLQn8hFyAAKAIAEB8iGEJ/VwRAIAAoAgAhASAAQQhqBEAgACABKAIMNgIIIAAgASgCEDYCDAsMAgsgEULiABAXIgZFBEAgAEEIagRAIABBADYCDCAAQQ42AggLDAILIBggFX0hEyAVQv////8PViAUQv//A1ZyIApyQQFxBEAgBkGZEkEEECwgBkIsEBggBkEtEA0gBkEtEA0gBkEAEBIgBkEAEBIgBiAUEBggBiAUEBggBiATEBggBiAVEBggBkGUEkEEECwgBkEAEBIgBiAYEBggBkEBEBILIAZBnhJBBBAsIAZBABASIAYgFEL//wMgFEL//wNUG6dB//8DcSIBEA0gBiABEA0gBkF/IBOnIBNC/v///w9WGxASIAZBfyAVpyAVQv7///8PVhsQEiAGIABBJEEgIAAtACgbaigCACIDBH8gAy8BBAVBAAtB//8DcRANIAYtAABFBEAgAEEIagRAIABBADYCDCAAQRQ2AggLIAYQCAwCCyAAIAYoAgQgBi0AAAR+IAYpAxAFQgALEBshASAGEAggAUEASA0BIAMEQCAAIAMoAgAgAzMBBBAbQQBIDQILIBMhFwwBCyAAKAIAIQEgAEEIagRAIAAgASgCDDYCCCAAIAEoAhA2AgwLQn8hFwsgEUHwAGokACAXQgBTDQAgACgCABAfQj+HpyECCyALEAYgAkEASA0BAn8gACgCACIBKAIkQQFHBEAgAUEMagRAIAFBADYCECABQRI2AgwLQX8MAQsgASgCICICQQJPBEAgAUEMagRAIAFBADYCECABQR02AgwLQX8MAQsCQCACQQFHDQAgARAaQQBODQBBfwwBCyABQQBCAEEJEA5Cf1cEQCABQQI2AiRBfwwBCyABQQA2AiRBAAtFDQIgACgCACECIAQEQCAEIAIoAgw2AgAgBCACKAIQNgIECwwBCyALEAYLIAAoAlQQfCAAKAIAEENBfyECDAILIAAoAlQQfAsgABBLQQAhAgsgB0HAwABqJAAgAgtFAEHwgwFCADcDAEHogwFCADcDAEHggwFCADcDAEHYgwFCADcDAEHQgwFCADcDAEHIgwFCADcDAEHAgwFCADcDAEHAgwELoQMBCH8jAEGgAWsiAiQAIAAQMQJAAn8CQCAAKAIAIgFBAE4EQCABQbATKAIASA0BCyACIAE2AhAgAkEgakH2ESACQRBqEHZBASEGIAJBIGohBCACQSBqECIhA0EADAELIAFBAnQiAUGwEmooAgAhBQJ/AkACQCABQcATaigCAEEBaw4CAAEECyAAKAIEIQNB9IIBKAIAIQdBACEBAkACQANAIAMgAUHQ8QBqLQAARwRAQdcAIQQgAUEBaiIBQdcARw0BDAILCyABIgQNAEGw8gAhAwwBC0Gw8gAhAQNAIAEtAAAhCCABQQFqIgMhASAIDQAgAyEBIARBAWsiBA0ACwsgBygCFBogAwwBC0EAIAAoAgRrQQJ0QdjAAGooAgALIgRFDQEgBBAiIQMgBUUEQEEAIQVBASEGQQAMAQsgBRAiQQJqCyEBIAEgA2pBAWoQCSIBRQRAQegSKAIAIQUMAQsgAiAENgIIIAJBrBJBkRIgBhs2AgQgAkGsEiAFIAYbNgIAIAFBqwogAhB2IAAgATYCCCABIQULIAJBoAFqJAAgBQszAQF/IAAoAhQiAyABIAIgACgCECADayIBIAEgAksbIgEQBxogACAAKAIUIAFqNgIUIAILBgBBsIgBCwYAQayIAQsGAEGkiAELBwAgAEEEagsHACAAQQhqCyYBAX8gACgCFCIBBEAgARALCyAAKAIEIQEgAEEEahAxIAAQBiABC6kBAQN/AkAgAC0AACICRQ0AA0AgAS0AACIERQRAIAIhAwwCCwJAIAIgBEYNACACQSByIAIgAkHBAGtBGkkbIAEtAAAiAkEgciACIAJBwQBrQRpJG0YNACAALQAAIQMMAgsgAUEBaiEBIAAtAAEhAiAAQQFqIQAgAg0ACwsgA0H/AXEiAEEgciAAIABBwQBrQRpJGyABLQAAIgBBIHIgACAAQcEAa0EaSRtrC8sGAgJ+An8jAEHgAGsiByQAAkACQAJAAkACQAJAAkACQAJAAkACQCAEDg8AAQoCAwQGBwgICAgICAUICyABQgA3AyAMCQsgACACIAMQESIFQn9XBEAgAUEIaiIBBEAgASAAKAIMNgIAIAEgACgCEDYCBAsMCAsCQCAFUARAIAEpAygiAyABKQMgUg0BIAEgAzcDGCABQQE2AgQgASgCAEUNASAAIAdBKGoQIUF/TARAIAFBCGoiAQRAIAEgACgCDDYCACABIAAoAhA2AgQLDAoLAkAgBykDKCIDQiCDUA0AIAcoAlQgASgCMEYNACABQQhqBEAgAUEANgIMIAFBBzYCCAsMCgsgA0IEg1ANASAHKQNAIAEpAxhRDQEgAUEIagRAIAFBADYCDCABQRU2AggLDAkLIAEoAgQNACABKQMoIgMgASkDICIGVA0AIAUgAyAGfSIDWA0AIAEoAjAhBANAIAECfyAFIAN9IgZC/////w8gBkL/////D1QbIganIQBBACACIAOnaiIIRQ0AGiAEIAggAEHUgAEoAgARAAALIgQ2AjAgASABKQMoIAZ8NwMoIAUgAyAGfCIDVg0ACwsgASABKQMgIAV8NwMgDAgLIAEoAgRFDQcgAiABKQMYIgM3AxggASgCMCEAIAJBADYCMCACIAM3AyAgAiAANgIsIAIgAikDAELsAYQ3AwAMBwsgA0IIWgR+IAIgASgCCDYCACACIAEoAgw2AgRCCAVCfwshBQwGCyABEAYMBQtCfyEFIAApAxgiA0J/VwRAIAFBCGoiAQRAIAEgACgCDDYCACABIAAoAhA2AgQLDAULIAdBfzYCGCAHQo+AgICAAjcDECAHQoyAgIDQATcDCCAHQomAgICgATcDACADQQggBxAkQn+FgyEFDAQLIANCD1gEQCABQQhqBEAgAUEANgIMIAFBEjYCCAsMAwsgAkUNAgJAIAAgAikDACACKAIIEBRBAE4EQCAAEDMiA0J/VQ0BCyABQQhqIgEEQCABIAAoAgw2AgAgASAAKAIQNgIECwwDCyABIAM3AyAMAwsgASkDICEFDAILIAFBCGoEQCABQQA2AgwgAUEcNgIICwtCfyEFCyAHQeAAaiQAIAULjAcCAn4CfyMAQRBrIgckAAJAAkACQAJAAkACQAJAAkACQAJAIAQOEQABAgMFBggICAgICAgIBwgECAsgAUJ/NwMgIAFBADoADyABQQA7AQwgAUIANwMYIAEoAqxAIAEoAqhAKAIMEQEArUIBfSEFDAgLQn8hBSABKAIADQdCACEFIANQDQcgAS0ADQ0HIAFBKGohBAJAA0ACQCAHIAMgBX03AwggASgCrEAgAiAFp2ogB0EIaiABKAKoQCgCHBEAACEIQgAgBykDCCAIQQJGGyAFfCEFAkACQAJAIAhBAWsOAwADAQILIAFBAToADSABKQMgIgNCf1cEQCABBEAgAUEANgIEIAFBFDYCAAsMBQsgAS0ADkUNBCADIAVWDQQgASADNwMYIAFBAToADyACIAQgA6cQBxogASkDGCEFDAwLIAEtAAwNAyAAIARCgMAAEBEiBkJ/VwRAIAEEQCABIAAoAgw2AgAgASAAKAIQNgIECwwECyAGUARAIAFBAToADCABKAKsQCABKAKoQCgCGBEDACABKQMgQn9VDQEgAUIANwMgDAELAkAgASkDIEIAWQRAIAFBADoADgwBCyABIAY3AyALIAEoAqxAIAQgBiABKAKoQCgCFBEPABoLIAMgBVYNAQwCCwsgASgCAA0AIAEEQCABQQA2AgQgAUEUNgIACwsgBVBFBEAgAUEAOgAOIAEgASkDGCAFfDcDGAwIC0J/QgAgASgCABshBQwHCyABKAKsQCABKAKoQCgCEBEBAK1CAX0hBQwGCyABLQAQBEAgAS0ADQRAIAIgAS0ADwR/QQAFQQggASgCFCIAIABBfUsbCzsBMCACIAEpAxg3AyAgAiACKQMAQsgAhDcDAAwHCyACIAIpAwBCt////w+DNwMADAYLIAJBADsBMCACKQMAIQMgAS0ADQRAIAEpAxghBSACIANCxACENwMAIAIgBTcDGEIAIQUMBgsgAiADQrv///8Pg0LAAIQ3AwAMBQsgAS0ADw0EIAEoAqxAIAEoAqhAKAIIEQEArCEFDAQLIANCCFoEfiACIAEoAgA2AgAgAiABKAIENgIEQggFQn8LIQUMAwsgAUUNAiABKAKsQCABKAKoQCgCBBEDACABEDEgARAGDAILIAdBfzYCAEEQIAcQJEI/hCEFDAELIAEEQCABQQA2AgQgAUEUNgIAC0J/IQULIAdBEGokACAFC2MAQcgAEAkiAEUEQEGEhAEoAgAhASACBEAgAiABNgIEIAJBATYCAAsgAA8LIABBADoADCAAQQA6AAQgACACNgIAIABBADYCOCAAQgA3AzAgACABQQkgAUEBa0EJSRs2AgggAAu3fAIefwZ+IAIpAwAhIiAAIAE2AhwgACAiQv////8PICJC/////w9UGz4CICAAQRBqIQECfyAALQAEBEACfyAALQAMQQJ0IQpBfiEEAkACQAJAIAEiBUUNACAFKAIgRQ0AIAUoAiRFDQAgBSgCHCIDRQ0AIAMoAgAgBUcNAAJAAkAgAygCICIGQTlrDjkBAgICAgICAgICAgIBAgICAQICAgICAgICAgICAgICAgICAQICAgICAgICAgICAQICAgICAgICAgEACyAGQZoFRg0AIAZBKkcNAQsgCkEFSw0AAkACQCAFKAIMRQ0AIAUoAgQiAQRAIAUoAgBFDQELIAZBmgVHDQEgCkEERg0BCyAFQeDAACgCADYCGEF+DAQLIAUoAhBFDQEgAygCJCEEIAMgCjYCJAJAIAMoAhAEQCADEDACQCAFKAIQIgYgAygCECIIIAYgCEkbIgFFDQAgBSgCDCADKAIIIAEQBxogBSAFKAIMIAFqNgIMIAMgAygCCCABajYCCCAFIAUoAhQgAWo2AhQgBSAFKAIQIAFrIgY2AhAgAyADKAIQIAFrIgg2AhAgCA0AIAMgAygCBDYCCEEAIQgLIAYEQCADKAIgIQYMAgsMBAsgAQ0AIApBAXRBd0EAIApBBEsbaiAEQQF0QXdBACAEQQRKG2pKDQAgCkEERg0ADAILAkACQAJAAkACQCAGQSpHBEAgBkGaBUcNASAFKAIERQ0DDAcLIAMoAhRFBEAgA0HxADYCIAwCCyADKAI0QQx0QYDwAWshBAJAIAMoAowBQQJODQAgAygCiAEiAUEBTA0AIAFBBUwEQCAEQcAAciEEDAELQYABQcABIAFBBkYbIARyIQQLIAMoAgQgCGogBEEgciAEIAMoAmgbIgFBH3AgAXJBH3NBCHQgAUGA/gNxQQh2cjsAACADIAMoAhBBAmoiATYCECADKAJoBEAgAygCBCABaiAFKAIwIgFBGHQgAUEIdEGAgPwHcXIgAUEIdkGA/gNxIAFBGHZycjYAACADIAMoAhBBBGo2AhALIAVBATYCMCADQfEANgIgIAUQCiADKAIQDQcgAygCICEGCwJAAkACQAJAIAZBOUYEfyADQaABakHkgAEoAgARAQAaIAMgAygCECIBQQFqNgIQIAEgAygCBGpBHzoAACADIAMoAhAiAUEBajYCECABIAMoAgRqQYsBOgAAIAMgAygCECIBQQFqNgIQIAEgAygCBGpBCDoAAAJAIAMoAhwiAUUEQCADKAIEIAMoAhBqQQA2AAAgAyADKAIQIgFBBWo2AhAgASADKAIEakEAOgAEQQIhBCADKAKIASIBQQlHBEBBBCABQQJIQQJ0IAMoAowBQQFKGyEECyADIAMoAhAiAUEBajYCECABIAMoAgRqIAQ6AAAgAyADKAIQIgFBAWo2AhAgASADKAIEakEDOgAAIANB8QA2AiAgBRAKIAMoAhBFDQEMDQsgASgCJCELIAEoAhwhCSABKAIQIQggASgCLCENIAEoAgAhBiADIAMoAhAiAUEBajYCEEECIQQgASADKAIEaiANQQBHQQF0IAZBAEdyIAhBAEdBAnRyIAlBAEdBA3RyIAtBAEdBBHRyOgAAIAMoAgQgAygCEGogAygCHCgCBDYAACADIAMoAhAiDUEEaiIGNgIQIAMoAogBIgFBCUcEQEEEIAFBAkhBAnQgAygCjAFBAUobIQQLIAMgDUEFajYCECADKAIEIAZqIAQ6AAAgAygCHCgCDCEEIAMgAygCECIBQQFqNgIQIAEgAygCBGogBDoAACADKAIcIgEoAhAEfyADKAIEIAMoAhBqIAEoAhQ7AAAgAyADKAIQQQJqNgIQIAMoAhwFIAELKAIsBEAgBQJ/IAUoAjAhBiADKAIQIQRBACADKAIEIgFFDQAaIAYgASAEQdSAASgCABEAAAs2AjALIANBxQA2AiAgA0EANgIYDAILIAMoAiAFIAYLQcUAaw4jAAQEBAEEBAQEBAQEBAQEBAQEBAQEBAIEBAQEBAQEBAQEBAMECyADKAIcIgEoAhAiBgRAIAMoAgwiCCADKAIQIgQgAS8BFCADKAIYIg1rIglqSQRAA0AgAygCBCAEaiAGIA1qIAggBGsiCBAHGiADIAMoAgwiDTYCEAJAIAMoAhwoAixFDQAgBCANTw0AIAUCfyAFKAIwIQZBACADKAIEIARqIgFFDQAaIAYgASANIARrQdSAASgCABEAAAs2AjALIAMgAygCGCAIajYCGCAFKAIcIgYQMAJAIAUoAhAiBCAGKAIQIgEgASAESxsiAUUNACAFKAIMIAYoAgggARAHGiAFIAUoAgwgAWo2AgwgBiAGKAIIIAFqNgIIIAUgBSgCFCABajYCFCAFIAUoAhAgAWs2AhAgBiAGKAIQIAFrIgE2AhAgAQ0AIAYgBigCBDYCCAsgAygCEA0MIAMoAhghDSADKAIcKAIQIQZBACEEIAkgCGsiCSADKAIMIghLDQALCyADKAIEIARqIAYgDWogCRAHGiADIAMoAhAgCWoiDTYCEAJAIAMoAhwoAixFDQAgBCANTw0AIAUCfyAFKAIwIQZBACADKAIEIARqIgFFDQAaIAYgASANIARrQdSAASgCABEAAAs2AjALIANBADYCGAsgA0HJADYCIAsgAygCHCgCHARAIAMoAhAiBCEJA0ACQCAEIAMoAgxHDQACQCADKAIcKAIsRQ0AIAQgCU0NACAFAn8gBSgCMCEGQQAgAygCBCAJaiIBRQ0AGiAGIAEgBCAJa0HUgAEoAgARAAALNgIwCyAFKAIcIgYQMAJAIAUoAhAiBCAGKAIQIgEgASAESxsiAUUNACAFKAIMIAYoAgggARAHGiAFIAUoAgwgAWo2AgwgBiAGKAIIIAFqNgIIIAUgBSgCFCABajYCFCAFIAUoAhAgAWs2AhAgBiAGKAIQIAFrIgE2AhAgAQ0AIAYgBigCBDYCCAtBACEEQQAhCSADKAIQRQ0ADAsLIAMoAhwoAhwhBiADIAMoAhgiAUEBajYCGCABIAZqLQAAIQEgAyAEQQFqNgIQIAMoAgQgBGogAToAACABBEAgAygCECEEDAELCwJAIAMoAhwoAixFDQAgAygCECIGIAlNDQAgBQJ/IAUoAjAhBEEAIAMoAgQgCWoiAUUNABogBCABIAYgCWtB1IABKAIAEQAACzYCMAsgA0EANgIYCyADQdsANgIgCwJAIAMoAhwoAiRFDQAgAygCECIEIQkDQAJAIAQgAygCDEcNAAJAIAMoAhwoAixFDQAgBCAJTQ0AIAUCfyAFKAIwIQZBACADKAIEIAlqIgFFDQAaIAYgASAEIAlrQdSAASgCABEAAAs2AjALIAUoAhwiBhAwAkAgBSgCECIEIAYoAhAiASABIARLGyIBRQ0AIAUoAgwgBigCCCABEAcaIAUgBSgCDCABajYCDCAGIAYoAgggAWo2AgggBSAFKAIUIAFqNgIUIAUgBSgCECABazYCECAGIAYoAhAgAWsiATYCECABDQAgBiAGKAIENgIIC0EAIQRBACEJIAMoAhBFDQAMCgsgAygCHCgCJCEGIAMgAygCGCIBQQFqNgIYIAEgBmotAAAhASADIARBAWo2AhAgAygCBCAEaiABOgAAIAEEQCADKAIQIQQMAQsLIAMoAhwoAixFDQAgAygCECIGIAlNDQAgBQJ/IAUoAjAhBEEAIAMoAgQgCWoiAUUNABogBCABIAYgCWtB1IABKAIAEQAACzYCMAsgA0HnADYCIAsCQCADKAIcKAIsBEAgAygCDCADKAIQIgFBAmpJBH8gBRAKIAMoAhANAkEABSABCyADKAIEaiAFKAIwOwAAIAMgAygCEEECajYCECADQaABakHkgAEoAgARAQAaCyADQfEANgIgIAUQCiADKAIQRQ0BDAcLDAYLIAUoAgQNAQsgAygCPA0AIApFDQEgAygCIEGaBUYNAQsCfyADKAKIASIBRQRAIAMgChCFAQwBCwJAAkACQCADKAKMAUECaw4CAAECCwJ/AkADQAJAAkAgAygCPA0AIAMQLyADKAI8DQAgCg0BQQAMBAsgAygCSCADKAJoai0AACEEIAMgAygC8C0iAUEBajYC8C0gASADKALsLWpBADoAACADIAMoAvAtIgFBAWo2AvAtIAEgAygC7C1qQQA6AAAgAyADKALwLSIBQQFqNgLwLSABIAMoAuwtaiAEOgAAIAMgBEECdGoiASABLwHkAUEBajsB5AEgAyADKAI8QQFrNgI8IAMgAygCaEEBaiIBNgJoIAMoAvAtIAMoAvQtRw0BQQAhBCADIAMoAlgiBkEATgR/IAMoAkggBmoFQQALIAEgBmtBABAPIAMgAygCaDYCWCADKAIAEAogAygCACgCEA0BDAILCyADQQA2AoQuIApBBEYEQCADIAMoAlgiAUEATgR/IAMoAkggAWoFQQALIAMoAmggAWtBARAPIAMgAygCaDYCWCADKAIAEApBA0ECIAMoAgAoAhAbDAILIAMoAvAtBEBBACEEIAMgAygCWCIBQQBOBH8gAygCSCABagVBAAsgAygCaCABa0EAEA8gAyADKAJoNgJYIAMoAgAQCiADKAIAKAIQRQ0BC0EBIQQLIAQLDAILAn8CQANAAkACQAJAAkACQCADKAI8Ig1BggJLDQAgAxAvAkAgAygCPCINQYICSw0AIAoNAEEADAgLIA1FDQQgDUECSw0AIAMoAmghCAwBCyADKAJoIghFBEBBACEIDAELIAMoAkggCGoiAUEBayIELQAAIgYgAS0AAEcNACAGIAQtAAJHDQAgBEEDaiEEQQAhCQJAA0AgBiAELQAARw0BIAQtAAEgBkcEQCAJQQFyIQkMAgsgBC0AAiAGRwRAIAlBAnIhCQwCCyAELQADIAZHBEAgCUEDciEJDAILIAQtAAQgBkcEQCAJQQRyIQkMAgsgBC0ABSAGRwRAIAlBBXIhCQwCCyAELQAGIAZHBEAgCUEGciEJDAILIAQtAAcgBkcEQCAJQQdyIQkMAgsgBEEIaiEEIAlB+AFJIQEgCUEIaiEJIAENAAtBgAIhCQtBggIhBCANIAlBAmoiASABIA1LGyIBQYECSw0BIAEiBEECSw0BCyADKAJIIAhqLQAAIQQgAyADKALwLSIBQQFqNgLwLSABIAMoAuwtakEAOgAAIAMgAygC8C0iAUEBajYC8C0gASADKALsLWpBADoAACADIAMoAvAtIgFBAWo2AvAtIAEgAygC7C1qIAQ6AAAgAyAEQQJ0aiIBIAEvAeQBQQFqOwHkASADIAMoAjxBAWs2AjwgAyADKAJoQQFqIgQ2AmgMAQsgAyADKALwLSIBQQFqNgLwLSABIAMoAuwtakEBOgAAIAMgAygC8C0iAUEBajYC8C0gASADKALsLWpBADoAACADIAMoAvAtIgFBAWo2AvAtIAEgAygC7C1qIARBA2s6AAAgAyADKAKALkEBajYCgC4gBEH9zgBqLQAAQQJ0IANqQegJaiIBIAEvAQBBAWo7AQAgA0GAywAtAABBAnRqQdgTaiIBIAEvAQBBAWo7AQAgAyADKAI8IARrNgI8IAMgAygCaCAEaiIENgJoCyADKALwLSADKAL0LUcNAUEAIQggAyADKAJYIgFBAE4EfyADKAJIIAFqBUEACyAEIAFrQQAQDyADIAMoAmg2AlggAygCABAKIAMoAgAoAhANAQwCCwsgA0EANgKELiAKQQRGBEAgAyADKAJYIgFBAE4EfyADKAJIIAFqBUEACyADKAJoIAFrQQEQDyADIAMoAmg2AlggAygCABAKQQNBAiADKAIAKAIQGwwCCyADKALwLQRAQQAhCCADIAMoAlgiAUEATgR/IAMoAkggAWoFQQALIAMoAmggAWtBABAPIAMgAygCaDYCWCADKAIAEAogAygCACgCEEUNAQtBASEICyAICwwBCyADIAogAUEMbEG42ABqKAIAEQIACyIBQX5xQQJGBEAgA0GaBTYCIAsgAUF9cUUEQEEAIQQgBSgCEA0CDAQLIAFBAUcNAAJAAkACQCAKQQFrDgUAAQEBAgELIAMpA5guISICfwJ+IAMoAqAuIgFBA2oiCUE/TQRAQgIgAa2GICKEDAELIAFBwABGBEAgAygCBCADKAIQaiAiNwAAIAMgAygCEEEIajYCEEICISJBCgwCCyADKAIEIAMoAhBqQgIgAa2GICKENwAAIAMgAygCEEEIajYCECABQT1rIQlCAkHAACABa62ICyEiIAlBB2ogCUE5SQ0AGiADKAIEIAMoAhBqICI3AAAgAyADKAIQQQhqNgIQQgAhIiAJQTlrCyEBIAMgIjcDmC4gAyABNgKgLiADEDAMAQsgA0EAQQBBABA5IApBA0cNACADKAJQQQBBgIAIEBkgAygCPA0AIANBADYChC4gA0EANgJYIANBADYCaAsgBRAKIAUoAhANAAwDC0EAIQQgCkEERw0AAkACfwJAAkAgAygCFEEBaw4CAQADCyAFIANBoAFqQeCAASgCABEBACIBNgIwIAMoAgQgAygCEGogATYAACADIAMoAhBBBGoiATYCECADKAIEIAFqIQQgBSgCCAwBCyADKAIEIAMoAhBqIQQgBSgCMCIBQRh0IAFBCHRBgID8B3FyIAFBCHZBgP4DcSABQRh2cnILIQEgBCABNgAAIAMgAygCEEEEajYCEAsgBRAKIAMoAhQiAUEBTgRAIANBACABazYCFAsgAygCEEUhBAsgBAwCCyAFQezAACgCADYCGEF7DAELIANBfzYCJEEACwwBCyMAQRBrIhQkAEF+IRcCQCABIgxFDQAgDCgCIEUNACAMKAIkRQ0AIAwoAhwiB0UNACAHKAIAIAxHDQAgBygCBCIIQbT+AGtBH0sNACAMKAIMIhBFDQAgDCgCACIBRQRAIAwoAgQNAQsgCEG//gBGBEAgB0HA/gA2AgRBwP4AIQgLIAdBpAFqIR8gB0G8BmohGSAHQbwBaiEcIAdBoAFqIR0gB0G4AWohGiAHQfwKaiEYIAdBQGshHiAHKAKIASEFIAwoAgQiICEGIAcoAoQBIQogDCgCECIPIRYCfwJAAkACQANAAkBBfSEEQQEhCQJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAIAhBtP4Aaw4fBwYICQolJicoBSwtLQsZGgQMAjIzATUANw0OAzlISUwLIAcoApQBIQMgASEEIAYhCAw1CyAHKAKUASEDIAEhBCAGIQgMMgsgBygCtAEhCAwuCyAHKAIMIQgMQQsgBUEOTw0pIAZFDUEgBUEIaiEIIAFBAWohBCAGQQFrIQkgAS0AACAFdCAKaiEKIAVBBkkNDCAEIQEgCSEGIAghBQwpCyAFQSBPDSUgBkUNQCABQQFqIQQgBkEBayEIIAEtAAAgBXQgCmohCiAFQRhJDQ0gBCEBIAghBgwlCyAFQRBPDRUgBkUNPyAFQQhqIQggAUEBaiEEIAZBAWshCSABLQAAIAV0IApqIQogBUEISQ0NIAQhASAJIQYgCCEFDBULIAcoAgwiC0UNByAFQRBPDSIgBkUNPiAFQQhqIQggAUEBaiEEIAZBAWshCSABLQAAIAV0IApqIQogBUEISQ0NIAQhASAJIQYgCCEFDCILIAVBH0sNFQwUCyAFQQ9LDRYMFQsgBygCFCIEQYAIcUUEQCAFIQgMFwsgCiEIIAVBD0sNGAwXCyAKIAVBB3F2IQogBUF4cSIFQR9LDQwgBkUNOiAFQQhqIQggAUEBaiEEIAZBAWshCSABLQAAIAV0IApqIQogBUEYSQ0GIAQhASAJIQYgCCEFDAwLIAcoArQBIgggBygCqAEiC08NIwwiCyAPRQ0qIBAgBygCjAE6AAAgB0HI/gA2AgQgD0EBayEPIBBBAWohECAHKAIEIQgMOQsgBygCDCIDRQRAQQAhCAwJCyAFQR9LDQcgBkUNNyAFQQhqIQggAUEBaiEEIAZBAWshCSABLQAAIAV0IApqIQogBUEYSQ0BIAQhASAJIQYgCCEFDAcLIAdBwP4ANgIEDCoLIAlFBEAgBCEBQQAhBiAIIQUgDSEEDDgLIAVBEGohCSABQQJqIQQgBkECayELIAEtAAEgCHQgCmohCiAFQQ9LBEAgBCEBIAshBiAJIQUMBgsgC0UEQCAEIQFBACEGIAkhBSANIQQMOAsgBUEYaiEIIAFBA2ohBCAGQQNrIQsgAS0AAiAJdCAKaiEKIAVBB0sEQCAEIQEgCyEGIAghBQwGCyALRQRAIAQhAUEAIQYgCCEFIA0hBAw4CyAFQSBqIQUgBkEEayEGIAEtAAMgCHQgCmohCiABQQRqIQEMBQsgCUUEQCAEIQFBACEGIAghBSANIQQMNwsgBUEQaiEFIAZBAmshBiABLQABIAh0IApqIQogAUECaiEBDBwLIAlFBEAgBCEBQQAhBiAIIQUgDSEEDDYLIAVBEGohCSABQQJqIQQgBkECayELIAEtAAEgCHQgCmohCiAFQQ9LBEAgBCEBIAshBiAJIQUMBgsgC0UEQCAEIQFBACEGIAkhBSANIQQMNgsgBUEYaiEIIAFBA2ohBCAGQQNrIQsgAS0AAiAJdCAKaiEKIAUEQCAEIQEgCyEGIAghBQwGCyALRQRAIAQhAUEAIQYgCCEFIA0hBAw2CyAFQSBqIQUgBkEEayEGIAEtAAMgCHQgCmohCiABQQRqIQEMBQsgBUEIaiEJIAhFBEAgBCEBQQAhBiAJIQUgDSEEDDULIAFBAmohBCAGQQJrIQggAS0AASAJdCAKaiEKIAVBD0sEQCAEIQEgCCEGDBgLIAVBEGohCSAIRQRAIAQhAUEAIQYgCSEFIA0hBAw1CyABQQNqIQQgBkEDayEIIAEtAAIgCXQgCmohCiAFQQdLBEAgBCEBIAghBgwYCyAFQRhqIQUgCEUEQCAEIQFBACEGIA0hBAw1CyAGQQRrIQYgAS0AAyAFdCAKaiEKIAFBBGohAQwXCyAJDQYgBCEBQQAhBiAIIQUgDSEEDDMLIAlFBEAgBCEBQQAhBiAIIQUgDSEEDDMLIAVBEGohBSAGQQJrIQYgAS0AASAIdCAKaiEKIAFBAmohAQwUCyAMIBYgD2siCSAMKAIUajYCFCAHIAcoAiAgCWo2AiACQCADQQRxRQ0AIAkEQAJAIBAgCWshBCAMKAIcIggoAhQEQCAIQUBrIAQgCUEAQdiAASgCABEIAAwBCyAIIAgoAhwgBCAJQcCAASgCABEAACIENgIcIAwgBDYCMAsLIAcoAhRFDQAgByAeQeCAASgCABEBACIENgIcIAwgBDYCMAsCQCAHKAIMIghBBHFFDQAgBygCHCAKIApBCHRBgID8B3EgCkEYdHIgCkEIdkGA/gNxIApBGHZyciAHKAIUG0YNACAHQdH+ADYCBCAMQaQMNgIYIA8hFiAHKAIEIQgMMQtBACEKQQAhBSAPIRYLIAdBz/4ANgIEDC0LIApB//8DcSIEIApBf3NBEHZHBEAgB0HR/gA2AgQgDEGOCjYCGCAHKAIEIQgMLwsgB0HC/gA2AgQgByAENgKMAUEAIQpBACEFCyAHQcP+ADYCBAsgBygCjAEiBARAIA8gBiAEIAQgBksbIgQgBCAPSxsiCEUNHiAQIAEgCBAHIQQgByAHKAKMASAIazYCjAEgBCAIaiEQIA8gCGshDyABIAhqIQEgBiAIayEGIAcoAgQhCAwtCyAHQb/+ADYCBCAHKAIEIQgMLAsgBUEQaiEFIAZBAmshBiABLQABIAh0IApqIQogAUECaiEBCyAHIAo2AhQgCkH/AXFBCEcEQCAHQdH+ADYCBCAMQYIPNgIYIAcoAgQhCAwrCyAKQYDAA3EEQCAHQdH+ADYCBCAMQY0JNgIYIAcoAgQhCAwrCyAHKAIkIgQEQCAEIApBCHZBAXE2AgALAkAgCkGABHFFDQAgBy0ADEEEcUUNACAUIAo7AAwgBwJ/IAcoAhwhBUEAIBRBDGoiBEUNABogBSAEQQJB1IABKAIAEQAACzYCHAsgB0G2/gA2AgRBACEFQQAhCgsgBkUNKCABQQFqIQQgBkEBayEIIAEtAAAgBXQgCmohCiAFQRhPBEAgBCEBIAghBgwBCyAFQQhqIQkgCEUEQCAEIQFBACEGIAkhBSANIQQMKwsgAUECaiEEIAZBAmshCCABLQABIAl0IApqIQogBUEPSwRAIAQhASAIIQYMAQsgBUEQaiEJIAhFBEAgBCEBQQAhBiAJIQUgDSEEDCsLIAFBA2ohBCAGQQNrIQggAS0AAiAJdCAKaiEKIAVBB0sEQCAEIQEgCCEGDAELIAVBGGohBSAIRQRAIAQhAUEAIQYgDSEEDCsLIAZBBGshBiABLQADIAV0IApqIQogAUEEaiEBCyAHKAIkIgQEQCAEIAo2AgQLAkAgBy0AFUECcUUNACAHLQAMQQRxRQ0AIBQgCjYADCAHAn8gBygCHCEFQQAgFEEMaiIERQ0AGiAFIARBBEHUgAEoAgARAAALNgIcCyAHQbf+ADYCBEEAIQVBACEKCyAGRQ0mIAFBAWohBCAGQQFrIQggAS0AACAFdCAKaiEKIAVBCE8EQCAEIQEgCCEGDAELIAVBCGohBSAIRQRAIAQhAUEAIQYgDSEEDCkLIAZBAmshBiABLQABIAV0IApqIQogAUECaiEBCyAHKAIkIgQEQCAEIApBCHY2AgwgBCAKQf8BcTYCCAsCQCAHLQAVQQJxRQ0AIActAAxBBHFFDQAgFCAKOwAMIAcCfyAHKAIcIQVBACAUQQxqIgRFDQAaIAUgBEECQdSAASgCABEAAAs2AhwLIAdBuP4ANgIEQQAhCEEAIQVBACEKIAcoAhQiBEGACHENAQsgBygCJCIEBEAgBEEANgIQCyAIIQUMAgsgBkUEQEEAIQYgCCEKIA0hBAwmCyABQQFqIQkgBkEBayELIAEtAAAgBXQgCGohCiAFQQhPBEAgCSEBIAshBgwBCyAFQQhqIQUgC0UEQCAJIQFBACEGIA0hBAwmCyAGQQJrIQYgAS0AASAFdCAKaiEKIAFBAmohAQsgByAKQf//A3EiCDYCjAEgBygCJCIFBEAgBSAINgIUC0EAIQUCQCAEQYAEcUUNACAHLQAMQQRxRQ0AIBQgCjsADCAHAn8gBygCHCEIQQAgFEEMaiIERQ0AGiAIIARBAkHUgAEoAgARAAALNgIcC0EAIQoLIAdBuf4ANgIECyAHKAIUIglBgAhxBEAgBiAHKAKMASIIIAYgCEkbIg4EQAJAIAcoAiQiA0UNACADKAIQIgRFDQAgAygCGCILIAMoAhQgCGsiCE0NACAEIAhqIAEgCyAIayAOIAggDmogC0sbEAcaIAcoAhQhCQsCQCAJQYAEcUUNACAHLQAMQQRxRQ0AIAcCfyAHKAIcIQRBACABRQ0AGiAEIAEgDkHUgAEoAgARAAALNgIcCyAHIAcoAowBIA5rIgg2AowBIAYgDmshBiABIA5qIQELIAgNEwsgB0G6/gA2AgQgB0EANgKMAQsCQCAHLQAVQQhxBEBBACEIIAZFDQQDQCABIAhqLQAAIQMCQCAHKAIkIgtFDQAgCygCHCIERQ0AIAcoAowBIgkgCygCIE8NACAHIAlBAWo2AowBIAQgCWogAzoAAAsgA0EAIAYgCEEBaiIISxsNAAsCQCAHLQAVQQJxRQ0AIActAAxBBHFFDQAgBwJ/IAcoAhwhBEEAIAFFDQAaIAQgASAIQdSAASgCABEAAAs2AhwLIAEgCGohASAGIAhrIQYgA0UNAQwTCyAHKAIkIgRFDQAgBEEANgIcCyAHQbv+ADYCBCAHQQA2AowBCwJAIActABVBEHEEQEEAIQggBkUNAwNAIAEgCGotAAAhAwJAIAcoAiQiC0UNACALKAIkIgRFDQAgBygCjAEiCSALKAIoTw0AIAcgCUEBajYCjAEgBCAJaiADOgAACyADQQAgBiAIQQFqIghLGw0ACwJAIActABVBAnFFDQAgBy0ADEEEcUUNACAHAn8gBygCHCEEQQAgAUUNABogBCABIAhB1IABKAIAEQAACzYCHAsgASAIaiEBIAYgCGshBiADRQ0BDBILIAcoAiQiBEUNACAEQQA2AiQLIAdBvP4ANgIECyAHKAIUIgtBgARxBEACQCAFQQ9LDQAgBkUNHyAFQQhqIQggAUEBaiEEIAZBAWshCSABLQAAIAV0IApqIQogBUEITwRAIAQhASAJIQYgCCEFDAELIAlFBEAgBCEBQQAhBiAIIQUgDSEEDCILIAVBEGohBSAGQQJrIQYgAS0AASAIdCAKaiEKIAFBAmohAQsCQCAHLQAMQQRxRQ0AIAogBy8BHEYNACAHQdH+ADYCBCAMQdcMNgIYIAcoAgQhCAwgC0EAIQpBACEFCyAHKAIkIgQEQCAEQQE2AjAgBCALQQl2QQFxNgIsCwJAIActAAxBBHFFDQAgC0UNACAHIB5B5IABKAIAEQEAIgQ2AhwgDCAENgIwCyAHQb/+ADYCBCAHKAIEIQgMHgtBACEGDA4LAkAgC0ECcUUNACAKQZ+WAkcNACAHKAIoRQRAIAdBDzYCKAtBACEKIAdBADYCHCAUQZ+WAjsADCAHIBRBDGoiBAR/QQAgBEECQdSAASgCABEAAAVBAAs2AhwgB0G1/gA2AgRBACEFIAcoAgQhCAwdCyAHKAIkIgQEQCAEQX82AjALAkAgC0EBcQRAIApBCHRBgP4DcSAKQQh2akEfcEUNAQsgB0HR/gA2AgQgDEH2CzYCGCAHKAIEIQgMHQsgCkEPcUEIRwRAIAdB0f4ANgIEIAxBgg82AhggBygCBCEIDB0LIApBBHYiBEEPcSIJQQhqIQsgCUEHTUEAIAcoAigiCAR/IAgFIAcgCzYCKCALCyALTxtFBEAgBUEEayEFIAdB0f4ANgIEIAxB+gw2AhggBCEKIAcoAgQhCAwdCyAHQQE2AhxBACEFIAdBADYCFCAHQYACIAl0NgIYIAxBATYCMCAHQb3+AEG//gAgCkGAwABxGzYCBEEAIQogBygCBCEIDBwLIAcgCkEIdEGAgPwHcSAKQRh0ciAKQQh2QYD+A3EgCkEYdnJyIgQ2AhwgDCAENgIwIAdBvv4ANgIEQQAhCkEAIQULIAcoAhBFBEAgDCAPNgIQIAwgEDYCDCAMIAY2AgQgDCABNgIAIAcgBTYCiAEgByAKNgKEAUECIRcMIAsgB0EBNgIcIAxBATYCMCAHQb/+ADYCBAsCfwJAIAcoAghFBEAgBUEDSQ0BIAUMAgsgB0HO/gA2AgQgCiAFQQdxdiEKIAVBeHEhBSAHKAIEIQgMGwsgBkUNGSAGQQFrIQYgAS0AACAFdCAKaiEKIAFBAWohASAFQQhqCyEEIAcgCkEBcTYCCAJAAkACQAJAAkAgCkEBdkEDcUEBaw4DAQIDAAsgB0HB/gA2AgQMAwsgB0Gw2wA2ApgBIAdCiYCAgNAANwOgASAHQbDrADYCnAEgB0HH/gA2AgQMAgsgB0HE/gA2AgQMAQsgB0HR/gA2AgQgDEHXDTYCGAsgBEEDayEFIApBA3YhCiAHKAIEIQgMGQsgByAKQR9xIghBgQJqNgKsASAHIApBBXZBH3EiBEEBajYCsAEgByAKQQp2QQ9xQQRqIgs2AqgBIAVBDmshBSAKQQ52IQogCEEdTUEAIARBHkkbRQRAIAdB0f4ANgIEIAxB6gk2AhggBygCBCEIDBkLIAdBxf4ANgIEQQAhCCAHQQA2ArQBCyAIIQQDQCAFQQJNBEAgBkUNGCAGQQFrIQYgAS0AACAFdCAKaiEKIAVBCGohBSABQQFqIQELIAcgBEEBaiIINgK0ASAHIARBAXRBsOwAai8BAEEBdGogCkEHcTsBvAEgBUEDayEFIApBA3YhCiALIAgiBEsNAAsLIAhBEk0EQEESIAhrIQ1BAyAIa0EDcSIEBEADQCAHIAhBAXRBsOwAai8BAEEBdGpBADsBvAEgCEEBaiEIIARBAWsiBA0ACwsgDUEDTwRAA0AgB0G8AWoiDSAIQQF0IgRBsOwAai8BAEEBdGpBADsBACANIARBsuwAai8BAEEBdGpBADsBACANIARBtOwAai8BAEEBdGpBADsBACANIARBtuwAai8BAEEBdGpBADsBACAIQQRqIghBE0cNAAsLIAdBEzYCtAELIAdBBzYCoAEgByAYNgKYASAHIBg2ArgBQQAhCEEAIBxBEyAaIB0gGRBOIg0EQCAHQdH+ADYCBCAMQfQINgIYIAcoAgQhCAwXCyAHQcb+ADYCBCAHQQA2ArQBQQAhDQsgBygCrAEiFSAHKAKwAWoiESAISwRAQX8gBygCoAF0QX9zIRIgBygCmAEhGwNAIAYhCSABIQsCQCAFIgMgGyAKIBJxIhNBAnRqLQABIg5PBEAgBSEEDAELA0AgCUUNDSALLQAAIAN0IQ4gC0EBaiELIAlBAWshCSADQQhqIgQhAyAEIBsgCiAOaiIKIBJxIhNBAnRqLQABIg5JDQALIAshASAJIQYLAkAgGyATQQJ0ai8BAiIFQQ9NBEAgByAIQQFqIgk2ArQBIAcgCEEBdGogBTsBvAEgBCAOayEFIAogDnYhCiAJIQgMAQsCfwJ/AkACQAJAIAVBEGsOAgABAgsgDkECaiIFIARLBEADQCAGRQ0bIAZBAWshBiABLQAAIAR0IApqIQogAUEBaiEBIARBCGoiBCAFSQ0ACwsgBCAOayEFIAogDnYhBCAIRQRAIAdB0f4ANgIEIAxBvAk2AhggBCEKIAcoAgQhCAwdCyAFQQJrIQUgBEECdiEKIARBA3FBA2ohCSAIQQF0IAdqLwG6AQwDCyAOQQNqIgUgBEsEQANAIAZFDRogBkEBayEGIAEtAAAgBHQgCmohCiABQQFqIQEgBEEIaiIEIAVJDQALCyAEIA5rQQNrIQUgCiAOdiIEQQN2IQogBEEHcUEDagwBCyAOQQdqIgUgBEsEQANAIAZFDRkgBkEBayEGIAEtAAAgBHQgCmohCiABQQFqIQEgBEEIaiIEIAVJDQALCyAEIA5rQQdrIQUgCiAOdiIEQQd2IQogBEH/AHFBC2oLIQlBAAshAyAIIAlqIBFLDRMgCUEBayEEIAlBA3EiCwRAA0AgByAIQQF0aiADOwG8ASAIQQFqIQggCUEBayEJIAtBAWsiCw0ACwsgBEEDTwRAA0AgByAIQQF0aiIEIAM7Ab4BIAQgAzsBvAEgBCADOwHAASAEIAM7AcIBIAhBBGohCCAJQQRrIgkNAAsLIAcgCDYCtAELIAggEUkNAAsLIAcvAbwFRQRAIAdB0f4ANgIEIAxB0Qs2AhggBygCBCEIDBYLIAdBCjYCoAEgByAYNgKYASAHIBg2ArgBQQEgHCAVIBogHSAZEE4iDQRAIAdB0f4ANgIEIAxB2Ag2AhggBygCBCEIDBYLIAdBCTYCpAEgByAHKAK4ATYCnAFBAiAHIAcoAqwBQQF0akG8AWogBygCsAEgGiAfIBkQTiINBEAgB0HR/gA2AgQgDEGmCTYCGCAHKAIEIQgMFgsgB0HH/gA2AgRBACENCyAHQcj+ADYCBAsCQCAGQQ9JDQAgD0GEAkkNACAMIA82AhAgDCAQNgIMIAwgBjYCBCAMIAE2AgAgByAFNgKIASAHIAo2AoQBIAwgFkHogAEoAgARBwAgBygCiAEhBSAHKAKEASEKIAwoAgQhBiAMKAIAIQEgDCgCECEPIAwoAgwhECAHKAIEQb/+AEcNByAHQX82ApBHIAcoAgQhCAwUCyAHQQA2ApBHIAUhCSAGIQggASEEAkAgBygCmAEiEiAKQX8gBygCoAF0QX9zIhVxIg5BAnRqLQABIgsgBU0EQCAFIQMMAQsDQCAIRQ0PIAQtAAAgCXQhCyAEQQFqIQQgCEEBayEIIAlBCGoiAyEJIAMgEiAKIAtqIgogFXEiDkECdGotAAEiC0kNAAsLIBIgDkECdGoiAS8BAiETAkBBACABLQAAIhEgEUHwAXEbRQRAIAshBgwBCyAIIQYgBCEBAkAgAyIFIAsgEiAKQX8gCyARanRBf3MiFXEgC3YgE2oiEUECdGotAAEiDmpPBEAgAyEJDAELA0AgBkUNDyABLQAAIAV0IQ4gAUEBaiEBIAZBAWshBiAFQQhqIgkhBSALIBIgCiAOaiIKIBVxIAt2IBNqIhFBAnRqLQABIg5qIAlLDQALIAEhBCAGIQgLIBIgEUECdGoiAS0AACERIAEvAQIhEyAHIAs2ApBHIAsgDmohBiAJIAtrIQMgCiALdiEKIA4hCwsgByAGNgKQRyAHIBNB//8DcTYCjAEgAyALayEFIAogC3YhCiARRQRAIAdBzf4ANgIEDBALIBFBIHEEQCAHQb/+ADYCBCAHQX82ApBHDBALIBFBwABxBEAgB0HR/gA2AgQgDEHQDjYCGAwQCyAHQcn+ADYCBCAHIBFBD3EiAzYClAELAkAgA0UEQCAHKAKMASELIAQhASAIIQYMAQsgBSEJIAghBiAEIQsCQCADIAVNBEAgBCEBDAELA0AgBkUNDSAGQQFrIQYgCy0AACAJdCAKaiEKIAtBAWoiASELIAlBCGoiCSADSQ0ACwsgByAHKAKQRyADajYCkEcgByAHKAKMASAKQX8gA3RBf3NxaiILNgKMASAJIANrIQUgCiADdiEKCyAHQcr+ADYCBCAHIAs2ApRHCyAFIQkgBiEIIAEhBAJAIAcoApwBIhIgCkF/IAcoAqQBdEF/cyIVcSIOQQJ0ai0AASIDIAVNBEAgBSELDAELA0AgCEUNCiAELQAAIAl0IQMgBEEBaiEEIAhBAWshCCAJQQhqIgshCSALIBIgAyAKaiIKIBVxIg5BAnRqLQABIgNJDQALCyASIA5BAnRqIgEvAQIhEwJAIAEtAAAiEUHwAXEEQCAHKAKQRyEGIAMhCQwBCyAIIQYgBCEBAkAgCyIFIAMgEiAKQX8gAyARanRBf3MiFXEgA3YgE2oiEUECdGotAAEiCWpPBEAgCyEODAELA0AgBkUNCiABLQAAIAV0IQkgAUEBaiEBIAZBAWshBiAFQQhqIg4hBSADIBIgCSAKaiIKIBVxIAN2IBNqIhFBAnRqLQABIglqIA5LDQALIAEhBCAGIQgLIBIgEUECdGoiAS0AACERIAEvAQIhEyAHIAcoApBHIANqIgY2ApBHIA4gA2shCyAKIAN2IQoLIAcgBiAJajYCkEcgCyAJayEFIAogCXYhCiARQcAAcQRAIAdB0f4ANgIEIAxB7A42AhggBCEBIAghBiAHKAIEIQgMEgsgB0HL/gA2AgQgByARQQ9xIgM2ApQBIAcgE0H//wNxNgKQAQsCQCADRQRAIAQhASAIIQYMAQsgBSEJIAghBiAEIQsCQCADIAVNBEAgBCEBDAELA0AgBkUNCCAGQQFrIQYgCy0AACAJdCAKaiEKIAtBAWoiASELIAlBCGoiCSADSQ0ACwsgByAHKAKQRyADajYCkEcgByAHKAKQASAKQX8gA3RBf3NxajYCkAEgCSADayEFIAogA3YhCgsgB0HM/gA2AgQLIA9FDQACfyAHKAKQASIIIBYgD2siBEsEQAJAIAggBGsiCCAHKAIwTQ0AIAcoAoxHRQ0AIAdB0f4ANgIEIAxBuQw2AhggBygCBCEIDBILAn8CQAJ/IAcoAjQiBCAISQRAIAcoAjggBygCLCAIIARrIghragwBCyAHKAI4IAQgCGtqCyILIBAgDyAQaiAQa0EBaqwiISAPIAcoAowBIgQgCCAEIAhJGyIEIAQgD0sbIgitIiIgISAiVBsiIqciCWoiBEkgCyAQT3ENACALIBBNIAkgC2ogEEtxDQAgECALIAkQBxogBAwBCyAQIAsgCyAQayIEIARBH3UiBGogBHMiCRAHIAlqIQQgIiAJrSIkfSIjUEUEQCAJIAtqIQkDQAJAICMgJCAjICRUGyIiQiBUBEAgIiEhDAELICIiIUIgfSImQgWIQgF8QgODIiVQRQRAA0AgBCAJKQAANwAAIAQgCSkAGDcAGCAEIAkpABA3ABAgBCAJKQAINwAIICFCIH0hISAJQSBqIQkgBEEgaiEEICVCAX0iJUIAUg0ACwsgJkLgAFQNAANAIAQgCSkAADcAACAEIAkpABg3ABggBCAJKQAQNwAQIAQgCSkACDcACCAEIAkpADg3ADggBCAJKQAwNwAwIAQgCSkAKDcAKCAEIAkpACA3ACAgBCAJKQBYNwBYIAQgCSkAUDcAUCAEIAkpAEg3AEggBCAJKQBANwBAIAQgCSkAYDcAYCAEIAkpAGg3AGggBCAJKQBwNwBwIAQgCSkAeDcAeCAJQYABaiEJIARBgAFqIQQgIUKAAX0iIUIfVg0ACwsgIUIQWgRAIAQgCSkAADcAACAEIAkpAAg3AAggIUIQfSEhIAlBEGohCSAEQRBqIQQLICFCCFoEQCAEIAkpAAA3AAAgIUIIfSEhIAlBCGohCSAEQQhqIQQLICFCBFoEQCAEIAkoAAA2AAAgIUIEfSEhIAlBBGohCSAEQQRqIQQLICFCAloEQCAEIAkvAAA7AAAgIUICfSEhIAlBAmohCSAEQQJqIQQLICMgIn0hIyAhUEUEQCAEIAktAAA6AAAgCUEBaiEJIARBAWohBAsgI0IAUg0ACwsgBAsMAQsgECAIIA8gBygCjAEiBCAEIA9LGyIIIA9ByIABKAIAEQQACyEQIAcgBygCjAEgCGsiBDYCjAEgDyAIayEPIAQNAiAHQcj+ADYCBCAHKAIEIQgMDwsgDSEJCyAJIQQMDgsgBygCBCEIDAwLIAEgBmohASAFIAZBA3RqIQUMCgsgBCAIaiEBIAUgCEEDdGohBQwJCyAEIAhqIQEgCyAIQQN0aiEFDAgLIAEgBmohASAFIAZBA3RqIQUMBwsgBCAIaiEBIAUgCEEDdGohBQwGCyAEIAhqIQEgAyAIQQN0aiEFDAULIAEgBmohASAFIAZBA3RqIQUMBAsgB0HR/gA2AgQgDEG8CTYCGCAHKAIEIQgMBAsgBCEBIAghBiAHKAIEIQgMAwtBACEGIAQhBSANIQQMAwsCQAJAIAhFBEAgCiEJDAELIAcoAhRFBEAgCiEJDAELAkAgBUEfSw0AIAZFDQMgBUEIaiEJIAFBAWohBCAGQQFrIQsgAS0AACAFdCAKaiEKIAVBGE8EQCAEIQEgCyEGIAkhBQwBCyALRQRAIAQhAUEAIQYgCSEFIA0hBAwGCyAFQRBqIQsgAUECaiEEIAZBAmshAyABLQABIAl0IApqIQogBUEPSwRAIAQhASADIQYgCyEFDAELIANFBEAgBCEBQQAhBiALIQUgDSEEDAYLIAVBGGohCSABQQNqIQQgBkEDayEDIAEtAAIgC3QgCmohCiAFQQdLBEAgBCEBIAMhBiAJIQUMAQsgA0UEQCAEIQFBACEGIAkhBSANIQQMBgsgBUEgaiEFIAZBBGshBiABLQADIAl0IApqIQogAUEEaiEBC0EAIQkgCEEEcQRAIAogBygCIEcNAgtBACEFCyAHQdD+ADYCBEEBIQQgCSEKDAMLIAdB0f4ANgIEIAxBjQw2AhggBygCBCEIDAELC0EAIQYgDSEECyAMIA82AhAgDCAQNgIMIAwgBjYCBCAMIAE2AgAgByAFNgKIASAHIAo2AoQBAkAgBygCLA0AIA8gFkYNAiAHKAIEIgFB0P4ASw0CIAFBzv4ASQ0ACwJ/IBYgD2shCiAHKAIMQQRxIQkCQAJAAkAgDCgCHCIDKAI4Ig1FBEBBASEIIAMgAygCACIBKAIgIAEoAiggAygCmEdBASADKAIodGpBARAoIg02AjggDUUNAQsgAygCLCIGRQRAIANCADcDMCADQQEgAygCKHQiBjYCLAsgBiAKTQRAAkAgCQRAAkAgBiAKTw0AIAogBmshBSAQIAprIQEgDCgCHCIGKAIUBEAgBkFAayABIAVBAEHYgAEoAgARCAAMAQsgBiAGKAIcIAEgBUHAgAEoAgARAAAiATYCHCAMIAE2AjALIAMoAiwiDUUNASAQIA1rIQUgAygCOCEBIAwoAhwiBigCFARAIAZBQGsgASAFIA1B3IABKAIAEQgADAILIAYgBigCHCABIAUgDUHEgAEoAgARBAAiATYCHCAMIAE2AjAMAQsgDSAQIAZrIAYQBxoLIANBADYCNCADIAMoAiw2AjBBAAwECyAKIAYgAygCNCIFayIBIAEgCksbIQsgECAKayEGIAUgDWohBQJAIAkEQAJAIAtFDQAgDCgCHCIBKAIUBEAgAUFAayAFIAYgC0HcgAEoAgARCAAMAQsgASABKAIcIAUgBiALQcSAASgCABEEACIBNgIcIAwgATYCMAsgCiALayIFRQ0BIBAgBWshBiADKAI4IQEgDCgCHCINKAIUBEAgDUFAayABIAYgBUHcgAEoAgARCAAMBQsgDSANKAIcIAEgBiAFQcSAASgCABEEACIBNgIcIAwgATYCMAwECyAFIAYgCxAHGiAKIAtrIgUNAgtBACEIIANBACADKAI0IAtqIgUgBSADKAIsIgFGGzYCNCABIAMoAjAiAU0NACADIAEgC2o2AjALIAgMAgsgAygCOCAQIAVrIAUQBxoLIAMgBTYCNCADIAMoAiw2AjBBAAtFBEAgDCgCECEPIAwoAgQhFyAHKAKIAQwDCyAHQdL+ADYCBAtBfCEXDAILIAYhFyAFCyEFIAwgICAXayIBIAwoAghqNgIIIAwgFiAPayIGIAwoAhRqNgIUIAcgBygCICAGajYCICAMIAcoAghBAEdBBnQgBWogBygCBCIFQb/+AEZBB3RqQYACIAVBwv4ARkEIdCAFQcf+AEYbajYCLCAEIARBeyAEGyABIAZyGyEXCyAUQRBqJAAgFwshASACIAIpAwAgADUCIH03AwACQAJAAkACQCABQQVqDgcBAgICAgMAAgtBAQ8LIAAoAhQNAEEDDwsgACgCACIABEAgACABNgIEIABBDTYCAAtBAiEBCyABCwkAIABBAToADAtEAAJAIAJC/////w9YBEAgACgCFEUNAQsgACgCACIABEAgAEEANgIEIABBEjYCAAtBAA8LIAAgATYCECAAIAI+AhRBAQu5AQEEfyAAQRBqIQECfyAALQAEBEAgARCEAQwBC0F+IQMCQCABRQ0AIAEoAiBFDQAgASgCJCIERQ0AIAEoAhwiAkUNACACKAIAIAFHDQAgAigCBEG0/gBrQR9LDQAgAigCOCIDBEAgBCABKAIoIAMQHiABKAIkIQQgASgCHCECCyAEIAEoAiggAhAeQQAhAyABQQA2AhwLIAMLIgEEQCAAKAIAIgAEQCAAIAE2AgQgAEENNgIACwsgAUUL0gwBBn8gAEIANwIQIABCADcCHCAAQRBqIQICfyAALQAEBEAgACgCCCEBQesMLQAAQTFGBH8Cf0F+IQMCQCACRQ0AIAJBADYCGCACKAIgIgRFBEAgAkEANgIoIAJBJzYCIEEnIQQLIAIoAiRFBEAgAkEoNgIkC0EGIAEgAUF/RhsiBUEASA0AIAVBCUoNAEF8IQMgBCACKAIoQQFB0C4QKCIBRQ0AIAIgATYCHCABIAI2AgAgAUEPNgI0IAFCgICAgKAFNwIcIAFBADYCFCABQYCAAjYCMCABQf//ATYCOCABIAIoAiAgAigCKEGAgAJBAhAoNgJIIAEgAigCICACKAIoIAEoAjBBAhAoIgM2AkwgA0EAIAEoAjBBAXQQGSACKAIgIAIoAihBgIAEQQIQKCEDIAFBgIACNgLoLSABQQA2AkAgASADNgJQIAEgAigCICACKAIoQYCAAkEEECgiAzYCBCABIAEoAugtIgRBAnQ2AgwCQAJAIAEoAkhFDQAgASgCTEUNACABKAJQRQ0AIAMNAQsgAUGaBTYCICACQejAACgCADYCGCACEIQBGkF8DAILIAFBADYCjAEgASAFNgKIASABQgA3AyggASADIARqNgLsLSABIARBA2xBA2s2AvQtQX4hAwJAIAJFDQAgAigCIEUNACACKAIkRQ0AIAIoAhwiAUUNACABKAIAIAJHDQACQAJAIAEoAiAiBEE5aw45AQICAgICAgICAgICAQICAgECAgICAgICAgICAgICAgICAgECAgICAgICAgICAgECAgICAgICAgIBAAsgBEGaBUYNACAEQSpHDQELIAJBAjYCLCACQQA2AgggAkIANwIUIAFBADYCECABIAEoAgQ2AgggASgCFCIDQX9MBEAgAUEAIANrIgM2AhQLIAFBOUEqIANBAkYbNgIgIAIgA0ECRgR/IAFBoAFqQeSAASgCABEBAAVBAQs2AjAgAUF+NgIkIAFBADYCoC4gAUIANwOYLiABQYgXakGg0wA2AgAgASABQcwVajYCgBcgAUH8FmpBjNMANgIAIAEgAUHYE2o2AvQWIAFB8BZqQfjSADYCACABIAFB5AFqNgLoFiABEIgBQQAhAwsgAw0AIAIoAhwiAiACKAIwQQF0NgJEQQAhAyACKAJQQQBBgIAIEBkgAiACKAKIASIEQQxsIgFBtNgAai8BADYClAEgAiABQbDYAGovAQA2ApABIAIgAUGy2ABqLwEANgJ4IAIgAUG22ABqLwEANgJ0QfiAASgCACEFQeyAASgCACEGQYCBASgCACEBIAJCADcCbCACQgA3AmQgAkEANgI8IAJBADYChC4gAkIANwJUIAJBKSABIARBCUYiARs2AnwgAkEqIAYgARs2AoABIAJBKyAFIAEbNgKEAQsgAwsFQXoLDAELAn9BekHrDC0AAEExRw0AGkF+IAJFDQAaIAJBADYCGCACKAIgIgNFBEAgAkEANgIoIAJBJzYCIEEnIQMLIAIoAiRFBEAgAkEoNgIkC0F8IAMgAigCKEEBQaDHABAoIgRFDQAaIAIgBDYCHCAEQQA2AjggBCACNgIAIARBtP4ANgIEIARBzIABKAIAEQkANgKYR0F+IQMCQCACRQ0AIAIoAiBFDQAgAigCJCIFRQ0AIAIoAhwiAUUNACABKAIAIAJHDQAgASgCBEG0/gBrQR9LDQACQAJAIAEoAjgiBgRAIAEoAihBD0cNAQsgAUEPNgIoIAFBADYCDAwBCyAFIAIoAiggBhAeIAFBADYCOCACKAIgIQUgAUEPNgIoIAFBADYCDCAFRQ0BCyACKAIkRQ0AIAIoAhwiAUUNACABKAIAIAJHDQAgASgCBEG0/gBrQR9LDQBBACEDIAFBADYCNCABQgA3AiwgAUEANgIgIAJBADYCCCACQgA3AhQgASgCDCIFBEAgAiAFQQFxNgIwCyABQrT+ADcCBCABQgA3AoQBIAFBADYCJCABQoCAgoAQNwMYIAFCgICAgHA3AxAgAUKBgICAcDcCjEcgASABQfwKaiIFNgK4ASABIAU2ApwBIAEgBTYCmAELQQAgA0UNABogAigCJCACKAIoIAQQHiACQQA2AhwgAwsLIgIEQCAAKAIAIgAEQCAAIAI2AgQgAEENNgIACwsgAkULKQEBfyAALQAERQRAQQAPC0ECIQEgACgCCCIAQQNOBH8gAEEHSgVBAgsLBgAgABAGC2MAQcgAEAkiAEUEQEGEhAEoAgAhASACBEAgAiABNgIEIAJBATYCAAsgAA8LIABBADoADCAAQQE6AAQgACACNgIAIABBADYCOCAAQgA3AzAgACABQQkgAUEBa0EJSRs2AgggAAukCgIIfwF+QfCAAUH0gAEgACgCdEGBCEkbIQYCQANAAkACfwJAIAAoAjxBhQJLDQAgABAvAkAgACgCPCICQYUCSw0AIAENAEEADwsgAkUNAiACQQRPDQBBAAwBCyAAIAAoAmggACgChAERAgALIQMgACAAKAJsOwFgQQIhAgJAIAA1AmggA619IgpCAVMNACAKIAAoAjBBhgJrrVUNACAAKAJwIAAoAnhPDQAgA0UNACAAIAMgBigCABECACICQQVLDQBBAiACIAAoAowBQQFGGyECCwJAIAAoAnAiA0EDSQ0AIAIgA0sNACAAIAAoAvAtIgJBAWo2AvAtIAAoAjwhBCACIAAoAuwtaiAAKAJoIgcgAC8BYEF/c2oiAjoAACAAIAAoAvAtIgVBAWo2AvAtIAUgACgC7C1qIAJBCHY6AAAgACAAKALwLSIFQQFqNgLwLSAFIAAoAuwtaiADQQNrOgAAIAAgACgCgC5BAWo2AoAuIANB/c4Aai0AAEECdCAAakHoCWoiAyADLwEAQQFqOwEAIAAgAkEBayICIAJBB3ZBgAJqIAJBgAJJG0GAywBqLQAAQQJ0akHYE2oiAiACLwEAQQFqOwEAIAAgACgCcCIFQQFrIgM2AnAgACAAKAI8IANrNgI8IAAoAvQtIQggACgC8C0hCSAEIAdqQQNrIgQgACgCaCICSwRAIAAgAkEBaiAEIAJrIgIgBUECayIEIAIgBEkbIAAoAoABEQUAIAAoAmghAgsgAEEANgJkIABBADYCcCAAIAIgA2oiBDYCaCAIIAlHDQJBACECIAAgACgCWCIDQQBOBH8gACgCSCADagVBAAsgBCADa0EAEA8gACAAKAJoNgJYIAAoAgAQCiAAKAIAKAIQDQIMAwsgACgCZARAIAAoAmggACgCSGpBAWstAAAhAyAAIAAoAvAtIgRBAWo2AvAtIAQgACgC7C1qQQA6AAAgACAAKALwLSIEQQFqNgLwLSAEIAAoAuwtakEAOgAAIAAgACgC8C0iBEEBajYC8C0gBCAAKALsLWogAzoAACAAIANBAnRqIgMgAy8B5AFBAWo7AeQBIAAoAvAtIAAoAvQtRgRAIAAgACgCWCIDQQBOBH8gACgCSCADagVBAAsgACgCaCADa0EAEA8gACAAKAJoNgJYIAAoAgAQCgsgACACNgJwIAAgACgCaEEBajYCaCAAIAAoAjxBAWs2AjwgACgCACgCEA0CQQAPBSAAQQE2AmQgACACNgJwIAAgACgCaEEBajYCaCAAIAAoAjxBAWs2AjwMAgsACwsgACgCZARAIAAoAmggACgCSGpBAWstAAAhAiAAIAAoAvAtIgNBAWo2AvAtIAMgACgC7C1qQQA6AAAgACAAKALwLSIDQQFqNgLwLSADIAAoAuwtakEAOgAAIAAgACgC8C0iA0EBajYC8C0gAyAAKALsLWogAjoAACAAIAJBAnRqIgIgAi8B5AFBAWo7AeQBIAAoAvAtIAAoAvQtRhogAEEANgJkCyAAIAAoAmgiA0ECIANBAkkbNgKELiABQQRGBEAgACAAKAJYIgFBAE4EfyAAKAJIIAFqBUEACyADIAFrQQEQDyAAIAAoAmg2AlggACgCABAKQQNBAiAAKAIAKAIQGw8LIAAoAvAtBEBBACECIAAgACgCWCIBQQBOBH8gACgCSCABagVBAAsgAyABa0EAEA8gACAAKAJoNgJYIAAoAgAQCiAAKAIAKAIQRQ0BC0EBIQILIAIL2BACEH8BfiAAKAKIAUEFSCEOA0ACQAJ/AkACQAJAAn8CQAJAIAAoAjxBhQJNBEAgABAvIAAoAjwiA0GFAksNASABDQFBAA8LIA4NASAIIQMgBSEHIAohDSAGQf//A3FFDQEMAwsgA0UNA0EAIANBBEkNARoLIAAgACgCaEH4gAEoAgARAgALIQZBASECQQAhDSAAKAJoIgOtIAatfSISQgFTDQIgEiAAKAIwQYYCa61VDQIgBkUNAiAAIAZB8IABKAIAEQIAIgZBASAGQfz/A3EbQQEgACgCbCINQf//A3EgA0H//wNxSRshBiADIQcLAkAgACgCPCIEIAZB//8DcSICQQRqTQ0AIAZB//8DcUEDTQRAQQEgBkEBa0H//wNxIglFDQQaIANB//8DcSIEIAdBAWpB//8DcSIDSw0BIAAgAyAJIAQgA2tBAWogAyAJaiAESxtB7IABKAIAEQUADAELAkAgACgCeEEEdCACSQ0AIARBBEkNACAGQQFrQf//A3EiDCAHQQFqQf//A3EiBGohCSAEIANB//8DcSIDTwRAQeyAASgCACELIAMgCUkEQCAAIAQgDCALEQUADAMLIAAgBCADIARrQQFqIAsRBQAMAgsgAyAJTw0BIAAgAyAJIANrQeyAASgCABEFAAwBCyAGIAdqQf//A3EiA0UNACAAIANBAWtB+IABKAIAEQIAGgsgBgwCCyAAIAAoAmgiBUECIAVBAkkbNgKELiABQQRGBEBBACEDIAAgACgCWCIBQQBOBH8gACgCSCABagVBAAsgBSABa0EBEA8gACAAKAJoNgJYIAAoAgAQCkEDQQIgACgCACgCEBsPCyAAKALwLQRAQQAhAkEAIQMgACAAKAJYIgFBAE4EfyAAKAJIIAFqBUEACyAFIAFrQQAQDyAAIAAoAmg2AlggACgCABAKIAAoAgAoAhBFDQMLQQEhAgwCCyADIQdBAQshBEEAIQYCQCAODQAgACgCPEGHAkkNACACIAdB//8DcSIQaiIDIAAoAkRBhgJrTw0AIAAgAzYCaEEAIQogACADQfiAASgCABECACEFAn8CQCAAKAJoIgitIAWtfSISQgFTDQAgEiAAKAIwQYYCa61VDQAgBUUNACAAIAVB8IABKAIAEQIAIQYgAC8BbCIKIAhB//8DcSIFTw0AIAZB//8DcSIDQQRJDQAgCCAEQf//A3FBAkkNARogCCACIApBAWpLDQEaIAggAiAFQQFqSw0BGiAIIAAoAkgiCSACa0EBaiICIApqLQAAIAIgBWotAABHDQEaIAggCUEBayICIApqIgwtAAAgAiAFaiIPLQAARw0BGiAIIAUgCCAAKAIwQYYCayICa0H//wNxQQAgAiAFSRsiEU0NARogCCADQf8BSw0BGiAGIQUgCCECIAQhAyAIIAoiCUECSQ0BGgNAAkAgA0EBayEDIAVBAWohCyAJQQFrIQkgAkEBayECIAxBAWsiDC0AACAPQQFrIg8tAABHDQAgA0H//wNxRQ0AIBEgAkH//wNxTw0AIAVB//8DcUH+AUsNACALIQUgCUH//wNxQQFLDQELCyAIIANB//8DcUEBSw0BGiAIIAtB//8DcUECRg0BGiAIQQFqIQggAyEEIAshBiAJIQogAgwBC0EBIQYgCAshBSAAIBA2AmgLAn8gBEH//wNxIgNBA00EQCAEQf//A3EiA0UNAyAAKAJIIAdB//8DcWotAAAhBCAAIAAoAvAtIgJBAWo2AvAtIAIgACgC7C1qQQA6AAAgACAAKALwLSICQQFqNgLwLSACIAAoAuwtakEAOgAAIAAgACgC8C0iAkEBajYC8C0gAiAAKALsLWogBDoAACAAIARBAnRqIgRB5AFqIAQvAeQBQQFqOwEAIAAgACgCPEEBazYCPCAAKALwLSICIAAoAvQtRiIEIANBAUYNARogACgCSCAHQQFqQf//A3FqLQAAIQkgACACQQFqNgLwLSAAKALsLSACakEAOgAAIAAgACgC8C0iAkEBajYC8C0gAiAAKALsLWpBADoAACAAIAAoAvAtIgJBAWo2AvAtIAIgACgC7C1qIAk6AAAgACAJQQJ0aiICQeQBaiACLwHkAUEBajsBACAAIAAoAjxBAWs2AjwgBCAAKALwLSICIAAoAvQtRmoiBCADQQJGDQEaIAAoAkggB0ECakH//wNxai0AACEHIAAgAkEBajYC8C0gACgC7C0gAmpBADoAACAAIAAoAvAtIgJBAWo2AvAtIAIgACgC7C1qQQA6AAAgACAAKALwLSICQQFqNgLwLSACIAAoAuwtaiAHOgAAIAAgB0ECdGoiB0HkAWogBy8B5AFBAWo7AQAgACAAKAI8QQFrNgI8IAQgACgC8C0gACgC9C1GagwBCyAAIAAoAvAtIgJBAWo2AvAtIAIgACgC7C1qIAdB//8DcSANQf//A3FrIgc6AAAgACAAKALwLSICQQFqNgLwLSACIAAoAuwtaiAHQQh2OgAAIAAgACgC8C0iAkEBajYC8C0gAiAAKALsLWogBEEDazoAACAAIAAoAoAuQQFqNgKALiADQf3OAGotAABBAnQgAGpB6AlqIgQgBC8BAEEBajsBACAAIAdBAWsiBCAEQQd2QYACaiAEQYACSRtBgMsAai0AAEECdGpB2BNqIgQgBC8BAEEBajsBACAAIAAoAjwgA2s2AjwgACgC8C0gACgC9C1GCyEEIAAgACgCaCADaiIHNgJoIARFDQFBACECQQAhBCAAIAAoAlgiA0EATgR/IAAoAkggA2oFQQALIAcgA2tBABAPIAAgACgCaDYCWCAAKAIAEAogACgCACgCEA0BCwsgAgu0BwIEfwF+AkADQAJAAkACQAJAIAAoAjxBhQJNBEAgABAvAkAgACgCPCICQYUCSw0AIAENAEEADwsgAkUNBCACQQRJDQELIAAgACgCaEH4gAEoAgARAgAhAiAANQJoIAKtfSIGQgFTDQAgBiAAKAIwQYYCa61VDQAgAkUNACAAIAJB8IABKAIAEQIAIgJBBEkNACAAIAAoAvAtIgNBAWo2AvAtIAMgACgC7C1qIAAoAmggACgCbGsiAzoAACAAIAAoAvAtIgRBAWo2AvAtIAQgACgC7C1qIANBCHY6AAAgACAAKALwLSIEQQFqNgLwLSAEIAAoAuwtaiACQQNrOgAAIAAgACgCgC5BAWo2AoAuIAJB/c4Aai0AAEECdCAAakHoCWoiBCAELwEAQQFqOwEAIAAgA0EBayIDIANBB3ZBgAJqIANBgAJJG0GAywBqLQAAQQJ0akHYE2oiAyADLwEAQQFqOwEAIAAgACgCPCACayIFNgI8IAAoAvQtIQMgACgC8C0hBCAAKAJ4IAJPQQAgBUEDSxsNASAAIAAoAmggAmoiAjYCaCAAIAJBAWtB+IABKAIAEQIAGiADIARHDQQMAgsgACgCSCAAKAJoai0AACECIAAgACgC8C0iA0EBajYC8C0gAyAAKALsLWpBADoAACAAIAAoAvAtIgNBAWo2AvAtIAMgACgC7C1qQQA6AAAgACAAKALwLSIDQQFqNgLwLSADIAAoAuwtaiACOgAAIAAgAkECdGoiAkHkAWogAi8B5AFBAWo7AQAgACAAKAI8QQFrNgI8IAAgACgCaEEBajYCaCAAKALwLSAAKAL0LUcNAwwBCyAAIAAoAmhBAWoiBTYCaCAAIAUgAkEBayICQeyAASgCABEFACAAIAAoAmggAmo2AmggAyAERw0CC0EAIQNBACECIAAgACgCWCIEQQBOBH8gACgCSCAEagVBAAsgACgCaCAEa0EAEA8gACAAKAJoNgJYIAAoAgAQCiAAKAIAKAIQDQEMAgsLIAAgACgCaCIEQQIgBEECSRs2AoQuIAFBBEYEQEEAIQIgACAAKAJYIgFBAE4EfyAAKAJIIAFqBUEACyAEIAFrQQEQDyAAIAAoAmg2AlggACgCABAKQQNBAiAAKAIAKAIQGw8LIAAoAvAtBEBBACEDQQAhAiAAIAAoAlgiAUEATgR/IAAoAkggAWoFQQALIAQgAWtBABAPIAAgACgCaDYCWCAAKAIAEAogACgCACgCEEUNAQtBASEDCyADC80JAgl/An4gAUEERiEGIAAoAiwhAgJAAkACQCABQQRGBEAgAkECRg0CIAIEQCAAQQAQUCAAQQA2AiwgACAAKAJoNgJYIAAoAgAQCiAAKAIAKAIQRQ0ECyAAIAYQTyAAQQI2AiwMAQsgAg0BIAAoAjxFDQEgACAGEE8gAEEBNgIsCyAAIAAoAmg2AlgLQQJBASABQQRGGyEKA0ACQCAAKAIMIAAoAhBBCGpLDQAgACgCABAKIAAoAgAiAigCEA0AQQAhAyABQQRHDQIgAigCBA0CIAAoAqAuDQIgACgCLEVBAXQPCwJAAkAgACgCPEGFAk0EQCAAEC8CQCAAKAI8IgNBhQJLDQAgAQ0AQQAPCyADRQ0CIAAoAiwEfyADBSAAIAYQTyAAIAo2AiwgACAAKAJoNgJYIAAoAjwLQQRJDQELIAAgACgCaEH4gAEoAgARAgAhBCAAKAJoIgKtIAStfSILQgFTDQAgCyAAKAIwQYYCa61VDQAgAiAAKAJIIgJqIgMvAAAgAiAEaiICLwAARw0AIANBAmogAkECakHQgAEoAgARAgBBAmoiA0EESQ0AIAAoAjwiAiADIAIgA0kbIgJBggIgAkGCAkkbIgdB/c4Aai0AACICQQJ0IgRBhMkAajMBACEMIARBhskAai8BACEDIAJBCGtBE00EQCAHQQNrIARBgNEAaigCAGutIAOthiAMhCEMIARBsNYAaigCACADaiEDCyAAKAKgLiEFIAMgC6dBAWsiCCAIQQd2QYACaiAIQYACSRtBgMsAai0AACICQQJ0IglBgsoAai8BAGohBCAJQYDKAGozAQAgA62GIAyEIQsgACkDmC4hDAJAIAUgAkEESQR/IAQFIAggCUGA0gBqKAIAa60gBK2GIAuEIQsgCUGw1wBqKAIAIARqCyICaiIDQT9NBEAgCyAFrYYgDIQhCwwBCyAFQcAARgRAIAAoAgQgACgCEGogDDcAACAAIAAoAhBBCGo2AhAgAiEDDAELIAAoAgQgACgCEGogCyAFrYYgDIQ3AAAgACAAKAIQQQhqNgIQIANBQGohAyALQcAAIAVrrYghCwsgACALNwOYLiAAIAM2AqAuIAAgACgCPCAHazYCPCAAIAAoAmggB2o2AmgMAgsgACgCSCAAKAJoai0AAEECdCICQYDBAGozAQAhCyAAKQOYLiEMAkAgACgCoC4iBCACQYLBAGovAQAiAmoiA0E/TQRAIAsgBK2GIAyEIQsMAQsgBEHAAEYEQCAAKAIEIAAoAhBqIAw3AAAgACAAKAIQQQhqNgIQIAIhAwwBCyAAKAIEIAAoAhBqIAsgBK2GIAyENwAAIAAgACgCEEEIajYCECADQUBqIQMgC0HAACAEa62IIQsLIAAgCzcDmC4gACADNgKgLiAAIAAoAmhBAWo2AmggACAAKAI8QQFrNgI8DAELCyAAIAAoAmgiAkECIAJBAkkbNgKELiAAKAIsIQIgAUEERgRAAkAgAkUNACAAQQEQUCAAQQA2AiwgACAAKAJoNgJYIAAoAgAQCiAAKAIAKAIQDQBBAg8LQQMPCyACBEBBACEDIABBABBQIABBADYCLCAAIAAoAmg2AlggACgCABAKIAAoAgAoAhBFDQELQQEhAwsgAwucAQEFfyACQQFOBEAgAiAAKAJIIAFqIgNqQQJqIQQgA0ECaiECIAAoAlQhAyAAKAJQIQUDQCAAIAItAAAgA0EFdEHg/wFxcyIDNgJUIAUgA0EBdGoiBi8BACIHIAFB//8DcUcEQCAAKAJMIAEgACgCOHFB//8DcUEBdGogBzsBACAGIAE7AQALIAFBAWohASACQQFqIgIgBEkNAAsLC1sBAn8gACAAKAJIIAFqLQACIAAoAlRBBXRB4P8BcXMiAjYCVCABIAAoAlAgAkEBdGoiAy8BACICRwRAIAAoAkwgACgCOCABcUEBdGogAjsBACADIAE7AQALIAILEwAgAUEFdEHg/wFxIAJB/wFxcwsGACABEAYLLwAjAEEQayIAJAAgAEEMaiABIAJsEIwBIQEgACgCDCECIABBEGokAEEAIAIgARsLjAoCAX4CfyMAQfAAayIGJAACQAJAAkACQAJAAkACQAJAIAQODwABBwIEBQYGBgYGBgYGAwYLQn8hBQJAIAAgBkHkAGpCDBARIgNCf1cEQCABBEAgASAAKAIMNgIAIAEgACgCEDYCBAsMAQsCQCADQgxSBEAgAQRAIAFBADYCBCABQRE2AgALDAELIAEoAhQhBEEAIQJCASEFA0AgBkHkAGogAmoiAiACLQAAIARB/f8DcSICQQJyIAJBA3NsQQh2cyICOgAAIAYgAjoAKCABAn8gASgCDEF/cyECQQAgBkEoaiIERQ0AGiACIARBAUHUgAEoAgARAAALQX9zIgI2AgwgASABKAIQIAJB/wFxakGFiKLAAGxBAWoiAjYCECAGIAJBGHY6ACggAQJ/IAEoAhRBf3MhAkEAIAZBKGoiBEUNABogAiAEQQFB1IABKAIAEQAAC0F/cyIENgIUIAVCDFIEQCAFpyECIAVCAXwhBQwBCwtCACEFIAAgBkEoahAhQQBIDQEgBigCUCEAIwBBEGsiAiQAIAIgADYCDCAGAn8gAkEMahCNASIARQRAIAZBITsBJEEADAELAn8gACgCFCIEQdAATgRAIARBCXQMAQsgAEHQADYCFEGAwAILIQQgBiAAKAIMIAQgACgCEEEFdGpqQaDAAWo7ASQgACgCBEEFdCAAKAIIQQt0aiAAKAIAQQF2ags7ASYgAkEQaiQAIAYtAG8iACAGLQBXRg0BIAYtACcgAEYNASABBEAgAUEANgIEIAFBGzYCAAsLQn8hBQsgBkHwAGokACAFDwtCfyEFIAAgAiADEBEiA0J/VwRAIAEEQCABIAAoAgw2AgAgASAAKAIQNgIECwwGCyMAQRBrIgAkAAJAIANQDQAgASgCFCEEIAJFBEBCASEFA0AgACACIAdqLQAAIARB/f8DcSIEQQJyIARBA3NsQQh2czoADyABAn8gASgCDEF/cyEEQQAgAEEPaiIHRQ0AGiAEIAdBAUHUgAEoAgARAAALQX9zIgQ2AgwgASABKAIQIARB/wFxakGFiKLAAGxBAWoiBDYCECAAIARBGHY6AA8gAQJ/IAEoAhRBf3MhBEEAIABBD2oiB0UNABogBCAHQQFB1IABKAIAEQAAC0F/cyIENgIUIAMgBVENAiAFpyEHIAVCAXwhBQwACwALQgEhBQNAIAAgAiAHai0AACAEQf3/A3EiBEECciAEQQNzbEEIdnMiBDoADyACIAdqIAQ6AAAgAQJ/IAEoAgxBf3MhBEEAIABBD2oiB0UNABogBCAHQQFB1IABKAIAEQAAC0F/cyIENgIMIAEgASgCECAEQf8BcWpBhYiiwABsQQFqIgQ2AhAgACAEQRh2OgAPIAECfyABKAIUQX9zIQRBACAAQQ9qIgdFDQAaIAQgB0EBQdSAASgCABEAAAtBf3MiBDYCFCADIAVRDQEgBachByAFQgF8IQUMAAsACyAAQRBqJAAgAyEFDAULIAJBADsBMiACIAIpAwAiA0KAAYQ3AwAgA0IIg1ANBCACIAIpAyBCDH03AyAMBAsgBkKFgICAcDcDECAGQoOAgIDAADcDCCAGQoGAgIAgNwMAQQAgBhAkIQUMAwsgA0IIWgR+IAIgASgCADYCACACIAEoAgQ2AgRCCAVCfwshBQwCCyABEAYMAQsgAQRAIAFBADYCBCABQRI2AgALQn8hBQsgBkHwAGokACAFC60DAgJ/An4jAEEQayIGJAACQAJAAkAgBEUNACABRQ0AIAJBAUYNAQtBACEDIABBCGoiAARAIABBADYCBCAAQRI2AgALDAELIANBAXEEQEEAIQMgAEEIaiIABEAgAEEANgIEIABBGDYCAAsMAQtBGBAJIgVFBEBBACEDIABBCGoiAARAIABBADYCBCAAQQ42AgALDAELIAVBADYCCCAFQgA3AgAgBUGQ8dmiAzYCFCAFQvis0ZGR8dmiIzcCDAJAIAQQIiICRQ0AIAKtIQhBACEDQYfTru5+IQJCASEHA0AgBiADIARqLQAAOgAPIAUgBkEPaiIDBH8gAiADQQFB1IABKAIAEQAABUEAC0F/cyICNgIMIAUgBSgCECACQf8BcWpBhYiiwABsQQFqIgI2AhAgBiACQRh2OgAPIAUCfyAFKAIUQX9zIQJBACAGQQ9qIgNFDQAaIAIgA0EBQdSAASgCABEAAAtBf3M2AhQgByAIUQ0BIAUoAgxBf3MhAiAHpyEDIAdCAXwhBwwACwALIAAgAUElIAUQQiIDDQAgBRAGQQAhAwsgBkEQaiQAIAMLnRoCBn4FfyMAQdAAayILJAACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQCADDhQFBhULAwQJDgACCBAKDw0HEQERDBELAkBByAAQCSIBBEAgAUIANwMAIAFCADcDMCABQQA2AiggAUIANwMgIAFCADcDGCABQgA3AxAgAUIANwMIIAFCADcDOCABQQgQCSIDNgIEIAMNASABEAYgAARAIABBADYCBCAAQQ42AgALCyAAQQA2AhQMFAsgA0IANwMAIAAgATYCFCABQUBrQgA3AwAgAUIANwM4DBQLAkACQCACUARAQcgAEAkiA0UNFCADQgA3AwAgA0IANwMwIANBADYCKCADQgA3AyAgA0IANwMYIANCADcDECADQgA3AwggA0IANwM4IANBCBAJIgE2AgQgAQ0BIAMQBiAABEAgAEEANgIEIABBDjYCAAsMFAsgAiAAKAIQIgEpAzBWBEAgAARAIABBADYCBCAAQRI2AgALDBQLIAEoAigEQCAABEAgAEEANgIEIABBHTYCAAsMFAsgASgCBCEDAkAgASkDCCIGQgF9IgdQDQADQAJAIAIgAyAHIAR9QgGIIAR8IgWnQQN0aikDAFQEQCAFQgF9IQcMAQsgBSAGUQRAIAYhBQwDCyADIAVCAXwiBKdBA3RqKQMAIAJWDQILIAQhBSAEIAdUDQALCwJAIAIgAyAFpyIKQQN0aikDAH0iBFBFBEAgASgCACIDIApBBHRqKQMIIQcMAQsgASgCACIDIAVCAX0iBadBBHRqKQMIIgchBAsgAiAHIAR9VARAIAAEQCAAQQA2AgQgAEEcNgIACwwUCyADIAVCAXwiBUEAIAAQiQEiA0UNEyADKAIAIAMoAggiCkEEdGpBCGsgBDcDACADKAIEIApBA3RqIAI3AwAgAyACNwMwIAMgASkDGCIGIAMpAwgiBEIBfSIHIAYgB1QbNwMYIAEgAzYCKCADIAE2AiggASAENwMgIAMgBTcDIAwBCyABQgA3AwALIAAgAzYCFCADIAQ3A0AgAyACNwM4QgAhBAwTCyAAKAIQIgEEQAJAIAEoAigiA0UEQCABKQMYIQIMAQsgA0EANgIoIAEoAihCADcDICABIAEpAxgiAiABKQMgIgUgAiAFVhsiAjcDGAsgASkDCCACVgRAA0AgASgCACACp0EEdGooAgAQBiACQgF8IgIgASkDCFQNAAsLIAEoAgAQBiABKAIEEAYgARAGCyAAKAIUIQEgAEEANgIUIAAgATYCEAwSCyACQghaBH4gASAAKAIANgIAIAEgACgCBDYCBEIIBUJ/CyEEDBELIAAoAhAiAQRAAkAgASgCKCIDRQRAIAEpAxghAgwBCyADQQA2AiggASgCKEIANwMgIAEgASkDGCICIAEpAyAiBSACIAVWGyICNwMYCyABKQMIIAJWBEADQCABKAIAIAKnQQR0aigCABAGIAJCAXwiAiABKQMIVA0ACwsgASgCABAGIAEoAgQQBiABEAYLIAAoAhQiAQRAAkAgASgCKCIDRQRAIAEpAxghAgwBCyADQQA2AiggASgCKEIANwMgIAEgASkDGCICIAEpAyAiBSACIAVWGyICNwMYCyABKQMIIAJWBEADQCABKAIAIAKnQQR0aigCABAGIAJCAXwiAiABKQMIVA0ACwsgASgCABAGIAEoAgQQBiABEAYLIAAQBgwQCyAAKAIQIgBCADcDOCAAQUBrQgA3AwAMDwsgAkJ/VwRAIAAEQCAAQQA2AgQgAEESNgIACwwOCyACIAAoAhAiAykDMCADKQM4IgZ9IgUgAiAFVBsiBVANDiABIAMpA0AiB6ciAEEEdCIBIAMoAgBqIgooAgAgBiADKAIEIABBA3RqKQMAfSICp2ogBSAKKQMIIAJ9IgYgBSAGVBsiBKcQByEKIAcgBCADKAIAIgAgAWopAwggAn1RrXwhAiAFIAZWBEADQCAKIASnaiAAIAKnQQR0IgFqIgAoAgAgBSAEfSIGIAApAwgiByAGIAdUGyIGpxAHGiACIAYgAygCACIAIAFqKQMIUa18IQIgBSAEIAZ8IgRWDQALCyADIAI3A0AgAyADKQM4IAR8NwM4DA4LQn8hBEHIABAJIgNFDQ0gA0IANwMAIANCADcDMCADQQA2AiggA0IANwMgIANCADcDGCADQgA3AxAgA0IANwMIIANCADcDOCADQQgQCSIBNgIEIAFFBEAgAxAGIAAEQCAAQQA2AgQgAEEONgIACwwOCyABQgA3AwAgACgCECIBBEACQCABKAIoIgpFBEAgASkDGCEEDAELIApBADYCKCABKAIoQgA3AyAgASABKQMYIgIgASkDICIFIAIgBVYbIgQ3AxgLIAEpAwggBFYEQANAIAEoAgAgBKdBBHRqKAIAEAYgBEIBfCIEIAEpAwhUDQALCyABKAIAEAYgASgCBBAGIAEQBgsgACADNgIQQgAhBAwNCyAAKAIUIgEEQAJAIAEoAigiA0UEQCABKQMYIQIMAQsgA0EANgIoIAEoAihCADcDICABIAEpAxgiAiABKQMgIgUgAiAFVhsiAjcDGAsgASkDCCACVgRAA0AgASgCACACp0EEdGooAgAQBiACQgF8IgIgASkDCFQNAAsLIAEoAgAQBiABKAIEEAYgARAGCyAAQQA2AhQMDAsgACgCECIDKQM4IAMpAzAgASACIAAQRCIHQgBTDQogAyAHNwM4AkAgAykDCCIGQgF9IgJQDQAgAygCBCEAA0ACQCAHIAAgAiAEfUIBiCAEfCIFp0EDdGopAwBUBEAgBUIBfSECDAELIAUgBlEEQCAGIQUMAwsgACAFQgF8IgSnQQN0aikDACAHVg0CCyAEIQUgAiAEVg0ACwsgAyAFNwNAQgAhBAwLCyAAKAIUIgMpAzggAykDMCABIAIgABBEIgdCAFMNCSADIAc3AzgCQCADKQMIIgZCAX0iAlANACADKAIEIQADQAJAIAcgACACIAR9QgGIIAR8IgWnQQN0aikDAFQEQCAFQgF9IQIMAQsgBSAGUQRAIAYhBQwDCyAAIAVCAXwiBKdBA3RqKQMAIAdWDQILIAQhBSACIARWDQALCyADIAU3A0BCACEEDAoLIAJCN1gEQCAABEAgAEEANgIEIABBEjYCAAsMCQsgARAqIAEgACgCDDYCKCAAKAIQKQMwIQIgAUEANgIwIAEgAjcDICABIAI3AxggAULcATcDAEI4IQQMCQsgACABKAIANgIMDAgLIAtBQGtBfzYCACALQouAgICwAjcDOCALQoyAgIDQATcDMCALQo+AgICgATcDKCALQpGAgICQATcDICALQoeAgICAATcDGCALQoWAgIDgADcDECALQoOAgIDAADcDCCALQoGAgIAgNwMAQQAgCxAkIQQMBwsgACgCECkDOCIEQn9VDQYgAARAIABBPTYCBCAAQR42AgALDAULIAAoAhQpAzgiBEJ/VQ0FIAAEQCAAQT02AgQgAEEeNgIACwwEC0J/IQQgAkJ/VwRAIAAEQCAAQQA2AgQgAEESNgIACwwFCyACIAAoAhQiAykDOCACfCIFQv//A3wiBFYEQCAABEAgAEEANgIEIABBEjYCAAsMBAsCQCAFIAMoAgQiCiADKQMIIganQQN0aikDACIHWA0AAkAgBCAHfUIQiCAGfCIIIAMpAxAiCVgNAEIQIAkgCVAbIQUDQCAFIgRCAYYhBSAEIAhUDQALIAQgCVQNACADKAIAIASnIgpBBHQQNCIMRQ0DIAMgDDYCACADKAIEIApBA3RBCGoQNCIKRQ0DIAMgBDcDECADIAo2AgQgAykDCCEGCyAGIAhaDQAgAygCACEMA0AgDCAGp0EEdGoiDUGAgAQQCSIONgIAIA5FBEAgAARAIABBADYCBCAAQQ42AgALDAYLIA1CgIAENwMIIAMgBkIBfCIFNwMIIAogBadBA3RqIAdCgIAEfCIHNwMAIAMpAwgiBiAIVA0ACwsgAykDQCEFIAMpAzghBwJAIAJQBEBCACEEDAELIAWnIgBBBHQiDCADKAIAaiINKAIAIAcgCiAAQQN0aikDAH0iBqdqIAEgAiANKQMIIAZ9IgcgAiAHVBsiBKcQBxogBSAEIAMoAgAiACAMaikDCCAGfVGtfCEFIAIgB1YEQANAIAAgBadBBHQiCmoiACgCACABIASnaiACIAR9IgYgACkDCCIHIAYgB1QbIganEAcaIAUgBiADKAIAIgAgCmopAwhRrXwhBSAEIAZ8IgQgAlQNAAsLIAMpAzghBwsgAyAFNwNAIAMgBCAHfCICNwM4IAIgAykDMFgNBCADIAI3AzAMBAsgAARAIABBADYCBCAAQRw2AgALDAILIAAEQCAAQQA2AgQgAEEONgIACyAABEAgAEEANgIEIABBDjYCAAsMAQsgAEEANgIUC0J/IQQLIAtB0ABqJAAgBAtIAQF/IABCADcCBCAAIAE2AgACQCABQQBIDQBBsBMoAgAgAUwNACABQQJ0QcATaigCAEEBRw0AQYSEASgCACECCyAAIAI2AgQLDgAgAkGx893xeWxBEHYLvgEAIwBBEGsiACQAIABBADoACEGAgQFBAjYCAEH8gAFBAzYCAEH4gAFBBDYCAEH0gAFBBTYCAEHwgAFBBjYCAEHsgAFBBzYCAEHogAFBCDYCAEHkgAFBCTYCAEHggAFBCjYCAEHcgAFBCzYCAEHYgAFBDDYCAEHUgAFBDTYCAEHQgAFBDjYCAEHMgAFBDzYCAEHIgAFBEDYCAEHEgAFBETYCAEHAgAFBEjYCACAAQRBqJAAgAkGx893xeWxBEHYLuQEBAX8jAEEQayIBJAAgAUEAOgAIQYCBAUECNgIAQfyAAUEDNgIAQfiAAUEENgIAQfSAAUEFNgIAQfCAAUEGNgIAQeyAAUEHNgIAQeiAAUEINgIAQeSAAUEJNgIAQeCAAUEKNgIAQdyAAUELNgIAQdiAAUEMNgIAQdSAAUENNgIAQdCAAUEONgIAQcyAAUEPNgIAQciAAUEQNgIAQcSAAUERNgIAQcCAAUESNgIAIAAQjgEgAUEQaiQAC78BAQF/IwBBEGsiAiQAIAJBADoACEGAgQFBAjYCAEH8gAFBAzYCAEH4gAFBBDYCAEH0gAFBBTYCAEHwgAFBBjYCAEHsgAFBBzYCAEHogAFBCDYCAEHkgAFBCTYCAEHggAFBCjYCAEHcgAFBCzYCAEHYgAFBDDYCAEHUgAFBDTYCAEHQgAFBDjYCAEHMgAFBDzYCAEHIgAFBEDYCAEHEgAFBETYCAEHAgAFBEjYCACAAIAEQkAEhACACQRBqJAAgAAu+AQEBfyMAQRBrIgIkACACQQA6AAhBgIEBQQI2AgBB/IABQQM2AgBB+IABQQQ2AgBB9IABQQU2AgBB8IABQQY2AgBB7IABQQc2AgBB6IABQQg2AgBB5IABQQk2AgBB4IABQQo2AgBB3IABQQs2AgBB2IABQQw2AgBB1IABQQ02AgBB0IABQQ42AgBBzIABQQ82AgBByIABQRA2AgBBxIABQRE2AgBBwIABQRI2AgAgACABEFohACACQRBqJAAgAAu+AQEBfyMAQRBrIgIkACACQQA6AAhBgIEBQQI2AgBB/IABQQM2AgBB+IABQQQ2AgBB9IABQQU2AgBB8IABQQY2AgBB7IABQQc2AgBB6IABQQg2AgBB5IABQQk2AgBB4IABQQo2AgBB3IABQQs2AgBB2IABQQw2AgBB1IABQQ02AgBB0IABQQ42AgBBzIABQQ82AgBByIABQRA2AgBBxIABQRE2AgBBwIABQRI2AgAgACABEFshACACQRBqJAAgAAu9AQEBfyMAQRBrIgMkACADQQA6AAhBgIEBQQI2AgBB/IABQQM2AgBB+IABQQQ2AgBB9IABQQU2AgBB8IABQQY2AgBB7IABQQc2AgBB6IABQQg2AgBB5IABQQk2AgBB4IABQQo2AgBB3IABQQs2AgBB2IABQQw2AgBB1IABQQ02AgBB0IABQQ42AgBBzIABQQ82AgBByIABQRA2AgBBxIABQRE2AgBBwIABQRI2AgAgACABIAIQjwEgA0EQaiQAC4UBAgR/AX4jAEEQayIBJAACQCAAKQMwUARADAELA0ACQCAAIAVBACABQQ9qIAFBCGoQZiIEQX9GDQAgAS0AD0EDRw0AIAIgASgCCEGAgICAf3FBgICAgHpGaiECC0F/IQMgBEF/Rg0BIAIhAyAFQgF8IgUgACkDMFQNAAsLIAFBEGokACADCwuMdSUAQYAIC7ELaW5zdWZmaWNpZW50IG1lbW9yeQBuZWVkIGRpY3Rpb25hcnkALSsgICAwWDB4AFppcCBhcmNoaXZlIGluY29uc2lzdGVudABJbnZhbGlkIGFyZ3VtZW50AGludmFsaWQgbGl0ZXJhbC9sZW5ndGhzIHNldABpbnZhbGlkIGNvZGUgbGVuZ3RocyBzZXQAdW5rbm93biBoZWFkZXIgZmxhZ3Mgc2V0AGludmFsaWQgZGlzdGFuY2VzIHNldABpbnZhbGlkIGJpdCBsZW5ndGggcmVwZWF0AEZpbGUgYWxyZWFkeSBleGlzdHMAdG9vIG1hbnkgbGVuZ3RoIG9yIGRpc3RhbmNlIHN5bWJvbHMAaW52YWxpZCBzdG9yZWQgYmxvY2sgbGVuZ3RocwAlcyVzJXMAYnVmZmVyIGVycm9yAE5vIGVycm9yAHN0cmVhbSBlcnJvcgBUZWxsIGVycm9yAEludGVybmFsIGVycm9yAFNlZWsgZXJyb3IAV3JpdGUgZXJyb3IAZmlsZSBlcnJvcgBSZWFkIGVycm9yAFpsaWIgZXJyb3IAZGF0YSBlcnJvcgBDUkMgZXJyb3IAaW5jb21wYXRpYmxlIHZlcnNpb24AaW52YWxpZCBjb2RlIC0tIG1pc3NpbmcgZW5kLW9mLWJsb2NrAGluY29ycmVjdCBoZWFkZXIgY2hlY2sAaW5jb3JyZWN0IGxlbmd0aCBjaGVjawBpbmNvcnJlY3QgZGF0YSBjaGVjawBpbnZhbGlkIGRpc3RhbmNlIHRvbyBmYXIgYmFjawBoZWFkZXIgY3JjIG1pc21hdGNoADEuMi4xMy56bGliLW5nAGludmFsaWQgd2luZG93IHNpemUAUmVhZC1vbmx5IGFyY2hpdmUATm90IGEgemlwIGFyY2hpdmUAUmVzb3VyY2Ugc3RpbGwgaW4gdXNlAE1hbGxvYyBmYWlsdXJlAGludmFsaWQgYmxvY2sgdHlwZQBGYWlsdXJlIHRvIGNyZWF0ZSB0ZW1wb3JhcnkgZmlsZQBDYW4ndCBvcGVuIGZpbGUATm8gc3VjaCBmaWxlAFByZW1hdHVyZSBlbmQgb2YgZmlsZQBDYW4ndCByZW1vdmUgZmlsZQBpbnZhbGlkIGxpdGVyYWwvbGVuZ3RoIGNvZGUAaW52YWxpZCBkaXN0YW5jZSBjb2RlAHVua25vd24gY29tcHJlc3Npb24gbWV0aG9kAHN0cmVhbSBlbmQAQ29tcHJlc3NlZCBkYXRhIGludmFsaWQATXVsdGktZGlzayB6aXAgYXJjaGl2ZXMgbm90IHN1cHBvcnRlZABPcGVyYXRpb24gbm90IHN1cHBvcnRlZABFbmNyeXB0aW9uIG1ldGhvZCBub3Qgc3VwcG9ydGVkAENvbXByZXNzaW9uIG1ldGhvZCBub3Qgc3VwcG9ydGVkAEVudHJ5IGhhcyBiZWVuIGRlbGV0ZWQAQ29udGFpbmluZyB6aXAgYXJjaGl2ZSB3YXMgY2xvc2VkAENsb3NpbmcgemlwIGFyY2hpdmUgZmFpbGVkAFJlbmFtaW5nIHRlbXBvcmFyeSBmaWxlIGZhaWxlZABFbnRyeSBoYXMgYmVlbiBjaGFuZ2VkAE5vIHBhc3N3b3JkIHByb3ZpZGVkAFdyb25nIHBhc3N3b3JkIHByb3ZpZGVkAFVua25vd24gZXJyb3IgJWQAQUUAKG51bGwpADogAFBLBgcAUEsGBgBQSwUGAFBLAwQAUEsBAgAAAAA/BQAAwAcAAJMIAAB4CAAAbwUAAJEFAAB6BQAAsgUAAFYIAAAbBwAA1gQAAAsHAADqBgAAnAUAAMgGAACyCAAAHggAACgHAABHBAAAoAYAAGAFAAAuBAAAPgcAAD8IAAD+BwAAjgYAAMkIAADeCAAA5gcAALIGAABVBQAAqAcAACAAQcgTCxEBAAAAAQAAAAEAAAABAAAAAQBB7BMLCQEAAAABAAAAAgBBmBQLAQEAQbgUCwEBAEHSFAukLDomOyZlJmYmYyZgJiIg2CXLJdklQiZAJmomayY8JrolxCWVITwgtgCnAKwlqCGRIZMhkiGQIR8ilCGyJbwlIAAhACIAIwAkACUAJgAnACgAKQAqACsALAAtAC4ALwAwADEAMgAzADQANQA2ADcAOAA5ADoAOwA8AD0APgA/AEAAQQBCAEMARABFAEYARwBIAEkASgBLAEwATQBOAE8AUABRAFIAUwBUAFUAVgBXAFgAWQBaAFsAXABdAF4AXwBgAGEAYgBjAGQAZQBmAGcAaABpAGoAawBsAG0AbgBvAHAAcQByAHMAdAB1AHYAdwB4AHkAegB7AHwAfQB+AAIjxwD8AOkA4gDkAOAA5QDnAOoA6wDoAO8A7gDsAMQAxQDJAOYAxgD0APYA8gD7APkA/wDWANwAogCjAKUApyCSAeEA7QDzAPoA8QDRAKoAugC/ABAjrAC9ALwAoQCrALsAkSWSJZMlAiUkJWElYiVWJVUlYyVRJVclXSVcJVslECUUJTQlLCUcJQAlPCVeJV8lWiVUJWklZiVgJVAlbCVnJWglZCVlJVklWCVSJVMlayVqJRglDCWIJYQljCWQJYAlsQPfAJMDwAOjA8MDtQDEA6YDmAOpA7QDHiLGA7UDKSJhIrEAZSJkIiAjISP3AEgisAAZIrcAGiJ/ILIAoCWgAAAAAACWMAd3LGEO7rpRCZkZxG0Hj/RqcDWlY+mjlWSeMojbDqS43Hke6dXgiNnSlytMtgm9fLF+By2455Edv5BkELcd8iCwakhxufPeQb6EfdTaGuvk3W1RtdT0x4XTg1aYbBPAqGtkevli/ezJZYpPXAEU2WwGY2M9D/r1DQiNyCBuO14QaUzkQWDVcnFnotHkAzxH1ARL/YUN0mu1CqX6qLU1bJiyQtbJu9tA+bys42zYMnVc30XPDdbcWT3Rq6ww2SY6AN5RgFHXyBZh0L+19LQhI8SzVpmVus8Ppb24nrgCKAiIBV+y2QzGJOkLsYd8by8RTGhYqx1hwT0tZraQQdx2BnHbAbwg0pgqENXviYWxcR+1tgal5L+fM9S46KLJB3g0+QAPjqgJlhiYDuG7DWp/LT1tCJdsZJEBXGPm9FFra2JhbBzYMGWFTgBi8u2VBmx7pQEbwfQIglfED/XG2bBlUOm3Euq4vot8iLn83x3dYkkt2hXzfNOMZUzU+1hhsk3OUbU6dAC8o+Iwu9RBpd9K15XYPW3E0aT79NbTaulpQ/zZbjRGiGet0Lhg2nMtBETlHQMzX0wKqsl8Dd08cQVQqkECJxAQC76GIAzJJbVoV7OFbyAJ1Ga5n+Rhzg753l6YydkpIpjQsLSo18cXPbNZgQ20LjtcvbetbLrAIIO47bazv5oM4rYDmtKxdDlH1eqvd9KdFSbbBIMW3HMSC2PjhDtklD5qbQ2oWmp6C88O5J3/CZMnrgAKsZ4HfUSTD/DSowiHaPIBHv7CBmldV2L3y2dlgHE2bBnnBmtudhvU/uAr04laetoQzErdZ2/fufn5776OQ763F9WOsGDoo9bWfpPRocTC2DhS8t9P8We70WdXvKbdBrU/SzaySNorDdhMGwqv9koDNmB6BEHD72DfVd9nqO+ObjF5vmlGjLNhyxqDZryg0m8lNuJoUpV3DMwDRwu7uRYCIi8mBVW+O7rFKAu9spJatCsEarNcp//XwjHP0LWLntksHa7eW7DCZJsm8mPsnKNqdQqTbQKpBgmcPzYO64VnB3ITVwAFgkq/lRR6uOKuK7F7OBu2DJuO0pINvtXlt+/cfCHf2wvU0tOGQuLU8fiz3Whug9ofzRa+gVsmufbhd7Bvd0e3GOZaCIhwag//yjsGZlwLARH/nmWPaa5i+NP/a2FFz2wWeOIKoO7SDddUgwROwrMDOWEmZ6f3FmDQTUdpSdt3bj5KatGu3FrW2WYL30DwO9g3U668qcWeu95/z7JH6f+1MBzyvb2KwrrKMJOzU6ajtCQFNtC6kwbXzSlX3lS/Z9kjLnpms7hKYcQCG2hdlCtvKje+C7ShjgzDG98FWo3vAi0AAAAARjtnZYx2zsrKTamvWevtTh/QiivVnSOEk6ZE4bLW25307bz4PqAVV3ibcjLrPTbTrQZRtmdL+BkhcJ98JavG4GOQoYWp3Qgq7+ZvT3xAK646e0zL8DblZLYNggGXfR190UZ6GBsL07ddMLTSzpbwM4itl1ZC4D75BNtZnAtQ/BpNa5t/hyYy0MEdVbVSuxFUFIB2Md7N356Y9rj7uYYnh/+9QOI18OlNc8uOKOBtysmmVq2sbBsEAyogY2Yu+zr6aMBdn6KN9DDktpNVdxDXtDErsNH7Zhl+vV1+G5wt4WfaFoYCEFsvrVZgSMjFxgwpg/1rTEmwwuMPi6WGFqD4NVCbn1Ca1jb/3O1Rmk9LFXsJcHIewz3bsYUGvNSkdiOo4k1EzSgA7WJuO4oH/Z3O5rumqYNx6wAsN9BnSTMLPtV1MFmwv33wH/lGl3pq4NObLNu0/uaWHVGgrXo0gd3lSMfmgi0NqyuCS5BM59g2CAaeDW9jVEDGzBJ7oakd8AQvW8tjSpGGyuXXva2ARBvpYQIgjgTIbSerjlZAzq8m37LpHbjXI1AReGVrdh32zTL8sPZVmXq7/DY8gJtTOFvCz35gpaq0LQwF8hZrYGGwL4Eni0jk7cbhS6v9hi6KjRlSzLZ+Nwb715hAwLD902b0HJVdk3lfEDrWGStdsyxA8Wtqe5YOoDY/oeYNWMR1qxwlM5B7QPnd0u+/5rWKnpYq9titTZMS4OQ8VNuDWcd9x7iBRqDdSwsJcg0wbhcJ6zeLT9BQ7oWd+UHDpp4kUADaxRY7vaDcdhQPmk1zars97Bb9BotzN0si3HFwRbni1gFYpO1mPW6gz5Iom6j3JxANcWErahSrZsO77V2k3n774D84wIda8o0u9bS2SZCVxtbs0/2xiRmwGCZfi39DzC07oooWXMdAW/VoBmCSDQK7y5FEgKz0js0FW8j2Yj5bUCbfHWtButcm6BWRHY9wsG0QDPZWd2k8G97GeiC5o+mG/UKvvZonZfAziCPLVO064AlefNtuO7aWx5TwraDxYwvkECUwg3XvfSraqUZNv4g20sPODbWmBEAcCUJ7e2zR3T+Nl+ZY6F2r8UcbkJYiH0vPvllwqNuTPQF01QZmEUagIvAAm0WVytbsOozti1+tnRQj66ZzRiHr2uln0L2M9Hb5bbJNngh4ADenPjtQwjGw9UR3i5IhvcY7jvv9XOtoWxgKLmB/b+Qt1sCiFrGlg2Yu2cVdSbwPEOATSSuHdtqNw5ectqTyVvsNXRDAajgUGzOkUiBUwZht/W7eVpoLTfDe6gvLuY/BhhAgh713RabN6Dng9o9cKrsm82yAQZb/JgV3uR1iEnNQy701a6zYAAAAAFiA4tfxBrR0qYZWo+INaOm6jYo+EwvcnUuLPkqFHaEJ3Z1D3nQbFX0sm/eqZxDJ4D+QKzeWFn2UzpafQwo7QhNSu6DE+z32Z6O9FLDoNir6sLbILRkwno5BsHxZjybjGtemAc1+IFduJqC1uW0ri/M1q2kknC0/h8St3VAUdoQmTPZm8eVwMFK98NKF9nvsz677DhgHfVi7X/26bJFrJS/J68f4YG2RWzjtc4xzZk3GK+avEYJg+bLa4BtlHk3GNUbNJOLvS3JBt8uQlvxArtykwEwLDUYaqFXG+H+bUGc8w9CF62pW00gy1jGfeV0P1SHd7QKIW7uh0NtZdijsCE1wbOqa2eq8OYFqXu7K4WCkkmGCczvn1NBjZzYHrfGpRPVxS5Nc9x0wBHf/50/8wa0XfCN6vvp12eZ6lw4i10peeleoidPR/iqLURz9wNoit5hawGAx3JbDaVx0FKfK61f/SgmAVsxfIw5MvfRFx4O+HUdhabTBN8rsQdUdPJqMa2QabrzNnDgflRzayN6X5IKGFwZVL5FQ9ncRsiG5hy1i4QfPtUiBmRYQAXvBW4pFiwMKp1yqjPH/8gwTKDahznhuISyvx6d6DJ8nmNvUrKaRjCxERiWqEuV9KvAys7xvces8jaZCutsFGjo50lGxB5gJMeVPoLez7Pg3UTtQ2BGaCFjzTaHepe75Xkc5stV5c+pVm6RD080HG1Mv0NXFsJONRVJEJMME53xD5jA3yNh6b0g6rcbObA6eTo7ZWuNTiQJjsV6r5ef982UFKrjuO2Dgbtm3SeiPFBFobcPf/vKAh34QVy74RvR2eKQjPfOaaWVzeL7M9S4dlHXMykSulbwcLndrtaghyO0owx+mo/1V/iMfglelSSEPJav2wbM0tZkz1mIwtYDBaDViFiO+XFx7Pr6L0rjoKIo4Cv9OldevFhU1eL+TY9vnE4EMrJi/RvQYXZFdngsyBR7p5cuIdqaTCJRxOo7C0mIOIAUphR5PcQX8mNiDqjuAA0jseDQZ1yC0+wCJMq2j0bJPdJo5cT7CuZPpaz/FSjO/J539KbjepalaCQwvDKpUr+59HyTQN0ekMuDuImRDtqKGlHIPW8Qqj7kTgwnvsNuJDWeQAjMtyILR+mEEh1k5hGWO9xL6za+SGBoGFE65XpSsbhUfkiRNn3Dz5BkmULyZxIdsQp3xNMJ/Jp1EKYXFxMtSjk/1GNbPF89/SUFsJ8mju+lfPPix394vGFmIjEDZalsLUlQRU9K2xvpU4GWi1AKyZnnf4j75PTWXf2uWz/+JQYR0twvc9FXcdXIDfy3y4ajjZH7ru+ScPBJiyp9K4ihIAWkWAlnp9NXwb6J2qO9AoQAAAADhtlLvg2vUBWLdhuoG16gL52H65IW8fA5kCi7hDK5RF+0YA/iPxYUSbnPX/Qp5+Rzrz6vziRItGWikf/YYXKMu+erxwZs3dyt6gSXEHosLJf89Wcqd4N8gfFaNzxTy8jn1RKDWl5kmPHYvdNMSJVoy85MI3ZFOjjdw+NzYMLhGXdEOFLKz05JYUmXAtzZv7lbX2by5tQQ6U1SyaLw8FhdK3aBFpb99w09ey5GgOsG/Qdt37a65qmtEWBw5qyjk5XPJUrecq48xdko5Y5kuM014z4Ufl61YmX1M7suSJEq0ZMX85ounIWBhRpcyjiKdHG/DK06AofbIakBAmoVgcI26gcbfVeMbWb8CrQtQZqclsYcRd17lzPG0BHqjW2ze3K2NaI5C77UIqA4DWkdqCXSmi78mSelioKMI1PJMeCwulJmafHv7R/qRGvGofn77hp+fTdRw/ZBSmhwmAHV0gn+DlTQtbPfpq4YWX/lpclXXiJPjhWfxPgONEIhRYlDIy+exfpkI06Mf4jIVTQ1WH2Pst6kxA9V0t+k0wuUGXGaa8L3QyB/fDU71PrscGlqxMvu7B2AU2drm/jhstBFIlGjJqSI6Jsv/vMwqSe4jTkPAwq/1ki3NKBTHLJ5GKEQ6Od6ljGsxx1Ht2ybnvzRC7ZHVo1vDOsGGRdAgMBc/geZrrmBQOUECjb+r4zvtRIcxw6Vmh5FKBFoXoOXsRU+NSDq5bP5oVg4j7rzvlbxTi5+SsmopwF0I9Ea36UIUWJm6yIB4DJpvGtEchftnTmqfbWCLftsyZBwGtI79sOZhlRSZl3Siy3gWf02S98kffZPDMZxydWNzEKjlmfEet3axXi3zUOh/HDI1+fbTg6sZt4mF+FY/1xc04lH91VQDEr3wfORcRi4LPpuo4d8t+g67J9TvWpGGADhMAOrZ+lIFqQKO3Ui03DIqaVrYy98IN6/VJtZOY3Q5LL7y080IoDylrN/KRBqNJSbHC8/HcVkgo3t3wULNJS4gEKPEwabxK+GW5hQAILT7Yv0yEYNLYP7nQU4fBvcc8GQqmhqFnMj17Ti3AwyO5exuU2MGj+Ux6evvHwgKWU3naITLDYkymeL5ykU6GHwX1XqhkT+bF8PQ/x3tMR6rv958djk0ncBr2/VkFC0U0kbCdg/AKJe5ksfzs7wmEgXuyXDYaCORbjrM0S6gSTCY8qZSRXRMs/Mmo9f5CEI2T1qtVJLcR7UkjqjdgPFePDajsV7rJVu/XXe021dZVTrhC7pYPI1QuYrfv8lyA2coxFGIShnXYquvhY3PpatsLhP5g0zOf2mteC2GxdxScCRqAJ9Gt4Z1pwHUmsML+nsivaiUQGAufqHWfJEAAAAAQ8umh8eQPNSEW5pTzycIc4zsrvQItzSnS3ySIJ5PEObdhLZhWd8sMhoUirVRaBiVEqO+Epb4JEHVM4LGfZlRFz5S95C6CW3D+cLLRLK+WWTxdf/jdS5lsDblwzfj1kHxoB3ndiRGfSVnjduiLPFJgm867wXrYXVWqKrT0foyoy65+QWpPaKf+n5pOX01Fatddt4N2vKFl4mxTjEOZH2zyCe2FU+j7Y8c4CYpm6tau7vokR08bMqHby8BIeiHq/I5xGBUvkA7zu0D8GhqSIz6SgtHXM2PHMaezNdgGRnk4t9aL0RY3nTeC52/eIzWw+qslQhMKxFT1nhSmHD/9GVGXbeu4Noz9XqJcD7cDjtCTi54ieip/NJy+r8Z1H1qKla7KeHwPK26am/ucczopQ1eyObG+E9inWIcIVbEm4n8F0rKN7HNTmwrng2njRlG2x85BRC5voFLI+3CgIVqF7MHrFR4oSvQIzt4k+id/9iUD9+bX6lYHwQzC1zPlYwOV+VzTZxD9MnH2aeKDH8gwXDtAIK7S4cG4NHURSt3U5AY9ZXT01MSV4jJQRRDb8ZfP/3mHPRbYZivwTLbZGe1c860ZDAFEuO0Xoiw95UuN7zpvBf/IhqQe3mAwziyJkTtgaSCrkoCBSoRmFZp2j7RIqas8WFtCnblNpAlpv02oujLjLqrACo9L1uwbmyQFukn7ITJZCciTuB8uB2jtx6adoScXDVPOtuxFKCI8t8GD7mjlC/6aDKofjOo+z34DnyVUt2t1pl7KlLC4XkRCUf+WnXV3hm+c1md5ekK3i5PjQsdzUtI1mvMzI3xn49GVxjEOsU4h/FjvwOq+exAYV9rEvkvlFEyiRPVaRNAlqK1x93eJ+eeFYFgGk4bM1mFvbSMtj9yz32Z9UsmA6YI7aUhQ5E3AQBakYaEAQvVx8qtUm9gfoMsq9gEqPBCV+s75NCgR3bw44zQd2fXSiQkHOyj8S9uZbLkyOI2v1KxdXT0Nj4IZhZ9w8CR+ZhawrpT/EUcrsrnX2VsYNs+9jOY9VC004nClJBCZBMUGf5AV9JYx4Lh2gHBKnyGRXHm1Qa6QFJNxtJyDg109YpW7qbJnUghYTeb8CL8PXemp6ck5WwBo64Qk4Pt2zUEaYCvVypLCdD/eIsWvLMtkTjot8J7IxFFMF+DZXOUJeL3z7+xtAQZNuacacmlV89OIQxVHWLH85opu2G6anDHPe4rXW6t4PvpeNN5LzsY36i/Q0X7/IjjfLf0cVz0P9fbcGRNiDOv6w+bBTje2M6eWVyVBAofXqKNVCIwrRfpliqTsgx50Hmq/gVKKDhGgY6/wtoU7IERsmvKbSBLiaaGzA39HJ9ONroYFAQAAJ0HAAAsCQAAhgUAAEgFAACnBQAAAAQAADIFAAC8BQAALAkAQYDBAAv3CQwACACMAAgATAAIAMwACAAsAAgArAAIAGwACADsAAgAHAAIAJwACABcAAgA3AAIADwACAC8AAgAfAAIAPwACAACAAgAggAIAEIACADCAAgAIgAIAKIACABiAAgA4gAIABIACACSAAgAUgAIANIACAAyAAgAsgAIAHIACADyAAgACgAIAIoACABKAAgAygAIACoACACqAAgAagAIAOoACAAaAAgAmgAIAFoACADaAAgAOgAIALoACAB6AAgA+gAIAAYACACGAAgARgAIAMYACAAmAAgApgAIAGYACADmAAgAFgAIAJYACABWAAgA1gAIADYACAC2AAgAdgAIAPYACAAOAAgAjgAIAE4ACADOAAgALgAIAK4ACABuAAgA7gAIAB4ACACeAAgAXgAIAN4ACAA+AAgAvgAIAH4ACAD+AAgAAQAIAIEACABBAAgAwQAIACEACAChAAgAYQAIAOEACAARAAgAkQAIAFEACADRAAgAMQAIALEACABxAAgA8QAIAAkACACJAAgASQAIAMkACAApAAgAqQAIAGkACADpAAgAGQAIAJkACABZAAgA2QAIADkACAC5AAgAeQAIAPkACAAFAAgAhQAIAEUACADFAAgAJQAIAKUACABlAAgA5QAIABUACACVAAgAVQAIANUACAA1AAgAtQAIAHUACAD1AAgADQAIAI0ACABNAAgAzQAIAC0ACACtAAgAbQAIAO0ACAAdAAgAnQAIAF0ACADdAAgAPQAIAL0ACAB9AAgA/QAIABMACQATAQkAkwAJAJMBCQBTAAkAUwEJANMACQDTAQkAMwAJADMBCQCzAAkAswEJAHMACQBzAQkA8wAJAPMBCQALAAkACwEJAIsACQCLAQkASwAJAEsBCQDLAAkAywEJACsACQArAQkAqwAJAKsBCQBrAAkAawEJAOsACQDrAQkAGwAJABsBCQCbAAkAmwEJAFsACQBbAQkA2wAJANsBCQA7AAkAOwEJALsACQC7AQkAewAJAHsBCQD7AAkA+wEJAAcACQAHAQkAhwAJAIcBCQBHAAkARwEJAMcACQDHAQkAJwAJACcBCQCnAAkApwEJAGcACQBnAQkA5wAJAOcBCQAXAAkAFwEJAJcACQCXAQkAVwAJAFcBCQDXAAkA1wEJADcACQA3AQkAtwAJALcBCQB3AAkAdwEJAPcACQD3AQkADwAJAA8BCQCPAAkAjwEJAE8ACQBPAQkAzwAJAM8BCQAvAAkALwEJAK8ACQCvAQkAbwAJAG8BCQDvAAkA7wEJAB8ACQAfAQkAnwAJAJ8BCQBfAAkAXwEJAN8ACQDfAQkAPwAJAD8BCQC/AAkAvwEJAH8ACQB/AQkA/wAJAP8BCQAAAAcAQAAHACAABwBgAAcAEAAHAFAABwAwAAcAcAAHAAgABwBIAAcAKAAHAGgABwAYAAcAWAAHADgABwB4AAcABAAHAEQABwAkAAcAZAAHABQABwBUAAcANAAHAHQABwADAAgAgwAIAEMACADDAAgAIwAIAKMACABjAAgA4wAIAAAABQAQAAUACAAFABgABQAEAAUAFAAFAAwABQAcAAUAAgAFABIABQAKAAUAGgAFAAYABQAWAAUADgAFAB4ABQABAAUAEQAFAAkABQAZAAUABQAFABUABQANAAUAHQAFAAMABQATAAUACwAFABsABQAHAAUAFwAFAEGBywAL7AYBAgMEBAUFBgYGBgcHBwcICAgICAgICAkJCQkJCQkJCgoKCgoKCgoKCgoKCgoKCgsLCwsLCwsLCwsLCwsLCwsMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8AABAREhITExQUFBQVFRUVFhYWFhYWFhYXFxcXFxcXFxgYGBgYGBgYGBgYGBgYGBgZGRkZGRkZGRkZGRkZGRkZGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhobGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwdHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dAAECAwQFBgcICAkJCgoLCwwMDAwNDQ0NDg4ODg8PDw8QEBAQEBAQEBEREREREREREhISEhISEhITExMTExMTExQUFBQUFBQUFBQUFBQUFBQVFRUVFRUVFRUVFRUVFRUVFhYWFhYWFhYWFhYWFhYWFhcXFxcXFxcXFxcXFxcXFxcYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhobGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbHAAAAAABAAAAAgAAAAMAAAAEAAAABQAAAAYAAAAHAAAACAAAAAoAAAAMAAAADgAAABAAAAAUAAAAGAAAABwAAAAgAAAAKAAAADAAAAA4AAAAQAAAAFAAAABgAAAAcAAAAIAAAACgAAAAwAAAAOAAQYTSAAutAQEAAAACAAAAAwAAAAQAAAAGAAAACAAAAAwAAAAQAAAAGAAAACAAAAAwAAAAQAAAAGAAAACAAAAAwAAAAAABAACAAQAAAAIAAAADAAAABAAAAAYAAAAIAAAADAAAABAAAAAYAAAAIAAAADAAAABAAAAAYAAAgCAAAMApAAABAQAAHgEAAA8AAAAAJQAAQCoAAAAAAAAeAAAADwAAAAAAAADAKgAAAAAAABMAAAAHAEHg0wALTQEAAAABAAAAAQAAAAEAAAACAAAAAgAAAAIAAAACAAAAAwAAAAMAAAADAAAAAwAAAAQAAAAEAAAABAAAAAQAAAAFAAAABQAAAAUAAAAFAEHQ1AALZQEAAAABAAAAAgAAAAIAAAADAAAAAwAAAAQAAAAEAAAABQAAAAUAAAAGAAAABgAAAAcAAAAHAAAACAAAAAgAAAAJAAAACQAAAAoAAAAKAAAACwAAAAsAAAAMAAAADAAAAA0AAAANAEGA1gALIwIAAAADAAAABwAAAAAAAAAQERIACAcJBgoFCwQMAw0CDgEPAEHQ1gALTQEAAAABAAAAAQAAAAEAAAACAAAAAgAAAAIAAAACAAAAAwAAAAMAAAADAAAAAwAAAAQAAAAEAAAABAAAAAQAAAAFAAAABQAAAAUAAAAFAEHA1wALZQEAAAABAAAAAgAAAAIAAAADAAAAAwAAAAQAAAAEAAAABQAAAAUAAAAGAAAABgAAAAcAAAAHAAAACAAAAAgAAAAJAAAACQAAAAoAAAAKAAAACwAAAAsAAAAMAAAADAAAAA0AAAANAEG42AALASwAQcTYAAthLQAAAAQABAAIAAQALgAAAAQABgAQAAYALwAAAAQADAAgABgALwAAAAgAEAAgACAALwAAAAgAEACAAIAALwAAAAgAIACAAAABMAAAACAAgAACAQAEMAAAACAAAgECAQAQMABBsNkAC6UTAwAEAAUABgAHAAgACQAKAAsADQAPABEAEwAXABsAHwAjACsAMwA7AEMAUwBjAHMAgwCjAMMA4wACAQAAAAAAABAAEAAQABAAEAAQABAAEAARABEAEQARABIAEgASABIAEwATABMAEwAUABQAFAAUABUAFQAVABUAEABNAMoAAAABAAIAAwAEAAUABwAJAA0AEQAZACEAMQBBAGEAgQDBAAEBgQEBAgEDAQQBBgEIAQwBEAEYASABMAFAAWAAAAAAEAAQABAAEAARABEAEgASABMAEwAUABQAFQAVABYAFgAXABcAGAAYABkAGQAaABoAGwAbABwAHAAdAB0AQABAAGAHAAAACFAAAAgQABQIcwASBx8AAAhwAAAIMAAACcAAEAcKAAAIYAAACCAAAAmgAAAIAAAACIAAAAhAAAAJ4AAQBwYAAAhYAAAIGAAACZAAEwc7AAAIeAAACDgAAAnQABEHEQAACGgAAAgoAAAJsAAACAgAAAiIAAAISAAACfAAEAcEAAAIVAAACBQAFQjjABMHKwAACHQAAAg0AAAJyAARBw0AAAhkAAAIJAAACagAAAgEAAAIhAAACEQAAAnoABAHCAAACFwAAAgcAAAJmAAUB1MAAAh8AAAIPAAACdgAEgcXAAAIbAAACCwAAAm4AAAIDAAACIwAAAhMAAAJ+AAQBwMAAAhSAAAIEgAVCKMAEwcjAAAIcgAACDIAAAnEABEHCwAACGIAAAgiAAAJpAAACAIAAAiCAAAIQgAACeQAEAcHAAAIWgAACBoAAAmUABQHQwAACHoAAAg6AAAJ1AASBxMAAAhqAAAIKgAACbQAAAgKAAAIigAACEoAAAn0ABAHBQAACFYAAAgWAEAIAAATBzMAAAh2AAAINgAACcwAEQcPAAAIZgAACCYAAAmsAAAIBgAACIYAAAhGAAAJ7AAQBwkAAAheAAAIHgAACZwAFAdjAAAIfgAACD4AAAncABIHGwAACG4AAAguAAAJvAAACA4AAAiOAAAITgAACfwAYAcAAAAIUQAACBEAFQiDABIHHwAACHEAAAgxAAAJwgAQBwoAAAhhAAAIIQAACaIAAAgBAAAIgQAACEEAAAniABAHBgAACFkAAAgZAAAJkgATBzsAAAh5AAAIOQAACdIAEQcRAAAIaQAACCkAAAmyAAAICQAACIkAAAhJAAAJ8gAQBwQAAAhVAAAIFQAQCAIBEwcrAAAIdQAACDUAAAnKABEHDQAACGUAAAglAAAJqgAACAUAAAiFAAAIRQAACeoAEAcIAAAIXQAACB0AAAmaABQHUwAACH0AAAg9AAAJ2gASBxcAAAhtAAAILQAACboAAAgNAAAIjQAACE0AAAn6ABAHAwAACFMAAAgTABUIwwATByMAAAhzAAAIMwAACcYAEQcLAAAIYwAACCMAAAmmAAAIAwAACIMAAAhDAAAJ5gAQBwcAAAhbAAAIGwAACZYAFAdDAAAIewAACDsAAAnWABIHEwAACGsAAAgrAAAJtgAACAsAAAiLAAAISwAACfYAEAcFAAAIVwAACBcAQAgAABMHMwAACHcAAAg3AAAJzgARBw8AAAhnAAAIJwAACa4AAAgHAAAIhwAACEcAAAnuABAHCQAACF8AAAgfAAAJngAUB2MAAAh/AAAIPwAACd4AEgcbAAAIbwAACC8AAAm+AAAIDwAACI8AAAhPAAAJ/gBgBwAAAAhQAAAIEAAUCHMAEgcfAAAIcAAACDAAAAnBABAHCgAACGAAAAggAAAJoQAACAAAAAiAAAAIQAAACeEAEAcGAAAIWAAACBgAAAmRABMHOwAACHgAAAg4AAAJ0QARBxEAAAhoAAAIKAAACbEAAAgIAAAIiAAACEgAAAnxABAHBAAACFQAAAgUABUI4wATBysAAAh0AAAINAAACckAEQcNAAAIZAAACCQAAAmpAAAIBAAACIQAAAhEAAAJ6QAQBwgAAAhcAAAIHAAACZkAFAdTAAAIfAAACDwAAAnZABIHFwAACGwAAAgsAAAJuQAACAwAAAiMAAAITAAACfkAEAcDAAAIUgAACBIAFQijABMHIwAACHIAAAgyAAAJxQARBwsAAAhiAAAIIgAACaUAAAgCAAAIggAACEIAAAnlABAHBwAACFoAAAgaAAAJlQAUB0MAAAh6AAAIOgAACdUAEgcTAAAIagAACCoAAAm1AAAICgAACIoAAAhKAAAJ9QAQBwUAAAhWAAAIFgBACAAAEwczAAAIdgAACDYAAAnNABEHDwAACGYAAAgmAAAJrQAACAYAAAiGAAAIRgAACe0AEAcJAAAIXgAACB4AAAmdABQHYwAACH4AAAg+AAAJ3QASBxsAAAhuAAAILgAACb0AAAgOAAAIjgAACE4AAAn9AGAHAAAACFEAAAgRABUIgwASBx8AAAhxAAAIMQAACcMAEAcKAAAIYQAACCEAAAmjAAAIAQAACIEAAAhBAAAJ4wAQBwYAAAhZAAAIGQAACZMAEwc7AAAIeQAACDkAAAnTABEHEQAACGkAAAgpAAAJswAACAkAAAiJAAAISQAACfMAEAcEAAAIVQAACBUAEAgCARMHKwAACHUAAAg1AAAJywARBw0AAAhlAAAIJQAACasAAAgFAAAIhQAACEUAAAnrABAHCAAACF0AAAgdAAAJmwAUB1MAAAh9AAAIPQAACdsAEgcXAAAIbQAACC0AAAm7AAAIDQAACI0AAAhNAAAJ+wAQBwMAAAhTAAAIEwAVCMMAEwcjAAAIcwAACDMAAAnHABEHCwAACGMAAAgjAAAJpwAACAMAAAiDAAAIQwAACecAEAcHAAAIWwAACBsAAAmXABQHQwAACHsAAAg7AAAJ1wASBxMAAAhrAAAIKwAACbcAAAgLAAAIiwAACEsAAAn3ABAHBQAACFcAAAgXAEAIAAATBzMAAAh3AAAINwAACc8AEQcPAAAIZwAACCcAAAmvAAAIBwAACIcAAAhHAAAJ7wAQBwkAAAhfAAAIHwAACZ8AFAdjAAAIfwAACD8AAAnfABIHGwAACG8AAAgvAAAJvwAACA8AAAiPAAAITwAACf8AEAUBABcFAQETBREAGwUBEBEFBQAZBQEEFQVBAB0FAUAQBQMAGAUBAhQFIQAcBQEgEgUJABoFAQgWBYEAQAUAABAFAgAXBYEBEwUZABsFARgRBQcAGQUBBhUFYQAdBQFgEAUEABgFAQMUBTEAHAUBMBIFDQAaBQEMFgXBAEAFAAAQABEAEgAAAAgABwAJAAYACgAFAAsABAAMAAMADQACAA4AAQAPAEHg7AALQREACgAREREAAAAABQAAAAAAAAkAAAAACwAAAAAAAAAAEQAPChEREQMKBwABAAkLCwAACQYLAAALAAYRAAAAERERAEGx7QALIQsAAAAAAAAAABEACgoREREACgAAAgAJCwAAAAkACwAACwBB6+0ACwEMAEH37QALFQwAAAAADAAAAAAJDAAAAAAADAAADABBpe4ACwEOAEGx7gALFQ0AAAAEDQAAAAAJDgAAAAAADgAADgBB3+4ACwEQAEHr7gALHg8AAAAADwAAAAAJEAAAAAAAEAAAEAAAEgAAABISEgBBou8ACw4SAAAAEhISAAAAAAAACQBB0+8ACwELAEHf7wALFQoAAAAACgAAAAAJCwAAAAAACwAACwBBjfAACwEMAEGZ8AALJwwAAAAADAAAAAAJDAAAAAAADAAADAAAMDEyMzQ1Njc4OUFCQ0RFRgBB5PAACwE+AEGL8QALBf//////AEHQ8QALVxkSRDsCPyxHFD0zMAobBkZLRTcPSQ6OFwNAHTxpKzYfSi0cASAlKSEIDBUWIi4QOD4LNDEYZHR1di9BCX85ESNDMkKJiosFBCYoJw0qHjWMBxpIkxOUlQBBsPIAC4oOSWxsZWdhbCBieXRlIHNlcXVlbmNlAERvbWFpbiBlcnJvcgBSZXN1bHQgbm90IHJlcHJlc2VudGFibGUATm90IGEgdHR5AFBlcm1pc3Npb24gZGVuaWVkAE9wZXJhdGlvbiBub3QgcGVybWl0dGVkAE5vIHN1Y2ggZmlsZSBvciBkaXJlY3RvcnkATm8gc3VjaCBwcm9jZXNzAEZpbGUgZXhpc3RzAFZhbHVlIHRvbyBsYXJnZSBmb3IgZGF0YSB0eXBlAE5vIHNwYWNlIGxlZnQgb24gZGV2aWNlAE91dCBvZiBtZW1vcnkAUmVzb3VyY2UgYnVzeQBJbnRlcnJ1cHRlZCBzeXN0ZW0gY2FsbABSZXNvdXJjZSB0ZW1wb3JhcmlseSB1bmF2YWlsYWJsZQBJbnZhbGlkIHNlZWsAQ3Jvc3MtZGV2aWNlIGxpbmsAUmVhZC1vbmx5IGZpbGUgc3lzdGVtAERpcmVjdG9yeSBub3QgZW1wdHkAQ29ubmVjdGlvbiByZXNldCBieSBwZWVyAE9wZXJhdGlvbiB0aW1lZCBvdXQAQ29ubmVjdGlvbiByZWZ1c2VkAEhvc3QgaXMgZG93bgBIb3N0IGlzIHVucmVhY2hhYmxlAEFkZHJlc3MgaW4gdXNlAEJyb2tlbiBwaXBlAEkvTyBlcnJvcgBObyBzdWNoIGRldmljZSBvciBhZGRyZXNzAEJsb2NrIGRldmljZSByZXF1aXJlZABObyBzdWNoIGRldmljZQBOb3QgYSBkaXJlY3RvcnkASXMgYSBkaXJlY3RvcnkAVGV4dCBmaWxlIGJ1c3kARXhlYyBmb3JtYXQgZXJyb3IASW52YWxpZCBhcmd1bWVudABBcmd1bWVudCBsaXN0IHRvbyBsb25nAFN5bWJvbGljIGxpbmsgbG9vcABGaWxlbmFtZSB0b28gbG9uZwBUb28gbWFueSBvcGVuIGZpbGVzIGluIHN5c3RlbQBObyBmaWxlIGRlc2NyaXB0b3JzIGF2YWlsYWJsZQBCYWQgZmlsZSBkZXNjcmlwdG9yAE5vIGNoaWxkIHByb2Nlc3MAQmFkIGFkZHJlc3MARmlsZSB0b28gbGFyZ2UAVG9vIG1hbnkgbGlua3MATm8gbG9ja3MgYXZhaWxhYmxlAFJlc291cmNlIGRlYWRsb2NrIHdvdWxkIG9jY3VyAFN0YXRlIG5vdCByZWNvdmVyYWJsZQBQcmV2aW91cyBvd25lciBkaWVkAE9wZXJhdGlvbiBjYW5jZWxlZABGdW5jdGlvbiBub3QgaW1wbGVtZW50ZWQATm8gbWVzc2FnZSBvZiBkZXNpcmVkIHR5cGUASWRlbnRpZmllciByZW1vdmVkAERldmljZSBub3QgYSBzdHJlYW0ATm8gZGF0YSBhdmFpbGFibGUARGV2aWNlIHRpbWVvdXQAT3V0IG9mIHN0cmVhbXMgcmVzb3VyY2VzAExpbmsgaGFzIGJlZW4gc2V2ZXJlZABQcm90b2NvbCBlcnJvcgBCYWQgbWVzc2FnZQBGaWxlIGRlc2NyaXB0b3IgaW4gYmFkIHN0YXRlAE5vdCBhIHNvY2tldABEZXN0aW5hdGlvbiBhZGRyZXNzIHJlcXVpcmVkAE1lc3NhZ2UgdG9vIGxhcmdlAFByb3RvY29sIHdyb25nIHR5cGUgZm9yIHNvY2tldABQcm90b2NvbCBub3QgYXZhaWxhYmxlAFByb3RvY29sIG5vdCBzdXBwb3J0ZWQAU29ja2V0IHR5cGUgbm90IHN1cHBvcnRlZABOb3Qgc3VwcG9ydGVkAFByb3RvY29sIGZhbWlseSBub3Qgc3VwcG9ydGVkAEFkZHJlc3MgZmFtaWx5IG5vdCBzdXBwb3J0ZWQgYnkgcHJvdG9jb2wAQWRkcmVzcyBub3QgYXZhaWxhYmxlAE5ldHdvcmsgaXMgZG93bgBOZXR3b3JrIHVucmVhY2hhYmxlAENvbm5lY3Rpb24gcmVzZXQgYnkgbmV0d29yawBDb25uZWN0aW9uIGFib3J0ZWQATm8gYnVmZmVyIHNwYWNlIGF2YWlsYWJsZQBTb2NrZXQgaXMgY29ubmVjdGVkAFNvY2tldCBub3QgY29ubmVjdGVkAENhbm5vdCBzZW5kIGFmdGVyIHNvY2tldCBzaHV0ZG93bgBPcGVyYXRpb24gYWxyZWFkeSBpbiBwcm9ncmVzcwBPcGVyYXRpb24gaW4gcHJvZ3Jlc3MAU3RhbGUgZmlsZSBoYW5kbGUAUmVtb3RlIEkvTyBlcnJvcgBRdW90YSBleGNlZWRlZABObyBtZWRpdW0gZm91bmQAV3JvbmcgbWVkaXVtIHR5cGUATm8gZXJyb3IgaW5mb3JtYXRpb24AQcCAAQuFARMAAAAUAAAAFQAAABYAAAAXAAAAGAAAABkAAAAaAAAAGwAAABwAAAAdAAAAHgAAAB8AAAAgAAAAIQAAACIAAAAjAAAAgERQADEAAAAyAAAAMwAAADQAAAA1AAAANgAAADcAAAA4AAAAOQAAADIAAAAzAAAANAAAADUAAAA2AAAANwAAADgAQfSCAQsCXEQAQbCDAQsQ/////////////////////w==\";Bo(ji)||(ji=P(ji));function ro(Je){try{if(Je==ji&&ce)return new Uint8Array(ce);var st=Me(Je);if(st)return st;if(R)return R(Je);throw\"sync fetching of the wasm failed: you can preload it to Module['wasmBinary'] manually, or emcc.py will do that for you when generating HTML (but not JS)\"}catch(St){ns(St)}}function vo(Je,st){var St,lr,ee;try{ee=ro(Je),lr=new WebAssembly.Module(ee),St=new WebAssembly.Instance(lr,st)}catch(Oe){var Ee=Oe.toString();throw te(\"failed to compile wasm module: \"+Ee),(Ee.includes(\"imported Memory\")||Ee.includes(\"memory import\"))&&te(\"Memory size incompatibility issues may be due to changing INITIAL_MEMORY at runtime to something too large. Use ALLOW_MEMORY_GROWTH to allow any size memory (and also make sure not to set INITIAL_MEMORY at runtime to something smaller than it was at compile time).\"),Oe}return[St,lr]}function RA(){var Je={a:fu};function st(ee,Ee){var Oe=ee.exports;r.asm=Oe,Be=r.asm.g,z(Be.buffer),$=r.asm.W,gn(r.asm.h),wo(\"wasm-instantiate\")}if(ai(\"wasm-instantiate\"),r.instantiateWasm)try{var St=r.instantiateWasm(Je,st);return St}catch(ee){return te(\"Module.instantiateWasm callback failed with error: \"+ee),!1}var lr=vo(ji,Je);return st(lr[0]),r.asm}function pf(Je){return F.getFloat32(Je,!0)}function yh(Je){return F.getFloat64(Je,!0)}function Eh(Je){return F.getInt16(Je,!0)}function no(Je){return F.getInt32(Je,!0)}function jn(Je,st){F.setInt32(Je,st,!0)}function Fs(Je){for(;Je.length>0;){var st=Je.shift();if(typeof st==\"function\"){st(r);continue}var St=st.func;typeof St==\"number\"?st.arg===void 0?$.get(St)():$.get(St)(st.arg):St(st.arg===void 0?null:st.arg)}}function io(Je,st){var St=new Date(no((Je>>2)*4)*1e3);jn((st>>2)*4,St.getUTCSeconds()),jn((st+4>>2)*4,St.getUTCMinutes()),jn((st+8>>2)*4,St.getUTCHours()),jn((st+12>>2)*4,St.getUTCDate()),jn((st+16>>2)*4,St.getUTCMonth()),jn((st+20>>2)*4,St.getUTCFullYear()-1900),jn((st+24>>2)*4,St.getUTCDay()),jn((st+36>>2)*4,0),jn((st+32>>2)*4,0);var lr=Date.UTC(St.getUTCFullYear(),0,1,0,0,0,0),ee=(St.getTime()-lr)/(1e3*60*60*24)|0;return jn((st+28>>2)*4,ee),io.GMTString||(io.GMTString=rt(\"GMT\")),jn((st+40>>2)*4,io.GMTString),st}function lu(Je,st){return io(Je,st)}function cu(Je,st,St){ke.copyWithin(Je,st,st+St)}function uu(Je){try{return Be.grow(Je-Pe.byteLength+65535>>>16),z(Be.buffer),1}catch{}}function FA(Je){var st=ke.length;Je=Je>>>0;var St=2147483648;if(Je>St)return!1;for(var lr=1;lr<=4;lr*=2){var ee=st*(1+.2/lr);ee=Math.min(ee,Je+100663296);var Ee=Math.min(St,Ne(Math.max(Je,ee),65536)),Oe=uu(Ee);if(Oe)return!0}return!1}function NA(Je){Ae(Je)}function aa(Je){var st=Date.now()/1e3|0;return Je&&jn((Je>>2)*4,st),st}function la(){if(la.called)return;la.called=!0;var Je=new Date().getFullYear(),st=new Date(Je,0,1),St=new Date(Je,6,1),lr=st.getTimezoneOffset(),ee=St.getTimezoneOffset(),Ee=Math.max(lr,ee);jn((Sl()>>2)*4,Ee*60),jn((ws()>>2)*4,+(lr!=ee));function Oe(fn){var li=fn.toTimeString().match(/\\(([A-Za-z ]+)\\)$/);return li?li[1]:\"GMT\"}var gt=Oe(st),yt=Oe(St),Dt=rt(gt),tr=rt(yt);ee<lr?(jn((_i()>>2)*4,Dt),jn((_i()+4>>2)*4,tr)):(jn((_i()>>2)*4,tr),jn((_i()+4>>2)*4,Dt))}function OA(Je){la();var st=Date.UTC(no((Je+20>>2)*4)+1900,no((Je+16>>2)*4),no((Je+12>>2)*4),no((Je+8>>2)*4),no((Je+4>>2)*4),no((Je>>2)*4),0),St=new Date(st);jn((Je+24>>2)*4,St.getUTCDay());var lr=Date.UTC(St.getUTCFullYear(),0,1,0,0,0,0),ee=(St.getTime()-lr)/(1e3*60*60*24)|0;return jn((Je+28>>2)*4,ee),St.getTime()/1e3|0}var gr=typeof atob==\"function\"?atob:function(Je){var st=\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\",St=\"\",lr,ee,Ee,Oe,gt,yt,Dt,tr=0;Je=Je.replace(/[^A-Za-z0-9\\+\\/\\=]/g,\"\");do Oe=st.indexOf(Je.charAt(tr++)),gt=st.indexOf(Je.charAt(tr++)),yt=st.indexOf(Je.charAt(tr++)),Dt=st.indexOf(Je.charAt(tr++)),lr=Oe<<2|gt>>4,ee=(gt&15)<<4|yt>>2,Ee=(yt&3)<<6|Dt,St=St+String.fromCharCode(lr),yt!==64&&(St=St+String.fromCharCode(ee)),Dt!==64&&(St=St+String.fromCharCode(Ee));while(tr<Je.length);return St};function So(Je){if(typeof C==\"boolean\"&&C){var st;try{st=Buffer.from(Je,\"base64\")}catch{st=new Buffer(Je,\"base64\")}return new Uint8Array(st.buffer,st.byteOffset,st.byteLength)}try{for(var St=gr(Je),lr=new Uint8Array(St.length),ee=0;ee<St.length;++ee)lr[ee]=St.charCodeAt(ee);return lr}catch{throw new Error(\"Converting base64 string to bytes failed.\")}}function Me(Je){if(Bo(Je))return So(Je.slice(to.length))}var fu={e:lu,c:cu,d:FA,a:NA,b:aa,f:OA},Cr=RA(),hf=r.___wasm_call_ctors=Cr.h,LA=r._zip_ext_count_symlinks=Cr.i,MA=r._zip_file_get_external_attributes=Cr.j,Au=r._zipstruct_statS=Cr.k,pu=r._zipstruct_stat_size=Cr.l,ac=r._zipstruct_stat_mtime=Cr.m,ve=r._zipstruct_stat_crc=Cr.n,Nt=r._zipstruct_errorS=Cr.o,lc=r._zipstruct_error_code_zip=Cr.p,Li=r._zipstruct_stat_comp_size=Cr.q,so=r._zipstruct_stat_comp_method=Cr.r,Rt=r._zip_close=Cr.s,xn=r._zip_delete=Cr.t,ca=r._zip_dir_add=Cr.u,qi=r._zip_discard=Cr.v,Mi=r._zip_error_init_with_code=Cr.w,Oa=r._zip_get_error=Cr.x,dn=r._zip_file_get_error=Cr.y,Jn=r._zip_error_strerror=Cr.z,hu=r._zip_fclose=Cr.A,Ih=r._zip_file_add=Cr.B,La=r._free=Cr.C,Ma=r._malloc=Cr.D,Ua=r._zip_source_error=Cr.E,Xe=r._zip_source_seek=Cr.F,Ha=r._zip_file_set_external_attributes=Cr.G,gf=r._zip_file_set_mtime=Cr.H,cc=r._zip_fopen_index=Cr.I,wn=r._zip_fread=Cr.J,ua=r._zip_get_name=Cr.K,_A=r._zip_get_num_entries=Cr.L,UA=r._zip_source_read=Cr.M,fa=r._zip_name_locate=Cr.N,vl=r._zip_open_from_source=Cr.O,Mt=r._zip_set_file_compression=Cr.P,kn=r._zip_source_buffer=Cr.Q,Aa=r._zip_source_buffer_create=Cr.R,ja=r._zip_source_close=Cr.S,is=r._zip_source_free=Cr.T,uc=r._zip_source_keep=Cr.U,gu=r._zip_source_open=Cr.V,fc=r._zip_source_tell=Cr.X,qa=r._zip_stat_index=Cr.Y,_i=r.__get_tzname=Cr.Z,ws=r.__get_daylight=Cr._,Sl=r.__get_timezone=Cr.$,df=r.stackSave=Cr.aa,Ac=r.stackRestore=Cr.ba,Bi=r.stackAlloc=Cr.ca;r.cwrap=se,r.getValue=pe;var Qn;nn=function Je(){Qn||pc(),Qn||(nn=Je)};function pc(Je){if(Je=Je||f,Ir>0||(It(),Ir>0))return;function st(){Qn||(Qn=!0,r.calledRun=!0,!Ce&&(qt(),s(r),r.onRuntimeInitialized&&r.onRuntimeInitialized(),ir()))}r.setStatus?(r.setStatus(\"Running...\"),setTimeout(function(){setTimeout(function(){r.setStatus(\"\")},1),st()},1)):st()}if(r.run=pc,r.preInit)for(typeof r.preInit==\"function\"&&(r.preInit=[r.preInit]);r.preInit.length>0;)r.preInit.pop()();return pc(),e}}();typeof IT==\"object\"&&typeof jj==\"object\"?jj.exports=Hj:typeof define==\"function\"&&define.amd?define([],function(){return Hj}):typeof IT==\"object\"&&(IT.createModule=Hj)});var Hp,kde,Qde,Tde=Ct(()=>{Hp=[\"number\",\"number\"],kde=(X=>(X[X.ZIP_ER_OK=0]=\"ZIP_ER_OK\",X[X.ZIP_ER_MULTIDISK=1]=\"ZIP_ER_MULTIDISK\",X[X.ZIP_ER_RENAME=2]=\"ZIP_ER_RENAME\",X[X.ZIP_ER_CLOSE=3]=\"ZIP_ER_CLOSE\",X[X.ZIP_ER_SEEK=4]=\"ZIP_ER_SEEK\",X[X.ZIP_ER_READ=5]=\"ZIP_ER_READ\",X[X.ZIP_ER_WRITE=6]=\"ZIP_ER_WRITE\",X[X.ZIP_ER_CRC=7]=\"ZIP_ER_CRC\",X[X.ZIP_ER_ZIPCLOSED=8]=\"ZIP_ER_ZIPCLOSED\",X[X.ZIP_ER_NOENT=9]=\"ZIP_ER_NOENT\",X[X.ZIP_ER_EXISTS=10]=\"ZIP_ER_EXISTS\",X[X.ZIP_ER_OPEN=11]=\"ZIP_ER_OPEN\",X[X.ZIP_ER_TMPOPEN=12]=\"ZIP_ER_TMPOPEN\",X[X.ZIP_ER_ZLIB=13]=\"ZIP_ER_ZLIB\",X[X.ZIP_ER_MEMORY=14]=\"ZIP_ER_MEMORY\",X[X.ZIP_ER_CHANGED=15]=\"ZIP_ER_CHANGED\",X[X.ZIP_ER_COMPNOTSUPP=16]=\"ZIP_ER_COMPNOTSUPP\",X[X.ZIP_ER_EOF=17]=\"ZIP_ER_EOF\",X[X.ZIP_ER_INVAL=18]=\"ZIP_ER_INVAL\",X[X.ZIP_ER_NOZIP=19]=\"ZIP_ER_NOZIP\",X[X.ZIP_ER_INTERNAL=20]=\"ZIP_ER_INTERNAL\",X[X.ZIP_ER_INCONS=21]=\"ZIP_ER_INCONS\",X[X.ZIP_ER_REMOVE=22]=\"ZIP_ER_REMOVE\",X[X.ZIP_ER_DELETED=23]=\"ZIP_ER_DELETED\",X[X.ZIP_ER_ENCRNOTSUPP=24]=\"ZIP_ER_ENCRNOTSUPP\",X[X.ZIP_ER_RDONLY=25]=\"ZIP_ER_RDONLY\",X[X.ZIP_ER_NOPASSWD=26]=\"ZIP_ER_NOPASSWD\",X[X.ZIP_ER_WRONGPASSWD=27]=\"ZIP_ER_WRONGPASSWD\",X[X.ZIP_ER_OPNOTSUPP=28]=\"ZIP_ER_OPNOTSUPP\",X[X.ZIP_ER_INUSE=29]=\"ZIP_ER_INUSE\",X[X.ZIP_ER_TELL=30]=\"ZIP_ER_TELL\",X[X.ZIP_ER_COMPRESSED_DATA=31]=\"ZIP_ER_COMPRESSED_DATA\",X))(kde||{}),Qde=t=>({get HEAPU8(){return t.HEAPU8},errors:kde,SEEK_SET:0,SEEK_CUR:1,SEEK_END:2,ZIP_CHECKCONS:4,ZIP_EXCL:2,ZIP_RDONLY:16,ZIP_FL_OVERWRITE:8192,ZIP_FL_COMPRESSED:4,ZIP_OPSYS_DOS:0,ZIP_OPSYS_AMIGA:1,ZIP_OPSYS_OPENVMS:2,ZIP_OPSYS_UNIX:3,ZIP_OPSYS_VM_CMS:4,ZIP_OPSYS_ATARI_ST:5,ZIP_OPSYS_OS_2:6,ZIP_OPSYS_MACINTOSH:7,ZIP_OPSYS_Z_SYSTEM:8,ZIP_OPSYS_CPM:9,ZIP_OPSYS_WINDOWS_NTFS:10,ZIP_OPSYS_MVS:11,ZIP_OPSYS_VSE:12,ZIP_OPSYS_ACORN_RISC:13,ZIP_OPSYS_VFAT:14,ZIP_OPSYS_ALTERNATE_MVS:15,ZIP_OPSYS_BEOS:16,ZIP_OPSYS_TANDEM:17,ZIP_OPSYS_OS_400:18,ZIP_OPSYS_OS_X:19,ZIP_CM_DEFAULT:-1,ZIP_CM_STORE:0,ZIP_CM_DEFLATE:8,uint08S:t._malloc(1),uint32S:t._malloc(4),malloc:t._malloc,free:t._free,getValue:t.getValue,openFromSource:t.cwrap(\"zip_open_from_source\",\"number\",[\"number\",\"number\",\"number\"]),close:t.cwrap(\"zip_close\",\"number\",[\"number\"]),discard:t.cwrap(\"zip_discard\",null,[\"number\"]),getError:t.cwrap(\"zip_get_error\",\"number\",[\"number\"]),getName:t.cwrap(\"zip_get_name\",\"string\",[\"number\",\"number\",\"number\"]),getNumEntries:t.cwrap(\"zip_get_num_entries\",\"number\",[\"number\",\"number\"]),delete:t.cwrap(\"zip_delete\",\"number\",[\"number\",\"number\"]),statIndex:t.cwrap(\"zip_stat_index\",\"number\",[\"number\",...Hp,\"number\",\"number\"]),fopenIndex:t.cwrap(\"zip_fopen_index\",\"number\",[\"number\",...Hp,\"number\"]),fread:t.cwrap(\"zip_fread\",\"number\",[\"number\",\"number\",\"number\",\"number\"]),fclose:t.cwrap(\"zip_fclose\",\"number\",[\"number\"]),dir:{add:t.cwrap(\"zip_dir_add\",\"number\",[\"number\",\"string\"])},file:{add:t.cwrap(\"zip_file_add\",\"number\",[\"number\",\"string\",\"number\",\"number\"]),getError:t.cwrap(\"zip_file_get_error\",\"number\",[\"number\"]),getExternalAttributes:t.cwrap(\"zip_file_get_external_attributes\",\"number\",[\"number\",...Hp,\"number\",\"number\",\"number\"]),setExternalAttributes:t.cwrap(\"zip_file_set_external_attributes\",\"number\",[\"number\",...Hp,\"number\",\"number\",\"number\"]),setMtime:t.cwrap(\"zip_file_set_mtime\",\"number\",[\"number\",...Hp,\"number\",\"number\"]),setCompression:t.cwrap(\"zip_set_file_compression\",\"number\",[\"number\",...Hp,\"number\",\"number\"])},ext:{countSymlinks:t.cwrap(\"zip_ext_count_symlinks\",\"number\",[\"number\"])},error:{initWithCode:t.cwrap(\"zip_error_init_with_code\",null,[\"number\",\"number\"]),strerror:t.cwrap(\"zip_error_strerror\",\"string\",[\"number\"])},name:{locate:t.cwrap(\"zip_name_locate\",\"number\",[\"number\",\"string\",\"number\"])},source:{fromUnattachedBuffer:t.cwrap(\"zip_source_buffer_create\",\"number\",[\"number\",...Hp,\"number\",\"number\"]),fromBuffer:t.cwrap(\"zip_source_buffer\",\"number\",[\"number\",\"number\",...Hp,\"number\"]),free:t.cwrap(\"zip_source_free\",null,[\"number\"]),keep:t.cwrap(\"zip_source_keep\",null,[\"number\"]),open:t.cwrap(\"zip_source_open\",\"number\",[\"number\"]),close:t.cwrap(\"zip_source_close\",\"number\",[\"number\"]),seek:t.cwrap(\"zip_source_seek\",\"number\",[\"number\",...Hp,\"number\"]),tell:t.cwrap(\"zip_source_tell\",\"number\",[\"number\"]),read:t.cwrap(\"zip_source_read\",\"number\",[\"number\",\"number\",\"number\"]),error:t.cwrap(\"zip_source_error\",\"number\",[\"number\"])},struct:{statS:t.cwrap(\"zipstruct_statS\",\"number\",[]),statSize:t.cwrap(\"zipstruct_stat_size\",\"number\",[\"number\"]),statCompSize:t.cwrap(\"zipstruct_stat_comp_size\",\"number\",[\"number\"]),statCompMethod:t.cwrap(\"zipstruct_stat_comp_method\",\"number\",[\"number\"]),statMtime:t.cwrap(\"zipstruct_stat_mtime\",\"number\",[\"number\"]),statCrc:t.cwrap(\"zipstruct_stat_crc\",\"number\",[\"number\"]),errorS:t.cwrap(\"zipstruct_errorS\",\"number\",[]),errorCodeZip:t.cwrap(\"zipstruct_error_code_zip\",\"number\",[\"number\"])}})});function qj(t,e){let r=t.indexOf(e);if(r<=0)return null;let s=r;for(;r>=0&&(s=r+e.length,t[s]!==K.sep);){if(t[r-1]===K.sep)return null;r=t.indexOf(e,s)}return t.length>s&&t[s]!==K.sep?null:t.slice(0,s)}var tA,Rde=Ct(()=>{bt();bt();rA();tA=class t extends r0{static async openPromise(e,r){let s=new t(r);try{return await e(s)}finally{s.saveAndClose()}}constructor(e={}){let r=e.fileExtensions,s=e.readOnlyArchives,a=typeof r>\"u\"?f=>qj(f,\".zip\"):f=>{for(let p of r){let h=qj(f,p);if(h)return h}return null},n=(f,p)=>new hs(p,{baseFs:f,readOnly:s,stats:f.statSync(p),customZipImplementation:e.customZipImplementation}),c=async(f,p)=>{let h={baseFs:f,readOnly:s,stats:await f.statPromise(p),customZipImplementation:e.customZipImplementation};return()=>new hs(p,h)};super({...e,factorySync:n,factoryPromise:c,getMountPoint:a})}}});var Gj,wI,Wj=Ct(()=>{Uj();Gj=class extends Error{constructor(e,r){super(e),this.name=\"Libzip Error\",this.code=r}},wI=class{constructor(e){this.filesShouldBeCached=!0;let r=\"buffer\"in e?e.buffer:e.baseFs.readFileSync(e.path);this.libzip=yv();let s=this.libzip.malloc(4);try{let c=0;e.readOnly&&(c|=this.libzip.ZIP_RDONLY);let f=this.allocateUnattachedSource(r);try{this.zip=this.libzip.openFromSource(f,c,s),this.lzSource=f}catch(p){throw this.libzip.source.free(f),p}if(this.zip===0){let p=this.libzip.struct.errorS();throw this.libzip.error.initWithCode(p,this.libzip.getValue(s,\"i32\")),this.makeLibzipError(p)}}finally{this.libzip.free(s)}let a=this.libzip.getNumEntries(this.zip,0),n=new Array(a);for(let c=0;c<a;++c)n[c]=this.libzip.getName(this.zip,c,0);if(this.listings=n,this.symlinkCount=this.libzip.ext.countSymlinks(this.zip),this.symlinkCount===-1)throw this.makeLibzipError(this.libzip.getError(this.zip))}getSymlinkCount(){return this.symlinkCount}getListings(){return this.listings}stat(e){let r=this.libzip.struct.statS();if(this.libzip.statIndex(this.zip,e,0,0,r)===-1)throw this.makeLibzipError(this.libzip.getError(this.zip));let a=this.libzip.struct.statSize(r)>>>0,n=this.libzip.struct.statMtime(r)>>>0,c=this.libzip.struct.statCrc(r)>>>0;return{size:a,mtime:n,crc:c}}makeLibzipError(e){let r=this.libzip.struct.errorCodeZip(e),s=this.libzip.error.strerror(e),a=new Gj(s,this.libzip.errors[r]);if(r===this.libzip.errors.ZIP_ER_CHANGED)throw new Error(`Assertion failed: Unexpected libzip error: ${a.message}`);return a}setFileSource(e,r,s){let a=this.allocateSource(s);try{let n=this.libzip.file.add(this.zip,e,a,this.libzip.ZIP_FL_OVERWRITE);if(n===-1)throw this.makeLibzipError(this.libzip.getError(this.zip));if(r!==null&&this.libzip.file.setCompression(this.zip,n,0,r[0],r[1])===-1)throw this.makeLibzipError(this.libzip.getError(this.zip));return n}catch(n){throw this.libzip.source.free(a),n}}setMtime(e,r){if(this.libzip.file.setMtime(this.zip,e,0,r,0)===-1)throw this.makeLibzipError(this.libzip.getError(this.zip))}getExternalAttributes(e){if(this.libzip.file.getExternalAttributes(this.zip,e,0,0,this.libzip.uint08S,this.libzip.uint32S)===-1)throw this.makeLibzipError(this.libzip.getError(this.zip));let s=this.libzip.getValue(this.libzip.uint08S,\"i8\")>>>0,a=this.libzip.getValue(this.libzip.uint32S,\"i32\")>>>0;return[s,a]}setExternalAttributes(e,r,s){if(this.libzip.file.setExternalAttributes(this.zip,e,0,0,r,s)===-1)throw this.makeLibzipError(this.libzip.getError(this.zip))}locate(e){return this.libzip.name.locate(this.zip,e,0)}getFileSource(e){let r=this.libzip.struct.statS();if(this.libzip.statIndex(this.zip,e,0,0,r)===-1)throw this.makeLibzipError(this.libzip.getError(this.zip));let a=this.libzip.struct.statCompSize(r),n=this.libzip.struct.statCompMethod(r),c=this.libzip.malloc(a);try{let f=this.libzip.fopenIndex(this.zip,e,0,this.libzip.ZIP_FL_COMPRESSED);if(f===0)throw this.makeLibzipError(this.libzip.getError(this.zip));try{let p=this.libzip.fread(f,c,a,0);if(p===-1)throw this.makeLibzipError(this.libzip.file.getError(f));if(p<a)throw new Error(\"Incomplete read\");if(p>a)throw new Error(\"Overread\");let h=this.libzip.HEAPU8.subarray(c,c+a);return{data:Buffer.from(h),compressionMethod:n}}finally{this.libzip.fclose(f)}}finally{this.libzip.free(c)}}deleteEntry(e){if(this.libzip.delete(this.zip,e)===-1)throw this.makeLibzipError(this.libzip.getError(this.zip))}addDirectory(e){let r=this.libzip.dir.add(this.zip,e);if(r===-1)throw this.makeLibzipError(this.libzip.getError(this.zip));return r}getBufferAndClose(){try{if(this.libzip.source.keep(this.lzSource),this.libzip.close(this.zip)===-1)throw this.makeLibzipError(this.libzip.getError(this.zip));if(this.libzip.source.open(this.lzSource)===-1)throw this.makeLibzipError(this.libzip.source.error(this.lzSource));if(this.libzip.source.seek(this.lzSource,0,0,this.libzip.SEEK_END)===-1)throw this.makeLibzipError(this.libzip.source.error(this.lzSource));let e=this.libzip.source.tell(this.lzSource);if(e===-1)throw this.makeLibzipError(this.libzip.source.error(this.lzSource));if(this.libzip.source.seek(this.lzSource,0,0,this.libzip.SEEK_SET)===-1)throw this.makeLibzipError(this.libzip.source.error(this.lzSource));let r=this.libzip.malloc(e);if(!r)throw new Error(\"Couldn't allocate enough memory\");try{let s=this.libzip.source.read(this.lzSource,r,e);if(s===-1)throw this.makeLibzipError(this.libzip.source.error(this.lzSource));if(s<e)throw new Error(\"Incomplete read\");if(s>e)throw new Error(\"Overread\");let a=Buffer.from(this.libzip.HEAPU8.subarray(r,r+e));return process.env.YARN_IS_TEST_ENV&&process.env.YARN_ZIP_DATA_EPILOGUE&&(a=Buffer.concat([a,Buffer.from(process.env.YARN_ZIP_DATA_EPILOGUE)])),a}finally{this.libzip.free(r)}}finally{this.libzip.source.close(this.lzSource),this.libzip.source.free(this.lzSource)}}allocateBuffer(e){Buffer.isBuffer(e)||(e=Buffer.from(e));let r=this.libzip.malloc(e.byteLength);if(!r)throw new Error(\"Couldn't allocate enough memory\");return new Uint8Array(this.libzip.HEAPU8.buffer,r,e.byteLength).set(e),{buffer:r,byteLength:e.byteLength}}allocateUnattachedSource(e){let r=this.libzip.struct.errorS(),{buffer:s,byteLength:a}=this.allocateBuffer(e),n=this.libzip.source.fromUnattachedBuffer(s,a,0,1,r);if(n===0)throw this.libzip.free(r),this.makeLibzipError(r);return n}allocateSource(e){let{buffer:r,byteLength:s}=this.allocateBuffer(e),a=this.libzip.source.fromBuffer(this.zip,r,s,0,1);if(a===0)throw this.libzip.free(r),this.makeLibzipError(this.libzip.getError(this.zip));return a}discard(){this.libzip.discard(this.zip)}}});function Wdt(t){if(typeof t==\"string\"&&String(+t)===t)return+t;if(typeof t==\"number\"&&Number.isFinite(t))return t<0?Date.now()/1e3:t;if(Fde.types.isDate(t))return t.getTime()/1e3;throw new Error(\"Invalid time\")}function CT(){return Buffer.from([80,75,5,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0])}var ka,Yj,Fde,Vj,om,Kj,Jj,Nde,hs,wT=Ct(()=>{bt();bt();bt();bt();bt();bt();ka=Ie(\"fs\"),Yj=Ie(\"stream\"),Fde=Ie(\"util\"),Vj=et(Ie(\"zlib\"));Wj();om=3,Kj=0,Jj=8,Nde=\"mixed\";hs=class extends Uf{constructor(r,s={}){super();this.listings=new Map;this.entries=new Map;this.fileSources=new Map;this.fds=new Map;this.nextFd=0;this.ready=!1;this.readOnly=!1;s.readOnly&&(this.readOnly=!0);let a=s;this.level=typeof a.level<\"u\"?a.level:Nde;let n=s.customZipImplementation??wI;if(typeof r==\"string\"){let{baseFs:f=new Yn}=a;this.baseFs=f,this.path=r}else this.path=null,this.baseFs=null;if(s.stats)this.stats=s.stats;else if(typeof r==\"string\")try{this.stats=this.baseFs.statSync(r)}catch(f){if(f.code===\"ENOENT\"&&a.create)this.stats=el.makeDefaultStats();else throw f}else this.stats=el.makeDefaultStats();typeof r==\"string\"?s.create?this.zipImpl=new n({buffer:CT(),readOnly:this.readOnly}):this.zipImpl=new n({path:r,baseFs:this.baseFs,readOnly:this.readOnly,size:this.stats.size}):this.zipImpl=new n({buffer:r??CT(),readOnly:this.readOnly}),this.listings.set(vt.root,new Set);let c=this.zipImpl.getListings();for(let f=0;f<c.length;f++){let p=c[f];if(K.isAbsolute(p))continue;let h=K.resolve(vt.root,p);this.registerEntry(h,f),p.endsWith(\"/\")&&this.registerListing(h)}this.symlinkCount=this.zipImpl.getSymlinkCount(),this.ready=!0}getExtractHint(r){for(let s of this.entries.keys()){let a=this.pathUtils.extname(s);if(r.relevantExtensions.has(a))return!0}return!1}getAllFiles(){return Array.from(this.entries.keys())}getRealPath(){if(!this.path)throw new Error(\"ZipFS don't have real paths when loaded from a buffer\");return this.path}prepareClose(){if(!this.ready)throw or.EBUSY(\"archive closed, close\");md(this)}getBufferAndClose(){if(this.prepareClose(),this.entries.size===0)return this.discardAndClose(),CT();try{return this.zipImpl.getBufferAndClose()}finally{this.ready=!1}}discardAndClose(){this.prepareClose(),this.zipImpl.discard(),this.ready=!1}saveAndClose(){if(!this.path||!this.baseFs)throw new Error(\"ZipFS cannot be saved and must be discarded when loaded from a buffer\");if(this.readOnly){this.discardAndClose();return}let r=this.baseFs.existsSync(this.path)||this.stats.mode===el.DEFAULT_MODE?void 0:this.stats.mode;this.baseFs.writeFileSync(this.path,this.getBufferAndClose(),{mode:r}),this.ready=!1}resolve(r){return K.resolve(vt.root,r)}async openPromise(r,s,a){return this.openSync(r,s,a)}openSync(r,s,a){let n=this.nextFd++;return this.fds.set(n,{cursor:0,p:r}),n}hasOpenFileHandles(){return!!this.fds.size}async opendirPromise(r,s){return this.opendirSync(r,s)}opendirSync(r,s={}){let a=this.resolveFilename(`opendir '${r}'`,r);if(!this.entries.has(a)&&!this.listings.has(a))throw or.ENOENT(`opendir '${r}'`);let n=this.listings.get(a);if(!n)throw or.ENOTDIR(`opendir '${r}'`);let c=[...n],f=this.openSync(a,\"r\");return lx(this,a,c,{onClose:()=>{this.closeSync(f)}})}async readPromise(r,s,a,n,c){return this.readSync(r,s,a,n,c)}readSync(r,s,a=0,n=s.byteLength,c=-1){let f=this.fds.get(r);if(typeof f>\"u\")throw or.EBADF(\"read\");let p=c===-1||c===null?f.cursor:c,h=this.readFileSync(f.p);h.copy(s,a,p,p+n);let E=Math.max(0,Math.min(h.length-p,n));return(c===-1||c===null)&&(f.cursor+=E),E}async writePromise(r,s,a,n,c){return typeof s==\"string\"?this.writeSync(r,s,c):this.writeSync(r,s,a,n,c)}writeSync(r,s,a,n,c){throw typeof this.fds.get(r)>\"u\"?or.EBADF(\"read\"):new Error(\"Unimplemented\")}async closePromise(r){return this.closeSync(r)}closeSync(r){if(typeof this.fds.get(r)>\"u\")throw or.EBADF(\"read\");this.fds.delete(r)}createReadStream(r,{encoding:s}={}){if(r===null)throw new Error(\"Unimplemented\");let a=this.openSync(r,\"r\"),n=Object.assign(new Yj.PassThrough({emitClose:!0,autoDestroy:!0,destroy:(f,p)=>{clearImmediate(c),this.closeSync(a),p(f)}}),{close(){n.destroy()},bytesRead:0,path:r,pending:!1}),c=setImmediate(async()=>{try{let f=await this.readFilePromise(r,s);n.bytesRead=f.length,n.end(f)}catch(f){n.destroy(f)}});return n}createWriteStream(r,{encoding:s}={}){if(this.readOnly)throw or.EROFS(`open '${r}'`);if(r===null)throw new Error(\"Unimplemented\");let a=[],n=this.openSync(r,\"w\"),c=Object.assign(new Yj.PassThrough({autoDestroy:!0,emitClose:!0,destroy:(f,p)=>{try{f?p(f):(this.writeFileSync(r,Buffer.concat(a),s),p(null))}catch(h){p(h)}finally{this.closeSync(n)}}}),{close(){c.destroy()},bytesWritten:0,path:r,pending:!1});return c.on(\"data\",f=>{let p=Buffer.from(f);c.bytesWritten+=p.length,a.push(p)}),c}async realpathPromise(r){return this.realpathSync(r)}realpathSync(r){let s=this.resolveFilename(`lstat '${r}'`,r);if(!this.entries.has(s)&&!this.listings.has(s))throw or.ENOENT(`lstat '${r}'`);return s}async existsPromise(r){return this.existsSync(r)}existsSync(r){if(!this.ready)throw or.EBUSY(`archive closed, existsSync '${r}'`);if(this.symlinkCount===0){let a=K.resolve(vt.root,r);return this.entries.has(a)||this.listings.has(a)}let s;try{s=this.resolveFilename(`stat '${r}'`,r,void 0,!1)}catch{return!1}return s===void 0?!1:this.entries.has(s)||this.listings.has(s)}async accessPromise(r,s){return this.accessSync(r,s)}accessSync(r,s=ka.constants.F_OK){let a=this.resolveFilename(`access '${r}'`,r);if(!this.entries.has(a)&&!this.listings.has(a))throw or.ENOENT(`access '${r}'`);if(this.readOnly&&s&ka.constants.W_OK)throw or.EROFS(`access '${r}'`)}async statPromise(r,s={bigint:!1}){return s.bigint?this.statSync(r,{bigint:!0}):this.statSync(r)}statSync(r,s={bigint:!1,throwIfNoEntry:!0}){let a=this.resolveFilename(`stat '${r}'`,r,void 0,s.throwIfNoEntry);if(a!==void 0){if(!this.entries.has(a)&&!this.listings.has(a)){if(s.throwIfNoEntry===!1)return;throw or.ENOENT(`stat '${r}'`)}if(r[r.length-1]===\"/\"&&!this.listings.has(a))throw or.ENOTDIR(`stat '${r}'`);return this.statImpl(`stat '${r}'`,a,s)}}async fstatPromise(r,s){return this.fstatSync(r,s)}fstatSync(r,s){let a=this.fds.get(r);if(typeof a>\"u\")throw or.EBADF(\"fstatSync\");let{p:n}=a,c=this.resolveFilename(`stat '${n}'`,n);if(!this.entries.has(c)&&!this.listings.has(c))throw or.ENOENT(`stat '${n}'`);if(n[n.length-1]===\"/\"&&!this.listings.has(c))throw or.ENOTDIR(`stat '${n}'`);return this.statImpl(`fstat '${n}'`,c,s)}async lstatPromise(r,s={bigint:!1}){return s.bigint?this.lstatSync(r,{bigint:!0}):this.lstatSync(r)}lstatSync(r,s={bigint:!1,throwIfNoEntry:!0}){let a=this.resolveFilename(`lstat '${r}'`,r,!1,s.throwIfNoEntry);if(a!==void 0){if(!this.entries.has(a)&&!this.listings.has(a)){if(s.throwIfNoEntry===!1)return;throw or.ENOENT(`lstat '${r}'`)}if(r[r.length-1]===\"/\"&&!this.listings.has(a))throw or.ENOTDIR(`lstat '${r}'`);return this.statImpl(`lstat '${r}'`,a,s)}}statImpl(r,s,a={}){let n=this.entries.get(s);if(typeof n<\"u\"){let c=this.zipImpl.stat(n),f=c.crc,p=c.size,h=c.mtime*1e3,E=this.stats.uid,C=this.stats.gid,S=512,P=Math.ceil(c.size/S),I=h,R=h,N=h,U=new Date(I),W=new Date(R),te=new Date(N),ie=new Date(h),Ae=this.listings.has(s)?ka.constants.S_IFDIR:this.isSymbolicLink(n)?ka.constants.S_IFLNK:ka.constants.S_IFREG,ce=Ae===ka.constants.S_IFDIR?493:420,me=Ae|this.getUnixMode(n,ce)&511,pe=Object.assign(new el.StatEntry,{uid:E,gid:C,size:p,blksize:S,blocks:P,atime:U,birthtime:W,ctime:te,mtime:ie,atimeMs:I,birthtimeMs:R,ctimeMs:N,mtimeMs:h,mode:me,crc:f});return a.bigint===!0?el.convertToBigIntStats(pe):pe}if(this.listings.has(s)){let c=this.stats.uid,f=this.stats.gid,p=0,h=512,E=0,C=this.stats.mtimeMs,S=this.stats.mtimeMs,P=this.stats.mtimeMs,I=this.stats.mtimeMs,R=new Date(C),N=new Date(S),U=new Date(P),W=new Date(I),te=ka.constants.S_IFDIR|493,Ae=Object.assign(new el.StatEntry,{uid:c,gid:f,size:p,blksize:h,blocks:E,atime:R,birthtime:N,ctime:U,mtime:W,atimeMs:C,birthtimeMs:S,ctimeMs:P,mtimeMs:I,mode:te,crc:0});return a.bigint===!0?el.convertToBigIntStats(Ae):Ae}throw new Error(\"Unreachable\")}getUnixMode(r,s){let[a,n]=this.zipImpl.getExternalAttributes(r);return a!==om?s:n>>>16}registerListing(r){let s=this.listings.get(r);if(s)return s;this.registerListing(K.dirname(r)).add(K.basename(r));let n=new Set;return this.listings.set(r,n),n}registerEntry(r,s){this.registerListing(K.dirname(r)).add(K.basename(r)),this.entries.set(r,s)}unregisterListing(r){this.listings.delete(r),this.listings.get(K.dirname(r))?.delete(K.basename(r))}unregisterEntry(r){this.unregisterListing(r);let s=this.entries.get(r);this.entries.delete(r),!(typeof s>\"u\")&&(this.fileSources.delete(s),this.isSymbolicLink(s)&&this.symlinkCount--)}deleteEntry(r,s){this.unregisterEntry(r),this.zipImpl.deleteEntry(s)}resolveFilename(r,s,a=!0,n=!0){if(!this.ready)throw or.EBUSY(`archive closed, ${r}`);let c=K.resolve(vt.root,s);if(c===\"/\")return vt.root;let f=this.entries.get(c);if(a&&f!==void 0)if(this.symlinkCount!==0&&this.isSymbolicLink(f)){let p=this.getFileSource(f).toString();return this.resolveFilename(r,K.resolve(K.dirname(c),p),!0,n)}else return c;for(;;){let p=this.resolveFilename(r,K.dirname(c),!0,n);if(p===void 0)return p;let h=this.listings.has(p),E=this.entries.has(p);if(!h&&!E){if(n===!1)return;throw or.ENOENT(r)}if(!h)throw or.ENOTDIR(r);if(c=K.resolve(p,K.basename(c)),!a||this.symlinkCount===0)break;let C=this.zipImpl.locate(c.slice(1));if(C===-1)break;if(this.isSymbolicLink(C)){let S=this.getFileSource(C).toString();c=K.resolve(K.dirname(c),S)}else break}return c}setFileSource(r,s){let a=Buffer.isBuffer(s)?s:Buffer.from(s),n=K.relative(vt.root,r),c=null;this.level!==\"mixed\"&&(c=[this.level===0?Kj:Jj,this.level]);let f=this.zipImpl.setFileSource(n,c,a);return this.fileSources.set(f,a),f}isSymbolicLink(r){if(this.symlinkCount===0)return!1;let[s,a]=this.zipImpl.getExternalAttributes(r);return s!==om?!1:(a>>>16&ka.constants.S_IFMT)===ka.constants.S_IFLNK}getFileSource(r,s={asyncDecompress:!1}){let a=this.fileSources.get(r);if(typeof a<\"u\")return a;let{data:n,compressionMethod:c}=this.zipImpl.getFileSource(r);if(c===Kj)return this.zipImpl.filesShouldBeCached&&this.fileSources.set(r,n),n;if(c===Jj){if(s.asyncDecompress)return new Promise((f,p)=>{Vj.default.inflateRaw(n,(h,E)=>{h?p(h):(this.zipImpl.filesShouldBeCached&&this.fileSources.set(r,E),f(E))})});{let f=Vj.default.inflateRawSync(n);return this.zipImpl.filesShouldBeCached&&this.fileSources.set(r,f),f}}else throw new Error(`Unsupported compression method: ${c}`)}async fchmodPromise(r,s){return this.chmodPromise(this.fdToPath(r,\"fchmod\"),s)}fchmodSync(r,s){return this.chmodSync(this.fdToPath(r,\"fchmodSync\"),s)}async chmodPromise(r,s){return this.chmodSync(r,s)}chmodSync(r,s){if(this.readOnly)throw or.EROFS(`chmod '${r}'`);s&=493;let a=this.resolveFilename(`chmod '${r}'`,r,!1),n=this.entries.get(a);if(typeof n>\"u\")throw new Error(`Assertion failed: The entry should have been registered (${a})`);let f=this.getUnixMode(n,ka.constants.S_IFREG|0)&-512|s;this.zipImpl.setExternalAttributes(n,om,f<<16)}async fchownPromise(r,s,a){return this.chownPromise(this.fdToPath(r,\"fchown\"),s,a)}fchownSync(r,s,a){return this.chownSync(this.fdToPath(r,\"fchownSync\"),s,a)}async chownPromise(r,s,a){return this.chownSync(r,s,a)}chownSync(r,s,a){throw new Error(\"Unimplemented\")}async renamePromise(r,s){return this.renameSync(r,s)}renameSync(r,s){throw new Error(\"Unimplemented\")}async copyFilePromise(r,s,a){let{indexSource:n,indexDest:c,resolvedDestP:f}=this.prepareCopyFile(r,s,a),p=await this.getFileSource(n,{asyncDecompress:!0}),h=this.setFileSource(f,p);h!==c&&this.registerEntry(f,h)}copyFileSync(r,s,a=0){let{indexSource:n,indexDest:c,resolvedDestP:f}=this.prepareCopyFile(r,s,a),p=this.getFileSource(n),h=this.setFileSource(f,p);h!==c&&this.registerEntry(f,h)}prepareCopyFile(r,s,a=0){if(this.readOnly)throw or.EROFS(`copyfile '${r} -> '${s}'`);if(a&ka.constants.COPYFILE_FICLONE_FORCE)throw or.ENOSYS(\"unsupported clone operation\",`copyfile '${r}' -> ${s}'`);let n=this.resolveFilename(`copyfile '${r} -> ${s}'`,r),c=this.entries.get(n);if(typeof c>\"u\")throw or.EINVAL(`copyfile '${r}' -> '${s}'`);let f=this.resolveFilename(`copyfile '${r}' -> ${s}'`,s),p=this.entries.get(f);if(a&(ka.constants.COPYFILE_EXCL|ka.constants.COPYFILE_FICLONE_FORCE)&&typeof p<\"u\")throw or.EEXIST(`copyfile '${r}' -> '${s}'`);return{indexSource:c,resolvedDestP:f,indexDest:p}}async appendFilePromise(r,s,a){if(this.readOnly)throw or.EROFS(`open '${r}'`);return typeof a>\"u\"?a={flag:\"a\"}:typeof a==\"string\"?a={flag:\"a\",encoding:a}:typeof a.flag>\"u\"&&(a={flag:\"a\",...a}),this.writeFilePromise(r,s,a)}appendFileSync(r,s,a={}){if(this.readOnly)throw or.EROFS(`open '${r}'`);return typeof a>\"u\"?a={flag:\"a\"}:typeof a==\"string\"?a={flag:\"a\",encoding:a}:typeof a.flag>\"u\"&&(a={flag:\"a\",...a}),this.writeFileSync(r,s,a)}fdToPath(r,s){let a=this.fds.get(r)?.p;if(typeof a>\"u\")throw or.EBADF(s);return a}async writeFilePromise(r,s,a){let{encoding:n,mode:c,index:f,resolvedP:p}=this.prepareWriteFile(r,a);f!==void 0&&typeof a==\"object\"&&a.flag&&a.flag.includes(\"a\")&&(s=Buffer.concat([await this.getFileSource(f,{asyncDecompress:!0}),Buffer.from(s)])),n!==null&&(s=s.toString(n));let h=this.setFileSource(p,s);h!==f&&this.registerEntry(p,h),c!==null&&await this.chmodPromise(p,c)}writeFileSync(r,s,a){let{encoding:n,mode:c,index:f,resolvedP:p}=this.prepareWriteFile(r,a);f!==void 0&&typeof a==\"object\"&&a.flag&&a.flag.includes(\"a\")&&(s=Buffer.concat([this.getFileSource(f),Buffer.from(s)])),n!==null&&(s=s.toString(n));let h=this.setFileSource(p,s);h!==f&&this.registerEntry(p,h),c!==null&&this.chmodSync(p,c)}prepareWriteFile(r,s){if(typeof r==\"number\"&&(r=this.fdToPath(r,\"read\")),this.readOnly)throw or.EROFS(`open '${r}'`);let a=this.resolveFilename(`open '${r}'`,r);if(this.listings.has(a))throw or.EISDIR(`open '${r}'`);let n=null,c=null;typeof s==\"string\"?n=s:typeof s==\"object\"&&({encoding:n=null,mode:c=null}=s);let f=this.entries.get(a);return{encoding:n,mode:c,resolvedP:a,index:f}}async unlinkPromise(r){return this.unlinkSync(r)}unlinkSync(r){if(this.readOnly)throw or.EROFS(`unlink '${r}'`);let s=this.resolveFilename(`unlink '${r}'`,r);if(this.listings.has(s))throw or.EISDIR(`unlink '${r}'`);let a=this.entries.get(s);if(typeof a>\"u\")throw or.EINVAL(`unlink '${r}'`);this.deleteEntry(s,a)}async utimesPromise(r,s,a){return this.utimesSync(r,s,a)}utimesSync(r,s,a){if(this.readOnly)throw or.EROFS(`utimes '${r}'`);let n=this.resolveFilename(`utimes '${r}'`,r);this.utimesImpl(n,a)}async lutimesPromise(r,s,a){return this.lutimesSync(r,s,a)}lutimesSync(r,s,a){if(this.readOnly)throw or.EROFS(`lutimes '${r}'`);let n=this.resolveFilename(`utimes '${r}'`,r,!1);this.utimesImpl(n,a)}utimesImpl(r,s){this.listings.has(r)&&(this.entries.has(r)||this.hydrateDirectory(r));let a=this.entries.get(r);if(a===void 0)throw new Error(\"Unreachable\");this.zipImpl.setMtime(a,Wdt(s))}async mkdirPromise(r,s){return this.mkdirSync(r,s)}mkdirSync(r,{mode:s=493,recursive:a=!1}={}){if(a)return this.mkdirpSync(r,{chmod:s});if(this.readOnly)throw or.EROFS(`mkdir '${r}'`);let n=this.resolveFilename(`mkdir '${r}'`,r);if(this.entries.has(n)||this.listings.has(n))throw or.EEXIST(`mkdir '${r}'`);this.hydrateDirectory(n),this.chmodSync(n,s)}async rmdirPromise(r,s){return this.rmdirSync(r,s)}rmdirSync(r,{recursive:s=!1}={}){if(this.readOnly)throw or.EROFS(`rmdir '${r}'`);if(s){this.removeSync(r);return}let a=this.resolveFilename(`rmdir '${r}'`,r),n=this.listings.get(a);if(!n)throw or.ENOTDIR(`rmdir '${r}'`);if(n.size>0)throw or.ENOTEMPTY(`rmdir '${r}'`);let c=this.entries.get(a);if(typeof c>\"u\")throw or.EINVAL(`rmdir '${r}'`);this.deleteEntry(r,c)}async rmPromise(r,s){return this.rmSync(r,s)}rmSync(r,{recursive:s=!1}={}){if(this.readOnly)throw or.EROFS(`rm '${r}'`);if(s){this.removeSync(r);return}let a=this.resolveFilename(`rm '${r}'`,r),n=this.listings.get(a);if(!n)throw or.ENOTDIR(`rm '${r}'`);if(n.size>0)throw or.ENOTEMPTY(`rm '${r}'`);let c=this.entries.get(a);if(typeof c>\"u\")throw or.EINVAL(`rm '${r}'`);this.deleteEntry(r,c)}hydrateDirectory(r){let s=this.zipImpl.addDirectory(K.relative(vt.root,r));return this.registerListing(r),this.registerEntry(r,s),s}async linkPromise(r,s){return this.linkSync(r,s)}linkSync(r,s){throw or.EOPNOTSUPP(`link '${r}' -> '${s}'`)}async symlinkPromise(r,s){return this.symlinkSync(r,s)}symlinkSync(r,s){if(this.readOnly)throw or.EROFS(`symlink '${r}' -> '${s}'`);let a=this.resolveFilename(`symlink '${r}' -> '${s}'`,s);if(this.listings.has(a))throw or.EISDIR(`symlink '${r}' -> '${s}'`);if(this.entries.has(a))throw or.EEXIST(`symlink '${r}' -> '${s}'`);let n=this.setFileSource(a,r);this.registerEntry(a,n),this.zipImpl.setExternalAttributes(n,om,(ka.constants.S_IFLNK|511)<<16),this.symlinkCount+=1}async readFilePromise(r,s){typeof s==\"object\"&&(s=s?s.encoding:void 0);let a=await this.readFileBuffer(r,{asyncDecompress:!0});return s?a.toString(s):a}readFileSync(r,s){typeof s==\"object\"&&(s=s?s.encoding:void 0);let a=this.readFileBuffer(r);return s?a.toString(s):a}readFileBuffer(r,s={asyncDecompress:!1}){typeof r==\"number\"&&(r=this.fdToPath(r,\"read\"));let a=this.resolveFilename(`open '${r}'`,r);if(!this.entries.has(a)&&!this.listings.has(a))throw or.ENOENT(`open '${r}'`);if(r[r.length-1]===\"/\"&&!this.listings.has(a))throw or.ENOTDIR(`open '${r}'`);if(this.listings.has(a))throw or.EISDIR(\"read\");let n=this.entries.get(a);if(n===void 0)throw new Error(\"Unreachable\");return this.getFileSource(n,s)}async readdirPromise(r,s){return this.readdirSync(r,s)}readdirSync(r,s){let a=this.resolveFilename(`scandir '${r}'`,r);if(!this.entries.has(a)&&!this.listings.has(a))throw or.ENOENT(`scandir '${r}'`);let n=this.listings.get(a);if(!n)throw or.ENOTDIR(`scandir '${r}'`);if(s?.recursive)if(s?.withFileTypes){let c=Array.from(n,f=>Object.assign(this.statImpl(\"lstat\",K.join(r,f)),{name:f,path:vt.dot}));for(let f of c){if(!f.isDirectory())continue;let p=K.join(f.path,f.name),h=this.listings.get(K.join(a,p));for(let E of h)c.push(Object.assign(this.statImpl(\"lstat\",K.join(r,p,E)),{name:E,path:p}))}return c}else{let c=[...n];for(let f of c){let p=this.listings.get(K.join(a,f));if(!(typeof p>\"u\"))for(let h of p)c.push(K.join(f,h))}return c}else return s?.withFileTypes?Array.from(n,c=>Object.assign(this.statImpl(\"lstat\",K.join(r,c)),{name:c,path:void 0})):[...n]}async readlinkPromise(r){let s=this.prepareReadlink(r);return(await this.getFileSource(s,{asyncDecompress:!0})).toString()}readlinkSync(r){let s=this.prepareReadlink(r);return this.getFileSource(s).toString()}prepareReadlink(r){let s=this.resolveFilename(`readlink '${r}'`,r,!1);if(!this.entries.has(s)&&!this.listings.has(s))throw or.ENOENT(`readlink '${r}'`);if(r[r.length-1]===\"/\"&&!this.listings.has(s))throw or.ENOTDIR(`open '${r}'`);if(this.listings.has(s))throw or.EINVAL(`readlink '${r}'`);let a=this.entries.get(s);if(a===void 0)throw new Error(\"Unreachable\");if(!this.isSymbolicLink(a))throw or.EINVAL(`readlink '${r}'`);return a}async truncatePromise(r,s=0){let a=this.resolveFilename(`open '${r}'`,r),n=this.entries.get(a);if(typeof n>\"u\")throw or.EINVAL(`open '${r}'`);let c=await this.getFileSource(n,{asyncDecompress:!0}),f=Buffer.alloc(s,0);return c.copy(f),await this.writeFilePromise(r,f)}truncateSync(r,s=0){let a=this.resolveFilename(`open '${r}'`,r),n=this.entries.get(a);if(typeof n>\"u\")throw or.EINVAL(`open '${r}'`);let c=this.getFileSource(n),f=Buffer.alloc(s,0);return c.copy(f),this.writeFileSync(r,f)}async ftruncatePromise(r,s){return this.truncatePromise(this.fdToPath(r,\"ftruncate\"),s)}ftruncateSync(r,s){return this.truncateSync(this.fdToPath(r,\"ftruncateSync\"),s)}watch(r,s,a){let n;switch(typeof s){case\"function\":case\"string\":case\"undefined\":n=!0;break;default:({persistent:n=!0}=s);break}if(!n)return{on:()=>{},close:()=>{}};let c=setInterval(()=>{},24*60*60*1e3);return{on:()=>{},close:()=>{clearInterval(c)}}}watchFile(r,s,a){let n=K.resolve(vt.root,r);return nE(this,n,s,a)}unwatchFile(r,s){let a=K.resolve(vt.root,r);return dd(this,a,s)}}});function Lde(t,e,r=Buffer.alloc(0),s){let a=new hs(r),n=C=>C===e||C.startsWith(`${e}/`)?C.slice(0,e.length):null,c=async(C,S)=>()=>a,f=(C,S)=>a,p={...t},h=new Yn(p),E=new r0({baseFs:h,getMountPoint:n,factoryPromise:c,factorySync:f,magicByte:21,maxAge:1/0,typeCheck:s?.typeCheck});return _2(Ode.default,new n0(E)),a}var Ode,Mde=Ct(()=>{bt();Ode=et(Ie(\"fs\"));wT()});var _de=Ct(()=>{Rde();wT();Mde()});var zj,Ev,BT,Ude=Ct(()=>{bt();wT();zj={CENTRAL_DIRECTORY:33639248,END_OF_CENTRAL_DIRECTORY:101010256},Ev=22,BT=class t{constructor(e){this.filesShouldBeCached=!1;if(\"buffer\"in e)throw new Error(\"Buffer based zip archives are not supported\");if(!e.readOnly)throw new Error(\"Writable zip archives are not supported\");this.baseFs=e.baseFs,this.fd=this.baseFs.openSync(e.path,\"r\");try{this.entries=t.readZipSync(this.fd,this.baseFs,e.size)}catch(r){throw this.baseFs.closeSync(this.fd),this.fd=\"closed\",r}}static readZipSync(e,r,s){if(s<Ev)throw new Error(\"Invalid ZIP file: EOCD not found\");let a=-1,n=Buffer.alloc(Ev);if(r.readSync(e,n,0,Ev,s-Ev),n.readUInt32LE(0)===zj.END_OF_CENTRAL_DIRECTORY)a=0;else{let R=Math.min(65557,s);n=Buffer.alloc(R),r.readSync(e,n,0,R,Math.max(0,s-R));for(let N=n.length-4;N>=0;N--)if(n.readUInt32LE(N)===zj.END_OF_CENTRAL_DIRECTORY){a=N;break}if(a===-1)throw new Error(\"Not a zip archive\")}let c=n.readUInt16LE(a+10),f=n.readUInt32LE(a+12),p=n.readUInt32LE(a+16),h=n.readUInt16LE(a+20);if(a+h+Ev>n.length)throw new Error(\"Zip archive inconsistent\");if(c==65535||f==4294967295||p==4294967295)throw new Error(\"Zip 64 is not supported\");if(f>s)throw new Error(\"Zip archive inconsistent\");if(c>f/46)throw new Error(\"Zip archive inconsistent\");let E=Buffer.alloc(f);if(r.readSync(e,E,0,E.length,p)!==E.length)throw new Error(\"Zip archive inconsistent\");let C=[],S=0,P=0,I=0;for(;P<c;){if(S+46>E.length)throw new Error(\"Zip archive inconsistent\");if(E.readUInt32LE(S)!==zj.CENTRAL_DIRECTORY)throw new Error(\"Zip archive inconsistent\");let N=E.readUInt16LE(S+4)>>>8;if(E.readUInt16LE(S+8)&1)throw new Error(\"Encrypted zip files are not supported\");let W=E.readUInt16LE(S+10),te=E.readUInt32LE(S+16),ie=E.readUInt16LE(S+28),Ae=E.readUInt16LE(S+30),ce=E.readUInt16LE(S+32),me=E.readUInt32LE(S+42),pe=E.toString(\"utf8\",S+46,S+46+ie).replaceAll(\"\\0\",\" \");if(pe.includes(\"\\0\"))throw new Error(\"Invalid ZIP file\");let Be=E.readUInt32LE(S+20),Ce=E.readUInt32LE(S+38);C.push({name:pe,os:N,mtime:fi.SAFE_TIME,crc:te,compressionMethod:W,isSymbolicLink:N===om&&(Ce>>>16&fi.S_IFMT)===fi.S_IFLNK,size:E.readUInt32LE(S+24),compressedSize:Be,externalAttributes:Ce,localHeaderOffset:me}),I+=Be,P+=1,S+=46+ie+Ae+ce}if(I>s)throw new Error(\"Zip archive inconsistent\");if(S!==E.length)throw new Error(\"Zip archive inconsistent\");return C}getExternalAttributes(e){let r=this.entries[e];return[r.os,r.externalAttributes]}getListings(){return this.entries.map(e=>e.name)}getSymlinkCount(){let e=0;for(let r of this.entries)r.isSymbolicLink&&(e+=1);return e}stat(e){let r=this.entries[e];return{crc:r.crc,mtime:r.mtime,size:r.size}}locate(e){for(let r=0;r<this.entries.length;r++)if(this.entries[r].name===e)return r;return-1}getFileSource(e){if(this.fd===\"closed\")throw new Error(\"ZIP file is closed\");let r=this.entries[e],s=Buffer.alloc(30);this.baseFs.readSync(this.fd,s,0,s.length,r.localHeaderOffset);let a=s.readUInt16LE(26),n=s.readUInt16LE(28),c=Buffer.alloc(r.compressedSize);if(this.baseFs.readSync(this.fd,c,0,r.compressedSize,r.localHeaderOffset+30+a+n)!==r.compressedSize)throw new Error(\"Invalid ZIP file\");return{data:c,compressionMethod:r.compressionMethod}}discard(){this.fd!==\"closed\"&&(this.baseFs.closeSync(this.fd),this.fd=\"closed\")}addDirectory(e){throw new Error(\"Not implemented\")}deleteEntry(e){throw new Error(\"Not implemented\")}setMtime(e,r){throw new Error(\"Not implemented\")}getBufferAndClose(){throw new Error(\"Not implemented\")}setFileSource(e,r,s){throw new Error(\"Not implemented\")}setExternalAttributes(e,r,s){throw new Error(\"Not implemented\")}}});var Iv={};Vt(Iv,{DEFAULT_COMPRESSION_LEVEL:()=>Nde,DEFLATE:()=>Jj,JsZipImpl:()=>BT,LibZipImpl:()=>wI,STORE:()=>Kj,ZIP_UNIX:()=>om,ZipFS:()=>hs,ZipOpenFS:()=>tA,getArchivePart:()=>qj,getLibzipPromise:()=>Vdt,getLibzipSync:()=>Ydt,makeEmptyArchive:()=>CT,mountMemoryDrive:()=>Lde});function Ydt(){return yv()}async function Vdt(){return yv()}var Hde,rA=Ct(()=>{Uj();Hde=et(xde());Tde();_de();Ude();Wj();Pde(()=>{let t=(0,Hde.default)();return Qde(t)})});var Cv,jde=Ct(()=>{bt();Wt();wv();Cv=class extends ot{constructor(){super(...arguments);this.cwd=ge.String(\"--cwd\",process.cwd(),{description:\"The directory to run the command in\"});this.commandName=ge.String();this.args=ge.Proxy()}static{this.usage={description:\"run a command using yarn's portable shell\",details:`\n      This command will run a command using Yarn's portable shell.\n\n      Make sure to escape glob patterns, redirections, and other features that might be expanded by your own shell.\n\n      Note: To escape something from Yarn's shell, you might have to escape it twice, the first time from your own shell.\n\n      Note: Don't use this command in Yarn scripts, as Yarn's shell is automatically used.\n\n      For a list of features, visit: https://github.com/yarnpkg/berry/blob/master/packages/yarnpkg-shell/README.md.\n    `,examples:[[\"Run a simple command\",\"$0 echo Hello\"],[\"Run a command with a glob pattern\",\"$0 echo '*.js'\"],[\"Run a command with a redirection\",\"$0 echo Hello World '>' hello.txt\"],[\"Run a command with an escaped glob pattern (The double escape is needed in Unix shells)\",`$0 echo '\"*.js\"'`],[\"Run a command with a variable (Double quotes are needed in Unix shells, to prevent them from expanding the variable)\",'$0 \"GREETING=Hello echo $GREETING World\"']]}}async execute(){let r=this.args.length>0?`${this.commandName} ${this.args.join(\" \")}`:this.commandName;return await BI(r,[],{cwd:ue.toPortablePath(this.cwd),stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr})}}});var Kl,qde=Ct(()=>{Kl=class extends Error{constructor(e){super(e),this.name=\"ShellError\"}}});var DT={};Vt(DT,{fastGlobOptions:()=>Yde,isBraceExpansion:()=>Zj,isGlobPattern:()=>Kdt,match:()=>Jdt,micromatchOptions:()=>ST});function Kdt(t){if(!vT.default.scan(t,ST).isGlob)return!1;try{vT.default.parse(t,ST)}catch{return!1}return!0}function Jdt(t,{cwd:e,baseFs:r}){return(0,Gde.default)(t,{...Yde,cwd:ue.fromPortablePath(e),fs:gx(Wde.default,new n0(r))})}function Zj(t){return vT.default.scan(t,ST).isBrace}var Gde,Wde,vT,ST,Yde,Vde=Ct(()=>{bt();Gde=et(CQ()),Wde=et(Ie(\"fs\")),vT=et(Sa()),ST={strictBrackets:!0},Yde={onlyDirectories:!1,onlyFiles:!1}});function Xj(){}function $j(){for(let t of am)t.kill()}function Zde(t,e,r,s){return a=>{let n=a[0]instanceof nA.Transform?\"pipe\":a[0],c=a[1]instanceof nA.Transform?\"pipe\":a[1],f=a[2]instanceof nA.Transform?\"pipe\":a[2],p=(0,Jde.default)(t,e,{...s,stdio:[n,c,f]});return am.add(p),am.size===1&&(process.on(\"SIGINT\",Xj),process.on(\"SIGTERM\",$j)),a[0]instanceof nA.Transform&&a[0].pipe(p.stdin),a[1]instanceof nA.Transform&&p.stdout.pipe(a[1],{end:!1}),a[2]instanceof nA.Transform&&p.stderr.pipe(a[2],{end:!1}),{stdin:p.stdin,promise:new Promise(h=>{p.on(\"error\",E=>{switch(am.delete(p),am.size===0&&(process.off(\"SIGINT\",Xj),process.off(\"SIGTERM\",$j)),E.code){case\"ENOENT\":a[2].write(`command not found: ${t}\n`),h(127);break;case\"EACCES\":a[2].write(`permission denied: ${t}\n`),h(128);break;default:a[2].write(`uncaught error: ${E.message}\n`),h(1);break}}),p.on(\"close\",E=>{am.delete(p),am.size===0&&(process.off(\"SIGINT\",Xj),process.off(\"SIGTERM\",$j)),h(E!==null?E:129)})})}}}function Xde(t){return e=>{let r=e[0]===\"pipe\"?new nA.PassThrough:e[0];return{stdin:r,promise:Promise.resolve().then(()=>t({stdin:r,stdout:e[1],stderr:e[2]}))}}}function bT(t,e){return t6.start(t,e)}function Kde(t,e=null){let r=new nA.PassThrough,s=new zde.StringDecoder,a=\"\";return r.on(\"data\",n=>{let c=s.write(n),f;do if(f=c.indexOf(`\n`),f!==-1){let p=a+c.substring(0,f);c=c.substring(f+1),a=\"\",t(e!==null?`${e} ${p}`:p)}while(f!==-1);a+=c}),r.on(\"end\",()=>{let n=s.end();n!==\"\"&&t(e!==null?`${e} ${n}`:n)}),r}function $de(t,{prefix:e}){return{stdout:Kde(r=>t.stdout.write(`${r}\n`),t.stdout.isTTY?e:null),stderr:Kde(r=>t.stderr.write(`${r}\n`),t.stderr.isTTY?e:null)}}var Jde,nA,zde,am,Mc,e6,t6,r6=Ct(()=>{Jde=et(j_()),nA=Ie(\"stream\"),zde=Ie(\"string_decoder\"),am=new Set;Mc=class{constructor(e){this.stream=e}close(){}get(){return this.stream}},e6=class{constructor(){this.stream=null}close(){if(this.stream===null)throw new Error(\"Assertion failed: No stream attached\");this.stream.end()}attach(e){this.stream=e}get(){if(this.stream===null)throw new Error(\"Assertion failed: No stream attached\");return this.stream}},t6=class t{constructor(e,r){this.stdin=null;this.stdout=null;this.stderr=null;this.pipe=null;this.ancestor=e,this.implementation=r}static start(e,{stdin:r,stdout:s,stderr:a}){let n=new t(null,e);return n.stdin=r,n.stdout=s,n.stderr=a,n}pipeTo(e,r=1){let s=new t(this,e),a=new e6;return s.pipe=a,s.stdout=this.stdout,s.stderr=this.stderr,(r&1)===1?this.stdout=a:this.ancestor!==null&&(this.stderr=this.ancestor.stdout),(r&2)===2?this.stderr=a:this.ancestor!==null&&(this.stderr=this.ancestor.stderr),s}async exec(){let e=[\"ignore\",\"ignore\",\"ignore\"];if(this.pipe)e[0]=\"pipe\";else{if(this.stdin===null)throw new Error(\"Assertion failed: No input stream registered\");e[0]=this.stdin.get()}let r;if(this.stdout===null)throw new Error(\"Assertion failed: No output stream registered\");r=this.stdout,e[1]=r.get();let s;if(this.stderr===null)throw new Error(\"Assertion failed: No error stream registered\");s=this.stderr,e[2]=s.get();let a=this.implementation(e);return this.pipe&&this.pipe.attach(a.stdin),await a.promise.then(n=>(r.close(),s.close(),n))}async run(){let e=[];for(let s=this;s;s=s.ancestor)e.push(s.exec());return(await Promise.all(e))[0]}}});var Dv={};Vt(Dv,{EntryCommand:()=>Cv,ShellError:()=>Kl,execute:()=>BI,globUtils:()=>DT});function eme(t,e,r){let s=new Jl.PassThrough({autoDestroy:!0});switch(t){case 0:(e&1)===1&&r.stdin.pipe(s,{end:!1}),(e&2)===2&&r.stdin instanceof Jl.Writable&&s.pipe(r.stdin,{end:!1});break;case 1:(e&1)===1&&r.stdout.pipe(s,{end:!1}),(e&2)===2&&s.pipe(r.stdout,{end:!1});break;case 2:(e&1)===1&&r.stderr.pipe(s,{end:!1}),(e&2)===2&&s.pipe(r.stderr,{end:!1});break;default:throw new Kl(`Bad file descriptor: \"${t}\"`)}return s}function xT(t,e={}){let r={...t,...e};return r.environment={...t.environment,...e.environment},r.variables={...t.variables,...e.variables},r}async function Zdt(t,e,r){let s=[],a=new Jl.PassThrough;return a.on(\"data\",n=>s.push(n)),await kT(t,e,xT(r,{stdout:a})),Buffer.concat(s).toString().replace(/[\\r\\n]+$/,\"\")}async function tme(t,e,r){let s=t.map(async n=>{let c=await lm(n.args,e,r);return{name:n.name,value:c.join(\" \")}});return(await Promise.all(s)).reduce((n,c)=>(n[c.name]=c.value,n),{})}function PT(t){return t.match(/[^ \\r\\n\\t]+/g)||[]}async function ame(t,e,r,s,a=s){switch(t.name){case\"$\":s(String(process.pid));break;case\"#\":s(String(e.args.length));break;case\"@\":if(t.quoted)for(let n of e.args)a(n);else for(let n of e.args){let c=PT(n);for(let f=0;f<c.length-1;++f)a(c[f]);s(c[c.length-1])}break;case\"*\":{let n=e.args.join(\" \");if(t.quoted)s(n);else for(let c of PT(n))a(c)}break;case\"PPID\":s(String(process.ppid));break;case\"RANDOM\":s(String(Math.floor(Math.random()*32768)));break;default:{let n=parseInt(t.name,10),c,f=Number.isFinite(n);if(f?n>=0&&n<e.args.length&&(c=e.args[n]):Object.hasOwn(r.variables,t.name)?c=r.variables[t.name]:Object.hasOwn(r.environment,t.name)&&(c=r.environment[t.name]),typeof c<\"u\"&&t.alternativeValue?c=(await lm(t.alternativeValue,e,r)).join(\" \"):typeof c>\"u\"&&(t.defaultValue?c=(await lm(t.defaultValue,e,r)).join(\" \"):t.alternativeValue&&(c=\"\")),typeof c>\"u\")throw f?new Kl(`Unbound argument #${n}`):new Kl(`Unbound variable \"${t.name}\"`);if(t.quoted)s(c);else{let p=PT(c);for(let E=0;E<p.length-1;++E)a(p[E]);let h=p[p.length-1];typeof h<\"u\"&&s(h)}}break}}async function Bv(t,e,r){if(t.type===\"number\"){if(Number.isInteger(t.value))return t.value;throw new Error(`Invalid number: \"${t.value}\", only integers are allowed`)}else if(t.type===\"variable\"){let s=[];await ame({...t,quoted:!0},e,r,n=>s.push(n));let a=Number(s.join(\" \"));return Number.isNaN(a)?Bv({type:\"variable\",name:s.join(\" \")},e,r):Bv({type:\"number\",value:a},e,r)}else return Xdt[t.type](await Bv(t.left,e,r),await Bv(t.right,e,r))}async function lm(t,e,r){let s=new Map,a=[],n=[],c=E=>{n.push(E)},f=()=>{n.length>0&&a.push(n.join(\"\")),n=[]},p=E=>{c(E),f()},h=(E,C,S)=>{let P=JSON.stringify({type:E,fd:C}),I=s.get(P);typeof I>\"u\"&&s.set(P,I=[]),I.push(S)};for(let E of t){let C=!1;switch(E.type){case\"redirection\":{let S=await lm(E.args,e,r);for(let P of S)h(E.subtype,E.fd,P)}break;case\"argument\":for(let S of E.segments)switch(S.type){case\"text\":c(S.text);break;case\"glob\":c(S.pattern),C=!0;break;case\"shell\":{let P=await Zdt(S.shell,e,r);if(S.quoted)c(P);else{let I=PT(P);for(let R=0;R<I.length-1;++R)p(I[R]);c(I[I.length-1])}}break;case\"variable\":await ame(S,e,r,c,p);break;case\"arithmetic\":c(String(await Bv(S.arithmetic,e,r)));break}break}if(f(),C){let S=a.pop();if(typeof S>\"u\")throw new Error(\"Assertion failed: Expected a glob pattern to have been set\");let P=await e.glob.match(S,{cwd:r.cwd,baseFs:e.baseFs});if(P.length===0){let I=Zj(S)?\". Note: Brace expansion of arbitrary strings isn't currently supported. For more details, please read this issue: https://github.com/yarnpkg/berry/issues/22\":\"\";throw new Kl(`No matches found: \"${S}\"${I}`)}for(let I of P.sort())p(I)}}if(s.size>0){let E=[];for(let[C,S]of s.entries())E.splice(E.length,0,C,String(S.length),...S);a.splice(0,0,\"__ysh_set_redirects\",...E,\"--\")}return a}function vv(t,e,r){e.builtins.has(t[0])||(t=[\"command\",...t]);let s=ue.fromPortablePath(r.cwd),a=r.environment;typeof a.PWD<\"u\"&&(a={...a,PWD:s});let[n,...c]=t;if(n===\"command\")return Zde(c[0],c.slice(1),e,{cwd:s,env:a});let f=e.builtins.get(n);if(typeof f>\"u\")throw new Error(`Assertion failed: A builtin should exist for \"${n}\"`);return Xde(async({stdin:p,stdout:h,stderr:E})=>{let{stdin:C,stdout:S,stderr:P}=r;r.stdin=p,r.stdout=h,r.stderr=E;try{return await f(c,e,r)}finally{r.stdin=C,r.stdout=S,r.stderr=P}})}function $dt(t,e,r){return s=>{let a=new Jl.PassThrough,n=kT(t,e,xT(r,{stdin:a}));return{stdin:a,promise:n}}}function emt(t,e,r){return s=>{let a=new Jl.PassThrough,n=kT(t,e,r);return{stdin:a,promise:n}}}function rme(t,e,r,s){if(e.length===0)return t;{let a;do a=String(Math.random());while(Object.hasOwn(s.procedures,a));return s.procedures={...s.procedures},s.procedures[a]=t,vv([...e,\"__ysh_run_procedure\",a],r,s)}}async function nme(t,e,r){let s=t,a=null,n=null;for(;s;){let c=s.then?{...r}:r,f;switch(s.type){case\"command\":{let p=await lm(s.args,e,r),h=await tme(s.envs,e,r);f=s.envs.length?vv(p,e,xT(c,{environment:h})):vv(p,e,c)}break;case\"subshell\":{let p=await lm(s.args,e,r),h=$dt(s.subshell,e,c);f=rme(h,p,e,c)}break;case\"group\":{let p=await lm(s.args,e,r),h=emt(s.group,e,c);f=rme(h,p,e,c)}break;case\"envs\":{let p=await tme(s.envs,e,r);c.environment={...c.environment,...p},f=vv([\"true\"],e,c)}break}if(typeof f>\"u\")throw new Error(\"Assertion failed: An action should have been generated\");if(a===null)n=bT(f,{stdin:new Mc(c.stdin),stdout:new Mc(c.stdout),stderr:new Mc(c.stderr)});else{if(n===null)throw new Error(\"Assertion failed: The execution pipeline should have been setup\");switch(a){case\"|\":n=n.pipeTo(f,1);break;case\"|&\":n=n.pipeTo(f,3);break}}s.then?(a=s.then.type,s=s.then.chain):s=null}if(n===null)throw new Error(\"Assertion failed: The execution pipeline should have been setup\");return await n.run()}async function tmt(t,e,r,{background:s=!1}={}){function a(n){let c=[\"#2E86AB\",\"#A23B72\",\"#F18F01\",\"#C73E1D\",\"#CCE2A3\"],f=c[n%c.length];return ime.default.hex(f)}if(s){let n=r.nextBackgroundJobIndex++,c=a(n),f=`[${n}]`,p=c(f),{stdout:h,stderr:E}=$de(r,{prefix:p});return r.backgroundJobs.push(nme(t,e,xT(r,{stdout:h,stderr:E})).catch(C=>E.write(`${C.message}\n`)).finally(()=>{r.stdout.isTTY&&r.stdout.write(`Job ${p}, '${c(uE(t))}' has ended\n`)})),0}return await nme(t,e,r)}async function rmt(t,e,r,{background:s=!1}={}){let a,n=f=>{a=f,r.variables[\"?\"]=String(f)},c=async f=>{try{return await tmt(f.chain,e,r,{background:s&&typeof f.then>\"u\"})}catch(p){if(!(p instanceof Kl))throw p;return r.stderr.write(`${p.message}\n`),1}};for(n(await c(t));t.then;){if(r.exitCode!==null)return r.exitCode;switch(t.then.type){case\"&&\":a===0&&n(await c(t.then.line));break;case\"||\":a!==0&&n(await c(t.then.line));break;default:throw new Error(`Assertion failed: Unsupported command type: \"${t.then.type}\"`)}t=t.then.line}return a}async function kT(t,e,r){let s=r.backgroundJobs;r.backgroundJobs=[];let a=0;for(let{command:n,type:c}of t){if(a=await rmt(n,e,r,{background:c===\"&\"}),r.exitCode!==null)return r.exitCode;r.variables[\"?\"]=String(a)}return await Promise.all(r.backgroundJobs),r.backgroundJobs=s,a}function lme(t){switch(t.type){case\"variable\":return t.name===\"@\"||t.name===\"#\"||t.name===\"*\"||Number.isFinite(parseInt(t.name,10))||\"defaultValue\"in t&&!!t.defaultValue&&t.defaultValue.some(e=>Sv(e))||\"alternativeValue\"in t&&!!t.alternativeValue&&t.alternativeValue.some(e=>Sv(e));case\"arithmetic\":return n6(t.arithmetic);case\"shell\":return i6(t.shell);default:return!1}}function Sv(t){switch(t.type){case\"redirection\":return t.args.some(e=>Sv(e));case\"argument\":return t.segments.some(e=>lme(e));default:throw new Error(`Assertion failed: Unsupported argument type: \"${t.type}\"`)}}function n6(t){switch(t.type){case\"variable\":return lme(t);case\"number\":return!1;default:return n6(t.left)||n6(t.right)}}function i6(t){return t.some(({command:e})=>{for(;e;){let r=e.chain;for(;r;){let s;switch(r.type){case\"subshell\":s=i6(r.subshell);break;case\"command\":s=r.envs.some(a=>a.args.some(n=>Sv(n)))||r.args.some(a=>Sv(a));break}if(s)return!0;if(!r.then)break;r=r.then.chain}if(!e.then)break;e=e.then.line}return!1})}async function BI(t,e=[],{baseFs:r=new Yn,builtins:s={},cwd:a=ue.toPortablePath(process.cwd()),env:n=process.env,stdin:c=process.stdin,stdout:f=process.stdout,stderr:p=process.stderr,variables:h={},glob:E=DT}={}){let C={};for(let[I,R]of Object.entries(n))typeof R<\"u\"&&(C[I]=R);let S=new Map(zdt);for(let[I,R]of Object.entries(s))S.set(I,R);c===null&&(c=new Jl.PassThrough,c.end());let P=yx(t,E);if(!i6(P)&&P.length>0&&e.length>0){let{command:I}=P[P.length-1];for(;I.then;)I=I.then.line;let R=I.chain;for(;R.then;)R=R.then.chain;R.type===\"command\"&&(R.args=R.args.concat(e.map(N=>({type:\"argument\",segments:[{type:\"text\",text:N}]}))))}return await kT(P,{args:e,baseFs:r,builtins:S,initialStdin:c,initialStdout:f,initialStderr:p,glob:E},{cwd:a,environment:C,exitCode:null,procedures:{},stdin:c,stdout:f,stderr:p,variables:Object.assign({},h,{\"?\":0}),nextBackgroundJobIndex:1,backgroundJobs:[]})}var ime,sme,Jl,ome,zdt,Xdt,wv=Ct(()=>{bt();Bc();ime=et(kE()),sme=Ie(\"os\"),Jl=Ie(\"stream\"),ome=Ie(\"timers/promises\");jde();qde();Vde();r6();r6();zdt=new Map([[\"cd\",async([t=(0,sme.homedir)(),...e],r,s)=>{let a=K.resolve(s.cwd,ue.toPortablePath(t));if(!(await r.baseFs.statPromise(a).catch(c=>{throw c.code===\"ENOENT\"?new Kl(`cd: no such file or directory: ${t}`):c})).isDirectory())throw new Kl(`cd: not a directory: ${t}`);return s.cwd=a,0}],[\"pwd\",async(t,e,r)=>(r.stdout.write(`${ue.fromPortablePath(r.cwd)}\n`),0)],[\":\",async(t,e,r)=>0],[\"true\",async(t,e,r)=>0],[\"false\",async(t,e,r)=>1],[\"exit\",async([t,...e],r,s)=>s.exitCode=parseInt(t??s.variables[\"?\"],10)],[\"echo\",async(t,e,r)=>(r.stdout.write(`${t.join(\" \")}\n`),0)],[\"sleep\",async([t],e,r)=>{if(typeof t>\"u\")throw new Kl(\"sleep: missing operand\");let s=Number(t);if(Number.isNaN(s))throw new Kl(`sleep: invalid time interval '${t}'`);return await(0,ome.setTimeout)(1e3*s,0)}],[\"unset\",async(t,e,r)=>{for(let s of t)delete r.environment[s],delete r.variables[s];return 0}],[\"__ysh_run_procedure\",async(t,e,r)=>{let s=r.procedures[t[0]];return await bT(s,{stdin:new Mc(r.stdin),stdout:new Mc(r.stdout),stderr:new Mc(r.stderr)}).run()}],[\"__ysh_set_redirects\",async(t,e,r)=>{let s=r.stdin,a=r.stdout,n=r.stderr,c=[],f=[],p=[],h=0;for(;t[h]!==\"--\";){let C=t[h++],{type:S,fd:P}=JSON.parse(C),I=W=>{switch(P){case null:case 0:c.push(W);break;default:throw new Error(`Unsupported file descriptor: \"${P}\"`)}},R=W=>{switch(P){case null:case 1:f.push(W);break;case 2:p.push(W);break;default:throw new Error(`Unsupported file descriptor: \"${P}\"`)}},N=Number(t[h++]),U=h+N;for(let W=h;W<U;++h,++W)switch(S){case\"<\":I(()=>e.baseFs.createReadStream(K.resolve(r.cwd,ue.toPortablePath(t[W]))));break;case\"<<<\":I(()=>{let te=new Jl.PassThrough;return process.nextTick(()=>{te.write(`${t[W]}\n`),te.end()}),te});break;case\"<&\":I(()=>eme(Number(t[W]),1,r));break;case\">\":case\">>\":{let te=K.resolve(r.cwd,ue.toPortablePath(t[W]));R(te===\"/dev/null\"?new Jl.Writable({autoDestroy:!0,emitClose:!0,write(ie,Ae,ce){setImmediate(ce)}}):e.baseFs.createWriteStream(te,S===\">>\"?{flags:\"a\"}:void 0))}break;case\">&\":R(eme(Number(t[W]),2,r));break;default:throw new Error(`Assertion failed: Unsupported redirection type: \"${S}\"`)}}if(c.length>0){let C=new Jl.PassThrough;s=C;let S=P=>{if(P===c.length)C.end();else{let I=c[P]();I.pipe(C,{end:!1}),I.on(\"end\",()=>{S(P+1)})}};S(0)}if(f.length>0){let C=new Jl.PassThrough;a=C;for(let S of f)C.pipe(S)}if(p.length>0){let C=new Jl.PassThrough;n=C;for(let S of p)C.pipe(S)}let E=await bT(vv(t.slice(h+1),e,r),{stdin:new Mc(s),stdout:new Mc(a),stderr:new Mc(n)}).run();return await Promise.all(f.map(C=>new Promise((S,P)=>{C.on(\"error\",I=>{P(I)}),C.on(\"close\",()=>{S()}),C.end()}))),await Promise.all(p.map(C=>new Promise((S,P)=>{C.on(\"error\",I=>{P(I)}),C.on(\"close\",()=>{S()}),C.end()}))),E}]]);Xdt={addition:(t,e)=>t+e,subtraction:(t,e)=>t-e,multiplication:(t,e)=>t*e,division:(t,e)=>Math.trunc(t/e)}});var QT=L((DXt,cme)=>{function nmt(t,e){for(var r=-1,s=t==null?0:t.length,a=Array(s);++r<s;)a[r]=e(t[r],r,t);return a}cme.exports=nmt});var gme=L((bXt,hme)=>{var ume=Gd(),imt=QT(),smt=xc(),omt=oI(),amt=1/0,fme=ume?ume.prototype:void 0,Ame=fme?fme.toString:void 0;function pme(t){if(typeof t==\"string\")return t;if(smt(t))return imt(t,pme)+\"\";if(omt(t))return Ame?Ame.call(t):\"\";var e=t+\"\";return e==\"0\"&&1/t==-amt?\"-0\":e}hme.exports=pme});var bv=L((PXt,dme)=>{var lmt=gme();function cmt(t){return t==null?\"\":lmt(t)}dme.exports=cmt});var s6=L((xXt,mme)=>{function umt(t,e,r){var s=-1,a=t.length;e<0&&(e=-e>a?0:a+e),r=r>a?a:r,r<0&&(r+=a),a=e>r?0:r-e>>>0,e>>>=0;for(var n=Array(a);++s<a;)n[s]=t[s+e];return n}mme.exports=umt});var Eme=L((kXt,yme)=>{var fmt=s6();function Amt(t,e,r){var s=t.length;return r=r===void 0?s:r,!e&&r>=s?t:fmt(t,e,r)}yme.exports=Amt});var o6=L((QXt,Ime)=>{var pmt=\"\\\\ud800-\\\\udfff\",hmt=\"\\\\u0300-\\\\u036f\",gmt=\"\\\\ufe20-\\\\ufe2f\",dmt=\"\\\\u20d0-\\\\u20ff\",mmt=hmt+gmt+dmt,ymt=\"\\\\ufe0e\\\\ufe0f\",Emt=\"\\\\u200d\",Imt=RegExp(\"[\"+Emt+pmt+mmt+ymt+\"]\");function Cmt(t){return Imt.test(t)}Ime.exports=Cmt});var wme=L((TXt,Cme)=>{function wmt(t){return t.split(\"\")}Cme.exports=wmt});var kme=L((RXt,xme)=>{var Bme=\"\\\\ud800-\\\\udfff\",Bmt=\"\\\\u0300-\\\\u036f\",vmt=\"\\\\ufe20-\\\\ufe2f\",Smt=\"\\\\u20d0-\\\\u20ff\",Dmt=Bmt+vmt+Smt,bmt=\"\\\\ufe0e\\\\ufe0f\",Pmt=\"[\"+Bme+\"]\",a6=\"[\"+Dmt+\"]\",l6=\"\\\\ud83c[\\\\udffb-\\\\udfff]\",xmt=\"(?:\"+a6+\"|\"+l6+\")\",vme=\"[^\"+Bme+\"]\",Sme=\"(?:\\\\ud83c[\\\\udde6-\\\\uddff]){2}\",Dme=\"[\\\\ud800-\\\\udbff][\\\\udc00-\\\\udfff]\",kmt=\"\\\\u200d\",bme=xmt+\"?\",Pme=\"[\"+bmt+\"]?\",Qmt=\"(?:\"+kmt+\"(?:\"+[vme,Sme,Dme].join(\"|\")+\")\"+Pme+bme+\")*\",Tmt=Pme+bme+Qmt,Rmt=\"(?:\"+[vme+a6+\"?\",a6,Sme,Dme,Pmt].join(\"|\")+\")\",Fmt=RegExp(l6+\"(?=\"+l6+\")|\"+Rmt+Tmt,\"g\");function Nmt(t){return t.match(Fmt)||[]}xme.exports=Nmt});var Tme=L((FXt,Qme)=>{var Omt=wme(),Lmt=o6(),Mmt=kme();function _mt(t){return Lmt(t)?Mmt(t):Omt(t)}Qme.exports=_mt});var Fme=L((NXt,Rme)=>{var Umt=Eme(),Hmt=o6(),jmt=Tme(),qmt=bv();function Gmt(t){return function(e){e=qmt(e);var r=Hmt(e)?jmt(e):void 0,s=r?r[0]:e.charAt(0),a=r?Umt(r,1).join(\"\"):e.slice(1);return s[t]()+a}}Rme.exports=Gmt});var Ome=L((OXt,Nme)=>{var Wmt=Fme(),Ymt=Wmt(\"toUpperCase\");Nme.exports=Ymt});var c6=L((LXt,Lme)=>{var Vmt=bv(),Kmt=Ome();function Jmt(t){return Kmt(Vmt(t).toLowerCase())}Lme.exports=Jmt});var Mme=L((MXt,TT)=>{function zmt(){var t=0,e=1,r=2,s=3,a=4,n=5,c=6,f=7,p=8,h=9,E=10,C=11,S=12,P=13,I=14,R=15,N=16,U=17,W=0,te=1,ie=2,Ae=3,ce=4;function me(g,we){return 55296<=g.charCodeAt(we)&&g.charCodeAt(we)<=56319&&56320<=g.charCodeAt(we+1)&&g.charCodeAt(we+1)<=57343}function pe(g,we){we===void 0&&(we=0);var ye=g.charCodeAt(we);if(55296<=ye&&ye<=56319&&we<g.length-1){var fe=ye,se=g.charCodeAt(we+1);return 56320<=se&&se<=57343?(fe-55296)*1024+(se-56320)+65536:fe}if(56320<=ye&&ye<=57343&&we>=1){var fe=g.charCodeAt(we-1),se=ye;return 55296<=fe&&fe<=56319?(fe-55296)*1024+(se-56320)+65536:se}return ye}function Be(g,we,ye){var fe=[g].concat(we).concat([ye]),se=fe[fe.length-2],X=ye,De=fe.lastIndexOf(I);if(De>1&&fe.slice(1,De).every(function(j){return j==s})&&[s,P,U].indexOf(g)==-1)return ie;var Re=fe.lastIndexOf(a);if(Re>0&&fe.slice(1,Re).every(function(j){return j==a})&&[S,a].indexOf(se)==-1)return fe.filter(function(j){return j==a}).length%2==1?Ae:ce;if(se==t&&X==e)return W;if(se==r||se==t||se==e)return X==I&&we.every(function(j){return j==s})?ie:te;if(X==r||X==t||X==e)return te;if(se==c&&(X==c||X==f||X==h||X==E))return W;if((se==h||se==f)&&(X==f||X==p))return W;if((se==E||se==p)&&X==p)return W;if(X==s||X==R)return W;if(X==n)return W;if(se==S)return W;var dt=fe.indexOf(s)!=-1?fe.lastIndexOf(s)-1:fe.length-2;return[P,U].indexOf(fe[dt])!=-1&&fe.slice(dt+1,-1).every(function(j){return j==s})&&X==I||se==R&&[N,U].indexOf(X)!=-1?W:we.indexOf(a)!=-1?ie:se==a&&X==a?W:te}this.nextBreak=function(g,we){if(we===void 0&&(we=0),we<0)return 0;if(we>=g.length-1)return g.length;for(var ye=Ce(pe(g,we)),fe=[],se=we+1;se<g.length;se++)if(!me(g,se-1)){var X=Ce(pe(g,se));if(Be(ye,fe,X))return se;fe.push(X)}return g.length},this.splitGraphemes=function(g){for(var we=[],ye=0,fe;(fe=this.nextBreak(g,ye))<g.length;)we.push(g.slice(ye,fe)),ye=fe;return ye<g.length&&we.push(g.slice(ye)),we},this.iterateGraphemes=function(g){var we=0,ye={next:function(){var fe,se;return(se=this.nextBreak(g,we))<g.length?(fe=g.slice(we,se),we=se,{value:fe,done:!1}):we<g.length?(fe=g.slice(we),we=g.length,{value:fe,done:!1}):{value:void 0,done:!0}}.bind(this)};return typeof Symbol<\"u\"&&Symbol.iterator&&(ye[Symbol.iterator]=function(){return ye}),ye},this.countGraphemes=function(g){for(var we=0,ye=0,fe;(fe=this.nextBreak(g,ye))<g.length;)ye=fe,we++;return ye<g.length&&we++,we};function Ce(g){return 1536<=g&&g<=1541||g==1757||g==1807||g==2274||g==3406||g==69821||70082<=g&&g<=70083||g==72250||72326<=g&&g<=72329||g==73030?S:g==13?t:g==10?e:0<=g&&g<=9||11<=g&&g<=12||14<=g&&g<=31||127<=g&&g<=159||g==173||g==1564||g==6158||g==8203||8206<=g&&g<=8207||g==8232||g==8233||8234<=g&&g<=8238||8288<=g&&g<=8292||g==8293||8294<=g&&g<=8303||55296<=g&&g<=57343||g==65279||65520<=g&&g<=65528||65529<=g&&g<=65531||113824<=g&&g<=113827||119155<=g&&g<=119162||g==917504||g==917505||917506<=g&&g<=917535||917632<=g&&g<=917759||918e3<=g&&g<=921599?r:768<=g&&g<=879||1155<=g&&g<=1159||1160<=g&&g<=1161||1425<=g&&g<=1469||g==1471||1473<=g&&g<=1474||1476<=g&&g<=1477||g==1479||1552<=g&&g<=1562||1611<=g&&g<=1631||g==1648||1750<=g&&g<=1756||1759<=g&&g<=1764||1767<=g&&g<=1768||1770<=g&&g<=1773||g==1809||1840<=g&&g<=1866||1958<=g&&g<=1968||2027<=g&&g<=2035||2070<=g&&g<=2073||2075<=g&&g<=2083||2085<=g&&g<=2087||2089<=g&&g<=2093||2137<=g&&g<=2139||2260<=g&&g<=2273||2275<=g&&g<=2306||g==2362||g==2364||2369<=g&&g<=2376||g==2381||2385<=g&&g<=2391||2402<=g&&g<=2403||g==2433||g==2492||g==2494||2497<=g&&g<=2500||g==2509||g==2519||2530<=g&&g<=2531||2561<=g&&g<=2562||g==2620||2625<=g&&g<=2626||2631<=g&&g<=2632||2635<=g&&g<=2637||g==2641||2672<=g&&g<=2673||g==2677||2689<=g&&g<=2690||g==2748||2753<=g&&g<=2757||2759<=g&&g<=2760||g==2765||2786<=g&&g<=2787||2810<=g&&g<=2815||g==2817||g==2876||g==2878||g==2879||2881<=g&&g<=2884||g==2893||g==2902||g==2903||2914<=g&&g<=2915||g==2946||g==3006||g==3008||g==3021||g==3031||g==3072||3134<=g&&g<=3136||3142<=g&&g<=3144||3146<=g&&g<=3149||3157<=g&&g<=3158||3170<=g&&g<=3171||g==3201||g==3260||g==3263||g==3266||g==3270||3276<=g&&g<=3277||3285<=g&&g<=3286||3298<=g&&g<=3299||3328<=g&&g<=3329||3387<=g&&g<=3388||g==3390||3393<=g&&g<=3396||g==3405||g==3415||3426<=g&&g<=3427||g==3530||g==3535||3538<=g&&g<=3540||g==3542||g==3551||g==3633||3636<=g&&g<=3642||3655<=g&&g<=3662||g==3761||3764<=g&&g<=3769||3771<=g&&g<=3772||3784<=g&&g<=3789||3864<=g&&g<=3865||g==3893||g==3895||g==3897||3953<=g&&g<=3966||3968<=g&&g<=3972||3974<=g&&g<=3975||3981<=g&&g<=3991||3993<=g&&g<=4028||g==4038||4141<=g&&g<=4144||4146<=g&&g<=4151||4153<=g&&g<=4154||4157<=g&&g<=4158||4184<=g&&g<=4185||4190<=g&&g<=4192||4209<=g&&g<=4212||g==4226||4229<=g&&g<=4230||g==4237||g==4253||4957<=g&&g<=4959||5906<=g&&g<=5908||5938<=g&&g<=5940||5970<=g&&g<=5971||6002<=g&&g<=6003||6068<=g&&g<=6069||6071<=g&&g<=6077||g==6086||6089<=g&&g<=6099||g==6109||6155<=g&&g<=6157||6277<=g&&g<=6278||g==6313||6432<=g&&g<=6434||6439<=g&&g<=6440||g==6450||6457<=g&&g<=6459||6679<=g&&g<=6680||g==6683||g==6742||6744<=g&&g<=6750||g==6752||g==6754||6757<=g&&g<=6764||6771<=g&&g<=6780||g==6783||6832<=g&&g<=6845||g==6846||6912<=g&&g<=6915||g==6964||6966<=g&&g<=6970||g==6972||g==6978||7019<=g&&g<=7027||7040<=g&&g<=7041||7074<=g&&g<=7077||7080<=g&&g<=7081||7083<=g&&g<=7085||g==7142||7144<=g&&g<=7145||g==7149||7151<=g&&g<=7153||7212<=g&&g<=7219||7222<=g&&g<=7223||7376<=g&&g<=7378||7380<=g&&g<=7392||7394<=g&&g<=7400||g==7405||g==7412||7416<=g&&g<=7417||7616<=g&&g<=7673||7675<=g&&g<=7679||g==8204||8400<=g&&g<=8412||8413<=g&&g<=8416||g==8417||8418<=g&&g<=8420||8421<=g&&g<=8432||11503<=g&&g<=11505||g==11647||11744<=g&&g<=11775||12330<=g&&g<=12333||12334<=g&&g<=12335||12441<=g&&g<=12442||g==42607||42608<=g&&g<=42610||42612<=g&&g<=42621||42654<=g&&g<=42655||42736<=g&&g<=42737||g==43010||g==43014||g==43019||43045<=g&&g<=43046||43204<=g&&g<=43205||43232<=g&&g<=43249||43302<=g&&g<=43309||43335<=g&&g<=43345||43392<=g&&g<=43394||g==43443||43446<=g&&g<=43449||g==43452||g==43493||43561<=g&&g<=43566||43569<=g&&g<=43570||43573<=g&&g<=43574||g==43587||g==43596||g==43644||g==43696||43698<=g&&g<=43700||43703<=g&&g<=43704||43710<=g&&g<=43711||g==43713||43756<=g&&g<=43757||g==43766||g==44005||g==44008||g==44013||g==64286||65024<=g&&g<=65039||65056<=g&&g<=65071||65438<=g&&g<=65439||g==66045||g==66272||66422<=g&&g<=66426||68097<=g&&g<=68099||68101<=g&&g<=68102||68108<=g&&g<=68111||68152<=g&&g<=68154||g==68159||68325<=g&&g<=68326||g==69633||69688<=g&&g<=69702||69759<=g&&g<=69761||69811<=g&&g<=69814||69817<=g&&g<=69818||69888<=g&&g<=69890||69927<=g&&g<=69931||69933<=g&&g<=69940||g==70003||70016<=g&&g<=70017||70070<=g&&g<=70078||70090<=g&&g<=70092||70191<=g&&g<=70193||g==70196||70198<=g&&g<=70199||g==70206||g==70367||70371<=g&&g<=70378||70400<=g&&g<=70401||g==70460||g==70462||g==70464||g==70487||70502<=g&&g<=70508||70512<=g&&g<=70516||70712<=g&&g<=70719||70722<=g&&g<=70724||g==70726||g==70832||70835<=g&&g<=70840||g==70842||g==70845||70847<=g&&g<=70848||70850<=g&&g<=70851||g==71087||71090<=g&&g<=71093||71100<=g&&g<=71101||71103<=g&&g<=71104||71132<=g&&g<=71133||71219<=g&&g<=71226||g==71229||71231<=g&&g<=71232||g==71339||g==71341||71344<=g&&g<=71349||g==71351||71453<=g&&g<=71455||71458<=g&&g<=71461||71463<=g&&g<=71467||72193<=g&&g<=72198||72201<=g&&g<=72202||72243<=g&&g<=72248||72251<=g&&g<=72254||g==72263||72273<=g&&g<=72278||72281<=g&&g<=72283||72330<=g&&g<=72342||72344<=g&&g<=72345||72752<=g&&g<=72758||72760<=g&&g<=72765||g==72767||72850<=g&&g<=72871||72874<=g&&g<=72880||72882<=g&&g<=72883||72885<=g&&g<=72886||73009<=g&&g<=73014||g==73018||73020<=g&&g<=73021||73023<=g&&g<=73029||g==73031||92912<=g&&g<=92916||92976<=g&&g<=92982||94095<=g&&g<=94098||113821<=g&&g<=113822||g==119141||119143<=g&&g<=119145||119150<=g&&g<=119154||119163<=g&&g<=119170||119173<=g&&g<=119179||119210<=g&&g<=119213||119362<=g&&g<=119364||121344<=g&&g<=121398||121403<=g&&g<=121452||g==121461||g==121476||121499<=g&&g<=121503||121505<=g&&g<=121519||122880<=g&&g<=122886||122888<=g&&g<=122904||122907<=g&&g<=122913||122915<=g&&g<=122916||122918<=g&&g<=122922||125136<=g&&g<=125142||125252<=g&&g<=125258||917536<=g&&g<=917631||917760<=g&&g<=917999?s:127462<=g&&g<=127487?a:g==2307||g==2363||2366<=g&&g<=2368||2377<=g&&g<=2380||2382<=g&&g<=2383||2434<=g&&g<=2435||2495<=g&&g<=2496||2503<=g&&g<=2504||2507<=g&&g<=2508||g==2563||2622<=g&&g<=2624||g==2691||2750<=g&&g<=2752||g==2761||2763<=g&&g<=2764||2818<=g&&g<=2819||g==2880||2887<=g&&g<=2888||2891<=g&&g<=2892||g==3007||3009<=g&&g<=3010||3014<=g&&g<=3016||3018<=g&&g<=3020||3073<=g&&g<=3075||3137<=g&&g<=3140||3202<=g&&g<=3203||g==3262||3264<=g&&g<=3265||3267<=g&&g<=3268||3271<=g&&g<=3272||3274<=g&&g<=3275||3330<=g&&g<=3331||3391<=g&&g<=3392||3398<=g&&g<=3400||3402<=g&&g<=3404||3458<=g&&g<=3459||3536<=g&&g<=3537||3544<=g&&g<=3550||3570<=g&&g<=3571||g==3635||g==3763||3902<=g&&g<=3903||g==3967||g==4145||4155<=g&&g<=4156||4182<=g&&g<=4183||g==4228||g==6070||6078<=g&&g<=6085||6087<=g&&g<=6088||6435<=g&&g<=6438||6441<=g&&g<=6443||6448<=g&&g<=6449||6451<=g&&g<=6456||6681<=g&&g<=6682||g==6741||g==6743||6765<=g&&g<=6770||g==6916||g==6965||g==6971||6973<=g&&g<=6977||6979<=g&&g<=6980||g==7042||g==7073||7078<=g&&g<=7079||g==7082||g==7143||7146<=g&&g<=7148||g==7150||7154<=g&&g<=7155||7204<=g&&g<=7211||7220<=g&&g<=7221||g==7393||7410<=g&&g<=7411||g==7415||43043<=g&&g<=43044||g==43047||43136<=g&&g<=43137||43188<=g&&g<=43203||43346<=g&&g<=43347||g==43395||43444<=g&&g<=43445||43450<=g&&g<=43451||43453<=g&&g<=43456||43567<=g&&g<=43568||43571<=g&&g<=43572||g==43597||g==43755||43758<=g&&g<=43759||g==43765||44003<=g&&g<=44004||44006<=g&&g<=44007||44009<=g&&g<=44010||g==44012||g==69632||g==69634||g==69762||69808<=g&&g<=69810||69815<=g&&g<=69816||g==69932||g==70018||70067<=g&&g<=70069||70079<=g&&g<=70080||70188<=g&&g<=70190||70194<=g&&g<=70195||g==70197||70368<=g&&g<=70370||70402<=g&&g<=70403||g==70463||70465<=g&&g<=70468||70471<=g&&g<=70472||70475<=g&&g<=70477||70498<=g&&g<=70499||70709<=g&&g<=70711||70720<=g&&g<=70721||g==70725||70833<=g&&g<=70834||g==70841||70843<=g&&g<=70844||g==70846||g==70849||71088<=g&&g<=71089||71096<=g&&g<=71099||g==71102||71216<=g&&g<=71218||71227<=g&&g<=71228||g==71230||g==71340||71342<=g&&g<=71343||g==71350||71456<=g&&g<=71457||g==71462||72199<=g&&g<=72200||g==72249||72279<=g&&g<=72280||g==72343||g==72751||g==72766||g==72873||g==72881||g==72884||94033<=g&&g<=94078||g==119142||g==119149?n:4352<=g&&g<=4447||43360<=g&&g<=43388?c:4448<=g&&g<=4519||55216<=g&&g<=55238?f:4520<=g&&g<=4607||55243<=g&&g<=55291?p:g==44032||g==44060||g==44088||g==44116||g==44144||g==44172||g==44200||g==44228||g==44256||g==44284||g==44312||g==44340||g==44368||g==44396||g==44424||g==44452||g==44480||g==44508||g==44536||g==44564||g==44592||g==44620||g==44648||g==44676||g==44704||g==44732||g==44760||g==44788||g==44816||g==44844||g==44872||g==44900||g==44928||g==44956||g==44984||g==45012||g==45040||g==45068||g==45096||g==45124||g==45152||g==45180||g==45208||g==45236||g==45264||g==45292||g==45320||g==45348||g==45376||g==45404||g==45432||g==45460||g==45488||g==45516||g==45544||g==45572||g==45600||g==45628||g==45656||g==45684||g==45712||g==45740||g==45768||g==45796||g==45824||g==45852||g==45880||g==45908||g==45936||g==45964||g==45992||g==46020||g==46048||g==46076||g==46104||g==46132||g==46160||g==46188||g==46216||g==46244||g==46272||g==46300||g==46328||g==46356||g==46384||g==46412||g==46440||g==46468||g==46496||g==46524||g==46552||g==46580||g==46608||g==46636||g==46664||g==46692||g==46720||g==46748||g==46776||g==46804||g==46832||g==46860||g==46888||g==46916||g==46944||g==46972||g==47e3||g==47028||g==47056||g==47084||g==47112||g==47140||g==47168||g==47196||g==47224||g==47252||g==47280||g==47308||g==47336||g==47364||g==47392||g==47420||g==47448||g==47476||g==47504||g==47532||g==47560||g==47588||g==47616||g==47644||g==47672||g==47700||g==47728||g==47756||g==47784||g==47812||g==47840||g==47868||g==47896||g==47924||g==47952||g==47980||g==48008||g==48036||g==48064||g==48092||g==48120||g==48148||g==48176||g==48204||g==48232||g==48260||g==48288||g==48316||g==48344||g==48372||g==48400||g==48428||g==48456||g==48484||g==48512||g==48540||g==48568||g==48596||g==48624||g==48652||g==48680||g==48708||g==48736||g==48764||g==48792||g==48820||g==48848||g==48876||g==48904||g==48932||g==48960||g==48988||g==49016||g==49044||g==49072||g==49100||g==49128||g==49156||g==49184||g==49212||g==49240||g==49268||g==49296||g==49324||g==49352||g==49380||g==49408||g==49436||g==49464||g==49492||g==49520||g==49548||g==49576||g==49604||g==49632||g==49660||g==49688||g==49716||g==49744||g==49772||g==49800||g==49828||g==49856||g==49884||g==49912||g==49940||g==49968||g==49996||g==50024||g==50052||g==50080||g==50108||g==50136||g==50164||g==50192||g==50220||g==50248||g==50276||g==50304||g==50332||g==50360||g==50388||g==50416||g==50444||g==50472||g==50500||g==50528||g==50556||g==50584||g==50612||g==50640||g==50668||g==50696||g==50724||g==50752||g==50780||g==50808||g==50836||g==50864||g==50892||g==50920||g==50948||g==50976||g==51004||g==51032||g==51060||g==51088||g==51116||g==51144||g==51172||g==51200||g==51228||g==51256||g==51284||g==51312||g==51340||g==51368||g==51396||g==51424||g==51452||g==51480||g==51508||g==51536||g==51564||g==51592||g==51620||g==51648||g==51676||g==51704||g==51732||g==51760||g==51788||g==51816||g==51844||g==51872||g==51900||g==51928||g==51956||g==51984||g==52012||g==52040||g==52068||g==52096||g==52124||g==52152||g==52180||g==52208||g==52236||g==52264||g==52292||g==52320||g==52348||g==52376||g==52404||g==52432||g==52460||g==52488||g==52516||g==52544||g==52572||g==52600||g==52628||g==52656||g==52684||g==52712||g==52740||g==52768||g==52796||g==52824||g==52852||g==52880||g==52908||g==52936||g==52964||g==52992||g==53020||g==53048||g==53076||g==53104||g==53132||g==53160||g==53188||g==53216||g==53244||g==53272||g==53300||g==53328||g==53356||g==53384||g==53412||g==53440||g==53468||g==53496||g==53524||g==53552||g==53580||g==53608||g==53636||g==53664||g==53692||g==53720||g==53748||g==53776||g==53804||g==53832||g==53860||g==53888||g==53916||g==53944||g==53972||g==54e3||g==54028||g==54056||g==54084||g==54112||g==54140||g==54168||g==54196||g==54224||g==54252||g==54280||g==54308||g==54336||g==54364||g==54392||g==54420||g==54448||g==54476||g==54504||g==54532||g==54560||g==54588||g==54616||g==54644||g==54672||g==54700||g==54728||g==54756||g==54784||g==54812||g==54840||g==54868||g==54896||g==54924||g==54952||g==54980||g==55008||g==55036||g==55064||g==55092||g==55120||g==55148||g==55176?h:44033<=g&&g<=44059||44061<=g&&g<=44087||44089<=g&&g<=44115||44117<=g&&g<=44143||44145<=g&&g<=44171||44173<=g&&g<=44199||44201<=g&&g<=44227||44229<=g&&g<=44255||44257<=g&&g<=44283||44285<=g&&g<=44311||44313<=g&&g<=44339||44341<=g&&g<=44367||44369<=g&&g<=44395||44397<=g&&g<=44423||44425<=g&&g<=44451||44453<=g&&g<=44479||44481<=g&&g<=44507||44509<=g&&g<=44535||44537<=g&&g<=44563||44565<=g&&g<=44591||44593<=g&&g<=44619||44621<=g&&g<=44647||44649<=g&&g<=44675||44677<=g&&g<=44703||44705<=g&&g<=44731||44733<=g&&g<=44759||44761<=g&&g<=44787||44789<=g&&g<=44815||44817<=g&&g<=44843||44845<=g&&g<=44871||44873<=g&&g<=44899||44901<=g&&g<=44927||44929<=g&&g<=44955||44957<=g&&g<=44983||44985<=g&&g<=45011||45013<=g&&g<=45039||45041<=g&&g<=45067||45069<=g&&g<=45095||45097<=g&&g<=45123||45125<=g&&g<=45151||45153<=g&&g<=45179||45181<=g&&g<=45207||45209<=g&&g<=45235||45237<=g&&g<=45263||45265<=g&&g<=45291||45293<=g&&g<=45319||45321<=g&&g<=45347||45349<=g&&g<=45375||45377<=g&&g<=45403||45405<=g&&g<=45431||45433<=g&&g<=45459||45461<=g&&g<=45487||45489<=g&&g<=45515||45517<=g&&g<=45543||45545<=g&&g<=45571||45573<=g&&g<=45599||45601<=g&&g<=45627||45629<=g&&g<=45655||45657<=g&&g<=45683||45685<=g&&g<=45711||45713<=g&&g<=45739||45741<=g&&g<=45767||45769<=g&&g<=45795||45797<=g&&g<=45823||45825<=g&&g<=45851||45853<=g&&g<=45879||45881<=g&&g<=45907||45909<=g&&g<=45935||45937<=g&&g<=45963||45965<=g&&g<=45991||45993<=g&&g<=46019||46021<=g&&g<=46047||46049<=g&&g<=46075||46077<=g&&g<=46103||46105<=g&&g<=46131||46133<=g&&g<=46159||46161<=g&&g<=46187||46189<=g&&g<=46215||46217<=g&&g<=46243||46245<=g&&g<=46271||46273<=g&&g<=46299||46301<=g&&g<=46327||46329<=g&&g<=46355||46357<=g&&g<=46383||46385<=g&&g<=46411||46413<=g&&g<=46439||46441<=g&&g<=46467||46469<=g&&g<=46495||46497<=g&&g<=46523||46525<=g&&g<=46551||46553<=g&&g<=46579||46581<=g&&g<=46607||46609<=g&&g<=46635||46637<=g&&g<=46663||46665<=g&&g<=46691||46693<=g&&g<=46719||46721<=g&&g<=46747||46749<=g&&g<=46775||46777<=g&&g<=46803||46805<=g&&g<=46831||46833<=g&&g<=46859||46861<=g&&g<=46887||46889<=g&&g<=46915||46917<=g&&g<=46943||46945<=g&&g<=46971||46973<=g&&g<=46999||47001<=g&&g<=47027||47029<=g&&g<=47055||47057<=g&&g<=47083||47085<=g&&g<=47111||47113<=g&&g<=47139||47141<=g&&g<=47167||47169<=g&&g<=47195||47197<=g&&g<=47223||47225<=g&&g<=47251||47253<=g&&g<=47279||47281<=g&&g<=47307||47309<=g&&g<=47335||47337<=g&&g<=47363||47365<=g&&g<=47391||47393<=g&&g<=47419||47421<=g&&g<=47447||47449<=g&&g<=47475||47477<=g&&g<=47503||47505<=g&&g<=47531||47533<=g&&g<=47559||47561<=g&&g<=47587||47589<=g&&g<=47615||47617<=g&&g<=47643||47645<=g&&g<=47671||47673<=g&&g<=47699||47701<=g&&g<=47727||47729<=g&&g<=47755||47757<=g&&g<=47783||47785<=g&&g<=47811||47813<=g&&g<=47839||47841<=g&&g<=47867||47869<=g&&g<=47895||47897<=g&&g<=47923||47925<=g&&g<=47951||47953<=g&&g<=47979||47981<=g&&g<=48007||48009<=g&&g<=48035||48037<=g&&g<=48063||48065<=g&&g<=48091||48093<=g&&g<=48119||48121<=g&&g<=48147||48149<=g&&g<=48175||48177<=g&&g<=48203||48205<=g&&g<=48231||48233<=g&&g<=48259||48261<=g&&g<=48287||48289<=g&&g<=48315||48317<=g&&g<=48343||48345<=g&&g<=48371||48373<=g&&g<=48399||48401<=g&&g<=48427||48429<=g&&g<=48455||48457<=g&&g<=48483||48485<=g&&g<=48511||48513<=g&&g<=48539||48541<=g&&g<=48567||48569<=g&&g<=48595||48597<=g&&g<=48623||48625<=g&&g<=48651||48653<=g&&g<=48679||48681<=g&&g<=48707||48709<=g&&g<=48735||48737<=g&&g<=48763||48765<=g&&g<=48791||48793<=g&&g<=48819||48821<=g&&g<=48847||48849<=g&&g<=48875||48877<=g&&g<=48903||48905<=g&&g<=48931||48933<=g&&g<=48959||48961<=g&&g<=48987||48989<=g&&g<=49015||49017<=g&&g<=49043||49045<=g&&g<=49071||49073<=g&&g<=49099||49101<=g&&g<=49127||49129<=g&&g<=49155||49157<=g&&g<=49183||49185<=g&&g<=49211||49213<=g&&g<=49239||49241<=g&&g<=49267||49269<=g&&g<=49295||49297<=g&&g<=49323||49325<=g&&g<=49351||49353<=g&&g<=49379||49381<=g&&g<=49407||49409<=g&&g<=49435||49437<=g&&g<=49463||49465<=g&&g<=49491||49493<=g&&g<=49519||49521<=g&&g<=49547||49549<=g&&g<=49575||49577<=g&&g<=49603||49605<=g&&g<=49631||49633<=g&&g<=49659||49661<=g&&g<=49687||49689<=g&&g<=49715||49717<=g&&g<=49743||49745<=g&&g<=49771||49773<=g&&g<=49799||49801<=g&&g<=49827||49829<=g&&g<=49855||49857<=g&&g<=49883||49885<=g&&g<=49911||49913<=g&&g<=49939||49941<=g&&g<=49967||49969<=g&&g<=49995||49997<=g&&g<=50023||50025<=g&&g<=50051||50053<=g&&g<=50079||50081<=g&&g<=50107||50109<=g&&g<=50135||50137<=g&&g<=50163||50165<=g&&g<=50191||50193<=g&&g<=50219||50221<=g&&g<=50247||50249<=g&&g<=50275||50277<=g&&g<=50303||50305<=g&&g<=50331||50333<=g&&g<=50359||50361<=g&&g<=50387||50389<=g&&g<=50415||50417<=g&&g<=50443||50445<=g&&g<=50471||50473<=g&&g<=50499||50501<=g&&g<=50527||50529<=g&&g<=50555||50557<=g&&g<=50583||50585<=g&&g<=50611||50613<=g&&g<=50639||50641<=g&&g<=50667||50669<=g&&g<=50695||50697<=g&&g<=50723||50725<=g&&g<=50751||50753<=g&&g<=50779||50781<=g&&g<=50807||50809<=g&&g<=50835||50837<=g&&g<=50863||50865<=g&&g<=50891||50893<=g&&g<=50919||50921<=g&&g<=50947||50949<=g&&g<=50975||50977<=g&&g<=51003||51005<=g&&g<=51031||51033<=g&&g<=51059||51061<=g&&g<=51087||51089<=g&&g<=51115||51117<=g&&g<=51143||51145<=g&&g<=51171||51173<=g&&g<=51199||51201<=g&&g<=51227||51229<=g&&g<=51255||51257<=g&&g<=51283||51285<=g&&g<=51311||51313<=g&&g<=51339||51341<=g&&g<=51367||51369<=g&&g<=51395||51397<=g&&g<=51423||51425<=g&&g<=51451||51453<=g&&g<=51479||51481<=g&&g<=51507||51509<=g&&g<=51535||51537<=g&&g<=51563||51565<=g&&g<=51591||51593<=g&&g<=51619||51621<=g&&g<=51647||51649<=g&&g<=51675||51677<=g&&g<=51703||51705<=g&&g<=51731||51733<=g&&g<=51759||51761<=g&&g<=51787||51789<=g&&g<=51815||51817<=g&&g<=51843||51845<=g&&g<=51871||51873<=g&&g<=51899||51901<=g&&g<=51927||51929<=g&&g<=51955||51957<=g&&g<=51983||51985<=g&&g<=52011||52013<=g&&g<=52039||52041<=g&&g<=52067||52069<=g&&g<=52095||52097<=g&&g<=52123||52125<=g&&g<=52151||52153<=g&&g<=52179||52181<=g&&g<=52207||52209<=g&&g<=52235||52237<=g&&g<=52263||52265<=g&&g<=52291||52293<=g&&g<=52319||52321<=g&&g<=52347||52349<=g&&g<=52375||52377<=g&&g<=52403||52405<=g&&g<=52431||52433<=g&&g<=52459||52461<=g&&g<=52487||52489<=g&&g<=52515||52517<=g&&g<=52543||52545<=g&&g<=52571||52573<=g&&g<=52599||52601<=g&&g<=52627||52629<=g&&g<=52655||52657<=g&&g<=52683||52685<=g&&g<=52711||52713<=g&&g<=52739||52741<=g&&g<=52767||52769<=g&&g<=52795||52797<=g&&g<=52823||52825<=g&&g<=52851||52853<=g&&g<=52879||52881<=g&&g<=52907||52909<=g&&g<=52935||52937<=g&&g<=52963||52965<=g&&g<=52991||52993<=g&&g<=53019||53021<=g&&g<=53047||53049<=g&&g<=53075||53077<=g&&g<=53103||53105<=g&&g<=53131||53133<=g&&g<=53159||53161<=g&&g<=53187||53189<=g&&g<=53215||53217<=g&&g<=53243||53245<=g&&g<=53271||53273<=g&&g<=53299||53301<=g&&g<=53327||53329<=g&&g<=53355||53357<=g&&g<=53383||53385<=g&&g<=53411||53413<=g&&g<=53439||53441<=g&&g<=53467||53469<=g&&g<=53495||53497<=g&&g<=53523||53525<=g&&g<=53551||53553<=g&&g<=53579||53581<=g&&g<=53607||53609<=g&&g<=53635||53637<=g&&g<=53663||53665<=g&&g<=53691||53693<=g&&g<=53719||53721<=g&&g<=53747||53749<=g&&g<=53775||53777<=g&&g<=53803||53805<=g&&g<=53831||53833<=g&&g<=53859||53861<=g&&g<=53887||53889<=g&&g<=53915||53917<=g&&g<=53943||53945<=g&&g<=53971||53973<=g&&g<=53999||54001<=g&&g<=54027||54029<=g&&g<=54055||54057<=g&&g<=54083||54085<=g&&g<=54111||54113<=g&&g<=54139||54141<=g&&g<=54167||54169<=g&&g<=54195||54197<=g&&g<=54223||54225<=g&&g<=54251||54253<=g&&g<=54279||54281<=g&&g<=54307||54309<=g&&g<=54335||54337<=g&&g<=54363||54365<=g&&g<=54391||54393<=g&&g<=54419||54421<=g&&g<=54447||54449<=g&&g<=54475||54477<=g&&g<=54503||54505<=g&&g<=54531||54533<=g&&g<=54559||54561<=g&&g<=54587||54589<=g&&g<=54615||54617<=g&&g<=54643||54645<=g&&g<=54671||54673<=g&&g<=54699||54701<=g&&g<=54727||54729<=g&&g<=54755||54757<=g&&g<=54783||54785<=g&&g<=54811||54813<=g&&g<=54839||54841<=g&&g<=54867||54869<=g&&g<=54895||54897<=g&&g<=54923||54925<=g&&g<=54951||54953<=g&&g<=54979||54981<=g&&g<=55007||55009<=g&&g<=55035||55037<=g&&g<=55063||55065<=g&&g<=55091||55093<=g&&g<=55119||55121<=g&&g<=55147||55149<=g&&g<=55175||55177<=g&&g<=55203?E:g==9757||g==9977||9994<=g&&g<=9997||g==127877||127938<=g&&g<=127940||g==127943||127946<=g&&g<=127948||128066<=g&&g<=128067||128070<=g&&g<=128080||g==128110||128112<=g&&g<=128120||g==128124||128129<=g&&g<=128131||128133<=g&&g<=128135||g==128170||128372<=g&&g<=128373||g==128378||g==128400||128405<=g&&g<=128406||128581<=g&&g<=128583||128587<=g&&g<=128591||g==128675||128692<=g&&g<=128694||g==128704||g==128716||129304<=g&&g<=129308||129310<=g&&g<=129311||g==129318||129328<=g&&g<=129337||129341<=g&&g<=129342||129489<=g&&g<=129501?P:127995<=g&&g<=127999?I:g==8205?R:g==9792||g==9794||9877<=g&&g<=9878||g==9992||g==10084||g==127752||g==127806||g==127859||g==127891||g==127908||g==127912||g==127979||g==127981||g==128139||128187<=g&&g<=128188||g==128295||g==128300||g==128488||g==128640||g==128658?N:128102<=g&&g<=128105?U:C}return this}typeof TT<\"u\"&&TT.exports&&(TT.exports=zmt)});var Ume=L((_Xt,_me)=>{var Zmt=/^(.*?)(\\x1b\\[[^m]+m|\\x1b\\]8;;.*?(\\x1b\\\\|\\u0007))/,RT;function Xmt(){if(RT)return RT;if(typeof Intl.Segmenter<\"u\"){let t=new Intl.Segmenter(\"en\",{granularity:\"grapheme\"});return RT=e=>Array.from(t.segment(e),({segment:r})=>r)}else{let t=Mme(),e=new t;return RT=r=>e.splitGraphemes(r)}}_me.exports=(t,e=0,r=t.length)=>{if(e<0||r<0)throw new RangeError(\"Negative indices aren't supported by this implementation\");let s=r-e,a=\"\",n=0,c=0;for(;t.length>0;){let f=t.match(Zmt)||[t,t,void 0],p=Xmt()(f[1]),h=Math.min(e-n,p.length);p=p.slice(h);let E=Math.min(s-c,p.length);a+=p.slice(0,E).join(\"\"),n+=h,c+=E,typeof f[2]<\"u\"&&(a+=f[2]),t=t.slice(f[0].length)}return a}});var un,Pv=Ct(()=>{un=process.env.YARN_IS_TEST_ENV?\"0.0.0\":\"4.9.2\"});function Yme(t,{configuration:e,json:r}){if(!e.get(\"enableMessageNames\"))return\"\";let a=Vf(t===null?0:t);return!r&&t===null?Ut(e,a,\"grey\"):a}function u6(t,{configuration:e,json:r}){let s=Yme(t,{configuration:e,json:r});if(!s||t===null||t===0)return s;let a=Dr[t],n=`https://yarnpkg.com/advanced/error-codes#${s}---${a}`.toLowerCase();return KE(e,s,n)}async function vI({configuration:t,stdout:e,forceError:r},s){let a=await Ot.start({configuration:t,stdout:e,includeFooter:!1},async n=>{let c=!1,f=!1;for(let p of s)typeof p.option<\"u\"&&(p.error||r?(f=!0,n.reportError(50,p.message)):(c=!0,n.reportWarning(50,p.message)),p.callback?.());c&&!f&&n.reportSeparator()});return a.hasErrors()?a.exitCode():null}var Gme,FT,$mt,Hme,jme,S0,Wme,qme,eyt,tyt,NT,ryt,Ot,xv=Ct(()=>{Gme=et(Ume()),FT=et(Rd());Zx();Fc();Pv();Qc();$mt=\"\\xB7\",Hme=[\"\\u280B\",\"\\u2819\",\"\\u2839\",\"\\u2838\",\"\\u283C\",\"\\u2834\",\"\\u2826\",\"\\u2827\",\"\\u2807\",\"\\u280F\"],jme=80,S0=FT.default.GITHUB_ACTIONS?{start:t=>`::group::${t}\n`,end:t=>`::endgroup::\n`}:FT.default.TRAVIS?{start:t=>`travis_fold:start:${t}\n`,end:t=>`travis_fold:end:${t}\n`}:FT.default.GITLAB?{start:t=>`section_start:${Math.floor(Date.now()/1e3)}:${t.toLowerCase().replace(/\\W+/g,\"_\")}[collapsed=true]\\r\\x1B[0K${t}\n`,end:t=>`section_end:${Math.floor(Date.now()/1e3)}:${t.toLowerCase().replace(/\\W+/g,\"_\")}\\r\\x1B[0K`}:null,Wme=S0!==null,qme=new Date,eyt=[\"iTerm.app\",\"Apple_Terminal\",\"WarpTerminal\",\"vscode\"].includes(process.env.TERM_PROGRAM)||!!process.env.WT_SESSION,tyt=t=>t,NT=tyt({patrick:{date:[17,3],chars:[\"\\u{1F340}\",\"\\u{1F331}\"],size:40},simba:{date:[19,7],chars:[\"\\u{1F981}\",\"\\u{1F334}\"],size:40},jack:{date:[31,10],chars:[\"\\u{1F383}\",\"\\u{1F987}\"],size:40},hogsfather:{date:[31,12],chars:[\"\\u{1F389}\",\"\\u{1F384}\"],size:40},default:{chars:[\"=\",\"-\"],size:80}}),ryt=eyt&&Object.keys(NT).find(t=>{let e=NT[t];return!(e.date&&(e.date[0]!==qme.getDate()||e.date[1]!==qme.getMonth()+1))})||\"default\";Ot=class extends ho{constructor({configuration:r,stdout:s,json:a=!1,forceSectionAlignment:n=!1,includeNames:c=!0,includePrefix:f=!0,includeFooter:p=!0,includeLogs:h=!a,includeInfos:E=h,includeWarnings:C=h}){super();this.uncommitted=new Set;this.warningCount=0;this.errorCount=0;this.timerFooter=[];this.startTime=Date.now();this.indent=0;this.level=0;this.progress=new Map;this.progressTime=0;this.progressFrame=0;this.progressTimeout=null;this.progressStyle=null;this.progressMaxScaledSize=null;if(HB(this,{configuration:r}),this.configuration=r,this.forceSectionAlignment=n,this.includeNames=c,this.includePrefix=f,this.includeFooter=p,this.includeInfos=E,this.includeWarnings=C,this.json=a,this.stdout=s,r.get(\"enableProgressBars\")&&!a&&s.isTTY&&s.columns>22){let S=r.get(\"progressBarStyle\")||ryt;if(!Object.hasOwn(NT,S))throw new Error(\"Assertion failed: Invalid progress bar style\");this.progressStyle=NT[S];let P=Math.min(this.getRecommendedLength(),80);this.progressMaxScaledSize=Math.floor(this.progressStyle.size*P/80)}}static async start(r,s){let a=new this(r),n=process.emitWarning;process.emitWarning=(c,f)=>{if(typeof c!=\"string\"){let h=c;c=h.message,f=f??h.name}let p=typeof f<\"u\"?`${f}: ${c}`:c;a.reportWarning(0,p)},r.includeVersion&&a.reportInfo(0,Kd(r.configuration,`Yarn ${un}`,2));try{await s(a)}catch(c){a.reportExceptionOnce(c)}finally{await a.finalize(),process.emitWarning=n}return a}hasErrors(){return this.errorCount>0}exitCode(){return this.hasErrors()?1:0}getRecommendedLength(){let s=this.progressStyle!==null?this.stdout.columns-1:super.getRecommendedLength();return Math.max(40,s-12-this.indent*2)}startSectionSync({reportHeader:r,reportFooter:s,skipIfEmpty:a},n){let c={committed:!1,action:()=>{r?.()}};a?this.uncommitted.add(c):(c.action(),c.committed=!0);let f=Date.now();try{return n()}catch(p){throw this.reportExceptionOnce(p),p}finally{let p=Date.now();this.uncommitted.delete(c),c.committed&&s?.(p-f)}}async startSectionPromise({reportHeader:r,reportFooter:s,skipIfEmpty:a},n){let c={committed:!1,action:()=>{r?.()}};a?this.uncommitted.add(c):(c.action(),c.committed=!0);let f=Date.now();try{return await n()}catch(p){throw this.reportExceptionOnce(p),p}finally{let p=Date.now();this.uncommitted.delete(c),c.committed&&s?.(p-f)}}startTimerImpl(r,s,a){return{cb:typeof s==\"function\"?s:a,reportHeader:()=>{this.level+=1,this.reportInfo(null,`\\u250C ${r}`),this.indent+=1,S0!==null&&!this.json&&this.includeInfos&&this.stdout.write(S0.start(r))},reportFooter:f=>{if(this.indent-=1,S0!==null&&!this.json&&this.includeInfos){this.stdout.write(S0.end(r));for(let p of this.timerFooter)p()}this.configuration.get(\"enableTimers\")&&f>200?this.reportInfo(null,`\\u2514 Completed in ${Ut(this.configuration,f,pt.DURATION)}`):this.reportInfo(null,\"\\u2514 Completed\"),this.level-=1},skipIfEmpty:(typeof s==\"function\"?{}:s).skipIfEmpty}}startTimerSync(r,s,a){let{cb:n,...c}=this.startTimerImpl(r,s,a);return this.startSectionSync(c,n)}async startTimerPromise(r,s,a){let{cb:n,...c}=this.startTimerImpl(r,s,a);return this.startSectionPromise(c,n)}reportSeparator(){this.indent===0?this.writeLine(\"\"):this.reportInfo(null,\"\")}reportInfo(r,s){if(!this.includeInfos)return;this.commit();let a=this.formatNameWithHyperlink(r),n=a?`${a}: `:\"\",c=`${this.formatPrefix(n,\"blueBright\")}${s}`;this.json?this.reportJson({type:\"info\",name:r,displayName:this.formatName(r),indent:this.formatIndent(),data:s}):this.writeLine(c)}reportWarning(r,s){if(this.warningCount+=1,!this.includeWarnings)return;this.commit();let a=this.formatNameWithHyperlink(r),n=a?`${a}: `:\"\";this.json?this.reportJson({type:\"warning\",name:r,displayName:this.formatName(r),indent:this.formatIndent(),data:s}):this.writeLine(`${this.formatPrefix(n,\"yellowBright\")}${s}`)}reportError(r,s){this.errorCount+=1,this.timerFooter.push(()=>this.reportErrorImpl(r,s)),this.reportErrorImpl(r,s)}reportErrorImpl(r,s){this.commit();let a=this.formatNameWithHyperlink(r),n=a?`${a}: `:\"\";this.json?this.reportJson({type:\"error\",name:r,displayName:this.formatName(r),indent:this.formatIndent(),data:s}):this.writeLine(`${this.formatPrefix(n,\"redBright\")}${s}`,{truncate:!1})}reportFold(r,s){if(!S0)return;let a=`${S0.start(r)}${s}${S0.end(r)}`;this.timerFooter.push(()=>this.stdout.write(a))}reportProgress(r){if(this.progressStyle===null)return{...Promise.resolve(),stop:()=>{}};if(r.hasProgress&&r.hasTitle)throw new Error(\"Unimplemented: Progress bars can't have both progress and titles.\");let s=!1,a=Promise.resolve().then(async()=>{let c={progress:r.hasProgress?0:void 0,title:r.hasTitle?\"\":void 0};this.progress.set(r,{definition:c,lastScaledSize:r.hasProgress?-1:void 0,lastTitle:void 0}),this.refreshProgress({delta:-1});for await(let{progress:f,title:p}of r)s||c.progress===f&&c.title===p||(c.progress=f,c.title=p,this.refreshProgress());n()}),n=()=>{s||(s=!0,this.progress.delete(r),this.refreshProgress({delta:1}))};return{...a,stop:n}}reportJson(r){this.json&&this.writeLine(`${JSON.stringify(r)}`)}async finalize(){if(!this.includeFooter)return;let r=\"\";this.errorCount>0?r=\"Failed with errors\":this.warningCount>0?r=\"Done with warnings\":r=\"Done\";let s=Ut(this.configuration,Date.now()-this.startTime,pt.DURATION),a=this.configuration.get(\"enableTimers\")?`${r} in ${s}`:r;this.errorCount>0?this.reportError(0,a):this.warningCount>0?this.reportWarning(0,a):this.reportInfo(0,a)}writeLine(r,{truncate:s}={}){this.clearProgress({clear:!0}),this.stdout.write(`${this.truncate(r,{truncate:s})}\n`),this.writeProgress()}writeLines(r,{truncate:s}={}){this.clearProgress({delta:r.length});for(let a of r)this.stdout.write(`${this.truncate(a,{truncate:s})}\n`);this.writeProgress()}commit(){let r=this.uncommitted;this.uncommitted=new Set;for(let s of r)s.committed=!0,s.action()}clearProgress({delta:r=0,clear:s=!1}){this.progressStyle!==null&&this.progress.size+r>0&&(this.stdout.write(`\\x1B[${this.progress.size+r}A`),(r>0||s)&&this.stdout.write(\"\\x1B[0J\"))}writeProgress(){if(this.progressStyle===null||(this.progressTimeout!==null&&clearTimeout(this.progressTimeout),this.progressTimeout=null,this.progress.size===0))return;let r=Date.now();r-this.progressTime>jme&&(this.progressFrame=(this.progressFrame+1)%Hme.length,this.progressTime=r);let s=Hme[this.progressFrame];for(let a of this.progress.values()){let n=\"\";if(typeof a.lastScaledSize<\"u\"){let h=this.progressStyle.chars[0].repeat(a.lastScaledSize),E=this.progressStyle.chars[1].repeat(this.progressMaxScaledSize-a.lastScaledSize);n=` ${h}${E}`}let c=this.formatName(null),f=c?`${c}: `:\"\",p=a.definition.title?` ${a.definition.title}`:\"\";this.stdout.write(`${Ut(this.configuration,\"\\u27A4\",\"blueBright\")} ${f}${s}${n}${p}\n`)}this.progressTimeout=setTimeout(()=>{this.refreshProgress({force:!0})},jme)}refreshProgress({delta:r=0,force:s=!1}={}){let a=!1,n=!1;if(s||this.progress.size===0)a=!0;else for(let c of this.progress.values()){let f=typeof c.definition.progress<\"u\"?Math.trunc(this.progressMaxScaledSize*c.definition.progress):void 0,p=c.lastScaledSize;c.lastScaledSize=f;let h=c.lastTitle;if(c.lastTitle=c.definition.title,f!==p||(n=h!==c.definition.title)){a=!0;break}}a&&(this.clearProgress({delta:r,clear:n}),this.writeProgress())}truncate(r,{truncate:s}={}){return this.progressStyle===null&&(s=!1),typeof s>\"u\"&&(s=this.configuration.get(\"preferTruncatedLines\")),s&&(r=(0,Gme.default)(r,0,this.stdout.columns-1)),r}formatName(r){return this.includeNames?Yme(r,{configuration:this.configuration,json:this.json}):\"\"}formatPrefix(r,s){return this.includePrefix?`${Ut(this.configuration,\"\\u27A4\",s)} ${r}${this.formatIndent()}`:\"\"}formatNameWithHyperlink(r){return this.includeNames?u6(r,{configuration:this.configuration,json:this.json}):\"\"}formatIndent(){return this.level>0||!this.forceSectionAlignment?\"\\u2502 \".repeat(this.indent):`${$mt} `}}});var In={};Vt(In,{PackageManager:()=>Jme,detectPackageManager:()=>zme,executePackageAccessibleBinary:()=>tye,executePackageScript:()=>OT,executePackageShellcode:()=>f6,executeWorkspaceAccessibleBinary:()=>cyt,executeWorkspaceLifecycleScript:()=>$me,executeWorkspaceScript:()=>Xme,getPackageAccessibleBinaries:()=>LT,getWorkspaceAccessibleBinaries:()=>eye,hasPackageScript:()=>oyt,hasWorkspaceScript:()=>A6,isNodeScript:()=>p6,makeScriptEnv:()=>kv,maybeExecuteWorkspaceLifecycleScript:()=>lyt,prepareExternalProject:()=>syt});async function D0(t,e,r,s=[]){if(process.platform===\"win32\"){let a=`@goto #_undefined_# 2>NUL || @title %COMSPEC% & @setlocal & @\"${r}\" ${s.map(n=>`\"${n.replace('\"','\"\"')}\"`).join(\" \")} %*`;await le.writeFilePromise(K.format({dir:t,name:e,ext:\".cmd\"}),a)}await le.writeFilePromise(K.join(t,e),`#!/bin/sh\nexec \"${r}\" ${s.map(a=>`'${a.replace(/'/g,`'\"'\"'`)}'`).join(\" \")} \"$@\"\n`,{mode:493})}async function zme(t){let e=await Ht.tryFind(t);if(e?.packageManager){let s=bQ(e.packageManager);if(s?.name){let a=`found ${JSON.stringify({packageManager:e.packageManager})} in manifest`,[n]=s.reference.split(\".\");switch(s.name){case\"yarn\":return{packageManagerField:!0,packageManager:Number(n)===1?\"Yarn Classic\":\"Yarn\",reason:a};case\"npm\":return{packageManagerField:!0,packageManager:\"npm\",reason:a};case\"pnpm\":return{packageManagerField:!0,packageManager:\"pnpm\",reason:a}}}}let r;try{r=await le.readFilePromise(K.join(t,Er.lockfile),\"utf8\")}catch{}return r!==void 0?r.match(/^__metadata:$/m)?{packageManager:\"Yarn\",reason:'\"__metadata\" key found in yarn.lock'}:{packageManager:\"Yarn Classic\",reason:'\"__metadata\" key not found in yarn.lock, must be a Yarn classic lockfile'}:le.existsSync(K.join(t,\"package-lock.json\"))?{packageManager:\"npm\",reason:`found npm's \"package-lock.json\" lockfile`}:le.existsSync(K.join(t,\"pnpm-lock.yaml\"))?{packageManager:\"pnpm\",reason:`found pnpm's \"pnpm-lock.yaml\" lockfile`}:null}async function kv({project:t,locator:e,binFolder:r,ignoreCorepack:s,lifecycleScript:a,baseEnv:n=t?.configuration.env??process.env}){let c={};for(let[E,C]of Object.entries(n))typeof C<\"u\"&&(c[E.toLowerCase()!==\"path\"?E:\"PATH\"]=C);let f=ue.fromPortablePath(r);c.BERRY_BIN_FOLDER=ue.fromPortablePath(f);let p=process.env.COREPACK_ROOT&&!s?ue.join(process.env.COREPACK_ROOT,\"dist/yarn.js\"):process.argv[1];if(await Promise.all([D0(r,\"node\",process.execPath),...un!==null?[D0(r,\"run\",process.execPath,[p,\"run\"]),D0(r,\"yarn\",process.execPath,[p]),D0(r,\"yarnpkg\",process.execPath,[p]),D0(r,\"node-gyp\",process.execPath,[p,\"run\",\"--top-level\",\"node-gyp\"])]:[]]),t&&(c.INIT_CWD=ue.fromPortablePath(t.configuration.startingCwd),c.PROJECT_CWD=ue.fromPortablePath(t.cwd)),c.PATH=c.PATH?`${f}${ue.delimiter}${c.PATH}`:`${f}`,c.npm_execpath=`${f}${ue.sep}yarn`,c.npm_node_execpath=`${f}${ue.sep}node`,e){if(!t)throw new Error(\"Assertion failed: Missing project\");let E=t.tryWorkspaceByLocator(e),C=E?E.manifest.version??\"\":t.storedPackages.get(e.locatorHash).version??\"\";c.npm_package_name=cn(e),c.npm_package_version=C;let S;if(E)S=E.cwd;else{let P=t.storedPackages.get(e.locatorHash);if(!P)throw new Error(`Package for ${Yr(t.configuration,e)} not found in the project`);let I=t.configuration.getLinkers(),R={project:t,report:new Ot({stdout:new b0.PassThrough,configuration:t.configuration})},N=I.find(U=>U.supportsPackage(P,R));if(!N)throw new Error(`The package ${Yr(t.configuration,P)} isn't supported by any of the available linkers`);S=await N.findPackageLocation(P,R)}c.npm_package_json=ue.fromPortablePath(K.join(S,Er.manifest))}let h=un!==null?`yarn/${un}`:`yarn/${kp(\"@yarnpkg/core\").version}-core`;return c.npm_config_user_agent=`${h} npm/? node/${process.version} ${process.platform} ${process.arch}`,a&&(c.npm_lifecycle_event=a),t&&await t.configuration.triggerHook(E=>E.setupScriptEnvironment,t,c,async(E,C,S)=>await D0(r,E,C,S)),c}async function syt(t,e,{configuration:r,report:s,workspace:a=null,locator:n=null}){await iyt(async()=>{await le.mktempPromise(async c=>{let f=K.join(c,\"pack.log\"),p=null,{stdout:h,stderr:E}=r.getSubprocessStreams(f,{prefix:ue.fromPortablePath(t),report:s}),C=n&&Gu(n)?tI(n):n,S=C?cl(C):\"an external project\";h.write(`Packing ${S} from sources\n`);let P=await zme(t),I;P!==null?(h.write(`Using ${P.packageManager} for bootstrap. Reason: ${P.reason}\n\n`),I=P.packageManager):(h.write(`No package manager configuration detected; defaulting to Yarn\n\n`),I=\"Yarn\");let R=I===\"Yarn\"&&!P?.packageManagerField;await le.mktempPromise(async N=>{let U=await kv({binFolder:N,ignoreCorepack:R,baseEnv:{...process.env,COREPACK_ENABLE_AUTO_PIN:\"0\"}}),te=new Map([[\"Yarn Classic\",async()=>{let Ae=a!==null?[\"workspace\",a]:[],ce=K.join(t,Er.manifest),me=await le.readFilePromise(ce),pe=await Yu(process.execPath,[process.argv[1],\"set\",\"version\",\"classic\",\"--only-if-needed\",\"--yarn-path\"],{cwd:t,env:U,stdin:p,stdout:h,stderr:E,end:1});if(pe.code!==0)return pe.code;await le.writeFilePromise(ce,me),await le.appendFilePromise(K.join(t,\".npmignore\"),`/.yarn\n`),h.write(`\n`),delete U.NODE_ENV;let Be=await Yu(\"yarn\",[\"install\"],{cwd:t,env:U,stdin:p,stdout:h,stderr:E,end:1});if(Be.code!==0)return Be.code;h.write(`\n`);let Ce=await Yu(\"yarn\",[...Ae,\"pack\",\"--filename\",ue.fromPortablePath(e)],{cwd:t,env:U,stdin:p,stdout:h,stderr:E});return Ce.code!==0?Ce.code:0}],[\"Yarn\",async()=>{let Ae=a!==null?[\"workspace\",a]:[];U.YARN_ENABLE_INLINE_BUILDS=\"1\";let ce=K.join(t,Er.lockfile);await le.existsPromise(ce)||await le.writeFilePromise(ce,\"\");let me=await Yu(\"yarn\",[...Ae,\"pack\",\"--install-if-needed\",\"--filename\",ue.fromPortablePath(e)],{cwd:t,env:U,stdin:p,stdout:h,stderr:E});return me.code!==0?me.code:0}],[\"npm\",async()=>{if(a!==null){let we=new b0.PassThrough,ye=GE(we);we.pipe(h,{end:!1});let fe=await Yu(\"npm\",[\"--version\"],{cwd:t,env:U,stdin:p,stdout:we,stderr:E,end:0});if(we.end(),fe.code!==0)return h.end(),E.end(),fe.code;let se=(await ye).toString().trim();if(!eA(se,\">=7.x\")){let X=ba(null,\"npm\"),De=On(X,se),Re=On(X,\">=7.x\");throw new Error(`Workspaces aren't supported by ${ni(r,De)}; please upgrade to ${ni(r,Re)} (npm has been detected as the primary package manager for ${Ut(r,t,pt.PATH)})`)}}let Ae=a!==null?[\"--workspace\",a]:[];delete U.npm_config_user_agent,delete U.npm_config_production,delete U.NPM_CONFIG_PRODUCTION,delete U.NODE_ENV;let ce=await Yu(\"npm\",[\"install\",\"--legacy-peer-deps\"],{cwd:t,env:U,stdin:p,stdout:h,stderr:E,end:1});if(ce.code!==0)return ce.code;let me=new b0.PassThrough,pe=GE(me);me.pipe(h);let Be=await Yu(\"npm\",[\"pack\",\"--silent\",...Ae],{cwd:t,env:U,stdin:p,stdout:me,stderr:E});if(Be.code!==0)return Be.code;let Ce=(await pe).toString().trim().replace(/^.*\\n/s,\"\"),g=K.resolve(t,ue.toPortablePath(Ce));return await le.renamePromise(g,e),0}]]).get(I);if(typeof te>\"u\")throw new Error(\"Assertion failed: Unsupported workflow\");let ie=await te();if(!(ie===0||typeof ie>\"u\"))throw le.detachTemp(c),new Yt(58,`Packing the package failed (exit code ${ie}, logs can be found here: ${Ut(r,f,pt.PATH)})`)})})})}async function oyt(t,e,{project:r}){let s=r.tryWorkspaceByLocator(t);if(s!==null)return A6(s,e);let a=r.storedPackages.get(t.locatorHash);if(!a)throw new Error(`Package for ${Yr(r.configuration,t)} not found in the project`);return await tA.openPromise(async n=>{let c=r.configuration,f=r.configuration.getLinkers(),p={project:r,report:new Ot({stdout:new b0.PassThrough,configuration:c})},h=f.find(P=>P.supportsPackage(a,p));if(!h)throw new Error(`The package ${Yr(r.configuration,a)} isn't supported by any of the available linkers`);let E=await h.findPackageLocation(a,p),C=new Sn(E,{baseFs:n});return(await Ht.find(vt.dot,{baseFs:C})).scripts.has(e)})}async function OT(t,e,r,{cwd:s,project:a,stdin:n,stdout:c,stderr:f}){return await le.mktempPromise(async p=>{let{manifest:h,env:E,cwd:C}=await Zme(t,{project:a,binFolder:p,cwd:s,lifecycleScript:e}),S=h.scripts.get(e);if(typeof S>\"u\")return 1;let P=async()=>await BI(S,r,{cwd:C,env:E,stdin:n,stdout:c,stderr:f});return await(await a.configuration.reduceHook(R=>R.wrapScriptExecution,P,a,t,e,{script:S,args:r,cwd:C,env:E,stdin:n,stdout:c,stderr:f}))()})}async function f6(t,e,r,{cwd:s,project:a,stdin:n,stdout:c,stderr:f}){return await le.mktempPromise(async p=>{let{env:h,cwd:E}=await Zme(t,{project:a,binFolder:p,cwd:s});return await BI(e,r,{cwd:E,env:h,stdin:n,stdout:c,stderr:f})})}async function ayt(t,{binFolder:e,cwd:r,lifecycleScript:s}){let a=await kv({project:t.project,locator:t.anchoredLocator,binFolder:e,lifecycleScript:s});return await h6(e,await eye(t)),typeof r>\"u\"&&(r=K.dirname(await le.realpathPromise(K.join(t.cwd,\"package.json\")))),{manifest:t.manifest,binFolder:e,env:a,cwd:r}}async function Zme(t,{project:e,binFolder:r,cwd:s,lifecycleScript:a}){let n=e.tryWorkspaceByLocator(t);if(n!==null)return ayt(n,{binFolder:r,cwd:s,lifecycleScript:a});let c=e.storedPackages.get(t.locatorHash);if(!c)throw new Error(`Package for ${Yr(e.configuration,t)} not found in the project`);return await tA.openPromise(async f=>{let p=e.configuration,h=e.configuration.getLinkers(),E={project:e,report:new Ot({stdout:new b0.PassThrough,configuration:p})},C=h.find(N=>N.supportsPackage(c,E));if(!C)throw new Error(`The package ${Yr(e.configuration,c)} isn't supported by any of the available linkers`);let S=await kv({project:e,locator:t,binFolder:r,lifecycleScript:a});await h6(r,await LT(t,{project:e}));let P=await C.findPackageLocation(c,E),I=new Sn(P,{baseFs:f}),R=await Ht.find(vt.dot,{baseFs:I});return typeof s>\"u\"&&(s=P),{manifest:R,binFolder:r,env:S,cwd:s}})}async function Xme(t,e,r,{cwd:s,stdin:a,stdout:n,stderr:c}){return await OT(t.anchoredLocator,e,r,{cwd:s,project:t.project,stdin:a,stdout:n,stderr:c})}function A6(t,e){return t.manifest.scripts.has(e)}async function $me(t,e,{cwd:r,report:s}){let{configuration:a}=t.project,n=null;await le.mktempPromise(async c=>{let f=K.join(c,`${e}.log`),p=`# This file contains the result of Yarn calling the \"${e}\" lifecycle script inside a workspace (\"${ue.fromPortablePath(t.cwd)}\")\n`,{stdout:h,stderr:E}=a.getSubprocessStreams(f,{report:s,prefix:Yr(a,t.anchoredLocator),header:p});s.reportInfo(36,`Calling the \"${e}\" lifecycle script`);let C=await Xme(t,e,[],{cwd:r,stdin:n,stdout:h,stderr:E});if(h.end(),E.end(),C!==0)throw le.detachTemp(c),new Yt(36,`${(0,Vme.default)(e)} script failed (exit code ${Ut(a,C,pt.NUMBER)}, logs can be found here: ${Ut(a,f,pt.PATH)}); run ${Ut(a,`yarn ${e}`,pt.CODE)} to investigate`)})}async function lyt(t,e,r){A6(t,e)&&await $me(t,e,r)}function p6(t){let e=K.extname(t);if(e.match(/\\.[cm]?[jt]sx?$/))return!0;if(e===\".exe\"||e===\".bin\")return!1;let r=Buffer.alloc(4),s;try{s=le.openSync(t,\"r\")}catch{return!0}try{le.readSync(s,r,0,r.length,0)}finally{le.closeSync(s)}let a=r.readUint32BE();return!(a===3405691582||a===3489328638||a===2135247942||(a&4294901760)===1297743872)}async function LT(t,{project:e}){let r=e.configuration,s=new Map,a=e.storedPackages.get(t.locatorHash);if(!a)throw new Error(`Package for ${Yr(r,t)} not found in the project`);let n=new b0.Writable,c=r.getLinkers(),f={project:e,report:new Ot({configuration:r,stdout:n})},p=new Set([t.locatorHash]);for(let E of a.dependencies.values()){let C=e.storedResolutions.get(E.descriptorHash);if(!C)throw new Error(`Assertion failed: The resolution (${ni(r,E)}) should have been registered`);p.add(C)}let h=await Promise.all(Array.from(p,async E=>{let C=e.storedPackages.get(E);if(!C)throw new Error(`Assertion failed: The package (${E}) should have been registered`);if(C.bin.size===0)return Yl.skip;let S=c.find(I=>I.supportsPackage(C,f));if(!S)return Yl.skip;let P=null;try{P=await S.findPackageLocation(C,f)}catch(I){if(I.code===\"LOCATOR_NOT_INSTALLED\")return Yl.skip;throw I}return{dependency:C,packageLocation:P}}));for(let E of h){if(E===Yl.skip)continue;let{dependency:C,packageLocation:S}=E;for(let[P,I]of C.bin){let R=K.resolve(S,I);s.set(P,[C,ue.fromPortablePath(R),p6(R)])}}return s}async function eye(t){return await LT(t.anchoredLocator,{project:t.project})}async function h6(t,e){await Promise.all(Array.from(e,([r,[,s,a]])=>a?D0(t,r,process.execPath,[s]):D0(t,r,s,[])))}async function tye(t,e,r,{cwd:s,project:a,stdin:n,stdout:c,stderr:f,nodeArgs:p=[],packageAccessibleBinaries:h}){h??=await LT(t,{project:a});let E=h.get(e);if(!E)throw new Error(`Binary not found (${e}) for ${Yr(a.configuration,t)}`);return await le.mktempPromise(async C=>{let[,S]=E,P=await kv({project:a,locator:t,binFolder:C});await h6(P.BERRY_BIN_FOLDER,h);let I=p6(ue.toPortablePath(S))?Yu(process.execPath,[...p,S,...r],{cwd:s,env:P,stdin:n,stdout:c,stderr:f}):Yu(S,r,{cwd:s,env:P,stdin:n,stdout:c,stderr:f}),R;try{R=await I}finally{await le.removePromise(P.BERRY_BIN_FOLDER)}return R.code})}async function cyt(t,e,r,{cwd:s,stdin:a,stdout:n,stderr:c,packageAccessibleBinaries:f}){return await tye(t.anchoredLocator,e,r,{project:t.project,cwd:s,stdin:a,stdout:n,stderr:c,packageAccessibleBinaries:f})}var Vme,Kme,b0,Jme,nyt,iyt,g6=Ct(()=>{bt();bt();rA();wv();Vme=et(c6()),Kme=et(Od()),b0=Ie(\"stream\");sI();Fc();xv();Pv();hT();Qc();kc();Np();Yo();Jme=(a=>(a.Yarn1=\"Yarn Classic\",a.Yarn2=\"Yarn\",a.Npm=\"npm\",a.Pnpm=\"pnpm\",a))(Jme||{});nyt=2,iyt=(0,Kme.default)(nyt)});var SI=L((o$t,nye)=>{\"use strict\";var rye=new Map([[\"C\",\"cwd\"],[\"f\",\"file\"],[\"z\",\"gzip\"],[\"P\",\"preservePaths\"],[\"U\",\"unlink\"],[\"strip-components\",\"strip\"],[\"stripComponents\",\"strip\"],[\"keep-newer\",\"newer\"],[\"keepNewer\",\"newer\"],[\"keep-newer-files\",\"newer\"],[\"keepNewerFiles\",\"newer\"],[\"k\",\"keep\"],[\"keep-existing\",\"keep\"],[\"keepExisting\",\"keep\"],[\"m\",\"noMtime\"],[\"no-mtime\",\"noMtime\"],[\"p\",\"preserveOwner\"],[\"L\",\"follow\"],[\"h\",\"follow\"]]);nye.exports=t=>t?Object.keys(t).map(e=>[rye.has(e)?rye.get(e):e,t[e]]).reduce((e,r)=>(e[r[0]]=r[1],e),Object.create(null)):{}});var bI=L((a$t,Aye)=>{\"use strict\";var iye=typeof process==\"object\"&&process?process:{stdout:null,stderr:null},uyt=Ie(\"events\"),sye=Ie(\"stream\"),oye=Ie(\"string_decoder\").StringDecoder,jp=Symbol(\"EOF\"),qp=Symbol(\"maybeEmitEnd\"),P0=Symbol(\"emittedEnd\"),MT=Symbol(\"emittingEnd\"),Qv=Symbol(\"emittedError\"),_T=Symbol(\"closed\"),aye=Symbol(\"read\"),UT=Symbol(\"flush\"),lye=Symbol(\"flushChunk\"),fl=Symbol(\"encoding\"),Gp=Symbol(\"decoder\"),HT=Symbol(\"flowing\"),Tv=Symbol(\"paused\"),DI=Symbol(\"resume\"),Ks=Symbol(\"bufferLength\"),d6=Symbol(\"bufferPush\"),m6=Symbol(\"bufferShift\"),zo=Symbol(\"objectMode\"),Zo=Symbol(\"destroyed\"),y6=Symbol(\"emitData\"),cye=Symbol(\"emitEnd\"),E6=Symbol(\"emitEnd2\"),Wp=Symbol(\"async\"),Rv=t=>Promise.resolve().then(t),uye=global._MP_NO_ITERATOR_SYMBOLS_!==\"1\",fyt=uye&&Symbol.asyncIterator||Symbol(\"asyncIterator not implemented\"),Ayt=uye&&Symbol.iterator||Symbol(\"iterator not implemented\"),pyt=t=>t===\"end\"||t===\"finish\"||t===\"prefinish\",hyt=t=>t instanceof ArrayBuffer||typeof t==\"object\"&&t.constructor&&t.constructor.name===\"ArrayBuffer\"&&t.byteLength>=0,gyt=t=>!Buffer.isBuffer(t)&&ArrayBuffer.isView(t),jT=class{constructor(e,r,s){this.src=e,this.dest=r,this.opts=s,this.ondrain=()=>e[DI](),r.on(\"drain\",this.ondrain)}unpipe(){this.dest.removeListener(\"drain\",this.ondrain)}proxyErrors(){}end(){this.unpipe(),this.opts.end&&this.dest.end()}},I6=class extends jT{unpipe(){this.src.removeListener(\"error\",this.proxyErrors),super.unpipe()}constructor(e,r,s){super(e,r,s),this.proxyErrors=a=>r.emit(\"error\",a),e.on(\"error\",this.proxyErrors)}};Aye.exports=class fye extends sye{constructor(e){super(),this[HT]=!1,this[Tv]=!1,this.pipes=[],this.buffer=[],this[zo]=e&&e.objectMode||!1,this[zo]?this[fl]=null:this[fl]=e&&e.encoding||null,this[fl]===\"buffer\"&&(this[fl]=null),this[Wp]=e&&!!e.async||!1,this[Gp]=this[fl]?new oye(this[fl]):null,this[jp]=!1,this[P0]=!1,this[MT]=!1,this[_T]=!1,this[Qv]=null,this.writable=!0,this.readable=!0,this[Ks]=0,this[Zo]=!1}get bufferLength(){return this[Ks]}get encoding(){return this[fl]}set encoding(e){if(this[zo])throw new Error(\"cannot set encoding in objectMode\");if(this[fl]&&e!==this[fl]&&(this[Gp]&&this[Gp].lastNeed||this[Ks]))throw new Error(\"cannot change encoding\");this[fl]!==e&&(this[Gp]=e?new oye(e):null,this.buffer.length&&(this.buffer=this.buffer.map(r=>this[Gp].write(r)))),this[fl]=e}setEncoding(e){this.encoding=e}get objectMode(){return this[zo]}set objectMode(e){this[zo]=this[zo]||!!e}get async(){return this[Wp]}set async(e){this[Wp]=this[Wp]||!!e}write(e,r,s){if(this[jp])throw new Error(\"write after end\");if(this[Zo])return this.emit(\"error\",Object.assign(new Error(\"Cannot call write after a stream was destroyed\"),{code:\"ERR_STREAM_DESTROYED\"})),!0;typeof r==\"function\"&&(s=r,r=\"utf8\"),r||(r=\"utf8\");let a=this[Wp]?Rv:n=>n();return!this[zo]&&!Buffer.isBuffer(e)&&(gyt(e)?e=Buffer.from(e.buffer,e.byteOffset,e.byteLength):hyt(e)?e=Buffer.from(e):typeof e!=\"string\"&&(this.objectMode=!0)),this[zo]?(this.flowing&&this[Ks]!==0&&this[UT](!0),this.flowing?this.emit(\"data\",e):this[d6](e),this[Ks]!==0&&this.emit(\"readable\"),s&&a(s),this.flowing):e.length?(typeof e==\"string\"&&!(r===this[fl]&&!this[Gp].lastNeed)&&(e=Buffer.from(e,r)),Buffer.isBuffer(e)&&this[fl]&&(e=this[Gp].write(e)),this.flowing&&this[Ks]!==0&&this[UT](!0),this.flowing?this.emit(\"data\",e):this[d6](e),this[Ks]!==0&&this.emit(\"readable\"),s&&a(s),this.flowing):(this[Ks]!==0&&this.emit(\"readable\"),s&&a(s),this.flowing)}read(e){if(this[Zo])return null;if(this[Ks]===0||e===0||e>this[Ks])return this[qp](),null;this[zo]&&(e=null),this.buffer.length>1&&!this[zo]&&(this.encoding?this.buffer=[this.buffer.join(\"\")]:this.buffer=[Buffer.concat(this.buffer,this[Ks])]);let r=this[aye](e||null,this.buffer[0]);return this[qp](),r}[aye](e,r){return e===r.length||e===null?this[m6]():(this.buffer[0]=r.slice(e),r=r.slice(0,e),this[Ks]-=e),this.emit(\"data\",r),!this.buffer.length&&!this[jp]&&this.emit(\"drain\"),r}end(e,r,s){return typeof e==\"function\"&&(s=e,e=null),typeof r==\"function\"&&(s=r,r=\"utf8\"),e&&this.write(e,r),s&&this.once(\"end\",s),this[jp]=!0,this.writable=!1,(this.flowing||!this[Tv])&&this[qp](),this}[DI](){this[Zo]||(this[Tv]=!1,this[HT]=!0,this.emit(\"resume\"),this.buffer.length?this[UT]():this[jp]?this[qp]():this.emit(\"drain\"))}resume(){return this[DI]()}pause(){this[HT]=!1,this[Tv]=!0}get destroyed(){return this[Zo]}get flowing(){return this[HT]}get paused(){return this[Tv]}[d6](e){this[zo]?this[Ks]+=1:this[Ks]+=e.length,this.buffer.push(e)}[m6](){return this.buffer.length&&(this[zo]?this[Ks]-=1:this[Ks]-=this.buffer[0].length),this.buffer.shift()}[UT](e){do;while(this[lye](this[m6]()));!e&&!this.buffer.length&&!this[jp]&&this.emit(\"drain\")}[lye](e){return e?(this.emit(\"data\",e),this.flowing):!1}pipe(e,r){if(this[Zo])return;let s=this[P0];return r=r||{},e===iye.stdout||e===iye.stderr?r.end=!1:r.end=r.end!==!1,r.proxyErrors=!!r.proxyErrors,s?r.end&&e.end():(this.pipes.push(r.proxyErrors?new I6(this,e,r):new jT(this,e,r)),this[Wp]?Rv(()=>this[DI]()):this[DI]()),e}unpipe(e){let r=this.pipes.find(s=>s.dest===e);r&&(this.pipes.splice(this.pipes.indexOf(r),1),r.unpipe())}addListener(e,r){return this.on(e,r)}on(e,r){let s=super.on(e,r);return e===\"data\"&&!this.pipes.length&&!this.flowing?this[DI]():e===\"readable\"&&this[Ks]!==0?super.emit(\"readable\"):pyt(e)&&this[P0]?(super.emit(e),this.removeAllListeners(e)):e===\"error\"&&this[Qv]&&(this[Wp]?Rv(()=>r.call(this,this[Qv])):r.call(this,this[Qv])),s}get emittedEnd(){return this[P0]}[qp](){!this[MT]&&!this[P0]&&!this[Zo]&&this.buffer.length===0&&this[jp]&&(this[MT]=!0,this.emit(\"end\"),this.emit(\"prefinish\"),this.emit(\"finish\"),this[_T]&&this.emit(\"close\"),this[MT]=!1)}emit(e,r,...s){if(e!==\"error\"&&e!==\"close\"&&e!==Zo&&this[Zo])return;if(e===\"data\")return r?this[Wp]?Rv(()=>this[y6](r)):this[y6](r):!1;if(e===\"end\")return this[cye]();if(e===\"close\"){if(this[_T]=!0,!this[P0]&&!this[Zo])return;let n=super.emit(\"close\");return this.removeAllListeners(\"close\"),n}else if(e===\"error\"){this[Qv]=r;let n=super.emit(\"error\",r);return this[qp](),n}else if(e===\"resume\"){let n=super.emit(\"resume\");return this[qp](),n}else if(e===\"finish\"||e===\"prefinish\"){let n=super.emit(e);return this.removeAllListeners(e),n}let a=super.emit(e,r,...s);return this[qp](),a}[y6](e){for(let s of this.pipes)s.dest.write(e)===!1&&this.pause();let r=super.emit(\"data\",e);return this[qp](),r}[cye](){this[P0]||(this[P0]=!0,this.readable=!1,this[Wp]?Rv(()=>this[E6]()):this[E6]())}[E6](){if(this[Gp]){let r=this[Gp].end();if(r){for(let s of this.pipes)s.dest.write(r);super.emit(\"data\",r)}}for(let r of this.pipes)r.end();let e=super.emit(\"end\");return this.removeAllListeners(\"end\"),e}collect(){let e=[];this[zo]||(e.dataLength=0);let r=this.promise();return this.on(\"data\",s=>{e.push(s),this[zo]||(e.dataLength+=s.length)}),r.then(()=>e)}concat(){return this[zo]?Promise.reject(new Error(\"cannot concat in objectMode\")):this.collect().then(e=>this[zo]?Promise.reject(new Error(\"cannot concat in objectMode\")):this[fl]?e.join(\"\"):Buffer.concat(e,e.dataLength))}promise(){return new Promise((e,r)=>{this.on(Zo,()=>r(new Error(\"stream destroyed\"))),this.on(\"error\",s=>r(s)),this.on(\"end\",()=>e())})}[fyt](){return{next:()=>{let r=this.read();if(r!==null)return Promise.resolve({done:!1,value:r});if(this[jp])return Promise.resolve({done:!0});let s=null,a=null,n=h=>{this.removeListener(\"data\",c),this.removeListener(\"end\",f),a(h)},c=h=>{this.removeListener(\"error\",n),this.removeListener(\"end\",f),this.pause(),s({value:h,done:!!this[jp]})},f=()=>{this.removeListener(\"error\",n),this.removeListener(\"data\",c),s({done:!0})},p=()=>n(new Error(\"stream destroyed\"));return new Promise((h,E)=>{a=E,s=h,this.once(Zo,p),this.once(\"error\",n),this.once(\"end\",f),this.once(\"data\",c)})}}}[Ayt](){return{next:()=>{let r=this.read();return{value:r,done:r===null}}}}destroy(e){return this[Zo]?(e?this.emit(\"error\",e):this.emit(Zo),this):(this[Zo]=!0,this.buffer.length=0,this[Ks]=0,typeof this.close==\"function\"&&!this[_T]&&this.close(),e?this.emit(\"error\",e):this.emit(Zo),this)}static isStream(e){return!!e&&(e instanceof fye||e instanceof sye||e instanceof uyt&&(typeof e.pipe==\"function\"||typeof e.write==\"function\"&&typeof e.end==\"function\"))}}});var hye=L((l$t,pye)=>{var dyt=Ie(\"zlib\").constants||{ZLIB_VERNUM:4736};pye.exports=Object.freeze(Object.assign(Object.create(null),{Z_NO_FLUSH:0,Z_PARTIAL_FLUSH:1,Z_SYNC_FLUSH:2,Z_FULL_FLUSH:3,Z_FINISH:4,Z_BLOCK:5,Z_OK:0,Z_STREAM_END:1,Z_NEED_DICT:2,Z_ERRNO:-1,Z_STREAM_ERROR:-2,Z_DATA_ERROR:-3,Z_MEM_ERROR:-4,Z_BUF_ERROR:-5,Z_VERSION_ERROR:-6,Z_NO_COMPRESSION:0,Z_BEST_SPEED:1,Z_BEST_COMPRESSION:9,Z_DEFAULT_COMPRESSION:-1,Z_FILTERED:1,Z_HUFFMAN_ONLY:2,Z_RLE:3,Z_FIXED:4,Z_DEFAULT_STRATEGY:0,DEFLATE:1,INFLATE:2,GZIP:3,GUNZIP:4,DEFLATERAW:5,INFLATERAW:6,UNZIP:7,BROTLI_DECODE:8,BROTLI_ENCODE:9,Z_MIN_WINDOWBITS:8,Z_MAX_WINDOWBITS:15,Z_DEFAULT_WINDOWBITS:15,Z_MIN_CHUNK:64,Z_MAX_CHUNK:1/0,Z_DEFAULT_CHUNK:16384,Z_MIN_MEMLEVEL:1,Z_MAX_MEMLEVEL:9,Z_DEFAULT_MEMLEVEL:8,Z_MIN_LEVEL:-1,Z_MAX_LEVEL:9,Z_DEFAULT_LEVEL:-1,BROTLI_OPERATION_PROCESS:0,BROTLI_OPERATION_FLUSH:1,BROTLI_OPERATION_FINISH:2,BROTLI_OPERATION_EMIT_METADATA:3,BROTLI_MODE_GENERIC:0,BROTLI_MODE_TEXT:1,BROTLI_MODE_FONT:2,BROTLI_DEFAULT_MODE:0,BROTLI_MIN_QUALITY:0,BROTLI_MAX_QUALITY:11,BROTLI_DEFAULT_QUALITY:11,BROTLI_MIN_WINDOW_BITS:10,BROTLI_MAX_WINDOW_BITS:24,BROTLI_LARGE_MAX_WINDOW_BITS:30,BROTLI_DEFAULT_WINDOW:22,BROTLI_MIN_INPUT_BLOCK_BITS:16,BROTLI_MAX_INPUT_BLOCK_BITS:24,BROTLI_PARAM_MODE:0,BROTLI_PARAM_QUALITY:1,BROTLI_PARAM_LGWIN:2,BROTLI_PARAM_LGBLOCK:3,BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING:4,BROTLI_PARAM_SIZE_HINT:5,BROTLI_PARAM_LARGE_WINDOW:6,BROTLI_PARAM_NPOSTFIX:7,BROTLI_PARAM_NDIRECT:8,BROTLI_DECODER_RESULT_ERROR:0,BROTLI_DECODER_RESULT_SUCCESS:1,BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT:2,BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT:3,BROTLI_DECODER_PARAM_DISABLE_RING_BUFFER_REALLOCATION:0,BROTLI_DECODER_PARAM_LARGE_WINDOW:1,BROTLI_DECODER_NO_ERROR:0,BROTLI_DECODER_SUCCESS:1,BROTLI_DECODER_NEEDS_MORE_INPUT:2,BROTLI_DECODER_NEEDS_MORE_OUTPUT:3,BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_NIBBLE:-1,BROTLI_DECODER_ERROR_FORMAT_RESERVED:-2,BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_META_NIBBLE:-3,BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_ALPHABET:-4,BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_SAME:-5,BROTLI_DECODER_ERROR_FORMAT_CL_SPACE:-6,BROTLI_DECODER_ERROR_FORMAT_HUFFMAN_SPACE:-7,BROTLI_DECODER_ERROR_FORMAT_CONTEXT_MAP_REPEAT:-8,BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_1:-9,BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_2:-10,BROTLI_DECODER_ERROR_FORMAT_TRANSFORM:-11,BROTLI_DECODER_ERROR_FORMAT_DICTIONARY:-12,BROTLI_DECODER_ERROR_FORMAT_WINDOW_BITS:-13,BROTLI_DECODER_ERROR_FORMAT_PADDING_1:-14,BROTLI_DECODER_ERROR_FORMAT_PADDING_2:-15,BROTLI_DECODER_ERROR_FORMAT_DISTANCE:-16,BROTLI_DECODER_ERROR_DICTIONARY_NOT_SET:-19,BROTLI_DECODER_ERROR_INVALID_ARGUMENTS:-20,BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MODES:-21,BROTLI_DECODER_ERROR_ALLOC_TREE_GROUPS:-22,BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MAP:-25,BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_1:-26,BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_2:-27,BROTLI_DECODER_ERROR_ALLOC_BLOCK_TYPE_TREES:-30,BROTLI_DECODER_ERROR_UNREACHABLE:-31},dyt))});var O6=L(zl=>{\"use strict\";var S6=Ie(\"assert\"),x0=Ie(\"buffer\").Buffer,mye=Ie(\"zlib\"),cm=zl.constants=hye(),myt=bI(),gye=x0.concat,um=Symbol(\"_superWrite\"),xI=class extends Error{constructor(e){super(\"zlib: \"+e.message),this.code=e.code,this.errno=e.errno,this.code||(this.code=\"ZLIB_ERROR\"),this.message=\"zlib: \"+e.message,Error.captureStackTrace(this,this.constructor)}get name(){return\"ZlibError\"}},yyt=Symbol(\"opts\"),Fv=Symbol(\"flushFlag\"),dye=Symbol(\"finishFlushFlag\"),N6=Symbol(\"fullFlushFlag\"),Ii=Symbol(\"handle\"),qT=Symbol(\"onError\"),PI=Symbol(\"sawError\"),C6=Symbol(\"level\"),w6=Symbol(\"strategy\"),B6=Symbol(\"ended\"),c$t=Symbol(\"_defaultFullFlush\"),GT=class extends myt{constructor(e,r){if(!e||typeof e!=\"object\")throw new TypeError(\"invalid options for ZlibBase constructor\");super(e),this[PI]=!1,this[B6]=!1,this[yyt]=e,this[Fv]=e.flush,this[dye]=e.finishFlush;try{this[Ii]=new mye[r](e)}catch(s){throw new xI(s)}this[qT]=s=>{this[PI]||(this[PI]=!0,this.close(),this.emit(\"error\",s))},this[Ii].on(\"error\",s=>this[qT](new xI(s))),this.once(\"end\",()=>this.close)}close(){this[Ii]&&(this[Ii].close(),this[Ii]=null,this.emit(\"close\"))}reset(){if(!this[PI])return S6(this[Ii],\"zlib binding closed\"),this[Ii].reset()}flush(e){this.ended||(typeof e!=\"number\"&&(e=this[N6]),this.write(Object.assign(x0.alloc(0),{[Fv]:e})))}end(e,r,s){return e&&this.write(e,r),this.flush(this[dye]),this[B6]=!0,super.end(null,null,s)}get ended(){return this[B6]}write(e,r,s){if(typeof r==\"function\"&&(s=r,r=\"utf8\"),typeof e==\"string\"&&(e=x0.from(e,r)),this[PI])return;S6(this[Ii],\"zlib binding closed\");let a=this[Ii]._handle,n=a.close;a.close=()=>{};let c=this[Ii].close;this[Ii].close=()=>{},x0.concat=h=>h;let f;try{let h=typeof e[Fv]==\"number\"?e[Fv]:this[Fv];f=this[Ii]._processChunk(e,h),x0.concat=gye}catch(h){x0.concat=gye,this[qT](new xI(h))}finally{this[Ii]&&(this[Ii]._handle=a,a.close=n,this[Ii].close=c,this[Ii].removeAllListeners(\"error\"))}this[Ii]&&this[Ii].on(\"error\",h=>this[qT](new xI(h)));let p;if(f)if(Array.isArray(f)&&f.length>0){p=this[um](x0.from(f[0]));for(let h=1;h<f.length;h++)p=this[um](f[h])}else p=this[um](x0.from(f));return s&&s(),p}[um](e){return super.write(e)}},Yp=class extends GT{constructor(e,r){e=e||{},e.flush=e.flush||cm.Z_NO_FLUSH,e.finishFlush=e.finishFlush||cm.Z_FINISH,super(e,r),this[N6]=cm.Z_FULL_FLUSH,this[C6]=e.level,this[w6]=e.strategy}params(e,r){if(!this[PI]){if(!this[Ii])throw new Error(\"cannot switch params when binding is closed\");if(!this[Ii].params)throw new Error(\"not supported in this implementation\");if(this[C6]!==e||this[w6]!==r){this.flush(cm.Z_SYNC_FLUSH),S6(this[Ii],\"zlib binding closed\");let s=this[Ii].flush;this[Ii].flush=(a,n)=>{this.flush(a),n()};try{this[Ii].params(e,r)}finally{this[Ii].flush=s}this[Ii]&&(this[C6]=e,this[w6]=r)}}}},D6=class extends Yp{constructor(e){super(e,\"Deflate\")}},b6=class extends Yp{constructor(e){super(e,\"Inflate\")}},v6=Symbol(\"_portable\"),P6=class extends Yp{constructor(e){super(e,\"Gzip\"),this[v6]=e&&!!e.portable}[um](e){return this[v6]?(this[v6]=!1,e[9]=255,super[um](e)):super[um](e)}},x6=class extends Yp{constructor(e){super(e,\"Gunzip\")}},k6=class extends Yp{constructor(e){super(e,\"DeflateRaw\")}},Q6=class extends Yp{constructor(e){super(e,\"InflateRaw\")}},T6=class extends Yp{constructor(e){super(e,\"Unzip\")}},WT=class extends GT{constructor(e,r){e=e||{},e.flush=e.flush||cm.BROTLI_OPERATION_PROCESS,e.finishFlush=e.finishFlush||cm.BROTLI_OPERATION_FINISH,super(e,r),this[N6]=cm.BROTLI_OPERATION_FLUSH}},R6=class extends WT{constructor(e){super(e,\"BrotliCompress\")}},F6=class extends WT{constructor(e){super(e,\"BrotliDecompress\")}};zl.Deflate=D6;zl.Inflate=b6;zl.Gzip=P6;zl.Gunzip=x6;zl.DeflateRaw=k6;zl.InflateRaw=Q6;zl.Unzip=T6;typeof mye.BrotliCompress==\"function\"?(zl.BrotliCompress=R6,zl.BrotliDecompress=F6):zl.BrotliCompress=zl.BrotliDecompress=class{constructor(){throw new Error(\"Brotli is not supported in this version of Node.js\")}}});var kI=L((A$t,yye)=>{var Eyt=process.env.TESTING_TAR_FAKE_PLATFORM||process.platform;yye.exports=Eyt!==\"win32\"?t=>t:t=>t&&t.replace(/\\\\/g,\"/\")});var YT=L((h$t,Eye)=>{\"use strict\";var Iyt=bI(),L6=kI(),M6=Symbol(\"slurp\");Eye.exports=class extends Iyt{constructor(e,r,s){switch(super(),this.pause(),this.extended=r,this.globalExtended=s,this.header=e,this.startBlockSize=512*Math.ceil(e.size/512),this.blockRemain=this.startBlockSize,this.remain=e.size,this.type=e.type,this.meta=!1,this.ignore=!1,this.type){case\"File\":case\"OldFile\":case\"Link\":case\"SymbolicLink\":case\"CharacterDevice\":case\"BlockDevice\":case\"Directory\":case\"FIFO\":case\"ContiguousFile\":case\"GNUDumpDir\":break;case\"NextFileHasLongLinkpath\":case\"NextFileHasLongPath\":case\"OldGnuLongPath\":case\"GlobalExtendedHeader\":case\"ExtendedHeader\":case\"OldExtendedHeader\":this.meta=!0;break;default:this.ignore=!0}this.path=L6(e.path),this.mode=e.mode,this.mode&&(this.mode=this.mode&4095),this.uid=e.uid,this.gid=e.gid,this.uname=e.uname,this.gname=e.gname,this.size=e.size,this.mtime=e.mtime,this.atime=e.atime,this.ctime=e.ctime,this.linkpath=L6(e.linkpath),this.uname=e.uname,this.gname=e.gname,r&&this[M6](r),s&&this[M6](s,!0)}write(e){let r=e.length;if(r>this.blockRemain)throw new Error(\"writing more to entry than is appropriate\");let s=this.remain,a=this.blockRemain;return this.remain=Math.max(0,s-r),this.blockRemain=Math.max(0,a-r),this.ignore?!0:s>=r?super.write(e):super.write(e.slice(0,s))}[M6](e,r){for(let s in e)e[s]!==null&&e[s]!==void 0&&!(r&&s===\"path\")&&(this[s]=s===\"path\"||s===\"linkpath\"?L6(e[s]):e[s])}}});var _6=L(VT=>{\"use strict\";VT.name=new Map([[\"0\",\"File\"],[\"\",\"OldFile\"],[\"1\",\"Link\"],[\"2\",\"SymbolicLink\"],[\"3\",\"CharacterDevice\"],[\"4\",\"BlockDevice\"],[\"5\",\"Directory\"],[\"6\",\"FIFO\"],[\"7\",\"ContiguousFile\"],[\"g\",\"GlobalExtendedHeader\"],[\"x\",\"ExtendedHeader\"],[\"A\",\"SolarisACL\"],[\"D\",\"GNUDumpDir\"],[\"I\",\"Inode\"],[\"K\",\"NextFileHasLongLinkpath\"],[\"L\",\"NextFileHasLongPath\"],[\"M\",\"ContinuationFile\"],[\"N\",\"OldGnuLongPath\"],[\"S\",\"SparseFile\"],[\"V\",\"TapeVolumeHeader\"],[\"X\",\"OldExtendedHeader\"]]);VT.code=new Map(Array.from(VT.name).map(t=>[t[1],t[0]]))});var Bye=L((d$t,wye)=>{\"use strict\";var Cyt=(t,e)=>{if(Number.isSafeInteger(t))t<0?Byt(t,e):wyt(t,e);else throw Error(\"cannot encode number outside of javascript safe integer range\");return e},wyt=(t,e)=>{e[0]=128;for(var r=e.length;r>1;r--)e[r-1]=t&255,t=Math.floor(t/256)},Byt=(t,e)=>{e[0]=255;var r=!1;t=t*-1;for(var s=e.length;s>1;s--){var a=t&255;t=Math.floor(t/256),r?e[s-1]=Iye(a):a===0?e[s-1]=0:(r=!0,e[s-1]=Cye(a))}},vyt=t=>{let e=t[0],r=e===128?Dyt(t.slice(1,t.length)):e===255?Syt(t):null;if(r===null)throw Error(\"invalid base256 encoding\");if(!Number.isSafeInteger(r))throw Error(\"parsed number outside of javascript safe integer range\");return r},Syt=t=>{for(var e=t.length,r=0,s=!1,a=e-1;a>-1;a--){var n=t[a],c;s?c=Iye(n):n===0?c=n:(s=!0,c=Cye(n)),c!==0&&(r-=c*Math.pow(256,e-a-1))}return r},Dyt=t=>{for(var e=t.length,r=0,s=e-1;s>-1;s--){var a=t[s];a!==0&&(r+=a*Math.pow(256,e-s-1))}return r},Iye=t=>(255^t)&255,Cye=t=>(255^t)+1&255;wye.exports={encode:Cyt,parse:vyt}});var TI=L((m$t,Sye)=>{\"use strict\";var U6=_6(),QI=Ie(\"path\").posix,vye=Bye(),H6=Symbol(\"slurp\"),Zl=Symbol(\"type\"),G6=class{constructor(e,r,s,a){this.cksumValid=!1,this.needPax=!1,this.nullBlock=!1,this.block=null,this.path=null,this.mode=null,this.uid=null,this.gid=null,this.size=null,this.mtime=null,this.cksum=null,this[Zl]=\"0\",this.linkpath=null,this.uname=null,this.gname=null,this.devmaj=0,this.devmin=0,this.atime=null,this.ctime=null,Buffer.isBuffer(e)?this.decode(e,r||0,s,a):e&&this.set(e)}decode(e,r,s,a){if(r||(r=0),!e||!(e.length>=r+512))throw new Error(\"need 512 bytes for header\");if(this.path=fm(e,r,100),this.mode=k0(e,r+100,8),this.uid=k0(e,r+108,8),this.gid=k0(e,r+116,8),this.size=k0(e,r+124,12),this.mtime=j6(e,r+136,12),this.cksum=k0(e,r+148,12),this[H6](s),this[H6](a,!0),this[Zl]=fm(e,r+156,1),this[Zl]===\"\"&&(this[Zl]=\"0\"),this[Zl]===\"0\"&&this.path.substr(-1)===\"/\"&&(this[Zl]=\"5\"),this[Zl]===\"5\"&&(this.size=0),this.linkpath=fm(e,r+157,100),e.slice(r+257,r+265).toString()===\"ustar\\x0000\")if(this.uname=fm(e,r+265,32),this.gname=fm(e,r+297,32),this.devmaj=k0(e,r+329,8),this.devmin=k0(e,r+337,8),e[r+475]!==0){let c=fm(e,r+345,155);this.path=c+\"/\"+this.path}else{let c=fm(e,r+345,130);c&&(this.path=c+\"/\"+this.path),this.atime=j6(e,r+476,12),this.ctime=j6(e,r+488,12)}let n=8*32;for(let c=r;c<r+148;c++)n+=e[c];for(let c=r+156;c<r+512;c++)n+=e[c];this.cksumValid=n===this.cksum,this.cksum===null&&n===8*32&&(this.nullBlock=!0)}[H6](e,r){for(let s in e)e[s]!==null&&e[s]!==void 0&&!(r&&s===\"path\")&&(this[s]=e[s])}encode(e,r){if(e||(e=this.block=Buffer.alloc(512),r=0),r||(r=0),!(e.length>=r+512))throw new Error(\"need 512 bytes for header\");let s=this.ctime||this.atime?130:155,a=byt(this.path||\"\",s),n=a[0],c=a[1];this.needPax=a[2],this.needPax=Am(e,r,100,n)||this.needPax,this.needPax=Q0(e,r+100,8,this.mode)||this.needPax,this.needPax=Q0(e,r+108,8,this.uid)||this.needPax,this.needPax=Q0(e,r+116,8,this.gid)||this.needPax,this.needPax=Q0(e,r+124,12,this.size)||this.needPax,this.needPax=q6(e,r+136,12,this.mtime)||this.needPax,e[r+156]=this[Zl].charCodeAt(0),this.needPax=Am(e,r+157,100,this.linkpath)||this.needPax,e.write(\"ustar\\x0000\",r+257,8),this.needPax=Am(e,r+265,32,this.uname)||this.needPax,this.needPax=Am(e,r+297,32,this.gname)||this.needPax,this.needPax=Q0(e,r+329,8,this.devmaj)||this.needPax,this.needPax=Q0(e,r+337,8,this.devmin)||this.needPax,this.needPax=Am(e,r+345,s,c)||this.needPax,e[r+475]!==0?this.needPax=Am(e,r+345,155,c)||this.needPax:(this.needPax=Am(e,r+345,130,c)||this.needPax,this.needPax=q6(e,r+476,12,this.atime)||this.needPax,this.needPax=q6(e,r+488,12,this.ctime)||this.needPax);let f=8*32;for(let p=r;p<r+148;p++)f+=e[p];for(let p=r+156;p<r+512;p++)f+=e[p];return this.cksum=f,Q0(e,r+148,8,this.cksum),this.cksumValid=!0,this.needPax}set(e){for(let r in e)e[r]!==null&&e[r]!==void 0&&(this[r]=e[r])}get type(){return U6.name.get(this[Zl])||this[Zl]}get typeKey(){return this[Zl]}set type(e){U6.code.has(e)?this[Zl]=U6.code.get(e):this[Zl]=e}},byt=(t,e)=>{let s=t,a=\"\",n,c=QI.parse(t).root||\".\";if(Buffer.byteLength(s)<100)n=[s,a,!1];else{a=QI.dirname(s),s=QI.basename(s);do Buffer.byteLength(s)<=100&&Buffer.byteLength(a)<=e?n=[s,a,!1]:Buffer.byteLength(s)>100&&Buffer.byteLength(a)<=e?n=[s.substr(0,99),a,!0]:(s=QI.join(QI.basename(a),s),a=QI.dirname(a));while(a!==c&&!n);n||(n=[t.substr(0,99),\"\",!0])}return n},fm=(t,e,r)=>t.slice(e,e+r).toString(\"utf8\").replace(/\\0.*/,\"\"),j6=(t,e,r)=>Pyt(k0(t,e,r)),Pyt=t=>t===null?null:new Date(t*1e3),k0=(t,e,r)=>t[e]&128?vye.parse(t.slice(e,e+r)):kyt(t,e,r),xyt=t=>isNaN(t)?null:t,kyt=(t,e,r)=>xyt(parseInt(t.slice(e,e+r).toString(\"utf8\").replace(/\\0.*$/,\"\").trim(),8)),Qyt={12:8589934591,8:2097151},Q0=(t,e,r,s)=>s===null?!1:s>Qyt[r]||s<0?(vye.encode(s,t.slice(e,e+r)),!0):(Tyt(t,e,r,s),!1),Tyt=(t,e,r,s)=>t.write(Ryt(s,r),e,r,\"ascii\"),Ryt=(t,e)=>Fyt(Math.floor(t).toString(8),e),Fyt=(t,e)=>(t.length===e-1?t:new Array(e-t.length-1).join(\"0\")+t+\" \")+\"\\0\",q6=(t,e,r,s)=>s===null?!1:Q0(t,e,r,s.getTime()/1e3),Nyt=new Array(156).join(\"\\0\"),Am=(t,e,r,s)=>s===null?!1:(t.write(s+Nyt,e,r,\"utf8\"),s.length!==Buffer.byteLength(s)||s.length>r);Sye.exports=G6});var KT=L((y$t,Dye)=>{\"use strict\";var Oyt=TI(),Lyt=Ie(\"path\"),Nv=class{constructor(e,r){this.atime=e.atime||null,this.charset=e.charset||null,this.comment=e.comment||null,this.ctime=e.ctime||null,this.gid=e.gid||null,this.gname=e.gname||null,this.linkpath=e.linkpath||null,this.mtime=e.mtime||null,this.path=e.path||null,this.size=e.size||null,this.uid=e.uid||null,this.uname=e.uname||null,this.dev=e.dev||null,this.ino=e.ino||null,this.nlink=e.nlink||null,this.global=r||!1}encode(){let e=this.encodeBody();if(e===\"\")return null;let r=Buffer.byteLength(e),s=512*Math.ceil(1+r/512),a=Buffer.allocUnsafe(s);for(let n=0;n<512;n++)a[n]=0;new Oyt({path:(\"PaxHeader/\"+Lyt.basename(this.path)).slice(0,99),mode:this.mode||420,uid:this.uid||null,gid:this.gid||null,size:r,mtime:this.mtime||null,type:this.global?\"GlobalExtendedHeader\":\"ExtendedHeader\",linkpath:\"\",uname:this.uname||\"\",gname:this.gname||\"\",devmaj:0,devmin:0,atime:this.atime||null,ctime:this.ctime||null}).encode(a),a.write(e,512,r,\"utf8\");for(let n=r+512;n<a.length;n++)a[n]=0;return a}encodeBody(){return this.encodeField(\"path\")+this.encodeField(\"ctime\")+this.encodeField(\"atime\")+this.encodeField(\"dev\")+this.encodeField(\"ino\")+this.encodeField(\"nlink\")+this.encodeField(\"charset\")+this.encodeField(\"comment\")+this.encodeField(\"gid\")+this.encodeField(\"gname\")+this.encodeField(\"linkpath\")+this.encodeField(\"mtime\")+this.encodeField(\"size\")+this.encodeField(\"uid\")+this.encodeField(\"uname\")}encodeField(e){if(this[e]===null||this[e]===void 0)return\"\";let r=this[e]instanceof Date?this[e].getTime()/1e3:this[e],s=\" \"+(e===\"dev\"||e===\"ino\"||e===\"nlink\"?\"SCHILY.\":\"\")+e+\"=\"+r+`\n`,a=Buffer.byteLength(s),n=Math.floor(Math.log(a)/Math.log(10))+1;return a+n>=Math.pow(10,n)&&(n+=1),n+a+s}};Nv.parse=(t,e,r)=>new Nv(Myt(_yt(t),e),r);var Myt=(t,e)=>e?Object.keys(t).reduce((r,s)=>(r[s]=t[s],r),e):t,_yt=t=>t.replace(/\\n$/,\"\").split(`\n`).reduce(Uyt,Object.create(null)),Uyt=(t,e)=>{let r=parseInt(e,10);if(r!==Buffer.byteLength(e)+1)return t;e=e.substr((r+\" \").length);let s=e.split(\"=\"),a=s.shift().replace(/^SCHILY\\.(dev|ino|nlink)/,\"$1\");if(!a)return t;let n=s.join(\"=\");return t[a]=/^([A-Z]+\\.)?([mac]|birth|creation)time$/.test(a)?new Date(n*1e3):/^[0-9]+$/.test(n)?+n:n,t};Dye.exports=Nv});var RI=L((E$t,bye)=>{bye.exports=t=>{let e=t.length-1,r=-1;for(;e>-1&&t.charAt(e)===\"/\";)r=e,e--;return r===-1?t:t.slice(0,r)}});var JT=L((I$t,Pye)=>{\"use strict\";Pye.exports=t=>class extends t{warn(e,r,s={}){this.file&&(s.file=this.file),this.cwd&&(s.cwd=this.cwd),s.code=r instanceof Error&&r.code||e,s.tarCode=e,!this.strict&&s.recoverable!==!1?(r instanceof Error&&(s=Object.assign(r,s),r=r.message),this.emit(\"warn\",s.tarCode,r,s)):r instanceof Error?this.emit(\"error\",Object.assign(r,s)):this.emit(\"error\",Object.assign(new Error(`${e}: ${r}`),s))}}});var Y6=L((w$t,xye)=>{\"use strict\";var zT=[\"|\",\"<\",\">\",\"?\",\":\"],W6=zT.map(t=>String.fromCharCode(61440+t.charCodeAt(0))),Hyt=new Map(zT.map((t,e)=>[t,W6[e]])),jyt=new Map(W6.map((t,e)=>[t,zT[e]]));xye.exports={encode:t=>zT.reduce((e,r)=>e.split(r).join(Hyt.get(r)),t),decode:t=>W6.reduce((e,r)=>e.split(r).join(jyt.get(r)),t)}});var V6=L((B$t,Qye)=>{var{isAbsolute:qyt,parse:kye}=Ie(\"path\").win32;Qye.exports=t=>{let e=\"\",r=kye(t);for(;qyt(t)||r.root;){let s=t.charAt(0)===\"/\"&&t.slice(0,4)!==\"//?/\"?\"/\":r.root;t=t.substr(s.length),e+=s,r=kye(t)}return[e,t]}});var Rye=L((v$t,Tye)=>{\"use strict\";Tye.exports=(t,e,r)=>(t&=4095,r&&(t=(t|384)&-19),e&&(t&256&&(t|=64),t&32&&(t|=8),t&4&&(t|=1)),t)});var nq=L((b$t,Vye)=>{\"use strict\";var Uye=bI(),Hye=KT(),jye=TI(),sA=Ie(\"fs\"),Fye=Ie(\"path\"),iA=kI(),Gyt=RI(),qye=(t,e)=>e?(t=iA(t).replace(/^\\.(\\/|$)/,\"\"),Gyt(e)+\"/\"+t):iA(t),Wyt=16*1024*1024,Nye=Symbol(\"process\"),Oye=Symbol(\"file\"),Lye=Symbol(\"directory\"),J6=Symbol(\"symlink\"),Mye=Symbol(\"hardlink\"),Ov=Symbol(\"header\"),ZT=Symbol(\"read\"),z6=Symbol(\"lstat\"),XT=Symbol(\"onlstat\"),Z6=Symbol(\"onread\"),X6=Symbol(\"onreadlink\"),$6=Symbol(\"openfile\"),eq=Symbol(\"onopenfile\"),T0=Symbol(\"close\"),$T=Symbol(\"mode\"),tq=Symbol(\"awaitDrain\"),K6=Symbol(\"ondrain\"),oA=Symbol(\"prefix\"),_ye=Symbol(\"hadError\"),Gye=JT(),Yyt=Y6(),Wye=V6(),Yye=Rye(),eR=Gye(class extends Uye{constructor(e,r){if(r=r||{},super(r),typeof e!=\"string\")throw new TypeError(\"path is required\");this.path=iA(e),this.portable=!!r.portable,this.myuid=process.getuid&&process.getuid()||0,this.myuser=process.env.USER||\"\",this.maxReadSize=r.maxReadSize||Wyt,this.linkCache=r.linkCache||new Map,this.statCache=r.statCache||new Map,this.preservePaths=!!r.preservePaths,this.cwd=iA(r.cwd||process.cwd()),this.strict=!!r.strict,this.noPax=!!r.noPax,this.noMtime=!!r.noMtime,this.mtime=r.mtime||null,this.prefix=r.prefix?iA(r.prefix):null,this.fd=null,this.blockLen=null,this.blockRemain=null,this.buf=null,this.offset=null,this.length=null,this.pos=null,this.remain=null,typeof r.onwarn==\"function\"&&this.on(\"warn\",r.onwarn);let s=!1;if(!this.preservePaths){let[a,n]=Wye(this.path);a&&(this.path=n,s=a)}this.win32=!!r.win32||process.platform===\"win32\",this.win32&&(this.path=Yyt.decode(this.path.replace(/\\\\/g,\"/\")),e=e.replace(/\\\\/g,\"/\")),this.absolute=iA(r.absolute||Fye.resolve(this.cwd,e)),this.path===\"\"&&(this.path=\"./\"),s&&this.warn(\"TAR_ENTRY_INFO\",`stripping ${s} from absolute path`,{entry:this,path:s+this.path}),this.statCache.has(this.absolute)?this[XT](this.statCache.get(this.absolute)):this[z6]()}emit(e,...r){return e===\"error\"&&(this[_ye]=!0),super.emit(e,...r)}[z6](){sA.lstat(this.absolute,(e,r)=>{if(e)return this.emit(\"error\",e);this[XT](r)})}[XT](e){this.statCache.set(this.absolute,e),this.stat=e,e.isFile()||(e.size=0),this.type=Kyt(e),this.emit(\"stat\",e),this[Nye]()}[Nye](){switch(this.type){case\"File\":return this[Oye]();case\"Directory\":return this[Lye]();case\"SymbolicLink\":return this[J6]();default:return this.end()}}[$T](e){return Yye(e,this.type===\"Directory\",this.portable)}[oA](e){return qye(e,this.prefix)}[Ov](){this.type===\"Directory\"&&this.portable&&(this.noMtime=!0),this.header=new jye({path:this[oA](this.path),linkpath:this.type===\"Link\"?this[oA](this.linkpath):this.linkpath,mode:this[$T](this.stat.mode),uid:this.portable?null:this.stat.uid,gid:this.portable?null:this.stat.gid,size:this.stat.size,mtime:this.noMtime?null:this.mtime||this.stat.mtime,type:this.type,uname:this.portable?null:this.stat.uid===this.myuid?this.myuser:\"\",atime:this.portable?null:this.stat.atime,ctime:this.portable?null:this.stat.ctime}),this.header.encode()&&!this.noPax&&super.write(new Hye({atime:this.portable?null:this.header.atime,ctime:this.portable?null:this.header.ctime,gid:this.portable?null:this.header.gid,mtime:this.noMtime?null:this.mtime||this.header.mtime,path:this[oA](this.path),linkpath:this.type===\"Link\"?this[oA](this.linkpath):this.linkpath,size:this.header.size,uid:this.portable?null:this.header.uid,uname:this.portable?null:this.header.uname,dev:this.portable?null:this.stat.dev,ino:this.portable?null:this.stat.ino,nlink:this.portable?null:this.stat.nlink}).encode()),super.write(this.header.block)}[Lye](){this.path.substr(-1)!==\"/\"&&(this.path+=\"/\"),this.stat.size=0,this[Ov](),this.end()}[J6](){sA.readlink(this.absolute,(e,r)=>{if(e)return this.emit(\"error\",e);this[X6](r)})}[X6](e){this.linkpath=iA(e),this[Ov](),this.end()}[Mye](e){this.type=\"Link\",this.linkpath=iA(Fye.relative(this.cwd,e)),this.stat.size=0,this[Ov](),this.end()}[Oye](){if(this.stat.nlink>1){let e=this.stat.dev+\":\"+this.stat.ino;if(this.linkCache.has(e)){let r=this.linkCache.get(e);if(r.indexOf(this.cwd)===0)return this[Mye](r)}this.linkCache.set(e,this.absolute)}if(this[Ov](),this.stat.size===0)return this.end();this[$6]()}[$6](){sA.open(this.absolute,\"r\",(e,r)=>{if(e)return this.emit(\"error\",e);this[eq](r)})}[eq](e){if(this.fd=e,this[_ye])return this[T0]();this.blockLen=512*Math.ceil(this.stat.size/512),this.blockRemain=this.blockLen;let r=Math.min(this.blockLen,this.maxReadSize);this.buf=Buffer.allocUnsafe(r),this.offset=0,this.pos=0,this.remain=this.stat.size,this.length=this.buf.length,this[ZT]()}[ZT](){let{fd:e,buf:r,offset:s,length:a,pos:n}=this;sA.read(e,r,s,a,n,(c,f)=>{if(c)return this[T0](()=>this.emit(\"error\",c));this[Z6](f)})}[T0](e){sA.close(this.fd,e)}[Z6](e){if(e<=0&&this.remain>0){let a=new Error(\"encountered unexpected EOF\");return a.path=this.absolute,a.syscall=\"read\",a.code=\"EOF\",this[T0](()=>this.emit(\"error\",a))}if(e>this.remain){let a=new Error(\"did not encounter expected EOF\");return a.path=this.absolute,a.syscall=\"read\",a.code=\"EOF\",this[T0](()=>this.emit(\"error\",a))}if(e===this.remain)for(let a=e;a<this.length&&e<this.blockRemain;a++)this.buf[a+this.offset]=0,e++,this.remain++;let r=this.offset===0&&e===this.buf.length?this.buf:this.buf.slice(this.offset,this.offset+e);this.write(r)?this[K6]():this[tq](()=>this[K6]())}[tq](e){this.once(\"drain\",e)}write(e){if(this.blockRemain<e.length){let r=new Error(\"writing more data than expected\");return r.path=this.absolute,this.emit(\"error\",r)}return this.remain-=e.length,this.blockRemain-=e.length,this.pos+=e.length,this.offset+=e.length,super.write(e)}[K6](){if(!this.remain)return this.blockRemain&&super.write(Buffer.alloc(this.blockRemain)),this[T0](e=>e?this.emit(\"error\",e):this.end());this.offset>=this.length&&(this.buf=Buffer.allocUnsafe(Math.min(this.blockRemain,this.buf.length)),this.offset=0),this.length=this.buf.length-this.offset,this[ZT]()}}),rq=class extends eR{[z6](){this[XT](sA.lstatSync(this.absolute))}[J6](){this[X6](sA.readlinkSync(this.absolute))}[$6](){this[eq](sA.openSync(this.absolute,\"r\"))}[ZT](){let e=!0;try{let{fd:r,buf:s,offset:a,length:n,pos:c}=this,f=sA.readSync(r,s,a,n,c);this[Z6](f),e=!1}finally{if(e)try{this[T0](()=>{})}catch{}}}[tq](e){e()}[T0](e){sA.closeSync(this.fd),e()}},Vyt=Gye(class extends Uye{constructor(e,r){r=r||{},super(r),this.preservePaths=!!r.preservePaths,this.portable=!!r.portable,this.strict=!!r.strict,this.noPax=!!r.noPax,this.noMtime=!!r.noMtime,this.readEntry=e,this.type=e.type,this.type===\"Directory\"&&this.portable&&(this.noMtime=!0),this.prefix=r.prefix||null,this.path=iA(e.path),this.mode=this[$T](e.mode),this.uid=this.portable?null:e.uid,this.gid=this.portable?null:e.gid,this.uname=this.portable?null:e.uname,this.gname=this.portable?null:e.gname,this.size=e.size,this.mtime=this.noMtime?null:r.mtime||e.mtime,this.atime=this.portable?null:e.atime,this.ctime=this.portable?null:e.ctime,this.linkpath=iA(e.linkpath),typeof r.onwarn==\"function\"&&this.on(\"warn\",r.onwarn);let s=!1;if(!this.preservePaths){let[a,n]=Wye(this.path);a&&(this.path=n,s=a)}this.remain=e.size,this.blockRemain=e.startBlockSize,this.header=new jye({path:this[oA](this.path),linkpath:this.type===\"Link\"?this[oA](this.linkpath):this.linkpath,mode:this.mode,uid:this.portable?null:this.uid,gid:this.portable?null:this.gid,size:this.size,mtime:this.noMtime?null:this.mtime,type:this.type,uname:this.portable?null:this.uname,atime:this.portable?null:this.atime,ctime:this.portable?null:this.ctime}),s&&this.warn(\"TAR_ENTRY_INFO\",`stripping ${s} from absolute path`,{entry:this,path:s+this.path}),this.header.encode()&&!this.noPax&&super.write(new Hye({atime:this.portable?null:this.atime,ctime:this.portable?null:this.ctime,gid:this.portable?null:this.gid,mtime:this.noMtime?null:this.mtime,path:this[oA](this.path),linkpath:this.type===\"Link\"?this[oA](this.linkpath):this.linkpath,size:this.size,uid:this.portable?null:this.uid,uname:this.portable?null:this.uname,dev:this.portable?null:this.readEntry.dev,ino:this.portable?null:this.readEntry.ino,nlink:this.portable?null:this.readEntry.nlink}).encode()),super.write(this.header.block),e.pipe(this)}[oA](e){return qye(e,this.prefix)}[$T](e){return Yye(e,this.type===\"Directory\",this.portable)}write(e){let r=e.length;if(r>this.blockRemain)throw new Error(\"writing more to entry than is appropriate\");return this.blockRemain-=r,super.write(e)}end(){return this.blockRemain&&super.write(Buffer.alloc(this.blockRemain)),super.end()}});eR.Sync=rq;eR.Tar=Vyt;var Kyt=t=>t.isFile()?\"File\":t.isDirectory()?\"Directory\":t.isSymbolicLink()?\"SymbolicLink\":\"Unsupported\";Vye.exports=eR});var cR=L((x$t,eEe)=>{\"use strict\";var aR=class{constructor(e,r){this.path=e||\"./\",this.absolute=r,this.entry=null,this.stat=null,this.readdir=null,this.pending=!1,this.ignore=!1,this.piped=!1}},Jyt=bI(),zyt=O6(),Zyt=YT(),Aq=nq(),Xyt=Aq.Sync,$yt=Aq.Tar,eEt=ak(),Kye=Buffer.alloc(1024),nR=Symbol(\"onStat\"),tR=Symbol(\"ended\"),aA=Symbol(\"queue\"),FI=Symbol(\"current\"),pm=Symbol(\"process\"),rR=Symbol(\"processing\"),Jye=Symbol(\"processJob\"),lA=Symbol(\"jobs\"),iq=Symbol(\"jobDone\"),iR=Symbol(\"addFSEntry\"),zye=Symbol(\"addTarEntry\"),lq=Symbol(\"stat\"),cq=Symbol(\"readdir\"),sR=Symbol(\"onreaddir\"),oR=Symbol(\"pipe\"),Zye=Symbol(\"entry\"),sq=Symbol(\"entryOpt\"),uq=Symbol(\"writeEntryClass\"),$ye=Symbol(\"write\"),oq=Symbol(\"ondrain\"),lR=Ie(\"fs\"),Xye=Ie(\"path\"),tEt=JT(),aq=kI(),pq=tEt(class extends Jyt{constructor(e){super(e),e=e||Object.create(null),this.opt=e,this.file=e.file||\"\",this.cwd=e.cwd||process.cwd(),this.maxReadSize=e.maxReadSize,this.preservePaths=!!e.preservePaths,this.strict=!!e.strict,this.noPax=!!e.noPax,this.prefix=aq(e.prefix||\"\"),this.linkCache=e.linkCache||new Map,this.statCache=e.statCache||new Map,this.readdirCache=e.readdirCache||new Map,this[uq]=Aq,typeof e.onwarn==\"function\"&&this.on(\"warn\",e.onwarn),this.portable=!!e.portable,this.zip=null,e.gzip?(typeof e.gzip!=\"object\"&&(e.gzip={}),this.portable&&(e.gzip.portable=!0),this.zip=new zyt.Gzip(e.gzip),this.zip.on(\"data\",r=>super.write(r)),this.zip.on(\"end\",r=>super.end()),this.zip.on(\"drain\",r=>this[oq]()),this.on(\"resume\",r=>this.zip.resume())):this.on(\"drain\",this[oq]),this.noDirRecurse=!!e.noDirRecurse,this.follow=!!e.follow,this.noMtime=!!e.noMtime,this.mtime=e.mtime||null,this.filter=typeof e.filter==\"function\"?e.filter:r=>!0,this[aA]=new eEt,this[lA]=0,this.jobs=+e.jobs||4,this[rR]=!1,this[tR]=!1}[$ye](e){return super.write(e)}add(e){return this.write(e),this}end(e){return e&&this.write(e),this[tR]=!0,this[pm](),this}write(e){if(this[tR])throw new Error(\"write after end\");return e instanceof Zyt?this[zye](e):this[iR](e),this.flowing}[zye](e){let r=aq(Xye.resolve(this.cwd,e.path));if(!this.filter(e.path,e))e.resume();else{let s=new aR(e.path,r,!1);s.entry=new $yt(e,this[sq](s)),s.entry.on(\"end\",a=>this[iq](s)),this[lA]+=1,this[aA].push(s)}this[pm]()}[iR](e){let r=aq(Xye.resolve(this.cwd,e));this[aA].push(new aR(e,r)),this[pm]()}[lq](e){e.pending=!0,this[lA]+=1;let r=this.follow?\"stat\":\"lstat\";lR[r](e.absolute,(s,a)=>{e.pending=!1,this[lA]-=1,s?this.emit(\"error\",s):this[nR](e,a)})}[nR](e,r){this.statCache.set(e.absolute,r),e.stat=r,this.filter(e.path,r)||(e.ignore=!0),this[pm]()}[cq](e){e.pending=!0,this[lA]+=1,lR.readdir(e.absolute,(r,s)=>{if(e.pending=!1,this[lA]-=1,r)return this.emit(\"error\",r);this[sR](e,s)})}[sR](e,r){this.readdirCache.set(e.absolute,r),e.readdir=r,this[pm]()}[pm](){if(!this[rR]){this[rR]=!0;for(let e=this[aA].head;e!==null&&this[lA]<this.jobs;e=e.next)if(this[Jye](e.value),e.value.ignore){let r=e.next;this[aA].removeNode(e),e.next=r}this[rR]=!1,this[tR]&&!this[aA].length&&this[lA]===0&&(this.zip?this.zip.end(Kye):(super.write(Kye),super.end()))}}get[FI](){return this[aA]&&this[aA].head&&this[aA].head.value}[iq](e){this[aA].shift(),this[lA]-=1,this[pm]()}[Jye](e){if(!e.pending){if(e.entry){e===this[FI]&&!e.piped&&this[oR](e);return}if(e.stat||(this.statCache.has(e.absolute)?this[nR](e,this.statCache.get(e.absolute)):this[lq](e)),!!e.stat&&!e.ignore&&!(!this.noDirRecurse&&e.stat.isDirectory()&&!e.readdir&&(this.readdirCache.has(e.absolute)?this[sR](e,this.readdirCache.get(e.absolute)):this[cq](e),!e.readdir))){if(e.entry=this[Zye](e),!e.entry){e.ignore=!0;return}e===this[FI]&&!e.piped&&this[oR](e)}}}[sq](e){return{onwarn:(r,s,a)=>this.warn(r,s,a),noPax:this.noPax,cwd:this.cwd,absolute:e.absolute,preservePaths:this.preservePaths,maxReadSize:this.maxReadSize,strict:this.strict,portable:this.portable,linkCache:this.linkCache,statCache:this.statCache,noMtime:this.noMtime,mtime:this.mtime,prefix:this.prefix}}[Zye](e){this[lA]+=1;try{return new this[uq](e.path,this[sq](e)).on(\"end\",()=>this[iq](e)).on(\"error\",r=>this.emit(\"error\",r))}catch(r){this.emit(\"error\",r)}}[oq](){this[FI]&&this[FI].entry&&this[FI].entry.resume()}[oR](e){e.piped=!0,e.readdir&&e.readdir.forEach(a=>{let n=e.path,c=n===\"./\"?\"\":n.replace(/\\/*$/,\"/\");this[iR](c+a)});let r=e.entry,s=this.zip;s?r.on(\"data\",a=>{s.write(a)||r.pause()}):r.on(\"data\",a=>{super.write(a)||r.pause()})}pause(){return this.zip&&this.zip.pause(),super.pause()}}),fq=class extends pq{constructor(e){super(e),this[uq]=Xyt}pause(){}resume(){}[lq](e){let r=this.follow?\"statSync\":\"lstatSync\";this[nR](e,lR[r](e.absolute))}[cq](e,r){this[sR](e,lR.readdirSync(e.absolute))}[oR](e){let r=e.entry,s=this.zip;e.readdir&&e.readdir.forEach(a=>{let n=e.path,c=n===\"./\"?\"\":n.replace(/\\/*$/,\"/\");this[iR](c+a)}),s?r.on(\"data\",a=>{s.write(a)}):r.on(\"data\",a=>{super[$ye](a)})}};pq.Sync=fq;eEe.exports=pq});var jI=L(Mv=>{\"use strict\";var rEt=bI(),nEt=Ie(\"events\").EventEmitter,Al=Ie(\"fs\"),dq=Al.writev;if(!dq){let t=process.binding(\"fs\"),e=t.FSReqWrap||t.FSReqCallback;dq=(r,s,a,n)=>{let c=(p,h)=>n(p,h,s),f=new e;f.oncomplete=c,t.writeBuffers(r,s,a,f)}}var UI=Symbol(\"_autoClose\"),Vu=Symbol(\"_close\"),Lv=Symbol(\"_ended\"),ii=Symbol(\"_fd\"),tEe=Symbol(\"_finished\"),F0=Symbol(\"_flags\"),hq=Symbol(\"_flush\"),mq=Symbol(\"_handleChunk\"),yq=Symbol(\"_makeBuf\"),hR=Symbol(\"_mode\"),uR=Symbol(\"_needDrain\"),MI=Symbol(\"_onerror\"),HI=Symbol(\"_onopen\"),gq=Symbol(\"_onread\"),OI=Symbol(\"_onwrite\"),N0=Symbol(\"_open\"),Vp=Symbol(\"_path\"),hm=Symbol(\"_pos\"),cA=Symbol(\"_queue\"),LI=Symbol(\"_read\"),rEe=Symbol(\"_readSize\"),R0=Symbol(\"_reading\"),fR=Symbol(\"_remain\"),nEe=Symbol(\"_size\"),AR=Symbol(\"_write\"),NI=Symbol(\"_writing\"),pR=Symbol(\"_defaultFlag\"),_I=Symbol(\"_errored\"),gR=class extends rEt{constructor(e,r){if(r=r||{},super(r),this.readable=!0,this.writable=!1,typeof e!=\"string\")throw new TypeError(\"path must be a string\");this[_I]=!1,this[ii]=typeof r.fd==\"number\"?r.fd:null,this[Vp]=e,this[rEe]=r.readSize||16*1024*1024,this[R0]=!1,this[nEe]=typeof r.size==\"number\"?r.size:1/0,this[fR]=this[nEe],this[UI]=typeof r.autoClose==\"boolean\"?r.autoClose:!0,typeof this[ii]==\"number\"?this[LI]():this[N0]()}get fd(){return this[ii]}get path(){return this[Vp]}write(){throw new TypeError(\"this is a readable stream\")}end(){throw new TypeError(\"this is a readable stream\")}[N0](){Al.open(this[Vp],\"r\",(e,r)=>this[HI](e,r))}[HI](e,r){e?this[MI](e):(this[ii]=r,this.emit(\"open\",r),this[LI]())}[yq](){return Buffer.allocUnsafe(Math.min(this[rEe],this[fR]))}[LI](){if(!this[R0]){this[R0]=!0;let e=this[yq]();if(e.length===0)return process.nextTick(()=>this[gq](null,0,e));Al.read(this[ii],e,0,e.length,null,(r,s,a)=>this[gq](r,s,a))}}[gq](e,r,s){this[R0]=!1,e?this[MI](e):this[mq](r,s)&&this[LI]()}[Vu](){if(this[UI]&&typeof this[ii]==\"number\"){let e=this[ii];this[ii]=null,Al.close(e,r=>r?this.emit(\"error\",r):this.emit(\"close\"))}}[MI](e){this[R0]=!0,this[Vu](),this.emit(\"error\",e)}[mq](e,r){let s=!1;return this[fR]-=e,e>0&&(s=super.write(e<r.length?r.slice(0,e):r)),(e===0||this[fR]<=0)&&(s=!1,this[Vu](),super.end()),s}emit(e,r){switch(e){case\"prefinish\":case\"finish\":break;case\"drain\":typeof this[ii]==\"number\"&&this[LI]();break;case\"error\":return this[_I]?void 0:(this[_I]=!0,super.emit(e,r));default:return super.emit(e,r)}}},Eq=class extends gR{[N0](){let e=!0;try{this[HI](null,Al.openSync(this[Vp],\"r\")),e=!1}finally{e&&this[Vu]()}}[LI](){let e=!0;try{if(!this[R0]){this[R0]=!0;do{let r=this[yq](),s=r.length===0?0:Al.readSync(this[ii],r,0,r.length,null);if(!this[mq](s,r))break}while(!0);this[R0]=!1}e=!1}finally{e&&this[Vu]()}}[Vu](){if(this[UI]&&typeof this[ii]==\"number\"){let e=this[ii];this[ii]=null,Al.closeSync(e),this.emit(\"close\")}}},dR=class extends nEt{constructor(e,r){r=r||{},super(r),this.readable=!1,this.writable=!0,this[_I]=!1,this[NI]=!1,this[Lv]=!1,this[uR]=!1,this[cA]=[],this[Vp]=e,this[ii]=typeof r.fd==\"number\"?r.fd:null,this[hR]=r.mode===void 0?438:r.mode,this[hm]=typeof r.start==\"number\"?r.start:null,this[UI]=typeof r.autoClose==\"boolean\"?r.autoClose:!0;let s=this[hm]!==null?\"r+\":\"w\";this[pR]=r.flags===void 0,this[F0]=this[pR]?s:r.flags,this[ii]===null&&this[N0]()}emit(e,r){if(e===\"error\"){if(this[_I])return;this[_I]=!0}return super.emit(e,r)}get fd(){return this[ii]}get path(){return this[Vp]}[MI](e){this[Vu](),this[NI]=!0,this.emit(\"error\",e)}[N0](){Al.open(this[Vp],this[F0],this[hR],(e,r)=>this[HI](e,r))}[HI](e,r){this[pR]&&this[F0]===\"r+\"&&e&&e.code===\"ENOENT\"?(this[F0]=\"w\",this[N0]()):e?this[MI](e):(this[ii]=r,this.emit(\"open\",r),this[hq]())}end(e,r){return e&&this.write(e,r),this[Lv]=!0,!this[NI]&&!this[cA].length&&typeof this[ii]==\"number\"&&this[OI](null,0),this}write(e,r){return typeof e==\"string\"&&(e=Buffer.from(e,r)),this[Lv]?(this.emit(\"error\",new Error(\"write() after end()\")),!1):this[ii]===null||this[NI]||this[cA].length?(this[cA].push(e),this[uR]=!0,!1):(this[NI]=!0,this[AR](e),!0)}[AR](e){Al.write(this[ii],e,0,e.length,this[hm],(r,s)=>this[OI](r,s))}[OI](e,r){e?this[MI](e):(this[hm]!==null&&(this[hm]+=r),this[cA].length?this[hq]():(this[NI]=!1,this[Lv]&&!this[tEe]?(this[tEe]=!0,this[Vu](),this.emit(\"finish\")):this[uR]&&(this[uR]=!1,this.emit(\"drain\"))))}[hq](){if(this[cA].length===0)this[Lv]&&this[OI](null,0);else if(this[cA].length===1)this[AR](this[cA].pop());else{let e=this[cA];this[cA]=[],dq(this[ii],e,this[hm],(r,s)=>this[OI](r,s))}}[Vu](){if(this[UI]&&typeof this[ii]==\"number\"){let e=this[ii];this[ii]=null,Al.close(e,r=>r?this.emit(\"error\",r):this.emit(\"close\"))}}},Iq=class extends dR{[N0](){let e;if(this[pR]&&this[F0]===\"r+\")try{e=Al.openSync(this[Vp],this[F0],this[hR])}catch(r){if(r.code===\"ENOENT\")return this[F0]=\"w\",this[N0]();throw r}else e=Al.openSync(this[Vp],this[F0],this[hR]);this[HI](null,e)}[Vu](){if(this[UI]&&typeof this[ii]==\"number\"){let e=this[ii];this[ii]=null,Al.closeSync(e),this.emit(\"close\")}}[AR](e){let r=!0;try{this[OI](null,Al.writeSync(this[ii],e,0,e.length,this[hm])),r=!1}finally{if(r)try{this[Vu]()}catch{}}}};Mv.ReadStream=gR;Mv.ReadStreamSync=Eq;Mv.WriteStream=dR;Mv.WriteStreamSync=Iq});var BR=L((T$t,uEe)=>{\"use strict\";var iEt=JT(),sEt=TI(),oEt=Ie(\"events\"),aEt=ak(),lEt=1024*1024,cEt=YT(),iEe=KT(),uEt=O6(),Cq=Buffer.from([31,139]),_c=Symbol(\"state\"),gm=Symbol(\"writeEntry\"),Kp=Symbol(\"readEntry\"),wq=Symbol(\"nextEntry\"),sEe=Symbol(\"processEntry\"),Uc=Symbol(\"extendedHeader\"),_v=Symbol(\"globalExtendedHeader\"),O0=Symbol(\"meta\"),oEe=Symbol(\"emitMeta\"),Pi=Symbol(\"buffer\"),Jp=Symbol(\"queue\"),dm=Symbol(\"ended\"),aEe=Symbol(\"emittedEnd\"),mm=Symbol(\"emit\"),pl=Symbol(\"unzip\"),mR=Symbol(\"consumeChunk\"),yR=Symbol(\"consumeChunkSub\"),Bq=Symbol(\"consumeBody\"),lEe=Symbol(\"consumeMeta\"),cEe=Symbol(\"consumeHeader\"),ER=Symbol(\"consuming\"),vq=Symbol(\"bufferConcat\"),Sq=Symbol(\"maybeEnd\"),Uv=Symbol(\"writing\"),L0=Symbol(\"aborted\"),IR=Symbol(\"onDone\"),ym=Symbol(\"sawValidEntry\"),CR=Symbol(\"sawNullBlock\"),wR=Symbol(\"sawEOF\"),fEt=t=>!0;uEe.exports=iEt(class extends oEt{constructor(e){e=e||{},super(e),this.file=e.file||\"\",this[ym]=null,this.on(IR,r=>{(this[_c]===\"begin\"||this[ym]===!1)&&this.warn(\"TAR_BAD_ARCHIVE\",\"Unrecognized archive format\")}),e.ondone?this.on(IR,e.ondone):this.on(IR,r=>{this.emit(\"prefinish\"),this.emit(\"finish\"),this.emit(\"end\"),this.emit(\"close\")}),this.strict=!!e.strict,this.maxMetaEntrySize=e.maxMetaEntrySize||lEt,this.filter=typeof e.filter==\"function\"?e.filter:fEt,this.writable=!0,this.readable=!1,this[Jp]=new aEt,this[Pi]=null,this[Kp]=null,this[gm]=null,this[_c]=\"begin\",this[O0]=\"\",this[Uc]=null,this[_v]=null,this[dm]=!1,this[pl]=null,this[L0]=!1,this[CR]=!1,this[wR]=!1,typeof e.onwarn==\"function\"&&this.on(\"warn\",e.onwarn),typeof e.onentry==\"function\"&&this.on(\"entry\",e.onentry)}[cEe](e,r){this[ym]===null&&(this[ym]=!1);let s;try{s=new sEt(e,r,this[Uc],this[_v])}catch(a){return this.warn(\"TAR_ENTRY_INVALID\",a)}if(s.nullBlock)this[CR]?(this[wR]=!0,this[_c]===\"begin\"&&(this[_c]=\"header\"),this[mm](\"eof\")):(this[CR]=!0,this[mm](\"nullBlock\"));else if(this[CR]=!1,!s.cksumValid)this.warn(\"TAR_ENTRY_INVALID\",\"checksum failure\",{header:s});else if(!s.path)this.warn(\"TAR_ENTRY_INVALID\",\"path is required\",{header:s});else{let a=s.type;if(/^(Symbolic)?Link$/.test(a)&&!s.linkpath)this.warn(\"TAR_ENTRY_INVALID\",\"linkpath required\",{header:s});else if(!/^(Symbolic)?Link$/.test(a)&&s.linkpath)this.warn(\"TAR_ENTRY_INVALID\",\"linkpath forbidden\",{header:s});else{let n=this[gm]=new cEt(s,this[Uc],this[_v]);if(!this[ym])if(n.remain){let c=()=>{n.invalid||(this[ym]=!0)};n.on(\"end\",c)}else this[ym]=!0;n.meta?n.size>this.maxMetaEntrySize?(n.ignore=!0,this[mm](\"ignoredEntry\",n),this[_c]=\"ignore\",n.resume()):n.size>0&&(this[O0]=\"\",n.on(\"data\",c=>this[O0]+=c),this[_c]=\"meta\"):(this[Uc]=null,n.ignore=n.ignore||!this.filter(n.path,n),n.ignore?(this[mm](\"ignoredEntry\",n),this[_c]=n.remain?\"ignore\":\"header\",n.resume()):(n.remain?this[_c]=\"body\":(this[_c]=\"header\",n.end()),this[Kp]?this[Jp].push(n):(this[Jp].push(n),this[wq]())))}}}[sEe](e){let r=!0;return e?Array.isArray(e)?this.emit.apply(this,e):(this[Kp]=e,this.emit(\"entry\",e),e.emittedEnd||(e.on(\"end\",s=>this[wq]()),r=!1)):(this[Kp]=null,r=!1),r}[wq](){do;while(this[sEe](this[Jp].shift()));if(!this[Jp].length){let e=this[Kp];!e||e.flowing||e.size===e.remain?this[Uv]||this.emit(\"drain\"):e.once(\"drain\",s=>this.emit(\"drain\"))}}[Bq](e,r){let s=this[gm],a=s.blockRemain,n=a>=e.length&&r===0?e:e.slice(r,r+a);return s.write(n),s.blockRemain||(this[_c]=\"header\",this[gm]=null,s.end()),n.length}[lEe](e,r){let s=this[gm],a=this[Bq](e,r);return this[gm]||this[oEe](s),a}[mm](e,r,s){!this[Jp].length&&!this[Kp]?this.emit(e,r,s):this[Jp].push([e,r,s])}[oEe](e){switch(this[mm](\"meta\",this[O0]),e.type){case\"ExtendedHeader\":case\"OldExtendedHeader\":this[Uc]=iEe.parse(this[O0],this[Uc],!1);break;case\"GlobalExtendedHeader\":this[_v]=iEe.parse(this[O0],this[_v],!0);break;case\"NextFileHasLongPath\":case\"OldGnuLongPath\":this[Uc]=this[Uc]||Object.create(null),this[Uc].path=this[O0].replace(/\\0.*/,\"\");break;case\"NextFileHasLongLinkpath\":this[Uc]=this[Uc]||Object.create(null),this[Uc].linkpath=this[O0].replace(/\\0.*/,\"\");break;default:throw new Error(\"unknown meta: \"+e.type)}}abort(e){this[L0]=!0,this.emit(\"abort\",e),this.warn(\"TAR_ABORT\",e,{recoverable:!1})}write(e){if(this[L0])return;if(this[pl]===null&&e){if(this[Pi]&&(e=Buffer.concat([this[Pi],e]),this[Pi]=null),e.length<Cq.length)return this[Pi]=e,!0;for(let s=0;this[pl]===null&&s<Cq.length;s++)e[s]!==Cq[s]&&(this[pl]=!1);if(this[pl]===null){let s=this[dm];this[dm]=!1,this[pl]=new uEt.Unzip,this[pl].on(\"data\",n=>this[mR](n)),this[pl].on(\"error\",n=>this.abort(n)),this[pl].on(\"end\",n=>{this[dm]=!0,this[mR]()}),this[Uv]=!0;let a=this[pl][s?\"end\":\"write\"](e);return this[Uv]=!1,a}}this[Uv]=!0,this[pl]?this[pl].write(e):this[mR](e),this[Uv]=!1;let r=this[Jp].length?!1:this[Kp]?this[Kp].flowing:!0;return!r&&!this[Jp].length&&this[Kp].once(\"drain\",s=>this.emit(\"drain\")),r}[vq](e){e&&!this[L0]&&(this[Pi]=this[Pi]?Buffer.concat([this[Pi],e]):e)}[Sq](){if(this[dm]&&!this[aEe]&&!this[L0]&&!this[ER]){this[aEe]=!0;let e=this[gm];if(e&&e.blockRemain){let r=this[Pi]?this[Pi].length:0;this.warn(\"TAR_BAD_ARCHIVE\",`Truncated input (needed ${e.blockRemain} more bytes, only ${r} available)`,{entry:e}),this[Pi]&&e.write(this[Pi]),e.end()}this[mm](IR)}}[mR](e){if(this[ER])this[vq](e);else if(!e&&!this[Pi])this[Sq]();else{if(this[ER]=!0,this[Pi]){this[vq](e);let r=this[Pi];this[Pi]=null,this[yR](r)}else this[yR](e);for(;this[Pi]&&this[Pi].length>=512&&!this[L0]&&!this[wR];){let r=this[Pi];this[Pi]=null,this[yR](r)}this[ER]=!1}(!this[Pi]||this[dm])&&this[Sq]()}[yR](e){let r=0,s=e.length;for(;r+512<=s&&!this[L0]&&!this[wR];)switch(this[_c]){case\"begin\":case\"header\":this[cEe](e,r),r+=512;break;case\"ignore\":case\"body\":r+=this[Bq](e,r);break;case\"meta\":r+=this[lEe](e,r);break;default:throw new Error(\"invalid state: \"+this[_c])}r<s&&(this[Pi]?this[Pi]=Buffer.concat([e.slice(r),this[Pi]]):this[Pi]=e.slice(r))}end(e){this[L0]||(this[pl]?this[pl].end(e):(this[dm]=!0,this.write(e)))}})});var vR=L((R$t,hEe)=>{\"use strict\";var AEt=SI(),AEe=BR(),qI=Ie(\"fs\"),pEt=jI(),fEe=Ie(\"path\"),Dq=RI();hEe.exports=(t,e,r)=>{typeof t==\"function\"?(r=t,e=null,t={}):Array.isArray(t)&&(e=t,t={}),typeof e==\"function\"&&(r=e,e=null),e?e=Array.from(e):e=[];let s=AEt(t);if(s.sync&&typeof r==\"function\")throw new TypeError(\"callback not supported for sync tar functions\");if(!s.file&&typeof r==\"function\")throw new TypeError(\"callback only supported with file option\");return e.length&&gEt(s,e),s.noResume||hEt(s),s.file&&s.sync?dEt(s):s.file?mEt(s,r):pEe(s)};var hEt=t=>{let e=t.onentry;t.onentry=e?r=>{e(r),r.resume()}:r=>r.resume()},gEt=(t,e)=>{let r=new Map(e.map(n=>[Dq(n),!0])),s=t.filter,a=(n,c)=>{let f=c||fEe.parse(n).root||\".\",p=n===f?!1:r.has(n)?r.get(n):a(fEe.dirname(n),f);return r.set(n,p),p};t.filter=s?(n,c)=>s(n,c)&&a(Dq(n)):n=>a(Dq(n))},dEt=t=>{let e=pEe(t),r=t.file,s=!0,a;try{let n=qI.statSync(r),c=t.maxReadSize||16*1024*1024;if(n.size<c)e.end(qI.readFileSync(r));else{let f=0,p=Buffer.allocUnsafe(c);for(a=qI.openSync(r,\"r\");f<n.size;){let h=qI.readSync(a,p,0,c,f);f+=h,e.write(p.slice(0,h))}e.end()}s=!1}finally{if(s&&a)try{qI.closeSync(a)}catch{}}},mEt=(t,e)=>{let r=new AEe(t),s=t.maxReadSize||16*1024*1024,a=t.file,n=new Promise((c,f)=>{r.on(\"error\",f),r.on(\"end\",c),qI.stat(a,(p,h)=>{if(p)f(p);else{let E=new pEt.ReadStream(a,{readSize:s,size:h.size});E.on(\"error\",f),E.pipe(r)}})});return e?n.then(e,e):n},pEe=t=>new AEe(t)});var IEe=L((F$t,EEe)=>{\"use strict\";var yEt=SI(),SR=cR(),gEe=jI(),dEe=vR(),mEe=Ie(\"path\");EEe.exports=(t,e,r)=>{if(typeof e==\"function\"&&(r=e),Array.isArray(t)&&(e=t,t={}),!e||!Array.isArray(e)||!e.length)throw new TypeError(\"no files or directories specified\");e=Array.from(e);let s=yEt(t);if(s.sync&&typeof r==\"function\")throw new TypeError(\"callback not supported for sync tar functions\");if(!s.file&&typeof r==\"function\")throw new TypeError(\"callback only supported with file option\");return s.file&&s.sync?EEt(s,e):s.file?IEt(s,e,r):s.sync?CEt(s,e):wEt(s,e)};var EEt=(t,e)=>{let r=new SR.Sync(t),s=new gEe.WriteStreamSync(t.file,{mode:t.mode||438});r.pipe(s),yEe(r,e)},IEt=(t,e,r)=>{let s=new SR(t),a=new gEe.WriteStream(t.file,{mode:t.mode||438});s.pipe(a);let n=new Promise((c,f)=>{a.on(\"error\",f),a.on(\"close\",c),s.on(\"error\",f)});return bq(s,e),r?n.then(r,r):n},yEe=(t,e)=>{e.forEach(r=>{r.charAt(0)===\"@\"?dEe({file:mEe.resolve(t.cwd,r.substr(1)),sync:!0,noResume:!0,onentry:s=>t.add(s)}):t.add(r)}),t.end()},bq=(t,e)=>{for(;e.length;){let r=e.shift();if(r.charAt(0)===\"@\")return dEe({file:mEe.resolve(t.cwd,r.substr(1)),noResume:!0,onentry:s=>t.add(s)}).then(s=>bq(t,e));t.add(r)}t.end()},CEt=(t,e)=>{let r=new SR.Sync(t);return yEe(r,e),r},wEt=(t,e)=>{let r=new SR(t);return bq(r,e),r}});var Pq=L((N$t,bEe)=>{\"use strict\";var BEt=SI(),CEe=cR(),Xl=Ie(\"fs\"),wEe=jI(),BEe=vR(),vEe=Ie(\"path\"),SEe=TI();bEe.exports=(t,e,r)=>{let s=BEt(t);if(!s.file)throw new TypeError(\"file is required\");if(s.gzip)throw new TypeError(\"cannot append to compressed archives\");if(!e||!Array.isArray(e)||!e.length)throw new TypeError(\"no files or directories specified\");return e=Array.from(e),s.sync?vEt(s,e):DEt(s,e,r)};var vEt=(t,e)=>{let r=new CEe.Sync(t),s=!0,a,n;try{try{a=Xl.openSync(t.file,\"r+\")}catch(p){if(p.code===\"ENOENT\")a=Xl.openSync(t.file,\"w+\");else throw p}let c=Xl.fstatSync(a),f=Buffer.alloc(512);e:for(n=0;n<c.size;n+=512){for(let E=0,C=0;E<512;E+=C){if(C=Xl.readSync(a,f,E,f.length-E,n+E),n===0&&f[0]===31&&f[1]===139)throw new Error(\"cannot append to compressed archives\");if(!C)break e}let p=new SEe(f);if(!p.cksumValid)break;let h=512*Math.ceil(p.size/512);if(n+h+512>c.size)break;n+=h,t.mtimeCache&&t.mtimeCache.set(p.path,p.mtime)}s=!1,SEt(t,r,n,a,e)}finally{if(s)try{Xl.closeSync(a)}catch{}}},SEt=(t,e,r,s,a)=>{let n=new wEe.WriteStreamSync(t.file,{fd:s,start:r});e.pipe(n),bEt(e,a)},DEt=(t,e,r)=>{e=Array.from(e);let s=new CEe(t),a=(c,f,p)=>{let h=(I,R)=>{I?Xl.close(c,N=>p(I)):p(null,R)},E=0;if(f===0)return h(null,0);let C=0,S=Buffer.alloc(512),P=(I,R)=>{if(I)return h(I);if(C+=R,C<512&&R)return Xl.read(c,S,C,S.length-C,E+C,P);if(E===0&&S[0]===31&&S[1]===139)return h(new Error(\"cannot append to compressed archives\"));if(C<512)return h(null,E);let N=new SEe(S);if(!N.cksumValid)return h(null,E);let U=512*Math.ceil(N.size/512);if(E+U+512>f||(E+=U+512,E>=f))return h(null,E);t.mtimeCache&&t.mtimeCache.set(N.path,N.mtime),C=0,Xl.read(c,S,0,512,E,P)};Xl.read(c,S,0,512,E,P)},n=new Promise((c,f)=>{s.on(\"error\",f);let p=\"r+\",h=(E,C)=>{if(E&&E.code===\"ENOENT\"&&p===\"r+\")return p=\"w+\",Xl.open(t.file,p,h);if(E)return f(E);Xl.fstat(C,(S,P)=>{if(S)return Xl.close(C,()=>f(S));a(C,P.size,(I,R)=>{if(I)return f(I);let N=new wEe.WriteStream(t.file,{fd:C,start:R});s.pipe(N),N.on(\"error\",f),N.on(\"close\",c),DEe(s,e)})})};Xl.open(t.file,p,h)});return r?n.then(r,r):n},bEt=(t,e)=>{e.forEach(r=>{r.charAt(0)===\"@\"?BEe({file:vEe.resolve(t.cwd,r.substr(1)),sync:!0,noResume:!0,onentry:s=>t.add(s)}):t.add(r)}),t.end()},DEe=(t,e)=>{for(;e.length;){let r=e.shift();if(r.charAt(0)===\"@\")return BEe({file:vEe.resolve(t.cwd,r.substr(1)),noResume:!0,onentry:s=>t.add(s)}).then(s=>DEe(t,e));t.add(r)}t.end()}});var xEe=L((O$t,PEe)=>{\"use strict\";var PEt=SI(),xEt=Pq();PEe.exports=(t,e,r)=>{let s=PEt(t);if(!s.file)throw new TypeError(\"file is required\");if(s.gzip)throw new TypeError(\"cannot append to compressed archives\");if(!e||!Array.isArray(e)||!e.length)throw new TypeError(\"no files or directories specified\");return e=Array.from(e),kEt(s),xEt(s,e,r)};var kEt=t=>{let e=t.filter;t.mtimeCache||(t.mtimeCache=new Map),t.filter=e?(r,s)=>e(r,s)&&!(t.mtimeCache.get(r)>s.mtime):(r,s)=>!(t.mtimeCache.get(r)>s.mtime)}});var TEe=L((L$t,QEe)=>{var{promisify:kEe}=Ie(\"util\"),M0=Ie(\"fs\"),QEt=t=>{if(!t)t={mode:511,fs:M0};else if(typeof t==\"object\")t={mode:511,fs:M0,...t};else if(typeof t==\"number\")t={mode:t,fs:M0};else if(typeof t==\"string\")t={mode:parseInt(t,8),fs:M0};else throw new TypeError(\"invalid options argument\");return t.mkdir=t.mkdir||t.fs.mkdir||M0.mkdir,t.mkdirAsync=kEe(t.mkdir),t.stat=t.stat||t.fs.stat||M0.stat,t.statAsync=kEe(t.stat),t.statSync=t.statSync||t.fs.statSync||M0.statSync,t.mkdirSync=t.mkdirSync||t.fs.mkdirSync||M0.mkdirSync,t};QEe.exports=QEt});var FEe=L((M$t,REe)=>{var TEt=process.platform,{resolve:REt,parse:FEt}=Ie(\"path\"),NEt=t=>{if(/\\0/.test(t))throw Object.assign(new TypeError(\"path must be a string without null bytes\"),{path:t,code:\"ERR_INVALID_ARG_VALUE\"});if(t=REt(t),TEt===\"win32\"){let e=/[*|\"<>?:]/,{root:r}=FEt(t);if(e.test(t.substr(r.length)))throw Object.assign(new Error(\"Illegal characters in path.\"),{path:t,code:\"EINVAL\"})}return t};REe.exports=NEt});var _Ee=L((_$t,MEe)=>{var{dirname:NEe}=Ie(\"path\"),OEe=(t,e,r=void 0)=>r===e?Promise.resolve():t.statAsync(e).then(s=>s.isDirectory()?r:void 0,s=>s.code===\"ENOENT\"?OEe(t,NEe(e),e):void 0),LEe=(t,e,r=void 0)=>{if(r!==e)try{return t.statSync(e).isDirectory()?r:void 0}catch(s){return s.code===\"ENOENT\"?LEe(t,NEe(e),e):void 0}};MEe.exports={findMade:OEe,findMadeSync:LEe}});var Qq=L((U$t,HEe)=>{var{dirname:UEe}=Ie(\"path\"),xq=(t,e,r)=>{e.recursive=!1;let s=UEe(t);return s===t?e.mkdirAsync(t,e).catch(a=>{if(a.code!==\"EISDIR\")throw a}):e.mkdirAsync(t,e).then(()=>r||t,a=>{if(a.code===\"ENOENT\")return xq(s,e).then(n=>xq(t,e,n));if(a.code!==\"EEXIST\"&&a.code!==\"EROFS\")throw a;return e.statAsync(t).then(n=>{if(n.isDirectory())return r;throw a},()=>{throw a})})},kq=(t,e,r)=>{let s=UEe(t);if(e.recursive=!1,s===t)try{return e.mkdirSync(t,e)}catch(a){if(a.code!==\"EISDIR\")throw a;return}try{return e.mkdirSync(t,e),r||t}catch(a){if(a.code===\"ENOENT\")return kq(t,e,kq(s,e,r));if(a.code!==\"EEXIST\"&&a.code!==\"EROFS\")throw a;try{if(!e.statSync(t).isDirectory())throw a}catch{throw a}}};HEe.exports={mkdirpManual:xq,mkdirpManualSync:kq}});var GEe=L((H$t,qEe)=>{var{dirname:jEe}=Ie(\"path\"),{findMade:OEt,findMadeSync:LEt}=_Ee(),{mkdirpManual:MEt,mkdirpManualSync:_Et}=Qq(),UEt=(t,e)=>(e.recursive=!0,jEe(t)===t?e.mkdirAsync(t,e):OEt(e,t).then(s=>e.mkdirAsync(t,e).then(()=>s).catch(a=>{if(a.code===\"ENOENT\")return MEt(t,e);throw a}))),HEt=(t,e)=>{if(e.recursive=!0,jEe(t)===t)return e.mkdirSync(t,e);let s=LEt(e,t);try{return e.mkdirSync(t,e),s}catch(a){if(a.code===\"ENOENT\")return _Et(t,e);throw a}};qEe.exports={mkdirpNative:UEt,mkdirpNativeSync:HEt}});var KEe=L((j$t,VEe)=>{var WEe=Ie(\"fs\"),jEt=process.version,Tq=jEt.replace(/^v/,\"\").split(\".\"),YEe=+Tq[0]>10||+Tq[0]==10&&+Tq[1]>=12,qEt=YEe?t=>t.mkdir===WEe.mkdir:()=>!1,GEt=YEe?t=>t.mkdirSync===WEe.mkdirSync:()=>!1;VEe.exports={useNative:qEt,useNativeSync:GEt}});var eIe=L((q$t,$Ee)=>{var GI=TEe(),WI=FEe(),{mkdirpNative:JEe,mkdirpNativeSync:zEe}=GEe(),{mkdirpManual:ZEe,mkdirpManualSync:XEe}=Qq(),{useNative:WEt,useNativeSync:YEt}=KEe(),YI=(t,e)=>(t=WI(t),e=GI(e),WEt(e)?JEe(t,e):ZEe(t,e)),VEt=(t,e)=>(t=WI(t),e=GI(e),YEt(e)?zEe(t,e):XEe(t,e));YI.sync=VEt;YI.native=(t,e)=>JEe(WI(t),GI(e));YI.manual=(t,e)=>ZEe(WI(t),GI(e));YI.nativeSync=(t,e)=>zEe(WI(t),GI(e));YI.manualSync=(t,e)=>XEe(WI(t),GI(e));$Ee.exports=YI});var aIe=L((G$t,oIe)=>{\"use strict\";var Hc=Ie(\"fs\"),Em=Ie(\"path\"),KEt=Hc.lchown?\"lchown\":\"chown\",JEt=Hc.lchownSync?\"lchownSync\":\"chownSync\",rIe=Hc.lchown&&!process.version.match(/v1[1-9]+\\./)&&!process.version.match(/v10\\.[6-9]/),tIe=(t,e,r)=>{try{return Hc[JEt](t,e,r)}catch(s){if(s.code!==\"ENOENT\")throw s}},zEt=(t,e,r)=>{try{return Hc.chownSync(t,e,r)}catch(s){if(s.code!==\"ENOENT\")throw s}},ZEt=rIe?(t,e,r,s)=>a=>{!a||a.code!==\"EISDIR\"?s(a):Hc.chown(t,e,r,s)}:(t,e,r,s)=>s,Rq=rIe?(t,e,r)=>{try{return tIe(t,e,r)}catch(s){if(s.code!==\"EISDIR\")throw s;zEt(t,e,r)}}:(t,e,r)=>tIe(t,e,r),XEt=process.version,nIe=(t,e,r)=>Hc.readdir(t,e,r),$Et=(t,e)=>Hc.readdirSync(t,e);/^v4\\./.test(XEt)&&(nIe=(t,e,r)=>Hc.readdir(t,r));var DR=(t,e,r,s)=>{Hc[KEt](t,e,r,ZEt(t,e,r,a=>{s(a&&a.code!==\"ENOENT\"?a:null)}))},iIe=(t,e,r,s,a)=>{if(typeof e==\"string\")return Hc.lstat(Em.resolve(t,e),(n,c)=>{if(n)return a(n.code!==\"ENOENT\"?n:null);c.name=e,iIe(t,c,r,s,a)});if(e.isDirectory())Fq(Em.resolve(t,e.name),r,s,n=>{if(n)return a(n);let c=Em.resolve(t,e.name);DR(c,r,s,a)});else{let n=Em.resolve(t,e.name);DR(n,r,s,a)}},Fq=(t,e,r,s)=>{nIe(t,{withFileTypes:!0},(a,n)=>{if(a){if(a.code===\"ENOENT\")return s();if(a.code!==\"ENOTDIR\"&&a.code!==\"ENOTSUP\")return s(a)}if(a||!n.length)return DR(t,e,r,s);let c=n.length,f=null,p=h=>{if(!f){if(h)return s(f=h);if(--c===0)return DR(t,e,r,s)}};n.forEach(h=>iIe(t,h,e,r,p))})},eIt=(t,e,r,s)=>{if(typeof e==\"string\")try{let a=Hc.lstatSync(Em.resolve(t,e));a.name=e,e=a}catch(a){if(a.code===\"ENOENT\")return;throw a}e.isDirectory()&&sIe(Em.resolve(t,e.name),r,s),Rq(Em.resolve(t,e.name),r,s)},sIe=(t,e,r)=>{let s;try{s=$Et(t,{withFileTypes:!0})}catch(a){if(a.code===\"ENOENT\")return;if(a.code===\"ENOTDIR\"||a.code===\"ENOTSUP\")return Rq(t,e,r);throw a}return s&&s.length&&s.forEach(a=>eIt(t,a,e,r)),Rq(t,e,r)};oIe.exports=Fq;Fq.sync=sIe});var fIe=L((W$t,Nq)=>{\"use strict\";var lIe=eIe(),jc=Ie(\"fs\"),bR=Ie(\"path\"),cIe=aIe(),Ku=kI(),PR=class extends Error{constructor(e,r){super(\"Cannot extract through symbolic link\"),this.path=r,this.symlink=e}get name(){return\"SylinkError\"}},xR=class extends Error{constructor(e,r){super(r+\": Cannot cd into '\"+e+\"'\"),this.path=e,this.code=r}get name(){return\"CwdError\"}},kR=(t,e)=>t.get(Ku(e)),Hv=(t,e,r)=>t.set(Ku(e),r),tIt=(t,e)=>{jc.stat(t,(r,s)=>{(r||!s.isDirectory())&&(r=new xR(t,r&&r.code||\"ENOTDIR\")),e(r)})};Nq.exports=(t,e,r)=>{t=Ku(t);let s=e.umask,a=e.mode|448,n=(a&s)!==0,c=e.uid,f=e.gid,p=typeof c==\"number\"&&typeof f==\"number\"&&(c!==e.processUid||f!==e.processGid),h=e.preserve,E=e.unlink,C=e.cache,S=Ku(e.cwd),P=(N,U)=>{N?r(N):(Hv(C,t,!0),U&&p?cIe(U,c,f,W=>P(W)):n?jc.chmod(t,a,r):r())};if(C&&kR(C,t)===!0)return P();if(t===S)return tIt(t,P);if(h)return lIe(t,{mode:a}).then(N=>P(null,N),P);let R=Ku(bR.relative(S,t)).split(\"/\");QR(S,R,a,C,E,S,null,P)};var QR=(t,e,r,s,a,n,c,f)=>{if(!e.length)return f(null,c);let p=e.shift(),h=Ku(bR.resolve(t+\"/\"+p));if(kR(s,h))return QR(h,e,r,s,a,n,c,f);jc.mkdir(h,r,uIe(h,e,r,s,a,n,c,f))},uIe=(t,e,r,s,a,n,c,f)=>p=>{p?jc.lstat(t,(h,E)=>{if(h)h.path=h.path&&Ku(h.path),f(h);else if(E.isDirectory())QR(t,e,r,s,a,n,c,f);else if(a)jc.unlink(t,C=>{if(C)return f(C);jc.mkdir(t,r,uIe(t,e,r,s,a,n,c,f))});else{if(E.isSymbolicLink())return f(new PR(t,t+\"/\"+e.join(\"/\")));f(p)}}):(c=c||t,QR(t,e,r,s,a,n,c,f))},rIt=t=>{let e=!1,r=\"ENOTDIR\";try{e=jc.statSync(t).isDirectory()}catch(s){r=s.code}finally{if(!e)throw new xR(t,r)}};Nq.exports.sync=(t,e)=>{t=Ku(t);let r=e.umask,s=e.mode|448,a=(s&r)!==0,n=e.uid,c=e.gid,f=typeof n==\"number\"&&typeof c==\"number\"&&(n!==e.processUid||c!==e.processGid),p=e.preserve,h=e.unlink,E=e.cache,C=Ku(e.cwd),S=N=>{Hv(E,t,!0),N&&f&&cIe.sync(N,n,c),a&&jc.chmodSync(t,s)};if(E&&kR(E,t)===!0)return S();if(t===C)return rIt(C),S();if(p)return S(lIe.sync(t,s));let I=Ku(bR.relative(C,t)).split(\"/\"),R=null;for(let N=I.shift(),U=C;N&&(U+=\"/\"+N);N=I.shift())if(U=Ku(bR.resolve(U)),!kR(E,U))try{jc.mkdirSync(U,s),R=R||U,Hv(E,U,!0)}catch{let te=jc.lstatSync(U);if(te.isDirectory()){Hv(E,U,!0);continue}else if(h){jc.unlinkSync(U),jc.mkdirSync(U,s),R=R||U,Hv(E,U,!0);continue}else if(te.isSymbolicLink())return new PR(U,U+\"/\"+I.join(\"/\"))}return S(R)}});var Lq=L((Y$t,AIe)=>{var Oq=Object.create(null),{hasOwnProperty:nIt}=Object.prototype;AIe.exports=t=>(nIt.call(Oq,t)||(Oq[t]=t.normalize(\"NFKD\")),Oq[t])});var dIe=L((V$t,gIe)=>{var pIe=Ie(\"assert\"),iIt=Lq(),sIt=RI(),{join:hIe}=Ie(\"path\"),oIt=process.env.TESTING_TAR_FAKE_PLATFORM||process.platform,aIt=oIt===\"win32\";gIe.exports=()=>{let t=new Map,e=new Map,r=h=>h.split(\"/\").slice(0,-1).reduce((C,S)=>(C.length&&(S=hIe(C[C.length-1],S)),C.push(S||\"/\"),C),[]),s=new Set,a=h=>{let E=e.get(h);if(!E)throw new Error(\"function does not have any path reservations\");return{paths:E.paths.map(C=>t.get(C)),dirs:[...E.dirs].map(C=>t.get(C))}},n=h=>{let{paths:E,dirs:C}=a(h);return E.every(S=>S[0]===h)&&C.every(S=>S[0]instanceof Set&&S[0].has(h))},c=h=>s.has(h)||!n(h)?!1:(s.add(h),h(()=>f(h)),!0),f=h=>{if(!s.has(h))return!1;let{paths:E,dirs:C}=e.get(h),S=new Set;return E.forEach(P=>{let I=t.get(P);pIe.equal(I[0],h),I.length===1?t.delete(P):(I.shift(),typeof I[0]==\"function\"?S.add(I[0]):I[0].forEach(R=>S.add(R)))}),C.forEach(P=>{let I=t.get(P);pIe(I[0]instanceof Set),I[0].size===1&&I.length===1?t.delete(P):I[0].size===1?(I.shift(),S.add(I[0])):I[0].delete(h)}),s.delete(h),S.forEach(P=>c(P)),!0};return{check:n,reserve:(h,E)=>{h=aIt?[\"win32 parallelization disabled\"]:h.map(S=>iIt(sIt(hIe(S))).toLowerCase());let C=new Set(h.map(S=>r(S)).reduce((S,P)=>S.concat(P)));return e.set(E,{dirs:C,paths:h}),h.forEach(S=>{let P=t.get(S);P?P.push(E):t.set(S,[E])}),C.forEach(S=>{let P=t.get(S);P?P[P.length-1]instanceof Set?P[P.length-1].add(E):P.push(new Set([E])):t.set(S,[new Set([E])])}),c(E)}}}});var EIe=L((K$t,yIe)=>{var lIt=process.platform,cIt=lIt===\"win32\",uIt=global.__FAKE_TESTING_FS__||Ie(\"fs\"),{O_CREAT:fIt,O_TRUNC:AIt,O_WRONLY:pIt,UV_FS_O_FILEMAP:mIe=0}=uIt.constants,hIt=cIt&&!!mIe,gIt=512*1024,dIt=mIe|AIt|fIt|pIt;yIe.exports=hIt?t=>t<gIt?dIt:\"w\":()=>\"w\"});var Yq=L((J$t,RIe)=>{\"use strict\";var mIt=Ie(\"assert\"),yIt=BR(),Mn=Ie(\"fs\"),EIt=jI(),zp=Ie(\"path\"),kIe=fIe(),IIe=Y6(),IIt=dIe(),CIt=V6(),$l=kI(),wIt=RI(),BIt=Lq(),CIe=Symbol(\"onEntry\"),Uq=Symbol(\"checkFs\"),wIe=Symbol(\"checkFs2\"),FR=Symbol(\"pruneCache\"),Hq=Symbol(\"isReusable\"),qc=Symbol(\"makeFs\"),jq=Symbol(\"file\"),qq=Symbol(\"directory\"),NR=Symbol(\"link\"),BIe=Symbol(\"symlink\"),vIe=Symbol(\"hardlink\"),SIe=Symbol(\"unsupported\"),DIe=Symbol(\"checkPath\"),_0=Symbol(\"mkdir\"),Xo=Symbol(\"onError\"),TR=Symbol(\"pending\"),bIe=Symbol(\"pend\"),VI=Symbol(\"unpend\"),Mq=Symbol(\"ended\"),_q=Symbol(\"maybeClose\"),Gq=Symbol(\"skip\"),jv=Symbol(\"doChown\"),qv=Symbol(\"uid\"),Gv=Symbol(\"gid\"),Wv=Symbol(\"checkedCwd\"),QIe=Ie(\"crypto\"),TIe=EIe(),vIt=process.env.TESTING_TAR_FAKE_PLATFORM||process.platform,Yv=vIt===\"win32\",SIt=(t,e)=>{if(!Yv)return Mn.unlink(t,e);let r=t+\".DELETE.\"+QIe.randomBytes(16).toString(\"hex\");Mn.rename(t,r,s=>{if(s)return e(s);Mn.unlink(r,e)})},DIt=t=>{if(!Yv)return Mn.unlinkSync(t);let e=t+\".DELETE.\"+QIe.randomBytes(16).toString(\"hex\");Mn.renameSync(t,e),Mn.unlinkSync(e)},PIe=(t,e,r)=>t===t>>>0?t:e===e>>>0?e:r,xIe=t=>BIt(wIt($l(t))).toLowerCase(),bIt=(t,e)=>{e=xIe(e);for(let r of t.keys()){let s=xIe(r);(s===e||s.indexOf(e+\"/\")===0)&&t.delete(r)}},PIt=t=>{for(let e of t.keys())t.delete(e)},Vv=class extends yIt{constructor(e){if(e||(e={}),e.ondone=r=>{this[Mq]=!0,this[_q]()},super(e),this[Wv]=!1,this.reservations=IIt(),this.transform=typeof e.transform==\"function\"?e.transform:null,this.writable=!0,this.readable=!1,this[TR]=0,this[Mq]=!1,this.dirCache=e.dirCache||new Map,typeof e.uid==\"number\"||typeof e.gid==\"number\"){if(typeof e.uid!=\"number\"||typeof e.gid!=\"number\")throw new TypeError(\"cannot set owner without number uid and gid\");if(e.preserveOwner)throw new TypeError(\"cannot preserve owner in archive and also set owner explicitly\");this.uid=e.uid,this.gid=e.gid,this.setOwner=!0}else this.uid=null,this.gid=null,this.setOwner=!1;e.preserveOwner===void 0&&typeof e.uid!=\"number\"?this.preserveOwner=process.getuid&&process.getuid()===0:this.preserveOwner=!!e.preserveOwner,this.processUid=(this.preserveOwner||this.setOwner)&&process.getuid?process.getuid():null,this.processGid=(this.preserveOwner||this.setOwner)&&process.getgid?process.getgid():null,this.forceChown=e.forceChown===!0,this.win32=!!e.win32||Yv,this.newer=!!e.newer,this.keep=!!e.keep,this.noMtime=!!e.noMtime,this.preservePaths=!!e.preservePaths,this.unlink=!!e.unlink,this.cwd=$l(zp.resolve(e.cwd||process.cwd())),this.strip=+e.strip||0,this.processUmask=e.noChmod?0:process.umask(),this.umask=typeof e.umask==\"number\"?e.umask:this.processUmask,this.dmode=e.dmode||511&~this.umask,this.fmode=e.fmode||438&~this.umask,this.on(\"entry\",r=>this[CIe](r))}warn(e,r,s={}){return(e===\"TAR_BAD_ARCHIVE\"||e===\"TAR_ABORT\")&&(s.recoverable=!1),super.warn(e,r,s)}[_q](){this[Mq]&&this[TR]===0&&(this.emit(\"prefinish\"),this.emit(\"finish\"),this.emit(\"end\"),this.emit(\"close\"))}[DIe](e){if(this.strip){let r=$l(e.path).split(\"/\");if(r.length<this.strip)return!1;if(e.path=r.slice(this.strip).join(\"/\"),e.type===\"Link\"){let s=$l(e.linkpath).split(\"/\");if(s.length>=this.strip)e.linkpath=s.slice(this.strip).join(\"/\");else return!1}}if(!this.preservePaths){let r=$l(e.path),s=r.split(\"/\");if(s.includes(\"..\")||Yv&&/^[a-z]:\\.\\.$/i.test(s[0]))return this.warn(\"TAR_ENTRY_ERROR\",\"path contains '..'\",{entry:e,path:r}),!1;let[a,n]=CIt(r);a&&(e.path=n,this.warn(\"TAR_ENTRY_INFO\",`stripping ${a} from absolute path`,{entry:e,path:r}))}if(zp.isAbsolute(e.path)?e.absolute=$l(zp.resolve(e.path)):e.absolute=$l(zp.resolve(this.cwd,e.path)),!this.preservePaths&&e.absolute.indexOf(this.cwd+\"/\")!==0&&e.absolute!==this.cwd)return this.warn(\"TAR_ENTRY_ERROR\",\"path escaped extraction target\",{entry:e,path:$l(e.path),resolvedPath:e.absolute,cwd:this.cwd}),!1;if(e.absolute===this.cwd&&e.type!==\"Directory\"&&e.type!==\"GNUDumpDir\")return!1;if(this.win32){let{root:r}=zp.win32.parse(e.absolute);e.absolute=r+IIe.encode(e.absolute.substr(r.length));let{root:s}=zp.win32.parse(e.path);e.path=s+IIe.encode(e.path.substr(s.length))}return!0}[CIe](e){if(!this[DIe](e))return e.resume();switch(mIt.equal(typeof e.absolute,\"string\"),e.type){case\"Directory\":case\"GNUDumpDir\":e.mode&&(e.mode=e.mode|448);case\"File\":case\"OldFile\":case\"ContiguousFile\":case\"Link\":case\"SymbolicLink\":return this[Uq](e);case\"CharacterDevice\":case\"BlockDevice\":case\"FIFO\":default:return this[SIe](e)}}[Xo](e,r){e.name===\"CwdError\"?this.emit(\"error\",e):(this.warn(\"TAR_ENTRY_ERROR\",e,{entry:r}),this[VI](),r.resume())}[_0](e,r,s){kIe($l(e),{uid:this.uid,gid:this.gid,processUid:this.processUid,processGid:this.processGid,umask:this.processUmask,preserve:this.preservePaths,unlink:this.unlink,cache:this.dirCache,cwd:this.cwd,mode:r,noChmod:this.noChmod},s)}[jv](e){return this.forceChown||this.preserveOwner&&(typeof e.uid==\"number\"&&e.uid!==this.processUid||typeof e.gid==\"number\"&&e.gid!==this.processGid)||typeof this.uid==\"number\"&&this.uid!==this.processUid||typeof this.gid==\"number\"&&this.gid!==this.processGid}[qv](e){return PIe(this.uid,e.uid,this.processUid)}[Gv](e){return PIe(this.gid,e.gid,this.processGid)}[jq](e,r){let s=e.mode&4095||this.fmode,a=new EIt.WriteStream(e.absolute,{flags:TIe(e.size),mode:s,autoClose:!1});a.on(\"error\",p=>{a.fd&&Mn.close(a.fd,()=>{}),a.write=()=>!0,this[Xo](p,e),r()});let n=1,c=p=>{if(p){a.fd&&Mn.close(a.fd,()=>{}),this[Xo](p,e),r();return}--n===0&&Mn.close(a.fd,h=>{h?this[Xo](h,e):this[VI](),r()})};a.on(\"finish\",p=>{let h=e.absolute,E=a.fd;if(e.mtime&&!this.noMtime){n++;let C=e.atime||new Date,S=e.mtime;Mn.futimes(E,C,S,P=>P?Mn.utimes(h,C,S,I=>c(I&&P)):c())}if(this[jv](e)){n++;let C=this[qv](e),S=this[Gv](e);Mn.fchown(E,C,S,P=>P?Mn.chown(h,C,S,I=>c(I&&P)):c())}c()});let f=this.transform&&this.transform(e)||e;f!==e&&(f.on(\"error\",p=>{this[Xo](p,e),r()}),e.pipe(f)),f.pipe(a)}[qq](e,r){let s=e.mode&4095||this.dmode;this[_0](e.absolute,s,a=>{if(a){this[Xo](a,e),r();return}let n=1,c=f=>{--n===0&&(r(),this[VI](),e.resume())};e.mtime&&!this.noMtime&&(n++,Mn.utimes(e.absolute,e.atime||new Date,e.mtime,c)),this[jv](e)&&(n++,Mn.chown(e.absolute,this[qv](e),this[Gv](e),c)),c()})}[SIe](e){e.unsupported=!0,this.warn(\"TAR_ENTRY_UNSUPPORTED\",`unsupported entry type: ${e.type}`,{entry:e}),e.resume()}[BIe](e,r){this[NR](e,e.linkpath,\"symlink\",r)}[vIe](e,r){let s=$l(zp.resolve(this.cwd,e.linkpath));this[NR](e,s,\"link\",r)}[bIe](){this[TR]++}[VI](){this[TR]--,this[_q]()}[Gq](e){this[VI](),e.resume()}[Hq](e,r){return e.type===\"File\"&&!this.unlink&&r.isFile()&&r.nlink<=1&&!Yv}[Uq](e){this[bIe]();let r=[e.path];e.linkpath&&r.push(e.linkpath),this.reservations.reserve(r,s=>this[wIe](e,s))}[FR](e){e.type===\"SymbolicLink\"?PIt(this.dirCache):e.type!==\"Directory\"&&bIt(this.dirCache,e.absolute)}[wIe](e,r){this[FR](e);let s=f=>{this[FR](e),r(f)},a=()=>{this[_0](this.cwd,this.dmode,f=>{if(f){this[Xo](f,e),s();return}this[Wv]=!0,n()})},n=()=>{if(e.absolute!==this.cwd){let f=$l(zp.dirname(e.absolute));if(f!==this.cwd)return this[_0](f,this.dmode,p=>{if(p){this[Xo](p,e),s();return}c()})}c()},c=()=>{Mn.lstat(e.absolute,(f,p)=>{if(p&&(this.keep||this.newer&&p.mtime>e.mtime)){this[Gq](e),s();return}if(f||this[Hq](e,p))return this[qc](null,e,s);if(p.isDirectory()){if(e.type===\"Directory\"){let h=!this.noChmod&&e.mode&&(p.mode&4095)!==e.mode,E=C=>this[qc](C,e,s);return h?Mn.chmod(e.absolute,e.mode,E):E()}if(e.absolute!==this.cwd)return Mn.rmdir(e.absolute,h=>this[qc](h,e,s))}if(e.absolute===this.cwd)return this[qc](null,e,s);SIt(e.absolute,h=>this[qc](h,e,s))})};this[Wv]?n():a()}[qc](e,r,s){if(e){this[Xo](e,r),s();return}switch(r.type){case\"File\":case\"OldFile\":case\"ContiguousFile\":return this[jq](r,s);case\"Link\":return this[vIe](r,s);case\"SymbolicLink\":return this[BIe](r,s);case\"Directory\":case\"GNUDumpDir\":return this[qq](r,s)}}[NR](e,r,s,a){Mn[s](r,e.absolute,n=>{n?this[Xo](n,e):(this[VI](),e.resume()),a()})}},RR=t=>{try{return[null,t()]}catch(e){return[e,null]}},Wq=class extends Vv{[qc](e,r){return super[qc](e,r,()=>{})}[Uq](e){if(this[FR](e),!this[Wv]){let n=this[_0](this.cwd,this.dmode);if(n)return this[Xo](n,e);this[Wv]=!0}if(e.absolute!==this.cwd){let n=$l(zp.dirname(e.absolute));if(n!==this.cwd){let c=this[_0](n,this.dmode);if(c)return this[Xo](c,e)}}let[r,s]=RR(()=>Mn.lstatSync(e.absolute));if(s&&(this.keep||this.newer&&s.mtime>e.mtime))return this[Gq](e);if(r||this[Hq](e,s))return this[qc](null,e);if(s.isDirectory()){if(e.type===\"Directory\"){let c=!this.noChmod&&e.mode&&(s.mode&4095)!==e.mode,[f]=c?RR(()=>{Mn.chmodSync(e.absolute,e.mode)}):[];return this[qc](f,e)}let[n]=RR(()=>Mn.rmdirSync(e.absolute));this[qc](n,e)}let[a]=e.absolute===this.cwd?[]:RR(()=>DIt(e.absolute));this[qc](a,e)}[jq](e,r){let s=e.mode&4095||this.fmode,a=f=>{let p;try{Mn.closeSync(n)}catch(h){p=h}(f||p)&&this[Xo](f||p,e),r()},n;try{n=Mn.openSync(e.absolute,TIe(e.size),s)}catch(f){return a(f)}let c=this.transform&&this.transform(e)||e;c!==e&&(c.on(\"error\",f=>this[Xo](f,e)),e.pipe(c)),c.on(\"data\",f=>{try{Mn.writeSync(n,f,0,f.length)}catch(p){a(p)}}),c.on(\"end\",f=>{let p=null;if(e.mtime&&!this.noMtime){let h=e.atime||new Date,E=e.mtime;try{Mn.futimesSync(n,h,E)}catch(C){try{Mn.utimesSync(e.absolute,h,E)}catch{p=C}}}if(this[jv](e)){let h=this[qv](e),E=this[Gv](e);try{Mn.fchownSync(n,h,E)}catch(C){try{Mn.chownSync(e.absolute,h,E)}catch{p=p||C}}}a(p)})}[qq](e,r){let s=e.mode&4095||this.dmode,a=this[_0](e.absolute,s);if(a){this[Xo](a,e),r();return}if(e.mtime&&!this.noMtime)try{Mn.utimesSync(e.absolute,e.atime||new Date,e.mtime)}catch{}if(this[jv](e))try{Mn.chownSync(e.absolute,this[qv](e),this[Gv](e))}catch{}r(),e.resume()}[_0](e,r){try{return kIe.sync($l(e),{uid:this.uid,gid:this.gid,processUid:this.processUid,processGid:this.processGid,umask:this.processUmask,preserve:this.preservePaths,unlink:this.unlink,cache:this.dirCache,cwd:this.cwd,mode:r})}catch(s){return s}}[NR](e,r,s,a){try{Mn[s+\"Sync\"](r,e.absolute),a(),e.resume()}catch(n){return this[Xo](n,e)}}};Vv.Sync=Wq;RIe.exports=Vv});var MIe=L((z$t,LIe)=>{\"use strict\";var xIt=SI(),OR=Yq(),NIe=Ie(\"fs\"),OIe=jI(),FIe=Ie(\"path\"),Vq=RI();LIe.exports=(t,e,r)=>{typeof t==\"function\"?(r=t,e=null,t={}):Array.isArray(t)&&(e=t,t={}),typeof e==\"function\"&&(r=e,e=null),e?e=Array.from(e):e=[];let s=xIt(t);if(s.sync&&typeof r==\"function\")throw new TypeError(\"callback not supported for sync tar functions\");if(!s.file&&typeof r==\"function\")throw new TypeError(\"callback only supported with file option\");return e.length&&kIt(s,e),s.file&&s.sync?QIt(s):s.file?TIt(s,r):s.sync?RIt(s):FIt(s)};var kIt=(t,e)=>{let r=new Map(e.map(n=>[Vq(n),!0])),s=t.filter,a=(n,c)=>{let f=c||FIe.parse(n).root||\".\",p=n===f?!1:r.has(n)?r.get(n):a(FIe.dirname(n),f);return r.set(n,p),p};t.filter=s?(n,c)=>s(n,c)&&a(Vq(n)):n=>a(Vq(n))},QIt=t=>{let e=new OR.Sync(t),r=t.file,s=NIe.statSync(r),a=t.maxReadSize||16*1024*1024;new OIe.ReadStreamSync(r,{readSize:a,size:s.size}).pipe(e)},TIt=(t,e)=>{let r=new OR(t),s=t.maxReadSize||16*1024*1024,a=t.file,n=new Promise((c,f)=>{r.on(\"error\",f),r.on(\"close\",c),NIe.stat(a,(p,h)=>{if(p)f(p);else{let E=new OIe.ReadStream(a,{readSize:s,size:h.size});E.on(\"error\",f),E.pipe(r)}})});return e?n.then(e,e):n},RIt=t=>new OR.Sync(t),FIt=t=>new OR(t)});var _Ie=L(ks=>{\"use strict\";ks.c=ks.create=IEe();ks.r=ks.replace=Pq();ks.t=ks.list=vR();ks.u=ks.update=xEe();ks.x=ks.extract=MIe();ks.Pack=cR();ks.Unpack=Yq();ks.Parse=BR();ks.ReadEntry=YT();ks.WriteEntry=nq();ks.Header=TI();ks.Pax=KT();ks.types=_6()});var Kq,UIe,U0,Kv,Jv,HIe=Ct(()=>{Kq=et(Od()),UIe=Ie(\"worker_threads\"),U0=Symbol(\"kTaskInfo\"),Kv=class{constructor(e,r){this.fn=e;this.limit=(0,Kq.default)(r.poolSize)}run(e){return this.limit(()=>this.fn(e))}},Jv=class{constructor(e,r){this.source=e;this.workers=[];this.limit=(0,Kq.default)(r.poolSize),this.cleanupInterval=setInterval(()=>{if(this.limit.pendingCount===0&&this.limit.activeCount===0){let s=this.workers.pop();s?s.terminate():clearInterval(this.cleanupInterval)}},5e3).unref()}createWorker(){this.cleanupInterval.refresh();let e=new UIe.Worker(this.source,{eval:!0,execArgv:[...process.execArgv,\"--unhandled-rejections=strict\"]});return e.on(\"message\",r=>{if(!e[U0])throw new Error(\"Assertion failed: Worker sent a result without having a task assigned\");e[U0].resolve(r),e[U0]=null,e.unref(),this.workers.push(e)}),e.on(\"error\",r=>{e[U0]?.reject(r),e[U0]=null}),e.on(\"exit\",r=>{r!==0&&e[U0]?.reject(new Error(`Worker exited with code ${r}`)),e[U0]=null}),e}run(e){return this.limit(()=>{let r=this.workers.pop()??this.createWorker();return r.ref(),new Promise((s,a)=>{r[U0]={resolve:s,reject:a},r.postMessage(e)})})}}});var qIe=L((eer,jIe)=>{var Jq;jIe.exports.getContent=()=>(typeof Jq>\"u\"&&(Jq=Ie(\"zlib\").brotliDecompressSync(Buffer.from(\"W2xFdgBPZrjSneDvVbLecg9fIhuy4cX6GuF9CJQpmu4RdNt2tSIi3YZAPJzO1Ju/O0dV1bTkYsgCLThVdbatry9HdhTU1geV2ROjsMltUFBZJKzSZoSLXaDMA7MJtfXUZJlq3aQXKbUKncLmJdo5ByJUTvhIXveNwEBNvBd2oxvnpn4bPkVdGHlvHIlNFxsdCpFJELoRwnbMYlM4po2Z06KXwCi1p2pjs9id3NE2aovZB2yHbSj773jMlfchfy8YwvdDUZ/vn38/MrcgKXdhPVyCRIJINOTc+nvG10A05G5fDWBJlRYRLcZ2SJ9KXzV9P+t4bZ/4ta/XzPq/ny+h1gFHGaDHLBUStJHA1I6ePGRc71wTQyYfc9XD5lW9lkNwtRR9fQNnHnpZTidToeBJ1Jm1RF0pyQsV2LW+fcW218zX0zX/IxA45ZhdTxJH79h9EQSUiPkborYYSHZWctm7f//rd+ZPtVfMU6BpdkJgCVQmfvqm+fVbEgYxqmR7xsfeTPDsKih7u8clJ/eEIKB1UIl7ilvT1LKqXzCI9eUZcoOKhSFnla7zhX1BzrDkzGO57PXtznEtQ5DI6RoVcQbKVsRC1v/6verXL2YYcm90hZP2vehoS2TLcW3ZHklOOlVVgmElU0lA2ZUfMcB//6lpq63QR6LxhEs0eyZXsfAPJnM1aQnRmWpTsunAngg8P3/llEf/LfOOuZqsQdCgcRCUxFQtq9rYCAxxd6DQ1POB53uacqH73VQR/fjG1vHQQUpr8fjmM+CgUANS0Y0wBrINE3e/ZGGx+Xz4MEVr7XN2s8kFODQXAtIf2roXIqLa9ogq2qqyBS5z7CeYnNVZchZhFsDSTev96F0FZpBgFPCIpvrj8NtZ6eMDCElwZ9JHVxBmuu6Hpnl4+nDr+/x4u6vOw5XfU7e701UkJJXQQvzDoBWIBB0ce3RguzkawgT8AMPzlHgdDw5idYnj+5NJM9XBL7HSG0M/wsbK7v5iUUOt5+PuLthWduVnVU8PNAbsQUGJ/JPlTUOUBMvIGWn96Efznz4/dnfvRE2e+TxVXd0UA2iBjTJ/E+ZaENTxhknQ/K5h3/EKWn6Wo8yMRhKZla5AvalupPqw5Kso3q/5ebzuH7bEI/DiYAraB7m1PH5xtjTj/2+m9u366oab8TLrfeSCpGGktTbc8Adh1zXvEuWaaAeyuwEMAYLUgJQ4BCGNce++V01VVUOaBsDZA0DaORiOMSZa+fUuC5wNNwyMTcL9/3vTrLb3/R8IBAgmBTJZEqgsk1WebctvO2CkSqmMPX3Uzq16sRHevfe/k/+990OK/yPQiv8j0EJEAEeIAHkKEQCrCYD5fwBkBUBmDpiZVYOkpDqUqTOUqTkse7KqfRKkZpSZ0jmVmVKbVHvVGONSY6xdOXf2bfxYs+r97Gaz7/VidrNczmo5i+X4/79WaRtnVo6UQAk7u1v/33o7HGQdPSpQj/7rqqYgCstG5MTLOF+dsIv//2aWtasTQFXXSGVKy0Ch0FwtLAv5xL+sjMzIJeSZkqQ+090j9RMRiYjIRDMBVHEBdLMPuzhK9ArtKWmta6w91npmkeMIbXl7nz+t0qqu7mqNZH8NgWcOML8gqf5fsvkoWoqCW/Uv9a31Jb231iAdAFq2b0f2AXJIgEFCSX5xeJctKHDjpJQ3m3Urk0iC5/t7U/875277i6mGdxYoptsKpVKptp46HgxpRCOeWYxBRAIkEfH8P2f4vnxABfSq3okFhW7Sh7EOU6Zknm9b/2dQZl1CfrShJVuQKkmDUKRlwEAYpohyd7/uuRO4vjhiW92oa7DifsWphJQsLIonVqN9+X6G95E9gJv1/aVCu6Vysu/NbAvVQJAIkgSLIIEgCcE1iBZvi3Talbv/B95N+2tvY1Qof7OKQVArLUEjJSQhhBgSgWJaCGz+exJ5As24WxMMguChXfbB3r3z09qdsMUgWww4SIpBUgwSMGCKKVKkSDFoiimmuGKFLRY8P+/j/1z/z8vcC0/38z9ixBEjRoTHiLRERESEEhFKHk1poFts2iWWWCLiyP783Pr/f3p9jjDzv+KKLbZo0QLRAoEgGQSZIMgEgSCZEogSJUqUWJmUwG/uv3/60+facZ/fES1atGixxRZhCENEGEpElAhMifCIiMh7RNRARD0osUTmQzS53d7gIWweY/AMx+gtFBHZ+QKBsEAgEAiEnXyTePKGdLaKJm1heyFaU3uzbTmJnADDv5s+/2iBsQLt8213mBZIEC+iwULwYIFUkDqt7977a5EjE/PA5Kn3lAZJ2jN6FtU6hpJswxeRU8EDzmheRavGU+8SAXcv9hs2VHFHpGFd2uSqhHfl+2vjalI8eXtMfadrWGGNgIrP+vNSPghBQhnaYRowg/SWg6qitd+w5dduV3M/w+v7ZmNa2EHT7PCw7b26WSDoIaI+BqiP5p2zrxStV+M2GSTNwLZe7+NuQ2yBmwrOzjTUkFHwTV/eBa16T3gA4/213h/1KeX+30V2dZfwJfquaEB6xymhDz3/VMrY5GD9qnZSnAOdHwOrSiaW52B2t2N16zP70evD5mkQyIw0SkzGfUSC0v6MnmPjA/zDgnWuNgwjo7uqtquP5iVWyxtfYeRFHYCX8Ri+J5QLlWqdxq/rU5NcBfWU0gwJLQozOPn8AKW8O8tlag5jTBhcLinjQ3x+ROz+sC1XeAEFjsiL/RBz5ZaHIRt1Zbw7BI/oqy9GqIvPir/AVOOYmyvYsW4S+OjA6lAao99TaXVi1/zOSY7OsRX/YRjJGmdyzupZMt8/DVsorPED2dvEHJaq3K/NE3bKc+Ilrb/azbMvPOIR2+6+xdd8ma/RzeYh23z26tLr9RU6lUdspWd2NAZvk1KsuWtCCp0djmdRFF8HywmTO5KH5Q7JmWezwwKTluDzWDDEEErDdtCCr0a3/GLiI1+HFJKGSB6KtqRHbbS4nsotDPyRz6MFVsQZEL/84gHTA3INdbmG+IoQeUnuY9jGbwRzWSQPASvKFzPQ8sMX+Ty0xAooDSUYEg2rB2Asi8sg++mGqyPPdcZaQiV7O4lZKh/GtbLxz6f2bTsRiLCS7YyUlJjXyQfUAqv97xnph6+1be14kuOkiiW9yBJa3qGJc/jQpCNb/vnTbiO8xEL8sWjHbz2Bnbw/6u0defDAf0FGLaQbLe/+iCD19fZdW4gLDjOLrMbQ2T9vzdtlMqbVl3aCRT/5cB8G8CCpn5B9Lf3jpPZHybpehwzVihnKVbsZkH26pXEqhZl3TmBX61DuBRGWyjOcuBvMT14I2t2ppPMw9ZDpZixooFP9mAgeVVq/i0VyO1POaBTOdukyymNgYmnefdg99y0VvJTipQXLHiIB+GYJk6iLBUtXC5Eut2DpuKRTvuBkW3pv6b3l9xr3/tvyL7GOfiZJ5G+M1aBLJ8TSrpD/ib7xQ9H4b9AfOQ/uEcDmZB6cL2xC41vkwfpiTmh85keSHMtuqSwHp3CQjy0hCN4mosrShflH0n4J1MoTLAROsfy6R7DbEVIUplDwMc4bwsJzphym5GmaVt3+FVff00PZlpU7E5+eHCn5OBo5v0P3QHYrsHNk0PZ7klsowDlcZtJdJgvEbmwvROEM44XY0SuLhahpubgq3SzjsieuutCgAA3qM4rw/MfmzN6HiA++fyU4Rojl44Jb3lXXiQdVSyENix+uraEeD7BibuDCZyFx7aSSW3MA55ymmgAwipqWKus8ykE9HSnJ7CAcn4q4rnO13Ll54POTEjqOxF+FpSAggq+iW01ABNH0JIpBemwUz1pq6GW5MeY0mCE5NtDFSzPrukTra4iNQgyYuZRHSsz72UwNvCA042mO1PKJUG7b896RNyXM88mIr7W1lyhCT8uigfq1LwQ1zXpPQsUrUocxVC+No06fCYUsGWWUjl0/D4tExtJmp4w1SYeaLpnQJ7CNbVODe+nUys2PIKLyxnBq0kHPfRWcq+THl5c2JS2fQeZBVxYtIn74wmnVXuTeFKjE4apGeJAQWnr5Jum5VD/KXuOoyZRPRtrgkZfqvDIhmlbcO6TcjEIhK7mkfR/ad7WeqFjihp7L40OITvp037LNCGX/L6y51MCmkxcpjKCpzBA0noqXTJW2WtDBHUAiBTBi4eBW4rLSC2L+o208CmJ/sxGolgvDgv6hwNsfmxveCnGodx1iKVgEsUO1vE1JKVnT4SgRTO2dgh9K+H599CAmLZE8YvfNp3nhge3MhwAfna99yEZihxv/XwtnAneD0/eEOhyhBTIjd37wBrwuGTKcNBm0/Mx8mIj73As7n47h25bDP3X6UH6TyhtoUa+4M/rKf5ClWLs9Y21CYGxQE809XrP2Jk3orKEJ6hOiL28/33rVJeS5dVpluNegSJcPZfWrG3wDPe1BG6B5cHPnHbNBlhNozcJdZMyFTFG7UPzgl+oUCXRn+ISQ1WnXACLe4kbKtvvthKJhtUPPc2w70asPUj6hAjfITl0GnlA+vRox2VZA9LnskDs68Tk16hXuKd1zfFgC7b6qnLKaoEVXr+2g/BhWXIgw+GVBoqgnDnVuAp2qiUC6qOG4x6GNRVF5WUi7Odw/iUrK/gQUFTBttWGE+ceQumw2t+2dqUrzOrsHSaolipYpBpeLVPvA+1LureB631Tl56A1Wd0ryu96SzibapY3Nz1TXxbMfhInq7WkbUrgGfVaH2vd/tsicD5w5CYV+eISjPH/omyb0wzec5XMokuSw+38AZ2b9rNMawsYSIHvehmbPWUWUuFHVW7var3Am1LM8YFd+G9VDZuKFOvxqm68LDL8bNbjxFevGsFlTyXE1FAbwNZcd6k29dl6ub5BZ6V/O5cTFBmJtgRrraPr7PoqJUnMj6QIpMIodZLDE57k2i6TROku8ZdH3m6Y1vYJFSWTeioWMDaeNqyKHeN8tlp4nDWkSQxHMqbaON4f71KnQF1IwiOkHHPCMrVw/D5W089eWX3/j60UkkuvoRPJTsumkpFd6wW09GwYBwLMgvEZcBgHED3tGu6bESdiXTBcD8W+EIsfaJeutJZ5THXopIx6YVJDbcsMGmYsZtIXb8bsVjewXzc88FcTZ5lYYoFhIrBcO6ljLt5+dp5HmzXv1Kg2MwCJDrRr7qVlXdraGTP828XfilNRkEJ1GwtTE3I1t/aITjVWiTHgXNljdnMXh5wdZpZcKzszsONMKEJhMh0NK+bDGn+rAJDC3mgiOZxq1OUUXNsxkQWhYW1GFtRiWFZNcNDeLLlIQll0jLYPjE2ynxKXI4lcBwCNsxFW85dwAN0PW2KmOMcI6cTvka8d0LYiqm5TNUQfQJPIoralnyMJ4bt6oiIaYBwZu+k4MkkXTQfL1e90rIWXSgjgUBMgCXkoTn9Rr9HCuegYSj1NaIXnzEQUfbtnz7/FkaUwrNSQpHIL+Jj0VvXs5zg6Gn4hCOMevrvMmTvdBdt6DOzxoF88Zp3bG+juT/Zl9hHsXlZY/IeRVTezaepfT0+FNz8u+rCFX+1LykI9/PPmJIfH8/IRAejJVADY7rGj+r8PWPt4mhxDEd6+n9rB/NPcTe2dTs3pXtOjtNyFndrtwLPSz6s+d+vOkWnztCqcbmMfyfd0LcFRcVF8kjkoWIncdj9IKIfZhh+PP+DeY7TVAGAK++IgvZUF6PTLIJT9EhxpprSPCoWuxThGwP8vmEbDs6kDehX0zWXz47U9+/Hqajad+simdjof8lRabLnIvfxoaVOQL907ZBofU7FPER91ifRhlz9nXfSHyGA+c9sQnfOh/SDUqx+vRyM4oJLJXEyfaISzIFoC6MDWR2JB9vBLhhchIiznCQbr7n4zxaEcvphNcZfivwbIKk4C7kb+IcPA8u66nd2Gb/vUiilkp7G6ydQXj82jFjlebJ0yyezuSSbikTcg/iPlGxcWL0JnPmnSbXtHfKBGopIcI3lir17wt8hz8Tw0UHbloVh1oDnNdFBZVkteweiH42CzircC5ZTif9eeYhieGEnmUuVH7ai/JO7HRhjYEPIibvKkVqM3z0jfZE3TOv0ECUC8NkRhCWEHvAOZQ2Di9cpB1UFmdoTca81BmGHQHV52E9WYKITgpIkjtau2nj2g+/51uj2O1NqXpe7/et2u+ywiRJcxClnpB8zPWr8KpuDNG1On7P5XzL7w4LaThoWCyw51tg67gUiQxAvac5QMfVAg7A9hcPddIYKqXNqHKVTRL1cI18UOJxu71LHOStvahBLKaojwKBgRA37Txbt+RZS2SV8fnhjPK3JtIrQYXS/KbLS+FL65SGQrNoZCPoQ3jPPJ5oGmhVQ7p1HPtUJWZUSK9u52UhHSn7Fz4LaB7f232yKKRJk07LL/FidQB0163aXVWAUV+9Uo0KWhJRPowfH1uqYdJztTXYWif3SQ2veJvBWruwtw9FsVjhQC7panWsvhWmb/auexdM60b7dpZ6YWOyOJa0qT+G9zC+cUTlJul16NOjStrdI5+HmW42OyTZigq9e6wSExmEs9irgKnyuV2XcQjptcAhXGxzo0uId2qEuEZLPpPSpkxKQDdnY2nESOYlFBYmNWyWgXWU1cgMEOrISgwBaXV58jMLxLhTFsomEXb26Cnyiq2J2giU9Fm2absgPt4Rbymjjkcd7KgXAtHaXNVLic47oHHBk8ARny/M5iBziv+H09TI7cjX/4l1dt0YkbjOG67cwvyDnwimukP5zYBXBFF7hxXAov2L5b2RfPdccCG3yiboYvK/mEAdstGcwwoUpM2weBoiRPCYEpRZxbEcXZdI3lGC5+PAl0a9AOvplhycISXApYj/Cb6zYy1K01G+osg1+ehGE0m/zhJpyLJ7Z57DmuoP90ZNkReZoycA3m5rCOFZTV8N6IbLjf5BqGMUl4znKQZT8ehgTTt5IvwXbnJLz/7W2WXCWlXpiwfXydTi/zOvfh/iZZU5gT/fCx3nc4PpiXjU8MdqGAs84cdBbTDHTs/YbHBvUVFzcLVURv20/zNCLGxwIchrqFeEBiuug3jSpTTTU7nE2FRDhL0LYczn6cZASeq3qNqi1zQVYub8kofKMm6437UYd5b3/SO7CKivw4FWFPLCLc4Z8CBcULyQE9K8kclUkMZwxwWqSVYIrnqhl3jFaMYj9xzk4XxZQBOZeTHSYKTGcyN0fb56s9a6UvmqOL8RLP5maDP0skmaEs2VciXWCWkS8gbAyh6gHDIsnXCmDhDERh10JM1UdBGKpt3XYeJrw/+Ox5PFGyCLErC+uRMXw76JlFhorQtT6lEItxakSkm2joAbmHfVOulpr1LyuY5qrCVm7ZV8y6SBu2UYc1R9GKlgLZ0FCB7GyxzUfoiunzAJUkS4CwDLnKYZlJE5rs6JF008a55Dco1ZmpojV5KSQyO3RGmuIu6MJqCkKcv/VWPC5Cmzr77J8L2amlHANFA8v4MLWPFTxCuY9+llLIkHb9KqC6drvO76U/HhzYd4TCrtX3hIMtbCl4wpA/crGvRH0eb0k3lkNxfNADxb3kdLBtYQIKSVtpVDXnukN6/Jdmoy9bYx2lx/ziK38opmSgnSmwC8vM2i8fKZ8MSMatN+ll9Va3rQptqQeOiUWdB5P8j67+kp4MWQFGUJgq/jA2SU0WLYbL3FznrYOcZUA2pFzq8l+c26QbiCbAl8Ch0La9zRiLDPy2srfCpXRVcMOatjv3XJEqv6lQBhL4ygI3GKN8DSMNoacSezvDfw84MD+EGYUFiyxXhVwAcjhmct3ea/nmTEyFPJL03efr5cMR1jXApiV6KATnd6csvUBQIDUUE/gF87lpIhcASzc3FNkongQzQBhyilusxM5JCHhq1vsAHUSGlgfPu3T1LMf8fUvu+nWo1UBLM6eduqghd2CF8y4g+jxwScriC7to9zCH1oCqa+AO4eXSC2V6Ayu3vW127r3ABmlmG7suJd51EhqnAydEaetoL5Z+Ih9DtWAiYG1DSpjkcYPAD5smccfdVDpabrJdAdk1Bwhk2f/0XFt+gZ89z9cWBxBadW17CYPkcnfxboTMe+1Gm9uLOdI72/ZEW8/y0dSUqGtJdXZHqbBgpaZqxg9gdyvqrqrbu6pWaCOvqGZ9bS2aNQDDcttEfa7PXefhfw+AEl08ngtUlua0VZbiX43A5T84leaUEbC5JWu0ClotsUtMv9U9Ma8XonMcneCouY74ROyoXJb2qJ3JxdQ0t2Q4GJsnrM6NKuEQsucEeknJx9Kow/RNlZAi5gmhVfd9kZGBWxrcGjGGclP8Dlyf/begmrKtRtKZ5yBT8yKmq5BbFMBNJ3ipr7VHfJAIAEVxbHyfCVVxhN4Ea+KJOX1kmZaTU/zPKeIuHT9RFhcximF6rOEch4CCeVy0QojIiYrbkxQjbaoz5+dTT2lV8Rvem+gxY85I+O944aZIxHzaH3mJ0YT77dfahgwJEN+Ecac7wiCCIbmkaWV98mdvPxjT8bb5DRzhJR3z2dolyrlyaNktNUvWxPOjxcke/OgOG/FwhyIXgS9DOAEITNdNLXNtuKDHc8plFH43V4UF92UVd917U4OC+UYmM9htdQeQb5I/FQp+3cw6YsWkTBNupvHaX4FOeZk90YqUGUsSz1gWzC1geFSSiYQeEdS0CY6LXPM4KVsvR61UCB4pu70JHkvpAE4e0B7PIba/7aQvUbAr9ZlScVQ3ZXzHatAGkBg+fO4eawSGac8km+CpXbCs+fb7FJ8xW/0Fy3TDoZwOwb6pW+BIv8uCG5EDbNrUSRJ/WUcQn4nnt35rFYyt6GLoroOfLw+6Gcj0pO2fsa+AtutLPb9/jmtx+rXd6t3Ls22SglWOFNbJHGG8r7Q9xIThX+tITsfORZ/N/tf/jGqe2ikQDYq2celmNH7OnXLzSvuO9YNSrDOoTSTs3LlGKochkEZlMW/XAAMt7Yp/jbjIlVq2TSg8sewqPiwvBC23Zm/dTcmPDerVVzsUQcHhB+nzht1kaCTCdTNhdvoWKwvYZ4oSsaqOGGcbb5Fl+rid+q6arHmMR20GI6+uWKihVOIb707/PrT1cPyirhOh3NZKdbTbl0cuJuRSqmEV3BOkAGkr3zd0DUr+L5QTewxGAetWpDipU3AdliEJHg0sdyYLdHyNYQueZGb6g0jlOWQQ5J5v3aM199JVy3Uf/1Ge3bkUt13caf0uBvT8mPeOg705fTxlxlV8YqKpH3Ky0eqPaZDkVLcckyXL+x/Se8g56COoCA+vP5ov6o+Gq0F+INLDEJbG6H7QTc1uS8BzgI5xdRrVjdzNfNl7xrtUcdNhwEyTmciqsCw9t2xIe+RMCZTaG6rH0HSa8IzUrSafJqsbmtZwLNfIT+ipGbS6EDg/AOjP2S0Q7NpnkskF6On9uZfJBNMc/vRuPPO+CgdQfjClqSgsCSMKIdCVJSvc5lo7XijOtAu1+cAnisoJqanxLtNhMiZquTYxAg0RznpnCrQ1N8m5SKv/9Ka54quCMo1bPbNcYTa/iO3IWD+FCky5gplE7yvElfoQPOiy3GB0tsPgZH0HbIeEcx5cI6QO00aSWe8+aiLcg8lMxFwL5rRyH2XFwnT+ZpIDbUYiKNB/G0P3n75pLoHkRmfle8JmO5BO2juC2oc1qe6HJ/TC45AjhJ6czzOtLg0Q99Zri3cs+gIfZMwKN+ZARqPe540Aj0bGZso2NHB1O1t5/RkeDdikWUxkEFPKEMbII7WtZuIc1sFeyNo0fo+No1AljZ40n68sAS64VLmvZ4P5++PAqbMkRjyKYh3PXfxynQI1lAg/kz1Ky+RNG2hK0Lu+tIqLD7o9+gSk4ACGxLoKeLU1+YaI1HXJtoNRuw1pMGcuWfZTpIvUyIatl1l45Elm6xNdbDS02RGC7HxTMmZULCwdGyYXsYp4/RJgdqBWINVf7FKIaio4QYm6H5aZIpV+2XsVIn2ATFIBBq739vS8O10e1CI9Zros+/6UQ2nmCDXg6z3adf3sV9bEp8t+e7piPl0Vn6K+O0ZwZDjsWLVv1mgXeNI1bBh6kk8iojUn7nRitqTJ7o+xfs6NZTQfilDoypCeK/kaNg0+yScxuUa3HXBSpNCIkv8gbspwrErL08UpBDJieyBraCuOA1hAPfmkPFJZ9wWq4uR4fB3I6YYRqJERQ5cGX7At+5Np41bUzSNyjseRMm+HeG/Y4AOTh4sFQ6eZrtDMr6g0N5x4Qj/WEqGJ53g3lPIgwX/BjbkvAN63C4acLsxgdIE6mJCCXUZhvDTnr7Nxa6EAYH4AlflhCVNGE6TM10ypmFEoUVr30VFr5dMlvj1dIZ+iXWpUQpswhGTZ0rUdIE1uAB2ho3IZCUkoAETlgWTYTpeHTq+R59HnIeee8yLnEKghPA6gPynJCqv9EmBxl5DHixNZwGIC+ISIP596tmySz1lKWOfJSzCNvSCsphu1WSjnZ5BhOFZrKuj4Q5BJTEAqjd5FcdDoy7EPgtGmeNT6dAtdPT5oKKNBnrUNt1bmp3X8dGpblRXKqVL6+ReHnjdSY3QaLY1HU/FmqVXaPTFvxYHJxUlqTNMfb/OJaIMHrSXQ6d5QHmVpnSy8xGXfAcd6FdokA1MKAzBqB+j85xb7scozV4FTownJXNbX9hsG6i8VjLYfYfFVwvqdoWg8d49fazKaITx5BOo3bIcHKBdMaTC3DrBju3cwmjGERPEz67R4I+AEDzJIO3z0q/ZjUo9uI6WejbnyrEJp+V/2TkToGvLmdDxPqLdErgttfHueQZ4wRk42tDr1WI8ZUpkTvHvSi0wss9WMPTuTccFYOp7Vc+65+JKgOZUryMKe4H6cmOM0m3GsQxeaOPGNKY9TnaotMkhqAptsqyevZ4uGBuo0ZWacIsUxWpCQz+DT7IwKbQRnd1CSfDDOh1mmV0VZj9xygoOSlrf3TxLf8QylmirPfJRzz0bzs5Rn15+jMml2WhWeddU8AM4eATCKiVf/80RzQzE/HS7HcZBCA7w7y8fl0m+8fuf2BIEPdXRYvXUac2yxwkuOKA77mLoxfFbWKQndw7U8GDJShjJxBIgNBGN+UU14ox0YgJ+IM7vYX5ObmNF8NKUC4CN00gHk+OEuqpI3rCNei6d1kR6KzxyHsQ2bruIRx1VHoFq+zW9Ig0WemXUnkWLSlgPd0Dm+ARifyFS0uujurMDt1a8HpqbYz911nQb4TwHyRqdLsFgm3PLoUmOnDL4udj7Z/97w1eaPfyMtBP0ewBq4l/Xnypqpl4el6OnUYFt4SecDUJjh5B0Hg3uQayutsdsj6iRMwO2hMuVSyPagTWUEh5No3x8CE/QRkQHzxmWErQwksxqj7aIQyRA0obK2FRuX67Fs04IxIWOrytjmMZpyMlZdOQowSjQ2jstNQt9dyGFTjTwsdzQsyj4OQ1SOojVrNBLDUtOyjB36Q88MyXlKDihQT1mhoAElDZhpRAJ1KJkLj2EwzWYaI+3SN/5dVpV5LZftFyzcztT2sLCjuGuAKPgaNxY7Nc2bn2UgA3xIlzlUPE0x5wMiNMa7b4KpKq1kS2RcZXz1l0RJajkZzj5iiSqvqYNE0wvIytCMEQBK8fuOzqNBwV/CBCcfhfuwuq64o6mT4miwYCeoAblNBALa6rhaPPQTiijH4KaYg2bD9IUkWwtoDFhpw2/q+paPxEU3jCQGs/LnZKbNxJoqZecAyVC18y6st4me59Qnfco59MewM7GFrp8eZChAKRvXk1tLx+HFdBacQZHR0oXoXdscR+45nbBRMdY0Jt1QH04iAHUwDO7Iku+pHtupJ/XuNcuDeCgbKlpbAd1u91zwSjAOoE80NFnZX8q1YRnYpbffDudICa6eWt5NSVcKLfl+cbdk+sUIOibTNqBNJjyYHkBbLOfADZHkSI8CCggwbr9goMPQZcvj6cKiR+uOQ4/HK/GAOIzNcVLj8a5bVHwJIbNgV+IosU8kQnt/O6JN4z08ORoYvyN5iOfg4xJgMRceOc3anQf65YOrZTSP0Zq+Rcsyms8Itz+PxKCKxZkYMeVFOKfGYbISW3i7P5Iax0nQH+BW/QAjDik9AJDdDqTFQb1zfgQv2wJ/FO2jTAh2jL6lLnM2dnbL/7BygCU0AWKvBHJbwu+CED04ZVad3yNuNpb93gn+XsopRH5LteJEwkqG+Ekrqy7OJlRyn5UJ4BnpxLRCksfT+YhG57Ay0Ivh6rmqT+9J7yZXr58Eus52M4TYBYndTj3HkRS7OBJ7dUkfcRDKiLrgSRcxZxD1MikpUfnjLYoBgonb3gcE2R/otu25r2+sl8+C/eTRvq4+dTSetKZnL4qG/6D/Im0MDe3VQRr+lkROZBeXPhUhu7hVT5NL512dVCWx71GZo3MherjBXD2vePP+q3poRAc6+bB6IvVW+xcbAVAujruIz8OE3RbaOl1Ugqs/uDJjqJRpZPQ0SlQ9Ivo1WkaqU6R68Mvrt3lPeOvET1iGUQXgTMyshouibO3A/wuZoOjc2hD3B/OdIjSXYkhPII7JCPu3QKMV80nSyM/n4VKY7pdIb6qZhR2JvplYrasbD6F/cIKnNGHvZkbINmSUNy0sdlwHbCEExifPCp+l5HM/2kKUEJzMZluCjiXCNENLG7iyYGLvnhldiknwSxYHZN3NzDk9D8kbcCT2woGofSJem943nDYcmMtyZCpzEMdwsO/loCxz+grJ4MZitO6rDKDHIacWBxibAWoc9BWWwTyoy/kNdOVEloQkyII9AVU18e871tLqGS3CaI3folUwms9IXwEaXE/cqv9yRW4ESOkBgOxmgJYM/6tyrZOHVK8w4pDSA+DB6ZW0ZOhTtGRUjoZEfVEetd9rNOYClETrOvfURb1BWPYd9e9lMmN9edm6qA3CfC/S4BpRLTvrhQw5kfcdLVg/ig29gUiTiPdeo+VHCmwWnCxcl0ZNLYmYOGTBPoLkfUd5/fRqQQVr2ToqcEtoKAc1mT1AXDno0x4vt+vn5WzkXyHLXjI38zzj4ty/MLhuiLqYb0FXHHmQRABZsAOpKkB3CYy8rp6YggkRGyElTkgUR4gqkhCxE57jta3ILH4Gn+nru/dQmojvt1k+R06Ba4lIkp9IDHJ5VWdBdyIFINaQgHe9u1B7PKcdQhGKWcg4sJTW6K90F0JTZChHDNkce5itjJb5yr8O89zqdb632zyIPe0df+TBW2qNtJQt+7585WbdQ2dOlTAnHsQSz002FRKZvcPR8/Qc/fK4lhzqXcgkRtdPoTN7kXOMGRXItT0fr4Zi1GSJvOeB9SzIa1APrT+tTPeDxfHZpd1itV1vgdSXkiUlzxzTS+hJfUoD2UoZphAnfXB5uXoUI8EF2hcXj820hev769o1gsGYtEa1tFPgATELWqPyeV2ZYIzyAl7J+Qo4F/a1N3LqV/OjrnJGpoZo0uI4Y1DW1jf3DRqEzWv7RRdVv5yG4Lnyh7agT/tf+tktBzkd0sPdHFLfP3ZBpI74T8AdJc1Tf2g4TN06i6ziXBnwpqSoypI3u7D/aPNAz/D6tI4YyGUT+cOzJ71ReWL1AerHHOeqeO7CeqEBneqw3DHPhYutpNg4VQ+NMwDTWTzmnjE/97qTUKzdmxox9WPjwyr8/58Bdi4dU5JylYkp9ubriWgYgJYJBF9Qw//H4tSwBgDEJRALURops49OS5z6RZtluLDJ0x9lA799/c34tDHsfWLhDLX8IklPe7Wtp/V4NO89nFMo7i9+6RC8gWUx0FyZIMGGOR/WjiMQ9paDOkxFdRTBSfaVVDA2Gsr0lxDsbwrR863VdxY6i6KQQBLJJV2nGQjU/Mjtwp7+AekN3fW3A/7Dexq8poXDXB3kGW19YXa47n+n9gMpu//ZPwFzWR62lY6J/Tm8pVlB305Smnkl6In+9yEVNsbk1wRrxY7077fU9sjDB6ntBtBpgd2hEdKrv+kraxOWGwjTjOhRX6IQXE17xq3LixEEvQkMM+Ye0BFpOg5jWMCwStz5yGye48bVSa3WvB19O1p7nRv6tXlp9IpT58bvHtjrXsWLLe4QSmL14mnfcL2GmS7BYK/vjDkt4lm8AN3zWxix275LeB7nitYSH3boqqh84JEUlRdUCSqMLxf5cfwC+0KEBfU01o0U2ddbRNFuQICKoT+p8MeYhwZi35FzW5c3BatsW/X09ZfOw2K/XY8NNZ7bW3hPd09j+DhJoFopL2Td1KTEJV199pnPzC1Mv7csySdSqxt52wPq1/vxEY94I+PF/p4w7nn2/maWKq4ij//uPUbPPtz7Iet8uu9+34heqvtT6XaMBcCQA5dmE6YdznFrpM1jhceli/E/VkZsWyo9dL+wWwvPYJeLud2MkvsCQBaTjuwjPqTReNJIMrJAKcvsIuCR1x45zt00mwAMdDhr0uwmz5o/E672l6mxa5uSvi7g6dVUyiyjl+Ki4M8PdC8vnIdK695dhKM/IU1YflL554i+KIFsmpa+vhg1dPxi4pPRf47NVb4nh/b+1BZZyXt8m1BEkHM6OzTEEb7jhtlIZMb1tOgRe12nWf0kp1iu7Y3Zjwtxxi9cscph6+Wpdek9k2NZe6t15LBAOMAA9bM02pYzOjsovPhIrf7cfs7Pa1Or4UaRtUAbKlhl5F/unfqvPMiBnAOil/djhSc4rS0c3Ji1evkgvKI4lyivNmGl70MPpN63Gk1Mix9dtf7pivhKe1Ib1LmcwTNoFNQS2XxhhNIA1gDKgwua/CzrXHScGUBOTb361NcszobHMitEj7TzDDB2266FC1hc0XliJvE0ltDflTsPLq32TMqeA0njyEngPyfkyRXqv39HpwJQZsRBHPrD0Fx2UhF7UTSH675ZD1i9ETygY3cFWcZM6IUJ+J3v5jc0jwzjp0Yr1DTOT4vezCVrqO3TJVoEswD42nl73LYLP03itFGb20YFwZ7zi3SiVmeqwt45dMeut02k0c0o0Lot9LMq64I1WzlSzuXGc45veEqE3SHDeM2WZ1kQRmnpGBpUi9bv+8NbQo7Th+8W2d63Fw42nFzatdTjhWEak2mQF8tkhmhwJYuzf2v33iN68SJPVkzcqiR3znKD1ZXD/ydzLbUdwLltd1Mfbc9w/P9S+4qyDsQ20e/3mfbvRAtCzNLQRm4cN4p2KGwDTxGdnkbSnUOI7uM1LiKXvqWXrOoKc+rxbDC09VyntHsFxIEmCUlRhHU/YTOyP74+KouFO1OF1LfmUzwkF/i1U4/8yTtIqbJKPRltRFFLn7Ld4PjOGFYGNAmd+EGG2P5pFEtTglQu9qPaQg8ZtHIFXQAukCgCpPde4xQoIzaxP+yPQxTA5riD/0FwJ4hED9uhk0W6/Wchrrgw82nl/xaCX8uKIUgLKoacHY+ZmBtbX4JSrV/vUalha6YBUOAH1tMAG7W4VAmCoWNQDLkBMzH49fMDlIO/b6jYig6JCXyhfTiyFGjymkPiyM3p5hvXg0mpQTJsYPtjTjqu1mbeYSWrYh80f90OJHOHOHJahZCL1EEuhUSUR9FiUXNaRpX89llNu8DXdA4xj7doINu8Q6kXN3lvp3fost3vHV7KMdYhtGIpvpx1pVimIu2Gm39hPpK/m6KMKVvhT91EOxJSgQ1TxNtzmt8WV+IfeiutIrRxznlCMrRB9aYamZ0sdMVm2pbCCBeLeArNOWnRQ8r44uYvXqV0MMHl6r8fCp/XFpGYVC6/gNOBclOa1pZkwbmU87FR0wh3DFIvsMqzO8g86q92AVgXKlCDBtZOfX+3SW0vXa/92dBx5L3PMRjFFkbhJRAXzIDOLgv3CZuOiQqD10pHQb7FoqtUS4xfsVCxKgAnW+72X+7PkgNFjPE8WgUgh8eX6W1gvY/UcjnbfPzAd5vjl6DB/TISaX1DFWUWFEkzvM3jer1BwAtKx0B2AOPYGL2DtxvhiW/TuwocAXO/UKtnTvGLWPJCWbwN0f5yTlkUIGNIo707TNY/KbbRWsvKVjYTm2CO/BAtV0XWnW15YA7T+B92yN5IUvGvXl94bN5x49vD5JKuS4yjdcrx+g6JyTxZL1NTFHTkOfIfWUseh69la1YBzdgi7a9WXyzxQrEVDzC1YWqh8rN39vtEbeIBDVEHgH56nsgYq/fauFgbD6u+q1RzO6zaA6D2RAxNGAePqVW0nDzqiZtPCGp8P/GPmID82P9wS/UHKxXbJxfAWsYCENQGbsfydLYzy8vhkTksn3XgNShDELREsxG2VjPi6AJZOwyV8xOO+EqHDmtt/jw/hCIg3XsVvgXPPsTybLbfbbzS0EZ/2+b9zj+1PA87FNYgYrlvvx/V3lMqQ8Hz+s8bnDiSUu2vIL00oMn81NaO1WxIIixPWxlo9WvX8dsw7aNR7kDgCsJppKHso1VBGmvmHqAhiana1+i3yYFETyE1vtPpc6J1QXLUwboWe5/R7cJkOisw6fCPiJBghYzyKL6zc9nahDl+l/xFNCfSJimbUCCP7wp+vDzeCuQ7S4VAPoD9S1dwJHZp3fng8+GCfP7vBIMn7GbdIQRpHv05T2a9+2kp84hZ1Nn6Tc18ueBdXfHcV0C9lPxtPc08HucFChZoyXjCIAsErejHgtEusvRrFk3HA7jXY6EZEL/S29ZFrZ6Km/CGs+fj3M8qkWzMJFb5HyWNCtfBCryU7wQnVm3bIYK3jqBPkkt9nF3sY+f1wTYtgvRA58uqvY1pf8TLanzsaDA3IEhQM12NiVlqFuNwizzh7/6bwIxnzOza9VAeILoQDrVZzVG0+IDA8jNTJ9fKJuwx99dq9p37ZhlqHJeZeMXo8yFEfdE2jZCaou76IAWa9H4dhts7MWKZZ74O0z/f7BoanEpX/aIq/EEKHvPDlKHLSXo145vg7QBkxFSvXmpf+lO/M09T9aPbfIgziu7rnKrRj+4d6kb1zorI6B0nJ8qhMc7+7M7zSh3XSAuQLtWWUSsLXGoSkGMWK3VgT3BOy3F02Gg/9wMw1p9wa6SwkrafkmrpfgN7L2GJbR72nAClVbtye8V8a4DPyQIu0EhmSgo1Oltrp4RVWpS0Xx/UqzodyprcKVDqpERN9RliKi608b1uKy1UyO8G54ZoWIoP3OTJzFh5aCU3ZceHeqFTMzja5JbLsh51q1IIq4MQFyaT1Hq9aojBzuMDlvwwJD6TKp6+rWlSfKUNWYVIQmBkGlgo+CFyfygBgmKKuzxTIxSJdsZf1+FqPFugGUHKZjm8ZP72tG55AIUZpcWdiQ/iE8lKqIKrajmMvGXyzTO3bjaQCZ3rMJaJaap54V9QPftcmAkl2lZfLmS9tbn5mBnkCIRY8tvSowaesopFhUnUOclWirztsmmtqu93W0fRf41ucwSLGiMtgStPNm3WNxtMSHLsMeq8jaFSHZ9kOvZJ6wuT7FEyLD8Yv+uzisUw68n3H5TQQsaL/tjUTwYIkkBML99VKpPdISLwCENHAOANUmcwqI0g+IMUjpy+Nn9Fx1Yr2b0mvqZSEdEm4lBwNgdeuPyhlGru8p5SvbNUDA6YP2MF/TB7xkwIeDIEzqYH5UKymipf76wlfWXxhDxYSjrdnuAGg30N6qzifM8DvBdcRryjmrU+CDMJtLhGuoKZVMBSscgJk9Y/l5ZctkwNwPmKJtRcd4lIq5g1qIu+sefQmeuUmleU0WG3YXalHaQqxdlY80WdMzsp0FtN2Q2UlDsLV1i6fhnTUre7pq0kcQ7hmtpU8VJUsxEMOngMNVuEibhaNZLMr8x11LZoeJ0dpEIvtywIwo4YvPktiRepoD8PLoi0IDzu7ubGEvms6twDJy3JnenAR24eKHclGnNwXEbn8uyxfgTABY3pz+GPQbaWgDyWTY++zP/jg3fRHy7Kxrh6TxvZsC2K0T071qArULYam2hKmhnOCoWJGXXxi9VPOadzx5lj43GN/7fYAFRFNDubI4Eh9vxm01VOZFEI0fHJzHHmuHl9bVjDr6rk/P8cb9c4JhW6vBtXLFJDy/GMplr8MaHAyknKnf2/1CFf6Jo1kW9+iFXItI6Dcw0u8hKZqJWt6QiY6riwjCKlNbBwDI6uYwtYdJTCRt5GE/PO/XBaI6fZHr2+NuiZDiFbkXMCWUwsVe3gDJeyZ66raXNpnzff0JBDH+dQnV5JpeTYqz7nQFDpUdkP9YAM6ZCby+tO3fZDHLobrKhJqsaj5tvBnDDiRXEsLzX6IK2djp9wKKH3vbjd5OZ5wxTRYFWmnCmAHmN8+2zO7mWQANUwBvDpxx44kS2x2d461wJgzA+hnt+VYujuO9J8ab1bz7g08J+XxtrdHMU2Q11sWGtb1ajdvRX7Ycf13NOJlfWdUBpxoN4kfMEmgC4l/4py7Xm9nnkuaWf2o9CJOVLNTWS/X/aOtXoph3sNY27ym0FqAug2/kj7jZJ28dOPYrD5RrnfdXjbU+pSi3VZyj8LJLzZCqYtRB1bOo1Sue/XF3F3pc2dVBq+FHZuod0Rivt3zsE98h99arUCUaYEBPvjmCZqeXtTGQiT0Yeh0iLEnGAfH0dUht9WKOViaxVrqsh+izP6oFdT0ouFvQjVQDFcl+mpeEcUdOpFoHg0JJy3c11gAvurWC8gzBPdtiSewge+BiFZA4AJUlAyZdkO7YFtBxiLmN4l6oTbCAJdv3OspEXBV8vYxoFEjJyMWACi5XM8QmQIoC3oqf+IkHD8SdUhWI1jcxhqk27jbLYY4yox5OIp8XavBwDYAr2Rb6Wc884TqFDh3qYjC3El2lk/AqyCRRnh7siTEuH3VB7Kaqyt8GQ/lzeN5SViIgrDCtM8hvbhCmFPpSH99dE1IS62QU3eflbvuA1SEeClfhqvC/i7YQgOFc7GRfmRyzsgTUAXLPcD8ND34Km5UzfowwTQMWAiu5h1CZ7aN6DhlIDy4iqkSoPlppfyXq5UWgl/baz8ATbywzL5mEAJ6JnGJ6xaCFwnFNkAnDzFnQZqIAPICL9OKyHzSsOEUrYHGHjQelWQEjGojkIZ8ji9sIB7w7xlMd3APfhNODKB51feEbINNvfm7b9oUONTI1dybZxzm9n2kmJgvcw5sF8kJhN3kemSjhZibMxV27jV75hATdrH15J6CroCWB+DOkVH+EOiCdyb6yMTbufK9guzqSbeuJK4hLOmnKIwcTQspZUClg2K7Mf0JtGTeQ/HqZpC7PNYxCzeU0mt5tbrlti1J0MdOQZ33QVJf/n7PbOsAbCO2d06CNQbtAyAdSQrNMXC0NWpnPmSCRoUFFlRJaeZ+Z4SOR6gQAqo/U4DoE5Sbb3AZx4vgZhyrFy6PbzhlkTxWCgrhcDezEZKldMgzVOrPSAsbAHowadGZDEuniZpVvfnPdGL+KZ00NGg1Vs1N40WVs1va07fSuDovh6mAjuCGmXjqCIULnVPsStWPWUq456n6IMmHXOn9vTIb0AV+ERrADpOHYglvFGNj3JJ8hVKSynUPqAclHrQNnkCyX6WtXTJ/GdiBA2HcX4/UA3GpNF70urARZWnYBv1wuaAUqU54MFwvl3KsEPVH8rq9rFPKR0dqm3aLUbZSRhkCUxKCYBicPVYuqQo0V93Aoqo+mkUJzRgqj6RqIVWw+n2kXts59IRMd/wVOYTaEhD1DnfGOmTGNus1E5edrHH/Y+UaerZUTEuEgoFEyTSAAD3IAwNUZ/nm/tKwfIr/2bG1XjYK1a4YhFg+BbjYpXxfvEHngADkXfSAeOQXULQGVY8O4nRqnxFYPZHtdm0DBPlLu/H96SoJ2wT05u1ye8xkVRGQmnwLzNiUdb7UC7sc0oQO1No54IgN2tFG0ZMmOoYlhgmV8+xFl0cL6eCq1lcSntZAd6Q+kZk0ls0fVD08fDVu8Kzem7zfET94w8YcJK41b5/DKVDevEFJPsliIBqUMj+mpnH5Ht6ccyltm8CnB/ZJWECv5StR6y2FqniG7V/26IMzRPd0+UMruS+naD0z7DCdStVfdu+wN7YKxb7YCtilZrWSNJKZG9fjkNx77fRbomr0j7W4w6Z/IVl9Icc8IPfApB+OF2PG66NK731jLUGYWb9HgEazE6l8b5tzCqZ7Z2heyMdgOE8V5pvT99gHP8y++9t0IoYnMJASKHDGM13KGwG8dhLjno6k4A1mXpfQO+N+1oNP1wCZqTLpJ61+jy5jCJb8sGP3NPC5dp2Wc09GKpX/WBq1CWj8906tTk+lB9ytk+A5ZHFhabqGin1lQRN4wmxNEd1CSuiy0k+hg5RORQJF4f8CMXsXxR3E1Dm6F+40ajj8hkCx2ARwO9rw1rnp/kspFw9Y6H71m8FsW9fbNsYt3bCM/g9P+cvNwcSHdwwa3yCAz3t9lUag/6sKdbcBqaqLy9BExuvW8eOcyv7uKMJFlKycAGdjCNCC0h1+mcJqbaf5lrIHJEhTOR5+scW2FzN9kZQZaMsgAbpmEiYy6pej/RnhPesKTP61hCKcR5ERR2f0xWT/JbZev3QBAZ7Z4DjWzlvxIVMVvqTS71FWaobdBnVmW+ZeFXiUUYJ+wJlf2hEGySkL6qtk0yNG8CL/AC9704eCnBepEB9scj9OrJX3kfdaChUHK2UV7F2dOeQuB9I5i9vANRw457YlljMHIeJaDbWe+TiaJ26riL3f1329f3Q2FucOurSIWWQ2jCJ52j6ZSSn/+sYAtocRfTp50EQ8tDUZjFOrVF8OEPWv5xrPf6G4kFNhxzFco+09JikmOpFjTjKWh27NQZiGqlrf5jvkkN+2szHUX8DgE3XbY7OTf5ldJP3zFOGogsH4rsJSstLjxZnSazmsMNQQsm0sjinT+eaNm7PG0j0NSNlGeQ4qPjasFM8y+RnBwGKcbSiNFr2PzsE6I8fFdYJ4IWnjWotZtBZtDqukcucDohIqXMoWhJF4eJcU6Ff9iDCw176pIzLKfh+WyJr7fZm5/tJvyC6nSPyxBT+dgdgUMOnMaz/fH7IZqehJvh2a2T6ZEhnNrqFRny3DkgMal0Z7sGS3Jw58rf1Tf1Uhsk31rItwgsotYpCHuucOO3f4TxC9gMEg9X6GM0AxUBhUa3l+hCXvXDSCSNTOiHxnUH2/MN+rNIWygUiPlmORqhYZ0tvGhJavnaPJTCCxggvqEsul7zhE/JVNAn9C7IVRwkvI/PFAYY7lEAGxpdeDQ+EHWlrM/glBLgb8+VTQmsDrkDsGcKUDFHUpOxbqlg3kJ6ej+y234ABf4gpjGJTr/NtpjBhmC3MarGDlAxpakIsaeoPBZiATv/rhJY6gyIneE80q0E0D3gXlbtZKVcXaYS9rQgRU8B5HIlYFqUfQsbm3oeAkUDBE++iIe0zqrQEPhCA86AsBvWFdEMgzgV0nBnV0bARuDOZhbZa59eN0Ar7ZzsrpNoV8gd9ZJlv5TwyuSu6DMJxAu8nZno/XBFGEm2e+MWiJZYFYfmg4XE/5rMzFLbZ9XiIYp92cBmdYmkwDJN8Pq+TU3T00JmGEbcduvzw+P/a4tY8VM65gdFAIpPNMcLoq6HbY+03j2qA+r+psSEyIUWU3Hv/We8dR3+seisFnkWi0cfgp1NXhh7Aa3QLpIz0wjlGSqdxQIRMioFv7uduNcltFYnu0HLS4MQTTgg2qXkRoc/PQZ5PaZYXQiJlS2H/1EaLUD4oPVGPNTex/ED6/k32yHB+SB6Dwdj80C+uhfT60+lI5NXc8moC9WB7oR5LAfcZRIi1cxTimeIpdJ98kJQF0PjHQhAQ5clWTFamAOqVG8wzCu7RadNvQqM1Mu5rTRqsSgMwVJJnx6RWra+kuT3YIIsALStrOFb9MFInjnh+ZOQGyi8Y7979auPp/EF+x0KKmAaIByCjiQePNoeo4IvljmG6Th6MrmVjtiBgC7RyKnHCNcLKw7x5UeLzcZDhSGcE8NhqXgCfC8DvAZchyih6JxiQLAHp7plvSyAdNQkcJhIm3PLAiHLiqDOuGLpbPaHIGzJfN2k7zgfWBo2R1fX6FHEQSDebBhhMqNVbH8/atmoReisrOgCuVeLgc4ZLesQ5obNElBQbQFBQRpYTFADoNRmwgMF4zGesJb+Skf5bqYg6KOomQZcNLWbnNBpFtrrdwwJKf4tC8133rLcwPbmheDZHfjnJIOz96sr8FKcIR35n5yA++nosoJR2U77fRxwfKlSEtiUxgzh/rhVEk813AY57CS4w/5l4iBxyUQFpWP+ILPgWOHpMiSWTZ5M6rg3WuWIKqG2GBAFIAa81WmDiCRd6g2P/NAAaPEySnz2AffbGZ/PuMlKx+CYQDs/iV3US5w73T8PFVWLcMMWjBY12DM/L2GaGGdxNQXVLmMEhVKi5oyW3eHF1ZzjMlozYk6g7Jk2TEAP5h72HUe+/H4cP+sKY8IJJL2pQT7T/kmIA5UoLZraDBPXY8oFEnRTy01TbC0PYGV++2L0oceQypwwEquHXJSUNPuU+KeChw3qQUIwmbCTULskc+m1FtHQDJxC7Rw5l/Jf/cirjF7/nAHAr91yKyD6ECzge6PiL3fd0aMW+UF0fdMxqd5h5Xyauxv7+rKpEq8oQKlQyouG6u5XKaGg66ZRUgnokQtJKJm8G2/aDkg23ZBXSwV70MAONVIExLPZGWV/d1TW4OatRa4FjL7/F9+2L7GH+N/4NusigrwXcoEqYqCVSTLlxi6LBtvew+9YrLNxfo773YTuhCh1eSGemgpjQVEGN6mq8SvDpffNaNuQHRIMA7oAPuTO/b0v6RgHy6AEG3ZQ2uyF3F/f7B97cPwNLZyFNoOVovg1sUQuM9/uJ2HWiYJsKc6vAyJgo50PFK41+5MXKQYrNCATVspR+lMxyOI6coxpqbLaoRVF4deS3rVy7bTxVxUm7qriOr2jiExdDj3/htp0zKpaQEeTZrIWtJ6p3QBihnzvMMLRbWSHr5CpDNUDeiFJ9kXeSJ7lEo/2R3XBlxSBzv5SoSTKlFAH2MWNofhf4L5qwD+rGgp2FI7/SquPiw2+x9fi8ofZeKbbKjnXuNLejn6mlDlDb4L1VKIea5lxExFFlj2Fo1b4Huozuk1mTiQ9WEYKTNYoE8A+qXFekEXF0Ho300UnSta4RBoO1swiEekYYNJf689Z4eruKWefoYM5mc2OIpqYb1shI+Eb5b82V4h6iDGI+JFb3XooGueQA5Mk9wrjKwSD+k0KbF7aA5L/wejFYxcMvZ3DH1urC+xog3W/1/2oyySIrT6iPRqFMFRtbwhgVc8rAUVkvgQUC6e26yaroEXGhIS5/edUT17dmc2sTePHCnsxLlhfx7KHzu7VXq0zH02j6PVqk5OW172tQJ72Lg4BDXZeKr8mlDAgLIKoGw+RdarEVEYMUqcASNY0vZsJmnXeazGFbJuXSkjEsEf+B5lHhYopRgSFYVD7l2/rmh+sLB+GxSXG8tBobHAjncV5gjGn6o6l4dBe6/85SkRIBBKRQtmCi/kHgh+uzVQczrsAMjd5OVdq2E3r6+cbfA88Oyqp8Q0Qv0Cq9nQptRq4xmfUoy1zr88LmKmH0HFUWdV+HL0aby3yD6BHAanRufB2bz0puq+G56TtfHBiWIVdt/Ggs1oQrLFV5pVJIIheyapbxVMeL6cHg7fGHR7bYJDfaKdZHVuEWasDvkFRR7KY1g4RXDzDOg57exUYPVTnRjk6DvmG3L4Y+ory30leorypJmM4Wf6EUAB7wWOX34s1VcCtB6L6UuDzRSD9hLAWUFdBMUzZywBu3jEuHqVyVXBaov6qr2vfYRN8Xdk91XrcUnOlRqCi6tSA7HLqrAG8izlmvOsogVF8i2kaSTJDAnuo8rVTq8G4K/ZjxwAkYmtw/eYBtI7WjJYzq6921FWhIhV7TUmuOxmgezAAkpGPAWfFofuSTQMgCx/1m2GUaU+WSlbPwP+fLJiVeVrwLaUpzTJWeeekRBvK7JIc5T854+ZEQQP8pr2I1VVkqPHHKX/lDHSD1MCeoWIpoj1gnTqFYwFk6OR85WMSqvGK1uT6ppX7rxo6eZHb2gspPWQ+kIfNGPSnDGNdmC2wYJ8oyhVzNaNOCx1RUxpTteGoGnC50456n3aC7xs+ugeGJpLR5QaofOCf2qjAKzmZYnDnvF/1WWW0nKZMFo1Lf3MT+PeO8zirLRZMzOyu8/VPQ7WYzpzEUrLYHmUvPFBkmrIaHkIQxxR4xJ1oOahd5jLZ9kOoHThbs5z66lR7WUp1ocp8cpPculdPKkRdYgrMRRqaaIVCDp4Cw+JbjbjaEj8yIQEIcjKHN0Tp2muBYroVGXXji14U5Zt8FTzbkqHMp4byJRc0FcF2L+rjRslgumUaNi1PMZ7xVJi3c8IhbyTT2sS9X1NdtwuPjX3EcXeiJhrIZLW3yN6NhyYhVsOch4AuRG6yJMjZlHW46PULXjuPtgYnsjAK5wMzlIU7CIapAZuNGaCWbXgseFqngcRjFa6ZbHnHR4pMgVVyjheGcYeqZ7lv+yjVhKusjsYgGsfEg91ioNKbsFNQCJ7/Pw06iSqz92tvwwxUyr2fECoqDSLUmJgUV/TSeWw00hlsD5hD73UzkL3ACWJ0tsKT0QnhP8WgCmUGVbAUK9wvhN9smcoZwEbCGCkHQzor941LOpfkJdM32c3EuzozmR/lHP4v/MfcO/2lSbN+Vfe0xUMN9JcU0BO32/PCOJ5C2mYgsKKqawVF2UMFgPp8fn6GzMTOtyzIhWeXcJUMXVBLpFaJq6lEI9cYltaBcMtjtgQsO/26ZZOjLdPVjhLYDxvp8YYFofLgAkjmbQhsQcDa38qBcSli22uYA0iTlg+4Pws5FB2vKDFgK3r4Bv2YpwaBwQ5wIk3TxH5JhMw9SPqUAXGpjQ9GG6hC4eGTGR/3Woh4Xwkas4DiLhdHMEQEtUuZo5e4USnZj1k6dFsu8X2cRtbX2aK7Wo7BXpvCN5YdLFAIykmyBw0YiRus7lUx6lR/mafZ1ekJal9iThy7Q0H1SdCIJqthItA4aedoB45I2UJ4NpV2YGOECTc8Iz9CcYZ8g4H62rryPso2tKbEfAxkIZ27Lno2U9jcONseDH+vSz6Y26JbBsIwyYL8KVSg/OefVfOQJVqgWcTyd3su2ZG1quF1SpdWE+eNlMKaN9b9SVQJidb1OS7TSH82J9mf/GNn92SxUnLEkdFJRRPwwGdzRgBa+V4tw7rqmVWXWJdUnyj8vgxkgJ0Xa0Y/jMB72C2aF3LveEPOJpIPQn3bMgqwBGc3CslNoSDEdqgt8n3Y+4ACfZEnZDTrOBEB+8cadmvk8Ci6xW4ek/KrOMHIaQIWyNVMyx7m7RSbIYuokoTetUAtcUpWnTMrNFLntX6FAXlBvJhPls8gi5DgKtmMC5rgECl0X4tyjhC7U9FVkogMpBH1/pEcd+l334uTDgqAGzK13yVFn0gHaXbrGWU+0Shi2K/kx7sTmXEzNjg0usmC9Kvj0nSWuqf+E4HBunQ8wIF0OW/gE9glOykYo3rfStrcYRlcfSs5FRpUap9CcIiCikzNLd4k4LOR69veGmSOds+ZFNz4ShbftUfnw8wvM27bPzeV6H8zE+pIqO1Gz8mzFcqhw6DANr8VL6Lh67tI8lAPMlmNOnI5lOpCUYXpvI/FarqxN2bHMsQdgG6/JjL1Py+D7js6M5WdrrkZ2ovqIHEQvqUlpa6XLumFpayUgXScAr+V5jFa7L4vzEitaOTIO8QR5lKyzNrATn9AsmkC0bRKP1j5YB7a9SP66YtWJL4dbDrdsL+PF57kAZooIyheTMhwOcMBayIGj+bsaNOW87s0DZlzqrslkFa2c7fPaAMtV3ncWpztjTzi97c8Odfa12wtx3UyzMicoZiUxt7DF5tD7bxkfLoyKfdCapQNk4EzvbN0FVO0JGePRaN5/dODIBVJmGhN8qHDlDBRfG2mXefC4eahBFojRskKPUpXa1ArYqHIdaHN5QO4KQ4BDzQwGVk0KmDKAMAYQsTDclQTjfyTIAHhIDWog8s5SUVLHHY0Wo4AzqwTpgyHxABhQP1QAvoNG2+BFjhDhAMxGoXRg9/1WpwEgjvJfjMPYC9gyA9cXzGD1XGtPA0AnONL9jhWI5VlnHYsGdTN2Feq5HXXWZYhQsCslwhLAVDhVU5bdUMXjFUnNjeOpGB530QdqbdDaj6UlPExmeBQkc40IPwlwkg5SKz4HH4qyc8b2nF0qyXuSn5SKVqPxWFFJfkKEqkurmKBsTI2woYiISrv3SGZL4+MU8mZvI6LjzzfBvtjuYXQ67SdRSyU8RnrHS01sKyR2fITg1knC+II82444iVk9UeGDxiTJz1XAfCh8bG0Hw9vcmMJi2MPVs1jq6LqdLPocnn06PYd19D65mB2a7LhTxN6V6eMZwKFoyQm0UY3wXijyjoifO/BlIKxK6GiFqjpVeEfAKAeR/WwkoaZH4ZzeO0SUMEtcxM5gswrFAOIIh9CVDlRaAoaHqWTZLt7g9j5pa6v2w8MfYMUMIAk3v4jSATueDk9U3MLdUH0/qjh1ywHEOLOUohk+FuS9js5qHTsIyRcsODsq7X8kovdbHWzgbBOftCoVdMkxnZN1uied4oK7Brc60QzHQuMlIeq2eazCgCDmSTcx8NGdVO+0+7T1jxQbMkWp5CNjT2PqgaQ0JfQzgeG24P7p/asg0Lp8anDZYjPJ88ddRxe7ExgNs7YI3B34Fhat+fdW2KHjB7SaW81dKXZAhRs3rOaCAlc2jJvuKnTBETKpGW67xwbbnLt09ipyNfzAYlsJ6yGQNnnHgHpvtfx2J7rAaqi/2uMc5XRptsyNFJOhgQb5VebV/SD7io2MejwNLCJRQGBgmc1vNHVAdcBtL6Du13XggvEgZ34I9veqmrgVYWg09zw2hlHuIKbSeGxIZ7Fwz6qjmsx2BiwVJ9rJiopl7cfnE6iFIUBY0dKR6WVaTxUB8QOaLbIu2GINk27++FwOtgVap0bMzCVI8KJK7eTkTBmwL0Jfeby1y1vrpfKF2UeqI0S7ocPrHO4m3kWgtu/YFGYnGIdoOjicp52CNi7P7EzZMjMmG3bjynaGg7xz4MrxKZlQAm5GJRxUlHqE9LFsNQkCByxqxGEG+j2y+aHBnyAI8qQDw4uBJrm4aCWQ33C5no5vsfgzdiYCCsoR7gLwHScxgLAmPxOTJlDSQail9rcC+0n14FIdo0qrSmoyPNBOox7Wv+zIS7qL6DNn9dz5e7Hjn3bjchqBH/sKnNy7dg/WKy40/rrTKywLwjbftwovOqUgClosgqFpHeCAOQlillefGI+/Sf6XUi2CH+ynjHFUf+8ik9q0O93ebMcdkQ9HsU7NEOQ+9xFhvzPRM9E90fvwHPhH2IiTk2BvOvH2ys/qW9z6fwTy06bwMJitnR8HXp3V4pJ2GcbDzmRWuT6J/sgHV98j4v8ATmQ2sLrhCR15j+YCfLhaJIU7YkyRrJn6ZcGF8aZ3oCXTG+IeJiIzCyjFiHOZrDkVLOoc/BiLdUUpskucvq5Fzmlv6qkS6I3HhL6vryG6XViEfsyvqsxA+Mq208JOGGbbk09+0OkFR/YvAeCpChuIC95zYVW+ExMRJLF2Ix0U2W6A2Lun5+Rnf/PMxl82gO8r/y2EyvTXpHLefzU/7wYbCuogUYtisx9L7PoDVapgg/emvB7EOXwXrI2U67GzXF/I27qKEkCF7mCDMsKGap9Rwwxh12yrR1XGlexnIlsHSPYXyOp7jokuht6TNDnijSUVgZykbs4IluMUUnWd7vQlkf3yBCqgTP30Q8cEVQ58PuubMGPjIjaDW23AR4xFs0WiAGByugzWDXx+VTxRIdm5f1B2XEmPUPD0lll6BWeN/4NGWRPZouiP1KBC+oW+a7reSgAqRL9MWWV436LOQh67IXPTTYsSHq1uljwXMkFIB1fUaX5ym0Kc1YUfOtUaCUr6gbvIBcqduJicG89qt1Lm1pzdC5Vl7TAWUAlSOdxtuIAQf5gD+BMm6MES83MeAB8Bl8z6yo1U4vd84IxJaZTXqWTv+aYN9lrBxjyklm0PwML/ulXg7Zv0WWvVwJN9WzqxagM6Kk12OTA+OYJIrXOHYtxOklzBtrqq1AoH4qvokdysJ60/+v/zAMmJGLqWuFn3wgB2G9V/Uh/m32M3XT9Qf7vwx8nZiyJ+WNqcsi8VbsotHVSENJC1DaY4XgL2U8ddj+8H2PGq9v319qaup+9XmUHbblm0paZJ82T+AsJhY4fwjpUtmTmUouTJFm/kl/il2ht9wIFCI7z6EHNX3Gia5/BQK0yRimbJujfZeUDzQusaqDMggRTo5DKIjsZDh3HqK8K5eHwCMK2ee1FdxNnbZxLjbT3/FVj5suDMPhoLGSg+PaeRqmAn6ifao66xcxTxUQG9nCAvmuFTxcL+2dNBwJ6yaBUZPMy0tePe9scNtOIRrj6RquPqJ7W5v+1U76/yQkEF7teG4cDGOj5sWbOdq4OHWlfX2kr+q8dq6T9GquFSFbZbzBBvmArbfp+gn5l6T7Ai/9bOAITxxhn8b1jTQPgdFtvLbKcIhLuIUvkt7pHNFZNLlmrI1j//4iP0TYSomqi/PZ4EIXlvLa99PTKWZ+FkhPFup80IFmpoEybwX0AEfTYho5gmbmIt40QOkxA8fJD+tVl13N4O98sgaH3eZInMJMmI5U+UJ8b0/z5Zo5gtnGpHdl9SQK1xKg5CpBISxYgbnC+02vb4D2VRICQ+rV2l56BFRWQl2jNqYZG/xAH2RYPQmp3F6sM2OO1fnwISvKa1DEhrVfH82JyhEFfAkjLuHVWFjmWba6O7EewTCA35G1Lk+QEsTUmk7hO/9IsYhVSmV9Ri+JwmhAuNVWqaq0YRe+4RoXN9iEuHs0jCWpmm6IM4EO/Mo3So5iM6uGxTDds5WLEEfa76zFyEcr6Iqx4mV9VVO+h568MkU9CXoOLE8YnhF30GY0sdKCoczpvQxCsKTgUQ6qPx8EgWNJIZbFxXizVNcVTTKbqovZFfW0FvdLmniEVM4/5/QrpYXAFbVCEEu0J0pfCGk1vK4jHal8pCM82+shClbWhRbP4ziOiGl66/I4jV3uJJEeu6IK/Df9ygqOtovnmMaSaICNfWeKMgEiKtYKJZ2WZZQZgQVYEdObRP9sEmz1UVBt48Wqv6AJYHqDIvJYk8v1OEXhvJlKo2i+ZfT71l+S4TiDJLNhydJURrLQQlwHNZMKakMwxVi24V61JyvW0p+037zm2yCCPGqJU8NK6NFAKy+enGJpLDC4DHCWAMEEBiApYIRmtgbc7cK8t0LZP10wjlQRqlZrvj+NMJMSUHMwu41YQUAVUX+H4KGj9ZLutUKP9yWk5PIlkc8nRQrOt3jrX5zi6KDcVEv32++o6D0QQwCEsn68NEum5DvwR8kvgHXTlcZdDCkBCwWRPZA5PdXnDG1Y6dT98lu+O+Z4NejVSMWhI54GOCZT7vw3EBjKXl8Q2p7w6g7SX8ZnDMrp8IzRDcQGNxGkzP14FRvxVJnDamGL0a1sEIFsdieRLPQU++q7RwICGpdvYG/fEDWDmeCbCSJGjmmtis6Ma409c+kJGwiCKOLsL12hOX6b3EaU9Z6C32lk8GdFj2YjQuJVKrk3Uam+HDBVous5xZJYhciFGWG/R10+oxfEHerfWDLGFXg2TfPQl9DhYbzpvnyjl4nWxiBMpipIyJackA5h8VPqkiuEJZf0woD/qeFnJ7k6DGDJAhcNwIsy2SSiDOsrHJya8HOZJIYVFNpY15i4yiNMxvqLnFE1ppEEJPAoFfhPnTpmS15GYqqf4Yq47WHhRB3Yi+wfpBTCexINpsDWc9Vwj4E4VN1y3UVz7s9cvrWfSVepMo+hgj/UDHVLTw1qPcE+OUU+1IvUWMNl5bZUE2xGtyLl8ZWxE9hQC8ssihqH0uwUFC7/vTzqBkbfjx6fYrpdfn14cfj3SnnpubC3bNQXsJeot4YUO9urxJdrfQ/CrMaA8Zd+e97v8W6y/DRQlY4FOh3OHumblV29Hm+IZ7pZV7GeXh6fO10N0kIh9e95w/E/9kYKQKRHlCPNvqaBXFTJ3c4TcVyh2EjwTHxmABGNDfkEjrU9lpSUHUYiJP2Nt6fNKvG3X7ppsODhgcQfRW1TmQigS0EgYb+iIG6z/NPL4COclYWIDVRXDFEWpgaYECwggrpC2KgnAdaslISl5KLZa+vdp73X+OV7OFqM+pjueu9XG7fIyh3/XSPidzk1L3r44R6NK7wcJ+XJdmYfr1kvLLQSdNC8XvK79vgAU40yCLy1IFyY9v4qgETv0qlP61A6vIs5yY1ahNFp2wfDFwAlLxntFWt6qCD+RRnNO/fGHnSN32HfVSr4o1Z1dTID4oz+7r5XpgOUYB2T4oWHFUxfZYxc11uRCORyixMI7vKR/UyTM0AIglNvYAzQKb+HQW76Z2yYPnMd4kCowCuxjpQHcfpnmL52IAx95ytVEv5//LlV9OjYMtvXmFOOCmBFisc9xRdAulCODb8T0/z3JgqnnqtHwAaU/7bD0eKoBuQzei1OyXfB81j+4wOi/egyoHoRunYwD6A3jnVaFBOfo0Ds3yph7JwHVP9/bwku0xxwqsXZgRWNogv6r5vKOdS916kmgc6LDQ+mBYuTKuQxAwyHtQz6SAGTtwIk2Qc/tz+qBUxI9Jr/taZPYR4yxNmXGy6YXU2XLh5+68Uw7o0rhKjxfD4V1ROLxL2lC+MbRTCXZ1dEoLiSzllw+ghs2HBSVthh8hNXeCc+3ZEnvuTrtPf5ufwdR+AXnzq3UeOyy03jhcHKsmzWGiP2rONY0VgUNaVEvG/N0bhIvv1bgPiKVQO3Ls0usuYCOtB1WUSsAchHQQTk2I7UoYsuGploBQeKIWmhXG1WJFMc24fONjOn85KxjFlLh80dgtBhv0QiK56iDnJyCdnlcSYGb6UWJImqbQWuGO1W2Z4XZSAkLRtd83wZvfpKYBGUJ3AGJ7spEbwPO2sFnjMqlUhHp9FZMPic7lgJ72/sWbOATLXUb8wVWYJw4XZV5M1DbskjvUdu+qIluO/qdsk+TrbF16zc69gWWf6/hABsERZndhgw6eACxIGTycQS7a9Ew5jOAHGHzQYcuWj+8u9/cjMfqhf46hisR2xqoeLO1CZV1VY+LDSaLojJc5yXwVbvMYMcA8CIscca+CYTmvvXyFvrTX6u7iLjD5VUClfgq8Al8ubHV3ceePWyhiIW2UquAPImGK22ZmHbe7h/iWMHo46hLC2JrXh9kDCH5BRBwS74y8tycMd+zvCVMci16R3kKfF96zzx+9vAIcJiVCPKBCDr7Uc3eDqwHkxgagAz33NAC6hgyCvmjuwJAV8ztii3O5AYZfX/JZoisZ/qF4td8ub+R2zI0kbdIS1GvejepoScGs7V5P1RD1ZJU0JERoi/nrweld1YfaAP8IF/Up3y/v5eGbt9Se/PHuTYOPnthgU5xd46ejr1PYWrLO4VSelbBjVeQxB5vyh9zn8FKO5Gi+0OhDyeSbC3fdsFGPo+ywqW3Ww4kDv3VCom3Y18plV11sZsu0dPuGswyoDQF4nKFm0Cy53tv2+ndXcb/JZ9CINPy04x+uyeGuB+2lVP8OJFsg8h4FRKvYHYHl0hpYD0VFegsd3nYNL7Ulzrc5m8kPrkhVTUE5C/8yQXTuZWBICE6Fbp8g6r4iR0yuB6K9zr5vrwReYOoCaVLWTp86KG4aWOFEdo7hO93sCIfJla7vrIC8wBQRrd5mwFag47us79GwAgrPfTwdmMNFeUfQeH5So1Vgk0M5DAsGoSk0FLhsJ/XF0lcX7447xSN5+Pn00s4PBD/Sl2pbFznqL0Y166wybWbKy1+s7zs1I6+oRvTf0tBxpWZzkn4cGLNezhTnGLJnJ2iogZ1qHA7e3uTf2sMlWwfHh784XJRXsu/jMfEx7tx7ViCeU3GzrjL0AFazslaqRo/Qatkb8IHiPfHu47Ad3wiqvI494lke8TAH0lWkfC9ytdV6PfpnVJJ6ktD9JLsH845XQGX24sUmXyj6gSFc9kwikQ6V+vhfr949YvKgdEKCZZTWAzIjLGZNToY3lnTZJWzmV32SYlP82haTbsU5xSZF1nac+RCmvTwP3qDb6hGOOQrFaQ7cBmFm7FDnGFl2ACmLX0j6QSfWD47WsG0KQubHAt9JvrsJKDag+gPRsQpFYq4QucRAA6mP95Sf9RfTqXA7VrSeBg/cfzEfd/weIl45yeqmVjNVUAY+ENiUyhpbEppm9YbVF6ljKQkSbKOUfdxPCqR0vwG5amMMN9XscvyKb3LRSxE8VN+kjmH62/s/GplOfxCVmpRhFDemyqTuJtkvmhDZmr2QjIV8W8sX/Ci1Jelsr6j9RX6JEihAxROfuG9zm7jgY0YkajA8ANj48JkdZ4QQ/EV//JcdmlsgWCF0fHFU1eHuGSGTw8fxzubYySuRo637fJmpId6imVh4Dul0Xxkw+XRWo5FNLzpbw7TipeuS/iV/iVqzcUJrKcVNHK10tufaJ9do5m5+RvRWfUR0fok5Hha50OBURRedWObHT6qw1BjqnJQIlYu5MhvFQeAY23jMIx4HSzzmgOOgxjWr3ilj8ODrS9D7g6HxgnvJ2hGBteRTbH/7sVYpKnx1EcA+DmwJfe8zzyvlPI8fOLhMvM7fykrCAXXCATmd5cr5zymxK9t3zm0T2LopDGkPI71130tCDoAe018dbCUzpV8m290WI67TwnrfpaBGFUwwFAkyT7H3xG7WEQobVs/lMsbMzz3aoukkFOgemQIVKTqGGOba7EF6fjEHwQoTOU6PvYNc4vxw6lLcdweccmHD/EKxIiPKj8J06UwybFTQ1ltvqx2CqMj06uxuW82a8ViKUfJB31csKMOCq2SjDJ/Z5EHsLs+2bN+k5+pMvn7FedIwOAYoJzXV+/7U/NSwlchc1RiNREtHNOOF3D8uyk+wVKTpvM36vOrq0PUlv/SRmbcy5KIY3/drDL5JUJWvn33LVXbL40mFjIwivr2FaKHDlZFY1apOb+GIMfjmt7tZCoiOCjufSx9uZU/zIbDfe/LO6lLu9d0judEFDsooN2jb0437G6WHd0tCy1hwvnMStPzeWtaHxSCIvgjT40S3/BML47tivCg3anAOFE5WakeID9iCgrGBBlTksuMSm6LTp4icidpU4ZBpnhqYrVzIsLUzua0lBUzzExgDImsy0qKF2oiUuw6MbcOwWnKb+tZh/uKWjqga6EJv59C1DcO04Dauf2MK+lscYbwn1FTqyqDbMAiUqtBChYe7hT2iLwmt3s5hAKwk5OWOy+hvQV1F9/SW8Kejk9+MxQTorcuH3gXI1lmFZJx8Ac4X0u6F6QMhXqnEQekVviAWK3wBaykqAEEdw1SuugAdYuCEHJRqYxbVZPNUE9g8IRekR8z0mlySHqmTSOOwt21ex8D38HBgvH5l84zv2aLnhNY7st55Ch10borHIJZOuuYg1gTnQCPUsUlMQq004Qu2owdInYCvrtnh2GvUJ6zZeDJV9igdXCVh3Bp5A9QbaL1Gnutdgh0VY7S4G1B7EjNyycpOdGqGmbbNPeGVsmxcS8kq1q6BxWukRwBTFiWg+hjgyjX+mB4BTOmTHBummeG6JBWKaMQJHP9xdJQtzLPSMIK2eoFRsxKAH4N+eyT5skyuIMt8AQdbXOcgrA9xugiqLyi8VMlH3ItsZa0rArKdLHi7lEO0g5cq6x7cdiIx+ComcliJA3E4iSzreVhxFtloGDYchPqFVJ3UbXlH8vV3zIJujcFiX7Otw5RWJMMTh9f4+CVbuVWHxIye1lqoqR6muCK0bglwMPhJW03aB6XRNC9Caj961DJt2syzZbIj+RP9+yTX2jsneeA1B7r/UFFd0Nq4qMOiP2QF+t/b+VJWyoZRZV0d8OfiCI/bEMgcgIZAx7G81nq3kt/V53NoO8BhdwVEqLbL92pyforF3ahaX5bh3pv2dFgf25ypJ0dWQKMsM0sfCLq/U13ER21xsdBcLzhtPaBs9P+QNJjfscNTJ8gDo2qQwzbUbLhmwza+cjXQCUlrGIsVII60OtOmbsq1YXrxBFJrotDiJbDJMKBivZFTXHHN+YeL2HSzffjnMccpHJT4whVizD9hIbwagSPzxT4Nyn/IHUMSUQ/sCoo0ieaMNcOH0ulIm5f7eBTgFoG5C3PMgIw7hhy5dkL1n7uBgyRkcW2sBBfcx2z4UeJE/Za+zhz3EiRIrLkID+4hTSHSQYFuHVyDYg3HOjCNjNOI4wzhPdijRkGtFNkoPWcLgqUANyM2OA2Pbjt5co05nA0ATReWW1IC085Dj6+L7i9xzxeUP1yVbhKQhBAn6bOFuHmOXe8cKev+jDY9Bo7byXfHiKwdhC1QXoQ6LqiFjV87Ic/3CljDWoEteGuzPC/6AmbIbQ7KK7ynejfyTokUJjeVKNAL6Uy14lXQKJop7tYdySAu7wML0EdWA7fzGP5mic5TNFTjmrsAGTaOVadL74fdFB1TCUh2y/To5BTJQzuWTvTdFKhJtmCZVhBlpUOjQGs1fZCw4IWBGhmlvKWsUL7yD5wkp9h/clGdYN592+M97VoiZ+H1YOE62Vy7ZEhFM4BJrZjDqjgje29swXPd2VDlejd3CUeCpmNdi8wQNVNcFxjD64ofaTzZVPRh82yyBi53cS+4NLJq7OGpU4ZUixVBzIzAj7VsS+b5cZOn98ftPC71c+Kx9pUqzp/3OMaain4tFxcv+/33qM19LPkMfv/OTBDDO/uDAH9ARZpeJKwReUBxwPYXx3ofbR5NGkAFt976AKs9Wbiy9uRSMnjyEbK2Zynapfke4GVV5RcFsh0Odg8qLv2xXV385xV9Qefhu8DcTnEXmimI1o4ZPvvydergaWdWcW1tzpUeRMlCv01dCEmDiYaxj1tQvYKJCok6IdBctLa5XL10+A+gQr5/OO2KTgvHJ+F3w/JL9Qu0a1njElxJVXgzK1orXSes0rhakFHP8oK2C261nDsTiALuCLo4avykuBkMx4QzpGlgtIjzCFMXhWxI1PBhT/KcaT5LwFz9YqTK9tbnuB2U1FaY/nJ1dg0UThFmfJLUkG3SyxVoUAjrL5RmA4zElppDiDV9Q2Co0OSM6K23ffGYIfhaEGrZa+iTY9KN/xQYGvUq1jKdX7eoblJtBTP2KKFp0o6d2cNJd5fzsvcQdjQV9/GLZ4zCdwuPyaoU32LBWTQhTRZ8+iuGoAzKhVM1tw2MoD5zf4x5ql0E3J6aULhC8NQ/GZooz4R6fA5PpcfsrxByGKc2nVMXUwHUmAvhs0kr7kGU6QT2lRP2r8JNI/pAMJsDw81XNJqQOZRI0V4H5Fjcc4zLTVZtytMfF6bChVg3kILIyJakQr06XrdwYqyfpFBrvTHrsAIDh8ELs6mZTvNNFfxRAvnz+HDqRucTB6YyylRLVYgFDjOt0NMIllIi5UyEEIWP5xW/j7RiH+qZjFNEWvoCiyA2w9lIseiMzisyObBH2ppURL9auW0hmmYFgzinZdiGeNjT4BkmMkywLE0tv0Qu96KQPVqZU7Giir3K8iaVejG/CpZOkGIYNs8hoy4aRT9+c0TDQvmQLzPjMTcy9PtAywWPRCX9lcML3J5uBll6JzvXzZpW+ARXnmFvMg5JLVBqFx+ksEOCS3rEKaWdGUzYc7lzYnqpzb4wD+bsLZPCiMEi9ey1VgfZ7twhZt/aje2NNiRSiWyjy4QBFWktrYr85JFwdPyY4oEWliUDDEknpVn7iAPOAs7+sWUlW3Eu5R+5CirwejT6kiO3cXCGn3agkTHzc1SP25yEp0ZPCJbuDLcFaHE1kzgVLeFDK0AmaSlEsLBHGHEYLOnqYrGd6/B2A5jvkz9GvcmcMOlY5q+bT6YcNj0OBwKrQfB1fHzb/j8RseMumdWe/dsdihuynyzeLJBSAPwMj73b6g3W+uRP6IeXUGAThGvUKWPV9dek/Stzg9jBpoOUu3NR61T4VU09HOCVyPQKwhatlIjGibdAG64yeLdAvNv7KkGzlugUFEelerd5VkX6LzKHEb7WKbykFMLz4v9LAkchdMQkVrQgChs6I4QAJqa3mZGC7CgazReEMF8dKlT601GcMB3ElEKyjJ40Xlf2F46IzW4qiBjTRbPjKIbCaqk9kAxasHslTKnhRVsbwFcgbk0iINOhoVwjlkbEUV6R0DLimAkOEitBcAtMEopViSEXGldzHuf7K4zSYLM3TGJVuIBILtiiOOH9sIZPVx4DWxqqwm3tZ9lOgWJ43fVWnpN//s4mn+wWbD9vHJiQebYDCpSY4Wyaz7js+GRCkE9yWg0EaxxBym+lo1WPRDHv1b943jn0JCMcNeZMdQdtKkEpK8NiZ7yqRKcLlvNbzlCTD++/2bhbwainlm9jHBYT/7oARrT4oHxckgA9hTYKTCYX3L9Vadg1t8LfV6N19vsKDodSgZ8+if579G12SwnMij0CqIjtZQcMKbUSipj7aPYv47+zPf+pNtErza0vs8Z/LQA0gbz7Y0VuJXdrWqrR/7JOb/GW1EfH8vC9bKpZ1Z+MDv9pZ/BniKZviEWxFi7oRvXj6mVHAHmCk6wy9mXasMKKxSVNo6kF87c5VKuBHpby6oBC7iP74aEPjte4fJaqbe2BFhhj7Fs0vL9/FrVX3t0NuHW4fyz73UiiMeWnmqsfy3S+weHtGSX9Ahwx3hPo3obYHtNujr4iMNtOCTRkYXHOvDaDjnPgBgoKEIfnmU6laDHJA91VF1/LHmRQFoIF+z+xu+BwfRjz0eCzHJ2Yq2a+9MlQE9/GWlvH2Pr21+6inbtCMySmwmL+T3Z0GjX9ojoBque9MaEvlUJ7zI0r9PLJMiW5EkuqOLlJGBthHY3YbSL/ZE4T1GhnzLhwA37aPonY4Ek9g7cc8nxTIId+eYUArHKwbZs40512ve4v+btfh6xrqj9tmPTUCLXap/EVVv3O30Z/xHW7dQOsSr72rFVO3EvHqXNtf+M/6TjXqXDFn7ziXreZmtb1LhTH3EM0pt/5W+KFC/zW1OGwb0z28Ik6vONc3UoVWPCBUs+n0s0ZHvS2+x2MN3/I7ffjHYbyx9Ll6IseAir+tpPDm+zWZ8JvUXPmTk1egQLl58RW/pB00e5dMEVH4RhYvp0tKbUDrPcSGqsKk39aW/hEpfytKQVGmGkP9tfqhs/uJ39ZFyhmkED161KVXhT5qbEh3cbV8QTcYl+CT1NcZwhq68Oz3fDF0Yc7kmKcwlq9eSXnWha4v12YXy1jzU6QqZzZbTESuFWYrZCww2Klx2+r34yjowqskqTv8K2DyNYtNTaszvP1ebTgx2h+RSaXvz21xDKv+1OTptqS6OfoezVb12oiDc3FTIACpfjTC9eqKX7kyFYm8eqi1WFl+44ZmQPTU2/zdnYQRQcY1Nn7siFNlUmM3qVlbnRDnbB334QvZdem8y5rIPWoav/L3C8ckxHBafJYBR7vLNJvzov+rhyMV0e81h/8jWe+kQe+kT6wc/DxmQm9lkSZ5ZfLN+9eBDacOtCHktpvsAHvMdXxc93Vl/WjRtRfZeN5hAOW39dOkjdJ4Rt86u8hT/UsScuHa4/jsxJiqODB6ef+mk9qB5ZwtDp+ODBtKhoLYB+KvA2UaMMcpRVzeQeyR8Zcwm8vK88VD7m+4xhpzcf3iFw6NFntNP0KaT+I1PUsHDTomU14ep7aSTz4JAjtvvPjWYgR3Qw6Hrm4knXGl0W8STZn4fOdP3Aap4HgdqLt9l2+8Mt+U52Yy9NIhIoWpWk02ySyq61XXWtwqOqo9rXqavKbrnV/OnUs9tAwpM8+DfHf29GWSdWOzwk+VV1n7Z+q+Q/mzTcy4WYBG9qJ6ex+czepnguyWvy1fhCr1bQpXH2fA29+Dwqc+CBv7Ee+Z/9a323nszyzPtHp38h0hMHB2ETgew0Pxg/5Mp74xWD+HYQY+3uF4LbLPyo4/b0DZ6ez+Iexu6NNzQQPn34ArI9cJGmTulBOSVub8gqfveI1v39ztNk4C2L0UdwUvh5/hX18T5aL3tdHTa2k88+9z+rk7UvMLnzw/2oXmImFbRRXU76hgmnzm1j+FIZvb5tBn56QPtmhnPko/Qi/GrMw6q6nVXza8+eXGuz95pwpwyW/5sf5nMO/GsOH7FmvGM7MzWTvcpRXAu0fkPcLewAk8e9LEgCghee6Q7Polmt2t6Aux8sa5WJfYq+tcYEE8nx3n1B2FQP6Rcr5VSq79dEHSMfMyvea3S/AyGdo5/xR8XrveL3/D17Xjqv79TaGK221mAGma0wDK93imAuMgeBgDdIXaGAFvCIw99BEgpDHdP7+P0gKDAdsg5UPY4hCls1/6qCXeN6uirbMQPlRAE61plrjHqhfMDgCnw7sMYEvR8XfyXCfq/8vnTEDNrXYtIvgwdmhE1cbFW2EhYGRDZsRJle+HhWWEekUsbUWLZhQA+4NeQU22MSSTfzOgzzJ2nVMXJA/bPm6AsErgjIcz4jCcPNxCahhBkpk1sGLhrciwioGZxEMGUAiZSatgvPLBq6WVAoYKwPsVBkGchByOgq2I2FMZOrJdiCoECxhUwbQAhKccglD6fRIGLOzGaB+gjFhA8ONSQXksSDLFYAANyZlIY091uEn0pYYwGZgsiOfcySzV8KX6sL4C9tWgDjilJpqfxDjHywn4nHClITewSfE+IKFEY8rvGel9ywviLHHIiM8Mc4ItS6PiPEvehCeFL9D6ZD4HhbfQVb+zqEQ4xVqI56OOGeljwgMiwn1kciK3wiph0c2sMYx9jUhD7hkpcLLDBYLqoqQF/yFUGnyhRjvUAkhb/hMQnt1HjF+xD4k8i3+QKgC/yPGBfYB0Qt+QajasGejYB832Cuhr1FbfICBXsBnxPgN+1HQj5xd6dUHB+MFvRJe44hlSLzWI5Yr4rUbsQzoXo0QIff718SfM/r0MqI/vfzIcfedy9/YfNyxuT3M1b09f319wq9RjsnXOLR88XKDg9IxlwkHpoe0Gflzw+9eveBPpVXadPgDLb36jd+ZM68esavoLm1qnA785tUGp0RBrhJOSgGKJ4wr/qYuw7iwuV7nrIvbLizv0yaLIEWXaygojhQOET1OswIiSqYZRSHH1WETcExzWKDIQm0yUETCdYwjZUeD3UKhHj9MO7papC0UnQYUwLEdGxhB28nQmUBGjQ6k3Zp7LaCoR9QnCqSa35n3hOuelmbU9N3eoY7mYp1QYT3sfSPIKRghZ5TUTcjpTq/g6LEtjgLlZr1AHIcdO2zCM+wWOojVTh2CoB7RPJFHjQ5hC1V1U6xrFzmQQK/g3sImiQ5Bi+LH1E4oimAHRUOcxqSEgEWCEoGZIkiFHRzFOoENZMnHdN5CoZ5WYJAW9GNRHMlEWCQoKsGJCLUDVmcdVrAUitrQXDonrJoG6eOdx+OYwiaQgc1BFHIFhyIG1PfJkNOKzBT+pFg1aqHGEiKMUPTnE+DZcm7giyMh5WY7QoURDe1BsskMLiSTNxlIEtd2xKpTol/YRXMEWeh/kmYJ7SCh8AXs/arogMYMiuzI8abd7xw5BAERnuQKnhSM0CRozBD84mhwe18ACtTNDVDKCG/biOHMRUbgRXtiol+LJKjv4CRvkbQVCdcxcExHgfoLRKj9kRV1S4ddGY5wfBakkH0bbhtBT7PsKCYWVxBys6aSRy6sQSGLfF7OkzrnIIeVYoFqx7sUJX2xWcJhcjHNg3S4Kh5PpR9gOiIvDmzckbqjC+Ime105u8Ol6kNDK4Hsz+ZMJt5xwgJlqoW6EztiHNezE9Z2Q+j9W/aO3swQ/yTuv3CgM+p3/za9Tx+n2OuSi/IM/CTdLMchRSNb3RfskhJnLRNIX+8Z7ydCy/LijwHYz7YUEC18vCKGQ0TKE6r6Z0C50PcNUryIHQ868NAxTUJhu+jVni8HG3kG9lDlWVkAx9eOnQN3ry87GqDkkfpl3DZahCMKVg1XmKCQYrE4rEcjPEjkNrVIz1ZHN093b5TijdyGZ5y3Fbjus8oheJ0UhnyWQyjg7Q+4dAVFy50hgdsJGX8tE1noIIAiUvxyuk0aXw9HfdqnMQfJBvJLrsoH7Y6jx3eLzIoSWEj/WKCp7tyBDxKKdshiLNKKk1HQB7B+3gOKpsY/4EQQOQhKwtPb2VDSJti9v4qwQM4oRsQcCpmFTYi10GytkPzLfa17JLBqHJiJk0GqxXWf3mlBP3ihrrqhm5L8SL9A+3CSOYieeBFHR2J1PFqRg+CDnzIKguARgoNaEw82PlFUf53F4zQhcSHAj04N7D8KQUJ3BWsNefA9FHAkMEOPDty7GVCUPxYzpw5QxN8U82sfC2CBQiQQlo/QRFU9qEolYLUJ2gCfUdDO9V8AfAOcpdmkEe3O45hUmLQWcG+TRorKedCnsaGuklmkAGTpwGBBS5qMKXntgAYKdSQTlTMvk7azC7SFahCyR0fLUW1ENgEzZ/Q+wcwZnRXnnNZKZHPgyp/Yc1Y7pOxnwhu+xnt4+t1IKzpbZEeNOE5jQZ+T6c0UXuwpUg7aGBHJsrjZMUo2F6TTAOx5HG1Vi5QYDmaW3odIP3pynCadZ4fIX22noEcHXRIAP2cwZ0V99RrFfZhcHAXKBWAHFAD4UQavR9JS/0WSwhw6YG0CUCUGBVoocAFEzAF7qAiGnQBGtjSnfM5oE/6AiDXT+hRgRQksL9ScDmwesL/2oEgWU97cH/1nLw6RqiymSfVsWdH6SvNTynHRBkrtBtykW9U8MI90b0aNVV+RaX+yCFYHcYbFoh3R9ED0Gvd7243aq5o7n1+djKoKrs00kSCRkxBBb6wL+0gnF/GeZtFa+OFfR4nBysKCMjAngYHjM3Mk8KGSGREo6HwYhJppUBBFmzfigmded4Us8XDUMG4CFOVsEEd3EOzI5DhBId2hmif9h3Q1BhR1rPq6KQHP9PZj2hGu04DmAewcNEbqCbDiUiIDt6OdOd4ImuVhE6JPCQFxLcARv9EHuLBBpaWJ3hkyFJjrw4TR1VKNZ3t3xOlHDQN+OHtiuFRTt2kqIb0yEuWC6TZ0oIMEspETfA4Soilww3FGLBvbQQgEIZ72xaizVeTRcBUKYcCX8C7E1nFQrkSmIfC7klThPJ4vKcZnUyhE6sNRY7uRuef5Lml/Oe55ZSTS0YIZC5qZi5/u8euNeOvp3oYuSN192sVe+4thereYGRIzdmB14C3UxOmI4SghzglaDVwmXSyomWaKprg9gtDqci+x3t7uZtCAExzredfpNhrEDw15tNvnMA2GwUBjew+L1V1YIUPKia8qG+MU6aLQH8xaB4u4t4vTQouQ9gZ+QGZ/cQhYm/gajsKAvd9/Kn0BLcVz4h/nRO198sKPVxYawBQufhoxaU4v0t8dScBy7EAndjOCdZ8Wh35orOLodt82A+L122YAHoBpMQ0uXAGdhm6JZZLsc0RU1DhAHLxDFRN2wfRMUiLe8W4/4bRYl8kyOdnPhAWKQt3t7QTNU6TjBQRGPdHRkzjWggRJB7l2cB5WEGnz2hBxhIU+8aDC+ELecuwggVqp7uyQz55xBwn4v5cOf7kaXi6mdJFmptL00CJ/7WB1yDi6YYiuV6BNcxxR1VsbxmVEe217gUxUJlSeY6IyWc08G7wkkVYDjP3v4hJMcaBmJs5GHnBnCmxk9JEJsqeCT06GGKtuLcYAG1BbN3Yesp2qSgYYIz+hRm3j4aTvsDKxAQSH4rELQLaYZSfEfvbyjE4VFt7PGRQ4pMaq13BVX7vnTzDp0zwEBakAQTpCKLZK2UV+D2a93oaDmZo97DIwCUeTLqOhBp+imkOqCVuGk/ehf9Rq55ucKHBK6lEgdpbuMDJcVbCpoXBUUQYwmvewRU+iquxu0Vou1wruk+eizAagtKCtdmw4cTQ99b2+849bc1T13/XrmIrPFxTwQZuc+FQ5uns4b999+4U70WgIBc/XdNK9wBouzahJd6pwbKdJrrTNtgcNHvRjVurcJsRE9zaOxz+wreI4Jwlhr0EjEKesHfszb23kUgHT4hpixYqSFoGcINatYAgxU0DAuTWUHNG/G5pdpNku0S6crHipILybRuqKXU4DLPZMR1M00424Hga1aXjOheMnm6615nxwEIxF2HJjKehp8V/1C2/0Z6slMe3azPhUg+somjyy1V8hkM4XlZvhmI8TDCp8wQjeBGTncXFe6Sy5uFkcHh5KsHRU5kkNAdp+2notVCETsEp0gL2uy0jhIrLtE7fXAPZWCsWtJFic28uJ2/nLxTS24OHCKFvEtlVcFD7q+Gz/chKgxrXDhWDE5hFvpebIM0AWDj2WlT0E7SW2igMtSXIawM2FuKDyY47MTy2gsk8CTdbu7yAyWfqCF6ttSyZVvBIo+FXRNdXMiLTHEp6doFb2pxpdwGEoyldBr4gF0kPaopQ48WLRDbFAvumKUWJ/qqnXPPYR6fzctsRdr4h0fHH30sdw6mwcIlIx0Q2KyFwZQvaf/taM9DV07qJ65oqB9jUJc6GBIc82xvETQzMrNNI5qumHZISIyPm3ifdTAQ60dTLLedHqq8kyQVqSWjf3pxQPl7LZcFZak4Jch6jhIhYy+cZFtJ240B6OvvuXirNH4AJ8kDfcqBodasWRUIhsdCDHrnmA6AxzrYkrw+kdCT38Tkb12LVr+88pPosDavhWR96iCOdU4ac4PZXPTiiarqcHxQ4ijdROEYC1WjrDOnFHTAkH0mDZmZ84amXGrCOGMUeVEs9CFhGqs4J5GfG9HCCwaLS5zi7yjRa6qm+Ua5pUFxqA2IQ97xwqYLU8QONYIUfyXXMgxrebzakJasF/85f0oeBm0aIdBIqSXHIiLfXHPt0J3GU7phyXEQUnOM0RMw5FXDTUsAU9qkkCh+h4IWqQDTsXKpXSvQkLOBvO4xywgFJfayS0DfNAHz0tjq3sap7DsXl/A/J412tj8kD3bSw+Vm4zBjHINkoEsJFQZ7I9cX7YzSxcW8iWYYNv37LI1BAEQTsI7JTI8oVDdSCbDxYLZt4o5faTxcpR6MI3k+/21P3WWLGnqMuoRBQThliQh0uFu2FOsBqaylFcTEUuQFAnMOdZ+e57DAVcgANUXwhjHVVkhvicMJIwMOjDNpL6W2xndnMHyRH84vmFrNrf3kUS/vlcn9JA0aHamcP4DXkrxe2EQ6T/CUmTdH1rEMeVObr0bErCkxoKsOL55/Wo1H6b0yYZG7A6C2jMngwHh9CKMCCIjDXDGNM6TCxFXf5f7sqQgAAHfOyM5aE6glHQOGlBjQ095q3p42Kz7lbI993emrEP5rpAQ6oepzIUP0eJGWesB5KgRhTFIjeA2ykq+luboI1G4xsg5yfIyF2y3j9agT6/+UnJnranwIz0zfZogA0tpTNExZhEd+ct6fp/BKMNwTYdX0xrSn7hNdbOzc2REyajm37mIhyzDg3C9VePkOvdCQSyziEh9aI/2akF09aiiYgGaodM62TUpoRBteHyXlig/cOU6p7TuyUjXygIqWE741mGCJUIu6ADuAdSx4D96gTQCLQ8GMfxz1YO9NkinMbQeIto67rYosxRnfO6HDK3SYqDb8HshGdqREDHkcAQaAQK61pHTICwblJQQJksHgBHucf+wOY7gO1mRscBaLv9oxMDW+2nCxecdYsK9V9lpJ7CSw/jZciQMgtcjRsbGOnABZmUx2CIaXdWSQen4BKs+77g6Jf8IVNZRACK4t7iWh7iSuCgZIiflQoiXUMNdwAZhHqwQMlGnp7PYkhrPXmEQD3SWLfBy+wfz7p2JEc6WhDF/oFiH0iScGIpFtNAqU/u2jQItBHADTCyLnFkVsYujiV+C0bvjdoyQwshKRITcA6OLiTjhJnYoE2RmCaCwEdYbbDzzf0R5gs+2IELD8w3g5n8/+ebMGzD+IYATzjFqrJxbQDH6eB1Km09JQ/zUJo4tGotGwMVioZnKSC2NihWpbYop2yaIRIrXbBAuPdAWz+BKEfEkwLPmBe77j2ourc8JKYGrRA6jHuwM9QskU1RZsiopEhzFogUEp39q8hWN0hQayn1KY34ciiuG2XIbRQk31USJrw7r022IYTUoEmud2fEzbMVZ4D9DB5AzcA20Lb9PCjgjcmaJiarPfD74TNWYwt+H8M4dEEHxrM0ZihBxJMCWcq0E3u1mBZNGlMXtvL9m2aXDBQRqXqcZTtFW8yXP/hn2MRJ36rErjQ2ApYTE4S1zqZILXTaTCakl7uvzZcr0Wso6qDbR+LMAYVYBGWOz83JIELJeh0kmiTCg5C20Hg1B3aWFONEm6tEkfMkCmWY3LpbKc5lcgcqlFzvXDQgW2vHMjgFFkvC21AVg+EcGLQFwlequ0i5hts8uxfiM5W8OMTTfIELXhEdqTCtLOrnAKsbwXqYSp4fgmHnbmfF24pdri9VtoBKCZ18x3kll+utJS83OrzliQL2mskjdnQzYIpvABEUThQKmoTxqf53BJz7Ngpqw/721EwA+/MIrS/AhASqXrA0vhMfg7Cwft98TSarcacDUt807qxywySMLC2psiOSxRK5Urr/ECTaf0dlP1qk8oBR8TIeHeAwCyxdiCdxmiZhBRaEi7xDOO/KdxvYfnU2ESWjJwME8kvtY1ai3+vFSuLrCySAyCS+UOwE47aHCFhU7iJzD2dYitfc3QQFv1ld3/rIXvHtTQSsBJvUU4xM03rUJHOeI7RMixQqZP398jwlUC9RDCOVn0s6kpYtVfNLht3mLhnhoF48qxT+VY9Gxk4eJq++0ouys4ydbNdxoEwcabtfIbKkVPT3Vv1471TunnN3saoxzCCpfNPze545BaPGEpR7IVFqa4o9Q/nb1cAh7yENPoHKVydiEAT4gz+DVrOMCL1pPrtfHC+foAf38METgjj5ISZvmo/u/zcrNJ+SmH1u/nax9Gp2JObTzLvKHcUtoiUmamdquXo8LyE2SQqD2jbapD/NVFUid3Vm0fHX/Ad/KpnbIqper8WaV1Xe4jMZ6HdQRai7LQfGp3nhAkeNt70voiDGkVY12eKo6pp0UWtbbGei48LNy5RoHv1/kVKM2+NccwcoiNZ8+1HHfLuuI/kg/lAH9EWlco3w1xt+F964KiRp/HduyoC96UuTNgiIPvnrx+KBYE6CD0Ju1FgKrUcJsHeLtySWsL/IE5+vOscOTmZVwKXZndb9c62ktnpEYpHVpOPRW1os6q7dhHvBl70y3LqKP9HqOBOnYDn2ti5D/erBfa/6+K4htbpceH42fF9W+I75U09ilbMhKF5Kq3x0wEWED+Ubv7j5Md0py2tChJqHhaugu6vyxAQTYif82VI81d4vkxT8zutc8LIeJ4UpJmp9KWhjYiJ86kLrUUBJTtSiWQYfCH0KdNROkH9I05XAR4mTB8Zd61d6H0GKxmbzH0Swm/am+Xv1pUH78y/7ASM+Epmm+TPWCx+FdSpVqUlfUk0j8FLPMKOdMP1LnUvDag/jE58WQ9v3CNFEK+x/SbuCd85/YHBf+gJpIBAToeMoGF0YZWEFkwEopqZrnvJ2n+7r+v+2+Di+QqVUqgkYTyqjtQdpLpB9WUwN21OMSAM5rl23lrhjAdOsl1ouYKBWUNUWpq4N7hKGf7y+Ec1wiV/GkKBqxyZg81BXkWWUORXvevd34cx/P+P1njwDq8dP+3xNYId07NLvGIzb92ZSBMWxDnBISuK/pOM6COynwg67TdHcPZaNz7ticNui2W7RLehWZvnYy3FrxuBhF5cLPtyEcG3a4O8uGsLOuPDBaPDvGnbKWfcb+3Stqn1fqLiZmkjru/GNCyzVe+lu6f6+hXQtFqxcTm+hKPJFTf0fDSdGodjQAfWI69e/zE9PUeEYpg4dRHGqrOpO0BBeT2cbxMHHcJTrMTKwx96a4qSa/5i+8j4oQneXdBkn8iTSzZHG19LNWh8tNl1C2gKt9S6ILR4paYxoW8DhP5/kkhE1gaoZWHh+LdB5t7MYbAnAsf6R/kER5dMS6ellGtmQtAUU8fy+01F1cTC63D/udkOkjP/DP4E+ciuwOtqC3Aa2Ru78vG+kc8yf8Hf/8EGdUhD9z7dQc0I2RPKgxKMsoV7YJLnxmBPPiIjKVyuI6djOFtLwnWmhz01+3099oZSSBxzbf+uk0rkZUJLrBjyoa6Nei9ea4nFe3D7DzUUU87W12WFklYwSfanV5frihQqP6XFpDA9OJ5L/cIjpZcSnNXxpWEAzrn5H2ZnZP+yviw2po5Kz6XgGJ6DqdrX9DUNNBTDk+PLWtM2MIv/bj2VkQnkW6QQ9PS5Lhw7xvJGs6IlextNgrWshTxPrflbclahfr3790x7K9xvBdTGqsShtQU698Nz+19+535RCj8K/lxF1f3lH0rWNE8s84/cc16Tdz2ZgaN3xln/XcDSWYyzgjnwQKhOhLWubsXg9Gvkdh4pBhcXMeIM/qy0U4grqGluwoCWLjZ74PElI36IXpHEFyF6wWvvQEpiztzQpchv3uqTGBTFmmoQmBsIVZfTDjcwPqlm3IDvdrNaPH0Us9zst5GgOjROSm9AikbXiA0mqc8wR2ceCpF+wptE1PXnwL0D5ZQ5AdNbepA1IZerHp2/dlRZ4oq9f2rOmd2brzQ83TqobGTy9VS71eRdJbXOcj+DQhuI9IlgvW/bVRGfTxhT6PujXI21Cyj8u9vo47D4LwsfxWgFnOkeLQyHGbf3v47sbA2w3zFLNQvG3GF7kERiSKsgXY3WIoDFV14G1mdRpea4CSm6DkEJTPdEQPnofMmHpzXC304AO2ca2x8KEONhhNa7Rwhc4OZMFNhC7MQJ5Qbp0x0rxJSg5MIcnodXQdoUd7A/QS7x72ycsaNZJ2aLBxb7vvy35j0qPjm/pe+1osBVNwZFkaPpgELRhX6t4mc8NRLDc+WbcGm45GB5Odn8AoMXZpuI1fxztknLYV+Vj4Ng6mEADwbdKy2ykU4RgdsDg3Rj96Q6HHzPLMI7E1sVV6fyI7AAK6/FHAJcBHi1QkCJuibfmpthkt/PXdSJfTqia0rGWXuOD2P2Lc7qdT39n5e7awgo6m7YVEhei6tTWcfkEB2Lsjgjtsgqn9jFhxGI6co0NOW3RnkQ97qqECyWQ+P9svcLqMGpNVihs9+yNO482Lv/nG0ibjBkbw3BOA7/GHnD07cB4WrG7AsSPZSjkFszUV2IYOviz5VSe6v1AZYj9XLX2ZkSBtLD1xjWwYmBk4zDXpQXBiFTrF4RrSQ8p5276VizmMF509xKVpuUzQi2nhFCK2wUlWj3Du+A7qYZ0oIfWbWCmkHRthcZ7JNkE/kD04xYx89O1vjpVOjdjm8f9mPq+fL36ufUZMlhnC376z8nvgWJz1m0qE2hoy1dzW/E1kMuDXo6IMxzHp8s5HbPJa5XwhT+5bKyrYOPZvkujzngX20fnpnwDSu3aUgOsgYEXIGDqzUSGBgfin5VDbRXH9OJ8Ol+KHkiqpg3gmZauv8LXmGy3YE48f++o01+4JQJoncPZcN+uJFctHYipbLaym22XTB7UJdXr+xUmzP3S9UWQBJyYUhDf/ej+IQU1suQI8smUpLjQZUn0X9PQX03tfCgStx+/hgWZ/UuRiAmuKIDTg3yND6dYVN/T4qR3vcUInDFOSJq+sOrzZtrQPGa1nXENo1Ab8hAOoVjHNWJiThkhAu7oa9dztzN2TAWdwRSRbRB8KZYc42VpBbXQnRgciruCAPADWNo15O7XRKui11XLq2+rwCB4kzHV9bW+fC4u0TvvbKyP8c/6RZ7pKDvOj7Rk3DTiPXc3MJTSIKixPv7Eq6g8OnyJjAY8uRB/SlPYMJyDGJZYMfmoUMR93ov9mc95aeaQnoTZHp7eYBM7M55pNECE6vNp+N7pOYDs656supWBK9Bi+10Ty6CjTeMEakWhn9NulNehqAMI64mg/QTMcoLUJmV7Fp7x+QOJlf3SjUf4WPPae+fe43QB46f3C9gvV7AnG954CRd5GaaSh9fuCoIFW56mXINwNR6gTcJTOGd692gX+hpaYvVkKEZ6lP3M2GRu54l51AIjrwuZKJCE8zAPqNTrWEcXxv8ycGS9geyTOdpl/3BoeLkmrtcOZuLqHju2aY6ZeWUQo9VaH7oIhS25jGILCFz3uv7X0HTnHS6XtHNk89trAI1zAruV+WIXHMc6bGNZgI4DdZ/TwLY2eCB39lNzlY3cJnTIZBDkZQW63lYQIfEkLXJSTK0SU22FFRoo4cx9SSl93heU9ET8dt0d9G6GTiGs2L3tVElL+Kjq8Rd0LacCeFtLd9H/AbVDB7lExoC6bpSWYszafbuGflRqATo3wUbd6YqjVteDUw5Rx61E5Jgj5OWK/X3n/EeaWlVUYl8XMsVHoVl3mHE7BWn7qODRHDssFud31qgFFPkClOThrmkHKnwhgqUD304JMg6Fm6aIpYauJOns7EO8eWqHWFU6xYWHUlL0ugijD7whcNBfJpESEVv3N70m82k6f7YeKn1zdBZOnv8i6IBfu10P7aAwLm9d41jSGcO4yyhWQ/fRj8CEhKiv6wdYckm96/NAtOy5kGLo39/HHgUaECXkhHE8TWVeVbp6uAZzdoVLJh8zSULjLq/bBnfFjD3ULMp7BiTqZkvEuXpVdesyoz48OmhykbjWJMsPWT/YV3kV9cpjoZKV9W6kEPRUGFkeyVrbInhJ8vmCAPN7kMl+bLIl5JZqZlQtXIByOtppnJjfT2rWWkJkeTG8U+HS5O7tzgoD2fH2hMhI2zc3MrjqWrxcu5nmtQq4tCOwDGOq6hLUxcb0PBUUsLDOW9VrMlKa6Bv/BQiVxeVkUXcC2zGWSczQoENUZWcWKq/LKFWh9kxgTtjBmVA0aRZva2fy9dTqErxbrFpn53XMDbZr3AZ1XPWyLf7TpRUEEb7dtUguyxojJleLK3szonAd/cDeW0vfz/S0jBmaeYUu9oQrMxhUTqfrBe9Vrc1Yt/5p3HTFtNUvQ9GWBGZYtouByZTnvt/o3USgqBi3qdSs1FJG93D21B2tw4SHSbXEEO7Vj8erlmDFQguZGFOkAH2TXrBbTpHFlZVExzCyvOECWTSSKA6hSEGUewgdrB/41MwQapKantwgy1M+yVSQXWG+Gsjrxqjf/f5pRty8OPT8QYxhhTaUEw8VbYY2aSFCXEcdJvdkTRDxoTnzUVg6tQTmWm7nshRKrvg18ElQ55y7hmC7K1l/JAc8i7WHyguZVNbjlbzOHfgtMKb1D0mzddFTL+C8cQ+ao38XmHVjMCI0v1oL8AO4JY48ycMr7FqjBSZ3JLgyF0O/mOWf9guJZKXCGuoS8fKCOMPi3Ml1oKL4MtrR4FsjvN2zN6GCtM6HRzQ93h42gQWwocrlcMqstyGsoEBRiQ07GoVBaq28nBg2WpeMLFunBnsNm9xDIeVihdB8clxkOGiyiansFj97i4c19um4umE3SQ6hGfD7a9b9RVWDUOISMhIY2WMpWi6iIukBTY/Ep5thVxTNx9uZu037Lv1f7UYcdkQkPIzQAC3xRTPkSLp7v4eZrT+/6S2Wt7H2hFErvXs69tebEcflQYCLKKPk6NEr6q2+d8fdulE7ulW836zNk+Jb8vaXBZeK8jitjVYQ6J5qdJ1PX1wJbyMrSh/WZSVxKfGoaWGvrRJUnANSP7V0YjYpRoyFtWuL5/fphqJTBJLWIYIRgzXhThOvKy2ZAV++PZNHi/betb5Vgg7tQmAqTpGAHX1UUAlh/3ENXa3ImA+UJDlBwt+eL0AdcMIiRBz0LQm0U9qKJHWpo5NvkHMAc8kHqEcx2M715sYi3g0EBdaXTgiAAtcBzfqgd5MNrB0ulDUlpSHafrQLx4m1JfnH6MOxQKuoix4pmLjycl4nHQrt6dZAkgEraJc4D7NxPt040TcmOh1BDDCk02COSuzOUZhnRXJcxoaRtc49vSQY90mbzgFwUi7S9f5PR8oJb8K2oaPe64/xgHv5SBk/bI5frgvluNi/7+eFFuqlOej4DqI1usTk8jmWqNs7TIzKiex0zp3Wn/WkzojkkV3iE3mx0VRnePWzre+CHT5bGuV7HbiY24P0fAj5m0v/GcWAzcaQuAC1x0BtstcKfppMtVtQpwk4lyazsdtw01g5bnJNmhPIpd+gtDQyY5ULadSn4lioGSuBgd0MsQZqEicQe1qtnqJGDqiZK9beDLnKPgRFFzViqafJfJ0KQjyburfAsgFKt3wYN4u337JEdDOYNrdvsSDPC68nErgxgAWcwVe304iY3/rXniyNT7lzNcARmKPv6fJOQdf3zD2AK7ykHjZ3lHWip+sgLRyAtrXnaoiJmPXSfDib9i7Symi7E6rprI6H5YeQCVR1tZux5youfVH6/ImwuklPPKkWWO+RAgi71WUd5aIeeBftdwIDNl4ltydzRJqtNh0sLh0IWb2NieHzYEBiXjNqbbQrbIy8iFKsKolqRqYPHn5TxQcs0xHis4UmllssWLr7QmC2WsVFDzmsAGFnL+cclCPbCSQEiPzfORF/mNdJ0oK+uRkMNHRdtbIPXL0wi3bYMRZyFRsDBCOPUy4V1tkH+wY/Cc424ZVGQpeZkGaSNO6FyH5hWvdnlwTzhVCYQ0rN5rMnKESe3tq787RtqTsFIR/NFaCNQ5QGneVN2zMnFjZ7iBx6zW6BhbsuVsvMrWpFMAZ5E556BRGzZ7iEWYmFz+5pRgLhzr7vt8mydjjs3yJUVR+cx//woDbO6/tRW1EvRasxrv4uDrZfn4/1JZVX7N4u37W+ZFNyECkYN427nx12+SSgGLzbUs/VUHEy87emuF/NoRYzM66azvG2kuql9rN6M5xMkwyIKRm8o0GpUBZMK6yyVXmaFyVIBSHy8YSywoKzMEILeZ3p4GeSMl8AJfF6vMbOBeokS9ypoDRSdiaUutI6HOYUU1Li50GOEovFZxiHG0uxDmjRXLip0/YqBiiJhxgZSJj2kyPOLjZkHVJ7VA6CqA8Oh+MpAk7Ubw+Ui6Eg4O1zkpCr71fZQEifFRzSaIXJF/qTDsut2sMHX4gnXn2tCW9K3smEBLKn5GzGhWE1PHU8EPWWoqhUxQGC6G82RckNl9yGlMAsTOahtM6BMqVlvaYjvOkqOdbEh+uSdfCPZ71PFkafMsXj9agn0J0RRsirwai1EgJ+E7Lc2qStusNMUNDYULHFDrV0tb8QwOlQcTh7J7WqIWy4RpMsQmmJASet1b3WRI3YyIPCYJNRMz21kaHnZKUP78N+JEJWMUVvzDnRu5POlYo/vpKFNlBClhh9X0TGdXzTLW1lTilADwh2pWb4mDA4PtSDmmVwOgCTRzHqzYOizjmCe+DtqmUCXoPG72no09mI64oLXPs0N2sGwv/mozbVe6kSNwVBn3rRH1b66FaGNSEx1E4C8Tpl4b5bLBu43hiZKXStvC4L1QSyeUSuHhITrg02GdxaoOtjCQvxFApZeLY81qDz4HVazE1V3TXyTugJNo2smpftr5JkMWeMd/ktrRnIoMl2TIhK3scgxjjzTFi73lgbmg4dwtavJ5JDwt73ZuacqBo7MAQ8BPSCvH7RneCUDJoRy4e/x90M4T8DwdKFDNvkANQZFqAOtxVsRdiqkWeF/XlNIgi+StBxaIIvrQjjkJp8rthY+wCqWFq7XLhRmhzmOoLpn3OcwwZ3Uy0rmY+wcRXzlPU3xa1iTTTEfYaXtHTr3MJ/uuKf6A9IxDHdS7mkFOME2f7TdEtYnmmq6BtnoD8rX0kS2SVEvrhJTNNzshwmzw2tXNqurdDOa1/BTvtjoe0uyDLvL6D79B9X+j/YlWCOgqYprfU/UDTexVhpfDPNBgSdhZgj03ACP8YeoCerF/487EKKPezc7cSAUaipVYk9iDX296ceRwpZqXIhbRJkaqNMUZ+8o40il5m1a+5JxxCkEtOCBn7Va4h6vYa2movddA7rzTOK3ei0Zm4W+hHmKYF5fPPvWPNNtQR/RzKbrhl0tsqSC7e2/eis9qTUNpeN8g5UzL07YoZl8i3pFFzdsAHHUwtvKknl0pTxX5XZvBUZbFFjOKnS7rTl0FoQhos6xjBw7IWGY1b5BT94cHS9iJepy4uJ93jSL1Fzwvp1Iyd1lutEsSV/URz0y4j51tcwUAnpR2IYri7OSaXAPJ7ZubpBYOpcjsil9N7nfEIcAGhvBHbCGU4Ny1OJ6zFoMau7t1GoRxfAtYx7poaZXbR1B0dXPMAnqvNOnt+NzFpv9neLmLD6ba2/1C/zWU5fgDxxOs4KyYTm/b8A9OC+OKoRNOo2rZMZVbtEIzYIalyCjtOU41RL5983HuO4Mfg2U35qLU/mIo5uN6FIAhVh7ww7IggWfS70wgZXAmcdK3YN98Xt3K0MokD+II6nrKhrUYlwtv61ftXnovqEKUoEF+bT06MRDN8yB/1kBu55oKdkrIcks4qXWPpiMI6knb93RQrF4u+K6VfRV/FEg6PQ10izCKJ9nkT0KlD1Mkt1KE8vwFY6/JqbJKgnoSsQiL1vp7QvAMDHmb7PPOFwm8KvfT8qcV7bWnXss8smMXnZXZFaGzK8owFdDpXjGnz03ekdMSxyC0hY2m8tLphS6nIOrNN39uuzH2p/ykuSufGHQg9h9v3K2iGIitjvp/2PqLEqivS++5Ji5Ke/unWn7+VbenOqNyVdvDFPI/r0UnkVqgS1was5a+j2dSLi7C1KFpJMj+wU/8ELkpuvUJeIOl19Ep/+AFwAyPOE3WqmVCn4ikeLajgjKFrqHJ8h22xb47C+1rqKi/24sFncErVG4nS5M9YVnJ0t82fFmcBXExAXfnoqxDi5h/muCrG6EjxYIavvp8o2uPD5qgs3w2tF5xpw0XMHSxcCuQCYoEDLAKCSH6xsIskSLWdkMquSToL9UFsBLtjqVQpzkdK6tsefA1DvhYK7i0WlViHjU1l9RnKM/+OqVvBv7NedCZAUqsLdMriWSj7GkZXdu1oQlQJMvH+D8AhJ3D6QGSWXDpiQqpH6nTf0yA2uxYiCUNHsfDfNjVvUBcjsh/NdRH0SAyh01P5QjZZ76y/pxBPT2kUVDnzdSKsYj0GJcSW7uU3UnMTP0fiBPwvfJUcYGOXbxGFBjGk5E9rj+SGU1N21fw5pkk0b+7D2iMB7Kc5Ij9gBHM1Ymw9Eh6eQXcWxke+rwg5wId/NB68KKN7XHKrMykogMHvXyytYNybgTMPt02iyhfd6xm6vPP/r89SjWS0+3Ogg8YJ8mjb6bqpX+PAmwE6Y3LGp2dBAYSMKxf4WOTA4789KnQT6royDDp5daHnyIIpVFHy6IEslgUTKoPTiLvc6uCv0Jo/LW6H4wEXJvfkonosBGxVusNzbZ0aFEb67b0oyiqCJias2FBpYkWUKAZ/pnmawDf0H76zUIgJmEkiN6+T3ELwDeDYEVIii6H9bKGxptCCcQINdFlpe3U4d1GwzNKxBegGoBFM0dlm6w8gkDi9VppxT6rA0L9jrZG2HAplYlxtBsYIxiRA7YYtQ8ADGrpDLi8gEVgUBbv0btjcB76nNgAHqlgOmr7xQgELKD/nGh1ab8WNwcCBNCrCtiyeWxQkWtkaDGzcJWbta4LFnrLHvEkE3CH119OQrwMc+r95q8Oa1lOdS/ba+P1gIJEsAn+cSxcAtrQFBRPJEFYkot0KimsdeWjAL8DppVX997Gi9S0GbH5TmoQ1hxxzqZFAyVozZAEqtHb71jdn82PAIrJ08fowfemxej/IoJEmCAUHG6EREyiGHkQK+Bq+g7oqiIBC2FvsZlAuPINv4eAu8HOmqq7cNj2le9zQIMVWgwrIFYDsuBw8ln21Xx/Ha2O1vAMB/OXLseX+hMxkEkTDvn2HIqAKDWVO6orI4RbabqXyT2MoymHjaHgRla8HCAJBc5lufvnqjhJQW6ttfIWkAv4bA/eR8uhoJiGiTkhmk0wDpGC8F4qim08nTizSjmVdogGCTTLmT02LuYRDTcYq01KvdTXbKILBC7EfiEH7s5J3Xo6noOKW9gUmMI/v3aaZlAAPCmnP+maco+L0SSp1vNTPee6iP1K8DWcRFxjsNpiNobZR7/w5dUfn5ktR7WaSMjQ3a3p9No4tUnCxuaB1zJAqsSxZabbFqnvZspiAt+z7rOp4nixzHKgLKcHXjnWEEGCggkKzzNOmZbXea6jZSolRqZh8GY8M0HTNLPETyxQUL/phxNAnrt7IuFu+wIVpF6bDkX7EN1olFxf0I7muqRUNxByAx1YlL+lwd7AgogG6qyhSBiCLEFVWC03egEJRWhm8rhRHrKqfQ/B4Sv+d3+XxCPI/83X0BJ3DKhxNkV48p2pKA8ltag/x/dd1sQWpFYhNEbjU2U6kOICPZAhz1ISKZULBkgG3RfOOBVzzsUWsOhEg/iOrVK2/KYu7LDsTr+4AF9BckhTGlOc8/xfpiSyTesBojMy8odz+03h1gNswp6rtta75lY9p0S3UB0orpVNDopR8oTLJl8hRAK2ZLrYQKgAmmbvsrQchq2ZvhzdEDRQ4yZSFwTPAsZ8Q/z6r9UKr2Khv8pkUuOSoxFYEyU610YIv7OwdG/IV524k2g8GUtY+WaeT2qBcUvediMSOuYT1GpvDUFcKL3PRmc/dZsc0PxGXI9mFbGMm3gjht4FEdCgFfvksgpFRiono8/jytqiuBQS00lqruTQZ1quPP9yd14T6CcpCVx9GxXoegqu6hLYdIdDyMQVMvJhpgtpHgSSmK/LFw35fKHN0M52aDAmfKW8LjhXPaw0xiH+zX91tTkGHvy/XG7Bk7tMdwJdWGYVODtX9hFHjG7qqDwm3vbe+YoHjwuwoTPWDDhDHkRkTfZsMqjfAJtCCuSOmRylipd+Y2tI5EpoplO/E9tsAYqMuTMdfAxulNKXJ3k+O9GCqLIWqMWBuJwXHGddWIkP09W7CgZluLJMghMASvVFhLWJZyFptZl+j7UeieY9tWsBRqrfs2DIgCogHgSixKX4n5pZG6P0JLfANQUcx6AQRQJtH3jmkBByIr1Glk656nRmo3ElUxYeo6aCKksyzOEXC0m67TxoTbwA3nzrzuUXt5lIlyae/RktvDiUA2w+I/iNqcqV76NCsbnlE+uEPtbg/E05rMPka7WFCDCcO66RH/g5nDlKD2sIHE6gak3qLFD2aKqIGqFNRgQIGY8GNPfz4kijzn7YV40gq0h2dARTvDxo/86Tm7ECnE4puM5filRT/EprX8Nv7ZwYlRGwpDTKZp8ibfjIYpJteQ56pIJt2Mu+UvN73B+MhpaRWb2qQQm2qWomRZ3g1aXQdB4DyveVCa7pKkx+7gZ5t7s/fBLTHdb2iRQUqyUtB6eyeJNqEaeI7QE3xjZ7+4sPU7wr5XZ+m+86SorObiDnPw208c626f57+cvxTIMFsIIKe34xjmawjTHqbafFPhWAEs8PlESKDW2HxRaYHt3e11dawvI9S73lSbV7z3IyvfG+SQvMw/+dDYZiQKnPjUOINtxvbpGoT8OGSTO6JhdwCCNJd479lwWOR0TX1CQ4lNzrE8bh60pGl4135T72Ome40AEfUwQtLyz8DCAuOafDG6ea2HMvz3V91wPnW1b3ll08tSYAdWPuS/y+9nC4qKsCj5Y9GuBHlHHvuZn0uPDTPDu+DJT1pqHvVwYsDuvNuEAj7wz1oOZSv56NR6msS2LqUwjH2ncOGODEB8cCwyAlw7QYNshzW4K5zFZd1kPEAATSYIbRHQrpcO1hEW6wSIPcI2uolIezHWvd83pRN1zndjzPjQTkcl3G2vp4K97nnpUhl7Fy3X0k1nsANwnOZSwEqW636OnZXfzU1bYd+bYeOKN4633pmSBCUq4OLWw3FxZDdzDvtPI4BySLACUd27Y9rdFtdvgDITP4yIO+YVRiev29o9n4gR3gu1ar3yLGW0Sax2mrG+9EDL49Sb5QJESquRIMeC6MoKaoO9khvFelE/32y9wEck1Fo+J8Om/T7OgchzAuWHbatGIE1UJmkaOyX25/BAlm2/6H7vixABSmD07C8SIN3T2eKa6LgVRMLVPBeCpDfIITA51v0dp08lerDHUnAzhgQENdecGyxKAgxIKSrujE50OMP1RzbAMfI6KU/hkYlcrGX+gQXkWiP4Xl53DpTf8hq50cq52xbWlp24vbcQ+pRo6AW5GaV4fR5g2fON7jNtgkV/qOEQnJLhVsGYwQzZIQfhvYAvjiRyK2JRLDNC/bnMQIhOPCMUUym25prvXBwHxUYZQRWSpHgSd7HETUI7BWupn2IMzCIWCL1dfLyQ2+4FxJoHFCfZISBXko61pmHC80zEjWOBtjFd8BRjrGugE3Eo2TGccfqcp8q2nV2MnrNW4TJbxpSPtDoCCplEo9ySsW+8MgcO8zTUlPa3KzFtxiTR7ohJhG4oTyUxspkNTw2zW2bipVKQdQjsmDiC5tOkGSBz9QJL8v1EybiBr2zEuoC2JMRssMljrDk511BmhY6khjT+g6+Z39ySR8SLNlArlvIIQ4p7d1irOC76deOLKqYgZ3GkQFYAEwuLSj0HSfenZd/L579BP1YufKYMpOEhB2XW+6S9hzjS2sKEZpynTatoW5FgnDyLIBfV2VfYoSYEIPM6gIs+eTF2UlvtQ0tl/dSEaphwo3mFyhBfPrtx6fHPi2l24br805R/WHwjMDfa1KAWujIr+uTTzpBYi2HEdt+Z9Hl9MYgjy73/0n3Xv5gumY304NiP1UiSjqdfQvSOe7LV46j9+fncHD4suUKIJxPvv0ja6v2aKuptyTds9jcHmT7SYysuZ+IYop+TsMKy86DESqkM8HxBHTAJRG2k/tCyCDrele3rMMVQrMKwj59oG7un/RWeArANVxN/wx7CGwqHj0sSXNSH3xbLGBF2sZD/xH3jqyrtf00mCjO/i8zkZkSx1pHFDxupBfkdBvPWkWBgCvv3XAePiwPtMtL0BByNrK3ViheVze6/io0RRWVWyYqzLcPAbdRIM2Odgmjuy8VdppPHtPtEpqDmQbSceShZjTyARgFrJeT3fbyh7bF4ddpcGBl9savCS/MNMrG4topmWv/3QlyyvywVcO+pJ1k+G7NCqVjblK6w43BRBbRYnQ1GulLe3A9Nbb6Euht86KBdhqmpvqADGuHtNjaHrG1FT5RhDTWmekUnhGnL7vvz/VuRlqboysEOmzqd3ki7rEi8gri/mWTqgd02DBrjexrdv0/eq56WfRiW+sq+mmBjBOZCcM4NP9bDjS5gkPKR6a28qoea8HYhNDJfqWKLc3fx6JC33pDUFRK8WP0aEZba/k4WctryDCWzdapwGejBXJUN8+btDhoU28gCzaMClnsN0yjRG8+Ye9SbIjbppETcdqxbibktliYu9CaXnEQrgcKm13TDhbI+n/pOg/VEYWjkaSj0q7UiWwjFCsb05130O5Co5w6MImJ9e2l2ukFCC2cUZ+pOJUhGxPmpaOABu+hmwEq4NJBg0HQGEb32hOi72VrzQ94vaVrOfmFzZGygTcEzv5sfBKs7K4NKKyiAcwQ30TGvXGosvah+ICa7TSS8bXxELbGBfpXbSPJywfjLzrccg38xfAfF6pKQBJFAfAIzRbBdxj0eq0CpFtCwxLpmSY6uPwqwi9IIMYwBDfjfUWbLVBilYPEg/mL6djJ1l4aguDz42UjgzhGvBnhoWDGvHCKbQVwYSWsH2mSazoDt4VLoVWHpDChGD4Tf30BTnBTQNferAO+ZhzfHaT6R9ahaog22CZXblfLE0FzoO1NqZJK/pOLth5yEeS9AR+U5dz/MUyZwvaAtPquEeMdWlT7HIsfMMVSSaT3XvKxP+EMx/KGlPjiBVqoF1CyYB3FbCZd6gI8p9BGHewFGovd1rPyMnZrmKQtZVdV141/MMeeKq9uU4Cs8Zyc7/9OBmdX4jVyxyoPWO5xMZLX1ZGImB8uLBRfx4Gxy2IqLeFxj+uSy1vcOT37kwuFnSaKBAXExgoV6r55aIC1ujOZHxiA4y36TN95ydaXWM3qeGrxLrFioF8hDClYmxMAZQuwjemL5zkTlfNJtHtV2GMEqnMYm1actepyqdx57OF2k9U7QmowzwoDj0VtWsLo6AhJ1jhlSRj8VO2a7i2s2MQUACdvRldIwSUZrfM6LQPaAxgYEixEHhvcoM1U0UoNJ2QE9sug40O4zWxY1ab+gyOqiD3r4xzEInPTLQMTz1M9d0GYtp38OD8HUkBgI5t4ozsNygToPzRRDe7oj0KpB0aLz7TeRDtsLUW3Qlu6bOcVbm16HUNDyxaTZDwNU46Mxb2h/aVfITsZu9pFmc1ueR2VIUJ0y3ANR5unaWJHnfYwLqSoXzq8lL8adqKDddglztPR9Q5JhRbHPdY3mSpiXq95DFvI8nIDZOq3BHPzHWLD7XJMXMqa3lVmdYCkFrIF1WbmnW+jPtw8p1puTl7Y590ey8IntRGrBcAGknuZQy/kCPdpmhU3fJ+uX95b+lLfUb06bMZUrbtIJx4dtYAfYhhvWvCjxtAwJtlXmuzYaV69++77fRMrT9dfvTO5utCHk9iod1eZ76MOwJrGES2KazlgNIsZDs29EKgL09q779xD4wgxYhkVr7NLQs2y0PSzH4I9R8bPut3AzoGCcIrShgnMdgnAsvzYQbs3f5sultRqU53MCm8vCXG6ZVEaIg75WG8rhtvIehtXDB0QAkPQZckEX6Thgq6nNRSw21R6nQCCWy4h1WUjKzwnppYcbChcdJva58ec7mCWiAO6HnEmPjUmYDrt2dDsWll9dUi1TyHi5Zpymcx/e9nOhvQ5OLobeH+fTl56y1ZIRCkPpEQL5impXVbx5Ykjg3ZTF6ItkKF9y+d9AcN5G8o2cLJBbUY9Nff1NRZvX4dvIB5RgLg71aRIeEgoapcKIh+8pDvDTDjnS04KLFAehRblnBeHdGrqd1wvpdSWz5qTn2ERdjTO40PI92ppP2ME0uHvBN0GJIseVYPyDtXUQqcSma5h6bjwak7nSCGs9A7fm3zQN9eQ51rfGak4ZPk3NTLaQgt5YQFMfyxuieSpL0aFA3ifuACUxdf2wFpwbYuCVfNRclTbSXojOAhqBg7i+FiWhki91OcP9+6uhsjiqIu8/yRJxQso72gpB9sqf58GEk8X1vn9ZOmSRND06GOM+SH+bAV102HH1Gk0eD57AEXYTMAI7yqzmYzcpPAjhpyAKfj/G3PrAX5idkx7+zeK5sMYsZr8w2eC/wMzm8gtRD2X7C/PIMnyHbsx/AX7S4776ZDMDbYm7cdTdji6FLk1oTwSzot1Pz0TMdILbv2FqbLgXoh/T3Q9YbWzwQumJiDOXu9EVzrtnt7Jv0y3cwYn7cuqutp7Gl24E27t2gBvnV9/3+Sb/bAL0WeVW/FQa1icjQSv9dJY9ccTJRb+pZJs2Aq9HwXt3XTQ4EHh+cRGh1pLckjC3nZsIXhq9T0cS7e+GLmGuDWOrxFGNCLX88NeAtdvU4U9Ylv9Awt2m4BlzocnLcRlDluzM/otHQZ612E4VkwIbDusRzBjoi98JRqN6aqzmZClMKoW/TZhKSb+VCevSCqraKlwMtlXF5YgLP7IA03RDjBpce4sqvtBVqxTU26E5SHhYENXBL1c/h7ViQmOHpf0DSMS6pBLU21Ta0f8VMCVbFg+zZYwTjx7GnBMVkTBscOXb3jOwZkkkINtebgXwUldYxWT6bdkHGKPtY6gsk4wLkqkM31+yxslD4f4wWa+vocer1LOw5zNF9ihLVDdL9dOSu4T2cVMWOnr8mkGHgwDfALhgBw60a1cuhVkNMgl74NfwS6H4egkR1VwwklKZKjFDbCOvlnjiDlQInRSvycrj0A5tTIpRlhnXvZRWZSleT8+DzVnpsk4hvijl2qHwhGnC2fbRVdkl4V6w83BepqLUzmsaUcKRwj2fNNw3U3vBMgpKevFIOi3pxzC9Zf0SdqSLivDMF7ly36QHKOWRbCNrBCkStkWCxQXurxc/dnTBW/OUTBCqTU2lxJdLiMBIgXnBIog9rIsBzQ2SZ0Snm4vHpDieiTfKewTBheo3HTfoKA30txZ3EZ6UoktEHoyU9z7Ew4OnEKgzGnVXOMlyXvp9QBRsTbQZEvMxcpBjqrzDuJrzkvyzxwt1rrUBEhzvdcpy7etS29SKs7HwrVxAdNtAJeqbVXF4EF0rkVt/5sdnbMadd5daRynC75CthQti9kRHsOtxL0ZdVlcmPoqC+wLgOvVQE15LeG/FxNg4Fr6V60JLqn2q+KLeQrCzLtV5XVrR+A2tJrTXX6+lObAsg7JCHBZBmSbSY0nryqqMgZ0epLcAHH6BCIbHUJHdPWxpbsdE/LYGHGj+Da2in2CDAo9YEuH0+axeM67wDe8pYgLp2ESj6KzH3so7f1sY3FzfKmiBGPmYh+3Vt1v/QwIUjfXv0H58wxMdCcfxje/yckqx0y3og8faGRieBRk2lDJI8ix3e7IYbitWzcvYNL3WSf8TbaP2yowToj12ovNzZEMKJnZMeMsc6EH1Um3t5WeczREkSU0V+zYunaRktgTguJ2L8CGVHjdNxbmcqlaNebK4EoFJbj10WiwK66vPGYZ86J76VaLXAECVCB7pqyfUjCYNXcbGvb584wd/n1aekUEUtVYRlfSPvptQME6NF6F4OaV9vO3TVoKhZyxZFmjzDup+aAYFvSAEIU47EJGOhZjqL3aNvsvpcMHeFJvhiZGoB1Zch94VTnIEZnkH01ZlNq9AJBONAmYlbaR6NYtJlyQVQUXVjd8Wh2pVahgrmpXATTMxDIVoqMTcDJqb0PnigezmmTrnbFWnGSmRU6UNbUbkdDmhgcxiYdW90TgxeVWOWEZSfeiwMutNPYzRIWoY3r3Fx3YXhxmhxs0fKKAi2yb+JjpmPMgNQokqvGFIfUtVmWCRVgaXQ5SbosBawkAWFWdIyMIsZmPA2nqTMikF6GT6ZtQyKCf7FbtQVVYMtVBAtI5bQVuMRDKqy2b1kB6HIwyp6PdaCLzRLGOk3p4SWUysHmkKuGsaLq27bZMLV0890G6XeqEQF20Wq2ZYJYS5AW+LfR/pWn5MOTbIUyOldel1zKFR8Zu8UB158is+Sf0MP7kBBV0NIwPl4O51jyenOaiZW1dBbOrtYNVhOIcxtwKUZ1tZU2hCg3uqifqoGiTGndqxSd1UEvb5/K6z7AXqUpeXFOOfRwUU2XlYiBlRTMBepNwepliv4LmWg7uugR3KFHtWHNu6l8iQ3lCMPVTM08o3jC3XQd0tpMKrB7EXzLZ3Hiqp0o7axN33zMzi1j8pq38U0ceAKaXrVRVXOkI+lwZWJ8eq1YENwuf4Aw8XzgZIHswjdKPbFZaNL7RxYgCBuWrC/SLUWvHh+FLeBKElGLA3/23fDU3dml/8faLCZcMTsmhO3pUxAVjtoG6JoujUROTqVaXE20Zq+YN8phz2Bw+6b9HLCujaekvFqg5dc/2DmAMONBkTZZjXaGoXk9nuKrEfl+p61LJ1/pHjExdaNe0yHaoJLgvlVA/sVm1/q8dzKhKcWsSuGoCgGrr1aLg7frto3vUX8tEMDfdPUmZIWEd5mt/4W+n2uO7mYzWr2vpeKJmUc4o3IxwSB94rbMoNUNF5fIiYmF5QVFpTJUQOVuyS6HFa1YcZ4V4RmLpp2jHa2PoQEuzbJ8ljr50bylh6jh0a7vsaic6xbFBreZuU9aKvem5pW/DysOUM2/nq83z1IDFcoWWQjWzlp3DWTDP4t5ECDa7G6+UdgxzxMFctO5g2GbXvejLjcMpCguoTps082mhyJFsg1gQnm173J7AEyFqCw7eveeTmUyKH9Q+SpZMsnbQyklZGUiRLkSydjKWTsfQykV4m1D0K/mDwju2r/0F7TzADAzFCM+V1Y4vFdq2TFwtEJ8FRbkqG8E97vKRTucCqc04m0TeBp/E/ego8nCwEQ+5st+BZ6EYHDe9FtcArO/PrP5Nc0ukkmok+Hx+inzMTH+m44940PR9tN5z8pj5dh/bbnJhBzbMdBf0M8CCjKK7C2Ft6cqORIjtHEHiL4rKGsCOOXvhnSzr1NQXWawSp+k0QvgmYkUhMMo75SRSluw+XWWEvevPZ9FEflg4OKzMi7IPNgPBRmKsKG8iFHmGD2hKMgkAol3BR9xQhQd4UC4VYhXekE2+/84oEKG74gMpfllbV0Mn+jkpayxp1zVvjUvP6fcP3vchaTg+zZUQtv7HkKJAJaN4IxqrIU+WCGBegf+a79xvxKn2QFLqobkvdo4ftQnrJSfb0IVGNWr5Rg1Arzv02dU1k0PyN0sDuSf7eG7nVjf8PZhn9V64aOg3o/OUSMcAJEuAS+gMMmsB92C6kF5nGrychi1psrXOdhLAU5ip4GfEeHKgo0kDQrq9GydBiIdALWu8yv1M3B7lcz3KHnHQogUAoKb5g429Ek7RKJmub059O+28zBkAUnvG0YvzG2Pp9onBKcf3k8ykNFBx8S7DpiZUQSvMQqk/LQ8a1UxmUUAtDUZCacQccUP09oMMc/KC7YweUjMkE5Zwoze4SV7gPhdnrsPnb22mfJgqOn/HDY8WZ3qi6HYA0bUsxy3kNRZsb2oq5xqB7tXyxnm6pkg1mHzbAzVeVuec8cIWlN1ADsP1rc1K/CatOVgdh1kJ2J7SYVhLT6QbgDnLT0Hsa2HmgbX6DC8wK6nTy6/aGB+31+HDz03l5LhRQUNIJyPQSfdSIllpJPcEXiM11e+p41q0QkeX6w4Ys+tz5D6Q+P/q7jBFtreFgAkiznTW9WPuWGdrKscIjxB6JZGTzecd4g3MFN2iuHN899R8wlgk2ADpkaWPb9+KMITzRvztDUdlPEExcWDE3TcAF1wB3a6fb30bp1YVq5lEsYoka2GFU/dBnD9J8mpGqMrcSI7wA7LxKoPNOp/3+xvU1zmifsmgJi2SGW4luZle/gh8dNLVIoYktoLBpQtDHU5bLi6UpCS6ky5fIy5g6GhzvKYyTYX+ZVE5MCQPo5FJ9J1Bk0hIzSi+uFwqci1uJVo+q0+m3UX+ZimVjkgQdaq4vpmaiRUqCpTgpakacgJEihK05AgwJ4J3yVMeyPy5uCdfP5xQPLWDZW/8iylSSNaOXO4Ojc2eOX0hTeq1NRrDrlQoAO/IFfR66VN5idHJeW8+uoO6uS2DcylTz7gMvLEvOEkseAJICauTDmtp9/kTzfSVF+n/eUvhTMbLfumbKNDI1txKX2XEPCZOa3sb8fmtduQzEjw7DzOLCBU8EpUW835rgXl3arQYV/WqJlcQprTPlYmFAZn5w5ggeMxfwDYxluu33J+UP6hbtw20Quqxt+vhusSoyncnF8msI97byUeam0OG9G9ceWsLMnugxXF30ePG762/TO7cDsZ7Iib7ZWeWWNg/6O/5dMFURuyXpPhgiMOIWwToy+jgE+muREKBdOpz3qYn/gsFCLbbXghvn8XxS0uM93tSPy/QVG5OpxQLCqtToCIaVrT5V3Dq2/w42zsH3Yto17J0ug59t//NqnuKFuzZE1N05kNeA3qU2YNAXQb00ow6M3XD3iqlDWqxvOmUz4q+pRZq78GOS0Bh4L6b9azHtHZS6uMhJ7rnYe1V4MrrHuvNjKpKJ4WXTfSa/WzRNu2r6fRM86ddgFm+TPVqZ7lNh0M7ohj5pcZQOH7XwDiTQdxCuQbdCNwWlk4QiaENFS9VhksVjn1kLntrGkFmtfpPK4HRcnVzfIDzQ2NAG8RaZGa0PuPGEC17UGNOMGtUZd5g518QzcQQDd7xD7xN6nvDP4I/S53waG8tqcBCvlfUBNB62q/a8vdtV1NVvlgUC0Mmd7zYymIqKVjRnh+uLn4Tj0eITwoADu6b2gvDsrlg8+aKJF/zj/sec4dWlj+y9vCrG6knHD5Kf8dJFMqScSh3dh0xeSVVeMRTzgm2E8m6UStBJxUFrTT6wv2sDNS/ztCv48yb8MBqj/Jbex+ek/txZOtM7QMWdtXIOqJ6a2pOvC4yxJeXHBSuQnV4GWZ5fN4GKF9ur2Uxi0l+4d6SLjZ/vbbokqzA2Jin8u4xGK68Y/37sHphX2qKF0jQaWs8/2ticnz25aBwsUKch2NWe80r4+bIWeqV2xCtdoD59Vcda5Ke1I3Ihxn7gc9L48+a9IM7QF2ZyK1A155FTjfQNDrxDGcotOjve8DX23CN7RmfFLW9rDtMRNZKMASNH9D7hyCd84qdRZ9qvflZtTaZm7qaTdGg85E26210nraQZm2aR+o7FF8Z+hJuxrzruRZ4QBsyZ9kJFj7DmiQshvq7t/NTdluGNU8c/5Mnocm+t95JajAPtsew22MXDa1W6o1gB/dkZzxXzzSXeGAjBSNdk2pexLa2qLzjVYQfO1+eKyEITztNPJY0EiaPppFSBjHq2Pm5VJYhutcEoEYaKPD2nyEpwXEBrMRjm14q3KxrYzzvQywsodz9xlqxrek+Z1j4jIXew42wUiVju+3Pw/STy9VgFAvUJmEVvN74sAVNtnW9NB+mP/uilF6hPwCx66aWXXsBe9EIw9AJm0UsvvfRyBOTKlmXTLO7TC3hWBXhWBXhOBLgNueQo1kxubRrn7/OlFV/ay43oVqmS8NMibZbDIP4BgYdsYEAhxWnTX/Hf+00YB+xofh3MePg4wLF9qy8auHCWIDbDDzOuOmYczJ89C1PdC56ugpt22H/ryVsyih36Vqs4vhNpHv/Ayhh1m/CclIl2fQtp+gd67Jqut3jHd2h9wDOfMAzD8KKxoXLExAnFCxor7v0ekS5cbbuewk9CLTGjztUTNB52rOP917u9M0d045lDY0dUjg1OsWEbN7dTynTkIJwQNFdzzyJIMIZu4pp5Cq+/pGL8+L6R0eiUBn3GIKnuusPN9KRBcgNMpEBjYmuO7wvMmBcomvu6mHHngoZGGjLLg+2r+fbMk3nQOM5pbx5GYNE4UdnZ8XKPELm53ycMuXjI/1ika9J2QiiSBRnAYfJ6bV+XEc3khkdFa1gyVsIEuabSBZF72LNi1z4xl/iCgqFHQhTLTBKnYT5HRixtuD1vYxXQTmc2jPoS3NKUBxtPoGd8Z2zCTnbMFkMNLWJzaO2AQczuUFyaEDmfUm8Rb7lOFNmemLRMWhYP7Rkg4/NQUGtkQWuoymzNjMoeRgyxOkM4LQ7tXJlPzgtlBZTUyXFRHNt5MSU/F6d2/pqB34qLdu7MzAfUoR3MYapoBGT2pALX84RpFG4uxNjUiTY41zTWYf19jgQy3OEtR8WBsy/hLFWoi6m++qLdBCFGIEtgupEX4rGLUOnL3KgcuGpnDumU1vnQgPgC5FVvUVhqtM+oxIEHLHbosjS95myaVP6ssWSr6jzzsu5hBA4hp3mTNHXEiuMBc1Jc7EmUW0pcprxlqbIdgJMcpqc9pWGqHOQjHwTlOe0yhw4ISYH2Dft3RnL7Yft0mGKGczBg9CqXCwFfxmN92df9DcZK7qblD5LaAHGT551AsCO5ikBmKZ2FlOtqKHLY0wkXVX0F41vZbRmUFo5jsmVT4w6wB32DC4HSJSlEi4oJAHaQhxSHdq7MJxeFsgJK6uT4uTi282JKfitO7fw1Ax+Ki3buzIy9yVBBKrpy+Cib4hoZSStvjfSzAEthK/J862Kx7VPV7lM9qSfQWkv+GR13Jn7OULWNVhxL5HITQr0vhNngSfDCUgOGICsRxAJqQ1AHeouBbUX10AszZ0ze936zR3Sj2fA8TYszKMEtqSSFxQnSQYAHgT9XaTx1V8wIiRYrPacEs1plexFQ/Y+7D8wKsxEkUaej6Pj+c7L6VDp9kz6/4BVkCwvyD9Mtwx0cd88Wd4ItWytrEX49SZrY94/AmbdE0sJLbNbonBqVN+qNtczq7lPeHbcLGjHzADkDuhGjxHd0XVKA6NvLUA1QG3lOe94V5mAqY4ybM2Mv0lpVQFmCrcapuL6Kp08BnUxES1PM84JqCCJs1RSishk/ksF0qgtzuhQH4N/4W7sJlu33rc2Rjae0cRpld3FT978zgkXwhRODXr8s1kpok+bA0Cpng5KgqrNUYlT+aCXBRQay2y+3iiCnmNLfPLX8ANlGROhbzkBMZqp+L92oZQzi+dX1IZY0+9RVRdJ4yjJFuEgPsmqhKevRDL8QUqANDznxSV0qfA8BCAQhA/iQYxSHcSha7WTyqqEX8EDBDgTVyWeL2icSbtwgx7KQNjZynxNpyOiY80azL3hpB0UQs03uv0GcSmu9KvJisg64UFH0jJR+zgBHzqsBhVnb1RTOK7sZXvNWzl01KeoTFgJVrIWuG8ECESRvhsB8K9KSjQbzg5LLdPXDbdyEeWJTnaqTjDnpSXVg1ddNHZSAcz/M0MrVUnyvSayu2LxpEtr7wjYD0Q5bvUOBjS331HQP0BerRwVgtsFcGS0t7nmmAHwNcy/YCZ4COqCex1lJihg+sZeVoUcXGhHvU61FnYGPW3dNXTbZdMCv6sQ4aUaRD/cDEZCBeYzofB6NmFwKVSz0wb5T6FDoomA3h1H9ZYpJg9EuMKFMsX2X+I8dKT90PgSmFZGoGxG+g6aKymx9fCGoLKaRAzH9zKBerOGC1KOsp1Nf6ndhxuPlpVxYrc+2wBncdZXmbiQmPQWce4FMiqAJLfxsrR1bqsBlx+2CLLF0/LBNwX4odmsFzd6c6eAopL4nTHFBwdAtS19uwxK+5hMHxeDXkVQXRnmQ8Cil6UjAK9xcGUkovo5HnUrVMwbzvjdZEBjXlIlSO1fZysuAV4scwO2DQGQsX9GDOwPbXnqxJtEQq0q2GTICotXRTCuewo3JMuKwaFDJcSG92sSHHG9HDviApDotu6Ru3zlTyZlEyFn7ZKW1tc3Cy89ob5BIFdafLAGxaNF9RCxYavJFd0Ewi8hpgcCE9oWpC2VitnD0YeUt2celrNhZI3TevPFgA2PmMlGJBREWQYqRe1xkHnXweyhxEUjs7R4KXIikgbG8HEoXpbHi0mVHDuwhUSJLQy5MhsA+TaDV/QVaXHLUwntilCQO1vRb+XBy9dmhJWq/gUbigL0AhG8Pb95+bXBLYgqypi3Cg1FnxEKTNl2NgBb8n/61SyYH7EQYnM7mNhbT/WSqMUWYmgErox2GvR60+GpWV69zneWOVXsUSApnr0qN3VIrin8qT97LSY9OK0WBBxSwuGU0//BTqufjHGsAOwJ8IsqrdhCjj4djdctlpCCU8Twn2u9nWuBwSb8xxdYFRm5Ll6unodOt2BorTUIqc1yoOd51vxMZ/WeeBqm9mtfiOf94qOrd+xH6FgeikZNOtSFXsVDl5xJ+He7angXNf7v+13RL8fPI9XJUvf/JZ6/Jku6TXve8J5flam+R/x6u6nIraBLdjDJjO7PMSlwFCMyIrxcyI80KBPgknv+MiJATqHLIggzPfby4SMqas8hExTo/xUD55XY/gWxARE9TnJEkNPVeK7O0xHWCBMdPPwDKLv/ti8YBpxst/v2+jNjetfa4+u/f0/tNfz+oOPz+Fj63Mv9zdHX6v9qTs3jPFXnGIDLnNFM2ZJo/t9ytsKVfjK5GxAsORVIU27yzz2Dj9duShl+koNneQhnp0X6WruzCsfYemdWkiS4m3MPCWInTLiAeclBiEQOFfPp0O8KFO+9GuAZf3hpKgE1yWqhgtMH0YyUFy4BTE5ivP2RK7GdNMQBKSRNaVNkf0YP3BoW5aJFGz8FsC/MYbHBYQD0ae4GhaNYPSLcGExd1oZH80raauqOjuLAubp/kMCv8CYCCl3eiMFRYDblamPqol0C57ybDiAzQ3/aAm7+hMNFs3eIYqYjN2HlORWu0PvJZYf1eoID98XShe6AkPADn4NRXw3n6qPR5qsimqcdhuFhNl2tTwiRcvtkqiBgFl6obDFJCGTwzV2PziATab3rKx9a/JzY1PVL9G0qa9rulYwALqz3YXVlA3gozcYWP9YLSkTRMiMZDx0dt8LJhYsF5pMBBNhILJ9vBXgKVoyheRYKXWOrd9dQG+P7pQ2bRxB4ephvE54jtcw4VKyenaq1AsWeJOqaokhZnkMw49AJb/yKqJn65w4KQ7bmaBEmimDwgiJXBLtUiQeSlgo6u9UmfCXaJPBte1nupEE7FdaAYpflmgaED/fEbRCTPSNy7siqchC9mDHGakKqVp6vhkqG9V/Uq9ayTBe2qaMzM9054EzQA6qszpNd93eGN2zKit7RKtLkkEF5NmXy403DTQju//AVATcxoO6UdDheQtA6zmzDXHlpjs9G7Y0JaNzuyQkBmjKFsi+JS9049EpfEPo4pNNNTqfAPK1Cky+nsGqv2NxP7UWCLuAjgg90BvQA7RaJWRXuCx5ocJReCtIhurSZniQHsI1zWalB6FSRIYB+QcPLWxVIEcJ9F8S0Hn212wVrw+E3KFslIhN0v2cCmGqN2vpJQTh1fFn9+hcnCcG3ThMNFIv/WtHLcf+qhJ7Wm/3esWZKknQK0WTlLD+yQtppplzYOWF1ubvYlsiJdWSfnx2BrDX+vwxATLmJrn5QL0aCX/zUiqwhlIyAaH2v6YXCclxnQhhgv4gSOYQabcAbdoaygU+UwHlJYmDxYcoiFySMQptjS7/hcKKhEZGwNQHguOAfUlgvudSZS2K3LFjlOf4ISoBC8jLHzxYu6ZnTJ8nzbBDxB8eCB3HJnfipl0cO0vF/fbADGjJqQmsr/KbgZvISvb+aRVqe1BKI/ZuW+VZ9RR15yYp+MlfbuNm/LFjufRM0CCelnRKaXS16YYEgT3QncTVhiIiRzKSiKKuWhjG+TtRhzScSOwSE2OyX/xQd6qauSPgYH9Of0eYedO5Opdwcz7nwcmQP0yhKOBaUAHn7F5BPxN+KJxRz22gJjGqA0qD9u0ZmhnwgPE/OWRykavVTJSo81MQDV0hIdWjQvyPAe4ayo9f+R+slKwTMW5+3pHF2Coj1FibLJaR/8v3OKaB4nC3RTBZLXUE8HkaQ2Rp3d2ALhkpAYYLyb98NrI3OifAbFFyJkh0QEVLZz2O6K2OoQ2e3Tgm2SNnyy8Rj9f2islVIj7yKK3RB/uvwfkiTdxPRd7PowEw34Z93E555YFvY1GNeLcVxy680JYcoQ5pBKMjJb9xocqXx+9onJTiOZH6zqz/VYXMehBculYeIZa3u0mIM4vv2Wl/q+77BzvfQIT8sAmkCfwgCy61hlADCM1XI2KRHbOiHbotu+K2mNDUNAbhlmZkGexZxp/N/jKDKvk1I7kduoMFmMg9eSuUQZbUE/Q8tMmuGKNMzQ+I8YnahNFf8Me7+kJNz12GFkTQDnA5mdJaHecTJL4TShl7OhwaIcmjLa+TbZeZO9vvQEFUwzQipNVtLAmnD0PWv0myXoXekwN4QHHi/qRKsVgVaNv+/gu7GzX2uuleYn/KAmckqejSpW/nGI4APeKgWLuQak73qbSNF2LMhhthHrRj10s74YTzrD03TrmtHgTvWNG925HWriAu95nHHXzumVV8sQW/drI/rp9ysFNYah2rFvK0lUAox4cT3r8mVHcO5szJT9B4j87jQ3Lz+MJ5ztFCdMkr63wj6AtFbhPbcPynunCeVWhwXaJUb4wArjte8jhLSXTDUPrZ5ygmA4qXIb4H5nA1wiKVAUbiosm1/FGDYoZXt+sHEr5asUbk4vMUFMr6f0BJjC0lJSocEA6QtH9hsAU8IxPNnOXWGn30XHTSGCa3cwZrt3ylk7YWsVMjzvXTnG7MqryEAz9R4aTAEBwxVuD2p67IhhyCKSdoZ3BQ8bPaEnY5ERNv0eOCN4M/Ux/ndEP4ANuoe5sgWO5Ol6ZPvLzjbsUI0IeN9ix9OarwJXoUMqDzfKw3FKbxfwd4pF4Hyg8DNkq0aTGcDzT6yeSjVgYEhjA8Bt2Ja1DxdtA9Dyo6xTS+qwLggcGTfAXSYOhWoM/sdB9ceVcb0yR5Lfnkk7J0R4wg7ojhk30v0mVm/Z8OuqVEUyq3AGBG6a1EzMzcZAs+kqNM4DCgyxEv3CFNIRmr9ufyVwdPYSU5uR5CkoJDE/bBvyXgORRe6tYCVsWBUmeBlsngceK04BRpBoWazHIa2ewPwoNjfoW90HGaqARVhGJdiTPFyqLIGeAplZlbXyPROWh5g0LWEMAxtwKewRNpGLYAVMTkjFiOk4d+RO3azjsMyFxnfhH8CnMPMBZ7kfHEJYhQGom927fr3EtslAB0e5rtIEYS33Es8GPHt38sQElWGOg2gDTiBq58YLgAbZa3D3NiZzXwix5t46H0cqoqMvQrHm6ECMjUH6GBCLnKRzjwfx0X/62nhU9fzflnRzB7cOGEu0qMEYaBQXGeVAECyREHZAcbI5JUko1m6QYR0mvuU573TgqyMPpg6BWo1g75eRneNOe/eNJzSU5wgmt9pKZCZFy5IQVZsVO1IapTS7jOmmOXOvyw0tuWKp2mJmI9khHOsr3Z+u5lTzXaR7RdxqFlbYgfbKlPa6W4lPrM5lAH1EkX3e8jkQl+/EILVg/nvYWYddswlzj6JSqaNpp0dNo3YkoFTHVYh7dye4FIx0D5dxcnAntYKfhvKSzy0p6C7ZOeB7r4F4Ku4LgKqHkBJQPAGF5ET3Hb/PAbJBR0RkoGI29thvNGRHnJqNc8hZRp2EoKtE302X59myfA/L51SBok5ZQOTBngwtnHZjcPsx8tdJYdbsgHG6fTLaE3/gzj7/szld1boZTCDr059Xt8CALKhq1NJOD6NR3ksQU34DcIDEwu2kc38hbBjH0Nj1wVjRxsh1amaitcxtwlvBworhtTQiIdNDG/QuE77bsDmMwkkkML1GViER4Rcmev2mIoYj9wiIBqFyym9kuWRZgG6B0yLR67pFkdNE1LFO7IP3ruJNQZOZTObkXEXZnxT7m0mstBmXvY8btHa4si+rftZONUN5LQ4OISU69YFLE8yA+RU1cF3dsag/LwntQJcEgxzMXHacbau6j0w+dxd/9E4BzKJaVKWTM1wqKoXgKZoLrJS2show1npI/H/YhNYzNmaC4LnDDVnwZkxsWSenfvCHQOPj9Re571yRsWTPrhtU8ypG18jz1gLjZoWdst72Tkr9pirjbyt+jIqC6Uz9AV59SSBzxT+9EKlG/eRzHQmKF1GMIJSXoD1Ustpzv7i85kn3mJTyIih1ZDo2E/XZsOqqoFzJlkjQDQOnt1lINhpqBkaLpO4k2Ny/SXkqZvwJkXzL1kxk7tJF5zPSC9+hX2j8FSk57LTJ7ZRsZc2V6g7MaEBn7BzBOWDVDkDeNhjU3aiLuyCBmNMVxmH9dVWKtKqZb2mNTU7f2hIIP1PMx+mwCMOVcJfl8mt7NS3FukK68L1/eFcIFneGfShkMWy86KMOsdRZo/tQSChnBTbV+O5Xhu1HbgbT2gpCrCJNJuOwcN8WniZPQxBdf++c/biuEgv1yTMtQNaEYhJ762XVMlezR7O3+r2IwlnJhOMGSoyUuyj0Geu7Qo3FYIQPg+ENMzeDvo2o1QNA/8xLGctSrPZO1JFl0FAkvlaWeyQsR1NubSU4FrtKAndrfJN5TvDiLpjk4zoSTBUQMZTyiTotgYDm2P9MGrzaBjUAmPOhmcTwNyF2WtDkrItBoBhKVfFeGF7htmoRDNQ0rktFBWy4qHblWXmvCuG7sUaOr5j3xQckY40AUjVFFNpRHhQqmBJBwlyVrVNTprQN3tYxTyPGiYfJRvVYSOfkAidNvHHj/SJE2VqxEUHwF/Sde/pE9PkB53+I8XRSXiFmvhFfJk6cu4aJThDclACA5ygdi9SMr/K0+ue7RruovGA9F9hbhIIkbx31Ri6DNTDCSQlw5nfoFW5BdISAnGtk1AbGfxU2WqB9sk1oqv8jHcms1EeX+E4xTXLYoDwncCdLqR+rknN8YMUB4u6usHifyJoZ0NCI+0mRaEs4WNze9gWBzU4sJDBuxSxfEwGIHxOVd8pAQ3ZJpkqPai0ECDjGiruTm0bQBr0uV/aFJUnBkyDuLX4uFoepBI/j65QivbW0qNa0wyUHoC0B7hY2mLBX7hN8mXgCwxrId+lzsNe2zn1iYfKFBdUbF+pnezx1A1CCM4JXG5GNKarzqGPw9G34bSOnYbM+3xOwYj8BgR74QEYGjAEUVGbLCJ47geJveyj+nj0kmqtT8pAsbZzjlapCzPFC3PQJEGXJBRnjQOEpNwyAObhZiyYPuz4NY2/B1QDPR3J/M46G+KOKYbC+H7nzxUkWvwtZymasHgBhbMmRHYx1PA1QTx7UTWXWCKMYd3k3ttZvRBtmqOQ7YvyR+XyPq/8yA7+HQneva/aNBICvTHwxuUcutguxFu4WAfyAHCiogb6e9QLQQcvba1MaMd6Yni+SVT8vaecWCHY5FlLK/QUwXf7WDDJCLzGsr0HYBxo8plSI8M4PL/01olkvGMD0MVBYgM47gn/WI3of0kPm3tpXX9QdjtU0hNj+vi2/y81vNNo4OtPGxWTusBNVeaOg4jD5Djn/53/1SYc7TTeyrDo/pNeAbxSflqmo+MDnoE0iFanEhBhtfgEoUtG9p/GWK3IP7T4Mxo7VUdzp8VUcSWBb8bYCZZhXgViduB7jOxfIb/y7F6eBrBC6E4mW5oKfK41oLwIY14UUvlCtR/FedPUp1I8cFdVHFeowhzpXiekrAnvfqqnNG/7ll2JQgZsONE03bxr8U+u5xz/1dQmExRker060frT8Nv6MzjkwWVPet8Zq8hEfLaudPxssDmEJFO9OUYBfaCikDzj1pH7WQF+r56ntzP08lKSXrIetXTV+2zF4rM3WaNO1fjtoXQnHOrWbKQ8tVMcP/D1yBVC5lQn8Gf0xJvJk5MfONhidyxEg0TsrawtRzJ3i4euvjI22BJF8xlLQXdL/Ne0uH0xQn9vEIepYl92WXC0Wbb+Tp9Uo0ZXvy8n+Jsa6+i8yKelWTimma8h0dNObq8tjdgrhpoZKVLCzJybHwMgwvrfu0UHkmL2riZosFAg4fh0GoAL8dI8H5NHb+GP+s+FP3N5Xq28/ev9Qf+KT+y3N00jZXlC17MEk0bdeD3KQAEIjdoHtS7PFaZYCpvVgpOQWVOGEGpbC7srAjGktIMUNOQe8VhzJSHbBg0E4i3bI0bzOpFQpBaqHDXSBc9oTwZo+Y5dtGgoiNq1+rxnlRVW+T2riAwelrRi8B4/rUcp3Ez8MCSKfFB6TW20yvJ6tXjJ0LCledsT9WsIid7vAZxs0hy0YMmAc3H8vb6uMffMCfPQvLthdrRTnN1iZGcPhdxJnlpt9kwWA1U+6RchD4ygxGg7eKCDgmmteLbYAGZ3l5fP5D7Ym2rWkiONP6ePyxI450+IF7GDdePLYRXhV8omvnrKNgR+8ABJlQn7hKWKY7p0F7VLnkoXao+iXZEaWHaZm9nDYoSej4Kby4VDYI0vr1E6O3i3BzLO81b5T9KskUIg9/DE770BqFuccDJQCvF93yjtyhCA/0TcvQCdUwPRHeEBOFpSW57jCfminreRQfnAebthmxCPo8gGy9FoTu2J7jqwgYc0IIWggnEsDDdruEmWdz0FctECPtbUj0qsP2lgdQpNUFHBiFnfi7CmUqmlgFSybjtp7rFtiOEcsSZORCCaRmAsunB8VFZnIw/uTjI7KuUaEQ8O6c27n43vaH3qshhq/JJZEy9vxkEukbk4YdB1pSZNMaCAG98U847qyKFG3cGlFjWhnb5pBhBp8crOSpBNVqN3rufCcCoTCQBA/ecT9PeuxoPeeRtcc0OXZPTeY4YIePBCM+QCxUEN6qoG977y3P2fpR9hPjjPZ+bWZizaDTc7B/h2g8/LaKdpg1Eq3pG74nITMnb/Ljgdqv9fGfpKTz5II44g9SuL3LYyg0D/+IMhpjCSO83KL/0YK0owdojwkiCQXuBd9MtF+vyBDjT83s/n2ywk74FStjaUEu/8JmDEn8eTox4QE9Tuz8wh1m+G/CzhTHTjydy25OWHxHWc/OQaHUHwlGfRRcz8l/gPj05gQcQC/kD2ruwfUq6STC/8eMscXOcnUDuzXe3Jao7UvHQSVTpc8whXwhXp4sxQLLC0ZJWtkkH15aG573kJ5CQm1wuaoIAU2VUTiODcGIdb93jve8J8D29XQ15VyS21u80Gm7Z5li2t3Tkgmp0gHZaTDiCt85UH3X+/hcCTc+N/pw7Udrmu2yyhJSd7GLR+SNLR1h0A/XgvLuiAGZQqsPzvUNkMJNnb2thcUdNGYDnMRpT7iz1gGI72G9QQ7T3emenOuc2CmVR5LTG4eiHFbAl/bPEI2SJAiTBPp4RaNml1F2y8W/tvpn3eJrI5QNCu11bZFxjWE5bpo/uRaGIj1WaQdrNMZWfHAVy49euuwfG6YqUePP/L6J0e34Hxv9+5P9BKRwcqJOxL8QVqZsrImtvQugjLFdZvgdCXDNpJ6H+tpI+1NiCAefiRjPlxNh/jYGfsJ6bLHgtxFuyPG3UncUKTL6Ge4zyP2AFiFNSE4r3ivuNR6i0rZHR5nPGkIA4O9EzlnFzV2fgr6HdOKm1SFefsMx9Q6/MOZ0pN8YHcwKlhVM4ADzSXWIbDW9DbFTtjmolshfAHn1J3Z5XNlpEKPppSp54JOKSpyZHDZO0r6nkPl5d9o4LOPpPIjkxaYlAOg0pxNcXNSlT03w7n+I7a2YZZZHuOKdUJslnVypY592LJXRMUHrdE8kn94QjfBQFe+yuPm0NCGFI1JkqNU5LZii+tLpwnnbC2fcvVLEFieg30m4F7sCVRwsD71ModjfsYVcRGuvC5OjzNSu/UdXryT1XYS2BkDCDQDlFiSUBVADLlCICwhxz9kqR4p8T7UUn9rej2Hay6CFT/MKOOdPwiyNE0eiMjyi0/SLebZ9Vc5/wSt95dfJFhVygoriEpfVbZvMqCZmCrC+k2qyVCTYxRCeVC9DOCKH1QzNisO/CUjJeOurBxYcFzMbibOg06fq40GNcvaNmdUqVQ9S4N3F/ZMWOjUAqvclM9YwgjpR5A0aSJUlUKW5qjJYi5xUM/qrdhOnVlUxgzRY+mggwFGept707ZHXaVx9LT5kqtFsFulrK3ek/RYQpxN7fErT7/cJirOtyOGEDhtSDs3fnFvkn0ZlDsS9qopgcHJ/ngvrRZ+VP5eh84TqzHYCvRBeA5CGrZNC/KjMKwrfJYvUlBu0UHTrA7hg7yZduYRXd9HhTRHN5gtuNjLHpsbkBy714+jeZqmZF6ihkCy63dqdRdfKJVJzu4MjSP/afc+YZQaNv08bkyZ7b2ndG3VS8tHkT27vyHYoaB01QT0eG1okG9Q2G36Tg84vVf4w82FpIg7oy3Lan/tyO+sji51p6iU7UKOWjulqrQn8qM79/lWOylu5WzGru5o9Ky4Q4pkosZ9mK5ZyTcgrP88QFOXg+mv0wn3bjsWpi02o0/u+oD3o7MEauOunMAFGJVy/41T/B93NTvOfPurKbAekwrf1dUMWhH1NOHKRbEKjwe/8EkLHMH3Yy0MzLaLjeBOPueOpbZdeaVdy53XusvTuwrf3XW/0f9zHF/cWdDgECNXbb7bal/GeLA7dXwfKl+mWOVYsvU5UVnmQO+ciUNbhZrbo+EO9JH5fhG8FS+WEHR/PVqj1MNd2zlu2J7+ppLWlrzOl4Mbk+XKWPhWLgh02wjZhBilstr7LzLzlbc1C7q6Bd312vM1Fn5fXFJg5Te+WZLuZl2omH0r/HraBecMUBjVI5yit12QoKWGFhzkex0CCBQ4glqxTtYHP2E0WJjWn89U2d/jdC68ldtIDDhPVRomJ+VBEEsSV1pcfHjTqKbG/HtoNofR8WaJvbadyfduJZBKBdXw9SKujzrGFuwn1RpZxSdMs/ZZbzOICr+86w3E2KnXlxL+ZkgqjH1vqUhB1ZfUKr7zVKu491G7imGyIln0ISHkbi2xSxqzN8trq/+78VxDlcs4NYkBPmQoiNAeGi0OR8/Rf9sJmhJYji9pF+2QxhXALFn4IEGP6YudV27SvOD8hIh3hLHUKfy5pYMSKRuVUFQlH+8bD5lErhNgNmlD/kZeSJ6iwJHnOTNSiZ4nwzW17Zq5n2DEGTMVvsvry0Qc0+zwZdJ4VoGh1VvQfDWjIukkikpeWrMayTDOlZNeIn6C03QTdT5C7dyJ5aOpu2Tm5QSDZ2QVvrtL57RAez4uU19Fm7vubUIY4RrTUzjCEzAiR1VsQHXQZ49RGX+9UVVAQqrJG99e43zwe80Xs0OK7WrHn4dJqKA+oiN//Wg1GPmhQuf447c26Ynp8vZ+Q8+vIogvhPzh2I8qK7Y9uNxSp83DzByGY0Lwf9Oq70kmTm1CTrS+efkrFSGflNZKexahXk3nX2bNnL4fQx7kSK7lp3D5m9umrMMxP0kKIQLiiMmp/FdyrPl3gs386n9ZW4eHnCcKKL8btw16Eas6x3dehWeR1rvyAe7qVAEsjsKctzV47nJXGwCY2f2oBA0b+9ei2CGyBCJUJHMgT6snXOPIGdsIEOY5wfoZgW0C8iq6HpngmunhZAJMLE/YBmrdNdyzNsM3qHJwpOP8GoWFKNDShCYTvWz+KQuM39sbk22ThlUnUoHDN46iiwcRI6qxPKnHCl7DmHRu2YVnaxT89zvFPOjmsMU9fIleIu0q4w2CQWnwx1vz5yeihHfVMjIcYHQnQkn95OCiPtusK/Nn4HtQsgE5jCRCXNEz6MYzxhTp0c/n/QU22aOG7wUZ+USyHJHPZIMdhI6d0Hwn/0pokD000239GAKcnohyBz/wgJ+XU/mYHjdt6X9mvGQG2AUY3qUpVc8cIEBs0FKn9qhbI+eyJE5vGxflonbHGxFe8fio4GM2aaul+g9s6neYl3DPzIG0pkXpCyZWX7KG6CKxvrdIuof8w2C5nT0vreGrC5ibyOuSTz7SUGb/PI1WjqJIFI/qjs6PMtu5e2PcPNcn0nFuAs3jmdY/Q+56QR8Ag8Ih04PzFFAaAjvXyTJ1H4ZVyZLj4fDVYRJItG+alEyeXtpiyjT45p14FhQFCzLF8CvkoMNUG1dK57ylpI+9zDRWmMiuEUzf4EiiN0bSJWHlqnhGHLNvo8FOqnPw7BBaFGsbJo0s257qMQgvxPmZAKLBIzFs9wAVSknoMOwr0LvGRBGR7z3Bj3BJwAfb8zkxNACkccAFQgbo1OZK4J9mJDBdBLnZlN7X9ebfhfTm66UhqY1cqUkKVypSiKXCl2Iei13KCIYzqIwAQOwJQfsFiLyo9KcFJMyq0zHAw2kyFD39BpDDRAFuCfCMv1nAifwX4T0AY4k07sCgEGaIvpZsVgHFpr083gKw9+rr7nv8/qJyfzhWFws/XPbpLkZpZ5op9Y63Qd62KzeHb4YiOp7wqR98IrAeh4d5MMwmymAqlEhE29XceKEBSLqu7+8u/3w60y6fafE/rNoVTQWm4tCPdAE2aMwHMDpWcDiP0OpfKOFJ9/qvUPjI4S0+/D8Ja0IWPiWsc8Uq/GUKYRMRMdUfMwoylHdRou7rwzUqpqjZRIN4V7fXuGcKYxMtUrqxGumYaklm6PTd403RiQv2q4lqQqry5/5CQMvsrzeqaytDa//Y+qB579GVo0sn7/TeGhi48teQuVvAq6wvMmaKxmM0TP+xCPhPQUGpSiPN68sR5gRPbjsd+THfOsLfv6y6FBm4148emIIYw3EMh4WjDUcdEVVEaERkESHBcDAorH+paURdprS5e/5XX4lQfyRyMYpm6Fnnc76aXVG+0/5LR/MP9yFP6tLBjdrBkjqETK73qIRj/0cKzD+3cAxGZPBBHPj9Vyc69l8++J9fw6BzfDFPs3HwXz7wD2uW/s+WqTVTFz7eSwnOuj60MTwm/F8+2n8Uqqkc6w4USbJWUNG2JrlFJn9kMxB8xSM3E6HIVMjL5+8e1v2Q1LE2fUGMFOfZt4e6TE3r//KBcb3qmFpNWOBf7qmLf4WwOkjolbHlCIgwlpr1WLO2NdmxCWici0d7nmCBnDmmlY6sJ53rttY8xu91s5osOK/h+C/Ow+L1ZlTHv8aB9KMiHsEsMvMNjbv+XiHqW+5Wg+Nb0g2avaoTOO2yomXJV7pwSsf9kPfWVb6DwNt3QWca3/gYs8Y5Sdlw3yyywQ27IzZ6ZyBPFDSODN0mRB0LwPhzadR3JZ7FqOvjSPcYLuUklPIWf00C3uZzfctdJTkSM31bu05CeMHuAZvEOZkIN2AAqW/j17QEJaV164uBJX5chqEXre65X7JNUCKDUq/77VOFxexdfqWii4pJnzzBn3++7Kgcs4zUkggzHI6O0jhWqNWGVoH2oxUWKy2K1OuTt6v/DWtLtgSqDKvbn3nEfAj6xwtpqJg7VBCjAPwgSxiQCvhlR9omY92xPL/ux0jNJc+gDGQW64z0Zf+TSIpg2Y831FAEhWsMhblenoiRMBcVROuEDk3F/isNnQCAp8F2j9oygQ9AdspwddIsCtBXw/mD8kGFDS27wpxvvhLOjN44ffGg8wZ8HoKPc1U0iOhZ+NqaNv6pJ/w1jSw6f1fAsb9pHrNSNz0eHpkW7jxKr/UnwY0b1a4wd3lmDybRuI4jj7Iovuqals4bhERHkah061nh9dEje6/R60UaVt/IWMurmdfYq3amdFdIp6R0W9rq9pSn8j/6+jKgoW74e2UWcsEQ9FAOipltqfJmL0m7JJhL1hkQm138olzstJzR1NRJTPXJnhp1aq/AtWxcGYsxcD/xlH7KQMlYYhnmgNiJZRWK4NKo3RFr/tylcodVR8IXEuQ1cdtKTzOPp8q0KnfN9RwgxEE/1FUVbtyOx/dlvReOmxsRPZoQzyLq08lTAkPeNSqLN/j+LAg7+FE1+KjUSEdtrpA6V7hpoAT6zhMlFw3004XWAxSmEV2CcO6j6kCdqBlfWLsAxUTObX27+8XxHhN9Vj/zocvvrIS3lXRTtZdH5vIQmpTM7enIGPtj8jDtUmgO64XuqGAgCR9/0LrESg9sYjDYVoaGrwWDD7rhk0Bd5BB6UukTon+/NXPxETEpinfsIXasmO9CB4soO8qiqpnZUwCmuOl1kCwLs1vTuMhudTo4WbiTgkVNo3pLRNS7fjoKyuVkRFIuNZ8p+Bzqy50NMLBYQqG3BMLb5hXUex3USosl0ggLAVVWSZwsSol4bZ2gy72iQKjKo4BdK6VGPDGxTYJyTzV6CEUdO1QEftEmRJ87Jym6E3VguhqlwcsJF0e/AC+lIJCDdOf7aDjiWF2cOGcOwUSbLKtKu3HINuzX34wD/crZ2teKcWEv2NU28Wh1GPK1WoH7H+r/Zf6U2MxhuKcTuH6WKuTbvOTJWpJrLG6ndD3MMksziwKtLwCRP71JO8Trjn6tCBu5C8SqQ+J+v8zykBOgQTYeO4ooUzZ/9M18zUB9NRy8Hqw7DgufGUHFAF7UcMxsyUOBVadpzRkBcsC7/QGmABy+x73rjmfxGxCfvdIOjw5NWiZ+ToY6hyvDHQWcrUOS0cEhwX8LXzElhCvX3grDHYv2kNCh5OgHc6G93DRMpKc3wNyM0I5YRFSWG/+RUKXIm7xJFJ6exrlfhQgpUtD6kqBnbhr2lwNlfpikWc67qiNT97vGqd4tpzMbLdf27PHWNlIIOpsejzAD/waRrwQDSdHgsFKpyoG3VTq8feZk/UQvT92nKmR5a6njBdzIu4QdepHRluefkjHd+TLCNAOMeiW8w/cNlRyMHVai8j+O/fvUjHE+M0gmTubu4pH/QsDMENCyd7Er4O95fnAz1m7Vmn6zZA/ZRATJW6U5PU6//ywhD0LbSCgvktkWWvSXNPSl1n/0uFnwwrs01sVegunEzfJIwUEsC6rPbF5HRNZecXi5XozgoVQ93c6J7nN7sYUjTxXg0xbM/i7Ix/HA3pBHETvB+k5RLDXTQJhxr69M/np3Wlt3wYzr95mE1PNReplduGH4XLqJZZkOSjHnN+qMX/uORlSHu9l8SkGQJ631SeoJVv/WsAVHu1ZXRzDubOmdbxMrvvJGJugqVLrsSp5aBDt3lUJPCshk0qhHKWKYqvUxQ+khMD8I1MpSohoyx8ClnMoFFvsd6YPknGuH1MM7Z/z2Q4VWD6hch2Q/b1PrqJADJ4boeNuDF+opP6aDSMf49lumQhX9YIzGQ1kexkd5vwFRhLb2251Ez2sg3z8QtchIWlIOJ3eFGVTNw48j/vGH87CXpG4QZiqUz26MvDVsEHstQsu0eENQpCPXBXV5RHb4yvWeK0o9G+yHR6o7osGxTI4PadDnQYWnyAallMCP9XXa6Vbnqul+ZoBUJIrI0zxnNPfgaVkBxJCoT/wdmZtIFePEfDSUoYGHTZ3wwASXxHzncpG86N/fTV8pr2dit2jkciFFG6Kzx+DA6uY8sLpppvrKmDDgz9FRADgLtnnkjYIoYC3O0b2+hRvVTJ80wLQkrqtMyU1jxuKYWPvHqnBvKE137AqfePLEWE8AeHeklXQf+iLu2ZyBxvkvvRwSY9+PVlA3H3sen5TSrKyVl2d1eYlJ9f31lIbi/ADADrL9+2WsVOVxp71TVkfJElwDA2P2VMmnrdBxGK5QM2uL/n0KmH3mR6U265a7oMVkQC4lgOCfsZDaFEzbmaGMIieKelhcMf+ZnO1zXNs0qDZsOwmPz2ZdKfVP1udRaBCm6VniteQ57vSpf28kNb0qpm2CpJ9a0fwPWg2VzbSSO9ijlFOG4mSiEWld66x2TYk6gQGXqtKZZJhZqiwyNO7QqpGqforWGZ/oX0+tm5L79EsiMhp+/hEhtfhwFbvxHl90hTop85U8zdNPDoHhOj9t6qib9bG+FBOs7tS/6pNZl1/Qft7OQx5eCdJJI3RY0o89aYhFv0T4MKRh1Rbukp7VnUYNKuQWKuXyd5B3TrebDL/hyvyn9GiH2bmE2WgyavxFJq03VsOjFjXcHF/ztEt4fJlNKof8oze+BYKUd/JZQn7SX0MNZG06b1n4he+t4h9BIfOY9XdE7dCVoeYYdgV7x5qvdqyMaee1Zno4AcFRGhvTle7C7Ptd9eySGqWWYNeq9aj7HHrnN4iTUIs/N8rNeOV0NC65+POCm2XaFrrzJvSdhEEos9j5aTsSl5UdHRrlNfAHVDpukFjGwPJAJvPUG2a7SbRqi2s1EQ7TOHsoyVOdwVQNodot3mysUroZLFh6nS9udz100+c6oTb+iWBqr8678NZIXK8uX8eE2cw4XwChoYMteJCktq9kjfbYoLyHKMzusjUrjquNdV4ItQCku9ogwJqMTn4E3AgdXtRHrP1lmsShUjWbrf+n7C5sjcbVLWW/2VjviEdyQii/ovOA82oyZUOUeMZn13f25GbD6QzuJXeFnXrYcphq7HQ63A5ucLpc+hYJ6XPFWeyakA9G62vwHDLffFXJnWcFP4KCmTgv8Fr2Th7RoiHpZ5tjmXeCTyjsFGuImcVq/z5iF/C2rs9mlWnLZpBKrNBzU6Mg5KEXo1fNvue4f0zf26q5GzHln1Up4cUv7Z10L4ZwsVGx3jB9VmDpREZbyB5tD+d6obSATFO+wYtGkO4rjpMi0VEFnPZvStUhCVg2BFPX1gjTvmsjms9Ga+HCma4L7eb05rpWD4H0jEVzlYunJtq3v/8n2ZLjjFoEDUWcQAJUWrNziHuHd+X8T+UL55MdSU/g4CSWePim0MVoiM/GCGqHFJulknQBlYHJlGco3Q6FWKOhc0herQRrx9zXYMW1hkejo4SeZoUxPuJRKF3b9AwSTVeN5lu2a7zzIoLRlTnXTRnnbtCKmqZ+r7C0aTVXQtIG9rm10RQKZxlmrSzadjSGN0e4MIjFxwic9QMxUXaEDlu+u9STG0gRtAfea+TA0vpH2Djalia0raMpndvVJO6Z0TE8vgrXwyd22G5K4Rg4HLYWHf478/He5XIi7BjtmgV+ikrZfhJU6bDpsLpio8CbgFvLQeYg6uKglxmSyUwrGUgOAM+ivRxvFyowjTLkcc3q4BbDL0Ah+q4asrDUElQsdPLiW7EAaapgCG5nZl303RRmgi2xqyJ89do3NJDUeYv/qiRJnqI/3jzK1n4WAG6e/rTG25ylk4SjOvkHJapn7FXLtPFGx19yu7Qj0tm6G8n6DA/rGKXDpCcF+9HTO0Mzm3ZEm9pwZZlRHS+IKTOS6TPCJqaWVn7EB31yUpkvlY4qcB3uoVxtlUIr5v4uhobOZL7iV19kIfnaEjr+MPcgNu1zF8+ayirObcaftmbhp6Dfm0dx2Gdznh4FM0IuRQIDVgEvIlqtw4MgobzrICJ6ADIm/dTIvvBFcDPWavHWplaZjqGPNQe2wB5L7ODXOfTgRk7MBWMI5PVWQRAg65fu2vqgak6inOTofMBusgbnvbcn01oheQjmCYyJ3VA+5TSCJyZdVE/mEFkaJ2JwdwzGecZpkmNzqvOptDYk+s+XEt0V0A0Kf+FTJTPMnTm2omCfMmuXKxmLPMV/twt9S+6gI2Oo0n+TtaJxAZsX5xTg5ATdn7W4RY2Sm5UoHu/oC2MfNWqVCsWRPc8PD1I+tMEN1jYXxg52A4hghTLhN8Yh/yhJ+hEPggvx9KjYbsWGVHpiGscNR+Jg9nOkHS3HmaNUROb4swtMI2F3qHvN2V0xa8MymT/CaY5i5rY8vK2x1EuGlFd5cD1SrsNHR8Mv+ilqBZc9B6MQ7X9V8ZYm/iCDDkMbCiiGsIHbwc1ogKThobH+EYuMp2dslk5mIt99OBUaZFtx9uNr2XrbTqtePQuFZMYyJSvlDh2UsvyBo2SWS7mYT+3JY3GJD6eWMh393C9j1MVZFoTdbOVJ6Gv3+P7IGT6+0KWl0F851k0hfU2cWhmnUeRSRIVk26HWy82sen8qxqD6HdE96jQYgJQDNzRS91e5gFuwBlWXx3uIqzGyq24q38RUoysqPZPWnsKBuZv9NJkuWuv3X0HaL/pu7qsGbWsfgIA03Kq3Jc2p1HRCCfZ+RU0Lu8l07WlSh0GH3eLICmb94PF3SN5hfLKGtdBbpa6PNtQWGYPgKZ1xMnV4+2m08Ett+Wca1CBq+5M2uM38Asu/MjFNdmP0icqeBz98tgYGWbzdpEQk0zaGJwkYiuIykv2y1OMC7yndieAXdrtdOloS6/uUacGlnDTMrq5Oxs1kEknyprcJBKSa1tK2ZXc0HgZ0tKZ+x936M+6bbiIUO4rlFDgVMiVNI4tUOAqM2LQy6oD58b4PQNufxbHWeLs31n8QKT0sTpQxexiB+3f0bPpzmqiN6eW7C61KFExu+nmlGHXt9Yh7nH9dyoZt7diuYE0EmW1tK+yOXFHnRrGVyjEnpqbNsQmisz1jR50K+WdReiNuBSCKhwYLvJVDFzTGO11AgJz1K3l4s+eqHXei4FzkEyRTOvUNTDbCwyuZZB6Y3/b3Y8jdzLmAZN1D2U5u3XSTNX2wzjRQI0ewhH4BO0//0p76I+MM8G96aj2yPFTeQ+nxm9H8w4bJ1Rh1EvLv5GmeuqdCwSYbaT8uD0dLyD8lQtNnfEJRDkEYR6d/bQp/JufkcdZwdKjlw+UCjW7JM4XjlTH6+aq8oZOXcqPYzRQoFd6t3E9Njy9pPEzgFUXkMJkPXHtJ53JVlOmNFtl7KUQ5nrgmL96w2W+tMwZMDFoGLRUd4RBZaEPGxlUuKDvpeGGrzOj38KtyouxD79nl/L3X1k27tO7aMyS3dwqhfD5rc4P1b2ubsApZhiv/GJAdoWIXn10fj/NaiuBIA1XXaWRKGVXFma1VMjnU3fE6eLKM+Ks57OeVUMsfMKLIr10IIVQleZYphy/ZQA8B0yFG8HUNw52rHiEcEs02gWbmI29AaCIiQgeMjjpwR2qAaqibFlsROBMhXcVNKuY80MjB47WZnqw8mndEV9dogO/sVjGMU6glsvfzFSBged5ZMkv/LYo3l8xUjXjvhF7TSku+xEtSsGMF5MXpvQCWo2uO3hWl/OXpwCWRc6WWmoAP7tmUNvyg0pL6z8LEiNm52ImQkSqjPEErMBpOcEMxIqGxUJG73MU9QbQQy0eo54NqjicJBRNh4kpd7jkFYzAZkrY46XQCfJWa4nApxLvgVzxJIH38DtvryIbX+ydieDaakJXJXHDGyQt3R4IeeS6kjDn6TifH6CrvTdp473clu/Z/7ZXJrrD51LnE4KMKLRwbxR1/BXyLNCGuJqlwzq0+k+G05ijCT2/jcIVPx9u0bMN6/3Osr7eN4n9L0EKwtfbfhRZafP6ZirffX8Fj3lfbx/uv8G33HmA7rbHXGiz07Gz1uH3y669J7Zsl+Fjt0ubUnw/olxYeVlPkNBXZHyOpBLbdrPetORc3s63ngDIbKuRQSffXNyGDMWN206ld+fPSLHn7ECR+9Ywr8xVFrpRwfcFIdogq9g0mrjfXMw7xQ3MxqzfsLRVCq76JZNQykgmFgTStBDxtJBhpdSOTJD/LyCQDOqfIzN0swzGPZR6ys8P4RBmYTBmJGsvgwoGnOxD8BkfGL+1B7/D0o10iPtyBLCDeyeqGIgWnhQ1jXVtSrwQMSol8Mc3Y2bX0g8rofFXAyJ2ybqoKTRZlKAm4b+dmrn5NYl7NAtEzcfyhNFp6x1GkrSaCySVPd2aUbZFVSSx7WdTszWYTbL3d2HCVaQC5Lwz6kU/JUcn5/FzrugllT6SEFqkiu4HGFNWZamDVSIbEOzWQgCIRiXOoD/hUHR3kri+R9v/UnApAaGWqGX2WQxTaHj1mRa8FlF7urQWvPuLEmEyuI24CNzEMqUZRLg1XBxA+6y8dBc+bcPj3Dscfj1TSUNAzXkRbQIhnq3VMoyq+0z+j53spISmueX48dyYYW8PQsf1TJE8Mp6KaRjQC/C/niUZNiJGjvxsN46JSRUxJoyIX9mgpqhbqlBeQCY03Mn0Est1NiBaeR0kIHBtYeDN1YbgVPRpTfKylWgl5c6ahOOJ2tuP+ZjxTVNghgNY2v9BvCko2Fcv8bu+xDiU2i7etrrkZXIEhVPTAUPXv49LzORRTuagUYIDWmovn0b6SFadd5x8FPplpjgiNuweVEper3Aru3lDcIL5MuWMUGbnkPNxPE3M/eGzLokKOO7vcstYYfXfs7qhnPNHI19xXpcrLLrjDp31AOGGPtyIu7k05tgHthXFwNhQ6y2483Zrl9EQl98PcOEKv70FbwCSaX368Xo+j2VyWTNw3UevhcTnT3nCw8ZSjiIgO2NIwRB0mDeCdHAA9Hfc28LCI6ibQYuEmtgdkmX2tvv6wr3Kl9zHceRBvuU35bPX5gRQWhQfj2PmnQZUdnKioxqMrFbu4Cdh1NKNXb4G8CchSk4jizhNAneEX5oHnLERcU00Rkc2mSmUsnW/x3AVXbH44JU6wTYP8hCSY2w0vtz0v+JQeY6HtQw8jLsLyKyJm8lfC+yM/GrLRGpjTc28S8QrOna3lGTZw1MK7HW0fp9Ho54d2kysZ4U41jLRRwicLOp0sJK14p8dj81uDaDszdoVKilqiyTYitBeGSGm96hDvEFI/RkVQV0qtPTBn6UFMtow+THv4K+hDuxL6oK2tEAgRLtCANFW7FitP5FZTRDEdYkBU8GDGPRIyurzaKIUHUp8/oNhgY0VXhcJpxy+qKyMzpfoVwihsNAk6mqsB/Ix4flSw/hOzdetDMGqb0GZw8N/C7fNseL+OCh6pVv/Fy4lS/xCqfSqZs+pfxe7Pm0BIJgp5io2sxUZC8zn95O4mqpIW1fxF32NNRFj3JggdmyFvoKp49mchzwnbEwaKExV+4hovScQ85f21mFyRYJ3uis0pfe7vbr8kmUl8O2Xx89uCF3c5LD1ofZY9ekoxfbum7KsBgzpFJMMNGsrCo40ONaaJ/cbEcEf2JPbrh2JZJvDVlqiVfZVQ1se+u2K0jip407S4bmn2qUmqKQwDAeYtwdRY6S1pLznrgWJCzqzCXVbYl8oKAcKHyarp06cpQUOiQ5REIXWOk0GJsrN9KIe+LvVDlT4z9U7jiXjy2Enb4wSoM1p9SbGT4laksfgZ0td+fDqIdk2cMGirG5CUw3NUeJiMijEHw+NPsRXXxVos06BXl2PtyZ0csZQMW7uUNixTkAYOjsPfMblZIX3HOpVslSVPNMH1pNurmXZaH0TSaXScnHAispfGeWWZYBzJ/lntnLxi5gKdBd6DlrjKMH91iJALUsq3yhn0WNNHZZ3UKjRMinc0tKofDnBZAyo7JfODNx2+K4mnFST5taM1808j5kCmSmFc+G33SCyCpnf0TMYZlW2BxmjfITBhISPMyg+o1+tLccPzmDA3dLZKZNfKlNVkY8Ds0sXA+PJRr1zaUtQ+YvNgFaUH4OSEu505p2MfnOOyOqqXn+qp76GYTvzkuTFyphqXTcl5RpdmBzys23+1r3JhK0qJVkm0F0XhdFWlZra94qzoDCC/PK3ISJMp2e9gzTTYVELScULUDF8kIscgnWh9R1CE7nEA1ooEzZ8UREDPALmHo2mS2kDnXj9lrhyJCHhmpzZWp6AiqXqOd7daEdKF/nh8ocCfRW8eJrhD35zonIZT7YOPPmQj2/eMYvIsXACZUmbu3qSPPAPjGbkKKCK2RzO6AF5wMJjF9uO74fIut0sJwyndxbGCtMvT2US2/n/IPbclT/6fTbw5K8+KF9VfrKuVO4mdF2tCA5+qFSO7TvMAlSoVBot680ljUrCBSCGNM8/hh9Igbrr2X1qsy5Ry1RtAMsv6KZREODcu3QDPukEHtUNsa5x5uWP6nHfe27W0zeywNn1m2KAPNHmU+nnsVRB7tIbcyFbCBAtNw9LoaEGrojFpHePnLfbdRmtj0Jkps2HseS4UNGvzZwCwh7C2TfffYSsNQ0NWPOgZjDgyZt3sWpV42pO1KVCCQ9gUOQgIu+h478CcvqUBHgl51Wwd5U2rFm9HOmxwJV51mowcmoIvFHBcyLOWHiDVhJ0usaGnAqA/i3uRncaNyJqeHXoXUCJG9UwPY8hIzeVc1zr7xCLtSpES5mrGrP+dv96h0PEvmDEwIZSJmJNW8eCy+HaMDaDD1GnTGTW9/ie2rSphH17jolvfcnaZ+8wUwBQlQwKxpEJF1eJMtATINl29XBWRCJYywHtEnsQEpYTSszknixECpYpG7sHHfLEnV594EtWGUvPBYbfarH+QCnsUA8FbR/ZPuk54V6lGRMoMVHe6bGeQsWWQbdT65Mz7BX/UI2uei43xawjUbSRGcI0GrzLbQQ8CPKeV0vUpQNCg0hdVG22jvO3Q7kNwh41e+9ExJKfbuW9rJLTvCx1gldUMw00IhamTJ7UOicTYZtrr7WywsKTJ+sgrU6SdaO64wMhFBVIMbo4LpK6gf4lUDyakwlc9R6jw5lCzkrHrxWZkboTNodT2lyWZG18eQUKNZzffrDvQ7nGeXE/xuAv18rPaexF5RtZHKu/AcNVxKTK0zPqwGZMH17oHjdOQ6qY+C4Fq4gmxm37mcrColTxzWrizkhJp0GKPTUmRqOGiJr5AtUNUkEcQ9reCp4BB/TuFESOvtFfPlwu+v1RFJLI+rnMCBVE3fL7I10JHMXEe+0QBpn+w+aOXK+XWen3HRL4McYSjFA07xtIlhkxSIfgy28mvadwVzEWUGvl2x7AcjpO1rZ7/ADK0GkCZrAh8Z77QArpqhHeDtXcPVbwRlVNVDbLsGZyyJZrqHFiNV1I+3xkiJhjTnPWf/v6Oa4eM7SKxPZCpZ+Ouxc6Hy3xilPdSmqKq9fk4HpSdBlKrNKSBAb9eFbafGqHMUfyai5YlQi74Ufj97DvCv/f5+SLfBKPplzzchmDuVRaEUzS8bel3JcKA45VlcM8lIcaPXw8KhPA+NJnwKBAoChMRHhmHwpRd7nGmXHDrhzK77U/G9FXk84fzLlWdOQwFH60jTZWOP5rdniz/tH9920XKVjQQ65x+FGBCv5hwvJEVP7ojzVM/omNR1CaHHadmGAZz1VII0DTx3YdJYVEYfLneXoopBvZUIs/Yx6Tg3HaC3p4nZofJsnBKH3TddtQS1E3gv2AnFAX17PqSYIeLOG/BlohdkZrj8iY3rWbrMQDGQJMOhf48H/H6sk/ENA7S68Fp5dJim9y9PVhFknuAOqX2VOvlqer39J4WDI6LfRM0hrhZT+ytmerKYF4wCG3eJb0WqY68owilztDdY+kjRosL8j8Aoz3Ui4Z2I7WYuLKzfKh1L6DpzRHH3aOhnS1qAK3nkETBNqXluXx0bhO0Wb4ND+l4x47cRg054R9TzUW3B9A3CEW1u4bQLUcRJC9Z8hAhoTq5dLToST38aaqevoUnc7xeNuQ+8G0+/NjdMLT9heoFWSWyUDshAG1lc8N3PdK2jO/ByXnB2nagxzzw89VSaKFXVfYbhiMpg+E0nXbuxO53DrSTq7xbx2k3Lc4v69oYR6pEiGbvEWkl8uR7ihgG2Td5JEKhdgNtHmwVU5nICE6lstZ+Ye/6kEUL8xQ9SbxNEDh2H+e9GuwhwAzwtEdlCpFhbnPAPgbarR6LFBniLUE8r+qKSe1PLh03VhZdA4OpndXU7b5kpUpIGf04EOR0nS3g7u6czr041+6lQBvOh/ZN3YZ/NN2KIpuxKfA34COL6b3oYPBIrho1sogiEpaReLvmH5J6Pl8Xq2MhSwyvsg0Oqaq73w/rWGg5NQbpih1xWJHizC9K9rr0I7M3v5vSu7Ec+6stdKVgBSWC3J65OLRnzpfVJhBqHveKOjjEqg6V3N0rD9wKlw1q6sr+GbXTdsBxrH4AxgQRgv12P316z5p5jtwuon12S3lSJpKgDE38BEP55v0zkXRsj+IPCMNBhPD9lUuUUCQD9qJftJUq49JMedwIs82xTtgt0A760FtKN0L7k9SHbgTtOS3OedE7qBSQmBjR7k4EgKQ8I4wE+qAE6a6UbbQDDeBsttsZFjzFpFq6jQM15YO25adUnaR1RGksD8byTZQ2sGstb6KQcsLPNG89SxSLi9HXpVp8NBtSqUlwJ2zHkBiqcG9RuT/48/C2zcIEXaKf7iCqlGc6tOBMKlw2YCPE2IuGRcUP1s24ruRdB6whHuexi/ZIhLLi1DeBD8Wf91k6p/+LmptN0ujQl/zbppiy963pcsDaZHlwzGwfdZNAGNGeLIpmFcJBj9VyG8c6IKmIhMXm8Z2nhd/8hCQJXjqrvKuL4DISR+ay94/Bh4ft3ou9rHxnCJliHFmG+cu+j96f8nZV1I6h18Fn2iXemezvcLnXaV9AZvNisoHO4RHTJMUItskYSkA2AqolIBkk20uMcU/FiIXIJrKYpJIvDPmRz47Ak+VP/PCkcIEiJcrIpL2iMGgYKoXhJtTOynjT3HHip6pIZxfxiHLBpgYsJ1n2G3oMC2qNq39wU0N8GfnOMsOj+KB1YhW9vm0QK3lKsAIcb0D89CSaTDugntp2ltrH1SbJqqDAaGw6EmyLsKLkw3u0INX8ykHGCww0o1SSyVuXP5jJKA4GiYnvVjNk4fHxYbbFpXJUSt1Kat1F1Ldtqq4FjQDx26Y2Qe42KVlq3ErAEbmzGC5UUwMYyrxp/MdfccUfFqvaD7l17KJvS5VvEmHyySK88d847xOReoY+wDLh6QPsyt74DhEvuB2Lz8Ft2PbehACZglMo+mMz/e2nyNHEwGQ5QWYP+vKpXF10XD0Q9RecCcL9dTJdZyxC94yDUgkDbduqwv4ieFfZqXtvhHwcW3xyju/XhWhvEuY+9yFSWv+x1ov5HhSi3PS2wIYA3SnfLdTEloD1ukxWFoUgQ9mjEQfd8OgNQDBpuUjJywDBOGIPaOGUyzbzG5rXS3VM6T+F65w0WguerjljNSfwBhsANMrySokQWhSHS9vikmE0p4hDCm35FaSizT3lVOU59QSlBWU9NFmf7AgE/WYsfkBk6hsFJcZ0rJFvYMbP83ovXkANiVZKbdKaZCcgO7eWLobFPCoX0qtMOUmO9uBsWQcg8+I59YXGLvnz5gJ5q8QRvE1G44vEdeV+CbXOAdiSWeSHH21RTPLwKLXIp7viDw6OZFqyFYOyTSSQP/hTQ/iPmrDpUny4UKzmf2bCZQ5HRvOq9bjcGH+S0detLeFq4eEcLx3NUjY5pVj/60xatkTLwfqfqONmoWZuB1PiMwM//53/9i9vmZffhqE9qRBHSpoG/rEdNNVogxxYgkE9sSk9E7Eaf5gFNW9jPKcIi7qO6OjGJbmWZldqKKkbhbmMXdieXOY9zpNuzo5vVc0JHFtOfJaYrGh9LIXPl18HKb2B0PnAoOhwPipL/a5+dQv6ERiQcLbDzJIU0wRWTdnIuiV9QI7rw6CFx7opyRRTdeLka0XW6IUBTSY4J8mUIU7Czg3XowYqOa75PrMb85aPJnDbSMgVqKe0LcrSpeQs5Uxfkrm+82cFVPIGX9LkWQsb9R2uSvR10+ay19+LsVz3MG4fqo0X/nweoDlSozaDFqk3EJ7mkuUAfyMLs93WV8M7fjjJkK+HC82gQkeR8lptvZdriqv17rne8CmWuRzA8Mxofx14Q1YlZxnQZRFKznCz9Md1H4gPAxnYqe277m4z3TAbkTI9XKmZFNXrlt4JadEX8IhHFGRmQy7j/GTe0BDKG+S23R5+21KMtxSyubqiUhC1SZ25pw7l5lKPsX6yeWci2mQcmfIEf4ToZmiDlCfwPPIXxrRO4o0U7YLEuRzwYHrl1OybRY1NmxdRWChvIucM+p5q718ukFzYBcvn5VomXi1h6VTaJL4s8ol4KkuLpoKf+2pP/ul6/Kid+MahMIQ/GVOG/Du3MqHQ98x92lPGPTnByRUeRTnZ5Qe7WxgtjFVx+LcxQFi8sW0eZ06VxMaQIEv30taEsaQtkrqN+wj2Xv4w+8e/zBQT/z5d4zhW3zntAuv4tS43syR/buL07C31+GlfWFdofPGIvz8tVVuTErzRGL3Cohj8Em4wVVFBsOK32LK2t3lk7S8km/soa30ci9qb5e7BF2+AY61KnKIFAWsfL0kdK2PvNYx4EDCFxfP1RMdjZx1EjV0Q14DmbcHSoaeorNSMNCBzgQn0wIaJ3wt3PqjJcW5ScFr0tdXAyUzX7tf8UxS5InjSX1ejzf4CASIpiTNQ2AeecWEcY012GnTrrEdCiad2LkZUVbjDqO3zbh0vBYaf82NOdF/GplM/RJrQdbNcZ7GCCC+J1VB++JGRcU6lfiiL6IzH9o2ST5bx7i4aiW6KWqybSH3w1/OjGKYvLYgTH6F70O/6DpnVrDt5MW25LzQ4GcHt/6eBfAOQFxM8Px+4FyKjzPKlob2LP2QPKJCSipojue03fT7PQDHqE9MQOHnMjfplRFX6tucrBLXKQ2IJkTXImXiroZoSLDi3/Dxx6TBb7+IpwRrMpyAlcVGz8eEed15GJjRimj1iDa7Kl78SeW761jPzzw0WjaNNlKhrwwRenQXbBLuR2FblPPVjER1FjY9TXCsHbVPrvAaGH/Xx3AvzHZsCXsdZyALxlHzV35+IfPL/H/XXozW3N3hOfdZvh2y9O05piTlW98SqGxxTazt0xAQR8JtHRPjOGsEnvHkSqeZZoLUBNHjwB2W43fX6+G9RJI90o++9Wcvwhz7hkpd1ZODHMo+0Juf1ycjyGVDT4tqrJlqB18/fC9UWZuMU1v08ekABI5RVGcdvYUYBPcJie1UjlJ6oVT3O6GIIydsVc1DbCW3r+YYdJkFuKABJI/M69/0DoCgiEePhk5tTZ4OJGHly9JSGP8K90wecZvLQltKqYn9+K/aCd3HGyc/i7lCFV3pukXvX0yWbJ/mrhR6qi1Vut9am9r37TbdjLOw3vQWo3dulS89DNp/4+iSC4H015sve93zXERddUgaOAcLJR/5MV0tt6Zdc3tEpc9FDT3ZwUhi2Om2fwlaxVlgyC+Bx+lkQhdmm0daafz+dFVTizcDQ3hRCUQiSL8jeCv1HIEF8Sl3ZIuyc+GkMh8YF8bAzFt6yJuvpc6Dj758ycR5D8FWCIsHcKZJqm+vBVWfzOV3LvQoh3vXCDPiJrvXD1xPUGNQu9rBGyEF/MO/ssFtUagnCUGsm5FiDRZxfQUoC2KexT3IKqbDEtoIywnjGg8cSsWnTlHdNBbNFiTAKiPoYbaVzvyduuXQ0f9y5Qgpbz+kHktEJ4dEX4Op96XtIidAoA+dfNyu4aXA95S37mJbGISKZgeoGYWspuiBM6fOSyZz3gHgBsq5ArITzNcVcUunw5fqvg+BQjNzQoHOiiV4EvmQ9AIzHJx63zVNBct9LDOpv9+AtV/nVWGa2d+74NqHZOzgOLt8M/c6FYPeKmLE3QrZfsGMpJeidlHXWpQ8eHx0Z+8cNvWCU58tmjB0hY5SXej30e6cID7vhlLl6/N8lFiOdHBWuJxWRBJsalnGYZ5beOlZRy6oapVoQY7kZ2cMvr2j549TliM/pMUnTrVC5ZrRUNwbX9bSRIsxj4a9rLIs5lhtPJuj5zIECOOFdrCHUTrvMpE74erDQLTNmkbtnSiC3f1IBTBaUCslMX81KIFAy+BgiNfymZgPgTfUwaUJTll1WS90Ajkkr4O4I93CcQ8zyMtgjAZVRTF93l0SWQKcYouXT+yEealmpz4ER4eusFn/qg4USkd+xFCX2Tir7VeXD/Uaxx4pS7S+jGfYVZGs9RENOkElNsNj1asmNslKBQj++xEFu4zJAGAe0djRTdcZtAzOhIfZHNXTXpUN5s5UmJMAUw7GralnZH5Zh0/REO+beMP+FLV72EpriYumPNNBgi4M6hVpHz8QFl1ZbLT4FW+cqe2jCRFlOh6t7SoxCTS/mPKeqjy7jEcsOlJpJJw/HKDk0Uv0gY+N9gVBPiDchyBbNkQGTiatPAhAiiRbSNS5e25lCg6SKNiairKJ0LeQb/f8kzs5QZ3UdDUPUPdabzunn/+B7fA8gDeWb0gnTmC2sPuvqnmjDQj52OGQl7qkuRoqzFRab8oqxl4xK9QvWtt2pfeaZpZ7puaAQuud9VhHD+rSVPbBfwa5Et9PZmahke2NIrGTikr2+3bxgOfTd5lzT+rQbDFuqNPZ3g43OH5jfSiY11kI71WWlpxLK55TbdFL7v6Zz7DX0wtKxe9yceGCY2Kuu7rs+H7TTA5rLz6e4k99Cp0ac4FgplwE8+YIPqq+552+xBmpK34k29SByGm9CSaoETWYp9lxuCPSHCT2WV5LTbl7ZXu6vZ5tgdlUfdPf0hXlMeUAiSEg0XdLiDCBGqDvpv0Sb/ZjdS/ZwhyMDNYMNG+hafgnd8BgNvEQdqnN/TLRb9MVhSlb+K3kDtNMb/q4baVjy4T/y41RbNeWAoChyBEFMNtdVsVxDUkbKtFuPoOTxgAiGnHm3IgtL27bh8EVBe56iKsKVbhbGqo5Jm9BPslQ1TPVIBXcolcurrNY+9qICRUjkfbOpJqXkzlQrL34T1/wVlTRZPncAjtQHzGMc7iA0JQDBRijqUdEn/W1+Qe/OgJOULwzvgMY/KkagcvhoXfuGlPMbjhnw005FOPka7Q9ida7H44YO91Lie4LnF1e245E6Uy8/fNZjCba+vtFmqbNINcFEH2p6uv1XtmC35utNzAVn2JOIYEn1fZfeEpFTYZKWNuYFgwv4bd34EY5zlTgr0rwqTn4lkudIo0rppjkxMpy1U21EQX0ghSwhrcYeTGzdro2S6XECRzNivIToA50vn/yPMWdgohcsBT4JvuIRE2Up1Fg66ajdEs54eNGALwDF1aZ7rTci3GIT7n2DlsMG17IYOwyGPpbajM/2JMwvasx55uxZflzr5eMsLkYJWBgp8Hv6tH0VXyA/gsxITWeX28Mu5QvJbvHL2Z7+GUBXyif2ToGXAz2qF13Jt9WlYL71TbmXFCF42Ybm1f2AzFvYHN+TEhZ2HhQMv1snXICjUxIIHV3KnB3s7kkB8RzirZYNC6H0aiMqGBnes8p2IbHYSZ7LuYlKcKlOE5it6ixsUlQ8wTFhCU8bf39PKk0uhbtj+h3GUwtW8ZGGlEiltTGsvFs1p0CWRH1MBFnFJAek+DTj7rXnCK8SXLXWLNrjGYmTvN/6GKgVqjLiT9TuSMauIHgRtxMs+TatELHtpk8F0VBrcJD3SYZyYvw/dqxMBfXUoghzvtUSrrudQIrh4//7q1Le+KTDXSH/kSWzatDk+KkWY9Me9dkRwYl5Sidc99nQvEl89XMUzJJPj0UOAgtFi2cS0f2ZDl+Xx+kO7gH9lmGj3cEgd/gisI5oCFP2zRTvoiIzybflptnyPb7UG4J1GNh2eYuOrGq0TzDXFz4d1+kEJXMhdDGXTn6saIXgUSjBYxXozdhoeholAkGBmNfjJn9VLFuW3TW/fMg/eMyRmhuZNFWLWz9p/uTSP5PYK0YvNEaVcGC5mcr178AMDnv3hNx56wqbdd9whLm9wzumoju78cI/Yp8QLwvPRse0HnzVx38bf2U97mx4I9amYWLuRwWrPbC/FmWbB1MylqPqXDovipowpy/w4JIQzE/+O7/GA+SQF/WCzWOtThbmxjdZKCxU6/aMjvfzEwRe9NRsNmxU0WspD9YodTUeO2Io/4ff2RVcDNBZvOhsM0w9JC7FPW5/8NkKC67fZtyeTT+zFEfJ4R1+fmUKbpnwpK10RjCOAum4T4iYFyULUl+urZKABvrniA88F9CwcMWQz3TWSfwlTpLVMazbJDAno0k1dMGDl4Tq7ypBxzjZ8muhmROsvlYahuZ7/8HifzDD9oFdrnamszkth4jL8a2aJLL3GGM9PYPcXvE5xXDjvrGPxxipb3hOcLmFutsTK6p5Mrwsy9IzGfzMoRgLoMKt/V00KXIXZ+uvEY36+RCXbXLpVcTz6GZUkSHeWDkWxYjyEct86UqW0LCsESNCFgyPka9yoDZJTLKrn2nLijzvnRxQO5TwTKHRZ4ItFS33G9swxGbpVnT9FxTa3EE+PlcDhZ9E8r21FclAuASvx8jmlm4m49KKSudi8g5ROq/JAI9ZhMn51uGfctSnYW1oD0zkdzfpxcvcXhZKQZ2BjWwRO//O+VX7zjtOU1StrOIZl6l/MpUaL9kXJzp4mKzapt0EeD0CWFLRX524Koi03IDQKl4eyIwC4k6fLYxyTvPj89CwyJY/6CpTJN69YxobUw0tGheyIeaSw8XTO+klFtOV0Xo6zITjugWZcvcGbpjt0Vm54Vsk7GdqxM/X99fj44yYiFgOBjEw41QKxYYaVKMwJwukNC9i7gG1BztUqIJdUuNgupUaqbfh3dBsBjSlVjvDu9Ba3VaQWrAoEJX+u6lo/91z7mtaxTc1iAO8xMZwRdFHstZS8N3OU12qis4mSB6h9FbUVKnz25de3n+85j44+Rv9q5O4eEsd7tdrh1Q8XHT0RO9bSwe1bYzGd5FlsKp/M8BM/OUkzZZC8NAQmyQ2i1LzK0+ecD8SQKIRRd672RWFmY3mC5lWK66WMH+kafL3w6T4pXJWqCBi13QqIcoXzd3ZHCo4Rb4eIizqEo1gtK0vUfCObhFsCuIL7FwVLxNqJuZiWfg5CKxh6bQW3cyZ1YyfxkYSQUF2YXPMio0PYZk9h6/N+eNtyCgfy0xAeFH3qmpwPGMJ5bGjU46J8vO849ysa9ogPNDIEg2yZaWUUkpFSimlFIKQlJRSSrkS5q6dUbM8z3PD8qYnkoZlmOhlRhIENONYJ0AdYGVuai8oUiyefNHES6SYM7y69Epm9uq4NYwgvHhQpr9s6laBOGDmIKvibQdobfPQLc7Bb/8777ogKL5zdg1NBc9ylXeNPtSKB26GhoBQz8NyzOsj6yB8a6xs+vdofItpgKn+MXB04zwSxDHXnxDFPgzYQ0HWsicmUSDU7GJzkcRy0vR2FfgNIz+lnIpZZsCglTZdSFc7DVwd29nFlwy8ANi4kNGOpEx3BmjZMy4fk//vpcjbljLUuAPYmHkaTRhcHsMyM0eTWzrFDkDnG4cmQvrfYWXfxtuNLscxiARkIJIctbO6KtVYtQCbLXIk/CoO7MzwYoO9r0kRGckPov+G8YCfIVz1EGAN0KSaJNoYHzDK0x5ugVQugDJ/LvG82r2VLH/Ska0/F+tuhTq+GI8UPK3Q+UIEkX7/rDBpKvXl1PB8AbrQBYtHxxEF1tdwBkR+Q2+hI+qjhHTrd4ZxrMfn9lF/Uxmkzz1yT4uza+H7HYTtHpQNIxYMGcBsXr8vLjY6NI92sDS2+8N2jPyRnq0fbGmMeNAE7+8BhxYJq1zzROYxkCb1eOYQGzDWI5gR+6Za4I2HwA4bUXtKGQQ7cwrehS+8l7B8x0zrom4JcYAOaGkyOVuu9sWBJRgQVpFZB0P2XxkcgALrcBsOZQxOpNQq8mfJAWnHKsGmIq+H76WVk6i9doRqwt/HSLwvlXIgpvNbVMkrCgJKdBzZd+D3KqZqH5+NBIL81MLyXJwGC81px7EmL+No2m5ji+BsQkRdKtN8czxkifBGmAVByDWOzN5hShyndUaXdD7wHgwlN7pWw0Bm1wcFg21O32oafYKSbcmPMCooaXRIujKbyUGzIiZFPqCvIGf4C6yNaxqXB/RqSRpjU+gKzAcG5Zr1uPBZ5IksmfWdhmXbpjGe8scruI70w+FMLNy7/tjYB1kEFgMjjZi2MOoRlpRe7e+k7DVb5CT2e30HomX/M17/JHvyf1ZojxpOgqjt9/+Ah3cY7FDWOx8TknK8x2Eumz64GdksMooTdJWCQy/bypWfeodNMbCNVJ9/gh6Uj2GLzKoWHjFw2xVEQgRQ7m2NKOCCkT3ND7eQ80cEkEa2iYuiBEpxGex2bIybJKjLu3Yw8hT1hvc54f/09QT798IweEddJv59jhm2FWlvplkpJ52gnNVGc0P1Mj/mDVJaNLpxDKWfU/DJ6GMVRM/yGqPatUKXG6cWBIvVAzU9EPuSOOSwYxWQxfTq1nonrl4vyoPQM8N2G1Kq1qvAT1MoybGdDNPtpTFV+CzbfxJIPw7tUgHbxwltQunSEax03iLBSjqsvTOmck4mPaDMvOkrlvVMeSdOcRUzytAZvq1+mWSjBMcxBDeMJYYdFd2RZwQuoEBWaesMVFFndkAgjmwcWjJICj/4A2Lu7QlHQf7KoCEAoaNIiHikkJTZyoITvGV9wsmjCl9sCMMbhvgmcW2dqxaM4qX7pJqU6dBleaPqGKRiW8w9+Ytal1tzOk0ZM2LVe82tjjcxNG7cBObkqele/V+ckRPlcjd1qMp8HcltrDl7iVnVulKhbF6834bB+vGw/n0OB2Y1So7xNkAf3E7mkWQoIHMPVhPJMw65z2dpCVcX4mq5xZ/01wfJmXLlaHGY86RSuTlHTpmK9feGQhGRr/ux+qySdXWH316zPqGaJaD+p8aQc6akkU1KAkdLfOyEU6+zvC+TsrxQaudS2OEyGQcMKQmnlGbymAUuXS8bG4EiWupCg2DjAn30HR8iQ4p+nf03oQ5FINCR7A9yX2rf9r3UIkPf7dMnVVBz8Xx8cuQijH/feOh6bDPIdLHmq5mXvwX74Y3+7ecfG6jxyQYTNR0Tp21ZYnU6cx3ElF+9wPufEFRq4de+vOant1Kio0VMr4tppEunUwgd+n6Z6yN9DzugwtSv8L4n0pPTfAvyNIDGXj8X362a1E1sHS9F/Zg/X5y0dmTJZ/yEPFZfE7/ErdIMUOairpe0pfssVw0DQ/ktl1D1h0/xGXqLgqPFDQiL1jctMb6OPfyWt3t+9OojIDTAx1sLVMGFR+YObJ1tN5usEENbs+zLCWlTOlBqhg9K80OGXQdX6up6S5dfci/9CnT5iFl3/6IKhrQm3XKtsdD0mDZljqCxrsHUws3IBgpoZnvptKmhcMG11qWg9xo8pvcEsfoYuDNsmD9XNiwjT/JFyA+RGsQFFXrQkRx22uPkab+BzZ+9TkzPkJ6/QOtda5wr3XBSeefdyZlod9WmDO4ADvWP4UkO+lR4VBj4rmrnuinIV8NRCBFf+9f1kM8bpexUtfnmJpaF44xjWmayGRTq0laZhEKBMDYC5a3AfnYC01yP9f+EiBSlbQm+NGRQEJKS/euMH+yiFqJ4YUzcKgJHhOZv9bR4mIi126dx7l09XDgm/dYIuQw8UuXE2/nAtMPiiazD2OgblTlTamkplnkXXTI9TlFTlENT9Jf3fTc39+Zvu7kJYx8IuN7rj/dtbj5r/xK/jk8hjXkoi/wKsQGAeSZ9YoYD6JRFog63GuNVm3mohTcYX7PQMI3W6owrwxdZN8cQO+JQC1nPmMndnHBQmUvF26XsYJ2TLc8+dWChkyqOEHNgJCcFmHQBm6h8d7zC/dOkXQEFFOHUBaKTQv0Yi5s5EqdOfJAYvbR8JsM8UMcwTxM1VEojFe57vWI9Dr7UYZMnCU2CELzFkRYyjTIKk4BUiebxooP+Wi6vcBpVUu8tw50gBzyZiDlDikXCo01NnfJirrdAbJWfV1UXC/WglgVa7+QBz6Hr3qp4qaymBGaOAdtSUN65nA8+d0939y0YyCOPDPD0U3+hLUKYEogjWoHsaYQU96N2wxRBR7GMitKlAXL8EJHPJgO8tGE/MPabwR3H5B5R+dX4t1IwL7vvb689kuIcLyctD9FWW5HpE4fVzfc+0K+VWJP45UUV91QCwN9rr+mSDCnfY3A2U0pxN+u6OMw6PATzULT8YaQEe13K/DgTn+aurDEs5+bodpb14Xo8QJE2LdJ6NEARpnIRuENRKslssaZS9vE9Bz2yGkkhn7FWdwRzEbKb4InEXRYWngfsTL2dzokVyNE6U8ZYltMkbdzD+DeJUaMAxFI/0AKQEkFQwIYVRHh6LSJeMFYVkZVu1TVyBeJe5CKrAsb18WIe/xqO6/dN6NTiOlJxjX7xlna1a17ebFM2HMN+uBQKrREcegwm/q3rjyQp8GiasCU1Do42Q096s1jbVHtJAIn5yD+aCvCzXJSDJqY8Q+Vrr9T0Z7SqjaPRBpw7EY+nhwkqSHIQQ7bp2VTCQyP05daD0o845ysESLAtf0zkJOB6Nm26PFypQ1MJKT74efKG1HQonJymG5SMTw+Y5EU+WoFR3We3S81dgH8GrzesPSl62Kdivo8035y/68RRfMCXToFSciJVcvjCi+zayRa3QlHFPSZ5+p5L9TqHcabZ0W2OalWFrXTU5R6oDTWWO48640XOzQ58m5XR8kY2ZdBg7EFLh6aR2Bn1u6Bk1jltZqnDjHG1ak26xURHMaRBh136eNXUBiM0aBbCgFH+uXRiKn6cCQCRHZ6mD60Wvo3vEvaCKZyJYVSZguAg3BaGsCMmLJyQqWGYq+jUGBYE3qqinw34bBD88gqaTGNZJUsoZow0iAhXfIGn1/TunGk+42DxWvp9ybaX2ZRMRZZPr9hRig/5GbvE8i4sn8HFwbSf/yHnrU3GUQcp+xoxsUZKg6G5vZz5WWvG8ikUK1pPXULMuH9T0XWsAOzidXiJgR0o6VzfGrobOH7qKljKiYNgC0/OCPz+gFC6weX5NBfmTdhvQlNRGi2NAUXWqNUmh60JUMIVXo1AqhQu1jvCadRZDnBxFMmY3buGiW3jmlU2inn2XFyLygnakVb3/VjDYDrcrOBH94ylMvwUQklIWJy5MfJACzEpw2Yb1+L+8ZEOz4G+jxL4warcy03u1YYlKLE56fTS62Ad+NUgnVdl1PpxTpdgNN3ick46jTKZrD6HApCKQKHkwx6//6DJ/tVJp/z+Jk11xHVBsbd2Las9BwP2QrZ+ym054bvchBWXD6CB7XpsDqHlm9IrQSytFIeekpM/ii7P+fxBTwfuHk9c7U0Kf+LNHoNCvE3nbU6LuZCxhLko1eAmkdftyuJCbT9b9G3LN86YXxpIzQPZMRucJK1AlSulCLkuaeNoamJZJ/8AFDiBcXECs88dHTPAKI+iiMklec3HQm8SgNI6/13J8OV3PePkIL0WllxqUOVGm/p7w+bTTDyBOk1Z8Vr4LrONZZpc/bH8NI++zHbNZ11fgYb9biTcv8yu/PkLQ1wDtriZbbNzj8OZ+TD4Pq5rGc0MpWf9ylA+qa6h9bXtqBaMGnfVnPcvZZWPADy4idwJ3aT2Hh4dt1z1+IOlYb8mYVsfpvLvG4GyY2/ACvNR7Nn6THJfrso6qVLu0bJNYC8nqzd/5KONaLq1b96Qp5P9pFN5jKR/Aj7gSznxOh0NUC0Lr9BzkYgHv87Llvw/p6UTOBxU+5WsMn06PGz6snmX1aWL0LEuLGpH7ur3yvVW+1/LZYyAC0n3IbrK37II9NjLoLK5gvlyewmr9hI13c9FR2jSVNeCrFXQwiHLYKBJ6TEgzUYT1VrHLyL1oQV2Ntgpnzo5FvZFu6IDvVMu23ysMB9F18BOXETxGXjLknvCkz7twKjGBXFcqP1GWTHA7VA3COh4x96fymIlXdTsH6AyiXdBcU7w3TrkpkJKbGniweny1dcjTXk2jXkdtf9bzxhyP++855AZB6qsDcWbvIVpDKSb6oQOFlyWTX2eYL4OvfKejC1wWd/u2wqfQqihrS5HlHQGGUsulHbgFzaRuZPWyboQpH+rQ1+l7y8kU7d7RXk4aNZ1EZdFkdyIDGixTh9UyO5P6jKHIlMJXR5MvCd5Fjqfyq+xEVCyriad9jWyuGnelLBzH8RXcSGP8/7m4bfvP/aw++YD0uAgjMs0OzcL+/WjZK5f1iO3dHvqhp8A1XFcqmZt0YAU38c520UlguiDSPkRbfaHVG6we/sDfdEMvLEjwMNd69Et8vVujrr8ugeWd0jOBDZhEyFTlZjO4NqV3LJdtVOLSwXXQAw/bD3AswCPHTMaB8BX4utGNXtyM7hL20AEIh2JYHe5/ZXDPBn5Efy4QeTo+1Xt3hXKYzD1NDYh8ZAojHqfKZxDme3Eg3YGroVHgdH/yVOFgYFnQG4FKueZS1XLzAKhele8stKBnMWC5OK1438ZifspS51vF4OVVJR6ExH8zj3Ra0Grp5Dtt14W4dnQqwVi/XeTH5jhQ1pUAlIKTOJj5KUEgxjDbufhDyTAsCc4Vzk/adgIuoJyVSIHLWT59mFqDjgpngwPdGe4CX6XdgeF4I8gb0JaJ2S/vQ223VK//fl8+ubt/UksobUfuDxzjHHYhxHULhtT5hH2dnht6kkvSR06jtjdN6O8e2C+gOqi6/KjdMY7rnQTWhjLsh7GJlgE5AhuLAZcjVXBB/WkWnR5mowL+uvUjlAPLLej9r10w8kSSNdVpDrzvVZSMrgKbElMF9FwEYudM26lpxW0x1Cmif0ANTKZHCe9iwwaB549AbRnUwaOtNAwIv3rYhC7P6BZhI0dUipvXtAvyAp+DK/gQPIwcc6CM7t5Q2D1ADyYQ0P1VYHXfQXeK+aEDaES0wZs6hY6+Hi45BW6F4eInaDJpdh/pNPl3xpLFGrPvPGFYLjAhxOMtFN6Lazg8w+bW4cM1tnjyS+TjP6myhjVRnYUHpTyjxkmnjFWDVB69hQuyFRCQNKKWAwAS0Qx9/v7nejNSVFr/jWoGESsI2cgcj/SgczmNF2auR0XC8i1bxy3xyhniKK7nPmFJqMgywdgPT+KO0AVy0M0OH3diQR2ye4doRmuR0zz3xeAs6pYU4rSad9Mhf1m0QtVCiQtAf7Br9l+feO4KzlAU4qxV3oTYkWXZ+6NTvCizoknsaDaPr8+mb7qOH8+NEr+BRWTN/ECOyhO5fh62JRLlGkrPGUMURrm/1+pYB6AQdG+ZJ3foCH3ptXIkUkYnzlWeXDzs24QRvKTeJsFNi6LXQXuBtlxjqiBdjI7mYppU152YYTsyo7FXOseigCvhy3XYLa+Hkd5+MWNCRl9YfeHMMutgSeGStgdEkEpsSVdvtDTIYuXceuhugr6WaEb0cphXdLw9dfkg3Jx1P/ToXhOirTlXwdpIUumMhtrdvYXi/3dbVp3Xz4+XvynGt1ivoDxTmQ2s7Nygoylbliw9DeokgLkWO3kXgM/XHsTFtjJRc5Jc2mk+w6og0wZWg0hqwpVgWMUEHISwYkZ7uRZ+t3zxZBNB7eRAmbgugl2pndCvfvuT0rfqyg/7qFoeaX/+Gl2CFGfHPXDEluaRwZ2hH3ki4qN24i4wkKaAXOl1JDnnJqPeTqBnI95OoE8GiNVoAQi09ZARE9qMPrmSA7N1McoLoXhpc3V4xOD1rXXgXQXeYkrtLNOHPXkT6Q+uCaYVnXB9nX0s7TDUlIf8y6u2Z81p0jBh1UrDRxUSFFK5b+ZxYf9hi9u0cRlG17l7Az3Nr/ZX/bckERglKNIEvrFgdcEjfHS1NHQCdp1sjIo2tD8qyFapwdElTP86PkctBJSBUghlSiCtVXYnGRxWFATeltf+RKpVCtorHUzeFZ6t6VF521x75YimMT919IAmKBpxYuBBOBXvgsB7NW7lh9GpoqxyJ54sLOqOz7V5yE8LiRasKEOvoZ38lx01SetQD4xJ9NxsqnNcPvuCusqwDBJZFIkvGfh/nYRJfCLrcVv6Z0qcmWCrQhUptMJMlkb1wcDjqslduAnN162JXa3F6+T4S03fFFklWTWDoWW0mxGNG+yf4i/8F3QcKUs2brYyaQITA/TAvQSMweIOaLrEvCz9cAuv4NgG+vVSAOM/0EfqrGeVuO9sXTgLJq1cPjhjOIU5KIfydg2PIPVxj04E77fg5bmUMyqh5vUZhWdqbML1AG0dZPFhhZH9exCreUavQuYbYFkCgxSaMBBdE3/kszGPK3zH5Pyp6280wAb3kHguqRuP05ripDeUDJuqjOG8H9aTl+3GFlORAasgWEwG1USjEe3Y2lHOvEYcJ7ytvhcf35l/vyTUKBNskETDVD5agbzJ7vGkEQClbrJd9NfoF6ZS8Sw5vMmsGlRPWGfTHNtvmMg3ugs2kSzrhL/WpgWHVxHPm/P83rTn79NIwpOcEgV/5ejpe99kiwDiRsEqSXI5JoIwAyao8nzNJE/rZQDXnUDmlBE9jXz8Wj9t4us3XAIzfutBQQIM4KTitGG1RjhRlT7pRAQSsEZDqpVrfMVVfyaV+FVzedNvhkJOWKz0Xd2hs84f5dmnTrV1TsdiU4DzL25KSf596l0OoHA3ARRqKhHkisn6Fx5I1yMU0CmyCjlkyuMdmMjk0e6Px3nLyVfEHnZMFGmRiqheUjXCieFbZ8e5ULKRprDjIRArUwtSmw8xc35LHkeAg03PUuIlsmkZzI0qwrYQj/hizoWeI3OcuM84BuRaTGKZxvzQM7sHepdFcBVOmRV1Mhm4MgZXv31ELH6q6EvuMkgGOf/OrBXrP4sJYd4gfW6ki0Yfy4weFYyC0w5AWcYIHJMh7KI8/tRuvxWII/zzzHWpwz4z0zMbkcJtCSvRumk9PSOIEweIIE2kavWQKxP9MZML9YZVNWmV/l0L4zJxZ4J6rsxKh3/R409DO62VWZjvf5p+NdjdbHVT6VRE+rjnQF5/HTYGizJeC+QW9XlvFszciomvO8Y7ljEGivVTO572ueKRoRc0VKYeBIxIStFzp3YByP/GjWAetRaeUXRTXDnczfQaDJe5oldu83TkuGcB2BU1ULr8L4gS1K84ESwfhTdEGzwPDTq4/ESUHRjHURNsLhs8GP82BbFe8ZQS747vU1gsUBL4MN6DdM3Tw1RO6EQ7CCRlgFC5vJ7y8bFu1nMkojTVLs67R8AURc8BMl0fm3JCY5oIXEHcL/usuMQQ/OLmAm4G8hA3sQnOJt98RqGk6OH1FwJkl8tSBGGhWgiJ607LiyVSlxIISuP36akUxlKYq1j+iq5H3R0KaAlRe+vxUwKKzERB31oPepBlk8lgU6qMWqAz1z7tv7yXaQKg2+156MZhjigx/8yDywrwLqVnzIYkmowUiJlMTJUJOiYHPUoQCkpaSXFS9WoRNIMxrRPMgrBcG2Uv6uxdeRExvzt/HZoyDk/Bt3VmaK7bOIFmNc0uJzIKO/spBZxMaNElNfMEXMoJt7JYZWJJpv1vHWe0XsCM8inFr6w307BA9fSMioOVWfnD5Ci3v1373X4v2zQl+qEBydw/b/qHOvQ//hA/lq2T1fv5Bvwn7VXq1P+S0n5Jf+Iv3Ls/SMwx+D/MjcmMO00zRun/S8l4etCgdpnVq9cBL+hI6sy/FM+HjJkk9qYnj1YHhwqyJyxW38NLv8lT9gA0AT/7XmUwST7tbSe7yKpHPTbsYpyRiEddxQXY/SSTmityg4waV6VK3/Tv/UH5z/Ofm8yrIbyH61gtK6SO6l1QcJDE1QiBhKNrWcHtFqs0nsqPYFYPd/k/dyGzc72+s0eWe1XSTMrtp9wLVhhvyb0EMA5ozpSDu8X3hJh2jSPSNX+DCUPZ/jrZK63oHrqr3jRGm6p6fbrron23ChgF/l/d4qAoilEdSCVHx3qhqmzXMlfcpX2Y/WBzheYssAdzz6tJoESlVFofaj88EQJVrlPzRR+ktMw8XJC5yj76T2xKa6v0+JKGxm0ro9jqiy/02DFls83tUUrjcZAfyGWbMEUpK88cLw9VJL8O1b+i937FUXoenJ3/F6Tbdjv7i5/Hcv9xVTZunYOrotWFcVVLDyE/X+yFGiYL5YjAz3/Ciqq8fratk9u+3yIXB//JCMAeht6wyNFKZeU+8Tm2C3ezT58p/8cnLr7Fr8NVLbfpMjRa/m7uX0//y9FqGQm4NON9O6OW2MLerae8LAwR79VCbbRbsVeAiY5Ff/ll2+aum+ab4n4W4K6XRQvc2rP/Z7Y2Zpssi8veIQWqMRPKXK+657ZHKjm2JUn26DnX+BpPWmr88p/1tlaGXgo55Kye2umpHHKZ91/KQDbRPEp18/X9/fN9T3e/unfYfxHkzW4v0oSYO8LmpZG+Mbzmrmz+MKB/P+hxDx6YleZ5zW5R1TiT2m87efojrffFCpqTVGCPyk8h4EeUzoBhZMlXv2qe3sN2+w4yFVYl2QDB1+zoiUH1qwi5gJqL0KtxicFT9svAcwxfD/jY03NglAd1gSk5r89PUwSag7NXNA1k2ERGts0KuLJgNxPhFcPttoheT6XsV6+VoEuuz77fCjzTCRHLeEEemky4xnMCyqqI4CEhMfkCd1lOMQzF48gKdS90yUPUjuQ9U0fem9xI63ZujibjNoSl10hft+FQ/3pPrPihs+BcNWaaiJXqDQCDx8s6HkAZOrfQT8yUrxD45nzfm5jcwx1lR5F/TKJtvdfNYra5D83nkIaE9VSsIGORRhxt+f0zIaTEu0oHeoN7aggoalQq4f+3Xgk5p68ffkhd36y9GWqyZOrTyCONmaXDY981d48hb82HOgvtweR1ZRbHQviOrYxgsWmrd3GweXFcE5/JCuuA15Sq+UHZLJcL0hmJUTaX/PFZJGi9VheHE8RBLtqKOdeYcrly9g7N7P8XRDcv58r+lj3gvzR12LF1L8uk0m99n5x/BSz/lmFaMAbUcwcUHIiLQJ89okSB6QTUbzaxDAkfJYZ70zx2tH9kYYzEytbEl8BoxlhHakTeGGPBQP8I9hYoasT3YE4nmzPakx0TwHvrbBMC6RbUfzggEAtdhP7mIAKejj2tCKnktdBQw/QPv9d6po/66wPNoXHRD9et/wzLrvpff17+231PDwPv7dt9Zjaj7hbrx7Hb/Vxq7xP7/df+8vV5/T2b9zephu3ny3OXPnbj1hs0qf8PD4ua9rWL2+x+Fp99m+ZI5HkmRPRK8aZMK6UH8TMEj+JBUtnpotWxh865Vr5i66w5j3dxHrmkq5iY7whUlUC/YotqaXfs3XJ+hM7kyX9zI3Kpf6SSdowJNMsk6H30eSOwbhVuWeYuSM9Miy4c2kfLgU8TSif/n9/xTuLwj3pg8XEvadXFhWfLf1ixEHTF2PmgXTEOPDg6YJx5IulD4zOV00HkJ/2c3fJ+sSFNSfWvNfmN+sX/t+bF9aXfLDmlZXyr3Yr1nv+te4tm4FLaz6wGXnj5ZZr58Xiiave96/Y8SX6oM03m4lLbTZcTfxj8QaBB6r9znA0oz/M4nA7ox/M4EWemhoj0wWDGglj0oWRGgZj8oWuGhZj7IWFGh6jwAWB6jujzgWF6jCjzYWVGlJj1IWBGg1j2oWNGjJjzoWzGjVjyoWjGg5jxIWeGhpj9oWb6jYjz0WKmjhjz0WOmjDj4dg1oxr8w1g9Qxn86fACQyT8xFgrQzq83OkSQwa85qmtsgtM6qmD0jG94tkoIzTdwTCpsheM1KmgoivMwkUNwzAMw3CRwZSoLgkWua8ulw7pK0FyD7pbwUdjAkz9GHmVsfQ5v3kYKg8VUcZNZ87e+J3G2Ux0rYsA+yEYjgvljbODoBcl1XFPNrTvVduVkxNCXfqZdN0DGsHuWfrQi8V+A2dJztrMJp1DdY8dWP1qmqx2zAgBEj1Sghg0D+4w73Tmx7GXBWNOFvyDE/FhMYvzcsoD878yzLg6mAQmNF0wt8XEpgdwrnafc+bqRZ8MkH8HhvyJMYcFCsU2X+ZF5KPuRjwP4iUEY+JuI8rxx6YtpAMwrTutQnl/uE7hdVD2miPYvDecxnQKGwIf4vySag36kZRU/lGuL7XJ9sLt40NnumeOU74IO8s5kz8NtDabYMZ3l0Rv4QLw2WQjrgO1QXsYoekqizYQ4DB2vzXq2HYJf0kkH62g7sMnp5ZHqgpsLNkTLYp7hqhtzv6JIUWi37AddSEhO73k6gj5UztKM9YCD8YSkrNjYE2ocG3YvZxUp88U+qJlMgwn0sZ/bVpGGvwBALftMaBWkAdEyXDUAijPRbvsWtIajMeJHaEClPkkbeZ+do2rA/5p3rtSJ1UnpLcNMhsnK/ij7Bh/DD3adowUX0JU4YTONgic+jIORxKSwvyqmodLSFpi/jEqLGX4DLjt35A4OhLJVw6rsvbOoXsLTBWxnZtp4yCQ3p/FnVdnru+MolgYmWf/jS8Gtif8dGpvyY8yXG13SWul6OU5qxgRKhseh9h9y5/DyONb7iBLNK0ER1EWrqIglxrz3jDakWJyHXg+D/Le8nRyZiusfJMcO41liOjoh5RjIwtIzs4zO51X2d4BeDE7hI1ZdS7OL+xlioD1Vc84SRKWQxKoSEfWIfHLQudRvdruUvgcwrceddI2FVUkFJXxreUluweg92efZy47X7aG9Gw3PSy8ObEEK8g8ifB1WNLzZgFW3ov4PY1Sr5vt9258un8NNFGjealLsIYobzy8+1zk5Sac0lETG0aARe6ixlz0sarZyR1CtpvFCoLm6WUb0iN9PodDzsgqInkuVY+Jmuxj1sytdDY/d7SVbabC/hOLwMKZRRU/fBixGTZwdF3isrRLI0XSYi+EVy8LWhXzPuPxBMCh5uQaee4AOi3JufSAqrsfjdqroZf6dzOgCY/pqvO2JNm7hCpUstKMU9ona0Aw9oeUjo/OuDI4T5GdZXgHmDaYIaL4I09UWYq2WKTHl2XQPK717AZvRcKUEjUqTrzjB+XqlSea97iWndKFinuERImOQvxj0Q0aEAS1FVF10Tj4k6pM1ABssP9354j27LtmqNYfEFl/co5onhwxPHn8e2OMjh6Y0kOvz+t0kK2WFA4nIW05cuet9RXAkV7bNz8v0ZQYLejNdBDDMAzj9uecJi/yH7vmZ9MdVffpt6DTdXc4e5YwEKmA5XqE4ChE5j9mb0wYol1e9Ppu+7m/O6l7TqUOsENbqDSlZreESZazJNGKOs1GAuntoy+jERhRQb9O8fmY6onZNFJcuzANBSkhsYcOkWVp6L73r/ljYN05wimH8STOmmc6M6cDsquZ4SfYfskHGUIZ5qF3vWIgKixilKSJ4kRC7z15JcncggB1LAWmrNEsqMvSLPb8jmkKN+TI2UNgvqVJkOQC/p3IDLacCc2keX44VzMsXz4+eWE/TJlM2xG4QxiQ8OfEojoTl4QTxOPew7TxjF58m2dtQHj3hel5LsPuiEgSNx4zQy6fYS6D+xxELdidBloX40MtZKV6fjQ/kkC6TW8oO2vBBlj4vYYhI/WysEUGU9TC92vaEvMlHuYwaXb2fEO3zxA2xOm5UfSRwVEa0XXDTCvXzQsCryySQ6nZ4wVqSnT0jHpqOsjcvovzcNbA6QbhmKziI7oPBV76WZVcsqGkGOeOqLP3Vkn6rji+M4Rx2XtNHKXpG1/JvWrvx5T5N2pCSX2V8z5WYMatpHAvWxT5fZ067DSc4o0E+YRq1NO3xJv7UbxZsw3SnUek2nRPJOnRMWHuoH4gi7z1iJtuO0Lr3dH79RQwn5yE8ZZ5dJ6GkByS1bAc0LEW+D2SvLM8vpehonOr8MRa+ARcqsSMDBfe3mc0cJZ07LmELgAke6TNa7LRZ3f6qeFhlkOF5sVHRUm/ZMe6G196z6EWDfTkbaESf6X7NOuQS1QCgcyvKzYEDJ+9bkLeGV+UrWNPA/xn+0GTbE6zy/mb0NGhsvi4+dzBjZisFjzZEdH8uLJMRI+qL2MWkbBnrbenh0WSITKgM0liPIU9SplRC3TRuYd4KRe+Z35AIPJ27vRIXFp3KM3/HEQuyxLFRslEYLiwE+fxjkZ+uCg02g/1ByRGVI8kPZ4HXF7L0cleZzERbOTKCf0cEuTwdhqVyEBJNClVHYcvwCSBgXbf6TKnNfN3nK2HFkRgzFjV5nlZZBa9uP/sGf8mzz0IXPA0aHzX3p5tQWreWINAh23xeTSxAlNwgUpWyO+iPmCOQJoQIrJTQZEPatLJ0G3f4/hs5uXbjgjBTjoJQdYoN8NMUBR+Z35Yy392MHDOrtMTRPq7nbwj1zhDOmLQco7nuWrOTYsxfDXb/ek8vfTQgYt2uNLeRUL2903H1rlEb6PpEwvmgHPCB9eJuzQ2SHIhRVh6+WMLFuN73iWX52Y+eFWcm/+F92HGLs9kfRNIvzUEHRs8aXuCEVmF66L7NV8Rza1fCci2LdO0JIy6WW4S/NzQC11o+zFRyMc4aQ6qTYheLtwJs+l8JARnxJ8wDMMwYsdgZ/2yuwttSRotgGJm1kT0yQIIz13MwaXbwybKmaCiKcyjs5OLMXRMYLWlL69iPOBofxWJMxL8a1Y7z0I6reldBC8AP4qkhEWLOr+Y3U4ceq7o7vDMC84e8pv2X95LZzUxBQwoYnmpGwdfEbR3oAFvyDDMHAS2lHeiIROUizP5djpRVfgYokZTpibS8338BEnybSPXYUfGIELkqrirHqgSVI0lEuJGf38W2PunAyppQHYLidoAuZ5h7DnKAyqZQW6qln57qMqe1OWM98vs5zc8wqPzQZJtYiwBMpAHUkE9NCcSyBpBUPPBvVRXIWTDnlySjqZE5NVC5pmWXX9wAvzk1pYh1UZZibjFF6lhETcMk8QV/z3DJtunfyLvtbS6dvh6uFnQL/Swcg3iEEg9GRTXnEnc9wojVUqMD9bB0FpVY7V0pe2C3aYH7k8/5tKdeJs9EvOias5n4QuJWq0RcA16zcSEx1srD27ctSu+mAXIQdlmuc+a1H44ZVDa6mZkiJPl+2/OfFOP7p99JhHjiiaJTxrquOjQc+EenYS3H9xhTm2fQcdObuIw8c1G2Cp2j6Gt8Lf1tgxSzeNrfNb+c3sp3ne/REnwKjVP5h3sWub23Cu4XbQJV0hrN/Md5HsX1UH1Wcpd5yFK/YJDo/SyeKMaVWgvevWTdoMG/ukgrJRxYv/7mVytFYnHQ4EfZ4gXwBpOhMtDFCRLsHFDZiweqmW6oSqohiHg6MvjPYN+ZkvkUEPsRW7lDFH5C5lGl+l3jtofIbHjVU1TSCBqe39ZCN/k54R6VWeLrLjkhV2Dt8a0KOaEH4m5t4tUmtPbtZVlUfhXOmnQHlaOcmx8g3eN+VPoc7mfWdN+FrQ8LzAtIByCnVE3YzV6nmCr2Y08uQGd6fDDk/KcCc9mfNiJnQXE4kvaO6FDe79oyoJxN22NZXWLbQBXOuAn9D0LmGDsage6t5PEqVjOzfGxLrnixaWUW+ZzqvtaC8lBk2IpTLC2Lm4XTkxNZsdv/cUwUH9UvJPCHwcBD6caG9JDuWqX6oIXPsldqb1mPyh6vQWqOEpreV+t2ZhxznPz2hrsAE7Ln++YUDUYF38pk8ufmyaNsmJHlLP15OA3z3wf5qXyUeUwvXF+iu4CkyC08IC3UmTRr078GeBJ7CKJAoHHq3fkbVAPnWvOKP/j7DAF+pe+Snk4K/qahgqqKyxoSSy+xun1AwhLZm6LFA16gXio1NRfwFjbdveiNHZL4qT0Ap9m46EHo+MGtIa89xpgUtTBjPal81xjPYnbfhTXyBX9IMCdxIXO5y5oMS7KWOHrD/2wrO9TmdwvwCtsVu2+ldawrlWYaIiYcV5pM35yQkU2i2YWh2EYhm/PUb8b5A7YSC/ba5FgotFxRCZwJaJqBh+4jmx5DXdFAEoYsLPfJPDy2Y5BZ8UB999/4v47VzmlqBtqMElizbiAan+f9EDL7yQaLxbk5dDVmqKjYisxk2pqMTP/1/+ofoZdjY9GfJhsOblL0/DUcPko3FDQVLT6vnwA808MvZXiUrBEXfshXE2CKWbOP73JMY+R/MNPxyEC2Psy/aHEttTQjBXXnKYfiK4+XGqsQwKd8kTJjMC36RQi9sG3rx/w2FaDvSo2jHrLYcETfLgMCMZ+LKhHAk6mGDbI4/JUYYNSI6bw5ZqViG3dtfj6TitlCeQ1iGCWOleygWWmJWwKBSGaIq/DysijnOJ253TSrRiPpHBLmBx/W4JYeesj5K9QDTEzBedIMlA2BuOjody42Js6kpq8auwWzVBgWzUq7rlGdcpq+SZdcHOlW1rqmSTbFaj90n3AlPWm9pkYOYSaGeBH3zlzu143LIlicFyLMY471e7bqH7txjIFpXWTkVc+oHrrdVAgwqixXgl9B45kxD5OYngZOoROYICeK5BiKcsoHXU+Fqz5gITt/SikcXuN+yJZhAmQcp/Avj1OVlRGqVc3TyHU4wZv49m8Cuv9wWaeDYSHDjU11pd1FZc0wSGskhh76XhfWD6RL5/v3+XIVA4X+OatQ5LckmkMtgCbKt33iXWsQOD6HNix/z5dpXgfIpxaXNRYcYkXKz7cADA9fsNzG1/CBuvJ/b/H/PU7HPCOaVkfEVJoIUOJQAkidSI+hcV4db2lUyja+pz9aavziNPr8/hS9pFOhaQPK21H10tH1Os+tIlqCPFoaqjr1OaN9P3KyPwFrR+nWqhONHvjDv0DqwVlXoGBOvcb4khPbBIBMQHht4CwUabh0OGFHX1qyy3cDtPt9VqwkjqBhiBV2r+jVZIYvjUYa0+BURE3R7PQoINQXtmycE8+mlJMAgzVM7US1MF1nfwgClIW/ht3E9RcdjNVL5c5CpSLcGgW9ESfQDdVD2sEzRaeLH81QIrw1mEU3SeTG/qExNQTm5ydAKvZuygoydmmdhNno4dJv0OZ57Pw6r0CxJB6IHiJ6r7lp9GiAJ0zxdf5ZPimSse/ISAk+YnheGsHH8hFynbAFz0Nl9hvGqfKfoDmgt0RMBxEDgqgIefKBmQ0tcKHo/4P8pmEJr6+mE8yznLzfjcgj2g8n0uoLfXc2DUO0JgWusY5QUF8eDtDVS9cMhj6rS8bW6xsPuuPkNzV8ALjuIIQuExDf285ck1sBXauZK9vavwYpFheUVK8do6T7brbBLXX7Dz01sYb6LdqZDorDpHe8vUKzt0YlZZOLIXXRw6mw9CB+ejurAscibnqTY5qVWAYhmEc6ppaqnJs0xMifPX/r1AK7D/221HO35s99PMUFbcFKy9bPW2jkjqMdgm6PXQztguFzQKENcdUQQ4NTJfqdHTFH/donCO4COWBQtddXQOiyH/LGuxLDx8PPh+fv+7hQX4XFp3LzpVqL5z78up0W1SbiSLIJ96TOIw2bfehevmWj8ABJ1rtTKuBGV+tGILF7CzLEzORWxNHbHr9XrBSGfk/rkLEAOjJhCowLlkn4swu8l4GF6JyY5Pzj2KVqpM3UMFfiQ3ugSH/C+Ipqd085Se85pRjA7FlI6t+s2wkdx6wk850yE3Q2a84HAEr5Y8eYDtGpzW0V/ThufUmmQdpKZTivLowc/npeFMLniz4/uT8Dse6qltBU/2AnUphGd60MSO1Sn5sDSGyCbyK4l9WB64+K5cAge7mSCmUMBcmbKZEaNdMUjb96dnnBpl7d5SQl8JZl8PvRdQVAOUaJdxE0pB30cUW73aU/8QGoCtBugt4GshjYkzkx/k5+LfH5LFCIPz99OVpY5aRrNJ4mWqemD8ZRSM9rJAwUw5c70QDnEnoNPYh2PBCrFcd1+VzKq1tEJ1k282TtLsfX89TqYILioBSnhGFy4LipXtoPLhM8l9vtgaVdnMqdGKev/vUwT+bzOP2YeFYb3EnMV2RnnSVLTuoSDy5OR/NlRnXG0KWq9d7fdsZbqF1+Hry6XPEa5hJxVdTruj8i6UuFunPl8jKxStiPrSt83pFjVOok5J4cupHDiQyXlvq3lqAH8X4+QuDEznhdSS1UeeweHC5oAaiOQ7RdgIKeCrxatDQDrd75yj/4FTg6TZ+BX1njJbCtxesI8BaUOzvx9qA6mWSkN6Fe7hHUfg61w4z12TGTYNfGq1UoKrERGykAcsNeBLv3DPOnv5+FEnp4JgYIlHILGgdXEAZh82GJBMY5w5fajuDiW7qxTg2uhE2m+VC4CBxk2tcNH8w7HdKpI69zhlk6+spj77SXB8+S0FuWHvL2IfMHlPSNqUfinOBtM2effVBISj2Y59jJDwS8wDo3krokIMgbOZGleVS1gikGmdCWk1eTG+RRma1+ZPcWJ5gJyMcUTXfU/34BoboZI3ILVfnoGkTv8opTqfsuJpWohjw6GEXAnMGzD6RPxCyhLvDb9W5kgcr5Yhu3TgHv19OSiWVVxQNEeDT2ArUSkd/EnhPxknNKyuyYhpDirYU5w3lSJcpfFkvRCKymZftCtvjiDgx+14r08T1/0hQogMdKCZBpe9rvYaK8Idsus4LyTU73rqJB8hZv68Qg6ii8AtZZqnjTTNDTnl2t17HbvOP5sUhedrAJtQ0vpWahACfcwlIRXCP6dZyj9W7LJN+BqVllbbMfUn0KGSgolQdvIaKo030rSV+SwUVXRoQtSiWnKhDI/h1HOoEkdG4QbZyAq9o/I1s4QTdjMaIrDhBKmj8F1nnBFGj8RXZxgkEGs1kfRZ0AY3cyK6SIL2gcWFkKQniGo2pkV0ngd9ovJpsTILuC40wsvxCkM7R+G2ymAjiDxr3Jlu/ELhH49lkw0TQ3aOxbmTLiSCdoPEfI7MniCUaWyNbDQSe0fhussVA0L2jMRhZGQjSLzTeGVk3EMQPNB5MthkIrGk8may/IOguaSyN7GpBkP6h8cHI0oIgntC4M7LrBYH/QOOXycYFQXcADZUs94IkaMwqiyiIZzT2SrbuBbZonFQ2REG3QWOlZMsoSHs0LpXMiSBWaOyUbDUS+I7GD5UtRoLuA42FkpWRIG3R+EvJupEgfqLxRWWbkcCAxlFl/SToWjSKkl31gvSGxnslS70gbtH4qGTXvcA7NH6qbOwF3REanZLltwTpAo0/KouKIP6i8Vll67cEHtA4q2yoCLpHNDZKtqwI0hkab5QsZUK5oY6cXKFkCSo3ODHNsXCdCW1uqCMrp9BlCRq+ceLV+8KYCZVv6silU9hkCcoSJ8JjIb8SGtbUkZ1T6F8ltFnjxG/vCzETyt/UkR+ucDVLqHzixL33hfUroc0ndWThFNIsoeEBJ569LwwzofJAHfnLKVzPEsoHnFjPsbCcCQ2n1JEvrjDOEtqc4sR/3AoOhDJSR46ukA8SKh1ObD0WVoXQpqOOFKcQRULDb5z47n1hUQiV39SR905hfZBQ7nBi8FgohdBwRx356BSGIqHNHU6881joCqH8lzry0xWWRULlGCcevC9sCqHNMXWkcwouJWi4wokn7wv9JaFyRR354wqrWoLyCyeWHgtXNaHhP3Xksyssagna/MeJDx4LqSaUr9SRsyuUWoLKDifuPBaua0KbHXVk4xS6WoKGQzjxy/vCWBMqh+jIG6ewqSUoOFAxEkguDQd6RgYkZ8aBA0Y0kkvmwBVGFkZy9jhwi5HOSC4XOJAw0leSc8KBTxhJSnKZOHCNkVFJTodGc1m/IugaNPJMdpUJ0isaF06GpFRMAgPJSErPZMCAmaQcMNEYSElSrjBZGAP2JOUWk84YSAuSkjDpKwNOJOUTJkkZSL2kXGMyKgNWJOUGk3AG0kxSRkwGZ6BfyJdbnrIXWu4T0yA2LMTKmLw8PiZ9cjV0+Nux6fznPy/Df3GsOuZfHG8vGv3fmC3Wa39m1ZvG1146iW08ppv4r06D6G276T+2z8Pt2ufctfuCNT8QfgHbxWb8ufE83f/ieFj8O2tv9T+Y4M+sx3FbrWU//VeNT9bW4cnInYuwXWpfV8VJ3B7UbzVYuqbKh6WLHKDLPKALYyhd6UGgPSwdu9s6f2j4wOGROxjKg6HVzREd9feAM+rIOPoy35mxMzmL+eTWnCunO+bCqc5wLJlzcLITGsD6TnW4ucY/f9WYwUVZeewXAlVVG0En6w5crlxwrIVTK77jZsk39x67pFD0VA2ToL/YQI7o6lfGBpncvJf0o1Uzy5s7e6pSFPVO25NLpTpiUNkHUg0N3WmmtKftRz3CcutSudiZMcuw36Id9xsL6hZHnRd9RRzf77Xgzlt8d/m3eWcs0+yBm6gkLzhuk+CwSja14bpirqKxuIn9qWNN938cvPO1icUPnoOdU8vNHj+flzUIyc+sytLSvoxRsXeddmcqyeBUo39o8CaBDFn1WzonOimoXuCUFqEemWS+OBEn/Q3zkqeZjDEPXOL8VfdKp2xIUT9zR5oZnSdiZuV8oF8xzfLEmGkeT6wyF05QGcVOP+C43jL6FaAH2UGYmLlxMu8qAdmbGFSy1vfSBavJ8nzmMS6J/bdm/vvJJyJaqQiLqGkn6JNpn2ixo6qIxay69Po9O1JmwC3wkDxTHv3Ljj358oHBuCMVFtiTRhbKPWli4XwmOSMeSBWVhIXv2PbXG9Z0cDvZ1zg68gqioHc4R95DBPBsQ4LEsV0WN1V82C/DYV6oqbY3/Vw+AHwZTvn/QDurFMdYEUuDNkGZIWjwmJB3EDv0DhH5I4Qog76+Srk7d0Sn0CqUL2zFKxxH5AJxb2gR+QgRK5wnEmOAaB1aQXnHlI4yHGvkDcSj6Vu5Q/4MERyeF8gdRJrhmFEOoIpnHK+R+8bHcJ7p5/KEfDCiSThHKY7BEcuE9gLlA4KMx4BcDfGkeocO+dYQMsFzL2mnjugmaCcoR9jJPuP4B/nKEA+Kdo78aER8gXMlMYoi2gHaL72MG/nOOP5AvjZEcX0tV8ifDBEGeJ6RkyHSHo5LlFNU8RHHJ8ijIbbOwMMr8lcjmgWci5TGpSOWC2j/oPyH4AIeL5FvDLFzew4gTxUh0aAvjZTGzhFdRNujuKniExyfkXNF3Cc0QW5KxB7nFxKjGKIdoW1RRnMj3zOOP5HXFfGY9LVskO+VCCM8fyGHItIJjiuU2qjiiuMt8qDUQE5xLn8jPyjR9DifS3FsFLHs0d5Q/hjBhMcWeauIp4neISHfKUIqeL4nadfPiK6Cdobyw9jJvuD4F3mpiIcJ2gXykxLxLZxPJEZmRJuh3Uh9nt2NfGUcv5FXjiiDvpY18t4RIcPzO7IZkVZwbFB+GlW84PiAvHDEdmDgoUH+4kQzw/mXlMY4I5YztE+Uv0bwCo9r5J0jdoPeoUX+6AgpVBpS7rIjugLtGOXbbMVrHH8jF0fcL9A65KMT8QDnfyTGoIi2hrZD+W2m9CPD8RDyxhGPC30rn5E/OxFqeD6A3DkiXcLxCuXQpMkMjorcM0WX6Vv5inyAaMBZJMZgiCVohjIpATyCXCGeot5hiXwLIQbPGyl3lzOiM2gLlErZyj7iOEG+gniIaAn5ESI2OO8lRoFoFVov9fnCuZGvGccK+RqijPpaLpA/QQSF5w/kBJEqHCPKiVLFDceCPEJsRwYebpC/QjQO562UxtYRS4c2o/xTghkeM/INxG7UOzTIU0NIMujLq5S7NCO6hPaFsldb8RnHF8i5Ie57tIDcjIgZ5zeJURzRTtDuobypKVUZjifI64Z47PWt3CDfGxEmeD5CDkOkFzieo5wpVbzH8RfyYCKgn8sf5AcjmgHOF1IcG0csB2jvKJ9KsIfHJfLWEE+V3mGFfGcIWcDzo6Td4IhuAe0AyrGyk/2M4z/IS0M8VGiXyE9GxAs4ny0BiNXmQJ+bezRllOgrlV5puVs0ZZQx3TD6gXNyhaaMHvc+CoEJ0HvUct9QZluUKX1S+dhyz9A0o1Seorz1ouXelDlnnJw6sq84Kxs8FZw53TF72nI/cYprnNd0TOl15zGeapzif5yDXcvd4anGqdOO2v84l17hf2ytNyVSadV4I5to4X2KKQ6ifBKN/aC3QqpaJlU0s2BKHHVIlYPU2GLrC2lqVfuVhqgykRho3MkQU5z7T6S5tbVN0sJC+yTP/TAoD1Jbi6ZeslbNfbqJRqaUJQ2Nci81rlq7S/QGqEv0e7QLAN+wJ4wBrySssKJTAheobOhHO2WpmyiMbdxGF/iG3LsTF+Dwa/SVTXiO21jzuTgJp3U4Qoc1LLHfgH4bt/SL/WllmepMs0j2MY0uNVk3SnCowz+RdHJQCY8r+vHYjK1Wne6cchyir+1I8vG00KPXLv0GONVn9Z2OmDCw8eMDqMfGz6SzWsM4BLG63mFpxttT2sXzk9O/OlzsNMJjOk4XeldEqoPabLGs7U5ntzgTVTVv1Ge97kwutjXf4JX/TrFq4u/8R99dvJaL9TQErTbtxiT9vGIS/5lY1xrL7pD4K/L3BXns/yXf7sfdtpnD5ms/Dk31nb08pNN2ubkpVzs9uRz8wniz/7j6M3y9fqwO7Ph2vou5k/42PS7qZbdYXzRxv+02R48vZync1T/j7qLJ43l5meYhhWFazdWP7unXSvYf+bRfT980yXyVxWK63H260NfW63EUNXs3J8EUIKeAbKEwBFLueaEO64zA/Uf91nqNg9bLoN4cP/QmMoLvlEaSrJ4NPvk37L8sCnUEqRrVCTvWJUIfL2+qSzZRI7hYpDe+1wn8SqYhlagFXd7ml4jhA2TQ8w0KrJzian4D3mMbNRgLGS65S1pLoygDbJfyFU/mKErmsIr+/2QgXDldCyAQbb/+npQhGRPgY2jQi/fTDo0VMlxhja/d3XpU4g+mVvDwIYF0TDYnEKBOkm+U9j4wpOMzTvgnl7ePfyPD/bxOXhq2q+YbanqipRtby0l5kKh2LVR9b6vIHxSCDIQSPKWzFwaPL7pIYxtNS3GcZnnb3+d58iCBQBkygh/ayE5oFT0toq7iUe8jpKvvTnSLKcDv73OfRD2FqyYUNO2HqozXApUI50Z1iBfriR2t7rhJ6gVUYbiiFCu/ImF/+z88w83yrZ9ifBf/xpO6k8SHFrSTt2sYXYtCxgCIfqQbc1XOcThPhKyjVrNfK4/jz7hu/Jrq+IavUI/xGRc8I8fD9VIeY2drDOo8393UwGRoBBS9VpxPfUU2JbZf02zDFF6YEhhUStBLHWHi9+ISkQbJKaQSKchwav3VP+c6B86nZv8DKD/ayDZ+jbrtxX4tGa4lsB9O6nLxywlEDMfQwxyz0S19vXSd3L0WGDGLtz0jjumKT9DFFcog3NWy3oEX5bKcDXcrzR88j0gauZCbt8E+YDi5EQ/Pjic3BIKi8FOTDsXD3OomrqXTRcc+y+dWzVOFaMroVaukJJAQId5cPKRWD/NM7kDxcFIhgUA9diiPnjEIAYq3FqMzRfIjUYNsKGl1rb2W1C3I12WAtCQT+0QXU5LhvZGjlsDnwcPNtnThJVKsgrRHcCfvNKFG3Vyj0CbOoJIGQ+oFZUgqvUunVKESqTNQsuyqSSVqqbsQzrMHzG8rB+jHJFBJm4A0c0mF+isRqLMi72rYO6lZEYouE/Xdt9H8eGHCmh/Lk32W5fx4I1BXiV2VJc5E6JSpWuFEVLoWSVP40ahGVyLIYF6HQgZP6GZCD7Z6p8A9RpEeQTZVQLqL4ti+07HSosdPmIHOAQr1+/BK9S9N0b07rSUVu/JoqqLFoCcnXbcaf3eTr9OSDA+JdCac5Wi5eDxJx6B/CR4gzdgn/qjq9q83Ep1M+Lu4ZwP5oVo4udDdZJL+g0Re0HhFY+zqu78iB7TgMt38rUeRC42SSdSViP5LEnpBKfUpIFPsid3o87exlmxjAE2qsepK3MLibhiFBiqOo3AWvIrA3MersfLehEjRbBdpjaIZMvWxKdrexzVZ0vptZ+52CumYlx05Vgqp2g0nN5OTsbp72yehELdxP+/p1XYgp2yeXsKpPSa0xxPwk9olRrMw0hsByAf98ZYN1R82dV3zeuP+wGFZhmOcnOTaoG3UtLNcf2jnaVMtbpUuwm+wcugUvAPXBl35v/RwXe13F4k/9TX0/oX/VKPuroM6h7tYqQ+ho8765rc2ctFNOBqT7a9pxHp2MSpB0NCyBDnZ9cbXPjh3K0Dv9mgFPyyBt1NBmjeibL5YEKBMfMCFPju7/LGstqRPBPjcFIxtMlu7JA/U9BLL9MMJ1pxTq39AgrP77kxuQ4P9q5i6yH4e8jzK70jiZXBTPerpgnyBa1oMRzcCBbWkjuleTn/y64R/9tXvHm+3j0eopqSmoCVquGMFi6BlGQEfoXWzCDB70nDc9O5dYvMWm5NTfz4R0/2PfWuXRdC6FbMQr//Tv+zMGW0lCXHvCyX8GF/auZNLyZGdXH6WZvkVor8Zi9i0mGC5DB/AOHBneetJcl5BdSW6HSw01Kk1tU4O+91QijXnSoz0t8MOiQamt1aN4eamLWV8TdkaCp0wLVjOX4jsGqH4DcbiLq311fUtpDvIIzDwokRLyW55RygeQUGOjkBMYBL8P62Eyccbp+lqsAr6s7+CMvPIB6DMCForJYS85p8lsPSNxjhe1iixkLp6e4SfttoAXu8E+i7uUf8QjnCpCe+g6GZSZICFXHDzi1+eCg5u/Pir/E5PH4Rp+hlJ+bGkzjZR7cb9if+LK2t6Zjk6mJ84LUqlWFyABH+U6yjECy1RrsUZqeLHdv3+ZCB7HyB35Ha3tx10K2lVrKU4e2a10EtnhY48ZvGEsDjhVVXX6DHc0SdI1zRlz1TKSOzj8fexT3p8keP9y2Liy3F91vaK052T7BpuXcLibpCpq3YqjRfQ4CsNBvnoRBq0p7H/hNLgeADUzUtfLh/8lIl/0wm8ooVhD7PnSfdTByfP5Humb+3zepcCtrsno3h0xh6YApdVhGGiE1Tk9eebKvYPkIEL/ZeXkTH8eWNaDnjXXRK2PIffU+fffc6POGDpn0q2/oob6qpZml5XE+SJm0MQv67o1tXa/FFZaUe1UMLcD5sFqHiRP2RmRaql56BYo5hN58IMoVvmbBAWQRhRu7f+hk969spX76rXy6U0pG7GbAPLwR6f4ScO3uJLjOKaOFIjXvMZyYoBiBB0BBLKNYs7Iy7QeFFSnSjHU0DKuXNECIThIhfaJrtHN3HhtW25Dv5MB8TPlg8vHWKw0MzpX18xJTZa8oYEFo5lAPeHSfzav2pjgOWVTrSHmusR46LxGS/FRCNUqL7KYXUf5gbTooWzTZK9yu6MJdaQYz3G4VT8LqbqaTqZ0gqd+683DI/j0+Ef1V2BH1+lt2F4LkqOSEjrEkZ29fhbYRDmnIO0THxF+i8z2pYr/WNAhd5QYPWzqYwBl906tTcBwwTyWc/OUdbOnfvI685qU7H6ske5f1oIed3auW8fAG140BzltoT+p/QkKEcjXRp8Grc1HL4p1O+ULIrFUn7hWbQhX7nfP1Ku/ck40Z+/A/uJQWLMsF0w8/uKpv79dqhtjV/78/diWhZX+teIbYT7AeLf1J5KshUhjuX0QblxLnG31fMLA8oKwmWBctEvZnDGLBL7X9a8ylnIpipMlZfGhqLv0C+WGXXjl0F+XBkbn8efW/Fc1D8atzuX8UfDb1Nj9NgfX2bOfAU78FnljoPD5TFAmK5LT+LOLIYYaohDexGQrfA8HcA2K5v99BMdGojWlLFfAUDYezbeX18/hUdpcZ30avoe134PPc2Dn0uTtv86FpBJU7vyhQTz9In3ZW/SKbuURmKqU34AgpRzHwkAvnFqPbThYZlFlD4mh8flGLhtAcTl4tXrnrMlBEcAypuUYvbSay1MIIxMyoXCY7Rp0KE+uYl7Y0I+p4B23shmy0yKAM0FcaHslTY9f51xvpKFtYNybuC67s230qVjCk2GgubH3pTbE6rKaSZEXzEXubncWmfrcy7T7HJTEDWyvjR43E2KeHlvWft/LQ2dhsGg91biXEQnMlJzfdWOubZks8PyWjWHW+ZN5XpKmQOtDf2t2pgqtZe+sFvYHOwmq39pa6Q6X1Pu8rZ6435IzZ82JFU7LeaC5naxkDi9kiG/+T1sBTxVUE6InduHhlMXbJaaCXnVQWV01IVq8qGWUBsL+VccpZDFVnUcwxNWdSL88k/ZNEucYidCWOrsl695v5+7wGUvfR5fzofBf/mDH/u0t74f5q0r+VMzvKVXOpkJ+an75vvU9EgL4UefNT8TAtbbMMhvwBfyo5dJ/ypsgraP2Zsmy2/apeslSg5KUfwNwnXrf5vTf9Uw7Hl9MK/iXL2zbv2VvmC+Z9y2Md3m79YWwxi9jCIUV5HOHPRExrFzoTviJyAffGgl3lQoadaxv99aK71i30/rc6nNh/M6n116Cc74V0f+lT5j953kj6ZtUk3Ne9DdeCgFCXBPAgkkkFsLpBRh2a/rX8f40OJTmN06SloyojQX29GHnxO2Dd2qjuSJ0iUBB1DgR1XiboeKGBYchHPcm9Y+6zSQjR9tQ5vdKxlTlMT3gef8q42wBLh6Ap9vHMwH9M5nB4WTSxD4ump85W5hI7z6JZMDlL1kuFBktXC3bPmbXTBUvZAUouG9wQvwvkrlz2X3kDXeXL4+UboNfsPN+LjfFkzTYWa8VtYOhd0j5uYT8fXnV3zMTpQGSuci138VvfZLKSVF9JBLEt+bDVYQTRPK1yVnKcRVgeN73/NLnLkMfi6WglP4zgQlgbzPTJ/D05CxlQJlXQU3ez7H8TGLVR1r7NHngCZtv94rcH63DfBQyLW1JB6J9AdFEkgkt/2jTNRk7hCW4U5hfY7AEA8PzAJmrdDGCl4V9IRYQBKTNpH5fOOXqPtVnXFL1i5LZK4Vw7axXhsLRiD98GakVo70TiKy6R1xkGwdrwSusTpcGp28o8SAjykDIlcR4vuQrpMgUi0ATT22nT2icpa3g8GlT1w6hEzt+F5XJDpasq3etU8UOhQOWL9TwU1c0ejkSPoZXbdJRaqTETGc9x2GWpQ6IRC0Y5ORW6Q60ajlLVinqN2/3ndLvFQzEqmO0FfnpqpbKXWYieq8Seup1Q6xXzJZyzTj9XLHOEbkcol1vUWlI2jf1k1RH1vuGvrw1XMQxa2dhqYfpxz9onElfp8vUlkdSqlDZOcZTahTubWT+AL9UqB1abVjIDbF68C9l1Yxjgb8ulAkXeuplNp5t5QNaz3ThRKNFpFDIU2aertjXCtUGrwwonMO/pVeqa6vLdcRoJLIrtPkiNS5spjo1RElsc1EHf7Y8HQ0yR1yiAld3juFN0GyjTU/3a4vWDwUxFpneRdBPvzn92ISVVgkpw/YsloX4v43+a6AfSQBeBqEtA0Jc2YIPoGNi0/RNE5DQIUGMRkZQ+KB9AwMlhGrTVzMv2jZ6rVaKBVC9e0x84oAP2z/y6fsbSTwleQ0yPO+UzaPuvB/CWyobLVB5vnl1fbPCgwyet6NvFgP0OHuzWgkfRrGf9lvm4YV8mf5TtJiBUTeq6d5Ix45VWrkvzT6omLK1QN68hURG8AjvBpJBTfm1YXKsrE+oKEEyryiu33l8whYYi5dyMxu+GzENbMJF5zI3JE0PhyvnXBcETPuz3yYbxgyvEPfooE4h9vSnGb0VO6MwBYtQQq6mYsfvFiaOVhJlqQPAkYT+VEzmGL0u0fSearp/ocYD/ihwUxC+eHJsWngD45RPkagFwvFqxF3DKWFm1LgA/yLOCh4JRwIDZUME2EQIseGqUNAezNF5C9HLl4ecHFJA5MFnoCImLfyTtPqyaXS+eEm27k/T97VejSXp44XRjLCbLcYLQjygkoQGJsuoBb5vaxKneFe9Qtbta1nFfhnqS9UgA+fZbgvGQGyaaW19o0pFiRb19oCrk3zhNOVk8qXxBZcEzylLSIKvxmX/7g+K2WTjfl6iwwF/lvwd/KHOe9t0UGxLMo8dGrjfM8WShdayhcPdQiMqWeyLeje/4r3J+iJ5Qu+oJ1pJig3Nw1I7V219lEiZrnXCkfTkfALne0aCQhyzzJW1M9cdC84VSXnUn0YOXdz8RRA4bULJg+8Ld1bbsiSZdaT0cJq7oP2MwUx4lxB+1msMRDnHht3oLTonu+R5cIGAVoOzv2j/SZRQN8RKlp3IThENY+1RZfXOTlTsydI21sQ8Beg3IH2yQSdUE4Zn55KQxXfzJAak+CD1n4Jmos1/YBzT031cdsbn05rHpdn1DwBl+25dxRZmuei8NpyDNHDC/6mRpSfqmtS3uctAVSoE1GAPlSnVzk1MVh4paLednMce+HCPBQE0pAFw06kjn/NNwGb+15aOz8+HAlmhDCf/b2xxAmzLD1hH3qHIlmAVXI3XgcJXFaszSGYJ7WQr+TBz2UWExyAvgFA4KDI+lYGfgQe0CvW8jOZy15RCJl3CVIHcJRxbnrEAQ0acM13scEshB+dEEVKy+VdVqS/t+mLdVZm+ykq7A8o7MEVF0xMkPGxQ7EBt9cv7yoWGpDE1PQnUNoAAlHFWUPZAhwFOQYTf6CiRYzXTuKlL7Qg4AAS7+7+LZqbEswEdZ9IF7SlcQmTyhMg0AHjkEeEPTwWCzMr+0mXYDA7c3853ARWVMAA79UgJrK6OusHXgA1jtCtMhDkTchGDyQm2mzHegGO/bXBZtIOyKLHjcO9HO892GQy2PlbbIZk03JnNiCY02GYntKqYhRuFdh3318y/plw/Tt8jr6edbH6jLvOsUBTZCMWvvXhWK6+pAqqZHoJ9ggLGTl26luSH1egvbG3QHYEWeKfxjVMcIKFa9Yktjo8vucEVDGwB9UxcgwBYxF0cgszar7izZgrSzuZVLsXxrdnCxgJ+zyoWoAJRmo3f41ywOAAixMEM8hMHSfQiqyXGM70p9VU5f4lZti5L+olVGalHaU+dgklCe96VEzoiLCpBcxcZKWwMeSRnPMCIbzmRrxv2V5+m8G0iok0FEUv6836f6YIPkxe6Z50bv5B1YEuH5ZsgvQ7OKmGrsQfqWA9/IVBO+nMh7M64llJbzI6spBEzkn/6TRYv3kzfE/JUlN7BrkEIUeFJaVLdLGvGLIfPgSUKOD4XsmcmaMI1dOFa5QIpd3FOeCs/QByGtWYS127EFGo350/MmQleE2e+Jk8yACshFi6tj7ClmY0jYZOXDQRabHtRRPKawQ6gihuHIqniS0GM1gmRlUN3b4lIbF+LNhc2hE6856JULb+PdV7Sd2Gf57bVtOJX5We0Ltkg3uG2iV9EtFFP+PHQ7Dv9UPIznHCrA2G48GqI0vBlFUfwK/CWAz+84MA2JlTJZGG8Y6n11lDbFOha67t9OkYt/1oKQFJOmAkNiYmoK06L7gog8QC/uKEuIO+kC2APKtR8dzQnPuuJap5ZYnBXCnkYzhMbyRDRLUE7DJxEl1QTOAsJP5XhDaIQybEymbHJ7NaMAhiJd15mYBkIYVVFOkfgS4tYJ8DSeKmEqXeXCcUNQC+EMNgkSWNZbEqmaIDsFbA8IS3lMtBmhCPZwtyOQJiFWfZNI0g9s8V/UMe3KUn1FMj9wQ6VAJ52kerxy9BfiHwWY/fRjIH0LBBXaJVzBk6TBlTFsBTLuhzkKLTAqdJ2LEAyxYkdB/0jDYTuQJE5kF8Y1RcWEJ3USTbO+mcCZGZPVNHszTuOU2mmZ1WHYWM1Sbx4T4nUrQPDYFIi4q0zcOl5aBAwWNe57yc0XwJEoMBL1HQglKgMPH/rY/MkFO+L41iGYdVTQGgBag+oiyNAAuk4A6laNB2xYnh5hul9SqJ7Hkp8votIiINBk2ieClQnN9rJlDSEle6PONmby4hcmHe/I1R02UtFvg/nHxa/zrWmqOKcbVGtRnJ6cULJ0c3/puL/jG0cSprp6Wg4G+S+5q4Zy9GqSWZf47TWUKs1ohwkOQyOh+nWIWhZu6yTNeWGYQ4ZEzXk1dvoGMhUbdMFPZONE0xY/QmAxWAsYnxxqtIP6PG4NlNMXBpx44JRY//GrrzfsIxIkSzEb7LYNokgCt0Hh4diSD2I4HTFWMxwgd5yc1sMFSsORkhyvIciUWaj3DbgrMIhxMhicOQzbCs5aHZIUJjh8qqbxI3/Dx72OPhJC5RFybyDokUiwYgvXs7MHJAnD18NwzZ0OHTixcddIoHs2+zK28FrWlmDe314w0Zyqmon2MmpDZaqWVuHpMMps3wLZcrS3jTFAjA5qiRtjKZCvxFrlZc5XU1mMZuGoAKS+PHaNyQvEbkbNtoC4qxtAAuB5/pOayIwNxgoIi7+VHRUCQCa4Y308KVwyOvSqZ9RDC86Mtji6GavZUxA6fJ9/OQkfnfwp+i/J2V1c8EO+WGwpMeVxvWeWX104XqQkQe1CDgi/etLaEfDKoMC+bA4tAeqERCaGu40RBW7ZC3AXkY5m+epTEDXr/fkEquCYg1+IrgoUrEGSw2SnAn62WaQJ9IvaHN7JzCwq4V4XmAEwLPMWo1W4j/UcWJlENYpQ/4A1O//2be2HgtXXMinNF5fHc1HsiRyezmN5wCIHHyALCl32Qg/x4GSPZ3WmzXA6d+x2g96EwzmtjMOFQ9jN3UEARxlrP5H4JpzC6UEDR6NO0tAA2FRtfzEJH5uzmfaNHDYycKYifxNtPqFEka8mLzg7OUnKBOktA9o1l8EX+W7hUq5Y3n951FRYti93tPjJ7T/85m0RmiBScUP2zkQn8IPIldzt37/vDDvwCzHHwl2dkU6+PyjyiqQfvrO5eci66Hp8sSHNn54O84X0XyR0Co5PkwJG6Q8lYXpb2IzJCIBgMzo3hCO90uuCN9gMiZsxDEGRLAd+nZqPlyyI5Xxrun9uX9wh8yqN3wDknK8ufSrSg/4W+z2w2hQQEEyik79bfLRiRUzgHBzZtCiWmLHg3sVVwYVi8wawTbFT+jtfTnb1lACexlOAgJJvOSZwtFQuIn5zF2jDHyswmsNMyEYTbU4pFxNaEUBzMSzS94GPFQOHDY0OBJzwATOwc3iTPOfiBnF1aJLmAIzI4ABUSeFpj/4oNGhqH/QNQZV0A+asyxF9mgf4oFN9OtMsML2fScoSBPGV6AgnyYBOU2xksS+MNODLV7E+Q8RlgLR4+Gb3x7GNWfh1aAm1pFjWIXtqPBT9Yh4/9OtGh3tlv1H5Pg4LBhwS1ndVb1WPWb5FvVUK/6I93I4W+WXnXmXrWsV8EJpJYNHAmbeuBHhMuk1XWOlYtvhVecYWzON6ceK/GEP2ng/2NObzlGv6CWQtyQag0PVxNM/9DtbzRN0wFZ21Mwp31Vl8s91Y+fgRn3LptE/sjGQNaiGByuyXKvrYXT3WUuTMy9UbA03AVrw3Uwn3jUAH+Y1uUxcjJRY3KBxczh5fULSXIEmM5ov8AEYozQ/+bfbVroT4Xxh/oWz/PgxMH6KADu9++T+IL5rRjaE235J3GeYAhI8fw9y3YuhTJ6KZSzlu9GVb6+7L4EGYFpaaQKkbNo/UQ8T9pR97zWp3cgWpRcu9udmZo+kFG86OHLL175Jphh4fCD/+D1nqvf5gEkXVCmg/PDINP2GXFu4N7ClGbkrLhLkSBwBWolCTGicsHxPFGyxbJl2bkwVb6gFhajIDesQSmfqPQHcK9NC6tm/ADnOzGui/ZAgqUXm3M5ucWt/hRWn3ML3c/aHVy3xVx23efSjHRVhAd763LNF1YjpYkEYX35dSymjdyC86qXvHlzPTitThS9R77iJU0A3Q6BGd7AlrLgsshP5zsdA0UKdFUN3z9wyFaE+BluzPuN7xWbbymR6Z8FxhsSZTix4tMKRYtlEN2Cg+yxETsBuu/3dS5S4qcXjT4DsATXIbz3+IzxUQux2yLPsDgmj5PmOUsMQkYaVZ3GCPvxMGIEb47oLmGmi42Txu2IWffGHIt4tv/R4b7ysWGZJOnJxykaKQ4/aWxag2ZJVSSov42hxwK5HiqXiLIlsO0GLIwta2scsUsttnv4zKCBYS6FVHmM6UuY72NvWkLnHXWXSc+nBTwOuDsYu7qW5JtPcUTFlS0FUrZ2ALY4gIYAJKApaQSmGj8BNIwFGZYO6KV79pwame2xONGZecJyTQweAnYfjfGlloYlfhHZWEc2QY6Scw6Y/E3Jawr6ubaTH7Ibpq30cxPirDX6ZjLLhCimaZGPsjjC8CYr97vz85jK9grgUi2bM2SZlehRBO42IlmDA+DDtlkXYi+sndYKkfxeptmGCuxs2mfw0sk/ApuLkTLqnnL+jL033KK2N970inDuikN1X3E2X4ptd0mvSVRk8JkNHU/VqyU7k60ZTbbNjstxgUcpzLNptUjDriSubCe/z0gB1LvVqY2wrqu/twi/DJVhFc66jhWaolCr2TRFVwyUXJSRfYLGT8yO0ojEzcz7xmaGO2m4TWSnuHZPr6iRgUUvYTAV+hyrXU+T9PeGiC1xm4jVPo6/g5udg6H3JkuMTimV6Jdi9gbDyDcFq903LYIuKvLa7NQHbiP8+W0KQrF8maYfoajtvek0F2mDvgSjarG40n/0gcLP5CXU47NwEz3zTNEJhJSSYntQIk2np70Ut4U/58pjhMt5BYqeVnOHuFyX9Etr172ircnErTqi1Dl38e4/aPtP8RIBxGsHyebQd7HSWKozKzLfUsVaWss7oWhrQf+2NZ8wMmy8/ZNW+7x7BGV0Nc859xyOTm5UpuWmroj6i89cCA48wG3V0SfAIeMPNXMYqRCmUg5k6F+1ShuNkTGbXPm/5zm4tAqHL0B8GgWZxhFX4SU/usm08c1Ao9oKy2EyTAPSM1ZHy4SGUQDAjAzZMnxAsM0OoRVCErO2SnNxzZu0WqnCHox2n8OC4hnGxRz4guIy4oLF9thU26tfDn5/hItBQacxg7d3BljGZi2a66Cz+6zz7Sn87ufoF2f9bU6b9s2vwrYp7//+lZotfjhkZt4W8WKEMNykFRMgmJGiW0YeWJPKCXslpjFsrfQrcONotN6+1xy4MXIo6AnM2oXUHP0tVF293fJAdyE7EI1obdVjZWwlk8LkF9796b02nytZ9fMcdQObG58Q1Sa6EePigvfw/ZwVmTdyZlf6vQ1nhsuKlytNaXJOK9FRRDhqxcwUPCrkSA82+UlMKLBQLPFaT0dwBxLArwDGHA4RBz0c4orpnKF6z0aJeWTAWHfQbVPM8sriQl+cdrfuvUM74j1q1/P2zAG7LN7MexHYpc+6ppTvH9tCIW2Dr+JxtbZV/jlqh8yKxW30jCEe5LWwVRMyIn+WlD1aFP+8mzmrTK9EDyKTsEfceeOchVdZrqJohCwVIaxWYJPB58tkuYEDXVLjdUNvty0eP3Y4knRr3Jt1+EjBVBcqp0Y5J8r3b7j7s9LI+qu/cvcWw7u/dBBBDpfc0E/uiX+H2eNt0KMrtJp1H7txv3jFN2sVUYbmMCz8DM01f8zp99dU8t4+qiC+oqGAUV3X/aOEP69le5rfn5s5G7D8kqVZTqxM+VqOR3cyD/3UCKbQ8vqjSNN0E5XgRFgYSiwVnMviy01ePEvHYh6xS1VJyAg1KTAXgRYkFc5WtFlUvmxqcwbj3kUKNUjOqBUDFvdhlt+b0LfS78BGIa0ea89AV8FyJKSYhDv7i9kCAPKioVYcOW1o3CoDxUeo2I2gg8LGhTfmdZSCsx1VS1j1pn6r+qT0KszHmxwZM6ETSS25FNjm/greq39XtJkzoHD0rADl7Izm23WaT8VlYx8m3xsR7vb1c03Qz7Zz8L3AITsx00xnIje1TshB6QBIlUaxKVLwnkuXo0zSp9GVVYS9LkAHD759iEt4U54axMqPuePg80pB876omzqrgKBGktC/5i5MYmBa2pRWdYkJQIeNSRjLxnBP1GJQg7/Qvmlc/ur9cLJaWR+cA17IoPeFnE0Edx2eUE6br4BWNk01TnNqmpdIc0qaxWhOXdNKk9HVfA3BDb60Z4bbnoI2+78puCExWW+2jGGrLMY3xWwMkCQHpobByHDsHEyWTa7cJBP+DBQx8shk3x5Fhq2qsRyTRqN5hW3q+VPQcHTcOPKcrg8E826b+KWam7ydIO4f9odUWDYnpN06wzql+0mdFtY9LCoViIxojBwZ+Txjn8JmGkwjiqjqN7xBGati8sm6fRi0kY0PRk4vjxkZpxStPD6tQobrphfNFzjVbD2BfHluXWE0p3eZjyfWvv5Gt3tY+AUyzyajvFKOe3tkuAEVeHYrMmx3HeQflhfZ7UVA8rQUIOLHGR3DTZtDXg09QNqY/tbeoW5fBCKh4EqJ4FKurTTz+2FgjlQB5qtb9L3yC3x1vXiRbkriNtCgWlR8l8dNK6FNdXudfQU91nD4fLJergct5M2oXbZvFpvUp8b4cCuuWpf4gGBTm+zokshHqDo6k+I+YnS5W5SUrxbP7thrZACjWfkSlvxvNl3kEl0q52mkvyFWbGieeB7mbO7SMOTVaKF3F3Rbej0ObCwo0jxETzo6vuVuByU6foHiFO96ALKLZ+zvc27SDe9JsXj+WXtOSL62+2yRCBRlQ0zewIXfhXTB7bd1+ITlvOI32c54DzhiN3X5GP+p3f3o03GATk4B6m98DmdCmv5FpLQBXje1Bz8cPt47yjeIqHZijtpBHI5z0pQctjAFWLvBS/tFFF+VZSxP98XTZqswkSV/1RkcvqbLdiLpee224HXFbojP3zOsaDx+O21oPCEPnFGD2oWUwWvWw0fxRgjPjEnEY0MWv3hJM8TfiIB0o9XVQ61QGgd2C/JXLjuHDLZEKKLlHrKLq4GCx0g+VIMA4WE5FaklP25a2+0BdnGekfb7NPFJ+ZvCRwWKhzdaThBRK74/sH1fNuKOYYMJo6utlbinMwvSBCvDgWYI+JcTOMHUcnCIiRLuf3tpeHj02bT4SRQTbpTiIRom9hD2uAlT23ABLiy/DPDMOS0nnSujA7m4LnGjfqeqwy8GDptik1cbt2MVfu2aIE8OFcVHE5LUFsBFP0Q/wtFtdrjmQEMeuv3yOoCBVslSjOYKdzLiXmwQpKQPnX+WxKwztC4vPUecNwO+0ySgNq6voBS8Y+mYIF2R6k/wjKPrRX100I0T6sdN237PPXVfpWd7tGCaZyK7dvkdNmghOFr40agJUuhZFFNuymqJYkK4RnaB0pq+/7qQUea7rraCA4T/sLtXI5Vz8V5wc7ZR+JgEjECxdeezrCqoMQ4yCG/Lzg84nggVPaNZnBgYd7vDEWFIvJmbfhBrqdeDxTMdH+1R9VX8ocvR9v2TvsouYjCSWdRm0SGUb1+hAsXRApI5/lE4sYl269HXmQPsif4lGeqvrT0Tw3NpyL+rpR4jqTiu0w1JdDmSuDt361V96q6aGhGT2aVCFMXvip8eErgLqiio5g5mycdEEJJZNAKamlRgsEuuLisAH3yy1yXNlCLWlXvV6g8UgZxZNIjqmohmZyQFpG5E/CIUyFhF6GraLLRtf7i6xyWYiIN0d5NWyyE3ktbh1L6PShIL0dgkqtsROTEUcAI70nmiZB/f9EivsTwUBKspsEOWfn2EjnMpSvt40ihVNYSyHIlF+2AyAmZpH4VJWwagwLsWVGHbPiw7aZRTSLlOh2I9YQTKBU7O4TjrxrhzxtXHAqRbBWIyobtxMsyTW7aEoz5B/o0BrxE9guxthPju+p4DSqiODnQK468Ht6LNygqAQ0ct7NboO3gnPbRvXfd95zQEIZBI50jE/xhYu3KfLG6E8iDp8Qd8/PGyFWRKoCaOtCvjWijBsIc1+6Q7d37iwUGcH4UcsiGOYtc8h8gm6oB5dA+itMxZy87UIPaHyrC6AKYXIqkh7jeNIj2yhXv3+5VNZi1OcI5USbcVlHEAek+zFS0lESQTQ+k8cTCJUtSxQPMglV5NOiumdjCKsqETiXMPHVbNsDD8zhAlfpgrqdINyH1sn0p6aB2BF1lhEBLVk2Omw/4+MgadjImZDixDY79q94cYOgtY5KtcFDxomzyz3XFkMU4HWulPjZkfgCX2mJ3xcJtuKQAuqzPsrXotiDm7diMSDssLuxvE3FEYCHso+R45Rkac890hNh35Qk44EnrLcvJdkBATlUWXKcKSvQwPpe0Kb7zxSpbuS8L4xEs6P8GVlDDB8T8z7BjIkOkBUmHox4WqMkflQOvwALSAemO/QmCIPdmC8E4iz9xhs6Dc754rSYNWIpAVZbPVFaIvIdEbx6SPW3JoOBZTEwo3IhsEWpmQ5kMlijpov4p/cqJu4xJaVVJQ7IERmo/6Z1CLre1+HYxnoI2wosUL2o0LZ7riR6RH5j+A/gsDHZ38xKTMLQHTHfyTrTDEi2xCPecRJXI1FdJ4JUb+VA7yqWos2IbqzHPmpFjyeyTEowLavBztmqC1MJBDLMdenOdQx0Sc6Lfe6UqVN9QlIKUWDwDiUkfrQDuHqMFq4+apw/7on3XmvHZ1Ycu9eq8C4Ve17b9NgCBAonSslY94AzckF+HNWYz4LtEh6W+1FR2QVjBtU3wPC+H7p2O2mPE9C8QsfjslSz/ZrV9AGbOsPYgFTTcNUe6n8kuhFczdhWt2wXScWFsOPKrYUkxgPcDojQT3LDPefDve1+Mra6Ai9Ptun8/hKthQbm2XSboGzht+p6vp++PZY4hlCbB4KrXIhRN2f2Jh7oRE43tY3OmuZse/yOi7aIOtS34+iaMIA9o5MkvS0d7beKrtM/sRE9u/iIF41BkGpYfmBn5RNWvLt3AMlnN7ej9DrUaPx1VaJzVHuZHfoQsCbOUgs4A3CJpm7th0OamslMim00/IemtTYZ9LaLTvZwMdzmUslKSKnm5f1rs4mRVa/JZEURzKwURjC6Rg4gUcctJmxlIxm4Ku2xH0WcAuNU+9DkGIjsMOCCHEIdPI4XWgS6rvZx380K1KL+NyGNJeFDQfJCZnOdsmYnOfWQX1Uon6Qi+vsFT5UJL+6Ka+wd2EhG84fZeNvul/REpU24U21Z4Dd3I1iZGH78HCPoOn5G8XpB4XW+NJXekMFToVjoAQm06jpeS9LTTCT+YVU4TYaXX//HDz44fzwvn+eWPMDiW8y+y3KmglJuBSJbwPnoNEvAyDpSh1ODGmF4uhppyvCercTVIYHgOujT8/L4mDpN6OWF0WW8YwQpV0EQ5V8kWdMR7zzu8iNefCybqM5mbZg4xm2/OLBraNRbL8olZacFIpqq6/N6Gj6vmhkBl5UDIajaaqFlY8VqljEREjOF+L1hsdG8AC15WE9+hR9jFAMX2RqGR8AsnZtCxFMv6k0DPPVLxtXMXlf0DQQ5xZcDQxTOoSd/ZL1sUQyXp4hmnQQ2kBxB1F36iGKYyw++JJozMEHzewgcZxavy4VJ/O2YC/s092CPAX4I5Gy3KrEwJqcB8DkixBZXSJiDAFc4sqdG9Tmzblcp5gT82p8uZEmnMGB648peTIncRa9JQmkzmS0cNNScpQt2HnOkMzdXnqRpt5o0Den6Dnq0Yt5aEtZ2Ti9Tng2FYiwZBHtAlBOGp/0Pg8AsK4i2dDvkzAuor37QIFtoremjpVpE/1Bb2s+K6W0rZj2qkNQ9myJZkK9MWtEnKLYBYxYxgmRbYgurr0beUUGPSBaddGoHRMtQ0FeBvqo6WuNM/AKO+WZjat2SR2grICebUe79u1HnFKOv2ZOMMJkexBJYtKDwghYSpkdgM8a9SfoUcftntY0gZrPPzoLIRhHpikYAJHpxel7GhnYpnaNuRkdtrZycl/qUs4uxJIuNSsUxBkisHRpZcmFH9KYY5J/EDM2s+BmULvX4dcXr7eP+urQJa8R0c7nUcALp7Cx7Q8TCwrhyInRdQJWy9UUvuzSxS1En/h1sxDJm8wme5X/FjIeINIMdmBJryg/JnbTa1kDavGjYoY5Nt4PmbDDQ1ZyHCCGT2SZlh8Dk8q7VsacCLZcN/byr3GXCNCyMqzSOsY5lPoYHNL0uFGNVODK8onowsWaTN5RIFu1bNcKWSVpLqt/EPVkgI5GLYCrlfYIJ5Oh+yADonlGvbO2otGHfr8hCxWji94Al8jPsBnaQQ7Z9DDEgU8SOx1UgYy6JGikeoquECXvcExuS1yLuyGWWIk1u8sdcR25rdbOZJ9zqDMozCKBFxDFE62M5PjIgvaHDVOp9wv7rMu7dxWusBcOrB4vksVgKVJmnbrw9Y/9vi4vNVg+nuZTW7SyrObXyo38H5q8EJ2IDG4P6X0DG6VwPNWAaJDHKeHfKvMBnw6XMuC3Ad4M7HUfipx2LgGYIx8WONm7MlJTdciC081I5h4r0FipxzJ8VmkIUk4bAu9dNuAfTuA8ewdKXDBLY1wm8saYeRmdDWtZ3KBofV7PAjSCBmyMQ0KTsp+OxCMUbQ83RsR0RsUZKLc1db3ZiEUT/oetOHjP+rQY8wo9o5uEOcNTZQhyeVN3MQ/AwzfmxDnfc92cL7kS1i+9rrxhoNXl8+Z3d1WPEN+JINuHWcf2+dDS0tsI7U+jNk7SPAkNjLLW7QBEn63YUx/P7xMI2Op7ZgALkNtQPl4MjmN93fHkjkiHCF5hHLC1zDpAo7lDUOfvbCYzb5o6kuVaOBI0wto+p7Zj9PNxRC2oOBYpzV2mFoZun84U8MKeAxyRGOlmf3k4khosCJs/JZIcEjAAW6CcA8Eh29Ouf5g31iLL8fLhYA/sbUt6qmVnwvM738ZLRJlGbqp5T2iimtABsnIAC6tXEPdXs5FGDaDVjjywZkjbcHRB9LaIythIR3MgPQfDFyR1ySuwzP7icPhMH+xxLJCXL5b5RvZgfyNDVIzSNM/UPYTAcLEXyzyBdpOfkFyTFPUCdTUfjZxlC6tEk70FxUHWRDqGWXC37BclLIY2dLU8YPSm2onRRk20YUd6r2ZzDEmhAiP45vmTxznZ5GS3GapbJm+ticlQU/tZyzn/97o0hdSlGbCy5KIbuQ+CqKF04DTmrQwBwRBceWi7+AcGSgQaMSvLNSKT5rfVzFTaeXZ8UkugMPoykvIkoeVt7SiEW72/aLTzK18qOUz0Bxcep95kjbYPzhCJXglHvpXDgtqxUO6Yqp2MBQrF/+i8UDyPn1YV9uvPA0Ui4e4fNlJapvIdxnUoMnIXH7PzS0OBuHizfAfAgMbvGaU4GHFAPQfjw0OxmF/pVTUE8JKU9Oi1ffqSanafqVNNQylSxriDyf4h6DodAH38QRb9fkwVxtDc+WGm+4FjOmaXD9xxyAFjNVrdcLSiyME12Dof0dqTB46kakd8x/j802xszefa4FWRgmumizF1IibLs0cyIHXxne+w+p4aw6poad4pi81la+3naSE8mtllzet6fJrTFX4fzH8/uGntqoBrXEnHFH1MUkTHikrPStRAl6C4CqJm/6cMrAstx0vFUAHSjCItyDXAl+5iC0RSG3tv0DX5LDKGllEBiTBiHxDB8G1J6xhTC6E+z08dQg76/qt7vu9Wq2gE2hBhBsxIcuDp1uCoVUz0t4wpmeVGIqWnwmCQzaiw4JhjdgrhnTECNVor4RhM19V6HW0cFCqZnAEofHCzQKt4JsBb+yr8BSPEG0QwLWpsqIGuWDWUZSkGGMuZiApgynd8boaDYolChAurClWoH1CzValJeZqoZTz6yuet21lnhRIRy40XtNb3CGTsw+jZcQ/3hZDjpJarsvEMZSPBuEP9vG7RBJ1SecD/nzMcjx8VhRFLq4hqf6WiDZjRSQ0EoOgTZR+lZqCMAfhVeAJ1duXmMzlHcKAOnBh2x7HVdGTMTEvDqaXYoC93fVU41DqUqpeGE+2c2yoRm3C56U+WnKaDaxiq6S2AWwOC9GPGF0qxQzNSHYLCWTASAEB33Ef5rY9wpqp6oWMsENCG5To+y6GHDwoWf3IRm6AgWfxB2l7nj/O5p1BKLe3kwG0i+8jiAHqU5keal+fcgkxs48r9X67NBjk58Ksj6STOnkaIYMwTkRK9w3eae3hTEIIsAZIi3KuH59A5PqlRnYO+a1cuSdUC7voshGfKl77RSqu7+kfX7mqWsvA/PX2z3JRGMbognUPzZPak9TtV2xjKMGwUcZIT/hY9tzWNpo+tE7IL3Qd2T6s9J9vQRmLHePR86PHqD0T2ox/hzUhMqUO3FubecRMe3F/poGeInpPRUQshEiQN61C++UNMmZxLRwL0V3+KDfAsJC9nE97LSLJMaX1Bm4AeZqN5REDmMmBinpcIEBrskexv9PRUxIyWaEDZMlrYFYvxV+XdvTssmd04yq10gSThU5k/ymfwKk7hESyLL7eR2dtqUf5KzEkTFF3LB4Qk9Tvy6NXMYCEGAFoboaC7gcv8tpH3t6gsfIYJDdzv7x8quwWwJdf3lRgKDpvElwyLoNTrl7uR611FOS88CwIlgmr/Mr6ZvNBZHpBowDvBv84LO/P2qU0RENrlyokaK535uVdqkPqiR+11TsxhzEGk4iApT2J4U36rhID96H/D0x77fblzNroqo22i2zOsOB5t8GNJ0F1y9NMotoiaVZrgWFYf+/sWXCMMAWPi0e0l8xwfC7CL9m8CVigNDbBgUmVvlrhmJWYHtjBKZcLVBCwUJ2y8tFsnwqcSxyIGuxEB5pAOIAU4ypsoEGsfyYOuw1ZuN18u2RPBSWGdF9MN3P6WxxWYhXRPhhMLnD3oCIe1dcC09cl018Ko/+M/Z6oXSRHMjhqP74Xl8U7nwOHQMupiE07qEbc6BASvVvq4RzyN53iVaLEjTkYG3drgXLWKBIi/ZaBaZjvKd9cd914JN9oL8e24QTSig6+B6xeu65qG5HL6ujPPZBm4LfYqIEQmhswvxAQ2KnPrW6FIKzlOoDrfgwxjYxLqZ94dsrjLTEU2xjvnxrlqghyLDiquwwExOFU3YgfBqS3VBLJC+/uxGU32iuUHMOEnOqtrOg2Qbpr1dW/flsY0b3c9NDc3Q2mEfY16hHH1RvjdpGqI1RrLERo58ifvz3WRxvy9/zzTQ//x6ZYBJufFQSbqPLKYq/ZdZJtdBgq3JaGE6ogJl03XcjRov/nghNwuVTbaA9+hUfI5mR3L5vndGjfWxQUXQAITgtLuLWbEYY6FBMH3/WUWzrUeuxr9VoA/6fVkU1ewaq+3uoUn9SZmt5BpiBfleTPOpnik5jehm1w22053B87Tims3gyO2oxTTW3c1dzwGZpX8ftGlHnX4Ip4GAJ9MGFranAFOI3HCXpz5TmOhO/1Fn8vPauOOnijqCLB1NE4dS84dnOcWiv3jja11phKxPz5F8zFNtPshwmua2QUCEBOyZAoxkvIsp7tyRKrKGjChDZUccO6X13hfl6LtSxmtlTFrGtFTmQOFP/3wKadEelg76dQb1e47Yy7/ZpQwQeiRaDt+qJlffCR9KAIfhC9WAQ/OvV4FPwkemNe+1n0qAt+IT0YBL+69GgTbP3tBjqovfj2aslrLGrO2tImy8k0OFM0DhS1y+uXt7qIKLjKxejkFmpuPdtns/h3quPEVvTBjd0Jio/aIl5INLw4r30BDGUl9Ou1Tyb5i4gzpaOzOMUk5WnvVEtFzXdsqyHGjmtw/zWoqGlfRbh+0Q4ZDvyhkJcYBlxgtYSsnZuy5h0QAULMcAvKNS3k7NyoaQMA5SRK69PKtyImMga/VzE2SZgbnGA1zwqo4EhiPuTSS0+dLZN3GZnSMOYnYKuIL68oDdPALz8ACpLAnoXHVcoUhCREKfBYupshyvl+6a3IGhYUWU2B+I9qIcVyCVcGthfFCdBOE8an8A5l+GwIYznse/vWGWyyGW9qt9DMsQYR+thYtBjlLhByAt8reut7tXSqMIik5i3FLiVHQNTsdGK/c9pcuE5LwZtLnPkh5R1V8tWWpQJj/CkqKsogOgeYYs56u+vhN+6LG+Gs3dtj2PS/pij2nFWQHMRTalOWz9bVut2uY6vMLng+BzXluXC3KU7Vx43/Qbk+0y5lcD/uheQovpAHJcatrnmxeLdDSHX7E/pqS80mCRAeVK8wuJ1+Qrkjdr2npzrdVVr6g/yoqEYWG5UTBaWqIpkpCtKHFAwCd6vmP6FFRbWDcchKguohPJkkhOoJ2xRgQeGBXySd26WBgW+FqhmSARmAXDGk/qGSTXEHkxnVYu5/2BgDPs67ubdYxtDOmoylPbiDGLbJPnSqRQyNYrJK7/6oftYP1VyQ0icbfWT2r/H56ZD9h179ZWU1CDHAXnb3kVnzZ5a/3c7DzTln1wM4fXEFsjNIDJ/sbEPokCfQuakXDB4Uh5lTMrojLPYcHxm0xeQctkzLpMMwpfDoJud3zeQwrw7Mo3JyIDWJFBvDGi5H37H2Tr0HftGZUYih9qFEzABRrORIXsCbdF8eshRySOLLYxUWcI/1w0R+jyBHFUi9BFKlP3pPkCoBDokp+Io09g1+UMntzJGrit1FL6J3hAhs/rzjzx3KGI0mKmp8NC3FtJ+O02KSn/aKY1QGmL3QBsfPczndCp5OPZnq7vwW90/wRAovdfRFrbjWEBXBI5VWwGgioaMvCoXa2h+KhYOVdAXgUIT4r9OYMKRESaWTEFLC+cCML2I1DuALA2ve5oFofIehpv0FVhIXk6qT99ajkUU34zTBJqkmMrIzHJyGOYVzQ9WM3FG99YqwU51ZDRFzPn/udd8YyiplGbAimlvzFOilUcucRvotnOoSlP+wzN3fGZ35OVyjHf06PU0pdFM+a52X5P9UI3AfUoKqvtqXTjjMDRWQoFkLCruwABrvuz70c/CqBSUMML6It86R8eDAuQp9xAzT0NTW3p0OHW17z9AVxfsI0QGDQbeKctg+m4479n6Apfp3J9NzsgsoB458dhDQxjgUXQjwe1OY4YqXYYD5maFAu7THbaPmd1vfcYfpOtS2e56ZOmbbZi9sI28KujfPmFdrBMCcY/1zqdbjFwVuTVWgxZZJt/WOQyju5eSa1tVr+/0q73AHfhdGJi+s5O1D95J1uZgZRd/NAtwejn5v4+YJnaIWBUykvd7kBg+f80QC26zYSF72Xx6JgeaomSQG8HzlKswfrZvbd4qmEKV+oUiotB3twIFEeBUKRY3z15Zex3BV8XBgLrD/gsQKuJL/9rVmWgSMfaDnJRB3rooEFFZ6I3vfxf8NmY6Ba+0NZwNvll0PzL08U9fs3KtCEXbi5MRJiFwTyw1fYwt6afg+y6Qs48nXerzfiNSIe2005Rr4NNr7jkuW46SKbYFRnAN/gIqC101SClkXLtgj3P3kqzADHgnDLoOCAmBB+dt7muGnbtCzZ70esX8DTjXKWhkyr9/uh2VqzGAf1f7LRZEr+A3IH6Xh/zTapxB+mMA//CT1qB+TNjdGrfHx3lekjN6Sxof+7dyn6uYb6VAg2uYQUqwDTz5E1c8JMUcXl0GTmQpotXFwSdhS8v9GenbbIP0y1dZCTO3EZd9xK2c6je44GFWwT7Y/1ESE2TwWb3XJCx3TXSSOWEZEr7W8pRGBMxR89HHgIy6D8Runr1y2Ty4/y5odVUk09K/64rDU/w//kIpbqx7x6WyWVZcvK1acFq9gK/cx8ncUrzr027B29g+XKpDhMPpA0nR43xv27T9DBelCGmQfMrcogz//Yp9An/616kJ9PKQcHAUhOYWkZsVTMuxAQ2A8MFUFqrUjSg4TFxA8BnS5aDZmEAr6zLU04GiOqWKHqiq4TumZg74+qQxd/8I0BWQr6NvE3DCXMTmnrXHqLlDmU73pBPCAmrqjQ6cepMJWMyeNJ+c5zqAibN9z0qrP6/Gdg56Htkcvpe7aqTLFoJwAtDsE7AOHjiUk5nOKY0ijnb3CR9/Lk1g0CUaRIaZ5q4NM+Y9Q2cE7ljFJUQ1m9Fz+cHju5aRR8UKK2TJQ6WgDH7ouOM8pU5TEd+A2hHtvtOkum/Rw/dFpN0BFQ7FM83wmgiQ0iDdoRzNqD2mrlA/P1+KqLYTaD15B2Q+jmv1Lue8Knv+RoG3urqKV4qFyqwaxSINNcHFLQFrwY2Ob30Fh9Q9U//ELy6qzpmw7dK7vbHMnvQg2EYcySJ52Njkj0XD5IszqHH+vka5wUJcDaiJuTyNj04tbtKLpkuEmJzA/2V321kV+svyty1vNFSE/VBKT2/Q4P3jrbSnucWHltlLiuX21w+MSDOYnqxwTcevY843YgD+trdB2g8vmL2ESEwHkNfR2Gch5aTTMZPpMucr/pvivs5gcOF3fPFGJNq6iyH7by5MAlUz1HUctmPZjoKjBaVIQl4xbw7BpO37+YK5bCjy+fdOBSYOM8PNUL2BCg7SIwx0NdSDkvWew+mZTKWLoHOYKB2923Jt/r00E6F6dGbs3S6OHoQPDR1ReXrElG2ZRqK3+H7k2LEBIGwFCBt5QDemKThycmHIPyBgJkD2Bjg/0b7hVxJFbIBJ+EtqiMtKUPl6QHzuIJj2N9Z09DWPfaYMFEkWk+U+oBqVjNBOt1ig7BCmDHxe8FgOqhXDU5se/UHN++VgZYt1wiRcqQIEICkD85YJoJ2heczgusNH+TcrX2yuHZh1KptbZ4HnQWVMb5p8bEYgf9ImOVsfRCQDf6bygGsR4qhxiIu/pstrK9z7BSKeNuSR9xJnkzgcUQWh+OKl8w9Ghsrvm6Mh+L9D6nxU2xOqTVzO/pbaa0VRWYTk23bWxOrDf50beiQum8Pi5BVPDKWi/KRzApwyG4ZFWHah7CNECalOkejPrKpxJWWSztuBtt2XuxhAQe/4xZ4Ft2RN0YC9IP+wBp2YTwun4IHGKvie2J3A+hSKiu5bbV/ZKpJCpBT+1NFuUTZ6ALRI7+9RZFH1YS+N7TX+YSmt+KxU8sjWD2HTctpFOeJMx4enp0Se4lXRZ4s36lWTNhxDietteEAI8eY/c/9I5jKHpVISfwAqk3tAHEeK6IeoLYNMoROJ6jF86N9yUUw6MGj37DyKmqTATgLDHUWBClYLzsfD2TWb06eoHp52Nxi2wmCxshIYIrpMqsh5GqdfgQEcO2rPCpdcYAe6OArAUV/Ns99RgLy/Pm/qJqZNXn1JzpyqAFpCNap2kAQm51Akwf4r+IwQ49jxnShOaQsS7lYiI3DR/NdQ70g56UuOCREN+/y7lA+ITsfnnkXgiRjcuiafqeMhk55bfBra/yoLefUgvMobOOHv7Am6P4AK3hDTFW3GxthSvQLHcoM0EZ14mmojI/IMHqxc9FVD+o14GEAAopZ1lmVW9ow5j6Khzc2eh8IPQCbIDxXrhjx9yKUXOjGsU7M3OjBH4bfEqUrYldKJhJ9/JBLatwLf0nuju8TX/JBHYH/kVE0L5sA3UoAJkZDX7RwgfmqiWpJD0sY2h+lt3asOGx5O/QOyL3VqSDxIQDkQvB5yoyF4V9Lt1Ul4YJw+zET35xp5RQK+PofRKsvLPUpzGxyj+F5ozcguKLCp+qHN1djd5Co0drD97fzArDuTXqwsaqUmc33hIJg7wgExq67khoIutB0k6yg7o5hIwm8ugDKi07DlaeIXrjBRwTmoNcRW3an4pdxaQzfLA/pw3Acw+kvmVh9AMd9E7aBRip1dSyf3t1UBs9+M7voTWC2Lm49UFoagIekLmfMx1a9qbH+gXuoBmq+LINcKeGq13rjR8F5HG8Ll+HUd14DM4canu8DVU+KcKy0k6Y4yLXO5MqLigc/wddaMeJiW/ic1rUu9gUsoXOdBH94pevjqu0b1UzlzM9HNfJ0rM3cPL6m4LE86Z33AdxBQrov1jY6yRiBN0jAU21vBqrna/qwTzu0Tup43i8dyUMqoqlgXNLhTcHZJyWuMVAieyOtcFZ+d8YkMGDYX17hPCMlD2y5dnXQXMCIwnT1A7AqyvgnWKDKOfHQg64cdoKnxFg9Vh570sbpdbauVjATYPIXIfS0WXAc1vng1M0pVG/At7MLEf2K4DrnLxI01ZbVFvUX+vGA194ikffttt38sVpBb6YCsL3RgYM6DKJi/mfNr0JZ1SoItG7+Nvhtnpizs9LkvxkwWLnvpVFSp6C7xO80HM6K3zPnegk5W1ERXmg+jPSavJeRquQ3cdyKdSw3Rort0ErI+6o60Lsu9dAGHUQgfQP6v8axFXy65QL5QwFcfKSuBZKOfcJYyzajAWyXW8Uq3N3oZyKpF3Cl4HwNGYJW9X1kdOlTV0jsp6rpOFA3DTe5VuXiEwPlT0eBRfU1FeC9V3oRj+8RwBn44TwldRFjWJQp4hnAjEofrmMzf6zEqhb5MAEDeDo6xcl7PMhb1E+yoeznNcMdJqBR/gSvoAQXKNdEhnIgBF9fpWpxtIUGmv0hXIugEW51lpGLzJRdsWTp8g0W6RTAWRcB1dzVGQWByi7YbBMNBzyrVjPuj3eVtE4ax6Bmr0vZmbDlSkgG8XbksQgoWtJbDYGhYTHLOtdb44X2J72VEVMKSRi+2M57SNanM0gWN2SN0dLfJ57PoZiLb6zzFUInZsAchApqtk1Dm0sHEUbuscm3Ay7mEpQpNhvLgzGbRDWIrh/g7nDRHrUpWaKhc1XhHcTtOOFqG14yrsFF4iVDSOt2n+SkCo+QT2ViNo4Y+wzSl3ssBsA+2j7IhKOTR4LEAm1qArHnXoDHEGW+RNRFMAYNVg4y2MYxMtiGBd0bjMokKIQtu0gLHErEL2ySm8IHeGmSJrvmsznngKXABkUYM+gqp3OLWPh8Z/HOCqNzdeLzoDZPkQA5bbJz7Dt3qijmakv9U4cPgDRRe+KZMHiJuwJQWX3jcvss8TrasOt6T6bA1S6ptgJQq9NpdVQLmk9KPulHFy+20NvvL1fSORPlJBr/tKI5geKushVnGxZnqYEcWZZjdmyItn4/NkA4WrXmeAI5b8lDw+EVQppej3Eb+ErAXN2viAjXYYtzUDtkYL617Nf40vg6RpFLHiHw72zv7HISTfyXeGJTnJ+5tAehnL1jEnNLcUo2yL1P7W81IqlR82o9c9NuDNW86FiJghZqJHIfDqih6V76/pNfgajmF8tsrWwOEG2tfJwXKtr83VTZGvW/eu/MwGeETrXAibRSSIzUuNDBEgClzSmTslCMRckNi7Qo3p7yBKPnfwL/fqISAf+U7rpfCod8BBGxhIi3SJR753hpMPfQL9XZCc3uAqQGvt0TJrFmxYqBLRo3qIzgJe2RHEOBMvYKHy+4FN1kpBTSWEBqk/Py4UXpkIMch5mJQhQcwhJtkrEzHuDoEDwlx7uiPkv/wFfE8CtPu6tuHOZ5tFIG4w0gsKIBKfhOxfzLd5bjD3x1P6mEaj5ve+Uft3RYGkb9CB4QXSUBvli8jBIrN+WarerU0Kr7Z1eb1yswLIyDJrmVJVMTbPaJ8+/J8EXcb4DwBHobgKQy8z+ArIzSL7GpagknzB6hdL+0Tz8VLoxkw+czDTTZy0RBZls3ZuicHX5mxpSjs6sSyLdiYt1KKdifO3qK7kpVN0m3uJF6VxfkWrvPiLHpY8J4zu1DNLzB793ZLU8zmXFD69C4s0bbo0juDVLN/wtb1xmZtT2lZcvJacOKRnblEVtZv1uKshUiwX/6CuQrMX06aJ23xSNqd8zdu2RrUFideczknC5rSVlbM9Bjavy7cLdgjEKiA2aXEsxFVh9jvJvOd99cQz6fnXCPOsC1vruNaJPxsEi9sH0ItOMgXvpM1E7eDiHq7oDJu1LqpIp9P2mmIqMae0Q00Z1U2atnPq93xDMnpIIsai/JI67nZ/pvYdxm7s3+8drFEXbmmpsf8E0aYdElcwQNwarUAXLNhk1EBO0pWfuWoExbUNNLClStDZiRwV45CebHjU8AUvE0UhR6nlBHsUmWD0QHOQQyBatg6fjIhsAROUTtT9aLrY5W/BxYXP9vA2fgGHnXoXK6bb18TWrdwN+yDp17WgtWIQso6oLEMdyqHmb/p9Wb7yz9SOTWMykZxfkaTv14X7+eAsiTNfb0KI9e4Hwevgi+mxz4mamxsq+8kSlO39a2ogVXmeBlZAk5FAaUERHPCvHPDm0PEfifYD+znGFpkbytZ+7t9mJ/AcUtg35+iqT5jLBpbYAJur88CFGaKVWGiA4as+7161ZG18dTFgC/zuCux3SJV8bBfPjVptO8B+kXle7jgbVo8tS2njSfpaV7DqYCc5vAwYSJT0hroLDRqJ9wSagvfGNqBRZnLtyOE6JXqQ+129WuwOCqEKiCuJfWiFeN1BgFLBZVd4BXHreSc8+VwazaV0H/XFOqzeIzdpYC1/pL71QcC4a2NaY4qC0ik4m5dmVjfGUfRNNYPavC+XTDJxrLQ5PmNsE5uTfLIFrwnXPRAIIIKQG+RYGE0Xog+tFoR95Ix0vptSAbG7KECieh47kM9he8QdNB5BCY17mKOC3K/1RzGcF5JopS6Bif25BcL3Yykx0OFD1PhwvfPNABuvrorSMbo4NaRt+qqKm744F7PX4z4HKJvjNNoYZxCR9jlppVMzFFXDU3t1nFITpAWWQloith6bj4UWmPrhulfZZKj3BB7ZkR2p6rOebtJAwiximrcqH7ouwC+7UBi4AjDlVseFL2NHnqkpGuan1IC0hNeYipcAy9il1v183BXs3DD4AcX0r2JcX38yBzYNZb7VzrmFg0fawMOwPSiwBpGPFT3VOuA/B/iR0HljMXeqOZJZ9CqfZA3OG36ZtuAyhc0Fvl1G+8vAtv0Rlaho6o4YncG4uJTD6lzs72c3hfUyJbxM2bsOs0RnOaPcVBs7sy6FeqUZQBWvsb1ht/gdIjkAB647uyakoV0dqd2nGedQ6HgiJ5EE1V6XR/165PPaX0hJl6R7fiSpRzH0lFPNVZPhvmGSh2D6gDS/UC7UdwT3Xo82Qdc3na0TbBUfwT+8NGJlJR6giCeJISgfmda+Z/4xTtESeL7cpy5mTbU2WzVbop3+IHzNLp+TyXWYYCUQIUJS77SMpQwgLi145LpHdH5GqoDrsVW3kvo9m0Ur2IobNS2Y+KvOgR2fZ32Bh2FFZc5OBmEFoSqYzdwVFuiO2Y4v6JxdBm0Gez2eBfVYrjRNrK9szto4xcabff5Ek+dqHWTqG3G42Bx3JIzgzFKvGqfTN5Z3rqaRQTarlyu4/02lDYFPXL8pFG0pj9ZV5MQLGQLsr7oxVALgGi4ihMg9Oa+FQQ7EgLUIF3oPV2pBFzsIVW7efF9ntngJBp1AJpflfNbnHls9iQ91SFbeGlHKErIQI3i1O0LOYQPJKm75YA0oLPOX/1DIk8Wjj+AQXBEky2+AMZkbymYr6o1bg8R7DJ9h2Fu84fzU3Kg07kDMQs41X4URlxx9LZuOxNzigXzvIHAcWimeSKjKfVEc1hpGJ2tYH29FVwuhoIbDOch05mHmz54n5yZe+aRuFL/D+7olLSRJGcQHIltoJDpo17Kl0JAwo0aXZduacWbkXbgzPR/Kajdh2QiPJHyFx4Ge36GgoyAAPU1L8HMHmlYGZpoiCZpvsoMRKUmRape81sn+j/IdTp7i9tiQ+qLpcYItLKSG7KsQb/BmCexn6OVirIBlTvHW/hO0TP05d8YKZ5ipfYfCwVOqkUxR9Z9aW+jvn75q1nQuVKgy5Cw2v0uUl8fR3J99xo0BOn8xDB4xe2YmMGV4TGkInlmDOhV9HE0z/DMmXFsuxHm85/69oohhbGaAwiKFzuPeWBvE1E6DiorgE5dsa3+KGNBdgyUsg5Sa4ZJCiZMidQ/ept1lQ00RZsW1WniJRYhDwy/yS6yQN+KC8vpuIzzhyru04KmEyFIqA6A7AnDYgFuEmeuNLCBlRvBYhGU6NfhIiHjcQA9AxAgI3FPA2VAxABeiqoRiKzhFWDi9g6+xhOz3RzNno3mRpwFqR1sgq/ZoJvNjlUNKORwaPjmKMEa0N1O4j5uVW7/Q6wliSieQt8A3fofe0OWykocWl1sk4fcfZzFc39cYdWd9YAkm5SQBJJUIxzGw4+XNXbxLLxdqeBobObRyPklP9RETYyI6JMr3lDVAZZGN7PX4d9rudCZCxXrnQsNiOXyi05yNnqScOsYLITbPdqpCK8uS7zg+fEya5sbHPLx0e+0poa+4a9Z+K+5idYqzFWL/lR5u8jz15HT7oVZmuO2Ci0crQKPESBqBBnX8QFXyCjUOkZkUrBJHKxS36KPpESyABg5Rg4ccA6imp7jGp24ih00NpmCgJ2/wy0lw+wL9N5223rYgk9i5bEz7Ye8MbrpjMmcfONCQK3HTbwU0BKa3iAkJT5esWJQWibyxFKpay6XO7VxR0BuuWTXrQix6xp17Pgx7gavz/CQKFMoGmAHSNn15/Ur4eHg8UXymxACP0KB/dAAG9wvoGOPB66Hp9b0H8UvqnQ81GuZRs9g4NSar0Hp4uudM7x/9pDp8BjKHxDr50AmhYlyqRciEZdGV8OSCX5lPXsKsGAUVlXg3fQuo6ih61AMK9cgi58CusI+khxN5IwC8qtjQQyssuTudN1Llhw0HRAnwhQHIITkbUo/gIopEIXSMM3xkOfEgWWdCQDAzUGK/BvXmqT51cmATnJMEmdUsx94aBnUgJgFntAd++St5MdCpSZkGEtifRwFn1DBKuKEW1h3lmRi8jDJ14Y4orAUMt73O/z0EYCfM4HMWyh99w9taGPvzO9LFN7SF2j+XKC6tNlDp2zrTHxDyqbA6Q7ERMzWxP2i2HcU4e5YWOFbXp4EbSZoMPr9kXe6etDw6xwySniAB0y35C/cA2IwwxSRpuZGe0+HPUtqDChSj1VI+bMdzeTA6eFkcI5aAf3/nSlIyHTGw+SqINS3teR0K8t3p+ZHi+cek4PNEaOYTVfOiucU/m0Oczee28lxit5CxqhqIn7orgm3hy5xS3CWq+e4tIguSKhkYFHzYnb5G3buPUvfAmtAJzwUS3PaRJUrc0P2jZgSs4liWtZCKE5L8ial0stcEVvm4UQ2F6iJBUwkKJ7jctLkQ4yFil3DhZPCIEeSEhzH3sCmRR+cepD5Scu5iC05SAKH6n8luJDmuP+It0I45Eo1v/Js93QAnPkdjY/a8Vh/8UrfOkfyIdom2pMXhYNZ9Iv5zCLEgNPh81bDw7EjMkuJeeiJDT9pXu2pWgTyr2p4KLMA43p7Bq76hVc4YYRaflGXJd/9RB9hJT7pkzLLy7ynWoGqTYNtVb7ScZjSRcBuRAX4KYccKgE5EUWumg8/LxRErFYIrzrFFxS7OMyD4GV1Tlk96t9pesToZqsbsns8h9FKiDO+G5fse12nGyLqqBMcDZf7ThSe7Tk9zGlCUQO6VbkCCdBR3+Fvtj3MVDrR/PZ/7xO6b3scZ5LF2j4YK8AvnHyJ0adSQIwC6f0Pg+EVwQhegHwbmH9vdlQ2CBAJVhEsZuCeRM3soCuBS4GLGEdF0I0qf+AAEBP3O7xXH0uaLyPCy4y3j3QeuYrLxYSBZLoI7brDIi8IA3vWHV/fWtS8/ryxq+5Mo/nXEYaQARhkCyAIsAIABUT1fgh589PqHMuGIX49j1zy24MYEccqcPZLpehyJj5lqPvaF9x7NUrSRxmNo/4nn/RsDR0l2P3qMZ5vMWBAXHxqM8LqEK2oJYYtg/OVU1jeIGJVzjUpUIYsPeV1SyoCENcxGDa8tR+Dlq9SGDQw/GkK2D42kVx6SbB79jMkfpNW1SuS5v5QH+fofC8atOTfsoq28X/iPdslR/0+fQViLGGqArZT+W7b8Efxr7RNBmT3tHshcwuHKBRIYnBMnDIG4ozFkfly4DkP8ws53F9wXmhJCu9kouO6svqe0w4PTRu58lQ87KRTc4JrwnlUSEEnK7ONWRc7lv/QMvORqgWfK/Zx1OWWaAQ0QpB6rIOmFhRf/PkEjrdrjBlyWYK7IX2cvXmFkzImo1WRv5ZUAAkh0j9Khv92Vm/Q8QdDIVgPS5LcUbTJ2l6Nh0QZxfWbN16WctRc1soxYSnmoKnmfUEH4EaeG8/cafTJ1I4Ct0JZgn113KgJomkrN8t+ugzhhl9K/3HCpPK2zinW8XE2TCPe5vTOGXo6amGb6bYsMrJNLM+fyIdtTX1HR4716E+OC31D1Vz2Yz+3kEGmOMRV64OpSCuiBnDqGQ8rNIcx+pDvIgpm3eabOYZgMI581fQAzDppv5GHMiJc61MOXcsxJaE8P9PYoI7eUtl4HIE3qZGyZ8S/TiEm6hxzJivU5gHHyosEDgQv3p2gN3IaEmoGty80kBziX5619mkqh1PrR6sA4/4Tz1mVApIknkxTjOoKAIiugAZ1GPSCx0mD8DXUPBp2khjBBv22QPF7A3J+2DqRod2DVPvT+AAOkJX6+wQldfRVqkRgji9B/LH66VsvTuzqyD4YBRbeGwKHzQGw/+iTOMG2yopqMqLA4uAa723hn9/5JbV5hKHmtco/b8QJXUQImudu9GiN/6LOYo5CBEcmUhc63hn8+sOgWcsA7FXmTFSj6Q3X4mLjRtlGclTYduj4XBv2T3rFyr6W0mlZBxaTXDQQEohaUkUYcUKk0M4saD8Fko9WBXA0fG6mMjt223CWKeagJjiEFSf6Kx+bPdbX3o7uK2jTIrsPsY8ZpjVjIoOX6ngosRb2oPeCAiD7+KpvWVjWhmrrrXCOKb2y0l4V2hpdvq5dv7/ACVd9BgsvHfNowkq6LvyEZ2Sa2Z8n9+Sw8ajAZzaNvZeyf62TaAqiwJ+pMSvjAbggTYjg+PexKY4eoySweZx9jc53bKlL8nTKj0Y4I3W+7Hnw1WgwnO+cJLRp0AQVf6RouXgxWCUHWkKZ1RjKuqBeRd/tusGEzepQmcIn6Ca05dqXzowN9FTd8S2sgf2rDm/nG1OrZsqLSNepdubsp/+NkQTLewXnKxz4IdOTAoIFDazI3OYwQjWzUMGa4Vy9y4uFCC34WMxRQfGNCinFjF3aH6lLabedml0BZAodhMRMsMyrLOpYtIMYxeS41LR5gRqAWRL19Dcv8g5OTyfgQVa6hkinyAb3dhbM0bJpEx0KRssFmS7qEaaSZS0YKuia3MW7R+eKDRkLPLM0BuKPswJQgTe6CZu/bVv2QSx1d/f4VB6tCy5RPW3NZfv6vdbhVv9iPqB9BWmefVq0zJtNgzrNjXYBOhCj5AnvuVi0OvWMKzLIt8E0GMZH1Lhf5IIQBNFdlyBsiTANBWYGrBsGm4F4l5UyRnPlk9E3F1AlWdwuyzF3C1jDGLIMuL9FwPb8WntoR4mzqyCO4ihAlum8qhWS/87LEYaLRYkhgHwbSjjfqZRUCWqUdjBxYXeHXRLqjbE/3G34qFW89gD6XLeeCFilfEGHzWejZXOtT2EgAhxx0Kw4F+xni7iXiUdzDVTaYxqtR2Q/5A7QWgkqp7DE8AlB6xsR8kAgSOVURL5dHSwNBc6g5VLBp/+5iPDvclzmsxIDZU8efSv2pe/QMZYTROES7lDOdjjIPz66TW2dvOVfxE5WE3lWsS3U6UypHrdpX89liJb+v41AI3fLt+ys4aP7dfcQvXtHTfZ/XCTVvB1arZdAdO3zV6+vvqnx/8230VFj5b4gQ/+dZUHD0/SehYeB1/doqdZ0sPCKhEvifVYX8VLVxOz5HAH6CAGhBtcqJhkeiFb0fSp2LgY46l0zDAD88EUihgGSiC84Yc8tDBADusLoFk7g0dpSxcFHAXl0pSMPn8afxD0TOdBo/JqbeD8Ne6fM44YbF2PS0wy1wOcSUXlC8Seqx1C1ykVhQEw0+FajP9nrxMXFhJwXz2IZG2XLGkTmf+Ll2WIO8hiY7pXJDlVji8bVINrsaQoqLgkv4RFmR3Dpn8seDmWzMeGonHfa1ocMm5GDfhROsxhK9CuqCU34UD6Fu5RKdj4wqLtUT+xEYj0mVw8vQGVChpTYHd13NCxoHFf6WaweIYTpNAgabIOL/lsYelUDC+yDbaty+3I58YYeGTj08yGx/sJ395mM5CQZ5IJNzZCvklYu6Uc4dwYrhbYjry1+4lhFRFCMAPQXIpymtx3DH6wtj5pebZ/Jt+5yMi9WWa/IrHbFVwMs/pLCPHrNn8g9cZo+OqHXF4n16D8OzhlAuBAUR00Gtgw7cznKQ7+qWu/R+7IUuCJ3ZdWQqIiIMb2u+Zd9nB/SDTW1Y4KyiPiFqqje/2JwoMD5ymnP8frnCf9UN71ZSdY63/s5C/4iohhSUsZ2Q78zdYlBtnS/rQ67ROeqVIOi8UgrCzb3eEMazMagDp2aEmfob45XtPny/UE0Zz8PrAuuZwE3tYqaiV2U7pCQ1wHc4pXjswhrH4ZZqQ5smVcdOtmk64IBsfblwGF2eapLkfGEL6qjkXxWMKP3I8AFO3T9Mf5hpHqyOvd/yrMv0gFOF1Zi7qoIVuwKg11JTPOiHZSsMCZ2rbV+x9lfDFrmm+GyauEM8DFIpDR3FYmeIxtxvLy+J3xaQ2LV4iO3RMv76bWRGEYJetQ+eAI8CacPz0BbOUaohqvJxsTUNKQvmfGJvGbffg8XyvEFuUPRJ+L1l16Y9F9XCtYCKpv2Jw7FbRNXXgMjRba9I1CqZxKupJ+x5UH4oD5qduewd1fQ6Urz7UtYryK+IvszAo5I59kQualULXKq3mp8VS+Ecj+nvRBsiU8EXrg34lAZEwwgXh7/V5xb18Z+JcTCbzzrbhADhxzuT3wklVvlLta4T/eCejyxWvrGydgdjArNGWAf3jDL1SawYieMqP5EJ/gJ+P26geYB+12PV+jdVYiP381BCO/ffbXLRiCJT+448PHSXfXiOKLtyvVbcr8IU7p1lzvXM2P0D87mtZ/olU8QzZU0deo6ZF086CeUSNFKYzpdXDGcxz2DXrZSTf1JBQjDHUddu3WW2AUVGvc/ROsYZzej14e1Z7zEftk7hL7XlgNNqNttTMLJbllA04coA+6izvfGf3TRPUWvTvmIE99gh1Icos4T7f5x2tZUxWeDb3EJ29DwXDChPJ4Zh+DuyBZdNq4T58wkVGp9hAbniA2NnZ+P6wck5ZRlu9SQQZQVb1mEeR6zY8hy3T0JOZXZ9ROj9szrCrW1UCjvbqBJFVjF/IEUkzsnuKJBKUPp9q6+z1Ch/rfcOgJGs/SU6FRvfa6H7heUn7GlUIRHRYu38luMVPXDt0LJsqqDbd418Di3Yun1Sbw/dv8LYkxfz4/Vo3ddb74bPddQGi29NtybRsl2AKpPFBz1C32cRI66U99+w+kJC0gANCe4AC3k5dmX4dtmotzTK/VzG5Bq42VE49kTqN22hpmXJsbtXw0bGdgdblMVZfkvYH20s99Q91PwBPuk6DSx3JNzjDjgpYuKYoxNz79bk7HdW+IMrrbRzEtMzVBg4CxCJVVUz2TqCwL3JzBWYDOs50seRCq2YXD5Q/1bvSb/F/tF0JSezmOM2czri1osaoD35fUQi3UtZfn49rmE/e7l57RsP2+PzBEnAoC81wToWBeZLjYajJl/P+pFmtbb3n53dIBMVPOteyXlXbmIaW+K2hkU8eE2duUiGoWldlO+VxbHSCkO02VNeknXSQZi5vGOoItmnZzhm6Lv6OCflAsyEJ1kLQmBGchg2WY7EKDkTDgGqLjRFZAqHs1ZzJsZBTIwEUJymGnHuPGJ1QqJg3aOhP0qRCEJcu+/W4/vrHz/kx6vAugF7ZsI6lK2gVDxk8tjqUVS4ZEjdpgDBnVPb0tbDdBWK2k/3fukhQAsW1mVuxNyF3XxoKtu+PmXBbesQidi0GE7Ajwy0w3902f1vsaOP2qtXjw29PD+M/sxQC+AZPVRuGaCRGA29qN7T75qA2VYjGNl54iEw6lKN5RrZdKEAcgpg9vasZaaO2xCJUwkF21wDz/QDdZgLeqeZoUDj2bF3I+mvE6eXF6IkmmcqQEl3SPsYsBUdbfsY4WLK9Y8J3XM5kmJ75tDZiodTj5/MwC/JcROn4Zd9UI25G2F9U3dOe7gULWNRT+cd5U1/JQPK9FUs8l4FZBlcZBu7cMwpsLtSPF7TtepEMNnRtCAmQKurOaIwOC3xIWXsi2BE7wndGL9ZCgPsLAcp//w4aM0kBHLf3uIOPEP3eFuxii4Ao8EKSOlzbY+WQpfeVRTOnVsRw8bgW4BXg1jsaP2WmFObwqxCgovePjQ4XF2IZGHA7g9CqkJouGSsARuSZuhNNAwV9eqqvWETQkaN3LS2Alwe72ZyU4XNIncx0lRHU+1OKOpNEBRhSX3eoZQCncSAikGx85co70QpskU6xPXu0/haX1nCqnDTqwQVAv4yiz4wYhaO1jDl490M0/beILUjN/pMIpHymqfsOQqI4Ujdu4wKPE1Ro6AHbech5PO5pyhxBTurIJajQdBFC1/h6pk2dG/H2H2EXkPMBKAAJAZUOMaB4NX42wQ1WJwlPgLojAtaVPSIFmNi3ny2sqcGsEEfS7SFhJ1EVP89YW1UbDm+S8wBaFbrJCqo9AVPfE1YJY93TkgYotJ3Cc6HScowibq+lLL8vh89LUIHqiV7U6oRgZNrJvliAITVEI4iMUj3IdRRjorsgmwUKlrcnqP8XUq/XDETUR8DtotmGY4VZhtxLhHnCcYDm2LNhgBZh0lhxz0cKbPR1iug4g10jme95j7JNhxf6jrUAmK15XuHOlsgGdsE/rHySriDpwPL5yLdF3zV/RVYVxmwI91VtBKAdUYLAFa7QAi9tggnhKYgGBoCNtt5kkLNNLnGmQ2d4O71e382OZSzOAMPPK9B2KHujr/Gj6TqaPExTi25XdTLuehRYEIPcCnP6JfTw+kWuojjCqbyW6Dsv/+UTt8Q/nrPbCql789dH3DP+yuPFc6wlTN7RyC7Oy9v6Eth6TBEOfVEPys2zL26hfJkCEzxrWEXbF1N1CiVtt9vXakggtXRjoCW9w45g8OI7tU6KTQzK/MrXOV4dYMqs96lixXrLG4as9hcpiE0/S/3OIQ8t8EUxE4whT2uMsUgFUN0OZW+LPED3rt6/wUt6i6s7dRjqpV184DhwZfiqSqYTWya0Hwoq7g8mHTdiIV3utlAd925FMWWvKC9It+JmK/e+Do5SepknyQP8DSgu1HHhnXOLb81zXL9wjvqpDHerlM/HITMJl5UXxbAGWxkxSY8Y+ttLM9UpVtiV4ec4fsGnsn1vuLHxqk+Ek1o97clkqHpyH6CtrV+iW0esqZqrQDNuPdPTbJ6Q+BDI6ddMp9pKlfwbp2/zkunZLnwnOS54x4VVc1PmjZw32jJZc294N3vzEczEk0ea+ktRCO5cOeqoHSg+cTp27kb8t2a6Jl4SgakcfWJMuLeO0hlRuodJcfDnWM723J+D7lkSx0IhuD24Cn8tyt40iSF/DT03F3yCQkXHHcOQBJAfDniRA2kuQhNNkwFjk7z8FcTCtk2XQXTpXokWp+k0OurHidStDO+JrFVyzcKVukrG2fWcs3uKTbVcJJBj3xvKBIL3aDvdnMixNDN2IAHpcD9+mUmmNXhTWYe5oAx6TOfmm2XAdMV3P/nqzz47Lp3an4uXPYd9J16C9i/Pv89BlT/IHEc/XcO6mED2rN9sVr25Z7X+ZIyvlXzszDjv0IJQgzTX2NVOxrdqHlEiqeTsagRoJCXrt8b0JyEadRNCN9OqHgZAuSAgIuDpgmkkwcSkN20Kw8WhhSG2oxqJtMoTXemo3l+8w3rNbM7MW1iXUNYv66LN9/akEAlAfRdyfSg/gQpg1pPqh+JhDWlJopFzyWc6H6UmFIrGlxcYGZMgGRXJuhmia3JMuH3xrK0Oj4hwaI3TyIyQ2V45ydqI+M6LQJG+zgaZMj145Y+idKoX8n33WE6bqFgqCx0YPRbmrzdmS6UTKt7/aWJUn+anO5wq7CzVdKEb4jxSUnFXL8i68GVWQs7uYSH3twUp4go3V8lXfcW3lOnVoKo1uCUQno1tV7jnsZFJllpauvUmkzKKiu1VhcalOe62ybZVVl1UaF0QTiJ2XVyk0B8K5OhUoSB9kvFmV1aNbsjzgjAC0LcCZ62c7favizvvZLop/ILhWeLM9Njs0wYHsnvUz4dTYdyKSR+lcle6SCumkp1fAlLQfR0DPZTnAVuUiwvlGAtF+82YklI0Y6c46Qs32IqCOyCG4yjaDD0ajI4HUhpf+RWDa9HPlFjczDDuROVaywiSt9uRHIYXkphybr89dt2vTaXVKQPoVrFTWeWdjyca7Wi/jE5BQuxSDP2iIZ1zufqMnk5r9WlfelxUWmYF6bllvaqPkiYXc1NAbO22Iaej6mrE1L6PMmppFJC+4umxqlhXWohUzYWRl2h6KP8ChxA9hifPvQpX1pqIar57qAiaVuop6zkNnWI8ScW0eRMW6mEKS1qzpwGb7dp4+GAkCStjMW14rE28na3uTKI65SEqcrjjfqSRNIicmWORapTMW8h2zXDl32hOMlt3OHiWneDj5NsfGo5Clv3Wb9U9qhPkH+O3A4aTjKhp9Q6ehZivOUTQOFQ0WundUlwWNsWlFsckmdXWMm1/V66mR5DqcWt0jU92ScCMSPsnW62X1n+gxvbli0wx2gVk94UnxLO6cw7pBYqaUWTsc36aczZB6KaFyZ1Rk3u/CzaC9EMc55iI2Rp5KiinLtcPLBKnftM9Nm5Nl589UtnFXdvxwtk/stO8HCtXt247hU2ergVW6twjGUEms+4/7J7ZCOkJuFsyVod3assY4lxjN6OZj3EPZTpxdlIwdPgx1lhOma6qVhlGvh19x4v9eqbJZLVJMx09aMAaAesnouGnCU/dqUKkuh1lDPNBfItH1X2W3l9IVqd2pUcBap4vc64zn/RiVXQryMhN/F1IEboDJstO+5QmKYv+wkNQCPP0dm+4tA4Y4TZH72uzIztzaguvNhFcItDSYF7Dj9bKO72arvaE9a5ylaNUw31AzFS7TxSn0KstnjI97jHSrwhzxWDWe4q8x1eHbv79teDVbZJg7JNqCjZTWKLbO7Sc9lJRTkwOSKgvHcDep2Psn1jYL/vyWlvm3iX+bJ3ZDONHBU9FJvdhlZxe5Wu3AE9DNanFArMMbrHSq4NTZ/Og1xI+jNaypqmc+w+dCZ1XoXDNrHlJIx0yRwEjHqd3GuNyjO6/rUlPOYTWqSovY9nYWEJatq3djs5ccXEElUyTb+7MSDntCDfWzXn3xNcnzPMTRUSw8ttYz9Wfos6nx/+5cK8ErZ5/KamXfzBWT8lwv7pyZBJmb/9j6KMm2Mre81Cmr9Dul3I38WULtxMU62MDGDVwoTFvs9WotQqzOOiRspnd7fM7m6r724qlG2HXwdg7dYF3IE9/9aiWltByKi483o8+jt+G1BeRHejnLxa7IzdQ542oyeSazI6vJDDG/YQhHPckXOwVHjbYU29C0BnUga6YF8GnD9OMtQ8/0E3J7HKch66NjVgcM+ufkSlcEMXIguITOkDZ8uUAfH1zarU5+MONa+RzUPNYgn4zF08ksWEVI85lMyaEVidg7QHkPeAdXVTMAVPTmUL+4LArutl8Rei2PoBlyJoLBgCxXirXmDso0RHg1c404Ot7BZcxcxBZf0eO1E4cJzwBS5ECAoyA+BcbfgF7jZ9rcAAfsQWZUZYIM/C4df7aflRlOzv8t6E9rrropsowfNPQcH8Ofz4sPGT8SL5Qh2YNHcPNcj60DMaZpeVoOh9ymAGTqXqdtGUKLIg9NlOxRqNO74n1kfhbfSfIKfDJ4OrVOZmP/kExX2VhjzFECGx7FUaqOQuu0abqMO5kntiO1tn8RaUdTMaaVoBEfNJPlW+6VcW2vOY8GfdsfXg1FJFa0H7oQsj9RYf6RjMtuUTV2G+yblcaatHeR7q0bPKVoeCB+F4MWVBQHfSN2MIn7thmbSOYqq1TxZyXlawNeUq+FPeShGXaq/e4GavG+cEf+JInzZC34h1zta1al7Qh0DucBlZVATZUwQyiwEMmmlAUwgQbwCsFGyaNXDNVtY72ZS049ualMOhMCq6+hxwLVsjotCCUQjzgdfgUItNUoJJUtyEp3MoyRRGGNLZxFzX3V3zd8we1uy+4hZ4m0PMeeSdy993YNwVCi3nl+2rudFFuZp+ogrlCT6jnrHcfDNhnlc5f81xnp1BCDa5NrvlzOigrSNUnia6opwpLYKQY686xiidTAyxSl8SeoEJFUQFMA21l4C0nu/8KgZ58urD2npcPhp8F238DtsdtrxtLfENt0JTbheifcFg/BUg2y9Te5o+B4qcitSHF9k0u3zSBvOm9lhmSWHPgJwlk2WX+to7WArs2S37ow1qnBTM4RGO1KDP9YUfmPTysT51aantlzxJhbJpiYv0TB8PK+M1S5EFocpO1a2L+Ox/k6HudjfvRu1JACB+8bhXYVyBmyTPzULu1PFAsoJPjxkFm4Qp38dsKjS3BFF8MPoCONt3dwVJWT6Lpaavlwfl0VN5KSNjpFmEdYLpko534TsNqO6/DLBt9PtVMhat2Fwiq9Q0hs/BqLDCXuoA8ENHzJsf6+NiGzZ0t+E+q00oZR4YLyKkTurGMpTS70VmU/+HQ1leUX7XD67xn8W1ZgwJVprRGsP74ScSRa1Rtg+J7/pH0GP+yMOCu+IRO+VTBOnEjauu/MzkeJCo+ZQE4gW5S3lHcJcwzVrc1C0k0DqNOJUm+RBUP6+CHROhtYxwlCIhjEwIeOYi4trOKRsXiuKCIkeZwpr0r+GKlm5tXJFfxUlJPTQppKzH/aR/OHLluoLfGKeuhzLhwk5HdtbczFoh51OpuWNpbJd3TEeUwBbFMtgm7F/ndMvH1f9+gQMk5DD0gmFSt920ZDehEw5VRAswvMgnL7ka+irncnFgDeBzOqQ2DFsKEnYndVlao48bEyKj9BGMkGLA57NZGtdYrLCc8LPuLTwH5wyT8ykgg98Yk3ttBtqTy8HurppNiMWTFOKYrAhOAEUlOTI9QTZA4rtymyFmiPWcLand9bYCOfB/ug1SIwwQnjDgnh5lKdtjgky5RIyKo0pCAvI7XWxcNCpilAIjnTiTlJ9EVs7labivqjg+xQq2qYdkZUgVVKjq7/9ag+MmIheVL6WYGlbUV6DHpj2zfOsN/NU1qk6Jpp1xdLGM2SUcZIT29pZB5x3MbfwF/fLd18EvpFZi7kLeVocM7/1c3OXLLdwJty6o1jJA5iPTiC4feTSlSDs85V0wudwYGE7zTDWF6bwQyhS15kTBLL90gx+mSl5YfBi6M6TIDEM+kXAtGBFjVlcTsEpdATLsUXCK+7VWMN0yPEd9G73keW0sS43n6iIVkAyBPRyMEE9cErbfj+u+uLNyEKCSOkSrEgJ1v8oK+9VEkIHvUR26yqtNWhuLTdMZIVHYqV5pBpt15AD8A5VHRUvOPN29FSO+8ew4SA/DNddt8oG7XgP7WYnGYUUAVeKm2i9Q6zFH5Bpyqmdfw6sFQV2OpihI8PPxx5jqiqkN15jWKO7gg8L363Sr9jQB/nZpZdNzzQWycxOVNwbbuNgwrkk8vqMt4/g3SjcT3Z1kO1bI+MILxFrfNmHu3JjEHwUPxVKFD3+Yhwi0HB8bHMgWcTg1DAjp79UVQWEBEVtYqxqPZJhnrSfdeyyRW9FYe/Sp269H4nIJ+85225Qo14yQNJfOl3W47f8AGtry4/D3OiujuxJMUWhx9teW7v5Qgyu/e+l+LiudLN0jnKkJnAAEpovL/3piwoah5ckoBEq/15r/RhbonG/sj0aFLFp1857pQjzEYrVErvCu3XVLFDoBzmZW0q6rF8oygI7D6+z39WCUe5yMgDtE+uZa3N0nxuUZOJoOkNNHProiBAw5QZoF3oaOF+Aj70L7vn8MiZQ5eTOsIN/OxCR8eJXezKkQ56qqLkVKe3CLu+AdboSWaXp/iCWdcYP0Y462m3hbVI1BzIevHzp55ul0/q7D8fzBiwOA3EgCP534E6H1gDzLC1vZbwE0Vl5qcPMtCmQyGEU9BDmlVRtdjrU9CaXJw9RiK1WMVnSqtR8BO1CJg0OhBvttBAVeUbYnwl09NkjokELchjbZZV7atY5KGJxYUfNGS64LNsvBX0nG6UBhHB7Rj6lgc0NIovm5PJYiZHaEAzSFa8LBwoTU+PvJcDnTk1hQRd0Cp62/mwzcNG94e++Om5EJvUKNMPmPsXf/FU58fsvIlDgvnjFaRkRPMfVIdUrweWB88nQFaTe67rzJ9+EK2oSv725Gv309dDz2Pks52Mmqu214fJBrtPcmBxfTwJepCtrA8XNwwnAOub8ZjeSDV4ltSHBzxlRKUfWZbl35KYNNDbmP99onATfE9686N6zidx1sed9Gczy+Q+ZhgTcULUc6K2H3JyDuVCloPac09RPltr6JLSD22UFkR0Aj5bYX6NevIgpD5FsdbGqBooN+nlRrms580rOlFl4Teh+6IF8sQES+UYQ1EfA5tH3TO8zM7rI8lEJ0IyaM1x4BYoLWguVtv9tHTLDcNCk3fNh3eKjgkHYNOfC7PXFZw+2TEhDWGt2gM6mmDSUEraUDmiQcqm0cKikZGWx448Du3GxgokXAcrlBa5mBxIbDFikCUOPjh7n5kUwsXWzTXuKZ24SfbFCF9iTYNy2oLHfbC+h2Anqe4UkutRfWXdD9C3V3cmopBjc5UqZd/UZBbL2kk45hcE6Axw+/wneWAZ+NYobI5SLIAulEo1ICQXlrCUcnKS8iIOqyOnNrqDNjKgbg9DuVo3eC/KQlGHYzXgQSxYagtAF+/hH8BggsoEd5pWFjuABVVrgAoa1oETGHQtHaukBUh4sETwF8WcAUFBDBlwg4ECRNcqp26A4nAmPGwzbcnWknjIWbJ/os7LxbdltSEhmgC5NwAvDSwQjkCp/yF8l6mUH4TQm1LKpUWVGCgAoZMBE+58lHrih//Zv1ML8rxYO4NkE/Fu8Z/31XwU+cyDn2sZJNAp/k4W12bz3O4Nv41HnyAiyNezA76pU/JS/73eBuEPXX18LqPLp1t9weEcW4VmdNkx6b32eZXlX6YsmjT8x3A+yBUb3PpEdL8AVcB5Q77kcHip+GhH7XI7OkccRp+pmPGLEO+rClBNSOQPKAmqk3EnybUKU6B1VM1LLAiRDVdCYIuyWo/PLZObqTL99ogi6f8w/Zt+JAFgZSFW387WeqEM8p9GYlrcIyd82D0RMLeqwesdS7U98qUCoouJPlQdsbny6XsU5z7U7JayX135INNTzZCpTbjWP0QNh0G/3skJvN+cYv34bpM58zg/SZQzI5gnoxf2C4WovXcFlo4byite4FpF0/bz7zESslMfq4NsJ1gEGbwG3/8ay+/Wc4yOtz9x9xwHyQSqsGZY4GPWJ6XBfz/sNdaZR1lcxpjc3Ll2oC3/WJ+Xz6rmHxcdxZHpClKgqiWbmZEYBPnjRhytlL4kos67A6SfIUz6COPvWOS4hrSF8Wl/u19O54W+AkK56NnWmW5pmqY5TbHTgdClLDAg92AslKZcu4X3qsiluFx62lA5XZqgqDRo5YYWsqdyk9Vn0Y+5BFggcC5MZ4D5FEs0V4sEK8EA/wPcpDFlWMyvg8WKeNgWb7EbHbqR1d92dlSn0E8nRsdOo+z3J7tbSAC3f9e3SzDJB5xVXbt+Zq3ayiGJzf4KV4Mfkf\",\"base64\")).toString()),Jq)});var gs={};Vt(gs,{convertToZip:()=>MIt,convertToZipWorker:()=>Xq,extractArchiveTo:()=>KIe,getDefaultTaskPool:()=>YIe,getTaskPoolForConfiguration:()=>VIe,makeArchiveFromDirectory:()=>LIt});function NIt(t,e){switch(t){case\"async\":return new Kv(Xq,{poolSize:e});case\"workers\":return new Jv((0,Zq.getContent)(),{poolSize:e});default:throw new Error(`Assertion failed: Unknown value ${t} for taskPoolMode`)}}function YIe(){return typeof zq>\"u\"&&(zq=NIt(\"workers\",ps.availableParallelism())),zq}function VIe(t){return typeof t>\"u\"?YIe():Vl(OIt,t,()=>{let e=t.get(\"taskPoolMode\"),r=t.get(\"taskPoolConcurrency\");switch(e){case\"async\":return new Kv(Xq,{poolSize:r});case\"workers\":return new Jv((0,Zq.getContent)(),{poolSize:r});default:throw new Error(`Assertion failed: Unknown value ${e} for taskPoolMode`)}})}async function Xq(t){let{tmpFile:e,tgz:r,compressionLevel:s,extractBufferOpts:a}=t,n=new hs(e,{create:!0,level:s,stats:el.makeDefaultStats()}),c=Buffer.from(r.buffer,r.byteOffset,r.byteLength);return await KIe(c,n,a),n.saveAndClose(),e}async function LIt(t,{baseFs:e=new Yn,prefixPath:r=vt.root,compressionLevel:s,inMemory:a=!1}={}){let n;if(a)n=new hs(null,{level:s});else{let f=await le.mktempPromise(),p=K.join(f,\"archive.zip\");n=new hs(p,{create:!0,level:s})}let c=K.resolve(vt.root,r);return await n.copyPromise(c,t,{baseFs:e,stableTime:!0,stableSort:!0}),n}async function MIt(t,e={}){let r=await le.mktempPromise(),s=K.join(r,\"archive.zip\"),a=e.compressionLevel??e.configuration?.get(\"compressionLevel\")??\"mixed\",n={prefixPath:e.prefixPath,stripComponents:e.stripComponents};return await(e.taskPool??VIe(e.configuration)).run({tmpFile:s,tgz:t,compressionLevel:a,extractBufferOpts:n}),new hs(s,{level:e.compressionLevel})}async function*_It(t){let e=new WIe.default.Parse,r=new GIe.PassThrough({objectMode:!0,autoDestroy:!0,emitClose:!0});e.on(\"entry\",s=>{r.write(s)}),e.on(\"error\",s=>{r.destroy(s)}),e.on(\"close\",()=>{r.destroyed||r.end()}),e.end(t);for await(let s of r){let a=s;yield a,a.resume()}}async function KIe(t,e,{stripComponents:r=0,prefixPath:s=vt.dot}={}){function a(n){if(n.path[0]===\"/\")return!0;let c=n.path.split(/\\//g);return!!(c.some(f=>f===\"..\")||c.length<=r)}for await(let n of _It(t)){if(a(n))continue;let c=K.normalize(ue.toPortablePath(n.path)).replace(/\\/$/,\"\").split(/\\//g);if(c.length<=r)continue;let f=c.slice(r).join(\"/\"),p=K.join(s,f),h=420;switch((n.type===\"Directory\"||(n.mode??0)&73)&&(h|=73),n.type){case\"Directory\":e.mkdirpSync(K.dirname(p),{chmod:493,utimes:[fi.SAFE_TIME,fi.SAFE_TIME]}),e.mkdirSync(p,{mode:h}),e.utimesSync(p,fi.SAFE_TIME,fi.SAFE_TIME);break;case\"OldFile\":case\"File\":e.mkdirpSync(K.dirname(p),{chmod:493,utimes:[fi.SAFE_TIME,fi.SAFE_TIME]}),e.writeFileSync(p,await GE(n),{mode:h}),e.utimesSync(p,fi.SAFE_TIME,fi.SAFE_TIME);break;case\"SymbolicLink\":e.mkdirpSync(K.dirname(p),{chmod:493,utimes:[fi.SAFE_TIME,fi.SAFE_TIME]}),e.symlinkSync(n.linkpath,p),e.lutimesSync(p,fi.SAFE_TIME,fi.SAFE_TIME);break}}return e}var GIe,WIe,Zq,zq,OIt,JIe=Ct(()=>{Ve();bt();rA();GIe=Ie(\"stream\"),WIe=et(_Ie());HIe();kc();Zq=et(qIe());OIt=new WeakMap});var ZIe=L(($q,zIe)=>{(function(t,e){typeof $q==\"object\"?zIe.exports=e():typeof define==\"function\"&&define.amd?define(e):t.treeify=e()})($q,function(){function t(a,n){var c=n?\"\\u2514\":\"\\u251C\";return a?c+=\"\\u2500 \":c+=\"\\u2500\\u2500\\u2510\",c}function e(a,n){var c=[];for(var f in a)a.hasOwnProperty(f)&&(n&&typeof a[f]==\"function\"||c.push(f));return c}function r(a,n,c,f,p,h,E){var C=\"\",S=0,P,I,R=f.slice(0);if(R.push([n,c])&&f.length>0&&(f.forEach(function(U,W){W>0&&(C+=(U[1]?\" \":\"\\u2502\")+\"  \"),!I&&U[0]===n&&(I=!0)}),C+=t(a,c)+a,p&&(typeof n!=\"object\"||n instanceof Date)&&(C+=\": \"+n),I&&(C+=\" (circular ref.)\"),E(C)),!I&&typeof n==\"object\"){var N=e(n,h);N.forEach(function(U){P=++S===N.length,r(U,n[U],P,R,p,h,E)})}}var s={};return s.asLines=function(a,n,c,f){var p=typeof c!=\"function\"?c:!1;r(\".\",a,!1,[],n,p,f||c)},s.asTree=function(a,n,c){var f=\"\";return r(\".\",a,!1,[],n,c,function(p){f+=p+`\n`}),f},s})});var Qs={};Vt(Qs,{emitList:()=>UIt,emitTree:()=>tCe,treeNodeToJson:()=>eCe,treeNodeToTreeify:()=>$Ie});function $Ie(t,{configuration:e}){let r={},s=0,a=(n,c)=>{let f=Array.isArray(n)?n.entries():Object.entries(n);for(let[p,h]of f){if(!h)continue;let{label:E,value:C,children:S}=h,P=[];typeof E<\"u\"&&P.push(Kd(e,E,2)),typeof C<\"u\"&&P.push(Ut(e,C[0],C[1])),P.length===0&&P.push(Kd(e,`${p}`,2));let I=P.join(\": \").trim(),R=`\\0${s++}\\0`,N=c[`${R}${I}`]={};typeof S<\"u\"&&a(S,N)}};if(typeof t.children>\"u\")throw new Error(\"The root node must only contain children\");return a(t.children,r),r}function eCe(t){let e=r=>{if(typeof r.children>\"u\"){if(typeof r.value>\"u\")throw new Error(\"Assertion failed: Expected a value to be set if the children are missing\");return Jd(r.value[0],r.value[1])}let s=Array.isArray(r.children)?r.children.entries():Object.entries(r.children??{}),a=Array.isArray(r.children)?[]:{};for(let[n,c]of s)c&&(a[HIt(n)]=e(c));return typeof r.value>\"u\"?a:{value:Jd(r.value[0],r.value[1]),children:a}};return e(t)}function UIt(t,{configuration:e,stdout:r,json:s}){let a=t.map(n=>({value:n}));tCe({children:a},{configuration:e,stdout:r,json:s})}function tCe(t,{configuration:e,stdout:r,json:s,separators:a=0}){if(s){let c=Array.isArray(t.children)?t.children.values():Object.values(t.children??{});for(let f of c)f&&r.write(`${JSON.stringify(eCe(f))}\n`);return}let n=(0,XIe.asTree)($Ie(t,{configuration:e}),!1,!1);if(n=n.replace(/\\0[0-9]+\\0/g,\"\"),a>=1&&(n=n.replace(/^([├└]─)/gm,`\\u2502\n$1`).replace(/^│\\n/,\"\")),a>=2)for(let c=0;c<2;++c)n=n.replace(/^([│ ].{2}[├│ ].{2}[^\\n]+\\n)(([│ ]).{2}[├└].{2}[^\\n]*\\n[│ ].{2}[│ ].{2}[├└]─)/gm,`$1$3  \\u2502 \n$2`).replace(/^│\\n/,\"\");if(a>=3)throw new Error(\"Only the first two levels are accepted by treeUtils.emitTree\");r.write(n)}function HIt(t){return typeof t==\"string\"?t.replace(/^\\0[0-9]+\\0/,\"\"):t}var XIe,rCe=Ct(()=>{XIe=et(ZIe());Qc()});var LR,nCe=Ct(()=>{LR=class{constructor(e){this.releaseFunction=e;this.map=new Map}addOrCreate(e,r){let s=this.map.get(e);if(typeof s<\"u\"){if(s.refCount<=0)throw new Error(`Race condition in RefCountedMap. While adding a new key the refCount is: ${s.refCount} for ${JSON.stringify(e)}`);return s.refCount++,{value:s.value,release:()=>this.release(e)}}else{let a=r();return this.map.set(e,{refCount:1,value:a}),{value:a,release:()=>this.release(e)}}}release(e){let r=this.map.get(e);if(!r)throw new Error(`Unbalanced calls to release. No known instances of: ${JSON.stringify(e)}`);let s=r.refCount;if(s<=0)throw new Error(`Unbalanced calls to release. Too many release vs alloc refcount would become: ${s-1} of ${JSON.stringify(e)}`);s==1?(this.map.delete(e),this.releaseFunction(r.value)):r.refCount--}}});function zv(t){let e=t.match(jIt);if(!e?.groups)throw new Error(\"Assertion failed: Expected the checksum to match the requested pattern\");let r=e.groups.cacheVersion?parseInt(e.groups.cacheVersion):null;return{cacheKey:e.groups.cacheKey??null,cacheVersion:r,cacheSpec:e.groups.cacheSpec??null,hash:e.groups.hash}}var iCe,eG,tG,MR,Jr,jIt,rG=Ct(()=>{Ve();bt();bt();rA();iCe=Ie(\"crypto\"),eG=et(Ie(\"fs\"));nCe();Fc();E0();kc();Yo();tG=WE(process.env.YARN_CACHE_CHECKPOINT_OVERRIDE??process.env.YARN_CACHE_VERSION_OVERRIDE??9),MR=WE(process.env.YARN_CACHE_VERSION_OVERRIDE??10),Jr=class t{constructor(e,{configuration:r,immutable:s=r.get(\"enableImmutableCache\"),check:a=!1}){this.markedFiles=new Set;this.mutexes=new Map;this.refCountedZipFsCache=new LR(e=>{e.discardAndClose()});this.cacheId=`-${(0,iCe.randomBytes)(8).toString(\"hex\")}.tmp`;this.configuration=r,this.cwd=e,this.immutable=s,this.check=a;let{cacheSpec:n,cacheKey:c}=t.getCacheKey(r);this.cacheSpec=n,this.cacheKey=c}static async find(e,{immutable:r,check:s}={}){let a=new t(e.get(\"cacheFolder\"),{configuration:e,immutable:r,check:s});return await a.setup(),a}static getCacheKey(e){let r=e.get(\"compressionLevel\"),s=r!==\"mixed\"?`c${r}`:\"\";return{cacheKey:[MR,s].join(\"\"),cacheSpec:s}}get mirrorCwd(){if(!this.configuration.get(\"enableMirror\"))return null;let e=`${this.configuration.get(\"globalFolder\")}/cache`;return e!==this.cwd?e:null}getVersionFilename(e){return`${rI(e)}-${this.cacheKey}.zip`}getChecksumFilename(e,r){let a=zv(r).hash.slice(0,10);return`${rI(e)}-${a}.zip`}isChecksumCompatible(e){if(e===null)return!1;let{cacheVersion:r,cacheSpec:s}=zv(e);if(r===null||r<tG)return!1;let a=this.configuration.get(\"cacheMigrationMode\");return!(r<MR&&a===\"always\"||s!==this.cacheSpec&&a!==\"required-only\")}getLocatorPath(e,r){return this.mirrorCwd===null?K.resolve(this.cwd,this.getVersionFilename(e)):r===null?K.resolve(this.cwd,this.getVersionFilename(e)):K.resolve(this.cwd,this.getChecksumFilename(e,r))}getLocatorMirrorPath(e){let r=this.mirrorCwd;return r!==null?K.resolve(r,this.getVersionFilename(e)):null}async setup(){if(!this.configuration.get(\"enableGlobalCache\"))if(this.immutable){if(!await le.existsPromise(this.cwd))throw new Yt(56,\"Cache path does not exist.\")}else{await le.mkdirPromise(this.cwd,{recursive:!0});let e=K.resolve(this.cwd,\".gitignore\");await le.changeFilePromise(e,`/.gitignore\n*.flock\n*.tmp\n`)}(this.mirrorCwd||!this.immutable)&&await le.mkdirPromise(this.mirrorCwd||this.cwd,{recursive:!0})}async fetchPackageFromCache(e,r,{onHit:s,onMiss:a,loader:n,...c}){let f=this.getLocatorMirrorPath(e),p=new Yn,h=()=>{let pe=new hs,Be=K.join(vt.root,j8(e));return pe.mkdirSync(Be,{recursive:!0}),pe.writeJsonSync(K.join(Be,Er.manifest),{name:cn(e),mocked:!0}),pe},E=async(pe,{isColdHit:Be,controlPath:Ce=null})=>{if(Ce===null&&c.unstablePackages?.has(e.locatorHash))return{isValid:!0,hash:null};let g=r&&!Be?zv(r).cacheKey:this.cacheKey,we=!c.skipIntegrityCheck||!r?`${g}/${await BQ(pe)}`:r;if(Ce!==null){let fe=!c.skipIntegrityCheck||!r?`${this.cacheKey}/${await BQ(Ce)}`:r;if(we!==fe)throw new Yt(18,\"The remote archive doesn't match the local checksum - has the local cache been corrupted?\")}let ye=null;switch(r!==null&&we!==r&&(this.check?ye=\"throw\":zv(r).cacheKey!==zv(we).cacheKey?ye=\"update\":ye=this.configuration.get(\"checksumBehavior\")),ye){case null:case\"update\":return{isValid:!0,hash:we};case\"ignore\":return{isValid:!0,hash:r};case\"reset\":return{isValid:!1,hash:r};default:case\"throw\":throw new Yt(18,\"The remote archive doesn't match the expected checksum\")}},C=async pe=>{if(!n)throw new Error(`Cache check required but no loader configured for ${Yr(this.configuration,e)}`);let Be=await n(),Ce=Be.getRealPath();Be.saveAndClose(),await le.chmodPromise(Ce,420);let g=await E(pe,{controlPath:Ce,isColdHit:!1});if(!g.isValid)throw new Error(\"Assertion failed: Expected a valid checksum\");return g.hash},S=async()=>{if(f===null||!await le.existsPromise(f)){let pe=await n(),Be=pe.getRealPath();return pe.saveAndClose(),{source:\"loader\",path:Be}}return{source:\"mirror\",path:f}},P=async()=>{if(!n)throw new Error(`Cache entry required but missing for ${Yr(this.configuration,e)}`);if(this.immutable)throw new Yt(56,`Cache entry required but missing for ${Yr(this.configuration,e)}`);let{path:pe,source:Be}=await S(),{hash:Ce}=await E(pe,{isColdHit:!0}),g=this.getLocatorPath(e,Ce),we=[];Be!==\"mirror\"&&f!==null&&we.push(async()=>{let fe=`${f}${this.cacheId}`;await le.copyFilePromise(pe,fe,eG.default.constants.COPYFILE_FICLONE),await le.chmodPromise(fe,420),await le.renamePromise(fe,f)}),(!c.mirrorWriteOnly||f===null)&&we.push(async()=>{let fe=`${g}${this.cacheId}`;await le.copyFilePromise(pe,fe,eG.default.constants.COPYFILE_FICLONE),await le.chmodPromise(fe,420),await le.renamePromise(fe,g)});let ye=c.mirrorWriteOnly?f??g:g;return await Promise.all(we.map(fe=>fe())),[!1,ye,Ce]},I=async()=>{let Be=(async()=>{let Ce=c.unstablePackages?.has(e.locatorHash),g=Ce||!r||this.isChecksumCompatible(r)?this.getLocatorPath(e,r):null,we=g!==null?this.markedFiles.has(g)||await p.existsPromise(g):!1,ye=!!c.mockedPackages?.has(e.locatorHash)&&(!this.check||!we),fe=ye||we,se=fe?s:a;if(se&&se(),fe){let X=null,De=g;if(!ye)if(this.check)X=await C(De);else{let Re=await E(De,{isColdHit:!1});if(Re.isValid)X=Re.hash;else return P()}return[ye,De,X]}else{if(this.immutable&&Ce)throw new Yt(56,`Cache entry required but missing for ${Yr(this.configuration,e)}; consider defining ${he.pretty(this.configuration,\"supportedArchitectures\",he.Type.CODE)} to cache packages for multiple systems`);return P()}})();this.mutexes.set(e.locatorHash,Be);try{return await Be}finally{this.mutexes.delete(e.locatorHash)}};for(let pe;pe=this.mutexes.get(e.locatorHash);)await pe;let[R,N,U]=await I();R||this.markedFiles.add(N);let W=()=>this.refCountedZipFsCache.addOrCreate(N,()=>R?h():new hs(N,{baseFs:p,readOnly:!0})),te,ie=new iE(()=>i3(()=>(te=W(),te.value),pe=>`Failed to open the cache entry for ${Yr(this.configuration,e)}: ${pe}`),K),Ae=new Hf(N,{baseFs:ie,pathUtils:K}),ce=()=>{te?.release()},me=c.unstablePackages?.has(e.locatorHash)?null:U;return[Ae,ce,me]}},jIt=/^(?:(?<cacheKey>(?<cacheVersion>[0-9]+)(?<cacheSpec>.*))\\/)?(?<hash>.*)$/});var _R,sCe=Ct(()=>{_R=(r=>(r[r.SCRIPT=0]=\"SCRIPT\",r[r.SHELLCODE=1]=\"SHELLCODE\",r))(_R||{})});var qIt,KI,nG=Ct(()=>{bt();Bc();Np();Yo();qIt=[[/^(git(?:\\+(?:https|ssh))?:\\/\\/.*(?:\\.git)?)#(.*)$/,(t,e,r,s)=>`${r}#commit=${s}`],[/^https:\\/\\/((?:[^/]+?)@)?codeload\\.github\\.com\\/([^/]+\\/[^/]+)\\/tar\\.gz\\/([0-9a-f]+)$/,(t,e,r=\"\",s,a)=>`https://${r}github.com/${s}.git#commit=${a}`],[/^https:\\/\\/((?:[^/]+?)@)?github\\.com\\/([^/]+\\/[^/]+?)(?:\\.git)?#([0-9a-f]+)$/,(t,e,r=\"\",s,a)=>`https://${r}github.com/${s}.git#commit=${a}`],[/^https?:\\/\\/[^/]+\\/(?:[^/]+\\/)*(?:@.+(?:\\/|(?:%2f)))?([^/]+)\\/(?:-|download)\\/\\1-[^/]+\\.tgz(?:#|$)/,t=>`npm:${t}`],[/^https:\\/\\/npm\\.pkg\\.github\\.com\\/download\\/(?:@[^/]+)\\/(?:[^/]+)\\/(?:[^/]+)\\/(?:[0-9a-f]+)(?:#|$)/,t=>`npm:${t}`],[/^https:\\/\\/npm\\.fontawesome\\.com\\/(?:@[^/]+)\\/([^/]+)\\/-\\/([^/]+)\\/\\1-\\2.tgz(?:#|$)/,t=>`npm:${t}`],[/^https?:\\/\\/[^/]+\\/.*\\/(@[^/]+)\\/([^/]+)\\/-\\/\\1\\/\\2-(?:[.\\d\\w-]+)\\.tgz(?:#|$)/,(t,e)=>PQ({protocol:\"npm:\",source:null,selector:t,params:{__archiveUrl:e}})],[/^[^/]+\\.tgz#[0-9a-f]+$/,t=>`npm:${t}`]],KI=class{constructor(e){this.resolver=e;this.resolutions=null}async setup(e,{report:r}){let s=K.join(e.cwd,Er.lockfile);if(!le.existsSync(s))return;let a=await le.readFilePromise(s,\"utf8\"),n=cs(a);if(Object.hasOwn(n,\"__metadata\"))return;let c=this.resolutions=new Map;for(let f of Object.keys(n)){let p=JB(f);if(!p){r.reportWarning(14,`Failed to parse the string \"${f}\" into a proper descriptor`);continue}let h=ul(p.range)?On(p,`npm:${p.range}`):p,{version:E,resolved:C}=n[f];if(!C)continue;let S;for(let[I,R]of qIt){let N=C.match(I);if(N){S=R(E,...N);break}}if(!S){r.reportWarning(14,`${ni(e.configuration,h)}: Only some patterns can be imported from legacy lockfiles (not \"${C}\")`);continue}let P=h;try{let I=Xd(h.range),R=JB(I.selector,!0);R&&(P=R)}catch{}c.set(h.descriptorHash,Vs(P,S))}}supportsDescriptor(e,r){return this.resolutions?this.resolutions.has(e.descriptorHash):!1}supportsLocator(e,r){return!1}shouldPersistResolution(e,r){throw new Error(\"Assertion failed: This resolver doesn't support resolving locators to packages\")}bindDescriptor(e,r,s){return e}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){if(!this.resolutions)throw new Error(\"Assertion failed: The resolution store should have been setup\");let a=this.resolutions.get(e.descriptorHash);if(!a)throw new Error(\"Assertion failed: The resolution should have been registered\");let n=M8(a),c=s.project.configuration.normalizeDependency(n);return await this.resolver.getCandidates(c,r,s)}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){throw new Error(\"Assertion failed: This resolver doesn't support resolving locators to packages\")}}});var uA,oCe=Ct(()=>{Fc();xv();Qc();uA=class extends ho{constructor({configuration:r,stdout:s,suggestInstall:a=!0}){super();this.errorCount=0;HB(this,{configuration:r}),this.configuration=r,this.stdout=s,this.suggestInstall=a}static async start(r,s){let a=new this(r);try{await s(a)}catch(n){a.reportExceptionOnce(n)}finally{await a.finalize()}return a}hasErrors(){return this.errorCount>0}exitCode(){return this.hasErrors()?1:0}reportCacheHit(r){}reportCacheMiss(r){}startSectionSync(r,s){return s()}async startSectionPromise(r,s){return await s()}startTimerSync(r,s,a){return(typeof s==\"function\"?s:a)()}async startTimerPromise(r,s,a){return await(typeof s==\"function\"?s:a)()}reportSeparator(){}reportInfo(r,s){}reportWarning(r,s){}reportError(r,s){this.errorCount+=1,this.stdout.write(`${Ut(this.configuration,\"\\u27A4\",\"redBright\")} ${this.formatNameWithHyperlink(r)}: ${s}\n`)}reportProgress(r){return{...Promise.resolve().then(async()=>{for await(let{}of r);}),stop:()=>{}}}reportJson(r){}reportFold(r,s){}async finalize(){this.errorCount>0&&(this.stdout.write(`\n`),this.stdout.write(`${Ut(this.configuration,\"\\u27A4\",\"redBright\")} Errors happened when preparing the environment required to run this command.\n`),this.suggestInstall&&this.stdout.write(`${Ut(this.configuration,\"\\u27A4\",\"redBright\")} This might be caused by packages being missing from the lockfile, in which case running \"yarn install\" might help.\n`))}formatNameWithHyperlink(r){return u6(r,{configuration:this.configuration,json:!1})}}});var JI,iG=Ct(()=>{Yo();JI=class{constructor(e){this.resolver=e}supportsDescriptor(e,r){return!!(r.project.storedResolutions.get(e.descriptorHash)||r.project.originalPackages.has(SQ(e).locatorHash))}supportsLocator(e,r){return!!(r.project.originalPackages.has(e.locatorHash)&&!r.project.lockfileNeedsRefresh)}shouldPersistResolution(e,r){throw new Error(\"The shouldPersistResolution method shouldn't be called on the lockfile resolver, which would always answer yes\")}bindDescriptor(e,r,s){return e}getResolutionDependencies(e,r){return this.resolver.getResolutionDependencies(e,r)}async getCandidates(e,r,s){let a=s.project.storedResolutions.get(e.descriptorHash);if(a){let c=s.project.originalPackages.get(a);if(c)return[c]}let n=s.project.originalPackages.get(SQ(e).locatorHash);if(n)return[n];throw new Error(\"Resolution expected from the lockfile data\")}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){let s=r.project.originalPackages.get(e.locatorHash);if(!s)throw new Error(\"The lockfile resolver isn't meant to resolve packages - they should already have been stored into a cache\");return s}}});function Zp(){}function GIt(t,e,r,s,a){for(var n=0,c=e.length,f=0,p=0;n<c;n++){var h=e[n];if(h.removed){if(h.value=t.join(s.slice(p,p+h.count)),p+=h.count,n&&e[n-1].added){var C=e[n-1];e[n-1]=e[n],e[n]=C}}else{if(!h.added&&a){var E=r.slice(f,f+h.count);E=E.map(function(P,I){var R=s[p+I];return R.length>P.length?R:P}),h.value=t.join(E)}else h.value=t.join(r.slice(f,f+h.count));f+=h.count,h.added||(p+=h.count)}}var S=e[c-1];return c>1&&typeof S.value==\"string\"&&(S.added||S.removed)&&t.equals(\"\",S.value)&&(e[c-2].value+=S.value,e.pop()),e}function WIt(t){return{newPos:t.newPos,components:t.components.slice(0)}}function YIt(t,e){if(typeof t==\"function\")e.callback=t;else if(t)for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r]);return e}function cCe(t,e,r){return r=YIt(r,{ignoreWhitespace:!0}),cG.diff(t,e,r)}function VIt(t,e,r){return uG.diff(t,e,r)}function UR(t){\"@babel/helpers - typeof\";return typeof Symbol==\"function\"&&typeof Symbol.iterator==\"symbol\"?UR=function(e){return typeof e}:UR=function(e){return e&&typeof Symbol==\"function\"&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e},UR(t)}function sG(t){return zIt(t)||ZIt(t)||XIt(t)||$It()}function zIt(t){if(Array.isArray(t))return oG(t)}function ZIt(t){if(typeof Symbol<\"u\"&&Symbol.iterator in Object(t))return Array.from(t)}function XIt(t,e){if(t){if(typeof t==\"string\")return oG(t,e);var r=Object.prototype.toString.call(t).slice(8,-1);if(r===\"Object\"&&t.constructor&&(r=t.constructor.name),r===\"Map\"||r===\"Set\")return Array.from(t);if(r===\"Arguments\"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return oG(t,e)}}function oG(t,e){(e==null||e>t.length)&&(e=t.length);for(var r=0,s=new Array(e);r<e;r++)s[r]=t[r];return s}function $It(){throw new TypeError(`Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}function aG(t,e,r,s,a){e=e||[],r=r||[],s&&(t=s(a,t));var n;for(n=0;n<e.length;n+=1)if(e[n]===t)return r[n];var c;if(eCt.call(t)===\"[object Array]\"){for(e.push(t),c=new Array(t.length),r.push(c),n=0;n<t.length;n+=1)c[n]=aG(t[n],e,r,s,a);return e.pop(),r.pop(),c}if(t&&t.toJSON&&(t=t.toJSON()),UR(t)===\"object\"&&t!==null){e.push(t),c={},r.push(c);var f=[],p;for(p in t)t.hasOwnProperty(p)&&f.push(p);for(f.sort(),n=0;n<f.length;n+=1)p=f[n],c[p]=aG(t[p],e,r,s,p);e.pop(),r.pop()}else c=t;return c}function uCe(t,e,r,s,a,n,c){c||(c={}),typeof c.context>\"u\"&&(c.context=4);var f=VIt(r,s,c);if(!f)return;f.push({value:\"\",lines:[]});function p(U){return U.map(function(W){return\" \"+W})}for(var h=[],E=0,C=0,S=[],P=1,I=1,R=function(W){var te=f[W],ie=te.lines||te.value.replace(/\\n$/,\"\").split(`\n`);if(te.lines=ie,te.added||te.removed){var Ae;if(!E){var ce=f[W-1];E=P,C=I,ce&&(S=c.context>0?p(ce.lines.slice(-c.context)):[],E-=S.length,C-=S.length)}(Ae=S).push.apply(Ae,sG(ie.map(function(fe){return(te.added?\"+\":\"-\")+fe}))),te.added?I+=ie.length:P+=ie.length}else{if(E)if(ie.length<=c.context*2&&W<f.length-2){var me;(me=S).push.apply(me,sG(p(ie)))}else{var pe,Be=Math.min(ie.length,c.context);(pe=S).push.apply(pe,sG(p(ie.slice(0,Be))));var Ce={oldStart:E,oldLines:P-E+Be,newStart:C,newLines:I-C+Be,lines:S};if(W>=f.length-2&&ie.length<=c.context){var g=/\\n$/.test(r),we=/\\n$/.test(s),ye=ie.length==0&&S.length>Ce.oldLines;!g&&ye&&r.length>0&&S.splice(Ce.oldLines,0,\"\\\\ No newline at end of file\"),(!g&&!ye||!we)&&S.push(\"\\\\ No newline at end of file\")}h.push(Ce),E=0,C=0,S=[]}P+=ie.length,I+=ie.length}},N=0;N<f.length;N++)R(N);return{oldFileName:t,newFileName:e,oldHeader:a,newHeader:n,hunks:h}}var ker,aCe,lCe,cG,uG,KIt,JIt,eCt,Zv,lG,fG=Ct(()=>{Zp.prototype={diff:function(e,r){var s=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{},a=s.callback;typeof s==\"function\"&&(a=s,s={}),this.options=s;var n=this;function c(R){return a?(setTimeout(function(){a(void 0,R)},0),!0):R}e=this.castInput(e),r=this.castInput(r),e=this.removeEmpty(this.tokenize(e)),r=this.removeEmpty(this.tokenize(r));var f=r.length,p=e.length,h=1,E=f+p;s.maxEditLength&&(E=Math.min(E,s.maxEditLength));var C=[{newPos:-1,components:[]}],S=this.extractCommon(C[0],r,e,0);if(C[0].newPos+1>=f&&S+1>=p)return c([{value:this.join(r),count:r.length}]);function P(){for(var R=-1*h;R<=h;R+=2){var N=void 0,U=C[R-1],W=C[R+1],te=(W?W.newPos:0)-R;U&&(C[R-1]=void 0);var ie=U&&U.newPos+1<f,Ae=W&&0<=te&&te<p;if(!ie&&!Ae){C[R]=void 0;continue}if(!ie||Ae&&U.newPos<W.newPos?(N=WIt(W),n.pushComponent(N.components,void 0,!0)):(N=U,N.newPos++,n.pushComponent(N.components,!0,void 0)),te=n.extractCommon(N,r,e,R),N.newPos+1>=f&&te+1>=p)return c(GIt(n,N.components,r,e,n.useLongestToken));C[R]=N}h++}if(a)(function R(){setTimeout(function(){if(h>E)return a();P()||R()},0)})();else for(;h<=E;){var I=P();if(I)return I}},pushComponent:function(e,r,s){var a=e[e.length-1];a&&a.added===r&&a.removed===s?e[e.length-1]={count:a.count+1,added:r,removed:s}:e.push({count:1,added:r,removed:s})},extractCommon:function(e,r,s,a){for(var n=r.length,c=s.length,f=e.newPos,p=f-a,h=0;f+1<n&&p+1<c&&this.equals(r[f+1],s[p+1]);)f++,p++,h++;return h&&e.components.push({count:h}),e.newPos=f,p},equals:function(e,r){return this.options.comparator?this.options.comparator(e,r):e===r||this.options.ignoreCase&&e.toLowerCase()===r.toLowerCase()},removeEmpty:function(e){for(var r=[],s=0;s<e.length;s++)e[s]&&r.push(e[s]);return r},castInput:function(e){return e},tokenize:function(e){return e.split(\"\")},join:function(e){return e.join(\"\")}};ker=new Zp;aCe=/^[A-Za-z\\xC0-\\u02C6\\u02C8-\\u02D7\\u02DE-\\u02FF\\u1E00-\\u1EFF]+$/,lCe=/\\S/,cG=new Zp;cG.equals=function(t,e){return this.options.ignoreCase&&(t=t.toLowerCase(),e=e.toLowerCase()),t===e||this.options.ignoreWhitespace&&!lCe.test(t)&&!lCe.test(e)};cG.tokenize=function(t){for(var e=t.split(/([^\\S\\r\\n]+|[()[\\]{}'\"\\r\\n]|\\b)/),r=0;r<e.length-1;r++)!e[r+1]&&e[r+2]&&aCe.test(e[r])&&aCe.test(e[r+2])&&(e[r]+=e[r+2],e.splice(r+1,2),r--);return e};uG=new Zp;uG.tokenize=function(t){var e=[],r=t.split(/(\\n|\\r\\n)/);r[r.length-1]||r.pop();for(var s=0;s<r.length;s++){var a=r[s];s%2&&!this.options.newlineIsToken?e[e.length-1]+=a:(this.options.ignoreWhitespace&&(a=a.trim()),e.push(a))}return e};KIt=new Zp;KIt.tokenize=function(t){return t.split(/(\\S.+?[.!?])(?=\\s+|$)/)};JIt=new Zp;JIt.tokenize=function(t){return t.split(/([{}:;,]|\\s+)/)};eCt=Object.prototype.toString,Zv=new Zp;Zv.useLongestToken=!0;Zv.tokenize=uG.tokenize;Zv.castInput=function(t){var e=this.options,r=e.undefinedReplacement,s=e.stringifyReplacer,a=s===void 0?function(n,c){return typeof c>\"u\"?r:c}:s;return typeof t==\"string\"?t:JSON.stringify(aG(t,null,null,a),a,\"  \")};Zv.equals=function(t,e){return Zp.prototype.equals.call(Zv,t.replace(/,([\\r\\n])/g,\"$1\"),e.replace(/,([\\r\\n])/g,\"$1\"))};lG=new Zp;lG.tokenize=function(t){return t.slice()};lG.join=lG.removeEmpty=function(t){return t}});var ACe=L((Ter,fCe)=>{var tCt=xc(),rCt=oI(),nCt=/\\.|\\[(?:[^[\\]]*|([\"'])(?:(?!\\1)[^\\\\]|\\\\.)*?\\1)\\]/,iCt=/^\\w*$/;function sCt(t,e){if(tCt(t))return!1;var r=typeof t;return r==\"number\"||r==\"symbol\"||r==\"boolean\"||t==null||rCt(t)?!0:iCt.test(t)||!nCt.test(t)||e!=null&&t in Object(e)}fCe.exports=sCt});var gCe=L((Rer,hCe)=>{var pCe=bk(),oCt=\"Expected a function\";function AG(t,e){if(typeof t!=\"function\"||e!=null&&typeof e!=\"function\")throw new TypeError(oCt);var r=function(){var s=arguments,a=e?e.apply(this,s):s[0],n=r.cache;if(n.has(a))return n.get(a);var c=t.apply(this,s);return r.cache=n.set(a,c)||n,c};return r.cache=new(AG.Cache||pCe),r}AG.Cache=pCe;hCe.exports=AG});var mCe=L((Fer,dCe)=>{var aCt=gCe(),lCt=500;function cCt(t){var e=aCt(t,function(s){return r.size===lCt&&r.clear(),s}),r=e.cache;return e}dCe.exports=cCt});var pG=L((Ner,yCe)=>{var uCt=mCe(),fCt=/[^.[\\]]+|\\[(?:(-?\\d+(?:\\.\\d+)?)|([\"'])((?:(?!\\2)[^\\\\]|\\\\.)*?)\\2)\\]|(?=(?:\\.|\\[\\])(?:\\.|\\[\\]|$))/g,ACt=/\\\\(\\\\)?/g,pCt=uCt(function(t){var e=[];return t.charCodeAt(0)===46&&e.push(\"\"),t.replace(fCt,function(r,s,a,n){e.push(a?n.replace(ACt,\"$1\"):s||r)}),e});yCe.exports=pCt});var Im=L((Oer,ECe)=>{var hCt=xc(),gCt=ACe(),dCt=pG(),mCt=bv();function yCt(t,e){return hCt(t)?t:gCt(t,e)?[t]:dCt(mCt(t))}ECe.exports=yCt});var zI=L((Ler,ICe)=>{var ECt=oI(),ICt=1/0;function CCt(t){if(typeof t==\"string\"||ECt(t))return t;var e=t+\"\";return e==\"0\"&&1/t==-ICt?\"-0\":e}ICe.exports=CCt});var HR=L((Mer,CCe)=>{var wCt=Im(),BCt=zI();function vCt(t,e){e=wCt(e,t);for(var r=0,s=e.length;t!=null&&r<s;)t=t[BCt(e[r++])];return r&&r==s?t:void 0}CCe.exports=vCt});var hG=L((_er,BCe)=>{var SCt=qk(),DCt=Im(),bCt=kB(),wCe=Wl(),PCt=zI();function xCt(t,e,r,s){if(!wCe(t))return t;e=DCt(e,t);for(var a=-1,n=e.length,c=n-1,f=t;f!=null&&++a<n;){var p=PCt(e[a]),h=r;if(p===\"__proto__\"||p===\"constructor\"||p===\"prototype\")return t;if(a!=c){var E=f[p];h=s?s(E,p,f):void 0,h===void 0&&(h=wCe(E)?E:bCt(e[a+1])?[]:{})}SCt(f,p,h),f=f[p]}return t}BCe.exports=xCt});var SCe=L((Uer,vCe)=>{var kCt=HR(),QCt=hG(),TCt=Im();function RCt(t,e,r){for(var s=-1,a=e.length,n={};++s<a;){var c=e[s],f=kCt(t,c);r(f,c)&&QCt(n,TCt(c,t),f)}return n}vCe.exports=RCt});var bCe=L((Her,DCe)=>{function FCt(t,e){return t!=null&&e in Object(t)}DCe.exports=FCt});var gG=L((jer,PCe)=>{var NCt=Im(),OCt=bB(),LCt=xc(),MCt=kB(),_Ct=Tk(),UCt=zI();function HCt(t,e,r){e=NCt(e,t);for(var s=-1,a=e.length,n=!1;++s<a;){var c=UCt(e[s]);if(!(n=t!=null&&r(t,c)))break;t=t[c]}return n||++s!=a?n:(a=t==null?0:t.length,!!a&&_Ct(a)&&MCt(c,a)&&(LCt(t)||OCt(t)))}PCe.exports=HCt});var kCe=L((qer,xCe)=>{var jCt=bCe(),qCt=gG();function GCt(t,e){return t!=null&&qCt(t,e,jCt)}xCe.exports=GCt});var TCe=L((Ger,QCe)=>{var WCt=SCe(),YCt=kCe();function VCt(t,e){return WCt(t,e,function(r,s){return YCt(t,s)})}QCe.exports=VCt});var OCe=L((Wer,NCe)=>{var RCe=Gd(),KCt=bB(),JCt=xc(),FCe=RCe?RCe.isConcatSpreadable:void 0;function zCt(t){return JCt(t)||KCt(t)||!!(FCe&&t&&t[FCe])}NCe.exports=zCt});var _Ce=L((Yer,MCe)=>{var ZCt=kk(),XCt=OCe();function LCe(t,e,r,s,a){var n=-1,c=t.length;for(r||(r=XCt),a||(a=[]);++n<c;){var f=t[n];e>0&&r(f)?e>1?LCe(f,e-1,r,s,a):ZCt(a,f):s||(a[a.length]=f)}return a}MCe.exports=LCe});var HCe=L((Ver,UCe)=>{var $Ct=_Ce();function ewt(t){var e=t==null?0:t.length;return e?$Ct(t,1):[]}UCe.exports=ewt});var dG=L((Ker,jCe)=>{var twt=HCe(),rwt=J4(),nwt=z4();function iwt(t){return nwt(rwt(t,void 0,twt),t+\"\")}jCe.exports=iwt});var mG=L((Jer,qCe)=>{var swt=TCe(),owt=dG(),awt=owt(function(t,e){return t==null?{}:swt(t,e)});qCe.exports=awt});var jR,GCe=Ct(()=>{Fc();jR=class{constructor(e){this.resolver=e}supportsDescriptor(e,r){return this.resolver.supportsDescriptor(e,r)}supportsLocator(e,r){return this.resolver.supportsLocator(e,r)}shouldPersistResolution(e,r){return this.resolver.shouldPersistResolution(e,r)}bindDescriptor(e,r,s){return this.resolver.bindDescriptor(e,r,s)}getResolutionDependencies(e,r){return this.resolver.getResolutionDependencies(e,r)}async getCandidates(e,r,s){throw new Yt(20,`This package doesn't seem to be present in your lockfile; run \"yarn install\" to update the lockfile`)}async getSatisfying(e,r,s,a){throw new Yt(20,`This package doesn't seem to be present in your lockfile; run \"yarn install\" to update the lockfile`)}async resolve(e,r){throw new Yt(20,`This package doesn't seem to be present in your lockfile; run \"yarn install\" to update the lockfile`)}}});var Yi,yG=Ct(()=>{Fc();Yi=class extends ho{reportCacheHit(e){}reportCacheMiss(e){}startSectionSync(e,r){return r()}async startSectionPromise(e,r){return await r()}startTimerSync(e,r,s){return(typeof r==\"function\"?r:s)()}async startTimerPromise(e,r,s){return await(typeof r==\"function\"?r:s)()}reportSeparator(){}reportInfo(e,r){}reportWarning(e,r){}reportError(e,r){}reportProgress(e){return{...Promise.resolve().then(async()=>{for await(let{}of e);}),stop:()=>{}}}reportJson(e){}reportFold(e,r){}async finalize(){}}});var WCe,ZI,EG=Ct(()=>{bt();WCe=et(CQ());sI();$d();Qc();E0();Np();Yo();ZI=class{constructor(e,{project:r}){this.workspacesCwds=new Set;this.project=r,this.cwd=e}async setup(){this.manifest=await Ht.tryFind(this.cwd)??new Ht,this.relativeCwd=K.relative(this.project.cwd,this.cwd)||vt.dot;let e=this.manifest.name?this.manifest.name:ba(null,`${this.computeCandidateName()}-${fs(this.relativeCwd).substring(0,6)}`);this.anchoredDescriptor=On(e,`${Ei.protocol}${this.relativeCwd}`),this.anchoredLocator=Vs(e,`${Ei.protocol}${this.relativeCwd}`);let r=this.manifest.workspaceDefinitions.map(({pattern:a})=>a);if(r.length===0)return;let s=await(0,WCe.default)(r,{cwd:ue.fromPortablePath(this.cwd),onlyDirectories:!0,ignore:[\"**/node_modules\",\"**/.git\",\"**/.yarn\"]});s.sort(),await s.reduce(async(a,n)=>{let c=K.resolve(this.cwd,ue.toPortablePath(n)),f=await le.existsPromise(K.join(c,\"package.json\"));await a,f&&this.workspacesCwds.add(c)},Promise.resolve())}get anchoredPackage(){let e=this.project.storedPackages.get(this.anchoredLocator.locatorHash);if(!e)throw new Error(`Assertion failed: Expected workspace ${ZB(this.project.configuration,this)} (${Ut(this.project.configuration,K.join(this.cwd,Er.manifest),pt.PATH)}) to have been resolved. Run \"yarn install\" to update the lockfile`);return e}accepts(e){let r=e.indexOf(\":\"),s=r!==-1?e.slice(0,r+1):null,a=r!==-1?e.slice(r+1):e;if(s===Ei.protocol&&K.normalize(a)===this.relativeCwd||s===Ei.protocol&&(a===\"*\"||a===\"^\"||a===\"~\"))return!0;let n=ul(a);return n?s===Ei.protocol?n.test(this.manifest.version??\"0.0.0\"):this.project.configuration.get(\"enableTransparentWorkspaces\")&&this.manifest.version!==null?n.test(this.manifest.version):!1:!1}computeCandidateName(){return this.cwd===this.project.cwd?\"root-workspace\":`${K.basename(this.cwd)}`||\"unnamed-workspace\"}getRecursiveWorkspaceDependencies({dependencies:e=Ht.hardDependencies}={}){let r=new Set,s=a=>{for(let n of e)for(let c of a.manifest[n].values()){let f=this.project.tryWorkspaceByDescriptor(c);f===null||r.has(f)||(r.add(f),s(f))}};return s(this),r}getRecursiveWorkspaceDependents({dependencies:e=Ht.hardDependencies}={}){let r=new Set,s=a=>{for(let n of this.project.workspaces)e.some(f=>[...n.manifest[f].values()].some(p=>{let h=this.project.tryWorkspaceByDescriptor(p);return h!==null&&KB(h.anchoredLocator,a.anchoredLocator)}))&&!r.has(n)&&(r.add(n),s(n))};return s(this),r}getRecursiveWorkspaceChildren(){let e=new Set([this]);for(let r of e)for(let s of r.workspacesCwds){let a=this.project.workspacesByCwd.get(s);a&&e.add(a)}return e.delete(this),Array.from(e)}async persistManifest(){let e={};this.manifest.exportTo(e);let r=K.join(this.cwd,Ht.fileName),s=`${JSON.stringify(e,null,this.manifest.indent)}\n`;await le.changeFilePromise(r,s,{automaticNewlines:!0}),this.manifest.raw=e}}});function pwt({project:t,allDescriptors:e,allResolutions:r,allPackages:s,accessibleLocators:a=new Set,optionalBuilds:n=new Set,peerRequirements:c=new Map,peerWarnings:f=[],peerRequirementNodes:p=new Map,volatileDescriptors:h=new Set}){let E=new Map,C=[],S=new Map,P=new Map,I=new Map,R=new Map,N=new Map,U=new Map(t.workspaces.map(ce=>{let me=ce.anchoredLocator.locatorHash,pe=s.get(me);if(typeof pe>\"u\")throw new Error(\"Assertion failed: The workspace should have an associated package\");return[me,WB(pe)]})),W=()=>{let ce=le.mktempSync(),me=K.join(ce,\"stacktrace.log\"),pe=String(C.length+1).length,Be=C.map((Ce,g)=>`${`${g+1}.`.padStart(pe,\" \")} ${cl(Ce)}\n`).join(\"\");throw le.writeFileSync(me,Be),le.detachTemp(ce),new Yt(45,`Encountered a stack overflow when resolving peer dependencies; cf ${ue.fromPortablePath(me)}`)},te=ce=>{let me=r.get(ce.descriptorHash);if(typeof me>\"u\")throw new Error(\"Assertion failed: The resolution should have been registered\");let pe=s.get(me);if(!pe)throw new Error(\"Assertion failed: The package could not be found\");return pe},ie=(ce,me,pe,{top:Be,optional:Ce})=>{C.length>1e3&&W(),C.push(me);let g=Ae(ce,me,pe,{top:Be,optional:Ce});return C.pop(),g},Ae=(ce,me,pe,{top:Be,optional:Ce})=>{if(Ce||n.delete(me.locatorHash),a.has(me.locatorHash))return;a.add(me.locatorHash);let g=s.get(me.locatorHash);if(!g)throw new Error(`Assertion failed: The package (${Yr(t.configuration,me)}) should have been registered`);let we=new Set,ye=new Map,fe=[],se=[],X=[],De=[];for(let Re of Array.from(g.dependencies.values())){if(g.peerDependencies.has(Re.identHash)&&g.locatorHash!==Be)continue;if(Tp(Re))throw new Error(\"Assertion failed: Virtual packages shouldn't be encountered when virtualizing a branch\");h.delete(Re.descriptorHash);let dt=Ce;if(!dt){let ke=g.dependenciesMeta.get(cn(Re));if(typeof ke<\"u\"){let it=ke.get(null);typeof it<\"u\"&&it.optional&&(dt=!0)}}let j=r.get(Re.descriptorHash);if(!j)throw new Error(`Assertion failed: The resolution (${ni(t.configuration,Re)}) should have been registered`);let rt=U.get(j)||s.get(j);if(!rt)throw new Error(`Assertion failed: The package (${j}, resolved from ${ni(t.configuration,Re)}) should have been registered`);if(rt.peerDependencies.size===0){ie(Re,rt,new Map,{top:Be,optional:dt});continue}let Fe,Ne,Pe=new Set,Ye=new Map;fe.push(()=>{Fe=U8(Re,me.locatorHash),Ne=H8(rt,me.locatorHash),g.dependencies.set(Re.identHash,Fe),r.set(Fe.descriptorHash,Ne.locatorHash),e.set(Fe.descriptorHash,Fe),s.set(Ne.locatorHash,Ne),xp(R,Ne.locatorHash).add(Fe.descriptorHash),we.add(Ne.locatorHash)}),se.push(()=>{N.set(Ne.locatorHash,Ye);for(let ke of Ne.peerDependencies.values()){let _e=Vl(ye,ke.identHash,()=>{let x=pe.get(ke.identHash)??null,w=g.dependencies.get(ke.identHash);return!w&&VB(me,ke)&&(ce.identHash===me.identHash?w=ce:(w=On(me,ce.range),e.set(w.descriptorHash,w),r.set(w.descriptorHash,me.locatorHash),h.delete(w.descriptorHash),x=null)),w||(w=On(ke,\"missing:\")),{subject:me,ident:ke,provided:w,root:!x,requests:new Map,hash:`p${fs(me.locatorHash,ke.identHash).slice(0,5)}`}}).provided;if(_e.range===\"missing:\"&&Ne.dependencies.has(ke.identHash)){Ne.peerDependencies.delete(ke.identHash);continue}if(Ye.set(ke.identHash,{requester:Ne,descriptor:ke,meta:Ne.peerDependenciesMeta.get(cn(ke)),children:new Map}),Ne.dependencies.set(ke.identHash,_e),Tp(_e)){let x=r.get(_e.descriptorHash);xp(I,x).add(Ne.locatorHash)}S.set(_e.identHash,_e),_e.range===\"missing:\"&&Pe.add(_e.identHash)}Ne.dependencies=new Map(Ys(Ne.dependencies,([ke,it])=>cn(it)))}),X.push(()=>{if(!s.has(Ne.locatorHash))return;let ke=E.get(rt.locatorHash);typeof ke==\"number\"&&ke>=2&&W();let it=E.get(rt.locatorHash),_e=typeof it<\"u\"?it+1:1;E.set(rt.locatorHash,_e),ie(Fe,Ne,Ye,{top:Be,optional:dt}),E.set(rt.locatorHash,_e-1)}),De.push(()=>{let ke=r.get(Fe.descriptorHash);if(typeof ke>\"u\")throw new Error(\"Assertion failed: Expected the descriptor to be registered\");let it=N.get(ke);if(typeof it>\"u\")throw new Error(\"Assertion failed: Expected the peer requests to be registered\");for(let _e of ye.values()){let x=it.get(_e.ident.identHash);x&&(_e.requests.set(Fe.descriptorHash,x),p.set(_e.hash,_e),_e.root||pe.get(_e.ident.identHash)?.children.set(Fe.descriptorHash,x))}if(s.has(Ne.locatorHash))for(let _e of Pe)Ne.dependencies.delete(_e)})}for(let Re of[...fe,...se])Re();for(let Re of we){we.delete(Re);let dt=s.get(Re),j=fs(tI(dt).locatorHash,...Array.from(dt.dependencies.values(),Pe=>{let Ye=Pe.range!==\"missing:\"?r.get(Pe.descriptorHash):\"missing:\";if(typeof Ye>\"u\")throw new Error(`Assertion failed: Expected the resolution for ${ni(t.configuration,Pe)} to have been registered`);return Ye===Be?`${Ye} (top)`:Ye})),rt=P.get(j);if(typeof rt>\"u\"){P.set(j,dt);continue}let Fe=xp(R,rt.locatorHash);for(let Pe of R.get(dt.locatorHash)??[])r.set(Pe,rt.locatorHash),Fe.add(Pe);s.delete(dt.locatorHash),a.delete(dt.locatorHash),we.delete(dt.locatorHash);let Ne=I.get(dt.locatorHash);if(Ne!==void 0){let Pe=xp(I,rt.locatorHash);for(let Ye of Ne)Pe.add(Ye),we.add(Ye)}}for(let Re of[...X,...De])Re()};for(let ce of t.workspaces){let me=ce.anchoredLocator;h.delete(ce.anchoredDescriptor.descriptorHash),ie(ce.anchoredDescriptor,me,new Map,{top:me.locatorHash,optional:!1})}for(let ce of p.values()){if(!ce.root)continue;let me=s.get(ce.subject.locatorHash);if(typeof me>\"u\")continue;for(let Be of ce.requests.values()){let Ce=`p${fs(ce.subject.locatorHash,cn(ce.ident),Be.requester.locatorHash).slice(0,5)}`;c.set(Ce,{subject:ce.subject.locatorHash,requested:ce.ident,rootRequester:Be.requester.locatorHash,allRequesters:Array.from(XB(Be),g=>g.requester.locatorHash)})}let pe=[...XB(ce)];if(ce.provided.range!==\"missing:\"){let Be=te(ce.provided),Ce=Be.version??\"0.0.0\",g=ye=>{if(ye.startsWith(Ei.protocol)){if(!t.tryWorkspaceByLocator(Be))return null;ye=ye.slice(Ei.protocol.length),(ye===\"^\"||ye===\"~\")&&(ye=\"*\")}return ye},we=!0;for(let ye of pe){let fe=g(ye.descriptor.range);if(fe===null){we=!1;continue}if(!eA(Ce,fe)){we=!1;let se=`p${fs(ce.subject.locatorHash,cn(ce.ident),ye.requester.locatorHash).slice(0,5)}`;f.push({type:1,subject:me,requested:ce.ident,requester:ye.requester,version:Ce,hash:se,requirementCount:pe.length})}}if(!we){let ye=pe.map(fe=>g(fe.descriptor.range));f.push({type:3,node:ce,range:ye.includes(null)?null:G8(ye),hash:ce.hash})}}else{let Be=!0;for(let Ce of pe)if(!Ce.meta?.optional){Be=!1;let g=`p${fs(ce.subject.locatorHash,cn(ce.ident),Ce.requester.locatorHash).slice(0,5)}`;f.push({type:0,subject:me,requested:ce.ident,requester:Ce.requester,hash:g})}Be||f.push({type:2,node:ce,hash:ce.hash})}}}function*hwt(t){let e=new Map;if(\"children\"in t)e.set(t,t);else for(let r of t.requests.values())e.set(r,r);for(let[r,s]of e){yield{request:r,root:s};for(let a of r.children.values())e.has(a)||e.set(a,s)}}function gwt(t,e){let r=[],s=[],a=!1;for(let n of t.peerWarnings)if(!(n.type===1||n.type===0)){if(!t.tryWorkspaceByLocator(n.node.subject)){a=!0;continue}if(n.type===3){let c=t.storedResolutions.get(n.node.provided.descriptorHash);if(typeof c>\"u\")throw new Error(\"Assertion failed: Expected the descriptor to be registered\");let f=t.storedPackages.get(c);if(typeof f>\"u\")throw new Error(\"Assertion failed: Expected the package to be registered\");let p=A0(hwt(n.node),({request:C,root:S})=>eA(f.version??\"0.0.0\",C.descriptor.range)?A0.skip:C===S?es(t.configuration,C.requester):`${es(t.configuration,C.requester)} (via ${es(t.configuration,S.requester)})`),h=[...XB(n.node)].length>1?\"and other dependencies request\":\"requests\",E=n.range?nI(t.configuration,n.range):Ut(t.configuration,\"but they have non-overlapping ranges!\",\"redBright\");r.push(`${es(t.configuration,n.node.ident)} is listed by your project with version ${zB(t.configuration,f.version??\"0.0.0\")} (${Ut(t.configuration,n.hash,pt.CODE)}), which doesn't satisfy what ${p} ${h} (${E}).`)}if(n.type===2){let c=n.node.requests.size>1?\" and other dependencies\":\"\";s.push(`${Yr(t.configuration,n.node.subject)} doesn't provide ${es(t.configuration,n.node.ident)} (${Ut(t.configuration,n.hash,pt.CODE)}), requested by ${es(t.configuration,n.node.requests.values().next().value.requester)}${c}.`)}}e.startSectionSync({reportFooter:()=>{e.reportWarning(86,`Some peer dependencies are incorrectly met by your project; run ${Ut(t.configuration,\"yarn explain peer-requirements <hash>\",pt.CODE)} for details, where ${Ut(t.configuration,\"<hash>\",pt.CODE)} is the six-letter p-prefixed code.`)},skipIfEmpty:!0},()=>{for(let n of Ys(r,c=>VE.default(c)))e.reportWarning(60,n);for(let n of Ys(s,c=>VE.default(c)))e.reportWarning(2,n)}),a&&e.reportWarning(86,`Some peer dependencies are incorrectly met by dependencies; run ${Ut(t.configuration,\"yarn explain peer-requirements\",pt.CODE)} for details.`)}var qR,GR,WR,KCe,wG,CG,BG,YR,lwt,cwt,YCe,uwt,fwt,Awt,ec,IG,VR,VCe,Tt,JCe=Ct(()=>{bt();bt();Bc();Wt();qR=Ie(\"crypto\");fG();GR=et(mG()),WR=et(Od()),KCe=et(Ai()),wG=Ie(\"util\"),CG=et(Ie(\"v8\")),BG=et(Ie(\"zlib\"));rG();dv();nG();iG();sI();J8();Fc();GCe();xv();yG();$d();EG();NQ();Qc();E0();kc();pT();g6();Np();Yo();YR=WE(process.env.YARN_LOCKFILE_VERSION_OVERRIDE??8),lwt=3,cwt=/ *, */g,YCe=/\\/$/,uwt=32,fwt=(0,wG.promisify)(BG.default.gzip),Awt=(0,wG.promisify)(BG.default.gunzip),ec=(r=>(r.UpdateLockfile=\"update-lockfile\",r.SkipBuild=\"skip-build\",r))(ec||{}),IG={restoreLinkersCustomData:[\"linkersCustomData\"],restoreResolutions:[\"accessibleLocators\",\"conditionalLocators\",\"disabledLocators\",\"optionalBuilds\",\"storedDescriptors\",\"storedResolutions\",\"storedPackages\",\"lockFileChecksum\"],restoreBuildState:[\"skippedBuilds\",\"storedBuildState\"]},VR=(a=>(a[a.NotProvided=0]=\"NotProvided\",a[a.NotCompatible=1]=\"NotCompatible\",a[a.NodeNotProvided=2]=\"NodeNotProvided\",a[a.NodeNotCompatible=3]=\"NodeNotCompatible\",a))(VR||{}),VCe=t=>fs(`${lwt}`,t),Tt=class t{constructor(e,{configuration:r}){this.resolutionAliases=new Map;this.workspaces=[];this.workspacesByCwd=new Map;this.workspacesByIdent=new Map;this.storedResolutions=new Map;this.storedDescriptors=new Map;this.storedPackages=new Map;this.storedChecksums=new Map;this.storedBuildState=new Map;this.accessibleLocators=new Set;this.conditionalLocators=new Set;this.disabledLocators=new Set;this.originalPackages=new Map;this.optionalBuilds=new Set;this.skippedBuilds=new Set;this.lockfileLastVersion=null;this.lockfileNeedsRefresh=!1;this.peerRequirements=new Map;this.peerWarnings=[];this.peerRequirementNodes=new Map;this.linkersCustomData=new Map;this.lockFileChecksum=null;this.installStateChecksum=null;this.configuration=r,this.cwd=e}static async find(e,r){if(!e.projectCwd)throw new nt(`No project found in ${r}`);let s=e.projectCwd,a=r,n=null;for(;n!==e.projectCwd;){if(n=a,le.existsSync(K.join(n,Er.manifest))){s=n;break}a=K.dirname(n)}let c=new t(e.projectCwd,{configuration:e});ze.telemetry?.reportProject(c.cwd),await c.setupResolutions(),await c.setupWorkspaces(),ze.telemetry?.reportWorkspaceCount(c.workspaces.length),ze.telemetry?.reportDependencyCount(c.workspaces.reduce((I,R)=>I+R.manifest.dependencies.size+R.manifest.devDependencies.size,0));let f=c.tryWorkspaceByCwd(s);if(f)return{project:c,workspace:f,locator:f.anchoredLocator};let p=await c.findLocatorForLocation(`${s}/`,{strict:!0});if(p)return{project:c,locator:p,workspace:null};let h=Ut(e,c.cwd,pt.PATH),E=Ut(e,K.relative(c.cwd,s),pt.PATH),C=`- If ${h} isn't intended to be a project, remove any yarn.lock and/or package.json file there.`,S=`- If ${h} is intended to be a project, it might be that you forgot to list ${E} in its workspace configuration.`,P=`- Finally, if ${h} is fine and you intend ${E} to be treated as a completely separate project (not even a workspace), create an empty yarn.lock file in it.`;throw new nt(`The nearest package directory (${Ut(e,s,pt.PATH)}) doesn't seem to be part of the project declared in ${Ut(e,c.cwd,pt.PATH)}.\n\n${[C,S,P].join(`\n`)}`)}async setupResolutions(){this.storedResolutions=new Map,this.storedDescriptors=new Map,this.storedPackages=new Map,this.lockFileChecksum=null;let e=K.join(this.cwd,Er.lockfile),r=this.configuration.get(\"defaultLanguageName\");if(le.existsSync(e)){let s=await le.readFilePromise(e,\"utf8\");this.lockFileChecksum=VCe(s);let a=cs(s);if(a.__metadata){let n=a.__metadata.version,c=a.__metadata.cacheKey;this.lockfileLastVersion=n,this.lockfileNeedsRefresh=n<YR;for(let f of Object.keys(a)){if(f===\"__metadata\")continue;let p=a[f];if(typeof p.resolution>\"u\")throw new Error(`Assertion failed: Expected the lockfile entry to have a resolution field (${f})`);let h=Rp(p.resolution,!0),E=new Ht;E.load(p,{yamlCompatibilityMode:!0});let C=E.version,S=E.languageName||r,P=p.linkType.toUpperCase(),I=p.conditions??null,R=E.dependencies,N=E.peerDependencies,U=E.dependenciesMeta,W=E.peerDependenciesMeta,te=E.bin;if(p.checksum!=null){let Ae=typeof c<\"u\"&&!p.checksum.includes(\"/\")?`${c}/${p.checksum}`:p.checksum;this.storedChecksums.set(h.locatorHash,Ae)}let ie={...h,version:C,languageName:S,linkType:P,conditions:I,dependencies:R,peerDependencies:N,dependenciesMeta:U,peerDependenciesMeta:W,bin:te};this.originalPackages.set(ie.locatorHash,ie);for(let Ae of f.split(cwt)){let ce=I0(Ae);n<=6&&(ce=this.configuration.normalizeDependency(ce),ce=On(ce,ce.range.replace(/^patch:[^@]+@(?!npm(:|%3A))/,\"$1npm%3A\"))),this.storedDescriptors.set(ce.descriptorHash,ce),this.storedResolutions.set(ce.descriptorHash,h.locatorHash)}}}else s.includes(\"yarn lockfile v1\")&&(this.lockfileLastVersion=-1)}}async setupWorkspaces(){this.workspaces=[],this.workspacesByCwd=new Map,this.workspacesByIdent=new Map;let e=new Set,r=(0,WR.default)(4),s=async(a,n)=>{if(e.has(n))return a;e.add(n);let c=new ZI(n,{project:this});await r(()=>c.setup());let f=a.then(()=>{this.addWorkspace(c)});return Array.from(c.workspacesCwds).reduce(s,f)};await s(Promise.resolve(),this.cwd)}addWorkspace(e){let r=this.workspacesByIdent.get(e.anchoredLocator.identHash);if(typeof r<\"u\")throw new Error(`Duplicate workspace name ${es(this.configuration,e.anchoredLocator)}: ${ue.fromPortablePath(e.cwd)} conflicts with ${ue.fromPortablePath(r.cwd)}`);this.workspaces.push(e),this.workspacesByCwd.set(e.cwd,e),this.workspacesByIdent.set(e.anchoredLocator.identHash,e)}get topLevelWorkspace(){return this.getWorkspaceByCwd(this.cwd)}tryWorkspaceByCwd(e){K.isAbsolute(e)||(e=K.resolve(this.cwd,e)),e=K.normalize(e).replace(/\\/+$/,\"\");let r=this.workspacesByCwd.get(e);return r||null}getWorkspaceByCwd(e){let r=this.tryWorkspaceByCwd(e);if(!r)throw new Error(`Workspace not found (${e})`);return r}tryWorkspaceByFilePath(e){let r=null;for(let s of this.workspaces)K.relative(s.cwd,e).startsWith(\"../\")||r&&r.cwd.length>=s.cwd.length||(r=s);return r||null}getWorkspaceByFilePath(e){let r=this.tryWorkspaceByFilePath(e);if(!r)throw new Error(`Workspace not found (${e})`);return r}tryWorkspaceByIdent(e){let r=this.workspacesByIdent.get(e.identHash);return typeof r>\"u\"?null:r}getWorkspaceByIdent(e){let r=this.tryWorkspaceByIdent(e);if(!r)throw new Error(`Workspace not found (${es(this.configuration,e)})`);return r}tryWorkspaceByDescriptor(e){if(e.range.startsWith(Ei.protocol)){let s=e.range.slice(Ei.protocol.length);if(s!==\"^\"&&s!==\"~\"&&s!==\"*\"&&!ul(s))return this.tryWorkspaceByCwd(s)}let r=this.tryWorkspaceByIdent(e);return r===null||(Tp(e)&&(e=YB(e)),!r.accepts(e.range))?null:r}getWorkspaceByDescriptor(e){let r=this.tryWorkspaceByDescriptor(e);if(r===null)throw new Error(`Workspace not found (${ni(this.configuration,e)})`);return r}tryWorkspaceByLocator(e){let r=this.tryWorkspaceByIdent(e);return r===null||(Gu(e)&&(e=tI(e)),r.anchoredLocator.locatorHash!==e.locatorHash)?null:r}getWorkspaceByLocator(e){let r=this.tryWorkspaceByLocator(e);if(!r)throw new Error(`Workspace not found (${Yr(this.configuration,e)})`);return r}deleteDescriptor(e){this.storedResolutions.delete(e),this.storedDescriptors.delete(e)}deleteLocator(e){this.originalPackages.delete(e),this.storedPackages.delete(e),this.accessibleLocators.delete(e)}forgetResolution(e){if(\"descriptorHash\"in e){let r=this.storedResolutions.get(e.descriptorHash);this.deleteDescriptor(e.descriptorHash);let s=new Set(this.storedResolutions.values());typeof r<\"u\"&&!s.has(r)&&this.deleteLocator(r)}if(\"locatorHash\"in e){this.deleteLocator(e.locatorHash);for(let[r,s]of this.storedResolutions)s===e.locatorHash&&this.deleteDescriptor(r)}}forgetTransientResolutions(){let e=this.configuration.makeResolver(),r=new Map;for(let[s,a]of this.storedResolutions.entries()){let n=r.get(a);n||r.set(a,n=new Set),n.add(s)}for(let s of this.originalPackages.values()){let a;try{a=e.shouldPersistResolution(s,{project:this,resolver:e})}catch{a=!1}if(!a){this.deleteLocator(s.locatorHash);let n=r.get(s.locatorHash);if(n){r.delete(s.locatorHash);for(let c of n)this.deleteDescriptor(c)}}}}forgetVirtualResolutions(){for(let e of this.storedPackages.values())for(let[r,s]of e.dependencies)Tp(s)&&e.dependencies.set(r,YB(s))}getDependencyMeta(e,r){let s={},n=this.topLevelWorkspace.manifest.dependenciesMeta.get(cn(e));if(!n)return s;let c=n.get(null);if(c&&Object.assign(s,c),r===null||!KCe.default.valid(r))return s;for(let[f,p]of n)f!==null&&f===r&&Object.assign(s,p);return s}async findLocatorForLocation(e,{strict:r=!1}={}){let s=new Yi,a=this.configuration.getLinkers(),n={project:this,report:s};for(let c of a){let f=await c.findPackageLocator(e,n);if(f){if(r&&(await c.findPackageLocation(f,n)).replace(YCe,\"\")!==e.replace(YCe,\"\"))continue;return f}}return null}async loadUserConfig(){let e=K.join(this.cwd,\".pnp.cjs\");await le.existsPromise(e)&&kp(e).setup();let r=K.join(this.cwd,\"yarn.config.cjs\");return await le.existsPromise(r)?kp(r):null}async preparePackage(e,{resolver:r,resolveOptions:s}){let a=await this.configuration.getPackageExtensions(),n=this.configuration.normalizePackage(e,{packageExtensions:a});for(let[c,f]of n.dependencies){let p=await this.configuration.reduceHook(E=>E.reduceDependency,f,this,n,f,{resolver:r,resolveOptions:s});if(!VB(f,p))throw new Error(\"Assertion failed: The descriptor ident cannot be changed through aliases\");let h=r.bindDescriptor(p,n,s);n.dependencies.set(c,h)}return n}async resolveEverything(e){if(!this.workspacesByCwd||!this.workspacesByIdent)throw new Error(\"Workspaces must have been setup before calling this function\");this.forgetVirtualResolutions();let r=new Map(this.originalPackages),s=[];e.lockfileOnly||this.forgetTransientResolutions();let a=e.resolver||this.configuration.makeResolver(),n=new KI(a);await n.setup(this,{report:e.report});let c=e.lockfileOnly?[new jR(a)]:[n,a],f=new em([new JI(a),...c]),p=new em([...c]),h=this.configuration.makeFetcher(),E=e.lockfileOnly?{project:this,report:e.report,resolver:f}:{project:this,report:e.report,resolver:f,fetchOptions:{project:this,cache:e.cache,checksums:this.storedChecksums,report:e.report,fetcher:h,cacheOptions:{mirrorWriteOnly:!0}}},C=new Map,S=new Map,P=new Map,I=new Map,R=new Map,N=new Map,U=this.topLevelWorkspace.anchoredLocator,W=new Set,te=[],ie=Sj(),Ae=this.configuration.getSupportedArchitectures();await e.report.startProgressPromise(ho.progressViaTitle(),async se=>{let X=async rt=>{let Fe=await qE(async()=>await f.resolve(rt,E),ke=>`${Yr(this.configuration,rt)}: ${ke}`);if(!KB(rt,Fe))throw new Error(`Assertion failed: The locator cannot be changed by the resolver (went from ${Yr(this.configuration,rt)} to ${Yr(this.configuration,Fe)})`);I.set(Fe.locatorHash,Fe),!r.delete(Fe.locatorHash)&&!this.tryWorkspaceByLocator(Fe)&&s.push(Fe);let Pe=await this.preparePackage(Fe,{resolver:f,resolveOptions:E}),Ye=Uu([...Pe.dependencies.values()].map(ke=>j(ke)));return te.push(Ye),Ye.catch(()=>{}),S.set(Pe.locatorHash,Pe),Pe},De=async rt=>{let Fe=R.get(rt.locatorHash);if(typeof Fe<\"u\")return Fe;let Ne=Promise.resolve().then(()=>X(rt));return R.set(rt.locatorHash,Ne),Ne},Re=async(rt,Fe)=>{let Ne=await j(Fe);return C.set(rt.descriptorHash,rt),P.set(rt.descriptorHash,Ne.locatorHash),Ne},dt=async rt=>{se.setTitle(ni(this.configuration,rt));let Fe=this.resolutionAliases.get(rt.descriptorHash);if(typeof Fe<\"u\")return Re(rt,this.storedDescriptors.get(Fe));let Ne=f.getResolutionDependencies(rt,E),Pe=Object.fromEntries(await Uu(Object.entries(Ne).map(async([it,_e])=>{let x=f.bindDescriptor(_e,U,E),w=await j(x);return W.add(w.locatorHash),[it,w]}))),ke=(await qE(async()=>await f.getCandidates(rt,Pe,E),it=>`${ni(this.configuration,rt)}: ${it}`))[0];if(typeof ke>\"u\")throw new Yt(82,`${ni(this.configuration,rt)}: No candidates found`);if(e.checkResolutions){let{locators:it}=await p.getSatisfying(rt,Pe,[ke],{...E,resolver:p});if(!it.find(_e=>_e.locatorHash===ke.locatorHash))throw new Yt(78,`Invalid resolution ${jB(this.configuration,rt,ke)}`)}return C.set(rt.descriptorHash,rt),P.set(rt.descriptorHash,ke.locatorHash),De(ke)},j=rt=>{let Fe=N.get(rt.descriptorHash);if(typeof Fe<\"u\")return Fe;C.set(rt.descriptorHash,rt);let Ne=Promise.resolve().then(()=>dt(rt));return N.set(rt.descriptorHash,Ne),Ne};for(let rt of this.workspaces){let Fe=rt.anchoredDescriptor;te.push(j(Fe))}for(;te.length>0;){let rt=[...te];te.length=0,await Uu(rt)}});let ce=Yl(r.values(),se=>this.tryWorkspaceByLocator(se)?Yl.skip:se);if(s.length>0||ce.length>0){let se=new Set(this.workspaces.flatMap(rt=>{let Fe=S.get(rt.anchoredLocator.locatorHash);if(!Fe)throw new Error(\"Assertion failed: The workspace should have been resolved\");return Array.from(Fe.dependencies.values(),Ne=>{let Pe=P.get(Ne.descriptorHash);if(!Pe)throw new Error(\"Assertion failed: The resolution should have been registered\");return Pe})})),X=rt=>se.has(rt.locatorHash)?\"0\":\"1\",De=rt=>cl(rt),Re=Ys(s,[X,De]),dt=Ys(ce,[X,De]),j=e.report.getRecommendedLength();Re.length>0&&e.report.reportInfo(85,`${Ut(this.configuration,\"+\",pt.ADDED)} ${Zk(this.configuration,Re,j)}`),dt.length>0&&e.report.reportInfo(85,`${Ut(this.configuration,\"-\",pt.REMOVED)} ${Zk(this.configuration,dt,j)}`)}let me=new Set(this.resolutionAliases.values()),pe=new Set(S.keys()),Be=new Set,Ce=new Map,g=[],we=new Map;pwt({project:this,accessibleLocators:Be,volatileDescriptors:me,optionalBuilds:pe,peerRequirements:Ce,peerWarnings:g,peerRequirementNodes:we,allDescriptors:C,allResolutions:P,allPackages:S});for(let se of W)pe.delete(se);for(let se of me)C.delete(se),P.delete(se);let ye=new Set,fe=new Set;for(let se of S.values())se.conditions!=null&&pe.has(se.locatorHash)&&(kQ(se,Ae)||(kQ(se,ie)&&e.report.reportWarningOnce(77,`${Yr(this.configuration,se)}: Your current architecture (${process.platform}-${process.arch}) is supported by this package, but is missing from the ${Ut(this.configuration,\"supportedArchitectures\",pt.SETTING)} setting`),fe.add(se.locatorHash)),ye.add(se.locatorHash));this.storedResolutions=P,this.storedDescriptors=C,this.storedPackages=S,this.accessibleLocators=Be,this.conditionalLocators=ye,this.disabledLocators=fe,this.originalPackages=I,this.optionalBuilds=pe,this.peerRequirements=Ce,this.peerWarnings=g,this.peerRequirementNodes=we}async fetchEverything({cache:e,report:r,fetcher:s,mode:a,persistProject:n=!0}){let c={mockedPackages:this.disabledLocators,unstablePackages:this.conditionalLocators},f=s||this.configuration.makeFetcher(),p={checksums:this.storedChecksums,project:this,cache:e,fetcher:f,report:r,cacheOptions:c},h=Array.from(new Set(Ys(this.storedResolutions.values(),[I=>{let R=this.storedPackages.get(I);if(!R)throw new Error(\"Assertion failed: The locator should have been registered\");return cl(R)}])));a===\"update-lockfile\"&&(h=h.filter(I=>!this.storedChecksums.has(I)));let E=!1,C=ho.progressViaCounter(h.length);await r.reportProgress(C);let S=(0,WR.default)(uwt);if(await Uu(h.map(I=>S(async()=>{let R=this.storedPackages.get(I);if(!R)throw new Error(\"Assertion failed: The locator should have been registered\");if(Gu(R))return;let N;try{N=await f.fetch(R,p)}catch(U){U.message=`${Yr(this.configuration,R)}: ${U.message}`,r.reportExceptionOnce(U),E=U;return}N.checksum!=null?this.storedChecksums.set(R.locatorHash,N.checksum):this.storedChecksums.delete(R.locatorHash),N.releaseFs&&N.releaseFs()}).finally(()=>{C.tick()}))),E)throw E;let P=n&&a!==\"update-lockfile\"?await this.cacheCleanup({cache:e,report:r}):null;if(r.cacheMisses.size>0||P){let R=(await Promise.all([...r.cacheMisses].map(async ce=>{let me=this.storedPackages.get(ce),pe=this.storedChecksums.get(ce)??null,Be=e.getLocatorPath(me,pe);return(await le.statPromise(Be)).size}))).reduce((ce,me)=>ce+me,0)-(P?.size??0),N=r.cacheMisses.size,U=P?.count??0,W=`${Gk(N,{zero:\"No new packages\",one:\"A package was\",more:`${Ut(this.configuration,N,pt.NUMBER)} packages were`})} added to the project`,te=`${Gk(U,{zero:\"none were\",one:\"one was\",more:`${Ut(this.configuration,U,pt.NUMBER)} were`})} removed`,ie=R!==0?` (${Ut(this.configuration,R,pt.SIZE_DIFF)})`:\"\",Ae=U>0?N>0?`${W}, and ${te}${ie}.`:`${W}, but ${te}${ie}.`:`${W}${ie}.`;r.reportInfo(13,Ae)}}async linkEverything({cache:e,report:r,fetcher:s,mode:a}){let n={mockedPackages:this.disabledLocators,unstablePackages:this.conditionalLocators,skipIntegrityCheck:!0},c=s||this.configuration.makeFetcher(),f={checksums:this.storedChecksums,project:this,cache:e,fetcher:c,report:r,cacheOptions:n},p=this.configuration.getLinkers(),h={project:this,report:r},E=new Map(p.map(ye=>{let fe=ye.makeInstaller(h),se=ye.getCustomDataKey(),X=this.linkersCustomData.get(se);return typeof X<\"u\"&&fe.attachCustomData(X),[ye,fe]})),C=new Map,S=new Map,P=new Map,I=new Map(await Uu([...this.accessibleLocators].map(async ye=>{let fe=this.storedPackages.get(ye);if(!fe)throw new Error(\"Assertion failed: The locator should have been registered\");return[ye,await c.fetch(fe,f)]}))),R=[],N=new Set,U=[];for(let ye of this.accessibleLocators){let fe=this.storedPackages.get(ye);if(typeof fe>\"u\")throw new Error(\"Assertion failed: The locator should have been registered\");let se=I.get(fe.locatorHash);if(typeof se>\"u\")throw new Error(\"Assertion failed: The fetch result should have been registered\");let X=[],De=dt=>{X.push(dt)},Re=this.tryWorkspaceByLocator(fe);if(Re!==null){let dt=[],{scripts:j}=Re.manifest;for(let Fe of[\"preinstall\",\"install\",\"postinstall\"])j.has(Fe)&&dt.push({type:0,script:Fe});try{for(let[Fe,Ne]of E)if(Fe.supportsPackage(fe,h)&&(await Ne.installPackage(fe,se,{holdFetchResult:De})).buildRequest!==null)throw new Error(\"Assertion failed: Linkers can't return build directives for workspaces; this responsibility befalls to the Yarn core\")}finally{X.length===0?se.releaseFs?.():R.push(Uu(X).catch(()=>{}).then(()=>{se.releaseFs?.()}))}let rt=K.join(se.packageFs.getRealPath(),se.prefixPath);S.set(fe.locatorHash,rt),!Gu(fe)&&dt.length>0&&P.set(fe.locatorHash,{buildDirectives:dt,buildLocations:[rt]})}else{let dt=p.find(Fe=>Fe.supportsPackage(fe,h));if(!dt)throw new Yt(12,`${Yr(this.configuration,fe)} isn't supported by any available linker`);let j=E.get(dt);if(!j)throw new Error(\"Assertion failed: The installer should have been registered\");let rt;try{rt=await j.installPackage(fe,se,{holdFetchResult:De})}finally{X.length===0?se.releaseFs?.():R.push(Uu(X).then(()=>{}).then(()=>{se.releaseFs?.()}))}C.set(fe.locatorHash,dt),S.set(fe.locatorHash,rt.packageLocation),rt.buildRequest&&rt.packageLocation&&(rt.buildRequest.skipped?(N.add(fe.locatorHash),this.skippedBuilds.has(fe.locatorHash)||U.push([fe,rt.buildRequest.explain])):P.set(fe.locatorHash,{buildDirectives:rt.buildRequest.directives,buildLocations:[rt.packageLocation]}))}}let W=new Map;for(let ye of this.accessibleLocators){let fe=this.storedPackages.get(ye);if(!fe)throw new Error(\"Assertion failed: The locator should have been registered\");let se=this.tryWorkspaceByLocator(fe)!==null,X=async(De,Re)=>{let dt=S.get(fe.locatorHash);if(typeof dt>\"u\")throw new Error(`Assertion failed: The package (${Yr(this.configuration,fe)}) should have been registered`);let j=[];for(let rt of fe.dependencies.values()){let Fe=this.storedResolutions.get(rt.descriptorHash);if(typeof Fe>\"u\")throw new Error(`Assertion failed: The resolution (${ni(this.configuration,rt)}, from ${Yr(this.configuration,fe)})should have been registered`);let Ne=this.storedPackages.get(Fe);if(typeof Ne>\"u\")throw new Error(`Assertion failed: The package (${Fe}, resolved from ${ni(this.configuration,rt)}) should have been registered`);let Pe=this.tryWorkspaceByLocator(Ne)===null?C.get(Fe):null;if(typeof Pe>\"u\")throw new Error(`Assertion failed: The package (${Fe}, resolved from ${ni(this.configuration,rt)}) should have been registered`);Pe===De||Pe===null?S.get(Ne.locatorHash)!==null&&j.push([rt,Ne]):!se&&dt!==null&&LB(W,Fe).push(dt)}dt!==null&&await Re.attachInternalDependencies(fe,j)};if(se)for(let[De,Re]of E)De.supportsPackage(fe,h)&&await X(De,Re);else{let De=C.get(fe.locatorHash);if(!De)throw new Error(\"Assertion failed: The linker should have been found\");let Re=E.get(De);if(!Re)throw new Error(\"Assertion failed: The installer should have been registered\");await X(De,Re)}}for(let[ye,fe]of W){let se=this.storedPackages.get(ye);if(!se)throw new Error(\"Assertion failed: The package should have been registered\");let X=C.get(se.locatorHash);if(!X)throw new Error(\"Assertion failed: The linker should have been found\");let De=E.get(X);if(!De)throw new Error(\"Assertion failed: The installer should have been registered\");await De.attachExternalDependents(se,fe)}let te=new Map;for(let[ye,fe]of E){let se=await fe.finalizeInstall();for(let X of se?.records??[])X.buildRequest.skipped?(N.add(X.locator.locatorHash),this.skippedBuilds.has(X.locator.locatorHash)||U.push([X.locator,X.buildRequest.explain])):P.set(X.locator.locatorHash,{buildDirectives:X.buildRequest.directives,buildLocations:X.buildLocations});typeof se?.customData<\"u\"&&te.set(ye.getCustomDataKey(),se.customData)}if(this.linkersCustomData=te,await Uu(R),a===\"skip-build\")return;for(let[,ye]of Ys(U,([fe])=>cl(fe)))ye(r);let ie=new Set(P.keys()),Ae=(0,qR.createHash)(\"sha512\");Ae.update(process.versions.node),await this.configuration.triggerHook(ye=>ye.globalHashGeneration,this,ye=>{Ae.update(\"\\0\"),Ae.update(ye)});let ce=Ae.digest(\"hex\"),me=new Map,pe=ye=>{let fe=me.get(ye.locatorHash);if(typeof fe<\"u\")return fe;let se=this.storedPackages.get(ye.locatorHash);if(typeof se>\"u\")throw new Error(\"Assertion failed: The package should have been registered\");let X=(0,qR.createHash)(\"sha512\");X.update(ye.locatorHash),me.set(ye.locatorHash,\"<recursive>\");for(let De of se.dependencies.values()){let Re=this.storedResolutions.get(De.descriptorHash);if(typeof Re>\"u\")throw new Error(`Assertion failed: The resolution (${ni(this.configuration,De)}) should have been registered`);let dt=this.storedPackages.get(Re);if(typeof dt>\"u\")throw new Error(\"Assertion failed: The package should have been registered\");X.update(pe(dt))}return fe=X.digest(\"hex\"),me.set(ye.locatorHash,fe),fe},Be=(ye,fe)=>{let se=(0,qR.createHash)(\"sha512\");se.update(ce),se.update(pe(ye));for(let X of fe)se.update(X);return se.digest(\"hex\")},Ce=new Map,g=!1,we=ye=>{let fe=new Set([ye.locatorHash]);for(let se of fe){let X=this.storedPackages.get(se);if(!X)throw new Error(\"Assertion failed: The package should have been registered\");for(let De of X.dependencies.values()){let Re=this.storedResolutions.get(De.descriptorHash);if(!Re)throw new Error(`Assertion failed: The resolution (${ni(this.configuration,De)}) should have been registered`);if(Re!==ye.locatorHash&&ie.has(Re))return!1;let dt=this.storedPackages.get(Re);if(!dt)throw new Error(\"Assertion failed: The package should have been registered\");let j=this.tryWorkspaceByLocator(dt);if(j){if(j.anchoredLocator.locatorHash!==ye.locatorHash&&ie.has(j.anchoredLocator.locatorHash))return!1;fe.add(j.anchoredLocator.locatorHash)}fe.add(Re)}}return!0};for(;ie.size>0;){let ye=ie.size,fe=[];for(let se of ie){let X=this.storedPackages.get(se);if(!X)throw new Error(\"Assertion failed: The package should have been registered\");if(!we(X))continue;let De=P.get(X.locatorHash);if(!De)throw new Error(\"Assertion failed: The build directive should have been registered\");let Re=Be(X,De.buildLocations);if(this.storedBuildState.get(X.locatorHash)===Re){Ce.set(X.locatorHash,Re),ie.delete(se);continue}g||(await this.persistInstallStateFile(),g=!0),this.storedBuildState.has(X.locatorHash)?r.reportInfo(8,`${Yr(this.configuration,X)} must be rebuilt because its dependency tree changed`):r.reportInfo(7,`${Yr(this.configuration,X)} must be built because it never has been before or the last one failed`);let dt=De.buildLocations.map(async j=>{if(!K.isAbsolute(j))throw new Error(`Assertion failed: Expected the build location to be absolute (not ${j})`);for(let rt of De.buildDirectives){let Fe=`# This file contains the result of Yarn building a package (${cl(X)})\n`;switch(rt.type){case 0:Fe+=`# Script name: ${rt.script}\n`;break;case 1:Fe+=`# Script code: ${rt.script}\n`;break}let Ne=null;if(!await le.mktempPromise(async Ye=>{let ke=K.join(Ye,\"build.log\"),{stdout:it,stderr:_e}=this.configuration.getSubprocessStreams(ke,{header:Fe,prefix:Yr(this.configuration,X),report:r}),x;try{switch(rt.type){case 0:x=await OT(X,rt.script,[],{cwd:j,project:this,stdin:Ne,stdout:it,stderr:_e});break;case 1:x=await f6(X,rt.script,[],{cwd:j,project:this,stdin:Ne,stdout:it,stderr:_e});break}}catch(y){_e.write(y.stack),x=1}if(it.end(),_e.end(),x===0)return!0;le.detachTemp(Ye);let w=`${Yr(this.configuration,X)} couldn't be built successfully (exit code ${Ut(this.configuration,x,pt.NUMBER)}, logs can be found here: ${Ut(this.configuration,ke,pt.PATH)})`,b=this.optionalBuilds.has(X.locatorHash);return b?r.reportInfo(9,w):r.reportError(9,w),Wme&&r.reportFold(ue.fromPortablePath(ke),le.readFileSync(ke,\"utf8\")),b}))return!1}return!0});fe.push(...dt,Promise.allSettled(dt).then(j=>{ie.delete(se),j.every(rt=>rt.status===\"fulfilled\"&&rt.value===!0)&&Ce.set(X.locatorHash,Re)}))}if(await Uu(fe),ye===ie.size){let se=Array.from(ie).map(X=>{let De=this.storedPackages.get(X);if(!De)throw new Error(\"Assertion failed: The package should have been registered\");return Yr(this.configuration,De)}).join(\", \");r.reportError(3,`Some packages have circular dependencies that make their build order unsatisfiable - as a result they won't be built (affected packages are: ${se})`);break}}this.storedBuildState=Ce,this.skippedBuilds=N}async installWithNewReport(e,r){return(await Ot.start({configuration:this.configuration,json:e.json,stdout:e.stdout,forceSectionAlignment:!0,includeLogs:!e.json&&!e.quiet,includeVersion:!0},async a=>{await this.install({...r,report:a})})).exitCode()}async install(e){let r=this.configuration.get(\"nodeLinker\");ze.telemetry?.reportInstall(r);let s=!1;if(await e.report.startTimerPromise(\"Project validation\",{skipIfEmpty:!0},async()=>{this.configuration.get(\"enableOfflineMode\")&&e.report.reportWarning(90,\"Offline work is enabled; Yarn won't fetch packages from the remote registry if it can avoid it\"),await this.configuration.triggerHook(E=>E.validateProject,this,{reportWarning:(E,C)=>{e.report.reportWarning(E,C)},reportError:(E,C)=>{e.report.reportError(E,C),s=!0}})}),s)return;let a=await this.configuration.getPackageExtensions();for(let E of a.values())for(let[,C]of E)for(let S of C)S.status=\"inactive\";let n=K.join(this.cwd,Er.lockfile),c=null;if(e.immutable)try{c=await le.readFilePromise(n,\"utf8\")}catch(E){throw E.code===\"ENOENT\"?new Yt(28,\"The lockfile would have been created by this install, which is explicitly forbidden.\"):E}await e.report.startTimerPromise(\"Resolution step\",async()=>{await this.resolveEverything(e)}),await e.report.startTimerPromise(\"Post-resolution validation\",{skipIfEmpty:!0},async()=>{gwt(this,e.report);for(let[,E]of a)for(let[,C]of E)for(let S of C)if(S.userProvided){let P=Ut(this.configuration,S,pt.PACKAGE_EXTENSION);switch(S.status){case\"inactive\":e.report.reportWarning(68,`${P}: No matching package in the dependency tree; you may not need this rule anymore.`);break;case\"redundant\":e.report.reportWarning(69,`${P}: This rule seems redundant when applied on the original package; the extension may have been applied upstream.`);break}}if(c!==null){let E=yd(c,this.generateLockfile());if(E!==c){let C=uCe(n,n,c,E,void 0,void 0,{maxEditLength:100});if(C){e.report.reportSeparator();for(let S of C.hunks){e.report.reportInfo(null,`@@ -${S.oldStart},${S.oldLines} +${S.newStart},${S.newLines} @@`);for(let P of S.lines)P.startsWith(\"+\")?e.report.reportError(28,Ut(this.configuration,P,pt.ADDED)):P.startsWith(\"-\")?e.report.reportError(28,Ut(this.configuration,P,pt.REMOVED)):e.report.reportInfo(null,Ut(this.configuration,P,\"grey\"))}e.report.reportSeparator()}throw new Yt(28,\"The lockfile would have been modified by this install, which is explicitly forbidden.\")}}});for(let E of a.values())for(let[,C]of E)for(let S of C)S.userProvided&&S.status===\"active\"&&ze.telemetry?.reportPackageExtension(Jd(S,pt.PACKAGE_EXTENSION));await e.report.startTimerPromise(\"Fetch step\",async()=>{await this.fetchEverything(e)});let f=e.immutable?[...new Set(this.configuration.get(\"immutablePatterns\"))].sort():[],p=await Promise.all(f.map(async E=>vQ(E,{cwd:this.cwd})));(typeof e.persistProject>\"u\"||e.persistProject)&&await this.persist(),await e.report.startTimerPromise(\"Link step\",async()=>{if(e.mode===\"update-lockfile\"){e.report.reportWarning(73,`Skipped due to ${Ut(this.configuration,\"mode=update-lockfile\",pt.CODE)}`);return}await this.linkEverything(e);let E=await Promise.all(f.map(async C=>vQ(C,{cwd:this.cwd})));for(let C=0;C<f.length;++C)p[C]!==E[C]&&e.report.reportError(64,`The checksum for ${f[C]} has been modified by this install, which is explicitly forbidden.`)}),await this.persistInstallStateFile();let h=!1;await e.report.startTimerPromise(\"Post-install validation\",{skipIfEmpty:!0},async()=>{await this.configuration.triggerHook(E=>E.validateProjectAfterInstall,this,{reportWarning:(E,C)=>{e.report.reportWarning(E,C)},reportError:(E,C)=>{e.report.reportError(E,C),h=!0}})}),!h&&await this.configuration.triggerHook(E=>E.afterAllInstalled,this,e)}generateLockfile(){let e=new Map;for(let[n,c]of this.storedResolutions.entries()){let f=e.get(c);f||e.set(c,f=new Set),f.add(n)}let r={},{cacheKey:s}=Jr.getCacheKey(this.configuration);r.__metadata={version:YR,cacheKey:s};for(let[n,c]of e.entries()){let f=this.originalPackages.get(n);if(!f)continue;let p=[];for(let C of c){let S=this.storedDescriptors.get(C);if(!S)throw new Error(\"Assertion failed: The descriptor should have been registered\");p.push(S)}let h=p.map(C=>ll(C)).sort().join(\", \"),E=new Ht;E.version=f.linkType===\"HARD\"?f.version:\"0.0.0-use.local\",E.languageName=f.languageName,E.dependencies=new Map(f.dependencies),E.peerDependencies=new Map(f.peerDependencies),E.dependenciesMeta=new Map(f.dependenciesMeta),E.peerDependenciesMeta=new Map(f.peerDependenciesMeta),E.bin=new Map(f.bin),r[h]={...E.exportTo({},{compatibilityMode:!1}),linkType:f.linkType.toLowerCase(),resolution:cl(f),checksum:this.storedChecksums.get(f.locatorHash),conditions:f.conditions||void 0}}return`${[`# This file is generated by running \"yarn install\" inside your project.\n`,`# Manual changes might be lost - proceed with caution!\n`].join(\"\")}\n`+il(r)}async persistLockfile(){let e=K.join(this.cwd,Er.lockfile),r=\"\";try{r=await le.readFilePromise(e,\"utf8\")}catch{}let s=this.generateLockfile(),a=yd(r,s);a!==r&&(await le.writeFilePromise(e,a),this.lockFileChecksum=VCe(a),this.lockfileNeedsRefresh=!1)}async persistInstallStateFile(){let e=[];for(let c of Object.values(IG))e.push(...c);let r=(0,GR.default)(this,e),s=CG.default.serialize(r),a=fs(s);if(this.installStateChecksum===a)return;let n=this.configuration.get(\"installStatePath\");await le.mkdirPromise(K.dirname(n),{recursive:!0}),await le.writeFilePromise(n,await fwt(s)),this.installStateChecksum=a}async restoreInstallState({restoreLinkersCustomData:e=!0,restoreResolutions:r=!0,restoreBuildState:s=!0}={}){let a=this.configuration.get(\"installStatePath\"),n;try{let c=await Awt(await le.readFilePromise(a));n=CG.default.deserialize(c),this.installStateChecksum=fs(c)}catch{r&&await this.applyLightResolution();return}e&&typeof n.linkersCustomData<\"u\"&&(this.linkersCustomData=n.linkersCustomData),s&&Object.assign(this,(0,GR.default)(n,IG.restoreBuildState)),r&&(n.lockFileChecksum===this.lockFileChecksum?Object.assign(this,(0,GR.default)(n,IG.restoreResolutions)):await this.applyLightResolution())}async applyLightResolution(){await this.resolveEverything({lockfileOnly:!0,report:new Yi}),await this.persistInstallStateFile()}async persist(){let e=(0,WR.default)(4);await Promise.all([this.persistLockfile(),...this.workspaces.map(r=>e(()=>r.persistManifest()))])}async cacheCleanup({cache:e,report:r}){if(this.configuration.get(\"enableGlobalCache\"))return null;let s=new Set([\".gitignore\"]);if(!sH(e.cwd,this.cwd)||!await le.existsPromise(e.cwd))return null;let a=[];for(let c of await le.readdirPromise(e.cwd)){if(s.has(c))continue;let f=K.resolve(e.cwd,c);e.markedFiles.has(f)||(e.immutable?r.reportError(56,`${Ut(this.configuration,K.basename(f),\"magenta\")} appears to be unused and would be marked for deletion, but the cache is immutable`):a.push(le.lstatPromise(f).then(async p=>(await le.removePromise(f),p.size))))}if(a.length===0)return null;let n=await Promise.all(a);return{count:a.length,size:n.reduce((c,f)=>c+f,0)}}}});function dwt(t){let s=Math.floor(t.timeNow/864e5),a=t.updateInterval*864e5,n=t.state.lastUpdate??t.timeNow+a+Math.floor(a*t.randomInitialInterval),c=n+a,f=t.state.lastTips??s*864e5,p=f+864e5+8*36e5-t.timeZone,h=c<=t.timeNow,E=p<=t.timeNow,C=null;return(h||E||!t.state.lastUpdate||!t.state.lastTips)&&(C={},C.lastUpdate=h?t.timeNow:n,C.lastTips=f,C.blocks=h?{}:t.state.blocks,C.displayedTips=t.state.displayedTips),{nextState:C,triggerUpdate:h,triggerTips:E,nextTips:E?s*864e5:f}}var XI,zCe=Ct(()=>{bt();Pv();E0();fT();kc();Np();XI=class{constructor(e,r){this.values=new Map;this.hits=new Map;this.enumerators=new Map;this.nextTips=0;this.displayedTips=[];this.shouldCommitTips=!1;this.configuration=e;let s=this.getRegistryPath();this.isNew=!le.existsSync(s),this.shouldShowTips=!1,this.sendReport(r),this.startBuffer()}commitTips(){this.shouldShowTips&&(this.shouldCommitTips=!0)}selectTip(e){let r=new Set(this.displayedTips),s=f=>f&&un?eA(un,f):!1,a=e.map((f,p)=>p).filter(f=>e[f]&&s(e[f]?.selector));if(a.length===0)return null;let n=a.filter(f=>!r.has(f));if(n.length===0){let f=Math.floor(a.length*.2);this.displayedTips=f>0?this.displayedTips.slice(-f):[],n=a.filter(p=>!r.has(p))}let c=n[Math.floor(Math.random()*n.length)];return this.displayedTips.push(c),this.commitTips(),e[c]}reportVersion(e){this.reportValue(\"version\",e.replace(/-git\\..*/,\"-git\"))}reportCommandName(e){this.reportValue(\"commandName\",e||\"<none>\")}reportPluginName(e){this.reportValue(\"pluginName\",e)}reportProject(e){this.reportEnumerator(\"projectCount\",e)}reportInstall(e){this.reportHit(\"installCount\",e)}reportPackageExtension(e){this.reportValue(\"packageExtension\",e)}reportWorkspaceCount(e){this.reportValue(\"workspaceCount\",String(e))}reportDependencyCount(e){this.reportValue(\"dependencyCount\",String(e))}reportValue(e,r){xp(this.values,e).add(r)}reportEnumerator(e,r){xp(this.enumerators,e).add(fs(r))}reportHit(e,r=\"*\"){let s=n3(this.hits,e),a=Vl(s,r,()=>0);s.set(r,a+1)}getRegistryPath(){let e=this.configuration.get(\"globalFolder\");return K.join(e,\"telemetry.json\")}sendReport(e){let r=this.getRegistryPath(),s;try{s=le.readJsonSync(r)}catch{s={}}let{nextState:a,triggerUpdate:n,triggerTips:c,nextTips:f}=dwt({state:s,timeNow:Date.now(),timeZone:new Date().getTimezoneOffset()*60*1e3,randomInitialInterval:Math.random(),updateInterval:this.configuration.get(\"telemetryInterval\")});if(this.nextTips=f,this.displayedTips=s.displayedTips??[],a!==null)try{le.mkdirSync(K.dirname(r),{recursive:!0}),le.writeJsonSync(r,a)}catch{return!1}if(c&&this.configuration.get(\"enableTips\")&&(this.shouldShowTips=!0),n){let p=s.blocks??{};if(Object.keys(p).length===0){let h=`https://browser-http-intake.logs.datadoghq.eu/v1/input/${e}?ddsource=yarn`,E=C=>vj(h,C,{configuration:this.configuration}).catch(()=>{});for(let[C,S]of Object.entries(s.blocks??{})){if(Object.keys(S).length===0)continue;let P=S;P.userId=C,P.reportType=\"primary\";for(let N of Object.keys(P.enumerators??{}))P.enumerators[N]=P.enumerators[N].length;E(P);let I=new Map,R=20;for(let[N,U]of Object.entries(P.values))U.length>0&&I.set(N,U.slice(0,R));for(;I.size>0;){let N={};N.userId=C,N.reportType=\"secondary\",N.metrics={};for(let[U,W]of I)N.metrics[U]=W.shift(),W.length===0&&I.delete(U);E(N)}}}}return!0}applyChanges(){let e=this.getRegistryPath(),r;try{r=le.readJsonSync(e)}catch{r={}}let s=this.configuration.get(\"telemetryUserId\")??\"*\",a=r.blocks=r.blocks??{},n=a[s]=a[s]??{};for(let c of this.hits.keys()){let f=n.hits=n.hits??{},p=f[c]=f[c]??{};for(let[h,E]of this.hits.get(c))p[h]=(p[h]??0)+E}for(let c of[\"values\",\"enumerators\"])for(let f of this[c].keys()){let p=n[c]=n[c]??{};p[f]=[...new Set([...p[f]??[],...this[c].get(f)??[]])]}this.shouldCommitTips&&(r.lastTips=this.nextTips,r.displayedTips=this.displayedTips),le.mkdirSync(K.dirname(e),{recursive:!0}),le.writeJsonSync(e,r)}startBuffer(){process.on(\"exit\",()=>{try{this.applyChanges()}catch{}})}}});var Xv={};Vt(Xv,{BuildDirectiveType:()=>_R,CACHE_CHECKPOINT:()=>tG,CACHE_VERSION:()=>MR,Cache:()=>Jr,Configuration:()=>ze,DEFAULT_RC_FILENAME:()=>Qj,FormatType:()=>Dde,InstallMode:()=>ec,LEGACY_PLUGINS:()=>hv,LOCKFILE_VERSION:()=>YR,LegacyMigrationResolver:()=>KI,LightReport:()=>uA,LinkType:()=>YE,LockfileResolver:()=>JI,Manifest:()=>Ht,MessageName:()=>Dr,MultiFetcher:()=>aI,PackageExtensionStatus:()=>a3,PackageExtensionType:()=>o3,PeerWarningType:()=>VR,Project:()=>Tt,Report:()=>ho,ReportError:()=>Yt,SettingsType:()=>gv,StreamReport:()=>Ot,TAG_REGEXP:()=>Up,TelemetryManager:()=>XI,ThrowReport:()=>Yi,VirtualFetcher:()=>lI,WindowsLinkType:()=>yT,Workspace:()=>ZI,WorkspaceFetcher:()=>cI,WorkspaceResolver:()=>Ei,YarnVersion:()=>un,execUtils:()=>Gr,folderUtils:()=>FQ,formatUtils:()=>he,hashUtils:()=>Nn,httpUtils:()=>An,miscUtils:()=>je,nodeUtils:()=>ps,parseMessageName:()=>zx,reportOptionDeprecations:()=>vI,scriptUtils:()=>In,semverUtils:()=>Or,stringifyMessageName:()=>Vf,structUtils:()=>q,tgzUtils:()=>gs,treeUtils:()=>Qs});var Ve=Ct(()=>{hT();NQ();Qc();E0();fT();kc();pT();g6();Np();Yo();JIe();rCe();rG();dv();dv();sCe();nG();oCe();iG();sI();Zx();K8();JCe();Fc();xv();zCe();yG();z8();Z8();$d();EG();Pv();hAe()});var rwe=L((wrr,eS)=>{\"use strict\";var ywt=process.env.TERM_PROGRAM===\"Hyper\",Ewt=process.platform===\"win32\",$Ce=process.platform===\"linux\",vG={ballotDisabled:\"\\u2612\",ballotOff:\"\\u2610\",ballotOn:\"\\u2611\",bullet:\"\\u2022\",bulletWhite:\"\\u25E6\",fullBlock:\"\\u2588\",heart:\"\\u2764\",identicalTo:\"\\u2261\",line:\"\\u2500\",mark:\"\\u203B\",middot:\"\\xB7\",minus:\"\\uFF0D\",multiplication:\"\\xD7\",obelus:\"\\xF7\",pencilDownRight:\"\\u270E\",pencilRight:\"\\u270F\",pencilUpRight:\"\\u2710\",percent:\"%\",pilcrow2:\"\\u2761\",pilcrow:\"\\xB6\",plusMinus:\"\\xB1\",section:\"\\xA7\",starsOff:\"\\u2606\",starsOn:\"\\u2605\",upDownArrow:\"\\u2195\"},ewe=Object.assign({},vG,{check:\"\\u221A\",cross:\"\\xD7\",ellipsisLarge:\"...\",ellipsis:\"...\",info:\"i\",question:\"?\",questionSmall:\"?\",pointer:\">\",pointerSmall:\"\\xBB\",radioOff:\"( )\",radioOn:\"(*)\",warning:\"\\u203C\"}),twe=Object.assign({},vG,{ballotCross:\"\\u2718\",check:\"\\u2714\",cross:\"\\u2716\",ellipsisLarge:\"\\u22EF\",ellipsis:\"\\u2026\",info:\"\\u2139\",question:\"?\",questionFull:\"\\uFF1F\",questionSmall:\"\\uFE56\",pointer:$Ce?\"\\u25B8\":\"\\u276F\",pointerSmall:$Ce?\"\\u2023\":\"\\u203A\",radioOff:\"\\u25EF\",radioOn:\"\\u25C9\",warning:\"\\u26A0\"});eS.exports=Ewt&&!ywt?ewe:twe;Reflect.defineProperty(eS.exports,\"common\",{enumerable:!1,value:vG});Reflect.defineProperty(eS.exports,\"windows\",{enumerable:!1,value:ewe});Reflect.defineProperty(eS.exports,\"other\",{enumerable:!1,value:twe})});var Ju=L((Brr,SG)=>{\"use strict\";var Iwt=t=>t!==null&&typeof t==\"object\"&&!Array.isArray(t),Cwt=/[\\u001b\\u009b][[\\]#;?()]*(?:(?:(?:[^\\W_]*;?[^\\W_]*)\\u0007)|(?:(?:[0-9]{1,4}(;[0-9]{0,4})*)?[~0-9=<>cf-nqrtyA-PRZ]))/g,nwe=()=>{let t={enabled:!0,visible:!0,styles:{},keys:{}};\"FORCE_COLOR\"in process.env&&(t.enabled=process.env.FORCE_COLOR!==\"0\");let e=n=>{let c=n.open=`\\x1B[${n.codes[0]}m`,f=n.close=`\\x1B[${n.codes[1]}m`,p=n.regex=new RegExp(`\\\\u001b\\\\[${n.codes[1]}m`,\"g\");return n.wrap=(h,E)=>{h.includes(f)&&(h=h.replace(p,f+c));let C=c+h+f;return E?C.replace(/\\r*\\n/g,`${f}$&${c}`):C},n},r=(n,c,f)=>typeof n==\"function\"?n(c):n.wrap(c,f),s=(n,c)=>{if(n===\"\"||n==null)return\"\";if(t.enabled===!1)return n;if(t.visible===!1)return\"\";let f=\"\"+n,p=f.includes(`\n`),h=c.length;for(h>0&&c.includes(\"unstyle\")&&(c=[...new Set([\"unstyle\",...c])].reverse());h-- >0;)f=r(t.styles[c[h]],f,p);return f},a=(n,c,f)=>{t.styles[n]=e({name:n,codes:c}),(t.keys[f]||(t.keys[f]=[])).push(n),Reflect.defineProperty(t,n,{configurable:!0,enumerable:!0,set(h){t.alias(n,h)},get(){let h=E=>s(E,h.stack);return Reflect.setPrototypeOf(h,t),h.stack=this.stack?this.stack.concat(n):[n],h}})};return a(\"reset\",[0,0],\"modifier\"),a(\"bold\",[1,22],\"modifier\"),a(\"dim\",[2,22],\"modifier\"),a(\"italic\",[3,23],\"modifier\"),a(\"underline\",[4,24],\"modifier\"),a(\"inverse\",[7,27],\"modifier\"),a(\"hidden\",[8,28],\"modifier\"),a(\"strikethrough\",[9,29],\"modifier\"),a(\"black\",[30,39],\"color\"),a(\"red\",[31,39],\"color\"),a(\"green\",[32,39],\"color\"),a(\"yellow\",[33,39],\"color\"),a(\"blue\",[34,39],\"color\"),a(\"magenta\",[35,39],\"color\"),a(\"cyan\",[36,39],\"color\"),a(\"white\",[37,39],\"color\"),a(\"gray\",[90,39],\"color\"),a(\"grey\",[90,39],\"color\"),a(\"bgBlack\",[40,49],\"bg\"),a(\"bgRed\",[41,49],\"bg\"),a(\"bgGreen\",[42,49],\"bg\"),a(\"bgYellow\",[43,49],\"bg\"),a(\"bgBlue\",[44,49],\"bg\"),a(\"bgMagenta\",[45,49],\"bg\"),a(\"bgCyan\",[46,49],\"bg\"),a(\"bgWhite\",[47,49],\"bg\"),a(\"blackBright\",[90,39],\"bright\"),a(\"redBright\",[91,39],\"bright\"),a(\"greenBright\",[92,39],\"bright\"),a(\"yellowBright\",[93,39],\"bright\"),a(\"blueBright\",[94,39],\"bright\"),a(\"magentaBright\",[95,39],\"bright\"),a(\"cyanBright\",[96,39],\"bright\"),a(\"whiteBright\",[97,39],\"bright\"),a(\"bgBlackBright\",[100,49],\"bgBright\"),a(\"bgRedBright\",[101,49],\"bgBright\"),a(\"bgGreenBright\",[102,49],\"bgBright\"),a(\"bgYellowBright\",[103,49],\"bgBright\"),a(\"bgBlueBright\",[104,49],\"bgBright\"),a(\"bgMagentaBright\",[105,49],\"bgBright\"),a(\"bgCyanBright\",[106,49],\"bgBright\"),a(\"bgWhiteBright\",[107,49],\"bgBright\"),t.ansiRegex=Cwt,t.hasColor=t.hasAnsi=n=>(t.ansiRegex.lastIndex=0,typeof n==\"string\"&&n!==\"\"&&t.ansiRegex.test(n)),t.alias=(n,c)=>{let f=typeof c==\"string\"?t[c]:c;if(typeof f!=\"function\")throw new TypeError(\"Expected alias to be the name of an existing color (string) or a function\");f.stack||(Reflect.defineProperty(f,\"name\",{value:n}),t.styles[n]=f,f.stack=[n]),Reflect.defineProperty(t,n,{configurable:!0,enumerable:!0,set(p){t.alias(n,p)},get(){let p=h=>s(h,p.stack);return Reflect.setPrototypeOf(p,t),p.stack=this.stack?this.stack.concat(f.stack):f.stack,p}})},t.theme=n=>{if(!Iwt(n))throw new TypeError(\"Expected theme to be an object\");for(let c of Object.keys(n))t.alias(c,n[c]);return t},t.alias(\"unstyle\",n=>typeof n==\"string\"&&n!==\"\"?(t.ansiRegex.lastIndex=0,n.replace(t.ansiRegex,\"\")):\"\"),t.alias(\"noop\",n=>n),t.none=t.clear=t.noop,t.stripColor=t.unstyle,t.symbols=rwe(),t.define=a,t};SG.exports=nwe();SG.exports.create=nwe});var $o=L(pn=>{\"use strict\";var wwt=Object.prototype.toString,Gc=Ju(),iwe=!1,DG=[],swe={yellow:\"blue\",cyan:\"red\",green:\"magenta\",black:\"white\",blue:\"yellow\",red:\"cyan\",magenta:\"green\",white:\"black\"};pn.longest=(t,e)=>t.reduce((r,s)=>Math.max(r,e?s[e].length:s.length),0);pn.hasColor=t=>!!t&&Gc.hasColor(t);var JR=pn.isObject=t=>t!==null&&typeof t==\"object\"&&!Array.isArray(t);pn.nativeType=t=>wwt.call(t).slice(8,-1).toLowerCase().replace(/\\s/g,\"\");pn.isAsyncFn=t=>pn.nativeType(t)===\"asyncfunction\";pn.isPrimitive=t=>t!=null&&typeof t!=\"object\"&&typeof t!=\"function\";pn.resolve=(t,e,...r)=>typeof e==\"function\"?e.call(t,...r):e;pn.scrollDown=(t=[])=>[...t.slice(1),t[0]];pn.scrollUp=(t=[])=>[t.pop(),...t];pn.reorder=(t=[])=>{let e=t.slice();return e.sort((r,s)=>r.index>s.index?1:r.index<s.index?-1:0),e};pn.swap=(t,e,r)=>{let s=t.length,a=r===s?0:r<0?s-1:r,n=t[e];t[e]=t[a],t[a]=n};pn.width=(t,e=80)=>{let r=t&&t.columns?t.columns:e;return t&&typeof t.getWindowSize==\"function\"&&(r=t.getWindowSize()[0]),process.platform===\"win32\"?r-1:r};pn.height=(t,e=20)=>{let r=t&&t.rows?t.rows:e;return t&&typeof t.getWindowSize==\"function\"&&(r=t.getWindowSize()[1]),r};pn.wordWrap=(t,e={})=>{if(!t)return t;typeof e==\"number\"&&(e={width:e});let{indent:r=\"\",newline:s=`\n`+r,width:a=80}=e,n=(s+r).match(/[^\\S\\n]/g)||[];a-=n.length;let c=`.{1,${a}}([\\\\s\\\\u200B]+|$)|[^\\\\s\\\\u200B]+?([\\\\s\\\\u200B]+|$)`,f=t.trim(),p=new RegExp(c,\"g\"),h=f.match(p)||[];return h=h.map(E=>E.replace(/\\n$/,\"\")),e.padEnd&&(h=h.map(E=>E.padEnd(a,\" \"))),e.padStart&&(h=h.map(E=>E.padStart(a,\" \"))),r+h.join(s)};pn.unmute=t=>{let e=t.stack.find(s=>Gc.keys.color.includes(s));return e?Gc[e]:t.stack.find(s=>s.slice(2)===\"bg\")?Gc[e.slice(2)]:s=>s};pn.pascal=t=>t?t[0].toUpperCase()+t.slice(1):\"\";pn.inverse=t=>{if(!t||!t.stack)return t;let e=t.stack.find(s=>Gc.keys.color.includes(s));if(e){let s=Gc[\"bg\"+pn.pascal(e)];return s?s.black:t}let r=t.stack.find(s=>s.slice(0,2)===\"bg\");return r?Gc[r.slice(2).toLowerCase()]||t:Gc.none};pn.complement=t=>{if(!t||!t.stack)return t;let e=t.stack.find(s=>Gc.keys.color.includes(s)),r=t.stack.find(s=>s.slice(0,2)===\"bg\");if(e&&!r)return Gc[swe[e]||e];if(r){let s=r.slice(2).toLowerCase(),a=swe[s];return a&&Gc[\"bg\"+pn.pascal(a)]||t}return Gc.none};pn.meridiem=t=>{let e=t.getHours(),r=t.getMinutes(),s=e>=12?\"pm\":\"am\";e=e%12;let a=e===0?12:e,n=r<10?\"0\"+r:r;return a+\":\"+n+\" \"+s};pn.set=(t={},e=\"\",r)=>e.split(\".\").reduce((s,a,n,c)=>{let f=c.length-1>n?s[a]||{}:r;return!pn.isObject(f)&&n<c.length-1&&(f={}),s[a]=f},t);pn.get=(t={},e=\"\",r)=>{let s=t[e]==null?e.split(\".\").reduce((a,n)=>a&&a[n],t):t[e];return s??r};pn.mixin=(t,e)=>{if(!JR(t))return e;if(!JR(e))return t;for(let r of Object.keys(e)){let s=Object.getOwnPropertyDescriptor(e,r);if(s.hasOwnProperty(\"value\"))if(t.hasOwnProperty(r)&&JR(s.value)){let a=Object.getOwnPropertyDescriptor(t,r);JR(a.value)?t[r]=pn.merge({},t[r],e[r]):Reflect.defineProperty(t,r,s)}else Reflect.defineProperty(t,r,s);else Reflect.defineProperty(t,r,s)}return t};pn.merge=(...t)=>{let e={};for(let r of t)pn.mixin(e,r);return e};pn.mixinEmitter=(t,e)=>{let r=e.constructor.prototype;for(let s of Object.keys(r)){let a=r[s];typeof a==\"function\"?pn.define(t,s,a.bind(e)):pn.define(t,s,a)}};pn.onExit=t=>{let e=(r,s)=>{iwe||(iwe=!0,DG.forEach(a=>a()),r===!0&&process.exit(128+s))};DG.length===0&&(process.once(\"SIGTERM\",e.bind(null,!0,15)),process.once(\"SIGINT\",e.bind(null,!0,2)),process.once(\"exit\",e)),DG.push(t)};pn.define=(t,e,r)=>{Reflect.defineProperty(t,e,{value:r})};pn.defineExport=(t,e,r)=>{let s;Reflect.defineProperty(t,e,{enumerable:!0,configurable:!0,set(a){s=a},get(){return s?s():r()}})}});var owe=L(rC=>{\"use strict\";rC.ctrl={a:\"first\",b:\"backward\",c:\"cancel\",d:\"deleteForward\",e:\"last\",f:\"forward\",g:\"reset\",i:\"tab\",k:\"cutForward\",l:\"reset\",n:\"newItem\",m:\"cancel\",j:\"submit\",p:\"search\",r:\"remove\",s:\"save\",u:\"undo\",w:\"cutLeft\",x:\"toggleCursor\",v:\"paste\"};rC.shift={up:\"shiftUp\",down:\"shiftDown\",left:\"shiftLeft\",right:\"shiftRight\",tab:\"prev\"};rC.fn={up:\"pageUp\",down:\"pageDown\",left:\"pageLeft\",right:\"pageRight\",delete:\"deleteForward\"};rC.option={b:\"backward\",f:\"forward\",d:\"cutRight\",left:\"cutLeft\",up:\"altUp\",down:\"altDown\"};rC.keys={pageup:\"pageUp\",pagedown:\"pageDown\",home:\"home\",end:\"end\",cancel:\"cancel\",delete:\"deleteForward\",backspace:\"delete\",down:\"down\",enter:\"submit\",escape:\"cancel\",left:\"left\",space:\"space\",number:\"number\",return:\"submit\",right:\"right\",tab:\"next\",up:\"up\"}});var cwe=L((Drr,lwe)=>{\"use strict\";var awe=Ie(\"readline\"),Bwt=owe(),vwt=/^(?:\\x1b)([a-zA-Z0-9])$/,Swt=/^(?:\\x1b+)(O|N|\\[|\\[\\[)(?:(\\d+)(?:;(\\d+))?([~^$])|(?:1;)?(\\d+)?([a-zA-Z]))/,Dwt={OP:\"f1\",OQ:\"f2\",OR:\"f3\",OS:\"f4\",\"[11~\":\"f1\",\"[12~\":\"f2\",\"[13~\":\"f3\",\"[14~\":\"f4\",\"[[A\":\"f1\",\"[[B\":\"f2\",\"[[C\":\"f3\",\"[[D\":\"f4\",\"[[E\":\"f5\",\"[15~\":\"f5\",\"[17~\":\"f6\",\"[18~\":\"f7\",\"[19~\":\"f8\",\"[20~\":\"f9\",\"[21~\":\"f10\",\"[23~\":\"f11\",\"[24~\":\"f12\",\"[A\":\"up\",\"[B\":\"down\",\"[C\":\"right\",\"[D\":\"left\",\"[E\":\"clear\",\"[F\":\"end\",\"[H\":\"home\",OA:\"up\",OB:\"down\",OC:\"right\",OD:\"left\",OE:\"clear\",OF:\"end\",OH:\"home\",\"[1~\":\"home\",\"[2~\":\"insert\",\"[3~\":\"delete\",\"[4~\":\"end\",\"[5~\":\"pageup\",\"[6~\":\"pagedown\",\"[[5~\":\"pageup\",\"[[6~\":\"pagedown\",\"[7~\":\"home\",\"[8~\":\"end\",\"[a\":\"up\",\"[b\":\"down\",\"[c\":\"right\",\"[d\":\"left\",\"[e\":\"clear\",\"[2$\":\"insert\",\"[3$\":\"delete\",\"[5$\":\"pageup\",\"[6$\":\"pagedown\",\"[7$\":\"home\",\"[8$\":\"end\",Oa:\"up\",Ob:\"down\",Oc:\"right\",Od:\"left\",Oe:\"clear\",\"[2^\":\"insert\",\"[3^\":\"delete\",\"[5^\":\"pageup\",\"[6^\":\"pagedown\",\"[7^\":\"home\",\"[8^\":\"end\",\"[Z\":\"tab\"};function bwt(t){return[\"[a\",\"[b\",\"[c\",\"[d\",\"[e\",\"[2$\",\"[3$\",\"[5$\",\"[6$\",\"[7$\",\"[8$\",\"[Z\"].includes(t)}function Pwt(t){return[\"Oa\",\"Ob\",\"Oc\",\"Od\",\"Oe\",\"[2^\",\"[3^\",\"[5^\",\"[6^\",\"[7^\",\"[8^\"].includes(t)}var zR=(t=\"\",e={})=>{let r,s={name:e.name,ctrl:!1,meta:!1,shift:!1,option:!1,sequence:t,raw:t,...e};if(Buffer.isBuffer(t)?t[0]>127&&t[1]===void 0?(t[0]-=128,t=\"\\x1B\"+String(t)):t=String(t):t!==void 0&&typeof t!=\"string\"?t=String(t):t||(t=s.sequence||\"\"),s.sequence=s.sequence||t||s.name,t===\"\\r\")s.raw=void 0,s.name=\"return\";else if(t===`\n`)s.name=\"enter\";else if(t===\"\t\")s.name=\"tab\";else if(t===\"\\b\"||t===\"\\x7F\"||t===\"\\x1B\\x7F\"||t===\"\\x1B\\b\")s.name=\"backspace\",s.meta=t.charAt(0)===\"\\x1B\";else if(t===\"\\x1B\"||t===\"\\x1B\\x1B\")s.name=\"escape\",s.meta=t.length===2;else if(t===\" \"||t===\"\\x1B \")s.name=\"space\",s.meta=t.length===2;else if(t<=\"\u001a\")s.name=String.fromCharCode(t.charCodeAt(0)+97-1),s.ctrl=!0;else if(t.length===1&&t>=\"0\"&&t<=\"9\")s.name=\"number\";else if(t.length===1&&t>=\"a\"&&t<=\"z\")s.name=t;else if(t.length===1&&t>=\"A\"&&t<=\"Z\")s.name=t.toLowerCase(),s.shift=!0;else if(r=vwt.exec(t))s.meta=!0,s.shift=/^[A-Z]$/.test(r[1]);else if(r=Swt.exec(t)){let a=[...t];a[0]===\"\\x1B\"&&a[1]===\"\\x1B\"&&(s.option=!0);let n=[r[1],r[2],r[4],r[6]].filter(Boolean).join(\"\"),c=(r[3]||r[5]||1)-1;s.ctrl=!!(c&4),s.meta=!!(c&10),s.shift=!!(c&1),s.code=n,s.name=Dwt[n],s.shift=bwt(n)||s.shift,s.ctrl=Pwt(n)||s.ctrl}return s};zR.listen=(t={},e)=>{let{stdin:r}=t;if(!r||r!==process.stdin&&!r.isTTY)throw new Error(\"Invalid stream passed\");let s=awe.createInterface({terminal:!0,input:r});awe.emitKeypressEvents(r,s);let a=(f,p)=>e(f,zR(f,p),s),n=r.isRaw;return r.isTTY&&r.setRawMode(!0),r.on(\"keypress\",a),s.resume(),()=>{r.isTTY&&r.setRawMode(n),r.removeListener(\"keypress\",a),s.pause(),s.close()}};zR.action=(t,e,r)=>{let s={...Bwt,...r};return e.ctrl?(e.action=s.ctrl[e.name],e):e.option&&s.option?(e.action=s.option[e.name],e):e.shift?(e.action=s.shift[e.name],e):(e.action=s.keys[e.name],e)};lwe.exports=zR});var fwe=L((brr,uwe)=>{\"use strict\";uwe.exports=t=>{t.timers=t.timers||{};let e=t.options.timers;if(e)for(let r of Object.keys(e)){let s=e[r];typeof s==\"number\"&&(s={interval:s}),xwt(t,r,s)}};function xwt(t,e,r={}){let s=t.timers[e]={name:e,start:Date.now(),ms:0,tick:0},a=r.interval||120;s.frames=r.frames||[],s.loading=!0;let n=setInterval(()=>{s.ms=Date.now()-s.start,s.tick++,t.render()},a);return s.stop=()=>{s.loading=!1,clearInterval(n)},Reflect.defineProperty(s,\"interval\",{value:n}),t.once(\"close\",()=>s.stop()),s.stop}});var pwe=L((Prr,Awe)=>{\"use strict\";var{define:kwt,width:Qwt}=$o(),bG=class{constructor(e){let r=e.options;kwt(this,\"_prompt\",e),this.type=e.type,this.name=e.name,this.message=\"\",this.header=\"\",this.footer=\"\",this.error=\"\",this.hint=\"\",this.input=\"\",this.cursor=0,this.index=0,this.lines=0,this.tick=0,this.prompt=\"\",this.buffer=\"\",this.width=Qwt(r.stdout||process.stdout),Object.assign(this,r),this.name=this.name||this.message,this.message=this.message||this.name,this.symbols=e.symbols,this.styles=e.styles,this.required=new Set,this.cancelled=!1,this.submitted=!1}clone(){let e={...this};return e.status=this.status,e.buffer=Buffer.from(e.buffer),delete e.clone,e}set color(e){this._color=e}get color(){let e=this.prompt.styles;if(this.cancelled)return e.cancelled;if(this.submitted)return e.submitted;let r=this._color||e[this.status];return typeof r==\"function\"?r:e.pending}set loading(e){this._loading=e}get loading(){return typeof this._loading==\"boolean\"?this._loading:this.loadingChoices?\"choices\":!1}get status(){return this.cancelled?\"cancelled\":this.submitted?\"submitted\":\"pending\"}};Awe.exports=bG});var gwe=L((xrr,hwe)=>{\"use strict\";var PG=$o(),mo=Ju(),xG={default:mo.noop,noop:mo.noop,set inverse(t){this._inverse=t},get inverse(){return this._inverse||PG.inverse(this.primary)},set complement(t){this._complement=t},get complement(){return this._complement||PG.complement(this.primary)},primary:mo.cyan,success:mo.green,danger:mo.magenta,strong:mo.bold,warning:mo.yellow,muted:mo.dim,disabled:mo.gray,dark:mo.dim.gray,underline:mo.underline,set info(t){this._info=t},get info(){return this._info||this.primary},set em(t){this._em=t},get em(){return this._em||this.primary.underline},set heading(t){this._heading=t},get heading(){return this._heading||this.muted.underline},set pending(t){this._pending=t},get pending(){return this._pending||this.primary},set submitted(t){this._submitted=t},get submitted(){return this._submitted||this.success},set cancelled(t){this._cancelled=t},get cancelled(){return this._cancelled||this.danger},set typing(t){this._typing=t},get typing(){return this._typing||this.dim},set placeholder(t){this._placeholder=t},get placeholder(){return this._placeholder||this.primary.dim},set highlight(t){this._highlight=t},get highlight(){return this._highlight||this.inverse}};xG.merge=(t={})=>{t.styles&&typeof t.styles.enabled==\"boolean\"&&(mo.enabled=t.styles.enabled),t.styles&&typeof t.styles.visible==\"boolean\"&&(mo.visible=t.styles.visible);let e=PG.merge({},xG,t.styles);delete e.merge;for(let r of Object.keys(mo))e.hasOwnProperty(r)||Reflect.defineProperty(e,r,{get:()=>mo[r]});for(let r of Object.keys(mo.styles))e.hasOwnProperty(r)||Reflect.defineProperty(e,r,{get:()=>mo[r]});return e};hwe.exports=xG});var mwe=L((krr,dwe)=>{\"use strict\";var kG=process.platform===\"win32\",Xp=Ju(),Twt=$o(),QG={...Xp.symbols,upDownDoubleArrow:\"\\u21D5\",upDownDoubleArrow2:\"\\u2B0D\",upDownArrow:\"\\u2195\",asterisk:\"*\",asterism:\"\\u2042\",bulletWhite:\"\\u25E6\",electricArrow:\"\\u2301\",ellipsisLarge:\"\\u22EF\",ellipsisSmall:\"\\u2026\",fullBlock:\"\\u2588\",identicalTo:\"\\u2261\",indicator:Xp.symbols.check,leftAngle:\"\\u2039\",mark:\"\\u203B\",minus:\"\\u2212\",multiplication:\"\\xD7\",obelus:\"\\xF7\",percent:\"%\",pilcrow:\"\\xB6\",pilcrow2:\"\\u2761\",pencilUpRight:\"\\u2710\",pencilDownRight:\"\\u270E\",pencilRight:\"\\u270F\",plus:\"+\",plusMinus:\"\\xB1\",pointRight:\"\\u261E\",rightAngle:\"\\u203A\",section:\"\\xA7\",hexagon:{off:\"\\u2B21\",on:\"\\u2B22\",disabled:\"\\u2B22\"},ballot:{on:\"\\u2611\",off:\"\\u2610\",disabled:\"\\u2612\"},stars:{on:\"\\u2605\",off:\"\\u2606\",disabled:\"\\u2606\"},folder:{on:\"\\u25BC\",off:\"\\u25B6\",disabled:\"\\u25B6\"},prefix:{pending:Xp.symbols.question,submitted:Xp.symbols.check,cancelled:Xp.symbols.cross},separator:{pending:Xp.symbols.pointerSmall,submitted:Xp.symbols.middot,cancelled:Xp.symbols.middot},radio:{off:kG?\"( )\":\"\\u25EF\",on:kG?\"(*)\":\"\\u25C9\",disabled:kG?\"(|)\":\"\\u24BE\"},numbers:[\"\\u24EA\",\"\\u2460\",\"\\u2461\",\"\\u2462\",\"\\u2463\",\"\\u2464\",\"\\u2465\",\"\\u2466\",\"\\u2467\",\"\\u2468\",\"\\u2469\",\"\\u246A\",\"\\u246B\",\"\\u246C\",\"\\u246D\",\"\\u246E\",\"\\u246F\",\"\\u2470\",\"\\u2471\",\"\\u2472\",\"\\u2473\",\"\\u3251\",\"\\u3252\",\"\\u3253\",\"\\u3254\",\"\\u3255\",\"\\u3256\",\"\\u3257\",\"\\u3258\",\"\\u3259\",\"\\u325A\",\"\\u325B\",\"\\u325C\",\"\\u325D\",\"\\u325E\",\"\\u325F\",\"\\u32B1\",\"\\u32B2\",\"\\u32B3\",\"\\u32B4\",\"\\u32B5\",\"\\u32B6\",\"\\u32B7\",\"\\u32B8\",\"\\u32B9\",\"\\u32BA\",\"\\u32BB\",\"\\u32BC\",\"\\u32BD\",\"\\u32BE\",\"\\u32BF\"]};QG.merge=t=>{let e=Twt.merge({},Xp.symbols,QG,t.symbols);return delete e.merge,e};dwe.exports=QG});var Ewe=L((Qrr,ywe)=>{\"use strict\";var Rwt=gwe(),Fwt=mwe(),Nwt=$o();ywe.exports=t=>{t.options=Nwt.merge({},t.options.theme,t.options),t.symbols=Fwt.merge(t.options),t.styles=Rwt.merge(t.options)}});var vwe=L((wwe,Bwe)=>{\"use strict\";var Iwe=process.env.TERM_PROGRAM===\"Apple_Terminal\",Owt=Ju(),TG=$o(),zu=Bwe.exports=wwe,Ui=\"\\x1B[\",Cwe=\"\\x07\",RG=!1,H0=zu.code={bell:Cwe,beep:Cwe,beginning:`${Ui}G`,down:`${Ui}J`,esc:Ui,getPosition:`${Ui}6n`,hide:`${Ui}?25l`,line:`${Ui}2K`,lineEnd:`${Ui}K`,lineStart:`${Ui}1K`,restorePosition:Ui+(Iwe?\"8\":\"u\"),savePosition:Ui+(Iwe?\"7\":\"s\"),screen:`${Ui}2J`,show:`${Ui}?25h`,up:`${Ui}1J`},Cm=zu.cursor={get hidden(){return RG},hide(){return RG=!0,H0.hide},show(){return RG=!1,H0.show},forward:(t=1)=>`${Ui}${t}C`,backward:(t=1)=>`${Ui}${t}D`,nextLine:(t=1)=>`${Ui}E`.repeat(t),prevLine:(t=1)=>`${Ui}F`.repeat(t),up:(t=1)=>t?`${Ui}${t}A`:\"\",down:(t=1)=>t?`${Ui}${t}B`:\"\",right:(t=1)=>t?`${Ui}${t}C`:\"\",left:(t=1)=>t?`${Ui}${t}D`:\"\",to(t,e){return e?`${Ui}${e+1};${t+1}H`:`${Ui}${t+1}G`},move(t=0,e=0){let r=\"\";return r+=t<0?Cm.left(-t):t>0?Cm.right(t):\"\",r+=e<0?Cm.up(-e):e>0?Cm.down(e):\"\",r},restore(t={}){let{after:e,cursor:r,initial:s,input:a,prompt:n,size:c,value:f}=t;if(s=TG.isPrimitive(s)?String(s):\"\",a=TG.isPrimitive(a)?String(a):\"\",f=TG.isPrimitive(f)?String(f):\"\",c){let p=zu.cursor.up(c)+zu.cursor.to(n.length),h=a.length-r;return h>0&&(p+=zu.cursor.left(h)),p}if(f||e){let p=!a&&s?-s.length:-a.length+r;return e&&(p-=e.length),a===\"\"&&s&&!n.includes(s)&&(p+=s.length),zu.cursor.move(p)}}},FG=zu.erase={screen:H0.screen,up:H0.up,down:H0.down,line:H0.line,lineEnd:H0.lineEnd,lineStart:H0.lineStart,lines(t){let e=\"\";for(let r=0;r<t;r++)e+=zu.erase.line+(r<t-1?zu.cursor.up(1):\"\");return t&&(e+=zu.code.beginning),e}};zu.clear=(t=\"\",e=process.stdout.columns)=>{if(!e)return FG.line+Cm.to(0);let r=n=>[...Owt.unstyle(n)].length,s=t.split(/\\r?\\n/),a=0;for(let n of s)a+=1+Math.floor(Math.max(r(n)-1,0)/e);return(FG.line+Cm.prevLine()).repeat(a-1)+FG.line+Cm.to(0)}});var nC=L((Trr,Dwe)=>{\"use strict\";var Lwt=Ie(\"events\"),Swe=Ju(),NG=cwe(),Mwt=fwe(),_wt=pwe(),Uwt=Ewe(),hl=$o(),wm=vwe(),OG=class t extends Lwt{constructor(e={}){super(),this.name=e.name,this.type=e.type,this.options=e,Uwt(this),Mwt(this),this.state=new _wt(this),this.initial=[e.initial,e.default].find(r=>r!=null),this.stdout=e.stdout||process.stdout,this.stdin=e.stdin||process.stdin,this.scale=e.scale||1,this.term=this.options.term||process.env.TERM_PROGRAM,this.margin=jwt(this.options.margin),this.setMaxListeners(0),Hwt(this)}async keypress(e,r={}){this.keypressed=!0;let s=NG.action(e,NG(e,r),this.options.actions);this.state.keypress=s,this.emit(\"keypress\",e,s),this.emit(\"state\",this.state.clone());let a=this.options[s.action]||this[s.action]||this.dispatch;if(typeof a==\"function\")return await a.call(this,e,s);this.alert()}alert(){delete this.state.alert,this.options.show===!1?this.emit(\"alert\"):this.stdout.write(wm.code.beep)}cursorHide(){this.stdout.write(wm.cursor.hide()),hl.onExit(()=>this.cursorShow())}cursorShow(){this.stdout.write(wm.cursor.show())}write(e){e&&(this.stdout&&this.state.show!==!1&&this.stdout.write(e),this.state.buffer+=e)}clear(e=0){let r=this.state.buffer;this.state.buffer=\"\",!(!r&&!e||this.options.show===!1)&&this.stdout.write(wm.cursor.down(e)+wm.clear(r,this.width))}restore(){if(this.state.closed||this.options.show===!1)return;let{prompt:e,after:r,rest:s}=this.sections(),{cursor:a,initial:n=\"\",input:c=\"\",value:f=\"\"}=this,p=this.state.size=s.length,h={after:r,cursor:a,initial:n,input:c,prompt:e,size:p,value:f},E=wm.cursor.restore(h);E&&this.stdout.write(E)}sections(){let{buffer:e,input:r,prompt:s}=this.state;s=Swe.unstyle(s);let a=Swe.unstyle(e),n=a.indexOf(s),c=a.slice(0,n),p=a.slice(n).split(`\n`),h=p[0],E=p[p.length-1],S=(s+(r?\" \"+r:\"\")).length,P=S<h.length?h.slice(S+1):\"\";return{header:c,prompt:h,after:P,rest:p.slice(1),last:E}}async submit(){this.state.submitted=!0,this.state.validating=!0,this.options.onSubmit&&await this.options.onSubmit.call(this,this.name,this.value,this);let e=this.state.error||await this.validate(this.value,this.state);if(e!==!0){let r=`\n`+this.symbols.pointer+\" \";typeof e==\"string\"?r+=e.trim():r+=\"Invalid input\",this.state.error=`\n`+this.styles.danger(r),this.state.submitted=!1,await this.render(),await this.alert(),this.state.validating=!1,this.state.error=void 0;return}this.state.validating=!1,await this.render(),await this.close(),this.value=await this.result(this.value),this.emit(\"submit\",this.value)}async cancel(e){this.state.cancelled=this.state.submitted=!0,await this.render(),await this.close(),typeof this.options.onCancel==\"function\"&&await this.options.onCancel.call(this,this.name,this.value,this),this.emit(\"cancel\",await this.error(e))}async close(){this.state.closed=!0;try{let e=this.sections(),r=Math.ceil(e.prompt.length/this.width);e.rest&&this.write(wm.cursor.down(e.rest.length)),this.write(`\n`.repeat(r))}catch{}this.emit(\"close\")}start(){!this.stop&&this.options.show!==!1&&(this.stop=NG.listen(this,this.keypress.bind(this)),this.once(\"close\",this.stop))}async skip(){return this.skipped=this.options.skip===!0,typeof this.options.skip==\"function\"&&(this.skipped=await this.options.skip.call(this,this.name,this.value)),this.skipped}async initialize(){let{format:e,options:r,result:s}=this;if(this.format=()=>e.call(this,this.value),this.result=()=>s.call(this,this.value),typeof r.initial==\"function\"&&(this.initial=await r.initial.call(this,this)),typeof r.onRun==\"function\"&&await r.onRun.call(this,this),typeof r.onSubmit==\"function\"){let a=r.onSubmit.bind(this),n=this.submit.bind(this);delete this.options.onSubmit,this.submit=async()=>(await a(this.name,this.value,this),n())}await this.start(),await this.render()}render(){throw new Error(\"expected prompt to have a custom render method\")}run(){return new Promise(async(e,r)=>{if(this.once(\"submit\",e),this.once(\"cancel\",r),await this.skip())return this.render=()=>{},this.submit();await this.initialize(),this.emit(\"run\")})}async element(e,r,s){let{options:a,state:n,symbols:c,timers:f}=this,p=f&&f[e];n.timer=p;let h=a[e]||n[e]||c[e],E=r&&r[e]!=null?r[e]:await h;if(E===\"\")return E;let C=await this.resolve(E,n,r,s);return!C&&r&&r[e]?this.resolve(h,n,r,s):C}async prefix(){let e=await this.element(\"prefix\")||this.symbols,r=this.timers&&this.timers.prefix,s=this.state;return s.timer=r,hl.isObject(e)&&(e=e[s.status]||e.pending),hl.hasColor(e)?e:(this.styles[s.status]||this.styles.pending)(e)}async message(){let e=await this.element(\"message\");return hl.hasColor(e)?e:this.styles.strong(e)}async separator(){let e=await this.element(\"separator\")||this.symbols,r=this.timers&&this.timers.separator,s=this.state;s.timer=r;let a=e[s.status]||e.pending||s.separator,n=await this.resolve(a,s);return hl.isObject(n)&&(n=n[s.status]||n.pending),hl.hasColor(n)?n:this.styles.muted(n)}async pointer(e,r){let s=await this.element(\"pointer\",e,r);if(typeof s==\"string\"&&hl.hasColor(s))return s;if(s){let a=this.styles,n=this.index===r,c=n?a.primary:h=>h,f=await this.resolve(s[n?\"on\":\"off\"]||s,this.state),p=hl.hasColor(f)?f:c(f);return n?p:\" \".repeat(f.length)}}async indicator(e,r){let s=await this.element(\"indicator\",e,r);if(typeof s==\"string\"&&hl.hasColor(s))return s;if(s){let a=this.styles,n=e.enabled===!0,c=n?a.success:a.dark,f=s[n?\"on\":\"off\"]||s;return hl.hasColor(f)?f:c(f)}return\"\"}body(){return null}footer(){if(this.state.status===\"pending\")return this.element(\"footer\")}header(){if(this.state.status===\"pending\")return this.element(\"header\")}async hint(){if(this.state.status===\"pending\"&&!this.isValue(this.state.input)){let e=await this.element(\"hint\");return hl.hasColor(e)?e:this.styles.muted(e)}}error(e){return this.state.submitted?\"\":e||this.state.error}format(e){return e}result(e){return e}validate(e){return this.options.required===!0?this.isValue(e):!0}isValue(e){return e!=null&&e!==\"\"}resolve(e,...r){return hl.resolve(this,e,...r)}get base(){return t.prototype}get style(){return this.styles[this.state.status]}get height(){return this.options.rows||hl.height(this.stdout,25)}get width(){return this.options.columns||hl.width(this.stdout,80)}get size(){return{width:this.width,height:this.height}}set cursor(e){this.state.cursor=e}get cursor(){return this.state.cursor}set input(e){this.state.input=e}get input(){return this.state.input}set value(e){this.state.value=e}get value(){let{input:e,value:r}=this.state,s=[r,e].find(this.isValue.bind(this));return this.isValue(s)?s:this.initial}static get prompt(){return e=>new this(e).run()}};function Hwt(t){let e=a=>t[a]===void 0||typeof t[a]==\"function\",r=[\"actions\",\"choices\",\"initial\",\"margin\",\"roles\",\"styles\",\"symbols\",\"theme\",\"timers\",\"value\"],s=[\"body\",\"footer\",\"error\",\"header\",\"hint\",\"indicator\",\"message\",\"prefix\",\"separator\",\"skip\"];for(let a of Object.keys(t.options)){if(r.includes(a)||/^on[A-Z]/.test(a))continue;let n=t.options[a];typeof n==\"function\"&&e(a)?s.includes(a)||(t[a]=n.bind(t)):typeof t[a]!=\"function\"&&(t[a]=n)}}function jwt(t){typeof t==\"number\"&&(t=[t,t,t,t]);let e=[].concat(t||[]),r=a=>a%2===0?`\n`:\" \",s=[];for(let a=0;a<4;a++){let n=r(a);e[a]?s.push(n.repeat(e[a])):s.push(\"\")}return s}Dwe.exports=OG});var xwe=L((Rrr,Pwe)=>{\"use strict\";var qwt=$o(),bwe={default(t,e){return e},checkbox(t,e){throw new Error(\"checkbox role is not implemented yet\")},editable(t,e){throw new Error(\"editable role is not implemented yet\")},expandable(t,e){throw new Error(\"expandable role is not implemented yet\")},heading(t,e){return e.disabled=\"\",e.indicator=[e.indicator,\" \"].find(r=>r!=null),e.message=e.message||\"\",e},input(t,e){throw new Error(\"input role is not implemented yet\")},option(t,e){return bwe.default(t,e)},radio(t,e){throw new Error(\"radio role is not implemented yet\")},separator(t,e){return e.disabled=\"\",e.indicator=[e.indicator,\" \"].find(r=>r!=null),e.message=e.message||t.symbols.line.repeat(5),e},spacer(t,e){return e}};Pwe.exports=(t,e={})=>{let r=qwt.merge({},bwe,e.roles);return r[t]||r.default}});var tS=L((Frr,Twe)=>{\"use strict\";var Gwt=Ju(),Wwt=nC(),Ywt=xwe(),ZR=$o(),{reorder:LG,scrollUp:Vwt,scrollDown:Kwt,isObject:kwe,swap:Jwt}=ZR,MG=class extends Wwt{constructor(e){super(e),this.cursorHide(),this.maxSelected=e.maxSelected||1/0,this.multiple=e.multiple||!1,this.initial=e.initial||0,this.delay=e.delay||0,this.longest=0,this.num=\"\"}async initialize(){typeof this.options.initial==\"function\"&&(this.initial=await this.options.initial.call(this)),await this.reset(!0),await super.initialize()}async reset(){let{choices:e,initial:r,autofocus:s,suggest:a}=this.options;if(this.state._choices=[],this.state.choices=[],this.choices=await Promise.all(await this.toChoices(e)),this.choices.forEach(n=>n.enabled=!1),typeof a!=\"function\"&&this.selectable.length===0)throw new Error(\"At least one choice must be selectable\");kwe(r)&&(r=Object.keys(r)),Array.isArray(r)?(s!=null&&(this.index=this.findIndex(s)),r.forEach(n=>this.enable(this.find(n))),await this.render()):(s!=null&&(r=s),typeof r==\"string\"&&(r=this.findIndex(r)),typeof r==\"number\"&&r>-1&&(this.index=Math.max(0,Math.min(r,this.choices.length)),this.enable(this.find(this.index)))),this.isDisabled(this.focused)&&await this.down()}async toChoices(e,r){this.state.loadingChoices=!0;let s=[],a=0,n=async(c,f)=>{typeof c==\"function\"&&(c=await c.call(this)),c instanceof Promise&&(c=await c);for(let p=0;p<c.length;p++){let h=c[p]=await this.toChoice(c[p],a++,f);s.push(h),h.choices&&await n(h.choices,h)}return s};return n(e,r).then(c=>(this.state.loadingChoices=!1,c))}async toChoice(e,r,s){if(typeof e==\"function\"&&(e=await e.call(this,this)),e instanceof Promise&&(e=await e),typeof e==\"string\"&&(e={name:e}),e.normalized)return e;e.normalized=!0;let a=e.value;if(e=Ywt(e.role,this.options)(this,e),typeof e.disabled==\"string\"&&!e.hint&&(e.hint=e.disabled,e.disabled=!0),e.disabled===!0&&e.hint==null&&(e.hint=\"(disabled)\"),e.index!=null)return e;e.name=e.name||e.key||e.title||e.value||e.message,e.message=e.message||e.name||\"\",e.value=[e.value,e.name].find(this.isValue.bind(this)),e.input=\"\",e.index=r,e.cursor=0,ZR.define(e,\"parent\",s),e.level=s?s.level+1:1,e.indent==null&&(e.indent=s?s.indent+\"  \":e.indent||\"\"),e.path=s?s.path+\".\"+e.name:e.name,e.enabled=!!(this.multiple&&!this.isDisabled(e)&&(e.enabled||this.isSelected(e))),this.isDisabled(e)||(this.longest=Math.max(this.longest,Gwt.unstyle(e.message).length));let c={...e};return e.reset=(f=c.input,p=c.value)=>{for(let h of Object.keys(c))e[h]=c[h];e.input=f,e.value=p},a==null&&typeof e.initial==\"function\"&&(e.input=await e.initial.call(this,this.state,e,r)),e}async onChoice(e,r){this.emit(\"choice\",e,r,this),typeof e.onChoice==\"function\"&&await e.onChoice.call(this,this.state,e,r)}async addChoice(e,r,s){let a=await this.toChoice(e,r,s);return this.choices.push(a),this.index=this.choices.length-1,this.limit=this.choices.length,a}async newItem(e,r,s){let a={name:\"New choice name?\",editable:!0,newChoice:!0,...e},n=await this.addChoice(a,r,s);return n.updateChoice=()=>{delete n.newChoice,n.name=n.message=n.input,n.input=\"\",n.cursor=0},this.render()}indent(e){return e.indent==null?e.level>1?\"  \".repeat(e.level-1):\"\":e.indent}dispatch(e,r){if(this.multiple&&this[r.name])return this[r.name]();this.alert()}focus(e,r){return typeof r!=\"boolean\"&&(r=e.enabled),r&&!e.enabled&&this.selected.length>=this.maxSelected?this.alert():(this.index=e.index,e.enabled=r&&!this.isDisabled(e),e)}space(){return this.multiple?(this.toggle(this.focused),this.render()):this.alert()}a(){if(this.maxSelected<this.choices.length)return this.alert();let e=this.selectable.every(r=>r.enabled);return this.choices.forEach(r=>r.enabled=!e),this.render()}i(){return this.choices.length-this.selected.length>this.maxSelected?this.alert():(this.choices.forEach(e=>e.enabled=!e.enabled),this.render())}g(e=this.focused){return this.choices.some(r=>!!r.parent)?(this.toggle(e.parent&&!e.choices?e.parent:e),this.render()):this.a()}toggle(e,r){if(!e.enabled&&this.selected.length>=this.maxSelected)return this.alert();typeof r!=\"boolean\"&&(r=!e.enabled),e.enabled=r,e.choices&&e.choices.forEach(a=>this.toggle(a,r));let s=e.parent;for(;s;){let a=s.choices.filter(n=>this.isDisabled(n));s.enabled=a.every(n=>n.enabled===!0),s=s.parent}return Qwe(this,this.choices),this.emit(\"toggle\",e,this),e}enable(e){return this.selected.length>=this.maxSelected?this.alert():(e.enabled=!this.isDisabled(e),e.choices&&e.choices.forEach(this.enable.bind(this)),e)}disable(e){return e.enabled=!1,e.choices&&e.choices.forEach(this.disable.bind(this)),e}number(e){this.num+=e;let r=s=>{let a=Number(s);if(a>this.choices.length-1)return this.alert();let n=this.focused,c=this.choices.find(f=>a===f.index);if(!c.enabled&&this.selected.length>=this.maxSelected)return this.alert();if(this.visible.indexOf(c)===-1){let f=LG(this.choices),p=f.indexOf(c);if(n.index>p){let h=f.slice(p,p+this.limit),E=f.filter(C=>!h.includes(C));this.choices=h.concat(E)}else{let h=p-this.limit+1;this.choices=f.slice(h).concat(f.slice(0,h))}}return this.index=this.choices.indexOf(c),this.toggle(this.focused),this.render()};return clearTimeout(this.numberTimeout),new Promise(s=>{let a=this.choices.length,n=this.num,c=(f=!1,p)=>{clearTimeout(this.numberTimeout),f&&(p=r(n)),this.num=\"\",s(p)};if(n===\"0\"||n.length===1&&+(n+\"0\")>a)return c(!0);if(Number(n)>a)return c(!1,this.alert());this.numberTimeout=setTimeout(()=>c(!0),this.delay)})}home(){return this.choices=LG(this.choices),this.index=0,this.render()}end(){let e=this.choices.length-this.limit,r=LG(this.choices);return this.choices=r.slice(e).concat(r.slice(0,e)),this.index=this.limit-1,this.render()}first(){return this.index=0,this.render()}last(){return this.index=this.visible.length-1,this.render()}prev(){return this.visible.length<=1?this.alert():this.up()}next(){return this.visible.length<=1?this.alert():this.down()}right(){return this.cursor>=this.input.length?this.alert():(this.cursor++,this.render())}left(){return this.cursor<=0?this.alert():(this.cursor--,this.render())}up(){let e=this.choices.length,r=this.visible.length,s=this.index;return this.options.scroll===!1&&s===0?this.alert():e>r&&s===0?this.scrollUp():(this.index=(s-1%e+e)%e,this.isDisabled()?this.up():this.render())}down(){let e=this.choices.length,r=this.visible.length,s=this.index;return this.options.scroll===!1&&s===r-1?this.alert():e>r&&s===r-1?this.scrollDown():(this.index=(s+1)%e,this.isDisabled()?this.down():this.render())}scrollUp(e=0){return this.choices=Vwt(this.choices),this.index=e,this.isDisabled()?this.up():this.render()}scrollDown(e=this.visible.length-1){return this.choices=Kwt(this.choices),this.index=e,this.isDisabled()?this.down():this.render()}async shiftUp(){if(this.options.sort===!0){this.sorting=!0,this.swap(this.index-1),await this.up(),this.sorting=!1;return}return this.scrollUp(this.index)}async shiftDown(){if(this.options.sort===!0){this.sorting=!0,this.swap(this.index+1),await this.down(),this.sorting=!1;return}return this.scrollDown(this.index)}pageUp(){return this.visible.length<=1?this.alert():(this.limit=Math.max(this.limit-1,0),this.index=Math.min(this.limit-1,this.index),this._limit=this.limit,this.isDisabled()?this.up():this.render())}pageDown(){return this.visible.length>=this.choices.length?this.alert():(this.index=Math.max(0,this.index),this.limit=Math.min(this.limit+1,this.choices.length),this._limit=this.limit,this.isDisabled()?this.down():this.render())}swap(e){Jwt(this.choices,this.index,e)}isDisabled(e=this.focused){return e&&[\"disabled\",\"collapsed\",\"hidden\",\"completing\",\"readonly\"].some(s=>e[s]===!0)?!0:e&&e.role===\"heading\"}isEnabled(e=this.focused){if(Array.isArray(e))return e.every(r=>this.isEnabled(r));if(e.choices){let r=e.choices.filter(s=>!this.isDisabled(s));return e.enabled&&r.every(s=>this.isEnabled(s))}return e.enabled&&!this.isDisabled(e)}isChoice(e,r){return e.name===r||e.index===Number(r)}isSelected(e){return Array.isArray(this.initial)?this.initial.some(r=>this.isChoice(e,r)):this.isChoice(e,this.initial)}map(e=[],r=\"value\"){return[].concat(e||[]).reduce((s,a)=>(s[a]=this.find(a,r),s),{})}filter(e,r){let a=typeof e==\"function\"?e:(f,p)=>[f.name,p].includes(e),c=(this.options.multiple?this.state._choices:this.choices).filter(a);return r?c.map(f=>f[r]):c}find(e,r){if(kwe(e))return r?e[r]:e;let a=typeof e==\"function\"?e:(c,f)=>[c.name,f].includes(e),n=this.choices.find(a);if(n)return r?n[r]:n}findIndex(e){return this.choices.indexOf(this.find(e))}async submit(){let e=this.focused;if(!e)return this.alert();if(e.newChoice)return e.input?(e.updateChoice(),this.render()):this.alert();if(this.choices.some(c=>c.newChoice))return this.alert();let{reorder:r,sort:s}=this.options,a=this.multiple===!0,n=this.selected;return n===void 0?this.alert():(Array.isArray(n)&&r!==!1&&s!==!0&&(n=ZR.reorder(n)),this.value=a?n.map(c=>c.name):n.name,super.submit())}set choices(e=[]){this.state._choices=this.state._choices||[],this.state.choices=e;for(let r of e)this.state._choices.some(s=>s.name===r.name)||this.state._choices.push(r);if(!this._initial&&this.options.initial){this._initial=!0;let r=this.initial;if(typeof r==\"string\"||typeof r==\"number\"){let s=this.find(r);s&&(this.initial=s.index,this.focus(s,!0))}}}get choices(){return Qwe(this,this.state.choices||[])}set visible(e){this.state.visible=e}get visible(){return(this.state.visible||this.choices).slice(0,this.limit)}set limit(e){this.state.limit=e}get limit(){let{state:e,options:r,choices:s}=this,a=e.limit||this._limit||r.limit||s.length;return Math.min(a,this.height)}set value(e){super.value=e}get value(){return typeof super.value!=\"string\"&&super.value===this.initial?this.input:super.value}set index(e){this.state.index=e}get index(){return Math.max(0,this.state?this.state.index:0)}get enabled(){return this.filter(this.isEnabled.bind(this))}get focused(){let e=this.choices[this.index];return e&&this.state.submitted&&this.multiple!==!0&&(e.enabled=!0),e}get selectable(){return this.choices.filter(e=>!this.isDisabled(e))}get selected(){return this.multiple?this.enabled:this.focused}};function Qwe(t,e){if(e instanceof Promise)return e;if(typeof e==\"function\"){if(ZR.isAsyncFn(e))return e;e=e.call(t,t)}for(let r of e){if(Array.isArray(r.choices)){let s=r.choices.filter(a=>!t.isDisabled(a));r.enabled=s.every(a=>a.enabled===!0)}t.isDisabled(r)===!0&&delete r.enabled}return e}Twe.exports=MG});var j0=L((Nrr,Rwe)=>{\"use strict\";var zwt=tS(),_G=$o(),UG=class extends zwt{constructor(e){super(e),this.emptyError=this.options.emptyError||\"No items were selected\"}async dispatch(e,r){if(this.multiple)return this[r.name]?await this[r.name](e,r):await super.dispatch(e,r);this.alert()}separator(){if(this.options.separator)return super.separator();let e=this.styles.muted(this.symbols.ellipsis);return this.state.submitted?super.separator():e}pointer(e,r){return!this.multiple||this.options.pointer?super.pointer(e,r):\"\"}indicator(e,r){return this.multiple?super.indicator(e,r):\"\"}choiceMessage(e,r){let s=this.resolve(e.message,this.state,e,r);return e.role===\"heading\"&&!_G.hasColor(s)&&(s=this.styles.strong(s)),this.resolve(s,this.state,e,r)}choiceSeparator(){return\":\"}async renderChoice(e,r){await this.onChoice(e,r);let s=this.index===r,a=await this.pointer(e,r),n=await this.indicator(e,r)+(e.pad||\"\"),c=await this.resolve(e.hint,this.state,e,r);c&&!_G.hasColor(c)&&(c=this.styles.muted(c));let f=this.indent(e),p=await this.choiceMessage(e,r),h=()=>[this.margin[3],f+a+n,p,this.margin[1],c].filter(Boolean).join(\" \");return e.role===\"heading\"?h():e.disabled?(_G.hasColor(p)||(p=this.styles.disabled(p)),h()):(s&&(p=this.styles.em(p)),h())}async renderChoices(){if(this.state.loading===\"choices\")return this.styles.warning(\"Loading choices\");if(this.state.submitted)return\"\";let e=this.visible.map(async(n,c)=>await this.renderChoice(n,c)),r=await Promise.all(e);r.length||r.push(this.styles.danger(\"No matching choices\"));let s=this.margin[0]+r.join(`\n`),a;return this.options.choicesHeader&&(a=await this.resolve(this.options.choicesHeader,this.state)),[a,s].filter(Boolean).join(`\n`)}format(){return!this.state.submitted||this.state.cancelled?\"\":Array.isArray(this.selected)?this.selected.map(e=>this.styles.primary(e.name)).join(\", \"):this.styles.primary(this.selected.name)}async render(){let{submitted:e,size:r}=this.state,s=\"\",a=await this.header(),n=await this.prefix(),c=await this.separator(),f=await this.message();this.options.promptLine!==!1&&(s=[n,f,c,\"\"].join(\" \"),this.state.prompt=s);let p=await this.format(),h=await this.error()||await this.hint(),E=await this.renderChoices(),C=await this.footer();p&&(s+=p),h&&!s.includes(h)&&(s+=\" \"+h),e&&!p&&!E.trim()&&this.multiple&&this.emptyError!=null&&(s+=this.styles.danger(this.emptyError)),this.clear(r),this.write([a,s,E,C].filter(Boolean).join(`\n`)),this.write(this.margin[2]),this.restore()}};Rwe.exports=UG});var Nwe=L((Orr,Fwe)=>{\"use strict\";var Zwt=j0(),Xwt=(t,e)=>{let r=t.toLowerCase();return s=>{let n=s.toLowerCase().indexOf(r),c=e(s.slice(n,n+r.length));return n>=0?s.slice(0,n)+c+s.slice(n+r.length):s}},HG=class extends Zwt{constructor(e){super(e),this.cursorShow()}moveCursor(e){this.state.cursor+=e}dispatch(e){return this.append(e)}space(e){return this.options.multiple?super.space(e):this.append(e)}append(e){let{cursor:r,input:s}=this.state;return this.input=s.slice(0,r)+e+s.slice(r),this.moveCursor(1),this.complete()}delete(){let{cursor:e,input:r}=this.state;return r?(this.input=r.slice(0,e-1)+r.slice(e),this.moveCursor(-1),this.complete()):this.alert()}deleteForward(){let{cursor:e,input:r}=this.state;return r[e]===void 0?this.alert():(this.input=`${r}`.slice(0,e)+`${r}`.slice(e+1),this.complete())}number(e){return this.append(e)}async complete(){this.completing=!0,this.choices=await this.suggest(this.input,this.state._choices),this.state.limit=void 0,this.index=Math.min(Math.max(this.visible.length-1,0),this.index),await this.render(),this.completing=!1}suggest(e=this.input,r=this.state._choices){if(typeof this.options.suggest==\"function\")return this.options.suggest.call(this,e,r);let s=e.toLowerCase();return r.filter(a=>a.message.toLowerCase().includes(s))}pointer(){return\"\"}format(){if(!this.focused)return this.input;if(this.options.multiple&&this.state.submitted)return this.selected.map(e=>this.styles.primary(e.message)).join(\", \");if(this.state.submitted){let e=this.value=this.input=this.focused.value;return this.styles.primary(e)}return this.input}async render(){if(this.state.status!==\"pending\")return super.render();let e=this.options.highlight?this.options.highlight.bind(this):this.styles.placeholder,r=Xwt(this.input,e),s=this.choices;this.choices=s.map(a=>({...a,message:r(a.message)})),await super.render(),this.choices=s}submit(){return this.options.multiple&&(this.value=this.selected.map(e=>e.name)),super.submit()}};Fwe.exports=HG});var qG=L((Lrr,Owe)=>{\"use strict\";var jG=$o();Owe.exports=(t,e={})=>{t.cursorHide();let{input:r=\"\",initial:s=\"\",pos:a,showCursor:n=!0,color:c}=e,f=c||t.styles.placeholder,p=jG.inverse(t.styles.primary),h=R=>p(t.styles.black(R)),E=r,C=\" \",S=h(C);if(t.blink&&t.blink.off===!0&&(h=R=>R,S=\"\"),n&&a===0&&s===\"\"&&r===\"\")return h(C);if(n&&a===0&&(r===s||r===\"\"))return h(s[0])+f(s.slice(1));s=jG.isPrimitive(s)?`${s}`:\"\",r=jG.isPrimitive(r)?`${r}`:\"\";let P=s&&s.startsWith(r)&&s!==r,I=P?h(s[r.length]):S;if(a!==r.length&&n===!0&&(E=r.slice(0,a)+h(r[a])+r.slice(a+1),I=\"\"),n===!1&&(I=\"\"),P){let R=t.styles.unstyle(E+I);return E+I+f(s.slice(R.length))}return E+I}});var XR=L((Mrr,Lwe)=>{\"use strict\";var $wt=Ju(),e1t=j0(),t1t=qG(),GG=class extends e1t{constructor(e){super({...e,multiple:!0}),this.type=\"form\",this.initial=this.options.initial,this.align=[this.options.align,\"right\"].find(r=>r!=null),this.emptyError=\"\",this.values={}}async reset(e){return await super.reset(),e===!0&&(this._index=this.index),this.index=this._index,this.values={},this.choices.forEach(r=>r.reset&&r.reset()),this.render()}dispatch(e){return!!e&&this.append(e)}append(e){let r=this.focused;if(!r)return this.alert();let{cursor:s,input:a}=r;return r.value=r.input=a.slice(0,s)+e+a.slice(s),r.cursor++,this.render()}delete(){let e=this.focused;if(!e||e.cursor<=0)return this.alert();let{cursor:r,input:s}=e;return e.value=e.input=s.slice(0,r-1)+s.slice(r),e.cursor--,this.render()}deleteForward(){let e=this.focused;if(!e)return this.alert();let{cursor:r,input:s}=e;if(s[r]===void 0)return this.alert();let a=`${s}`.slice(0,r)+`${s}`.slice(r+1);return e.value=e.input=a,this.render()}right(){let e=this.focused;return e?e.cursor>=e.input.length?this.alert():(e.cursor++,this.render()):this.alert()}left(){let e=this.focused;return e?e.cursor<=0?this.alert():(e.cursor--,this.render()):this.alert()}space(e,r){return this.dispatch(e,r)}number(e,r){return this.dispatch(e,r)}next(){let e=this.focused;if(!e)return this.alert();let{initial:r,input:s}=e;return r&&r.startsWith(s)&&s!==r?(e.value=e.input=r,e.cursor=e.value.length,this.render()):super.next()}prev(){let e=this.focused;return e?e.cursor===0?super.prev():(e.value=e.input=\"\",e.cursor=0,this.render()):this.alert()}separator(){return\"\"}format(e){return this.state.submitted?\"\":super.format(e)}pointer(){return\"\"}indicator(e){return e.input?\"\\u29BF\":\"\\u2299\"}async choiceSeparator(e,r){let s=await this.resolve(e.separator,this.state,e,r)||\":\";return s?\" \"+this.styles.disabled(s):\"\"}async renderChoice(e,r){await this.onChoice(e,r);let{state:s,styles:a}=this,{cursor:n,initial:c=\"\",name:f,hint:p,input:h=\"\"}=e,{muted:E,submitted:C,primary:S,danger:P}=a,I=p,R=this.index===r,N=e.validate||(()=>!0),U=await this.choiceSeparator(e,r),W=e.message;this.align===\"right\"&&(W=W.padStart(this.longest+1,\" \")),this.align===\"left\"&&(W=W.padEnd(this.longest+1,\" \"));let te=this.values[f]=h||c,ie=h?\"success\":\"dark\";await N.call(e,te,this.state)!==!0&&(ie=\"danger\");let Ae=a[ie],ce=Ae(await this.indicator(e,r))+(e.pad||\"\"),me=this.indent(e),pe=()=>[me,ce,W+U,h,I].filter(Boolean).join(\" \");if(s.submitted)return W=$wt.unstyle(W),h=C(h),I=\"\",pe();if(e.format)h=await e.format.call(this,h,e,r);else{let Be=this.styles.muted;h=t1t(this,{input:h,initial:c,pos:n,showCursor:R,color:Be})}return this.isValue(h)||(h=this.styles.muted(this.symbols.ellipsis)),e.result&&(this.values[f]=await e.result.call(this,te,e,r)),R&&(W=S(W)),e.error?h+=(h?\" \":\"\")+P(e.error.trim()):e.hint&&(h+=(h?\" \":\"\")+E(e.hint.trim())),pe()}async submit(){return this.value=this.values,super.base.submit.call(this)}};Lwe.exports=GG});var WG=L((_rr,_we)=>{\"use strict\";var r1t=XR(),n1t=()=>{throw new Error(\"expected prompt to have a custom authenticate method\")},Mwe=(t=n1t)=>{class e extends r1t{constructor(s){super(s)}async submit(){this.value=await t.call(this,this.values,this.state),super.base.submit.call(this)}static create(s){return Mwe(s)}}return e};_we.exports=Mwe()});var jwe=L((Urr,Hwe)=>{\"use strict\";var i1t=WG();function s1t(t,e){return t.username===this.options.username&&t.password===this.options.password}var Uwe=(t=s1t)=>{let e=[{name:\"username\",message:\"username\"},{name:\"password\",message:\"password\",format(s){return this.options.showPassword?s:(this.state.submitted?this.styles.primary:this.styles.muted)(this.symbols.asterisk.repeat(s.length))}}];class r extends i1t.create(t){constructor(a){super({...a,choices:e})}static create(a){return Uwe(a)}}return r};Hwe.exports=Uwe()});var $R=L((Hrr,qwe)=>{\"use strict\";var o1t=nC(),{isPrimitive:a1t,hasColor:l1t}=$o(),YG=class extends o1t{constructor(e){super(e),this.cursorHide()}async initialize(){let e=await this.resolve(this.initial,this.state);this.input=await this.cast(e),await super.initialize()}dispatch(e){return this.isValue(e)?(this.input=e,this.submit()):this.alert()}format(e){let{styles:r,state:s}=this;return s.submitted?r.success(e):r.primary(e)}cast(e){return this.isTrue(e)}isTrue(e){return/^[ty1]/i.test(e)}isFalse(e){return/^[fn0]/i.test(e)}isValue(e){return a1t(e)&&(this.isTrue(e)||this.isFalse(e))}async hint(){if(this.state.status===\"pending\"){let e=await this.element(\"hint\");return l1t(e)?e:this.styles.muted(e)}}async render(){let{input:e,size:r}=this.state,s=await this.prefix(),a=await this.separator(),n=await this.message(),c=this.styles.muted(this.default),f=[s,n,c,a].filter(Boolean).join(\" \");this.state.prompt=f;let p=await this.header(),h=this.value=this.cast(e),E=await this.format(h),C=await this.error()||await this.hint(),S=await this.footer();C&&!f.includes(C)&&(E+=\" \"+C),f+=\" \"+E,this.clear(r),this.write([p,f,S].filter(Boolean).join(`\n`)),this.restore()}set value(e){super.value=e}get value(){return this.cast(super.value)}};qwe.exports=YG});var Wwe=L((jrr,Gwe)=>{\"use strict\";var c1t=$R(),VG=class extends c1t{constructor(e){super(e),this.default=this.options.default||(this.initial?\"(Y/n)\":\"(y/N)\")}};Gwe.exports=VG});var Vwe=L((qrr,Ywe)=>{\"use strict\";var u1t=j0(),f1t=XR(),iC=f1t.prototype,KG=class extends u1t{constructor(e){super({...e,multiple:!0}),this.align=[this.options.align,\"left\"].find(r=>r!=null),this.emptyError=\"\",this.values={}}dispatch(e,r){let s=this.focused,a=s.parent||{};return!s.editable&&!a.editable&&(e===\"a\"||e===\"i\")?super[e]():iC.dispatch.call(this,e,r)}append(e,r){return iC.append.call(this,e,r)}delete(e,r){return iC.delete.call(this,e,r)}space(e){return this.focused.editable?this.append(e):super.space()}number(e){return this.focused.editable?this.append(e):super.number(e)}next(){return this.focused.editable?iC.next.call(this):super.next()}prev(){return this.focused.editable?iC.prev.call(this):super.prev()}async indicator(e,r){let s=e.indicator||\"\",a=e.editable?s:super.indicator(e,r);return await this.resolve(a,this.state,e,r)||\"\"}indent(e){return e.role===\"heading\"?\"\":e.editable?\" \":\"  \"}async renderChoice(e,r){return e.indent=\"\",e.editable?iC.renderChoice.call(this,e,r):super.renderChoice(e,r)}error(){return\"\"}footer(){return this.state.error}async validate(){let e=!0;for(let r of this.choices){if(typeof r.validate!=\"function\"||r.role===\"heading\")continue;let s=r.parent?this.value[r.parent.name]:this.value;if(r.editable?s=r.value===r.name?r.initial||\"\":r.value:this.isDisabled(r)||(s=r.enabled===!0),e=await r.validate(s,this.state),e!==!0)break}return e!==!0&&(this.state.error=typeof e==\"string\"?e:\"Invalid Input\"),e}submit(){if(this.focused.newChoice===!0)return super.submit();if(this.choices.some(e=>e.newChoice))return this.alert();this.value={};for(let e of this.choices){let r=e.parent?this.value[e.parent.name]:this.value;if(e.role===\"heading\"){this.value[e.name]={};continue}e.editable?r[e.name]=e.value===e.name?e.initial||\"\":e.value:this.isDisabled(e)||(r[e.name]=e.enabled===!0)}return this.base.submit.call(this)}};Ywe.exports=KG});var Bm=L((Grr,Kwe)=>{\"use strict\";var A1t=nC(),p1t=qG(),{isPrimitive:h1t}=$o(),JG=class extends A1t{constructor(e){super(e),this.initial=h1t(this.initial)?String(this.initial):\"\",this.initial&&this.cursorHide(),this.state.prevCursor=0,this.state.clipboard=[]}async keypress(e,r={}){let s=this.state.prevKeypress;return this.state.prevKeypress=r,this.options.multiline===!0&&r.name===\"return\"&&(!s||s.name!==\"return\")?this.append(`\n`,r):super.keypress(e,r)}moveCursor(e){this.cursor+=e}reset(){return this.input=this.value=\"\",this.cursor=0,this.render()}dispatch(e,r){if(!e||r.ctrl||r.code)return this.alert();this.append(e)}append(e){let{cursor:r,input:s}=this.state;this.input=`${s}`.slice(0,r)+e+`${s}`.slice(r),this.moveCursor(String(e).length),this.render()}insert(e){this.append(e)}delete(){let{cursor:e,input:r}=this.state;if(e<=0)return this.alert();this.input=`${r}`.slice(0,e-1)+`${r}`.slice(e),this.moveCursor(-1),this.render()}deleteForward(){let{cursor:e,input:r}=this.state;if(r[e]===void 0)return this.alert();this.input=`${r}`.slice(0,e)+`${r}`.slice(e+1),this.render()}cutForward(){let e=this.cursor;if(this.input.length<=e)return this.alert();this.state.clipboard.push(this.input.slice(e)),this.input=this.input.slice(0,e),this.render()}cutLeft(){let e=this.cursor;if(e===0)return this.alert();let r=this.input.slice(0,e),s=this.input.slice(e),a=r.split(\" \");this.state.clipboard.push(a.pop()),this.input=a.join(\" \"),this.cursor=this.input.length,this.input+=s,this.render()}paste(){if(!this.state.clipboard.length)return this.alert();this.insert(this.state.clipboard.pop()),this.render()}toggleCursor(){this.state.prevCursor?(this.cursor=this.state.prevCursor,this.state.prevCursor=0):(this.state.prevCursor=this.cursor,this.cursor=0),this.render()}first(){this.cursor=0,this.render()}last(){this.cursor=this.input.length-1,this.render()}next(){let e=this.initial!=null?String(this.initial):\"\";if(!e||!e.startsWith(this.input))return this.alert();this.input=this.initial,this.cursor=this.initial.length,this.render()}prev(){if(!this.input)return this.alert();this.reset()}backward(){return this.left()}forward(){return this.right()}right(){return this.cursor>=this.input.length?this.alert():(this.moveCursor(1),this.render())}left(){return this.cursor<=0?this.alert():(this.moveCursor(-1),this.render())}isValue(e){return!!e}async format(e=this.value){let r=await this.resolve(this.initial,this.state);return this.state.submitted?this.styles.submitted(e||r):p1t(this,{input:e,initial:r,pos:this.cursor})}async render(){let e=this.state.size,r=await this.prefix(),s=await this.separator(),a=await this.message(),n=[r,a,s].filter(Boolean).join(\" \");this.state.prompt=n;let c=await this.header(),f=await this.format(),p=await this.error()||await this.hint(),h=await this.footer();p&&!f.includes(p)&&(f+=\" \"+p),n+=\" \"+f,this.clear(e),this.write([c,n,h].filter(Boolean).join(`\n`)),this.restore()}};Kwe.exports=JG});var zwe=L((Wrr,Jwe)=>{\"use strict\";var g1t=t=>t.filter((e,r)=>t.lastIndexOf(e)===r),eF=t=>g1t(t).filter(Boolean);Jwe.exports=(t,e={},r=\"\")=>{let{past:s=[],present:a=\"\"}=e,n,c;switch(t){case\"prev\":case\"undo\":return n=s.slice(0,s.length-1),c=s[s.length-1]||\"\",{past:eF([r,...n]),present:c};case\"next\":case\"redo\":return n=s.slice(1),c=s[0]||\"\",{past:eF([...n,r]),present:c};case\"save\":return{past:eF([...s,r]),present:\"\"};case\"remove\":return c=eF(s.filter(f=>f!==r)),a=\"\",c.length&&(a=c.pop()),{past:c,present:a};default:throw new Error(`Invalid action: \"${t}\"`)}}});var ZG=L((Yrr,Xwe)=>{\"use strict\";var d1t=Bm(),Zwe=zwe(),zG=class extends d1t{constructor(e){super(e);let r=this.options.history;if(r&&r.store){let s=r.values||this.initial;this.autosave=!!r.autosave,this.store=r.store,this.data=this.store.get(\"values\")||{past:[],present:s},this.initial=this.data.present||this.data.past[this.data.past.length-1]}}completion(e){return this.store?(this.data=Zwe(e,this.data,this.input),this.data.present?(this.input=this.data.present,this.cursor=this.input.length,this.render()):this.alert()):this.alert()}altUp(){return this.completion(\"prev\")}altDown(){return this.completion(\"next\")}prev(){return this.save(),super.prev()}save(){this.store&&(this.data=Zwe(\"save\",this.data,this.input),this.store.set(\"values\",this.data))}submit(){return this.store&&this.autosave===!0&&this.save(),super.submit()}};Xwe.exports=zG});var e1e=L((Vrr,$we)=>{\"use strict\";var m1t=Bm(),XG=class extends m1t{format(){return\"\"}};$we.exports=XG});var r1e=L((Krr,t1e)=>{\"use strict\";var y1t=Bm(),$G=class extends y1t{constructor(e={}){super(e),this.sep=this.options.separator||/, */,this.initial=e.initial||\"\"}split(e=this.value){return e?String(e).split(this.sep):[]}format(){let e=this.state.submitted?this.styles.primary:r=>r;return this.list.map(e).join(\", \")}async submit(e){let r=this.state.error||await this.validate(this.list,this.state);return r!==!0?(this.state.error=r,super.submit()):(this.value=this.list,super.submit())}get list(){return this.split()}};t1e.exports=$G});var i1e=L((Jrr,n1e)=>{\"use strict\";var E1t=j0(),e5=class extends E1t{constructor(e){super({...e,multiple:!0})}};n1e.exports=e5});var r5=L((zrr,s1e)=>{\"use strict\";var I1t=Bm(),t5=class extends I1t{constructor(e={}){super({style:\"number\",...e}),this.min=this.isValue(e.min)?this.toNumber(e.min):-1/0,this.max=this.isValue(e.max)?this.toNumber(e.max):1/0,this.delay=e.delay!=null?e.delay:1e3,this.float=e.float!==!1,this.round=e.round===!0||e.float===!1,this.major=e.major||10,this.minor=e.minor||1,this.initial=e.initial!=null?e.initial:\"\",this.input=String(this.initial),this.cursor=this.input.length,this.cursorShow()}append(e){return!/[-+.]/.test(e)||e===\".\"&&this.input.includes(\".\")?this.alert(\"invalid number\"):super.append(e)}number(e){return super.append(e)}next(){return this.input&&this.input!==this.initial?this.alert():this.isValue(this.initial)?(this.input=this.initial,this.cursor=String(this.initial).length,this.render()):this.alert()}up(e){let r=e||this.minor,s=this.toNumber(this.input);return s>this.max+r?this.alert():(this.input=`${s+r}`,this.render())}down(e){let r=e||this.minor,s=this.toNumber(this.input);return s<this.min-r?this.alert():(this.input=`${s-r}`,this.render())}shiftDown(){return this.down(this.major)}shiftUp(){return this.up(this.major)}format(e=this.input){return typeof this.options.format==\"function\"?this.options.format.call(this,e):this.styles.info(e)}toNumber(e=\"\"){return this.float?+e:Math.round(+e)}isValue(e){return/^[-+]?[0-9]+((\\.)|(\\.[0-9]+))?$/.test(e)}submit(){let e=[this.input,this.initial].find(r=>this.isValue(r));return this.value=this.toNumber(e||0),super.submit()}};s1e.exports=t5});var a1e=L((Zrr,o1e)=>{o1e.exports=r5()});var c1e=L((Xrr,l1e)=>{\"use strict\";var C1t=Bm(),n5=class extends C1t{constructor(e){super(e),this.cursorShow()}format(e=this.input){return this.keypressed?(this.state.submitted?this.styles.primary:this.styles.muted)(this.symbols.asterisk.repeat(e.length)):\"\"}};l1e.exports=n5});var A1e=L(($rr,f1e)=>{\"use strict\";var w1t=Ju(),B1t=tS(),u1e=$o(),i5=class extends B1t{constructor(e={}){super(e),this.widths=[].concat(e.messageWidth||50),this.align=[].concat(e.align||\"left\"),this.linebreak=e.linebreak||!1,this.edgeLength=e.edgeLength||3,this.newline=e.newline||`\n   `;let r=e.startNumber||1;typeof this.scale==\"number\"&&(this.scaleKey=!1,this.scale=Array(this.scale).fill(0).map((s,a)=>({name:a+r})))}async reset(){return this.tableized=!1,await super.reset(),this.render()}tableize(){if(this.tableized===!0)return;this.tableized=!0;let e=0;for(let r of this.choices){e=Math.max(e,r.message.length),r.scaleIndex=r.initial||2,r.scale=[];for(let s=0;s<this.scale.length;s++)r.scale.push({index:s})}this.widths[0]=Math.min(this.widths[0],e+3)}async dispatch(e,r){if(this.multiple)return this[r.name]?await this[r.name](e,r):await super.dispatch(e,r);this.alert()}heading(e,r,s){return this.styles.strong(e)}separator(){return this.styles.muted(this.symbols.ellipsis)}right(){let e=this.focused;return e.scaleIndex>=this.scale.length-1?this.alert():(e.scaleIndex++,this.render())}left(){let e=this.focused;return e.scaleIndex<=0?this.alert():(e.scaleIndex--,this.render())}indent(){return\"\"}format(){return this.state.submitted?this.choices.map(r=>this.styles.info(r.index)).join(\", \"):\"\"}pointer(){return\"\"}renderScaleKey(){return this.scaleKey===!1||this.state.submitted?\"\":[\"\",...this.scale.map(s=>`   ${s.name} - ${s.message}`)].map(s=>this.styles.muted(s)).join(`\n`)}renderScaleHeading(e){let r=this.scale.map(p=>p.name);typeof this.options.renderScaleHeading==\"function\"&&(r=this.options.renderScaleHeading.call(this,e));let s=this.scaleLength-r.join(\"\").length,a=Math.round(s/(r.length-1)),c=r.map(p=>this.styles.strong(p)).join(\" \".repeat(a)),f=\" \".repeat(this.widths[0]);return this.margin[3]+f+this.margin[1]+c}scaleIndicator(e,r,s){if(typeof this.options.scaleIndicator==\"function\")return this.options.scaleIndicator.call(this,e,r,s);let a=e.scaleIndex===r.index;return r.disabled?this.styles.hint(this.symbols.radio.disabled):a?this.styles.success(this.symbols.radio.on):this.symbols.radio.off}renderScale(e,r){let s=e.scale.map(n=>this.scaleIndicator(e,n,r)),a=this.term===\"Hyper\"?\"\":\" \";return s.join(a+this.symbols.line.repeat(this.edgeLength))}async renderChoice(e,r){await this.onChoice(e,r);let s=this.index===r,a=await this.pointer(e,r),n=await e.hint;n&&!u1e.hasColor(n)&&(n=this.styles.muted(n));let c=I=>this.margin[3]+I.replace(/\\s+$/,\"\").padEnd(this.widths[0],\" \"),f=this.newline,p=this.indent(e),h=await this.resolve(e.message,this.state,e,r),E=await this.renderScale(e,r),C=this.margin[1]+this.margin[3];this.scaleLength=w1t.unstyle(E).length,this.widths[0]=Math.min(this.widths[0],this.width-this.scaleLength-C.length);let P=u1e.wordWrap(h,{width:this.widths[0],newline:f}).split(`\n`).map(I=>c(I)+this.margin[1]);return s&&(E=this.styles.info(E),P=P.map(I=>this.styles.info(I))),P[0]+=E,this.linebreak&&P.push(\"\"),[p+a,P.join(`\n`)].filter(Boolean)}async renderChoices(){if(this.state.submitted)return\"\";this.tableize();let e=this.visible.map(async(a,n)=>await this.renderChoice(a,n)),r=await Promise.all(e),s=await this.renderScaleHeading();return this.margin[0]+[s,...r.map(a=>a.join(\" \"))].join(`\n`)}async render(){let{submitted:e,size:r}=this.state,s=await this.prefix(),a=await this.separator(),n=await this.message(),c=\"\";this.options.promptLine!==!1&&(c=[s,n,a,\"\"].join(\" \"),this.state.prompt=c);let f=await this.header(),p=await this.format(),h=await this.renderScaleKey(),E=await this.error()||await this.hint(),C=await this.renderChoices(),S=await this.footer(),P=this.emptyError;p&&(c+=p),E&&!c.includes(E)&&(c+=\" \"+E),e&&!p&&!C.trim()&&this.multiple&&P!=null&&(c+=this.styles.danger(P)),this.clear(r),this.write([f,c,h,C,S].filter(Boolean).join(`\n`)),this.state.submitted||this.write(this.margin[2]),this.restore()}submit(){this.value={};for(let e of this.choices)this.value[e.name]=e.scaleIndex;return this.base.submit.call(this)}};f1e.exports=i5});var g1e=L((enr,h1e)=>{\"use strict\";var p1e=Ju(),v1t=(t=\"\")=>typeof t==\"string\"?t.replace(/^['\"]|['\"]$/g,\"\"):\"\",o5=class{constructor(e){this.name=e.key,this.field=e.field||{},this.value=v1t(e.initial||this.field.initial||\"\"),this.message=e.message||this.name,this.cursor=0,this.input=\"\",this.lines=[]}},S1t=async(t={},e={},r=s=>s)=>{let s=new Set,a=t.fields||[],n=t.template,c=[],f=[],p=[],h=1;typeof n==\"function\"&&(n=await n());let E=-1,C=()=>n[++E],S=()=>n[E+1],P=I=>{I.line=h,c.push(I)};for(P({type:\"bos\",value:\"\"});E<n.length-1;){let I=C();if(/^[^\\S\\n ]$/.test(I)){P({type:\"text\",value:I});continue}if(I===`\n`){P({type:\"newline\",value:I}),h++;continue}if(I===\"\\\\\"){I+=C(),P({type:\"text\",value:I});continue}if((I===\"$\"||I===\"#\"||I===\"{\")&&S()===\"{\"){let N=C();I+=N;let U={type:\"template\",open:I,inner:\"\",close:\"\",value:I},W;for(;W=C();){if(W===\"}\"){S()===\"}\"&&(W+=C()),U.value+=W,U.close=W;break}W===\":\"?(U.initial=\"\",U.key=U.inner):U.initial!==void 0&&(U.initial+=W),U.value+=W,U.inner+=W}U.template=U.open+(U.initial||U.inner)+U.close,U.key=U.key||U.inner,e.hasOwnProperty(U.key)&&(U.initial=e[U.key]),U=r(U),P(U),p.push(U.key),s.add(U.key);let te=f.find(ie=>ie.name===U.key);U.field=a.find(ie=>ie.name===U.key),te||(te=new o5(U),f.push(te)),te.lines.push(U.line-1);continue}let R=c[c.length-1];R.type===\"text\"&&R.line===h?R.value+=I:P({type:\"text\",value:I})}return P({type:\"eos\",value:\"\"}),{input:n,tabstops:c,unique:s,keys:p,items:f}};h1e.exports=async t=>{let e=t.options,r=new Set(e.required===!0?[]:e.required||[]),s={...e.values,...e.initial},{tabstops:a,items:n,keys:c}=await S1t(e,s),f=s5(\"result\",t,e),p=s5(\"format\",t,e),h=s5(\"validate\",t,e,!0),E=t.isValue.bind(t);return async(C={},S=!1)=>{let P=0;C.required=r,C.items=n,C.keys=c,C.output=\"\";let I=async(W,te,ie,Ae)=>{let ce=await h(W,te,ie,Ae);return ce===!1?\"Invalid field \"+ie.name:ce};for(let W of a){let te=W.value,ie=W.key;if(W.type!==\"template\"){te&&(C.output+=te);continue}if(W.type===\"template\"){let Ae=n.find(Ce=>Ce.name===ie);e.required===!0&&C.required.add(Ae.name);let ce=[Ae.input,C.values[Ae.value],Ae.value,te].find(E),pe=(Ae.field||{}).message||W.inner;if(S){let Ce=await I(C.values[ie],C,Ae,P);if(Ce&&typeof Ce==\"string\"||Ce===!1){C.invalid.set(ie,Ce);continue}C.invalid.delete(ie);let g=await f(C.values[ie],C,Ae,P);C.output+=p1e.unstyle(g);continue}Ae.placeholder=!1;let Be=te;te=await p(te,C,Ae,P),ce!==te?(C.values[ie]=ce,te=t.styles.typing(ce),C.missing.delete(pe)):(C.values[ie]=void 0,ce=`<${pe}>`,te=t.styles.primary(ce),Ae.placeholder=!0,C.required.has(ie)&&C.missing.add(pe)),C.missing.has(pe)&&C.validating&&(te=t.styles.warning(ce)),C.invalid.has(ie)&&C.validating&&(te=t.styles.danger(ce)),P===C.index&&(Be!==te?te=t.styles.underline(te):te=t.styles.heading(p1e.unstyle(te))),P++}te&&(C.output+=te)}let R=C.output.split(`\n`).map(W=>\" \"+W),N=n.length,U=0;for(let W of n)C.invalid.has(W.name)&&W.lines.forEach(te=>{R[te][0]===\" \"&&(R[te]=C.styles.danger(C.symbols.bullet)+R[te].slice(1))}),t.isValue(C.values[W.name])&&U++;return C.completed=(U/N*100).toFixed(0),C.output=R.join(`\n`),C.output}};function s5(t,e,r,s){return(a,n,c,f)=>typeof c.field[t]==\"function\"?c.field[t].call(e,a,n,c,f):[s,a].find(p=>e.isValue(p))}});var m1e=L((tnr,d1e)=>{\"use strict\";var D1t=Ju(),b1t=g1e(),P1t=nC(),a5=class extends P1t{constructor(e){super(e),this.cursorHide(),this.reset(!0)}async initialize(){this.interpolate=await b1t(this),await super.initialize()}async reset(e){this.state.keys=[],this.state.invalid=new Map,this.state.missing=new Set,this.state.completed=0,this.state.values={},e!==!0&&(await this.initialize(),await this.render())}moveCursor(e){let r=this.getItem();this.cursor+=e,r.cursor+=e}dispatch(e,r){if(!r.code&&!r.ctrl&&e!=null&&this.getItem()){this.append(e,r);return}this.alert()}append(e,r){let s=this.getItem(),a=s.input.slice(0,this.cursor),n=s.input.slice(this.cursor);this.input=s.input=`${a}${e}${n}`,this.moveCursor(1),this.render()}delete(){let e=this.getItem();if(this.cursor<=0||!e.input)return this.alert();let r=e.input.slice(this.cursor),s=e.input.slice(0,this.cursor-1);this.input=e.input=`${s}${r}`,this.moveCursor(-1),this.render()}increment(e){return e>=this.state.keys.length-1?0:e+1}decrement(e){return e<=0?this.state.keys.length-1:e-1}first(){this.state.index=0,this.render()}last(){this.state.index=this.state.keys.length-1,this.render()}right(){if(this.cursor>=this.input.length)return this.alert();this.moveCursor(1),this.render()}left(){if(this.cursor<=0)return this.alert();this.moveCursor(-1),this.render()}prev(){this.state.index=this.decrement(this.state.index),this.getItem(),this.render()}next(){this.state.index=this.increment(this.state.index),this.getItem(),this.render()}up(){this.prev()}down(){this.next()}format(e){let r=this.state.completed<100?this.styles.warning:this.styles.success;return this.state.submitted===!0&&this.state.completed!==100&&(r=this.styles.danger),r(`${this.state.completed}% completed`)}async render(){let{index:e,keys:r=[],submitted:s,size:a}=this.state,n=[this.options.newline,`\n`].find(W=>W!=null),c=await this.prefix(),f=await this.separator(),p=await this.message(),h=[c,p,f].filter(Boolean).join(\" \");this.state.prompt=h;let E=await this.header(),C=await this.error()||\"\",S=await this.hint()||\"\",P=s?\"\":await this.interpolate(this.state),I=this.state.key=r[e]||\"\",R=await this.format(I),N=await this.footer();R&&(h+=\" \"+R),S&&!R&&this.state.completed===0&&(h+=\" \"+S),this.clear(a);let U=[E,h,P,N,C.trim()];this.write(U.filter(Boolean).join(n)),this.restore()}getItem(e){let{items:r,keys:s,index:a}=this.state,n=r.find(c=>c.name===s[a]);return n&&n.input!=null&&(this.input=n.input,this.cursor=n.cursor),n}async submit(){typeof this.interpolate!=\"function\"&&await this.initialize(),await this.interpolate(this.state,!0);let{invalid:e,missing:r,output:s,values:a}=this.state;if(e.size){let f=\"\";for(let[p,h]of e)f+=`Invalid ${p}: ${h}\n`;return this.state.error=f,super.submit()}if(r.size)return this.state.error=\"Required: \"+[...r.keys()].join(\", \"),super.submit();let c=D1t.unstyle(s).split(`\n`).map(f=>f.slice(1)).join(`\n`);return this.value={values:a,result:c},super.submit()}};d1e.exports=a5});var E1e=L((rnr,y1e)=>{\"use strict\";var x1t=\"(Use <shift>+<up/down> to sort)\",k1t=j0(),l5=class extends k1t{constructor(e){super({...e,reorder:!1,sort:!0,multiple:!0}),this.state.hint=[this.options.hint,x1t].find(this.isValue.bind(this))}indicator(){return\"\"}async renderChoice(e,r){let s=await super.renderChoice(e,r),a=this.symbols.identicalTo+\" \",n=this.index===r&&this.sorting?this.styles.muted(a):\"  \";return this.options.drag===!1&&(n=\"\"),this.options.numbered===!0?n+`${r+1} - `+s:n+s}get selected(){return this.choices}submit(){return this.value=this.choices.map(e=>e.value),super.submit()}};y1e.exports=l5});var C1e=L((nnr,I1e)=>{\"use strict\";var Q1t=tS(),c5=class extends Q1t{constructor(e={}){if(super(e),this.emptyError=e.emptyError||\"No items were selected\",this.term=process.env.TERM_PROGRAM,!this.options.header){let r=[\"\",\"4 - Strongly Agree\",\"3 - Agree\",\"2 - Neutral\",\"1 - Disagree\",\"0 - Strongly Disagree\",\"\"];r=r.map(s=>this.styles.muted(s)),this.state.header=r.join(`\n   `)}}async toChoices(...e){if(this.createdScales)return!1;this.createdScales=!0;let r=await super.toChoices(...e);for(let s of r)s.scale=T1t(5,this.options),s.scaleIdx=2;return r}dispatch(){this.alert()}space(){let e=this.focused,r=e.scale[e.scaleIdx],s=r.selected;return e.scale.forEach(a=>a.selected=!1),r.selected=!s,this.render()}indicator(){return\"\"}pointer(){return\"\"}separator(){return this.styles.muted(this.symbols.ellipsis)}right(){let e=this.focused;return e.scaleIdx>=e.scale.length-1?this.alert():(e.scaleIdx++,this.render())}left(){let e=this.focused;return e.scaleIdx<=0?this.alert():(e.scaleIdx--,this.render())}indent(){return\"   \"}async renderChoice(e,r){await this.onChoice(e,r);let s=this.index===r,a=this.term===\"Hyper\",n=a?9:8,c=a?\"\":\" \",f=this.symbols.line.repeat(n),p=\" \".repeat(n+(a?0:1)),h=te=>(te?this.styles.success(\"\\u25C9\"):\"\\u25EF\")+c,E=r+1+\".\",C=s?this.styles.heading:this.styles.noop,S=await this.resolve(e.message,this.state,e,r),P=this.indent(e),I=P+e.scale.map((te,ie)=>h(ie===e.scaleIdx)).join(f),R=te=>te===e.scaleIdx?C(te):te,N=P+e.scale.map((te,ie)=>R(ie)).join(p),U=()=>[E,S].filter(Boolean).join(\" \"),W=()=>[U(),I,N,\" \"].filter(Boolean).join(`\n`);return s&&(I=this.styles.cyan(I),N=this.styles.cyan(N)),W()}async renderChoices(){if(this.state.submitted)return\"\";let e=this.visible.map(async(s,a)=>await this.renderChoice(s,a)),r=await Promise.all(e);return r.length||r.push(this.styles.danger(\"No matching choices\")),r.join(`\n`)}format(){return this.state.submitted?this.choices.map(r=>this.styles.info(r.scaleIdx)).join(\", \"):\"\"}async render(){let{submitted:e,size:r}=this.state,s=await this.prefix(),a=await this.separator(),n=await this.message(),c=[s,n,a].filter(Boolean).join(\" \");this.state.prompt=c;let f=await this.header(),p=await this.format(),h=await this.error()||await this.hint(),E=await this.renderChoices(),C=await this.footer();(p||!h)&&(c+=\" \"+p),h&&!c.includes(h)&&(c+=\" \"+h),e&&!p&&!E&&this.multiple&&this.type!==\"form\"&&(c+=this.styles.danger(this.emptyError)),this.clear(r),this.write([c,f,E,C].filter(Boolean).join(`\n`)),this.restore()}submit(){this.value={};for(let e of this.choices)this.value[e.name]=e.scaleIdx;return this.base.submit.call(this)}};function T1t(t,e={}){if(Array.isArray(e.scale))return e.scale.map(s=>({...s}));let r=[];for(let s=1;s<t+1;s++)r.push({i:s,selected:!1});return r}I1e.exports=c5});var B1e=L((inr,w1e)=>{w1e.exports=ZG()});var S1e=L((snr,v1e)=>{\"use strict\";var R1t=$R(),u5=class extends R1t{async initialize(){await super.initialize(),this.value=this.initial=!!this.options.initial,this.disabled=this.options.disabled||\"no\",this.enabled=this.options.enabled||\"yes\",await this.render()}reset(){this.value=this.initial,this.render()}delete(){this.alert()}toggle(){this.value=!this.value,this.render()}enable(){if(this.value===!0)return this.alert();this.value=!0,this.render()}disable(){if(this.value===!1)return this.alert();this.value=!1,this.render()}up(){this.toggle()}down(){this.toggle()}right(){this.toggle()}left(){this.toggle()}next(){this.toggle()}prev(){this.toggle()}dispatch(e=\"\",r){switch(e.toLowerCase()){case\" \":return this.toggle();case\"1\":case\"y\":case\"t\":return this.enable();case\"0\":case\"n\":case\"f\":return this.disable();default:return this.alert()}}format(){let e=s=>this.styles.primary.underline(s);return[this.value?this.disabled:e(this.disabled),this.value?e(this.enabled):this.enabled].join(this.styles.muted(\" / \"))}async render(){let{size:e}=this.state,r=await this.header(),s=await this.prefix(),a=await this.separator(),n=await this.message(),c=await this.format(),f=await this.error()||await this.hint(),p=await this.footer(),h=[s,n,a,c].join(\" \");this.state.prompt=h,f&&!h.includes(f)&&(h+=\" \"+f),this.clear(e),this.write([r,h,p].filter(Boolean).join(`\n`)),this.write(this.margin[2]),this.restore()}};v1e.exports=u5});var b1e=L((onr,D1e)=>{\"use strict\";var F1t=j0(),f5=class extends F1t{constructor(e){if(super(e),typeof this.options.correctChoice!=\"number\"||this.options.correctChoice<0)throw new Error(\"Please specify the index of the correct answer from the list of choices\")}async toChoices(e,r){let s=await super.toChoices(e,r);if(s.length<2)throw new Error(\"Please give at least two choices to the user\");if(this.options.correctChoice>s.length)throw new Error(\"Please specify the index of the correct answer from the list of choices\");return s}check(e){return e.index===this.options.correctChoice}async result(e){return{selectedAnswer:e,correctAnswer:this.options.choices[this.options.correctChoice].value,correct:await this.check(this.state)}}};D1e.exports=f5});var x1e=L(A5=>{\"use strict\";var P1e=$o(),Ts=(t,e)=>{P1e.defineExport(A5,t,e),P1e.defineExport(A5,t.toLowerCase(),e)};Ts(\"AutoComplete\",()=>Nwe());Ts(\"BasicAuth\",()=>jwe());Ts(\"Confirm\",()=>Wwe());Ts(\"Editable\",()=>Vwe());Ts(\"Form\",()=>XR());Ts(\"Input\",()=>ZG());Ts(\"Invisible\",()=>e1e());Ts(\"List\",()=>r1e());Ts(\"MultiSelect\",()=>i1e());Ts(\"Numeral\",()=>a1e());Ts(\"Password\",()=>c1e());Ts(\"Scale\",()=>A1e());Ts(\"Select\",()=>j0());Ts(\"Snippet\",()=>m1e());Ts(\"Sort\",()=>E1e());Ts(\"Survey\",()=>C1e());Ts(\"Text\",()=>B1e());Ts(\"Toggle\",()=>S1e());Ts(\"Quiz\",()=>b1e())});var Q1e=L((lnr,k1e)=>{k1e.exports={ArrayPrompt:tS(),AuthPrompt:WG(),BooleanPrompt:$R(),NumberPrompt:r5(),StringPrompt:Bm()}});var nS=L((cnr,R1e)=>{\"use strict\";var T1e=Ie(\"assert\"),h5=Ie(\"events\"),q0=$o(),Zu=class extends h5{constructor(e,r){super(),this.options=q0.merge({},e),this.answers={...r}}register(e,r){if(q0.isObject(e)){for(let a of Object.keys(e))this.register(a,e[a]);return this}T1e.equal(typeof r,\"function\",\"expected a function\");let s=e.toLowerCase();return r.prototype instanceof this.Prompt?this.prompts[s]=r:this.prompts[s]=r(this.Prompt,this),this}async prompt(e=[]){for(let r of[].concat(e))try{typeof r==\"function\"&&(r=await r.call(this)),await this.ask(q0.merge({},this.options,r))}catch(s){return Promise.reject(s)}return this.answers}async ask(e){typeof e==\"function\"&&(e=await e.call(this));let r=q0.merge({},this.options,e),{type:s,name:a}=e,{set:n,get:c}=q0;if(typeof s==\"function\"&&(s=await s.call(this,e,this.answers)),!s)return this.answers[a];T1e(this.prompts[s],`Prompt \"${s}\" is not registered`);let f=new this.prompts[s](r),p=c(this.answers,a);f.state.answers=this.answers,f.enquirer=this,a&&f.on(\"submit\",E=>{this.emit(\"answer\",a,E,f),n(this.answers,a,E)});let h=f.emit.bind(f);return f.emit=(...E)=>(this.emit.call(this,...E),h(...E)),this.emit(\"prompt\",f,this),r.autofill&&p!=null?(f.value=f.input=p,r.autofill===\"show\"&&await f.submit()):p=f.value=await f.run(),p}use(e){return e.call(this,this),this}set Prompt(e){this._Prompt=e}get Prompt(){return this._Prompt||this.constructor.Prompt}get prompts(){return this.constructor.prompts}static set Prompt(e){this._Prompt=e}static get Prompt(){return this._Prompt||nC()}static get prompts(){return x1e()}static get types(){return Q1e()}static get prompt(){let e=(r,...s)=>{let a=new this(...s),n=a.emit.bind(a);return a.emit=(...c)=>(e.emit(...c),n(...c)),a.prompt(r)};return q0.mixinEmitter(e,new h5),e}};q0.mixinEmitter(Zu,new h5);var p5=Zu.prompts;for(let t of Object.keys(p5)){let e=t.toLowerCase(),r=s=>new p5[t](s).run();Zu.prompt[e]=r,Zu[e]=r,Zu[t]||Reflect.defineProperty(Zu,t,{get:()=>p5[t]})}var rS=t=>{q0.defineExport(Zu,t,()=>Zu.types[t])};rS(\"ArrayPrompt\");rS(\"AuthPrompt\");rS(\"BooleanPrompt\");rS(\"NumberPrompt\");rS(\"StringPrompt\");R1e.exports=Zu});var aS=L((Wnr,U1e)=>{var H1t=HR();function j1t(t,e,r){var s=t==null?void 0:H1t(t,e);return s===void 0?r:s}U1e.exports=j1t});var q1e=L((Znr,j1e)=>{function q1t(t,e){for(var r=-1,s=t==null?0:t.length;++r<s&&e(t[r],r,t)!==!1;);return t}j1e.exports=q1t});var W1e=L((Xnr,G1e)=>{var G1t=Vd(),W1t=Lk();function Y1t(t,e){return t&&G1t(e,W1t(e),t)}G1e.exports=Y1t});var V1e=L(($nr,Y1e)=>{var V1t=Vd(),K1t=jE();function J1t(t,e){return t&&V1t(e,K1t(e),t)}Y1e.exports=J1t});var J1e=L((eir,K1e)=>{var z1t=Vd(),Z1t=Qk();function X1t(t,e){return z1t(t,Z1t(t),e)}K1e.exports=X1t});var I5=L((tir,z1e)=>{var $1t=kk(),e2t=jk(),t2t=Qk(),r2t=k4(),n2t=Object.getOwnPropertySymbols,i2t=n2t?function(t){for(var e=[];t;)$1t(e,t2t(t)),t=e2t(t);return e}:r2t;z1e.exports=i2t});var X1e=L((rir,Z1e)=>{var s2t=Vd(),o2t=I5();function a2t(t,e){return s2t(t,o2t(t),e)}Z1e.exports=a2t});var C5=L((nir,$1e)=>{var l2t=x4(),c2t=I5(),u2t=jE();function f2t(t){return l2t(t,u2t,c2t)}$1e.exports=f2t});var t2e=L((iir,e2e)=>{var A2t=Object.prototype,p2t=A2t.hasOwnProperty;function h2t(t){var e=t.length,r=new t.constructor(e);return e&&typeof t[0]==\"string\"&&p2t.call(t,\"index\")&&(r.index=t.index,r.input=t.input),r}e2e.exports=h2t});var n2e=L((sir,r2e)=>{var g2t=Uk();function d2t(t,e){var r=e?g2t(t.buffer):t.buffer;return new t.constructor(r,t.byteOffset,t.byteLength)}r2e.exports=d2t});var s2e=L((oir,i2e)=>{var m2t=/\\w*$/;function y2t(t){var e=new t.constructor(t.source,m2t.exec(t));return e.lastIndex=t.lastIndex,e}i2e.exports=y2t});var u2e=L((air,c2e)=>{var o2e=Gd(),a2e=o2e?o2e.prototype:void 0,l2e=a2e?a2e.valueOf:void 0;function E2t(t){return l2e?Object(l2e.call(t)):{}}c2e.exports=E2t});var A2e=L((lir,f2e)=>{var I2t=Uk(),C2t=n2e(),w2t=s2e(),B2t=u2e(),v2t=G4(),S2t=\"[object Boolean]\",D2t=\"[object Date]\",b2t=\"[object Map]\",P2t=\"[object Number]\",x2t=\"[object RegExp]\",k2t=\"[object Set]\",Q2t=\"[object String]\",T2t=\"[object Symbol]\",R2t=\"[object ArrayBuffer]\",F2t=\"[object DataView]\",N2t=\"[object Float32Array]\",O2t=\"[object Float64Array]\",L2t=\"[object Int8Array]\",M2t=\"[object Int16Array]\",_2t=\"[object Int32Array]\",U2t=\"[object Uint8Array]\",H2t=\"[object Uint8ClampedArray]\",j2t=\"[object Uint16Array]\",q2t=\"[object Uint32Array]\";function G2t(t,e,r){var s=t.constructor;switch(e){case R2t:return I2t(t);case S2t:case D2t:return new s(+t);case F2t:return C2t(t,r);case N2t:case O2t:case L2t:case M2t:case _2t:case U2t:case H2t:case j2t:case q2t:return v2t(t,r);case b2t:return new s;case P2t:case Q2t:return new s(t);case x2t:return w2t(t);case k2t:return new s;case T2t:return B2t(t)}}f2e.exports=G2t});var h2e=L((cir,p2e)=>{var W2t=FB(),Y2t=zf(),V2t=\"[object Map]\";function K2t(t){return Y2t(t)&&W2t(t)==V2t}p2e.exports=K2t});var y2e=L((uir,m2e)=>{var J2t=h2e(),z2t=Rk(),g2e=Fk(),d2e=g2e&&g2e.isMap,Z2t=d2e?z2t(d2e):J2t;m2e.exports=Z2t});var I2e=L((fir,E2e)=>{var X2t=FB(),$2t=zf(),eBt=\"[object Set]\";function tBt(t){return $2t(t)&&X2t(t)==eBt}E2e.exports=tBt});var v2e=L((Air,B2e)=>{var rBt=I2e(),nBt=Rk(),C2e=Fk(),w2e=C2e&&C2e.isSet,iBt=w2e?nBt(w2e):rBt;B2e.exports=iBt});var w5=L((pir,P2e)=>{var sBt=Pk(),oBt=q1e(),aBt=qk(),lBt=W1e(),cBt=V1e(),uBt=q4(),fBt=Hk(),ABt=J1e(),pBt=X1e(),hBt=F4(),gBt=C5(),dBt=FB(),mBt=t2e(),yBt=A2e(),EBt=W4(),IBt=xc(),CBt=xB(),wBt=y2e(),BBt=Wl(),vBt=v2e(),SBt=Lk(),DBt=jE(),bBt=1,PBt=2,xBt=4,S2e=\"[object Arguments]\",kBt=\"[object Array]\",QBt=\"[object Boolean]\",TBt=\"[object Date]\",RBt=\"[object Error]\",D2e=\"[object Function]\",FBt=\"[object GeneratorFunction]\",NBt=\"[object Map]\",OBt=\"[object Number]\",b2e=\"[object Object]\",LBt=\"[object RegExp]\",MBt=\"[object Set]\",_Bt=\"[object String]\",UBt=\"[object Symbol]\",HBt=\"[object WeakMap]\",jBt=\"[object ArrayBuffer]\",qBt=\"[object DataView]\",GBt=\"[object Float32Array]\",WBt=\"[object Float64Array]\",YBt=\"[object Int8Array]\",VBt=\"[object Int16Array]\",KBt=\"[object Int32Array]\",JBt=\"[object Uint8Array]\",zBt=\"[object Uint8ClampedArray]\",ZBt=\"[object Uint16Array]\",XBt=\"[object Uint32Array]\",Ci={};Ci[S2e]=Ci[kBt]=Ci[jBt]=Ci[qBt]=Ci[QBt]=Ci[TBt]=Ci[GBt]=Ci[WBt]=Ci[YBt]=Ci[VBt]=Ci[KBt]=Ci[NBt]=Ci[OBt]=Ci[b2e]=Ci[LBt]=Ci[MBt]=Ci[_Bt]=Ci[UBt]=Ci[JBt]=Ci[zBt]=Ci[ZBt]=Ci[XBt]=!0;Ci[RBt]=Ci[D2e]=Ci[HBt]=!1;function rF(t,e,r,s,a,n){var c,f=e&bBt,p=e&PBt,h=e&xBt;if(r&&(c=a?r(t,s,a,n):r(t)),c!==void 0)return c;if(!BBt(t))return t;var E=IBt(t);if(E){if(c=mBt(t),!f)return fBt(t,c)}else{var C=dBt(t),S=C==D2e||C==FBt;if(CBt(t))return uBt(t,f);if(C==b2e||C==S2e||S&&!a){if(c=p||S?{}:EBt(t),!f)return p?pBt(t,cBt(c,t)):ABt(t,lBt(c,t))}else{if(!Ci[C])return a?t:{};c=yBt(t,C,f)}}n||(n=new sBt);var P=n.get(t);if(P)return P;n.set(t,c),vBt(t)?t.forEach(function(N){c.add(rF(N,e,r,N,t,n))}):wBt(t)&&t.forEach(function(N,U){c.set(U,rF(N,e,r,U,t,n))});var I=h?p?gBt:hBt:p?DBt:SBt,R=E?void 0:I(t);return oBt(R||t,function(N,U){R&&(U=N,N=t[U]),aBt(c,U,rF(N,e,r,U,t,n))}),c}P2e.exports=rF});var B5=L((hir,x2e)=>{var $Bt=w5(),evt=1,tvt=4;function rvt(t){return $Bt(t,evt|tvt)}x2e.exports=rvt});var v5=L((gir,k2e)=>{var nvt=hG();function ivt(t,e,r){return t==null?t:nvt(t,e,r)}k2e.exports=ivt});var N2e=L((Cir,F2e)=>{var svt=Object.prototype,ovt=svt.hasOwnProperty;function avt(t,e){return t!=null&&ovt.call(t,e)}F2e.exports=avt});var L2e=L((wir,O2e)=>{var lvt=N2e(),cvt=gG();function uvt(t,e){return t!=null&&cvt(t,e,lvt)}O2e.exports=uvt});var _2e=L((Bir,M2e)=>{function fvt(t){var e=t==null?0:t.length;return e?t[e-1]:void 0}M2e.exports=fvt});var H2e=L((vir,U2e)=>{var Avt=HR(),pvt=s6();function hvt(t,e){return e.length<2?t:Avt(t,pvt(e,0,-1))}U2e.exports=hvt});var D5=L((Sir,j2e)=>{var gvt=Im(),dvt=_2e(),mvt=H2e(),yvt=zI();function Evt(t,e){return e=gvt(e,t),t=mvt(t,e),t==null||delete t[yvt(dvt(e))]}j2e.exports=Evt});var b5=L((Dir,q2e)=>{var Ivt=D5();function Cvt(t,e){return t==null?!0:Ivt(t,e)}q2e.exports=Cvt});var K2e=L((tsr,vvt)=>{vvt.exports={name:\"@yarnpkg/cli\",version:\"4.9.2\",license:\"BSD-2-Clause\",main:\"./sources/index.ts\",exports:{\".\":\"./sources/index.ts\",\"./polyfills\":\"./sources/polyfills.ts\",\"./package.json\":\"./package.json\"},dependencies:{\"@yarnpkg/core\":\"workspace:^\",\"@yarnpkg/fslib\":\"workspace:^\",\"@yarnpkg/libzip\":\"workspace:^\",\"@yarnpkg/parsers\":\"workspace:^\",\"@yarnpkg/plugin-compat\":\"workspace:^\",\"@yarnpkg/plugin-constraints\":\"workspace:^\",\"@yarnpkg/plugin-dlx\":\"workspace:^\",\"@yarnpkg/plugin-essentials\":\"workspace:^\",\"@yarnpkg/plugin-exec\":\"workspace:^\",\"@yarnpkg/plugin-file\":\"workspace:^\",\"@yarnpkg/plugin-git\":\"workspace:^\",\"@yarnpkg/plugin-github\":\"workspace:^\",\"@yarnpkg/plugin-http\":\"workspace:^\",\"@yarnpkg/plugin-init\":\"workspace:^\",\"@yarnpkg/plugin-interactive-tools\":\"workspace:^\",\"@yarnpkg/plugin-jsr\":\"workspace:^\",\"@yarnpkg/plugin-link\":\"workspace:^\",\"@yarnpkg/plugin-nm\":\"workspace:^\",\"@yarnpkg/plugin-npm\":\"workspace:^\",\"@yarnpkg/plugin-npm-cli\":\"workspace:^\",\"@yarnpkg/plugin-pack\":\"workspace:^\",\"@yarnpkg/plugin-patch\":\"workspace:^\",\"@yarnpkg/plugin-pnp\":\"workspace:^\",\"@yarnpkg/plugin-pnpm\":\"workspace:^\",\"@yarnpkg/plugin-stage\":\"workspace:^\",\"@yarnpkg/plugin-typescript\":\"workspace:^\",\"@yarnpkg/plugin-version\":\"workspace:^\",\"@yarnpkg/plugin-workspace-tools\":\"workspace:^\",\"@yarnpkg/shell\":\"workspace:^\",\"ci-info\":\"^4.0.0\",clipanion:\"^4.0.0-rc.2\",semver:\"^7.1.2\",tslib:\"^2.4.0\",typanion:\"^3.14.0\"},devDependencies:{\"@types/semver\":\"^7.1.0\",\"@yarnpkg/builder\":\"workspace:^\",\"@yarnpkg/monorepo\":\"workspace:^\",\"@yarnpkg/pnpify\":\"workspace:^\"},peerDependencies:{\"@yarnpkg/core\":\"workspace:^\"},scripts:{postpack:\"rm -rf lib\",prepack:'run build:compile \"$(pwd)\"',\"build:cli+hook\":\"run build:pnp:hook && builder build bundle\",\"build:cli\":\"builder build bundle\",\"run:cli\":\"builder run\",\"update-local\":\"run build:cli --no-git-hash && rsync -a --delete bundles/ bin/\"},publishConfig:{main:\"./lib/index.js\",bin:null,exports:{\".\":\"./lib/index.js\",\"./package.json\":\"./package.json\"}},files:[\"/lib/**/*\",\"!/lib/pluginConfiguration.*\",\"!/lib/cli.*\"],\"@yarnpkg/builder\":{bundles:{standard:[\"@yarnpkg/plugin-essentials\",\"@yarnpkg/plugin-compat\",\"@yarnpkg/plugin-constraints\",\"@yarnpkg/plugin-dlx\",\"@yarnpkg/plugin-exec\",\"@yarnpkg/plugin-file\",\"@yarnpkg/plugin-git\",\"@yarnpkg/plugin-github\",\"@yarnpkg/plugin-http\",\"@yarnpkg/plugin-init\",\"@yarnpkg/plugin-interactive-tools\",\"@yarnpkg/plugin-jsr\",\"@yarnpkg/plugin-link\",\"@yarnpkg/plugin-nm\",\"@yarnpkg/plugin-npm\",\"@yarnpkg/plugin-npm-cli\",\"@yarnpkg/plugin-pack\",\"@yarnpkg/plugin-patch\",\"@yarnpkg/plugin-pnp\",\"@yarnpkg/plugin-pnpm\",\"@yarnpkg/plugin-stage\",\"@yarnpkg/plugin-typescript\",\"@yarnpkg/plugin-version\",\"@yarnpkg/plugin-workspace-tools\"]}},repository:{type:\"git\",url:\"git+https://github.com/yarnpkg/berry.git\",directory:\"packages/yarnpkg-cli\"},engines:{node:\">=18.12.0\"}}});var O5=L((Flr,oBe)=>{\"use strict\";oBe.exports=function(e,r){r===!0&&(r=0);var s=\"\";if(typeof e==\"string\")try{s=new URL(e).protocol}catch{}else e&&e.constructor===URL&&(s=e.protocol);var a=s.split(/\\:|\\+/).filter(Boolean);return typeof r==\"number\"?a[r]:a}});var lBe=L((Nlr,aBe)=>{\"use strict\";var Gvt=O5();function Wvt(t){var e={protocols:[],protocol:null,port:null,resource:\"\",host:\"\",user:\"\",password:\"\",pathname:\"\",hash:\"\",search:\"\",href:t,query:{},parse_failed:!1};try{var r=new URL(t);e.protocols=Gvt(r),e.protocol=e.protocols[0],e.port=r.port,e.resource=r.hostname,e.host=r.host,e.user=r.username||\"\",e.password=r.password||\"\",e.pathname=r.pathname,e.hash=r.hash.slice(1),e.search=r.search.slice(1),e.href=r.href,e.query=Object.fromEntries(r.searchParams)}catch{e.protocols=[\"file\"],e.protocol=e.protocols[0],e.port=\"\",e.resource=\"\",e.user=\"\",e.pathname=\"\",e.hash=\"\",e.search=\"\",e.href=t,e.query={},e.parse_failed=!0}return e}aBe.exports=Wvt});var fBe=L((Olr,uBe)=>{\"use strict\";var Yvt=lBe();function Vvt(t){return t&&typeof t==\"object\"&&\"default\"in t?t:{default:t}}var Kvt=Vvt(Yvt),Jvt=\"text/plain\",zvt=\"us-ascii\",cBe=(t,e)=>e.some(r=>r instanceof RegExp?r.test(t):r===t),Zvt=(t,{stripHash:e})=>{let r=/^data:(?<type>[^,]*?),(?<data>[^#]*?)(?:#(?<hash>.*))?$/.exec(t);if(!r)throw new Error(`Invalid URL: ${t}`);let{type:s,data:a,hash:n}=r.groups,c=s.split(\";\");n=e?\"\":n;let f=!1;c[c.length-1]===\"base64\"&&(c.pop(),f=!0);let p=(c.shift()||\"\").toLowerCase(),E=[...c.map(C=>{let[S,P=\"\"]=C.split(\"=\").map(I=>I.trim());return S===\"charset\"&&(P=P.toLowerCase(),P===zvt)?\"\":`${S}${P?`=${P}`:\"\"}`}).filter(Boolean)];return f&&E.push(\"base64\"),(E.length>0||p&&p!==Jvt)&&E.unshift(p),`data:${E.join(\";\")},${f?a.trim():a}${n?`#${n}`:\"\"}`};function Xvt(t,e){if(e={defaultProtocol:\"http:\",normalizeProtocol:!0,forceHttp:!1,forceHttps:!1,stripAuthentication:!0,stripHash:!1,stripTextFragment:!0,stripWWW:!0,removeQueryParameters:[/^utm_\\w+/i],removeTrailingSlash:!0,removeSingleSlash:!0,removeDirectoryIndex:!1,sortQueryParameters:!0,...e},t=t.trim(),/^data:/i.test(t))return Zvt(t,e);if(/^view-source:/i.test(t))throw new Error(\"`view-source:` is not supported as it is a non-standard protocol\");let r=t.startsWith(\"//\");!r&&/^\\.*\\//.test(t)||(t=t.replace(/^(?!(?:\\w+:)?\\/\\/)|^\\/\\//,e.defaultProtocol));let a=new URL(t);if(e.forceHttp&&e.forceHttps)throw new Error(\"The `forceHttp` and `forceHttps` options cannot be used together\");if(e.forceHttp&&a.protocol===\"https:\"&&(a.protocol=\"http:\"),e.forceHttps&&a.protocol===\"http:\"&&(a.protocol=\"https:\"),e.stripAuthentication&&(a.username=\"\",a.password=\"\"),e.stripHash?a.hash=\"\":e.stripTextFragment&&(a.hash=a.hash.replace(/#?:~:text.*?$/i,\"\")),a.pathname){let c=/\\b[a-z][a-z\\d+\\-.]{1,50}:\\/\\//g,f=0,p=\"\";for(;;){let E=c.exec(a.pathname);if(!E)break;let C=E[0],S=E.index,P=a.pathname.slice(f,S);p+=P.replace(/\\/{2,}/g,\"/\"),p+=C,f=S+C.length}let h=a.pathname.slice(f,a.pathname.length);p+=h.replace(/\\/{2,}/g,\"/\"),a.pathname=p}if(a.pathname)try{a.pathname=decodeURI(a.pathname)}catch{}if(e.removeDirectoryIndex===!0&&(e.removeDirectoryIndex=[/^index\\.[a-z]+$/]),Array.isArray(e.removeDirectoryIndex)&&e.removeDirectoryIndex.length>0){let c=a.pathname.split(\"/\"),f=c[c.length-1];cBe(f,e.removeDirectoryIndex)&&(c=c.slice(0,-1),a.pathname=c.slice(1).join(\"/\")+\"/\")}if(a.hostname&&(a.hostname=a.hostname.replace(/\\.$/,\"\"),e.stripWWW&&/^www\\.(?!www\\.)[a-z\\-\\d]{1,63}\\.[a-z.\\-\\d]{2,63}$/.test(a.hostname)&&(a.hostname=a.hostname.replace(/^www\\./,\"\"))),Array.isArray(e.removeQueryParameters))for(let c of[...a.searchParams.keys()])cBe(c,e.removeQueryParameters)&&a.searchParams.delete(c);if(e.removeQueryParameters===!0&&(a.search=\"\"),e.sortQueryParameters){a.searchParams.sort();try{a.search=decodeURIComponent(a.search)}catch{}}e.removeTrailingSlash&&(a.pathname=a.pathname.replace(/\\/$/,\"\"));let n=t;return t=a.toString(),!e.removeSingleSlash&&a.pathname===\"/\"&&!n.endsWith(\"/\")&&a.hash===\"\"&&(t=t.replace(/\\/$/,\"\")),(e.removeTrailingSlash||a.pathname===\"/\")&&a.hash===\"\"&&e.removeSingleSlash&&(t=t.replace(/\\/$/,\"\")),r&&!e.normalizeProtocol&&(t=t.replace(/^http:\\/\\//,\"//\")),e.stripProtocol&&(t=t.replace(/^(?:https?:)?\\/\\//,\"\")),t}var L5=(t,e=!1)=>{let r=/^(?:([a-z_][a-z0-9_-]{0,31})@|https?:\\/\\/)([\\w\\.\\-@]+)[\\/:]([\\~,\\.\\w,\\-,\\_,\\/]+?(?:\\.git|\\/)?)$/,s=n=>{let c=new Error(n);throw c.subject_url=t,c};(typeof t!=\"string\"||!t.trim())&&s(\"Invalid url.\"),t.length>L5.MAX_INPUT_LENGTH&&s(\"Input exceeds maximum length. If needed, change the value of parseUrl.MAX_INPUT_LENGTH.\"),e&&(typeof e!=\"object\"&&(e={stripHash:!1}),t=Xvt(t,e));let a=Kvt.default(t);if(a.parse_failed){let n=a.href.match(r);n?(a.protocols=[\"ssh\"],a.protocol=\"ssh\",a.resource=n[2],a.host=n[2],a.user=n[1],a.pathname=`/${n[3]}`,a.parse_failed=!1):s(\"URL parsing failed.\")}return a};L5.MAX_INPUT_LENGTH=2048;uBe.exports=L5});var hBe=L((Llr,pBe)=>{\"use strict\";var $vt=O5();function ABe(t){if(Array.isArray(t))return t.indexOf(\"ssh\")!==-1||t.indexOf(\"rsync\")!==-1;if(typeof t!=\"string\")return!1;var e=$vt(t);if(t=t.substring(t.indexOf(\"://\")+3),ABe(e))return!0;var r=new RegExp(\".([a-zA-Z\\\\d]+):(\\\\d+)/\");return!t.match(r)&&t.indexOf(\"@\")<t.indexOf(\":\")}pBe.exports=ABe});var mBe=L((Mlr,dBe)=>{\"use strict\";var eSt=fBe(),gBe=hBe();function tSt(t){var e=eSt(t);return e.token=\"\",e.password===\"x-oauth-basic\"?e.token=e.user:e.user===\"x-token-auth\"&&(e.token=e.password),gBe(e.protocols)||e.protocols.length===0&&gBe(t)?e.protocol=\"ssh\":e.protocols.length?e.protocol=e.protocols[0]:(e.protocol=\"file\",e.protocols=[\"file\"]),e.href=e.href.replace(/\\/$/,\"\"),e}dBe.exports=tSt});var EBe=L((_lr,yBe)=>{\"use strict\";var rSt=mBe();function M5(t){if(typeof t!=\"string\")throw new Error(\"The url must be a string.\");var e=/^([a-z\\d-]{1,39})\\/([-\\.\\w]{1,100})$/i;e.test(t)&&(t=\"https://github.com/\"+t);var r=rSt(t),s=r.resource.split(\".\"),a=null;switch(r.toString=function(N){return M5.stringify(this,N)},r.source=s.length>2?s.slice(1-s.length).join(\".\"):r.source=r.resource,r.git_suffix=/\\.git$/.test(r.pathname),r.name=decodeURIComponent((r.pathname||r.href).replace(/(^\\/)|(\\/$)/g,\"\").replace(/\\.git$/,\"\")),r.owner=decodeURIComponent(r.user),r.source){case\"git.cloudforge.com\":r.owner=r.user,r.organization=s[0],r.source=\"cloudforge.com\";break;case\"visualstudio.com\":if(r.resource===\"vs-ssh.visualstudio.com\"){a=r.name.split(\"/\"),a.length===4&&(r.organization=a[1],r.owner=a[2],r.name=a[3],r.full_name=a[2]+\"/\"+a[3]);break}else{a=r.name.split(\"/\"),a.length===2?(r.owner=a[1],r.name=a[1],r.full_name=\"_git/\"+r.name):a.length===3?(r.name=a[2],a[0]===\"DefaultCollection\"?(r.owner=a[2],r.organization=a[0],r.full_name=r.organization+\"/_git/\"+r.name):(r.owner=a[0],r.full_name=r.owner+\"/_git/\"+r.name)):a.length===4&&(r.organization=a[0],r.owner=a[1],r.name=a[3],r.full_name=r.organization+\"/\"+r.owner+\"/_git/\"+r.name);break}case\"dev.azure.com\":case\"azure.com\":if(r.resource===\"ssh.dev.azure.com\"){a=r.name.split(\"/\"),a.length===4&&(r.organization=a[1],r.owner=a[2],r.name=a[3]);break}else{a=r.name.split(\"/\"),a.length===5?(r.organization=a[0],r.owner=a[1],r.name=a[4],r.full_name=\"_git/\"+r.name):a.length===3?(r.name=a[2],a[0]===\"DefaultCollection\"?(r.owner=a[2],r.organization=a[0],r.full_name=r.organization+\"/_git/\"+r.name):(r.owner=a[0],r.full_name=r.owner+\"/_git/\"+r.name)):a.length===4&&(r.organization=a[0],r.owner=a[1],r.name=a[3],r.full_name=r.organization+\"/\"+r.owner+\"/_git/\"+r.name),r.query&&r.query.path&&(r.filepath=r.query.path.replace(/^\\/+/g,\"\")),r.query&&r.query.version&&(r.ref=r.query.version.replace(/^GB/,\"\"));break}default:a=r.name.split(\"/\");var n=a.length-1;if(a.length>=2){var c=a.indexOf(\"-\",2),f=a.indexOf(\"blob\",2),p=a.indexOf(\"tree\",2),h=a.indexOf(\"commit\",2),E=a.indexOf(\"src\",2),C=a.indexOf(\"raw\",2),S=a.indexOf(\"edit\",2);n=c>0?c-1:f>0?f-1:p>0?p-1:h>0?h-1:E>0?E-1:C>0?C-1:S>0?S-1:n,r.owner=a.slice(0,n).join(\"/\"),r.name=a[n],h&&(r.commit=a[n+2])}r.ref=\"\",r.filepathtype=\"\",r.filepath=\"\";var P=a.length>n&&a[n+1]===\"-\"?n+1:n;a.length>P+2&&[\"raw\",\"src\",\"blob\",\"tree\",\"edit\"].indexOf(a[P+1])>=0&&(r.filepathtype=a[P+1],r.ref=a[P+2],a.length>P+3&&(r.filepath=a.slice(P+3).join(\"/\"))),r.organization=r.owner;break}r.full_name||(r.full_name=r.owner,r.name&&(r.full_name&&(r.full_name+=\"/\"),r.full_name+=r.name)),r.owner.startsWith(\"scm/\")&&(r.source=\"bitbucket-server\",r.owner=r.owner.replace(\"scm/\",\"\"),r.organization=r.owner,r.full_name=r.owner+\"/\"+r.name);var I=/(projects|users)\\/(.*?)\\/repos\\/(.*?)((\\/.*$)|$)/,R=I.exec(r.pathname);return R!=null&&(r.source=\"bitbucket-server\",R[1]===\"users\"?r.owner=\"~\"+R[2]:r.owner=R[2],r.organization=r.owner,r.name=R[3],a=R[4].split(\"/\"),a.length>1&&([\"raw\",\"browse\"].indexOf(a[1])>=0?(r.filepathtype=a[1],a.length>2&&(r.filepath=a.slice(2).join(\"/\"))):a[1]===\"commits\"&&a.length>2&&(r.commit=a[2])),r.full_name=r.owner+\"/\"+r.name,r.query.at?r.ref=r.query.at:r.ref=\"\"),r}M5.stringify=function(t,e){e=e||(t.protocols&&t.protocols.length?t.protocols.join(\"+\"):t.protocol);var r=t.port?\":\"+t.port:\"\",s=t.user||\"git\",a=t.git_suffix?\".git\":\"\";switch(e){case\"ssh\":return r?\"ssh://\"+s+\"@\"+t.resource+r+\"/\"+t.full_name+a:s+\"@\"+t.resource+\":\"+t.full_name+a;case\"git+ssh\":case\"ssh+git\":case\"ftp\":case\"ftps\":return e+\"://\"+s+\"@\"+t.resource+r+\"/\"+t.full_name+a;case\"http\":case\"https\":var n=t.token?nSt(t):t.user&&(t.protocols.includes(\"http\")||t.protocols.includes(\"https\"))?t.user+\"@\":\"\";return e+\"://\"+n+t.resource+r+\"/\"+iSt(t)+a;default:return t.href}};function nSt(t){switch(t.source){case\"bitbucket.org\":return\"x-token-auth:\"+t.token+\"@\";default:return t.token+\"@\"}}function iSt(t){switch(t.source){case\"bitbucket-server\":return\"scm/\"+t.full_name;default:return\"\"+t.full_name}}yBe.exports=M5});var NBe=L((yur,FBe)=>{var gSt=QT(),dSt=Hk(),mSt=xc(),ySt=oI(),ESt=pG(),ISt=zI(),CSt=bv();function wSt(t){return mSt(t)?gSt(t,ISt):ySt(t)?[t]:dSt(ESt(CSt(t)))}FBe.exports=wSt});function DSt(t,e){return e===1&&SSt.has(t[0])}function hS(t){let e=Array.isArray(t)?t:(0,MBe.default)(t);return e.map((s,a)=>BSt.test(s)?`[${s}]`:vSt.test(s)&&!DSt(e,a)?`.${s}`:`[${JSON.stringify(s)}]`).join(\"\").replace(/^\\./,\"\")}function bSt(t,e){let r=[];if(e.methodName!==null&&r.push(he.pretty(t,e.methodName,he.Type.CODE)),e.file!==null){let s=[];s.push(he.pretty(t,e.file,he.Type.PATH)),e.line!==null&&(s.push(he.pretty(t,e.line,he.Type.NUMBER)),e.column!==null&&s.push(he.pretty(t,e.column,he.Type.NUMBER))),r.push(`(${s.join(he.pretty(t,\":\",\"grey\"))})`)}return r.join(\" \")}function oF(t,{manifestUpdates:e,reportedErrors:r},{fix:s}={}){let a=new Map,n=new Map,c=[...r.keys()].map(f=>[f,new Map]);for(let[f,p]of[...c,...e]){let h=r.get(f)?.map(P=>({text:P,fixable:!1}))??[],E=!1,C=t.getWorkspaceByCwd(f),S=C.manifest.exportTo({});for(let[P,I]of p){if(I.size>1){let R=[...I].map(([N,U])=>{let W=he.pretty(t.configuration,N,he.Type.INSPECT),te=U.size>0?bSt(t.configuration,U.values().next().value):null;return te!==null?`\n${W} at ${te}`:`\n${W}`}).join(\"\");h.push({text:`Conflict detected in constraint targeting ${he.pretty(t.configuration,P,he.Type.CODE)}; conflicting values are:${R}`,fixable:!1})}else{let[[R]]=I,N=(0,OBe.default)(S,P);if(JSON.stringify(N)===JSON.stringify(R))continue;if(!s){let U=typeof N>\"u\"?`Missing field ${he.pretty(t.configuration,P,he.Type.CODE)}; expected ${he.pretty(t.configuration,R,he.Type.INSPECT)}`:typeof R>\"u\"?`Extraneous field ${he.pretty(t.configuration,P,he.Type.CODE)} currently set to ${he.pretty(t.configuration,N,he.Type.INSPECT)}`:`Invalid field ${he.pretty(t.configuration,P,he.Type.CODE)}; expected ${he.pretty(t.configuration,R,he.Type.INSPECT)}, found ${he.pretty(t.configuration,N,he.Type.INSPECT)}`;h.push({text:U,fixable:!0});continue}typeof R>\"u\"?(0,_Be.default)(S,P):(0,LBe.default)(S,P,R),E=!0}E&&a.set(C,S)}h.length>0&&n.set(C,h)}return{changedWorkspaces:a,remainingErrors:n}}function UBe(t,{configuration:e}){let r={children:[]};for(let[s,a]of t){let n=[];for(let f of a){let p=f.text.split(/\\n/);f.fixable&&(p[0]=`${he.pretty(e,\"\\u2699\",\"gray\")} ${p[0]}`),n.push({value:he.tuple(he.Type.NO_HINT,p[0]),children:p.slice(1).map(h=>({value:he.tuple(he.Type.NO_HINT,h)}))})}let c={value:he.tuple(he.Type.LOCATOR,s.anchoredLocator),children:je.sortMap(n,f=>f.value[1])};r.children.push(c)}return r.children=je.sortMap(r.children,s=>s.value[1]),r}var OBe,LBe,MBe,_Be,WC,BSt,vSt,SSt,gS=Ct(()=>{Ve();OBe=et(aS()),LBe=et(v5()),MBe=et(NBe()),_Be=et(b5()),WC=class{constructor(e){this.indexedFields=e;this.items=[];this.indexes={};this.clear()}clear(){this.items=[];for(let e of this.indexedFields)this.indexes[e]=new Map}insert(e){this.items.push(e);for(let r of this.indexedFields){let s=Object.hasOwn(e,r)?e[r]:void 0;if(typeof s>\"u\")continue;je.getArrayWithDefault(this.indexes[r],s).push(e)}return e}find(e){if(typeof e>\"u\")return this.items;let r=Object.entries(e);if(r.length===0)return this.items;let s=[],a;for(let[c,f]of r){let p=c,h=Object.hasOwn(this.indexes,p)?this.indexes[p]:void 0;if(typeof h>\"u\"){s.push([p,f]);continue}let E=new Set(h.get(f)??[]);if(E.size===0)return[];if(typeof a>\"u\")a=E;else for(let C of a)E.has(C)||a.delete(C);if(a.size===0)break}let n=[...a??[]];return s.length>0&&(n=n.filter(c=>{for(let[f,p]of s)if(!(typeof p<\"u\"?Object.hasOwn(c,f)&&c[f]===p:Object.hasOwn(c,f)===!1))return!1;return!0})),n}},BSt=/^[0-9]+$/,vSt=/^[a-zA-Z0-9_]+$/,SSt=new Set([\"scripts\",...Ht.allDependencies])});var HBe=L((kur,X5)=>{var PSt;(function(t){var e=function(){return{\"append/2\":[new t.type.Rule(new t.type.Term(\"append\",[new t.type.Var(\"X\"),new t.type.Var(\"L\")]),new t.type.Term(\"foldl\",[new t.type.Term(\"append\",[]),new t.type.Var(\"X\"),new t.type.Term(\"[]\",[]),new t.type.Var(\"L\")]))],\"append/3\":[new t.type.Rule(new t.type.Term(\"append\",[new t.type.Term(\"[]\",[]),new t.type.Var(\"X\"),new t.type.Var(\"X\")]),null),new t.type.Rule(new t.type.Term(\"append\",[new t.type.Term(\".\",[new t.type.Var(\"H\"),new t.type.Var(\"T\")]),new t.type.Var(\"X\"),new t.type.Term(\".\",[new t.type.Var(\"H\"),new t.type.Var(\"S\")])]),new t.type.Term(\"append\",[new t.type.Var(\"T\"),new t.type.Var(\"X\"),new t.type.Var(\"S\")]))],\"member/2\":[new t.type.Rule(new t.type.Term(\"member\",[new t.type.Var(\"X\"),new t.type.Term(\".\",[new t.type.Var(\"X\"),new t.type.Var(\"_\")])]),null),new t.type.Rule(new t.type.Term(\"member\",[new t.type.Var(\"X\"),new t.type.Term(\".\",[new t.type.Var(\"_\"),new t.type.Var(\"Xs\")])]),new t.type.Term(\"member\",[new t.type.Var(\"X\"),new t.type.Var(\"Xs\")]))],\"permutation/2\":[new t.type.Rule(new t.type.Term(\"permutation\",[new t.type.Term(\"[]\",[]),new t.type.Term(\"[]\",[])]),null),new t.type.Rule(new t.type.Term(\"permutation\",[new t.type.Term(\".\",[new t.type.Var(\"H\"),new t.type.Var(\"T\")]),new t.type.Var(\"S\")]),new t.type.Term(\",\",[new t.type.Term(\"permutation\",[new t.type.Var(\"T\"),new t.type.Var(\"P\")]),new t.type.Term(\",\",[new t.type.Term(\"append\",[new t.type.Var(\"X\"),new t.type.Var(\"Y\"),new t.type.Var(\"P\")]),new t.type.Term(\"append\",[new t.type.Var(\"X\"),new t.type.Term(\".\",[new t.type.Var(\"H\"),new t.type.Var(\"Y\")]),new t.type.Var(\"S\")])])]))],\"maplist/2\":[new t.type.Rule(new t.type.Term(\"maplist\",[new t.type.Var(\"_\"),new t.type.Term(\"[]\",[])]),null),new t.type.Rule(new t.type.Term(\"maplist\",[new t.type.Var(\"P\"),new t.type.Term(\".\",[new t.type.Var(\"X\"),new t.type.Var(\"Xs\")])]),new t.type.Term(\",\",[new t.type.Term(\"call\",[new t.type.Var(\"P\"),new t.type.Var(\"X\")]),new t.type.Term(\"maplist\",[new t.type.Var(\"P\"),new t.type.Var(\"Xs\")])]))],\"maplist/3\":[new t.type.Rule(new t.type.Term(\"maplist\",[new t.type.Var(\"_\"),new t.type.Term(\"[]\",[]),new t.type.Term(\"[]\",[])]),null),new t.type.Rule(new t.type.Term(\"maplist\",[new t.type.Var(\"P\"),new t.type.Term(\".\",[new t.type.Var(\"A\"),new t.type.Var(\"As\")]),new t.type.Term(\".\",[new t.type.Var(\"B\"),new t.type.Var(\"Bs\")])]),new t.type.Term(\",\",[new t.type.Term(\"call\",[new t.type.Var(\"P\"),new t.type.Var(\"A\"),new t.type.Var(\"B\")]),new t.type.Term(\"maplist\",[new t.type.Var(\"P\"),new t.type.Var(\"As\"),new t.type.Var(\"Bs\")])]))],\"maplist/4\":[new t.type.Rule(new t.type.Term(\"maplist\",[new t.type.Var(\"_\"),new t.type.Term(\"[]\",[]),new t.type.Term(\"[]\",[]),new t.type.Term(\"[]\",[])]),null),new t.type.Rule(new t.type.Term(\"maplist\",[new t.type.Var(\"P\"),new t.type.Term(\".\",[new t.type.Var(\"A\"),new t.type.Var(\"As\")]),new t.type.Term(\".\",[new t.type.Var(\"B\"),new t.type.Var(\"Bs\")]),new t.type.Term(\".\",[new t.type.Var(\"C\"),new t.type.Var(\"Cs\")])]),new t.type.Term(\",\",[new t.type.Term(\"call\",[new t.type.Var(\"P\"),new t.type.Var(\"A\"),new t.type.Var(\"B\"),new t.type.Var(\"C\")]),new t.type.Term(\"maplist\",[new t.type.Var(\"P\"),new t.type.Var(\"As\"),new t.type.Var(\"Bs\"),new t.type.Var(\"Cs\")])]))],\"maplist/5\":[new t.type.Rule(new t.type.Term(\"maplist\",[new t.type.Var(\"_\"),new t.type.Term(\"[]\",[]),new t.type.Term(\"[]\",[]),new t.type.Term(\"[]\",[]),new t.type.Term(\"[]\",[])]),null),new t.type.Rule(new t.type.Term(\"maplist\",[new t.type.Var(\"P\"),new t.type.Term(\".\",[new t.type.Var(\"A\"),new t.type.Var(\"As\")]),new t.type.Term(\".\",[new t.type.Var(\"B\"),new t.type.Var(\"Bs\")]),new t.type.Term(\".\",[new t.type.Var(\"C\"),new t.type.Var(\"Cs\")]),new t.type.Term(\".\",[new t.type.Var(\"D\"),new t.type.Var(\"Ds\")])]),new t.type.Term(\",\",[new t.type.Term(\"call\",[new t.type.Var(\"P\"),new t.type.Var(\"A\"),new t.type.Var(\"B\"),new t.type.Var(\"C\"),new t.type.Var(\"D\")]),new t.type.Term(\"maplist\",[new t.type.Var(\"P\"),new t.type.Var(\"As\"),new t.type.Var(\"Bs\"),new t.type.Var(\"Cs\"),new t.type.Var(\"Ds\")])]))],\"maplist/6\":[new t.type.Rule(new t.type.Term(\"maplist\",[new t.type.Var(\"_\"),new t.type.Term(\"[]\",[]),new t.type.Term(\"[]\",[]),new t.type.Term(\"[]\",[]),new t.type.Term(\"[]\",[]),new t.type.Term(\"[]\",[])]),null),new t.type.Rule(new t.type.Term(\"maplist\",[new t.type.Var(\"P\"),new t.type.Term(\".\",[new t.type.Var(\"A\"),new t.type.Var(\"As\")]),new t.type.Term(\".\",[new t.type.Var(\"B\"),new t.type.Var(\"Bs\")]),new t.type.Term(\".\",[new t.type.Var(\"C\"),new t.type.Var(\"Cs\")]),new t.type.Term(\".\",[new t.type.Var(\"D\"),new t.type.Var(\"Ds\")]),new t.type.Term(\".\",[new t.type.Var(\"E\"),new t.type.Var(\"Es\")])]),new t.type.Term(\",\",[new t.type.Term(\"call\",[new t.type.Var(\"P\"),new t.type.Var(\"A\"),new t.type.Var(\"B\"),new t.type.Var(\"C\"),new t.type.Var(\"D\"),new t.type.Var(\"E\")]),new t.type.Term(\"maplist\",[new t.type.Var(\"P\"),new t.type.Var(\"As\"),new t.type.Var(\"Bs\"),new t.type.Var(\"Cs\"),new t.type.Var(\"Ds\"),new t.type.Var(\"Es\")])]))],\"maplist/7\":[new t.type.Rule(new t.type.Term(\"maplist\",[new t.type.Var(\"_\"),new t.type.Term(\"[]\",[]),new t.type.Term(\"[]\",[]),new t.type.Term(\"[]\",[]),new t.type.Term(\"[]\",[]),new t.type.Term(\"[]\",[]),new t.type.Term(\"[]\",[])]),null),new t.type.Rule(new t.type.Term(\"maplist\",[new t.type.Var(\"P\"),new t.type.Term(\".\",[new t.type.Var(\"A\"),new t.type.Var(\"As\")]),new t.type.Term(\".\",[new t.type.Var(\"B\"),new t.type.Var(\"Bs\")]),new t.type.Term(\".\",[new t.type.Var(\"C\"),new t.type.Var(\"Cs\")]),new t.type.Term(\".\",[new t.type.Var(\"D\"),new t.type.Var(\"Ds\")]),new t.type.Term(\".\",[new t.type.Var(\"E\"),new t.type.Var(\"Es\")]),new t.type.Term(\".\",[new t.type.Var(\"F\"),new t.type.Var(\"Fs\")])]),new t.type.Term(\",\",[new t.type.Term(\"call\",[new t.type.Var(\"P\"),new t.type.Var(\"A\"),new t.type.Var(\"B\"),new t.type.Var(\"C\"),new t.type.Var(\"D\"),new t.type.Var(\"E\"),new t.type.Var(\"F\")]),new t.type.Term(\"maplist\",[new t.type.Var(\"P\"),new t.type.Var(\"As\"),new t.type.Var(\"Bs\"),new t.type.Var(\"Cs\"),new t.type.Var(\"Ds\"),new t.type.Var(\"Es\"),new t.type.Var(\"Fs\")])]))],\"maplist/8\":[new t.type.Rule(new t.type.Term(\"maplist\",[new t.type.Var(\"_\"),new t.type.Term(\"[]\",[]),new t.type.Term(\"[]\",[]),new t.type.Term(\"[]\",[]),new t.type.Term(\"[]\",[]),new t.type.Term(\"[]\",[]),new t.type.Term(\"[]\",[]),new t.type.Term(\"[]\",[])]),null),new t.type.Rule(new t.type.Term(\"maplist\",[new t.type.Var(\"P\"),new t.type.Term(\".\",[new t.type.Var(\"A\"),new t.type.Var(\"As\")]),new t.type.Term(\".\",[new t.type.Var(\"B\"),new t.type.Var(\"Bs\")]),new t.type.Term(\".\",[new t.type.Var(\"C\"),new t.type.Var(\"Cs\")]),new t.type.Term(\".\",[new t.type.Var(\"D\"),new t.type.Var(\"Ds\")]),new t.type.Term(\".\",[new t.type.Var(\"E\"),new t.type.Var(\"Es\")]),new t.type.Term(\".\",[new t.type.Var(\"F\"),new t.type.Var(\"Fs\")]),new t.type.Term(\".\",[new t.type.Var(\"G\"),new t.type.Var(\"Gs\")])]),new t.type.Term(\",\",[new t.type.Term(\"call\",[new t.type.Var(\"P\"),new t.type.Var(\"A\"),new t.type.Var(\"B\"),new t.type.Var(\"C\"),new t.type.Var(\"D\"),new t.type.Var(\"E\"),new t.type.Var(\"F\"),new t.type.Var(\"G\")]),new t.type.Term(\"maplist\",[new t.type.Var(\"P\"),new t.type.Var(\"As\"),new t.type.Var(\"Bs\"),new t.type.Var(\"Cs\"),new t.type.Var(\"Ds\"),new t.type.Var(\"Es\"),new t.type.Var(\"Fs\"),new t.type.Var(\"Gs\")])]))],\"include/3\":[new t.type.Rule(new t.type.Term(\"include\",[new t.type.Var(\"_\"),new t.type.Term(\"[]\",[]),new t.type.Term(\"[]\",[])]),null),new t.type.Rule(new t.type.Term(\"include\",[new t.type.Var(\"P\"),new t.type.Term(\".\",[new t.type.Var(\"H\"),new t.type.Var(\"T\")]),new t.type.Var(\"L\")]),new t.type.Term(\",\",[new t.type.Term(\"=..\",[new t.type.Var(\"P\"),new t.type.Var(\"A\")]),new t.type.Term(\",\",[new t.type.Term(\"append\",[new t.type.Var(\"A\"),new t.type.Term(\".\",[new t.type.Var(\"H\"),new t.type.Term(\"[]\",[])]),new t.type.Var(\"B\")]),new t.type.Term(\",\",[new t.type.Term(\"=..\",[new t.type.Var(\"F\"),new t.type.Var(\"B\")]),new t.type.Term(\",\",[new t.type.Term(\";\",[new t.type.Term(\",\",[new t.type.Term(\"call\",[new t.type.Var(\"F\")]),new t.type.Term(\",\",[new t.type.Term(\"=\",[new t.type.Var(\"L\"),new t.type.Term(\".\",[new t.type.Var(\"H\"),new t.type.Var(\"S\")])]),new t.type.Term(\"!\",[])])]),new t.type.Term(\"=\",[new t.type.Var(\"L\"),new t.type.Var(\"S\")])]),new t.type.Term(\"include\",[new t.type.Var(\"P\"),new t.type.Var(\"T\"),new t.type.Var(\"S\")])])])])]))],\"exclude/3\":[new t.type.Rule(new t.type.Term(\"exclude\",[new t.type.Var(\"_\"),new t.type.Term(\"[]\",[]),new t.type.Term(\"[]\",[])]),null),new t.type.Rule(new t.type.Term(\"exclude\",[new t.type.Var(\"P\"),new t.type.Term(\".\",[new t.type.Var(\"H\"),new t.type.Var(\"T\")]),new t.type.Var(\"S\")]),new t.type.Term(\",\",[new t.type.Term(\"exclude\",[new t.type.Var(\"P\"),new t.type.Var(\"T\"),new t.type.Var(\"E\")]),new t.type.Term(\",\",[new t.type.Term(\"=..\",[new t.type.Var(\"P\"),new t.type.Var(\"L\")]),new t.type.Term(\",\",[new t.type.Term(\"append\",[new t.type.Var(\"L\"),new t.type.Term(\".\",[new t.type.Var(\"H\"),new t.type.Term(\"[]\",[])]),new t.type.Var(\"Q\")]),new t.type.Term(\",\",[new t.type.Term(\"=..\",[new t.type.Var(\"R\"),new t.type.Var(\"Q\")]),new t.type.Term(\";\",[new t.type.Term(\",\",[new t.type.Term(\"call\",[new t.type.Var(\"R\")]),new t.type.Term(\",\",[new t.type.Term(\"!\",[]),new t.type.Term(\"=\",[new t.type.Var(\"S\"),new t.type.Var(\"E\")])])]),new t.type.Term(\"=\",[new t.type.Var(\"S\"),new t.type.Term(\".\",[new t.type.Var(\"H\"),new t.type.Var(\"E\")])])])])])])]))],\"foldl/4\":[new t.type.Rule(new t.type.Term(\"foldl\",[new t.type.Var(\"_\"),new t.type.Term(\"[]\",[]),new t.type.Var(\"I\"),new t.type.Var(\"I\")]),null),new t.type.Rule(new t.type.Term(\"foldl\",[new t.type.Var(\"P\"),new t.type.Term(\".\",[new t.type.Var(\"H\"),new t.type.Var(\"T\")]),new t.type.Var(\"I\"),new t.type.Var(\"R\")]),new t.type.Term(\",\",[new t.type.Term(\"=..\",[new t.type.Var(\"P\"),new t.type.Var(\"L\")]),new t.type.Term(\",\",[new t.type.Term(\"append\",[new t.type.Var(\"L\"),new t.type.Term(\".\",[new t.type.Var(\"I\"),new t.type.Term(\".\",[new t.type.Var(\"H\"),new t.type.Term(\".\",[new t.type.Var(\"X\"),new t.type.Term(\"[]\",[])])])]),new t.type.Var(\"L2\")]),new t.type.Term(\",\",[new t.type.Term(\"=..\",[new t.type.Var(\"P2\"),new t.type.Var(\"L2\")]),new t.type.Term(\",\",[new t.type.Term(\"call\",[new t.type.Var(\"P2\")]),new t.type.Term(\"foldl\",[new t.type.Var(\"P\"),new t.type.Var(\"T\"),new t.type.Var(\"X\"),new t.type.Var(\"R\")])])])])]))],\"select/3\":[new t.type.Rule(new t.type.Term(\"select\",[new t.type.Var(\"E\"),new t.type.Term(\".\",[new t.type.Var(\"E\"),new t.type.Var(\"Xs\")]),new t.type.Var(\"Xs\")]),null),new t.type.Rule(new t.type.Term(\"select\",[new t.type.Var(\"E\"),new t.type.Term(\".\",[new t.type.Var(\"X\"),new t.type.Var(\"Xs\")]),new t.type.Term(\".\",[new t.type.Var(\"X\"),new t.type.Var(\"Ys\")])]),new t.type.Term(\"select\",[new t.type.Var(\"E\"),new t.type.Var(\"Xs\"),new t.type.Var(\"Ys\")]))],\"sum_list/2\":[new t.type.Rule(new t.type.Term(\"sum_list\",[new t.type.Term(\"[]\",[]),new t.type.Num(0,!1)]),null),new t.type.Rule(new t.type.Term(\"sum_list\",[new t.type.Term(\".\",[new t.type.Var(\"X\"),new t.type.Var(\"Xs\")]),new t.type.Var(\"S\")]),new t.type.Term(\",\",[new t.type.Term(\"sum_list\",[new t.type.Var(\"Xs\"),new t.type.Var(\"Y\")]),new t.type.Term(\"is\",[new t.type.Var(\"S\"),new t.type.Term(\"+\",[new t.type.Var(\"X\"),new t.type.Var(\"Y\")])])]))],\"max_list/2\":[new t.type.Rule(new t.type.Term(\"max_list\",[new t.type.Term(\".\",[new t.type.Var(\"X\"),new t.type.Term(\"[]\",[])]),new t.type.Var(\"X\")]),null),new t.type.Rule(new t.type.Term(\"max_list\",[new t.type.Term(\".\",[new t.type.Var(\"X\"),new t.type.Var(\"Xs\")]),new t.type.Var(\"S\")]),new t.type.Term(\",\",[new t.type.Term(\"max_list\",[new t.type.Var(\"Xs\"),new t.type.Var(\"Y\")]),new t.type.Term(\";\",[new t.type.Term(\",\",[new t.type.Term(\">=\",[new t.type.Var(\"X\"),new t.type.Var(\"Y\")]),new t.type.Term(\",\",[new t.type.Term(\"=\",[new t.type.Var(\"S\"),new t.type.Var(\"X\")]),new t.type.Term(\"!\",[])])]),new t.type.Term(\"=\",[new t.type.Var(\"S\"),new t.type.Var(\"Y\")])])]))],\"min_list/2\":[new t.type.Rule(new t.type.Term(\"min_list\",[new t.type.Term(\".\",[new t.type.Var(\"X\"),new t.type.Term(\"[]\",[])]),new t.type.Var(\"X\")]),null),new t.type.Rule(new t.type.Term(\"min_list\",[new t.type.Term(\".\",[new t.type.Var(\"X\"),new t.type.Var(\"Xs\")]),new t.type.Var(\"S\")]),new t.type.Term(\",\",[new t.type.Term(\"min_list\",[new t.type.Var(\"Xs\"),new t.type.Var(\"Y\")]),new t.type.Term(\";\",[new t.type.Term(\",\",[new t.type.Term(\"=<\",[new t.type.Var(\"X\"),new t.type.Var(\"Y\")]),new t.type.Term(\",\",[new t.type.Term(\"=\",[new t.type.Var(\"S\"),new t.type.Var(\"X\")]),new t.type.Term(\"!\",[])])]),new t.type.Term(\"=\",[new t.type.Var(\"S\"),new t.type.Var(\"Y\")])])]))],\"prod_list/2\":[new t.type.Rule(new t.type.Term(\"prod_list\",[new t.type.Term(\"[]\",[]),new t.type.Num(1,!1)]),null),new t.type.Rule(new t.type.Term(\"prod_list\",[new t.type.Term(\".\",[new t.type.Var(\"X\"),new t.type.Var(\"Xs\")]),new t.type.Var(\"S\")]),new t.type.Term(\",\",[new t.type.Term(\"prod_list\",[new t.type.Var(\"Xs\"),new t.type.Var(\"Y\")]),new t.type.Term(\"is\",[new t.type.Var(\"S\"),new t.type.Term(\"*\",[new t.type.Var(\"X\"),new t.type.Var(\"Y\")])])]))],\"last/2\":[new t.type.Rule(new t.type.Term(\"last\",[new t.type.Term(\".\",[new t.type.Var(\"X\"),new t.type.Term(\"[]\",[])]),new t.type.Var(\"X\")]),null),new t.type.Rule(new t.type.Term(\"last\",[new t.type.Term(\".\",[new t.type.Var(\"_\"),new t.type.Var(\"Xs\")]),new t.type.Var(\"X\")]),new t.type.Term(\"last\",[new t.type.Var(\"Xs\"),new t.type.Var(\"X\")]))],\"prefix/2\":[new t.type.Rule(new t.type.Term(\"prefix\",[new t.type.Var(\"Part\"),new t.type.Var(\"Whole\")]),new t.type.Term(\"append\",[new t.type.Var(\"Part\"),new t.type.Var(\"_\"),new t.type.Var(\"Whole\")]))],\"nth0/3\":[new t.type.Rule(new t.type.Term(\"nth0\",[new t.type.Var(\"X\"),new t.type.Var(\"Y\"),new t.type.Var(\"Z\")]),new t.type.Term(\";\",[new t.type.Term(\"->\",[new t.type.Term(\"var\",[new t.type.Var(\"X\")]),new t.type.Term(\"nth\",[new t.type.Num(0,!1),new t.type.Var(\"X\"),new t.type.Var(\"Y\"),new t.type.Var(\"Z\"),new t.type.Var(\"_\")])]),new t.type.Term(\",\",[new t.type.Term(\">=\",[new t.type.Var(\"X\"),new t.type.Num(0,!1)]),new t.type.Term(\",\",[new t.type.Term(\"nth\",[new t.type.Num(0,!1),new t.type.Var(\"X\"),new t.type.Var(\"Y\"),new t.type.Var(\"Z\"),new t.type.Var(\"_\")]),new t.type.Term(\"!\",[])])])]))],\"nth1/3\":[new t.type.Rule(new t.type.Term(\"nth1\",[new t.type.Var(\"X\"),new t.type.Var(\"Y\"),new t.type.Var(\"Z\")]),new t.type.Term(\";\",[new t.type.Term(\"->\",[new t.type.Term(\"var\",[new t.type.Var(\"X\")]),new t.type.Term(\"nth\",[new t.type.Num(1,!1),new t.type.Var(\"X\"),new t.type.Var(\"Y\"),new t.type.Var(\"Z\"),new t.type.Var(\"_\")])]),new t.type.Term(\",\",[new t.type.Term(\">\",[new t.type.Var(\"X\"),new t.type.Num(0,!1)]),new t.type.Term(\",\",[new t.type.Term(\"nth\",[new t.type.Num(1,!1),new t.type.Var(\"X\"),new t.type.Var(\"Y\"),new t.type.Var(\"Z\"),new t.type.Var(\"_\")]),new t.type.Term(\"!\",[])])])]))],\"nth0/4\":[new t.type.Rule(new t.type.Term(\"nth0\",[new t.type.Var(\"X\"),new t.type.Var(\"Y\"),new t.type.Var(\"Z\"),new t.type.Var(\"W\")]),new t.type.Term(\";\",[new t.type.Term(\"->\",[new t.type.Term(\"var\",[new t.type.Var(\"X\")]),new t.type.Term(\"nth\",[new t.type.Num(0,!1),new t.type.Var(\"X\"),new t.type.Var(\"Y\"),new t.type.Var(\"Z\"),new t.type.Var(\"W\")])]),new t.type.Term(\",\",[new t.type.Term(\">=\",[new t.type.Var(\"X\"),new t.type.Num(0,!1)]),new t.type.Term(\",\",[new t.type.Term(\"nth\",[new t.type.Num(0,!1),new t.type.Var(\"X\"),new t.type.Var(\"Y\"),new t.type.Var(\"Z\"),new t.type.Var(\"W\")]),new t.type.Term(\"!\",[])])])]))],\"nth1/4\":[new t.type.Rule(new t.type.Term(\"nth1\",[new t.type.Var(\"X\"),new t.type.Var(\"Y\"),new t.type.Var(\"Z\"),new t.type.Var(\"W\")]),new t.type.Term(\";\",[new t.type.Term(\"->\",[new t.type.Term(\"var\",[new t.type.Var(\"X\")]),new t.type.Term(\"nth\",[new t.type.Num(1,!1),new t.type.Var(\"X\"),new t.type.Var(\"Y\"),new t.type.Var(\"Z\"),new t.type.Var(\"W\")])]),new t.type.Term(\",\",[new t.type.Term(\">\",[new t.type.Var(\"X\"),new t.type.Num(0,!1)]),new t.type.Term(\",\",[new t.type.Term(\"nth\",[new t.type.Num(1,!1),new t.type.Var(\"X\"),new t.type.Var(\"Y\"),new t.type.Var(\"Z\"),new t.type.Var(\"W\")]),new t.type.Term(\"!\",[])])])]))],\"nth/5\":[new t.type.Rule(new t.type.Term(\"nth\",[new t.type.Var(\"N\"),new t.type.Var(\"N\"),new t.type.Term(\".\",[new t.type.Var(\"X\"),new t.type.Var(\"Xs\")]),new t.type.Var(\"X\"),new t.type.Var(\"Xs\")]),null),new t.type.Rule(new t.type.Term(\"nth\",[new t.type.Var(\"N\"),new t.type.Var(\"O\"),new t.type.Term(\".\",[new t.type.Var(\"X\"),new t.type.Var(\"Xs\")]),new t.type.Var(\"Y\"),new t.type.Term(\".\",[new t.type.Var(\"X\"),new t.type.Var(\"Ys\")])]),new t.type.Term(\",\",[new t.type.Term(\"is\",[new t.type.Var(\"M\"),new t.type.Term(\"+\",[new t.type.Var(\"N\"),new t.type.Num(1,!1)])]),new t.type.Term(\"nth\",[new t.type.Var(\"M\"),new t.type.Var(\"O\"),new t.type.Var(\"Xs\"),new t.type.Var(\"Y\"),new t.type.Var(\"Ys\")])]))],\"length/2\":function(s,a,n){var c=n.args[0],f=n.args[1];if(!t.type.is_variable(f)&&!t.type.is_integer(f))s.throw_error(t.error.type(\"integer\",f,n.indicator));else if(t.type.is_integer(f)&&f.value<0)s.throw_error(t.error.domain(\"not_less_than_zero\",f,n.indicator));else{var p=new t.type.Term(\"length\",[c,new t.type.Num(0,!1),f]);t.type.is_integer(f)&&(p=new t.type.Term(\",\",[p,new t.type.Term(\"!\",[])])),s.prepend([new t.type.State(a.goal.replace(p),a.substitution,a)])}},\"length/3\":[new t.type.Rule(new t.type.Term(\"length\",[new t.type.Term(\"[]\",[]),new t.type.Var(\"N\"),new t.type.Var(\"N\")]),null),new t.type.Rule(new t.type.Term(\"length\",[new t.type.Term(\".\",[new t.type.Var(\"_\"),new t.type.Var(\"X\")]),new t.type.Var(\"A\"),new t.type.Var(\"N\")]),new t.type.Term(\",\",[new t.type.Term(\"succ\",[new t.type.Var(\"A\"),new t.type.Var(\"B\")]),new t.type.Term(\"length\",[new t.type.Var(\"X\"),new t.type.Var(\"B\"),new t.type.Var(\"N\")])]))],\"replicate/3\":function(s,a,n){var c=n.args[0],f=n.args[1],p=n.args[2];if(t.type.is_variable(f))s.throw_error(t.error.instantiation(n.indicator));else if(!t.type.is_integer(f))s.throw_error(t.error.type(\"integer\",f,n.indicator));else if(f.value<0)s.throw_error(t.error.domain(\"not_less_than_zero\",f,n.indicator));else if(!t.type.is_variable(p)&&!t.type.is_list(p))s.throw_error(t.error.type(\"list\",p,n.indicator));else{for(var h=new t.type.Term(\"[]\"),E=0;E<f.value;E++)h=new t.type.Term(\".\",[c,h]);s.prepend([new t.type.State(a.goal.replace(new t.type.Term(\"=\",[h,p])),a.substitution,a)])}},\"sort/2\":function(s,a,n){var c=n.args[0],f=n.args[1];if(t.type.is_variable(c))s.throw_error(t.error.instantiation(n.indicator));else if(!t.type.is_variable(f)&&!t.type.is_fully_list(f))s.throw_error(t.error.type(\"list\",f,n.indicator));else{for(var p=[],h=c;h.indicator===\"./2\";)p.push(h.args[0]),h=h.args[1];if(t.type.is_variable(h))s.throw_error(t.error.instantiation(n.indicator));else if(!t.type.is_empty_list(h))s.throw_error(t.error.type(\"list\",c,n.indicator));else{for(var E=p.sort(t.compare),C=E.length-1;C>0;C--)E[C].equals(E[C-1])&&E.splice(C,1);for(var S=new t.type.Term(\"[]\"),C=E.length-1;C>=0;C--)S=new t.type.Term(\".\",[E[C],S]);s.prepend([new t.type.State(a.goal.replace(new t.type.Term(\"=\",[S,f])),a.substitution,a)])}}},\"msort/2\":function(s,a,n){var c=n.args[0],f=n.args[1];if(t.type.is_variable(c))s.throw_error(t.error.instantiation(n.indicator));else if(!t.type.is_variable(f)&&!t.type.is_fully_list(f))s.throw_error(t.error.type(\"list\",f,n.indicator));else{for(var p=[],h=c;h.indicator===\"./2\";)p.push(h.args[0]),h=h.args[1];if(t.type.is_variable(h))s.throw_error(t.error.instantiation(n.indicator));else if(!t.type.is_empty_list(h))s.throw_error(t.error.type(\"list\",c,n.indicator));else{for(var E=p.sort(t.compare),C=new t.type.Term(\"[]\"),S=E.length-1;S>=0;S--)C=new t.type.Term(\".\",[E[S],C]);s.prepend([new t.type.State(a.goal.replace(new t.type.Term(\"=\",[C,f])),a.substitution,a)])}}},\"keysort/2\":function(s,a,n){var c=n.args[0],f=n.args[1];if(t.type.is_variable(c))s.throw_error(t.error.instantiation(n.indicator));else if(!t.type.is_variable(f)&&!t.type.is_fully_list(f))s.throw_error(t.error.type(\"list\",f,n.indicator));else{for(var p=[],h,E=c;E.indicator===\"./2\";){if(h=E.args[0],t.type.is_variable(h)){s.throw_error(t.error.instantiation(n.indicator));return}else if(!t.type.is_term(h)||h.indicator!==\"-/2\"){s.throw_error(t.error.type(\"pair\",h,n.indicator));return}h.args[0].pair=h.args[1],p.push(h.args[0]),E=E.args[1]}if(t.type.is_variable(E))s.throw_error(t.error.instantiation(n.indicator));else if(!t.type.is_empty_list(E))s.throw_error(t.error.type(\"list\",c,n.indicator));else{for(var C=p.sort(t.compare),S=new t.type.Term(\"[]\"),P=C.length-1;P>=0;P--)S=new t.type.Term(\".\",[new t.type.Term(\"-\",[C[P],C[P].pair]),S]),delete C[P].pair;s.prepend([new t.type.State(a.goal.replace(new t.type.Term(\"=\",[S,f])),a.substitution,a)])}}},\"take/3\":function(s,a,n){var c=n.args[0],f=n.args[1],p=n.args[2];if(t.type.is_variable(f)||t.type.is_variable(c))s.throw_error(t.error.instantiation(n.indicator));else if(!t.type.is_list(f))s.throw_error(t.error.type(\"list\",f,n.indicator));else if(!t.type.is_integer(c))s.throw_error(t.error.type(\"integer\",c,n.indicator));else if(!t.type.is_variable(p)&&!t.type.is_list(p))s.throw_error(t.error.type(\"list\",p,n.indicator));else{for(var h=c.value,E=[],C=f;h>0&&C.indicator===\"./2\";)E.push(C.args[0]),C=C.args[1],h--;if(h===0){for(var S=new t.type.Term(\"[]\"),h=E.length-1;h>=0;h--)S=new t.type.Term(\".\",[E[h],S]);s.prepend([new t.type.State(a.goal.replace(new t.type.Term(\"=\",[S,p])),a.substitution,a)])}}},\"drop/3\":function(s,a,n){var c=n.args[0],f=n.args[1],p=n.args[2];if(t.type.is_variable(f)||t.type.is_variable(c))s.throw_error(t.error.instantiation(n.indicator));else if(!t.type.is_list(f))s.throw_error(t.error.type(\"list\",f,n.indicator));else if(!t.type.is_integer(c))s.throw_error(t.error.type(\"integer\",c,n.indicator));else if(!t.type.is_variable(p)&&!t.type.is_list(p))s.throw_error(t.error.type(\"list\",p,n.indicator));else{for(var h=c.value,E=[],C=f;h>0&&C.indicator===\"./2\";)E.push(C.args[0]),C=C.args[1],h--;h===0&&s.prepend([new t.type.State(a.goal.replace(new t.type.Term(\"=\",[C,p])),a.substitution,a)])}},\"reverse/2\":function(s,a,n){var c=n.args[0],f=n.args[1],p=t.type.is_instantiated_list(c),h=t.type.is_instantiated_list(f);if(t.type.is_variable(c)&&t.type.is_variable(f))s.throw_error(t.error.instantiation(n.indicator));else if(!t.type.is_variable(c)&&!t.type.is_fully_list(c))s.throw_error(t.error.type(\"list\",c,n.indicator));else if(!t.type.is_variable(f)&&!t.type.is_fully_list(f))s.throw_error(t.error.type(\"list\",f,n.indicator));else if(!p&&!h)s.throw_error(t.error.instantiation(n.indicator));else{for(var E=p?c:f,C=new t.type.Term(\"[]\",[]);E.indicator===\"./2\";)C=new t.type.Term(\".\",[E.args[0],C]),E=E.args[1];s.prepend([new t.type.State(a.goal.replace(new t.type.Term(\"=\",[C,p?f:c])),a.substitution,a)])}},\"list_to_set/2\":function(s,a,n){var c=n.args[0],f=n.args[1];if(t.type.is_variable(c))s.throw_error(t.error.instantiation(n.indicator));else{for(var p=c,h=[];p.indicator===\"./2\";)h.push(p.args[0]),p=p.args[1];if(t.type.is_variable(p))s.throw_error(t.error.instantiation(n.indicator));else if(!t.type.is_term(p)||p.indicator!==\"[]/0\")s.throw_error(t.error.type(\"list\",c,n.indicator));else{for(var E=[],C=new t.type.Term(\"[]\",[]),S,P=0;P<h.length;P++){S=!1;for(var I=0;I<E.length&&!S;I++)S=t.compare(h[P],E[I])===0;S||E.push(h[P])}for(P=E.length-1;P>=0;P--)C=new t.type.Term(\".\",[E[P],C]);s.prepend([new t.type.State(a.goal.replace(new t.type.Term(\"=\",[f,C])),a.substitution,a)])}}}}},r=[\"append/2\",\"append/3\",\"member/2\",\"permutation/2\",\"maplist/2\",\"maplist/3\",\"maplist/4\",\"maplist/5\",\"maplist/6\",\"maplist/7\",\"maplist/8\",\"include/3\",\"exclude/3\",\"foldl/4\",\"sum_list/2\",\"max_list/2\",\"min_list/2\",\"prod_list/2\",\"last/2\",\"prefix/2\",\"nth0/3\",\"nth1/3\",\"nth0/4\",\"nth1/4\",\"length/2\",\"replicate/3\",\"select/3\",\"sort/2\",\"msort/2\",\"keysort/2\",\"take/3\",\"drop/3\",\"reverse/2\",\"list_to_set/2\"];typeof X5<\"u\"?X5.exports=function(s){t=s,new t.type.Module(\"lists\",e(),r)}:new t.type.Module(\"lists\",e(),r)})(PSt)});var rve=L($r=>{\"use strict\";var Dm=process.platform===\"win32\",$5=\"aes-256-cbc\",xSt=\"sha256\",GBe=\"The current environment doesn't support interactive reading from TTY.\",si=Ie(\"fs\"),jBe=process.binding(\"tty_wrap\").TTY,t9=Ie(\"child_process\"),Y0=Ie(\"path\"),r9={prompt:\"> \",hideEchoBack:!1,mask:\"*\",limit:[],limitMessage:\"Input another, please.$<( [)limit(])>\",defaultInput:\"\",trueValue:[],falseValue:[],caseSensitive:!1,keepWhitespace:!1,encoding:\"utf8\",bufferSize:1024,print:void 0,history:!0,cd:!1,phContent:void 0,preCheck:void 0},$p=\"none\",$u,VC,qBe=!1,W0,lF,e9,kSt=0,a9=\"\",Sm=[],cF,WBe=!1,n9=!1,dS=!1;function YBe(t){function e(r){return r.replace(/[^\\w\\u0080-\\uFFFF]/g,function(s){return\"#\"+s.charCodeAt(0)+\";\"})}return lF.concat(function(r){var s=[];return Object.keys(r).forEach(function(a){r[a]===\"boolean\"?t[a]&&s.push(\"--\"+a):r[a]===\"string\"&&t[a]&&s.push(\"--\"+a,e(t[a]))}),s}({display:\"string\",displayOnly:\"boolean\",keyIn:\"boolean\",hideEchoBack:\"boolean\",mask:\"string\",limit:\"string\",caseSensitive:\"boolean\"}))}function QSt(t,e){function r(U){var W,te=\"\",ie;for(e9=e9||Ie(\"os\").tmpdir();;){W=Y0.join(e9,U+te);try{ie=si.openSync(W,\"wx\")}catch(Ae){if(Ae.code===\"EEXIST\"){te++;continue}else throw Ae}si.closeSync(ie);break}return W}var s,a,n,c={},f,p,h=r(\"readline-sync.stdout\"),E=r(\"readline-sync.stderr\"),C=r(\"readline-sync.exit\"),S=r(\"readline-sync.done\"),P=Ie(\"crypto\"),I,R,N;I=P.createHash(xSt),I.update(\"\"+process.pid+kSt+++Math.random()),N=I.digest(\"hex\"),R=P.createDecipher($5,N),s=YBe(t),Dm?(a=process.env.ComSpec||\"cmd.exe\",process.env.Q='\"',n=[\"/V:ON\",\"/S\",\"/C\",\"(%Q%\"+a+\"%Q% /V:ON /S /C %Q%%Q%\"+W0+\"%Q%\"+s.map(function(U){return\" %Q%\"+U+\"%Q%\"}).join(\"\")+\" & (echo !ERRORLEVEL!)>%Q%\"+C+\"%Q%%Q%) 2>%Q%\"+E+\"%Q% |%Q%\"+process.execPath+\"%Q% %Q%\"+__dirname+\"\\\\encrypt.js%Q% %Q%\"+$5+\"%Q% %Q%\"+N+\"%Q% >%Q%\"+h+\"%Q% & (echo 1)>%Q%\"+S+\"%Q%\"]):(a=\"/bin/sh\",n=[\"-c\",'(\"'+W0+'\"'+s.map(function(U){return\" '\"+U.replace(/'/g,\"'\\\\''\")+\"'\"}).join(\"\")+'; echo $?>\"'+C+'\") 2>\"'+E+'\" |\"'+process.execPath+'\" \"'+__dirname+'/encrypt.js\" \"'+$5+'\" \"'+N+'\" >\"'+h+'\"; echo 1 >\"'+S+'\"']),dS&&dS(\"_execFileSync\",s);try{t9.spawn(a,n,e)}catch(U){c.error=new Error(U.message),c.error.method=\"_execFileSync - spawn\",c.error.program=a,c.error.args=n}for(;si.readFileSync(S,{encoding:t.encoding}).trim()!==\"1\";);return(f=si.readFileSync(C,{encoding:t.encoding}).trim())===\"0\"?c.input=R.update(si.readFileSync(h,{encoding:\"binary\"}),\"hex\",t.encoding)+R.final(t.encoding):(p=si.readFileSync(E,{encoding:t.encoding}).trim(),c.error=new Error(GBe+(p?`\n`+p:\"\")),c.error.method=\"_execFileSync\",c.error.program=a,c.error.args=n,c.error.extMessage=p,c.error.exitCode=+f),si.unlinkSync(h),si.unlinkSync(E),si.unlinkSync(C),si.unlinkSync(S),c}function TSt(t){var e,r={},s,a={env:process.env,encoding:t.encoding};if(W0||(Dm?process.env.PSModulePath?(W0=\"powershell.exe\",lF=[\"-ExecutionPolicy\",\"Bypass\",\"-File\",__dirname+\"\\\\read.ps1\"]):(W0=\"cscript.exe\",lF=[\"//nologo\",__dirname+\"\\\\read.cs.js\"]):(W0=\"/bin/sh\",lF=[__dirname+\"/read.sh\"])),Dm&&!process.env.PSModulePath&&(a.stdio=[process.stdin]),t9.execFileSync){e=YBe(t),dS&&dS(\"execFileSync\",e);try{r.input=t9.execFileSync(W0,e,a)}catch(n){s=n.stderr?(n.stderr+\"\").trim():\"\",r.error=new Error(GBe+(s?`\n`+s:\"\")),r.error.method=\"execFileSync\",r.error.program=W0,r.error.args=e,r.error.extMessage=s,r.error.exitCode=n.status,r.error.code=n.code,r.error.signal=n.signal}}else r=QSt(t,a);return r.error||(r.input=r.input.replace(/^\\s*'|'\\s*$/g,\"\"),t.display=\"\"),r}function i9(t){var e=\"\",r=t.display,s=!t.display&&t.keyIn&&t.hideEchoBack&&!t.mask;function a(){var n=TSt(t);if(n.error)throw n.error;return n.input}return n9&&n9(t),function(){var n,c,f;function p(){return n||(n=process.binding(\"fs\"),c=process.binding(\"constants\")),n}if(typeof $p==\"string\")if($p=null,Dm){if(f=function(h){var E=h.replace(/^\\D+/,\"\").split(\".\"),C=0;return(E[0]=+E[0])&&(C+=E[0]*1e4),(E[1]=+E[1])&&(C+=E[1]*100),(E[2]=+E[2])&&(C+=E[2]),C}(process.version),!(f>=20302&&f<40204||f>=5e4&&f<50100||f>=50600&&f<60200)&&process.stdin.isTTY)process.stdin.pause(),$p=process.stdin.fd,VC=process.stdin._handle;else try{$p=p().open(\"CONIN$\",c.O_RDWR,parseInt(\"0666\",8)),VC=new jBe($p,!0)}catch{}if(process.stdout.isTTY)$u=process.stdout.fd;else{try{$u=si.openSync(\"\\\\\\\\.\\\\CON\",\"w\")}catch{}if(typeof $u!=\"number\")try{$u=p().open(\"CONOUT$\",c.O_RDWR,parseInt(\"0666\",8))}catch{}}}else{if(process.stdin.isTTY){process.stdin.pause();try{$p=si.openSync(\"/dev/tty\",\"r\"),VC=process.stdin._handle}catch{}}else try{$p=si.openSync(\"/dev/tty\",\"r\"),VC=new jBe($p,!1)}catch{}if(process.stdout.isTTY)$u=process.stdout.fd;else try{$u=si.openSync(\"/dev/tty\",\"w\")}catch{}}}(),function(){var n,c,f=!t.hideEchoBack&&!t.keyIn,p,h,E,C,S;cF=\"\";function P(I){return I===qBe?!0:VC.setRawMode(I)!==0?!1:(qBe=I,!0)}if(WBe||!VC||typeof $u!=\"number\"&&(t.display||!f)){e=a();return}if(t.display&&(si.writeSync($u,t.display),t.display=\"\"),!t.displayOnly){if(!P(!f)){e=a();return}for(h=t.keyIn?1:t.bufferSize,p=Buffer.allocUnsafe&&Buffer.alloc?Buffer.alloc(h):new Buffer(h),t.keyIn&&t.limit&&(c=new RegExp(\"[^\"+t.limit+\"]\",\"g\"+(t.caseSensitive?\"\":\"i\")));;){E=0;try{E=si.readSync($p,p,0,h)}catch(I){if(I.code!==\"EOF\"){P(!1),e+=a();return}}if(E>0?(C=p.toString(t.encoding,0,E),cF+=C):(C=`\n`,cF+=\"\\0\"),C&&typeof(S=(C.match(/^(.*?)[\\r\\n]/)||[])[1])==\"string\"&&(C=S,n=!0),C&&(C=C.replace(/[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]/g,\"\")),C&&c&&(C=C.replace(c,\"\")),C&&(f||(t.hideEchoBack?t.mask&&si.writeSync($u,new Array(C.length+1).join(t.mask)):si.writeSync($u,C)),e+=C),!t.keyIn&&n||t.keyIn&&e.length>=h)break}!f&&!s&&si.writeSync($u,`\n`),P(!1)}}(),t.print&&!s&&t.print(r+(t.displayOnly?\"\":(t.hideEchoBack?new Array(e.length+1).join(t.mask):e)+`\n`),t.encoding),t.displayOnly?\"\":a9=t.keepWhitespace||t.keyIn?e:e.trim()}function RSt(t,e){var r=[];function s(a){a!=null&&(Array.isArray(a)?a.forEach(s):(!e||e(a))&&r.push(a))}return s(t),r}function l9(t){return t.replace(/[\\x00-\\x7f]/g,function(e){return\"\\\\x\"+(\"00\"+e.charCodeAt().toString(16)).substr(-2)})}function Js(){var t=Array.prototype.slice.call(arguments),e,r;return t.length&&typeof t[0]==\"boolean\"&&(r=t.shift(),r&&(e=Object.keys(r9),t.unshift(r9))),t.reduce(function(s,a){return a==null||(a.hasOwnProperty(\"noEchoBack\")&&!a.hasOwnProperty(\"hideEchoBack\")&&(a.hideEchoBack=a.noEchoBack,delete a.noEchoBack),a.hasOwnProperty(\"noTrim\")&&!a.hasOwnProperty(\"keepWhitespace\")&&(a.keepWhitespace=a.noTrim,delete a.noTrim),r||(e=Object.keys(a)),e.forEach(function(n){var c;if(a.hasOwnProperty(n))switch(c=a[n],n){case\"mask\":case\"limitMessage\":case\"defaultInput\":case\"encoding\":c=c!=null?c+\"\":\"\",c&&n!==\"limitMessage\"&&(c=c.replace(/[\\r\\n]/g,\"\")),s[n]=c;break;case\"bufferSize\":!isNaN(c=parseInt(c,10))&&typeof c==\"number\"&&(s[n]=c);break;case\"displayOnly\":case\"keyIn\":case\"hideEchoBack\":case\"caseSensitive\":case\"keepWhitespace\":case\"history\":case\"cd\":s[n]=!!c;break;case\"limit\":case\"trueValue\":case\"falseValue\":s[n]=RSt(c,function(f){var p=typeof f;return p===\"string\"||p===\"number\"||p===\"function\"||f instanceof RegExp}).map(function(f){return typeof f==\"string\"?f.replace(/[\\r\\n]/g,\"\"):f});break;case\"print\":case\"phContent\":case\"preCheck\":s[n]=typeof c==\"function\"?c:void 0;break;case\"prompt\":case\"display\":s[n]=c??\"\";break}})),s},{})}function s9(t,e,r){return e.some(function(s){var a=typeof s;return a===\"string\"?r?t===s:t.toLowerCase()===s.toLowerCase():a===\"number\"?parseFloat(t)===s:a===\"function\"?s(t):s instanceof RegExp?s.test(t):!1})}function c9(t,e){var r=Y0.normalize(Dm?(process.env.HOMEDRIVE||\"\")+(process.env.HOMEPATH||\"\"):process.env.HOME||\"\").replace(/[\\/\\\\]+$/,\"\");return t=Y0.normalize(t),e?t.replace(/^~(?=\\/|\\\\|$)/,r):t.replace(new RegExp(\"^\"+l9(r)+\"(?=\\\\/|\\\\\\\\|$)\",Dm?\"i\":\"\"),\"~\")}function KC(t,e){var r=\"(?:\\\\(([\\\\s\\\\S]*?)\\\\))?(\\\\w+|.-.)(?:\\\\(([\\\\s\\\\S]*?)\\\\))?\",s=new RegExp(\"(\\\\$)?(\\\\$<\"+r+\">)\",\"g\"),a=new RegExp(\"(\\\\$)?(\\\\$\\\\{\"+r+\"\\\\})\",\"g\");function n(c,f,p,h,E,C){var S;return f||typeof(S=e(E))!=\"string\"?p:S?(h||\"\")+S+(C||\"\"):\"\"}return t.replace(s,n).replace(a,n)}function VBe(t,e,r){var s,a=[],n=-1,c=0,f=\"\",p;function h(E,C){return C.length>3?(E.push(C[0]+\"...\"+C[C.length-1]),p=!0):C.length&&(E=E.concat(C)),E}return s=t.reduce(function(E,C){return E.concat((C+\"\").split(\"\"))},[]).reduce(function(E,C){var S,P;return e||(C=C.toLowerCase()),S=/^\\d$/.test(C)?1:/^[A-Z]$/.test(C)?2:/^[a-z]$/.test(C)?3:0,r&&S===0?f+=C:(P=C.charCodeAt(0),S&&S===n&&P===c+1?a.push(C):(E=h(E,a),a=[C],n=S),c=P),E},[]),s=h(s,a),f&&(s.push(f),p=!0),{values:s,suppressed:p}}function KBe(t,e){return t.join(t.length>2?\", \":e?\" / \":\"/\")}function JBe(t,e){var r,s,a={},n;if(e.phContent&&(r=e.phContent(t,e)),typeof r!=\"string\")switch(t){case\"hideEchoBack\":case\"mask\":case\"defaultInput\":case\"caseSensitive\":case\"keepWhitespace\":case\"encoding\":case\"bufferSize\":case\"history\":case\"cd\":r=e.hasOwnProperty(t)?typeof e[t]==\"boolean\"?e[t]?\"on\":\"off\":e[t]+\"\":\"\";break;case\"limit\":case\"trueValue\":case\"falseValue\":s=e[e.hasOwnProperty(t+\"Src\")?t+\"Src\":t],e.keyIn?(a=VBe(s,e.caseSensitive),s=a.values):s=s.filter(function(c){var f=typeof c;return f===\"string\"||f===\"number\"}),r=KBe(s,a.suppressed);break;case\"limitCount\":case\"limitCountNotZero\":r=e[e.hasOwnProperty(\"limitSrc\")?\"limitSrc\":\"limit\"].length,r=r||t!==\"limitCountNotZero\"?r+\"\":\"\";break;case\"lastInput\":r=a9;break;case\"cwd\":case\"CWD\":case\"cwdHome\":r=process.cwd(),t===\"CWD\"?r=Y0.basename(r):t===\"cwdHome\"&&(r=c9(r));break;case\"date\":case\"time\":case\"localeDate\":case\"localeTime\":r=new Date()[\"to\"+t.replace(/^./,function(c){return c.toUpperCase()})+\"String\"]();break;default:typeof(n=(t.match(/^history_m(\\d+)$/)||[])[1])==\"string\"&&(r=Sm[Sm.length-n]||\"\")}return r}function zBe(t){var e=/^(.)-(.)$/.exec(t),r=\"\",s,a,n,c;if(!e)return null;for(s=e[1].charCodeAt(0),a=e[2].charCodeAt(0),c=s<a?1:-1,n=s;n!==a+c;n+=c)r+=String.fromCharCode(n);return r}function o9(t){var e=new RegExp(/(\\s*)(?:(\"|')(.*?)(?:\\2|$)|(\\S+))/g),r,s=\"\",a=[],n;for(t=t.trim();r=e.exec(t);)n=r[3]||r[4]||\"\",r[1]&&(a.push(s),s=\"\"),s+=n;return s&&a.push(s),a}function ZBe(t,e){return e.trueValue.length&&s9(t,e.trueValue,e.caseSensitive)?!0:e.falseValue.length&&s9(t,e.falseValue,e.caseSensitive)?!1:t}function XBe(t){var e,r,s,a,n,c,f;function p(E){return JBe(E,t)}function h(E){t.display+=(/[^\\r\\n]$/.test(t.display)?`\n`:\"\")+E}for(t.limitSrc=t.limit,t.displaySrc=t.display,t.limit=\"\",t.display=KC(t.display+\"\",p);;){if(e=i9(t),r=!1,s=\"\",t.defaultInput&&!e&&(e=t.defaultInput),t.history&&((a=/^\\s*\\!(?:\\!|-1)(:p)?\\s*$/.exec(e))?(n=Sm[0]||\"\",a[1]?r=!0:e=n,h(n+`\n`),r||(t.displayOnly=!0,i9(t),t.displayOnly=!1)):e&&e!==Sm[Sm.length-1]&&(Sm=[e])),!r&&t.cd&&e)switch(c=o9(e),c[0].toLowerCase()){case\"cd\":if(c[1])try{process.chdir(c9(c[1],!0))}catch(E){h(E+\"\")}r=!0;break;case\"pwd\":h(process.cwd()),r=!0;break}if(!r&&t.preCheck&&(f=t.preCheck(e,t),e=f.res,f.forceNext&&(r=!0)),!r){if(!t.limitSrc.length||s9(e,t.limitSrc,t.caseSensitive))break;t.limitMessage&&(s=KC(t.limitMessage,p))}h((s?s+`\n`:\"\")+KC(t.displaySrc+\"\",p))}return ZBe(e,t)}$r._DBG_set_useExt=function(t){WBe=t};$r._DBG_set_checkOptions=function(t){n9=t};$r._DBG_set_checkMethod=function(t){dS=t};$r._DBG_clearHistory=function(){a9=\"\",Sm=[]};$r.setDefaultOptions=function(t){return r9=Js(!0,t),Js(!0)};$r.question=function(t,e){return XBe(Js(Js(!0,e),{display:t}))};$r.prompt=function(t){var e=Js(!0,t);return e.display=e.prompt,XBe(e)};$r.keyIn=function(t,e){var r=Js(Js(!0,e),{display:t,keyIn:!0,keepWhitespace:!0});return r.limitSrc=r.limit.filter(function(s){var a=typeof s;return a===\"string\"||a===\"number\"}).map(function(s){return KC(s+\"\",zBe)}),r.limit=l9(r.limitSrc.join(\"\")),[\"trueValue\",\"falseValue\"].forEach(function(s){r[s]=r[s].reduce(function(a,n){var c=typeof n;return c===\"string\"||c===\"number\"?a=a.concat((n+\"\").split(\"\")):a.push(n),a},[])}),r.display=KC(r.display+\"\",function(s){return JBe(s,r)}),ZBe(i9(r),r)};$r.questionEMail=function(t,e){return t==null&&(t=\"Input e-mail address: \"),$r.question(t,Js({hideEchoBack:!1,limit:/^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/,limitMessage:\"Input valid e-mail address, please.\",trueValue:null,falseValue:null},e,{keepWhitespace:!1,cd:!1}))};$r.questionNewPassword=function(t,e){var r,s,a,n=Js({hideEchoBack:!0,mask:\"*\",limitMessage:`It can include: $<charlist>\nAnd the length must be: $<length>`,trueValue:null,falseValue:null,caseSensitive:!0},e,{history:!1,cd:!1,phContent:function(P){return P===\"charlist\"?r.text:P===\"length\"?s+\"...\"+a:null}}),c,f,p,h,E,C,S;for(e=e||{},c=KC(e.charlist?e.charlist+\"\":\"$<!-~>\",zBe),(isNaN(s=parseInt(e.min,10))||typeof s!=\"number\")&&(s=12),(isNaN(a=parseInt(e.max,10))||typeof a!=\"number\")&&(a=24),h=new RegExp(\"^[\"+l9(c)+\"]{\"+s+\",\"+a+\"}$\"),r=VBe([c],n.caseSensitive,!0),r.text=KBe(r.values,r.suppressed),f=e.confirmMessage!=null?e.confirmMessage:\"Reinput a same one to confirm it: \",p=e.unmatchMessage!=null?e.unmatchMessage:\"It differs from first one. Hit only the Enter key if you want to retry from first one.\",t==null&&(t=\"Input new password: \"),E=n.limitMessage;!S;)n.limit=h,n.limitMessage=E,C=$r.question(t,n),n.limit=[C,\"\"],n.limitMessage=p,S=$r.question(f,n);return C};function $Be(t,e,r){var s;function a(n){return s=r(n),!isNaN(s)&&typeof s==\"number\"}return $r.question(t,Js({limitMessage:\"Input valid number, please.\"},e,{limit:a,cd:!1})),s}$r.questionInt=function(t,e){return $Be(t,e,function(r){return parseInt(r,10)})};$r.questionFloat=function(t,e){return $Be(t,e,parseFloat)};$r.questionPath=function(t,e){var r,s=\"\",a=Js({hideEchoBack:!1,limitMessage:`$<error(\n)>Input valid path, please.$<( Min:)min>$<( Max:)max>`,history:!0,cd:!0},e,{keepWhitespace:!1,limit:function(n){var c,f,p;n=c9(n,!0),s=\"\";function h(E){E.split(/\\/|\\\\/).reduce(function(C,S){var P=Y0.resolve(C+=S+Y0.sep);if(!si.existsSync(P))si.mkdirSync(P);else if(!si.statSync(P).isDirectory())throw new Error(\"Non directory already exists: \"+P);return C},\"\")}try{if(c=si.existsSync(n),r=c?si.realpathSync(n):Y0.resolve(n),!e.hasOwnProperty(\"exists\")&&!c||typeof e.exists==\"boolean\"&&e.exists!==c)return s=(c?\"Already exists\":\"No such file or directory\")+\": \"+r,!1;if(!c&&e.create&&(e.isDirectory?h(r):(h(Y0.dirname(r)),si.closeSync(si.openSync(r,\"w\"))),r=si.realpathSync(r)),c&&(e.min||e.max||e.isFile||e.isDirectory)){if(f=si.statSync(r),e.isFile&&!f.isFile())return s=\"Not file: \"+r,!1;if(e.isDirectory&&!f.isDirectory())return s=\"Not directory: \"+r,!1;if(e.min&&f.size<+e.min||e.max&&f.size>+e.max)return s=\"Size \"+f.size+\" is out of range: \"+r,!1}if(typeof e.validate==\"function\"&&(p=e.validate(r))!==!0)return typeof p==\"string\"&&(s=p),!1}catch(E){return s=E+\"\",!1}return!0},phContent:function(n){return n===\"error\"?s:n!==\"min\"&&n!==\"max\"?null:e.hasOwnProperty(n)?e[n]+\"\":\"\"}});return e=e||{},t==null&&(t='Input path (you can \"cd\" and \"pwd\"): '),$r.question(t,a),r};function eve(t,e){var r={},s={};return typeof t==\"object\"?(Object.keys(t).forEach(function(a){typeof t[a]==\"function\"&&(s[e.caseSensitive?a:a.toLowerCase()]=t[a])}),r.preCheck=function(a){var n;return r.args=o9(a),n=r.args[0]||\"\",e.caseSensitive||(n=n.toLowerCase()),r.hRes=n!==\"_\"&&s.hasOwnProperty(n)?s[n].apply(a,r.args.slice(1)):s.hasOwnProperty(\"_\")?s._.apply(a,r.args):null,{res:a,forceNext:!1}},s.hasOwnProperty(\"_\")||(r.limit=function(){var a=r.args[0]||\"\";return e.caseSensitive||(a=a.toLowerCase()),s.hasOwnProperty(a)})):r.preCheck=function(a){return r.args=o9(a),r.hRes=typeof t==\"function\"?t.apply(a,r.args):!0,{res:a,forceNext:!1}},r}$r.promptCL=function(t,e){var r=Js({hideEchoBack:!1,limitMessage:\"Requested command is not available.\",caseSensitive:!1,history:!0},e),s=eve(t,r);return r.limit=s.limit,r.preCheck=s.preCheck,$r.prompt(r),s.args};$r.promptLoop=function(t,e){for(var r=Js({hideEchoBack:!1,trueValue:null,falseValue:null,caseSensitive:!1,history:!0},e);!t($r.prompt(r)););};$r.promptCLLoop=function(t,e){var r=Js({hideEchoBack:!1,limitMessage:\"Requested command is not available.\",caseSensitive:!1,history:!0},e),s=eve(t,r);for(r.limit=s.limit,r.preCheck=s.preCheck;$r.prompt(r),!s.hRes;);};$r.promptSimShell=function(t){return $r.prompt(Js({hideEchoBack:!1,history:!0},t,{prompt:function(){return Dm?\"$<cwd>>\":(process.env.USER||\"\")+(process.env.HOSTNAME?\"@\"+process.env.HOSTNAME.replace(/\\..*$/,\"\"):\"\")+\":$<cwdHome>$ \"}()}))};function tve(t,e,r){var s;return t==null&&(t=\"Are you sure? \"),(!e||e.guide!==!1)&&(t+=\"\")&&(t=t.replace(/\\s*:?\\s*$/,\"\")+\" [y/n]: \"),s=$r.keyIn(t,Js(e,{hideEchoBack:!1,limit:r,trueValue:\"y\",falseValue:\"n\",caseSensitive:!1})),typeof s==\"boolean\"?s:\"\"}$r.keyInYN=function(t,e){return tve(t,e)};$r.keyInYNStrict=function(t,e){return tve(t,e,\"yn\")};$r.keyInPause=function(t,e){t==null&&(t=\"Continue...\"),(!e||e.guide!==!1)&&(t+=\"\")&&(t=t.replace(/\\s+$/,\"\")+\" (Hit any key)\"),$r.keyIn(t,Js({limit:null},e,{hideEchoBack:!0,mask:\"\"}))};$r.keyInSelect=function(t,e,r){var s=Js({hideEchoBack:!1},r,{trueValue:null,falseValue:null,caseSensitive:!1,phContent:function(p){return p===\"itemsCount\"?t.length+\"\":p===\"firstItem\"?(t[0]+\"\").trim():p===\"lastItem\"?(t[t.length-1]+\"\").trim():null}}),a=\"\",n={},c=49,f=`\n`;if(!Array.isArray(t)||!t.length||t.length>35)throw\"`items` must be Array (max length: 35).\";return t.forEach(function(p,h){var E=String.fromCharCode(c);a+=E,n[E]=h,f+=\"[\"+E+\"] \"+(p+\"\").trim()+`\n`,c=c===57?97:c+1}),(!r||r.cancel!==!1)&&(a+=\"0\",n[0]=-1,f+=\"[0] \"+(r&&r.cancel!=null&&typeof r.cancel!=\"boolean\"?(r.cancel+\"\").trim():\"CANCEL\")+`\n`),s.limit=a,f+=`\n`,e==null&&(e=\"Choose one from list: \"),(e+=\"\")&&((!r||r.guide!==!1)&&(e=e.replace(/\\s*:?\\s*$/,\"\")+\" [$<limit>]: \"),f+=e),n[$r.keyIn(f,s).toLowerCase()]};$r.getRawInput=function(){return cF};function mS(t,e){var r;return e.length&&(r={},r[t]=e[0]),$r.setDefaultOptions(r)[t]}$r.setPrint=function(){return mS(\"print\",arguments)};$r.setPrompt=function(){return mS(\"prompt\",arguments)};$r.setEncoding=function(){return mS(\"encoding\",arguments)};$r.setMask=function(){return mS(\"mask\",arguments)};$r.setBufferSize=function(){return mS(\"bufferSize\",arguments)}});var u9=L((Tur,tc)=>{(function(){var t={major:0,minor:2,patch:66,status:\"beta\"};tau_file_system={files:{},open:function(w,b,y){var F=tau_file_system.files[w];if(!F){if(y===\"read\")return null;F={path:w,text:\"\",type:b,get:function(z,Z){return Z===this.text.length||Z>this.text.length?\"end_of_file\":this.text.substring(Z,Z+z)},put:function(z,Z){return Z===\"end_of_file\"?(this.text+=z,!0):Z===\"past_end_of_file\"?null:(this.text=this.text.substring(0,Z)+z+this.text.substring(Z+z.length),!0)},get_byte:function(z){if(z===\"end_of_stream\")return-1;var Z=Math.floor(z/2);if(this.text.length<=Z)return-1;var $=n(this.text[Math.floor(z/2)],0);return z%2===0?$&255:$/256>>>0},put_byte:function(z,Z){var $=Z===\"end_of_stream\"?this.text.length:Math.floor(Z/2);if(this.text.length<$)return null;var oe=this.text.length===$?-1:n(this.text[Math.floor(Z/2)],0);return Z%2===0?(oe=oe/256>>>0,oe=(oe&255)<<8|z&255):(oe=oe&255,oe=(z&255)<<8|oe&255),this.text.length===$?this.text+=c(oe):this.text=this.text.substring(0,$)+c(oe)+this.text.substring($+1),!0},flush:function(){return!0},close:function(){var z=tau_file_system.files[this.path];return z?!0:null}},tau_file_system.files[w]=F}return y===\"write\"&&(F.text=\"\"),F}},tau_user_input={buffer:\"\",get:function(w,b){for(var y;tau_user_input.buffer.length<w;)y=window.prompt(),y&&(tau_user_input.buffer+=y);return y=tau_user_input.buffer.substr(0,w),tau_user_input.buffer=tau_user_input.buffer.substr(w),y}},tau_user_output={put:function(w,b){return console.log(w),!0},flush:function(){return!0}},nodejs_file_system={open:function(w,b,y){var F=Ie(\"fs\"),z=F.openSync(w,y[0]);return y===\"read\"&&!F.existsSync(w)?null:{get:function(Z,$){var oe=new Buffer(Z);return F.readSync(z,oe,0,Z,$),oe.toString()},put:function(Z,$){var oe=Buffer.from(Z);if($===\"end_of_file\")F.writeSync(z,oe);else{if($===\"past_end_of_file\")return null;F.writeSync(z,oe,0,oe.length,$)}return!0},get_byte:function(Z){return null},put_byte:function(Z,$){return null},flush:function(){return!0},close:function(){return F.closeSync(z),!0}}}},nodejs_user_input={buffer:\"\",get:function(w,b){for(var y,F=rve();nodejs_user_input.buffer.length<w;)nodejs_user_input.buffer+=F.question();return y=nodejs_user_input.buffer.substr(0,w),nodejs_user_input.buffer=nodejs_user_input.buffer.substr(w),y}},nodejs_user_output={put:function(w,b){return process.stdout.write(w),!0},flush:function(){return!0}};var e;Array.prototype.indexOf?e=function(w,b){return w.indexOf(b)}:e=function(w,b){for(var y=w.length,F=0;F<y;F++)if(b===w[F])return F;return-1};var r=function(w,b){if(w.length!==0){for(var y=w[0],F=w.length,z=1;z<F;z++)y=b(y,w[z]);return y}},s;Array.prototype.map?s=function(w,b){return w.map(b)}:s=function(w,b){for(var y=[],F=w.length,z=0;z<F;z++)y.push(b(w[z]));return y};var a;Array.prototype.filter?a=function(w,b){return w.filter(b)}:a=function(w,b){for(var y=[],F=w.length,z=0;z<F;z++)b(w[z])&&y.push(w[z]);return y};var n;String.prototype.codePointAt?n=function(w,b){return w.codePointAt(b)}:n=function(w,b){return w.charCodeAt(b)};var c;String.fromCodePoint?c=function(){return String.fromCodePoint.apply(null,arguments)}:c=function(){return String.fromCharCode.apply(null,arguments)};var f=0,p=1,h=/(\\\\a)|(\\\\b)|(\\\\f)|(\\\\n)|(\\\\r)|(\\\\t)|(\\\\v)|\\\\x([0-9a-fA-F]+)\\\\|\\\\([0-7]+)\\\\|(\\\\\\\\)|(\\\\')|('')|(\\\\\")|(\\\\`)|(\\\\.)|(.)/g,E={\"\\\\a\":7,\"\\\\b\":8,\"\\\\f\":12,\"\\\\n\":10,\"\\\\r\":13,\"\\\\t\":9,\"\\\\v\":11};function C(w){var b=[],y=!1;return w.replace(h,function(F,z,Z,$,oe,xe,Te,lt,It,qt,ir,Pt,gn,Pr,Ir,Nr,nn){switch(!0){case It!==void 0:return b.push(parseInt(It,16)),\"\";case qt!==void 0:return b.push(parseInt(qt,8)),\"\";case ir!==void 0:case Pt!==void 0:case gn!==void 0:case Pr!==void 0:case Ir!==void 0:return b.push(n(F.substr(1),0)),\"\";case nn!==void 0:return b.push(n(nn,0)),\"\";case Nr!==void 0:y=!0;default:return b.push(E[F]),\"\"}}),y?null:b}function S(w,b){var y=\"\";if(w.length<2)return w;try{w=w.replace(/\\\\([0-7]+)\\\\/g,function($,oe){return c(parseInt(oe,8))}),w=w.replace(/\\\\x([0-9a-fA-F]+)\\\\/g,function($,oe){return c(parseInt(oe,16))})}catch{return null}for(var F=0;F<w.length;F++){var z=w.charAt(F),Z=w.charAt(F+1);if(z===b&&Z===b)F++,y+=b;else if(z===\"\\\\\")if([\"a\",\"b\",\"f\",\"n\",\"r\",\"t\",\"v\",\"'\",'\"',\"\\\\\",\"a\",\"\\b\",\"\\f\",`\n`,\"\\r\",\"\t\",\"\\v\"].indexOf(Z)!==-1)switch(F+=1,Z){case\"a\":y+=\"a\";break;case\"b\":y+=\"\\b\";break;case\"f\":y+=\"\\f\";break;case\"n\":y+=`\n`;break;case\"r\":y+=\"\\r\";break;case\"t\":y+=\"\t\";break;case\"v\":y+=\"\\v\";break;case\"'\":y+=\"'\";break;case'\"':y+='\"';break;case\"\\\\\":y+=\"\\\\\";break}else return null;else y+=z}return y}function P(w){for(var b=\"\",y=0;y<w.length;y++)switch(w.charAt(y)){case\"'\":b+=\"\\\\'\";break;case\"\\\\\":b+=\"\\\\\\\\\";break;case\"\\b\":b+=\"\\\\b\";break;case\"\\f\":b+=\"\\\\f\";break;case`\n`:b+=\"\\\\n\";break;case\"\\r\":b+=\"\\\\r\";break;case\"\t\":b+=\"\\\\t\";break;case\"\\v\":b+=\"\\\\v\";break;default:b+=w.charAt(y);break}return b}function I(w){var b=w.substr(2);switch(w.substr(0,2).toLowerCase()){case\"0x\":return parseInt(b,16);case\"0b\":return parseInt(b,2);case\"0o\":return parseInt(b,8);case\"0'\":return C(b)[0];default:return parseFloat(w)}}var R={whitespace:/^\\s*(?:(?:%.*)|(?:\\/\\*(?:\\n|\\r|.)*?\\*\\/)|(?:\\s+))\\s*/,variable:/^(?:[A-Z_][a-zA-Z0-9_]*)/,atom:/^(\\!|,|;|[a-z][0-9a-zA-Z_]*|[#\\$\\&\\*\\+\\-\\.\\/\\:\\<\\=\\>\\?\\@\\^\\~\\\\]+|'(?:[^']*?(?:\\\\(?:x?\\d+)?\\\\)*(?:'')*(?:\\\\')*)*')/,number:/^(?:0o[0-7]+|0x[0-9a-fA-F]+|0b[01]+|0'(?:''|\\\\[abfnrtv\\\\'\"`]|\\\\x?\\d+\\\\|[^\\\\])|\\d+(?:\\.\\d+(?:[eE][+-]?\\d+)?)?)/,string:/^(?:\"([^\"]|\"\"|\\\\\")*\"|`([^`]|``|\\\\`)*`)/,l_brace:/^(?:\\[)/,r_brace:/^(?:\\])/,l_bracket:/^(?:\\{)/,r_bracket:/^(?:\\})/,bar:/^(?:\\|)/,l_paren:/^(?:\\()/,r_paren:/^(?:\\))/};function N(w,b){return w.get_flag(\"char_conversion\").id===\"on\"?b.replace(/./g,function(y){return w.get_char_conversion(y)}):b}function U(w){this.thread=w,this.text=\"\",this.tokens=[]}U.prototype.set_last_tokens=function(w){return this.tokens=w},U.prototype.new_text=function(w){this.text=w,this.tokens=[]},U.prototype.get_tokens=function(w){var b,y=0,F=0,z=0,Z=[],$=!1;if(w){var oe=this.tokens[w-1];y=oe.len,b=N(this.thread,this.text.substr(oe.len)),F=oe.line,z=oe.start}else b=this.text;if(/^\\s*$/.test(b))return null;for(;b!==\"\";){var xe=[],Te=!1;if(/^\\n/.exec(b)!==null){F++,z=0,y++,b=b.replace(/\\n/,\"\"),$=!0;continue}for(var lt in R)if(R.hasOwnProperty(lt)){var It=R[lt].exec(b);It&&xe.push({value:It[0],name:lt,matches:It})}if(!xe.length)return this.set_last_tokens([{value:b,matches:[],name:\"lexical\",line:F,start:z}]);var oe=r(xe,function(Pr,Ir){return Pr.value.length>=Ir.value.length?Pr:Ir});switch(oe.start=z,oe.line=F,b=b.replace(oe.value,\"\"),z+=oe.value.length,y+=oe.value.length,oe.name){case\"atom\":oe.raw=oe.value,oe.value.charAt(0)===\"'\"&&(oe.value=S(oe.value.substr(1,oe.value.length-2),\"'\"),oe.value===null&&(oe.name=\"lexical\",oe.value=\"unknown escape sequence\"));break;case\"number\":oe.float=oe.value.substring(0,2)!==\"0x\"&&oe.value.match(/[.eE]/)!==null&&oe.value!==\"0'.\",oe.value=I(oe.value),oe.blank=Te;break;case\"string\":var qt=oe.value.charAt(0);oe.value=S(oe.value.substr(1,oe.value.length-2),qt),oe.value===null&&(oe.name=\"lexical\",oe.value=\"unknown escape sequence\");break;case\"whitespace\":var ir=Z[Z.length-1];ir&&(ir.space=!0),Te=!0;continue;case\"r_bracket\":Z.length>0&&Z[Z.length-1].name===\"l_bracket\"&&(oe=Z.pop(),oe.name=\"atom\",oe.value=\"{}\",oe.raw=\"{}\",oe.space=!1);break;case\"r_brace\":Z.length>0&&Z[Z.length-1].name===\"l_brace\"&&(oe=Z.pop(),oe.name=\"atom\",oe.value=\"[]\",oe.raw=\"[]\",oe.space=!1);break}oe.len=y,Z.push(oe),Te=!1}var Pt=this.set_last_tokens(Z);return Pt.length===0?null:Pt};function W(w,b,y,F,z){if(!b[y])return{type:f,value:x.error.syntax(b[y-1],\"expression expected\",!0)};var Z;if(F===\"0\"){var $=b[y];switch($.name){case\"number\":return{type:p,len:y+1,value:new x.type.Num($.value,$.float)};case\"variable\":return{type:p,len:y+1,value:new x.type.Var($.value)};case\"string\":var oe;switch(w.get_flag(\"double_quotes\").id){case\"atom\":oe=new j($.value,[]);break;case\"codes\":oe=new j(\"[]\",[]);for(var xe=$.value.length-1;xe>=0;xe--)oe=new j(\".\",[new x.type.Num(n($.value,xe),!1),oe]);break;case\"chars\":oe=new j(\"[]\",[]);for(var xe=$.value.length-1;xe>=0;xe--)oe=new j(\".\",[new x.type.Term($.value.charAt(xe),[]),oe]);break}return{type:p,len:y+1,value:oe};case\"l_paren\":var Pt=W(w,b,y+1,w.__get_max_priority(),!0);return Pt.type!==p?Pt:b[Pt.len]&&b[Pt.len].name===\"r_paren\"?(Pt.len++,Pt):{type:f,derived:!0,value:x.error.syntax(b[Pt.len]?b[Pt.len]:b[Pt.len-1],\") or operator expected\",!b[Pt.len])};case\"l_bracket\":var Pt=W(w,b,y+1,w.__get_max_priority(),!0);return Pt.type!==p?Pt:b[Pt.len]&&b[Pt.len].name===\"r_bracket\"?(Pt.len++,Pt.value=new j(\"{}\",[Pt.value]),Pt):{type:f,derived:!0,value:x.error.syntax(b[Pt.len]?b[Pt.len]:b[Pt.len-1],\"} or operator expected\",!b[Pt.len])}}var Te=te(w,b,y,z);return Te.type===p||Te.derived||(Te=ie(w,b,y),Te.type===p||Te.derived)?Te:{type:f,derived:!1,value:x.error.syntax(b[y],\"unexpected token\")}}var lt=w.__get_max_priority(),It=w.__get_next_priority(F),qt=y;if(b[y].name===\"atom\"&&b[y+1]&&(b[y].space||b[y+1].name!==\"l_paren\")){var $=b[y++],ir=w.__lookup_operator_classes(F,$.value);if(ir&&ir.indexOf(\"fy\")>-1){var Pt=W(w,b,y,F,z);if(Pt.type!==f)return $.value===\"-\"&&!$.space&&x.type.is_number(Pt.value)?{value:new x.type.Num(-Pt.value.value,Pt.value.is_float),len:Pt.len,type:p}:{value:new x.type.Term($.value,[Pt.value]),len:Pt.len,type:p};Z=Pt}else if(ir&&ir.indexOf(\"fx\")>-1){var Pt=W(w,b,y,It,z);if(Pt.type!==f)return{value:new x.type.Term($.value,[Pt.value]),len:Pt.len,type:p};Z=Pt}}y=qt;var Pt=W(w,b,y,It,z);if(Pt.type===p){y=Pt.len;var $=b[y];if(b[y]&&(b[y].name===\"atom\"&&w.__lookup_operator_classes(F,$.value)||b[y].name===\"bar\"&&w.__lookup_operator_classes(F,\"|\"))){var gn=It,Pr=F,ir=w.__lookup_operator_classes(F,$.value);if(ir.indexOf(\"xf\")>-1)return{value:new x.type.Term($.value,[Pt.value]),len:++Pt.len,type:p};if(ir.indexOf(\"xfx\")>-1){var Ir=W(w,b,y+1,gn,z);return Ir.type===p?{value:new x.type.Term($.value,[Pt.value,Ir.value]),len:Ir.len,type:p}:(Ir.derived=!0,Ir)}else if(ir.indexOf(\"xfy\")>-1){var Ir=W(w,b,y+1,Pr,z);return Ir.type===p?{value:new x.type.Term($.value,[Pt.value,Ir.value]),len:Ir.len,type:p}:(Ir.derived=!0,Ir)}else if(Pt.type!==f)for(;;){y=Pt.len;var $=b[y];if($&&$.name===\"atom\"&&w.__lookup_operator_classes(F,$.value)){var ir=w.__lookup_operator_classes(F,$.value);if(ir.indexOf(\"yf\")>-1)Pt={value:new x.type.Term($.value,[Pt.value]),len:++y,type:p};else if(ir.indexOf(\"yfx\")>-1){var Ir=W(w,b,++y,gn,z);if(Ir.type===f)return Ir.derived=!0,Ir;y=Ir.len,Pt={value:new x.type.Term($.value,[Pt.value,Ir.value]),len:y,type:p}}else break}else break}}else Z={type:f,value:x.error.syntax(b[Pt.len-1],\"operator expected\")};return Pt}return Pt}function te(w,b,y,F){if(!b[y]||b[y].name===\"atom\"&&b[y].raw===\".\"&&!F&&(b[y].space||!b[y+1]||b[y+1].name!==\"l_paren\"))return{type:f,derived:!1,value:x.error.syntax(b[y-1],\"unfounded token\")};var z=b[y],Z=[];if(b[y].name===\"atom\"&&b[y].raw!==\",\"){if(y++,b[y-1].space)return{type:p,len:y,value:new x.type.Term(z.value,Z)};if(b[y]&&b[y].name===\"l_paren\"){if(b[y+1]&&b[y+1].name===\"r_paren\")return{type:f,derived:!0,value:x.error.syntax(b[y+1],\"argument expected\")};var $=W(w,b,++y,\"999\",!0);if($.type===f)return $.derived?$:{type:f,derived:!0,value:x.error.syntax(b[y]?b[y]:b[y-1],\"argument expected\",!b[y])};for(Z.push($.value),y=$.len;b[y]&&b[y].name===\"atom\"&&b[y].value===\",\";){if($=W(w,b,y+1,\"999\",!0),$.type===f)return $.derived?$:{type:f,derived:!0,value:x.error.syntax(b[y+1]?b[y+1]:b[y],\"argument expected\",!b[y+1])};Z.push($.value),y=$.len}if(b[y]&&b[y].name===\"r_paren\")y++;else return{type:f,derived:!0,value:x.error.syntax(b[y]?b[y]:b[y-1],\", or ) expected\",!b[y])}}return{type:p,len:y,value:new x.type.Term(z.value,Z)}}return{type:f,derived:!1,value:x.error.syntax(b[y],\"term expected\")}}function ie(w,b,y){if(!b[y])return{type:f,derived:!1,value:x.error.syntax(b[y-1],\"[ expected\")};if(b[y]&&b[y].name===\"l_brace\"){var F=W(w,b,++y,\"999\",!0),z=[F.value],Z=void 0;if(F.type===f)return b[y]&&b[y].name===\"r_brace\"?{type:p,len:y+1,value:new x.type.Term(\"[]\",[])}:{type:f,derived:!0,value:x.error.syntax(b[y],\"] expected\")};for(y=F.len;b[y]&&b[y].name===\"atom\"&&b[y].value===\",\";){if(F=W(w,b,y+1,\"999\",!0),F.type===f)return F.derived?F:{type:f,derived:!0,value:x.error.syntax(b[y+1]?b[y+1]:b[y],\"argument expected\",!b[y+1])};z.push(F.value),y=F.len}var $=!1;if(b[y]&&b[y].name===\"bar\"){if($=!0,F=W(w,b,y+1,\"999\",!0),F.type===f)return F.derived?F:{type:f,derived:!0,value:x.error.syntax(b[y+1]?b[y+1]:b[y],\"argument expected\",!b[y+1])};Z=F.value,y=F.len}return b[y]&&b[y].name===\"r_brace\"?{type:p,len:y+1,value:g(z,Z)}:{type:f,derived:!0,value:x.error.syntax(b[y]?b[y]:b[y-1],$?\"] expected\":\", or | or ] expected\",!b[y])}}return{type:f,derived:!1,value:x.error.syntax(b[y],\"list expected\")}}function Ae(w,b,y){var F=b[y].line,z=W(w,b,y,w.__get_max_priority(),!1),Z=null,$;if(z.type!==f)if(y=z.len,b[y]&&b[y].name===\"atom\"&&b[y].raw===\".\")if(y++,x.type.is_term(z.value)){if(z.value.indicator===\":-/2\"?(Z=new x.type.Rule(z.value.args[0],Ce(z.value.args[1])),$={value:Z,len:y,type:p}):z.value.indicator===\"-->/2\"?(Z=pe(new x.type.Rule(z.value.args[0],z.value.args[1]),w),Z.body=Ce(Z.body),$={value:Z,len:y,type:x.type.is_rule(Z)?p:f}):(Z=new x.type.Rule(z.value,null),$={value:Z,len:y,type:p}),Z){var oe=Z.singleton_variables();oe.length>0&&w.throw_warning(x.warning.singleton(oe,Z.head.indicator,F))}return $}else return{type:f,value:x.error.syntax(b[y],\"callable expected\")};else return{type:f,value:x.error.syntax(b[y]?b[y]:b[y-1],\". or operator expected\")};return z}function ce(w,b,y){y=y||{},y.from=y.from?y.from:\"$tau-js\",y.reconsult=y.reconsult!==void 0?y.reconsult:!0;var F=new U(w),z={},Z;F.new_text(b);var $=0,oe=F.get_tokens($);do{if(oe===null||!oe[$])break;var xe=Ae(w,oe,$);if(xe.type===f)return new j(\"throw\",[xe.value]);if(xe.value.body===null&&xe.value.head.indicator===\"?-/1\"){var Te=new it(w.session);Te.add_goal(xe.value.head.args[0]),Te.answer(function(It){x.type.is_error(It)?w.throw_warning(It.args[0]):(It===!1||It===null)&&w.throw_warning(x.warning.failed_goal(xe.value.head.args[0],xe.len))}),$=xe.len;var lt=!0}else if(xe.value.body===null&&xe.value.head.indicator===\":-/1\"){var lt=w.run_directive(xe.value.head.args[0]);$=xe.len,xe.value.head.args[0].indicator===\"char_conversion/2\"&&(oe=F.get_tokens($),$=0)}else{Z=xe.value.head.indicator,y.reconsult!==!1&&z[Z]!==!0&&!w.is_multifile_predicate(Z)&&(w.session.rules[Z]=a(w.session.rules[Z]||[],function(qt){return qt.dynamic}),z[Z]=!0);var lt=w.add_rule(xe.value,y);$=xe.len}if(!lt)return lt}while(!0);return!0}function me(w,b){var y=new U(w);y.new_text(b);var F=0;do{var z=y.get_tokens(F);if(z===null)break;var Z=W(w,z,0,w.__get_max_priority(),!1);if(Z.type!==f){var $=Z.len,oe=$;if(z[$]&&z[$].name===\"atom\"&&z[$].raw===\".\")w.add_goal(Ce(Z.value));else{var xe=z[$];return new j(\"throw\",[x.error.syntax(xe||z[$-1],\". or operator expected\",!xe)])}F=Z.len+1}else return new j(\"throw\",[Z.value])}while(!0);return!0}function pe(w,b){w=w.rename(b);var y=b.next_free_variable(),F=Be(w.body,y,b);return F.error?F.value:(w.body=F.value,w.head.args=w.head.args.concat([y,F.variable]),w.head=new j(w.head.id,w.head.args),w)}function Be(w,b,y){var F;if(x.type.is_term(w)&&w.indicator===\"!/0\")return{value:w,variable:b,error:!1};if(x.type.is_term(w)&&w.indicator===\",/2\"){var z=Be(w.args[0],b,y);if(z.error)return z;var Z=Be(w.args[1],z.variable,y);return Z.error?Z:{value:new j(\",\",[z.value,Z.value]),variable:Z.variable,error:!1}}else{if(x.type.is_term(w)&&w.indicator===\"{}/1\")return{value:w.args[0],variable:b,error:!1};if(x.type.is_empty_list(w))return{value:new j(\"true\",[]),variable:b,error:!1};if(x.type.is_list(w)){F=y.next_free_variable();for(var $=w,oe;$.indicator===\"./2\";)oe=$,$=$.args[1];return x.type.is_variable($)?{value:x.error.instantiation(\"DCG\"),variable:b,error:!0}:x.type.is_empty_list($)?(oe.args[1]=F,{value:new j(\"=\",[b,w]),variable:F,error:!1}):{value:x.error.type(\"list\",w,\"DCG\"),variable:b,error:!0}}else return x.type.is_callable(w)?(F=y.next_free_variable(),w.args=w.args.concat([b,F]),w=new j(w.id,w.args),{value:w,variable:F,error:!1}):{value:x.error.type(\"callable\",w,\"DCG\"),variable:b,error:!0}}}function Ce(w){return x.type.is_variable(w)?new j(\"call\",[w]):x.type.is_term(w)&&[\",/2\",\";/2\",\"->/2\"].indexOf(w.indicator)!==-1?new j(w.id,[Ce(w.args[0]),Ce(w.args[1])]):w}function g(w,b){for(var y=b||new x.type.Term(\"[]\",[]),F=w.length-1;F>=0;F--)y=new x.type.Term(\".\",[w[F],y]);return y}function we(w,b){for(var y=w.length-1;y>=0;y--)w[y]===b&&w.splice(y,1)}function ye(w){for(var b={},y=[],F=0;F<w.length;F++)w[F]in b||(y.push(w[F]),b[w[F]]=!0);return y}function fe(w,b,y,F){if(w.session.rules[y]!==null){for(var z=0;z<w.session.rules[y].length;z++)if(w.session.rules[y][z]===F){w.session.rules[y].splice(z,1),w.success(b);break}}}function se(w){return function(b,y,F){var z=F.args[0],Z=F.args.slice(1,w);if(x.type.is_variable(z))b.throw_error(x.error.instantiation(b.level));else if(!x.type.is_callable(z))b.throw_error(x.error.type(\"callable\",z,b.level));else{var $=new j(z.id,z.args.concat(Z));b.prepend([new Pe(y.goal.replace($),y.substitution,y)])}}}function X(w){for(var b=w.length-1;b>=0;b--)if(w.charAt(b)===\"/\")return new j(\"/\",[new j(w.substring(0,b)),new Re(parseInt(w.substring(b+1)),!1)])}function De(w){this.id=w}function Re(w,b){this.is_float=b!==void 0?b:parseInt(w)!==w,this.value=this.is_float?w:parseInt(w)}var dt=0;function j(w,b,y){this.ref=y||++dt,this.id=w,this.args=b||[],this.indicator=w+\"/\"+this.args.length}var rt=0;function Fe(w,b,y,F,z,Z){this.id=rt++,this.stream=w,this.mode=b,this.alias=y,this.type=F!==void 0?F:\"text\",this.reposition=z!==void 0?z:!0,this.eof_action=Z!==void 0?Z:\"eof_code\",this.position=this.mode===\"append\"?\"end_of_stream\":0,this.output=this.mode===\"write\"||this.mode===\"append\",this.input=this.mode===\"read\"}function Ne(w){w=w||{},this.links=w}function Pe(w,b,y){b=b||new Ne,y=y||null,this.goal=w,this.substitution=b,this.parent=y}function Ye(w,b,y){this.head=w,this.body=b,this.dynamic=y||!1}function ke(w){w=w===void 0||w<=0?1e3:w,this.rules={},this.src_predicates={},this.rename=0,this.modules=[],this.thread=new it(this),this.total_threads=1,this.renamed_variables={},this.public_predicates={},this.multifile_predicates={},this.limit=w,this.streams={user_input:new Fe(typeof tc<\"u\"&&tc.exports?nodejs_user_input:tau_user_input,\"read\",\"user_input\",\"text\",!1,\"reset\"),user_output:new Fe(typeof tc<\"u\"&&tc.exports?nodejs_user_output:tau_user_output,\"write\",\"user_output\",\"text\",!1,\"eof_code\")},this.file_system=typeof tc<\"u\"&&tc.exports?nodejs_file_system:tau_file_system,this.standard_input=this.streams.user_input,this.standard_output=this.streams.user_output,this.current_input=this.streams.user_input,this.current_output=this.streams.user_output,this.format_success=function(b){return b.substitution},this.format_error=function(b){return b.goal},this.flag={bounded:x.flag.bounded.value,max_integer:x.flag.max_integer.value,min_integer:x.flag.min_integer.value,integer_rounding_function:x.flag.integer_rounding_function.value,char_conversion:x.flag.char_conversion.value,debug:x.flag.debug.value,max_arity:x.flag.max_arity.value,unknown:x.flag.unknown.value,double_quotes:x.flag.double_quotes.value,occurs_check:x.flag.occurs_check.value,dialect:x.flag.dialect.value,version_data:x.flag.version_data.value,nodejs:x.flag.nodejs.value},this.__loaded_modules=[],this.__char_conversion={},this.__operators={1200:{\":-\":[\"fx\",\"xfx\"],\"-->\":[\"xfx\"],\"?-\":[\"fx\"]},1100:{\";\":[\"xfy\"]},1050:{\"->\":[\"xfy\"]},1e3:{\",\":[\"xfy\"]},900:{\"\\\\+\":[\"fy\"]},700:{\"=\":[\"xfx\"],\"\\\\=\":[\"xfx\"],\"==\":[\"xfx\"],\"\\\\==\":[\"xfx\"],\"@<\":[\"xfx\"],\"@=<\":[\"xfx\"],\"@>\":[\"xfx\"],\"@>=\":[\"xfx\"],\"=..\":[\"xfx\"],is:[\"xfx\"],\"=:=\":[\"xfx\"],\"=\\\\=\":[\"xfx\"],\"<\":[\"xfx\"],\"=<\":[\"xfx\"],\">\":[\"xfx\"],\">=\":[\"xfx\"]},600:{\":\":[\"xfy\"]},500:{\"+\":[\"yfx\"],\"-\":[\"yfx\"],\"/\\\\\":[\"yfx\"],\"\\\\/\":[\"yfx\"]},400:{\"*\":[\"yfx\"],\"/\":[\"yfx\"],\"//\":[\"yfx\"],rem:[\"yfx\"],mod:[\"yfx\"],\"<<\":[\"yfx\"],\">>\":[\"yfx\"]},200:{\"**\":[\"xfx\"],\"^\":[\"xfy\"],\"-\":[\"fy\"],\"+\":[\"fy\"],\"\\\\\":[\"fy\"]}}}function it(w){this.epoch=Date.now(),this.session=w,this.session.total_threads++,this.total_steps=0,this.cpu_time=0,this.cpu_time_last=0,this.points=[],this.debugger=!1,this.debugger_states=[],this.level=\"top_level/0\",this.__calls=[],this.current_limit=this.session.limit,this.warnings=[]}function _e(w,b,y){this.id=w,this.rules=b,this.exports=y,x.module[w]=this}_e.prototype.exports_predicate=function(w){return this.exports.indexOf(w)!==-1},De.prototype.unify=function(w,b){if(b&&e(w.variables(),this.id)!==-1&&!x.type.is_variable(w))return null;var y={};return y[this.id]=w,new Ne(y)},Re.prototype.unify=function(w,b){return x.type.is_number(w)&&this.value===w.value&&this.is_float===w.is_float?new Ne:null},j.prototype.unify=function(w,b){if(x.type.is_term(w)&&this.indicator===w.indicator){for(var y=new Ne,F=0;F<this.args.length;F++){var z=x.unify(this.args[F].apply(y),w.args[F].apply(y),b);if(z===null)return null;for(var Z in z.links)y.links[Z]=z.links[Z];y=y.apply(z)}return y}return null},Fe.prototype.unify=function(w,b){return x.type.is_stream(w)&&this.id===w.id?new Ne:null},De.prototype.toString=function(w){return this.id},Re.prototype.toString=function(w){return this.is_float&&e(this.value.toString(),\".\")===-1?this.value+\".0\":this.value.toString()},j.prototype.toString=function(w,b,y){if(w=w||{},w.quoted=w.quoted===void 0?!0:w.quoted,w.ignore_ops=w.ignore_ops===void 0?!1:w.ignore_ops,w.numbervars=w.numbervars===void 0?!1:w.numbervars,b=b===void 0?1200:b,y=y===void 0?\"\":y,w.numbervars&&this.indicator===\"$VAR/1\"&&x.type.is_integer(this.args[0])&&this.args[0].value>=0){var F=this.args[0].value,z=Math.floor(F/26),Z=F%26;return\"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"[Z]+(z!==0?z:\"\")}switch(this.indicator){case\"[]/0\":case\"{}/0\":case\"!/0\":return this.id;case\"{}/1\":return\"{\"+this.args[0].toString(w)+\"}\";case\"./2\":for(var $=\"[\"+this.args[0].toString(w),oe=this.args[1];oe.indicator===\"./2\";)$+=\", \"+oe.args[0].toString(w),oe=oe.args[1];return oe.indicator!==\"[]/0\"&&($+=\"|\"+oe.toString(w)),$+=\"]\",$;case\",/2\":return\"(\"+this.args[0].toString(w)+\", \"+this.args[1].toString(w)+\")\";default:var xe=this.id,Te=w.session?w.session.lookup_operator(this.id,this.args.length):null;if(w.session===void 0||w.ignore_ops||Te===null)return w.quoted&&!/^(!|,|;|[a-z][0-9a-zA-Z_]*)$/.test(xe)&&xe!==\"{}\"&&xe!==\"[]\"&&(xe=\"'\"+P(xe)+\"'\"),xe+(this.args.length?\"(\"+s(this.args,function(ir){return ir.toString(w)}).join(\", \")+\")\":\"\");var lt=Te.priority>b.priority||Te.priority===b.priority&&(Te.class===\"xfy\"&&this.indicator!==b.indicator||Te.class===\"yfx\"&&this.indicator!==b.indicator||this.indicator===b.indicator&&Te.class===\"yfx\"&&y===\"right\"||this.indicator===b.indicator&&Te.class===\"xfy\"&&y===\"left\");Te.indicator=this.indicator;var It=lt?\"(\":\"\",qt=lt?\")\":\"\";return this.args.length===0?\"(\"+this.id+\")\":[\"fy\",\"fx\"].indexOf(Te.class)!==-1?It+xe+\" \"+this.args[0].toString(w,Te)+qt:[\"yf\",\"xf\"].indexOf(Te.class)!==-1?It+this.args[0].toString(w,Te)+\" \"+xe+qt:It+this.args[0].toString(w,Te,\"left\")+\" \"+this.id+\" \"+this.args[1].toString(w,Te,\"right\")+qt}},Fe.prototype.toString=function(w){return\"<stream>(\"+this.id+\")\"},Ne.prototype.toString=function(w){var b=\"{\";for(var y in this.links)this.links.hasOwnProperty(y)&&(b!==\"{\"&&(b+=\", \"),b+=y+\"/\"+this.links[y].toString(w));return b+=\"}\",b},Pe.prototype.toString=function(w){return this.goal===null?\"<\"+this.substitution.toString(w)+\">\":\"<\"+this.goal.toString(w)+\", \"+this.substitution.toString(w)+\">\"},Ye.prototype.toString=function(w){return this.body?this.head.toString(w)+\" :- \"+this.body.toString(w)+\".\":this.head.toString(w)+\".\"},ke.prototype.toString=function(w){for(var b=\"\",y=0;y<this.modules.length;y++)b+=\":- use_module(library(\"+this.modules[y]+`)).\n`;b+=`\n`;for(key in this.rules)for(y=0;y<this.rules[key].length;y++)b+=this.rules[key][y].toString(w),b+=`\n`;return b},De.prototype.clone=function(){return new De(this.id)},Re.prototype.clone=function(){return new Re(this.value,this.is_float)},j.prototype.clone=function(){return new j(this.id,s(this.args,function(w){return w.clone()}))},Fe.prototype.clone=function(){return new Stram(this.stream,this.mode,this.alias,this.type,this.reposition,this.eof_action)},Ne.prototype.clone=function(){var w={};for(var b in this.links)this.links.hasOwnProperty(b)&&(w[b]=this.links[b].clone());return new Ne(w)},Pe.prototype.clone=function(){return new Pe(this.goal.clone(),this.substitution.clone(),this.parent)},Ye.prototype.clone=function(){return new Ye(this.head.clone(),this.body!==null?this.body.clone():null)},De.prototype.equals=function(w){return x.type.is_variable(w)&&this.id===w.id},Re.prototype.equals=function(w){return x.type.is_number(w)&&this.value===w.value&&this.is_float===w.is_float},j.prototype.equals=function(w){if(!x.type.is_term(w)||this.indicator!==w.indicator)return!1;for(var b=0;b<this.args.length;b++)if(!this.args[b].equals(w.args[b]))return!1;return!0},Fe.prototype.equals=function(w){return x.type.is_stream(w)&&this.id===w.id},Ne.prototype.equals=function(w){var b;if(!x.type.is_substitution(w))return!1;for(b in this.links)if(this.links.hasOwnProperty(b)&&(!w.links[b]||!this.links[b].equals(w.links[b])))return!1;for(b in w.links)if(w.links.hasOwnProperty(b)&&!this.links[b])return!1;return!0},Pe.prototype.equals=function(w){return x.type.is_state(w)&&this.goal.equals(w.goal)&&this.substitution.equals(w.substitution)&&this.parent===w.parent},Ye.prototype.equals=function(w){return x.type.is_rule(w)&&this.head.equals(w.head)&&(this.body===null&&w.body===null||this.body!==null&&this.body.equals(w.body))},De.prototype.rename=function(w){return w.get_free_variable(this)},Re.prototype.rename=function(w){return this},j.prototype.rename=function(w){return new j(this.id,s(this.args,function(b){return b.rename(w)}))},Fe.prototype.rename=function(w){return this},Ye.prototype.rename=function(w){return new Ye(this.head.rename(w),this.body!==null?this.body.rename(w):null)},De.prototype.variables=function(){return[this.id]},Re.prototype.variables=function(){return[]},j.prototype.variables=function(){return[].concat.apply([],s(this.args,function(w){return w.variables()}))},Fe.prototype.variables=function(){return[]},Ye.prototype.variables=function(){return this.body===null?this.head.variables():this.head.variables().concat(this.body.variables())},De.prototype.apply=function(w){return w.lookup(this.id)?w.lookup(this.id):this},Re.prototype.apply=function(w){return this},j.prototype.apply=function(w){if(this.indicator===\"./2\"){for(var b=[],y=this;y.indicator===\"./2\";)b.push(y.args[0].apply(w)),y=y.args[1];for(var F=y.apply(w),z=b.length-1;z>=0;z--)F=new j(\".\",[b[z],F]);return F}return new j(this.id,s(this.args,function(Z){return Z.apply(w)}),this.ref)},Fe.prototype.apply=function(w){return this},Ye.prototype.apply=function(w){return new Ye(this.head.apply(w),this.body!==null?this.body.apply(w):null)},Ne.prototype.apply=function(w){var b,y={};for(b in this.links)this.links.hasOwnProperty(b)&&(y[b]=this.links[b].apply(w));return new Ne(y)},j.prototype.select=function(){for(var w=this;w.indicator===\",/2\";)w=w.args[0];return w},j.prototype.replace=function(w){return this.indicator===\",/2\"?this.args[0].indicator===\",/2\"?new j(\",\",[this.args[0].replace(w),this.args[1]]):w===null?this.args[1]:new j(\",\",[w,this.args[1]]):w},j.prototype.search=function(w){if(x.type.is_term(w)&&w.ref!==void 0&&this.ref===w.ref)return!0;for(var b=0;b<this.args.length;b++)if(x.type.is_term(this.args[b])&&this.args[b].search(w))return!0;return!1},ke.prototype.get_current_input=function(){return this.current_input},it.prototype.get_current_input=function(){return this.session.get_current_input()},ke.prototype.get_current_output=function(){return this.current_output},it.prototype.get_current_output=function(){return this.session.get_current_output()},ke.prototype.set_current_input=function(w){this.current_input=w},it.prototype.set_current_input=function(w){return this.session.set_current_input(w)},ke.prototype.set_current_output=function(w){this.current_input=w},it.prototype.set_current_output=function(w){return this.session.set_current_output(w)},ke.prototype.get_stream_by_alias=function(w){return this.streams[w]},it.prototype.get_stream_by_alias=function(w){return this.session.get_stream_by_alias(w)},ke.prototype.file_system_open=function(w,b,y){return this.file_system.open(w,b,y)},it.prototype.file_system_open=function(w,b,y){return this.session.file_system_open(w,b,y)},ke.prototype.get_char_conversion=function(w){return this.__char_conversion[w]||w},it.prototype.get_char_conversion=function(w){return this.session.get_char_conversion(w)},ke.prototype.parse=function(w){return this.thread.parse(w)},it.prototype.parse=function(w){var b=new U(this);b.new_text(w);var y=b.get_tokens();if(y===null)return!1;var F=W(this,y,0,this.__get_max_priority(),!1);return F.len!==y.length?!1:{value:F.value,expr:F,tokens:y}},ke.prototype.get_flag=function(w){return this.flag[w]},it.prototype.get_flag=function(w){return this.session.get_flag(w)},ke.prototype.add_rule=function(w,b){return b=b||{},b.from=b.from?b.from:\"$tau-js\",this.src_predicates[w.head.indicator]=b.from,this.rules[w.head.indicator]||(this.rules[w.head.indicator]=[]),this.rules[w.head.indicator].push(w),this.public_predicates.hasOwnProperty(w.head.indicator)||(this.public_predicates[w.head.indicator]=!1),!0},it.prototype.add_rule=function(w,b){return this.session.add_rule(w,b)},ke.prototype.run_directive=function(w){this.thread.run_directive(w)},it.prototype.run_directive=function(w){return x.type.is_directive(w)?(x.directive[w.indicator](this,w),!0):!1},ke.prototype.__get_max_priority=function(){return\"1200\"},it.prototype.__get_max_priority=function(){return this.session.__get_max_priority()},ke.prototype.__get_next_priority=function(w){var b=0;w=parseInt(w);for(var y in this.__operators)if(this.__operators.hasOwnProperty(y)){var F=parseInt(y);F>b&&F<w&&(b=F)}return b.toString()},it.prototype.__get_next_priority=function(w){return this.session.__get_next_priority(w)},ke.prototype.__lookup_operator_classes=function(w,b){return this.__operators.hasOwnProperty(w)&&this.__operators[w][b]instanceof Array&&this.__operators[w][b]||!1},it.prototype.__lookup_operator_classes=function(w,b){return this.session.__lookup_operator_classes(w,b)},ke.prototype.lookup_operator=function(w,b){for(var y in this.__operators)if(this.__operators[y][w]){for(var F=0;F<this.__operators[y][w].length;F++)if(b===0||this.__operators[y][w][F].length===b+1)return{priority:y,class:this.__operators[y][w][F]}}return null},it.prototype.lookup_operator=function(w,b){return this.session.lookup_operator(w,b)},ke.prototype.throw_warning=function(w){this.thread.throw_warning(w)},it.prototype.throw_warning=function(w){this.warnings.push(w)},ke.prototype.get_warnings=function(){return this.thread.get_warnings()},it.prototype.get_warnings=function(){return this.warnings},ke.prototype.add_goal=function(w,b){this.thread.add_goal(w,b)},it.prototype.add_goal=function(w,b,y){y=y||null,b===!0&&(this.points=[]);for(var F=w.variables(),z={},Z=0;Z<F.length;Z++)z[F[Z]]=new De(F[Z]);this.points.push(new Pe(w,new Ne(z),y))},ke.prototype.consult=function(w,b){return this.thread.consult(w,b)},it.prototype.consult=function(w,b){var y=\"\";if(typeof w==\"string\"){y=w;var F=y.length;if(y.substring(F-3,F)===\".pl\"&&document.getElementById(y)){var z=document.getElementById(y),Z=z.getAttribute(\"type\");Z!==null&&Z.replace(/ /g,\"\").toLowerCase()===\"text/prolog\"&&(y=z.text)}}else if(w.nodeName)switch(w.nodeName.toLowerCase()){case\"input\":case\"textarea\":y=w.value;break;default:y=w.innerHTML;break}else return!1;return this.warnings=[],ce(this,y,b)},ke.prototype.query=function(w){return this.thread.query(w)},it.prototype.query=function(w){return this.points=[],this.debugger_points=[],me(this,w)},ke.prototype.head_point=function(){return this.thread.head_point()},it.prototype.head_point=function(){return this.points[this.points.length-1]},ke.prototype.get_free_variable=function(w){return this.thread.get_free_variable(w)},it.prototype.get_free_variable=function(w){var b=[];if(w.id===\"_\"||this.session.renamed_variables[w.id]===void 0){for(this.session.rename++,this.points.length>0&&(b=this.head_point().substitution.domain());e(b,x.format_variable(this.session.rename))!==-1;)this.session.rename++;if(w.id===\"_\")return new De(x.format_variable(this.session.rename));this.session.renamed_variables[w.id]=x.format_variable(this.session.rename)}return new De(this.session.renamed_variables[w.id])},ke.prototype.next_free_variable=function(){return this.thread.next_free_variable()},it.prototype.next_free_variable=function(){this.session.rename++;var w=[];for(this.points.length>0&&(w=this.head_point().substitution.domain());e(w,x.format_variable(this.session.rename))!==-1;)this.session.rename++;return new De(x.format_variable(this.session.rename))},ke.prototype.is_public_predicate=function(w){return!this.public_predicates.hasOwnProperty(w)||this.public_predicates[w]===!0},it.prototype.is_public_predicate=function(w){return this.session.is_public_predicate(w)},ke.prototype.is_multifile_predicate=function(w){return this.multifile_predicates.hasOwnProperty(w)&&this.multifile_predicates[w]===!0},it.prototype.is_multifile_predicate=function(w){return this.session.is_multifile_predicate(w)},ke.prototype.prepend=function(w){return this.thread.prepend(w)},it.prototype.prepend=function(w){for(var b=w.length-1;b>=0;b--)this.points.push(w[b])},ke.prototype.success=function(w,b){return this.thread.success(w,b)},it.prototype.success=function(w,y){var y=typeof y>\"u\"?w:y;this.prepend([new Pe(w.goal.replace(null),w.substitution,y)])},ke.prototype.throw_error=function(w){return this.thread.throw_error(w)},it.prototype.throw_error=function(w){this.prepend([new Pe(new j(\"throw\",[w]),new Ne,null,null)])},ke.prototype.step_rule=function(w,b){return this.thread.step_rule(w,b)},it.prototype.step_rule=function(w,b){var y=b.indicator;if(w===\"user\"&&(w=null),w===null&&this.session.rules.hasOwnProperty(y))return this.session.rules[y];for(var F=w===null?this.session.modules:e(this.session.modules,w)===-1?[]:[w],z=0;z<F.length;z++){var Z=x.module[F[z]];if(Z.rules.hasOwnProperty(y)&&(Z.rules.hasOwnProperty(this.level)||Z.exports_predicate(y)))return x.module[F[z]].rules[y]}return null},ke.prototype.step=function(){return this.thread.step()},it.prototype.step=function(){if(this.points.length!==0){var w=!1,b=this.points.pop();if(this.debugger&&this.debugger_states.push(b),x.type.is_term(b.goal)){var y=b.goal.select(),F=null,z=[];if(y!==null){this.total_steps++;for(var Z=b;Z.parent!==null&&Z.parent.goal.search(y);)Z=Z.parent;if(this.level=Z.parent===null?\"top_level/0\":Z.parent.goal.select().indicator,x.type.is_term(y)&&y.indicator===\":/2\"&&(F=y.args[0].id,y=y.args[1]),F===null&&x.type.is_builtin(y))this.__call_indicator=y.indicator,w=x.predicate[y.indicator](this,b,y);else{var $=this.step_rule(F,y);if($===null)this.session.rules.hasOwnProperty(y.indicator)||(this.get_flag(\"unknown\").id===\"error\"?this.throw_error(x.error.existence(\"procedure\",y.indicator,this.level)):this.get_flag(\"unknown\").id===\"warning\"&&this.throw_warning(\"unknown procedure \"+y.indicator+\" (from \"+this.level+\")\"));else if($ instanceof Function)w=$(this,b,y);else{for(var oe in $)if($.hasOwnProperty(oe)){var xe=$[oe];this.session.renamed_variables={},xe=xe.rename(this);var Te=this.get_flag(\"occurs_check\").indicator===\"true/0\",lt=new Pe,It=x.unify(y,xe.head,Te);It!==null&&(lt.goal=b.goal.replace(xe.body),lt.goal!==null&&(lt.goal=lt.goal.apply(It)),lt.substitution=b.substitution.apply(It),lt.parent=b,z.push(lt))}this.prepend(z)}}}}else x.type.is_variable(b.goal)?this.throw_error(x.error.instantiation(this.level)):this.throw_error(x.error.type(\"callable\",b.goal,this.level));return w}},ke.prototype.answer=function(w){return this.thread.answer(w)},it.prototype.answer=function(w){w=w||function(b){},this.__calls.push(w),!(this.__calls.length>1)&&this.again()},ke.prototype.answers=function(w,b,y){return this.thread.answers(w,b,y)},it.prototype.answers=function(w,b,y){var F=b||1e3,z=this;if(b<=0){y&&y();return}this.answer(function(Z){w(Z),Z!==!1?setTimeout(function(){z.answers(w,b-1,y)},1):y&&y()})},ke.prototype.again=function(w){return this.thread.again(w)},it.prototype.again=function(w){for(var b,y=Date.now();this.__calls.length>0;){for(this.warnings=[],w!==!1&&(this.current_limit=this.session.limit);this.current_limit>0&&this.points.length>0&&this.head_point().goal!==null&&!x.type.is_error(this.head_point().goal);)if(this.current_limit--,this.step()===!0)return;var F=Date.now();this.cpu_time_last=F-y,this.cpu_time+=this.cpu_time_last;var z=this.__calls.shift();this.current_limit<=0?z(null):this.points.length===0?z(!1):x.type.is_error(this.head_point().goal)?(b=this.session.format_error(this.points.pop()),this.points=[],z(b)):(this.debugger&&this.debugger_states.push(this.head_point()),b=this.session.format_success(this.points.pop()),z(b))}},ke.prototype.unfold=function(w){if(w.body===null)return!1;var b=w.head,y=w.body,F=y.select(),z=new it(this),Z=[];z.add_goal(F),z.step();for(var $=z.points.length-1;$>=0;$--){var oe=z.points[$],xe=b.apply(oe.substitution),Te=y.replace(oe.goal);Te!==null&&(Te=Te.apply(oe.substitution)),Z.push(new Ye(xe,Te))}var lt=this.rules[b.indicator],It=e(lt,w);return Z.length>0&&It!==-1?(lt.splice.apply(lt,[It,1].concat(Z)),!0):!1},it.prototype.unfold=function(w){return this.session.unfold(w)},De.prototype.interpret=function(w){return x.error.instantiation(w.level)},Re.prototype.interpret=function(w){return this},j.prototype.interpret=function(w){return x.type.is_unitary_list(this)?this.args[0].interpret(w):x.operate(w,this)},De.prototype.compare=function(w){return this.id<w.id?-1:this.id>w.id?1:0},Re.prototype.compare=function(w){if(this.value===w.value&&this.is_float===w.is_float)return 0;if(this.value<w.value||this.value===w.value&&this.is_float&&!w.is_float)return-1;if(this.value>w.value)return 1},j.prototype.compare=function(w){if(this.args.length<w.args.length||this.args.length===w.args.length&&this.id<w.id)return-1;if(this.args.length>w.args.length||this.args.length===w.args.length&&this.id>w.id)return 1;for(var b=0;b<this.args.length;b++){var y=x.compare(this.args[b],w.args[b]);if(y!==0)return y}return 0},Ne.prototype.lookup=function(w){return this.links[w]?this.links[w]:null},Ne.prototype.filter=function(w){var b={};for(var y in this.links)if(this.links.hasOwnProperty(y)){var F=this.links[y];w(y,F)&&(b[y]=F)}return new Ne(b)},Ne.prototype.exclude=function(w){var b={};for(var y in this.links)this.links.hasOwnProperty(y)&&e(w,y)===-1&&(b[y]=this.links[y]);return new Ne(b)},Ne.prototype.add=function(w,b){this.links[w]=b},Ne.prototype.domain=function(w){var b=w===!0?function(z){return z}:function(z){return new De(z)},y=[];for(var F in this.links)y.push(b(F));return y},De.prototype.compile=function(){return'new pl.type.Var(\"'+this.id.toString()+'\")'},Re.prototype.compile=function(){return\"new pl.type.Num(\"+this.value.toString()+\", \"+this.is_float.toString()+\")\"},j.prototype.compile=function(){return'new pl.type.Term(\"'+this.id.replace(/\"/g,'\\\\\"')+'\", ['+s(this.args,function(w){return w.compile()})+\"])\"},Ye.prototype.compile=function(){return\"new pl.type.Rule(\"+this.head.compile()+\", \"+(this.body===null?\"null\":this.body.compile())+\")\"},ke.prototype.compile=function(){var w,b=[],y;for(var F in this.rules)if(this.rules.hasOwnProperty(F)){var z=this.rules[F];y=[],w='\"'+F+'\": [';for(var Z=0;Z<z.length;Z++)y.push(z[Z].compile());w+=y.join(),w+=\"]\",b.push(w)}return\"{\"+b.join()+\"};\"},De.prototype.toJavaScript=function(){},Re.prototype.toJavaScript=function(){return this.value},j.prototype.toJavaScript=function(){if(this.args.length===0&&this.indicator!==\"[]/0\")return this.id;if(x.type.is_list(this)){for(var w=[],b=this,y;b.indicator===\"./2\";){if(y=b.args[0].toJavaScript(),y===void 0)return;w.push(y),b=b.args[1]}if(b.indicator===\"[]/0\")return w}},Ye.prototype.singleton_variables=function(){var w=this.head.variables(),b={},y=[];this.body!==null&&(w=w.concat(this.body.variables()));for(var F=0;F<w.length;F++)b[w[F]]===void 0&&(b[w[F]]=0),b[w[F]]++;for(var z in b)z!==\"_\"&&b[z]===1&&y.push(z);return y};var x={__env:typeof tc<\"u\"&&tc.exports?global:window,module:{},version:t,parser:{tokenizer:U,expression:W},utils:{str_indicator:X,codePointAt:n,fromCodePoint:c},statistics:{getCountTerms:function(){return dt}},fromJavaScript:{test:{boolean:function(w){return w===!0||w===!1},number:function(w){return typeof w==\"number\"},string:function(w){return typeof w==\"string\"},list:function(w){return w instanceof Array},variable:function(w){return w===void 0},any:function(w){return!0}},conversion:{boolean:function(w){return new j(w?\"true\":\"false\",[])},number:function(w){return new Re(w,w%1!==0)},string:function(w){return new j(w,[])},list:function(w){for(var b=[],y,F=0;F<w.length;F++){if(y=x.fromJavaScript.apply(w[F]),y===void 0)return;b.push(y)}return g(b)},variable:function(w){return new De(\"_\")},any:function(w){}},apply:function(w){for(var b in x.fromJavaScript.test)if(b!==\"any\"&&x.fromJavaScript.test[b](w))return x.fromJavaScript.conversion[b](w);return x.fromJavaScript.conversion.any(w)}},type:{Var:De,Num:Re,Term:j,Rule:Ye,State:Pe,Stream:Fe,Module:_e,Thread:it,Session:ke,Substitution:Ne,order:[De,Re,j,Fe],compare:function(w,b){var y=e(x.type.order,w.constructor),F=e(x.type.order,b.constructor);if(y<F)return-1;if(y>F)return 1;if(w.constructor===Re){if(w.is_float&&b.is_float)return 0;if(w.is_float)return-1;if(b.is_float)return 1}return 0},is_substitution:function(w){return w instanceof Ne},is_state:function(w){return w instanceof Pe},is_rule:function(w){return w instanceof Ye},is_variable:function(w){return w instanceof De},is_stream:function(w){return w instanceof Fe},is_anonymous_var:function(w){return w instanceof De&&w.id===\"_\"},is_callable:function(w){return w instanceof j},is_number:function(w){return w instanceof Re},is_integer:function(w){return w instanceof Re&&!w.is_float},is_float:function(w){return w instanceof Re&&w.is_float},is_term:function(w){return w instanceof j},is_atom:function(w){return w instanceof j&&w.args.length===0},is_ground:function(w){if(w instanceof De)return!1;if(w instanceof j){for(var b=0;b<w.args.length;b++)if(!x.type.is_ground(w.args[b]))return!1}return!0},is_atomic:function(w){return w instanceof j&&w.args.length===0||w instanceof Re},is_compound:function(w){return w instanceof j&&w.args.length>0},is_list:function(w){return w instanceof j&&(w.indicator===\"[]/0\"||w.indicator===\"./2\")},is_empty_list:function(w){return w instanceof j&&w.indicator===\"[]/0\"},is_non_empty_list:function(w){return w instanceof j&&w.indicator===\"./2\"},is_fully_list:function(w){for(;w instanceof j&&w.indicator===\"./2\";)w=w.args[1];return w instanceof De||w instanceof j&&w.indicator===\"[]/0\"},is_instantiated_list:function(w){for(;w instanceof j&&w.indicator===\"./2\";)w=w.args[1];return w instanceof j&&w.indicator===\"[]/0\"},is_unitary_list:function(w){return w instanceof j&&w.indicator===\"./2\"&&w.args[1]instanceof j&&w.args[1].indicator===\"[]/0\"},is_character:function(w){return w instanceof j&&(w.id.length===1||w.id.length>0&&w.id.length<=2&&n(w.id,0)>=65536)},is_character_code:function(w){return w instanceof Re&&!w.is_float&&w.value>=0&&w.value<=1114111},is_byte:function(w){return w instanceof Re&&!w.is_float&&w.value>=0&&w.value<=255},is_operator:function(w){return w instanceof j&&x.arithmetic.evaluation[w.indicator]},is_directive:function(w){return w instanceof j&&x.directive[w.indicator]!==void 0},is_builtin:function(w){return w instanceof j&&x.predicate[w.indicator]!==void 0},is_error:function(w){return w instanceof j&&w.indicator===\"throw/1\"},is_predicate_indicator:function(w){return w instanceof j&&w.indicator===\"//2\"&&w.args[0]instanceof j&&w.args[0].args.length===0&&w.args[1]instanceof Re&&w.args[1].is_float===!1},is_flag:function(w){return w instanceof j&&w.args.length===0&&x.flag[w.id]!==void 0},is_value_flag:function(w,b){if(!x.type.is_flag(w))return!1;for(var y in x.flag[w.id].allowed)if(x.flag[w.id].allowed.hasOwnProperty(y)&&x.flag[w.id].allowed[y].equals(b))return!0;return!1},is_io_mode:function(w){return x.type.is_atom(w)&&[\"read\",\"write\",\"append\"].indexOf(w.id)!==-1},is_stream_option:function(w){return x.type.is_term(w)&&(w.indicator===\"alias/1\"&&x.type.is_atom(w.args[0])||w.indicator===\"reposition/1\"&&x.type.is_atom(w.args[0])&&(w.args[0].id===\"true\"||w.args[0].id===\"false\")||w.indicator===\"type/1\"&&x.type.is_atom(w.args[0])&&(w.args[0].id===\"text\"||w.args[0].id===\"binary\")||w.indicator===\"eof_action/1\"&&x.type.is_atom(w.args[0])&&(w.args[0].id===\"error\"||w.args[0].id===\"eof_code\"||w.args[0].id===\"reset\"))},is_stream_position:function(w){return x.type.is_integer(w)&&w.value>=0||x.type.is_atom(w)&&(w.id===\"end_of_stream\"||w.id===\"past_end_of_stream\")},is_stream_property:function(w){return x.type.is_term(w)&&(w.indicator===\"input/0\"||w.indicator===\"output/0\"||w.indicator===\"alias/1\"&&(x.type.is_variable(w.args[0])||x.type.is_atom(w.args[0]))||w.indicator===\"file_name/1\"&&(x.type.is_variable(w.args[0])||x.type.is_atom(w.args[0]))||w.indicator===\"position/1\"&&(x.type.is_variable(w.args[0])||x.type.is_stream_position(w.args[0]))||w.indicator===\"reposition/1\"&&(x.type.is_variable(w.args[0])||x.type.is_atom(w.args[0])&&(w.args[0].id===\"true\"||w.args[0].id===\"false\"))||w.indicator===\"type/1\"&&(x.type.is_variable(w.args[0])||x.type.is_atom(w.args[0])&&(w.args[0].id===\"text\"||w.args[0].id===\"binary\"))||w.indicator===\"mode/1\"&&(x.type.is_variable(w.args[0])||x.type.is_atom(w.args[0])&&(w.args[0].id===\"read\"||w.args[0].id===\"write\"||w.args[0].id===\"append\"))||w.indicator===\"eof_action/1\"&&(x.type.is_variable(w.args[0])||x.type.is_atom(w.args[0])&&(w.args[0].id===\"error\"||w.args[0].id===\"eof_code\"||w.args[0].id===\"reset\"))||w.indicator===\"end_of_stream/1\"&&(x.type.is_variable(w.args[0])||x.type.is_atom(w.args[0])&&(w.args[0].id===\"at\"||w.args[0].id===\"past\"||w.args[0].id===\"not\")))},is_streamable:function(w){return w.__proto__.stream!==void 0},is_read_option:function(w){return x.type.is_term(w)&&[\"variables/1\",\"variable_names/1\",\"singletons/1\"].indexOf(w.indicator)!==-1},is_write_option:function(w){return x.type.is_term(w)&&(w.indicator===\"quoted/1\"&&x.type.is_atom(w.args[0])&&(w.args[0].id===\"true\"||w.args[0].id===\"false\")||w.indicator===\"ignore_ops/1\"&&x.type.is_atom(w.args[0])&&(w.args[0].id===\"true\"||w.args[0].id===\"false\")||w.indicator===\"numbervars/1\"&&x.type.is_atom(w.args[0])&&(w.args[0].id===\"true\"||w.args[0].id===\"false\"))},is_close_option:function(w){return x.type.is_term(w)&&w.indicator===\"force/1\"&&x.type.is_atom(w.args[0])&&(w.args[0].id===\"true\"||w.args[0].id===\"false\")},is_modifiable_flag:function(w){return x.type.is_flag(w)&&x.flag[w.id].changeable},is_module:function(w){return w instanceof j&&w.indicator===\"library/1\"&&w.args[0]instanceof j&&w.args[0].args.length===0&&x.module[w.args[0].id]!==void 0}},arithmetic:{evaluation:{\"e/0\":{type_args:null,type_result:!0,fn:function(w){return Math.E}},\"pi/0\":{type_args:null,type_result:!0,fn:function(w){return Math.PI}},\"tau/0\":{type_args:null,type_result:!0,fn:function(w){return 2*Math.PI}},\"epsilon/0\":{type_args:null,type_result:!0,fn:function(w){return Number.EPSILON}},\"+/1\":{type_args:null,type_result:null,fn:function(w,b){return w}},\"-/1\":{type_args:null,type_result:null,fn:function(w,b){return-w}},\"\\\\/1\":{type_args:!1,type_result:!1,fn:function(w,b){return~w}},\"abs/1\":{type_args:null,type_result:null,fn:function(w,b){return Math.abs(w)}},\"sign/1\":{type_args:null,type_result:null,fn:function(w,b){return Math.sign(w)}},\"float_integer_part/1\":{type_args:!0,type_result:!1,fn:function(w,b){return parseInt(w)}},\"float_fractional_part/1\":{type_args:!0,type_result:!0,fn:function(w,b){return w-parseInt(w)}},\"float/1\":{type_args:null,type_result:!0,fn:function(w,b){return parseFloat(w)}},\"floor/1\":{type_args:!0,type_result:!1,fn:function(w,b){return Math.floor(w)}},\"truncate/1\":{type_args:!0,type_result:!1,fn:function(w,b){return parseInt(w)}},\"round/1\":{type_args:!0,type_result:!1,fn:function(w,b){return Math.round(w)}},\"ceiling/1\":{type_args:!0,type_result:!1,fn:function(w,b){return Math.ceil(w)}},\"sin/1\":{type_args:null,type_result:!0,fn:function(w,b){return Math.sin(w)}},\"cos/1\":{type_args:null,type_result:!0,fn:function(w,b){return Math.cos(w)}},\"tan/1\":{type_args:null,type_result:!0,fn:function(w,b){return Math.tan(w)}},\"asin/1\":{type_args:null,type_result:!0,fn:function(w,b){return Math.asin(w)}},\"acos/1\":{type_args:null,type_result:!0,fn:function(w,b){return Math.acos(w)}},\"atan/1\":{type_args:null,type_result:!0,fn:function(w,b){return Math.atan(w)}},\"atan2/2\":{type_args:null,type_result:!0,fn:function(w,b,y){return Math.atan2(w,b)}},\"exp/1\":{type_args:null,type_result:!0,fn:function(w,b){return Math.exp(w)}},\"sqrt/1\":{type_args:null,type_result:!0,fn:function(w,b){return Math.sqrt(w)}},\"log/1\":{type_args:null,type_result:!0,fn:function(w,b){return w>0?Math.log(w):x.error.evaluation(\"undefined\",b.__call_indicator)}},\"+/2\":{type_args:null,type_result:null,fn:function(w,b,y){return w+b}},\"-/2\":{type_args:null,type_result:null,fn:function(w,b,y){return w-b}},\"*/2\":{type_args:null,type_result:null,fn:function(w,b,y){return w*b}},\"//2\":{type_args:null,type_result:!0,fn:function(w,b,y){return b?w/b:x.error.evaluation(\"zero_division\",y.__call_indicator)}},\"///2\":{type_args:!1,type_result:!1,fn:function(w,b,y){return b?parseInt(w/b):x.error.evaluation(\"zero_division\",y.__call_indicator)}},\"**/2\":{type_args:null,type_result:!0,fn:function(w,b,y){return Math.pow(w,b)}},\"^/2\":{type_args:null,type_result:null,fn:function(w,b,y){return Math.pow(w,b)}},\"<</2\":{type_args:!1,type_result:!1,fn:function(w,b,y){return w<<b}},\">>/2\":{type_args:!1,type_result:!1,fn:function(w,b,y){return w>>b}},\"/\\\\/2\":{type_args:!1,type_result:!1,fn:function(w,b,y){return w&b}},\"\\\\//2\":{type_args:!1,type_result:!1,fn:function(w,b,y){return w|b}},\"xor/2\":{type_args:!1,type_result:!1,fn:function(w,b,y){return w^b}},\"rem/2\":{type_args:!1,type_result:!1,fn:function(w,b,y){return b?w%b:x.error.evaluation(\"zero_division\",y.__call_indicator)}},\"mod/2\":{type_args:!1,type_result:!1,fn:function(w,b,y){return b?w-parseInt(w/b)*b:x.error.evaluation(\"zero_division\",y.__call_indicator)}},\"max/2\":{type_args:null,type_result:null,fn:function(w,b,y){return Math.max(w,b)}},\"min/2\":{type_args:null,type_result:null,fn:function(w,b,y){return Math.min(w,b)}}}},directive:{\"dynamic/1\":function(w,b){var y=b.args[0];if(x.type.is_variable(y))w.throw_error(x.error.instantiation(b.indicator));else if(!x.type.is_compound(y)||y.indicator!==\"//2\")w.throw_error(x.error.type(\"predicate_indicator\",y,b.indicator));else if(x.type.is_variable(y.args[0])||x.type.is_variable(y.args[1]))w.throw_error(x.error.instantiation(b.indicator));else if(!x.type.is_atom(y.args[0]))w.throw_error(x.error.type(\"atom\",y.args[0],b.indicator));else if(!x.type.is_integer(y.args[1]))w.throw_error(x.error.type(\"integer\",y.args[1],b.indicator));else{var F=b.args[0].args[0].id+\"/\"+b.args[0].args[1].value;w.session.public_predicates[F]=!0,w.session.rules[F]||(w.session.rules[F]=[])}},\"multifile/1\":function(w,b){var y=b.args[0];x.type.is_variable(y)?w.throw_error(x.error.instantiation(b.indicator)):!x.type.is_compound(y)||y.indicator!==\"//2\"?w.throw_error(x.error.type(\"predicate_indicator\",y,b.indicator)):x.type.is_variable(y.args[0])||x.type.is_variable(y.args[1])?w.throw_error(x.error.instantiation(b.indicator)):x.type.is_atom(y.args[0])?x.type.is_integer(y.args[1])?w.session.multifile_predicates[b.args[0].args[0].id+\"/\"+b.args[0].args[1].value]=!0:w.throw_error(x.error.type(\"integer\",y.args[1],b.indicator)):w.throw_error(x.error.type(\"atom\",y.args[0],b.indicator))},\"set_prolog_flag/2\":function(w,b){var y=b.args[0],F=b.args[1];x.type.is_variable(y)||x.type.is_variable(F)?w.throw_error(x.error.instantiation(b.indicator)):x.type.is_atom(y)?x.type.is_flag(y)?x.type.is_value_flag(y,F)?x.type.is_modifiable_flag(y)?w.session.flag[y.id]=F:w.throw_error(x.error.permission(\"modify\",\"flag\",y)):w.throw_error(x.error.domain(\"flag_value\",new j(\"+\",[y,F]),b.indicator)):w.throw_error(x.error.domain(\"prolog_flag\",y,b.indicator)):w.throw_error(x.error.type(\"atom\",y,b.indicator))},\"use_module/1\":function(w,b){var y=b.args[0];if(x.type.is_variable(y))w.throw_error(x.error.instantiation(b.indicator));else if(!x.type.is_term(y))w.throw_error(x.error.type(\"term\",y,b.indicator));else if(x.type.is_module(y)){var F=y.args[0].id;e(w.session.modules,F)===-1&&w.session.modules.push(F)}},\"char_conversion/2\":function(w,b){var y=b.args[0],F=b.args[1];x.type.is_variable(y)||x.type.is_variable(F)?w.throw_error(x.error.instantiation(b.indicator)):x.type.is_character(y)?x.type.is_character(F)?y.id===F.id?delete w.session.__char_conversion[y.id]:w.session.__char_conversion[y.id]=F.id:w.throw_error(x.error.type(\"character\",F,b.indicator)):w.throw_error(x.error.type(\"character\",y,b.indicator))},\"op/3\":function(w,b){var y=b.args[0],F=b.args[1],z=b.args[2];if(x.type.is_variable(y)||x.type.is_variable(F)||x.type.is_variable(z))w.throw_error(x.error.instantiation(b.indicator));else if(!x.type.is_integer(y))w.throw_error(x.error.type(\"integer\",y,b.indicator));else if(!x.type.is_atom(F))w.throw_error(x.error.type(\"atom\",F,b.indicator));else if(!x.type.is_atom(z))w.throw_error(x.error.type(\"atom\",z,b.indicator));else if(y.value<0||y.value>1200)w.throw_error(x.error.domain(\"operator_priority\",y,b.indicator));else if(z.id===\",\")w.throw_error(x.error.permission(\"modify\",\"operator\",z,b.indicator));else if(z.id===\"|\"&&(y.value<1001||F.id.length!==3))w.throw_error(x.error.permission(\"modify\",\"operator\",z,b.indicator));else if([\"fy\",\"fx\",\"yf\",\"xf\",\"xfx\",\"yfx\",\"xfy\"].indexOf(F.id)===-1)w.throw_error(x.error.domain(\"operator_specifier\",F,b.indicator));else{var Z={prefix:null,infix:null,postfix:null};for(var $ in w.session.__operators)if(w.session.__operators.hasOwnProperty($)){var oe=w.session.__operators[$][z.id];oe&&(e(oe,\"fx\")!==-1&&(Z.prefix={priority:$,type:\"fx\"}),e(oe,\"fy\")!==-1&&(Z.prefix={priority:$,type:\"fy\"}),e(oe,\"xf\")!==-1&&(Z.postfix={priority:$,type:\"xf\"}),e(oe,\"yf\")!==-1&&(Z.postfix={priority:$,type:\"yf\"}),e(oe,\"xfx\")!==-1&&(Z.infix={priority:$,type:\"xfx\"}),e(oe,\"xfy\")!==-1&&(Z.infix={priority:$,type:\"xfy\"}),e(oe,\"yfx\")!==-1&&(Z.infix={priority:$,type:\"yfx\"}))}var xe;switch(F.id){case\"fy\":case\"fx\":xe=\"prefix\";break;case\"yf\":case\"xf\":xe=\"postfix\";break;default:xe=\"infix\";break}if(((Z.prefix&&xe===\"prefix\"||Z.postfix&&xe===\"postfix\"||Z.infix&&xe===\"infix\")&&Z[xe].type!==F.id||Z.infix&&xe===\"postfix\"||Z.postfix&&xe===\"infix\")&&y.value!==0)w.throw_error(x.error.permission(\"create\",\"operator\",z,b.indicator));else return Z[xe]&&(we(w.session.__operators[Z[xe].priority][z.id],F.id),w.session.__operators[Z[xe].priority][z.id].length===0&&delete w.session.__operators[Z[xe].priority][z.id]),y.value>0&&(w.session.__operators[y.value]||(w.session.__operators[y.value.toString()]={}),w.session.__operators[y.value][z.id]||(w.session.__operators[y.value][z.id]=[]),w.session.__operators[y.value][z.id].push(F.id)),!0}}},predicate:{\"op/3\":function(w,b,y){x.directive[\"op/3\"](w,y)&&w.success(b)},\"current_op/3\":function(w,b,y){var F=y.args[0],z=y.args[1],Z=y.args[2],$=[];for(var oe in w.session.__operators)for(var xe in w.session.__operators[oe])for(var Te=0;Te<w.session.__operators[oe][xe].length;Te++)$.push(new Pe(b.goal.replace(new j(\",\",[new j(\"=\",[new Re(oe,!1),F]),new j(\",\",[new j(\"=\",[new j(w.session.__operators[oe][xe][Te],[]),z]),new j(\"=\",[new j(xe,[]),Z])])])),b.substitution,b));w.prepend($)},\";/2\":function(w,b,y){if(x.type.is_term(y.args[0])&&y.args[0].indicator===\"->/2\"){var F=w.points,z=w.session.format_success,Z=w.session.format_error;w.session.format_success=function(Te){return Te.substitution},w.session.format_error=function(Te){return Te.goal},w.points=[new Pe(y.args[0].args[0],b.substitution,b)];var $=function(Te){w.points=F,w.session.format_success=z,w.session.format_error=Z,Te===!1?w.prepend([new Pe(b.goal.replace(y.args[1]),b.substitution,b)]):x.type.is_error(Te)?w.throw_error(Te.args[0]):Te===null?(w.prepend([b]),w.__calls.shift()(null)):w.prepend([new Pe(b.goal.replace(y.args[0].args[1]).apply(Te),b.substitution.apply(Te),b)])};w.__calls.unshift($)}else{var oe=new Pe(b.goal.replace(y.args[0]),b.substitution,b),xe=new Pe(b.goal.replace(y.args[1]),b.substitution,b);w.prepend([oe,xe])}},\"!/0\":function(w,b,y){var F,z,Z=[];for(F=b,z=null;F.parent!==null&&F.parent.goal.search(y);)if(z=F,F=F.parent,F.goal!==null){var $=F.goal.select();if($&&$.id===\"call\"&&$.search(y)){F=z;break}}for(var oe=w.points.length-1;oe>=0;oe--){for(var xe=w.points[oe],Te=xe.parent;Te!==null&&Te!==F.parent;)Te=Te.parent;Te===null&&Te!==F.parent&&Z.push(xe)}w.points=Z.reverse(),w.success(b)},\"\\\\+/1\":function(w,b,y){var F=y.args[0];x.type.is_variable(F)?w.throw_error(x.error.instantiation(w.level)):x.type.is_callable(F)?w.prepend([new Pe(b.goal.replace(new j(\",\",[new j(\",\",[new j(\"call\",[F]),new j(\"!\",[])]),new j(\"fail\",[])])),b.substitution,b),new Pe(b.goal.replace(null),b.substitution,b)]):w.throw_error(x.error.type(\"callable\",F,w.level))},\"->/2\":function(w,b,y){var F=b.goal.replace(new j(\",\",[y.args[0],new j(\",\",[new j(\"!\"),y.args[1]])]));w.prepend([new Pe(F,b.substitution,b)])},\"fail/0\":function(w,b,y){},\"false/0\":function(w,b,y){},\"true/0\":function(w,b,y){w.success(b)},\"call/1\":se(1),\"call/2\":se(2),\"call/3\":se(3),\"call/4\":se(4),\"call/5\":se(5),\"call/6\":se(6),\"call/7\":se(7),\"call/8\":se(8),\"once/1\":function(w,b,y){var F=y.args[0];w.prepend([new Pe(b.goal.replace(new j(\",\",[new j(\"call\",[F]),new j(\"!\",[])])),b.substitution,b)])},\"forall/2\":function(w,b,y){var F=y.args[0],z=y.args[1];w.prepend([new Pe(b.goal.replace(new j(\"\\\\+\",[new j(\",\",[new j(\"call\",[F]),new j(\"\\\\+\",[new j(\"call\",[z])])])])),b.substitution,b)])},\"repeat/0\":function(w,b,y){w.prepend([new Pe(b.goal.replace(null),b.substitution,b),b])},\"throw/1\":function(w,b,y){x.type.is_variable(y.args[0])?w.throw_error(x.error.instantiation(w.level)):w.throw_error(y.args[0])},\"catch/3\":function(w,b,y){var F=w.points;w.points=[],w.prepend([new Pe(y.args[0],b.substitution,b)]);var z=w.session.format_success,Z=w.session.format_error;w.session.format_success=function(oe){return oe.substitution},w.session.format_error=function(oe){return oe.goal};var $=function(oe){var xe=w.points;if(w.points=F,w.session.format_success=z,w.session.format_error=Z,x.type.is_error(oe)){for(var Te=[],lt=w.points.length-1;lt>=0;lt--){for(var ir=w.points[lt],It=ir.parent;It!==null&&It!==b.parent;)It=It.parent;It===null&&It!==b.parent&&Te.push(ir)}w.points=Te;var qt=w.get_flag(\"occurs_check\").indicator===\"true/0\",ir=new Pe,Pt=x.unify(oe.args[0],y.args[1],qt);Pt!==null?(ir.substitution=b.substitution.apply(Pt),ir.goal=b.goal.replace(y.args[2]).apply(Pt),ir.parent=b,w.prepend([ir])):w.throw_error(oe.args[0])}else if(oe!==!1){for(var gn=oe===null?[]:[new Pe(b.goal.apply(oe).replace(null),b.substitution.apply(oe),b)],Pr=[],lt=xe.length-1;lt>=0;lt--){Pr.push(xe[lt]);var Ir=xe[lt].goal!==null?xe[lt].goal.select():null;if(x.type.is_term(Ir)&&Ir.indicator===\"!/0\")break}var Nr=s(Pr,function(nn){return nn.goal===null&&(nn.goal=new j(\"true\",[])),nn=new Pe(b.goal.replace(new j(\"catch\",[nn.goal,y.args[1],y.args[2]])),b.substitution.apply(nn.substitution),nn.parent),nn.exclude=y.args[0].variables(),nn}).reverse();w.prepend(Nr),w.prepend(gn),oe===null&&(this.current_limit=0,w.__calls.shift()(null))}};w.__calls.unshift($)},\"=/2\":function(w,b,y){var F=w.get_flag(\"occurs_check\").indicator===\"true/0\",z=new Pe,Z=x.unify(y.args[0],y.args[1],F);Z!==null&&(z.goal=b.goal.apply(Z).replace(null),z.substitution=b.substitution.apply(Z),z.parent=b,w.prepend([z]))},\"unify_with_occurs_check/2\":function(w,b,y){var F=new Pe,z=x.unify(y.args[0],y.args[1],!0);z!==null&&(F.goal=b.goal.apply(z).replace(null),F.substitution=b.substitution.apply(z),F.parent=b,w.prepend([F]))},\"\\\\=/2\":function(w,b,y){var F=w.get_flag(\"occurs_check\").indicator===\"true/0\",z=x.unify(y.args[0],y.args[1],F);z===null&&w.success(b)},\"subsumes_term/2\":function(w,b,y){var F=w.get_flag(\"occurs_check\").indicator===\"true/0\",z=x.unify(y.args[1],y.args[0],F);z!==null&&y.args[1].apply(z).equals(y.args[1])&&w.success(b)},\"findall/3\":function(w,b,y){var F=y.args[0],z=y.args[1],Z=y.args[2];if(x.type.is_variable(z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_callable(z))w.throw_error(x.error.type(\"callable\",z,y.indicator));else if(!x.type.is_variable(Z)&&!x.type.is_list(Z))w.throw_error(x.error.type(\"list\",Z,y.indicator));else{var $=w.next_free_variable(),oe=new j(\",\",[z,new j(\"=\",[$,F])]),xe=w.points,Te=w.session.limit,lt=w.session.format_success;w.session.format_success=function(ir){return ir.substitution},w.add_goal(oe,!0,b);var It=[],qt=function(ir){if(ir!==!1&&ir!==null&&!x.type.is_error(ir))w.__calls.unshift(qt),It.push(ir.links[$.id]),w.session.limit=w.current_limit;else if(w.points=xe,w.session.limit=Te,w.session.format_success=lt,x.type.is_error(ir))w.throw_error(ir.args[0]);else if(w.current_limit>0){for(var Pt=new j(\"[]\"),gn=It.length-1;gn>=0;gn--)Pt=new j(\".\",[It[gn],Pt]);w.prepend([new Pe(b.goal.replace(new j(\"=\",[Z,Pt])),b.substitution,b)])}};w.__calls.unshift(qt)}},\"bagof/3\":function(w,b,y){var F,z=y.args[0],Z=y.args[1],$=y.args[2];if(x.type.is_variable(Z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_callable(Z))w.throw_error(x.error.type(\"callable\",Z,y.indicator));else if(!x.type.is_variable($)&&!x.type.is_list($))w.throw_error(x.error.type(\"list\",$,y.indicator));else{var oe=w.next_free_variable(),xe;Z.indicator===\"^/2\"?(xe=Z.args[0].variables(),Z=Z.args[1]):xe=[],xe=xe.concat(z.variables());for(var Te=Z.variables().filter(function(Nr){return e(xe,Nr)===-1}),lt=new j(\"[]\"),It=Te.length-1;It>=0;It--)lt=new j(\".\",[new De(Te[It]),lt]);var qt=new j(\",\",[Z,new j(\"=\",[oe,new j(\",\",[lt,z])])]),ir=w.points,Pt=w.session.limit,gn=w.session.format_success;w.session.format_success=function(Nr){return Nr.substitution},w.add_goal(qt,!0,b);var Pr=[],Ir=function(Nr){if(Nr!==!1&&Nr!==null&&!x.type.is_error(Nr)){w.__calls.unshift(Ir);var nn=!1,ai=Nr.links[oe.id].args[0],wo=Nr.links[oe.id].args[1];for(var ns in Pr)if(Pr.hasOwnProperty(ns)){var to=Pr[ns];if(to.variables.equals(ai)){to.answers.push(wo),nn=!0;break}}nn||Pr.push({variables:ai,answers:[wo]}),w.session.limit=w.current_limit}else if(w.points=ir,w.session.limit=Pt,w.session.format_success=gn,x.type.is_error(Nr))w.throw_error(Nr.args[0]);else if(w.current_limit>0){for(var Bo=[],ji=0;ji<Pr.length;ji++){Nr=Pr[ji].answers;for(var ro=new j(\"[]\"),vo=Nr.length-1;vo>=0;vo--)ro=new j(\".\",[Nr[vo],ro]);Bo.push(new Pe(b.goal.replace(new j(\",\",[new j(\"=\",[lt,Pr[ji].variables]),new j(\"=\",[$,ro])])),b.substitution,b))}w.prepend(Bo)}};w.__calls.unshift(Ir)}},\"setof/3\":function(w,b,y){var F,z=y.args[0],Z=y.args[1],$=y.args[2];if(x.type.is_variable(Z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_callable(Z))w.throw_error(x.error.type(\"callable\",Z,y.indicator));else if(!x.type.is_variable($)&&!x.type.is_list($))w.throw_error(x.error.type(\"list\",$,y.indicator));else{var oe=w.next_free_variable(),xe;Z.indicator===\"^/2\"?(xe=Z.args[0].variables(),Z=Z.args[1]):xe=[],xe=xe.concat(z.variables());for(var Te=Z.variables().filter(function(Nr){return e(xe,Nr)===-1}),lt=new j(\"[]\"),It=Te.length-1;It>=0;It--)lt=new j(\".\",[new De(Te[It]),lt]);var qt=new j(\",\",[Z,new j(\"=\",[oe,new j(\",\",[lt,z])])]),ir=w.points,Pt=w.session.limit,gn=w.session.format_success;w.session.format_success=function(Nr){return Nr.substitution},w.add_goal(qt,!0,b);var Pr=[],Ir=function(Nr){if(Nr!==!1&&Nr!==null&&!x.type.is_error(Nr)){w.__calls.unshift(Ir);var nn=!1,ai=Nr.links[oe.id].args[0],wo=Nr.links[oe.id].args[1];for(var ns in Pr)if(Pr.hasOwnProperty(ns)){var to=Pr[ns];if(to.variables.equals(ai)){to.answers.push(wo),nn=!0;break}}nn||Pr.push({variables:ai,answers:[wo]}),w.session.limit=w.current_limit}else if(w.points=ir,w.session.limit=Pt,w.session.format_success=gn,x.type.is_error(Nr))w.throw_error(Nr.args[0]);else if(w.current_limit>0){for(var Bo=[],ji=0;ji<Pr.length;ji++){Nr=Pr[ji].answers.sort(x.compare);for(var ro=new j(\"[]\"),vo=Nr.length-1;vo>=0;vo--)ro=new j(\".\",[Nr[vo],ro]);Bo.push(new Pe(b.goal.replace(new j(\",\",[new j(\"=\",[lt,Pr[ji].variables]),new j(\"=\",[$,ro])])),b.substitution,b))}w.prepend(Bo)}};w.__calls.unshift(Ir)}},\"functor/3\":function(w,b,y){var F,z=y.args[0],Z=y.args[1],$=y.args[2];if(x.type.is_variable(z)&&(x.type.is_variable(Z)||x.type.is_variable($)))w.throw_error(x.error.instantiation(\"functor/3\"));else if(!x.type.is_variable($)&&!x.type.is_integer($))w.throw_error(x.error.type(\"integer\",y.args[2],\"functor/3\"));else if(!x.type.is_variable(Z)&&!x.type.is_atomic(Z))w.throw_error(x.error.type(\"atomic\",y.args[1],\"functor/3\"));else if(x.type.is_integer(Z)&&x.type.is_integer($)&&$.value!==0)w.throw_error(x.error.type(\"atom\",y.args[1],\"functor/3\"));else if(x.type.is_variable(z)){if(y.args[2].value>=0){for(var oe=[],xe=0;xe<$.value;xe++)oe.push(w.next_free_variable());var Te=x.type.is_integer(Z)?Z:new j(Z.id,oe);w.prepend([new Pe(b.goal.replace(new j(\"=\",[z,Te])),b.substitution,b)])}}else{var lt=x.type.is_integer(z)?z:new j(z.id,[]),It=x.type.is_integer(z)?new Re(0,!1):new Re(z.args.length,!1),qt=new j(\",\",[new j(\"=\",[lt,Z]),new j(\"=\",[It,$])]);w.prepend([new Pe(b.goal.replace(qt),b.substitution,b)])}},\"arg/3\":function(w,b,y){if(x.type.is_variable(y.args[0])||x.type.is_variable(y.args[1]))w.throw_error(x.error.instantiation(y.indicator));else if(y.args[0].value<0)w.throw_error(x.error.domain(\"not_less_than_zero\",y.args[0],y.indicator));else if(!x.type.is_compound(y.args[1]))w.throw_error(x.error.type(\"compound\",y.args[1],y.indicator));else{var F=y.args[0].value;if(F>0&&F<=y.args[1].args.length){var z=new j(\"=\",[y.args[1].args[F-1],y.args[2]]);w.prepend([new Pe(b.goal.replace(z),b.substitution,b)])}}},\"=../2\":function(w,b,y){var F;if(x.type.is_variable(y.args[0])&&(x.type.is_variable(y.args[1])||x.type.is_non_empty_list(y.args[1])&&x.type.is_variable(y.args[1].args[0])))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_fully_list(y.args[1]))w.throw_error(x.error.type(\"list\",y.args[1],y.indicator));else if(x.type.is_variable(y.args[0])){if(!x.type.is_variable(y.args[1])){var Z=[];for(F=y.args[1].args[1];F.indicator===\"./2\";)Z.push(F.args[0]),F=F.args[1];x.type.is_variable(y.args[0])&&x.type.is_variable(F)?w.throw_error(x.error.instantiation(y.indicator)):Z.length===0&&x.type.is_compound(y.args[1].args[0])?w.throw_error(x.error.type(\"atomic\",y.args[1].args[0],y.indicator)):Z.length>0&&(x.type.is_compound(y.args[1].args[0])||x.type.is_number(y.args[1].args[0]))?w.throw_error(x.error.type(\"atom\",y.args[1].args[0],y.indicator)):Z.length===0?w.prepend([new Pe(b.goal.replace(new j(\"=\",[y.args[1].args[0],y.args[0]],b)),b.substitution,b)]):w.prepend([new Pe(b.goal.replace(new j(\"=\",[new j(y.args[1].args[0].id,Z),y.args[0]])),b.substitution,b)])}}else{if(x.type.is_atomic(y.args[0]))F=new j(\".\",[y.args[0],new j(\"[]\")]);else{F=new j(\"[]\");for(var z=y.args[0].args.length-1;z>=0;z--)F=new j(\".\",[y.args[0].args[z],F]);F=new j(\".\",[new j(y.args[0].id),F])}w.prepend([new Pe(b.goal.replace(new j(\"=\",[F,y.args[1]])),b.substitution,b)])}},\"copy_term/2\":function(w,b,y){var F=y.args[0].rename(w);w.prepend([new Pe(b.goal.replace(new j(\"=\",[F,y.args[1]])),b.substitution,b.parent)])},\"term_variables/2\":function(w,b,y){var F=y.args[0],z=y.args[1];if(!x.type.is_fully_list(z))w.throw_error(x.error.type(\"list\",z,y.indicator));else{var Z=g(s(ye(F.variables()),function($){return new De($)}));w.prepend([new Pe(b.goal.replace(new j(\"=\",[z,Z])),b.substitution,b)])}},\"clause/2\":function(w,b,y){if(x.type.is_variable(y.args[0]))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_callable(y.args[0]))w.throw_error(x.error.type(\"callable\",y.args[0],y.indicator));else if(!x.type.is_variable(y.args[1])&&!x.type.is_callable(y.args[1]))w.throw_error(x.error.type(\"callable\",y.args[1],y.indicator));else if(w.session.rules[y.args[0].indicator]!==void 0)if(w.is_public_predicate(y.args[0].indicator)){var F=[];for(var z in w.session.rules[y.args[0].indicator])if(w.session.rules[y.args[0].indicator].hasOwnProperty(z)){var Z=w.session.rules[y.args[0].indicator][z];w.session.renamed_variables={},Z=Z.rename(w),Z.body===null&&(Z.body=new j(\"true\"));var $=new j(\",\",[new j(\"=\",[Z.head,y.args[0]]),new j(\"=\",[Z.body,y.args[1]])]);F.push(new Pe(b.goal.replace($),b.substitution,b))}w.prepend(F)}else w.throw_error(x.error.permission(\"access\",\"private_procedure\",y.args[0].indicator,y.indicator))},\"current_predicate/1\":function(w,b,y){var F=y.args[0];if(!x.type.is_variable(F)&&(!x.type.is_compound(F)||F.indicator!==\"//2\"))w.throw_error(x.error.type(\"predicate_indicator\",F,y.indicator));else if(!x.type.is_variable(F)&&!x.type.is_variable(F.args[0])&&!x.type.is_atom(F.args[0]))w.throw_error(x.error.type(\"atom\",F.args[0],y.indicator));else if(!x.type.is_variable(F)&&!x.type.is_variable(F.args[1])&&!x.type.is_integer(F.args[1]))w.throw_error(x.error.type(\"integer\",F.args[1],y.indicator));else{var z=[];for(var Z in w.session.rules)if(w.session.rules.hasOwnProperty(Z)){var $=Z.lastIndexOf(\"/\"),oe=Z.substr(0,$),xe=parseInt(Z.substr($+1,Z.length-($+1))),Te=new j(\"/\",[new j(oe),new Re(xe,!1)]),lt=new j(\"=\",[Te,F]);z.push(new Pe(b.goal.replace(lt),b.substitution,b))}w.prepend(z)}},\"asserta/1\":function(w,b,y){if(x.type.is_variable(y.args[0]))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_callable(y.args[0]))w.throw_error(x.error.type(\"callable\",y.args[0],y.indicator));else{var F,z;y.args[0].indicator===\":-/2\"?(F=y.args[0].args[0],z=Ce(y.args[0].args[1])):(F=y.args[0],z=null),x.type.is_callable(F)?z!==null&&!x.type.is_callable(z)?w.throw_error(x.error.type(\"callable\",z,y.indicator)):w.is_public_predicate(F.indicator)?(w.session.rules[F.indicator]===void 0&&(w.session.rules[F.indicator]=[]),w.session.public_predicates[F.indicator]=!0,w.session.rules[F.indicator]=[new Ye(F,z,!0)].concat(w.session.rules[F.indicator]),w.success(b)):w.throw_error(x.error.permission(\"modify\",\"static_procedure\",F.indicator,y.indicator)):w.throw_error(x.error.type(\"callable\",F,y.indicator))}},\"assertz/1\":function(w,b,y){if(x.type.is_variable(y.args[0]))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_callable(y.args[0]))w.throw_error(x.error.type(\"callable\",y.args[0],y.indicator));else{var F,z;y.args[0].indicator===\":-/2\"?(F=y.args[0].args[0],z=Ce(y.args[0].args[1])):(F=y.args[0],z=null),x.type.is_callable(F)?z!==null&&!x.type.is_callable(z)?w.throw_error(x.error.type(\"callable\",z,y.indicator)):w.is_public_predicate(F.indicator)?(w.session.rules[F.indicator]===void 0&&(w.session.rules[F.indicator]=[]),w.session.public_predicates[F.indicator]=!0,w.session.rules[F.indicator].push(new Ye(F,z,!0)),w.success(b)):w.throw_error(x.error.permission(\"modify\",\"static_procedure\",F.indicator,y.indicator)):w.throw_error(x.error.type(\"callable\",F,y.indicator))}},\"retract/1\":function(w,b,y){if(x.type.is_variable(y.args[0]))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_callable(y.args[0]))w.throw_error(x.error.type(\"callable\",y.args[0],y.indicator));else{var F,z;if(y.args[0].indicator===\":-/2\"?(F=y.args[0].args[0],z=y.args[0].args[1]):(F=y.args[0],z=new j(\"true\")),typeof b.retract>\"u\")if(w.is_public_predicate(F.indicator)){if(w.session.rules[F.indicator]!==void 0){for(var Z=[],$=0;$<w.session.rules[F.indicator].length;$++){w.session.renamed_variables={};var oe=w.session.rules[F.indicator][$],xe=oe.rename(w);xe.body===null&&(xe.body=new j(\"true\",[]));var Te=w.get_flag(\"occurs_check\").indicator===\"true/0\",lt=x.unify(new j(\",\",[F,z]),new j(\",\",[xe.head,xe.body]),Te);if(lt!==null){var It=new Pe(b.goal.replace(new j(\",\",[new j(\"retract\",[new j(\":-\",[F,z])]),new j(\",\",[new j(\"=\",[F,xe.head]),new j(\"=\",[z,xe.body])])])),b.substitution,b);It.retract=oe,Z.push(It)}}w.prepend(Z)}}else w.throw_error(x.error.permission(\"modify\",\"static_procedure\",F.indicator,y.indicator));else fe(w,b,F.indicator,b.retract)}},\"retractall/1\":function(w,b,y){var F=y.args[0];x.type.is_variable(F)?w.throw_error(x.error.instantiation(y.indicator)):x.type.is_callable(F)?w.prepend([new Pe(b.goal.replace(new j(\",\",[new j(\"retract\",[new x.type.Term(\":-\",[F,new De(\"_\")])]),new j(\"fail\",[])])),b.substitution,b),new Pe(b.goal.replace(null),b.substitution,b)]):w.throw_error(x.error.type(\"callable\",F,y.indicator))},\"abolish/1\":function(w,b,y){if(x.type.is_variable(y.args[0])||x.type.is_term(y.args[0])&&y.args[0].indicator===\"//2\"&&(x.type.is_variable(y.args[0].args[0])||x.type.is_variable(y.args[0].args[1])))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_term(y.args[0])||y.args[0].indicator!==\"//2\")w.throw_error(x.error.type(\"predicate_indicator\",y.args[0],y.indicator));else if(!x.type.is_atom(y.args[0].args[0]))w.throw_error(x.error.type(\"atom\",y.args[0].args[0],y.indicator));else if(!x.type.is_integer(y.args[0].args[1]))w.throw_error(x.error.type(\"integer\",y.args[0].args[1],y.indicator));else if(y.args[0].args[1].value<0)w.throw_error(x.error.domain(\"not_less_than_zero\",y.args[0].args[1],y.indicator));else if(x.type.is_number(w.get_flag(\"max_arity\"))&&y.args[0].args[1].value>w.get_flag(\"max_arity\").value)w.throw_error(x.error.representation(\"max_arity\",y.indicator));else{var F=y.args[0].args[0].id+\"/\"+y.args[0].args[1].value;w.is_public_predicate(F)?(delete w.session.rules[F],w.success(b)):w.throw_error(x.error.permission(\"modify\",\"static_procedure\",F,y.indicator))}},\"atom_length/2\":function(w,b,y){if(x.type.is_variable(y.args[0]))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_atom(y.args[0]))w.throw_error(x.error.type(\"atom\",y.args[0],y.indicator));else if(!x.type.is_variable(y.args[1])&&!x.type.is_integer(y.args[1]))w.throw_error(x.error.type(\"integer\",y.args[1],y.indicator));else if(x.type.is_integer(y.args[1])&&y.args[1].value<0)w.throw_error(x.error.domain(\"not_less_than_zero\",y.args[1],y.indicator));else{var F=new Re(y.args[0].id.length,!1);w.prepend([new Pe(b.goal.replace(new j(\"=\",[F,y.args[1]])),b.substitution,b)])}},\"atom_concat/3\":function(w,b,y){var F,z,Z=y.args[0],$=y.args[1],oe=y.args[2];if(x.type.is_variable(oe)&&(x.type.is_variable(Z)||x.type.is_variable($)))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(Z)&&!x.type.is_atom(Z))w.throw_error(x.error.type(\"atom\",Z,y.indicator));else if(!x.type.is_variable($)&&!x.type.is_atom($))w.throw_error(x.error.type(\"atom\",$,y.indicator));else if(!x.type.is_variable(oe)&&!x.type.is_atom(oe))w.throw_error(x.error.type(\"atom\",oe,y.indicator));else{var xe=x.type.is_variable(Z),Te=x.type.is_variable($);if(!xe&&!Te)z=new j(\"=\",[oe,new j(Z.id+$.id)]),w.prepend([new Pe(b.goal.replace(z),b.substitution,b)]);else if(xe&&!Te)F=oe.id.substr(0,oe.id.length-$.id.length),F+$.id===oe.id&&(z=new j(\"=\",[Z,new j(F)]),w.prepend([new Pe(b.goal.replace(z),b.substitution,b)]));else if(Te&&!xe)F=oe.id.substr(Z.id.length),Z.id+F===oe.id&&(z=new j(\"=\",[$,new j(F)]),w.prepend([new Pe(b.goal.replace(z),b.substitution,b)]));else{for(var lt=[],It=0;It<=oe.id.length;It++){var qt=new j(oe.id.substr(0,It)),ir=new j(oe.id.substr(It));z=new j(\",\",[new j(\"=\",[qt,Z]),new j(\"=\",[ir,$])]),lt.push(new Pe(b.goal.replace(z),b.substitution,b))}w.prepend(lt)}}},\"sub_atom/5\":function(w,b,y){var F,z=y.args[0],Z=y.args[1],$=y.args[2],oe=y.args[3],xe=y.args[4];if(x.type.is_variable(z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(Z)&&!x.type.is_integer(Z))w.throw_error(x.error.type(\"integer\",Z,y.indicator));else if(!x.type.is_variable($)&&!x.type.is_integer($))w.throw_error(x.error.type(\"integer\",$,y.indicator));else if(!x.type.is_variable(oe)&&!x.type.is_integer(oe))w.throw_error(x.error.type(\"integer\",oe,y.indicator));else if(x.type.is_integer(Z)&&Z.value<0)w.throw_error(x.error.domain(\"not_less_than_zero\",Z,y.indicator));else if(x.type.is_integer($)&&$.value<0)w.throw_error(x.error.domain(\"not_less_than_zero\",$,y.indicator));else if(x.type.is_integer(oe)&&oe.value<0)w.throw_error(x.error.domain(\"not_less_than_zero\",oe,y.indicator));else{var Te=[],lt=[],It=[];if(x.type.is_variable(Z))for(F=0;F<=z.id.length;F++)Te.push(F);else Te.push(Z.value);if(x.type.is_variable($))for(F=0;F<=z.id.length;F++)lt.push(F);else lt.push($.value);if(x.type.is_variable(oe))for(F=0;F<=z.id.length;F++)It.push(F);else It.push(oe.value);var qt=[];for(var ir in Te)if(Te.hasOwnProperty(ir)){F=Te[ir];for(var Pt in lt)if(lt.hasOwnProperty(Pt)){var gn=lt[Pt],Pr=z.id.length-F-gn;if(e(It,Pr)!==-1&&F+gn+Pr===z.id.length){var Ir=z.id.substr(F,gn);if(z.id===z.id.substr(0,F)+Ir+z.id.substr(F+gn,Pr)){var Nr=new j(\"=\",[new j(Ir),xe]),nn=new j(\"=\",[Z,new Re(F)]),ai=new j(\"=\",[$,new Re(gn)]),wo=new j(\"=\",[oe,new Re(Pr)]),ns=new j(\",\",[new j(\",\",[new j(\",\",[nn,ai]),wo]),Nr]);qt.push(new Pe(b.goal.replace(ns),b.substitution,b))}}}}w.prepend(qt)}},\"atom_chars/2\":function(w,b,y){var F=y.args[0],z=y.args[1];if(x.type.is_variable(F)&&x.type.is_variable(z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(F)&&!x.type.is_atom(F))w.throw_error(x.error.type(\"atom\",F,y.indicator));else if(x.type.is_variable(F)){for(var oe=z,xe=x.type.is_variable(F),Te=\"\";oe.indicator===\"./2\";){if(x.type.is_character(oe.args[0]))Te+=oe.args[0].id;else if(x.type.is_variable(oe.args[0])&&xe){w.throw_error(x.error.instantiation(y.indicator));return}else if(!x.type.is_variable(oe.args[0])){w.throw_error(x.error.type(\"character\",oe.args[0],y.indicator));return}oe=oe.args[1]}x.type.is_variable(oe)&&xe?w.throw_error(x.error.instantiation(y.indicator)):!x.type.is_empty_list(oe)&&!x.type.is_variable(oe)?w.throw_error(x.error.type(\"list\",z,y.indicator)):w.prepend([new Pe(b.goal.replace(new j(\"=\",[new j(Te),F])),b.substitution,b)])}else{for(var Z=new j(\"[]\"),$=F.id.length-1;$>=0;$--)Z=new j(\".\",[new j(F.id.charAt($)),Z]);w.prepend([new Pe(b.goal.replace(new j(\"=\",[z,Z])),b.substitution,b)])}},\"atom_codes/2\":function(w,b,y){var F=y.args[0],z=y.args[1];if(x.type.is_variable(F)&&x.type.is_variable(z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(F)&&!x.type.is_atom(F))w.throw_error(x.error.type(\"atom\",F,y.indicator));else if(x.type.is_variable(F)){for(var oe=z,xe=x.type.is_variable(F),Te=\"\";oe.indicator===\"./2\";){if(x.type.is_character_code(oe.args[0]))Te+=c(oe.args[0].value);else if(x.type.is_variable(oe.args[0])&&xe){w.throw_error(x.error.instantiation(y.indicator));return}else if(!x.type.is_variable(oe.args[0])){w.throw_error(x.error.representation(\"character_code\",y.indicator));return}oe=oe.args[1]}x.type.is_variable(oe)&&xe?w.throw_error(x.error.instantiation(y.indicator)):!x.type.is_empty_list(oe)&&!x.type.is_variable(oe)?w.throw_error(x.error.type(\"list\",z,y.indicator)):w.prepend([new Pe(b.goal.replace(new j(\"=\",[new j(Te),F])),b.substitution,b)])}else{for(var Z=new j(\"[]\"),$=F.id.length-1;$>=0;$--)Z=new j(\".\",[new Re(n(F.id,$),!1),Z]);w.prepend([new Pe(b.goal.replace(new j(\"=\",[z,Z])),b.substitution,b)])}},\"char_code/2\":function(w,b,y){var F=y.args[0],z=y.args[1];if(x.type.is_variable(F)&&x.type.is_variable(z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(F)&&!x.type.is_character(F))w.throw_error(x.error.type(\"character\",F,y.indicator));else if(!x.type.is_variable(z)&&!x.type.is_integer(z))w.throw_error(x.error.type(\"integer\",z,y.indicator));else if(!x.type.is_variable(z)&&!x.type.is_character_code(z))w.throw_error(x.error.representation(\"character_code\",y.indicator));else if(x.type.is_variable(z)){var Z=new Re(n(F.id,0),!1);w.prepend([new Pe(b.goal.replace(new j(\"=\",[Z,z])),b.substitution,b)])}else{var $=new j(c(z.value));w.prepend([new Pe(b.goal.replace(new j(\"=\",[$,F])),b.substitution,b)])}},\"number_chars/2\":function(w,b,y){var F,z=y.args[0],Z=y.args[1];if(x.type.is_variable(z)&&x.type.is_variable(Z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(z)&&!x.type.is_number(z))w.throw_error(x.error.type(\"number\",z,y.indicator));else if(!x.type.is_variable(Z)&&!x.type.is_list(Z))w.throw_error(x.error.type(\"list\",Z,y.indicator));else{var $=x.type.is_variable(z);if(!x.type.is_variable(Z)){var oe=Z,xe=!0;for(F=\"\";oe.indicator===\"./2\";){if(x.type.is_character(oe.args[0]))F+=oe.args[0].id;else if(x.type.is_variable(oe.args[0]))xe=!1;else if(!x.type.is_variable(oe.args[0])){w.throw_error(x.error.type(\"character\",oe.args[0],y.indicator));return}oe=oe.args[1]}if(xe=xe&&x.type.is_empty_list(oe),!x.type.is_empty_list(oe)&&!x.type.is_variable(oe)){w.throw_error(x.error.type(\"list\",Z,y.indicator));return}if(!xe&&$){w.throw_error(x.error.instantiation(y.indicator));return}else if(xe)if(x.type.is_variable(oe)&&$){w.throw_error(x.error.instantiation(y.indicator));return}else{var Te=w.parse(F),lt=Te.value;!x.type.is_number(lt)||Te.tokens[Te.tokens.length-1].space?w.throw_error(x.error.syntax_by_predicate(\"parseable_number\",y.indicator)):w.prepend([new Pe(b.goal.replace(new j(\"=\",[z,lt])),b.substitution,b)]);return}}if(!$){F=z.toString();for(var It=new j(\"[]\"),qt=F.length-1;qt>=0;qt--)It=new j(\".\",[new j(F.charAt(qt)),It]);w.prepend([new Pe(b.goal.replace(new j(\"=\",[Z,It])),b.substitution,b)])}}},\"number_codes/2\":function(w,b,y){var F,z=y.args[0],Z=y.args[1];if(x.type.is_variable(z)&&x.type.is_variable(Z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(z)&&!x.type.is_number(z))w.throw_error(x.error.type(\"number\",z,y.indicator));else if(!x.type.is_variable(Z)&&!x.type.is_list(Z))w.throw_error(x.error.type(\"list\",Z,y.indicator));else{var $=x.type.is_variable(z);if(!x.type.is_variable(Z)){var oe=Z,xe=!0;for(F=\"\";oe.indicator===\"./2\";){if(x.type.is_character_code(oe.args[0]))F+=c(oe.args[0].value);else if(x.type.is_variable(oe.args[0]))xe=!1;else if(!x.type.is_variable(oe.args[0])){w.throw_error(x.error.type(\"character_code\",oe.args[0],y.indicator));return}oe=oe.args[1]}if(xe=xe&&x.type.is_empty_list(oe),!x.type.is_empty_list(oe)&&!x.type.is_variable(oe)){w.throw_error(x.error.type(\"list\",Z,y.indicator));return}if(!xe&&$){w.throw_error(x.error.instantiation(y.indicator));return}else if(xe)if(x.type.is_variable(oe)&&$){w.throw_error(x.error.instantiation(y.indicator));return}else{var Te=w.parse(F),lt=Te.value;!x.type.is_number(lt)||Te.tokens[Te.tokens.length-1].space?w.throw_error(x.error.syntax_by_predicate(\"parseable_number\",y.indicator)):w.prepend([new Pe(b.goal.replace(new j(\"=\",[z,lt])),b.substitution,b)]);return}}if(!$){F=z.toString();for(var It=new j(\"[]\"),qt=F.length-1;qt>=0;qt--)It=new j(\".\",[new Re(n(F,qt),!1),It]);w.prepend([new Pe(b.goal.replace(new j(\"=\",[Z,It])),b.substitution,b)])}}},\"upcase_atom/2\":function(w,b,y){var F=y.args[0],z=y.args[1];x.type.is_variable(F)?w.throw_error(x.error.instantiation(y.indicator)):x.type.is_atom(F)?!x.type.is_variable(z)&&!x.type.is_atom(z)?w.throw_error(x.error.type(\"atom\",z,y.indicator)):w.prepend([new Pe(b.goal.replace(new j(\"=\",[z,new j(F.id.toUpperCase(),[])])),b.substitution,b)]):w.throw_error(x.error.type(\"atom\",F,y.indicator))},\"downcase_atom/2\":function(w,b,y){var F=y.args[0],z=y.args[1];x.type.is_variable(F)?w.throw_error(x.error.instantiation(y.indicator)):x.type.is_atom(F)?!x.type.is_variable(z)&&!x.type.is_atom(z)?w.throw_error(x.error.type(\"atom\",z,y.indicator)):w.prepend([new Pe(b.goal.replace(new j(\"=\",[z,new j(F.id.toLowerCase(),[])])),b.substitution,b)]):w.throw_error(x.error.type(\"atom\",F,y.indicator))},\"atomic_list_concat/2\":function(w,b,y){var F=y.args[0],z=y.args[1];w.prepend([new Pe(b.goal.replace(new j(\"atomic_list_concat\",[F,new j(\"\",[]),z])),b.substitution,b)])},\"atomic_list_concat/3\":function(w,b,y){var F=y.args[0],z=y.args[1],Z=y.args[2];if(x.type.is_variable(z)||x.type.is_variable(F)&&x.type.is_variable(Z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(F)&&!x.type.is_list(F))w.throw_error(x.error.type(\"list\",F,y.indicator));else if(!x.type.is_variable(Z)&&!x.type.is_atom(Z))w.throw_error(x.error.type(\"atom\",Z,y.indicator));else if(x.type.is_variable(Z)){for(var oe=\"\",xe=F;x.type.is_term(xe)&&xe.indicator===\"./2\";){if(!x.type.is_atom(xe.args[0])&&!x.type.is_number(xe.args[0])){w.throw_error(x.error.type(\"atomic\",xe.args[0],y.indicator));return}oe!==\"\"&&(oe+=z.id),x.type.is_atom(xe.args[0])?oe+=xe.args[0].id:oe+=\"\"+xe.args[0].value,xe=xe.args[1]}oe=new j(oe,[]),x.type.is_variable(xe)?w.throw_error(x.error.instantiation(y.indicator)):!x.type.is_term(xe)||xe.indicator!==\"[]/0\"?w.throw_error(x.error.type(\"list\",F,y.indicator)):w.prepend([new Pe(b.goal.replace(new j(\"=\",[oe,Z])),b.substitution,b)])}else{var $=g(s(Z.id.split(z.id),function(Te){return new j(Te,[])}));w.prepend([new Pe(b.goal.replace(new j(\"=\",[$,F])),b.substitution,b)])}},\"@=</2\":function(w,b,y){x.compare(y.args[0],y.args[1])<=0&&w.success(b)},\"==/2\":function(w,b,y){x.compare(y.args[0],y.args[1])===0&&w.success(b)},\"\\\\==/2\":function(w,b,y){x.compare(y.args[0],y.args[1])!==0&&w.success(b)},\"@</2\":function(w,b,y){x.compare(y.args[0],y.args[1])<0&&w.success(b)},\"@>/2\":function(w,b,y){x.compare(y.args[0],y.args[1])>0&&w.success(b)},\"@>=/2\":function(w,b,y){x.compare(y.args[0],y.args[1])>=0&&w.success(b)},\"compare/3\":function(w,b,y){var F=y.args[0],z=y.args[1],Z=y.args[2];if(!x.type.is_variable(F)&&!x.type.is_atom(F))w.throw_error(x.error.type(\"atom\",F,y.indicator));else if(x.type.is_atom(F)&&[\"<\",\">\",\"=\"].indexOf(F.id)===-1)w.throw_error(x.type.domain(\"order\",F,y.indicator));else{var $=x.compare(z,Z);$=$===0?\"=\":$===-1?\"<\":\">\",w.prepend([new Pe(b.goal.replace(new j(\"=\",[F,new j($,[])])),b.substitution,b)])}},\"is/2\":function(w,b,y){var F=y.args[1].interpret(w);x.type.is_number(F)?w.prepend([new Pe(b.goal.replace(new j(\"=\",[y.args[0],F],w.level)),b.substitution,b)]):w.throw_error(F)},\"between/3\":function(w,b,y){var F=y.args[0],z=y.args[1],Z=y.args[2];if(x.type.is_variable(F)||x.type.is_variable(z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_integer(F))w.throw_error(x.error.type(\"integer\",F,y.indicator));else if(!x.type.is_integer(z))w.throw_error(x.error.type(\"integer\",z,y.indicator));else if(!x.type.is_variable(Z)&&!x.type.is_integer(Z))w.throw_error(x.error.type(\"integer\",Z,y.indicator));else if(x.type.is_variable(Z)){var $=[new Pe(b.goal.replace(new j(\"=\",[Z,F])),b.substitution,b)];F.value<z.value&&$.push(new Pe(b.goal.replace(new j(\"between\",[new Re(F.value+1,!1),z,Z])),b.substitution,b)),w.prepend($)}else F.value<=Z.value&&z.value>=Z.value&&w.success(b)},\"succ/2\":function(w,b,y){var F=y.args[0],z=y.args[1];x.type.is_variable(F)&&x.type.is_variable(z)?w.throw_error(x.error.instantiation(y.indicator)):!x.type.is_variable(F)&&!x.type.is_integer(F)?w.throw_error(x.error.type(\"integer\",F,y.indicator)):!x.type.is_variable(z)&&!x.type.is_integer(z)?w.throw_error(x.error.type(\"integer\",z,y.indicator)):!x.type.is_variable(F)&&F.value<0?w.throw_error(x.error.domain(\"not_less_than_zero\",F,y.indicator)):!x.type.is_variable(z)&&z.value<0?w.throw_error(x.error.domain(\"not_less_than_zero\",z,y.indicator)):(x.type.is_variable(z)||z.value>0)&&(x.type.is_variable(F)?w.prepend([new Pe(b.goal.replace(new j(\"=\",[F,new Re(z.value-1,!1)])),b.substitution,b)]):w.prepend([new Pe(b.goal.replace(new j(\"=\",[z,new Re(F.value+1,!1)])),b.substitution,b)]))},\"=:=/2\":function(w,b,y){var F=x.arithmetic_compare(w,y.args[0],y.args[1]);x.type.is_term(F)?w.throw_error(F):F===0&&w.success(b)},\"=\\\\=/2\":function(w,b,y){var F=x.arithmetic_compare(w,y.args[0],y.args[1]);x.type.is_term(F)?w.throw_error(F):F!==0&&w.success(b)},\"</2\":function(w,b,y){var F=x.arithmetic_compare(w,y.args[0],y.args[1]);x.type.is_term(F)?w.throw_error(F):F<0&&w.success(b)},\"=</2\":function(w,b,y){var F=x.arithmetic_compare(w,y.args[0],y.args[1]);x.type.is_term(F)?w.throw_error(F):F<=0&&w.success(b)},\">/2\":function(w,b,y){var F=x.arithmetic_compare(w,y.args[0],y.args[1]);x.type.is_term(F)?w.throw_error(F):F>0&&w.success(b)},\">=/2\":function(w,b,y){var F=x.arithmetic_compare(w,y.args[0],y.args[1]);x.type.is_term(F)?w.throw_error(F):F>=0&&w.success(b)},\"var/1\":function(w,b,y){x.type.is_variable(y.args[0])&&w.success(b)},\"atom/1\":function(w,b,y){x.type.is_atom(y.args[0])&&w.success(b)},\"atomic/1\":function(w,b,y){x.type.is_atomic(y.args[0])&&w.success(b)},\"compound/1\":function(w,b,y){x.type.is_compound(y.args[0])&&w.success(b)},\"integer/1\":function(w,b,y){x.type.is_integer(y.args[0])&&w.success(b)},\"float/1\":function(w,b,y){x.type.is_float(y.args[0])&&w.success(b)},\"number/1\":function(w,b,y){x.type.is_number(y.args[0])&&w.success(b)},\"nonvar/1\":function(w,b,y){x.type.is_variable(y.args[0])||w.success(b)},\"ground/1\":function(w,b,y){y.variables().length===0&&w.success(b)},\"acyclic_term/1\":function(w,b,y){for(var F=b.substitution.apply(b.substitution),z=y.args[0].variables(),Z=0;Z<z.length;Z++)if(b.substitution.links[z[Z]]!==void 0&&!b.substitution.links[z[Z]].equals(F.links[z[Z]]))return;w.success(b)},\"callable/1\":function(w,b,y){x.type.is_callable(y.args[0])&&w.success(b)},\"is_list/1\":function(w,b,y){for(var F=y.args[0];x.type.is_term(F)&&F.indicator===\"./2\";)F=F.args[1];x.type.is_term(F)&&F.indicator===\"[]/0\"&&w.success(b)},\"current_input/1\":function(w,b,y){var F=y.args[0];!x.type.is_variable(F)&&!x.type.is_stream(F)&&!x.type.is_atom(F)?w.throw_error(x.error.domain(\"stream\",F,y.indicator)):(x.type.is_atom(F)&&w.get_stream_by_alias(F.id)&&(F=w.get_stream_by_alias(F.id)),w.prepend([new Pe(b.goal.replace(new j(\"=\",[F,w.get_current_input()])),b.substitution,b)]))},\"current_output/1\":function(w,b,y){var F=y.args[0];!x.type.is_variable(F)&&!x.type.is_stream(F)&&!x.type.is_atom(F)?w.throw_error(x.error.domain(\"stream_or_alias\",F,y.indicator)):(x.type.is_atom(F)&&w.get_stream_by_alias(F.id)&&(F=w.get_stream_by_alias(F.id)),w.prepend([new Pe(b.goal.replace(new j(\"=\",[F,w.get_current_output()])),b.substitution,b)]))},\"set_input/1\":function(w,b,y){var F=y.args[0],z=x.type.is_stream(F)?F:w.get_stream_by_alias(F.id);x.type.is_variable(F)?w.throw_error(x.error.instantiation(y.indicator)):!x.type.is_variable(F)&&!x.type.is_stream(F)&&!x.type.is_atom(F)?w.throw_error(x.error.domain(\"stream_or_alias\",F,y.indicator)):x.type.is_stream(z)?z.output===!0?w.throw_error(x.error.permission(\"input\",\"stream\",F,y.indicator)):(w.set_current_input(z),w.success(b)):w.throw_error(x.error.existence(\"stream\",F,y.indicator))},\"set_output/1\":function(w,b,y){var F=y.args[0],z=x.type.is_stream(F)?F:w.get_stream_by_alias(F.id);x.type.is_variable(F)?w.throw_error(x.error.instantiation(y.indicator)):!x.type.is_variable(F)&&!x.type.is_stream(F)&&!x.type.is_atom(F)?w.throw_error(x.error.domain(\"stream_or_alias\",F,y.indicator)):x.type.is_stream(z)?z.input===!0?w.throw_error(x.error.permission(\"output\",\"stream\",F,y.indicator)):(w.set_current_output(z),w.success(b)):w.throw_error(x.error.existence(\"stream\",F,y.indicator))},\"open/3\":function(w,b,y){var F=y.args[0],z=y.args[1],Z=y.args[2];w.prepend([new Pe(b.goal.replace(new j(\"open\",[F,z,Z,new j(\"[]\",[])])),b.substitution,b)])},\"open/4\":function(w,b,y){var F=y.args[0],z=y.args[1],Z=y.args[2],$=y.args[3];if(x.type.is_variable(F)||x.type.is_variable(z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(z)&&!x.type.is_atom(z))w.throw_error(x.error.type(\"atom\",z,y.indicator));else if(!x.type.is_list($))w.throw_error(x.error.type(\"list\",$,y.indicator));else if(!x.type.is_variable(Z))w.throw_error(x.error.type(\"variable\",Z,y.indicator));else if(!x.type.is_atom(F)&&!x.type.is_streamable(F))w.throw_error(x.error.domain(\"source_sink\",F,y.indicator));else if(!x.type.is_io_mode(z))w.throw_error(x.error.domain(\"io_mode\",z,y.indicator));else{for(var oe={},xe=$,Te;x.type.is_term(xe)&&xe.indicator===\"./2\";){if(Te=xe.args[0],x.type.is_variable(Te)){w.throw_error(x.error.instantiation(y.indicator));return}else if(!x.type.is_stream_option(Te)){w.throw_error(x.error.domain(\"stream_option\",Te,y.indicator));return}oe[Te.id]=Te.args[0].id,xe=xe.args[1]}if(xe.indicator!==\"[]/0\"){x.type.is_variable(xe)?w.throw_error(x.error.instantiation(y.indicator)):w.throw_error(x.error.type(\"list\",$,y.indicator));return}else{var lt=oe.alias;if(lt&&w.get_stream_by_alias(lt)){w.throw_error(x.error.permission(\"open\",\"source_sink\",new j(\"alias\",[new j(lt,[])]),y.indicator));return}oe.type||(oe.type=\"text\");var It;if(x.type.is_atom(F)?It=w.file_system_open(F.id,oe.type,z.id):It=F.stream(oe.type,z.id),It===!1){w.throw_error(x.error.permission(\"open\",\"source_sink\",F,y.indicator));return}else if(It===null){w.throw_error(x.error.existence(\"source_sink\",F,y.indicator));return}var qt=new Fe(It,z.id,oe.alias,oe.type,oe.reposition===\"true\",oe.eof_action);lt?w.session.streams[lt]=qt:w.session.streams[qt.id]=qt,w.prepend([new Pe(b.goal.replace(new j(\"=\",[Z,qt])),b.substitution,b)])}}},\"close/1\":function(w,b,y){var F=y.args[0];w.prepend([new Pe(b.goal.replace(new j(\"close\",[F,new j(\"[]\",[])])),b.substitution,b)])},\"close/2\":function(w,b,y){var F=y.args[0],z=y.args[1],Z=x.type.is_stream(F)?F:w.get_stream_by_alias(F.id);if(x.type.is_variable(F)||x.type.is_variable(z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_list(z))w.throw_error(x.error.type(\"list\",z,y.indicator));else if(!x.type.is_stream(F)&&!x.type.is_atom(F))w.throw_error(x.error.domain(\"stream_or_alias\",F,y.indicator));else if(!x.type.is_stream(Z)||Z.stream===null)w.throw_error(x.error.existence(\"stream\",F,y.indicator));else{for(var $={},oe=z,xe;x.type.is_term(oe)&&oe.indicator===\"./2\";){if(xe=oe.args[0],x.type.is_variable(xe)){w.throw_error(x.error.instantiation(y.indicator));return}else if(!x.type.is_close_option(xe)){w.throw_error(x.error.domain(\"close_option\",xe,y.indicator));return}$[xe.id]=xe.args[0].id===\"true\",oe=oe.args[1]}if(oe.indicator!==\"[]/0\"){x.type.is_variable(oe)?w.throw_error(x.error.instantiation(y.indicator)):w.throw_error(x.error.type(\"list\",z,y.indicator));return}else{if(Z===w.session.standard_input||Z===w.session.standard_output){w.success(b);return}else Z===w.session.current_input?w.session.current_input=w.session.standard_input:Z===w.session.current_output&&(w.session.current_output=w.session.current_output);Z.alias!==null?delete w.session.streams[Z.alias]:delete w.session.streams[Z.id],Z.output&&Z.stream.flush();var Te=Z.stream.close();Z.stream=null,($.force===!0||Te===!0)&&w.success(b)}}},\"flush_output/0\":function(w,b,y){w.prepend([new Pe(b.goal.replace(new j(\",\",[new j(\"current_output\",[new De(\"S\")]),new j(\"flush_output\",[new De(\"S\")])])),b.substitution,b)])},\"flush_output/1\":function(w,b,y){var F=y.args[0],z=x.type.is_stream(F)?F:w.get_stream_by_alias(F.id);x.type.is_variable(F)?w.throw_error(x.error.instantiation(y.indicator)):!x.type.is_stream(F)&&!x.type.is_atom(F)?w.throw_error(x.error.domain(\"stream_or_alias\",F,y.indicator)):!x.type.is_stream(z)||z.stream===null?w.throw_error(x.error.existence(\"stream\",F,y.indicator)):F.input===!0?w.throw_error(x.error.permission(\"output\",\"stream\",output,y.indicator)):(z.stream.flush(),w.success(b))},\"stream_property/2\":function(w,b,y){var F=y.args[0],z=y.args[1],Z=x.type.is_stream(F)?F:w.get_stream_by_alias(F.id);if(!x.type.is_variable(F)&&!x.type.is_stream(F)&&!x.type.is_atom(F))w.throw_error(x.error.domain(\"stream_or_alias\",F,y.indicator));else if(!x.type.is_variable(F)&&(!x.type.is_stream(Z)||Z.stream===null))w.throw_error(x.error.existence(\"stream\",F,y.indicator));else if(!x.type.is_variable(z)&&!x.type.is_stream_property(z))w.throw_error(x.error.domain(\"stream_property\",z,y.indicator));else{var $=[],oe=[];if(!x.type.is_variable(F))$.push(Z);else for(var xe in w.session.streams)$.push(w.session.streams[xe]);for(var Te=0;Te<$.length;Te++){var lt=[];$[Te].filename&&lt.push(new j(\"file_name\",[new j($[Te].file_name,[])])),lt.push(new j(\"mode\",[new j($[Te].mode,[])])),lt.push(new j($[Te].input?\"input\":\"output\",[])),$[Te].alias&&lt.push(new j(\"alias\",[new j($[Te].alias,[])])),lt.push(new j(\"position\",[typeof $[Te].position==\"number\"?new Re($[Te].position,!1):new j($[Te].position,[])])),lt.push(new j(\"end_of_stream\",[new j($[Te].position===\"end_of_stream\"?\"at\":$[Te].position===\"past_end_of_stream\"?\"past\":\"not\",[])])),lt.push(new j(\"eof_action\",[new j($[Te].eof_action,[])])),lt.push(new j(\"reposition\",[new j($[Te].reposition?\"true\":\"false\",[])])),lt.push(new j(\"type\",[new j($[Te].type,[])]));for(var It=0;It<lt.length;It++)oe.push(new Pe(b.goal.replace(new j(\",\",[new j(\"=\",[x.type.is_variable(F)?F:Z,$[Te]]),new j(\"=\",[z,lt[It]])])),b.substitution,b))}w.prepend(oe)}},\"at_end_of_stream/0\":function(w,b,y){w.prepend([new Pe(b.goal.replace(new j(\",\",[new j(\"current_input\",[new De(\"S\")]),new j(\",\",[new j(\"stream_property\",[new De(\"S\"),new j(\"end_of_stream\",[new De(\"E\")])]),new j(\",\",[new j(\"!\",[]),new j(\";\",[new j(\"=\",[new De(\"E\"),new j(\"at\",[])]),new j(\"=\",[new De(\"E\"),new j(\"past\",[])])])])])])),b.substitution,b)])},\"at_end_of_stream/1\":function(w,b,y){var F=y.args[0];w.prepend([new Pe(b.goal.replace(new j(\",\",[new j(\"stream_property\",[F,new j(\"end_of_stream\",[new De(\"E\")])]),new j(\",\",[new j(\"!\",[]),new j(\";\",[new j(\"=\",[new De(\"E\"),new j(\"at\",[])]),new j(\"=\",[new De(\"E\"),new j(\"past\",[])])])])])),b.substitution,b)])},\"set_stream_position/2\":function(w,b,y){var F=y.args[0],z=y.args[1],Z=x.type.is_stream(F)?F:w.get_stream_by_alias(F.id);x.type.is_variable(F)||x.type.is_variable(z)?w.throw_error(x.error.instantiation(y.indicator)):!x.type.is_stream(F)&&!x.type.is_atom(F)?w.throw_error(x.error.domain(\"stream_or_alias\",F,y.indicator)):!x.type.is_stream(Z)||Z.stream===null?w.throw_error(x.error.existence(\"stream\",F,y.indicator)):x.type.is_stream_position(z)?Z.reposition===!1?w.throw_error(x.error.permission(\"reposition\",\"stream\",F,y.indicator)):(x.type.is_integer(z)?Z.position=z.value:Z.position=z.id,w.success(b)):w.throw_error(x.error.domain(\"stream_position\",z,y.indicator))},\"get_char/1\":function(w,b,y){var F=y.args[0];w.prepend([new Pe(b.goal.replace(new j(\",\",[new j(\"current_input\",[new De(\"S\")]),new j(\"get_char\",[new De(\"S\"),F])])),b.substitution,b)])},\"get_char/2\":function(w,b,y){var F=y.args[0],z=y.args[1],Z=x.type.is_stream(F)?F:w.get_stream_by_alias(F.id);if(x.type.is_variable(F))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(z)&&!x.type.is_character(z))w.throw_error(x.error.type(\"in_character\",z,y.indicator));else if(!x.type.is_stream(F)&&!x.type.is_atom(F))w.throw_error(x.error.domain(\"stream_or_alias\",F,y.indicator));else if(!x.type.is_stream(Z)||Z.stream===null)w.throw_error(x.error.existence(\"stream\",F,y.indicator));else if(Z.output)w.throw_error(x.error.permission(\"input\",\"stream\",F,y.indicator));else if(Z.type===\"binary\")w.throw_error(x.error.permission(\"input\",\"binary_stream\",F,y.indicator));else if(Z.position===\"past_end_of_stream\"&&Z.eof_action===\"error\")w.throw_error(x.error.permission(\"input\",\"past_end_of_stream\",F,y.indicator));else{var $;if(Z.position===\"end_of_stream\")$=\"end_of_file\",Z.position=\"past_end_of_stream\";else{if($=Z.stream.get(1,Z.position),$===null){w.throw_error(x.error.representation(\"character\",y.indicator));return}Z.position++}w.prepend([new Pe(b.goal.replace(new j(\"=\",[new j($,[]),z])),b.substitution,b)])}},\"get_code/1\":function(w,b,y){var F=y.args[0];w.prepend([new Pe(b.goal.replace(new j(\",\",[new j(\"current_input\",[new De(\"S\")]),new j(\"get_code\",[new De(\"S\"),F])])),b.substitution,b)])},\"get_code/2\":function(w,b,y){var F=y.args[0],z=y.args[1],Z=x.type.is_stream(F)?F:w.get_stream_by_alias(F.id);if(x.type.is_variable(F))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(z)&&!x.type.is_integer(z))w.throw_error(x.error.type(\"integer\",char,y.indicator));else if(!x.type.is_variable(F)&&!x.type.is_stream(F)&&!x.type.is_atom(F))w.throw_error(x.error.domain(\"stream_or_alias\",F,y.indicator));else if(!x.type.is_stream(Z)||Z.stream===null)w.throw_error(x.error.existence(\"stream\",F,y.indicator));else if(Z.output)w.throw_error(x.error.permission(\"input\",\"stream\",F,y.indicator));else if(Z.type===\"binary\")w.throw_error(x.error.permission(\"input\",\"binary_stream\",F,y.indicator));else if(Z.position===\"past_end_of_stream\"&&Z.eof_action===\"error\")w.throw_error(x.error.permission(\"input\",\"past_end_of_stream\",F,y.indicator));else{var $;if(Z.position===\"end_of_stream\")$=-1,Z.position=\"past_end_of_stream\";else{if($=Z.stream.get(1,Z.position),$===null){w.throw_error(x.error.representation(\"character\",y.indicator));return}$=n($,0),Z.position++}w.prepend([new Pe(b.goal.replace(new j(\"=\",[new Re($,!1),z])),b.substitution,b)])}},\"peek_char/1\":function(w,b,y){var F=y.args[0];w.prepend([new Pe(b.goal.replace(new j(\",\",[new j(\"current_input\",[new De(\"S\")]),new j(\"peek_char\",[new De(\"S\"),F])])),b.substitution,b)])},\"peek_char/2\":function(w,b,y){var F=y.args[0],z=y.args[1],Z=x.type.is_stream(F)?F:w.get_stream_by_alias(F.id);if(x.type.is_variable(F))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(z)&&!x.type.is_character(z))w.throw_error(x.error.type(\"in_character\",z,y.indicator));else if(!x.type.is_stream(F)&&!x.type.is_atom(F))w.throw_error(x.error.domain(\"stream_or_alias\",F,y.indicator));else if(!x.type.is_stream(Z)||Z.stream===null)w.throw_error(x.error.existence(\"stream\",F,y.indicator));else if(Z.output)w.throw_error(x.error.permission(\"input\",\"stream\",F,y.indicator));else if(Z.type===\"binary\")w.throw_error(x.error.permission(\"input\",\"binary_stream\",F,y.indicator));else if(Z.position===\"past_end_of_stream\"&&Z.eof_action===\"error\")w.throw_error(x.error.permission(\"input\",\"past_end_of_stream\",F,y.indicator));else{var $;if(Z.position===\"end_of_stream\")$=\"end_of_file\",Z.position=\"past_end_of_stream\";else if($=Z.stream.get(1,Z.position),$===null){w.throw_error(x.error.representation(\"character\",y.indicator));return}w.prepend([new Pe(b.goal.replace(new j(\"=\",[new j($,[]),z])),b.substitution,b)])}},\"peek_code/1\":function(w,b,y){var F=y.args[0];w.prepend([new Pe(b.goal.replace(new j(\",\",[new j(\"current_input\",[new De(\"S\")]),new j(\"peek_code\",[new De(\"S\"),F])])),b.substitution,b)])},\"peek_code/2\":function(w,b,y){var F=y.args[0],z=y.args[1],Z=x.type.is_stream(F)?F:w.get_stream_by_alias(F.id);if(x.type.is_variable(F))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(z)&&!x.type.is_integer(z))w.throw_error(x.error.type(\"integer\",char,y.indicator));else if(!x.type.is_variable(F)&&!x.type.is_stream(F)&&!x.type.is_atom(F))w.throw_error(x.error.domain(\"stream_or_alias\",F,y.indicator));else if(!x.type.is_stream(Z)||Z.stream===null)w.throw_error(x.error.existence(\"stream\",F,y.indicator));else if(Z.output)w.throw_error(x.error.permission(\"input\",\"stream\",F,y.indicator));else if(Z.type===\"binary\")w.throw_error(x.error.permission(\"input\",\"binary_stream\",F,y.indicator));else if(Z.position===\"past_end_of_stream\"&&Z.eof_action===\"error\")w.throw_error(x.error.permission(\"input\",\"past_end_of_stream\",F,y.indicator));else{var $;if(Z.position===\"end_of_stream\")$=-1,Z.position=\"past_end_of_stream\";else{if($=Z.stream.get(1,Z.position),$===null){w.throw_error(x.error.representation(\"character\",y.indicator));return}$=n($,0)}w.prepend([new Pe(b.goal.replace(new j(\"=\",[new Re($,!1),z])),b.substitution,b)])}},\"put_char/1\":function(w,b,y){var F=y.args[0];w.prepend([new Pe(b.goal.replace(new j(\",\",[new j(\"current_output\",[new De(\"S\")]),new j(\"put_char\",[new De(\"S\"),F])])),b.substitution,b)])},\"put_char/2\":function(w,b,y){var F=y.args[0],z=y.args[1],Z=x.type.is_stream(F)?F:w.get_stream_by_alias(F.id);x.type.is_variable(F)||x.type.is_variable(z)?w.throw_error(x.error.instantiation(y.indicator)):x.type.is_character(z)?!x.type.is_variable(F)&&!x.type.is_stream(F)&&!x.type.is_atom(F)?w.throw_error(x.error.domain(\"stream_or_alias\",F,y.indicator)):!x.type.is_stream(Z)||Z.stream===null?w.throw_error(x.error.existence(\"stream\",F,y.indicator)):Z.input?w.throw_error(x.error.permission(\"output\",\"stream\",F,y.indicator)):Z.type===\"binary\"?w.throw_error(x.error.permission(\"output\",\"binary_stream\",F,y.indicator)):Z.stream.put(z.id,Z.position)&&(typeof Z.position==\"number\"&&Z.position++,w.success(b)):w.throw_error(x.error.type(\"character\",z,y.indicator))},\"put_code/1\":function(w,b,y){var F=y.args[0];w.prepend([new Pe(b.goal.replace(new j(\",\",[new j(\"current_output\",[new De(\"S\")]),new j(\"put_code\",[new De(\"S\"),F])])),b.substitution,b)])},\"put_code/2\":function(w,b,y){var F=y.args[0],z=y.args[1],Z=x.type.is_stream(F)?F:w.get_stream_by_alias(F.id);x.type.is_variable(F)||x.type.is_variable(z)?w.throw_error(x.error.instantiation(y.indicator)):x.type.is_integer(z)?x.type.is_character_code(z)?!x.type.is_variable(F)&&!x.type.is_stream(F)&&!x.type.is_atom(F)?w.throw_error(x.error.domain(\"stream_or_alias\",F,y.indicator)):!x.type.is_stream(Z)||Z.stream===null?w.throw_error(x.error.existence(\"stream\",F,y.indicator)):Z.input?w.throw_error(x.error.permission(\"output\",\"stream\",F,y.indicator)):Z.type===\"binary\"?w.throw_error(x.error.permission(\"output\",\"binary_stream\",F,y.indicator)):Z.stream.put_char(c(z.value),Z.position)&&(typeof Z.position==\"number\"&&Z.position++,w.success(b)):w.throw_error(x.error.representation(\"character_code\",y.indicator)):w.throw_error(x.error.type(\"integer\",z,y.indicator))},\"nl/0\":function(w,b,y){w.prepend([new Pe(b.goal.replace(new j(\",\",[new j(\"current_output\",[new De(\"S\")]),new j(\"put_char\",[new De(\"S\"),new j(`\n`,[])])])),b.substitution,b)])},\"nl/1\":function(w,b,y){var F=y.args[0];w.prepend([new Pe(b.goal.replace(new j(\"put_char\",[F,new j(`\n`,[])])),b.substitution,b)])},\"get_byte/1\":function(w,b,y){var F=y.args[0];w.prepend([new Pe(b.goal.replace(new j(\",\",[new j(\"current_input\",[new De(\"S\")]),new j(\"get_byte\",[new De(\"S\"),F])])),b.substitution,b)])},\"get_byte/2\":function(w,b,y){var F=y.args[0],z=y.args[1],Z=x.type.is_stream(F)?F:w.get_stream_by_alias(F.id);if(x.type.is_variable(F))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(z)&&!x.type.is_byte(z))w.throw_error(x.error.type(\"in_byte\",char,y.indicator));else if(!x.type.is_stream(F)&&!x.type.is_atom(F))w.throw_error(x.error.domain(\"stream_or_alias\",F,y.indicator));else if(!x.type.is_stream(Z)||Z.stream===null)w.throw_error(x.error.existence(\"stream\",F,y.indicator));else if(Z.output)w.throw_error(x.error.permission(\"input\",\"stream\",F,y.indicator));else if(Z.type===\"text\")w.throw_error(x.error.permission(\"input\",\"text_stream\",F,y.indicator));else if(Z.position===\"past_end_of_stream\"&&Z.eof_action===\"error\")w.throw_error(x.error.permission(\"input\",\"past_end_of_stream\",F,y.indicator));else{var $;if(Z.position===\"end_of_stream\")$=\"end_of_file\",Z.position=\"past_end_of_stream\";else{if($=Z.stream.get_byte(Z.position),$===null){w.throw_error(x.error.representation(\"byte\",y.indicator));return}Z.position++}w.prepend([new Pe(b.goal.replace(new j(\"=\",[new Re($,!1),z])),b.substitution,b)])}},\"peek_byte/1\":function(w,b,y){var F=y.args[0];w.prepend([new Pe(b.goal.replace(new j(\",\",[new j(\"current_input\",[new De(\"S\")]),new j(\"peek_byte\",[new De(\"S\"),F])])),b.substitution,b)])},\"peek_byte/2\":function(w,b,y){var F=y.args[0],z=y.args[1],Z=x.type.is_stream(F)?F:w.get_stream_by_alias(F.id);if(x.type.is_variable(F))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(z)&&!x.type.is_byte(z))w.throw_error(x.error.type(\"in_byte\",char,y.indicator));else if(!x.type.is_stream(F)&&!x.type.is_atom(F))w.throw_error(x.error.domain(\"stream_or_alias\",F,y.indicator));else if(!x.type.is_stream(Z)||Z.stream===null)w.throw_error(x.error.existence(\"stream\",F,y.indicator));else if(Z.output)w.throw_error(x.error.permission(\"input\",\"stream\",F,y.indicator));else if(Z.type===\"text\")w.throw_error(x.error.permission(\"input\",\"text_stream\",F,y.indicator));else if(Z.position===\"past_end_of_stream\"&&Z.eof_action===\"error\")w.throw_error(x.error.permission(\"input\",\"past_end_of_stream\",F,y.indicator));else{var $;if(Z.position===\"end_of_stream\")$=\"end_of_file\",Z.position=\"past_end_of_stream\";else if($=Z.stream.get_byte(Z.position),$===null){w.throw_error(x.error.representation(\"byte\",y.indicator));return}w.prepend([new Pe(b.goal.replace(new j(\"=\",[new Re($,!1),z])),b.substitution,b)])}},\"put_byte/1\":function(w,b,y){var F=y.args[0];w.prepend([new Pe(b.goal.replace(new j(\",\",[new j(\"current_output\",[new De(\"S\")]),new j(\"put_byte\",[new De(\"S\"),F])])),b.substitution,b)])},\"put_byte/2\":function(w,b,y){var F=y.args[0],z=y.args[1],Z=x.type.is_stream(F)?F:w.get_stream_by_alias(F.id);x.type.is_variable(F)||x.type.is_variable(z)?w.throw_error(x.error.instantiation(y.indicator)):x.type.is_byte(z)?!x.type.is_variable(F)&&!x.type.is_stream(F)&&!x.type.is_atom(F)?w.throw_error(x.error.domain(\"stream_or_alias\",F,y.indicator)):!x.type.is_stream(Z)||Z.stream===null?w.throw_error(x.error.existence(\"stream\",F,y.indicator)):Z.input?w.throw_error(x.error.permission(\"output\",\"stream\",F,y.indicator)):Z.type===\"text\"?w.throw_error(x.error.permission(\"output\",\"text_stream\",F,y.indicator)):Z.stream.put_byte(z.value,Z.position)&&(typeof Z.position==\"number\"&&Z.position++,w.success(b)):w.throw_error(x.error.type(\"byte\",z,y.indicator))},\"read/1\":function(w,b,y){var F=y.args[0];w.prepend([new Pe(b.goal.replace(new j(\",\",[new j(\"current_input\",[new De(\"S\")]),new j(\"read_term\",[new De(\"S\"),F,new j(\"[]\",[])])])),b.substitution,b)])},\"read/2\":function(w,b,y){var F=y.args[0],z=y.args[1];w.prepend([new Pe(b.goal.replace(new j(\"read_term\",[F,z,new j(\"[]\",[])])),b.substitution,b)])},\"read_term/2\":function(w,b,y){var F=y.args[0],z=y.args[1];w.prepend([new Pe(b.goal.replace(new j(\",\",[new j(\"current_input\",[new De(\"S\")]),new j(\"read_term\",[new De(\"S\"),F,z])])),b.substitution,b)])},\"read_term/3\":function(w,b,y){var F=y.args[0],z=y.args[1],Z=y.args[2],$=x.type.is_stream(F)?F:w.get_stream_by_alias(F.id);if(x.type.is_variable(F)||x.type.is_variable(Z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_list(Z))w.throw_error(x.error.type(\"list\",Z,y.indicator));else if(!x.type.is_stream(F)&&!x.type.is_atom(F))w.throw_error(x.error.domain(\"stream_or_alias\",F,y.indicator));else if(!x.type.is_stream($)||$.stream===null)w.throw_error(x.error.existence(\"stream\",F,y.indicator));else if($.output)w.throw_error(x.error.permission(\"input\",\"stream\",F,y.indicator));else if($.type===\"binary\")w.throw_error(x.error.permission(\"input\",\"binary_stream\",F,y.indicator));else if($.position===\"past_end_of_stream\"&&$.eof_action===\"error\")w.throw_error(x.error.permission(\"input\",\"past_end_of_stream\",F,y.indicator));else{for(var oe={},xe=Z,Te;x.type.is_term(xe)&&xe.indicator===\"./2\";){if(Te=xe.args[0],x.type.is_variable(Te)){w.throw_error(x.error.instantiation(y.indicator));return}else if(!x.type.is_read_option(Te)){w.throw_error(x.error.domain(\"read_option\",Te,y.indicator));return}oe[Te.id]=Te.args[0],xe=xe.args[1]}if(xe.indicator!==\"[]/0\"){x.type.is_variable(xe)?w.throw_error(x.error.instantiation(y.indicator)):w.throw_error(x.error.type(\"list\",Z,y.indicator));return}else{for(var lt,It,qt,ir=\"\",Pt=[],gn=null;gn===null||gn.name!==\"atom\"||gn.value!==\".\"||qt.type===f&&x.flatten_error(new j(\"throw\",[qt.value])).found===\"token_not_found\";){if(lt=$.stream.get(1,$.position),lt===null){w.throw_error(x.error.representation(\"character\",y.indicator));return}if(lt===\"end_of_file\"||lt===\"past_end_of_file\"){qt?w.throw_error(x.error.syntax(Pt[qt.len-1],\". or expression expected\",!1)):w.throw_error(x.error.syntax(null,\"token not found\",!0));return}$.position++,ir+=lt,It=new U(w),It.new_text(ir),Pt=It.get_tokens(),gn=Pt!==null&&Pt.length>0?Pt[Pt.length-1]:null,Pt!==null&&(qt=W(w,Pt,0,w.__get_max_priority(),!1))}if(qt.type===p&&qt.len===Pt.length-1&&gn.value===\".\"){qt=qt.value.rename(w);var Pr=new j(\"=\",[z,qt]);if(oe.variables){var Ir=g(s(ye(qt.variables()),function(Nr){return new De(Nr)}));Pr=new j(\",\",[Pr,new j(\"=\",[oe.variables,Ir])])}if(oe.variable_names){var Ir=g(s(ye(qt.variables()),function(nn){var ai;for(ai in w.session.renamed_variables)if(w.session.renamed_variables.hasOwnProperty(ai)&&w.session.renamed_variables[ai]===nn)break;return new j(\"=\",[new j(ai,[]),new De(nn)])}));Pr=new j(\",\",[Pr,new j(\"=\",[oe.variable_names,Ir])])}if(oe.singletons){var Ir=g(s(new Ye(qt,null).singleton_variables(),function(nn){var ai;for(ai in w.session.renamed_variables)if(w.session.renamed_variables.hasOwnProperty(ai)&&w.session.renamed_variables[ai]===nn)break;return new j(\"=\",[new j(ai,[]),new De(nn)])}));Pr=new j(\",\",[Pr,new j(\"=\",[oe.singletons,Ir])])}w.prepend([new Pe(b.goal.replace(Pr),b.substitution,b)])}else qt.type===p?w.throw_error(x.error.syntax(Pt[qt.len],\"unexpected token\",!1)):w.throw_error(qt.value)}}},\"write/1\":function(w,b,y){var F=y.args[0];w.prepend([new Pe(b.goal.replace(new j(\",\",[new j(\"current_output\",[new De(\"S\")]),new j(\"write\",[new De(\"S\"),F])])),b.substitution,b)])},\"write/2\":function(w,b,y){var F=y.args[0],z=y.args[1];w.prepend([new Pe(b.goal.replace(new j(\"write_term\",[F,z,new j(\".\",[new j(\"quoted\",[new j(\"false\",[])]),new j(\".\",[new j(\"ignore_ops\",[new j(\"false\")]),new j(\".\",[new j(\"numbervars\",[new j(\"true\")]),new j(\"[]\",[])])])])])),b.substitution,b)])},\"writeq/1\":function(w,b,y){var F=y.args[0];w.prepend([new Pe(b.goal.replace(new j(\",\",[new j(\"current_output\",[new De(\"S\")]),new j(\"writeq\",[new De(\"S\"),F])])),b.substitution,b)])},\"writeq/2\":function(w,b,y){var F=y.args[0],z=y.args[1];w.prepend([new Pe(b.goal.replace(new j(\"write_term\",[F,z,new j(\".\",[new j(\"quoted\",[new j(\"true\",[])]),new j(\".\",[new j(\"ignore_ops\",[new j(\"false\")]),new j(\".\",[new j(\"numbervars\",[new j(\"true\")]),new j(\"[]\",[])])])])])),b.substitution,b)])},\"write_canonical/1\":function(w,b,y){var F=y.args[0];w.prepend([new Pe(b.goal.replace(new j(\",\",[new j(\"current_output\",[new De(\"S\")]),new j(\"write_canonical\",[new De(\"S\"),F])])),b.substitution,b)])},\"write_canonical/2\":function(w,b,y){var F=y.args[0],z=y.args[1];w.prepend([new Pe(b.goal.replace(new j(\"write_term\",[F,z,new j(\".\",[new j(\"quoted\",[new j(\"true\",[])]),new j(\".\",[new j(\"ignore_ops\",[new j(\"true\")]),new j(\".\",[new j(\"numbervars\",[new j(\"false\")]),new j(\"[]\",[])])])])])),b.substitution,b)])},\"write_term/2\":function(w,b,y){var F=y.args[0],z=y.args[1];w.prepend([new Pe(b.goal.replace(new j(\",\",[new j(\"current_output\",[new De(\"S\")]),new j(\"write_term\",[new De(\"S\"),F,z])])),b.substitution,b)])},\"write_term/3\":function(w,b,y){var F=y.args[0],z=y.args[1],Z=y.args[2],$=x.type.is_stream(F)?F:w.get_stream_by_alias(F.id);if(x.type.is_variable(F)||x.type.is_variable(Z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_list(Z))w.throw_error(x.error.type(\"list\",Z,y.indicator));else if(!x.type.is_stream(F)&&!x.type.is_atom(F))w.throw_error(x.error.domain(\"stream_or_alias\",F,y.indicator));else if(!x.type.is_stream($)||$.stream===null)w.throw_error(x.error.existence(\"stream\",F,y.indicator));else if($.input)w.throw_error(x.error.permission(\"output\",\"stream\",F,y.indicator));else if($.type===\"binary\")w.throw_error(x.error.permission(\"output\",\"binary_stream\",F,y.indicator));else if($.position===\"past_end_of_stream\"&&$.eof_action===\"error\")w.throw_error(x.error.permission(\"output\",\"past_end_of_stream\",F,y.indicator));else{for(var oe={},xe=Z,Te;x.type.is_term(xe)&&xe.indicator===\"./2\";){if(Te=xe.args[0],x.type.is_variable(Te)){w.throw_error(x.error.instantiation(y.indicator));return}else if(!x.type.is_write_option(Te)){w.throw_error(x.error.domain(\"write_option\",Te,y.indicator));return}oe[Te.id]=Te.args[0].id===\"true\",xe=xe.args[1]}if(xe.indicator!==\"[]/0\"){x.type.is_variable(xe)?w.throw_error(x.error.instantiation(y.indicator)):w.throw_error(x.error.type(\"list\",Z,y.indicator));return}else{oe.session=w.session;var lt=z.toString(oe);$.stream.put(lt,$.position),typeof $.position==\"number\"&&($.position+=lt.length),w.success(b)}}},\"halt/0\":function(w,b,y){w.points=[]},\"halt/1\":function(w,b,y){var F=y.args[0];x.type.is_variable(F)?w.throw_error(x.error.instantiation(y.indicator)):x.type.is_integer(F)?w.points=[]:w.throw_error(x.error.type(\"integer\",F,y.indicator))},\"current_prolog_flag/2\":function(w,b,y){var F=y.args[0],z=y.args[1];if(!x.type.is_variable(F)&&!x.type.is_atom(F))w.throw_error(x.error.type(\"atom\",F,y.indicator));else if(!x.type.is_variable(F)&&!x.type.is_flag(F))w.throw_error(x.error.domain(\"prolog_flag\",F,y.indicator));else{var Z=[];for(var $ in x.flag)if(x.flag.hasOwnProperty($)){var oe=new j(\",\",[new j(\"=\",[new j($),F]),new j(\"=\",[w.get_flag($),z])]);Z.push(new Pe(b.goal.replace(oe),b.substitution,b))}w.prepend(Z)}},\"set_prolog_flag/2\":function(w,b,y){var F=y.args[0],z=y.args[1];x.type.is_variable(F)||x.type.is_variable(z)?w.throw_error(x.error.instantiation(y.indicator)):x.type.is_atom(F)?x.type.is_flag(F)?x.type.is_value_flag(F,z)?x.type.is_modifiable_flag(F)?(w.session.flag[F.id]=z,w.success(b)):w.throw_error(x.error.permission(\"modify\",\"flag\",F)):w.throw_error(x.error.domain(\"flag_value\",new j(\"+\",[F,z]),y.indicator)):w.throw_error(x.error.domain(\"prolog_flag\",F,y.indicator)):w.throw_error(x.error.type(\"atom\",F,y.indicator))}},flag:{bounded:{allowed:[new j(\"true\"),new j(\"false\")],value:new j(\"true\"),changeable:!1},max_integer:{allowed:[new Re(Number.MAX_SAFE_INTEGER)],value:new Re(Number.MAX_SAFE_INTEGER),changeable:!1},min_integer:{allowed:[new Re(Number.MIN_SAFE_INTEGER)],value:new Re(Number.MIN_SAFE_INTEGER),changeable:!1},integer_rounding_function:{allowed:[new j(\"down\"),new j(\"toward_zero\")],value:new j(\"toward_zero\"),changeable:!1},char_conversion:{allowed:[new j(\"on\"),new j(\"off\")],value:new j(\"on\"),changeable:!0},debug:{allowed:[new j(\"on\"),new j(\"off\")],value:new j(\"off\"),changeable:!0},max_arity:{allowed:[new j(\"unbounded\")],value:new j(\"unbounded\"),changeable:!1},unknown:{allowed:[new j(\"error\"),new j(\"fail\"),new j(\"warning\")],value:new j(\"error\"),changeable:!0},double_quotes:{allowed:[new j(\"chars\"),new j(\"codes\"),new j(\"atom\")],value:new j(\"codes\"),changeable:!0},occurs_check:{allowed:[new j(\"false\"),new j(\"true\")],value:new j(\"false\"),changeable:!0},dialect:{allowed:[new j(\"tau\")],value:new j(\"tau\"),changeable:!1},version_data:{allowed:[new j(\"tau\",[new Re(t.major,!1),new Re(t.minor,!1),new Re(t.patch,!1),new j(t.status)])],value:new j(\"tau\",[new Re(t.major,!1),new Re(t.minor,!1),new Re(t.patch,!1),new j(t.status)]),changeable:!1},nodejs:{allowed:[new j(\"yes\"),new j(\"no\")],value:new j(typeof tc<\"u\"&&tc.exports?\"yes\":\"no\"),changeable:!1}},unify:function(w,b,y){y=y===void 0?!1:y;for(var F=[{left:w,right:b}],z={};F.length!==0;){var Z=F.pop();if(w=Z.left,b=Z.right,x.type.is_term(w)&&x.type.is_term(b)){if(w.indicator!==b.indicator)return null;for(var $=0;$<w.args.length;$++)F.push({left:w.args[$],right:b.args[$]})}else if(x.type.is_number(w)&&x.type.is_number(b)){if(w.value!==b.value||w.is_float!==b.is_float)return null}else if(x.type.is_variable(w)){if(x.type.is_variable(b)&&w.id===b.id)continue;if(y===!0&&b.variables().indexOf(w.id)!==-1)return null;if(w.id!==\"_\"){var oe=new Ne;oe.add(w.id,b);for(var $=0;$<F.length;$++)F[$].left=F[$].left.apply(oe),F[$].right=F[$].right.apply(oe);for(var $ in z)z[$]=z[$].apply(oe);z[w.id]=b}}else if(x.type.is_variable(b))F.push({left:b,right:w});else if(w.unify!==void 0){if(!w.unify(b))return null}else return null}return new Ne(z)},compare:function(w,b){var y=x.type.compare(w,b);return y!==0?y:w.compare(b)},arithmetic_compare:function(w,b,y){var F=b.interpret(w);if(x.type.is_number(F)){var z=y.interpret(w);return x.type.is_number(z)?F.value<z.value?-1:F.value>z.value?1:0:z}else return F},operate:function(w,b){if(x.type.is_operator(b)){for(var y=x.type.is_operator(b),F=[],z,Z=!1,$=0;$<b.args.length;$++){if(z=b.args[$].interpret(w),x.type.is_number(z)){if(y.type_args!==null&&z.is_float!==y.type_args)return x.error.type(y.type_args?\"float\":\"integer\",z,w.__call_indicator);F.push(z.value)}else return z;Z=Z||z.is_float}return F.push(w),z=x.arithmetic.evaluation[b.indicator].fn.apply(this,F),Z=y.type_result===null?Z:y.type_result,x.type.is_term(z)?z:z===Number.POSITIVE_INFINITY||z===Number.NEGATIVE_INFINITY?x.error.evaluation(\"overflow\",w.__call_indicator):Z===!1&&w.get_flag(\"bounded\").id===\"true\"&&(z>w.get_flag(\"max_integer\").value||z<w.get_flag(\"min_integer\").value)?x.error.evaluation(\"int_overflow\",w.__call_indicator):new Re(z,Z)}else return x.error.type(\"evaluable\",b.indicator,w.__call_indicator)},error:{existence:function(w,b,y){return typeof b==\"string\"&&(b=X(b)),new j(\"error\",[new j(\"existence_error\",[new j(w),b]),X(y)])},type:function(w,b,y){return new j(\"error\",[new j(\"type_error\",[new j(w),b]),X(y)])},instantiation:function(w){return new j(\"error\",[new j(\"instantiation_error\"),X(w)])},domain:function(w,b,y){return new j(\"error\",[new j(\"domain_error\",[new j(w),b]),X(y)])},representation:function(w,b){return new j(\"error\",[new j(\"representation_error\",[new j(w)]),X(b)])},permission:function(w,b,y,F){return new j(\"error\",[new j(\"permission_error\",[new j(w),new j(b),y]),X(F)])},evaluation:function(w,b){return new j(\"error\",[new j(\"evaluation_error\",[new j(w)]),X(b)])},syntax:function(w,b,y){w=w||{value:\"\",line:0,column:0,matches:[\"\"],start:0};var F=y&&w.matches.length>0?w.start+w.matches[0].length:w.start,z=y?new j(\"token_not_found\"):new j(\"found\",[new j(w.value.toString())]),Z=new j(\".\",[new j(\"line\",[new Re(w.line+1)]),new j(\".\",[new j(\"column\",[new Re(F+1)]),new j(\".\",[z,new j(\"[]\",[])])])]);return new j(\"error\",[new j(\"syntax_error\",[new j(b)]),Z])},syntax_by_predicate:function(w,b){return new j(\"error\",[new j(\"syntax_error\",[new j(w)]),X(b)])}},warning:{singleton:function(w,b,y){for(var F=new j(\"[]\"),z=w.length-1;z>=0;z--)F=new j(\".\",[new De(w[z]),F]);return new j(\"warning\",[new j(\"singleton_variables\",[F,X(b)]),new j(\".\",[new j(\"line\",[new Re(y,!1)]),new j(\"[]\")])])},failed_goal:function(w,b){return new j(\"warning\",[new j(\"failed_goal\",[w]),new j(\".\",[new j(\"line\",[new Re(b,!1)]),new j(\"[]\")])])}},format_variable:function(w){return\"_\"+w},format_answer:function(w,b,F){b instanceof ke&&(b=b.thread);var F=F||{};if(F.session=b?b.session:void 0,x.type.is_error(w))return\"uncaught exception: \"+w.args[0].toString();if(w===!1)return\"false.\";if(w===null)return\"limit exceeded ;\";var z=0,Z=\"\";if(x.type.is_substitution(w)){var $=w.domain(!0);w=w.filter(function(Te,lt){return!x.type.is_variable(lt)||$.indexOf(lt.id)!==-1&&Te!==lt.id})}for(var oe in w.links)w.links.hasOwnProperty(oe)&&(z++,Z!==\"\"&&(Z+=\", \"),Z+=oe.toString(F)+\" = \"+w.links[oe].toString(F));var xe=typeof b>\"u\"||b.points.length>0?\" ;\":\".\";return z===0?\"true\"+xe:Z+xe},flatten_error:function(w){if(!x.type.is_error(w))return null;w=w.args[0];var b={};return b.type=w.args[0].id,b.thrown=b.type===\"syntax_error\"?null:w.args[1].id,b.expected=null,b.found=null,b.representation=null,b.existence=null,b.existence_type=null,b.line=null,b.column=null,b.permission_operation=null,b.permission_type=null,b.evaluation_type=null,b.type===\"type_error\"||b.type===\"domain_error\"?(b.expected=w.args[0].args[0].id,b.found=w.args[0].args[1].toString()):b.type===\"syntax_error\"?w.args[1].indicator===\"./2\"?(b.expected=w.args[0].args[0].id,b.found=w.args[1].args[1].args[1].args[0],b.found=b.found.id===\"token_not_found\"?b.found.id:b.found.args[0].id,b.line=w.args[1].args[0].args[0].value,b.column=w.args[1].args[1].args[0].args[0].value):b.thrown=w.args[1].id:b.type===\"permission_error\"?(b.found=w.args[0].args[2].toString(),b.permission_operation=w.args[0].args[0].id,b.permission_type=w.args[0].args[1].id):b.type===\"evaluation_error\"?b.evaluation_type=w.args[0].args[0].id:b.type===\"representation_error\"?b.representation=w.args[0].args[0].id:b.type===\"existence_error\"&&(b.existence=w.args[0].args[1].toString(),b.existence_type=w.args[0].args[0].id),b},create:function(w){return new x.type.Session(w)}};typeof tc<\"u\"?tc.exports=x:window.pl=x})()});function nve(t,e,r){t.prepend(r.map(s=>new gl.default.type.State(e.goal.replace(s),e.substitution,e)))}function f9(t){let e=sve.get(t.session);if(e==null)throw new Error(\"Assertion failed: A project should have been registered for the active session\");return e}function ove(t,e){sve.set(t,e),t.consult(`:- use_module(library(${OSt.id})).`)}var A9,gl,ive,V0,FSt,NSt,sve,OSt,ave=Ct(()=>{Ve();A9=et(aS()),gl=et(u9()),ive=et(Ie(\"vm\")),{is_atom:V0,is_variable:FSt,is_instantiated_list:NSt}=gl.default.type;sve=new WeakMap;OSt=new gl.default.type.Module(\"constraints\",{\"project_workspaces_by_descriptor/3\":(t,e,r)=>{let[s,a,n]=r.args;if(!V0(s)||!V0(a)){t.throw_error(gl.default.error.instantiation(r.indicator));return}let c=q.parseIdent(s.id),f=q.makeDescriptor(c,a.id),h=f9(t).tryWorkspaceByDescriptor(f);FSt(n)&&h!==null&&nve(t,e,[new gl.default.type.Term(\"=\",[n,new gl.default.type.Term(String(h.relativeCwd))])]),V0(n)&&h!==null&&h.relativeCwd===n.id&&t.success(e)},\"workspace_field/3\":(t,e,r)=>{let[s,a,n]=r.args;if(!V0(s)||!V0(a)){t.throw_error(gl.default.error.instantiation(r.indicator));return}let f=f9(t).tryWorkspaceByCwd(s.id);if(f==null)return;let p=(0,A9.default)(f.manifest.raw,a.id);typeof p>\"u\"||nve(t,e,[new gl.default.type.Term(\"=\",[n,new gl.default.type.Term(typeof p==\"object\"?JSON.stringify(p):p)])])},\"workspace_field_test/3\":(t,e,r)=>{let[s,a,n]=r.args;t.prepend([new gl.default.type.State(e.goal.replace(new gl.default.type.Term(\"workspace_field_test\",[s,a,n,new gl.default.type.Term(\"[]\",[])])),e.substitution,e)])},\"workspace_field_test/4\":(t,e,r)=>{let[s,a,n,c]=r.args;if(!V0(s)||!V0(a)||!V0(n)||!NSt(c)){t.throw_error(gl.default.error.instantiation(r.indicator));return}let p=f9(t).tryWorkspaceByCwd(s.id);if(p==null)return;let h=(0,A9.default)(p.manifest.raw,a.id);if(typeof h>\"u\")return;let E={$$:h};for(let[S,P]of c.toJavaScript().entries())E[`$${S}`]=P;ive.default.runInNewContext(n.id,E)&&t.success(e)}},[\"project_workspaces_by_descriptor/3\",\"workspace_field/3\",\"workspace_field_test/3\",\"workspace_field_test/4\"])});var yS={};Vt(yS,{Constraints:()=>h9,DependencyType:()=>fve});function yo(t){if(t instanceof JC.default.type.Num)return t.value;if(t instanceof JC.default.type.Term)switch(t.indicator){case\"throw/1\":return yo(t.args[0]);case\"error/1\":return yo(t.args[0]);case\"error/2\":if(t.args[0]instanceof JC.default.type.Term&&t.args[0].indicator===\"syntax_error/1\")return Object.assign(yo(t.args[0]),...yo(t.args[1]));{let e=yo(t.args[0]);return e.message+=` (in ${yo(t.args[1])})`,e}case\"syntax_error/1\":return new Yt(43,`Syntax error: ${yo(t.args[0])}`);case\"existence_error/2\":return new Yt(44,`Existence error: ${yo(t.args[0])} ${yo(t.args[1])} not found`);case\"instantiation_error/0\":return new Yt(75,\"Instantiation error: an argument is variable when an instantiated argument was expected\");case\"line/1\":return{line:yo(t.args[0])};case\"column/1\":return{column:yo(t.args[0])};case\"found/1\":return{found:yo(t.args[0])};case\"./2\":return[yo(t.args[0])].concat(yo(t.args[1]));case\"//2\":return`${yo(t.args[0])}/${yo(t.args[1])}`;default:return t.id}throw`couldn't pretty print because of unsupported node ${t}`}function cve(t){let e;try{e=yo(t)}catch(r){throw typeof r==\"string\"?new Yt(42,`Unknown error: ${t} (note: ${r})`):r}return typeof e.line<\"u\"&&typeof e.column<\"u\"&&(e.message+=` at line ${e.line}, column ${e.column}`),e}function bm(t){return t.id===\"null\"?null:`${t.toJavaScript()}`}function LSt(t){if(t.id===\"null\")return null;{let e=t.toJavaScript();if(typeof e!=\"string\")return JSON.stringify(e);try{return JSON.stringify(JSON.parse(e))}catch{return JSON.stringify(e)}}}function K0(t){return typeof t==\"string\"?`'${t}'`:\"[]\"}var uve,JC,fve,lve,p9,h9,ES=Ct(()=>{Ve();Ve();bt();uve=et(HBe()),JC=et(u9());gS();ave();(0,uve.default)(JC.default);fve=(s=>(s.Dependencies=\"dependencies\",s.DevDependencies=\"devDependencies\",s.PeerDependencies=\"peerDependencies\",s))(fve||{}),lve=[\"dependencies\",\"devDependencies\",\"peerDependencies\"];p9=class{constructor(e,r){let s=1e3*e.workspaces.length;this.session=JC.default.create(s),ove(this.session,e),this.session.consult(\":- use_module(library(lists)).\"),this.session.consult(r)}fetchNextAnswer(){return new Promise(e=>{this.session.answer(r=>{e(r)})})}async*makeQuery(e){let r=this.session.query(e);if(r!==!0)throw cve(r);for(;;){let s=await this.fetchNextAnswer();if(s===null)throw new Yt(79,\"Resolution limit exceeded\");if(!s)break;if(s.id===\"throw\")throw cve(s);yield s}}};h9=class t{constructor(e){this.source=\"\";this.project=e;let r=e.configuration.get(\"constraintsPath\");le.existsSync(r)&&(this.source=le.readFileSync(r,\"utf8\"))}static async find(e){return new t(e)}getProjectDatabase(){let e=\"\";for(let r of lve)e+=`dependency_type(${r}).\n`;for(let r of this.project.workspacesByCwd.values()){let s=r.relativeCwd;e+=`workspace(${K0(s)}).\n`,e+=`workspace_ident(${K0(s)}, ${K0(q.stringifyIdent(r.anchoredLocator))}).\n`,e+=`workspace_version(${K0(s)}, ${K0(r.manifest.version)}).\n`;for(let a of lve)for(let n of r.manifest[a].values())e+=`workspace_has_dependency(${K0(s)}, ${K0(q.stringifyIdent(n))}, ${K0(n.range)}, ${a}).\n`}return e+=`workspace(_) :- false.\n`,e+=`workspace_ident(_, _) :- false.\n`,e+=`workspace_version(_, _) :- false.\n`,e+=`workspace_has_dependency(_, _, _, _) :- false.\n`,e}getDeclarations(){let e=\"\";return e+=`gen_enforced_dependency(_, _, _, _) :- false.\n`,e+=`gen_enforced_field(_, _, _) :- false.\n`,e}get fullSource(){return`${this.getProjectDatabase()}\n${this.source}\n${this.getDeclarations()}`}createSession(){return new p9(this.project,this.fullSource)}async processClassic(){let e=this.createSession();return{enforcedDependencies:await this.genEnforcedDependencies(e),enforcedFields:await this.genEnforcedFields(e)}}async process(){let{enforcedDependencies:e,enforcedFields:r}=await this.processClassic(),s=new Map;for(let{workspace:a,dependencyIdent:n,dependencyRange:c,dependencyType:f}of e){let p=hS([f,q.stringifyIdent(n)]),h=je.getMapWithDefault(s,a.cwd);je.getMapWithDefault(h,p).set(c??void 0,new Set)}for(let{workspace:a,fieldPath:n,fieldValue:c}of r){let f=hS(n),p=je.getMapWithDefault(s,a.cwd);je.getMapWithDefault(p,f).set(JSON.parse(c)??void 0,new Set)}return{manifestUpdates:s,reportedErrors:new Map}}async genEnforcedDependencies(e){let r=[];for await(let s of e.makeQuery(\"workspace(WorkspaceCwd), dependency_type(DependencyType), gen_enforced_dependency(WorkspaceCwd, DependencyIdent, DependencyRange, DependencyType).\")){let a=K.resolve(this.project.cwd,bm(s.links.WorkspaceCwd)),n=bm(s.links.DependencyIdent),c=bm(s.links.DependencyRange),f=bm(s.links.DependencyType);if(a===null||n===null)throw new Error(\"Invalid rule\");let p=this.project.getWorkspaceByCwd(a),h=q.parseIdent(n);r.push({workspace:p,dependencyIdent:h,dependencyRange:c,dependencyType:f})}return je.sortMap(r,[({dependencyRange:s})=>s!==null?\"0\":\"1\",({workspace:s})=>q.stringifyIdent(s.anchoredLocator),({dependencyIdent:s})=>q.stringifyIdent(s)])}async genEnforcedFields(e){let r=[];for await(let s of e.makeQuery(\"workspace(WorkspaceCwd), gen_enforced_field(WorkspaceCwd, FieldPath, FieldValue).\")){let a=K.resolve(this.project.cwd,bm(s.links.WorkspaceCwd)),n=bm(s.links.FieldPath),c=LSt(s.links.FieldValue);if(a===null||n===null)throw new Error(\"Invalid rule\");let f=this.project.getWorkspaceByCwd(a);r.push({workspace:f,fieldPath:n,fieldValue:c})}return je.sortMap(r,[({workspace:s})=>q.stringifyIdent(s.anchoredLocator),({fieldPath:s})=>s])}async*query(e){let r=this.createSession();for await(let s of r.makeQuery(e)){let a={};for(let[n,c]of Object.entries(s.links))n!==\"_\"&&(a[n]=bm(c));yield a}}}});var Ive=L(pF=>{\"use strict\";Object.defineProperty(pF,\"__esModule\",{value:!0});function NS(t){let e=[...t.caches],r=e.shift();return r===void 0?Eve():{get(s,a,n={miss:()=>Promise.resolve()}){return r.get(s,a,n).catch(()=>NS({caches:e}).get(s,a,n))},set(s,a){return r.set(s,a).catch(()=>NS({caches:e}).set(s,a))},delete(s){return r.delete(s).catch(()=>NS({caches:e}).delete(s))},clear(){return r.clear().catch(()=>NS({caches:e}).clear())}}}function Eve(){return{get(t,e,r={miss:()=>Promise.resolve()}){return e().then(a=>Promise.all([a,r.miss(a)])).then(([a])=>a)},set(t,e){return Promise.resolve(e)},delete(t){return Promise.resolve()},clear(){return Promise.resolve()}}}pF.createFallbackableCache=NS;pF.createNullCache=Eve});var wve=L((Apr,Cve)=>{Cve.exports=Ive()});var Bve=L(P9=>{\"use strict\";Object.defineProperty(P9,\"__esModule\",{value:!0});function rDt(t={serializable:!0}){let e={};return{get(r,s,a={miss:()=>Promise.resolve()}){let n=JSON.stringify(r);if(n in e)return Promise.resolve(t.serializable?JSON.parse(e[n]):e[n]);let c=s(),f=a&&a.miss||(()=>Promise.resolve());return c.then(p=>f(p)).then(()=>c)},set(r,s){return e[JSON.stringify(r)]=t.serializable?JSON.stringify(s):s,Promise.resolve(s)},delete(r){return delete e[JSON.stringify(r)],Promise.resolve()},clear(){return e={},Promise.resolve()}}}P9.createInMemoryCache=rDt});var Sve=L((hpr,vve)=>{vve.exports=Bve()});var bve=L(ef=>{\"use strict\";Object.defineProperty(ef,\"__esModule\",{value:!0});function nDt(t,e,r){let s={\"x-algolia-api-key\":r,\"x-algolia-application-id\":e};return{headers(){return t===x9.WithinHeaders?s:{}},queryParameters(){return t===x9.WithinQueryParameters?s:{}}}}function iDt(t){let e=0,r=()=>(e++,new Promise(s=>{setTimeout(()=>{s(t(r))},Math.min(100*e,1e3))}));return t(r)}function Dve(t,e=(r,s)=>Promise.resolve()){return Object.assign(t,{wait(r){return Dve(t.then(s=>Promise.all([e(s,r),s])).then(s=>s[1]))}})}function sDt(t){let e=t.length-1;for(e;e>0;e--){let r=Math.floor(Math.random()*(e+1)),s=t[e];t[e]=t[r],t[r]=s}return t}function oDt(t,e){return e&&Object.keys(e).forEach(r=>{t[r]=e[r](t)}),t}function aDt(t,...e){let r=0;return t.replace(/%s/g,()=>encodeURIComponent(e[r++]))}var lDt=\"4.22.1\",cDt=t=>()=>t.transporter.requester.destroy(),x9={WithinQueryParameters:0,WithinHeaders:1};ef.AuthMode=x9;ef.addMethods=oDt;ef.createAuth=nDt;ef.createRetryablePromise=iDt;ef.createWaitablePromise=Dve;ef.destroy=cDt;ef.encode=aDt;ef.shuffle=sDt;ef.version=lDt});var OS=L((dpr,Pve)=>{Pve.exports=bve()});var xve=L(k9=>{\"use strict\";Object.defineProperty(k9,\"__esModule\",{value:!0});var uDt={Delete:\"DELETE\",Get:\"GET\",Post:\"POST\",Put:\"PUT\"};k9.MethodEnum=uDt});var LS=L((ypr,kve)=>{kve.exports=xve()});var Wve=L(Vi=>{\"use strict\";Object.defineProperty(Vi,\"__esModule\",{value:!0});var Tve=LS();function Q9(t,e){let r=t||{},s=r.data||{};return Object.keys(r).forEach(a=>{[\"timeout\",\"headers\",\"queryParameters\",\"data\",\"cacheable\"].indexOf(a)===-1&&(s[a]=r[a])}),{data:Object.entries(s).length>0?s:void 0,timeout:r.timeout||e,headers:r.headers||{},queryParameters:r.queryParameters||{},cacheable:r.cacheable}}var MS={Read:1,Write:2,Any:3},sw={Up:1,Down:2,Timeouted:3},Rve=2*60*1e3;function R9(t,e=sw.Up){return{...t,status:e,lastUpdate:Date.now()}}function Fve(t){return t.status===sw.Up||Date.now()-t.lastUpdate>Rve}function Nve(t){return t.status===sw.Timeouted&&Date.now()-t.lastUpdate<=Rve}function F9(t){return typeof t==\"string\"?{protocol:\"https\",url:t,accept:MS.Any}:{protocol:t.protocol||\"https\",url:t.url,accept:t.accept||MS.Any}}function fDt(t,e){return Promise.all(e.map(r=>t.get(r,()=>Promise.resolve(R9(r))))).then(r=>{let s=r.filter(f=>Fve(f)),a=r.filter(f=>Nve(f)),n=[...s,...a],c=n.length>0?n.map(f=>F9(f)):e;return{getTimeout(f,p){return(a.length===0&&f===0?1:a.length+3+f)*p},statelessHosts:c}})}var ADt=({isTimedOut:t,status:e})=>!t&&~~e===0,pDt=t=>{let e=t.status;return t.isTimedOut||ADt(t)||~~(e/100)!==2&&~~(e/100)!==4},hDt=({status:t})=>~~(t/100)===2,gDt=(t,e)=>pDt(t)?e.onRetry(t):hDt(t)?e.onSuccess(t):e.onFail(t);function Qve(t,e,r,s){let a=[],n=Uve(r,s),c=Hve(t,s),f=r.method,p=r.method!==Tve.MethodEnum.Get?{}:{...r.data,...s.data},h={\"x-algolia-agent\":t.userAgent.value,...t.queryParameters,...p,...s.queryParameters},E=0,C=(S,P)=>{let I=S.pop();if(I===void 0)throw Gve(T9(a));let R={data:n,headers:c,method:f,url:Mve(I,r.path,h),connectTimeout:P(E,t.timeouts.connect),responseTimeout:P(E,s.timeout)},N=W=>{let te={request:R,response:W,host:I,triesLeft:S.length};return a.push(te),te},U={onSuccess:W=>Ove(W),onRetry(W){let te=N(W);return W.isTimedOut&&E++,Promise.all([t.logger.info(\"Retryable failure\",N9(te)),t.hostsCache.set(I,R9(I,W.isTimedOut?sw.Timeouted:sw.Down))]).then(()=>C(S,P))},onFail(W){throw N(W),Lve(W,T9(a))}};return t.requester.send(R).then(W=>gDt(W,U))};return fDt(t.hostsCache,e).then(S=>C([...S.statelessHosts].reverse(),S.getTimeout))}function dDt(t){let{hostsCache:e,logger:r,requester:s,requestsCache:a,responsesCache:n,timeouts:c,userAgent:f,hosts:p,queryParameters:h,headers:E}=t,C={hostsCache:e,logger:r,requester:s,requestsCache:a,responsesCache:n,timeouts:c,userAgent:f,headers:E,queryParameters:h,hosts:p.map(S=>F9(S)),read(S,P){let I=Q9(P,C.timeouts.read),R=()=>Qve(C,C.hosts.filter(W=>(W.accept&MS.Read)!==0),S,I);if((I.cacheable!==void 0?I.cacheable:S.cacheable)!==!0)return R();let U={request:S,mappedRequestOptions:I,transporter:{queryParameters:C.queryParameters,headers:C.headers}};return C.responsesCache.get(U,()=>C.requestsCache.get(U,()=>C.requestsCache.set(U,R()).then(W=>Promise.all([C.requestsCache.delete(U),W]),W=>Promise.all([C.requestsCache.delete(U),Promise.reject(W)])).then(([W,te])=>te)),{miss:W=>C.responsesCache.set(U,W)})},write(S,P){return Qve(C,C.hosts.filter(I=>(I.accept&MS.Write)!==0),S,Q9(P,C.timeouts.write))}};return C}function mDt(t){let e={value:`Algolia for JavaScript (${t})`,add(r){let s=`; ${r.segment}${r.version!==void 0?` (${r.version})`:\"\"}`;return e.value.indexOf(s)===-1&&(e.value=`${e.value}${s}`),e}};return e}function Ove(t){try{return JSON.parse(t.content)}catch(e){throw qve(e.message,t)}}function Lve({content:t,status:e},r){let s=t;try{s=JSON.parse(t).message}catch{}return jve(s,e,r)}function yDt(t,...e){let r=0;return t.replace(/%s/g,()=>encodeURIComponent(e[r++]))}function Mve(t,e,r){let s=_ve(r),a=`${t.protocol}://${t.url}/${e.charAt(0)===\"/\"?e.substr(1):e}`;return s.length&&(a+=`?${s}`),a}function _ve(t){let e=r=>Object.prototype.toString.call(r)===\"[object Object]\"||Object.prototype.toString.call(r)===\"[object Array]\";return Object.keys(t).map(r=>yDt(\"%s=%s\",r,e(t[r])?JSON.stringify(t[r]):t[r])).join(\"&\")}function Uve(t,e){if(t.method===Tve.MethodEnum.Get||t.data===void 0&&e.data===void 0)return;let r=Array.isArray(t.data)?t.data:{...t.data,...e.data};return JSON.stringify(r)}function Hve(t,e){let r={...t.headers,...e.headers},s={};return Object.keys(r).forEach(a=>{let n=r[a];s[a.toLowerCase()]=n}),s}function T9(t){return t.map(e=>N9(e))}function N9(t){let e=t.request.headers[\"x-algolia-api-key\"]?{\"x-algolia-api-key\":\"*****\"}:{};return{...t,request:{...t.request,headers:{...t.request.headers,...e}}}}function jve(t,e,r){return{name:\"ApiError\",message:t,status:e,transporterStackTrace:r}}function qve(t,e){return{name:\"DeserializationError\",message:t,response:e}}function Gve(t){return{name:\"RetryError\",message:\"Unreachable hosts - your application id may be incorrect. If the error persists, contact support@algolia.com.\",transporterStackTrace:t}}Vi.CallEnum=MS;Vi.HostStatusEnum=sw;Vi.createApiError=jve;Vi.createDeserializationError=qve;Vi.createMappedRequestOptions=Q9;Vi.createRetryError=Gve;Vi.createStatefulHost=R9;Vi.createStatelessHost=F9;Vi.createTransporter=dDt;Vi.createUserAgent=mDt;Vi.deserializeFailure=Lve;Vi.deserializeSuccess=Ove;Vi.isStatefulHostTimeouted=Nve;Vi.isStatefulHostUp=Fve;Vi.serializeData=Uve;Vi.serializeHeaders=Hve;Vi.serializeQueryParameters=_ve;Vi.serializeUrl=Mve;Vi.stackFrameWithoutCredentials=N9;Vi.stackTraceWithoutCredentials=T9});var _S=L((Ipr,Yve)=>{Yve.exports=Wve()});var Vve=L(z0=>{\"use strict\";Object.defineProperty(z0,\"__esModule\",{value:!0});var ow=OS(),EDt=_S(),US=LS(),IDt=t=>{let e=t.region||\"us\",r=ow.createAuth(ow.AuthMode.WithinHeaders,t.appId,t.apiKey),s=EDt.createTransporter({hosts:[{url:`analytics.${e}.algolia.com`}],...t,headers:{...r.headers(),\"content-type\":\"application/json\",...t.headers},queryParameters:{...r.queryParameters(),...t.queryParameters}}),a=t.appId;return ow.addMethods({appId:a,transporter:s},t.methods)},CDt=t=>(e,r)=>t.transporter.write({method:US.MethodEnum.Post,path:\"2/abtests\",data:e},r),wDt=t=>(e,r)=>t.transporter.write({method:US.MethodEnum.Delete,path:ow.encode(\"2/abtests/%s\",e)},r),BDt=t=>(e,r)=>t.transporter.read({method:US.MethodEnum.Get,path:ow.encode(\"2/abtests/%s\",e)},r),vDt=t=>e=>t.transporter.read({method:US.MethodEnum.Get,path:\"2/abtests\"},e),SDt=t=>(e,r)=>t.transporter.write({method:US.MethodEnum.Post,path:ow.encode(\"2/abtests/%s/stop\",e)},r);z0.addABTest=CDt;z0.createAnalyticsClient=IDt;z0.deleteABTest=wDt;z0.getABTest=BDt;z0.getABTests=vDt;z0.stopABTest=SDt});var Jve=L((wpr,Kve)=>{Kve.exports=Vve()});var Zve=L(HS=>{\"use strict\";Object.defineProperty(HS,\"__esModule\",{value:!0});var O9=OS(),DDt=_S(),zve=LS(),bDt=t=>{let e=t.region||\"us\",r=O9.createAuth(O9.AuthMode.WithinHeaders,t.appId,t.apiKey),s=DDt.createTransporter({hosts:[{url:`personalization.${e}.algolia.com`}],...t,headers:{...r.headers(),\"content-type\":\"application/json\",...t.headers},queryParameters:{...r.queryParameters(),...t.queryParameters}});return O9.addMethods({appId:t.appId,transporter:s},t.methods)},PDt=t=>e=>t.transporter.read({method:zve.MethodEnum.Get,path:\"1/strategies/personalization\"},e),xDt=t=>(e,r)=>t.transporter.write({method:zve.MethodEnum.Post,path:\"1/strategies/personalization\",data:e},r);HS.createPersonalizationClient=bDt;HS.getPersonalizationStrategy=PDt;HS.setPersonalizationStrategy=xDt});var $ve=L((vpr,Xve)=>{Xve.exports=Zve()});var pSe=L(Ft=>{\"use strict\";Object.defineProperty(Ft,\"__esModule\",{value:!0});var Kt=OS(),dl=_S(),br=LS(),kDt=Ie(\"crypto\");function hF(t){let e=r=>t.request(r).then(s=>{if(t.batch!==void 0&&t.batch(s.hits),!t.shouldStop(s))return s.cursor?e({cursor:s.cursor}):e({page:(r.page||0)+1})});return e({})}var QDt=t=>{let e=t.appId,r=Kt.createAuth(t.authMode!==void 0?t.authMode:Kt.AuthMode.WithinHeaders,e,t.apiKey),s=dl.createTransporter({hosts:[{url:`${e}-dsn.algolia.net`,accept:dl.CallEnum.Read},{url:`${e}.algolia.net`,accept:dl.CallEnum.Write}].concat(Kt.shuffle([{url:`${e}-1.algolianet.com`},{url:`${e}-2.algolianet.com`},{url:`${e}-3.algolianet.com`}])),...t,headers:{...r.headers(),\"content-type\":\"application/x-www-form-urlencoded\",...t.headers},queryParameters:{...r.queryParameters(),...t.queryParameters}}),a={transporter:s,appId:e,addAlgoliaAgent(n,c){s.userAgent.add({segment:n,version:c})},clearCache(){return Promise.all([s.requestsCache.clear(),s.responsesCache.clear()]).then(()=>{})}};return Kt.addMethods(a,t.methods)};function eSe(){return{name:\"MissingObjectIDError\",message:\"All objects must have an unique objectID (like a primary key) to be valid. Algolia is also able to generate objectIDs automatically but *it's not recommended*. To do it, use the `{'autoGenerateObjectIDIfNotExist': true}` option.\"}}function tSe(){return{name:\"ObjectNotFoundError\",message:\"Object not found.\"}}function rSe(){return{name:\"ValidUntilNotFoundError\",message:\"ValidUntil not found in given secured api key.\"}}var TDt=t=>(e,r)=>{let{queryParameters:s,...a}=r||{},n={acl:e,...s!==void 0?{queryParameters:s}:{}},c=(f,p)=>Kt.createRetryablePromise(h=>jS(t)(f.key,p).catch(E=>{if(E.status!==404)throw E;return h()}));return Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:\"1/keys\",data:n},a),c)},RDt=t=>(e,r,s)=>{let a=dl.createMappedRequestOptions(s);return a.queryParameters[\"X-Algolia-User-ID\"]=e,t.transporter.write({method:br.MethodEnum.Post,path:\"1/clusters/mapping\",data:{cluster:r}},a)},FDt=t=>(e,r,s)=>t.transporter.write({method:br.MethodEnum.Post,path:\"1/clusters/mapping/batch\",data:{users:e,cluster:r}},s),NDt=t=>(e,r)=>Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Kt.encode(\"/1/dictionaries/%s/batch\",e),data:{clearExistingDictionaryEntries:!0,requests:{action:\"addEntry\",body:[]}}},r),(s,a)=>aw(t)(s.taskID,a)),gF=t=>(e,r,s)=>{let a=(n,c)=>qS(t)(e,{methods:{waitTask:ds}}).waitTask(n.taskID,c);return Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Kt.encode(\"1/indexes/%s/operation\",e),data:{operation:\"copy\",destination:r}},s),a)},ODt=t=>(e,r,s)=>gF(t)(e,r,{...s,scope:[mF.Rules]}),LDt=t=>(e,r,s)=>gF(t)(e,r,{...s,scope:[mF.Settings]}),MDt=t=>(e,r,s)=>gF(t)(e,r,{...s,scope:[mF.Synonyms]}),_Dt=t=>(e,r)=>e.method===br.MethodEnum.Get?t.transporter.read(e,r):t.transporter.write(e,r),UDt=t=>(e,r)=>{let s=(a,n)=>Kt.createRetryablePromise(c=>jS(t)(e,n).then(c).catch(f=>{if(f.status!==404)throw f}));return Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Delete,path:Kt.encode(\"1/keys/%s\",e)},r),s)},HDt=t=>(e,r,s)=>{let a=r.map(n=>({action:\"deleteEntry\",body:{objectID:n}}));return Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Kt.encode(\"/1/dictionaries/%s/batch\",e),data:{clearExistingDictionaryEntries:!1,requests:a}},s),(n,c)=>aw(t)(n.taskID,c))},jDt=()=>(t,e)=>{let r=dl.serializeQueryParameters(e),s=kDt.createHmac(\"sha256\",t).update(r).digest(\"hex\");return Buffer.from(s+r).toString(\"base64\")},jS=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Get,path:Kt.encode(\"1/keys/%s\",e)},r),nSe=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Get,path:Kt.encode(\"1/task/%s\",e.toString())},r),qDt=t=>e=>t.transporter.read({method:br.MethodEnum.Get,path:\"/1/dictionaries/*/settings\"},e),GDt=t=>e=>t.transporter.read({method:br.MethodEnum.Get,path:\"1/logs\"},e),WDt=()=>t=>{let e=Buffer.from(t,\"base64\").toString(\"ascii\"),r=/validUntil=(\\d+)/,s=e.match(r);if(s===null)throw rSe();return parseInt(s[1],10)-Math.round(new Date().getTime()/1e3)},YDt=t=>e=>t.transporter.read({method:br.MethodEnum.Get,path:\"1/clusters/mapping/top\"},e),VDt=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Get,path:Kt.encode(\"1/clusters/mapping/%s\",e)},r),KDt=t=>e=>{let{retrieveMappings:r,...s}=e||{};return r===!0&&(s.getClusters=!0),t.transporter.read({method:br.MethodEnum.Get,path:\"1/clusters/mapping/pending\"},s)},qS=t=>(e,r={})=>{let s={transporter:t.transporter,appId:t.appId,indexName:e};return Kt.addMethods(s,r.methods)},JDt=t=>e=>t.transporter.read({method:br.MethodEnum.Get,path:\"1/keys\"},e),zDt=t=>e=>t.transporter.read({method:br.MethodEnum.Get,path:\"1/clusters\"},e),ZDt=t=>e=>t.transporter.read({method:br.MethodEnum.Get,path:\"1/indexes\"},e),XDt=t=>e=>t.transporter.read({method:br.MethodEnum.Get,path:\"1/clusters/mapping\"},e),$Dt=t=>(e,r,s)=>{let a=(n,c)=>qS(t)(e,{methods:{waitTask:ds}}).waitTask(n.taskID,c);return Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Kt.encode(\"1/indexes/%s/operation\",e),data:{operation:\"move\",destination:r}},s),a)},ebt=t=>(e,r)=>{let s=(a,n)=>Promise.all(Object.keys(a.taskID).map(c=>qS(t)(c,{methods:{waitTask:ds}}).waitTask(a.taskID[c],n)));return Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:\"1/indexes/*/batch\",data:{requests:e}},r),s)},tbt=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Post,path:\"1/indexes/*/objects\",data:{requests:e}},r),rbt=t=>(e,r)=>{let s=e.map(a=>({...a,params:dl.serializeQueryParameters(a.params||{})}));return t.transporter.read({method:br.MethodEnum.Post,path:\"1/indexes/*/queries\",data:{requests:s},cacheable:!0},r)},nbt=t=>(e,r)=>Promise.all(e.map(s=>{let{facetName:a,facetQuery:n,...c}=s.params;return qS(t)(s.indexName,{methods:{searchForFacetValues:uSe}}).searchForFacetValues(a,n,{...r,...c})})),ibt=t=>(e,r)=>{let s=dl.createMappedRequestOptions(r);return s.queryParameters[\"X-Algolia-User-ID\"]=e,t.transporter.write({method:br.MethodEnum.Delete,path:\"1/clusters/mapping\"},s)},sbt=t=>(e,r,s)=>{let a=r.map(n=>({action:\"addEntry\",body:n}));return Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Kt.encode(\"/1/dictionaries/%s/batch\",e),data:{clearExistingDictionaryEntries:!0,requests:a}},s),(n,c)=>aw(t)(n.taskID,c))},obt=t=>(e,r)=>{let s=(a,n)=>Kt.createRetryablePromise(c=>jS(t)(e,n).catch(f=>{if(f.status!==404)throw f;return c()}));return Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Kt.encode(\"1/keys/%s/restore\",e)},r),s)},abt=t=>(e,r,s)=>{let a=r.map(n=>({action:\"addEntry\",body:n}));return Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Kt.encode(\"/1/dictionaries/%s/batch\",e),data:{clearExistingDictionaryEntries:!1,requests:a}},s),(n,c)=>aw(t)(n.taskID,c))},lbt=t=>(e,r,s)=>t.transporter.read({method:br.MethodEnum.Post,path:Kt.encode(\"/1/dictionaries/%s/search\",e),data:{query:r},cacheable:!0},s),cbt=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Post,path:\"1/clusters/mapping/search\",data:{query:e}},r),ubt=t=>(e,r)=>Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Put,path:\"/1/dictionaries/*/settings\",data:e},r),(s,a)=>aw(t)(s.taskID,a)),fbt=t=>(e,r)=>{let s=Object.assign({},r),{queryParameters:a,...n}=r||{},c=a?{queryParameters:a}:{},f=[\"acl\",\"indexes\",\"referers\",\"restrictSources\",\"queryParameters\",\"description\",\"maxQueriesPerIPPerHour\",\"maxHitsPerQuery\"],p=E=>Object.keys(s).filter(C=>f.indexOf(C)!==-1).every(C=>{if(Array.isArray(E[C])&&Array.isArray(s[C])){let S=E[C];return S.length===s[C].length&&S.every((P,I)=>P===s[C][I])}else return E[C]===s[C]}),h=(E,C)=>Kt.createRetryablePromise(S=>jS(t)(e,C).then(P=>p(P)?Promise.resolve():S()));return Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Put,path:Kt.encode(\"1/keys/%s\",e),data:c},n),h)},aw=t=>(e,r)=>Kt.createRetryablePromise(s=>nSe(t)(e,r).then(a=>a.status!==\"published\"?s():void 0)),iSe=t=>(e,r)=>{let s=(a,n)=>ds(t)(a.taskID,n);return Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Kt.encode(\"1/indexes/%s/batch\",t.indexName),data:{requests:e}},r),s)},Abt=t=>e=>hF({shouldStop:r=>r.cursor===void 0,...e,request:r=>t.transporter.read({method:br.MethodEnum.Post,path:Kt.encode(\"1/indexes/%s/browse\",t.indexName),data:r},e)}),pbt=t=>e=>{let r={hitsPerPage:1e3,...e};return hF({shouldStop:s=>s.hits.length<r.hitsPerPage,...r,request(s){return fSe(t)(\"\",{...r,...s}).then(a=>({...a,hits:a.hits.map(n=>(delete n._highlightResult,n))}))}})},hbt=t=>e=>{let r={hitsPerPage:1e3,...e};return hF({shouldStop:s=>s.hits.length<r.hitsPerPage,...r,request(s){return ASe(t)(\"\",{...r,...s}).then(a=>({...a,hits:a.hits.map(n=>(delete n._highlightResult,n))}))}})},dF=t=>(e,r,s)=>{let{batchSize:a,...n}=s||{},c={taskIDs:[],objectIDs:[]},f=(p=0)=>{let h=[],E;for(E=p;E<e.length&&(h.push(e[E]),h.length!==(a||1e3));E++);return h.length===0?Promise.resolve(c):iSe(t)(h.map(C=>({action:r,body:C})),n).then(C=>(c.objectIDs=c.objectIDs.concat(C.objectIDs),c.taskIDs.push(C.taskID),E++,f(E)))};return Kt.createWaitablePromise(f(),(p,h)=>Promise.all(p.taskIDs.map(E=>ds(t)(E,h))))},gbt=t=>e=>Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Kt.encode(\"1/indexes/%s/clear\",t.indexName)},e),(r,s)=>ds(t)(r.taskID,s)),dbt=t=>e=>{let{forwardToReplicas:r,...s}=e||{},a=dl.createMappedRequestOptions(s);return r&&(a.queryParameters.forwardToReplicas=1),Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Kt.encode(\"1/indexes/%s/rules/clear\",t.indexName)},a),(n,c)=>ds(t)(n.taskID,c))},mbt=t=>e=>{let{forwardToReplicas:r,...s}=e||{},a=dl.createMappedRequestOptions(s);return r&&(a.queryParameters.forwardToReplicas=1),Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Kt.encode(\"1/indexes/%s/synonyms/clear\",t.indexName)},a),(n,c)=>ds(t)(n.taskID,c))},ybt=t=>(e,r)=>Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Kt.encode(\"1/indexes/%s/deleteByQuery\",t.indexName),data:e},r),(s,a)=>ds(t)(s.taskID,a)),Ebt=t=>e=>Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Delete,path:Kt.encode(\"1/indexes/%s\",t.indexName)},e),(r,s)=>ds(t)(r.taskID,s)),Ibt=t=>(e,r)=>Kt.createWaitablePromise(sSe(t)([e],r).then(s=>({taskID:s.taskIDs[0]})),(s,a)=>ds(t)(s.taskID,a)),sSe=t=>(e,r)=>{let s=e.map(a=>({objectID:a}));return dF(t)(s,xm.DeleteObject,r)},Cbt=t=>(e,r)=>{let{forwardToReplicas:s,...a}=r||{},n=dl.createMappedRequestOptions(a);return s&&(n.queryParameters.forwardToReplicas=1),Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Delete,path:Kt.encode(\"1/indexes/%s/rules/%s\",t.indexName,e)},n),(c,f)=>ds(t)(c.taskID,f))},wbt=t=>(e,r)=>{let{forwardToReplicas:s,...a}=r||{},n=dl.createMappedRequestOptions(a);return s&&(n.queryParameters.forwardToReplicas=1),Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Delete,path:Kt.encode(\"1/indexes/%s/synonyms/%s\",t.indexName,e)},n),(c,f)=>ds(t)(c.taskID,f))},Bbt=t=>e=>oSe(t)(e).then(()=>!0).catch(r=>{if(r.status!==404)throw r;return!1}),vbt=t=>(e,r,s)=>t.transporter.read({method:br.MethodEnum.Post,path:Kt.encode(\"1/answers/%s/prediction\",t.indexName),data:{query:e,queryLanguages:r},cacheable:!0},s),Sbt=t=>(e,r)=>{let{query:s,paginate:a,...n}=r||{},c=0,f=()=>cSe(t)(s||\"\",{...n,page:c}).then(p=>{for(let[h,E]of Object.entries(p.hits))if(e(E))return{object:E,position:parseInt(h,10),page:c};if(c++,a===!1||c>=p.nbPages)throw tSe();return f()});return f()},Dbt=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Get,path:Kt.encode(\"1/indexes/%s/%s\",t.indexName,e)},r),bbt=()=>(t,e)=>{for(let[r,s]of Object.entries(t.hits))if(s.objectID===e)return parseInt(r,10);return-1},Pbt=t=>(e,r)=>{let{attributesToRetrieve:s,...a}=r||{},n=e.map(c=>({indexName:t.indexName,objectID:c,...s?{attributesToRetrieve:s}:{}}));return t.transporter.read({method:br.MethodEnum.Post,path:\"1/indexes/*/objects\",data:{requests:n}},a)},xbt=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Get,path:Kt.encode(\"1/indexes/%s/rules/%s\",t.indexName,e)},r),oSe=t=>e=>t.transporter.read({method:br.MethodEnum.Get,path:Kt.encode(\"1/indexes/%s/settings\",t.indexName),data:{getVersion:2}},e),kbt=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Get,path:Kt.encode(\"1/indexes/%s/synonyms/%s\",t.indexName,e)},r),aSe=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Get,path:Kt.encode(\"1/indexes/%s/task/%s\",t.indexName,e.toString())},r),Qbt=t=>(e,r)=>Kt.createWaitablePromise(lSe(t)([e],r).then(s=>({objectID:s.objectIDs[0],taskID:s.taskIDs[0]})),(s,a)=>ds(t)(s.taskID,a)),lSe=t=>(e,r)=>{let{createIfNotExists:s,...a}=r||{},n=s?xm.PartialUpdateObject:xm.PartialUpdateObjectNoCreate;return dF(t)(e,n,a)},Tbt=t=>(e,r)=>{let{safe:s,autoGenerateObjectIDIfNotExist:a,batchSize:n,...c}=r||{},f=(I,R,N,U)=>Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Kt.encode(\"1/indexes/%s/operation\",I),data:{operation:N,destination:R}},U),(W,te)=>ds(t)(W.taskID,te)),p=Math.random().toString(36).substring(7),h=`${t.indexName}_tmp_${p}`,E=L9({appId:t.appId,transporter:t.transporter,indexName:h}),C=[],S=f(t.indexName,h,\"copy\",{...c,scope:[\"settings\",\"synonyms\",\"rules\"]});C.push(S);let P=(s?S.wait(c):S).then(()=>{let I=E(e,{...c,autoGenerateObjectIDIfNotExist:a,batchSize:n});return C.push(I),s?I.wait(c):I}).then(()=>{let I=f(h,t.indexName,\"move\",c);return C.push(I),s?I.wait(c):I}).then(()=>Promise.all(C)).then(([I,R,N])=>({objectIDs:R.objectIDs,taskIDs:[I.taskID,...R.taskIDs,N.taskID]}));return Kt.createWaitablePromise(P,(I,R)=>Promise.all(C.map(N=>N.wait(R))))},Rbt=t=>(e,r)=>M9(t)(e,{...r,clearExistingRules:!0}),Fbt=t=>(e,r)=>_9(t)(e,{...r,clearExistingSynonyms:!0}),Nbt=t=>(e,r)=>Kt.createWaitablePromise(L9(t)([e],r).then(s=>({objectID:s.objectIDs[0],taskID:s.taskIDs[0]})),(s,a)=>ds(t)(s.taskID,a)),L9=t=>(e,r)=>{let{autoGenerateObjectIDIfNotExist:s,...a}=r||{},n=s?xm.AddObject:xm.UpdateObject;if(n===xm.UpdateObject){for(let c of e)if(c.objectID===void 0)return Kt.createWaitablePromise(Promise.reject(eSe()))}return dF(t)(e,n,a)},Obt=t=>(e,r)=>M9(t)([e],r),M9=t=>(e,r)=>{let{forwardToReplicas:s,clearExistingRules:a,...n}=r||{},c=dl.createMappedRequestOptions(n);return s&&(c.queryParameters.forwardToReplicas=1),a&&(c.queryParameters.clearExistingRules=1),Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Kt.encode(\"1/indexes/%s/rules/batch\",t.indexName),data:e},c),(f,p)=>ds(t)(f.taskID,p))},Lbt=t=>(e,r)=>_9(t)([e],r),_9=t=>(e,r)=>{let{forwardToReplicas:s,clearExistingSynonyms:a,replaceExistingSynonyms:n,...c}=r||{},f=dl.createMappedRequestOptions(c);return s&&(f.queryParameters.forwardToReplicas=1),(n||a)&&(f.queryParameters.replaceExistingSynonyms=1),Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Kt.encode(\"1/indexes/%s/synonyms/batch\",t.indexName),data:e},f),(p,h)=>ds(t)(p.taskID,h))},cSe=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Post,path:Kt.encode(\"1/indexes/%s/query\",t.indexName),data:{query:e},cacheable:!0},r),uSe=t=>(e,r,s)=>t.transporter.read({method:br.MethodEnum.Post,path:Kt.encode(\"1/indexes/%s/facets/%s/query\",t.indexName,e),data:{facetQuery:r},cacheable:!0},s),fSe=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Post,path:Kt.encode(\"1/indexes/%s/rules/search\",t.indexName),data:{query:e}},r),ASe=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Post,path:Kt.encode(\"1/indexes/%s/synonyms/search\",t.indexName),data:{query:e}},r),Mbt=t=>(e,r)=>{let{forwardToReplicas:s,...a}=r||{},n=dl.createMappedRequestOptions(a);return s&&(n.queryParameters.forwardToReplicas=1),Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Put,path:Kt.encode(\"1/indexes/%s/settings\",t.indexName),data:e},n),(c,f)=>ds(t)(c.taskID,f))},ds=t=>(e,r)=>Kt.createRetryablePromise(s=>aSe(t)(e,r).then(a=>a.status!==\"published\"?s():void 0)),_bt={AddObject:\"addObject\",Analytics:\"analytics\",Browser:\"browse\",DeleteIndex:\"deleteIndex\",DeleteObject:\"deleteObject\",EditSettings:\"editSettings\",Inference:\"inference\",ListIndexes:\"listIndexes\",Logs:\"logs\",Personalization:\"personalization\",Recommendation:\"recommendation\",Search:\"search\",SeeUnretrievableAttributes:\"seeUnretrievableAttributes\",Settings:\"settings\",Usage:\"usage\"},xm={AddObject:\"addObject\",UpdateObject:\"updateObject\",PartialUpdateObject:\"partialUpdateObject\",PartialUpdateObjectNoCreate:\"partialUpdateObjectNoCreate\",DeleteObject:\"deleteObject\",DeleteIndex:\"delete\",ClearIndex:\"clear\"},mF={Settings:\"settings\",Synonyms:\"synonyms\",Rules:\"rules\"},Ubt={None:\"none\",StopIfEnoughMatches:\"stopIfEnoughMatches\"},Hbt={Synonym:\"synonym\",OneWaySynonym:\"oneWaySynonym\",AltCorrection1:\"altCorrection1\",AltCorrection2:\"altCorrection2\",Placeholder:\"placeholder\"};Ft.ApiKeyACLEnum=_bt;Ft.BatchActionEnum=xm;Ft.ScopeEnum=mF;Ft.StrategyEnum=Ubt;Ft.SynonymEnum=Hbt;Ft.addApiKey=TDt;Ft.assignUserID=RDt;Ft.assignUserIDs=FDt;Ft.batch=iSe;Ft.browseObjects=Abt;Ft.browseRules=pbt;Ft.browseSynonyms=hbt;Ft.chunkedBatch=dF;Ft.clearDictionaryEntries=NDt;Ft.clearObjects=gbt;Ft.clearRules=dbt;Ft.clearSynonyms=mbt;Ft.copyIndex=gF;Ft.copyRules=ODt;Ft.copySettings=LDt;Ft.copySynonyms=MDt;Ft.createBrowsablePromise=hF;Ft.createMissingObjectIDError=eSe;Ft.createObjectNotFoundError=tSe;Ft.createSearchClient=QDt;Ft.createValidUntilNotFoundError=rSe;Ft.customRequest=_Dt;Ft.deleteApiKey=UDt;Ft.deleteBy=ybt;Ft.deleteDictionaryEntries=HDt;Ft.deleteIndex=Ebt;Ft.deleteObject=Ibt;Ft.deleteObjects=sSe;Ft.deleteRule=Cbt;Ft.deleteSynonym=wbt;Ft.exists=Bbt;Ft.findAnswers=vbt;Ft.findObject=Sbt;Ft.generateSecuredApiKey=jDt;Ft.getApiKey=jS;Ft.getAppTask=nSe;Ft.getDictionarySettings=qDt;Ft.getLogs=GDt;Ft.getObject=Dbt;Ft.getObjectPosition=bbt;Ft.getObjects=Pbt;Ft.getRule=xbt;Ft.getSecuredApiKeyRemainingValidity=WDt;Ft.getSettings=oSe;Ft.getSynonym=kbt;Ft.getTask=aSe;Ft.getTopUserIDs=YDt;Ft.getUserID=VDt;Ft.hasPendingMappings=KDt;Ft.initIndex=qS;Ft.listApiKeys=JDt;Ft.listClusters=zDt;Ft.listIndices=ZDt;Ft.listUserIDs=XDt;Ft.moveIndex=$Dt;Ft.multipleBatch=ebt;Ft.multipleGetObjects=tbt;Ft.multipleQueries=rbt;Ft.multipleSearchForFacetValues=nbt;Ft.partialUpdateObject=Qbt;Ft.partialUpdateObjects=lSe;Ft.removeUserID=ibt;Ft.replaceAllObjects=Tbt;Ft.replaceAllRules=Rbt;Ft.replaceAllSynonyms=Fbt;Ft.replaceDictionaryEntries=sbt;Ft.restoreApiKey=obt;Ft.saveDictionaryEntries=abt;Ft.saveObject=Nbt;Ft.saveObjects=L9;Ft.saveRule=Obt;Ft.saveRules=M9;Ft.saveSynonym=Lbt;Ft.saveSynonyms=_9;Ft.search=cSe;Ft.searchDictionaryEntries=lbt;Ft.searchForFacetValues=uSe;Ft.searchRules=fSe;Ft.searchSynonyms=ASe;Ft.searchUserIDs=cbt;Ft.setDictionarySettings=ubt;Ft.setSettings=Mbt;Ft.updateApiKey=fbt;Ft.waitAppTask=aw;Ft.waitTask=ds});var gSe=L((Dpr,hSe)=>{hSe.exports=pSe()});var dSe=L(yF=>{\"use strict\";Object.defineProperty(yF,\"__esModule\",{value:!0});function jbt(){return{debug(t,e){return Promise.resolve()},info(t,e){return Promise.resolve()},error(t,e){return Promise.resolve()}}}var qbt={Debug:1,Info:2,Error:3};yF.LogLevelEnum=qbt;yF.createNullLogger=jbt});var ySe=L((Ppr,mSe)=>{mSe.exports=dSe()});var wSe=L(U9=>{\"use strict\";Object.defineProperty(U9,\"__esModule\",{value:!0});var ESe=Ie(\"http\"),ISe=Ie(\"https\"),Gbt=Ie(\"url\"),CSe={keepAlive:!0},Wbt=new ESe.Agent(CSe),Ybt=new ISe.Agent(CSe);function Vbt({agent:t,httpAgent:e,httpsAgent:r,requesterOptions:s={}}={}){let a=e||t||Wbt,n=r||t||Ybt;return{send(c){return new Promise(f=>{let p=Gbt.parse(c.url),h=p.query===null?p.pathname:`${p.pathname}?${p.query}`,E={...s,agent:p.protocol===\"https:\"?n:a,hostname:p.hostname,path:h,method:c.method,headers:{...s&&s.headers?s.headers:{},...c.headers},...p.port!==void 0?{port:p.port||\"\"}:{}},C=(p.protocol===\"https:\"?ISe:ESe).request(E,R=>{let N=[];R.on(\"data\",U=>{N=N.concat(U)}),R.on(\"end\",()=>{clearTimeout(P),clearTimeout(I),f({status:R.statusCode||0,content:Buffer.concat(N).toString(),isTimedOut:!1})})}),S=(R,N)=>setTimeout(()=>{C.abort(),f({status:0,content:N,isTimedOut:!0})},R*1e3),P=S(c.connectTimeout,\"Connection timeout\"),I;C.on(\"error\",R=>{clearTimeout(P),clearTimeout(I),f({status:0,content:R.message,isTimedOut:!1})}),C.once(\"response\",()=>{clearTimeout(P),I=S(c.responseTimeout,\"Socket timeout\")}),c.data!==void 0&&C.write(c.data),C.end()})},destroy(){return a.destroy(),n.destroy(),Promise.resolve()}}}U9.createNodeHttpRequester=Vbt});var vSe=L((kpr,BSe)=>{BSe.exports=wSe()});var PSe=L((Qpr,bSe)=>{\"use strict\";var SSe=wve(),Kbt=Sve(),lw=Jve(),j9=OS(),H9=$ve(),jt=gSe(),Jbt=ySe(),zbt=vSe(),Zbt=_S();function DSe(t,e,r){let s={appId:t,apiKey:e,timeouts:{connect:2,read:5,write:30},requester:zbt.createNodeHttpRequester(),logger:Jbt.createNullLogger(),responsesCache:SSe.createNullCache(),requestsCache:SSe.createNullCache(),hostsCache:Kbt.createInMemoryCache(),userAgent:Zbt.createUserAgent(j9.version).add({segment:\"Node.js\",version:process.versions.node})},a={...s,...r},n=()=>c=>H9.createPersonalizationClient({...s,...c,methods:{getPersonalizationStrategy:H9.getPersonalizationStrategy,setPersonalizationStrategy:H9.setPersonalizationStrategy}});return jt.createSearchClient({...a,methods:{search:jt.multipleQueries,searchForFacetValues:jt.multipleSearchForFacetValues,multipleBatch:jt.multipleBatch,multipleGetObjects:jt.multipleGetObjects,multipleQueries:jt.multipleQueries,copyIndex:jt.copyIndex,copySettings:jt.copySettings,copyRules:jt.copyRules,copySynonyms:jt.copySynonyms,moveIndex:jt.moveIndex,listIndices:jt.listIndices,getLogs:jt.getLogs,listClusters:jt.listClusters,multipleSearchForFacetValues:jt.multipleSearchForFacetValues,getApiKey:jt.getApiKey,addApiKey:jt.addApiKey,listApiKeys:jt.listApiKeys,updateApiKey:jt.updateApiKey,deleteApiKey:jt.deleteApiKey,restoreApiKey:jt.restoreApiKey,assignUserID:jt.assignUserID,assignUserIDs:jt.assignUserIDs,getUserID:jt.getUserID,searchUserIDs:jt.searchUserIDs,listUserIDs:jt.listUserIDs,getTopUserIDs:jt.getTopUserIDs,removeUserID:jt.removeUserID,hasPendingMappings:jt.hasPendingMappings,generateSecuredApiKey:jt.generateSecuredApiKey,getSecuredApiKeyRemainingValidity:jt.getSecuredApiKeyRemainingValidity,destroy:j9.destroy,clearDictionaryEntries:jt.clearDictionaryEntries,deleteDictionaryEntries:jt.deleteDictionaryEntries,getDictionarySettings:jt.getDictionarySettings,getAppTask:jt.getAppTask,replaceDictionaryEntries:jt.replaceDictionaryEntries,saveDictionaryEntries:jt.saveDictionaryEntries,searchDictionaryEntries:jt.searchDictionaryEntries,setDictionarySettings:jt.setDictionarySettings,waitAppTask:jt.waitAppTask,customRequest:jt.customRequest,initIndex:c=>f=>jt.initIndex(c)(f,{methods:{batch:jt.batch,delete:jt.deleteIndex,findAnswers:jt.findAnswers,getObject:jt.getObject,getObjects:jt.getObjects,saveObject:jt.saveObject,saveObjects:jt.saveObjects,search:jt.search,searchForFacetValues:jt.searchForFacetValues,waitTask:jt.waitTask,setSettings:jt.setSettings,getSettings:jt.getSettings,partialUpdateObject:jt.partialUpdateObject,partialUpdateObjects:jt.partialUpdateObjects,deleteObject:jt.deleteObject,deleteObjects:jt.deleteObjects,deleteBy:jt.deleteBy,clearObjects:jt.clearObjects,browseObjects:jt.browseObjects,getObjectPosition:jt.getObjectPosition,findObject:jt.findObject,exists:jt.exists,saveSynonym:jt.saveSynonym,saveSynonyms:jt.saveSynonyms,getSynonym:jt.getSynonym,searchSynonyms:jt.searchSynonyms,browseSynonyms:jt.browseSynonyms,deleteSynonym:jt.deleteSynonym,clearSynonyms:jt.clearSynonyms,replaceAllObjects:jt.replaceAllObjects,replaceAllSynonyms:jt.replaceAllSynonyms,searchRules:jt.searchRules,getRule:jt.getRule,deleteRule:jt.deleteRule,saveRule:jt.saveRule,saveRules:jt.saveRules,replaceAllRules:jt.replaceAllRules,browseRules:jt.browseRules,clearRules:jt.clearRules}}),initAnalytics:()=>c=>lw.createAnalyticsClient({...s,...c,methods:{addABTest:lw.addABTest,getABTest:lw.getABTest,getABTests:lw.getABTests,stopABTest:lw.stopABTest,deleteABTest:lw.deleteABTest}}),initPersonalization:n,initRecommendation:()=>c=>(a.logger.info(\"The `initRecommendation` method is deprecated. Use `initPersonalization` instead.\"),n()(c))}})}DSe.version=j9.version;bSe.exports=DSe});var G9=L((Tpr,q9)=>{var xSe=PSe();q9.exports=xSe;q9.exports.default=xSe});var V9=L((Fpr,TSe)=>{\"use strict\";var QSe=Object.getOwnPropertySymbols,$bt=Object.prototype.hasOwnProperty,ePt=Object.prototype.propertyIsEnumerable;function tPt(t){if(t==null)throw new TypeError(\"Object.assign cannot be called with null or undefined\");return Object(t)}function rPt(){try{if(!Object.assign)return!1;var t=new String(\"abc\");if(t[5]=\"de\",Object.getOwnPropertyNames(t)[0]===\"5\")return!1;for(var e={},r=0;r<10;r++)e[\"_\"+String.fromCharCode(r)]=r;var s=Object.getOwnPropertyNames(e).map(function(n){return e[n]});if(s.join(\"\")!==\"0123456789\")return!1;var a={};return\"abcdefghijklmnopqrst\".split(\"\").forEach(function(n){a[n]=n}),Object.keys(Object.assign({},a)).join(\"\")===\"abcdefghijklmnopqrst\"}catch{return!1}}TSe.exports=rPt()?Object.assign:function(t,e){for(var r,s=tPt(t),a,n=1;n<arguments.length;n++){r=Object(arguments[n]);for(var c in r)$bt.call(r,c)&&(s[c]=r[c]);if(QSe){a=QSe(r);for(var f=0;f<a.length;f++)ePt.call(r,a[f])&&(s[a[f]]=r[a[f]])}}return s}});var KSe=L(Dn=>{\"use strict\";var J9=V9(),cw=60103,NSe=60106;Dn.Fragment=60107;Dn.StrictMode=60108;Dn.Profiler=60114;var OSe=60109,LSe=60110,MSe=60112;Dn.Suspense=60113;var _Se=60115,USe=60116;typeof Symbol==\"function\"&&Symbol.for&&(Wc=Symbol.for,cw=Wc(\"react.element\"),NSe=Wc(\"react.portal\"),Dn.Fragment=Wc(\"react.fragment\"),Dn.StrictMode=Wc(\"react.strict_mode\"),Dn.Profiler=Wc(\"react.profiler\"),OSe=Wc(\"react.provider\"),LSe=Wc(\"react.context\"),MSe=Wc(\"react.forward_ref\"),Dn.Suspense=Wc(\"react.suspense\"),_Se=Wc(\"react.memo\"),USe=Wc(\"react.lazy\"));var Wc,RSe=typeof Symbol==\"function\"&&Symbol.iterator;function nPt(t){return t===null||typeof t!=\"object\"?null:(t=RSe&&t[RSe]||t[\"@@iterator\"],typeof t==\"function\"?t:null)}function GS(t){for(var e=\"https://reactjs.org/docs/error-decoder.html?invariant=\"+t,r=1;r<arguments.length;r++)e+=\"&args[]=\"+encodeURIComponent(arguments[r]);return\"Minified React error #\"+t+\"; visit \"+e+\" for the full message or use the non-minified dev environment for full errors and additional helpful warnings.\"}var HSe={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},jSe={};function uw(t,e,r){this.props=t,this.context=e,this.refs=jSe,this.updater=r||HSe}uw.prototype.isReactComponent={};uw.prototype.setState=function(t,e){if(typeof t!=\"object\"&&typeof t!=\"function\"&&t!=null)throw Error(GS(85));this.updater.enqueueSetState(this,t,e,\"setState\")};uw.prototype.forceUpdate=function(t){this.updater.enqueueForceUpdate(this,t,\"forceUpdate\")};function qSe(){}qSe.prototype=uw.prototype;function z9(t,e,r){this.props=t,this.context=e,this.refs=jSe,this.updater=r||HSe}var Z9=z9.prototype=new qSe;Z9.constructor=z9;J9(Z9,uw.prototype);Z9.isPureReactComponent=!0;var X9={current:null},GSe=Object.prototype.hasOwnProperty,WSe={key:!0,ref:!0,__self:!0,__source:!0};function YSe(t,e,r){var s,a={},n=null,c=null;if(e!=null)for(s in e.ref!==void 0&&(c=e.ref),e.key!==void 0&&(n=\"\"+e.key),e)GSe.call(e,s)&&!WSe.hasOwnProperty(s)&&(a[s]=e[s]);var f=arguments.length-2;if(f===1)a.children=r;else if(1<f){for(var p=Array(f),h=0;h<f;h++)p[h]=arguments[h+2];a.children=p}if(t&&t.defaultProps)for(s in f=t.defaultProps,f)a[s]===void 0&&(a[s]=f[s]);return{$$typeof:cw,type:t,key:n,ref:c,props:a,_owner:X9.current}}function iPt(t,e){return{$$typeof:cw,type:t.type,key:e,ref:t.ref,props:t.props,_owner:t._owner}}function $9(t){return typeof t==\"object\"&&t!==null&&t.$$typeof===cw}function sPt(t){var e={\"=\":\"=0\",\":\":\"=2\"};return\"$\"+t.replace(/[=:]/g,function(r){return e[r]})}var FSe=/\\/+/g;function K9(t,e){return typeof t==\"object\"&&t!==null&&t.key!=null?sPt(\"\"+t.key):e.toString(36)}function IF(t,e,r,s,a){var n=typeof t;(n===\"undefined\"||n===\"boolean\")&&(t=null);var c=!1;if(t===null)c=!0;else switch(n){case\"string\":case\"number\":c=!0;break;case\"object\":switch(t.$$typeof){case cw:case NSe:c=!0}}if(c)return c=t,a=a(c),t=s===\"\"?\".\"+K9(c,0):s,Array.isArray(a)?(r=\"\",t!=null&&(r=t.replace(FSe,\"$&/\")+\"/\"),IF(a,e,r,\"\",function(h){return h})):a!=null&&($9(a)&&(a=iPt(a,r+(!a.key||c&&c.key===a.key?\"\":(\"\"+a.key).replace(FSe,\"$&/\")+\"/\")+t)),e.push(a)),1;if(c=0,s=s===\"\"?\".\":s+\":\",Array.isArray(t))for(var f=0;f<t.length;f++){n=t[f];var p=s+K9(n,f);c+=IF(n,e,r,p,a)}else if(p=nPt(t),typeof p==\"function\")for(t=p.call(t),f=0;!(n=t.next()).done;)n=n.value,p=s+K9(n,f++),c+=IF(n,e,r,p,a);else if(n===\"object\")throw e=\"\"+t,Error(GS(31,e===\"[object Object]\"?\"object with keys {\"+Object.keys(t).join(\", \")+\"}\":e));return c}function EF(t,e,r){if(t==null)return t;var s=[],a=0;return IF(t,s,\"\",\"\",function(n){return e.call(r,n,a++)}),s}function oPt(t){if(t._status===-1){var e=t._result;e=e(),t._status=0,t._result=e,e.then(function(r){t._status===0&&(r=r.default,t._status=1,t._result=r)},function(r){t._status===0&&(t._status=2,t._result=r)})}if(t._status===1)return t._result;throw t._result}var VSe={current:null};function eh(){var t=VSe.current;if(t===null)throw Error(GS(321));return t}var aPt={ReactCurrentDispatcher:VSe,ReactCurrentBatchConfig:{transition:0},ReactCurrentOwner:X9,IsSomeRendererActing:{current:!1},assign:J9};Dn.Children={map:EF,forEach:function(t,e,r){EF(t,function(){e.apply(this,arguments)},r)},count:function(t){var e=0;return EF(t,function(){e++}),e},toArray:function(t){return EF(t,function(e){return e})||[]},only:function(t){if(!$9(t))throw Error(GS(143));return t}};Dn.Component=uw;Dn.PureComponent=z9;Dn.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=aPt;Dn.cloneElement=function(t,e,r){if(t==null)throw Error(GS(267,t));var s=J9({},t.props),a=t.key,n=t.ref,c=t._owner;if(e!=null){if(e.ref!==void 0&&(n=e.ref,c=X9.current),e.key!==void 0&&(a=\"\"+e.key),t.type&&t.type.defaultProps)var f=t.type.defaultProps;for(p in e)GSe.call(e,p)&&!WSe.hasOwnProperty(p)&&(s[p]=e[p]===void 0&&f!==void 0?f[p]:e[p])}var p=arguments.length-2;if(p===1)s.children=r;else if(1<p){f=Array(p);for(var h=0;h<p;h++)f[h]=arguments[h+2];s.children=f}return{$$typeof:cw,type:t.type,key:a,ref:n,props:s,_owner:c}};Dn.createContext=function(t,e){return e===void 0&&(e=null),t={$$typeof:LSe,_calculateChangedBits:e,_currentValue:t,_currentValue2:t,_threadCount:0,Provider:null,Consumer:null},t.Provider={$$typeof:OSe,_context:t},t.Consumer=t};Dn.createElement=YSe;Dn.createFactory=function(t){var e=YSe.bind(null,t);return e.type=t,e};Dn.createRef=function(){return{current:null}};Dn.forwardRef=function(t){return{$$typeof:MSe,render:t}};Dn.isValidElement=$9;Dn.lazy=function(t){return{$$typeof:USe,_payload:{_status:-1,_result:t},_init:oPt}};Dn.memo=function(t,e){return{$$typeof:_Se,type:t,compare:e===void 0?null:e}};Dn.useCallback=function(t,e){return eh().useCallback(t,e)};Dn.useContext=function(t,e){return eh().useContext(t,e)};Dn.useDebugValue=function(){};Dn.useEffect=function(t,e){return eh().useEffect(t,e)};Dn.useImperativeHandle=function(t,e,r){return eh().useImperativeHandle(t,e,r)};Dn.useLayoutEffect=function(t,e){return eh().useLayoutEffect(t,e)};Dn.useMemo=function(t,e){return eh().useMemo(t,e)};Dn.useReducer=function(t,e,r){return eh().useReducer(t,e,r)};Dn.useRef=function(t){return eh().useRef(t)};Dn.useState=function(t){return eh().useState(t)};Dn.version=\"17.0.2\"});var hn=L((Opr,JSe)=>{\"use strict\";JSe.exports=KSe()});var tW=L((Lpr,eW)=>{\"use strict\";var Cn=eW.exports;eW.exports.default=Cn;var Zn=\"\\x1B[\",WS=\"\\x1B]\",fw=\"\\x07\",CF=\";\",zSe=process.env.TERM_PROGRAM===\"Apple_Terminal\";Cn.cursorTo=(t,e)=>{if(typeof t!=\"number\")throw new TypeError(\"The `x` argument is required\");return typeof e!=\"number\"?Zn+(t+1)+\"G\":Zn+(e+1)+\";\"+(t+1)+\"H\"};Cn.cursorMove=(t,e)=>{if(typeof t!=\"number\")throw new TypeError(\"The `x` argument is required\");let r=\"\";return t<0?r+=Zn+-t+\"D\":t>0&&(r+=Zn+t+\"C\"),e<0?r+=Zn+-e+\"A\":e>0&&(r+=Zn+e+\"B\"),r};Cn.cursorUp=(t=1)=>Zn+t+\"A\";Cn.cursorDown=(t=1)=>Zn+t+\"B\";Cn.cursorForward=(t=1)=>Zn+t+\"C\";Cn.cursorBackward=(t=1)=>Zn+t+\"D\";Cn.cursorLeft=Zn+\"G\";Cn.cursorSavePosition=zSe?\"\\x1B7\":Zn+\"s\";Cn.cursorRestorePosition=zSe?\"\\x1B8\":Zn+\"u\";Cn.cursorGetPosition=Zn+\"6n\";Cn.cursorNextLine=Zn+\"E\";Cn.cursorPrevLine=Zn+\"F\";Cn.cursorHide=Zn+\"?25l\";Cn.cursorShow=Zn+\"?25h\";Cn.eraseLines=t=>{let e=\"\";for(let r=0;r<t;r++)e+=Cn.eraseLine+(r<t-1?Cn.cursorUp():\"\");return t&&(e+=Cn.cursorLeft),e};Cn.eraseEndLine=Zn+\"K\";Cn.eraseStartLine=Zn+\"1K\";Cn.eraseLine=Zn+\"2K\";Cn.eraseDown=Zn+\"J\";Cn.eraseUp=Zn+\"1J\";Cn.eraseScreen=Zn+\"2J\";Cn.scrollUp=Zn+\"S\";Cn.scrollDown=Zn+\"T\";Cn.clearScreen=\"\\x1Bc\";Cn.clearTerminal=process.platform===\"win32\"?`${Cn.eraseScreen}${Zn}0f`:`${Cn.eraseScreen}${Zn}3J${Zn}H`;Cn.beep=fw;Cn.link=(t,e)=>[WS,\"8\",CF,CF,e,fw,t,WS,\"8\",CF,CF,fw].join(\"\");Cn.image=(t,e={})=>{let r=`${WS}1337;File=inline=1`;return e.width&&(r+=`;width=${e.width}`),e.height&&(r+=`;height=${e.height}`),e.preserveAspectRatio===!1&&(r+=\";preserveAspectRatio=0\"),r+\":\"+t.toString(\"base64\")+fw};Cn.iTerm={setCwd:(t=process.cwd())=>`${WS}50;CurrentDir=${t}${fw}`,annotation:(t,e={})=>{let r=`${WS}1337;`,s=typeof e.x<\"u\",a=typeof e.y<\"u\";if((s||a)&&!(s&&a&&typeof e.length<\"u\"))throw new Error(\"`x`, `y` and `length` must be defined when `x` or `y` is defined\");return t=t.replace(/\\|/g,\"\"),r+=e.isHidden?\"AddHiddenAnnotation=\":\"AddAnnotation=\",e.length>0?r+=(s?[t,e.length,e.x,e.y]:[e.length,t]).join(\"|\"):r+=t,r+fw}}});var XSe=L((Mpr,rW)=>{\"use strict\";var ZSe=(t,e)=>{for(let r of Reflect.ownKeys(e))Object.defineProperty(t,r,Object.getOwnPropertyDescriptor(e,r));return t};rW.exports=ZSe;rW.exports.default=ZSe});var eDe=L((_pr,BF)=>{\"use strict\";var lPt=XSe(),wF=new WeakMap,$Se=(t,e={})=>{if(typeof t!=\"function\")throw new TypeError(\"Expected a function\");let r,s=0,a=t.displayName||t.name||\"<anonymous>\",n=function(...c){if(wF.set(n,++s),s===1)r=t.apply(this,c),t=null;else if(e.throw===!0)throw new Error(`Function \\`${a}\\` can only be called once`);return r};return lPt(n,t),wF.set(n,s),n};BF.exports=$Se;BF.exports.default=$Se;BF.exports.callCount=t=>{if(!wF.has(t))throw new Error(`The given function \\`${t.name}\\` is not wrapped by the \\`onetime\\` package`);return wF.get(t)}});var tDe=L((Upr,vF)=>{vF.exports=[\"SIGABRT\",\"SIGALRM\",\"SIGHUP\",\"SIGINT\",\"SIGTERM\"];process.platform!==\"win32\"&&vF.exports.push(\"SIGVTALRM\",\"SIGXCPU\",\"SIGXFSZ\",\"SIGUSR2\",\"SIGTRAP\",\"SIGSYS\",\"SIGQUIT\",\"SIGIOT\");process.platform===\"linux\"&&vF.exports.push(\"SIGIO\",\"SIGPOLL\",\"SIGPWR\",\"SIGSTKFLT\",\"SIGUNUSED\")});var sW=L((Hpr,hw)=>{var Ti=global.process,km=function(t){return t&&typeof t==\"object\"&&typeof t.removeListener==\"function\"&&typeof t.emit==\"function\"&&typeof t.reallyExit==\"function\"&&typeof t.listeners==\"function\"&&typeof t.kill==\"function\"&&typeof t.pid==\"number\"&&typeof t.on==\"function\"};km(Ti)?(rDe=Ie(\"assert\"),Aw=tDe(),nDe=/^win/i.test(Ti.platform),YS=Ie(\"events\"),typeof YS!=\"function\"&&(YS=YS.EventEmitter),Ti.__signal_exit_emitter__?zs=Ti.__signal_exit_emitter__:(zs=Ti.__signal_exit_emitter__=new YS,zs.count=0,zs.emitted={}),zs.infinite||(zs.setMaxListeners(1/0),zs.infinite=!0),hw.exports=function(t,e){if(!km(global.process))return function(){};rDe.equal(typeof t,\"function\",\"a callback must be provided for exit handler\"),pw===!1&&nW();var r=\"exit\";e&&e.alwaysLast&&(r=\"afterexit\");var s=function(){zs.removeListener(r,t),zs.listeners(\"exit\").length===0&&zs.listeners(\"afterexit\").length===0&&SF()};return zs.on(r,t),s},SF=function(){!pw||!km(global.process)||(pw=!1,Aw.forEach(function(e){try{Ti.removeListener(e,DF[e])}catch{}}),Ti.emit=bF,Ti.reallyExit=iW,zs.count-=1)},hw.exports.unload=SF,Qm=function(e,r,s){zs.emitted[e]||(zs.emitted[e]=!0,zs.emit(e,r,s))},DF={},Aw.forEach(function(t){DF[t]=function(){if(km(global.process)){var r=Ti.listeners(t);r.length===zs.count&&(SF(),Qm(\"exit\",null,t),Qm(\"afterexit\",null,t),nDe&&t===\"SIGHUP\"&&(t=\"SIGINT\"),Ti.kill(Ti.pid,t))}}}),hw.exports.signals=function(){return Aw},pw=!1,nW=function(){pw||!km(global.process)||(pw=!0,zs.count+=1,Aw=Aw.filter(function(e){try{return Ti.on(e,DF[e]),!0}catch{return!1}}),Ti.emit=sDe,Ti.reallyExit=iDe)},hw.exports.load=nW,iW=Ti.reallyExit,iDe=function(e){km(global.process)&&(Ti.exitCode=e||0,Qm(\"exit\",Ti.exitCode,null),Qm(\"afterexit\",Ti.exitCode,null),iW.call(Ti,Ti.exitCode))},bF=Ti.emit,sDe=function(e,r){if(e===\"exit\"&&km(global.process)){r!==void 0&&(Ti.exitCode=r);var s=bF.apply(this,arguments);return Qm(\"exit\",Ti.exitCode,null),Qm(\"afterexit\",Ti.exitCode,null),s}else return bF.apply(this,arguments)}):hw.exports=function(){return function(){}};var rDe,Aw,nDe,YS,zs,SF,Qm,DF,pw,nW,iW,iDe,bF,sDe});var aDe=L((jpr,oDe)=>{\"use strict\";var cPt=eDe(),uPt=sW();oDe.exports=cPt(()=>{uPt(()=>{process.stderr.write(\"\\x1B[?25h\")},{alwaysLast:!0})})});var oW=L(gw=>{\"use strict\";var fPt=aDe(),PF=!1;gw.show=(t=process.stderr)=>{t.isTTY&&(PF=!1,t.write(\"\\x1B[?25h\"))};gw.hide=(t=process.stderr)=>{t.isTTY&&(fPt(),PF=!0,t.write(\"\\x1B[?25l\"))};gw.toggle=(t,e)=>{t!==void 0&&(PF=t),PF?gw.show(e):gw.hide(e)}});var fDe=L(VS=>{\"use strict\";var uDe=VS&&VS.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(VS,\"__esModule\",{value:!0});var lDe=uDe(tW()),cDe=uDe(oW()),APt=(t,{showCursor:e=!1}={})=>{let r=0,s=\"\",a=!1,n=c=>{!e&&!a&&(cDe.default.hide(),a=!0);let f=c+`\n`;f!==s&&(s=f,t.write(lDe.default.eraseLines(r)+f),r=f.split(`\n`).length)};return n.clear=()=>{t.write(lDe.default.eraseLines(r)),s=\"\",r=0},n.done=()=>{s=\"\",r=0,e||(cDe.default.show(),a=!1)},n};VS.default={create:APt}});var ADe=L((Wpr,pPt)=>{pPt.exports=[{name:\"AppVeyor\",constant:\"APPVEYOR\",env:\"APPVEYOR\",pr:\"APPVEYOR_PULL_REQUEST_NUMBER\"},{name:\"Azure Pipelines\",constant:\"AZURE_PIPELINES\",env:\"SYSTEM_TEAMFOUNDATIONCOLLECTIONURI\",pr:\"SYSTEM_PULLREQUEST_PULLREQUESTID\"},{name:\"Bamboo\",constant:\"BAMBOO\",env:\"bamboo_planKey\"},{name:\"Bitbucket Pipelines\",constant:\"BITBUCKET\",env:\"BITBUCKET_COMMIT\",pr:\"BITBUCKET_PR_ID\"},{name:\"Bitrise\",constant:\"BITRISE\",env:\"BITRISE_IO\",pr:\"BITRISE_PULL_REQUEST\"},{name:\"Buddy\",constant:\"BUDDY\",env:\"BUDDY_WORKSPACE_ID\",pr:\"BUDDY_EXECUTION_PULL_REQUEST_ID\"},{name:\"Buildkite\",constant:\"BUILDKITE\",env:\"BUILDKITE\",pr:{env:\"BUILDKITE_PULL_REQUEST\",ne:\"false\"}},{name:\"CircleCI\",constant:\"CIRCLE\",env:\"CIRCLECI\",pr:\"CIRCLE_PULL_REQUEST\"},{name:\"Cirrus CI\",constant:\"CIRRUS\",env:\"CIRRUS_CI\",pr:\"CIRRUS_PR\"},{name:\"AWS CodeBuild\",constant:\"CODEBUILD\",env:\"CODEBUILD_BUILD_ARN\"},{name:\"Codeship\",constant:\"CODESHIP\",env:{CI_NAME:\"codeship\"}},{name:\"Drone\",constant:\"DRONE\",env:\"DRONE\",pr:{DRONE_BUILD_EVENT:\"pull_request\"}},{name:\"dsari\",constant:\"DSARI\",env:\"DSARI\"},{name:\"GitLab CI\",constant:\"GITLAB\",env:\"GITLAB_CI\"},{name:\"GoCD\",constant:\"GOCD\",env:\"GO_PIPELINE_LABEL\"},{name:\"Hudson\",constant:\"HUDSON\",env:\"HUDSON_URL\"},{name:\"Jenkins\",constant:\"JENKINS\",env:[\"JENKINS_URL\",\"BUILD_ID\"],pr:{any:[\"ghprbPullId\",\"CHANGE_ID\"]}},{name:\"Magnum CI\",constant:\"MAGNUM\",env:\"MAGNUM\"},{name:\"Netlify CI\",constant:\"NETLIFY\",env:\"NETLIFY_BUILD_BASE\",pr:{env:\"PULL_REQUEST\",ne:\"false\"}},{name:\"Sail CI\",constant:\"SAIL\",env:\"SAILCI\",pr:\"SAIL_PULL_REQUEST_NUMBER\"},{name:\"Semaphore\",constant:\"SEMAPHORE\",env:\"SEMAPHORE\",pr:\"PULL_REQUEST_NUMBER\"},{name:\"Shippable\",constant:\"SHIPPABLE\",env:\"SHIPPABLE\",pr:{IS_PULL_REQUEST:\"true\"}},{name:\"Solano CI\",constant:\"SOLANO\",env:\"TDDIUM\",pr:\"TDDIUM_PR_ID\"},{name:\"Strider CD\",constant:\"STRIDER\",env:\"STRIDER\"},{name:\"TaskCluster\",constant:\"TASKCLUSTER\",env:[\"TASK_ID\",\"RUN_ID\"]},{name:\"TeamCity\",constant:\"TEAMCITY\",env:\"TEAMCITY_VERSION\"},{name:\"Travis CI\",constant:\"TRAVIS\",env:\"TRAVIS\",pr:{env:\"TRAVIS_PULL_REQUEST\",ne:\"false\"}}]});var gDe=L(rc=>{\"use strict\";var hDe=ADe(),AA=process.env;Object.defineProperty(rc,\"_vendors\",{value:hDe.map(function(t){return t.constant})});rc.name=null;rc.isPR=null;hDe.forEach(function(t){var e=Array.isArray(t.env)?t.env:[t.env],r=e.every(function(s){return pDe(s)});if(rc[t.constant]=r,r)switch(rc.name=t.name,typeof t.pr){case\"string\":rc.isPR=!!AA[t.pr];break;case\"object\":\"env\"in t.pr?rc.isPR=t.pr.env in AA&&AA[t.pr.env]!==t.pr.ne:\"any\"in t.pr?rc.isPR=t.pr.any.some(function(s){return!!AA[s]}):rc.isPR=pDe(t.pr);break;default:rc.isPR=null}});rc.isCI=!!(AA.CI||AA.CONTINUOUS_INTEGRATION||AA.BUILD_NUMBER||AA.RUN_ID||rc.name);function pDe(t){return typeof t==\"string\"?!!AA[t]:Object.keys(t).every(function(e){return AA[e]===t[e]})}});var mDe=L((Vpr,dDe)=>{\"use strict\";dDe.exports=gDe().isCI});var EDe=L((Kpr,yDe)=>{\"use strict\";var hPt=t=>{let e=new Set;do for(let r of Reflect.ownKeys(t))e.add([t,r]);while((t=Reflect.getPrototypeOf(t))&&t!==Object.prototype);return e};yDe.exports=(t,{include:e,exclude:r}={})=>{let s=a=>{let n=c=>typeof c==\"string\"?a===c:c.test(a);return e?e.some(n):r?!r.some(n):!0};for(let[a,n]of hPt(t.constructor.prototype)){if(n===\"constructor\"||!s(n))continue;let c=Reflect.getOwnPropertyDescriptor(a,n);c&&typeof c.value==\"function\"&&(t[n]=t[n].bind(t))}return t}});var SDe=L(Vn=>{\"use strict\";var mw,zS,TF,pW;typeof performance==\"object\"&&typeof performance.now==\"function\"?(IDe=performance,Vn.unstable_now=function(){return IDe.now()}):(aW=Date,CDe=aW.now(),Vn.unstable_now=function(){return aW.now()-CDe});var IDe,aW,CDe;typeof window>\"u\"||typeof MessageChannel!=\"function\"?(dw=null,lW=null,cW=function(){if(dw!==null)try{var t=Vn.unstable_now();dw(!0,t),dw=null}catch(e){throw setTimeout(cW,0),e}},mw=function(t){dw!==null?setTimeout(mw,0,t):(dw=t,setTimeout(cW,0))},zS=function(t,e){lW=setTimeout(t,e)},TF=function(){clearTimeout(lW)},Vn.unstable_shouldYield=function(){return!1},pW=Vn.unstable_forceFrameRate=function(){}):(wDe=window.setTimeout,BDe=window.clearTimeout,typeof console<\"u\"&&(vDe=window.cancelAnimationFrame,typeof window.requestAnimationFrame!=\"function\"&&console.error(\"This browser doesn't support requestAnimationFrame. Make sure that you load a polyfill in older browsers. https://reactjs.org/link/react-polyfills\"),typeof vDe!=\"function\"&&console.error(\"This browser doesn't support cancelAnimationFrame. Make sure that you load a polyfill in older browsers. https://reactjs.org/link/react-polyfills\")),KS=!1,JS=null,xF=-1,uW=5,fW=0,Vn.unstable_shouldYield=function(){return Vn.unstable_now()>=fW},pW=function(){},Vn.unstable_forceFrameRate=function(t){0>t||125<t?console.error(\"forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported\"):uW=0<t?Math.floor(1e3/t):5},AW=new MessageChannel,kF=AW.port2,AW.port1.onmessage=function(){if(JS!==null){var t=Vn.unstable_now();fW=t+uW;try{JS(!0,t)?kF.postMessage(null):(KS=!1,JS=null)}catch(e){throw kF.postMessage(null),e}}else KS=!1},mw=function(t){JS=t,KS||(KS=!0,kF.postMessage(null))},zS=function(t,e){xF=wDe(function(){t(Vn.unstable_now())},e)},TF=function(){BDe(xF),xF=-1});var dw,lW,cW,wDe,BDe,vDe,KS,JS,xF,uW,fW,AW,kF;function hW(t,e){var r=t.length;t.push(e);e:for(;;){var s=r-1>>>1,a=t[s];if(a!==void 0&&0<QF(a,e))t[s]=e,t[r]=a,r=s;else break e}}function tf(t){return t=t[0],t===void 0?null:t}function RF(t){var e=t[0];if(e!==void 0){var r=t.pop();if(r!==e){t[0]=r;e:for(var s=0,a=t.length;s<a;){var n=2*(s+1)-1,c=t[n],f=n+1,p=t[f];if(c!==void 0&&0>QF(c,r))p!==void 0&&0>QF(p,c)?(t[s]=p,t[f]=r,s=f):(t[s]=c,t[n]=r,s=n);else if(p!==void 0&&0>QF(p,r))t[s]=p,t[f]=r,s=f;else break e}}return e}return null}function QF(t,e){var r=t.sortIndex-e.sortIndex;return r!==0?r:t.id-e.id}var pA=[],Z0=[],gPt=1,Yc=null,ea=3,FF=!1,Tm=!1,ZS=!1;function gW(t){for(var e=tf(Z0);e!==null;){if(e.callback===null)RF(Z0);else if(e.startTime<=t)RF(Z0),e.sortIndex=e.expirationTime,hW(pA,e);else break;e=tf(Z0)}}function dW(t){if(ZS=!1,gW(t),!Tm)if(tf(pA)!==null)Tm=!0,mw(mW);else{var e=tf(Z0);e!==null&&zS(dW,e.startTime-t)}}function mW(t,e){Tm=!1,ZS&&(ZS=!1,TF()),FF=!0;var r=ea;try{for(gW(e),Yc=tf(pA);Yc!==null&&(!(Yc.expirationTime>e)||t&&!Vn.unstable_shouldYield());){var s=Yc.callback;if(typeof s==\"function\"){Yc.callback=null,ea=Yc.priorityLevel;var a=s(Yc.expirationTime<=e);e=Vn.unstable_now(),typeof a==\"function\"?Yc.callback=a:Yc===tf(pA)&&RF(pA),gW(e)}else RF(pA);Yc=tf(pA)}if(Yc!==null)var n=!0;else{var c=tf(Z0);c!==null&&zS(dW,c.startTime-e),n=!1}return n}finally{Yc=null,ea=r,FF=!1}}var dPt=pW;Vn.unstable_IdlePriority=5;Vn.unstable_ImmediatePriority=1;Vn.unstable_LowPriority=4;Vn.unstable_NormalPriority=3;Vn.unstable_Profiling=null;Vn.unstable_UserBlockingPriority=2;Vn.unstable_cancelCallback=function(t){t.callback=null};Vn.unstable_continueExecution=function(){Tm||FF||(Tm=!0,mw(mW))};Vn.unstable_getCurrentPriorityLevel=function(){return ea};Vn.unstable_getFirstCallbackNode=function(){return tf(pA)};Vn.unstable_next=function(t){switch(ea){case 1:case 2:case 3:var e=3;break;default:e=ea}var r=ea;ea=e;try{return t()}finally{ea=r}};Vn.unstable_pauseExecution=function(){};Vn.unstable_requestPaint=dPt;Vn.unstable_runWithPriority=function(t,e){switch(t){case 1:case 2:case 3:case 4:case 5:break;default:t=3}var r=ea;ea=t;try{return e()}finally{ea=r}};Vn.unstable_scheduleCallback=function(t,e,r){var s=Vn.unstable_now();switch(typeof r==\"object\"&&r!==null?(r=r.delay,r=typeof r==\"number\"&&0<r?s+r:s):r=s,t){case 1:var a=-1;break;case 2:a=250;break;case 5:a=1073741823;break;case 4:a=1e4;break;default:a=5e3}return a=r+a,t={id:gPt++,callback:e,priorityLevel:t,startTime:r,expirationTime:a,sortIndex:-1},r>s?(t.sortIndex=r,hW(Z0,t),tf(pA)===null&&t===tf(Z0)&&(ZS?TF():ZS=!0,zS(dW,r-s))):(t.sortIndex=a,hW(pA,t),Tm||FF||(Tm=!0,mw(mW))),t};Vn.unstable_wrapCallback=function(t){var e=ea;return function(){var r=ea;ea=e;try{return t.apply(this,arguments)}finally{ea=r}}}});var yW=L((zpr,DDe)=>{\"use strict\";DDe.exports=SDe()});var bDe=L((Zpr,XS)=>{XS.exports=function(e){var r={},s=V9(),a=hn(),n=yW();function c(v){for(var D=\"https://reactjs.org/docs/error-decoder.html?invariant=\"+v,Q=1;Q<arguments.length;Q++)D+=\"&args[]=\"+encodeURIComponent(arguments[Q]);return\"Minified React error #\"+v+\"; visit \"+D+\" for the full message or use the non-minified dev environment for full errors and additional helpful warnings.\"}var f=a.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,p=60103,h=60106,E=60107,C=60108,S=60114,P=60109,I=60110,R=60112,N=60113,U=60120,W=60115,te=60116,ie=60121,Ae=60129,ce=60130,me=60131;if(typeof Symbol==\"function\"&&Symbol.for){var pe=Symbol.for;p=pe(\"react.element\"),h=pe(\"react.portal\"),E=pe(\"react.fragment\"),C=pe(\"react.strict_mode\"),S=pe(\"react.profiler\"),P=pe(\"react.provider\"),I=pe(\"react.context\"),R=pe(\"react.forward_ref\"),N=pe(\"react.suspense\"),U=pe(\"react.suspense_list\"),W=pe(\"react.memo\"),te=pe(\"react.lazy\"),ie=pe(\"react.block\"),pe(\"react.scope\"),Ae=pe(\"react.debug_trace_mode\"),ce=pe(\"react.offscreen\"),me=pe(\"react.legacy_hidden\")}var Be=typeof Symbol==\"function\"&&Symbol.iterator;function Ce(v){return v===null||typeof v!=\"object\"?null:(v=Be&&v[Be]||v[\"@@iterator\"],typeof v==\"function\"?v:null)}function g(v){if(v==null)return null;if(typeof v==\"function\")return v.displayName||v.name||null;if(typeof v==\"string\")return v;switch(v){case E:return\"Fragment\";case h:return\"Portal\";case S:return\"Profiler\";case C:return\"StrictMode\";case N:return\"Suspense\";case U:return\"SuspenseList\"}if(typeof v==\"object\")switch(v.$$typeof){case I:return(v.displayName||\"Context\")+\".Consumer\";case P:return(v._context.displayName||\"Context\")+\".Provider\";case R:var D=v.render;return D=D.displayName||D.name||\"\",v.displayName||(D!==\"\"?\"ForwardRef(\"+D+\")\":\"ForwardRef\");case W:return g(v.type);case ie:return g(v._render);case te:D=v._payload,v=v._init;try{return g(v(D))}catch{}}return null}function we(v){var D=v,Q=v;if(v.alternate)for(;D.return;)D=D.return;else{v=D;do D=v,D.flags&1026&&(Q=D.return),v=D.return;while(v)}return D.tag===3?Q:null}function ye(v){if(we(v)!==v)throw Error(c(188))}function fe(v){var D=v.alternate;if(!D){if(D=we(v),D===null)throw Error(c(188));return D!==v?null:v}for(var Q=v,H=D;;){var V=Q.return;if(V===null)break;var ne=V.alternate;if(ne===null){if(H=V.return,H!==null){Q=H;continue}break}if(V.child===ne.child){for(ne=V.child;ne;){if(ne===Q)return ye(V),v;if(ne===H)return ye(V),D;ne=ne.sibling}throw Error(c(188))}if(Q.return!==H.return)Q=V,H=ne;else{for(var Se=!1,Ue=V.child;Ue;){if(Ue===Q){Se=!0,Q=V,H=ne;break}if(Ue===H){Se=!0,H=V,Q=ne;break}Ue=Ue.sibling}if(!Se){for(Ue=ne.child;Ue;){if(Ue===Q){Se=!0,Q=ne,H=V;break}if(Ue===H){Se=!0,H=ne,Q=V;break}Ue=Ue.sibling}if(!Se)throw Error(c(189))}}if(Q.alternate!==H)throw Error(c(190))}if(Q.tag!==3)throw Error(c(188));return Q.stateNode.current===Q?v:D}function se(v){if(v=fe(v),!v)return null;for(var D=v;;){if(D.tag===5||D.tag===6)return D;if(D.child)D.child.return=D,D=D.child;else{if(D===v)break;for(;!D.sibling;){if(!D.return||D.return===v)return null;D=D.return}D.sibling.return=D.return,D=D.sibling}}return null}function X(v){if(v=fe(v),!v)return null;for(var D=v;;){if(D.tag===5||D.tag===6)return D;if(D.child&&D.tag!==4)D.child.return=D,D=D.child;else{if(D===v)break;for(;!D.sibling;){if(!D.return||D.return===v)return null;D=D.return}D.sibling.return=D.return,D=D.sibling}}return null}function De(v,D){for(var Q=v.alternate;D!==null;){if(D===v||D===Q)return!0;D=D.return}return!1}var Re=e.getPublicInstance,dt=e.getRootHostContext,j=e.getChildHostContext,rt=e.prepareForCommit,Fe=e.resetAfterCommit,Ne=e.createInstance,Pe=e.appendInitialChild,Ye=e.finalizeInitialChildren,ke=e.prepareUpdate,it=e.shouldSetTextContent,_e=e.createTextInstance,x=e.scheduleTimeout,w=e.cancelTimeout,b=e.noTimeout,y=e.isPrimaryRenderer,F=e.supportsMutation,z=e.supportsPersistence,Z=e.supportsHydration,$=e.getInstanceFromNode,oe=e.makeOpaqueHydratingObject,xe=e.makeClientId,Te=e.beforeActiveInstanceBlur,lt=e.afterActiveInstanceBlur,It=e.preparePortalMount,qt=e.supportsTestSelectors,ir=e.findFiberRoot,Pt=e.getBoundingRect,gn=e.getTextContent,Pr=e.isHiddenSubtree,Ir=e.matchAccessibilityRole,Nr=e.setFocusIfFocusable,nn=e.setupIntersectionObserver,ai=e.appendChild,wo=e.appendChildToContainer,ns=e.commitTextUpdate,to=e.commitMount,Bo=e.commitUpdate,ji=e.insertBefore,ro=e.insertInContainerBefore,vo=e.removeChild,RA=e.removeChildFromContainer,pf=e.resetTextContent,yh=e.hideInstance,Eh=e.hideTextInstance,no=e.unhideInstance,jn=e.unhideTextInstance,Fs=e.clearContainer,io=e.cloneInstance,lu=e.createContainerChildSet,cu=e.appendChildToContainerChildSet,uu=e.finalizeContainerChildren,FA=e.replaceContainerChildren,NA=e.cloneHiddenInstance,aa=e.cloneHiddenTextInstance,la=e.canHydrateInstance,OA=e.canHydrateTextInstance,gr=e.isSuspenseInstancePending,So=e.isSuspenseInstanceFallback,Me=e.getNextHydratableSibling,fu=e.getFirstHydratableChild,Cr=e.hydrateInstance,hf=e.hydrateTextInstance,LA=e.getNextHydratableInstanceAfterSuspenseInstance,MA=e.commitHydratedContainer,Au=e.commitHydratedSuspenseInstance,pu;function ac(v){if(pu===void 0)try{throw Error()}catch(Q){var D=Q.stack.trim().match(/\\n( *(at )?)/);pu=D&&D[1]||\"\"}return`\n`+pu+v}var ve=!1;function Nt(v,D){if(!v||ve)return\"\";ve=!0;var Q=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{if(D)if(D=function(){throw Error()},Object.defineProperty(D.prototype,\"props\",{set:function(){throw Error()}}),typeof Reflect==\"object\"&&Reflect.construct){try{Reflect.construct(D,[])}catch(At){var H=At}Reflect.construct(v,[],D)}else{try{D.call()}catch(At){H=At}v.call(D.prototype)}else{try{throw Error()}catch(At){H=At}v()}}catch(At){if(At&&H&&typeof At.stack==\"string\"){for(var V=At.stack.split(`\n`),ne=H.stack.split(`\n`),Se=V.length-1,Ue=ne.length-1;1<=Se&&0<=Ue&&V[Se]!==ne[Ue];)Ue--;for(;1<=Se&&0<=Ue;Se--,Ue--)if(V[Se]!==ne[Ue]){if(Se!==1||Ue!==1)do if(Se--,Ue--,0>Ue||V[Se]!==ne[Ue])return`\n`+V[Se].replace(\" at new \",\" at \");while(1<=Se&&0<=Ue);break}}}finally{ve=!1,Error.prepareStackTrace=Q}return(v=v?v.displayName||v.name:\"\")?ac(v):\"\"}var lc=[],Li=-1;function so(v){return{current:v}}function Rt(v){0>Li||(v.current=lc[Li],lc[Li]=null,Li--)}function xn(v,D){Li++,lc[Li]=v.current,v.current=D}var ca={},qi=so(ca),Mi=so(!1),Oa=ca;function dn(v,D){var Q=v.type.contextTypes;if(!Q)return ca;var H=v.stateNode;if(H&&H.__reactInternalMemoizedUnmaskedChildContext===D)return H.__reactInternalMemoizedMaskedChildContext;var V={},ne;for(ne in Q)V[ne]=D[ne];return H&&(v=v.stateNode,v.__reactInternalMemoizedUnmaskedChildContext=D,v.__reactInternalMemoizedMaskedChildContext=V),V}function Jn(v){return v=v.childContextTypes,v!=null}function hu(){Rt(Mi),Rt(qi)}function Ih(v,D,Q){if(qi.current!==ca)throw Error(c(168));xn(qi,D),xn(Mi,Q)}function La(v,D,Q){var H=v.stateNode;if(v=D.childContextTypes,typeof H.getChildContext!=\"function\")return Q;H=H.getChildContext();for(var V in H)if(!(V in v))throw Error(c(108,g(D)||\"Unknown\",V));return s({},Q,H)}function Ma(v){return v=(v=v.stateNode)&&v.__reactInternalMemoizedMergedChildContext||ca,Oa=qi.current,xn(qi,v),xn(Mi,Mi.current),!0}function Ua(v,D,Q){var H=v.stateNode;if(!H)throw Error(c(169));Q?(v=La(v,D,Oa),H.__reactInternalMemoizedMergedChildContext=v,Rt(Mi),Rt(qi),xn(qi,v)):Rt(Mi),xn(Mi,Q)}var Xe=null,Ha=null,gf=n.unstable_now;gf();var cc=0,wn=8;function ua(v){if(1&v)return wn=15,1;if(2&v)return wn=14,2;if(4&v)return wn=13,4;var D=24&v;return D!==0?(wn=12,D):v&32?(wn=11,32):(D=192&v,D!==0?(wn=10,D):v&256?(wn=9,256):(D=3584&v,D!==0?(wn=8,D):v&4096?(wn=7,4096):(D=4186112&v,D!==0?(wn=6,D):(D=62914560&v,D!==0?(wn=5,D):v&67108864?(wn=4,67108864):v&134217728?(wn=3,134217728):(D=805306368&v,D!==0?(wn=2,D):1073741824&v?(wn=1,1073741824):(wn=8,v))))))}function _A(v){switch(v){case 99:return 15;case 98:return 10;case 97:case 96:return 8;case 95:return 2;default:return 0}}function UA(v){switch(v){case 15:case 14:return 99;case 13:case 12:case 11:case 10:return 98;case 9:case 8:case 7:case 6:case 4:case 5:return 97;case 3:case 2:case 1:return 95;case 0:return 90;default:throw Error(c(358,v))}}function fa(v,D){var Q=v.pendingLanes;if(Q===0)return wn=0;var H=0,V=0,ne=v.expiredLanes,Se=v.suspendedLanes,Ue=v.pingedLanes;if(ne!==0)H=ne,V=wn=15;else if(ne=Q&134217727,ne!==0){var At=ne&~Se;At!==0?(H=ua(At),V=wn):(Ue&=ne,Ue!==0&&(H=ua(Ue),V=wn))}else ne=Q&~Se,ne!==0?(H=ua(ne),V=wn):Ue!==0&&(H=ua(Ue),V=wn);if(H===0)return 0;if(H=31-is(H),H=Q&((0>H?0:1<<H)<<1)-1,D!==0&&D!==H&&!(D&Se)){if(ua(D),V<=wn)return D;wn=V}if(D=v.entangledLanes,D!==0)for(v=v.entanglements,D&=H;0<D;)Q=31-is(D),V=1<<Q,H|=v[Q],D&=~V;return H}function vl(v){return v=v.pendingLanes&-1073741825,v!==0?v:v&1073741824?1073741824:0}function Mt(v,D){switch(v){case 15:return 1;case 14:return 2;case 12:return v=kn(24&~D),v===0?Mt(10,D):v;case 10:return v=kn(192&~D),v===0?Mt(8,D):v;case 8:return v=kn(3584&~D),v===0&&(v=kn(4186112&~D),v===0&&(v=512)),v;case 2:return D=kn(805306368&~D),D===0&&(D=268435456),D}throw Error(c(358,v))}function kn(v){return v&-v}function Aa(v){for(var D=[],Q=0;31>Q;Q++)D.push(v);return D}function ja(v,D,Q){v.pendingLanes|=D;var H=D-1;v.suspendedLanes&=H,v.pingedLanes&=H,v=v.eventTimes,D=31-is(D),v[D]=Q}var is=Math.clz32?Math.clz32:fc,uc=Math.log,gu=Math.LN2;function fc(v){return v===0?32:31-(uc(v)/gu|0)|0}var qa=n.unstable_runWithPriority,_i=n.unstable_scheduleCallback,ws=n.unstable_cancelCallback,Sl=n.unstable_shouldYield,df=n.unstable_requestPaint,Ac=n.unstable_now,Bi=n.unstable_getCurrentPriorityLevel,Qn=n.unstable_ImmediatePriority,pc=n.unstable_UserBlockingPriority,Je=n.unstable_NormalPriority,st=n.unstable_LowPriority,St=n.unstable_IdlePriority,lr={},ee=df!==void 0?df:function(){},Ee=null,Oe=null,gt=!1,yt=Ac(),Dt=1e4>yt?Ac:function(){return Ac()-yt};function tr(){switch(Bi()){case Qn:return 99;case pc:return 98;case Je:return 97;case st:return 96;case St:return 95;default:throw Error(c(332))}}function fn(v){switch(v){case 99:return Qn;case 98:return pc;case 97:return Je;case 96:return st;case 95:return St;default:throw Error(c(332))}}function li(v,D){return v=fn(v),qa(v,D)}function Gi(v,D,Q){return v=fn(v),_i(v,D,Q)}function Tn(){if(Oe!==null){var v=Oe;Oe=null,ws(v)}Ga()}function Ga(){if(!gt&&Ee!==null){gt=!0;var v=0;try{var D=Ee;li(99,function(){for(;v<D.length;v++){var Q=D[v];do Q=Q(!0);while(Q!==null)}}),Ee=null}catch(Q){throw Ee!==null&&(Ee=Ee.slice(v+1)),_i(Qn,Tn),Q}finally{gt=!1}}}var gy=f.ReactCurrentBatchConfig;function X1(v,D){return v===D&&(v!==0||1/v===1/D)||v!==v&&D!==D}var Do=typeof Object.is==\"function\"?Object.is:X1,dy=Object.prototype.hasOwnProperty;function Ch(v,D){if(Do(v,D))return!0;if(typeof v!=\"object\"||v===null||typeof D!=\"object\"||D===null)return!1;var Q=Object.keys(v),H=Object.keys(D);if(Q.length!==H.length)return!1;for(H=0;H<Q.length;H++)if(!dy.call(D,Q[H])||!Do(v[Q[H]],D[Q[H]]))return!1;return!0}function $1(v){switch(v.tag){case 5:return ac(v.type);case 16:return ac(\"Lazy\");case 13:return ac(\"Suspense\");case 19:return ac(\"SuspenseList\");case 0:case 2:case 15:return v=Nt(v.type,!1),v;case 11:return v=Nt(v.type.render,!1),v;case 22:return v=Nt(v.type._render,!1),v;case 1:return v=Nt(v.type,!0),v;default:return\"\"}}function bo(v,D){if(v&&v.defaultProps){D=s({},D),v=v.defaultProps;for(var Q in v)D[Q]===void 0&&(D[Q]=v[Q]);return D}return D}var wh=so(null),Bh=null,du=null,vh=null;function Rg(){vh=du=Bh=null}function Fg(v,D){v=v.type._context,y?(xn(wh,v._currentValue),v._currentValue=D):(xn(wh,v._currentValue2),v._currentValue2=D)}function Ng(v){var D=wh.current;Rt(wh),v=v.type._context,y?v._currentValue=D:v._currentValue2=D}function my(v,D){for(;v!==null;){var Q=v.alternate;if((v.childLanes&D)===D){if(Q===null||(Q.childLanes&D)===D)break;Q.childLanes|=D}else v.childLanes|=D,Q!==null&&(Q.childLanes|=D);v=v.return}}function mf(v,D){Bh=v,vh=du=null,v=v.dependencies,v!==null&&v.firstContext!==null&&(v.lanes&D&&(Ke=!0),v.firstContext=null)}function Po(v,D){if(vh!==v&&D!==!1&&D!==0)if((typeof D!=\"number\"||D===1073741823)&&(vh=v,D=1073741823),D={context:v,observedBits:D,next:null},du===null){if(Bh===null)throw Error(c(308));du=D,Bh.dependencies={lanes:0,firstContext:D,responders:null}}else du=du.next=D;return y?v._currentValue:v._currentValue2}var Dl=!1;function Sh(v){v.updateQueue={baseState:v.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null},effects:null}}function Og(v,D){v=v.updateQueue,D.updateQueue===v&&(D.updateQueue={baseState:v.baseState,firstBaseUpdate:v.firstBaseUpdate,lastBaseUpdate:v.lastBaseUpdate,shared:v.shared,effects:v.effects})}function bl(v,D){return{eventTime:v,lane:D,tag:0,payload:null,callback:null,next:null}}function Pl(v,D){if(v=v.updateQueue,v!==null){v=v.shared;var Q=v.pending;Q===null?D.next=D:(D.next=Q.next,Q.next=D),v.pending=D}}function yy(v,D){var Q=v.updateQueue,H=v.alternate;if(H!==null&&(H=H.updateQueue,Q===H)){var V=null,ne=null;if(Q=Q.firstBaseUpdate,Q!==null){do{var Se={eventTime:Q.eventTime,lane:Q.lane,tag:Q.tag,payload:Q.payload,callback:Q.callback,next:null};ne===null?V=ne=Se:ne=ne.next=Se,Q=Q.next}while(Q!==null);ne===null?V=ne=D:ne=ne.next=D}else V=ne=D;Q={baseState:H.baseState,firstBaseUpdate:V,lastBaseUpdate:ne,shared:H.shared,effects:H.effects},v.updateQueue=Q;return}v=Q.lastBaseUpdate,v===null?Q.firstBaseUpdate=D:v.next=D,Q.lastBaseUpdate=D}function HA(v,D,Q,H){var V=v.updateQueue;Dl=!1;var ne=V.firstBaseUpdate,Se=V.lastBaseUpdate,Ue=V.shared.pending;if(Ue!==null){V.shared.pending=null;var At=Ue,Gt=At.next;At.next=null,Se===null?ne=Gt:Se.next=Gt,Se=At;var vr=v.alternate;if(vr!==null){vr=vr.updateQueue;var Lr=vr.lastBaseUpdate;Lr!==Se&&(Lr===null?vr.firstBaseUpdate=Gt:Lr.next=Gt,vr.lastBaseUpdate=At)}}if(ne!==null){Lr=V.baseState,Se=0,vr=Gt=At=null;do{Ue=ne.lane;var Xt=ne.eventTime;if((H&Ue)===Ue){vr!==null&&(vr=vr.next={eventTime:Xt,lane:0,tag:ne.tag,payload:ne.payload,callback:ne.callback,next:null});e:{var zn=v,yi=ne;switch(Ue=D,Xt=Q,yi.tag){case 1:if(zn=yi.payload,typeof zn==\"function\"){Lr=zn.call(Xt,Lr,Ue);break e}Lr=zn;break e;case 3:zn.flags=zn.flags&-4097|64;case 0:if(zn=yi.payload,Ue=typeof zn==\"function\"?zn.call(Xt,Lr,Ue):zn,Ue==null)break e;Lr=s({},Lr,Ue);break e;case 2:Dl=!0}}ne.callback!==null&&(v.flags|=32,Ue=V.effects,Ue===null?V.effects=[ne]:Ue.push(ne))}else Xt={eventTime:Xt,lane:Ue,tag:ne.tag,payload:ne.payload,callback:ne.callback,next:null},vr===null?(Gt=vr=Xt,At=Lr):vr=vr.next=Xt,Se|=Ue;if(ne=ne.next,ne===null){if(Ue=V.shared.pending,Ue===null)break;ne=Ue.next,Ue.next=null,V.lastBaseUpdate=Ue,V.shared.pending=null}}while(!0);vr===null&&(At=Lr),V.baseState=At,V.firstBaseUpdate=Gt,V.lastBaseUpdate=vr,Zg|=Se,v.lanes=Se,v.memoizedState=Lr}}function Ey(v,D,Q){if(v=D.effects,D.effects=null,v!==null)for(D=0;D<v.length;D++){var H=v[D],V=H.callback;if(V!==null){if(H.callback=null,H=Q,typeof V!=\"function\")throw Error(c(191,V));V.call(H)}}}var Iy=new a.Component().refs;function jA(v,D,Q,H){D=v.memoizedState,Q=Q(H,D),Q=Q==null?D:s({},D,Q),v.memoizedState=Q,v.lanes===0&&(v.updateQueue.baseState=Q)}var qA={isMounted:function(v){return(v=v._reactInternals)?we(v)===v:!1},enqueueSetState:function(v,D,Q){v=v._reactInternals;var H=To(),V=Ss(v),ne=bl(H,V);ne.payload=D,Q!=null&&(ne.callback=Q),Pl(v,ne),Rl(v,V,H)},enqueueReplaceState:function(v,D,Q){v=v._reactInternals;var H=To(),V=Ss(v),ne=bl(H,V);ne.tag=1,ne.payload=D,Q!=null&&(ne.callback=Q),Pl(v,ne),Rl(v,V,H)},enqueueForceUpdate:function(v,D){v=v._reactInternals;var Q=To(),H=Ss(v),V=bl(Q,H);V.tag=2,D!=null&&(V.callback=D),Pl(v,V),Rl(v,H,Q)}};function Y(v,D,Q,H,V,ne,Se){return v=v.stateNode,typeof v.shouldComponentUpdate==\"function\"?v.shouldComponentUpdate(H,ne,Se):D.prototype&&D.prototype.isPureReactComponent?!Ch(Q,H)||!Ch(V,ne):!0}function xt(v,D,Q){var H=!1,V=ca,ne=D.contextType;return typeof ne==\"object\"&&ne!==null?ne=Po(ne):(V=Jn(D)?Oa:qi.current,H=D.contextTypes,ne=(H=H!=null)?dn(v,V):ca),D=new D(Q,ne),v.memoizedState=D.state!==null&&D.state!==void 0?D.state:null,D.updater=qA,v.stateNode=D,D._reactInternals=v,H&&(v=v.stateNode,v.__reactInternalMemoizedUnmaskedChildContext=V,v.__reactInternalMemoizedMaskedChildContext=ne),D}function GA(v,D,Q,H){v=D.state,typeof D.componentWillReceiveProps==\"function\"&&D.componentWillReceiveProps(Q,H),typeof D.UNSAFE_componentWillReceiveProps==\"function\"&&D.UNSAFE_componentWillReceiveProps(Q,H),D.state!==v&&qA.enqueueReplaceState(D,D.state,null)}function xo(v,D,Q,H){var V=v.stateNode;V.props=Q,V.state=v.memoizedState,V.refs=Iy,Sh(v);var ne=D.contextType;typeof ne==\"object\"&&ne!==null?V.context=Po(ne):(ne=Jn(D)?Oa:qi.current,V.context=dn(v,ne)),HA(v,Q,V,H),V.state=v.memoizedState,ne=D.getDerivedStateFromProps,typeof ne==\"function\"&&(jA(v,D,ne,Q),V.state=v.memoizedState),typeof D.getDerivedStateFromProps==\"function\"||typeof V.getSnapshotBeforeUpdate==\"function\"||typeof V.UNSAFE_componentWillMount!=\"function\"&&typeof V.componentWillMount!=\"function\"||(D=V.state,typeof V.componentWillMount==\"function\"&&V.componentWillMount(),typeof V.UNSAFE_componentWillMount==\"function\"&&V.UNSAFE_componentWillMount(),D!==V.state&&qA.enqueueReplaceState(V,V.state,null),HA(v,Q,V,H),V.state=v.memoizedState),typeof V.componentDidMount==\"function\"&&(v.flags|=4)}var yf=Array.isArray;function mt(v,D,Q){if(v=Q.ref,v!==null&&typeof v!=\"function\"&&typeof v!=\"object\"){if(Q._owner){if(Q=Q._owner,Q){if(Q.tag!==1)throw Error(c(309));var H=Q.stateNode}if(!H)throw Error(c(147,v));var V=\"\"+v;return D!==null&&D.ref!==null&&typeof D.ref==\"function\"&&D.ref._stringRef===V?D.ref:(D=function(ne){var Se=H.refs;Se===Iy&&(Se=H.refs={}),ne===null?delete Se[V]:Se[V]=ne},D._stringRef=V,D)}if(typeof v!=\"string\")throw Error(c(284));if(!Q._owner)throw Error(c(290,v))}return v}function mu(v,D){if(v.type!==\"textarea\")throw Error(c(31,Object.prototype.toString.call(D)===\"[object Object]\"?\"object with keys {\"+Object.keys(D).join(\", \")+\"}\":D))}function Cy(v){function D($e,qe){if(v){var ht=$e.lastEffect;ht!==null?(ht.nextEffect=qe,$e.lastEffect=qe):$e.firstEffect=$e.lastEffect=qe,qe.nextEffect=null,qe.flags=8}}function Q($e,qe){if(!v)return null;for(;qe!==null;)D($e,qe),qe=qe.sibling;return null}function H($e,qe){for($e=new Map;qe!==null;)qe.key!==null?$e.set(qe.key,qe):$e.set(qe.index,qe),qe=qe.sibling;return $e}function V($e,qe){return $e=bu($e,qe),$e.index=0,$e.sibling=null,$e}function ne($e,qe,ht){return $e.index=ht,v?(ht=$e.alternate,ht!==null?(ht=ht.index,ht<qe?($e.flags=2,qe):ht):($e.flags=2,qe)):qe}function Se($e){return v&&$e.alternate===null&&($e.flags=2),$e}function Ue($e,qe,ht,Zt){return qe===null||qe.tag!==6?(qe=b2(ht,$e.mode,Zt),qe.return=$e,qe):(qe=V(qe,ht),qe.return=$e,qe)}function At($e,qe,ht,Zt){return qe!==null&&qe.elementType===ht.type?(Zt=V(qe,ht.props),Zt.ref=mt($e,qe,ht),Zt.return=$e,Zt):(Zt=id(ht.type,ht.key,ht.props,null,$e.mode,Zt),Zt.ref=mt($e,qe,ht),Zt.return=$e,Zt)}function Gt($e,qe,ht,Zt){return qe===null||qe.tag!==4||qe.stateNode.containerInfo!==ht.containerInfo||qe.stateNode.implementation!==ht.implementation?(qe=Ro(ht,$e.mode,Zt),qe.return=$e,qe):(qe=V(qe,ht.children||[]),qe.return=$e,qe)}function vr($e,qe,ht,Zt,Sr){return qe===null||qe.tag!==7?(qe=Qf(ht,$e.mode,Zt,Sr),qe.return=$e,qe):(qe=V(qe,ht),qe.return=$e,qe)}function Lr($e,qe,ht){if(typeof qe==\"string\"||typeof qe==\"number\")return qe=b2(\"\"+qe,$e.mode,ht),qe.return=$e,qe;if(typeof qe==\"object\"&&qe!==null){switch(qe.$$typeof){case p:return ht=id(qe.type,qe.key,qe.props,null,$e.mode,ht),ht.ref=mt($e,null,qe),ht.return=$e,ht;case h:return qe=Ro(qe,$e.mode,ht),qe.return=$e,qe}if(yf(qe)||Ce(qe))return qe=Qf(qe,$e.mode,ht,null),qe.return=$e,qe;mu($e,qe)}return null}function Xt($e,qe,ht,Zt){var Sr=qe!==null?qe.key:null;if(typeof ht==\"string\"||typeof ht==\"number\")return Sr!==null?null:Ue($e,qe,\"\"+ht,Zt);if(typeof ht==\"object\"&&ht!==null){switch(ht.$$typeof){case p:return ht.key===Sr?ht.type===E?vr($e,qe,ht.props.children,Zt,Sr):At($e,qe,ht,Zt):null;case h:return ht.key===Sr?Gt($e,qe,ht,Zt):null}if(yf(ht)||Ce(ht))return Sr!==null?null:vr($e,qe,ht,Zt,null);mu($e,ht)}return null}function zn($e,qe,ht,Zt,Sr){if(typeof Zt==\"string\"||typeof Zt==\"number\")return $e=$e.get(ht)||null,Ue(qe,$e,\"\"+Zt,Sr);if(typeof Zt==\"object\"&&Zt!==null){switch(Zt.$$typeof){case p:return $e=$e.get(Zt.key===null?ht:Zt.key)||null,Zt.type===E?vr(qe,$e,Zt.props.children,Sr,Zt.key):At(qe,$e,Zt,Sr);case h:return $e=$e.get(Zt.key===null?ht:Zt.key)||null,Gt(qe,$e,Zt,Sr)}if(yf(Zt)||Ce(Zt))return $e=$e.get(ht)||null,vr(qe,$e,Zt,Sr,null);mu(qe,Zt)}return null}function yi($e,qe,ht,Zt){for(var Sr=null,Xn=null,kr=qe,Rn=qe=0,Un=null;kr!==null&&Rn<ht.length;Rn++){kr.index>Rn?(Un=kr,kr=null):Un=kr.sibling;var zr=Xt($e,kr,ht[Rn],Zt);if(zr===null){kr===null&&(kr=Un);break}v&&kr&&zr.alternate===null&&D($e,kr),qe=ne(zr,qe,Rn),Xn===null?Sr=zr:Xn.sibling=zr,Xn=zr,kr=Un}if(Rn===ht.length)return Q($e,kr),Sr;if(kr===null){for(;Rn<ht.length;Rn++)kr=Lr($e,ht[Rn],Zt),kr!==null&&(qe=ne(kr,qe,Rn),Xn===null?Sr=kr:Xn.sibling=kr,Xn=kr);return Sr}for(kr=H($e,kr);Rn<ht.length;Rn++)Un=zn(kr,$e,Rn,ht[Rn],Zt),Un!==null&&(v&&Un.alternate!==null&&kr.delete(Un.key===null?Rn:Un.key),qe=ne(Un,qe,Rn),Xn===null?Sr=Un:Xn.sibling=Un,Xn=Un);return v&&kr.forEach(function(ci){return D($e,ci)}),Sr}function Za($e,qe,ht,Zt){var Sr=Ce(ht);if(typeof Sr!=\"function\")throw Error(c(150));if(ht=Sr.call(ht),ht==null)throw Error(c(151));for(var Xn=Sr=null,kr=qe,Rn=qe=0,Un=null,zr=ht.next();kr!==null&&!zr.done;Rn++,zr=ht.next()){kr.index>Rn?(Un=kr,kr=null):Un=kr.sibling;var ci=Xt($e,kr,zr.value,Zt);if(ci===null){kr===null&&(kr=Un);break}v&&kr&&ci.alternate===null&&D($e,kr),qe=ne(ci,qe,Rn),Xn===null?Sr=ci:Xn.sibling=ci,Xn=ci,kr=Un}if(zr.done)return Q($e,kr),Sr;if(kr===null){for(;!zr.done;Rn++,zr=ht.next())zr=Lr($e,zr.value,Zt),zr!==null&&(qe=ne(zr,qe,Rn),Xn===null?Sr=zr:Xn.sibling=zr,Xn=zr);return Sr}for(kr=H($e,kr);!zr.done;Rn++,zr=ht.next())zr=zn(kr,$e,Rn,zr.value,Zt),zr!==null&&(v&&zr.alternate!==null&&kr.delete(zr.key===null?Rn:zr.key),qe=ne(zr,qe,Rn),Xn===null?Sr=zr:Xn.sibling=zr,Xn=zr);return v&&kr.forEach(function(Pu){return D($e,Pu)}),Sr}return function($e,qe,ht,Zt){var Sr=typeof ht==\"object\"&&ht!==null&&ht.type===E&&ht.key===null;Sr&&(ht=ht.props.children);var Xn=typeof ht==\"object\"&&ht!==null;if(Xn)switch(ht.$$typeof){case p:e:{for(Xn=ht.key,Sr=qe;Sr!==null;){if(Sr.key===Xn){switch(Sr.tag){case 7:if(ht.type===E){Q($e,Sr.sibling),qe=V(Sr,ht.props.children),qe.return=$e,$e=qe;break e}break;default:if(Sr.elementType===ht.type){Q($e,Sr.sibling),qe=V(Sr,ht.props),qe.ref=mt($e,Sr,ht),qe.return=$e,$e=qe;break e}}Q($e,Sr);break}else D($e,Sr);Sr=Sr.sibling}ht.type===E?(qe=Qf(ht.props.children,$e.mode,Zt,ht.key),qe.return=$e,$e=qe):(Zt=id(ht.type,ht.key,ht.props,null,$e.mode,Zt),Zt.ref=mt($e,qe,ht),Zt.return=$e,$e=Zt)}return Se($e);case h:e:{for(Sr=ht.key;qe!==null;){if(qe.key===Sr)if(qe.tag===4&&qe.stateNode.containerInfo===ht.containerInfo&&qe.stateNode.implementation===ht.implementation){Q($e,qe.sibling),qe=V(qe,ht.children||[]),qe.return=$e,$e=qe;break e}else{Q($e,qe);break}else D($e,qe);qe=qe.sibling}qe=Ro(ht,$e.mode,Zt),qe.return=$e,$e=qe}return Se($e)}if(typeof ht==\"string\"||typeof ht==\"number\")return ht=\"\"+ht,qe!==null&&qe.tag===6?(Q($e,qe.sibling),qe=V(qe,ht),qe.return=$e,$e=qe):(Q($e,qe),qe=b2(ht,$e.mode,Zt),qe.return=$e,$e=qe),Se($e);if(yf(ht))return yi($e,qe,ht,Zt);if(Ce(ht))return Za($e,qe,ht,Zt);if(Xn&&mu($e,ht),typeof ht>\"u\"&&!Sr)switch($e.tag){case 1:case 22:case 0:case 11:case 15:throw Error(c(152,g($e.type)||\"Component\"))}return Q($e,qe)}}var Lg=Cy(!0),e2=Cy(!1),Dh={},ur=so(Dh),Zi=so(Dh),Ef=so(Dh);function Wa(v){if(v===Dh)throw Error(c(174));return v}function Mg(v,D){xn(Ef,D),xn(Zi,v),xn(ur,Dh),v=dt(D),Rt(ur),xn(ur,v)}function yu(){Rt(ur),Rt(Zi),Rt(Ef)}function If(v){var D=Wa(Ef.current),Q=Wa(ur.current);D=j(Q,v.type,D),Q!==D&&(xn(Zi,v),xn(ur,D))}function wt(v){Zi.current===v&&(Rt(ur),Rt(Zi))}var di=so(0);function WA(v){for(var D=v;D!==null;){if(D.tag===13){var Q=D.memoizedState;if(Q!==null&&(Q=Q.dehydrated,Q===null||gr(Q)||So(Q)))return D}else if(D.tag===19&&D.memoizedProps.revealOrder!==void 0){if(D.flags&64)return D}else if(D.child!==null){D.child.return=D,D=D.child;continue}if(D===v)break;for(;D.sibling===null;){if(D.return===null||D.return===v)return null;D=D.return}D.sibling.return=D.return,D=D.sibling}return null}var Ya=null,pa=null,Va=!1;function _g(v,D){var Q=za(5,null,null,0);Q.elementType=\"DELETED\",Q.type=\"DELETED\",Q.stateNode=D,Q.return=v,Q.flags=8,v.lastEffect!==null?(v.lastEffect.nextEffect=Q,v.lastEffect=Q):v.firstEffect=v.lastEffect=Q}function bh(v,D){switch(v.tag){case 5:return D=la(D,v.type,v.pendingProps),D!==null?(v.stateNode=D,!0):!1;case 6:return D=OA(D,v.pendingProps),D!==null?(v.stateNode=D,!0):!1;case 13:return!1;default:return!1}}function Ug(v){if(Va){var D=pa;if(D){var Q=D;if(!bh(v,D)){if(D=Me(Q),!D||!bh(v,D)){v.flags=v.flags&-1025|2,Va=!1,Ya=v;return}_g(Ya,Q)}Ya=v,pa=fu(D)}else v.flags=v.flags&-1025|2,Va=!1,Ya=v}}function wy(v){for(v=v.return;v!==null&&v.tag!==5&&v.tag!==3&&v.tag!==13;)v=v.return;Ya=v}function YA(v){if(!Z||v!==Ya)return!1;if(!Va)return wy(v),Va=!0,!1;var D=v.type;if(v.tag!==5||D!==\"head\"&&D!==\"body\"&&!it(D,v.memoizedProps))for(D=pa;D;)_g(v,D),D=Me(D);if(wy(v),v.tag===13){if(!Z)throw Error(c(316));if(v=v.memoizedState,v=v!==null?v.dehydrated:null,!v)throw Error(c(317));pa=LA(v)}else pa=Ya?Me(v.stateNode):null;return!0}function Hg(){Z&&(pa=Ya=null,Va=!1)}var Eu=[];function Iu(){for(var v=0;v<Eu.length;v++){var D=Eu[v];y?D._workInProgressVersionPrimary=null:D._workInProgressVersionSecondary=null}Eu.length=0}var Cf=f.ReactCurrentDispatcher,Ns=f.ReactCurrentBatchConfig,Cu=0,qn=null,ss=null,ki=null,VA=!1,wf=!1;function mn(){throw Error(c(321))}function jg(v,D){if(D===null)return!1;for(var Q=0;Q<D.length&&Q<v.length;Q++)if(!Do(v[Q],D[Q]))return!1;return!0}function qg(v,D,Q,H,V,ne){if(Cu=ne,qn=D,D.memoizedState=null,D.updateQueue=null,D.lanes=0,Cf.current=v===null||v.memoizedState===null?O:J,v=Q(H,V),wf){ne=0;do{if(wf=!1,!(25>ne))throw Error(c(301));ne+=1,ki=ss=null,D.updateQueue=null,Cf.current=re,v=Q(H,V)}while(wf)}if(Cf.current=kt,D=ss!==null&&ss.next!==null,Cu=0,ki=ss=qn=null,VA=!1,D)throw Error(c(300));return v}function os(){var v={memoizedState:null,baseState:null,baseQueue:null,queue:null,next:null};return ki===null?qn.memoizedState=ki=v:ki=ki.next=v,ki}function xl(){if(ss===null){var v=qn.alternate;v=v!==null?v.memoizedState:null}else v=ss.next;var D=ki===null?qn.memoizedState:ki.next;if(D!==null)ki=D,ss=v;else{if(v===null)throw Error(c(310));ss=v,v={memoizedState:ss.memoizedState,baseState:ss.baseState,baseQueue:ss.baseQueue,queue:ss.queue,next:null},ki===null?qn.memoizedState=ki=v:ki=ki.next=v}return ki}function ko(v,D){return typeof D==\"function\"?D(v):D}function Bf(v){var D=xl(),Q=D.queue;if(Q===null)throw Error(c(311));Q.lastRenderedReducer=v;var H=ss,V=H.baseQueue,ne=Q.pending;if(ne!==null){if(V!==null){var Se=V.next;V.next=ne.next,ne.next=Se}H.baseQueue=V=ne,Q.pending=null}if(V!==null){V=V.next,H=H.baseState;var Ue=Se=ne=null,At=V;do{var Gt=At.lane;if((Cu&Gt)===Gt)Ue!==null&&(Ue=Ue.next={lane:0,action:At.action,eagerReducer:At.eagerReducer,eagerState:At.eagerState,next:null}),H=At.eagerReducer===v?At.eagerState:v(H,At.action);else{var vr={lane:Gt,action:At.action,eagerReducer:At.eagerReducer,eagerState:At.eagerState,next:null};Ue===null?(Se=Ue=vr,ne=H):Ue=Ue.next=vr,qn.lanes|=Gt,Zg|=Gt}At=At.next}while(At!==null&&At!==V);Ue===null?ne=H:Ue.next=Se,Do(H,D.memoizedState)||(Ke=!0),D.memoizedState=H,D.baseState=ne,D.baseQueue=Ue,Q.lastRenderedState=H}return[D.memoizedState,Q.dispatch]}function vf(v){var D=xl(),Q=D.queue;if(Q===null)throw Error(c(311));Q.lastRenderedReducer=v;var H=Q.dispatch,V=Q.pending,ne=D.memoizedState;if(V!==null){Q.pending=null;var Se=V=V.next;do ne=v(ne,Se.action),Se=Se.next;while(Se!==V);Do(ne,D.memoizedState)||(Ke=!0),D.memoizedState=ne,D.baseQueue===null&&(D.baseState=ne),Q.lastRenderedState=ne}return[ne,H]}function kl(v,D,Q){var H=D._getVersion;H=H(D._source);var V=y?D._workInProgressVersionPrimary:D._workInProgressVersionSecondary;if(V!==null?v=V===H:(v=v.mutableReadLanes,(v=(Cu&v)===v)&&(y?D._workInProgressVersionPrimary=H:D._workInProgressVersionSecondary=H,Eu.push(D))),v)return Q(D._source);throw Eu.push(D),Error(c(350))}function yn(v,D,Q,H){var V=ao;if(V===null)throw Error(c(349));var ne=D._getVersion,Se=ne(D._source),Ue=Cf.current,At=Ue.useState(function(){return kl(V,D,Q)}),Gt=At[1],vr=At[0];At=ki;var Lr=v.memoizedState,Xt=Lr.refs,zn=Xt.getSnapshot,yi=Lr.source;Lr=Lr.subscribe;var Za=qn;return v.memoizedState={refs:Xt,source:D,subscribe:H},Ue.useEffect(function(){Xt.getSnapshot=Q,Xt.setSnapshot=Gt;var $e=ne(D._source);if(!Do(Se,$e)){$e=Q(D._source),Do(vr,$e)||(Gt($e),$e=Ss(Za),V.mutableReadLanes|=$e&V.pendingLanes),$e=V.mutableReadLanes,V.entangledLanes|=$e;for(var qe=V.entanglements,ht=$e;0<ht;){var Zt=31-is(ht),Sr=1<<Zt;qe[Zt]|=$e,ht&=~Sr}}},[Q,D,H]),Ue.useEffect(function(){return H(D._source,function(){var $e=Xt.getSnapshot,qe=Xt.setSnapshot;try{qe($e(D._source));var ht=Ss(Za);V.mutableReadLanes|=ht&V.pendingLanes}catch(Zt){qe(function(){throw Zt})}})},[D,H]),Do(zn,Q)&&Do(yi,D)&&Do(Lr,H)||(v={pending:null,dispatch:null,lastRenderedReducer:ko,lastRenderedState:vr},v.dispatch=Gt=Qh.bind(null,qn,v),At.queue=v,At.baseQueue=null,vr=kl(V,D,Q),At.memoizedState=At.baseState=vr),vr}function Qo(v,D,Q){var H=xl();return yn(H,v,D,Q)}function wu(v){var D=os();return typeof v==\"function\"&&(v=v()),D.memoizedState=D.baseState=v,v=D.queue={pending:null,dispatch:null,lastRenderedReducer:ko,lastRenderedState:v},v=v.dispatch=Qh.bind(null,qn,v),[D.memoizedState,v]}function ha(v,D,Q,H){return v={tag:v,create:D,destroy:Q,deps:H,next:null},D=qn.updateQueue,D===null?(D={lastEffect:null},qn.updateQueue=D,D.lastEffect=v.next=v):(Q=D.lastEffect,Q===null?D.lastEffect=v.next=v:(H=Q.next,Q.next=v,v.next=H,D.lastEffect=v)),v}function Os(v){var D=os();return v={current:v},D.memoizedState=v}function Ph(){return xl().memoizedState}function KA(v,D,Q,H){var V=os();qn.flags|=v,V.memoizedState=ha(1|D,Q,void 0,H===void 0?null:H)}function Sf(v,D,Q,H){var V=xl();H=H===void 0?null:H;var ne=void 0;if(ss!==null){var Se=ss.memoizedState;if(ne=Se.destroy,H!==null&&jg(H,Se.deps)){ha(D,Q,ne,H);return}}qn.flags|=v,V.memoizedState=ha(1|D,Q,ne,H)}function oo(v,D){return KA(516,4,v,D)}function Xr(v,D){return Sf(516,4,v,D)}function xh(v,D){return Sf(4,2,v,D)}function JA(v,D){if(typeof D==\"function\")return v=v(),D(v),function(){D(null)};if(D!=null)return v=v(),D.current=v,function(){D.current=null}}function By(v,D,Q){return Q=Q!=null?Q.concat([v]):null,Sf(4,2,JA.bind(null,D,v),Q)}function Gg(){}function kh(v,D){var Q=xl();D=D===void 0?null:D;var H=Q.memoizedState;return H!==null&&D!==null&&jg(D,H[1])?H[0]:(Q.memoizedState=[v,D],v)}function hc(v,D){var Q=xl();D=D===void 0?null:D;var H=Q.memoizedState;return H!==null&&D!==null&&jg(D,H[1])?H[0]:(v=v(),Q.memoizedState=[v,D],v)}function vy(v,D){var Q=tr();li(98>Q?98:Q,function(){v(!0)}),li(97<Q?97:Q,function(){var H=Ns.transition;Ns.transition=1;try{v(!1),D()}finally{Ns.transition=H}})}function Qh(v,D,Q){var H=To(),V=Ss(v),ne={lane:V,action:Q,eagerReducer:null,eagerState:null,next:null},Se=D.pending;if(Se===null?ne.next=ne:(ne.next=Se.next,Se.next=ne),D.pending=ne,Se=v.alternate,v===qn||Se!==null&&Se===qn)wf=VA=!0;else{if(v.lanes===0&&(Se===null||Se.lanes===0)&&(Se=D.lastRenderedReducer,Se!==null))try{var Ue=D.lastRenderedState,At=Se(Ue,Q);if(ne.eagerReducer=Se,ne.eagerState=At,Do(At,Ue))return}catch{}finally{}Rl(v,V,H)}}var kt={readContext:Po,useCallback:mn,useContext:mn,useEffect:mn,useImperativeHandle:mn,useLayoutEffect:mn,useMemo:mn,useReducer:mn,useRef:mn,useState:mn,useDebugValue:mn,useDeferredValue:mn,useTransition:mn,useMutableSource:mn,useOpaqueIdentifier:mn,unstable_isNewReconciler:!1},O={readContext:Po,useCallback:function(v,D){return os().memoizedState=[v,D===void 0?null:D],v},useContext:Po,useEffect:oo,useImperativeHandle:function(v,D,Q){return Q=Q!=null?Q.concat([v]):null,KA(4,2,JA.bind(null,D,v),Q)},useLayoutEffect:function(v,D){return KA(4,2,v,D)},useMemo:function(v,D){var Q=os();return D=D===void 0?null:D,v=v(),Q.memoizedState=[v,D],v},useReducer:function(v,D,Q){var H=os();return D=Q!==void 0?Q(D):D,H.memoizedState=H.baseState=D,v=H.queue={pending:null,dispatch:null,lastRenderedReducer:v,lastRenderedState:D},v=v.dispatch=Qh.bind(null,qn,v),[H.memoizedState,v]},useRef:Os,useState:wu,useDebugValue:Gg,useDeferredValue:function(v){var D=wu(v),Q=D[0],H=D[1];return oo(function(){var V=Ns.transition;Ns.transition=1;try{H(v)}finally{Ns.transition=V}},[v]),Q},useTransition:function(){var v=wu(!1),D=v[0];return v=vy.bind(null,v[1]),Os(v),[v,D]},useMutableSource:function(v,D,Q){var H=os();return H.memoizedState={refs:{getSnapshot:D,setSnapshot:null},source:v,subscribe:Q},yn(H,v,D,Q)},useOpaqueIdentifier:function(){if(Va){var v=!1,D=oe(function(){throw v||(v=!0,Q(xe())),Error(c(355))}),Q=wu(D)[1];return!(qn.mode&2)&&(qn.flags|=516,ha(5,function(){Q(xe())},void 0,null)),D}return D=xe(),wu(D),D},unstable_isNewReconciler:!1},J={readContext:Po,useCallback:kh,useContext:Po,useEffect:Xr,useImperativeHandle:By,useLayoutEffect:xh,useMemo:hc,useReducer:Bf,useRef:Ph,useState:function(){return Bf(ko)},useDebugValue:Gg,useDeferredValue:function(v){var D=Bf(ko),Q=D[0],H=D[1];return Xr(function(){var V=Ns.transition;Ns.transition=1;try{H(v)}finally{Ns.transition=V}},[v]),Q},useTransition:function(){var v=Bf(ko)[0];return[Ph().current,v]},useMutableSource:Qo,useOpaqueIdentifier:function(){return Bf(ko)[0]},unstable_isNewReconciler:!1},re={readContext:Po,useCallback:kh,useContext:Po,useEffect:Xr,useImperativeHandle:By,useLayoutEffect:xh,useMemo:hc,useReducer:vf,useRef:Ph,useState:function(){return vf(ko)},useDebugValue:Gg,useDeferredValue:function(v){var D=vf(ko),Q=D[0],H=D[1];return Xr(function(){var V=Ns.transition;Ns.transition=1;try{H(v)}finally{Ns.transition=V}},[v]),Q},useTransition:function(){var v=vf(ko)[0];return[Ph().current,v]},useMutableSource:Qo,useOpaqueIdentifier:function(){return vf(ko)[0]},unstable_isNewReconciler:!1},de=f.ReactCurrentOwner,Ke=!1;function ft(v,D,Q,H){D.child=v===null?e2(D,null,Q,H):Lg(D,v.child,Q,H)}function dr(v,D,Q,H,V){Q=Q.render;var ne=D.ref;return mf(D,V),H=qg(v,D,Q,H,ne,V),v!==null&&!Ke?(D.updateQueue=v.updateQueue,D.flags&=-517,v.lanes&=~V,Gn(v,D,V)):(D.flags|=1,ft(v,D,H,V),D.child)}function Br(v,D,Q,H,V,ne){if(v===null){var Se=Q.type;return typeof Se==\"function\"&&!S2(Se)&&Se.defaultProps===void 0&&Q.compare===null&&Q.defaultProps===void 0?(D.tag=15,D.type=Se,_n(v,D,Se,H,V,ne)):(v=id(Q.type,null,H,D,D.mode,ne),v.ref=D.ref,v.return=D,D.child=v)}return Se=v.child,!(V&ne)&&(V=Se.memoizedProps,Q=Q.compare,Q=Q!==null?Q:Ch,Q(V,H)&&v.ref===D.ref)?Gn(v,D,ne):(D.flags|=1,v=bu(Se,H),v.ref=D.ref,v.return=D,D.child=v)}function _n(v,D,Q,H,V,ne){if(v!==null&&Ch(v.memoizedProps,H)&&v.ref===D.ref)if(Ke=!1,(ne&V)!==0)v.flags&16384&&(Ke=!0);else return D.lanes=v.lanes,Gn(v,D,ne);return zA(v,D,Q,H,ne)}function mi(v,D,Q){var H=D.pendingProps,V=H.children,ne=v!==null?v.memoizedState:null;if(H.mode===\"hidden\"||H.mode===\"unstable-defer-without-hiding\")if(!(D.mode&4))D.memoizedState={baseLanes:0},jy(D,Q);else if(Q&1073741824)D.memoizedState={baseLanes:0},jy(D,ne!==null?ne.baseLanes:Q);else return v=ne!==null?ne.baseLanes|Q:Q,D.lanes=D.childLanes=1073741824,D.memoizedState={baseLanes:v},jy(D,v),null;else ne!==null?(H=ne.baseLanes|Q,D.memoizedState=null):H=Q,jy(D,H);return ft(v,D,V,Q),D.child}function Bs(v,D){var Q=D.ref;(v===null&&Q!==null||v!==null&&v.ref!==Q)&&(D.flags|=128)}function zA(v,D,Q,H,V){var ne=Jn(Q)?Oa:qi.current;return ne=dn(D,ne),mf(D,V),Q=qg(v,D,Q,H,ne,V),v!==null&&!Ke?(D.updateQueue=v.updateQueue,D.flags&=-517,v.lanes&=~V,Gn(v,D,V)):(D.flags|=1,ft(v,D,Q,V),D.child)}function dP(v,D,Q,H,V){if(Jn(Q)){var ne=!0;Ma(D)}else ne=!1;if(mf(D,V),D.stateNode===null)v!==null&&(v.alternate=null,D.alternate=null,D.flags|=2),xt(D,Q,H),xo(D,Q,H,V),H=!0;else if(v===null){var Se=D.stateNode,Ue=D.memoizedProps;Se.props=Ue;var At=Se.context,Gt=Q.contextType;typeof Gt==\"object\"&&Gt!==null?Gt=Po(Gt):(Gt=Jn(Q)?Oa:qi.current,Gt=dn(D,Gt));var vr=Q.getDerivedStateFromProps,Lr=typeof vr==\"function\"||typeof Se.getSnapshotBeforeUpdate==\"function\";Lr||typeof Se.UNSAFE_componentWillReceiveProps!=\"function\"&&typeof Se.componentWillReceiveProps!=\"function\"||(Ue!==H||At!==Gt)&&GA(D,Se,H,Gt),Dl=!1;var Xt=D.memoizedState;Se.state=Xt,HA(D,H,Se,V),At=D.memoizedState,Ue!==H||Xt!==At||Mi.current||Dl?(typeof vr==\"function\"&&(jA(D,Q,vr,H),At=D.memoizedState),(Ue=Dl||Y(D,Q,Ue,H,Xt,At,Gt))?(Lr||typeof Se.UNSAFE_componentWillMount!=\"function\"&&typeof Se.componentWillMount!=\"function\"||(typeof Se.componentWillMount==\"function\"&&Se.componentWillMount(),typeof Se.UNSAFE_componentWillMount==\"function\"&&Se.UNSAFE_componentWillMount()),typeof Se.componentDidMount==\"function\"&&(D.flags|=4)):(typeof Se.componentDidMount==\"function\"&&(D.flags|=4),D.memoizedProps=H,D.memoizedState=At),Se.props=H,Se.state=At,Se.context=Gt,H=Ue):(typeof Se.componentDidMount==\"function\"&&(D.flags|=4),H=!1)}else{Se=D.stateNode,Og(v,D),Ue=D.memoizedProps,Gt=D.type===D.elementType?Ue:bo(D.type,Ue),Se.props=Gt,Lr=D.pendingProps,Xt=Se.context,At=Q.contextType,typeof At==\"object\"&&At!==null?At=Po(At):(At=Jn(Q)?Oa:qi.current,At=dn(D,At));var zn=Q.getDerivedStateFromProps;(vr=typeof zn==\"function\"||typeof Se.getSnapshotBeforeUpdate==\"function\")||typeof Se.UNSAFE_componentWillReceiveProps!=\"function\"&&typeof Se.componentWillReceiveProps!=\"function\"||(Ue!==Lr||Xt!==At)&&GA(D,Se,H,At),Dl=!1,Xt=D.memoizedState,Se.state=Xt,HA(D,H,Se,V);var yi=D.memoizedState;Ue!==Lr||Xt!==yi||Mi.current||Dl?(typeof zn==\"function\"&&(jA(D,Q,zn,H),yi=D.memoizedState),(Gt=Dl||Y(D,Q,Gt,H,Xt,yi,At))?(vr||typeof Se.UNSAFE_componentWillUpdate!=\"function\"&&typeof Se.componentWillUpdate!=\"function\"||(typeof Se.componentWillUpdate==\"function\"&&Se.componentWillUpdate(H,yi,At),typeof Se.UNSAFE_componentWillUpdate==\"function\"&&Se.UNSAFE_componentWillUpdate(H,yi,At)),typeof Se.componentDidUpdate==\"function\"&&(D.flags|=4),typeof Se.getSnapshotBeforeUpdate==\"function\"&&(D.flags|=256)):(typeof Se.componentDidUpdate!=\"function\"||Ue===v.memoizedProps&&Xt===v.memoizedState||(D.flags|=4),typeof Se.getSnapshotBeforeUpdate!=\"function\"||Ue===v.memoizedProps&&Xt===v.memoizedState||(D.flags|=256),D.memoizedProps=H,D.memoizedState=yi),Se.props=H,Se.state=yi,Se.context=At,H=Gt):(typeof Se.componentDidUpdate!=\"function\"||Ue===v.memoizedProps&&Xt===v.memoizedState||(D.flags|=4),typeof Se.getSnapshotBeforeUpdate!=\"function\"||Ue===v.memoizedProps&&Xt===v.memoizedState||(D.flags|=256),H=!1)}return t2(v,D,Q,H,ne,V)}function t2(v,D,Q,H,V,ne){Bs(v,D);var Se=(D.flags&64)!==0;if(!H&&!Se)return V&&Ua(D,Q,!1),Gn(v,D,ne);H=D.stateNode,de.current=D;var Ue=Se&&typeof Q.getDerivedStateFromError!=\"function\"?null:H.render();return D.flags|=1,v!==null&&Se?(D.child=Lg(D,v.child,null,ne),D.child=Lg(D,null,Ue,ne)):ft(v,D,Ue,ne),D.memoizedState=H.state,V&&Ua(D,Q,!0),D.child}function Sy(v){var D=v.stateNode;D.pendingContext?Ih(v,D.pendingContext,D.pendingContext!==D.context):D.context&&Ih(v,D.context,!1),Mg(v,D.containerInfo)}var Th={dehydrated:null,retryLane:0};function r2(v,D,Q){var H=D.pendingProps,V=di.current,ne=!1,Se;return(Se=(D.flags&64)!==0)||(Se=v!==null&&v.memoizedState===null?!1:(V&2)!==0),Se?(ne=!0,D.flags&=-65):v!==null&&v.memoizedState===null||H.fallback===void 0||H.unstable_avoidThisFallback===!0||(V|=1),xn(di,V&1),v===null?(H.fallback!==void 0&&Ug(D),v=H.children,V=H.fallback,ne?(v=Ka(D,v,V,Q),D.child.memoizedState={baseLanes:Q},D.memoizedState=Th,v):typeof H.unstable_expectedLoadTime==\"number\"?(v=Ka(D,v,V,Q),D.child.memoizedState={baseLanes:Q},D.memoizedState=Th,D.lanes=33554432,v):(Q=D2({mode:\"visible\",children:v},D.mode,Q,null),Q.return=D,D.child=Q)):v.memoizedState!==null?ne?(H=ZA(v,D,H.children,H.fallback,Q),ne=D.child,V=v.child.memoizedState,ne.memoizedState=V===null?{baseLanes:Q}:{baseLanes:V.baseLanes|Q},ne.childLanes=v.childLanes&~Q,D.memoizedState=Th,H):(Q=n2(v,D,H.children,Q),D.memoizedState=null,Q):ne?(H=ZA(v,D,H.children,H.fallback,Q),ne=D.child,V=v.child.memoizedState,ne.memoizedState=V===null?{baseLanes:Q}:{baseLanes:V.baseLanes|Q},ne.childLanes=v.childLanes&~Q,D.memoizedState=Th,H):(Q=n2(v,D,H.children,Q),D.memoizedState=null,Q)}function Ka(v,D,Q,H){var V=v.mode,ne=v.child;return D={mode:\"hidden\",children:D},!(V&2)&&ne!==null?(ne.childLanes=0,ne.pendingProps=D):ne=D2(D,V,0,null),Q=Qf(Q,V,H,null),ne.return=v,Q.return=v,ne.sibling=Q,v.child=ne,Q}function n2(v,D,Q,H){var V=v.child;return v=V.sibling,Q=bu(V,{mode:\"visible\",children:Q}),!(D.mode&2)&&(Q.lanes=H),Q.return=D,Q.sibling=null,v!==null&&(v.nextEffect=null,v.flags=8,D.firstEffect=D.lastEffect=v),D.child=Q}function ZA(v,D,Q,H,V){var ne=D.mode,Se=v.child;v=Se.sibling;var Ue={mode:\"hidden\",children:Q};return!(ne&2)&&D.child!==Se?(Q=D.child,Q.childLanes=0,Q.pendingProps=Ue,Se=Q.lastEffect,Se!==null?(D.firstEffect=Q.firstEffect,D.lastEffect=Se,Se.nextEffect=null):D.firstEffect=D.lastEffect=null):Q=bu(Se,Ue),v!==null?H=bu(v,H):(H=Qf(H,ne,V,null),H.flags|=2),H.return=D,Q.return=D,Q.sibling=H,D.child=Q,H}function Rh(v,D){v.lanes|=D;var Q=v.alternate;Q!==null&&(Q.lanes|=D),my(v.return,D)}function Dy(v,D,Q,H,V,ne){var Se=v.memoizedState;Se===null?v.memoizedState={isBackwards:D,rendering:null,renderingStartTime:0,last:H,tail:Q,tailMode:V,lastEffect:ne}:(Se.isBackwards=D,Se.rendering=null,Se.renderingStartTime=0,Se.last=H,Se.tail=Q,Se.tailMode=V,Se.lastEffect=ne)}function mP(v,D,Q){var H=D.pendingProps,V=H.revealOrder,ne=H.tail;if(ft(v,D,H.children,Q),H=di.current,H&2)H=H&1|2,D.flags|=64;else{if(v!==null&&v.flags&64)e:for(v=D.child;v!==null;){if(v.tag===13)v.memoizedState!==null&&Rh(v,Q);else if(v.tag===19)Rh(v,Q);else if(v.child!==null){v.child.return=v,v=v.child;continue}if(v===D)break e;for(;v.sibling===null;){if(v.return===null||v.return===D)break e;v=v.return}v.sibling.return=v.return,v=v.sibling}H&=1}if(xn(di,H),!(D.mode&2))D.memoizedState=null;else switch(V){case\"forwards\":for(Q=D.child,V=null;Q!==null;)v=Q.alternate,v!==null&&WA(v)===null&&(V=Q),Q=Q.sibling;Q=V,Q===null?(V=D.child,D.child=null):(V=Q.sibling,Q.sibling=null),Dy(D,!1,V,Q,ne,D.lastEffect);break;case\"backwards\":for(Q=null,V=D.child,D.child=null;V!==null;){if(v=V.alternate,v!==null&&WA(v)===null){D.child=V;break}v=V.sibling,V.sibling=Q,Q=V,V=v}Dy(D,!0,Q,null,ne,D.lastEffect);break;case\"together\":Dy(D,!1,null,null,void 0,D.lastEffect);break;default:D.memoizedState=null}return D.child}function Gn(v,D,Q){if(v!==null&&(D.dependencies=v.dependencies),Zg|=D.lanes,Q&D.childLanes){if(v!==null&&D.child!==v.child)throw Error(c(153));if(D.child!==null){for(v=D.child,Q=bu(v,v.pendingProps),D.child=Q,Q.return=D;v.sibling!==null;)v=v.sibling,Q=Q.sibling=bu(v,v.pendingProps),Q.return=D;Q.sibling=null}return D.child}return null}function as(v){v.flags|=4}var Ql,Tl,Bu,ga;if(F)Ql=function(v,D){for(var Q=D.child;Q!==null;){if(Q.tag===5||Q.tag===6)Pe(v,Q.stateNode);else if(Q.tag!==4&&Q.child!==null){Q.child.return=Q,Q=Q.child;continue}if(Q===D)break;for(;Q.sibling===null;){if(Q.return===null||Q.return===D)return;Q=Q.return}Q.sibling.return=Q.return,Q=Q.sibling}},Tl=function(){},Bu=function(v,D,Q,H,V){if(v=v.memoizedProps,v!==H){var ne=D.stateNode,Se=Wa(ur.current);Q=ke(ne,Q,v,H,V,Se),(D.updateQueue=Q)&&as(D)}},ga=function(v,D,Q,H){Q!==H&&as(D)};else if(z){Ql=function(v,D,Q,H){for(var V=D.child;V!==null;){if(V.tag===5){var ne=V.stateNode;Q&&H&&(ne=NA(ne,V.type,V.memoizedProps,V)),Pe(v,ne)}else if(V.tag===6)ne=V.stateNode,Q&&H&&(ne=aa(ne,V.memoizedProps,V)),Pe(v,ne);else if(V.tag!==4){if(V.tag===13&&V.flags&4&&(ne=V.memoizedState!==null)){var Se=V.child;if(Se!==null&&(Se.child!==null&&(Se.child.return=Se,Ql(v,Se,!0,ne)),ne=Se.sibling,ne!==null)){ne.return=V,V=ne;continue}}if(V.child!==null){V.child.return=V,V=V.child;continue}}if(V===D)break;for(;V.sibling===null;){if(V.return===null||V.return===D)return;V=V.return}V.sibling.return=V.return,V=V.sibling}};var XA=function(v,D,Q,H){for(var V=D.child;V!==null;){if(V.tag===5){var ne=V.stateNode;Q&&H&&(ne=NA(ne,V.type,V.memoizedProps,V)),cu(v,ne)}else if(V.tag===6)ne=V.stateNode,Q&&H&&(ne=aa(ne,V.memoizedProps,V)),cu(v,ne);else if(V.tag!==4){if(V.tag===13&&V.flags&4&&(ne=V.memoizedState!==null)){var Se=V.child;if(Se!==null&&(Se.child!==null&&(Se.child.return=Se,XA(v,Se,!0,ne)),ne=Se.sibling,ne!==null)){ne.return=V,V=ne;continue}}if(V.child!==null){V.child.return=V,V=V.child;continue}}if(V===D)break;for(;V.sibling===null;){if(V.return===null||V.return===D)return;V=V.return}V.sibling.return=V.return,V=V.sibling}};Tl=function(v){var D=v.stateNode;if(v.firstEffect!==null){var Q=D.containerInfo,H=lu(Q);XA(H,v,!1,!1),D.pendingChildren=H,as(v),uu(Q,H)}},Bu=function(v,D,Q,H,V){var ne=v.stateNode,Se=v.memoizedProps;if((v=D.firstEffect===null)&&Se===H)D.stateNode=ne;else{var Ue=D.stateNode,At=Wa(ur.current),Gt=null;Se!==H&&(Gt=ke(Ue,Q,Se,H,V,At)),v&&Gt===null?D.stateNode=ne:(ne=io(ne,Gt,Q,Se,H,D,v,Ue),Ye(ne,Q,H,V,At)&&as(D),D.stateNode=ne,v?as(D):Ql(ne,D,!1,!1))}},ga=function(v,D,Q,H){Q!==H?(v=Wa(Ef.current),Q=Wa(ur.current),D.stateNode=_e(H,v,Q,D),as(D)):D.stateNode=v.stateNode}}else Tl=function(){},Bu=function(){},ga=function(){};function $A(v,D){if(!Va)switch(v.tailMode){case\"hidden\":D=v.tail;for(var Q=null;D!==null;)D.alternate!==null&&(Q=D),D=D.sibling;Q===null?v.tail=null:Q.sibling=null;break;case\"collapsed\":Q=v.tail;for(var H=null;Q!==null;)Q.alternate!==null&&(H=Q),Q=Q.sibling;H===null?D||v.tail===null?v.tail=null:v.tail.sibling=null:H.sibling=null}}function WL(v,D,Q){var H=D.pendingProps;switch(D.tag){case 2:case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return null;case 1:return Jn(D.type)&&hu(),null;case 3:return yu(),Rt(Mi),Rt(qi),Iu(),H=D.stateNode,H.pendingContext&&(H.context=H.pendingContext,H.pendingContext=null),(v===null||v.child===null)&&(YA(D)?as(D):H.hydrate||(D.flags|=256)),Tl(D),null;case 5:wt(D);var V=Wa(Ef.current);if(Q=D.type,v!==null&&D.stateNode!=null)Bu(v,D,Q,H,V),v.ref!==D.ref&&(D.flags|=128);else{if(!H){if(D.stateNode===null)throw Error(c(166));return null}if(v=Wa(ur.current),YA(D)){if(!Z)throw Error(c(175));v=Cr(D.stateNode,D.type,D.memoizedProps,V,v,D),D.updateQueue=v,v!==null&&as(D)}else{var ne=Ne(Q,H,V,v,D);Ql(ne,D,!1,!1),D.stateNode=ne,Ye(ne,Q,H,V,v)&&as(D)}D.ref!==null&&(D.flags|=128)}return null;case 6:if(v&&D.stateNode!=null)ga(v,D,v.memoizedProps,H);else{if(typeof H!=\"string\"&&D.stateNode===null)throw Error(c(166));if(v=Wa(Ef.current),V=Wa(ur.current),YA(D)){if(!Z)throw Error(c(176));hf(D.stateNode,D.memoizedProps,D)&&as(D)}else D.stateNode=_e(H,v,V,D)}return null;case 13:return Rt(di),H=D.memoizedState,D.flags&64?(D.lanes=Q,D):(H=H!==null,V=!1,v===null?D.memoizedProps.fallback!==void 0&&YA(D):V=v.memoizedState!==null,H&&!V&&D.mode&2&&(v===null&&D.memoizedProps.unstable_avoidThisFallback!==!0||di.current&1?vs===0&&(vs=3):((vs===0||vs===3)&&(vs=4),ao===null||!(Zg&134217727)&&!(Oh&134217727)||Lh(ao,Ls))),z&&H&&(D.flags|=4),F&&(H||V)&&(D.flags|=4),null);case 4:return yu(),Tl(D),v===null&&It(D.stateNode.containerInfo),null;case 10:return Ng(D),null;case 17:return Jn(D.type)&&hu(),null;case 19:if(Rt(di),H=D.memoizedState,H===null)return null;if(V=(D.flags&64)!==0,ne=H.rendering,ne===null)if(V)$A(H,!1);else{if(vs!==0||v!==null&&v.flags&64)for(v=D.child;v!==null;){if(ne=WA(v),ne!==null){for(D.flags|=64,$A(H,!1),v=ne.updateQueue,v!==null&&(D.updateQueue=v,D.flags|=4),H.lastEffect===null&&(D.firstEffect=null),D.lastEffect=H.lastEffect,v=Q,H=D.child;H!==null;)V=H,Q=v,V.flags&=2,V.nextEffect=null,V.firstEffect=null,V.lastEffect=null,ne=V.alternate,ne===null?(V.childLanes=0,V.lanes=Q,V.child=null,V.memoizedProps=null,V.memoizedState=null,V.updateQueue=null,V.dependencies=null,V.stateNode=null):(V.childLanes=ne.childLanes,V.lanes=ne.lanes,V.child=ne.child,V.memoizedProps=ne.memoizedProps,V.memoizedState=ne.memoizedState,V.updateQueue=ne.updateQueue,V.type=ne.type,Q=ne.dependencies,V.dependencies=Q===null?null:{lanes:Q.lanes,firstContext:Q.firstContext}),H=H.sibling;return xn(di,di.current&1|2),D.child}v=v.sibling}H.tail!==null&&Dt()>m2&&(D.flags|=64,V=!0,$A(H,!1),D.lanes=33554432)}else{if(!V)if(v=WA(ne),v!==null){if(D.flags|=64,V=!0,v=v.updateQueue,v!==null&&(D.updateQueue=v,D.flags|=4),$A(H,!0),H.tail===null&&H.tailMode===\"hidden\"&&!ne.alternate&&!Va)return D=D.lastEffect=H.lastEffect,D!==null&&(D.nextEffect=null),null}else 2*Dt()-H.renderingStartTime>m2&&Q!==1073741824&&(D.flags|=64,V=!0,$A(H,!1),D.lanes=33554432);H.isBackwards?(ne.sibling=D.child,D.child=ne):(v=H.last,v!==null?v.sibling=ne:D.child=ne,H.last=ne)}return H.tail!==null?(v=H.tail,H.rendering=v,H.tail=v.sibling,H.lastEffect=D.lastEffect,H.renderingStartTime=Dt(),v.sibling=null,D=di.current,xn(di,V?D&1|2:D&1),v):null;case 23:case 24:return B2(),v!==null&&v.memoizedState!==null!=(D.memoizedState!==null)&&H.mode!==\"unstable-defer-without-hiding\"&&(D.flags|=4),null}throw Error(c(156,D.tag))}function YL(v){switch(v.tag){case 1:Jn(v.type)&&hu();var D=v.flags;return D&4096?(v.flags=D&-4097|64,v):null;case 3:if(yu(),Rt(Mi),Rt(qi),Iu(),D=v.flags,D&64)throw Error(c(285));return v.flags=D&-4097|64,v;case 5:return wt(v),null;case 13:return Rt(di),D=v.flags,D&4096?(v.flags=D&-4097|64,v):null;case 19:return Rt(di),null;case 4:return yu(),null;case 10:return Ng(v),null;case 23:case 24:return B2(),null;default:return null}}function Wg(v,D){try{var Q=\"\",H=D;do Q+=$1(H),H=H.return;while(H);var V=Q}catch(ne){V=`\nError generating stack: `+ne.message+`\n`+ne.stack}return{value:v,source:D,stack:V}}function Yg(v,D){try{console.error(D.value)}catch(Q){setTimeout(function(){throw Q})}}var VL=typeof WeakMap==\"function\"?WeakMap:Map;function i2(v,D,Q){Q=bl(-1,Q),Q.tag=3,Q.payload={element:null};var H=D.value;return Q.callback=function(){My||(My=!0,y2=H),Yg(v,D)},Q}function Vg(v,D,Q){Q=bl(-1,Q),Q.tag=3;var H=v.type.getDerivedStateFromError;if(typeof H==\"function\"){var V=D.value;Q.payload=function(){return Yg(v,D),H(V)}}var ne=v.stateNode;return ne!==null&&typeof ne.componentDidCatch==\"function\"&&(Q.callback=function(){typeof H!=\"function\"&&(gc===null?gc=new Set([this]):gc.add(this),Yg(v,D));var Se=D.stack;this.componentDidCatch(D.value,{componentStack:Se!==null?Se:\"\"})}),Q}var KL=typeof WeakSet==\"function\"?WeakSet:Set;function s2(v){var D=v.ref;if(D!==null)if(typeof D==\"function\")try{D(null)}catch(Q){kf(v,Q)}else D.current=null}function by(v,D){switch(D.tag){case 0:case 11:case 15:case 22:return;case 1:if(D.flags&256&&v!==null){var Q=v.memoizedProps,H=v.memoizedState;v=D.stateNode,D=v.getSnapshotBeforeUpdate(D.elementType===D.type?Q:bo(D.type,Q),H),v.__reactInternalSnapshotBeforeUpdate=D}return;case 3:F&&D.flags&256&&Fs(D.stateNode.containerInfo);return;case 5:case 6:case 4:case 17:return}throw Error(c(163))}function Fh(v,D){if(D=D.updateQueue,D=D!==null?D.lastEffect:null,D!==null){var Q=D=D.next;do{if((Q.tag&v)===v){var H=Q.destroy;Q.destroy=void 0,H!==void 0&&H()}Q=Q.next}while(Q!==D)}}function yP(v,D,Q){switch(Q.tag){case 0:case 11:case 15:case 22:if(D=Q.updateQueue,D=D!==null?D.lastEffect:null,D!==null){v=D=D.next;do{if((v.tag&3)===3){var H=v.create;v.destroy=H()}v=v.next}while(v!==D)}if(D=Q.updateQueue,D=D!==null?D.lastEffect:null,D!==null){v=D=D.next;do{var V=v;H=V.next,V=V.tag,V&4&&V&1&&(TP(Q,v),nM(Q,v)),v=H}while(v!==D)}return;case 1:v=Q.stateNode,Q.flags&4&&(D===null?v.componentDidMount():(H=Q.elementType===Q.type?D.memoizedProps:bo(Q.type,D.memoizedProps),v.componentDidUpdate(H,D.memoizedState,v.__reactInternalSnapshotBeforeUpdate))),D=Q.updateQueue,D!==null&&Ey(Q,D,v);return;case 3:if(D=Q.updateQueue,D!==null){if(v=null,Q.child!==null)switch(Q.child.tag){case 5:v=Re(Q.child.stateNode);break;case 1:v=Q.child.stateNode}Ey(Q,D,v)}return;case 5:v=Q.stateNode,D===null&&Q.flags&4&&to(v,Q.type,Q.memoizedProps,Q);return;case 6:return;case 4:return;case 12:return;case 13:Z&&Q.memoizedState===null&&(Q=Q.alternate,Q!==null&&(Q=Q.memoizedState,Q!==null&&(Q=Q.dehydrated,Q!==null&&Au(Q))));return;case 19:case 17:case 20:case 21:case 23:case 24:return}throw Error(c(163))}function EP(v,D){if(F)for(var Q=v;;){if(Q.tag===5){var H=Q.stateNode;D?yh(H):no(Q.stateNode,Q.memoizedProps)}else if(Q.tag===6)H=Q.stateNode,D?Eh(H):jn(H,Q.memoizedProps);else if((Q.tag!==23&&Q.tag!==24||Q.memoizedState===null||Q===v)&&Q.child!==null){Q.child.return=Q,Q=Q.child;continue}if(Q===v)break;for(;Q.sibling===null;){if(Q.return===null||Q.return===v)return;Q=Q.return}Q.sibling.return=Q.return,Q=Q.sibling}}function Py(v,D){if(Ha&&typeof Ha.onCommitFiberUnmount==\"function\")try{Ha.onCommitFiberUnmount(Xe,D)}catch{}switch(D.tag){case 0:case 11:case 14:case 15:case 22:if(v=D.updateQueue,v!==null&&(v=v.lastEffect,v!==null)){var Q=v=v.next;do{var H=Q,V=H.destroy;if(H=H.tag,V!==void 0)if(H&4)TP(D,Q);else{H=D;try{V()}catch(ne){kf(H,ne)}}Q=Q.next}while(Q!==v)}break;case 1:if(s2(D),v=D.stateNode,typeof v.componentWillUnmount==\"function\")try{v.props=D.memoizedProps,v.state=D.memoizedState,v.componentWillUnmount()}catch(ne){kf(D,ne)}break;case 5:s2(D);break;case 4:F?BP(v,D):z&&z&&(D=D.stateNode.containerInfo,v=lu(D),FA(D,v))}}function IP(v,D){for(var Q=D;;)if(Py(v,Q),Q.child===null||F&&Q.tag===4){if(Q===D)break;for(;Q.sibling===null;){if(Q.return===null||Q.return===D)return;Q=Q.return}Q.sibling.return=Q.return,Q=Q.sibling}else Q.child.return=Q,Q=Q.child}function xy(v){v.alternate=null,v.child=null,v.dependencies=null,v.firstEffect=null,v.lastEffect=null,v.memoizedProps=null,v.memoizedState=null,v.pendingProps=null,v.return=null,v.updateQueue=null}function CP(v){return v.tag===5||v.tag===3||v.tag===4}function wP(v){if(F){e:{for(var D=v.return;D!==null;){if(CP(D))break e;D=D.return}throw Error(c(160))}var Q=D;switch(D=Q.stateNode,Q.tag){case 5:var H=!1;break;case 3:D=D.containerInfo,H=!0;break;case 4:D=D.containerInfo,H=!0;break;default:throw Error(c(161))}Q.flags&16&&(pf(D),Q.flags&=-17);e:t:for(Q=v;;){for(;Q.sibling===null;){if(Q.return===null||CP(Q.return)){Q=null;break e}Q=Q.return}for(Q.sibling.return=Q.return,Q=Q.sibling;Q.tag!==5&&Q.tag!==6&&Q.tag!==18;){if(Q.flags&2||Q.child===null||Q.tag===4)continue t;Q.child.return=Q,Q=Q.child}if(!(Q.flags&2)){Q=Q.stateNode;break e}}H?o2(v,Q,D):a2(v,Q,D)}}function o2(v,D,Q){var H=v.tag,V=H===5||H===6;if(V)v=V?v.stateNode:v.stateNode.instance,D?ro(Q,v,D):wo(Q,v);else if(H!==4&&(v=v.child,v!==null))for(o2(v,D,Q),v=v.sibling;v!==null;)o2(v,D,Q),v=v.sibling}function a2(v,D,Q){var H=v.tag,V=H===5||H===6;if(V)v=V?v.stateNode:v.stateNode.instance,D?ji(Q,v,D):ai(Q,v);else if(H!==4&&(v=v.child,v!==null))for(a2(v,D,Q),v=v.sibling;v!==null;)a2(v,D,Q),v=v.sibling}function BP(v,D){for(var Q=D,H=!1,V,ne;;){if(!H){H=Q.return;e:for(;;){if(H===null)throw Error(c(160));switch(V=H.stateNode,H.tag){case 5:ne=!1;break e;case 3:V=V.containerInfo,ne=!0;break e;case 4:V=V.containerInfo,ne=!0;break e}H=H.return}H=!0}if(Q.tag===5||Q.tag===6)IP(v,Q),ne?RA(V,Q.stateNode):vo(V,Q.stateNode);else if(Q.tag===4){if(Q.child!==null){V=Q.stateNode.containerInfo,ne=!0,Q.child.return=Q,Q=Q.child;continue}}else if(Py(v,Q),Q.child!==null){Q.child.return=Q,Q=Q.child;continue}if(Q===D)break;for(;Q.sibling===null;){if(Q.return===null||Q.return===D)return;Q=Q.return,Q.tag===4&&(H=!1)}Q.sibling.return=Q.return,Q=Q.sibling}}function l2(v,D){if(F){switch(D.tag){case 0:case 11:case 14:case 15:case 22:Fh(3,D);return;case 1:return;case 5:var Q=D.stateNode;if(Q!=null){var H=D.memoizedProps;v=v!==null?v.memoizedProps:H;var V=D.type,ne=D.updateQueue;D.updateQueue=null,ne!==null&&Bo(Q,ne,V,v,H,D)}return;case 6:if(D.stateNode===null)throw Error(c(162));Q=D.memoizedProps,ns(D.stateNode,v!==null?v.memoizedProps:Q,Q);return;case 3:Z&&(D=D.stateNode,D.hydrate&&(D.hydrate=!1,MA(D.containerInfo)));return;case 12:return;case 13:vP(D),Kg(D);return;case 19:Kg(D);return;case 17:return;case 23:case 24:EP(D,D.memoizedState!==null);return}throw Error(c(163))}switch(D.tag){case 0:case 11:case 14:case 15:case 22:Fh(3,D);return;case 12:return;case 13:vP(D),Kg(D);return;case 19:Kg(D);return;case 3:Z&&(Q=D.stateNode,Q.hydrate&&(Q.hydrate=!1,MA(Q.containerInfo)));break;case 23:case 24:return}e:if(z){switch(D.tag){case 1:case 5:case 6:case 20:break e;case 3:case 4:D=D.stateNode,FA(D.containerInfo,D.pendingChildren);break e}throw Error(c(163))}}function vP(v){v.memoizedState!==null&&(d2=Dt(),F&&EP(v.child,!0))}function Kg(v){var D=v.updateQueue;if(D!==null){v.updateQueue=null;var Q=v.stateNode;Q===null&&(Q=v.stateNode=new KL),D.forEach(function(H){var V=sM.bind(null,v,H);Q.has(H)||(Q.add(H),H.then(V,V))})}}function JL(v,D){return v!==null&&(v=v.memoizedState,v===null||v.dehydrated!==null)?(D=D.memoizedState,D!==null&&D.dehydrated===null):!1}var ky=0,Qy=1,Ty=2,Jg=3,Ry=4;if(typeof Symbol==\"function\"&&Symbol.for){var zg=Symbol.for;ky=zg(\"selector.component\"),Qy=zg(\"selector.has_pseudo_class\"),Ty=zg(\"selector.role\"),Jg=zg(\"selector.test_id\"),Ry=zg(\"selector.text\")}function Fy(v){var D=$(v);if(D!=null){if(typeof D.memoizedProps[\"data-testname\"]!=\"string\")throw Error(c(364));return D}if(v=ir(v),v===null)throw Error(c(362));return v.stateNode.current}function Df(v,D){switch(D.$$typeof){case ky:if(v.type===D.value)return!0;break;case Qy:e:{D=D.value,v=[v,0];for(var Q=0;Q<v.length;){var H=v[Q++],V=v[Q++],ne=D[V];if(H.tag!==5||!Pr(H)){for(;ne!=null&&Df(H,ne);)V++,ne=D[V];if(V===D.length){D=!0;break e}else for(H=H.child;H!==null;)v.push(H,V),H=H.sibling}}D=!1}return D;case Ty:if(v.tag===5&&Ir(v.stateNode,D.value))return!0;break;case Ry:if((v.tag===5||v.tag===6)&&(v=gn(v),v!==null&&0<=v.indexOf(D.value)))return!0;break;case Jg:if(v.tag===5&&(v=v.memoizedProps[\"data-testname\"],typeof v==\"string\"&&v.toLowerCase()===D.value.toLowerCase()))return!0;break;default:throw Error(c(365,D))}return!1}function bf(v){switch(v.$$typeof){case ky:return\"<\"+(g(v.value)||\"Unknown\")+\">\";case Qy:return\":has(\"+(bf(v)||\"\")+\")\";case Ty:return'[role=\"'+v.value+'\"]';case Ry:return'\"'+v.value+'\"';case Jg:return'[data-testname=\"'+v.value+'\"]';default:throw Error(c(365,v))}}function c2(v,D){var Q=[];v=[v,0];for(var H=0;H<v.length;){var V=v[H++],ne=v[H++],Se=D[ne];if(V.tag!==5||!Pr(V)){for(;Se!=null&&Df(V,Se);)ne++,Se=D[ne];if(ne===D.length)Q.push(V);else for(V=V.child;V!==null;)v.push(V,ne),V=V.sibling}}return Q}function u2(v,D){if(!qt)throw Error(c(363));v=Fy(v),v=c2(v,D),D=[],v=Array.from(v);for(var Q=0;Q<v.length;){var H=v[Q++];if(H.tag===5)Pr(H)||D.push(H.stateNode);else for(H=H.child;H!==null;)v.push(H),H=H.sibling}return D}var Ny=null;function zL(v){if(Ny===null)try{var D=(\"require\"+Math.random()).slice(0,7);Ny=(XS&&XS[D]).call(XS,\"timers\").setImmediate}catch{Ny=function(H){var V=new MessageChannel;V.port1.onmessage=H,V.port2.postMessage(void 0)}}return Ny(v)}var ZL=Math.ceil,Oy=f.ReactCurrentDispatcher,f2=f.ReactCurrentOwner,A2=f.IsSomeRendererActing,xr=0,ao=null,Xi=null,Ls=0,ep=0,p2=so(0),vs=0,Ly=null,Nh=0,Zg=0,Oh=0,h2=0,g2=null,d2=0,m2=1/0;function Pf(){m2=Dt()+500}var sr=null,My=!1,y2=null,gc=null,xf=!1,Xg=null,$g=90,E2=[],I2=[],vu=null,ed=0,C2=null,_y=-1,Su=0,Uy=0,td=null,rd=!1;function To(){return xr&48?Dt():_y!==-1?_y:_y=Dt()}function Ss(v){if(v=v.mode,!(v&2))return 1;if(!(v&4))return tr()===99?1:2;if(Su===0&&(Su=Nh),gy.transition!==0){Uy!==0&&(Uy=g2!==null?g2.pendingLanes:0),v=Su;var D=4186112&~Uy;return D&=-D,D===0&&(v=4186112&~v,D=v&-v,D===0&&(D=8192)),D}return v=tr(),xr&4&&v===98?v=Mt(12,Su):(v=_A(v),v=Mt(v,Su)),v}function Rl(v,D,Q){if(50<ed)throw ed=0,C2=null,Error(c(185));if(v=Hy(v,D),v===null)return null;ja(v,D,Q),v===ao&&(Oh|=D,vs===4&&Lh(v,Ls));var H=tr();D===1?xr&8&&!(xr&48)?w2(v):(da(v,Q),xr===0&&(Pf(),Tn())):(!(xr&4)||H!==98&&H!==99||(vu===null?vu=new Set([v]):vu.add(v)),da(v,Q)),g2=v}function Hy(v,D){v.lanes|=D;var Q=v.alternate;for(Q!==null&&(Q.lanes|=D),Q=v,v=v.return;v!==null;)v.childLanes|=D,Q=v.alternate,Q!==null&&(Q.childLanes|=D),Q=v,v=v.return;return Q.tag===3?Q.stateNode:null}function da(v,D){for(var Q=v.callbackNode,H=v.suspendedLanes,V=v.pingedLanes,ne=v.expirationTimes,Se=v.pendingLanes;0<Se;){var Ue=31-is(Se),At=1<<Ue,Gt=ne[Ue];if(Gt===-1){if(!(At&H)||At&V){Gt=D,ua(At);var vr=wn;ne[Ue]=10<=vr?Gt+250:6<=vr?Gt+5e3:-1}}else Gt<=D&&(v.expiredLanes|=At);Se&=~At}if(H=fa(v,v===ao?Ls:0),D=wn,H===0)Q!==null&&(Q!==lr&&ws(Q),v.callbackNode=null,v.callbackPriority=0);else{if(Q!==null){if(v.callbackPriority===D)return;Q!==lr&&ws(Q)}D===15?(Q=w2.bind(null,v),Ee===null?(Ee=[Q],Oe=_i(Qn,Ga)):Ee.push(Q),Q=lr):D===14?Q=Gi(99,w2.bind(null,v)):(Q=UA(D),Q=Gi(Q,SP.bind(null,v))),v.callbackPriority=D,v.callbackNode=Q}}function SP(v){if(_y=-1,Uy=Su=0,xr&48)throw Error(c(327));var D=v.callbackNode;if(Du()&&v.callbackNode!==D)return null;var Q=fa(v,v===ao?Ls:0);if(Q===0)return null;var H=Q,V=xr;xr|=16;var ne=xP();(ao!==v||Ls!==H)&&(Pf(),Mh(v,H));do try{eM();break}catch(Ue){PP(v,Ue)}while(!0);if(Rg(),Oy.current=ne,xr=V,Xi!==null?H=0:(ao=null,Ls=0,H=vs),Nh&Oh)Mh(v,0);else if(H!==0){if(H===2&&(xr|=64,v.hydrate&&(v.hydrate=!1,Fs(v.containerInfo)),Q=vl(v),Q!==0&&(H=nd(v,Q))),H===1)throw D=Ly,Mh(v,0),Lh(v,Q),da(v,Dt()),D;switch(v.finishedWork=v.current.alternate,v.finishedLanes=Q,H){case 0:case 1:throw Error(c(345));case 2:tp(v);break;case 3:if(Lh(v,Q),(Q&62914560)===Q&&(H=d2+500-Dt(),10<H)){if(fa(v,0)!==0)break;if(V=v.suspendedLanes,(V&Q)!==Q){To(),v.pingedLanes|=v.suspendedLanes&V;break}v.timeoutHandle=x(tp.bind(null,v),H);break}tp(v);break;case 4:if(Lh(v,Q),(Q&4186112)===Q)break;for(H=v.eventTimes,V=-1;0<Q;){var Se=31-is(Q);ne=1<<Se,Se=H[Se],Se>V&&(V=Se),Q&=~ne}if(Q=V,Q=Dt()-Q,Q=(120>Q?120:480>Q?480:1080>Q?1080:1920>Q?1920:3e3>Q?3e3:4320>Q?4320:1960*ZL(Q/1960))-Q,10<Q){v.timeoutHandle=x(tp.bind(null,v),Q);break}tp(v);break;case 5:tp(v);break;default:throw Error(c(329))}}return da(v,Dt()),v.callbackNode===D?SP.bind(null,v):null}function Lh(v,D){for(D&=~h2,D&=~Oh,v.suspendedLanes|=D,v.pingedLanes&=~D,v=v.expirationTimes;0<D;){var Q=31-is(D),H=1<<Q;v[Q]=-1,D&=~H}}function w2(v){if(xr&48)throw Error(c(327));if(Du(),v===ao&&v.expiredLanes&Ls){var D=Ls,Q=nd(v,D);Nh&Oh&&(D=fa(v,D),Q=nd(v,D))}else D=fa(v,0),Q=nd(v,D);if(v.tag!==0&&Q===2&&(xr|=64,v.hydrate&&(v.hydrate=!1,Fs(v.containerInfo)),D=vl(v),D!==0&&(Q=nd(v,D))),Q===1)throw Q=Ly,Mh(v,0),Lh(v,D),da(v,Dt()),Q;return v.finishedWork=v.current.alternate,v.finishedLanes=D,tp(v),da(v,Dt()),null}function XL(){if(vu!==null){var v=vu;vu=null,v.forEach(function(D){D.expiredLanes|=24&D.pendingLanes,da(D,Dt())})}Tn()}function DP(v,D){var Q=xr;xr|=1;try{return v(D)}finally{xr=Q,xr===0&&(Pf(),Tn())}}function bP(v,D){var Q=xr;if(Q&48)return v(D);xr|=1;try{if(v)return li(99,v.bind(null,D))}finally{xr=Q,Tn()}}function jy(v,D){xn(p2,ep),ep|=D,Nh|=D}function B2(){ep=p2.current,Rt(p2)}function Mh(v,D){v.finishedWork=null,v.finishedLanes=0;var Q=v.timeoutHandle;if(Q!==b&&(v.timeoutHandle=b,w(Q)),Xi!==null)for(Q=Xi.return;Q!==null;){var H=Q;switch(H.tag){case 1:H=H.type.childContextTypes,H!=null&&hu();break;case 3:yu(),Rt(Mi),Rt(qi),Iu();break;case 5:wt(H);break;case 4:yu();break;case 13:Rt(di);break;case 19:Rt(di);break;case 10:Ng(H);break;case 23:case 24:B2()}Q=Q.return}ao=v,Xi=bu(v.current,null),Ls=ep=Nh=D,vs=0,Ly=null,h2=Oh=Zg=0}function PP(v,D){do{var Q=Xi;try{if(Rg(),Cf.current=kt,VA){for(var H=qn.memoizedState;H!==null;){var V=H.queue;V!==null&&(V.pending=null),H=H.next}VA=!1}if(Cu=0,ki=ss=qn=null,wf=!1,f2.current=null,Q===null||Q.return===null){vs=1,Ly=D,Xi=null;break}e:{var ne=v,Se=Q.return,Ue=Q,At=D;if(D=Ls,Ue.flags|=2048,Ue.firstEffect=Ue.lastEffect=null,At!==null&&typeof At==\"object\"&&typeof At.then==\"function\"){var Gt=At;if(!(Ue.mode&2)){var vr=Ue.alternate;vr?(Ue.updateQueue=vr.updateQueue,Ue.memoizedState=vr.memoizedState,Ue.lanes=vr.lanes):(Ue.updateQueue=null,Ue.memoizedState=null)}var Lr=(di.current&1)!==0,Xt=Se;do{var zn;if(zn=Xt.tag===13){var yi=Xt.memoizedState;if(yi!==null)zn=yi.dehydrated!==null;else{var Za=Xt.memoizedProps;zn=Za.fallback===void 0?!1:Za.unstable_avoidThisFallback!==!0?!0:!Lr}}if(zn){var $e=Xt.updateQueue;if($e===null){var qe=new Set;qe.add(Gt),Xt.updateQueue=qe}else $e.add(Gt);if(!(Xt.mode&2)){if(Xt.flags|=64,Ue.flags|=16384,Ue.flags&=-2981,Ue.tag===1)if(Ue.alternate===null)Ue.tag=17;else{var ht=bl(-1,1);ht.tag=2,Pl(Ue,ht)}Ue.lanes|=1;break e}At=void 0,Ue=D;var Zt=ne.pingCache;if(Zt===null?(Zt=ne.pingCache=new VL,At=new Set,Zt.set(Gt,At)):(At=Zt.get(Gt),At===void 0&&(At=new Set,Zt.set(Gt,At))),!At.has(Ue)){At.add(Ue);var Sr=FP.bind(null,ne,Gt,Ue);Gt.then(Sr,Sr)}Xt.flags|=4096,Xt.lanes=D;break e}Xt=Xt.return}while(Xt!==null);At=Error((g(Ue.type)||\"A React component\")+` suspended while rendering, but no fallback UI was specified.\n\nAdd a <Suspense fallback=...> component higher in the tree to provide a loading indicator or placeholder to display.`)}vs!==5&&(vs=2),At=Wg(At,Ue),Xt=Se;do{switch(Xt.tag){case 3:ne=At,Xt.flags|=4096,D&=-D,Xt.lanes|=D;var Xn=i2(Xt,ne,D);yy(Xt,Xn);break e;case 1:ne=At;var kr=Xt.type,Rn=Xt.stateNode;if(!(Xt.flags&64)&&(typeof kr.getDerivedStateFromError==\"function\"||Rn!==null&&typeof Rn.componentDidCatch==\"function\"&&(gc===null||!gc.has(Rn)))){Xt.flags|=4096,D&=-D,Xt.lanes|=D;var Un=Vg(Xt,ne,D);yy(Xt,Un);break e}}Xt=Xt.return}while(Xt!==null)}QP(Q)}catch(zr){D=zr,Xi===Q&&Q!==null&&(Xi=Q=Q.return);continue}break}while(!0)}function xP(){var v=Oy.current;return Oy.current=kt,v===null?kt:v}function nd(v,D){var Q=xr;xr|=16;var H=xP();ao===v&&Ls===D||Mh(v,D);do try{$L();break}catch(V){PP(v,V)}while(!0);if(Rg(),xr=Q,Oy.current=H,Xi!==null)throw Error(c(261));return ao=null,Ls=0,vs}function $L(){for(;Xi!==null;)kP(Xi)}function eM(){for(;Xi!==null&&!Sl();)kP(Xi)}function kP(v){var D=NP(v.alternate,v,ep);v.memoizedProps=v.pendingProps,D===null?QP(v):Xi=D,f2.current=null}function QP(v){var D=v;do{var Q=D.alternate;if(v=D.return,D.flags&2048){if(Q=YL(D),Q!==null){Q.flags&=2047,Xi=Q;return}v!==null&&(v.firstEffect=v.lastEffect=null,v.flags|=2048)}else{if(Q=WL(Q,D,ep),Q!==null){Xi=Q;return}if(Q=D,Q.tag!==24&&Q.tag!==23||Q.memoizedState===null||ep&1073741824||!(Q.mode&4)){for(var H=0,V=Q.child;V!==null;)H|=V.lanes|V.childLanes,V=V.sibling;Q.childLanes=H}v!==null&&!(v.flags&2048)&&(v.firstEffect===null&&(v.firstEffect=D.firstEffect),D.lastEffect!==null&&(v.lastEffect!==null&&(v.lastEffect.nextEffect=D.firstEffect),v.lastEffect=D.lastEffect),1<D.flags&&(v.lastEffect!==null?v.lastEffect.nextEffect=D:v.firstEffect=D,v.lastEffect=D))}if(D=D.sibling,D!==null){Xi=D;return}Xi=D=v}while(D!==null);vs===0&&(vs=5)}function tp(v){var D=tr();return li(99,tM.bind(null,v,D)),null}function tM(v,D){do Du();while(Xg!==null);if(xr&48)throw Error(c(327));var Q=v.finishedWork;if(Q===null)return null;if(v.finishedWork=null,v.finishedLanes=0,Q===v.current)throw Error(c(177));v.callbackNode=null;var H=Q.lanes|Q.childLanes,V=H,ne=v.pendingLanes&~V;v.pendingLanes=V,v.suspendedLanes=0,v.pingedLanes=0,v.expiredLanes&=V,v.mutableReadLanes&=V,v.entangledLanes&=V,V=v.entanglements;for(var Se=v.eventTimes,Ue=v.expirationTimes;0<ne;){var At=31-is(ne),Gt=1<<At;V[At]=0,Se[At]=-1,Ue[At]=-1,ne&=~Gt}if(vu!==null&&!(H&24)&&vu.has(v)&&vu.delete(v),v===ao&&(Xi=ao=null,Ls=0),1<Q.flags?Q.lastEffect!==null?(Q.lastEffect.nextEffect=Q,H=Q.firstEffect):H=Q:H=Q.firstEffect,H!==null){V=xr,xr|=32,f2.current=null,td=rt(v.containerInfo),rd=!1,sr=H;do try{rM()}catch(qe){if(sr===null)throw Error(c(330));kf(sr,qe),sr=sr.nextEffect}while(sr!==null);td=null,sr=H;do try{for(Se=v;sr!==null;){var vr=sr.flags;if(vr&16&&F&&pf(sr.stateNode),vr&128){var Lr=sr.alternate;if(Lr!==null){var Xt=Lr.ref;Xt!==null&&(typeof Xt==\"function\"?Xt(null):Xt.current=null)}}switch(vr&1038){case 2:wP(sr),sr.flags&=-3;break;case 6:wP(sr),sr.flags&=-3,l2(sr.alternate,sr);break;case 1024:sr.flags&=-1025;break;case 1028:sr.flags&=-1025,l2(sr.alternate,sr);break;case 4:l2(sr.alternate,sr);break;case 8:Ue=Se,ne=sr,F?BP(Ue,ne):IP(Ue,ne);var zn=ne.alternate;xy(ne),zn!==null&&xy(zn)}sr=sr.nextEffect}}catch(qe){if(sr===null)throw Error(c(330));kf(sr,qe),sr=sr.nextEffect}while(sr!==null);rd&&lt(),Fe(v.containerInfo),v.current=Q,sr=H;do try{for(vr=v;sr!==null;){var yi=sr.flags;if(yi&36&&yP(vr,sr.alternate,sr),yi&128){Lr=void 0;var Za=sr.ref;if(Za!==null){var $e=sr.stateNode;switch(sr.tag){case 5:Lr=Re($e);break;default:Lr=$e}typeof Za==\"function\"?Za(Lr):Za.current=Lr}}sr=sr.nextEffect}}catch(qe){if(sr===null)throw Error(c(330));kf(sr,qe),sr=sr.nextEffect}while(sr!==null);sr=null,ee(),xr=V}else v.current=Q;if(xf)xf=!1,Xg=v,$g=D;else for(sr=H;sr!==null;)D=sr.nextEffect,sr.nextEffect=null,sr.flags&8&&(yi=sr,yi.sibling=null,yi.stateNode=null),sr=D;if(H=v.pendingLanes,H===0&&(gc=null),H===1?v===C2?ed++:(ed=0,C2=v):ed=0,Q=Q.stateNode,Ha&&typeof Ha.onCommitFiberRoot==\"function\")try{Ha.onCommitFiberRoot(Xe,Q,void 0,(Q.current.flags&64)===64)}catch{}if(da(v,Dt()),My)throw My=!1,v=y2,y2=null,v;return xr&8||Tn(),null}function rM(){for(;sr!==null;){var v=sr.alternate;rd||td===null||(sr.flags&8?De(sr,td)&&(rd=!0,Te()):sr.tag===13&&JL(v,sr)&&De(sr,td)&&(rd=!0,Te()));var D=sr.flags;D&256&&by(v,sr),!(D&512)||xf||(xf=!0,Gi(97,function(){return Du(),null})),sr=sr.nextEffect}}function Du(){if($g!==90){var v=97<$g?97:$g;return $g=90,li(v,iM)}return!1}function nM(v,D){E2.push(D,v),xf||(xf=!0,Gi(97,function(){return Du(),null}))}function TP(v,D){I2.push(D,v),xf||(xf=!0,Gi(97,function(){return Du(),null}))}function iM(){if(Xg===null)return!1;var v=Xg;if(Xg=null,xr&48)throw Error(c(331));var D=xr;xr|=32;var Q=I2;I2=[];for(var H=0;H<Q.length;H+=2){var V=Q[H],ne=Q[H+1],Se=V.destroy;if(V.destroy=void 0,typeof Se==\"function\")try{Se()}catch(At){if(ne===null)throw Error(c(330));kf(ne,At)}}for(Q=E2,E2=[],H=0;H<Q.length;H+=2){V=Q[H],ne=Q[H+1];try{var Ue=V.create;V.destroy=Ue()}catch(At){if(ne===null)throw Error(c(330));kf(ne,At)}}for(Ue=v.current.firstEffect;Ue!==null;)v=Ue.nextEffect,Ue.nextEffect=null,Ue.flags&8&&(Ue.sibling=null,Ue.stateNode=null),Ue=v;return xr=D,Tn(),!0}function RP(v,D,Q){D=Wg(Q,D),D=i2(v,D,1),Pl(v,D),D=To(),v=Hy(v,1),v!==null&&(ja(v,1,D),da(v,D))}function kf(v,D){if(v.tag===3)RP(v,v,D);else for(var Q=v.return;Q!==null;){if(Q.tag===3){RP(Q,v,D);break}else if(Q.tag===1){var H=Q.stateNode;if(typeof Q.type.getDerivedStateFromError==\"function\"||typeof H.componentDidCatch==\"function\"&&(gc===null||!gc.has(H))){v=Wg(D,v);var V=Vg(Q,v,1);if(Pl(Q,V),V=To(),Q=Hy(Q,1),Q!==null)ja(Q,1,V),da(Q,V);else if(typeof H.componentDidCatch==\"function\"&&(gc===null||!gc.has(H)))try{H.componentDidCatch(D,v)}catch{}break}}Q=Q.return}}function FP(v,D,Q){var H=v.pingCache;H!==null&&H.delete(D),D=To(),v.pingedLanes|=v.suspendedLanes&Q,ao===v&&(Ls&Q)===Q&&(vs===4||vs===3&&(Ls&62914560)===Ls&&500>Dt()-d2?Mh(v,0):h2|=Q),da(v,D)}function sM(v,D){var Q=v.stateNode;Q!==null&&Q.delete(D),D=0,D===0&&(D=v.mode,D&2?D&4?(Su===0&&(Su=Nh),D=kn(62914560&~Su),D===0&&(D=4194304)):D=tr()===99?1:2:D=1),Q=To(),v=Hy(v,D),v!==null&&(ja(v,D,Q),da(v,Q))}var NP;NP=function(v,D,Q){var H=D.lanes;if(v!==null)if(v.memoizedProps!==D.pendingProps||Mi.current)Ke=!0;else if(Q&H)Ke=!!(v.flags&16384);else{switch(Ke=!1,D.tag){case 3:Sy(D),Hg();break;case 5:If(D);break;case 1:Jn(D.type)&&Ma(D);break;case 4:Mg(D,D.stateNode.containerInfo);break;case 10:Fg(D,D.memoizedProps.value);break;case 13:if(D.memoizedState!==null)return Q&D.child.childLanes?r2(v,D,Q):(xn(di,di.current&1),D=Gn(v,D,Q),D!==null?D.sibling:null);xn(di,di.current&1);break;case 19:if(H=(Q&D.childLanes)!==0,v.flags&64){if(H)return mP(v,D,Q);D.flags|=64}var V=D.memoizedState;if(V!==null&&(V.rendering=null,V.tail=null,V.lastEffect=null),xn(di,di.current),H)break;return null;case 23:case 24:return D.lanes=0,mi(v,D,Q)}return Gn(v,D,Q)}else Ke=!1;switch(D.lanes=0,D.tag){case 2:if(H=D.type,v!==null&&(v.alternate=null,D.alternate=null,D.flags|=2),v=D.pendingProps,V=dn(D,qi.current),mf(D,Q),V=qg(null,D,H,v,V,Q),D.flags|=1,typeof V==\"object\"&&V!==null&&typeof V.render==\"function\"&&V.$$typeof===void 0){if(D.tag=1,D.memoizedState=null,D.updateQueue=null,Jn(H)){var ne=!0;Ma(D)}else ne=!1;D.memoizedState=V.state!==null&&V.state!==void 0?V.state:null,Sh(D);var Se=H.getDerivedStateFromProps;typeof Se==\"function\"&&jA(D,H,Se,v),V.updater=qA,D.stateNode=V,V._reactInternals=D,xo(D,H,v,Q),D=t2(null,D,H,!0,ne,Q)}else D.tag=0,ft(null,D,V,Q),D=D.child;return D;case 16:V=D.elementType;e:{switch(v!==null&&(v.alternate=null,D.alternate=null,D.flags|=2),v=D.pendingProps,ne=V._init,V=ne(V._payload),D.type=V,ne=D.tag=aM(V),v=bo(V,v),ne){case 0:D=zA(null,D,V,v,Q);break e;case 1:D=dP(null,D,V,v,Q);break e;case 11:D=dr(null,D,V,v,Q);break e;case 14:D=Br(null,D,V,bo(V.type,v),H,Q);break e}throw Error(c(306,V,\"\"))}return D;case 0:return H=D.type,V=D.pendingProps,V=D.elementType===H?V:bo(H,V),zA(v,D,H,V,Q);case 1:return H=D.type,V=D.pendingProps,V=D.elementType===H?V:bo(H,V),dP(v,D,H,V,Q);case 3:if(Sy(D),H=D.updateQueue,v===null||H===null)throw Error(c(282));if(H=D.pendingProps,V=D.memoizedState,V=V!==null?V.element:null,Og(v,D),HA(D,H,null,Q),H=D.memoizedState.element,H===V)Hg(),D=Gn(v,D,Q);else{if(V=D.stateNode,(ne=V.hydrate)&&(Z?(pa=fu(D.stateNode.containerInfo),Ya=D,ne=Va=!0):ne=!1),ne){if(Z&&(v=V.mutableSourceEagerHydrationData,v!=null))for(V=0;V<v.length;V+=2)ne=v[V],Se=v[V+1],y?ne._workInProgressVersionPrimary=Se:ne._workInProgressVersionSecondary=Se,Eu.push(ne);for(Q=e2(D,null,H,Q),D.child=Q;Q;)Q.flags=Q.flags&-3|1024,Q=Q.sibling}else ft(v,D,H,Q),Hg();D=D.child}return D;case 5:return If(D),v===null&&Ug(D),H=D.type,V=D.pendingProps,ne=v!==null?v.memoizedProps:null,Se=V.children,it(H,V)?Se=null:ne!==null&&it(H,ne)&&(D.flags|=16),Bs(v,D),ft(v,D,Se,Q),D.child;case 6:return v===null&&Ug(D),null;case 13:return r2(v,D,Q);case 4:return Mg(D,D.stateNode.containerInfo),H=D.pendingProps,v===null?D.child=Lg(D,null,H,Q):ft(v,D,H,Q),D.child;case 11:return H=D.type,V=D.pendingProps,V=D.elementType===H?V:bo(H,V),dr(v,D,H,V,Q);case 7:return ft(v,D,D.pendingProps,Q),D.child;case 8:return ft(v,D,D.pendingProps.children,Q),D.child;case 12:return ft(v,D,D.pendingProps.children,Q),D.child;case 10:e:{if(H=D.type._context,V=D.pendingProps,Se=D.memoizedProps,ne=V.value,Fg(D,ne),Se!==null){var Ue=Se.value;if(ne=Do(Ue,ne)?0:(typeof H._calculateChangedBits==\"function\"?H._calculateChangedBits(Ue,ne):1073741823)|0,ne===0){if(Se.children===V.children&&!Mi.current){D=Gn(v,D,Q);break e}}else for(Ue=D.child,Ue!==null&&(Ue.return=D);Ue!==null;){var At=Ue.dependencies;if(At!==null){Se=Ue.child;for(var Gt=At.firstContext;Gt!==null;){if(Gt.context===H&&Gt.observedBits&ne){Ue.tag===1&&(Gt=bl(-1,Q&-Q),Gt.tag=2,Pl(Ue,Gt)),Ue.lanes|=Q,Gt=Ue.alternate,Gt!==null&&(Gt.lanes|=Q),my(Ue.return,Q),At.lanes|=Q;break}Gt=Gt.next}}else Se=Ue.tag===10&&Ue.type===D.type?null:Ue.child;if(Se!==null)Se.return=Ue;else for(Se=Ue;Se!==null;){if(Se===D){Se=null;break}if(Ue=Se.sibling,Ue!==null){Ue.return=Se.return,Se=Ue;break}Se=Se.return}Ue=Se}}ft(v,D,V.children,Q),D=D.child}return D;case 9:return V=D.type,ne=D.pendingProps,H=ne.children,mf(D,Q),V=Po(V,ne.unstable_observedBits),H=H(V),D.flags|=1,ft(v,D,H,Q),D.child;case 14:return V=D.type,ne=bo(V,D.pendingProps),ne=bo(V.type,ne),Br(v,D,V,ne,H,Q);case 15:return _n(v,D,D.type,D.pendingProps,H,Q);case 17:return H=D.type,V=D.pendingProps,V=D.elementType===H?V:bo(H,V),v!==null&&(v.alternate=null,D.alternate=null,D.flags|=2),D.tag=1,Jn(H)?(v=!0,Ma(D)):v=!1,mf(D,Q),xt(D,H,V),xo(D,H,V,Q),t2(null,D,H,!0,v,Q);case 19:return mP(v,D,Q);case 23:return mi(v,D,Q);case 24:return mi(v,D,Q)}throw Error(c(156,D.tag))};var qy={current:!1},Ms=n.unstable_flushAllWithoutAsserting,OP=typeof Ms==\"function\";function v2(){if(Ms!==void 0)return Ms();for(var v=!1;Du();)v=!0;return v}function ma(v){try{v2(),zL(function(){v2()?ma(v):v()})}catch(D){v(D)}}var Ja=0,Gy=!1;function oM(v,D,Q,H){this.tag=v,this.key=Q,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=D,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=H,this.flags=0,this.lastEffect=this.firstEffect=this.nextEffect=null,this.childLanes=this.lanes=0,this.alternate=null}function za(v,D,Q,H){return new oM(v,D,Q,H)}function S2(v){return v=v.prototype,!(!v||!v.isReactComponent)}function aM(v){if(typeof v==\"function\")return S2(v)?1:0;if(v!=null){if(v=v.$$typeof,v===R)return 11;if(v===W)return 14}return 2}function bu(v,D){var Q=v.alternate;return Q===null?(Q=za(v.tag,D,v.key,v.mode),Q.elementType=v.elementType,Q.type=v.type,Q.stateNode=v.stateNode,Q.alternate=v,v.alternate=Q):(Q.pendingProps=D,Q.type=v.type,Q.flags=0,Q.nextEffect=null,Q.firstEffect=null,Q.lastEffect=null),Q.childLanes=v.childLanes,Q.lanes=v.lanes,Q.child=v.child,Q.memoizedProps=v.memoizedProps,Q.memoizedState=v.memoizedState,Q.updateQueue=v.updateQueue,D=v.dependencies,Q.dependencies=D===null?null:{lanes:D.lanes,firstContext:D.firstContext},Q.sibling=v.sibling,Q.index=v.index,Q.ref=v.ref,Q}function id(v,D,Q,H,V,ne){var Se=2;if(H=v,typeof v==\"function\")S2(v)&&(Se=1);else if(typeof v==\"string\")Se=5;else e:switch(v){case E:return Qf(Q.children,V,ne,D);case Ae:Se=8,V|=16;break;case C:Se=8,V|=1;break;case S:return v=za(12,Q,D,V|8),v.elementType=S,v.type=S,v.lanes=ne,v;case N:return v=za(13,Q,D,V),v.type=N,v.elementType=N,v.lanes=ne,v;case U:return v=za(19,Q,D,V),v.elementType=U,v.lanes=ne,v;case ce:return D2(Q,V,ne,D);case me:return v=za(24,Q,D,V),v.elementType=me,v.lanes=ne,v;default:if(typeof v==\"object\"&&v!==null)switch(v.$$typeof){case P:Se=10;break e;case I:Se=9;break e;case R:Se=11;break e;case W:Se=14;break e;case te:Se=16,H=null;break e;case ie:Se=22;break e}throw Error(c(130,v==null?v:typeof v,\"\"))}return D=za(Se,Q,D,V),D.elementType=v,D.type=H,D.lanes=ne,D}function Qf(v,D,Q,H){return v=za(7,v,H,D),v.lanes=Q,v}function D2(v,D,Q,H){return v=za(23,v,H,D),v.elementType=ce,v.lanes=Q,v}function b2(v,D,Q){return v=za(6,v,null,D),v.lanes=Q,v}function Ro(v,D,Q){return D=za(4,v.children!==null?v.children:[],v.key,D),D.lanes=Q,D.stateNode={containerInfo:v.containerInfo,pendingChildren:null,implementation:v.implementation},D}function lM(v,D,Q){this.tag=D,this.containerInfo=v,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=b,this.pendingContext=this.context=null,this.hydrate=Q,this.callbackNode=null,this.callbackPriority=0,this.eventTimes=Aa(0),this.expirationTimes=Aa(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=Aa(0),Z&&(this.mutableSourceEagerHydrationData=null)}function LP(v){var D=v._reactInternals;if(D===void 0)throw typeof v.render==\"function\"?Error(c(188)):Error(c(268,Object.keys(v)));return v=se(D),v===null?null:v.stateNode}function MP(v,D){if(v=v.memoizedState,v!==null&&v.dehydrated!==null){var Q=v.retryLane;v.retryLane=Q!==0&&Q<D?Q:D}}function Wy(v,D){MP(v,D),(v=v.alternate)&&MP(v,D)}function cM(v){return v=se(v),v===null?null:v.stateNode}function uM(){return null}return r.IsThisRendererActing=qy,r.act=function(v){function D(){Ja--,A2.current=Q,qy.current=H}Gy===!1&&(Gy=!0,console.error(\"act(...) is not supported in production builds of React, and might not behave as expected.\")),Ja++;var Q=A2.current,H=qy.current;A2.current=!0,qy.current=!0;try{var V=DP(v)}catch(ne){throw D(),ne}if(V!==null&&typeof V==\"object\"&&typeof V.then==\"function\")return{then:function(ne,Se){V.then(function(){1<Ja||OP===!0&&Q===!0?(D(),ne()):ma(function(Ue){D(),Ue?Se(Ue):ne()})},function(Ue){D(),Se(Ue)})}};try{Ja!==1||OP!==!1&&Q!==!1||v2(),D()}catch(ne){throw D(),ne}return{then:function(ne){ne()}}},r.attemptContinuousHydration=function(v){if(v.tag===13){var D=To();Rl(v,67108864,D),Wy(v,67108864)}},r.attemptHydrationAtCurrentPriority=function(v){if(v.tag===13){var D=To(),Q=Ss(v);Rl(v,Q,D),Wy(v,Q)}},r.attemptSynchronousHydration=function(v){switch(v.tag){case 3:var D=v.stateNode;if(D.hydrate){var Q=ua(D.pendingLanes);D.expiredLanes|=Q&D.pendingLanes,da(D,Dt()),!(xr&48)&&(Pf(),Tn())}break;case 13:var H=To();bP(function(){return Rl(v,1,H)}),Wy(v,4)}},r.attemptUserBlockingHydration=function(v){if(v.tag===13){var D=To();Rl(v,4,D),Wy(v,4)}},r.batchedEventUpdates=function(v,D){var Q=xr;xr|=2;try{return v(D)}finally{xr=Q,xr===0&&(Pf(),Tn())}},r.batchedUpdates=DP,r.createComponentSelector=function(v){return{$$typeof:ky,value:v}},r.createContainer=function(v,D,Q){return v=new lM(v,D,Q),D=za(3,null,null,D===2?7:D===1?3:0),v.current=D,D.stateNode=v,Sh(D),v},r.createHasPsuedoClassSelector=function(v){return{$$typeof:Qy,value:v}},r.createPortal=function(v,D,Q){var H=3<arguments.length&&arguments[3]!==void 0?arguments[3]:null;return{$$typeof:h,key:H==null?null:\"\"+H,children:v,containerInfo:D,implementation:Q}},r.createRoleSelector=function(v){return{$$typeof:Ty,value:v}},r.createTestNameSelector=function(v){return{$$typeof:Jg,value:v}},r.createTextSelector=function(v){return{$$typeof:Ry,value:v}},r.deferredUpdates=function(v){return li(97,v)},r.discreteUpdates=function(v,D,Q,H,V){var ne=xr;xr|=4;try{return li(98,v.bind(null,D,Q,H,V))}finally{xr=ne,xr===0&&(Pf(),Tn())}},r.findAllNodes=u2,r.findBoundingRects=function(v,D){if(!qt)throw Error(c(363));D=u2(v,D),v=[];for(var Q=0;Q<D.length;Q++)v.push(Pt(D[Q]));for(D=v.length-1;0<D;D--){Q=v[D];for(var H=Q.x,V=H+Q.width,ne=Q.y,Se=ne+Q.height,Ue=D-1;0<=Ue;Ue--)if(D!==Ue){var At=v[Ue],Gt=At.x,vr=Gt+At.width,Lr=At.y,Xt=Lr+At.height;if(H>=Gt&&ne>=Lr&&V<=vr&&Se<=Xt){v.splice(D,1);break}else if(H!==Gt||Q.width!==At.width||Xt<ne||Lr>Se){if(!(ne!==Lr||Q.height!==At.height||vr<H||Gt>V)){Gt>H&&(At.width+=Gt-H,At.x=H),vr<V&&(At.width=V-Gt),v.splice(D,1);break}}else{Lr>ne&&(At.height+=Lr-ne,At.y=ne),Xt<Se&&(At.height=Se-Lr),v.splice(D,1);break}}}return v},r.findHostInstance=LP,r.findHostInstanceWithNoPortals=function(v){return v=X(v),v===null?null:v.tag===20?v.stateNode.instance:v.stateNode},r.findHostInstanceWithWarning=function(v){return LP(v)},r.flushControlled=function(v){var D=xr;xr|=1;try{li(99,v)}finally{xr=D,xr===0&&(Pf(),Tn())}},r.flushDiscreteUpdates=function(){!(xr&49)&&(XL(),Du())},r.flushPassiveEffects=Du,r.flushSync=bP,r.focusWithin=function(v,D){if(!qt)throw Error(c(363));for(v=Fy(v),D=c2(v,D),D=Array.from(D),v=0;v<D.length;){var Q=D[v++];if(!Pr(Q)){if(Q.tag===5&&Nr(Q.stateNode))return!0;for(Q=Q.child;Q!==null;)D.push(Q),Q=Q.sibling}}return!1},r.getCurrentUpdateLanePriority=function(){return cc},r.getFindAllNodesFailureDescription=function(v,D){if(!qt)throw Error(c(363));var Q=0,H=[];v=[Fy(v),0];for(var V=0;V<v.length;){var ne=v[V++],Se=v[V++],Ue=D[Se];if((ne.tag!==5||!Pr(ne))&&(Df(ne,Ue)&&(H.push(bf(Ue)),Se++,Se>Q&&(Q=Se)),Se<D.length))for(ne=ne.child;ne!==null;)v.push(ne,Se),ne=ne.sibling}if(Q<D.length){for(v=[];Q<D.length;Q++)v.push(bf(D[Q]));return`findAllNodes was able to match part of the selector:\n  `+(H.join(\" > \")+`\n\nNo matching component was found for:\n  `)+v.join(\" > \")}return null},r.getPublicRootInstance=function(v){if(v=v.current,!v.child)return null;switch(v.child.tag){case 5:return Re(v.child.stateNode);default:return v.child.stateNode}},r.injectIntoDevTools=function(v){if(v={bundleType:v.bundleType,version:v.version,rendererPackageName:v.rendererPackageName,rendererConfig:v.rendererConfig,overrideHookState:null,overrideHookStateDeletePath:null,overrideHookStateRenamePath:null,overrideProps:null,overridePropsDeletePath:null,overridePropsRenamePath:null,setSuspenseHandler:null,scheduleUpdate:null,currentDispatcherRef:f.ReactCurrentDispatcher,findHostInstanceByFiber:cM,findFiberByHostInstance:v.findFiberByHostInstance||uM,findHostInstancesForRefresh:null,scheduleRefresh:null,scheduleRoot:null,setRefreshHandler:null,getCurrentFiber:null},typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>\"u\")v=!1;else{var D=__REACT_DEVTOOLS_GLOBAL_HOOK__;if(!D.isDisabled&&D.supportsFiber)try{Xe=D.inject(v),Ha=D}catch{}v=!0}return v},r.observeVisibleRects=function(v,D,Q,H){if(!qt)throw Error(c(363));v=u2(v,D);var V=nn(v,Q,H).disconnect;return{disconnect:function(){V()}}},r.registerMutableSourceForHydration=function(v,D){var Q=D._getVersion;Q=Q(D._source),v.mutableSourceEagerHydrationData==null?v.mutableSourceEagerHydrationData=[D,Q]:v.mutableSourceEagerHydrationData.push(D,Q)},r.runWithPriority=function(v,D){var Q=cc;try{return cc=v,D()}finally{cc=Q}},r.shouldSuspend=function(){return!1},r.unbatchedUpdates=function(v,D){var Q=xr;xr&=-2,xr|=8;try{return v(D)}finally{xr=Q,xr===0&&(Pf(),Tn())}},r.updateContainer=function(v,D,Q,H){var V=D.current,ne=To(),Se=Ss(V);e:if(Q){Q=Q._reactInternals;t:{if(we(Q)!==Q||Q.tag!==1)throw Error(c(170));var Ue=Q;do{switch(Ue.tag){case 3:Ue=Ue.stateNode.context;break t;case 1:if(Jn(Ue.type)){Ue=Ue.stateNode.__reactInternalMemoizedMergedChildContext;break t}}Ue=Ue.return}while(Ue!==null);throw Error(c(171))}if(Q.tag===1){var At=Q.type;if(Jn(At)){Q=La(Q,At,Ue);break e}}Q=Ue}else Q=ca;return D.context===null?D.context=Q:D.pendingContext=Q,D=bl(ne,Se),D.payload={element:v},H=H===void 0?null:H,H!==null&&(D.callback=H),Pl(V,D),Rl(V,Se,ne),Se},r}});var xDe=L((Xpr,PDe)=>{\"use strict\";PDe.exports=bDe()});var QDe=L(($pr,kDe)=>{\"use strict\";var mPt={ALIGN_COUNT:8,ALIGN_AUTO:0,ALIGN_FLEX_START:1,ALIGN_CENTER:2,ALIGN_FLEX_END:3,ALIGN_STRETCH:4,ALIGN_BASELINE:5,ALIGN_SPACE_BETWEEN:6,ALIGN_SPACE_AROUND:7,DIMENSION_COUNT:2,DIMENSION_WIDTH:0,DIMENSION_HEIGHT:1,DIRECTION_COUNT:3,DIRECTION_INHERIT:0,DIRECTION_LTR:1,DIRECTION_RTL:2,DISPLAY_COUNT:2,DISPLAY_FLEX:0,DISPLAY_NONE:1,EDGE_COUNT:9,EDGE_LEFT:0,EDGE_TOP:1,EDGE_RIGHT:2,EDGE_BOTTOM:3,EDGE_START:4,EDGE_END:5,EDGE_HORIZONTAL:6,EDGE_VERTICAL:7,EDGE_ALL:8,EXPERIMENTAL_FEATURE_COUNT:1,EXPERIMENTAL_FEATURE_WEB_FLEX_BASIS:0,FLEX_DIRECTION_COUNT:4,FLEX_DIRECTION_COLUMN:0,FLEX_DIRECTION_COLUMN_REVERSE:1,FLEX_DIRECTION_ROW:2,FLEX_DIRECTION_ROW_REVERSE:3,JUSTIFY_COUNT:6,JUSTIFY_FLEX_START:0,JUSTIFY_CENTER:1,JUSTIFY_FLEX_END:2,JUSTIFY_SPACE_BETWEEN:3,JUSTIFY_SPACE_AROUND:4,JUSTIFY_SPACE_EVENLY:5,LOG_LEVEL_COUNT:6,LOG_LEVEL_ERROR:0,LOG_LEVEL_WARN:1,LOG_LEVEL_INFO:2,LOG_LEVEL_DEBUG:3,LOG_LEVEL_VERBOSE:4,LOG_LEVEL_FATAL:5,MEASURE_MODE_COUNT:3,MEASURE_MODE_UNDEFINED:0,MEASURE_MODE_EXACTLY:1,MEASURE_MODE_AT_MOST:2,NODE_TYPE_COUNT:2,NODE_TYPE_DEFAULT:0,NODE_TYPE_TEXT:1,OVERFLOW_COUNT:3,OVERFLOW_VISIBLE:0,OVERFLOW_HIDDEN:1,OVERFLOW_SCROLL:2,POSITION_TYPE_COUNT:2,POSITION_TYPE_RELATIVE:0,POSITION_TYPE_ABSOLUTE:1,PRINT_OPTIONS_COUNT:3,PRINT_OPTIONS_LAYOUT:1,PRINT_OPTIONS_STYLE:2,PRINT_OPTIONS_CHILDREN:4,UNIT_COUNT:4,UNIT_UNDEFINED:0,UNIT_POINT:1,UNIT_PERCENT:2,UNIT_AUTO:3,WRAP_COUNT:3,WRAP_NO_WRAP:0,WRAP_WRAP:1,WRAP_WRAP_REVERSE:2};kDe.exports=mPt});var NDe=L((ehr,FDe)=>{\"use strict\";var yPt=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var r=arguments[e];for(var s in r)Object.prototype.hasOwnProperty.call(r,s)&&(t[s]=r[s])}return t},NF=function(){function t(e,r){for(var s=0;s<r.length;s++){var a=r[s];a.enumerable=a.enumerable||!1,a.configurable=!0,\"value\"in a&&(a.writable=!0),Object.defineProperty(e,a.key,a)}}return function(e,r,s){return r&&t(e.prototype,r),s&&t(e,s),e}}();function EW(t,e,r){return e in t?Object.defineProperty(t,e,{value:r,enumerable:!0,configurable:!0,writable:!0}):t[e]=r,t}function IW(t,e){if(!(t instanceof e))throw new TypeError(\"Cannot call a class as a function\")}var rf=QDe(),EPt=function(){function t(e,r,s,a,n,c){IW(this,t),this.left=e,this.right=r,this.top=s,this.bottom=a,this.width=n,this.height=c}return NF(t,[{key:\"fromJS\",value:function(r){r(this.left,this.right,this.top,this.bottom,this.width,this.height)}},{key:\"toString\",value:function(){return\"<Layout#\"+this.left+\":\"+this.right+\";\"+this.top+\":\"+this.bottom+\";\"+this.width+\":\"+this.height+\">\"}}]),t}(),TDe=function(){NF(t,null,[{key:\"fromJS\",value:function(r){var s=r.width,a=r.height;return new t(s,a)}}]);function t(e,r){IW(this,t),this.width=e,this.height=r}return NF(t,[{key:\"fromJS\",value:function(r){r(this.width,this.height)}},{key:\"toString\",value:function(){return\"<Size#\"+this.width+\"x\"+this.height+\">\"}}]),t}(),RDe=function(){function t(e,r){IW(this,t),this.unit=e,this.value=r}return NF(t,[{key:\"fromJS\",value:function(r){r(this.unit,this.value)}},{key:\"toString\",value:function(){switch(this.unit){case rf.UNIT_POINT:return String(this.value);case rf.UNIT_PERCENT:return this.value+\"%\";case rf.UNIT_AUTO:return\"auto\";default:return this.value+\"?\"}}},{key:\"valueOf\",value:function(){return this.value}}]),t}();FDe.exports=function(t,e){function r(c,f,p){var h=c[f];c[f]=function(){for(var E=arguments.length,C=Array(E),S=0;S<E;S++)C[S]=arguments[S];return p.call.apply(p,[this,h].concat(C))}}for(var s=[\"setPosition\",\"setMargin\",\"setFlexBasis\",\"setWidth\",\"setHeight\",\"setMinWidth\",\"setMinHeight\",\"setMaxWidth\",\"setMaxHeight\",\"setPadding\"],a=function(){var f,p=s[n],h=(f={},EW(f,rf.UNIT_POINT,e.Node.prototype[p]),EW(f,rf.UNIT_PERCENT,e.Node.prototype[p+\"Percent\"]),EW(f,rf.UNIT_AUTO,e.Node.prototype[p+\"Auto\"]),f);r(e.Node.prototype,p,function(E){for(var C=arguments.length,S=Array(C>1?C-1:0),P=1;P<C;P++)S[P-1]=arguments[P];var I=S.pop(),R=void 0,N=void 0;if(I===\"auto\")R=rf.UNIT_AUTO,N=void 0;else if(I instanceof RDe)R=I.unit,N=I.valueOf();else if(R=typeof I==\"string\"&&I.endsWith(\"%\")?rf.UNIT_PERCENT:rf.UNIT_POINT,N=parseFloat(I),!Number.isNaN(I)&&Number.isNaN(N))throw new Error(\"Invalid value \"+I+\" for \"+p);if(!h[R])throw new Error('Failed to execute \"'+p+`\": Unsupported unit '`+I+\"'\");if(N!==void 0){var U;return(U=h[R]).call.apply(U,[this].concat(S,[N]))}else{var W;return(W=h[R]).call.apply(W,[this].concat(S))}})},n=0;n<s.length;n++)a();return r(e.Config.prototype,\"free\",function(){e.Config.destroy(this)}),r(e.Node,\"create\",function(c,f){return f?e.Node.createWithConfig(f):e.Node.createDefault()}),r(e.Node.prototype,\"free\",function(){e.Node.destroy(this)}),r(e.Node.prototype,\"freeRecursive\",function(){for(var c=0,f=this.getChildCount();c<f;++c)this.getChild(0).freeRecursive();this.free()}),r(e.Node.prototype,\"setMeasureFunc\",function(c,f){return f?c.call(this,function(){return TDe.fromJS(f.apply(void 0,arguments))}):this.unsetMeasureFunc()}),r(e.Node.prototype,\"calculateLayout\",function(c){var f=arguments.length>1&&arguments[1]!==void 0?arguments[1]:NaN,p=arguments.length>2&&arguments[2]!==void 0?arguments[2]:NaN,h=arguments.length>3&&arguments[3]!==void 0?arguments[3]:rf.DIRECTION_LTR;return c.call(this,f,p,h)}),yPt({Config:e.Config,Node:e.Node,Layout:t(\"Layout\",EPt),Size:t(\"Size\",TDe),Value:t(\"Value\",RDe),getInstanceCount:function(){return e.getInstanceCount.apply(e,arguments)}},rf)}});var ODe=L((exports,module)=>{(function(t,e){typeof define==\"function\"&&define.amd?define([],function(){return e}):typeof module==\"object\"&&module.exports?module.exports=e:(t.nbind=t.nbind||{}).init=e})(exports,function(Module,cb){typeof Module==\"function\"&&(cb=Module,Module={}),Module.onRuntimeInitialized=function(t,e){return function(){t&&t.apply(this,arguments);try{Module.ccall(\"nbind_init\")}catch(r){e(r);return}e(null,{bind:Module._nbind_value,reflect:Module.NBind.reflect,queryType:Module.NBind.queryType,toggleLightGC:Module.toggleLightGC,lib:Module})}}(Module.onRuntimeInitialized,cb);var Module;Module||(Module=(typeof Module<\"u\"?Module:null)||{});var moduleOverrides={};for(var key in Module)Module.hasOwnProperty(key)&&(moduleOverrides[key]=Module[key]);var ENVIRONMENT_IS_WEB=!1,ENVIRONMENT_IS_WORKER=!1,ENVIRONMENT_IS_NODE=!1,ENVIRONMENT_IS_SHELL=!1;if(Module.ENVIRONMENT)if(Module.ENVIRONMENT===\"WEB\")ENVIRONMENT_IS_WEB=!0;else if(Module.ENVIRONMENT===\"WORKER\")ENVIRONMENT_IS_WORKER=!0;else if(Module.ENVIRONMENT===\"NODE\")ENVIRONMENT_IS_NODE=!0;else if(Module.ENVIRONMENT===\"SHELL\")ENVIRONMENT_IS_SHELL=!0;else throw new Error(\"The provided Module['ENVIRONMENT'] value is not valid. It must be one of: WEB|WORKER|NODE|SHELL.\");else ENVIRONMENT_IS_WEB=typeof window==\"object\",ENVIRONMENT_IS_WORKER=typeof importScripts==\"function\",ENVIRONMENT_IS_NODE=typeof process==\"object\"&&typeof Ie==\"function\"&&!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_WORKER,ENVIRONMENT_IS_SHELL=!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_NODE&&!ENVIRONMENT_IS_WORKER;if(ENVIRONMENT_IS_NODE){Module.print||(Module.print=console.log),Module.printErr||(Module.printErr=console.warn);var nodeFS,nodePath;Module.read=function(e,r){nodeFS||(nodeFS={}(\"\")),nodePath||(nodePath={}(\"\")),e=nodePath.normalize(e);var s=nodeFS.readFileSync(e);return r?s:s.toString()},Module.readBinary=function(e){var r=Module.read(e,!0);return r.buffer||(r=new Uint8Array(r)),assert(r.buffer),r},Module.load=function(e){globalEval(read(e))},Module.thisProgram||(process.argv.length>1?Module.thisProgram=process.argv[1].replace(/\\\\/g,\"/\"):Module.thisProgram=\"unknown-program\"),Module.arguments=process.argv.slice(2),typeof module<\"u\"&&(module.exports=Module),Module.inspect=function(){return\"[Emscripten Module object]\"}}else if(ENVIRONMENT_IS_SHELL)Module.print||(Module.print=print),typeof printErr<\"u\"&&(Module.printErr=printErr),typeof read<\"u\"?Module.read=read:Module.read=function(){throw\"no read() available\"},Module.readBinary=function(e){if(typeof readbuffer==\"function\")return new Uint8Array(readbuffer(e));var r=read(e,\"binary\");return assert(typeof r==\"object\"),r},typeof scriptArgs<\"u\"?Module.arguments=scriptArgs:typeof arguments<\"u\"&&(Module.arguments=arguments),typeof quit==\"function\"&&(Module.quit=function(t,e){quit(t)});else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(Module.read=function(e){var r=new XMLHttpRequest;return r.open(\"GET\",e,!1),r.send(null),r.responseText},ENVIRONMENT_IS_WORKER&&(Module.readBinary=function(e){var r=new XMLHttpRequest;return r.open(\"GET\",e,!1),r.responseType=\"arraybuffer\",r.send(null),new Uint8Array(r.response)}),Module.readAsync=function(e,r,s){var a=new XMLHttpRequest;a.open(\"GET\",e,!0),a.responseType=\"arraybuffer\",a.onload=function(){a.status==200||a.status==0&&a.response?r(a.response):s()},a.onerror=s,a.send(null)},typeof arguments<\"u\"&&(Module.arguments=arguments),typeof console<\"u\")Module.print||(Module.print=function(e){console.log(e)}),Module.printErr||(Module.printErr=function(e){console.warn(e)});else{var TRY_USE_DUMP=!1;Module.print||(Module.print=TRY_USE_DUMP&&typeof dump<\"u\"?function(t){dump(t)}:function(t){})}ENVIRONMENT_IS_WORKER&&(Module.load=importScripts),typeof Module.setWindowTitle>\"u\"&&(Module.setWindowTitle=function(t){document.title=t})}else throw\"Unknown runtime environment. Where are we?\";function globalEval(t){eval.call(null,t)}!Module.load&&Module.read&&(Module.load=function(e){globalEval(Module.read(e))}),Module.print||(Module.print=function(){}),Module.printErr||(Module.printErr=Module.print),Module.arguments||(Module.arguments=[]),Module.thisProgram||(Module.thisProgram=\"./this.program\"),Module.quit||(Module.quit=function(t,e){throw e}),Module.print=Module.print,Module.printErr=Module.printErr,Module.preRun=[],Module.postRun=[];for(var key in moduleOverrides)moduleOverrides.hasOwnProperty(key)&&(Module[key]=moduleOverrides[key]);moduleOverrides=void 0;var Runtime={setTempRet0:function(t){return tempRet0=t,t},getTempRet0:function(){return tempRet0},stackSave:function(){return STACKTOP},stackRestore:function(t){STACKTOP=t},getNativeTypeSize:function(t){switch(t){case\"i1\":case\"i8\":return 1;case\"i16\":return 2;case\"i32\":return 4;case\"i64\":return 8;case\"float\":return 4;case\"double\":return 8;default:{if(t[t.length-1]===\"*\")return Runtime.QUANTUM_SIZE;if(t[0]===\"i\"){var e=parseInt(t.substr(1));return assert(e%8===0),e/8}else return 0}}},getNativeFieldSize:function(t){return Math.max(Runtime.getNativeTypeSize(t),Runtime.QUANTUM_SIZE)},STACK_ALIGN:16,prepVararg:function(t,e){return e===\"double\"||e===\"i64\"?t&7&&(assert((t&7)===4),t+=4):assert((t&3)===0),t},getAlignSize:function(t,e,r){return!r&&(t==\"i64\"||t==\"double\")?8:t?Math.min(e||(t?Runtime.getNativeFieldSize(t):0),Runtime.QUANTUM_SIZE):Math.min(e,8)},dynCall:function(t,e,r){return r&&r.length?Module[\"dynCall_\"+t].apply(null,[e].concat(r)):Module[\"dynCall_\"+t].call(null,e)},functionPointers:[],addFunction:function(t){for(var e=0;e<Runtime.functionPointers.length;e++)if(!Runtime.functionPointers[e])return Runtime.functionPointers[e]=t,2*(1+e);throw\"Finished up all reserved function pointers. Use a higher value for RESERVED_FUNCTION_POINTERS.\"},removeFunction:function(t){Runtime.functionPointers[(t-2)/2]=null},warnOnce:function(t){Runtime.warnOnce.shown||(Runtime.warnOnce.shown={}),Runtime.warnOnce.shown[t]||(Runtime.warnOnce.shown[t]=1,Module.printErr(t))},funcWrappers:{},getFuncWrapper:function(t,e){if(t){assert(e),Runtime.funcWrappers[e]||(Runtime.funcWrappers[e]={});var r=Runtime.funcWrappers[e];return r[t]||(e.length===1?r[t]=function(){return Runtime.dynCall(e,t)}:e.length===2?r[t]=function(a){return Runtime.dynCall(e,t,[a])}:r[t]=function(){return Runtime.dynCall(e,t,Array.prototype.slice.call(arguments))}),r[t]}},getCompilerSetting:function(t){throw\"You must build with -s RETAIN_COMPILER_SETTINGS=1 for Runtime.getCompilerSetting or emscripten_get_compiler_setting to work\"},stackAlloc:function(t){var e=STACKTOP;return STACKTOP=STACKTOP+t|0,STACKTOP=STACKTOP+15&-16,e},staticAlloc:function(t){var e=STATICTOP;return STATICTOP=STATICTOP+t|0,STATICTOP=STATICTOP+15&-16,e},dynamicAlloc:function(t){var e=HEAP32[DYNAMICTOP_PTR>>2],r=(e+t+15|0)&-16;if(HEAP32[DYNAMICTOP_PTR>>2]=r,r>=TOTAL_MEMORY){var s=enlargeMemory();if(!s)return HEAP32[DYNAMICTOP_PTR>>2]=e,0}return e},alignMemory:function(t,e){var r=t=Math.ceil(t/(e||16))*(e||16);return r},makeBigInt:function(t,e,r){var s=r?+(t>>>0)+ +(e>>>0)*4294967296:+(t>>>0)+ +(e|0)*4294967296;return s},GLOBAL_BASE:8,QUANTUM_SIZE:4,__dummy__:0};Module.Runtime=Runtime;var ABORT=0,EXITSTATUS=0;function assert(t,e){t||abort(\"Assertion failed: \"+e)}function getCFunc(ident){var func=Module[\"_\"+ident];if(!func)try{func=eval(\"_\"+ident)}catch(t){}return assert(func,\"Cannot call unknown function \"+ident+\" (perhaps LLVM optimizations or closure removed it?)\"),func}var cwrap,ccall;(function(){var JSfuncs={stackSave:function(){Runtime.stackSave()},stackRestore:function(){Runtime.stackRestore()},arrayToC:function(t){var e=Runtime.stackAlloc(t.length);return writeArrayToMemory(t,e),e},stringToC:function(t){var e=0;if(t!=null&&t!==0){var r=(t.length<<2)+1;e=Runtime.stackAlloc(r),stringToUTF8(t,e,r)}return e}},toC={string:JSfuncs.stringToC,array:JSfuncs.arrayToC};ccall=function(e,r,s,a,n){var c=getCFunc(e),f=[],p=0;if(a)for(var h=0;h<a.length;h++){var E=toC[s[h]];E?(p===0&&(p=Runtime.stackSave()),f[h]=E(a[h])):f[h]=a[h]}var C=c.apply(null,f);if(r===\"string\"&&(C=Pointer_stringify(C)),p!==0){if(n&&n.async){EmterpreterAsync.asyncFinalizers.push(function(){Runtime.stackRestore(p)});return}Runtime.stackRestore(p)}return C};var sourceRegex=/^function\\s*[a-zA-Z$_0-9]*\\s*\\(([^)]*)\\)\\s*{\\s*([^*]*?)[\\s;]*(?:return\\s*(.*?)[;\\s]*)?}$/;function parseJSFunc(t){var e=t.toString().match(sourceRegex).slice(1);return{arguments:e[0],body:e[1],returnValue:e[2]}}var JSsource=null;function ensureJSsource(){if(!JSsource){JSsource={};for(var t in JSfuncs)JSfuncs.hasOwnProperty(t)&&(JSsource[t]=parseJSFunc(JSfuncs[t]))}}cwrap=function cwrap(ident,returnType,argTypes){argTypes=argTypes||[];var cfunc=getCFunc(ident),numericArgs=argTypes.every(function(t){return t===\"number\"}),numericRet=returnType!==\"string\";if(numericRet&&numericArgs)return cfunc;var argNames=argTypes.map(function(t,e){return\"$\"+e}),funcstr=\"(function(\"+argNames.join(\",\")+\") {\",nargs=argTypes.length;if(!numericArgs){ensureJSsource(),funcstr+=\"var stack = \"+JSsource.stackSave.body+\";\";for(var i=0;i<nargs;i++){var arg=argNames[i],type=argTypes[i];if(type!==\"number\"){var convertCode=JSsource[type+\"ToC\"];funcstr+=\"var \"+convertCode.arguments+\" = \"+arg+\";\",funcstr+=convertCode.body+\";\",funcstr+=arg+\"=(\"+convertCode.returnValue+\");\"}}}var cfuncname=parseJSFunc(function(){return cfunc}).returnValue;if(funcstr+=\"var ret = \"+cfuncname+\"(\"+argNames.join(\",\")+\");\",!numericRet){var strgfy=parseJSFunc(function(){return Pointer_stringify}).returnValue;funcstr+=\"ret = \"+strgfy+\"(ret);\"}return numericArgs||(ensureJSsource(),funcstr+=JSsource.stackRestore.body.replace(\"()\",\"(stack)\")+\";\"),funcstr+=\"return ret})\",eval(funcstr)}})(),Module.ccall=ccall,Module.cwrap=cwrap;function setValue(t,e,r,s){switch(r=r||\"i8\",r.charAt(r.length-1)===\"*\"&&(r=\"i32\"),r){case\"i1\":HEAP8[t>>0]=e;break;case\"i8\":HEAP8[t>>0]=e;break;case\"i16\":HEAP16[t>>1]=e;break;case\"i32\":HEAP32[t>>2]=e;break;case\"i64\":tempI64=[e>>>0,(tempDouble=e,+Math_abs(tempDouble)>=1?tempDouble>0?(Math_min(+Math_floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math_ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[t>>2]=tempI64[0],HEAP32[t+4>>2]=tempI64[1];break;case\"float\":HEAPF32[t>>2]=e;break;case\"double\":HEAPF64[t>>3]=e;break;default:abort(\"invalid type for setValue: \"+r)}}Module.setValue=setValue;function getValue(t,e,r){switch(e=e||\"i8\",e.charAt(e.length-1)===\"*\"&&(e=\"i32\"),e){case\"i1\":return HEAP8[t>>0];case\"i8\":return HEAP8[t>>0];case\"i16\":return HEAP16[t>>1];case\"i32\":return HEAP32[t>>2];case\"i64\":return HEAP32[t>>2];case\"float\":return HEAPF32[t>>2];case\"double\":return HEAPF64[t>>3];default:abort(\"invalid type for setValue: \"+e)}return null}Module.getValue=getValue;var ALLOC_NORMAL=0,ALLOC_STACK=1,ALLOC_STATIC=2,ALLOC_DYNAMIC=3,ALLOC_NONE=4;Module.ALLOC_NORMAL=ALLOC_NORMAL,Module.ALLOC_STACK=ALLOC_STACK,Module.ALLOC_STATIC=ALLOC_STATIC,Module.ALLOC_DYNAMIC=ALLOC_DYNAMIC,Module.ALLOC_NONE=ALLOC_NONE;function allocate(t,e,r,s){var a,n;typeof t==\"number\"?(a=!0,n=t):(a=!1,n=t.length);var c=typeof e==\"string\"?e:null,f;if(r==ALLOC_NONE?f=s:f=[typeof _malloc==\"function\"?_malloc:Runtime.staticAlloc,Runtime.stackAlloc,Runtime.staticAlloc,Runtime.dynamicAlloc][r===void 0?ALLOC_STATIC:r](Math.max(n,c?1:e.length)),a){var s=f,p;for(assert((f&3)==0),p=f+(n&-4);s<p;s+=4)HEAP32[s>>2]=0;for(p=f+n;s<p;)HEAP8[s++>>0]=0;return f}if(c===\"i8\")return t.subarray||t.slice?HEAPU8.set(t,f):HEAPU8.set(new Uint8Array(t),f),f;for(var h=0,E,C,S;h<n;){var P=t[h];if(typeof P==\"function\"&&(P=Runtime.getFunctionIndex(P)),E=c||e[h],E===0){h++;continue}E==\"i64\"&&(E=\"i32\"),setValue(f+h,P,E),S!==E&&(C=Runtime.getNativeTypeSize(E),S=E),h+=C}return f}Module.allocate=allocate;function getMemory(t){return staticSealed?runtimeInitialized?_malloc(t):Runtime.dynamicAlloc(t):Runtime.staticAlloc(t)}Module.getMemory=getMemory;function Pointer_stringify(t,e){if(e===0||!t)return\"\";for(var r=0,s,a=0;s=HEAPU8[t+a>>0],r|=s,!(s==0&&!e||(a++,e&&a==e)););e||(e=a);var n=\"\";if(r<128){for(var c=1024,f;e>0;)f=String.fromCharCode.apply(String,HEAPU8.subarray(t,t+Math.min(e,c))),n=n?n+f:f,t+=c,e-=c;return n}return Module.UTF8ToString(t)}Module.Pointer_stringify=Pointer_stringify;function AsciiToString(t){for(var e=\"\";;){var r=HEAP8[t++>>0];if(!r)return e;e+=String.fromCharCode(r)}}Module.AsciiToString=AsciiToString;function stringToAscii(t,e){return writeAsciiToMemory(t,e,!1)}Module.stringToAscii=stringToAscii;var UTF8Decoder=typeof TextDecoder<\"u\"?new TextDecoder(\"utf8\"):void 0;function UTF8ArrayToString(t,e){for(var r=e;t[r];)++r;if(r-e>16&&t.subarray&&UTF8Decoder)return UTF8Decoder.decode(t.subarray(e,r));for(var s,a,n,c,f,p,h=\"\";;){if(s=t[e++],!s)return h;if(!(s&128)){h+=String.fromCharCode(s);continue}if(a=t[e++]&63,(s&224)==192){h+=String.fromCharCode((s&31)<<6|a);continue}if(n=t[e++]&63,(s&240)==224?s=(s&15)<<12|a<<6|n:(c=t[e++]&63,(s&248)==240?s=(s&7)<<18|a<<12|n<<6|c:(f=t[e++]&63,(s&252)==248?s=(s&3)<<24|a<<18|n<<12|c<<6|f:(p=t[e++]&63,s=(s&1)<<30|a<<24|n<<18|c<<12|f<<6|p))),s<65536)h+=String.fromCharCode(s);else{var E=s-65536;h+=String.fromCharCode(55296|E>>10,56320|E&1023)}}}Module.UTF8ArrayToString=UTF8ArrayToString;function UTF8ToString(t){return UTF8ArrayToString(HEAPU8,t)}Module.UTF8ToString=UTF8ToString;function stringToUTF8Array(t,e,r,s){if(!(s>0))return 0;for(var a=r,n=r+s-1,c=0;c<t.length;++c){var f=t.charCodeAt(c);if(f>=55296&&f<=57343&&(f=65536+((f&1023)<<10)|t.charCodeAt(++c)&1023),f<=127){if(r>=n)break;e[r++]=f}else if(f<=2047){if(r+1>=n)break;e[r++]=192|f>>6,e[r++]=128|f&63}else if(f<=65535){if(r+2>=n)break;e[r++]=224|f>>12,e[r++]=128|f>>6&63,e[r++]=128|f&63}else if(f<=2097151){if(r+3>=n)break;e[r++]=240|f>>18,e[r++]=128|f>>12&63,e[r++]=128|f>>6&63,e[r++]=128|f&63}else if(f<=67108863){if(r+4>=n)break;e[r++]=248|f>>24,e[r++]=128|f>>18&63,e[r++]=128|f>>12&63,e[r++]=128|f>>6&63,e[r++]=128|f&63}else{if(r+5>=n)break;e[r++]=252|f>>30,e[r++]=128|f>>24&63,e[r++]=128|f>>18&63,e[r++]=128|f>>12&63,e[r++]=128|f>>6&63,e[r++]=128|f&63}}return e[r]=0,r-a}Module.stringToUTF8Array=stringToUTF8Array;function stringToUTF8(t,e,r){return stringToUTF8Array(t,HEAPU8,e,r)}Module.stringToUTF8=stringToUTF8;function lengthBytesUTF8(t){for(var e=0,r=0;r<t.length;++r){var s=t.charCodeAt(r);s>=55296&&s<=57343&&(s=65536+((s&1023)<<10)|t.charCodeAt(++r)&1023),s<=127?++e:s<=2047?e+=2:s<=65535?e+=3:s<=2097151?e+=4:s<=67108863?e+=5:e+=6}return e}Module.lengthBytesUTF8=lengthBytesUTF8;var UTF16Decoder=typeof TextDecoder<\"u\"?new TextDecoder(\"utf-16le\"):void 0;function demangle(t){var e=Module.___cxa_demangle||Module.__cxa_demangle;if(e){try{var r=t.substr(1),s=lengthBytesUTF8(r)+1,a=_malloc(s);stringToUTF8(r,a,s);var n=_malloc(4),c=e(a,0,0,n);if(getValue(n,\"i32\")===0&&c)return Pointer_stringify(c)}catch{}finally{a&&_free(a),n&&_free(n),c&&_free(c)}return t}return Runtime.warnOnce(\"warning: build with  -s DEMANGLE_SUPPORT=1  to link in libcxxabi demangling\"),t}function demangleAll(t){var e=/__Z[\\w\\d_]+/g;return t.replace(e,function(r){var s=demangle(r);return r===s?r:r+\" [\"+s+\"]\"})}function jsStackTrace(){var t=new Error;if(!t.stack){try{throw new Error(0)}catch(e){t=e}if(!t.stack)return\"(no stack trace available)\"}return t.stack.toString()}function stackTrace(){var t=jsStackTrace();return Module.extraStackTrace&&(t+=`\n`+Module.extraStackTrace()),demangleAll(t)}Module.stackTrace=stackTrace;var HEAP,buffer,HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateGlobalBufferViews(){Module.HEAP8=HEAP8=new Int8Array(buffer),Module.HEAP16=HEAP16=new Int16Array(buffer),Module.HEAP32=HEAP32=new Int32Array(buffer),Module.HEAPU8=HEAPU8=new Uint8Array(buffer),Module.HEAPU16=HEAPU16=new Uint16Array(buffer),Module.HEAPU32=HEAPU32=new Uint32Array(buffer),Module.HEAPF32=HEAPF32=new Float32Array(buffer),Module.HEAPF64=HEAPF64=new Float64Array(buffer)}var STATIC_BASE,STATICTOP,staticSealed,STACK_BASE,STACKTOP,STACK_MAX,DYNAMIC_BASE,DYNAMICTOP_PTR;STATIC_BASE=STATICTOP=STACK_BASE=STACKTOP=STACK_MAX=DYNAMIC_BASE=DYNAMICTOP_PTR=0,staticSealed=!1;function abortOnCannotGrowMemory(){abort(\"Cannot enlarge memory arrays. Either (1) compile with  -s TOTAL_MEMORY=X  with X higher than the current value \"+TOTAL_MEMORY+\", (2) compile with  -s ALLOW_MEMORY_GROWTH=1  which allows increasing the size at runtime but prevents some optimizations, (3) set Module.TOTAL_MEMORY to a higher value before the program runs, or (4) if you want malloc to return NULL (0) instead of this abort, compile with  -s ABORTING_MALLOC=0 \")}function enlargeMemory(){abortOnCannotGrowMemory()}var TOTAL_STACK=Module.TOTAL_STACK||5242880,TOTAL_MEMORY=Module.TOTAL_MEMORY||134217728;TOTAL_MEMORY<TOTAL_STACK&&Module.printErr(\"TOTAL_MEMORY should be larger than TOTAL_STACK, was \"+TOTAL_MEMORY+\"! (TOTAL_STACK=\"+TOTAL_STACK+\")\"),Module.buffer?buffer=Module.buffer:buffer=new ArrayBuffer(TOTAL_MEMORY),updateGlobalBufferViews();function getTotalMemory(){return TOTAL_MEMORY}if(HEAP32[0]=1668509029,HEAP16[1]=25459,HEAPU8[2]!==115||HEAPU8[3]!==99)throw\"Runtime error: expected the system to be little-endian!\";Module.HEAP=HEAP,Module.buffer=buffer,Module.HEAP8=HEAP8,Module.HEAP16=HEAP16,Module.HEAP32=HEAP32,Module.HEAPU8=HEAPU8,Module.HEAPU16=HEAPU16,Module.HEAPU32=HEAPU32,Module.HEAPF32=HEAPF32,Module.HEAPF64=HEAPF64;function callRuntimeCallbacks(t){for(;t.length>0;){var e=t.shift();if(typeof e==\"function\"){e();continue}var r=e.func;typeof r==\"number\"?e.arg===void 0?Module.dynCall_v(r):Module.dynCall_vi(r,e.arg):r(e.arg===void 0?null:e.arg)}}var __ATPRERUN__=[],__ATINIT__=[],__ATMAIN__=[],__ATEXIT__=[],__ATPOSTRUN__=[],runtimeInitialized=!1,runtimeExited=!1;function preRun(){if(Module.preRun)for(typeof Module.preRun==\"function\"&&(Module.preRun=[Module.preRun]);Module.preRun.length;)addOnPreRun(Module.preRun.shift());callRuntimeCallbacks(__ATPRERUN__)}function ensureInitRuntime(){runtimeInitialized||(runtimeInitialized=!0,callRuntimeCallbacks(__ATINIT__))}function preMain(){callRuntimeCallbacks(__ATMAIN__)}function exitRuntime(){callRuntimeCallbacks(__ATEXIT__),runtimeExited=!0}function postRun(){if(Module.postRun)for(typeof Module.postRun==\"function\"&&(Module.postRun=[Module.postRun]);Module.postRun.length;)addOnPostRun(Module.postRun.shift());callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(t){__ATPRERUN__.unshift(t)}Module.addOnPreRun=addOnPreRun;function addOnInit(t){__ATINIT__.unshift(t)}Module.addOnInit=addOnInit;function addOnPreMain(t){__ATMAIN__.unshift(t)}Module.addOnPreMain=addOnPreMain;function addOnExit(t){__ATEXIT__.unshift(t)}Module.addOnExit=addOnExit;function addOnPostRun(t){__ATPOSTRUN__.unshift(t)}Module.addOnPostRun=addOnPostRun;function intArrayFromString(t,e,r){var s=r>0?r:lengthBytesUTF8(t)+1,a=new Array(s),n=stringToUTF8Array(t,a,0,a.length);return e&&(a.length=n),a}Module.intArrayFromString=intArrayFromString;function intArrayToString(t){for(var e=[],r=0;r<t.length;r++){var s=t[r];s>255&&(s&=255),e.push(String.fromCharCode(s))}return e.join(\"\")}Module.intArrayToString=intArrayToString;function writeStringToMemory(t,e,r){Runtime.warnOnce(\"writeStringToMemory is deprecated and should not be called! Use stringToUTF8() instead!\");var s,a;r&&(a=e+lengthBytesUTF8(t),s=HEAP8[a]),stringToUTF8(t,e,1/0),r&&(HEAP8[a]=s)}Module.writeStringToMemory=writeStringToMemory;function writeArrayToMemory(t,e){HEAP8.set(t,e)}Module.writeArrayToMemory=writeArrayToMemory;function writeAsciiToMemory(t,e,r){for(var s=0;s<t.length;++s)HEAP8[e++>>0]=t.charCodeAt(s);r||(HEAP8[e>>0]=0)}if(Module.writeAsciiToMemory=writeAsciiToMemory,(!Math.imul||Math.imul(4294967295,5)!==-5)&&(Math.imul=function t(e,r){var s=e>>>16,a=e&65535,n=r>>>16,c=r&65535;return a*c+(s*c+a*n<<16)|0}),Math.imul=Math.imul,!Math.fround){var froundBuffer=new Float32Array(1);Math.fround=function(t){return froundBuffer[0]=t,froundBuffer[0]}}Math.fround=Math.fround,Math.clz32||(Math.clz32=function(t){t=t>>>0;for(var e=0;e<32;e++)if(t&1<<31-e)return e;return 32}),Math.clz32=Math.clz32,Math.trunc||(Math.trunc=function(t){return t<0?Math.ceil(t):Math.floor(t)}),Math.trunc=Math.trunc;var Math_abs=Math.abs,Math_cos=Math.cos,Math_sin=Math.sin,Math_tan=Math.tan,Math_acos=Math.acos,Math_asin=Math.asin,Math_atan=Math.atan,Math_atan2=Math.atan2,Math_exp=Math.exp,Math_log=Math.log,Math_sqrt=Math.sqrt,Math_ceil=Math.ceil,Math_floor=Math.floor,Math_pow=Math.pow,Math_imul=Math.imul,Math_fround=Math.fround,Math_round=Math.round,Math_min=Math.min,Math_clz32=Math.clz32,Math_trunc=Math.trunc,runDependencies=0,runDependencyWatcher=null,dependenciesFulfilled=null;function getUniqueRunDependency(t){return t}function addRunDependency(t){runDependencies++,Module.monitorRunDependencies&&Module.monitorRunDependencies(runDependencies)}Module.addRunDependency=addRunDependency;function removeRunDependency(t){if(runDependencies--,Module.monitorRunDependencies&&Module.monitorRunDependencies(runDependencies),runDependencies==0&&(runDependencyWatcher!==null&&(clearInterval(runDependencyWatcher),runDependencyWatcher=null),dependenciesFulfilled)){var e=dependenciesFulfilled;dependenciesFulfilled=null,e()}}Module.removeRunDependency=removeRunDependency,Module.preloadedImages={},Module.preloadedAudios={};var ASM_CONSTS=[function(t,e,r,s,a,n,c,f){return _nbind.callbackSignatureList[t].apply(this,arguments)}];function _emscripten_asm_const_iiiiiiii(t,e,r,s,a,n,c,f){return ASM_CONSTS[t](e,r,s,a,n,c,f)}function _emscripten_asm_const_iiiii(t,e,r,s,a){return ASM_CONSTS[t](e,r,s,a)}function _emscripten_asm_const_iiidddddd(t,e,r,s,a,n,c,f,p){return ASM_CONSTS[t](e,r,s,a,n,c,f,p)}function _emscripten_asm_const_iiididi(t,e,r,s,a,n,c){return ASM_CONSTS[t](e,r,s,a,n,c)}function _emscripten_asm_const_iiii(t,e,r,s){return ASM_CONSTS[t](e,r,s)}function _emscripten_asm_const_iiiid(t,e,r,s,a){return ASM_CONSTS[t](e,r,s,a)}function _emscripten_asm_const_iiiiii(t,e,r,s,a,n){return ASM_CONSTS[t](e,r,s,a,n)}STATIC_BASE=Runtime.GLOBAL_BASE,STATICTOP=STATIC_BASE+12800,__ATINIT__.push({func:function(){__GLOBAL__sub_I_Yoga_cpp()}},{func:function(){__GLOBAL__sub_I_nbind_cc()}},{func:function(){__GLOBAL__sub_I_common_cc()}},{func:function(){__GLOBAL__sub_I_Binding_cc()}}),allocate([0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,127,0,0,192,127,0,0,192,127,0,0,192,127,3,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,3,0,0,0,0,0,192,127,3,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,127,0,0,192,127,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,127,0,0,0,0,0,0,0,0,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,127,0,0,192,127,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,0,0,128,191,0,0,128,191,0,0,192,127,0,0,0,0,0,0,0,0,0,0,128,63,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,3,0,0,0,1,0,0,0,2,0,0,0,0,0,0,0,190,12,0,0,200,12,0,0,208,12,0,0,216,12,0,0,230,12,0,0,242,12,0,0,1,0,0,0,3,0,0,0,0,0,0,0,2,0,0,0,0,0,192,127,3,0,0,0,180,45,0,0,181,45,0,0,182,45,0,0,181,45,0,0,182,45,0,0,0,0,0,0,0,0,0,0,1,0,0,0,2,0,0,0,3,0,0,0,1,0,0,0,4,0,0,0,183,45,0,0,181,45,0,0,181,45,0,0,181,45,0,0,181,45,0,0,181,45,0,0,181,45,0,0,184,45,0,0,185,45,0,0,181,45,0,0,181,45,0,0,182,45,0,0,186,45,0,0,185,45,0,0,148,4,0,0,3,0,0,0,187,45,0,0,164,4,0,0,188,45,0,0,2,0,0,0,189,45,0,0,164,4,0,0,188,45,0,0,185,45,0,0,164,4,0,0,185,45,0,0,164,4,0,0,188,45,0,0,181,45,0,0,182,45,0,0,181,45,0,0,0,0,0,0,0,0,0,0,1,0,0,0,5,0,0,0,6,0,0,0,1,0,0,0,7,0,0,0,183,45,0,0,182,45,0,0,181,45,0,0,190,45,0,0,190,45,0,0,182,45,0,0,182,45,0,0,185,45,0,0,181,45,0,0,185,45,0,0,182,45,0,0,181,45,0,0,185,45,0,0,182,45,0,0,185,45,0,0,48,5,0,0,3,0,0,0,56,5,0,0,1,0,0,0,189,45,0,0,185,45,0,0,164,4,0,0,76,5,0,0,2,0,0,0,191,45,0,0,186,45,0,0,182,45,0,0,185,45,0,0,192,45,0,0,185,45,0,0,182,45,0,0,186,45,0,0,185,45,0,0,76,5,0,0,76,5,0,0,136,5,0,0,182,45,0,0,181,45,0,0,2,0,0,0,190,45,0,0,136,5,0,0,56,19,0,0,156,5,0,0,2,0,0,0,184,45,0,0,0,0,0,0,0,0,0,0,1,0,0,0,8,0,0,0,9,0,0,0,1,0,0,0,10,0,0,0,204,5,0,0,181,45,0,0,181,45,0,0,2,0,0,0,180,45,0,0,204,5,0,0,2,0,0,0,195,45,0,0,236,5,0,0,97,19,0,0,198,45,0,0,211,45,0,0,212,45,0,0,213,45,0,0,214,45,0,0,215,45,0,0,188,45,0,0,182,45,0,0,216,45,0,0,217,45,0,0,218,45,0,0,219,45,0,0,192,45,0,0,181,45,0,0,0,0,0,0,185,45,0,0,110,19,0,0,186,45,0,0,115,19,0,0,221,45,0,0,120,19,0,0,148,4,0,0,132,19,0,0,96,6,0,0,145,19,0,0,222,45,0,0,164,19,0,0,223,45,0,0,173,19,0,0,0,0,0,0,3,0,0,0,104,6,0,0,1,0,0,0,187,45,0,0,0,0,0,0,0,0,0,0,1,0,0,0,11,0,0,0,12,0,0,0,1,0,0,0,13,0,0,0,185,45,0,0,224,45,0,0,164,6,0,0,188,45,0,0,172,6,0,0,180,6,0,0,2,0,0,0,188,6,0,0,7,0,0,0,224,45,0,0,7,0,0,0,164,6,0,0,1,0,0,0,213,45,0,0,185,45,0,0,224,45,0,0,172,6,0,0,185,45,0,0,224,45,0,0,164,6,0,0,185,45,0,0,224,45,0,0,211,45,0,0,211,45,0,0,222,45,0,0,211,45,0,0,224,45,0,0,222,45,0,0,211,45,0,0,224,45,0,0,172,6,0,0,222,45,0,0,211,45,0,0,224,45,0,0,188,45,0,0,222,45,0,0,211,45,0,0,40,7,0,0,188,45,0,0,2,0,0,0,224,45,0,0,185,45,0,0,188,45,0,0,188,45,0,0,188,45,0,0,188,45,0,0,222,45,0,0,224,45,0,0,148,4,0,0,185,45,0,0,148,4,0,0,148,4,0,0,148,4,0,0,148,4,0,0,148,4,0,0,185,45,0,0,164,6,0,0,148,4,0,0,0,0,0,0,0,0,0,0,1,0,0,0,14,0,0,0,15,0,0,0,1,0,0,0,16,0,0,0,148,7,0,0,2,0,0,0,225,45,0,0,183,45,0,0,188,45,0,0,168,7,0,0,5,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,2,0,0,0,234,45,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,148,45,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,9,0,0,5,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,2,0,0,0,242,45,0,0,0,4,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,67,111,117,108,100,32,110,111,116,32,97,108,108,111,99,97,116,101,32,109,101,109,111,114,121,32,102,111,114,32,110,111,100,101,0,67,97,110,110,111,116,32,114,101,115,101,116,32,97,32,110,111,100,101,32,119,104,105,99,104,32,115,116,105,108,108,32,104,97,115,32,99,104,105,108,100,114,101,110,32,97,116,116,97,99,104,101,100,0,67,97,110,110,111,116,32,114,101,115,101,116,32,97,32,110,111,100,101,32,115,116,105,108,108,32,97,116,116,97,99,104,101,100,32,116,111,32,97,32,112,97,114,101,110,116,0,67,111,117,108,100,32,110,111,116,32,97,108,108,111,99,97,116,101,32,109,101,109,111,114,121,32,102,111,114,32,99,111,110,102,105,103,0,67,97,110,110,111,116,32,115,101,116,32,109,101,97,115,117,114,101,32,102,117,110,99,116,105,111,110,58,32,78,111,100,101,115,32,119,105,116,104,32,109,101,97,115,117,114,101,32,102,117,110,99,116,105,111,110,115,32,99,97,110,110,111,116,32,104,97,118,101,32,99,104,105,108,100,114,101,110,46,0,67,104,105,108,100,32,97,108,114,101,97,100,121,32,104,97,115,32,97,32,112,97,114,101,110,116,44,32,105,116,32,109,117,115,116,32,98,101,32,114,101,109,111,118,101,100,32,102,105,114,115,116,46,0,67,97,110,110,111,116,32,97,100,100,32,99,104,105,108,100,58,32,78,111,100,101,115,32,119,105,116,104,32,109,101,97,115,117,114,101,32,102,117,110,99,116,105,111,110,115,32,99,97,110,110,111,116,32,104,97,118,101,32,99,104,105,108,100,114,101,110,46,0,79,110,108,121,32,108,101,97,102,32,110,111,100,101,115,32,119,105,116,104,32,99,117,115,116,111,109,32,109,101,97,115,117,114,101,32,102,117,110,99,116,105,111,110,115,115,104,111,117,108,100,32,109,97,110,117,97,108,108,121,32,109,97,114,107,32,116,104,101,109,115,101,108,118,101,115,32,97,115,32,100,105,114,116,121,0,67,97,110,110,111,116,32,103,101,116,32,108,97,121,111,117,116,32,112,114,111,112,101,114,116,105,101,115,32,111,102,32,109,117,108,116,105,45,101,100,103,101,32,115,104,111,114,116,104,97,110,100,115,0,37,115,37,100,46,123,91,115,107,105,112,112,101,100,93,32,0,119,109,58,32,37,115,44,32,104,109,58,32,37,115,44,32,97,119,58,32,37,102,32,97,104,58,32,37,102,32,61,62,32,100,58,32,40,37,102,44,32,37,102,41,32,37,115,10,0,37,115,37,100,46,123,37,115,0,42,0,119,109,58,32,37,115,44,32,104,109,58,32,37,115,44,32,97,119,58,32,37,102,32,97,104,58,32,37,102,32,37,115,10,0,37,115,37,100,46,125,37,115,0,119,109,58,32,37,115,44,32,104,109,58,32,37,115,44,32,100,58,32,40,37,102,44,32,37,102,41,32,37,115,10,0,79,117,116,32,111,102,32,99,97,99,104,101,32,101,110,116,114,105,101,115,33,10,0,83,99,97,108,101,32,102,97,99,116,111,114,32,115,104,111,117,108,100,32,110,111,116,32,98,101,32,108,101,115,115,32,116,104,97,110,32,122,101,114,111,0,105,110,105,116,105,97,108,0,37,115,10,0,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,0,85,78,68,69,70,73,78,69,68,0,69,88,65,67,84,76,89,0,65,84,95,77,79,83,84,0,76,65,89,95,85,78,68,69,70,73,78,69,68,0,76,65,89,95,69,88,65,67,84,76,89,0,76,65,89,95,65,84,95,77,79,83,84,0,97,118,97,105,108,97,98,108,101,87,105,100,116,104,32,105,115,32,105,110,100,101,102,105,110,105,116,101,32,115,111,32,119,105,100,116,104,77,101,97,115,117,114,101,77,111,100,101,32,109,117,115,116,32,98,101,32,89,71,77,101,97,115,117,114,101,77,111,100,101,85,110,100,101,102,105,110,101,100,0,97,118,97,105,108,97,98,108,101,72,101,105,103,104,116,32,105,115,32,105,110,100,101,102,105,110,105,116,101,32,115,111,32,104,101,105,103,104,116,77,101,97,115,117,114,101,77,111,100,101,32,109,117,115,116,32,98,101,32,89,71,77,101,97,115,117,114,101,77,111,100,101,85,110,100,101,102,105,110,101,100,0,102,108,101,120,0,115,116,114,101,116,99,104,0,109,117,108,116,105,108,105,110,101,45,115,116,114,101,116,99,104,0,69,120,112,101,99,116,101,100,32,110,111,100,101,32,116,111,32,104,97,118,101,32,99,117,115,116,111,109,32,109,101,97,115,117,114,101,32,102,117,110,99,116,105,111,110,0,109,101,97,115,117,114,101,0,69,120,112,101,99,116,32,99,117,115,116,111,109,32,98,97,115,101,108,105,110,101,32,102,117,110,99,116,105,111,110,32,116,111,32,110,111,116,32,114,101,116,117,114,110,32,78,97,78,0,97,98,115,45,109,101,97,115,117,114,101,0,97,98,115,45,108,97,121,111,117,116,0,78,111,100,101,0,99,114,101,97,116,101,68,101,102,97,117,108,116,0,99,114,101,97,116,101,87,105,116,104,67,111,110,102,105,103,0,100,101,115,116,114,111,121,0,114,101,115,101,116,0,99,111,112,121,83,116,121,108,101,0,115,101,116,80,111,115,105,116,105,111,110,84,121,112,101,0,115,101,116,80,111,115,105,116,105,111,110,0,115,101,116,80,111,115,105,116,105,111,110,80,101,114,99,101,110,116,0,115,101,116,65,108,105,103,110,67,111,110,116,101,110,116,0,115,101,116,65,108,105,103,110,73,116,101,109,115,0,115,101,116,65,108,105,103,110,83,101,108,102,0,115,101,116,70,108,101,120,68,105,114,101,99,116,105,111,110,0,115,101,116,70,108,101,120,87,114,97,112,0,115,101,116,74,117,115,116,105,102,121,67,111,110,116,101,110,116,0,115,101,116,77,97,114,103,105,110,0,115,101,116,77,97,114,103,105,110,80,101,114,99,101,110,116,0,115,101,116,77,97,114,103,105,110,65,117,116,111,0,115,101,116,79,118,101,114,102,108,111,119,0,115,101,116,68,105,115,112,108,97,121,0,115,101,116,70,108,101,120,0,115,101,116,70,108,101,120,66,97,115,105,115,0,115,101,116,70,108,101,120,66,97,115,105,115,80,101,114,99,101,110,116,0,115,101,116,70,108,101,120,71,114,111,119,0,115,101,116,70,108,101,120,83,104,114,105,110,107,0,115,101,116,87,105,100,116,104,0,115,101,116,87,105,100,116,104,80,101,114,99,101,110,116,0,115,101,116,87,105,100,116,104,65,117,116,111,0,115,101,116,72,101,105,103,104,116,0,115,101,116,72,101,105,103,104,116,80,101,114,99,101,110,116,0,115,101,116,72,101,105,103,104,116,65,117,116,111,0,115,101,116,77,105,110,87,105,100,116,104,0,115,101,116,77,105,110,87,105,100,116,104,80,101,114,99,101,110,116,0,115,101,116,77,105,110,72,101,105,103,104,116,0,115,101,116,77,105,110,72,101,105,103,104,116,80,101,114,99,101,110,116,0,115,101,116,77,97,120,87,105,100,116,104,0,115,101,116,77,97,120,87,105,100,116,104,80,101,114,99,101,110,116,0,115,101,116,77,97,120,72,101,105,103,104,116,0,115,101,116,77,97,120,72,101,105,103,104,116,80,101,114,99,101,110,116,0,115,101,116,65,115,112,101,99,116,82,97,116,105,111,0,115,101,116,66,111,114,100,101,114,0,115,101,116,80,97,100,100,105,110,103,0,115,101,116,80,97,100,100,105,110,103,80,101,114,99,101,110,116,0,103,101,116,80,111,115,105,116,105,111,110,84,121,112,101,0,103,101,116,80,111,115,105,116,105,111,110,0,103,101,116,65,108,105,103,110,67,111,110,116,101,110,116,0,103,101,116,65,108,105,103,110,73,116,101,109,115,0,103,101,116,65,108,105,103,110,83,101,108,102,0,103,101,116,70,108,101,120,68,105,114,101,99,116,105,111,110,0,103,101,116,70,108,101,120,87,114,97,112,0,103,101,116,74,117,115,116,105,102,121,67,111,110,116,101,110,116,0,103,101,116,77,97,114,103,105,110,0,103,101,116,70,108,101,120,66,97,115,105,115,0,103,101,116,70,108,101,120,71,114,111,119,0,103,101,116,70,108,101,120,83,104,114,105,110,107,0,103,101,116,87,105,100,116,104,0,103,101,116,72,101,105,103,104,116,0,103,101,116,77,105,110,87,105,100,116,104,0,103,101,116,77,105,110,72,101,105,103,104,116,0,103,101,116,77,97,120,87,105,100,116,104,0,103,101,116,77,97,120,72,101,105,103,104,116,0,103,101,116,65,115,112,101,99,116,82,97,116,105,111,0,103,101,116,66,111,114,100,101,114,0,103,101,116,79,118,101,114,102,108,111,119,0,103,101,116,68,105,115,112,108,97,121,0,103,101,116,80,97,100,100,105,110,103,0,105,110,115,101,114,116,67,104,105,108,100,0,114,101,109,111,118,101,67,104,105,108,100,0,103,101,116,67,104,105,108,100,67,111,117,110,116,0,103,101,116,80,97,114,101,110,116,0,103,101,116,67,104,105,108,100,0,115,101,116,77,101,97,115,117,114,101,70,117,110,99,0,117,110,115,101,116,77,101,97,115,117,114,101,70,117,110,99,0,109,97,114,107,68,105,114,116,121,0,105,115,68,105,114,116,121,0,99,97,108,99,117,108,97,116,101,76,97,121,111,117,116,0,103,101,116,67,111,109,112,117,116,101,100,76,101,102,116,0,103,101,116,67,111,109,112,117,116,101,100,82,105,103,104,116,0,103,101,116,67,111,109,112,117,116,101,100,84,111,112,0,103,101,116,67,111,109,112,117,116,101,100,66,111,116,116,111,109,0,103,101,116,67,111,109,112,117,116,101,100,87,105,100,116,104,0,103,101,116,67,111,109,112,117,116,101,100,72,101,105,103,104,116,0,103,101,116,67,111,109,112,117,116,101,100,76,97,121,111,117,116,0,103,101,116,67,111,109,112,117,116,101,100,77,97,114,103,105,110,0,103,101,116,67,111,109,112,117,116,101,100,66,111,114,100,101,114,0,103,101,116,67,111,109,112,117,116,101,100,80,97,100,100,105,110,103,0,67,111,110,102,105,103,0,99,114,101,97,116,101,0,115,101,116,69,120,112,101,114,105,109,101,110,116,97,108,70,101,97,116,117,114,101,69,110,97,98,108,101,100,0,115,101,116,80,111,105,110,116,83,99,97,108,101,70,97,99,116,111,114,0,105,115,69,120,112,101,114,105,109,101,110,116,97,108,70,101,97,116,117,114,101,69,110,97,98,108,101,100,0,86,97,108,117,101,0,76,97,121,111,117,116,0,83,105,122,101,0,103,101,116,73,110,115,116,97,110,99,101,67,111,117,110,116,0,73,110,116,54,52,0,1,1,1,2,2,4,4,4,4,8,8,4,8,118,111,105,100,0,98,111,111,108,0,115,116,100,58,58,115,116,114,105,110,103,0,99,98,70,117,110,99,116,105,111,110,32,38,0,99,111,110,115,116,32,99,98,70,117,110,99,116,105,111,110,32,38,0,69,120,116,101,114,110,97,108,0,66,117,102,102,101,114,0,78,66,105,110,100,73,68,0,78,66,105,110,100,0,98,105,110,100,95,118,97,108,117,101,0,114,101,102,108,101,99,116,0,113,117,101,114,121,84,121,112,101,0,108,97,108,108,111,99,0,108,114,101,115,101,116,0,123,114,101,116,117,114,110,40,95,110,98,105,110,100,46,99,97,108,108,98,97,99,107,83,105,103,110,97,116,117,114,101,76,105,115,116,91,36,48,93,46,97,112,112,108,121,40,116,104,105,115,44,97,114,103,117,109,101,110,116,115,41,41,59,125,0,95,110,98,105,110,100,95,110,101,119,0,17,0,10,0,17,17,17,0,0,0,0,5,0,0,0,0,0,0,9,0,0,0,0,11,0,0,0,0,0,0,0,0,17,0,15,10,17,17,17,3,10,7,0,1,19,9,11,11,0,0,9,6,11,0,0,11,0,6,17,0,0,0,17,17,17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,0,17,0,10,10,17,17,17,0,10,0,0,2,0,9,11,0,0,0,9,0,11,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,12,0,0,0,0,9,12,0,0,0,0,0,12,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,13,0,0,0,4,13,0,0,0,0,9,14,0,0,0,0,0,14,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16,0,0,0,0,0,0,0,0,0,0,0,15,0,0,0,0,15,0,0,0,0,9,16,0,0,0,0,0,16,0,0,16,0,0,18,0,0,0,18,18,18,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,0,0,0,18,18,18,0,0,0,0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,0,0,0,0,10,0,0,0,0,10,0,0,0,0,9,11,0,0,0,0,0,11,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,12,0,0,0,0,9,12,0,0,0,0,0,12,0,0,12,0,0,45,43,32,32,32,48,88,48,120,0,40,110,117,108,108,41,0,45,48,88,43,48,88,32,48,88,45,48,120,43,48,120,32,48,120,0,105,110,102,0,73,78,70,0,110,97,110,0,78,65,78,0,48,49,50,51,52,53,54,55,56,57,65,66,67,68,69,70,46,0,84,33,34,25,13,1,2,3,17,75,28,12,16,4,11,29,18,30,39,104,110,111,112,113,98,32,5,6,15,19,20,21,26,8,22,7,40,36,23,24,9,10,14,27,31,37,35,131,130,125,38,42,43,60,61,62,63,67,71,74,77,88,89,90,91,92,93,94,95,96,97,99,100,101,102,103,105,106,107,108,114,115,116,121,122,123,124,0,73,108,108,101,103,97,108,32,98,121,116,101,32,115,101,113,117,101,110,99,101,0,68,111,109,97,105,110,32,101,114,114,111,114,0,82,101,115,117,108,116,32,110,111,116,32,114,101,112,114,101,115,101,110,116,97,98,108,101,0,78,111,116,32,97,32,116,116,121,0,80,101,114,109,105,115,115,105,111,110,32,100,101,110,105,101,100,0,79,112,101,114,97,116,105,111,110,32,110,111,116,32,112,101,114,109,105,116,116,101,100,0,78,111,32,115,117,99,104,32,102,105,108,101,32,111,114,32,100,105,114,101,99,116,111,114,121,0,78,111,32,115,117,99,104,32,112,114,111,99,101,115,115,0,70,105,108,101,32,101,120,105,115,116,115,0,86,97,108,117,101,32,116,111,111,32,108,97,114,103,101,32,102,111,114,32,100,97,116,97,32,116,121,112,101,0,78,111,32,115,112,97,99,101,32,108,101,102,116,32,111,110,32,100,101,118,105,99,101,0,79,117,116,32,111,102,32,109,101,109,111,114,121,0,82,101,115,111,117,114,99,101,32,98,117,115,121,0,73,110,116,101,114,114,117,112,116,101,100,32,115,121,115,116,101,109,32,99,97,108,108,0,82,101,115,111,117,114,99,101,32,116,101,109,112,111,114,97,114,105,108,121,32,117,110,97,118,97,105,108,97,98,108,101,0,73,110,118,97,108,105,100,32,115,101,101,107,0,67,114,111,115,115,45,100,101,118,105,99,101,32,108,105,110,107,0,82,101,97,100,45,111,110,108,121,32,102,105,108,101,32,115,121,115,116,101,109,0,68,105,114,101,99,116,111,114,121,32,110,111,116,32,101,109,112,116,121,0,67,111,110,110,101,99,116,105,111,110,32,114,101,115,101,116,32,98,121,32,112,101,101,114,0,79,112,101,114,97,116,105,111,110,32,116,105,109,101,100,32,111,117,116,0,67,111,110,110,101,99,116,105,111,110,32,114,101,102,117,115,101,100,0,72,111,115,116,32,105,115,32,100,111,119,110,0,72,111,115,116,32,105,115,32,117,110,114,101,97,99,104,97,98,108,101,0,65,100,100,114,101,115,115,32,105,110,32,117,115,101,0,66,114,111,107,101,110,32,112,105,112,101,0,73,47,79,32,101,114,114,111,114,0,78,111,32,115,117,99,104,32,100,101,118,105,99,101,32,111,114,32,97,100,100,114,101,115,115,0,66,108,111,99,107,32,100,101,118,105,99,101,32,114,101,113,117,105,114,101,100,0,78,111,32,115,117,99,104,32,100,101,118,105,99,101,0,78,111,116,32,97,32,100,105,114,101,99,116,111,114,121,0,73,115,32,97,32,100,105,114,101,99,116,111,114,121,0,84,101,120,116,32,102,105,108,101,32,98,117,115,121,0,69,120,101,99,32,102,111,114,109,97,116,32,101,114,114,111,114,0,73,110,118,97,108,105,100,32,97,114,103,117,109,101,110,116,0,65,114,103,117,109,101,110,116,32,108,105,115,116,32,116,111,111,32,108,111,110,103,0,83,121,109,98,111,108,105,99,32,108,105,110,107,32,108,111,111,112,0,70,105,108,101,110,97,109,101,32,116,111,111,32,108,111,110,103,0,84,111,111,32,109,97,110,121,32,111,112,101,110,32,102,105,108,101,115,32,105,110,32,115,121,115,116,101,109,0,78,111,32,102,105,108,101,32,100,101,115,99,114,105,112,116,111,114,115,32,97,118,97,105,108,97,98,108,101,0,66,97,100,32,102,105,108,101,32,100,101,115,99,114,105,112,116,111,114,0,78,111,32,99,104,105,108,100,32,112,114,111,99,101,115,115,0,66,97,100,32,97,100,100,114,101,115,115,0,70,105,108,101,32,116,111,111,32,108,97,114,103,101,0,84,111,111,32,109,97,110,121,32,108,105,110,107,115,0,78,111,32,108,111,99,107,115,32,97,118,97,105,108,97,98,108,101,0,82,101,115,111,117,114,99,101,32,100,101,97,100,108,111,99,107,32,119,111,117,108,100,32,111,99,99,117,114,0,83,116,97,116,101,32,110,111,116,32,114,101,99,111,118,101,114,97,98,108,101,0,80,114,101,118,105,111,117,115,32,111,119,110,101,114,32,100,105,101,100,0,79,112,101,114,97,116,105,111,110,32,99,97,110,99,101,108,101,100,0,70,117,110,99,116,105,111,110,32,110,111,116,32,105,109,112,108,101,109,101,110,116,101,100,0,78,111,32,109,101,115,115,97,103,101,32,111,102,32,100,101,115,105,114,101,100,32,116,121,112,101,0,73,100,101,110,116,105,102,105,101,114,32,114,101,109,111,118,101,100,0,68,101,118,105,99,101,32,110,111,116,32,97,32,115,116,114,101,97,109,0,78,111,32,100,97,116,97,32,97,118,97,105,108,97,98,108,101,0,68,101,118,105,99,101,32,116,105,109,101,111,117,116,0,79,117,116,32,111,102,32,115,116,114,101,97,109,115,32,114,101,115,111,117,114,99,101,115,0,76,105,110,107,32,104,97,115,32,98,101,101,110,32,115,101,118,101,114,101,100,0,80,114,111,116,111,99,111,108,32,101,114,114,111,114,0,66,97,100,32,109,101,115,115,97,103,101,0,70,105,108,101,32,100,101,115,99,114,105,112,116,111,114,32,105,110,32,98,97,100,32,115,116,97,116,101,0,78,111,116,32,97,32,115,111,99,107,101,116,0,68,101,115,116,105,110,97,116,105,111,110,32,97,100,100,114,101,115,115,32,114,101,113,117,105,114,101,100,0,77,101,115,115,97,103,101,32,116,111,111,32,108,97,114,103,101,0,80,114,111,116,111,99,111,108,32,119,114,111,110,103,32,116,121,112,101,32,102,111,114,32,115,111,99,107,101,116,0,80,114,111,116,111,99,111,108,32,110,111,116,32,97,118,97,105,108,97,98,108,101,0,80,114,111,116,111,99,111,108,32,110,111,116,32,115,117,112,112,111,114,116,101,100,0,83,111,99,107,101,116,32,116,121,112,101,32,110,111,116,32,115,117,112,112,111,114,116,101,100,0,78,111,116,32,115,117,112,112,111,114,116,101,100,0,80,114,111,116,111,99,111,108,32,102,97,109,105,108,121,32,110,111,116,32,115,117,112,112,111,114,116,101,100,0,65,100,100,114,101,115,115,32,102,97,109,105,108,121,32,110,111,116,32,115,117,112,112,111,114,116,101,100,32,98,121,32,112,114,111,116,111,99,111,108,0,65,100,100,114,101,115,115,32,110,111,116,32,97,118,97,105,108,97,98,108,101,0,78,101,116,119,111,114,107,32,105,115,32,100,111,119,110,0,78,101,116,119,111,114,107,32,117,110,114,101,97,99,104,97,98,108,101,0,67,111,110,110,101,99,116,105,111,110,32,114,101,115,101,116,32,98,121,32,110,101,116,119,111,114,107,0,67,111,110,110,101,99,116,105,111,110,32,97,98,111,114,116,101,100,0,78,111,32,98,117,102,102,101,114,32,115,112,97,99,101,32,97,118,97,105,108,97,98,108,101,0,83,111,99,107,101,116,32,105,115,32,99,111,110,110,101,99,116,101,100,0,83,111,99,107,101,116,32,110,111,116,32,99,111,110,110,101,99,116,101,100,0,67,97,110,110,111,116,32,115,101,110,100,32,97,102,116,101,114,32,115,111,99,107,101,116,32,115,104,117,116,100,111,119,110,0,79,112,101,114,97,116,105,111,110,32,97,108,114,101,97,100,121,32,105,110,32,112,114,111,103,114,101,115,115,0,79,112,101,114,97,116,105,111,110,32,105,110,32,112,114,111,103,114,101,115,115,0,83,116,97,108,101,32,102,105,108,101,32,104,97,110,100,108,101,0,82,101,109,111,116,101,32,73,47,79,32,101,114,114,111,114,0,81,117,111,116,97,32,101,120,99,101,101,100,101,100,0,78,111,32,109,101,100,105,117,109,32,102,111,117,110,100,0,87,114,111,110,103,32,109,101,100,105,117,109,32,116,121,112,101,0,78,111,32,101,114,114,111,114,32,105,110,102,111,114,109,97,116,105,111,110,0,0],\"i8\",ALLOC_NONE,Runtime.GLOBAL_BASE);var tempDoublePtr=STATICTOP;STATICTOP+=16;function _atexit(t,e){__ATEXIT__.unshift({func:t,arg:e})}function ___cxa_atexit(){return _atexit.apply(null,arguments)}function _abort(){Module.abort()}function __ZN8facebook4yoga14YGNodeToStringEPNSt3__212basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEP6YGNode14YGPrintOptionsj(){Module.printErr(\"missing function: _ZN8facebook4yoga14YGNodeToStringEPNSt3__212basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEP6YGNode14YGPrintOptionsj\"),abort(-1)}function __decorate(t,e,r,s){var a=arguments.length,n=a<3?e:s===null?s=Object.getOwnPropertyDescriptor(e,r):s,c;if(typeof Reflect==\"object\"&&typeof Reflect.decorate==\"function\")n=Reflect.decorate(t,e,r,s);else for(var f=t.length-1;f>=0;f--)(c=t[f])&&(n=(a<3?c(n):a>3?c(e,r,n):c(e,r))||n);return a>3&&n&&Object.defineProperty(e,r,n),n}function _defineHidden(t){return function(e,r){Object.defineProperty(e,r,{configurable:!1,enumerable:!1,value:t,writable:!0})}}var _nbind={};function __nbind_free_external(t){_nbind.externalList[t].dereference(t)}function __nbind_reference_external(t){_nbind.externalList[t].reference()}function _llvm_stackrestore(t){var e=_llvm_stacksave,r=e.LLVM_SAVEDSTACKS[t];e.LLVM_SAVEDSTACKS.splice(t,1),Runtime.stackRestore(r)}function __nbind_register_pool(t,e,r,s){_nbind.Pool.pageSize=t,_nbind.Pool.usedPtr=e/4,_nbind.Pool.rootPtr=r,_nbind.Pool.pagePtr=s/4,HEAP32[e/4]=16909060,HEAP8[e]==1&&(_nbind.bigEndian=!0),HEAP32[e/4]=0,_nbind.makeTypeKindTbl=(n={},n[1024]=_nbind.PrimitiveType,n[64]=_nbind.Int64Type,n[2048]=_nbind.BindClass,n[3072]=_nbind.BindClassPtr,n[4096]=_nbind.SharedClassPtr,n[5120]=_nbind.ArrayType,n[6144]=_nbind.ArrayType,n[7168]=_nbind.CStringType,n[9216]=_nbind.CallbackType,n[10240]=_nbind.BindType,n),_nbind.makeTypeNameTbl={Buffer:_nbind.BufferType,External:_nbind.ExternalType,Int64:_nbind.Int64Type,_nbind_new:_nbind.CreateValueType,bool:_nbind.BooleanType,\"cbFunction &\":_nbind.CallbackType,\"const cbFunction &\":_nbind.CallbackType,\"const std::string &\":_nbind.StringType,\"std::string\":_nbind.StringType},Module.toggleLightGC=_nbind.toggleLightGC,_nbind.callUpcast=Module.dynCall_ii;var a=_nbind.makeType(_nbind.constructType,{flags:2048,id:0,name:\"\"});a.proto=Module,_nbind.BindClass.list.push(a);var n}function _emscripten_set_main_loop_timing(t,e){if(Browser.mainLoop.timingMode=t,Browser.mainLoop.timingValue=e,!Browser.mainLoop.func)return 1;if(t==0)Browser.mainLoop.scheduler=function(){var c=Math.max(0,Browser.mainLoop.tickStartTime+e-_emscripten_get_now())|0;setTimeout(Browser.mainLoop.runner,c)},Browser.mainLoop.method=\"timeout\";else if(t==1)Browser.mainLoop.scheduler=function(){Browser.requestAnimationFrame(Browser.mainLoop.runner)},Browser.mainLoop.method=\"rAF\";else if(t==2){if(!window.setImmediate){let n=function(c){c.source===window&&c.data===s&&(c.stopPropagation(),r.shift()())};var a=n,r=[],s=\"setimmediate\";window.addEventListener(\"message\",n,!0),window.setImmediate=function(f){r.push(f),ENVIRONMENT_IS_WORKER?(Module.setImmediates===void 0&&(Module.setImmediates=[]),Module.setImmediates.push(f),window.postMessage({target:s})):window.postMessage(s,\"*\")}}Browser.mainLoop.scheduler=function(){window.setImmediate(Browser.mainLoop.runner)},Browser.mainLoop.method=\"immediate\"}return 0}function _emscripten_get_now(){abort()}function _emscripten_set_main_loop(t,e,r,s,a){Module.noExitRuntime=!0,assert(!Browser.mainLoop.func,\"emscripten_set_main_loop: there can only be one main loop function at once: call emscripten_cancel_main_loop to cancel the previous one before setting a new one with different parameters.\"),Browser.mainLoop.func=t,Browser.mainLoop.arg=s;var n;typeof s<\"u\"?n=function(){Module.dynCall_vi(t,s)}:n=function(){Module.dynCall_v(t)};var c=Browser.mainLoop.currentlyRunningMainloop;if(Browser.mainLoop.runner=function(){if(!ABORT){if(Browser.mainLoop.queue.length>0){var p=Date.now(),h=Browser.mainLoop.queue.shift();if(h.func(h.arg),Browser.mainLoop.remainingBlockers){var E=Browser.mainLoop.remainingBlockers,C=E%1==0?E-1:Math.floor(E);h.counted?Browser.mainLoop.remainingBlockers=C:(C=C+.5,Browser.mainLoop.remainingBlockers=(8*E+C)/9)}if(console.log('main loop blocker \"'+h.name+'\" took '+(Date.now()-p)+\" ms\"),Browser.mainLoop.updateStatus(),c<Browser.mainLoop.currentlyRunningMainloop)return;setTimeout(Browser.mainLoop.runner,0);return}if(!(c<Browser.mainLoop.currentlyRunningMainloop)){if(Browser.mainLoop.currentFrameNumber=Browser.mainLoop.currentFrameNumber+1|0,Browser.mainLoop.timingMode==1&&Browser.mainLoop.timingValue>1&&Browser.mainLoop.currentFrameNumber%Browser.mainLoop.timingValue!=0){Browser.mainLoop.scheduler();return}else Browser.mainLoop.timingMode==0&&(Browser.mainLoop.tickStartTime=_emscripten_get_now());Browser.mainLoop.method===\"timeout\"&&Module.ctx&&(Module.printErr(\"Looks like you are rendering without using requestAnimationFrame for the main loop. You should use 0 for the frame rate in emscripten_set_main_loop in order to use requestAnimationFrame, as that can greatly improve your frame rates!\"),Browser.mainLoop.method=\"\"),Browser.mainLoop.runIter(n),!(c<Browser.mainLoop.currentlyRunningMainloop)&&(typeof SDL==\"object\"&&SDL.audio&&SDL.audio.queueNewAudioData&&SDL.audio.queueNewAudioData(),Browser.mainLoop.scheduler())}}},a||(e&&e>0?_emscripten_set_main_loop_timing(0,1e3/e):_emscripten_set_main_loop_timing(1,1),Browser.mainLoop.scheduler()),r)throw\"SimulateInfiniteLoop\"}var Browser={mainLoop:{scheduler:null,method:\"\",currentlyRunningMainloop:0,func:null,arg:0,timingMode:0,timingValue:0,currentFrameNumber:0,queue:[],pause:function(){Browser.mainLoop.scheduler=null,Browser.mainLoop.currentlyRunningMainloop++},resume:function(){Browser.mainLoop.currentlyRunningMainloop++;var t=Browser.mainLoop.timingMode,e=Browser.mainLoop.timingValue,r=Browser.mainLoop.func;Browser.mainLoop.func=null,_emscripten_set_main_loop(r,0,!1,Browser.mainLoop.arg,!0),_emscripten_set_main_loop_timing(t,e),Browser.mainLoop.scheduler()},updateStatus:function(){if(Module.setStatus){var t=Module.statusMessage||\"Please wait...\",e=Browser.mainLoop.remainingBlockers,r=Browser.mainLoop.expectedBlockers;e?e<r?Module.setStatus(t+\" (\"+(r-e)+\"/\"+r+\")\"):Module.setStatus(t):Module.setStatus(\"\")}},runIter:function(t){if(!ABORT){if(Module.preMainLoop){var e=Module.preMainLoop();if(e===!1)return}try{t()}catch(r){if(r instanceof ExitStatus)return;throw r&&typeof r==\"object\"&&r.stack&&Module.printErr(\"exception thrown: \"+[r,r.stack]),r}Module.postMainLoop&&Module.postMainLoop()}}},isFullscreen:!1,pointerLock:!1,moduleContextCreatedCallbacks:[],workers:[],init:function(){if(Module.preloadPlugins||(Module.preloadPlugins=[]),Browser.initted)return;Browser.initted=!0;try{new Blob,Browser.hasBlobConstructor=!0}catch{Browser.hasBlobConstructor=!1,console.log(\"warning: no blob constructor, cannot create blobs with mimetypes\")}Browser.BlobBuilder=typeof MozBlobBuilder<\"u\"?MozBlobBuilder:typeof WebKitBlobBuilder<\"u\"?WebKitBlobBuilder:Browser.hasBlobConstructor?null:console.log(\"warning: no BlobBuilder\"),Browser.URLObject=typeof window<\"u\"?window.URL?window.URL:window.webkitURL:void 0,!Module.noImageDecoding&&typeof Browser.URLObject>\"u\"&&(console.log(\"warning: Browser does not support creating object URLs. Built-in browser image decoding will not be available.\"),Module.noImageDecoding=!0);var t={};t.canHandle=function(n){return!Module.noImageDecoding&&/\\.(jpg|jpeg|png|bmp)$/i.test(n)},t.handle=function(n,c,f,p){var h=null;if(Browser.hasBlobConstructor)try{h=new Blob([n],{type:Browser.getMimetype(c)}),h.size!==n.length&&(h=new Blob([new Uint8Array(n).buffer],{type:Browser.getMimetype(c)}))}catch(P){Runtime.warnOnce(\"Blob constructor present but fails: \"+P+\"; falling back to blob builder\")}if(!h){var E=new Browser.BlobBuilder;E.append(new Uint8Array(n).buffer),h=E.getBlob()}var C=Browser.URLObject.createObjectURL(h),S=new Image;S.onload=function(){assert(S.complete,\"Image \"+c+\" could not be decoded\");var I=document.createElement(\"canvas\");I.width=S.width,I.height=S.height;var R=I.getContext(\"2d\");R.drawImage(S,0,0),Module.preloadedImages[c]=I,Browser.URLObject.revokeObjectURL(C),f&&f(n)},S.onerror=function(I){console.log(\"Image \"+C+\" could not be decoded\"),p&&p()},S.src=C},Module.preloadPlugins.push(t);var e={};e.canHandle=function(n){return!Module.noAudioDecoding&&n.substr(-4)in{\".ogg\":1,\".wav\":1,\".mp3\":1}},e.handle=function(n,c,f,p){var h=!1;function E(R){h||(h=!0,Module.preloadedAudios[c]=R,f&&f(n))}function C(){h||(h=!0,Module.preloadedAudios[c]=new Audio,p&&p())}if(Browser.hasBlobConstructor){try{var S=new Blob([n],{type:Browser.getMimetype(c)})}catch{return C()}var P=Browser.URLObject.createObjectURL(S),I=new Audio;I.addEventListener(\"canplaythrough\",function(){E(I)},!1),I.onerror=function(N){if(h)return;console.log(\"warning: browser could not fully decode audio \"+c+\", trying slower base64 approach\");function U(W){for(var te=\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\",ie=\"=\",Ae=\"\",ce=0,me=0,pe=0;pe<W.length;pe++)for(ce=ce<<8|W[pe],me+=8;me>=6;){var Be=ce>>me-6&63;me-=6,Ae+=te[Be]}return me==2?(Ae+=te[(ce&3)<<4],Ae+=ie+ie):me==4&&(Ae+=te[(ce&15)<<2],Ae+=ie),Ae}I.src=\"data:audio/x-\"+c.substr(-3)+\";base64,\"+U(n),E(I)},I.src=P,Browser.safeSetTimeout(function(){E(I)},1e4)}else return C()},Module.preloadPlugins.push(e);function r(){Browser.pointerLock=document.pointerLockElement===Module.canvas||document.mozPointerLockElement===Module.canvas||document.webkitPointerLockElement===Module.canvas||document.msPointerLockElement===Module.canvas}var s=Module.canvas;s&&(s.requestPointerLock=s.requestPointerLock||s.mozRequestPointerLock||s.webkitRequestPointerLock||s.msRequestPointerLock||function(){},s.exitPointerLock=document.exitPointerLock||document.mozExitPointerLock||document.webkitExitPointerLock||document.msExitPointerLock||function(){},s.exitPointerLock=s.exitPointerLock.bind(document),document.addEventListener(\"pointerlockchange\",r,!1),document.addEventListener(\"mozpointerlockchange\",r,!1),document.addEventListener(\"webkitpointerlockchange\",r,!1),document.addEventListener(\"mspointerlockchange\",r,!1),Module.elementPointerLock&&s.addEventListener(\"click\",function(a){!Browser.pointerLock&&Module.canvas.requestPointerLock&&(Module.canvas.requestPointerLock(),a.preventDefault())},!1))},createContext:function(t,e,r,s){if(e&&Module.ctx&&t==Module.canvas)return Module.ctx;var a,n;if(e){var c={antialias:!1,alpha:!1};if(s)for(var f in s)c[f]=s[f];n=GL.createContext(t,c),n&&(a=GL.getContext(n).GLctx)}else a=t.getContext(\"2d\");return a?(r&&(e||assert(typeof GLctx>\"u\",\"cannot set in module if GLctx is used, but we are a non-GL context that would replace it\"),Module.ctx=a,e&&GL.makeContextCurrent(n),Module.useWebGL=e,Browser.moduleContextCreatedCallbacks.forEach(function(p){p()}),Browser.init()),a):null},destroyContext:function(t,e,r){},fullscreenHandlersInstalled:!1,lockPointer:void 0,resizeCanvas:void 0,requestFullscreen:function(t,e,r){Browser.lockPointer=t,Browser.resizeCanvas=e,Browser.vrDevice=r,typeof Browser.lockPointer>\"u\"&&(Browser.lockPointer=!0),typeof Browser.resizeCanvas>\"u\"&&(Browser.resizeCanvas=!1),typeof Browser.vrDevice>\"u\"&&(Browser.vrDevice=null);var s=Module.canvas;function a(){Browser.isFullscreen=!1;var c=s.parentNode;(document.fullscreenElement||document.mozFullScreenElement||document.msFullscreenElement||document.webkitFullscreenElement||document.webkitCurrentFullScreenElement)===c?(s.exitFullscreen=document.exitFullscreen||document.cancelFullScreen||document.mozCancelFullScreen||document.msExitFullscreen||document.webkitCancelFullScreen||function(){},s.exitFullscreen=s.exitFullscreen.bind(document),Browser.lockPointer&&s.requestPointerLock(),Browser.isFullscreen=!0,Browser.resizeCanvas&&Browser.setFullscreenCanvasSize()):(c.parentNode.insertBefore(s,c),c.parentNode.removeChild(c),Browser.resizeCanvas&&Browser.setWindowedCanvasSize()),Module.onFullScreen&&Module.onFullScreen(Browser.isFullscreen),Module.onFullscreen&&Module.onFullscreen(Browser.isFullscreen),Browser.updateCanvasDimensions(s)}Browser.fullscreenHandlersInstalled||(Browser.fullscreenHandlersInstalled=!0,document.addEventListener(\"fullscreenchange\",a,!1),document.addEventListener(\"mozfullscreenchange\",a,!1),document.addEventListener(\"webkitfullscreenchange\",a,!1),document.addEventListener(\"MSFullscreenChange\",a,!1));var n=document.createElement(\"div\");s.parentNode.insertBefore(n,s),n.appendChild(s),n.requestFullscreen=n.requestFullscreen||n.mozRequestFullScreen||n.msRequestFullscreen||(n.webkitRequestFullscreen?function(){n.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT)}:null)||(n.webkitRequestFullScreen?function(){n.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT)}:null),r?n.requestFullscreen({vrDisplay:r}):n.requestFullscreen()},requestFullScreen:function(t,e,r){return Module.printErr(\"Browser.requestFullScreen() is deprecated. Please call Browser.requestFullscreen instead.\"),Browser.requestFullScreen=function(s,a,n){return Browser.requestFullscreen(s,a,n)},Browser.requestFullscreen(t,e,r)},nextRAF:0,fakeRequestAnimationFrame:function(t){var e=Date.now();if(Browser.nextRAF===0)Browser.nextRAF=e+1e3/60;else for(;e+2>=Browser.nextRAF;)Browser.nextRAF+=1e3/60;var r=Math.max(Browser.nextRAF-e,0);setTimeout(t,r)},requestAnimationFrame:function t(e){typeof window>\"u\"?Browser.fakeRequestAnimationFrame(e):(window.requestAnimationFrame||(window.requestAnimationFrame=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||window.msRequestAnimationFrame||window.oRequestAnimationFrame||Browser.fakeRequestAnimationFrame),window.requestAnimationFrame(e))},safeCallback:function(t){return function(){if(!ABORT)return t.apply(null,arguments)}},allowAsyncCallbacks:!0,queuedAsyncCallbacks:[],pauseAsyncCallbacks:function(){Browser.allowAsyncCallbacks=!1},resumeAsyncCallbacks:function(){if(Browser.allowAsyncCallbacks=!0,Browser.queuedAsyncCallbacks.length>0){var t=Browser.queuedAsyncCallbacks;Browser.queuedAsyncCallbacks=[],t.forEach(function(e){e()})}},safeRequestAnimationFrame:function(t){return Browser.requestAnimationFrame(function(){ABORT||(Browser.allowAsyncCallbacks?t():Browser.queuedAsyncCallbacks.push(t))})},safeSetTimeout:function(t,e){return Module.noExitRuntime=!0,setTimeout(function(){ABORT||(Browser.allowAsyncCallbacks?t():Browser.queuedAsyncCallbacks.push(t))},e)},safeSetInterval:function(t,e){return Module.noExitRuntime=!0,setInterval(function(){ABORT||Browser.allowAsyncCallbacks&&t()},e)},getMimetype:function(t){return{jpg:\"image/jpeg\",jpeg:\"image/jpeg\",png:\"image/png\",bmp:\"image/bmp\",ogg:\"audio/ogg\",wav:\"audio/wav\",mp3:\"audio/mpeg\"}[t.substr(t.lastIndexOf(\".\")+1)]},getUserMedia:function(t){window.getUserMedia||(window.getUserMedia=navigator.getUserMedia||navigator.mozGetUserMedia),window.getUserMedia(t)},getMovementX:function(t){return t.movementX||t.mozMovementX||t.webkitMovementX||0},getMovementY:function(t){return t.movementY||t.mozMovementY||t.webkitMovementY||0},getMouseWheelDelta:function(t){var e=0;switch(t.type){case\"DOMMouseScroll\":e=t.detail;break;case\"mousewheel\":e=t.wheelDelta;break;case\"wheel\":e=t.deltaY;break;default:throw\"unrecognized mouse wheel event: \"+t.type}return e},mouseX:0,mouseY:0,mouseMovementX:0,mouseMovementY:0,touches:{},lastTouches:{},calculateMouseEvent:function(t){if(Browser.pointerLock)t.type!=\"mousemove\"&&\"mozMovementX\"in t?Browser.mouseMovementX=Browser.mouseMovementY=0:(Browser.mouseMovementX=Browser.getMovementX(t),Browser.mouseMovementY=Browser.getMovementY(t)),typeof SDL<\"u\"?(Browser.mouseX=SDL.mouseX+Browser.mouseMovementX,Browser.mouseY=SDL.mouseY+Browser.mouseMovementY):(Browser.mouseX+=Browser.mouseMovementX,Browser.mouseY+=Browser.mouseMovementY);else{var e=Module.canvas.getBoundingClientRect(),r=Module.canvas.width,s=Module.canvas.height,a=typeof window.scrollX<\"u\"?window.scrollX:window.pageXOffset,n=typeof window.scrollY<\"u\"?window.scrollY:window.pageYOffset;if(t.type===\"touchstart\"||t.type===\"touchend\"||t.type===\"touchmove\"){var c=t.touch;if(c===void 0)return;var f=c.pageX-(a+e.left),p=c.pageY-(n+e.top);f=f*(r/e.width),p=p*(s/e.height);var h={x:f,y:p};if(t.type===\"touchstart\")Browser.lastTouches[c.identifier]=h,Browser.touches[c.identifier]=h;else if(t.type===\"touchend\"||t.type===\"touchmove\"){var E=Browser.touches[c.identifier];E||(E=h),Browser.lastTouches[c.identifier]=E,Browser.touches[c.identifier]=h}return}var C=t.pageX-(a+e.left),S=t.pageY-(n+e.top);C=C*(r/e.width),S=S*(s/e.height),Browser.mouseMovementX=C-Browser.mouseX,Browser.mouseMovementY=S-Browser.mouseY,Browser.mouseX=C,Browser.mouseY=S}},asyncLoad:function(t,e,r,s){var a=s?\"\":\"al \"+t;Module.readAsync(t,function(n){assert(n,'Loading data file \"'+t+'\" failed (no arrayBuffer).'),e(new Uint8Array(n)),a&&removeRunDependency(a)},function(n){if(r)r();else throw'Loading data file \"'+t+'\" failed.'}),a&&addRunDependency(a)},resizeListeners:[],updateResizeListeners:function(){var t=Module.canvas;Browser.resizeListeners.forEach(function(e){e(t.width,t.height)})},setCanvasSize:function(t,e,r){var s=Module.canvas;Browser.updateCanvasDimensions(s,t,e),r||Browser.updateResizeListeners()},windowedWidth:0,windowedHeight:0,setFullscreenCanvasSize:function(){if(typeof SDL<\"u\"){var t=HEAPU32[SDL.screen+Runtime.QUANTUM_SIZE*0>>2];t=t|8388608,HEAP32[SDL.screen+Runtime.QUANTUM_SIZE*0>>2]=t}Browser.updateResizeListeners()},setWindowedCanvasSize:function(){if(typeof SDL<\"u\"){var t=HEAPU32[SDL.screen+Runtime.QUANTUM_SIZE*0>>2];t=t&-8388609,HEAP32[SDL.screen+Runtime.QUANTUM_SIZE*0>>2]=t}Browser.updateResizeListeners()},updateCanvasDimensions:function(t,e,r){e&&r?(t.widthNative=e,t.heightNative=r):(e=t.widthNative,r=t.heightNative);var s=e,a=r;if(Module.forcedAspectRatio&&Module.forcedAspectRatio>0&&(s/a<Module.forcedAspectRatio?s=Math.round(a*Module.forcedAspectRatio):a=Math.round(s/Module.forcedAspectRatio)),(document.fullscreenElement||document.mozFullScreenElement||document.msFullscreenElement||document.webkitFullscreenElement||document.webkitCurrentFullScreenElement)===t.parentNode&&typeof screen<\"u\"){var n=Math.min(screen.width/s,screen.height/a);s=Math.round(s*n),a=Math.round(a*n)}Browser.resizeCanvas?(t.width!=s&&(t.width=s),t.height!=a&&(t.height=a),typeof t.style<\"u\"&&(t.style.removeProperty(\"width\"),t.style.removeProperty(\"height\"))):(t.width!=e&&(t.width=e),t.height!=r&&(t.height=r),typeof t.style<\"u\"&&(s!=e||a!=r?(t.style.setProperty(\"width\",s+\"px\",\"important\"),t.style.setProperty(\"height\",a+\"px\",\"important\")):(t.style.removeProperty(\"width\"),t.style.removeProperty(\"height\"))))},wgetRequests:{},nextWgetRequestHandle:0,getNextWgetRequestHandle:function(){var t=Browser.nextWgetRequestHandle;return Browser.nextWgetRequestHandle++,t}},SYSCALLS={varargs:0,get:function(t){SYSCALLS.varargs+=4;var e=HEAP32[SYSCALLS.varargs-4>>2];return e},getStr:function(){var t=Pointer_stringify(SYSCALLS.get());return t},get64:function(){var t=SYSCALLS.get(),e=SYSCALLS.get();return t>=0?assert(e===0):assert(e===-1),t},getZero:function(){assert(SYSCALLS.get()===0)}};function ___syscall6(t,e){SYSCALLS.varargs=e;try{var r=SYSCALLS.getStreamFromFD();return FS.close(r),0}catch(s){return(typeof FS>\"u\"||!(s instanceof FS.ErrnoError))&&abort(s),-s.errno}}function ___syscall54(t,e){SYSCALLS.varargs=e;try{return 0}catch(r){return(typeof FS>\"u\"||!(r instanceof FS.ErrnoError))&&abort(r),-r.errno}}function _typeModule(t){var e=[[0,1,\"X\"],[1,1,\"const X\"],[128,1,\"X *\"],[256,1,\"X &\"],[384,1,\"X &&\"],[512,1,\"std::shared_ptr<X>\"],[640,1,\"std::unique_ptr<X>\"],[5120,1,\"std::vector<X>\"],[6144,2,\"std::array<X, Y>\"],[9216,-1,\"std::function<X (Y)>\"]];function r(p,h,E,C,S,P){if(h==1){var I=C&896;(I==128||I==256||I==384)&&(p=\"X const\")}var R;return P?R=E.replace(\"X\",p).replace(\"Y\",S):R=p.replace(\"X\",E).replace(\"Y\",S),R.replace(/([*&]) (?=[*&])/g,\"$1\")}function s(p,h,E,C,S){throw new Error(p+\" type \"+E.replace(\"X\",h+\"?\")+(C?\" with flag \"+C:\"\")+\" in \"+S)}function a(p,h,E,C,S,P,I,R){P===void 0&&(P=\"X\"),R===void 0&&(R=1);var N=E(p);if(N)return N;var U=C(p),W=U.placeholderFlag,te=e[W];I&&te&&(P=r(I[2],I[0],P,te[0],\"?\",!0));var ie;W==0&&(ie=\"Unbound\"),W>=10&&(ie=\"Corrupt\"),R>20&&(ie=\"Deeply nested\"),ie&&s(ie,p,P,W,S||\"?\");var Ae=U.paramList[0],ce=a(Ae,h,E,C,S,P,te,R+1),me,pe={flags:te[0],id:p,name:\"\",paramList:[ce]},Be=[],Ce=\"?\";switch(U.placeholderFlag){case 1:me=ce.spec;break;case 2:if((ce.flags&15360)==1024&&ce.spec.ptrSize==1){pe.flags=7168;break}case 3:case 6:case 5:me=ce.spec,ce.flags&15360;break;case 8:Ce=\"\"+U.paramList[1],pe.paramList.push(U.paramList[1]);break;case 9:for(var g=0,we=U.paramList[1];g<we.length;g++){var ye=we[g],fe=a(ye,h,E,C,S,P,te,R+1);Be.push(fe.name),pe.paramList.push(fe)}Ce=Be.join(\", \");break;default:break}if(pe.name=r(te[2],te[0],ce.name,ce.flags,Ce),me){for(var se=0,X=Object.keys(me);se<X.length;se++){var De=X[se];pe[De]=pe[De]||me[De]}pe.flags|=me.flags}return n(h,pe)}function n(p,h){var E=h.flags,C=E&896,S=E&15360;return!h.name&&S==1024&&(h.ptrSize==1?h.name=(E&16?\"\":(E&8?\"un\":\"\")+\"signed \")+\"char\":h.name=(E&8?\"u\":\"\")+(E&32?\"float\":\"int\")+(h.ptrSize*8+\"_t\")),h.ptrSize==8&&!(E&32)&&(S=64),S==2048&&(C==512||C==640?S=4096:C&&(S=3072)),p(S,h)}var c=function(){function p(h){this.id=h.id,this.name=h.name,this.flags=h.flags,this.spec=h}return p.prototype.toString=function(){return this.name},p}(),f={Type:c,getComplexType:a,makeType:n,structureList:e};return t.output=f,t.output||f}function __nbind_register_type(t,e){var r=_nbind.readAsciiString(e),s={flags:10240,id:t,name:r};_nbind.makeType(_nbind.constructType,s)}function __nbind_register_callback_signature(t,e){var r=_nbind.readTypeIdList(t,e),s=_nbind.callbackSignatureList.length;return _nbind.callbackSignatureList[s]=_nbind.makeJSCaller(r),s}function __extends(t,e){for(var r in e)e.hasOwnProperty(r)&&(t[r]=e[r]);function s(){this.constructor=t}s.prototype=e.prototype,t.prototype=new s}function __nbind_register_class(t,e,r,s,a,n,c){var f=_nbind.readAsciiString(c),p=_nbind.readPolicyList(e),h=HEAPU32.subarray(t/4,t/4+2),E={flags:2048|(p.Value?2:0),id:h[0],name:f},C=_nbind.makeType(_nbind.constructType,E);C.ptrType=_nbind.getComplexType(h[1],_nbind.constructType,_nbind.getType,_nbind.queryType),C.destroy=_nbind.makeMethodCaller(C.ptrType,{boundID:E.id,flags:0,name:\"destroy\",num:0,ptr:n,title:C.name+\".free\",typeList:[\"void\",\"uint32_t\",\"uint32_t\"]}),a&&(C.superIdList=Array.prototype.slice.call(HEAPU32.subarray(r/4,r/4+a)),C.upcastList=Array.prototype.slice.call(HEAPU32.subarray(s/4,s/4+a))),Module[C.name]=C.makeBound(p),_nbind.BindClass.list.push(C)}function _removeAccessorPrefix(t){var e=/^[Gg]et_?([A-Z]?([A-Z]?))/;return t.replace(e,function(r,s,a){return a?s:s.toLowerCase()})}function __nbind_register_function(t,e,r,s,a,n,c,f,p,h){var E=_nbind.getType(t),C=_nbind.readPolicyList(e),S=_nbind.readTypeIdList(r,s),P;if(c==5)P=[{direct:a,name:\"__nbindConstructor\",ptr:0,title:E.name+\" constructor\",typeList:[\"uint32_t\"].concat(S.slice(1))},{direct:n,name:\"__nbindValueConstructor\",ptr:0,title:E.name+\" value constructor\",typeList:[\"void\",\"uint32_t\"].concat(S.slice(1))}];else{var I=_nbind.readAsciiString(f),R=(E.name&&E.name+\".\")+I;(c==3||c==4)&&(I=_removeAccessorPrefix(I)),P=[{boundID:t,direct:n,name:I,ptr:a,title:R,typeList:S}]}for(var N=0,U=P;N<U.length;N++){var W=U[N];W.signatureType=c,W.policyTbl=C,W.num=p,W.flags=h,E.addMethod(W)}}function _nbind_value(t,e){_nbind.typeNameTbl[t]||_nbind.throwError(\"Unknown value type \"+t),Module.NBind.bind_value(t,e),_defineHidden(_nbind.typeNameTbl[t].proto.prototype.__nbindValueConstructor)(e.prototype,\"__nbindValueConstructor\")}Module._nbind_value=_nbind_value;function __nbind_get_value_object(t,e){var r=_nbind.popValue(t);if(!r.fromJS)throw new Error(\"Object \"+r+\" has no fromJS function\");r.fromJS(function(){r.__nbindValueConstructor.apply(this,Array.prototype.concat.apply([e],arguments))})}function _emscripten_memcpy_big(t,e,r){return HEAPU8.set(HEAPU8.subarray(e,e+r),t),t}function __nbind_register_primitive(t,e,r){var s={flags:1024|r,id:t,ptrSize:e};_nbind.makeType(_nbind.constructType,s)}var cttz_i8=allocate([8,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,5,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,6,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,5,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,7,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,5,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,6,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,5,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0],\"i8\",ALLOC_STATIC);function ___setErrNo(t){return Module.___errno_location&&(HEAP32[Module.___errno_location()>>2]=t),t}function _llvm_stacksave(){var t=_llvm_stacksave;return t.LLVM_SAVEDSTACKS||(t.LLVM_SAVEDSTACKS=[]),t.LLVM_SAVEDSTACKS.push(Runtime.stackSave()),t.LLVM_SAVEDSTACKS.length-1}function ___syscall140(t,e){SYSCALLS.varargs=e;try{var r=SYSCALLS.getStreamFromFD(),s=SYSCALLS.get(),a=SYSCALLS.get(),n=SYSCALLS.get(),c=SYSCALLS.get(),f=a;return FS.llseek(r,f,c),HEAP32[n>>2]=r.position,r.getdents&&f===0&&c===0&&(r.getdents=null),0}catch(p){return(typeof FS>\"u\"||!(p instanceof FS.ErrnoError))&&abort(p),-p.errno}}function ___syscall146(t,e){SYSCALLS.varargs=e;try{var r=SYSCALLS.get(),s=SYSCALLS.get(),a=SYSCALLS.get(),n=0;___syscall146.buffer||(___syscall146.buffers=[null,[],[]],___syscall146.printChar=function(E,C){var S=___syscall146.buffers[E];assert(S),C===0||C===10?((E===1?Module.print:Module.printErr)(UTF8ArrayToString(S,0)),S.length=0):S.push(C)});for(var c=0;c<a;c++){for(var f=HEAP32[s+c*8>>2],p=HEAP32[s+(c*8+4)>>2],h=0;h<p;h++)___syscall146.printChar(r,HEAPU8[f+h]);n+=p}return n}catch(E){return(typeof FS>\"u\"||!(E instanceof FS.ErrnoError))&&abort(E),-E.errno}}function __nbind_finish(){for(var t=0,e=_nbind.BindClass.list;t<e.length;t++){var r=e[t];r.finish()}}var ___dso_handle=STATICTOP;STATICTOP+=16,function(_nbind){var typeIdTbl={};_nbind.typeNameTbl={};var Pool=function(){function t(){}return t.lalloc=function(e){e=e+7&-8;var r=HEAPU32[t.usedPtr];if(e>t.pageSize/2||e>t.pageSize-r){var s=_nbind.typeNameTbl.NBind.proto;return s.lalloc(e)}else return HEAPU32[t.usedPtr]=r+e,t.rootPtr+r},t.lreset=function(e,r){var s=HEAPU32[t.pagePtr];if(s){var a=_nbind.typeNameTbl.NBind.proto;a.lreset(e,r)}else HEAPU32[t.usedPtr]=e},t}();_nbind.Pool=Pool;function constructType(t,e){var r=t==10240?_nbind.makeTypeNameTbl[e.name]||_nbind.BindType:_nbind.makeTypeKindTbl[t],s=new r(e);return typeIdTbl[e.id]=s,_nbind.typeNameTbl[e.name]=s,s}_nbind.constructType=constructType;function getType(t){return typeIdTbl[t]}_nbind.getType=getType;function queryType(t){var e=HEAPU8[t],r=_nbind.structureList[e][1];t/=4,r<0&&(++t,r=HEAPU32[t]+1);var s=Array.prototype.slice.call(HEAPU32.subarray(t+1,t+1+r));return e==9&&(s=[s[0],s.slice(1)]),{paramList:s,placeholderFlag:e}}_nbind.queryType=queryType;function getTypes(t,e){return t.map(function(r){return typeof r==\"number\"?_nbind.getComplexType(r,constructType,getType,queryType,e):_nbind.typeNameTbl[r]})}_nbind.getTypes=getTypes;function readTypeIdList(t,e){return Array.prototype.slice.call(HEAPU32,t/4,t/4+e)}_nbind.readTypeIdList=readTypeIdList;function readAsciiString(t){for(var e=t;HEAPU8[e++];);return String.fromCharCode.apply(\"\",HEAPU8.subarray(t,e-1))}_nbind.readAsciiString=readAsciiString;function readPolicyList(t){var e={};if(t)for(;;){var r=HEAPU32[t/4];if(!r)break;e[readAsciiString(r)]=!0,t+=4}return e}_nbind.readPolicyList=readPolicyList;function getDynCall(t,e){var r={float32_t:\"d\",float64_t:\"d\",int64_t:\"d\",uint64_t:\"d\",void:\"v\"},s=t.map(function(n){return r[n.name]||\"i\"}).join(\"\"),a=Module[\"dynCall_\"+s];if(!a)throw new Error(\"dynCall_\"+s+\" not found for \"+e+\"(\"+t.map(function(n){return n.name}).join(\", \")+\")\");return a}_nbind.getDynCall=getDynCall;function addMethod(t,e,r,s){var a=t[e];t.hasOwnProperty(e)&&a?((a.arity||a.arity===0)&&(a=_nbind.makeOverloader(a,a.arity),t[e]=a),a.addMethod(r,s)):(r.arity=s,t[e]=r)}_nbind.addMethod=addMethod;function throwError(t){throw new Error(t)}_nbind.throwError=throwError,_nbind.bigEndian=!1,_a=_typeModule(_typeModule),_nbind.Type=_a.Type,_nbind.makeType=_a.makeType,_nbind.getComplexType=_a.getComplexType,_nbind.structureList=_a.structureList;var BindType=function(t){__extends(e,t);function e(){var r=t!==null&&t.apply(this,arguments)||this;return r.heap=HEAPU32,r.ptrSize=4,r}return e.prototype.needsWireRead=function(r){return!!this.wireRead||!!this.makeWireRead},e.prototype.needsWireWrite=function(r){return!!this.wireWrite||!!this.makeWireWrite},e}(_nbind.Type);_nbind.BindType=BindType;var PrimitiveType=function(t){__extends(e,t);function e(r){var s=t.call(this,r)||this,a=r.flags&32?{32:HEAPF32,64:HEAPF64}:r.flags&8?{8:HEAPU8,16:HEAPU16,32:HEAPU32}:{8:HEAP8,16:HEAP16,32:HEAP32};return s.heap=a[r.ptrSize*8],s.ptrSize=r.ptrSize,s}return e.prototype.needsWireWrite=function(r){return!!r&&!!r.Strict},e.prototype.makeWireWrite=function(r,s){return s&&s.Strict&&function(a){if(typeof a==\"number\")return a;throw new Error(\"Type mismatch\")}},e}(BindType);_nbind.PrimitiveType=PrimitiveType;function pushCString(t,e){if(t==null){if(e&&e.Nullable)return 0;throw new Error(\"Type mismatch\")}if(e&&e.Strict){if(typeof t!=\"string\")throw new Error(\"Type mismatch\")}else t=t.toString();var r=Module.lengthBytesUTF8(t)+1,s=_nbind.Pool.lalloc(r);return Module.stringToUTF8Array(t,HEAPU8,s,r),s}_nbind.pushCString=pushCString;function popCString(t){return t===0?null:Module.Pointer_stringify(t)}_nbind.popCString=popCString;var CStringType=function(t){__extends(e,t);function e(){var r=t!==null&&t.apply(this,arguments)||this;return r.wireRead=popCString,r.wireWrite=pushCString,r.readResources=[_nbind.resources.pool],r.writeResources=[_nbind.resources.pool],r}return e.prototype.makeWireWrite=function(r,s){return function(a){return pushCString(a,s)}},e}(BindType);_nbind.CStringType=CStringType;var BooleanType=function(t){__extends(e,t);function e(){var r=t!==null&&t.apply(this,arguments)||this;return r.wireRead=function(s){return!!s},r}return e.prototype.needsWireWrite=function(r){return!!r&&!!r.Strict},e.prototype.makeWireRead=function(r){return\"!!(\"+r+\")\"},e.prototype.makeWireWrite=function(r,s){return s&&s.Strict&&function(a){if(typeof a==\"boolean\")return a;throw new Error(\"Type mismatch\")}||r},e}(BindType);_nbind.BooleanType=BooleanType;var Wrapper=function(){function t(){}return t.prototype.persist=function(){this.__nbindState|=1},t}();_nbind.Wrapper=Wrapper;function makeBound(t,e){var r=function(s){__extends(a,s);function a(n,c,f,p){var h=s.call(this)||this;if(!(h instanceof a))return new(Function.prototype.bind.apply(a,Array.prototype.concat.apply([null],arguments)));var E=c,C=f,S=p;if(n!==_nbind.ptrMarker){var P=h.__nbindConstructor.apply(h,arguments);E=4608,S=HEAPU32[P/4],C=HEAPU32[P/4+1]}var I={configurable:!0,enumerable:!1,value:null,writable:!1},R={__nbindFlags:E,__nbindPtr:C};S&&(R.__nbindShared=S,_nbind.mark(h));for(var N=0,U=Object.keys(R);N<U.length;N++){var W=U[N];I.value=R[W],Object.defineProperty(h,W,I)}return _defineHidden(0)(h,\"__nbindState\"),h}return a.prototype.free=function(){e.destroy.call(this,this.__nbindShared,this.__nbindFlags),this.__nbindState|=2,disableMember(this,\"__nbindShared\"),disableMember(this,\"__nbindPtr\")},a}(Wrapper);return __decorate([_defineHidden()],r.prototype,\"__nbindConstructor\",void 0),__decorate([_defineHidden()],r.prototype,\"__nbindValueConstructor\",void 0),__decorate([_defineHidden(t)],r.prototype,\"__nbindPolicies\",void 0),r}_nbind.makeBound=makeBound;function disableMember(t,e){function r(){throw new Error(\"Accessing deleted object\")}Object.defineProperty(t,e,{configurable:!1,enumerable:!1,get:r,set:r})}_nbind.ptrMarker={};var BindClass=function(t){__extends(e,t);function e(r){var s=t.call(this,r)||this;return s.wireRead=function(a){return _nbind.popValue(a,s.ptrType)},s.wireWrite=function(a){return pushPointer(a,s.ptrType,!0)},s.pendingSuperCount=0,s.ready=!1,s.methodTbl={},r.paramList?(s.classType=r.paramList[0].classType,s.proto=s.classType.proto):s.classType=s,s}return e.prototype.makeBound=function(r){var s=_nbind.makeBound(r,this);return this.proto=s,this.ptrType.proto=s,s},e.prototype.addMethod=function(r){var s=this.methodTbl[r.name]||[];s.push(r),this.methodTbl[r.name]=s},e.prototype.registerMethods=function(r,s){for(var a,n=0,c=Object.keys(r.methodTbl);n<c.length;n++)for(var f=c[n],p=r.methodTbl[f],h=0,E=p;h<E.length;h++){var C=E[h],S=void 0,P=void 0;if(S=this.proto.prototype,!(s&&C.signatureType!=1))switch(C.signatureType){case 1:S=this.proto;case 5:P=_nbind.makeCaller(C),_nbind.addMethod(S,C.name,P,C.typeList.length-1);break;case 4:a=_nbind.makeMethodCaller(r.ptrType,C);break;case 3:Object.defineProperty(S,C.name,{configurable:!0,enumerable:!1,get:_nbind.makeMethodCaller(r.ptrType,C),set:a});break;case 2:P=_nbind.makeMethodCaller(r.ptrType,C),_nbind.addMethod(S,C.name,P,C.typeList.length-1);break;default:break}}},e.prototype.registerSuperMethods=function(r,s,a){if(!a[r.name]){a[r.name]=!0;for(var n=0,c,f=0,p=r.superIdList||[];f<p.length;f++){var h=p[f],E=_nbind.getType(h);n++<s||s<0?c=-1:c=0,this.registerSuperMethods(E,c,a)}this.registerMethods(r,s<0)}},e.prototype.finish=function(){if(this.ready)return this;this.ready=!0,this.superList=(this.superIdList||[]).map(function(a){return _nbind.getType(a).finish()});var r=this.proto;if(this.superList.length){var s=function(){this.constructor=r};s.prototype=this.superList[0].proto.prototype,r.prototype=new s}return r!=Module&&(r.prototype.__nbindType=this),this.registerSuperMethods(this,1,{}),this},e.prototype.upcastStep=function(r,s){if(r==this)return s;for(var a=0;a<this.superList.length;++a){var n=this.superList[a].upcastStep(r,_nbind.callUpcast(this.upcastList[a],s));if(n)return n}return 0},e}(_nbind.BindType);BindClass.list=[],_nbind.BindClass=BindClass;function popPointer(t,e){return t?new e.proto(_nbind.ptrMarker,e.flags,t):null}_nbind.popPointer=popPointer;function pushPointer(t,e,r){if(!(t instanceof _nbind.Wrapper)){if(r)return _nbind.pushValue(t);throw new Error(\"Type mismatch\")}var s=t.__nbindPtr,a=t.__nbindType.classType,n=e.classType;if(t instanceof e.proto)for(;a!=n;)s=_nbind.callUpcast(a.upcastList[0],s),a=a.superList[0];else if(s=a.upcastStep(n,s),!s)throw new Error(\"Type mismatch\");return s}_nbind.pushPointer=pushPointer;function pushMutablePointer(t,e){var r=pushPointer(t,e);if(t.__nbindFlags&1)throw new Error(\"Passing a const value as a non-const argument\");return r}var BindClassPtr=function(t){__extends(e,t);function e(r){var s=t.call(this,r)||this;s.classType=r.paramList[0].classType,s.proto=s.classType.proto;var a=r.flags&1,n=(s.flags&896)==256&&r.flags&2,c=a?pushPointer:pushMutablePointer,f=n?_nbind.popValue:popPointer;return s.makeWireWrite=function(p,h){return h.Nullable?function(E){return E?c(E,s):0}:function(E){return c(E,s)}},s.wireRead=function(p){return f(p,s)},s.wireWrite=function(p){return c(p,s)},s}return e}(_nbind.BindType);_nbind.BindClassPtr=BindClassPtr;function popShared(t,e){var r=HEAPU32[t/4],s=HEAPU32[t/4+1];return s?new e.proto(_nbind.ptrMarker,e.flags,s,r):null}_nbind.popShared=popShared;function pushShared(t,e){if(!(t instanceof e.proto))throw new Error(\"Type mismatch\");return t.__nbindShared}function pushMutableShared(t,e){if(!(t instanceof e.proto))throw new Error(\"Type mismatch\");if(t.__nbindFlags&1)throw new Error(\"Passing a const value as a non-const argument\");return t.__nbindShared}var SharedClassPtr=function(t){__extends(e,t);function e(r){var s=t.call(this,r)||this;s.readResources=[_nbind.resources.pool],s.classType=r.paramList[0].classType,s.proto=s.classType.proto;var a=r.flags&1,n=a?pushShared:pushMutableShared;return s.wireRead=function(c){return popShared(c,s)},s.wireWrite=function(c){return n(c,s)},s}return e}(_nbind.BindType);_nbind.SharedClassPtr=SharedClassPtr,_nbind.externalList=[0];var firstFreeExternal=0,External=function(){function t(e){this.refCount=1,this.data=e}return t.prototype.register=function(){var e=firstFreeExternal;return e?firstFreeExternal=_nbind.externalList[e]:e=_nbind.externalList.length,_nbind.externalList[e]=this,e},t.prototype.reference=function(){++this.refCount},t.prototype.dereference=function(e){--this.refCount==0&&(this.free&&this.free(),_nbind.externalList[e]=firstFreeExternal,firstFreeExternal=e)},t}();_nbind.External=External;function popExternal(t){var e=_nbind.externalList[t];return e.dereference(t),e.data}function pushExternal(t){var e=new External(t);return e.reference(),e.register()}var ExternalType=function(t){__extends(e,t);function e(){var r=t!==null&&t.apply(this,arguments)||this;return r.wireRead=popExternal,r.wireWrite=pushExternal,r}return e}(_nbind.BindType);_nbind.ExternalType=ExternalType,_nbind.callbackSignatureList=[];var CallbackType=function(t){__extends(e,t);function e(){var r=t!==null&&t.apply(this,arguments)||this;return r.wireWrite=function(s){return typeof s!=\"function\"&&_nbind.throwError(\"Type mismatch\"),new _nbind.External(s).register()},r}return e}(_nbind.BindType);_nbind.CallbackType=CallbackType,_nbind.valueList=[0];var firstFreeValue=0;function pushValue(t){var e=firstFreeValue;return e?firstFreeValue=_nbind.valueList[e]:e=_nbind.valueList.length,_nbind.valueList[e]=t,e*2+1}_nbind.pushValue=pushValue;function popValue(t,e){if(t||_nbind.throwError(\"Value type JavaScript class is missing or not registered\"),t&1){t>>=1;var r=_nbind.valueList[t];return _nbind.valueList[t]=firstFreeValue,firstFreeValue=t,r}else{if(e)return _nbind.popShared(t,e);throw new Error(\"Invalid value slot \"+t)}}_nbind.popValue=popValue;var valueBase=18446744073709552e3;function push64(t){return typeof t==\"number\"?t:pushValue(t)*4096+valueBase}function pop64(t){return t<valueBase?t:popValue((t-valueBase)/4096)}var CreateValueType=function(t){__extends(e,t);function e(){return t!==null&&t.apply(this,arguments)||this}return e.prototype.makeWireWrite=function(r){return\"(_nbind.pushValue(new \"+r+\"))\"},e}(_nbind.BindType);_nbind.CreateValueType=CreateValueType;var Int64Type=function(t){__extends(e,t);function e(){var r=t!==null&&t.apply(this,arguments)||this;return r.wireWrite=push64,r.wireRead=pop64,r}return e}(_nbind.BindType);_nbind.Int64Type=Int64Type;function pushArray(t,e){if(!t)return 0;var r=t.length;if((e.size||e.size===0)&&r<e.size)throw new Error(\"Type mismatch\");var s=e.memberType.ptrSize,a=_nbind.Pool.lalloc(4+r*s);HEAPU32[a/4]=r;var n=e.memberType.heap,c=(a+4)/s,f=e.memberType.wireWrite,p=0;if(f)for(;p<r;)n[c++]=f(t[p++]);else for(;p<r;)n[c++]=t[p++];return a}_nbind.pushArray=pushArray;function popArray(t,e){if(t===0)return null;var r=HEAPU32[t/4],s=new Array(r),a=e.memberType.heap;t=(t+4)/e.memberType.ptrSize;var n=e.memberType.wireRead,c=0;if(n)for(;c<r;)s[c++]=n(a[t++]);else for(;c<r;)s[c++]=a[t++];return s}_nbind.popArray=popArray;var ArrayType=function(t){__extends(e,t);function e(r){var s=t.call(this,r)||this;return s.wireRead=function(a){return popArray(a,s)},s.wireWrite=function(a){return pushArray(a,s)},s.readResources=[_nbind.resources.pool],s.writeResources=[_nbind.resources.pool],s.memberType=r.paramList[0],r.paramList[1]&&(s.size=r.paramList[1]),s}return e}(_nbind.BindType);_nbind.ArrayType=ArrayType;function pushString(t,e){if(t==null)if(e&&e.Nullable)t=\"\";else throw new Error(\"Type mismatch\");if(e&&e.Strict){if(typeof t!=\"string\")throw new Error(\"Type mismatch\")}else t=t.toString();var r=Module.lengthBytesUTF8(t),s=_nbind.Pool.lalloc(4+r+1);return HEAPU32[s/4]=r,Module.stringToUTF8Array(t,HEAPU8,s+4,r+1),s}_nbind.pushString=pushString;function popString(t){if(t===0)return null;var e=HEAPU32[t/4];return Module.Pointer_stringify(t+4,e)}_nbind.popString=popString;var StringType=function(t){__extends(e,t);function e(){var r=t!==null&&t.apply(this,arguments)||this;return r.wireRead=popString,r.wireWrite=pushString,r.readResources=[_nbind.resources.pool],r.writeResources=[_nbind.resources.pool],r}return e.prototype.makeWireWrite=function(r,s){return function(a){return pushString(a,s)}},e}(_nbind.BindType);_nbind.StringType=StringType;function makeArgList(t){return Array.apply(null,Array(t)).map(function(e,r){return\"a\"+(r+1)})}function anyNeedsWireWrite(t,e){return t.reduce(function(r,s){return r||s.needsWireWrite(e)},!1)}function anyNeedsWireRead(t,e){return t.reduce(function(r,s){return r||!!s.needsWireRead(e)},!1)}function makeWireRead(t,e,r,s){var a=t.length;return r.makeWireRead?r.makeWireRead(s,t,a):r.wireRead?(t[a]=r.wireRead,\"(convertParamList[\"+a+\"](\"+s+\"))\"):s}function makeWireWrite(t,e,r,s){var a,n=t.length;return r.makeWireWrite?a=r.makeWireWrite(s,e,t,n):a=r.wireWrite,a?typeof a==\"string\"?a:(t[n]=a,\"(convertParamList[\"+n+\"](\"+s+\"))\"):s}function buildCallerFunction(dynCall,ptrType,ptr,num,policyTbl,needsWireWrite,prefix,returnType,argTypeList,mask,err){var argList=makeArgList(argTypeList.length),convertParamList=[],callExpression=makeWireRead(convertParamList,policyTbl,returnType,\"dynCall(\"+[prefix].concat(argList.map(function(t,e){return makeWireWrite(convertParamList,policyTbl,argTypeList[e],t)})).join(\",\")+\")\"),resourceSet=_nbind.listResources([returnType],argTypeList),sourceCode=\"function(\"+argList.join(\",\")+\"){\"+(mask?\"this.__nbindFlags&mask&&err();\":\"\")+resourceSet.makeOpen()+\"var r=\"+callExpression+\";\"+resourceSet.makeClose()+\"return r;}\";return eval(\"(\"+sourceCode+\")\")}function buildJSCallerFunction(returnType,argTypeList){var argList=makeArgList(argTypeList.length),convertParamList=[],callExpression=makeWireWrite(convertParamList,null,returnType,\"_nbind.externalList[num].data(\"+argList.map(function(t,e){return makeWireRead(convertParamList,null,argTypeList[e],t)}).join(\",\")+\")\"),resourceSet=_nbind.listResources(argTypeList,[returnType]);resourceSet.remove(_nbind.resources.pool);var sourceCode=\"function(\"+[\"dummy\",\"num\"].concat(argList).join(\",\")+\"){\"+resourceSet.makeOpen()+\"var r=\"+callExpression+\";\"+resourceSet.makeClose()+\"return r;}\";return eval(\"(\"+sourceCode+\")\")}_nbind.buildJSCallerFunction=buildJSCallerFunction;function makeJSCaller(t){var e=t.length-1,r=_nbind.getTypes(t,\"callback\"),s=r[0],a=r.slice(1),n=anyNeedsWireRead(a,null),c=s.needsWireWrite(null);if(!c&&!n)switch(e){case 0:return function(f,p){return _nbind.externalList[p].data()};case 1:return function(f,p,h){return _nbind.externalList[p].data(h)};case 2:return function(f,p,h,E){return _nbind.externalList[p].data(h,E)};case 3:return function(f,p,h,E,C){return _nbind.externalList[p].data(h,E,C)};default:break}return buildJSCallerFunction(s,a)}_nbind.makeJSCaller=makeJSCaller;function makeMethodCaller(t,e){var r=e.typeList.length-1,s=e.typeList.slice(0);s.splice(1,0,\"uint32_t\",e.boundID);var a=_nbind.getTypes(s,e.title),n=a[0],c=a.slice(3),f=n.needsWireRead(e.policyTbl),p=anyNeedsWireWrite(c,e.policyTbl),h=e.ptr,E=e.num,C=_nbind.getDynCall(a,e.title),S=~e.flags&1;function P(){throw new Error(\"Calling a non-const method on a const object\")}if(!f&&!p)switch(r){case 0:return function(){return this.__nbindFlags&S?P():C(h,E,_nbind.pushPointer(this,t))};case 1:return function(I){return this.__nbindFlags&S?P():C(h,E,_nbind.pushPointer(this,t),I)};case 2:return function(I,R){return this.__nbindFlags&S?P():C(h,E,_nbind.pushPointer(this,t),I,R)};case 3:return function(I,R,N){return this.__nbindFlags&S?P():C(h,E,_nbind.pushPointer(this,t),I,R,N)};default:break}return buildCallerFunction(C,t,h,E,e.policyTbl,p,\"ptr,num,pushPointer(this,ptrType)\",n,c,S,P)}_nbind.makeMethodCaller=makeMethodCaller;function makeCaller(t){var e=t.typeList.length-1,r=_nbind.getTypes(t.typeList,t.title),s=r[0],a=r.slice(1),n=s.needsWireRead(t.policyTbl),c=anyNeedsWireWrite(a,t.policyTbl),f=t.direct,p=t.ptr;if(t.direct&&!n&&!c){var h=_nbind.getDynCall(r,t.title);switch(e){case 0:return function(){return h(f)};case 1:return function(P){return h(f,P)};case 2:return function(P,I){return h(f,P,I)};case 3:return function(P,I,R){return h(f,P,I,R)};default:break}p=0}var E;if(p){var C=t.typeList.slice(0);C.splice(1,0,\"uint32_t\"),r=_nbind.getTypes(C,t.title),E=\"ptr,num\"}else p=f,E=\"ptr\";var S=_nbind.getDynCall(r,t.title);return buildCallerFunction(S,null,p,t.num,t.policyTbl,c,E,s,a)}_nbind.makeCaller=makeCaller;function makeOverloader(t,e){var r=[];function s(){return r[arguments.length].apply(this,arguments)}return s.addMethod=function(a,n){r[n]=a},s.addMethod(t,e),s}_nbind.makeOverloader=makeOverloader;var Resource=function(){function t(e,r){var s=this;this.makeOpen=function(){return Object.keys(s.openTbl).join(\"\")},this.makeClose=function(){return Object.keys(s.closeTbl).join(\"\")},this.openTbl={},this.closeTbl={},e&&(this.openTbl[e]=!0),r&&(this.closeTbl[r]=!0)}return t.prototype.add=function(e){for(var r=0,s=Object.keys(e.openTbl);r<s.length;r++){var a=s[r];this.openTbl[a]=!0}for(var n=0,c=Object.keys(e.closeTbl);n<c.length;n++){var a=c[n];this.closeTbl[a]=!0}},t.prototype.remove=function(e){for(var r=0,s=Object.keys(e.openTbl);r<s.length;r++){var a=s[r];delete this.openTbl[a]}for(var n=0,c=Object.keys(e.closeTbl);n<c.length;n++){var a=c[n];delete this.closeTbl[a]}},t}();_nbind.Resource=Resource;function listResources(t,e){for(var r=new Resource,s=0,a=t;s<a.length;s++)for(var n=a[s],c=0,f=n.readResources||[];c<f.length;c++){var p=f[c];r.add(p)}for(var h=0,E=e;h<E.length;h++)for(var n=E[h],C=0,S=n.writeResources||[];C<S.length;C++){var p=S[C];r.add(p)}return r}_nbind.listResources=listResources,_nbind.resources={pool:new Resource(\"var used=HEAPU32[_nbind.Pool.usedPtr],page=HEAPU32[_nbind.Pool.pagePtr];\",\"_nbind.Pool.lreset(used,page);\")};var ExternalBuffer=function(t){__extends(e,t);function e(r,s){var a=t.call(this,r)||this;return a.ptr=s,a}return e.prototype.free=function(){_free(this.ptr)},e}(_nbind.External);function getBuffer(t){return t instanceof ArrayBuffer?new Uint8Array(t):t instanceof DataView?new Uint8Array(t.buffer,t.byteOffset,t.byteLength):t}function pushBuffer(t,e){if(t==null&&e&&e.Nullable&&(t=[]),typeof t!=\"object\")throw new Error(\"Type mismatch\");var r=t,s=r.byteLength||r.length;if(!s&&s!==0&&r.byteLength!==0)throw new Error(\"Type mismatch\");var a=_nbind.Pool.lalloc(8),n=_malloc(s),c=a/4;return HEAPU32[c++]=s,HEAPU32[c++]=n,HEAPU32[c++]=new ExternalBuffer(t,n).register(),HEAPU8.set(getBuffer(t),n),a}var BufferType=function(t){__extends(e,t);function e(){var r=t!==null&&t.apply(this,arguments)||this;return r.wireWrite=pushBuffer,r.readResources=[_nbind.resources.pool],r.writeResources=[_nbind.resources.pool],r}return e.prototype.makeWireWrite=function(r,s){return function(a){return pushBuffer(a,s)}},e}(_nbind.BindType);_nbind.BufferType=BufferType;function commitBuffer(t,e,r){var s=_nbind.externalList[t].data,a=Buffer;if(typeof Buffer!=\"function\"&&(a=function(){}),!(s instanceof Array)){var n=HEAPU8.subarray(e,e+r);if(s instanceof a){var c=void 0;typeof Buffer.from==\"function\"&&Buffer.from.length>=3?c=Buffer.from(n):c=new Buffer(n),c.copy(s)}else getBuffer(s).set(n)}}_nbind.commitBuffer=commitBuffer;var dirtyList=[],gcTimer=0;function sweep(){for(var t=0,e=dirtyList;t<e.length;t++){var r=e[t];r.__nbindState&3||r.free()}dirtyList=[],gcTimer=0}_nbind.mark=function(t){};function toggleLightGC(t){t?_nbind.mark=function(e){dirtyList.push(e),gcTimer||(gcTimer=setTimeout(sweep,0))}:_nbind.mark=function(e){}}_nbind.toggleLightGC=toggleLightGC}(_nbind),Module.requestFullScreen=function t(e,r,s){Module.printErr(\"Module.requestFullScreen is deprecated. Please call Module.requestFullscreen instead.\"),Module.requestFullScreen=Module.requestFullscreen,Browser.requestFullScreen(e,r,s)},Module.requestFullscreen=function t(e,r,s){Browser.requestFullscreen(e,r,s)},Module.requestAnimationFrame=function t(e){Browser.requestAnimationFrame(e)},Module.setCanvasSize=function t(e,r,s){Browser.setCanvasSize(e,r,s)},Module.pauseMainLoop=function t(){Browser.mainLoop.pause()},Module.resumeMainLoop=function t(){Browser.mainLoop.resume()},Module.getUserMedia=function t(){Browser.getUserMedia()},Module.createContext=function t(e,r,s,a){return Browser.createContext(e,r,s,a)},ENVIRONMENT_IS_NODE?_emscripten_get_now=function(){var e=process.hrtime();return e[0]*1e3+e[1]/1e6}:typeof dateNow<\"u\"?_emscripten_get_now=dateNow:typeof self==\"object\"&&self.performance&&typeof self.performance.now==\"function\"?_emscripten_get_now=function(){return self.performance.now()}:typeof performance==\"object\"&&typeof performance.now==\"function\"?_emscripten_get_now=function(){return performance.now()}:_emscripten_get_now=Date.now,__ATEXIT__.push(function(){var t=Module._fflush;t&&t(0);var e=___syscall146.printChar;if(e){var r=___syscall146.buffers;r[1].length&&e(1,10),r[2].length&&e(2,10)}}),DYNAMICTOP_PTR=allocate(1,\"i32\",ALLOC_STATIC),STACK_BASE=STACKTOP=Runtime.alignMemory(STATICTOP),STACK_MAX=STACK_BASE+TOTAL_STACK,DYNAMIC_BASE=Runtime.alignMemory(STACK_MAX),HEAP32[DYNAMICTOP_PTR>>2]=DYNAMIC_BASE,staticSealed=!0;function invoke_viiiii(t,e,r,s,a,n){try{Module.dynCall_viiiii(t,e,r,s,a,n)}catch(c){if(typeof c!=\"number\"&&c!==\"longjmp\")throw c;Module.setThrew(1,0)}}function invoke_vif(t,e,r){try{Module.dynCall_vif(t,e,r)}catch(s){if(typeof s!=\"number\"&&s!==\"longjmp\")throw s;Module.setThrew(1,0)}}function invoke_vid(t,e,r){try{Module.dynCall_vid(t,e,r)}catch(s){if(typeof s!=\"number\"&&s!==\"longjmp\")throw s;Module.setThrew(1,0)}}function invoke_fiff(t,e,r,s){try{return Module.dynCall_fiff(t,e,r,s)}catch(a){if(typeof a!=\"number\"&&a!==\"longjmp\")throw a;Module.setThrew(1,0)}}function invoke_vi(t,e){try{Module.dynCall_vi(t,e)}catch(r){if(typeof r!=\"number\"&&r!==\"longjmp\")throw r;Module.setThrew(1,0)}}function invoke_vii(t,e,r){try{Module.dynCall_vii(t,e,r)}catch(s){if(typeof s!=\"number\"&&s!==\"longjmp\")throw s;Module.setThrew(1,0)}}function invoke_ii(t,e){try{return Module.dynCall_ii(t,e)}catch(r){if(typeof r!=\"number\"&&r!==\"longjmp\")throw r;Module.setThrew(1,0)}}function invoke_viddi(t,e,r,s,a){try{Module.dynCall_viddi(t,e,r,s,a)}catch(n){if(typeof n!=\"number\"&&n!==\"longjmp\")throw n;Module.setThrew(1,0)}}function invoke_vidd(t,e,r,s){try{Module.dynCall_vidd(t,e,r,s)}catch(a){if(typeof a!=\"number\"&&a!==\"longjmp\")throw a;Module.setThrew(1,0)}}function invoke_iiii(t,e,r,s){try{return Module.dynCall_iiii(t,e,r,s)}catch(a){if(typeof a!=\"number\"&&a!==\"longjmp\")throw a;Module.setThrew(1,0)}}function invoke_diii(t,e,r,s){try{return Module.dynCall_diii(t,e,r,s)}catch(a){if(typeof a!=\"number\"&&a!==\"longjmp\")throw a;Module.setThrew(1,0)}}function invoke_di(t,e){try{return Module.dynCall_di(t,e)}catch(r){if(typeof r!=\"number\"&&r!==\"longjmp\")throw r;Module.setThrew(1,0)}}function invoke_iid(t,e,r){try{return Module.dynCall_iid(t,e,r)}catch(s){if(typeof s!=\"number\"&&s!==\"longjmp\")throw s;Module.setThrew(1,0)}}function invoke_iii(t,e,r){try{return Module.dynCall_iii(t,e,r)}catch(s){if(typeof s!=\"number\"&&s!==\"longjmp\")throw s;Module.setThrew(1,0)}}function invoke_viiddi(t,e,r,s,a,n){try{Module.dynCall_viiddi(t,e,r,s,a,n)}catch(c){if(typeof c!=\"number\"&&c!==\"longjmp\")throw c;Module.setThrew(1,0)}}function invoke_viiiiii(t,e,r,s,a,n,c){try{Module.dynCall_viiiiii(t,e,r,s,a,n,c)}catch(f){if(typeof f!=\"number\"&&f!==\"longjmp\")throw f;Module.setThrew(1,0)}}function invoke_dii(t,e,r){try{return Module.dynCall_dii(t,e,r)}catch(s){if(typeof s!=\"number\"&&s!==\"longjmp\")throw s;Module.setThrew(1,0)}}function invoke_i(t){try{return Module.dynCall_i(t)}catch(e){if(typeof e!=\"number\"&&e!==\"longjmp\")throw e;Module.setThrew(1,0)}}function invoke_iiiiii(t,e,r,s,a,n){try{return Module.dynCall_iiiiii(t,e,r,s,a,n)}catch(c){if(typeof c!=\"number\"&&c!==\"longjmp\")throw c;Module.setThrew(1,0)}}function invoke_viiid(t,e,r,s,a){try{Module.dynCall_viiid(t,e,r,s,a)}catch(n){if(typeof n!=\"number\"&&n!==\"longjmp\")throw n;Module.setThrew(1,0)}}function invoke_viififi(t,e,r,s,a,n,c){try{Module.dynCall_viififi(t,e,r,s,a,n,c)}catch(f){if(typeof f!=\"number\"&&f!==\"longjmp\")throw f;Module.setThrew(1,0)}}function invoke_viii(t,e,r,s){try{Module.dynCall_viii(t,e,r,s)}catch(a){if(typeof a!=\"number\"&&a!==\"longjmp\")throw a;Module.setThrew(1,0)}}function invoke_v(t){try{Module.dynCall_v(t)}catch(e){if(typeof e!=\"number\"&&e!==\"longjmp\")throw e;Module.setThrew(1,0)}}function invoke_viid(t,e,r,s){try{Module.dynCall_viid(t,e,r,s)}catch(a){if(typeof a!=\"number\"&&a!==\"longjmp\")throw a;Module.setThrew(1,0)}}function invoke_idd(t,e,r){try{return Module.dynCall_idd(t,e,r)}catch(s){if(typeof s!=\"number\"&&s!==\"longjmp\")throw s;Module.setThrew(1,0)}}function invoke_viiii(t,e,r,s,a){try{Module.dynCall_viiii(t,e,r,s,a)}catch(n){if(typeof n!=\"number\"&&n!==\"longjmp\")throw n;Module.setThrew(1,0)}}Module.asmGlobalArg={Math,Int8Array,Int16Array,Int32Array,Uint8Array,Uint16Array,Uint32Array,Float32Array,Float64Array,NaN:NaN,Infinity:1/0},Module.asmLibraryArg={abort,assert,enlargeMemory,getTotalMemory,abortOnCannotGrowMemory,invoke_viiiii,invoke_vif,invoke_vid,invoke_fiff,invoke_vi,invoke_vii,invoke_ii,invoke_viddi,invoke_vidd,invoke_iiii,invoke_diii,invoke_di,invoke_iid,invoke_iii,invoke_viiddi,invoke_viiiiii,invoke_dii,invoke_i,invoke_iiiiii,invoke_viiid,invoke_viififi,invoke_viii,invoke_v,invoke_viid,invoke_idd,invoke_viiii,_emscripten_asm_const_iiiii,_emscripten_asm_const_iiidddddd,_emscripten_asm_const_iiiid,__nbind_reference_external,_emscripten_asm_const_iiiiiiii,_removeAccessorPrefix,_typeModule,__nbind_register_pool,__decorate,_llvm_stackrestore,___cxa_atexit,__extends,__nbind_get_value_object,__ZN8facebook4yoga14YGNodeToStringEPNSt3__212basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEP6YGNode14YGPrintOptionsj,_emscripten_set_main_loop_timing,__nbind_register_primitive,__nbind_register_type,_emscripten_memcpy_big,__nbind_register_function,___setErrNo,__nbind_register_class,__nbind_finish,_abort,_nbind_value,_llvm_stacksave,___syscall54,_defineHidden,_emscripten_set_main_loop,_emscripten_get_now,__nbind_register_callback_signature,_emscripten_asm_const_iiiiii,__nbind_free_external,_emscripten_asm_const_iiii,_emscripten_asm_const_iiididi,___syscall6,_atexit,___syscall140,___syscall146,DYNAMICTOP_PTR,tempDoublePtr,ABORT,STACKTOP,STACK_MAX,cttz_i8,___dso_handle};var asm=function(t,e,r){var s=new t.Int8Array(r),a=new t.Int16Array(r),n=new t.Int32Array(r),c=new t.Uint8Array(r),f=new t.Uint16Array(r),p=new t.Uint32Array(r),h=new t.Float32Array(r),E=new t.Float64Array(r),C=e.DYNAMICTOP_PTR|0,S=e.tempDoublePtr|0,P=e.ABORT|0,I=e.STACKTOP|0,R=e.STACK_MAX|0,N=e.cttz_i8|0,U=e.___dso_handle|0,W=0,te=0,ie=0,Ae=0,ce=t.NaN,me=t.Infinity,pe=0,Be=0,Ce=0,g=0,we=0,ye=0,fe=t.Math.floor,se=t.Math.abs,X=t.Math.sqrt,De=t.Math.pow,Re=t.Math.cos,dt=t.Math.sin,j=t.Math.tan,rt=t.Math.acos,Fe=t.Math.asin,Ne=t.Math.atan,Pe=t.Math.atan2,Ye=t.Math.exp,ke=t.Math.log,it=t.Math.ceil,_e=t.Math.imul,x=t.Math.min,w=t.Math.max,b=t.Math.clz32,y=t.Math.fround,F=e.abort,z=e.assert,Z=e.enlargeMemory,$=e.getTotalMemory,oe=e.abortOnCannotGrowMemory,xe=e.invoke_viiiii,Te=e.invoke_vif,lt=e.invoke_vid,It=e.invoke_fiff,qt=e.invoke_vi,ir=e.invoke_vii,Pt=e.invoke_ii,gn=e.invoke_viddi,Pr=e.invoke_vidd,Ir=e.invoke_iiii,Nr=e.invoke_diii,nn=e.invoke_di,ai=e.invoke_iid,wo=e.invoke_iii,ns=e.invoke_viiddi,to=e.invoke_viiiiii,Bo=e.invoke_dii,ji=e.invoke_i,ro=e.invoke_iiiiii,vo=e.invoke_viiid,RA=e.invoke_viififi,pf=e.invoke_viii,yh=e.invoke_v,Eh=e.invoke_viid,no=e.invoke_idd,jn=e.invoke_viiii,Fs=e._emscripten_asm_const_iiiii,io=e._emscripten_asm_const_iiidddddd,lu=e._emscripten_asm_const_iiiid,cu=e.__nbind_reference_external,uu=e._emscripten_asm_const_iiiiiiii,FA=e._removeAccessorPrefix,NA=e._typeModule,aa=e.__nbind_register_pool,la=e.__decorate,OA=e._llvm_stackrestore,gr=e.___cxa_atexit,So=e.__extends,Me=e.__nbind_get_value_object,fu=e.__ZN8facebook4yoga14YGNodeToStringEPNSt3__212basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEP6YGNode14YGPrintOptionsj,Cr=e._emscripten_set_main_loop_timing,hf=e.__nbind_register_primitive,LA=e.__nbind_register_type,MA=e._emscripten_memcpy_big,Au=e.__nbind_register_function,pu=e.___setErrNo,ac=e.__nbind_register_class,ve=e.__nbind_finish,Nt=e._abort,lc=e._nbind_value,Li=e._llvm_stacksave,so=e.___syscall54,Rt=e._defineHidden,xn=e._emscripten_set_main_loop,ca=e._emscripten_get_now,qi=e.__nbind_register_callback_signature,Mi=e._emscripten_asm_const_iiiiii,Oa=e.__nbind_free_external,dn=e._emscripten_asm_const_iiii,Jn=e._emscripten_asm_const_iiididi,hu=e.___syscall6,Ih=e._atexit,La=e.___syscall140,Ma=e.___syscall146,Ua=y(0);let Xe=y(0);function Ha(o){o=o|0;var l=0;return l=I,I=I+o|0,I=I+15&-16,l|0}function gf(){return I|0}function cc(o){o=o|0,I=o}function wn(o,l){o=o|0,l=l|0,I=o,R=l}function ua(o,l){o=o|0,l=l|0,W||(W=o,te=l)}function _A(o){o=o|0,ye=o}function UA(){return ye|0}function fa(){var o=0,l=0;Qr(8104,8,400)|0,Qr(8504,408,540)|0,o=9044,l=o+44|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));s[9088]=0,s[9089]=1,n[2273]=0,n[2274]=948,n[2275]=948,gr(17,8104,U|0)|0}function vl(o){o=o|0,gt(o+948|0)}function Mt(o){return o=y(o),((EP(o)|0)&2147483647)>>>0>2139095040|0}function kn(o,l,u){o=o|0,l=l|0,u=u|0;e:do if(n[o+(l<<3)+4>>2]|0)o=o+(l<<3)|0;else{if((l|2|0)==3&&n[o+60>>2]|0){o=o+56|0;break}switch(l|0){case 0:case 2:case 4:case 5:{if(n[o+52>>2]|0){o=o+48|0;break e}break}default:}if(n[o+68>>2]|0){o=o+64|0;break}else{o=(l|1|0)==5?948:u;break}}while(!1);return o|0}function Aa(o){o=o|0;var l=0;return l=KP(1e3)|0,ja(o,(l|0)!=0,2456),n[2276]=(n[2276]|0)+1,Qr(l|0,8104,1e3)|0,s[o+2>>0]|0&&(n[l+4>>2]=2,n[l+12>>2]=4),n[l+976>>2]=o,l|0}function ja(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0;d=I,I=I+16|0,A=d,l||(n[A>>2]=u,Gg(o,5,3197,A)),I=d}function is(){return Aa(956)|0}function uc(o){o=o|0;var l=0;return l=Jt(1e3)|0,gu(l,o),ja(n[o+976>>2]|0,1,2456),n[2276]=(n[2276]|0)+1,n[l+944>>2]=0,l|0}function gu(o,l){o=o|0,l=l|0;var u=0;Qr(o|0,l|0,948)|0,vy(o+948|0,l+948|0),u=o+960|0,o=l+960|0,l=u+40|0;do n[u>>2]=n[o>>2],u=u+4|0,o=o+4|0;while((u|0)<(l|0))}function fc(o){o=o|0;var l=0,u=0,A=0,d=0;if(l=o+944|0,u=n[l>>2]|0,u|0&&(qa(u+948|0,o)|0,n[l>>2]=0),u=_i(o)|0,u|0){l=0;do n[(ws(o,l)|0)+944>>2]=0,l=l+1|0;while((l|0)!=(u|0))}u=o+948|0,A=n[u>>2]|0,d=o+952|0,l=n[d>>2]|0,(l|0)!=(A|0)&&(n[d>>2]=l+(~((l+-4-A|0)>>>2)<<2)),Sl(u),JP(o),n[2276]=(n[2276]|0)+-1}function qa(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0;A=n[o>>2]|0,k=o+4|0,u=n[k>>2]|0,m=u;e:do if((A|0)==(u|0))d=A,B=4;else for(o=A;;){if((n[o>>2]|0)==(l|0)){d=o,B=4;break e}if(o=o+4|0,(o|0)==(u|0)){o=0;break}}while(!1);return(B|0)==4&&((d|0)!=(u|0)?(A=d+4|0,o=m-A|0,l=o>>2,l&&(Q2(d|0,A|0,o|0)|0,u=n[k>>2]|0),o=d+(l<<2)|0,(u|0)==(o|0)||(n[k>>2]=u+(~((u+-4-o|0)>>>2)<<2)),o=1):o=0),o|0}function _i(o){return o=o|0,(n[o+952>>2]|0)-(n[o+948>>2]|0)>>2|0}function ws(o,l){o=o|0,l=l|0;var u=0;return u=n[o+948>>2]|0,(n[o+952>>2]|0)-u>>2>>>0>l>>>0?o=n[u+(l<<2)>>2]|0:o=0,o|0}function Sl(o){o=o|0;var l=0,u=0,A=0,d=0;A=I,I=I+32|0,l=A,d=n[o>>2]|0,u=(n[o+4>>2]|0)-d|0,((n[o+8>>2]|0)-d|0)>>>0>u>>>0&&(d=u>>2,Py(l,d,d,o+8|0),IP(o,l),xy(l)),I=A}function df(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0;_=_i(o)|0;do if(_|0){if((n[(ws(o,0)|0)+944>>2]|0)==(o|0)){if(!(qa(o+948|0,l)|0))break;Qr(l+400|0,8504,540)|0,n[l+944>>2]=0,Oe(o);break}B=n[(n[o+976>>2]|0)+12>>2]|0,k=o+948|0,T=(B|0)==0,u=0,m=0;do A=n[(n[k>>2]|0)+(m<<2)>>2]|0,(A|0)==(l|0)?Oe(o):(d=uc(A)|0,n[(n[k>>2]|0)+(u<<2)>>2]=d,n[d+944>>2]=o,T||y_[B&15](A,d,o,u),u=u+1|0),m=m+1|0;while((m|0)!=(_|0));if(u>>>0<_>>>0){T=o+948|0,k=o+952|0,B=u,u=n[k>>2]|0;do m=(n[T>>2]|0)+(B<<2)|0,A=m+4|0,d=u-A|0,l=d>>2,l&&(Q2(m|0,A|0,d|0)|0,u=n[k>>2]|0),d=u,A=m+(l<<2)|0,(d|0)!=(A|0)&&(u=d+(~((d+-4-A|0)>>>2)<<2)|0,n[k>>2]=u),B=B+1|0;while((B|0)!=(_|0))}}while(!1)}function Ac(o){o=o|0;var l=0,u=0,A=0,d=0;Bi(o,(_i(o)|0)==0,2491),Bi(o,(n[o+944>>2]|0)==0,2545),l=o+948|0,u=n[l>>2]|0,A=o+952|0,d=n[A>>2]|0,(d|0)!=(u|0)&&(n[A>>2]=d+(~((d+-4-u|0)>>>2)<<2)),Sl(l),l=o+976|0,u=n[l>>2]|0,Qr(o|0,8104,1e3)|0,s[u+2>>0]|0&&(n[o+4>>2]=2,n[o+12>>2]=4),n[l>>2]=u}function Bi(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0;d=I,I=I+16|0,A=d,l||(n[A>>2]=u,Qo(o,5,3197,A)),I=d}function Qn(){return n[2276]|0}function pc(){var o=0;return o=KP(20)|0,Je((o|0)!=0,2592),n[2277]=(n[2277]|0)+1,n[o>>2]=n[239],n[o+4>>2]=n[240],n[o+8>>2]=n[241],n[o+12>>2]=n[242],n[o+16>>2]=n[243],o|0}function Je(o,l){o=o|0,l=l|0;var u=0,A=0;A=I,I=I+16|0,u=A,o||(n[u>>2]=l,Qo(0,5,3197,u)),I=A}function st(o){o=o|0,JP(o),n[2277]=(n[2277]|0)+-1}function St(o,l){o=o|0,l=l|0;var u=0;l?(Bi(o,(_i(o)|0)==0,2629),u=1):(u=0,l=0),n[o+964>>2]=l,n[o+988>>2]=u}function lr(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;A=I,I=I+16|0,m=A+8|0,d=A+4|0,B=A,n[d>>2]=l,Bi(o,(n[l+944>>2]|0)==0,2709),Bi(o,(n[o+964>>2]|0)==0,2763),ee(o),l=o+948|0,n[B>>2]=(n[l>>2]|0)+(u<<2),n[m>>2]=n[B>>2],Ee(l,m,d)|0,n[(n[d>>2]|0)+944>>2]=o,Oe(o),I=A}function ee(o){o=o|0;var l=0,u=0,A=0,d=0,m=0,B=0,k=0;if(u=_i(o)|0,u|0&&(n[(ws(o,0)|0)+944>>2]|0)!=(o|0)){A=n[(n[o+976>>2]|0)+12>>2]|0,d=o+948|0,m=(A|0)==0,l=0;do B=n[(n[d>>2]|0)+(l<<2)>>2]|0,k=uc(B)|0,n[(n[d>>2]|0)+(l<<2)>>2]=k,n[k+944>>2]=o,m||y_[A&15](B,k,o,l),l=l+1|0;while((l|0)!=(u|0))}}function Ee(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0,We=0,Le=0,Qe=0,tt=0,Ze=0;tt=I,I=I+64|0,G=tt+52|0,k=tt+48|0,ae=tt+28|0,We=tt+24|0,Le=tt+20|0,Qe=tt,A=n[o>>2]|0,m=A,l=A+((n[l>>2]|0)-m>>2<<2)|0,A=o+4|0,d=n[A>>2]|0,B=o+8|0;do if(d>>>0<(n[B>>2]|0)>>>0){if((l|0)==(d|0)){n[l>>2]=n[u>>2],n[A>>2]=(n[A>>2]|0)+4;break}CP(o,l,d,l+4|0),l>>>0<=u>>>0&&(u=(n[A>>2]|0)>>>0>u>>>0?u+4|0:u),n[l>>2]=n[u>>2]}else{A=(d-m>>2)+1|0,d=O(o)|0,d>>>0<A>>>0&&sn(o),M=n[o>>2]|0,_=(n[B>>2]|0)-M|0,m=_>>1,Py(Qe,_>>2>>>0<d>>>1>>>0?m>>>0<A>>>0?A:m:d,l-M>>2,o+8|0),M=Qe+8|0,A=n[M>>2]|0,m=Qe+12|0,_=n[m>>2]|0,B=_,T=A;do if((A|0)==(_|0)){if(_=Qe+4|0,A=n[_>>2]|0,Ze=n[Qe>>2]|0,d=Ze,A>>>0<=Ze>>>0){A=B-d>>1,A=A|0?A:1,Py(ae,A,A>>>2,n[Qe+16>>2]|0),n[We>>2]=n[_>>2],n[Le>>2]=n[M>>2],n[k>>2]=n[We>>2],n[G>>2]=n[Le>>2],o2(ae,k,G),A=n[Qe>>2]|0,n[Qe>>2]=n[ae>>2],n[ae>>2]=A,A=ae+4|0,Ze=n[_>>2]|0,n[_>>2]=n[A>>2],n[A>>2]=Ze,A=ae+8|0,Ze=n[M>>2]|0,n[M>>2]=n[A>>2],n[A>>2]=Ze,A=ae+12|0,Ze=n[m>>2]|0,n[m>>2]=n[A>>2],n[A>>2]=Ze,xy(ae),A=n[M>>2]|0;break}m=A,B=((m-d>>2)+1|0)/-2|0,k=A+(B<<2)|0,d=T-m|0,m=d>>2,m&&(Q2(k|0,A|0,d|0)|0,A=n[_>>2]|0),Ze=k+(m<<2)|0,n[M>>2]=Ze,n[_>>2]=A+(B<<2),A=Ze}while(!1);n[A>>2]=n[u>>2],n[M>>2]=(n[M>>2]|0)+4,l=wP(o,Qe,l)|0,xy(Qe)}while(!1);return I=tt,l|0}function Oe(o){o=o|0;var l=0;do{if(l=o+984|0,s[l>>0]|0)break;s[l>>0]=1,h[o+504>>2]=y(ce),o=n[o+944>>2]|0}while(o|0)}function gt(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-4-A|0)>>>2)<<2)),Et(u))}function yt(o){return o=o|0,n[o+944>>2]|0}function Dt(o){o=o|0,Bi(o,(n[o+964>>2]|0)!=0,2832),Oe(o)}function tr(o){return o=o|0,(s[o+984>>0]|0)!=0|0}function fn(o,l){o=o|0,l=l|0,EYe(o,l,400)|0&&(Qr(o|0,l|0,400)|0,Oe(o))}function li(o){o=o|0;var l=Xe;return l=y(h[o+44>>2]),o=Mt(l)|0,y(o?y(0):l)}function Gi(o){o=o|0;var l=Xe;return l=y(h[o+48>>2]),Mt(l)|0&&(l=s[(n[o+976>>2]|0)+2>>0]|0?y(1):y(0)),y(l)}function Tn(o,l){o=o|0,l=l|0,n[o+980>>2]=l}function Ga(o){return o=o|0,n[o+980>>2]|0}function gy(o,l){o=o|0,l=l|0;var u=0;u=o+4|0,(n[u>>2]|0)!=(l|0)&&(n[u>>2]=l,Oe(o))}function X1(o){return o=o|0,n[o+4>>2]|0}function Do(o,l){o=o|0,l=l|0;var u=0;u=o+8|0,(n[u>>2]|0)!=(l|0)&&(n[u>>2]=l,Oe(o))}function dy(o){return o=o|0,n[o+8>>2]|0}function Ch(o,l){o=o|0,l=l|0;var u=0;u=o+12|0,(n[u>>2]|0)!=(l|0)&&(n[u>>2]=l,Oe(o))}function $1(o){return o=o|0,n[o+12>>2]|0}function bo(o,l){o=o|0,l=l|0;var u=0;u=o+16|0,(n[u>>2]|0)!=(l|0)&&(n[u>>2]=l,Oe(o))}function wh(o){return o=o|0,n[o+16>>2]|0}function Bh(o,l){o=o|0,l=l|0;var u=0;u=o+20|0,(n[u>>2]|0)!=(l|0)&&(n[u>>2]=l,Oe(o))}function du(o){return o=o|0,n[o+20>>2]|0}function vh(o,l){o=o|0,l=l|0;var u=0;u=o+24|0,(n[u>>2]|0)!=(l|0)&&(n[u>>2]=l,Oe(o))}function Rg(o){return o=o|0,n[o+24>>2]|0}function Fg(o,l){o=o|0,l=l|0;var u=0;u=o+28|0,(n[u>>2]|0)!=(l|0)&&(n[u>>2]=l,Oe(o))}function Ng(o){return o=o|0,n[o+28>>2]|0}function my(o,l){o=o|0,l=l|0;var u=0;u=o+32|0,(n[u>>2]|0)!=(l|0)&&(n[u>>2]=l,Oe(o))}function mf(o){return o=o|0,n[o+32>>2]|0}function Po(o,l){o=o|0,l=l|0;var u=0;u=o+36|0,(n[u>>2]|0)!=(l|0)&&(n[u>>2]=l,Oe(o))}function Dl(o){return o=o|0,n[o+36>>2]|0}function Sh(o,l){o=o|0,l=y(l);var u=0;u=o+40|0,y(h[u>>2])!=l&&(h[u>>2]=l,Oe(o))}function Og(o,l){o=o|0,l=y(l);var u=0;u=o+44|0,y(h[u>>2])!=l&&(h[u>>2]=l,Oe(o))}function bl(o,l){o=o|0,l=y(l);var u=0;u=o+48|0,y(h[u>>2])!=l&&(h[u>>2]=l,Oe(o))}function Pl(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=(m^1)&1,A=o+52|0,d=o+56|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function yy(o,l){o=o|0,l=y(l);var u=0,A=0;A=o+52|0,u=o+56|0,y(h[A>>2])==l&&(n[u>>2]|0)==2||(h[A>>2]=l,A=Mt(l)|0,n[u>>2]=A?3:2,Oe(o))}function HA(o,l){o=o|0,l=l|0;var u=0,A=0;A=l+52|0,u=n[A+4>>2]|0,l=o,n[l>>2]=n[A>>2],n[l+4>>2]=u}function Ey(o,l,u){o=o|0,l=l|0,u=y(u);var A=0,d=0,m=0;m=Mt(u)|0,A=(m^1)&1,d=o+132+(l<<3)|0,l=o+132+(l<<3)+4|0,m|y(h[d>>2])==u&&(n[l>>2]|0)==(A|0)||(h[d>>2]=u,n[l>>2]=A,Oe(o))}function Iy(o,l,u){o=o|0,l=l|0,u=y(u);var A=0,d=0,m=0;m=Mt(u)|0,A=m?0:2,d=o+132+(l<<3)|0,l=o+132+(l<<3)+4|0,m|y(h[d>>2])==u&&(n[l>>2]|0)==(A|0)||(h[d>>2]=u,n[l>>2]=A,Oe(o))}function jA(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=l+132+(u<<3)|0,l=n[A+4>>2]|0,u=o,n[u>>2]=n[A>>2],n[u+4>>2]=l}function qA(o,l,u){o=o|0,l=l|0,u=y(u);var A=0,d=0,m=0;m=Mt(u)|0,A=(m^1)&1,d=o+60+(l<<3)|0,l=o+60+(l<<3)+4|0,m|y(h[d>>2])==u&&(n[l>>2]|0)==(A|0)||(h[d>>2]=u,n[l>>2]=A,Oe(o))}function Y(o,l,u){o=o|0,l=l|0,u=y(u);var A=0,d=0,m=0;m=Mt(u)|0,A=m?0:2,d=o+60+(l<<3)|0,l=o+60+(l<<3)+4|0,m|y(h[d>>2])==u&&(n[l>>2]|0)==(A|0)||(h[d>>2]=u,n[l>>2]=A,Oe(o))}function xt(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=l+60+(u<<3)|0,l=n[A+4>>2]|0,u=o,n[u>>2]=n[A>>2],n[u+4>>2]=l}function GA(o,l){o=o|0,l=l|0;var u=0;u=o+60+(l<<3)+4|0,(n[u>>2]|0)!=3&&(h[o+60+(l<<3)>>2]=y(ce),n[u>>2]=3,Oe(o))}function xo(o,l,u){o=o|0,l=l|0,u=y(u);var A=0,d=0,m=0;m=Mt(u)|0,A=(m^1)&1,d=o+204+(l<<3)|0,l=o+204+(l<<3)+4|0,m|y(h[d>>2])==u&&(n[l>>2]|0)==(A|0)||(h[d>>2]=u,n[l>>2]=A,Oe(o))}function yf(o,l,u){o=o|0,l=l|0,u=y(u);var A=0,d=0,m=0;m=Mt(u)|0,A=m?0:2,d=o+204+(l<<3)|0,l=o+204+(l<<3)+4|0,m|y(h[d>>2])==u&&(n[l>>2]|0)==(A|0)||(h[d>>2]=u,n[l>>2]=A,Oe(o))}function mt(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=l+204+(u<<3)|0,l=n[A+4>>2]|0,u=o,n[u>>2]=n[A>>2],n[u+4>>2]=l}function mu(o,l,u){o=o|0,l=l|0,u=y(u);var A=0,d=0,m=0;m=Mt(u)|0,A=(m^1)&1,d=o+276+(l<<3)|0,l=o+276+(l<<3)+4|0,m|y(h[d>>2])==u&&(n[l>>2]|0)==(A|0)||(h[d>>2]=u,n[l>>2]=A,Oe(o))}function Cy(o,l){return o=o|0,l=l|0,y(h[o+276+(l<<3)>>2])}function Lg(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=(m^1)&1,A=o+348|0,d=o+352|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function e2(o,l){o=o|0,l=y(l);var u=0,A=0;A=o+348|0,u=o+352|0,y(h[A>>2])==l&&(n[u>>2]|0)==2||(h[A>>2]=l,A=Mt(l)|0,n[u>>2]=A?3:2,Oe(o))}function Dh(o){o=o|0;var l=0;l=o+352|0,(n[l>>2]|0)!=3&&(h[o+348>>2]=y(ce),n[l>>2]=3,Oe(o))}function ur(o,l){o=o|0,l=l|0;var u=0,A=0;A=l+348|0,u=n[A+4>>2]|0,l=o,n[l>>2]=n[A>>2],n[l+4>>2]=u}function Zi(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=(m^1)&1,A=o+356|0,d=o+360|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function Ef(o,l){o=o|0,l=y(l);var u=0,A=0;A=o+356|0,u=o+360|0,y(h[A>>2])==l&&(n[u>>2]|0)==2||(h[A>>2]=l,A=Mt(l)|0,n[u>>2]=A?3:2,Oe(o))}function Wa(o){o=o|0;var l=0;l=o+360|0,(n[l>>2]|0)!=3&&(h[o+356>>2]=y(ce),n[l>>2]=3,Oe(o))}function Mg(o,l){o=o|0,l=l|0;var u=0,A=0;A=l+356|0,u=n[A+4>>2]|0,l=o,n[l>>2]=n[A>>2],n[l+4>>2]=u}function yu(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=(m^1)&1,A=o+364|0,d=o+368|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function If(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=m?0:2,A=o+364|0,d=o+368|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function wt(o,l){o=o|0,l=l|0;var u=0,A=0;A=l+364|0,u=n[A+4>>2]|0,l=o,n[l>>2]=n[A>>2],n[l+4>>2]=u}function di(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=(m^1)&1,A=o+372|0,d=o+376|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function WA(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=m?0:2,A=o+372|0,d=o+376|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function Ya(o,l){o=o|0,l=l|0;var u=0,A=0;A=l+372|0,u=n[A+4>>2]|0,l=o,n[l>>2]=n[A>>2],n[l+4>>2]=u}function pa(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=(m^1)&1,A=o+380|0,d=o+384|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function Va(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=m?0:2,A=o+380|0,d=o+384|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function _g(o,l){o=o|0,l=l|0;var u=0,A=0;A=l+380|0,u=n[A+4>>2]|0,l=o,n[l>>2]=n[A>>2],n[l+4>>2]=u}function bh(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=(m^1)&1,A=o+388|0,d=o+392|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function Ug(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=m?0:2,A=o+388|0,d=o+392|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function wy(o,l){o=o|0,l=l|0;var u=0,A=0;A=l+388|0,u=n[A+4>>2]|0,l=o,n[l>>2]=n[A>>2],n[l+4>>2]=u}function YA(o,l){o=o|0,l=y(l);var u=0;u=o+396|0,y(h[u>>2])!=l&&(h[u>>2]=l,Oe(o))}function Hg(o){return o=o|0,y(h[o+396>>2])}function Eu(o){return o=o|0,y(h[o+400>>2])}function Iu(o){return o=o|0,y(h[o+404>>2])}function Cf(o){return o=o|0,y(h[o+408>>2])}function Ns(o){return o=o|0,y(h[o+412>>2])}function Cu(o){return o=o|0,y(h[o+416>>2])}function qn(o){return o=o|0,y(h[o+420>>2])}function ss(o,l){switch(o=o|0,l=l|0,Bi(o,(l|0)<6,2918),l|0){case 0:{l=(n[o+496>>2]|0)==2?5:4;break}case 2:{l=(n[o+496>>2]|0)==2?4:5;break}default:}return y(h[o+424+(l<<2)>>2])}function ki(o,l){switch(o=o|0,l=l|0,Bi(o,(l|0)<6,2918),l|0){case 0:{l=(n[o+496>>2]|0)==2?5:4;break}case 2:{l=(n[o+496>>2]|0)==2?4:5;break}default:}return y(h[o+448+(l<<2)>>2])}function VA(o,l){switch(o=o|0,l=l|0,Bi(o,(l|0)<6,2918),l|0){case 0:{l=(n[o+496>>2]|0)==2?5:4;break}case 2:{l=(n[o+496>>2]|0)==2?4:5;break}default:}return y(h[o+472+(l<<2)>>2])}function wf(o,l){o=o|0,l=l|0;var u=0,A=Xe;return u=n[o+4>>2]|0,(u|0)==(n[l+4>>2]|0)?u?(A=y(h[o>>2]),o=y(se(y(A-y(h[l>>2]))))<y(999999974e-13)):o=1:o=0,o|0}function mn(o,l){o=y(o),l=y(l);var u=0;return Mt(o)|0?u=Mt(l)|0:u=y(se(y(o-l)))<y(999999974e-13),u|0}function jg(o,l){o=o|0,l=l|0,qg(o,l)}function qg(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u+4|0,n[A>>2]=0,n[A+4>>2]=0,n[A+8>>2]=0,fu(A|0,o|0,l|0,0),Qo(o,3,(s[A+11>>0]|0)<0?n[A>>2]|0:A,u),jYe(A),I=u}function os(o,l,u,A){o=y(o),l=y(l),u=u|0,A=A|0;var d=Xe;o=y(o*l),d=y(A_(o,y(1)));do if(mn(d,y(0))|0)o=y(o-d);else{if(o=y(o-d),mn(d,y(1))|0){o=y(o+y(1));break}if(u){o=y(o+y(1));break}A||(d>y(.5)?d=y(1):(A=mn(d,y(.5))|0,d=y(A?1:0)),o=y(o+d))}while(!1);return y(o/l)}function xl(o,l,u,A,d,m,B,k,T,_,M,G,ae){o=o|0,l=y(l),u=u|0,A=y(A),d=d|0,m=y(m),B=B|0,k=y(k),T=y(T),_=y(_),M=y(M),G=y(G),ae=ae|0;var We=0,Le=Xe,Qe=Xe,tt=Xe,Ze=Xe,ct=Xe,He=Xe;return T<y(0)|_<y(0)?ae=0:(ae|0&&(Le=y(h[ae+4>>2]),Le!=y(0))?(tt=y(os(l,Le,0,0)),Ze=y(os(A,Le,0,0)),Qe=y(os(m,Le,0,0)),Le=y(os(k,Le,0,0))):(Qe=m,tt=l,Le=k,Ze=A),(d|0)==(o|0)?We=mn(Qe,tt)|0:We=0,(B|0)==(u|0)?ae=mn(Le,Ze)|0:ae=0,!We&&(ct=y(l-M),!(ko(o,ct,T)|0))&&!(Bf(o,ct,d,T)|0)?We=vf(o,ct,d,m,T)|0:We=1,!ae&&(He=y(A-G),!(ko(u,He,_)|0))&&!(Bf(u,He,B,_)|0)?ae=vf(u,He,B,k,_)|0:ae=1,ae=We&ae),ae|0}function ko(o,l,u){return o=o|0,l=y(l),u=y(u),(o|0)==1?o=mn(l,u)|0:o=0,o|0}function Bf(o,l,u,A){return o=o|0,l=y(l),u=u|0,A=y(A),(o|0)==2&(u|0)==0?l>=A?o=1:o=mn(l,A)|0:o=0,o|0}function vf(o,l,u,A,d){return o=o|0,l=y(l),u=u|0,A=y(A),d=y(d),(o|0)==2&(u|0)==2&A>l?d<=l?o=1:o=mn(l,d)|0:o=0,o|0}function kl(o,l,u,A,d,m,B,k,T,_,M){o=o|0,l=y(l),u=y(u),A=A|0,d=d|0,m=m|0,B=y(B),k=y(k),T=T|0,_=_|0,M=M|0;var G=0,ae=0,We=0,Le=0,Qe=Xe,tt=Xe,Ze=0,ct=0,He=0,Ge=0,Lt=0,qr=0,fr=0,$t=0,Tr=0,Hr=0,cr=0,Hn=Xe,Fo=Xe,No=Xe,Oo=0,$a=0;cr=I,I=I+160|0,$t=cr+152|0,fr=cr+120|0,qr=cr+104|0,He=cr+72|0,Le=cr+56|0,Lt=cr+8|0,ct=cr,Ge=(n[2279]|0)+1|0,n[2279]=Ge,Tr=o+984|0,s[Tr>>0]|0&&(n[o+512>>2]|0)!=(n[2278]|0)?Ze=4:(n[o+516>>2]|0)==(A|0)?Hr=0:Ze=4,(Ze|0)==4&&(n[o+520>>2]=0,n[o+924>>2]=-1,n[o+928>>2]=-1,h[o+932>>2]=y(-1),h[o+936>>2]=y(-1),Hr=1);e:do if(n[o+964>>2]|0)if(Qe=y(yn(o,2,B)),tt=y(yn(o,0,B)),G=o+916|0,No=y(h[G>>2]),Fo=y(h[o+920>>2]),Hn=y(h[o+932>>2]),xl(d,l,m,u,n[o+924>>2]|0,No,n[o+928>>2]|0,Fo,Hn,y(h[o+936>>2]),Qe,tt,M)|0)Ze=22;else if(We=n[o+520>>2]|0,!We)Ze=21;else for(ae=0;;){if(G=o+524+(ae*24|0)|0,Hn=y(h[G>>2]),Fo=y(h[o+524+(ae*24|0)+4>>2]),No=y(h[o+524+(ae*24|0)+16>>2]),xl(d,l,m,u,n[o+524+(ae*24|0)+8>>2]|0,Hn,n[o+524+(ae*24|0)+12>>2]|0,Fo,No,y(h[o+524+(ae*24|0)+20>>2]),Qe,tt,M)|0){Ze=22;break e}if(ae=ae+1|0,ae>>>0>=We>>>0){Ze=21;break}}else{if(T){if(G=o+916|0,!(mn(y(h[G>>2]),l)|0)){Ze=21;break}if(!(mn(y(h[o+920>>2]),u)|0)){Ze=21;break}if((n[o+924>>2]|0)!=(d|0)){Ze=21;break}G=(n[o+928>>2]|0)==(m|0)?G:0,Ze=22;break}if(We=n[o+520>>2]|0,!We)Ze=21;else for(ae=0;;){if(G=o+524+(ae*24|0)|0,mn(y(h[G>>2]),l)|0&&mn(y(h[o+524+(ae*24|0)+4>>2]),u)|0&&(n[o+524+(ae*24|0)+8>>2]|0)==(d|0)&&(n[o+524+(ae*24|0)+12>>2]|0)==(m|0)){Ze=22;break e}if(ae=ae+1|0,ae>>>0>=We>>>0){Ze=21;break}}}while(!1);do if((Ze|0)==21)s[11697]|0?(G=0,Ze=28):(G=0,Ze=31);else if((Ze|0)==22){if(ae=(s[11697]|0)!=0,!((G|0)!=0&(Hr^1)))if(ae){Ze=28;break}else{Ze=31;break}Le=G+16|0,n[o+908>>2]=n[Le>>2],We=G+20|0,n[o+912>>2]=n[We>>2],(s[11698]|0)==0|ae^1||(n[ct>>2]=wu(Ge)|0,n[ct+4>>2]=Ge,Qo(o,4,2972,ct),ae=n[o+972>>2]|0,ae|0&&op[ae&127](o),d=ha(d,T)|0,m=ha(m,T)|0,$a=+y(h[Le>>2]),Oo=+y(h[We>>2]),n[Lt>>2]=d,n[Lt+4>>2]=m,E[Lt+8>>3]=+l,E[Lt+16>>3]=+u,E[Lt+24>>3]=$a,E[Lt+32>>3]=Oo,n[Lt+40>>2]=_,Qo(o,4,2989,Lt))}while(!1);return(Ze|0)==28&&(ae=wu(Ge)|0,n[Le>>2]=ae,n[Le+4>>2]=Ge,n[Le+8>>2]=Hr?3047:11699,Qo(o,4,3038,Le),ae=n[o+972>>2]|0,ae|0&&op[ae&127](o),Lt=ha(d,T)|0,Ze=ha(m,T)|0,n[He>>2]=Lt,n[He+4>>2]=Ze,E[He+8>>3]=+l,E[He+16>>3]=+u,n[He+24>>2]=_,Qo(o,4,3049,He),Ze=31),(Ze|0)==31&&(Os(o,l,u,A,d,m,B,k,T,M),s[11697]|0&&(ae=n[2279]|0,Lt=wu(ae)|0,n[qr>>2]=Lt,n[qr+4>>2]=ae,n[qr+8>>2]=Hr?3047:11699,Qo(o,4,3083,qr),ae=n[o+972>>2]|0,ae|0&&op[ae&127](o),Lt=ha(d,T)|0,qr=ha(m,T)|0,Oo=+y(h[o+908>>2]),$a=+y(h[o+912>>2]),n[fr>>2]=Lt,n[fr+4>>2]=qr,E[fr+8>>3]=Oo,E[fr+16>>3]=$a,n[fr+24>>2]=_,Qo(o,4,3092,fr)),n[o+516>>2]=A,G||(ae=o+520|0,G=n[ae>>2]|0,(G|0)==16&&(s[11697]|0&&Qo(o,4,3124,$t),n[ae>>2]=0,G=0),T?G=o+916|0:(n[ae>>2]=G+1,G=o+524+(G*24|0)|0),h[G>>2]=l,h[G+4>>2]=u,n[G+8>>2]=d,n[G+12>>2]=m,n[G+16>>2]=n[o+908>>2],n[G+20>>2]=n[o+912>>2],G=0)),T&&(n[o+416>>2]=n[o+908>>2],n[o+420>>2]=n[o+912>>2],s[o+985>>0]=1,s[Tr>>0]=0),n[2279]=(n[2279]|0)+-1,n[o+512>>2]=n[2278],I=cr,Hr|(G|0)==0|0}function yn(o,l,u){o=o|0,l=l|0,u=y(u);var A=Xe;return A=y(J(o,l,u)),y(A+y(re(o,l,u)))}function Qo(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=I,I=I+16|0,d=m,n[d>>2]=A,o?A=n[o+976>>2]|0:A=0,kh(A,o,l,u,d),I=m}function wu(o){return o=o|0,(o>>>0>60?3201:3201+(60-o)|0)|0}function ha(o,l){o=o|0,l=l|0;var u=0,A=0,d=0;return d=I,I=I+32|0,u=d+12|0,A=d,n[u>>2]=n[254],n[u+4>>2]=n[255],n[u+8>>2]=n[256],n[A>>2]=n[257],n[A+4>>2]=n[258],n[A+8>>2]=n[259],(o|0)>2?o=11699:o=n[(l?A:u)+(o<<2)>>2]|0,I=d,o|0}function Os(o,l,u,A,d,m,B,k,T,_){o=o|0,l=y(l),u=y(u),A=A|0,d=d|0,m=m|0,B=y(B),k=y(k),T=T|0,_=_|0;var M=0,G=0,ae=0,We=0,Le=Xe,Qe=Xe,tt=Xe,Ze=Xe,ct=Xe,He=Xe,Ge=Xe,Lt=0,qr=0,fr=0,$t=Xe,Tr=Xe,Hr=0,cr=Xe,Hn=0,Fo=0,No=0,Oo=0,$a=0,Vh=0,Kh=0,dc=0,Jh=0,Ff=0,Nf=0,zh=0,Zh=0,Xh=0,on=0,mc=0,$h=0,ku=0,e0=Xe,t0=Xe,Of=Xe,Lf=Xe,Qu=Xe,lo=0,Ml=0,ya=0,yc=0,lp=0,cp=Xe,Mf=Xe,up=Xe,fp=Xe,co=Xe,Us=Xe,Ec=0,Wn=Xe,Ap=Xe,Lo=Xe,Tu=Xe,Mo=Xe,Ru=Xe,pp=0,hp=0,Fu=Xe,uo=Xe,Ic=0,gp=0,dp=0,mp=0,Fr=Xe,ui=0,Hs=0,_o=0,fo=0,Mr=0,Ar=0,Cc=0,zt=Xe,yp=0,vi=0;Cc=I,I=I+16|0,lo=Cc+12|0,Ml=Cc+8|0,ya=Cc+4|0,yc=Cc,Bi(o,(d|0)==0|(Mt(l)|0)^1,3326),Bi(o,(m|0)==0|(Mt(u)|0)^1,3406),Hs=ft(o,A)|0,n[o+496>>2]=Hs,Mr=dr(2,Hs)|0,Ar=dr(0,Hs)|0,h[o+440>>2]=y(J(o,Mr,B)),h[o+444>>2]=y(re(o,Mr,B)),h[o+428>>2]=y(J(o,Ar,B)),h[o+436>>2]=y(re(o,Ar,B)),h[o+464>>2]=y(Br(o,Mr)),h[o+468>>2]=y(_n(o,Mr)),h[o+452>>2]=y(Br(o,Ar)),h[o+460>>2]=y(_n(o,Ar)),h[o+488>>2]=y(mi(o,Mr,B)),h[o+492>>2]=y(Bs(o,Mr,B)),h[o+476>>2]=y(mi(o,Ar,B)),h[o+484>>2]=y(Bs(o,Ar,B));do if(n[o+964>>2]|0)zA(o,l,u,d,m,B,k);else{if(_o=o+948|0,fo=(n[o+952>>2]|0)-(n[_o>>2]|0)>>2,!fo){dP(o,l,u,d,m,B,k);break}if(!T&&t2(o,l,u,d,m,B,k)|0)break;ee(o),mc=o+508|0,s[mc>>0]=0,Mr=dr(n[o+4>>2]|0,Hs)|0,Ar=Sy(Mr,Hs)|0,ui=de(Mr)|0,$h=n[o+8>>2]|0,gp=o+28|0,ku=(n[gp>>2]|0)!=0,Mo=ui?B:k,Fu=ui?k:B,e0=y(Th(o,Mr,B)),t0=y(r2(o,Mr,B)),Le=y(Th(o,Ar,B)),Ru=y(Ka(o,Mr,B)),uo=y(Ka(o,Ar,B)),fr=ui?d:m,Ic=ui?m:d,Fr=ui?Ru:uo,ct=ui?uo:Ru,Tu=y(yn(o,2,B)),Ze=y(yn(o,0,B)),Qe=y(y(Xr(o+364|0,B))-Fr),tt=y(y(Xr(o+380|0,B))-Fr),He=y(y(Xr(o+372|0,k))-ct),Ge=y(y(Xr(o+388|0,k))-ct),Of=ui?Qe:He,Lf=ui?tt:Ge,Tu=y(l-Tu),l=y(Tu-Fr),Mt(l)|0?Fr=l:Fr=y($n(y(Ad(l,tt)),Qe)),Ap=y(u-Ze),l=y(Ap-ct),Mt(l)|0?Lo=l:Lo=y($n(y(Ad(l,Ge)),He)),Qe=ui?Fr:Lo,Wn=ui?Lo:Fr;e:do if((fr|0)==1)for(A=0,G=0;;){if(M=ws(o,G)|0,!A)y(ZA(M))>y(0)&&y(Rh(M))>y(0)?A=M:A=0;else if(n2(M)|0){We=0;break e}if(G=G+1|0,G>>>0>=fo>>>0){We=A;break}}else We=0;while(!1);Lt=We+500|0,qr=We+504|0,A=0,M=0,l=y(0),ae=0;do{if(G=n[(n[_o>>2]|0)+(ae<<2)>>2]|0,(n[G+36>>2]|0)==1)Dy(G),s[G+985>>0]=1,s[G+984>>0]=0;else{Sf(G),T&&xh(G,ft(G,Hs)|0,Qe,Wn,Fr);do if((n[G+24>>2]|0)!=1)if((G|0)==(We|0)){n[Lt>>2]=n[2278],h[qr>>2]=y(0);break}else{mP(o,G,Fr,d,Lo,Fr,Lo,m,Hs,_);break}else M|0&&(n[M+960>>2]=G),n[G+960>>2]=0,M=G,A=A|0?A:G;while(!1);Us=y(h[G+504>>2]),l=y(l+y(Us+y(yn(G,Mr,Fr))))}ae=ae+1|0}while((ae|0)!=(fo|0));for(No=l>Qe,Ec=ku&((fr|0)==2&No)?1:fr,Hn=(Ic|0)==1,$a=Hn&(T^1),Vh=(Ec|0)==1,Kh=(Ec|0)==2,dc=976+(Mr<<2)|0,Jh=(Ic|2|0)==2,Xh=Hn&(ku^1),Ff=1040+(Ar<<2)|0,Nf=1040+(Mr<<2)|0,zh=976+(Ar<<2)|0,Zh=(Ic|0)!=1,No=ku&((fr|0)!=0&No),Fo=o+976|0,Hn=Hn^1,l=Qe,Hr=0,Oo=0,Us=y(0),Qu=y(0);;){e:do if(Hr>>>0<fo>>>0)for(qr=n[_o>>2]|0,ae=0,Ge=y(0),He=y(0),tt=y(0),Qe=y(0),G=0,M=0,We=Hr;;){if(Lt=n[qr+(We<<2)>>2]|0,(n[Lt+36>>2]|0)!=1&&(n[Lt+940>>2]=Oo,(n[Lt+24>>2]|0)!=1)){if(Ze=y(yn(Lt,Mr,Fr)),on=n[dc>>2]|0,u=y(Xr(Lt+380+(on<<3)|0,Mo)),ct=y(h[Lt+504>>2]),u=y(Ad(u,ct)),u=y($n(y(Xr(Lt+364+(on<<3)|0,Mo)),u)),ku&(ae|0)!=0&y(Ze+y(He+u))>l){m=ae,Ze=Ge,fr=We;break e}Ze=y(Ze+u),u=y(He+Ze),Ze=y(Ge+Ze),n2(Lt)|0&&(tt=y(tt+y(ZA(Lt))),Qe=y(Qe-y(ct*y(Rh(Lt))))),M|0&&(n[M+960>>2]=Lt),n[Lt+960>>2]=0,ae=ae+1|0,M=Lt,G=G|0?G:Lt}else Ze=Ge,u=He;if(We=We+1|0,We>>>0<fo>>>0)Ge=Ze,He=u;else{m=ae,fr=We;break}}else m=0,Ze=y(0),tt=y(0),Qe=y(0),G=0,fr=Hr;while(!1);on=tt>y(0)&tt<y(1),$t=on?y(1):tt,on=Qe>y(0)&Qe<y(1),Ge=on?y(1):Qe;do if(Vh)on=51;else if(Ze<Of&((Mt(Of)|0)^1))l=Of,on=51;else if(Ze>Lf&((Mt(Lf)|0)^1))l=Lf,on=51;else if(s[(n[Fo>>2]|0)+3>>0]|0)on=51;else{if($t!=y(0)&&y(ZA(o))!=y(0)){on=53;break}l=Ze,on=53}while(!1);if((on|0)==51&&(on=0,Mt(l)|0?on=53:(Tr=y(l-Ze),cr=l)),(on|0)==53&&(on=0,Ze<y(0)?(Tr=y(-Ze),cr=l):(Tr=y(0),cr=l)),!$a&&(lp=(G|0)==0,!lp)){ae=n[dc>>2]|0,We=Tr<y(0),ct=y(Tr/Ge),Lt=Tr>y(0),He=y(Tr/$t),tt=y(0),Ze=y(0),l=y(0),M=G;do u=y(Xr(M+380+(ae<<3)|0,Mo)),Qe=y(Xr(M+364+(ae<<3)|0,Mo)),Qe=y(Ad(u,y($n(Qe,y(h[M+504>>2]))))),We?(u=y(Qe*y(Rh(M))),u!=y(-0)&&(zt=y(Qe-y(ct*u)),cp=y(Gn(M,Mr,zt,cr,Fr)),zt!=cp)&&(tt=y(tt-y(cp-Qe)),l=y(l+u))):Lt&&(Mf=y(ZA(M)),Mf!=y(0))&&(zt=y(Qe+y(He*Mf)),up=y(Gn(M,Mr,zt,cr,Fr)),zt!=up)&&(tt=y(tt-y(up-Qe)),Ze=y(Ze-Mf)),M=n[M+960>>2]|0;while(M|0);if(l=y(Ge+l),Qe=y(Tr+tt),lp)l=y(0);else{ct=y($t+Ze),We=n[dc>>2]|0,Lt=Qe<y(0),qr=l==y(0),He=y(Qe/l),ae=Qe>y(0),ct=y(Qe/ct),l=y(0);do{zt=y(Xr(G+380+(We<<3)|0,Mo)),tt=y(Xr(G+364+(We<<3)|0,Mo)),tt=y(Ad(zt,y($n(tt,y(h[G+504>>2]))))),Lt?(zt=y(tt*y(Rh(G))),Qe=y(-zt),zt!=y(-0)?(zt=y(He*Qe),Qe=y(Gn(G,Mr,y(tt+(qr?Qe:zt)),cr,Fr))):Qe=tt):ae&&(fp=y(ZA(G)),fp!=y(0))?Qe=y(Gn(G,Mr,y(tt+y(ct*fp)),cr,Fr)):Qe=tt,l=y(l-y(Qe-tt)),Ze=y(yn(G,Mr,Fr)),u=y(yn(G,Ar,Fr)),Qe=y(Qe+Ze),h[Ml>>2]=Qe,n[yc>>2]=1,tt=y(h[G+396>>2]);e:do if(Mt(tt)|0){M=Mt(Wn)|0;do if(!M){if(No|(oo(G,Ar,Wn)|0|Hn)||(as(o,G)|0)!=4||(n[(Ql(G,Ar)|0)+4>>2]|0)==3||(n[(Tl(G,Ar)|0)+4>>2]|0)==3)break;h[lo>>2]=Wn,n[ya>>2]=1;break e}while(!1);if(oo(G,Ar,Wn)|0){M=n[G+992+(n[zh>>2]<<2)>>2]|0,zt=y(u+y(Xr(M,Wn))),h[lo>>2]=zt,M=Zh&(n[M+4>>2]|0)==2,n[ya>>2]=((Mt(zt)|0|M)^1)&1;break}else{h[lo>>2]=Wn,n[ya>>2]=M?0:2;break}}else zt=y(Qe-Ze),$t=y(zt/tt),zt=y(tt*zt),n[ya>>2]=1,h[lo>>2]=y(u+(ui?$t:zt));while(!1);Bu(G,Mr,cr,Fr,yc,Ml),Bu(G,Ar,Wn,Fr,ya,lo);do if(!(oo(G,Ar,Wn)|0)&&(as(o,G)|0)==4){if((n[(Ql(G,Ar)|0)+4>>2]|0)==3){M=0;break}M=(n[(Tl(G,Ar)|0)+4>>2]|0)!=3}else M=0;while(!1);zt=y(h[Ml>>2]),$t=y(h[lo>>2]),yp=n[yc>>2]|0,vi=n[ya>>2]|0,kl(G,ui?zt:$t,ui?$t:zt,Hs,ui?yp:vi,ui?vi:yp,Fr,Lo,T&(M^1),3488,_)|0,s[mc>>0]=s[mc>>0]|s[G+508>>0],G=n[G+960>>2]|0}while(G|0)}}else l=y(0);if(l=y(Tr+l),vi=l<y(0)&1,s[mc>>0]=vi|c[mc>>0],Kh&l>y(0)?(M=n[dc>>2]|0,n[o+364+(M<<3)+4>>2]|0&&(co=y(Xr(o+364+(M<<3)|0,Mo)),co>=y(0))?Qe=y($n(y(0),y(co-y(cr-l)))):Qe=y(0)):Qe=l,Lt=Hr>>>0<fr>>>0,Lt){We=n[_o>>2]|0,ae=Hr,M=0;do G=n[We+(ae<<2)>>2]|0,n[G+24>>2]|0||(M=((n[(Ql(G,Mr)|0)+4>>2]|0)==3&1)+M|0,M=M+((n[(Tl(G,Mr)|0)+4>>2]|0)==3&1)|0),ae=ae+1|0;while((ae|0)!=(fr|0));M?(Ze=y(0),u=y(0)):on=101}else on=101;e:do if((on|0)==101)switch(on=0,$h|0){case 1:{M=0,Ze=y(Qe*y(.5)),u=y(0);break e}case 2:{M=0,Ze=Qe,u=y(0);break e}case 3:{if(m>>>0<=1){M=0,Ze=y(0),u=y(0);break e}u=y((m+-1|0)>>>0),M=0,Ze=y(0),u=y(y($n(Qe,y(0)))/u);break e}case 5:{u=y(Qe/y((m+1|0)>>>0)),M=0,Ze=u;break e}case 4:{u=y(Qe/y(m>>>0)),M=0,Ze=y(u*y(.5));break e}default:{M=0,Ze=y(0),u=y(0);break e}}while(!1);if(l=y(e0+Ze),Lt){tt=y(Qe/y(M|0)),ae=n[_o>>2]|0,G=Hr,Qe=y(0);do{M=n[ae+(G<<2)>>2]|0;e:do if((n[M+36>>2]|0)!=1){switch(n[M+24>>2]|0){case 1:{if(ga(M,Mr)|0){if(!T)break e;zt=y(XA(M,Mr,cr)),zt=y(zt+y(Br(o,Mr))),zt=y(zt+y(J(M,Mr,Fr))),h[M+400+(n[Nf>>2]<<2)>>2]=zt;break e}break}case 0:if(vi=(n[(Ql(M,Mr)|0)+4>>2]|0)==3,zt=y(tt+l),l=vi?zt:l,T&&(vi=M+400+(n[Nf>>2]<<2)|0,h[vi>>2]=y(l+y(h[vi>>2]))),vi=(n[(Tl(M,Mr)|0)+4>>2]|0)==3,zt=y(tt+l),l=vi?zt:l,$a){zt=y(u+y(yn(M,Mr,Fr))),Qe=Wn,l=y(l+y(zt+y(h[M+504>>2])));break e}else{l=y(l+y(u+y($A(M,Mr,Fr)))),Qe=y($n(Qe,y($A(M,Ar,Fr))));break e}default:}T&&(zt=y(Ze+y(Br(o,Mr))),vi=M+400+(n[Nf>>2]<<2)|0,h[vi>>2]=y(zt+y(h[vi>>2])))}while(!1);G=G+1|0}while((G|0)!=(fr|0))}else Qe=y(0);if(u=y(t0+l),Jh?Ze=y(y(Gn(o,Ar,y(uo+Qe),Fu,B))-uo):Ze=Wn,tt=y(y(Gn(o,Ar,y(uo+(Xh?Wn:Qe)),Fu,B))-uo),Lt&T){G=Hr;do{ae=n[(n[_o>>2]|0)+(G<<2)>>2]|0;do if((n[ae+36>>2]|0)!=1){if((n[ae+24>>2]|0)==1){if(ga(ae,Ar)|0){if(zt=y(XA(ae,Ar,Wn)),zt=y(zt+y(Br(o,Ar))),zt=y(zt+y(J(ae,Ar,Fr))),M=n[Ff>>2]|0,h[ae+400+(M<<2)>>2]=zt,!(Mt(zt)|0))break}else M=n[Ff>>2]|0;zt=y(Br(o,Ar)),h[ae+400+(M<<2)>>2]=y(zt+y(J(ae,Ar,Fr)));break}M=as(o,ae)|0;do if((M|0)==4){if((n[(Ql(ae,Ar)|0)+4>>2]|0)==3){on=139;break}if((n[(Tl(ae,Ar)|0)+4>>2]|0)==3){on=139;break}if(oo(ae,Ar,Wn)|0){l=Le;break}yp=n[ae+908+(n[dc>>2]<<2)>>2]|0,n[lo>>2]=yp,l=y(h[ae+396>>2]),vi=Mt(l)|0,Qe=(n[S>>2]=yp,y(h[S>>2])),vi?l=tt:(Tr=y(yn(ae,Ar,Fr)),zt=y(Qe/l),l=y(l*Qe),l=y(Tr+(ui?zt:l))),h[Ml>>2]=l,h[lo>>2]=y(y(yn(ae,Mr,Fr))+Qe),n[ya>>2]=1,n[yc>>2]=1,Bu(ae,Mr,cr,Fr,ya,lo),Bu(ae,Ar,Wn,Fr,yc,Ml),l=y(h[lo>>2]),Tr=y(h[Ml>>2]),zt=ui?l:Tr,l=ui?Tr:l,vi=((Mt(zt)|0)^1)&1,kl(ae,zt,l,Hs,vi,((Mt(l)|0)^1)&1,Fr,Lo,1,3493,_)|0,l=Le}else on=139;while(!1);e:do if((on|0)==139){on=0,l=y(Ze-y($A(ae,Ar,Fr)));do if((n[(Ql(ae,Ar)|0)+4>>2]|0)==3){if((n[(Tl(ae,Ar)|0)+4>>2]|0)!=3)break;l=y(Le+y($n(y(0),y(l*y(.5)))));break e}while(!1);if((n[(Tl(ae,Ar)|0)+4>>2]|0)==3){l=Le;break}if((n[(Ql(ae,Ar)|0)+4>>2]|0)==3){l=y(Le+y($n(y(0),l)));break}switch(M|0){case 1:{l=Le;break e}case 2:{l=y(Le+y(l*y(.5)));break e}default:{l=y(Le+l);break e}}}while(!1);zt=y(Us+l),vi=ae+400+(n[Ff>>2]<<2)|0,h[vi>>2]=y(zt+y(h[vi>>2]))}while(!1);G=G+1|0}while((G|0)!=(fr|0))}if(Us=y(Us+tt),Qu=y($n(Qu,u)),m=Oo+1|0,fr>>>0>=fo>>>0)break;l=cr,Hr=fr,Oo=m}do if(T){if(M=m>>>0>1,!M&&!(WL(o)|0))break;if(!(Mt(Wn)|0)){l=y(Wn-Us);e:do switch(n[o+12>>2]|0){case 3:{Le=y(Le+l),He=y(0);break}case 2:{Le=y(Le+y(l*y(.5))),He=y(0);break}case 4:{Wn>Us?He=y(l/y(m>>>0)):He=y(0);break}case 7:if(Wn>Us){Le=y(Le+y(l/y(m<<1>>>0))),He=y(l/y(m>>>0)),He=M?He:y(0);break e}else{Le=y(Le+y(l*y(.5))),He=y(0);break e}case 6:{He=y(l/y(Oo>>>0)),He=Wn>Us&M?He:y(0);break}default:He=y(0)}while(!1);if(m|0)for(Lt=1040+(Ar<<2)|0,qr=976+(Ar<<2)|0,We=0,G=0;;){e:do if(G>>>0<fo>>>0)for(Qe=y(0),tt=y(0),l=y(0),ae=G;;){M=n[(n[_o>>2]|0)+(ae<<2)>>2]|0;do if((n[M+36>>2]|0)!=1&&!(n[M+24>>2]|0)){if((n[M+940>>2]|0)!=(We|0))break e;if(YL(M,Ar)|0&&(zt=y(h[M+908+(n[qr>>2]<<2)>>2]),l=y($n(l,y(zt+y(yn(M,Ar,Fr)))))),(as(o,M)|0)!=5)break;co=y(Wg(M)),co=y(co+y(J(M,0,Fr))),zt=y(h[M+912>>2]),zt=y(y(zt+y(yn(M,0,Fr)))-co),co=y($n(tt,co)),zt=y($n(Qe,zt)),Qe=zt,tt=co,l=y($n(l,y(co+zt)))}while(!1);if(M=ae+1|0,M>>>0<fo>>>0)ae=M;else{ae=M;break}}else tt=y(0),l=y(0),ae=G;while(!1);if(ct=y(He+l),u=Le,Le=y(Le+ct),G>>>0<ae>>>0){Ze=y(u+tt),M=G;do{G=n[(n[_o>>2]|0)+(M<<2)>>2]|0;e:do if((n[G+36>>2]|0)!=1&&!(n[G+24>>2]|0))switch(as(o,G)|0){case 1:{zt=y(u+y(J(G,Ar,Fr))),h[G+400+(n[Lt>>2]<<2)>>2]=zt;break e}case 3:{zt=y(y(Le-y(re(G,Ar,Fr)))-y(h[G+908+(n[qr>>2]<<2)>>2])),h[G+400+(n[Lt>>2]<<2)>>2]=zt;break e}case 2:{zt=y(u+y(y(ct-y(h[G+908+(n[qr>>2]<<2)>>2]))*y(.5))),h[G+400+(n[Lt>>2]<<2)>>2]=zt;break e}case 4:{if(zt=y(u+y(J(G,Ar,Fr))),h[G+400+(n[Lt>>2]<<2)>>2]=zt,oo(G,Ar,Wn)|0||(ui?(Qe=y(h[G+908>>2]),l=y(Qe+y(yn(G,Mr,Fr))),tt=ct):(tt=y(h[G+912>>2]),tt=y(tt+y(yn(G,Ar,Fr))),l=ct,Qe=y(h[G+908>>2])),mn(l,Qe)|0&&mn(tt,y(h[G+912>>2]))|0))break e;kl(G,l,tt,Hs,1,1,Fr,Lo,1,3501,_)|0;break e}case 5:{h[G+404>>2]=y(y(Ze-y(Wg(G)))+y(XA(G,0,Wn)));break e}default:break e}while(!1);M=M+1|0}while((M|0)!=(ae|0))}if(We=We+1|0,(We|0)==(m|0))break;G=ae}}}while(!1);if(h[o+908>>2]=y(Gn(o,2,Tu,B,B)),h[o+912>>2]=y(Gn(o,0,Ap,k,B)),Ec|0&&(pp=n[o+32>>2]|0,hp=(Ec|0)==2,!(hp&(pp|0)!=2))?hp&(pp|0)==2&&(l=y(Ru+cr),l=y($n(y(Ad(l,y(Yg(o,Mr,Qu,Mo)))),Ru)),on=198):(l=y(Gn(o,Mr,Qu,Mo,B)),on=198),(on|0)==198&&(h[o+908+(n[976+(Mr<<2)>>2]<<2)>>2]=l),Ic|0&&(dp=n[o+32>>2]|0,mp=(Ic|0)==2,!(mp&(dp|0)!=2))?mp&(dp|0)==2&&(l=y(uo+Wn),l=y($n(y(Ad(l,y(Yg(o,Ar,y(uo+Us),Fu)))),uo)),on=204):(l=y(Gn(o,Ar,y(uo+Us),Fu,B)),on=204),(on|0)==204&&(h[o+908+(n[976+(Ar<<2)>>2]<<2)>>2]=l),T){if((n[gp>>2]|0)==2){G=976+(Ar<<2)|0,ae=1040+(Ar<<2)|0,M=0;do We=ws(o,M)|0,n[We+24>>2]|0||(yp=n[G>>2]|0,zt=y(h[o+908+(yp<<2)>>2]),vi=We+400+(n[ae>>2]<<2)|0,zt=y(zt-y(h[vi>>2])),h[vi>>2]=y(zt-y(h[We+908+(yp<<2)>>2]))),M=M+1|0;while((M|0)!=(fo|0))}if(A|0){M=ui?Ec:d;do VL(o,A,Fr,M,Lo,Hs,_),A=n[A+960>>2]|0;while(A|0)}if(M=(Mr|2|0)==3,G=(Ar|2|0)==3,M|G){A=0;do ae=n[(n[_o>>2]|0)+(A<<2)>>2]|0,(n[ae+36>>2]|0)!=1&&(M&&i2(o,ae,Mr),G&&i2(o,ae,Ar)),A=A+1|0;while((A|0)!=(fo|0))}}}while(!1);I=Cc}function Ph(o,l){o=o|0,l=y(l);var u=0;ja(o,l>=y(0),3147),u=l==y(0),h[o+4>>2]=u?y(0):l}function KA(o,l,u,A){o=o|0,l=y(l),u=y(u),A=A|0;var d=Xe,m=Xe,B=0,k=0,T=0;n[2278]=(n[2278]|0)+1,Sf(o),oo(o,2,l)|0?(d=y(Xr(n[o+992>>2]|0,l)),T=1,d=y(d+y(yn(o,2,l)))):(d=y(Xr(o+380|0,l)),d>=y(0)?T=2:(T=((Mt(l)|0)^1)&1,d=l)),oo(o,0,u)|0?(m=y(Xr(n[o+996>>2]|0,u)),k=1,m=y(m+y(yn(o,0,l)))):(m=y(Xr(o+388|0,u)),m>=y(0)?k=2:(k=((Mt(u)|0)^1)&1,m=u)),B=o+976|0,kl(o,d,m,A,T,k,l,u,1,3189,n[B>>2]|0)|0&&(xh(o,n[o+496>>2]|0,l,u,l),JA(o,y(h[(n[B>>2]|0)+4>>2]),y(0),y(0)),s[11696]|0)&&jg(o,7)}function Sf(o){o=o|0;var l=0,u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0;k=I,I=I+32|0,B=k+24|0,m=k+16|0,A=k+8|0,d=k,u=0;do l=o+380+(u<<3)|0,n[o+380+(u<<3)+4>>2]|0&&(T=l,_=n[T+4>>2]|0,M=A,n[M>>2]=n[T>>2],n[M+4>>2]=_,M=o+364+(u<<3)|0,_=n[M+4>>2]|0,T=d,n[T>>2]=n[M>>2],n[T+4>>2]=_,n[m>>2]=n[A>>2],n[m+4>>2]=n[A+4>>2],n[B>>2]=n[d>>2],n[B+4>>2]=n[d+4>>2],wf(m,B)|0)||(l=o+348+(u<<3)|0),n[o+992+(u<<2)>>2]=l,u=u+1|0;while((u|0)!=2);I=k}function oo(o,l,u){o=o|0,l=l|0,u=y(u);var A=0;switch(o=n[o+992+(n[976+(l<<2)>>2]<<2)>>2]|0,n[o+4>>2]|0){case 0:case 3:{o=0;break}case 1:{y(h[o>>2])<y(0)?o=0:A=5;break}case 2:{y(h[o>>2])<y(0)?o=0:o=(Mt(u)|0)^1;break}default:A=5}return(A|0)==5&&(o=1),o|0}function Xr(o,l){switch(o=o|0,l=y(l),n[o+4>>2]|0){case 2:{l=y(y(y(h[o>>2])*l)/y(100));break}case 1:{l=y(h[o>>2]);break}default:l=y(ce)}return y(l)}function xh(o,l,u,A,d){o=o|0,l=l|0,u=y(u),A=y(A),d=y(d);var m=0,B=Xe;l=n[o+944>>2]|0?l:1,m=dr(n[o+4>>2]|0,l)|0,l=Sy(m,l)|0,u=y(yP(o,m,u)),A=y(yP(o,l,A)),B=y(u+y(J(o,m,d))),h[o+400+(n[1040+(m<<2)>>2]<<2)>>2]=B,u=y(u+y(re(o,m,d))),h[o+400+(n[1e3+(m<<2)>>2]<<2)>>2]=u,u=y(A+y(J(o,l,d))),h[o+400+(n[1040+(l<<2)>>2]<<2)>>2]=u,d=y(A+y(re(o,l,d))),h[o+400+(n[1e3+(l<<2)>>2]<<2)>>2]=d}function JA(o,l,u,A){o=o|0,l=y(l),u=y(u),A=y(A);var d=0,m=0,B=Xe,k=Xe,T=0,_=0,M=Xe,G=0,ae=Xe,We=Xe,Le=Xe,Qe=Xe;if(l!=y(0)&&(d=o+400|0,Qe=y(h[d>>2]),m=o+404|0,Le=y(h[m>>2]),G=o+416|0,We=y(h[G>>2]),_=o+420|0,B=y(h[_>>2]),ae=y(Qe+u),M=y(Le+A),A=y(ae+We),k=y(M+B),T=(n[o+988>>2]|0)==1,h[d>>2]=y(os(Qe,l,0,T)),h[m>>2]=y(os(Le,l,0,T)),u=y(A_(y(We*l),y(1))),mn(u,y(0))|0?m=0:m=(mn(u,y(1))|0)^1,u=y(A_(y(B*l),y(1))),mn(u,y(0))|0?d=0:d=(mn(u,y(1))|0)^1,Qe=y(os(A,l,T&m,T&(m^1))),h[G>>2]=y(Qe-y(os(ae,l,0,T))),Qe=y(os(k,l,T&d,T&(d^1))),h[_>>2]=y(Qe-y(os(M,l,0,T))),m=(n[o+952>>2]|0)-(n[o+948>>2]|0)>>2,m|0)){d=0;do JA(ws(o,d)|0,l,ae,M),d=d+1|0;while((d|0)!=(m|0))}}function By(o,l,u,A,d){switch(o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,u|0){case 5:case 0:{o=WX(n[489]|0,A,d)|0;break}default:o=MYe(A,d)|0}return o|0}function Gg(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;d=I,I=I+16|0,m=d,n[m>>2]=A,kh(o,0,l,u,m),I=d}function kh(o,l,u,A,d){if(o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,o=o|0?o:956,A$[n[o+8>>2]&1](o,l,u,A,d)|0,(u|0)==5)Nt();else return}function hc(o,l,u){o=o|0,l=l|0,u=u|0,s[o+l>>0]=u&1}function vy(o,l){o=o|0,l=l|0;var u=0,A=0;n[o>>2]=0,n[o+4>>2]=0,n[o+8>>2]=0,u=l+4|0,A=(n[u>>2]|0)-(n[l>>2]|0)>>2,A|0&&(Qh(o,A),kt(o,n[l>>2]|0,n[u>>2]|0,A))}function Qh(o,l){o=o|0,l=l|0;var u=0;if((O(o)|0)>>>0<l>>>0&&sn(o),l>>>0>1073741823)Nt();else{u=Jt(l<<2)|0,n[o+4>>2]=u,n[o>>2]=u,n[o+8>>2]=u+(l<<2);return}}function kt(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,A=o+4|0,o=u-l|0,(o|0)>0&&(Qr(n[A>>2]|0,l|0,o|0)|0,n[A>>2]=(n[A>>2]|0)+(o>>>2<<2))}function O(o){return o=o|0,1073741823}function J(o,l,u){return o=o|0,l=l|0,u=y(u),de(l)|0&&n[o+96>>2]|0?o=o+92|0:o=kn(o+60|0,n[1040+(l<<2)>>2]|0,992)|0,y(Ke(o,u))}function re(o,l,u){return o=o|0,l=l|0,u=y(u),de(l)|0&&n[o+104>>2]|0?o=o+100|0:o=kn(o+60|0,n[1e3+(l<<2)>>2]|0,992)|0,y(Ke(o,u))}function de(o){return o=o|0,(o|1|0)==3|0}function Ke(o,l){return o=o|0,l=y(l),(n[o+4>>2]|0)==3?l=y(0):l=y(Xr(o,l)),y(l)}function ft(o,l){return o=o|0,l=l|0,o=n[o>>2]|0,(o|0?o:(l|0)>1?l:1)|0}function dr(o,l){o=o|0,l=l|0;var u=0;e:do if((l|0)==2){switch(o|0){case 2:{o=3;break e}case 3:break;default:{u=4;break e}}o=2}else u=4;while(!1);return o|0}function Br(o,l){o=o|0,l=l|0;var u=Xe;return de(l)|0&&n[o+312>>2]|0&&(u=y(h[o+308>>2]),u>=y(0))||(u=y($n(y(h[(kn(o+276|0,n[1040+(l<<2)>>2]|0,992)|0)>>2]),y(0)))),y(u)}function _n(o,l){o=o|0,l=l|0;var u=Xe;return de(l)|0&&n[o+320>>2]|0&&(u=y(h[o+316>>2]),u>=y(0))||(u=y($n(y(h[(kn(o+276|0,n[1e3+(l<<2)>>2]|0,992)|0)>>2]),y(0)))),y(u)}function mi(o,l,u){o=o|0,l=l|0,u=y(u);var A=Xe;return de(l)|0&&n[o+240>>2]|0&&(A=y(Xr(o+236|0,u)),A>=y(0))||(A=y($n(y(Xr(kn(o+204|0,n[1040+(l<<2)>>2]|0,992)|0,u)),y(0)))),y(A)}function Bs(o,l,u){o=o|0,l=l|0,u=y(u);var A=Xe;return de(l)|0&&n[o+248>>2]|0&&(A=y(Xr(o+244|0,u)),A>=y(0))||(A=y($n(y(Xr(kn(o+204|0,n[1e3+(l<<2)>>2]|0,992)|0,u)),y(0)))),y(A)}function zA(o,l,u,A,d,m,B){o=o|0,l=y(l),u=y(u),A=A|0,d=d|0,m=y(m),B=y(B);var k=Xe,T=Xe,_=Xe,M=Xe,G=Xe,ae=Xe,We=0,Le=0,Qe=0;Qe=I,I=I+16|0,We=Qe,Le=o+964|0,Bi(o,(n[Le>>2]|0)!=0,3519),k=y(Ka(o,2,l)),T=y(Ka(o,0,l)),_=y(yn(o,2,l)),M=y(yn(o,0,l)),Mt(l)|0?G=l:G=y($n(y(0),y(y(l-_)-k))),Mt(u)|0?ae=u:ae=y($n(y(0),y(y(u-M)-T))),(A|0)==1&(d|0)==1?(h[o+908>>2]=y(Gn(o,2,y(l-_),m,m)),l=y(Gn(o,0,y(u-M),B,m))):(p$[n[Le>>2]&1](We,o,G,A,ae,d),G=y(k+y(h[We>>2])),ae=y(l-_),h[o+908>>2]=y(Gn(o,2,(A|2|0)==2?G:ae,m,m)),ae=y(T+y(h[We+4>>2])),l=y(u-M),l=y(Gn(o,0,(d|2|0)==2?ae:l,B,m))),h[o+912>>2]=l,I=Qe}function dP(o,l,u,A,d,m,B){o=o|0,l=y(l),u=y(u),A=A|0,d=d|0,m=y(m),B=y(B);var k=Xe,T=Xe,_=Xe,M=Xe;_=y(Ka(o,2,m)),k=y(Ka(o,0,m)),M=y(yn(o,2,m)),T=y(yn(o,0,m)),l=y(l-M),h[o+908>>2]=y(Gn(o,2,(A|2|0)==2?_:l,m,m)),u=y(u-T),h[o+912>>2]=y(Gn(o,0,(d|2|0)==2?k:u,B,m))}function t2(o,l,u,A,d,m,B){o=o|0,l=y(l),u=y(u),A=A|0,d=d|0,m=y(m),B=y(B);var k=0,T=Xe,_=Xe;return k=(A|0)==2,!(l<=y(0)&k)&&!(u<=y(0)&(d|0)==2)&&!((A|0)==1&(d|0)==1)?o=0:(T=y(yn(o,0,m)),_=y(yn(o,2,m)),k=l<y(0)&k|(Mt(l)|0),l=y(l-_),h[o+908>>2]=y(Gn(o,2,k?y(0):l,m,m)),l=y(u-T),k=u<y(0)&(d|0)==2|(Mt(u)|0),h[o+912>>2]=y(Gn(o,0,k?y(0):l,B,m)),o=1),o|0}function Sy(o,l){return o=o|0,l=l|0,Vg(o)|0?o=dr(2,l)|0:o=0,o|0}function Th(o,l,u){return o=o|0,l=l|0,u=y(u),u=y(mi(o,l,u)),y(u+y(Br(o,l)))}function r2(o,l,u){return o=o|0,l=l|0,u=y(u),u=y(Bs(o,l,u)),y(u+y(_n(o,l)))}function Ka(o,l,u){o=o|0,l=l|0,u=y(u);var A=Xe;return A=y(Th(o,l,u)),y(A+y(r2(o,l,u)))}function n2(o){return o=o|0,n[o+24>>2]|0?o=0:y(ZA(o))!=y(0)?o=1:o=y(Rh(o))!=y(0),o|0}function ZA(o){o=o|0;var l=Xe;if(n[o+944>>2]|0){if(l=y(h[o+44>>2]),Mt(l)|0)return l=y(h[o+40>>2]),o=l>y(0)&((Mt(l)|0)^1),y(o?l:y(0))}else l=y(0);return y(l)}function Rh(o){o=o|0;var l=Xe,u=0,A=Xe;do if(n[o+944>>2]|0){if(l=y(h[o+48>>2]),Mt(l)|0){if(u=s[(n[o+976>>2]|0)+2>>0]|0,!(u<<24>>24)&&(A=y(h[o+40>>2]),A<y(0)&((Mt(A)|0)^1))){l=y(-A);break}l=u<<24>>24?y(1):y(0)}}else l=y(0);while(!1);return y(l)}function Dy(o){o=o|0;var l=0,u=0;if(Xy(o+400|0,0,540)|0,s[o+985>>0]=1,ee(o),u=_i(o)|0,u|0){l=o+948|0,o=0;do Dy(n[(n[l>>2]|0)+(o<<2)>>2]|0),o=o+1|0;while((o|0)!=(u|0))}}function mP(o,l,u,A,d,m,B,k,T,_){o=o|0,l=l|0,u=y(u),A=A|0,d=y(d),m=y(m),B=y(B),k=k|0,T=T|0,_=_|0;var M=0,G=Xe,ae=0,We=0,Le=Xe,Qe=Xe,tt=0,Ze=Xe,ct=0,He=Xe,Ge=0,Lt=0,qr=0,fr=0,$t=0,Tr=0,Hr=0,cr=0,Hn=0,Fo=0;Hn=I,I=I+16|0,qr=Hn+12|0,fr=Hn+8|0,$t=Hn+4|0,Tr=Hn,cr=dr(n[o+4>>2]|0,T)|0,Ge=de(cr)|0,G=y(Xr(KL(l)|0,Ge?m:B)),Lt=oo(l,2,m)|0,Hr=oo(l,0,B)|0;do if(!(Mt(G)|0)&&!(Mt(Ge?u:d)|0)){if(M=l+504|0,!(Mt(y(h[M>>2]))|0)&&(!(s2(n[l+976>>2]|0,0)|0)||(n[l+500>>2]|0)==(n[2278]|0)))break;h[M>>2]=y($n(G,y(Ka(l,cr,m))))}else ae=7;while(!1);do if((ae|0)==7){if(ct=Ge^1,!(ct|Lt^1)){B=y(Xr(n[l+992>>2]|0,m)),h[l+504>>2]=y($n(B,y(Ka(l,2,m))));break}if(!(Ge|Hr^1)){B=y(Xr(n[l+996>>2]|0,B)),h[l+504>>2]=y($n(B,y(Ka(l,0,m))));break}h[qr>>2]=y(ce),h[fr>>2]=y(ce),n[$t>>2]=0,n[Tr>>2]=0,Ze=y(yn(l,2,m)),He=y(yn(l,0,m)),Lt?(Le=y(Ze+y(Xr(n[l+992>>2]|0,m))),h[qr>>2]=Le,n[$t>>2]=1,We=1):(We=0,Le=y(ce)),Hr?(G=y(He+y(Xr(n[l+996>>2]|0,B))),h[fr>>2]=G,n[Tr>>2]=1,M=1):(M=0,G=y(ce)),ae=n[o+32>>2]|0,Ge&(ae|0)==2?ae=2:Mt(Le)|0&&!(Mt(u)|0)&&(h[qr>>2]=u,n[$t>>2]=2,We=2,Le=u),!((ae|0)==2&ct)&&Mt(G)|0&&!(Mt(d)|0)&&(h[fr>>2]=d,n[Tr>>2]=2,M=2,G=d),Qe=y(h[l+396>>2]),tt=Mt(Qe)|0;do if(tt)ae=We;else{if((We|0)==1&ct){h[fr>>2]=y(y(Le-Ze)/Qe),n[Tr>>2]=1,M=1,ae=1;break}Ge&(M|0)==1?(h[qr>>2]=y(Qe*y(G-He)),n[$t>>2]=1,M=1,ae=1):ae=We}while(!1);Fo=Mt(u)|0,We=(as(o,l)|0)!=4,!(Ge|Lt|((A|0)!=1|Fo)|(We|(ae|0)==1))&&(h[qr>>2]=u,n[$t>>2]=1,!tt)&&(h[fr>>2]=y(y(u-Ze)/Qe),n[Tr>>2]=1,M=1),!(Hr|ct|((k|0)!=1|(Mt(d)|0))|(We|(M|0)==1))&&(h[fr>>2]=d,n[Tr>>2]=1,!tt)&&(h[qr>>2]=y(Qe*y(d-He)),n[$t>>2]=1),Bu(l,2,m,m,$t,qr),Bu(l,0,B,m,Tr,fr),u=y(h[qr>>2]),d=y(h[fr>>2]),kl(l,u,d,T,n[$t>>2]|0,n[Tr>>2]|0,m,B,0,3565,_)|0,B=y(h[l+908+(n[976+(cr<<2)>>2]<<2)>>2]),h[l+504>>2]=y($n(B,y(Ka(l,cr,m))))}while(!1);n[l+500>>2]=n[2278],I=Hn}function Gn(o,l,u,A,d){return o=o|0,l=l|0,u=y(u),A=y(A),d=y(d),A=y(Yg(o,l,u,A)),y($n(A,y(Ka(o,l,d))))}function as(o,l){return o=o|0,l=l|0,l=l+20|0,l=n[(n[l>>2]|0?l:o+16|0)>>2]|0,(l|0)==5&&Vg(n[o+4>>2]|0)|0&&(l=1),l|0}function Ql(o,l){return o=o|0,l=l|0,de(l)|0&&n[o+96>>2]|0?l=4:l=n[1040+(l<<2)>>2]|0,o+60+(l<<3)|0}function Tl(o,l){return o=o|0,l=l|0,de(l)|0&&n[o+104>>2]|0?l=5:l=n[1e3+(l<<2)>>2]|0,o+60+(l<<3)|0}function Bu(o,l,u,A,d,m){switch(o=o|0,l=l|0,u=y(u),A=y(A),d=d|0,m=m|0,u=y(Xr(o+380+(n[976+(l<<2)>>2]<<3)|0,u)),u=y(u+y(yn(o,l,A))),n[d>>2]|0){case 2:case 1:{d=Mt(u)|0,A=y(h[m>>2]),h[m>>2]=d|A<u?A:u;break}case 0:{Mt(u)|0||(n[d>>2]=2,h[m>>2]=u);break}default:}}function ga(o,l){return o=o|0,l=l|0,o=o+132|0,de(l)|0&&n[(kn(o,4,948)|0)+4>>2]|0?o=1:o=(n[(kn(o,n[1040+(l<<2)>>2]|0,948)|0)+4>>2]|0)!=0,o|0}function XA(o,l,u){o=o|0,l=l|0,u=y(u);var A=0,d=0;return o=o+132|0,de(l)|0&&(A=kn(o,4,948)|0,(n[A+4>>2]|0)!=0)?d=4:(A=kn(o,n[1040+(l<<2)>>2]|0,948)|0,n[A+4>>2]|0?d=4:u=y(0)),(d|0)==4&&(u=y(Xr(A,u))),y(u)}function $A(o,l,u){o=o|0,l=l|0,u=y(u);var A=Xe;return A=y(h[o+908+(n[976+(l<<2)>>2]<<2)>>2]),A=y(A+y(J(o,l,u))),y(A+y(re(o,l,u)))}function WL(o){o=o|0;var l=0,u=0,A=0;e:do if(Vg(n[o+4>>2]|0)|0)l=0;else if((n[o+16>>2]|0)!=5)if(u=_i(o)|0,!u)l=0;else for(l=0;;){if(A=ws(o,l)|0,!(n[A+24>>2]|0)&&(n[A+20>>2]|0)==5){l=1;break e}if(l=l+1|0,l>>>0>=u>>>0){l=0;break}}else l=1;while(!1);return l|0}function YL(o,l){o=o|0,l=l|0;var u=Xe;return u=y(h[o+908+(n[976+(l<<2)>>2]<<2)>>2]),u>=y(0)&((Mt(u)|0)^1)|0}function Wg(o){o=o|0;var l=Xe,u=0,A=0,d=0,m=0,B=0,k=0,T=Xe;if(u=n[o+968>>2]|0,u)T=y(h[o+908>>2]),l=y(h[o+912>>2]),l=y(l$[u&0](o,T,l)),Bi(o,(Mt(l)|0)^1,3573);else{m=_i(o)|0;do if(m|0){for(u=0,d=0;;){if(A=ws(o,d)|0,n[A+940>>2]|0){B=8;break}if((n[A+24>>2]|0)!=1)if(k=(as(o,A)|0)==5,k){u=A;break}else u=u|0?u:A;if(d=d+1|0,d>>>0>=m>>>0){B=8;break}}if((B|0)==8&&!u)break;return l=y(Wg(u)),y(l+y(h[u+404>>2]))}while(!1);l=y(h[o+912>>2])}return y(l)}function Yg(o,l,u,A){o=o|0,l=l|0,u=y(u),A=y(A);var d=Xe,m=0;return Vg(l)|0?(l=1,m=3):de(l)|0?(l=0,m=3):(A=y(ce),d=y(ce)),(m|0)==3&&(d=y(Xr(o+364+(l<<3)|0,A)),A=y(Xr(o+380+(l<<3)|0,A))),m=A<u&(A>=y(0)&((Mt(A)|0)^1)),u=m?A:u,m=d>=y(0)&((Mt(d)|0)^1)&u<d,y(m?d:u)}function VL(o,l,u,A,d,m,B){o=o|0,l=l|0,u=y(u),A=A|0,d=y(d),m=m|0,B=B|0;var k=Xe,T=Xe,_=0,M=0,G=Xe,ae=Xe,We=Xe,Le=0,Qe=0,tt=0,Ze=0,ct=Xe,He=0;tt=dr(n[o+4>>2]|0,m)|0,Le=Sy(tt,m)|0,Qe=de(tt)|0,G=y(yn(l,2,u)),ae=y(yn(l,0,u)),oo(l,2,u)|0?k=y(G+y(Xr(n[l+992>>2]|0,u))):ga(l,2)|0&&by(l,2)|0?(k=y(h[o+908>>2]),T=y(Br(o,2)),T=y(k-y(T+y(_n(o,2)))),k=y(XA(l,2,u)),k=y(Gn(l,2,y(T-y(k+y(Fh(l,2,u)))),u,u))):k=y(ce),oo(l,0,d)|0?T=y(ae+y(Xr(n[l+996>>2]|0,d))):ga(l,0)|0&&by(l,0)|0?(T=y(h[o+912>>2]),ct=y(Br(o,0)),ct=y(T-y(ct+y(_n(o,0)))),T=y(XA(l,0,d)),T=y(Gn(l,0,y(ct-y(T+y(Fh(l,0,d)))),d,u))):T=y(ce),_=Mt(k)|0,M=Mt(T)|0;do if(_^M&&(We=y(h[l+396>>2]),!(Mt(We)|0)))if(_){k=y(G+y(y(T-ae)*We));break}else{ct=y(ae+y(y(k-G)/We)),T=M?ct:T;break}while(!1);M=Mt(k)|0,_=Mt(T)|0,M|_&&(He=(M^1)&1,A=u>y(0)&((A|0)!=0&M),k=Qe?k:A?u:k,kl(l,k,T,m,Qe?He:A?2:He,M&(_^1)&1,k,T,0,3623,B)|0,k=y(h[l+908>>2]),k=y(k+y(yn(l,2,u))),T=y(h[l+912>>2]),T=y(T+y(yn(l,0,u)))),kl(l,k,T,m,1,1,k,T,1,3635,B)|0,by(l,tt)|0&&!(ga(l,tt)|0)?(He=n[976+(tt<<2)>>2]|0,ct=y(h[o+908+(He<<2)>>2]),ct=y(ct-y(h[l+908+(He<<2)>>2])),ct=y(ct-y(_n(o,tt))),ct=y(ct-y(re(l,tt,u))),ct=y(ct-y(Fh(l,tt,Qe?u:d))),h[l+400+(n[1040+(tt<<2)>>2]<<2)>>2]=ct):Ze=21;do if((Ze|0)==21){if(!(ga(l,tt)|0)&&(n[o+8>>2]|0)==1){He=n[976+(tt<<2)>>2]|0,ct=y(h[o+908+(He<<2)>>2]),ct=y(y(ct-y(h[l+908+(He<<2)>>2]))*y(.5)),h[l+400+(n[1040+(tt<<2)>>2]<<2)>>2]=ct;break}!(ga(l,tt)|0)&&(n[o+8>>2]|0)==2&&(He=n[976+(tt<<2)>>2]|0,ct=y(h[o+908+(He<<2)>>2]),ct=y(ct-y(h[l+908+(He<<2)>>2])),h[l+400+(n[1040+(tt<<2)>>2]<<2)>>2]=ct)}while(!1);by(l,Le)|0&&!(ga(l,Le)|0)?(He=n[976+(Le<<2)>>2]|0,ct=y(h[o+908+(He<<2)>>2]),ct=y(ct-y(h[l+908+(He<<2)>>2])),ct=y(ct-y(_n(o,Le))),ct=y(ct-y(re(l,Le,u))),ct=y(ct-y(Fh(l,Le,Qe?d:u))),h[l+400+(n[1040+(Le<<2)>>2]<<2)>>2]=ct):Ze=30;do if((Ze|0)==30&&!(ga(l,Le)|0)){if((as(o,l)|0)==2){He=n[976+(Le<<2)>>2]|0,ct=y(h[o+908+(He<<2)>>2]),ct=y(y(ct-y(h[l+908+(He<<2)>>2]))*y(.5)),h[l+400+(n[1040+(Le<<2)>>2]<<2)>>2]=ct;break}He=(as(o,l)|0)==3,He^(n[o+28>>2]|0)==2&&(He=n[976+(Le<<2)>>2]|0,ct=y(h[o+908+(He<<2)>>2]),ct=y(ct-y(h[l+908+(He<<2)>>2])),h[l+400+(n[1040+(Le<<2)>>2]<<2)>>2]=ct)}while(!1)}function i2(o,l,u){o=o|0,l=l|0,u=u|0;var A=Xe,d=0;d=n[976+(u<<2)>>2]|0,A=y(h[l+908+(d<<2)>>2]),A=y(y(h[o+908+(d<<2)>>2])-A),A=y(A-y(h[l+400+(n[1040+(u<<2)>>2]<<2)>>2])),h[l+400+(n[1e3+(u<<2)>>2]<<2)>>2]=A}function Vg(o){return o=o|0,(o|1|0)==1|0}function KL(o){o=o|0;var l=Xe;switch(n[o+56>>2]|0){case 0:case 3:{l=y(h[o+40>>2]),l>y(0)&((Mt(l)|0)^1)?o=s[(n[o+976>>2]|0)+2>>0]|0?1056:992:o=1056;break}default:o=o+52|0}return o|0}function s2(o,l){return o=o|0,l=l|0,(s[o+l>>0]|0)!=0|0}function by(o,l){return o=o|0,l=l|0,o=o+132|0,de(l)|0&&n[(kn(o,5,948)|0)+4>>2]|0?o=1:o=(n[(kn(o,n[1e3+(l<<2)>>2]|0,948)|0)+4>>2]|0)!=0,o|0}function Fh(o,l,u){o=o|0,l=l|0,u=y(u);var A=0,d=0;return o=o+132|0,de(l)|0&&(A=kn(o,5,948)|0,(n[A+4>>2]|0)!=0)?d=4:(A=kn(o,n[1e3+(l<<2)>>2]|0,948)|0,n[A+4>>2]|0?d=4:u=y(0)),(d|0)==4&&(u=y(Xr(A,u))),y(u)}function yP(o,l,u){return o=o|0,l=l|0,u=y(u),ga(o,l)|0?u=y(XA(o,l,u)):u=y(-y(Fh(o,l,u))),y(u)}function EP(o){return o=y(o),h[S>>2]=o,n[S>>2]|0|0}function Py(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>1073741823)Nt();else{d=Jt(l<<2)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<2)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<2)}function IP(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>2)<<2)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function xy(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-4-l|0)>>>2)<<2)),o=n[o>>2]|0,o|0&&Et(o)}function CP(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0;if(B=o+4|0,k=n[B>>2]|0,d=k-A|0,m=d>>2,o=l+(m<<2)|0,o>>>0<u>>>0){A=k;do n[A>>2]=n[o>>2],o=o+4|0,A=(n[B>>2]|0)+4|0,n[B>>2]=A;while(o>>>0<u>>>0)}m|0&&Q2(k+(0-m<<2)|0,l|0,d|0)|0}function wP(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0;return k=l+4|0,T=n[k>>2]|0,d=n[o>>2]|0,B=u,m=B-d|0,A=T+(0-(m>>2)<<2)|0,n[k>>2]=A,(m|0)>0&&Qr(A|0,d|0,m|0)|0,d=o+4|0,m=l+8|0,A=(n[d>>2]|0)-B|0,(A|0)>0&&(Qr(n[m>>2]|0,u|0,A|0)|0,n[m>>2]=(n[m>>2]|0)+(A>>>2<<2)),B=n[o>>2]|0,n[o>>2]=n[k>>2],n[k>>2]=B,B=n[d>>2]|0,n[d>>2]=n[m>>2],n[m>>2]=B,B=o+8|0,u=l+12|0,o=n[B>>2]|0,n[B>>2]=n[u>>2],n[u>>2]=o,n[l>>2]=n[k>>2],T|0}function o2(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;if(B=n[l>>2]|0,m=n[u>>2]|0,(B|0)!=(m|0)){d=o+8|0,u=((m+-4-B|0)>>>2)+1|0,o=B,A=n[d>>2]|0;do n[A>>2]=n[o>>2],A=(n[d>>2]|0)+4|0,n[d>>2]=A,o=o+4|0;while((o|0)!=(m|0));n[l>>2]=B+(u<<2)}}function a2(){fa()}function BP(){var o=0;return o=Jt(4)|0,l2(o),o|0}function l2(o){o=o|0,n[o>>2]=pc()|0}function vP(o){o=o|0,o|0&&(Kg(o),Et(o))}function Kg(o){o=o|0,st(n[o>>2]|0)}function JL(o,l,u){o=o|0,l=l|0,u=u|0,hc(n[o>>2]|0,l,u)}function ky(o,l){o=o|0,l=y(l),Ph(n[o>>2]|0,l)}function Qy(o,l){return o=o|0,l=l|0,s2(n[o>>2]|0,l)|0}function Ty(){var o=0;return o=Jt(8)|0,Jg(o,0),o|0}function Jg(o,l){o=o|0,l=l|0,l?l=Aa(n[l>>2]|0)|0:l=is()|0,n[o>>2]=l,n[o+4>>2]=0,Tn(l,o)}function Ry(o){o=o|0;var l=0;return l=Jt(8)|0,Jg(l,o),l|0}function zg(o){o=o|0,o|0&&(Fy(o),Et(o))}function Fy(o){o=o|0;var l=0;fc(n[o>>2]|0),l=o+4|0,o=n[l>>2]|0,n[l>>2]=0,o|0&&(Df(o),Et(o))}function Df(o){o=o|0,bf(o)}function bf(o){o=o|0,o=n[o>>2]|0,o|0&&Oa(o|0)}function c2(o){return o=o|0,Ga(o)|0}function u2(o){o=o|0;var l=0,u=0;u=o+4|0,l=n[u>>2]|0,n[u>>2]=0,l|0&&(Df(l),Et(l)),Ac(n[o>>2]|0)}function Ny(o,l){o=o|0,l=l|0,fn(n[o>>2]|0,n[l>>2]|0)}function zL(o,l){o=o|0,l=l|0,vh(n[o>>2]|0,l)}function ZL(o,l,u){o=o|0,l=l|0,u=+u,Ey(n[o>>2]|0,l,y(u))}function Oy(o,l,u){o=o|0,l=l|0,u=+u,Iy(n[o>>2]|0,l,y(u))}function f2(o,l){o=o|0,l=l|0,Ch(n[o>>2]|0,l)}function A2(o,l){o=o|0,l=l|0,bo(n[o>>2]|0,l)}function xr(o,l){o=o|0,l=l|0,Bh(n[o>>2]|0,l)}function ao(o,l){o=o|0,l=l|0,gy(n[o>>2]|0,l)}function Xi(o,l){o=o|0,l=l|0,Fg(n[o>>2]|0,l)}function Ls(o,l){o=o|0,l=l|0,Do(n[o>>2]|0,l)}function ep(o,l,u){o=o|0,l=l|0,u=+u,qA(n[o>>2]|0,l,y(u))}function p2(o,l,u){o=o|0,l=l|0,u=+u,Y(n[o>>2]|0,l,y(u))}function vs(o,l){o=o|0,l=l|0,GA(n[o>>2]|0,l)}function Ly(o,l){o=o|0,l=l|0,my(n[o>>2]|0,l)}function Nh(o,l){o=o|0,l=l|0,Po(n[o>>2]|0,l)}function Zg(o,l){o=o|0,l=+l,Sh(n[o>>2]|0,y(l))}function Oh(o,l){o=o|0,l=+l,Pl(n[o>>2]|0,y(l))}function h2(o,l){o=o|0,l=+l,yy(n[o>>2]|0,y(l))}function g2(o,l){o=o|0,l=+l,Og(n[o>>2]|0,y(l))}function d2(o,l){o=o|0,l=+l,bl(n[o>>2]|0,y(l))}function m2(o,l){o=o|0,l=+l,Lg(n[o>>2]|0,y(l))}function Pf(o,l){o=o|0,l=+l,e2(n[o>>2]|0,y(l))}function sr(o){o=o|0,Dh(n[o>>2]|0)}function My(o,l){o=o|0,l=+l,Zi(n[o>>2]|0,y(l))}function y2(o,l){o=o|0,l=+l,Ef(n[o>>2]|0,y(l))}function gc(o){o=o|0,Wa(n[o>>2]|0)}function xf(o,l){o=o|0,l=+l,yu(n[o>>2]|0,y(l))}function Xg(o,l){o=o|0,l=+l,If(n[o>>2]|0,y(l))}function $g(o,l){o=o|0,l=+l,di(n[o>>2]|0,y(l))}function E2(o,l){o=o|0,l=+l,WA(n[o>>2]|0,y(l))}function I2(o,l){o=o|0,l=+l,pa(n[o>>2]|0,y(l))}function vu(o,l){o=o|0,l=+l,Va(n[o>>2]|0,y(l))}function ed(o,l){o=o|0,l=+l,bh(n[o>>2]|0,y(l))}function C2(o,l){o=o|0,l=+l,Ug(n[o>>2]|0,y(l))}function _y(o,l){o=o|0,l=+l,YA(n[o>>2]|0,y(l))}function Su(o,l,u){o=o|0,l=l|0,u=+u,mu(n[o>>2]|0,l,y(u))}function Uy(o,l,u){o=o|0,l=l|0,u=+u,xo(n[o>>2]|0,l,y(u))}function td(o,l,u){o=o|0,l=l|0,u=+u,yf(n[o>>2]|0,l,y(u))}function rd(o){return o=o|0,Rg(n[o>>2]|0)|0}function To(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0;A=I,I=I+16|0,d=A,jA(d,n[l>>2]|0,u),Ss(o,d),I=A}function Ss(o,l){o=o|0,l=l|0,Rl(o,n[l+4>>2]|0,+y(h[l>>2]))}function Rl(o,l,u){o=o|0,l=l|0,u=+u,n[o>>2]=l,E[o+8>>3]=u}function Hy(o){return o=o|0,$1(n[o>>2]|0)|0}function da(o){return o=o|0,wh(n[o>>2]|0)|0}function SP(o){return o=o|0,du(n[o>>2]|0)|0}function Lh(o){return o=o|0,X1(n[o>>2]|0)|0}function w2(o){return o=o|0,Ng(n[o>>2]|0)|0}function XL(o){return o=o|0,dy(n[o>>2]|0)|0}function DP(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0;A=I,I=I+16|0,d=A,xt(d,n[l>>2]|0,u),Ss(o,d),I=A}function bP(o){return o=o|0,mf(n[o>>2]|0)|0}function jy(o){return o=o|0,Dl(n[o>>2]|0)|0}function B2(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,HA(A,n[l>>2]|0),Ss(o,A),I=u}function Mh(o){return o=o|0,+ +y(li(n[o>>2]|0))}function PP(o){return o=o|0,+ +y(Gi(n[o>>2]|0))}function xP(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,ur(A,n[l>>2]|0),Ss(o,A),I=u}function nd(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,Mg(A,n[l>>2]|0),Ss(o,A),I=u}function $L(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,wt(A,n[l>>2]|0),Ss(o,A),I=u}function eM(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,Ya(A,n[l>>2]|0),Ss(o,A),I=u}function kP(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,_g(A,n[l>>2]|0),Ss(o,A),I=u}function QP(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,wy(A,n[l>>2]|0),Ss(o,A),I=u}function tp(o){return o=o|0,+ +y(Hg(n[o>>2]|0))}function tM(o,l){return o=o|0,l=l|0,+ +y(Cy(n[o>>2]|0,l))}function rM(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0;A=I,I=I+16|0,d=A,mt(d,n[l>>2]|0,u),Ss(o,d),I=A}function Du(o,l,u){o=o|0,l=l|0,u=u|0,lr(n[o>>2]|0,n[l>>2]|0,u)}function nM(o,l){o=o|0,l=l|0,df(n[o>>2]|0,n[l>>2]|0)}function TP(o){return o=o|0,_i(n[o>>2]|0)|0}function iM(o){return o=o|0,o=yt(n[o>>2]|0)|0,o?o=c2(o)|0:o=0,o|0}function RP(o,l){return o=o|0,l=l|0,o=ws(n[o>>2]|0,l)|0,o?o=c2(o)|0:o=0,o|0}function kf(o,l){o=o|0,l=l|0;var u=0,A=0;A=Jt(4)|0,FP(A,l),u=o+4|0,l=n[u>>2]|0,n[u>>2]=A,l|0&&(Df(l),Et(l)),St(n[o>>2]|0,1)}function FP(o,l){o=o|0,l=l|0,lM(o,l)}function sM(o,l,u,A,d,m){o=o|0,l=l|0,u=y(u),A=A|0,d=y(d),m=m|0;var B=0,k=0;B=I,I=I+16|0,k=B,NP(k,Ga(l)|0,+u,A,+d,m),h[o>>2]=y(+E[k>>3]),h[o+4>>2]=y(+E[k+8>>3]),I=B}function NP(o,l,u,A,d,m){o=o|0,l=l|0,u=+u,A=A|0,d=+d,m=m|0;var B=0,k=0,T=0,_=0,M=0;B=I,I=I+32|0,M=B+8|0,_=B+20|0,T=B,k=B+16|0,E[M>>3]=u,n[_>>2]=A,E[T>>3]=d,n[k>>2]=m,qy(o,n[l+4>>2]|0,M,_,T,k),I=B}function qy(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0;var B=0,k=0;B=I,I=I+16|0,k=B,Nl(k),l=Ms(l)|0,OP(o,l,+E[u>>3],n[A>>2]|0,+E[d>>3],n[m>>2]|0),Ol(k),I=B}function Ms(o){return o=o|0,n[o>>2]|0}function OP(o,l,u,A,d,m){o=o|0,l=l|0,u=+u,A=A|0,d=+d,m=m|0;var B=0;B=ma(v2()|0)|0,u=+Ja(u),A=Gy(A)|0,d=+Ja(d),oM(o,Jn(0,B|0,l|0,+u,A|0,+d,Gy(m)|0)|0)}function v2(){var o=0;return s[7608]|0||(D2(9120),o=7608,n[o>>2]=1,n[o+4>>2]=0),9120}function ma(o){return o=o|0,n[o+8>>2]|0}function Ja(o){return o=+o,+ +Qf(o)}function Gy(o){return o=o|0,id(o)|0}function oM(o,l){o=o|0,l=l|0;var u=0,A=0,d=0;d=I,I=I+32|0,u=d,A=l,A&1?(za(u,0),Me(A|0,u|0)|0,S2(o,u),aM(u)):(n[o>>2]=n[l>>2],n[o+4>>2]=n[l+4>>2],n[o+8>>2]=n[l+8>>2],n[o+12>>2]=n[l+12>>2]),I=d}function za(o,l){o=o|0,l=l|0,bu(o,l),n[o+8>>2]=0,s[o+24>>0]=0}function S2(o,l){o=o|0,l=l|0,l=l+8|0,n[o>>2]=n[l>>2],n[o+4>>2]=n[l+4>>2],n[o+8>>2]=n[l+8>>2],n[o+12>>2]=n[l+12>>2]}function aM(o){o=o|0,s[o+24>>0]=0}function bu(o,l){o=o|0,l=l|0,n[o>>2]=l}function id(o){return o=o|0,o|0}function Qf(o){return o=+o,+o}function D2(o){o=o|0,Ro(o,b2()|0,4)}function b2(){return 1064}function Ro(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u,n[o+8>>2]=qi(l|0,u+1|0)|0}function lM(o,l){o=o|0,l=l|0,l=n[l>>2]|0,n[o>>2]=l,cu(l|0)}function LP(o){o=o|0;var l=0,u=0;u=o+4|0,l=n[u>>2]|0,n[u>>2]=0,l|0&&(Df(l),Et(l)),St(n[o>>2]|0,0)}function MP(o){o=o|0,Dt(n[o>>2]|0)}function Wy(o){return o=o|0,tr(n[o>>2]|0)|0}function cM(o,l,u,A){o=o|0,l=+l,u=+u,A=A|0,KA(n[o>>2]|0,y(l),y(u),A)}function uM(o){return o=o|0,+ +y(Eu(n[o>>2]|0))}function v(o){return o=o|0,+ +y(Cf(n[o>>2]|0))}function D(o){return o=o|0,+ +y(Iu(n[o>>2]|0))}function Q(o){return o=o|0,+ +y(Ns(n[o>>2]|0))}function H(o){return o=o|0,+ +y(Cu(n[o>>2]|0))}function V(o){return o=o|0,+ +y(qn(n[o>>2]|0))}function ne(o,l){o=o|0,l=l|0,E[o>>3]=+y(Eu(n[l>>2]|0)),E[o+8>>3]=+y(Cf(n[l>>2]|0)),E[o+16>>3]=+y(Iu(n[l>>2]|0)),E[o+24>>3]=+y(Ns(n[l>>2]|0)),E[o+32>>3]=+y(Cu(n[l>>2]|0)),E[o+40>>3]=+y(qn(n[l>>2]|0))}function Se(o,l){return o=o|0,l=l|0,+ +y(ss(n[o>>2]|0,l))}function Ue(o,l){return o=o|0,l=l|0,+ +y(ki(n[o>>2]|0,l))}function At(o,l){return o=o|0,l=l|0,+ +y(VA(n[o>>2]|0,l))}function Gt(){return Qn()|0}function vr(){Lr(),Xt(),zn(),yi(),Za(),$e()}function Lr(){vqe(11713,4938,1)}function Xt(){q6e(10448)}function zn(){v6e(10408)}function yi(){Vje(10324)}function Za(){tHe(10096)}function $e(){qe(9132)}function qe(o){o=o|0;var l=0,u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0,We=0,Le=0,Qe=0,tt=0,Ze=0,ct=0,He=0,Ge=0,Lt=0,qr=0,fr=0,$t=0,Tr=0,Hr=0,cr=0,Hn=0,Fo=0,No=0,Oo=0,$a=0,Vh=0,Kh=0,dc=0,Jh=0,Ff=0,Nf=0,zh=0,Zh=0,Xh=0,on=0,mc=0,$h=0,ku=0,e0=0,t0=0,Of=0,Lf=0,Qu=0,lo=0,Ml=0,ya=0,yc=0,lp=0,cp=0,Mf=0,up=0,fp=0,co=0,Us=0,Ec=0,Wn=0,Ap=0,Lo=0,Tu=0,Mo=0,Ru=0,pp=0,hp=0,Fu=0,uo=0,Ic=0,gp=0,dp=0,mp=0,Fr=0,ui=0,Hs=0,_o=0,fo=0,Mr=0,Ar=0,Cc=0;l=I,I=I+672|0,u=l+656|0,Cc=l+648|0,Ar=l+640|0,Mr=l+632|0,fo=l+624|0,_o=l+616|0,Hs=l+608|0,ui=l+600|0,Fr=l+592|0,mp=l+584|0,dp=l+576|0,gp=l+568|0,Ic=l+560|0,uo=l+552|0,Fu=l+544|0,hp=l+536|0,pp=l+528|0,Ru=l+520|0,Mo=l+512|0,Tu=l+504|0,Lo=l+496|0,Ap=l+488|0,Wn=l+480|0,Ec=l+472|0,Us=l+464|0,co=l+456|0,fp=l+448|0,up=l+440|0,Mf=l+432|0,cp=l+424|0,lp=l+416|0,yc=l+408|0,ya=l+400|0,Ml=l+392|0,lo=l+384|0,Qu=l+376|0,Lf=l+368|0,Of=l+360|0,t0=l+352|0,e0=l+344|0,ku=l+336|0,$h=l+328|0,mc=l+320|0,on=l+312|0,Xh=l+304|0,Zh=l+296|0,zh=l+288|0,Nf=l+280|0,Ff=l+272|0,Jh=l+264|0,dc=l+256|0,Kh=l+248|0,Vh=l+240|0,$a=l+232|0,Oo=l+224|0,No=l+216|0,Fo=l+208|0,Hn=l+200|0,cr=l+192|0,Hr=l+184|0,Tr=l+176|0,$t=l+168|0,fr=l+160|0,qr=l+152|0,Lt=l+144|0,Ge=l+136|0,He=l+128|0,ct=l+120|0,Ze=l+112|0,tt=l+104|0,Qe=l+96|0,Le=l+88|0,We=l+80|0,ae=l+72|0,G=l+64|0,M=l+56|0,_=l+48|0,T=l+40|0,k=l+32|0,B=l+24|0,m=l+16|0,d=l+8|0,A=l,ht(o,3646),Zt(o,3651,2)|0,Sr(o,3665,2)|0,Xn(o,3682,18)|0,n[Cc>>2]=19,n[Cc+4>>2]=0,n[u>>2]=n[Cc>>2],n[u+4>>2]=n[Cc+4>>2],kr(o,3690,u)|0,n[Ar>>2]=1,n[Ar+4>>2]=0,n[u>>2]=n[Ar>>2],n[u+4>>2]=n[Ar+4>>2],Rn(o,3696,u)|0,n[Mr>>2]=2,n[Mr+4>>2]=0,n[u>>2]=n[Mr>>2],n[u+4>>2]=n[Mr+4>>2],Un(o,3706,u)|0,n[fo>>2]=1,n[fo+4>>2]=0,n[u>>2]=n[fo>>2],n[u+4>>2]=n[fo+4>>2],zr(o,3722,u)|0,n[_o>>2]=2,n[_o+4>>2]=0,n[u>>2]=n[_o>>2],n[u+4>>2]=n[_o+4>>2],zr(o,3734,u)|0,n[Hs>>2]=3,n[Hs+4>>2]=0,n[u>>2]=n[Hs>>2],n[u+4>>2]=n[Hs+4>>2],Un(o,3753,u)|0,n[ui>>2]=4,n[ui+4>>2]=0,n[u>>2]=n[ui>>2],n[u+4>>2]=n[ui+4>>2],Un(o,3769,u)|0,n[Fr>>2]=5,n[Fr+4>>2]=0,n[u>>2]=n[Fr>>2],n[u+4>>2]=n[Fr+4>>2],Un(o,3783,u)|0,n[mp>>2]=6,n[mp+4>>2]=0,n[u>>2]=n[mp>>2],n[u+4>>2]=n[mp+4>>2],Un(o,3796,u)|0,n[dp>>2]=7,n[dp+4>>2]=0,n[u>>2]=n[dp>>2],n[u+4>>2]=n[dp+4>>2],Un(o,3813,u)|0,n[gp>>2]=8,n[gp+4>>2]=0,n[u>>2]=n[gp>>2],n[u+4>>2]=n[gp+4>>2],Un(o,3825,u)|0,n[Ic>>2]=3,n[Ic+4>>2]=0,n[u>>2]=n[Ic>>2],n[u+4>>2]=n[Ic+4>>2],zr(o,3843,u)|0,n[uo>>2]=4,n[uo+4>>2]=0,n[u>>2]=n[uo>>2],n[u+4>>2]=n[uo+4>>2],zr(o,3853,u)|0,n[Fu>>2]=9,n[Fu+4>>2]=0,n[u>>2]=n[Fu>>2],n[u+4>>2]=n[Fu+4>>2],Un(o,3870,u)|0,n[hp>>2]=10,n[hp+4>>2]=0,n[u>>2]=n[hp>>2],n[u+4>>2]=n[hp+4>>2],Un(o,3884,u)|0,n[pp>>2]=11,n[pp+4>>2]=0,n[u>>2]=n[pp>>2],n[u+4>>2]=n[pp+4>>2],Un(o,3896,u)|0,n[Ru>>2]=1,n[Ru+4>>2]=0,n[u>>2]=n[Ru>>2],n[u+4>>2]=n[Ru+4>>2],ci(o,3907,u)|0,n[Mo>>2]=2,n[Mo+4>>2]=0,n[u>>2]=n[Mo>>2],n[u+4>>2]=n[Mo+4>>2],ci(o,3915,u)|0,n[Tu>>2]=3,n[Tu+4>>2]=0,n[u>>2]=n[Tu>>2],n[u+4>>2]=n[Tu+4>>2],ci(o,3928,u)|0,n[Lo>>2]=4,n[Lo+4>>2]=0,n[u>>2]=n[Lo>>2],n[u+4>>2]=n[Lo+4>>2],ci(o,3948,u)|0,n[Ap>>2]=5,n[Ap+4>>2]=0,n[u>>2]=n[Ap>>2],n[u+4>>2]=n[Ap+4>>2],ci(o,3960,u)|0,n[Wn>>2]=6,n[Wn+4>>2]=0,n[u>>2]=n[Wn>>2],n[u+4>>2]=n[Wn+4>>2],ci(o,3974,u)|0,n[Ec>>2]=7,n[Ec+4>>2]=0,n[u>>2]=n[Ec>>2],n[u+4>>2]=n[Ec+4>>2],ci(o,3983,u)|0,n[Us>>2]=20,n[Us+4>>2]=0,n[u>>2]=n[Us>>2],n[u+4>>2]=n[Us+4>>2],kr(o,3999,u)|0,n[co>>2]=8,n[co+4>>2]=0,n[u>>2]=n[co>>2],n[u+4>>2]=n[co+4>>2],ci(o,4012,u)|0,n[fp>>2]=9,n[fp+4>>2]=0,n[u>>2]=n[fp>>2],n[u+4>>2]=n[fp+4>>2],ci(o,4022,u)|0,n[up>>2]=21,n[up+4>>2]=0,n[u>>2]=n[up>>2],n[u+4>>2]=n[up+4>>2],kr(o,4039,u)|0,n[Mf>>2]=10,n[Mf+4>>2]=0,n[u>>2]=n[Mf>>2],n[u+4>>2]=n[Mf+4>>2],ci(o,4053,u)|0,n[cp>>2]=11,n[cp+4>>2]=0,n[u>>2]=n[cp>>2],n[u+4>>2]=n[cp+4>>2],ci(o,4065,u)|0,n[lp>>2]=12,n[lp+4>>2]=0,n[u>>2]=n[lp>>2],n[u+4>>2]=n[lp+4>>2],ci(o,4084,u)|0,n[yc>>2]=13,n[yc+4>>2]=0,n[u>>2]=n[yc>>2],n[u+4>>2]=n[yc+4>>2],ci(o,4097,u)|0,n[ya>>2]=14,n[ya+4>>2]=0,n[u>>2]=n[ya>>2],n[u+4>>2]=n[ya+4>>2],ci(o,4117,u)|0,n[Ml>>2]=15,n[Ml+4>>2]=0,n[u>>2]=n[Ml>>2],n[u+4>>2]=n[Ml+4>>2],ci(o,4129,u)|0,n[lo>>2]=16,n[lo+4>>2]=0,n[u>>2]=n[lo>>2],n[u+4>>2]=n[lo+4>>2],ci(o,4148,u)|0,n[Qu>>2]=17,n[Qu+4>>2]=0,n[u>>2]=n[Qu>>2],n[u+4>>2]=n[Qu+4>>2],ci(o,4161,u)|0,n[Lf>>2]=18,n[Lf+4>>2]=0,n[u>>2]=n[Lf>>2],n[u+4>>2]=n[Lf+4>>2],ci(o,4181,u)|0,n[Of>>2]=5,n[Of+4>>2]=0,n[u>>2]=n[Of>>2],n[u+4>>2]=n[Of+4>>2],zr(o,4196,u)|0,n[t0>>2]=6,n[t0+4>>2]=0,n[u>>2]=n[t0>>2],n[u+4>>2]=n[t0+4>>2],zr(o,4206,u)|0,n[e0>>2]=7,n[e0+4>>2]=0,n[u>>2]=n[e0>>2],n[u+4>>2]=n[e0+4>>2],zr(o,4217,u)|0,n[ku>>2]=3,n[ku+4>>2]=0,n[u>>2]=n[ku>>2],n[u+4>>2]=n[ku+4>>2],Pu(o,4235,u)|0,n[$h>>2]=1,n[$h+4>>2]=0,n[u>>2]=n[$h>>2],n[u+4>>2]=n[$h+4>>2],fM(o,4251,u)|0,n[mc>>2]=4,n[mc+4>>2]=0,n[u>>2]=n[mc>>2],n[u+4>>2]=n[mc+4>>2],Pu(o,4263,u)|0,n[on>>2]=5,n[on+4>>2]=0,n[u>>2]=n[on>>2],n[u+4>>2]=n[on+4>>2],Pu(o,4279,u)|0,n[Xh>>2]=6,n[Xh+4>>2]=0,n[u>>2]=n[Xh>>2],n[u+4>>2]=n[Xh+4>>2],Pu(o,4293,u)|0,n[Zh>>2]=7,n[Zh+4>>2]=0,n[u>>2]=n[Zh>>2],n[u+4>>2]=n[Zh+4>>2],Pu(o,4306,u)|0,n[zh>>2]=8,n[zh+4>>2]=0,n[u>>2]=n[zh>>2],n[u+4>>2]=n[zh+4>>2],Pu(o,4323,u)|0,n[Nf>>2]=9,n[Nf+4>>2]=0,n[u>>2]=n[Nf>>2],n[u+4>>2]=n[Nf+4>>2],Pu(o,4335,u)|0,n[Ff>>2]=2,n[Ff+4>>2]=0,n[u>>2]=n[Ff>>2],n[u+4>>2]=n[Ff+4>>2],fM(o,4353,u)|0,n[Jh>>2]=12,n[Jh+4>>2]=0,n[u>>2]=n[Jh>>2],n[u+4>>2]=n[Jh+4>>2],sd(o,4363,u)|0,n[dc>>2]=1,n[dc+4>>2]=0,n[u>>2]=n[dc>>2],n[u+4>>2]=n[dc+4>>2],rp(o,4376,u)|0,n[Kh>>2]=2,n[Kh+4>>2]=0,n[u>>2]=n[Kh>>2],n[u+4>>2]=n[Kh+4>>2],rp(o,4388,u)|0,n[Vh>>2]=13,n[Vh+4>>2]=0,n[u>>2]=n[Vh>>2],n[u+4>>2]=n[Vh+4>>2],sd(o,4402,u)|0,n[$a>>2]=14,n[$a+4>>2]=0,n[u>>2]=n[$a>>2],n[u+4>>2]=n[$a+4>>2],sd(o,4411,u)|0,n[Oo>>2]=15,n[Oo+4>>2]=0,n[u>>2]=n[Oo>>2],n[u+4>>2]=n[Oo+4>>2],sd(o,4421,u)|0,n[No>>2]=16,n[No+4>>2]=0,n[u>>2]=n[No>>2],n[u+4>>2]=n[No+4>>2],sd(o,4433,u)|0,n[Fo>>2]=17,n[Fo+4>>2]=0,n[u>>2]=n[Fo>>2],n[u+4>>2]=n[Fo+4>>2],sd(o,4446,u)|0,n[Hn>>2]=18,n[Hn+4>>2]=0,n[u>>2]=n[Hn>>2],n[u+4>>2]=n[Hn+4>>2],sd(o,4458,u)|0,n[cr>>2]=3,n[cr+4>>2]=0,n[u>>2]=n[cr>>2],n[u+4>>2]=n[cr+4>>2],rp(o,4471,u)|0,n[Hr>>2]=1,n[Hr+4>>2]=0,n[u>>2]=n[Hr>>2],n[u+4>>2]=n[Hr+4>>2],_P(o,4486,u)|0,n[Tr>>2]=10,n[Tr+4>>2]=0,n[u>>2]=n[Tr>>2],n[u+4>>2]=n[Tr+4>>2],Pu(o,4496,u)|0,n[$t>>2]=11,n[$t+4>>2]=0,n[u>>2]=n[$t>>2],n[u+4>>2]=n[$t+4>>2],Pu(o,4508,u)|0,n[fr>>2]=3,n[fr+4>>2]=0,n[u>>2]=n[fr>>2],n[u+4>>2]=n[fr+4>>2],fM(o,4519,u)|0,n[qr>>2]=4,n[qr+4>>2]=0,n[u>>2]=n[qr>>2],n[u+4>>2]=n[qr+4>>2],TOe(o,4530,u)|0,n[Lt>>2]=19,n[Lt+4>>2]=0,n[u>>2]=n[Lt>>2],n[u+4>>2]=n[Lt+4>>2],ROe(o,4542,u)|0,n[Ge>>2]=12,n[Ge+4>>2]=0,n[u>>2]=n[Ge>>2],n[u+4>>2]=n[Ge+4>>2],FOe(o,4554,u)|0,n[He>>2]=13,n[He+4>>2]=0,n[u>>2]=n[He>>2],n[u+4>>2]=n[He+4>>2],NOe(o,4568,u)|0,n[ct>>2]=2,n[ct+4>>2]=0,n[u>>2]=n[ct>>2],n[u+4>>2]=n[ct+4>>2],OOe(o,4578,u)|0,n[Ze>>2]=20,n[Ze+4>>2]=0,n[u>>2]=n[Ze>>2],n[u+4>>2]=n[Ze+4>>2],LOe(o,4587,u)|0,n[tt>>2]=22,n[tt+4>>2]=0,n[u>>2]=n[tt>>2],n[u+4>>2]=n[tt+4>>2],kr(o,4602,u)|0,n[Qe>>2]=23,n[Qe+4>>2]=0,n[u>>2]=n[Qe>>2],n[u+4>>2]=n[Qe+4>>2],kr(o,4619,u)|0,n[Le>>2]=14,n[Le+4>>2]=0,n[u>>2]=n[Le>>2],n[u+4>>2]=n[Le+4>>2],MOe(o,4629,u)|0,n[We>>2]=1,n[We+4>>2]=0,n[u>>2]=n[We>>2],n[u+4>>2]=n[We+4>>2],_Oe(o,4637,u)|0,n[ae>>2]=4,n[ae+4>>2]=0,n[u>>2]=n[ae>>2],n[u+4>>2]=n[ae+4>>2],rp(o,4653,u)|0,n[G>>2]=5,n[G+4>>2]=0,n[u>>2]=n[G>>2],n[u+4>>2]=n[G+4>>2],rp(o,4669,u)|0,n[M>>2]=6,n[M+4>>2]=0,n[u>>2]=n[M>>2],n[u+4>>2]=n[M+4>>2],rp(o,4686,u)|0,n[_>>2]=7,n[_+4>>2]=0,n[u>>2]=n[_>>2],n[u+4>>2]=n[_+4>>2],rp(o,4701,u)|0,n[T>>2]=8,n[T+4>>2]=0,n[u>>2]=n[T>>2],n[u+4>>2]=n[T+4>>2],rp(o,4719,u)|0,n[k>>2]=9,n[k+4>>2]=0,n[u>>2]=n[k>>2],n[u+4>>2]=n[k+4>>2],rp(o,4736,u)|0,n[B>>2]=21,n[B+4>>2]=0,n[u>>2]=n[B>>2],n[u+4>>2]=n[B+4>>2],UOe(o,4754,u)|0,n[m>>2]=2,n[m+4>>2]=0,n[u>>2]=n[m>>2],n[u+4>>2]=n[m+4>>2],_P(o,4772,u)|0,n[d>>2]=3,n[d+4>>2]=0,n[u>>2]=n[d>>2],n[u+4>>2]=n[d+4>>2],_P(o,4790,u)|0,n[A>>2]=4,n[A+4>>2]=0,n[u>>2]=n[A>>2],n[u+4>>2]=n[A+4>>2],_P(o,4808,u)|0,I=l}function ht(o,l){o=o|0,l=l|0;var u=0;u=Y8e()|0,n[o>>2]=u,V8e(u,l),Gh(n[o>>2]|0)}function Zt(o,l,u){return o=o|0,l=l|0,u=u|0,T8e(o,Bn(l)|0,u,0),o|0}function Sr(o,l,u){return o=o|0,l=l|0,u=u|0,d8e(o,Bn(l)|0,u,0),o|0}function Xn(o,l,u){return o=o|0,l=l|0,u=u|0,r8e(o,Bn(l)|0,u,0),o|0}function kr(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],U3e(o,l,d),I=A,o|0}function Rn(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],C3e(o,l,d),I=A,o|0}function Un(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],s3e(o,l,d),I=A,o|0}function zr(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],q4e(o,l,d),I=A,o|0}function ci(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],b4e(o,l,d),I=A,o|0}function Pu(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],f4e(o,l,d),I=A,o|0}function fM(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],JUe(o,l,d),I=A,o|0}function sd(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],CUe(o,l,d),I=A,o|0}function rp(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],sUe(o,l,d),I=A,o|0}function _P(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],q_e(o,l,d),I=A,o|0}function TOe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],b_e(o,l,d),I=A,o|0}function ROe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],f_e(o,l,d),I=A,o|0}function FOe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],zMe(o,l,d),I=A,o|0}function NOe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],NMe(o,l,d),I=A,o|0}function OOe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],yMe(o,l,d),I=A,o|0}function LOe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],tMe(o,l,d),I=A,o|0}function MOe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],_Le(o,l,d),I=A,o|0}function _Oe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],CLe(o,l,d),I=A,o|0}function UOe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],HOe(o,l,d),I=A,o|0}function HOe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],jOe(o,u,d,1),I=A}function Bn(o){return o=o|0,o|0}function jOe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=AM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=qOe(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,GOe(m,A)|0,A),I=d}function AM(){var o=0,l=0;if(s[7616]|0||(jz(9136),gr(24,9136,U|0)|0,l=7616,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9136)|0)){o=9136,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));jz(9136)}return 9136}function qOe(o){return o=o|0,0}function GOe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=AM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],Hz(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(VOe(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function vn(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0;var B=0,k=0,T=0,_=0,M=0,G=0,ae=0,We=0;B=I,I=I+32|0,ae=B+24|0,G=B+20|0,T=B+16|0,M=B+12|0,_=B+8|0,k=B+4|0,We=B,n[G>>2]=l,n[T>>2]=u,n[M>>2]=A,n[_>>2]=d,n[k>>2]=m,m=o+28|0,n[We>>2]=n[m>>2],n[ae>>2]=n[We>>2],WOe(o+24|0,ae,G,M,_,T,k)|0,n[m>>2]=n[n[m>>2]>>2],I=B}function WOe(o,l,u,A,d,m,B){return o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,B=B|0,o=YOe(l)|0,l=Jt(24)|0,Uz(l+4|0,n[u>>2]|0,n[A>>2]|0,n[d>>2]|0,n[m>>2]|0,n[B>>2]|0),n[l>>2]=n[o>>2],n[o>>2]=l,l|0}function YOe(o){return o=o|0,n[o>>2]|0}function Uz(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,n[o>>2]=l,n[o+4>>2]=u,n[o+8>>2]=A,n[o+12>>2]=d,n[o+16>>2]=m}function yr(o,l){return o=o|0,l=l|0,l|o|0}function Hz(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function VOe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=KOe(o)|0,m>>>0<d>>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,JOe(k,ae>>>0<m>>>1>>>0?G>>>0<d>>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],Hz(m,A,u),n[T>>2]=(n[T>>2]|0)+12,zOe(o,k),ZOe(k),I=_;return}}function KOe(o){return o=o|0,357913941}function JOe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function zOe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function ZOe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&Et(o)}function jz(o){o=o|0,eLe(o)}function XOe(o){o=o|0,$Oe(o+24|0)}function Ur(o){return o=o|0,n[o>>2]|0}function $Oe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),Et(u))}function eLe(o){o=o|0;var l=0;l=en()|0,tn(o,2,3,l,tLe()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function en(){return 9228}function tLe(){return 1140}function rLe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0;return u=I,I=I+16|0,A=u+8|0,d=u,m=nLe(o)|0,o=n[m+4>>2]|0,n[d>>2]=n[m>>2],n[d+4>>2]=o,n[A>>2]=n[d>>2],n[A+4>>2]=n[d+4>>2],l=iLe(l,A)|0,I=u,l|0}function tn(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,n[o>>2]=l,n[o+4>>2]=u,n[o+8>>2]=A,n[o+12>>2]=d,n[o+16>>2]=m}function nLe(o){return o=o|0,(n[(AM()|0)+24>>2]|0)+(o*12|0)|0}function iLe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0;return d=I,I=I+48|0,A=d,u=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(u=n[(n[o>>2]|0)+u>>2]|0),ap[u&31](A,o),A=sLe(A)|0,I=d,A|0}function sLe(o){o=o|0;var l=0,u=0,A=0,d=0;return d=I,I=I+32|0,l=d+12|0,u=d,A=pM(qz()|0)|0,A?(hM(l,A),gM(u,l),oLe(o,u),o=dM(l)|0):o=aLe(o)|0,I=d,o|0}function qz(){var o=0;return s[7632]|0||(mLe(9184),gr(25,9184,U|0)|0,o=7632,n[o>>2]=1,n[o+4>>2]=0),9184}function pM(o){return o=o|0,n[o+36>>2]|0}function hM(o,l){o=o|0,l=l|0,n[o>>2]=l,n[o+4>>2]=o,n[o+8>>2]=0}function gM(o,l){o=o|0,l=l|0,n[o>>2]=n[l>>2],n[o+4>>2]=n[l+4>>2],n[o+8>>2]=0}function oLe(o,l){o=o|0,l=l|0,fLe(l,o,o+8|0,o+16|0,o+24|0,o+32|0,o+40|0)|0}function dM(o){return o=o|0,n[(n[o+4>>2]|0)+8>>2]|0}function aLe(o){o=o|0;var l=0,u=0,A=0,d=0,m=0,B=0,k=0,T=0;T=I,I=I+16|0,u=T+4|0,A=T,d=Fl(8)|0,m=d,B=Jt(48)|0,k=B,l=k+48|0;do n[k>>2]=n[o>>2],k=k+4|0,o=o+4|0;while((k|0)<(l|0));return l=m+4|0,n[l>>2]=B,k=Jt(8)|0,B=n[l>>2]|0,n[A>>2]=0,n[u>>2]=n[A>>2],Gz(k,B,u),n[d>>2]=k,I=T,m|0}function Gz(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,u=Jt(16)|0,n[u+4>>2]=0,n[u+8>>2]=0,n[u>>2]=1092,n[u+12>>2]=l,n[o+4>>2]=u}function lLe(o){o=o|0,Zy(o),Et(o)}function cLe(o){o=o|0,o=n[o+12>>2]|0,o|0&&Et(o)}function uLe(o){o=o|0,Et(o)}function fLe(o,l,u,A,d,m,B){return o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,B=B|0,m=ALe(n[o>>2]|0,l,u,A,d,m,B)|0,B=o+4|0,n[(n[B>>2]|0)+8>>2]=m,n[(n[B>>2]|0)+8>>2]|0}function ALe(o,l,u,A,d,m,B){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,B=B|0;var k=0,T=0;return k=I,I=I+16|0,T=k,Nl(T),o=Ms(o)|0,B=pLe(o,+E[l>>3],+E[u>>3],+E[A>>3],+E[d>>3],+E[m>>3],+E[B>>3])|0,Ol(T),I=k,B|0}function pLe(o,l,u,A,d,m,B){o=o|0,l=+l,u=+u,A=+A,d=+d,m=+m,B=+B;var k=0;return k=ma(hLe()|0)|0,l=+Ja(l),u=+Ja(u),A=+Ja(A),d=+Ja(d),m=+Ja(m),io(0,k|0,o|0,+l,+u,+A,+d,+m,+ +Ja(B))|0}function hLe(){var o=0;return s[7624]|0||(gLe(9172),o=7624,n[o>>2]=1,n[o+4>>2]=0),9172}function gLe(o){o=o|0,Ro(o,dLe()|0,6)}function dLe(){return 1112}function mLe(o){o=o|0,_h(o)}function yLe(o){o=o|0,Wz(o+24|0),Yz(o+16|0)}function Wz(o){o=o|0,ILe(o)}function Yz(o){o=o|0,ELe(o)}function ELe(o){o=o|0;var l=0,u=0;if(l=n[o>>2]|0,l|0)do u=l,l=n[l>>2]|0,Et(u);while(l|0);n[o>>2]=0}function ILe(o){o=o|0;var l=0,u=0;if(l=n[o>>2]|0,l|0)do u=l,l=n[l>>2]|0,Et(u);while(l|0);n[o>>2]=0}function _h(o){o=o|0;var l=0;n[o+16>>2]=0,n[o+20>>2]=0,l=o+24|0,n[l>>2]=0,n[o+28>>2]=l,n[o+36>>2]=0,s[o+40>>0]=0,s[o+41>>0]=0}function CLe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],wLe(o,u,d,0),I=A}function wLe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=mM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=BLe(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,vLe(m,A)|0,A),I=d}function mM(){var o=0,l=0;if(s[7640]|0||(Kz(9232),gr(26,9232,U|0)|0,l=7640,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9232)|0)){o=9232,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));Kz(9232)}return 9232}function BLe(o){return o=o|0,0}function vLe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=mM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],Vz(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(SLe(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function Vz(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function SLe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=DLe(o)|0,m>>>0<d>>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,bLe(k,ae>>>0<m>>>1>>>0?G>>>0<d>>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],Vz(m,A,u),n[T>>2]=(n[T>>2]|0)+12,PLe(o,k),xLe(k),I=_;return}}function DLe(o){return o=o|0,357913941}function bLe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function PLe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function xLe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&Et(o)}function Kz(o){o=o|0,TLe(o)}function kLe(o){o=o|0,QLe(o+24|0)}function QLe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),Et(u))}function TLe(o){o=o|0;var l=0;l=en()|0,tn(o,2,1,l,RLe()|0,3),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function RLe(){return 1144}function FLe(o,l,u,A,d){o=o|0,l=l|0,u=+u,A=+A,d=d|0;var m=0,B=0,k=0,T=0;m=I,I=I+16|0,B=m+8|0,k=m,T=NLe(o)|0,o=n[T+4>>2]|0,n[k>>2]=n[T>>2],n[k+4>>2]=o,n[B>>2]=n[k>>2],n[B+4>>2]=n[k+4>>2],OLe(l,B,u,A,d),I=m}function NLe(o){return o=o|0,(n[(mM()|0)+24>>2]|0)+(o*12|0)|0}function OLe(o,l,u,A,d){o=o|0,l=l|0,u=+u,A=+A,d=d|0;var m=0,B=0,k=0,T=0,_=0;_=I,I=I+16|0,B=_+2|0,k=_+1|0,T=_,m=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(m=n[(n[o>>2]|0)+m>>2]|0),Tf(B,u),u=+Rf(B,u),Tf(k,A),A=+Rf(k,A),np(T,d),T=ip(T,d)|0,c$[m&1](o,u,A,T),I=_}function Tf(o,l){o=o|0,l=+l}function Rf(o,l){return o=o|0,l=+l,+ +MLe(l)}function np(o,l){o=o|0,l=l|0}function ip(o,l){return o=o|0,l=l|0,LLe(l)|0}function LLe(o){return o=o|0,o|0}function MLe(o){return o=+o,+o}function _Le(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],ULe(o,u,d,1),I=A}function ULe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=yM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=HLe(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,jLe(m,A)|0,A),I=d}function yM(){var o=0,l=0;if(s[7648]|0||(zz(9268),gr(27,9268,U|0)|0,l=7648,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9268)|0)){o=9268,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));zz(9268)}return 9268}function HLe(o){return o=o|0,0}function jLe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=yM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],Jz(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(qLe(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function Jz(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function qLe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=GLe(o)|0,m>>>0<d>>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,WLe(k,ae>>>0<m>>>1>>>0?G>>>0<d>>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],Jz(m,A,u),n[T>>2]=(n[T>>2]|0)+12,YLe(o,k),VLe(k),I=_;return}}function GLe(o){return o=o|0,357913941}function WLe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function YLe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function VLe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&Et(o)}function zz(o){o=o|0,zLe(o)}function KLe(o){o=o|0,JLe(o+24|0)}function JLe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),Et(u))}function zLe(o){o=o|0;var l=0;l=en()|0,tn(o,2,4,l,ZLe()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function ZLe(){return 1160}function XLe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0;return u=I,I=I+16|0,A=u+8|0,d=u,m=$Le(o)|0,o=n[m+4>>2]|0,n[d>>2]=n[m>>2],n[d+4>>2]=o,n[A>>2]=n[d>>2],n[A+4>>2]=n[d+4>>2],l=eMe(l,A)|0,I=u,l|0}function $Le(o){return o=o|0,(n[(yM()|0)+24>>2]|0)+(o*12|0)|0}function eMe(o,l){o=o|0,l=l|0;var u=0;return u=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(u=n[(n[o>>2]|0)+u>>2]|0),Zz(hd[u&31](o)|0)|0}function Zz(o){return o=o|0,o&1|0}function tMe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],rMe(o,u,d,0),I=A}function rMe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=EM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=nMe(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,iMe(m,A)|0,A),I=d}function EM(){var o=0,l=0;if(s[7656]|0||($z(9304),gr(28,9304,U|0)|0,l=7656,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9304)|0)){o=9304,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));$z(9304)}return 9304}function nMe(o){return o=o|0,0}function iMe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=EM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],Xz(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(sMe(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function Xz(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function sMe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=oMe(o)|0,m>>>0<d>>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,aMe(k,ae>>>0<m>>>1>>>0?G>>>0<d>>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],Xz(m,A,u),n[T>>2]=(n[T>>2]|0)+12,lMe(o,k),cMe(k),I=_;return}}function oMe(o){return o=o|0,357913941}function aMe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function lMe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function cMe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&Et(o)}function $z(o){o=o|0,AMe(o)}function uMe(o){o=o|0,fMe(o+24|0)}function fMe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),Et(u))}function AMe(o){o=o|0;var l=0;l=en()|0,tn(o,2,5,l,pMe()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function pMe(){return 1164}function hMe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;A=I,I=I+16|0,d=A+8|0,m=A,B=gMe(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],dMe(l,d,u),I=A}function gMe(o){return o=o|0,(n[(EM()|0)+24>>2]|0)+(o*12|0)|0}function dMe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;m=I,I=I+16|0,d=m,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),Uh(d,u),u=Hh(d,u)|0,ap[A&31](o,u),jh(d),I=m}function Uh(o,l){o=o|0,l=l|0,mMe(o,l)}function Hh(o,l){return o=o|0,l=l|0,o|0}function jh(o){o=o|0,Df(o)}function mMe(o,l){o=o|0,l=l|0,IM(o,l)}function IM(o,l){o=o|0,l=l|0,n[o>>2]=l}function yMe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],EMe(o,u,d,0),I=A}function EMe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=CM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=IMe(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,CMe(m,A)|0,A),I=d}function CM(){var o=0,l=0;if(s[7664]|0||(tZ(9340),gr(29,9340,U|0)|0,l=7664,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9340)|0)){o=9340,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));tZ(9340)}return 9340}function IMe(o){return o=o|0,0}function CMe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=CM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],eZ(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(wMe(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function eZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function wMe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=BMe(o)|0,m>>>0<d>>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,vMe(k,ae>>>0<m>>>1>>>0?G>>>0<d>>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],eZ(m,A,u),n[T>>2]=(n[T>>2]|0)+12,SMe(o,k),DMe(k),I=_;return}}function BMe(o){return o=o|0,357913941}function vMe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function SMe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function DMe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&Et(o)}function tZ(o){o=o|0,xMe(o)}function bMe(o){o=o|0,PMe(o+24|0)}function PMe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),Et(u))}function xMe(o){o=o|0;var l=0;l=en()|0,tn(o,2,4,l,kMe()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function kMe(){return 1180}function QMe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=TMe(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],u=RMe(l,d,u)|0,I=A,u|0}function TMe(o){return o=o|0,(n[(CM()|0)+24>>2]|0)+(o*12|0)|0}function RMe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;return m=I,I=I+16|0,d=m,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),od(d,u),d=ad(d,u)|0,d=UP(m_[A&15](o,d)|0)|0,I=m,d|0}function od(o,l){o=o|0,l=l|0}function ad(o,l){return o=o|0,l=l|0,FMe(l)|0}function UP(o){return o=o|0,o|0}function FMe(o){return o=o|0,o|0}function NMe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],OMe(o,u,d,0),I=A}function OMe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=wM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=LMe(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,MMe(m,A)|0,A),I=d}function wM(){var o=0,l=0;if(s[7672]|0||(nZ(9376),gr(30,9376,U|0)|0,l=7672,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9376)|0)){o=9376,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));nZ(9376)}return 9376}function LMe(o){return o=o|0,0}function MMe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=wM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],rZ(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(_Me(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function rZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function _Me(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=UMe(o)|0,m>>>0<d>>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,HMe(k,ae>>>0<m>>>1>>>0?G>>>0<d>>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],rZ(m,A,u),n[T>>2]=(n[T>>2]|0)+12,jMe(o,k),qMe(k),I=_;return}}function UMe(o){return o=o|0,357913941}function HMe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function jMe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function qMe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&Et(o)}function nZ(o){o=o|0,YMe(o)}function GMe(o){o=o|0,WMe(o+24|0)}function WMe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),Et(u))}function YMe(o){o=o|0;var l=0;l=en()|0,tn(o,2,5,l,iZ()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function iZ(){return 1196}function VMe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0;return u=I,I=I+16|0,A=u+8|0,d=u,m=KMe(o)|0,o=n[m+4>>2]|0,n[d>>2]=n[m>>2],n[d+4>>2]=o,n[A>>2]=n[d>>2],n[A+4>>2]=n[d+4>>2],l=JMe(l,A)|0,I=u,l|0}function KMe(o){return o=o|0,(n[(wM()|0)+24>>2]|0)+(o*12|0)|0}function JMe(o,l){o=o|0,l=l|0;var u=0;return u=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(u=n[(n[o>>2]|0)+u>>2]|0),UP(hd[u&31](o)|0)|0}function zMe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],ZMe(o,u,d,1),I=A}function ZMe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=BM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=XMe(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,$Me(m,A)|0,A),I=d}function BM(){var o=0,l=0;if(s[7680]|0||(oZ(9412),gr(31,9412,U|0)|0,l=7680,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9412)|0)){o=9412,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));oZ(9412)}return 9412}function XMe(o){return o=o|0,0}function $Me(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=BM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],sZ(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(e_e(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function sZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function e_e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=t_e(o)|0,m>>>0<d>>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,r_e(k,ae>>>0<m>>>1>>>0?G>>>0<d>>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],sZ(m,A,u),n[T>>2]=(n[T>>2]|0)+12,n_e(o,k),i_e(k),I=_;return}}function t_e(o){return o=o|0,357913941}function r_e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function n_e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function i_e(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&Et(o)}function oZ(o){o=o|0,a_e(o)}function s_e(o){o=o|0,o_e(o+24|0)}function o_e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),Et(u))}function a_e(o){o=o|0;var l=0;l=en()|0,tn(o,2,6,l,aZ()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function aZ(){return 1200}function l_e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0;return u=I,I=I+16|0,A=u+8|0,d=u,m=c_e(o)|0,o=n[m+4>>2]|0,n[d>>2]=n[m>>2],n[d+4>>2]=o,n[A>>2]=n[d>>2],n[A+4>>2]=n[d+4>>2],l=u_e(l,A)|0,I=u,l|0}function c_e(o){return o=o|0,(n[(BM()|0)+24>>2]|0)+(o*12|0)|0}function u_e(o,l){o=o|0,l=l|0;var u=0;return u=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(u=n[(n[o>>2]|0)+u>>2]|0),HP(hd[u&31](o)|0)|0}function HP(o){return o=o|0,o|0}function f_e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],A_e(o,u,d,0),I=A}function A_e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=vM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=p_e(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,h_e(m,A)|0,A),I=d}function vM(){var o=0,l=0;if(s[7688]|0||(cZ(9448),gr(32,9448,U|0)|0,l=7688,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9448)|0)){o=9448,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));cZ(9448)}return 9448}function p_e(o){return o=o|0,0}function h_e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=vM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],lZ(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(g_e(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function lZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function g_e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=d_e(o)|0,m>>>0<d>>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,m_e(k,ae>>>0<m>>>1>>>0?G>>>0<d>>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],lZ(m,A,u),n[T>>2]=(n[T>>2]|0)+12,y_e(o,k),E_e(k),I=_;return}}function d_e(o){return o=o|0,357913941}function m_e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function y_e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function E_e(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&Et(o)}function cZ(o){o=o|0,w_e(o)}function I_e(o){o=o|0,C_e(o+24|0)}function C_e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),Et(u))}function w_e(o){o=o|0;var l=0;l=en()|0,tn(o,2,6,l,uZ()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function uZ(){return 1204}function B_e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;A=I,I=I+16|0,d=A+8|0,m=A,B=v_e(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],S_e(l,d,u),I=A}function v_e(o){return o=o|0,(n[(vM()|0)+24>>2]|0)+(o*12|0)|0}function S_e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;m=I,I=I+16|0,d=m,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),SM(d,u),d=DM(d,u)|0,ap[A&31](o,d),I=m}function SM(o,l){o=o|0,l=l|0}function DM(o,l){return o=o|0,l=l|0,D_e(l)|0}function D_e(o){return o=o|0,o|0}function b_e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],P_e(o,u,d,0),I=A}function P_e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=bM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=x_e(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,k_e(m,A)|0,A),I=d}function bM(){var o=0,l=0;if(s[7696]|0||(AZ(9484),gr(33,9484,U|0)|0,l=7696,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9484)|0)){o=9484,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));AZ(9484)}return 9484}function x_e(o){return o=o|0,0}function k_e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=bM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],fZ(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(Q_e(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function fZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function Q_e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=T_e(o)|0,m>>>0<d>>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,R_e(k,ae>>>0<m>>>1>>>0?G>>>0<d>>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],fZ(m,A,u),n[T>>2]=(n[T>>2]|0)+12,F_e(o,k),N_e(k),I=_;return}}function T_e(o){return o=o|0,357913941}function R_e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function F_e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function N_e(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&Et(o)}function AZ(o){o=o|0,M_e(o)}function O_e(o){o=o|0,L_e(o+24|0)}function L_e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),Et(u))}function M_e(o){o=o|0;var l=0;l=en()|0,tn(o,2,1,l,__e()|0,2),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function __e(){return 1212}function U_e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0;d=I,I=I+16|0,m=d+8|0,B=d,k=H_e(o)|0,o=n[k+4>>2]|0,n[B>>2]=n[k>>2],n[B+4>>2]=o,n[m>>2]=n[B>>2],n[m+4>>2]=n[B+4>>2],j_e(l,m,u,A),I=d}function H_e(o){return o=o|0,(n[(bM()|0)+24>>2]|0)+(o*12|0)|0}function j_e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0;k=I,I=I+16|0,m=k+1|0,B=k,d=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(d=n[(n[o>>2]|0)+d>>2]|0),SM(m,u),m=DM(m,u)|0,od(B,A),B=ad(B,A)|0,F2[d&15](o,m,B),I=k}function q_e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],G_e(o,u,d,1),I=A}function G_e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=PM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=W_e(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,Y_e(m,A)|0,A),I=d}function PM(){var o=0,l=0;if(s[7704]|0||(hZ(9520),gr(34,9520,U|0)|0,l=7704,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9520)|0)){o=9520,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));hZ(9520)}return 9520}function W_e(o){return o=o|0,0}function Y_e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=PM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],pZ(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(V_e(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function pZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function V_e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=K_e(o)|0,m>>>0<d>>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,J_e(k,ae>>>0<m>>>1>>>0?G>>>0<d>>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],pZ(m,A,u),n[T>>2]=(n[T>>2]|0)+12,z_e(o,k),Z_e(k),I=_;return}}function K_e(o){return o=o|0,357913941}function J_e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function z_e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function Z_e(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&Et(o)}function hZ(o){o=o|0,eUe(o)}function X_e(o){o=o|0,$_e(o+24|0)}function $_e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),Et(u))}function eUe(o){o=o|0;var l=0;l=en()|0,tn(o,2,1,l,tUe()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function tUe(){return 1224}function rUe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;return d=I,I=I+16|0,m=d+8|0,B=d,k=nUe(o)|0,o=n[k+4>>2]|0,n[B>>2]=n[k>>2],n[B+4>>2]=o,n[m>>2]=n[B>>2],n[m+4>>2]=n[B+4>>2],A=+iUe(l,m,u),I=d,+A}function nUe(o){return o=o|0,(n[(PM()|0)+24>>2]|0)+(o*12|0)|0}function iUe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return m=I,I=I+16|0,d=m,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),np(d,u),d=ip(d,u)|0,B=+Qf(+f$[A&7](o,d)),I=m,+B}function sUe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],oUe(o,u,d,1),I=A}function oUe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=xM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=aUe(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,lUe(m,A)|0,A),I=d}function xM(){var o=0,l=0;if(s[7712]|0||(dZ(9556),gr(35,9556,U|0)|0,l=7712,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9556)|0)){o=9556,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));dZ(9556)}return 9556}function aUe(o){return o=o|0,0}function lUe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=xM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],gZ(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(cUe(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function gZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function cUe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=uUe(o)|0,m>>>0<d>>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,fUe(k,ae>>>0<m>>>1>>>0?G>>>0<d>>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],gZ(m,A,u),n[T>>2]=(n[T>>2]|0)+12,AUe(o,k),pUe(k),I=_;return}}function uUe(o){return o=o|0,357913941}function fUe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function AUe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function pUe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&Et(o)}function dZ(o){o=o|0,dUe(o)}function hUe(o){o=o|0,gUe(o+24|0)}function gUe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),Et(u))}function dUe(o){o=o|0;var l=0;l=en()|0,tn(o,2,5,l,mUe()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function mUe(){return 1232}function yUe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=EUe(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],u=+IUe(l,d),I=A,+u}function EUe(o){return o=o|0,(n[(xM()|0)+24>>2]|0)+(o*12|0)|0}function IUe(o,l){o=o|0,l=l|0;var u=0;return u=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(u=n[(n[o>>2]|0)+u>>2]|0),+ +Qf(+u$[u&15](o))}function CUe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],wUe(o,u,d,1),I=A}function wUe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=kM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=BUe(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,vUe(m,A)|0,A),I=d}function kM(){var o=0,l=0;if(s[7720]|0||(yZ(9592),gr(36,9592,U|0)|0,l=7720,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9592)|0)){o=9592,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));yZ(9592)}return 9592}function BUe(o){return o=o|0,0}function vUe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=kM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],mZ(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(SUe(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function mZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function SUe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=DUe(o)|0,m>>>0<d>>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,bUe(k,ae>>>0<m>>>1>>>0?G>>>0<d>>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],mZ(m,A,u),n[T>>2]=(n[T>>2]|0)+12,PUe(o,k),xUe(k),I=_;return}}function DUe(o){return o=o|0,357913941}function bUe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function PUe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function xUe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&Et(o)}function yZ(o){o=o|0,TUe(o)}function kUe(o){o=o|0,QUe(o+24|0)}function QUe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),Et(u))}function TUe(o){o=o|0;var l=0;l=en()|0,tn(o,2,7,l,RUe()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function RUe(){return 1276}function FUe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0;return u=I,I=I+16|0,A=u+8|0,d=u,m=NUe(o)|0,o=n[m+4>>2]|0,n[d>>2]=n[m>>2],n[d+4>>2]=o,n[A>>2]=n[d>>2],n[A+4>>2]=n[d+4>>2],l=OUe(l,A)|0,I=u,l|0}function NUe(o){return o=o|0,(n[(kM()|0)+24>>2]|0)+(o*12|0)|0}function OUe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0;return d=I,I=I+16|0,A=d,u=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(u=n[(n[o>>2]|0)+u>>2]|0),ap[u&31](A,o),A=EZ(A)|0,I=d,A|0}function EZ(o){o=o|0;var l=0,u=0,A=0,d=0;return d=I,I=I+32|0,l=d+12|0,u=d,A=pM(IZ()|0)|0,A?(hM(l,A),gM(u,l),LUe(o,u),o=dM(l)|0):o=MUe(o)|0,I=d,o|0}function IZ(){var o=0;return s[7736]|0||(KUe(9640),gr(25,9640,U|0)|0,o=7736,n[o>>2]=1,n[o+4>>2]=0),9640}function LUe(o,l){o=o|0,l=l|0,jUe(l,o,o+8|0)|0}function MUe(o){o=o|0;var l=0,u=0,A=0,d=0,m=0,B=0,k=0;return u=I,I=I+16|0,d=u+4|0,B=u,A=Fl(8)|0,l=A,k=Jt(16)|0,n[k>>2]=n[o>>2],n[k+4>>2]=n[o+4>>2],n[k+8>>2]=n[o+8>>2],n[k+12>>2]=n[o+12>>2],m=l+4|0,n[m>>2]=k,o=Jt(8)|0,m=n[m>>2]|0,n[B>>2]=0,n[d>>2]=n[B>>2],QM(o,m,d),n[A>>2]=o,I=u,l|0}function QM(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,u=Jt(16)|0,n[u+4>>2]=0,n[u+8>>2]=0,n[u>>2]=1244,n[u+12>>2]=l,n[o+4>>2]=u}function _Ue(o){o=o|0,Zy(o),Et(o)}function UUe(o){o=o|0,o=n[o+12>>2]|0,o|0&&Et(o)}function HUe(o){o=o|0,Et(o)}function jUe(o,l,u){return o=o|0,l=l|0,u=u|0,l=qUe(n[o>>2]|0,l,u)|0,u=o+4|0,n[(n[u>>2]|0)+8>>2]=l,n[(n[u>>2]|0)+8>>2]|0}function qUe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0;return A=I,I=I+16|0,d=A,Nl(d),o=Ms(o)|0,u=GUe(o,n[l>>2]|0,+E[u>>3])|0,Ol(d),I=A,u|0}function GUe(o,l,u){o=o|0,l=l|0,u=+u;var A=0;return A=ma(WUe()|0)|0,l=Gy(l)|0,lu(0,A|0,o|0,l|0,+ +Ja(u))|0}function WUe(){var o=0;return s[7728]|0||(YUe(9628),o=7728,n[o>>2]=1,n[o+4>>2]=0),9628}function YUe(o){o=o|0,Ro(o,VUe()|0,2)}function VUe(){return 1264}function KUe(o){o=o|0,_h(o)}function JUe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],zUe(o,u,d,1),I=A}function zUe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=TM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=ZUe(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,XUe(m,A)|0,A),I=d}function TM(){var o=0,l=0;if(s[7744]|0||(wZ(9684),gr(37,9684,U|0)|0,l=7744,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9684)|0)){o=9684,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));wZ(9684)}return 9684}function ZUe(o){return o=o|0,0}function XUe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=TM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],CZ(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):($Ue(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function CZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function $Ue(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=e4e(o)|0,m>>>0<d>>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,t4e(k,ae>>>0<m>>>1>>>0?G>>>0<d>>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],CZ(m,A,u),n[T>>2]=(n[T>>2]|0)+12,r4e(o,k),n4e(k),I=_;return}}function e4e(o){return o=o|0,357913941}function t4e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function r4e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function n4e(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&Et(o)}function wZ(o){o=o|0,o4e(o)}function i4e(o){o=o|0,s4e(o+24|0)}function s4e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),Et(u))}function o4e(o){o=o|0;var l=0;l=en()|0,tn(o,2,5,l,a4e()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function a4e(){return 1280}function l4e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=c4e(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],u=u4e(l,d,u)|0,I=A,u|0}function c4e(o){return o=o|0,(n[(TM()|0)+24>>2]|0)+(o*12|0)|0}function u4e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return B=I,I=I+32|0,d=B,m=B+16|0,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),np(m,u),m=ip(m,u)|0,F2[A&15](d,o,m),m=EZ(d)|0,I=B,m|0}function f4e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],A4e(o,u,d,1),I=A}function A4e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=RM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=p4e(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,h4e(m,A)|0,A),I=d}function RM(){var o=0,l=0;if(s[7752]|0||(vZ(9720),gr(38,9720,U|0)|0,l=7752,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9720)|0)){o=9720,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));vZ(9720)}return 9720}function p4e(o){return o=o|0,0}function h4e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=RM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],BZ(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(g4e(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function BZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function g4e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=d4e(o)|0,m>>>0<d>>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,m4e(k,ae>>>0<m>>>1>>>0?G>>>0<d>>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],BZ(m,A,u),n[T>>2]=(n[T>>2]|0)+12,y4e(o,k),E4e(k),I=_;return}}function d4e(o){return o=o|0,357913941}function m4e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function y4e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function E4e(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&Et(o)}function vZ(o){o=o|0,w4e(o)}function I4e(o){o=o|0,C4e(o+24|0)}function C4e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),Et(u))}function w4e(o){o=o|0;var l=0;l=en()|0,tn(o,2,8,l,B4e()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function B4e(){return 1288}function v4e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0;return u=I,I=I+16|0,A=u+8|0,d=u,m=S4e(o)|0,o=n[m+4>>2]|0,n[d>>2]=n[m>>2],n[d+4>>2]=o,n[A>>2]=n[d>>2],n[A+4>>2]=n[d+4>>2],l=D4e(l,A)|0,I=u,l|0}function S4e(o){return o=o|0,(n[(RM()|0)+24>>2]|0)+(o*12|0)|0}function D4e(o,l){o=o|0,l=l|0;var u=0;return u=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(u=n[(n[o>>2]|0)+u>>2]|0),id(hd[u&31](o)|0)|0}function b4e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],P4e(o,u,d,0),I=A}function P4e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=FM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=x4e(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,k4e(m,A)|0,A),I=d}function FM(){var o=0,l=0;if(s[7760]|0||(DZ(9756),gr(39,9756,U|0)|0,l=7760,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9756)|0)){o=9756,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));DZ(9756)}return 9756}function x4e(o){return o=o|0,0}function k4e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=FM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],SZ(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(Q4e(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function SZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function Q4e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=T4e(o)|0,m>>>0<d>>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,R4e(k,ae>>>0<m>>>1>>>0?G>>>0<d>>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],SZ(m,A,u),n[T>>2]=(n[T>>2]|0)+12,F4e(o,k),N4e(k),I=_;return}}function T4e(o){return o=o|0,357913941}function R4e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function F4e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function N4e(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&Et(o)}function DZ(o){o=o|0,M4e(o)}function O4e(o){o=o|0,L4e(o+24|0)}function L4e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),Et(u))}function M4e(o){o=o|0;var l=0;l=en()|0,tn(o,2,8,l,_4e()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function _4e(){return 1292}function U4e(o,l,u){o=o|0,l=l|0,u=+u;var A=0,d=0,m=0,B=0;A=I,I=I+16|0,d=A+8|0,m=A,B=H4e(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],j4e(l,d,u),I=A}function H4e(o){return o=o|0,(n[(FM()|0)+24>>2]|0)+(o*12|0)|0}function j4e(o,l,u){o=o|0,l=l|0,u=+u;var A=0,d=0,m=0;m=I,I=I+16|0,d=m,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),Tf(d,u),u=+Rf(d,u),a$[A&31](o,u),I=m}function q4e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],G4e(o,u,d,0),I=A}function G4e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=NM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=W4e(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,Y4e(m,A)|0,A),I=d}function NM(){var o=0,l=0;if(s[7768]|0||(PZ(9792),gr(40,9792,U|0)|0,l=7768,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9792)|0)){o=9792,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));PZ(9792)}return 9792}function W4e(o){return o=o|0,0}function Y4e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=NM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],bZ(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(V4e(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function bZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function V4e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=K4e(o)|0,m>>>0<d>>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,J4e(k,ae>>>0<m>>>1>>>0?G>>>0<d>>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],bZ(m,A,u),n[T>>2]=(n[T>>2]|0)+12,z4e(o,k),Z4e(k),I=_;return}}function K4e(o){return o=o|0,357913941}function J4e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function z4e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function Z4e(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&Et(o)}function PZ(o){o=o|0,e3e(o)}function X4e(o){o=o|0,$4e(o+24|0)}function $4e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),Et(u))}function e3e(o){o=o|0;var l=0;l=en()|0,tn(o,2,1,l,t3e()|0,2),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function t3e(){return 1300}function r3e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=+A;var d=0,m=0,B=0,k=0;d=I,I=I+16|0,m=d+8|0,B=d,k=n3e(o)|0,o=n[k+4>>2]|0,n[B>>2]=n[k>>2],n[B+4>>2]=o,n[m>>2]=n[B>>2],n[m+4>>2]=n[B+4>>2],i3e(l,m,u,A),I=d}function n3e(o){return o=o|0,(n[(NM()|0)+24>>2]|0)+(o*12|0)|0}function i3e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=+A;var d=0,m=0,B=0,k=0;k=I,I=I+16|0,m=k+1|0,B=k,d=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(d=n[(n[o>>2]|0)+d>>2]|0),np(m,u),m=ip(m,u)|0,Tf(B,A),A=+Rf(B,A),g$[d&15](o,m,A),I=k}function s3e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],o3e(o,u,d,0),I=A}function o3e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=OM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=a3e(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,l3e(m,A)|0,A),I=d}function OM(){var o=0,l=0;if(s[7776]|0||(kZ(9828),gr(41,9828,U|0)|0,l=7776,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9828)|0)){o=9828,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));kZ(9828)}return 9828}function a3e(o){return o=o|0,0}function l3e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=OM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],xZ(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(c3e(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function xZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function c3e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=u3e(o)|0,m>>>0<d>>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,f3e(k,ae>>>0<m>>>1>>>0?G>>>0<d>>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],xZ(m,A,u),n[T>>2]=(n[T>>2]|0)+12,A3e(o,k),p3e(k),I=_;return}}function u3e(o){return o=o|0,357913941}function f3e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function A3e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function p3e(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&Et(o)}function kZ(o){o=o|0,d3e(o)}function h3e(o){o=o|0,g3e(o+24|0)}function g3e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),Et(u))}function d3e(o){o=o|0;var l=0;l=en()|0,tn(o,2,7,l,m3e()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function m3e(){return 1312}function y3e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;A=I,I=I+16|0,d=A+8|0,m=A,B=E3e(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],I3e(l,d,u),I=A}function E3e(o){return o=o|0,(n[(OM()|0)+24>>2]|0)+(o*12|0)|0}function I3e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;m=I,I=I+16|0,d=m,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),np(d,u),d=ip(d,u)|0,ap[A&31](o,d),I=m}function C3e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],w3e(o,u,d,0),I=A}function w3e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=LM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=B3e(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,v3e(m,A)|0,A),I=d}function LM(){var o=0,l=0;if(s[7784]|0||(TZ(9864),gr(42,9864,U|0)|0,l=7784,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9864)|0)){o=9864,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));TZ(9864)}return 9864}function B3e(o){return o=o|0,0}function v3e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=LM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],QZ(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(S3e(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function QZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function S3e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=D3e(o)|0,m>>>0<d>>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,b3e(k,ae>>>0<m>>>1>>>0?G>>>0<d>>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],QZ(m,A,u),n[T>>2]=(n[T>>2]|0)+12,P3e(o,k),x3e(k),I=_;return}}function D3e(o){return o=o|0,357913941}function b3e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function P3e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function x3e(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&Et(o)}function TZ(o){o=o|0,T3e(o)}function k3e(o){o=o|0,Q3e(o+24|0)}function Q3e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),Et(u))}function T3e(o){o=o|0;var l=0;l=en()|0,tn(o,2,8,l,R3e()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function R3e(){return 1320}function F3e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;A=I,I=I+16|0,d=A+8|0,m=A,B=N3e(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],O3e(l,d,u),I=A}function N3e(o){return o=o|0,(n[(LM()|0)+24>>2]|0)+(o*12|0)|0}function O3e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;m=I,I=I+16|0,d=m,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),L3e(d,u),d=M3e(d,u)|0,ap[A&31](o,d),I=m}function L3e(o,l){o=o|0,l=l|0}function M3e(o,l){return o=o|0,l=l|0,_3e(l)|0}function _3e(o){return o=o|0,o|0}function U3e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],H3e(o,u,d,0),I=A}function H3e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=MM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=j3e(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,q3e(m,A)|0,A),I=d}function MM(){var o=0,l=0;if(s[7792]|0||(FZ(9900),gr(43,9900,U|0)|0,l=7792,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9900)|0)){o=9900,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));FZ(9900)}return 9900}function j3e(o){return o=o|0,0}function q3e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=MM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],RZ(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(G3e(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function RZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function G3e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=W3e(o)|0,m>>>0<d>>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,Y3e(k,ae>>>0<m>>>1>>>0?G>>>0<d>>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],RZ(m,A,u),n[T>>2]=(n[T>>2]|0)+12,V3e(o,k),K3e(k),I=_;return}}function W3e(o){return o=o|0,357913941}function Y3e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function V3e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function K3e(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&Et(o)}function FZ(o){o=o|0,Z3e(o)}function J3e(o){o=o|0,z3e(o+24|0)}function z3e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),Et(u))}function Z3e(o){o=o|0;var l=0;l=en()|0,tn(o,2,22,l,X3e()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function X3e(){return 1344}function $3e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0;u=I,I=I+16|0,A=u+8|0,d=u,m=e8e(o)|0,o=n[m+4>>2]|0,n[d>>2]=n[m>>2],n[d+4>>2]=o,n[A>>2]=n[d>>2],n[A+4>>2]=n[d+4>>2],t8e(l,A),I=u}function e8e(o){return o=o|0,(n[(MM()|0)+24>>2]|0)+(o*12|0)|0}function t8e(o,l){o=o|0,l=l|0;var u=0;u=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(u=n[(n[o>>2]|0)+u>>2]|0),op[u&127](o)}function r8e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=_M()|0,o=n8e(u)|0,vn(m,l,d,o,i8e(u,A)|0,A)}function _M(){var o=0,l=0;if(s[7800]|0||(OZ(9936),gr(44,9936,U|0)|0,l=7800,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9936)|0)){o=9936,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));OZ(9936)}return 9936}function n8e(o){return o=o|0,o|0}function i8e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=_M()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(NZ(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(s8e(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function NZ(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function s8e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=o8e(o)|0,A>>>0<B>>>0)sn(o);else{T=n[o>>2]|0,M=(n[o+8>>2]|0)-T|0,_=M>>2,a8e(d,M>>3>>>0<A>>>1>>>0?_>>>0<B>>>0?B:_:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,NZ(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,l8e(o,d),c8e(d),I=k;return}}function o8e(o){return o=o|0,536870911}function a8e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Jt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function l8e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function c8e(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&Et(o)}function OZ(o){o=o|0,A8e(o)}function u8e(o){o=o|0,f8e(o+24|0)}function f8e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),Et(u))}function A8e(o){o=o|0;var l=0;l=en()|0,tn(o,1,23,l,uZ()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function p8e(o,l){o=o|0,l=l|0,g8e(n[(h8e(o)|0)>>2]|0,l)}function h8e(o){return o=o|0,(n[(_M()|0)+24>>2]|0)+(o<<3)|0}function g8e(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,SM(A,l),l=DM(A,l)|0,op[o&127](l),I=u}function d8e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=UM()|0,o=m8e(u)|0,vn(m,l,d,o,y8e(u,A)|0,A)}function UM(){var o=0,l=0;if(s[7808]|0||(MZ(9972),gr(45,9972,U|0)|0,l=7808,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9972)|0)){o=9972,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));MZ(9972)}return 9972}function m8e(o){return o=o|0,o|0}function y8e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=UM()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(LZ(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(E8e(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function LZ(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function E8e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=I8e(o)|0,A>>>0<B>>>0)sn(o);else{T=n[o>>2]|0,M=(n[o+8>>2]|0)-T|0,_=M>>2,C8e(d,M>>3>>>0<A>>>1>>>0?_>>>0<B>>>0?B:_:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,LZ(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,w8e(o,d),B8e(d),I=k;return}}function I8e(o){return o=o|0,536870911}function C8e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Jt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function w8e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function B8e(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&Et(o)}function MZ(o){o=o|0,D8e(o)}function v8e(o){o=o|0,S8e(o+24|0)}function S8e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),Et(u))}function D8e(o){o=o|0;var l=0;l=en()|0,tn(o,1,9,l,b8e()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function b8e(){return 1348}function P8e(o,l){return o=o|0,l=l|0,k8e(n[(x8e(o)|0)>>2]|0,l)|0}function x8e(o){return o=o|0,(n[(UM()|0)+24>>2]|0)+(o<<3)|0}function k8e(o,l){o=o|0,l=l|0;var u=0,A=0;return u=I,I=I+16|0,A=u,_Z(A,l),l=UZ(A,l)|0,l=UP(hd[o&31](l)|0)|0,I=u,l|0}function _Z(o,l){o=o|0,l=l|0}function UZ(o,l){return o=o|0,l=l|0,Q8e(l)|0}function Q8e(o){return o=o|0,o|0}function T8e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=HM()|0,o=R8e(u)|0,vn(m,l,d,o,F8e(u,A)|0,A)}function HM(){var o=0,l=0;if(s[7816]|0||(jZ(10008),gr(46,10008,U|0)|0,l=7816,n[l>>2]=1,n[l+4>>2]=0),!(Ur(10008)|0)){o=10008,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));jZ(10008)}return 10008}function R8e(o){return o=o|0,o|0}function F8e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=HM()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(HZ(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(N8e(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function HZ(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function N8e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=O8e(o)|0,A>>>0<B>>>0)sn(o);else{T=n[o>>2]|0,M=(n[o+8>>2]|0)-T|0,_=M>>2,L8e(d,M>>3>>>0<A>>>1>>>0?_>>>0<B>>>0?B:_:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,HZ(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,M8e(o,d),_8e(d),I=k;return}}function O8e(o){return o=o|0,536870911}function L8e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Jt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function M8e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function _8e(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&Et(o)}function jZ(o){o=o|0,j8e(o)}function U8e(o){o=o|0,H8e(o+24|0)}function H8e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),Et(u))}function j8e(o){o=o|0;var l=0;l=en()|0,tn(o,1,15,l,iZ()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function q8e(o){return o=o|0,W8e(n[(G8e(o)|0)>>2]|0)|0}function G8e(o){return o=o|0,(n[(HM()|0)+24>>2]|0)+(o<<3)|0}function W8e(o){return o=o|0,UP(tx[o&7]()|0)|0}function Y8e(){var o=0;return s[7832]|0||(eHe(10052),gr(25,10052,U|0)|0,o=7832,n[o>>2]=1,n[o+4>>2]=0),10052}function V8e(o,l){o=o|0,l=l|0,n[o>>2]=K8e()|0,n[o+4>>2]=J8e()|0,n[o+12>>2]=l,n[o+8>>2]=z8e()|0,n[o+32>>2]=2}function K8e(){return 11709}function J8e(){return 1188}function z8e(){return jP()|0}function Z8e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,(qh(A,896)|0)==512?u|0&&(X8e(u),Et(u)):l|0&&(Fy(l),Et(l))}function qh(o,l){return o=o|0,l=l|0,l&o|0}function X8e(o){o=o|0,o=n[o+4>>2]|0,o|0&&Wh(o)}function jP(){var o=0;return s[7824]|0||(n[2511]=$8e()|0,n[2512]=0,o=7824,n[o>>2]=1,n[o+4>>2]=0),10044}function $8e(){return 0}function eHe(o){o=o|0,_h(o)}function tHe(o){o=o|0;var l=0,u=0,A=0,d=0,m=0;l=I,I=I+32|0,u=l+24|0,m=l+16|0,d=l+8|0,A=l,rHe(o,4827),nHe(o,4834,3)|0,iHe(o,3682,47)|0,n[m>>2]=9,n[m+4>>2]=0,n[u>>2]=n[m>>2],n[u+4>>2]=n[m+4>>2],sHe(o,4841,u)|0,n[d>>2]=1,n[d+4>>2]=0,n[u>>2]=n[d>>2],n[u+4>>2]=n[d+4>>2],oHe(o,4871,u)|0,n[A>>2]=10,n[A+4>>2]=0,n[u>>2]=n[A>>2],n[u+4>>2]=n[A+4>>2],aHe(o,4891,u)|0,I=l}function rHe(o,l){o=o|0,l=l|0;var u=0;u=_je()|0,n[o>>2]=u,Uje(u,l),Gh(n[o>>2]|0)}function nHe(o,l,u){return o=o|0,l=l|0,u=u|0,Bje(o,Bn(l)|0,u,0),o|0}function iHe(o,l,u){return o=o|0,l=l|0,u=u|0,lje(o,Bn(l)|0,u,0),o|0}function sHe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],qHe(o,l,d),I=A,o|0}function oHe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],vHe(o,l,d),I=A,o|0}function aHe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],lHe(o,l,d),I=A,o|0}function lHe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],cHe(o,u,d,1),I=A}function cHe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=jM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=uHe(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,fHe(m,A)|0,A),I=d}function jM(){var o=0,l=0;if(s[7840]|0||(GZ(10100),gr(48,10100,U|0)|0,l=7840,n[l>>2]=1,n[l+4>>2]=0),!(Ur(10100)|0)){o=10100,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));GZ(10100)}return 10100}function uHe(o){return o=o|0,0}function fHe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=jM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],qZ(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(AHe(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function qZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function AHe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=pHe(o)|0,m>>>0<d>>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,hHe(k,ae>>>0<m>>>1>>>0?G>>>0<d>>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],qZ(m,A,u),n[T>>2]=(n[T>>2]|0)+12,gHe(o,k),dHe(k),I=_;return}}function pHe(o){return o=o|0,357913941}function hHe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function gHe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function dHe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&Et(o)}function GZ(o){o=o|0,EHe(o)}function mHe(o){o=o|0,yHe(o+24|0)}function yHe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),Et(u))}function EHe(o){o=o|0;var l=0;l=en()|0,tn(o,2,6,l,IHe()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function IHe(){return 1364}function CHe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=wHe(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],u=BHe(l,d,u)|0,I=A,u|0}function wHe(o){return o=o|0,(n[(jM()|0)+24>>2]|0)+(o*12|0)|0}function BHe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;return m=I,I=I+16|0,d=m,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),np(d,u),d=ip(d,u)|0,d=Zz(m_[A&15](o,d)|0)|0,I=m,d|0}function vHe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],SHe(o,u,d,0),I=A}function SHe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=qM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=DHe(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,bHe(m,A)|0,A),I=d}function qM(){var o=0,l=0;if(s[7848]|0||(YZ(10136),gr(49,10136,U|0)|0,l=7848,n[l>>2]=1,n[l+4>>2]=0),!(Ur(10136)|0)){o=10136,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));YZ(10136)}return 10136}function DHe(o){return o=o|0,0}function bHe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=qM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],WZ(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(PHe(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function WZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function PHe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=xHe(o)|0,m>>>0<d>>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,kHe(k,ae>>>0<m>>>1>>>0?G>>>0<d>>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],WZ(m,A,u),n[T>>2]=(n[T>>2]|0)+12,QHe(o,k),THe(k),I=_;return}}function xHe(o){return o=o|0,357913941}function kHe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function QHe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function THe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&Et(o)}function YZ(o){o=o|0,NHe(o)}function RHe(o){o=o|0,FHe(o+24|0)}function FHe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),Et(u))}function NHe(o){o=o|0;var l=0;l=en()|0,tn(o,2,9,l,OHe()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function OHe(){return 1372}function LHe(o,l,u){o=o|0,l=l|0,u=+u;var A=0,d=0,m=0,B=0;A=I,I=I+16|0,d=A+8|0,m=A,B=MHe(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],_He(l,d,u),I=A}function MHe(o){return o=o|0,(n[(qM()|0)+24>>2]|0)+(o*12|0)|0}function _He(o,l,u){o=o|0,l=l|0,u=+u;var A=0,d=0,m=0,B=Xe;m=I,I=I+16|0,d=m,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),UHe(d,u),B=y(HHe(d,u)),o$[A&1](o,B),I=m}function UHe(o,l){o=o|0,l=+l}function HHe(o,l){return o=o|0,l=+l,y(jHe(l))}function jHe(o){return o=+o,y(o)}function qHe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],GHe(o,u,d,0),I=A}function GHe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=GM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=WHe(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,YHe(m,A)|0,A),I=d}function GM(){var o=0,l=0;if(s[7856]|0||(KZ(10172),gr(50,10172,U|0)|0,l=7856,n[l>>2]=1,n[l+4>>2]=0),!(Ur(10172)|0)){o=10172,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));KZ(10172)}return 10172}function WHe(o){return o=o|0,0}function YHe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=GM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],VZ(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(VHe(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function VZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function VHe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=KHe(o)|0,m>>>0<d>>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,JHe(k,ae>>>0<m>>>1>>>0?G>>>0<d>>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],VZ(m,A,u),n[T>>2]=(n[T>>2]|0)+12,zHe(o,k),ZHe(k),I=_;return}}function KHe(o){return o=o|0,357913941}function JHe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function zHe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function ZHe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&Et(o)}function KZ(o){o=o|0,eje(o)}function XHe(o){o=o|0,$He(o+24|0)}function $He(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),Et(u))}function eje(o){o=o|0;var l=0;l=en()|0,tn(o,2,3,l,tje()|0,2),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function tje(){return 1380}function rje(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0;d=I,I=I+16|0,m=d+8|0,B=d,k=nje(o)|0,o=n[k+4>>2]|0,n[B>>2]=n[k>>2],n[B+4>>2]=o,n[m>>2]=n[B>>2],n[m+4>>2]=n[B+4>>2],ije(l,m,u,A),I=d}function nje(o){return o=o|0,(n[(GM()|0)+24>>2]|0)+(o*12|0)|0}function ije(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0;k=I,I=I+16|0,m=k+1|0,B=k,d=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(d=n[(n[o>>2]|0)+d>>2]|0),np(m,u),m=ip(m,u)|0,sje(B,A),B=oje(B,A)|0,F2[d&15](o,m,B),I=k}function sje(o,l){o=o|0,l=l|0}function oje(o,l){return o=o|0,l=l|0,aje(l)|0}function aje(o){return o=o|0,(o|0)!=0|0}function lje(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=WM()|0,o=cje(u)|0,vn(m,l,d,o,uje(u,A)|0,A)}function WM(){var o=0,l=0;if(s[7864]|0||(zZ(10208),gr(51,10208,U|0)|0,l=7864,n[l>>2]=1,n[l+4>>2]=0),!(Ur(10208)|0)){o=10208,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));zZ(10208)}return 10208}function cje(o){return o=o|0,o|0}function uje(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=WM()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(JZ(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(fje(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function JZ(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function fje(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=Aje(o)|0,A>>>0<B>>>0)sn(o);else{T=n[o>>2]|0,M=(n[o+8>>2]|0)-T|0,_=M>>2,pje(d,M>>3>>>0<A>>>1>>>0?_>>>0<B>>>0?B:_:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,JZ(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,hje(o,d),gje(d),I=k;return}}function Aje(o){return o=o|0,536870911}function pje(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Jt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function hje(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function gje(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&Et(o)}function zZ(o){o=o|0,yje(o)}function dje(o){o=o|0,mje(o+24|0)}function mje(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),Et(u))}function yje(o){o=o|0;var l=0;l=en()|0,tn(o,1,24,l,Eje()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function Eje(){return 1392}function Ije(o,l){o=o|0,l=l|0,wje(n[(Cje(o)|0)>>2]|0,l)}function Cje(o){return o=o|0,(n[(WM()|0)+24>>2]|0)+(o<<3)|0}function wje(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,_Z(A,l),l=UZ(A,l)|0,op[o&127](l),I=u}function Bje(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=YM()|0,o=vje(u)|0,vn(m,l,d,o,Sje(u,A)|0,A)}function YM(){var o=0,l=0;if(s[7872]|0||(XZ(10244),gr(52,10244,U|0)|0,l=7872,n[l>>2]=1,n[l+4>>2]=0),!(Ur(10244)|0)){o=10244,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));XZ(10244)}return 10244}function vje(o){return o=o|0,o|0}function Sje(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=YM()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(ZZ(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(Dje(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function ZZ(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function Dje(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=bje(o)|0,A>>>0<B>>>0)sn(o);else{T=n[o>>2]|0,M=(n[o+8>>2]|0)-T|0,_=M>>2,Pje(d,M>>3>>>0<A>>>1>>>0?_>>>0<B>>>0?B:_:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,ZZ(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,xje(o,d),kje(d),I=k;return}}function bje(o){return o=o|0,536870911}function Pje(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Jt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function xje(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function kje(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&Et(o)}function XZ(o){o=o|0,Rje(o)}function Qje(o){o=o|0,Tje(o+24|0)}function Tje(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),Et(u))}function Rje(o){o=o|0;var l=0;l=en()|0,tn(o,1,16,l,Fje()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function Fje(){return 1400}function Nje(o){return o=o|0,Lje(n[(Oje(o)|0)>>2]|0)|0}function Oje(o){return o=o|0,(n[(YM()|0)+24>>2]|0)+(o<<3)|0}function Lje(o){return o=o|0,Mje(tx[o&7]()|0)|0}function Mje(o){return o=o|0,o|0}function _je(){var o=0;return s[7880]|0||(Yje(10280),gr(25,10280,U|0)|0,o=7880,n[o>>2]=1,n[o+4>>2]=0),10280}function Uje(o,l){o=o|0,l=l|0,n[o>>2]=Hje()|0,n[o+4>>2]=jje()|0,n[o+12>>2]=l,n[o+8>>2]=qje()|0,n[o+32>>2]=4}function Hje(){return 11711}function jje(){return 1356}function qje(){return jP()|0}function Gje(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,(qh(A,896)|0)==512?u|0&&(Wje(u),Et(u)):l|0&&(Kg(l),Et(l))}function Wje(o){o=o|0,o=n[o+4>>2]|0,o|0&&Wh(o)}function Yje(o){o=o|0,_h(o)}function Vje(o){o=o|0,Kje(o,4920),Jje(o)|0,zje(o)|0}function Kje(o,l){o=o|0,l=l|0;var u=0;u=IZ()|0,n[o>>2]=u,m6e(u,l),Gh(n[o>>2]|0)}function Jje(o){o=o|0;var l=0;return l=n[o>>2]|0,ld(l,a6e()|0),o|0}function zje(o){o=o|0;var l=0;return l=n[o>>2]|0,ld(l,Zje()|0),o|0}function Zje(){var o=0;return s[7888]|0||($Z(10328),gr(53,10328,U|0)|0,o=7888,n[o>>2]=1,n[o+4>>2]=0),Ur(10328)|0||$Z(10328),10328}function ld(o,l){o=o|0,l=l|0,vn(o,0,l,0,0,0)}function $Z(o){o=o|0,e6e(o),cd(o,10)}function Xje(o){o=o|0,$je(o+24|0)}function $je(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),Et(u))}function e6e(o){o=o|0;var l=0;l=en()|0,tn(o,5,1,l,i6e()|0,2),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function t6e(o,l,u){o=o|0,l=l|0,u=+u,r6e(o,l,u)}function cd(o,l){o=o|0,l=l|0,n[o+20>>2]=l}function r6e(o,l,u){o=o|0,l=l|0,u=+u;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,m=A+8|0,k=A+13|0,d=A,B=A+12|0,np(k,l),n[m>>2]=ip(k,l)|0,Tf(B,u),E[d>>3]=+Rf(B,u),n6e(o,m,d),I=A}function n6e(o,l,u){o=o|0,l=l|0,u=u|0,Rl(o+8|0,n[l>>2]|0,+E[u>>3]),s[o+24>>0]=1}function i6e(){return 1404}function s6e(o,l){return o=o|0,l=+l,o6e(o,l)|0}function o6e(o,l){o=o|0,l=+l;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return A=I,I=I+16|0,m=A+4|0,B=A+8|0,k=A,d=Fl(8)|0,u=d,T=Jt(16)|0,np(m,o),o=ip(m,o)|0,Tf(B,l),Rl(T,o,+Rf(B,l)),B=u+4|0,n[B>>2]=T,o=Jt(8)|0,B=n[B>>2]|0,n[k>>2]=0,n[m>>2]=n[k>>2],QM(o,B,m),n[d>>2]=o,I=A,u|0}function a6e(){var o=0;return s[7896]|0||(eX(10364),gr(54,10364,U|0)|0,o=7896,n[o>>2]=1,n[o+4>>2]=0),Ur(10364)|0||eX(10364),10364}function eX(o){o=o|0,u6e(o),cd(o,55)}function l6e(o){o=o|0,c6e(o+24|0)}function c6e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),Et(u))}function u6e(o){o=o|0;var l=0;l=en()|0,tn(o,5,4,l,h6e()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function f6e(o){o=o|0,A6e(o)}function A6e(o){o=o|0,p6e(o)}function p6e(o){o=o|0,tX(o+8|0),s[o+24>>0]=1}function tX(o){o=o|0,n[o>>2]=0,E[o+8>>3]=0}function h6e(){return 1424}function g6e(){return d6e()|0}function d6e(){var o=0,l=0,u=0,A=0,d=0,m=0,B=0;return l=I,I=I+16|0,d=l+4|0,B=l,u=Fl(8)|0,o=u,A=Jt(16)|0,tX(A),m=o+4|0,n[m>>2]=A,A=Jt(8)|0,m=n[m>>2]|0,n[B>>2]=0,n[d>>2]=n[B>>2],QM(A,m,d),n[u>>2]=A,I=l,o|0}function m6e(o,l){o=o|0,l=l|0,n[o>>2]=y6e()|0,n[o+4>>2]=E6e()|0,n[o+12>>2]=l,n[o+8>>2]=I6e()|0,n[o+32>>2]=5}function y6e(){return 11710}function E6e(){return 1416}function I6e(){return qP()|0}function C6e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,(qh(A,896)|0)==512?u|0&&(w6e(u),Et(u)):l|0&&Et(l)}function w6e(o){o=o|0,o=n[o+4>>2]|0,o|0&&Wh(o)}function qP(){var o=0;return s[7904]|0||(n[2600]=B6e()|0,n[2601]=0,o=7904,n[o>>2]=1,n[o+4>>2]=0),10400}function B6e(){return n[357]|0}function v6e(o){o=o|0,S6e(o,4926),D6e(o)|0}function S6e(o,l){o=o|0,l=l|0;var u=0;u=qz()|0,n[o>>2]=u,L6e(u,l),Gh(n[o>>2]|0)}function D6e(o){o=o|0;var l=0;return l=n[o>>2]|0,ld(l,b6e()|0),o|0}function b6e(){var o=0;return s[7912]|0||(rX(10412),gr(56,10412,U|0)|0,o=7912,n[o>>2]=1,n[o+4>>2]=0),Ur(10412)|0||rX(10412),10412}function rX(o){o=o|0,k6e(o),cd(o,57)}function P6e(o){o=o|0,x6e(o+24|0)}function x6e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),Et(u))}function k6e(o){o=o|0;var l=0;l=en()|0,tn(o,5,5,l,F6e()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function Q6e(o){o=o|0,T6e(o)}function T6e(o){o=o|0,R6e(o)}function R6e(o){o=o|0;var l=0,u=0;l=o+8|0,u=l+48|0;do n[l>>2]=0,l=l+4|0;while((l|0)<(u|0));s[o+56>>0]=1}function F6e(){return 1432}function N6e(){return O6e()|0}function O6e(){var o=0,l=0,u=0,A=0,d=0,m=0,B=0,k=0;B=I,I=I+16|0,o=B+4|0,l=B,u=Fl(8)|0,A=u,d=Jt(48)|0,m=d,k=m+48|0;do n[m>>2]=0,m=m+4|0;while((m|0)<(k|0));return m=A+4|0,n[m>>2]=d,k=Jt(8)|0,m=n[m>>2]|0,n[l>>2]=0,n[o>>2]=n[l>>2],Gz(k,m,o),n[u>>2]=k,I=B,A|0}function L6e(o,l){o=o|0,l=l|0,n[o>>2]=M6e()|0,n[o+4>>2]=_6e()|0,n[o+12>>2]=l,n[o+8>>2]=U6e()|0,n[o+32>>2]=6}function M6e(){return 11704}function _6e(){return 1436}function U6e(){return qP()|0}function H6e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,(qh(A,896)|0)==512?u|0&&(j6e(u),Et(u)):l|0&&Et(l)}function j6e(o){o=o|0,o=n[o+4>>2]|0,o|0&&Wh(o)}function q6e(o){o=o|0,G6e(o,4933),W6e(o)|0,Y6e(o)|0}function G6e(o,l){o=o|0,l=l|0;var u=0;u=dqe()|0,n[o>>2]=u,mqe(u,l),Gh(n[o>>2]|0)}function W6e(o){o=o|0;var l=0;return l=n[o>>2]|0,ld(l,oqe()|0),o|0}function Y6e(o){o=o|0;var l=0;return l=n[o>>2]|0,ld(l,V6e()|0),o|0}function V6e(){var o=0;return s[7920]|0||(nX(10452),gr(58,10452,U|0)|0,o=7920,n[o>>2]=1,n[o+4>>2]=0),Ur(10452)|0||nX(10452),10452}function nX(o){o=o|0,z6e(o),cd(o,1)}function K6e(o){o=o|0,J6e(o+24|0)}function J6e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),Et(u))}function z6e(o){o=o|0;var l=0;l=en()|0,tn(o,5,1,l,eqe()|0,2),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function Z6e(o,l,u){o=o|0,l=+l,u=+u,X6e(o,l,u)}function X6e(o,l,u){o=o|0,l=+l,u=+u;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+32|0,m=A+8|0,k=A+17|0,d=A,B=A+16|0,Tf(k,l),E[m>>3]=+Rf(k,l),Tf(B,u),E[d>>3]=+Rf(B,u),$6e(o,m,d),I=A}function $6e(o,l,u){o=o|0,l=l|0,u=u|0,iX(o+8|0,+E[l>>3],+E[u>>3]),s[o+24>>0]=1}function iX(o,l,u){o=o|0,l=+l,u=+u,E[o>>3]=l,E[o+8>>3]=u}function eqe(){return 1472}function tqe(o,l){return o=+o,l=+l,rqe(o,l)|0}function rqe(o,l){o=+o,l=+l;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return A=I,I=I+16|0,B=A+4|0,k=A+8|0,T=A,d=Fl(8)|0,u=d,m=Jt(16)|0,Tf(B,o),o=+Rf(B,o),Tf(k,l),iX(m,o,+Rf(k,l)),k=u+4|0,n[k>>2]=m,m=Jt(8)|0,k=n[k>>2]|0,n[T>>2]=0,n[B>>2]=n[T>>2],sX(m,k,B),n[d>>2]=m,I=A,u|0}function sX(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,u=Jt(16)|0,n[u+4>>2]=0,n[u+8>>2]=0,n[u>>2]=1452,n[u+12>>2]=l,n[o+4>>2]=u}function nqe(o){o=o|0,Zy(o),Et(o)}function iqe(o){o=o|0,o=n[o+12>>2]|0,o|0&&Et(o)}function sqe(o){o=o|0,Et(o)}function oqe(){var o=0;return s[7928]|0||(oX(10488),gr(59,10488,U|0)|0,o=7928,n[o>>2]=1,n[o+4>>2]=0),Ur(10488)|0||oX(10488),10488}function oX(o){o=o|0,cqe(o),cd(o,60)}function aqe(o){o=o|0,lqe(o+24|0)}function lqe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),Et(u))}function cqe(o){o=o|0;var l=0;l=en()|0,tn(o,5,6,l,pqe()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function uqe(o){o=o|0,fqe(o)}function fqe(o){o=o|0,Aqe(o)}function Aqe(o){o=o|0,aX(o+8|0),s[o+24>>0]=1}function aX(o){o=o|0,n[o>>2]=0,n[o+4>>2]=0,n[o+8>>2]=0,n[o+12>>2]=0}function pqe(){return 1492}function hqe(){return gqe()|0}function gqe(){var o=0,l=0,u=0,A=0,d=0,m=0,B=0;return l=I,I=I+16|0,d=l+4|0,B=l,u=Fl(8)|0,o=u,A=Jt(16)|0,aX(A),m=o+4|0,n[m>>2]=A,A=Jt(8)|0,m=n[m>>2]|0,n[B>>2]=0,n[d>>2]=n[B>>2],sX(A,m,d),n[u>>2]=A,I=l,o|0}function dqe(){var o=0;return s[7936]|0||(Bqe(10524),gr(25,10524,U|0)|0,o=7936,n[o>>2]=1,n[o+4>>2]=0),10524}function mqe(o,l){o=o|0,l=l|0,n[o>>2]=yqe()|0,n[o+4>>2]=Eqe()|0,n[o+12>>2]=l,n[o+8>>2]=Iqe()|0,n[o+32>>2]=7}function yqe(){return 11700}function Eqe(){return 1484}function Iqe(){return qP()|0}function Cqe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,(qh(A,896)|0)==512?u|0&&(wqe(u),Et(u)):l|0&&Et(l)}function wqe(o){o=o|0,o=n[o+4>>2]|0,o|0&&Wh(o)}function Bqe(o){o=o|0,_h(o)}function vqe(o,l,u){o=o|0,l=l|0,u=u|0,o=Bn(l)|0,l=Sqe(u)|0,u=Dqe(u,0)|0,rGe(o,l,u,VM()|0,0)}function Sqe(o){return o=o|0,o|0}function Dqe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=VM()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(cX(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(Rqe(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function VM(){var o=0,l=0;if(s[7944]|0||(lX(10568),gr(61,10568,U|0)|0,l=7944,n[l>>2]=1,n[l+4>>2]=0),!(Ur(10568)|0)){o=10568,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));lX(10568)}return 10568}function lX(o){o=o|0,xqe(o)}function bqe(o){o=o|0,Pqe(o+24|0)}function Pqe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),Et(u))}function xqe(o){o=o|0;var l=0;l=en()|0,tn(o,1,17,l,aZ()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function kqe(o){return o=o|0,Tqe(n[(Qqe(o)|0)>>2]|0)|0}function Qqe(o){return o=o|0,(n[(VM()|0)+24>>2]|0)+(o<<3)|0}function Tqe(o){return o=o|0,HP(tx[o&7]()|0)|0}function cX(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function Rqe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=Fqe(o)|0,A>>>0<B>>>0)sn(o);else{T=n[o>>2]|0,M=(n[o+8>>2]|0)-T|0,_=M>>2,Nqe(d,M>>3>>>0<A>>>1>>>0?_>>>0<B>>>0?B:_:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,cX(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,Oqe(o,d),Lqe(d),I=k;return}}function Fqe(o){return o=o|0,536870911}function Nqe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Jt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function Oqe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function Lqe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&Et(o)}function Mqe(){_qe()}function _qe(){Uqe(10604)}function Uqe(o){o=o|0,Hqe(o,4955)}function Hqe(o,l){o=o|0,l=l|0;var u=0;u=jqe()|0,n[o>>2]=u,qqe(u,l),Gh(n[o>>2]|0)}function jqe(){var o=0;return s[7952]|0||(Xqe(10612),gr(25,10612,U|0)|0,o=7952,n[o>>2]=1,n[o+4>>2]=0),10612}function qqe(o,l){o=o|0,l=l|0,n[o>>2]=Vqe()|0,n[o+4>>2]=Kqe()|0,n[o+12>>2]=l,n[o+8>>2]=Jqe()|0,n[o+32>>2]=8}function Gh(o){o=o|0;var l=0,u=0;l=I,I=I+16|0,u=l,Yy()|0,n[u>>2]=o,Gqe(10608,u),I=l}function Yy(){return s[11714]|0||(n[2652]=0,gr(62,10608,U|0)|0,s[11714]=1),10608}function Gqe(o,l){o=o|0,l=l|0;var u=0;u=Jt(8)|0,n[u+4>>2]=n[l>>2],n[u>>2]=n[o>>2],n[o>>2]=u}function Wqe(o){o=o|0,Yqe(o)}function Yqe(o){o=o|0;var l=0,u=0;if(l=n[o>>2]|0,l|0)do u=l,l=n[l>>2]|0,Et(u);while(l|0);n[o>>2]=0}function Vqe(){return 11715}function Kqe(){return 1496}function Jqe(){return jP()|0}function zqe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,(qh(A,896)|0)==512?u|0&&(Zqe(u),Et(u)):l|0&&Et(l)}function Zqe(o){o=o|0,o=n[o+4>>2]|0,o|0&&Wh(o)}function Xqe(o){o=o|0,_h(o)}function $qe(o,l){o=o|0,l=l|0;var u=0,A=0;Yy()|0,u=n[2652]|0;e:do if(u|0){for(;A=n[u+4>>2]|0,!(A|0&&!(GX(KM(A)|0,o)|0));)if(u=n[u>>2]|0,!u)break e;eGe(A,l)}while(!1)}function KM(o){return o=o|0,n[o+12>>2]|0}function eGe(o,l){o=o|0,l=l|0;var u=0;o=o+36|0,u=n[o>>2]|0,u|0&&(Df(u),Et(u)),u=Jt(4)|0,FP(u,l),n[o>>2]=u}function JM(){return s[11716]|0||(n[2664]=0,gr(63,10656,U|0)|0,s[11716]=1),10656}function uX(){var o=0;return s[11717]|0?o=n[2665]|0:(tGe(),n[2665]=1504,s[11717]=1,o=1504),o|0}function tGe(){s[11740]|0||(s[11718]=yr(yr(8,0)|0,0)|0,s[11719]=yr(yr(0,0)|0,0)|0,s[11720]=yr(yr(0,16)|0,0)|0,s[11721]=yr(yr(8,0)|0,0)|0,s[11722]=yr(yr(0,0)|0,0)|0,s[11723]=yr(yr(8,0)|0,0)|0,s[11724]=yr(yr(0,0)|0,0)|0,s[11725]=yr(yr(8,0)|0,0)|0,s[11726]=yr(yr(0,0)|0,0)|0,s[11727]=yr(yr(8,0)|0,0)|0,s[11728]=yr(yr(0,0)|0,0)|0,s[11729]=yr(yr(0,0)|0,32)|0,s[11730]=yr(yr(0,0)|0,32)|0,s[11740]=1)}function fX(){return 1572}function rGe(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0;var m=0,B=0,k=0,T=0,_=0,M=0;m=I,I=I+32|0,M=m+16|0,_=m+12|0,T=m+8|0,k=m+4|0,B=m,n[M>>2]=o,n[_>>2]=l,n[T>>2]=u,n[k>>2]=A,n[B>>2]=d,JM()|0,nGe(10656,M,_,T,k,B),I=m}function nGe(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0;var B=0;B=Jt(24)|0,Uz(B+4|0,n[l>>2]|0,n[u>>2]|0,n[A>>2]|0,n[d>>2]|0,n[m>>2]|0),n[B>>2]=n[o>>2],n[o>>2]=B}function AX(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0,We=0,Le=0,Qe=0,tt=0,Ze=0,ct=0;if(ct=I,I=I+32|0,Le=ct+20|0,Qe=ct+8|0,tt=ct+4|0,Ze=ct,l=n[l>>2]|0,l|0){We=Le+4|0,T=Le+8|0,_=Qe+4|0,M=Qe+8|0,G=Qe+8|0,ae=Le+8|0;do{if(B=l+4|0,k=zM(B)|0,k|0){if(d=P2(k)|0,n[Le>>2]=0,n[We>>2]=0,n[T>>2]=0,A=(x2(k)|0)+1|0,iGe(Le,A),A|0)for(;A=A+-1|0,xu(Qe,n[d>>2]|0),m=n[We>>2]|0,m>>>0<(n[ae>>2]|0)>>>0?(n[m>>2]=n[Qe>>2],n[We>>2]=(n[We>>2]|0)+4):ZM(Le,Qe),A;)d=d+4|0;A=k2(k)|0,n[Qe>>2]=0,n[_>>2]=0,n[M>>2]=0;e:do if(n[A>>2]|0)for(d=0,m=0;;){if((d|0)==(m|0)?sGe(Qe,A):(n[d>>2]=n[A>>2],n[_>>2]=(n[_>>2]|0)+4),A=A+4|0,!(n[A>>2]|0))break e;d=n[_>>2]|0,m=n[G>>2]|0}while(!1);n[tt>>2]=GP(B)|0,n[Ze>>2]=Ur(k)|0,oGe(u,o,tt,Ze,Le,Qe),XM(Qe),sp(Le)}l=n[l>>2]|0}while(l|0)}I=ct}function zM(o){return o=o|0,n[o+12>>2]|0}function P2(o){return o=o|0,n[o+12>>2]|0}function x2(o){return o=o|0,n[o+16>>2]|0}function iGe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0;d=I,I=I+32|0,u=d,A=n[o>>2]|0,(n[o+8>>2]|0)-A>>2>>>0<l>>>0&&(IX(u,l,(n[o+4>>2]|0)-A>>2,o+8|0),CX(o,u),wX(u)),I=d}function ZM(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0;if(B=I,I=I+32|0,u=B,A=o+4|0,d=((n[A>>2]|0)-(n[o>>2]|0)>>2)+1|0,m=EX(o)|0,m>>>0<d>>>0)sn(o);else{k=n[o>>2]|0,_=(n[o+8>>2]|0)-k|0,T=_>>1,IX(u,_>>2>>>0<m>>>1>>>0?T>>>0<d>>>0?d:T:m,(n[A>>2]|0)-k>>2,o+8|0),m=u+8|0,n[n[m>>2]>>2]=n[l>>2],n[m>>2]=(n[m>>2]|0)+4,CX(o,u),wX(u),I=B;return}}function k2(o){return o=o|0,n[o+8>>2]|0}function sGe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0;if(B=I,I=I+32|0,u=B,A=o+4|0,d=((n[A>>2]|0)-(n[o>>2]|0)>>2)+1|0,m=yX(o)|0,m>>>0<d>>>0)sn(o);else{k=n[o>>2]|0,_=(n[o+8>>2]|0)-k|0,T=_>>1,SGe(u,_>>2>>>0<m>>>1>>>0?T>>>0<d>>>0?d:T:m,(n[A>>2]|0)-k>>2,o+8|0),m=u+8|0,n[n[m>>2]>>2]=n[l>>2],n[m>>2]=(n[m>>2]|0)+4,DGe(o,u),bGe(u),I=B;return}}function GP(o){return o=o|0,n[o>>2]|0}function oGe(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,aGe(o,l,u,A,d,m)}function XM(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-4-A|0)>>>2)<<2)),Et(u))}function sp(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-4-A|0)>>>2)<<2)),Et(u))}function aGe(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0;var B=0,k=0,T=0,_=0,M=0,G=0;B=I,I=I+48|0,M=B+40|0,k=B+32|0,G=B+24|0,T=B+12|0,_=B,Nl(k),o=Ms(o)|0,n[G>>2]=n[l>>2],u=n[u>>2]|0,A=n[A>>2]|0,$M(T,d),lGe(_,m),n[M>>2]=n[G>>2],cGe(o,M,u,A,T,_),XM(_),sp(T),Ol(k),I=B}function $M(o,l){o=o|0,l=l|0;var u=0,A=0;n[o>>2]=0,n[o+4>>2]=0,n[o+8>>2]=0,u=l+4|0,A=(n[u>>2]|0)-(n[l>>2]|0)>>2,A|0&&(BGe(o,A),vGe(o,n[l>>2]|0,n[u>>2]|0,A))}function lGe(o,l){o=o|0,l=l|0;var u=0,A=0;n[o>>2]=0,n[o+4>>2]=0,n[o+8>>2]=0,u=l+4|0,A=(n[u>>2]|0)-(n[l>>2]|0)>>2,A|0&&(CGe(o,A),wGe(o,n[l>>2]|0,n[u>>2]|0,A))}function cGe(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0;var B=0,k=0,T=0,_=0,M=0,G=0;B=I,I=I+32|0,M=B+28|0,G=B+24|0,k=B+12|0,T=B,_=ma(uGe()|0)|0,n[G>>2]=n[l>>2],n[M>>2]=n[G>>2],l=ud(M)|0,u=pX(u)|0,A=e_(A)|0,n[k>>2]=n[d>>2],M=d+4|0,n[k+4>>2]=n[M>>2],G=d+8|0,n[k+8>>2]=n[G>>2],n[G>>2]=0,n[M>>2]=0,n[d>>2]=0,d=t_(k)|0,n[T>>2]=n[m>>2],M=m+4|0,n[T+4>>2]=n[M>>2],G=m+8|0,n[T+8>>2]=n[G>>2],n[G>>2]=0,n[M>>2]=0,n[m>>2]=0,uu(0,_|0,o|0,l|0,u|0,A|0,d|0,fGe(T)|0)|0,XM(T),sp(k),I=B}function uGe(){var o=0;return s[7968]|0||(EGe(10708),o=7968,n[o>>2]=1,n[o+4>>2]=0),10708}function ud(o){return o=o|0,gX(o)|0}function pX(o){return o=o|0,hX(o)|0}function e_(o){return o=o|0,HP(o)|0}function t_(o){return o=o|0,pGe(o)|0}function fGe(o){return o=o|0,AGe(o)|0}function AGe(o){o=o|0;var l=0,u=0,A=0;if(A=(n[o+4>>2]|0)-(n[o>>2]|0)|0,u=A>>2,A=Fl(A+4|0)|0,n[A>>2]=u,u|0){l=0;do n[A+4+(l<<2)>>2]=hX(n[(n[o>>2]|0)+(l<<2)>>2]|0)|0,l=l+1|0;while((l|0)!=(u|0))}return A|0}function hX(o){return o=o|0,o|0}function pGe(o){o=o|0;var l=0,u=0,A=0;if(A=(n[o+4>>2]|0)-(n[o>>2]|0)|0,u=A>>2,A=Fl(A+4|0)|0,n[A>>2]=u,u|0){l=0;do n[A+4+(l<<2)>>2]=gX((n[o>>2]|0)+(l<<2)|0)|0,l=l+1|0;while((l|0)!=(u|0))}return A|0}function gX(o){o=o|0;var l=0,u=0,A=0,d=0;return d=I,I=I+32|0,l=d+12|0,u=d,A=pM(dX()|0)|0,A?(hM(l,A),gM(u,l),JWe(o,u),o=dM(l)|0):o=hGe(o)|0,I=d,o|0}function dX(){var o=0;return s[7960]|0||(yGe(10664),gr(25,10664,U|0)|0,o=7960,n[o>>2]=1,n[o+4>>2]=0),10664}function hGe(o){o=o|0;var l=0,u=0,A=0,d=0,m=0,B=0,k=0;return u=I,I=I+16|0,d=u+4|0,B=u,A=Fl(8)|0,l=A,k=Jt(4)|0,n[k>>2]=n[o>>2],m=l+4|0,n[m>>2]=k,o=Jt(8)|0,m=n[m>>2]|0,n[B>>2]=0,n[d>>2]=n[B>>2],mX(o,m,d),n[A>>2]=o,I=u,l|0}function mX(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,u=Jt(16)|0,n[u+4>>2]=0,n[u+8>>2]=0,n[u>>2]=1656,n[u+12>>2]=l,n[o+4>>2]=u}function gGe(o){o=o|0,Zy(o),Et(o)}function dGe(o){o=o|0,o=n[o+12>>2]|0,o|0&&Et(o)}function mGe(o){o=o|0,Et(o)}function yGe(o){o=o|0,_h(o)}function EGe(o){o=o|0,Ro(o,IGe()|0,5)}function IGe(){return 1676}function CGe(o,l){o=o|0,l=l|0;var u=0;if((yX(o)|0)>>>0<l>>>0&&sn(o),l>>>0>1073741823)Nt();else{u=Jt(l<<2)|0,n[o+4>>2]=u,n[o>>2]=u,n[o+8>>2]=u+(l<<2);return}}function wGe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,A=o+4|0,o=u-l|0,(o|0)>0&&(Qr(n[A>>2]|0,l|0,o|0)|0,n[A>>2]=(n[A>>2]|0)+(o>>>2<<2))}function yX(o){return o=o|0,1073741823}function BGe(o,l){o=o|0,l=l|0;var u=0;if((EX(o)|0)>>>0<l>>>0&&sn(o),l>>>0>1073741823)Nt();else{u=Jt(l<<2)|0,n[o+4>>2]=u,n[o>>2]=u,n[o+8>>2]=u+(l<<2);return}}function vGe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,A=o+4|0,o=u-l|0,(o|0)>0&&(Qr(n[A>>2]|0,l|0,o|0)|0,n[A>>2]=(n[A>>2]|0)+(o>>>2<<2))}function EX(o){return o=o|0,1073741823}function SGe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>1073741823)Nt();else{d=Jt(l<<2)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<2)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<2)}function DGe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>2)<<2)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function bGe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-4-l|0)>>>2)<<2)),o=n[o>>2]|0,o|0&&Et(o)}function IX(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>1073741823)Nt();else{d=Jt(l<<2)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<2)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<2)}function CX(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>2)<<2)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function wX(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-4-l|0)>>>2)<<2)),o=n[o>>2]|0,o|0&&Et(o)}function PGe(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0;var m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0,We=0,Le=0,Qe=0;if(Qe=I,I=I+32|0,M=Qe+20|0,G=Qe+12|0,_=Qe+16|0,ae=Qe+4|0,We=Qe,Le=Qe+8|0,k=uX()|0,m=n[k>>2]|0,B=n[m>>2]|0,B|0)for(T=n[k+8>>2]|0,k=n[k+4>>2]|0;xu(M,B),xGe(o,M,k,T),m=m+4|0,B=n[m>>2]|0,B;)T=T+1|0,k=k+1|0;if(m=fX()|0,B=n[m>>2]|0,B|0)do xu(M,B),n[G>>2]=n[m+4>>2],kGe(l,M,G),m=m+8|0,B=n[m>>2]|0;while(B|0);if(m=n[(Yy()|0)>>2]|0,m|0)do l=n[m+4>>2]|0,xu(M,n[(Vy(l)|0)>>2]|0),n[G>>2]=KM(l)|0,QGe(u,M,G),m=n[m>>2]|0;while(m|0);if(xu(_,0),m=JM()|0,n[M>>2]=n[_>>2],AX(M,m,d),m=n[(Yy()|0)>>2]|0,m|0){o=M+4|0,l=M+8|0,u=M+8|0;do{if(T=n[m+4>>2]|0,xu(G,n[(Vy(T)|0)>>2]|0),TGe(ae,BX(T)|0),B=n[ae>>2]|0,B|0){n[M>>2]=0,n[o>>2]=0,n[l>>2]=0;do xu(We,n[(Vy(n[B+4>>2]|0)|0)>>2]|0),k=n[o>>2]|0,k>>>0<(n[u>>2]|0)>>>0?(n[k>>2]=n[We>>2],n[o>>2]=(n[o>>2]|0)+4):ZM(M,We),B=n[B>>2]|0;while(B|0);RGe(A,G,M),sp(M)}n[Le>>2]=n[G>>2],_=vX(T)|0,n[M>>2]=n[Le>>2],AX(M,_,d),Yz(ae),m=n[m>>2]|0}while(m|0)}I=Qe}function xGe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,WGe(o,l,u,A)}function kGe(o,l,u){o=o|0,l=l|0,u=u|0,GGe(o,l,u)}function Vy(o){return o=o|0,o|0}function QGe(o,l,u){o=o|0,l=l|0,u=u|0,UGe(o,l,u)}function BX(o){return o=o|0,o+16|0}function TGe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;if(m=I,I=I+16|0,d=m+8|0,u=m,n[o>>2]=0,A=n[l>>2]|0,n[d>>2]=A,n[u>>2]=o,u=_Ge(u)|0,A|0){if(A=Jt(12)|0,B=(SX(d)|0)+4|0,o=n[B+4>>2]|0,l=A+4|0,n[l>>2]=n[B>>2],n[l+4>>2]=o,l=n[n[d>>2]>>2]|0,n[d>>2]=l,!l)o=A;else for(l=A;o=Jt(12)|0,T=(SX(d)|0)+4|0,k=n[T+4>>2]|0,B=o+4|0,n[B>>2]=n[T>>2],n[B+4>>2]=k,n[l>>2]=o,B=n[n[d>>2]>>2]|0,n[d>>2]=B,B;)l=o;n[o>>2]=n[u>>2],n[u>>2]=A}I=m}function RGe(o,l,u){o=o|0,l=l|0,u=u|0,FGe(o,l,u)}function vX(o){return o=o|0,o+24|0}function FGe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+32|0,B=A+24|0,d=A+16|0,k=A+12|0,m=A,Nl(d),o=Ms(o)|0,n[k>>2]=n[l>>2],$M(m,u),n[B>>2]=n[k>>2],NGe(o,B,m),sp(m),Ol(d),I=A}function NGe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+32|0,B=A+16|0,k=A+12|0,d=A,m=ma(OGe()|0)|0,n[k>>2]=n[l>>2],n[B>>2]=n[k>>2],l=ud(B)|0,n[d>>2]=n[u>>2],B=u+4|0,n[d+4>>2]=n[B>>2],k=u+8|0,n[d+8>>2]=n[k>>2],n[k>>2]=0,n[B>>2]=0,n[u>>2]=0,Fs(0,m|0,o|0,l|0,t_(d)|0)|0,sp(d),I=A}function OGe(){var o=0;return s[7976]|0||(LGe(10720),o=7976,n[o>>2]=1,n[o+4>>2]=0),10720}function LGe(o){o=o|0,Ro(o,MGe()|0,2)}function MGe(){return 1732}function _Ge(o){return o=o|0,n[o>>2]|0}function SX(o){return o=o|0,n[o>>2]|0}function UGe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;A=I,I=I+32|0,m=A+16|0,d=A+8|0,B=A,Nl(d),o=Ms(o)|0,n[B>>2]=n[l>>2],u=n[u>>2]|0,n[m>>2]=n[B>>2],DX(o,m,u),Ol(d),I=A}function DX(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;A=I,I=I+16|0,m=A+4|0,B=A,d=ma(HGe()|0)|0,n[B>>2]=n[l>>2],n[m>>2]=n[B>>2],l=ud(m)|0,Fs(0,d|0,o|0,l|0,pX(u)|0)|0,I=A}function HGe(){var o=0;return s[7984]|0||(jGe(10732),o=7984,n[o>>2]=1,n[o+4>>2]=0),10732}function jGe(o){o=o|0,Ro(o,qGe()|0,2)}function qGe(){return 1744}function GGe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;A=I,I=I+32|0,m=A+16|0,d=A+8|0,B=A,Nl(d),o=Ms(o)|0,n[B>>2]=n[l>>2],u=n[u>>2]|0,n[m>>2]=n[B>>2],DX(o,m,u),Ol(d),I=A}function WGe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0;d=I,I=I+32|0,B=d+16|0,m=d+8|0,k=d,Nl(m),o=Ms(o)|0,n[k>>2]=n[l>>2],u=s[u>>0]|0,A=s[A>>0]|0,n[B>>2]=n[k>>2],YGe(o,B,u,A),Ol(m),I=d}function YGe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0;d=I,I=I+16|0,B=d+4|0,k=d,m=ma(VGe()|0)|0,n[k>>2]=n[l>>2],n[B>>2]=n[k>>2],l=ud(B)|0,u=Ky(u)|0,Mi(0,m|0,o|0,l|0,u|0,Ky(A)|0)|0,I=d}function VGe(){var o=0;return s[7992]|0||(JGe(10744),o=7992,n[o>>2]=1,n[o+4>>2]=0),10744}function Ky(o){return o=o|0,KGe(o)|0}function KGe(o){return o=o|0,o&255|0}function JGe(o){o=o|0,Ro(o,zGe()|0,3)}function zGe(){return 1756}function ZGe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;switch(ae=I,I=I+32|0,k=ae+8|0,T=ae+4|0,_=ae+20|0,M=ae,IM(o,0),A=KWe(l)|0,n[k>>2]=0,G=k+4|0,n[G>>2]=0,n[k+8>>2]=0,A<<24>>24){case 0:{s[_>>0]=0,XGe(T,u,_),WP(o,T)|0,bf(T);break}case 8:{G=a_(l)|0,s[_>>0]=8,xu(M,n[G+4>>2]|0),$Ge(T,u,_,M,G+8|0),WP(o,T)|0,bf(T);break}case 9:{if(m=a_(l)|0,l=n[m+4>>2]|0,l|0)for(B=k+8|0,d=m+12|0;l=l+-1|0,xu(T,n[d>>2]|0),A=n[G>>2]|0,A>>>0<(n[B>>2]|0)>>>0?(n[A>>2]=n[T>>2],n[G>>2]=(n[G>>2]|0)+4):ZM(k,T),l;)d=d+4|0;s[_>>0]=9,xu(M,n[m+8>>2]|0),e5e(T,u,_,M,k),WP(o,T)|0,bf(T);break}default:G=a_(l)|0,s[_>>0]=A,xu(M,n[G+4>>2]|0),t5e(T,u,_,M),WP(o,T)|0,bf(T)}sp(k),I=ae}function XGe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0;A=I,I=I+16|0,d=A,Nl(d),l=Ms(l)|0,h5e(o,l,s[u>>0]|0),Ol(d),I=A}function WP(o,l){o=o|0,l=l|0;var u=0;return u=n[o>>2]|0,u|0&&Oa(u|0),n[o>>2]=n[l>>2],n[l>>2]=0,o|0}function $Ge(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0;var m=0,B=0,k=0,T=0;m=I,I=I+32|0,k=m+16|0,B=m+8|0,T=m,Nl(B),l=Ms(l)|0,u=s[u>>0]|0,n[T>>2]=n[A>>2],d=n[d>>2]|0,n[k>>2]=n[T>>2],u5e(o,l,u,k,d),Ol(B),I=m}function e5e(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0;var m=0,B=0,k=0,T=0,_=0;m=I,I=I+32|0,T=m+24|0,B=m+16|0,_=m+12|0,k=m,Nl(B),l=Ms(l)|0,u=s[u>>0]|0,n[_>>2]=n[A>>2],$M(k,d),n[T>>2]=n[_>>2],o5e(o,l,u,T,k),sp(k),Ol(B),I=m}function t5e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0;d=I,I=I+32|0,B=d+16|0,m=d+8|0,k=d,Nl(m),l=Ms(l)|0,u=s[u>>0]|0,n[k>>2]=n[A>>2],n[B>>2]=n[k>>2],r5e(o,l,u,B),Ol(m),I=d}function r5e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0;d=I,I=I+16|0,m=d+4|0,k=d,B=ma(n5e()|0)|0,u=Ky(u)|0,n[k>>2]=n[A>>2],n[m>>2]=n[k>>2],YP(o,Fs(0,B|0,l|0,u|0,ud(m)|0)|0),I=d}function n5e(){var o=0;return s[8e3]|0||(i5e(10756),o=8e3,n[o>>2]=1,n[o+4>>2]=0),10756}function YP(o,l){o=o|0,l=l|0,IM(o,l)}function i5e(o){o=o|0,Ro(o,s5e()|0,2)}function s5e(){return 1772}function o5e(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0;var m=0,B=0,k=0,T=0,_=0;m=I,I=I+32|0,T=m+16|0,_=m+12|0,B=m,k=ma(a5e()|0)|0,u=Ky(u)|0,n[_>>2]=n[A>>2],n[T>>2]=n[_>>2],A=ud(T)|0,n[B>>2]=n[d>>2],T=d+4|0,n[B+4>>2]=n[T>>2],_=d+8|0,n[B+8>>2]=n[_>>2],n[_>>2]=0,n[T>>2]=0,n[d>>2]=0,YP(o,Mi(0,k|0,l|0,u|0,A|0,t_(B)|0)|0),sp(B),I=m}function a5e(){var o=0;return s[8008]|0||(l5e(10768),o=8008,n[o>>2]=1,n[o+4>>2]=0),10768}function l5e(o){o=o|0,Ro(o,c5e()|0,3)}function c5e(){return 1784}function u5e(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0;var m=0,B=0,k=0,T=0;m=I,I=I+16|0,k=m+4|0,T=m,B=ma(f5e()|0)|0,u=Ky(u)|0,n[T>>2]=n[A>>2],n[k>>2]=n[T>>2],A=ud(k)|0,YP(o,Mi(0,B|0,l|0,u|0,A|0,e_(d)|0)|0),I=m}function f5e(){var o=0;return s[8016]|0||(A5e(10780),o=8016,n[o>>2]=1,n[o+4>>2]=0),10780}function A5e(o){o=o|0,Ro(o,p5e()|0,3)}function p5e(){return 1800}function h5e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=ma(g5e()|0)|0,YP(o,dn(0,A|0,l|0,Ky(u)|0)|0)}function g5e(){var o=0;return s[8024]|0||(d5e(10792),o=8024,n[o>>2]=1,n[o+4>>2]=0),10792}function d5e(o){o=o|0,Ro(o,m5e()|0,1)}function m5e(){return 1816}function y5e(){E5e(),I5e(),C5e()}function E5e(){n[2702]=e$(65536)|0}function I5e(){H5e(10856)}function C5e(){w5e(10816)}function w5e(o){o=o|0,B5e(o,5044),v5e(o)|0}function B5e(o,l){o=o|0,l=l|0;var u=0;u=dX()|0,n[o>>2]=u,N5e(u,l),Gh(n[o>>2]|0)}function v5e(o){o=o|0;var l=0;return l=n[o>>2]|0,ld(l,S5e()|0),o|0}function S5e(){var o=0;return s[8032]|0||(bX(10820),gr(64,10820,U|0)|0,o=8032,n[o>>2]=1,n[o+4>>2]=0),Ur(10820)|0||bX(10820),10820}function bX(o){o=o|0,P5e(o),cd(o,25)}function D5e(o){o=o|0,b5e(o+24|0)}function b5e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),Et(u))}function P5e(o){o=o|0;var l=0;l=en()|0,tn(o,5,18,l,T5e()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function x5e(o,l){o=o|0,l=l|0,k5e(o,l)}function k5e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0;u=I,I=I+16|0,A=u,d=u+4|0,od(d,l),n[A>>2]=ad(d,l)|0,Q5e(o,A),I=u}function Q5e(o,l){o=o|0,l=l|0,PX(o+4|0,n[l>>2]|0),s[o+8>>0]=1}function PX(o,l){o=o|0,l=l|0,n[o>>2]=l}function T5e(){return 1824}function R5e(o){return o=o|0,F5e(o)|0}function F5e(o){o=o|0;var l=0,u=0,A=0,d=0,m=0,B=0,k=0;return u=I,I=I+16|0,d=u+4|0,B=u,A=Fl(8)|0,l=A,k=Jt(4)|0,od(d,o),PX(k,ad(d,o)|0),m=l+4|0,n[m>>2]=k,o=Jt(8)|0,m=n[m>>2]|0,n[B>>2]=0,n[d>>2]=n[B>>2],mX(o,m,d),n[A>>2]=o,I=u,l|0}function Fl(o){o=o|0;var l=0,u=0;return o=o+7&-8,o>>>0<=32768&&(l=n[2701]|0,o>>>0<=(65536-l|0)>>>0)?(u=(n[2702]|0)+l|0,n[2701]=l+o,o=u):(o=e$(o+8|0)|0,n[o>>2]=n[2703],n[2703]=o,o=o+8|0),o|0}function N5e(o,l){o=o|0,l=l|0,n[o>>2]=O5e()|0,n[o+4>>2]=L5e()|0,n[o+12>>2]=l,n[o+8>>2]=M5e()|0,n[o+32>>2]=9}function O5e(){return 11744}function L5e(){return 1832}function M5e(){return qP()|0}function _5e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,(qh(A,896)|0)==512?u|0&&(U5e(u),Et(u)):l|0&&Et(l)}function U5e(o){o=o|0,o=n[o+4>>2]|0,o|0&&Wh(o)}function H5e(o){o=o|0,j5e(o,5052),q5e(o)|0,G5e(o,5058,26)|0,W5e(o,5069,1)|0,Y5e(o,5077,10)|0,V5e(o,5087,19)|0,K5e(o,5094,27)|0}function j5e(o,l){o=o|0,l=l|0;var u=0;u=UWe()|0,n[o>>2]=u,HWe(u,l),Gh(n[o>>2]|0)}function q5e(o){o=o|0;var l=0;return l=n[o>>2]|0,ld(l,DWe()|0),o|0}function G5e(o,l,u){return o=o|0,l=l|0,u=u|0,lWe(o,Bn(l)|0,u,0),o|0}function W5e(o,l,u){return o=o|0,l=l|0,u=u|0,V9e(o,Bn(l)|0,u,0),o|0}function Y5e(o,l,u){return o=o|0,l=l|0,u=u|0,S9e(o,Bn(l)|0,u,0),o|0}function V5e(o,l,u){return o=o|0,l=l|0,u=u|0,u9e(o,Bn(l)|0,u,0),o|0}function xX(o,l){o=o|0,l=l|0;var u=0,A=0;e:for(;;){for(u=n[2703]|0;;){if((u|0)==(l|0))break e;if(A=n[u>>2]|0,n[2703]=A,!u)u=A;else break}Et(u)}n[2701]=o}function K5e(o,l,u){return o=o|0,l=l|0,u=u|0,J5e(o,Bn(l)|0,u,0),o|0}function J5e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=r_()|0,o=z5e(u)|0,vn(m,l,d,o,Z5e(u,A)|0,A)}function r_(){var o=0,l=0;if(s[8040]|0||(QX(10860),gr(65,10860,U|0)|0,l=8040,n[l>>2]=1,n[l+4>>2]=0),!(Ur(10860)|0)){o=10860,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));QX(10860)}return 10860}function z5e(o){return o=o|0,o|0}function Z5e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=r_()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(kX(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(X5e(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function kX(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function X5e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=$5e(o)|0,A>>>0<B>>>0)sn(o);else{T=n[o>>2]|0,M=(n[o+8>>2]|0)-T|0,_=M>>2,e9e(d,M>>3>>>0<A>>>1>>>0?_>>>0<B>>>0?B:_:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,kX(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,t9e(o,d),r9e(d),I=k;return}}function $5e(o){return o=o|0,536870911}function e9e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Jt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function t9e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function r9e(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&Et(o)}function QX(o){o=o|0,s9e(o)}function n9e(o){o=o|0,i9e(o+24|0)}function i9e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),Et(u))}function s9e(o){o=o|0;var l=0;l=en()|0,tn(o,1,11,l,o9e()|0,2),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function o9e(){return 1840}function a9e(o,l,u){o=o|0,l=l|0,u=u|0,c9e(n[(l9e(o)|0)>>2]|0,l,u)}function l9e(o){return o=o|0,(n[(r_()|0)+24>>2]|0)+(o<<3)|0}function c9e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;A=I,I=I+16|0,m=A+1|0,d=A,od(m,l),l=ad(m,l)|0,od(d,u),u=ad(d,u)|0,ap[o&31](l,u),I=A}function u9e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=n_()|0,o=f9e(u)|0,vn(m,l,d,o,A9e(u,A)|0,A)}function n_(){var o=0,l=0;if(s[8048]|0||(RX(10896),gr(66,10896,U|0)|0,l=8048,n[l>>2]=1,n[l+4>>2]=0),!(Ur(10896)|0)){o=10896,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));RX(10896)}return 10896}function f9e(o){return o=o|0,o|0}function A9e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=n_()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(TX(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(p9e(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function TX(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function p9e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=h9e(o)|0,A>>>0<B>>>0)sn(o);else{T=n[o>>2]|0,M=(n[o+8>>2]|0)-T|0,_=M>>2,g9e(d,M>>3>>>0<A>>>1>>>0?_>>>0<B>>>0?B:_:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,TX(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,d9e(o,d),m9e(d),I=k;return}}function h9e(o){return o=o|0,536870911}function g9e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Jt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function d9e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function m9e(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&Et(o)}function RX(o){o=o|0,I9e(o)}function y9e(o){o=o|0,E9e(o+24|0)}function E9e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),Et(u))}function I9e(o){o=o|0;var l=0;l=en()|0,tn(o,1,11,l,C9e()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function C9e(){return 1852}function w9e(o,l){return o=o|0,l=l|0,v9e(n[(B9e(o)|0)>>2]|0,l)|0}function B9e(o){return o=o|0,(n[(n_()|0)+24>>2]|0)+(o<<3)|0}function v9e(o,l){o=o|0,l=l|0;var u=0,A=0;return u=I,I=I+16|0,A=u,od(A,l),l=ad(A,l)|0,l=HP(hd[o&31](l)|0)|0,I=u,l|0}function S9e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=i_()|0,o=D9e(u)|0,vn(m,l,d,o,b9e(u,A)|0,A)}function i_(){var o=0,l=0;if(s[8056]|0||(NX(10932),gr(67,10932,U|0)|0,l=8056,n[l>>2]=1,n[l+4>>2]=0),!(Ur(10932)|0)){o=10932,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));NX(10932)}return 10932}function D9e(o){return o=o|0,o|0}function b9e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=i_()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(FX(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(P9e(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function FX(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function P9e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=x9e(o)|0,A>>>0<B>>>0)sn(o);else{T=n[o>>2]|0,M=(n[o+8>>2]|0)-T|0,_=M>>2,k9e(d,M>>3>>>0<A>>>1>>>0?_>>>0<B>>>0?B:_:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,FX(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,Q9e(o,d),T9e(d),I=k;return}}function x9e(o){return o=o|0,536870911}function k9e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Jt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function Q9e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function T9e(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&Et(o)}function NX(o){o=o|0,N9e(o)}function R9e(o){o=o|0,F9e(o+24|0)}function F9e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),Et(u))}function N9e(o){o=o|0;var l=0;l=en()|0,tn(o,1,7,l,O9e()|0,2),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function O9e(){return 1860}function L9e(o,l,u){return o=o|0,l=l|0,u=u|0,_9e(n[(M9e(o)|0)>>2]|0,l,u)|0}function M9e(o){return o=o|0,(n[(i_()|0)+24>>2]|0)+(o<<3)|0}function _9e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0;return A=I,I=I+32|0,B=A+12|0,m=A+8|0,k=A,T=A+16|0,d=A+4|0,U9e(T,l),H9e(k,T,l),Uh(d,u),u=Hh(d,u)|0,n[B>>2]=n[k>>2],F2[o&15](m,B,u),u=j9e(m)|0,bf(m),jh(d),I=A,u|0}function U9e(o,l){o=o|0,l=l|0}function H9e(o,l,u){o=o|0,l=l|0,u=u|0,q9e(o,u)}function j9e(o){return o=o|0,Ms(o)|0}function q9e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0;d=I,I=I+16|0,u=d,A=l,A&1?(G9e(u,0),Me(A|0,u|0)|0,W9e(o,u),Y9e(u)):n[o>>2]=n[l>>2],I=d}function G9e(o,l){o=o|0,l=l|0,bu(o,l),n[o+4>>2]=0,s[o+8>>0]=0}function W9e(o,l){o=o|0,l=l|0,n[o>>2]=n[l+4>>2]}function Y9e(o){o=o|0,s[o+8>>0]=0}function V9e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=s_()|0,o=K9e(u)|0,vn(m,l,d,o,J9e(u,A)|0,A)}function s_(){var o=0,l=0;if(s[8064]|0||(LX(10968),gr(68,10968,U|0)|0,l=8064,n[l>>2]=1,n[l+4>>2]=0),!(Ur(10968)|0)){o=10968,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));LX(10968)}return 10968}function K9e(o){return o=o|0,o|0}function J9e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=s_()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(OX(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(z9e(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function OX(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function z9e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=Z9e(o)|0,A>>>0<B>>>0)sn(o);else{T=n[o>>2]|0,M=(n[o+8>>2]|0)-T|0,_=M>>2,X9e(d,M>>3>>>0<A>>>1>>>0?_>>>0<B>>>0?B:_:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,OX(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,$9e(o,d),eWe(d),I=k;return}}function Z9e(o){return o=o|0,536870911}function X9e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Jt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function $9e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function eWe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&Et(o)}function LX(o){o=o|0,nWe(o)}function tWe(o){o=o|0,rWe(o+24|0)}function rWe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),Et(u))}function nWe(o){o=o|0;var l=0;l=en()|0,tn(o,1,1,l,iWe()|0,5),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function iWe(){return 1872}function sWe(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,aWe(n[(oWe(o)|0)>>2]|0,l,u,A,d,m)}function oWe(o){return o=o|0,(n[(s_()|0)+24>>2]|0)+(o<<3)|0}function aWe(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0;var B=0,k=0,T=0,_=0,M=0,G=0;B=I,I=I+32|0,k=B+16|0,T=B+12|0,_=B+8|0,M=B+4|0,G=B,Uh(k,l),l=Hh(k,l)|0,Uh(T,u),u=Hh(T,u)|0,Uh(_,A),A=Hh(_,A)|0,Uh(M,d),d=Hh(M,d)|0,Uh(G,m),m=Hh(G,m)|0,s$[o&1](l,u,A,d,m),jh(G),jh(M),jh(_),jh(T),jh(k),I=B}function lWe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=o_()|0,o=cWe(u)|0,vn(m,l,d,o,uWe(u,A)|0,A)}function o_(){var o=0,l=0;if(s[8072]|0||(_X(11004),gr(69,11004,U|0)|0,l=8072,n[l>>2]=1,n[l+4>>2]=0),!(Ur(11004)|0)){o=11004,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));_X(11004)}return 11004}function cWe(o){return o=o|0,o|0}function uWe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=o_()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(MX(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(fWe(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function MX(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function fWe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=AWe(o)|0,A>>>0<B>>>0)sn(o);else{T=n[o>>2]|0,M=(n[o+8>>2]|0)-T|0,_=M>>2,pWe(d,M>>3>>>0<A>>>1>>>0?_>>>0<B>>>0?B:_:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,MX(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,hWe(o,d),gWe(d),I=k;return}}function AWe(o){return o=o|0,536870911}function pWe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Jt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function hWe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function gWe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&Et(o)}function _X(o){o=o|0,yWe(o)}function dWe(o){o=o|0,mWe(o+24|0)}function mWe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),Et(u))}function yWe(o){o=o|0;var l=0;l=en()|0,tn(o,1,12,l,EWe()|0,2),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function EWe(){return 1896}function IWe(o,l,u){o=o|0,l=l|0,u=u|0,wWe(n[(CWe(o)|0)>>2]|0,l,u)}function CWe(o){return o=o|0,(n[(o_()|0)+24>>2]|0)+(o<<3)|0}function wWe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;A=I,I=I+16|0,m=A+4|0,d=A,BWe(m,l),l=vWe(m,l)|0,Uh(d,u),u=Hh(d,u)|0,ap[o&31](l,u),jh(d),I=A}function BWe(o,l){o=o|0,l=l|0}function vWe(o,l){return o=o|0,l=l|0,SWe(l)|0}function SWe(o){return o=o|0,o|0}function DWe(){var o=0;return s[8080]|0||(UX(11040),gr(70,11040,U|0)|0,o=8080,n[o>>2]=1,n[o+4>>2]=0),Ur(11040)|0||UX(11040),11040}function UX(o){o=o|0,xWe(o),cd(o,71)}function bWe(o){o=o|0,PWe(o+24|0)}function PWe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),Et(u))}function xWe(o){o=o|0;var l=0;l=en()|0,tn(o,5,7,l,RWe()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function kWe(o){o=o|0,QWe(o)}function QWe(o){o=o|0,TWe(o)}function TWe(o){o=o|0,s[o+8>>0]=1}function RWe(){return 1936}function FWe(){return NWe()|0}function NWe(){var o=0,l=0,u=0,A=0,d=0,m=0,B=0;return l=I,I=I+16|0,d=l+4|0,B=l,u=Fl(8)|0,o=u,m=o+4|0,n[m>>2]=Jt(1)|0,A=Jt(8)|0,m=n[m>>2]|0,n[B>>2]=0,n[d>>2]=n[B>>2],OWe(A,m,d),n[u>>2]=A,I=l,o|0}function OWe(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,u=Jt(16)|0,n[u+4>>2]=0,n[u+8>>2]=0,n[u>>2]=1916,n[u+12>>2]=l,n[o+4>>2]=u}function LWe(o){o=o|0,Zy(o),Et(o)}function MWe(o){o=o|0,o=n[o+12>>2]|0,o|0&&Et(o)}function _We(o){o=o|0,Et(o)}function UWe(){var o=0;return s[8088]|0||(VWe(11076),gr(25,11076,U|0)|0,o=8088,n[o>>2]=1,n[o+4>>2]=0),11076}function HWe(o,l){o=o|0,l=l|0,n[o>>2]=jWe()|0,n[o+4>>2]=qWe()|0,n[o+12>>2]=l,n[o+8>>2]=GWe()|0,n[o+32>>2]=10}function jWe(){return 11745}function qWe(){return 1940}function GWe(){return jP()|0}function WWe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,(qh(A,896)|0)==512?u|0&&(YWe(u),Et(u)):l|0&&Et(l)}function YWe(o){o=o|0,o=n[o+4>>2]|0,o|0&&Wh(o)}function VWe(o){o=o|0,_h(o)}function xu(o,l){o=o|0,l=l|0,n[o>>2]=l}function a_(o){return o=o|0,n[o>>2]|0}function KWe(o){return o=o|0,s[n[o>>2]>>0]|0}function JWe(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,n[A>>2]=n[o>>2],zWe(l,A)|0,I=u}function zWe(o,l){o=o|0,l=l|0;var u=0;return u=ZWe(n[o>>2]|0,l)|0,l=o+4|0,n[(n[l>>2]|0)+8>>2]=u,n[(n[l>>2]|0)+8>>2]|0}function ZWe(o,l){o=o|0,l=l|0;var u=0,A=0;return u=I,I=I+16|0,A=u,Nl(A),o=Ms(o)|0,l=XWe(o,n[l>>2]|0)|0,Ol(A),I=u,l|0}function Nl(o){o=o|0,n[o>>2]=n[2701],n[o+4>>2]=n[2703]}function XWe(o,l){o=o|0,l=l|0;var u=0;return u=ma($We()|0)|0,dn(0,u|0,o|0,e_(l)|0)|0}function Ol(o){o=o|0,xX(n[o>>2]|0,n[o+4>>2]|0)}function $We(){var o=0;return s[8096]|0||(eYe(11120),o=8096,n[o>>2]=1,n[o+4>>2]=0),11120}function eYe(o){o=o|0,Ro(o,tYe()|0,1)}function tYe(){return 1948}function rYe(){nYe()}function nYe(){var o=0,l=0,u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0,We=0,Le=0,Qe=0;if(Le=I,I=I+16|0,M=Le+4|0,G=Le,aa(65536,10804,n[2702]|0,10812),u=uX()|0,l=n[u>>2]|0,o=n[l>>2]|0,o|0)for(A=n[u+8>>2]|0,u=n[u+4>>2]|0;hf(o|0,c[u>>0]|0|0,s[A>>0]|0),l=l+4|0,o=n[l>>2]|0,o;)A=A+1|0,u=u+1|0;if(o=fX()|0,l=n[o>>2]|0,l|0)do LA(l|0,n[o+4>>2]|0),o=o+8|0,l=n[o>>2]|0;while(l|0);LA(iYe()|0,5167),_=Yy()|0,o=n[_>>2]|0;e:do if(o|0){do sYe(n[o+4>>2]|0),o=n[o>>2]|0;while(o|0);if(o=n[_>>2]|0,o|0){T=_;do{for(;d=o,o=n[o>>2]|0,d=n[d+4>>2]|0,!!(oYe(d)|0);)if(n[G>>2]=T,n[M>>2]=n[G>>2],aYe(_,M)|0,!o)break e;if(lYe(d),T=n[T>>2]|0,l=HX(d)|0,m=Li()|0,B=I,I=I+((1*(l<<2)|0)+15&-16)|0,k=I,I=I+((1*(l<<2)|0)+15&-16)|0,l=n[(BX(d)|0)>>2]|0,l|0)for(u=B,A=k;n[u>>2]=n[(Vy(n[l+4>>2]|0)|0)>>2],n[A>>2]=n[l+8>>2],l=n[l>>2]|0,l;)u=u+4|0,A=A+4|0;Qe=Vy(d)|0,l=cYe(d)|0,u=HX(d)|0,A=uYe(d)|0,ac(Qe|0,l|0,B|0,k|0,u|0,A|0,KM(d)|0),OA(m|0)}while(o|0)}}while(!1);if(o=n[(JM()|0)>>2]|0,o|0)do Qe=o+4|0,_=zM(Qe)|0,d=k2(_)|0,m=P2(_)|0,B=(x2(_)|0)+1|0,k=VP(_)|0,T=jX(Qe)|0,_=Ur(_)|0,M=GP(Qe)|0,G=l_(Qe)|0,Au(0,d|0,m|0,B|0,k|0,T|0,_|0,M|0,G|0,c_(Qe)|0),o=n[o>>2]|0;while(o|0);o=n[(Yy()|0)>>2]|0;e:do if(o|0){t:for(;;){if(l=n[o+4>>2]|0,l|0&&(ae=n[(Vy(l)|0)>>2]|0,We=n[(vX(l)|0)>>2]|0,We|0)){u=We;do{l=u+4|0,A=zM(l)|0;r:do if(A|0)switch(Ur(A)|0){case 0:break t;case 4:case 3:case 2:{k=k2(A)|0,T=P2(A)|0,_=(x2(A)|0)+1|0,M=VP(A)|0,G=Ur(A)|0,Qe=GP(l)|0,Au(ae|0,k|0,T|0,_|0,M|0,0,G|0,Qe|0,l_(l)|0,c_(l)|0);break r}case 1:{B=k2(A)|0,k=P2(A)|0,T=(x2(A)|0)+1|0,_=VP(A)|0,M=jX(l)|0,G=Ur(A)|0,Qe=GP(l)|0,Au(ae|0,B|0,k|0,T|0,_|0,M|0,G|0,Qe|0,l_(l)|0,c_(l)|0);break r}case 5:{_=k2(A)|0,M=P2(A)|0,G=(x2(A)|0)+1|0,Qe=VP(A)|0,Au(ae|0,_|0,M|0,G|0,Qe|0,fYe(A)|0,Ur(A)|0,0,0,0);break r}default:break r}while(!1);u=n[u>>2]|0}while(u|0)}if(o=n[o>>2]|0,!o)break e}Nt()}while(!1);ve(),I=Le}function iYe(){return 11703}function sYe(o){o=o|0,s[o+40>>0]=0}function oYe(o){return o=o|0,(s[o+40>>0]|0)!=0|0}function aYe(o,l){return o=o|0,l=l|0,l=AYe(l)|0,o=n[l>>2]|0,n[l>>2]=n[o>>2],Et(o),n[l>>2]|0}function lYe(o){o=o|0,s[o+40>>0]=1}function HX(o){return o=o|0,n[o+20>>2]|0}function cYe(o){return o=o|0,n[o+8>>2]|0}function uYe(o){return o=o|0,n[o+32>>2]|0}function VP(o){return o=o|0,n[o+4>>2]|0}function jX(o){return o=o|0,n[o+4>>2]|0}function l_(o){return o=o|0,n[o+8>>2]|0}function c_(o){return o=o|0,n[o+16>>2]|0}function fYe(o){return o=o|0,n[o+20>>2]|0}function AYe(o){return o=o|0,n[o>>2]|0}function KP(o){o=o|0;var l=0,u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0,We=0,Le=0,Qe=0,tt=0,Ze=0,ct=0,He=0,Ge=0,Lt=0;Lt=I,I=I+16|0,ae=Lt;do if(o>>>0<245){if(_=o>>>0<11?16:o+11&-8,o=_>>>3,G=n[2783]|0,u=G>>>o,u&3|0)return l=(u&1^1)+o|0,o=11172+(l<<1<<2)|0,u=o+8|0,A=n[u>>2]|0,d=A+8|0,m=n[d>>2]|0,(o|0)==(m|0)?n[2783]=G&~(1<<l):(n[m+12>>2]=o,n[u>>2]=m),Ge=l<<3,n[A+4>>2]=Ge|3,Ge=A+Ge+4|0,n[Ge>>2]=n[Ge>>2]|1,Ge=d,I=Lt,Ge|0;if(M=n[2785]|0,_>>>0>M>>>0){if(u|0)return l=2<<o,l=u<<o&(l|0-l),l=(l&0-l)+-1|0,B=l>>>12&16,l=l>>>B,u=l>>>5&8,l=l>>>u,d=l>>>2&4,l=l>>>d,o=l>>>1&2,l=l>>>o,A=l>>>1&1,A=(u|B|d|o|A)+(l>>>A)|0,l=11172+(A<<1<<2)|0,o=l+8|0,d=n[o>>2]|0,B=d+8|0,u=n[B>>2]|0,(l|0)==(u|0)?(o=G&~(1<<A),n[2783]=o):(n[u+12>>2]=l,n[o>>2]=u,o=G),m=(A<<3)-_|0,n[d+4>>2]=_|3,A=d+_|0,n[A+4>>2]=m|1,n[A+m>>2]=m,M|0&&(d=n[2788]|0,l=M>>>3,u=11172+(l<<1<<2)|0,l=1<<l,o&l?(o=u+8|0,l=n[o>>2]|0):(n[2783]=o|l,l=u,o=u+8|0),n[o>>2]=d,n[l+12>>2]=d,n[d+8>>2]=l,n[d+12>>2]=u),n[2785]=m,n[2788]=A,Ge=B,I=Lt,Ge|0;if(k=n[2784]|0,k){if(u=(k&0-k)+-1|0,B=u>>>12&16,u=u>>>B,m=u>>>5&8,u=u>>>m,T=u>>>2&4,u=u>>>T,A=u>>>1&2,u=u>>>A,o=u>>>1&1,o=n[11436+((m|B|T|A|o)+(u>>>o)<<2)>>2]|0,u=(n[o+4>>2]&-8)-_|0,A=n[o+16+(((n[o+16>>2]|0)==0&1)<<2)>>2]|0,!A)T=o,m=u;else{do B=(n[A+4>>2]&-8)-_|0,T=B>>>0<u>>>0,u=T?B:u,o=T?A:o,A=n[A+16+(((n[A+16>>2]|0)==0&1)<<2)>>2]|0;while(A|0);T=o,m=u}if(B=T+_|0,T>>>0<B>>>0){d=n[T+24>>2]|0,l=n[T+12>>2]|0;do if((l|0)==(T|0)){if(o=T+20|0,l=n[o>>2]|0,!l&&(o=T+16|0,l=n[o>>2]|0,!l)){u=0;break}for(;;){if(u=l+20|0,A=n[u>>2]|0,A|0){l=A,o=u;continue}if(u=l+16|0,A=n[u>>2]|0,A)l=A,o=u;else break}n[o>>2]=0,u=l}else u=n[T+8>>2]|0,n[u+12>>2]=l,n[l+8>>2]=u,u=l;while(!1);do if(d|0){if(l=n[T+28>>2]|0,o=11436+(l<<2)|0,(T|0)==(n[o>>2]|0)){if(n[o>>2]=u,!u){n[2784]=k&~(1<<l);break}}else if(n[d+16+(((n[d+16>>2]|0)!=(T|0)&1)<<2)>>2]=u,!u)break;n[u+24>>2]=d,l=n[T+16>>2]|0,l|0&&(n[u+16>>2]=l,n[l+24>>2]=u),l=n[T+20>>2]|0,l|0&&(n[u+20>>2]=l,n[l+24>>2]=u)}while(!1);return m>>>0<16?(Ge=m+_|0,n[T+4>>2]=Ge|3,Ge=T+Ge+4|0,n[Ge>>2]=n[Ge>>2]|1):(n[T+4>>2]=_|3,n[B+4>>2]=m|1,n[B+m>>2]=m,M|0&&(A=n[2788]|0,l=M>>>3,u=11172+(l<<1<<2)|0,l=1<<l,G&l?(o=u+8|0,l=n[o>>2]|0):(n[2783]=G|l,l=u,o=u+8|0),n[o>>2]=A,n[l+12>>2]=A,n[A+8>>2]=l,n[A+12>>2]=u),n[2785]=m,n[2788]=B),Ge=T+8|0,I=Lt,Ge|0}else G=_}else G=_}else G=_}else if(o>>>0<=4294967231)if(o=o+11|0,_=o&-8,T=n[2784]|0,T){A=0-_|0,o=o>>>8,o?_>>>0>16777215?k=31:(G=(o+1048320|0)>>>16&8,He=o<<G,M=(He+520192|0)>>>16&4,He=He<<M,k=(He+245760|0)>>>16&2,k=14-(M|G|k)+(He<<k>>>15)|0,k=_>>>(k+7|0)&1|k<<1):k=0,u=n[11436+(k<<2)>>2]|0;e:do if(!u)u=0,o=0,He=57;else for(o=0,B=_<<((k|0)==31?0:25-(k>>>1)|0),m=0;;){if(d=(n[u+4>>2]&-8)-_|0,d>>>0<A>>>0)if(d)o=u,A=d;else{o=u,A=0,d=u,He=61;break e}if(d=n[u+20>>2]|0,u=n[u+16+(B>>>31<<2)>>2]|0,m=(d|0)==0|(d|0)==(u|0)?m:d,d=(u|0)==0,d){u=m,He=57;break}else B=B<<((d^1)&1)}while(!1);if((He|0)==57){if((u|0)==0&(o|0)==0){if(o=2<<k,o=T&(o|0-o),!o){G=_;break}G=(o&0-o)+-1|0,B=G>>>12&16,G=G>>>B,m=G>>>5&8,G=G>>>m,k=G>>>2&4,G=G>>>k,M=G>>>1&2,G=G>>>M,u=G>>>1&1,o=0,u=n[11436+((m|B|k|M|u)+(G>>>u)<<2)>>2]|0}u?(d=u,He=61):(k=o,B=A)}if((He|0)==61)for(;;)if(He=0,u=(n[d+4>>2]&-8)-_|0,G=u>>>0<A>>>0,u=G?u:A,o=G?d:o,d=n[d+16+(((n[d+16>>2]|0)==0&1)<<2)>>2]|0,d)A=u,He=61;else{k=o,B=u;break}if(k|0&&B>>>0<((n[2785]|0)-_|0)>>>0){if(m=k+_|0,k>>>0>=m>>>0)return Ge=0,I=Lt,Ge|0;d=n[k+24>>2]|0,l=n[k+12>>2]|0;do if((l|0)==(k|0)){if(o=k+20|0,l=n[o>>2]|0,!l&&(o=k+16|0,l=n[o>>2]|0,!l)){l=0;break}for(;;){if(u=l+20|0,A=n[u>>2]|0,A|0){l=A,o=u;continue}if(u=l+16|0,A=n[u>>2]|0,A)l=A,o=u;else break}n[o>>2]=0}else Ge=n[k+8>>2]|0,n[Ge+12>>2]=l,n[l+8>>2]=Ge;while(!1);do if(d){if(o=n[k+28>>2]|0,u=11436+(o<<2)|0,(k|0)==(n[u>>2]|0)){if(n[u>>2]=l,!l){A=T&~(1<<o),n[2784]=A;break}}else if(n[d+16+(((n[d+16>>2]|0)!=(k|0)&1)<<2)>>2]=l,!l){A=T;break}n[l+24>>2]=d,o=n[k+16>>2]|0,o|0&&(n[l+16>>2]=o,n[o+24>>2]=l),o=n[k+20>>2]|0,o&&(n[l+20>>2]=o,n[o+24>>2]=l),A=T}else A=T;while(!1);do if(B>>>0>=16){if(n[k+4>>2]=_|3,n[m+4>>2]=B|1,n[m+B>>2]=B,l=B>>>3,B>>>0<256){u=11172+(l<<1<<2)|0,o=n[2783]|0,l=1<<l,o&l?(o=u+8|0,l=n[o>>2]|0):(n[2783]=o|l,l=u,o=u+8|0),n[o>>2]=m,n[l+12>>2]=m,n[m+8>>2]=l,n[m+12>>2]=u;break}if(l=B>>>8,l?B>>>0>16777215?l=31:(He=(l+1048320|0)>>>16&8,Ge=l<<He,ct=(Ge+520192|0)>>>16&4,Ge=Ge<<ct,l=(Ge+245760|0)>>>16&2,l=14-(ct|He|l)+(Ge<<l>>>15)|0,l=B>>>(l+7|0)&1|l<<1):l=0,u=11436+(l<<2)|0,n[m+28>>2]=l,o=m+16|0,n[o+4>>2]=0,n[o>>2]=0,o=1<<l,!(A&o)){n[2784]=A|o,n[u>>2]=m,n[m+24>>2]=u,n[m+12>>2]=m,n[m+8>>2]=m;break}for(o=B<<((l|0)==31?0:25-(l>>>1)|0),u=n[u>>2]|0;;){if((n[u+4>>2]&-8|0)==(B|0)){He=97;break}if(A=u+16+(o>>>31<<2)|0,l=n[A>>2]|0,l)o=o<<1,u=l;else{He=96;break}}if((He|0)==96){n[A>>2]=m,n[m+24>>2]=u,n[m+12>>2]=m,n[m+8>>2]=m;break}else if((He|0)==97){He=u+8|0,Ge=n[He>>2]|0,n[Ge+12>>2]=m,n[He>>2]=m,n[m+8>>2]=Ge,n[m+12>>2]=u,n[m+24>>2]=0;break}}else Ge=B+_|0,n[k+4>>2]=Ge|3,Ge=k+Ge+4|0,n[Ge>>2]=n[Ge>>2]|1;while(!1);return Ge=k+8|0,I=Lt,Ge|0}else G=_}else G=_;else G=-1;while(!1);if(u=n[2785]|0,u>>>0>=G>>>0)return l=u-G|0,o=n[2788]|0,l>>>0>15?(Ge=o+G|0,n[2788]=Ge,n[2785]=l,n[Ge+4>>2]=l|1,n[Ge+l>>2]=l,n[o+4>>2]=G|3):(n[2785]=0,n[2788]=0,n[o+4>>2]=u|3,Ge=o+u+4|0,n[Ge>>2]=n[Ge>>2]|1),Ge=o+8|0,I=Lt,Ge|0;if(B=n[2786]|0,B>>>0>G>>>0)return ct=B-G|0,n[2786]=ct,Ge=n[2789]|0,He=Ge+G|0,n[2789]=He,n[He+4>>2]=ct|1,n[Ge+4>>2]=G|3,Ge=Ge+8|0,I=Lt,Ge|0;if(n[2901]|0?o=n[2903]|0:(n[2903]=4096,n[2902]=4096,n[2904]=-1,n[2905]=-1,n[2906]=0,n[2894]=0,o=ae&-16^1431655768,n[ae>>2]=o,n[2901]=o,o=4096),k=G+48|0,T=G+47|0,m=o+T|0,d=0-o|0,_=m&d,_>>>0<=G>>>0||(o=n[2893]|0,o|0&&(M=n[2891]|0,ae=M+_|0,ae>>>0<=M>>>0|ae>>>0>o>>>0)))return Ge=0,I=Lt,Ge|0;e:do if(n[2894]&4)l=0,He=133;else{u=n[2789]|0;t:do if(u){for(A=11580;o=n[A>>2]|0,!(o>>>0<=u>>>0&&(Qe=A+4|0,(o+(n[Qe>>2]|0)|0)>>>0>u>>>0));)if(o=n[A+8>>2]|0,o)A=o;else{He=118;break t}if(l=m-B&d,l>>>0<2147483647)if(o=Yh(l|0)|0,(o|0)==((n[A>>2]|0)+(n[Qe>>2]|0)|0)){if((o|0)!=-1){B=l,m=o,He=135;break e}}else A=o,He=126;else l=0}else He=118;while(!1);do if((He|0)==118)if(u=Yh(0)|0,(u|0)!=-1&&(l=u,We=n[2902]|0,Le=We+-1|0,l=(Le&l|0?(Le+l&0-We)-l|0:0)+_|0,We=n[2891]|0,Le=l+We|0,l>>>0>G>>>0&l>>>0<2147483647)){if(Qe=n[2893]|0,Qe|0&&Le>>>0<=We>>>0|Le>>>0>Qe>>>0){l=0;break}if(o=Yh(l|0)|0,(o|0)==(u|0)){B=l,m=u,He=135;break e}else A=o,He=126}else l=0;while(!1);do if((He|0)==126){if(u=0-l|0,!(k>>>0>l>>>0&(l>>>0<2147483647&(A|0)!=-1)))if((A|0)==-1){l=0;break}else{B=l,m=A,He=135;break e}if(o=n[2903]|0,o=T-l+o&0-o,o>>>0>=2147483647){B=l,m=A,He=135;break e}if((Yh(o|0)|0)==-1){Yh(u|0)|0,l=0;break}else{B=o+l|0,m=A,He=135;break e}}while(!1);n[2894]=n[2894]|4,He=133}while(!1);if((He|0)==133&&_>>>0<2147483647&&(ct=Yh(_|0)|0,Qe=Yh(0)|0,tt=Qe-ct|0,Ze=tt>>>0>(G+40|0)>>>0,!((ct|0)==-1|Ze^1|ct>>>0<Qe>>>0&((ct|0)!=-1&(Qe|0)!=-1)^1))&&(B=Ze?tt:l,m=ct,He=135),(He|0)==135){l=(n[2891]|0)+B|0,n[2891]=l,l>>>0>(n[2892]|0)>>>0&&(n[2892]=l),T=n[2789]|0;do if(T){for(l=11580;;){if(o=n[l>>2]|0,u=l+4|0,A=n[u>>2]|0,(m|0)==(o+A|0)){He=145;break}if(d=n[l+8>>2]|0,d)l=d;else break}if((He|0)==145&&!(n[l+12>>2]&8|0)&&T>>>0<m>>>0&T>>>0>=o>>>0){n[u>>2]=A+B,Ge=T+8|0,Ge=Ge&7|0?0-Ge&7:0,He=T+Ge|0,Ge=(n[2786]|0)+(B-Ge)|0,n[2789]=He,n[2786]=Ge,n[He+4>>2]=Ge|1,n[He+Ge+4>>2]=40,n[2790]=n[2905];break}for(m>>>0<(n[2787]|0)>>>0&&(n[2787]=m),u=m+B|0,l=11580;;){if((n[l>>2]|0)==(u|0)){He=153;break}if(o=n[l+8>>2]|0,o)l=o;else break}if((He|0)==153&&!(n[l+12>>2]&8|0)){n[l>>2]=m,M=l+4|0,n[M>>2]=(n[M>>2]|0)+B,M=m+8|0,M=m+(M&7|0?0-M&7:0)|0,l=u+8|0,l=u+(l&7|0?0-l&7:0)|0,_=M+G|0,k=l-M-G|0,n[M+4>>2]=G|3;do if((l|0)!=(T|0)){if((l|0)==(n[2788]|0)){Ge=(n[2785]|0)+k|0,n[2785]=Ge,n[2788]=_,n[_+4>>2]=Ge|1,n[_+Ge>>2]=Ge;break}if(o=n[l+4>>2]|0,(o&3|0)==1){B=o&-8,A=o>>>3;e:do if(o>>>0<256)if(o=n[l+8>>2]|0,u=n[l+12>>2]|0,(u|0)==(o|0)){n[2783]=n[2783]&~(1<<A);break}else{n[o+12>>2]=u,n[u+8>>2]=o;break}else{m=n[l+24>>2]|0,o=n[l+12>>2]|0;do if((o|0)==(l|0)){if(A=l+16|0,u=A+4|0,o=n[u>>2]|0,!o)if(o=n[A>>2]|0,o)u=A;else{o=0;break}for(;;){if(A=o+20|0,d=n[A>>2]|0,d|0){o=d,u=A;continue}if(A=o+16|0,d=n[A>>2]|0,d)o=d,u=A;else break}n[u>>2]=0}else Ge=n[l+8>>2]|0,n[Ge+12>>2]=o,n[o+8>>2]=Ge;while(!1);if(!m)break;u=n[l+28>>2]|0,A=11436+(u<<2)|0;do if((l|0)!=(n[A>>2]|0)){if(n[m+16+(((n[m+16>>2]|0)!=(l|0)&1)<<2)>>2]=o,!o)break e}else{if(n[A>>2]=o,o|0)break;n[2784]=n[2784]&~(1<<u);break e}while(!1);if(n[o+24>>2]=m,u=l+16|0,A=n[u>>2]|0,A|0&&(n[o+16>>2]=A,n[A+24>>2]=o),u=n[u+4>>2]|0,!u)break;n[o+20>>2]=u,n[u+24>>2]=o}while(!1);l=l+B|0,d=B+k|0}else d=k;if(l=l+4|0,n[l>>2]=n[l>>2]&-2,n[_+4>>2]=d|1,n[_+d>>2]=d,l=d>>>3,d>>>0<256){u=11172+(l<<1<<2)|0,o=n[2783]|0,l=1<<l,o&l?(o=u+8|0,l=n[o>>2]|0):(n[2783]=o|l,l=u,o=u+8|0),n[o>>2]=_,n[l+12>>2]=_,n[_+8>>2]=l,n[_+12>>2]=u;break}l=d>>>8;do if(!l)l=0;else{if(d>>>0>16777215){l=31;break}He=(l+1048320|0)>>>16&8,Ge=l<<He,ct=(Ge+520192|0)>>>16&4,Ge=Ge<<ct,l=(Ge+245760|0)>>>16&2,l=14-(ct|He|l)+(Ge<<l>>>15)|0,l=d>>>(l+7|0)&1|l<<1}while(!1);if(A=11436+(l<<2)|0,n[_+28>>2]=l,o=_+16|0,n[o+4>>2]=0,n[o>>2]=0,o=n[2784]|0,u=1<<l,!(o&u)){n[2784]=o|u,n[A>>2]=_,n[_+24>>2]=A,n[_+12>>2]=_,n[_+8>>2]=_;break}for(o=d<<((l|0)==31?0:25-(l>>>1)|0),u=n[A>>2]|0;;){if((n[u+4>>2]&-8|0)==(d|0)){He=194;break}if(A=u+16+(o>>>31<<2)|0,l=n[A>>2]|0,l)o=o<<1,u=l;else{He=193;break}}if((He|0)==193){n[A>>2]=_,n[_+24>>2]=u,n[_+12>>2]=_,n[_+8>>2]=_;break}else if((He|0)==194){He=u+8|0,Ge=n[He>>2]|0,n[Ge+12>>2]=_,n[He>>2]=_,n[_+8>>2]=Ge,n[_+12>>2]=u,n[_+24>>2]=0;break}}else Ge=(n[2786]|0)+k|0,n[2786]=Ge,n[2789]=_,n[_+4>>2]=Ge|1;while(!1);return Ge=M+8|0,I=Lt,Ge|0}for(l=11580;o=n[l>>2]|0,!(o>>>0<=T>>>0&&(Ge=o+(n[l+4>>2]|0)|0,Ge>>>0>T>>>0));)l=n[l+8>>2]|0;d=Ge+-47|0,o=d+8|0,o=d+(o&7|0?0-o&7:0)|0,d=T+16|0,o=o>>>0<d>>>0?T:o,l=o+8|0,u=m+8|0,u=u&7|0?0-u&7:0,He=m+u|0,u=B+-40-u|0,n[2789]=He,n[2786]=u,n[He+4>>2]=u|1,n[He+u+4>>2]=40,n[2790]=n[2905],u=o+4|0,n[u>>2]=27,n[l>>2]=n[2895],n[l+4>>2]=n[2896],n[l+8>>2]=n[2897],n[l+12>>2]=n[2898],n[2895]=m,n[2896]=B,n[2898]=0,n[2897]=l,l=o+24|0;do He=l,l=l+4|0,n[l>>2]=7;while((He+8|0)>>>0<Ge>>>0);if((o|0)!=(T|0)){if(m=o-T|0,n[u>>2]=n[u>>2]&-2,n[T+4>>2]=m|1,n[o>>2]=m,l=m>>>3,m>>>0<256){u=11172+(l<<1<<2)|0,o=n[2783]|0,l=1<<l,o&l?(o=u+8|0,l=n[o>>2]|0):(n[2783]=o|l,l=u,o=u+8|0),n[o>>2]=T,n[l+12>>2]=T,n[T+8>>2]=l,n[T+12>>2]=u;break}if(l=m>>>8,l?m>>>0>16777215?u=31:(He=(l+1048320|0)>>>16&8,Ge=l<<He,ct=(Ge+520192|0)>>>16&4,Ge=Ge<<ct,u=(Ge+245760|0)>>>16&2,u=14-(ct|He|u)+(Ge<<u>>>15)|0,u=m>>>(u+7|0)&1|u<<1):u=0,A=11436+(u<<2)|0,n[T+28>>2]=u,n[T+20>>2]=0,n[d>>2]=0,l=n[2784]|0,o=1<<u,!(l&o)){n[2784]=l|o,n[A>>2]=T,n[T+24>>2]=A,n[T+12>>2]=T,n[T+8>>2]=T;break}for(o=m<<((u|0)==31?0:25-(u>>>1)|0),u=n[A>>2]|0;;){if((n[u+4>>2]&-8|0)==(m|0)){He=216;break}if(A=u+16+(o>>>31<<2)|0,l=n[A>>2]|0,l)o=o<<1,u=l;else{He=215;break}}if((He|0)==215){n[A>>2]=T,n[T+24>>2]=u,n[T+12>>2]=T,n[T+8>>2]=T;break}else if((He|0)==216){He=u+8|0,Ge=n[He>>2]|0,n[Ge+12>>2]=T,n[He>>2]=T,n[T+8>>2]=Ge,n[T+12>>2]=u,n[T+24>>2]=0;break}}}else{Ge=n[2787]|0,(Ge|0)==0|m>>>0<Ge>>>0&&(n[2787]=m),n[2895]=m,n[2896]=B,n[2898]=0,n[2792]=n[2901],n[2791]=-1,l=0;do Ge=11172+(l<<1<<2)|0,n[Ge+12>>2]=Ge,n[Ge+8>>2]=Ge,l=l+1|0;while((l|0)!=32);Ge=m+8|0,Ge=Ge&7|0?0-Ge&7:0,He=m+Ge|0,Ge=B+-40-Ge|0,n[2789]=He,n[2786]=Ge,n[He+4>>2]=Ge|1,n[He+Ge+4>>2]=40,n[2790]=n[2905]}while(!1);if(l=n[2786]|0,l>>>0>G>>>0)return ct=l-G|0,n[2786]=ct,Ge=n[2789]|0,He=Ge+G|0,n[2789]=He,n[He+4>>2]=ct|1,n[Ge+4>>2]=G|3,Ge=Ge+8|0,I=Lt,Ge|0}return n[(Jy()|0)>>2]=12,Ge=0,I=Lt,Ge|0}function JP(o){o=o|0;var l=0,u=0,A=0,d=0,m=0,B=0,k=0,T=0;if(o){u=o+-8|0,d=n[2787]|0,o=n[o+-4>>2]|0,l=o&-8,T=u+l|0;do if(o&1)k=u,B=u;else{if(A=n[u>>2]|0,!(o&3)||(B=u+(0-A)|0,m=A+l|0,B>>>0<d>>>0))return;if((B|0)==(n[2788]|0)){if(o=T+4|0,l=n[o>>2]|0,(l&3|0)!=3){k=B,l=m;break}n[2785]=m,n[o>>2]=l&-2,n[B+4>>2]=m|1,n[B+m>>2]=m;return}if(u=A>>>3,A>>>0<256)if(o=n[B+8>>2]|0,l=n[B+12>>2]|0,(l|0)==(o|0)){n[2783]=n[2783]&~(1<<u),k=B,l=m;break}else{n[o+12>>2]=l,n[l+8>>2]=o,k=B,l=m;break}d=n[B+24>>2]|0,o=n[B+12>>2]|0;do if((o|0)==(B|0)){if(u=B+16|0,l=u+4|0,o=n[l>>2]|0,!o)if(o=n[u>>2]|0,o)l=u;else{o=0;break}for(;;){if(u=o+20|0,A=n[u>>2]|0,A|0){o=A,l=u;continue}if(u=o+16|0,A=n[u>>2]|0,A)o=A,l=u;else break}n[l>>2]=0}else k=n[B+8>>2]|0,n[k+12>>2]=o,n[o+8>>2]=k;while(!1);if(d){if(l=n[B+28>>2]|0,u=11436+(l<<2)|0,(B|0)==(n[u>>2]|0)){if(n[u>>2]=o,!o){n[2784]=n[2784]&~(1<<l),k=B,l=m;break}}else if(n[d+16+(((n[d+16>>2]|0)!=(B|0)&1)<<2)>>2]=o,!o){k=B,l=m;break}n[o+24>>2]=d,l=B+16|0,u=n[l>>2]|0,u|0&&(n[o+16>>2]=u,n[u+24>>2]=o),l=n[l+4>>2]|0,l?(n[o+20>>2]=l,n[l+24>>2]=o,k=B,l=m):(k=B,l=m)}else k=B,l=m}while(!1);if(!(B>>>0>=T>>>0)&&(o=T+4|0,A=n[o>>2]|0,!!(A&1))){if(A&2)n[o>>2]=A&-2,n[k+4>>2]=l|1,n[B+l>>2]=l,d=l;else{if(o=n[2788]|0,(T|0)==(n[2789]|0)){if(T=(n[2786]|0)+l|0,n[2786]=T,n[2789]=k,n[k+4>>2]=T|1,(k|0)!=(o|0))return;n[2788]=0,n[2785]=0;return}if((T|0)==(o|0)){T=(n[2785]|0)+l|0,n[2785]=T,n[2788]=B,n[k+4>>2]=T|1,n[B+T>>2]=T;return}d=(A&-8)+l|0,u=A>>>3;do if(A>>>0<256)if(l=n[T+8>>2]|0,o=n[T+12>>2]|0,(o|0)==(l|0)){n[2783]=n[2783]&~(1<<u);break}else{n[l+12>>2]=o,n[o+8>>2]=l;break}else{m=n[T+24>>2]|0,o=n[T+12>>2]|0;do if((o|0)==(T|0)){if(u=T+16|0,l=u+4|0,o=n[l>>2]|0,!o)if(o=n[u>>2]|0,o)l=u;else{u=0;break}for(;;){if(u=o+20|0,A=n[u>>2]|0,A|0){o=A,l=u;continue}if(u=o+16|0,A=n[u>>2]|0,A)o=A,l=u;else break}n[l>>2]=0,u=o}else u=n[T+8>>2]|0,n[u+12>>2]=o,n[o+8>>2]=u,u=o;while(!1);if(m|0){if(o=n[T+28>>2]|0,l=11436+(o<<2)|0,(T|0)==(n[l>>2]|0)){if(n[l>>2]=u,!u){n[2784]=n[2784]&~(1<<o);break}}else if(n[m+16+(((n[m+16>>2]|0)!=(T|0)&1)<<2)>>2]=u,!u)break;n[u+24>>2]=m,o=T+16|0,l=n[o>>2]|0,l|0&&(n[u+16>>2]=l,n[l+24>>2]=u),o=n[o+4>>2]|0,o|0&&(n[u+20>>2]=o,n[o+24>>2]=u)}}while(!1);if(n[k+4>>2]=d|1,n[B+d>>2]=d,(k|0)==(n[2788]|0)){n[2785]=d;return}}if(o=d>>>3,d>>>0<256){u=11172+(o<<1<<2)|0,l=n[2783]|0,o=1<<o,l&o?(l=u+8|0,o=n[l>>2]|0):(n[2783]=l|o,o=u,l=u+8|0),n[l>>2]=k,n[o+12>>2]=k,n[k+8>>2]=o,n[k+12>>2]=u;return}o=d>>>8,o?d>>>0>16777215?o=31:(B=(o+1048320|0)>>>16&8,T=o<<B,m=(T+520192|0)>>>16&4,T=T<<m,o=(T+245760|0)>>>16&2,o=14-(m|B|o)+(T<<o>>>15)|0,o=d>>>(o+7|0)&1|o<<1):o=0,A=11436+(o<<2)|0,n[k+28>>2]=o,n[k+20>>2]=0,n[k+16>>2]=0,l=n[2784]|0,u=1<<o;do if(l&u){for(l=d<<((o|0)==31?0:25-(o>>>1)|0),u=n[A>>2]|0;;){if((n[u+4>>2]&-8|0)==(d|0)){o=73;break}if(A=u+16+(l>>>31<<2)|0,o=n[A>>2]|0,o)l=l<<1,u=o;else{o=72;break}}if((o|0)==72){n[A>>2]=k,n[k+24>>2]=u,n[k+12>>2]=k,n[k+8>>2]=k;break}else if((o|0)==73){B=u+8|0,T=n[B>>2]|0,n[T+12>>2]=k,n[B>>2]=k,n[k+8>>2]=T,n[k+12>>2]=u,n[k+24>>2]=0;break}}else n[2784]=l|u,n[A>>2]=k,n[k+24>>2]=A,n[k+12>>2]=k,n[k+8>>2]=k;while(!1);if(T=(n[2791]|0)+-1|0,n[2791]=T,!T)o=11588;else return;for(;o=n[o>>2]|0,o;)o=o+8|0;n[2791]=-1}}}function pYe(){return 11628}function hYe(o){o=o|0;var l=0,u=0;return l=I,I=I+16|0,u=l,n[u>>2]=mYe(n[o+60>>2]|0)|0,o=zP(hu(6,u|0)|0)|0,I=l,o|0}function qX(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0,We=0;G=I,I=I+48|0,_=G+16|0,m=G,d=G+32|0,k=o+28|0,A=n[k>>2]|0,n[d>>2]=A,T=o+20|0,A=(n[T>>2]|0)-A|0,n[d+4>>2]=A,n[d+8>>2]=l,n[d+12>>2]=u,A=A+u|0,B=o+60|0,n[m>>2]=n[B>>2],n[m+4>>2]=d,n[m+8>>2]=2,m=zP(Ma(146,m|0)|0)|0;e:do if((A|0)!=(m|0)){for(l=2;!((m|0)<0);)if(A=A-m|0,We=n[d+4>>2]|0,ae=m>>>0>We>>>0,d=ae?d+8|0:d,l=(ae<<31>>31)+l|0,We=m-(ae?We:0)|0,n[d>>2]=(n[d>>2]|0)+We,ae=d+4|0,n[ae>>2]=(n[ae>>2]|0)-We,n[_>>2]=n[B>>2],n[_+4>>2]=d,n[_+8>>2]=l,m=zP(Ma(146,_|0)|0)|0,(A|0)==(m|0)){M=3;break e}n[o+16>>2]=0,n[k>>2]=0,n[T>>2]=0,n[o>>2]=n[o>>2]|32,(l|0)==2?u=0:u=u-(n[d+4>>2]|0)|0}else M=3;while(!1);return(M|0)==3&&(We=n[o+44>>2]|0,n[o+16>>2]=We+(n[o+48>>2]|0),n[k>>2]=We,n[T>>2]=We),I=G,u|0}function gYe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;return d=I,I=I+32|0,m=d,A=d+20|0,n[m>>2]=n[o+60>>2],n[m+4>>2]=0,n[m+8>>2]=l,n[m+12>>2]=A,n[m+16>>2]=u,(zP(La(140,m|0)|0)|0)<0?(n[A>>2]=-1,o=-1):o=n[A>>2]|0,I=d,o|0}function zP(o){return o=o|0,o>>>0>4294963200&&(n[(Jy()|0)>>2]=0-o,o=-1),o|0}function Jy(){return(dYe()|0)+64|0}function dYe(){return u_()|0}function u_(){return 2084}function mYe(o){return o=o|0,o|0}function yYe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0;return d=I,I=I+32|0,A=d,n[o+36>>2]=1,!(n[o>>2]&64|0)&&(n[A>>2]=n[o+60>>2],n[A+4>>2]=21523,n[A+8>>2]=d+16,so(54,A|0)|0)&&(s[o+75>>0]=-1),A=qX(o,l,u)|0,I=d,A|0}function GX(o,l){o=o|0,l=l|0;var u=0,A=0;if(u=s[o>>0]|0,A=s[l>>0]|0,!(u<<24>>24)||u<<24>>24!=A<<24>>24)o=A;else{do o=o+1|0,l=l+1|0,u=s[o>>0]|0,A=s[l>>0]|0;while(!(!(u<<24>>24)||u<<24>>24!=A<<24>>24));o=A}return(u&255)-(o&255)|0}function EYe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0;e:do if(!u)o=0;else{for(;A=s[o>>0]|0,d=s[l>>0]|0,A<<24>>24==d<<24>>24;)if(u=u+-1|0,u)o=o+1|0,l=l+1|0;else{o=0;break e}o=(A&255)-(d&255)|0}while(!1);return o|0}function WX(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0,We=0,Le=0,Qe=0;Qe=I,I=I+224|0,M=Qe+120|0,G=Qe+80|0,We=Qe,Le=Qe+136|0,A=G,d=A+40|0;do n[A>>2]=0,A=A+4|0;while((A|0)<(d|0));return n[M>>2]=n[u>>2],(f_(0,l,M,We,G)|0)<0?u=-1:((n[o+76>>2]|0)>-1?ae=IYe(o)|0:ae=0,u=n[o>>2]|0,_=u&32,(s[o+74>>0]|0)<1&&(n[o>>2]=u&-33),A=o+48|0,n[A>>2]|0?u=f_(o,l,M,We,G)|0:(d=o+44|0,m=n[d>>2]|0,n[d>>2]=Le,B=o+28|0,n[B>>2]=Le,k=o+20|0,n[k>>2]=Le,n[A>>2]=80,T=o+16|0,n[T>>2]=Le+80,u=f_(o,l,M,We,G)|0,m&&(ex[n[o+36>>2]&7](o,0,0)|0,u=n[k>>2]|0?u:-1,n[d>>2]=m,n[A>>2]=0,n[T>>2]=0,n[B>>2]=0,n[k>>2]=0)),A=n[o>>2]|0,n[o>>2]=A|_,ae|0&&CYe(o),u=A&32|0?-1:u),I=Qe,u|0}function f_(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0;var m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0,We=0,Le=0,Qe=0,tt=0,Ze=0,ct=0,He=0,Ge=0,Lt=0,qr=0,fr=0,$t=0,Tr=0,Hr=0,cr=0;cr=I,I=I+64|0,fr=cr+16|0,$t=cr,Lt=cr+24|0,Tr=cr+8|0,Hr=cr+20|0,n[fr>>2]=l,ct=(o|0)!=0,He=Lt+40|0,Ge=He,Lt=Lt+39|0,qr=Tr+4|0,B=0,m=0,M=0;e:for(;;){do if((m|0)>-1)if((B|0)>(2147483647-m|0)){n[(Jy()|0)>>2]=75,m=-1;break}else{m=B+m|0;break}while(!1);if(B=s[l>>0]|0,B<<24>>24)k=l;else{Ze=87;break}t:for(;;){switch(B<<24>>24){case 37:{B=k,Ze=9;break t}case 0:{B=k;break t}default:}tt=k+1|0,n[fr>>2]=tt,B=s[tt>>0]|0,k=tt}t:do if((Ze|0)==9)for(;;){if(Ze=0,(s[k+1>>0]|0)!=37)break t;if(B=B+1|0,k=k+2|0,n[fr>>2]=k,(s[k>>0]|0)==37)Ze=9;else break}while(!1);if(B=B-l|0,ct&&Ds(o,l,B),B|0){l=k;continue}T=k+1|0,B=(s[T>>0]|0)+-48|0,B>>>0<10?(tt=(s[k+2>>0]|0)==36,Qe=tt?B:-1,M=tt?1:M,T=tt?k+3|0:T):Qe=-1,n[fr>>2]=T,B=s[T>>0]|0,k=(B<<24>>24)+-32|0;t:do if(k>>>0<32)for(_=0,G=B;;){if(B=1<<k,!(B&75913)){B=G;break t}if(_=B|_,T=T+1|0,n[fr>>2]=T,B=s[T>>0]|0,k=(B<<24>>24)+-32|0,k>>>0>=32)break;G=B}else _=0;while(!1);if(B<<24>>24==42){if(k=T+1|0,B=(s[k>>0]|0)+-48|0,B>>>0<10&&(s[T+2>>0]|0)==36)n[d+(B<<2)>>2]=10,B=n[A+((s[k>>0]|0)+-48<<3)>>2]|0,M=1,T=T+3|0;else{if(M|0){m=-1;break}ct?(M=(n[u>>2]|0)+3&-4,B=n[M>>2]|0,n[u>>2]=M+4,M=0,T=k):(B=0,M=0,T=k)}n[fr>>2]=T,tt=(B|0)<0,B=tt?0-B|0:B,_=tt?_|8192:_}else{if(B=YX(fr)|0,(B|0)<0){m=-1;break}T=n[fr>>2]|0}do if((s[T>>0]|0)==46){if((s[T+1>>0]|0)!=42){n[fr>>2]=T+1,k=YX(fr)|0,T=n[fr>>2]|0;break}if(G=T+2|0,k=(s[G>>0]|0)+-48|0,k>>>0<10&&(s[T+3>>0]|0)==36){n[d+(k<<2)>>2]=10,k=n[A+((s[G>>0]|0)+-48<<3)>>2]|0,T=T+4|0,n[fr>>2]=T;break}if(M|0){m=-1;break e}ct?(tt=(n[u>>2]|0)+3&-4,k=n[tt>>2]|0,n[u>>2]=tt+4):k=0,n[fr>>2]=G,T=G}else k=-1;while(!1);for(Le=0;;){if(((s[T>>0]|0)+-65|0)>>>0>57){m=-1;break e}if(tt=T+1|0,n[fr>>2]=tt,G=s[(s[T>>0]|0)+-65+(5178+(Le*58|0))>>0]|0,ae=G&255,(ae+-1|0)>>>0<8)Le=ae,T=tt;else break}if(!(G<<24>>24)){m=-1;break}We=(Qe|0)>-1;do if(G<<24>>24==19)if(We){m=-1;break e}else Ze=49;else{if(We){n[d+(Qe<<2)>>2]=ae,We=A+(Qe<<3)|0,Qe=n[We+4>>2]|0,Ze=$t,n[Ze>>2]=n[We>>2],n[Ze+4>>2]=Qe,Ze=49;break}if(!ct){m=0;break e}VX($t,ae,u)}while(!1);if((Ze|0)==49&&(Ze=0,!ct)){B=0,l=tt;continue}T=s[T>>0]|0,T=(Le|0)!=0&(T&15|0)==3?T&-33:T,We=_&-65537,Qe=_&8192|0?We:_;t:do switch(T|0){case 110:switch((Le&255)<<24>>24){case 0:{n[n[$t>>2]>>2]=m,B=0,l=tt;continue e}case 1:{n[n[$t>>2]>>2]=m,B=0,l=tt;continue e}case 2:{B=n[$t>>2]|0,n[B>>2]=m,n[B+4>>2]=((m|0)<0)<<31>>31,B=0,l=tt;continue e}case 3:{a[n[$t>>2]>>1]=m,B=0,l=tt;continue e}case 4:{s[n[$t>>2]>>0]=m,B=0,l=tt;continue e}case 6:{n[n[$t>>2]>>2]=m,B=0,l=tt;continue e}case 7:{B=n[$t>>2]|0,n[B>>2]=m,n[B+4>>2]=((m|0)<0)<<31>>31,B=0,l=tt;continue e}default:{B=0,l=tt;continue e}}case 112:{T=120,k=k>>>0>8?k:8,l=Qe|8,Ze=61;break}case 88:case 120:{l=Qe,Ze=61;break}case 111:{T=$t,l=n[T>>2]|0,T=n[T+4>>2]|0,ae=BYe(l,T,He)|0,We=Ge-ae|0,_=0,G=5642,k=(Qe&8|0)==0|(k|0)>(We|0)?k:We+1|0,We=Qe,Ze=67;break}case 105:case 100:if(T=$t,l=n[T>>2]|0,T=n[T+4>>2]|0,(T|0)<0){l=ZP(0,0,l|0,T|0)|0,T=ye,_=$t,n[_>>2]=l,n[_+4>>2]=T,_=1,G=5642,Ze=66;break t}else{_=(Qe&2049|0)!=0&1,G=Qe&2048|0?5643:Qe&1|0?5644:5642,Ze=66;break t}case 117:{T=$t,_=0,G=5642,l=n[T>>2]|0,T=n[T+4>>2]|0,Ze=66;break}case 99:{s[Lt>>0]=n[$t>>2],l=Lt,_=0,G=5642,ae=He,T=1,k=We;break}case 109:{T=vYe(n[(Jy()|0)>>2]|0)|0,Ze=71;break}case 115:{T=n[$t>>2]|0,T=T|0?T:5652,Ze=71;break}case 67:{n[Tr>>2]=n[$t>>2],n[qr>>2]=0,n[$t>>2]=Tr,ae=-1,T=Tr,Ze=75;break}case 83:{l=n[$t>>2]|0,k?(ae=k,T=l,Ze=75):(_s(o,32,B,0,Qe),l=0,Ze=84);break}case 65:case 71:case 70:case 69:case 97:case 103:case 102:case 101:{B=DYe(o,+E[$t>>3],B,k,Qe,T)|0,l=tt;continue e}default:_=0,G=5642,ae=He,T=k,k=Qe}while(!1);t:do if((Ze|0)==61)Qe=$t,Le=n[Qe>>2]|0,Qe=n[Qe+4>>2]|0,ae=wYe(Le,Qe,He,T&32)|0,G=(l&8|0)==0|(Le|0)==0&(Qe|0)==0,_=G?0:2,G=G?5642:5642+(T>>4)|0,We=l,l=Le,T=Qe,Ze=67;else if((Ze|0)==66)ae=zy(l,T,He)|0,We=Qe,Ze=67;else if((Ze|0)==71)Ze=0,Qe=SYe(T,0,k)|0,Le=(Qe|0)==0,l=T,_=0,G=5642,ae=Le?T+k|0:Qe,T=Le?k:Qe-T|0,k=We;else if((Ze|0)==75){for(Ze=0,G=T,l=0,k=0;_=n[G>>2]|0,!(!_||(k=KX(Hr,_)|0,(k|0)<0|k>>>0>(ae-l|0)>>>0));)if(l=k+l|0,ae>>>0>l>>>0)G=G+4|0;else break;if((k|0)<0){m=-1;break e}if(_s(o,32,B,l,Qe),!l)l=0,Ze=84;else for(_=0;;){if(k=n[T>>2]|0,!k){Ze=84;break t}if(k=KX(Hr,k)|0,_=k+_|0,(_|0)>(l|0)){Ze=84;break t}if(Ds(o,Hr,k),_>>>0>=l>>>0){Ze=84;break}else T=T+4|0}}while(!1);if((Ze|0)==67)Ze=0,T=(l|0)!=0|(T|0)!=0,Qe=(k|0)!=0|T,T=((T^1)&1)+(Ge-ae)|0,l=Qe?ae:He,ae=He,T=Qe?(k|0)>(T|0)?k:T:k,k=(k|0)>-1?We&-65537:We;else if((Ze|0)==84){Ze=0,_s(o,32,B,l,Qe^8192),B=(B|0)>(l|0)?B:l,l=tt;continue}Le=ae-l|0,We=(T|0)<(Le|0)?Le:T,Qe=We+_|0,B=(B|0)<(Qe|0)?Qe:B,_s(o,32,B,Qe,k),Ds(o,G,_),_s(o,48,B,Qe,k^65536),_s(o,48,We,Le,0),Ds(o,l,Le),_s(o,32,B,Qe,k^8192),l=tt}e:do if((Ze|0)==87&&!o)if(!M)m=0;else{for(m=1;l=n[d+(m<<2)>>2]|0,!!l;)if(VX(A+(m<<3)|0,l,u),m=m+1|0,(m|0)>=10){m=1;break e}for(;;){if(n[d+(m<<2)>>2]|0){m=-1;break e}if(m=m+1|0,(m|0)>=10){m=1;break}}}while(!1);return I=cr,m|0}function IYe(o){return o=o|0,0}function CYe(o){o=o|0}function Ds(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]&32||NYe(l,u,o)|0}function YX(o){o=o|0;var l=0,u=0,A=0;if(u=n[o>>2]|0,A=(s[u>>0]|0)+-48|0,A>>>0<10){l=0;do l=A+(l*10|0)|0,u=u+1|0,n[o>>2]=u,A=(s[u>>0]|0)+-48|0;while(A>>>0<10)}else l=0;return l|0}function VX(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;e:do if(l>>>0<=20)do switch(l|0){case 9:{A=(n[u>>2]|0)+3&-4,l=n[A>>2]|0,n[u>>2]=A+4,n[o>>2]=l;break e}case 10:{A=(n[u>>2]|0)+3&-4,l=n[A>>2]|0,n[u>>2]=A+4,A=o,n[A>>2]=l,n[A+4>>2]=((l|0)<0)<<31>>31;break e}case 11:{A=(n[u>>2]|0)+3&-4,l=n[A>>2]|0,n[u>>2]=A+4,A=o,n[A>>2]=l,n[A+4>>2]=0;break e}case 12:{A=(n[u>>2]|0)+7&-8,l=A,d=n[l>>2]|0,l=n[l+4>>2]|0,n[u>>2]=A+8,A=o,n[A>>2]=d,n[A+4>>2]=l;break e}case 13:{d=(n[u>>2]|0)+3&-4,A=n[d>>2]|0,n[u>>2]=d+4,A=(A&65535)<<16>>16,d=o,n[d>>2]=A,n[d+4>>2]=((A|0)<0)<<31>>31;break e}case 14:{d=(n[u>>2]|0)+3&-4,A=n[d>>2]|0,n[u>>2]=d+4,d=o,n[d>>2]=A&65535,n[d+4>>2]=0;break e}case 15:{d=(n[u>>2]|0)+3&-4,A=n[d>>2]|0,n[u>>2]=d+4,A=(A&255)<<24>>24,d=o,n[d>>2]=A,n[d+4>>2]=((A|0)<0)<<31>>31;break e}case 16:{d=(n[u>>2]|0)+3&-4,A=n[d>>2]|0,n[u>>2]=d+4,d=o,n[d>>2]=A&255,n[d+4>>2]=0;break e}case 17:{d=(n[u>>2]|0)+7&-8,m=+E[d>>3],n[u>>2]=d+8,E[o>>3]=m;break e}case 18:{d=(n[u>>2]|0)+7&-8,m=+E[d>>3],n[u>>2]=d+8,E[o>>3]=m;break e}default:break e}while(!1);while(!1)}function wYe(o,l,u,A){if(o=o|0,l=l|0,u=u|0,A=A|0,!((o|0)==0&(l|0)==0))do u=u+-1|0,s[u>>0]=c[5694+(o&15)>>0]|0|A,o=XP(o|0,l|0,4)|0,l=ye;while(!((o|0)==0&(l|0)==0));return u|0}function BYe(o,l,u){if(o=o|0,l=l|0,u=u|0,!((o|0)==0&(l|0)==0))do u=u+-1|0,s[u>>0]=o&7|48,o=XP(o|0,l|0,3)|0,l=ye;while(!((o|0)==0&(l|0)==0));return u|0}function zy(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;if(l>>>0>0|(l|0)==0&o>>>0>4294967295){for(;A=g_(o|0,l|0,10,0)|0,u=u+-1|0,s[u>>0]=A&255|48,A=o,o=h_(o|0,l|0,10,0)|0,l>>>0>9|(l|0)==9&A>>>0>4294967295;)l=ye;l=o}else l=o;if(l)for(;u=u+-1|0,s[u>>0]=(l>>>0)%10|0|48,!(l>>>0<10);)l=(l>>>0)/10|0;return u|0}function vYe(o){return o=o|0,QYe(o,n[(kYe()|0)+188>>2]|0)|0}function SYe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;m=l&255,A=(u|0)!=0;e:do if(A&(o&3|0)!=0)for(d=l&255;;){if((s[o>>0]|0)==d<<24>>24){B=6;break e}if(o=o+1|0,u=u+-1|0,A=(u|0)!=0,!(A&(o&3|0)!=0)){B=5;break}}else B=5;while(!1);(B|0)==5&&(A?B=6:u=0);e:do if((B|0)==6&&(d=l&255,(s[o>>0]|0)!=d<<24>>24)){A=_e(m,16843009)|0;t:do if(u>>>0>3){for(;m=n[o>>2]^A,!((m&-2139062144^-2139062144)&m+-16843009|0);)if(o=o+4|0,u=u+-4|0,u>>>0<=3){B=11;break t}}else B=11;while(!1);if((B|0)==11&&!u){u=0;break}for(;;){if((s[o>>0]|0)==d<<24>>24)break e;if(o=o+1|0,u=u+-1|0,!u){u=0;break}}}while(!1);return(u|0?o:0)|0}function _s(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0;var m=0,B=0;if(B=I,I=I+256|0,m=B,(u|0)>(A|0)&(d&73728|0)==0){if(d=u-A|0,Xy(m|0,l|0,(d>>>0<256?d:256)|0)|0,d>>>0>255){l=u-A|0;do Ds(o,m,256),d=d+-256|0;while(d>>>0>255);d=l&255}Ds(o,m,d)}I=B}function KX(o,l){return o=o|0,l=l|0,o?o=PYe(o,l,0)|0:o=0,o|0}function DYe(o,l,u,A,d,m){o=o|0,l=+l,u=u|0,A=A|0,d=d|0,m=m|0;var B=0,k=0,T=0,_=0,M=0,G=0,ae=0,We=0,Le=0,Qe=0,tt=0,Ze=0,ct=0,He=0,Ge=0,Lt=0,qr=0,fr=0,$t=0,Tr=0,Hr=0,cr=0,Hn=0;Hn=I,I=I+560|0,T=Hn+8|0,tt=Hn,cr=Hn+524|0,Hr=cr,_=Hn+512|0,n[tt>>2]=0,Tr=_+12|0,JX(l)|0,(ye|0)<0?(l=-l,fr=1,qr=5659):(fr=(d&2049|0)!=0&1,qr=d&2048|0?5662:d&1|0?5665:5660),JX(l)|0,$t=ye&2146435072;do if($t>>>0<2146435072|($t|0)==2146435072&!1){if(We=+bYe(l,tt)*2,B=We!=0,B&&(n[tt>>2]=(n[tt>>2]|0)+-1),ct=m|32,(ct|0)==97){Le=m&32,ae=Le|0?qr+9|0:qr,G=fr|2,B=12-A|0;do if(A>>>0>11|(B|0)==0)l=We;else{l=8;do B=B+-1|0,l=l*16;while(B|0);if((s[ae>>0]|0)==45){l=-(l+(-We-l));break}else{l=We+l-l;break}}while(!1);k=n[tt>>2]|0,B=(k|0)<0?0-k|0:k,B=zy(B,((B|0)<0)<<31>>31,Tr)|0,(B|0)==(Tr|0)&&(B=_+11|0,s[B>>0]=48),s[B+-1>>0]=(k>>31&2)+43,M=B+-2|0,s[M>>0]=m+15,_=(A|0)<1,T=(d&8|0)==0,B=cr;do $t=~~l,k=B+1|0,s[B>>0]=c[5694+$t>>0]|Le,l=(l-+($t|0))*16,(k-Hr|0)==1&&!(T&(_&l==0))?(s[k>>0]=46,B=B+2|0):B=k;while(l!=0);$t=B-Hr|0,Hr=Tr-M|0,Tr=(A|0)!=0&($t+-2|0)<(A|0)?A+2|0:$t,B=Hr+G+Tr|0,_s(o,32,u,B,d),Ds(o,ae,G),_s(o,48,u,B,d^65536),Ds(o,cr,$t),_s(o,48,Tr-$t|0,0,0),Ds(o,M,Hr),_s(o,32,u,B,d^8192);break}k=(A|0)<0?6:A,B?(B=(n[tt>>2]|0)+-28|0,n[tt>>2]=B,l=We*268435456):(l=We,B=n[tt>>2]|0),$t=(B|0)<0?T:T+288|0,T=$t;do Ge=~~l>>>0,n[T>>2]=Ge,T=T+4|0,l=(l-+(Ge>>>0))*1e9;while(l!=0);if((B|0)>0)for(_=$t,G=T;;){if(M=(B|0)<29?B:29,B=G+-4|0,B>>>0>=_>>>0){T=0;do He=t$(n[B>>2]|0,0,M|0)|0,He=p_(He|0,ye|0,T|0,0)|0,Ge=ye,Ze=g_(He|0,Ge|0,1e9,0)|0,n[B>>2]=Ze,T=h_(He|0,Ge|0,1e9,0)|0,B=B+-4|0;while(B>>>0>=_>>>0);T&&(_=_+-4|0,n[_>>2]=T)}for(T=G;!(T>>>0<=_>>>0);)if(B=T+-4|0,!(n[B>>2]|0))T=B;else break;if(B=(n[tt>>2]|0)-M|0,n[tt>>2]=B,(B|0)>0)G=T;else break}else _=$t;if((B|0)<0){A=((k+25|0)/9|0)+1|0,Qe=(ct|0)==102;do{if(Le=0-B|0,Le=(Le|0)<9?Le:9,_>>>0<T>>>0){M=(1<<Le)+-1|0,G=1e9>>>Le,ae=0,B=_;do Ge=n[B>>2]|0,n[B>>2]=(Ge>>>Le)+ae,ae=_e(Ge&M,G)|0,B=B+4|0;while(B>>>0<T>>>0);B=n[_>>2]|0?_:_+4|0,ae?(n[T>>2]=ae,_=B,B=T+4|0):(_=B,B=T)}else _=n[_>>2]|0?_:_+4|0,B=T;T=Qe?$t:_,T=(B-T>>2|0)>(A|0)?T+(A<<2)|0:B,B=(n[tt>>2]|0)+Le|0,n[tt>>2]=B}while((B|0)<0);B=_,A=T}else B=_,A=T;if(Ge=$t,B>>>0<A>>>0){if(T=(Ge-B>>2)*9|0,M=n[B>>2]|0,M>>>0>=10){_=10;do _=_*10|0,T=T+1|0;while(M>>>0>=_>>>0)}}else T=0;if(Qe=(ct|0)==103,Ze=(k|0)!=0,_=k-((ct|0)!=102?T:0)+((Ze&Qe)<<31>>31)|0,(_|0)<(((A-Ge>>2)*9|0)+-9|0)){if(_=_+9216|0,Le=$t+4+(((_|0)/9|0)+-1024<<2)|0,_=((_|0)%9|0)+1|0,(_|0)<9){M=10;do M=M*10|0,_=_+1|0;while((_|0)!=9)}else M=10;if(G=n[Le>>2]|0,ae=(G>>>0)%(M>>>0)|0,_=(Le+4|0)==(A|0),_&(ae|0)==0)_=Le;else if(We=((G>>>0)/(M>>>0)|0)&1|0?9007199254740994:9007199254740992,He=(M|0)/2|0,l=ae>>>0<He>>>0?.5:_&(ae|0)==(He|0)?1:1.5,fr&&(He=(s[qr>>0]|0)==45,l=He?-l:l,We=He?-We:We),_=G-ae|0,n[Le>>2]=_,We+l!=We){if(He=_+M|0,n[Le>>2]=He,He>>>0>999999999)for(T=Le;_=T+-4|0,n[T>>2]=0,_>>>0<B>>>0&&(B=B+-4|0,n[B>>2]=0),He=(n[_>>2]|0)+1|0,n[_>>2]=He,He>>>0>999999999;)T=_;else _=Le;if(T=(Ge-B>>2)*9|0,G=n[B>>2]|0,G>>>0>=10){M=10;do M=M*10|0,T=T+1|0;while(G>>>0>=M>>>0)}}else _=Le;_=_+4|0,_=A>>>0>_>>>0?_:A,He=B}else _=A,He=B;for(ct=_;;){if(ct>>>0<=He>>>0){tt=0;break}if(B=ct+-4|0,!(n[B>>2]|0))ct=B;else{tt=1;break}}A=0-T|0;do if(Qe)if(B=((Ze^1)&1)+k|0,(B|0)>(T|0)&(T|0)>-5?(M=m+-1|0,k=B+-1-T|0):(M=m+-2|0,k=B+-1|0),B=d&8,B)Le=B;else{if(tt&&(Lt=n[ct+-4>>2]|0,(Lt|0)!=0))if((Lt>>>0)%10|0)_=0;else{_=0,B=10;do B=B*10|0,_=_+1|0;while(!((Lt>>>0)%(B>>>0)|0|0))}else _=9;if(B=((ct-Ge>>2)*9|0)+-9|0,(M|32|0)==102){Le=B-_|0,Le=(Le|0)>0?Le:0,k=(k|0)<(Le|0)?k:Le,Le=0;break}else{Le=B+T-_|0,Le=(Le|0)>0?Le:0,k=(k|0)<(Le|0)?k:Le,Le=0;break}}else M=m,Le=d&8;while(!1);if(Qe=k|Le,G=(Qe|0)!=0&1,ae=(M|32|0)==102,ae)Ze=0,B=(T|0)>0?T:0;else{if(B=(T|0)<0?A:T,B=zy(B,((B|0)<0)<<31>>31,Tr)|0,_=Tr,(_-B|0)<2)do B=B+-1|0,s[B>>0]=48;while((_-B|0)<2);s[B+-1>>0]=(T>>31&2)+43,B=B+-2|0,s[B>>0]=M,Ze=B,B=_-B|0}if(B=fr+1+k+G+B|0,_s(o,32,u,B,d),Ds(o,qr,fr),_s(o,48,u,B,d^65536),ae){M=He>>>0>$t>>>0?$t:He,Le=cr+9|0,G=Le,ae=cr+8|0,_=M;do{if(T=zy(n[_>>2]|0,0,Le)|0,(_|0)==(M|0))(T|0)==(Le|0)&&(s[ae>>0]=48,T=ae);else if(T>>>0>cr>>>0){Xy(cr|0,48,T-Hr|0)|0;do T=T+-1|0;while(T>>>0>cr>>>0)}Ds(o,T,G-T|0),_=_+4|0}while(_>>>0<=$t>>>0);if(Qe|0&&Ds(o,5710,1),_>>>0<ct>>>0&(k|0)>0)for(;;){if(T=zy(n[_>>2]|0,0,Le)|0,T>>>0>cr>>>0){Xy(cr|0,48,T-Hr|0)|0;do T=T+-1|0;while(T>>>0>cr>>>0)}if(Ds(o,T,(k|0)<9?k:9),_=_+4|0,T=k+-9|0,_>>>0<ct>>>0&(k|0)>9)k=T;else{k=T;break}}_s(o,48,k+9|0,9,0)}else{if(Qe=tt?ct:He+4|0,(k|0)>-1){tt=cr+9|0,Le=(Le|0)==0,A=tt,G=0-Hr|0,ae=cr+8|0,M=He;do{T=zy(n[M>>2]|0,0,tt)|0,(T|0)==(tt|0)&&(s[ae>>0]=48,T=ae);do if((M|0)==(He|0)){if(_=T+1|0,Ds(o,T,1),Le&(k|0)<1){T=_;break}Ds(o,5710,1),T=_}else{if(T>>>0<=cr>>>0)break;Xy(cr|0,48,T+G|0)|0;do T=T+-1|0;while(T>>>0>cr>>>0)}while(!1);Hr=A-T|0,Ds(o,T,(k|0)>(Hr|0)?Hr:k),k=k-Hr|0,M=M+4|0}while(M>>>0<Qe>>>0&(k|0)>-1)}_s(o,48,k+18|0,18,0),Ds(o,Ze,Tr-Ze|0)}_s(o,32,u,B,d^8192)}else cr=(m&32|0)!=0,B=fr+3|0,_s(o,32,u,B,d&-65537),Ds(o,qr,fr),Ds(o,l!=l|!1?cr?5686:5690:cr?5678:5682,3),_s(o,32,u,B,d^8192);while(!1);return I=Hn,((B|0)<(u|0)?u:B)|0}function JX(o){o=+o;var l=0;return E[S>>3]=o,l=n[S>>2]|0,ye=n[S+4>>2]|0,l|0}function bYe(o,l){return o=+o,l=l|0,+ +zX(o,l)}function zX(o,l){o=+o,l=l|0;var u=0,A=0,d=0;switch(E[S>>3]=o,u=n[S>>2]|0,A=n[S+4>>2]|0,d=XP(u|0,A|0,52)|0,d&2047){case 0:{o!=0?(o=+zX(o*18446744073709552e3,l),u=(n[l>>2]|0)+-64|0):u=0,n[l>>2]=u;break}case 2047:break;default:n[l>>2]=(d&2047)+-1022,n[S>>2]=u,n[S+4>>2]=A&-2146435073|1071644672,o=+E[S>>3]}return+o}function PYe(o,l,u){o=o|0,l=l|0,u=u|0;do if(o){if(l>>>0<128){s[o>>0]=l,o=1;break}if(!(n[n[(xYe()|0)+188>>2]>>2]|0))if((l&-128|0)==57216){s[o>>0]=l,o=1;break}else{n[(Jy()|0)>>2]=84,o=-1;break}if(l>>>0<2048){s[o>>0]=l>>>6|192,s[o+1>>0]=l&63|128,o=2;break}if(l>>>0<55296|(l&-8192|0)==57344){s[o>>0]=l>>>12|224,s[o+1>>0]=l>>>6&63|128,s[o+2>>0]=l&63|128,o=3;break}if((l+-65536|0)>>>0<1048576){s[o>>0]=l>>>18|240,s[o+1>>0]=l>>>12&63|128,s[o+2>>0]=l>>>6&63|128,s[o+3>>0]=l&63|128,o=4;break}else{n[(Jy()|0)>>2]=84,o=-1;break}}else o=1;while(!1);return o|0}function xYe(){return u_()|0}function kYe(){return u_()|0}function QYe(o,l){o=o|0,l=l|0;var u=0,A=0;for(A=0;;){if((c[5712+A>>0]|0)==(o|0)){o=2;break}if(u=A+1|0,(u|0)==87){u=5800,A=87,o=5;break}else A=u}if((o|0)==2&&(A?(u=5800,o=5):u=5800),(o|0)==5)for(;;){do o=u,u=u+1|0;while(s[o>>0]|0);if(A=A+-1|0,A)o=5;else break}return TYe(u,n[l+20>>2]|0)|0}function TYe(o,l){return o=o|0,l=l|0,RYe(o,l)|0}function RYe(o,l){return o=o|0,l=l|0,l?l=FYe(n[l>>2]|0,n[l+4>>2]|0,o)|0:l=0,(l|0?l:o)|0}function FYe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;ae=(n[o>>2]|0)+1794895138|0,m=fd(n[o+8>>2]|0,ae)|0,A=fd(n[o+12>>2]|0,ae)|0,d=fd(n[o+16>>2]|0,ae)|0;e:do if(m>>>0<l>>>2>>>0&&(G=l-(m<<2)|0,A>>>0<G>>>0&d>>>0<G>>>0)&&!((d|A)&3|0)){for(G=A>>>2,M=d>>>2,_=0;;){if(k=m>>>1,T=_+k|0,B=T<<1,d=B+G|0,A=fd(n[o+(d<<2)>>2]|0,ae)|0,d=fd(n[o+(d+1<<2)>>2]|0,ae)|0,!(d>>>0<l>>>0&A>>>0<(l-d|0)>>>0)){A=0;break e}if(s[o+(d+A)>>0]|0){A=0;break e}if(A=GX(u,o+d|0)|0,!A)break;if(A=(A|0)<0,(m|0)==1){A=0;break e}else _=A?_:T,m=A?k:m-k|0}A=B+M|0,d=fd(n[o+(A<<2)>>2]|0,ae)|0,A=fd(n[o+(A+1<<2)>>2]|0,ae)|0,A>>>0<l>>>0&d>>>0<(l-A|0)>>>0?A=s[o+(A+d)>>0]|0?0:o+A|0:A=0}else A=0;while(!1);return A|0}function fd(o,l){o=o|0,l=l|0;var u=0;return u=i$(o|0)|0,(l|0?u:o)|0}function NYe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=u+16|0,d=n[A>>2]|0,d?m=5:OYe(u)|0?A=0:(d=n[A>>2]|0,m=5);e:do if((m|0)==5){if(k=u+20|0,B=n[k>>2]|0,A=B,(d-B|0)>>>0<l>>>0){A=ex[n[u+36>>2]&7](u,o,l)|0;break}t:do if((s[u+75>>0]|0)>-1){for(B=l;;){if(!B){m=0,d=o;break t}if(d=B+-1|0,(s[o+d>>0]|0)==10)break;B=d}if(A=ex[n[u+36>>2]&7](u,o,B)|0,A>>>0<B>>>0)break e;m=B,d=o+B|0,l=l-B|0,A=n[k>>2]|0}else m=0,d=o;while(!1);Qr(A|0,d|0,l|0)|0,n[k>>2]=(n[k>>2]|0)+l,A=m+l|0}while(!1);return A|0}function OYe(o){o=o|0;var l=0,u=0;return l=o+74|0,u=s[l>>0]|0,s[l>>0]=u+255|u,l=n[o>>2]|0,l&8?(n[o>>2]=l|32,o=-1):(n[o+8>>2]=0,n[o+4>>2]=0,u=n[o+44>>2]|0,n[o+28>>2]=u,n[o+20>>2]=u,n[o+16>>2]=u+(n[o+48>>2]|0),o=0),o|0}function $n(o,l){o=y(o),l=y(l);var u=0,A=0;u=ZX(o)|0;do if((u&2147483647)>>>0<=2139095040){if(A=ZX(l)|0,(A&2147483647)>>>0<=2139095040)if((A^u|0)<0){o=(u|0)<0?l:o;break}else{o=o<l?l:o;break}}else o=l;while(!1);return y(o)}function ZX(o){return o=y(o),h[S>>2]=o,n[S>>2]|0|0}function Ad(o,l){o=y(o),l=y(l);var u=0,A=0;u=XX(o)|0;do if((u&2147483647)>>>0<=2139095040){if(A=XX(l)|0,(A&2147483647)>>>0<=2139095040)if((A^u|0)<0){o=(u|0)<0?o:l;break}else{o=o<l?o:l;break}}else o=l;while(!1);return y(o)}function XX(o){return o=y(o),h[S>>2]=o,n[S>>2]|0|0}function A_(o,l){o=y(o),l=y(l);var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0;m=(h[S>>2]=o,n[S>>2]|0),k=(h[S>>2]=l,n[S>>2]|0),u=m>>>23&255,B=k>>>23&255,T=m&-2147483648,d=k<<1;e:do if(d|0&&!((u|0)==255|((LYe(l)|0)&2147483647)>>>0>2139095040)){if(A=m<<1,A>>>0<=d>>>0)return l=y(o*y(0)),y((A|0)==(d|0)?l:o);if(u)A=m&8388607|8388608;else{if(u=m<<9,(u|0)>-1){A=u,u=0;do u=u+-1|0,A=A<<1;while((A|0)>-1)}else u=0;A=m<<1-u}if(B)k=k&8388607|8388608;else{if(m=k<<9,(m|0)>-1){d=0;do d=d+-1|0,m=m<<1;while((m|0)>-1)}else d=0;B=d,k=k<<1-d}d=A-k|0,m=(d|0)>-1;t:do if((u|0)>(B|0)){for(;;){if(m)if(d)A=d;else break;if(A=A<<1,u=u+-1|0,d=A-k|0,m=(d|0)>-1,(u|0)<=(B|0))break t}l=y(o*y(0));break e}while(!1);if(m)if(d)A=d;else{l=y(o*y(0));break}if(A>>>0<8388608)do A=A<<1,u=u+-1|0;while(A>>>0<8388608);(u|0)>0?u=A+-8388608|u<<23:u=A>>>(1-u|0),l=(n[S>>2]=u|T,y(h[S>>2]))}else _=3;while(!1);return(_|0)==3&&(l=y(o*l),l=y(l/l)),y(l)}function LYe(o){return o=y(o),h[S>>2]=o,n[S>>2]|0|0}function MYe(o,l){return o=o|0,l=l|0,WX(n[582]|0,o,l)|0}function sn(o){o=o|0,Nt()}function Zy(o){o=o|0}function _Ye(o,l){return o=o|0,l=l|0,0}function UYe(o){return o=o|0,($X(o+4|0)|0)==-1?(op[n[(n[o>>2]|0)+8>>2]&127](o),o=1):o=0,o|0}function $X(o){o=o|0;var l=0;return l=n[o>>2]|0,n[o>>2]=l+-1,l+-1|0}function Wh(o){o=o|0,UYe(o)|0&&HYe(o)}function HYe(o){o=o|0;var l=0;l=o+8|0,n[l>>2]|0&&($X(l)|0)!=-1||op[n[(n[o>>2]|0)+16>>2]&127](o)}function Jt(o){o=o|0;var l=0;for(l=o|0?o:1;o=KP(l)|0,!(o|0);){if(o=qYe()|0,!o){o=0;break}h$[o&0]()}return o|0}function e$(o){return o=o|0,Jt(o)|0}function Et(o){o=o|0,JP(o)}function jYe(o){o=o|0,(s[o+11>>0]|0)<0&&Et(n[o>>2]|0)}function qYe(){var o=0;return o=n[2923]|0,n[2923]=o+0,o|0}function GYe(){}function ZP(o,l,u,A){return o=o|0,l=l|0,u=u|0,A=A|0,A=l-A-(u>>>0>o>>>0|0)>>>0,ye=A,o-u>>>0|0|0}function p_(o,l,u,A){return o=o|0,l=l|0,u=u|0,A=A|0,u=o+u>>>0,ye=l+A+(u>>>0<o>>>0|0)>>>0,u|0|0}function Xy(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;if(m=o+u|0,l=l&255,(u|0)>=67){for(;o&3;)s[o>>0]=l,o=o+1|0;for(A=m&-4|0,d=A-64|0,B=l|l<<8|l<<16|l<<24;(o|0)<=(d|0);)n[o>>2]=B,n[o+4>>2]=B,n[o+8>>2]=B,n[o+12>>2]=B,n[o+16>>2]=B,n[o+20>>2]=B,n[o+24>>2]=B,n[o+28>>2]=B,n[o+32>>2]=B,n[o+36>>2]=B,n[o+40>>2]=B,n[o+44>>2]=B,n[o+48>>2]=B,n[o+52>>2]=B,n[o+56>>2]=B,n[o+60>>2]=B,o=o+64|0;for(;(o|0)<(A|0);)n[o>>2]=B,o=o+4|0}for(;(o|0)<(m|0);)s[o>>0]=l,o=o+1|0;return m-u|0}function t$(o,l,u){return o=o|0,l=l|0,u=u|0,(u|0)<32?(ye=l<<u|(o&(1<<u)-1<<32-u)>>>32-u,o<<u):(ye=o<<u-32,0)}function XP(o,l,u){return o=o|0,l=l|0,u=u|0,(u|0)<32?(ye=l>>>u,o>>>u|(l&(1<<u)-1)<<32-u):(ye=0,l>>>u-32|0)}function Qr(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;if((u|0)>=8192)return MA(o|0,l|0,u|0)|0;if(m=o|0,d=o+u|0,(o&3)==(l&3)){for(;o&3;){if(!u)return m|0;s[o>>0]=s[l>>0]|0,o=o+1|0,l=l+1|0,u=u-1|0}for(u=d&-4|0,A=u-64|0;(o|0)<=(A|0);)n[o>>2]=n[l>>2],n[o+4>>2]=n[l+4>>2],n[o+8>>2]=n[l+8>>2],n[o+12>>2]=n[l+12>>2],n[o+16>>2]=n[l+16>>2],n[o+20>>2]=n[l+20>>2],n[o+24>>2]=n[l+24>>2],n[o+28>>2]=n[l+28>>2],n[o+32>>2]=n[l+32>>2],n[o+36>>2]=n[l+36>>2],n[o+40>>2]=n[l+40>>2],n[o+44>>2]=n[l+44>>2],n[o+48>>2]=n[l+48>>2],n[o+52>>2]=n[l+52>>2],n[o+56>>2]=n[l+56>>2],n[o+60>>2]=n[l+60>>2],o=o+64|0,l=l+64|0;for(;(o|0)<(u|0);)n[o>>2]=n[l>>2],o=o+4|0,l=l+4|0}else for(u=d-4|0;(o|0)<(u|0);)s[o>>0]=s[l>>0]|0,s[o+1>>0]=s[l+1>>0]|0,s[o+2>>0]=s[l+2>>0]|0,s[o+3>>0]=s[l+3>>0]|0,o=o+4|0,l=l+4|0;for(;(o|0)<(d|0);)s[o>>0]=s[l>>0]|0,o=o+1|0,l=l+1|0;return m|0}function r$(o){o=o|0;var l=0;return l=s[N+(o&255)>>0]|0,(l|0)<8?l|0:(l=s[N+(o>>8&255)>>0]|0,(l|0)<8?l+8|0:(l=s[N+(o>>16&255)>>0]|0,(l|0)<8?l+16|0:(s[N+(o>>>24)>>0]|0)+24|0))}function n$(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0;var m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0,We=0,Le=0;if(M=o,T=l,_=T,B=u,ae=A,k=ae,!_)return m=(d|0)!=0,k?m?(n[d>>2]=o|0,n[d+4>>2]=l&0,ae=0,d=0,ye=ae,d|0):(ae=0,d=0,ye=ae,d|0):(m&&(n[d>>2]=(M>>>0)%(B>>>0),n[d+4>>2]=0),ae=0,d=(M>>>0)/(B>>>0)>>>0,ye=ae,d|0);m=(k|0)==0;do if(B){if(!m){if(m=(b(k|0)|0)-(b(_|0)|0)|0,m>>>0<=31){G=m+1|0,k=31-m|0,l=m-31>>31,B=G,o=M>>>(G>>>0)&l|_<<k,l=_>>>(G>>>0)&l,m=0,k=M<<k;break}return d?(n[d>>2]=o|0,n[d+4>>2]=T|l&0,ae=0,d=0,ye=ae,d|0):(ae=0,d=0,ye=ae,d|0)}if(m=B-1|0,m&B|0){k=(b(B|0)|0)+33-(b(_|0)|0)|0,Le=64-k|0,G=32-k|0,T=G>>31,We=k-32|0,l=We>>31,B=k,o=G-1>>31&_>>>(We>>>0)|(_<<G|M>>>(k>>>0))&l,l=l&_>>>(k>>>0),m=M<<Le&T,k=(_<<Le|M>>>(We>>>0))&T|M<<G&k-33>>31;break}return d|0&&(n[d>>2]=m&M,n[d+4>>2]=0),(B|0)==1?(We=T|l&0,Le=o|0|0,ye=We,Le|0):(Le=r$(B|0)|0,We=_>>>(Le>>>0)|0,Le=_<<32-Le|M>>>(Le>>>0)|0,ye=We,Le|0)}else{if(m)return d|0&&(n[d>>2]=(_>>>0)%(B>>>0),n[d+4>>2]=0),We=0,Le=(_>>>0)/(B>>>0)>>>0,ye=We,Le|0;if(!M)return d|0&&(n[d>>2]=0,n[d+4>>2]=(_>>>0)%(k>>>0)),We=0,Le=(_>>>0)/(k>>>0)>>>0,ye=We,Le|0;if(m=k-1|0,!(m&k))return d|0&&(n[d>>2]=o|0,n[d+4>>2]=m&_|l&0),We=0,Le=_>>>((r$(k|0)|0)>>>0),ye=We,Le|0;if(m=(b(k|0)|0)-(b(_|0)|0)|0,m>>>0<=30){l=m+1|0,k=31-m|0,B=l,o=_<<k|M>>>(l>>>0),l=_>>>(l>>>0),m=0,k=M<<k;break}return d?(n[d>>2]=o|0,n[d+4>>2]=T|l&0,We=0,Le=0,ye=We,Le|0):(We=0,Le=0,ye=We,Le|0)}while(!1);if(!B)_=k,T=0,k=0;else{G=u|0|0,M=ae|A&0,_=p_(G|0,M|0,-1,-1)|0,u=ye,T=k,k=0;do A=T,T=m>>>31|T<<1,m=k|m<<1,A=o<<1|A>>>31|0,ae=o>>>31|l<<1|0,ZP(_|0,u|0,A|0,ae|0)|0,Le=ye,We=Le>>31|((Le|0)<0?-1:0)<<1,k=We&1,o=ZP(A|0,ae|0,We&G|0,(((Le|0)<0?-1:0)>>31|((Le|0)<0?-1:0)<<1)&M|0)|0,l=ye,B=B-1|0;while(B|0);_=T,T=0}return B=0,d|0&&(n[d>>2]=o,n[d+4>>2]=l),We=(m|0)>>>31|(_|B)<<1|(B<<1|m>>>31)&0|T,Le=(m<<1|0)&-2|k,ye=We,Le|0}function h_(o,l,u,A){return o=o|0,l=l|0,u=u|0,A=A|0,n$(o,l,u,A,0)|0}function Yh(o){o=o|0;var l=0,u=0;return u=o+15&-16|0,l=n[C>>2]|0,o=l+u|0,(u|0)>0&(o|0)<(l|0)|(o|0)<0?(oe()|0,pu(12),-1):(n[C>>2]=o,(o|0)>($()|0)&&!(Z()|0)?(n[C>>2]=l,pu(12),-1):l|0)}function Q2(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;if((l|0)<(o|0)&(o|0)<(l+u|0)){for(A=o,l=l+u|0,o=o+u|0;(u|0)>0;)o=o-1|0,l=l-1|0,u=u-1|0,s[o>>0]=s[l>>0]|0;o=A}else Qr(o,l,u)|0;return o|0}function g_(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;return m=I,I=I+16|0,d=m|0,n$(o,l,u,A,d)|0,I=m,ye=n[d+4>>2]|0,n[d>>2]|0|0}function i$(o){return o=o|0,(o&255)<<24|(o>>8&255)<<16|(o>>16&255)<<8|o>>>24|0}function WYe(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,s$[o&1](l|0,u|0,A|0,d|0,m|0)}function YYe(o,l,u){o=o|0,l=l|0,u=y(u),o$[o&1](l|0,y(u))}function VYe(o,l,u){o=o|0,l=l|0,u=+u,a$[o&31](l|0,+u)}function KYe(o,l,u,A){return o=o|0,l=l|0,u=y(u),A=y(A),y(l$[o&0](l|0,y(u),y(A)))}function JYe(o,l){o=o|0,l=l|0,op[o&127](l|0)}function zYe(o,l,u){o=o|0,l=l|0,u=u|0,ap[o&31](l|0,u|0)}function ZYe(o,l){return o=o|0,l=l|0,hd[o&31](l|0)|0}function XYe(o,l,u,A,d){o=o|0,l=l|0,u=+u,A=+A,d=d|0,c$[o&1](l|0,+u,+A,d|0)}function $Ye(o,l,u,A){o=o|0,l=l|0,u=+u,A=+A,RVe[o&1](l|0,+u,+A)}function eVe(o,l,u,A){return o=o|0,l=l|0,u=u|0,A=A|0,ex[o&7](l|0,u|0,A|0)|0}function tVe(o,l,u,A){return o=o|0,l=l|0,u=u|0,A=A|0,+FVe[o&1](l|0,u|0,A|0)}function rVe(o,l){return o=o|0,l=l|0,+u$[o&15](l|0)}function nVe(o,l,u){return o=o|0,l=l|0,u=+u,NVe[o&1](l|0,+u)|0}function iVe(o,l,u){return o=o|0,l=l|0,u=u|0,m_[o&15](l|0,u|0)|0}function sVe(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=+A,d=+d,m=m|0,OVe[o&1](l|0,u|0,+A,+d,m|0)}function oVe(o,l,u,A,d,m,B){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,B=B|0,LVe[o&1](l|0,u|0,A|0,d|0,m|0,B|0)}function aVe(o,l,u){return o=o|0,l=l|0,u=u|0,+f$[o&7](l|0,u|0)}function lVe(o){return o=o|0,tx[o&7]()|0}function cVe(o,l,u,A,d,m){return o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,A$[o&1](l|0,u|0,A|0,d|0,m|0)|0}function uVe(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=+d,MVe[o&1](l|0,u|0,A|0,+d)}function fVe(o,l,u,A,d,m,B){o=o|0,l=l|0,u=u|0,A=y(A),d=d|0,m=y(m),B=B|0,p$[o&1](l|0,u|0,y(A),d|0,y(m),B|0)}function AVe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,F2[o&15](l|0,u|0,A|0)}function pVe(o){o=o|0,h$[o&0]()}function hVe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=+A,g$[o&15](l|0,u|0,+A)}function gVe(o,l,u){return o=o|0,l=+l,u=+u,_Ve[o&1](+l,+u)|0}function dVe(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,y_[o&15](l|0,u|0,A|0,d|0)}function mVe(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,F(0)}function yVe(o,l){o=o|0,l=y(l),F(1)}function Xa(o,l){o=o|0,l=+l,F(2)}function EVe(o,l,u){return o=o|0,l=y(l),u=y(u),F(3),Xe}function wr(o){o=o|0,F(4)}function T2(o,l){o=o|0,l=l|0,F(5)}function Ll(o){return o=o|0,F(6),0}function IVe(o,l,u,A){o=o|0,l=+l,u=+u,A=A|0,F(7)}function CVe(o,l,u){o=o|0,l=+l,u=+u,F(8)}function wVe(o,l,u){return o=o|0,l=l|0,u=u|0,F(9),0}function BVe(o,l,u){return o=o|0,l=l|0,u=u|0,F(10),0}function pd(o){return o=o|0,F(11),0}function vVe(o,l){return o=o|0,l=+l,F(12),0}function R2(o,l){return o=o|0,l=l|0,F(13),0}function SVe(o,l,u,A,d){o=o|0,l=l|0,u=+u,A=+A,d=d|0,F(14)}function DVe(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,F(15)}function d_(o,l){return o=o|0,l=l|0,F(16),0}function bVe(){return F(17),0}function PVe(o,l,u,A,d){return o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,F(18),0}function xVe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=+A,F(19)}function kVe(o,l,u,A,d,m){o=o|0,l=l|0,u=y(u),A=A|0,d=y(d),m=m|0,F(20)}function $P(o,l,u){o=o|0,l=l|0,u=u|0,F(21)}function QVe(){F(22)}function $y(o,l,u){o=o|0,l=l|0,u=+u,F(23)}function TVe(o,l){return o=+o,l=+l,F(24),0}function eE(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,F(25)}var s$=[mVe,PGe],o$=[yVe,ky],a$=[Xa,Zg,Oh,h2,g2,d2,m2,Pf,My,y2,xf,Xg,$g,E2,I2,vu,ed,C2,_y,Xa,Xa,Xa,Xa,Xa,Xa,Xa,Xa,Xa,Xa,Xa,Xa,Xa],l$=[EVe],op=[wr,Zy,lLe,cLe,uLe,_Ue,UUe,HUe,nqe,iqe,sqe,gGe,dGe,mGe,LWe,MWe,_We,vl,zg,u2,sr,gc,LP,MP,XOe,yLe,kLe,KLe,uMe,bMe,GMe,s_e,I_e,O_e,X_e,hUe,kUe,i4e,I4e,O4e,X4e,h3e,k3e,J3e,u8e,v8e,U8e,vP,mHe,RHe,XHe,dje,Qje,Xje,l6e,f6e,P6e,Q6e,K6e,aqe,uqe,bqe,Wqe,Wz,D5e,n9e,y9e,R9e,tWe,dWe,bWe,kWe,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr],ap=[T2,Ny,zL,f2,A2,xr,ao,Xi,Ls,vs,Ly,Nh,B2,xP,nd,$L,eM,kP,QP,nM,kf,ne,$3e,p8e,Ije,x5e,$qe,xX,T2,T2,T2,T2],hd=[Ll,hYe,Ry,rd,Hy,da,SP,Lh,w2,XL,bP,jy,TP,iM,Wy,q8e,Nje,kqe,R5e,Fl,Ll,Ll,Ll,Ll,Ll,Ll,Ll,Ll,Ll,Ll,Ll,Ll],c$=[IVe,cM],RVe=[CVe,Z6e],ex=[wVe,qX,gYe,yYe,QMe,l4e,CHe,L9e],FVe=[BVe,rUe],u$=[pd,Mh,PP,tp,uM,v,D,Q,H,V,pd,pd,pd,pd,pd,pd],NVe=[vVe,s6e],m_=[R2,_Ye,RP,rLe,XLe,VMe,l_e,FUe,v4e,P8e,Qy,w9e,R2,R2,R2,R2],OVe=[SVe,FLe],LVe=[DVe,sWe],f$=[d_,tM,Se,Ue,At,yUe,d_,d_],tx=[bVe,Gt,Ty,BP,g6e,N6e,hqe,FWe],A$=[PVe,By],MVe=[xVe,r3e],p$=[kVe,sM],F2=[$P,To,DP,rM,Du,hMe,B_e,y3e,F3e,JL,ZGe,a9e,IWe,$P,$P,$P],h$=[QVe],g$=[$y,ZL,Oy,ep,p2,Su,Uy,td,U4e,LHe,t6e,$y,$y,$y,$y,$y],_Ve=[TVe,tqe],y_=[eE,U_e,Z8e,rje,Gje,C6e,H6e,Cqe,zqe,_5e,WWe,eE,eE,eE,eE,eE];return{_llvm_bswap_i32:i$,dynCall_idd:gVe,dynCall_i:lVe,_i64Subtract:ZP,___udivdi3:h_,dynCall_vif:YYe,setThrew:ua,dynCall_viii:AVe,_bitshift64Lshr:XP,_bitshift64Shl:t$,dynCall_vi:JYe,dynCall_viiddi:sVe,dynCall_diii:tVe,dynCall_iii:iVe,_memset:Xy,_sbrk:Yh,_memcpy:Qr,__GLOBAL__sub_I_Yoga_cpp:a2,dynCall_vii:zYe,___uremdi3:g_,dynCall_vid:VYe,stackAlloc:Ha,_nbind_init:rYe,getTempRet0:UA,dynCall_di:rVe,dynCall_iid:nVe,setTempRet0:_A,_i64Add:p_,dynCall_fiff:KYe,dynCall_iiii:eVe,_emscripten_get_global_libc:pYe,dynCall_viid:hVe,dynCall_viiid:uVe,dynCall_viififi:fVe,dynCall_ii:ZYe,__GLOBAL__sub_I_Binding_cc:y5e,dynCall_viiii:dVe,dynCall_iiiiii:cVe,stackSave:gf,dynCall_viiiii:WYe,__GLOBAL__sub_I_nbind_cc:vr,dynCall_vidd:$Ye,_free:JP,runPostSets:GYe,dynCall_viiiiii:oVe,establishStackSpace:wn,_memmove:Q2,stackRestore:cc,_malloc:KP,__GLOBAL__sub_I_common_cc:Mqe,dynCall_viddi:XYe,dynCall_dii:aVe,dynCall_v:pVe}}(Module.asmGlobalArg,Module.asmLibraryArg,buffer),_llvm_bswap_i32=Module._llvm_bswap_i32=asm._llvm_bswap_i32,getTempRet0=Module.getTempRet0=asm.getTempRet0,___udivdi3=Module.___udivdi3=asm.___udivdi3,setThrew=Module.setThrew=asm.setThrew,_bitshift64Lshr=Module._bitshift64Lshr=asm._bitshift64Lshr,_bitshift64Shl=Module._bitshift64Shl=asm._bitshift64Shl,_memset=Module._memset=asm._memset,_sbrk=Module._sbrk=asm._sbrk,_memcpy=Module._memcpy=asm._memcpy,stackAlloc=Module.stackAlloc=asm.stackAlloc,___uremdi3=Module.___uremdi3=asm.___uremdi3,_nbind_init=Module._nbind_init=asm._nbind_init,_i64Subtract=Module._i64Subtract=asm._i64Subtract,setTempRet0=Module.setTempRet0=asm.setTempRet0,_i64Add=Module._i64Add=asm._i64Add,_emscripten_get_global_libc=Module._emscripten_get_global_libc=asm._emscripten_get_global_libc,__GLOBAL__sub_I_Yoga_cpp=Module.__GLOBAL__sub_I_Yoga_cpp=asm.__GLOBAL__sub_I_Yoga_cpp,__GLOBAL__sub_I_Binding_cc=Module.__GLOBAL__sub_I_Binding_cc=asm.__GLOBAL__sub_I_Binding_cc,stackSave=Module.stackSave=asm.stackSave,__GLOBAL__sub_I_nbind_cc=Module.__GLOBAL__sub_I_nbind_cc=asm.__GLOBAL__sub_I_nbind_cc,_free=Module._free=asm._free,runPostSets=Module.runPostSets=asm.runPostSets,establishStackSpace=Module.establishStackSpace=asm.establishStackSpace,_memmove=Module._memmove=asm._memmove,stackRestore=Module.stackRestore=asm.stackRestore,_malloc=Module._malloc=asm._malloc,__GLOBAL__sub_I_common_cc=Module.__GLOBAL__sub_I_common_cc=asm.__GLOBAL__sub_I_common_cc,dynCall_viiiii=Module.dynCall_viiiii=asm.dynCall_viiiii,dynCall_vif=Module.dynCall_vif=asm.dynCall_vif,dynCall_vid=Module.dynCall_vid=asm.dynCall_vid,dynCall_fiff=Module.dynCall_fiff=asm.dynCall_fiff,dynCall_vi=Module.dynCall_vi=asm.dynCall_vi,dynCall_vii=Module.dynCall_vii=asm.dynCall_vii,dynCall_ii=Module.dynCall_ii=asm.dynCall_ii,dynCall_viddi=Module.dynCall_viddi=asm.dynCall_viddi,dynCall_vidd=Module.dynCall_vidd=asm.dynCall_vidd,dynCall_iiii=Module.dynCall_iiii=asm.dynCall_iiii,dynCall_diii=Module.dynCall_diii=asm.dynCall_diii,dynCall_di=Module.dynCall_di=asm.dynCall_di,dynCall_iid=Module.dynCall_iid=asm.dynCall_iid,dynCall_iii=Module.dynCall_iii=asm.dynCall_iii,dynCall_viiddi=Module.dynCall_viiddi=asm.dynCall_viiddi,dynCall_viiiiii=Module.dynCall_viiiiii=asm.dynCall_viiiiii,dynCall_dii=Module.dynCall_dii=asm.dynCall_dii,dynCall_i=Module.dynCall_i=asm.dynCall_i,dynCall_iiiiii=Module.dynCall_iiiiii=asm.dynCall_iiiiii,dynCall_viiid=Module.dynCall_viiid=asm.dynCall_viiid,dynCall_viififi=Module.dynCall_viififi=asm.dynCall_viififi,dynCall_viii=Module.dynCall_viii=asm.dynCall_viii,dynCall_v=Module.dynCall_v=asm.dynCall_v,dynCall_viid=Module.dynCall_viid=asm.dynCall_viid,dynCall_idd=Module.dynCall_idd=asm.dynCall_idd,dynCall_viiii=Module.dynCall_viiii=asm.dynCall_viiii;Runtime.stackAlloc=Module.stackAlloc,Runtime.stackSave=Module.stackSave,Runtime.stackRestore=Module.stackRestore,Runtime.establishStackSpace=Module.establishStackSpace,Runtime.setTempRet0=Module.setTempRet0,Runtime.getTempRet0=Module.getTempRet0,Module.asm=asm;function ExitStatus(t){this.name=\"ExitStatus\",this.message=\"Program terminated with exit(\"+t+\")\",this.status=t}ExitStatus.prototype=new Error,ExitStatus.prototype.constructor=ExitStatus;var initialStackTop,preloadStartTime=null,calledMain=!1;dependenciesFulfilled=function t(){Module.calledRun||run(),Module.calledRun||(dependenciesFulfilled=t)},Module.callMain=Module.callMain=function t(e){e=e||[],ensureInitRuntime();var r=e.length+1;function s(){for(var p=0;p<3;p++)a.push(0)}var a=[allocate(intArrayFromString(Module.thisProgram),\"i8\",ALLOC_NORMAL)];s();for(var n=0;n<r-1;n=n+1)a.push(allocate(intArrayFromString(e[n]),\"i8\",ALLOC_NORMAL)),s();a.push(0),a=allocate(a,\"i32\",ALLOC_NORMAL);try{var c=Module._main(r,a,0);exit(c,!0)}catch(p){if(p instanceof ExitStatus)return;if(p==\"SimulateInfiniteLoop\"){Module.noExitRuntime=!0;return}else{var f=p;p&&typeof p==\"object\"&&p.stack&&(f=[p,p.stack]),Module.printErr(\"exception thrown: \"+f),Module.quit(1,p)}}finally{calledMain=!0}};function run(t){if(t=t||Module.arguments,preloadStartTime===null&&(preloadStartTime=Date.now()),runDependencies>0||(preRun(),runDependencies>0)||Module.calledRun)return;function e(){Module.calledRun||(Module.calledRun=!0,!ABORT&&(ensureInitRuntime(),preMain(),Module.onRuntimeInitialized&&Module.onRuntimeInitialized(),Module._main&&shouldRunNow&&Module.callMain(t),postRun()))}Module.setStatus?(Module.setStatus(\"Running...\"),setTimeout(function(){setTimeout(function(){Module.setStatus(\"\")},1),e()},1)):e()}Module.run=Module.run=run;function exit(t,e){e&&Module.noExitRuntime||(Module.noExitRuntime||(ABORT=!0,EXITSTATUS=t,STACKTOP=initialStackTop,exitRuntime(),Module.onExit&&Module.onExit(t)),ENVIRONMENT_IS_NODE&&process.exit(t),Module.quit(t,new ExitStatus(t)))}Module.exit=Module.exit=exit;var abortDecorators=[];function abort(t){Module.onAbort&&Module.onAbort(t),t!==void 0?(Module.print(t),Module.printErr(t),t=JSON.stringify(t)):t=\"\",ABORT=!0,EXITSTATUS=1;var e=`\nIf this abort() is unexpected, build with -s ASSERTIONS=1 which can give more information.`,r=\"abort(\"+t+\") at \"+stackTrace()+e;throw abortDecorators&&abortDecorators.forEach(function(s){r=s(r,t)}),r}if(Module.abort=Module.abort=abort,Module.preInit)for(typeof Module.preInit==\"function\"&&(Module.preInit=[Module.preInit]);Module.preInit.length>0;)Module.preInit.pop()();var shouldRunNow=!0;Module.noInitialRun&&(shouldRunNow=!1),run()})});var Rm=L((rhr,LDe)=>{\"use strict\";var IPt=NDe(),CPt=ODe(),CW=!1,wW=null;CPt({},function(t,e){if(!CW){if(CW=!0,t)throw t;wW=e}});if(!CW)throw new Error(\"Failed to load the yoga module - it needed to be loaded synchronously, but didn't\");LDe.exports=IPt(wW.bind,wW.lib)});var vW=L((nhr,BW)=>{\"use strict\";var MDe=t=>Number.isNaN(t)?!1:t>=4352&&(t<=4447||t===9001||t===9002||11904<=t&&t<=12871&&t!==12351||12880<=t&&t<=19903||19968<=t&&t<=42182||43360<=t&&t<=43388||44032<=t&&t<=55203||63744<=t&&t<=64255||65040<=t&&t<=65049||65072<=t&&t<=65131||65281<=t&&t<=65376||65504<=t&&t<=65510||110592<=t&&t<=110593||127488<=t&&t<=127569||131072<=t&&t<=262141);BW.exports=MDe;BW.exports.default=MDe});var UDe=L((ihr,_De)=>{\"use strict\";_De.exports=function(){return/\\uD83C\\uDFF4\\uDB40\\uDC67\\uDB40\\uDC62(?:\\uDB40\\uDC65\\uDB40\\uDC6E\\uDB40\\uDC67|\\uDB40\\uDC73\\uDB40\\uDC63\\uDB40\\uDC74|\\uDB40\\uDC77\\uDB40\\uDC6C\\uDB40\\uDC73)\\uDB40\\uDC7F|\\uD83D\\uDC68(?:\\uD83C\\uDFFC\\u200D(?:\\uD83E\\uDD1D\\u200D\\uD83D\\uDC68\\uD83C\\uDFFB|\\uD83C[\\uDF3E\\uDF73\\uDF93\\uDFA4\\uDFA8\\uDFEB\\uDFED]|\\uD83D[\\uDCBB\\uDCBC\\uDD27\\uDD2C\\uDE80\\uDE92]|\\uD83E[\\uDDAF-\\uDDB3\\uDDBC\\uDDBD])|\\uD83C\\uDFFF\\u200D(?:\\uD83E\\uDD1D\\u200D\\uD83D\\uDC68(?:\\uD83C[\\uDFFB-\\uDFFE])|\\uD83C[\\uDF3E\\uDF73\\uDF93\\uDFA4\\uDFA8\\uDFEB\\uDFED]|\\uD83D[\\uDCBB\\uDCBC\\uDD27\\uDD2C\\uDE80\\uDE92]|\\uD83E[\\uDDAF-\\uDDB3\\uDDBC\\uDDBD])|\\uD83C\\uDFFE\\u200D(?:\\uD83E\\uDD1D\\u200D\\uD83D\\uDC68(?:\\uD83C[\\uDFFB-\\uDFFD])|\\uD83C[\\uDF3E\\uDF73\\uDF93\\uDFA4\\uDFA8\\uDFEB\\uDFED]|\\uD83D[\\uDCBB\\uDCBC\\uDD27\\uDD2C\\uDE80\\uDE92]|\\uD83E[\\uDDAF-\\uDDB3\\uDDBC\\uDDBD])|\\uD83C\\uDFFD\\u200D(?:\\uD83E\\uDD1D\\u200D\\uD83D\\uDC68(?:\\uD83C[\\uDFFB\\uDFFC])|\\uD83C[\\uDF3E\\uDF73\\uDF93\\uDFA4\\uDFA8\\uDFEB\\uDFED]|\\uD83D[\\uDCBB\\uDCBC\\uDD27\\uDD2C\\uDE80\\uDE92]|\\uD83E[\\uDDAF-\\uDDB3\\uDDBC\\uDDBD])|\\u200D(?:\\u2764\\uFE0F\\u200D(?:\\uD83D\\uDC8B\\u200D)?\\uD83D\\uDC68|(?:\\uD83D[\\uDC68\\uDC69])\\u200D(?:\\uD83D\\uDC66\\u200D\\uD83D\\uDC66|\\uD83D\\uDC67\\u200D(?:\\uD83D[\\uDC66\\uDC67]))|\\uD83D\\uDC66\\u200D\\uD83D\\uDC66|\\uD83D\\uDC67\\u200D(?:\\uD83D[\\uDC66\\uDC67])|(?:\\uD83D[\\uDC68\\uDC69])\\u200D(?:\\uD83D[\\uDC66\\uDC67])|[\\u2695\\u2696\\u2708]\\uFE0F|\\uD83D[\\uDC66\\uDC67]|\\uD83C[\\uDF3E\\uDF73\\uDF93\\uDFA4\\uDFA8\\uDFEB\\uDFED]|\\uD83D[\\uDCBB\\uDCBC\\uDD27\\uDD2C\\uDE80\\uDE92]|\\uD83E[\\uDDAF-\\uDDB3\\uDDBC\\uDDBD])|(?:\\uD83C\\uDFFB\\u200D[\\u2695\\u2696\\u2708]|\\uD83C\\uDFFF\\u200D[\\u2695\\u2696\\u2708]|\\uD83C\\uDFFE\\u200D[\\u2695\\u2696\\u2708]|\\uD83C\\uDFFD\\u200D[\\u2695\\u2696\\u2708]|\\uD83C\\uDFFC\\u200D[\\u2695\\u2696\\u2708])\\uFE0F|\\uD83C\\uDFFB\\u200D(?:\\uD83C[\\uDF3E\\uDF73\\uDF93\\uDFA4\\uDFA8\\uDFEB\\uDFED]|\\uD83D[\\uDCBB\\uDCBC\\uDD27\\uDD2C\\uDE80\\uDE92]|\\uD83E[\\uDDAF-\\uDDB3\\uDDBC\\uDDBD])|\\uD83C[\\uDFFB-\\uDFFF])|(?:\\uD83E\\uDDD1\\uD83C\\uDFFB\\u200D\\uD83E\\uDD1D\\u200D\\uD83E\\uDDD1|\\uD83D\\uDC69\\uD83C\\uDFFC\\u200D\\uD83E\\uDD1D\\u200D\\uD83D\\uDC69)\\uD83C\\uDFFB|\\uD83E\\uDDD1(?:\\uD83C\\uDFFF\\u200D\\uD83E\\uDD1D\\u200D\\uD83E\\uDDD1(?:\\uD83C[\\uDFFB-\\uDFFF])|\\u200D\\uD83E\\uDD1D\\u200D\\uD83E\\uDDD1)|(?:\\uD83E\\uDDD1\\uD83C\\uDFFE\\u200D\\uD83E\\uDD1D\\u200D\\uD83E\\uDDD1|\\uD83D\\uDC69\\uD83C\\uDFFF\\u200D\\uD83E\\uDD1D\\u200D(?:\\uD83D[\\uDC68\\uDC69]))(?:\\uD83C[\\uDFFB-\\uDFFE])|(?:\\uD83E\\uDDD1\\uD83C\\uDFFC\\u200D\\uD83E\\uDD1D\\u200D\\uD83E\\uDDD1|\\uD83D\\uDC69\\uD83C\\uDFFD\\u200D\\uD83E\\uDD1D\\u200D\\uD83D\\uDC69)(?:\\uD83C[\\uDFFB\\uDFFC])|\\uD83D\\uDC69(?:\\uD83C\\uDFFE\\u200D(?:\\uD83E\\uDD1D\\u200D\\uD83D\\uDC68(?:\\uD83C[\\uDFFB-\\uDFFD\\uDFFF])|\\uD83C[\\uDF3E\\uDF73\\uDF93\\uDFA4\\uDFA8\\uDFEB\\uDFED]|\\uD83D[\\uDCBB\\uDCBC\\uDD27\\uDD2C\\uDE80\\uDE92]|\\uD83E[\\uDDAF-\\uDDB3\\uDDBC\\uDDBD])|\\uD83C\\uDFFC\\u200D(?:\\uD83E\\uDD1D\\u200D\\uD83D\\uDC68(?:\\uD83C[\\uDFFB\\uDFFD-\\uDFFF])|\\uD83C[\\uDF3E\\uDF73\\uDF93\\uDFA4\\uDFA8\\uDFEB\\uDFED]|\\uD83D[\\uDCBB\\uDCBC\\uDD27\\uDD2C\\uDE80\\uDE92]|\\uD83E[\\uDDAF-\\uDDB3\\uDDBC\\uDDBD])|\\uD83C\\uDFFB\\u200D(?:\\uD83E\\uDD1D\\u200D\\uD83D\\uDC68(?:\\uD83C[\\uDFFC-\\uDFFF])|\\uD83C[\\uDF3E\\uDF73\\uDF93\\uDFA4\\uDFA8\\uDFEB\\uDFED]|\\uD83D[\\uDCBB\\uDCBC\\uDD27\\uDD2C\\uDE80\\uDE92]|\\uD83E[\\uDDAF-\\uDDB3\\uDDBC\\uDDBD])|\\uD83C\\uDFFD\\u200D(?:\\uD83E\\uDD1D\\u200D\\uD83D\\uDC68(?:\\uD83C[\\uDFFB\\uDFFC\\uDFFE\\uDFFF])|\\uD83C[\\uDF3E\\uDF73\\uDF93\\uDFA4\\uDFA8\\uDFEB\\uDFED]|\\uD83D[\\uDCBB\\uDCBC\\uDD27\\uDD2C\\uDE80\\uDE92]|\\uD83E[\\uDDAF-\\uDDB3\\uDDBC\\uDDBD])|\\u200D(?:\\u2764\\uFE0F\\u200D(?:\\uD83D\\uDC8B\\u200D(?:\\uD83D[\\uDC68\\uDC69])|\\uD83D[\\uDC68\\uDC69])|\\uD83C[\\uDF3E\\uDF73\\uDF93\\uDFA4\\uDFA8\\uDFEB\\uDFED]|\\uD83D[\\uDCBB\\uDCBC\\uDD27\\uDD2C\\uDE80\\uDE92]|\\uD83E[\\uDDAF-\\uDDB3\\uDDBC\\uDDBD])|\\uD83C\\uDFFF\\u200D(?:\\uD83C[\\uDF3E\\uDF73\\uDF93\\uDFA4\\uDFA8\\uDFEB\\uDFED]|\\uD83D[\\uDCBB\\uDCBC\\uDD27\\uDD2C\\uDE80\\uDE92]|\\uD83E[\\uDDAF-\\uDDB3\\uDDBC\\uDDBD]))|\\uD83D\\uDC69\\u200D\\uD83D\\uDC69\\u200D(?:\\uD83D\\uDC66\\u200D\\uD83D\\uDC66|\\uD83D\\uDC67\\u200D(?:\\uD83D[\\uDC66\\uDC67]))|(?:\\uD83E\\uDDD1\\uD83C\\uDFFD\\u200D\\uD83E\\uDD1D\\u200D\\uD83E\\uDDD1|\\uD83D\\uDC69\\uD83C\\uDFFE\\u200D\\uD83E\\uDD1D\\u200D\\uD83D\\uDC69)(?:\\uD83C[\\uDFFB-\\uDFFD])|\\uD83D\\uDC69\\u200D\\uD83D\\uDC66\\u200D\\uD83D\\uDC66|\\uD83D\\uDC69\\u200D\\uD83D\\uDC69\\u200D(?:\\uD83D[\\uDC66\\uDC67])|(?:\\uD83D\\uDC41\\uFE0F\\u200D\\uD83D\\uDDE8|\\uD83D\\uDC69(?:\\uD83C\\uDFFF\\u200D[\\u2695\\u2696\\u2708]|\\uD83C\\uDFFE\\u200D[\\u2695\\u2696\\u2708]|\\uD83C\\uDFFC\\u200D[\\u2695\\u2696\\u2708]|\\uD83C\\uDFFB\\u200D[\\u2695\\u2696\\u2708]|\\uD83C\\uDFFD\\u200D[\\u2695\\u2696\\u2708]|\\u200D[\\u2695\\u2696\\u2708])|(?:(?:\\u26F9|\\uD83C[\\uDFCB\\uDFCC]|\\uD83D\\uDD75)\\uFE0F|\\uD83D\\uDC6F|\\uD83E[\\uDD3C\\uDDDE\\uDDDF])\\u200D[\\u2640\\u2642]|(?:\\u26F9|\\uD83C[\\uDFCB\\uDFCC]|\\uD83D\\uDD75)(?:\\uD83C[\\uDFFB-\\uDFFF])\\u200D[\\u2640\\u2642]|(?:\\uD83C[\\uDFC3\\uDFC4\\uDFCA]|\\uD83D[\\uDC6E\\uDC71\\uDC73\\uDC77\\uDC81\\uDC82\\uDC86\\uDC87\\uDE45-\\uDE47\\uDE4B\\uDE4D\\uDE4E\\uDEA3\\uDEB4-\\uDEB6]|\\uD83E[\\uDD26\\uDD37-\\uDD39\\uDD3D\\uDD3E\\uDDB8\\uDDB9\\uDDCD-\\uDDCF\\uDDD6-\\uDDDD])(?:(?:\\uD83C[\\uDFFB-\\uDFFF])\\u200D[\\u2640\\u2642]|\\u200D[\\u2640\\u2642])|\\uD83C\\uDFF4\\u200D\\u2620)\\uFE0F|\\uD83D\\uDC69\\u200D\\uD83D\\uDC67\\u200D(?:\\uD83D[\\uDC66\\uDC67])|\\uD83C\\uDFF3\\uFE0F\\u200D\\uD83C\\uDF08|\\uD83D\\uDC15\\u200D\\uD83E\\uDDBA|\\uD83D\\uDC69\\u200D\\uD83D\\uDC66|\\uD83D\\uDC69\\u200D\\uD83D\\uDC67|\\uD83C\\uDDFD\\uD83C\\uDDF0|\\uD83C\\uDDF4\\uD83C\\uDDF2|\\uD83C\\uDDF6\\uD83C\\uDDE6|[#\\*0-9]\\uFE0F\\u20E3|\\uD83C\\uDDE7(?:\\uD83C[\\uDDE6\\uDDE7\\uDDE9-\\uDDEF\\uDDF1-\\uDDF4\\uDDF6-\\uDDF9\\uDDFB\\uDDFC\\uDDFE\\uDDFF])|\\uD83C\\uDDF9(?:\\uD83C[\\uDDE6\\uDDE8\\uDDE9\\uDDEB-\\uDDED\\uDDEF-\\uDDF4\\uDDF7\\uDDF9\\uDDFB\\uDDFC\\uDDFF])|\\uD83C\\uDDEA(?:\\uD83C[\\uDDE6\\uDDE8\\uDDEA\\uDDEC\\uDDED\\uDDF7-\\uDDFA])|\\uD83E\\uDDD1(?:\\uD83C[\\uDFFB-\\uDFFF])|\\uD83C\\uDDF7(?:\\uD83C[\\uDDEA\\uDDF4\\uDDF8\\uDDFA\\uDDFC])|\\uD83D\\uDC69(?:\\uD83C[\\uDFFB-\\uDFFF])|\\uD83C\\uDDF2(?:\\uD83C[\\uDDE6\\uDDE8-\\uDDED\\uDDF0-\\uDDFF])|\\uD83C\\uDDE6(?:\\uD83C[\\uDDE8-\\uDDEC\\uDDEE\\uDDF1\\uDDF2\\uDDF4\\uDDF6-\\uDDFA\\uDDFC\\uDDFD\\uDDFF])|\\uD83C\\uDDF0(?:\\uD83C[\\uDDEA\\uDDEC-\\uDDEE\\uDDF2\\uDDF3\\uDDF5\\uDDF7\\uDDFC\\uDDFE\\uDDFF])|\\uD83C\\uDDED(?:\\uD83C[\\uDDF0\\uDDF2\\uDDF3\\uDDF7\\uDDF9\\uDDFA])|\\uD83C\\uDDE9(?:\\uD83C[\\uDDEA\\uDDEC\\uDDEF\\uDDF0\\uDDF2\\uDDF4\\uDDFF])|\\uD83C\\uDDFE(?:\\uD83C[\\uDDEA\\uDDF9])|\\uD83C\\uDDEC(?:\\uD83C[\\uDDE6\\uDDE7\\uDDE9-\\uDDEE\\uDDF1-\\uDDF3\\uDDF5-\\uDDFA\\uDDFC\\uDDFE])|\\uD83C\\uDDF8(?:\\uD83C[\\uDDE6-\\uDDEA\\uDDEC-\\uDDF4\\uDDF7-\\uDDF9\\uDDFB\\uDDFD-\\uDDFF])|\\uD83C\\uDDEB(?:\\uD83C[\\uDDEE-\\uDDF0\\uDDF2\\uDDF4\\uDDF7])|\\uD83C\\uDDF5(?:\\uD83C[\\uDDE6\\uDDEA-\\uDDED\\uDDF0-\\uDDF3\\uDDF7-\\uDDF9\\uDDFC\\uDDFE])|\\uD83C\\uDDFB(?:\\uD83C[\\uDDE6\\uDDE8\\uDDEA\\uDDEC\\uDDEE\\uDDF3\\uDDFA])|\\uD83C\\uDDF3(?:\\uD83C[\\uDDE6\\uDDE8\\uDDEA-\\uDDEC\\uDDEE\\uDDF1\\uDDF4\\uDDF5\\uDDF7\\uDDFA\\uDDFF])|\\uD83C\\uDDE8(?:\\uD83C[\\uDDE6\\uDDE8\\uDDE9\\uDDEB-\\uDDEE\\uDDF0-\\uDDF5\\uDDF7\\uDDFA-\\uDDFF])|\\uD83C\\uDDF1(?:\\uD83C[\\uDDE6-\\uDDE8\\uDDEE\\uDDF0\\uDDF7-\\uDDFB\\uDDFE])|\\uD83C\\uDDFF(?:\\uD83C[\\uDDE6\\uDDF2\\uDDFC])|\\uD83C\\uDDFC(?:\\uD83C[\\uDDEB\\uDDF8])|\\uD83C\\uDDFA(?:\\uD83C[\\uDDE6\\uDDEC\\uDDF2\\uDDF3\\uDDF8\\uDDFE\\uDDFF])|\\uD83C\\uDDEE(?:\\uD83C[\\uDDE8-\\uDDEA\\uDDF1-\\uDDF4\\uDDF6-\\uDDF9])|\\uD83C\\uDDEF(?:\\uD83C[\\uDDEA\\uDDF2\\uDDF4\\uDDF5])|(?:\\uD83C[\\uDFC3\\uDFC4\\uDFCA]|\\uD83D[\\uDC6E\\uDC71\\uDC73\\uDC77\\uDC81\\uDC82\\uDC86\\uDC87\\uDE45-\\uDE47\\uDE4B\\uDE4D\\uDE4E\\uDEA3\\uDEB4-\\uDEB6]|\\uD83E[\\uDD26\\uDD37-\\uDD39\\uDD3D\\uDD3E\\uDDB8\\uDDB9\\uDDCD-\\uDDCF\\uDDD6-\\uDDDD])(?:\\uD83C[\\uDFFB-\\uDFFF])|(?:\\u26F9|\\uD83C[\\uDFCB\\uDFCC]|\\uD83D\\uDD75)(?:\\uD83C[\\uDFFB-\\uDFFF])|(?:[\\u261D\\u270A-\\u270D]|\\uD83C[\\uDF85\\uDFC2\\uDFC7]|\\uD83D[\\uDC42\\uDC43\\uDC46-\\uDC50\\uDC66\\uDC67\\uDC6B-\\uDC6D\\uDC70\\uDC72\\uDC74-\\uDC76\\uDC78\\uDC7C\\uDC83\\uDC85\\uDCAA\\uDD74\\uDD7A\\uDD90\\uDD95\\uDD96\\uDE4C\\uDE4F\\uDEC0\\uDECC]|\\uD83E[\\uDD0F\\uDD18-\\uDD1C\\uDD1E\\uDD1F\\uDD30-\\uDD36\\uDDB5\\uDDB6\\uDDBB\\uDDD2-\\uDDD5])(?:\\uD83C[\\uDFFB-\\uDFFF])|(?:[\\u231A\\u231B\\u23E9-\\u23EC\\u23F0\\u23F3\\u25FD\\u25FE\\u2614\\u2615\\u2648-\\u2653\\u267F\\u2693\\u26A1\\u26AA\\u26AB\\u26BD\\u26BE\\u26C4\\u26C5\\u26CE\\u26D4\\u26EA\\u26F2\\u26F3\\u26F5\\u26FA\\u26FD\\u2705\\u270A\\u270B\\u2728\\u274C\\u274E\\u2753-\\u2755\\u2757\\u2795-\\u2797\\u27B0\\u27BF\\u2B1B\\u2B1C\\u2B50\\u2B55]|\\uD83C[\\uDC04\\uDCCF\\uDD8E\\uDD91-\\uDD9A\\uDDE6-\\uDDFF\\uDE01\\uDE1A\\uDE2F\\uDE32-\\uDE36\\uDE38-\\uDE3A\\uDE50\\uDE51\\uDF00-\\uDF20\\uDF2D-\\uDF35\\uDF37-\\uDF7C\\uDF7E-\\uDF93\\uDFA0-\\uDFCA\\uDFCF-\\uDFD3\\uDFE0-\\uDFF0\\uDFF4\\uDFF8-\\uDFFF]|\\uD83D[\\uDC00-\\uDC3E\\uDC40\\uDC42-\\uDCFC\\uDCFF-\\uDD3D\\uDD4B-\\uDD4E\\uDD50-\\uDD67\\uDD7A\\uDD95\\uDD96\\uDDA4\\uDDFB-\\uDE4F\\uDE80-\\uDEC5\\uDECC\\uDED0-\\uDED2\\uDED5\\uDEEB\\uDEEC\\uDEF4-\\uDEFA\\uDFE0-\\uDFEB]|\\uD83E[\\uDD0D-\\uDD3A\\uDD3C-\\uDD45\\uDD47-\\uDD71\\uDD73-\\uDD76\\uDD7A-\\uDDA2\\uDDA5-\\uDDAA\\uDDAE-\\uDDCA\\uDDCD-\\uDDFF\\uDE70-\\uDE73\\uDE78-\\uDE7A\\uDE80-\\uDE82\\uDE90-\\uDE95])|(?:[#\\*0-9\\xA9\\xAE\\u203C\\u2049\\u2122\\u2139\\u2194-\\u2199\\u21A9\\u21AA\\u231A\\u231B\\u2328\\u23CF\\u23E9-\\u23F3\\u23F8-\\u23FA\\u24C2\\u25AA\\u25AB\\u25B6\\u25C0\\u25FB-\\u25FE\\u2600-\\u2604\\u260E\\u2611\\u2614\\u2615\\u2618\\u261D\\u2620\\u2622\\u2623\\u2626\\u262A\\u262E\\u262F\\u2638-\\u263A\\u2640\\u2642\\u2648-\\u2653\\u265F\\u2660\\u2663\\u2665\\u2666\\u2668\\u267B\\u267E\\u267F\\u2692-\\u2697\\u2699\\u269B\\u269C\\u26A0\\u26A1\\u26AA\\u26AB\\u26B0\\u26B1\\u26BD\\u26BE\\u26C4\\u26C5\\u26C8\\u26CE\\u26CF\\u26D1\\u26D3\\u26D4\\u26E9\\u26EA\\u26F0-\\u26F5\\u26F7-\\u26FA\\u26FD\\u2702\\u2705\\u2708-\\u270D\\u270F\\u2712\\u2714\\u2716\\u271D\\u2721\\u2728\\u2733\\u2734\\u2744\\u2747\\u274C\\u274E\\u2753-\\u2755\\u2757\\u2763\\u2764\\u2795-\\u2797\\u27A1\\u27B0\\u27BF\\u2934\\u2935\\u2B05-\\u2B07\\u2B1B\\u2B1C\\u2B50\\u2B55\\u3030\\u303D\\u3297\\u3299]|\\uD83C[\\uDC04\\uDCCF\\uDD70\\uDD71\\uDD7E\\uDD7F\\uDD8E\\uDD91-\\uDD9A\\uDDE6-\\uDDFF\\uDE01\\uDE02\\uDE1A\\uDE2F\\uDE32-\\uDE3A\\uDE50\\uDE51\\uDF00-\\uDF21\\uDF24-\\uDF93\\uDF96\\uDF97\\uDF99-\\uDF9B\\uDF9E-\\uDFF0\\uDFF3-\\uDFF5\\uDFF7-\\uDFFF]|\\uD83D[\\uDC00-\\uDCFD\\uDCFF-\\uDD3D\\uDD49-\\uDD4E\\uDD50-\\uDD67\\uDD6F\\uDD70\\uDD73-\\uDD7A\\uDD87\\uDD8A-\\uDD8D\\uDD90\\uDD95\\uDD96\\uDDA4\\uDDA5\\uDDA8\\uDDB1\\uDDB2\\uDDBC\\uDDC2-\\uDDC4\\uDDD1-\\uDDD3\\uDDDC-\\uDDDE\\uDDE1\\uDDE3\\uDDE8\\uDDEF\\uDDF3\\uDDFA-\\uDE4F\\uDE80-\\uDEC5\\uDECB-\\uDED2\\uDED5\\uDEE0-\\uDEE5\\uDEE9\\uDEEB\\uDEEC\\uDEF0\\uDEF3-\\uDEFA\\uDFE0-\\uDFEB]|\\uD83E[\\uDD0D-\\uDD3A\\uDD3C-\\uDD45\\uDD47-\\uDD71\\uDD73-\\uDD76\\uDD7A-\\uDDA2\\uDDA5-\\uDDAA\\uDDAE-\\uDDCA\\uDDCD-\\uDDFF\\uDE70-\\uDE73\\uDE78-\\uDE7A\\uDE80-\\uDE82\\uDE90-\\uDE95])\\uFE0F|(?:[\\u261D\\u26F9\\u270A-\\u270D]|\\uD83C[\\uDF85\\uDFC2-\\uDFC4\\uDFC7\\uDFCA-\\uDFCC]|\\uD83D[\\uDC42\\uDC43\\uDC46-\\uDC50\\uDC66-\\uDC78\\uDC7C\\uDC81-\\uDC83\\uDC85-\\uDC87\\uDC8F\\uDC91\\uDCAA\\uDD74\\uDD75\\uDD7A\\uDD90\\uDD95\\uDD96\\uDE45-\\uDE47\\uDE4B-\\uDE4F\\uDEA3\\uDEB4-\\uDEB6\\uDEC0\\uDECC]|\\uD83E[\\uDD0F\\uDD18-\\uDD1F\\uDD26\\uDD30-\\uDD39\\uDD3C-\\uDD3E\\uDDB5\\uDDB6\\uDDB8\\uDDB9\\uDDBB\\uDDCD-\\uDDCF\\uDDD1-\\uDDDD])/g}});var $S=L((shr,SW)=>{\"use strict\";var wPt=vk(),BPt=vW(),vPt=UDe(),HDe=t=>{if(typeof t!=\"string\"||t.length===0||(t=wPt(t),t.length===0))return 0;t=t.replace(vPt(),\"  \");let e=0;for(let r=0;r<t.length;r++){let s=t.codePointAt(r);s<=31||s>=127&&s<=159||s>=768&&s<=879||(s>65535&&r++,e+=BPt(s)?2:1)}return e};SW.exports=HDe;SW.exports.default=HDe});var bW=L((ohr,DW)=>{\"use strict\";var SPt=$S(),jDe=t=>{let e=0;for(let r of t.split(`\n`))e=Math.max(e,SPt(r));return e};DW.exports=jDe;DW.exports.default=jDe});var qDe=L(eD=>{\"use strict\";var DPt=eD&&eD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(eD,\"__esModule\",{value:!0});var bPt=DPt(bW()),PW={};eD.default=t=>{if(t.length===0)return{width:0,height:0};if(PW[t])return PW[t];let e=bPt.default(t),r=t.split(`\n`).length;return PW[t]={width:e,height:r},{width:e,height:r}}});var GDe=L(tD=>{\"use strict\";var PPt=tD&&tD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(tD,\"__esModule\",{value:!0});var bn=PPt(Rm()),xPt=(t,e)=>{\"position\"in e&&t.setPositionType(e.position===\"absolute\"?bn.default.POSITION_TYPE_ABSOLUTE:bn.default.POSITION_TYPE_RELATIVE)},kPt=(t,e)=>{\"marginLeft\"in e&&t.setMargin(bn.default.EDGE_START,e.marginLeft||0),\"marginRight\"in e&&t.setMargin(bn.default.EDGE_END,e.marginRight||0),\"marginTop\"in e&&t.setMargin(bn.default.EDGE_TOP,e.marginTop||0),\"marginBottom\"in e&&t.setMargin(bn.default.EDGE_BOTTOM,e.marginBottom||0)},QPt=(t,e)=>{\"paddingLeft\"in e&&t.setPadding(bn.default.EDGE_LEFT,e.paddingLeft||0),\"paddingRight\"in e&&t.setPadding(bn.default.EDGE_RIGHT,e.paddingRight||0),\"paddingTop\"in e&&t.setPadding(bn.default.EDGE_TOP,e.paddingTop||0),\"paddingBottom\"in e&&t.setPadding(bn.default.EDGE_BOTTOM,e.paddingBottom||0)},TPt=(t,e)=>{var r;\"flexGrow\"in e&&t.setFlexGrow((r=e.flexGrow)!==null&&r!==void 0?r:0),\"flexShrink\"in e&&t.setFlexShrink(typeof e.flexShrink==\"number\"?e.flexShrink:1),\"flexDirection\"in e&&(e.flexDirection===\"row\"&&t.setFlexDirection(bn.default.FLEX_DIRECTION_ROW),e.flexDirection===\"row-reverse\"&&t.setFlexDirection(bn.default.FLEX_DIRECTION_ROW_REVERSE),e.flexDirection===\"column\"&&t.setFlexDirection(bn.default.FLEX_DIRECTION_COLUMN),e.flexDirection===\"column-reverse\"&&t.setFlexDirection(bn.default.FLEX_DIRECTION_COLUMN_REVERSE)),\"flexBasis\"in e&&(typeof e.flexBasis==\"number\"?t.setFlexBasis(e.flexBasis):typeof e.flexBasis==\"string\"?t.setFlexBasisPercent(Number.parseInt(e.flexBasis,10)):t.setFlexBasis(NaN)),\"alignItems\"in e&&((e.alignItems===\"stretch\"||!e.alignItems)&&t.setAlignItems(bn.default.ALIGN_STRETCH),e.alignItems===\"flex-start\"&&t.setAlignItems(bn.default.ALIGN_FLEX_START),e.alignItems===\"center\"&&t.setAlignItems(bn.default.ALIGN_CENTER),e.alignItems===\"flex-end\"&&t.setAlignItems(bn.default.ALIGN_FLEX_END)),\"alignSelf\"in e&&((e.alignSelf===\"auto\"||!e.alignSelf)&&t.setAlignSelf(bn.default.ALIGN_AUTO),e.alignSelf===\"flex-start\"&&t.setAlignSelf(bn.default.ALIGN_FLEX_START),e.alignSelf===\"center\"&&t.setAlignSelf(bn.default.ALIGN_CENTER),e.alignSelf===\"flex-end\"&&t.setAlignSelf(bn.default.ALIGN_FLEX_END)),\"justifyContent\"in e&&((e.justifyContent===\"flex-start\"||!e.justifyContent)&&t.setJustifyContent(bn.default.JUSTIFY_FLEX_START),e.justifyContent===\"center\"&&t.setJustifyContent(bn.default.JUSTIFY_CENTER),e.justifyContent===\"flex-end\"&&t.setJustifyContent(bn.default.JUSTIFY_FLEX_END),e.justifyContent===\"space-between\"&&t.setJustifyContent(bn.default.JUSTIFY_SPACE_BETWEEN),e.justifyContent===\"space-around\"&&t.setJustifyContent(bn.default.JUSTIFY_SPACE_AROUND))},RPt=(t,e)=>{var r,s;\"width\"in e&&(typeof e.width==\"number\"?t.setWidth(e.width):typeof e.width==\"string\"?t.setWidthPercent(Number.parseInt(e.width,10)):t.setWidthAuto()),\"height\"in e&&(typeof e.height==\"number\"?t.setHeight(e.height):typeof e.height==\"string\"?t.setHeightPercent(Number.parseInt(e.height,10)):t.setHeightAuto()),\"minWidth\"in e&&(typeof e.minWidth==\"string\"?t.setMinWidthPercent(Number.parseInt(e.minWidth,10)):t.setMinWidth((r=e.minWidth)!==null&&r!==void 0?r:0)),\"minHeight\"in e&&(typeof e.minHeight==\"string\"?t.setMinHeightPercent(Number.parseInt(e.minHeight,10)):t.setMinHeight((s=e.minHeight)!==null&&s!==void 0?s:0))},FPt=(t,e)=>{\"display\"in e&&t.setDisplay(e.display===\"flex\"?bn.default.DISPLAY_FLEX:bn.default.DISPLAY_NONE)},NPt=(t,e)=>{if(\"borderStyle\"in e){let r=typeof e.borderStyle==\"string\"?1:0;t.setBorder(bn.default.EDGE_TOP,r),t.setBorder(bn.default.EDGE_BOTTOM,r),t.setBorder(bn.default.EDGE_LEFT,r),t.setBorder(bn.default.EDGE_RIGHT,r)}};tD.default=(t,e={})=>{xPt(t,e),kPt(t,e),QPt(t,e),TPt(t,e),RPt(t,e),FPt(t,e),NPt(t,e)}});var VDe=L((chr,YDe)=>{\"use strict\";var rD=$S(),OPt=vk(),LPt=pk(),kW=new Set([\"\\x1B\",\"\\x9B\"]),MPt=39,WDe=t=>`${kW.values().next().value}[${t}m`,_Pt=t=>t.split(\" \").map(e=>rD(e)),xW=(t,e,r)=>{let s=[...e],a=!1,n=rD(OPt(t[t.length-1]));for(let[c,f]of s.entries()){let p=rD(f);if(n+p<=r?t[t.length-1]+=f:(t.push(f),n=0),kW.has(f))a=!0;else if(a&&f===\"m\"){a=!1;continue}a||(n+=p,n===r&&c<s.length-1&&(t.push(\"\"),n=0))}!n&&t[t.length-1].length>0&&t.length>1&&(t[t.length-2]+=t.pop())},UPt=t=>{let e=t.split(\" \"),r=e.length;for(;r>0&&!(rD(e[r-1])>0);)r--;return r===e.length?t:e.slice(0,r).join(\" \")+e.slice(r).join(\"\")},HPt=(t,e,r={})=>{if(r.trim!==!1&&t.trim()===\"\")return\"\";let s=\"\",a=\"\",n,c=_Pt(t),f=[\"\"];for(let[p,h]of t.split(\" \").entries()){r.trim!==!1&&(f[f.length-1]=f[f.length-1].trimLeft());let E=rD(f[f.length-1]);if(p!==0&&(E>=e&&(r.wordWrap===!1||r.trim===!1)&&(f.push(\"\"),E=0),(E>0||r.trim===!1)&&(f[f.length-1]+=\" \",E++)),r.hard&&c[p]>e){let C=e-E,S=1+Math.floor((c[p]-C-1)/e);Math.floor((c[p]-1)/e)<S&&f.push(\"\"),xW(f,h,e);continue}if(E+c[p]>e&&E>0&&c[p]>0){if(r.wordWrap===!1&&E<e){xW(f,h,e);continue}f.push(\"\")}if(E+c[p]>e&&r.wordWrap===!1){xW(f,h,e);continue}f[f.length-1]+=h}r.trim!==!1&&(f=f.map(UPt)),s=f.join(`\n`);for(let[p,h]of[...s].entries()){if(a+=h,kW.has(h)){let C=parseFloat(/\\d[^m]*/.exec(s.slice(p,p+4)));n=C===MPt?null:C}let E=LPt.codes.get(Number(n));n&&E&&(s[p+1]===`\n`?a+=WDe(E):h===`\n`&&(a+=WDe(n)))}return a};YDe.exports=(t,e,r)=>String(t).normalize().replace(/\\r\\n/g,`\n`).split(`\n`).map(s=>HPt(s,e,r)).join(`\n`)});var zDe=L((uhr,JDe)=>{\"use strict\";var KDe=\"[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]\",jPt=t=>t&&t.exact?new RegExp(`^${KDe}$`):new RegExp(KDe,\"g\");JDe.exports=jPt});var QW=L((fhr,ebe)=>{\"use strict\";var qPt=vW(),GPt=zDe(),ZDe=pk(),$De=[\"\\x1B\",\"\\x9B\"],OF=t=>`${$De[0]}[${t}m`,XDe=(t,e,r)=>{let s=[];t=[...t];for(let a of t){let n=a;a.match(\";\")&&(a=a.split(\";\")[0][0]+\"0\");let c=ZDe.codes.get(parseInt(a,10));if(c){let f=t.indexOf(c.toString());f>=0?t.splice(f,1):s.push(OF(e?c:n))}else if(e){s.push(OF(0));break}else s.push(OF(n))}if(e&&(s=s.filter((a,n)=>s.indexOf(a)===n),r!==void 0)){let a=OF(ZDe.codes.get(parseInt(r,10)));s=s.reduce((n,c)=>c===a?[c,...n]:[...n,c],[])}return s.join(\"\")};ebe.exports=(t,e,r)=>{let s=[...t.normalize()],a=[];r=typeof r==\"number\"?r:s.length;let n=!1,c,f=0,p=\"\";for(let[h,E]of s.entries()){let C=!1;if($De.includes(E)){let S=/\\d[^m]*/.exec(t.slice(h,h+18));c=S&&S.length>0?S[0]:void 0,f<r&&(n=!0,c!==void 0&&a.push(c))}else n&&E===\"m\"&&(n=!1,C=!0);if(!n&&!C&&++f,!GPt({exact:!0}).test(E)&&qPt(E.codePointAt())&&++f,f>e&&f<=r)p+=E;else if(f===e&&!n&&c!==void 0)p=XDe(a);else if(f>=r){p+=XDe(a,!0,c);break}}return p}});var rbe=L((Ahr,tbe)=>{\"use strict\";var X0=QW(),WPt=$S();function LF(t,e,r){if(t.charAt(e)===\" \")return e;for(let s=1;s<=3;s++)if(r){if(t.charAt(e+s)===\" \")return e+s}else if(t.charAt(e-s)===\" \")return e-s;return e}tbe.exports=(t,e,r)=>{r={position:\"end\",preferTruncationOnSpace:!1,...r};let{position:s,space:a,preferTruncationOnSpace:n}=r,c=\"\\u2026\",f=1;if(typeof t!=\"string\")throw new TypeError(`Expected \\`input\\` to be a string, got ${typeof t}`);if(typeof e!=\"number\")throw new TypeError(`Expected \\`columns\\` to be a number, got ${typeof e}`);if(e<1)return\"\";if(e===1)return c;let p=WPt(t);if(p<=e)return t;if(s===\"start\"){if(n){let h=LF(t,p-e+1,!0);return c+X0(t,h,p).trim()}return a===!0&&(c+=\" \",f=2),c+X0(t,p-e+f,p)}if(s===\"middle\"){a===!0&&(c=\" \"+c+\" \",f=3);let h=Math.floor(e/2);if(n){let E=LF(t,h),C=LF(t,p-(e-h)+1,!0);return X0(t,0,E)+c+X0(t,C,p).trim()}return X0(t,0,h)+c+X0(t,p-(e-h)+f,p)}if(s===\"end\"){if(n){let h=LF(t,e-1);return X0(t,0,h)+c}return a===!0&&(c=\" \"+c,f=2),X0(t,0,e-f)+c}throw new Error(`Expected \\`options.position\\` to be either \\`start\\`, \\`middle\\` or \\`end\\`, got ${s}`)}});var RW=L(nD=>{\"use strict\";var nbe=nD&&nD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(nD,\"__esModule\",{value:!0});var YPt=nbe(VDe()),VPt=nbe(rbe()),TW={};nD.default=(t,e,r)=>{let s=t+String(e)+String(r);if(TW[s])return TW[s];let a=t;if(r===\"wrap\"&&(a=YPt.default(t,e,{trim:!1,hard:!0})),r.startsWith(\"truncate\")){let n=\"end\";r===\"truncate-middle\"&&(n=\"middle\"),r===\"truncate-start\"&&(n=\"start\"),a=VPt.default(t,e,{position:n})}return TW[s]=a,a}});var NW=L(FW=>{\"use strict\";Object.defineProperty(FW,\"__esModule\",{value:!0});var ibe=t=>{let e=\"\";if(t.childNodes.length>0)for(let r of t.childNodes){let s=\"\";r.nodeName===\"#text\"?s=r.nodeValue:((r.nodeName===\"ink-text\"||r.nodeName===\"ink-virtual-text\")&&(s=ibe(r)),s.length>0&&typeof r.internal_transform==\"function\"&&(s=r.internal_transform(s))),e+=s}return e};FW.default=ibe});var OW=L(xi=>{\"use strict\";var iD=xi&&xi.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(xi,\"__esModule\",{value:!0});xi.setTextNodeValue=xi.createTextNode=xi.setStyle=xi.setAttribute=xi.removeChildNode=xi.insertBeforeNode=xi.appendChildNode=xi.createNode=xi.TEXT_NAME=void 0;var KPt=iD(Rm()),sbe=iD(qDe()),JPt=iD(GDe()),zPt=iD(RW()),ZPt=iD(NW());xi.TEXT_NAME=\"#text\";xi.createNode=t=>{var e;let r={nodeName:t,style:{},attributes:{},childNodes:[],parentNode:null,yogaNode:t===\"ink-virtual-text\"?void 0:KPt.default.Node.create()};return t===\"ink-text\"&&((e=r.yogaNode)===null||e===void 0||e.setMeasureFunc(XPt.bind(null,r))),r};xi.appendChildNode=(t,e)=>{var r;e.parentNode&&xi.removeChildNode(e.parentNode,e),e.parentNode=t,t.childNodes.push(e),e.yogaNode&&((r=t.yogaNode)===null||r===void 0||r.insertChild(e.yogaNode,t.yogaNode.getChildCount())),(t.nodeName===\"ink-text\"||t.nodeName===\"ink-virtual-text\")&&MF(t)};xi.insertBeforeNode=(t,e,r)=>{var s,a;e.parentNode&&xi.removeChildNode(e.parentNode,e),e.parentNode=t;let n=t.childNodes.indexOf(r);if(n>=0){t.childNodes.splice(n,0,e),e.yogaNode&&((s=t.yogaNode)===null||s===void 0||s.insertChild(e.yogaNode,n));return}t.childNodes.push(e),e.yogaNode&&((a=t.yogaNode)===null||a===void 0||a.insertChild(e.yogaNode,t.yogaNode.getChildCount())),(t.nodeName===\"ink-text\"||t.nodeName===\"ink-virtual-text\")&&MF(t)};xi.removeChildNode=(t,e)=>{var r,s;e.yogaNode&&((s=(r=e.parentNode)===null||r===void 0?void 0:r.yogaNode)===null||s===void 0||s.removeChild(e.yogaNode)),e.parentNode=null;let a=t.childNodes.indexOf(e);a>=0&&t.childNodes.splice(a,1),(t.nodeName===\"ink-text\"||t.nodeName===\"ink-virtual-text\")&&MF(t)};xi.setAttribute=(t,e,r)=>{t.attributes[e]=r};xi.setStyle=(t,e)=>{t.style=e,t.yogaNode&&JPt.default(t.yogaNode,e)};xi.createTextNode=t=>{let e={nodeName:\"#text\",nodeValue:t,yogaNode:void 0,parentNode:null,style:{}};return xi.setTextNodeValue(e,t),e};var XPt=function(t,e){var r,s;let a=t.nodeName===\"#text\"?t.nodeValue:ZPt.default(t),n=sbe.default(a);if(n.width<=e||n.width>=1&&e>0&&e<1)return n;let c=(s=(r=t.style)===null||r===void 0?void 0:r.textWrap)!==null&&s!==void 0?s:\"wrap\",f=zPt.default(a,e,c);return sbe.default(f)},obe=t=>{var e;if(!(!t||!t.parentNode))return(e=t.yogaNode)!==null&&e!==void 0?e:obe(t.parentNode)},MF=t=>{let e=obe(t);e?.markDirty()};xi.setTextNodeValue=(t,e)=>{typeof e!=\"string\"&&(e=String(e)),t.nodeValue=e,MF(t)}});var fbe=L(sD=>{\"use strict\";var ube=sD&&sD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(sD,\"__esModule\",{value:!0});var abe=yW(),$Pt=ube(xDe()),lbe=ube(Rm()),ta=OW(),cbe=t=>{t?.unsetMeasureFunc(),t?.freeRecursive()};sD.default=$Pt.default({schedulePassiveEffects:abe.unstable_scheduleCallback,cancelPassiveEffects:abe.unstable_cancelCallback,now:Date.now,getRootHostContext:()=>({isInsideText:!1}),prepareForCommit:()=>null,preparePortalMount:()=>null,clearContainer:()=>!1,shouldDeprioritizeSubtree:()=>!1,resetAfterCommit:t=>{if(t.isStaticDirty){t.isStaticDirty=!1,typeof t.onImmediateRender==\"function\"&&t.onImmediateRender();return}typeof t.onRender==\"function\"&&t.onRender()},getChildHostContext:(t,e)=>{let r=t.isInsideText,s=e===\"ink-text\"||e===\"ink-virtual-text\";return r===s?t:{isInsideText:s}},shouldSetTextContent:()=>!1,createInstance:(t,e,r,s)=>{if(s.isInsideText&&t===\"ink-box\")throw new Error(\"<Box> can\\u2019t be nested inside <Text> component\");let a=t===\"ink-text\"&&s.isInsideText?\"ink-virtual-text\":t,n=ta.createNode(a);for(let[c,f]of Object.entries(e))c!==\"children\"&&(c===\"style\"?ta.setStyle(n,f):c===\"internal_transform\"?n.internal_transform=f:c===\"internal_static\"?n.internal_static=!0:ta.setAttribute(n,c,f));return n},createTextInstance:(t,e,r)=>{if(!r.isInsideText)throw new Error(`Text string \"${t}\" must be rendered inside <Text> component`);return ta.createTextNode(t)},resetTextContent:()=>{},hideTextInstance:t=>{ta.setTextNodeValue(t,\"\")},unhideTextInstance:(t,e)=>{ta.setTextNodeValue(t,e)},getPublicInstance:t=>t,hideInstance:t=>{var e;(e=t.yogaNode)===null||e===void 0||e.setDisplay(lbe.default.DISPLAY_NONE)},unhideInstance:t=>{var e;(e=t.yogaNode)===null||e===void 0||e.setDisplay(lbe.default.DISPLAY_FLEX)},appendInitialChild:ta.appendChildNode,appendChild:ta.appendChildNode,insertBefore:ta.insertBeforeNode,finalizeInitialChildren:(t,e,r,s)=>(t.internal_static&&(s.isStaticDirty=!0,s.staticNode=t),!1),supportsMutation:!0,appendChildToContainer:ta.appendChildNode,insertInContainerBefore:ta.insertBeforeNode,removeChildFromContainer:(t,e)=>{ta.removeChildNode(t,e),cbe(e.yogaNode)},prepareUpdate:(t,e,r,s,a)=>{t.internal_static&&(a.isStaticDirty=!0);let n={},c=Object.keys(s);for(let f of c)if(s[f]!==r[f]){if(f===\"style\"&&typeof s.style==\"object\"&&typeof r.style==\"object\"){let h=s.style,E=r.style,C=Object.keys(h);for(let S of C){if(S===\"borderStyle\"||S===\"borderColor\"){if(typeof n.style!=\"object\"){let P={};n.style=P}n.style.borderStyle=h.borderStyle,n.style.borderColor=h.borderColor}if(h[S]!==E[S]){if(typeof n.style!=\"object\"){let P={};n.style=P}n.style[S]=h[S]}}continue}n[f]=s[f]}return n},commitUpdate:(t,e)=>{for(let[r,s]of Object.entries(e))r!==\"children\"&&(r===\"style\"?ta.setStyle(t,s):r===\"internal_transform\"?t.internal_transform=s:r===\"internal_static\"?t.internal_static=!0:ta.setAttribute(t,r,s))},commitTextUpdate:(t,e,r)=>{ta.setTextNodeValue(t,r)},removeChild:(t,e)=>{ta.removeChildNode(t,e),cbe(e.yogaNode)}})});var pbe=L((mhr,Abe)=>{\"use strict\";Abe.exports=(t,e=1,r)=>{if(r={indent:\" \",includeEmptyLines:!1,...r},typeof t!=\"string\")throw new TypeError(`Expected \\`input\\` to be a \\`string\\`, got \\`${typeof t}\\``);if(typeof e!=\"number\")throw new TypeError(`Expected \\`count\\` to be a \\`number\\`, got \\`${typeof e}\\``);if(typeof r.indent!=\"string\")throw new TypeError(`Expected \\`options.indent\\` to be a \\`string\\`, got \\`${typeof r.indent}\\``);if(e===0)return t;let s=r.includeEmptyLines?/^/gm:/^(?!\\s*$)/gm;return t.replace(s,r.indent.repeat(e))}});var hbe=L(oD=>{\"use strict\";var ext=oD&&oD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(oD,\"__esModule\",{value:!0});var _F=ext(Rm());oD.default=t=>t.getComputedWidth()-t.getComputedPadding(_F.default.EDGE_LEFT)-t.getComputedPadding(_F.default.EDGE_RIGHT)-t.getComputedBorder(_F.default.EDGE_LEFT)-t.getComputedBorder(_F.default.EDGE_RIGHT)});var gbe=L((Ehr,txt)=>{txt.exports={single:{topLeft:\"\\u250C\",topRight:\"\\u2510\",bottomRight:\"\\u2518\",bottomLeft:\"\\u2514\",vertical:\"\\u2502\",horizontal:\"\\u2500\"},double:{topLeft:\"\\u2554\",topRight:\"\\u2557\",bottomRight:\"\\u255D\",bottomLeft:\"\\u255A\",vertical:\"\\u2551\",horizontal:\"\\u2550\"},round:{topLeft:\"\\u256D\",topRight:\"\\u256E\",bottomRight:\"\\u256F\",bottomLeft:\"\\u2570\",vertical:\"\\u2502\",horizontal:\"\\u2500\"},bold:{topLeft:\"\\u250F\",topRight:\"\\u2513\",bottomRight:\"\\u251B\",bottomLeft:\"\\u2517\",vertical:\"\\u2503\",horizontal:\"\\u2501\"},singleDouble:{topLeft:\"\\u2553\",topRight:\"\\u2556\",bottomRight:\"\\u255C\",bottomLeft:\"\\u2559\",vertical:\"\\u2551\",horizontal:\"\\u2500\"},doubleSingle:{topLeft:\"\\u2552\",topRight:\"\\u2555\",bottomRight:\"\\u255B\",bottomLeft:\"\\u2558\",vertical:\"\\u2502\",horizontal:\"\\u2550\"},classic:{topLeft:\"+\",topRight:\"+\",bottomRight:\"+\",bottomLeft:\"+\",vertical:\"|\",horizontal:\"-\"}}});var mbe=L((Ihr,LW)=>{\"use strict\";var dbe=gbe();LW.exports=dbe;LW.exports.default=dbe});var MW=L(lD=>{\"use strict\";var rxt=lD&&lD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(lD,\"__esModule\",{value:!0});var aD=rxt(kE()),nxt=/^(rgb|hsl|hsv|hwb)\\(\\s?(\\d+),\\s?(\\d+),\\s?(\\d+)\\s?\\)$/,ixt=/^(ansi|ansi256)\\(\\s?(\\d+)\\s?\\)$/,UF=(t,e)=>e===\"foreground\"?t:\"bg\"+t[0].toUpperCase()+t.slice(1);lD.default=(t,e,r)=>{if(!e)return t;if(e in aD.default){let a=UF(e,r);return aD.default[a](t)}if(e.startsWith(\"#\")){let a=UF(\"hex\",r);return aD.default[a](e)(t)}if(e.startsWith(\"ansi\")){let a=ixt.exec(e);if(!a)return t;let n=UF(a[1],r),c=Number(a[2]);return aD.default[n](c)(t)}if(e.startsWith(\"rgb\")||e.startsWith(\"hsl\")||e.startsWith(\"hsv\")||e.startsWith(\"hwb\")){let a=nxt.exec(e);if(!a)return t;let n=UF(a[1],r),c=Number(a[2]),f=Number(a[3]),p=Number(a[4]);return aD.default[n](c,f,p)(t)}return t}});var Ebe=L(cD=>{\"use strict\";var ybe=cD&&cD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(cD,\"__esModule\",{value:!0});var sxt=ybe(mbe()),_W=ybe(MW());cD.default=(t,e,r,s)=>{if(typeof r.style.borderStyle==\"string\"){let a=r.yogaNode.getComputedWidth(),n=r.yogaNode.getComputedHeight(),c=r.style.borderColor,f=sxt.default[r.style.borderStyle],p=_W.default(f.topLeft+f.horizontal.repeat(a-2)+f.topRight,c,\"foreground\"),h=(_W.default(f.vertical,c,\"foreground\")+`\n`).repeat(n-2),E=_W.default(f.bottomLeft+f.horizontal.repeat(a-2)+f.bottomRight,c,\"foreground\");s.write(t,e,p,{transformers:[]}),s.write(t,e+1,h,{transformers:[]}),s.write(t+a-1,e+1,h,{transformers:[]}),s.write(t,e+n-1,E,{transformers:[]})}}});var Cbe=L(uD=>{\"use strict\";var Fm=uD&&uD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(uD,\"__esModule\",{value:!0});var oxt=Fm(Rm()),axt=Fm(bW()),lxt=Fm(pbe()),cxt=Fm(RW()),uxt=Fm(hbe()),fxt=Fm(NW()),Axt=Fm(Ebe()),pxt=(t,e)=>{var r;let s=(r=t.childNodes[0])===null||r===void 0?void 0:r.yogaNode;if(s){let a=s.getComputedLeft(),n=s.getComputedTop();e=`\n`.repeat(n)+lxt.default(e,a)}return e},Ibe=(t,e,r)=>{var s;let{offsetX:a=0,offsetY:n=0,transformers:c=[],skipStaticElements:f}=r;if(f&&t.internal_static)return;let{yogaNode:p}=t;if(p){if(p.getDisplay()===oxt.default.DISPLAY_NONE)return;let h=a+p.getComputedLeft(),E=n+p.getComputedTop(),C=c;if(typeof t.internal_transform==\"function\"&&(C=[t.internal_transform,...c]),t.nodeName===\"ink-text\"){let S=fxt.default(t);if(S.length>0){let P=axt.default(S),I=uxt.default(p);if(P>I){let R=(s=t.style.textWrap)!==null&&s!==void 0?s:\"wrap\";S=cxt.default(S,I,R)}S=pxt(t,S),e.write(h,E,S,{transformers:C})}return}if(t.nodeName===\"ink-box\"&&Axt.default(h,E,t,e),t.nodeName===\"ink-root\"||t.nodeName===\"ink-box\")for(let S of t.childNodes)Ibe(S,e,{offsetX:h,offsetY:E,transformers:C,skipStaticElements:f})}};uD.default=Ibe});var vbe=L(fD=>{\"use strict\";var Bbe=fD&&fD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(fD,\"__esModule\",{value:!0});var wbe=Bbe(QW()),hxt=Bbe($S()),UW=class{constructor(e){this.writes=[];let{width:r,height:s}=e;this.width=r,this.height=s}write(e,r,s,a){let{transformers:n}=a;s&&this.writes.push({x:e,y:r,text:s,transformers:n})}get(){let e=[];for(let s=0;s<this.height;s++)e.push(\" \".repeat(this.width));for(let s of this.writes){let{x:a,y:n,text:c,transformers:f}=s,p=c.split(`\n`),h=0;for(let E of p){let C=e[n+h];if(!C)continue;let S=hxt.default(E);for(let P of f)E=P(E);e[n+h]=wbe.default(C,0,a)+E+wbe.default(C,a+S),h++}}return{output:e.map(s=>s.trimRight()).join(`\n`),height:e.length}}};fD.default=UW});var bbe=L(AD=>{\"use strict\";var HW=AD&&AD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(AD,\"__esModule\",{value:!0});var gxt=HW(Rm()),Sbe=HW(Cbe()),Dbe=HW(vbe());AD.default=(t,e)=>{var r;if(t.yogaNode.setWidth(e),t.yogaNode){t.yogaNode.calculateLayout(void 0,void 0,gxt.default.DIRECTION_LTR);let s=new Dbe.default({width:t.yogaNode.getComputedWidth(),height:t.yogaNode.getComputedHeight()});Sbe.default(t,s,{skipStaticElements:!0});let a;!((r=t.staticNode)===null||r===void 0)&&r.yogaNode&&(a=new Dbe.default({width:t.staticNode.yogaNode.getComputedWidth(),height:t.staticNode.yogaNode.getComputedHeight()}),Sbe.default(t.staticNode,a,{skipStaticElements:!1}));let{output:n,height:c}=s.get();return{output:n,outputHeight:c,staticOutput:a?`${a.get().output}\n`:\"\"}}return{output:\"\",outputHeight:0,staticOutput:\"\"}}});var Qbe=L((Dhr,kbe)=>{\"use strict\";var Pbe=Ie(\"stream\"),xbe=[\"assert\",\"count\",\"countReset\",\"debug\",\"dir\",\"dirxml\",\"error\",\"group\",\"groupCollapsed\",\"groupEnd\",\"info\",\"log\",\"table\",\"time\",\"timeEnd\",\"timeLog\",\"trace\",\"warn\"],jW={},dxt=t=>{let e=new Pbe.PassThrough,r=new Pbe.PassThrough;e.write=a=>t(\"stdout\",a),r.write=a=>t(\"stderr\",a);let s=new console.Console(e,r);for(let a of xbe)jW[a]=console[a],console[a]=s[a];return()=>{for(let a of xbe)console[a]=jW[a];jW={}}};kbe.exports=dxt});var GW=L(qW=>{\"use strict\";Object.defineProperty(qW,\"__esModule\",{value:!0});qW.default=new WeakMap});var YW=L(WW=>{\"use strict\";Object.defineProperty(WW,\"__esModule\",{value:!0});var mxt=hn(),Tbe=mxt.createContext({exit:()=>{}});Tbe.displayName=\"InternalAppContext\";WW.default=Tbe});var KW=L(VW=>{\"use strict\";Object.defineProperty(VW,\"__esModule\",{value:!0});var yxt=hn(),Rbe=yxt.createContext({stdin:void 0,setRawMode:()=>{},isRawModeSupported:!1,internal_exitOnCtrlC:!0});Rbe.displayName=\"InternalStdinContext\";VW.default=Rbe});var zW=L(JW=>{\"use strict\";Object.defineProperty(JW,\"__esModule\",{value:!0});var Ext=hn(),Fbe=Ext.createContext({stdout:void 0,write:()=>{}});Fbe.displayName=\"InternalStdoutContext\";JW.default=Fbe});var XW=L(ZW=>{\"use strict\";Object.defineProperty(ZW,\"__esModule\",{value:!0});var Ixt=hn(),Nbe=Ixt.createContext({stderr:void 0,write:()=>{}});Nbe.displayName=\"InternalStderrContext\";ZW.default=Nbe});var HF=L($W=>{\"use strict\";Object.defineProperty($W,\"__esModule\",{value:!0});var Cxt=hn(),Obe=Cxt.createContext({activeId:void 0,add:()=>{},remove:()=>{},activate:()=>{},deactivate:()=>{},enableFocus:()=>{},disableFocus:()=>{},focusNext:()=>{},focusPrevious:()=>{},focus:()=>{}});Obe.displayName=\"InternalFocusContext\";$W.default=Obe});var Mbe=L((Rhr,Lbe)=>{\"use strict\";var wxt=/[|\\\\{}()[\\]^$+*?.-]/g;Lbe.exports=t=>{if(typeof t!=\"string\")throw new TypeError(\"Expected a string\");return t.replace(wxt,\"\\\\$&\")}});var jbe=L((Fhr,Hbe)=>{\"use strict\";var Bxt=Mbe(),vxt=typeof process==\"object\"&&process&&typeof process.cwd==\"function\"?process.cwd():\".\",Ube=[].concat(Ie(\"module\").builtinModules,\"bootstrap_node\",\"node\").map(t=>new RegExp(`(?:\\\\((?:node:)?${t}(?:\\\\.js)?:\\\\d+:\\\\d+\\\\)$|^\\\\s*at (?:node:)?${t}(?:\\\\.js)?:\\\\d+:\\\\d+$)`));Ube.push(/\\((?:node:)?internal\\/[^:]+:\\d+:\\d+\\)$/,/\\s*at (?:node:)?internal\\/[^:]+:\\d+:\\d+$/,/\\/\\.node-spawn-wrap-\\w+-\\w+\\/node:\\d+:\\d+\\)?$/);var eY=class t{constructor(e){e={ignoredPackages:[],...e},\"internals\"in e||(e.internals=t.nodeInternals()),\"cwd\"in e||(e.cwd=vxt),this._cwd=e.cwd.replace(/\\\\/g,\"/\"),this._internals=[].concat(e.internals,Sxt(e.ignoredPackages)),this._wrapCallSite=e.wrapCallSite||!1}static nodeInternals(){return[...Ube]}clean(e,r=0){r=\" \".repeat(r),Array.isArray(e)||(e=e.split(`\n`)),!/^\\s*at /.test(e[0])&&/^\\s*at /.test(e[1])&&(e=e.slice(1));let s=!1,a=null,n=[];return e.forEach(c=>{if(c=c.replace(/\\\\/g,\"/\"),this._internals.some(p=>p.test(c)))return;let f=/^\\s*at /.test(c);s?c=c.trimEnd().replace(/^(\\s+)at /,\"$1\"):(c=c.trim(),f&&(c=c.slice(3))),c=c.replace(`${this._cwd}/`,\"\"),c&&(f?(a&&(n.push(a),a=null),n.push(c)):(s=!0,a=c))}),n.map(c=>`${r}${c}\n`).join(\"\")}captureString(e,r=this.captureString){typeof e==\"function\"&&(r=e,e=1/0);let{stackTraceLimit:s}=Error;e&&(Error.stackTraceLimit=e);let a={};Error.captureStackTrace(a,r);let{stack:n}=a;return Error.stackTraceLimit=s,this.clean(n)}capture(e,r=this.capture){typeof e==\"function\"&&(r=e,e=1/0);let{prepareStackTrace:s,stackTraceLimit:a}=Error;Error.prepareStackTrace=(f,p)=>this._wrapCallSite?p.map(this._wrapCallSite):p,e&&(Error.stackTraceLimit=e);let n={};Error.captureStackTrace(n,r);let{stack:c}=n;return Object.assign(Error,{prepareStackTrace:s,stackTraceLimit:a}),c}at(e=this.at){let[r]=this.capture(1,e);if(!r)return{};let s={line:r.getLineNumber(),column:r.getColumnNumber()};_be(s,r.getFileName(),this._cwd),r.isConstructor()&&(s.constructor=!0),r.isEval()&&(s.evalOrigin=r.getEvalOrigin()),r.isNative()&&(s.native=!0);let a;try{a=r.getTypeName()}catch{}a&&a!==\"Object\"&&a!==\"[object Object]\"&&(s.type=a);let n=r.getFunctionName();n&&(s.function=n);let c=r.getMethodName();return c&&n!==c&&(s.method=c),s}parseLine(e){let r=e&&e.match(Dxt);if(!r)return null;let s=r[1]===\"new\",a=r[2],n=r[3],c=r[4],f=Number(r[5]),p=Number(r[6]),h=r[7],E=r[8],C=r[9],S=r[10]===\"native\",P=r[11]===\")\",I,R={};if(E&&(R.line=Number(E)),C&&(R.column=Number(C)),P&&h){let N=0;for(let U=h.length-1;U>0;U--)if(h.charAt(U)===\")\")N++;else if(h.charAt(U)===\"(\"&&h.charAt(U-1)===\" \"&&(N--,N===-1&&h.charAt(U-1)===\" \")){let W=h.slice(0,U-1);h=h.slice(U+1),a+=` (${W}`;break}}if(a){let N=a.match(bxt);N&&(a=N[1],I=N[2])}return _be(R,h,this._cwd),s&&(R.constructor=!0),n&&(R.evalOrigin=n,R.evalLine=f,R.evalColumn=p,R.evalFile=c&&c.replace(/\\\\/g,\"/\")),S&&(R.native=!0),a&&(R.function=a),I&&a!==I&&(R.method=I),R}};function _be(t,e,r){e&&(e=e.replace(/\\\\/g,\"/\"),e.startsWith(`${r}/`)&&(e=e.slice(r.length+1)),t.file=e)}function Sxt(t){if(t.length===0)return[];let e=t.map(r=>Bxt(r));return new RegExp(`[/\\\\\\\\]node_modules[/\\\\\\\\](?:${e.join(\"|\")})[/\\\\\\\\][^:]+:\\\\d+:\\\\d+`)}var Dxt=new RegExp(\"^(?:\\\\s*at )?(?:(new) )?(?:(.*?) \\\\()?(?:eval at ([^ ]+) \\\\((.+?):(\\\\d+):(\\\\d+)\\\\), )?(?:(.+?):(\\\\d+):(\\\\d+)|(native))(\\\\)?)$\"),bxt=/^(.*?) \\[as (.*?)\\]$/;Hbe.exports=eY});var Gbe=L((Nhr,qbe)=>{\"use strict\";qbe.exports=(t,e)=>t.replace(/^\\t+/gm,r=>\" \".repeat(r.length*(e||2)))});var Ybe=L((Ohr,Wbe)=>{\"use strict\";var Pxt=Gbe(),xxt=(t,e)=>{let r=[],s=t-e,a=t+e;for(let n=s;n<=a;n++)r.push(n);return r};Wbe.exports=(t,e,r)=>{if(typeof t!=\"string\")throw new TypeError(\"Source code is missing.\");if(!e||e<1)throw new TypeError(\"Line number must start from `1`.\");if(t=Pxt(t).split(/\\r?\\n/),!(e>t.length))return r={around:3,...r},xxt(e,r.around).filter(s=>t[s-1]!==void 0).map(s=>({line:s,value:t[s-1]}))}});var jF=L(nf=>{\"use strict\";var kxt=nf&&nf.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r),Object.defineProperty(t,s,{enumerable:!0,get:function(){return e[r]}})}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),Qxt=nf&&nf.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,\"default\",{enumerable:!0,value:e})}:function(t,e){t.default=e}),Txt=nf&&nf.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!==\"default\"&&Object.hasOwnProperty.call(t,r)&&kxt(e,t,r);return Qxt(e,t),e},Rxt=nf&&nf.__rest||function(t,e){var r={};for(var s in t)Object.prototype.hasOwnProperty.call(t,s)&&e.indexOf(s)<0&&(r[s]=t[s]);if(t!=null&&typeof Object.getOwnPropertySymbols==\"function\")for(var a=0,s=Object.getOwnPropertySymbols(t);a<s.length;a++)e.indexOf(s[a])<0&&Object.prototype.propertyIsEnumerable.call(t,s[a])&&(r[s[a]]=t[s[a]]);return r};Object.defineProperty(nf,\"__esModule\",{value:!0});var Vbe=Txt(hn()),tY=Vbe.forwardRef((t,e)=>{var{children:r}=t,s=Rxt(t,[\"children\"]);let a=Object.assign(Object.assign({},s),{marginLeft:s.marginLeft||s.marginX||s.margin||0,marginRight:s.marginRight||s.marginX||s.margin||0,marginTop:s.marginTop||s.marginY||s.margin||0,marginBottom:s.marginBottom||s.marginY||s.margin||0,paddingLeft:s.paddingLeft||s.paddingX||s.padding||0,paddingRight:s.paddingRight||s.paddingX||s.padding||0,paddingTop:s.paddingTop||s.paddingY||s.padding||0,paddingBottom:s.paddingBottom||s.paddingY||s.padding||0});return Vbe.default.createElement(\"ink-box\",{ref:e,style:a},r)});tY.displayName=\"Box\";tY.defaultProps={flexDirection:\"row\",flexGrow:0,flexShrink:1};nf.default=tY});var iY=L(pD=>{\"use strict\";var rY=pD&&pD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(pD,\"__esModule\",{value:!0});var Fxt=rY(hn()),yw=rY(kE()),Kbe=rY(MW()),nY=({color:t,backgroundColor:e,dimColor:r,bold:s,italic:a,underline:n,strikethrough:c,inverse:f,wrap:p,children:h})=>{if(h==null)return null;let E=C=>(r&&(C=yw.default.dim(C)),t&&(C=Kbe.default(C,t,\"foreground\")),e&&(C=Kbe.default(C,e,\"background\")),s&&(C=yw.default.bold(C)),a&&(C=yw.default.italic(C)),n&&(C=yw.default.underline(C)),c&&(C=yw.default.strikethrough(C)),f&&(C=yw.default.inverse(C)),C);return Fxt.default.createElement(\"ink-text\",{style:{flexGrow:0,flexShrink:1,flexDirection:\"row\",textWrap:p},internal_transform:E},h)};nY.displayName=\"Text\";nY.defaultProps={dimColor:!1,bold:!1,italic:!1,underline:!1,strikethrough:!1,wrap:\"wrap\"};pD.default=nY});var Xbe=L(sf=>{\"use strict\";var Nxt=sf&&sf.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r),Object.defineProperty(t,s,{enumerable:!0,get:function(){return e[r]}})}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),Oxt=sf&&sf.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,\"default\",{enumerable:!0,value:e})}:function(t,e){t.default=e}),Lxt=sf&&sf.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!==\"default\"&&Object.hasOwnProperty.call(t,r)&&Nxt(e,t,r);return Oxt(e,t),e},hD=sf&&sf.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(sf,\"__esModule\",{value:!0});var Jbe=Lxt(Ie(\"fs\")),Rs=hD(hn()),zbe=hD(jbe()),Mxt=hD(Ybe()),th=hD(jF()),hA=hD(iY()),Zbe=new zbe.default({cwd:process.cwd(),internals:zbe.default.nodeInternals()}),_xt=({error:t})=>{let e=t.stack?t.stack.split(`\n`).slice(1):void 0,r=e?Zbe.parseLine(e[0]):void 0,s,a=0;if(r?.file&&r?.line&&Jbe.existsSync(r.file)){let n=Jbe.readFileSync(r.file,\"utf8\");if(s=Mxt.default(n,r.line),s)for(let{line:c}of s)a=Math.max(a,String(c).length)}return Rs.default.createElement(th.default,{flexDirection:\"column\",padding:1},Rs.default.createElement(th.default,null,Rs.default.createElement(hA.default,{backgroundColor:\"red\",color:\"white\"},\" \",\"ERROR\",\" \"),Rs.default.createElement(hA.default,null,\" \",t.message)),r&&Rs.default.createElement(th.default,{marginTop:1},Rs.default.createElement(hA.default,{dimColor:!0},r.file,\":\",r.line,\":\",r.column)),r&&s&&Rs.default.createElement(th.default,{marginTop:1,flexDirection:\"column\"},s.map(({line:n,value:c})=>Rs.default.createElement(th.default,{key:n},Rs.default.createElement(th.default,{width:a+1},Rs.default.createElement(hA.default,{dimColor:n!==r.line,backgroundColor:n===r.line?\"red\":void 0,color:n===r.line?\"white\":void 0},String(n).padStart(a,\" \"),\":\")),Rs.default.createElement(hA.default,{key:n,backgroundColor:n===r.line?\"red\":void 0,color:n===r.line?\"white\":void 0},\" \"+c)))),t.stack&&Rs.default.createElement(th.default,{marginTop:1,flexDirection:\"column\"},t.stack.split(`\n`).slice(1).map(n=>{let c=Zbe.parseLine(n);return c?Rs.default.createElement(th.default,{key:n},Rs.default.createElement(hA.default,{dimColor:!0},\"- \"),Rs.default.createElement(hA.default,{dimColor:!0,bold:!0},c.function),Rs.default.createElement(hA.default,{dimColor:!0,color:\"gray\"},\" \",\"(\",c.file,\":\",c.line,\":\",c.column,\")\")):Rs.default.createElement(th.default,{key:n},Rs.default.createElement(hA.default,{dimColor:!0},\"- \"),Rs.default.createElement(hA.default,{dimColor:!0,bold:!0},n))})))};sf.default=_xt});var ePe=L(of=>{\"use strict\";var Uxt=of&&of.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r),Object.defineProperty(t,s,{enumerable:!0,get:function(){return e[r]}})}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),Hxt=of&&of.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,\"default\",{enumerable:!0,value:e})}:function(t,e){t.default=e}),jxt=of&&of.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!==\"default\"&&Object.hasOwnProperty.call(t,r)&&Uxt(e,t,r);return Hxt(e,t),e},Om=of&&of.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(of,\"__esModule\",{value:!0});var Nm=jxt(hn()),$be=Om(oW()),qxt=Om(YW()),Gxt=Om(KW()),Wxt=Om(zW()),Yxt=Om(XW()),Vxt=Om(HF()),Kxt=Om(Xbe()),Jxt=\"\t\",zxt=\"\\x1B[Z\",Zxt=\"\\x1B\",qF=class extends Nm.PureComponent{constructor(){super(...arguments),this.state={isFocusEnabled:!0,activeFocusId:void 0,focusables:[],error:void 0},this.rawModeEnabledCount=0,this.handleSetRawMode=e=>{let{stdin:r}=this.props;if(!this.isRawModeSupported())throw r===process.stdin?new Error(`Raw mode is not supported on the current process.stdin, which Ink uses as input stream by default.\nRead about how to prevent this error on https://github.com/vadimdemedes/ink/#israwmodesupported`):new Error(`Raw mode is not supported on the stdin provided to Ink.\nRead about how to prevent this error on https://github.com/vadimdemedes/ink/#israwmodesupported`);if(r.setEncoding(\"utf8\"),e){this.rawModeEnabledCount===0&&(r.addListener(\"data\",this.handleInput),r.resume(),r.setRawMode(!0)),this.rawModeEnabledCount++;return}--this.rawModeEnabledCount===0&&(r.setRawMode(!1),r.removeListener(\"data\",this.handleInput),r.pause())},this.handleInput=e=>{e===\"\u0003\"&&this.props.exitOnCtrlC&&this.handleExit(),e===Zxt&&this.state.activeFocusId&&this.setState({activeFocusId:void 0}),this.state.isFocusEnabled&&this.state.focusables.length>0&&(e===Jxt&&this.focusNext(),e===zxt&&this.focusPrevious())},this.handleExit=e=>{this.isRawModeSupported()&&this.handleSetRawMode(!1),this.props.onExit(e)},this.enableFocus=()=>{this.setState({isFocusEnabled:!0})},this.disableFocus=()=>{this.setState({isFocusEnabled:!1})},this.focus=e=>{this.setState(r=>r.focusables.some(a=>a?.id===e)?{activeFocusId:e}:r)},this.focusNext=()=>{this.setState(e=>{var r;let s=(r=e.focusables[0])===null||r===void 0?void 0:r.id;return{activeFocusId:this.findNextFocusable(e)||s}})},this.focusPrevious=()=>{this.setState(e=>{var r;let s=(r=e.focusables[e.focusables.length-1])===null||r===void 0?void 0:r.id;return{activeFocusId:this.findPreviousFocusable(e)||s}})},this.addFocusable=(e,{autoFocus:r})=>{this.setState(s=>{let a=s.activeFocusId;return!a&&r&&(a=e),{activeFocusId:a,focusables:[...s.focusables,{id:e,isActive:!0}]}})},this.removeFocusable=e=>{this.setState(r=>({activeFocusId:r.activeFocusId===e?void 0:r.activeFocusId,focusables:r.focusables.filter(s=>s.id!==e)}))},this.activateFocusable=e=>{this.setState(r=>({focusables:r.focusables.map(s=>s.id!==e?s:{id:e,isActive:!0})}))},this.deactivateFocusable=e=>{this.setState(r=>({activeFocusId:r.activeFocusId===e?void 0:r.activeFocusId,focusables:r.focusables.map(s=>s.id!==e?s:{id:e,isActive:!1})}))},this.findNextFocusable=e=>{var r;let s=e.focusables.findIndex(a=>a.id===e.activeFocusId);for(let a=s+1;a<e.focusables.length;a++)if(!((r=e.focusables[a])===null||r===void 0)&&r.isActive)return e.focusables[a].id},this.findPreviousFocusable=e=>{var r;let s=e.focusables.findIndex(a=>a.id===e.activeFocusId);for(let a=s-1;a>=0;a--)if(!((r=e.focusables[a])===null||r===void 0)&&r.isActive)return e.focusables[a].id}}static getDerivedStateFromError(e){return{error:e}}isRawModeSupported(){return this.props.stdin.isTTY}render(){return Nm.default.createElement(qxt.default.Provider,{value:{exit:this.handleExit}},Nm.default.createElement(Gxt.default.Provider,{value:{stdin:this.props.stdin,setRawMode:this.handleSetRawMode,isRawModeSupported:this.isRawModeSupported(),internal_exitOnCtrlC:this.props.exitOnCtrlC}},Nm.default.createElement(Wxt.default.Provider,{value:{stdout:this.props.stdout,write:this.props.writeToStdout}},Nm.default.createElement(Yxt.default.Provider,{value:{stderr:this.props.stderr,write:this.props.writeToStderr}},Nm.default.createElement(Vxt.default.Provider,{value:{activeId:this.state.activeFocusId,add:this.addFocusable,remove:this.removeFocusable,activate:this.activateFocusable,deactivate:this.deactivateFocusable,enableFocus:this.enableFocus,disableFocus:this.disableFocus,focusNext:this.focusNext,focusPrevious:this.focusPrevious,focus:this.focus}},this.state.error?Nm.default.createElement(Kxt.default,{error:this.state.error}):this.props.children)))))}componentDidMount(){$be.default.hide(this.props.stdout)}componentWillUnmount(){$be.default.show(this.props.stdout),this.isRawModeSupported()&&this.handleSetRawMode(!1)}componentDidCatch(e){this.handleExit(e)}};of.default=qF;qF.displayName=\"InternalApp\"});var nPe=L(af=>{\"use strict\";var Xxt=af&&af.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r),Object.defineProperty(t,s,{enumerable:!0,get:function(){return e[r]}})}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),$xt=af&&af.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,\"default\",{enumerable:!0,value:e})}:function(t,e){t.default=e}),ekt=af&&af.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!==\"default\"&&Object.hasOwnProperty.call(t,r)&&Xxt(e,t,r);return $xt(e,t),e},lf=af&&af.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(af,\"__esModule\",{value:!0});var tkt=lf(hn()),tPe=V8(),rkt=lf(fDe()),nkt=lf(tW()),ikt=lf(mDe()),skt=lf(EDe()),sY=lf(fbe()),okt=lf(bbe()),akt=lf(sW()),lkt=lf(Qbe()),ckt=ekt(OW()),ukt=lf(GW()),fkt=lf(ePe()),Ew=process.env.CI===\"false\"?!1:ikt.default,rPe=()=>{},oY=class{constructor(e){this.resolveExitPromise=()=>{},this.rejectExitPromise=()=>{},this.unsubscribeExit=()=>{},this.onRender=()=>{if(this.isUnmounted)return;let{output:r,outputHeight:s,staticOutput:a}=okt.default(this.rootNode,this.options.stdout.columns||80),n=a&&a!==`\n`;if(this.options.debug){n&&(this.fullStaticOutput+=a),this.options.stdout.write(this.fullStaticOutput+r);return}if(Ew){n&&this.options.stdout.write(a),this.lastOutput=r;return}if(n&&(this.fullStaticOutput+=a),s>=this.options.stdout.rows){this.options.stdout.write(nkt.default.clearTerminal+this.fullStaticOutput+r),this.lastOutput=r;return}n&&(this.log.clear(),this.options.stdout.write(a),this.log(r)),!n&&r!==this.lastOutput&&this.throttledLog(r),this.lastOutput=r},skt.default(this),this.options=e,this.rootNode=ckt.createNode(\"ink-root\"),this.rootNode.onRender=e.debug?this.onRender:tPe(this.onRender,32,{leading:!0,trailing:!0}),this.rootNode.onImmediateRender=this.onRender,this.log=rkt.default.create(e.stdout),this.throttledLog=e.debug?this.log:tPe(this.log,void 0,{leading:!0,trailing:!0}),this.isUnmounted=!1,this.lastOutput=\"\",this.fullStaticOutput=\"\",this.container=sY.default.createContainer(this.rootNode,0,!1,null),this.unsubscribeExit=akt.default(this.unmount,{alwaysLast:!1}),e.patchConsole&&this.patchConsole(),Ew||(e.stdout.on(\"resize\",this.onRender),this.unsubscribeResize=()=>{e.stdout.off(\"resize\",this.onRender)})}render(e){let r=tkt.default.createElement(fkt.default,{stdin:this.options.stdin,stdout:this.options.stdout,stderr:this.options.stderr,writeToStdout:this.writeToStdout,writeToStderr:this.writeToStderr,exitOnCtrlC:this.options.exitOnCtrlC,onExit:this.unmount},e);sY.default.updateContainer(r,this.container,null,rPe)}writeToStdout(e){if(!this.isUnmounted){if(this.options.debug){this.options.stdout.write(e+this.fullStaticOutput+this.lastOutput);return}if(Ew){this.options.stdout.write(e);return}this.log.clear(),this.options.stdout.write(e),this.log(this.lastOutput)}}writeToStderr(e){if(!this.isUnmounted){if(this.options.debug){this.options.stderr.write(e),this.options.stdout.write(this.fullStaticOutput+this.lastOutput);return}if(Ew){this.options.stderr.write(e);return}this.log.clear(),this.options.stderr.write(e),this.log(this.lastOutput)}}unmount(e){this.isUnmounted||(this.onRender(),this.unsubscribeExit(),typeof this.restoreConsole==\"function\"&&this.restoreConsole(),typeof this.unsubscribeResize==\"function\"&&this.unsubscribeResize(),Ew?this.options.stdout.write(this.lastOutput+`\n`):this.options.debug||this.log.done(),this.isUnmounted=!0,sY.default.updateContainer(null,this.container,null,rPe),ukt.default.delete(this.options.stdout),e instanceof Error?this.rejectExitPromise(e):this.resolveExitPromise())}waitUntilExit(){return this.exitPromise||(this.exitPromise=new Promise((e,r)=>{this.resolveExitPromise=e,this.rejectExitPromise=r})),this.exitPromise}clear(){!Ew&&!this.options.debug&&this.log.clear()}patchConsole(){this.options.debug||(this.restoreConsole=lkt.default((e,r)=>{e===\"stdout\"&&this.writeToStdout(r),e===\"stderr\"&&(r.startsWith(\"The above error occurred\")||this.writeToStderr(r))}))}};af.default=oY});var sPe=L(gD=>{\"use strict\";var iPe=gD&&gD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(gD,\"__esModule\",{value:!0});var Akt=iPe(nPe()),GF=iPe(GW()),pkt=Ie(\"stream\"),hkt=(t,e)=>{let r=Object.assign({stdout:process.stdout,stdin:process.stdin,stderr:process.stderr,debug:!1,exitOnCtrlC:!0,patchConsole:!0},gkt(e)),s=dkt(r.stdout,()=>new Akt.default(r));return s.render(t),{rerender:s.render,unmount:()=>s.unmount(),waitUntilExit:s.waitUntilExit,cleanup:()=>GF.default.delete(r.stdout),clear:s.clear}};gD.default=hkt;var gkt=(t={})=>t instanceof pkt.Stream?{stdout:t,stdin:process.stdin}:t,dkt=(t,e)=>{let r;return GF.default.has(t)?r=GF.default.get(t):(r=e(),GF.default.set(t,r)),r}});var aPe=L(rh=>{\"use strict\";var mkt=rh&&rh.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r),Object.defineProperty(t,s,{enumerable:!0,get:function(){return e[r]}})}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),ykt=rh&&rh.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,\"default\",{enumerable:!0,value:e})}:function(t,e){t.default=e}),Ekt=rh&&rh.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!==\"default\"&&Object.hasOwnProperty.call(t,r)&&mkt(e,t,r);return ykt(e,t),e};Object.defineProperty(rh,\"__esModule\",{value:!0});var dD=Ekt(hn()),oPe=t=>{let{items:e,children:r,style:s}=t,[a,n]=dD.useState(0),c=dD.useMemo(()=>e.slice(a),[e,a]);dD.useLayoutEffect(()=>{n(e.length)},[e.length]);let f=c.map((h,E)=>r(h,a+E)),p=dD.useMemo(()=>Object.assign({position:\"absolute\",flexDirection:\"column\"},s),[s]);return dD.default.createElement(\"ink-box\",{internal_static:!0,style:p},f)};oPe.displayName=\"Static\";rh.default=oPe});var cPe=L(mD=>{\"use strict\";var Ikt=mD&&mD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(mD,\"__esModule\",{value:!0});var Ckt=Ikt(hn()),lPe=({children:t,transform:e})=>t==null?null:Ckt.default.createElement(\"ink-text\",{style:{flexGrow:0,flexShrink:1,flexDirection:\"row\"},internal_transform:e},t);lPe.displayName=\"Transform\";mD.default=lPe});var fPe=L(yD=>{\"use strict\";var wkt=yD&&yD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(yD,\"__esModule\",{value:!0});var Bkt=wkt(hn()),uPe=({count:t=1})=>Bkt.default.createElement(\"ink-text\",null,`\n`.repeat(t));uPe.displayName=\"Newline\";yD.default=uPe});var hPe=L(ED=>{\"use strict\";var APe=ED&&ED.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(ED,\"__esModule\",{value:!0});var vkt=APe(hn()),Skt=APe(jF()),pPe=()=>vkt.default.createElement(Skt.default,{flexGrow:1});pPe.displayName=\"Spacer\";ED.default=pPe});var WF=L(ID=>{\"use strict\";var Dkt=ID&&ID.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(ID,\"__esModule\",{value:!0});var bkt=hn(),Pkt=Dkt(KW()),xkt=()=>bkt.useContext(Pkt.default);ID.default=xkt});var dPe=L(CD=>{\"use strict\";var kkt=CD&&CD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(CD,\"__esModule\",{value:!0});var gPe=hn(),Qkt=kkt(WF()),Tkt=(t,e={})=>{let{stdin:r,setRawMode:s,internal_exitOnCtrlC:a}=Qkt.default();gPe.useEffect(()=>{if(e.isActive!==!1)return s(!0),()=>{s(!1)}},[e.isActive,s]),gPe.useEffect(()=>{if(e.isActive===!1)return;let n=c=>{let f=String(c),p={upArrow:f===\"\\x1B[A\",downArrow:f===\"\\x1B[B\",leftArrow:f===\"\\x1B[D\",rightArrow:f===\"\\x1B[C\",pageDown:f===\"\\x1B[6~\",pageUp:f===\"\\x1B[5~\",return:f===\"\\r\",escape:f===\"\\x1B\",ctrl:!1,shift:!1,tab:f===\"\t\"||f===\"\\x1B[Z\",backspace:f===\"\\b\",delete:f===\"\\x7F\"||f===\"\\x1B[3~\",meta:!1};f<=\"\u001a\"&&!p.return&&(f=String.fromCharCode(f.charCodeAt(0)+97-1),p.ctrl=!0),f.startsWith(\"\\x1B\")&&(f=f.slice(1),p.meta=!0);let h=f>=\"A\"&&f<=\"Z\",E=f>=\"\\u0410\"&&f<=\"\\u042F\";f.length===1&&(h||E)&&(p.shift=!0),p.tab&&f===\"[Z\"&&(p.shift=!0),(p.tab||p.backspace||p.delete)&&(f=\"\"),(!(f===\"c\"&&p.ctrl)||!a)&&t(f,p)};return r?.on(\"data\",n),()=>{r?.off(\"data\",n)}},[e.isActive,r,a,t])};CD.default=Tkt});var mPe=L(wD=>{\"use strict\";var Rkt=wD&&wD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(wD,\"__esModule\",{value:!0});var Fkt=hn(),Nkt=Rkt(YW()),Okt=()=>Fkt.useContext(Nkt.default);wD.default=Okt});var yPe=L(BD=>{\"use strict\";var Lkt=BD&&BD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(BD,\"__esModule\",{value:!0});var Mkt=hn(),_kt=Lkt(zW()),Ukt=()=>Mkt.useContext(_kt.default);BD.default=Ukt});var EPe=L(vD=>{\"use strict\";var Hkt=vD&&vD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(vD,\"__esModule\",{value:!0});var jkt=hn(),qkt=Hkt(XW()),Gkt=()=>jkt.useContext(qkt.default);vD.default=Gkt});var CPe=L(DD=>{\"use strict\";var IPe=DD&&DD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(DD,\"__esModule\",{value:!0});var SD=hn(),Wkt=IPe(HF()),Ykt=IPe(WF()),Vkt=({isActive:t=!0,autoFocus:e=!1,id:r}={})=>{let{isRawModeSupported:s,setRawMode:a}=Ykt.default(),{activeId:n,add:c,remove:f,activate:p,deactivate:h,focus:E}=SD.useContext(Wkt.default),C=SD.useMemo(()=>r??Math.random().toString().slice(2,7),[r]);return SD.useEffect(()=>(c(C,{autoFocus:e}),()=>{f(C)}),[C,e]),SD.useEffect(()=>{t?p(C):h(C)},[t,C]),SD.useEffect(()=>{if(!(!s||!t))return a(!0),()=>{a(!1)}},[t]),{isFocused:!!C&&n===C,focus:E}};DD.default=Vkt});var wPe=L(bD=>{\"use strict\";var Kkt=bD&&bD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(bD,\"__esModule\",{value:!0});var Jkt=hn(),zkt=Kkt(HF()),Zkt=()=>{let t=Jkt.useContext(zkt.default);return{enableFocus:t.enableFocus,disableFocus:t.disableFocus,focusNext:t.focusNext,focusPrevious:t.focusPrevious,focus:t.focus}};bD.default=Zkt});var BPe=L(aY=>{\"use strict\";Object.defineProperty(aY,\"__esModule\",{value:!0});aY.default=t=>{var e,r,s,a;return{width:(r=(e=t.yogaNode)===null||e===void 0?void 0:e.getComputedWidth())!==null&&r!==void 0?r:0,height:(a=(s=t.yogaNode)===null||s===void 0?void 0:s.getComputedHeight())!==null&&a!==void 0?a:0}}});var Vc=L(Eo=>{\"use strict\";Object.defineProperty(Eo,\"__esModule\",{value:!0});var Xkt=sPe();Object.defineProperty(Eo,\"render\",{enumerable:!0,get:function(){return Xkt.default}});var $kt=jF();Object.defineProperty(Eo,\"Box\",{enumerable:!0,get:function(){return $kt.default}});var eQt=iY();Object.defineProperty(Eo,\"Text\",{enumerable:!0,get:function(){return eQt.default}});var tQt=aPe();Object.defineProperty(Eo,\"Static\",{enumerable:!0,get:function(){return tQt.default}});var rQt=cPe();Object.defineProperty(Eo,\"Transform\",{enumerable:!0,get:function(){return rQt.default}});var nQt=fPe();Object.defineProperty(Eo,\"Newline\",{enumerable:!0,get:function(){return nQt.default}});var iQt=hPe();Object.defineProperty(Eo,\"Spacer\",{enumerable:!0,get:function(){return iQt.default}});var sQt=dPe();Object.defineProperty(Eo,\"useInput\",{enumerable:!0,get:function(){return sQt.default}});var oQt=mPe();Object.defineProperty(Eo,\"useApp\",{enumerable:!0,get:function(){return oQt.default}});var aQt=WF();Object.defineProperty(Eo,\"useStdin\",{enumerable:!0,get:function(){return aQt.default}});var lQt=yPe();Object.defineProperty(Eo,\"useStdout\",{enumerable:!0,get:function(){return lQt.default}});var cQt=EPe();Object.defineProperty(Eo,\"useStderr\",{enumerable:!0,get:function(){return cQt.default}});var uQt=CPe();Object.defineProperty(Eo,\"useFocus\",{enumerable:!0,get:function(){return uQt.default}});var fQt=wPe();Object.defineProperty(Eo,\"useFocusManager\",{enumerable:!0,get:function(){return fQt.default}});var AQt=BPe();Object.defineProperty(Eo,\"measureElement\",{enumerable:!0,get:function(){return AQt.default}})});var cY={};Vt(cY,{Gem:()=>lY});var vPe,Lm,lY,YF=Ct(()=>{vPe=et(Vc()),Lm=et(hn()),lY=(0,Lm.memo)(({active:t})=>{let e=(0,Lm.useMemo)(()=>t?\"\\u25C9\":\"\\u25EF\",[t]),r=(0,Lm.useMemo)(()=>t?\"green\":\"yellow\",[t]);return Lm.default.createElement(vPe.Text,{color:r},e)})});var DPe={};Vt(DPe,{useKeypress:()=>Mm});function Mm({active:t},e,r){let{stdin:s}=(0,SPe.useStdin)(),a=(0,VF.useCallback)((n,c)=>e(n,c),r);(0,VF.useEffect)(()=>{if(!(!t||!s))return s.on(\"keypress\",a),()=>{s.off(\"keypress\",a)}},[t,a,s])}var SPe,VF,PD=Ct(()=>{SPe=et(Vc()),VF=et(hn())});var PPe={};Vt(PPe,{FocusRequest:()=>bPe,useFocusRequest:()=>uY});var bPe,uY,fY=Ct(()=>{PD();bPe=(r=>(r.BEFORE=\"before\",r.AFTER=\"after\",r))(bPe||{}),uY=function({active:t},e,r){Mm({active:t},(s,a)=>{a.name===\"tab\"&&(a.shift?e(\"before\"):e(\"after\"))},r)}});var xPe={};Vt(xPe,{useListInput:()=>xD});var xD,KF=Ct(()=>{PD();xD=function(t,e,{active:r,minus:s,plus:a,set:n,loop:c=!0}){Mm({active:r},(f,p)=>{let h=e.indexOf(t);switch(p.name){case s:{let E=h-1;if(c){n(e[(e.length+E)%e.length]);return}if(E<0)return;n(e[E])}break;case a:{let E=h+1;if(c){n(e[E%e.length]);return}if(E>=e.length)return;n(e[E])}break}},[e,t,a,n,c])}});var JF={};Vt(JF,{ScrollableItems:()=>pQt});var $0,ml,pQt,zF=Ct(()=>{$0=et(Vc()),ml=et(hn());fY();KF();pQt=({active:t=!0,children:e=[],radius:r=10,size:s=1,loop:a=!0,onFocusRequest:n,willReachEnd:c})=>{let f=N=>{if(N.key===null)throw new Error(\"Expected all children to have a key\");return N.key},p=ml.default.Children.map(e,N=>f(N)),h=p[0],[E,C]=(0,ml.useState)(h),S=p.indexOf(E);(0,ml.useEffect)(()=>{p.includes(E)||C(h)},[e]),(0,ml.useEffect)(()=>{c&&S>=p.length-2&&c()},[S]),uY({active:t&&!!n},N=>{n?.(N)},[n]),xD(E,p,{active:t,minus:\"up\",plus:\"down\",set:C,loop:a});let P=S-r,I=S+r;I>p.length&&(P-=I-p.length,I=p.length),P<0&&(I+=-P,P=0),I>=p.length&&(I=p.length-1);let R=[];for(let N=P;N<=I;++N){let U=p[N],W=t&&U===E;R.push(ml.default.createElement($0.Box,{key:U,height:s},ml.default.createElement($0.Box,{marginLeft:1,marginRight:1},ml.default.createElement($0.Text,null,W?ml.default.createElement($0.Text,{color:\"cyan\",bold:!0},\">\"):\" \")),ml.default.createElement($0.Box,null,ml.default.cloneElement(e[N],{active:W}))))}return ml.default.createElement($0.Box,{flexDirection:\"column\",width:\"100%\"},R)}});var kPe,nh,QPe,AY,TPe,pY=Ct(()=>{kPe=et(Vc()),nh=et(hn()),QPe=Ie(\"readline\"),AY=nh.default.createContext(null),TPe=({children:t})=>{let{stdin:e,setRawMode:r}=(0,kPe.useStdin)();(0,nh.useEffect)(()=>{r&&r(!0),e&&(0,QPe.emitKeypressEvents)(e)},[e,r]);let[s,a]=(0,nh.useState)(new Map),n=(0,nh.useMemo)(()=>({getAll:()=>s,get:c=>s.get(c),set:(c,f)=>a(new Map([...s,[c,f]]))}),[s,a]);return nh.default.createElement(AY.Provider,{value:n,children:t})}});var hY={};Vt(hY,{useMinistore:()=>hQt});function hQt(t,e){let r=(0,ZF.useContext)(AY);if(r===null)throw new Error(\"Expected this hook to run with a ministore context attached\");if(typeof t>\"u\")return r.getAll();let s=(0,ZF.useCallback)(n=>{r.set(t,n)},[t,r.set]),a=r.get(t);return typeof a>\"u\"&&(a=e),[a,s]}var ZF,gY=Ct(()=>{ZF=et(hn());pY()});var $F={};Vt($F,{renderForm:()=>gQt});async function gQt(t,e,{stdin:r,stdout:s,stderr:a}){let n,c=p=>{let{exit:h}=(0,XF.useApp)();Mm({active:!0},(E,C)=>{C.name===\"return\"&&(n=p,h())},[h,p])},{waitUntilExit:f}=(0,XF.render)(dY.default.createElement(TPe,null,dY.default.createElement(t,{...e,useSubmit:c})),{stdin:r,stdout:s,stderr:a});return await f(),n}var XF,dY,eN=Ct(()=>{XF=et(Vc()),dY=et(hn());pY();PD()});var OPe=L(kD=>{\"use strict\";Object.defineProperty(kD,\"__esModule\",{value:!0});kD.UncontrolledTextInput=void 0;var FPe=hn(),mY=hn(),RPe=Vc(),_m=kE(),NPe=({value:t,placeholder:e=\"\",focus:r=!0,mask:s,highlightPastedText:a=!1,showCursor:n=!0,onChange:c,onSubmit:f})=>{let[{cursorOffset:p,cursorWidth:h},E]=mY.useState({cursorOffset:(t||\"\").length,cursorWidth:0});mY.useEffect(()=>{E(R=>{if(!r||!n)return R;let N=t||\"\";return R.cursorOffset>N.length-1?{cursorOffset:N.length,cursorWidth:0}:R})},[t,r,n]);let C=a?h:0,S=s?s.repeat(t.length):t,P=S,I=e?_m.grey(e):void 0;if(n&&r){I=e.length>0?_m.inverse(e[0])+_m.grey(e.slice(1)):_m.inverse(\" \"),P=S.length>0?\"\":_m.inverse(\" \");let R=0;for(let N of S)R>=p-C&&R<=p?P+=_m.inverse(N):P+=N,R++;S.length>0&&p===S.length&&(P+=_m.inverse(\" \"))}return RPe.useInput((R,N)=>{if(N.upArrow||N.downArrow||N.ctrl&&R===\"c\"||N.tab||N.shift&&N.tab)return;if(N.return){f&&f(t);return}let U=p,W=t,te=0;N.leftArrow?n&&U--:N.rightArrow?n&&U++:N.backspace||N.delete?p>0&&(W=t.slice(0,p-1)+t.slice(p,t.length),U--):(W=t.slice(0,p)+R+t.slice(p,t.length),U+=R.length,R.length>1&&(te=R.length)),p<0&&(U=0),p>t.length&&(U=t.length),E({cursorOffset:U,cursorWidth:te}),W!==t&&c(W)},{isActive:r}),FPe.createElement(RPe.Text,null,e?S.length>0?P:I:P)};kD.default=NPe;kD.UncontrolledTextInput=({initialValue:t=\"\",...e})=>{let[r,s]=mY.useState(t);return FPe.createElement(NPe,Object.assign({},e,{value:r,onChange:s}))}});var _Pe={};Vt(_Pe,{Pad:()=>yY});var LPe,MPe,yY,EY=Ct(()=>{LPe=et(Vc()),MPe=et(hn()),yY=({length:t,active:e})=>{if(t===0)return null;let r=t>1?` ${\"-\".repeat(t-1)}`:\" \";return MPe.default.createElement(LPe.Text,{dimColor:!e},r)}});var UPe={};Vt(UPe,{ItemOptions:()=>dQt});var TD,eg,dQt,HPe=Ct(()=>{TD=et(Vc()),eg=et(hn());KF();YF();EY();dQt=function({active:t,skewer:e,options:r,value:s,onChange:a,sizes:n=[]}){let c=r.filter(({label:p})=>!!p).map(({value:p})=>p),f=r.findIndex(p=>p.value===s&&p.label!=\"\");return xD(s,c,{active:t,minus:\"left\",plus:\"right\",set:a}),eg.default.createElement(eg.default.Fragment,null,r.map(({label:p},h)=>{let E=h===f,C=n[h]-1||0,S=p.replace(/[\\u001b\\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,\"\"),P=Math.max(0,C-S.length-2);return p?eg.default.createElement(TD.Box,{key:p,width:C,marginLeft:1},eg.default.createElement(TD.Text,{wrap:\"truncate\"},eg.default.createElement(lY,{active:E}),\" \",p),e?eg.default.createElement(yY,{active:t,length:P}):null):eg.default.createElement(TD.Box,{key:`spacer-${h}`,width:C,marginLeft:1})}))}});var rxe=L((Ugr,txe)=>{var kY;txe.exports=()=>(typeof kY>\"u\"&&(kY=Ie(\"zlib\").brotliDecompressSync(Buffer.from(\"Wx6iVsM8y37oTpDqz9ttuZc9II7bU8Dm0eSoiEX5X+cI6oZJXQfiuc4xndBuXaAQQxqqqnlJZYxtR/YfQKWsqrIlDzhSaK0b0Sl4sGIivE3xwFR3yFnY7YHRO/xw5NmsXhLGMmIJnQ7RQOSgLL9ts5fdaYhcxoWHF7dahKcbL7xdpZna+sOZHQ3C9aU56oudzh85R5BU6q3+VceftEQSBD0HUBi3vlcAQxQJJXS6NubAera9xHt4WLyEj/DTf2xqnfHl9KwwY4nyvz1tK1taQwTRw0R2J01oLV0sv0ZNGpLrcMPW3wSK8dBkiX/hvpvN7J/Pa/EVRKpkyjCk+Hp9OUWGhcRbQBPgmnfO//bO/uubdIUpwz5xJof7RDxrN6HZUguxathf+nrP5eR02lnTdac+CEfPIPEQONnqWLfllz+tvn61uxegTmZDxpeYFBgfTArYbsME6aHr7jHYVfjZ8hXR0aFbef0186b7kBPUWMxO69JY0mkI2VZfSVctgoJx8qX7Vqpmr6ainSnTsfwYuhhPxJq81wGrwRFj82d0+nuz//58jdJ7jNXB6aX3NFIRgdBmnyiQq1SEbAqzxF0WECarcjoIWVuN5tNi+TBQMBscGC0P+rXm1/E6v5mwHsFaHk5AMy03wxY/9YTk6vvpdFwTbscrqwR29Td96Z4dLDi+AISU7/zj4f0CpCXvONrV2ktiQAFDzA0MiOJC2rpUgP/oXOPggHqNG99PQvnC4QcJwmaNBeV61L+1145XwNApR0mrG2akK1l51Fu/En0kzKoo+mGx+cdDD6bo99vjm8kkG2DBbIhIb0jrbIiIatsl+vGNreNhD1LZrh3ffAYcFOqBVHQzXD7kbpi4+6WB7eZoCBPwA+xHP5r/9Pmxu3uJmjzzeaq6uikG0AJ7lPmbMNeCoI43TILGjxpq/fGw+3+wrezIx/eqq6EQYDcKSuSbLE+qiTLBMkqQBh6xdP3x8NsAW49PsiYR3Ww/UmXh7clfY8DSTev96F0FZpBgFDz//6nqDwdJfunT/Q5B4UIVqrZnNmVfyF5k0rny/f/v/dSqqtqBoFwbYybT9hQAqr0dDHvN45979t3Ct2I4SAgArAKNVpKSciUpprH3mPu+DSgiQKkBSJWLpEqV3oza+uGoe9yDWc9GEWCbcmbW/39fqtX2vv8DgQAhUSDFtEHLmUk7exDTXZOrTm87AFC2phxm9TgvNuZ797539N97P9LxfwTKET8ClYgfwDEjQJ5kRAB9CID8PwDQAYhSg5IyG6TtPJTT2U3JzjrMcRJB6hxTlM8xRakGydmD7R7dw7hV1jBOq6pejWdfw9zjsKp973qz7/Wid71c1mrZi2X7/7/8d5bSJNKGeIpHCTJz9+zUqlkY/07d+X+Rge6aUfLOj3lx4D+/5qe99933zpvQZNum6ue3LFSFuW8yf4lUSZlN5v5ZCBQQJHCShfwiuOoq9FXASpDzlbJywbCTVyi8DXFpDl9lsMJzLsv+bIOILqZ/M0P3IBmn2n6SBpZgqcT/fxwsrXPhq74JKKSAEvCEaEV8zVotS7XhUZRHIoxh0yF8v1qJRX1nyWyPu/J3y3SFaNvAGXgquv2y/gRu1v+k28JesS/drYDHCIQgSQiWoFZaVALBPEBXngywzf4PFdg5ef5cgoGESoo2UUYhm5E4tPe3i977UUST2xXhY/MH7K/f9j/Hx84wiyzfr40FNgRURIy6pbfC25T9sv8eOHVhExcSQZ4KxEy8+O/6VmBhIVAIBAYKgcBAIFD4Agez0/9/0Jx38/2f4QyGmODBBCWYoMEQDR40GKpSUTQIKqgGF+5wofn8TF1f9Ne70uHfZ0BAQIOABg0CAqJTgHUKMAgwsJ4MDOpJBrZ08k8q/wNyd9f2gQcCAgwCDAIs1cCqDKzKwMBSFxgYLFiwYFQNiJ/bf/98p8+1z/1atNiixRZbIBAIMkEgSpBMCTJBIJgEUS8pUaLEErxPjZ0N/mZ+xd5RmXiDBygVtROd2c9/hKMk2faG0K3vD1fRE5Cra4OeAqQhJIQSaldpXUAsbd1X/u8Jmcy4OoSb9f/oFaixfWK7BQqFJEhCwAuFIMWkpYhIEqxU//f4PKlHlH8VSgf8q0a+G9cecRRLrDewqDXIr1HkZZwHWG83yHqVyUtb5cXAGmyCEiA/fKbWva8f37WBtBDNhd5ukA/tzc4CosZIjfHUL+E6vhZeA6tt7cdwv3VOu6Ad6hZsEj/dcyf8Koc+Ii/1E0m93QTEr8X7TPx6v0Hw4hgT0NsiBzi/Ojr+aAjNlK5T+VHQGly0ERkOwSh/vRliHz3BItngE8RENKNdGrxiiL5hBGi5rcwT0QlJFatE4bIbzXe0McICrXV/xde1yXPZyaRUs7gU+MpkzOHxhxVGu+jvWUOSpCNhdEBczkhaTU/m9qyaFOTubSWcVZ3SaKxWvsT9oA762PXd6Fpe/O8eGFtrbQv9H5jUkP9Xv4L9yt3GEuZDICzdqhhX6bybxUCiJdKJVt+IvaaA8pBXb9aP2spgL/w4jR8UmO3+smtT0A+0hFLC9wvrMrl8Dd1ndAnhiyfRVSXrzN4LHh9xAHkaO4/8Q8IS00EE3nPzHWfECG3QIQwbjoe0k5iOovmQMBsoifhgSMQWjU0QhkWqELzEYEh0etfEGCG/mT41Cqk+uWKIGR9a3uepyL+fhJbtKzj//RQZtS/ycolxB8RZCGjrzeaK78ojq5ky3j7HIZ76kpqV7qp3f9rsQ9ORRWkEdji+zm/K1QMX8IfIoXv44nD5BcFG3zGUklDKnUTbINPf0KuNprc9I8vRhHEWn6Mevc/kMldwancCJglrytG4wtx+QVKlcdFagd+ifV4h9mkojgAHI0Yutc+QzeZ72wAfQiWJPN6thWo1Fq51zEZ/abkgV1BxRLa/Y3VIyexOxU+B5OHvrXoqIFLo5R+9AjP55vc1dLSvIYxt8fPVD5Bt+aDn/+QUR4BSWphE0j5mFv7eCgkKlCQiFzPG3iehYMSoKF8d5bOx98JIJgq+4cvSv84ye+Uk6+9RW84h4skdf+pKOunpUvu6Yp6K/R+ezL63icRaPpzoIuS9jchG4DXTGeMtW4/ttHAWqEf/yIAM/8oyJoBvylHmB8Uu+9NTMWWMqf18uFrGXgE+VdvznXGVl/+bjv0G2xs0ZSjCu6SlnfQxnoCfh6xvafwQB4N+nJffQKB+vActlnzfHzFclcrXdZS16BjvPr8k4yr9pZZKeUCaO6y7o+zV9OhVKIGzqAQH7M4o+yb6k1JJ3BTl3Poiweyk450Mrjd624ba95IcB8lQRpsMl96/quD8W5Jx/swK6wG2+3Zeyhwu278j8jLzuv6O59ocMbP8JgciFip943CXFsBLWEIYhUW4wC1sb9pYS4kZ3UJ+C/kt5p+dPyctkvzTMs1dWCgvjamuDCDjTghl2ykbWi6TXXkLBmtQfwVxHyb9qAdwCenDxP8EHMA8HzD5+QBap16HHGr5tnstysVebx275eK9qqnLhKZemkf+faykRK0Ihgj/SC/y2JWYYzK4EKN/QFg5m4Le7WJ5Xj50NzPuiBbJpzxltmqmElpC2skoBl+8l6P5H2GtjcVMK4hohyPqSfJKkQMVW0W2u4is8mYeTzug8pSgrTFMRh/m5N4NotSL5IqK6dEWl6rw/KlpSBFVFMgstbby2bKSgMQ1ZcksZBcVYFw7Xoxb0oO3b7BJsD1Sednx5u3Lbm13GGPF1KCdSOkr6Qkzo5Qf/vMDzqrHIedVyZQxwnl9a5toMJGYfJEAbvcRQV8FQdxKJ9Z2T8O4kQ6vtyyesmVPstmSUH5MJ/o7OiWZtrS/QzGINI/IOm4Q8DDSxKI2nQSJ1U3U9vSkxvtdhNCpgwbu5PHRyQNAMA+wKyeCm32Ibd9JyMTIU9OeXynIz3k8q4ovMxbXTxG9nkZWst6eJoOtvXVdLIqO31LBlOrPyitw967ni5roPG92lTTvhNSJf4P4cuMN2pfZspUiBdxNUzHLj5y6qB/2ajpZ+ZP4VPZN+hCzacWYtNdfJF3VlDd78njhx36F7SVFBKm/94aeX/xfskxdBrotrbw6fNiCJaa/g3lksHQrS9/7KyTxkPKqEXv4KNyv5K5cwHthJI7K8vqeKVh3OYro8ESEJz+5TP3eExO6OWaHPEzjjd+Pfg/kqyCifid6BVdaUHgmVFDqT5VHoN47yMsrayq2foT9WaS1f2o1iQPeNdVyjB14t8OrllHUluJ0teDqrYTZFZm6HNQs2AyUei6/8sXt/kpheFe2/0reuhKFxWFRl3zaygGdsepcsjpRP+Fe8QGPnaF1bqISrSPlp4iK0Z6SAJzOQNtxFQb+EoL3EdEv/zNxzBt3scaovgp7S2NsdlRyxyrncjCF9PLQNFsjyZZe5cheSHRin3BouoVTLa4LJR0M+iSUaqh6P9hdewKtOKBjWvbjwcQcllujNcbVX//noV1zBJTM3s+F2McT517FoFbS+tTlS1JQI+OlflmRoIgltiF+3xHaICWpV84rYNfAwYWfU1BDYoyy4vMvy7qaggqZF4FtZQCSxmMMU6n4TVnOoeCKSlW0CaZoihUm0U3mhgL54Z+9YGwHN5raP+eBfJb9T15L60ZP26O7x2tG6sa4f0y/cmf4X9D8/j3lJWlWUyL16zlFF9kssyROJtTZPtVS31cFLDk2dj/+EnkPdwF/toVCQC1vwGL0ZGOKUbXAxxUOhe9UyDMUbHww4VKR2dxXMESDAKmsUCzp7F5h/ToMHVE/7S/A9K/Rb45BhY3HeVOvXRwahS2GUK83vRIT9JZmHhoBvIcW76djG2iljbkX9ZhD2jmIwHIURIz5CgqGGH01FbbPsyFVDcSniN1DJ1K4h1PUdbLNwaaLRYtnWz0sQ8y24JjrBbyPfO4Iwyq6S8Y/ksLC+qz99DNA8iyCJi4C3LsVz5fSubnZn+0pnbquH1uknY4eJivf7DSfl6JIVgSIImtIb1oJFKO2Lip6U+lEZ6ZMmnUG3zcGvX3edi4wrm/unSQdrkmRp/gFt4VwFJb/vJit59ztRLV3anmIDv1sXRcMYTyMXesZiomInUwGW2VX3GIXW3Zp636GGfjIkFTUlti9kHlvwBhdYBlHeg7G4PSwMjGzKw+3o5Y5sSdebUUmc0qwSMsaye19pXS34jpdU4KxVdnVord5RS6Q2Cm9HxTnjeWRQqpkR8vyMWLiFu+QyfzlqM+x+fz8nWyyLvrw/Uc/dlh8UyowXHd0xFZ6rC5uLkd/JHk/mV/k3lLp+ZDl6DddL6acmWlSs02APGrzqCIQexVzhQL7UiLOMzc/REYJCInpVNOsPboHnhYZmE2+yJZnSgZXaveqFjpFdwSU5/Jk9vjIUNaAJdbBABFpKitglNZT2NVltZJWqNp9w69Y3ugmnrEMKHCQZbRPQ8KZ1XrxWsWkM0ir2FD4SeLPPHRlujUVVW/LJ6ramdGe4OCTrX6+MHY2iEQl1fMmYmfiBhFtdCy1ZVc8b/T2Jfv4LppnO1iDd/wnvG3gMSb9aJ6QocuyTC0+NbCGt3A4i/EI2fW8zUmwclImssYsMFP0iSDLcuTlHzbYzSLSF7NohMIVU17BTIMZuJV/BgGFYUFpQjGRm1Y3cJxWaCtOtxfoWInTYU2tTYq6s3VqYSQJ9tRGx+5Yrgp5/BcnTOI9cZmLWpd57+UiuUJd58UbMnevtP2dOBJn1CWmXYxE7KA7Ml2ADIWQQI+RUV1vQoJqbJrEaeUnIhT2tWTGFHw+rlhTqnkMq/6TQmq+ViMg6CCUXmuKMiCk7GZpg8gZwloCUe1jW2EENhXtcq1QdgIN09RWJa7ZRmWInrcB5CwLIQilwfXswDMKSZ5ODv/vazs9+alib8qOJxa1MsrdY9kuwVSvT5Og1r+jNdBGEfEaMg1Nau4HLTiMxnd2pAMopIzdHelTJBPgxG5YqHrvF8jJ1Vosbo/orfJsB1AikDra51HOTEWuZO3aVGzAgzvxuWGZjLayta7CbBE2G1DQOEzOIqXgoeysfN3JTVujkzMZPbl1Gwb8SFF+g/IrX8YEnnNFh9ZAWxWt7ag4RJSGBzDeKLlFBAW/zPaGjubJuU77JFeg1R9hZoBkhkiaTMZd8m277Bm8667+Gw2cD5/8RRPei8999fGxLrFjJ5P7dXzqo+xkD6y4Y2eqcjKh2GWSLwRK34eG+/l6Y3bcAFoOVind+iYaD8sxprepmGEmK6+dpjwXksQqAVhZeBsnPbZp2LyMhxY/TqbKOpiP7fy4ddFygZTQ6s7ePKyN572xEkNh8SWTJ3rnERxUJsVca0FeJNzUUbvHYnEHvbvlJWELivnZLGZI2zENj5ziQAbo0rsewVn0u4huW/WbtXtG4pj1MeAOE3wHwEnpgbxQ8XW5BiTA7TDRv1oxAFgfc1XSr8drtXjrwToIO9HYtFZduXLaMC9jsb1VYBlVrJ//wrQlvuyuowSmEkESBjkA8zscLOUNJ3zsQl4yOA/7cAwz19YxkkH7qEvWIv3yi3hjbeIOTGMh0L6wZtZuzLYb6v/37SNDW0eiYzRst4meHITeTNFPLCdePw67pqhgc+S2vC7DuL99ri1kSwmdSgzEtUp0CjUgLp4XNdzWraF7TcuqZ4bEbqjbY+EyzVLRP9KwXFWmoBdtqEWZ9FW6sEatEBTR8qXrh8BGGOaoJQ1LNHbpui1zepTiw7eGbdBault5lh9bAFPI2NjjkRFhwnFjF7VFvcVpNc0kMLNa5ToGhQMbKdiJJ4riKNsge0PZQ5ZJd6vL2u2Yjt9/KuQybQrlWR4RPQ0BD4PrBUvbtvTZfruOfTwfpmeev+Mv+Q5nqfVif53YxrRRqxdodXLhK6MQ+ZntW4Bd63RVh52+BDn/qitocNnxWKya/N8Zlh9a79SroUbMkyOZ0flWajJAzwDrVJlkA4A9pnrQ1UmszDpPyDoY2CdRx5ck6M6gWToKRi7vXXrLLXwiV3wM0ih1Km+02Eq6pIHxVz0Ems47nJeTYx2hrWHXUOhp4hoDEX93uiM7razDcf6vS7gA+0etv78/cJmdcRv1EWPVSTLF/x6KqcRgc16Ek/PlupbY3gx/+P5HXbiGrh0U4GBqp+1vJHbzVBhe0MwmBcge+Xo9G/uait3PdVjMZtB5WNeeddq5k2KGB5SBOsgBFfpHr1zGB58UwCiNI1dL3NUfxaR2NBK3ZbNMMfPieYL05wtYOmCZADj+h0BKQIff3wMqk4q9u7GMnbzU72qLGMMNvD2MsUWOxqLU03CCiqzs6yagX2sqzcA2X9Q2MaBaQO3vlieqc6pFwCMelwaopCy6MJ3WHAtFjXKWNIRdeULJsc6IYNv57eYd7QJuhs8ywUslcNpjjv6ifH70F96L1eHXie5YeKm6CvsZVdzwP/tW2IxYUOaePGKuel8oSG/Caeiev3M9rFvqW1i5N8yrjN0m5AY++Fjr/nTH+z993cFbnTmxV3cXmIi/MTRQflSbSeVoWY5b+cCXbygn08nvdIVh3wmzGyB775MElntgRQYcTjCNDsZgZxFbhfZj9IWJBob7q3SldTS6M/rUiNApGxpI2m3eSY6MXqW4yRpdK2bBDUcMLXQ2nSyTF9qYQBEx2pzKT01pkT5ttdGNkeCLw9r4E66E3LJ1Mar7Foj829i9CRYY91Cl+hwKmrK+3I6baJIoGoyDBN/5W8rpOZCW+IFKNlMR+Dp4q6iCacF58vzn0bApoZ6r5n6YPympm36TQ7iPaZWjK/iH/hXT788VACV8akU5CjOZaGAYdsgzHaRbWoqcBCopZK2tmkOyqbibkBcNTpRZUyyOGNvrQGLDfJ2mZB1QdqFB8RejGifB2NlV0CKveMWhb5hP+pgxxnqZ7LVOKo6xV9t5D8tOEs1E02WGeXO6aGLJl10Hi0T1yGPhHOyEutgKA/HKRLf60dmM36ybxWtnVyThHL+2FVj+k3tMXHsdyQF9RfBEvUUOP/Elag3lNGRkUIAiqWSKIKSRlTGEGtKgYXC2pxtGG4gktjo0lY9A0HgyjGz7m5Q0F1AnjAvUkrPdjF+JK1TCC3N1IuWkBWcVs56kO9JUn6JX6kh9yIFXpWUt1xfYrUc9+BzpKf/WxX0g1OCkyqWSsk6uTU9GqK1ohho70LhA7OOf4F5NzIiu5jx3X80+kl6YmUeM5JgeHDLq20hcGi/tfPebpFKjFvvNYGrSdOnr4cp831HQthXiJdB8YKsDPyJ0XcTPFvRDYpqiCiUQsTajdyfUV6FeE/7tb0SEojHGQpQt8NLvNTK+aV0qPFTch4rZ+nlnshxQjpAWKQCqM5sBK3xYpXlWUWWXCwH1DIL9Rra//tDtx6SIsMv5kEE2GoBhA0dg4w2SMhbtON44lwSLvXCOcYtNLG9XERChQpptAbDJdd4aML9ma7PxO/cG/pxPa3lxl/JMc/HlnDnRyJ6UI/V6k/tCTeXVkM1P2QgGaow0c4KC4/ZY6Fur4XNqNWG0HqpGqSe1qkVuIIdUWE/GxD/tK4TeM1RV0OHeCxW2hROzET+ECrOxg9EqThvIDC/pKFvOPuk2v2bAzrT6HICV8AUgqRTKQ/RgbCas3lcPe501EOqFy6wWdPjIePkyjZl5M419WnoK2WFyW3OSgnMhVaE8OMAkDnvASBtF/NqhgqEPwaLa5mv9bui6f2YCXrkKt71ZmToxlPwBRU5hmV9MpCm/hQCnMTf5U0BE8+dAsGXXULGLDe8YgDxX03S0T97sW42K9N1OzSSxrPfnz31MBQWOZcMyRInVHtzhnepW9nxrfDsFbMdyzQpGvMHDrCPeYdkV4XtbmzToL+8jgJsyMbSDtey77kANqOi6HFe4cGelZw1Z4y+nNRd7z8STuWs/nY6s07KkGEOY/9ke1tdBZ8InkMUhNnIAAL/7V8Gj2lxQBhlI3YJD+JhP7HNCh6T+M14cNV5M6Q6F2P897hr2If+wvx4/Ws7Ply7zD4f5GVPDyPXxNJQ8lZtOfa71uSZoA+XKe5hHJIaL93CcWWolcUSkXXYjahCtYt/rAvH9QYJTRMzNLXC2oLCpv+KySWe00pbKjMpgaq41ns9MvklMOCmD/6KgDcuMfIO+9LsX+pr8xEuXjh/LWIJJ/dZUD+yS+3r11/84PsEgN+Q6w76Prw8Fo7NZsL5viwFmZHUI4Lh6C7BVj40GdldopvyldjrvzQLMwlluK9WzQyTaHOIOO63s3PoJc46Mrgv/SwuybizrXIuNjKKzaSb3UX7wLZY+/cQjgF0iZEcg6aqPqv8FgWc/SFc2H4sH2pNkTv7+mbBnqzTXhhbFLC11lW4GpSz+ZFYk8I3hxhPPi/fH3CawFiKFZZnSA89e0nrVcHUOOf5tSDNt7VPpP2d/AxTvULMRiMvEHLdj6Q5jWK36swSDXBvZAidsic35GQDK2s8ZnY3h1e78UIXktJ0OnBxqpwSCkzdYDpAVlrnNJKRMZ1ZcULw+0SN74EgbSobXlVFeisXm5YX+mn5hVgb82/X2xo2Te3mvLbOVf+CxfdwbtM8VceWu8tk5PhB/FKIhM9tKSWfw5ivvoV1fUDfQ2urTDPBmVMmbQB9nJes4x0XF8JkBdxBlUuJ7wJR003O1VVFJOVXIiuOTLzFk3D9ePaaVXlxAVNY6d+K0v8bBSFgCq5hgP9dt5nr0gL19PZo8BE0bDC8yHUXXpkCC7/99YgYpmzgApj8+KduQD7dYgtKEI0C9NKGdkbxY06fM2/HyR2xk76lJy5pu1bMg+EIdPOIciMCPL79ch+pSCCVghyiiUwYLD5HADUOkEmHwBYEGb6oMcYYoj5h5rEQulhavWIJ7pGqwhDGKpM3HKgbbpKrxA+QmqbBtmrsLnwqP8XYIsMNsVWITWbQ3CqSBIi7E+lD9XkqkdXnVPfofeOVH/NOPjOd4Q/fsJ9XWM/8fxNLKfBnyHPS1gX69T+bQfabHg/8sxYWoUAZLOLwFqliZd7jlJXW1KIB1Rdj7Eh6TAEYHFLlROlw0I0ucHv8xbYblQ6W8wuuEA0eDBLW8gj/rKm8G5q6W958oLN8qMgULG20cx0CIsjvr7WVcfZt8o5eUrTYFe4T9FYoSZZiHKk/nGJS2s1tbY56aTFlo3y174Mqq8bok1smdOIGXTlitgF5LXtXtYxErgmHKryKz1I577W30j+gax47TjLI6aNop4ZpRbU7UT7s6DBZ5ai/CeqlOHtAt9bnPDb/VbOgGIn4TedKnvx/p5wslnUcxZUD0GSAQWYGgHmRim6P3vPqZqWY1UDzCx9xCzR7joot9CJ6DOHzqcArrhMo8RChDPGaNlJbLhrUzhsc4282Hwjwl46jHwrA0CvpudIVHvNgbDJJKTGiaGlZe0bcbntBhu20bey3vZgGC9vLlHy49rve+lfZD5iknAv6BMbCf76rd6zLq8f8spuWZY2gDo3pl/BEQ0sMvVkqpABbhq+E5Ulcjof/ULuz2va2Ail6ddMoYP1mznysm0f1V+Ib/HLqFgnVy4MHIyEX6fTmxw2pptFa7A8pe9xK6RhK/Hy1k94LSnVtTdDvHtpTp8z904wMqqXh0pCaEtvifxZGzxmlbOUIKCeKE9HKC0T9ElAhabAfguvbp58Vj24AIPW3/EN9m2XYBoI22DTi6//+QL1Prl/DzSm0AzzWlr9DOPc1r1hPz1Xax+9I9g+ewec7vDwsWiL/sukd24e4cp8UvrZXNwL7R//qvEtuz7LxjhdcYVCbSnsmzNdyDSkGUyAZr81K8PF+75ucWTQcM2W2Yrubia7Ze0EYPCa/bmPexZV/1pK5TbSeIpLcbxcBsxmCUTWKZxPDzKDmpR39JIWaMumk5V24g78mYNKRiNUK3lZ7hjB+/cuRkyUQ89G6QSSeW1ChSdufCrr6z4GWFQ61s3JzTxixs8i7f9e7a4hoT7NciIBm693vPB5OkqV60UHzKsHo170G8Y0DvFMTTPy6ZMipyDk0wGG2u7aHULLcqVxhBf88iGNQVtVP6mGLWXx36w2EzaHWHdv+9luNCUb4YWxfw/HpMkgz6hcq4m0ZM5rKdaElTS3uUnEb+gQhPFaM9XzlcHG9cPiDOaOYdpK3wj7qBHtA81qUmRvYGKTYXOEe8gpmKfsqJPm3q3c+hbXA1xFyOHUH3lsj9k2iqLpnmle5JAVz/iqUn0Ft2fNhbYeWL+jQxtV0D0RgJNB6Aht90gVfzxhZsSihlItW9wHaHj0uMdRk89RNOsnU8dxfyho468xTdZ72hsAtfFxNRD5bCyHfv7YL8VWBim2M/4LNixrufrW5oFCqpQ5MMHbUnSwkQPrrSNU7GZ5KLdDRmVBTVwEFOifbnVkxqa1lrdKnwHuzOovBCsu0EO26WEooywCwzDASX+PUaIjGLaYTKQcyE8X6lJc204WMfzuTYGowPSQQg5lwLMyQVUv4aq1L+AEhweCchPh5AM5wStPC6+mLdL1P6ejN6UgN1KUaO7OEZ0KUVui/cpp0gi08dJZVBbqfXbWwGBNMj1hwFAXzW5d2wYgtbBSuFHTPEFvxWABSdUmnxp/klJgggFl2PwOB9+mQ5zjMWCTYiIh8F9UKJHhVL5/ex0zomCFm7+KZPFtz4VUKisNSuAr2Hw7pc9L6GjVBeonECuu1aJ47BlUNVRGgtpfEgRu4x3rYdFI2ZLB9qOB5u5/OQsMUCjbnT6I28ZZbIkvEhvz7MavtWFIz1+Ig6ChPX2Vi2wzCXPMWey6KhlNdHebHRIJAIUdzv75YucVIuCcVlaf9+70jZalSQmcWNzbqbob0s2tXQlqZL7dtuRZ4zhakxSaJMHRX1PLXKm4lCJQ6xx8eKtLDwSZoQvjF0/e150v133+rRMElBrvFqBq/OEBf3PLfKm4tCJQ57xMtKtbElwp/zybl/+P3gmvQi98emOZSONJi74b2XrObpxMkjuh52lO0lNi002Hz57iTd6l56pbbsxMp6BHtxM9B6ZKxi29WgTdHkzTuNa6ATEoTL/Jb+6TSsrGMB1VhF7Jd+PyCtZXoCKlSt3QWYqRP/4ktR/2FHgAHNGESCvSy3LCuK1U4WR74GwHmAt+4Ur333x7SYteEbnk36wpuvjaKgqBJ7N19S1Z/A0P4W7W+IC+qazvBYsgzMGmlh6cr9eU30gSXLwPmKdZbWXJvapPaoXaqZWLZP3Fk8EUjukUhZOxvgONTlAkpLCPz3NoQfPzTLE2nis52HT7eXbdszSg2y2ExTd8EBP8bHJoO5prF/rFgcWCagwyO4e7mVjf/OqeK7Hs+LyM2MZeJ7xOqwuVkU27+TFr+ScqgbqunWBS4UA2fc88OF7jfx/gfvdDj11kvQbGWCUR7FgmyfCLZwp6B2tkybzJlIjTZWlO4ijftEFq7ryLfowF06ZuPIbu7CWhlQqhtgpg6Ll+G/UFc65Nb7CtlGZOGUP4Nu49xKDp/KTCyaJ5zmoWc0Soy50pziMS5V6eOyJCts10RyV3hSZmEOECS+AROgaZW6mfHk4p6wf+0tMdnopfDXfu6oCb8C1fWzMuPgJqG4Hz+AXWocz0+Q7twA5ptvt4KmYrCxU9SatzVsRM1uEibfRGdtYerezLQQmAplnq+1BLOe2E4vs6CLU3Oobof3HTSUDMppgiwSg45GtlqCyipCNYIbHXgLvyvQk59J3X9sxyDeaX3U5mQSPNUi3dE2+6qMktMeEEZjxmbfQSVebl9vFxHjLiKKMr/divOd62GC1mW0Hcl2BD6yTvmFPdg9qsh18SXWHeN2A2knza771/ItrFw7dLsU2g5AxoZLaJ+yJMbZCF4g+23kYMh1ZxCVVRXEA7kxY4+lmD+gpfBWuRhBCeeWQhy1Lqt7KtsZEzM1tpHvyY0VG3C0/xf8z5rEhWXcZ2kK52t7pBH+qou1ZrLRU0lxJ8Jz7YAII93riii6FPiTavFYGNn0BVUUG+nuXFGBuIVqUUE+FEMxBCgLta2rWloVNn+UcX2rjZSUG/AfOdrsGRee6qkw9yhZ3Ky9SAbsQsINYFCZYeSXNuHRg2zhCiOceOVRYQzKwUA/VufjqGKfoUdEs4fOs9YD07/HfocciQYftQDKOUG2a1jNr1rzGVAc10YmCfAjpN9ze3ubSpY4YiClbBhRJ/jym1A9+m3+iqICVmtPkZP1jE0kvV//84IfNDjOWmgbDRWPr7RGwY2uHq0XW3RrSVP5mlaj9+oNn2vwQZ/Owxyboy9WD4KArO+CmD3tcBtCJe/acuW4SL81KkEqxhiKD+3GpBuwJf2DXF1Zoif5GMqwMeJ2I1UlKPZwLKTfrKajNafvDas4ZfWdbiVkLWyTTbt1ayluzbqVuNPercV2+w4ZOldDP51F52Vof0P5ZGD90WxIkaV931VPMAa/EPS1H0quTUQhqScvW4eyQ0ORxKwP1pCzTIohUk+MphN60AdjKLl2EoaonnTtO5YdNprka70++FJuIoI689LVqfZw1hO8CRYETosTvvUND/GUDneyhk3ObmsHcVI5/LEg8UmZZC5EUTnb1zoZb+0FEylmRZmTctVxlXo/7SR3FyIemEONk6ZgrLqs++JPV+Q+FENgMQ5Ggz8N3R8nTp95a9BhiDc5M3BdWDxtf5X0YHinxrDai+P5HvqD3mDRXyju4+eSWC+yRyrBnBJ1gIZgFqwHgnAVFnPElcs2m3qxij6I525oR4v2N1TPhtE336rPcmNoP59pYx3KhquecTP/jbSV/xAAMiPUZxTRI0lrHUk9jDqn2qNmVcniKf0eJnuIZwXmu3lQX6BlDYLKc8WCLX2zQzJjwAPzscdfxSHL7w5axS4DGw2c272jOHgpVhkY8zhLNOzm+CUxt+dD9OlOV7T7XH5Q0GTOi4OBISbjysgvp88FcLNpXKB0mbu2uKMCH9Wy1pfFtcsOBQ222LcVuY17sNfA1YlwNtTHlMTuIIUlCjkcYtLAI+IcdpOxeNfHrNbjH4em8nzudSL0hQZgqrWGClm7LsmG9JZCZMyy6fa5euwx9+V9XA/Wi9R7cQll4ls5C5kZdYhk9SMm4sFDBcBUFgRZlrqwb5CElb2t8RszOH2nsqESZHKqA0Y+iAhCU84OpS4GmLSQCPGRskRBCPqK6rNFCRZyHtqs0fywsKzrwpG7tMG6f+bIz3TqLyjJXU/wzn7cfYL3OXlsVv6BnLctgS6fFvkHZ0kz19fZKz9Qcue8TdlTqzDeErjhuqyt6/JL6cO9hBW6lXXQ7SdhD5LyCtu9RShtX0skEKUKW5/6QzSLfYsIPORl2a6sPn2jDxt+kPPxEK8U25XPjHKAWN2FWkGVwffv/AH9pqkgbBfftSE5O7q1md626NehsrKXGCUZsxVNicx7+3Fe2/PaVAqa47e4gRTZjeHJLLy1+XZFFvth8+YD+dvnSY0ypLYeY/aRk/tQ27DnxpvIc9asZB1m0muX0kvcddkbfFPWf0+tsumlMeUY+VJWAPCLIuTggqH3/vjNRkufLOy7HjdneULDh8QufdqwrfvxnY1FiQX1aBewYEg0apj+ok9bbTagi3YyfEfyeK4KmAgd2o6o89IaI8OhxCujrMFFn7barIeO+latBVHKrsE3PvjhQpt2cpI+tdosN5o3rRET+Pi8JprLnyegn5d/LLSf97K735MMzZIZCcndeI7AtBPf+BxS4dipmufZUlrK1oK/kjjEteIIHxG+MrldtKoiWEj72mU9ZgKrs6qeeFahu63KFoefa25AgpeuikfpxxxD/e07gIyXchDQ4nGyXaONoV+U8uORlE3Raib3gXcxdmHPROWSVZZVNTVoniQW23o5vLaVLU+AgC28EoVdCnQnD/2s9Sj6Ejodtwibt9gWzVSLXIaDCLyxBACyxcXhZfwJyByYjN0lXkwjRQ8pE6CilSXXS8ZJ0LNHwmoJa0RBIFh7h2cZkeHAvzfLjfdHHFqgPBaAPnj4VnQHDYAY2CIK6Oc0QWqwzAD5+sm7xCjunSR174up5j/xlw1lktL3u1/vwvRWm6nwEYVMbKV3PTjtBWPhaXK8fhAuC3wO1MNAyK6WxAFDPbeL3meK88Ac30tAWLu3wMCJ64bBg/A1qPuKgJ+BbDYcK51RyoLW1IFlxbdPWWd4HLXJmLzMdpCUwtYvODQ/l8oWKLJSgfTogRI2nTVgMhDR7HJwFECUTr6hLyB41kye9azmQ2mw4H0SKln+gK6jLDUNlj5rJ/L53ZKYJ3JPS0nDvXXhKXZzU1zIs2VxMObdte8EeWv8UgHg/7XHxrF+4hLB+4EEQOVLxlcL92CDyzrGjzTi5ZJDJ91PHAcu1DLcSEbeajCr1/JM0nO42H58Gde/tI3+st0XjS/Y632VH5Jgof9aWGqbePiAZJ18Tu1C3I5Fvr3kMox+qWKdY0cuhctf4BeJN7jGyICH25JnBfeOo03D/WVF7S2wqF7cKYtLBYl3Fsc6h82V22dyPl6dPYUDxNbGJ/FaTrOPNS6r/mag5SDOL4OkHwmGXnJ0sBbdemg2n9J3Wyysbz/IuAC+4vJe+rYMBDTdaanjqilWzdJ3acSsz1ueyhnNkmyuW+tgkBNajUnD25LqL9timcmv3lYXZLdarQ+jcP3tV/XNB5ZDEentaVJSC7OojjNpnKmhnQydn0XnYujNDNVX3dJrMdPk2vBApqEWVqu/w7BeI8+xwiedQGgSmnLdMz3E3HqIP1Im2GpYnzBN/83HoAKINu2s+uRs+jCRNG/ykDHs4YWKv/SkQbLq9pwxQDsX0Na7JTAdTAk8hIw0MYpeOJ4+Zklh18cusMgHaGZduJ4+lomx6GIaoE46USXML/ZngPuqOpoFawjkA0qOeJa3hcgZnpLnjHLny42S3ZlEkHbXE9PR8hvfogh0Ts4e5VkK/MLn9U2mAuzr2uXh/vT0rniumnnzOzZ25HX5WkaSR3dZ49sNEYLd7OTU+3jaZOMy4bzNBx9YksPhm6LJJZmY0FSkihULfAcorkggDkmHjkCdoSfPmEOGl7eSaOKFkZHpCJQKgafgE4EBdScrs3MPmraQMCV0pfCFdmsaUmfQrC1eDX3iF0D6KgJFtEAuCqMFKQ6X6X42fGXN++eAe4UNYEATNdgT30qTdMZ7xl9kjj5Cw0ng8vhtPc/ew1WV+8/wchlaxTTGbzwBHhxpVea6z0lrdHQxWfKWl6EMiI3shcU6z+Il9nXtUA+2CTfUVnc4TuLmVyeSbmcDrY07/MMThutzGJB9/ol7OM8GXAUq2KRXVg/pySLGdeP34iwhjCU4bTESB+BBLERcLMJdN3svm9M9SQ7xoQ0uNIwGQ5XUtCnRNdkncaN9Q5o358Iuz1iJVhED2CnMeISTTPtpzttvVuOukvkqz2D81AXkXYFKm6XAIXWljcmM6+ulEmKsy4oh1MR0gixCnj7UsgU1lVQZwLyx/3yJ/obUsoMivtfz69ez9g3Mohfy6cyYFVS+sGCjfN0UZ05OeQfW56n7bxdyHXCAwI2ZMSS7MWxMiyE2FQaLAJfXmtcPBZdV3/bgKKU/jiKzAOiVAIshaJfC13dfwQV9e1LOQshbX01f39ZJIVm3k6FeZUZBHXEQnL2h3Q2ds0XnZ2gXQ5I3I9D3gZhb3+0QqUfBraXmAnDogXbr8L9pYneCezaASB3WUnMBOPTwJeZ4FHVKtUWdTZ1DTaq6912opxzUOzLrgbxVk3wwp3uHBv9OcrWlU1KiDqf1bF3Fb/+gH7kFD+Stn2QECN4SQrVlZ6Uk3R9z+KB5Wwl9p6eF9cTngxVHsv52EvouTzGJiLVeqqvt8uOcTMXSs3T3RMu2wfxcEEko+8F8uSPcyoLoTDokqjrKTKPDulgHbayLNuzXd2BGWt+NPhMAYsUV//VtGkmIOtWazvlWf38B/TyDlNDkGp2QLVby6zIo6p+FTR9KK3M0os34Ii2N9Ds96LETuuy0EHex9Ke2BRYopRRSQfT08YNiIgLTs1TomQsMszI4xol4YJtecCDdoL74hQbwMVRsXuciKBWAESfDUTaJicGn9Cey2hTyVs6BwOIN262JCfjCjBBmYtxxfws329OdFdIQBJMfPw1yEdtm+bsftujauGixNN5nMwCO66WNFpHNkrCkCdrp2bFWn11IoHpDY5HhhePlNIrnK0T1qiZWaJxL3zbB7pJ783PBfy+R18Z+6nhnceuE0npit++RAs5yCNtFKVR0HI2aip50bzMW4wG3ZTPVSY54+CJsN8aKSom+IswS8anLJtOmodPKViSbEx6tqI14wayvcoGMaOqMbWjVwhLrHCSyQQpSQ+kqgHhCqKpzlYiMDiyJmWtky8U0bWdPoK9g+hrXFCTxDmbYVdKHzMU7rIiCtgO/FlqLPZYFs80cpVrMs5bEi1fSSSPaAC84LdVAG/XejH3KNw26h3jEAr5aa9pwpp1cbXGGPfdCboj4feUD95z2ssJay3lmczEWT+QCvt7XcSu9J+Sm+cgIaXTi0x26vRaVBZ5w0Tnj0EZibE0tLkOZCkUdbxKhC8pQif2kBERi6+xjbVQU+XlIHpDWTpJDn9ZYB1qYBKEurEpG/bllUSMwkihXS1h/hz2vSkCkYqW1PzrgBzqwT34v4Wtg1lDgU/3zSXYKaeRSxG/oXUtXkW+/5pk3ZMFvd0ub6pW2H8pCG7yqZ4zFtHDIPW/mtHBqtUFA+QMpiOwtL4liGXi2cFrFiLjqfWsNgPPWnsZr3jYGBuqO7MY6os7EV6yPT4F2ncO59Nt6WhMN0+xl/ix1J8ort4LE+K7kTntoKfjfrBjHzh7vOD1uHYtev+V4izcMHzGEzMMxfRuPdrBZibPn15WIhvW0gli1aZNH0xtG66p7bYsXoTIFr//6TjXIYvFt4Tc05cHEFmMhxbVti9dzxGTYQE9VAxA5Nui27WOKQxCVAlbdb/+U5+EFnX/2LhxQasOjAS2d0Sz7xUN6eWkQP2h14xdmmceJq2/5ecsi5L9IzythWlkIxRChjxVWBaXqto9YwTW2AF3ln9dp8NJtkPB99Hezc7tTITmyP8q5cyE7nam7QKdKzApzMeN6fu5IJcKsqjnYtlBqLHaYRWTnc0r6p632ZnvV3wewORq+XyXH6zfrPEU+/DmHje4AP5m8ZGnef9dcnOP71P3j7Bv/8E0iORz8/3QOK4pd43t25UNnqmbuRr11RukS30G9RyPYeylKB4nPie1I3v6wEezqg4UM/OGv09+49ClwqiNamwgIhWGieWFviPn8RMH0hcliQMZBKEa19GrPJTE3Xenk02P6kDWr6i9iv+J/AOVRg+GqaMqpMq8mGM6JqibJw4v4z8Q2pjwqPuqBOKJvVOWy69/LgCn66syey7biQai7vVTFm0Kr9Y0ueRyMLMw2aKqIDEegCLGL7HrcfSotRxPNfdhDolrOpzguRK1Ao1gQy40mqvyY6AHQtchA3DTGWWS2A0zuLbtAsE6Rkzhu2au6h5bqfU7TraoqQj0hRGu+rcRzLdGITa5GNSVU7m7ZNi1F8OdEcsNlakwW5S3A3SJdtNTnF+Wr2m7HEADo5YrkGhkzYUmr2pTJgNjZi+GX+qtXNh7TMkWgs2YWk1n8GZz0hJctOuqXAZByzNSFdQ7Z/GbLIjaYt+XSlXLFqThHReLDxGrjgeeRY2pPMNpjTtaw7LUbyzNGx0e+8uaSZh1/EV5/7gJl8N5PzGYAOOUosMG6AV07H8qwiJc+MSw9l+jzQOZXZwvRu119xhAZA4uYZqamMcdfiOZX2TipuscBNHHU4wG52iYo0Dim46vfETDChYltpfF3D1SB5RGm333Kuym8sf0KYSyitnNtF+eJve+bQq711V9FjLEpUsx6xXhyxJz4az6+I1lZNE51/B1n0Ex0PNNjiWpqLsJGrtdDXp55m/WnF1yfE6UBuU/n+20DZ7xe9wAyIMhdvVCF/bamswNaGCf1CyPsbP3zEZHbE69mUMG0VDh3imY7zkDHbPrLZ336W1wciynkxMcasQ9vN4+YoQ4X49TsEfqQ9c9XO5NfUWe/Dxc2wBMRL+epLY9y9NztlcsWz9OtO55T/qQW7xpUL9fZeW/LyX5+3/jcbuv5g2WL7jcm21dteJz7ipWlPTubLvQSoBxoWgI14pR9uG4hyuTH7DrYIGh5Upf6Xn3Cn00wOJ5ORRHv6BAuSSOB5WZZ92AN2XiB/if0FsIkcArUVk97yo/H850Iov9mvmf8WhwN3ecOgs6zB0HX6u4cesaA0eMiwp0WrZMLIBgBMoGLG0aMNvzUGWqJyj0nphdqg09fYgrIS0W0hWqWjoofwaNnObMOSr84PAhPi+XlnNj0jaGI6KBoDqAPzo8BkGoebfJXetIxCWScR1saBfVpZ7ezXSgWjoqgQAiwlEAP7P4SRx2e2jJvcZtpmZk1aJzG/nrW9XNEnGqBt74k6pibH88E1N/g2HxMs1SRiVTK7S1pHnbTWS0o56tXX5Sj1FPr4kOnkgbKRTuUjTS67lCOB9xLx2L8tMKFdixuuo6yZTlAN9MqXQa68S9G+4FizAeKlYPj7s+1aIIqifJwciGUVjgcGaWiKps8qJXWO4fFE/vNNzAGlJVuiu95dXyTcLqibSz1BAWxERN2nsv5Q8Xpn37FvJJ+t9eo+MheMC4Nmx05gXP1vvfIj3Tomy05z4UC3woYU0y20OPln1x8bKcAT185k4OV0HLHeYJdQ1OpNjp0tvJdxPndNE6C7AiVapL8+wKNgj4RoJoE88Y6N0A5GRp9q7oEXnjsc32k28p9kljcjqSohOr0nOrE1fZWiHvvrGBp/3PFKlVFe8b1Qcx47JmRhMlTYSdf3j8Xc2x/SmhrSiBZTgzN9aANlSYD/IrLYatITsSD00kwlBvZScTLPN13xMj85cdWs8qpzSMezmUs8Ndy8NdyUz8Ltb6b3CxzAqnft8Rgf0oqhvzHgnFYwB8ZJSG0G/cK2o9/VfoOELMHfuzPsrAiTDPJyRLTMIxhtoQcMZBcicfQR2CfzmLwslhKLCti2/1pqrhlkC2fKLdAxHRb/v5hAtk5Rl726elKquXzRxCJwk8ZcJ07O8LtelKHxhMqEea1SWn5IeGmeJaoahXSijBVBhXU9yq2xiMvl+NT5g7iqomC1zpuCRFf/qwyX5n8FA5uk+Uu6WscIF/6/JyX8OwE0dky9/cIXT5T0RiFS9ktuAgysSUPJ2N7xYIDWHmEkGT9U520odgFdUMsnDonTvQ50rbRtq45pzJr1qQ+Aw6o3aD++ukutRss06Gn8l3IKxdtjXUV0qXL1FDMiQLykjI23U6HKdNua4um3cVL9rTbLLgt96Iq0teUTaHs7NwjRUsd9tPAnlqPU1HlVHOJA6wWvzLOxnz+miZm6X9xz9501R4LgAHXx0iGWd4cpEHVIcCdHsVuJSKg07bLc2xsXd4A7J5mWvkhzTmqXxNlfA3qUzD3WvaR5gTQHhGk8PamyOgB1hy/4sxJ7Bttd310eIy82kV+9wX+HuMhcYP68RmTw2QA9r38YSIf9LHkwsjztsnXWYRu7w3+PD9u2dnf2rurfhC321asmLfpPjDJc5yebZ53L8Sg26k0anw7R31mU4/KNKl9pc2VADU5boRNHStLAPM9Z2Haeaaus0hdV+rjE/2gUAbbV3IpC/s0XSP0UTDygSAq3GIsP8dnGtWpXl0ViVBx/UnXukfwlrxlqeSoYsg8Nys6+bMxZgUL8y3MvrMoWnO+Qc+4EpHDVRkdCGD2rX8PLrN3wZ0Jk5b7qIEqxyxRObqD15anacuOvKsq/9EaAIsF6rZLiMOuvbDsWDMmkPItVd6j/e67AQIsAaNymBlqAetaZcRQ9yM6DpasI54Elj5wfDbhQW7mSKz0ObKppgOEOfsq5fByhRzjpLTBdmlFnH3txSL5p/knB8Fn+81xAapZhnktshady2+jAE8ElLeITxZucN/Wy19dKveBY6zIQ5ucY0xL7Mlsz6AEcwyTpzw/yV2T6IWPsggyyJ4x1Eq0mAxcXWoZ5ElzyP8ppcTNCY95JxxFdBb+AUFxuODyAk2eC44xJ0AhQ7zk93nsgCCgBKu0wOZIVYdnegHiql5gBr+HpMnC38o84ps3vUPsAxq9Re5/R4n59NnhqmgzW4mBoNl6kgxq/HQKy1hrxlaaGA7ufWoodjnLnPw9MdJoFu1n6fgcztiqEjYWvSBfOkUtUauQbfyBEauwx3UGR8WiGoXZHFTs4uQ37ZxuuO/mfstMtIzOkYNCLuU7ROigSoNAYQ+oNljYHH5dbIi4bA3qcj4NhXSo+1vLQVSdhoGdkdtWyeyX6erP4nwVvNZmNsXwDHCajVmDZticdVRDxthkXsDcfDeuUzz8mYQUDxJR6vKIDKeFjSLx8xNsSOtwbUg7IKFbfuayRKmR9oc5MqX8LkJx2mUFWw280XpX40ezjNU0x8ahgh0KiaiGwh6Iqji3FWbHF5iIPsz6v+5/G+LhYb3LzdAHFylqQNTsljnbnuOJ9kF/zZHuTlgsWW5HPGDvC8Ulws5Pf6eQbcdnerF050WurlJP5VUGki2hQzFKsISP7pdvnocPPW8b4bzdk7L8kU8xbOppBMRHcg0B4trGABIzgo5tXUjNFihXv0NFsueQfEFIaWtqqXgYTBsAGK1QT3r5Ow0GdSFYYHzjcd+s641fslfxm3JFp1nRgHS/XI+aK5kgu10rhks3mCnPFw7KlQe9uaUS/+BvypZFnEv7U3iy7NQBVkJsvmhGgSmegiYBwL9tLJOSTBpb7HHKMzlaPXiRaWkIYm/BHVcoDeYZL+MlMhr4EquOHVGM1zcHPNRzCiZjtyP15mZ8cF3T5khIu0cn/9RPNAud/WdDFDN/2xEVWyW+BNmrG5GtiuKmTppyM2F12GmGhjWUhgRD8yb/ZEk4KYs7DMNjRJx4+foDW6xinwvPpBBVblsU9MF6kGfhP1zOXcFf1o7zVTn1NwEB7ddEQfSuMg9rRuWgM2et7GExPEzvxAi0fmRyjN58pQClimifXt0izJOxcoOcZdadq/JET18Qn1bnNwNW+0KKfQ2CllLEx+A5/xTvWg0XEdRYlFRH0IEg2Bp0VReR0btu0Er8MVseFkXDq9XAelPgMbsRd6jbcEvnZlYOvhVm+/W3ES6tXCWNSzT4yA0ynkyW4hTj0HNznNKaXuoGHAQZpKoOgNuOdWQbYTZuSQPQyyvvc4V4kVPmHHVn6oylqSyXY6pl6mY4HaTVExoDj3u7ugeHCgxj82yT4gvofcMNGcAPbACaao75VfaKihf3n6z6eDtq3MIubU9nRHQ6uin75/+6jIJigbfaow3d+9B+3aWJ7j7PM209UBNI9yIJKr7HyXLJlD81k1i0OisIhTc51mg3zBfBrAMg1GPzQzCQkLZnV3ul02yglzgHsZwnkKvST41BSEP8BRcIxYgotkI4LtTkrhIgAufCYSBMo3dtVWwNL6zTlbfcXUMNd9y81Uq0rGG8qtGy2MliH1JPbu1QxlD1mCTurim870mImd7+9YT57zaTxScjr8EZpK4gWp9C8pNPantREL9Loabcvm7WqSF+glTqGXnWh9bXMJAgbsJjCAN8PLiIO0M6+mDuuSCNs+S8nuQvfVibczyB3xxbE8JMOK/mlds8LxUY+H0k3TM2pUy8bOJj9CixaJ5x4Okf/CLBggebQLsxrZMUehq7Yu0Xf0RS7WJJ3bkgFEzoxsi8wSi5D3RKTxFc0lVCUb7qLLSBma9vRF5CTGC00Sfg+gohLtTtpNoRPxXc7q2eClpv0X94BOvfuFn/g9nVb2JRAgPNwIbCxWomKsZIgZd0x3Gg25qrOqi4m4jFSZLKlYq/3GNdhmkPNZf1LKVOFIQWKtxwgutq/MGySsFPHCviUJ8nypLd0VSRiCEePVX6jIe0mDqVxQr4GMn4cbvi+5u83Yc8njJMYF/QxxROQniX11NKPFQi2j/XsijgjY5jR3ieHN82JQQphF9GxV2ncDCFfYWH4S+oYWPS+xjwprA2+HDXhTmarb6n/JnmYLmWBf5nipDs+SXK5kqsZfJH7lnPMurqVas30fn7YSOlHmuojQo1/eEFKMuNZ3lHqUat0GNIcUud6oICkUAmFL7ibPYqPdDTQeuBfzHQxijjB/jFBNkYLtBXsGBBwNeJz7+gH1ppcJV7tAVhS55Ovgix3GxZOdoo/dyT2MOZK8KWnOJEZVxYrC6bkcF7+TjWQslTNN6g/491/NMdN3kval+S9ga+OF6Bl1NZ2VWl0+/EoBUqDjW8VxrFOpoB6WTRTV5gIl4r+xcQfocsRyd15rsTyJyEjeLNACHHWe/IeXYaRuQTgmFGEpng4uZ71nZ1qw0bSnGqpdS/GMcWVzEBx1lblDKecYb8MGc4ErnaGYbSBLrFMvd6KCYnGJrdFORe1WcTaDTbUOotNj2zhYrzu8I87JdGdbdme6LcjWz6/CXRhE6DxI+Mbphd9f1Xi21u3WVIUIsyHgHU1lP5QynEaHPJbG1d1tT/Isae94K6pZX3zYmb9xHsQeHviCF2ggGh1Qj7alTAC30mv0J1h50LyWLdyBPDITr1rm0YWVgA7z6WSHIzctWo2tbm3LPNthIGEgEPgKHBSwUuDl+1ATCBJBHnSStuB2CTOuoZjfVnyVM5HFSu/2tmuYsg5Y8AXO3hFpnYG50hQX+vS247Cmvd5ES9NgKtigho7hpQSTyNbWUxDjrY2ssPPXE6nn9X6s9QUOBvrPKKBCUBwQ164UNUjnMNr9fwvZm42URHi8YPt9LvK7MPc/aKsXmEEc4YB7VHiosgmKYTGY2CTQpmNcQY4d4EjeKhL5IvjuwTXhH8LvmtL7Xx7P3A0hIcxKETbI3DD2R4No1gyHwPJe0oLhOs28UHgc2wJreGr4937zBdwPLnvOqRftCmtG33ZJukznJkp6TWptsx5piRj7xaQ43qNkYORhpz5jpVjuNVIas94slPj7Bq0sd8k6n08vuMSJwpejEjim+8lTs6JEVslG/kqda+wELe8vFBrDcx3nwSN+l/BymAnM0JiKEjj/EW8cAOoqqnyqvm0wFW/NlUlFlCuLrhRnHGnRP457S4338XJ8mb5yZBWvedabYHKQoNaO5dajhV3g9OURj661F/TCcoFFdl5q4u+xzqv0vDvknCA0iCfZfhsKRDPpfp32z8cgsuhuxSk80UwL8TiTvpApix0AlEX3xVYipBMU6fxQkUrUolc0hikwhjG2kSU0AqXrDavkv8yYhJ1VBxUBiHMUEKYyLJhFbtINQ4EZluhrC2USuOzjBxoxQ6dsjyEKIz9qBDdg0ssRJXwxV7Iz/ubO7z8GbbxVmg0BNYB5FlrclYdJkQ9iEKlnFJTF7VxvLm00ktw0axrfYMhX6SbfpzwD/NdbM6qfeDh+pYm2bbbZAcP/gINZ7TAMt41KZtfkxtSjoh4jVlNKUc6fdniIcKthJey/TUYvUG/SYblCeA71dcLH2LaWsr5Mctm3fMK7Xzztvm68CMv1hS7kOIixHNbDQ9p3qNnOzgOB5gcK/okP1zTvEv4RR/fRtVaVpZehDAfjDZJ5u2B4B2ylYDMA61kH2yf54L+2ddWNgQgv/uIFP7txSitee/D4nMhETlfbm45Obtf4KVai5YGocovRtdYkUslwswdCE0o6ZeJzlzUUozQcOwarSNwqaM3zUxxsdYxbK6SdB9Y2IrVx22pDD7gCAAnmhM36bmEan2wDCO1Dd1Bp3oJo2mjNoB/JxDuieSqDseDSBgYhoy/CmWlyPFT/oGtKZlBOmXUUUZNeRl1J2XKWBNL9dbGJRjmQ0MZ5qZwnjoCU3ARzQnIcqFS1sJfbFfTrdwVXROrGIG/rAgRt/Qe4z6CHRXMEqvOm33kuJurBP1ib6tVk9In1jQf/y7ZupweTf44YIaN5zAHG7sAjZ1rPkmBZzD7TAuwOj9qwXAfN/bRtKNqLHn+aVwMwIlNm4+YfLBIRyilD95UxtD6w1B6h8rbLbaPMX6y9e+/pRYL0WrklzMYyJZu9si1O4AvkaF5vqBaDgE1cWJgiKsKdaX1fpoIhgJNHkdmoPX19SByl8iwf5GG3zffa6elYql0/i3fS90HcHrSRUZrmTING/PZBKmXTiBY6rt2Rzz2BzPwo0Xpq4Dkf5FI8Qp8nIt/YqR79nPZ1bvYBkidPiZ32z2/NrsOyL5n5dVk7mNKIsLYyy/XUHpQ5+Nz84ugfyMpC5Ej7UYAKAg5NziI3i8Dmk/Be19FAw4eK2MAgCzf3r/4GYBLWzwpd0COUreLQ9OHZnHXkPaX1xDL1Ae9Z8cfnG4vo/gdwcOYYUctkbj3ARKxsyHtzBFmRGmb8B/d+oREDSHq3BnlnMAGjNPy5cRTAWgw1M8/CgqS7jHjKJgOVK+lcOyCfwJAMXSUivRAchfcHQMnBzA2THQOylc/j94Gv3ik14CIx2EakKTjOfiY6uuOm/Hgq2y0htRiScX9T4JrBOxuRBdZksSOnCvJRQEunHLTQwNEiLeXRbRVmLcp/clgrdCTTh7pCa8xuUCqvENVBGvCb7YaCwK1idSlzF6oBHTJNbnFHOPqxarLaY1QUpmiiEJlWR7ISbCVMQ1Fh8QqgrWqJkCW9CcTE8wTpJNgmlKvmdAd46pECi8KrGKBDPxKIGMVyWmKlaCxeo/4SgMJK9I4hM9RhSvV8Hn8i+XB82YoOyJTG4t/0TKT7JZuJ7xpnKH+oGU38xcuF7yI4Tugr8jJZh1wk7ZY2R0vkPKJznCznhzoXtLX7ByiM5yH+EbUpZ0LuwmbiH04CFhRegmoa+8YcSCmN5IVgrrQegHfofQNtIhViqbXugnfjFy4ekTKRs2VeiDH1O5tBRIOWO9EvoVfzDSk96QsmW9EK4qvzDSByuskaJcsTbhaqDzssOQa9IvpNyw3gtXe24utItcMJ3ITXgpT2Qr4eXkiWwpvJw+kS0k7VxQlUSZx+sRu5xOLhbYz/XJR+3Vx1vY3a60k83QRp3tmw26gS4St+g21LvYohM6+7hCZ+hVkgG7Db24pMdO6FmSPXagJ5dssfU0dckabU1TSVpsNzRxid2P9QOAU6JoqRJOShHKJ4wrfqcuxzizud4siy5uulV5n9Z5DCm7pYaCkphwiOhxmjWQSDpNKAo5Lo/bgGOawwJFFmqTgSIKrmMSUx0NdgOFevww7ehqUTZQ9IxogGM7NjAC29HQe4GMGh2I3Zo7llA0I+ojBSKLW/OecN3LnZns+37vUEdzsc6o9D3sfSvIKcqQM0rqRuT0oFdw9NhmR4EKb71BHMc9O2zCM+wGOpg1jg7B0IzoMpJHjQ5hA1V1U2waFzuQQa/g3sImiQ6BZfllamcURVBA0YqmMSkhYJNAEbBSDKlRwFGsM9hAlnxM5w0U6mkFBrGUL4vmSF6ETQJRG05EaBywOuuwgmVQNB7NpXPGZBal3+88HscU9gIZ1BxEIVdwKGJEvo+GnJaJJvCrYtXIkp4lRBih7C9n141ybuGzIyEV3napciPq2iNpQxm8jSZvTEgcbft05SlVyO3iowyZ6X+SZgntIKEMBez9puiB1hsU2ZHjj9bfOXEIBhI82RU8KRhhn6D1hhBmR4PbhQJ+oHZugYolvK0ShTOXeYqX7UVJvi2KobmFk4JFYSscexg4poOgebsItT/SZFd0KMpVhqs0I6W9V+G2MexlZs+JouMYQuHtMK+SWVWgYMOfjTzYnoMMXqBcoC/UHCLXl5slHEYXYxGlw03xeCr9AGdiy+ygpq3UnFygy+x16ewO1+DHhtYG9jdyJi/eacICZYpD04qdKKxedsZst6k+uuXg6GeK+Jfx/p8DncEf+DW5Tx4n2H3JeXkKfppuZrJD0bKrhYJNKUInM4H4el96K6HNPbNfBuBvtDSQLIK4EoVDSGVJDPtvgWrB963IeYo6Hjnw2JomWuJk+GrPa8FGnoEdVHlWNsDxtWfnwA12LY4GjHmoeQ23DctVRsHS4xoTFFIuFkFYXcMhErduWLpRE/94un2nlG/i1jqj3ldguNNXjsDruDDkCxxCDm+fcelKie/2Hgncjsj4zzyShQ4CKKIkL6f7xOR6POnhkMY1eCFYbrsqH7Q7Zo/vFpWJClhI/4qIy7p6Dz5IKNshiympY05GWQFAE3oGFG2Qn3ESiBwMleDp7WyoZB3t399E2CBnFCNiAYXMwl5I59DofCG71/ra9EhgMonMzMkgsrwe0juW8oUMdNWIbmPyYoUc7aNpZsB6rIY1Hamq4xfHDnICfL9RoE9YEBxUmni45b1l/e9ZPE4jEhcGfnRq4PC7ECR0NzAbyEPooYAjgRV6dODezYCi+rKYOzpAEb9TLK59LIEFCpGQmv8Cs2F1oCpV8FYn6BH+hIHtdV8AfAP+XMb3aQRV63FMqpffF/Dnqc1ionahhqKVrpJZpARk6cBgQydMx5S8dkALhTqSl6ED8/MmrcMLtIHqtEzDhi3ibEQ+gTMy+5jygoUdynNkWYl8Dl7xyT3y7Jjyn4lvcMP3LhSebo1Y09miOmrEcRob9kUiPedY8hRTA48ziMRZGZF0eUF6RuBQZXdLlpkoHKzM3wOp7zs5TpM+FBTwX2BL0KODTouAnxOYN6x57AbkPozPjgLVDFC4GQBeZCF7Gvsy/S+QYI4dsDYBqFMPBToocAFEzAE7qAiGrQEmfm7O+YLxevUDIraf1mcAExWwvIRlHO90tOu+SmwHimQx9h19dv+9FYdIXZXTqJy2zGp9xfq6cly0kFK7Be/TLVlE5pHuzWS/r2/I2J8tgukg4jBbthmDt0XPc2+/e8P2MU2gT6xORmcJ1xs1liChExl+v11L+5iTi8jPv9eL1qJDDxIFU4UNVWROAgPHI3Mk8KGSGREo6HwchPbToCCJ12+ipc687gtZ4uGkYdwGKKrZII6uwNiR8XGEAroFNk87END1GeDUCfNrpmx6vrcv045wnQZ4DyDxQC2pJWAf5xIZ5tvdmeMPo707bkOUKiYgrgU44jUcARc0MLUwYTxIhgITRJCgXzGZzDveEqcfeQz4nuxE4VI3u0lTGfKbj0S1MPaCHnSQQLvkRF+IhGhK6GCcQIumO8EEAhPJfzYetjgZFATuRDIMZkbIpdg6jsol0agEn04i4TxdrikmZ1MoRJrDSWO7pbrj+Taxv5Y2vU7FsuGSpmY0Nec/Xeefd+Ktp3sbFvF5oQ1U7LW/GqYPi7lHUkYBpgNvoGZOJwpHCXHO0DESmmRDTs20zZg1t6dG3Hd+LyjnvsnNoC2c4VTLRcfdcDBJNCTTbp/BJBjEFBrXMyxWF2IFDikjnjJtRFOoM0u/K2sdbCLgLk4HLUQwGEgCbvzLQ8DaxNdVFgbs/fFTFQrwqTolEnJc1N5HOfxkVckIRrj4KWPinV48fzqSgOXUgU7sZgTrPgWBLxtLHAXvq2eAvX71DMAjMCkmwYUroJPQ8ZhHyVInF9+onaiKOHg/iQmenPFgWiHe7u1hwrF8KNO71CMWwAJFETNccGdnaJ9iagwIjHrCpCeCrJkJpA9y9egkazbK+nWogGIW+FSGcuMrysueg/i6TzVnB374jDtIwP+zdPjLzfByNaWLmXumJpULJbaF1SGadNEQ3bpAG+aU0dnZeIxzBgZt9xwZ8YypPFdJqsdC5tkgJ5F0NDH2v5DzDrQEFfMibigDv1402MjomQmiMPOTkQFaq8vLMcBbqK0gO5v0ssqSm4xNMEZPbRqxL3Q4NrEBKAcS7QXwibGCwocr7eQZHYk93sptKbBDTZmvIayeLtAbW7lUkxIyUiYCuSTaxbjEzoyrYzaKzWDYqEYPu6gmQs2Q/t81eHgBWbSVk4mpR8gfSkilcWHQ3hL7pZ4Yqs6yIAKHmzI3FRRKys4AhvMm3tN9qMoXnLcWqwcWd8lzUeZDM99DW9/F0LGT6f7P9YN3vK4yqroxHPHJPm4p4IM2PfFQObndnHfvvvuCHttrCARfb+6ku8UGLs1on+5IOLbTKNc6atUDow1/z4qhq4SU5N5GjfzMtorTlCSEHaPGIE5ZOw4X3tnIJQFjeQ2xZqLCho1OYA9xMASrycDg3Bp67NK/G9ptzHz7De3k8a7bBeHdNFJX7AsZYLNnOZoCmk7nOhjUpuF19OP4vn3XSns+ioiwRmLs4tKwl8V/5s+8ya+rksT4a5ep9ze4lqIt4t36vED6UFRuhlM+jjCoDAVjeBOQnafZea2z5PLHIggCiuaOqhoywsfoz1qvhSpkAlaJDrDXhzImXES2Q+J2jGBvleFcSYbFub2c5ztxuZrGMaQwBLRCbFPHRRnsjkcMryUBato4XAhG3D/hrdQl8gwghmOHX5QDSO0ktoojrXOEGs3LC3FRFfISp9+/gPJVMTSY3V9mZLwKBU9V21RKJG4RFDOW0Q/WOhXyj2g8PEGt6s8VumiOgUhhCXtWzJB+PmPKRB/SGl0UCyicrBwmkqiKej3LFkKft4wu6OJLIkufLV77aFCdrUOIrCryA4hl6g4g93e8MRO+rpzboJnnRVP75oQ518KQbBsj+pGh9Qv1dLZr+udjCEO2YLWI91MBjvi1L8t51vLVUzNGukPGLu/PKR6uZTOFVnzjpJyHyOM8ZjL+zkW8WrRkDyefK0xY2hviAnyQ19qCEQwidSuOhHLHCAo4Cm7/2KZz2JNNXBpOH2BCxl/MqB67dm3/OeLHFPlcicudBItEHFeFnyz1RD48odx3PR8SO4jUUzvaAOJr4dLailtRepAp9ZfL+eGjViYvOkKRya4U2A9Z2KjWChZE5zs5QlBRe3OeXSgcLnVl4DBXNCk3NJqqXlj2YtQA09TWLLAXckg/NAea+kqzKTVB9/CP/Up+K3i/aNkOAkUlLj2R7vTHId0Z3GU7ppzmjIIznLzElEMe5w2LsQWe4dwEKuloaUrNBp0QFSr6HsECDofzNAUs2nG1FRJG62zINWStGlu5K0+H43OvAUW89o1nimCPbc5Cjt3lPMUk+6iRwEdBk8nvhYvDNlZB46FbwIfYfbCIc0iCYiNIbFtk74VTDRBbDIgH2HRB5+qzxdpR6Aw4TdDuqful1mJTYZhRmtAqDDAphyrB9X5BJBzUOF0WjbvCdgGMWpE5L+x336tQ9DCQidolhLFmMHgxXe5gJHJg8I4k3IXU3i7s5iWiKHjF60uY7O+vIQn/fNDnNIh9KHemMHpDugrx2utg2X9C0iQ+3BvEcW2OLjubkvBEjwKsfP56Oym13+ayTscGrM4CWm8Hw8EhtCIMkHJ5IypotQ6uJa/L/stcG6JgQJOPy7GsrEMYBmpKDDXd6hFvTW8ZG3W/Qq3r5t5MXZ1rAjWqZnRmfAEZiszTZ0FyGwGkJskM4Hayi95mV8QuDccHIGcnyFFg0vI/XIHe1n6l5H/QniIz4dvuiy1Y1Ek2Q5gsHuHt5Yq6/tNhsYtgy06vpjWl3z0VZifo5jiXeAxvu1nLVV5UORDaW34F3NydiCEtHrytVvbnnYphbPc0ElFT7ZBpywy7DDWoNvN8BDboCsVz3+nD1DZHBUFW6HweKc8UqQpxl3SE8CgamNBTJ0FGoufpqMm/rRzps0U4jaENFgFft8iSsoPqgte9IaOtkiX8ALMuz9WMhNaRwBRkJCobukAgQtp0KSykII8L4SjP2A3UPB7Bd/3RcST6rjc6OcBSoZUOhchZL8HS4S+01XfgRUNjCtyGFWAzXlkDo3vMlgmZEUam2VkKF/bDxd+sfsbJ9wQ7TCWMAOTFzUW4JOixwFGpbV5ez4m0DQ1cBK5SMgIWKHmbJ+fDGNL5HmWCoRYz4a7/4v+rs25EyZEWS5FEQgTh29LUoFiyxqgqtcmdnwamNgTmAwxtCmyb1XbnJ3xEDKP32xqbRzMiCSRjcPa3L0jlmHmxgZ8sEVbRCD7E8sPQ2J6NMN/A0Q8oGMD3wbj//31riDksvBjkBbewMm2eH8TfEZgO1W5PBc9Kubo4lrWenSdVygazNhJfWxSvWkvKLTb5iHoyC1ko3HCQa5K22ZyMRwLP8IybAj6tF7h3SKAWrRI5zMtwMNI8ibUpfTJqLdqCJaJFxPz/ON6th8jQ8KleVa3zlTK+Ts+YW8hMflYSXN41N162KZRk0JqyzzPiFdlLMEKPwVMwmviEXVdBAbuTTeWaPBatrsHrFWikxgr8PoZp6MwOjIdpzFEB0UcF2ivTuvZyOYVNGs5LjPP8O3GXDjRwsPJpom4/bTBdfY04yF4cl65S4uojI2DeO4FZfDbF04KrjeSFOHPttflyJXldCR24eybOHFGIpVkmCx1PBtGrBnsJNI2T4IFZU2i89oI3W1JMUlE4SGIXkOTKMHl8uybOxb0D5SqtdS5WyXjjVNSTYbYSvCkrGwy9oN9ChHequ5jawG3277nkjHVpiaSjh5JiomsojLXF/RTaq6lDD87k5hAMW3czKd5W7Jb6imkzqIlq3zsux3L1sPOP1xod3paRAnbM7REF3QwY8Q0gAcsRR04TMx5BfK7ARWijofHY352bCeBwydD6G3YrISoocEFwfDU+BmFjft59fYxX404HsK4p7y116OIwAtw23S2RLPbisdLdxyDh7tMue3FPSj+hF6/h0QFeLQLLFhYkTdMoTGABubZ3COd9+Z5icu3js7E4HSUVEFAydbBaET/X4UoU6m9gAJZxAUzFMgi6OcRAWLmO4nWcx9K85u7OLLzBViSdnjgofnoJASvKBvWUbUbHHu5zevISWhwjGgsOcbc8jxHU62jFkOnHk2ZS4FK2EDSst3zaCEf14pl0eFdOGtGeLEyOv5Jh2WGmTvbnacCNc+IlzSGvKyb9qqq/3zva3fPSfYtrDE8R1aFZ+3XPnIORiQXqOjhQZU1xbb/89OAV7yEtPoE6VGdiFAz4g9+DlnkxbzVs7nr24cUXlgfom/bwm04IeefhkTcNm7uefPj/XRXuOqS8eTjf9cvz7obV2bnmfDjzSLlbIawwUbvNPrMqHyTEfSITZm2VrLOvZqoqmms6s+j4z/QHvyuiKyKS11tDxZPsm30hjxlaqGM0C+eq8VleuMhR410fimiIScy6ET87qrp9uqi1LbYL0Wvp5kAlnAcHXqaU43qaau4BBckD377U6cxt15FcYL9wCfrKVB4l/PsGnzNvXRWSmPydmjJgb3zZpw0Gkg/+/LJ4IFiT4YOQGzWVks0oAfZu8YaiEu4v88Tnm85RwMmqCrg0W/Pdu/RzfAsnJBVqTSrtvqPFIgztNtwbvtxLw52weBO2ljNpMpcjb+siVLIOHXrVJ9cMqdXVwpsXw/fAwnPYzz2Z1c6GoHgpLfXogIsIH8o3fvHhy3SvLG8IEdo/zoyDrk3qxAQzUmfPjVI81T7MoxT8Nqg3eE1Pk8KIiVpfGV0iSoJPXbAZpTEqRMY0yuADqtdBR+0M+Yc0XRk8S5w8MO66EOu9gJR7m01/EDHhpD8av1Zf9oC/+Iz/YTITB0dHf9B64d2wjqUq7dOXeJMgFOwZFrUjp/tI59KwugfxaaaYoV2WEE7ch62vsJfwPvdnBvdVKJAGAikJeo2G0YVRlb5gOgDFNPikw7Lbp0u7/n8tfxxfw+CcNlhMPa6Mzh7oWSK7qpyaie9mxFsXgVrjuhukYwlj3kstF7DGOJAcMzu4S6d85XwTmeHSMOeQoGrHJmDzUONItDPleLu+14Qz//yC10fqDKwZP+3zDYEC6bioW4zy7r2ZFIE41gEOSWnyF7mIhMBeDjvsHxqc9Vfy+WUkRo8s9+uWuQd0K2f5le/yzhkn/Bzh6gG/3QkTH5x2wt8RGiZ2jpv40+GEf7MolCtD32JVs1Xte6hSEemk90Y/XORgL1fuhu7vu1HXsoelyzmJQtsXNNUA3Do2cuUaC8jJn6cSSU/Pkf/6DVOHHqI4VZbcGlrB6/ligzn4GE5ykyMDO+x8U5zI4sv0jfdRA/QvkS55JE8ifQyto+2fx9Dw0UI4jdgCne+FUIw5U9IY0baAyL+N5NJR8EIwp2/15lqg837nY7A7AI4IiEsI2pTPSpSnlymzIbEFFPH8sdesOjsYfRGedtvBVr66//DXQeJbgTXxBuTWsCi59fWxlV40f9j/+W8f6IiD9X+HhZpjsihKAKUyZQl46T7JbYBXfIuIaLeKBDNqd7qa7Fkbm5C68e+HqbdKmQTAsf23H1V6NwNKdj2E1Loy6g2B8RRPU3j7PLdXUcwfrMsLQ8tolChNcbfU326C5VV5XIbDg9Oz5D0UqvIyjsAovjCE0ASt34HWnu43+OtsQ+ak0mwcz+AoVO+6rv9CSU0FMOT46t60yz1F/ncYc2ZCdRbpDXt5XOYOPeJ5K1mxF3kZT4O1roU8jWx6TOQZjtiH1K/f4aF8vxNcF1mNRetKcQzCM4YfuXN/WBo9CgWdjMjd5QNM11FOLPI8ouO4T9r9GAgGI49v/HffOKzKMUK4XR+MqtRoo9rlk7wLDf9lMhb9qrO55+2II9pAya5A19hyEgxabJTFmyQjvsUvSOMeoB2D1cmPLYE1y0uDIreWr030XKCjaaOwD1U4q1N9TyPrA5kvDLLTH9HmyaMQ5n7HUA696OEJqQwFANb44gGMM3TEEdrvkKTbsKbR9bnv4F+AsstRE0Qv/FtlA+KYeg31/IK/R6OIfP2CgXi99sTSQ1w8rW+okJwrXerrGpLa5jQdwMEPNfuQCAg+tqsmOZsOR+P3nH+eaL3C/UNix8dh+1GgvgOvJXCxdbQ4FHQ02vtPw9sxwLaKlhgYmCcZ0vYwDEgnLKdidYig9cyuARs1rtP8UgbY3AQhGadaosGAdP5cCMt+KLydVDpgr91yEhMsYYvpuQHsfsEKPchErMsMZE0kTFetiS+B4sAa+gLL9maNAhYI6Dbv3g4JG9qHRLV4cLHPx29K/zmnR8f39Ll2NNiapmeeIVmymEMwdCvzVvEyncMXcpjrpxmZYd3dYHpo4IEI4DZnFc1r/n2wH0ytPz8fAye+gzHUEcK6tMkjPkIEwwMG78YSoPZw+DGhiNUoal9c5e4P0x1AbjN+L+AywKMN8hPoinhnbooHhGt/nSfy5YSyMSGL9Rofqd3vm9P9dPprUe5uLayUs2lbIXFhRkdrRxRgQFBFcYrRNjQ9rpBSRjFRS66xAdcK9iTpsSZKqIovI35XvcLqMGr7rFDY9jkYdx6tXPbXw5R1ypCNYAhX36+RDXa/GRhPS3Y3gBSAt0OOYV69pWi9CRRV5VSe6/0AzcIOR9fJtxkF4nS9pg2wYmsaEAx45xYCS6XinxCmE7sRdzt9KhaDWF10d2cVBfN5oVZIKqjMrYjDFi7wLnpF1TASdMemDbkpJF3XoVEKJRvB74z+vkNEf6DW1/sr56Zs07jf1ePeO1EK3eYgGaxzDe/4Mfo9UC7Puo1lAg2r+QXcxnwN5OKglSFxhjvw6WpGR/wo18VC7s9SND5ZEcsnC8/NOkW8YoX4NJLVdVe1FSNoQd8iCJjafgKlwSUioGOH2hLX5OJ8On9UgsCTNW0hnmkT9je4S8TvF8ppyOBb6vSX7FKE0Dr3kOm+UiWuXjqkpnzhynI2XTp5Y5vI60dZDM381E3RT5v2TsmPJS9uuV8p9SwZhPdNSPNLXZSi+5qG/mZq6QMT6G78Ghbi7X8YCZkCFhXidCBf48OTLKT4gDixpDcY33PGMUvTl1R93k6LmqdFbWdeg2jJDfoIh0CMdfVENKsMEUPu8GvPuyuZOqaC7mCC8VUw3o6lx3hV+YIsuTWDQyMvP4T4PmztSeTu18S6wt1y7eW3VbMf/MkI2Vvrq+fyEv3z2uZ6j/6sXeQ5ISXX+UJLxU1j3nN3F4jiKBHWWJcAzKxwRrw8AQ+wRqoGf1hTWjEhhYFFvrOptThEHOj1OFz4vMzNHMLLzaE/Zi9WNhZMBiil+s40fO/0HUCZt31ZdT1bN4VO7W81sQo62pRH0BAlP8bNKPc3Rw4gWf2iwH4EzsoXEf14GJ7x+QORVb3yVkd4uGnt/fOrOE4XMCDWgsN8A/uywbWfcJGVQZp7cO2wIJrx87PQ1QlXOwLaIDytpOYN7tAt+LqKBgcyEHs9rN6Ss3F8J0h51ClDr6cZKxAP6GHqQ9Z9bMILR9wLjqw3kL+ao131xLHhuzVxvXawUrYu82ObpMzMuoE5rVaKsBgY23IOZEsY2PP68K+hZehVLte0c+Knq5sQWkiFYCxljG1wHOvWpWUBeg3Je09SNjQmD+TqfmquFyO3XjlEHBhJZKC9toIImS+JxVkpXAVNtlW0UEcFd+7JWO713l7Jk+gK3R1UhsPAFowW2zcdKacJr1M9/oKu5VQAb2vpbmD+htqhA/JgtHsaD6OkjFVmV3S98KS7ZV8WCwFrokN92Y5a3eApkRXiWpREnHKMrLCC41cvvqSpJVGb/SnyMf+pQLPuMOO2BLEFtYMH8thhUWtGTy2gyBegkgB3TTtQGQoxLB0YcUuEBg6FPaadZK6hq9J0cvcB3ny1Q3SutJ/LABVP0msiHH+IKoYH3VUaU0TydW/NN46p4cLFeFR4zdLp6OTmV0EP/NrvemgHhWXz1k855nTG5NxFFLuh+wG7YEJC5OvDJjA5rdn2y0Byx7uAkuC/u489CrQl6ignSBJB5djKdPVxddbsDOfMPqY0SZh3Xmt6uGvU2EH7RXmXA7Gm0YkoXkms2xQZFNH+oNHlqWtFsnw77Ql766CiPhA2Ts+8rN1KadjLdGh7eN7aFMPLoLcJAszbh5nkycY7QY7J3CdrwYI4p6Udk9QAv6pZawmR5dXwToXL0tv3upUcZM2GvSJDXdfUyGDitfm55n6aP62AKzuGlkEixM5BXaxMx1MpEUv7vFK9Jk/K0hOO/wKEqvJytsitwbaKZRQztOgRSXVmoeo6Z6kKtWcBa0IBZ0K5T7N4WzsR9FKrK2ihrlrl8zzmJdt15AZNterVarynipUYIni7XRNXzAojIlDqla4OKVyHfwv+7SqfL/R0F8XDzTp6+l5wNZ0Gq91O2xveK7rNSF33NN53heDql81oSgLnWLaDEQOV777b6N1YpCgZr6rQ6mohx/dwluc7WjdKXCbRiX3I10/Gy1Zg5YKlTYxZrAB9g15QKFRoUbei6DSQLK51Sjo24nGcCEZyosQj4WKfFR3SZRM105qO3CLLE6ZoKphfCcwasdfF0bx7/gOMuHx26OU3MQkstI9zqkPFqqGLWshRXEsdZuHlCkOURRAvGSnMrVoCM52ruaqM0it+bn0U6NzzgkewbZlPxtMDkmPt7bzIB/+65AS8Th34LTAlCwBJs33REx+E5LgHzf2/Bqy6MZhIPL/QGPAjdIscWcyDKyxbvQPEOxSUzcXUz2b5p/0SqWSpgha1RXR5jTmD8szmGlAn+Rxwd/CNEd4e2ZvQVFol4ox9j3fHbeAjsIGazQmh6bUrWCnRmUrVUCRSolFpy2uJq4ZNpciydWqw11VTBCikWugISpTjAo+LxI1NRbf46XJPPeDTHaLfsBsnh9AI+P0VeXdRNSBUkQmaCQEu8xQtxbBMeaCpk326GXZ50XwUDt5t2rfl/2o/RmK7RMHDCA2Qks+aRwbTXek63mzz8U3vsRRG2rMALvWuf+SvNyKOS5ePYpgxd/jopEr9+3DcjZu13VvF+93aPCm+J3+/oYApL4/S2mgHga6rJtfp9MWV8La3rPJgX44lrixeFZb+vlICzTkgcSrWidmoGDEX1moXSfiBVuL9BKLOAcSIyRpzp4m1UieGwJOXNHqottdv+FYLenQLw85x8gRs96OCKgiH3zdQfb1STcAqOXSCJG4nClAPOWERIG77igTaUjZ2KLXa0dk3yDniZEDYX3kqOuxMOS1XRLy+xIG1pbMUQIXXPsm93zvKOtYuMuS04llDsPtYGe4UnS7x308XjgVaA88GpEh0eWVap5MoYVFXRpIOWEOn5LTRxnl146m+sWOnXUQBhvSMpLTpxwYgyrQiOY49xdhaZ17PCcbD4IZzQEDJ1bQ3/OmImiJO2BZ11Hv9sSNwzzuV+RML8kotV43L+/fDVDZI8mW47bwEpnUAj8o5hJhTtxJMFJdjutM6d1p/Uors6JYnfDq4jxcXm+oMr/60Td3fRWhzo/xXN88WfiO8DyD/CdN9RjFgszJ4vAC1zEFtstgqADVLsh1DnUbgeJbOxo7DTSPVuMzt05xBKt4+TkvGZnTOJRKp1+NUaD9WAgO7GWJ7aJ84g1jUcnUADh3iZiKtuRky0R6OFF1USIXSIMfcshEj2kSLRTTrY37Ll3FyvPqWFaKbwYlz8w4LsC69GUngvQnM4Qy+sJ0yIsbe1558Gp/xEyHgIjTfZQEYvCWodt2lB3CFl9TDpEfZJ+zvgXRwhLTxTusBVkStp+fD2bR3McnRVmdq5CmvR6UIH6II1k5mmzVXYdC84kxrzUufeiIdgvKhAgi6VUVatOC41MP95ogBly8R2JNBwlNb7U/CqqVDSlZrY2r6ZCAQea9z6rYpOvTL4cUwRWLxWM2IAInLa4m8xyodId4o7GO+x4Kluy8EZq0VtPCRgxoirO3nnKNyZWuFCT7A/ztn4hGzOk4s5StaQaGGrnrLueDq0izaoyCc5nMaChGMACScKqzTXVsGTwnONj2lRtrnWZBkiZv3XFSvMK5bq1OC+UwovUtKyPPhExQiT+/tnSdpXWpOiZF31xrQ1iFai972Ji2aE3PrHcQO5cJjsDHzIrXc4Mp1+GOjFHnki4iZpeliZgVO9P7GME8C4c4+H3cxYYmD/h2kTHadwP/wQCsnuT9sS+rzaDXH5e/ZxXbr86m+kqpMDm7jzvU2jeIgUipuGnc/OezyKeUcqzagOtHVOMdJSXCFuNfnq7CEI0u971neSiIvtZ/VG+JoGmVLTJXDO/SrM8qcscR6cFWcJrUEKSGpfHVC0TH/fDSCqtxjOviFaZknISVm9qIm2AvU6WDiaqVkMdWZUJ0s9eAwp5ilJV7e5jCxWfSEcbDlKdaFWigNl3Rsb8cB6pAHczKQIO3Eo5pdrEgPUntSboSoDy6HIFUKjsnfJpxdDIQjvC1Oh8j2HUTzYSDY6JDFOsbvwqO0VG7Vug+9EE+68FoT3s7y9iUkkE1V1mZcqyBEno/7cn6LQGpcfoDA9peaouATSrcoE7BKLnN/WueIaFGxd+kIb7pCj3Bqleu+OBXe6al3A4MYxiYyeT2eKLxVGGGyGsxtRxIm8DbygetdtKa7nAlKdxxVyZRb1IdXBwoB3D0abqg+nVeNCjuco1E2AW0xoOTbk69A1IyFHs4YMslRZ3p/I0M9rqEXt7AgKiUzHGNG3DLBKzmTJX4XRlTN15RM5fRWjhkTGefQMlM7J9VEF4TM6jaH7xEGYzdebsswuHsEdr0e8JGTKaonEbYD3qynmYANLH9fQ6+ejlQLTH8yO2sEwv8k8jpmtUFrcztD0Gc+eoOuD+94awMSV6cnvvidojH3ZssGphzBMYmuVNdZVU3ENmfk+mdSwrXJRpNbCXQF4IGheFSF1t1ocowFz2toar42SG6XeTXsBTZlVs2K+w9Lspgzlnl5bbQZ0kGTLRkThaCLQYpR9kzQXrnbhsZDh9bezURySNH39Vm5EKnHDgwJH3z6QdLglho9hNJv9ywbfqW7GcK/BoJli4PYmCNQykJhnXhjwdYpWzmqXNifNTwGEfcCTnJQxOdWgnFTWdGsiUi/j3Ox5OnlzI2SeJgbELOHIKidHq5aruqDPoyOy5iF1nuYcAFrogH6eV7S0j0sLfynM/7h/oBEHNP9YS6eFd+EBMJP0x1heSFh6bxJhNkn+aU0PXgJkIuXiDF7Rstx9N3QceV27Qa0DdqzgbU/ZlqGQZ4q3UL36Z+51v/BrgRrNLSLiR/+hgDpfYxjjW+miMCEu2BzkWs4gQCQB6Jv4cIfx31bvgedaxgbMDI1zbnEJsCN/PWpw5FWrM/lAlkPSdGjjFGXvKNlOTAztHPuSMcQpD5O83UAVmsg59cn0YaFWbUVhM/PmHixY6O8XxKQoMhSmCe3UBK0L6sl+DOSt+paw864qrR0bKuPzmoHpXbBZJbXSMbcu11mWKXQEqeO8lpNuHjEeFMZzJWTj7gr99O+QLFCjeGk+vROG454IRiKNYxh68gPDUZk2QV1HRpdryLRuvpiSXdwY0hkIVivizlrkPaIYku+mB/CF58rdwXC4OY5kp1sGKxfx6UmIMlTO5eLDEbncIps6tPYkerkCED3w7ATLnD0WJ7m7MXAclbXYuNRvXACRGe1Z1qa2QL2HJxc8RhAIdVr+X43Nivl5PFmMWF1q/TX+g6f2vx4nCdO12ErKqH96C8Add2ZxBWCAq9ik86o2g0bsn44j4vQ66hinOh0/n7ZMY4bga8y5YfW8mxOYvd2G8o0UhEOztZdgmHR50IvXHIpMPKqolj6TSWWC1ZRNMgfxPE0L2pbXCE8bbj3rvpcVB8wRpHgkvt09GgNw4vj0E+G4LoHeUrHekwyq/iJuT8MliVpz+DbIiWh/m0x/im4VZ1oIinhbVrsZpVqW3uS1yOqTlapD+X5DlhJh3+mJgnqS7AMArHz7Zz2BQjxhaxvMR862I3xGu9/fClKsrq4jn122nicPO+uSOsNGP7RIGEOlDvrf3DvwF4UFpkFJDHfTxUXKpl0yxL4nazbr73f3f60zEnm5e+gPLTa97K1mxAWseA/6H9EJiQmla+upBj56bEu3/G9fMNgTpTj4Nkb87SnP7jUQYVqAT2ifNQYuLvvjTh8g0qFjHiyrhGHrkhuPktMYU1yAXj89Bm4AJDnEbthTqkVfMWTRXpcEBSddK7PkHW2rXnobdjUcbHbGTKMKyIbJE2XujFmEEJbJOiYM4HLYxBnfmAqxLgU7j3M11SINbXgwRdy/j5XtsaFzzdduBmeNZ1rwEUvHSxbGHIBMEFNLQMTFTVhaR2DlkJ2hmZ8FfUWJIgKBrgguVcqvZLk9rU5/hxh9y417i4W6VwFjO18AWTLkQdQrWoh//ruoRMCPIkqcXcQzqW6H6PISvI9VrgqORh7f1h4hMRu7sk9JZO2mbhVQ80Hb+XROzgXcxWYsnbt+iQ/3LJmQHffk//E1EcRgUyH7XfXaLXz/ZVD8dS0klG1gJ6XKRerY1DXuJRd4EZqDuSnB6nJm/Ws00vo+9BXi+MsrEyr3WCU57tRSffQ/rq4UTbt8zu1xyMUTYGRRr1HGLwr1uYD0eFpxW7qYpnfXo0CBkO2nw4BF1CeX2OoMmMrvhja6z5g/hqagDNuuEezSTJe0sZuylID35sM2TEPfKh+tgMbGcT5wHdTvcaHNwF2wuSOh0YNLRQ2olDs72wHMs+7s1Ingb5KeUDC00ctS9JIsbSDDxRkrhIQTOlXetzlTgd3hd70srEzKppcFFyfD+PFGLBR4QYrgn12lRuh+p1T4kCMxiaumlDo7ZIsTLTgnmawFH2dd1axjIGZBKLnJv0+Bu8Ang3BLmRC9v+mMNxDo00IX4QG+7DQJmvuSzOhydq9aKzRs4mKhspuqA2oh7GQvHhLUcIC08ZzY6iV7ihvByKgWJcqo5ziuXg/DUHCPPYh7qZkLApfwJMYcP3fsG0DR9XeCgYsK93egOWrKAzAQCoOxSaGss35GhycSJdC7JvBE2T3Ag/fsOENTUM3oVE4eMY6+5kYGiKMNTBSrIQYJyx/aAgCtgLo6hmefIRwIJg8EWLekRgM3sqGkESmSFKZhAorJalFHKWE2QWvqzb5t2M7XuW72rl8SdoJbQkHRmiSScV4ceYDUdoG+My3s3wtXoltJM+v+WVrOZNqDSRNiglJ5hhERckpp1ECvyavpOFaoiISWI8egwoRkWQfD1vgvZFufG0sXB1ez2kQYalEA6sS4HZeEJ5Jpi3Xa73x7HoNIMSvQIEj9wqdyymoxLk3w5RLJSbzpqIoU0bVgGbbLsP5VhHMclh5DhCSwnpAAIKIETN84wzMg0oL/O3zkgbwawjcj86nm5GBRNaZeIP0jJCO8coQRzUdTw6v0ox4L9dCmJsozJ0Cj/OLMUjp2ASgZlAbT3YqP/iFgpLoRZior9Y6kabaxwn0DYxHOZzDP8g0D2BA/OaM/8xTNPzeE0qd71Azij7UMvVrWxZ4kSyeutLRbK1Xu1CCrpz56Urwez4ph+c/vAb2Z9PoYpqaFSGtPY6lBNoAQK22yDWJPpsJSXPup5XHTbLIcawioAxXRd4aRoCtAgFJq0+TWm0ba6r5TJBS4ZldGDwbGHXEzNUhijbOWfBG1OEofr+VkzH7gA2AJaXK4v+hbXBSLKbuxfC+QpZLFTGAyFRZFxG6Gti5oACslpWdBHQVISmpujhdB1mBsunQjVLpXesyp/sq2FOyEnbIuTiAdScgxxkhJ//m8R0ZqikJmEHMBmQBlB+aIKSmLTZG4hbifirNAmQon4FjRuhKViWau8DVaK1K81Vc9oQ2++JKH0L9GpTCsqL/eQfSE77Ngv3nJ4wxgTpPPyUIYywSf/g1RnjP9+IfWuvblEVzwvldtTXfF2NZE6a8wNaq0qlwpOd+oBDKlt0nH2gxyWoFVQBMEHar3whCVssLRsOj24rlpilHgZ4FT3uN4t9aUQBK2V6Bgx9f5NKjklJ5m6PV6hCscH+nwIg/ht5qkfVgMCr6ERtjF6WeUex1F8EZ1QZGMxSG+28QOuamF5zr74ykvi9Kg69QrIZs+TiYUwru1qM08OW7BLKVCuqq3pF8HCB5LDKotVelbuhJZp0i/Yn+rG4VSVAqsvQ4qtbzEHDVTRJ0CEV3xBh76uXEA6w2EjwpRZE/Zu/3Flijm+HcbFDgTLmiLXK1sprYzj+c8CtmWJsnp2avMXtGgRUZnqYKGTY0+PY/R45nQbAaaBX4XvWOCYrHEFcY6wGLRowhLyP8PolWbob3ogNxjR8+4ijHmq1321xHQiNm2UxDjyAYmer8YUw5kHh6JEXuLnG8F5tlsUm1ERsxscZTo1zWY2SNTzLMb/ytWTLkNhj8N2dYy92005lvZj2kH0Whe37edpAlU2sVuGGTyKc9AFxKDOsrsIdpZPZnsMI3ALWTWSvABeSD9L1qmsFB3BUkG0q7mzRI7VYiiQlTR6KxFZ1leA5NR7smnjZC3AbvsN4wEUhF87AbpdSe0YnbGRS+hMOqfxDROVP/WZ/4sh4YAYSrNO7mSDw9UP2P4a5qaMwJw8jpLv+DeYcpQSsiESkm7BN+K2S+PzuJGnaVMjbQpECr8KWnP78lmjwvu28WpCUcH+KBknaijRq9CYKUCD+KFQUO54S5Rd9F2Jq/jG/dPvHhusK4JpO0WZNth8MEk2lRd5Usu901hdjLy/EG45nTvC4StlFpxmyWsGRR3k1ajYPY4MTZtC0Nd0ngY3vws8312bfDrajDX2eZG5Uop6B19s6SrFd44HHBXqAdW4fFheXfLv57dZHucxnVLqu4uMscNfdTx4bw0/rX6y8Ed5hkBJB6sHkXsw2ESQ8TbbapFpzC7aAViJBBbW/zRW0lAryr6+pYX8VotvRU4SuK/nDyO4O8oi4cyLuNhSMR4uT9xolsM65QN2bID0H2siYZ4gMQuMEUd6QUh0VGu5sTGopu5e6ja95awqVpKGn/qvWJOnz1CNHuIRtJy/8GCwrsbPTFqOi1Xkr076/6IHuqqX3rLd0DnwcKsOJx+S/6rexQYRE/CkRnyeIXpNzzc3kxPTbMDO+CpzBqmXu4hxoDtufdNpQGBP0Ue0a5TrpaIfYywW1tSumx+63SrfYiAqoHxzFDk2s9xoEiWZ27Ql1sqJrSAT5QGT+shkE7Om2vo2F8IpG4d9mGNxYxGmNd6c4p7a1zththFc12ukphHWBPNEgC3b7Unr2L79bSjWewA3Cc5jJAThbrXsoOK26npi23680QuqLQ8co7o1igyBCnFh5OsyO7oXM0fto1Dkjyl2ZU++6Ytg5y/ShUufkxI8bMS4xqXrfTeyKJIpRytYp3OssoTTJr1GZVX3pTS3rVN5YLECWrxw49oiEqs5xaf4flhrE51jebrTd4RkLD6jndOO0jOduicoUAIFYtG4O0cfVSw9QJsr9IALJsh0V3u1CER6Bi+ho+QVuAuJOzxQei184QBVcTN/gJceCbYJbn7u41vZ4YwAFLw00OCQj0rjm3WJQgGJBCxNzJPtHhE65f2ADHyJtEnUKjid5YbGHDEHKlNbzm152D02/5lFxs5dyNGGvLatzeywh96mq8nOVKsCoWKFIA2HYeR/uF1tGMQFRwiB7RcKfmmAGPrNEHC/LW3xjJ5JbFvFhktHvzrL64OtUZYaBSsVPXeOfiuC02AglqmN4UT8+WGHOF0ZZgVggqhxGdVAXi4M4+Xsirl7eqLCInJ9oh2K4IHmt9i1ye5yNtnQq0S6RITEbWrgFSRPdRkgme0+deVcfNxcUgOq+KcJnNWRqCNTFQ2iQo5YoF+8Zrc9Nhmoa5YoS7FLOFjJNsj+IlxkXVqSRFR0fCE8ORLVPxXGXISIQgTTWG9tSQGXDnagUG5WOSdGSmOHmB/bmQJSF2JBY9woY6oDlyZEKVrEAa66+bzquXLRnxgg5UiiUsQUhPXxvWKo6Lft2GonqWkbPY1SArgImFxbRehMTdaT708vkv0JecC16oC256UEWZTaS085DEbHxgabL0tFkXBhbRkONPFrhaHX6NAo3AhzuvI4OxxRxxJFNT7beOSwiqCRMxoar1jPI2Tz69cfrT5Wq6uPBsfgfVHxbfBKwt9zWorVxZ1f6kj7WNYi2iUaz86sTnh6tBHFnuNZjuif9y+nQ24sGxH6tMsl6nXyFefi9+9RR68583c3P4sORKI51M9P8ssdWWk6q4t6VWSfAvjxb6mMdWlM7YMUQvDyKMZe1tqSplQMDNgjpgFkTaSP1xbSF0x0vb142qQnEOgz5p0373ftBf4RCAbSiP/QW7n24COnxSsYa8Pvg2y8ZQEMcSHlC1b/xkuQeo2MSjmU+TldyMaNY6svhxK40hv8NgXp1ABqaUgC929Lg40N5FmZ6Bo5i1DV7xsrL5x69mY0RRmVWy4izNxO1VZMzcszJh9RMkJXfd13BMuydtCngeRKvdB5bxyG1oMbA4Ib9H8ga2B9ib02bC0OiJZWWeG3CQscU1LvfTdqAT4pL99bCAy3YJWt6IWaFUPtiUrlFww3AKWuTOIk+6Ugruhwb3X8LsDdIOmmWYmgUw2qbRwF2l5pC1qx0UJVoDje+9oTPCtGP3+fnxvXgbmfTYaM88NCN7HqyjJybxBuL+dpSpB4pAjBDH1zW6ex+9J0/z4a4Y9439aYKNEVhLw7k/kdhDy85h4AdIppfzaiaK5e3i0nDM/BybnduLR4fe9aGbeowML0ZhPcYy31/OQk4bnaF0to6EyyA/zVXZMN8XrqJBsWUWiJNyWK43TFliMJ4x16mXRGxCpkjcYtZxUm4HRtaF3uSxo3gpxrDZo7rBQmCfPrpOg/XEcELM42boN3mF0hDKYxjT73cDbkNMfenAKA2LY3tpYt0ggYVPAc/UnCozWdicr8ciDyiimwGr4NJAhoHZDHL1mhNiILT1BAh0d6jxQHul1eHwIMuCF/5nfoSsrq4MqnxBA5x7uIxOeelSn4PFnLiI0G6SGAeOr1FLbKBv5faxJeJi/DX2zXfh90XooFhdEpIhCkhPYKUYtu26I2AVKc0Dmhizpot0IJR/GCGOUg9x9Qy04Lcym8fCqAyLB/FX0/ezJHDayGWjqfL5HvFqgIeGlfLSIbIZxIWR2DjQOmc2A7aDS6EnYdkMKEYPhN/tjFIdIAZ60v0qwPvcQ3I5chTKj7Kl6GCTULu4lIRDM6HrTKlR3fw5mrdpTszknwEtleel+3GawsK1gq75sBLinVrF+pih+CCCx2TodNe0rG2UFkNpS11wBq1xC2gIMvdjYEkcdh+JCl2k4V5gJWGpR5WfUeBpEltI46quG/1ihhmXX1+hAl/hvXaU9O8AVqanAipj1QueeDib6NfWkhdB2R9gzLmYtmeZw9ZcJuMpu/bppmzYOzyFzKNDrVGiwQeigRgzlX/uaAzSoWq0RxQH/gX4G7/1lstLlZ/B87jk9VqLuXohPJGctTExUJcQu0SeVL77olyidNvLTDtkZScd2Vr9aRkdN0Y0jD2cLmazNXRC46Aw4ITeAK2B9RtNiZrG3FYWmqrqlCWvOLWRESZI0I3KFDeK57TGuZ5FLawuCEEQHLFteH9oqhr3qr9bto1HP7oaaZ+1ZTGX5guKnC9M9fCOu+A78JdMEamrPyelrB/k9PduE4weSYECTkZ5HpYLDMXKjFa0RB6BFgyKzj5fDWgO6zczLmlF/8mcko1nsB0o5QCLl9PpAOX4KM7r7K8rC9gB2Y0+Zs7d/Dksg2bXKdHdz8480HiW5DmSsZTqauHCGvQiy1NgaFe8gzm5034D0mVJEek6R1Mm5Jkr+gk/5CaagNk6NmANIZFFENA1XuSwMphZniArGayZ65y8H2zBr28fUqwXKLe3OfcHsgiS7UQaw3ABZKbmMM/pgoO0yhc2fR+tP+Ar+tOyo357DseUrhmmYy6am0ABw02ErOlYio95SPDBMs+t0riZddvn4zamMuyP7ELu7rQV9HcXRxV+D3zY2ChWUErkqJO1BpFDouA3AhYBOveu+/cYuGgGa4Zga0HUwBfgaGlYjsH/8/+t1v4WfgwYWjAJhlQEt0MU5PJrEeHW/J1jTm/oobxckrk5L6xV0KQ0Ah70SDhUg930pRWrhxQRACR9NrqgC3XcsoXU5gIXmzrKyeCb7GqKMckakLyuFxrXFQ47jO0KTxke5CcKCNwLuBGRrZLAEOtWdWsOl12eVt+lFZO7tc9pOgUiA9C59sY/KRksPcmnHz4lrXlOyLwAHyGgtih31dQbJ4ZkvptqIH0FDfmdG4CLGM6BJM8cKDN+8XwQb3Xty4vWG2kwkqIEc3aoTaN4IoFgWeoueuKbygv8pEOG9HTkQsUVkoXNTclVloZuG/SbPekKaM1Ix7GJXGYeznV6nbbmEwoYo/ot3waKxE5rw/gHG99BpDabavBm4TE6k7vKGbSZA63725GhmQJic+NBd87x6RpwlqEqGIRWJ+atUEDdICVLBR2QzLkDFETYdcNacOFtBjrLu81JWVt3IXoZaMQgFCdsVhLqA0xd8rh3Xx6oBsRJUgSKzylYktcxGCL8V8roEa+OF9fH7mXukEXe6uBgjPub+hsPtNGj4Pk17KAyeAqPkFCgaAuK7io+dmIhjQgvNWLDRzHm6m//Xdk628PfI3jJbXAEs78r+eA/wRzJ9e9CM5Tt35VHtuFrdWv+dxJGPW2pbf8BYaCrcDfmuUOPyujShO5EsKriRISJmOkMt3/B6NlwbyRfpvv/YY1ngzfKPob8UDE0DpVDYeh5aJl5u7PD0GHIhv4Mceiaae7EF/lIph/qfFSHgvf+Tt/g7zCAZZybLCgUrfNI0Gm32Haijw4uKEegrQoFNqLhnWA1g5sCneIiQq0s+YDcHdoqE5GZCv2zkJ1N95unrK6+zxPIYUYbFdO/fEZvFcmjBK7fJuFuU/CMudHk5TjnkCWrs1+HR0OetdiOxKSmTS+CNwao8cvBaMlTY1mIT2FSKcN9wlRK+hMfWrPO7KqVwuVgWxKVIWe+awHGbC2KBt72ClV8oatXMKizR3uQ8HAkykM4sF5ujWNCK4m5BQTwmHXOLFDflLoxq2TF4mE25zhZ6UHMfeEgEcO2lye+B2H7JZKAjju1M8BLtLCMKfgb22+wS6vHUFlGGedcpiL8ftsaJw+F+8NoPV9XHq9Tz8Le0mRZypaw2R/Wz0puErrHTdno6PFrAj0OBnwD4IICHClxj10Ks6bRpOwDr+k5HYqv1xBRc4ORd1WwkqPEVdJ5qU6s0wqhI/QaLT1u7sBDUDHMwnPtS2lph/F8et5u6kxXswrwBZZrhsIhq0nw2ycm1SXh3lB4uMxjYWvrNY0oLULG3Uwa7vdTe8OSBZXEOJOOlXwZpnZCmAQpXZSEeZ/lsft9emjHXqItc06QQuFoJPhqgSn28seOLnhrDskEo8arqSh6uYaYGskwbTgGVQ+bgUAzA581pFPx1DEpThfiDfcmy+ESAJyOKjrMIgTXohziCRadIwkwpeQTAR6qEhKoMVrAK5jkQM/93iG6tiZGDYejjDwpMG8cV1PW5Z9dxhB/tw2gEvLOKXu259JE1abOUy3ruYDcm1FUl6zFFQtH59WI53rD35+xGXfeXbGJM4TvAscLHxDzTsdmm4HpKNAs6RW/BQX3fYCb1EODeEeED1ZMwo2plvVcUSfEnwxf7j6DPB4yKXfOpXOOn2gNoj1+vR6aY1YeBuXLiOODoNxJqBso7XAyqXSnt+ktgBkShB4DRYfX8XNzlxPp2zrlQPN3YLUEBeuXGYkFcU9vZnldcI1veE8RE4THMhpF73toV9L9TWHwgj3LohlS9GQ2CtazV/cmAYo2/rdjP75lFO0Kx+md7/JyTpHsFhJCGncGoWdGhkOllCZ5mLs5W1ytKpfNy9g0PaATAhRu4hXzorMSEdmxzi+hDe+QuCPRW+SIEap8b2UvP+NsiiAvVUjcV3HlLKWzvQIuFDoLH1PpcdP3qJ/99bIxzzFXFDHRrQeyVUURf/W4SEkfupd/pRgwAKgcRAq/WkJjauLmpaLcveUbP/jrfPaMbCOrNZ1URCA930TGuSYvB4qXc9rH+yFtZRh6xrF5FdW331CiERTUhDmEgvFAWDYSYuo/C+Lou752Fmwx3wyzRqQejHEd6MscPQnRaYdpqzJjjrajIYYFzCrcEHw1C7aLkoqromZH7fmhXFcYY0kXGLhhXoxJwVocMzOD5hL0oahi5Dw164wt68STHDwx3sAWRk6OFBgErRiuvPNSjk1y0qHKXqoPHc5mralPZHQIlybKrRCbLko7GWHOjR8okkVWTfxF9MoZIRcJFSbxwJC6lqRNiEmrkMsOys1gYLEw0EnJOSlkZhajGsR8JVtGpZA8mSwOWyrPyX7NXigrQ0MxCniw5dbQDcTBjKLZBBDpcUOCkAb/iKXBl01eRqJ9v8hi4wTrEl0QeQ4ujeC27Ye+VRX1XaJJ5Vw02azmsVII3AFvs30fM5ofUa56SL2e0oh6HvPkqOqbvMgdfvxVPUn9DD6pgvxujqFBdhCB3eO+aQ5qquIqiE309lckE1ws+stBid86NP1DlJB7YpH6BA6i6Y4rWaZuKgm7nj59tp1BXer8mmLy9aSAIjuPMzEjChbsRCr8Zooz85/n2Z94dRQYle0IOrYNnTCUXleMNMrnSeXhsWV6aNqFVAaAUNDBbOlzf0mlbsQT10+bqcWtf5nWP5DYF7cmVK5XkzzWEfK53ndCGVbr9u1NcafygccL+QGSTPQI3CR3iUX1BTayBhCYqybUZqHWLgCOt/MuCC3BgL2Fb/tuaGfX+MNfZzlcNLwgD6e0XikrQEzcb9aiZDouUtj1alrsbTG1/J96lh32KyN0y5LOKtU1fkvFJg9dvP5B7INvNYYTuaHXYLWz6WzrbkHJubrOOrrOX1xDxVErp123A43IJbccZyIIq+3P5dPlPXSdYlJXDMCvN68XDevjtyv2rr8IkNnQcF8Xs4YkZskP+o2vcMEXJ2861nzV5HuhgpJ7FckTDrEjaQub+gOUdN4hFCemDxWWttig5F19T5TwcTrXOxmhpkRMzZiRRpthH0AaPPtCj+wtU0MZyc5Vh7Z4vo8PoOdsALakmrI++GOe19o0vGyUzqDlWI891iFRuEJHaTZ8nJcMWiPN4CUjQxpcldfrB2LUicitliRGNaM0r/QaB1MnvLE2YVLn4cagI0YDcS5c3PSGosC8iffjz94LO5HbWVI9vL6qWTvN2mmrY211rEWrFq3a6UQ7nWivU+11Kt0V8AdDcGxf+wvdPcGKLIib0DK9qbbRwJvxybsLJCnJSe5KpvCXPV7SuXzDsnPOJtF3gafxP3IKPJwsDEPh/H6hs9APDhrei2qBnTrz7d9JL+l8Er2JPp82MUyZiY85brg3Tc8n2w0nv6jPN6H9Nhdl0P5oL0O/BB5kdMbNHfugg4eNLLfzJkiCi2VmzRBJvPGFfzo5VJ0Qf8MIPIRMvgnCNxdTiIlBzmSeoyiVnymzImJ0/myI1DOywRFnhp2/hRBuR7musYHcyiOsXBiUSIFpKKdp2fQUIVfelEcFjYX3pxPvvrdjEqD4yAdU3lF9gR6daXgB01rG1DUrx7n9m4+t5fcyazltZnNGLb+z0miQCWjbGIvJ8MNyRhUX5P9Md+9F9sqlECFdAbglahO7B9LLJClzuiTSE+cLG9zavLPt3s+J+O2fhT8wNsmfYtRx8b+Nmfd/ddXQM6ILFyAkYUIQJiEk9uczaAW1tNuVXnSavx5BFo3Zehc6TohxyK6gg0Ra2FdnpI5A4pejdWlxFLAJT3yObC1C+4VnT54Hdzv4CkMg6Q3GxhdZG7VKM2uV05/Oh29TFgArZfi0ZNxm7AxfKZxSXj95c0qDREffEWx6ZhhCYQ+B+rdp02h+qosC5mHoIZRnSIJtqt/bdODC5yxOHTC2eJFRxrHK7C92VWvwHu1LyfHb+/mQJoookmYQj5RnbFOz2wZZ3IpylssailXX2UrAxoDAWgqE1M3VtiHwQyG46aoO+JwArrDQQ2AAq7+2Z/XrVdV562BkXPAFBRbf5uh0KgLcgV8ayKdBpAfa8je4yKyUDpXv2Xk8skfy4eYdgjwXCijC1/Ep+BJ+1DktlUp6lLeIzXV76njWrZGIX+oPG/IXjsp7RPb2+O+cI3+3NRi8C9JoP01NJ78tDF3tcMEJjg8HM6Z+3j/e4VzRjRorp3f17iPGMsG2YPkq0EFt1zs0hgmK3u0ZRekw02CqhZV50wBcMhvg9uTp/pdRdhWiinkkDI2iOgrsqxdWLZOaDzBSWeZ1ikduAYVXCXTe67jd37q+Rp7OsTX4X0a6E8pEt/zVTfCjg5gLIU1cg7g7SfD7J1Xs8lRpSoYL6Q1MkxBS8SCcriuMk2F3GVVOZgml0PFgfa20yKgTNZVe3t4oci5uJF4+qU7nX0b9lRmLxkgxeajxcDUNFM1SGs0N46Lm0RYwMgjzv2xHgCG/9jtfnutYFpyhE1y/nFM8jIHl7s03ok1lQ1DoD+4Mjs4dR4gD3VTE2mQEBZxSAolHngyYhh6WbzSi3LP1siule+heMR5nqnj3ARmUpW8OxwsAjkNg8kEjKz9xovm+8iNP+oWbe0wNgf1Cm6nb0DTginZmyz0ksDW2V/n1vr5iFS0gPA68RcMzHgtKs3i/N9LlbJodo0qCxWKV2Eo9plwsHJOb+dzKMfzZTERFGIt0s/JX5Q/qFi1xH4wua5t+f7dYD5rs5sbyawj3fuW5SiCjwwchTz62hJk92j24vv7TxR9uv0z7+5gWP+GuvtFZ6lobR/0V/zxjqvL5WNJ9CEWIR1cY1swY4ibor4NCgG7ucD4kFv/2wYdarG4MN87T5QzTY40Xa6d5LFQ9U7DFIolVAekiHZaPe724dWz/7Wycgu/FuGnYO10GP9v+5828zAu27T3WtKdAXm913mkLBhUw2WuOzdQLtYHAbGwRy4c7sixH/Da1KDMRX5KMxsB7MW5fi3HrAOviMf8EqKAUvCocjO7hrv6UimRSeNl0381Pi6ZxU02/b4Mn/RrMx7vkn6xNj6kMaafQoVctjtLh4xbegQz6DsI1qMJ0WVA6SSijVZRYgYdRFn/+4IPgqWkM2djqNqk9HHeWhrUxItjDs01jhRnMNgbeMPECi4zRYWTS5NxxRs7Ec3EGwDvs2MfEARP/nv6Gfe5M43BZBkfxgbJugOXyTbXn3REfR7R+WScMwrB8Z7eV7bS8aMvnHM773JNwMlqsMxwQ4LrsFfE5XEGixLKLF/4T/gfO8RoyvHsHL6+LXM0dP0i+wy4yl0H1hSjdJZNXMpmVoZg3bBnwnU5Hg5CBX25DWxjEQwPtXy3jpuDP2/DDaIzye3rfoZL+1C3cwQPI4Qmq+ZZ5WqyWQqQY14IfkTV4T6cgy/PbNlBDrb1Z1ES8fNO9I11s/Pxo0zVZItyo/PDnGY2WXjH+/bhAMKt26KA05VBr9cnGBd14dNE4WKBOQ/A3e+5NZdDjPOiSK8FLXaDD6Yd1rEV+WDfmLojnDz0njT+aj4LIo70sbgmquZ4Ocz0na+MBO5aZVNjhfIMb7tEzR2aH8hbbzKETkYnlDBwR2cfEI858wOGmzuNfkwxuraaFbqOTDLFc+i7dzX74HMtClnZSUw8SS2c/4Wbsq+aDSJ3hAM7ZK1U9w/tIKJXEOtuG9t2W4w1Z4B85GF0erQteyov5VHuuNpccktl1MvrkPhrkf3KNNmw5Y8YxEIIjMmhqZXcxrto91aEA9zycDXMLnVCln8YKDK9j8ARBkLDn+oWywjQ6r4XBHEPKPa0oMhuGeAp65xgUe+mKdwyxVx2oOUK1/RJ8klh8pEtQN9oIwfqCnXbKJKz3k7nhLIk6MgcViDQmYoiFxTyRSi3PZ+ZFNjbGkMZEDDHGGOIohgEYYogxxgQiClVpW1pyWhFvVyHerkK8Q4R4A3KPk+TW97bJkxvIxftiKeMrj7kTWyxNF34iyvpuUOMfCDzmAwNKy9Aq//J/H7ZhHFDQATudEPGjLh7V8PgLD+66SFCc6YcbjzvsqDt/exaWphc8XQU37bD93pZnpB+HM3gdxx5eSSP5Bytj1G3Dc1Im6fU90vQX7FFU3i54jTXbGPiMM4dZ6GD28ExETkgsUdhDeR/3jPrg5ZabQfwIaokxdUc9g+XyYx0fvx7xN2afLh47Mg6JRWxpGlVtcYWH9VwzgnJK2IcGPUSJYl6FCTW4jdc/qRg/vm9lNDqlgSEcUVHGO9nhWZobJLfQROwV2+z53m6eeYGysWvKGXnOaKivIbfa3Z5u4KZpOPfryWeaMnckMGudqDzt4nBPEKnC3yYMOajT/0KkY9y2RkIp8zOEw+QZ27zOGRWlqodl61oyVscEOafSCKF74LPasL0yl4aChqFHQjTLvSRNHX2cjFh5cXvechXQbb2MkEJ2TvOyR1QfYc/irrGPtrNjNrtq9OduA+vbpxvTaxTlx4R7VGwv3F32iCLb0zYt9TQpiPYMkfFYGdQsmdEaxjJbk9v4y4hf/HObWYflik+LlAtJDznLjssLlc+dlr8MvnW53DJ7MIxpwXZnIRVVQDK1FKGRJ0z90FyYqbOTmX8u64U5tqtJZIIUtojLsDzs7RHQYkJpnPqHX7bbIKQIggmMjP5APEqMipXRYRy8amsN4/UEiwZkuAANK10wyUqOdnupYkN2YOUWXaJOT2mj0Z9sluRXnVae2B2MoGG117xEmjpxRTGB6WEx3aMot7K4SMXYQu09AEdNTE+rSt1U95BnPhiqc9rlDh8QEKH2dft3wnKbYGl//ZjhHAwYvcrVwuBLPtVXfdL/wL6S+9HuEFqNR2EKNNNZxaRPhQQyd/pZQDkeC5kOeknhoqxvcHwr6y3B0oIxPzhv9FwDNqF1ILjZQ1JcelAxEwDWL8KAHZYrPi1TLiQ95HzuuLxQ+dZp+cvgocvllvnpybUxBTF0TMJvYVPMkpH48lTC7ynwUNim8slNorEzT6ZbqbSUFCjohKExMO3oEnKNdUgtwSM1JIh/IaE1390sZcWIYSoxVMWGtKqwTvWCA2xGcU6OMzKn6E3W3xkwyIiZMN6JS/cIcJlV2VUTpIkAh4A/N6k/dTfUCCoXx3pNKfxaaXtRUbs+aj/rCrXhJ2WnRfR6/z1efSytfp8+P+AVZAUb8g/T3ZbbP62fsfaGRVvr5qLz69nim6D1z8CZt0TWAUxstunsz8qL9cY6ZRJ+isXpWtKaeQ+gM6CLMSqER9slRXhgdbVSA9TGXac9rwtHcSKLOJdmxk6ptWqCsgRcrWbmFisaQDl0/MFaHGSSB1U3ROCqKZVlk7+iwXTGC3M61B7Au5G4ZhMsX/HjzZHdp7h0Gt3wimXd+z4VLPwvezHY65fpWgpt2GwbWvOsXx4p66yXGNVA4mS4aEF2e+JWFriTKfxJVcsPkG2VhK7lAsR0rOr3/I36xiCeWF0fE12zS11V5ClpLlOiCy0hCz40ZUua4VtMcrSmTsedUpvK0EMFAkPQAK9mWCjEojPidjS5WdcLdkF+AYNqFbNFtgrvQbfIQXaVtY/YF0QZcjoWvMkcS9wxrERiutLdP1Ay63y7sjyVfAdcqGiQbkovF4FD5/nQA5eGrqxwPrSf4XWZLeQeAE31ERvBKk6GrhvD0sao4fbpKZesExx11gcz11zPCLwFE0aKTaUqT9LraCnVYbPeM3VQAtJ9J0OrWfMFyEqRwWL5wz6hXShsOWBtpzVbHNjIcoeVl5C+qno0AaarTMXRUueOZwrAxyC3g50AKsA+Jb2DFfWIwdMGszT0+EIjCn6ivsJq0IX4FLPJHb064Bd0Ypxso2InXg1GWfVNZUini7jPeDBUstAKW47hA6GR/O0oFgYwU5wahHeB8SaAYr9Nw+8NUR+aHyLTKknYkALA04sZm87WyktBVTlldsWkE7WZljLOSD1pezq1pn5XZ+RunlMmCvZWi5z+XWNprkds0lPQ8RfIpBiawFbf8capPee79rjakCVWTh8WKtgNxIYtoV2dU00chNT6hCrOKQi8ZenEbUnHN3zzbTH4dSTRiGEedDx81nRC4A1uroxklFx7pEYVLY3+vPLZUluO84Sot1Ohrdz2ecTkCMQPhJGRhoWX7gxse/3Fmk6Dm1X0ztjWcBGrbcc5j3fQYjlpWraoQtxVXn3CocD7EREOrkQ1W3pSXblzpkqzkBDu0x42YLPlQqZotD1QqequPw3iYZMW3sfigvQmT7hXCI4RPbP2NK3hZVsXyLTdEv0PSzlpn5TyjI8YgQsOjkMNTJlydTmPXDwIkoiuOdPJiI/QyPNBIKK9NbJ8IBIFixwnIXW4KZWhOjJjNsoclCzKUIGzyw8T7DGD1vdv0bI7LbtPESlKorUEVyxOqgBRWsL2G2ykGrFXgcT9neu3by/cnBgh2LTFeBDuDDfRaE9XfaAFAai/PZPRATslRgxocx+PJH801RgkJM+ApdsOQ98Fmn91RFf3uUnvj1WNFEwKR758XjTMLmX5z7Wj93KypONcUfABBS2OjeYfTKVaPs7eBhAV4JUor9tBjD4eTtUd76JEIZebnGg/X2iB+2v6hSm+LlFyK7qlOgwdN4prVWkexVxHRbrHh+7bYkv++8EcUnszzzIk+7ij6jrGkHylg8jQWafyIFksoP2cxq/jQ9uz4PmvtP823VESMnQzH8n7n2z+5izpTun1fvdJubnaWRbfx5u69HKaWDelTNlNLUckLgEMU+IhIVPSEQEhn7jrPygi5AQsntiEDM5ZXpQpJY6JpCKLUl9goBQzux9GPiCipzHWUBqY8tdkXVLNgDf6X/dA2RXfrbHuoA9sxP6/nYzYPrQ+4Nx//X7gftf8/UWKwzd38WdW8u+m6w/8L7WlTVZd1GgSmMwCjRUmJn+7LhNcHUXvI+r5x+JhlfEGmvtxWI/9VUnvPJlCaqd9lP0saf8sXdmtejvNydSTLVo6cWulLzL7wGeR7nQli30FfXr0gYSl2+BGuIZfXh4qgJ1yWuRg+IABSGYK0gInJjBPf7Op0J9nKAY1h2QLzavESJQAX790GM3S6NmcrVAegw0ODahHdy8olI3mJ9KFxtBFa2goP+2tpmnqMC78i6snOU4Kfwak4Jf/RGGoMD1yvfCOj3sJVIRuMoyO/V0/XBlP+g2lV856FsdIxWxWnZfncJbWRj6brD8CFPBAHih091SCB+AMnPomOA8clb5YFdk0JdkJF7/pfOOVUAkbd1z5EaXgUnmDS4opwWfauMY8AIV1x/k6m1g/G9vU3KbU/4YSnpaOdAxgY/qNXZcZ+Idhxq7x0WuonFi7DdG43N3HbfBYHdWC75J8h9lwODHctvcSrKii4sgzcbCl3h4XNsDZO66YxxN72Ew2sI8hv8U5nK2cAgt2DIqd0vQGjahTwpGxzNj5DCv/IpJpmB/QIKeRgEFp9FAQ00uEsRLcpVymmLxE4O4Nn/gssEvk2RCz8rbCOOXXvqKUppsFig70h24QkTwjsa/JJJyEL2YKctondRt07oRBcO9jBqN6mmaB66pwZ2Z7JzwCDYDmsRnU676ucc/tCNNbWD22vBIQXk2ZwGBn48apdmL5HwBDMeHtlH04WEDX2sluRF1LvMZNjd4dE2KDRMJKFRlRhny0UJjaa/VI+yRiOSbwTCRe1eUxKNLlQLuCZfvfKLai4Ji0jACE7QF7A3oKla0Se5FHthylGYLEJDdWlsPEAA4hLrPVL+0K4ie4D0gwef2qUkaAn1nzHZufVf6KjeHxU5jNiiehdr+kgxtqivJ5ikTROu6NP/+EycJg7dWE4yki/9a0dHwCVeIntabwJ+xZkqUtAVovnaUHdkgb5uzSgwNWl9u7Q4lqkU76J6dz8LYGv/cdUSVcFNcuqRZmUTv/YSKrCOUjIBmfavpicFzmCdBaFLeqCRzHDF7hFL5D+UEnimqEFggoAlJAEFBEIqBRXf/T50JBFaJia9abBgWcA5rHTRef6L2k0GTXky12mj78CqLgv/Sy80VGPml0yfL8wQl4gvLQQdzd3vw0ywqOaXH38HEDMGa0hVRU/k/VzeEleH1zkMSd1hIsGJml+9aAhk152ol9OlbaEW7ely3xbBruF1BIJyMqvVxSw0cG9LGrgbsRK0qCB13yy8wKuTOrd8nXWaQTmR1vBNg8lfQpLvG2rvN6CQ7Yn9PnAwp3JmfvGo7c6SR2CHSsDNoiqwTEw6uneSX+TjypmMOOLTCmAWaD2uMW6QzdJHiYmBc9fKa+t/rBwmNZDGC1OEkHFhX0M8CHOWuG/d9YP1greMbivDufo38kb4eRwmya2nP/d0kRFaQHfV1WgcgbWt5DJHFQWwqjjdhtI0pSiPF6Ygyu1cyYYg2yLyjKdogIrGzt0PVlMusQyXhN8E7iko9eHqP/l8ZKZQ34OiLZDfOny/+YqOtGxnfF6+NMNOBDdPsEtcW8cKjhuDLOHZLjN2eEKhNAh8RDQ7N13uBQ7fGry8wKjYK+v7E/sxcZthxaMGfsKBqztseLOajGt+8cE2d3HveuFx4BailIE+gFDGC79tQGAENoLWebULKti7IVuh26Eht0mIDkFmEbzUj6zEnK/0pHmQedlNqRXEuFyiIkfCwc6kjDze/HaFGJN1SUuhlaBonhUG3Q/DPw/bSScLdHgb41AZ4PZApfQq3X5JjCgSJuHwkNpuXAlNTaF1HhTQb62haYNY4JzjbejV1rVv0v/dHvYUw+lA5zVajrxYHNaLUo0KoR4Lme9XjazzWXy/A1f1Al6Vkcjype9nFAAARynoIV6fpk8eoVKT8gY1aOkpdYtwaiS/XVeNhFLGpo6o4Hd+TbfnTtmqihC7zpccydO6egeC0D6t+vjOgd8DsFNYahXrN1S2mcB4x4YElr8mUvcq5urJT/B0jC/jw6Nx5KQ852phMSSt9aYReEdqvgngsIpr7WkHLrnQZtFCM7wRIjgudtBN8x/tTcuXpKC4LRhxVxgFqeXHCRpMhUuKyw3P8qcliilPf5UOuWyo9REp1uM0GM13M6AEZYVlkqGXWRvu7JfhQwJRzDk/fcNaL9zjpuCyJcu4Y7210runak1ppkaOS7sofZlbvIQFPtJR5MQQGDFcQHNUDWRDGkMUlrA7z8n0s94ierIiNw+hm4IPCZuhj/OyITYBF3MFfVwZEsnY/sgVnbix3QiwF5xnY0sOnIcR04PCeRo9Qh+7H3S/gzxTKiHygEDXqrhdMMIBmK5VNpCPQNa/zyQBWg6CpROBdtRoJ406R/ak4dloRA0cUSuM7HA6Eyg/8hUP9yZVzPzaHkdWdpx4UET+gB3W3HjXSAiQWc9/h5NasKQauQBghvGmdu5mZjwNl45FonoQIDakZfmlZ0gvbT7c8Gks5e4mpzk1yAwkPmh3eDf9qA5aF7P1gBS1ZJCWAGm+TgkekUYAyLlkZ77ND6Cc4vFOc6fSqBkMAKIhPLuAR71ogLtS9QmpBps/JG5b+FsGJhkDQhFAO5cDH0DD+Sy2gFVE5AZZ/pOHr4HRtax445Ghpnub8pBATjF0FNtwIlhIwYgJrZv1vYC+yd9LV7FHkVhwjZued4NiDa25MnFrAM7TlQOoghhDVdfAHQIvMG1+9lMvfZ4OZczvRRpDza/SKUsEAPUmxbpJeAWOwsnXs8mGcG9blxv+r53xitc9u3DhqL0qiuGKQUZRouQSRYYiHsgOMkh0qUUM5ePwM7TLxPo97pwRdGHk0pgVrdYB+Vib/EnvTuyy6oK89RzFJvK9FJGM1LQjRuluwoa5TZbAMDTnPystENTbp8KdtibAvBg0jWN7o73cyRxTbmalFtNasrRKE9O/nddyvwmgy6BKEPKfbQW34TxMZpDJwP5j/HzjrwmpyYmxSXik2Na72wNWonBApNbIW5dwvGheCme7TUfYM7zhfxbagxeTwWA6+xOAd87xkJV3FLQFQlRopB7QowLCO6b/otjqANfElECixmY5tVkH2fHt8DPeQwo05C0PVjH6BDclvmH2HFqMpR1HsWBPZgU44XThIZ3H6E/O4oFXomAA+4V6M96QfW9vGf5HRT62awiKxNf96IgT75UGW/pWgPw3HWC1BUfhQ4QlLhdtg5PxE4LHrR2BlCaNHayM3zchutZXQj4goWVlyvhRELmexcvydNeO7CCiEOJ5LAEltsFiIRvmUi7zcRNRi5SkDcD5dTfOOkc1YF+BY4zZK8PozJc1rEOpYKWXgfKt4UNplK5ug4N1ZVkuIdbPTTpkh7nzSwcbiyL6t+1o7McV6zg0N4oqR69tIE89D8khq8ruZUDKCnhW6gS6NBE6Z9djXbxngfmHyv84nhOwUxC2sxlo5PcKXklIBP4Wggs7SV+SDi9ULi/+M21J/QMWMEzxwuqoJ7I2KLnMT60A+Bxu39t/TXV5SRdEfkDSx7GSM58oRvgXG5Es3SaztnFT/lyujbigLConA603yBl18SzFztoG5FqlE/+pwjQfEiihGGchvsoZLVnvvZ5XUZdY9KKS6iekumUzPJn0XLrg7UYsYigUuGGOB6EdroqukbMZSmmWBz/0bVqRjze0WyHfozUxSTvQS7ExLDcwOWev6MKQ49bXJN+cS872w1iEiqT3OsHcU5iMJtg9yvM6jBhTVuSQIpZ3OMQrvrqjLpaJuP2WDT1Fd2AsLPFIue2inCgCWxzlIJKjt6PlV1hSeD9/7yQyFY7Cl2gZDPsP6iT1bEQueP7kAhoRoXWFnjO0C5ts/cDMbGFwRZhZqcxkHhqik8jVxDEN2crZ3BKJeRhbLyTAvifYGYx+WMWbDK9cxplvNfRmXOSjIU21dxpAij0Kf87wo3VgXjziAo3iBzRfDMiGHdB/jPvOQyL+XyQKMOLAeHovi1NuFDYU0a02sqwdHYZTLAW/2bzKOCpnfOLBt7SDASEjGQUgo7zQOh5qyxjXCQ5fUbCDClhGOJAHBM7DSlka4rAkFFOBZG3jI8x21dJxiwclSakgrccFbt0iv80yqI8BZlcnzJvDWelY41AkrVMFHYR6lbMGt8DJJIV3k7NSWLC+EVnfKU0VPUUTYKSGo6JcCwz8QfbrxbhDBhK14iCN4yYO223ht/POvyNzEZUIoXYuZL8QaR4tQ4JHb8QFQCAF6kcirSKL7K0/Lv7zbtvGrJw1Rgb1FykuRM6oxdDtuzFY4qYG/+SnuFW5AcISbXKchA680wWGGtBdzHRwWm+p/pgGazPpLiW+U0ynGJkjRwJwsZk1VpOh7goghxfZdovFeyLgxoZcR9pSiyERx8bjf6glxhdWIhgXHBZn4bi0AUEKm8ywbstNNyUh7VWhAQcBUrzvdtGkHr8no3t5dWYqVOgrm3+DlVHKYyPI63/UR6b+mjXNMaFx2A2vg4nznEhL5yD/BlkiEMq29nhc/uPrl17hPbym8iq1rjwgBtj+/dABRAR5DlYfmIojyPUvqHM6P36zkNy/WJnoAV/Qnw9ZUP6GYQcYD8ut0yguiOoPhsDuXfC0eisTolFT6pjaP8RF1vajvjKopPgCRPL9gYBwpRuWgArIFozfZZ2M37MDYXZA3wRBT3d0HS4A6pZOiu70DuQ/Eui08jS7Ufqw0gjC058UdjjqcByuUR1qnULCEc4zrPRzb8MrRhiprnxOKH17K9mP8bDP4WGp3f1/zLCICenSfY5B4p4zbEWhAufDCBHCiwgb697QWgo7o3H0ypz2hpulkUX/24pp1bYLEvMJUSBBjAZPmrMsiIxBShXQ3CLtIbI0q5GB/8+NofQ5rmwQAmjz6BATqvCd6Zk8g/xMvklbU3/4b+cKqmAcT3dqPpbmZ+0HjtUJJMhg6NSOpSoUhdHSLkwp//8k2HPLVrbTb1BpFSi52jYrVsi0QILkKXxjS9RIZJe/4AcKTK+m6TRVdpPrj+EBxbkyrel/M1mIVgUZFYCC3meSDWpK7H+DEPr9X+3QptwC/VbiMRbaH69XtK2S8AkrxgwYYqPCr4ylr/wpGof1ehDnMovYpNPy+wC62a2rThj5+DQSVwyUGm8fSRITy3jnv801znYFgc5PH6ROtX7DfJxwOUBgk2xb81viwv+Gh548LFYMkKmIAxrxYN+IWGQvqCc0fqJw10snAZ295cTUOFesFybO2y+quOwWNttl46nesrRetsUOvUeqY4WsIg3/d31z2A2i1N4C/4jxGRpyM/1rb++FyKAkmhdi1BGm8qz8Xmz/+WqUkCmeXAglP7S/q3pn9YCiYY0G3aI+rEmt2UXC+mbbezp2WWSObbVfa3MTb1f9JM8rqcU4rZLDKd5JSjdX5cnGq4acASJRQexegUGBtGV9Y5HSSQkV+N8mSmQNDx8zBAFeCHe9omT3zjl/xnw6+4ua+W3770/mf9gVDu9mmexVSPQdnyS5OU31ozyKsEBDhiHei+5MY4uamAqReMlN5ALuxVw3yQsAxMOdd8Mt0gPeAt5khGEvQfXcJpvEOGpnUWpsosUDqsW8FKWpsiXNNT+KJNysBiw/JzvVWeJfX+UTEf5lD2iv6r8CiPnMJL+OeQQPJRGTC8k0w9yhtnHDcec4qXvXg/lzDN3d4PCfvt6xYMuTC27z4nuNWtfeMCfLSwmLLrmivOt79uZwzfuZo5s9wcmiwArX7aHcLun8SR0fDqjRyCmHWNq8UWIqOzxnwi/8HWK3exlUnyPXsi5GjHXKTxDAr4N54+iih3pR/i5gXbBAjCJZBoE1J8RacYfHHJgu5O55KF2pPyF+Wem3aYGtnDcY0ej4ab0oeDYXetfKd2duzdHbbzpHE17xT7eeIryB5+mz0/kCqGafuDrADvNcuSLx1B0E/43bPQCRYw/Us8oEAfSuw1LtEf2qUrUsq3z4OIbTIiQZcvsLm3mpSMLamsIEFdMEKwh3A8izSw5gVKPJv9OG2BcnNrR6xXF7T1dQiPUlHIiVmcs7M0KZWzyC/4kkbUfH1oReEcsRRORqKYJGSWXDg/Kooylof3pxmdnHM9ig4N/87xbYjvaYXosAZqehs3Jdruj4ZcIINzxKBjV580yYQGUuy/aNfYU5UVmhYwreQRru0N8xC1Rly7WenSEQnDey2XYa8CITcQJE+fuMKnOQt6xzU654AvT6+6/gwYlABBMNoJsIXq1P0NLM+y3v4cpy+wmxwXsgtrRxcuB8ucg/3bQUfmuVPE/riVdiXd8DkMaX/+ITsd5P1m6/1xTWHhaAhJLGWJtx9jBIf+yQdBTmMkcVzme/yXZUgjcYgaESPUXeCe7aOJ9uyWPGr8sZ3Nt1/O2AFH+loWUvT+KzDPnMyTky8TMjTv250z1G2G/67Ym+qwJ//Qkpsfzb+jDMbH4BCYzyWDVmrspsQfMP4gXkg0gG/xtdXcT5SsoMNLfO4wp1dNyTQU9vM90dZw9nMREWan057gCvgsP+Q4FVkW7rZK1gtCyeY5DjKAL5SVQFAurJAMVHBTJSbu4L1BmHXme8sb/nXceNX5ujRvqeVtOsjA3bQscs3OGWnlGG27jJL4t6GXwBa4+/MRjifCjf/GL9fWOM7ZDqNCJXob151I1MqGECGYvBa+dUYEy2ToPTvU1kMeNjb3qhcUdNb4pEiHldqI373pZ4kNmzF2Ac9N5XP2OLDSMpU5JTd3xagtgc9tHiUdyEiJprH0eCPcmm1F188+/ivpnw+JqlQoHJfa2ttC4zLCfF70jHItW4jeLNIN1lmNrHDwFalHbyjLx4eZeuL8Ie8fqW7++cruCVB4W2CDFY07NHwIaZnws0a2CiRB/hQzN8LpJohtFPYeImojNY5EoCC+kGc+XE2H+Ni7+xHrssnC3Fm3JsrdTVhVIZkF03CLJ/QBsAptQHCe8zLDxIwLK1sfniSfbYS+Ad9RoBNJjehPfr9mWiFKlZQX0GBM9RMflCxe8y/RIIQLVgX1QaE5zToE1UbuJzYUtj3wggkdwBl1Z3YZUhpp0sNBZZrRoNMzJTlucYkfxaXPoeL5LzTwjn0EP0MvdTApDfQroDNuJsDFPT7Duf4Vt3a/XSaNjIrWCnG/rBNQBtmybQrYNPXBbjSN5BCcsU4Q1JWO4nhYqGvDM/VRElUqdFu02fW10welbS2ljurO7Zijgy0m5J7tEVawvT63M90RKRqXySm46jo7PdRQbTe3ePFmVguKb28ZEFAzQIF1CVUEgSJXGIQ55PhLMNViyu4H69rc6lDY5rRmQqYf2iqLc28gZW6aPIiR+RcfpWHMC/lX0X/8lv/llQ8sLJr3AM1mX5W756kQjmIsClk4sSwDTZphCDlClUnAVXxh2cix2PcX3ZSEvLLyyoI7xOzm5NzuZF0tNqj5RWvnjOo5VdLCwO8SbRMUmjXHyi/FGosYIYUhRG2WqlWFkKg52bJon9ivbfVOccfmzKuqDLFk6aiJAcbam9rTti+7SiLpcflFsoW/nadkr96UtllEmp3t1VedfqlWmGFsHi0M4WkD2rzVF/dK2ZduuSphR2YCBqcr5PW0XoRF+XMdOi5WfbAV7AMRHYg1bBpLYVIYthbe4A8pKjfooWlWh/Hp0Q4K3z6G6wh/SGIWLjJbINnzNdsbmJwEoE6rX9rbDfMKZQSiZ79Vq3V8kVUm0d0JHIcMavc+YlQ2Nv3ScySb7T1XurLqZezjyNzuh4NCqEqPmaAebwkN8rDCbtl3BsDpeeGPtgqEmDvDuqXtv3bc8SyMLnVvquQjA412O2WstCcBpDsCfdxOiosmAluyu3B7ltxhRVKRq32YjiOShxE2/xygqUvD7I8DCfduNxKmFTaXD+76gPjDtwlU8r4c4QKES7n7xsn+j7s8Mk59+0/SYD0mdWddRyOwJvlpwpGJbBkcd8zwcYsc/vcXWxg4bmdbwx2Fzpr0tnXx0s5lzVt3eXJXd9Z33G30/9yBL+7Mb5GIoauX31VNKGMy2Nog34U6lCkxOhZsifLLu0H/zpk03Kfitn97NKxB7833q+CtWJIQuLu+XAMtcbOF43bi6W+qa62Ld469N7hxeaHM3TmZg4LctEJ2YRRpbOWPf8Tcn6KtW8jVGWiwt5YPu3NaDi20xztu9ii3212k3P428OutLnrTgISbmhFQcWI7KFErjS05Ks51CCRU8kjqZZ6Fz0TEaNEw8Z/PpPYv9zpL7+QmigLQU5aGyklwCKqSjEwrpp84rddT4wDeMZtD6chxYm+8Zyea/wCEpQJrnKTtFyJ6CV2wlRVsaGWfMB2xH/E2hag7GzseIMZO7T6X8HcBpULuektFCsa+qKGx57Fc1d6zfVWE8q5o0YdHIbdriSLZzN7DZG1rvy2QX8JiOUMnTSnfTkWFCe2g0fJw4o7+CSdoSmA5vaZdvEUa9wBmycIHCXpMX2yD8j7NG87PiLiLKEudAaBjHZRY2bCCqSr6+NhyZCK5SZDZqA35TOyG+CGF6HsaNUM162k4nFNXs6n7MUacMld31nncK+KYZgcvw0b6BQZWvqD5qkhJ0rsoSnmpz0IUj3G07AsSxTc4DjhWZyM0zh1bPp6mUaZejjF4SlV31nfo/DTQpo/DNEfe5m46mxVMcI3qyR0hVYzQURgbcB343WFUxn/e5SogIjKBmU+vkT6HI0z8YuYNzWqnn6+TUIFdJEf+/LJrONiWJ+HPhDX+6rCkJ6ZbdveHPH9lFF8I+cWxv6ss2fbgckNOuwwzcxiNKcH/Tcu+J5k4dR9you9bkL9SE/JBa0P6t1zp1XT+ZR5YMPT3kJ9yJLzjxDJFG5q7pioFm2cpQMAckUB9nmZq4r8TbObj/3m2Do6cVAYU88bt/F7JKs9xllXmSamVRzbAukY14OLzgso845Iw91gDBDO9IhVgY0OGLSoeLEdEBRZJvZIn3guukeSEbgQwkzRYL4OQV6D1+cs0b4MrpsfjBBhJozAN1rp+wGdlhh9S5kCloxTrhYUyEGtCm4xelv+ED8brd1w8uEoaVp1IDQ9fnEVnD6JIlGP1rh4riA1j+rllEs/KaPjvV4pF1NBBiyv4Yj0HW1fSiwwDZWDknfTnJ6KFvJrbhm2MhCKhq+8bXNR3U3VlDgR996uNQAZDCTFXGAK6+IIxRXlk+ny0wCar1OC9KsNfQjCH5LeywRZCy05ovpa/ExpkB814UTuGAKhHsdzC/3jAz2upQ0yPm7b0b2G8LAywCzC8S1O5nnqFAG6E5jr6J+sa0suiOL0vXZiOWh8vRnhF5KOJDzdKKy3jP7CpX2Nu45G5A7GRRRUKpr6+2hwKkkRvb1G4gwZEcLm1uZfWENY5zE3c7SIrH9cq3ABKm1aGwigyD0vP/3yE4qJ1+864DqmNpBe6MTROF1qGBr/rGX8A9EtKpgT05FDqBnuzk2X5Pgi/kiXq4eCVMgnEukcmJSPXS/u4/Ol/4yceBkXBgkwwcVLSYMWkpSthdV5Rye+tzLTNMApeI6i+/tHY3phQ56gVWfGgOGZfTYMdV+cgPRFaTGuLJo0q26oqtwi34sJEAMngURS2/QlVS1HtMMoWuO4GE0VErQ5U/RCZBLy9O78pBrsAbQ5iiqALOrG8IvynEQUsGUEuNaVnu176PZPuPaQMUeO5LzVp9spICrFQsimmsXimjKA4/doNAERcCFwxQPjbSn9VwKNpg7oIkkhNAqMn7wEyGCvArQDS/ZuGnPC/K7x7YCohkyb0i2iG6Gnd5kVQG7XXxhsA9l59PXyP/72q3B9O1beFL6+HdBensHfOFHrqrb8s3KWb4xmBER2PCNXnngisL8QTqRDQJszYViilCBeso/VWA5B0XT8+XP/+dKG9e5HFf6pq5zRmmYiFP801g1wa0wGUjhUkzhla/wslPFlfGYcOvvjKYI0TZOH3hI3cYMLnncuYsC0ipv17VpIp76pQd/twq2asYVuLeLfbW/eMGds6ZPWXIektjS7zoH3febv0ScL+magmRGqgt4VBr/zTH9IHa1c2/rL1p957jy4dvXPy1uCNkZFLv10xnyW8qoZl17ZoemlS+C8C/lVQVTrS2F6/R1gTPLkFdZjMHyf4+8nylNjInWEumRLGHMccPkeboymmLobF5MVYiqZgqUSlBhI2mrLnbvpfAaVE/bnIQTiagWdPwKQ+J+rZFSVw2n/paH+IisTWhYMjtYM5OoRkzg8ohmP/RwrMz2Uao4wknsOB33+R0bH/8sE/v5BBckI+T3Lo4L984IdkS/9ny+ibiYhP9lKZZ52/slbcI/xfProfZddUjM0S8iRaKwhp40nukMyf2zQFva448sEM4sEmagk79A8P66FRalgbauBSilLt2kFdpsp1f/nAuF6RptIIQvyN4i5+XpgOEnplbEsERBhLzTzWzLYmf2oCWueS0Z5H2CBnTqmvi8UnIrvt6j3Gz3Vj1uROHG+SJ8Cg+L0ZlfKf5ED8XUF3YRWZ+Y7WvX6VMPUdd6vB8T1JB81O2AmcdFrR0ulLXcDpuBvyHl8vD9jydl3QeY3PPGitcW5SNnxrFHnAJTkk1nvnzTzFVmPX0G1D5FgAxtvpke9KOosR2J8R5R70ZeomlPAXf3MC3heWWvNdLTkYLftWdp2E8IJiY51EaZl4YrAEROpWX9PSLCkpXZ8NbPHPMEPGF3H33M8JR6iQRKGX/naRsET9y89WBFbR4MeP+LMvGw5MYYtIY3FohsPu0VqnCrXGoxWh+egLC5fcjNTqs9es+w1rS36+rZIs736kUuVjRCF5r65iDlF+DATwAjVBQDzg1xxlk451p/L8dhiZupQ8g3KQt9jkwsfdB5EUwzIYbaigjKrnGLJzAz+K4a0uSoj6CR0qKyhYGMgAgOf+mo/KMoUUQLff43qft8iAF3UXjuIHdXdgSQxzvnwnnBm9cfxTFnTegs9D8DxVRYOInoWvLeFp6SD8NY1sOn9XwLG/bZ4WpW68sXNk6rrTqPzWnwYfHKnWZXWXZRqaRON1B/Iw8+Jd1zR33iJE240klXuYFF6Dj2y+RagVadb6Rlqbv595jr1wZ0J/lqRKSrqlrXRP+Sr/pteXAa10w58bs5CLhpv316CWs7Eqb2eTpFOCuVo8A6pmnzwqFns+p2Q1NTITfpKvRo3ai3CtqldGYvTdUzwhoTIQMxZ4F7NNDLOsRLHBtJDvsA19rlO8Q8Ij5jMJEgDX20pSM42n4sS1uy4l7bONg36gKyzcuh218Gt6Lxy3d2J6NCGdRdTHk0MCQ95lKrM/8PqZFHLxw4IQWGqoozyXSY1rXNZRwf5ObiZnDfS1iHgDhXJEV2Y4d1J1JFDUbGFYuQINkVzaErf3i+M9rPBa/ZkPXX4uSnhbSTcWfH5krmBoErtuKSUj6x+TG2qd2+agXkiPch64xxe/dT5gea1zJf1BuzIwXMwYvNkNY4G6yCDwJNRHm/6t1uTZ71BJVLwmjci1YrpbuViE+XE3qiqaxgGY4KaXQtJumBybRkXWK9UR7GLrUktBjaIXi5G+XioxyuRoROLONZ8peB3qq7kN0DdjtEOPBcIT51mUfG3XWo7F0hAzAVV6ScEuSox4bv1Wl+iizFCVTwE5V3KeyI1ikwzVrGoUhqKGAkWBXzgN4efakfLuWOObtEbJ8DJGGeIfgIzLCGQ7HcKPhiuO1e4puGYGZvJkxAJRLZGu2o/cPAf5lovVME5wIR3saqJ4vNoQ2Vr1wa0PNbjXnxKbOfFESgXOn6UQ2S4rWZKc5BzLLxP6DmWbpSlGgXoLQORPr9QO8DqnnyvuDt35otfBDX+3zPJYCIyDbAQeRTtTVIB0zXxNQIA1GPwerAPEwjtoQ0UmvELGMbVFHwUSniZlZ2iSA97db2O6JRrwSR86gBavaST0lWZ4dMjSUjEyMtC7XBnOKeBscy8aHWsk+GfhLaa24cq5t6RwyMJZJHJcZKNtLdnLZd2L0HoRzM0INamKiNBy8T+2UimLSi8ThadDnijLhEmegtbmBElzw7C7QMp8D0mzXPaF55CfZtTUbO/GMxvfre3Z451tpBXobHo6QRH8oyJfCQaSqMFRJVTlqrfSerx95mQ9qxen7s9XwVtWVhwv4EbBKWzHi4zaPO/8GNG9JzqmecvQL+H9fmio7GDkuGKV91G66i7zxjgj19g+nNwtqpl/74E3w63Y3sX2gV/5+uBmbNyy9frdIt5vHaNR3oLM6Wn887sIezDaxEZFmc62YNlf09CXcv/h4w0ML1ynsS58DUVMdjM/UgDFMkM+L/TaIrL2osMN+jGCB1IldWtHu9Pt2BaMxBUgvAWzu/YzIA/sjPgUkRus51hF+2ZsCpNn+9LkAdYnt3UXtHT9NpOQ+T5yObMLR4wxprtEZRmhUsz5mUL5te/2iArezWacAj+zrfWRailWD9egxZr2ZF2dwLjbTYlwU1V8XR2ZIK1QMdN9eWJhbK1rQrECUsnUm1EyGZSCvWQoPQTWB4NaVVkURGbou7gTOaNif4Z6P7nn2iH+4M4dv/lQo5MEKuchvtIrNBwVAjlRIHnlTlwoqXxJB5Fe8e2XTIUq9s0xHHdkeWgh5f8GhEGa7He7PT31guz+nsgyx/2Scjm5JsxD1Wy+HPHt+kOdZkvdIKxUip/dGHqr2sAFNguxsXyzoUgGrxmaHHHgoVeml84otXywX2qr9ogGt2hyfEBvfjFUeIIsUsoEfKO2TlvpaqdR5r4mTKlwM7L0GFg17ztzcwTYiIR9WuGhwgmFMU7MRkNJGvjY1AgPy+CCKPBczpOX/fu76Rvl9UIUs0atFrZtQ3QaGdG0urkPLDzsp/rimNDkL9JRBDgLdv/EWxGRr3Kc3Xu+rbZkzidGYExcdxUp/diYsVAmkf+RMFw2pumBVwjz5KmxYgB8ONZZzI99Effs3YFG9S8+HhB13/dVVO8/9jp+V2KzotbZpN7lNpPq+9s5DeV5A8AOsv37ZayE5dWevVtWS8kWnAN9v9xTeR+wRnfAUJEaWTtQ+AKwes0PS2XW7fxMdkkHRLBlaW8jITUpGlfeUGqRvWUzKM6YvzNn+8hrmwbVhm0n4fHZ61cK++82pJJrYF7THQ4Z23PcSVR/VSR6vS7GNUFSmFhjD/BbGZXNoyWRmMbKB2DITyaqkXpaZ7WbsagVGHApLKKSieQSuaGqcY16DdW1U7Tu+DgCdjRcV7pPYU5ERtXPbxSij49Xsevg8UVXKJVSYPGDnm5xCAqX+mlTSr3vIb2VmWF1t/4Fn866vIDuiwsPeXC1lY5aI7ik73/SEMt+DvJxSMOqzd4lPQs8japU0DGUU+Zt2j443WyTfPhUeis8amLWzuNIGI0a/4hlWmeshkeWZVwZX5dxk/D4wk8yjeyjbOEcgVM8/iwhP+0/Qxl4fzJ1Xv069Ja/EY28x6y/I2qHPhfFZYIY5B9rvmqyUsqdt5jX416IaKeRMX0AiJgIsS7aztFTygl2rixJLSyBPj8qTkJj/lItN6e+02JSafl2y/tlXAvdeRv2nYTBKPexC+PaFhtKEGGjZBW/U6XTFtG5FSwOsmlSwGG200Tc+LgRFImGGuUAJfqqkzlV3ahxjDcPeindNhcsJfnZ3bobb/JcIzTqPxZM49d5Dd4Zmev19Wur0EKzO1+ATsSS1tbNxPl9Nsu0yZrlGaYwzcvKrDjubaxzV6hEIO5VBgHceHT2w9dq6PCoNmLjMdMgDbWw2W48QOdwwdaqLNAWt9vbWO9E23LMqDirnECpNZTyIVB1xifYdfHkZrPU6U8nZ4lk3emXwGzkdjrYNq9/ern0lRLSF4mz2DUqQaN1NXgNuXe+KubafEE3QMGbyBniNe+dPdqLBsTPJic2zwWvKGyJNcTcx2p/P6IIeFuXaDlt2rE3SDGO0fO+RlLQR6+KPWX70nV/j743ycK1mfLQKlXw4+c7HvUv3sbFRtF63fRFhKXTQBoje9Q+HN8bZaXRNKbsv8gE8VvJgRUpXFXA6fCsUMMkiNwhmKa8Rpbt61ajebxtMS6cSbuE5hyfndcqDKRUFx2FTNw/0X7T+3/iDTvOUCdouMnx5ZaKbQqXuPd7V85/Q7Z0PsmVdC8ObqI9D17mutgeEhITDbpX2LwvSWZRGnSgkqrS1VCI7TX0LskLtWHtlvc/JrCyWMdkOwlfs1yOZzwaZQ82vZiJJguHHJcmjkSM8Edn5kw3YTyqfn1qkvqtwlKrhVwbWS3I69ZGZShcuKG10m2b0Rie7eLCIBXP0XTWC+hY1TI67Pj6dE9uIMVQGnzEyJHF9o6to2grmtC1jqYj3brYuGdGp/AY9KQ20Ol/15UPhRENYpvNw39nPj6Izl3hSqP9ftFQBUOabhJU6TDtkMBiscCbgFvdQQqKOj/oZZKoNGsrzyChfKtC4TnJeSjHNMyQR3VR0C2GHwEhes4c1LFUEpQwdPPCL2wB1lDB8KCZWbXbt1NYyXCJnBXh6xPad7SQBDfGvyqJlgv0/3efspM/XAA3z/+PjVd450nCUaD8XYo0M/bCZZx6w+OvnXsDPNtIW3Oj3T4ZiLWM0mGUlIL9xNmtoZ1NO5J143FtxWEdLYXGZjFQHxT2Ypr9yvf6wCdntQJTSal8l+IOFBqvsvLi7q9laOhcHmu8xiIPKTae1OHD3ZP4dK67SB8SniBiHvDWLDwV9HsFKRR6bU7T49YMbZdCgQorhxcWLUtxCGRUtB1ERBKA6qTPjezKUAQ3Y0Mf72xqlekY+oQFsAUOWKKAZ+fAgyM5Li/IioC/48oIgsl7id0SHkjvJCkGHV002SXW4Pz3Ni2jLZMsGLMERsPdQMY5DeHRURrVkTtEleqx6K+BME4wTqMA55Sn0/3agEhAbye6K6AbFP7CQyUzvD5z/DGCQyqevFzKWOTJA7hdENoBDzpUhyoJOOor6hewgnEO2AE7QfdoiyvUKLlZacyjOX1m7KNMrVQhOrJnDpFG5QObwcF1mgujgGL1W7YyNokrEQo9pDjyMx4NF+LpUbQdx4aUf2QaW060xfbsSNIOi3Gek7MiBYC2xDQSClj3irN7z6wJq6QACaYpkJlr88SXikoTNCT8yv0rqFxLj452xehlwCVcZh0RCEf/u8q35PAP0g8ObSCMMZj13RxuRhUkNhC29vdaZDw9Y7p0Mw7z7cdToWF3yy4dietuuq2pVb+emUJSZJmSmHKI9ktu3jAnny6TMjGbmpPH4hQfTO3YtPtzP+eos7MsCPPZrz5PRn/2pHC5wMdgOrc0+ruTdVNAf1inUsZJj1wKqdzbtqj18oGvPppaMQh/h+jrIO1ADubAdcZ0gOUh3Iy1KDwvXcdcjZG/6UPxG5hqEKO018G1o3Rg7fkf2emUDT//F6z7M4Obu6pBu8YLwCcZt6I3xzqRGs4otb4/Rq+Fu8kj2w+SOQwcNgvQClr8weMvk7zD+GgNG6O3Wl2fbGh8ZAyGFxfH8czh7Utp4Sf9/M80UCVq91gbVGt8hCU4O2FNdmT8LsSB9cETn/SBft7gLlMSsWkXn0QJaIz8KrRdx9S5i3eU7kTwI7ubMB0tiP59wjTjUrCGydnVyamiNEjijlNOAz4Jp7XUbt4dbRUDmm0UwN6vL6v2+24ilDhJDAdO3ZuQJpFZKp2YRlG5IhqT+ck+j0DXX8SpNnh7VNc/h5QeFpgyZmdI4P4VPnsJOrJqfXr97kSrovmTo35WMUyNvYLcCw5sUz6slbRtw5rIaDbe1ihAVxRyFOmpHO/EBOPsmcB62y/tkKyQfzalN9GOASJ7qKvAXzlwUXUkvgADuuuX8vBs01U+7kSmmdhjY82IuG/g+TCEr8RBc9t8RfEnkAlN6YCkHURJU25deZNWfpDU1Fc1h780/gK2nv6lHSiScgf4Y55aj2wPtfdQevxmNP+wYXI1Rp2E/Ds5TFP3VCjYy8N+v1oeT7qQ3y5G02d8QlF2QRhHp3+NiX8m5+Rp0nB0qO7D5RyNbskyPhGvlNrPU/YNnPyU68VumaBQ+LcSpRoeX1KLmLBaZJHu8prv8ktE5ipRRxxtu++5EO2IucbfviHF38nOGDH17UVQsOgK3uPVJmRsnOX8ikx3FLa6HP0WftW1KHnRS82ct5PbmmkX211LVXJzS8jY5zE/F1n/vrbJWiG95Mo+jkiMljPktevh64SWIgA1UKWdxCaUYVVrs5OVLA7NgiSJx8uIP5oNf0IJjb2BxzH5LQsTBKqDntWxw7OKrzBgOoTxv/ZiuHMph8LzRDQyJ9IlRZ2ChJIIhzpg4aoDdygIKIc6bZEegZJh0lbcpJLrgUagxyszPWJeMu82XW2TAb62W9FEqFxjob0jLEAQOUo0mbl/Fs2fixfLRvx3Qq8ByMibeE4KtrWAZKjOC2AF+grpIjPlMY+QS6ygYs01AR/rZ1Np8f1aTuo9k7MbYl5IhWjkByujq4I4MNq14KIiI6HVUaHD3Kk9QbIwX0rQTEcVl0cJ5TLCJMXaKFLBCIwbpRXOek6A91NTwku1vLPhojpJGPzoqaixI4te759F4dtoQmImr4NTbl5ifkTjI+lESlZH3+zsGF1HPkuq772ubh88YDttsmtszjynefi4RgfXRramC1+/iFGhUk3GIdctPIvHuddhkHe0cbjGp4svyd0NSf6PM19vGtH/iqCFYGvtX5nmWrztQsXbv0XBY97V2+/3X+H7/j3AtmywYwM2enZ+9bR98vTXNCyBoxJ8LIGZB9wfr4+DUnefpcjTHNwbbSlvbrpZ7+tzPK5n23CBUhwq26GCzrCPIINF0uSoU7nyx5uz6O+DRvELF1yb9yl0poSbJZnsEIXsi0x82F4vlogfm8tZvWpvrhA6AU4go5yRlChkShMn4GkqEcEWHmyS5GkZmiWBSEVm7mjZGJO6elXoYYQpAxPpc7G6ZF7YF98BDXBE23jVjuBTgX+0TpsPd0AdiMRWNxTMP3VtGGvaiYDFV7OU0GfljJ2dS59TRuerCIbfMfGGqhC7KFWJr9Q9esjUb07KC1kgiorjDfXhSgJSJPUmjNEpT/eKlFVRVcksf5zXbs6KE1F/+6DFTSoHqvJ8u/m9n9KjkgsputZ1E3IfywQxkUiwrzpFkVM1rWrLgHiqmhKQJyxRGrUBn8Kj7dxRU4z+/9acC0DobKoZ+7yAKLQ7esyKPUsovdxbN5boAMWJMZ1cR8sJnBN5WzmaeGmwhiC84U+OgudtOPx7h+OPJ4pp8ObzXkRbQEhnq700SpVCJ4FeEnGcSLYU5zzfOH4hGJPDwJECEyRLZFCFZQ1oNPHf0HdsN2ZGwL/rDWO3UplMSMPCQ3uhKyoX6pbnkxKNpjJ9NGW7lpAsgjBKQlizgQU3IxGDraBpVZJjOVVfyJqDrSQx1bQZ97cZVZTZAZitLX+hX5eSrutF84e9w9qYomI0cbXNzeAiDKCo5aGmsAjcD4liIueVHAwQb7R8bu+zWfTQIE37lW6mOSK07h5U7bkUw7mCBA7EVeLzVDi1I0OnnFYfNy5Z/yBwy6xCjndyuiW5sfDesQXYs8A08rT881PtZRc94NM+2DgJmsexlvfKHNua7QVJhA4l3GUXn+7NCnkaJ7cCd+zC60fQFjAL6FV/Bns9tmdjWSmtv3G8Hhw7NM4O2xtxjkKRkcBWqiEkGd8CiR0APQK+9YWLKG/DaMzc2LZBermv1dsfdmWtJT+GyQfxsttUzFai70xhUQZzHDv/1qk26EQ1bjy+UrRTm4Cikxk9g3MUTLAtNWlYXJsFFIpfUES+sxCXw7VBRGS6KUsVTheTPBHhrHGIUwoWWzbYT0iCOd5Qh9v0YqHSYWy0+UljYRlh9SEOZ/xPhnzjf+aQxdbAmp7picOXfyG3tjvwAEdtq7fd7eOcjb5+aDe5DiREVqO2Nmr7SVudq2Uh69SRZQTOjwfRjjfWZZcUtjQq22ijPTPEHO0VSSQMqRftItCVQqsRzFiyGaMdg4pJH39l/NCsjX6PrnEIZBjO0YA0FbwSa3/wq6KIhDpEJlH+oyJ3V8jo8mqjFB4H++xRbBxsLOqC0GrcrBjVK7VrSlAWwjBszAYtzfWABQ17eVzQ/+Ny6/LHKGtjaTO4+K/AwctseL+1iwBVq/3UBVPq16Pap5J3Vv2jKN4eASEbK+QJfmjDHnh7Nie/u7uxqqRNOP96PnNNIlgNBwGhNww+Sr1YeOPPjJmTdU8QKFRY+K3WeDVITFNBsdn0itTa6m54OKWQ+avdz0nmQb6ZqTiCm//qMCfy9xvV0uBTatm37rF9IWCgE0WSwUFdWVZpw0NFaSJgcVK4I/sSe82XWrSM4astSD27aqO8j9Q7a6MdFvGSaXad1OxTpVRlyBAEmCYFU8MlJ6XNBIsJObUPd1WhXyo7BAgfJ5PZ0/cJUV2iw5QUbOocxwNF0ut9qAa+bvYD2qiZuNb4QKo8odb7yDd/Fc68pNhJhUvS2vwM6FU3Ph0k206OKLTlDVjKwSWKPMyCi6F4RKvju/a60dRWAFHXqcux9uLOjphLHlvLlCYsJ0wDRRAT4B65OUZ8x+bJJ1a2POEE55N2r2bay30Elk691+SGE5L9NE4o2wTzSPLQauYUFDUX6CzEU2iJrgj0V4NIgUEKhbKcYI813VD2SXHJVRK9C1Or2uEIpzWgotNjegjGw9ck87SCpLB29Gd6r8cC2FQJlAuvXSAyht8kj9JonHDZZmSMHh4ASxY0wyz9gIC9tjQ3Pa8S5q5OdonkXJmwm6wPmJ26CBqff9UqV9YUld+x/SIhpYRwcsK1zpzLsQ/Ocd4f1cpP9dWXOKZjX8tUHzkTjvPB6D6jK8MDmtjr/yL6mS1JhQbaJ6E2CkPqKkxNKb6qWTEcQH5tWosnTabksIftp8GmEpKOIyJo+BtE5BgoFa3tCaLQTQ7CWhXC6HcJEtAxQO7nyThJqdBp3E8YLIcmLJ7S1VbUKapYWD3HvFatCOlCfzy9luFWZ6rqcYQNlYRE5zScah/daKMmYP2eEk3eCxdApqShu1z2oW9gdUbuA/KILNKMboFnHAxnsfr4brh8yN1cAnFygXF8TtrGpDbX92vARsWWvIR/PvHSrDyLXtSI0dNQe4vDF8lCfV+sTcBdYL+FVCrVmC5qzUeZSc4PWBiUOdMqfiBN46YfAQqLnZkSr8opJLWxn0CdMc6nWDC2wqIdOKidZONx4uWZ6fNeW7trRdHssDajZtBiFDRFpNol90KITdpGbmxrdoIFF2JJfDS/FdKYtJPx8lX13VZuYzCcKcNhMfYsNbQP5i8QYPdjbZoevgNXGgaHLHmQNBhxZUw72sW1edLK2hQswmHVGSkQCDnpyGvwzj7WAI8mqNVoHeUybszbuU50uYLuO01eIFyEzxRwYMgNSw+QesKOd9jQSxWER4v7Lb2QG5KTenbYO5MSMgpoOhRDLjSYc1188ZFt2tkqTJDrNbP+dwV7lZLjX3BjYEnIH2yOW+LBZfZtD3vAiwfSOy+o6fU/kb+pY/mPr3XxjSudXaVRMwExhQkhS8ypUNm4Yi1aAGYb729USMSCpSzwqSgghwXFpJLrOWdhIwRMV5i5+79XjB25/sBBhBtK0AfH9cqzfh0V9igGgrdO/D/tOhZcG6sjY2X6Kj2dtzPI3NLPVmpdcub9ijedifNiaopfQ6BuKzGGyzRon9kMPAj0nGZZe1OEwKiSGJUbraOkQrT9GD1gK7H9GBqi0/00utUT0vxCB1hQNY8JJ4AC1V1phutEYnwzaXtbGywscbQ/krUKtGPOtfcbmTgiThHMcYvUNuxfYuGDCenQfekKFdwe25ZP7Xq2GVkdiQ5R6EbMgqyON1YkpsYTnB7XHEh/nCXXY0waRGWOVscvLKCRHZLKndA49xuGgWXmO80zA3DxPWhciJQfmtMQsC2eEsUCNJP9WbAkDWphbSUtlKFVPTYxRUSP25ooA9UakUHOIOxuBE8Ribq3hIkit6RfT08I/RWpq9aJW14YCpJu+HyVr5muYuyy5xPA+L9u3ijnSrzVq++YyJdJDpGUwmFLWrxJoEkqDD0gS/mu1l3BKgbQKo0KvXLiqPim5fkPJEPcAEpmVcQPFgqtoqtGqNdffnd/xYOo7K8boG1P/7QtlaqmQQV5BaX01SFihjHNVf/pQ6y5e0hZLyLhfVl81m9b6D5cppPivBfSIlXt0EfxIC01sLxKRuob9OtjwvpTI5w5MmB10Urb+mLuDvw++o7w78Py8i3nCKNJmCRygz+PKzPGGR7eqvS9EmPA9coyvOeckOLda9RCgHjvGE2o5AsYhYkYj4REYYqee5QpE9zm/sxu+8sx/eT57OGES1VnEIMRhptAU7Pju+bAx1u+9F/fdTHVigyix/F3AZb0jVTjj9LyBnfEleoLOhVF4+W458wQw2C1WghBGma+/zoubApDIIsRgUzTt+cSSXMfkyJQ7/F7i54qOyyXmVMSofO2o5ygZgYPBjvhKKhP1+cEBW6LlYvoi2IwUqtcUnPjfrYWI6AMNORQ6PunS7q+HhIxJlF6PRAMMZab9PZ9Y4lENwF3SmlVa9WpGvn0vjQYwZMQHGWGQl2PBD7TVFfYxhmmoeVF4rVITRxcRTB9Au8eyR5FxGpB/k9guIOA0Yg+skesBUZ3+YDNOKDpcpfj/snQzRYZZIUmFT8Ge5WWz+LJ5/v0aX0Hx2Mm67tO3JoeOeEfb15S7O+PP2agMyjYN3cDIa5GkaAL5ED7SYK36/Tm7qIn9fDnqTp8Cx/m9KRoKD/7bT7+2D8yogATAgbeyfKAHRuAxtnc8FuL2dbyGZpcYK1VB3nMgz7fbIVmelW5v2Y4FmYNQE2nHT+x+56CreUs7zbVtJu25xu6O8KxqpPAs5fFtBJ9/D1FDKLgl3gkwaF2A6oPVorRJjKCWynvt3/Qdz1Qo418qkRRskkxONYf6yMa7GeQKSDlR0VZgcPc5IBdHGG5uiemIInchEi/6pJZ+48vnbZWtl19AyrdaiJAV7IiBSujGweBnG7rBaHfPeqZF+faveRoTTmh5tRt4s10e4p01qYA4IAPLab4soPBI7l61KgiiihYMvF21zs+avl8U7GWD1liDJWxf0hXPf8OWKdSmv1jEO/ZEYlVlDn1BxM+L/2f3XvWzfB6ZInd9Qa6UtCCIiP3aKoQas3pgVIDCdnjZhEIB1gFonx+96qmbkJmgwYHs9+ptmwHHMfqB2BKECHYX5nm2zMb5Tl6s4j2Gc4to3IxEczQJB8wFO8+/lyReHCPohAIAUIEcA3LaUoo0wF72yv8StRybqI8qcJse7BTjbsbybbFt7Quhu4rsY/dGrpzapT3rHdQOiA1MardnwkAlNwXRiK9OEH6q2Qf/SADOZ/9ts+Cl1gLl67kQPvqJdvqe92gok1ESWV+8Cy/WNY6itb2JgwpL3EHV66HiWJxMXo115hnwyGVHu9ohe0YEkdVTxwE8mdALvVW4KtaMVV2B1P1PNihA2VT0qoLGymOvcpYVAr+qo5rSy46oA3RTC8GaVlIqEpOoxP4QPxzi2V4+r+ohbdJWkn0pQRNI6YCeDZ4PmBtMt0+LoQfdpOCGKFWVjBnGUJDSWx+jWLjE1+Ewozz5C6Uhi5/WJYML50l9WoieCCKhwaz+8Ygf10o24vFLM8NIVekQ9swPfNHnxATrtqidgwvbAnUE69d8K91teOkLyBH8mKyic7OEdMyxQi6yQiKT1YCsi9gQZC75xTjoRxDFmKbCkaFKvjWkF7Z7Ag8W/1OL8mHMxSzWEaW7SWFScNAxQ6XoHG+lpfMnSaKqqZ0diFPOBdkbECckm4AQ69Bwa1x+ad3eZAyQ59pcsgyb7eOscLXdxWx4qgIKwDy+kRRydNo3AHNxLY23z6pNl5WVQUGYzOUYG+EFWYQ3KMPqQ5YDKIxMOCaUvEmKS+/Ky+jOBgoprheyJGFZ8iQHVqkclVKPIwabqPq215V04JmgOLXpl6BDjcJbqrfSUQTfidGnKmnBjKUqFP/hz8MK94N4lX+lxvRzvs2V4cmFY2/iIkX/x2XQyJ8T+AHolbi/AFyZq//B2EwuB1Jzv5d6ObehCCZ/HM4fGukP/0UvTwxePzioxg+6KuPYnXixf4By7/kQhDun6DxOqERsjNtPwuYapuWhAWG/y60U08fhHwHtuLDOX1UF6J7LFH3TbNjqIx46pdxn0EhyU9vq2wE0h1zXkH72BGwPuGPwxaF4BYLgGYc4pEssY8io5wLxbwEESzm7D59HPNZZX5H7/VSIFP4V8YeZy0bQ9Q1Z7TmCN6gAwCDLFeUDOJFUUhljatguzBNMamg0i8nNar3lFuVdOvxKxvKWthni5Ir0HksnkCukXcUVCxxjzlyjBm/0tMvfkCjEjOC8qRAshtQZp4xdXtieT+qHkiv+WmSlD9mDmkXIPPiO/Ulpi75i+cGea9EA2+S0XojUWu5XwI2eQBjmUXe6dU0xcQRj5OVPtAVf3rQJNWXrZiULSOD/MHvGubQ01RVuKF6vJKxpn8uRSfQmtoMr3KPDcb5y4x+Lu8EVzMVj77C0TBlmVPKh3+dieOg0NN+pd9Rx06DzVwJpsTnAgT9D/7Lv3whan41hm6fQhwpaCrirRk19WiF7Psk/WZpU3guxkL8YhbcvGvEc4qweIatMpqXZFqSYInJpGYUBjN2YX2ywaeUKz4+Mr2cLAQ0m0/DInFJ1PpECqU33goJyQHT6cCw6HDeLUvzrn55D/piGbBgtuPMohSzJBZMuZdNX3pWjg/HQQ4vWqcgi0w1jKO3Fdqj5wY0lOC0RGOGajJ2bzifBqrkDs13mr84b/RwArcNhUyBZkJboWx9Js6nnRpNUk/WvqOgSh7wRxo8ayHkitspJMvXba9LP/kIxnY9xrh9QD9jeHNPgKukeuMNXKTWQIiac1YDPI4sDHfjr4Z3BHHkIo+Fpd6gQkemkv221stNmcv17rO255MxM9mH6ZnhiO0HU1bFaSnYZRYKznSz8Nt5H4iOA+k7rG25GXBTAzGZkVKJqFlTaaqrFb0zYtkXAIm7lOLNrngX5w/zhodQ3KK86v74y5ZmqlRI5+uKSoM1Jg3nUiBKHaaWYP1us24i3GQmmfAMv5jodnKDjJbwNPfnxtQj8Ydt2y2Jc93mwfjKacg7C7ubNhestBDfhM419HXV3r98Q3NhF8135hubeL+AzVNh4/miyJ8nHmsT01TwdF/oKTzfzCCVE9/myA2rT8WUK/6dWplQ8n3hPu1QdM9OtG+JR1b2dcvG7rZGM7kqLv82BgmKd7fl48S5ktkYFmTu3rqmaMRrs6RuK0BM5cWfZd/+dVNM/PN8g+NYfee0D8zxa11uJ0nh0sTpxfvU46d9YVOjDcUT/v60VG9NWvCGs/QxFcUgmXAn0mU1BCuei7Fve7+SrPyBVDK/i9qURiI7s9w9hOIdciyuxclcEET7i5ucy8rc+yRGPAimhcXN9iLHU2cdRE1dMNeA5n3L0pKnuKoFjTglU5UR9sCGpd+tOx/p5TQ1KTiWfW0ZOVnp7t26/GRlpvSfJNjl6Nd/vkCRFMQdKNhHvnFBHOON94y6HuuRUCGqHY+x1DXysJD3yYd3VAbD3/YjzvS+lg3HMEQb40Mz3+QJYYTnxBqIP7w4lXFupV5oYfFBj+0bZhDdxdvnwkx0WWT50MQMEd7Fj2yYvLQoHvsI3AP/U98hs5p1Z8vGFfclo7MRHP+/FOQbgKyZ4Pt5z72RGCWXJ0PtOoUBe0QBp5XVFM1tv/yLeQqyUZ/IJqCTR9yoX+ZUJXG7WSGo1R6SSoh9g50pHtbJrMiMM8svEpcOM5a39xtWf74IKYiTmk2O97j6kpnQmjF/xBpB1TB7xYrM9FVn5l8UzRqHGytR2sZRvCkLVhmKTGyrmu7/sihcRY2Pld8oF23TSL36xhj20SIw89xEObyOsgBlJ+Wt/uHEEjL4K6P5+XIt/BOe03+X3xfYf7ecUsubs5obQRmBZRm/RV+cCTHw+bEUfWuNMpzY3UEiC4fMZgJoiuTKHV69Mnvdp/CkIrmoR9b7s2KMN3/CKT9rQcKPZh6zKdDjyQs2nAMptWwzrJSsAbU7e6PGh9lMxRqb5w8oYYQyJtvxSWZE0BEWFXRFcPFFNc1MI0B522IOaj1xunnJAjuMrFxXAJTGuATxIowKjCI0MR/etzl1Pp2wkZZHFzT8Zu+1voqFJk1uCbmqGNEvx87wztBi+TO7O1ThllxdZN71eNPmZf5CyUP1bZ/bbriv/fh+yW2OpQFMbwNqd5bpXPYEofd+fWKR/74f8+Te9fzQiZaDUgqQAcK+R/5INkxlGeZy1uE5c+GmL7h/EhnGHdvn0GWsTRbNAoCcbhZEYdZpvK3CuHdoOZWrrgeiQVwIZTWQwyA/Bj0+7xvpnLqixdbj4qccjo3z43FnxL6dmkbBdgl2x/unVKwnEJTljaFh9RRKtc6VgNXpnA40dq/CsI8f0YJNDFzr14eKatPJCnGrhQUSHmHy9LDLVGqJogm5GZM9LWZxfg06EWIfZz8+V0ClLbYVnBFCNO4/m7B1k6a87SwiY8ETRiFRn6ANf271TC+XDrPHtZsktwmUgy9Z7RUefgEu3FehB58wDXPQ/r5Zzk39Wyrr069JZRTC0imoHiG6lioZhAn7rKSyJy0QbUF6CsRaOGdTriaVFJ9HgC0Eh3LuhgadE030I/CkIiEwnlx05jaHqmS+FxTW3+7gWx7jn+4ytf/gTm+TOnIHx/41nGK/djXIwaLK2Jkh2y/osZgW7N0MpdaFDyrvHhm7xw29YZTnB2aMHyKjlBd6SfRHpghPvMG00PWOv5eiIskEsXodqYgkWNUQl8M8tfnWsZFiqxvUa0ZMOWdoB//IyiFwTJfBezYlFa1Ui4mVsFSP+rf2pBGjTGPhsWsskSmeG/cl6InMkQI64UNsIOWddJ5KnfAQWPChGTPJPFBZIDd/UgFN5pcmSS1g9YolkDfoICI1ASfWA+BN/TJpSaNlVaKldQB7pOWwvol7OE0hZn5VjIcilEYxfl1fIukiOuUAbaA+CvFQa4ujDowAX9e1+F4bfKgTeh5zYqreSXPfVf682yg2WWXK/XU+E6MFWlsP0KTj53zjL5a9WvLDzFSwwE8X1SC3sQkijIJaO5ypPOG4IbLBSfb7NPTXuUON083GxMciw7Fz0NpqqQy6H5up9Hz+uoUvhMECqJoSHQt/+AGnWzGtY7geehYttb7at+MF2Dpn2R0TprJldLB+qhgp0HRhzquq2ugyIxH3QKVZdtKELHgH56rvIjc+FAj3lHhjg3TlHOmznHjsiQUCJOYSJRsVr+1aoS5WRxwVIbsoXav5oP+e2cUZakw31BBI3RO9Hp7+jh9Cj28BpKt8eTqjUqarq8/SuqdaK5uPHI5pnrvQxfBxusmqRt0zdpJRk3+AbZud2meu6fJTiZaURve8zyxU2J/Giwf2LFgX85pO6N9MMW5MiRVXnLbj248XLMfR25zbWp/0wbDBZiOPN1jr/JUorrhSYze0VmuW9lJH/ppwk0xs67eU9ryuZGg6eI86FE4NYzuyfej6fJRP9+swfwflrXznOhpxgyOxXAry5O0fVF/T2B+rBulM34zWzSByqG5DVVjeOJ6l3FHNIYRHTOCxxhyhdivL5nJ3vVgFk7N6v+cHvvlHpN0nGVEUO4GPUxaQCLA7LsFYOvuxvucEwIOYG7AjLNpA1/KL7QbHiBA48EBd/Ytk22VfHMeE+d8pr4+eetFfdSucqPvgHw5yqq07s09YFK5DEKPNdV8cN4YhK2XcrUdY8uqAsAb3vbsgC1vXtgHqAc7lVGRVdCWdhbGsOXETRkriq7qpHstBQ0rp+satld9bYYIqSuhDxymnFe9O1Sg/fhN3/SWVVIlD14CONAfUY+7wIDQsAAGHJOzR1PvC/XJL786Ak5QvDO+ANjwp5qBi+Bhf+YS0ZNcU19ePtSpRfo3PHVPu9eLAoZ29lBQk63nle9vVTF2rFzefEKIOq17fZH2ns2gL7JQdP9+KEx+uBzrxjzHnnMFemETUVd8P6rGInKoTdbI2MOhfxa/u1k9wPDrsogonGzn6lYijQ8bN0s4KUWR5xE71EgX2wb8seSRu/cbE193uKJqeJng0I+ZLAn+g9eWT/zDr9E12ydLgoyAs7jJRlkJNpeePyg0hjjsXDQgDUD7W9DR+I8JDMuLeMsg5bHAuq6LAdOgh2CaEtqdhfnsWjLbxaU6/r871cuMILkaLMJMpAHz6pMCqXiDvjMxYntau8Jtd2kvJ3+OXs0f9TkBXyif2TpGXgzyqF4PJV9elYL6NTpnXRFHEAxs6WPsDMq/AOV5AxizoAyyY+I3PuAhJp2AgOrrkOjtY3qMI4kXMWy0bFkbZ1UZcMDS9J6HtTFKxlDyecxtX8FLL2shs6WJyhaX/wjombOJR9fcrU2lyKayP6c8IUc1fGUg05XzZsDGH5Z9XzRBIvKhPyQhiUaF9lvkA4+6ec4TX43Mz3xpGS6ohQ/FPXO8BxaEqY/6E3bbQYjDwXxluL0u2S4pEbKf2KSGaavUP9O2SobQY/Yc1m1OBPcUUYdrHneB64PGlIh7+3b9K5a1XOqxzxRev21WGdvyQxOXYuYd9ek5wElrUMdf7jHDlxL7qIhCflM6PFTQE5soWTyUSIaY6vDuPkx2cA3ouQ6vd0SBX+UNgzWgIi/ZTKdnGZe4lX7gQFH1fUqbWEOzDwDbLO/QFK0f/Heb6Qqk7dNpLSd0KXAymox8r2iHIMvKOkQp4OTKanuYJX3AgpvWk5i+LJcy8u+aLj/kHT0ssz8VMmhrGbZ42f9DpXuoVY/8ra4wq4aByM5YnX4sZHHbuSdDnvUtEzyecYO7x8C7S0Z3g4tZvtE+Il4Vno2PaDL4a5b+av7IZdX5wzdimJWPmRwmrRfC1H+qY8GBKxXISnnPxFczGzOlzPLrCROTI/16/RgO40rO6ZPNYq7ONeRAQBQqxVc9O0etuhSKgF6WgzQZVFV4Vlula8F1Fxw6d6AE739lVnQ3QWVzobDWMY6TYjEt6P/dshQXdrzjuLqaficoN8WCBoF+agmMmPGurnxGUI1+G7r0i5kXMIspfbHw3Qg10TxEfdS6wYcEekz6VtEnmATzMoTbbxkEGaRlOKqPzH/0k1IhXCjrL8bF/KzRzhtV/H0jDsP/eU/fQDuujulzvvc6mtBymWk7vDcXB5WuE9cAMcveK0gmSG/crRnkRJ/ELzxEu92B3m2JTNc6Gl41Zci0j7lA4QgMoaWeBF06K3UUZe6tNw5E+wEVRHkthSfXwJgvJBOJYMZYNIRVzlvkymMyhYdkmRtjMH15DX8Ugo6JETK+f6s2qgPKaq9QadTxYMv6IsKXKZX6JXgyJuXMsOv8PRNo0RD651lOFl4ga80wrvCEAE+SYkc4v2VFGpeVVzsXkMyzrtCYTPCKSSNCzIn/BUQ8T28AOmsjrPqZXXRb+YqYytBrY0Bbxsx+UY7nv/OM0mdXKPp6QmbonU6nxioDVRAcfkwV7aLcBYA8hllQCb0pa50RiNyClZS/35J4C4k5vFUb1BXOWL0bDIk0Bv2tOlkh/R6WsTzXkaFTNuphLUGyfPkgvsaLqIrEpwiC0dUuSrHdxQ9mjW0k4KGE3qOWf2/tre5woC1RErCvCGJi8mBKMg12qXpgXDKQwr2TmUcgP1mhSBbykZsJkPzXScoPbydkMeEpxO8M721pVLHA+WFQJ6/8Bqtx/95z7mZTisgLxJpncCK4xgaE8Xhq+W3qqm2XT9QRyJFRyT43NOr19Gee9p3fd+2dwsb91Yhi52aGRvXJMRcZZjx/4LV46qHVjmD+YluCq/liAefGXszRbGsFDQ9wncb8o7X8toyfczwWQcE5ZWfvZRGHeSvNWxr0VV0uYP9I0+vtxVATTnsWlIaLXvFhtJIVhcZlNoYJU5GkhxCIw0QiIin0OJJ7pDsMmJz7HzlX2MkZvbM6WmZ+DABsWzrvR5Zz7mvHT2EzCL9guFl6zYuOlbLPn9LU7f7wSGdVB5oUEi40+mBYnfUuo1o5OWRPl552Oc7eu9Y0RwV58zZYZXDHGGBIsxhhDEhfDENQqxhhjQkXz0PXpLe/kuWV5aUmtZZkmeelLghBzTiwTmgAU3GWvpFKBRIplFy+VCjjHa8juy7290nVrGEnico9k6RrJiPeYod4pzKZC4ITWeuV2pTP6c/x75vWcoPjOeXJ4kOBZYLX5bTSLTmQOfjA0hDXqs7ACcw+ZnyKOW3M1SSiNX2HMYz9TaIyprsVTtcgZX9uIjqg45TmK8gnRig3jQKptkvWxEpdXUr2Tur13EpIyRclMWNTXClcZYWnT3Y3tMsnPbPlCsOGlGrZvksLwtkSb/xx1oHE+YKqab2f5unHJcmPwhLReXrplMGOS0pVLljr7hsbh9Rgrb9jOd3HZwsApycK1ZLSlrmfNajWDlYUX9jdxy50BL5NI+8XZcREZaSY85BJk3OaXRdwdh0DHHsWSdKca79POkx4vg9QuWsz8M8Rzf5d9eXaJAus/Hut+hVK+HI8UPS/Rh0IMUb7/rMA2pfqKOeRsICbRGYvHqSMMLGETPBD63QYXetz8KFG6ddd68dk86S+Kg/R5TO7ocHYtvL/9oPAR3GHMhiEFmU07+NnFmgOCcAtbY7s7bsfIG37aMN3iLHHIDO8fvoZnSTS65pnMIyAtAvLUMTZgJEkwI/pNucSlxGCHNWukWEbAnTlFPIcvpE2YX7PSzqhdQhzAA2KmkxNm6y8uLFLsobvXpdlD9mDpHwSD6xAADkUMYqQEK/yPlIKizirCpkyvh9+5lbMov3YE20LvRkkOwdmyLWYrXCGUV2XwKdlRZHD/BAcvpmrfigSCfGFheTJOnY1WtaPYoJ9xOE3fGBOcnQjLl8g2294RMgd5EGYmEDKtI0fuMC2MFV/QpcMPfPhD3edU0QpCoGAXf7DO6VpNo09xsi15EoYFMY0OiZdmU4HQsAgLw79gX0LO8u+08dzgzNxnXIvSGDtD22AhkCjT9OLCZ5GngvFK4FwH6djcGE8lEPfETtILhYOxcO/6PWPvZyaYDYykM01jNHMsir2an0k5bsbkLI58XQeRu/5rxv4U9xL+rNAdNZwEke33/0CH91bYgqzo5PeEpBll+rSy6aObkQ0jwziBVylA9KKthfnCB2yigVVEPn8FPYofgxbZlW11yMAVVxAJMUS5VI4o6IKhHa4QV5ALJwSQTLaBi6IOSnTp73U2xi0T1IN1uxhJUb1RTSf8n66eYP9emgavKc0U/1MbRSQuxR8LrYSbjl9Oa8PVobqYHhcVKW4b3TiC0q0p+uTZj30QfvJ3GaXeCp1unIYRLJZHakoY+4K45JBrFZDFAuvyZmuufi/Kh1AaYrstilVrdeTnKRTk4E4C6papTDQ6zfa3AfH3rl0SsF2SynolXUqMldSbpaKZdmrvzKmcs8mOKDNvEot5PVOu2blaxcwzeIpv3M+jqEoAwV7gDRdJXJnlGo0XONeYSCTLtHEHKqmT2ycYRzIPLRgshe96g1gEOyAp8D85whQA8VF4G3vEsJjdsYUm+NjahLNPlbiwIRSpDMVl5Nq6V80YxUX7iUyrlPAB/6jqCKRkG9S9XbyBC9+cT1PWErPqIxa+jo9FeKG68b2bp6antb9xwU7kAW/iWJXpmgtc32j2E7OUu2K1bN6+X4HB/P24A34RB1Y1SY/xLkBf1FOmkbjIJ4MPEhTxMwek//yNTnB1Ja7me/xJf7yQ3OSrWBxbrlW9JHtylYdFIt6vCoUiIl8PY/VZKOvqGn89Z/1YRMDqlABVGHJqijLZqBLQlMy8mItKbcqamYTthRI8F4KOy2YksJK2Dj3tKI9ZANP1otrwFdNSlzIEG7fow+/oGBkU9evswQl1IEKBDmVvoPx0/23fSy0WSLw1+arws4ub8eDI5Sr+feex66Fo4M/ZRq+m2bxtx+oV377S848tBPlohSlkHWPntcypOt25tmPKsVZgDShEpVootiXv+vELp2l3E+N1No106XQRocPoz8tdxPewAy5M7QrvWpGOnOZ7kK8BtPbmM/+2ZlK3U1rEGvl7vv+NiY0TS17je+Wp/Jooxu6UbACzZ3Wtkk3JD3P1MDAU35a6EP6Jj+oZ+iJYR4sjkKhc1zTH+DqO8a/07ZG/e/UROAJEPvqpQDrGbz6ZaxD2SLsjyXNxZRui/VxC3JcOlFjjg+IKkYDXwZXAutbJ5Q+/F75ZuniE3926bIQBbUu3nGusNr4mSem6545e0r76caONRNBE+MJZraGQwbW2JqO88h9TfIpY3/DdKTZEobMbmrAn6TYUO4ge5AKr3e9QDjtpOWnhb2D1Z69L04PE8xdo5d36udIjJ+Ofvds3Ee2u4ZTJHcCRzjEc5KiH6qPIwHNmOzcNQ76akAIIA9u/zod04ihmqBqux7geYPY46JjmqWwKhTq3oRJbKBjGeqSsDOxmZzAWkqz/N0SkrPyO4HNDHgpCErO/2/RQF7UQxm/mxHEZOaI0fqmX7cNYbNwWrXvvwvHKMUm4hshl4KE6I1L3IVIPyqeC7cRG76jUqVJzS7Pcv+i06WmKmqIdm6K/fOu6vXs0f9fnRoxdMGCv1x8fu9ze0eGi+pqfwxrzUjb5JVITAPNc+LEZ9iFVZqk63mqE13mjoTp8zJkBFjKm4VqdcmX4NknnCGSHHSsrNj7zu4NMyXHiZv5uLjtY6mSr008dWEil8iPEDAjLSUEmLWEvKv+5XgkNaq3FcHDaLnwQ4Nirs4L9GIucOZQkznzgMJ1y9yIe82Atj3mWCqISMqng0FtWtGT/my32cpDwyAr+Wyy+R7oVR2EikErR+7zoaMTmARYfN23qv2V4GKSg+xIxZ1CxUHi+qaiTYpHvZZBa7elV1tlG3a+1gVaePSBduq6vipfKMhOwchzYFoL4zuVsmCcjSO++EgNZZJGCnr7wJ7pKBCkFOcI9yKZWlPx+2C6aIkqSL8OipGmADN9P5B4yIDc35AlGnjNC5xjdI4q/6v9WCigz/I722iMxynMjqUlMW+0rjd8ptN3XJy1hK7As8ecWEvdSAaDfaq/pJjcq32JwRhNKfnfETX444nA/zP3RssehCuy+lOlxKn6SvrzPMKebk+NZ2ovryQBR2qRIStIAeZjIeaAhRQkl0xWbSOnXtxz0Ssskmf2Mtb4lWMsVuxGgiN2lgeFpyk613IFCVMEc9ZgywTKfJ2mTMca7eRg1CxBo6ttagFJCEArYRBONFNpuRXRBWVXFLHXLwZGaUGgjZ5lEK7XIy3n6Yzjuv29DTx83Mc01gVO3tGvc/tXd7kmmY4CZg6zY1hgSvQjI/q3pj8Qr8GTa0AU1Dw63w1h6qVnbNHxBIInp0D9cDNAnD8uBkVHXqHT3jZr+jJbcO1p5wPET8Xh6mSCERBcxQTg9W0xIPEJPPnxQ+D/WiRoBEnDFbxM5CbilTZcyTVjq2FTEive/PuSkmgwnlqfJLsUt1D1e8sK/4sCoQ5DjpeZBwDt39BrWnogx9kLMd9Gm2/NvjTQyEPjCKViCUzhTdl74LcMfIvnZqYtUeI/kyfdUr/scRpmOjK5wVEsaYfGwy00QHGpseB53To2MOzJQABJHz+db0qTB2KOWdkyZ6Bm1u8CC1jSt16nASmvctH26g5zuGRGnHXLqo31TE4zQpYkNfcX5p/qxMf9xKgBYdniSfWAN+y1+l7AzTCFNDKVq5QcX4aYwhCBVwUIL/jQMdBWeGtMC3/tV+LWFn/WDZ15Ok9nbVvG6YDTCGkSMK7xFC2y8Pm66XHC0eC39XlZLDy7JLhyd12XFwxRf/M9lF1kuQtMpXBRQ+xUg6oJpOS6kkHy1IVN7pCQcmrvnvp/kZsShTEVT63FQqDLuL0u6jueDYLQTLyi0Ayae2cdD9wOun7oOmHDjQODCfTMCfdsmlLJ/+TzApZdL9kvQVMRGc2NAsTUqjZfCygQo4QqvZiBlCh1rHeE86jQNqEKq0CLDDw0Te8c5K3fk85Fzfs0re2mHOt2PZHWDSXHTrB/eK5rK4FMYxSNBcfJWkYfSIJlShDaxuJyA7YwYFOzbKIFfJMzdaIJvMix+hc6J55eeB0vBHwv8+ZiMXDtN6RQsjLeopuNOo1RQEqBDCUhEwXD8y55+/7QpwJuSTnn+jZvqiPOCjG/tblp+DgYsw8Y/5KZc8m00aUXnbWiBu56YY3ielp6N4uJeceA5Qf5b4oOj/ysNWHYPT2eudiaFP/FWyyAxb1O5m9N2LmBsYk5JDXifyO1KVXmhtp99+orlG2csbvYtzz2LY9Y/UdyDKmdKFjJd1CXx4sSyVP4eKLAX42IvZl80OmaAYR5GHqNT8km/FwYTE5SU+q+Lizc++IyKg+xclHexSxVbDvZ2W5tNU3Ev6iRVyWv222AvS3W62lP5aSC9wZ5Np12fha79YSQ8whxZ9XcsDHEN2Pz2SNu1xxzKzsOWb+EGl+HckH96l4bqidxF62vvUX1YyNxVh9664lY6BHD7OnYodJPSPjw+7rvq9adL135zwra+TKXglwFlzfGARObD2LSNmujAXJZNYoU8pqU7wWZOWB++0eFGuODasujKk0lWiCJPkIxQoNt8rql4gocDlKvSL/R4FNHBs3PDvP9PtwwDM4EcWfqJ0yfDo+Ybpl5ams5Cy7pVUbtyl19Xzrfa/5kvIwMwJDmK6MqB82LQgy/92uoK5o2OE1YtJirczQ2O4bKppgWJq8CFA5XpQJPYo0+q9SbSXscrbfWOBQor7IRXHdeGLdGz8EjvhNM2HSxMh9HVUYiTqPgEXrHUveCJTzoY1b0gqRZqf1F2DHA70E0DOj6z9+eyWElZyPa3SZXIh6A4J/lhHZemSQrsa2zj9Nn6IaQp36YRr8P2X/8ZQe7mbZAumdVA5baB+JiUmMaAuqlhaHvBadnUJwpm68Ab9dHcQmzsm31b4VUIDXTl+YSTTjBj8YVS7Nj5rvDyPt9JTwjSv71LX6nvb0VTt7sSHLHdrPOidmOGuWUa0HydPKiw7Z/UeQqHxhK6Opp4U/ItdD6VZ2M9oCJeRXquNrSkc16SULgX4l1c8NJ0uHt5XQayiR0475EGB2FUpiNzR2AfGiX1ZEOvzNLd40D8FKgP5FJta8supCg/ytk6mhFEH5iqFEvSM5M0R9jN/ZG+whC8vhUAxJ3ryTHyXW6Ounz3Q9R4QE4FNkGNkGpMkVL/9uXzpQdrp+ZXCrABSb7T94DPAjxxSupfp5gi8HVD5k9uBg9qi0hAwrkIthsdsVzF5cj34u9bRI3eX+XuXaQ8AlNLYyMSiUVh6H+mkA55pRUP2nl4LX3zK4YM2TdA8ATJrWDlpMFUucptEMrr8orEDugu5icnrzX/60jcTVlqfDObrCQi1ZqICFfvSV5LWT6v2H7ruRGeDV1ZYNZ/gDzBjB7TopLgdIhEXmKZSzBIMWy3AR5BDAR88gKQoZo0bIRdKbgji/wOk3cNDFNzxFnxZHqgO9NN8Ou0PMiFsIsEE2KTiv9rhG1vZlCKGU71WFVxuVhBZ0GwMYOMUdjHEeivW2PmIZU6XpvG0tMyh14jihBm5HgJ5wsoL7pBP8xmxONSN+GFETfrYXaChU+u4MaiwFWY7NiuZ+Giy5NksaU/T+0Qdd/y2+3QSzecLIF03UeqU98rlaTqlWMfxWwRUWxj4x5VlelJEe0elIkJQhWHJsvjaO9ik8a+p08A7cWYgaLNNIxY/7qYxG4NcBZmqw4pnfd06GdkBU+GN3AQe5g4V8Gp3aQSWDgAD96lofxK6HWPzddLNGFDqM3YhU3TR/sfEBe9Rn/hEBIUNLoUhxt4vvx7Yy7QoIEj2nfu96tx2ZbyWvqCn99jbh0y2GePpt9YaPQ4VeaoNrakkGDK32+UdMbYNUApLSpcoLGExbIwVDEBLFCZaeTf6lzijOR1z/3vhJ2L68hBiNwPdSBHcposTd2NigVkugfH7R7KQZL4NmP+LpNRou0DqJ5f+R2gjOUxGpSD6TiOG2X3AfEMexFj8f0KcVYWa0xROGnOVOivEL1is0DSEuTXfc326xvHfckBmlJM/NIbECsHPXNvdiJu65w8CkzPgPHhbPom5fw7oFHyLsAiA5Nssf3yXI6HYFNOUabt8ZQ5RGHYAbxTwWyAgVWFsfGrteIu40aOjcLo1LHSxq3Q2kEYyVPifRYIl9mmBHeEbDlH1GBrGZ5MxKQpK+swQ3ZtzYqe7H4WAV4rLrVhusWeRHn8xcUEGX9hY8cwH28FTwqWsD4sgkJqTTp/oZdjFjfw8P5c+vt1oRuv5VDmxNXbP6MXyqmR/8dd8ZKWz9i/jtJH5ow2m2u2dheL/fNVbLovH79f/qch3XK6gPKOBW1kZ6UFmktdtGBrUKdaqpBhpcs9B3659ggW2Ml5zskyaaj7WqgDjRnihpCFharKVRQwciLDiRzuJG39rvtiyMbBbUwADZw3wdL0ami2n/1Z6XMVOcg95C3P9ttn2SHEfXLSD0tsaR57sBPs4lBUatrFojUW0gyYq6yNpHtqNmZpL3I2Zmkv8nAMSagELKhtoIie1uBLeHQzaKQ+QYUxFG1uHjs5M6invQnCbfQ5rtGtO8W8x39n6qtr/Glf52+uhj8LOzQldSPr7IY8WjMqyvBxNZSBowoxUq0A3fjH/YYvbvHE+Sja56wN+Da93j2QjkgsEM5RaAn/YttbAse4PXU0dIbuIfkyKLqV+VdTtFYPiOQ03qC8DuKEVENScDayYG0jNkZZHLbUiN/WVb3EtHoPLdrdDL6VKsAJd7rZ4ullZTSK+y+mATBB1ipu+xaErug5RLBX12/zYyZMiYcWxf3dVd3xwrAMMVZmgQI21OnXqC7PRVt91ArlU+V0OlA2oQ63786wrgIMFwqNipiXNu7fJKIkr7xifk/v5UJnJtiwgLU6kCDjjedme8CBtUgQ8d1N5u0ORYDZm+SRdMMXVbCCrNuh4GaaDImmzTSI/IXXfkOWsmTtYkejIvB6WBgIPzFzBJnDkp0GfvY9UOS4HWxlvRBlgBFA8Gs54Xk18tLSkbPCsIViDGYcJz9XflfFtuUZTJ8P8Ezi2yb0NYeqqnq8yYlZJchhIvOFWMIusMVgaSdDlqHW0g2vlVOvL9kCg+QaxDR60v4lnY1ntc6DjK8gaeedhNiQR4HrqnxbcV5TiHSJCbmpphhi/FU5/Vxm4pdUTJ9tMAhyC6EEIdKtWd+hnj35nCv/YHymP7E0f/lRVJBl8oA2HqAaVgzmP2/XCBJJoHo3+G76B1TWukAkazxvAxsX1RJ2CTUezPckFI9aizbRLK2sOZkaeHQder3d5PWuP3+bMgrOcEhX/L9Hc5+HJGGCEjcIVEkQ9xoZQA5cpfGTmtCf9so+77sBs1BE6HeyfDxYm9npojiszPutDQQYkYISq6JbNWSIG2btltJAqFjHj5ottdNX5aKrrDKv2j5rsu1QyBAZjn6wC3zW2bsk8dS5rvJ8JDwPEOrfJXnBe0q9BCKTRJGVm5BEcjqR3XDRopAkEglJRJwQ3o2NZB7q/mqcfxSHiVO8MCMqlmmwSnAl0rbSWWndp0fO4NSh7DDFI05NiuGCmk9Rhz5MnodAwyXPEqJtsqHcAQ2rfLbRD/gybhRSt4sYMebAlmliRvlsZe7Lqb0De2cFcKUOmeU1fCA40gb3fx7974f6zdQX3HgwjNN/Hdqrqv7CJzt4gf264i4Yvx4YfER2BJhwAk6RgOUqHWrDzt9+5YobllQuUA5fMOA/QzG7mSf4B5pp5OcNerpGEBOOS7xeDLlusWkDRG/MeJuahWsaCn28y+dV0aLp+Rg1x/9R49Cq1x8JwzHdvwp/vbzFtedUOnTCnvHegevZp5L17L5VPQ5MWu+RyxhzoaJHx7A+/gZ2iY5y59MuU9Zj5Kbm5FRHESOyQqTd3l009K9RE6jHzebLi26CtccLEWaT1aNlJNh6cyVhrB8hjMpaSjt4qMqclS84E6wbZTcEG3wPjXp53AaKPvgOomZYXB7wYwZZFc17xtDwvjOhBxdizxBPH+8/Td99NVjjBE5YP6bmJoKXszvL7kX6LCJKqGmxXZ4OLwBrch78ZDx/cskJjrhH7E7BqK9z6GBD44t4Y3E3oIm8iR9wLu3v2N8/uXpw3hXA++UCH4nQEE2kpXXXhaDikKpa7NjjpykxlYaSXHBM+QmqYy4soC+F728VjQorRmEHfew/6oW2TwUBT2oymj2eYHe385fvIo3of28/jYgcZpue/us8ILcgvbgE0UE+qstwmpTNyEDhoT1y0pmFFJ/wGe1L46hkEiQWqe9jlIzhxiiFFxLfkWMa83f4W9d/kQWUBazrVNF1dvIZ2NQ5qKacVer0pJi4mFmi4NjkIXPIKPLssNJEo81S3gz+zssiQ0geNKQsuMnJEo3veMNAdXd+lpTN9r+Dr8P/ZYN+1DDcP4nz/1X3WIf+xwfyz7x9uPonX4R/rf1anfJfSqof/n/zV669fw7mGvxf5sYkJp0meeu0/6Uk/Cw1qH0s6pfL/+5v6NCqFAWVlwc32ig4xk8JzMOdBRk0tuvPweW/5gmZAJrovz+PMpkkz5b4+SGK0EG/HazIfQpKucO4mKXndELrV3YASpONrjxO//oquPD77Nc2wxoo/9Jazfuo+SlCHyzoCOADAV0ziGOu5Qqwk9RELd4jibf+etH3dW6KXW71izwlG3+XXHoX/zDuUYr9yM57JzytsGvoUZ/b6AlcFa0yimoB8EEC9qvY2UI7EDjonfLMRQfg24A/FWoMPXHv9vypqRPqLhF2RfH+8N0ccG251vyfkhVcmAegmuAuCeFwjMXT4GSDRdS4j1WtyqrQgoqRS8inODSLDBugIulvjESXFPbjOpZbidn5SVxTrreiSGnL1wtwViqO6cD/mPIUj6LmrFWia8qp5iOF/Tee5REQR0A7aDj8jqw7+NPd9O/7cNy7phzvVUCostRmlywn7sr7t5Wa+GbwbCX662PyZLoIe7fbd+3XHRT7fZXRCJib6FSmuuihuI+B7nNZDOuv6PauxxOey2I3hcvuEXVFdO6luzxZ/PBv+yWJ7tWZqOFwLezRX30knVKOo/xTGpRNdlL66wrHZUnx5149gtg8gpoj/T/RutLRZRgjr92trF2PrIv+3EplgCaC1zXLevNxKUeDHW0zkeKTvz6iFm8DXlmKQbrcySZxN0FjxXCzScSrxf2I/v1BtA3oR7pef55uk/0pT594i4tn4Ft6/5YEYR9rMRanY5Z+9VJ5nGmh/L+7oMrK2t9V+tWFcyJ4TVmum4HbbzqobFS+krf4KWFrpnBqgjUmDi2xlxF9ZcBuZYUIRey01eE+trK2+dGKVLPOEZxCAHRPpRr3EoWjDV6e97SNXCaHXaUtcOyfGM8GV3a0XAYL+1pHZgBtw9mMq0RKPZDefdxulpDeSOUv7r9U5xS20gH1nuKy22SsiQJf4krnlpJ2NeIl6NQoO9+5U8joaJzBYIndilUnCNxW1hsP/ZepM16ynVp2zs6BaTeKf7Gn3TzZd5nUqdGAo941AyUj4jT1O5n8tUGU5u/Xrc3qDGBN8/mrz43PmSWxWvRjvVfAl3iuULVJf/e4OcQxYKeET2SZ4fSxOzrP7xQX+HbZzQ1QKmewYQng8K/APlBFyvUnHrr6VRbqmkHZoSwEKM3sL0UQq1VuIsMvPwopdrHAt6JBskwuNueWw2zR3r/E8eK2MfQjdixUyFNz1WpF9Fbviii9K11wrCZRZqo+4jf7fZwptC5TlZejKab701fvev1U8yFvt9+igQ77QPkTb8RH1Do34mIX0cfq+wn7vxWCA+A/IQQbcNJVkGUC7iKDEy0gX8Vg9gzKUIE7CXDgBswt5J/i4JUaOm8gfSSgdQeStyCtAHtaaCjAmwDnPgJ5VSH/JQE8+wDdVEF+SMAXH4FNbUCuxOAnG5DARn9I70oAGIAC3PtBRY6l2yQDwN/2Irbh/+9g2J1nFUpoe1HIsr9zpodLce96vrSX9t45PqZuqpYZb8+9HDvwZQir3Ib8PeBNNTZdePfr1BUz33+puEy7/HA+1pKW4bFX8Z3Xvk+9FnhCVWpwRvWcOFeUMzN+omYy+I1asgk8WumcB9o4NQ7JIwV8Ie8pgpC84NwJUDCThHZskYIObEMqOlG5bNBgYXKGruiRLaXSh/SUniuXHcVpTG6JZpbIPdGSXYgTK4PLM7FxY/JCPLKCfhDvWQXNxAvunP6QwGjyQdJxDx3QDtwHLWkndk7HtMHe5C+oz+9bWHE28ex5ooAvll8p9rxG3lOsOPY8kxvvlk84G3j3sOB8YOX5mPOJIMI1pwEoB6dOmNX4vKsKO4M30Q/MB4SiS8wG/DkVYHD6hnpH5jGI+kb2g3PVVNzlPY71mRvTt0fbsK379m5Ltpt+WdkN25/eqLrmhfSZqjcs6OfK7tiGfXO0NTfnvjtaxxb9WtkrN67vlO3ZnvtG2Ypt3q+y/eLS9Vo1KbfSn1Qj3KL/kfkenGuP4oQw17+iDV5bfUHxiTfRyTGL8VbrWcWH9Nugq/icHnFKq/kTjDvOQJd+bxAfOajPKQbVOAAAVJPjn2yWqC4JFrmvLpcO6StBcg+6W8FHYwJM/Rh5lbH0Ob95GCoPFVHGTWfO3vidxtlMdK2LAPshGI4L5Y2zg6AXJdVxTza071XblZMTQl36mXTdAxrB7ln60IvFfgNnSc7azCadQ3WPHVj9apqsdswIARI9UoIYNA/uMO905sexlwVjThb8gxPxYTGL83LKA/O/Msy4OpgEJjRdMLfFxKYHcK52n3Pm6kWfDJB/B4b8iTGHBQrFNl/mReSj7kY8D+IlBGPibiPK8cemLaQDMK07rUJ5f7hO4XVQ9poj2Lw3nMZ0ChsCH+L8kmoN+pGUVP5Rri+1yfbC7eNDZ7pnjlO+CDvLOZM/DbQ2m2DGd5dEb+EC8NlkI64DtUF7GKHpKos2EOAwdr816th2CX9JJB+toO7DJ6eWR6oKbCzZEy2Ke4aobc7+iSFFot+wHXUhITu95OoI+VM7SjPWAg/GEpKzY2BNqHBt2L2cVKfPFPqiZTIMJ9LGf21aRhr8AQC37TGgVpAHRMlw1AIoz0W77FrSGozHiR2hApT5JG3mfnaNqwP+ad67UidVJ6S3DTIbJyv4o+wYfww92naMFF9CVOGEzjYInPoyDkcSksL8qpqHS0haYv4xKixl+Ay47d+QODoSyVcOq7L2zqF7C0wVsZ2baeMgkN6fxZ1XZ67vjKJYGJln/40vBrYn/HRqb8mPMlxtd0lrpejlOasYESobHofYfcufw8jjW+4gSzStBEdRFq6iIJca894w2pFich14Pg/y3vJ0cmYrrHyTHDuNZYjo6IeUYyMLSM7OMzudV9neAXgxO4SNWXUuzi/sZYqA9VXPOEkSlkMSqEhH1iHxy0LnUb3a7lL4HMK3HnXSNhVVJBSV8a3lJbsHoPdnn2cuO1+2hvRsNz0svDmxBCvIPInwdVjS82YBVt6L+D2NUq+b7fdufLp/DTRRo3mpS7CGKG88vPtc5OUmnNJRExtGgEXuosZc9LGq2ckdQrabxQqC7ullG9IjfT6HQ87IKiJ5LlWPiZrsY9bMrXQ2P3e0lW2mwv4Ti8DCmUUVP3wYsRk2cHRd4rK0SyNF0mIvhFcvC1oV8z7j8QTAr5qTa+S5A+i0JOfSA6rufjRqr4Ze6t/NgCY8pqvO25Jk7xKqUMlKM05pn6wBwdwfUjo+OuPK4DxFdpbhHWDaYIaI4o88UWUp2mKRHl+WQfO41rMbvBUJU0rUqDjxjh+Uq1eeaN7jWnZKFyruERIlOgrxj0U3aEAQ1FZE1UXj4E+qMlEDsMH+350j2rPvmqFaf0Bk/ck5onlyxPDk8e+NMTp6YEoPvT6v00G2WlI4nIS05cidt9ZXAEd6bd/8vERTYrSgN9NBDMMwjNufc5q8yH/smp9Nd1Tdp9+CTtfd4exZwkCkApbrEYKjEJn/mL0xYYh2edHru+3n/u6k7jmVOsAObaHSlJrdEiZZzpJEK+o0Gwmkt4++jEZgRAX9OsXnY6onZtNIce3CNBSkhMQeOkSWpaH73r/mj4F15winHMaTOGue6cycDsiuZoafYPslH2QIZZiH3vWKgaiwiFGSJooTCb335JUkcwsCwrEUmLJGs6AuS7PY8zumKdyQI2cPgfmWJkGSC/h3IjPYciY0k+b54VzNsHz5+OSF/TBlMm1H4A5hQMKfE4vqTFwSThCPew/TxjN68W2etQHh3Rem57kMuyMiSdx4zAy5fIa5DO5zELVgdxpoXYwPtZCV6vnR/EgC6Ta9oeysBRtg4fcahozUy8IWGUxRC9+vaUvMl3iYw6TZ2fMN3T5D2BCn50bRRwZHaUTXDTOtXDcvCLyySA6lZo8XqCnR0TPqqekgc/suzsNZA6cbhGOyio/oPhR46WdVcsmGkmKcO6LO3lsl6bvi+M4QxmXvNXGUpm98JfeqvR9T5t+oCSX1Vc77WIEZt5LCvWxR5Pd16rDTcIo3EuQTqlFP3xJv7kfxZs02SHcekWrTPZGkR8eEuYP6gSzy1iNuuu0IrXdH79dTwHxyEsZb5tF5GkJySFbDckDHWuD3SPLO8vhehorOrcITa+ETcKkSMzJceHuf0cBZ0rHnEroAkO+RNq/JRp/d6aeGh1kOFZoXHxUl/ZId62586T2HWjTQk7eFSvyV7tOsQy5RCQQyv67YEDB89roJeWd8UbaOPQ3wn+0HTbI5zS7nb0JHh8ri4+ZzBzdislrwZEdE8+PKMhE9qr6MWUTCnrXenh4WSYbIgM4kifEU9ihlRi3QRece4qVc+J75AYHI27nTI3Fp3aE0/3MQuSxLFBslE4Hhwk6cxzsa+eGi0Gg/1B+QGFE9kvR4HnB5LUcne53FRLCRKyf0c0iQw9tpVCIDJdGkVHUcvgCTBAbafafLnNbM33G2HloQgTFjVZvnZZFZ9OL+s2f8mzz3IHDB06DxXXt7tgWpeWMNAh22xefRxApMwQUqWSG/i/qAOQJpQojITgVFPqhJJ0O3fY/js5mXbzsiBDvpJARZo9wMM0FR+J35YS3/2cHAObtOTxDp73byjlzjDOmIQcs5nueqOTctxvDVbPen8/TSQwcu2uFKexcJ2d83HVvnEr2Npk8smAPOCR9cJ+7S2CDJhRRh6eWPLViM73mXXJ6b+eBVcW7+F96HGbs8k/VNIP3WEHRs8KTtCUZkFa6L7td8RTS3fiUg27ZM05Iw6ma5SfBzQy90oe3HRCEf46Q5qDYherlwJ8ym85EQnBF/wjAMw4gdg531y+4utCVptACKmVkT0ScLIDx3MQeXbg+bKGeCiqYwj85OLsbQMYHVlr68ivGAo/1VJM5I8K9Z7TwL6bSmdxG8APwokhIWLer8YnY7cei5orvDMy84e8hv2n95L53VxBQwoIjlpW4cfEXQ3oEGvCHDMHMQ2FLeiYZMUC7O5NvpRFXhY4gaTZmaSM/38RMkybeNXIcdGYMIkavirnqgSlA1lkiIG/39WWDvnw6opAHZLSRqA+R6hrHnKA+oZAa5qVr67aEqe1KXM94vs5/f8AiPzgdJtomxBMhAHkgF9dCcSCBrBEHNB/dSXYWQDXtySTqaEpFXC5lnWnb9wQnwk1tbhlQbZSXiFl+khkXcMEwSV/z3DJtsn/6JvNfS6trh6+FmQb/Qw8o1iEMg9WRQXHMmcd8rjFQpMT5YB0NrVY3V0pW2C3abHrg//ZhLd+Jt9kjMi6o5n4UvJGq1RsA16DUTEx5vrTy4cdeu+GIWIAdlm+U+a1L74ZRBaaubkSFOlu+/OfNNPbp/9plEjCuaJD5pqOOiQ8+Fe3QS3n5whzm1fQYdO7mJw8Q3G2Gr2D2GtsLf1tsySDWPr/FZ+8/tpXjf/RIlwavUPJl3sGuZ23Ov4HbRJlwhrd3Md5DvXVQH1Wcpd52HKPULDo3Sy+KNalShvejVT9oNGving7BSxon972dytVYkHg8FfpwhXgBrOBEuD1GQLMHGDZmxeKiW6YaqoBqGgKMvj/cM+pktkUMNsRe5lTNE5S9kGl2m3zlqf4TEjlc1TSGBqO39ZSF8k58T6lWdLbLikhd2Dd4a06KYE34k5t4uUmlOb9dWlkXhX+mkQXtYOcqx8Q3eNeZPoc/lfmZN+1nQ8rzAtIBwCHZG3YzV6HmCrWY38uQGdKbDD0/KcyY8m/FhJ3YWEIsvae+EDu39oikLxt20NZbVLbYBXOmAn9D3LGCCsasd6N5OEqdiOTfHx7rkiheXUm6Zz6nuay0kB02KpTDB2rq4XTgxNZkdv/UXw0D9UfFOCn8cBDycamxID+WqXaoLXvgkd6X2mv2g6PUWqOIoreV9tWZjxjnPzWtrsAM4LX++Y0LVYFz8pUwuf26aNMqKHVHO1pOD3zzzfZiXykeVw/TG+Sm6C0yC0MID3kqRRb868WeAJ7GLJAoEHq/ekbdBPXSuOaP8j7PDFOhf+irl4azoaxoqqK6woCWx+Bqn1w8gLJm5LVI06AXioVJTfwFjbdvdi9LYLYmT0gt8mo2HHoyOG9Aa8t5rgElRBzPal85zjfUkbvtRXCNX9IOAchIXOp+7oMW4KGOFrz/0w7K+T2VyvwCvsFm1+1Zaw7pWYaIhYsZ5pc34yQkV2SyaWRyGYRi+PUf9bpA7YCO9bK9FgolGxxGZwJWIqhl84Dqy5TXcFQEoYcDOfpPAy2c7Bp0VB9x//4n771zllKJuqMEkiTXjAqr9fdIDLb+TaLxYkJdDV2uKjoqtxEyqqcXM/F//o/oZdjU+GvFhsuXkLk3DU8Plo3BDQVPR6vvyAcw/MfRWikvBEnXth3A1CaaYOf/0Jsc8RvIPPx2HCGDvy/SHEttSQzNWXHOafiC6+nCpsQ4JdMoTJTMC36ZTiNgH375+wGNbDfaq2DDqLYcFT/DhMiAY+7GgHgk4mWLYII/LU4UNSo2YwpdrViK2ddfi6zutlCWQ1yCCWepcyQaWmZawKRSEaIq8Disjj3KK253TSbdiPJLCLWFy/G0JYuWtj5C/QjXEzBScI8lA2RiMj4Zy42Jv6khq8qqxWzRDgW3VqLjnGtUpq+WbdMHNlW5pqWeSbFeg9kv3AVPWm9pnYuQQamaAH33nzO163bAkisFxLcY47lS7b6P6tRvLFJTWTUZe+YDqrddBgQijxnol9B04khH7OInhZegQOoEBeq5AiqUso3TU+Viw5gMStvejkMbtNe6LZBEmQMp9Avv2OFlRGaVe3TyFUI8bvI1n8yqs9webeTYQHjrU1Fhf1lVc0gSHsEpi7KXjfWH5RL58vn+XI1M5XOCbtw5JckumMdgCbKp03yfWsQKB63Ngx/77dJXifYhwanFRY8UlXqz4cAPA9PgNz218CRusJ/f/HvPX73DAO6ZlfURIoYUMJQIliNSJ+BQW49X1lk6haOtz9qetziNOr8/jS9lHOhWSPqy0HV0vHVGv+9AmqiHEo6mhrlObN9L3KyPzF7R+nGqhOtHsjTv0D6wWlHkFBurcb4gjPbFJBMQEhN8CwkaZhkOHF3b0qS23cDtMt9drwUrqBBqCVGn/jlZJYvjWYKw9BUZF3BzNQoMOQnlly8I9+WhKMQkwVM/USlAH13XygyhIWfhv3E1Qc9nNVL1c5ihQLsKhWdATfQLdVD2sETRbeLL81QApwluHUXSfTG7oExJTT2xydgKsZu+ioCRnm9pNnI0eJv0OZZ7Pwqv3ChBD6oHgJar7lp9GiwJ0zhRf55Phmyod/4aAkOQnhuOtHXwgFynbAV/0NFxiv2mcKvsBmgt2R8BwEDkogIacKxuQ0dQKH476P8hnEpr4+mI+yTjLzfvdgDyi8XwuobbUc2PXOEBjWuga5wQF8eHtDFW9cMlg6Le+bGyxsvmsP0JyV8MLjOMKQuAyDf295cg1sRXYuZK9vqnxY5BieUVJ8do5TrbrbhPUXrPz0Fsbb6DfqpHprDhEesvXKzh3Y1RaOrEUXh85mA5DB+aju7MucCTmqjc5qlWBYRiGcahraqnKsU1PiPDV/79CKbD/2G9HOX9v9tDPU1TcFqy8bPW0jUrqMNol6PbQzdguFDYLENYcUwU5NDBdqtPRFX/co3GO4CKUBwpdd3UNiCL/LWuwLz18PPh8fP66hwf5XVh0LjtXqr1w7sur021RbSaKIJ94T+Iw2rTdh+rlWz4CB5xotTOtBmZ8tWIIFrOzLE/MRG5NHLHp9XvBSmXk/7gKEQOgJxOqwLhknYgzu8h7GVyIyo1Nzj+KVapO3kAFfyU2uAeG/C+Ip6R285Sf8JpTjg3Elo2s+s2ykdx5wE460yE3QWe/4nAErJQ/eoDtGJ3W0F7Rh+fWm2QepKVQivPqwszlp+NNLXiy4PuT8zsc66puBU31A3YqhWV408aM1Cr5sTWEyCbwKop/WR24+qxcAgS6myOlUMJcmLCZEqFdM0nZ9Kdnnxtk7t1RQl4KZ10OvxdRVwCUa5RwE0lD3kUXW7zbUf4TG4CuBOku4Gkgj4kxkR/n5+DfHpPHCoHw99OXp41ZRrJK42WqeWL+ZBSN9LBCwkw5cL0TDXAmodPYh2DDC7FedVyXz6m0tkF0km03T9Lufnw9T6UKLigCSnlGFC4LipfuofHgMsl/vdkaVNrNqdCJef7uUwf/bDKP24eFY73FncR0RXrSVbbsoCLx5OZ8NFdmXG8IWa5e7/VtZ7iF1uHryafPEa9hJhVfTbmi8y+WulikP18iKxeviPnQts7rFTVOoU5K4smpHzmQyHhtqXtrAX4U4+cvDE7khNeR1Eadw+LB5YIaiOY4RNsJKOCpxKtBQzvc7p2j/INTgafb+BX0nTFaCt9esI4Aa0Gxvx9rA6qXSUJ6F+7hHkXh61w7zFyTGTcNfmm0UoGqEhOxkQYsN+BJvHPPOHv6+1EkpYNjYohEIbOgdXABZRw2G5JMYJw7fKntDCa6qRfj2OhG2GyWC4GDxE2ucdH8wbDfKZE69jpnkK2vpzz6SnN9+CwFuWHtLWMfMntMSduUfijOBdI2e/bVB4Wg2I99jpHwSMwDoHsroUMOgrCZG1WWS1kjkGqcCWk1eTG9RRqZ1eZPcmN5gp2McETVfE/14xsYopM1IrdcnYOmTfwqpzidsuNqWoliwKOHXQjMGTD7RP5AyBLuDr9V50oerJQjunXjHPx+OSmVVF5RNESAT2MrUCsd/UngPRknNa+syIppDCnaUpw3lCNdpvBlvRCJyGZetitsjyPixOx7rUwT1/8jQYkOdKCYBJW+r/UaKsIfsuk6LyTX7HjrJh4gZ/2+QgyiisIvZJmljjfNDDnl2d16HbvNP5oXh+RpA5tQ0/hWahICfM4lIBXBPaZbyz1W77JM+hmUllXaMvclEdUSViyLHX5NweYnUxfyKgErdnWPrFbDFd9Yhyz442izIKRD84hyHQS/ovkF5SIIuxnNjLIEYYxo/knZBSErmi8od0EQNO3KVMIuoammXFZhfEPzr1JGFXKB5qkpN1XwJ5pfXZmrsPtAE6fUj4SxRvOnK9kT8hPNR1dufyT4iOboyn5P2N2iuW3K1Z4wvtJ8V4r3hDRo7p1yPRAc0fzkysVA2L2g2TtlGQjjGs3fS9kNhBzSvLpyNxDc0nx1ZfqXsDujuXLK5VIYb2j+W8pYCnlA884pN0vBdzR/uDIvhd1fNFZKTcIQNGdTokKe0Lw3yu0kuEfzzZS9CrstmmujXKkw3tH8LxTfCDlB82CU64ngJzQ/m3IxEXY/0FwYZZkI4wbNP0LZTYR8oflsyt1EsEezmTI9CbuCZjHKZS+M72j+HcrohVyheTbKTS/4O81vpsy9sDug2Rml/iaMUzT/NyULIb/RfDLl9m+CVzQnU/YLYXeP5s4oVwth/E/z1ygdWKQjGZfYxDnAnHSSTC5hog5lTjuS5UtsIg+Y3Emy9AkmfkKYtiOZfCITYcCcd5KMDUyoUKbOKEsbbKIKGJ0lWd7AxD2ESTPK+IVMfAWY9SzJ5BeYWIUwZzPK8i9kIg4YmSVZugMTlxCmzCiTO2RiGjCnsyTjGCaKUGYzoywdYxO7ANPOkiwfw8QohGFEGS02cQgwdZRk0sJEGcp0I8pyi01wwKRRkqVXMPEZwjQjyuQVmXgJmLNRkrGDiSiUORlRlnbYhA2YMkqyvIOJSSiTR5RxhE38BpjNKMnkCCa2Icz5iLJ8hE3ogGElydI5TJxCGF2RTM6xiUeA6VaSjD9gIgtl1iuSpT/IxDrANCtJlv+AibdQRlYk4xGZuAaYk5Ukk0cwsQxlTlcky4/IxDxg8kqSpf9g4j+EaVckk/+wiSDAnK8kGcCIQEsCCXEwYqClgAQejGxACw6FVBiZQUvjUNDCSAMt2aGQJYwQtKghwRmMdNAihoT0MPIBLa0hgQbHBVK0I5EzOGmQsu5IyDNwnoMUQAyDngQFcchQ6ClQICBjBD04EVIRYw09jRPBgBjX0JOdCFkihkCPGgUmyLiBHjEK0iPGKfS0RoEOMS6hJwURMkNGCz0liNBGPgLZMYPgnQp9ER8a8bGUGJ+TiW+wBv/8Nfhe+zue6xR/TzSZ+HticZrtf2O+PHv319bqPMe72wdhq091oe/WF7Grsu2vytNw/e4Ld+V95B7QH/g7uWm209fH0z77eyJP77OOyv/TCfhr677cLt6l3wKsRjcreorg5EZStskvVzGt2z/SP28wykh3fyZZpQAluxSgZNkfkDJNAwRsGFCyTgF/ZujCzhJ8DS8D0HklOiaWxASOTtDwfjo7Gad3mmkJss7ayXtYOqZLwHFndOqe5E78OpyBDZcX8PYbojwUgtwT36CoPNuE6lSdAZezFxzxtL/jh2ifPLf3nhVf+sTA0p6hHyiJMBKtrR3OTFfOK7T1RZBd5r2vb+hAktOmKp6qkX5F0JfgQJGr6e+dpq0dwOaGcYrR2rTTtLZqqnOrlmX3xLHCXiP2pNhqFoEt8HvmX/HAyWoUuyq7Xw5Mt5XEeCK+F1NYRyxSdp3n6a8cL8P/4+Ag3o0tD7k6F4GMlwP8fqqjnt05tbTxe2OOwOAG9mtK5MB+x9xf8YgE1UlRfemRdBwJk4H9uIL9R1dkPp1QX/tLAgM94iTgKGMNM9o4RxQNlshMEpxQM3nwhnb0Ic8kHR78TN6xDBSGOTva55re0vYMwON2BDAWLJyO+kwA71YMxu96FTml22cwXRFcuMbeAvV/nN+QlM5EaBS3LKDipoKrxkzENcVCFB24QDoKXAMISgIqwUigz/pAcZbISAwDMhEbt9CeOFhDQQL30AJDoNGhN7fye0OsjoFK3leMuNgCdQ0ZMTmsUe8hCRzdMFTb/9qWJavEgG1AXUBuMJ3HBfUZkh1OZuisK9I49B7LDKHhraIeII+YHBT1DBFjb8/PRteuA2kNeoHlA/cSC2wTaoHcOlpRG0RXOO0N5gQpAT1iecHTOBiwrVB3kHs39XihPkFSwHGJ2kHqGbYOy1+sEh22C9TUPKd1x3QeX9TBSa44qaFzCqSp6G9YfkAovCXU6siDmRxa1Isjsodjb1hrgbR76K9YDniQ9w7bT9SlI3eGXqPenOiPcFoYzGJIGaCvTQqP8tlhO0TdODKGaR3PUR8cSQMcZ9RwpL6HrcFyzCoxYXtAzY7cBHten1FfnOQlTqNR5yaQZol+w/IHwr/wdoZ6dOQxvOcA6mlFRO3tWzbq3AbSKvodS7hVYo/tCVUrclvRgmojOuH0zWAWR8oEfYNlco/yOWP7grpdkftqWsct6qORNMHxAxVD6jfYTrCsyiph2K5Qe2NN1NDz+KFejeQep7Whczak6dHfsfx0whPeCurekIe9yaGi3hkiCxxvDWv7M9Iu0P9jOSwP8j5i+426MuRujz5FfTWif8Ppq8HMGSkd9KXR1zk8ykeH7RN1Hcg4mNZxg3ofSOrg+IJyRuo1bBnLl7JKjNjuUBeB3Azsec2oz0HyDKdro856RpoZ+heW3074Fd42qIdAHgeTQ0E9ByIjK43RtV0g7Qh9hOXT3UussL2ilkBul+gWtQXRA5zeDOZkSFmhH7G8uqdxOGD7D3UXyP3S1OON+hQkrXD8C7ULpP4P2zmW/yqVCTZDTTyl7Zh6fFAHSAYnMZiTIw1ox7I3AryBWiEPanJoUC8QcThuja49OyOtQy+xLMK9vCu2PeoScqfoinqDaMPp3WAWSDHo3ujrX5NH+ZixLVA3kHEyreMp6gMkGRx/oAakrrAplq9hlXBsI2qG3Ezseb1EfYHkgNONUecSSBPQM5Y3I5zhrUM9Qh4nk0NGPTUi1d6+PRtdW89IW9EfWN7NvcSM7RuqGrnt0QnVTrRw+m4wSyBlD32L5bt5GosB21fUbSP3vanHA/XRSdrD8QAVR+qPsK2x/B9WiR7bNWrvIjCdxx/16iQPcDo1dM6BNAP0C5ZfRngPbw3q3pGHhcnhBPXOEVnieG9YOwTSLtF/YTkKD/I+Y3tDXTlyt0Cfob460X/h9DtpV4DwcN8CpenljMEh1HMIsfeUcsbgGMY3tHoMqZwxON+A+UUEPoAg+J5GjiORI5gdVHlPNGcODiiSA8q8J//5rOFhjMyo9zDiPVN4HBM7Iu9p9yjSPY5BFa/iUM9QHkW3R5F8T7VnKA9qalS3B5Xt+WOpwkPpBRkLppeyVZroSUWLGDdiqsWuBFkUekyso6EXnqxUYxRTV9czsi+YX8ugGD0IBWcpg4pG3yFzYetbsZHYepGnPhqMe7Hiil4RL1jXy6XS0ksntWTjVky74jtBKYC56o7SBPhI4N+CgeREMEPQQUc2EizBOEdbP4aNbZXklLpVRn4ivG1iJOfwJw1qnKcnLdW6tQQE0yYFSDZsgA0MW4CgM6UpYeLbmyBL5YeYR9Y6A06etB1YeUDQ/ESe6HW0oLzJYZInVHdH4o2GLV9zcLIG0efuBKoLxp4DnPEXGC1Djjlwo08jkD3J96WJCzHTUWaTb/lnjuqbKK3Mk33D33PwXMWtH9JKwpOVEI1HKytYVtm/eTs5A+Y5i2F8wWmP8bgL8bDZ/3IBuTb3/CGTj9PmuagjJeMyKh3Ry5wmG41loVRpH/zhKPdE7a+T7e1clfpHze+3JNLjR45hTecuKxebeSWvReQ/kCxun7NHu6+O4w0q9LPKpJrqJj0+S6Gfsw9t2i4pJ9ujILUMX0z1ofPzjI/DHJFqB/bjr+TU5HT7TM+3ou6F6GeeQj0PRXUiAnsX4budc3nLbFDU6GyR4jHRJIYq8vQG79DjNOVd6/VAIeSLZEG+40YswXXDGZ6sRJdDpP1torarBTslsj2c8gEs8wnr94bNDDiQ/O7RVFnTvEQl+Un9PBme9Z789dICxzaXbCS8XB/qRgXOywynHSm9p7M6A8ynqzuuMlBdVIcFkr8lho9tCho1iGa8vw1wYDzJvbNhXMqE2QVAIWll4SS9V0euxRKvSXaM+p1BrWM8syZAWuxmps747BSsRkHht61cv/61knJjmQJ2I6gZrX6i1TeWp1PcJ1fNgkPwR/qd5UuHwBSBK0WltSSaKGC0IY65Tc4B1XDT//49WliDSwJFDA7fvVZuAJtrzRKr6blvJwW3ngh6qgP8yXksi2oH42ZALcZszGUeayLAU6FdXQavrG3HcLhq7IQ/Cl01OsWxx0xaFIBgs0Lzl9Jlhqe+zUdzRNAPvaAZcj0TaXvUJlNA/SNpHoViRQk9FUpXpZVveZ5Er8wu/rrmu+ir7SkuBYPnVdTHU3qcX4+Z1gm1qGslk4FWouj0LuvhsxZtCvJdyeUsVEiiXDIopYgvNeCy8qlphiiup45zUYETqnD0bLR6bBfd+1mhWnOrbvxCewhn/cncxwZZJ8ADfP1cTjobS/KoKml+c+d3aNu73gSnR5frtkKQaeuHhV1ZKJIuWCnrIL2oosWy8Sjsd11lsC89aDfvw34qQRfOPLr06qBOg0AeXqbSEkPfeHSkYMrO+l1WxNF+qhZVTtFEpxWVFy2E6dKQD1Xar00vzyOGfjQpiK8nstaA9CiEoETx3vrbU2PyrFUDdSgCtPiTgz2cqvsD4Ll4a5/SJBrut51uwSBfm4iwjpNiwgGKpl3p7TvCBUACNKXAh6CBQ/Wb5nOaINgGT++KigjERqSSeW4GH1JcHQlCN55QHVKFgFPjZBla4IlOywhHOjHvC+urgx+klTUZfWnV0+rGAqOJMS++lLLdetXzGp0mVvPMqk9xFwHbZJXEQVNpe7CvpCB5eytChbkOlUnKzjtR9hB5TySE8xxMn8GsKUopJzGZPYLlcEXN03o6XxpZEllLYNn6TU9J+2d7zzDuNZSV1Ng09JOn2qeYT23Q+yotTqWAWJK2dMBMNZ/K92M/A+7W4b7LIl9znX2Lzognj/F/HfOJcjcvcb08EMLFb2eWgcRIHG0zL58j3gO0MhNUNyRxV3qUusb0LLx/bXI/h1m9BExGtahQ2mrjPSVLBcDn8WgAVZiv2JwlR9NKTkrQ4n8k/Pb1Pd3mbcO5isQhiil5vcxURje2Yw8Fvmrs3r1UC5XpczMN810LmaqYcb2+uY5HF5+NEQjJWcpNrV/NJsrSz8JkbBdWIiNdQS4Z3gMiUYJcUq83ZdO0f+dmj22d9wG+k+NMenrO2ZzNWfe4pvnA6v5QwabcnecO19LI2k/rA0AXgGwABwCnDsjjY1PvDNyTq/789k9VB6d3Dvecxu+sjjxeJGxOOzXsLmVstv+eVOh9KIZO6xAIuyfVJh2ETDaoofWxM2nomOuo5Qguq8qFzJPxKcJGxSZ9SRvr/uzJ87dD0uG6BOxsdH0kWRIfn0UxR3hcdcBy7lhXQ5W8IglDit8moBpFVqDLc66niddavBdaXyxaTkqSxhMBsQgmMyC+5Q8Cr9CRrJYrcVsd/rq49+i/o1vIwDDxdS0XI4fN3HU0ohgm8AUr35VBJEUcY2YyEM988P350OoSeXr25h11Y1Umja9MBFekufWfCmmzicW2pVpGmIQCVmhDf961T+9y1IKZyPHhTl02sMJHKLrUbq4ThvqsYiQRf8BwgQYGPrKt9WYStJzf2KaILMyF4l+ZVkBdMVZKiYrqWXHmn4r++cRjtlP110yg/ydxCgnsHWVlUBrVm202DlMjfQfJqYBVKpCe9+BxGGU3nCZwfSGAhlWxp3FQB/rejhWaAtG6cXgG6YAtc2Ke/BRmjG2YTy2og4tpYZKZI0alUKeZZASys26g4qyMcSnsWwGtjBGvmkaYCsWs9oloPxmn6KJgl+KcT6F8dJ7xZxNm0Pa1/YLw2h8l8oLzRh9xie8LcCkUnBrzbUJuESSLYJvESevXwp2mwKfwgw8Elv9FftNsEkX3QF6ouFkhKN8SyHbkrOFe9PlGUSuNU9LGWYBkXiJUNCjqpRcttj49nWuPje0YA552g4NTTz6kgWe79U0uSg1PqnzU+S/scs7Gna+U3YGNsEVLy1SKtySuO+p+UxJrkOGpLPELXN7LWhRWwxM4ghCPsvRubHeP8K5HnHXVH9Nq8bjAwWRl7GjHc8ynba6g61iDuZVLT04P66ZCi38lZKmBII1/+0r9l/vCGBku/G8jfYxCrtPh0/+yfsbAPVnEedGTlJPR4N4Nv+ihcToSo4B03rxcCyYxSSnnc6pATLBQGjrsKuBykQO1LZZKY+JXYw0ibPy4VTBhUHlLe2OZF5ZLtQwmalkb6hHyRLiVQHN1pr21u/gRg5RAr2kwF82dFmzhA50CRJFStW6QPxyUYhPxde7dqyGOnfsPp6/eRHlhTInTKkedYc4QfyUVOy1xfMmoIeosWBATcNnoT2ZaOuz++dT9DbidMoH7ThQySPyrtf2Tv5WyBMro/sT7IcSl84aI3EoUjo/uj/yL0SxcfiTcHS2nvlw8MLDf8m5Wx0ewin12eno0vjmIGVueNofvfnnq+sYWJGlP6CWkLOTdXaiu7uAETOxBfzh/S+AE4OfpL4zrC7/V2gZ2wO6zs0qKdR5XAC69138cpnoX9TgBwAUAOsPeVtjpn80PMxlCN+7lR4HZ7m8TK2Xy1+0+TO7jUemDE7CLjubG/xfQW/h6XHStDS4hMxlzTfri98WDNnpzkP6BwVGvvHAAyjQuto0dnNCZU30ZrjSODd8Dstf9whm+8PoMn+zXh6CIK7tPlka1/3HGuCFgl26/c/Od38JtvaXmEEjKo4BiffOAKWs6V8BB5QCkDCyO/1oNWoGGzSv134To1n3jM6ZB8/+AFksrDauJ79kz5qeNKaRtsVP77qJozK73UAC+8flUviS7NvZvDMXP8X/Wpzx0fdnZLv+VY8CGFPDyh25BtqPk2wGPSb0RFRiOTeYW6HawrPk/hTlC0aEO9yi/HLjZUtmZ1iVxPqvGF0g/CFtsdQ9ijev3E6NKLd/z7IoFhqp7F4WtJfZP7Bfu8R2XDcdz4X/gDM3hxxP0/f5L0pHB+LLJL+TKhs30fJZifHLpgp2ezbE24s13g1zOEGnAfqPklUT6lcsb85ALqwANErozYa8WYoBWNDPQSpzGeTknynGXC94vBwlv1nh8GmfKHDrm4q91oedZa/ofti3+5XoRlKT8ozTDO3VfFmHTUaUtWCAh8E5f+HhoFGmFy8VUaZ1KSh+5770pB6i+uDgXJZPBL7pcSWQtc6KhiJNWVmKq84+mBvxWwbTgGSCyJGIsndMOnL8z/loHObTcHRM+3aAXfPsDW1Qf8vIRRv8/R4J9Orr7MnqqLk71VTfe19TmMcFXGk5QuYzhPPFnBDDEbWqqIH8qxGYaYdgpQS6PvGuqBhiXFJs3EXtdZ6fnejrF2dH5MfVRb33lE7YVdN0L6d0V2fq5ntw4dFnpfMlhNtgP53bzZzQveIfKTdSMpuiZ+wTW1L93je+cpMJyk3Ta6eCfTdGv/bnEoyxjn7MsNtu03kXiDwGkt5d/G/n8v7/jFmirYC8u4s8WJ/NXxfXRfXjym+0P6ewWTUTzhh/CC5AUXBexHpbATDGp7W/d2KLgwlI50lGGs5StU8QX3o71w0JdJj57swTiQ80QyBuuPcpYfpfIkgSbLeZ3DVxwcC8SX8eFA0fI+uhmGMTP0ndSPOTPel94BSqgKud16T237KKn06zcQEuw0659NyglqFLXzFKB1bxUmYCedlss/ybHzwd8ncc3h9C2dx/K8i73P0c34jrdDAPnxEb5msrT/PonDbrrZ2EAmmnUy7f6N3+Sbi/HSuWbnxUtxGV0ctNn9TaHicaEfWP0cl7Gukcbw5flZARb6WX5J5pcJ24V5Uurth6nas3e0g2JAY35qULF5DNir/Gn/z+qQYSRyZvyRAcIM2rcXJQRGRm/Bf3Zyroi9zd/hafCNJmbLtF+b42wqr8iLN3YmN+8OxrQ7o3Wzv3s9XV3XVXrWedhCu+wLsXr3h2zytiOu/HGk31mhy9sch0A9onbz3bxdmO6fasG6gdXbg/svG0mLILG2I47TTvVeU3YedeJP2932NN9m4Gh2fGMqVvYl29QNRq7HcyeczupwY6HJveL16zvyMjOR73oVObmx1KR6cN9TWN087z0glxRoKTG9KYn0sHa1a2oojG586QRuFpg5pT/aU71vtSY33LhLZldSzNvUex8evbvusUi3PKZdGwTvuXzf9cFrrvppW7D3jhdu5rD5varXcRGobFy42t+DOFmCGoyN37gwxE3zGgTfukvJBu1gWF+Y37RdBonbqdtjgpt7iazTeFGB4DNXTiXeMH8dAGXlx1gxat2ydU3miroR3dHp3LBmUEicPvzp7sFkfFFjJe1hB0LdVyeBcCcOxZikKNua2Bxah2TNZg/F0hfSZMWx99Qw7KsB47wmmbEdA4daZRK9qGpGULKF4kZFlQwERwUYtFrzGyInnPzQ4eQzsGw2PDV1Zi+e9Whaoon9Y1NB7pFHgk4fMP5XlekeNTVvzvguR90NxgHOSPa7ZPPF77xLQrr1+lNjAFmKB9/tEXDLF6HZzzw8/uF6SE2WXqdUpigTk6KcowSwCgTPjj6hk7sSul1H3cBtwyuNAdt6ggaH2Ovrj+txN+s3jr0JNLQhH0hgr+QS+L1JlphqRcAv5FT/NPa5ihIH+Y2iRcp7NywA2CMc5GcXmRhx+Dxu5ZXDIQglRI+U0Z5/vnaG1C2i5QNi8nCnCnJ/L/Bbw9jE4fP1pNagZSVdLinggwJU32EipqliIfwDJoV9LNWEWsOSzI11T6DT8BTRE9PrgtVRJJqWnouLoFMADk9i6w9WGlUWUmHM0aQMCh0VtBhyMj3nBE+nRJId9KI5ScTEQ0F2cGfmvJ/QFFO77U20iYCTQpy1n9szkzfAqRnXIzwGYcX5XTpJomVhWgN0SmrD9pFQgqZZOSseb8ZJVRWqZles19aQeeoHp5zPyh3iphgSanG0d0Ja6IvAdLTZHB8OukjInqYPJa/8XFoUU4fqX/syoCPYJ2yekumXjje9j1ANYP3yKB6XR9EUUmXjDNEkz1kl2zNBpE7BFVUq5pJ16bDic4q+lmvcgL2NRRqFquLJb/9bJn2IJL8N8HmHdqD8S1fJ3bmwxMWgi09Nj85nuBp5+93J9nnV3v8zMyWaJnb9Jbzlguo2XdwzeNveIzf3Py3NuRU0RUL/m2o4skhsVJ3Bxi1dqqV80/rwpvRvN2cU3+aYp1wduxa6csY2QP1G4pkSyAWlaW5137SipezNcqYvm9OJNJ3QzShFS0H6q8yKWnItd3d2v+xkvVWzYuW0+R/FRLNUFNmyl70ypo2bwDhKNI9cQN9uCZzMc/1O6BCITthbZu8oC+qnZgbxA9P4Iir6BXcIA/vLJjs7+kb1Csut5/dZHmc3xfXoBPhAscrwGv1l1ZF6mX/U8BJYSukbslt+ZcCVMQ7I/eOwqXXBnc3KFFPeliBfSMvluyuL50hL9y4bt8otsF9l0wIh/bYP4vzjjb6pWXte2oJ75CiS3/C9IT5fX2bJnnFtPYxDWkt/vN/wIuZnvAO5UDazmiUE2JE+wct4VwxS4+YjKIVCLuhdf2/p3wrd0GeqZKrhDPs98HupGBbuv1CCmInxEwsGDWITLA2N8BwYLBowEveEOHhEIChIyIlsyCCHfYcCy4oTkeXOIj/qHjEkIsp2J5mn9bYA+2niXCrpQEwSg/m+LjrkrS9mymyD46TLiGTvCht+ClqxeKwBvoi3TuhF2Knix9Z5lPJ97T93pyB1ebiithkv1nKxAR2XLC0k+78DuJivdeQ9b/JPrFPvamIBNEntAh3A/L5EeTBJS8O3iM70/Yn+Pkvz74fbB4TjIYN772l2KJoWOB1PES0FOqU2AddP++W8QSzbNflJB5zkEpctrGnPmkvryxA+HYRSazxILJbZcQhiP7eHHFYNlkrpl0qinqoI3w+cTsA2JuCfWqlVKZJqumLzgAUNO3UpCFfPr53t6xhRycGkE6xxkYI75MXYoLpdzftyOpgEgQzBXqslINCOsbG8Iji3hPtMCcr8UksmC7ub1xZ3kyCYHa4yCNwDMMdiEnMjhhpzKfHDY2KqVpJM/LN2zgkB6tboLlY153oJyUlDdqUhrnTroSHotf/YEiox1FBfXp8lIqSqrpd/WZy7eH7zLVJ9opcFypEGq1INzEaAiNOzErz307iEg0CaLeDAdW0c86wN0L0mmqAoOS+px8KB9BobTU6IcAwyGGRn6dIM+lQdqLeivqKj7iFvFjCsJRgum4R7313Z0DRyXllRnUVVgvXMmIGVQ4q4MgRQD4JcYgv3G4S1nl0QbtDKGt0AVXZE+gtR9mME9nsKfDLjSKfLpzRQ2qHDssIZdP+5L+G7ZhEM2Wgd3FQwKFI2Wof26XtWz3dY6Y8btCjSe+uB4AgXi0TYsuZad2qQpLqVe/KkZBBvApPM7fMt2OJ4KDjWPFU+mdMCGOG9EHnnh1rcrejLkZwXTZ81Zqnqg4nMuM+U5Z+QqFyFG5FXMbDjUCdURiApENPDuBNIlAc8fI6tWVMXf+79BshOtuA18AiD4PNI8S7n52c99IwBff1HMNzo4rW1n7ROrybKWDnSn1YFJZBcl0HcbymKm5krFYx7MhAI51RERrC4NtUR7mhWH7gLbD0mN3LIQZvucJCBjAZtgxw8H/UvdnKvsBIyN7RMyY58awVezXbDKs+uRBVMvk4paB0HzDX1FNilGrWVSkeQqqUWDTbKYhGY8KUuSlTVF9WjF1sqRP1FExyF8LXirmQKpXr5K27s7jEWUC01XWHS9NxJIKZ3wUzFcTYm+wV/JdLO11/mgrAQ/+qGV8JncAjcRwCxRNYRpIJlZCtWHKVTEj9psUTb1sEy1RJFJ0v17qdy07kqOq5ZrkdDu2NtrZQ5zgauNswUGr6S6ziT1D4lTXO4eOlq9q1CtZi6aUQ9MDQ4PRgzyOqlIldFQoMJNmyV7/vg5sKy4Jq2VO5/uSR8TBGQwsKmMHy5diDUblx2NUkN5V1YES8y6JaXZO9JBqgdXJCJTJQXgnQTYn6NftBoQqQtWkeIdSpZDH0WXnGGIkVcrZkPM//tu2coaeLy0iI2XgDwN5cQx7uIoQiaemQ/7Hr6/JcYn9WMT3Sh5GBlq3F9Fc44iEJtlZNIeP58mH0s2w/o4dWDV58jJRJlr4o9PylFuih7pmtXrWamWIVXnJyTZKY3YkgdE9RGVl20rCLtNdEnBHEmh7S4UVNq+eoRhtpxcO6PFEstyIWbMrX5evpp3+zuuYROono4uYdxCNX2xkDW+spw3dQ5FqP1rlvpPJXCP2UV+9Cch5jLC4ObPTx6jsTBHN5zsLEhP/mqfhjwVYtRiRWFjQiNzAsVyxbyIM7rmDOKBAULUj8iHBskLOlBpKzqQZO8xT/iyDbAKn104rUblc2rFrbzEb70DqDVkJU25QWOe+MjD90lcxRZUw9cUlgSzrzYkxqTGV7jDWpD/SRshMb7iQKOxWdHpo0utx9+tSGVgiCNsxjwuA6AyxNwMmnUtS03gl4idhzVIopPy8iN0sOdv46k1znNd5Ff90nr/UuOOyl+b1RjZ4egRNLUJSzqFpHw+UsF+N+TQXPHeeiZNhwqzPH+NqLLGRRTllcmCDiWMDEkJYXoOOylWYfWQW3BI6NyJjp78bjj8fqiUOqvbgZWNRRw0prkjG5ShQWZR4ZCx7i9gn0S+ZAMRl6d/njrBbms+jmehNqhmBZFq+uTD42l4yF+G+qGBXiZa6p2dC8+jop7llbMyRMoa6jMB0Y1X5gQjtA3FIXNl2z7aj1CHCGTRy61pU/RdGWkkqF29Zk6YK3wTFxZOPOwG2nwW0KgqbpZqw657u9gfXiGWkvPoNuKrUAqU1STqgL63wNkKKVYc8U0ZdpRV4uSM6IDTNkPbOrLgoIvZrEJspzn6ish+GtS6JehSoWJA632aS/a1THMMutjplSng/adQD/mQKPk94UQG2YS2WLHXXrHl2BwvlMRahuogMThmtlyjVfpFyj9hSxChguS8HImC++tJWBJhdfHqSZsuPaomjCc9veZnMb41wlhYxdxmZ/xK3knY5uGbQQ/wKWaOhjSCZvOLqwMVjHrF9eZm3pGGGSCSHZMFYszyV+YjnAZdf1V+5cDq5OyVoWYD9wua7aZlXz79OYqjYRrVLeWJ1X9voH/u7y+RhXmJek9F3K7rHF0K9XYg5cDrd7Bgqp/jcBr1KLQmEjtYpaDXmd1eDbPbqjhw38rpgpytzQbz63FiRIX4c1laK1s06B6PWG7RPhaHVccffs95f6TCwsF2xuk2+TCJ2yHGbq8bR2idHUQznNXgI9znkJAlaGqdXDbbtBNYjY/GBZejIkG4v5W0shTMCMuzjMsSv45TrY4wHrisRKLNsU5qYzOTX6Z6Jq0JEwurSskY1KKpE0rEETWaL4DKfYbkHKWAVRA9HGOHwx/X5hRiKB/qG4cGsbqaeu6H2BX5kQBJGmaZvaGVZ0LHiWXifOC5Uq1QfarB72SNiVuTbJeeWGPVVmcT1fFoaN3KdQjWK6XhqNEtZqwt+JAbr7CTCYnZ0zH6BGlWgrPX4ECNjlajbHEAt2q0dJFYTbCE8OnBer5d8Nb4q9HcZkOX5SdAqUt1R3u2I0eXDBsUgjmL75t/vOa0ACMMaC3wFnLqORucCOvTCr1Z/ocxYMgcTgwXSVSFtBCz3MHrIA6GVLj4llY/bu5o6H7/gx7H7izZ+M67+MxIZi6uNQql1OVWaB9vCaQNvQYXqNjMExU2y61J8cC9+6XBBhdg6eGnjGq1rq8ogF6ibuQcf2MmhNXHVLxI37cD7C9QjapFqJCI6Mhi1tUJ1Nao1YsFwAIZKabGfR480HJB5n9K0JbiucX0PcwsioRQmm6PC8Oz7JIO9UUvsDxRN+eEhqnP1kZS+QY+/EQzProSQNTvI07mDLB06HyP6OJlc0dGHG13Fkh+HkO6hHVTtDzFfdFBVdqITV9DUre20mKOpLWo7MLbIGgYmgCLQudNAy9sVMHHXCmGD7xUM9LxgayzET/ULyr0d9L2PFEFTsS9OUMWVDGCmFbBLSG5tIKOIsQq+wpcNqTISrnwl75TSUdDK63eDTjFwZ8bbJLnNsAG5T5dqKpsl6RcHWuLJXv88ATxHkTUgT8ElP5BjQDorVNETH8qShLc4+/aKo7EkeMbhQtvgZYHL1FMnvYFJN2DPT2uzFrMkJVsmZbSSExRNE3RvbTvQh6QDT3UwblQ+mXKPNU4lt3SMtUAtiE6EoBKvJxYgAPdqrL1NHTFeZ0EHA4SpKYMcgudjApz1w40d+Ch5D2R33BpwMf1aBnXIyZxX057463leyaOz4cALE+QmBKiCOOuUeDZWs4ZfSUSGotVnKULb44ehKtIF6CEVSaYijoMynOzqdbKlN3SiQ8Rags70wDHCuG0SbpfuO5rtTWWr4SHEt6LqnH/eAGadtb/wUvYQRWssQQxuWPoa9NQBG1A65yh9U68dzm3Dn5WM9P6QCRi1zrJsYAaPgCy+N/e267txQEOVvzMPiZUduFvEDsbM79/xmaqxW7P3icOgCLtrzPY63bHssfbrF8doKqEMaCe5yBTEHq1rsgcX3zySd5MaWJgOGK3lrZuNBW0sxWCkkMpKLb6vReWxIWY2VVlUG7EIy7pSLpEK30lP0wURxiFq0igE4XYzWfqS0iFVkcVlIe2JUd314jXqCcsCX93YrD99aX9jvQgh5gNRYFOerHjJnnocDX5UU/dz5SCw90rxjaJVfL6TibaoOsr69eBEhIslXncgKF2JHEZMU2bZ9U5ZHFXlMtcwFtVQj9nmtRpBsdLmWnWytVwo+ZRzkQLbGeiZxu9EZjl08lgsCGFva0HZ/fKQlIuyoo7nxi9JIBgmbPnQKza5kt9sQk6KRCfFYCXDBPrZiZesjOJcEDO8uFRSiWEBgWjL0oQysFkxmSkisKJp/YjnFYidcTpAbsoZ+WMnNzVkowaN6P5X5yR0wtpNt9XaOmeJCNoQj0Xr4nvgto86pI1AhDJyB6rPxfn5ncD4uEx5T5XSsZxV/K+xu0VShWx6dURltNrnTylVDrhQLeC1+4qU3cddMTOqSa6eUq5I54q2T18oWGXcH0jYpMchWGAtXJqD0AePUuoC/wsqGHSveWFbKu3nZU8JskZJl0rJ18U8bmUHSb0IV9l+RJxg+K1vNVejDzlstO3y0zhDtRgP4zXRtzVsbWMTIe+fbqo0ghMRfm4i5/3eY8qVgLDNUsZI0gUkyYZTajCQZaviiCBqy9GuTExn1YkR7maMenhMFgHUV7KXH1yL7QAVfQ/uxDn8YmKtTqOn7wA668VC/LkHa7LY9AlYCGKLQqEr1TC+8btkJp1SftG5ZUrWhaUoUwYyyI3L2EGjQei9SzPjhFS9ptXVyuo+V8a4/zdrLQGtWBIfTOqM5WG5GJyAOUdl1uvKAP3Hu/5JP6cwdBt8JE18MtBr5rJ7v9c9Bn40360r57cJQSE6WsVw6eCqGaav4QqiPO2T6c74/0X1ex+0D9dwxM0APCeQi5F6ViFbGOS87aRxwZoSpczUaDzx5cDPYvPxM45KCgVHrBMQzOdKwANASyjliXsES+1vzKhNmV8NovXb4RCLDgkiL2vYEZCwsq6e4JckAdN3MP8ddP0JO+2eghUf0czJK5n+6B1M6Z3vSnLXgyr85qVdyvF2qipxm1xgYc+JXDCA/6bt5IpSYRL9ounaXANItsZKhE69vy4XegT+uByaHaVS9uBoG63Ou01s8ChU/dH7t36BhFHtyKq9E6XcA2mmJKARWPeOR2g1UwtEqFDpJGk1dlnPpyzp6kr65RQqrgwf2gZKSCfelpa8SXIcaY6g7C8HCpUhAdsZCxKVwpELCob8fS6GtGNyc4tsc7mUrjH1zbGiP23NdqnGCkNEqN3ZIYWlo9pLXPi+RrXyXxiVVfPTxYRiDMu06tFRf/qLLjdarLT2PrN/7E2yR+z1K29tme3+S2L2Ndg8+fRza+ZPG7jwau/cI7B4dK4Taev2JcRHGMNFibCfZvZEINDWVP/2n2XwKHth1lIrr2zKOFkrCohy3BqLPHczljL5mk3WjaHhn+txHuWP9hjc36lrNePd0LrBFNLXTgZfvb/HkkP5jcwuPJ4AFBwMXZG0SPO6ytk9R7Fxn85oS8H4H4al11TLihp27s71xccPlTbfcMlV+xvkIqWGasUCAO4yd8FB8CEksdP+3RF5iiE0gW5GmHqOqMMXcvJeQtZJB8zETAi2uxTBLQxaMCAVszGN8XYsRbyhEOSu/bdCDMyupUAhhG1jHAdI3s1LK7Z/MCJ7aKOXmPogntK4n5s2/fnp7TFqvzL0ObktBEaGWPrsRotkxqLKCsDnn5rlhIvHynuVndBw8YswBdsAsbn3uyOz9Th9N4eymy+dxYcHMFWfJ60NYsvxpJ6aueFijQLWg9HA1fb4m/8KtbYzO1CPC9aHcJkB2Xa+2rlFISgODy/ic+qNhx9SUWibwBFo1QP6kZXsRFa0fsxOSbEPIc+sx6llVAgYUkzwffVqtGofyRklLRjYrcdWilM44XRHmHpBsiMfTn5UGm22MtnYY0u6kcq4Sc6VCwdZhrztoa1VuN5YMXyBWaVTjC4qTTP/RPPAoDhBZtjy2xsBP/NZfo7eWSNuQvRQ+VBFxXl5DvmAzFHJNNwQ1nD/wWC+9HZEreXFIwUE4wdngdBKL1wo2l1CwJdrikjDWqLmze9kNG0sb0rBo+uePCvQPUcTX5bHs6saoyWI3P+MapF4ciydrhkKTlnGDdZN4nsTkSM11IZjChETsTEyBapwqFrvGXnsYPqWrbXnnpMNGhc0HWvE02jgpTjJ9CHxAvN6Mibxc7rLYkgTT6yc/AFnGGWn0zu/m4T5s2+aYwoBf3OqDSxeJl+5xIYDIZiUxcM1Up95JVwzJuE9G0ddFFO/7+xBpE6E+S8TudPl4WLJZHBer0caKwxgcXoSZkKNtUT9oJA+48WZpLESxkSU5wSN95sRRuG6aTy1dOTxqn7V39tmaoTxo9o1TKoPkps8wDYebRhtZBM88uCJ1q5w7MOvk3NR0yoRrrtxgZWPWala9U57zRnKui+EWQ4vlBp10qZJ3RLQSx9xsvsGkZ2mYWnyrtLu9vCFn61FaB9qgO2zIoBFZPTLuwtWclb0wPmM3PQEhHkHPXGJdzpYVGYhXDpYT5vKKBFaki5RaAV87j7vksqveNp3UOgpzHCMG7YALaYeszjUEwTMbs9c69cpv7gaE8tBA5ro0IZhevTOKxIacz6Z/JHzVVBQ0m0g+emAO/JJK7OC/XED0wmZr1Wz4LIdI1dEwjBeewfzYwWQkcgMgCnEujxEdc9/mQjqMCk5ldGQcsH/sIpgYwDh4lPRGDYJAUx5ILEhrdpsFxgFhM4sRdcIUTyDuC/HS/8jkM7hA8qLw2fPr6L715Wi2J1sG6YHcG8JP8jR+IzyU+4QqUIqtvioECOkCS0B2HzpJ+qIzCJYH+D4kWR1Q51sbcCZ2IeQZBOcJnGQdvEP1WxOzqxugp4Q+Ddcr0n8nkns/eibXaitNnVTbyKKeAHsdubloySWG911zX8OK+5biQS55yT63h1BxgvCPOcH/1yI0CD/qMYk34Yf3rF3M93wQVfn6xgUw/xiHUcImcgksUVP/0FnSN6Sf5cL3j/X6kQpI//TfmDU8Ev/EyuhP0rHVJdDYL4/47i0SS7WAjXL1hNzY0Ex3GMdk2MTiigbwNUMtWqbJOTPx1SXZCeumdtpOfHniXZbYYMwaZtp8h7ikMcrgQ0jxLVxc4hUfy67BtuD91YyOnrB47gk75EVsHrCcjNTSfm2pZ9W5vG2PTz00LuMZnupLUG18wtXetzcJBsfHntFFJbiGWa4Sjyk4u9w+9oaSkdp0Ca/Wg0reksqX9kCMxiQ/doz3KFE5Y7eIIV3WIybpkBeWiTDZ7AqKpX/6IV5hrwU4SDIn1rIhW54NaiCdr6qhJhKryfq/WiB9moIHlV37Zd9YaMELJxXn2AkiYfW2wlXFNW+z7uPknPfsUTNfTh93myY+ljiGYVSfc4Hkw/nyVsBDI7cQJyx+jI+OWMQ8snVhIoGjd8vRaFb7Mycd/gpXnYfw0XIvuuFVB5/NF4ocM2s0F2gaL0HDZX7hnVl9xwxmeGqQ50uCypdfmfXLqLQnmvfx+M64qm51z/sJdDRM07XS7RoZsA/ZhKD4+MWAMfw7R1Tofug62SINu/zRKUVNzNbCSFSi2z5IMg+DXQaz4PHqczbbfNxJcqn49rEwQXm9Xz1xKRXhXaDkY8H8yXZLCD6uY7KUcnDYdcISBwMtZwC8R1qy7SAj45HqNVTNaGO+tKXJkBQnfr9FJi21+Mx6KQ6N1Bla2aaunkePUz+9nyjDks8PlLKgerdZ6zRplY1hQdlIbXuLiYyFARtbimf4REfz8k1ptvojKL427L8sjJlQqmaqGXWoMpYJVkdWVfiHhaSjASNcSS5FZzFRZ/yEBUrAeykA7+i0Jqw/IezenzB2jyMXg4ZaaD8KWOcyuu4b8rwh5h/aj81UKT920U9p3xA/CgOHdqZZ50T2eEDDPhLbf3SL8m0ZmgipuIlX8LTWANNZZXRrbWp8shC9qWLtSGYZiAFHbKGgQGuo+FzwDm8kmTBXX9btH8Ieo5Djfmnsb9b5E/+GR3ak4iaOF7Smur640B+O/3ynGMPcWQwmWjvLZsf9yYCsUHdlNnlN8sIlRNUA90gSYi1puloNCdKRPuR0dzdx9h9kDi7ZwZU9uKoHl+HgCh4Me5DMdbGTnkJsH8zcOTnVn+oc3zxEJ+FutiEe34N5cC2zlFRF/355+GcUGTTGnPMVBh1zZERI4LBXEf1D1mNF6udRbHXAYgHEhHE9pF+GbWqkMS/7KrLHq/5mTdxsGY4np/fdYYpzyDjfP6unuDS67bCIfjJolUNO9vFv2qLCjw8oM/+2IKMtpwMn6ACicUHfL+T5hWnOda+PWrfUdM9sD6Ypw4V4EfV2HqCyFsrM/1UWRSyEJQfUEu6dzL3sLtLBOzqvzh0QLvemR9bLpRWIFO3VumKnBAYDE+pFDqaXBmiZwVlsiuwNuyrcXyxt9VgNUr6CupgWzvq/MKC6v7UYI0hM9FgO85Arl8Mj8qwl3vj+WMNohasmzczNdIR7uEK4ogcfDQY5+Mwmuq5SbfdNv89DLGKkHcFCBI3LqtZ8jLps5mKVtN4pSI8X/8e40nGoJU5p8jWPiO//AtSzjVeOmfeg1TV5h41JsjMlql6kcn+a2rTRy30Qs/ZO5ZHfjckP/skvt+i/74wvSHDoSM/lDEdTLlsP3BZlHDeXnxQM7U4MLydi7ABhvlAiuWGLouXrY72PXH7hHJ/8I0uyca8fLWbimgEGvWeFa68uabbSe1i1p2vcZi5vqIEGlsVijrOQuk900AZC9UMwwHOJGwrk4HlWD2VvlM/+VayRMmbiTUqGCgSlTw3dMETAMeJLbdVc/jZEy8bgSyuGWFRB3m2krfNbAH5mGz5Ci84fmdDGYsQRIybBx5HYykXU5YqO7KxUOlwW3EfRmFw6RsJiHh4CpIybvHdYPNYFFQQhIu3ltM5evE8bnCblGCktNM+BuwuQ9nbw611gQbBPGLOqAjNBYi/tiQGQROJz106WBx/6KlaMxwH7duqE6H5g+gPeM9refExvHxtP2opZwqFPWy9E97G2mtaCBPTb0KtAZTUSAcnC0/zO8tmhL7Fp0uxYmXM+PJ4/uLoT6PDx1lrYL9TAj52kGtqwF7n/jX2Ze5Mdc7JP/Pcn4QEAnDMCH+txtPfzLOdQAPNz7/3lRfVxsHyCBHO1Ec13Tz0RhDsu1H0p0VyjRfPgAgqwD/YaAPnVl8d026jxXysi9mr2N1xAdXvjPvuCeXbS1ibvdeWDcUxxm4WZPFMtCiXGSAnSKDV5saeVKwoVbrCcux9ZDMwrJA8yxl8TdnAqQpYzvpSwYVpoBqcl0YjUmeRQz19o0oRpYi58NRLpt8FzyT98eeJ82BW4wrXjbl9t581PerzgaGNp+14lkV2W1L2LSjD1yWXZtdJgr6fn63/18FcbDIUb6rJrmvekUuiZnN/4+PWv2OM6MywudjooCJgMHP5sxStoux6T+xNDf1bh7hmplJxkyJIkHYZitfvxvt1fBas8b+75anjduLoe02heY+Gab71vrhdfyY65qZXBz+NRGqJdMfQ59n4s7ew1Tl604hoxmZcyzcPz3hrnfjtWQPVSoD93cSicIgF85rcwzMgbrJrmNCDH+DjW+BWHoo/xVgDrg5huEJrp9B7MgwTtuo7EvkkR3qzK2I1WyvIvnsDweZbx0DbOpoxzjiAboTKu0BX3SjUDJO3akLSmPHe/yIdGXkpdj4mFTYuXxN8bbUD/ON9CFj5ZqUgVXo63LXMROM0Qoz57pxPjm0n8ulx46Qkj7mJfFKWdryLIRFVJ5+yX2+KjzYQk4L5S8xcMI2qixhvmqpniu2ZB1q+D79OrRGqfDyFri1w/t10SIwxpqKS3gJ3wL7wxAuQcrTgsIUARrCVXNpNbL51wg4Qejz5d2mXsM7oJPXvhB18CWvzQlJasmjvKP29knllWAf5a9Mkwtfk196UXiUc9vv/z6NI3fx0E19+cclRRnHZnvgEKCPYqxMdqDtApin+hDIYAO+pVm7Sd3ZP97ImzpMQ4F1uU9wdmrkPW6wcXMNKaOOTiSbFPQ3cjPJYeFipJETAab8j+FK1OjH9zWrZfDYWVP7B0Zy/Hq4bkJLB7mgfEvvMexhGz9vSlHUnaPBGklsaW05k+UUVCexvKotEN3Avn6v0JmFulR3gpnS5em/d2dSKJdiL+014g1a58yEZlyUUq8xWuUA0ZTVhQ6d6YJeDzoDLzJpU53e4zS9OlqY2A3ktTfvuDtf3PeZsfFJoUbtZLSpPM8EBSxj8EUf5IyqemduD8T9FDluMpci9rzc105TSbF+G4EOvlFgGlgjZLrYhDM+OS7KsN8+ivpYUor47v51qZWBCwGFE75QRMyKh6KMfZ0xk+Ly1DCPW1oi//up7Si0RYbJ17F/PNDdCdMhHKsXjojdaLzgQYPSTpp6+Fp+weWul+MlOgujhnvKZ6HSpM/LL1mftSN9pZlsfgkeN0aiGiPPym4yayiQiAEbGbyCVxENRmXOK2SbGuL1WZC/Zi+azKyNi2cu7kz21+FwrQqH07L3NCklutYFjHyubvWneN2XyUUQuxLws7T5kPqmhm5OD2xO6T9oXS/1Kx9B7V/md1jdtw4o0G/cd5DEyM80TLkVk8202w75WT+o+Gf7bwfl+W9XLO/ZTYh9Mo9zMH2pjO5YngvB/LaBXt2b7Vmh4AdnP5Zot4NfTITKvWjLPvOpVunck+y2XjJZU8I2brqHzsH34XYmPxaL/kD1P7cKIprg77PnF8gdFSuGmzDys+zmj/ReYxI3tkd4svsJMj8l0zYiFFi2mMP7luECkQerAwjYPW5cT3wb7d3/zxlO/iqOdfJbXN3j+xrQgbfz3OQIAnDUCZ0r2+gR3PeGb9uqO9bSzVPhMRKl1dWxC9S9oEU8ehoEyjYA3iQMEU2KDjOMqJg/YCIvl4wABEh7HJjziaPWs0O1vtnFBB+3OQWpQ0RgBmq7ezLzewmp+3If8uisifir4yWQ1k5S4bG9mNQLsc7HJ0ly9OgQ+RBfqqk6DsRT3PW7DC1ZFjB4wmY6D4kndxaHJlhABnkSoilbTvHWK4XScU+SrutEpWSwjqEITg0polFgb5Ju4M7GS6Cl79FXHXBVcXeARO+14/xWIxiyXzPj5qOD55/v2R1Jr7ONfO6wXYRr1v4w+7iyYfdbNor/nx40VPyrBJFvit6ASfbmTkBKsoevoRWVvw1gmlFi0WKNfQeXRH4uyISYaU1wVk2IUvWVgueJ3JqFZsiYZSVhICp/Xs2LZhsoArv24T5dnEZva5WvvPslaiGhQFbvO6QosoB/S4L76cEqGOQMKhcCeDh5u01c+uddJIuNIIh8YAL1//q/tNz9B719YmZGrJaxlCBb+WdeMwOuObDS36dxAicDNaDGLluOWLWCZJt0A775NfgRPjnO805HnRtjzorn25vjuh82p9RejWU6kHtYwDOP2uISTpnVx/kELLTHHvTnzHGaZo9cZ9fRQaIoiTqOnRE+02G4YhwmzGiaL+fh7cI+ESsbDWj0UybjcRork2oMgYyVe2eSKR6tGO4g2WLGThRYS5xNKaCIR4ASLe4mPW9kHDMm0Vajz4q3nxTCMOOdQ2D+MGz2MZMFdd0iftYe1x2HgjZWQl8q0xxsOm5Ly79Lb+VUGTd5z0gB2/KhV4+vjIuf78EDcMSKWsT3VnrMxWsFl1hnGO5AQof/GjNvGBsD0+cqx+xAAP7ApQR0DrYP24iU4SoBA2qOR9j133Aa+5f3PeZVrVMguJLWh/svVWJEbu7D0JMDXRaxMvawhj9dlYFVokJkSyxnv3pFCfiM5/0mnN1fVL5tY0rJ4k1GeeFnD1Z5ucaosjPaaVB6JIzZlqKgdmCTESOjTVrHnJsI4SiAWXDcBrGBbqU0yCKD98exOIcGFM9ISQn0Qbmv0eEpNZipNAhLOCPsvjlJQ0NZ+sgl1qilURRUiq+Z5+9h7bFzcu1y/j9xepJa39YiA9T77uiU/40GQ9SOl8+x2VjVbmjWQu27pUAL34H0zTGAZrACzbl2vomPAVT14tIscyzjygS3hMR+CW4HmzXpGyDyQ7CtY74SV+eX8KyyODptYKstVKGYnE2rKinPW8LR1KruGNDhraqsTN7xcGhEloLnlRuEXFVjWZOzlGHhu4JjAlobQ1jUoUCOPyTcxtA5mNpdYyZkbYrnY8nRsb0Ti58C1Bh+YuBGd90FFGuGCmOB5mPBt0DoA1WfRsqOjPrCWCgHqsldrpxc6XkmerBU/GYBRGG9CLr8CP/3O93MvTq8LBtl4KaIraoXp4KpJZmCwQB6VQLK4QNnaQ2WIMHn6WkzaDhQmOCSX50MlItKqI5q55NKZEFsRBXprJJd3UXfxbenEo/xYh5wJKEf78Mw5IIwPtrLKzEjBorYnj/HMYcTNWop9YnmtcTSouEyf0tJSL+u9ybfhSEVmrkCM82/6mlnQOHRyoSM1pZpIGr4oqeltxDmHi3CAhZyjIb8nHrb97drkLSzVRRffXZOuX4rWh2ua3cBZsoGxvH7/e323VaXH7L8QxUBYEXuoo0ooRGmcS/megWU8I2LbgPNvNpB9BGuwW1jj3Od8/uSsF2gSdfhSRDmxE2rBdZvL02PAg+AkUi9Iiaq9+rjGLFJHdOF/Z0ZgaKX8DQ+uZJsZEQd9oqVrNZ4yHtlJpSdPIcFTQRleEmrhjlkgs+453qbn4hxXZvZ93kcO5/0PdxOEcAuTE/EPSicn8188r+XYkj6MZBCcSFdaGYqlfInAbTg6EbKeht0oz8GPA1Nh6c6CCVNYLi83tToCN9mYRvzbqOc8Lor+qiYPSMIPTUmDie/lUGHyoH7vq3tz+k2DTQbkPBkCY+B7TIzmzteQTP9TB54NZwHNpQ8NjOw+qFdmWgtW5gnVDTm0UwGpeYGQC7c3y7MDEQsbounmmgwmn6d2NbUaRM+XOuOItEd3x+m0nPy21wzmrid5skrBu4MoeUqCf84Sm7UEi49yv/wA8+Z1fU1rtoPddbDZ9MBwDqB5POAFWifnftgfLUNZFmBBNPEeBuBvVsawbuT5KNW5Y+3SbuPg9tDSF40K0guI6r1FSdJxeBKU0QOh5E5Xm6qjR8RLGUKqgbDwv6t/AaJ5xWm3JZRG+vxAMhz5wobHnz3PE7GdZQ0h5RrfpEpf4rOH57F2HFQ4p8eIGuoLYHXU+35caQHu1G/tvDfGTaqBMu6bWGa565Iqauqh+Hq+H5KYagsrHCbs5g/tj5HJ1nqcw1H6zSJbRAO60jY9wrD0aoGDi1hDX+/51eu53YkbKiYhpOT4DoBVJu8a0qQm0nCsipsrZ++swTJ1OKMn11kNbyDCPra0grS2fQA0pYBXUo1jQ+/5cYtXJyH/8MNdna5VqQqHr6TNb6TsLOCHUirss4Ha0hUnj5zCq2diik6jKiLtsUp08abhwa/z0FGHCBAy33Kh0vB/ve6O3xkcNnFMRUWeud6En5w5wE84WItg37d6JSvraIw1eVMZ7C2LYWJtiGC8wMh3flU2tAyb0lULRyOKrzFPz+rNeQbXFXxUIwG+jsoga+LPeqpDIGuKGf2kCDNKBFQ4VDlzP54V1plPLqL1MbCr3dJnf0NpYrDTWy8WPssYUd8ibLO1JyrXtKflXaAhhQFMJ9ZQj8Qu1fs8hjWE3QBtEW78ROrwKnMRDs62ZpHulidOvtnJ59a6u9OFTi1OkbkkTkx86UY3t3mnLnP1RHgrpfP7c//L1HDSu7UIVtOfhv7WHrkW3mOCr4B/fnAMHdVxgNKDK/NWC6a2ngX5nphCZbYWc+IPeVCvt1APBF71cyc8f5i1gXEZKjujFqTSQD+YtQPca7Zuq9K1V3tlh0uTgN7su7RUJucgX6/YFXfvRXHZnRJ9Ks96qxBc2et4SfRsHsa3B1HQpTsJJMTgGE6YeeDpgwo9LR2n3EOTrpwYPcAt20qPGdjK2066OU5dreBcUQDdvEIjLVNE0phSZIqoojDxnjCoMfc4LKzou6YM0AhycV829FAWHq9MVrhsCBtG/V78Qm4AbrcGYRYNPpmnnfOppod+JyUszOwoO02+/KlP30el5B1ra32CFhjLfk6fia5VaoULN01WQRtbPGRWEKIRo+kp5JEEFYw/drIVwG2D7t3EXCPy5TcrXTU1jR9/NBICSEiGeHcrSOD5VPMK115Zg3AQvcEOLlgxg9h8a4BtPsWwt3I6NucoSQWjXyQ7oMAC03PUwwPuq24/QE96NMFkkBfSjHy8GdaDMeDbFpINsM2tNUTEAJwqYhYv9LXcaRtc6pocWi+8RNS07Trdg+0yKttJpjbz8VNC7dcnTJuP7ME9RIVp3fNZgJtLS630ljwaGRXTYcpfrYsmLdMlM7obl3w/Az1sSV+fo4yyZSBYyNhirD5toIPewY0Nne0Rcz6TEYYPfRAAMDr5+ul1p2pTVWWqfG5eiYLrBt0nh30bCj19MxSw2i8IsZU1ybN+Ct8X2fbvgh6WOoLhRkK1P2Ffyat27T0/EbBllNT9czB9+ZqGybj5KrHuWyMT/xgQLJrr7j8ilQ5LTO9jIF/UmlWKqZboOIsY+efT4txasFw07LV/YgfoVZjjLrGXuCcHRgNJ0bLF/QwGfkaH6lTfB772DuyvXmy8/NLgTbQD+sFoyQ3a/7us3L5HvhO+RHicAgYDalD27QntY2n0rSPoOL58WC8Vt9SZndeCgqM9bh77uro1eWfzsVj6xg6yJEn1qMTARWjxZrgVKyyN2aa91zT/izn/FkmdeFGzVNQAiWySBgbaTUwYNTT5LPKpEElbFbCZQswE0oiZaxV5Ipx4lqOi3OEY4Z+OPfNWw777OaBTL9jzxMcT0tK3dqCFZBdCuqY9UgBzGXhk9OzjKd3xww1TP4aaCMmV6prDhKA4sijjSoO2d65+0gjQGLQPIkKZFBxeBuHxtT4X+hfrbsJ99p2U7JbkCO4F7f713rNdQjQleoplIHT+4UqSi/hMTXLS62wCSpylj0iX/QAh9+oTrqthQgekVXxUbvSRQEnLgAWl0ExR0DEBe862HcGf8BJN6pea0rmpV83/ybCkNomvRD73/r2ZJxyDm4Sg9QqXCmO7WIZwH0f20lGb1P11uVs+tGj6ER3Sb9O6e1KyBwEaByT0KPHqvYrmFWTSkc4lLa0GaHw56SeOT8Ttv7dplV2WVV/7ENtovu3bVVq9nhj/YzPpIJyyZ4x/ETzuLBNO5ZKKcvz/AalTpx69CImPpOcPn1HBqH17DMk8RX0zzzRDP6NYpDIbypwRaSAR4n3P5rqVYKd2C0v1vuP5Fkd+buL4MSehOHK9t1TysQ723BCqc33Hs4fO6sbi2zt/Bcg94jJ/2gzkd0i+EOYgS9MKHxRspE8Mh5RBcgiWdDzWjm6e6S+Fd2rUa2YOtL66h4iLAGgS848YN2vUmoQVio4kv9qgBUx6FQQZPBgk4w1+/OodpY+xYjfcO8zZBPLHG6j3CT7wcma1jpmJh2MHOiWOB7yAHvP47EM4jb+SE2kd/T7Gay5W4QcJTTLx50b1brKitUHe0q7Gp8QEeJxeccQa8QbjOkl9SEkH/cTA9AtvcAY3HYqeF++DxS8iH4dnRDQjcJv8hEoYkJOW1uJp9wBZQ4qR8dDzeSIiFa+Yy0UVKMq0iEezKxsThQfC/I+PbgQoOVsuJlhcHJe8Z9sU1VvrKC8Vmq94spR+mxH/S1jF0LFZZVSWLjCiE5KARCZZa4pcb1DXBNzlcI81c3RsCuBBdwC9lhq5Pkapmdhfu157lKIROk7jgYPaFg5ImP0qRiA8u+ulPWjRpbUnSdKhECAXlHTiTZIzvJSm3FZy2U5DGZXPkRxUbPwCQMVyy6O0A+G6JMo3TtL3rZ8Y16I6OtTy2WYcL+pESLyJADQC1kt8W+wpJAQjT/GWZob/pi6SxgpqBOimnQu3xS7dtixSClzyqrMSvnQfa0NT2d7ssX52qxvlHZmUzpqZdcyPvuSSFNBF8ezM4CKBjlQ7dp131nxeYg4r2Ti0eR45H1YFvNAJCkFd3f4qL+eHG5HV1o43Oh/amAMkvXRB8wnl0cVsdIJRkrL4MAlWHTOK6Oj96G+YdqHz+ftI30YXZ5QGePQMwrS12scEZHsZ03KH1YcAzN5xWZvsssmXHtn4u01zmCE69WfMFLQy+XYyD+ZJCQjoXefRYIQS81CrYiGF+EPqEIhQ/Mr5EmkySj4uXNoRO9ljyM4ETUTwWZOtxwV91QmH29geNMttwGWpHPmFZYOEqM2lba/MD3DsYRGMAxxbmD4SPsONPh6bOPvODJS7BtgAvADJqKXskfAp1+i2jg3P9YqSUZjEtKFp03/nusOrYC6Q/7IS5AHdDPfWcDqiJhmwvLNXqVuOX4adetQ6UkmU31gcrqHBMCqyzgz3Mrf30f9z+9yLLJ4PxXMJl9Mrm8TNCX/gkvX5JI4KUXNWhVsbtC++aKjHT7bYRyT41qbU/HHpZXTr3TErn935ZB1JBHuMEhkcUNc7ZfNwTv3u3DocxbKzB4GSKbT940mm3ntLq818xZMPd3fPcGe7jSNXqNWCKdjORpP+ME9BI9IXJI3N6tl7ZlM3S3NfHel8f6319Pg8wrA6ZG3BPy11tnb7+QI2b3377FUMSZhfQIPK9YZpVGl0kZVIgB5HYJLsgriFlJcp1aeHYlWCUa1wPAmJw6qVREFgsRbZ+50+Y01WBZvzp4VRNvF184eWN+azUyPU/cI8oy/wRZWk8og2Kwe/t90m0tmgYHvluOTWCTjWil419bIyKIYWKVKzQu0jRslurW8Ss2G0PFusZ7LZyZ/HS6VY9RPjzBknDz0f9XmKZ81ND4AoxEG7clqfG4eOuDRPlQnUU0mDRg+lYGi2A0pcygiWt0+m5Q17Pi3J/jFWHS5qf4S0+dt+cZS+vi7fvMescG2J+p3despFxy05ON8e8y1t+fUuvrmnmZD+2Qfhl6wvWY2Of9Todcsjm8Cbh944RlL+HoVEH5Ys64uHqUnJG5WmlTyWAB0yIZYleIhebW4pGMhugEG8SJzBrA7EkyFmEp0mc0+I7c89fdTvjdpZkpgG7Axh5XdrbW3tNoXEDUyUqAegnJXuDOGyp1tUDZT0adPbGM/5Zm/Be/HoBMrFWBo1f+1scN5w410aEJ8cahbtTn/BalBQZTnjQpEqGIvqz+jBX592bmS0j3pj4S5U8eIUTjZ5rjhUVE5rfhAObjOKJ3mly38sUBPchl20NM0Eucq81P55h49Gv6cFRupgmpt4MZzixo3p/ggmB+T23e8KdHrWrLw1hMmC89spWhjRqOCYNJfrYETSBzYcozRSL3mHcGIPnw8V9sOl09jGykfmGyvaUKAHwctdvpv3HpwoHDTGn3oTZOmW6gwycKfPs5p08ILzM7YMQUSqrVaqdg7XCS9yn4O0a2Gb2hUBxZjV94JrhKq5KvRpmI6luLRkR11+Z5ut4tsuGgcpW8+LCNpr8OtUCNEdWGEzo1D+0zulapjFmao8SIDMVC7mVLW1tjYBvudYBAhCHFtfjBDp8NYeDBfTJM3Ef1gW43Wc4waEDM44WEF0Sl9GO7fcBSDp0GoQ0LbWs8ciNvWQMuhEWRpqNOHO+kUgaev5s3DY2AD7l5g6czXuY76haBkzY3ly6/t9iNqna8PAHHqwv3aRsQR/7nRKapsL4bM3uA5I8bxgM6SF27cUrBVoHbdnhWWIqXdIVJPNidp2HMyNNTXWwfrz0Pvb9EoJ4hhwC6e98TKexxaozAm8ADfiTB4w6cAN2vfDdvmzQcskEVixJ5OpMEh1uYFbXp56wvCRxOjisXB7GHJJ9n8blJSkCfTqeCwVhb3Fj84KeIiCe4wusbnplJmV2jFEoOt2E3mVPbGwexBEUd6/43Vrg8/1brD7SDoRXby6Tw4xubF6emch6gK5W3LZYHkFO4oa1sPzrOgJCAzbwVsAPCO5LFeVZf7YCkXxhfwv1S+qb1Jp0yNjCYmfTdKN28pL9p0PuhhcW7IRcR95XcYgO34ccNk4+7YwOxzfdrsGvRKkat+z9zL28JytNxZh/ZjvITSUFp7YZLLmabVzCwWg0P17exRr3wdIOdd6VwzBVH7ByQ38rkBIKpo2Heb6d4vKJ2Q1Xt7q6vjJQFbz1SAfLBuGBnDoqsNPtLIl1Ly78I26IxwGKvfRfbTUiIt7HJh4OWJr362P9bkb/mZYa9RaI2z9/ylfNV31ktD6Wcz6ZlDRx6cQ+LO58NYeB7f4NYfKWfjtXsQMd1MfiSpZN92El7MkXWU9v9Ua9NwMpiIKgLwJp9OSB+tFIBHsDD1v50+uW3M0/dHv+dAJfWagAMT9+d/c+UeRPwVR+wQFGx1/LrJLLomoFhm+xj24b7ee0IrL3ukiOpIIE26hi+rV51uD9MQpqFuYAtDCnE2IZQv/mhBp37QJg0KibmV8BigYJv4+gE84+tHts2wJCvUsfXaFwvLZZs8xaBTzkMbbcjtDZMbZjHq9L1tMzzTPJoY5PnOWHc4X9pfpJVNj36G6/Yzzc8jcPZJi9TA9qox0imm+s/foxH4n7VgvCBSFV1d8NqkiHW68Km2GcqsICyjRDV3aFuLRqVa8Wm2lO45Dzz9OTOkDeNzaS1LwLivs76idq0XGnjTU6jsefWB1HSMJ3xAF9G4pdlolDXp+4oRh3nb1/ITrNGJu4sTE+YSObeXl4heG3Q8SFN5lK5+PRtHpL1UzJE60CD8TtBogwWCvwdPj60fIn8bLQ7W8hjM7QBIHbnYmoJ0vyTA5bpGM/HglSBs7j5FYzpPfUusNlCdZguiut/pZynAKYqINLYRsJhRnZeD0rjk3SMrK3eHA4ZGujRHy7tGYIcXksSpETXaMySesUb33gl+wmF28yOalTEzjThmKyEAYlPEtgOsO//sizjFg7sVg3aR5hJ9sVxVEiXkBPQUw3lzYf9sXp2okL4/8/veTr7rK8ixso/zz7XmfLKs9agbgTVVehHiLO9fpxcrWXLDALldGOTICwJmZgfgtLeY4P26DGhbEriuOhmwWdhDXrQSZzKSNcHJmpX5op6h7vc7MfM+OBw4UavBawxMx41HDq6Gye2Xkv5w7zHiE2pp0VLzmG3HEKO0060JNkpbtxCNc6br+jVggJeDG6nO3IoE+hw4WWbC3OGTfuBxjxWw3WK0ubKd4owukGIf/zoMSDtTAeG00iQwzcqBQkMlwfrm91Ynn9OZEub0AX68PQCDXAlMElcpacT/4xrKMkEK62e5R3kpbkKsXyjJE4db+jRCD9xbV8y43dqMx3wnu8987U8K1kL+y7Vx1OVuRqhY1n3FzG0Rgm3A93w341xc5u7LdP+8k4WiuL//SMBhztEtUw45/X4im36m7/CpB/B8+4fceF68GbLTzj7QrgB0EwgYeuxTkqzIQc8xjx4ldNdgaPUQcjZ884su+jk3p62Aqpmf5EzPOWgfwKt7gXvPXDcYDZKF99nflUrsgOSW5rsRErTIYcb9Eg3Pj4ONQjujiu3G17Qin1noSzSfqZugNLcsT6NiLHwWp+UMvD9VFt3gJS0GQavP9pMTcVnBhLPvD26wV1NfyheR1S7wy4eZ8t6sPqbd/Pzk6Ff51ADdhRvsrFkMOW3KV2065RIw4scnXwqKMXqRPKFXZzjCgHDNbSr+1f6lorLxR5P7QFb1VFLnJFo23r+aOJ05VtPsu4MxMIovumLqSbLdR0CVsq9P4wC4tOnpq+Xo+OVJTuMaXL6Y82Ktqrq9imor2hlQgiyHgK7cTpvzEktIexwJhv8U3cE9gBoQa3NobG/lF7BNAMKUmJzNWn7YUiBATYRgiUBKGdr6zYUNJ00SR/a30Vt8AZZdatGV7V6C6OhkMwd/N+n65EZ3ECsK3rrSN+3M+cIMzt9HniqXJTl9h8TJf0Tgqtbdd+KV8JDKTgorX8/cT/ReMT2scJPDykTPB+JhUprzrsaMpebDoz0fXemZ7RPGo3KrnZo+oh2GIGuND/UcVl2YqS/AymXSZ+eh2ywfThCt4EwlQmcn+a0HW1aag/lY5ZheHzmwh234Hkev5g0P2/+nQcLwwR8cB4+hcMBgfZyyJRpiQlEBCdepbwbCfEq4xAEztqF5FhVsbZzosHSNoUB1TiKuUR0cJySA3bhQXv38+4NVheQOCLbgYlKhVySyvP3vlJrJru4FxH3YlMmyHOfBjXIZbG3xq0impPDmiFPBf0WUMHaMG9amECQBT+KzhCK53F7AH9RJXCCLc9ZMLmCfUSTCIMsKtQMBBMEOi07tMk0cnFTi4mWvebwcewGqflz4v375QrOeFhTHkB9my0+P03nd1z4hKjtzkNndvhoHXl++1wKrKwr4XvaLfrhRhTiwkSsIF+7YfR3DHPZjs/DJkRvIgx+9lAAoM6bEPlk2NzKxy/f5Kx0/X9kcRq4UHor3JLEiZBw7VmH1O2ZjR6ZitWGCpe9Rx7lHCq41YC42qOhp/VVRryUJIxCBGdUdKp2pt8IbWQ6EuWIfD0/nlp5YYguc2ey3llbyZnKgYXBSxbJwmU5zZIrHgOYGud2r/CdXDbXOFEd+BDdVKqY5x7hfG4xsf0Dnksx6vInCY0elnocJyqE6sQUGAKrt9Ex+MT/hAcBk5qv5vwIJ5Wmkqq8K/S1HyEwac2q+ChLqZz9L7Tc5DhyiwF+hhohPD0qBkmTdcoU2IIid8GOdihTRdGRpJNoeECaqcyC5CA2LBSyhosWvVtotnK5ktTraH6qRz+O6/Tj3U7lZckTabbmj8dh5xHU3yY51DhL0BMJeXaxKny26Md+vlHA9RvxRMavbWQozsm13wA3arkB2ital+IxLP+Jb0TxF2FTMAc+hxCUcg1+j8kxKuZuQ4ASUapFzw6choL59LNR+B8mA6JdK+suDLEpuRS1OCJidLpEGrCSl9Q/XAQxyPB5oM/gnOnahZ/RhPcuJOZPfJR5dx7y26jWrKQ6d8i7gPoU17dKzxDQUQHZeJsMPcXzFIZr9hlIpALoGGNZDXnPsKvIj7ngCqUg+mS6ebXsJERFOlRgkGzeIOWFrFCwqtH0DJJKEe9/GvcNBFHIdQbVDg0cXRf9hNRiaXR5a726bhKIIVHZXbJEAtIJtliU4N3waf23KoXNq/8t4lXIG/guuHf+qZenHLqojichFD0KHoBRobSbJEehRz8IFDM4yRE7J9ZRCgGCrQMnv8vspH4LD42SOaDCKVIAGhAGogNaAMQWnagksBvooeFXBS2+OhdJdVKydLgo4sH/BWu6ISgshUYGSoMSpWfwz/shS04DVWx8CJ/M4FdIYF1WtDIKCPZ9H4ow4x553bP0MLZklJ+AdiqHrFSOUnbDISjkYj8BTps7PGj9K06kQtD+FmOpqWU3HAYsVIDQmezhI4i9QYWBTGoBEwpgbII/g1tBSgNnpXDCEsLff1ttOMqC64HT5wS0D6TBJ/jIwTL1VbUpX4p+bRsjp8JDFhH+H+mSQW43nYSOuBbMQOOf88jvCFlyz/SwBrxqQXJI9sIOtZiOYy+S6jKKsa08G+lsNEEQTtbgGHLSBxFgJPsf18H7DsWJg6FeJb25q984raAAWsBlFYx+d4WCApZwZhAgTq5EnFa/1nabbCLBJuIMnFmAVhwFVyyIi8CivVgiy7FF/98WhHrD4H/jXFyLEF4gn/oTkoFrnCIpVoRW6ziZAic2YnAbESXVrkH9iGJdrnxalOEL2DT+o8Qz4pSdF6gfvhgEvE8T7oGtr56yvnAtqjKtM+qad1RiDWtbt4uknGZbLccYKftfmphKXRUkNq1sTfvX0/tEyJNJTKKVGe261kVM/6rMm5f8Rb6QDLv5IBXDY9PqJMdRO/2PP/7DYJVz9AO1F8yVTNvtcbn2jSRw1rBhmyhW29/jjh6QKuRRSjWVsN86bC8AtXvphl7hHq5OTI80uNZ1aiu0Vgm23PgPf2vMO0qhfQpgOds4ZUuR8KawfZ+/aUWS8GPVFqigdXGGSksyixcCBm0Ei6DM8ba3eg4rWd8XY/Q1ZjuMIS3N+o6XeH7UO/QIWHRgU3AhhzRORpDDVaGuZmIVktW4LkKhgzdeOpR+II/B1oPOzR4e6zQvLaUisHQJs3FDJ6khSoyHJcizWzsPZzLW1U4E3x0/N29378x3Tm3FpDvYvgKVD5t/3SHKSZHiWX5gPKHnUZmvRqL3412w4S1PIOYfhagRSvoS6C3hG7pG2ZINJj7MOfFn5uq5EKuZHCEd74HDhoOjicADU/JqylCBAc10b/a5EWSwM/Ogo3uV5jzudi3igsP6Vp+5xHrttSnRJuqvH7C2YSL7bvsGgobPWLvjXEDbhnYODaQF1FuUNZjSUIt4t5L33qcOG25JEhISP/pSMekReLES5ZfNdbuHYgp10kV1uATsfLZnnKMruIXY6Q/ycB3z7S6Zt0yNf7qZqU12axrBgbFHKzlAzhOGsFBJv3DSxBUDYFkluhhm4PmuhYfBmnPjrRKgp0FpxNw9HJEueiI0SopQceZkY41U29lqvIGvX9fg6j7tphihLFdte8pA3Z0l/4rEwtv7F87mjEuw1GOKAQvjEH36NNoAvX7g2OHW6XvYiur8wn1Dbg+pXnHc7H/sctlY823KLKfAknH6FgNiQHQtUaSnHj+KKjZJO3eEZtVmtUeXFEk2HTXU31SPXDKXXamXzx2+FUrLgw1NL5i2i2L67/PXRVCEeyKMJrebpingC1gl9tOApKkqrTqhUuR/1yV/OJ2YKRUlkx2yxppW8J/hn74/eH+oeuXf4x/+WBabzyJ340R6jAu5sfl98123KEAK6XR238UNl/5pRwrgv3uevqUEnTkkM2DuZBbRlTz9u7HlM8sdGe+X6lwCXk5qz6tT7B+FN1EgGSoMpX7ZZq6YuQBW+cZ3ZAfDbKEMW8xFqHD7hP5pW8Bdl+aUzLH/EmqNG7MwkOeE2sfrltqEJ9w7WOORTp0RD+O935+VfmtwJrcj+02z8ewm5TnIMmbtQL5f1qaD8+vcaB+Kqc8RDG71JSwvLD54zTfw47dwdxDSIGJgozGJE9+bB3n2GaPe5d0po2fIhxUncdk44/YxCUXd+md0pR+GUX6TPjH1tBUUdKw+1oYm6KPM5CmVVc2cRcsqZ7hiUBUzhYp4PsDJIk7+hihaCEQMiyVTg20hYHmzdYep2E0momR/E3vUT4hWa2IUrmP80QTuevLo27fGd4zhg+gU8L78m7V/7oGw2hCR4ckPm1OF8gU4YLqpNvRNN727hHEcF2YLTUNwRVMDMmXCRSVqs3mB4+VE4LlpKRu9yzajAIV1u0Skq9AJyWFIlh7VSpVpG9iC0obZxjjPZRAomHuVPj29QNCyhb+epaBP9D3HE3oexnPtMRncZsCb3YmS4JsS82BdktBcaC1wSjCVOoPoruZ/wwRUC1wCF3n05jg8n94DFJqwOCjh4zQKVi4v4615uY/9BepifQqWde4TvCOUQs1OHTKSSUZ0GrEVurRDCnvs7AsgdmgnHLJjKhbcSJls8ByRBcbV8aNrOfGuwr5Chv12qMgSWUsNSO1RWLj1lrEqsffvAjwZHBdalSXBhUg1xWN36y63k2+7dn3cG00ahwW8sKkXUNVfJmECcJSIj6fiEyIJvsVSia0sJcZDkWRARDsN8tfGZwbf6mGvQNqhIy73FiU5Epm3LjE5Ga8myHN0VjBMWNG5ZHlSOwAmSuOdFGOd86C5XA5rMs/8BWPCNorIrsModEzF+CJAQAp8Wy6pDBmp6fW1ffKpVvhpDyiC/r4gCsUUazwolshY3+2E+5qCjSC3MwR8VCObWoAYIYSCkuQ/tLq5AxuwUxqXEfBIyxn8px5SmrbIyLbIZE9JuoH4jEhTOn6OGdnMHDT8erdxW5K+kNqjsZh2sVZ0KjRstFFnm2Kplb38ZorEZ6TX3D62AgWQ6bN5TZFW5YBItEkQXGWSopCsqpGIk+tBKla0cvit4kE9WRBAoNIRh6+77Pb88BwnllIy3WJ3hEntbZBjiZb1tsVm+6bFJ3aidzht/FksQNmCNmiR5hzmRwjPwc8j4l/w9kEUXnpjdgffy0gIsX8pI8opH3Dt4OFayyOrsUWvhYk4p4p8RhjUn6TJ36LRW8MoAO66G3KK6nMzgRqj6Kwxcf2ic9OEUrX4KtKbuHi3V5JC2TELnK1KBZbpPY+JgpJnu6ogsGqj562+2Pf8j0vpyRhK2DeT8NHunCDOFqcZvwtMvSLRlgftiGo83p4ZbcyJD6/1BjTZ8jNvPkyqs3JSvHDUAIGw3bzkUrnrXvrYBQZbhVKDKG5yuznFFSyo5gokMQpW0a0Gybz3jAoZHqbfdaAQe/l0GLau9HCVCkLOF8kFFBdpTIeg1e7KcFmm6NRpYpAQhnWZyhftqEqrD6xFQt2549hecRWnlRyJfz3GU1YeYtj26J0r8YZzmxaT6tdbS3JgOxxHnau1M6W9uhf3zg0Sjr57Zh8sxW8M9HagjeqH0OxbftCQCv36fyKx7BJsuuyNvnmG3lZ4xhDCp9Eu6o7M3CLDeiQDjzCkwHrD021D6VXyimdhy6cIZeOWQwmoXrtHZ6xu/AONvcBOA0fFqGNCmE/ngw+eXp5KItwJV6ektB928XKYBMkLaLot5BuGI8weRIhLyUzBmIKzbRvs+o1cvlA1hCSu+UVrnXohJhGq+m/EuDjbrEyzuBbTOIIoCKMR6ipMR7eeu2MhA0MYor3CoAUlmrroykJysqkByGCUHr61jB+VG5zfrsayZGwgUX0OgZ98DnZzDSdeEZtTnqwOLiWyVkb2Dlm3O5B+NcINbgM45E4LnIdG0VzCtqUBjr8KzS7jWj5alXq9qJcaKylyEd8rQ1KXjGV/hmZFne4ObYgtkk2gYohPKQKhMoADbeJSiPzF65VN/Y1CzVJwNZG1C/E0SJ2Mrq4qK7qGi/0YuVGn4JkHmZLQU+wsstSx8nodDVfnAcdiri/VmwcU2f/ehU2zPeUYsRqdfspV0ylNEl78lpcODtJrrUlu4N4WwimiTrhmaPK173jsOgBbUVVXaoATPPbEpBrNLqaOB0v6Q+u3lqB7d7yfteyLC77xJZEitmQk4z9IanGovHGV7qPOUd65WfJsNopChsIDinXumctS427GKdGs5kb8chIM3s7z/UguiHWE7KbwFIZKdt3zdQWDz6HZp/73+jycqqFJIeQy3JG+VztZVyVT+NZW+lX3ft8aDtk8ebdr0FvdCVTDY5YfPaUNX9uBYDIqrDrAJ3COzpeUYdrpSUHseb6VaZTaojNCRQyUg1DzQHUNJgurE68zqqEITZZGQdIuYdqsKkBaa1w3UEenTzpxj3Pf7ijadJ4DKCZg0YbKHpv7AX3TRHB09taRzU6bapedbnsXSi1EUxJVYIsiFrj7sjOlFP91U7xtyBx6UruJEPYKqP9mcStW+wTMM0fchnVYmrSGy69fmcjQMQC8TQDly14+v+J5At3pRPR2RuKYZEHIY1w1li4qqi3FBMbgGMqPH0eIuhWzZXG2iMRGzKlLcaq+4If9vZADahxMvrcDWbPWMP4yoqD7p/VB7dFhdVTxA9vm39EMyw2lSWKzX2TTfRSLS+Rdc7FFJXFpmQRQXGiSO7UAhVB1BhkDwpCCPFIKmKmPjt8sOcueYLwHSf+zBn4boYVqFc5B3FBj1JyRTlhWumlsMqxZsE0IF56gvLJVp7sI/Jb4MIEBmoQPtrxN+bBditNA6WDU0Pbsl/GW5Jl9rWHeaavC3k/n1uPQ16syCuutmL5CiqogOKiLElkeAhzmV8iSkZCipZ3vetU5xIrA4gw/u+C7BX9qwA0LedySASXr/4T7j0hT+L25zuOM9NVIv1Y8cMNv4NUlzlEaxYMjqu6aVbnhRPRjCUcfmauXArCY5vwOkofTC/jPJm/8wmpf7Wnvq3IiOkBLPYLLKQdbRv8G2fhHSPHbryyY7UUyr1ftZLWmuXXr9HunmnGvrZDwSe6Z/+oMNiPly9Mkx9fgMWF6Q4vHYSwXEvnBTsD882hY1t0l0VgmUGWOKUiqqbwYYUDn/3rODwTDc+Ckon70BpH9aswnWJqSu9hxu6aQO+PefIkGjk8jm6xf0eX3Cxj98N1lXZq7WU9IF6HN6igIr0pUQ667E/SMvJw8IcXX1vXE4W+ckbmsGHeU0cRET6tmEwpggcJ76Z1Tzj+9rsPgMoELDhcHwjPXoRiICSodwuWKIV725V++zOKUaBWyuHp16n+WWwclh4kXqZSyo5ZO6k525Liav8eOUSQZcuKfcqexROlg1lM19lVKCW4xVcbIvNPJ7Y1ZRb6tuRGnc2lLCrH+QMSq2cJeD3THB5sacuMCZqaScx55fC/UYlAsrn2N2AI2RQANCJvQTRl0y7q4BGgGPVi0NZnpx8P7L/Z5tq+8nH9gx6i8enqB2nf0za+NTAlnQzmhbHgC4yOG1RejRIUQIpR7PX2KtlnikTUE/xo2Kp4S6Q6DnN4iJFijFyu0nrJZsUvsNyoEzx5eirGaqP0lM//iPKqVxfn/2md+/ExX55aXCoubMWk2rGeZthWIkIbusQ2+CPt7sqQV8sstVVhOKSPJZo6GpBqcSTR0aufSe0qU8r4WE8z86BqpGJf8+dZEd5PrS8Aa6Ei9W1UCJoAuq53gfYRSONWH38kMhQMLb7wzdn3sJ3DKpH+lE0pjl+kvMzKOCO61CE4vvq4RcSxj/zl6SEhe/nZT39mw0jx/E/911VY60hI/4yk/7tkkChFdnPxce76eZpsMW+8hBaUZP0ud2pjRkaEMw6JrgH2tHPI0C8yvoFF7mwnXz3u4uyPycCI9jp9UmaeuutDK4MhIsFferTsO/Nnah9x6JMetWZK04g3Y7mQ/m6hpjE13tRE8r28Q+H2oUG4cqlDD0PHqzHMBKOR1vLppvjoJLQf0OLSd4RoCbkLEjqHR3YaspM/vcBrjI+Nd+m6H7YHRXDQX9uXYvVTFxVg8UEviTN5afx+FQrCfKyQY6L/rnYponhmkQOW1YxFAA+MaoSqzNsOS1hPJ9KZLHuJpa/JJJgxgudeIr78slNWFhBhZ48Ua7iG9P4gC8RmAGlvDzat/AkwYFcYr0tbHnoAMzliy+8MFU1JEfdrpqLOidL0Ml3y/FhwTzveABPlZLoDp3stbuxtDR9bwgxfxHnh09g0uKjX0y2EcAxw3rWn9PcrcJqdZmIdS8rmd9hjeDVgNI9B8B7vvXB6OeaDaf2Cin4/oCLt8fkDxRJFVBHzRnYXsK0O92MceW7Q03vd0+shbYCnBlly3BsuNrJ6rH9Fz13KTGkRfLOJAMOs9HfNvHyRg0A8bvjq4TWYJKnYGAz68C+Jp7fnp1hpvzOhhAIsPwVrSYP4+ONirfzVhBdcprAM0gLOE71RheL963zG/K+MBIR0ge9FVnrWMGRlSkhN3qhr/+HP3MN5E3hEKRkZyY+jAYH3MGrsPl578IFR05nijU8MlPmaTLMIn00Al2PVBLkfUXyikFqtaLasZOzbRKlN7wL8G5fk/Hrzei/uX1w8TNudD93lCTNkvNUybU2xv7kvVEfPgNhZFjHbfG8pK0n7pL179T3ZiL5W6pNx0S7WrGox9dkOpIXkpIVFmO1QupOVxPQ1ggpoALijDkSWGnmWxXrylpkBlu+bqXVbtBi1vKgFLp/04WDg/iaSvlXxFCQZCZ7G+N9Wgwq1QwqkXIj4NLH7A2CcFnFS/K18+aDrf/ejtCnV6IYQ/hQyRBGL97KiS6uIn4I73Ht4tz42v6Kp3dfRHGDqM+79hgGATruuWz2gm/7nHXEETuKYvJU+S9ihsdwlnpvzle9hjdDlDY75p9cOEwdSq9dpAubpsu/VKVSREaJMzT3oGgcolgwY853bjvNDvdHXo6eAbDZQYAZ15QchBpSnpmiOsRtwh71wiaklh51QMqDjZqo8Z+Xym2TquSecmD7sNTX+1Z91l/Z29gjtFXLjEv+Qk1meFyxi05UmRMXFZYtzOB41owTVXJo9XFghFDRA8mN7JiJU+L6AEExbuZf5WNAT/vnCY0ql/SNxifOQEc6W6qOhwSamOxNzHuIcnj0NqgmWMyaZBNE3JDj/5Bq9OPsTbIUjfO3AiclRyYkXQPurusIcWCweiXsBqny95XjCQC3qf1LGA3RcYmZfi6msGbPEupM7xB6x++YTBuNE90U6P4FtCG4xs3Y7ldhKpAn3Tu2b1thj22us48sOxWZlX/egazrCtnCkH3lOg1ZlcLDPOynzo0njHCl5CDyakPgOEBpmGywWpMnsb9zbSROneitpDOdp42fMZW3oBpj25NlWLrtRc5ofjL6Q0QQye+UdXvOD0VaaHfyqyxh+Cp6/BgZdG7Tvz93bZIpe61fXugiotqJFZoK8jTjB6dpivbZw5jN1XJkvI55jrWtBMoVTlyLzFHbAlEQXM9QCcqs4pV0ozLofdVvfiSt4FmplgHAmrNFBqmqIMEzaoLqHyQzcMnWOD/QWU/zWJIsUnCxtso+NchlCMWKLretyfK1FYFRRrZv/dU+JLNIZF+UU0RqtjLP0FbU8Ujzxx3dmEnl9ouptS5BDvqOsNsabaVfwAM1TWyZfEsNnhmFQtHmoeHbk/uCVZ2WPLeTqF+uUdyg8hZ/bW1k/Zv0Ff9fUuZCHrmFx2HcJNT1vCIh4PkeDDwsKvttTk8hs0hFN8eLo70w+7CQv4ZDbvOvUmD6mJxyKchFx1y1+HVv8Y5BHyxDBFaad42Fi49ADSNuTJKU+n2AaaJH71lSpZREvAxCb7+BozHgnB3sMrQq/2TkpJ/Z0Qw6Tq/bwpf8o5U6Q4cu+eo5QjUod9MEJkkGoSBHU9gOlqZ9sWUsbyV483wiD3Te/3SatV0g27Ew6QSgGiV0Ip2OObppjp+oGhHGCa6vdKUw7HxQ+aKhWt4m3gDtzahwHF0FEE7D/SzxCVeNCQ9oofV+FGUB03PfZu+ewgZ2uOh0+jbsT0/Y7FZwx1uJWC1yd6b50xPlfTkoSF1B0JjNvK37ZCmGx19l8TW9Y5o2xe4C81avPJhSipY2oM/QYhvrv2KVKShxfI03bGQSdKRumdgaMIFQb/JqVRswrbzAxXi5cq+1MgYQRf/SJqhM42Li5Oyzn3+K3sfF1j6Bxcwc2EUp7rvutceMgG+vOp1ZZnhdI1J3l4dotJeMwS/lpgC3XDTvIefdrGZqTZROsyisMK63SUe9vED7MPaEuWRdASsQ93cl1YKxs3YtxAsuJNMXI4gB3tstyFwABBDbwfa2EDTFAh1cVzLHTH5WTDWLKx3ITx7Pj1HIAuh8/x0eywFynJSWQYqhJvT6Y3Xy9Img8c5uwn3l+H4nuV09h8LT9FAGzg4nftTZqPoTXZspbwjW4zs1UH9u+s3zdO2t9f+MNV+Fs9/tp0wXsXK3xvHqEdry2gUtUfH/3e5WE+ygNttMKL506itoMGp2Qij9ZnLJ9Tj1dwAEVLPjJyysPYSMuvNyBl9lwxOX0Tfw1vTVYGcda72RoCc+CCzIxIjCMNzAR9Gw6ngYmUzlyMQI0w+InvXp/WORH5u6Mja8QyQFxRCEAZmnCMueiuoqsdTdaSJOnL/SjyNTQYBSz3xORk34c7Sg59iamEKQCQ5DEp+Q7kB12k92bJYmfpaQdnFt8jsJYNVRbn3w/aywofiHPc/rF5SETX0K+1NyK953vhc7Nvj7Z2YQGd+4kvqdBPuPX9FDhuT3p7ry6kOZ001UjuAiaSuOc7qWpDPzSXhTno6weipSGgR1oEVpXKuCDParIsae/OaUVvU6moeXbPYC8rJKN/2r2eNaNGsu0ptiqMefcKcN2l9DesMXX/DtRYIkVPyfWZPVqVvjINse4QBu7MRBW+E86hONeQBEWehqzvaNxuNBQYTH/5hxivD/tTOPsvaxq4va10Lf1ysFRb7bPk6zqat9C1XHiLQrRAcLlitIHyf8ug0DkSokbbiVcH92Dh6GoHJ3YP5Ysou2Gu4g2pbNvnwMwJz0Yir5AIWQRRs5herU3wM966wbmTfRxF3IeageJS5YuuAOfpFb677W4SX1xbn1YGVz1GsbAjfXE8CTOufqojf73s9YcVB7oNZKbTUISvfWw2Ur6UrXj6X1xTxDzcEvTcX04tmMJtwb6VkYHSfIijz6OR8Dzn9Hgf9yTSm5MFA5WLX8u3KTQEN92J3vfSRHkyZtNpPxQjp5g2apCnJDgwlSqmwSArGBO6VJkRQ6Wwt6f6J374G8tr/DSFUVP+ugS0KEg1UhmTLnWoWGEDSoHr89BESvGZjUG/RD1ymekYP/L5wB7RiOTM4w8NFDKHYlLNcj95Fv+dMqqai9OQZJONP2v29iZ5CwsJEmpo1BjU+IaujxbsSIcJyS7KXStVYqSe8IkMYlQqtr3CNcOPQglGBH0ylrJ8jjZSjWnY++wyYR3vT4/qN4KYivVnzCbMmdO/numbF8UrNPqFLkPrtOU9oAmfBTpRlsqDJoH1b7+HFEnvY2aKOr6Et3chpk5YrjpT+iFrq3yM0BGWRUSKjESJpwHNN4YVJNJQjFO8g7+q3+xfy/HBnNzaNzXmFwkQ3WxaWuF9z4P8Ia70u9x50A+l/433Rg3u/MBA/w+ZcFgbbcoGOvdpslKRwtxrMeW5pilmvvukQ0DrtwYAEXCt7NhgfDQxLzmSik6EjbZRgBKrzU0bg6NC2gXIrzYchht4iq/uZh4OJ0oLearGNgdfTN7Fls3DwUd0fRa/5Zu2/RRq4hjewIl+WECNF5zf1OG9YZyoe3W2i3wQfdsKAfPecPhYvKj+uxoE95J+RQ3c1oMxdqRYSwym3mU8tTwdfSnDHMw0D6ywLV/fYFmZ+5x7yPO5LOPIc+2XF3r8VrDHldYr+TQna25H9ZL/n495daMRGq5YmXLwU9RcrJMvs6CZVMQ71itmojZo6XeN2NxrWPA6VhldgCj1ZiiNuUs7pGJAlrdezcru7ounYRSYuByb/jIU5xSs2kFneAZ2uZXm+1EugmuCYff7X4zN/om1/1flEWToOyPAw==\",\"base64\")).toString()),kY)});var Bxe=L((fdr,wxe)=>{var _Y=Symbol(\"arg flag\"),Kc=class t extends Error{constructor(e,r){super(e),this.name=\"ArgError\",this.code=r,Object.setPrototypeOf(this,t.prototype)}};function JD(t,{argv:e=process.argv.slice(2),permissive:r=!1,stopAtPositional:s=!1}={}){if(!t)throw new Kc(\"argument specification object is required\",\"ARG_CONFIG_NO_SPEC\");let a={_:[]},n={},c={};for(let f of Object.keys(t)){if(!f)throw new Kc(\"argument key cannot be an empty string\",\"ARG_CONFIG_EMPTY_KEY\");if(f[0]!==\"-\")throw new Kc(`argument key must start with '-' but found: '${f}'`,\"ARG_CONFIG_NONOPT_KEY\");if(f.length===1)throw new Kc(`argument key must have a name; singular '-' keys are not allowed: ${f}`,\"ARG_CONFIG_NONAME_KEY\");if(typeof t[f]==\"string\"){n[f]=t[f];continue}let p=t[f],h=!1;if(Array.isArray(p)&&p.length===1&&typeof p[0]==\"function\"){let[E]=p;p=(C,S,P=[])=>(P.push(E(C,S,P[P.length-1])),P),h=E===Boolean||E[_Y]===!0}else if(typeof p==\"function\")h=p===Boolean||p[_Y]===!0;else throw new Kc(`type missing or not a function or valid array type: ${f}`,\"ARG_CONFIG_VAD_TYPE\");if(f[1]!==\"-\"&&f.length>2)throw new Kc(`short argument keys (with a single hyphen) must have only one character: ${f}`,\"ARG_CONFIG_SHORTOPT_TOOLONG\");c[f]=[p,h]}for(let f=0,p=e.length;f<p;f++){let h=e[f];if(s&&a._.length>0){a._=a._.concat(e.slice(f));break}if(h===\"--\"){a._=a._.concat(e.slice(f+1));break}if(h.length>1&&h[0]===\"-\"){let E=h[1]===\"-\"||h.length===2?[h]:h.slice(1).split(\"\").map(C=>`-${C}`);for(let C=0;C<E.length;C++){let S=E[C],[P,I]=S[1]===\"-\"?S.split(/=(.*)/,2):[S,void 0],R=P;for(;R in n;)R=n[R];if(!(R in c))if(r){a._.push(S);continue}else throw new Kc(`unknown or unexpected option: ${P}`,\"ARG_UNKNOWN_OPTION\");let[N,U]=c[R];if(!U&&C+1<E.length)throw new Kc(`option requires argument (but was followed by another short argument): ${P}`,\"ARG_MISSING_REQUIRED_SHORTARG\");if(U)a[R]=N(!0,R,a[R]);else if(I===void 0){if(e.length<f+2||e[f+1].length>1&&e[f+1][0]===\"-\"&&!(e[f+1].match(/^-?\\d*(\\.(?=\\d))?\\d*$/)&&(N===Number||typeof BigInt<\"u\"&&N===BigInt))){let W=P===R?\"\":` (alias for ${R})`;throw new Kc(`option requires argument: ${P}${W}`,\"ARG_MISSING_REQUIRED_LONGARG\")}a[R]=N(e[f+1],R,a[R]),++f}else a[R]=N(I,R,a[R])}}else a._.push(h)}return a}JD.flag=t=>(t[_Y]=!0,t);JD.COUNT=JD.flag((t,e,r)=>(r||0)+1);JD.ArgError=Kc;wxe.exports=JD});var Qxe=L((Hdr,kxe)=>{var qY;kxe.exports=()=>(typeof qY>\"u\"&&(qY=Ie(\"zlib\").brotliDecompressSync(Buffer.from(\"W6UZIYpg4+ABk/1MjAzU09E6CFgW2IZIP1r7kmgpa8Jywxvv1VQ2S2cjN4L44wxwJ0ckpPdNVX/XMr0ojMLnAkSreT6m18l0jOSXUkD5tVfz3z9fL06DyVpOqXJ6cUr1aCJOrHzECBgW586Z4H+qc2eZsNJkc6iYLopIG7Zs8pHnSjV8WpoIPJ9uVdXkgvjWDI9/YtVVpoE1yVoFMUm3aW3xio3wUyXg+Zofuqpu6vV6LlBKtKqVXecY9Nk9itr5C62+ps1FnN+/b1puJAHimiBVpqMkXuMYy4WKoumq++oetp1Bw4gGB+PI9eRY86rq/Y/uRi8PQFJH5JAzfn0k5yLvsniCeMMIQ9kkVBDL6pe9AkCEExcC0r2+beWIVCL8JvUo7lfItpmLR0IMKHtrZ5A5NkqwzcwSOO2P6ffsdfzV9oYmAcIUECF6+zLNf1nQphkd4KFlWZbNXeD/+7H0/w9ttFnx/Z+GWRhWcUCT2z9HRyjFu1AWWw38yUi0WSrmP2XxOepke9ZIaQ2nZYtXw6lcXC0Y9uVlW0bej848wojBuZV/Riwq+r70JT6/7CiOyME5+5uClWXyT0ceBpJ8JkP/dbp8SCUCHnuXxBd3urs0kenohxq1csBG52upT7XnAjYYVVEoe2QpAJgxkOmsJXeRKusQ8hP5C9CNrN3fNQCrMCdM+JcBfgbkGEsLapMGYP99RuA05PNbAk29VLa3CR0Wj7M6QxZMNdjZ2Sc1KYo7hZXSn90MJxbgGtMHNyDzzynoCxIXW3TxZ1Pwx4VrdhRL48Qlmm9ZkbyDMGo8YOJFmymPNO5AHyVUFM3uN0L48JGoK4BbAEFbZCHShYYKhUBl10ntO8JKaD7hT8lurrhkCvuPUcKgP+qETi6+nwonTVqPDlBjAdibBWC+6E3uT/lanBfquMf8EvWtcw4AGIjw4FH3j9ViVeVWSoSaX+Iv4RxobRXxhcZE4ggNbHjmJr2KENniVUQfF04aEZTw15MpoOwyL7GvEbgKNG2ADdhqzKgAxSZVr47ndpeYSJfvTnXONQ+nnGHqTmzhSMFW3IQ77479pQn2VmTXPET/q8c4J0/+PZCP0aWL/48W7dCKiEFRPtALh0B7YtGiMZHNnczxaT3szj5alWrFvPgrDMrdUcTyaQ5PTep88/C7p9y+6Pb9ngssgI5jd1C/cr3ErD9GEadZ0j+pVovDuksCqskeGUZwFErfqZ29wY12ZR5CeW0HJxYr+CAstCG/NQYDNoBeibtqOnMOVT2A/buK1b9eVN+Q2iNL6pH3t3KKd2jWUTlThmpErmBLMaKCazH64isjJHdKaH6/Ag2eQP0+WW32uef3LjmJlI6WZ6YV3S8XsSznNCzv5ABVbUTpbvVbyplvSoWnatOKHcpNb2n7WPkTqi05xdEteIxesLAu5qXVoHU1LMCFdW0Di1AueBY6RmEVJc07eyypdMYGljyA8KbciskpLeEpRwG8Mqh+Mwn0dw2rKO96J2DZxWbLfxdLRtv2NfI76fC/IF9t/J57bvUio8PsOUWGNOALM2BglbpoO9FOIuUjmyq4DnUzndKET3IGIHlKCFAncslm9u+9E65bd/co5XahR/pFPob1Xx+DM0V03gi3lBdTCThraWyx3HIkccFIPScE/aqXYgrFHY6EHpECsj1n2lmXU9Qmkg44ad74h1jzo+sOjp3g8Lutw3+WKgfXXk3JK6otEqFuQGQjZ7aXkhA7AeWCmOJLBF0qnP0Cr7r1RvlegIBI9+MZ7HCePoIGtQjAGWpRYVMIdb4xfhGL5zWTSYpHoq3M0hylN69bFJPS0p1S/ZcgF6XsCYqJX0CxHQiu6l4Zvg3cWnD3NYxpaBkBAOTRKp8sT6e1eNTwWLVdfAOyCI74YSQgZhlLo72OedA42eHpeTgLNkM7ZIoUjwNBHz33SfTNxJBFGVdr8MBhNGzKfBHA4MV1VvhIs78XVDT8feeBr+G85QZHSy8IDerEBfQRf5uUzlqgy/6kjE4qXz04lAd4eLuyxYMtjvDbo3NOCXFz3VFpzdpiaWqhEXxtm7n5A0nj69482O5N1sv2aLrV2m+qx60ikJNFtvMLUSV4RJD5Ayl7Cw+qf81LV1TXPPKXTb84JSCLYBg8hHB/BDXV2FdEWTW2TLpFdG8oLaIGKnpiihXmvLSdoOQCkCnPQICeKjZFwUXr+8TqoeG4PH/kOXREblZtSwuWVENO9V/MjAh7aROpA9lVayhkCBno9xHBU3zTLY6EOPuPmAoFbinHP+n9skGHwNcMSKcugeLVVZd0fTmR+QrUU7bDEZzdKgaH0GLKHWXeA+0kwVWHeyBQu+wDo/YJFycstwqYnLl4b3nsw2Ms5lP3pmRdiThnwMAEXSyfows6b3Sw8x6L14BUugPY0gRV+HfklpekWTVXSo9SYuVIXwDRy57SKSDDWHP7K5W4W4VYt8o+2DsSxvhYm06yXTmI4O1f3e6xYCMfP40CXeberfe25pj0mXh2A44jdFlNomIdY5GShDnlmedr6NX0rMQ3YMDml0dh6pew+ipCD3Cc5N/nKKZ0QevD2JxRQY6H05yfFyiWeIDgh1vJ0MK8+M0ZQ+SjoO9PENOobhohNHq14jKtPW4XZD8BzYLNRid3S/TZ8OPYXDkKxDtMZEzyD0XX2FAqa/ManeF18yKBQfulvw8IDvW0Lpi803w+50XJzI4n1fZQO/JWWT7Fh9Uulo6OsybmIp1Kn8JTFIlBAHscrlUpTPGiykfZ2nXDV0yQNTdQalq8Ws6itSufZUN2LJm+3mFK/QX367CKvpW+vBv6PKPLQrTXI8DUDowWX4OvRO6LjST8uJQjXPeRaFDQHlVtt5Y3Kb6Orq6XtX47vhDviVn/e2znPQCB1j3R9dmN5b+ggFyaBf5FLkScllfQaKY2Qp7B2YrYeyfiSw9jpac6YRNUXFGOArUXXBkbgO/h5CqQmGc/pUSI9GFBeaHpFdY0pQuvP7hz2/GUze1zPOczsfUWkYy8KQpkKZCrmLIrKwt7sFpCEnlnlXsfXOEHxXy4CF1r7yzrhEY7pwMXydjjy/B7Dwm2em0w19Qxz1Dq17xxdm9HmxY8JWoB8xIkvfB8OzSFZeyLXWuFmtrVLFI27i+3P1FXxb+aAVG5Y1wPjeVXpeNscUeLTswWiTBGkDKHjVb3CZnnd7ZXmmcpv2F6oU5ubp/E89lxFMSVdlY7oDfdh5nw5YU8bxNx5pxruawC6kpFL2IuoPNn6b9hDvZeOAFE7iHK36x4/IICFLJqtLOaizkdOdkvpsrMQjKTj9oyjEQDWfcvDySz1/GtxjocHvcHt8z91+lSz9c0rcqwrggPg9i3lQfom+R9M4KQ92kfA3aE01abmz7omXFVmyxoOScs+0v+yijyYbG9JNRfHmbISKZdbiiOJFWBdPxpmZLSWPJHs40hnnZvdvz8M7TMTmJwwPtBzGqlFTsd287XCRAdhAElnpq84fAlm7Hm1E/yDWWOebgtzUrfhmtcO00pQZ8y7AAXd9xRH//93XV1PSK1ROZ8yYIk9KDUUdM712jRwEAr69twDrQ1Dj0CsZ/RJ0xXcfzEXNHCpZk4cde9esMZCEMSNffIp7NDlNpNoW3AuJbLuy2/cvkpmGd9Ypjy6Td3cOwtbMOSspJ63wQB/5iD2/vfUDvScoOppb0MtQ8S3MV3oNkaYApPuXlZ8AnH9O83gn7ESon52e54H3Zl33X/Gs6N8T4OX4OYkQ+CdPUrkDTZRnOR0fQzhRRD//2eC9pDYfnExgJqZRH2mQqQSJf9uFRZgvP7iRpAQkflrgJPFCochjCX+Imiw0SQHld/r5x9jEVBKsoFaf9F1m1ZisJbPu22Ll82oVDdoaGbQlQ3i+YlJLDdhiQY9rH/Rm7Yum6sdrU2p5+4BC73hAREluIdC4Cu6agHfHtvFmc+luP5Z1gS11RK/C++oGlaTW2E9aQ/EjOJcriKqUu3SNgh4rFE+p5nkTay4ft8L2ufg79RE6pnR8vG97ugvsfvqyuXS2O0s2a+P60zTX7gRiPHc66f8b4eFFlzbb75tZCHUb4rk/5nzncnH3q/vaDGlmk45FQ5G1oTTl7lT731UfnIm3/8FyTQJLQHAMDExTZsdK6iEwTgA3w+hKG09lk663KJdO+zL05Zt6x/FCSrSBMEIVn7KVC11JN0CbaOpwia62CMGfUn9XZMaDxoxNZp4hwhrPshB8CoORtuaviTR+KGNTuwONrGoD3890H9fyNs28IEEblKfzuGE15ltrJ53og3r8DN3qEPjJW/KpT7x/1R0zecs1DcvuoaVgs3bMBSN+icqPIuSK+DzsG8JgXhe8+22hslrYtlT62J3078WY2QuALJc5EG1WGNWWWfV2toWai7yMzJK1HlGhGUKJuEC6cxVn1JtmPj0z3dEckFw0j63hzK56qFOzUkAYYsp+7c1lShbed/C1W4NhUY30IRpxg4QhYg7vY/T2yV8gH2HyhbJ3iKoHfrUk+A7PATOZO34u/Lxryd/iTNcr2pq07VlDjx+p7Fo3uk9Z2rXXErDn8vyU8av1m+tKqz2pDomXr2QN4zCdYcs1wcW46diI0dt/JQchoC/YuhrdFKeALwuvbqW/LhHLkCSPg8wjfida52Agtz69RQW8ls2Q8C+WVVNHzk1dcYGRmyH0pYf9NV582YaddzY9i4QPGbq6N1qSNE4Z2ZcwmFY0NFF6qawlljxTyWd77F2wtatBPfiJ6bdLiktt3DvvPER8zjGPLKnzQVNhm2ievd2SD6TAh90s4dS6Tfjhfyz92Wmt1OnegnP6T+MO5et65WRvlE33XUoDwmG92/WOvPl3NxaCusWtdS+m4TtjwzVmB7D7MkC8vSYrnt5MlEQSRjM4AdEgFIEym/QtkFm+z1qNPsfdqVESiPp80JNpRN0FZ7E6Wafuk8bhqjkHkLezisqjIuf0dfBW+VVqEpFKzZum25QZpv9m4aH9qFPPPD/V98zyc7qu8mul8TmLT+CAl+lfH2kVrcF3f2JIOM2T0GcSt70MKx+BwlUp6apywszaEGQEyx5wCJ8ORBg0Bhzn2qUyfoHKZtRUSbEj+tydFHL9A7jakwL2/bE1+7APM0x2rwoaa9WDT38SSXS9+Bd8kA3SYGHRzhKrnEtXCdGH2mdbdgJtDeG5Uv1xGVp5iWX4V5LK7JAkoJX7F3rrtumMb/sn7WLhcnEUIcts2r/6EU8vrk4XoeMcMp2dpoerjYcG5+ZU1hBAZdLRzUhSoVwLE+QdhYuUMayni3lOi3TevwS1j1lePA+c4QT1Rz9M7ULh7vRXnkt45kmsC4vb91dtXZ7kdskrNdqSw7Kv0J8yOu0Y9LmDXTx9H2zbUaPRJBygqHYREJnD2PnCWKpNc6CfnornzuNT5OjraLYsZRsxYAJXKF4M/m6faGtO4z16tAGYHqVzVTXrtsVvOB195cl4uVYgyfk+O2MN/ucxyYQ97gyDTjbln6ztfSdH+2l8PFgs+dTHqOtGCGyB6edP7c6K8z0C44rIn1p+GiId3erhZXEp3mhfSWESNcXnXjQbl0Ib70KNZ4fIOXfdJsucKEA++qPtFz7GL8ac1bw7zlxqRVWXtcQ8hlAlHqxyJX0HYpkpBAy2ja59L+Z4C7AO1UmX3HoUz/0WdaCGW2e+Xro+8bhJRGTX8b0jDDJn4/Re26dhtpg+n+mQIllZgcPNdlVUli0ig9gAkdqxZEvqKHpq/QkW0I93TZrK7ZO6uQsfvUSbVNuV5O5kesddcpIgCGhOXPTneUE1Qj0MMdNEo4OO7HyryfgKt4ZZY9IXhfPG9XmJ23KDT6FVLLba6ekfvvsH3m/QRyXeykKrjKPrptcLSi7IoRkZ3uq3+YZ3UIYYxMSbxUn/4wMy7Pgv0wvnUhmVfoyv6xduCgjM73Olm+Pyifl286dppjVm7qGCxt684E2ud02Y8AO/6Q4C7yvS+Et/e+jnK1fJ+BmgyE9zMczJFjrVSDQWTYwI8F168HA02f/J6vJtoIzrbiJpF5ee5GuKtfsqEWKZNlkmqI9ZimyrKkQd7/1LENTKFUjtDxVS9dKGrlQheDKFsoTdMpCFOEKbBoLMjwXJhM2hxBXNmSQmyw5nD+Jc6KakwK4Fb2k6/N3L19edgo9Xqd1yHtBbO0+rXKwQGGbC9rRKQoaEiJPRECVHfr/eS09koblSdlYzDbey7BQBYxeSJKvQnEEvOIiJ/ejeB8axvFYpVZ8IkDXmkhAVe/92LW1nWJPnxkvM2YZRRxj7lAGlKk5GmHPLxSt8mYIMT1klTDEYvEljsAQ2aJ8p8rc1nRVajbdlc1xros8MNqEwQ5pyAs0yQq9X+MSO5tRAJvhScb1TzXjEzjNTBCFD4s3NBy6Ppbxh4mKLOCLA8+2MEgU+8WZAePYeD1CI8jnRBOhNPfmPdc8OESs95KERVZgya+sfQiRWSzurLWQIdUrM+wTTt7J27rOrjx61BjI4+STrMWe6gAvlqBSoDoEZelAOK1ToQwisWs5xQjLCFiGk7M5CqGAHW+zLV8v4Xp9HGVnWIY4r06clBG5wPQrujFuZqf1vLTqn5alHN5O93ayC4DxBt1I8oIIwiPR3t6PTrxFMvWo0IGJMj5nbY0p8ST8FtfnSVLVw4mAUkBzii1OuIYyuPZnl6fTjzF8o6okRkZkYTcc35xNhk+OXi7Xrt91fUXwOIbsJxd3isDK6kfbJgTEQWM1lpl0GDAgUtrJavL63W0HwsoXlw8hjTRRjwNMpf1ZBUz2WbXxBKQdFrIyXwQlGnlqyxHAYLh4utR3kVFi5I8EAE8JCcN6Lr117o6vE149RVGfYXtuXo927LE4LpYS8S9ZniNjeXTbdW14x2nyVhYf3Fwka5pcxWSA2Dd0n9Hsp6OwE/r+2l9P7EjnahuR5CyGXeFwVVkPt1h4v145ek45em45kl2Fp01Z9XZ5CnL/iKLNYBkTkREtXoAsx8daYDpLf3tDYKCd0mIZk6kkh1scxpuIrQdu16I3PcuDTsacKd0hv8WNRupyFAuUeqdF14Km6vTyaiOvpxilvO+EG3dYanvnhELiIQ9J+yz9c+dkE7x0s01eQGku0rMsRXJieHuVPw/6sENbv7jayGu7haJO1P/sP3ZdthA0K2eTFz8ctoZ/REDWF+2r4IQ974eAOnlgWtvD+uCc3jNukDT3cB5/wbQ3c2vd8r7MJgS1255x9ugQqCYCpAYJQOBXzoTIES7ZeOOgbmlA6G2LzbsOFa6Is1haHUXx2L8D5qSbILbku0mX+XFsmNje8uXo8Xe0cf5UZzsPz/OnE4NzOjo/wcMieftyhTdn2rGTu7Dz9q5cd8xTwpvmH2mlG3HG9tNeNid9KdZ226aC6nbd1Fz4aQ9PK+E8iX+86O9UeHyMrEvj56edgCcUK05xgtaNAWbHnUmHufySHtcXFTI3Jh2AZbZSv/njqdodX4ydaBJvxFq9fNB7/DKDwEqUQpaDJWS6LDCc0RVRDEcTtW5qyaI872Mmz7WTYnO3JkzXByGfkirtu8OeUeK1FOPhCFHNqJht5qhtgfXEnZ3fKiFMSmLnb3rnpArmHbO+tdB6V9mPiUrwlgJjo4j8YKd1kVR9iRa5hGHQrRHciU05SBeiGemYHzfdNl7tR54oyiEPKWgMWUbCMv+xd1CuAsEmj7eT7ymH7vlAaLf+jdfL0bCPiPtdTRBVq+ZH8Lh7kLauHdXHqKH7xWIDTeFDZNOERrErrMBhyc7hUb/cz7ncz5zbpx7U56S4gNTO8FzOwyL/yNo9zmiaKW7ysuEVMLd8IpEzIwjG+cFTGBpH7yE5QaJOJAonu/i6KvuF6WxPaMPRJWyVOxXPCKrz5n1xHyJ6HPq/1PSN4PfOg0QTWvaMoSBddzEdZ9YeY0E9Ia5/Y7KPpe3KmOZsgKqY1gi8ft0FxJVHbf5GSRhe5OrwrVFiAV9ujD/VL5GF1audjTtDQzHq1QAWJDUdfJiVK7viCHvw6qOXl3gOUEDafq+YKEYVAp5IGVNhpxYMa8/noFEiS/ZV1n50Q+EinSKioTNRbrB5Epqp+hG1qus7bd5RclQCHFoEUFFGrYYbkS6oEvrZE4fCQZZ0usPbou7LWCtVqn6YVHEgVgHj4Pr/7VOrv8jP/1X/XR0fvpv+Wl9P+W1fvvLMdAgcn2BVdckBtVG0+9rnHIh0SWLupay4SQfJ/Tayv1SAh1LQCYTtQY0qPebfinglAwdvWy02tWWo0p80WtZ9z9AJcPeoiedcTG40cuxrslNY4ye227N7n6BL2RTD7CRXawWtkz63drj1h8wXX7p1yZXBwr3hnRJ3mPivgWFm45Na1y1MaVeOTvw1XOKNH3WVTvT0+y61VXuJ5O0P8czGYu/o2pfD75X00PM/GmIu/DU/FeSnPFK/Fu/Wj/3X4FOfI17dfSXdkDev4a4Tu0xYumnyyh9z5FuyYBU1ljaSjnVe6XETGXF1d0tpV96/3U/rein9f1U7/PSL7bxmKVJaL3an8ZykpVTvV/N/E1og+o2DOyMpt5xiLy0BNKWzps5z3nWnCtneTep/pwlW7ST8DTNBvquWFhoOnnWd83qFjdo5RbQNkf1d38cVD/Q6KVbpBnVhkK9k1K8GMi5fKPvXbP9NTBf5yFaZyf78iDLd/6ZzFdx+Bs2Mt6LwnD2wp+/f6bZ/+oPDDocD6iPY9fV1Z0xxxvoMe7CYO6oZFzmh8U6fLb37f732Omw2xnhnZpRw5R8W2Q0VI/JMRuoa3YzXU9E8b7aheT7qwugUN4O2hWj63M2gUuqj3FMTSvl9lONo10+qPvpp/a31Yg/bsPZYc/4APr0Y5MqeOCtxQBD1ij7UrbLezFJM4jKhC7tp+lxk5eRvr9ms6QWKkQvl0m9DygfrYaYrEnIdjt9QWlp+hns7xNKY02ON9s3NB8fLLHRZ+QWqaV4dcbxOq+mLwlnf/bqPW5BACZ5rKn4O6cwh8X7Ewu1WHeXjqF3/4eGYZz9bkw02plb6HJclKMceJqEEg6N/PH/1ep8pt0nIyBoUGLT06fMi3Txms6YL+t5g9vM7h+SyF8gE/phM8/w4TNjihEqzE97IwIG2KfUDUYunEI/X+EFDiZbw6sAanAK0Iw+7LoTl1jtQQ9OAZT6AAox1t3Cas/fknG3lqOdY6R+3MWAP+0nY3qO6WEWlve8K0rcbqEwH2+vo2usOsMMmZ7oYewj4V1vjS3irRb92D6fbQLmfGoOPl4PKwMsxrsXBbMcBQO/us26LEOVs4O3I4TeAajKcQTYof7iRw+x3A7EgzNeuWGNA6HeCzo72rgbd7XRPREhBvB3pnOaIezqZfaZq4KJBxeggMsa6Pa997HKxIARRuIohl2VAhWOj9oT9Z3qPHpeGZ2R/m0J95eyanMEwkHydtELri8NFc8ubDodB/G4a6/THdnzgGdIA3xDe0JAXy8ruzegDHbG9UPCfgK5Fw7F3fA4QgrSyjTjEY5V3eOhOwnJpbv8GmO2pf3b0zH0/eEnnEkmMPXhnRAEJLOplXagMapY6xbpTwk/K4a+K3y1E2xN3ehVv7sK98mS7y6DlRuC44nR6Lfvp6Hahz6144S4t0tnvM6OOORQMtDluL9gODtVw19nYoZXKjEF3aFmurlKRBUdovpFVhtDvE12RQozC9EgN2U+SgrO9El1nCscKUc99dusxKksDoZ2GD7rAZnv0cQPSfH+NhaN/Tquz7HAw4Ldcb1AlPRIY0OuKHQOMJSNkxHsNLGqvednQG25SiYrkcshWj7KyE+xn8ymxvg0njFBTJEu92+jGtCvDvZyEJ4K8qOvkYyrCIjuGVNKXIIgX2fEN5XXRDsHKIzZ14gmemetsgcfQv7hE5xMIENILHwE4Yk/linQwNfR0M0uzLlAPbaCTl8C8Usl/uK9q5ear3x8lOHstw1O4pARhGj+QHA/l+kLRIQ5nO69Rl99KmCSLx/jfBJZgMzIcS3aXdIbleO0Lo0jGB1VHEIu417ZY3a3iaPZM0WeFXp06rXfStbNPfqGPzfG8pmTyabE3P1GQldDRcY634Fw6kfk8hFRluzaGMc20qyHgR3SXQCkw2LXVSLKdShL+KpX+gcIrsKwut3x7xEbfBDpyR6xsZ0gGTrJEiysVDlACtq1LhQv3BCGs54JWFNMS31GC7AvHZK3ldQ6c9GS8xFPj2osLu01Xe4cJmqYD+GH6K/wf3HfOI/H2ScQkLJcj/UcE4DfhNLo3USze73pfgdXVOVTpMGdFw5porBLaJdP+fAJc36uz6Fc/2pvgHemcqAZKyWB6neSmO/2sL2nPriHRvX7QLSg3BlAB9QqkmG/dC65MxENT03NBrDduzC847n7EzqKC9hvAaJW3n3k8ux5WVXOf8f4snjVas9ywkgIk0OxVyWXNZ+crgjJdeDqRFDX0+3B8F+/0X+p/0g/81Xjf5+80PsT4nz5HGPWGKSz5+VvI9MtzROgX530w+EU3XOIQSNFZTTvbcaudqPtVEM+QisLn5PoVBflKLwzhHqf3RYE756xTH0OCuBAG9nChUJdpPyIXuzdXDID425iQ7XAuWhWEHWFa+RMT7G5AO5e8LXmhHJ99c6So2rQ9Keso7HnenXNXrB2ZeQl6O6ujNzW+ZIBexIECcS2IFbmTh/IaFI5PMTtRPvDWKrQQflZugoZ891uGCZCw4GqD78x8PGgUMUDAO5fW6CCq9oWvIULgLskhYIS2KIOjvdlNaZfdjk8+HEOcn+ScwaClL2W7MH3XrynqeITnHQs20MrMsMDpd2w89qOFMqJ1GkfpogSY6h0s9X6Yp6mXNgTT7m3qmzO3cU17aWdMKKoLORD7lzpsQ7W82YgYOKqCojZp2VyXvGwuf5glkVEgP5DCEm/X9bfqvZE+4EAVqM7EZ0+GWerH6xKrj83UF633a0r7Cc71+we5/C3WXWap6TAh44oJo6IwwNllQpE0Jw+i6MMo0ZGoLeCMdV0KVqiXtvWi/NiXYYHFrji70MtxE98OQ1PlSsYzQ9JDezqVzVv1xRvEzjT3d7BmDUqWfSJcAQtSHvjzDZbEtwwbN+B7cLXrUqVbKSJ+QZ5HUlPEb8MW4NbrAOa0IFCz1/JX7fBrO3G3coKnyaM4Zi33Ajod/3MbzRr95wXXD6chKuO6o9DvDliCxBQ4Bigb39pBPolAI9Hf+gXRp5RiFJmQMvHSCJl0PphKkEaNT/JY71J+jCUPgFaT+d4ki6fLU90HKcMT9qU2BJT0qL5bbxBsxqOo07UosDVD1MNNlGZoaxdikK/WEou8M4g5QkV8G6ebECHn/3E/eplqode3v3Traj38u5Pjevo6NOOu05mub1Mb8ln7+5vlXh35+B2+lCAOI2qvNiM/M4kYOcCDU09Hgdr1XVWENovQ9QqxxhOJHlRdt9fzlbTaQnj94KN4mQrRCacTkHhyzOFEGneCoWqnMUrRcig43cWmcpf/bJZ6FU4Vdf5v1LhmmDcvS5t6EQSK5czucZi58ssc5yu9avhy3fQAHpEHX/TTImfYT+TzBBEBliBD8fVMflfpbHECClIqoUzBvKstWAbizQZHrCa/kUIkmdl9jIAlmuODLpOXhRcYOmlbWnXHzpUPqzmYDprNnNcmogZc1k5zv6aB5E9vyXhYXuglGHNaPgp0mREdRTwaQfEshnD5ifv8bTNNgm7QmZwb9/7e1yNBMakZgUj+jEyLR2nvE3zT44kP7qyCadwdcsHUmr5/Wt5NaXehuVc8MUSI680q34Xar7+t3a42KjLDMDV5fvrBYERy1PvgMhaFPs7PtQCqBPoSAovKINMegA5s7uJktm4jDQQCg6mT9YUfezqwcHvYxHOuZDS0u6gtDDWO/M+XMBucH4K+Dhpx+pvqHiTL6tCtmgMS3LT7WrnhRCF8iPBLua+p35oPwrHAKEzKgao2K7/f6F9y4e6yQ14n65eB6fAzucKSGVi8MkoqTFoyFgjHzUvkF9ezhG18FmUka89ac5asxqd0SiEYFElfPcdS8Ma6u/9SGYA/2PFFpAjzFer6yIlAJGOvkzyndRDsYeP1aDjlDJ/cJA0qrv6WoW7bbPuPHN74t5peqb9On/ObVKzrsf/OicdPAFxqnUbsx1x+jrmWazQlyTLnSpMmcYlXoTwlIo7YHxoTsKVCNzgechUZj/gQrVlvUeJMlOJCHvePOj1TowkfX2SwogbAb0EChhg/OM5A7MeXBW4Pk0lHFiHtTIhFKGUYVPYAjloU0UJ+5JUUDghP2nIxFxlUwmeQvCTblrkuq4TM0LUT8gqwsh3g0frjZonPUvHkujccCuPKdixha6tXTF01LeqJFO7jsJTpSoC6wt6U9TAD2FKQ8sXingHQ8WPm+rJR2CBYne4T3tKj4iLI+IbNpJ7fzhTnVKT5aLJpTuZp1TYOpynZ/+WVO0kaX/ffAZ0nluTgMHooBl81qzG79CwfJraKZrkRN80pcCqRW3MDma8EM/WhZ+EiKc6yeWarNIOLxyJ+RIfKxtfSu70hEvTCbFw6OPVtuIKcxExMbhRS4nDOS/4vKYTJ34zAYgLwZ1RfhYHUuWIOJ4V334mvaZT2LPn+mIQiqNbUgUdl/hrxd+Dvss7mC3tl4X54BgFCpW339mluHIRCooiDfLREUdCsKoebHHdrFTp3TGwoQEBkKwj/WftXbtq+Lh2QxqRVglQ4Iv0Lcu81o5orvfagg+gKT3xwKFRT8c47a+NnA3AmOCI2Mro8BaFd3DnnER9CmjyME2c8YWQsfPurRCs3kqvT/2mNBV3PsVTPOVTPLWXeAdin4WfN9SwaOzsv4/l6HbFjg+S0A1cYOXcxQGDslElmwrWjYKOc5WcJZKFgpTV3l/XDaBxjK5oU4w/jyTeVue/x+jZjvo7RDleE6OEJnBSmA9nbpA8R0C7/xrK2oYsKQ7kE2xz/gm2Oa8Ov8OtaxdetFlI+HY3TnkAMDyABv4tKILsPVIRBbV8W07SsjQzu9OUNbD3d9CKoIsj3sECxjiSg8E+G9MJpj7avosMxyL+XVp/CUVzpw8e/UqoZwDyxomU6YUdZQsAsKFsH6H9HP9OQMZFLaUWZeSJ3DaM9d8fzjXu9cXJ/zecdTz0zXFj3Cw2r29c3yw3b2zc2Pg8F8BmvWnXstE2rpu1Ged2mc1heUjs3BnavPbYNegI3tUaqO/cCmVnV4nDO8pI37QYrWr0wgaG2w8SF4OaG5TUlPJygljtz2oljiVBmH4qgpMgdiWOKzZOt8dz6DfOdtl2fDYWE4DZy8PM7K4+vQ0Gs8WelpyA3j0NufUSoc6v7CnOSEYmoyJs+Px3VkyQUNWXzvjNH7puqQ4B0kgVDEGTh9+A2Fi3vnQaLikZJHPwufa4qckOzMpdpMvDj4znIYg690+VXLDlkvWxQkFl+gEaJu39ImtHsZaplO1pgj0ce18bjw3pZPFg1HERTcPYkCuBm3UzE+ha1BwMvu/nf5emlFdFZ5hqgmagrLsSgpT/lOa4JgXhYMj4ktPSCWs43Y0lbUnRANE9N7uQaY8SX8BbQw+ORbHrq7yToDpKJUTIOXXi/ErAKpnASipjOrBPH+Ju1Stdt6P3G+6da3mFlTJaHevm1Zik8cLx6VhmiWw37ctuWbRQ733QUsCT16ErFeHj2rQEKhzrrxrNLTrviiE6rfW7BnWmUmxFzQBouob44QQkGAqoYBERcsaiXNxwnaKkfCXrDQFFlR7gbFzppG6ti6Y2j8cLAqQ9AMwifHJGKDm+CBMWsDsA2RUi4xje5TVzEOwWgMwwRk5i/KxB4pqQb428CzmVcstzzC2vBJG/sk9L6YwGz4cfApSrb11ZDfJfk7UFINKtH3VyZR325ybLzzMh6U3wyXdPQwWvFWEKZnP6lGL/DndCMUqd8Ms5Xg/YfA7Bu64xopUsnIfUYqQGvkcB4+ecgdpbx1z7jDmuGBi7v26NnryKealauNhLz6OOWo7QhR031ctugKUJsD3q4gWCcMqoJCuVo7aX9sdvtXzLuMOYeEiWAfeMbwKAm+zIdFFOid6LM78vqL+uOsaX/k7lPv+87kgsLKEsmiyD+fZJzXbzOg160SbIOZO7U0IXlhF3/w0fcvb/iI1N8hQnsm3WYLbxYkNNx7lKE8L1esp1aHG/dPYHNcJvOTwCyr+2tHsI0sMpGUG9cQpNa/PxWWRfkH25TO2QOpo1RJkeXZlDfsHjTz3iNTVRckn1m6lqfJCp/DPVWwVpSP5i30sjd2HOqcgWs/xnexv7cjg1pEvthiVgx+DSvyzGmLOLIKxxrVLpD9B9bbVHVm7FCzNd4kzoFSzzmd6AhaxVrUOOGLCfnGPBYg2+NFvSFXtHvrKtbKWlgDGv+WF268kEhVyR0uEWDxE6S3RccwB1gXSAUZZVJVeYSeW71rsxNFQCYC5bWvvbLPxMcjojrqKp4ea61C08MVdzBKQ5lmKZl00oyT6c+CkfDEMeLXVtMnLDX0XLUkYafg9MieUlisGzr8RiYWT57jU91C1N5EqaO0csg19UT8dmfxl5Aaw4w8awCTRyd9CUiQgdnFRDv4salU46N57KS+qDcgYKrKIYy5u1Cn4ZAyhT61qx7UFspBn1p0lSgc4GVejQaINcG7e2oNUAwxkk5MoynCzyh1IQutomlhE1tUd+ev0kEI6fq3IWlWURXmQp69fhdsDSaKrUZ1hSkiEWMeBP+g8fOz5cQrPZBloguMiHmnkwmb/zBx89Pbo/vO3kmyPm9QHob7KqAFqdQDsP/mFcsOuQHiUHxKqw0CyCtA8Wzsx0qfAiIY8VCGGhBLy/kWbiYpp99Q1Tb3ICfzpECoXULIC+AKUnNoXO7ahPlreKtSN3Ge0u7tk1KQs8wSVFl3UjpZtPE6/o1OYbt2to9FEOi+pDm73pvKXIUf76PVl0FEVUm3jcXYh8sS5/4i2rVwg/cA3QtOkLbo7Y8h21rUGUpjYvonu3O9cE/SUfwR1dY5HWRZEWhatgomKKWJU3Ei+JcmguLEdqSsDXVW+oRrVquKpNKELtkn1SHedU1GTe47JFebUcCFGidam1HuEDU7HUtcmi4rY4oiiTW6z+MFyzb4snsk1L5e6TPoFCTq4K94h1a/OyCBkV9WB3duHw0MC7VcJE+dZCwsUi0Ts4nTCU2TvX66LFGhvdBmiwJ8WTz/bW7h6iYETWpSimiYSab43GvftRmE0fGewbA/hrrpp2cK499PAnm+IdFvVG+BhNjRSUW1Uw1zIE2MFZbe1EHLb5F3HPG43wdfH2emjerUKrxAGu4N9ULTKthADHALKksRwTufCY9sCwX8CNYLVGpEjaFCtUBHLGVM7JAoWLsJmzJyAA5ISNL7+qrQF6h+3aQJNT7quhymEGrTUzKLC/0bCiYwlS0iqNJVYRonkKhAwQY2uhnIzbOyYfZGrc6Iu0MKXhF921w1R31Yp5gYVb0E3kAhT4BtgON3HLYhNATZq5l7/Er0Fk5Bcc22LagPDJqhtfGp+VLWGQ3HFIsb4tK+JGq8zlRYphEzqN3XjHK7UpwWb1/hkFRM1JQxQvMkHJVEeZHzKz2U0CtyXW5XzX1rkc+sPx5hBOnMin0gyZLRRZWNSlHN5LhBUS1bHgzfjhWn/Ydp2hOyWQ6ggdsao4wCFSTU/vsQ6Y5JlBSgoZbVonVg/RIAPosE2RGDhNZ1nYA/5jimN6mFKeS+HmL2c2Zb7YCV77xvkY3vWKXYPjMHMNS9PoiHhFUD0PgSgzVq3A9o+N3PWf3mQMwfHOWivzAT4JL2oVnqEvpq0AhO9O7XaoPlqbknSp0iIilrrdzDL3XQqvhakWDZif3wX4TKz/el/LeFuGuozpTGN5SKrw3/BWbmBGTZafEyRItMm+882t6xCCpkPQcgGRHfrhsmIB7jbvoOCpH8eMcRwkLnb7ouDYNqDSyHug3RdqKtBYdxD4xItP/khh/psvnZSlGFSeuvh9lfN0qcgzlk/JLV9LxWV41smMF3JMGS12du5VUPFqQVc8OgvotlqBKGIEDMSiVxMUBeYtfh3TXZDaZRQLZHxogWgTAmPLUsoglq0JeAe59tVb+NAudFS/5lfnTAf94/n5KsbFz04KulZbm9wE0sP5ONBXDujpi2VTQLnVKTJNoH1WuCD8WIdbhVbuxQULuItmr6nKItAd8tqoFQmASabahH2QEpJZhYvYJ/gBBAxF61lfQzD5mmLPvPfRlJtegWjRwY1BTUYrfhQt0j7OZN+6D+X0+657Z+9nsV0nK/2bPvI7cf8+H7AvG5tQVKAwxWvxgb3ufgAceVi4eot0VvXZ1GeVn0WushXLEQkQI2MDy9wX85H7Z/qxDq9qm2qKt6VaPbacIhH7zoi/yoLxLAE3R/9itUQJHgA=\",\"base64\")).toString()),qY)});var Oxe=L((JY,zY)=>{(function(t){JY&&typeof JY==\"object\"&&typeof zY<\"u\"?zY.exports=t():typeof define==\"function\"&&define.amd?define([],t):typeof window<\"u\"?window.isWindows=t():typeof global<\"u\"?global.isWindows=t():typeof self<\"u\"?self.isWindows=t():this.isWindows=t()})(function(){\"use strict\";return function(){return process&&(process.platform===\"win32\"||/^(msys|cygwin)$/.test(process.env.OSTYPE))}})});var Uxe=L((_mr,_xe)=>{\"use strict\";ZY.ifExists=mTt;var Dw=Ie(\"util\"),Jc=Ie(\"path\"),Lxe=Oxe(),hTt=/^#!\\s*(?:\\/usr\\/bin\\/env)?\\s*([^ \\t]+)(.*)$/,gTt={createPwshFile:!0,createCmdFile:Lxe(),fs:Ie(\"fs\")},dTt=new Map([[\".js\",\"node\"],[\".cjs\",\"node\"],[\".mjs\",\"node\"],[\".cmd\",\"cmd\"],[\".bat\",\"cmd\"],[\".ps1\",\"pwsh\"],[\".sh\",\"sh\"]]);function Mxe(t){let e={...gTt,...t},r=e.fs;return e.fs_={chmod:r.chmod?Dw.promisify(r.chmod):async()=>{},mkdir:Dw.promisify(r.mkdir),readFile:Dw.promisify(r.readFile),stat:Dw.promisify(r.stat),unlink:Dw.promisify(r.unlink),writeFile:Dw.promisify(r.writeFile)},e}async function ZY(t,e,r){let s=Mxe(r);await s.fs_.stat(t),await ETt(t,e,s)}function mTt(t,e,r){return ZY(t,e,r).catch(()=>{})}function yTt(t,e){return e.fs_.unlink(t).catch(()=>{})}async function ETt(t,e,r){let s=await vTt(t,r);return await ITt(e,r),CTt(t,e,s,r)}function ITt(t,e){return e.fs_.mkdir(Jc.dirname(t),{recursive:!0})}function CTt(t,e,r,s){let a=Mxe(s),n=[{generator:bTt,extension:\"\"}];return a.createCmdFile&&n.push({generator:DTt,extension:\".cmd\"}),a.createPwshFile&&n.push({generator:PTt,extension:\".ps1\"}),Promise.all(n.map(c=>STt(t,e+c.extension,r,c.generator,a)))}function wTt(t,e){return yTt(t,e)}function BTt(t,e){return xTt(t,e)}async function vTt(t,e){let a=(await e.fs_.readFile(t,\"utf8\")).trim().split(/\\r*\\n/)[0].match(hTt);if(!a){let n=Jc.extname(t).toLowerCase();return{program:dTt.get(n)||null,additionalArgs:\"\"}}return{program:a[1],additionalArgs:a[2]}}async function STt(t,e,r,s,a){let n=a.preserveSymlinks?\"--preserve-symlinks\":\"\",c=[r.additionalArgs,n].filter(f=>f).join(\" \");return a=Object.assign({},a,{prog:r.program,args:c}),await wTt(e,a),await a.fs_.writeFile(e,s(t,e,a),\"utf8\"),BTt(e,a)}function DTt(t,e,r){let a=Jc.relative(Jc.dirname(e),t).split(\"/\").join(\"\\\\\"),n=Jc.isAbsolute(a)?`\"${a}\"`:`\"%~dp0\\\\${a}\"`,c,f=r.prog,p=r.args||\"\",h=XY(r.nodePath).win32;f?(c=`\"%~dp0\\\\${f}.exe\"`,a=n):(f=n,p=\"\",a=\"\");let E=r.progArgs?`${r.progArgs.join(\" \")} `:\"\",C=h?`@SET NODE_PATH=${h}\\r\n`:\"\";return c?C+=`@IF EXIST ${c} (\\r\n  ${c} ${p} ${a} ${E}%*\\r\n) ELSE (\\r\n  @SETLOCAL\\r\n  @SET PATHEXT=%PATHEXT:;.JS;=;%\\r\n  ${f} ${p} ${a} ${E}%*\\r\n)\\r\n`:C+=`@${f} ${p} ${a} ${E}%*\\r\n`,C}function bTt(t,e,r){let s=Jc.relative(Jc.dirname(e),t),a=r.prog&&r.prog.split(\"\\\\\").join(\"/\"),n;s=s.split(\"\\\\\").join(\"/\");let c=Jc.isAbsolute(s)?`\"${s}\"`:`\"$basedir/${s}\"`,f=r.args||\"\",p=XY(r.nodePath).posix;a?(n=`\"$basedir/${r.prog}\"`,s=c):(a=c,f=\"\",s=\"\");let h=r.progArgs?`${r.progArgs.join(\" \")} `:\"\",E=`#!/bin/sh\nbasedir=$(dirname \"$(echo \"$0\" | sed -e 's,\\\\\\\\,/,g')\")\n\ncase \\`uname\\` in\n    *CYGWIN*) basedir=\\`cygpath -w \"$basedir\"\\`;;\nesac\n\n`,C=r.nodePath?`export NODE_PATH=\"${p}\"\n`:\"\";return n?E+=`${C}if [ -x ${n} ]; then\n  exec ${n} ${f} ${s} ${h}\"$@\"\nelse\n  exec ${a} ${f} ${s} ${h}\"$@\"\nfi\n`:E+=`${C}${a} ${f} ${s} ${h}\"$@\"\nexit $?\n`,E}function PTt(t,e,r){let s=Jc.relative(Jc.dirname(e),t),a=r.prog&&r.prog.split(\"\\\\\").join(\"/\"),n=a&&`\"${a}$exe\"`,c;s=s.split(\"\\\\\").join(\"/\");let f=Jc.isAbsolute(s)?`\"${s}\"`:`\"$basedir/${s}\"`,p=r.args||\"\",h=XY(r.nodePath),E=h.win32,C=h.posix;n?(c=`\"$basedir/${r.prog}$exe\"`,s=f):(n=f,p=\"\",s=\"\");let S=r.progArgs?`${r.progArgs.join(\" \")} `:\"\",P=`#!/usr/bin/env pwsh\n$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent\n\n$exe=\"\"\n${r.nodePath?`$env_node_path=$env:NODE_PATH\n$env:NODE_PATH=\"${E}\"\n`:\"\"}if ($PSVersionTable.PSVersion -lt \"6.0\" -or $IsWindows) {\n  # Fix case when both the Windows and Linux builds of Node\n  # are installed in the same directory\n  $exe=\".exe\"\n}`;return r.nodePath&&(P+=` else {\n  $env:NODE_PATH=\"${C}\"\n}`),c?P+=`\n$ret=0\nif (Test-Path ${c}) {\n  # Support pipeline input\n  if ($MyInvocation.ExpectingInput) {\n    $input | & ${c} ${p} ${s} ${S}$args\n  } else {\n    & ${c} ${p} ${s} ${S}$args\n  }\n  $ret=$LASTEXITCODE\n} else {\n  # Support pipeline input\n  if ($MyInvocation.ExpectingInput) {\n    $input | & ${n} ${p} ${s} ${S}$args\n  } else {\n    & ${n} ${p} ${s} ${S}$args\n  }\n  $ret=$LASTEXITCODE\n}\n${r.nodePath?`$env:NODE_PATH=$env_node_path\n`:\"\"}exit $ret\n`:P+=`\n# Support pipeline input\nif ($MyInvocation.ExpectingInput) {\n  $input | & ${n} ${p} ${s} ${S}$args\n} else {\n  & ${n} ${p} ${s} ${S}$args\n}\n${r.nodePath?`$env:NODE_PATH=$env_node_path\n`:\"\"}exit $LASTEXITCODE\n`,P}function xTt(t,e){return e.fs_.chmod(t,493)}function XY(t){if(!t)return{win32:\"\",posix:\"\"};let e=typeof t==\"string\"?t.split(Jc.delimiter):Array.from(t),r={};for(let s=0;s<e.length;s++){let a=e[s].split(\"/\").join(\"\\\\\"),n=Lxe()?e[s].split(\"\\\\\").join(\"/\").replace(/^([^:\\\\/]*):/,(c,f)=>`/mnt/${f.toLowerCase()}`):e[s];r.win32=r.win32?`${r.win32};${a}`:a,r.posix=r.posix?`${r.posix}:${n}`:n,r[s]={win32:a,posix:n}}return r}_xe.exports=ZY});var AV=L((oEr,oke)=>{oke.exports=Ie(\"stream\")});var uke=L((aEr,cke)=>{\"use strict\";function ake(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(t);e&&(s=s.filter(function(a){return Object.getOwnPropertyDescriptor(t,a).enumerable})),r.push.apply(r,s)}return r}function sRt(t){for(var e=1;e<arguments.length;e++){var r=arguments[e]!=null?arguments[e]:{};e%2?ake(Object(r),!0).forEach(function(s){oRt(t,s,r[s])}):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(r)):ake(Object(r)).forEach(function(s){Object.defineProperty(t,s,Object.getOwnPropertyDescriptor(r,s))})}return t}function oRt(t,e,r){return e in t?Object.defineProperty(t,e,{value:r,enumerable:!0,configurable:!0,writable:!0}):t[e]=r,t}function aRt(t,e){if(!(t instanceof e))throw new TypeError(\"Cannot call a class as a function\")}function lke(t,e){for(var r=0;r<e.length;r++){var s=e[r];s.enumerable=s.enumerable||!1,s.configurable=!0,\"value\"in s&&(s.writable=!0),Object.defineProperty(t,s.key,s)}}function lRt(t,e,r){return e&&lke(t.prototype,e),r&&lke(t,r),t}var cRt=Ie(\"buffer\"),hN=cRt.Buffer,uRt=Ie(\"util\"),pV=uRt.inspect,fRt=pV&&pV.custom||\"inspect\";function ARt(t,e,r){hN.prototype.copy.call(t,e,r)}cke.exports=function(){function t(){aRt(this,t),this.head=null,this.tail=null,this.length=0}return lRt(t,[{key:\"push\",value:function(r){var s={data:r,next:null};this.length>0?this.tail.next=s:this.head=s,this.tail=s,++this.length}},{key:\"unshift\",value:function(r){var s={data:r,next:this.head};this.length===0&&(this.tail=s),this.head=s,++this.length}},{key:\"shift\",value:function(){if(this.length!==0){var r=this.head.data;return this.length===1?this.head=this.tail=null:this.head=this.head.next,--this.length,r}}},{key:\"clear\",value:function(){this.head=this.tail=null,this.length=0}},{key:\"join\",value:function(r){if(this.length===0)return\"\";for(var s=this.head,a=\"\"+s.data;s=s.next;)a+=r+s.data;return a}},{key:\"concat\",value:function(r){if(this.length===0)return hN.alloc(0);for(var s=hN.allocUnsafe(r>>>0),a=this.head,n=0;a;)ARt(a.data,s,n),n+=a.data.length,a=a.next;return s}},{key:\"consume\",value:function(r,s){var a;return r<this.head.data.length?(a=this.head.data.slice(0,r),this.head.data=this.head.data.slice(r)):r===this.head.data.length?a=this.shift():a=s?this._getString(r):this._getBuffer(r),a}},{key:\"first\",value:function(){return this.head.data}},{key:\"_getString\",value:function(r){var s=this.head,a=1,n=s.data;for(r-=n.length;s=s.next;){var c=s.data,f=r>c.length?c.length:r;if(f===c.length?n+=c:n+=c.slice(0,r),r-=f,r===0){f===c.length?(++a,s.next?this.head=s.next:this.head=this.tail=null):(this.head=s,s.data=c.slice(f));break}++a}return this.length-=a,n}},{key:\"_getBuffer\",value:function(r){var s=hN.allocUnsafe(r),a=this.head,n=1;for(a.data.copy(s),r-=a.data.length;a=a.next;){var c=a.data,f=r>c.length?c.length:r;if(c.copy(s,s.length-r,0,f),r-=f,r===0){f===c.length?(++n,a.next?this.head=a.next:this.head=this.tail=null):(this.head=a,a.data=c.slice(f));break}++n}return this.length-=n,s}},{key:fRt,value:function(r,s){return pV(this,sRt({},s,{depth:0,customInspect:!1}))}}]),t}()});var gV=L((lEr,Ake)=>{\"use strict\";function pRt(t,e){var r=this,s=this._readableState&&this._readableState.destroyed,a=this._writableState&&this._writableState.destroyed;return s||a?(e?e(t):t&&(this._writableState?this._writableState.errorEmitted||(this._writableState.errorEmitted=!0,process.nextTick(hV,this,t)):process.nextTick(hV,this,t)),this):(this._readableState&&(this._readableState.destroyed=!0),this._writableState&&(this._writableState.destroyed=!0),this._destroy(t||null,function(n){!e&&n?r._writableState?r._writableState.errorEmitted?process.nextTick(gN,r):(r._writableState.errorEmitted=!0,process.nextTick(fke,r,n)):process.nextTick(fke,r,n):e?(process.nextTick(gN,r),e(n)):process.nextTick(gN,r)}),this)}function fke(t,e){hV(t,e),gN(t)}function gN(t){t._writableState&&!t._writableState.emitClose||t._readableState&&!t._readableState.emitClose||t.emit(\"close\")}function hRt(){this._readableState&&(this._readableState.destroyed=!1,this._readableState.reading=!1,this._readableState.ended=!1,this._readableState.endEmitted=!1),this._writableState&&(this._writableState.destroyed=!1,this._writableState.ended=!1,this._writableState.ending=!1,this._writableState.finalCalled=!1,this._writableState.prefinished=!1,this._writableState.finished=!1,this._writableState.errorEmitted=!1)}function hV(t,e){t.emit(\"error\",e)}function gRt(t,e){var r=t._readableState,s=t._writableState;r&&r.autoDestroy||s&&s.autoDestroy?t.destroy(e):t.emit(\"error\",e)}Ake.exports={destroy:pRt,undestroy:hRt,errorOrDestroy:gRt}});var ag=L((cEr,gke)=>{\"use strict\";var hke={};function Zc(t,e,r){r||(r=Error);function s(n,c,f){return typeof e==\"string\"?e:e(n,c,f)}class a extends r{constructor(c,f,p){super(s(c,f,p))}}a.prototype.name=r.name,a.prototype.code=t,hke[t]=a}function pke(t,e){if(Array.isArray(t)){let r=t.length;return t=t.map(s=>String(s)),r>2?`one of ${e} ${t.slice(0,r-1).join(\", \")}, or `+t[r-1]:r===2?`one of ${e} ${t[0]} or ${t[1]}`:`of ${e} ${t[0]}`}else return`of ${e} ${String(t)}`}function dRt(t,e,r){return t.substr(!r||r<0?0:+r,e.length)===e}function mRt(t,e,r){return(r===void 0||r>t.length)&&(r=t.length),t.substring(r-e.length,r)===e}function yRt(t,e,r){return typeof r!=\"number\"&&(r=0),r+e.length>t.length?!1:t.indexOf(e,r)!==-1}Zc(\"ERR_INVALID_OPT_VALUE\",function(t,e){return'The value \"'+e+'\" is invalid for option \"'+t+'\"'},TypeError);Zc(\"ERR_INVALID_ARG_TYPE\",function(t,e,r){let s;typeof e==\"string\"&&dRt(e,\"not \")?(s=\"must not be\",e=e.replace(/^not /,\"\")):s=\"must be\";let a;if(mRt(t,\" argument\"))a=`The ${t} ${s} ${pke(e,\"type\")}`;else{let n=yRt(t,\".\")?\"property\":\"argument\";a=`The \"${t}\" ${n} ${s} ${pke(e,\"type\")}`}return a+=`. Received type ${typeof r}`,a},TypeError);Zc(\"ERR_STREAM_PUSH_AFTER_EOF\",\"stream.push() after EOF\");Zc(\"ERR_METHOD_NOT_IMPLEMENTED\",function(t){return\"The \"+t+\" method is not implemented\"});Zc(\"ERR_STREAM_PREMATURE_CLOSE\",\"Premature close\");Zc(\"ERR_STREAM_DESTROYED\",function(t){return\"Cannot call \"+t+\" after a stream was destroyed\"});Zc(\"ERR_MULTIPLE_CALLBACK\",\"Callback called multiple times\");Zc(\"ERR_STREAM_CANNOT_PIPE\",\"Cannot pipe, not readable\");Zc(\"ERR_STREAM_WRITE_AFTER_END\",\"write after end\");Zc(\"ERR_STREAM_NULL_VALUES\",\"May not write null values to stream\",TypeError);Zc(\"ERR_UNKNOWN_ENCODING\",function(t){return\"Unknown encoding: \"+t},TypeError);Zc(\"ERR_STREAM_UNSHIFT_AFTER_END_EVENT\",\"stream.unshift() after end event\");gke.exports.codes=hke});var dV=L((uEr,dke)=>{\"use strict\";var ERt=ag().codes.ERR_INVALID_OPT_VALUE;function IRt(t,e,r){return t.highWaterMark!=null?t.highWaterMark:e?t[r]:null}function CRt(t,e,r,s){var a=IRt(e,s,r);if(a!=null){if(!(isFinite(a)&&Math.floor(a)===a)||a<0){var n=s?r:\"highWaterMark\";throw new ERt(n,a)}return Math.floor(a)}return t.objectMode?16:16*1024}dke.exports={getHighWaterMark:CRt}});var mke=L((fEr,mV)=>{typeof Object.create==\"function\"?mV.exports=function(e,r){r&&(e.super_=r,e.prototype=Object.create(r.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}))}:mV.exports=function(e,r){if(r){e.super_=r;var s=function(){};s.prototype=r.prototype,e.prototype=new s,e.prototype.constructor=e}}});var lg=L((AEr,EV)=>{try{if(yV=Ie(\"util\"),typeof yV.inherits!=\"function\")throw\"\";EV.exports=yV.inherits}catch{EV.exports=mke()}var yV});var Eke=L((pEr,yke)=>{yke.exports=Ie(\"util\").deprecate});var wV=L((hEr,Ske)=>{\"use strict\";Ske.exports=Ki;function Cke(t){var e=this;this.next=null,this.entry=null,this.finish=function(){KRt(e,t)}}var Qw;Ki.WritableState=ab;var wRt={deprecate:Eke()},wke=AV(),mN=Ie(\"buffer\").Buffer,BRt=global.Uint8Array||function(){};function vRt(t){return mN.from(t)}function SRt(t){return mN.isBuffer(t)||t instanceof BRt}var CV=gV(),DRt=dV(),bRt=DRt.getHighWaterMark,cg=ag().codes,PRt=cg.ERR_INVALID_ARG_TYPE,xRt=cg.ERR_METHOD_NOT_IMPLEMENTED,kRt=cg.ERR_MULTIPLE_CALLBACK,QRt=cg.ERR_STREAM_CANNOT_PIPE,TRt=cg.ERR_STREAM_DESTROYED,RRt=cg.ERR_STREAM_NULL_VALUES,FRt=cg.ERR_STREAM_WRITE_AFTER_END,NRt=cg.ERR_UNKNOWN_ENCODING,Tw=CV.errorOrDestroy;lg()(Ki,wke);function ORt(){}function ab(t,e,r){Qw=Qw||Wm(),t=t||{},typeof r!=\"boolean\"&&(r=e instanceof Qw),this.objectMode=!!t.objectMode,r&&(this.objectMode=this.objectMode||!!t.writableObjectMode),this.highWaterMark=bRt(this,t,\"writableHighWaterMark\",r),this.finalCalled=!1,this.needDrain=!1,this.ending=!1,this.ended=!1,this.finished=!1,this.destroyed=!1;var s=t.decodeStrings===!1;this.decodeStrings=!s,this.defaultEncoding=t.defaultEncoding||\"utf8\",this.length=0,this.writing=!1,this.corked=0,this.sync=!0,this.bufferProcessing=!1,this.onwrite=function(a){qRt(e,a)},this.writecb=null,this.writelen=0,this.bufferedRequest=null,this.lastBufferedRequest=null,this.pendingcb=0,this.prefinished=!1,this.errorEmitted=!1,this.emitClose=t.emitClose!==!1,this.autoDestroy=!!t.autoDestroy,this.bufferedRequestCount=0,this.corkedRequestsFree=new Cke(this)}ab.prototype.getBuffer=function(){for(var e=this.bufferedRequest,r=[];e;)r.push(e),e=e.next;return r};(function(){try{Object.defineProperty(ab.prototype,\"buffer\",{get:wRt.deprecate(function(){return this.getBuffer()},\"_writableState.buffer is deprecated. Use _writableState.getBuffer instead.\",\"DEP0003\")})}catch{}})();var dN;typeof Symbol==\"function\"&&Symbol.hasInstance&&typeof Function.prototype[Symbol.hasInstance]==\"function\"?(dN=Function.prototype[Symbol.hasInstance],Object.defineProperty(Ki,Symbol.hasInstance,{value:function(e){return dN.call(this,e)?!0:this!==Ki?!1:e&&e._writableState instanceof ab}})):dN=function(e){return e instanceof this};function Ki(t){Qw=Qw||Wm();var e=this instanceof Qw;if(!e&&!dN.call(Ki,this))return new Ki(t);this._writableState=new ab(t,this,e),this.writable=!0,t&&(typeof t.write==\"function\"&&(this._write=t.write),typeof t.writev==\"function\"&&(this._writev=t.writev),typeof t.destroy==\"function\"&&(this._destroy=t.destroy),typeof t.final==\"function\"&&(this._final=t.final)),wke.call(this)}Ki.prototype.pipe=function(){Tw(this,new QRt)};function LRt(t,e){var r=new FRt;Tw(t,r),process.nextTick(e,r)}function MRt(t,e,r,s){var a;return r===null?a=new RRt:typeof r!=\"string\"&&!e.objectMode&&(a=new PRt(\"chunk\",[\"string\",\"Buffer\"],r)),a?(Tw(t,a),process.nextTick(s,a),!1):!0}Ki.prototype.write=function(t,e,r){var s=this._writableState,a=!1,n=!s.objectMode&&SRt(t);return n&&!mN.isBuffer(t)&&(t=vRt(t)),typeof e==\"function\"&&(r=e,e=null),n?e=\"buffer\":e||(e=s.defaultEncoding),typeof r!=\"function\"&&(r=ORt),s.ending?LRt(this,r):(n||MRt(this,s,t,r))&&(s.pendingcb++,a=URt(this,s,n,t,e,r)),a};Ki.prototype.cork=function(){this._writableState.corked++};Ki.prototype.uncork=function(){var t=this._writableState;t.corked&&(t.corked--,!t.writing&&!t.corked&&!t.bufferProcessing&&t.bufferedRequest&&Bke(this,t))};Ki.prototype.setDefaultEncoding=function(e){if(typeof e==\"string\"&&(e=e.toLowerCase()),!([\"hex\",\"utf8\",\"utf-8\",\"ascii\",\"binary\",\"base64\",\"ucs2\",\"ucs-2\",\"utf16le\",\"utf-16le\",\"raw\"].indexOf((e+\"\").toLowerCase())>-1))throw new NRt(e);return this._writableState.defaultEncoding=e,this};Object.defineProperty(Ki.prototype,\"writableBuffer\",{enumerable:!1,get:function(){return this._writableState&&this._writableState.getBuffer()}});function _Rt(t,e,r){return!t.objectMode&&t.decodeStrings!==!1&&typeof e==\"string\"&&(e=mN.from(e,r)),e}Object.defineProperty(Ki.prototype,\"writableHighWaterMark\",{enumerable:!1,get:function(){return this._writableState.highWaterMark}});function URt(t,e,r,s,a,n){if(!r){var c=_Rt(e,s,a);s!==c&&(r=!0,a=\"buffer\",s=c)}var f=e.objectMode?1:s.length;e.length+=f;var p=e.length<e.highWaterMark;if(p||(e.needDrain=!0),e.writing||e.corked){var h=e.lastBufferedRequest;e.lastBufferedRequest={chunk:s,encoding:a,isBuf:r,callback:n,next:null},h?h.next=e.lastBufferedRequest:e.bufferedRequest=e.lastBufferedRequest,e.bufferedRequestCount+=1}else IV(t,e,!1,f,s,a,n);return p}function IV(t,e,r,s,a,n,c){e.writelen=s,e.writecb=c,e.writing=!0,e.sync=!0,e.destroyed?e.onwrite(new TRt(\"write\")):r?t._writev(a,e.onwrite):t._write(a,n,e.onwrite),e.sync=!1}function HRt(t,e,r,s,a){--e.pendingcb,r?(process.nextTick(a,s),process.nextTick(ob,t,e),t._writableState.errorEmitted=!0,Tw(t,s)):(a(s),t._writableState.errorEmitted=!0,Tw(t,s),ob(t,e))}function jRt(t){t.writing=!1,t.writecb=null,t.length-=t.writelen,t.writelen=0}function qRt(t,e){var r=t._writableState,s=r.sync,a=r.writecb;if(typeof a!=\"function\")throw new kRt;if(jRt(r),e)HRt(t,r,s,e,a);else{var n=vke(r)||t.destroyed;!n&&!r.corked&&!r.bufferProcessing&&r.bufferedRequest&&Bke(t,r),s?process.nextTick(Ike,t,r,n,a):Ike(t,r,n,a)}}function Ike(t,e,r,s){r||GRt(t,e),e.pendingcb--,s(),ob(t,e)}function GRt(t,e){e.length===0&&e.needDrain&&(e.needDrain=!1,t.emit(\"drain\"))}function Bke(t,e){e.bufferProcessing=!0;var r=e.bufferedRequest;if(t._writev&&r&&r.next){var s=e.bufferedRequestCount,a=new Array(s),n=e.corkedRequestsFree;n.entry=r;for(var c=0,f=!0;r;)a[c]=r,r.isBuf||(f=!1),r=r.next,c+=1;a.allBuffers=f,IV(t,e,!0,e.length,a,\"\",n.finish),e.pendingcb++,e.lastBufferedRequest=null,n.next?(e.corkedRequestsFree=n.next,n.next=null):e.corkedRequestsFree=new Cke(e),e.bufferedRequestCount=0}else{for(;r;){var p=r.chunk,h=r.encoding,E=r.callback,C=e.objectMode?1:p.length;if(IV(t,e,!1,C,p,h,E),r=r.next,e.bufferedRequestCount--,e.writing)break}r===null&&(e.lastBufferedRequest=null)}e.bufferedRequest=r,e.bufferProcessing=!1}Ki.prototype._write=function(t,e,r){r(new xRt(\"_write()\"))};Ki.prototype._writev=null;Ki.prototype.end=function(t,e,r){var s=this._writableState;return typeof t==\"function\"?(r=t,t=null,e=null):typeof e==\"function\"&&(r=e,e=null),t!=null&&this.write(t,e),s.corked&&(s.corked=1,this.uncork()),s.ending||VRt(this,s,r),this};Object.defineProperty(Ki.prototype,\"writableLength\",{enumerable:!1,get:function(){return this._writableState.length}});function vke(t){return t.ending&&t.length===0&&t.bufferedRequest===null&&!t.finished&&!t.writing}function WRt(t,e){t._final(function(r){e.pendingcb--,r&&Tw(t,r),e.prefinished=!0,t.emit(\"prefinish\"),ob(t,e)})}function YRt(t,e){!e.prefinished&&!e.finalCalled&&(typeof t._final==\"function\"&&!e.destroyed?(e.pendingcb++,e.finalCalled=!0,process.nextTick(WRt,t,e)):(e.prefinished=!0,t.emit(\"prefinish\")))}function ob(t,e){var r=vke(e);if(r&&(YRt(t,e),e.pendingcb===0&&(e.finished=!0,t.emit(\"finish\"),e.autoDestroy))){var s=t._readableState;(!s||s.autoDestroy&&s.endEmitted)&&t.destroy()}return r}function VRt(t,e,r){e.ending=!0,ob(t,e),r&&(e.finished?process.nextTick(r):t.once(\"finish\",r)),e.ended=!0,t.writable=!1}function KRt(t,e,r){var s=t.entry;for(t.entry=null;s;){var a=s.callback;e.pendingcb--,a(r),s=s.next}e.corkedRequestsFree.next=t}Object.defineProperty(Ki.prototype,\"destroyed\",{enumerable:!1,get:function(){return this._writableState===void 0?!1:this._writableState.destroyed},set:function(e){this._writableState&&(this._writableState.destroyed=e)}});Ki.prototype.destroy=CV.destroy;Ki.prototype._undestroy=CV.undestroy;Ki.prototype._destroy=function(t,e){e(t)}});var Wm=L((gEr,bke)=>{\"use strict\";var JRt=Object.keys||function(t){var e=[];for(var r in t)e.push(r);return e};bke.exports=yA;var Dke=SV(),vV=wV();lg()(yA,Dke);for(BV=JRt(vV.prototype),yN=0;yN<BV.length;yN++)EN=BV[yN],yA.prototype[EN]||(yA.prototype[EN]=vV.prototype[EN]);var BV,EN,yN;function yA(t){if(!(this instanceof yA))return new yA(t);Dke.call(this,t),vV.call(this,t),this.allowHalfOpen=!0,t&&(t.readable===!1&&(this.readable=!1),t.writable===!1&&(this.writable=!1),t.allowHalfOpen===!1&&(this.allowHalfOpen=!1,this.once(\"end\",zRt)))}Object.defineProperty(yA.prototype,\"writableHighWaterMark\",{enumerable:!1,get:function(){return this._writableState.highWaterMark}});Object.defineProperty(yA.prototype,\"writableBuffer\",{enumerable:!1,get:function(){return this._writableState&&this._writableState.getBuffer()}});Object.defineProperty(yA.prototype,\"writableLength\",{enumerable:!1,get:function(){return this._writableState.length}});function zRt(){this._writableState.ended||process.nextTick(ZRt,this)}function ZRt(t){t.end()}Object.defineProperty(yA.prototype,\"destroyed\",{enumerable:!1,get:function(){return this._readableState===void 0||this._writableState===void 0?!1:this._readableState.destroyed&&this._writableState.destroyed},set:function(e){this._readableState===void 0||this._writableState===void 0||(this._readableState.destroyed=e,this._writableState.destroyed=e)}})});var kke=L((DV,xke)=>{var IN=Ie(\"buffer\"),ch=IN.Buffer;function Pke(t,e){for(var r in t)e[r]=t[r]}ch.from&&ch.alloc&&ch.allocUnsafe&&ch.allocUnsafeSlow?xke.exports=IN:(Pke(IN,DV),DV.Buffer=Rw);function Rw(t,e,r){return ch(t,e,r)}Pke(ch,Rw);Rw.from=function(t,e,r){if(typeof t==\"number\")throw new TypeError(\"Argument must not be a number\");return ch(t,e,r)};Rw.alloc=function(t,e,r){if(typeof t!=\"number\")throw new TypeError(\"Argument must be a number\");var s=ch(t);return e!==void 0?typeof r==\"string\"?s.fill(e,r):s.fill(e):s.fill(0),s};Rw.allocUnsafe=function(t){if(typeof t!=\"number\")throw new TypeError(\"Argument must be a number\");return ch(t)};Rw.allocUnsafeSlow=function(t){if(typeof t!=\"number\")throw new TypeError(\"Argument must be a number\");return IN.SlowBuffer(t)}});var xV=L(Tke=>{\"use strict\";var PV=kke().Buffer,Qke=PV.isEncoding||function(t){switch(t=\"\"+t,t&&t.toLowerCase()){case\"hex\":case\"utf8\":case\"utf-8\":case\"ascii\":case\"binary\":case\"base64\":case\"ucs2\":case\"ucs-2\":case\"utf16le\":case\"utf-16le\":case\"raw\":return!0;default:return!1}};function XRt(t){if(!t)return\"utf8\";for(var e;;)switch(t){case\"utf8\":case\"utf-8\":return\"utf8\";case\"ucs2\":case\"ucs-2\":case\"utf16le\":case\"utf-16le\":return\"utf16le\";case\"latin1\":case\"binary\":return\"latin1\";case\"base64\":case\"ascii\":case\"hex\":return t;default:if(e)return;t=(\"\"+t).toLowerCase(),e=!0}}function $Rt(t){var e=XRt(t);if(typeof e!=\"string\"&&(PV.isEncoding===Qke||!Qke(t)))throw new Error(\"Unknown encoding: \"+t);return e||t}Tke.StringDecoder=lb;function lb(t){this.encoding=$Rt(t);var e;switch(this.encoding){case\"utf16le\":this.text=sFt,this.end=oFt,e=4;break;case\"utf8\":this.fillLast=rFt,e=4;break;case\"base64\":this.text=aFt,this.end=lFt,e=3;break;default:this.write=cFt,this.end=uFt;return}this.lastNeed=0,this.lastTotal=0,this.lastChar=PV.allocUnsafe(e)}lb.prototype.write=function(t){if(t.length===0)return\"\";var e,r;if(this.lastNeed){if(e=this.fillLast(t),e===void 0)return\"\";r=this.lastNeed,this.lastNeed=0}else r=0;return r<t.length?e?e+this.text(t,r):this.text(t,r):e||\"\"};lb.prototype.end=iFt;lb.prototype.text=nFt;lb.prototype.fillLast=function(t){if(this.lastNeed<=t.length)return t.copy(this.lastChar,this.lastTotal-this.lastNeed,0,this.lastNeed),this.lastChar.toString(this.encoding,0,this.lastTotal);t.copy(this.lastChar,this.lastTotal-this.lastNeed,0,t.length),this.lastNeed-=t.length};function bV(t){return t<=127?0:t>>5===6?2:t>>4===14?3:t>>3===30?4:t>>6===2?-1:-2}function eFt(t,e,r){var s=e.length-1;if(s<r)return 0;var a=bV(e[s]);return a>=0?(a>0&&(t.lastNeed=a-1),a):--s<r||a===-2?0:(a=bV(e[s]),a>=0?(a>0&&(t.lastNeed=a-2),a):--s<r||a===-2?0:(a=bV(e[s]),a>=0?(a>0&&(a===2?a=0:t.lastNeed=a-3),a):0))}function tFt(t,e,r){if((e[0]&192)!==128)return t.lastNeed=0,\"\\uFFFD\";if(t.lastNeed>1&&e.length>1){if((e[1]&192)!==128)return t.lastNeed=1,\"\\uFFFD\";if(t.lastNeed>2&&e.length>2&&(e[2]&192)!==128)return t.lastNeed=2,\"\\uFFFD\"}}function rFt(t){var e=this.lastTotal-this.lastNeed,r=tFt(this,t,e);if(r!==void 0)return r;if(this.lastNeed<=t.length)return t.copy(this.lastChar,e,0,this.lastNeed),this.lastChar.toString(this.encoding,0,this.lastTotal);t.copy(this.lastChar,e,0,t.length),this.lastNeed-=t.length}function nFt(t,e){var r=eFt(this,t,e);if(!this.lastNeed)return t.toString(\"utf8\",e);this.lastTotal=r;var s=t.length-(r-this.lastNeed);return t.copy(this.lastChar,0,s),t.toString(\"utf8\",e,s)}function iFt(t){var e=t&&t.length?this.write(t):\"\";return this.lastNeed?e+\"\\uFFFD\":e}function sFt(t,e){if((t.length-e)%2===0){var r=t.toString(\"utf16le\",e);if(r){var s=r.charCodeAt(r.length-1);if(s>=55296&&s<=56319)return this.lastNeed=2,this.lastTotal=4,this.lastChar[0]=t[t.length-2],this.lastChar[1]=t[t.length-1],r.slice(0,-1)}return r}return this.lastNeed=1,this.lastTotal=2,this.lastChar[0]=t[t.length-1],t.toString(\"utf16le\",e,t.length-1)}function oFt(t){var e=t&&t.length?this.write(t):\"\";if(this.lastNeed){var r=this.lastTotal-this.lastNeed;return e+this.lastChar.toString(\"utf16le\",0,r)}return e}function aFt(t,e){var r=(t.length-e)%3;return r===0?t.toString(\"base64\",e):(this.lastNeed=3-r,this.lastTotal=3,r===1?this.lastChar[0]=t[t.length-1]:(this.lastChar[0]=t[t.length-2],this.lastChar[1]=t[t.length-1]),t.toString(\"base64\",e,t.length-r))}function lFt(t){var e=t&&t.length?this.write(t):\"\";return this.lastNeed?e+this.lastChar.toString(\"base64\",0,3-this.lastNeed):e}function cFt(t){return t.toString(this.encoding)}function uFt(t){return t&&t.length?this.write(t):\"\"}});var CN=L((mEr,Nke)=>{\"use strict\";var Rke=ag().codes.ERR_STREAM_PREMATURE_CLOSE;function fFt(t){var e=!1;return function(){if(!e){e=!0;for(var r=arguments.length,s=new Array(r),a=0;a<r;a++)s[a]=arguments[a];t.apply(this,s)}}}function AFt(){}function pFt(t){return t.setHeader&&typeof t.abort==\"function\"}function Fke(t,e,r){if(typeof e==\"function\")return Fke(t,null,e);e||(e={}),r=fFt(r||AFt);var s=e.readable||e.readable!==!1&&t.readable,a=e.writable||e.writable!==!1&&t.writable,n=function(){t.writable||f()},c=t._writableState&&t._writableState.finished,f=function(){a=!1,c=!0,s||r.call(t)},p=t._readableState&&t._readableState.endEmitted,h=function(){s=!1,p=!0,a||r.call(t)},E=function(I){r.call(t,I)},C=function(){var I;if(s&&!p)return(!t._readableState||!t._readableState.ended)&&(I=new Rke),r.call(t,I);if(a&&!c)return(!t._writableState||!t._writableState.ended)&&(I=new Rke),r.call(t,I)},S=function(){t.req.on(\"finish\",f)};return pFt(t)?(t.on(\"complete\",f),t.on(\"abort\",C),t.req?S():t.on(\"request\",S)):a&&!t._writableState&&(t.on(\"end\",n),t.on(\"close\",n)),t.on(\"end\",h),t.on(\"finish\",f),e.error!==!1&&t.on(\"error\",E),t.on(\"close\",C),function(){t.removeListener(\"complete\",f),t.removeListener(\"abort\",C),t.removeListener(\"request\",S),t.req&&t.req.removeListener(\"finish\",f),t.removeListener(\"end\",n),t.removeListener(\"close\",n),t.removeListener(\"finish\",f),t.removeListener(\"end\",h),t.removeListener(\"error\",E),t.removeListener(\"close\",C)}}Nke.exports=Fke});var Lke=L((yEr,Oke)=>{\"use strict\";var wN;function ug(t,e,r){return e in t?Object.defineProperty(t,e,{value:r,enumerable:!0,configurable:!0,writable:!0}):t[e]=r,t}var hFt=CN(),fg=Symbol(\"lastResolve\"),Ym=Symbol(\"lastReject\"),ub=Symbol(\"error\"),BN=Symbol(\"ended\"),Vm=Symbol(\"lastPromise\"),kV=Symbol(\"handlePromise\"),Km=Symbol(\"stream\");function Ag(t,e){return{value:t,done:e}}function gFt(t){var e=t[fg];if(e!==null){var r=t[Km].read();r!==null&&(t[Vm]=null,t[fg]=null,t[Ym]=null,e(Ag(r,!1)))}}function dFt(t){process.nextTick(gFt,t)}function mFt(t,e){return function(r,s){t.then(function(){if(e[BN]){r(Ag(void 0,!0));return}e[kV](r,s)},s)}}var yFt=Object.getPrototypeOf(function(){}),EFt=Object.setPrototypeOf((wN={get stream(){return this[Km]},next:function(){var e=this,r=this[ub];if(r!==null)return Promise.reject(r);if(this[BN])return Promise.resolve(Ag(void 0,!0));if(this[Km].destroyed)return new Promise(function(c,f){process.nextTick(function(){e[ub]?f(e[ub]):c(Ag(void 0,!0))})});var s=this[Vm],a;if(s)a=new Promise(mFt(s,this));else{var n=this[Km].read();if(n!==null)return Promise.resolve(Ag(n,!1));a=new Promise(this[kV])}return this[Vm]=a,a}},ug(wN,Symbol.asyncIterator,function(){return this}),ug(wN,\"return\",function(){var e=this;return new Promise(function(r,s){e[Km].destroy(null,function(a){if(a){s(a);return}r(Ag(void 0,!0))})})}),wN),yFt),IFt=function(e){var r,s=Object.create(EFt,(r={},ug(r,Km,{value:e,writable:!0}),ug(r,fg,{value:null,writable:!0}),ug(r,Ym,{value:null,writable:!0}),ug(r,ub,{value:null,writable:!0}),ug(r,BN,{value:e._readableState.endEmitted,writable:!0}),ug(r,kV,{value:function(n,c){var f=s[Km].read();f?(s[Vm]=null,s[fg]=null,s[Ym]=null,n(Ag(f,!1))):(s[fg]=n,s[Ym]=c)},writable:!0}),r));return s[Vm]=null,hFt(e,function(a){if(a&&a.code!==\"ERR_STREAM_PREMATURE_CLOSE\"){var n=s[Ym];n!==null&&(s[Vm]=null,s[fg]=null,s[Ym]=null,n(a)),s[ub]=a;return}var c=s[fg];c!==null&&(s[Vm]=null,s[fg]=null,s[Ym]=null,c(Ag(void 0,!0))),s[BN]=!0}),e.on(\"readable\",dFt.bind(null,s)),s};Oke.exports=IFt});var Hke=L((EEr,Uke)=>{\"use strict\";function Mke(t,e,r,s,a,n,c){try{var f=t[n](c),p=f.value}catch(h){r(h);return}f.done?e(p):Promise.resolve(p).then(s,a)}function CFt(t){return function(){var e=this,r=arguments;return new Promise(function(s,a){var n=t.apply(e,r);function c(p){Mke(n,s,a,c,f,\"next\",p)}function f(p){Mke(n,s,a,c,f,\"throw\",p)}c(void 0)})}}function _ke(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(t);e&&(s=s.filter(function(a){return Object.getOwnPropertyDescriptor(t,a).enumerable})),r.push.apply(r,s)}return r}function wFt(t){for(var e=1;e<arguments.length;e++){var r=arguments[e]!=null?arguments[e]:{};e%2?_ke(Object(r),!0).forEach(function(s){BFt(t,s,r[s])}):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(r)):_ke(Object(r)).forEach(function(s){Object.defineProperty(t,s,Object.getOwnPropertyDescriptor(r,s))})}return t}function BFt(t,e,r){return e in t?Object.defineProperty(t,e,{value:r,enumerable:!0,configurable:!0,writable:!0}):t[e]=r,t}var vFt=ag().codes.ERR_INVALID_ARG_TYPE;function SFt(t,e,r){var s;if(e&&typeof e.next==\"function\")s=e;else if(e&&e[Symbol.asyncIterator])s=e[Symbol.asyncIterator]();else if(e&&e[Symbol.iterator])s=e[Symbol.iterator]();else throw new vFt(\"iterable\",[\"Iterable\"],e);var a=new t(wFt({objectMode:!0},r)),n=!1;a._read=function(){n||(n=!0,c())};function c(){return f.apply(this,arguments)}function f(){return f=CFt(function*(){try{var p=yield s.next(),h=p.value,E=p.done;E?a.push(null):a.push(yield h)?c():n=!1}catch(C){a.destroy(C)}}),f.apply(this,arguments)}return a}Uke.exports=SFt});var SV=L((CEr,Zke)=>{\"use strict\";Zke.exports=Pn;var Fw;Pn.ReadableState=Wke;var IEr=Ie(\"events\").EventEmitter,Gke=function(e,r){return e.listeners(r).length},Ab=AV(),vN=Ie(\"buffer\").Buffer,DFt=global.Uint8Array||function(){};function bFt(t){return vN.from(t)}function PFt(t){return vN.isBuffer(t)||t instanceof DFt}var QV=Ie(\"util\"),ln;QV&&QV.debuglog?ln=QV.debuglog(\"stream\"):ln=function(){};var xFt=uke(),MV=gV(),kFt=dV(),QFt=kFt.getHighWaterMark,SN=ag().codes,TFt=SN.ERR_INVALID_ARG_TYPE,RFt=SN.ERR_STREAM_PUSH_AFTER_EOF,FFt=SN.ERR_METHOD_NOT_IMPLEMENTED,NFt=SN.ERR_STREAM_UNSHIFT_AFTER_END_EVENT,Nw,TV,RV;lg()(Pn,Ab);var fb=MV.errorOrDestroy,FV=[\"error\",\"close\",\"destroy\",\"pause\",\"resume\"];function OFt(t,e,r){if(typeof t.prependListener==\"function\")return t.prependListener(e,r);!t._events||!t._events[e]?t.on(e,r):Array.isArray(t._events[e])?t._events[e].unshift(r):t._events[e]=[r,t._events[e]]}function Wke(t,e,r){Fw=Fw||Wm(),t=t||{},typeof r!=\"boolean\"&&(r=e instanceof Fw),this.objectMode=!!t.objectMode,r&&(this.objectMode=this.objectMode||!!t.readableObjectMode),this.highWaterMark=QFt(this,t,\"readableHighWaterMark\",r),this.buffer=new xFt,this.length=0,this.pipes=null,this.pipesCount=0,this.flowing=null,this.ended=!1,this.endEmitted=!1,this.reading=!1,this.sync=!0,this.needReadable=!1,this.emittedReadable=!1,this.readableListening=!1,this.resumeScheduled=!1,this.paused=!0,this.emitClose=t.emitClose!==!1,this.autoDestroy=!!t.autoDestroy,this.destroyed=!1,this.defaultEncoding=t.defaultEncoding||\"utf8\",this.awaitDrain=0,this.readingMore=!1,this.decoder=null,this.encoding=null,t.encoding&&(Nw||(Nw=xV().StringDecoder),this.decoder=new Nw(t.encoding),this.encoding=t.encoding)}function Pn(t){if(Fw=Fw||Wm(),!(this instanceof Pn))return new Pn(t);var e=this instanceof Fw;this._readableState=new Wke(t,this,e),this.readable=!0,t&&(typeof t.read==\"function\"&&(this._read=t.read),typeof t.destroy==\"function\"&&(this._destroy=t.destroy)),Ab.call(this)}Object.defineProperty(Pn.prototype,\"destroyed\",{enumerable:!1,get:function(){return this._readableState===void 0?!1:this._readableState.destroyed},set:function(e){this._readableState&&(this._readableState.destroyed=e)}});Pn.prototype.destroy=MV.destroy;Pn.prototype._undestroy=MV.undestroy;Pn.prototype._destroy=function(t,e){e(t)};Pn.prototype.push=function(t,e){var r=this._readableState,s;return r.objectMode?s=!0:typeof t==\"string\"&&(e=e||r.defaultEncoding,e!==r.encoding&&(t=vN.from(t,e),e=\"\"),s=!0),Yke(this,t,e,!1,s)};Pn.prototype.unshift=function(t){return Yke(this,t,null,!0,!1)};function Yke(t,e,r,s,a){ln(\"readableAddChunk\",e);var n=t._readableState;if(e===null)n.reading=!1,_Ft(t,n);else{var c;if(a||(c=LFt(n,e)),c)fb(t,c);else if(n.objectMode||e&&e.length>0)if(typeof e!=\"string\"&&!n.objectMode&&Object.getPrototypeOf(e)!==vN.prototype&&(e=bFt(e)),s)n.endEmitted?fb(t,new NFt):NV(t,n,e,!0);else if(n.ended)fb(t,new RFt);else{if(n.destroyed)return!1;n.reading=!1,n.decoder&&!r?(e=n.decoder.write(e),n.objectMode||e.length!==0?NV(t,n,e,!1):LV(t,n)):NV(t,n,e,!1)}else s||(n.reading=!1,LV(t,n))}return!n.ended&&(n.length<n.highWaterMark||n.length===0)}function NV(t,e,r,s){e.flowing&&e.length===0&&!e.sync?(e.awaitDrain=0,t.emit(\"data\",r)):(e.length+=e.objectMode?1:r.length,s?e.buffer.unshift(r):e.buffer.push(r),e.needReadable&&DN(t)),LV(t,e)}function LFt(t,e){var r;return!PFt(e)&&typeof e!=\"string\"&&e!==void 0&&!t.objectMode&&(r=new TFt(\"chunk\",[\"string\",\"Buffer\",\"Uint8Array\"],e)),r}Pn.prototype.isPaused=function(){return this._readableState.flowing===!1};Pn.prototype.setEncoding=function(t){Nw||(Nw=xV().StringDecoder);var e=new Nw(t);this._readableState.decoder=e,this._readableState.encoding=this._readableState.decoder.encoding;for(var r=this._readableState.buffer.head,s=\"\";r!==null;)s+=e.write(r.data),r=r.next;return this._readableState.buffer.clear(),s!==\"\"&&this._readableState.buffer.push(s),this._readableState.length=s.length,this};var jke=1073741824;function MFt(t){return t>=jke?t=jke:(t--,t|=t>>>1,t|=t>>>2,t|=t>>>4,t|=t>>>8,t|=t>>>16,t++),t}function qke(t,e){return t<=0||e.length===0&&e.ended?0:e.objectMode?1:t!==t?e.flowing&&e.length?e.buffer.head.data.length:e.length:(t>e.highWaterMark&&(e.highWaterMark=MFt(t)),t<=e.length?t:e.ended?e.length:(e.needReadable=!0,0))}Pn.prototype.read=function(t){ln(\"read\",t),t=parseInt(t,10);var e=this._readableState,r=t;if(t!==0&&(e.emittedReadable=!1),t===0&&e.needReadable&&((e.highWaterMark!==0?e.length>=e.highWaterMark:e.length>0)||e.ended))return ln(\"read: emitReadable\",e.length,e.ended),e.length===0&&e.ended?OV(this):DN(this),null;if(t=qke(t,e),t===0&&e.ended)return e.length===0&&OV(this),null;var s=e.needReadable;ln(\"need readable\",s),(e.length===0||e.length-t<e.highWaterMark)&&(s=!0,ln(\"length less than watermark\",s)),e.ended||e.reading?(s=!1,ln(\"reading or ended\",s)):s&&(ln(\"do read\"),e.reading=!0,e.sync=!0,e.length===0&&(e.needReadable=!0),this._read(e.highWaterMark),e.sync=!1,e.reading||(t=qke(r,e)));var a;return t>0?a=Jke(t,e):a=null,a===null?(e.needReadable=e.length<=e.highWaterMark,t=0):(e.length-=t,e.awaitDrain=0),e.length===0&&(e.ended||(e.needReadable=!0),r!==t&&e.ended&&OV(this)),a!==null&&this.emit(\"data\",a),a};function _Ft(t,e){if(ln(\"onEofChunk\"),!e.ended){if(e.decoder){var r=e.decoder.end();r&&r.length&&(e.buffer.push(r),e.length+=e.objectMode?1:r.length)}e.ended=!0,e.sync?DN(t):(e.needReadable=!1,e.emittedReadable||(e.emittedReadable=!0,Vke(t)))}}function DN(t){var e=t._readableState;ln(\"emitReadable\",e.needReadable,e.emittedReadable),e.needReadable=!1,e.emittedReadable||(ln(\"emitReadable\",e.flowing),e.emittedReadable=!0,process.nextTick(Vke,t))}function Vke(t){var e=t._readableState;ln(\"emitReadable_\",e.destroyed,e.length,e.ended),!e.destroyed&&(e.length||e.ended)&&(t.emit(\"readable\"),e.emittedReadable=!1),e.needReadable=!e.flowing&&!e.ended&&e.length<=e.highWaterMark,_V(t)}function LV(t,e){e.readingMore||(e.readingMore=!0,process.nextTick(UFt,t,e))}function UFt(t,e){for(;!e.reading&&!e.ended&&(e.length<e.highWaterMark||e.flowing&&e.length===0);){var r=e.length;if(ln(\"maybeReadMore read 0\"),t.read(0),r===e.length)break}e.readingMore=!1}Pn.prototype._read=function(t){fb(this,new FFt(\"_read()\"))};Pn.prototype.pipe=function(t,e){var r=this,s=this._readableState;switch(s.pipesCount){case 0:s.pipes=t;break;case 1:s.pipes=[s.pipes,t];break;default:s.pipes.push(t);break}s.pipesCount+=1,ln(\"pipe count=%d opts=%j\",s.pipesCount,e);var a=(!e||e.end!==!1)&&t!==process.stdout&&t!==process.stderr,n=a?f:R;s.endEmitted?process.nextTick(n):r.once(\"end\",n),t.on(\"unpipe\",c);function c(N,U){ln(\"onunpipe\"),N===r&&U&&U.hasUnpiped===!1&&(U.hasUnpiped=!0,E())}function f(){ln(\"onend\"),t.end()}var p=HFt(r);t.on(\"drain\",p);var h=!1;function E(){ln(\"cleanup\"),t.removeListener(\"close\",P),t.removeListener(\"finish\",I),t.removeListener(\"drain\",p),t.removeListener(\"error\",S),t.removeListener(\"unpipe\",c),r.removeListener(\"end\",f),r.removeListener(\"end\",R),r.removeListener(\"data\",C),h=!0,s.awaitDrain&&(!t._writableState||t._writableState.needDrain)&&p()}r.on(\"data\",C);function C(N){ln(\"ondata\");var U=t.write(N);ln(\"dest.write\",U),U===!1&&((s.pipesCount===1&&s.pipes===t||s.pipesCount>1&&zke(s.pipes,t)!==-1)&&!h&&(ln(\"false write response, pause\",s.awaitDrain),s.awaitDrain++),r.pause())}function S(N){ln(\"onerror\",N),R(),t.removeListener(\"error\",S),Gke(t,\"error\")===0&&fb(t,N)}OFt(t,\"error\",S);function P(){t.removeListener(\"finish\",I),R()}t.once(\"close\",P);function I(){ln(\"onfinish\"),t.removeListener(\"close\",P),R()}t.once(\"finish\",I);function R(){ln(\"unpipe\"),r.unpipe(t)}return t.emit(\"pipe\",r),s.flowing||(ln(\"pipe resume\"),r.resume()),t};function HFt(t){return function(){var r=t._readableState;ln(\"pipeOnDrain\",r.awaitDrain),r.awaitDrain&&r.awaitDrain--,r.awaitDrain===0&&Gke(t,\"data\")&&(r.flowing=!0,_V(t))}}Pn.prototype.unpipe=function(t){var e=this._readableState,r={hasUnpiped:!1};if(e.pipesCount===0)return this;if(e.pipesCount===1)return t&&t!==e.pipes?this:(t||(t=e.pipes),e.pipes=null,e.pipesCount=0,e.flowing=!1,t&&t.emit(\"unpipe\",this,r),this);if(!t){var s=e.pipes,a=e.pipesCount;e.pipes=null,e.pipesCount=0,e.flowing=!1;for(var n=0;n<a;n++)s[n].emit(\"unpipe\",this,{hasUnpiped:!1});return this}var c=zke(e.pipes,t);return c===-1?this:(e.pipes.splice(c,1),e.pipesCount-=1,e.pipesCount===1&&(e.pipes=e.pipes[0]),t.emit(\"unpipe\",this,r),this)};Pn.prototype.on=function(t,e){var r=Ab.prototype.on.call(this,t,e),s=this._readableState;return t===\"data\"?(s.readableListening=this.listenerCount(\"readable\")>0,s.flowing!==!1&&this.resume()):t===\"readable\"&&!s.endEmitted&&!s.readableListening&&(s.readableListening=s.needReadable=!0,s.flowing=!1,s.emittedReadable=!1,ln(\"on readable\",s.length,s.reading),s.length?DN(this):s.reading||process.nextTick(jFt,this)),r};Pn.prototype.addListener=Pn.prototype.on;Pn.prototype.removeListener=function(t,e){var r=Ab.prototype.removeListener.call(this,t,e);return t===\"readable\"&&process.nextTick(Kke,this),r};Pn.prototype.removeAllListeners=function(t){var e=Ab.prototype.removeAllListeners.apply(this,arguments);return(t===\"readable\"||t===void 0)&&process.nextTick(Kke,this),e};function Kke(t){var e=t._readableState;e.readableListening=t.listenerCount(\"readable\")>0,e.resumeScheduled&&!e.paused?e.flowing=!0:t.listenerCount(\"data\")>0&&t.resume()}function jFt(t){ln(\"readable nexttick read 0\"),t.read(0)}Pn.prototype.resume=function(){var t=this._readableState;return t.flowing||(ln(\"resume\"),t.flowing=!t.readableListening,qFt(this,t)),t.paused=!1,this};function qFt(t,e){e.resumeScheduled||(e.resumeScheduled=!0,process.nextTick(GFt,t,e))}function GFt(t,e){ln(\"resume\",e.reading),e.reading||t.read(0),e.resumeScheduled=!1,t.emit(\"resume\"),_V(t),e.flowing&&!e.reading&&t.read(0)}Pn.prototype.pause=function(){return ln(\"call pause flowing=%j\",this._readableState.flowing),this._readableState.flowing!==!1&&(ln(\"pause\"),this._readableState.flowing=!1,this.emit(\"pause\")),this._readableState.paused=!0,this};function _V(t){var e=t._readableState;for(ln(\"flow\",e.flowing);e.flowing&&t.read()!==null;);}Pn.prototype.wrap=function(t){var e=this,r=this._readableState,s=!1;t.on(\"end\",function(){if(ln(\"wrapped end\"),r.decoder&&!r.ended){var c=r.decoder.end();c&&c.length&&e.push(c)}e.push(null)}),t.on(\"data\",function(c){if(ln(\"wrapped data\"),r.decoder&&(c=r.decoder.write(c)),!(r.objectMode&&c==null)&&!(!r.objectMode&&(!c||!c.length))){var f=e.push(c);f||(s=!0,t.pause())}});for(var a in t)this[a]===void 0&&typeof t[a]==\"function\"&&(this[a]=function(f){return function(){return t[f].apply(t,arguments)}}(a));for(var n=0;n<FV.length;n++)t.on(FV[n],this.emit.bind(this,FV[n]));return this._read=function(c){ln(\"wrapped _read\",c),s&&(s=!1,t.resume())},this};typeof Symbol==\"function\"&&(Pn.prototype[Symbol.asyncIterator]=function(){return TV===void 0&&(TV=Lke()),TV(this)});Object.defineProperty(Pn.prototype,\"readableHighWaterMark\",{enumerable:!1,get:function(){return this._readableState.highWaterMark}});Object.defineProperty(Pn.prototype,\"readableBuffer\",{enumerable:!1,get:function(){return this._readableState&&this._readableState.buffer}});Object.defineProperty(Pn.prototype,\"readableFlowing\",{enumerable:!1,get:function(){return this._readableState.flowing},set:function(e){this._readableState&&(this._readableState.flowing=e)}});Pn._fromList=Jke;Object.defineProperty(Pn.prototype,\"readableLength\",{enumerable:!1,get:function(){return this._readableState.length}});function Jke(t,e){if(e.length===0)return null;var r;return e.objectMode?r=e.buffer.shift():!t||t>=e.length?(e.decoder?r=e.buffer.join(\"\"):e.buffer.length===1?r=e.buffer.first():r=e.buffer.concat(e.length),e.buffer.clear()):r=e.buffer.consume(t,e.decoder),r}function OV(t){var e=t._readableState;ln(\"endReadable\",e.endEmitted),e.endEmitted||(e.ended=!0,process.nextTick(WFt,e,t))}function WFt(t,e){if(ln(\"endReadableNT\",t.endEmitted,t.length),!t.endEmitted&&t.length===0&&(t.endEmitted=!0,e.readable=!1,e.emit(\"end\"),t.autoDestroy)){var r=e._writableState;(!r||r.autoDestroy&&r.finished)&&e.destroy()}}typeof Symbol==\"function\"&&(Pn.from=function(t,e){return RV===void 0&&(RV=Hke()),RV(Pn,t,e)});function zke(t,e){for(var r=0,s=t.length;r<s;r++)if(t[r]===e)return r;return-1}});var UV=L((wEr,$ke)=>{\"use strict\";$ke.exports=uh;var bN=ag().codes,YFt=bN.ERR_METHOD_NOT_IMPLEMENTED,VFt=bN.ERR_MULTIPLE_CALLBACK,KFt=bN.ERR_TRANSFORM_ALREADY_TRANSFORMING,JFt=bN.ERR_TRANSFORM_WITH_LENGTH_0,PN=Wm();lg()(uh,PN);function zFt(t,e){var r=this._transformState;r.transforming=!1;var s=r.writecb;if(s===null)return this.emit(\"error\",new VFt);r.writechunk=null,r.writecb=null,e!=null&&this.push(e),s(t);var a=this._readableState;a.reading=!1,(a.needReadable||a.length<a.highWaterMark)&&this._read(a.highWaterMark)}function uh(t){if(!(this instanceof uh))return new uh(t);PN.call(this,t),this._transformState={afterTransform:zFt.bind(this),needTransform:!1,transforming:!1,writecb:null,writechunk:null,writeencoding:null},this._readableState.needReadable=!0,this._readableState.sync=!1,t&&(typeof t.transform==\"function\"&&(this._transform=t.transform),typeof t.flush==\"function\"&&(this._flush=t.flush)),this.on(\"prefinish\",ZFt)}function ZFt(){var t=this;typeof this._flush==\"function\"&&!this._readableState.destroyed?this._flush(function(e,r){Xke(t,e,r)}):Xke(this,null,null)}uh.prototype.push=function(t,e){return this._transformState.needTransform=!1,PN.prototype.push.call(this,t,e)};uh.prototype._transform=function(t,e,r){r(new YFt(\"_transform()\"))};uh.prototype._write=function(t,e,r){var s=this._transformState;if(s.writecb=r,s.writechunk=t,s.writeencoding=e,!s.transforming){var a=this._readableState;(s.needTransform||a.needReadable||a.length<a.highWaterMark)&&this._read(a.highWaterMark)}};uh.prototype._read=function(t){var e=this._transformState;e.writechunk!==null&&!e.transforming?(e.transforming=!0,this._transform(e.writechunk,e.writeencoding,e.afterTransform)):e.needTransform=!0};uh.prototype._destroy=function(t,e){PN.prototype._destroy.call(this,t,function(r){e(r)})};function Xke(t,e,r){if(e)return t.emit(\"error\",e);if(r!=null&&t.push(r),t._writableState.length)throw new JFt;if(t._transformState.transforming)throw new KFt;return t.push(null)}});var rQe=L((BEr,tQe)=>{\"use strict\";tQe.exports=pb;var eQe=UV();lg()(pb,eQe);function pb(t){if(!(this instanceof pb))return new pb(t);eQe.call(this,t)}pb.prototype._transform=function(t,e,r){r(null,t)}});var aQe=L((vEr,oQe)=>{\"use strict\";var HV;function XFt(t){var e=!1;return function(){e||(e=!0,t.apply(void 0,arguments))}}var sQe=ag().codes,$Ft=sQe.ERR_MISSING_ARGS,eNt=sQe.ERR_STREAM_DESTROYED;function nQe(t){if(t)throw t}function tNt(t){return t.setHeader&&typeof t.abort==\"function\"}function rNt(t,e,r,s){s=XFt(s);var a=!1;t.on(\"close\",function(){a=!0}),HV===void 0&&(HV=CN()),HV(t,{readable:e,writable:r},function(c){if(c)return s(c);a=!0,s()});var n=!1;return function(c){if(!a&&!n){if(n=!0,tNt(t))return t.abort();if(typeof t.destroy==\"function\")return t.destroy();s(c||new eNt(\"pipe\"))}}}function iQe(t){t()}function nNt(t,e){return t.pipe(e)}function iNt(t){return!t.length||typeof t[t.length-1]!=\"function\"?nQe:t.pop()}function sNt(){for(var t=arguments.length,e=new Array(t),r=0;r<t;r++)e[r]=arguments[r];var s=iNt(e);if(Array.isArray(e[0])&&(e=e[0]),e.length<2)throw new $Ft(\"streams\");var a,n=e.map(function(c,f){var p=f<e.length-1,h=f>0;return rNt(c,p,h,function(E){a||(a=E),E&&n.forEach(iQe),!p&&(n.forEach(iQe),s(a))})});return e.reduce(nNt)}oQe.exports=sNt});var Ow=L((Xc,gb)=>{var hb=Ie(\"stream\");process.env.READABLE_STREAM===\"disable\"&&hb?(gb.exports=hb.Readable,Object.assign(gb.exports,hb),gb.exports.Stream=hb):(Xc=gb.exports=SV(),Xc.Stream=hb||Xc,Xc.Readable=Xc,Xc.Writable=wV(),Xc.Duplex=Wm(),Xc.Transform=UV(),Xc.PassThrough=rQe(),Xc.finished=CN(),Xc.pipeline=aQe())});var uQe=L((SEr,cQe)=>{\"use strict\";var{Buffer:uf}=Ie(\"buffer\"),lQe=Symbol.for(\"BufferList\");function wi(t){if(!(this instanceof wi))return new wi(t);wi._init.call(this,t)}wi._init=function(e){Object.defineProperty(this,lQe,{value:!0}),this._bufs=[],this.length=0,e&&this.append(e)};wi.prototype._new=function(e){return new wi(e)};wi.prototype._offset=function(e){if(e===0)return[0,0];let r=0;for(let s=0;s<this._bufs.length;s++){let a=r+this._bufs[s].length;if(e<a||s===this._bufs.length-1)return[s,e-r];r=a}};wi.prototype._reverseOffset=function(t){let e=t[0],r=t[1];for(let s=0;s<e;s++)r+=this._bufs[s].length;return r};wi.prototype.get=function(e){if(e>this.length||e<0)return;let r=this._offset(e);return this._bufs[r[0]][r[1]]};wi.prototype.slice=function(e,r){return typeof e==\"number\"&&e<0&&(e+=this.length),typeof r==\"number\"&&r<0&&(r+=this.length),this.copy(null,0,e,r)};wi.prototype.copy=function(e,r,s,a){if((typeof s!=\"number\"||s<0)&&(s=0),(typeof a!=\"number\"||a>this.length)&&(a=this.length),s>=this.length||a<=0)return e||uf.alloc(0);let n=!!e,c=this._offset(s),f=a-s,p=f,h=n&&r||0,E=c[1];if(s===0&&a===this.length){if(!n)return this._bufs.length===1?this._bufs[0]:uf.concat(this._bufs,this.length);for(let C=0;C<this._bufs.length;C++)this._bufs[C].copy(e,h),h+=this._bufs[C].length;return e}if(p<=this._bufs[c[0]].length-E)return n?this._bufs[c[0]].copy(e,r,E,E+p):this._bufs[c[0]].slice(E,E+p);n||(e=uf.allocUnsafe(f));for(let C=c[0];C<this._bufs.length;C++){let S=this._bufs[C].length-E;if(p>S)this._bufs[C].copy(e,h,E),h+=S;else{this._bufs[C].copy(e,h,E,E+p),h+=S;break}p-=S,E&&(E=0)}return e.length>h?e.slice(0,h):e};wi.prototype.shallowSlice=function(e,r){if(e=e||0,r=typeof r!=\"number\"?this.length:r,e<0&&(e+=this.length),r<0&&(r+=this.length),e===r)return this._new();let s=this._offset(e),a=this._offset(r),n=this._bufs.slice(s[0],a[0]+1);return a[1]===0?n.pop():n[n.length-1]=n[n.length-1].slice(0,a[1]),s[1]!==0&&(n[0]=n[0].slice(s[1])),this._new(n)};wi.prototype.toString=function(e,r,s){return this.slice(r,s).toString(e)};wi.prototype.consume=function(e){if(e=Math.trunc(e),Number.isNaN(e)||e<=0)return this;for(;this._bufs.length;)if(e>=this._bufs[0].length)e-=this._bufs[0].length,this.length-=this._bufs[0].length,this._bufs.shift();else{this._bufs[0]=this._bufs[0].slice(e),this.length-=e;break}return this};wi.prototype.duplicate=function(){let e=this._new();for(let r=0;r<this._bufs.length;r++)e.append(this._bufs[r]);return e};wi.prototype.append=function(e){if(e==null)return this;if(e.buffer)this._appendBuffer(uf.from(e.buffer,e.byteOffset,e.byteLength));else if(Array.isArray(e))for(let r=0;r<e.length;r++)this.append(e[r]);else if(this._isBufferList(e))for(let r=0;r<e._bufs.length;r++)this.append(e._bufs[r]);else typeof e==\"number\"&&(e=e.toString()),this._appendBuffer(uf.from(e));return this};wi.prototype._appendBuffer=function(e){this._bufs.push(e),this.length+=e.length};wi.prototype.indexOf=function(t,e,r){if(r===void 0&&typeof e==\"string\"&&(r=e,e=void 0),typeof t==\"function\"||Array.isArray(t))throw new TypeError('The \"value\" argument must be one of type string, Buffer, BufferList, or Uint8Array.');if(typeof t==\"number\"?t=uf.from([t]):typeof t==\"string\"?t=uf.from(t,r):this._isBufferList(t)?t=t.slice():Array.isArray(t.buffer)?t=uf.from(t.buffer,t.byteOffset,t.byteLength):uf.isBuffer(t)||(t=uf.from(t)),e=Number(e||0),isNaN(e)&&(e=0),e<0&&(e=this.length+e),e<0&&(e=0),t.length===0)return e>this.length?this.length:e;let s=this._offset(e),a=s[0],n=s[1];for(;a<this._bufs.length;a++){let c=this._bufs[a];for(;n<c.length;)if(c.length-n>=t.length){let p=c.indexOf(t,n);if(p!==-1)return this._reverseOffset([a,p]);n=c.length-t.length+1}else{let p=this._reverseOffset([a,n]);if(this._match(p,t))return p;n++}n=0}return-1};wi.prototype._match=function(t,e){if(this.length-t<e.length)return!1;for(let r=0;r<e.length;r++)if(this.get(t+r)!==e[r])return!1;return!0};(function(){let t={readDoubleBE:8,readDoubleLE:8,readFloatBE:4,readFloatLE:4,readInt32BE:4,readInt32LE:4,readUInt32BE:4,readUInt32LE:4,readInt16BE:2,readInt16LE:2,readUInt16BE:2,readUInt16LE:2,readInt8:1,readUInt8:1,readIntBE:null,readIntLE:null,readUIntBE:null,readUIntLE:null};for(let e in t)(function(r){t[r]===null?wi.prototype[r]=function(s,a){return this.slice(s,s+a)[r](0,a)}:wi.prototype[r]=function(s=0){return this.slice(s,s+t[r])[r](0)}})(e)})();wi.prototype._isBufferList=function(e){return e instanceof wi||wi.isBufferList(e)};wi.isBufferList=function(e){return e!=null&&e[lQe]};cQe.exports=wi});var fQe=L((DEr,xN)=>{\"use strict\";var jV=Ow().Duplex,oNt=lg(),db=uQe();function na(t){if(!(this instanceof na))return new na(t);if(typeof t==\"function\"){this._callback=t;let e=function(s){this._callback&&(this._callback(s),this._callback=null)}.bind(this);this.on(\"pipe\",function(s){s.on(\"error\",e)}),this.on(\"unpipe\",function(s){s.removeListener(\"error\",e)}),t=null}db._init.call(this,t),jV.call(this)}oNt(na,jV);Object.assign(na.prototype,db.prototype);na.prototype._new=function(e){return new na(e)};na.prototype._write=function(e,r,s){this._appendBuffer(e),typeof s==\"function\"&&s()};na.prototype._read=function(e){if(!this.length)return this.push(null);e=Math.min(e,this.length),this.push(this.slice(0,e)),this.consume(e)};na.prototype.end=function(e){jV.prototype.end.call(this,e),this._callback&&(this._callback(null,this.slice()),this._callback=null)};na.prototype._destroy=function(e,r){this._bufs.length=0,this.length=0,r(e)};na.prototype._isBufferList=function(e){return e instanceof na||e instanceof db||na.isBufferList(e)};na.isBufferList=db.isBufferList;xN.exports=na;xN.exports.BufferListStream=na;xN.exports.BufferList=db});var WV=L(Mw=>{var aNt=Buffer.alloc,lNt=\"0000000000000000000\",cNt=\"7777777777777777777\",AQe=48,pQe=Buffer.from(\"ustar\\0\",\"binary\"),uNt=Buffer.from(\"00\",\"binary\"),fNt=Buffer.from(\"ustar \",\"binary\"),ANt=Buffer.from(\" \\0\",\"binary\"),pNt=parseInt(\"7777\",8),mb=257,GV=263,hNt=function(t,e,r){return typeof t!=\"number\"?r:(t=~~t,t>=e?e:t>=0||(t+=e,t>=0)?t:0)},gNt=function(t){switch(t){case 0:return\"file\";case 1:return\"link\";case 2:return\"symlink\";case 3:return\"character-device\";case 4:return\"block-device\";case 5:return\"directory\";case 6:return\"fifo\";case 7:return\"contiguous-file\";case 72:return\"pax-header\";case 55:return\"pax-global-header\";case 27:return\"gnu-long-link-path\";case 28:case 30:return\"gnu-long-path\"}return null},dNt=function(t){switch(t){case\"file\":return 0;case\"link\":return 1;case\"symlink\":return 2;case\"character-device\":return 3;case\"block-device\":return 4;case\"directory\":return 5;case\"fifo\":return 6;case\"contiguous-file\":return 7;case\"pax-header\":return 72}return 0},hQe=function(t,e,r,s){for(;r<s;r++)if(t[r]===e)return r;return s},gQe=function(t){for(var e=256,r=0;r<148;r++)e+=t[r];for(var s=156;s<512;s++)e+=t[s];return e},pg=function(t,e){return t=t.toString(8),t.length>e?cNt.slice(0,e)+\" \":lNt.slice(0,e-t.length)+t+\" \"};function mNt(t){var e;if(t[0]===128)e=!0;else if(t[0]===255)e=!1;else return null;for(var r=[],s=t.length-1;s>0;s--){var a=t[s];e?r.push(a):r.push(255-a)}var n=0,c=r.length;for(s=0;s<c;s++)n+=r[s]*Math.pow(256,s);return e?n:-1*n}var hg=function(t,e,r){if(t=t.slice(e,e+r),e=0,t[e]&128)return mNt(t);for(;e<t.length&&t[e]===32;)e++;for(var s=hNt(hQe(t,32,e,t.length),t.length,t.length);e<s&&t[e]===0;)e++;return s===e?0:parseInt(t.slice(e,s).toString(),8)},Lw=function(t,e,r,s){return t.slice(e,hQe(t,0,e,e+r)).toString(s)},qV=function(t){var e=Buffer.byteLength(t),r=Math.floor(Math.log(e)/Math.log(10))+1;return e+r>=Math.pow(10,r)&&r++,e+r+t};Mw.decodeLongPath=function(t,e){return Lw(t,0,t.length,e)};Mw.encodePax=function(t){var e=\"\";t.name&&(e+=qV(\" path=\"+t.name+`\n`)),t.linkname&&(e+=qV(\" linkpath=\"+t.linkname+`\n`));var r=t.pax;if(r)for(var s in r)e+=qV(\" \"+s+\"=\"+r[s]+`\n`);return Buffer.from(e)};Mw.decodePax=function(t){for(var e={};t.length;){for(var r=0;r<t.length&&t[r]!==32;)r++;var s=parseInt(t.slice(0,r).toString(),10);if(!s)return e;var a=t.slice(r+1,s-1).toString(),n=a.indexOf(\"=\");if(n===-1)return e;e[a.slice(0,n)]=a.slice(n+1),t=t.slice(s)}return e};Mw.encode=function(t){var e=aNt(512),r=t.name,s=\"\";if(t.typeflag===5&&r[r.length-1]!==\"/\"&&(r+=\"/\"),Buffer.byteLength(r)!==r.length)return null;for(;Buffer.byteLength(r)>100;){var a=r.indexOf(\"/\");if(a===-1)return null;s+=s?\"/\"+r.slice(0,a):r.slice(0,a),r=r.slice(a+1)}return Buffer.byteLength(r)>100||Buffer.byteLength(s)>155||t.linkname&&Buffer.byteLength(t.linkname)>100?null:(e.write(r),e.write(pg(t.mode&pNt,6),100),e.write(pg(t.uid,6),108),e.write(pg(t.gid,6),116),e.write(pg(t.size,11),124),e.write(pg(t.mtime.getTime()/1e3|0,11),136),e[156]=AQe+dNt(t.type),t.linkname&&e.write(t.linkname,157),pQe.copy(e,mb),uNt.copy(e,GV),t.uname&&e.write(t.uname,265),t.gname&&e.write(t.gname,297),e.write(pg(t.devmajor||0,6),329),e.write(pg(t.devminor||0,6),337),s&&e.write(s,345),e.write(pg(gQe(e),6),148),e)};Mw.decode=function(t,e,r){var s=t[156]===0?0:t[156]-AQe,a=Lw(t,0,100,e),n=hg(t,100,8),c=hg(t,108,8),f=hg(t,116,8),p=hg(t,124,12),h=hg(t,136,12),E=gNt(s),C=t[157]===0?null:Lw(t,157,100,e),S=Lw(t,265,32),P=Lw(t,297,32),I=hg(t,329,8),R=hg(t,337,8),N=gQe(t);if(N===8*32)return null;if(N!==hg(t,148,8))throw new Error(\"Invalid tar header. Maybe the tar is corrupted or it needs to be gunzipped?\");if(pQe.compare(t,mb,mb+6)===0)t[345]&&(a=Lw(t,345,155,e)+\"/\"+a);else if(!(fNt.compare(t,mb,mb+6)===0&&ANt.compare(t,GV,GV+2)===0)){if(!r)throw new Error(\"Invalid tar header: unknown format.\")}return s===0&&a&&a[a.length-1]===\"/\"&&(s=5),{name:a,mode:n,uid:c,gid:f,size:p,mtime:new Date(1e3*h),type:E,linkname:C,uname:S,gname:P,devmajor:I,devminor:R}}});var wQe=L((PEr,CQe)=>{var mQe=Ie(\"util\"),yNt=fQe(),yb=WV(),yQe=Ow().Writable,EQe=Ow().PassThrough,IQe=function(){},dQe=function(t){return t&=511,t&&512-t},ENt=function(t,e){var r=new kN(t,e);return r.end(),r},INt=function(t,e){return e.path&&(t.name=e.path),e.linkpath&&(t.linkname=e.linkpath),e.size&&(t.size=parseInt(e.size,10)),t.pax=e,t},kN=function(t,e){this._parent=t,this.offset=e,EQe.call(this,{autoDestroy:!1})};mQe.inherits(kN,EQe);kN.prototype.destroy=function(t){this._parent.destroy(t)};var fh=function(t){if(!(this instanceof fh))return new fh(t);yQe.call(this,t),t=t||{},this._offset=0,this._buffer=yNt(),this._missing=0,this._partial=!1,this._onparse=IQe,this._header=null,this._stream=null,this._overflow=null,this._cb=null,this._locked=!1,this._destroyed=!1,this._pax=null,this._paxGlobal=null,this._gnuLongPath=null,this._gnuLongLinkPath=null;var e=this,r=e._buffer,s=function(){e._continue()},a=function(S){if(e._locked=!1,S)return e.destroy(S);e._stream||s()},n=function(){e._stream=null;var S=dQe(e._header.size);S?e._parse(S,c):e._parse(512,C),e._locked||s()},c=function(){e._buffer.consume(dQe(e._header.size)),e._parse(512,C),s()},f=function(){var S=e._header.size;e._paxGlobal=yb.decodePax(r.slice(0,S)),r.consume(S),n()},p=function(){var S=e._header.size;e._pax=yb.decodePax(r.slice(0,S)),e._paxGlobal&&(e._pax=Object.assign({},e._paxGlobal,e._pax)),r.consume(S),n()},h=function(){var S=e._header.size;this._gnuLongPath=yb.decodeLongPath(r.slice(0,S),t.filenameEncoding),r.consume(S),n()},E=function(){var S=e._header.size;this._gnuLongLinkPath=yb.decodeLongPath(r.slice(0,S),t.filenameEncoding),r.consume(S),n()},C=function(){var S=e._offset,P;try{P=e._header=yb.decode(r.slice(0,512),t.filenameEncoding,t.allowUnknownFormat)}catch(I){e.emit(\"error\",I)}if(r.consume(512),!P){e._parse(512,C),s();return}if(P.type===\"gnu-long-path\"){e._parse(P.size,h),s();return}if(P.type===\"gnu-long-link-path\"){e._parse(P.size,E),s();return}if(P.type===\"pax-global-header\"){e._parse(P.size,f),s();return}if(P.type===\"pax-header\"){e._parse(P.size,p),s();return}if(e._gnuLongPath&&(P.name=e._gnuLongPath,e._gnuLongPath=null),e._gnuLongLinkPath&&(P.linkname=e._gnuLongLinkPath,e._gnuLongLinkPath=null),e._pax&&(e._header=P=INt(P,e._pax),e._pax=null),e._locked=!0,!P.size||P.type===\"directory\"){e._parse(512,C),e.emit(\"entry\",P,ENt(e,S),a);return}e._stream=new kN(e,S),e.emit(\"entry\",P,e._stream,a),e._parse(P.size,n),s()};this._onheader=C,this._parse(512,C)};mQe.inherits(fh,yQe);fh.prototype.destroy=function(t){this._destroyed||(this._destroyed=!0,t&&this.emit(\"error\",t),this.emit(\"close\"),this._stream&&this._stream.emit(\"close\"))};fh.prototype._parse=function(t,e){this._destroyed||(this._offset+=t,this._missing=t,e===this._onheader&&(this._partial=!1),this._onparse=e)};fh.prototype._continue=function(){if(!this._destroyed){var t=this._cb;this._cb=IQe,this._overflow?this._write(this._overflow,void 0,t):t()}};fh.prototype._write=function(t,e,r){if(!this._destroyed){var s=this._stream,a=this._buffer,n=this._missing;if(t.length&&(this._partial=!0),t.length<n)return this._missing-=t.length,this._overflow=null,s?s.write(t,r):(a.append(t),r());this._cb=r,this._missing=0;var c=null;t.length>n&&(c=t.slice(n),t=t.slice(0,n)),s?s.end(t):a.append(t),this._overflow=c,this._onparse()}};fh.prototype._final=function(t){if(this._partial)return this.destroy(new Error(\"Unexpected end of data\"));t()};CQe.exports=fh});var vQe=L((xEr,BQe)=>{BQe.exports=Ie(\"fs\").constants||Ie(\"constants\")});var xQe=L((kEr,PQe)=>{var _w=vQe(),SQe=vH(),TN=lg(),CNt=Buffer.alloc,DQe=Ow().Readable,Uw=Ow().Writable,wNt=Ie(\"string_decoder\").StringDecoder,QN=WV(),BNt=parseInt(\"755\",8),vNt=parseInt(\"644\",8),bQe=CNt(1024),VV=function(){},YV=function(t,e){e&=511,e&&t.push(bQe.slice(0,512-e))};function SNt(t){switch(t&_w.S_IFMT){case _w.S_IFBLK:return\"block-device\";case _w.S_IFCHR:return\"character-device\";case _w.S_IFDIR:return\"directory\";case _w.S_IFIFO:return\"fifo\";case _w.S_IFLNK:return\"symlink\"}return\"file\"}var RN=function(t){Uw.call(this),this.written=0,this._to=t,this._destroyed=!1};TN(RN,Uw);RN.prototype._write=function(t,e,r){if(this.written+=t.length,this._to.push(t))return r();this._to._drain=r};RN.prototype.destroy=function(){this._destroyed||(this._destroyed=!0,this.emit(\"close\"))};var FN=function(){Uw.call(this),this.linkname=\"\",this._decoder=new wNt(\"utf-8\"),this._destroyed=!1};TN(FN,Uw);FN.prototype._write=function(t,e,r){this.linkname+=this._decoder.write(t),r()};FN.prototype.destroy=function(){this._destroyed||(this._destroyed=!0,this.emit(\"close\"))};var Eb=function(){Uw.call(this),this._destroyed=!1};TN(Eb,Uw);Eb.prototype._write=function(t,e,r){r(new Error(\"No body allowed for this entry\"))};Eb.prototype.destroy=function(){this._destroyed||(this._destroyed=!0,this.emit(\"close\"))};var EA=function(t){if(!(this instanceof EA))return new EA(t);DQe.call(this,t),this._drain=VV,this._finalized=!1,this._finalizing=!1,this._destroyed=!1,this._stream=null};TN(EA,DQe);EA.prototype.entry=function(t,e,r){if(this._stream)throw new Error(\"already piping an entry\");if(!(this._finalized||this._destroyed)){typeof e==\"function\"&&(r=e,e=null),r||(r=VV);var s=this;if((!t.size||t.type===\"symlink\")&&(t.size=0),t.type||(t.type=SNt(t.mode)),t.mode||(t.mode=t.type===\"directory\"?BNt:vNt),t.uid||(t.uid=0),t.gid||(t.gid=0),t.mtime||(t.mtime=new Date),typeof e==\"string\"&&(e=Buffer.from(e)),Buffer.isBuffer(e)){t.size=e.length,this._encode(t);var a=this.push(e);return YV(s,t.size),a?process.nextTick(r):this._drain=r,new Eb}if(t.type===\"symlink\"&&!t.linkname){var n=new FN;return SQe(n,function(f){if(f)return s.destroy(),r(f);t.linkname=n.linkname,s._encode(t),r()}),n}if(this._encode(t),t.type!==\"file\"&&t.type!==\"contiguous-file\")return process.nextTick(r),new Eb;var c=new RN(this);return this._stream=c,SQe(c,function(f){if(s._stream=null,f)return s.destroy(),r(f);if(c.written!==t.size)return s.destroy(),r(new Error(\"size mismatch\"));YV(s,t.size),s._finalizing&&s.finalize(),r()}),c}};EA.prototype.finalize=function(){if(this._stream){this._finalizing=!0;return}this._finalized||(this._finalized=!0,this.push(bQe),this.push(null))};EA.prototype.destroy=function(t){this._destroyed||(this._destroyed=!0,t&&this.emit(\"error\",t),this.emit(\"close\"),this._stream&&this._stream.destroy&&this._stream.destroy())};EA.prototype._encode=function(t){if(!t.pax){var e=QN.encode(t);if(e){this.push(e);return}}this._encodePax(t)};EA.prototype._encodePax=function(t){var e=QN.encodePax({name:t.name,linkname:t.linkname,pax:t.pax}),r={name:\"PaxHeader\",mode:t.mode,uid:t.uid,gid:t.gid,size:e.length,mtime:t.mtime,type:\"pax-header\",linkname:t.linkname&&\"PaxHeader\",uname:t.uname,gname:t.gname,devmajor:t.devmajor,devminor:t.devminor};this.push(QN.encode(r)),this.push(e),YV(this,e.length),r.size=t.size,r.type=t.type,this.push(QN.encode(r))};EA.prototype._read=function(t){var e=this._drain;this._drain=VV,e()};PQe.exports=EA});var kQe=L(KV=>{KV.extract=wQe();KV.pack=xQe()});var qQe=L(Ra=>{\"use strict\";var MNt=Ra&&Ra.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(Ra,\"__esModule\",{value:!0});Ra.Minipass=Ra.isWritable=Ra.isReadable=Ra.isStream=void 0;var MQe=typeof process==\"object\"&&process?process:{stdout:null,stderr:null},o7=Ie(\"node:events\"),jQe=MNt(Ie(\"node:stream\")),_Nt=Ie(\"node:string_decoder\"),UNt=t=>!!t&&typeof t==\"object\"&&(t instanceof qN||t instanceof jQe.default||(0,Ra.isReadable)(t)||(0,Ra.isWritable)(t));Ra.isStream=UNt;var HNt=t=>!!t&&typeof t==\"object\"&&t instanceof o7.EventEmitter&&typeof t.pipe==\"function\"&&t.pipe!==jQe.default.Writable.prototype.pipe;Ra.isReadable=HNt;var jNt=t=>!!t&&typeof t==\"object\"&&t instanceof o7.EventEmitter&&typeof t.write==\"function\"&&typeof t.end==\"function\";Ra.isWritable=jNt;var Ah=Symbol(\"EOF\"),ph=Symbol(\"maybeEmitEnd\"),gg=Symbol(\"emittedEnd\"),LN=Symbol(\"emittingEnd\"),Ib=Symbol(\"emittedError\"),MN=Symbol(\"closed\"),_Qe=Symbol(\"read\"),_N=Symbol(\"flush\"),UQe=Symbol(\"flushChunk\"),ff=Symbol(\"encoding\"),jw=Symbol(\"decoder\"),Zs=Symbol(\"flowing\"),Cb=Symbol(\"paused\"),qw=Symbol(\"resume\"),Xs=Symbol(\"buffer\"),Ta=Symbol(\"pipes\"),$s=Symbol(\"bufferLength\"),e7=Symbol(\"bufferPush\"),UN=Symbol(\"bufferShift\"),ia=Symbol(\"objectMode\"),rs=Symbol(\"destroyed\"),t7=Symbol(\"error\"),r7=Symbol(\"emitData\"),HQe=Symbol(\"emitEnd\"),n7=Symbol(\"emitEnd2\"),CA=Symbol(\"async\"),i7=Symbol(\"abort\"),HN=Symbol(\"aborted\"),wb=Symbol(\"signal\"),Jm=Symbol(\"dataListeners\"),nc=Symbol(\"discarded\"),Bb=t=>Promise.resolve().then(t),qNt=t=>t(),GNt=t=>t===\"end\"||t===\"finish\"||t===\"prefinish\",WNt=t=>t instanceof ArrayBuffer||!!t&&typeof t==\"object\"&&t.constructor&&t.constructor.name===\"ArrayBuffer\"&&t.byteLength>=0,YNt=t=>!Buffer.isBuffer(t)&&ArrayBuffer.isView(t),jN=class{src;dest;opts;ondrain;constructor(e,r,s){this.src=e,this.dest=r,this.opts=s,this.ondrain=()=>e[qw](),this.dest.on(\"drain\",this.ondrain)}unpipe(){this.dest.removeListener(\"drain\",this.ondrain)}proxyErrors(e){}end(){this.unpipe(),this.opts.end&&this.dest.end()}},s7=class extends jN{unpipe(){this.src.removeListener(\"error\",this.proxyErrors),super.unpipe()}constructor(e,r,s){super(e,r,s),this.proxyErrors=a=>r.emit(\"error\",a),e.on(\"error\",this.proxyErrors)}},VNt=t=>!!t.objectMode,KNt=t=>!t.objectMode&&!!t.encoding&&t.encoding!==\"buffer\",qN=class extends o7.EventEmitter{[Zs]=!1;[Cb]=!1;[Ta]=[];[Xs]=[];[ia];[ff];[CA];[jw];[Ah]=!1;[gg]=!1;[LN]=!1;[MN]=!1;[Ib]=null;[$s]=0;[rs]=!1;[wb];[HN]=!1;[Jm]=0;[nc]=!1;writable=!0;readable=!0;constructor(...e){let r=e[0]||{};if(super(),r.objectMode&&typeof r.encoding==\"string\")throw new TypeError(\"Encoding and objectMode may not be used together\");VNt(r)?(this[ia]=!0,this[ff]=null):KNt(r)?(this[ff]=r.encoding,this[ia]=!1):(this[ia]=!1,this[ff]=null),this[CA]=!!r.async,this[jw]=this[ff]?new _Nt.StringDecoder(this[ff]):null,r&&r.debugExposeBuffer===!0&&Object.defineProperty(this,\"buffer\",{get:()=>this[Xs]}),r&&r.debugExposePipes===!0&&Object.defineProperty(this,\"pipes\",{get:()=>this[Ta]});let{signal:s}=r;s&&(this[wb]=s,s.aborted?this[i7]():s.addEventListener(\"abort\",()=>this[i7]()))}get bufferLength(){return this[$s]}get encoding(){return this[ff]}set encoding(e){throw new Error(\"Encoding must be set at instantiation time\")}setEncoding(e){throw new Error(\"Encoding must be set at instantiation time\")}get objectMode(){return this[ia]}set objectMode(e){throw new Error(\"objectMode must be set at instantiation time\")}get async(){return this[CA]}set async(e){this[CA]=this[CA]||!!e}[i7](){this[HN]=!0,this.emit(\"abort\",this[wb]?.reason),this.destroy(this[wb]?.reason)}get aborted(){return this[HN]}set aborted(e){}write(e,r,s){if(this[HN])return!1;if(this[Ah])throw new Error(\"write after end\");if(this[rs])return this.emit(\"error\",Object.assign(new Error(\"Cannot call write after a stream was destroyed\"),{code:\"ERR_STREAM_DESTROYED\"})),!0;typeof r==\"function\"&&(s=r,r=\"utf8\"),r||(r=\"utf8\");let a=this[CA]?Bb:qNt;if(!this[ia]&&!Buffer.isBuffer(e)){if(YNt(e))e=Buffer.from(e.buffer,e.byteOffset,e.byteLength);else if(WNt(e))e=Buffer.from(e);else if(typeof e!=\"string\")throw new Error(\"Non-contiguous data written to non-objectMode stream\")}return this[ia]?(this[Zs]&&this[$s]!==0&&this[_N](!0),this[Zs]?this.emit(\"data\",e):this[e7](e),this[$s]!==0&&this.emit(\"readable\"),s&&a(s),this[Zs]):e.length?(typeof e==\"string\"&&!(r===this[ff]&&!this[jw]?.lastNeed)&&(e=Buffer.from(e,r)),Buffer.isBuffer(e)&&this[ff]&&(e=this[jw].write(e)),this[Zs]&&this[$s]!==0&&this[_N](!0),this[Zs]?this.emit(\"data\",e):this[e7](e),this[$s]!==0&&this.emit(\"readable\"),s&&a(s),this[Zs]):(this[$s]!==0&&this.emit(\"readable\"),s&&a(s),this[Zs])}read(e){if(this[rs])return null;if(this[nc]=!1,this[$s]===0||e===0||e&&e>this[$s])return this[ph](),null;this[ia]&&(e=null),this[Xs].length>1&&!this[ia]&&(this[Xs]=[this[ff]?this[Xs].join(\"\"):Buffer.concat(this[Xs],this[$s])]);let r=this[_Qe](e||null,this[Xs][0]);return this[ph](),r}[_Qe](e,r){if(this[ia])this[UN]();else{let s=r;e===s.length||e===null?this[UN]():typeof s==\"string\"?(this[Xs][0]=s.slice(e),r=s.slice(0,e),this[$s]-=e):(this[Xs][0]=s.subarray(e),r=s.subarray(0,e),this[$s]-=e)}return this.emit(\"data\",r),!this[Xs].length&&!this[Ah]&&this.emit(\"drain\"),r}end(e,r,s){return typeof e==\"function\"&&(s=e,e=void 0),typeof r==\"function\"&&(s=r,r=\"utf8\"),e!==void 0&&this.write(e,r),s&&this.once(\"end\",s),this[Ah]=!0,this.writable=!1,(this[Zs]||!this[Cb])&&this[ph](),this}[qw](){this[rs]||(!this[Jm]&&!this[Ta].length&&(this[nc]=!0),this[Cb]=!1,this[Zs]=!0,this.emit(\"resume\"),this[Xs].length?this[_N]():this[Ah]?this[ph]():this.emit(\"drain\"))}resume(){return this[qw]()}pause(){this[Zs]=!1,this[Cb]=!0,this[nc]=!1}get destroyed(){return this[rs]}get flowing(){return this[Zs]}get paused(){return this[Cb]}[e7](e){this[ia]?this[$s]+=1:this[$s]+=e.length,this[Xs].push(e)}[UN](){return this[ia]?this[$s]-=1:this[$s]-=this[Xs][0].length,this[Xs].shift()}[_N](e=!1){do;while(this[UQe](this[UN]())&&this[Xs].length);!e&&!this[Xs].length&&!this[Ah]&&this.emit(\"drain\")}[UQe](e){return this.emit(\"data\",e),this[Zs]}pipe(e,r){if(this[rs])return e;this[nc]=!1;let s=this[gg];return r=r||{},e===MQe.stdout||e===MQe.stderr?r.end=!1:r.end=r.end!==!1,r.proxyErrors=!!r.proxyErrors,s?r.end&&e.end():(this[Ta].push(r.proxyErrors?new s7(this,e,r):new jN(this,e,r)),this[CA]?Bb(()=>this[qw]()):this[qw]()),e}unpipe(e){let r=this[Ta].find(s=>s.dest===e);r&&(this[Ta].length===1?(this[Zs]&&this[Jm]===0&&(this[Zs]=!1),this[Ta]=[]):this[Ta].splice(this[Ta].indexOf(r),1),r.unpipe())}addListener(e,r){return this.on(e,r)}on(e,r){let s=super.on(e,r);if(e===\"data\")this[nc]=!1,this[Jm]++,!this[Ta].length&&!this[Zs]&&this[qw]();else if(e===\"readable\"&&this[$s]!==0)super.emit(\"readable\");else if(GNt(e)&&this[gg])super.emit(e),this.removeAllListeners(e);else if(e===\"error\"&&this[Ib]){let a=r;this[CA]?Bb(()=>a.call(this,this[Ib])):a.call(this,this[Ib])}return s}removeListener(e,r){return this.off(e,r)}off(e,r){let s=super.off(e,r);return e===\"data\"&&(this[Jm]=this.listeners(\"data\").length,this[Jm]===0&&!this[nc]&&!this[Ta].length&&(this[Zs]=!1)),s}removeAllListeners(e){let r=super.removeAllListeners(e);return(e===\"data\"||e===void 0)&&(this[Jm]=0,!this[nc]&&!this[Ta].length&&(this[Zs]=!1)),r}get emittedEnd(){return this[gg]}[ph](){!this[LN]&&!this[gg]&&!this[rs]&&this[Xs].length===0&&this[Ah]&&(this[LN]=!0,this.emit(\"end\"),this.emit(\"prefinish\"),this.emit(\"finish\"),this[MN]&&this.emit(\"close\"),this[LN]=!1)}emit(e,...r){let s=r[0];if(e!==\"error\"&&e!==\"close\"&&e!==rs&&this[rs])return!1;if(e===\"data\")return!this[ia]&&!s?!1:this[CA]?(Bb(()=>this[r7](s)),!0):this[r7](s);if(e===\"end\")return this[HQe]();if(e===\"close\"){if(this[MN]=!0,!this[gg]&&!this[rs])return!1;let n=super.emit(\"close\");return this.removeAllListeners(\"close\"),n}else if(e===\"error\"){this[Ib]=s,super.emit(t7,s);let n=!this[wb]||this.listeners(\"error\").length?super.emit(\"error\",s):!1;return this[ph](),n}else if(e===\"resume\"){let n=super.emit(\"resume\");return this[ph](),n}else if(e===\"finish\"||e===\"prefinish\"){let n=super.emit(e);return this.removeAllListeners(e),n}let a=super.emit(e,...r);return this[ph](),a}[r7](e){for(let s of this[Ta])s.dest.write(e)===!1&&this.pause();let r=this[nc]?!1:super.emit(\"data\",e);return this[ph](),r}[HQe](){return this[gg]?!1:(this[gg]=!0,this.readable=!1,this[CA]?(Bb(()=>this[n7]()),!0):this[n7]())}[n7](){if(this[jw]){let r=this[jw].end();if(r){for(let s of this[Ta])s.dest.write(r);this[nc]||super.emit(\"data\",r)}}for(let r of this[Ta])r.end();let e=super.emit(\"end\");return this.removeAllListeners(\"end\"),e}async collect(){let e=Object.assign([],{dataLength:0});this[ia]||(e.dataLength=0);let r=this.promise();return this.on(\"data\",s=>{e.push(s),this[ia]||(e.dataLength+=s.length)}),await r,e}async concat(){if(this[ia])throw new Error(\"cannot concat in objectMode\");let e=await this.collect();return this[ff]?e.join(\"\"):Buffer.concat(e,e.dataLength)}async promise(){return new Promise((e,r)=>{this.on(rs,()=>r(new Error(\"stream destroyed\"))),this.on(\"error\",s=>r(s)),this.on(\"end\",()=>e())})}[Symbol.asyncIterator](){this[nc]=!1;let e=!1,r=async()=>(this.pause(),e=!0,{value:void 0,done:!0});return{next:()=>{if(e)return r();let a=this.read();if(a!==null)return Promise.resolve({done:!1,value:a});if(this[Ah])return r();let n,c,f=C=>{this.off(\"data\",p),this.off(\"end\",h),this.off(rs,E),r(),c(C)},p=C=>{this.off(\"error\",f),this.off(\"end\",h),this.off(rs,E),this.pause(),n({value:C,done:!!this[Ah]})},h=()=>{this.off(\"error\",f),this.off(\"data\",p),this.off(rs,E),r(),n({done:!0,value:void 0})},E=()=>f(new Error(\"stream destroyed\"));return new Promise((C,S)=>{c=S,n=C,this.once(rs,E),this.once(\"error\",f),this.once(\"end\",h),this.once(\"data\",p)})},throw:r,return:r,[Symbol.asyncIterator](){return this}}}[Symbol.iterator](){this[nc]=!1;let e=!1,r=()=>(this.pause(),this.off(t7,r),this.off(rs,r),this.off(\"end\",r),e=!0,{done:!0,value:void 0}),s=()=>{if(e)return r();let a=this.read();return a===null?r():{done:!1,value:a}};return this.once(\"end\",r),this.once(t7,r),this.once(rs,r),{next:s,throw:r,return:r,[Symbol.iterator](){return this}}}destroy(e){if(this[rs])return e?this.emit(\"error\",e):this.emit(rs),this;this[rs]=!0,this[nc]=!0,this[Xs].length=0,this[$s]=0;let r=this;return typeof r.close==\"function\"&&!this[MN]&&r.close(),e?this.emit(\"error\",e):this.emit(rs),this}static get isStream(){return Ra.isStream}};Ra.Minipass=qN});var YQe=L((ZEr,wA)=>{\"use strict\";var Sb=Ie(\"crypto\"),{Minipass:JNt}=qQe(),l7=[\"sha512\",\"sha384\",\"sha256\"],u7=[\"sha512\"],zNt=/^[a-z0-9+/]+(?:=?=?)$/i,ZNt=/^([a-z0-9]+)-([^?]+)([?\\S*]*)$/,XNt=/^([a-z0-9]+)-([A-Za-z0-9+/=]{44,88})(\\?[\\x21-\\x7E]*)?$/,$Nt=/^[\\x21-\\x7E]+$/,Db=t=>t?.length?`?${t.join(\"?\")}`:\"\",c7=class extends JNt{#t;#r;#i;constructor(e){super(),this.size=0,this.opts=e,this.#e(),e?.algorithms?this.algorithms=[...e.algorithms]:this.algorithms=[...u7],this.algorithm!==null&&!this.algorithms.includes(this.algorithm)&&this.algorithms.push(this.algorithm),this.hashes=this.algorithms.map(Sb.createHash)}#e(){this.sri=this.opts?.integrity?ic(this.opts?.integrity,this.opts):null,this.expectedSize=this.opts?.size,this.sri?this.sri.isHash?(this.goodSri=!0,this.algorithm=this.sri.algorithm):(this.goodSri=!this.sri.isEmpty(),this.algorithm=this.sri.pickAlgorithm(this.opts)):this.algorithm=null,this.digests=this.goodSri?this.sri[this.algorithm]:null,this.optString=Db(this.opts?.options)}on(e,r){return e===\"size\"&&this.#r?r(this.#r):e===\"integrity\"&&this.#t?r(this.#t):e===\"verified\"&&this.#i?r(this.#i):super.on(e,r)}emit(e,r){return e===\"end\"&&this.#n(),super.emit(e,r)}write(e){return this.size+=e.length,this.hashes.forEach(r=>r.update(e)),super.write(e)}#n(){this.goodSri||this.#e();let e=ic(this.hashes.map((s,a)=>`${this.algorithms[a]}-${s.digest(\"base64\")}${this.optString}`).join(\" \"),this.opts),r=this.goodSri&&e.match(this.sri,this.opts);if(typeof this.expectedSize==\"number\"&&this.size!==this.expectedSize){let s=new Error(`stream size mismatch when checking ${this.sri}.\n  Wanted: ${this.expectedSize}\n  Found: ${this.size}`);s.code=\"EBADSIZE\",s.found=this.size,s.expected=this.expectedSize,s.sri=this.sri,this.emit(\"error\",s)}else if(this.sri&&!r){let s=new Error(`${this.sri} integrity checksum failed when using ${this.algorithm}: wanted ${this.digests} but got ${e}. (${this.size} bytes)`);s.code=\"EINTEGRITY\",s.found=e,s.expected=this.digests,s.algorithm=this.algorithm,s.sri=this.sri,this.emit(\"error\",s)}else this.#r=this.size,this.emit(\"size\",this.size),this.#t=e,this.emit(\"integrity\",e),r&&(this.#i=r,this.emit(\"verified\",r))}},hh=class{get isHash(){return!0}constructor(e,r){let s=r?.strict;this.source=e.trim(),this.digest=\"\",this.algorithm=\"\",this.options=[];let a=this.source.match(s?XNt:ZNt);if(!a||s&&!l7.includes(a[1]))return;this.algorithm=a[1],this.digest=a[2];let n=a[3];n&&(this.options=n.slice(1).split(\"?\"))}hexDigest(){return this.digest&&Buffer.from(this.digest,\"base64\").toString(\"hex\")}toJSON(){return this.toString()}match(e,r){let s=ic(e,r);if(!s)return!1;if(s.isIntegrity){let a=s.pickAlgorithm(r,[this.algorithm]);if(!a)return!1;let n=s[a].find(c=>c.digest===this.digest);return n||!1}return s.digest===this.digest?s:!1}toString(e){return e?.strict&&!(l7.includes(this.algorithm)&&this.digest.match(zNt)&&this.options.every(r=>r.match($Nt)))?\"\":`${this.algorithm}-${this.digest}${Db(this.options)}`}};function GQe(t,e,r,s){let a=t!==\"\",n=!1,c=\"\",f=s.length-1;for(let h=0;h<f;h++){let E=hh.prototype.toString.call(s[h],r);E&&(n=!0,c+=E,c+=e)}let p=hh.prototype.toString.call(s[f],r);return p&&(n=!0,c+=p),a&&n?t+e+c:t+c}var zm=class{get isIntegrity(){return!0}toJSON(){return this.toString()}isEmpty(){return Object.keys(this).length===0}toString(e){let r=e?.sep||\" \",s=\"\";if(e?.strict){r=r.replace(/\\S+/g,\" \");for(let a of l7)this[a]&&(s=GQe(s,r,e,this[a]))}else for(let a of Object.keys(this))s=GQe(s,r,e,this[a]);return s}concat(e,r){let s=typeof e==\"string\"?e:vb(e,r);return ic(`${this.toString(r)} ${s}`,r)}hexDigest(){return ic(this,{single:!0}).hexDigest()}merge(e,r){let s=ic(e,r);for(let a in s)if(this[a]){if(!this[a].find(n=>s[a].find(c=>n.digest===c.digest)))throw new Error(\"hashes do not match, cannot update integrity\")}else this[a]=s[a]}match(e,r){let s=ic(e,r);if(!s)return!1;let a=s.pickAlgorithm(r,Object.keys(this));return!!a&&this[a]&&s[a]&&this[a].find(n=>s[a].find(c=>n.digest===c.digest))||!1}pickAlgorithm(e,r){let s=e?.pickAlgorithm||aOt,a=Object.keys(this).filter(n=>r?.length?r.includes(n):!0);return a.length?a.reduce((n,c)=>s(n,c)||n):null}};wA.exports.parse=ic;function ic(t,e){if(!t)return null;if(typeof t==\"string\")return a7(t,e);if(t.algorithm&&t.digest){let r=new zm;return r[t.algorithm]=[t],a7(vb(r,e),e)}else return a7(vb(t,e),e)}function a7(t,e){if(e?.single)return new hh(t,e);let r=t.trim().split(/\\s+/).reduce((s,a)=>{let n=new hh(a,e);if(n.algorithm&&n.digest){let c=n.algorithm;s[c]||(s[c]=[]),s[c].push(n)}return s},new zm);return r.isEmpty()?null:r}wA.exports.stringify=vb;function vb(t,e){return t.algorithm&&t.digest?hh.prototype.toString.call(t,e):typeof t==\"string\"?vb(ic(t,e),e):zm.prototype.toString.call(t,e)}wA.exports.fromHex=eOt;function eOt(t,e,r){let s=Db(r?.options);return ic(`${e}-${Buffer.from(t,\"hex\").toString(\"base64\")}${s}`,r)}wA.exports.fromData=tOt;function tOt(t,e){let r=e?.algorithms||[...u7],s=Db(e?.options);return r.reduce((a,n)=>{let c=Sb.createHash(n).update(t).digest(\"base64\"),f=new hh(`${n}-${c}${s}`,e);if(f.algorithm&&f.digest){let p=f.algorithm;a[p]||(a[p]=[]),a[p].push(f)}return a},new zm)}wA.exports.fromStream=rOt;function rOt(t,e){let r=f7(e);return new Promise((s,a)=>{t.pipe(r),t.on(\"error\",a),r.on(\"error\",a);let n;r.on(\"integrity\",c=>{n=c}),r.on(\"end\",()=>s(n)),r.resume()})}wA.exports.checkData=nOt;function nOt(t,e,r){if(e=ic(e,r),!e||!Object.keys(e).length){if(r?.error)throw Object.assign(new Error(\"No valid integrity hashes to check against\"),{code:\"EINTEGRITY\"});return!1}let s=e.pickAlgorithm(r),a=Sb.createHash(s).update(t).digest(\"base64\"),n=ic({algorithm:s,digest:a}),c=n.match(e,r);if(r=r||{},c||!r.error)return c;if(typeof r.size==\"number\"&&t.length!==r.size){let f=new Error(`data size mismatch when checking ${e}.\n  Wanted: ${r.size}\n  Found: ${t.length}`);throw f.code=\"EBADSIZE\",f.found=t.length,f.expected=r.size,f.sri=e,f}else{let f=new Error(`Integrity checksum failed when using ${s}: Wanted ${e}, but got ${n}. (${t.length} bytes)`);throw f.code=\"EINTEGRITY\",f.found=n,f.expected=e,f.algorithm=s,f.sri=e,f}}wA.exports.checkStream=iOt;function iOt(t,e,r){if(r=r||Object.create(null),r.integrity=e,e=ic(e,r),!e||!Object.keys(e).length)return Promise.reject(Object.assign(new Error(\"No valid integrity hashes to check against\"),{code:\"EINTEGRITY\"}));let s=f7(r);return new Promise((a,n)=>{t.pipe(s),t.on(\"error\",n),s.on(\"error\",n);let c;s.on(\"verified\",f=>{c=f}),s.on(\"end\",()=>a(c)),s.resume()})}wA.exports.integrityStream=f7;function f7(t=Object.create(null)){return new c7(t)}wA.exports.create=sOt;function sOt(t){let e=t?.algorithms||[...u7],r=Db(t?.options),s=e.map(Sb.createHash);return{update:function(a,n){return s.forEach(c=>c.update(a,n)),this},digest:function(){return e.reduce((n,c)=>{let f=s.shift().digest(\"base64\"),p=new hh(`${c}-${f}${r}`,t);if(p.algorithm&&p.digest){let h=p.algorithm;n[h]||(n[h]=[]),n[h].push(p)}return n},new zm)}}}var oOt=Sb.getHashes(),WQe=[\"md5\",\"whirlpool\",\"sha1\",\"sha224\",\"sha256\",\"sha384\",\"sha512\",\"sha3\",\"sha3-256\",\"sha3-384\",\"sha3-512\",\"sha3_256\",\"sha3_384\",\"sha3_512\"].filter(t=>oOt.includes(t));function aOt(t,e){return WQe.indexOf(t.toLowerCase())>=WQe.indexOf(e.toLowerCase())?t:e}});var A7=L(dg=>{\"use strict\";Object.defineProperty(dg,\"__esModule\",{value:!0});dg.Signature=dg.Envelope=void 0;dg.Envelope={fromJSON(t){return{payload:GN(t.payload)?Buffer.from(VQe(t.payload)):Buffer.alloc(0),payloadType:GN(t.payloadType)?globalThis.String(t.payloadType):\"\",signatures:globalThis.Array.isArray(t?.signatures)?t.signatures.map(e=>dg.Signature.fromJSON(e)):[]}},toJSON(t){let e={};return t.payload.length!==0&&(e.payload=KQe(t.payload)),t.payloadType!==\"\"&&(e.payloadType=t.payloadType),t.signatures?.length&&(e.signatures=t.signatures.map(r=>dg.Signature.toJSON(r))),e}};dg.Signature={fromJSON(t){return{sig:GN(t.sig)?Buffer.from(VQe(t.sig)):Buffer.alloc(0),keyid:GN(t.keyid)?globalThis.String(t.keyid):\"\"}},toJSON(t){let e={};return t.sig.length!==0&&(e.sig=KQe(t.sig)),t.keyid!==\"\"&&(e.keyid=t.keyid),e}};function VQe(t){return Uint8Array.from(globalThis.Buffer.from(t,\"base64\"))}function KQe(t){return globalThis.Buffer.from(t).toString(\"base64\")}function GN(t){return t!=null}});var zQe=L(WN=>{\"use strict\";Object.defineProperty(WN,\"__esModule\",{value:!0});WN.Timestamp=void 0;WN.Timestamp={fromJSON(t){return{seconds:JQe(t.seconds)?globalThis.String(t.seconds):\"0\",nanos:JQe(t.nanos)?globalThis.Number(t.nanos):0}},toJSON(t){let e={};return t.seconds!==\"0\"&&(e.seconds=t.seconds),t.nanos!==0&&(e.nanos=Math.round(t.nanos)),e}};function JQe(t){return t!=null}});var Gw=L(_r=>{\"use strict\";Object.defineProperty(_r,\"__esModule\",{value:!0});_r.TimeRange=_r.X509CertificateChain=_r.SubjectAlternativeName=_r.X509Certificate=_r.DistinguishedName=_r.ObjectIdentifierValuePair=_r.ObjectIdentifier=_r.PublicKeyIdentifier=_r.PublicKey=_r.RFC3161SignedTimestamp=_r.LogId=_r.MessageSignature=_r.HashOutput=_r.SubjectAlternativeNameType=_r.PublicKeyDetails=_r.HashAlgorithm=void 0;_r.hashAlgorithmFromJSON=XQe;_r.hashAlgorithmToJSON=$Qe;_r.publicKeyDetailsFromJSON=eTe;_r.publicKeyDetailsToJSON=tTe;_r.subjectAlternativeNameTypeFromJSON=rTe;_r.subjectAlternativeNameTypeToJSON=nTe;var lOt=zQe(),El;(function(t){t[t.HASH_ALGORITHM_UNSPECIFIED=0]=\"HASH_ALGORITHM_UNSPECIFIED\",t[t.SHA2_256=1]=\"SHA2_256\",t[t.SHA2_384=2]=\"SHA2_384\",t[t.SHA2_512=3]=\"SHA2_512\",t[t.SHA3_256=4]=\"SHA3_256\",t[t.SHA3_384=5]=\"SHA3_384\"})(El||(_r.HashAlgorithm=El={}));function XQe(t){switch(t){case 0:case\"HASH_ALGORITHM_UNSPECIFIED\":return El.HASH_ALGORITHM_UNSPECIFIED;case 1:case\"SHA2_256\":return El.SHA2_256;case 2:case\"SHA2_384\":return El.SHA2_384;case 3:case\"SHA2_512\":return El.SHA2_512;case 4:case\"SHA3_256\":return El.SHA3_256;case 5:case\"SHA3_384\":return El.SHA3_384;default:throw new globalThis.Error(\"Unrecognized enum value \"+t+\" for enum HashAlgorithm\")}}function $Qe(t){switch(t){case El.HASH_ALGORITHM_UNSPECIFIED:return\"HASH_ALGORITHM_UNSPECIFIED\";case El.SHA2_256:return\"SHA2_256\";case El.SHA2_384:return\"SHA2_384\";case El.SHA2_512:return\"SHA2_512\";case El.SHA3_256:return\"SHA3_256\";case El.SHA3_384:return\"SHA3_384\";default:throw new globalThis.Error(\"Unrecognized enum value \"+t+\" for enum HashAlgorithm\")}}var rn;(function(t){t[t.PUBLIC_KEY_DETAILS_UNSPECIFIED=0]=\"PUBLIC_KEY_DETAILS_UNSPECIFIED\",t[t.PKCS1_RSA_PKCS1V5=1]=\"PKCS1_RSA_PKCS1V5\",t[t.PKCS1_RSA_PSS=2]=\"PKCS1_RSA_PSS\",t[t.PKIX_RSA_PKCS1V5=3]=\"PKIX_RSA_PKCS1V5\",t[t.PKIX_RSA_PSS=4]=\"PKIX_RSA_PSS\",t[t.PKIX_RSA_PKCS1V15_2048_SHA256=9]=\"PKIX_RSA_PKCS1V15_2048_SHA256\",t[t.PKIX_RSA_PKCS1V15_3072_SHA256=10]=\"PKIX_RSA_PKCS1V15_3072_SHA256\",t[t.PKIX_RSA_PKCS1V15_4096_SHA256=11]=\"PKIX_RSA_PKCS1V15_4096_SHA256\",t[t.PKIX_RSA_PSS_2048_SHA256=16]=\"PKIX_RSA_PSS_2048_SHA256\",t[t.PKIX_RSA_PSS_3072_SHA256=17]=\"PKIX_RSA_PSS_3072_SHA256\",t[t.PKIX_RSA_PSS_4096_SHA256=18]=\"PKIX_RSA_PSS_4096_SHA256\",t[t.PKIX_ECDSA_P256_HMAC_SHA_256=6]=\"PKIX_ECDSA_P256_HMAC_SHA_256\",t[t.PKIX_ECDSA_P256_SHA_256=5]=\"PKIX_ECDSA_P256_SHA_256\",t[t.PKIX_ECDSA_P384_SHA_384=12]=\"PKIX_ECDSA_P384_SHA_384\",t[t.PKIX_ECDSA_P521_SHA_512=13]=\"PKIX_ECDSA_P521_SHA_512\",t[t.PKIX_ED25519=7]=\"PKIX_ED25519\",t[t.PKIX_ED25519_PH=8]=\"PKIX_ED25519_PH\",t[t.LMS_SHA256=14]=\"LMS_SHA256\",t[t.LMOTS_SHA256=15]=\"LMOTS_SHA256\"})(rn||(_r.PublicKeyDetails=rn={}));function eTe(t){switch(t){case 0:case\"PUBLIC_KEY_DETAILS_UNSPECIFIED\":return rn.PUBLIC_KEY_DETAILS_UNSPECIFIED;case 1:case\"PKCS1_RSA_PKCS1V5\":return rn.PKCS1_RSA_PKCS1V5;case 2:case\"PKCS1_RSA_PSS\":return rn.PKCS1_RSA_PSS;case 3:case\"PKIX_RSA_PKCS1V5\":return rn.PKIX_RSA_PKCS1V5;case 4:case\"PKIX_RSA_PSS\":return rn.PKIX_RSA_PSS;case 9:case\"PKIX_RSA_PKCS1V15_2048_SHA256\":return rn.PKIX_RSA_PKCS1V15_2048_SHA256;case 10:case\"PKIX_RSA_PKCS1V15_3072_SHA256\":return rn.PKIX_RSA_PKCS1V15_3072_SHA256;case 11:case\"PKIX_RSA_PKCS1V15_4096_SHA256\":return rn.PKIX_RSA_PKCS1V15_4096_SHA256;case 16:case\"PKIX_RSA_PSS_2048_SHA256\":return rn.PKIX_RSA_PSS_2048_SHA256;case 17:case\"PKIX_RSA_PSS_3072_SHA256\":return rn.PKIX_RSA_PSS_3072_SHA256;case 18:case\"PKIX_RSA_PSS_4096_SHA256\":return rn.PKIX_RSA_PSS_4096_SHA256;case 6:case\"PKIX_ECDSA_P256_HMAC_SHA_256\":return rn.PKIX_ECDSA_P256_HMAC_SHA_256;case 5:case\"PKIX_ECDSA_P256_SHA_256\":return rn.PKIX_ECDSA_P256_SHA_256;case 12:case\"PKIX_ECDSA_P384_SHA_384\":return rn.PKIX_ECDSA_P384_SHA_384;case 13:case\"PKIX_ECDSA_P521_SHA_512\":return rn.PKIX_ECDSA_P521_SHA_512;case 7:case\"PKIX_ED25519\":return rn.PKIX_ED25519;case 8:case\"PKIX_ED25519_PH\":return rn.PKIX_ED25519_PH;case 14:case\"LMS_SHA256\":return rn.LMS_SHA256;case 15:case\"LMOTS_SHA256\":return rn.LMOTS_SHA256;default:throw new globalThis.Error(\"Unrecognized enum value \"+t+\" for enum PublicKeyDetails\")}}function tTe(t){switch(t){case rn.PUBLIC_KEY_DETAILS_UNSPECIFIED:return\"PUBLIC_KEY_DETAILS_UNSPECIFIED\";case rn.PKCS1_RSA_PKCS1V5:return\"PKCS1_RSA_PKCS1V5\";case rn.PKCS1_RSA_PSS:return\"PKCS1_RSA_PSS\";case rn.PKIX_RSA_PKCS1V5:return\"PKIX_RSA_PKCS1V5\";case rn.PKIX_RSA_PSS:return\"PKIX_RSA_PSS\";case rn.PKIX_RSA_PKCS1V15_2048_SHA256:return\"PKIX_RSA_PKCS1V15_2048_SHA256\";case rn.PKIX_RSA_PKCS1V15_3072_SHA256:return\"PKIX_RSA_PKCS1V15_3072_SHA256\";case rn.PKIX_RSA_PKCS1V15_4096_SHA256:return\"PKIX_RSA_PKCS1V15_4096_SHA256\";case rn.PKIX_RSA_PSS_2048_SHA256:return\"PKIX_RSA_PSS_2048_SHA256\";case rn.PKIX_RSA_PSS_3072_SHA256:return\"PKIX_RSA_PSS_3072_SHA256\";case rn.PKIX_RSA_PSS_4096_SHA256:return\"PKIX_RSA_PSS_4096_SHA256\";case rn.PKIX_ECDSA_P256_HMAC_SHA_256:return\"PKIX_ECDSA_P256_HMAC_SHA_256\";case rn.PKIX_ECDSA_P256_SHA_256:return\"PKIX_ECDSA_P256_SHA_256\";case rn.PKIX_ECDSA_P384_SHA_384:return\"PKIX_ECDSA_P384_SHA_384\";case rn.PKIX_ECDSA_P521_SHA_512:return\"PKIX_ECDSA_P521_SHA_512\";case rn.PKIX_ED25519:return\"PKIX_ED25519\";case rn.PKIX_ED25519_PH:return\"PKIX_ED25519_PH\";case rn.LMS_SHA256:return\"LMS_SHA256\";case rn.LMOTS_SHA256:return\"LMOTS_SHA256\";default:throw new globalThis.Error(\"Unrecognized enum value \"+t+\" for enum PublicKeyDetails\")}}var BA;(function(t){t[t.SUBJECT_ALTERNATIVE_NAME_TYPE_UNSPECIFIED=0]=\"SUBJECT_ALTERNATIVE_NAME_TYPE_UNSPECIFIED\",t[t.EMAIL=1]=\"EMAIL\",t[t.URI=2]=\"URI\",t[t.OTHER_NAME=3]=\"OTHER_NAME\"})(BA||(_r.SubjectAlternativeNameType=BA={}));function rTe(t){switch(t){case 0:case\"SUBJECT_ALTERNATIVE_NAME_TYPE_UNSPECIFIED\":return BA.SUBJECT_ALTERNATIVE_NAME_TYPE_UNSPECIFIED;case 1:case\"EMAIL\":return BA.EMAIL;case 2:case\"URI\":return BA.URI;case 3:case\"OTHER_NAME\":return BA.OTHER_NAME;default:throw new globalThis.Error(\"Unrecognized enum value \"+t+\" for enum SubjectAlternativeNameType\")}}function nTe(t){switch(t){case BA.SUBJECT_ALTERNATIVE_NAME_TYPE_UNSPECIFIED:return\"SUBJECT_ALTERNATIVE_NAME_TYPE_UNSPECIFIED\";case BA.EMAIL:return\"EMAIL\";case BA.URI:return\"URI\";case BA.OTHER_NAME:return\"OTHER_NAME\";default:throw new globalThis.Error(\"Unrecognized enum value \"+t+\" for enum SubjectAlternativeNameType\")}}_r.HashOutput={fromJSON(t){return{algorithm:ys(t.algorithm)?XQe(t.algorithm):0,digest:ys(t.digest)?Buffer.from(Zm(t.digest)):Buffer.alloc(0)}},toJSON(t){let e={};return t.algorithm!==0&&(e.algorithm=$Qe(t.algorithm)),t.digest.length!==0&&(e.digest=Xm(t.digest)),e}};_r.MessageSignature={fromJSON(t){return{messageDigest:ys(t.messageDigest)?_r.HashOutput.fromJSON(t.messageDigest):void 0,signature:ys(t.signature)?Buffer.from(Zm(t.signature)):Buffer.alloc(0)}},toJSON(t){let e={};return t.messageDigest!==void 0&&(e.messageDigest=_r.HashOutput.toJSON(t.messageDigest)),t.signature.length!==0&&(e.signature=Xm(t.signature)),e}};_r.LogId={fromJSON(t){return{keyId:ys(t.keyId)?Buffer.from(Zm(t.keyId)):Buffer.alloc(0)}},toJSON(t){let e={};return t.keyId.length!==0&&(e.keyId=Xm(t.keyId)),e}};_r.RFC3161SignedTimestamp={fromJSON(t){return{signedTimestamp:ys(t.signedTimestamp)?Buffer.from(Zm(t.signedTimestamp)):Buffer.alloc(0)}},toJSON(t){let e={};return t.signedTimestamp.length!==0&&(e.signedTimestamp=Xm(t.signedTimestamp)),e}};_r.PublicKey={fromJSON(t){return{rawBytes:ys(t.rawBytes)?Buffer.from(Zm(t.rawBytes)):void 0,keyDetails:ys(t.keyDetails)?eTe(t.keyDetails):0,validFor:ys(t.validFor)?_r.TimeRange.fromJSON(t.validFor):void 0}},toJSON(t){let e={};return t.rawBytes!==void 0&&(e.rawBytes=Xm(t.rawBytes)),t.keyDetails!==0&&(e.keyDetails=tTe(t.keyDetails)),t.validFor!==void 0&&(e.validFor=_r.TimeRange.toJSON(t.validFor)),e}};_r.PublicKeyIdentifier={fromJSON(t){return{hint:ys(t.hint)?globalThis.String(t.hint):\"\"}},toJSON(t){let e={};return t.hint!==\"\"&&(e.hint=t.hint),e}};_r.ObjectIdentifier={fromJSON(t){return{id:globalThis.Array.isArray(t?.id)?t.id.map(e=>globalThis.Number(e)):[]}},toJSON(t){let e={};return t.id?.length&&(e.id=t.id.map(r=>Math.round(r))),e}};_r.ObjectIdentifierValuePair={fromJSON(t){return{oid:ys(t.oid)?_r.ObjectIdentifier.fromJSON(t.oid):void 0,value:ys(t.value)?Buffer.from(Zm(t.value)):Buffer.alloc(0)}},toJSON(t){let e={};return t.oid!==void 0&&(e.oid=_r.ObjectIdentifier.toJSON(t.oid)),t.value.length!==0&&(e.value=Xm(t.value)),e}};_r.DistinguishedName={fromJSON(t){return{organization:ys(t.organization)?globalThis.String(t.organization):\"\",commonName:ys(t.commonName)?globalThis.String(t.commonName):\"\"}},toJSON(t){let e={};return t.organization!==\"\"&&(e.organization=t.organization),t.commonName!==\"\"&&(e.commonName=t.commonName),e}};_r.X509Certificate={fromJSON(t){return{rawBytes:ys(t.rawBytes)?Buffer.from(Zm(t.rawBytes)):Buffer.alloc(0)}},toJSON(t){let e={};return t.rawBytes.length!==0&&(e.rawBytes=Xm(t.rawBytes)),e}};_r.SubjectAlternativeName={fromJSON(t){return{type:ys(t.type)?rTe(t.type):0,identity:ys(t.regexp)?{$case:\"regexp\",regexp:globalThis.String(t.regexp)}:ys(t.value)?{$case:\"value\",value:globalThis.String(t.value)}:void 0}},toJSON(t){let e={};return t.type!==0&&(e.type=nTe(t.type)),t.identity?.$case===\"regexp\"?e.regexp=t.identity.regexp:t.identity?.$case===\"value\"&&(e.value=t.identity.value),e}};_r.X509CertificateChain={fromJSON(t){return{certificates:globalThis.Array.isArray(t?.certificates)?t.certificates.map(e=>_r.X509Certificate.fromJSON(e)):[]}},toJSON(t){let e={};return t.certificates?.length&&(e.certificates=t.certificates.map(r=>_r.X509Certificate.toJSON(r))),e}};_r.TimeRange={fromJSON(t){return{start:ys(t.start)?ZQe(t.start):void 0,end:ys(t.end)?ZQe(t.end):void 0}},toJSON(t){let e={};return t.start!==void 0&&(e.start=t.start.toISOString()),t.end!==void 0&&(e.end=t.end.toISOString()),e}};function Zm(t){return Uint8Array.from(globalThis.Buffer.from(t,\"base64\"))}function Xm(t){return globalThis.Buffer.from(t).toString(\"base64\")}function cOt(t){let e=(globalThis.Number(t.seconds)||0)*1e3;return e+=(t.nanos||0)/1e6,new globalThis.Date(e)}function ZQe(t){return t instanceof globalThis.Date?t:typeof t==\"string\"?new globalThis.Date(t):cOt(lOt.Timestamp.fromJSON(t))}function ys(t){return t!=null}});var p7=L(Es=>{\"use strict\";Object.defineProperty(Es,\"__esModule\",{value:!0});Es.TransparencyLogEntry=Es.InclusionPromise=Es.InclusionProof=Es.Checkpoint=Es.KindVersion=void 0;var iTe=Gw();Es.KindVersion={fromJSON(t){return{kind:Fa(t.kind)?globalThis.String(t.kind):\"\",version:Fa(t.version)?globalThis.String(t.version):\"\"}},toJSON(t){let e={};return t.kind!==\"\"&&(e.kind=t.kind),t.version!==\"\"&&(e.version=t.version),e}};Es.Checkpoint={fromJSON(t){return{envelope:Fa(t.envelope)?globalThis.String(t.envelope):\"\"}},toJSON(t){let e={};return t.envelope!==\"\"&&(e.envelope=t.envelope),e}};Es.InclusionProof={fromJSON(t){return{logIndex:Fa(t.logIndex)?globalThis.String(t.logIndex):\"0\",rootHash:Fa(t.rootHash)?Buffer.from(YN(t.rootHash)):Buffer.alloc(0),treeSize:Fa(t.treeSize)?globalThis.String(t.treeSize):\"0\",hashes:globalThis.Array.isArray(t?.hashes)?t.hashes.map(e=>Buffer.from(YN(e))):[],checkpoint:Fa(t.checkpoint)?Es.Checkpoint.fromJSON(t.checkpoint):void 0}},toJSON(t){let e={};return t.logIndex!==\"0\"&&(e.logIndex=t.logIndex),t.rootHash.length!==0&&(e.rootHash=VN(t.rootHash)),t.treeSize!==\"0\"&&(e.treeSize=t.treeSize),t.hashes?.length&&(e.hashes=t.hashes.map(r=>VN(r))),t.checkpoint!==void 0&&(e.checkpoint=Es.Checkpoint.toJSON(t.checkpoint)),e}};Es.InclusionPromise={fromJSON(t){return{signedEntryTimestamp:Fa(t.signedEntryTimestamp)?Buffer.from(YN(t.signedEntryTimestamp)):Buffer.alloc(0)}},toJSON(t){let e={};return t.signedEntryTimestamp.length!==0&&(e.signedEntryTimestamp=VN(t.signedEntryTimestamp)),e}};Es.TransparencyLogEntry={fromJSON(t){return{logIndex:Fa(t.logIndex)?globalThis.String(t.logIndex):\"0\",logId:Fa(t.logId)?iTe.LogId.fromJSON(t.logId):void 0,kindVersion:Fa(t.kindVersion)?Es.KindVersion.fromJSON(t.kindVersion):void 0,integratedTime:Fa(t.integratedTime)?globalThis.String(t.integratedTime):\"0\",inclusionPromise:Fa(t.inclusionPromise)?Es.InclusionPromise.fromJSON(t.inclusionPromise):void 0,inclusionProof:Fa(t.inclusionProof)?Es.InclusionProof.fromJSON(t.inclusionProof):void 0,canonicalizedBody:Fa(t.canonicalizedBody)?Buffer.from(YN(t.canonicalizedBody)):Buffer.alloc(0)}},toJSON(t){let e={};return t.logIndex!==\"0\"&&(e.logIndex=t.logIndex),t.logId!==void 0&&(e.logId=iTe.LogId.toJSON(t.logId)),t.kindVersion!==void 0&&(e.kindVersion=Es.KindVersion.toJSON(t.kindVersion)),t.integratedTime!==\"0\"&&(e.integratedTime=t.integratedTime),t.inclusionPromise!==void 0&&(e.inclusionPromise=Es.InclusionPromise.toJSON(t.inclusionPromise)),t.inclusionProof!==void 0&&(e.inclusionProof=Es.InclusionProof.toJSON(t.inclusionProof)),t.canonicalizedBody.length!==0&&(e.canonicalizedBody=VN(t.canonicalizedBody)),e}};function YN(t){return Uint8Array.from(globalThis.Buffer.from(t,\"base64\"))}function VN(t){return globalThis.Buffer.from(t).toString(\"base64\")}function Fa(t){return t!=null}});var h7=L($c=>{\"use strict\";Object.defineProperty($c,\"__esModule\",{value:!0});$c.Bundle=$c.VerificationMaterial=$c.TimestampVerificationData=void 0;var sTe=A7(),vA=Gw(),oTe=p7();$c.TimestampVerificationData={fromJSON(t){return{rfc3161Timestamps:globalThis.Array.isArray(t?.rfc3161Timestamps)?t.rfc3161Timestamps.map(e=>vA.RFC3161SignedTimestamp.fromJSON(e)):[]}},toJSON(t){let e={};return t.rfc3161Timestamps?.length&&(e.rfc3161Timestamps=t.rfc3161Timestamps.map(r=>vA.RFC3161SignedTimestamp.toJSON(r))),e}};$c.VerificationMaterial={fromJSON(t){return{content:mg(t.publicKey)?{$case:\"publicKey\",publicKey:vA.PublicKeyIdentifier.fromJSON(t.publicKey)}:mg(t.x509CertificateChain)?{$case:\"x509CertificateChain\",x509CertificateChain:vA.X509CertificateChain.fromJSON(t.x509CertificateChain)}:mg(t.certificate)?{$case:\"certificate\",certificate:vA.X509Certificate.fromJSON(t.certificate)}:void 0,tlogEntries:globalThis.Array.isArray(t?.tlogEntries)?t.tlogEntries.map(e=>oTe.TransparencyLogEntry.fromJSON(e)):[],timestampVerificationData:mg(t.timestampVerificationData)?$c.TimestampVerificationData.fromJSON(t.timestampVerificationData):void 0}},toJSON(t){let e={};return t.content?.$case===\"publicKey\"?e.publicKey=vA.PublicKeyIdentifier.toJSON(t.content.publicKey):t.content?.$case===\"x509CertificateChain\"?e.x509CertificateChain=vA.X509CertificateChain.toJSON(t.content.x509CertificateChain):t.content?.$case===\"certificate\"&&(e.certificate=vA.X509Certificate.toJSON(t.content.certificate)),t.tlogEntries?.length&&(e.tlogEntries=t.tlogEntries.map(r=>oTe.TransparencyLogEntry.toJSON(r))),t.timestampVerificationData!==void 0&&(e.timestampVerificationData=$c.TimestampVerificationData.toJSON(t.timestampVerificationData)),e}};$c.Bundle={fromJSON(t){return{mediaType:mg(t.mediaType)?globalThis.String(t.mediaType):\"\",verificationMaterial:mg(t.verificationMaterial)?$c.VerificationMaterial.fromJSON(t.verificationMaterial):void 0,content:mg(t.messageSignature)?{$case:\"messageSignature\",messageSignature:vA.MessageSignature.fromJSON(t.messageSignature)}:mg(t.dsseEnvelope)?{$case:\"dsseEnvelope\",dsseEnvelope:sTe.Envelope.fromJSON(t.dsseEnvelope)}:void 0}},toJSON(t){let e={};return t.mediaType!==\"\"&&(e.mediaType=t.mediaType),t.verificationMaterial!==void 0&&(e.verificationMaterial=$c.VerificationMaterial.toJSON(t.verificationMaterial)),t.content?.$case===\"messageSignature\"?e.messageSignature=vA.MessageSignature.toJSON(t.content.messageSignature):t.content?.$case===\"dsseEnvelope\"&&(e.dsseEnvelope=sTe.Envelope.toJSON(t.content.dsseEnvelope)),e}};function mg(t){return t!=null}});var g7=L(Fi=>{\"use strict\";Object.defineProperty(Fi,\"__esModule\",{value:!0});Fi.ClientTrustConfig=Fi.SigningConfig=Fi.TrustedRoot=Fi.CertificateAuthority=Fi.TransparencyLogInstance=void 0;var Il=Gw();Fi.TransparencyLogInstance={fromJSON(t){return{baseUrl:sa(t.baseUrl)?globalThis.String(t.baseUrl):\"\",hashAlgorithm:sa(t.hashAlgorithm)?(0,Il.hashAlgorithmFromJSON)(t.hashAlgorithm):0,publicKey:sa(t.publicKey)?Il.PublicKey.fromJSON(t.publicKey):void 0,logId:sa(t.logId)?Il.LogId.fromJSON(t.logId):void 0,checkpointKeyId:sa(t.checkpointKeyId)?Il.LogId.fromJSON(t.checkpointKeyId):void 0}},toJSON(t){let e={};return t.baseUrl!==\"\"&&(e.baseUrl=t.baseUrl),t.hashAlgorithm!==0&&(e.hashAlgorithm=(0,Il.hashAlgorithmToJSON)(t.hashAlgorithm)),t.publicKey!==void 0&&(e.publicKey=Il.PublicKey.toJSON(t.publicKey)),t.logId!==void 0&&(e.logId=Il.LogId.toJSON(t.logId)),t.checkpointKeyId!==void 0&&(e.checkpointKeyId=Il.LogId.toJSON(t.checkpointKeyId)),e}};Fi.CertificateAuthority={fromJSON(t){return{subject:sa(t.subject)?Il.DistinguishedName.fromJSON(t.subject):void 0,uri:sa(t.uri)?globalThis.String(t.uri):\"\",certChain:sa(t.certChain)?Il.X509CertificateChain.fromJSON(t.certChain):void 0,validFor:sa(t.validFor)?Il.TimeRange.fromJSON(t.validFor):void 0}},toJSON(t){let e={};return t.subject!==void 0&&(e.subject=Il.DistinguishedName.toJSON(t.subject)),t.uri!==\"\"&&(e.uri=t.uri),t.certChain!==void 0&&(e.certChain=Il.X509CertificateChain.toJSON(t.certChain)),t.validFor!==void 0&&(e.validFor=Il.TimeRange.toJSON(t.validFor)),e}};Fi.TrustedRoot={fromJSON(t){return{mediaType:sa(t.mediaType)?globalThis.String(t.mediaType):\"\",tlogs:globalThis.Array.isArray(t?.tlogs)?t.tlogs.map(e=>Fi.TransparencyLogInstance.fromJSON(e)):[],certificateAuthorities:globalThis.Array.isArray(t?.certificateAuthorities)?t.certificateAuthorities.map(e=>Fi.CertificateAuthority.fromJSON(e)):[],ctlogs:globalThis.Array.isArray(t?.ctlogs)?t.ctlogs.map(e=>Fi.TransparencyLogInstance.fromJSON(e)):[],timestampAuthorities:globalThis.Array.isArray(t?.timestampAuthorities)?t.timestampAuthorities.map(e=>Fi.CertificateAuthority.fromJSON(e)):[]}},toJSON(t){let e={};return t.mediaType!==\"\"&&(e.mediaType=t.mediaType),t.tlogs?.length&&(e.tlogs=t.tlogs.map(r=>Fi.TransparencyLogInstance.toJSON(r))),t.certificateAuthorities?.length&&(e.certificateAuthorities=t.certificateAuthorities.map(r=>Fi.CertificateAuthority.toJSON(r))),t.ctlogs?.length&&(e.ctlogs=t.ctlogs.map(r=>Fi.TransparencyLogInstance.toJSON(r))),t.timestampAuthorities?.length&&(e.timestampAuthorities=t.timestampAuthorities.map(r=>Fi.CertificateAuthority.toJSON(r))),e}};Fi.SigningConfig={fromJSON(t){return{mediaType:sa(t.mediaType)?globalThis.String(t.mediaType):\"\",caUrl:sa(t.caUrl)?globalThis.String(t.caUrl):\"\",oidcUrl:sa(t.oidcUrl)?globalThis.String(t.oidcUrl):\"\",tlogUrls:globalThis.Array.isArray(t?.tlogUrls)?t.tlogUrls.map(e=>globalThis.String(e)):[],tsaUrls:globalThis.Array.isArray(t?.tsaUrls)?t.tsaUrls.map(e=>globalThis.String(e)):[]}},toJSON(t){let e={};return t.mediaType!==\"\"&&(e.mediaType=t.mediaType),t.caUrl!==\"\"&&(e.caUrl=t.caUrl),t.oidcUrl!==\"\"&&(e.oidcUrl=t.oidcUrl),t.tlogUrls?.length&&(e.tlogUrls=t.tlogUrls),t.tsaUrls?.length&&(e.tsaUrls=t.tsaUrls),e}};Fi.ClientTrustConfig={fromJSON(t){return{mediaType:sa(t.mediaType)?globalThis.String(t.mediaType):\"\",trustedRoot:sa(t.trustedRoot)?Fi.TrustedRoot.fromJSON(t.trustedRoot):void 0,signingConfig:sa(t.signingConfig)?Fi.SigningConfig.fromJSON(t.signingConfig):void 0}},toJSON(t){let e={};return t.mediaType!==\"\"&&(e.mediaType=t.mediaType),t.trustedRoot!==void 0&&(e.trustedRoot=Fi.TrustedRoot.toJSON(t.trustedRoot)),t.signingConfig!==void 0&&(e.signingConfig=Fi.SigningConfig.toJSON(t.signingConfig)),e}};function sa(t){return t!=null}});var cTe=L(Vr=>{\"use strict\";Object.defineProperty(Vr,\"__esModule\",{value:!0});Vr.Input=Vr.Artifact=Vr.ArtifactVerificationOptions_ObserverTimestampOptions=Vr.ArtifactVerificationOptions_TlogIntegratedTimestampOptions=Vr.ArtifactVerificationOptions_TimestampAuthorityOptions=Vr.ArtifactVerificationOptions_CtlogOptions=Vr.ArtifactVerificationOptions_TlogOptions=Vr.ArtifactVerificationOptions=Vr.PublicKeyIdentities=Vr.CertificateIdentities=Vr.CertificateIdentity=void 0;var aTe=h7(),yg=Gw(),lTe=g7();Vr.CertificateIdentity={fromJSON(t){return{issuer:gi(t.issuer)?globalThis.String(t.issuer):\"\",san:gi(t.san)?yg.SubjectAlternativeName.fromJSON(t.san):void 0,oids:globalThis.Array.isArray(t?.oids)?t.oids.map(e=>yg.ObjectIdentifierValuePair.fromJSON(e)):[]}},toJSON(t){let e={};return t.issuer!==\"\"&&(e.issuer=t.issuer),t.san!==void 0&&(e.san=yg.SubjectAlternativeName.toJSON(t.san)),t.oids?.length&&(e.oids=t.oids.map(r=>yg.ObjectIdentifierValuePair.toJSON(r))),e}};Vr.CertificateIdentities={fromJSON(t){return{identities:globalThis.Array.isArray(t?.identities)?t.identities.map(e=>Vr.CertificateIdentity.fromJSON(e)):[]}},toJSON(t){let e={};return t.identities?.length&&(e.identities=t.identities.map(r=>Vr.CertificateIdentity.toJSON(r))),e}};Vr.PublicKeyIdentities={fromJSON(t){return{publicKeys:globalThis.Array.isArray(t?.publicKeys)?t.publicKeys.map(e=>yg.PublicKey.fromJSON(e)):[]}},toJSON(t){let e={};return t.publicKeys?.length&&(e.publicKeys=t.publicKeys.map(r=>yg.PublicKey.toJSON(r))),e}};Vr.ArtifactVerificationOptions={fromJSON(t){return{signers:gi(t.certificateIdentities)?{$case:\"certificateIdentities\",certificateIdentities:Vr.CertificateIdentities.fromJSON(t.certificateIdentities)}:gi(t.publicKeys)?{$case:\"publicKeys\",publicKeys:Vr.PublicKeyIdentities.fromJSON(t.publicKeys)}:void 0,tlogOptions:gi(t.tlogOptions)?Vr.ArtifactVerificationOptions_TlogOptions.fromJSON(t.tlogOptions):void 0,ctlogOptions:gi(t.ctlogOptions)?Vr.ArtifactVerificationOptions_CtlogOptions.fromJSON(t.ctlogOptions):void 0,tsaOptions:gi(t.tsaOptions)?Vr.ArtifactVerificationOptions_TimestampAuthorityOptions.fromJSON(t.tsaOptions):void 0,integratedTsOptions:gi(t.integratedTsOptions)?Vr.ArtifactVerificationOptions_TlogIntegratedTimestampOptions.fromJSON(t.integratedTsOptions):void 0,observerOptions:gi(t.observerOptions)?Vr.ArtifactVerificationOptions_ObserverTimestampOptions.fromJSON(t.observerOptions):void 0}},toJSON(t){let e={};return t.signers?.$case===\"certificateIdentities\"?e.certificateIdentities=Vr.CertificateIdentities.toJSON(t.signers.certificateIdentities):t.signers?.$case===\"publicKeys\"&&(e.publicKeys=Vr.PublicKeyIdentities.toJSON(t.signers.publicKeys)),t.tlogOptions!==void 0&&(e.tlogOptions=Vr.ArtifactVerificationOptions_TlogOptions.toJSON(t.tlogOptions)),t.ctlogOptions!==void 0&&(e.ctlogOptions=Vr.ArtifactVerificationOptions_CtlogOptions.toJSON(t.ctlogOptions)),t.tsaOptions!==void 0&&(e.tsaOptions=Vr.ArtifactVerificationOptions_TimestampAuthorityOptions.toJSON(t.tsaOptions)),t.integratedTsOptions!==void 0&&(e.integratedTsOptions=Vr.ArtifactVerificationOptions_TlogIntegratedTimestampOptions.toJSON(t.integratedTsOptions)),t.observerOptions!==void 0&&(e.observerOptions=Vr.ArtifactVerificationOptions_ObserverTimestampOptions.toJSON(t.observerOptions)),e}};Vr.ArtifactVerificationOptions_TlogOptions={fromJSON(t){return{threshold:gi(t.threshold)?globalThis.Number(t.threshold):0,performOnlineVerification:gi(t.performOnlineVerification)?globalThis.Boolean(t.performOnlineVerification):!1,disable:gi(t.disable)?globalThis.Boolean(t.disable):!1}},toJSON(t){let e={};return t.threshold!==0&&(e.threshold=Math.round(t.threshold)),t.performOnlineVerification!==!1&&(e.performOnlineVerification=t.performOnlineVerification),t.disable!==!1&&(e.disable=t.disable),e}};Vr.ArtifactVerificationOptions_CtlogOptions={fromJSON(t){return{threshold:gi(t.threshold)?globalThis.Number(t.threshold):0,disable:gi(t.disable)?globalThis.Boolean(t.disable):!1}},toJSON(t){let e={};return t.threshold!==0&&(e.threshold=Math.round(t.threshold)),t.disable!==!1&&(e.disable=t.disable),e}};Vr.ArtifactVerificationOptions_TimestampAuthorityOptions={fromJSON(t){return{threshold:gi(t.threshold)?globalThis.Number(t.threshold):0,disable:gi(t.disable)?globalThis.Boolean(t.disable):!1}},toJSON(t){let e={};return t.threshold!==0&&(e.threshold=Math.round(t.threshold)),t.disable!==!1&&(e.disable=t.disable),e}};Vr.ArtifactVerificationOptions_TlogIntegratedTimestampOptions={fromJSON(t){return{threshold:gi(t.threshold)?globalThis.Number(t.threshold):0,disable:gi(t.disable)?globalThis.Boolean(t.disable):!1}},toJSON(t){let e={};return t.threshold!==0&&(e.threshold=Math.round(t.threshold)),t.disable!==!1&&(e.disable=t.disable),e}};Vr.ArtifactVerificationOptions_ObserverTimestampOptions={fromJSON(t){return{threshold:gi(t.threshold)?globalThis.Number(t.threshold):0,disable:gi(t.disable)?globalThis.Boolean(t.disable):!1}},toJSON(t){let e={};return t.threshold!==0&&(e.threshold=Math.round(t.threshold)),t.disable!==!1&&(e.disable=t.disable),e}};Vr.Artifact={fromJSON(t){return{data:gi(t.artifactUri)?{$case:\"artifactUri\",artifactUri:globalThis.String(t.artifactUri)}:gi(t.artifact)?{$case:\"artifact\",artifact:Buffer.from(uOt(t.artifact))}:gi(t.artifactDigest)?{$case:\"artifactDigest\",artifactDigest:yg.HashOutput.fromJSON(t.artifactDigest)}:void 0}},toJSON(t){let e={};return t.data?.$case===\"artifactUri\"?e.artifactUri=t.data.artifactUri:t.data?.$case===\"artifact\"?e.artifact=fOt(t.data.artifact):t.data?.$case===\"artifactDigest\"&&(e.artifactDigest=yg.HashOutput.toJSON(t.data.artifactDigest)),e}};Vr.Input={fromJSON(t){return{artifactTrustRoot:gi(t.artifactTrustRoot)?lTe.TrustedRoot.fromJSON(t.artifactTrustRoot):void 0,artifactVerificationOptions:gi(t.artifactVerificationOptions)?Vr.ArtifactVerificationOptions.fromJSON(t.artifactVerificationOptions):void 0,bundle:gi(t.bundle)?aTe.Bundle.fromJSON(t.bundle):void 0,artifact:gi(t.artifact)?Vr.Artifact.fromJSON(t.artifact):void 0}},toJSON(t){let e={};return t.artifactTrustRoot!==void 0&&(e.artifactTrustRoot=lTe.TrustedRoot.toJSON(t.artifactTrustRoot)),t.artifactVerificationOptions!==void 0&&(e.artifactVerificationOptions=Vr.ArtifactVerificationOptions.toJSON(t.artifactVerificationOptions)),t.bundle!==void 0&&(e.bundle=aTe.Bundle.toJSON(t.bundle)),t.artifact!==void 0&&(e.artifact=Vr.Artifact.toJSON(t.artifact)),e}};function uOt(t){return Uint8Array.from(globalThis.Buffer.from(t,\"base64\"))}function fOt(t){return globalThis.Buffer.from(t).toString(\"base64\")}function gi(t){return t!=null}});var bb=L(eu=>{\"use strict\";var AOt=eu&&eu.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||(\"get\"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),Ww=eu&&eu.__exportStar||function(t,e){for(var r in t)r!==\"default\"&&!Object.prototype.hasOwnProperty.call(e,r)&&AOt(e,t,r)};Object.defineProperty(eu,\"__esModule\",{value:!0});Ww(A7(),eu);Ww(h7(),eu);Ww(Gw(),eu);Ww(p7(),eu);Ww(g7(),eu);Ww(cTe(),eu)});var KN=L(Cl=>{\"use strict\";Object.defineProperty(Cl,\"__esModule\",{value:!0});Cl.BUNDLE_V03_MEDIA_TYPE=Cl.BUNDLE_V03_LEGACY_MEDIA_TYPE=Cl.BUNDLE_V02_MEDIA_TYPE=Cl.BUNDLE_V01_MEDIA_TYPE=void 0;Cl.isBundleWithCertificateChain=pOt;Cl.isBundleWithPublicKey=hOt;Cl.isBundleWithMessageSignature=gOt;Cl.isBundleWithDsseEnvelope=dOt;Cl.BUNDLE_V01_MEDIA_TYPE=\"application/vnd.dev.sigstore.bundle+json;version=0.1\";Cl.BUNDLE_V02_MEDIA_TYPE=\"application/vnd.dev.sigstore.bundle+json;version=0.2\";Cl.BUNDLE_V03_LEGACY_MEDIA_TYPE=\"application/vnd.dev.sigstore.bundle+json;version=0.3\";Cl.BUNDLE_V03_MEDIA_TYPE=\"application/vnd.dev.sigstore.bundle.v0.3+json\";function pOt(t){return t.verificationMaterial.content.$case===\"x509CertificateChain\"}function hOt(t){return t.verificationMaterial.content.$case===\"publicKey\"}function gOt(t){return t.content.$case===\"messageSignature\"}function dOt(t){return t.content.$case===\"dsseEnvelope\"}});var fTe=L(zN=>{\"use strict\";Object.defineProperty(zN,\"__esModule\",{value:!0});zN.toMessageSignatureBundle=yOt;zN.toDSSEBundle=EOt;var mOt=bb(),JN=KN();function yOt(t){return{mediaType:t.certificateChain?JN.BUNDLE_V02_MEDIA_TYPE:JN.BUNDLE_V03_MEDIA_TYPE,content:{$case:\"messageSignature\",messageSignature:{messageDigest:{algorithm:mOt.HashAlgorithm.SHA2_256,digest:t.digest},signature:t.signature}},verificationMaterial:uTe(t)}}function EOt(t){return{mediaType:t.certificateChain?JN.BUNDLE_V02_MEDIA_TYPE:JN.BUNDLE_V03_MEDIA_TYPE,content:{$case:\"dsseEnvelope\",dsseEnvelope:IOt(t)},verificationMaterial:uTe(t)}}function IOt(t){return{payloadType:t.artifactType,payload:t.artifact,signatures:[COt(t)]}}function COt(t){return{keyid:t.keyHint||\"\",sig:t.signature}}function uTe(t){return{content:wOt(t),tlogEntries:[],timestampVerificationData:{rfc3161Timestamps:[]}}}function wOt(t){return t.certificate?t.certificateChain?{$case:\"x509CertificateChain\",x509CertificateChain:{certificates:[{rawBytes:t.certificate}]}}:{$case:\"certificate\",certificate:{rawBytes:t.certificate}}:{$case:\"publicKey\",publicKey:{hint:t.keyHint||\"\"}}}});var m7=L(ZN=>{\"use strict\";Object.defineProperty(ZN,\"__esModule\",{value:!0});ZN.ValidationError=void 0;var d7=class extends Error{constructor(e,r){super(e),this.fields=r}};ZN.ValidationError=d7});var y7=L($m=>{\"use strict\";Object.defineProperty($m,\"__esModule\",{value:!0});$m.assertBundle=BOt;$m.assertBundleV01=ATe;$m.isBundleV01=vOt;$m.assertBundleV02=SOt;$m.assertBundleLatest=DOt;var XN=m7();function BOt(t){let e=$N(t);if(e.length>0)throw new XN.ValidationError(\"invalid bundle\",e)}function ATe(t){let e=[];if(e.push(...$N(t)),e.push(...bOt(t)),e.length>0)throw new XN.ValidationError(\"invalid v0.1 bundle\",e)}function vOt(t){try{return ATe(t),!0}catch{return!1}}function SOt(t){let e=[];if(e.push(...$N(t)),e.push(...pTe(t)),e.length>0)throw new XN.ValidationError(\"invalid v0.2 bundle\",e)}function DOt(t){let e=[];if(e.push(...$N(t)),e.push(...pTe(t)),e.push(...POt(t)),e.length>0)throw new XN.ValidationError(\"invalid bundle\",e)}function $N(t){let e=[];if((t.mediaType===void 0||!t.mediaType.match(/^application\\/vnd\\.dev\\.sigstore\\.bundle\\+json;version=\\d\\.\\d/)&&!t.mediaType.match(/^application\\/vnd\\.dev\\.sigstore\\.bundle\\.v\\d\\.\\d\\+json/))&&e.push(\"mediaType\"),t.content===void 0)e.push(\"content\");else switch(t.content.$case){case\"messageSignature\":t.content.messageSignature.messageDigest===void 0?e.push(\"content.messageSignature.messageDigest\"):t.content.messageSignature.messageDigest.digest.length===0&&e.push(\"content.messageSignature.messageDigest.digest\"),t.content.messageSignature.signature.length===0&&e.push(\"content.messageSignature.signature\");break;case\"dsseEnvelope\":t.content.dsseEnvelope.payload.length===0&&e.push(\"content.dsseEnvelope.payload\"),t.content.dsseEnvelope.signatures.length!==1?e.push(\"content.dsseEnvelope.signatures\"):t.content.dsseEnvelope.signatures[0].sig.length===0&&e.push(\"content.dsseEnvelope.signatures[0].sig\");break}if(t.verificationMaterial===void 0)e.push(\"verificationMaterial\");else{if(t.verificationMaterial.content===void 0)e.push(\"verificationMaterial.content\");else switch(t.verificationMaterial.content.$case){case\"x509CertificateChain\":t.verificationMaterial.content.x509CertificateChain.certificates.length===0&&e.push(\"verificationMaterial.content.x509CertificateChain.certificates\"),t.verificationMaterial.content.x509CertificateChain.certificates.forEach((r,s)=>{r.rawBytes.length===0&&e.push(`verificationMaterial.content.x509CertificateChain.certificates[${s}].rawBytes`)});break;case\"certificate\":t.verificationMaterial.content.certificate.rawBytes.length===0&&e.push(\"verificationMaterial.content.certificate.rawBytes\");break}t.verificationMaterial.tlogEntries===void 0?e.push(\"verificationMaterial.tlogEntries\"):t.verificationMaterial.tlogEntries.length>0&&t.verificationMaterial.tlogEntries.forEach((r,s)=>{r.logId===void 0&&e.push(`verificationMaterial.tlogEntries[${s}].logId`),r.kindVersion===void 0&&e.push(`verificationMaterial.tlogEntries[${s}].kindVersion`)})}return e}function bOt(t){let e=[];return t.verificationMaterial&&t.verificationMaterial.tlogEntries?.length>0&&t.verificationMaterial.tlogEntries.forEach((r,s)=>{r.inclusionPromise===void 0&&e.push(`verificationMaterial.tlogEntries[${s}].inclusionPromise`)}),e}function pTe(t){let e=[];return t.verificationMaterial&&t.verificationMaterial.tlogEntries?.length>0&&t.verificationMaterial.tlogEntries.forEach((r,s)=>{r.inclusionProof===void 0?e.push(`verificationMaterial.tlogEntries[${s}].inclusionProof`):r.inclusionProof.checkpoint===void 0&&e.push(`verificationMaterial.tlogEntries[${s}].inclusionProof.checkpoint`)}),e}function POt(t){let e=[];return t.verificationMaterial?.content?.$case===\"x509CertificateChain\"&&e.push(\"verificationMaterial.content.$case\"),e}});var gTe=L(SA=>{\"use strict\";Object.defineProperty(SA,\"__esModule\",{value:!0});SA.envelopeToJSON=SA.envelopeFromJSON=SA.bundleToJSON=SA.bundleFromJSON=void 0;var eO=bb(),hTe=KN(),E7=y7(),xOt=t=>{let e=eO.Bundle.fromJSON(t);switch(e.mediaType){case hTe.BUNDLE_V01_MEDIA_TYPE:(0,E7.assertBundleV01)(e);break;case hTe.BUNDLE_V02_MEDIA_TYPE:(0,E7.assertBundleV02)(e);break;default:(0,E7.assertBundleLatest)(e);break}return e};SA.bundleFromJSON=xOt;var kOt=t=>eO.Bundle.toJSON(t);SA.bundleToJSON=kOt;var QOt=t=>eO.Envelope.fromJSON(t);SA.envelopeFromJSON=QOt;var TOt=t=>eO.Envelope.toJSON(t);SA.envelopeToJSON=TOt});var xb=L(Zr=>{\"use strict\";Object.defineProperty(Zr,\"__esModule\",{value:!0});Zr.isBundleV01=Zr.assertBundleV02=Zr.assertBundleV01=Zr.assertBundleLatest=Zr.assertBundle=Zr.envelopeToJSON=Zr.envelopeFromJSON=Zr.bundleToJSON=Zr.bundleFromJSON=Zr.ValidationError=Zr.isBundleWithPublicKey=Zr.isBundleWithMessageSignature=Zr.isBundleWithDsseEnvelope=Zr.isBundleWithCertificateChain=Zr.BUNDLE_V03_MEDIA_TYPE=Zr.BUNDLE_V03_LEGACY_MEDIA_TYPE=Zr.BUNDLE_V02_MEDIA_TYPE=Zr.BUNDLE_V01_MEDIA_TYPE=Zr.toMessageSignatureBundle=Zr.toDSSEBundle=void 0;var dTe=fTe();Object.defineProperty(Zr,\"toDSSEBundle\",{enumerable:!0,get:function(){return dTe.toDSSEBundle}});Object.defineProperty(Zr,\"toMessageSignatureBundle\",{enumerable:!0,get:function(){return dTe.toMessageSignatureBundle}});var Eg=KN();Object.defineProperty(Zr,\"BUNDLE_V01_MEDIA_TYPE\",{enumerable:!0,get:function(){return Eg.BUNDLE_V01_MEDIA_TYPE}});Object.defineProperty(Zr,\"BUNDLE_V02_MEDIA_TYPE\",{enumerable:!0,get:function(){return Eg.BUNDLE_V02_MEDIA_TYPE}});Object.defineProperty(Zr,\"BUNDLE_V03_LEGACY_MEDIA_TYPE\",{enumerable:!0,get:function(){return Eg.BUNDLE_V03_LEGACY_MEDIA_TYPE}});Object.defineProperty(Zr,\"BUNDLE_V03_MEDIA_TYPE\",{enumerable:!0,get:function(){return Eg.BUNDLE_V03_MEDIA_TYPE}});Object.defineProperty(Zr,\"isBundleWithCertificateChain\",{enumerable:!0,get:function(){return Eg.isBundleWithCertificateChain}});Object.defineProperty(Zr,\"isBundleWithDsseEnvelope\",{enumerable:!0,get:function(){return Eg.isBundleWithDsseEnvelope}});Object.defineProperty(Zr,\"isBundleWithMessageSignature\",{enumerable:!0,get:function(){return Eg.isBundleWithMessageSignature}});Object.defineProperty(Zr,\"isBundleWithPublicKey\",{enumerable:!0,get:function(){return Eg.isBundleWithPublicKey}});var ROt=m7();Object.defineProperty(Zr,\"ValidationError\",{enumerable:!0,get:function(){return ROt.ValidationError}});var tO=gTe();Object.defineProperty(Zr,\"bundleFromJSON\",{enumerable:!0,get:function(){return tO.bundleFromJSON}});Object.defineProperty(Zr,\"bundleToJSON\",{enumerable:!0,get:function(){return tO.bundleToJSON}});Object.defineProperty(Zr,\"envelopeFromJSON\",{enumerable:!0,get:function(){return tO.envelopeFromJSON}});Object.defineProperty(Zr,\"envelopeToJSON\",{enumerable:!0,get:function(){return tO.envelopeToJSON}});var Pb=y7();Object.defineProperty(Zr,\"assertBundle\",{enumerable:!0,get:function(){return Pb.assertBundle}});Object.defineProperty(Zr,\"assertBundleLatest\",{enumerable:!0,get:function(){return Pb.assertBundleLatest}});Object.defineProperty(Zr,\"assertBundleV01\",{enumerable:!0,get:function(){return Pb.assertBundleV01}});Object.defineProperty(Zr,\"assertBundleV02\",{enumerable:!0,get:function(){return Pb.assertBundleV02}});Object.defineProperty(Zr,\"isBundleV01\",{enumerable:!0,get:function(){return Pb.isBundleV01}})});var kb=L(nO=>{\"use strict\";Object.defineProperty(nO,\"__esModule\",{value:!0});nO.ByteStream=void 0;var I7=class extends Error{},rO=class t{constructor(e){this.start=0,e?(this.buf=e,this.view=Buffer.from(e)):(this.buf=new ArrayBuffer(0),this.view=Buffer.from(this.buf))}get buffer(){return this.view.subarray(0,this.start)}get length(){return this.view.byteLength}get position(){return this.start}seek(e){this.start=e}slice(e,r){let s=e+r;if(s>this.length)throw new I7(\"request past end of buffer\");return this.view.subarray(e,s)}appendChar(e){this.ensureCapacity(1),this.view[this.start]=e,this.start+=1}appendUint16(e){this.ensureCapacity(2);let r=new Uint16Array([e]),s=new Uint8Array(r.buffer);this.view[this.start]=s[1],this.view[this.start+1]=s[0],this.start+=2}appendUint24(e){this.ensureCapacity(3);let r=new Uint32Array([e]),s=new Uint8Array(r.buffer);this.view[this.start]=s[2],this.view[this.start+1]=s[1],this.view[this.start+2]=s[0],this.start+=3}appendView(e){this.ensureCapacity(e.length),this.view.set(e,this.start),this.start+=e.length}getBlock(e){if(e<=0)return Buffer.alloc(0);if(this.start+e>this.view.length)throw new Error(\"request past end of buffer\");let r=this.view.subarray(this.start,this.start+e);return this.start+=e,r}getUint8(){return this.getBlock(1)[0]}getUint16(){let e=this.getBlock(2);return e[0]<<8|e[1]}ensureCapacity(e){if(this.start+e>this.view.byteLength){let r=t.BLOCK_SIZE+(e>t.BLOCK_SIZE?e:0);this.realloc(this.view.byteLength+r)}}realloc(e){let r=new ArrayBuffer(e),s=Buffer.from(r);s.set(this.view),this.buf=r,this.view=s}};nO.ByteStream=rO;rO.BLOCK_SIZE=1024});var iO=L(Yw=>{\"use strict\";Object.defineProperty(Yw,\"__esModule\",{value:!0});Yw.ASN1TypeError=Yw.ASN1ParseError=void 0;var C7=class extends Error{};Yw.ASN1ParseError=C7;var w7=class extends Error{};Yw.ASN1TypeError=w7});var yTe=L(sO=>{\"use strict\";Object.defineProperty(sO,\"__esModule\",{value:!0});sO.decodeLength=FOt;sO.encodeLength=NOt;var mTe=iO();function FOt(t){let e=t.getUint8();if(!(e&128))return e;let r=e&127;if(r>6)throw new mTe.ASN1ParseError(\"length exceeds 6 byte limit\");let s=0;for(let a=0;a<r;a++)s=s*256+t.getUint8();if(s===0)throw new mTe.ASN1ParseError(\"indefinite length encoding not supported\");return s}function NOt(t){if(t<128)return Buffer.from([t]);let e=BigInt(t),r=[];for(;e>0n;)r.unshift(Number(e&255n)),e=e>>8n;return Buffer.from([128|r.length,...r])}});var ITe=L(Ig=>{\"use strict\";Object.defineProperty(Ig,\"__esModule\",{value:!0});Ig.parseInteger=MOt;Ig.parseStringASCII=ETe;Ig.parseTime=_Ot;Ig.parseOID=UOt;Ig.parseBoolean=HOt;Ig.parseBitString=jOt;var OOt=/^(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\.\\d{3})?Z$/,LOt=/^(\\d{4})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\.\\d{3})?Z$/;function MOt(t){let e=0,r=t.length,s=t[e],a=s>127,n=a?255:0;for(;s==n&&++e<r;)s=t[e];if(r-e===0)return BigInt(a?-1:0);s=a?s-256:s;let f=BigInt(s);for(let p=e+1;p<r;++p)f=f*BigInt(256)+BigInt(t[p]);return f}function ETe(t){return t.toString(\"ascii\")}function _Ot(t,e){let r=ETe(t),s=e?OOt.exec(r):LOt.exec(r);if(!s)throw new Error(\"invalid time\");if(e){let a=Number(s[1]);a+=a>=50?1900:2e3,s[1]=a.toString()}return new Date(`${s[1]}-${s[2]}-${s[3]}T${s[4]}:${s[5]}:${s[6]}Z`)}function UOt(t){let e=0,r=t.length,s=t[e++],a=Math.floor(s/40),n=s%40,c=`${a}.${n}`,f=0;for(;e<r;++e)s=t[e],f=(f<<7)+(s&127),s&128||(c+=`.${f}`,f=0);return c}function HOt(t){return t[0]!==0}function jOt(t){let e=t[0],r=1,s=t.length,a=[];for(let n=r;n<s;++n){let c=t[n],f=n===s-1?e:0;for(let p=7;p>=f;--p)a.push(c>>p&1)}return a}});var wTe=L(oO=>{\"use strict\";Object.defineProperty(oO,\"__esModule\",{value:!0});oO.ASN1Tag=void 0;var CTe=iO(),ey={BOOLEAN:1,INTEGER:2,BIT_STRING:3,OCTET_STRING:4,OBJECT_IDENTIFIER:6,SEQUENCE:16,SET:17,PRINTABLE_STRING:19,UTC_TIME:23,GENERALIZED_TIME:24},B7={UNIVERSAL:0,APPLICATION:1,CONTEXT_SPECIFIC:2,PRIVATE:3},v7=class{constructor(e){if(this.number=e&31,this.constructed=(e&32)===32,this.class=e>>6,this.number===31)throw new CTe.ASN1ParseError(\"long form tags not supported\");if(this.class===B7.UNIVERSAL&&this.number===0)throw new CTe.ASN1ParseError(\"unsupported tag 0x00\")}isUniversal(){return this.class===B7.UNIVERSAL}isContextSpecific(e){let r=this.class===B7.CONTEXT_SPECIFIC;return e!==void 0?r&&this.number===e:r}isBoolean(){return this.isUniversal()&&this.number===ey.BOOLEAN}isInteger(){return this.isUniversal()&&this.number===ey.INTEGER}isBitString(){return this.isUniversal()&&this.number===ey.BIT_STRING}isOctetString(){return this.isUniversal()&&this.number===ey.OCTET_STRING}isOID(){return this.isUniversal()&&this.number===ey.OBJECT_IDENTIFIER}isUTCTime(){return this.isUniversal()&&this.number===ey.UTC_TIME}isGeneralizedTime(){return this.isUniversal()&&this.number===ey.GENERALIZED_TIME}toDER(){return this.number|(this.constructed?32:0)|this.class<<6}};oO.ASN1Tag=v7});var DTe=L(lO=>{\"use strict\";Object.defineProperty(lO,\"__esModule\",{value:!0});lO.ASN1Obj=void 0;var S7=kb(),ty=iO(),vTe=yTe(),Vw=ITe(),qOt=wTe(),aO=class{constructor(e,r,s){this.tag=e,this.value=r,this.subs=s}static parseBuffer(e){return STe(new S7.ByteStream(e))}toDER(){let e=new S7.ByteStream;if(this.subs.length>0)for(let a of this.subs)e.appendView(a.toDER());else e.appendView(this.value);let r=e.buffer,s=new S7.ByteStream;return s.appendChar(this.tag.toDER()),s.appendView((0,vTe.encodeLength)(r.length)),s.appendView(r),s.buffer}toBoolean(){if(!this.tag.isBoolean())throw new ty.ASN1TypeError(\"not a boolean\");return(0,Vw.parseBoolean)(this.value)}toInteger(){if(!this.tag.isInteger())throw new ty.ASN1TypeError(\"not an integer\");return(0,Vw.parseInteger)(this.value)}toOID(){if(!this.tag.isOID())throw new ty.ASN1TypeError(\"not an OID\");return(0,Vw.parseOID)(this.value)}toDate(){switch(!0){case this.tag.isUTCTime():return(0,Vw.parseTime)(this.value,!0);case this.tag.isGeneralizedTime():return(0,Vw.parseTime)(this.value,!1);default:throw new ty.ASN1TypeError(\"not a date\")}}toBitString(){if(!this.tag.isBitString())throw new ty.ASN1TypeError(\"not a bit string\");return(0,Vw.parseBitString)(this.value)}};lO.ASN1Obj=aO;function STe(t){let e=new qOt.ASN1Tag(t.getUint8()),r=(0,vTe.decodeLength)(t),s=t.slice(t.position,r),a=t.position,n=[];if(e.constructed)n=BTe(t,r);else if(e.isOctetString())try{n=BTe(t,r)}catch{}return n.length===0&&t.seek(a+r),new aO(e,s,n)}function BTe(t,e){let r=t.position+e;if(r>t.length)throw new ty.ASN1ParseError(\"invalid length\");let s=[];for(;t.position<r;)s.push(STe(t));if(t.position!==r)throw new ty.ASN1ParseError(\"invalid length\");return s}});var uO=L(cO=>{\"use strict\";Object.defineProperty(cO,\"__esModule\",{value:!0});cO.ASN1Obj=void 0;var GOt=DTe();Object.defineProperty(cO,\"ASN1Obj\",{enumerable:!0,get:function(){return GOt.ASN1Obj}})});var Kw=L(Cg=>{\"use strict\";var WOt=Cg&&Cg.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(Cg,\"__esModule\",{value:!0});Cg.createPublicKey=YOt;Cg.digest=VOt;Cg.verify=KOt;Cg.bufferEqual=JOt;var Qb=WOt(Ie(\"crypto\"));function YOt(t,e=\"spki\"){return typeof t==\"string\"?Qb.default.createPublicKey(t):Qb.default.createPublicKey({key:t,format:\"der\",type:e})}function VOt(t,...e){let r=Qb.default.createHash(t);for(let s of e)r.update(s);return r.digest()}function KOt(t,e,r,s){try{return Qb.default.verify(s,t,e,r)}catch{return!1}}function JOt(t,e){try{return Qb.default.timingSafeEqual(t,e)}catch{return!1}}});var bTe=L(D7=>{\"use strict\";Object.defineProperty(D7,\"__esModule\",{value:!0});D7.preAuthEncoding=ZOt;var zOt=\"DSSEv1\";function ZOt(t,e){let r=[zOt,t.length,t,e.length,\"\"].join(\" \");return Buffer.concat([Buffer.from(r,\"ascii\"),e])}});var kTe=L(fO=>{\"use strict\";Object.defineProperty(fO,\"__esModule\",{value:!0});fO.base64Encode=XOt;fO.base64Decode=$Ot;var PTe=\"base64\",xTe=\"utf-8\";function XOt(t){return Buffer.from(t,xTe).toString(PTe)}function $Ot(t){return Buffer.from(t,PTe).toString(xTe)}});var QTe=L(P7=>{\"use strict\";Object.defineProperty(P7,\"__esModule\",{value:!0});P7.canonicalize=b7;function b7(t){let e=\"\";if(t===null||typeof t!=\"object\"||t.toJSON!=null)e+=JSON.stringify(t);else if(Array.isArray(t)){e+=\"[\";let r=!0;t.forEach(s=>{r||(e+=\",\"),r=!1,e+=b7(s)}),e+=\"]\"}else{e+=\"{\";let r=!0;Object.keys(t).sort().forEach(s=>{r||(e+=\",\"),r=!1,e+=JSON.stringify(s),e+=\":\",e+=b7(t[s])}),e+=\"}\"}return e}});var x7=L(AO=>{\"use strict\";Object.defineProperty(AO,\"__esModule\",{value:!0});AO.toDER=rLt;AO.fromDER=nLt;var eLt=/-----BEGIN (.*)-----/,tLt=/-----END (.*)-----/;function rLt(t){let e=\"\";return t.split(`\n`).forEach(r=>{r.match(eLt)||r.match(tLt)||(e+=r)}),Buffer.from(e,\"base64\")}function nLt(t,e=\"CERTIFICATE\"){let s=t.toString(\"base64\").match(/.{1,64}/g)||\"\";return[`-----BEGIN ${e}-----`,...s,`-----END ${e}-----`].join(`\n`).concat(`\n`)}});var pO=L(Jw=>{\"use strict\";Object.defineProperty(Jw,\"__esModule\",{value:!0});Jw.SHA2_HASH_ALGOS=Jw.ECDSA_SIGNATURE_ALGOS=void 0;Jw.ECDSA_SIGNATURE_ALGOS={\"1.2.840.10045.4.3.1\":\"sha224\",\"1.2.840.10045.4.3.2\":\"sha256\",\"1.2.840.10045.4.3.3\":\"sha384\",\"1.2.840.10045.4.3.4\":\"sha512\"};Jw.SHA2_HASH_ALGOS={\"2.16.840.1.101.3.4.2.1\":\"sha256\",\"2.16.840.1.101.3.4.2.2\":\"sha384\",\"2.16.840.1.101.3.4.2.3\":\"sha512\"}});var Q7=L(hO=>{\"use strict\";Object.defineProperty(hO,\"__esModule\",{value:!0});hO.RFC3161TimestampVerificationError=void 0;var k7=class extends Error{};hO.RFC3161TimestampVerificationError=k7});var RTe=L(DA=>{\"use strict\";var iLt=DA&&DA.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||(\"get\"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),sLt=DA&&DA.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,\"default\",{enumerable:!0,value:e})}:function(t,e){t.default=e}),oLt=DA&&DA.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!==\"default\"&&Object.prototype.hasOwnProperty.call(t,r)&&iLt(e,t,r);return sLt(e,t),e};Object.defineProperty(DA,\"__esModule\",{value:!0});DA.TSTInfo=void 0;var TTe=oLt(Kw()),aLt=pO(),lLt=Q7(),T7=class{constructor(e){this.root=e}get version(){return this.root.subs[0].toInteger()}get genTime(){return this.root.subs[4].toDate()}get messageImprintHashAlgorithm(){let e=this.messageImprintObj.subs[0].subs[0].toOID();return aLt.SHA2_HASH_ALGOS[e]}get messageImprintHashedMessage(){return this.messageImprintObj.subs[1].value}get raw(){return this.root.toDER()}verify(e){let r=TTe.digest(this.messageImprintHashAlgorithm,e);if(!TTe.bufferEqual(r,this.messageImprintHashedMessage))throw new lLt.RFC3161TimestampVerificationError(\"message imprint does not match artifact\")}get messageImprintObj(){return this.root.subs[2]}};DA.TSTInfo=T7});var NTe=L(bA=>{\"use strict\";var cLt=bA&&bA.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||(\"get\"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),uLt=bA&&bA.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,\"default\",{enumerable:!0,value:e})}:function(t,e){t.default=e}),fLt=bA&&bA.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!==\"default\"&&Object.prototype.hasOwnProperty.call(t,r)&&cLt(e,t,r);return uLt(e,t),e};Object.defineProperty(bA,\"__esModule\",{value:!0});bA.RFC3161Timestamp=void 0;var ALt=uO(),R7=fLt(Kw()),FTe=pO(),Tb=Q7(),pLt=RTe(),hLt=\"1.2.840.113549.1.7.2\",gLt=\"1.2.840.113549.1.9.16.1.4\",dLt=\"1.2.840.113549.1.9.4\",F7=class t{constructor(e){this.root=e}static parse(e){let r=ALt.ASN1Obj.parseBuffer(e);return new t(r)}get status(){return this.pkiStatusInfoObj.subs[0].toInteger()}get contentType(){return this.contentTypeObj.toOID()}get eContentType(){return this.eContentTypeObj.toOID()}get signingTime(){return this.tstInfo.genTime}get signerIssuer(){return this.signerSidObj.subs[0].value}get signerSerialNumber(){return this.signerSidObj.subs[1].value}get signerDigestAlgorithm(){let e=this.signerDigestAlgorithmObj.subs[0].toOID();return FTe.SHA2_HASH_ALGOS[e]}get signatureAlgorithm(){let e=this.signatureAlgorithmObj.subs[0].toOID();return FTe.ECDSA_SIGNATURE_ALGOS[e]}get signatureValue(){return this.signatureValueObj.value}get tstInfo(){return new pLt.TSTInfo(this.eContentObj.subs[0].subs[0])}verify(e,r){if(!this.timeStampTokenObj)throw new Tb.RFC3161TimestampVerificationError(\"timeStampToken is missing\");if(this.contentType!==hLt)throw new Tb.RFC3161TimestampVerificationError(`incorrect content type: ${this.contentType}`);if(this.eContentType!==gLt)throw new Tb.RFC3161TimestampVerificationError(`incorrect encapsulated content type: ${this.eContentType}`);this.tstInfo.verify(e),this.verifyMessageDigest(),this.verifySignature(r)}verifyMessageDigest(){let e=R7.digest(this.signerDigestAlgorithm,this.tstInfo.raw),r=this.messageDigestAttributeObj.subs[1].subs[0].value;if(!R7.bufferEqual(e,r))throw new Tb.RFC3161TimestampVerificationError(\"signed data does not match tstInfo\")}verifySignature(e){let r=this.signedAttrsObj.toDER();if(r[0]=49,!R7.verify(r,e,this.signatureValue,this.signatureAlgorithm))throw new Tb.RFC3161TimestampVerificationError(\"signature verification failed\")}get pkiStatusInfoObj(){return this.root.subs[0]}get timeStampTokenObj(){return this.root.subs[1]}get contentTypeObj(){return this.timeStampTokenObj.subs[0]}get signedDataObj(){return this.timeStampTokenObj.subs.find(r=>r.tag.isContextSpecific(0)).subs[0]}get encapContentInfoObj(){return this.signedDataObj.subs[2]}get signerInfosObj(){let e=this.signedDataObj;return e.subs[e.subs.length-1]}get signerInfoObj(){return this.signerInfosObj.subs[0]}get eContentTypeObj(){return this.encapContentInfoObj.subs[0]}get eContentObj(){return this.encapContentInfoObj.subs[1]}get signedAttrsObj(){return this.signerInfoObj.subs.find(r=>r.tag.isContextSpecific(0))}get messageDigestAttributeObj(){return this.signedAttrsObj.subs.find(r=>r.subs[0].tag.isOID()&&r.subs[0].toOID()===dLt)}get signerSidObj(){return this.signerInfoObj.subs[1]}get signerDigestAlgorithmObj(){return this.signerInfoObj.subs[2]}get signatureAlgorithmObj(){return this.signerInfoObj.subs[4]}get signatureValueObj(){return this.signerInfoObj.subs[5]}};bA.RFC3161Timestamp=F7});var OTe=L(gO=>{\"use strict\";Object.defineProperty(gO,\"__esModule\",{value:!0});gO.RFC3161Timestamp=void 0;var mLt=NTe();Object.defineProperty(gO,\"RFC3161Timestamp\",{enumerable:!0,get:function(){return mLt.RFC3161Timestamp}})});var MTe=L(PA=>{\"use strict\";var yLt=PA&&PA.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||(\"get\"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),ELt=PA&&PA.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,\"default\",{enumerable:!0,value:e})}:function(t,e){t.default=e}),ILt=PA&&PA.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!==\"default\"&&Object.prototype.hasOwnProperty.call(t,r)&&yLt(e,t,r);return ELt(e,t),e};Object.defineProperty(PA,\"__esModule\",{value:!0});PA.SignedCertificateTimestamp=void 0;var CLt=ILt(Kw()),LTe=kb(),N7=class t{constructor(e){this.version=e.version,this.logID=e.logID,this.timestamp=e.timestamp,this.extensions=e.extensions,this.hashAlgorithm=e.hashAlgorithm,this.signatureAlgorithm=e.signatureAlgorithm,this.signature=e.signature}get datetime(){return new Date(Number(this.timestamp.readBigInt64BE()))}get algorithm(){switch(this.hashAlgorithm){case 0:return\"none\";case 1:return\"md5\";case 2:return\"sha1\";case 3:return\"sha224\";case 4:return\"sha256\";case 5:return\"sha384\";case 6:return\"sha512\";default:return\"unknown\"}}verify(e,r){let s=new LTe.ByteStream;return s.appendChar(this.version),s.appendChar(0),s.appendView(this.timestamp),s.appendUint16(1),s.appendView(e),s.appendUint16(this.extensions.byteLength),this.extensions.byteLength>0&&s.appendView(this.extensions),CLt.verify(s.buffer,r,this.signature,this.algorithm)}static parse(e){let r=new LTe.ByteStream(e),s=r.getUint8(),a=r.getBlock(32),n=r.getBlock(8),c=r.getUint16(),f=r.getBlock(c),p=r.getUint8(),h=r.getUint8(),E=r.getUint16(),C=r.getBlock(E);if(r.position!==e.length)throw new Error(\"SCT buffer length mismatch\");return new t({version:s,logID:a,timestamp:n,extensions:f,hashAlgorithm:p,signatureAlgorithm:h,signature:C})}};PA.SignedCertificateTimestamp=N7});var j7=L(oa=>{\"use strict\";Object.defineProperty(oa,\"__esModule\",{value:!0});oa.X509SCTExtension=oa.X509SubjectKeyIDExtension=oa.X509AuthorityKeyIDExtension=oa.X509SubjectAlternativeNameExtension=oa.X509KeyUsageExtension=oa.X509BasicConstraintsExtension=oa.X509Extension=void 0;var wLt=kb(),BLt=MTe(),gh=class{constructor(e){this.root=e}get oid(){return this.root.subs[0].toOID()}get critical(){return this.root.subs.length===3?this.root.subs[1].toBoolean():!1}get value(){return this.extnValueObj.value}get valueObj(){return this.extnValueObj}get extnValueObj(){return this.root.subs[this.root.subs.length-1]}};oa.X509Extension=gh;var O7=class extends gh{get isCA(){return this.sequence.subs[0]?.toBoolean()??!1}get pathLenConstraint(){return this.sequence.subs.length>1?this.sequence.subs[1].toInteger():void 0}get sequence(){return this.extnValueObj.subs[0]}};oa.X509BasicConstraintsExtension=O7;var L7=class extends gh{get digitalSignature(){return this.bitString[0]===1}get keyCertSign(){return this.bitString[5]===1}get crlSign(){return this.bitString[6]===1}get bitString(){return this.extnValueObj.subs[0].toBitString()}};oa.X509KeyUsageExtension=L7;var M7=class extends gh{get rfc822Name(){return this.findGeneralName(1)?.value.toString(\"ascii\")}get uri(){return this.findGeneralName(6)?.value.toString(\"ascii\")}otherName(e){let r=this.findGeneralName(0);return r===void 0||r.subs[0].toOID()!==e?void 0:r.subs[1].subs[0].value.toString(\"ascii\")}findGeneralName(e){return this.generalNames.find(r=>r.tag.isContextSpecific(e))}get generalNames(){return this.extnValueObj.subs[0].subs}};oa.X509SubjectAlternativeNameExtension=M7;var _7=class extends gh{get keyIdentifier(){return this.findSequenceMember(0)?.value}findSequenceMember(e){return this.sequence.subs.find(r=>r.tag.isContextSpecific(e))}get sequence(){return this.extnValueObj.subs[0]}};oa.X509AuthorityKeyIDExtension=_7;var U7=class extends gh{get keyIdentifier(){return this.extnValueObj.subs[0].value}};oa.X509SubjectKeyIDExtension=U7;var H7=class extends gh{constructor(e){super(e)}get signedCertificateTimestamps(){let e=this.extnValueObj.subs[0].value,r=new wLt.ByteStream(e),s=r.getUint16()+2,a=[];for(;r.position<s;){let n=r.getUint16(),c=r.getBlock(n);a.push(BLt.SignedCertificateTimestamp.parse(c))}if(r.position!==s)throw new Error(\"SCT list length does not match actual length\");return a}};oa.X509SCTExtension=H7});var HTe=L(sc=>{\"use strict\";var vLt=sc&&sc.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||(\"get\"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),SLt=sc&&sc.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,\"default\",{enumerable:!0,value:e})}:function(t,e){t.default=e}),UTe=sc&&sc.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!==\"default\"&&Object.prototype.hasOwnProperty.call(t,r)&&vLt(e,t,r);return SLt(e,t),e};Object.defineProperty(sc,\"__esModule\",{value:!0});sc.X509Certificate=sc.EXTENSION_OID_SCT=void 0;var DLt=uO(),_Te=UTe(Kw()),bLt=pO(),PLt=UTe(x7()),ry=j7(),xLt=\"2.5.29.14\",kLt=\"2.5.29.15\",QLt=\"2.5.29.17\",TLt=\"2.5.29.19\",RLt=\"2.5.29.35\";sc.EXTENSION_OID_SCT=\"1.3.6.1.4.1.11129.2.4.2\";var q7=class t{constructor(e){this.root=e}static parse(e){let r=typeof e==\"string\"?PLt.toDER(e):e,s=DLt.ASN1Obj.parseBuffer(r);return new t(s)}get tbsCertificate(){return this.tbsCertificateObj}get version(){return`v${(this.versionObj.subs[0].toInteger()+BigInt(1)).toString()}`}get serialNumber(){return this.serialNumberObj.value}get notBefore(){return this.validityObj.subs[0].toDate()}get notAfter(){return this.validityObj.subs[1].toDate()}get issuer(){return this.issuerObj.value}get subject(){return this.subjectObj.value}get publicKey(){return this.subjectPublicKeyInfoObj.toDER()}get signatureAlgorithm(){let e=this.signatureAlgorithmObj.subs[0].toOID();return bLt.ECDSA_SIGNATURE_ALGOS[e]}get signatureValue(){return this.signatureValueObj.value.subarray(1)}get subjectAltName(){let e=this.extSubjectAltName;return e?.uri||e?.rfc822Name}get extensions(){return this.extensionsObj?.subs[0]?.subs||[]}get extKeyUsage(){let e=this.findExtension(kLt);return e?new ry.X509KeyUsageExtension(e):void 0}get extBasicConstraints(){let e=this.findExtension(TLt);return e?new ry.X509BasicConstraintsExtension(e):void 0}get extSubjectAltName(){let e=this.findExtension(QLt);return e?new ry.X509SubjectAlternativeNameExtension(e):void 0}get extAuthorityKeyID(){let e=this.findExtension(RLt);return e?new ry.X509AuthorityKeyIDExtension(e):void 0}get extSubjectKeyID(){let e=this.findExtension(xLt);return e?new ry.X509SubjectKeyIDExtension(e):void 0}get extSCT(){let e=this.findExtension(sc.EXTENSION_OID_SCT);return e?new ry.X509SCTExtension(e):void 0}get isCA(){let e=this.extBasicConstraints?.isCA||!1;return this.extKeyUsage?e&&this.extKeyUsage.keyCertSign:e}extension(e){let r=this.findExtension(e);return r?new ry.X509Extension(r):void 0}verify(e){let r=e?.publicKey||this.publicKey,s=_Te.createPublicKey(r);return _Te.verify(this.tbsCertificate.toDER(),s,this.signatureValue,this.signatureAlgorithm)}validForDate(e){return this.notBefore<=e&&e<=this.notAfter}equals(e){return this.root.toDER().equals(e.root.toDER())}clone(){let e=this.root.toDER(),r=Buffer.alloc(e.length);return e.copy(r),t.parse(r)}findExtension(e){return this.extensions.find(r=>r.subs[0].toOID()===e)}get tbsCertificateObj(){return this.root.subs[0]}get signatureAlgorithmObj(){return this.root.subs[1]}get signatureValueObj(){return this.root.subs[2]}get versionObj(){return this.tbsCertificateObj.subs[0]}get serialNumberObj(){return this.tbsCertificateObj.subs[1]}get issuerObj(){return this.tbsCertificateObj.subs[3]}get validityObj(){return this.tbsCertificateObj.subs[4]}get subjectObj(){return this.tbsCertificateObj.subs[5]}get subjectPublicKeyInfoObj(){return this.tbsCertificateObj.subs[6]}get extensionsObj(){return this.tbsCertificateObj.subs.find(e=>e.tag.isContextSpecific(3))}};sc.X509Certificate=q7});var qTe=L(wg=>{\"use strict\";Object.defineProperty(wg,\"__esModule\",{value:!0});wg.X509SCTExtension=wg.X509Certificate=wg.EXTENSION_OID_SCT=void 0;var jTe=HTe();Object.defineProperty(wg,\"EXTENSION_OID_SCT\",{enumerable:!0,get:function(){return jTe.EXTENSION_OID_SCT}});Object.defineProperty(wg,\"X509Certificate\",{enumerable:!0,get:function(){return jTe.X509Certificate}});var FLt=j7();Object.defineProperty(wg,\"X509SCTExtension\",{enumerable:!0,get:function(){return FLt.X509SCTExtension}})});var wl=L(Kn=>{\"use strict\";var NLt=Kn&&Kn.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||(\"get\"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),OLt=Kn&&Kn.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,\"default\",{enumerable:!0,value:e})}:function(t,e){t.default=e}),Rb=Kn&&Kn.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!==\"default\"&&Object.prototype.hasOwnProperty.call(t,r)&&NLt(e,t,r);return OLt(e,t),e};Object.defineProperty(Kn,\"__esModule\",{value:!0});Kn.X509SCTExtension=Kn.X509Certificate=Kn.EXTENSION_OID_SCT=Kn.ByteStream=Kn.RFC3161Timestamp=Kn.pem=Kn.json=Kn.encoding=Kn.dsse=Kn.crypto=Kn.ASN1Obj=void 0;var LLt=uO();Object.defineProperty(Kn,\"ASN1Obj\",{enumerable:!0,get:function(){return LLt.ASN1Obj}});Kn.crypto=Rb(Kw());Kn.dsse=Rb(bTe());Kn.encoding=Rb(kTe());Kn.json=Rb(QTe());Kn.pem=Rb(x7());var MLt=OTe();Object.defineProperty(Kn,\"RFC3161Timestamp\",{enumerable:!0,get:function(){return MLt.RFC3161Timestamp}});var _Lt=kb();Object.defineProperty(Kn,\"ByteStream\",{enumerable:!0,get:function(){return _Lt.ByteStream}});var G7=qTe();Object.defineProperty(Kn,\"EXTENSION_OID_SCT\",{enumerable:!0,get:function(){return G7.EXTENSION_OID_SCT}});Object.defineProperty(Kn,\"X509Certificate\",{enumerable:!0,get:function(){return G7.X509Certificate}});Object.defineProperty(Kn,\"X509SCTExtension\",{enumerable:!0,get:function(){return G7.X509SCTExtension}})});var GTe=L(W7=>{\"use strict\";Object.defineProperty(W7,\"__esModule\",{value:!0});W7.extractJWTSubject=HLt;var ULt=wl();function HLt(t){let e=t.split(\".\",3),r=JSON.parse(ULt.encoding.base64Decode(e[1]));switch(r.iss){case\"https://accounts.google.com\":case\"https://oauth2.sigstore.dev/auth\":return r.email;default:return r.sub}}});var WTe=L((NIr,jLt)=>{jLt.exports={name:\"@sigstore/sign\",version:\"3.1.0\",description:\"Sigstore signing library\",main:\"dist/index.js\",types:\"dist/index.d.ts\",scripts:{clean:\"shx rm -rf dist *.tsbuildinfo\",build:\"tsc --build\",test:\"jest\"},files:[\"dist\"],author:\"bdehamer@github.com\",license:\"Apache-2.0\",repository:{type:\"git\",url:\"git+https://github.com/sigstore/sigstore-js.git\"},bugs:{url:\"https://github.com/sigstore/sigstore-js/issues\"},homepage:\"https://github.com/sigstore/sigstore-js/tree/main/packages/sign#readme\",publishConfig:{provenance:!0},devDependencies:{\"@sigstore/jest\":\"^0.0.0\",\"@sigstore/mock\":\"^0.10.0\",\"@sigstore/rekor-types\":\"^3.0.0\",\"@types/make-fetch-happen\":\"^10.0.4\",\"@types/promise-retry\":\"^1.1.6\"},dependencies:{\"@sigstore/bundle\":\"^3.1.0\",\"@sigstore/core\":\"^2.0.0\",\"@sigstore/protobuf-specs\":\"^0.4.0\",\"make-fetch-happen\":\"^14.0.2\",\"proc-log\":\"^5.0.0\",\"promise-retry\":\"^2.0.1\"},engines:{node:\"^18.17.0 || >=20.5.0\"}}});var VTe=L(zw=>{\"use strict\";var qLt=zw&&zw.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(zw,\"__esModule\",{value:!0});zw.getUserAgent=void 0;var YTe=qLt(Ie(\"os\")),GLt=()=>{let t=WTe().version,e=process.version,r=YTe.default.platform(),s=YTe.default.arch();return`sigstore-js/${t} (Node ${e}) (${r}/${s})`};zw.getUserAgent=GLt});var Bg=L(Ji=>{\"use strict\";var WLt=Ji&&Ji.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||(\"get\"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),YLt=Ji&&Ji.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,\"default\",{enumerable:!0,value:e})}:function(t,e){t.default=e}),KTe=Ji&&Ji.__importStar||function(){var t=function(e){return t=Object.getOwnPropertyNames||function(r){var s=[];for(var a in r)Object.prototype.hasOwnProperty.call(r,a)&&(s[s.length]=a);return s},t(e)};return function(e){if(e&&e.__esModule)return e;var r={};if(e!=null)for(var s=t(e),a=0;a<s.length;a++)s[a]!==\"default\"&&WLt(r,e,s[a]);return YLt(r,e),r}}();Object.defineProperty(Ji,\"__esModule\",{value:!0});Ji.ua=Ji.oidc=Ji.pem=Ji.json=Ji.encoding=Ji.dsse=Ji.crypto=void 0;var Fb=wl();Object.defineProperty(Ji,\"crypto\",{enumerable:!0,get:function(){return Fb.crypto}});Object.defineProperty(Ji,\"dsse\",{enumerable:!0,get:function(){return Fb.dsse}});Object.defineProperty(Ji,\"encoding\",{enumerable:!0,get:function(){return Fb.encoding}});Object.defineProperty(Ji,\"json\",{enumerable:!0,get:function(){return Fb.json}});Object.defineProperty(Ji,\"pem\",{enumerable:!0,get:function(){return Fb.pem}});Ji.oidc=KTe(GTe());Ji.ua=KTe(VTe())});var V7=L(dO=>{\"use strict\";Object.defineProperty(dO,\"__esModule\",{value:!0});dO.BaseBundleBuilder=void 0;var Y7=class{constructor(e){this.signer=e.signer,this.witnesses=e.witnesses}async create(e){let r=await this.prepare(e).then(f=>this.signer.sign(f)),s=await this.package(e,r),a=await Promise.all(this.witnesses.map(f=>f.testify(s.content,VLt(r.key)))),n=[],c=[];return a.forEach(({tlogEntries:f,rfc3161Timestamps:p})=>{n.push(...f??[]),c.push(...p??[])}),s.verificationMaterial.tlogEntries=n,s.verificationMaterial.timestampVerificationData={rfc3161Timestamps:c},s}async prepare(e){return e.data}};dO.BaseBundleBuilder=Y7;function VLt(t){switch(t.$case){case\"publicKey\":return t.publicKey;case\"x509Certificate\":return t.certificate}}});var J7=L(xA=>{\"use strict\";var KLt=xA&&xA.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||(\"get\"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),JLt=xA&&xA.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,\"default\",{enumerable:!0,value:e})}:function(t,e){t.default=e}),zLt=xA&&xA.__importStar||function(){var t=function(e){return t=Object.getOwnPropertyNames||function(r){var s=[];for(var a in r)Object.prototype.hasOwnProperty.call(r,a)&&(s[s.length]=a);return s},t(e)};return function(e){if(e&&e.__esModule)return e;var r={};if(e!=null)for(var s=t(e),a=0;a<s.length;a++)s[a]!==\"default\"&&KLt(r,e,s[a]);return JLt(r,e),r}}();Object.defineProperty(xA,\"__esModule\",{value:!0});xA.toMessageSignatureBundle=ZLt;xA.toDSSEBundle=XLt;var JTe=zLt(xb()),K7=Bg();function ZLt(t,e){let r=K7.crypto.digest(\"sha256\",t.data);return JTe.toMessageSignatureBundle({digest:r,signature:e.signature,certificate:e.key.$case===\"x509Certificate\"?K7.pem.toDER(e.key.certificate):void 0,keyHint:e.key.$case===\"publicKey\"?e.key.hint:void 0,certificateChain:!0})}function XLt(t,e,r){return JTe.toDSSEBundle({artifact:t.data,artifactType:t.type,signature:e.signature,certificate:e.key.$case===\"x509Certificate\"?K7.pem.toDER(e.key.certificate):void 0,keyHint:e.key.$case===\"publicKey\"?e.key.hint:void 0,certificateChain:r})}});var ZTe=L(mO=>{\"use strict\";Object.defineProperty(mO,\"__esModule\",{value:!0});mO.DSSEBundleBuilder=void 0;var $Lt=Bg(),eMt=V7(),tMt=J7(),z7=class extends eMt.BaseBundleBuilder{constructor(e){super(e),this.certificateChain=e.certificateChain??!1}async prepare(e){let r=zTe(e);return $Lt.dsse.preAuthEncoding(r.type,r.data)}async package(e,r){return(0,tMt.toDSSEBundle)(zTe(e),r,this.certificateChain)}};mO.DSSEBundleBuilder=z7;function zTe(t){return{...t,type:t.type??\"\"}}});var XTe=L(yO=>{\"use strict\";Object.defineProperty(yO,\"__esModule\",{value:!0});yO.MessageSignatureBundleBuilder=void 0;var rMt=V7(),nMt=J7(),Z7=class extends rMt.BaseBundleBuilder{constructor(e){super(e)}async package(e,r){return(0,nMt.toMessageSignatureBundle)(e,r)}};yO.MessageSignatureBundleBuilder=Z7});var $Te=L(Zw=>{\"use strict\";Object.defineProperty(Zw,\"__esModule\",{value:!0});Zw.MessageSignatureBundleBuilder=Zw.DSSEBundleBuilder=void 0;var iMt=ZTe();Object.defineProperty(Zw,\"DSSEBundleBuilder\",{enumerable:!0,get:function(){return iMt.DSSEBundleBuilder}});var sMt=XTe();Object.defineProperty(Zw,\"MessageSignatureBundleBuilder\",{enumerable:!0,get:function(){return sMt.MessageSignatureBundleBuilder}})});var IO=L(EO=>{\"use strict\";Object.defineProperty(EO,\"__esModule\",{value:!0});EO.HTTPError=void 0;var X7=class extends Error{constructor({status:e,message:r,location:s}){super(`(${e}) ${r}`),this.statusCode=e,this.location=s}};EO.HTTPError=X7});var Xw=L(Nb=>{\"use strict\";Object.defineProperty(Nb,\"__esModule\",{value:!0});Nb.InternalError=void 0;Nb.internalError=aMt;var oMt=IO(),CO=class extends Error{constructor({code:e,message:r,cause:s}){super(r),this.name=this.constructor.name,this.cause=s,this.code=e}};Nb.InternalError=CO;function aMt(t,e,r){throw t instanceof oMt.HTTPError&&(r+=` - ${t.message}`),new CO({code:e,message:r,cause:t})}});var wO=L((WIr,eRe)=>{eRe.exports=fetch});var tRe=L($w=>{\"use strict\";var lMt=$w&&$w.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty($w,\"__esModule\",{value:!0});$w.CIContextProvider=void 0;var cMt=lMt(wO()),uMt=[fMt,AMt],$7=class{constructor(e=\"sigstore\"){this.audience=e}async getToken(){return Promise.any(uMt.map(e=>e(this.audience))).catch(()=>Promise.reject(\"CI: no tokens available\"))}};$w.CIContextProvider=$7;async function fMt(t){if(!process.env.ACTIONS_ID_TOKEN_REQUEST_URL||!process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN)return Promise.reject(\"no token available\");let e=new URL(process.env.ACTIONS_ID_TOKEN_REQUEST_URL);return e.searchParams.append(\"audience\",t),(await(0,cMt.default)(e.href,{retry:2,headers:{Accept:\"application/json\",Authorization:`Bearer ${process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN}`}})).json().then(s=>s.value)}async function AMt(){return process.env.SIGSTORE_ID_TOKEN?process.env.SIGSTORE_ID_TOKEN:Promise.reject(\"no token available\")}});var rRe=L(BO=>{\"use strict\";Object.defineProperty(BO,\"__esModule\",{value:!0});BO.CIContextProvider=void 0;var pMt=tRe();Object.defineProperty(BO,\"CIContextProvider\",{enumerable:!0,get:function(){return pMt.CIContextProvider}})});var iRe=L((KIr,nRe)=>{var hMt=Symbol(\"proc-log.meta\");nRe.exports={META:hMt,output:{LEVELS:[\"standard\",\"error\",\"buffer\",\"flush\"],KEYS:{standard:\"standard\",error:\"error\",buffer:\"buffer\",flush:\"flush\"},standard:function(...t){return process.emit(\"output\",\"standard\",...t)},error:function(...t){return process.emit(\"output\",\"error\",...t)},buffer:function(...t){return process.emit(\"output\",\"buffer\",...t)},flush:function(...t){return process.emit(\"output\",\"flush\",...t)}},log:{LEVELS:[\"notice\",\"error\",\"warn\",\"info\",\"verbose\",\"http\",\"silly\",\"timing\",\"pause\",\"resume\"],KEYS:{notice:\"notice\",error:\"error\",warn:\"warn\",info:\"info\",verbose:\"verbose\",http:\"http\",silly:\"silly\",timing:\"timing\",pause:\"pause\",resume:\"resume\"},error:function(...t){return process.emit(\"log\",\"error\",...t)},notice:function(...t){return process.emit(\"log\",\"notice\",...t)},warn:function(...t){return process.emit(\"log\",\"warn\",...t)},info:function(...t){return process.emit(\"log\",\"info\",...t)},verbose:function(...t){return process.emit(\"log\",\"verbose\",...t)},http:function(...t){return process.emit(\"log\",\"http\",...t)},silly:function(...t){return process.emit(\"log\",\"silly\",...t)},timing:function(...t){return process.emit(\"log\",\"timing\",...t)},pause:function(){return process.emit(\"log\",\"pause\")},resume:function(){return process.emit(\"log\",\"resume\")}},time:{LEVELS:[\"start\",\"end\"],KEYS:{start:\"start\",end:\"end\"},start:function(t,e){process.emit(\"time\",\"start\",t);function r(){return process.emit(\"time\",\"end\",t)}if(typeof e==\"function\"){let s=e();return s&&s.finally?s.finally(r):(r(),s)}return r},end:function(t){return process.emit(\"time\",\"end\",t)}},input:{LEVELS:[\"start\",\"end\",\"read\"],KEYS:{start:\"start\",end:\"end\",read:\"read\"},start:function(t){process.emit(\"input\",\"start\");function e(){return process.emit(\"input\",\"end\")}if(typeof t==\"function\"){let r=t();return r&&r.finally?r.finally(e):(e(),r)}return e},end:function(){return process.emit(\"input\",\"end\")},read:function(...t){let e,r,s=new Promise((a,n)=>{e=a,r=n});return process.emit(\"input\",\"read\",e,r,...t),s}}}});var aRe=L((JIr,oRe)=>{\"use strict\";function sRe(t,e){for(let r in e)Object.defineProperty(t,r,{value:e[r],enumerable:!0,configurable:!0});return t}function gMt(t,e,r){if(!t||typeof t==\"string\")throw new TypeError(\"Please pass an Error to err-code\");r||(r={}),typeof e==\"object\"&&(r=e,e=void 0),e!=null&&(r.code=e);try{return sRe(t,r)}catch{r.message=t.message,r.stack=t.stack;let a=function(){};return a.prototype=Object.create(Object.getPrototypeOf(t)),sRe(new a,r)}}oRe.exports=gMt});var cRe=L((zIr,lRe)=>{function tu(t,e){typeof e==\"boolean\"&&(e={forever:e}),this._originalTimeouts=JSON.parse(JSON.stringify(t)),this._timeouts=t,this._options=e||{},this._maxRetryTime=e&&e.maxRetryTime||1/0,this._fn=null,this._errors=[],this._attempts=1,this._operationTimeout=null,this._operationTimeoutCb=null,this._timeout=null,this._operationStart=null,this._options.forever&&(this._cachedTimeouts=this._timeouts.slice(0))}lRe.exports=tu;tu.prototype.reset=function(){this._attempts=1,this._timeouts=this._originalTimeouts};tu.prototype.stop=function(){this._timeout&&clearTimeout(this._timeout),this._timeouts=[],this._cachedTimeouts=null};tu.prototype.retry=function(t){if(this._timeout&&clearTimeout(this._timeout),!t)return!1;var e=new Date().getTime();if(t&&e-this._operationStart>=this._maxRetryTime)return this._errors.unshift(new Error(\"RetryOperation timeout occurred\")),!1;this._errors.push(t);var r=this._timeouts.shift();if(r===void 0)if(this._cachedTimeouts)this._errors.splice(this._errors.length-1,this._errors.length),this._timeouts=this._cachedTimeouts.slice(0),r=this._timeouts.shift();else return!1;var s=this,a=setTimeout(function(){s._attempts++,s._operationTimeoutCb&&(s._timeout=setTimeout(function(){s._operationTimeoutCb(s._attempts)},s._operationTimeout),s._options.unref&&s._timeout.unref()),s._fn(s._attempts)},r);return this._options.unref&&a.unref(),!0};tu.prototype.attempt=function(t,e){this._fn=t,e&&(e.timeout&&(this._operationTimeout=e.timeout),e.cb&&(this._operationTimeoutCb=e.cb));var r=this;this._operationTimeoutCb&&(this._timeout=setTimeout(function(){r._operationTimeoutCb()},r._operationTimeout)),this._operationStart=new Date().getTime(),this._fn(this._attempts)};tu.prototype.try=function(t){console.log(\"Using RetryOperation.try() is deprecated\"),this.attempt(t)};tu.prototype.start=function(t){console.log(\"Using RetryOperation.start() is deprecated\"),this.attempt(t)};tu.prototype.start=tu.prototype.try;tu.prototype.errors=function(){return this._errors};tu.prototype.attempts=function(){return this._attempts};tu.prototype.mainError=function(){if(this._errors.length===0)return null;for(var t={},e=null,r=0,s=0;s<this._errors.length;s++){var a=this._errors[s],n=a.message,c=(t[n]||0)+1;t[n]=c,c>=r&&(e=a,r=c)}return e}});var uRe=L(ny=>{var dMt=cRe();ny.operation=function(t){var e=ny.timeouts(t);return new dMt(e,{forever:t&&t.forever,unref:t&&t.unref,maxRetryTime:t&&t.maxRetryTime})};ny.timeouts=function(t){if(t instanceof Array)return[].concat(t);var e={retries:10,factor:2,minTimeout:1*1e3,maxTimeout:1/0,randomize:!1};for(var r in t)e[r]=t[r];if(e.minTimeout>e.maxTimeout)throw new Error(\"minTimeout is greater than maxTimeout\");for(var s=[],a=0;a<e.retries;a++)s.push(this.createTimeout(a,e));return t&&t.forever&&!s.length&&s.push(this.createTimeout(a,e)),s.sort(function(n,c){return n-c}),s};ny.createTimeout=function(t,e){var r=e.randomize?Math.random()+1:1,s=Math.round(r*e.minTimeout*Math.pow(e.factor,t));return s=Math.min(s,e.maxTimeout),s};ny.wrap=function(t,e,r){if(e instanceof Array&&(r=e,e=null),!r){r=[];for(var s in t)typeof t[s]==\"function\"&&r.push(s)}for(var a=0;a<r.length;a++){var n=r[a],c=t[n];t[n]=function(p){var h=ny.operation(e),E=Array.prototype.slice.call(arguments,1),C=E.pop();E.push(function(S){h.retry(S)||(S&&(arguments[0]=h.mainError()),C.apply(this,arguments))}),h.attempt(function(){p.apply(t,E)})}.bind(t,c),t[n].options=e}}});var ARe=L((XIr,fRe)=>{fRe.exports=uRe()});var gRe=L(($Ir,hRe)=>{\"use strict\";var mMt=aRe(),yMt=ARe(),EMt=Object.prototype.hasOwnProperty;function pRe(t){return t&&t.code===\"EPROMISERETRY\"&&EMt.call(t,\"retried\")}function IMt(t,e){var r,s;return typeof t==\"object\"&&typeof e==\"function\"&&(r=e,e=t,t=r),s=yMt.operation(e),new Promise(function(a,n){s.attempt(function(c){Promise.resolve().then(function(){return t(function(f){throw pRe(f)&&(f=f.retried),mMt(new Error(\"Retrying\"),\"EPROMISERETRY\",{retried:f})},c)}).then(a,function(f){pRe(f)&&(f=f.retried,s.retry(f||new Error))||n(f)})})})}hRe.exports=IMt});var vO=L(Ob=>{\"use strict\";var mRe=Ob&&Ob.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(Ob,\"__esModule\",{value:!0});Ob.fetchWithRetry=TMt;var CMt=Ie(\"http2\"),wMt=mRe(wO()),dRe=iRe(),BMt=mRe(gRe()),vMt=Bg(),SMt=IO(),{HTTP2_HEADER_LOCATION:DMt,HTTP2_HEADER_CONTENT_TYPE:bMt,HTTP2_HEADER_USER_AGENT:PMt,HTTP_STATUS_INTERNAL_SERVER_ERROR:xMt,HTTP_STATUS_TOO_MANY_REQUESTS:kMt,HTTP_STATUS_REQUEST_TIMEOUT:QMt}=CMt.constants;async function TMt(t,e){return(0,BMt.default)(async(r,s)=>{let a=e.method||\"POST\",n={[PMt]:vMt.ua.getUserAgent(),...e.headers},c=await(0,wMt.default)(t,{method:a,headers:n,body:e.body,timeout:e.timeout,retry:!1}).catch(f=>(dRe.log.http(\"fetch\",`${a} ${t} attempt ${s} failed with ${f}`),r(f)));if(c.ok)return c;{let f=await RMt(c);if(dRe.log.http(\"fetch\",`${a} ${t} attempt ${s} failed with ${c.status}`),FMt(c.status))return r(f);throw f}},NMt(e.retry))}var RMt=async t=>{let e=t.statusText,r=t.headers.get(DMt)||void 0;if(t.headers.get(bMt)?.includes(\"application/json\"))try{e=(await t.json()).message||e}catch{}return new SMt.HTTPError({status:t.status,message:e,location:r})},FMt=t=>[QMt,kMt].includes(t)||t>=xMt,NMt=t=>typeof t==\"boolean\"?{retries:t?1:0}:typeof t==\"number\"?{retries:t}:{retries:0,...t}});var yRe=L(SO=>{\"use strict\";Object.defineProperty(SO,\"__esModule\",{value:!0});SO.Fulcio=void 0;var OMt=vO(),eK=class{constructor(e){this.options=e}async createSigningCertificate(e){let{baseURL:r,retry:s,timeout:a}=this.options,n=`${r}/api/v2/signingCert`;return(await(0,OMt.fetchWithRetry)(n,{headers:{\"Content-Type\":\"application/json\"},body:JSON.stringify(e),timeout:a,retry:s})).json()}};SO.Fulcio=eK});var ERe=L(DO=>{\"use strict\";Object.defineProperty(DO,\"__esModule\",{value:!0});DO.CAClient=void 0;var LMt=Xw(),MMt=yRe(),tK=class{constructor(e){this.fulcio=new MMt.Fulcio({baseURL:e.fulcioBaseURL,retry:e.retry,timeout:e.timeout})}async createSigningCertificate(e,r,s){let a=_Mt(e,r,s);try{let n=await this.fulcio.createSigningCertificate(a);return(n.signedCertificateEmbeddedSct?n.signedCertificateEmbeddedSct:n.signedCertificateDetachedSct).chain.certificates}catch(n){(0,LMt.internalError)(n,\"CA_CREATE_SIGNING_CERTIFICATE_ERROR\",\"error creating signing certificate\")}}};DO.CAClient=tK;function _Mt(t,e,r){return{credentials:{oidcIdentityToken:t},publicKeyRequest:{publicKey:{algorithm:\"ECDSA\",content:e},proofOfPossession:r.toString(\"base64\")}}}});var CRe=L(e1=>{\"use strict\";var UMt=e1&&e1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(e1,\"__esModule\",{value:!0});e1.EphemeralSigner=void 0;var IRe=UMt(Ie(\"crypto\")),HMt=\"ec\",jMt=\"P-256\",rK=class{constructor(){this.keypair=IRe.default.generateKeyPairSync(HMt,{namedCurve:jMt})}async sign(e){let r=IRe.default.sign(null,e,this.keypair.privateKey),s=this.keypair.publicKey.export({format:\"pem\",type:\"spki\"}).toString(\"ascii\");return{signature:r,key:{$case:\"publicKey\",publicKey:s}}}};e1.EphemeralSigner=rK});var wRe=L(iy=>{\"use strict\";Object.defineProperty(iy,\"__esModule\",{value:!0});iy.FulcioSigner=iy.DEFAULT_FULCIO_URL=void 0;var nK=Xw(),qMt=Bg(),GMt=ERe(),WMt=CRe();iy.DEFAULT_FULCIO_URL=\"https://fulcio.sigstore.dev\";var iK=class{constructor(e){this.ca=new GMt.CAClient({...e,fulcioBaseURL:e.fulcioBaseURL||iy.DEFAULT_FULCIO_URL}),this.identityProvider=e.identityProvider,this.keyHolder=e.keyHolder||new WMt.EphemeralSigner}async sign(e){let r=await this.getIdentityToken(),s;try{s=qMt.oidc.extractJWTSubject(r)}catch(f){throw new nK.InternalError({code:\"IDENTITY_TOKEN_PARSE_ERROR\",message:`invalid identity token: ${r}`,cause:f})}let a=await this.keyHolder.sign(Buffer.from(s));if(a.key.$case!==\"publicKey\")throw new nK.InternalError({code:\"CA_CREATE_SIGNING_CERTIFICATE_ERROR\",message:\"unexpected format for signing key\"});let n=await this.ca.createSigningCertificate(r,a.key.publicKey,a.signature);return{signature:(await this.keyHolder.sign(e)).signature,key:{$case:\"x509Certificate\",certificate:n[0]}}}async getIdentityToken(){try{return await this.identityProvider.getToken()}catch(e){throw new nK.InternalError({code:\"IDENTITY_TOKEN_READ_ERROR\",message:\"error retrieving identity token\",cause:e})}}};iy.FulcioSigner=iK});var vRe=L(t1=>{\"use strict\";Object.defineProperty(t1,\"__esModule\",{value:!0});t1.FulcioSigner=t1.DEFAULT_FULCIO_URL=void 0;var BRe=wRe();Object.defineProperty(t1,\"DEFAULT_FULCIO_URL\",{enumerable:!0,get:function(){return BRe.DEFAULT_FULCIO_URL}});Object.defineProperty(t1,\"FulcioSigner\",{enumerable:!0,get:function(){return BRe.FulcioSigner}})});var bRe=L(bO=>{\"use strict\";Object.defineProperty(bO,\"__esModule\",{value:!0});bO.Rekor=void 0;var SRe=vO(),sK=class{constructor(e){this.options=e}async createEntry(e){let{baseURL:r,timeout:s,retry:a}=this.options,n=`${r}/api/v1/log/entries`,f=await(await(0,SRe.fetchWithRetry)(n,{headers:{\"Content-Type\":\"application/json\",Accept:\"application/json\"},body:JSON.stringify(e),timeout:s,retry:a})).json();return DRe(f)}async getEntry(e){let{baseURL:r,timeout:s,retry:a}=this.options,n=`${r}/api/v1/log/entries/${e}`,f=await(await(0,SRe.fetchWithRetry)(n,{method:\"GET\",headers:{Accept:\"application/json\"},timeout:s,retry:a})).json();return DRe(f)}};bO.Rekor=sK;function DRe(t){let e=Object.entries(t);if(e.length!=1)throw new Error(\"Received multiple entries in Rekor response\");let[r,s]=e[0];return{...s,uuid:r}}});var xRe=L(PO=>{\"use strict\";Object.defineProperty(PO,\"__esModule\",{value:!0});PO.TLogClient=void 0;var PRe=Xw(),YMt=IO(),VMt=bRe(),oK=class{constructor(e){this.fetchOnConflict=e.fetchOnConflict??!1,this.rekor=new VMt.Rekor({baseURL:e.rekorBaseURL,retry:e.retry,timeout:e.timeout})}async createEntry(e){let r;try{r=await this.rekor.createEntry(e)}catch(s){if(KMt(s)&&this.fetchOnConflict){let a=s.location.split(\"/\").pop()||\"\";try{r=await this.rekor.getEntry(a)}catch(n){(0,PRe.internalError)(n,\"TLOG_FETCH_ENTRY_ERROR\",\"error fetching tlog entry\")}}else(0,PRe.internalError)(s,\"TLOG_CREATE_ENTRY_ERROR\",\"error creating tlog entry\")}return r}};PO.TLogClient=oK;function KMt(t){return t instanceof YMt.HTTPError&&t.statusCode===409&&t.location!==void 0}});var kRe=L(aK=>{\"use strict\";Object.defineProperty(aK,\"__esModule\",{value:!0});aK.toProposedEntry=zMt;var JMt=xb(),vg=Bg(),Lb=\"sha256\";function zMt(t,e,r=\"dsse\"){switch(t.$case){case\"dsseEnvelope\":return r===\"intoto\"?$Mt(t.dsseEnvelope,e):XMt(t.dsseEnvelope,e);case\"messageSignature\":return ZMt(t.messageSignature,e)}}function ZMt(t,e){let r=t.messageDigest.digest.toString(\"hex\"),s=t.signature.toString(\"base64\"),a=vg.encoding.base64Encode(e);return{apiVersion:\"0.0.1\",kind:\"hashedrekord\",spec:{data:{hash:{algorithm:Lb,value:r}},signature:{content:s,publicKey:{content:a}}}}}function XMt(t,e){let r=JSON.stringify((0,JMt.envelopeToJSON)(t)),s=vg.encoding.base64Encode(e);return{apiVersion:\"0.0.1\",kind:\"dsse\",spec:{proposedContent:{envelope:r,verifiers:[s]}}}}function $Mt(t,e){let r=vg.crypto.digest(Lb,t.payload).toString(\"hex\"),s=e_t(t,e),a=vg.encoding.base64Encode(t.payload.toString(\"base64\")),n=vg.encoding.base64Encode(t.signatures[0].sig.toString(\"base64\")),c=t.signatures[0].keyid,f=vg.encoding.base64Encode(e),p={payloadType:t.payloadType,payload:a,signatures:[{sig:n,publicKey:f}]};return c.length>0&&(p.signatures[0].keyid=c),{apiVersion:\"0.0.2\",kind:\"intoto\",spec:{content:{envelope:p,hash:{algorithm:Lb,value:s},payloadHash:{algorithm:Lb,value:r}}}}}function e_t(t,e){let r={payloadType:t.payloadType,payload:t.payload.toString(\"base64\"),signatures:[{sig:t.signatures[0].sig.toString(\"base64\"),publicKey:e}]};return t.signatures[0].keyid.length>0&&(r.signatures[0].keyid=t.signatures[0].keyid),vg.crypto.digest(Lb,vg.json.canonicalize(r)).toString(\"hex\")}});var QRe=L(sy=>{\"use strict\";Object.defineProperty(sy,\"__esModule\",{value:!0});sy.RekorWitness=sy.DEFAULT_REKOR_URL=void 0;var t_t=Bg(),r_t=xRe(),n_t=kRe();sy.DEFAULT_REKOR_URL=\"https://rekor.sigstore.dev\";var lK=class{constructor(e){this.entryType=e.entryType,this.tlog=new r_t.TLogClient({...e,rekorBaseURL:e.rekorBaseURL||sy.DEFAULT_REKOR_URL})}async testify(e,r){let s=(0,n_t.toProposedEntry)(e,r,this.entryType),a=await this.tlog.createEntry(s);return i_t(a)}};sy.RekorWitness=lK;function i_t(t){let e=Buffer.from(t.logID,\"hex\"),r=t_t.encoding.base64Decode(t.body),s=JSON.parse(r),a=t?.verification?.signedEntryTimestamp?s_t(t.verification.signedEntryTimestamp):void 0,n=t?.verification?.inclusionProof?o_t(t.verification.inclusionProof):void 0;return{tlogEntries:[{logIndex:t.logIndex.toString(),logId:{keyId:e},integratedTime:t.integratedTime.toString(),kindVersion:{kind:s.kind,version:s.apiVersion},inclusionPromise:a,inclusionProof:n,canonicalizedBody:Buffer.from(t.body,\"base64\")}]}}function s_t(t){return{signedEntryTimestamp:Buffer.from(t,\"base64\")}}function o_t(t){return{logIndex:t.logIndex.toString(),treeSize:t.treeSize.toString(),rootHash:Buffer.from(t.rootHash,\"hex\"),hashes:t.hashes.map(e=>Buffer.from(e,\"hex\")),checkpoint:{envelope:t.checkpoint}}}});var TRe=L(xO=>{\"use strict\";Object.defineProperty(xO,\"__esModule\",{value:!0});xO.TimestampAuthority=void 0;var a_t=vO(),cK=class{constructor(e){this.options=e}async createTimestamp(e){let{baseURL:r,timeout:s,retry:a}=this.options,n=`${r}/api/v1/timestamp`;return(await(0,a_t.fetchWithRetry)(n,{headers:{\"Content-Type\":\"application/json\"},body:JSON.stringify(e),timeout:s,retry:a})).buffer()}};xO.TimestampAuthority=cK});var FRe=L(kO=>{\"use strict\";Object.defineProperty(kO,\"__esModule\",{value:!0});kO.TSAClient=void 0;var l_t=Xw(),c_t=TRe(),u_t=Bg(),RRe=\"sha256\",uK=class{constructor(e){this.tsa=new c_t.TimestampAuthority({baseURL:e.tsaBaseURL,retry:e.retry,timeout:e.timeout})}async createTimestamp(e){let r={artifactHash:u_t.crypto.digest(RRe,e).toString(\"base64\"),hashAlgorithm:RRe};try{return await this.tsa.createTimestamp(r)}catch(s){(0,l_t.internalError)(s,\"TSA_CREATE_TIMESTAMP_ERROR\",\"error creating timestamp\")}}};kO.TSAClient=uK});var NRe=L(QO=>{\"use strict\";Object.defineProperty(QO,\"__esModule\",{value:!0});QO.TSAWitness=void 0;var f_t=FRe(),fK=class{constructor(e){this.tsa=new f_t.TSAClient({tsaBaseURL:e.tsaBaseURL,retry:e.retry,timeout:e.timeout})}async testify(e){let r=A_t(e);return{rfc3161Timestamps:[{signedTimestamp:await this.tsa.createTimestamp(r)}]}}};QO.TSAWitness=fK;function A_t(t){switch(t.$case){case\"dsseEnvelope\":return t.dsseEnvelope.signatures[0].sig;case\"messageSignature\":return t.messageSignature.signature}}});var LRe=L(Sg=>{\"use strict\";Object.defineProperty(Sg,\"__esModule\",{value:!0});Sg.TSAWitness=Sg.RekorWitness=Sg.DEFAULT_REKOR_URL=void 0;var ORe=QRe();Object.defineProperty(Sg,\"DEFAULT_REKOR_URL\",{enumerable:!0,get:function(){return ORe.DEFAULT_REKOR_URL}});Object.defineProperty(Sg,\"RekorWitness\",{enumerable:!0,get:function(){return ORe.RekorWitness}});var p_t=NRe();Object.defineProperty(Sg,\"TSAWitness\",{enumerable:!0,get:function(){return p_t.TSAWitness}})});var pK=L(Is=>{\"use strict\";Object.defineProperty(Is,\"__esModule\",{value:!0});Is.TSAWitness=Is.RekorWitness=Is.DEFAULT_REKOR_URL=Is.FulcioSigner=Is.DEFAULT_FULCIO_URL=Is.CIContextProvider=Is.InternalError=Is.MessageSignatureBundleBuilder=Is.DSSEBundleBuilder=void 0;var MRe=$Te();Object.defineProperty(Is,\"DSSEBundleBuilder\",{enumerable:!0,get:function(){return MRe.DSSEBundleBuilder}});Object.defineProperty(Is,\"MessageSignatureBundleBuilder\",{enumerable:!0,get:function(){return MRe.MessageSignatureBundleBuilder}});var h_t=Xw();Object.defineProperty(Is,\"InternalError\",{enumerable:!0,get:function(){return h_t.InternalError}});var g_t=rRe();Object.defineProperty(Is,\"CIContextProvider\",{enumerable:!0,get:function(){return g_t.CIContextProvider}});var _Re=vRe();Object.defineProperty(Is,\"DEFAULT_FULCIO_URL\",{enumerable:!0,get:function(){return _Re.DEFAULT_FULCIO_URL}});Object.defineProperty(Is,\"FulcioSigner\",{enumerable:!0,get:function(){return _Re.FulcioSigner}});var AK=LRe();Object.defineProperty(Is,\"DEFAULT_REKOR_URL\",{enumerable:!0,get:function(){return AK.DEFAULT_REKOR_URL}});Object.defineProperty(Is,\"RekorWitness\",{enumerable:!0,get:function(){return AK.RekorWitness}});Object.defineProperty(Is,\"TSAWitness\",{enumerable:!0,get:function(){return AK.TSAWitness}})});var HRe=L(Mb=>{\"use strict\";var URe=Mb&&Mb.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(Mb,\"__esModule\",{value:!0});Mb.appDataPath=m_t;var d_t=URe(Ie(\"os\")),r1=URe(Ie(\"path\"));function m_t(t){let e=d_t.default.homedir();switch(process.platform){case\"darwin\":{let r=r1.default.join(e,\"Library\",\"Application Support\");return r1.default.join(r,t)}case\"win32\":{let r=process.env.LOCALAPPDATA||r1.default.join(e,\"AppData\",\"Local\");return r1.default.join(r,t,\"Data\")}default:{let r=process.env.XDG_DATA_HOME||r1.default.join(e,\".local\",\"share\");return r1.default.join(r,t)}}}});var kA=L(Bl=>{\"use strict\";Object.defineProperty(Bl,\"__esModule\",{value:!0});Bl.UnsupportedAlgorithmError=Bl.CryptoError=Bl.LengthOrHashMismatchError=Bl.UnsignedMetadataError=Bl.RepositoryError=Bl.ValueError=void 0;var hK=class extends Error{};Bl.ValueError=hK;var _b=class extends Error{};Bl.RepositoryError=_b;var gK=class extends _b{};Bl.UnsignedMetadataError=gK;var dK=class extends _b{};Bl.LengthOrHashMismatchError=dK;var TO=class extends Error{};Bl.CryptoError=TO;var mK=class extends TO{};Bl.UnsupportedAlgorithmError=mK});var qRe=L(Dg=>{\"use strict\";Object.defineProperty(Dg,\"__esModule\",{value:!0});Dg.isDefined=y_t;Dg.isObject=jRe;Dg.isStringArray=E_t;Dg.isObjectArray=I_t;Dg.isStringRecord=C_t;Dg.isObjectRecord=w_t;function y_t(t){return t!==void 0}function jRe(t){return typeof t==\"object\"&&t!==null}function E_t(t){return Array.isArray(t)&&t.every(e=>typeof e==\"string\")}function I_t(t){return Array.isArray(t)&&t.every(jRe)}function C_t(t){return typeof t==\"object\"&&t!==null&&Object.keys(t).every(e=>typeof e==\"string\")&&Object.values(t).every(e=>typeof e==\"string\")}function w_t(t){return typeof t==\"object\"&&t!==null&&Object.keys(t).every(e=>typeof e==\"string\")&&Object.values(t).every(e=>typeof e==\"object\"&&e!==null)}});var EK=L((yCr,YRe)=>{var GRe=\",\",B_t=\":\",v_t=\"[\",S_t=\"]\",D_t=\"{\",b_t=\"}\";function yK(t){let e=[];if(typeof t==\"string\")e.push(WRe(t));else if(typeof t==\"boolean\")e.push(JSON.stringify(t));else if(Number.isInteger(t))e.push(JSON.stringify(t));else if(t===null)e.push(JSON.stringify(t));else if(Array.isArray(t)){e.push(v_t);let r=!0;t.forEach(s=>{r||e.push(GRe),r=!1,e.push(yK(s))}),e.push(S_t)}else if(typeof t==\"object\"){e.push(D_t);let r=!0;Object.keys(t).sort().forEach(s=>{r||e.push(GRe),r=!1,e.push(WRe(s)),e.push(B_t),e.push(yK(t[s]))}),e.push(b_t)}else throw new TypeError(\"cannot encode \"+t.toString());return e.join(\"\")}function WRe(t){return'\"'+t.replace(/\\\\/g,\"\\\\\\\\\").replace(/\"/g,'\\\\\"')+'\"'}YRe.exports={canonicalize:yK}});var VRe=L(n1=>{\"use strict\";var P_t=n1&&n1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(n1,\"__esModule\",{value:!0});n1.verifySignature=void 0;var x_t=EK(),k_t=P_t(Ie(\"crypto\")),Q_t=(t,e,r)=>{let s=Buffer.from((0,x_t.canonicalize)(t));return k_t.default.verify(void 0,s,e,Buffer.from(r,\"hex\"))};n1.verifySignature=Q_t});var Af=L(ru=>{\"use strict\";var T_t=ru&&ru.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||(\"get\"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),R_t=ru&&ru.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,\"default\",{enumerable:!0,value:e})}:function(t,e){t.default=e}),KRe=ru&&ru.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!==\"default\"&&Object.prototype.hasOwnProperty.call(t,r)&&T_t(e,t,r);return R_t(e,t),e};Object.defineProperty(ru,\"__esModule\",{value:!0});ru.crypto=ru.guard=void 0;ru.guard=KRe(qRe());ru.crypto=KRe(VRe())});var oy=L(dh=>{\"use strict\";var F_t=dh&&dh.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(dh,\"__esModule\",{value:!0});dh.Signed=dh.MetadataKind=void 0;dh.isMetadataKind=O_t;var N_t=F_t(Ie(\"util\")),Ub=kA(),IK=Af(),JRe=[\"1\",\"0\",\"31\"],CK;(function(t){t.Root=\"root\",t.Timestamp=\"timestamp\",t.Snapshot=\"snapshot\",t.Targets=\"targets\"})(CK||(dh.MetadataKind=CK={}));function O_t(t){return typeof t==\"string\"&&Object.values(CK).includes(t)}var wK=class t{constructor(e){this.specVersion=e.specVersion||JRe.join(\".\");let r=this.specVersion.split(\".\");if(!(r.length===2||r.length===3)||!r.every(s=>L_t(s)))throw new Ub.ValueError(\"Failed to parse specVersion\");if(r[0]!=JRe[0])throw new Ub.ValueError(\"Unsupported specVersion\");this.expires=e.expires,this.version=e.version,this.unrecognizedFields=e.unrecognizedFields||{}}equals(e){return e instanceof t?this.specVersion===e.specVersion&&this.expires===e.expires&&this.version===e.version&&N_t.default.isDeepStrictEqual(this.unrecognizedFields,e.unrecognizedFields):!1}isExpired(e){return e||(e=new Date),e>=new Date(this.expires)}static commonFieldsFromJSON(e){let{spec_version:r,expires:s,version:a,...n}=e;if(IK.guard.isDefined(r)){if(typeof r!=\"string\")throw new TypeError(\"spec_version must be a string\")}else throw new Ub.ValueError(\"spec_version is not defined\");if(IK.guard.isDefined(s)){if(typeof s!=\"string\")throw new TypeError(\"expires must be a string\")}else throw new Ub.ValueError(\"expires is not defined\");if(IK.guard.isDefined(a)){if(typeof a!=\"number\")throw new TypeError(\"version must be a number\")}else throw new Ub.ValueError(\"version is not defined\");return{specVersion:r,expires:s,version:a,unrecognizedFields:n}}};dh.Signed=wK;function L_t(t){return!isNaN(Number(t))}});var Hb=L(Pg=>{\"use strict\";var zRe=Pg&&Pg.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(Pg,\"__esModule\",{value:!0});Pg.TargetFile=Pg.MetaFile=void 0;var ZRe=zRe(Ie(\"crypto\")),FO=zRe(Ie(\"util\")),bg=kA(),RO=Af(),BK=class t{constructor(e){if(e.version<=0)throw new bg.ValueError(\"Metafile version must be at least 1\");e.length!==void 0&&XRe(e.length),this.version=e.version,this.length=e.length,this.hashes=e.hashes,this.unrecognizedFields=e.unrecognizedFields||{}}equals(e){return e instanceof t?this.version===e.version&&this.length===e.length&&FO.default.isDeepStrictEqual(this.hashes,e.hashes)&&FO.default.isDeepStrictEqual(this.unrecognizedFields,e.unrecognizedFields):!1}verify(e){if(this.length!==void 0&&e.length!==this.length)throw new bg.LengthOrHashMismatchError(`Expected length ${this.length} but got ${e.length}`);this.hashes&&Object.entries(this.hashes).forEach(([r,s])=>{let a;try{a=ZRe.default.createHash(r)}catch{throw new bg.LengthOrHashMismatchError(`Hash algorithm ${r} not supported`)}let n=a.update(e).digest(\"hex\");if(n!==s)throw new bg.LengthOrHashMismatchError(`Expected hash ${s} but got ${n}`)})}toJSON(){let e={version:this.version,...this.unrecognizedFields};return this.length!==void 0&&(e.length=this.length),this.hashes&&(e.hashes=this.hashes),e}static fromJSON(e){let{version:r,length:s,hashes:a,...n}=e;if(typeof r!=\"number\")throw new TypeError(\"version must be a number\");if(RO.guard.isDefined(s)&&typeof s!=\"number\")throw new TypeError(\"length must be a number\");if(RO.guard.isDefined(a)&&!RO.guard.isStringRecord(a))throw new TypeError(\"hashes must be string keys and values\");return new t({version:r,length:s,hashes:a,unrecognizedFields:n})}};Pg.MetaFile=BK;var vK=class t{constructor(e){XRe(e.length),this.length=e.length,this.path=e.path,this.hashes=e.hashes,this.unrecognizedFields=e.unrecognizedFields||{}}get custom(){let e=this.unrecognizedFields.custom;return!e||Array.isArray(e)||typeof e!=\"object\"?{}:e}equals(e){return e instanceof t?this.length===e.length&&this.path===e.path&&FO.default.isDeepStrictEqual(this.hashes,e.hashes)&&FO.default.isDeepStrictEqual(this.unrecognizedFields,e.unrecognizedFields):!1}async verify(e){let r=0,s=Object.keys(this.hashes).reduce((a,n)=>{try{a[n]=ZRe.default.createHash(n)}catch{throw new bg.LengthOrHashMismatchError(`Hash algorithm ${n} not supported`)}return a},{});for await(let a of e)r+=a.length,Object.values(s).forEach(n=>{n.update(a)});if(r!==this.length)throw new bg.LengthOrHashMismatchError(`Expected length ${this.length} but got ${r}`);Object.entries(s).forEach(([a,n])=>{let c=this.hashes[a],f=n.digest(\"hex\");if(f!==c)throw new bg.LengthOrHashMismatchError(`Expected hash ${c} but got ${f}`)})}toJSON(){return{length:this.length,hashes:this.hashes,...this.unrecognizedFields}}static fromJSON(e,r){let{length:s,hashes:a,...n}=r;if(typeof s!=\"number\")throw new TypeError(\"length must be a number\");if(!RO.guard.isStringRecord(a))throw new TypeError(\"hashes must have string keys and values\");return new t({length:s,path:e,hashes:a,unrecognizedFields:n})}};Pg.TargetFile=vK;function XRe(t){if(t<0)throw new bg.ValueError(\"Length must be at least 0\")}});var $Re=L(SK=>{\"use strict\";Object.defineProperty(SK,\"__esModule\",{value:!0});SK.encodeOIDString=__t;var M_t=6;function __t(t){let e=t.split(\".\"),r=parseInt(e[0],10)*40+parseInt(e[1],10),s=[];e.slice(2).forEach(n=>{let c=U_t(parseInt(n,10));s.push(...c)});let a=Buffer.from([r,...s]);return Buffer.from([M_t,a.length,...a])}function U_t(t){let e=[],r=0;for(;t>0;)e.unshift(t&127|r),t>>=7,r=128;return e}});var nFe=L(qb=>{\"use strict\";var H_t=qb&&qb.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(qb,\"__esModule\",{value:!0});qb.getPublicKey=W_t;var i1=H_t(Ie(\"crypto\")),jb=kA(),DK=$Re(),NO=48,eFe=3,tFe=0,j_t=\"1.3.101.112\",q_t=\"1.2.840.10045.2.1\",G_t=\"1.2.840.10045.3.1.7\",bK=\"-----BEGIN PUBLIC KEY-----\";function W_t(t){switch(t.keyType){case\"rsa\":return Y_t(t);case\"ed25519\":return V_t(t);case\"ecdsa\":case\"ecdsa-sha2-nistp256\":case\"ecdsa-sha2-nistp384\":return K_t(t);default:throw new jb.UnsupportedAlgorithmError(`Unsupported key type: ${t.keyType}`)}}function Y_t(t){if(!t.keyVal.startsWith(bK))throw new jb.CryptoError(\"Invalid key format\");let e=i1.default.createPublicKey(t.keyVal);switch(t.scheme){case\"rsassa-pss-sha256\":return{key:e,padding:i1.default.constants.RSA_PKCS1_PSS_PADDING};default:throw new jb.UnsupportedAlgorithmError(`Unsupported RSA scheme: ${t.scheme}`)}}function V_t(t){let e;if(t.keyVal.startsWith(bK))e=i1.default.createPublicKey(t.keyVal);else{if(!rFe(t.keyVal))throw new jb.CryptoError(\"Invalid key format\");e=i1.default.createPublicKey({key:J_t.hexToDER(t.keyVal),format:\"der\",type:\"spki\"})}return{key:e}}function K_t(t){let e;if(t.keyVal.startsWith(bK))e=i1.default.createPublicKey(t.keyVal);else{if(!rFe(t.keyVal))throw new jb.CryptoError(\"Invalid key format\");e=i1.default.createPublicKey({key:z_t.hexToDER(t.keyVal),format:\"der\",type:\"spki\"})}return{key:e}}var J_t={hexToDER:t=>{let e=Buffer.from(t,\"hex\"),r=(0,DK.encodeOIDString)(j_t),s=Buffer.concat([Buffer.concat([Buffer.from([NO]),Buffer.from([r.length]),r]),Buffer.concat([Buffer.from([eFe]),Buffer.from([e.length+1]),Buffer.from([tFe]),e])]);return Buffer.concat([Buffer.from([NO]),Buffer.from([s.length]),s])}},z_t={hexToDER:t=>{let e=Buffer.from(t,\"hex\"),r=Buffer.concat([Buffer.from([eFe]),Buffer.from([e.length+1]),Buffer.from([tFe]),e]),s=Buffer.concat([(0,DK.encodeOIDString)(q_t),(0,DK.encodeOIDString)(G_t)]),a=Buffer.concat([Buffer.from([NO]),Buffer.from([s.length]),s]);return Buffer.concat([Buffer.from([NO]),Buffer.from([a.length+r.length]),a,r])}},rFe=t=>/^[0-9a-fA-F]+$/.test(t)});var OO=L(s1=>{\"use strict\";var Z_t=s1&&s1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(s1,\"__esModule\",{value:!0});s1.Key=void 0;var iFe=Z_t(Ie(\"util\")),Gb=kA(),sFe=Af(),X_t=nFe(),PK=class t{constructor(e){let{keyID:r,keyType:s,scheme:a,keyVal:n,unrecognizedFields:c}=e;this.keyID=r,this.keyType=s,this.scheme=a,this.keyVal=n,this.unrecognizedFields=c||{}}verifySignature(e){let r=e.signatures[this.keyID];if(!r)throw new Gb.UnsignedMetadataError(\"no signature for key found in metadata\");if(!this.keyVal.public)throw new Gb.UnsignedMetadataError(\"no public key found\");let s=(0,X_t.getPublicKey)({keyType:this.keyType,scheme:this.scheme,keyVal:this.keyVal.public}),a=e.signed.toJSON();try{if(!sFe.crypto.verifySignature(a,s,r.sig))throw new Gb.UnsignedMetadataError(`failed to verify ${this.keyID} signature`)}catch(n){throw n instanceof Gb.UnsignedMetadataError?n:new Gb.UnsignedMetadataError(`failed to verify ${this.keyID} signature`)}}equals(e){return e instanceof t?this.keyID===e.keyID&&this.keyType===e.keyType&&this.scheme===e.scheme&&iFe.default.isDeepStrictEqual(this.keyVal,e.keyVal)&&iFe.default.isDeepStrictEqual(this.unrecognizedFields,e.unrecognizedFields):!1}toJSON(){return{keytype:this.keyType,scheme:this.scheme,keyval:this.keyVal,...this.unrecognizedFields}}static fromJSON(e,r){let{keytype:s,scheme:a,keyval:n,...c}=r;if(typeof s!=\"string\")throw new TypeError(\"keytype must be a string\");if(typeof a!=\"string\")throw new TypeError(\"scheme must be a string\");if(!sFe.guard.isStringRecord(n))throw new TypeError(\"keyval must be a string record\");return new t({keyID:e,keyType:s,scheme:a,keyVal:n,unrecognizedFields:c})}};s1.Key=PK});var uFe=L((DCr,cFe)=>{\"use strict\";cFe.exports=aFe;function aFe(t,e,r){t instanceof RegExp&&(t=oFe(t,r)),e instanceof RegExp&&(e=oFe(e,r));var s=lFe(t,e,r);return s&&{start:s[0],end:s[1],pre:r.slice(0,s[0]),body:r.slice(s[0]+t.length,s[1]),post:r.slice(s[1]+e.length)}}function oFe(t,e){var r=e.match(t);return r?r[0]:null}aFe.range=lFe;function lFe(t,e,r){var s,a,n,c,f,p=r.indexOf(t),h=r.indexOf(e,p+1),E=p;if(p>=0&&h>0){for(s=[],n=r.length;E>=0&&!f;)E==p?(s.push(E),p=r.indexOf(t,E+1)):s.length==1?f=[s.pop(),h]:(a=s.pop(),a<n&&(n=a,c=h),h=r.indexOf(e,E+1)),E=p<h&&p>=0?p:h;s.length&&(f=[n,c])}return f}});var yFe=L((bCr,mFe)=>{var fFe=uFe();mFe.exports=tUt;var AFe=\"\\0SLASH\"+Math.random()+\"\\0\",pFe=\"\\0OPEN\"+Math.random()+\"\\0\",kK=\"\\0CLOSE\"+Math.random()+\"\\0\",hFe=\"\\0COMMA\"+Math.random()+\"\\0\",gFe=\"\\0PERIOD\"+Math.random()+\"\\0\";function xK(t){return parseInt(t,10)==t?parseInt(t,10):t.charCodeAt(0)}function $_t(t){return t.split(\"\\\\\\\\\").join(AFe).split(\"\\\\{\").join(pFe).split(\"\\\\}\").join(kK).split(\"\\\\,\").join(hFe).split(\"\\\\.\").join(gFe)}function eUt(t){return t.split(AFe).join(\"\\\\\").split(pFe).join(\"{\").split(kK).join(\"}\").split(hFe).join(\",\").split(gFe).join(\".\")}function dFe(t){if(!t)return[\"\"];var e=[],r=fFe(\"{\",\"}\",t);if(!r)return t.split(\",\");var s=r.pre,a=r.body,n=r.post,c=s.split(\",\");c[c.length-1]+=\"{\"+a+\"}\";var f=dFe(n);return n.length&&(c[c.length-1]+=f.shift(),c.push.apply(c,f)),e.push.apply(e,c),e}function tUt(t){return t?(t.substr(0,2)===\"{}\"&&(t=\"\\\\{\\\\}\"+t.substr(2)),Wb($_t(t),!0).map(eUt)):[]}function rUt(t){return\"{\"+t+\"}\"}function nUt(t){return/^-?0\\d/.test(t)}function iUt(t,e){return t<=e}function sUt(t,e){return t>=e}function Wb(t,e){var r=[],s=fFe(\"{\",\"}\",t);if(!s)return[t];var a=s.pre,n=s.post.length?Wb(s.post,!1):[\"\"];if(/\\$$/.test(s.pre))for(var c=0;c<n.length;c++){var f=a+\"{\"+s.body+\"}\"+n[c];r.push(f)}else{var p=/^-?\\d+\\.\\.-?\\d+(?:\\.\\.-?\\d+)?$/.test(s.body),h=/^[a-zA-Z]\\.\\.[a-zA-Z](?:\\.\\.-?\\d+)?$/.test(s.body),E=p||h,C=s.body.indexOf(\",\")>=0;if(!E&&!C)return s.post.match(/,.*\\}/)?(t=s.pre+\"{\"+s.body+kK+s.post,Wb(t)):[t];var S;if(E)S=s.body.split(/\\.\\./);else if(S=dFe(s.body),S.length===1&&(S=Wb(S[0],!1).map(rUt),S.length===1))return n.map(function(Ce){return s.pre+S[0]+Ce});var P;if(E){var I=xK(S[0]),R=xK(S[1]),N=Math.max(S[0].length,S[1].length),U=S.length==3?Math.abs(xK(S[2])):1,W=iUt,te=R<I;te&&(U*=-1,W=sUt);var ie=S.some(nUt);P=[];for(var Ae=I;W(Ae,R);Ae+=U){var ce;if(h)ce=String.fromCharCode(Ae),ce===\"\\\\\"&&(ce=\"\");else if(ce=String(Ae),ie){var me=N-ce.length;if(me>0){var pe=new Array(me+1).join(\"0\");Ae<0?ce=\"-\"+pe+ce.slice(1):ce=pe+ce}}P.push(ce)}}else{P=[];for(var Be=0;Be<S.length;Be++)P.push.apply(P,Wb(S[Be],!1))}for(var Be=0;Be<P.length;Be++)for(var c=0;c<n.length;c++){var f=a+P[Be]+n[c];(!e||E||f)&&r.push(f)}}return r}});var EFe=L(LO=>{\"use strict\";Object.defineProperty(LO,\"__esModule\",{value:!0});LO.assertValidPattern=void 0;var oUt=1024*64,aUt=t=>{if(typeof t!=\"string\")throw new TypeError(\"invalid pattern\");if(t.length>oUt)throw new TypeError(\"pattern is too long\")};LO.assertValidPattern=aUt});var CFe=L(MO=>{\"use strict\";Object.defineProperty(MO,\"__esModule\",{value:!0});MO.parseClass=void 0;var lUt={\"[:alnum:]\":[\"\\\\p{L}\\\\p{Nl}\\\\p{Nd}\",!0],\"[:alpha:]\":[\"\\\\p{L}\\\\p{Nl}\",!0],\"[:ascii:]\":[\"\\\\x00-\\\\x7f\",!1],\"[:blank:]\":[\"\\\\p{Zs}\\\\t\",!0],\"[:cntrl:]\":[\"\\\\p{Cc}\",!0],\"[:digit:]\":[\"\\\\p{Nd}\",!0],\"[:graph:]\":[\"\\\\p{Z}\\\\p{C}\",!0,!0],\"[:lower:]\":[\"\\\\p{Ll}\",!0],\"[:print:]\":[\"\\\\p{C}\",!0],\"[:punct:]\":[\"\\\\p{P}\",!0],\"[:space:]\":[\"\\\\p{Z}\\\\t\\\\r\\\\n\\\\v\\\\f\",!0],\"[:upper:]\":[\"\\\\p{Lu}\",!0],\"[:word:]\":[\"\\\\p{L}\\\\p{Nl}\\\\p{Nd}\\\\p{Pc}\",!0],\"[:xdigit:]\":[\"A-Fa-f0-9\",!1]},Yb=t=>t.replace(/[[\\]\\\\-]/g,\"\\\\$&\"),cUt=t=>t.replace(/[-[\\]{}()*+?.,\\\\^$|#\\s]/g,\"\\\\$&\"),IFe=t=>t.join(\"\"),uUt=(t,e)=>{let r=e;if(t.charAt(r)!==\"[\")throw new Error(\"not in a brace expression\");let s=[],a=[],n=r+1,c=!1,f=!1,p=!1,h=!1,E=r,C=\"\";e:for(;n<t.length;){let R=t.charAt(n);if((R===\"!\"||R===\"^\")&&n===r+1){h=!0,n++;continue}if(R===\"]\"&&c&&!p){E=n+1;break}if(c=!0,R===\"\\\\\"&&!p){p=!0,n++;continue}if(R===\"[\"&&!p){for(let[N,[U,W,te]]of Object.entries(lUt))if(t.startsWith(N,n)){if(C)return[\"$.\",!1,t.length-r,!0];n+=N.length,te?a.push(U):s.push(U),f=f||W;continue e}}if(p=!1,C){R>C?s.push(Yb(C)+\"-\"+Yb(R)):R===C&&s.push(Yb(R)),C=\"\",n++;continue}if(t.startsWith(\"-]\",n+1)){s.push(Yb(R+\"-\")),n+=2;continue}if(t.startsWith(\"-\",n+1)){C=R,n+=2;continue}s.push(Yb(R)),n++}if(E<n)return[\"\",!1,0,!1];if(!s.length&&!a.length)return[\"$.\",!1,t.length-r,!0];if(a.length===0&&s.length===1&&/^\\\\?.$/.test(s[0])&&!h){let R=s[0].length===2?s[0].slice(-1):s[0];return[cUt(R),!1,E-r,!1]}let S=\"[\"+(h?\"^\":\"\")+IFe(s)+\"]\",P=\"[\"+(h?\"\":\"^\")+IFe(a)+\"]\";return[s.length&&a.length?\"(\"+S+\"|\"+P+\")\":s.length?S:P,f,E-r,!0]};MO.parseClass=uUt});var UO=L(_O=>{\"use strict\";Object.defineProperty(_O,\"__esModule\",{value:!0});_O.unescape=void 0;var fUt=(t,{windowsPathsNoEscape:e=!1}={})=>e?t.replace(/\\[([^\\/\\\\])\\]/g,\"$1\"):t.replace(/((?!\\\\).|^)\\[([^\\/\\\\])\\]/g,\"$1$2\").replace(/\\\\([^\\/])/g,\"$1\");_O.unescape=fUt});var RK=L(qO=>{\"use strict\";Object.defineProperty(qO,\"__esModule\",{value:!0});qO.AST=void 0;var AUt=CFe(),HO=UO(),pUt=new Set([\"!\",\"?\",\"+\",\"*\",\"@\"]),wFe=t=>pUt.has(t),hUt=\"(?!(?:^|/)\\\\.\\\\.?(?:$|/))\",jO=\"(?!\\\\.)\",gUt=new Set([\"[\",\".\"]),dUt=new Set([\"..\",\".\"]),mUt=new Set(\"().*{}+?[]^$\\\\!\"),yUt=t=>t.replace(/[-[\\]{}()*+?.,\\\\^$|#\\s]/g,\"\\\\$&\"),TK=\"[^/]\",BFe=TK+\"*?\",vFe=TK+\"+?\",QK=class t{type;#t;#r;#i=!1;#e=[];#n;#o;#l;#a=!1;#s;#c;#f=!1;constructor(e,r,s={}){this.type=e,e&&(this.#r=!0),this.#n=r,this.#t=this.#n?this.#n.#t:this,this.#s=this.#t===this?s:this.#t.#s,this.#l=this.#t===this?[]:this.#t.#l,e===\"!\"&&!this.#t.#a&&this.#l.push(this),this.#o=this.#n?this.#n.#e.length:0}get hasMagic(){if(this.#r!==void 0)return this.#r;for(let e of this.#e)if(typeof e!=\"string\"&&(e.type||e.hasMagic))return this.#r=!0;return this.#r}toString(){return this.#c!==void 0?this.#c:this.type?this.#c=this.type+\"(\"+this.#e.map(e=>String(e)).join(\"|\")+\")\":this.#c=this.#e.map(e=>String(e)).join(\"\")}#p(){if(this!==this.#t)throw new Error(\"should only call on root\");if(this.#a)return this;this.toString(),this.#a=!0;let e;for(;e=this.#l.pop();){if(e.type!==\"!\")continue;let r=e,s=r.#n;for(;s;){for(let a=r.#o+1;!s.type&&a<s.#e.length;a++)for(let n of e.#e){if(typeof n==\"string\")throw new Error(\"string part in extglob AST??\");n.copyIn(s.#e[a])}r=s,s=r.#n}}return this}push(...e){for(let r of e)if(r!==\"\"){if(typeof r!=\"string\"&&!(r instanceof t&&r.#n===this))throw new Error(\"invalid part: \"+r);this.#e.push(r)}}toJSON(){let e=this.type===null?this.#e.slice().map(r=>typeof r==\"string\"?r:r.toJSON()):[this.type,...this.#e.map(r=>r.toJSON())];return this.isStart()&&!this.type&&e.unshift([]),this.isEnd()&&(this===this.#t||this.#t.#a&&this.#n?.type===\"!\")&&e.push({}),e}isStart(){if(this.#t===this)return!0;if(!this.#n?.isStart())return!1;if(this.#o===0)return!0;let e=this.#n;for(let r=0;r<this.#o;r++){let s=e.#e[r];if(!(s instanceof t&&s.type===\"!\"))return!1}return!0}isEnd(){if(this.#t===this||this.#n?.type===\"!\")return!0;if(!this.#n?.isEnd())return!1;if(!this.type)return this.#n?.isEnd();let e=this.#n?this.#n.#e.length:0;return this.#o===e-1}copyIn(e){typeof e==\"string\"?this.push(e):this.push(e.clone(this))}clone(e){let r=new t(this.type,e);for(let s of this.#e)r.copyIn(s);return r}static#u(e,r,s,a){let n=!1,c=!1,f=-1,p=!1;if(r.type===null){let P=s,I=\"\";for(;P<e.length;){let R=e.charAt(P++);if(n||R===\"\\\\\"){n=!n,I+=R;continue}if(c){P===f+1?(R===\"^\"||R===\"!\")&&(p=!0):R===\"]\"&&!(P===f+2&&p)&&(c=!1),I+=R;continue}else if(R===\"[\"){c=!0,f=P,p=!1,I+=R;continue}if(!a.noext&&wFe(R)&&e.charAt(P)===\"(\"){r.push(I),I=\"\";let N=new t(R,r);P=t.#u(e,N,P,a),r.push(N);continue}I+=R}return r.push(I),P}let h=s+1,E=new t(null,r),C=[],S=\"\";for(;h<e.length;){let P=e.charAt(h++);if(n||P===\"\\\\\"){n=!n,S+=P;continue}if(c){h===f+1?(P===\"^\"||P===\"!\")&&(p=!0):P===\"]\"&&!(h===f+2&&p)&&(c=!1),S+=P;continue}else if(P===\"[\"){c=!0,f=h,p=!1,S+=P;continue}if(wFe(P)&&e.charAt(h)===\"(\"){E.push(S),S=\"\";let I=new t(P,E);E.push(I),h=t.#u(e,I,h,a);continue}if(P===\"|\"){E.push(S),S=\"\",C.push(E),E=new t(null,r);continue}if(P===\")\")return S===\"\"&&r.#e.length===0&&(r.#f=!0),E.push(S),S=\"\",r.push(...C,E),h;S+=P}return r.type=null,r.#r=void 0,r.#e=[e.substring(s-1)],h}static fromGlob(e,r={}){let s=new t(null,void 0,r);return t.#u(e,s,0,r),s}toMMPattern(){if(this!==this.#t)return this.#t.toMMPattern();let e=this.toString(),[r,s,a,n]=this.toRegExpSource();if(!(a||this.#r||this.#s.nocase&&!this.#s.nocaseMagicOnly&&e.toUpperCase()!==e.toLowerCase()))return s;let f=(this.#s.nocase?\"i\":\"\")+(n?\"u\":\"\");return Object.assign(new RegExp(`^${r}$`,f),{_src:r,_glob:e})}get options(){return this.#s}toRegExpSource(e){let r=e??!!this.#s.dot;if(this.#t===this&&this.#p(),!this.type){let p=this.isStart()&&this.isEnd(),h=this.#e.map(P=>{let[I,R,N,U]=typeof P==\"string\"?t.#h(P,this.#r,p):P.toRegExpSource(e);return this.#r=this.#r||N,this.#i=this.#i||U,I}).join(\"\"),E=\"\";if(this.isStart()&&typeof this.#e[0]==\"string\"&&!(this.#e.length===1&&dUt.has(this.#e[0]))){let I=gUt,R=r&&I.has(h.charAt(0))||h.startsWith(\"\\\\.\")&&I.has(h.charAt(2))||h.startsWith(\"\\\\.\\\\.\")&&I.has(h.charAt(4)),N=!r&&!e&&I.has(h.charAt(0));E=R?hUt:N?jO:\"\"}let C=\"\";return this.isEnd()&&this.#t.#a&&this.#n?.type===\"!\"&&(C=\"(?:$|\\\\/)\"),[E+h+C,(0,HO.unescape)(h),this.#r=!!this.#r,this.#i]}let s=this.type===\"*\"||this.type===\"+\",a=this.type===\"!\"?\"(?:(?!(?:\":\"(?:\",n=this.#A(r);if(this.isStart()&&this.isEnd()&&!n&&this.type!==\"!\"){let p=this.toString();return this.#e=[p],this.type=null,this.#r=void 0,[p,(0,HO.unescape)(this.toString()),!1,!1]}let c=!s||e||r||!jO?\"\":this.#A(!0);c===n&&(c=\"\"),c&&(n=`(?:${n})(?:${c})*?`);let f=\"\";if(this.type===\"!\"&&this.#f)f=(this.isStart()&&!r?jO:\"\")+vFe;else{let p=this.type===\"!\"?\"))\"+(this.isStart()&&!r&&!e?jO:\"\")+BFe+\")\":this.type===\"@\"?\")\":this.type===\"?\"?\")?\":this.type===\"+\"&&c?\")\":this.type===\"*\"&&c?\")?\":`)${this.type}`;f=a+n+p}return[f,(0,HO.unescape)(n),this.#r=!!this.#r,this.#i]}#A(e){return this.#e.map(r=>{if(typeof r==\"string\")throw new Error(\"string type in extglob ast??\");let[s,a,n,c]=r.toRegExpSource(e);return this.#i=this.#i||c,s}).filter(r=>!(this.isStart()&&this.isEnd())||!!r).join(\"|\")}static#h(e,r,s=!1){let a=!1,n=\"\",c=!1;for(let f=0;f<e.length;f++){let p=e.charAt(f);if(a){a=!1,n+=(mUt.has(p)?\"\\\\\":\"\")+p;continue}if(p===\"\\\\\"){f===e.length-1?n+=\"\\\\\\\\\":a=!0;continue}if(p===\"[\"){let[h,E,C,S]=(0,AUt.parseClass)(e,f);if(C){n+=h,c=c||E,f+=C-1,r=r||S;continue}}if(p===\"*\"){s&&e===\"*\"?n+=vFe:n+=BFe,r=!0;continue}if(p===\"?\"){n+=TK,r=!0;continue}n+=yUt(p)}return[n,(0,HO.unescape)(e),!!r,c]}};qO.AST=QK});var FK=L(GO=>{\"use strict\";Object.defineProperty(GO,\"__esModule\",{value:!0});GO.escape=void 0;var EUt=(t,{windowsPathsNoEscape:e=!1}={})=>e?t.replace(/[?*()[\\]]/g,\"[$&]\"):t.replace(/[?*()[\\]\\\\]/g,\"\\\\$&\");GO.escape=EUt});var QFe=L(pr=>{\"use strict\";var IUt=pr&&pr.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(pr,\"__esModule\",{value:!0});pr.unescape=pr.escape=pr.AST=pr.Minimatch=pr.match=pr.makeRe=pr.braceExpand=pr.defaults=pr.filter=pr.GLOBSTAR=pr.sep=pr.minimatch=void 0;var CUt=IUt(yFe()),WO=EFe(),bFe=RK(),wUt=FK(),BUt=UO(),vUt=(t,e,r={})=>((0,WO.assertValidPattern)(e),!r.nocomment&&e.charAt(0)===\"#\"?!1:new ay(e,r).match(t));pr.minimatch=vUt;var SUt=/^\\*+([^+@!?\\*\\[\\(]*)$/,DUt=t=>e=>!e.startsWith(\".\")&&e.endsWith(t),bUt=t=>e=>e.endsWith(t),PUt=t=>(t=t.toLowerCase(),e=>!e.startsWith(\".\")&&e.toLowerCase().endsWith(t)),xUt=t=>(t=t.toLowerCase(),e=>e.toLowerCase().endsWith(t)),kUt=/^\\*+\\.\\*+$/,QUt=t=>!t.startsWith(\".\")&&t.includes(\".\"),TUt=t=>t!==\".\"&&t!==\"..\"&&t.includes(\".\"),RUt=/^\\.\\*+$/,FUt=t=>t!==\".\"&&t!==\"..\"&&t.startsWith(\".\"),NUt=/^\\*+$/,OUt=t=>t.length!==0&&!t.startsWith(\".\"),LUt=t=>t.length!==0&&t!==\".\"&&t!==\"..\",MUt=/^\\?+([^+@!?\\*\\[\\(]*)?$/,_Ut=([t,e=\"\"])=>{let r=PFe([t]);return e?(e=e.toLowerCase(),s=>r(s)&&s.toLowerCase().endsWith(e)):r},UUt=([t,e=\"\"])=>{let r=xFe([t]);return e?(e=e.toLowerCase(),s=>r(s)&&s.toLowerCase().endsWith(e)):r},HUt=([t,e=\"\"])=>{let r=xFe([t]);return e?s=>r(s)&&s.endsWith(e):r},jUt=([t,e=\"\"])=>{let r=PFe([t]);return e?s=>r(s)&&s.endsWith(e):r},PFe=([t])=>{let e=t.length;return r=>r.length===e&&!r.startsWith(\".\")},xFe=([t])=>{let e=t.length;return r=>r.length===e&&r!==\".\"&&r!==\"..\"},kFe=typeof process==\"object\"&&process?typeof process.env==\"object\"&&process.env&&process.env.__MINIMATCH_TESTING_PLATFORM__||process.platform:\"posix\",SFe={win32:{sep:\"\\\\\"},posix:{sep:\"/\"}};pr.sep=kFe===\"win32\"?SFe.win32.sep:SFe.posix.sep;pr.minimatch.sep=pr.sep;pr.GLOBSTAR=Symbol(\"globstar **\");pr.minimatch.GLOBSTAR=pr.GLOBSTAR;var qUt=\"[^/]\",GUt=qUt+\"*?\",WUt=\"(?:(?!(?:\\\\/|^)(?:\\\\.{1,2})($|\\\\/)).)*?\",YUt=\"(?:(?!(?:\\\\/|^)\\\\.).)*?\",VUt=(t,e={})=>r=>(0,pr.minimatch)(r,t,e);pr.filter=VUt;pr.minimatch.filter=pr.filter;var nu=(t,e={})=>Object.assign({},t,e),KUt=t=>{if(!t||typeof t!=\"object\"||!Object.keys(t).length)return pr.minimatch;let e=pr.minimatch;return Object.assign((s,a,n={})=>e(s,a,nu(t,n)),{Minimatch:class extends e.Minimatch{constructor(a,n={}){super(a,nu(t,n))}static defaults(a){return e.defaults(nu(t,a)).Minimatch}},AST:class extends e.AST{constructor(a,n,c={}){super(a,n,nu(t,c))}static fromGlob(a,n={}){return e.AST.fromGlob(a,nu(t,n))}},unescape:(s,a={})=>e.unescape(s,nu(t,a)),escape:(s,a={})=>e.escape(s,nu(t,a)),filter:(s,a={})=>e.filter(s,nu(t,a)),defaults:s=>e.defaults(nu(t,s)),makeRe:(s,a={})=>e.makeRe(s,nu(t,a)),braceExpand:(s,a={})=>e.braceExpand(s,nu(t,a)),match:(s,a,n={})=>e.match(s,a,nu(t,n)),sep:e.sep,GLOBSTAR:pr.GLOBSTAR})};pr.defaults=KUt;pr.minimatch.defaults=pr.defaults;var JUt=(t,e={})=>((0,WO.assertValidPattern)(t),e.nobrace||!/\\{(?:(?!\\{).)*\\}/.test(t)?[t]:(0,CUt.default)(t));pr.braceExpand=JUt;pr.minimatch.braceExpand=pr.braceExpand;var zUt=(t,e={})=>new ay(t,e).makeRe();pr.makeRe=zUt;pr.minimatch.makeRe=pr.makeRe;var ZUt=(t,e,r={})=>{let s=new ay(e,r);return t=t.filter(a=>s.match(a)),s.options.nonull&&!t.length&&t.push(e),t};pr.match=ZUt;pr.minimatch.match=pr.match;var DFe=/[?*]|[+@!]\\(.*?\\)|\\[|\\]/,XUt=t=>t.replace(/[-[\\]{}()*+?.,\\\\^$|#\\s]/g,\"\\\\$&\"),ay=class{options;set;pattern;windowsPathsNoEscape;nonegate;negate;comment;empty;preserveMultipleSlashes;partial;globSet;globParts;nocase;isWindows;platform;windowsNoMagicRoot;regexp;constructor(e,r={}){(0,WO.assertValidPattern)(e),r=r||{},this.options=r,this.pattern=e,this.platform=r.platform||kFe,this.isWindows=this.platform===\"win32\",this.windowsPathsNoEscape=!!r.windowsPathsNoEscape||r.allowWindowsEscape===!1,this.windowsPathsNoEscape&&(this.pattern=this.pattern.replace(/\\\\/g,\"/\")),this.preserveMultipleSlashes=!!r.preserveMultipleSlashes,this.regexp=null,this.negate=!1,this.nonegate=!!r.nonegate,this.comment=!1,this.empty=!1,this.partial=!!r.partial,this.nocase=!!this.options.nocase,this.windowsNoMagicRoot=r.windowsNoMagicRoot!==void 0?r.windowsNoMagicRoot:!!(this.isWindows&&this.nocase),this.globSet=[],this.globParts=[],this.set=[],this.make()}hasMagic(){if(this.options.magicalBraces&&this.set.length>1)return!0;for(let e of this.set)for(let r of e)if(typeof r!=\"string\")return!0;return!1}debug(...e){}make(){let e=this.pattern,r=this.options;if(!r.nocomment&&e.charAt(0)===\"#\"){this.comment=!0;return}if(!e){this.empty=!0;return}this.parseNegate(),this.globSet=[...new Set(this.braceExpand())],r.debug&&(this.debug=(...n)=>console.error(...n)),this.debug(this.pattern,this.globSet);let s=this.globSet.map(n=>this.slashSplit(n));this.globParts=this.preprocess(s),this.debug(this.pattern,this.globParts);let a=this.globParts.map((n,c,f)=>{if(this.isWindows&&this.windowsNoMagicRoot){let p=n[0]===\"\"&&n[1]===\"\"&&(n[2]===\"?\"||!DFe.test(n[2]))&&!DFe.test(n[3]),h=/^[a-z]:/i.test(n[0]);if(p)return[...n.slice(0,4),...n.slice(4).map(E=>this.parse(E))];if(h)return[n[0],...n.slice(1).map(E=>this.parse(E))]}return n.map(p=>this.parse(p))});if(this.debug(this.pattern,a),this.set=a.filter(n=>n.indexOf(!1)===-1),this.isWindows)for(let n=0;n<this.set.length;n++){let c=this.set[n];c[0]===\"\"&&c[1]===\"\"&&this.globParts[n][2]===\"?\"&&typeof c[3]==\"string\"&&/^[a-z]:$/i.test(c[3])&&(c[2]=\"?\")}this.debug(this.pattern,this.set)}preprocess(e){if(this.options.noglobstar)for(let s=0;s<e.length;s++)for(let a=0;a<e[s].length;a++)e[s][a]===\"**\"&&(e[s][a]=\"*\");let{optimizationLevel:r=1}=this.options;return r>=2?(e=this.firstPhasePreProcess(e),e=this.secondPhasePreProcess(e)):r>=1?e=this.levelOneOptimize(e):e=this.adjascentGlobstarOptimize(e),e}adjascentGlobstarOptimize(e){return e.map(r=>{let s=-1;for(;(s=r.indexOf(\"**\",s+1))!==-1;){let a=s;for(;r[a+1]===\"**\";)a++;a!==s&&r.splice(s,a-s)}return r})}levelOneOptimize(e){return e.map(r=>(r=r.reduce((s,a)=>{let n=s[s.length-1];return a===\"**\"&&n===\"**\"?s:a===\"..\"&&n&&n!==\"..\"&&n!==\".\"&&n!==\"**\"?(s.pop(),s):(s.push(a),s)},[]),r.length===0?[\"\"]:r))}levelTwoFileOptimize(e){Array.isArray(e)||(e=this.slashSplit(e));let r=!1;do{if(r=!1,!this.preserveMultipleSlashes){for(let a=1;a<e.length-1;a++){let n=e[a];a===1&&n===\"\"&&e[0]===\"\"||(n===\".\"||n===\"\")&&(r=!0,e.splice(a,1),a--)}e[0]===\".\"&&e.length===2&&(e[1]===\".\"||e[1]===\"\")&&(r=!0,e.pop())}let s=0;for(;(s=e.indexOf(\"..\",s+1))!==-1;){let a=e[s-1];a&&a!==\".\"&&a!==\"..\"&&a!==\"**\"&&(r=!0,e.splice(s-1,2),s-=2)}}while(r);return e.length===0?[\"\"]:e}firstPhasePreProcess(e){let r=!1;do{r=!1;for(let s of e){let a=-1;for(;(a=s.indexOf(\"**\",a+1))!==-1;){let c=a;for(;s[c+1]===\"**\";)c++;c>a&&s.splice(a+1,c-a);let f=s[a+1],p=s[a+2],h=s[a+3];if(f!==\"..\"||!p||p===\".\"||p===\"..\"||!h||h===\".\"||h===\"..\")continue;r=!0,s.splice(a,1);let E=s.slice(0);E[a]=\"**\",e.push(E),a--}if(!this.preserveMultipleSlashes){for(let c=1;c<s.length-1;c++){let f=s[c];c===1&&f===\"\"&&s[0]===\"\"||(f===\".\"||f===\"\")&&(r=!0,s.splice(c,1),c--)}s[0]===\".\"&&s.length===2&&(s[1]===\".\"||s[1]===\"\")&&(r=!0,s.pop())}let n=0;for(;(n=s.indexOf(\"..\",n+1))!==-1;){let c=s[n-1];if(c&&c!==\".\"&&c!==\"..\"&&c!==\"**\"){r=!0;let p=n===1&&s[n+1]===\"**\"?[\".\"]:[];s.splice(n-1,2,...p),s.length===0&&s.push(\"\"),n-=2}}}}while(r);return e}secondPhasePreProcess(e){for(let r=0;r<e.length-1;r++)for(let s=r+1;s<e.length;s++){let a=this.partsMatch(e[r],e[s],!this.preserveMultipleSlashes);if(a){e[r]=[],e[s]=a;break}}return e.filter(r=>r.length)}partsMatch(e,r,s=!1){let a=0,n=0,c=[],f=\"\";for(;a<e.length&&n<r.length;)if(e[a]===r[n])c.push(f===\"b\"?r[n]:e[a]),a++,n++;else if(s&&e[a]===\"**\"&&r[n]===e[a+1])c.push(e[a]),a++;else if(s&&r[n]===\"**\"&&e[a]===r[n+1])c.push(r[n]),n++;else if(e[a]===\"*\"&&r[n]&&(this.options.dot||!r[n].startsWith(\".\"))&&r[n]!==\"**\"){if(f===\"b\")return!1;f=\"a\",c.push(e[a]),a++,n++}else if(r[n]===\"*\"&&e[a]&&(this.options.dot||!e[a].startsWith(\".\"))&&e[a]!==\"**\"){if(f===\"a\")return!1;f=\"b\",c.push(r[n]),a++,n++}else return!1;return e.length===r.length&&c}parseNegate(){if(this.nonegate)return;let e=this.pattern,r=!1,s=0;for(let a=0;a<e.length&&e.charAt(a)===\"!\";a++)r=!r,s++;s&&(this.pattern=e.slice(s)),this.negate=r}matchOne(e,r,s=!1){let a=this.options;if(this.isWindows){let R=typeof e[0]==\"string\"&&/^[a-z]:$/i.test(e[0]),N=!R&&e[0]===\"\"&&e[1]===\"\"&&e[2]===\"?\"&&/^[a-z]:$/i.test(e[3]),U=typeof r[0]==\"string\"&&/^[a-z]:$/i.test(r[0]),W=!U&&r[0]===\"\"&&r[1]===\"\"&&r[2]===\"?\"&&typeof r[3]==\"string\"&&/^[a-z]:$/i.test(r[3]),te=N?3:R?0:void 0,ie=W?3:U?0:void 0;if(typeof te==\"number\"&&typeof ie==\"number\"){let[Ae,ce]=[e[te],r[ie]];Ae.toLowerCase()===ce.toLowerCase()&&(r[ie]=Ae,ie>te?r=r.slice(ie):te>ie&&(e=e.slice(te)))}}let{optimizationLevel:n=1}=this.options;n>=2&&(e=this.levelTwoFileOptimize(e)),this.debug(\"matchOne\",this,{file:e,pattern:r}),this.debug(\"matchOne\",e.length,r.length);for(var c=0,f=0,p=e.length,h=r.length;c<p&&f<h;c++,f++){this.debug(\"matchOne loop\");var E=r[f],C=e[c];if(this.debug(r,E,C),E===!1)return!1;if(E===pr.GLOBSTAR){this.debug(\"GLOBSTAR\",[r,E,C]);var S=c,P=f+1;if(P===h){for(this.debug(\"** at the end\");c<p;c++)if(e[c]===\".\"||e[c]===\"..\"||!a.dot&&e[c].charAt(0)===\".\")return!1;return!0}for(;S<p;){var I=e[S];if(this.debug(`\nglobstar while`,e,S,r,P,I),this.matchOne(e.slice(S),r.slice(P),s))return this.debug(\"globstar found match!\",S,p,I),!0;if(I===\".\"||I===\"..\"||!a.dot&&I.charAt(0)===\".\"){this.debug(\"dot detected!\",e,S,r,P);break}this.debug(\"globstar swallow a segment, and continue\"),S++}return!!(s&&(this.debug(`\n>>> no match, partial?`,e,S,r,P),S===p))}let R;if(typeof E==\"string\"?(R=C===E,this.debug(\"string match\",E,C,R)):(R=E.test(C),this.debug(\"pattern match\",E,C,R)),!R)return!1}if(c===p&&f===h)return!0;if(c===p)return s;if(f===h)return c===p-1&&e[c]===\"\";throw new Error(\"wtf?\")}braceExpand(){return(0,pr.braceExpand)(this.pattern,this.options)}parse(e){(0,WO.assertValidPattern)(e);let r=this.options;if(e===\"**\")return pr.GLOBSTAR;if(e===\"\")return\"\";let s,a=null;(s=e.match(NUt))?a=r.dot?LUt:OUt:(s=e.match(SUt))?a=(r.nocase?r.dot?xUt:PUt:r.dot?bUt:DUt)(s[1]):(s=e.match(MUt))?a=(r.nocase?r.dot?UUt:_Ut:r.dot?HUt:jUt)(s):(s=e.match(kUt))?a=r.dot?TUt:QUt:(s=e.match(RUt))&&(a=FUt);let n=bFe.AST.fromGlob(e,this.options).toMMPattern();return a&&typeof n==\"object\"&&Reflect.defineProperty(n,\"test\",{value:a}),n}makeRe(){if(this.regexp||this.regexp===!1)return this.regexp;let e=this.set;if(!e.length)return this.regexp=!1,this.regexp;let r=this.options,s=r.noglobstar?GUt:r.dot?WUt:YUt,a=new Set(r.nocase?[\"i\"]:[]),n=e.map(p=>{let h=p.map(E=>{if(E instanceof RegExp)for(let C of E.flags.split(\"\"))a.add(C);return typeof E==\"string\"?XUt(E):E===pr.GLOBSTAR?pr.GLOBSTAR:E._src});return h.forEach((E,C)=>{let S=h[C+1],P=h[C-1];E!==pr.GLOBSTAR||P===pr.GLOBSTAR||(P===void 0?S!==void 0&&S!==pr.GLOBSTAR?h[C+1]=\"(?:\\\\/|\"+s+\"\\\\/)?\"+S:h[C]=s:S===void 0?h[C-1]=P+\"(?:\\\\/|\"+s+\")?\":S!==pr.GLOBSTAR&&(h[C-1]=P+\"(?:\\\\/|\\\\/\"+s+\"\\\\/)\"+S,h[C+1]=pr.GLOBSTAR))}),h.filter(E=>E!==pr.GLOBSTAR).join(\"/\")}).join(\"|\"),[c,f]=e.length>1?[\"(?:\",\")\"]:[\"\",\"\"];n=\"^\"+c+n+f+\"$\",this.negate&&(n=\"^(?!\"+n+\").+$\");try{this.regexp=new RegExp(n,[...a].join(\"\"))}catch{this.regexp=!1}return this.regexp}slashSplit(e){return this.preserveMultipleSlashes?e.split(\"/\"):this.isWindows&&/^\\/\\/[^\\/]+/.test(e)?[\"\",...e.split(/\\/+/)]:e.split(/\\/+/)}match(e,r=this.partial){if(this.debug(\"match\",e,this.pattern),this.comment)return!1;if(this.empty)return e===\"\";if(e===\"/\"&&r)return!0;let s=this.options;this.isWindows&&(e=e.split(\"\\\\\").join(\"/\"));let a=this.slashSplit(e);this.debug(this.pattern,\"split\",a);let n=this.set;this.debug(this.pattern,\"set\",n);let c=a[a.length-1];if(!c)for(let f=a.length-2;!c&&f>=0;f--)c=a[f];for(let f=0;f<n.length;f++){let p=n[f],h=a;if(s.matchBase&&p.length===1&&(h=[c]),this.matchOne(h,p,r))return s.flipNegate?!0:!this.negate}return s.flipNegate?!1:this.negate}static defaults(e){return pr.minimatch.defaults(e).Minimatch}};pr.Minimatch=ay;var $Ut=RK();Object.defineProperty(pr,\"AST\",{enumerable:!0,get:function(){return $Ut.AST}});var e4t=FK();Object.defineProperty(pr,\"escape\",{enumerable:!0,get:function(){return e4t.escape}});var t4t=UO();Object.defineProperty(pr,\"unescape\",{enumerable:!0,get:function(){return t4t.unescape}});pr.minimatch.AST=bFe.AST;pr.minimatch.Minimatch=ay;pr.minimatch.escape=wUt.escape;pr.minimatch.unescape=BUt.unescape});var LK=L(iu=>{\"use strict\";var TFe=iu&&iu.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(iu,\"__esModule\",{value:!0});iu.SuccinctRoles=iu.DelegatedRole=iu.Role=iu.TOP_LEVEL_ROLE_NAMES=void 0;var RFe=TFe(Ie(\"crypto\")),r4t=QFe(),YO=TFe(Ie(\"util\")),VO=kA(),ly=Af();iu.TOP_LEVEL_ROLE_NAMES=[\"root\",\"targets\",\"snapshot\",\"timestamp\"];var Vb=class t{constructor(e){let{keyIDs:r,threshold:s,unrecognizedFields:a}=e;if(n4t(r))throw new VO.ValueError(\"duplicate key IDs found\");if(s<1)throw new VO.ValueError(\"threshold must be at least 1\");this.keyIDs=r,this.threshold=s,this.unrecognizedFields=a||{}}equals(e){return e instanceof t?this.threshold===e.threshold&&YO.default.isDeepStrictEqual(this.keyIDs,e.keyIDs)&&YO.default.isDeepStrictEqual(this.unrecognizedFields,e.unrecognizedFields):!1}toJSON(){return{keyids:this.keyIDs,threshold:this.threshold,...this.unrecognizedFields}}static fromJSON(e){let{keyids:r,threshold:s,...a}=e;if(!ly.guard.isStringArray(r))throw new TypeError(\"keyids must be an array\");if(typeof s!=\"number\")throw new TypeError(\"threshold must be a number\");return new t({keyIDs:r,threshold:s,unrecognizedFields:a})}};iu.Role=Vb;function n4t(t){return new Set(t).size!==t.length}var NK=class t extends Vb{constructor(e){super(e);let{name:r,terminating:s,paths:a,pathHashPrefixes:n}=e;if(this.name=r,this.terminating=s,e.paths&&e.pathHashPrefixes)throw new VO.ValueError(\"paths and pathHashPrefixes are mutually exclusive\");this.paths=a,this.pathHashPrefixes=n}equals(e){return e instanceof t?super.equals(e)&&this.name===e.name&&this.terminating===e.terminating&&YO.default.isDeepStrictEqual(this.paths,e.paths)&&YO.default.isDeepStrictEqual(this.pathHashPrefixes,e.pathHashPrefixes):!1}isDelegatedPath(e){if(this.paths)return this.paths.some(r=>s4t(e,r));if(this.pathHashPrefixes){let s=RFe.default.createHash(\"sha256\").update(e).digest(\"hex\");return this.pathHashPrefixes.some(a=>s.startsWith(a))}return!1}toJSON(){let e={...super.toJSON(),name:this.name,terminating:this.terminating};return this.paths&&(e.paths=this.paths),this.pathHashPrefixes&&(e.path_hash_prefixes=this.pathHashPrefixes),e}static fromJSON(e){let{keyids:r,threshold:s,name:a,terminating:n,paths:c,path_hash_prefixes:f,...p}=e;if(!ly.guard.isStringArray(r))throw new TypeError(\"keyids must be an array of strings\");if(typeof s!=\"number\")throw new TypeError(\"threshold must be a number\");if(typeof a!=\"string\")throw new TypeError(\"name must be a string\");if(typeof n!=\"boolean\")throw new TypeError(\"terminating must be a boolean\");if(ly.guard.isDefined(c)&&!ly.guard.isStringArray(c))throw new TypeError(\"paths must be an array of strings\");if(ly.guard.isDefined(f)&&!ly.guard.isStringArray(f))throw new TypeError(\"path_hash_prefixes must be an array of strings\");return new t({keyIDs:r,threshold:s,name:a,terminating:n,paths:c,pathHashPrefixes:f,unrecognizedFields:p})}};iu.DelegatedRole=NK;var i4t=(t,e)=>t.map((r,s)=>[r,e[s]]);function s4t(t,e){let r=t.split(\"/\"),s=e.split(\"/\");return s.length!=r.length?!1:i4t(r,s).every(([a,n])=>(0,r4t.minimatch)(a,n))}var OK=class t extends Vb{constructor(e){super(e);let{bitLength:r,namePrefix:s}=e;if(r<=0||r>32)throw new VO.ValueError(\"bitLength must be between 1 and 32\");this.bitLength=r,this.namePrefix=s,this.numberOfBins=Math.pow(2,r),this.suffixLen=(this.numberOfBins-1).toString(16).length}equals(e){return e instanceof t?super.equals(e)&&this.bitLength===e.bitLength&&this.namePrefix===e.namePrefix:!1}getRoleForTarget(e){let a=RFe.default.createHash(\"sha256\").update(e).digest().subarray(0,4),n=32-this.bitLength,f=(a.readUInt32BE()>>>n).toString(16).padStart(this.suffixLen,\"0\");return`${this.namePrefix}-${f}`}*getRoles(){for(let e=0;e<this.numberOfBins;e++){let r=e.toString(16).padStart(this.suffixLen,\"0\");yield`${this.namePrefix}-${r}`}}isDelegatedRole(e){let r=this.namePrefix+\"-\";if(!e.startsWith(r))return!1;let s=e.slice(r.length,e.length);if(s.length!=this.suffixLen||!s.match(/^[0-9a-fA-F]+$/))return!1;let a=parseInt(s,16);return 0<=a&&a<this.numberOfBins}toJSON(){return{...super.toJSON(),bit_length:this.bitLength,name_prefix:this.namePrefix}}static fromJSON(e){let{keyids:r,threshold:s,bit_length:a,name_prefix:n,...c}=e;if(!ly.guard.isStringArray(r))throw new TypeError(\"keyids must be an array of strings\");if(typeof s!=\"number\")throw new TypeError(\"threshold must be a number\");if(typeof a!=\"number\")throw new TypeError(\"bit_length must be a number\");if(typeof n!=\"string\")throw new TypeError(\"name_prefix must be a string\");return new t({keyIDs:r,threshold:s,bitLength:a,namePrefix:n,unrecognizedFields:c})}};iu.SuccinctRoles=OK});var UK=L(o1=>{\"use strict\";var o4t=o1&&o1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(o1,\"__esModule\",{value:!0});o1.Root=void 0;var FFe=o4t(Ie(\"util\")),MK=oy(),NFe=kA(),a4t=OO(),KO=LK(),JO=Af(),_K=class t extends MK.Signed{constructor(e){if(super(e),this.type=MK.MetadataKind.Root,this.keys=e.keys||{},this.consistentSnapshot=e.consistentSnapshot??!0,!e.roles)this.roles=KO.TOP_LEVEL_ROLE_NAMES.reduce((r,s)=>({...r,[s]:new KO.Role({keyIDs:[],threshold:1})}),{});else{let r=new Set(Object.keys(e.roles));if(!KO.TOP_LEVEL_ROLE_NAMES.every(s=>r.has(s)))throw new NFe.ValueError(\"missing top-level role\");this.roles=e.roles}}addKey(e,r){if(!this.roles[r])throw new NFe.ValueError(`role ${r} does not exist`);this.roles[r].keyIDs.includes(e.keyID)||this.roles[r].keyIDs.push(e.keyID),this.keys[e.keyID]=e}equals(e){return e instanceof t?super.equals(e)&&this.consistentSnapshot===e.consistentSnapshot&&FFe.default.isDeepStrictEqual(this.keys,e.keys)&&FFe.default.isDeepStrictEqual(this.roles,e.roles):!1}toJSON(){return{_type:this.type,spec_version:this.specVersion,version:this.version,expires:this.expires,keys:l4t(this.keys),roles:c4t(this.roles),consistent_snapshot:this.consistentSnapshot,...this.unrecognizedFields}}static fromJSON(e){let{unrecognizedFields:r,...s}=MK.Signed.commonFieldsFromJSON(e),{keys:a,roles:n,consistent_snapshot:c,...f}=r;if(typeof c!=\"boolean\")throw new TypeError(\"consistent_snapshot must be a boolean\");return new t({...s,keys:u4t(a),roles:f4t(n),consistentSnapshot:c,unrecognizedFields:f})}};o1.Root=_K;function l4t(t){return Object.entries(t).reduce((e,[r,s])=>({...e,[r]:s.toJSON()}),{})}function c4t(t){return Object.entries(t).reduce((e,[r,s])=>({...e,[r]:s.toJSON()}),{})}function u4t(t){let e;if(JO.guard.isDefined(t)){if(!JO.guard.isObjectRecord(t))throw new TypeError(\"keys must be an object\");e=Object.entries(t).reduce((r,[s,a])=>({...r,[s]:a4t.Key.fromJSON(s,a)}),{})}return e}function f4t(t){let e;if(JO.guard.isDefined(t)){if(!JO.guard.isObjectRecord(t))throw new TypeError(\"roles must be an object\");e=Object.entries(t).reduce((r,[s,a])=>({...r,[s]:KO.Role.fromJSON(a)}),{})}return e}});var jK=L(zO=>{\"use strict\";Object.defineProperty(zO,\"__esModule\",{value:!0});zO.Signature=void 0;var HK=class t{constructor(e){let{keyID:r,sig:s}=e;this.keyID=r,this.sig=s}toJSON(){return{keyid:this.keyID,sig:this.sig}}static fromJSON(e){let{keyid:r,sig:s}=e;if(typeof r!=\"string\")throw new TypeError(\"keyid must be a string\");if(typeof s!=\"string\")throw new TypeError(\"sig must be a string\");return new t({keyID:r,sig:s})}};zO.Signature=HK});var WK=L(a1=>{\"use strict\";var A4t=a1&&a1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(a1,\"__esModule\",{value:!0});a1.Snapshot=void 0;var p4t=A4t(Ie(\"util\")),qK=oy(),LFe=Hb(),OFe=Af(),GK=class t extends qK.Signed{constructor(e){super(e),this.type=qK.MetadataKind.Snapshot,this.meta=e.meta||{\"targets.json\":new LFe.MetaFile({version:1})}}equals(e){return e instanceof t?super.equals(e)&&p4t.default.isDeepStrictEqual(this.meta,e.meta):!1}toJSON(){return{_type:this.type,meta:h4t(this.meta),spec_version:this.specVersion,version:this.version,expires:this.expires,...this.unrecognizedFields}}static fromJSON(e){let{unrecognizedFields:r,...s}=qK.Signed.commonFieldsFromJSON(e),{meta:a,...n}=r;return new t({...s,meta:g4t(a),unrecognizedFields:n})}};a1.Snapshot=GK;function h4t(t){return Object.entries(t).reduce((e,[r,s])=>({...e,[r]:s.toJSON()}),{})}function g4t(t){let e;if(OFe.guard.isDefined(t))if(OFe.guard.isObjectRecord(t))e=Object.entries(t).reduce((r,[s,a])=>({...r,[s]:LFe.MetaFile.fromJSON(a)}),{});else throw new TypeError(\"meta field is malformed\");return e}});var MFe=L(l1=>{\"use strict\";var d4t=l1&&l1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(l1,\"__esModule\",{value:!0});l1.Delegations=void 0;var ZO=d4t(Ie(\"util\")),m4t=kA(),y4t=OO(),YK=LK(),XO=Af(),VK=class t{constructor(e){if(this.keys=e.keys,this.unrecognizedFields=e.unrecognizedFields||{},e.roles&&Object.keys(e.roles).some(r=>YK.TOP_LEVEL_ROLE_NAMES.includes(r)))throw new m4t.ValueError(\"Delegated role name conflicts with top-level role name\");this.succinctRoles=e.succinctRoles,this.roles=e.roles}equals(e){return e instanceof t?ZO.default.isDeepStrictEqual(this.keys,e.keys)&&ZO.default.isDeepStrictEqual(this.roles,e.roles)&&ZO.default.isDeepStrictEqual(this.unrecognizedFields,e.unrecognizedFields)&&ZO.default.isDeepStrictEqual(this.succinctRoles,e.succinctRoles):!1}*rolesForTarget(e){if(this.roles)for(let r of Object.values(this.roles))r.isDelegatedPath(e)&&(yield{role:r.name,terminating:r.terminating});else this.succinctRoles&&(yield{role:this.succinctRoles.getRoleForTarget(e),terminating:!0})}toJSON(){let e={keys:E4t(this.keys),...this.unrecognizedFields};return this.roles?e.roles=I4t(this.roles):this.succinctRoles&&(e.succinct_roles=this.succinctRoles.toJSON()),e}static fromJSON(e){let{keys:r,roles:s,succinct_roles:a,...n}=e,c;return XO.guard.isObject(a)&&(c=YK.SuccinctRoles.fromJSON(a)),new t({keys:C4t(r),roles:w4t(s),unrecognizedFields:n,succinctRoles:c})}};l1.Delegations=VK;function E4t(t){return Object.entries(t).reduce((e,[r,s])=>({...e,[r]:s.toJSON()}),{})}function I4t(t){return Object.values(t).map(e=>e.toJSON())}function C4t(t){if(!XO.guard.isObjectRecord(t))throw new TypeError(\"keys is malformed\");return Object.entries(t).reduce((e,[r,s])=>({...e,[r]:y4t.Key.fromJSON(r,s)}),{})}function w4t(t){let e;if(XO.guard.isDefined(t)){if(!XO.guard.isObjectArray(t))throw new TypeError(\"roles is malformed\");e=t.reduce((r,s)=>{let a=YK.DelegatedRole.fromJSON(s);return{...r,[a.name]:a}},{})}return e}});var zK=L(c1=>{\"use strict\";var B4t=c1&&c1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(c1,\"__esModule\",{value:!0});c1.Targets=void 0;var _Fe=B4t(Ie(\"util\")),KK=oy(),v4t=MFe(),S4t=Hb(),$O=Af(),JK=class t extends KK.Signed{constructor(e){super(e),this.type=KK.MetadataKind.Targets,this.targets=e.targets||{},this.delegations=e.delegations}addTarget(e){this.targets[e.path]=e}equals(e){return e instanceof t?super.equals(e)&&_Fe.default.isDeepStrictEqual(this.targets,e.targets)&&_Fe.default.isDeepStrictEqual(this.delegations,e.delegations):!1}toJSON(){let e={_type:this.type,spec_version:this.specVersion,version:this.version,expires:this.expires,targets:D4t(this.targets),...this.unrecognizedFields};return this.delegations&&(e.delegations=this.delegations.toJSON()),e}static fromJSON(e){let{unrecognizedFields:r,...s}=KK.Signed.commonFieldsFromJSON(e),{targets:a,delegations:n,...c}=r;return new t({...s,targets:b4t(a),delegations:P4t(n),unrecognizedFields:c})}};c1.Targets=JK;function D4t(t){return Object.entries(t).reduce((e,[r,s])=>({...e,[r]:s.toJSON()}),{})}function b4t(t){let e;if($O.guard.isDefined(t))if($O.guard.isObjectRecord(t))e=Object.entries(t).reduce((r,[s,a])=>({...r,[s]:S4t.TargetFile.fromJSON(s,a)}),{});else throw new TypeError(\"targets must be an object\");return e}function P4t(t){let e;if($O.guard.isDefined(t))if($O.guard.isObject(t))e=v4t.Delegations.fromJSON(t);else throw new TypeError(\"delegations must be an object\");return e}});var eJ=L(eL=>{\"use strict\";Object.defineProperty(eL,\"__esModule\",{value:!0});eL.Timestamp=void 0;var ZK=oy(),UFe=Hb(),XK=Af(),$K=class t extends ZK.Signed{constructor(e){super(e),this.type=ZK.MetadataKind.Timestamp,this.snapshotMeta=e.snapshotMeta||new UFe.MetaFile({version:1})}equals(e){return e instanceof t?super.equals(e)&&this.snapshotMeta.equals(e.snapshotMeta):!1}toJSON(){return{_type:this.type,spec_version:this.specVersion,version:this.version,expires:this.expires,meta:{\"snapshot.json\":this.snapshotMeta.toJSON()},...this.unrecognizedFields}}static fromJSON(e){let{unrecognizedFields:r,...s}=ZK.Signed.commonFieldsFromJSON(e),{meta:a,...n}=r;return new t({...s,snapshotMeta:x4t(a),unrecognizedFields:n})}};eL.Timestamp=$K;function x4t(t){let e;if(XK.guard.isDefined(t)){let r=t[\"snapshot.json\"];if(!XK.guard.isDefined(r)||!XK.guard.isObject(r))throw new TypeError(\"missing snapshot.json in meta\");e=UFe.MetaFile.fromJSON(r)}return e}});var jFe=L(f1=>{\"use strict\";var k4t=f1&&f1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(f1,\"__esModule\",{value:!0});f1.Metadata=void 0;var Q4t=EK(),HFe=k4t(Ie(\"util\")),u1=oy(),Kb=kA(),T4t=UK(),R4t=jK(),F4t=WK(),N4t=zK(),O4t=eJ(),tJ=Af(),rJ=class t{constructor(e,r,s){this.signed=e,this.signatures=r||{},this.unrecognizedFields=s||{}}sign(e,r=!0){let s=Buffer.from((0,Q4t.canonicalize)(this.signed.toJSON())),a=e(s);r||(this.signatures={}),this.signatures[a.keyID]=a}verifyDelegate(e,r){let s,a={};switch(this.signed.type){case u1.MetadataKind.Root:a=this.signed.keys,s=this.signed.roles[e];break;case u1.MetadataKind.Targets:if(!this.signed.delegations)throw new Kb.ValueError(`No delegations found for ${e}`);a=this.signed.delegations.keys,this.signed.delegations.roles?s=this.signed.delegations.roles[e]:this.signed.delegations.succinctRoles&&this.signed.delegations.succinctRoles.isDelegatedRole(e)&&(s=this.signed.delegations.succinctRoles);break;default:throw new TypeError(\"invalid metadata type\")}if(!s)throw new Kb.ValueError(`no delegation found for ${e}`);let n=new Set;if(s.keyIDs.forEach(c=>{let f=a[c];if(f)try{f.verifySignature(r),n.add(f.keyID)}catch{}}),n.size<s.threshold)throw new Kb.UnsignedMetadataError(`${e} was signed by ${n.size}/${s.threshold} keys`)}equals(e){return e instanceof t?this.signed.equals(e.signed)&&HFe.default.isDeepStrictEqual(this.signatures,e.signatures)&&HFe.default.isDeepStrictEqual(this.unrecognizedFields,e.unrecognizedFields):!1}toJSON(){return{signatures:Object.values(this.signatures).map(r=>r.toJSON()),signed:this.signed.toJSON(),...this.unrecognizedFields}}static fromJSON(e,r){let{signed:s,signatures:a,...n}=r;if(!tJ.guard.isDefined(s)||!tJ.guard.isObject(s))throw new TypeError(\"signed is not defined\");if(e!==s._type)throw new Kb.ValueError(`expected '${e}', got ${s._type}`);if(!tJ.guard.isObjectArray(a))throw new TypeError(\"signatures is not an array\");let c;switch(e){case u1.MetadataKind.Root:c=T4t.Root.fromJSON(s);break;case u1.MetadataKind.Timestamp:c=O4t.Timestamp.fromJSON(s);break;case u1.MetadataKind.Snapshot:c=F4t.Snapshot.fromJSON(s);break;case u1.MetadataKind.Targets:c=N4t.Targets.fromJSON(s);break;default:throw new TypeError(\"invalid metadata type\")}let f={};return a.forEach(p=>{let h=R4t.Signature.fromJSON(p);if(f[h.keyID])throw new Kb.ValueError(`multiple signatures found for keyid: ${h.keyID}`);f[h.keyID]=h}),new t(c,f,n)}};f1.Metadata=rJ});var tL=L(Ni=>{\"use strict\";Object.defineProperty(Ni,\"__esModule\",{value:!0});Ni.Timestamp=Ni.Targets=Ni.Snapshot=Ni.Signature=Ni.Root=Ni.Metadata=Ni.Key=Ni.TargetFile=Ni.MetaFile=Ni.ValueError=Ni.MetadataKind=void 0;var L4t=oy();Object.defineProperty(Ni,\"MetadataKind\",{enumerable:!0,get:function(){return L4t.MetadataKind}});var M4t=kA();Object.defineProperty(Ni,\"ValueError\",{enumerable:!0,get:function(){return M4t.ValueError}});var qFe=Hb();Object.defineProperty(Ni,\"MetaFile\",{enumerable:!0,get:function(){return qFe.MetaFile}});Object.defineProperty(Ni,\"TargetFile\",{enumerable:!0,get:function(){return qFe.TargetFile}});var _4t=OO();Object.defineProperty(Ni,\"Key\",{enumerable:!0,get:function(){return _4t.Key}});var U4t=jFe();Object.defineProperty(Ni,\"Metadata\",{enumerable:!0,get:function(){return U4t.Metadata}});var H4t=UK();Object.defineProperty(Ni,\"Root\",{enumerable:!0,get:function(){return H4t.Root}});var j4t=jK();Object.defineProperty(Ni,\"Signature\",{enumerable:!0,get:function(){return j4t.Signature}});var q4t=WK();Object.defineProperty(Ni,\"Snapshot\",{enumerable:!0,get:function(){return q4t.Snapshot}});var G4t=zK();Object.defineProperty(Ni,\"Targets\",{enumerable:!0,get:function(){return G4t.Targets}});var W4t=eJ();Object.defineProperty(Ni,\"Timestamp\",{enumerable:!0,get:function(){return W4t.Timestamp}})});var WFe=L((WCr,GFe)=>{var A1=1e3,p1=A1*60,h1=p1*60,cy=h1*24,Y4t=cy*7,V4t=cy*365.25;GFe.exports=function(t,e){e=e||{};var r=typeof t;if(r===\"string\"&&t.length>0)return K4t(t);if(r===\"number\"&&isFinite(t))return e.long?z4t(t):J4t(t);throw new Error(\"val is not a non-empty string or a valid number. val=\"+JSON.stringify(t))};function K4t(t){if(t=String(t),!(t.length>100)){var e=/^(-?(?:\\d+)?\\.?\\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(t);if(e){var r=parseFloat(e[1]),s=(e[2]||\"ms\").toLowerCase();switch(s){case\"years\":case\"year\":case\"yrs\":case\"yr\":case\"y\":return r*V4t;case\"weeks\":case\"week\":case\"w\":return r*Y4t;case\"days\":case\"day\":case\"d\":return r*cy;case\"hours\":case\"hour\":case\"hrs\":case\"hr\":case\"h\":return r*h1;case\"minutes\":case\"minute\":case\"mins\":case\"min\":case\"m\":return r*p1;case\"seconds\":case\"second\":case\"secs\":case\"sec\":case\"s\":return r*A1;case\"milliseconds\":case\"millisecond\":case\"msecs\":case\"msec\":case\"ms\":return r;default:return}}}}function J4t(t){var e=Math.abs(t);return e>=cy?Math.round(t/cy)+\"d\":e>=h1?Math.round(t/h1)+\"h\":e>=p1?Math.round(t/p1)+\"m\":e>=A1?Math.round(t/A1)+\"s\":t+\"ms\"}function z4t(t){var e=Math.abs(t);return e>=cy?rL(t,e,cy,\"day\"):e>=h1?rL(t,e,h1,\"hour\"):e>=p1?rL(t,e,p1,\"minute\"):e>=A1?rL(t,e,A1,\"second\"):t+\" ms\"}function rL(t,e,r,s){var a=e>=r*1.5;return Math.round(t/r)+\" \"+s+(a?\"s\":\"\")}});var nJ=L((YCr,YFe)=>{function Z4t(t){r.debug=r,r.default=r,r.coerce=p,r.disable=c,r.enable=a,r.enabled=f,r.humanize=WFe(),r.destroy=h,Object.keys(t).forEach(E=>{r[E]=t[E]}),r.names=[],r.skips=[],r.formatters={};function e(E){let C=0;for(let S=0;S<E.length;S++)C=(C<<5)-C+E.charCodeAt(S),C|=0;return r.colors[Math.abs(C)%r.colors.length]}r.selectColor=e;function r(E){let C,S=null,P,I;function R(...N){if(!R.enabled)return;let U=R,W=Number(new Date),te=W-(C||W);U.diff=te,U.prev=C,U.curr=W,C=W,N[0]=r.coerce(N[0]),typeof N[0]!=\"string\"&&N.unshift(\"%O\");let ie=0;N[0]=N[0].replace(/%([a-zA-Z%])/g,(ce,me)=>{if(ce===\"%%\")return\"%\";ie++;let pe=r.formatters[me];if(typeof pe==\"function\"){let Be=N[ie];ce=pe.call(U,Be),N.splice(ie,1),ie--}return ce}),r.formatArgs.call(U,N),(U.log||r.log).apply(U,N)}return R.namespace=E,R.useColors=r.useColors(),R.color=r.selectColor(E),R.extend=s,R.destroy=r.destroy,Object.defineProperty(R,\"enabled\",{enumerable:!0,configurable:!1,get:()=>S!==null?S:(P!==r.namespaces&&(P=r.namespaces,I=r.enabled(E)),I),set:N=>{S=N}}),typeof r.init==\"function\"&&r.init(R),R}function s(E,C){let S=r(this.namespace+(typeof C>\"u\"?\":\":C)+E);return S.log=this.log,S}function a(E){r.save(E),r.namespaces=E,r.names=[],r.skips=[];let C=(typeof E==\"string\"?E:\"\").trim().replace(\" \",\",\").split(\",\").filter(Boolean);for(let S of C)S[0]===\"-\"?r.skips.push(S.slice(1)):r.names.push(S)}function n(E,C){let S=0,P=0,I=-1,R=0;for(;S<E.length;)if(P<C.length&&(C[P]===E[S]||C[P]===\"*\"))C[P]===\"*\"?(I=P,R=S,P++):(S++,P++);else if(I!==-1)P=I+1,R++,S=R;else return!1;for(;P<C.length&&C[P]===\"*\";)P++;return P===C.length}function c(){let E=[...r.names,...r.skips.map(C=>\"-\"+C)].join(\",\");return r.enable(\"\"),E}function f(E){for(let C of r.skips)if(n(E,C))return!1;for(let C of r.names)if(n(E,C))return!0;return!1}function p(E){return E instanceof Error?E.stack||E.message:E}function h(){console.warn(\"Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.\")}return r.enable(r.load()),r}YFe.exports=Z4t});var VFe=L((oc,nL)=>{oc.formatArgs=$4t;oc.save=e3t;oc.load=t3t;oc.useColors=X4t;oc.storage=r3t();oc.destroy=(()=>{let t=!1;return()=>{t||(t=!0,console.warn(\"Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.\"))}})();oc.colors=[\"#0000CC\",\"#0000FF\",\"#0033CC\",\"#0033FF\",\"#0066CC\",\"#0066FF\",\"#0099CC\",\"#0099FF\",\"#00CC00\",\"#00CC33\",\"#00CC66\",\"#00CC99\",\"#00CCCC\",\"#00CCFF\",\"#3300CC\",\"#3300FF\",\"#3333CC\",\"#3333FF\",\"#3366CC\",\"#3366FF\",\"#3399CC\",\"#3399FF\",\"#33CC00\",\"#33CC33\",\"#33CC66\",\"#33CC99\",\"#33CCCC\",\"#33CCFF\",\"#6600CC\",\"#6600FF\",\"#6633CC\",\"#6633FF\",\"#66CC00\",\"#66CC33\",\"#9900CC\",\"#9900FF\",\"#9933CC\",\"#9933FF\",\"#99CC00\",\"#99CC33\",\"#CC0000\",\"#CC0033\",\"#CC0066\",\"#CC0099\",\"#CC00CC\",\"#CC00FF\",\"#CC3300\",\"#CC3333\",\"#CC3366\",\"#CC3399\",\"#CC33CC\",\"#CC33FF\",\"#CC6600\",\"#CC6633\",\"#CC9900\",\"#CC9933\",\"#CCCC00\",\"#CCCC33\",\"#FF0000\",\"#FF0033\",\"#FF0066\",\"#FF0099\",\"#FF00CC\",\"#FF00FF\",\"#FF3300\",\"#FF3333\",\"#FF3366\",\"#FF3399\",\"#FF33CC\",\"#FF33FF\",\"#FF6600\",\"#FF6633\",\"#FF9900\",\"#FF9933\",\"#FFCC00\",\"#FFCC33\"];function X4t(){if(typeof window<\"u\"&&window.process&&(window.process.type===\"renderer\"||window.process.__nwjs))return!0;if(typeof navigator<\"u\"&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/(edge|trident)\\/(\\d+)/))return!1;let t;return typeof document<\"u\"&&document.documentElement&&document.documentElement.style&&document.documentElement.style.WebkitAppearance||typeof window<\"u\"&&window.console&&(window.console.firebug||window.console.exception&&window.console.table)||typeof navigator<\"u\"&&navigator.userAgent&&(t=navigator.userAgent.toLowerCase().match(/firefox\\/(\\d+)/))&&parseInt(t[1],10)>=31||typeof navigator<\"u\"&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\\/(\\d+)/)}function $4t(t){if(t[0]=(this.useColors?\"%c\":\"\")+this.namespace+(this.useColors?\" %c\":\" \")+t[0]+(this.useColors?\"%c \":\" \")+\"+\"+nL.exports.humanize(this.diff),!this.useColors)return;let e=\"color: \"+this.color;t.splice(1,0,e,\"color: inherit\");let r=0,s=0;t[0].replace(/%[a-zA-Z%]/g,a=>{a!==\"%%\"&&(r++,a===\"%c\"&&(s=r))}),t.splice(s,0,e)}oc.log=console.debug||console.log||(()=>{});function e3t(t){try{t?oc.storage.setItem(\"debug\",t):oc.storage.removeItem(\"debug\")}catch{}}function t3t(){let t;try{t=oc.storage.getItem(\"debug\")}catch{}return!t&&typeof process<\"u\"&&\"env\"in process&&(t=process.env.DEBUG),t}function r3t(){try{return localStorage}catch{}}nL.exports=nJ()(oc);var{formatters:n3t}=nL.exports;n3t.j=function(t){try{return JSON.stringify(t)}catch(e){return\"[UnexpectedJSONParseError]: \"+e.message}}});var JFe=L((eo,sL)=>{var i3t=Ie(\"tty\"),iL=Ie(\"util\");eo.init=f3t;eo.log=l3t;eo.formatArgs=o3t;eo.save=c3t;eo.load=u3t;eo.useColors=s3t;eo.destroy=iL.deprecate(()=>{},\"Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.\");eo.colors=[6,2,3,4,5,1];try{let t=Ie(\"supports-color\");t&&(t.stderr||t).level>=2&&(eo.colors=[20,21,26,27,32,33,38,39,40,41,42,43,44,45,56,57,62,63,68,69,74,75,76,77,78,79,80,81,92,93,98,99,112,113,128,129,134,135,148,149,160,161,162,163,164,165,166,167,168,169,170,171,172,173,178,179,184,185,196,197,198,199,200,201,202,203,204,205,206,207,208,209,214,215,220,221])}catch{}eo.inspectOpts=Object.keys(process.env).filter(t=>/^debug_/i.test(t)).reduce((t,e)=>{let r=e.substring(6).toLowerCase().replace(/_([a-z])/g,(a,n)=>n.toUpperCase()),s=process.env[e];return/^(yes|on|true|enabled)$/i.test(s)?s=!0:/^(no|off|false|disabled)$/i.test(s)?s=!1:s===\"null\"?s=null:s=Number(s),t[r]=s,t},{});function s3t(){return\"colors\"in eo.inspectOpts?!!eo.inspectOpts.colors:i3t.isatty(process.stderr.fd)}function o3t(t){let{namespace:e,useColors:r}=this;if(r){let s=this.color,a=\"\\x1B[3\"+(s<8?s:\"8;5;\"+s),n=`  ${a};1m${e} \\x1B[0m`;t[0]=n+t[0].split(`\n`).join(`\n`+n),t.push(a+\"m+\"+sL.exports.humanize(this.diff)+\"\\x1B[0m\")}else t[0]=a3t()+e+\" \"+t[0]}function a3t(){return eo.inspectOpts.hideDate?\"\":new Date().toISOString()+\" \"}function l3t(...t){return process.stderr.write(iL.formatWithOptions(eo.inspectOpts,...t)+`\n`)}function c3t(t){t?process.env.DEBUG=t:delete process.env.DEBUG}function u3t(){return process.env.DEBUG}function f3t(t){t.inspectOpts={};let e=Object.keys(eo.inspectOpts);for(let r=0;r<e.length;r++)t.inspectOpts[e[r]]=eo.inspectOpts[e[r]]}sL.exports=nJ()(eo);var{formatters:KFe}=sL.exports;KFe.o=function(t){return this.inspectOpts.colors=this.useColors,iL.inspect(t,this.inspectOpts).split(`\n`).map(e=>e.trim()).join(\" \")};KFe.O=function(t){return this.inspectOpts.colors=this.useColors,iL.inspect(t,this.inspectOpts)}});var sJ=L((VCr,iJ)=>{typeof process>\"u\"||process.type===\"renderer\"||process.browser===!0||process.__nwjs?iJ.exports=VFe():iJ.exports=JFe()});var aL=L(zi=>{\"use strict\";Object.defineProperty(zi,\"__esModule\",{value:!0});zi.DownloadHTTPError=zi.DownloadLengthMismatchError=zi.DownloadError=zi.ExpiredMetadataError=zi.EqualVersionError=zi.BadVersionError=zi.RepositoryError=zi.PersistError=zi.RuntimeError=zi.ValueError=void 0;var oJ=class extends Error{};zi.ValueError=oJ;var aJ=class extends Error{};zi.RuntimeError=aJ;var lJ=class extends Error{};zi.PersistError=lJ;var Jb=class extends Error{};zi.RepositoryError=Jb;var oL=class extends Jb{};zi.BadVersionError=oL;var cJ=class extends oL{};zi.EqualVersionError=cJ;var uJ=class extends Jb{};zi.ExpiredMetadataError=uJ;var zb=class extends Error{};zi.DownloadError=zb;var fJ=class extends zb{};zi.DownloadLengthMismatchError=fJ;var AJ=class extends zb{constructor(e,r){super(e),this.statusCode=r}};zi.DownloadHTTPError=AJ});var ZFe=L(g1=>{\"use strict\";var hJ=g1&&g1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(g1,\"__esModule\",{value:!0});g1.withTempFile=void 0;var pJ=hJ(Ie(\"fs/promises\")),A3t=hJ(Ie(\"os\")),zFe=hJ(Ie(\"path\")),p3t=async t=>h3t(async e=>t(zFe.default.join(e,\"tempfile\")));g1.withTempFile=p3t;var h3t=async t=>{let e=await pJ.default.realpath(A3t.default.tmpdir()),r=await pJ.default.mkdtemp(e+zFe.default.sep);try{return await t(r)}finally{await pJ.default.rm(r,{force:!0,recursive:!0,maxRetries:3})}}});var dJ=L(xg=>{\"use strict\";var cL=xg&&xg.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(xg,\"__esModule\",{value:!0});xg.DefaultFetcher=xg.BaseFetcher=void 0;var g3t=cL(sJ()),XFe=cL(Ie(\"fs\")),d3t=cL(wO()),m3t=cL(Ie(\"util\")),$Fe=aL(),y3t=ZFe(),E3t=(0,g3t.default)(\"tuf:fetch\"),lL=class{async downloadFile(e,r,s){return(0,y3t.withTempFile)(async a=>{let n=await this.fetch(e),c=0,f=XFe.default.createWriteStream(a);try{for await(let p of n){let h=Buffer.from(p);if(c+=h.length,c>r)throw new $Fe.DownloadLengthMismatchError(\"Max length reached\");await I3t(f,h)}}finally{await m3t.default.promisify(f.close).bind(f)()}return s(a)})}async downloadBytes(e,r){return this.downloadFile(e,r,async s=>{let a=XFe.default.createReadStream(s),n=[];for await(let c of a)n.push(c);return Buffer.concat(n)})}};xg.BaseFetcher=lL;var gJ=class extends lL{constructor(e={}){super(),this.timeout=e.timeout,this.retry=e.retry}async fetch(e){E3t(\"GET %s\",e);let r=await(0,d3t.default)(e,{timeout:this.timeout,retry:this.retry});if(!r.ok||!r?.body)throw new $Fe.DownloadHTTPError(\"Failed to download\",r.status);return r.body}};xg.DefaultFetcher=gJ;var I3t=async(t,e)=>new Promise((r,s)=>{t.write(e,a=>{a&&s(a),r(!0)})})});var eNe=L(uL=>{\"use strict\";Object.defineProperty(uL,\"__esModule\",{value:!0});uL.defaultConfig=void 0;uL.defaultConfig={maxRootRotations:256,maxDelegations:32,rootMaxLength:512e3,timestampMaxLength:16384,snapshotMaxLength:2e6,targetsMaxLength:5e6,prefixTargetsWithHash:!0,fetchTimeout:1e5,fetchRetries:void 0,fetchRetry:2}});var tNe=L(fL=>{\"use strict\";Object.defineProperty(fL,\"__esModule\",{value:!0});fL.TrustedMetadataStore=void 0;var Cs=tL(),Hi=aL(),mJ=class{constructor(e){this.trustedSet={},this.referenceTime=new Date,this.loadTrustedRoot(e)}get root(){if(!this.trustedSet.root)throw new ReferenceError(\"No trusted root metadata\");return this.trustedSet.root}get timestamp(){return this.trustedSet.timestamp}get snapshot(){return this.trustedSet.snapshot}get targets(){return this.trustedSet.targets}getRole(e){return this.trustedSet[e]}updateRoot(e){let r=JSON.parse(e.toString(\"utf8\")),s=Cs.Metadata.fromJSON(Cs.MetadataKind.Root,r);if(s.signed.type!=Cs.MetadataKind.Root)throw new Hi.RepositoryError(`Expected 'root', got ${s.signed.type}`);if(this.root.verifyDelegate(Cs.MetadataKind.Root,s),s.signed.version!=this.root.signed.version+1)throw new Hi.BadVersionError(`Expected version ${this.root.signed.version+1}, got ${s.signed.version}`);return s.verifyDelegate(Cs.MetadataKind.Root,s),this.trustedSet.root=s,s}updateTimestamp(e){if(this.snapshot)throw new Hi.RuntimeError(\"Cannot update timestamp after snapshot\");if(this.root.signed.isExpired(this.referenceTime))throw new Hi.ExpiredMetadataError(\"Final root.json is expired\");let r=JSON.parse(e.toString(\"utf8\")),s=Cs.Metadata.fromJSON(Cs.MetadataKind.Timestamp,r);if(s.signed.type!=Cs.MetadataKind.Timestamp)throw new Hi.RepositoryError(`Expected 'timestamp', got ${s.signed.type}`);if(this.root.verifyDelegate(Cs.MetadataKind.Timestamp,s),this.timestamp){if(s.signed.version<this.timestamp.signed.version)throw new Hi.BadVersionError(`New timestamp version ${s.signed.version} is less than current version ${this.timestamp.signed.version}`);if(s.signed.version===this.timestamp.signed.version)throw new Hi.EqualVersionError(`New timestamp version ${s.signed.version} is equal to current version ${this.timestamp.signed.version}`);let a=this.timestamp.signed.snapshotMeta,n=s.signed.snapshotMeta;if(n.version<a.version)throw new Hi.BadVersionError(`New snapshot version ${n.version} is less than current version ${a.version}`)}return this.trustedSet.timestamp=s,this.checkFinalTimestamp(),s}updateSnapshot(e,r=!1){if(!this.timestamp)throw new Hi.RuntimeError(\"Cannot update snapshot before timestamp\");if(this.targets)throw new Hi.RuntimeError(\"Cannot update snapshot after targets\");this.checkFinalTimestamp();let s=this.timestamp.signed.snapshotMeta;r||s.verify(e);let a=JSON.parse(e.toString(\"utf8\")),n=Cs.Metadata.fromJSON(Cs.MetadataKind.Snapshot,a);if(n.signed.type!=Cs.MetadataKind.Snapshot)throw new Hi.RepositoryError(`Expected 'snapshot', got ${n.signed.type}`);return this.root.verifyDelegate(Cs.MetadataKind.Snapshot,n),this.snapshot&&Object.entries(this.snapshot.signed.meta).forEach(([c,f])=>{let p=n.signed.meta[c];if(!p)throw new Hi.RepositoryError(`Missing file ${c} in new snapshot`);if(p.version<f.version)throw new Hi.BadVersionError(`New version ${p.version} of ${c} is less than current version ${f.version}`)}),this.trustedSet.snapshot=n,this.checkFinalSnapsnot(),n}updateDelegatedTargets(e,r,s){if(!this.snapshot)throw new Hi.RuntimeError(\"Cannot update delegated targets before snapshot\");this.checkFinalSnapsnot();let a=this.trustedSet[s];if(!a)throw new Hi.RuntimeError(`No trusted ${s} metadata`);let n=this.snapshot.signed.meta?.[`${r}.json`];if(!n)throw new Hi.RepositoryError(`Missing ${r}.json in snapshot`);n.verify(e);let c=JSON.parse(e.toString(\"utf8\")),f=Cs.Metadata.fromJSON(Cs.MetadataKind.Targets,c);if(f.signed.type!=Cs.MetadataKind.Targets)throw new Hi.RepositoryError(`Expected 'targets', got ${f.signed.type}`);a.verifyDelegate(r,f);let p=f.signed.version;if(p!=n.version)throw new Hi.BadVersionError(`Version ${p} of ${r} does not match snapshot version ${n.version}`);if(f.signed.isExpired(this.referenceTime))throw new Hi.ExpiredMetadataError(`${r}.json is expired`);this.trustedSet[r]=f}loadTrustedRoot(e){let r=JSON.parse(e.toString(\"utf8\")),s=Cs.Metadata.fromJSON(Cs.MetadataKind.Root,r);if(s.signed.type!=Cs.MetadataKind.Root)throw new Hi.RepositoryError(`Expected 'root', got ${s.signed.type}`);s.verifyDelegate(Cs.MetadataKind.Root,s),this.trustedSet.root=s}checkFinalTimestamp(){if(!this.timestamp)throw new ReferenceError(\"No trusted timestamp metadata\");if(this.timestamp.signed.isExpired(this.referenceTime))throw new Hi.ExpiredMetadataError(\"Final timestamp.json is expired\")}checkFinalSnapsnot(){if(!this.snapshot)throw new ReferenceError(\"No trusted snapshot metadata\");if(!this.timestamp)throw new ReferenceError(\"No trusted timestamp metadata\");if(this.snapshot.signed.isExpired(this.referenceTime))throw new Hi.ExpiredMetadataError(\"snapshot.json is expired\");let e=this.timestamp.signed.snapshotMeta;if(this.snapshot.signed.version!==e.version)throw new Hi.BadVersionError(\"Snapshot version doesn't match timestamp\")}};fL.TrustedMetadataStore=mJ});var rNe=L(yJ=>{\"use strict\";Object.defineProperty(yJ,\"__esModule\",{value:!0});yJ.join=w3t;var C3t=Ie(\"url\");function w3t(t,e){return new C3t.URL(B3t(t)+v3t(e)).toString()}function B3t(t){return t.endsWith(\"/\")?t:t+\"/\"}function v3t(t){return t.startsWith(\"/\")?t.slice(1):t}});var nNe=L(su=>{\"use strict\";var S3t=su&&su.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||(\"get\"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),D3t=su&&su.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,\"default\",{enumerable:!0,value:e})}:function(t,e){t.default=e}),CJ=su&&su.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!==\"default\"&&Object.prototype.hasOwnProperty.call(t,r)&&S3t(e,t,r);return D3t(e,t),e},b3t=su&&su.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(su,\"__esModule\",{value:!0});su.Updater=void 0;var QA=tL(),P3t=b3t(sJ()),d1=CJ(Ie(\"fs\")),AL=CJ(Ie(\"path\")),x3t=eNe(),uy=aL(),k3t=dJ(),Q3t=tNe(),Zb=CJ(rNe()),EJ=(0,P3t.default)(\"tuf:cache\"),IJ=class{constructor(e){let{metadataDir:r,metadataBaseUrl:s,targetDir:a,targetBaseUrl:n,fetcher:c,config:f}=e;this.dir=r,this.metadataBaseUrl=s,this.targetDir=a,this.targetBaseUrl=n,this.forceCache=e.forceCache??!1;let p=this.loadLocalMetadata(QA.MetadataKind.Root);this.trustedSet=new Q3t.TrustedMetadataStore(p),this.config={...x3t.defaultConfig,...f},this.fetcher=c||new k3t.DefaultFetcher({timeout:this.config.fetchTimeout,retry:this.config.fetchRetries??this.config.fetchRetry})}async refresh(){if(this.forceCache)try{await this.loadTimestamp({checkRemote:!1})}catch{await this.loadRoot(),await this.loadTimestamp()}else await this.loadRoot(),await this.loadTimestamp();await this.loadSnapshot(),await this.loadTargets(QA.MetadataKind.Targets,QA.MetadataKind.Root)}async getTargetInfo(e){return this.trustedSet.targets||await this.refresh(),this.preorderDepthFirstWalk(e)}async downloadTarget(e,r,s){let a=r||this.generateTargetPath(e);if(!s){if(!this.targetBaseUrl)throw new uy.ValueError(\"Target base URL not set\");s=this.targetBaseUrl}let n=e.path;if(this.trustedSet.root.signed.consistentSnapshot&&this.config.prefixTargetsWithHash){let p=Object.values(e.hashes),{dir:h,base:E}=AL.parse(n),C=`${p[0]}.${E}`;n=h?`${h}/${C}`:C}let f=Zb.join(s,n);return await this.fetcher.downloadFile(f,e.length,async p=>{await e.verify(d1.createReadStream(p)),EJ(\"WRITE %s\",a),d1.copyFileSync(p,a)}),a}async findCachedTarget(e,r){r||(r=this.generateTargetPath(e));try{if(d1.existsSync(r))return await e.verify(d1.createReadStream(r)),r}catch{return}}loadLocalMetadata(e){let r=AL.join(this.dir,`${e}.json`);return EJ(\"READ %s\",r),d1.readFileSync(r)}async loadRoot(){let r=this.trustedSet.root.signed.version+1,s=r+this.config.maxRootRotations;for(let a=r;a<s;a++){let n=Zb.join(this.metadataBaseUrl,`${a}.root.json`);try{let c=await this.fetcher.downloadBytes(n,this.config.rootMaxLength);this.trustedSet.updateRoot(c),this.persistMetadata(QA.MetadataKind.Root,c)}catch(c){if(c instanceof uy.DownloadHTTPError&&[403,404].includes(c.statusCode))break;throw c}}}async loadTimestamp({checkRemote:e}={checkRemote:!0}){try{let a=this.loadLocalMetadata(QA.MetadataKind.Timestamp);if(this.trustedSet.updateTimestamp(a),!e)return}catch{}let r=Zb.join(this.metadataBaseUrl,\"timestamp.json\"),s=await this.fetcher.downloadBytes(r,this.config.timestampMaxLength);try{this.trustedSet.updateTimestamp(s)}catch(a){if(a instanceof uy.EqualVersionError)return;throw a}this.persistMetadata(QA.MetadataKind.Timestamp,s)}async loadSnapshot(){try{let e=this.loadLocalMetadata(QA.MetadataKind.Snapshot);this.trustedSet.updateSnapshot(e,!0)}catch{if(!this.trustedSet.timestamp)throw new ReferenceError(\"No timestamp metadata\");let r=this.trustedSet.timestamp.signed.snapshotMeta,s=r.length||this.config.snapshotMaxLength,a=this.trustedSet.root.signed.consistentSnapshot?r.version:void 0,n=Zb.join(this.metadataBaseUrl,a?`${a}.snapshot.json`:\"snapshot.json\");try{let c=await this.fetcher.downloadBytes(n,s);this.trustedSet.updateSnapshot(c),this.persistMetadata(QA.MetadataKind.Snapshot,c)}catch(c){throw new uy.RuntimeError(`Unable to load snapshot metadata error ${c}`)}}}async loadTargets(e,r){if(this.trustedSet.getRole(e))return this.trustedSet.getRole(e);try{let s=this.loadLocalMetadata(e);this.trustedSet.updateDelegatedTargets(s,e,r)}catch{if(!this.trustedSet.snapshot)throw new ReferenceError(\"No snapshot metadata\");let a=this.trustedSet.snapshot.signed.meta[`${e}.json`],n=a.length||this.config.targetsMaxLength,c=this.trustedSet.root.signed.consistentSnapshot?a.version:void 0,f=encodeURIComponent(e),p=Zb.join(this.metadataBaseUrl,c?`${c}.${f}.json`:`${f}.json`);try{let h=await this.fetcher.downloadBytes(p,n);this.trustedSet.updateDelegatedTargets(h,e,r),this.persistMetadata(e,h)}catch(h){throw new uy.RuntimeError(`Unable to load targets error ${h}`)}}return this.trustedSet.getRole(e)}async preorderDepthFirstWalk(e){let r=[{roleName:QA.MetadataKind.Targets,parentRoleName:QA.MetadataKind.Root}],s=new Set;for(;s.size<=this.config.maxDelegations&&r.length>0;){let{roleName:a,parentRoleName:n}=r.pop();if(s.has(a))continue;let c=(await this.loadTargets(a,n))?.signed;if(!c)continue;let f=c.targets?.[e];if(f)return f;if(s.add(a),c.delegations){let p=[],h=c.delegations.rolesForTarget(e);for(let{role:E,terminating:C}of h)if(p.push({roleName:E,parentRoleName:a}),C){r.splice(0);break}p.reverse(),r.push(...p)}}}generateTargetPath(e){if(!this.targetDir)throw new uy.ValueError(\"Target directory not set\");let r=encodeURIComponent(e.path);return AL.join(this.targetDir,r)}persistMetadata(e,r){let s=encodeURIComponent(e);try{let a=AL.join(this.dir,`${s}.json`);EJ(\"WRITE %s\",a),d1.writeFileSync(a,r.toString(\"utf8\"))}catch(a){throw new uy.PersistError(`Failed to persist metadata ${s} error: ${a}`)}}};su.Updater=IJ});var iNe=L(kg=>{\"use strict\";Object.defineProperty(kg,\"__esModule\",{value:!0});kg.Updater=kg.BaseFetcher=kg.TargetFile=void 0;var T3t=tL();Object.defineProperty(kg,\"TargetFile\",{enumerable:!0,get:function(){return T3t.TargetFile}});var R3t=dJ();Object.defineProperty(kg,\"BaseFetcher\",{enumerable:!0,get:function(){return R3t.BaseFetcher}});var F3t=nNe();Object.defineProperty(kg,\"Updater\",{enumerable:!0,get:function(){return F3t.Updater}})});var BJ=L(pL=>{\"use strict\";Object.defineProperty(pL,\"__esModule\",{value:!0});pL.TUFError=void 0;var wJ=class extends Error{constructor({code:e,message:r,cause:s}){super(r),this.code=e,this.cause=s,this.name=this.constructor.name}};pL.TUFError=wJ});var sNe=L(Xb=>{\"use strict\";var N3t=Xb&&Xb.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(Xb,\"__esModule\",{value:!0});Xb.readTarget=L3t;var O3t=N3t(Ie(\"fs\")),hL=BJ();async function L3t(t,e){let r=await M3t(t,e);return new Promise((s,a)=>{O3t.default.readFile(r,\"utf-8\",(n,c)=>{n?a(new hL.TUFError({code:\"TUF_READ_TARGET_ERROR\",message:`error reading target ${r}`,cause:n})):s(c)})})}async function M3t(t,e){let r;try{r=await t.getTargetInfo(e)}catch(a){throw new hL.TUFError({code:\"TUF_REFRESH_METADATA_ERROR\",message:\"error refreshing TUF metadata\",cause:a})}if(!r)throw new hL.TUFError({code:\"TUF_FIND_TARGET_ERROR\",message:`target ${e} not found`});let s=await t.findCachedTarget(r);if(!s)try{s=await t.downloadTarget(r)}catch(a){throw new hL.TUFError({code:\"TUF_DOWNLOAD_TARGET_ERROR\",message:`error downloading target ${s}`,cause:a})}return s}});var oNe=L((iwr,_3t)=>{_3t.exports={\"https://tuf-repo-cdn.sigstore.dev\":{\"root.json\":\"ewogInNpZ25hdHVyZXMiOiBbCiAgewogICAia2V5aWQiOiAiNmYyNjAwODlkNTkyM2RhZjIwMTY2Y2E2NTdjNTQzYWY2MTgzNDZhYjk3MTg4NGE5OTk2MmIwMTk4OGJiZTBjMyIsCiAgICJzaWciOiAiMzA0NjAyMjEwMDhhYjFmNmYxN2Q0ZjllNmQ3ZGNmMWM4ODkxMmI2YjUzY2MxMDM4ODY0NGFlMWYwOWJjMzdhMDgyY2QwNjAwM2UwMjIxMDBlMTQ1ZWY0YzdiNzgyZDRlODEwN2I1MzQzN2U2NjlkMDQ3Njg5MmNlOTk5OTAzYWUzM2QxNDQ0ODM2Njk5NmU3IgogIH0sCiAgewogICAia2V5aWQiOiAiZTcxYTU0ZDU0MzgzNWJhODZhZGFkOTQ2MDM3OWM3NjQxZmI4NzI2ZDE2NGVhNzY2ODAxYTFjNTIyYWJhN2VhMiIsCiAgICJzaWciOiAiMzA0NTAyMjEwMGM3NjhiMmY4NmRhOTk1NjkwMTljMTYwYTA4MWRhNTRhZTM2YzM0YzBhMzEyMGQzY2I2OWI1M2I3ZDExMzc1OGUwMjIwNGY2NzE1MThmNjE3YjIwZDQ2NTM3ZmFlNmMzYjYzYmFlODkxM2Y0ZjE5NjIxNTYxMDVjYzRmMDE5YWMzNWM2YSIKICB9LAogIHsKICAgImtleWlkIjogIjIyZjRjYWVjNmQ4ZTZmOTU1NWFmNjZiM2Q0YzNjYjA2YTNiYjIzZmRjN2UzOWM5MTZjNjFmNDYyZTZmNTJiMDYiLAogICAic2lnIjogIjMwNDUwMjIxMDBiNDQzNGU2OTk1ZDM2OGQyM2U3NDc1OWFjZDBjYjkwMTNjODNhNWQzNTExZjBmOTk3ZWM1NGM0NTZhZTQzNTBhMDIyMDE1YjBlMjY1ZDE4MmQyYjYxZGM3NGUxNTVkOThiM2MzZmJlNTY0YmEwNTI4NmFhMTRjOGRmMDJjOWI3NTY1MTYiCiAgfSwKICB7CiAgICJrZXlpZCI6ICI2MTY0MzgzODEyNWI0NDBiNDBkYjY5NDJmNWNiNWEzMWMwZGMwNDM2ODMxNmViMmFhYTU4Yjk1OTA0YTU4MjIyIiwKICAgInNpZyI6ICIzMDQ1MDIyMTAwODJjNTg0MTFkOTg5ZWI5Zjg2MTQxMDg1N2Q0MjM4MTU5MGVjOTQyNGRiZGFhNTFlNzhlZDEzNTE1NDMxOTA0ZTAyMjAxMTgxODVkYTZhNmMyOTQ3MTMxYzE3Nzk3ZTJiYjc2MjBjZTI2ZTVmMzAxZDFjZWFjNWYyYTdlNThmOWRjZjJlIgogIH0sCiAgewogICAia2V5aWQiOiAiYTY4N2U1YmY0ZmFiODJiMGVlNThkNDZlMDVjOTUzNTE0NWEyYzlhZmI0NThmNDNkNDJiNDVjYTBmZGNlMmE3MCIsCiAgICJzaWciOiAiMzA0NjAyMjEwMGM3ODUxMzg1NGNhZTljMzJlYWE2Yjg4ZTE4OTEyZjQ4MDA2YzI3NTdhMjU4ZjkxNzMxMmNhYmE3NTk0OGViOWUwMjIxMDBkOWUxYjRjZTBhZGZlOWZkMmUyMTQ4ZDdmYTI3YTJmNDBiYTExMjJiZDY5ZGE3NjEyZDhkMTc3NmIwMTNjOTFkIgogIH0sCiAgewogICAia2V5aWQiOiAiZmRmYTgzYTA3YjVhODM1ODliODdkZWQ0MWY3N2YzOWQyMzJhZDkxZjdjY2U1Mjg2OGRhY2QwNmJhMDg5ODQ5ZiIsCiAgICJzaWciOiAiMzA0NTAyMjA1NjQ4M2EyZDVkOWVhOWNlYzZlMTFlYWRmYjMzYzQ4NGI2MTQyOThmYWNhMTVhY2YxYzQzMWIxMWVkN2Y3MzRjMDIyMTAwZDBjMWQ3MjZhZjkyYTg3ZTRlNjY0NTljYTVhZGYzOGEwNWI0NGUxZjk0MzE4NDIzZjk1NGJhZThiY2E1YmIyZSIKICB9LAogIHsKICAgImtleWlkIjogImUyZjU5YWNiOTQ4ODUxOTQwN2UxOGNiZmM5MzI5NTEwYmUwM2MwNGFjYTk5MjlkMmYwMzAxMzQzZmVjODU1MjMiLAogICAic2lnIjogIjMwNDYwMjIxMDBkMDA0ZGU4ODAyNGMzMmRjNTY1M2E5ZjQ4NDNjZmM1MjE1NDI3MDQ4YWQ5NjAwZDJjZjljOTY5ZTZlZGZmM2QyMDIyMTAwZDllYmI3OThmNWZjNjZhZjEwODk5ZGVjZTAxNGE4NjI4Y2NmM2M1NDAyY2Q0YTQyNzAyMDc0NzJmOGY2ZTcxMiIKICB9LAogIHsKICAgImtleWlkIjogIjNjMzQ0YWEwNjhmZDRjYzRlODdkYzUwYjYxMmMwMjQzMWZiYzc3MWU5NTAwMzk5MzY4M2EyYjBiZjI2MGNmMGUiLAogICAic2lnIjogIjMwNDYwMjIxMDBiN2IwOTk5NmM0NWNhMmQ0YjA1NjAzZTU2YmFlZmEyOTcxOGEwYjcxMTQ3Y2Y4YzZlNjYzNDliYWE2MTQ3N2RmMDIyMTAwYzRkYTgwYzcxN2I0ZmE3YmJhMGZkNWM3MmRhOGEwNDk5MzU4YjAxMzU4YjIzMDlmNDFkMTQ1NmVhMWU3ZTFkOSIKICB9LAogIHsKICAgImtleWlkIjogImVjODE2Njk3MzRlMDE3OTk2YzViODVmM2QwMmMzZGUxZGQ0NjM3YTE1MjAxOWZlMWFmMTI1ZDJmOTM2OGI5NWUiLAogICAic2lnIjogIjMwNDYwMjIxMDBiZTk3ODJjMzA3NDRlNDExYTgyZmE4NWI1MTM4ZDYwMWNlMTQ4YmMxOTI1OGFlYzY0ZTdlYzI0NDc4ZjM4ODEyMDIyMTAwY2FlZjYzZGNhZjFhNGI5YTUwMGQzYmQwZTNmMTY0ZWMxOGYxYjYzZDdhOTQ2MGQ5YWNhYjEwNjZkYjBmMDE2ZCIKICB9LAogIHsKICAgImtleWlkIjogIjFlMWQ2NWNlOThiMTBhZGRhZDQ3NjRmZWJmN2RkYTJkMDQzNmIzZDNhMzg5MzU3OWMwZGRkYWVhMjBlNTQ4NDkiLAogICAic2lnIjogIjMwNDUwMjIwNzQ2ZWMzZjg1MzRjZTU1NTMxZDBkMDFmZjY0OTY0ZWY0NDBkMWU3ZDJjNGMxNDI0MDliOGU5NzY5ZjFhZGE2ZjAyMjEwMGUzYjkyOWZjZDkzZWExOGZlYWEwODI1ODg3YTcyMTA0ODk4NzlhNjY3ODBjMDdhODNmNGJkNDZlMmYwOWFiM2IiCiAgfQogXSwKICJzaWduZWQiOiB7CiAgIl90eXBlIjogInJvb3QiLAogICJjb25zaXN0ZW50X3NuYXBzaG90IjogdHJ1ZSwKICAiZXhwaXJlcyI6ICIyMDI1LTAyLTE5VDA4OjA0OjMyWiIsCiAgImtleXMiOiB7CiAgICIyMmY0Y2FlYzZkOGU2Zjk1NTVhZjY2YjNkNGMzY2IwNmEzYmIyM2ZkYzdlMzljOTE2YzYxZjQ2MmU2ZjUyYjA2IjogewogICAgImtleWlkX2hhc2hfYWxnb3JpdGhtcyI6IFsKICAgICAic2hhMjU2IiwKICAgICAic2hhNTEyIgogICAgXSwKICAgICJrZXl0eXBlIjogImVjZHNhIiwKICAgICJrZXl2YWwiOiB7CiAgICAgInB1YmxpYyI6ICItLS0tLUJFR0lOIFBVQkxJQyBLRVktLS0tLVxuTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFekJ6Vk9tSENQb2pNVkxTSTM2NFdpaVY4TlByRFxuNklnUnhWbGlza3ovdit5M0pFUjVtY1ZHY09ObGlEY1dNQzVKMmxmSG1qUE5QaGI0SDd4bThMemZTQT09XG4tLS0tLUVORCBQVUJMSUMgS0VZLS0tLS1cbiIKICAgIH0sCiAgICAic2NoZW1lIjogImVjZHNhLXNoYTItbmlzdHAyNTYiLAogICAgIngtdHVmLW9uLWNpLWtleW93bmVyIjogIkBzYW50aWFnb3RvcnJlcyIKICAgfSwKICAgIjYxNjQzODM4MTI1YjQ0MGI0MGRiNjk0MmY1Y2I1YTMxYzBkYzA0MzY4MzE2ZWIyYWFhNThiOTU5MDRhNTgyMjIiOiB7CiAgICAia2V5aWRfaGFzaF9hbGdvcml0aG1zIjogWwogICAgICJzaGEyNTYiLAogICAgICJzaGE1MTIiCiAgICBdLAogICAgImtleXR5cGUiOiAiZWNkc2EiLAogICAgImtleXZhbCI6IHsKICAgICAicHVibGljIjogIi0tLS0tQkVHSU4gUFVCTElDIEtFWS0tLS0tXG5NRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVpbmlrU3NBUW1Za05lSDVlWXEvQ25JekxhYWNPXG54bFNhYXdRRE93cUt5L3RDcXhxNXh4UFNKYzIxSzRXSWhzOUd5T2tLZnp1ZVkzR0lMemNNSlo0Y1d3PT1cbi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLVxuIgogICAgfSwKICAgICJzY2hlbWUiOiAiZWNkc2Etc2hhMi1uaXN0cDI1NiIsCiAgICAieC10dWYtb24tY2kta2V5b3duZXIiOiAiQGJvYmNhbGxhd2F5IgogICB9LAogICAiNmYyNjAwODlkNTkyM2RhZjIwMTY2Y2E2NTdjNTQzYWY2MTgzNDZhYjk3MTg4NGE5OTk2MmIwMTk4OGJiZTBjMyI6IHsKICAgICJrZXlpZF9oYXNoX2FsZ29yaXRobXMiOiBbCiAgICAgInNoYTI1NiIsCiAgICAgInNoYTUxMiIKICAgIF0sCiAgICAia2V5dHlwZSI6ICJlY2RzYSIsCiAgICAia2V5dmFsIjogewogICAgICJwdWJsaWMiOiAiLS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS1cbk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRXk4WEtzbWhCWURJOEpjMEd3ekJ4ZUtheDBjbTVcblNUS0VVNjVIUEZ1blVuNDFzVDhwaTBGak00SWtIei9ZVW13bUxVTzBXdDdseGhqNkJrTElLNHFZQXc9PVxuLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tXG4iCiAgICB9LAogICAgInNjaGVtZSI6ICJlY2RzYS1zaGEyLW5pc3RwMjU2IiwKICAgICJ4LXR1Zi1vbi1jaS1rZXlvd25lciI6ICJAZGxvcmVuYyIKICAgfSwKICAgIjcyNDdmMGRiYWQ4NWIxNDdlMTg2M2JhZGU3NjEyNDNjYzc4NWRjYjdhYTQxMGU3MTA1ZGQzZDJiNjFhMzZkMmMiOiB7CiAgICAia2V5aWRfaGFzaF9hbGdvcml0aG1zIjogWwogICAgICJzaGEyNTYiLAogICAgICJzaGE1MTIiCiAgICBdLAogICAgImtleXR5cGUiOiAiZWNkc2EiLAogICAgImtleXZhbCI6IHsKICAgICAicHVibGljIjogIi0tLS0tQkVHSU4gUFVCTElDIEtFWS0tLS0tXG5NRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVXUmlHcjUraiszSjVTc0grWnRyNW5FMkgyd083XG5CVituTzNzOTNnTGNhMThxVE96SFkxb1d5QUdEeWtNU3NHVFVCU3Q5RCtBbjBLZktzRDJtZlNNNDJRPT1cbi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLVxuIgogICAgfSwKICAgICJzY2hlbWUiOiAiZWNkc2Etc2hhMi1uaXN0cDI1NiIsCiAgICAieC10dWYtb24tY2ktb25saW5lLXVyaSI6ICJnY3BrbXM6Ly9wcm9qZWN0cy9zaWdzdG9yZS1yb290LXNpZ25pbmcvbG9jYXRpb25zL2dsb2JhbC9rZXlSaW5ncy9yb290L2NyeXB0b0tleXMvdGltZXN0YW1wIgogICB9LAogICAiYTY4N2U1YmY0ZmFiODJiMGVlNThkNDZlMDVjOTUzNTE0NWEyYzlhZmI0NThmNDNkNDJiNDVjYTBmZGNlMmE3MCI6IHsKICAgICJrZXlpZF9oYXNoX2FsZ29yaXRobXMiOiBbCiAgICAgInNoYTI1NiIsCiAgICAgInNoYTUxMiIKICAgIF0sCiAgICAia2V5dHlwZSI6ICJlY2RzYSIsCiAgICAia2V5dmFsIjogewogICAgICJwdWJsaWMiOiAiLS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS1cbk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTBnaHJoOTJMdzFZcjNpZEdWNVdxQ3RNREI4Q3hcbitEOGhkQzR3MlpMTklwbFZSb1ZHTHNrWWEzZ2hlTXlPamlKOGtQaTE1YVEyLy83UCtvajdVdkpQR3c9PVxuLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tXG4iCiAgICB9LAogICAgInNjaGVtZSI6ICJlY2RzYS1zaGEyLW5pc3RwMjU2IiwKICAgICJ4LXR1Zi1vbi1jaS1rZXlvd25lciI6ICJAam9zaHVhZ2wiCiAgIH0sCiAgICJlNzFhNTRkNTQzODM1YmE4NmFkYWQ5NDYwMzc5Yzc2NDFmYjg3MjZkMTY0ZWE3NjY4MDFhMWM1MjJhYmE3ZWEyIjogewogICAgImtleWlkX2hhc2hfYWxnb3JpdGhtcyI6IFsKICAgICAic2hhMjU2IiwKICAgICAic2hhNTEyIgogICAgXSwKICAgICJrZXl0eXBlIjogImVjZHNhIiwKICAgICJrZXl2YWwiOiB7CiAgICAgInB1YmxpYyI6ICItLS0tLUJFR0lOIFBVQkxJQyBLRVktLS0tLVxuTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFRVhzejNTWlhGYjhqTVY0Mmo2cEpseWpialI4S1xuTjNCd29jZXhxNkxNSWI1cXNXS09RdkxOMTZOVWVmTGM0SHN3T291bVJzVlZhYWpTcFFTNmZvYmtSdz09XG4tLS0tLUVORCBQVUJMSUMgS0VZLS0tLS1cbiIKICAgIH0sCiAgICAic2NoZW1lIjogImVjZHNhLXNoYTItbmlzdHAyNTYiLAogICAgIngtdHVmLW9uLWNpLWtleW93bmVyIjogIkBtbm02NzgiCiAgIH0KICB9LAogICJyb2xlcyI6IHsKICAgInJvb3QiOiB7CiAgICAia2V5aWRzIjogWwogICAgICI2ZjI2MDA4OWQ1OTIzZGFmMjAxNjZjYTY1N2M1NDNhZjYxODM0NmFiOTcxODg0YTk5OTYyYjAxOTg4YmJlMGMzIiwKICAgICAiZTcxYTU0ZDU0MzgzNWJhODZhZGFkOTQ2MDM3OWM3NjQxZmI4NzI2ZDE2NGVhNzY2ODAxYTFjNTIyYWJhN2VhMiIsCiAgICAgIjIyZjRjYWVjNmQ4ZTZmOTU1NWFmNjZiM2Q0YzNjYjA2YTNiYjIzZmRjN2UzOWM5MTZjNjFmNDYyZTZmNTJiMDYiLAogICAgICI2MTY0MzgzODEyNWI0NDBiNDBkYjY5NDJmNWNiNWEzMWMwZGMwNDM2ODMxNmViMmFhYTU4Yjk1OTA0YTU4MjIyIiwKICAgICAiYTY4N2U1YmY0ZmFiODJiMGVlNThkNDZlMDVjOTUzNTE0NWEyYzlhZmI0NThmNDNkNDJiNDVjYTBmZGNlMmE3MCIKICAgIF0sCiAgICAidGhyZXNob2xkIjogMwogICB9LAogICAic25hcHNob3QiOiB7CiAgICAia2V5aWRzIjogWwogICAgICI3MjQ3ZjBkYmFkODViMTQ3ZTE4NjNiYWRlNzYxMjQzY2M3ODVkY2I3YWE0MTBlNzEwNWRkM2QyYjYxYTM2ZDJjIgogICAgXSwKICAgICJ0aHJlc2hvbGQiOiAxLAogICAgIngtdHVmLW9uLWNpLWV4cGlyeS1wZXJpb2QiOiAzNjUwLAogICAgIngtdHVmLW9uLWNpLXNpZ25pbmctcGVyaW9kIjogMzY1CiAgIH0sCiAgICJ0YXJnZXRzIjogewogICAgImtleWlkcyI6IFsKICAgICAiNmYyNjAwODlkNTkyM2RhZjIwMTY2Y2E2NTdjNTQzYWY2MTgzNDZhYjk3MTg4NGE5OTk2MmIwMTk4OGJiZTBjMyIsCiAgICAgImU3MWE1NGQ1NDM4MzViYTg2YWRhZDk0NjAzNzljNzY0MWZiODcyNmQxNjRlYTc2NjgwMWExYzUyMmFiYTdlYTIiLAogICAgICIyMmY0Y2FlYzZkOGU2Zjk1NTVhZjY2YjNkNGMzY2IwNmEzYmIyM2ZkYzdlMzljOTE2YzYxZjQ2MmU2ZjUyYjA2IiwKICAgICAiNjE2NDM4MzgxMjViNDQwYjQwZGI2OTQyZjVjYjVhMzFjMGRjMDQzNjgzMTZlYjJhYWE1OGI5NTkwNGE1ODIyMiIsCiAgICAgImE2ODdlNWJmNGZhYjgyYjBlZTU4ZDQ2ZTA1Yzk1MzUxNDVhMmM5YWZiNDU4ZjQzZDQyYjQ1Y2EwZmRjZTJhNzAiCiAgICBdLAogICAgInRocmVzaG9sZCI6IDMKICAgfSwKICAgInRpbWVzdGFtcCI6IHsKICAgICJrZXlpZHMiOiBbCiAgICAgIjcyNDdmMGRiYWQ4NWIxNDdlMTg2M2JhZGU3NjEyNDNjYzc4NWRjYjdhYTQxMGU3MTA1ZGQzZDJiNjFhMzZkMmMiCiAgICBdLAogICAgInRocmVzaG9sZCI6IDEsCiAgICAieC10dWYtb24tY2ktZXhwaXJ5LXBlcmlvZCI6IDcsCiAgICAieC10dWYtb24tY2ktc2lnbmluZy1wZXJpb2QiOiA0CiAgIH0KICB9LAogICJzcGVjX3ZlcnNpb24iOiAiMS4wIiwKICAidmVyc2lvbiI6IDEwLAogICJ4LXR1Zi1vbi1jaS1leHBpcnktcGVyaW9kIjogMTgyLAogICJ4LXR1Zi1vbi1jaS1zaWduaW5nLXBlcmlvZCI6IDMxCiB9Cn0=\",targets:{\"trusted_root.json\":\"ewogICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLmRldi5zaWdzdG9yZS50cnVzdGVkcm9vdCtqc29uO3ZlcnNpb249MC4xIiwKICAidGxvZ3MiOiBbCiAgICB7CiAgICAgICJiYXNlVXJsIjogImh0dHBzOi8vcmVrb3Iuc2lnc3RvcmUuZGV2IiwKICAgICAgImhhc2hBbGdvcml0aG0iOiAiU0hBMl8yNTYiLAogICAgICAicHVibGljS2V5IjogewogICAgICAgICJyYXdCeXRlcyI6ICJNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUyRzJZKzJ0YWJkVFY1QmNHaUJJeDBhOWZBRndya0JibUxTR3RrczRMM3FYNnlZWTB6dWZCbmhDOFVyL2l5NTVHaFdQLzlBL2JZMkxoQzMwTTkrUll0dz09IiwKICAgICAgICAia2V5RGV0YWlscyI6ICJQS0lYX0VDRFNBX1AyNTZfU0hBXzI1NiIsCiAgICAgICAgInZhbGlkRm9yIjogewogICAgICAgICAgInN0YXJ0IjogIjIwMjEtMDEtMTJUMTE6NTM6MjcuMDAwWiIKICAgICAgICB9CiAgICAgIH0sCiAgICAgICJsb2dJZCI6IHsKICAgICAgICAia2V5SWQiOiAid05JOWF0UUdseitWV2ZPNkxSeWdINFFVZlkvOFc0UkZ3aVQ1aTVXUmdCMD0iCiAgICAgIH0KICAgIH0KICBdLAogICJjZXJ0aWZpY2F0ZUF1dGhvcml0aWVzIjogWwogICAgewogICAgICAic3ViamVjdCI6IHsKICAgICAgICAib3JnYW5pemF0aW9uIjogInNpZ3N0b3JlLmRldiIsCiAgICAgICAgImNvbW1vbk5hbWUiOiAic2lnc3RvcmUiCiAgICAgIH0sCiAgICAgICJ1cmkiOiAiaHR0cHM6Ly9mdWxjaW8uc2lnc3RvcmUuZGV2IiwKICAgICAgImNlcnRDaGFpbiI6IHsKICAgICAgICAiY2VydGlmaWNhdGVzIjogWwogICAgICAgICAgewogICAgICAgICAgICAicmF3Qnl0ZXMiOiAiTUlJQitEQ0NBWDZnQXdJQkFnSVROVmtEWm9DaW9mUERzeTdkZm02Z2VMYnVoekFLQmdncWhrak9QUVFEQXpBcU1SVXdFd1lEVlFRS0V3eHphV2R6ZEc5eVpTNWtaWFl4RVRBUEJnTlZCQU1UQ0hOcFozTjBiM0psTUI0WERUSXhNRE13TnpBek1qQXlPVm9YRFRNeE1ESXlNekF6TWpBeU9Wb3dLakVWTUJNR0ExVUVDaE1NYzJsbmMzUnZjbVV1WkdWMk1SRXdEd1lEVlFRREV3aHphV2R6ZEc5eVpUQjJNQkFHQnlxR1NNNDlBZ0VHQlN1QkJBQWlBMklBQkxTeUE3SWk1aytwTk84WkVXWTB5bGVtV0Rvd09rTmEza0wrR1pFNVo1R1dlaEw5L0E5YlJOQTNSYnJzWjVpMEpjYXN0YVJMN1NwNWZwL2pENWR4cWMvVWRUVm5sdlMxNmFuKzJZZnN3ZS9RdUxvbFJVQ3JjT0UyKzJpQTUrdHpkNk5tTUdRd0RnWURWUjBQQVFIL0JBUURBZ0VHTUJJR0ExVWRFd0VCL3dRSU1BWUJBZjhDQVFFd0hRWURWUjBPQkJZRUZNakZIUUJCbWlRcE1sRWs2dzJ1U3UxS0J0UHNNQjhHQTFVZEl3UVlNQmFBRk1qRkhRQkJtaVFwTWxFazZ3MnVTdTFLQnRQc01Bb0dDQ3FHU000OUJBTURBMmdBTUdVQ01IOGxpV0pmTXVpNnZYWEJoakRnWTRNd3NsbU4vVEp4VmUvODNXckZvbXdtTmYwNTZ5MVg0OEY5YzRtM2Ezb3pYQUl4QUtqUmF5NS9hai9qc0tLR0lrbVFhdGpJOHV1cEhyLytDeEZ2YUpXbXBZcU5rTERHUlUrOW9yemg1aEkyUnJjdWFRPT0iCiAgICAgICAgICB9CiAgICAgICAgXQogICAgICB9LAogICAgICAidmFsaWRGb3IiOiB7CiAgICAgICAgInN0YXJ0IjogIjIwMjEtMDMtMDdUMDM6MjA6MjkuMDAwWiIsCiAgICAgICAgImVuZCI6ICIyMDIyLTEyLTMxVDIzOjU5OjU5Ljk5OVoiCiAgICAgIH0KICAgIH0sCiAgICB7CiAgICAgICJzdWJqZWN0IjogewogICAgICAgICJvcmdhbml6YXRpb24iOiAic2lnc3RvcmUuZGV2IiwKICAgICAgICAiY29tbW9uTmFtZSI6ICJzaWdzdG9yZSIKICAgICAgfSwKICAgICAgInVyaSI6ICJodHRwczovL2Z1bGNpby5zaWdzdG9yZS5kZXYiLAogICAgICAiY2VydENoYWluIjogewogICAgICAgICJjZXJ0aWZpY2F0ZXMiOiBbCiAgICAgICAgICB7CiAgICAgICAgICAgICJyYXdCeXRlcyI6ICJNSUlDR2pDQ0FhR2dBd0lCQWdJVUFMblZpVmZuVTBickphc21Sa0hybi9VbmZhUXdDZ1lJS29aSXpqMEVBd013S2pFVk1CTUdBMVVFQ2hNTWMybG5jM1J2Y21VdVpHVjJNUkV3RHdZRFZRUURFd2h6YVdkemRHOXlaVEFlRncweU1qQTBNVE15TURBMk1UVmFGdzB6TVRFd01EVXhNelUyTlRoYU1EY3hGVEFUQmdOVkJBb1RESE5wWjNOMGIzSmxMbVJsZGpFZU1Cd0dBMVVFQXhNVmMybG5jM1J2Y21VdGFXNTBaWEp0WldScFlYUmxNSFl3RUFZSEtvWkl6ajBDQVFZRks0RUVBQ0lEWWdBRThSVlMveXNIK05PdnVEWnlQSVp0aWxnVUY5TmxhcllwQWQ5SFAxdkJCSDFVNUNWNzdMU1M3czBaaUg0bkU3SHY3cHRTNkx2dlIvU1RrNzk4TFZnTXpMbEo0SGVJZkYzdEhTYWV4TGNZcFNBU3Ixa1MwTi9SZ0JKei85aldDaVhubzNzd2VUQU9CZ05WSFE4QkFmOEVCQU1DQVFZd0V3WURWUjBsQkF3d0NnWUlLd1lCQlFVSEF3TXdFZ1lEVlIwVEFRSC9CQWd3QmdFQi93SUJBREFkQmdOVkhRNEVGZ1FVMzlQcHoxWWtFWmI1cU5qcEtGV2l4aTRZWkQ4d0h3WURWUjBqQkJnd0ZvQVVXTUFlWDVGRnBXYXBlc3lRb1pNaTBDckZ4Zm93Q2dZSUtvWkl6ajBFQXdNRFp3QXdaQUl3UENzUUs0RFlpWllEUElhRGk1SEZLbmZ4WHg2QVNTVm1FUmZzeW5ZQmlYMlg2U0pSblpVODQvOURaZG5GdnZ4bUFqQk90NlFwQmxjNEovMER4dmtUQ3FwY2x2emlMNkJDQ1BuamRsSUIzUHUzQnhzUG15Z1VZN0lpMnpiZENkbGlpb3c9IgogICAgICAgICAgfSwKICAgICAgICAgIHsKICAgICAgICAgICAgInJhd0J5dGVzIjogIk1JSUI5ekNDQVh5Z0F3SUJBZ0lVQUxaTkFQRmR4SFB3amVEbG9Ed3lZQ2hBTy80d0NnWUlLb1pJemowRUF3TXdLakVWTUJNR0ExVUVDaE1NYzJsbmMzUnZjbVV1WkdWMk1SRXdEd1lEVlFRREV3aHphV2R6ZEc5eVpUQWVGdzB5TVRFd01EY3hNelUyTlRsYUZ3MHpNVEV3TURVeE16VTJOVGhhTUNveEZUQVRCZ05WQkFvVERITnBaM04wYjNKbExtUmxkakVSTUE4R0ExVUVBeE1JYzJsbmMzUnZjbVV3ZGpBUUJnY3Foa2pPUFFJQkJnVXJnUVFBSWdOaUFBVDdYZUZUNHJiM1BRR3dTNElhanRMazMvT2xucGdhbmdhQmNsWXBzWUJyNWkrNHluQjA3Y2ViM0xQME9JT1pkeGV4WDY5YzVpVnV5SlJRK0h6MDV5aStVRjN1QldBbEhwaVM1c2gwK0gyR0hFN1NYcmsxRUM1bTFUcjE5TDlnZzkyall6QmhNQTRHQTFVZER3RUIvd1FFQXdJQkJqQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01CMEdBMVVkRGdRV0JCUll3QjVma1VXbFpxbDZ6SkNoa3lMUUtzWEYrakFmQmdOVkhTTUVHREFXZ0JSWXdCNWZrVVdsWnFsNnpKQ2hreUxRS3NYRitqQUtCZ2dxaGtqT1BRUURBd05wQURCbUFqRUFqMW5IZVhacCsxM05XQk5hK0VEc0RQOEcxV1dnMXRDTVdQL1dIUHFwYVZvMGpoc3dlTkZaZ1NzMGVFN3dZSTRxQWpFQTJXQjlvdDk4c0lrb0YzdlpZZGQzL1Z0V0I1YjlUTk1lYTdJeC9zdEo1VGZjTExlQUJMRTRCTkpPc1E0dm5CSEoiCiAgICAgICAgICB9CiAgICAgICAgXQogICAgICB9LAogICAgICAidmFsaWRGb3IiOiB7CiAgICAgICAgInN0YXJ0IjogIjIwMjItMDQtMTNUMjA6MDY6MTUuMDAwWiIKICAgICAgfQogICAgfQogIF0sCiAgImN0bG9ncyI6IFsKICAgIHsKICAgICAgImJhc2VVcmwiOiAiaHR0cHM6Ly9jdGZlLnNpZ3N0b3JlLmRldi90ZXN0IiwKICAgICAgImhhc2hBbGdvcml0aG0iOiAiU0hBMl8yNTYiLAogICAgICAicHVibGljS2V5IjogewogICAgICAgICJyYXdCeXRlcyI6ICJNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUViZndSK1JKdWRYc2NnUkJScEtYMVhGRHkzUHl1ZER4ei9TZm5SaTFmVDhla3BmQmQyTzF1b3o3anIzWjhuS3p4QTY5RVVRK2VGQ0ZJM3pldWJQV1U3dz09IiwKICAgICAgICAia2V5RGV0YWlscyI6ICJQS0lYX0VDRFNBX1AyNTZfU0hBXzI1NiIsCiAgICAgICAgInZhbGlkRm9yIjogewogICAgICAgICAgInN0YXJ0IjogIjIwMjEtMDMtMTRUMDA6MDA6MDAuMDAwWiIsCiAgICAgICAgICAiZW5kIjogIjIwMjItMTAtMzFUMjM6NTk6NTkuOTk5WiIKICAgICAgICB9CiAgICAgIH0sCiAgICAgICJsb2dJZCI6IHsKICAgICAgICAia2V5SWQiOiAiQ0dDUzhDaFMvMmhGMGRGcko0U2NSV2NZckJZOXd6alNiZWE4SWdZMmIzST0iCiAgICAgIH0KICAgIH0sCiAgICB7CiAgICAgICJiYXNlVXJsIjogImh0dHBzOi8vY3RmZS5zaWdzdG9yZS5kZXYvMjAyMiIsCiAgICAgICJoYXNoQWxnb3JpdGhtIjogIlNIQTJfMjU2IiwKICAgICAgInB1YmxpY0tleSI6IHsKICAgICAgICAicmF3Qnl0ZXMiOiAiTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFaVBTbEZpMENtRlRmRWpDVXFGOUh1Q0VjWVhOS0FhWWFsSUptQlo4eXllelBqVHFoeHJLQnBNbmFvY1Z0TEpCSTFlTTN1WG5RelFHQUpkSjRnczlGeXc9PSIsCiAgICAgICAgImtleURldGFpbHMiOiAiUEtJWF9FQ0RTQV9QMjU2X1NIQV8yNTYiLAogICAgICAgICJ2YWxpZEZvciI6IHsKICAgICAgICAgICJzdGFydCI6ICIyMDIyLTEwLTIwVDAwOjAwOjAwLjAwMFoiCiAgICAgICAgfQogICAgICB9LAogICAgICAibG9nSWQiOiB7CiAgICAgICAgImtleUlkIjogIjNUMHdhc2JIRVRKakdSNGNtV2MzQXFKS1hyamVQSzMvaDRweWdDOHA3bzQ9IgogICAgICB9CiAgICB9CiAgXSwKICAidGltZXN0YW1wQXV0aG9yaXRpZXMiOiBbCiAgICB7CiAgICAgICJzdWJqZWN0IjogewogICAgICAgICJvcmdhbml6YXRpb24iOiAiR2l0SHViLCBJbmMuIiwKICAgICAgICAiY29tbW9uTmFtZSI6ICJJbnRlcm5hbCBTZXJ2aWNlcyBSb290IgogICAgICB9LAogICAgICAiY2VydENoYWluIjogewogICAgICAgICJjZXJ0aWZpY2F0ZXMiOiBbCiAgICAgICAgICB7CiAgICAgICAgICAgICJyYXdCeXRlcyI6ICJNSUlCM0RDQ0FXS2dBd0lCQWdJVWNoa05zSDM2WGEwNGIxTHFJYytxcjlEVmVjTXdDZ1lJS29aSXpqMEVBd013TWpFVk1CTUdBMVVFQ2hNTVIybDBTSFZpTENCSmJtTXVNUmt3RndZRFZRUURFeEJVVTBFZ2FXNTBaWEp0WldScFlYUmxNQjRYRFRJek1EUXhOREF3TURBd01Gb1hEVEkwTURReE16QXdNREF3TUZvd01qRVZNQk1HQTFVRUNoTU1SMmwwU0hWaUxDQkpibU11TVJrd0Z3WURWUVFERXhCVVUwRWdWR2x0WlhOMFlXMXdhVzVuTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFVUQ1Wk5iU3FZTWQ2cjhxcE9PRVg5aWJHblpUOUdzdVhPaHIvZjhVOUZKdWdCR0V4S1lwNDBPVUxTMGVyalpXN3hWOXhWNTJObkpmNU9lRHE0ZTVaS3FOV01GUXdEZ1lEVlIwUEFRSC9CQVFEQWdlQU1CTUdBMVVkSlFRTU1Bb0dDQ3NHQVFVRkJ3TUlNQXdHQTFVZEV3RUIvd1FDTUFBd0h3WURWUjBqQkJnd0ZvQVVhVzFSdWRPZ1Z0MGxlcVkwV0tZYnVQcjQ3d0F3Q2dZSUtvWkl6ajBFQXdNRGFBQXdaUUl3YlVIOUh2RDRlakNaSk9XUW5xQWxrcVVSbGx2dTlNOCtWcUxiaVJLK3pTZlpDWndzaWxqUm44TVFRUlNrWEVFNUFqRUFnK1Z4cXRvamZWZnU4RGh6emhDeDlHS0VUYkpIYjE5aVY3Mm1NS1ViREFGbXpaNmJROGI1NFpiOHRpZHk1YVdlIgogICAgICAgICAgfSwKICAgICAgICAgIHsKICAgICAgICAgICAgInJhd0J5dGVzIjogIk1JSUNFRENDQVpXZ0F3SUJBZ0lVWDhaTzVRWFA3dk40ZE1RNWU5c1UzbnViOE9nd0NnWUlLb1pJemowRUF3TXdPREVWTUJNR0ExVUVDaE1NUjJsMFNIVmlMQ0JKYm1NdU1SOHdIUVlEVlFRREV4WkpiblJsY201aGJDQlRaWEoyYVdObGN5QlNiMjkwTUI0WERUSXpNRFF4TkRBd01EQXdNRm9YRFRJNE1EUXhNakF3TURBd01Gb3dNakVWTUJNR0ExVUVDaE1NUjJsMFNIVmlMQ0JKYm1NdU1Sa3dGd1lEVlFRREV4QlVVMEVnYVc1MFpYSnRaV1JwWVhSbE1IWXdFQVlIS29aSXpqMENBUVlGSzRFRUFDSURZZ0FFdk1MWS9kVFZidklKWUFOQXVzekV3Sm5RRTFsbGZ0eW55TUtJTWhoNDhIbXFiVnI1eWd5YnpzTFJMVktiQldPZFoyMWFlSnorZ1ppeXRaZXRxY3lGOVdsRVI1TkVNZjZKVjdaTm9qUXB4SHE0UkhHb0dTY2VRdi9xdlRpWnhFREtvMll3WkRBT0JnTlZIUThCQWY4RUJBTUNBUVl3RWdZRFZSMFRBUUgvQkFnd0JnRUIvd0lCQURBZEJnTlZIUTRFRmdRVWFXMVJ1ZE9nVnQwbGVxWTBXS1lidVByNDd3QXdId1lEVlIwakJCZ3dGb0FVOU5ZWWxvYm5BRzRjMC9xanh5SC9scS93eitRd0NnWUlLb1pJemowRUF3TURhUUF3WmdJeEFLMUIxODV5Z0NySVlGbElzM0dqc3dqbndTTUc2TFk4d29MVmRha0tEWnhWYThmOGNxTXMxRGhjeEowKzA5dzk1UUl4QU8rdEJ6Wms3dmpVSjlpSmdENFI2WldUeFFXS3FObTc0ak85OW8rbzlzdjRGSS9TWlRaVEZ5TW4wSUpFSGRObXlBPT0iCiAgICAgICAgICB9LAogICAgICAgICAgewogICAgICAgICAgICAicmF3Qnl0ZXMiOiAiTUlJQjlEQ0NBWHFnQXdJQkFnSVVhL0pBa2RVaks0SlV3c3F0YWlSSkdXaHFMU293Q2dZSUtvWkl6ajBFQXdNd09ERVZNQk1HQTFVRUNoTU1SMmwwU0hWaUxDQkpibU11TVI4d0hRWURWUVFERXhaSmJuUmxjbTVoYkNCVFpYSjJhV05sY3lCU2IyOTBNQjRYRFRJek1EUXhOREF3TURBd01Gb1hEVE16TURReE1UQXdNREF3TUZvd09ERVZNQk1HQTFVRUNoTU1SMmwwU0hWaUxDQkpibU11TVI4d0hRWURWUVFERXhaSmJuUmxjbTVoYkNCVFpYSjJhV05sY3lCU2IyOTBNSFl3RUFZSEtvWkl6ajBDQVFZRks0RUVBQ0lEWWdBRWY5akZBWHh6NGt4NjhBSFJNT2tGQmhmbERjTVR2emFYejR4L0ZDY1hqSi8xcUVLb24vcVBJR25hVVJza0R0eU5iTkRPcGVKVERERnF0NDhpTVBybnpweDZJWndxZW1mVUpONHhCRVpmemErcFl0L2l5b2QrOXRacjIwUlJXU3YvbzBVd1F6QU9CZ05WSFE4QkFmOEVCQU1DQVFZd0VnWURWUjBUQVFIL0JBZ3dCZ0VCL3dJQkFqQWRCZ05WSFE0RUZnUVU5TllZbG9ibkFHNGMwL3FqeHlIL2xxL3d6K1F3Q2dZSUtvWkl6ajBFQXdNRGFBQXdaUUl4QUxaTFo4QmdSWHpLeExNTU45VklsTytlNGhyQm5OQmdGN3R6N0hucm93djJOZXRaRXJJQUNLRnltQmx2V0R2dE1BSXdaTytraTZzc1ExYnNabzk4TzhtRUFmMk5aN2lpQ2dERFUwVndqZWNvNnp5ZWgwekJUczkvN2dWNkFITlE1M3hEIgogICAgICAgICAgfQogICAgICAgIF0KICAgICAgfSwKICAgICAgInZhbGlkRm9yIjogewogICAgICAgICJzdGFydCI6ICIyMDIzLTA0LTE0VDAwOjAwOjAwLjAwMFoiCiAgICAgIH0KICAgIH0KICBdCn0K\",\"registry.npmjs.org%2Fkeys.json\":\"ewogICAgImtleXMiOiBbCiAgICAgICAgewogICAgICAgICAgICAia2V5SWQiOiAiU0hBMjU2OmpsM2J3c3d1ODBQampva0NnaDBvMnc1YzJVNExoUUFFNTdnajljejFrekEiLAogICAgICAgICAgICAia2V5VXNhZ2UiOiAibnBtOnNpZ25hdHVyZXMiLAogICAgICAgICAgICAicHVibGljS2V5IjogewogICAgICAgICAgICAgICAgInJhd0J5dGVzIjogIk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTFPbGIzek1BRkZ4WEtIaUlrUU81Y0ozWWhsNWk2VVBwK0lodXRlQkpidUhjQTVVb2dLbzBFV3RsV3dXNktTYUtvVE5FWUw3SmxDUWlWbmtoQmt0VWdnPT0iLAogICAgICAgICAgICAgICAgImtleURldGFpbHMiOiAiUEtJWF9FQ0RTQV9QMjU2X1NIQV8yNTYiLAogICAgICAgICAgICAgICAgInZhbGlkRm9yIjogewogICAgICAgICAgICAgICAgICAgICJzdGFydCI6ICIxOTk5LTAxLTAxVDAwOjAwOjAwLjAwMFoiLAogICAgICAgICAgICAgICAgICAgICJlbmQiOiAiMjAyNS0wMS0yOVQwMDowMDowMC4wMDBaIgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAgICJrZXlJZCI6ICJTSEEyNTY6amwzYndzd3U4MFBqam9rQ2doMG8ydzVjMlU0TGhRQUU1N2dqOWN6MWt6QSIsCiAgICAgICAgICAgICJrZXlVc2FnZSI6ICJucG06YXR0ZXN0YXRpb25zIiwKICAgICAgICAgICAgInB1YmxpY0tleSI6IHsKICAgICAgICAgICAgICAgICJyYXdCeXRlcyI6ICJNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUxT2xiM3pNQUZGeFhLSGlJa1FPNWNKM1lobDVpNlVQcCtJaHV0ZUJKYnVIY0E1VW9nS28wRVd0bFd3VzZLU2FLb1RORVlMN0psQ1FpVm5raEJrdFVnZz09IiwKICAgICAgICAgICAgICAgICJrZXlEZXRhaWxzIjogIlBLSVhfRUNEU0FfUDI1Nl9TSEFfMjU2IiwKICAgICAgICAgICAgICAgICJ2YWxpZEZvciI6IHsKICAgICAgICAgICAgICAgICAgICAic3RhcnQiOiAiMjAyMi0xMi0wMVQwMDowMDowMC4wMDBaIiwKICAgICAgICAgICAgICAgICAgICAiZW5kIjogIjIwMjUtMDEtMjlUMDA6MDA6MDAuMDAwWiIKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgewogICAgICAgICAgICAia2V5SWQiOiAiU0hBMjU2OkRoUTh3UjVBUEJ2RkhMRi8rVGMrQVl2UE9kVHBjSURxT2h4c0JIUndDN1UiLAogICAgICAgICAgICAia2V5VXNhZ2UiOiAibnBtOnNpZ25hdHVyZXMiLAogICAgICAgICAgICAicHVibGljS2V5IjogewogICAgICAgICAgICAgICAgInJhd0J5dGVzIjogIk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRVk2WWE3VysrN2FVUHp2TVRyZXpINlljeDNjK0hPS1lDY05HeWJKWlNDSnEvZmQ3UWE4dXVBS3RkSWtVUXRRaUVLRVJoQW1FNWxNTUpoUDhPa0RPYTJnPT0iLAogICAgICAgICAgICAgICAgImtleURldGFpbHMiOiAiUEtJWF9FQ0RTQV9QMjU2X1NIQV8yNTYiLAogICAgICAgICAgICAgICAgInZhbGlkRm9yIjogewogICAgICAgICAgICAgICAgICAgICJzdGFydCI6ICIyMDI1LTAxLTEzVDAwOjAwOjAwLjAwMFoiCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICB9LAogICAgICAgIHsKICAgICAgICAgICAgImtleUlkIjogIlNIQTI1NjpEaFE4d1I1QVBCdkZITEYvK1RjK0FZdlBPZFRwY0lEcU9oeHNCSFJ3QzdVIiwKICAgICAgICAgICAgImtleVVzYWdlIjogIm5wbTphdHRlc3RhdGlvbnMiLAogICAgICAgICAgICAicHVibGljS2V5IjogewogICAgICAgICAgICAgICAgInJhd0J5dGVzIjogIk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRVk2WWE3VysrN2FVUHp2TVRyZXpINlljeDNjK0hPS1lDY05HeWJKWlNDSnEvZmQ3UWE4dXVBS3RkSWtVUXRRaUVLRVJoQW1FNWxNTUpoUDhPa0RPYTJnPT0iLAogICAgICAgICAgICAgICAgImtleURldGFpbHMiOiAiUEtJWF9FQ0RTQV9QMjU2X1NIQV8yNTYiLAogICAgICAgICAgICAgICAgInZhbGlkRm9yIjogewogICAgICAgICAgICAgICAgICAgICJzdGFydCI6ICIyMDI1LTAxLTEzVDAwOjAwOjAwLjAwMFoiCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICBdCn0K\"}}}});var lNe=L(m1=>{\"use strict\";var aNe=m1&&m1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(m1,\"__esModule\",{value:!0});m1.TUFClient=void 0;var Qg=aNe(Ie(\"fs\")),$b=aNe(Ie(\"path\")),U3t=iNe(),H3t=gL(),j3t=sNe(),SJ=\"targets\",vJ=class{constructor(e){let r=new URL(e.mirrorURL),s=encodeURIComponent(r.host+r.pathname.replace(/\\/$/,\"\")),a=$b.default.join(e.cachePath,s);q3t(a),G3t({cachePath:a,mirrorURL:e.mirrorURL,tufRootPath:e.rootPath,forceInit:e.forceInit}),this.updater=W3t({mirrorURL:e.mirrorURL,cachePath:a,forceCache:e.forceCache,retry:e.retry,timeout:e.timeout})}async refresh(){return this.updater.refresh()}getTarget(e){return(0,j3t.readTarget)(this.updater,e)}};m1.TUFClient=vJ;function q3t(t){let e=$b.default.join(t,SJ);Qg.default.existsSync(t)||Qg.default.mkdirSync(t,{recursive:!0}),Qg.default.existsSync(e)||Qg.default.mkdirSync(e)}function G3t({cachePath:t,mirrorURL:e,tufRootPath:r,forceInit:s}){let a=$b.default.join(t,\"root.json\");if(!Qg.default.existsSync(a)||s)if(r)Qg.default.copyFileSync(r,a);else{let c=oNe()[e];if(!c)throw new H3t.TUFError({code:\"TUF_INIT_CACHE_ERROR\",message:`No root.json found for mirror: ${e}`});Qg.default.writeFileSync(a,Buffer.from(c[\"root.json\"],\"base64\")),Object.entries(c.targets).forEach(([f,p])=>{Qg.default.writeFileSync($b.default.join(t,SJ,f),Buffer.from(p,\"base64\"))})}}function W3t(t){let e={fetchTimeout:t.timeout,fetchRetry:t.retry};return new U3t.Updater({metadataBaseUrl:t.mirrorURL,targetBaseUrl:`${t.mirrorURL}/targets`,metadataDir:t.cachePath,targetDir:$b.default.join(t.cachePath,SJ),forceCache:t.forceCache,config:e})}});var gL=L(mh=>{\"use strict\";Object.defineProperty(mh,\"__esModule\",{value:!0});mh.TUFError=mh.DEFAULT_MIRROR_URL=void 0;mh.getTrustedRoot=$3t;mh.initTUF=e8t;var Y3t=bb(),V3t=HRe(),K3t=lNe();mh.DEFAULT_MIRROR_URL=\"https://tuf-repo-cdn.sigstore.dev\";var J3t=\"sigstore-js\",z3t={retries:2},Z3t=5e3,X3t=\"trusted_root.json\";async function $3t(t={}){let r=await cNe(t).getTarget(X3t);return Y3t.TrustedRoot.fromJSON(JSON.parse(r))}async function e8t(t={}){let e=cNe(t);return e.refresh().then(()=>e)}function cNe(t){return new K3t.TUFClient({cachePath:t.cachePath||(0,V3t.appDataPath)(J3t),rootPath:t.rootPath,mirrorURL:t.mirrorURL||mh.DEFAULT_MIRROR_URL,retry:t.retry??z3t,timeout:t.timeout??Z3t,forceCache:t.forceCache??!1,forceInit:t.forceInit??t.force??!1})}var t8t=BJ();Object.defineProperty(mh,\"TUFError\",{enumerable:!0,get:function(){return t8t.TUFError}})});var uNe=L(dL=>{\"use strict\";Object.defineProperty(dL,\"__esModule\",{value:!0});dL.DSSESignatureContent=void 0;var eP=wl(),DJ=class{constructor(e){this.env=e}compareDigest(e){return eP.crypto.bufferEqual(e,eP.crypto.digest(\"sha256\",this.env.payload))}compareSignature(e){return eP.crypto.bufferEqual(e,this.signature)}verifySignature(e){return eP.crypto.verify(this.preAuthEncoding,e,this.signature)}get signature(){return this.env.signatures.length>0?this.env.signatures[0].sig:Buffer.from(\"\")}get preAuthEncoding(){return eP.dsse.preAuthEncoding(this.env.payloadType,this.env.payload)}};dL.DSSESignatureContent=DJ});var fNe=L(mL=>{\"use strict\";Object.defineProperty(mL,\"__esModule\",{value:!0});mL.MessageSignatureContent=void 0;var bJ=wl(),PJ=class{constructor(e,r){this.signature=e.signature,this.messageDigest=e.messageDigest.digest,this.artifact=r}compareSignature(e){return bJ.crypto.bufferEqual(e,this.signature)}compareDigest(e){return bJ.crypto.bufferEqual(e,this.messageDigest)}verifySignature(e){return bJ.crypto.verify(this.artifact,e,this.signature)}};mL.MessageSignatureContent=PJ});var pNe=L(yL=>{\"use strict\";Object.defineProperty(yL,\"__esModule\",{value:!0});yL.toSignedEntity=i8t;yL.signatureContent=ANe;var xJ=wl(),r8t=uNe(),n8t=fNe();function i8t(t,e){let{tlogEntries:r,timestampVerificationData:s}=t.verificationMaterial,a=[];for(let n of r)a.push({$case:\"transparency-log\",tlogEntry:n});for(let n of s?.rfc3161Timestamps??[])a.push({$case:\"timestamp-authority\",timestamp:xJ.RFC3161Timestamp.parse(n.signedTimestamp)});return{signature:ANe(t,e),key:s8t(t),tlogEntries:r,timestamps:a}}function ANe(t,e){switch(t.content.$case){case\"dsseEnvelope\":return new r8t.DSSESignatureContent(t.content.dsseEnvelope);case\"messageSignature\":return new n8t.MessageSignatureContent(t.content.messageSignature,e)}}function s8t(t){switch(t.verificationMaterial.content.$case){case\"publicKey\":return{$case:\"public-key\",hint:t.verificationMaterial.content.publicKey.hint};case\"x509CertificateChain\":return{$case:\"certificate\",certificate:xJ.X509Certificate.parse(t.verificationMaterial.content.x509CertificateChain.certificates[0].rawBytes)};case\"certificate\":return{$case:\"certificate\",certificate:xJ.X509Certificate.parse(t.verificationMaterial.content.certificate.rawBytes)}}}});var Co=L(y1=>{\"use strict\";Object.defineProperty(y1,\"__esModule\",{value:!0});y1.PolicyError=y1.VerificationError=void 0;var EL=class extends Error{constructor({code:e,message:r,cause:s}){super(r),this.code=e,this.cause=s,this.name=this.constructor.name}},kJ=class extends EL{};y1.VerificationError=kJ;var QJ=class extends EL{};y1.PolicyError=QJ});var hNe=L(IL=>{\"use strict\";Object.defineProperty(IL,\"__esModule\",{value:!0});IL.filterCertAuthorities=o8t;IL.filterTLogAuthorities=a8t;function o8t(t,e){return t.filter(r=>r.validFor.start<=e.start&&r.validFor.end>=e.end)}function a8t(t,e){return t.filter(r=>e.logID&&!r.logID.equals(e.logID)?!1:r.validFor.start<=e.targetDate&&e.targetDate<=r.validFor.end)}});var Ay=L(fy=>{\"use strict\";Object.defineProperty(fy,\"__esModule\",{value:!0});fy.filterTLogAuthorities=fy.filterCertAuthorities=void 0;fy.toTrustMaterial=c8t;var TJ=wl(),tP=bb(),l8t=Co(),RJ=new Date(0),FJ=new Date(864e13),mNe=hNe();Object.defineProperty(fy,\"filterCertAuthorities\",{enumerable:!0,get:function(){return mNe.filterCertAuthorities}});Object.defineProperty(fy,\"filterTLogAuthorities\",{enumerable:!0,get:function(){return mNe.filterTLogAuthorities}});function c8t(t,e){let r=typeof e==\"function\"?e:u8t(e);return{certificateAuthorities:t.certificateAuthorities.map(dNe),timestampAuthorities:t.timestampAuthorities.map(dNe),tlogs:t.tlogs.map(gNe),ctlogs:t.ctlogs.map(gNe),publicKey:r}}function gNe(t){let e=t.publicKey.keyDetails,r=e===tP.PublicKeyDetails.PKCS1_RSA_PKCS1V5||e===tP.PublicKeyDetails.PKIX_RSA_PKCS1V5||e===tP.PublicKeyDetails.PKIX_RSA_PKCS1V15_2048_SHA256||e===tP.PublicKeyDetails.PKIX_RSA_PKCS1V15_3072_SHA256||e===tP.PublicKeyDetails.PKIX_RSA_PKCS1V15_4096_SHA256?\"pkcs1\":\"spki\";return{logID:t.logId.keyId,publicKey:TJ.crypto.createPublicKey(t.publicKey.rawBytes,r),validFor:{start:t.publicKey.validFor?.start||RJ,end:t.publicKey.validFor?.end||FJ}}}function dNe(t){return{certChain:t.certChain.certificates.map(e=>TJ.X509Certificate.parse(e.rawBytes)),validFor:{start:t.validFor?.start||RJ,end:t.validFor?.end||FJ}}}function u8t(t){return e=>{let r=(t||{})[e];if(!r)throw new l8t.VerificationError({code:\"PUBLIC_KEY_ERROR\",message:`key not found: ${e}`});return{publicKey:TJ.crypto.createPublicKey(r.rawBytes),validFor:s=>(r.validFor?.start||RJ)<=s&&(r.validFor?.end||FJ)>=s}}}});var NJ=L(rP=>{\"use strict\";Object.defineProperty(rP,\"__esModule\",{value:!0});rP.CertificateChainVerifier=void 0;rP.verifyCertificateChain=A8t;var py=Co(),f8t=Ay();function A8t(t,e){let r=(0,f8t.filterCertAuthorities)(e,{start:t.notBefore,end:t.notAfter}),s;for(let a of r)try{return new CL({trustedCerts:a.certChain,untrustedCert:t}).verify()}catch(n){s=n}throw new py.VerificationError({code:\"CERTIFICATE_ERROR\",message:\"Failed to verify certificate chain\",cause:s})}var CL=class{constructor(e){this.untrustedCert=e.untrustedCert,this.trustedCerts=e.trustedCerts,this.localCerts=p8t([...e.trustedCerts,e.untrustedCert])}verify(){let e=this.sort();return this.checkPath(e),e}sort(){let e=this.untrustedCert,r=this.buildPaths(e);if(r=r.filter(a=>a.some(n=>this.trustedCerts.includes(n))),r.length===0)throw new py.VerificationError({code:\"CERTIFICATE_ERROR\",message:\"no trusted certificate path found\"});let s=r.reduce((a,n)=>a.length<n.length?a:n);return[e,...s].slice(0,-1)}buildPaths(e){let r=[],s=this.findIssuer(e);if(s.length===0)throw new py.VerificationError({code:\"CERTIFICATE_ERROR\",message:\"no valid certificate path found\"});for(let a=0;a<s.length;a++){let n=s[a];if(n.equals(e)){r.push([e]);continue}let c=this.buildPaths(n);for(let f=0;f<c.length;f++)r.push([n,...c[f]])}return r}findIssuer(e){let r=[],s;return e.subject.equals(e.issuer)&&e.verify()?[e]:(e.extAuthorityKeyID&&(s=e.extAuthorityKeyID.keyIdentifier),this.localCerts.forEach(a=>{if(s&&a.extSubjectKeyID){a.extSubjectKeyID.keyIdentifier.equals(s)&&r.push(a);return}a.subject.equals(e.issuer)&&r.push(a)}),r=r.filter(a=>{try{return e.verify(a)}catch{return!1}}),r)}checkPath(e){if(e.length<1)throw new py.VerificationError({code:\"CERTIFICATE_ERROR\",message:\"certificate chain must contain at least one certificate\"});if(!e.slice(1).every(s=>s.isCA))throw new py.VerificationError({code:\"CERTIFICATE_ERROR\",message:\"intermediate certificate is not a CA\"});for(let s=e.length-2;s>=0;s--)if(!e[s].issuer.equals(e[s+1].subject))throw new py.VerificationError({code:\"CERTIFICATE_ERROR\",message:\"incorrect certificate name chaining\"});for(let s=0;s<e.length;s++){let a=e[s];if(a.extBasicConstraints?.isCA){let n=a.extBasicConstraints.pathLenConstraint;if(n!==void 0&&n<s-1)throw new py.VerificationError({code:\"CERTIFICATE_ERROR\",message:\"path length constraint exceeded\"})}}}};rP.CertificateChainVerifier=CL;function p8t(t){for(let e=0;e<t.length;e++)for(let r=e+1;r<t.length;r++)t[e].equals(t[r])&&(t.splice(r,1),r--);return t}});var yNe=L(OJ=>{\"use strict\";Object.defineProperty(OJ,\"__esModule\",{value:!0});OJ.verifySCTs=d8t;var wL=wl(),h8t=Co(),g8t=Ay();function d8t(t,e,r){let s,a=t.clone();for(let p=0;p<a.extensions.length;p++){let h=a.extensions[p];if(h.subs[0].toOID()===wL.EXTENSION_OID_SCT){s=new wL.X509SCTExtension(h),a.extensions.splice(p,1);break}}if(!s)return[];if(s.signedCertificateTimestamps.length===0)return[];let n=new wL.ByteStream,c=wL.crypto.digest(\"sha256\",e.publicKey);n.appendView(c);let f=a.tbsCertificate.toDER();return n.appendUint24(f.length),n.appendView(f),s.signedCertificateTimestamps.map(p=>{if(!(0,g8t.filterTLogAuthorities)(r,{logID:p.logID,targetDate:p.datetime}).some(C=>p.verify(n.buffer,C.publicKey)))throw new h8t.VerificationError({code:\"CERTIFICATE_ERROR\",message:\"SCT verification failed\"});return p.logID})}});var INe=L(BL=>{\"use strict\";Object.defineProperty(BL,\"__esModule\",{value:!0});BL.verifyPublicKey=w8t;BL.verifyCertificate=B8t;var m8t=wl(),ENe=Co(),y8t=NJ(),E8t=yNe(),I8t=\"1.3.6.1.4.1.57264.1.1\",C8t=\"1.3.6.1.4.1.57264.1.8\";function w8t(t,e,r){let s=r.publicKey(t);return e.forEach(a=>{if(!s.validFor(a))throw new ENe.VerificationError({code:\"PUBLIC_KEY_ERROR\",message:`Public key is not valid for timestamp: ${a.toISOString()}`})}),{key:s.publicKey}}function B8t(t,e,r){let s=(0,y8t.verifyCertificateChain)(t,r.certificateAuthorities);if(!e.every(n=>s.every(c=>c.validForDate(n))))throw new ENe.VerificationError({code:\"CERTIFICATE_ERROR\",message:\"certificate is not valid or expired at the specified date\"});return{scts:(0,E8t.verifySCTs)(s[0],s[1],r.ctlogs),signer:v8t(s[0])}}function v8t(t){let e,r=t.extension(C8t);r?e=r.valueObj.subs?.[0]?.value.toString(\"ascii\"):e=t.extension(I8t)?.value.toString(\"ascii\");let s={extensions:{issuer:e},subjectAlternativeName:t.subjectAltName};return{key:m8t.crypto.createPublicKey(t.publicKey),identity:s}}});var wNe=L(vL=>{\"use strict\";Object.defineProperty(vL,\"__esModule\",{value:!0});vL.verifySubjectAlternativeName=S8t;vL.verifyExtensions=D8t;var CNe=Co();function S8t(t,e){if(e===void 0||!e.match(t))throw new CNe.PolicyError({code:\"UNTRUSTED_SIGNER_ERROR\",message:`certificate identity error - expected ${t}, got ${e}`})}function D8t(t,e={}){let r;for(r in t)if(e[r]!==t[r])throw new CNe.PolicyError({code:\"UNTRUSTED_SIGNER_ERROR\",message:`invalid certificate extension - expected ${r}=${t[r]}, got ${r}=${e[r]}`})}});var BNe=L(HJ=>{\"use strict\";Object.defineProperty(HJ,\"__esModule\",{value:!0});HJ.verifyCheckpoint=x8t;var MJ=wl(),E1=Co(),b8t=Ay(),LJ=`\n\n`,P8t=/\\u2014 (\\S+) (\\S+)\\n/g;function x8t(t,e){let r=(0,b8t.filterTLogAuthorities)(e,{targetDate:new Date(Number(t.integratedTime)*1e3)}),s=t.inclusionProof,a=_J.fromString(s.checkpoint.envelope),n=UJ.fromString(a.note);if(!k8t(a,r))throw new E1.VerificationError({code:\"TLOG_INCLUSION_PROOF_ERROR\",message:\"invalid checkpoint signature\"});if(!MJ.crypto.bufferEqual(n.logHash,s.rootHash))throw new E1.VerificationError({code:\"TLOG_INCLUSION_PROOF_ERROR\",message:\"root hash mismatch\"})}function k8t(t,e){let r=Buffer.from(t.note,\"utf-8\");return t.signatures.every(s=>{let a=e.find(n=>MJ.crypto.bufferEqual(n.logID.subarray(0,4),s.keyHint));return a?MJ.crypto.verify(r,a.publicKey,s.signature):!1})}var _J=class t{constructor(e,r){this.note=e,this.signatures=r}static fromString(e){if(!e.includes(LJ))throw new E1.VerificationError({code:\"TLOG_INCLUSION_PROOF_ERROR\",message:\"missing checkpoint separator\"});let r=e.indexOf(LJ),s=e.slice(0,r+1),n=e.slice(r+LJ.length).matchAll(P8t),c=Array.from(n,f=>{let[,p,h]=f,E=Buffer.from(h,\"base64\");if(E.length<5)throw new E1.VerificationError({code:\"TLOG_INCLUSION_PROOF_ERROR\",message:\"malformed checkpoint signature\"});return{name:p,keyHint:E.subarray(0,4),signature:E.subarray(4)}});if(c.length===0)throw new E1.VerificationError({code:\"TLOG_INCLUSION_PROOF_ERROR\",message:\"no signatures found in checkpoint\"});return new t(s,c)}},UJ=class t{constructor(e,r,s,a){this.origin=e,this.logSize=r,this.logHash=s,this.rest=a}static fromString(e){let r=e.trimEnd().split(`\n`);if(r.length<3)throw new E1.VerificationError({code:\"TLOG_INCLUSION_PROOF_ERROR\",message:\"too few lines in checkpoint header\"});let s=r[0],a=BigInt(r[1]),n=Buffer.from(r[2],\"base64\"),c=r.slice(3);return new t(s,a,n,c)}}});var vNe=L(WJ=>{\"use strict\";Object.defineProperty(WJ,\"__esModule\",{value:!0});WJ.verifyMerkleInclusion=R8t;var GJ=wl(),jJ=Co(),Q8t=Buffer.from([0]),T8t=Buffer.from([1]);function R8t(t){let e=t.inclusionProof,r=BigInt(e.logIndex),s=BigInt(e.treeSize);if(r<0n||r>=s)throw new jJ.VerificationError({code:\"TLOG_INCLUSION_PROOF_ERROR\",message:`invalid index: ${r}`});let{inner:a,border:n}=F8t(r,s);if(e.hashes.length!==a+n)throw new jJ.VerificationError({code:\"TLOG_INCLUSION_PROOF_ERROR\",message:\"invalid hash count\"});let c=e.hashes.slice(0,a),f=e.hashes.slice(a),p=U8t(t.canonicalizedBody),h=O8t(N8t(p,c,r),f);if(!GJ.crypto.bufferEqual(h,e.rootHash))throw new jJ.VerificationError({code:\"TLOG_INCLUSION_PROOF_ERROR\",message:\"calculated root hash does not match inclusion proof\"})}function F8t(t,e){let r=L8t(t,e),s=M8t(t>>BigInt(r));return{inner:r,border:s}}function N8t(t,e,r){return e.reduce((s,a,n)=>r>>BigInt(n)&BigInt(1)?qJ(a,s):qJ(s,a),t)}function O8t(t,e){return e.reduce((r,s)=>qJ(s,r),t)}function L8t(t,e){return _8t(t^e-BigInt(1))}function M8t(t){return t.toString(2).split(\"1\").length-1}function _8t(t){return t===0n?0:t.toString(2).length}function qJ(t,e){return GJ.crypto.digest(\"sha256\",T8t,t,e)}function U8t(t){return GJ.crypto.digest(\"sha256\",Q8t,t)}});var DNe=L(YJ=>{\"use strict\";Object.defineProperty(YJ,\"__esModule\",{value:!0});YJ.verifyTLogSET=q8t;var SNe=wl(),H8t=Co(),j8t=Ay();function q8t(t,e){if(!(0,j8t.filterTLogAuthorities)(e,{logID:t.logId.keyId,targetDate:new Date(Number(t.integratedTime)*1e3)}).some(a=>{let n=G8t(t),c=Buffer.from(SNe.json.canonicalize(n),\"utf8\"),f=t.inclusionPromise.signedEntryTimestamp;return SNe.crypto.verify(c,a.publicKey,f)}))throw new H8t.VerificationError({code:\"TLOG_INCLUSION_PROMISE_ERROR\",message:\"inclusion promise could not be verified\"})}function G8t(t){let{integratedTime:e,logIndex:r,logId:s,canonicalizedBody:a}=t;return{body:a.toString(\"base64\"),integratedTime:Number(e),logIndex:Number(r),logID:s.keyId.toString(\"hex\")}}});var bNe=L(JJ=>{\"use strict\";Object.defineProperty(JJ,\"__esModule\",{value:!0});JJ.verifyRFC3161Timestamp=V8t;var VJ=wl(),KJ=Co(),W8t=NJ(),Y8t=Ay();function V8t(t,e,r){let s=t.signingTime;if(r=(0,Y8t.filterCertAuthorities)(r,{start:s,end:s}),r=J8t(r,{serialNumber:t.signerSerialNumber,issuer:t.signerIssuer}),!r.some(n=>{try{return K8t(t,e,n),!0}catch{return!1}}))throw new KJ.VerificationError({code:\"TIMESTAMP_ERROR\",message:\"timestamp could not be verified\"})}function K8t(t,e,r){let[s,...a]=r.certChain,n=VJ.crypto.createPublicKey(s.publicKey),c=t.signingTime;try{new W8t.CertificateChainVerifier({untrustedCert:s,trustedCerts:a}).verify()}catch{throw new KJ.VerificationError({code:\"TIMESTAMP_ERROR\",message:\"invalid certificate chain\"})}if(!r.certChain.every(p=>p.validForDate(c)))throw new KJ.VerificationError({code:\"TIMESTAMP_ERROR\",message:\"timestamp was signed with an expired certificate\"});t.verify(e,n)}function J8t(t,e){return t.filter(r=>r.certChain.length>0&&VJ.crypto.bufferEqual(r.certChain[0].serialNumber,e.serialNumber)&&VJ.crypto.bufferEqual(r.certChain[0].issuer,e.issuer))}});var PNe=L(SL=>{\"use strict\";Object.defineProperty(SL,\"__esModule\",{value:!0});SL.verifyTSATimestamp=tHt;SL.verifyTLogTimestamp=rHt;var z8t=Co(),Z8t=BNe(),X8t=vNe(),$8t=DNe(),eHt=bNe();function tHt(t,e,r){return(0,eHt.verifyRFC3161Timestamp)(t,e,r),{type:\"timestamp-authority\",logID:t.signerSerialNumber,timestamp:t.signingTime}}function rHt(t,e){let r=!1;if(nHt(t)&&((0,$8t.verifyTLogSET)(t,e),r=!0),iHt(t)&&((0,X8t.verifyMerkleInclusion)(t),(0,Z8t.verifyCheckpoint)(t,e),r=!0),!r)throw new z8t.VerificationError({code:\"TLOG_MISSING_INCLUSION_ERROR\",message:\"inclusion could not be verified\"});return{type:\"transparency-log\",logID:t.logId.keyId,timestamp:new Date(Number(t.integratedTime)*1e3)}}function nHt(t){return t.inclusionPromise!==void 0}function iHt(t){return t.inclusionProof!==void 0}});var xNe=L(zJ=>{\"use strict\";Object.defineProperty(zJ,\"__esModule\",{value:!0});zJ.verifyDSSETLogBody=sHt;var DL=Co();function sHt(t,e){switch(t.apiVersion){case\"0.0.1\":return oHt(t,e);default:throw new DL.VerificationError({code:\"TLOG_BODY_ERROR\",message:`unsupported dsse version: ${t.apiVersion}`})}}function oHt(t,e){if(t.spec.signatures?.length!==1)throw new DL.VerificationError({code:\"TLOG_BODY_ERROR\",message:\"signature count mismatch\"});let r=t.spec.signatures[0].signature;if(!e.compareSignature(Buffer.from(r,\"base64\")))throw new DL.VerificationError({code:\"TLOG_BODY_ERROR\",message:\"tlog entry signature mismatch\"});let s=t.spec.payloadHash?.value||\"\";if(!e.compareDigest(Buffer.from(s,\"hex\")))throw new DL.VerificationError({code:\"TLOG_BODY_ERROR\",message:\"DSSE payload hash mismatch\"})}});var kNe=L(XJ=>{\"use strict\";Object.defineProperty(XJ,\"__esModule\",{value:!0});XJ.verifyHashedRekordTLogBody=aHt;var ZJ=Co();function aHt(t,e){switch(t.apiVersion){case\"0.0.1\":return lHt(t,e);default:throw new ZJ.VerificationError({code:\"TLOG_BODY_ERROR\",message:`unsupported hashedrekord version: ${t.apiVersion}`})}}function lHt(t,e){let r=t.spec.signature.content||\"\";if(!e.compareSignature(Buffer.from(r,\"base64\")))throw new ZJ.VerificationError({code:\"TLOG_BODY_ERROR\",message:\"signature mismatch\"});let s=t.spec.data.hash?.value||\"\";if(!e.compareDigest(Buffer.from(s,\"hex\")))throw new ZJ.VerificationError({code:\"TLOG_BODY_ERROR\",message:\"digest mismatch\"})}});var QNe=L($J=>{\"use strict\";Object.defineProperty($J,\"__esModule\",{value:!0});$J.verifyIntotoTLogBody=cHt;var bL=Co();function cHt(t,e){switch(t.apiVersion){case\"0.0.2\":return uHt(t,e);default:throw new bL.VerificationError({code:\"TLOG_BODY_ERROR\",message:`unsupported intoto version: ${t.apiVersion}`})}}function uHt(t,e){if(t.spec.content.envelope.signatures?.length!==1)throw new bL.VerificationError({code:\"TLOG_BODY_ERROR\",message:\"signature count mismatch\"});let r=fHt(t.spec.content.envelope.signatures[0].sig);if(!e.compareSignature(Buffer.from(r,\"base64\")))throw new bL.VerificationError({code:\"TLOG_BODY_ERROR\",message:\"tlog entry signature mismatch\"});let s=t.spec.content.payloadHash?.value||\"\";if(!e.compareDigest(Buffer.from(s,\"hex\")))throw new bL.VerificationError({code:\"TLOG_BODY_ERROR\",message:\"DSSE payload hash mismatch\"})}function fHt(t){return Buffer.from(t,\"base64\").toString(\"utf-8\")}});var RNe=L(ez=>{\"use strict\";Object.defineProperty(ez,\"__esModule\",{value:!0});ez.verifyTLogBody=gHt;var TNe=Co(),AHt=xNe(),pHt=kNe(),hHt=QNe();function gHt(t,e){let{kind:r,version:s}=t.kindVersion,a=JSON.parse(t.canonicalizedBody.toString(\"utf8\"));if(r!==a.kind||s!==a.apiVersion)throw new TNe.VerificationError({code:\"TLOG_BODY_ERROR\",message:`kind/version mismatch - expected: ${r}/${s}, received: ${a.kind}/${a.apiVersion}`});switch(a.kind){case\"dsse\":return(0,AHt.verifyDSSETLogBody)(a,e);case\"intoto\":return(0,hHt.verifyIntotoTLogBody)(a,e);case\"hashedrekord\":return(0,pHt.verifyHashedRekordTLogBody)(a,e);default:throw new TNe.VerificationError({code:\"TLOG_BODY_ERROR\",message:`unsupported kind: ${r}`})}}});var MNe=L(PL=>{\"use strict\";Object.defineProperty(PL,\"__esModule\",{value:!0});PL.Verifier=void 0;var dHt=Ie(\"util\"),I1=Co(),FNe=INe(),NNe=wNe(),ONe=PNe(),mHt=RNe(),tz=class{constructor(e,r={}){this.trustMaterial=e,this.options={ctlogThreshold:r.ctlogThreshold??1,tlogThreshold:r.tlogThreshold??1,tsaThreshold:r.tsaThreshold??0}}verify(e,r){let s=this.verifyTimestamps(e),a=this.verifySigningKey(e,s);return this.verifyTLogs(e),this.verifySignature(e,a),r&&this.verifyPolicy(r,a.identity||{}),a}verifyTimestamps(e){let r=0,s=0,a=e.timestamps.map(n=>{switch(n.$case){case\"timestamp-authority\":return s++,(0,ONe.verifyTSATimestamp)(n.timestamp,e.signature.signature,this.trustMaterial.timestampAuthorities);case\"transparency-log\":return r++,(0,ONe.verifyTLogTimestamp)(n.tlogEntry,this.trustMaterial.tlogs)}});if(LNe(a))throw new I1.VerificationError({code:\"TIMESTAMP_ERROR\",message:\"duplicate timestamp\"});if(r<this.options.tlogThreshold)throw new I1.VerificationError({code:\"TIMESTAMP_ERROR\",message:`expected ${this.options.tlogThreshold} tlog timestamps, got ${r}`});if(s<this.options.tsaThreshold)throw new I1.VerificationError({code:\"TIMESTAMP_ERROR\",message:`expected ${this.options.tsaThreshold} tsa timestamps, got ${s}`});return a.map(n=>n.timestamp)}verifySigningKey({key:e},r){switch(e.$case){case\"public-key\":return(0,FNe.verifyPublicKey)(e.hint,r,this.trustMaterial);case\"certificate\":{let s=(0,FNe.verifyCertificate)(e.certificate,r,this.trustMaterial);if(LNe(s.scts))throw new I1.VerificationError({code:\"CERTIFICATE_ERROR\",message:\"duplicate SCT\"});if(s.scts.length<this.options.ctlogThreshold)throw new I1.VerificationError({code:\"CERTIFICATE_ERROR\",message:`expected ${this.options.ctlogThreshold} SCTs, got ${s.scts.length}`});return s.signer}}}verifyTLogs({signature:e,tlogEntries:r}){r.forEach(s=>(0,mHt.verifyTLogBody)(s,e))}verifySignature(e,r){if(!e.signature.verifySignature(r.key))throw new I1.VerificationError({code:\"SIGNATURE_ERROR\",message:\"signature verification failed\"})}verifyPolicy(e,r){e.subjectAlternativeName&&(0,NNe.verifySubjectAlternativeName)(e.subjectAlternativeName,r.subjectAlternativeName),e.extensions&&(0,NNe.verifyExtensions)(e.extensions,r.extensions)}};PL.Verifier=tz;function LNe(t){for(let e=0;e<t.length;e++)for(let r=e+1;r<t.length;r++)if((0,dHt.isDeepStrictEqual)(t[e],t[r]))return!0;return!1}});var xL=L(ou=>{\"use strict\";Object.defineProperty(ou,\"__esModule\",{value:!0});ou.Verifier=ou.toTrustMaterial=ou.VerificationError=ou.PolicyError=ou.toSignedEntity=void 0;var yHt=pNe();Object.defineProperty(ou,\"toSignedEntity\",{enumerable:!0,get:function(){return yHt.toSignedEntity}});var _Ne=Co();Object.defineProperty(ou,\"PolicyError\",{enumerable:!0,get:function(){return _Ne.PolicyError}});Object.defineProperty(ou,\"VerificationError\",{enumerable:!0,get:function(){return _Ne.VerificationError}});var EHt=Ay();Object.defineProperty(ou,\"toTrustMaterial\",{enumerable:!0,get:function(){return EHt.toTrustMaterial}});var IHt=MNe();Object.defineProperty(ou,\"Verifier\",{enumerable:!0,get:function(){return IHt.Verifier}})});var UNe=L(Na=>{\"use strict\";Object.defineProperty(Na,\"__esModule\",{value:!0});Na.DEFAULT_TIMEOUT=Na.DEFAULT_RETRY=void 0;Na.createBundleBuilder=BHt;Na.createKeyFinder=vHt;Na.createVerificationPolicy=SHt;var CHt=wl(),C1=pK(),wHt=xL();Na.DEFAULT_RETRY={retries:2};Na.DEFAULT_TIMEOUT=5e3;function BHt(t,e){let r={signer:DHt(e),witnesses:PHt(e)};switch(t){case\"messageSignature\":return new C1.MessageSignatureBundleBuilder(r);case\"dsseEnvelope\":return new C1.DSSEBundleBuilder({...r,certificateChain:e.legacyCompatibility})}}function vHt(t){return e=>{let r=t(e);if(!r)throw new wHt.VerificationError({code:\"PUBLIC_KEY_ERROR\",message:`key not found: ${e}`});return{publicKey:CHt.crypto.createPublicKey(r),validFor:()=>!0}}}function SHt(t){let e={},r=t.certificateIdentityEmail||t.certificateIdentityURI;return r&&(e.subjectAlternativeName=r),t.certificateIssuer&&(e.extensions={issuer:t.certificateIssuer}),e}function DHt(t){return new C1.FulcioSigner({fulcioBaseURL:t.fulcioURL,identityProvider:t.identityProvider||bHt(t),retry:t.retry??Na.DEFAULT_RETRY,timeout:t.timeout??Na.DEFAULT_TIMEOUT})}function bHt(t){let e=t.identityToken;return e?{getToken:()=>Promise.resolve(e)}:new C1.CIContextProvider(\"sigstore\")}function PHt(t){let e=[];return xHt(t)&&e.push(new C1.RekorWitness({rekorBaseURL:t.rekorURL,entryType:t.legacyCompatibility?\"intoto\":\"dsse\",fetchOnConflict:!1,retry:t.retry??Na.DEFAULT_RETRY,timeout:t.timeout??Na.DEFAULT_TIMEOUT})),kHt(t)&&e.push(new C1.TSAWitness({tsaBaseURL:t.tsaServerURL,retry:t.retry??Na.DEFAULT_RETRY,timeout:t.timeout??Na.DEFAULT_TIMEOUT})),e}function xHt(t){return t.tlogUpload!==!1}function kHt(t){return t.tsaServerURL!==void 0}});var qNe=L(au=>{\"use strict\";var QHt=au&&au.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||(\"get\"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),THt=au&&au.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,\"default\",{enumerable:!0,value:e})}:function(t,e){t.default=e}),HNe=au&&au.__importStar||function(){var t=function(e){return t=Object.getOwnPropertyNames||function(r){var s=[];for(var a in r)Object.prototype.hasOwnProperty.call(r,a)&&(s[s.length]=a);return s},t(e)};return function(e){if(e&&e.__esModule)return e;var r={};if(e!=null)for(var s=t(e),a=0;a<s.length;a++)s[a]!==\"default\"&&QHt(r,e,s[a]);return THt(r,e),r}}();Object.defineProperty(au,\"__esModule\",{value:!0});au.sign=FHt;au.attest=NHt;au.verify=OHt;au.createVerifier=jNe;var nz=xb(),RHt=HNe(gL()),rz=xL(),w1=HNe(UNe());async function FHt(t,e={}){let s=await w1.createBundleBuilder(\"messageSignature\",e).create({data:t});return(0,nz.bundleToJSON)(s)}async function NHt(t,e,r={}){let a=await w1.createBundleBuilder(\"dsseEnvelope\",r).create({data:t,type:e});return(0,nz.bundleToJSON)(a)}async function OHt(t,e,r){let s;return Buffer.isBuffer(e)?s=e:r=e,jNe(r).then(a=>a.verify(t,s))}async function jNe(t={}){let e=await RHt.getTrustedRoot({mirrorURL:t.tufMirrorURL,rootPath:t.tufRootPath,cachePath:t.tufCachePath,forceCache:t.tufForceCache,retry:t.retry??w1.DEFAULT_RETRY,timeout:t.timeout??w1.DEFAULT_TIMEOUT}),r=t.keySelector?w1.createKeyFinder(t.keySelector):void 0,s=(0,rz.toTrustMaterial)(e,r),a={ctlogThreshold:t.ctLogThreshold,tlogThreshold:t.tlogThreshold},n=new rz.Verifier(s,a),c=w1.createVerificationPolicy(t);return{verify:(f,p)=>{let h=(0,nz.bundleFromJSON)(f),E=(0,rz.toSignedEntity)(h,p);n.verify(E,c)}}}});var WNe=L(Oi=>{\"use strict\";Object.defineProperty(Oi,\"__esModule\",{value:!0});Oi.verify=Oi.sign=Oi.createVerifier=Oi.attest=Oi.VerificationError=Oi.PolicyError=Oi.TUFError=Oi.InternalError=Oi.DEFAULT_REKOR_URL=Oi.DEFAULT_FULCIO_URL=Oi.ValidationError=void 0;var LHt=xb();Object.defineProperty(Oi,\"ValidationError\",{enumerable:!0,get:function(){return LHt.ValidationError}});var iz=pK();Object.defineProperty(Oi,\"DEFAULT_FULCIO_URL\",{enumerable:!0,get:function(){return iz.DEFAULT_FULCIO_URL}});Object.defineProperty(Oi,\"DEFAULT_REKOR_URL\",{enumerable:!0,get:function(){return iz.DEFAULT_REKOR_URL}});Object.defineProperty(Oi,\"InternalError\",{enumerable:!0,get:function(){return iz.InternalError}});var MHt=gL();Object.defineProperty(Oi,\"TUFError\",{enumerable:!0,get:function(){return MHt.TUFError}});var GNe=xL();Object.defineProperty(Oi,\"PolicyError\",{enumerable:!0,get:function(){return GNe.PolicyError}});Object.defineProperty(Oi,\"VerificationError\",{enumerable:!0,get:function(){return GNe.VerificationError}});var kL=qNe();Object.defineProperty(Oi,\"attest\",{enumerable:!0,get:function(){return kL.attest}});Object.defineProperty(Oi,\"createVerifier\",{enumerable:!0,get:function(){return kL.createVerifier}});Object.defineProperty(Oi,\"sign\",{enumerable:!0,get:function(){return kL.sign}});Object.defineProperty(Oi,\"verify\",{enumerable:!0,get:function(){return kL.verify}})});var IOe=L((Fvr,EOe)=>{var Kjt=Y4();function Jjt(t){return Kjt(t)?void 0:t}EOe.exports=Jjt});var wOe=L((Nvr,COe)=>{var zjt=QT(),Zjt=w5(),Xjt=D5(),$jt=Im(),e6t=Vd(),t6t=IOe(),r6t=dG(),n6t=C5(),i6t=1,s6t=2,o6t=4,a6t=r6t(function(t,e){var r={};if(t==null)return r;var s=!1;e=zjt(e,function(n){return n=$jt(n,t),s||(s=n.length>1),n}),e6t(t,n6t(t),r),s&&(r=Zjt(r,i6t|s6t|o6t,t6t));for(var a=e.length;a--;)Xjt(r,e[a]);return r});COe.exports=a6t});bt();Ve();bt();var bOe=Ie(\"child_process\"),POe=et(Rd());Wt();var $I=new Map([]);var $v={};Vt($v,{BaseCommand:()=>ut,WorkspaceRequiredError:()=>ar,getCli:()=>XCe,getDynamicLibs:()=>ZCe,getPluginConfiguration:()=>tC,openWorkspace:()=>eC,pluginCommands:()=>$I,runExit:()=>KR});Wt();var ut=class extends ot{constructor(){super(...arguments);this.cwd=ge.String(\"--cwd\",{hidden:!0})}validateAndExecute(){if(typeof this.cwd<\"u\")throw new nt(\"The --cwd option is ambiguous when used anywhere else than the very first parameter provided in the command line, before even the command path\");return super.validateAndExecute()}};Ve();bt();Wt();var ar=class extends nt{constructor(e,r){let s=K.relative(e,r),a=K.join(e,Ht.fileName);super(`This command can only be run from within a workspace of your project (${s} isn't a workspace of ${a}).`)}};Ve();bt();rA();Bc();wv();Wt();var mwt=et(Ai());Ul();var ZCe=()=>new Map([[\"@yarnpkg/cli\",$v],[\"@yarnpkg/core\",Xv],[\"@yarnpkg/fslib\",U2],[\"@yarnpkg/libzip\",Iv],[\"@yarnpkg/parsers\",K2],[\"@yarnpkg/shell\",Dv],[\"clipanion\",oB],[\"semver\",mwt],[\"typanion\",Ia]]);Ve();async function eC(t,e){let{project:r,workspace:s}=await Tt.find(t,e);if(!s)throw new ar(r.cwd,e);return s}Ve();bt();rA();Bc();wv();Wt();var d6t=et(Ai());Ul();var Y5={};Vt(Y5,{AddCommand:()=>sC,BinCommand:()=>oC,CacheCleanCommand:()=>aC,ClipanionCommand:()=>pC,ConfigCommand:()=>fC,ConfigGetCommand:()=>lC,ConfigSetCommand:()=>cC,ConfigUnsetCommand:()=>uC,DedupeCommand:()=>AC,EntryCommand:()=>gC,ExecCommand:()=>mC,ExplainCommand:()=>IC,ExplainPeerRequirementsCommand:()=>yC,HelpCommand:()=>hC,InfoCommand:()=>CC,LinkCommand:()=>BC,NodeCommand:()=>vC,PluginCheckCommand:()=>SC,PluginImportCommand:()=>PC,PluginImportSourcesCommand:()=>xC,PluginListCommand:()=>DC,PluginRemoveCommand:()=>kC,PluginRuntimeCommand:()=>QC,RebuildCommand:()=>TC,RemoveCommand:()=>RC,RunCommand:()=>NC,RunIndexCommand:()=>FC,SetResolutionCommand:()=>OC,SetVersionCommand:()=>EC,SetVersionSourcesCommand:()=>bC,UnlinkCommand:()=>LC,UpCommand:()=>MC,VersionCommand:()=>dC,WhyCommand:()=>_C,WorkspaceCommand:()=>GC,WorkspacesListCommand:()=>qC,YarnCommand:()=>wC,dedupeUtils:()=>iF,default:()=>ASt,suggestUtils:()=>Xu});var xBe=et(Rd());Ve();Ve();Ve();Wt();var _1e=et(nS());Ul();var Xu={};Vt(Xu,{Modifier:()=>d5,Strategy:()=>tF,Target:()=>iS,WorkspaceModifier:()=>F1e,applyModifier:()=>L1t,extractDescriptorFromPath:()=>m5,extractRangeModifier:()=>N1e,fetchDescriptorFrom:()=>y5,findProjectDescriptors:()=>M1e,getModifier:()=>sS,getSuggestedDescriptors:()=>oS,makeWorkspaceDescriptor:()=>L1e,toWorkspaceModifier:()=>O1e});Ve();Ve();bt();var g5=et(Ai()),N1t=\"workspace:\",iS=(s=>(s.REGULAR=\"dependencies\",s.DEVELOPMENT=\"devDependencies\",s.PEER=\"peerDependencies\",s))(iS||{}),d5=(s=>(s.CARET=\"^\",s.TILDE=\"~\",s.EXACT=\"\",s))(d5||{}),F1e=(s=>(s.CARET=\"^\",s.TILDE=\"~\",s.EXACT=\"*\",s))(F1e||{}),tF=(n=>(n.KEEP=\"keep\",n.REUSE=\"reuse\",n.PROJECT=\"project\",n.LATEST=\"latest\",n.CACHE=\"cache\",n))(tF||{});function sS(t,e){return t.exact?\"\":t.caret?\"^\":t.tilde?\"~\":e.configuration.get(\"defaultSemverRangePrefix\")}var O1t=/^([\\^~]?)[0-9]+(?:\\.[0-9]+){0,2}(?:-\\S+)?$/;function N1e(t,{project:e}){let r=t.match(O1t);return r?r[1]:e.configuration.get(\"defaultSemverRangePrefix\")}function L1t(t,e){let{protocol:r,source:s,params:a,selector:n}=q.parseRange(t.range);return g5.default.valid(n)&&(n=`${e}${t.range}`),q.makeDescriptor(t,q.makeRange({protocol:r,source:s,params:a,selector:n}))}function O1e(t){switch(t){case\"^\":return\"^\";case\"~\":return\"~\";case\"\":return\"*\";default:throw new Error(`Assertion failed: Unknown modifier: \"${t}\"`)}}function L1e(t,e){return q.makeDescriptor(t.anchoredDescriptor,`${N1t}${O1e(e)}`)}async function M1e(t,{project:e,target:r}){let s=new Map,a=n=>{let c=s.get(n.descriptorHash);return c||s.set(n.descriptorHash,c={descriptor:n,locators:[]}),c};for(let n of e.workspaces)if(r===\"peerDependencies\"){let c=n.manifest.peerDependencies.get(t.identHash);c!==void 0&&a(c).locators.push(n.anchoredLocator)}else{let c=n.manifest.dependencies.get(t.identHash),f=n.manifest.devDependencies.get(t.identHash);r===\"devDependencies\"?f!==void 0?a(f).locators.push(n.anchoredLocator):c!==void 0&&a(c).locators.push(n.anchoredLocator):c!==void 0?a(c).locators.push(n.anchoredLocator):f!==void 0&&a(f).locators.push(n.anchoredLocator)}return s}async function m5(t,{cwd:e,workspace:r}){return await _1t(async s=>{K.isAbsolute(t)||(t=K.relative(r.cwd,K.resolve(e,t)),t.match(/^\\.{0,2}\\//)||(t=`./${t}`));let{project:a}=r,n=await y5(q.makeIdent(null,\"archive\"),t,{project:r.project,cache:s,workspace:r});if(!n)throw new Error(\"Assertion failed: The descriptor should have been found\");let c=new Yi,f=a.configuration.makeResolver(),p=a.configuration.makeFetcher(),h={checksums:a.storedChecksums,project:a,cache:s,fetcher:p,report:c,resolver:f},E=f.bindDescriptor(n,r.anchoredLocator,h),C=q.convertDescriptorToLocator(E),S=await p.fetch(C,h),P=await Ht.find(S.prefixPath,{baseFs:S.packageFs});if(!P.name)throw new Error(\"Target path doesn't have a name\");return q.makeDescriptor(P.name,t)})}function M1t(t){if(t.range===\"unknown\")return{type:\"resolve\",range:\"latest\"};if(Or.validRange(t.range))return{type:\"fixed\",range:t.range};if(Up.test(t.range))return{type:\"resolve\",range:t.range};let e=t.range.match(/^(?:jsr:|npm:)(.*)/);if(!e)return{type:\"fixed\",range:t.range};let[,r]=e,s=`${q.stringifyIdent(t)}@`;return r.startsWith(s)&&(r=r.slice(s.length)),Or.validRange(r)?{type:\"fixed\",range:t.range}:Up.test(r)?{type:\"resolve\",range:t.range}:{type:\"fixed\",range:t.range}}async function oS(t,{project:e,workspace:r,cache:s,target:a,fixed:n,modifier:c,strategies:f,maxResults:p=1/0}){if(!(p>=0))throw new Error(`Invalid maxResults (${p})`);let h=!n||t.range===\"unknown\"?M1t(t):{type:\"fixed\",range:t.range};if(h.type===\"fixed\")return{suggestions:[{descriptor:t,name:`Use ${q.prettyDescriptor(e.configuration,t)}`,reason:\"(unambiguous explicit request)\"}],rejections:[]};let E=typeof r<\"u\"&&r!==null&&r.manifest[a].get(t.identHash)||null,C=[],S=[],P=async I=>{try{await I()}catch(R){S.push(R)}};for(let I of f){if(C.length>=p)break;switch(I){case\"keep\":await P(async()=>{E&&C.push({descriptor:E,name:`Keep ${q.prettyDescriptor(e.configuration,E)}`,reason:\"(no changes)\"})});break;case\"reuse\":await P(async()=>{for(let{descriptor:R,locators:N}of(await M1e(t,{project:e,target:a})).values()){if(N.length===1&&N[0].locatorHash===r.anchoredLocator.locatorHash&&f.includes(\"keep\"))continue;let U=`(originally used by ${q.prettyLocator(e.configuration,N[0])}`;U+=N.length>1?` and ${N.length-1} other${N.length>2?\"s\":\"\"})`:\")\",C.push({descriptor:R,name:`Reuse ${q.prettyDescriptor(e.configuration,R)}`,reason:U})}});break;case\"cache\":await P(async()=>{for(let R of e.storedDescriptors.values())R.identHash===t.identHash&&C.push({descriptor:R,name:`Reuse ${q.prettyDescriptor(e.configuration,R)}`,reason:\"(already used somewhere in the lockfile)\"})});break;case\"project\":await P(async()=>{if(r.manifest.name!==null&&t.identHash===r.manifest.name.identHash)return;let R=e.tryWorkspaceByIdent(t);if(R===null)return;let N=L1e(R,c);C.push({descriptor:N,name:`Attach ${q.prettyDescriptor(e.configuration,N)}`,reason:`(local workspace at ${he.pretty(e.configuration,R.relativeCwd,he.Type.PATH)})`})});break;case\"latest\":{let R=e.configuration.get(\"enableNetwork\"),N=e.configuration.get(\"enableOfflineMode\");await P(async()=>{if(a===\"peerDependencies\")C.push({descriptor:q.makeDescriptor(t,\"*\"),name:\"Use *\",reason:\"(catch-all peer dependency pattern)\"});else if(!R&&!N)C.push({descriptor:null,name:\"Resolve from latest\",reason:he.pretty(e.configuration,\"(unavailable because enableNetwork is toggled off)\",\"grey\")});else{let U=await y5(t,h.range,{project:e,cache:s,workspace:r,modifier:c});U&&C.push({descriptor:U,name:`Use ${q.prettyDescriptor(e.configuration,U)}`,reason:`(resolved from ${N?\"the cache\":\"latest\"})`})}})}break}}return{suggestions:C.slice(0,p),rejections:S.slice(0,p)}}async function y5(t,e,{project:r,cache:s,workspace:a,preserveModifier:n=!0,modifier:c}){let f=r.configuration.normalizeDependency(q.makeDescriptor(t,e)),p=new Yi,h=r.configuration.makeFetcher(),E=r.configuration.makeResolver(),C={project:r,fetcher:h,cache:s,checksums:r.storedChecksums,report:p,cacheOptions:{skipIntegrityCheck:!0}},S={...C,resolver:E,fetchOptions:C},P=E.bindDescriptor(f,a.anchoredLocator,S),I=await E.getCandidates(P,{},S);if(I.length===0)return null;let R=I[0],{protocol:N,source:U,params:W,selector:te}=q.parseRange(q.convertToManifestRange(R.reference));if(N===r.configuration.get(\"defaultProtocol\")&&(N=null),g5.default.valid(te)){let ie=te;if(typeof c<\"u\")te=c+te;else if(n!==!1){let me=typeof n==\"string\"?n:f.range;te=N1e(me,{project:r})+te}let Ae=q.makeDescriptor(R,q.makeRange({protocol:N,source:U,params:W,selector:te}));(await E.getCandidates(r.configuration.normalizeDependency(Ae),{},S)).length!==1&&(te=ie)}return q.makeDescriptor(R,q.makeRange({protocol:N,source:U,params:W,selector:te}))}async function _1t(t){return await le.mktempPromise(async e=>{let r=ze.create(e);return r.useWithSource(e,{enableMirror:!1,compressionLevel:0},e,{overwrite:!0}),await t(new Jr(e,{configuration:r,check:!1,immutable:!1}))})}var sC=class extends ut{constructor(){super(...arguments);this.json=ge.Boolean(\"--json\",!1,{description:\"Format the output as an NDJSON stream\"});this.fixed=ge.Boolean(\"-F,--fixed\",!1,{description:\"Store dependency tags as-is instead of resolving them\"});this.exact=ge.Boolean(\"-E,--exact\",!1,{description:\"Don't use any semver modifier on the resolved range\"});this.tilde=ge.Boolean(\"-T,--tilde\",!1,{description:\"Use the `~` semver modifier on the resolved range\"});this.caret=ge.Boolean(\"-C,--caret\",!1,{description:\"Use the `^` semver modifier on the resolved range\"});this.dev=ge.Boolean(\"-D,--dev\",!1,{description:\"Add a package as a dev dependency\"});this.peer=ge.Boolean(\"-P,--peer\",!1,{description:\"Add a package as a peer dependency\"});this.optional=ge.Boolean(\"-O,--optional\",!1,{description:\"Add / upgrade a package to an optional regular / peer dependency\"});this.preferDev=ge.Boolean(\"--prefer-dev\",!1,{description:\"Add / upgrade a package to a dev dependency\"});this.interactive=ge.Boolean(\"-i,--interactive\",{description:\"Reuse the specified package from other workspaces in the project\"});this.cached=ge.Boolean(\"--cached\",!1,{description:\"Reuse the highest version already used somewhere within the project\"});this.mode=ge.String(\"--mode\",{description:\"Change what artifacts installs generate\",validator:po(ec)});this.silent=ge.Boolean(\"--silent\",{hidden:!0});this.packages=ge.Rest()}static{this.paths=[[\"add\"]]}static{this.usage=ot.Usage({description:\"add dependencies to the project\",details:\"\\n      This command adds a package to the package.json for the nearest workspace.\\n\\n      - If it didn't exist before, the package will by default be added to the regular `dependencies` field, but this behavior can be overriden thanks to the `-D,--dev` flag (which will cause the dependency to be added to the `devDependencies` field instead) and the `-P,--peer` flag (which will do the same but for `peerDependencies`).\\n\\n      - If the package was already listed in your dependencies, it will by default be upgraded whether it's part of your `dependencies` or `devDependencies` (it won't ever update `peerDependencies`, though).\\n\\n      - If set, the `--prefer-dev` flag will operate as a more flexible `-D,--dev` in that it will add the package to your `devDependencies` if it isn't already listed in either `dependencies` or `devDependencies`, but it will also happily upgrade your `dependencies` if that's what you already use (whereas `-D,--dev` would throw an exception).\\n\\n      - If set, the `-O,--optional` flag will add the package to the `optionalDependencies` field and, in combination with the `-P,--peer` flag, it will add the package as an optional peer dependency. If the package was already listed in your `dependencies`, it will be upgraded to `optionalDependencies`. If the package was already listed in your `peerDependencies`, in combination with the `-P,--peer` flag, it will be upgraded to an optional peer dependency: `\\\"peerDependenciesMeta\\\": { \\\"<package>\\\": { \\\"optional\\\": true } }`\\n\\n      - If the added package doesn't specify a range at all its `latest` tag will be resolved and the returned version will be used to generate a new semver range (using the `^` modifier by default unless otherwise configured via the `defaultSemverRangePrefix` configuration, or the `~` modifier if `-T,--tilde` is specified, or no modifier at all if `-E,--exact` is specified). Two exceptions to this rule: the first one is that if the package is a workspace then its local version will be used, and the second one is that if you use `-P,--peer` the default range will be `*` and won't be resolved at all.\\n\\n      - If the added package specifies a range (such as `^1.0.0`, `latest`, or `rc`), Yarn will add this range as-is in the resulting package.json entry (in particular, tags such as `rc` will be encoded as-is rather than being converted into a semver range).\\n\\n      If the `--cached` option is used, Yarn will preferably reuse the highest version already used somewhere within the project, even if through a transitive dependency.\\n\\n      If the `-i,--interactive` option is used (or if the `preferInteractive` settings is toggled on) the command will first try to check whether other workspaces in the project use the specified package and, if so, will offer to reuse them.\\n\\n      If the `--mode=<mode>` option is set, Yarn will change which artifacts are generated. The modes currently supported are:\\n\\n      - `skip-build` will not run the build scripts at all. Note that this is different from setting `enableScripts` to false because the latter will disable build scripts, and thus affect the content of the artifacts generated on disk, whereas the former will just disable the build step - but not the scripts themselves, which just won't run.\\n\\n      - `update-lockfile` will skip the link step altogether, and only fetch packages that are missing from the lockfile (or that have no associated checksums). This mode is typically used by tools like Renovate or Dependabot to keep a lockfile up-to-date without incurring the full install cost.\\n\\n      For a compilation of all the supported protocols, please consult the dedicated page from our website: https://yarnpkg.com/protocols.\\n    \",examples:[[\"Add a regular package to the current workspace\",\"$0 add lodash\"],[\"Add a specific version for a package to the current workspace\",\"$0 add lodash@1.2.3\"],[\"Add a package from a GitHub repository (the master branch) to the current workspace using a URL\",\"$0 add lodash@https://github.com/lodash/lodash\"],[\"Add a package from a GitHub repository (the master branch) to the current workspace using the GitHub protocol\",\"$0 add lodash@github:lodash/lodash\"],[\"Add a package from a GitHub repository (the master branch) to the current workspace using the GitHub protocol (shorthand)\",\"$0 add lodash@lodash/lodash\"],[\"Add a package from a specific branch of a GitHub repository to the current workspace using the GitHub protocol (shorthand)\",\"$0 add lodash-es@lodash/lodash#es\"],[\"Add a local package (gzipped tarball format) to the current workspace\",\"$0 add local-package-name@file:../path/to/local-package-name-v0.1.2.tgz\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Jr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState({restoreResolutions:!1});let c=this.fixed,f=r.isInteractive({interactive:this.interactive,stdout:this.context.stdout}),p=f||r.get(\"preferReuse\"),h=sS(this,s),E=[p?\"reuse\":void 0,\"project\",this.cached?\"cache\":void 0,\"latest\"].filter(W=>typeof W<\"u\"),C=f?1/0:1,S=W=>{let te=q.tryParseDescriptor(W.slice(4));return te?te.range===\"unknown\"?q.makeDescriptor(te,`jsr:${q.stringifyIdent(te)}@latest`):q.makeDescriptor(te,`jsr:${te.range}`):null},P=await Promise.all(this.packages.map(async W=>{let te=W.match(/^\\.{0,2}\\//)?await m5(W,{cwd:this.context.cwd,workspace:a}):W.startsWith(\"jsr:\")?S(W):q.tryParseDescriptor(W),ie=W.match(/^(https?:|git@github)/);if(ie)throw new nt(`It seems you are trying to add a package using a ${he.pretty(r,`${ie[0]}...`,he.Type.RANGE)} url; we now require package names to be explicitly specified.\nTry running the command again with the package name prefixed: ${he.pretty(r,\"yarn add\",he.Type.CODE)} ${he.pretty(r,q.makeDescriptor(q.makeIdent(null,\"my-package\"),`${ie[0]}...`),he.Type.DESCRIPTOR)}`);if(!te)throw new nt(`The ${he.pretty(r,W,he.Type.CODE)} string didn't match the required format (package-name@range). Did you perhaps forget to explicitly reference the package name?`);let Ae=U1t(a,te,{dev:this.dev,peer:this.peer,preferDev:this.preferDev,optional:this.optional});return await Promise.all(Ae.map(async me=>{let pe=await oS(te,{project:s,workspace:a,cache:n,fixed:c,target:me,modifier:h,strategies:E,maxResults:C});return{request:te,suggestedDescriptors:pe,target:me}}))})).then(W=>W.flat()),I=await uA.start({configuration:r,stdout:this.context.stdout,suggestInstall:!1},async W=>{for(let{request:te,suggestedDescriptors:{suggestions:ie,rejections:Ae}}of P)if(ie.filter(me=>me.descriptor!==null).length===0){let[me]=Ae;if(typeof me>\"u\")throw new Error(\"Assertion failed: Expected an error to have been set\");s.configuration.get(\"enableNetwork\")?W.reportError(27,`${q.prettyDescriptor(r,te)} can't be resolved to a satisfying range`):W.reportError(27,`${q.prettyDescriptor(r,te)} can't be resolved to a satisfying range (note: network resolution has been disabled)`),W.reportSeparator(),W.reportExceptionOnce(me)}});if(I.hasErrors())return I.exitCode();let R=!1,N=[],U=[];for(let{suggestedDescriptors:{suggestions:W},target:te}of P){let ie,Ae=W.filter(Be=>Be.descriptor!==null),ce=Ae[0].descriptor,me=Ae.every(Be=>q.areDescriptorsEqual(Be.descriptor,ce));Ae.length===1||me?ie=ce:(R=!0,{answer:ie}=await(0,_1e.prompt)({type:\"select\",name:\"answer\",message:\"Which range do you want to use?\",choices:W.map(({descriptor:Be,name:Ce,reason:g})=>Be?{name:Ce,hint:g,descriptor:Be}:{name:Ce,hint:g,disabled:!0}),onCancel:()=>process.exit(130),result(Be){return this.find(Be,\"descriptor\")},stdin:this.context.stdin,stdout:this.context.stdout}));let pe=a.manifest[te].get(ie.identHash);(typeof pe>\"u\"||pe.descriptorHash!==ie.descriptorHash)&&(a.manifest[te].set(ie.identHash,ie),this.optional&&(te===\"dependencies\"?a.manifest.ensureDependencyMeta({...ie,range:\"unknown\"}).optional=!0:te===\"peerDependencies\"&&(a.manifest.ensurePeerDependencyMeta({...ie,range:\"unknown\"}).optional=!0)),typeof pe>\"u\"?N.push([a,te,ie,E]):U.push([a,te,pe,ie]))}return await r.triggerMultipleHooks(W=>W.afterWorkspaceDependencyAddition,N),await r.triggerMultipleHooks(W=>W.afterWorkspaceDependencyReplacement,U),R&&this.context.stdout.write(`\n`),await s.installWithNewReport({json:this.json,stdout:this.context.stdout,quiet:this.context.quiet},{cache:n,mode:this.mode})}};function U1t(t,e,{dev:r,peer:s,preferDev:a,optional:n}){let c=t.manifest.dependencies.has(e.identHash),f=t.manifest.devDependencies.has(e.identHash),p=t.manifest.peerDependencies.has(e.identHash);if((r||s)&&c)throw new nt(`Package \"${q.prettyIdent(t.project.configuration,e)}\" is already listed as a regular dependency - remove the -D,-P flags or remove it from your dependencies first`);if(!r&&!s&&p)throw new nt(`Package \"${q.prettyIdent(t.project.configuration,e)}\" is already listed as a peer dependency - use either of -D or -P, or remove it from your peer dependencies first`);if(n&&f)throw new nt(`Package \"${q.prettyIdent(t.project.configuration,e)}\" is already listed as a dev dependency - remove the -O flag or remove it from your dev dependencies first`);if(n&&!s&&p)throw new nt(`Package \"${q.prettyIdent(t.project.configuration,e)}\" is already listed as a peer dependency - remove the -O flag or add the -P flag or remove it from your peer dependencies first`);if((r||a)&&n)throw new nt(`Package \"${q.prettyIdent(t.project.configuration,e)}\" cannot simultaneously be a dev dependency and an optional dependency`);let h=[];return s&&h.push(\"peerDependencies\"),(r||a)&&h.push(\"devDependencies\"),n&&h.push(\"dependencies\"),h.length>0?h:f?[\"devDependencies\"]:p?[\"peerDependencies\"]:[\"dependencies\"]}Ve();Ve();Wt();var oC=class extends ut{constructor(){super(...arguments);this.verbose=ge.Boolean(\"-v,--verbose\",!1,{description:\"Print both the binary name and the locator of the package that provides the binary\"});this.json=ge.Boolean(\"--json\",!1,{description:\"Format the output as an NDJSON stream\"});this.name=ge.String({required:!1})}static{this.paths=[[\"bin\"]]}static{this.usage=ot.Usage({description:\"get the path to a binary script\",details:`\n      When used without arguments, this command will print the list of all the binaries available in the current workspace. Adding the \\`-v,--verbose\\` flag will cause the output to contain both the binary name and the locator of the package that provides the binary.\n\n      When an argument is specified, this command will just print the path to the binary on the standard output and exit. Note that the reported path may be stored within a zip archive.\n    `,examples:[[\"List all the available binaries\",\"$0 bin\"],[\"Print the path to a specific binary\",\"$0 bin eslint\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,locator:a}=await Tt.find(r,this.context.cwd);if(await s.restoreInstallState(),this.name){let f=(await In.getPackageAccessibleBinaries(a,{project:s})).get(this.name);if(!f)throw new nt(`Couldn't find a binary named \"${this.name}\" for package \"${q.prettyLocator(r,a)}\"`);let[,p]=f;return this.context.stdout.write(`${p}\n`),0}return(await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout},async c=>{let f=await In.getPackageAccessibleBinaries(a,{project:s}),h=Array.from(f.keys()).reduce((E,C)=>Math.max(E,C.length),0);for(let[E,[C,S]]of f)c.reportJson({name:E,source:q.stringifyIdent(C),path:S});if(this.verbose)for(let[E,[C]]of f)c.reportInfo(null,`${E.padEnd(h,\" \")}   ${q.prettyLocator(r,C)}`);else for(let E of f.keys())c.reportInfo(null,E)})).exitCode()}};Ve();bt();Wt();var aC=class extends ut{constructor(){super(...arguments);this.mirror=ge.Boolean(\"--mirror\",!1,{description:\"Remove the global cache files instead of the local cache files\"});this.all=ge.Boolean(\"--all\",!1,{description:\"Remove both the global cache files and the local cache files of the current project\"})}static{this.paths=[[\"cache\",\"clean\"],[\"cache\",\"clear\"]]}static{this.usage=ot.Usage({description:\"remove the shared cache files\",details:`\n      This command will remove all the files from the cache.\n    `,examples:[[\"Remove all the local archives\",\"$0 cache clean\"],[\"Remove all the archives stored in the ~/.yarn directory\",\"$0 cache clean --mirror\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins);if(!r.get(\"enableCacheClean\"))throw new nt(\"Cache cleaning is currently disabled. To enable it, set `enableCacheClean: true` in your configuration file. Note: Cache cleaning is typically not required and should be avoided when using Zero-Installs.\");let s=await Jr.find(r);return(await Ot.start({configuration:r,stdout:this.context.stdout},async()=>{let n=(this.all||this.mirror)&&s.mirrorCwd!==null,c=!this.mirror;n&&(await le.removePromise(s.mirrorCwd),await r.triggerHook(f=>f.cleanGlobalArtifacts,r)),c&&await le.removePromise(s.cwd)})).exitCode()}};Ve();Wt();var H1e=et(aS()),E5=Ie(\"util\"),lC=class extends ut{constructor(){super(...arguments);this.why=ge.Boolean(\"--why\",!1,{description:\"Print the explanation for why a setting has its value\"});this.json=ge.Boolean(\"--json\",!1,{description:\"Format the output as an NDJSON stream\"});this.unsafe=ge.Boolean(\"--no-redacted\",!1,{description:\"Don't redact secrets (such as tokens) from the output\"});this.name=ge.String()}static{this.paths=[[\"config\",\"get\"]]}static{this.usage=ot.Usage({description:\"read a configuration settings\",details:`\n      This command will print a configuration setting.\n\n      Secrets (such as tokens) will be redacted from the output by default. If this behavior isn't desired, set the \\`--no-redacted\\` to get the untransformed value.\n    `,examples:[[\"Print a simple configuration setting\",\"yarn config get yarnPath\"],[\"Print a complex configuration setting\",\"yarn config get packageExtensions\"],[\"Print a nested field from the configuration\",`yarn config get 'npmScopes[\"my-company\"].npmRegistryServer'`],[\"Print a token from the configuration\",\"yarn config get npmAuthToken --no-redacted\"],[\"Print a configuration setting as JSON\",\"yarn config get packageExtensions --json\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),s=this.name.replace(/[.[].*$/,\"\"),a=this.name.replace(/^[^.[]*/,\"\");if(typeof r.settings.get(s)>\"u\")throw new nt(`Couldn't find a configuration settings named \"${s}\"`);let c=r.getSpecial(s,{hideSecrets:!this.unsafe,getNativePaths:!0}),f=je.convertMapsToIndexableObjects(c),p=a?(0,H1e.default)(f,a):f,h=await Ot.start({configuration:r,includeFooter:!1,json:this.json,stdout:this.context.stdout},async E=>{E.reportJson(p)});if(!this.json){if(typeof p==\"string\")return this.context.stdout.write(`${p}\n`),h.exitCode();E5.inspect.styles.name=\"cyan\",this.context.stdout.write(`${(0,E5.inspect)(p,{depth:1/0,colors:r.get(\"enableColors\"),compact:!1})}\n`)}return h.exitCode()}};Ve();Wt();var Q2e=et(B5()),T2e=et(aS()),R2e=et(v5()),S5=Ie(\"util\"),cC=class extends ut{constructor(){super(...arguments);this.json=ge.Boolean(\"--json\",!1,{description:\"Set complex configuration settings to JSON values\"});this.home=ge.Boolean(\"-H,--home\",!1,{description:\"Update the home configuration instead of the project configuration\"});this.name=ge.String();this.value=ge.String()}static{this.paths=[[\"config\",\"set\"]]}static{this.usage=ot.Usage({description:\"change a configuration settings\",details:`\n      This command will set a configuration setting.\n\n      When used without the \\`--json\\` flag, it can only set a simple configuration setting (a string, a number, or a boolean).\n\n      When used with the \\`--json\\` flag, it can set both simple and complex configuration settings, including Arrays and Objects.\n    `,examples:[[\"Set a simple configuration setting (a string, a number, or a boolean)\",\"yarn config set initScope myScope\"],[\"Set a simple configuration setting (a string, a number, or a boolean) using the `--json` flag\",'yarn config set initScope --json \\\\\"myScope\\\\\"'],[\"Set a complex configuration setting (an Array) using the `--json` flag\",`yarn config set unsafeHttpWhitelist --json '[\"*.example.com\", \"example.com\"]'`],[\"Set a complex configuration setting (an Object) using the `--json` flag\",`yarn config set packageExtensions --json '{ \"@babel/parser@*\": { \"dependencies\": { \"@babel/types\": \"*\" } } }'`],[\"Set a nested configuration setting\",'yarn config set npmScopes.company.npmRegistryServer \"https://npm.example.com\"'],[\"Set a nested configuration setting using indexed access for non-simple keys\",`yarn config set 'npmRegistries[\"//npm.example.com\"].npmAuthToken' \"ffffffff-ffff-ffff-ffff-ffffffffffff\"`]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),s=()=>{if(!r.projectCwd)throw new nt(\"This command must be run from within a project folder\");return r.projectCwd},a=this.name.replace(/[.[].*$/,\"\"),n=this.name.replace(/^[^.[]*\\.?/,\"\");if(typeof r.settings.get(a)>\"u\")throw new nt(`Couldn't find a configuration settings named \"${a}\"`);if(a===\"enableStrictSettings\")throw new nt(\"This setting only affects the file it's in, and thus cannot be set from the CLI\");let f=this.json?JSON.parse(this.value):this.value;await(this.home?I=>ze.updateHomeConfiguration(I):I=>ze.updateConfiguration(s(),I))(I=>{if(n){let R=(0,Q2e.default)(I);return(0,R2e.default)(R,this.name,f),R}else return{...I,[a]:f}});let E=(await ze.find(this.context.cwd,this.context.plugins)).getSpecial(a,{hideSecrets:!0,getNativePaths:!0}),C=je.convertMapsToIndexableObjects(E),S=n?(0,T2e.default)(C,n):C;return(await Ot.start({configuration:r,includeFooter:!1,stdout:this.context.stdout},async I=>{S5.inspect.styles.name=\"cyan\",I.reportInfo(0,`Successfully set ${this.name} to ${(0,S5.inspect)(S,{depth:1/0,colors:r.get(\"enableColors\"),compact:!1})}`)})).exitCode()}};Ve();Wt();var G2e=et(B5()),W2e=et(L2e()),Y2e=et(b5()),uC=class extends ut{constructor(){super(...arguments);this.home=ge.Boolean(\"-H,--home\",!1,{description:\"Update the home configuration instead of the project configuration\"});this.name=ge.String()}static{this.paths=[[\"config\",\"unset\"]]}static{this.usage=ot.Usage({description:\"unset a configuration setting\",details:`\n      This command will unset a configuration setting.\n    `,examples:[[\"Unset a simple configuration setting\",\"yarn config unset initScope\"],[\"Unset a complex configuration setting\",\"yarn config unset packageExtensions\"],[\"Unset a nested configuration setting\",\"yarn config unset npmScopes.company.npmRegistryServer\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),s=()=>{if(!r.projectCwd)throw new nt(\"This command must be run from within a project folder\");return r.projectCwd},a=this.name.replace(/[.[].*$/,\"\"),n=this.name.replace(/^[^.[]*\\.?/,\"\");if(typeof r.settings.get(a)>\"u\")throw new nt(`Couldn't find a configuration settings named \"${a}\"`);let f=this.home?h=>ze.updateHomeConfiguration(h):h=>ze.updateConfiguration(s(),h);return(await Ot.start({configuration:r,includeFooter:!1,stdout:this.context.stdout},async h=>{let E=!1;await f(C=>{if(!(0,W2e.default)(C,this.name))return h.reportWarning(0,`Configuration doesn't contain setting ${this.name}; there is nothing to unset`),E=!0,C;let S=n?(0,G2e.default)(C):{...C};return(0,Y2e.default)(S,this.name),S}),E||h.reportInfo(0,`Successfully unset ${this.name}`)})).exitCode()}};Ve();bt();Wt();var nF=Ie(\"util\"),fC=class extends ut{constructor(){super(...arguments);this.noDefaults=ge.Boolean(\"--no-defaults\",!1,{description:\"Omit the default values from the display\"});this.json=ge.Boolean(\"--json\",!1,{description:\"Format the output as an NDJSON stream\"});this.verbose=ge.Boolean(\"-v,--verbose\",{hidden:!0});this.why=ge.Boolean(\"--why\",{hidden:!0});this.names=ge.Rest()}static{this.paths=[[\"config\"]]}static{this.usage=ot.Usage({description:\"display the current configuration\",details:`\n      This command prints the current active configuration settings.\n    `,examples:[[\"Print the active configuration settings\",\"$0 config\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins,{strict:!1}),s=await vI({configuration:r,stdout:this.context.stdout,forceError:this.json},[{option:this.verbose,message:\"The --verbose option is deprecated, the settings' descriptions are now always displayed\"},{option:this.why,message:\"The --why option is deprecated, the settings' sources are now always displayed\"}]);if(s!==null)return s;let a=this.names.length>0?[...new Set(this.names)].sort():[...r.settings.keys()].sort(),n,c=await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout,includeFooter:!1},async f=>{if(r.invalid.size>0&&!this.json){for(let[p,h]of r.invalid)f.reportError(34,`Invalid configuration key \"${p}\" in ${h}`);f.reportSeparator()}if(this.json)for(let p of a){if(this.noDefaults&&!r.sources.has(p))continue;let h=r.settings.get(p);typeof h>\"u\"&&f.reportError(34,`No configuration key named \"${p}\"`);let E=r.getSpecial(p,{hideSecrets:!0,getNativePaths:!0}),C=r.sources.get(p)??\"<default>\",S=C&&C[0]!==\"<\"?ue.fromPortablePath(C):C;f.reportJson({key:p,effective:E,source:S,...h})}else{let p={breakLength:1/0,colors:r.get(\"enableColors\"),maxArrayLength:2},h={},E={children:h};for(let C of a){if(this.noDefaults&&!r.sources.has(C))continue;let S=r.settings.get(C),P=r.sources.get(C)??\"<default>\",I=r.getSpecial(C,{hideSecrets:!0,getNativePaths:!0}),R={Description:{label:\"Description\",value:he.tuple(he.Type.MARKDOWN,{text:S.description,format:this.cli.format(),paragraphs:!1})},Source:{label:\"Source\",value:he.tuple(P[0]===\"<\"?he.Type.CODE:he.Type.PATH,P)}};h[C]={value:he.tuple(he.Type.CODE,C),children:R};let N=(U,W)=>{for(let[te,ie]of W)if(ie instanceof Map){let Ae={};U[te]={children:Ae},N(Ae,ie)}else U[te]={label:te,value:he.tuple(he.Type.NO_HINT,(0,nF.inspect)(ie,p))}};I instanceof Map?N(R,I):R.Value={label:\"Value\",value:he.tuple(he.Type.NO_HINT,(0,nF.inspect)(I,p))}}a.length!==1&&(n=void 0),Qs.emitTree(E,{configuration:r,json:this.json,stdout:this.context.stdout,separators:2})}});if(!this.json&&typeof n<\"u\"){let f=a[0],p=(0,nF.inspect)(r.getSpecial(f,{hideSecrets:!0,getNativePaths:!0}),{colors:r.get(\"enableColors\")});this.context.stdout.write(`\n`),this.context.stdout.write(`${p}\n`)}return c.exitCode()}};Ve();Wt();Ul();var iF={};Vt(iF,{Strategy:()=>lS,acceptedStrategies:()=>wvt,dedupe:()=>P5});Ve();Ve();var V2e=et(Sa()),lS=(e=>(e.HIGHEST=\"highest\",e))(lS||{}),wvt=new Set(Object.values(lS)),Bvt={highest:async(t,e,{resolver:r,fetcher:s,resolveOptions:a,fetchOptions:n})=>{let c=new Map;for(let[p,h]of t.storedResolutions){let E=t.storedDescriptors.get(p);if(typeof E>\"u\")throw new Error(`Assertion failed: The descriptor (${p}) should have been registered`);je.getSetWithDefault(c,E.identHash).add(h)}let f=new Map(je.mapAndFilter(t.storedDescriptors.values(),p=>q.isVirtualDescriptor(p)?je.mapAndFilter.skip:[p.descriptorHash,je.makeDeferred()]));for(let p of t.storedDescriptors.values()){let h=f.get(p.descriptorHash);if(typeof h>\"u\")throw new Error(`Assertion failed: The descriptor (${p.descriptorHash}) should have been registered`);let E=t.storedResolutions.get(p.descriptorHash);if(typeof E>\"u\")throw new Error(`Assertion failed: The resolution (${p.descriptorHash}) should have been registered`);let C=t.originalPackages.get(E);if(typeof C>\"u\")throw new Error(`Assertion failed: The package (${E}) should have been registered`);Promise.resolve().then(async()=>{let S=r.getResolutionDependencies(p,a),P=Object.fromEntries(await je.allSettledSafe(Object.entries(S).map(async([te,ie])=>{let Ae=f.get(ie.descriptorHash);if(typeof Ae>\"u\")throw new Error(`Assertion failed: The descriptor (${ie.descriptorHash}) should have been registered`);let ce=await Ae.promise;if(!ce)throw new Error(\"Assertion failed: Expected the dependency to have been through the dedupe process itself\");return[te,ce.updatedPackage]})));if(e.length&&!V2e.default.isMatch(q.stringifyIdent(p),e)||!r.shouldPersistResolution(C,a))return C;let I=c.get(p.identHash);if(typeof I>\"u\")throw new Error(`Assertion failed: The resolutions (${p.identHash}) should have been registered`);if(I.size===1)return C;let R=[...I].map(te=>{let ie=t.originalPackages.get(te);if(typeof ie>\"u\")throw new Error(`Assertion failed: The package (${te}) should have been registered`);return ie}),N=await r.getSatisfying(p,P,R,a),U=N.locators?.[0];if(typeof U>\"u\"||!N.sorted)return C;let W=t.originalPackages.get(U.locatorHash);if(typeof W>\"u\")throw new Error(`Assertion failed: The package (${U.locatorHash}) should have been registered`);return W}).then(async S=>{let P=await t.preparePackage(S,{resolver:r,resolveOptions:a});h.resolve({descriptor:p,currentPackage:C,updatedPackage:S,resolvedPackage:P})}).catch(S=>{h.reject(S)})}return[...f.values()].map(p=>p.promise)}};async function P5(t,{strategy:e,patterns:r,cache:s,report:a}){let{configuration:n}=t,c=new Yi,f=n.makeResolver(),p=n.makeFetcher(),h={cache:s,checksums:t.storedChecksums,fetcher:p,project:t,report:c,cacheOptions:{skipIntegrityCheck:!0}},E={project:t,resolver:f,report:c,fetchOptions:h};return await a.startTimerPromise(\"Deduplication step\",async()=>{let C=Bvt[e],S=await C(t,r,{resolver:f,resolveOptions:E,fetcher:p,fetchOptions:h}),P=ho.progressViaCounter(S.length);await a.reportProgress(P);let I=0;await Promise.all(S.map(U=>U.then(W=>{if(W===null||W.currentPackage.locatorHash===W.updatedPackage.locatorHash)return;I++;let{descriptor:te,currentPackage:ie,updatedPackage:Ae}=W;a.reportInfo(0,`${q.prettyDescriptor(n,te)} can be deduped from ${q.prettyLocator(n,ie)} to ${q.prettyLocator(n,Ae)}`),a.reportJson({descriptor:q.stringifyDescriptor(te),currentResolution:q.stringifyLocator(ie),updatedResolution:q.stringifyLocator(Ae)}),t.storedResolutions.set(te.descriptorHash,Ae.locatorHash)}).finally(()=>P.tick())));let R;switch(I){case 0:R=\"No packages\";break;case 1:R=\"One package\";break;default:R=`${I} packages`}let N=he.pretty(n,e,he.Type.CODE);return a.reportInfo(0,`${R} can be deduped using the ${N} strategy`),I})}var AC=class extends ut{constructor(){super(...arguments);this.strategy=ge.String(\"-s,--strategy\",\"highest\",{description:\"The strategy to use when deduping dependencies\",validator:po(lS)});this.check=ge.Boolean(\"-c,--check\",!1,{description:\"Exit with exit code 1 when duplicates are found, without persisting the dependency tree\"});this.json=ge.Boolean(\"--json\",!1,{description:\"Format the output as an NDJSON stream\"});this.mode=ge.String(\"--mode\",{description:\"Change what artifacts installs generate\",validator:po(ec)});this.patterns=ge.Rest()}static{this.paths=[[\"dedupe\"]]}static{this.usage=ot.Usage({description:\"deduplicate dependencies with overlapping ranges\",details:\"\\n      Duplicates are defined as descriptors with overlapping ranges being resolved and locked to different locators. They are a natural consequence of Yarn's deterministic installs, but they can sometimes pile up and unnecessarily increase the size of your project.\\n\\n      This command dedupes dependencies in the current project using different strategies (only one is implemented at the moment):\\n\\n      - `highest`: Reuses (where possible) the locators with the highest versions. This means that dependencies can only be upgraded, never downgraded. It's also guaranteed that it never takes more than a single pass to dedupe the entire dependency tree.\\n\\n      **Note:** Even though it never produces a wrong dependency tree, this command should be used with caution, as it modifies the dependency tree, which can sometimes cause problems when packages don't strictly follow semver recommendations. Because of this, it is recommended to also review the changes manually.\\n\\n      If set, the `-c,--check` flag will only report the found duplicates, without persisting the modified dependency tree. If changes are found, the command will exit with a non-zero exit code, making it suitable for CI purposes.\\n\\n      If the `--mode=<mode>` option is set, Yarn will change which artifacts are generated. The modes currently supported are:\\n\\n      - `skip-build` will not run the build scripts at all. Note that this is different from setting `enableScripts` to false because the latter will disable build scripts, and thus affect the content of the artifacts generated on disk, whereas the former will just disable the build step - but not the scripts themselves, which just won't run.\\n\\n      - `update-lockfile` will skip the link step altogether, and only fetch packages that are missing from the lockfile (or that have no associated checksums). This mode is typically used by tools like Renovate or Dependabot to keep a lockfile up-to-date without incurring the full install cost.\\n\\n      This command accepts glob patterns as arguments (if valid Idents and supported by [micromatch](https://github.com/micromatch/micromatch)). Make sure to escape the patterns, to prevent your own shell from trying to expand them.\\n\\n      ### In-depth explanation:\\n\\n      Yarn doesn't deduplicate dependencies by default, otherwise installs wouldn't be deterministic and the lockfile would be useless. What it actually does is that it tries to not duplicate dependencies in the first place.\\n\\n      **Example:** If `foo@^2.3.4` (a dependency of a dependency) has already been resolved to `foo@2.3.4`, running `yarn add foo@*`will cause Yarn to reuse `foo@2.3.4`, even if the latest `foo` is actually `foo@2.10.14`, thus preventing unnecessary duplication.\\n\\n      Duplication happens when Yarn can't unlock dependencies that have already been locked inside the lockfile.\\n\\n      **Example:** If `foo@^2.3.4` (a dependency of a dependency) has already been resolved to `foo@2.3.4`, running `yarn add foo@2.10.14` will cause Yarn to install `foo@2.10.14` because the existing resolution doesn't satisfy the range `2.10.14`. This behavior can lead to (sometimes) unwanted duplication, since now the lockfile contains 2 separate resolutions for the 2 `foo` descriptors, even though they have overlapping ranges, which means that the lockfile can be simplified so that both descriptors resolve to `foo@2.10.14`.\\n    \",examples:[[\"Dedupe all packages\",\"$0 dedupe\"],[\"Dedupe all packages using a specific strategy\",\"$0 dedupe --strategy highest\"],[\"Dedupe a specific package\",\"$0 dedupe lodash\"],[\"Dedupe all packages with the `@babel/*` scope\",\"$0 dedupe '@babel/*'\"],[\"Check for duplicates (can be used as a CI step)\",\"$0 dedupe --check\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s}=await Tt.find(r,this.context.cwd),a=await Jr.find(r);await s.restoreInstallState({restoreResolutions:!1});let n=0,c=await Ot.start({configuration:r,includeFooter:!1,stdout:this.context.stdout,json:this.json},async f=>{n=await P5(s,{strategy:this.strategy,patterns:this.patterns,cache:a,report:f})});return c.hasErrors()?c.exitCode():this.check?n?1:0:await s.installWithNewReport({json:this.json,stdout:this.context.stdout},{cache:a,mode:this.mode})}};Ve();Wt();var pC=class extends ut{static{this.paths=[[\"--clipanion=definitions\"]]}async execute(){let{plugins:e}=await ze.find(this.context.cwd,this.context.plugins),r=[];for(let c of e){let{commands:f}=c[1];if(f){let h=wa.from(f).definitions();r.push([c[0],h])}}let s=this.cli.definitions(),a=(c,f)=>c.split(\" \").slice(1).join()===f.split(\" \").slice(1).join(),n=K2e()[\"@yarnpkg/builder\"].bundles.standard;for(let c of r){let f=c[1];for(let p of f)s.find(h=>a(h.path,p.path)).plugin={name:c[0],isDefault:n.includes(c[0])}}this.context.stdout.write(`${JSON.stringify(s,null,2)}\n`)}};var hC=class extends ut{static{this.paths=[[\"help\"],[\"--help\"],[\"-h\"]]}async execute(){this.context.stdout.write(this.cli.usage(null))}};Ve();bt();Wt();var gC=class extends ut{constructor(){super(...arguments);this.leadingArgument=ge.String();this.args=ge.Proxy()}async execute(){if(this.leadingArgument.match(/[\\\\/]/)&&!q.tryParseIdent(this.leadingArgument)){let r=K.resolve(this.context.cwd,ue.toPortablePath(this.leadingArgument));return await this.cli.run(this.args,{cwd:r})}else return await this.cli.run([\"run\",this.leadingArgument,...this.args])}};Ve();var dC=class extends ut{static{this.paths=[[\"-v\"],[\"--version\"]]}async execute(){this.context.stdout.write(`${un||\"<unknown>\"}\n`)}};Ve();Ve();Wt();var mC=class extends ut{constructor(){super(...arguments);this.commandName=ge.String();this.args=ge.Proxy()}static{this.paths=[[\"exec\"]]}static{this.usage=ot.Usage({description:\"execute a shell script\",details:`\n      This command simply executes a shell script within the context of the root directory of the active workspace using the portable shell.\n\n      It also makes sure to call it in a way that's compatible with the current project (for example, on PnP projects the environment will be setup in such a way that PnP will be correctly injected into the environment).\n    `,examples:[[\"Execute a single shell command\",\"$0 exec echo Hello World\"],[\"Execute a shell script\",'$0 exec \"tsc & babel src --out-dir lib\"']]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,locator:a}=await Tt.find(r,this.context.cwd);return await s.restoreInstallState(),await In.executePackageShellcode(a,this.commandName,this.args,{cwd:this.context.cwd,stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr,project:s})}};Ve();Wt();Ul();var yC=class extends ut{constructor(){super(...arguments);this.hash=ge.String({required:!1,validator:qx(IE(),[X2(/^p[0-9a-f]{5}$/)])})}static{this.paths=[[\"explain\",\"peer-requirements\"]]}static{this.usage=ot.Usage({description:\"explain a set of peer requirements\",details:`\n      A peer requirement represents all peer requests that a subject must satisfy when providing a requested package to requesters.\n\n      When the hash argument is specified, this command prints a detailed explanation of the peer requirement corresponding to the hash and whether it is satisfied or not.\n\n      When used without arguments, this command lists all peer requirements and the corresponding hash that can be used to get detailed information about a given requirement.\n\n      **Note:** A hash is a six-letter p-prefixed code that can be obtained from peer dependency warnings or from the list of all peer requirements (\\`yarn explain peer-requirements\\`).\n    `,examples:[[\"Explain the corresponding peer requirement for a hash\",\"$0 explain peer-requirements p1a4ed\"],[\"List all peer requirements\",\"$0 explain peer-requirements\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s}=await Tt.find(r,this.context.cwd);return await s.restoreInstallState({restoreResolutions:!1}),await s.applyLightResolution(),typeof this.hash<\"u\"?await Svt(this.hash,s,{stdout:this.context.stdout}):await Dvt(s,{stdout:this.context.stdout})}};async function Svt(t,e,r){let s=e.peerRequirementNodes.get(t);if(typeof s>\"u\")throw new Error(`No peerDependency requirements found for hash: \"${t}\"`);let a=new Set,n=p=>a.has(p.requester.locatorHash)?{value:he.tuple(he.Type.DEPENDENT,{locator:p.requester,descriptor:p.descriptor}),children:p.children.size>0?[{value:he.tuple(he.Type.NO_HINT,\"...\")}]:[]}:(a.add(p.requester.locatorHash),{value:he.tuple(he.Type.DEPENDENT,{locator:p.requester,descriptor:p.descriptor}),children:Object.fromEntries(Array.from(p.children.values(),h=>[q.stringifyLocator(h.requester),n(h)]))}),c=e.peerWarnings.find(p=>p.hash===t);return(await Ot.start({configuration:e.configuration,stdout:r.stdout,includeFooter:!1,includePrefix:!1},async p=>{let h=he.mark(e.configuration),E=c?h.Cross:h.Check;if(p.reportInfo(0,`Package ${he.pretty(e.configuration,s.subject,he.Type.LOCATOR)} is requested to provide ${he.pretty(e.configuration,s.ident,he.Type.IDENT)} by its descendants`),p.reportSeparator(),p.reportInfo(0,he.pretty(e.configuration,s.subject,he.Type.LOCATOR)),Qs.emitTree({children:Object.fromEntries(Array.from(s.requests.values(),C=>[q.stringifyLocator(C.requester),n(C)]))},{configuration:e.configuration,stdout:r.stdout,json:!1}),p.reportSeparator(),s.provided.range===\"missing:\"){let C=c?\"\":\" , but all peer requests are optional\";p.reportInfo(0,`${E} Package ${he.pretty(e.configuration,s.subject,he.Type.LOCATOR)} does not provide ${he.pretty(e.configuration,s.ident,he.Type.IDENT)}${C}.`)}else{let C=e.storedResolutions.get(s.provided.descriptorHash);if(!C)throw new Error(\"Assertion failed: Expected the descriptor to be registered\");let S=e.storedPackages.get(C);if(!S)throw new Error(\"Assertion failed: Expected the package to be registered\");p.reportInfo(0,`${E} Package ${he.pretty(e.configuration,s.subject,he.Type.LOCATOR)} provides ${he.pretty(e.configuration,s.ident,he.Type.IDENT)} with version ${q.prettyReference(e.configuration,S.version??\"0.0.0\")}, ${c?\"which does not satisfy all requests.\":\"which satisfies all requests\"}`),c?.type===3&&(c.range?p.reportInfo(0,`  The combined requested range is ${he.pretty(e.configuration,c.range,he.Type.RANGE)}`):p.reportInfo(0,\"  Unfortunately, the requested ranges have no overlap\"))}})).exitCode()}async function Dvt(t,e){return(await Ot.start({configuration:t.configuration,stdout:e.stdout,includeFooter:!1,includePrefix:!1},async s=>{let a=he.mark(t.configuration),n=je.sortMap(t.peerRequirementNodes,[([,c])=>q.stringifyLocator(c.subject),([,c])=>q.stringifyIdent(c.ident)]);for(let[,c]of n.values()){if(!c.root)continue;let f=t.peerWarnings.find(E=>E.hash===c.hash),p=[...q.allPeerRequests(c)],h;if(p.length>2?h=` and ${p.length-1} other dependencies`:p.length===2?h=\" and 1 other dependency\":h=\"\",c.provided.range!==\"missing:\"){let E=t.storedResolutions.get(c.provided.descriptorHash);if(!E)throw new Error(\"Assertion failed: Expected the resolution to have been registered\");let C=t.storedPackages.get(E);if(!C)throw new Error(\"Assertion failed: Expected the provided package to have been registered\");let S=`${he.pretty(t.configuration,c.hash,he.Type.CODE)} \\u2192 ${f?a.Cross:a.Check} ${q.prettyLocator(t.configuration,c.subject)} provides ${q.prettyLocator(t.configuration,C)} to ${q.prettyLocator(t.configuration,p[0].requester)}${h}`;f?s.reportWarning(0,S):s.reportInfo(0,S)}else{let E=`${he.pretty(t.configuration,c.hash,he.Type.CODE)} \\u2192 ${f?a.Cross:a.Check} ${q.prettyLocator(t.configuration,c.subject)} doesn't provide ${q.prettyIdent(t.configuration,c.ident)} to ${q.prettyLocator(t.configuration,p[0].requester)}${h}`;f?s.reportWarning(0,E):s.reportInfo(0,E)}}})).exitCode()}Ve();Wt();Ul();Ve();Ve();bt();Wt();var J2e=et(Ai()),EC=class extends ut{constructor(){super(...arguments);this.useYarnPath=ge.Boolean(\"--yarn-path\",{description:\"Set the yarnPath setting even if the version can be accessed by Corepack\"});this.onlyIfNeeded=ge.Boolean(\"--only-if-needed\",!1,{description:\"Only lock the Yarn version if it isn't already locked\"});this.version=ge.String()}static{this.paths=[[\"set\",\"version\"]]}static{this.usage=ot.Usage({description:\"lock the Yarn version used by the project\",details:\"\\n      This command will set a specific release of Yarn to be used by Corepack: https://nodejs.org/api/corepack.html.\\n\\n      By default it only will set the `packageManager` field at the root of your project, but if the referenced release cannot be represented this way, if you already have `yarnPath` configured, or if you set the `--yarn-path` command line flag, then the release will also be downloaded from the Yarn GitHub repository, stored inside your project, and referenced via the `yarnPath` settings from your project `.yarnrc.yml` file.\\n\\n      A very good use case for this command is to enforce the version of Yarn used by any single member of your team inside the same project - by doing this you ensure that you have control over Yarn upgrades and downgrades (including on your deployment servers), and get rid of most of the headaches related to someone using a slightly different version and getting different behavior.\\n\\n      The version specifier can be:\\n\\n      - a tag:\\n        - `latest` / `berry` / `stable` -> the most recent stable berry (`>=2.0.0`) release\\n        - `canary` -> the most recent canary (release candidate) berry (`>=2.0.0`) release\\n        - `classic` -> the most recent classic (`^0.x || ^1.x`) release\\n\\n      - a semver range (e.g. `2.x`) -> the most recent version satisfying the range (limited to berry releases)\\n\\n      - a semver version (e.g. `2.4.1`, `1.22.1`)\\n\\n      - a local file referenced through either a relative or absolute path\\n\\n      - `self` -> the version used to invoke the command\\n    \",examples:[[\"Download the latest release from the Yarn repository\",\"$0 set version latest\"],[\"Download the latest canary release from the Yarn repository\",\"$0 set version canary\"],[\"Download the latest classic release from the Yarn repository\",\"$0 set version classic\"],[\"Download the most recent Yarn 3 build\",\"$0 set version 3.x\"],[\"Download a specific Yarn 2 build\",\"$0 set version 2.0.0-rc.30\"],[\"Switch back to a specific Yarn 1 release\",\"$0 set version 1.22.1\"],[\"Use a release from the local filesystem\",\"$0 set version ./yarn.cjs\"],[\"Use a release from a URL\",\"$0 set version https://repo.yarnpkg.com/3.1.0/packages/yarnpkg-cli/bin/yarn.js\"],[\"Download the version used to invoke the command\",\"$0 set version self\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins);if(this.onlyIfNeeded&&r.get(\"yarnPath\")){let f=r.sources.get(\"yarnPath\");if(!f)throw new Error(\"Assertion failed: Expected 'yarnPath' to have a source\");let p=r.projectCwd??r.startingCwd;if(K.contains(p,f))return 0}let s=()=>{if(typeof un>\"u\")throw new nt(\"The --install flag can only be used without explicit version specifier from the Yarn CLI\");return`file://${process.argv[1]}`},a,n=(f,p)=>({version:p,url:f.replace(/\\{\\}/g,p)});if(this.version===\"self\")a={url:s(),version:un??\"self\"};else if(this.version===\"latest\"||this.version===\"berry\"||this.version===\"stable\")a=n(\"https://repo.yarnpkg.com/{}/packages/yarnpkg-cli/bin/yarn.js\",await cS(r,\"stable\"));else if(this.version===\"canary\")a=n(\"https://repo.yarnpkg.com/{}/packages/yarnpkg-cli/bin/yarn.js\",await cS(r,\"canary\"));else if(this.version===\"classic\")a={url:\"https://classic.yarnpkg.com/latest.js\",version:\"classic\"};else if(this.version.match(/^https?:/))a={url:this.version,version:\"remote\"};else if(this.version.match(/^\\.{0,2}[\\\\/]/)||ue.isAbsolute(this.version))a={url:`file://${K.resolve(ue.toPortablePath(this.version))}`,version:\"file\"};else if(Or.satisfiesWithPrereleases(this.version,\">=2.0.0\"))a=n(\"https://repo.yarnpkg.com/{}/packages/yarnpkg-cli/bin/yarn.js\",this.version);else if(Or.satisfiesWithPrereleases(this.version,\"^0.x || ^1.x\"))a=n(\"https://github.com/yarnpkg/yarn/releases/download/v{}/yarn-{}.js\",this.version);else if(Or.validRange(this.version))a=n(\"https://repo.yarnpkg.com/{}/packages/yarnpkg-cli/bin/yarn.js\",await bvt(r,this.version));else throw new nt(`Invalid version descriptor \"${this.version}\"`);return(await Ot.start({configuration:r,stdout:this.context.stdout,includeLogs:!this.context.quiet},async f=>{let p=async()=>{let h=\"file://\";return a.url.startsWith(h)?(f.reportInfo(0,`Retrieving ${he.pretty(r,a.url,he.Type.PATH)}`),await le.readFilePromise(a.url.slice(h.length))):(f.reportInfo(0,`Downloading ${he.pretty(r,a.url,he.Type.URL)}`),await An.get(a.url,{configuration:r}))};await x5(r,a.version,p,{report:f,useYarnPath:this.useYarnPath})})).exitCode()}};async function bvt(t,e){let s=(await An.get(\"https://repo.yarnpkg.com/tags\",{configuration:t,jsonResponse:!0})).tags.filter(a=>Or.satisfiesWithPrereleases(a,e));if(s.length===0)throw new nt(`No matching release found for range ${he.pretty(t,e,he.Type.RANGE)}.`);return s[0]}async function cS(t,e){let r=await An.get(\"https://repo.yarnpkg.com/tags\",{configuration:t,jsonResponse:!0});if(!r.latest[e])throw new nt(`Tag ${he.pretty(t,e,he.Type.RANGE)} not found`);return r.latest[e]}async function x5(t,e,r,{report:s,useYarnPath:a}){let n,c=async()=>(typeof n>\"u\"&&(n=await r()),n);if(e===null){let te=await c();await le.mktempPromise(async ie=>{let Ae=K.join(ie,\"yarn.cjs\");await le.writeFilePromise(Ae,te);let{stdout:ce}=await Gr.execvp(process.execPath,[ue.fromPortablePath(Ae),\"--version\"],{cwd:ie,env:{...t.env,YARN_IGNORE_PATH:\"1\"}});if(e=ce.trim(),!J2e.default.valid(e))throw new Error(`Invalid semver version. ${he.pretty(t,\"yarn --version\",he.Type.CODE)} returned:\n${e}`)})}let f=t.projectCwd??t.startingCwd,p=K.resolve(f,\".yarn/releases\"),h=K.resolve(p,`yarn-${e}.cjs`),E=K.relative(t.startingCwd,h),C=je.isTaggedYarnVersion(e),S=t.get(\"yarnPath\"),P=!C,I=P||!!S||!!a;if(a===!1){if(P)throw new Yt(0,\"You explicitly opted out of yarnPath usage in your command line, but the version you specified cannot be represented by Corepack\");I=!1}else!I&&!process.env.COREPACK_ROOT&&(s.reportWarning(0,`You don't seem to have ${he.applyHyperlink(t,\"Corepack\",\"https://nodejs.org/api/corepack.html\")} enabled; we'll have to rely on ${he.applyHyperlink(t,\"yarnPath\",\"https://yarnpkg.com/configuration/yarnrc#yarnPath\")} instead`),I=!0);if(I){let te=await c();s.reportInfo(0,`Saving the new release in ${he.pretty(t,E,\"magenta\")}`),await le.removePromise(K.dirname(h)),await le.mkdirPromise(K.dirname(h),{recursive:!0}),await le.writeFilePromise(h,te,{mode:493}),await ze.updateConfiguration(f,{yarnPath:K.relative(f,h)})}else await le.removePromise(K.dirname(h)),await ze.updateConfiguration(f,{yarnPath:ze.deleteProperty});let R=await Ht.tryFind(f)||new Ht;R.packageManager=`yarn@${C?e:await cS(t,\"stable\")}`;let N={};R.exportTo(N);let U=K.join(f,Ht.fileName),W=`${JSON.stringify(N,null,R.indent)}\n`;return await le.changeFilePromise(U,W,{automaticNewlines:!0}),{bundleVersion:e}}function z2e(t){return Dr[zx(t)]}var Pvt=/## (?<code>YN[0-9]{4}) - `(?<name>[A-Z_]+)`\\n\\n(?<details>(?:.(?!##))+)/gs;async function xvt(t){let r=`https://repo.yarnpkg.com/${je.isTaggedYarnVersion(un)?un:await cS(t,\"canary\")}/packages/docusaurus/docs/advanced/01-general-reference/error-codes.mdx`,s=await An.get(r,{configuration:t});return new Map(Array.from(s.toString().matchAll(Pvt),({groups:a})=>{if(!a)throw new Error(\"Assertion failed: Expected the match to have been successful\");let n=z2e(a.code);if(a.name!==n)throw new Error(`Assertion failed: Invalid error code data: Expected \"${a.name}\" to be named \"${n}\"`);return[a.code,a.details]}))}var IC=class extends ut{constructor(){super(...arguments);this.code=ge.String({required:!1,validator:$2(IE(),[X2(/^YN[0-9]{4}$/)])});this.json=ge.Boolean(\"--json\",!1,{description:\"Format the output as an NDJSON stream\"})}static{this.paths=[[\"explain\"]]}static{this.usage=ot.Usage({description:\"explain an error code\",details:`\n      When the code argument is specified, this command prints its name and its details.\n\n      When used without arguments, this command lists all error codes and their names.\n    `,examples:[[\"Explain an error code\",\"$0 explain YN0006\"],[\"List all error codes\",\"$0 explain\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins);if(typeof this.code<\"u\"){let s=z2e(this.code),a=he.pretty(r,s,he.Type.CODE),n=this.cli.format().header(`${this.code} - ${a}`),f=(await xvt(r)).get(this.code),p=typeof f<\"u\"?he.jsonOrPretty(this.json,r,he.tuple(he.Type.MARKDOWN,{text:f,format:this.cli.format(),paragraphs:!0})):`This error code does not have a description.\n\nYou can help us by editing this page on GitHub \\u{1F642}:\n${he.jsonOrPretty(this.json,r,he.tuple(he.Type.URL,\"https://github.com/yarnpkg/berry/blob/master/packages/docusaurus/docs/advanced/01-general-reference/error-codes.mdx\"))}\n`;this.json?this.context.stdout.write(`${JSON.stringify({code:this.code,name:s,details:p})}\n`):this.context.stdout.write(`${n}\n\n${p}\n`)}else{let s={children:je.mapAndFilter(Object.entries(Dr),([a,n])=>Number.isNaN(Number(a))?je.mapAndFilter.skip:{label:Vf(Number(a)),value:he.tuple(he.Type.CODE,n)})};Qs.emitTree(s,{configuration:r,stdout:this.context.stdout,json:this.json})}}};Ve();bt();Wt();var Z2e=et(Sa()),CC=class extends ut{constructor(){super(...arguments);this.all=ge.Boolean(\"-A,--all\",!1,{description:\"Print versions of a package from the whole project\"});this.recursive=ge.Boolean(\"-R,--recursive\",!1,{description:\"Print information for all packages, including transitive dependencies\"});this.extra=ge.Array(\"-X,--extra\",[],{description:\"An array of requests of extra data provided by plugins\"});this.cache=ge.Boolean(\"--cache\",!1,{description:\"Print information about the cache entry of a package (path, size, checksum)\"});this.dependents=ge.Boolean(\"--dependents\",!1,{description:\"Print all dependents for each matching package\"});this.manifest=ge.Boolean(\"--manifest\",!1,{description:\"Print data obtained by looking at the package archive (license, homepage, ...)\"});this.nameOnly=ge.Boolean(\"--name-only\",!1,{description:\"Only print the name for the matching packages\"});this.virtuals=ge.Boolean(\"--virtuals\",!1,{description:\"Print each instance of the virtual packages\"});this.json=ge.Boolean(\"--json\",!1,{description:\"Format the output as an NDJSON stream\"});this.patterns=ge.Rest()}static{this.paths=[[\"info\"]]}static{this.usage=ot.Usage({description:\"see information related to packages\",details:\"\\n      This command prints various information related to the specified packages, accepting glob patterns.\\n\\n      By default, if the locator reference is missing, Yarn will default to print the information about all the matching direct dependencies of the package for the active workspace. To instead print all versions of the package that are direct dependencies of any of your workspaces, use the `-A,--all` flag. Adding the `-R,--recursive` flag will also report transitive dependencies.\\n\\n      Some fields will be hidden by default in order to keep the output readable, but can be selectively displayed by using additional options (`--dependents`, `--manifest`, `--virtuals`, ...) described in the option descriptions.\\n\\n      Note that this command will only print the information directly related to the selected packages - if you wish to know why the package is there in the first place, use `yarn why` which will do just that (it also provides a `-R,--recursive` flag that may be of some help).\\n    \",examples:[[\"Show information about Lodash\",\"$0 info lodash\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Jr.find(r);if(!a&&!this.all)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState();let c=new Set(this.extra);this.cache&&c.add(\"cache\"),this.dependents&&c.add(\"dependents\"),this.manifest&&c.add(\"manifest\");let f=(ie,{recursive:Ae})=>{let ce=ie.anchoredLocator.locatorHash,me=new Map,pe=[ce];for(;pe.length>0;){let Be=pe.shift();if(me.has(Be))continue;let Ce=s.storedPackages.get(Be);if(typeof Ce>\"u\")throw new Error(\"Assertion failed: Expected the package to be registered\");if(me.set(Be,Ce),q.isVirtualLocator(Ce)&&pe.push(q.devirtualizeLocator(Ce).locatorHash),!(!Ae&&Be!==ce))for(let g of Ce.dependencies.values()){let we=s.storedResolutions.get(g.descriptorHash);if(typeof we>\"u\")throw new Error(\"Assertion failed: Expected the resolution to be registered\");pe.push(we)}}return me.values()},p=({recursive:ie})=>{let Ae=new Map;for(let ce of s.workspaces)for(let me of f(ce,{recursive:ie}))Ae.set(me.locatorHash,me);return Ae.values()},h=({all:ie,recursive:Ae})=>ie&&Ae?s.storedPackages.values():ie?p({recursive:Ae}):f(a,{recursive:Ae}),E=({all:ie,recursive:Ae})=>{let ce=h({all:ie,recursive:Ae}),me=this.patterns.map(Ce=>{let g=q.parseLocator(Ce),we=Z2e.default.makeRe(q.stringifyIdent(g)),ye=q.isVirtualLocator(g),fe=ye?q.devirtualizeLocator(g):g;return se=>{let X=q.stringifyIdent(se);if(!we.test(X))return!1;if(g.reference===\"unknown\")return!0;let De=q.isVirtualLocator(se),Re=De?q.devirtualizeLocator(se):se;return!(ye&&De&&g.reference!==se.reference||fe.reference!==Re.reference)}}),pe=je.sortMap([...ce],Ce=>q.stringifyLocator(Ce));return{selection:pe.filter(Ce=>me.length===0||me.some(g=>g(Ce))),sortedLookup:pe}},{selection:C,sortedLookup:S}=E({all:this.all,recursive:this.recursive});if(C.length===0)throw new nt(\"No package matched your request\");let P=new Map;if(this.dependents)for(let ie of S)for(let Ae of ie.dependencies.values()){let ce=s.storedResolutions.get(Ae.descriptorHash);if(typeof ce>\"u\")throw new Error(\"Assertion failed: Expected the resolution to be registered\");je.getArrayWithDefault(P,ce).push(ie)}let I=new Map;for(let ie of S){if(!q.isVirtualLocator(ie))continue;let Ae=q.devirtualizeLocator(ie);je.getArrayWithDefault(I,Ae.locatorHash).push(ie)}let R={},N={children:R},U=r.makeFetcher(),W={project:s,fetcher:U,cache:n,checksums:s.storedChecksums,report:new Yi,cacheOptions:{skipIntegrityCheck:!0}},te=[async(ie,Ae,ce)=>{if(!Ae.has(\"manifest\"))return;let me=await U.fetch(ie,W),pe;try{pe=await Ht.find(me.prefixPath,{baseFs:me.packageFs})}finally{me.releaseFs?.()}ce(\"Manifest\",{License:he.tuple(he.Type.NO_HINT,pe.license),Homepage:he.tuple(he.Type.URL,pe.raw.homepage??null)})},async(ie,Ae,ce)=>{if(!Ae.has(\"cache\"))return;let me=s.storedChecksums.get(ie.locatorHash)??null,pe=n.getLocatorPath(ie,me),Be;if(pe!==null)try{Be=await le.statPromise(pe)}catch{}let Ce=typeof Be<\"u\"?[Be.size,he.Type.SIZE]:void 0;ce(\"Cache\",{Checksum:he.tuple(he.Type.NO_HINT,me),Path:he.tuple(he.Type.PATH,pe),Size:Ce})}];for(let ie of C){let Ae=q.isVirtualLocator(ie);if(!this.virtuals&&Ae)continue;let ce={},me={value:[ie,he.Type.LOCATOR],children:ce};if(R[q.stringifyLocator(ie)]=me,this.nameOnly){delete me.children;continue}let pe=I.get(ie.locatorHash);typeof pe<\"u\"&&(ce.Instances={label:\"Instances\",value:he.tuple(he.Type.NUMBER,pe.length)}),ce.Version={label:\"Version\",value:he.tuple(he.Type.NO_HINT,ie.version)};let Be=(g,we)=>{let ye={};if(ce[g]=ye,Array.isArray(we))ye.children=we.map(fe=>({value:fe}));else{let fe={};ye.children=fe;for(let[se,X]of Object.entries(we))typeof X>\"u\"||(fe[se]={label:se,value:X})}};if(!Ae){for(let g of te)await g(ie,c,Be);await r.triggerHook(g=>g.fetchPackageInfo,ie,c,Be)}ie.bin.size>0&&!Ae&&Be(\"Exported Binaries\",[...ie.bin.keys()].map(g=>he.tuple(he.Type.PATH,g)));let Ce=P.get(ie.locatorHash);typeof Ce<\"u\"&&Ce.length>0&&Be(\"Dependents\",Ce.map(g=>he.tuple(he.Type.LOCATOR,g))),ie.dependencies.size>0&&!Ae&&Be(\"Dependencies\",[...ie.dependencies.values()].map(g=>{let we=s.storedResolutions.get(g.descriptorHash),ye=typeof we<\"u\"?s.storedPackages.get(we)??null:null;return he.tuple(he.Type.RESOLUTION,{descriptor:g,locator:ye})})),ie.peerDependencies.size>0&&Ae&&Be(\"Peer dependencies\",[...ie.peerDependencies.values()].map(g=>{let we=ie.dependencies.get(g.identHash),ye=typeof we<\"u\"?s.storedResolutions.get(we.descriptorHash)??null:null,fe=ye!==null?s.storedPackages.get(ye)??null:null;return he.tuple(he.Type.RESOLUTION,{descriptor:g,locator:fe})}))}Qs.emitTree(N,{configuration:r,json:this.json,stdout:this.context.stdout,separators:this.nameOnly?0:2})}};Ve();bt();Bc();var sF=et(Rd());Wt();var k5=et(Ai());Ul();var kvt=[{selector:t=>t===-1,name:\"nodeLinker\",value:\"node-modules\"},{selector:t=>t!==-1&&t<8,name:\"enableGlobalCache\",value:!1},{selector:t=>t!==-1&&t<8,name:\"compressionLevel\",value:\"mixed\"}],wC=class extends ut{constructor(){super(...arguments);this.json=ge.Boolean(\"--json\",!1,{description:\"Format the output as an NDJSON stream\"});this.immutable=ge.Boolean(\"--immutable\",{description:\"Abort with an error exit code if the lockfile was to be modified\"});this.immutableCache=ge.Boolean(\"--immutable-cache\",{description:\"Abort with an error exit code if the cache folder was to be modified\"});this.refreshLockfile=ge.Boolean(\"--refresh-lockfile\",{description:\"Refresh the package metadata stored in the lockfile\"});this.checkCache=ge.Boolean(\"--check-cache\",{description:\"Always refetch the packages and ensure that their checksums are consistent\"});this.checkResolutions=ge.Boolean(\"--check-resolutions\",{description:\"Validates that the package resolutions are coherent\"});this.inlineBuilds=ge.Boolean(\"--inline-builds\",{description:\"Verbosely print the output of the build steps of dependencies\"});this.mode=ge.String(\"--mode\",{description:\"Change what artifacts installs generate\",validator:po(ec)});this.cacheFolder=ge.String(\"--cache-folder\",{hidden:!0});this.frozenLockfile=ge.Boolean(\"--frozen-lockfile\",{hidden:!0});this.ignoreEngines=ge.Boolean(\"--ignore-engines\",{hidden:!0});this.nonInteractive=ge.Boolean(\"--non-interactive\",{hidden:!0});this.preferOffline=ge.Boolean(\"--prefer-offline\",{hidden:!0});this.production=ge.Boolean(\"--production\",{hidden:!0});this.registry=ge.String(\"--registry\",{hidden:!0});this.silent=ge.Boolean(\"--silent\",{hidden:!0});this.networkTimeout=ge.String(\"--network-timeout\",{hidden:!0})}static{this.paths=[[\"install\"],ot.Default]}static{this.usage=ot.Usage({description:\"install the project dependencies\",details:\"\\n      This command sets up your project if needed. The installation is split into four different steps that each have their own characteristics:\\n\\n      - **Resolution:** First the package manager will resolve your dependencies. The exact way a dependency version is privileged over another isn't standardized outside of the regular semver guarantees. If a package doesn't resolve to what you would expect, check that all dependencies are correctly declared (also check our website for more information: ).\\n\\n      - **Fetch:** Then we download all the dependencies if needed, and make sure that they're all stored within our cache (check the value of `cacheFolder` in `yarn config` to see where the cache files are stored).\\n\\n      - **Link:** Then we send the dependency tree information to internal plugins tasked with writing them on the disk in some form (for example by generating the `.pnp.cjs` file you might know).\\n\\n      - **Build:** Once the dependency tree has been written on the disk, the package manager will now be free to run the build scripts for all packages that might need it, in a topological order compatible with the way they depend on one another. See https://yarnpkg.com/advanced/lifecycle-scripts for detail.\\n\\n      Note that running this command is not part of the recommended workflow. Yarn supports zero-installs, which means that as long as you store your cache and your `.pnp.cjs` file inside your repository, everything will work without requiring any install right after cloning your repository or switching branches.\\n\\n      If the `--immutable` option is set (defaults to true on CI), Yarn will abort with an error exit code if the lockfile was to be modified (other paths can be added using the `immutablePatterns` configuration setting). For backward compatibility we offer an alias under the name of `--frozen-lockfile`, but it will be removed in a later release.\\n\\n      If the `--immutable-cache` option is set, Yarn will abort with an error exit code if the cache folder was to be modified (either because files would be added, or because they'd be removed).\\n\\n      If the `--refresh-lockfile` option is set, Yarn will keep the same resolution for the packages currently in the lockfile but will refresh their metadata. If used together with `--immutable`, it can validate that the lockfile information are consistent. This flag is enabled by default when Yarn detects it runs within a pull request context.\\n\\n      If the `--check-cache` option is set, Yarn will always refetch the packages and will ensure that their checksum matches what's 1/ described in the lockfile 2/ inside the existing cache files (if present). This is recommended as part of your CI workflow if you're both following the Zero-Installs model and accepting PRs from third-parties, as they'd otherwise have the ability to alter the checked-in packages before submitting them.\\n\\n      If the `--inline-builds` option is set, Yarn will verbosely print the output of the build steps of your dependencies (instead of writing them into individual files). This is likely useful mostly for debug purposes only when using Docker-like environments.\\n\\n      If the `--mode=<mode>` option is set, Yarn will change which artifacts are generated. The modes currently supported are:\\n\\n      - `skip-build` will not run the build scripts at all. Note that this is different from setting `enableScripts` to false because the latter will disable build scripts, and thus affect the content of the artifacts generated on disk, whereas the former will just disable the build step - but not the scripts themselves, which just won't run.\\n\\n      - `update-lockfile` will skip the link step altogether, and only fetch packages that are missing from the lockfile (or that have no associated checksums). This mode is typically used by tools like Renovate or Dependabot to keep a lockfile up-to-date without incurring the full install cost.\\n    \",examples:[[\"Install the project\",\"$0 install\"],[\"Validate a project when using Zero-Installs\",\"$0 install --immutable --immutable-cache\"],[\"Validate a project when using Zero-Installs (slightly safer if you accept external PRs)\",\"$0 install --immutable --immutable-cache --check-cache\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins);typeof this.inlineBuilds<\"u\"&&r.useWithSource(\"<cli>\",{enableInlineBuilds:this.inlineBuilds},r.startingCwd,{overwrite:!0});let s=!!process.env.FUNCTION_TARGET||!!process.env.GOOGLE_RUNTIME,a=await vI({configuration:r,stdout:this.context.stdout},[{option:this.ignoreEngines,message:\"The --ignore-engines option is deprecated; engine checking isn't a core feature anymore\",error:!sF.default.VERCEL},{option:this.registry,message:\"The --registry option is deprecated; prefer setting npmRegistryServer in your .yarnrc.yml file\"},{option:this.preferOffline,message:\"The --prefer-offline flag is deprecated; use the --cached flag with 'yarn add' instead\",error:!sF.default.VERCEL},{option:this.production,message:\"The --production option is deprecated on 'install'; use 'yarn workspaces focus' instead\",error:!0},{option:this.nonInteractive,message:\"The --non-interactive option is deprecated\",error:!s},{option:this.frozenLockfile,message:\"The --frozen-lockfile option is deprecated; use --immutable and/or --immutable-cache instead\",callback:()=>this.immutable=this.frozenLockfile},{option:this.cacheFolder,message:\"The cache-folder option has been deprecated; use rc settings instead\",error:!sF.default.NETLIFY}]);if(a!==null)return a;let n=this.mode===\"update-lockfile\";if(n&&(this.immutable||this.immutableCache))throw new nt(`${he.pretty(r,\"--immutable\",he.Type.CODE)} and ${he.pretty(r,\"--immutable-cache\",he.Type.CODE)} cannot be used with ${he.pretty(r,\"--mode=update-lockfile\",he.Type.CODE)}`);let c=(this.immutable??r.get(\"enableImmutableInstalls\"))&&!n,f=this.immutableCache&&!n;if(r.projectCwd!==null){let R=await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout,includeFooter:!1},async N=>{let U=!1;await Rvt(r,c)&&(N.reportInfo(48,\"Automatically removed core plugins that are now builtins \\u{1F44D}\"),U=!0),await Tvt(r,c)&&(N.reportInfo(48,\"Automatically fixed merge conflicts \\u{1F44D}\"),U=!0),U&&N.reportSeparator()});if(R.hasErrors())return R.exitCode()}if(r.projectCwd!==null){let R=await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout,includeFooter:!1},async N=>{if(ze.telemetry?.isNew)ze.telemetry.commitTips(),N.reportInfo(65,\"Yarn will periodically gather anonymous telemetry: https://yarnpkg.com/advanced/telemetry\"),N.reportInfo(65,`Run ${he.pretty(r,\"yarn config set --home enableTelemetry 0\",he.Type.CODE)} to disable`),N.reportSeparator();else if(ze.telemetry?.shouldShowTips){let U=await An.get(\"https://repo.yarnpkg.com/tags\",{configuration:r,jsonResponse:!0}).catch(()=>null);if(U!==null){let W=null;if(un!==null){let ie=k5.default.prerelease(un)?\"canary\":\"stable\",Ae=U.latest[ie];k5.default.gt(Ae,un)&&(W=[ie,Ae])}if(W)ze.telemetry.commitTips(),N.reportInfo(88,`${he.applyStyle(r,`A new ${W[0]} version of Yarn is available:`,he.Style.BOLD)} ${q.prettyReference(r,W[1])}!`),N.reportInfo(88,`Upgrade now by running ${he.pretty(r,`yarn set version ${W[1]}`,he.Type.CODE)}`),N.reportSeparator();else{let te=ze.telemetry.selectTip(U.tips);te&&(N.reportInfo(89,he.pretty(r,te.message,he.Type.MARKDOWN_INLINE)),te.url&&N.reportInfo(89,`Learn more at ${te.url}`),N.reportSeparator())}}}});if(R.hasErrors())return R.exitCode()}let{project:p,workspace:h}=await Tt.find(r,this.context.cwd),E=p.lockfileLastVersion;if(E!==null){let R=await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout,includeFooter:!1},async N=>{let U={};for(let W of kvt)W.selector(E)&&typeof r.sources.get(W.name)>\"u\"&&(r.use(\"<compat>\",{[W.name]:W.value},p.cwd,{overwrite:!0}),U[W.name]=W.value);Object.keys(U).length>0&&(await ze.updateConfiguration(p.cwd,U),N.reportInfo(87,\"Migrated your project to the latest Yarn version \\u{1F680}\"),N.reportSeparator())});if(R.hasErrors())return R.exitCode()}let C=await Jr.find(r,{immutable:f,check:this.checkCache});if(!h)throw new ar(p.cwd,this.context.cwd);await p.restoreInstallState({restoreResolutions:!1});let S=r.get(\"enableHardenedMode\");S&&typeof r.sources.get(\"enableHardenedMode\")>\"u\"&&await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout,includeFooter:!1},async R=>{R.reportWarning(0,\"Yarn detected that the current workflow is executed from a public pull request. For safety the hardened mode has been enabled.\"),R.reportWarning(0,`It will prevent malicious lockfile manipulations, in exchange for a slower install time. You can opt-out if necessary; check our ${he.applyHyperlink(r,\"documentation\",\"https://yarnpkg.com/features/security#hardened-mode\")} for more details.`),R.reportSeparator()}),(this.refreshLockfile??S)&&(p.lockfileNeedsRefresh=!0);let P=this.checkResolutions??S;return(await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout,forceSectionAlignment:!0,includeLogs:!0,includeVersion:!0},async R=>{await p.install({cache:C,report:R,immutable:c,checkResolutions:P,mode:this.mode})})).exitCode()}},Qvt=\"<<<<<<<\";async function Tvt(t,e){if(!t.projectCwd)return!1;let r=K.join(t.projectCwd,Er.lockfile);if(!await le.existsPromise(r)||!(await le.readFilePromise(r,\"utf8\")).includes(Qvt))return!1;if(e)throw new Yt(47,\"Cannot autofix a lockfile when running an immutable install\");let a=await Gr.execvp(\"git\",[\"rev-parse\",\"MERGE_HEAD\",\"HEAD\"],{cwd:t.projectCwd});if(a.code!==0&&(a=await Gr.execvp(\"git\",[\"rev-parse\",\"REBASE_HEAD\",\"HEAD\"],{cwd:t.projectCwd})),a.code!==0&&(a=await Gr.execvp(\"git\",[\"rev-parse\",\"CHERRY_PICK_HEAD\",\"HEAD\"],{cwd:t.projectCwd})),a.code!==0)throw new Yt(83,\"Git returned an error when trying to find the commits pertaining to the conflict\");let n=await Promise.all(a.stdout.trim().split(/\\n/).map(async f=>{let p=await Gr.execvp(\"git\",[\"show\",`${f}:./${Er.lockfile}`],{cwd:t.projectCwd});if(p.code!==0)throw new Yt(83,`Git returned an error when trying to access the lockfile content in ${f}`);try{return cs(p.stdout)}catch{throw new Yt(46,\"A variant of the conflicting lockfile failed to parse\")}}));n=n.filter(f=>!!f.__metadata);for(let f of n){if(f.__metadata.version<7)for(let p of Object.keys(f)){if(p===\"__metadata\")continue;let h=q.parseDescriptor(p,!0),E=t.normalizeDependency(h),C=q.stringifyDescriptor(E);C!==p&&(f[C]=f[p],delete f[p])}for(let p of Object.keys(f)){if(p===\"__metadata\")continue;let h=f[p].checksum;typeof h>\"u\"||h.includes(\"/\")||(f[p].checksum=`${f.__metadata.cacheKey}/${h}`)}}let c=Object.assign({},...n);c.__metadata.version=`${Math.min(...n.map(f=>parseInt(f.__metadata.version??0)))}`,c.__metadata.cacheKey=\"merged\";for(let[f,p]of Object.entries(c))typeof p==\"string\"&&delete c[f];return await le.changeFilePromise(r,il(c),{automaticNewlines:!0}),!0}async function Rvt(t,e){if(!t.projectCwd)return!1;let r=[],s=K.join(t.projectCwd,\".yarn/plugins/@yarnpkg\");return await ze.updateConfiguration(t.projectCwd,{plugins:n=>{if(!Array.isArray(n))return n;let c=n.filter(f=>{if(!f.path)return!0;let p=K.resolve(t.projectCwd,f.path),h=hv.has(f.spec)&&K.contains(s,p);return h&&r.push(p),!h});return c.length===0?ze.deleteProperty:c.length===n.length?n:c}},{immutable:e})?(await Promise.all(r.map(async n=>{await le.removePromise(n)})),!0):!1}Ve();bt();Wt();var BC=class extends ut{constructor(){super(...arguments);this.all=ge.Boolean(\"-A,--all\",!1,{description:\"Link all workspaces belonging to the target projects to the current one\"});this.private=ge.Boolean(\"-p,--private\",!1,{description:\"Also link private workspaces belonging to the target projects to the current one\"});this.relative=ge.Boolean(\"-r,--relative\",!1,{description:\"Link workspaces using relative paths instead of absolute paths\"});this.destinations=ge.Rest()}static{this.paths=[[\"link\"]]}static{this.usage=ot.Usage({description:\"connect the local project to another one\",details:\"\\n      This command will set a new `resolutions` field in the project-level manifest and point it to the workspace at the specified location (even if part of another project).\\n    \",examples:[[\"Register one or more remote workspaces for use in the current project\",\"$0 link ~/ts-loader ~/jest\"],[\"Register all workspaces from a remote project for use in the current project\",\"$0 link ~/jest --all\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Jr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState({restoreResolutions:!1});let c=s.topLevelWorkspace,f=[];for(let p of this.destinations){let h=K.resolve(this.context.cwd,ue.toPortablePath(p)),E=await ze.find(h,this.context.plugins,{useRc:!1,strict:!1}),{project:C,workspace:S}=await Tt.find(E,h);if(s.cwd===C.cwd)throw new nt(`Invalid destination '${p}'; Can't link the project to itself`);if(!S)throw new ar(C.cwd,h);if(this.all){let P=!1;for(let I of C.workspaces)I.manifest.name&&(!I.manifest.private||this.private)&&(f.push(I),P=!0);if(!P)throw new nt(`No workspace found to be linked in the target project: ${p}`)}else{if(!S.manifest.name)throw new nt(`The target workspace at '${p}' doesn't have a name and thus cannot be linked`);if(S.manifest.private&&!this.private)throw new nt(`The target workspace at '${p}' is marked private - use the --private flag to link it anyway`);f.push(S)}}for(let p of f){let h=q.stringifyIdent(p.anchoredLocator),E=this.relative?K.relative(s.cwd,p.cwd):p.cwd;c.manifest.resolutions.push({pattern:{descriptor:{fullName:h}},reference:`portal:${E}`})}return await s.installWithNewReport({stdout:this.context.stdout},{cache:n})}};Wt();var vC=class extends ut{constructor(){super(...arguments);this.args=ge.Proxy()}static{this.paths=[[\"node\"]]}static{this.usage=ot.Usage({description:\"run node with the hook already setup\",details:`\n      This command simply runs Node. It also makes sure to call it in a way that's compatible with the current project (for example, on PnP projects the environment will be setup in such a way that PnP will be correctly injected into the environment).\n\n      The Node process will use the exact same version of Node as the one used to run Yarn itself, which might be a good way to ensure that your commands always use a consistent Node version.\n    `,examples:[[\"Run a Node script\",\"$0 node ./my-script.js\"]]})}async execute(){return this.cli.run([\"exec\",\"node\",...this.args])}};Ve();Wt();var SC=class extends ut{constructor(){super(...arguments);this.json=ge.Boolean(\"--json\",!1,{description:\"Format the output as an NDJSON stream\"})}static{this.paths=[[\"plugin\",\"check\"]]}static{this.usage=ot.Usage({category:\"Plugin-related commands\",description:\"find all third-party plugins that differ from their own spec\",details:`\n      Check only the plugins from https.\n\n      If this command detects any plugin differences in the CI environment, it will throw an error.\n    `,examples:[[\"find all third-party plugins that differ from their own spec\",\"$0 plugin check\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),s=await ze.findRcFiles(this.context.cwd);return(await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout},async n=>{for(let c of s)if(c.data?.plugins)for(let f of c.data.plugins){if(!f.checksum||!f.spec.match(/^https?:/))continue;let p=await An.get(f.spec,{configuration:r}),h=Nn.makeHash(p);if(f.checksum===h)continue;let E=he.pretty(r,f.path,he.Type.PATH),C=he.pretty(r,f.spec,he.Type.URL),S=`${E} is different from the file provided by ${C}`;n.reportJson({...f,newChecksum:h}),n.reportError(0,S)}})).exitCode()}};Ve();Ve();bt();Wt();var rBe=Ie(\"os\");Ve();bt();Wt();var X2e=Ie(\"os\");Ve();Bc();Wt();var Fvt=\"https://raw.githubusercontent.com/yarnpkg/berry/master/plugins.yml\";async function vm(t,e){let r=await An.get(Fvt,{configuration:t}),s=cs(r.toString());return Object.fromEntries(Object.entries(s).filter(([a,n])=>!e||Or.satisfiesWithPrereleases(e,n.range??\"<4.0.0-rc.1\")))}var DC=class extends ut{constructor(){super(...arguments);this.json=ge.Boolean(\"--json\",!1,{description:\"Format the output as an NDJSON stream\"})}static{this.paths=[[\"plugin\",\"list\"]]}static{this.usage=ot.Usage({category:\"Plugin-related commands\",description:\"list the available official plugins\",details:\"\\n      This command prints the plugins available directly from the Yarn repository. Only those plugins can be referenced by name in `yarn plugin import`.\\n    \",examples:[[\"List the official plugins\",\"$0 plugin list\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins);return(await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout},async a=>{let n=await vm(r,un);for(let[c,{experimental:f,...p}]of Object.entries(n)){let h=c;f&&(h+=\" [experimental]\"),a.reportJson({name:c,experimental:f,...p}),a.reportInfo(null,h)}})).exitCode()}};var Nvt=/^[0-9]+$/,Ovt=process.platform===\"win32\";function $2e(t){return Nvt.test(t)?`pull/${t}/head`:t}var Lvt=({repository:t,branch:e},r)=>[[\"git\",\"init\",ue.fromPortablePath(r)],[\"git\",\"remote\",\"add\",\"origin\",t],[\"git\",\"fetch\",\"origin\",\"--depth=1\",$2e(e)],[\"git\",\"reset\",\"--hard\",\"FETCH_HEAD\"]],Mvt=({branch:t})=>[[\"git\",\"fetch\",\"origin\",\"--depth=1\",$2e(t),\"--force\"],[\"git\",\"reset\",\"--hard\",\"FETCH_HEAD\"],[\"git\",\"clean\",\"-dfx\",\"-e\",\"packages/yarnpkg-cli/bundles\"]],_vt=({plugins:t,noMinify:e},r,s)=>[[\"yarn\",\"build:cli\",...new Array().concat(...t.map(a=>[\"--plugin\",K.resolve(s,a)])),...e?[\"--no-minify\"]:[],\"|\"],[Ovt?\"move\":\"mv\",\"packages/yarnpkg-cli/bundles/yarn.js\",ue.fromPortablePath(r),\"|\"]],bC=class extends ut{constructor(){super(...arguments);this.installPath=ge.String(\"--path\",{description:\"The path where the repository should be cloned to\"});this.repository=ge.String(\"--repository\",\"https://github.com/yarnpkg/berry.git\",{description:\"The repository that should be cloned\"});this.branch=ge.String(\"--branch\",\"master\",{description:\"The branch of the repository that should be cloned\"});this.plugins=ge.Array(\"--plugin\",[],{description:\"An array of additional plugins that should be included in the bundle\"});this.dryRun=ge.Boolean(\"-n,--dry-run\",!1,{description:\"If set, the bundle will be built but not added to the project\"});this.noMinify=ge.Boolean(\"--no-minify\",!1,{description:\"Build a bundle for development (debugging) - non-minified and non-mangled\"});this.force=ge.Boolean(\"-f,--force\",!1,{description:\"Always clone the repository instead of trying to fetch the latest commits\"});this.skipPlugins=ge.Boolean(\"--skip-plugins\",!1,{description:\"Skip updating the contrib plugins\"})}static{this.paths=[[\"set\",\"version\",\"from\",\"sources\"]]}static{this.usage=ot.Usage({description:\"build Yarn from master\",details:`\n      This command will clone the Yarn repository into a temporary folder, then build it. The resulting bundle will then be copied into the local project.\n\n      By default, it also updates all contrib plugins to the same commit the bundle is built from. This behavior can be disabled by using the \\`--skip-plugins\\` flag.\n    `,examples:[[\"Build Yarn from master\",\"$0 set version from sources\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s}=await Tt.find(r,this.context.cwd),a=typeof this.installPath<\"u\"?K.resolve(this.context.cwd,ue.toPortablePath(this.installPath)):K.resolve(ue.toPortablePath((0,X2e.tmpdir)()),\"yarnpkg-sources\",Nn.makeHash(this.repository).slice(0,6));return(await Ot.start({configuration:r,stdout:this.context.stdout},async c=>{await Q5(this,{configuration:r,report:c,target:a}),c.reportSeparator(),c.reportInfo(0,\"Building a fresh bundle\"),c.reportSeparator();let f=await Gr.execvp(\"git\",[\"rev-parse\",\"--short\",\"HEAD\"],{cwd:a,strict:!0}),p=K.join(a,`packages/yarnpkg-cli/bundles/yarn-${f.stdout.trim()}.js`);le.existsSync(p)||(await uS(_vt(this,p,a),{configuration:r,context:this.context,target:a}),c.reportSeparator());let h=await le.readFilePromise(p);if(!this.dryRun){let{bundleVersion:E}=await x5(r,null,async()=>h,{report:c});this.skipPlugins||await Uvt(this,E,{project:s,report:c,target:a})}})).exitCode()}};async function uS(t,{configuration:e,context:r,target:s}){for(let[a,...n]of t){let c=n[n.length-1]===\"|\";if(c&&n.pop(),c)await Gr.pipevp(a,n,{cwd:s,stdin:r.stdin,stdout:r.stdout,stderr:r.stderr,strict:!0});else{r.stdout.write(`${he.pretty(e,`  $ ${[a,...n].join(\" \")}`,\"grey\")}\n`);try{await Gr.execvp(a,n,{cwd:s,strict:!0})}catch(f){throw r.stdout.write(f.stdout||f.stack),f}}}}async function Q5(t,{configuration:e,report:r,target:s}){let a=!1;if(!t.force&&le.existsSync(K.join(s,\".git\"))){r.reportInfo(0,\"Fetching the latest commits\"),r.reportSeparator();try{await uS(Mvt(t),{configuration:e,context:t.context,target:s}),a=!0}catch{r.reportSeparator(),r.reportWarning(0,\"Repository update failed; we'll try to regenerate it\")}}a||(r.reportInfo(0,\"Cloning the remote repository\"),r.reportSeparator(),await le.removePromise(s),await le.mkdirPromise(s,{recursive:!0}),await uS(Lvt(t,s),{configuration:e,context:t.context,target:s}))}async function Uvt(t,e,{project:r,report:s,target:a}){let n=await vm(r.configuration,e),c=new Set(Object.keys(n));for(let f of r.configuration.plugins.keys())c.has(f)&&await T5(f,t,{project:r,report:s,target:a})}Ve();Ve();bt();Wt();var eBe=et(Ai()),tBe=Ie(\"vm\");var PC=class extends ut{constructor(){super(...arguments);this.name=ge.String();this.checksum=ge.Boolean(\"--checksum\",!0,{description:\"Whether to care if this plugin is modified\"})}static{this.paths=[[\"plugin\",\"import\"]]}static{this.usage=ot.Usage({category:\"Plugin-related commands\",description:\"download a plugin\",details:`\n      This command downloads the specified plugin from its remote location and updates the configuration to reference it in further CLI invocations.\n\n      Three types of plugin references are accepted:\n\n      - If the plugin is stored within the Yarn repository, it can be referenced by name.\n      - Third-party plugins can be referenced directly through their public urls.\n      - Local plugins can be referenced by their path on the disk.\n\n      If the \\`--no-checksum\\` option is set, Yarn will no longer care if the plugin is modified.\n\n      Plugins cannot be downloaded from the npm registry, and aren't allowed to have dependencies (they need to be bundled into a single file, possibly thanks to the \\`@yarnpkg/builder\\` package).\n    `,examples:[['Download and activate the \"@yarnpkg/plugin-exec\" plugin',\"$0 plugin import @yarnpkg/plugin-exec\"],['Download and activate the \"@yarnpkg/plugin-exec\" plugin (shorthand)',\"$0 plugin import exec\"],[\"Download and activate a community plugin\",\"$0 plugin import https://example.org/path/to/plugin.js\"],[\"Activate a local plugin\",\"$0 plugin import ./path/to/plugin.js\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins);return(await Ot.start({configuration:r,stdout:this.context.stdout},async a=>{let{project:n}=await Tt.find(r,this.context.cwd),c,f;if(this.name.match(/^\\.{0,2}[\\\\/]/)||ue.isAbsolute(this.name)){let p=K.resolve(this.context.cwd,ue.toPortablePath(this.name));a.reportInfo(0,`Reading ${he.pretty(r,p,he.Type.PATH)}`),c=K.relative(n.cwd,p),f=await le.readFilePromise(p)}else{let p;if(this.name.match(/^https?:/)){try{new URL(this.name)}catch{throw new Yt(52,`Plugin specifier \"${this.name}\" is neither a plugin name nor a valid url`)}c=this.name,p=this.name}else{let h=q.parseLocator(this.name.replace(/^((@yarnpkg\\/)?plugin-)?/,\"@yarnpkg/plugin-\"));if(h.reference!==\"unknown\"&&!eBe.default.valid(h.reference))throw new Yt(0,\"Official plugins only accept strict version references. Use an explicit URL if you wish to download them from another location.\");let E=q.stringifyIdent(h),C=await vm(r,un);if(!Object.hasOwn(C,E)){let S=`Couldn't find a plugin named ${q.prettyIdent(r,h)} on the remote registry.\n`;throw r.plugins.has(E)?S+=`A plugin named ${q.prettyIdent(r,h)} is already installed; possibly attempting to import a built-in plugin.`:S+=`Note that only the plugins referenced on our website (${he.pretty(r,\"https://github.com/yarnpkg/berry/blob/master/plugins.yml\",he.Type.URL)}) can be referenced by their name; any other plugin will have to be referenced through its public url (for example ${he.pretty(r,\"https://github.com/yarnpkg/berry/raw/master/packages/plugin-typescript/bin/%40yarnpkg/plugin-typescript.js\",he.Type.URL)}).`,new Yt(51,S)}c=E,p=C[E].url,h.reference!==\"unknown\"?p=p.replace(/\\/master\\//,`/${E}/${h.reference}/`):un!==null&&(p=p.replace(/\\/master\\//,`/@yarnpkg/cli/${un}/`))}a.reportInfo(0,`Downloading ${he.pretty(r,p,\"green\")}`),f=await An.get(p,{configuration:r})}await R5(c,f,{checksum:this.checksum,project:n,report:a})})).exitCode()}};async function R5(t,e,{checksum:r=!0,project:s,report:a}){let{configuration:n}=s,c={},f={exports:c};(0,tBe.runInNewContext)(e.toString(),{module:f,exports:c});let h=`.yarn/plugins/${f.exports.name}.cjs`,E=K.resolve(s.cwd,h);a.reportInfo(0,`Saving the new plugin in ${he.pretty(n,h,\"magenta\")}`),await le.mkdirPromise(K.dirname(E),{recursive:!0}),await le.writeFilePromise(E,e);let C={path:h,spec:t};r&&(C.checksum=Nn.makeHash(e)),await ze.addPlugin(s.cwd,[C])}var Hvt=({pluginName:t,noMinify:e},r)=>[[\"yarn\",`build:${t}`,...e?[\"--no-minify\"]:[],\"|\"]],xC=class extends ut{constructor(){super(...arguments);this.installPath=ge.String(\"--path\",{description:\"The path where the repository should be cloned to\"});this.repository=ge.String(\"--repository\",\"https://github.com/yarnpkg/berry.git\",{description:\"The repository that should be cloned\"});this.branch=ge.String(\"--branch\",\"master\",{description:\"The branch of the repository that should be cloned\"});this.noMinify=ge.Boolean(\"--no-minify\",!1,{description:\"Build a plugin for development (debugging) - non-minified and non-mangled\"});this.force=ge.Boolean(\"-f,--force\",!1,{description:\"Always clone the repository instead of trying to fetch the latest commits\"});this.name=ge.String()}static{this.paths=[[\"plugin\",\"import\",\"from\",\"sources\"]]}static{this.usage=ot.Usage({category:\"Plugin-related commands\",description:\"build a plugin from sources\",details:`\n      This command clones the Yarn repository into a temporary folder, builds the specified contrib plugin and updates the configuration to reference it in further CLI invocations.\n\n      The plugins can be referenced by their short name if sourced from the official Yarn repository.\n    `,examples:[['Build and activate the \"@yarnpkg/plugin-exec\" plugin',\"$0 plugin import from sources @yarnpkg/plugin-exec\"],['Build and activate the \"@yarnpkg/plugin-exec\" plugin (shorthand)',\"$0 plugin import from sources exec\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),s=typeof this.installPath<\"u\"?K.resolve(this.context.cwd,ue.toPortablePath(this.installPath)):K.resolve(ue.toPortablePath((0,rBe.tmpdir)()),\"yarnpkg-sources\",Nn.makeHash(this.repository).slice(0,6));return(await Ot.start({configuration:r,stdout:this.context.stdout},async n=>{let{project:c}=await Tt.find(r,this.context.cwd),f=q.parseIdent(this.name.replace(/^((@yarnpkg\\/)?plugin-)?/,\"@yarnpkg/plugin-\")),p=q.stringifyIdent(f),h=await vm(r,un);if(!Object.hasOwn(h,p))throw new Yt(51,`Couldn't find a plugin named \"${p}\" on the remote registry. Note that only the plugins referenced on our website (https://github.com/yarnpkg/berry/blob/master/plugins.yml) can be built and imported from sources.`);let E=p;await Q5(this,{configuration:r,report:n,target:s}),await T5(E,this,{project:c,report:n,target:s})})).exitCode()}};async function T5(t,{context:e,noMinify:r},{project:s,report:a,target:n}){let c=t.replace(/@yarnpkg\\//,\"\"),{configuration:f}=s;a.reportSeparator(),a.reportInfo(0,`Building a fresh ${c}`),a.reportSeparator(),await uS(Hvt({pluginName:c,noMinify:r},n),{configuration:f,context:e,target:n}),a.reportSeparator();let p=K.resolve(n,`packages/${c}/bundles/${t}.js`),h=await le.readFilePromise(p);await R5(t,h,{project:s,report:a})}Ve();bt();Wt();var kC=class extends ut{constructor(){super(...arguments);this.name=ge.String()}static{this.paths=[[\"plugin\",\"remove\"]]}static{this.usage=ot.Usage({category:\"Plugin-related commands\",description:\"remove a plugin\",details:`\n      This command deletes the specified plugin from the .yarn/plugins folder and removes it from the configuration.\n\n      **Note:** The plugins have to be referenced by their name property, which can be obtained using the \\`yarn plugin runtime\\` command. Shorthands are not allowed.\n   `,examples:[[\"Remove a plugin imported from the Yarn repository\",\"$0 plugin remove @yarnpkg/plugin-typescript\"],[\"Remove a plugin imported from a local file\",\"$0 plugin remove my-local-plugin\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s}=await Tt.find(r,this.context.cwd);return(await Ot.start({configuration:r,stdout:this.context.stdout},async n=>{let c=this.name,f=q.parseIdent(c);if(!r.plugins.has(c))throw new nt(`${q.prettyIdent(r,f)} isn't referenced by the current configuration`);let p=`.yarn/plugins/${c}.cjs`,h=K.resolve(s.cwd,p);le.existsSync(h)&&(n.reportInfo(0,`Removing ${he.pretty(r,p,he.Type.PATH)}...`),await le.removePromise(h)),n.reportInfo(0,\"Updating the configuration...\"),await ze.updateConfiguration(s.cwd,{plugins:E=>{if(!Array.isArray(E))return E;let C=E.filter(S=>S.path!==p);return C.length===0?ze.deleteProperty:C.length===E.length?E:C}})})).exitCode()}};Ve();Wt();var QC=class extends ut{constructor(){super(...arguments);this.json=ge.Boolean(\"--json\",!1,{description:\"Format the output as an NDJSON stream\"})}static{this.paths=[[\"plugin\",\"runtime\"]]}static{this.usage=ot.Usage({category:\"Plugin-related commands\",description:\"list the active plugins\",details:`\n      This command prints the currently active plugins. Will be displayed both builtin plugins and external plugins.\n    `,examples:[[\"List the currently active plugins\",\"$0 plugin runtime\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins);return(await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout},async a=>{for(let n of r.plugins.keys()){let c=this.context.plugins.plugins.has(n),f=n;c&&(f+=\" [builtin]\"),a.reportJson({name:n,builtin:c}),a.reportInfo(null,`${f}`)}})).exitCode()}};Ve();Ve();Wt();var TC=class extends ut{constructor(){super(...arguments);this.idents=ge.Rest()}static{this.paths=[[\"rebuild\"]]}static{this.usage=ot.Usage({description:\"rebuild the project's native packages\",details:`\n      This command will automatically cause Yarn to forget about previous compilations of the given packages and to run them again.\n\n      Note that while Yarn forgets the compilation, the previous artifacts aren't erased from the filesystem and may affect the next builds (in good or bad). To avoid this, you may remove the .yarn/unplugged folder, or any other relevant location where packages might have been stored (Yarn may offer a way to do that automatically in the future).\n\n      By default all packages will be rebuilt, but you can filter the list by specifying the names of the packages you want to clear from memory.\n    `,examples:[[\"Rebuild all packages\",\"$0 rebuild\"],[\"Rebuild fsevents only\",\"$0 rebuild fsevents\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Jr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);let c=new Set;for(let f of this.idents)c.add(q.parseIdent(f).identHash);if(await s.restoreInstallState({restoreResolutions:!1}),await s.resolveEverything({cache:n,report:new Yi}),c.size>0)for(let f of s.storedPackages.values())c.has(f.identHash)&&(s.storedBuildState.delete(f.locatorHash),s.skippedBuilds.delete(f.locatorHash));else s.storedBuildState.clear(),s.skippedBuilds.clear();return await s.installWithNewReport({stdout:this.context.stdout,quiet:this.context.quiet},{cache:n})}};Ve();Ve();Ve();Wt();var F5=et(Sa());Ul();var RC=class extends ut{constructor(){super(...arguments);this.all=ge.Boolean(\"-A,--all\",!1,{description:\"Apply the operation to all workspaces from the current project\"});this.mode=ge.String(\"--mode\",{description:\"Change what artifacts installs generate\",validator:po(ec)});this.patterns=ge.Rest()}static{this.paths=[[\"remove\"]]}static{this.usage=ot.Usage({description:\"remove dependencies from the project\",details:`\n      This command will remove the packages matching the specified patterns from the current workspace.\n\n      If the \\`--mode=<mode>\\` option is set, Yarn will change which artifacts are generated. The modes currently supported are:\n\n      - \\`skip-build\\` will not run the build scripts at all. Note that this is different from setting \\`enableScripts\\` to false because the latter will disable build scripts, and thus affect the content of the artifacts generated on disk, whereas the former will just disable the build step - but not the scripts themselves, which just won't run.\n\n      - \\`update-lockfile\\` will skip the link step altogether, and only fetch packages that are missing from the lockfile (or that have no associated checksums). This mode is typically used by tools like Renovate or Dependabot to keep a lockfile up-to-date without incurring the full install cost.\n\n      This command accepts glob patterns as arguments (if valid Idents and supported by [micromatch](https://github.com/micromatch/micromatch)). Make sure to escape the patterns, to prevent your own shell from trying to expand them.\n    `,examples:[[\"Remove a dependency from the current project\",\"$0 remove lodash\"],[\"Remove a dependency from all workspaces at once\",\"$0 remove lodash --all\"],[\"Remove all dependencies starting with `eslint-`\",\"$0 remove 'eslint-*'\"],[\"Remove all dependencies with the `@babel` scope\",\"$0 remove '@babel/*'\"],[\"Remove all dependencies matching `react-dom` or `react-helmet`\",\"$0 remove 'react-{dom,helmet}'\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Jr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState({restoreResolutions:!1});let c=this.all?s.workspaces:[a],f=[\"dependencies\",\"devDependencies\",\"peerDependencies\"],p=[],h=!1,E=[];for(let I of this.patterns){let R=!1,N=q.parseIdent(I);for(let U of c){let W=[...U.manifest.peerDependenciesMeta.keys()];for(let te of(0,F5.default)(W,I))U.manifest.peerDependenciesMeta.delete(te),h=!0,R=!0;for(let te of f){let ie=U.manifest.getForScope(te),Ae=[...ie.values()].map(ce=>q.stringifyIdent(ce));for(let ce of(0,F5.default)(Ae,q.stringifyIdent(N))){let{identHash:me}=q.parseIdent(ce),pe=ie.get(me);if(typeof pe>\"u\")throw new Error(\"Assertion failed: Expected the descriptor to be registered\");U.manifest[te].delete(me),E.push([U,te,pe]),h=!0,R=!0}}}R||p.push(I)}let C=p.length>1?\"Patterns\":\"Pattern\",S=p.length>1?\"don't\":\"doesn't\",P=this.all?\"any\":\"this\";if(p.length>0)throw new nt(`${C} ${he.prettyList(r,p,he.Type.CODE)} ${S} match any packages referenced by ${P} workspace`);return h?(await r.triggerMultipleHooks(I=>I.afterWorkspaceDependencyRemoval,E),await s.installWithNewReport({stdout:this.context.stdout},{cache:n,mode:this.mode})):0}};Ve();Ve();Wt();var nBe=Ie(\"util\"),FC=class extends ut{constructor(){super(...arguments);this.json=ge.Boolean(\"--json\",!1,{description:\"Format the output as an NDJSON stream\"})}static{this.paths=[[\"run\"]]}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);return(await Ot.start({configuration:r,stdout:this.context.stdout,json:this.json},async c=>{let f=a.manifest.scripts,p=je.sortMap(f.keys(),C=>C),h={breakLength:1/0,colors:r.get(\"enableColors\"),maxArrayLength:2},E=p.reduce((C,S)=>Math.max(C,S.length),0);for(let[C,S]of f.entries())c.reportInfo(null,`${C.padEnd(E,\" \")}   ${(0,nBe.inspect)(S,h)}`),c.reportJson({name:C,script:S})})).exitCode()}};Ve();Ve();Wt();var NC=class extends ut{constructor(){super(...arguments);this.inspect=ge.String(\"--inspect\",!1,{tolerateBoolean:!0,description:\"Forwarded to the underlying Node process when executing a binary\"});this.inspectBrk=ge.String(\"--inspect-brk\",!1,{tolerateBoolean:!0,description:\"Forwarded to the underlying Node process when executing a binary\"});this.topLevel=ge.Boolean(\"-T,--top-level\",!1,{description:\"Check the root workspace for scripts and/or binaries instead of the current one\"});this.binariesOnly=ge.Boolean(\"-B,--binaries-only\",!1,{description:\"Ignore any user defined scripts and only check for binaries\"});this.require=ge.String(\"--require\",{description:\"Forwarded to the underlying Node process when executing a binary\"});this.silent=ge.Boolean(\"--silent\",{hidden:!0});this.scriptName=ge.String();this.args=ge.Proxy()}static{this.paths=[[\"run\"]]}static{this.usage=ot.Usage({description:\"run a script defined in the package.json\",details:`\n      This command will run a tool. The exact tool that will be executed will depend on the current state of your workspace:\n\n      - If the \\`scripts\\` field from your local package.json contains a matching script name, its definition will get executed.\n\n      - Otherwise, if one of the local workspace's dependencies exposes a binary with a matching name, this binary will get executed.\n\n      - Otherwise, if the specified name contains a colon character and if one of the workspaces in the project contains exactly one script with a matching name, then this script will get executed.\n\n      Whatever happens, the cwd of the spawned process will be the workspace that declares the script (which makes it possible to call commands cross-workspaces using the third syntax).\n    `,examples:[[\"Run the tests from the local workspace\",\"$0 run test\"],['Same thing, but without the \"run\" keyword',\"$0 test\"],[\"Inspect Webpack while running\",\"$0 run --inspect-brk webpack\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a,locator:n}=await Tt.find(r,this.context.cwd);await s.restoreInstallState();let c=this.topLevel?s.topLevelWorkspace.anchoredLocator:n;if(!this.binariesOnly&&await In.hasPackageScript(c,this.scriptName,{project:s}))return await In.executePackageScript(c,this.scriptName,this.args,{project:s,stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr});let f=await In.getPackageAccessibleBinaries(c,{project:s});if(f.get(this.scriptName)){let h=[];return this.inspect&&(typeof this.inspect==\"string\"?h.push(`--inspect=${this.inspect}`):h.push(\"--inspect\")),this.inspectBrk&&(typeof this.inspectBrk==\"string\"?h.push(`--inspect-brk=${this.inspectBrk}`):h.push(\"--inspect-brk\")),this.require&&h.push(`--require=${this.require}`),await In.executePackageAccessibleBinary(c,this.scriptName,this.args,{cwd:this.context.cwd,project:s,stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr,nodeArgs:h,packageAccessibleBinaries:f})}if(!this.topLevel&&!this.binariesOnly&&a&&this.scriptName.includes(\":\")){let E=(await Promise.all(s.workspaces.map(async C=>C.manifest.scripts.has(this.scriptName)?C:null))).filter(C=>C!==null);if(E.length===1)return await In.executeWorkspaceScript(E[0],this.scriptName,this.args,{stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr})}if(this.topLevel)throw this.scriptName===\"node-gyp\"?new nt(`Couldn't find a script name \"${this.scriptName}\" in the top-level (used by ${q.prettyLocator(r,n)}). This typically happens because some package depends on \"node-gyp\" to build itself, but didn't list it in their dependencies. To fix that, please run \"yarn add node-gyp\" into your top-level workspace. You also can open an issue on the repository of the specified package to suggest them to use an optional peer dependency.`):new nt(`Couldn't find a script name \"${this.scriptName}\" in the top-level (used by ${q.prettyLocator(r,n)}).`);{if(this.scriptName===\"global\")throw new nt(\"The 'yarn global' commands have been removed in 2.x - consider using 'yarn dlx' or a third-party plugin instead\");let h=[this.scriptName].concat(this.args);for(let[E,C]of $I)for(let S of C)if(h.length>=S.length&&JSON.stringify(h.slice(0,S.length))===JSON.stringify(S))throw new nt(`Couldn't find a script named \"${this.scriptName}\", but a matching command can be found in the ${E} plugin. You can install it with \"yarn plugin import ${E}\".`);throw new nt(`Couldn't find a script named \"${this.scriptName}\".`)}}};Ve();Ve();Wt();var OC=class extends ut{constructor(){super(...arguments);this.descriptor=ge.String();this.resolution=ge.String()}static{this.paths=[[\"set\",\"resolution\"]]}static{this.usage=ot.Usage({description:\"enforce a package resolution\",details:'\\n      This command updates the resolution table so that `descriptor` is resolved by `resolution`.\\n\\n      Note that by default this command only affect the current resolution table - meaning that this \"manual override\" will disappear if you remove the lockfile, or if the package disappear from the table. If you wish to make the enforced resolution persist whatever happens, edit the `resolutions` field in your top-level manifest.\\n\\n      Note that no attempt is made at validating that `resolution` is a valid resolution entry for `descriptor`.\\n    ',examples:[[\"Force all instances of lodash@npm:^1.2.3 to resolve to 1.5.0\",\"$0 set resolution lodash@npm:^1.2.3 npm:1.5.0\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Jr.find(r);if(await s.restoreInstallState({restoreResolutions:!1}),!a)throw new ar(s.cwd,this.context.cwd);let c=q.parseDescriptor(this.descriptor,!0),f=q.makeDescriptor(c,this.resolution);return s.storedDescriptors.set(c.descriptorHash,c),s.storedDescriptors.set(f.descriptorHash,f),s.resolutionAliases.set(c.descriptorHash,f.descriptorHash),await s.installWithNewReport({stdout:this.context.stdout},{cache:n})}};Ve();bt();Wt();var iBe=et(Sa()),LC=class extends ut{constructor(){super(...arguments);this.all=ge.Boolean(\"-A,--all\",!1,{description:\"Unlink all workspaces belonging to the target project from the current one\"});this.leadingArguments=ge.Rest()}static{this.paths=[[\"unlink\"]]}static{this.usage=ot.Usage({description:\"disconnect the local project from another one\",details:`\n      This command will remove any resolutions in the project-level manifest that would have been added via a yarn link with similar arguments.\n    `,examples:[[\"Unregister a remote workspace in the current project\",\"$0 unlink ~/ts-loader\"],[\"Unregister all workspaces from a remote project in the current project\",\"$0 unlink ~/jest --all\"],[\"Unregister all previously linked workspaces\",\"$0 unlink --all\"],[\"Unregister all workspaces matching a glob\",\"$0 unlink '@babel/*' 'pkg-{a,b}'\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Jr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);let c=s.topLevelWorkspace,f=new Set;if(this.leadingArguments.length===0&&this.all)for(let{pattern:p,reference:h}of c.manifest.resolutions)h.startsWith(\"portal:\")&&f.add(p.descriptor.fullName);if(this.leadingArguments.length>0)for(let p of this.leadingArguments){let h=K.resolve(this.context.cwd,ue.toPortablePath(p));if(je.isPathLike(p)){let E=await ze.find(h,this.context.plugins,{useRc:!1,strict:!1}),{project:C,workspace:S}=await Tt.find(E,h);if(!S)throw new ar(C.cwd,h);if(this.all){for(let P of C.workspaces)P.manifest.name&&f.add(q.stringifyIdent(P.anchoredLocator));if(f.size===0)throw new nt(\"No workspace found to be unlinked in the target project\")}else{if(!S.manifest.name)throw new nt(\"The target workspace doesn't have a name and thus cannot be unlinked\");f.add(q.stringifyIdent(S.anchoredLocator))}}else{let E=[...c.manifest.resolutions.map(({pattern:C})=>C.descriptor.fullName)];for(let C of(0,iBe.default)(E,p))f.add(C)}}return c.manifest.resolutions=c.manifest.resolutions.filter(({pattern:p})=>!f.has(p.descriptor.fullName)),await s.installWithNewReport({stdout:this.context.stdout,quiet:this.context.quiet},{cache:n})}};Ve();Ve();Ve();Wt();var sBe=et(nS()),N5=et(Sa());Ul();var MC=class extends ut{constructor(){super(...arguments);this.interactive=ge.Boolean(\"-i,--interactive\",{description:\"Offer various choices, depending on the detected upgrade paths\"});this.fixed=ge.Boolean(\"-F,--fixed\",!1,{description:\"Store dependency tags as-is instead of resolving them\"});this.exact=ge.Boolean(\"-E,--exact\",!1,{description:\"Don't use any semver modifier on the resolved range\"});this.tilde=ge.Boolean(\"-T,--tilde\",!1,{description:\"Use the `~` semver modifier on the resolved range\"});this.caret=ge.Boolean(\"-C,--caret\",!1,{description:\"Use the `^` semver modifier on the resolved range\"});this.recursive=ge.Boolean(\"-R,--recursive\",!1,{description:\"Resolve again ALL resolutions for those packages\"});this.mode=ge.String(\"--mode\",{description:\"Change what artifacts installs generate\",validator:po(ec)});this.patterns=ge.Rest()}static{this.paths=[[\"up\"]]}static{this.usage=ot.Usage({description:\"upgrade dependencies across the project\",details:\"\\n      This command upgrades the packages matching the list of specified patterns to their latest available version across the whole project (regardless of whether they're part of `dependencies` or `devDependencies` - `peerDependencies` won't be affected). This is a project-wide command: all workspaces will be upgraded in the process.\\n\\n      If `-R,--recursive` is set the command will change behavior and no other switch will be allowed. When operating under this mode `yarn up` will force all ranges matching the selected packages to be resolved again (often to the highest available versions) before being stored in the lockfile. It however won't touch your manifests anymore, so depending on your needs you might want to run both `yarn up` and `yarn up -R` to cover all bases.\\n\\n      If `-i,--interactive` is set (or if the `preferInteractive` settings is toggled on) the command will offer various choices, depending on the detected upgrade paths. Some upgrades require this flag in order to resolve ambiguities.\\n\\n      The, `-C,--caret`, `-E,--exact` and  `-T,--tilde` options have the same meaning as in the `add` command (they change the modifier used when the range is missing or a tag, and are ignored when the range is explicitly set).\\n\\n      If the `--mode=<mode>` option is set, Yarn will change which artifacts are generated. The modes currently supported are:\\n\\n      - `skip-build` will not run the build scripts at all. Note that this is different from setting `enableScripts` to false because the latter will disable build scripts, and thus affect the content of the artifacts generated on disk, whereas the former will just disable the build step - but not the scripts themselves, which just won't run.\\n\\n      - `update-lockfile` will skip the link step altogether, and only fetch packages that are missing from the lockfile (or that have no associated checksums). This mode is typically used by tools like Renovate or Dependabot to keep a lockfile up-to-date without incurring the full install cost.\\n\\n      Generally you can see `yarn up` as a counterpart to what was `yarn upgrade --latest` in Yarn 1 (ie it ignores the ranges previously listed in your manifests), but unlike `yarn upgrade` which only upgraded dependencies in the current workspace, `yarn up` will upgrade all workspaces at the same time.\\n\\n      This command accepts glob patterns as arguments (if valid Descriptors and supported by [micromatch](https://github.com/micromatch/micromatch)). Make sure to escape the patterns, to prevent your own shell from trying to expand them.\\n\\n      **Note:** The ranges have to be static, only the package scopes and names can contain glob patterns.\\n    \",examples:[[\"Upgrade all instances of lodash to the latest release\",\"$0 up lodash\"],[\"Upgrade all instances of lodash to the latest release, but ask confirmation for each\",\"$0 up lodash -i\"],[\"Upgrade all instances of lodash to 1.2.3\",\"$0 up lodash@1.2.3\"],[\"Upgrade all instances of packages with the `@babel` scope to the latest release\",\"$0 up '@babel/*'\"],[\"Upgrade all instances of packages containing the word `jest` to the latest release\",\"$0 up '*jest*'\"],[\"Upgrade all instances of packages with the `@babel` scope to 7.0.0\",\"$0 up '@babel/*@7.0.0'\"]]})}static{this.schema=[tB(\"recursive\",Wf.Forbids,[\"interactive\",\"exact\",\"tilde\",\"caret\"],{ignore:[void 0,!1]})]}async execute(){return this.recursive?await this.executeUpRecursive():await this.executeUpClassic()}async executeUpRecursive(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Jr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState({restoreResolutions:!1});let c=[...s.storedDescriptors.values()],f=c.map(E=>q.stringifyIdent(E)),p=new Set;for(let E of this.patterns){if(q.parseDescriptor(E).range!==\"unknown\")throw new nt(\"Ranges aren't allowed when using --recursive\");for(let C of(0,N5.default)(f,E)){let S=q.parseIdent(C);p.add(S.identHash)}}let h=c.filter(E=>p.has(E.identHash));for(let E of h)s.storedDescriptors.delete(E.descriptorHash),s.storedResolutions.delete(E.descriptorHash);return await s.installWithNewReport({stdout:this.context.stdout},{cache:n,mode:this.mode})}async executeUpClassic(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Jr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState({restoreResolutions:!1});let c=this.fixed,f=r.isInteractive({interactive:this.interactive,stdout:this.context.stdout}),p=sS(this,s),h=f?[\"keep\",\"reuse\",\"project\",\"latest\"]:[\"project\",\"latest\"],E=[],C=[];for(let N of this.patterns){let U=!1,W=q.parseDescriptor(N),te=q.stringifyIdent(W);for(let ie of s.workspaces)for(let Ae of[\"dependencies\",\"devDependencies\"]){let me=[...ie.manifest.getForScope(Ae).values()].map(Be=>q.stringifyIdent(Be)),pe=te===\"*\"?me:(0,N5.default)(me,te);for(let Be of pe){let Ce=q.parseIdent(Be),g=ie.manifest[Ae].get(Ce.identHash);if(typeof g>\"u\")throw new Error(\"Assertion failed: Expected the descriptor to be registered\");let we=q.makeDescriptor(Ce,W.range);E.push(Promise.resolve().then(async()=>[ie,Ae,g,await oS(we,{project:s,workspace:ie,cache:n,target:Ae,fixed:c,modifier:p,strategies:h})])),U=!0}}U||C.push(N)}if(C.length>1)throw new nt(`Patterns ${he.prettyList(r,C,he.Type.CODE)} don't match any packages referenced by any workspace`);if(C.length>0)throw new nt(`Pattern ${he.prettyList(r,C,he.Type.CODE)} doesn't match any packages referenced by any workspace`);let S=await Promise.all(E),P=await uA.start({configuration:r,stdout:this.context.stdout,suggestInstall:!1},async N=>{for(let[,,U,{suggestions:W,rejections:te}]of S){let ie=W.filter(Ae=>Ae.descriptor!==null);if(ie.length===0){let[Ae]=te;if(typeof Ae>\"u\")throw new Error(\"Assertion failed: Expected an error to have been set\");let ce=this.cli.error(Ae);s.configuration.get(\"enableNetwork\")?N.reportError(27,`${q.prettyDescriptor(r,U)} can't be resolved to a satisfying range\n\n${ce}`):N.reportError(27,`${q.prettyDescriptor(r,U)} can't be resolved to a satisfying range (note: network resolution has been disabled)\n\n${ce}`)}else ie.length>1&&!f&&N.reportError(27,`${q.prettyDescriptor(r,U)} has multiple possible upgrade strategies; use -i to disambiguate manually`)}});if(P.hasErrors())return P.exitCode();let I=!1,R=[];for(let[N,U,,{suggestions:W}]of S){let te,ie=W.filter(pe=>pe.descriptor!==null),Ae=ie[0].descriptor,ce=ie.every(pe=>q.areDescriptorsEqual(pe.descriptor,Ae));ie.length===1||ce?te=Ae:(I=!0,{answer:te}=await(0,sBe.prompt)({type:\"select\",name:\"answer\",message:`Which range do you want to use in ${q.prettyWorkspace(r,N)} \\u276F ${U}?`,choices:W.map(({descriptor:pe,name:Be,reason:Ce})=>pe?{name:Be,hint:Ce,descriptor:pe}:{name:Be,hint:Ce,disabled:!0}),onCancel:()=>process.exit(130),result(pe){return this.find(pe,\"descriptor\")},stdin:this.context.stdin,stdout:this.context.stdout}));let me=N.manifest[U].get(te.identHash);if(typeof me>\"u\")throw new Error(\"Assertion failed: This descriptor should have a matching entry\");if(me.descriptorHash!==te.descriptorHash)N.manifest[U].set(te.identHash,te),R.push([N,U,me,te]);else{let pe=r.makeResolver(),Be={project:s,resolver:pe},Ce=r.normalizeDependency(me),g=pe.bindDescriptor(Ce,N.anchoredLocator,Be);s.forgetResolution(g)}}return await r.triggerMultipleHooks(N=>N.afterWorkspaceDependencyReplacement,R),I&&this.context.stdout.write(`\n`),await s.installWithNewReport({stdout:this.context.stdout},{cache:n,mode:this.mode})}};Ve();Ve();Ve();Wt();var _C=class extends ut{constructor(){super(...arguments);this.recursive=ge.Boolean(\"-R,--recursive\",!1,{description:\"List, for each workspace, what are all the paths that lead to the dependency\"});this.json=ge.Boolean(\"--json\",!1,{description:\"Format the output as an NDJSON stream\"});this.peers=ge.Boolean(\"--peers\",!1,{description:\"Also print the peer dependencies that match the specified name\"});this.package=ge.String()}static{this.paths=[[\"why\"]]}static{this.usage=ot.Usage({description:\"display the reason why a package is needed\",details:`\n      This command prints the exact reasons why a package appears in the dependency tree.\n\n      If \\`-R,--recursive\\` is set, the listing will go in depth and will list, for each workspaces, what are all the paths that lead to the dependency. Note that the display is somewhat optimized in that it will not print the package listing twice for a single package, so if you see a leaf named \"Foo\" when looking for \"Bar\", it means that \"Foo\" already got printed higher in the tree.\n    `,examples:[[\"Explain why lodash is used in your project\",\"$0 why lodash\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState();let n=q.parseIdent(this.package).identHash,c=this.recursive?qvt(s,n,{configuration:r,peers:this.peers}):jvt(s,n,{configuration:r,peers:this.peers});Qs.emitTree(c,{configuration:r,stdout:this.context.stdout,json:this.json,separators:1})}};function jvt(t,e,{configuration:r,peers:s}){let a=je.sortMap(t.storedPackages.values(),f=>q.stringifyLocator(f)),n={},c={children:n};for(let f of a){let p={};for(let E of f.dependencies.values()){if(!s&&f.peerDependencies.has(E.identHash))continue;let C=t.storedResolutions.get(E.descriptorHash);if(!C)throw new Error(\"Assertion failed: The resolution should have been registered\");let S=t.storedPackages.get(C);if(!S)throw new Error(\"Assertion failed: The package should have been registered\");if(S.identHash!==e)continue;{let I=q.stringifyLocator(f);n[I]={value:[f,he.Type.LOCATOR],children:p}}let P=q.stringifyLocator(S);p[P]={value:[{descriptor:E,locator:S},he.Type.DEPENDENT]}}}return c}function qvt(t,e,{configuration:r,peers:s}){let a=je.sortMap(t.workspaces,S=>q.stringifyLocator(S.anchoredLocator)),n=new Set,c=new Set,f=S=>{if(n.has(S.locatorHash))return c.has(S.locatorHash);if(n.add(S.locatorHash),S.identHash===e)return c.add(S.locatorHash),!0;let P=!1;S.identHash===e&&(P=!0);for(let I of S.dependencies.values()){if(!s&&S.peerDependencies.has(I.identHash))continue;let R=t.storedResolutions.get(I.descriptorHash);if(!R)throw new Error(\"Assertion failed: The resolution should have been registered\");let N=t.storedPackages.get(R);if(!N)throw new Error(\"Assertion failed: The package should have been registered\");f(N)&&(P=!0)}return P&&c.add(S.locatorHash),P};for(let S of a)f(S.anchoredPackage);let p=new Set,h={},E={children:h},C=(S,P,I)=>{if(!c.has(S.locatorHash))return;let R=I!==null?he.tuple(he.Type.DEPENDENT,{locator:S,descriptor:I}):he.tuple(he.Type.LOCATOR,S),N={},U={value:R,children:N},W=q.stringifyLocator(S);if(P[W]=U,!(I!==null&&t.tryWorkspaceByLocator(S))&&!p.has(S.locatorHash)){p.add(S.locatorHash);for(let te of S.dependencies.values()){if(!s&&S.peerDependencies.has(te.identHash))continue;let ie=t.storedResolutions.get(te.descriptorHash);if(!ie)throw new Error(\"Assertion failed: The resolution should have been registered\");let Ae=t.storedPackages.get(ie);if(!Ae)throw new Error(\"Assertion failed: The package should have been registered\");C(Ae,N,te)}}};for(let S of a)C(S.anchoredPackage,h,null);return E}Ve();var W5={};Vt(W5,{GitFetcher:()=>AS,GitResolver:()=>pS,default:()=>uSt,gitUtils:()=>Qa});Ve();bt();var Qa={};Vt(Qa,{TreeishProtocols:()=>fS,clone:()=>G5,fetchBase:()=>bBe,fetchChangedFiles:()=>PBe,fetchChangedWorkspaces:()=>lSt,fetchRoot:()=>DBe,isGitUrl:()=>jC,lsRemote:()=>SBe,normalizeLocator:()=>aSt,normalizeRepoUrl:()=>UC,resolveUrl:()=>q5,splitRepoUrl:()=>G0,validateRepoUrl:()=>j5});Ve();bt();Wt();var wBe=et(EBe()),BBe=et(c6()),HC=et(Ie(\"querystring\")),U5=et(Ai());function _5(t,e,r){let s=t.indexOf(r);return t.lastIndexOf(e,s>-1?s:1/0)}function IBe(t){try{return new URL(t)}catch{return}}function sSt(t){let e=_5(t,\"@\",\"#\"),r=_5(t,\":\",\"#\");return r>e&&(t=`${t.slice(0,r)}/${t.slice(r+1)}`),_5(t,\":\",\"#\")===-1&&t.indexOf(\"//\")===-1&&(t=`ssh://${t}`),t}function CBe(t){return IBe(t)||IBe(sSt(t))}function UC(t,{git:e=!1}={}){if(t=t.replace(/^git\\+https:/,\"https:\"),t=t.replace(/^(?:github:|https:\\/\\/github\\.com\\/|git:\\/\\/github\\.com\\/)?(?!\\.{1,2}\\/)([a-zA-Z0-9._-]+)\\/(?!\\.{1,2}(?:#|$))([a-zA-Z0-9._-]+?)(?:\\.git)?(#.*)?$/,\"https://github.com/$1/$2.git$3\"),t=t.replace(/^https:\\/\\/github\\.com\\/(?!\\.{1,2}\\/)([a-zA-Z0-9._-]+)\\/(?!\\.{1,2}(?:#|$))([a-zA-Z0-9._-]+?)\\/tarball\\/(.+)?$/,\"https://github.com/$1/$2.git#$3\"),e){let r=CBe(t);r&&(t=r.href),t=t.replace(/^git\\+([^:]+):/,\"$1:\")}return t}function vBe(){return{...process.env,GIT_SSH_COMMAND:process.env.GIT_SSH_COMMAND||`${process.env.GIT_SSH||\"ssh\"} -o BatchMode=yes`}}var oSt=[/^ssh:/,/^git(?:\\+[^:]+)?:/,/^(?:git\\+)?https?:[^#]+\\/[^#]+(?:\\.git)(?:#.*)?$/,/^git@[^#]+\\/[^#]+\\.git(?:#.*)?$/,/^(?:github:|https:\\/\\/github\\.com\\/)?(?!\\.{1,2}\\/)([a-zA-Z._0-9-]+)\\/(?!\\.{1,2}(?:#|$))([a-zA-Z._0-9-]+?)(?:\\.git)?(?:#.*)?$/,/^https:\\/\\/github\\.com\\/(?!\\.{1,2}\\/)([a-zA-Z0-9._-]+)\\/(?!\\.{1,2}(?:#|$))([a-zA-Z0-9._-]+?)\\/tarball\\/(.+)?$/],fS=(a=>(a.Commit=\"commit\",a.Head=\"head\",a.Tag=\"tag\",a.Semver=\"semver\",a))(fS||{});function jC(t){return t?oSt.some(e=>!!t.match(e)):!1}function G0(t){t=UC(t);let e=t.indexOf(\"#\");if(e===-1)return{repo:t,treeish:{protocol:\"head\",request:\"HEAD\"},extra:{}};let r=t.slice(0,e),s=t.slice(e+1);if(s.match(/^[a-z]+=/)){let a=HC.default.parse(s);for(let[p,h]of Object.entries(a))if(typeof h!=\"string\")throw new Error(`Assertion failed: The ${p} parameter must be a literal string`);let n=Object.values(fS).find(p=>Object.hasOwn(a,p)),[c,f]=typeof n<\"u\"?[n,a[n]]:[\"head\",\"HEAD\"];for(let p of Object.values(fS))delete a[p];return{repo:r,treeish:{protocol:c,request:f},extra:a}}else{let a=s.indexOf(\":\"),[n,c]=a===-1?[null,s]:[s.slice(0,a),s.slice(a+1)];return{repo:r,treeish:{protocol:n,request:c},extra:{}}}}function aSt(t){return q.makeLocator(t,UC(t.reference))}function j5(t,{configuration:e}){let r=UC(t,{git:!0});if(!An.getNetworkSettings(`https://${(0,wBe.default)(r).resource}`,{configuration:e}).enableNetwork)throw new Yt(80,`Request to '${r}' has been blocked because of your configuration settings`);return r}async function SBe(t,e){let r=j5(t,{configuration:e}),s=await H5(\"listing refs\",[\"ls-remote\",r],{cwd:e.startingCwd,env:vBe()},{configuration:e,normalizedRepoUrl:r}),a=new Map,n=/^([a-f0-9]{40})\\t([^\\n]+)/gm,c;for(;(c=n.exec(s.stdout))!==null;)a.set(c[2],c[1]);return a}async function q5(t,e){let{repo:r,treeish:{protocol:s,request:a},extra:n}=G0(t),c=await SBe(r,e),f=(h,E)=>{switch(h){case\"commit\":{if(!E.match(/^[a-f0-9]{40}$/))throw new Error(\"Invalid commit hash\");return HC.default.stringify({...n,commit:E})}case\"head\":{let C=c.get(E===\"HEAD\"?E:`refs/heads/${E}`);if(typeof C>\"u\")throw new Error(`Unknown head (\"${E}\")`);return HC.default.stringify({...n,commit:C})}case\"tag\":{let C=c.get(`refs/tags/${E}`);if(typeof C>\"u\")throw new Error(`Unknown tag (\"${E}\")`);return HC.default.stringify({...n,commit:C})}case\"semver\":{let C=Or.validRange(E);if(!C)throw new Error(`Invalid range (\"${E}\")`);let S=new Map([...c.entries()].filter(([I])=>I.startsWith(\"refs/tags/\")).map(([I,R])=>[U5.default.parse(I.slice(10)),R]).filter(I=>I[0]!==null)),P=U5.default.maxSatisfying([...S.keys()],C);if(P===null)throw new Error(`No matching range (\"${E}\")`);return HC.default.stringify({...n,commit:S.get(P)})}case null:{let C;if((C=p(\"commit\",E))!==null||(C=p(\"tag\",E))!==null||(C=p(\"head\",E))!==null)return C;throw E.match(/^[a-f0-9]+$/)?new Error(`Couldn't resolve \"${E}\" as either a commit, a tag, or a head - if a commit, use the 40-characters commit hash`):new Error(`Couldn't resolve \"${E}\" as either a commit, a tag, or a head`)}default:throw new Error(`Invalid Git resolution protocol (\"${h}\")`)}},p=(h,E)=>{try{return f(h,E)}catch{return null}};return UC(`${r}#${f(s,a)}`)}async function G5(t,e){return await e.getLimit(\"cloneConcurrency\")(async()=>{let{repo:r,treeish:{protocol:s,request:a}}=G0(t);if(s!==\"commit\")throw new Error(\"Invalid treeish protocol when cloning\");let n=j5(r,{configuration:e}),c=await le.mktempPromise(),f={cwd:c,env:vBe()};return await H5(\"cloning the repository\",[\"clone\",\"-c core.autocrlf=false\",n,ue.fromPortablePath(c)],f,{configuration:e,normalizedRepoUrl:n}),await H5(\"switching branch\",[\"checkout\",`${a}`],f,{configuration:e,normalizedRepoUrl:n}),c})}async function DBe(t){let e,r=t;do{if(e=r,await le.existsPromise(K.join(e,\".git\")))return e;r=K.dirname(e)}while(r!==e);return null}async function bBe(t,{baseRefs:e}){if(e.length===0)throw new nt(\"Can't run this command with zero base refs specified.\");let r=[];for(let f of e){let{code:p}=await Gr.execvp(\"git\",[\"merge-base\",f,\"HEAD\"],{cwd:t});p===0&&r.push(f)}if(r.length===0)throw new nt(`No ancestor could be found between any of HEAD and ${e.join(\", \")}`);let{stdout:s}=await Gr.execvp(\"git\",[\"merge-base\",\"HEAD\",...r],{cwd:t,strict:!0}),a=s.trim(),{stdout:n}=await Gr.execvp(\"git\",[\"show\",\"--quiet\",\"--pretty=format:%s\",a],{cwd:t,strict:!0}),c=n.trim();return{hash:a,title:c}}async function PBe(t,{base:e,project:r}){let s=je.buildIgnorePattern(r.configuration.get(\"changesetIgnorePatterns\")),{stdout:a}=await Gr.execvp(\"git\",[\"diff\",\"--name-only\",`${e}`],{cwd:t,strict:!0}),n=a.split(/\\r\\n|\\r|\\n/).filter(h=>h.length>0).map(h=>K.resolve(t,ue.toPortablePath(h))),{stdout:c}=await Gr.execvp(\"git\",[\"ls-files\",\"--others\",\"--exclude-standard\"],{cwd:t,strict:!0}),f=c.split(/\\r\\n|\\r|\\n/).filter(h=>h.length>0).map(h=>K.resolve(t,ue.toPortablePath(h))),p=[...new Set([...n,...f].sort())];return s?p.filter(h=>!K.relative(r.cwd,h).match(s)):p}async function lSt({ref:t,project:e}){if(e.configuration.projectCwd===null)throw new nt(\"This command can only be run from within a Yarn project\");let r=[K.resolve(e.cwd,Er.lockfile),K.resolve(e.cwd,e.configuration.get(\"cacheFolder\")),K.resolve(e.cwd,e.configuration.get(\"installStatePath\")),K.resolve(e.cwd,e.configuration.get(\"virtualFolder\"))];await e.configuration.triggerHook(c=>c.populateYarnPaths,e,c=>{c!=null&&r.push(c)});let s=await DBe(e.configuration.projectCwd);if(s==null)throw new nt(\"This command can only be run on Git repositories\");let a=await bBe(s,{baseRefs:typeof t==\"string\"?[t]:e.configuration.get(\"changesetBaseRefs\")}),n=await PBe(s,{base:a.hash,project:e});return new Set(je.mapAndFilter(n,c=>{let f=e.tryWorkspaceByFilePath(c);return f===null?je.mapAndFilter.skip:r.some(p=>c.startsWith(p))?je.mapAndFilter.skip:f}))}async function H5(t,e,r,{configuration:s,normalizedRepoUrl:a}){try{return await Gr.execvp(\"git\",e,{...r,strict:!0})}catch(n){if(!(n instanceof Gr.ExecError))throw n;let c=n.reportExtra,f=n.stderr.toString();throw new Yt(1,`Failed ${t}`,p=>{p.reportError(1,`  ${he.prettyField(s,{label:\"Repository URL\",value:he.tuple(he.Type.URL,a)})}`);for(let h of f.matchAll(/^(.+?): (.*)$/gm)){let[,E,C]=h;E=E.toLowerCase();let S=E===\"error\"?\"Error\":`${(0,BBe.default)(E)} Error`;p.reportError(1,`  ${he.prettyField(s,{label:S,value:he.tuple(he.Type.NO_HINT,C)})}`)}c?.(p)})}}var AS=class{supports(e,r){return jC(e.reference)}getLocalPath(e,r){return null}async fetch(e,r){let s=r.checksums.get(e.locatorHash)||null,a=new Map(r.checksums);a.set(e.locatorHash,s);let n={...r,checksums:a},c=await this.downloadHosted(e,n);if(c!==null)return c;let[f,p,h]=await r.cache.fetchPackageFromCache(e,s,{onHit:()=>r.report.reportCacheHit(e),onMiss:()=>r.report.reportCacheMiss(e,`${q.prettyLocator(r.project.configuration,e)} can't be found in the cache and will be fetched from the remote repository`),loader:()=>this.cloneFromRemote(e,n),...r.cacheOptions});return{packageFs:f,releaseFs:p,prefixPath:q.getIdentVendorPath(e),checksum:h}}async downloadHosted(e,r){return r.project.configuration.reduceHook(s=>s.fetchHostedRepository,null,e,r)}async cloneFromRemote(e,r){let s=G0(e.reference),a=await G5(e.reference,r.project.configuration),n=K.resolve(a,s.extra.cwd??vt.dot),c=K.join(n,\"package.tgz\");await In.prepareExternalProject(n,c,{configuration:r.project.configuration,report:r.report,workspace:s.extra.workspace,locator:e});let f=await le.readFilePromise(c);return await je.releaseAfterUseAsync(async()=>await gs.convertToZip(f,{configuration:r.project.configuration,prefixPath:q.getIdentVendorPath(e),stripComponents:1}))}};Ve();Ve();var pS=class{supportsDescriptor(e,r){return jC(e.range)}supportsLocator(e,r){return jC(e.reference)}shouldPersistResolution(e,r){return!0}bindDescriptor(e,r,s){return e}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){let a=await q5(e.range,s.project.configuration);return[q.makeLocator(e,a)]}async getSatisfying(e,r,s,a){let n=G0(e.range);return{locators:s.filter(f=>{if(f.identHash!==e.identHash)return!1;let p=G0(f.reference);return!(n.repo!==p.repo||n.treeish.protocol===\"commit\"&&n.treeish.request!==p.treeish.request)}),sorted:!1}}async resolve(e,r){if(!r.fetchOptions)throw new Error(\"Assertion failed: This resolver cannot be used unless a fetcher is configured\");let s=await r.fetchOptions.fetcher.fetch(e,r.fetchOptions),a=await je.releaseAfterUseAsync(async()=>await Ht.find(s.prefixPath,{baseFs:s.packageFs}),s.releaseFs);return{...e,version:a.version||\"0.0.0\",languageName:a.languageName||r.project.configuration.get(\"defaultLanguageName\"),linkType:\"HARD\",conditions:a.getConditions(),dependencies:r.project.configuration.normalizeDependencyMap(a.dependencies),peerDependencies:a.peerDependencies,dependenciesMeta:a.dependenciesMeta,peerDependenciesMeta:a.peerDependenciesMeta,bin:a.bin}}};var cSt={configuration:{changesetBaseRefs:{description:\"The base git refs that the current HEAD is compared against when detecting changes. Supports git branches, tags, and commits.\",type:\"STRING\",isArray:!0,isNullable:!1,default:[\"master\",\"origin/master\",\"upstream/master\",\"main\",\"origin/main\",\"upstream/main\"]},changesetIgnorePatterns:{description:\"Array of glob patterns; files matching them will be ignored when fetching the changed files\",type:\"STRING\",default:[],isArray:!0},cloneConcurrency:{description:\"Maximal number of concurrent clones\",type:\"NUMBER\",default:2}},fetchers:[AS],resolvers:[pS]};var uSt=cSt;Wt();var qC=class extends ut{constructor(){super(...arguments);this.since=ge.String(\"--since\",{description:\"Only include workspaces that have been changed since the specified ref.\",tolerateBoolean:!0});this.recursive=ge.Boolean(\"-R,--recursive\",!1,{description:\"Find packages via dependencies/devDependencies instead of using the workspaces field\"});this.noPrivate=ge.Boolean(\"--no-private\",{description:\"Exclude workspaces that have the private field set to true\"});this.verbose=ge.Boolean(\"-v,--verbose\",!1,{description:\"Also return the cross-dependencies between workspaces\"});this.json=ge.Boolean(\"--json\",!1,{description:\"Format the output as an NDJSON stream\"})}static{this.paths=[[\"workspaces\",\"list\"]]}static{this.usage=ot.Usage({category:\"Workspace-related commands\",description:\"list all available workspaces\",details:\"\\n      This command will print the list of all workspaces in the project.\\n\\n      - If `--since` is set, Yarn will only list workspaces that have been modified since the specified ref. By default Yarn will use the refs specified by the `changesetBaseRefs` configuration option.\\n\\n      - If `-R,--recursive` is set, Yarn will find workspaces to run the command on by recursively evaluating `dependencies` and `devDependencies` fields, instead of looking at the `workspaces` fields.\\n\\n      - If `--no-private` is set, Yarn will not list any workspaces that have the `private` field set to `true`.\\n\\n      - If both the `-v,--verbose` and `--json` options are set, Yarn will also return the cross-dependencies between each workspaces (useful when you wish to automatically generate Buck / Bazel rules).\\n    \"})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s}=await Tt.find(r,this.context.cwd);return(await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout},async n=>{let c=this.since?await Qa.fetchChangedWorkspaces({ref:this.since,project:s}):s.workspaces,f=new Set(c);if(this.recursive)for(let p of[...c].map(h=>h.getRecursiveWorkspaceDependents()))for(let h of p)f.add(h);for(let p of f){let{manifest:h}=p;if(h.private&&this.noPrivate)continue;let E;if(this.verbose){let C=new Set,S=new Set;for(let P of Ht.hardDependencies)for(let[I,R]of h.getForScope(P)){let N=s.tryWorkspaceByDescriptor(R);N===null?s.workspacesByIdent.has(I)&&S.add(R):C.add(N)}E={workspaceDependencies:Array.from(C).map(P=>P.relativeCwd),mismatchedWorkspaceDependencies:Array.from(S).map(P=>q.stringifyDescriptor(P))}}n.reportInfo(null,`${p.relativeCwd}`),n.reportJson({location:p.relativeCwd,name:h.name?q.stringifyIdent(h.name):null,...E})}})).exitCode()}};Ve();Ve();Wt();var GC=class extends ut{constructor(){super(...arguments);this.workspaceName=ge.String();this.commandName=ge.String();this.args=ge.Proxy()}static{this.paths=[[\"workspace\"]]}static{this.usage=ot.Usage({category:\"Workspace-related commands\",description:\"run a command within the specified workspace\",details:`\n      This command will run a given sub-command on a single workspace.\n    `,examples:[[\"Add a package to a single workspace\",\"yarn workspace components add -D react\"],[\"Run build script on a single workspace\",\"yarn workspace components run build\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);let n=s.workspaces,c=new Map(n.map(p=>[q.stringifyIdent(p.anchoredLocator),p])),f=c.get(this.workspaceName);if(f===void 0){let p=Array.from(c.keys()).sort();throw new nt(`Workspace '${this.workspaceName}' not found. Did you mean any of the following:\n  - ${p.join(`\n  - `)}?`)}return this.cli.run([this.commandName,...this.args],{cwd:f.cwd})}};var fSt={configuration:{enableImmutableInstalls:{description:\"If true (the default on CI), prevents the install command from modifying the lockfile\",type:\"BOOLEAN\",default:xBe.isCI},defaultSemverRangePrefix:{description:\"The default save prefix: '^', '~' or ''\",type:\"STRING\",values:[\"^\",\"~\",\"\"],default:\"^\"},preferReuse:{description:\"If true, `yarn add` will attempt to reuse the most common dependency range in other workspaces.\",type:\"BOOLEAN\",default:!1}},commands:[aC,lC,cC,uC,OC,bC,EC,qC,pC,hC,gC,dC,sC,oC,fC,AC,mC,yC,IC,CC,wC,BC,LC,vC,SC,xC,PC,kC,DC,QC,TC,RC,FC,NC,MC,_C,GC]},ASt=fSt;var Z5={};Vt(Z5,{default:()=>hSt});Ve();var Qt={optional:!0},V5=[[\"@tailwindcss/aspect-ratio@<0.2.1\",{peerDependencies:{tailwindcss:\"^2.0.2\"}}],[\"@tailwindcss/line-clamp@<0.2.1\",{peerDependencies:{tailwindcss:\"^2.0.2\"}}],[\"@fullhuman/postcss-purgecss@3.1.3 || 3.1.3-alpha.0\",{peerDependencies:{postcss:\"^8.0.0\"}}],[\"@samverschueren/stream-to-observable@<0.3.1\",{peerDependenciesMeta:{rxjs:Qt,zenObservable:Qt}}],[\"any-observable@<0.5.1\",{peerDependenciesMeta:{rxjs:Qt,zenObservable:Qt}}],[\"@pm2/agent@<1.0.4\",{dependencies:{debug:\"*\"}}],[\"debug@<4.2.0\",{peerDependenciesMeta:{\"supports-color\":Qt}}],[\"got@<11\",{dependencies:{\"@types/responselike\":\"^1.0.0\",\"@types/keyv\":\"^3.1.1\"}}],[\"cacheable-lookup@<4.1.2\",{dependencies:{\"@types/keyv\":\"^3.1.1\"}}],[\"http-link-dataloader@*\",{peerDependencies:{graphql:\"^0.13.1 || ^14.0.0\"}}],[\"typescript-language-server@*\",{dependencies:{\"vscode-jsonrpc\":\"^5.0.1\",\"vscode-languageserver-protocol\":\"^3.15.0\"}}],[\"postcss-syntax@*\",{peerDependenciesMeta:{\"postcss-html\":Qt,\"postcss-jsx\":Qt,\"postcss-less\":Qt,\"postcss-markdown\":Qt,\"postcss-scss\":Qt}}],[\"jss-plugin-rule-value-function@<=10.1.1\",{dependencies:{\"tiny-warning\":\"^1.0.2\"}}],[\"ink-select-input@<4.1.0\",{peerDependencies:{react:\"^16.8.2\"}}],[\"license-webpack-plugin@<2.3.18\",{peerDependenciesMeta:{webpack:Qt}}],[\"snowpack@>=3.3.0\",{dependencies:{\"node-gyp\":\"^7.1.0\"}}],[\"promise-inflight@*\",{peerDependenciesMeta:{bluebird:Qt}}],[\"reactcss@*\",{peerDependencies:{react:\"*\"}}],[\"react-color@<=2.19.0\",{peerDependencies:{react:\"*\"}}],[\"gatsby-plugin-i18n@*\",{dependencies:{ramda:\"^0.24.1\"}}],[\"useragent@^2.0.0\",{dependencies:{request:\"^2.88.0\",yamlparser:\"0.0.x\",semver:\"5.5.x\"}}],[\"@apollographql/apollo-tools@<=0.5.2\",{peerDependencies:{graphql:\"^14.2.1 || ^15.0.0\"}}],[\"material-table@^2.0.0\",{dependencies:{\"@babel/runtime\":\"^7.11.2\"}}],[\"@babel/parser@*\",{dependencies:{\"@babel/types\":\"^7.8.3\"}}],[\"fork-ts-checker-webpack-plugin@<=6.3.4\",{peerDependencies:{eslint:\">= 6\",typescript:\">= 2.7\",webpack:\">= 4\",\"vue-template-compiler\":\"*\"},peerDependenciesMeta:{eslint:Qt,\"vue-template-compiler\":Qt}}],[\"rc-animate@<=3.1.1\",{peerDependencies:{react:\">=16.9.0\",\"react-dom\":\">=16.9.0\"}}],[\"react-bootstrap-table2-paginator@*\",{dependencies:{classnames:\"^2.2.6\"}}],[\"react-draggable@<=4.4.3\",{peerDependencies:{react:\">= 16.3.0\",\"react-dom\":\">= 16.3.0\"}}],[\"apollo-upload-client@<14\",{peerDependencies:{graphql:\"14 - 15\"}}],[\"react-instantsearch-core@<=6.7.0\",{peerDependencies:{algoliasearch:\">= 3.1 < 5\"}}],[\"react-instantsearch-dom@<=6.7.0\",{dependencies:{\"react-fast-compare\":\"^3.0.0\"}}],[\"ws@<7.2.1\",{peerDependencies:{bufferutil:\"^4.0.1\",\"utf-8-validate\":\"^5.0.2\"},peerDependenciesMeta:{bufferutil:Qt,\"utf-8-validate\":Qt}}],[\"react-portal@<4.2.2\",{peerDependencies:{\"react-dom\":\"^15.0.0-0 || ^16.0.0-0 || ^17.0.0-0\"}}],[\"react-scripts@<=4.0.1\",{peerDependencies:{react:\"*\"}}],[\"testcafe@<=1.10.1\",{dependencies:{\"@babel/plugin-transform-for-of\":\"^7.12.1\",\"@babel/runtime\":\"^7.12.5\"}}],[\"testcafe-legacy-api@<=4.2.0\",{dependencies:{\"testcafe-hammerhead\":\"^17.0.1\",\"read-file-relative\":\"^1.2.0\"}}],[\"@google-cloud/firestore@<=4.9.3\",{dependencies:{protobufjs:\"^6.8.6\"}}],[\"gatsby-source-apiserver@*\",{dependencies:{\"babel-polyfill\":\"^6.26.0\"}}],[\"@webpack-cli/package-utils@<=1.0.1-alpha.4\",{dependencies:{\"cross-spawn\":\"^7.0.3\"}}],[\"gatsby-remark-prismjs@<3.3.28\",{dependencies:{lodash:\"^4\"}}],[\"gatsby-plugin-favicon@*\",{peerDependencies:{webpack:\"*\"}}],[\"gatsby-plugin-sharp@<=4.6.0-next.3\",{dependencies:{debug:\"^4.3.1\"}}],[\"gatsby-react-router-scroll@<=5.6.0-next.0\",{dependencies:{\"prop-types\":\"^15.7.2\"}}],[\"@rebass/forms@*\",{dependencies:{\"@styled-system/should-forward-prop\":\"^5.0.0\"},peerDependencies:{react:\"^16.8.6\"}}],[\"rebass@*\",{peerDependencies:{react:\"^16.8.6\"}}],[\"@ant-design/react-slick@<=0.28.3\",{peerDependencies:{react:\">=16.0.0\"}}],[\"mqtt@<4.2.7\",{dependencies:{duplexify:\"^4.1.1\"}}],[\"vue-cli-plugin-vuetify@<=2.0.3\",{dependencies:{semver:\"^6.3.0\"},peerDependenciesMeta:{\"sass-loader\":Qt,\"vuetify-loader\":Qt}}],[\"vue-cli-plugin-vuetify@<=2.0.4\",{dependencies:{\"null-loader\":\"^3.0.0\"}}],[\"vue-cli-plugin-vuetify@>=2.4.3\",{peerDependencies:{vue:\"*\"}}],[\"@vuetify/cli-plugin-utils@<=0.0.4\",{dependencies:{semver:\"^6.3.0\"},peerDependenciesMeta:{\"sass-loader\":Qt}}],[\"@vue/cli-plugin-typescript@<=5.0.0-alpha.0\",{dependencies:{\"babel-loader\":\"^8.1.0\"}}],[\"@vue/cli-plugin-typescript@<=5.0.0-beta.0\",{dependencies:{\"@babel/core\":\"^7.12.16\"},peerDependencies:{\"vue-template-compiler\":\"^2.0.0\"},peerDependenciesMeta:{\"vue-template-compiler\":Qt}}],[\"cordova-ios@<=6.3.0\",{dependencies:{underscore:\"^1.9.2\"}}],[\"cordova-lib@<=10.0.1\",{dependencies:{underscore:\"^1.9.2\"}}],[\"git-node-fs@*\",{peerDependencies:{\"js-git\":\"^0.7.8\"},peerDependenciesMeta:{\"js-git\":Qt}}],[\"consolidate@<0.16.0\",{peerDependencies:{mustache:\"^3.0.0\"},peerDependenciesMeta:{mustache:Qt}}],[\"consolidate@<=0.16.0\",{peerDependencies:{velocityjs:\"^2.0.1\",tinyliquid:\"^0.2.34\",\"liquid-node\":\"^3.0.1\",jade:\"^1.11.0\",\"then-jade\":\"*\",dust:\"^0.3.0\",\"dustjs-helpers\":\"^1.7.4\",\"dustjs-linkedin\":\"^2.7.5\",swig:\"^1.4.2\",\"swig-templates\":\"^2.0.3\",\"razor-tmpl\":\"^1.3.1\",atpl:\">=0.7.6\",liquor:\"^0.0.5\",twig:\"^1.15.2\",ejs:\"^3.1.5\",eco:\"^1.1.0-rc-3\",jazz:\"^0.0.18\",jqtpl:\"~1.1.0\",hamljs:\"^0.6.2\",hamlet:\"^0.3.3\",whiskers:\"^0.4.0\",\"haml-coffee\":\"^1.14.1\",\"hogan.js\":\"^3.0.2\",templayed:\">=0.2.3\",handlebars:\"^4.7.6\",underscore:\"^1.11.0\",lodash:\"^4.17.20\",pug:\"^3.0.0\",\"then-pug\":\"*\",qejs:\"^3.0.5\",walrus:\"^0.10.1\",mustache:\"^4.0.1\",just:\"^0.1.8\",ect:\"^0.5.9\",mote:\"^0.2.0\",toffee:\"^0.3.6\",dot:\"^1.1.3\",\"bracket-template\":\"^1.1.5\",ractive:\"^1.3.12\",nunjucks:\"^3.2.2\",htmling:\"^0.0.8\",\"babel-core\":\"^6.26.3\",plates:\"~0.4.11\",\"react-dom\":\"^16.13.1\",react:\"^16.13.1\",\"arc-templates\":\"^0.5.3\",vash:\"^0.13.0\",slm:\"^2.0.0\",marko:\"^3.14.4\",teacup:\"^2.0.0\",\"coffee-script\":\"^1.12.7\",squirrelly:\"^5.1.0\",twing:\"^5.0.2\"},peerDependenciesMeta:{velocityjs:Qt,tinyliquid:Qt,\"liquid-node\":Qt,jade:Qt,\"then-jade\":Qt,dust:Qt,\"dustjs-helpers\":Qt,\"dustjs-linkedin\":Qt,swig:Qt,\"swig-templates\":Qt,\"razor-tmpl\":Qt,atpl:Qt,liquor:Qt,twig:Qt,ejs:Qt,eco:Qt,jazz:Qt,jqtpl:Qt,hamljs:Qt,hamlet:Qt,whiskers:Qt,\"haml-coffee\":Qt,\"hogan.js\":Qt,templayed:Qt,handlebars:Qt,underscore:Qt,lodash:Qt,pug:Qt,\"then-pug\":Qt,qejs:Qt,walrus:Qt,mustache:Qt,just:Qt,ect:Qt,mote:Qt,toffee:Qt,dot:Qt,\"bracket-template\":Qt,ractive:Qt,nunjucks:Qt,htmling:Qt,\"babel-core\":Qt,plates:Qt,\"react-dom\":Qt,react:Qt,\"arc-templates\":Qt,vash:Qt,slm:Qt,marko:Qt,teacup:Qt,\"coffee-script\":Qt,squirrelly:Qt,twing:Qt}}],[\"vue-loader@<=16.3.3\",{peerDependencies:{\"@vue/compiler-sfc\":\"^3.0.8\",webpack:\"^4.1.0 || ^5.0.0-0\"},peerDependenciesMeta:{\"@vue/compiler-sfc\":Qt}}],[\"vue-loader@^16.7.0\",{peerDependencies:{\"@vue/compiler-sfc\":\"^3.0.8\",vue:\"^3.2.13\"},peerDependenciesMeta:{\"@vue/compiler-sfc\":Qt,vue:Qt}}],[\"scss-parser@<=1.0.5\",{dependencies:{lodash:\"^4.17.21\"}}],[\"query-ast@<1.0.5\",{dependencies:{lodash:\"^4.17.21\"}}],[\"redux-thunk@<=2.3.0\",{peerDependencies:{redux:\"^4.0.0\"}}],[\"skypack@<=0.3.2\",{dependencies:{tar:\"^6.1.0\"}}],[\"@npmcli/metavuln-calculator@<2.0.0\",{dependencies:{\"json-parse-even-better-errors\":\"^2.3.1\"}}],[\"bin-links@<2.3.0\",{dependencies:{\"mkdirp-infer-owner\":\"^1.0.2\"}}],[\"rollup-plugin-polyfill-node@<=0.8.0\",{peerDependencies:{rollup:\"^1.20.0 || ^2.0.0\"}}],[\"snowpack@<3.8.6\",{dependencies:{\"magic-string\":\"^0.25.7\"}}],[\"elm-webpack-loader@*\",{dependencies:{temp:\"^0.9.4\"}}],[\"winston-transport@<=4.4.0\",{dependencies:{logform:\"^2.2.0\"}}],[\"jest-vue-preprocessor@*\",{dependencies:{\"@babel/core\":\"7.8.7\",\"@babel/template\":\"7.8.6\"},peerDependencies:{pug:\"^2.0.4\"},peerDependenciesMeta:{pug:Qt}}],[\"redux-persist@*\",{peerDependencies:{react:\">=16\"},peerDependenciesMeta:{react:Qt}}],[\"sodium@>=3\",{dependencies:{\"node-gyp\":\"^3.8.0\"}}],[\"babel-plugin-graphql-tag@<=3.1.0\",{peerDependencies:{graphql:\"^14.0.0 || ^15.0.0\"}}],[\"@playwright/test@<=1.14.1\",{dependencies:{\"jest-matcher-utils\":\"^26.4.2\"}}],...[\"babel-plugin-remove-graphql-queries@<3.14.0-next.1\",\"babel-preset-gatsby-package@<1.14.0-next.1\",\"create-gatsby@<1.14.0-next.1\",\"gatsby-admin@<0.24.0-next.1\",\"gatsby-cli@<3.14.0-next.1\",\"gatsby-core-utils@<2.14.0-next.1\",\"gatsby-design-tokens@<3.14.0-next.1\",\"gatsby-legacy-polyfills@<1.14.0-next.1\",\"gatsby-plugin-benchmark-reporting@<1.14.0-next.1\",\"gatsby-plugin-graphql-config@<0.23.0-next.1\",\"gatsby-plugin-image@<1.14.0-next.1\",\"gatsby-plugin-mdx@<2.14.0-next.1\",\"gatsby-plugin-netlify-cms@<5.14.0-next.1\",\"gatsby-plugin-no-sourcemaps@<3.14.0-next.1\",\"gatsby-plugin-page-creator@<3.14.0-next.1\",\"gatsby-plugin-preact@<5.14.0-next.1\",\"gatsby-plugin-preload-fonts@<2.14.0-next.1\",\"gatsby-plugin-schema-snapshot@<2.14.0-next.1\",\"gatsby-plugin-styletron@<6.14.0-next.1\",\"gatsby-plugin-subfont@<3.14.0-next.1\",\"gatsby-plugin-utils@<1.14.0-next.1\",\"gatsby-recipes@<0.25.0-next.1\",\"gatsby-source-shopify@<5.6.0-next.1\",\"gatsby-source-wikipedia@<3.14.0-next.1\",\"gatsby-transformer-screenshot@<3.14.0-next.1\",\"gatsby-worker@<0.5.0-next.1\"].map(t=>[t,{dependencies:{\"@babel/runtime\":\"^7.14.8\"}}]),[\"gatsby-core-utils@<2.14.0-next.1\",{dependencies:{got:\"8.3.2\"}}],[\"gatsby-plugin-gatsby-cloud@<=3.1.0-next.0\",{dependencies:{\"gatsby-core-utils\":\"^2.13.0-next.0\"}}],[\"gatsby-plugin-gatsby-cloud@<=3.2.0-next.1\",{peerDependencies:{webpack:\"*\"}}],[\"babel-plugin-remove-graphql-queries@<=3.14.0-next.1\",{dependencies:{\"gatsby-core-utils\":\"^2.8.0-next.1\"}}],[\"gatsby-plugin-netlify@3.13.0-next.1\",{dependencies:{\"gatsby-core-utils\":\"^2.13.0-next.0\"}}],[\"clipanion-v3-codemod@<=0.2.0\",{peerDependencies:{jscodeshift:\"^0.11.0\"}}],[\"react-live@*\",{peerDependencies:{\"react-dom\":\"*\",react:\"*\"}}],[\"webpack@<4.44.1\",{peerDependenciesMeta:{\"webpack-cli\":Qt,\"webpack-command\":Qt}}],[\"webpack@<5.0.0-beta.23\",{peerDependenciesMeta:{\"webpack-cli\":Qt}}],[\"webpack-dev-server@<3.10.2\",{peerDependenciesMeta:{\"webpack-cli\":Qt}}],[\"@docusaurus/responsive-loader@<1.5.0\",{peerDependenciesMeta:{sharp:Qt,jimp:Qt}}],[\"eslint-module-utils@*\",{peerDependenciesMeta:{\"eslint-import-resolver-node\":Qt,\"eslint-import-resolver-typescript\":Qt,\"eslint-import-resolver-webpack\":Qt,\"@typescript-eslint/parser\":Qt}}],[\"eslint-plugin-import@*\",{peerDependenciesMeta:{\"@typescript-eslint/parser\":Qt}}],[\"critters-webpack-plugin@<3.0.2\",{peerDependenciesMeta:{\"html-webpack-plugin\":Qt}}],[\"terser@<=5.10.0\",{dependencies:{acorn:\"^8.5.0\"}}],[\"babel-preset-react-app@10.0.x <10.0.2\",{dependencies:{\"@babel/plugin-proposal-private-property-in-object\":\"^7.16.7\"}}],[\"eslint-config-react-app@*\",{peerDependenciesMeta:{typescript:Qt}}],[\"@vue/eslint-config-typescript@<11.0.0\",{peerDependenciesMeta:{typescript:Qt}}],[\"unplugin-vue2-script-setup@<0.9.1\",{peerDependencies:{\"@vue/composition-api\":\"^1.4.3\",\"@vue/runtime-dom\":\"^3.2.26\"}}],[\"@cypress/snapshot@*\",{dependencies:{debug:\"^3.2.7\"}}],[\"auto-relay@<=0.14.0\",{peerDependencies:{\"reflect-metadata\":\"^0.1.13\"}}],[\"vue-template-babel-compiler@<1.2.0\",{peerDependencies:{\"vue-template-compiler\":\"^2.6.0\"}}],[\"@parcel/transformer-image@<2.5.0\",{peerDependencies:{\"@parcel/core\":\"*\"}}],[\"@parcel/transformer-js@<2.5.0\",{peerDependencies:{\"@parcel/core\":\"*\"}}],[\"parcel@*\",{peerDependenciesMeta:{\"@parcel/core\":Qt}}],[\"react-scripts@*\",{peerDependencies:{eslint:\"*\"}}],[\"focus-trap-react@^8.0.0\",{dependencies:{tabbable:\"^5.3.2\"}}],[\"react-rnd@<10.3.7\",{peerDependencies:{react:\">=16.3.0\",\"react-dom\":\">=16.3.0\"}}],[\"connect-mongo@<5.0.0\",{peerDependencies:{\"express-session\":\"^1.17.1\"}}],[\"vue-i18n@<9\",{peerDependencies:{vue:\"^2\"}}],[\"vue-router@<4\",{peerDependencies:{vue:\"^2\"}}],[\"unified@<10\",{dependencies:{\"@types/unist\":\"^2.0.0\"}}],[\"react-github-btn@<=1.3.0\",{peerDependencies:{react:\">=16.3.0\"}}],[\"react-dev-utils@*\",{peerDependencies:{typescript:\">=2.7\",webpack:\">=4\"},peerDependenciesMeta:{typescript:Qt}}],[\"@asyncapi/react-component@<=1.0.0-next.39\",{peerDependencies:{react:\">=16.8.0\",\"react-dom\":\">=16.8.0\"}}],[\"xo@*\",{peerDependencies:{webpack:\">=1.11.0\"},peerDependenciesMeta:{webpack:Qt}}],[\"babel-plugin-remove-graphql-queries@<=4.20.0-next.0\",{dependencies:{\"@babel/types\":\"^7.15.4\"}}],[\"gatsby-plugin-page-creator@<=4.20.0-next.1\",{dependencies:{\"fs-extra\":\"^10.1.0\"}}],[\"gatsby-plugin-utils@<=3.14.0-next.1\",{dependencies:{fastq:\"^1.13.0\"},peerDependencies:{graphql:\"^15.0.0\"}}],[\"gatsby-plugin-mdx@<3.1.0-next.1\",{dependencies:{mkdirp:\"^1.0.4\"}}],[\"gatsby-plugin-mdx@^2\",{peerDependencies:{gatsby:\"^3.0.0-next\"}}],[\"fdir@<=5.2.0\",{peerDependencies:{picomatch:\"2.x\"},peerDependenciesMeta:{picomatch:Qt}}],[\"babel-plugin-transform-typescript-metadata@<=0.3.2\",{peerDependencies:{\"@babel/core\":\"^7\",\"@babel/traverse\":\"^7\"},peerDependenciesMeta:{\"@babel/traverse\":Qt}}],[\"graphql-compose@>=9.0.10\",{peerDependencies:{graphql:\"^14.2.0 || ^15.0.0 || ^16.0.0\"}}],[\"vite-plugin-vuetify@<=1.0.2\",{peerDependencies:{vue:\"^3.0.0\"}}],[\"webpack-plugin-vuetify@<=2.0.1\",{peerDependencies:{vue:\"^3.2.6\"}}],[\"eslint-import-resolver-vite@<2.0.1\",{dependencies:{debug:\"^4.3.4\",resolve:\"^1.22.8\"}}],[\"notistack@^3.0.0\",{dependencies:{csstype:\"^3.0.10\"}}],[\"@fastify/type-provider-typebox@^5.0.0\",{peerDependencies:{fastify:\"^5.0.0\"}}],[\"@fastify/type-provider-typebox@^4.0.0\",{peerDependencies:{fastify:\"^4.0.0\"}}]];var K5;function kBe(){return typeof K5>\"u\"&&(K5=Ie(\"zlib\").brotliDecompressSync(Buffer.from(\"G7weAByFTVk3Vs7UfHhq4yykgEM7pbW7TI43SG2S5tvGrwHBAzdz+s/npQ6tgEvobvxisrPIadkXeUAJotBn5bDZ5kAhcRqsIHe3F75Walet5hNalwgFDtxb0BiDUjiUQkjG0yW2hto9HPgiCkm316d6bC0kST72YN7D7rfkhCE9x4J0XwB0yavalxpUu2t9xszHrmtwalOxT7VslsxWcB1qpqZwERUra4psWhTV8BgwWeizurec82Caf1ABL11YMfbf8FJ9JBceZOkgmvrQPbC9DUldX/yMbmX06UQluCEjSwUoyO+EZPIjofr+/oAZUck2enraRD+oWLlnlYnj8xB+gwSo9lmmks4fXv574qSqcWA6z21uYkzMu3EWj+K23RxeQlLqiE35/rC8GcS4CGkKHKKq+zAIQwD9iRDNfiAqueLLpicFFrNsAI4zeTD/eO9MHcnRa5m8UT+M2+V+AkFST4BlKneiAQRSdST8KEAIyFlULt6wa9EBd0Ds28VmpaxquJdVt+nwdEs5xUskI13OVtFyY0UrQIRAlCuvvWivvlSKQfTO+2Q8OyUR1W5RvetaPz4jD27hdtwHFFA1Ptx6Ee/t2cY2rg2G46M1pNDRf2pWhvpy8pqMnuI3++4OF3+7OFIWXGjh+o7Nr2jNvbiYcQdQS1h903/jVFgOpA0yJ78z+x759bFA0rq+6aY5qPB4FzS3oYoLupDUhD9nDz6F6H7hpnlMf18KNKDu4IKjTWwrAnY6MFQw1W6ymOALHlFyCZmQhldg1MQHaMVVQTVgDC60TfaBqG++Y8PEoFhN/PBTZT175KNP/BlHDYGOOBmnBdzqJKplZ/ljiVG0ZBzfqeBRrrUkn6rA54462SgiliKoYVnbeptMdXNfAuaupIEi0bApF10TlgHfmEJAPUVidRVFyDupSem5po5vErPqWKhKbUIp0LozpYsIKK57dM/HKr+nguF+7924IIWMICkQ8JUigs9D+W+c4LnNoRtPPKNRUiCYmP+Jfo2lfKCKw8qpraEeWU3uiNRO6zcyKQoXPR5htmzzLznke7b4YbXW3I1lIRzmgG02Udb58U+7TpwyN7XymCgH+wuPDthZVQvRZuEP+SnLtMicz9m5zASWOBiAcLmkuFlTKuHspSIhCBD0yUPKcxu81A+4YD78rA2vtwsUEday9WNyrShyrl60rWmA+SmbYZkQOwFJWArxRYYc5jGhA5ikxYw1rx3ei4NmeX/lKiwpZ9Ln1tV2Ae7sArvxuVLbJjqJRjW1vFXAyHpvLG+8MJ6T2Ubx5M2KDa2SN6vuIGxJ9WQM9Mk3Q7aCNiZONXllhqq24DmoLbQfW2rYWsOgHWjtOmIQMyMKdiHZDjoyIq5+U700nZ6odJAoYXPQBvFNiQ78d5jaXliBqLTJEqUCwi+LiH2mx92EmNKDsJL74Z613+3lf20pxkV1+erOrjj8pW00vsPaahKUM+05ssd5uwM7K482KWEf3TCwlg/o3e5ngto7qSMz7YteIgCsF1UOcsLk7F7MxWbvrPMY473ew0G+noVL8EPbkmEMftMSeL6HFub/zy+2JQ==\",\"base64\")).toString()),K5}var J5;function QBe(){return typeof J5>\"u\"&&(J5=Ie(\"zlib\").brotliDecompressSync(Buffer.from(\"G8MSIIzURnVBnObTcvb3XE6v2S9Qgc2K801Oa5otNKEtK8BINZNcaQHy+9/vf/WXBimwutXC33P2DPc64pps5rz7NGGWaOKNSPL4Y2KRE8twut2lFOIN+OXPtRmPMRhMTILib2bEQx43az2I5d3YS8Roa5UZpF/ujHb3Djd3GDvYUfvFYSUQ39vb2cmifp/rgB4J/65JK3wRBTvMBoNBmn3mbXC63/gbBkW/2IRPri0O8bcsRBsmarF328pAln04nyJFkwUAvNu934supAqLtyerZZpJ8I8suJHhf/ocMV+scKwa8NOiDKIPXw6Ex/EEZD6TEGaW8N5zvNHYF10l6Lfooj7D5W2k3dgvQSbp2Wv8TGOayS978gxlOLVjTGXs66ozewbrjwElLtyrYNnWTfzzdEutgROUFPVMhnMoy8EjJLLlWwIEoySxliim9kYW30JUHiPVyjt0iAw/ZpPmCbUCltYPnq6ZNblIKhTNhqS/oqC9iya5sGKZTOVsTEg34n92uZTf2iPpcZih8rPW8CzA+adIGmyCPcKdLMsBLShd+zuEbTrqpwuh+DLmracZcjPC5Sdf5odDAhKpFuOsQS67RT+1VgWWygSv3YwxDnylc04/PYuaMeIzhBkLrvs7e/OUzRTF56MmfY6rI63QtEjEQzq637zQqJ39nNhu3NmoRRhW/086bHGBUtx0PE0j3aEGvkdh9WJC8y8j8mqqke9/dQ5la+Q3ba4RlhvTbnfQhPDDab3tUifkjKuOsp13mXEmO00Mu88F/M67R7LXfoFDFLNtgCSWjWX+3Jn1371pJTK9xPBiMJafvDjtFyAzu8rxeQ0TKMQXNPs5xxiBOd+BRJP8KP88XPtJIbZKh/cdW8KvBUkpqKpGoiIaA32c3/JnQr4efXt85mXvidOvn/eU3Pase1typLYBalJ14mCso9h79nuMOuCa/kZAOkJHmTjP5RM2WNoPasZUAnT1TAE/NH25hUxcQv6hQWR/m1PKk4ooXMcM4SR1iYU3fUohvqk4RY2hbmTVVIXv6TvqO+0doOjgeVFAcom+RlwJQmOVH7pr1Q9LoJT6n1DeQEB+NHygsATbIwTcOKZlJsY8G4+suX1uQLjUWwLjjs0mvSvZcLTpIGAekeR7GCgl8eo3ndAqEe2XCav4huliHjdbIPBsGJuPX7lrO9HX1UbXRH5opOe1x6JsOSgHZR+EaxuXVhpLLxm6jk1LJtZfHSc6BKPun3CpYYVMJGwEUyk8MTGG0XL5MfEwaXpnc9TKnBmlGn6nHiGREc3ysn47XIBDzA+YvFdjZzVIEDcKGpS6PbUJehFRjEne8D0lVU1XuRtlgszq6pTNlQ/3MzNOEgCWPyTct22V2mEi2krizn5VDo9B19/X2DB3hCGRMM7ONbtnAcIx/OWB1u5uPbW1gsH8irXxT/IzG0PoXWYjhbMsH3KTuoOl5o17PulcgvsfTSnKFM354GWI8luqZnrswWjiXy3G+Vbyo1KMopFmmvBwNELgaS8z8dNZchx/Cl/xjddxhMcyqtzFyONb2Zdu90NkI8pAeufe7YlXrp53v8Dj/l8vWeVspRKBGXScBBPI/HinSTGmLDOGGOCIyH0JFdOZx0gWsacNlQLJMIrBhqRxXxHF/5pseWwejlAAvZ3klZSDSYY8mkToaWejXhgNomeGtx1DTLEUFMRkgF5yFB22WYdJnaWN14r1YJj81hGi45+jrADS5nYRhCiSlCJJ1nL8pYX+HDSMhdTEWyRcgHVp/IsUIZYMfT+YYncUQPgcxNGCHfZ88vDdrcUuaGIl6zhAsiaq7R5dfqrqXH/JcBhfjT8D0azayIyEz75Nxp6YkcyDxlJq3EXnJUpqDohJJOysL1t1uNiHESlvsxPb5cpbW0+ICZqJmUZus1BMW0F5IVBODLIo2zHHjA0=\",\"base64\")).toString()),J5}var z5;function TBe(){return typeof z5>\"u\"&&(z5=Ie(\"zlib\").brotliDecompressSync(Buffer.from(\"m9XmPqMRsZ7bFo1U5CxexdgYepcdMsrcAbbqv7/rCXGM7SZhmJ2jPScITf1tA+qxuDFE8KC9mQaCs84ftss/pB0UrlDfSS52Q7rXyYIcHbrGG2egYMqC8FFfnNfZVLU+4ZieJEVLu1qxY0MYkbD8opX7TYstjKzqxwBObq8HUIQwogljOgs72xyCrxj0q79cf/hN2Ys/0fU6gkRgxFedikACuQLS4lvO/N5NpZ85m+BdO3c5VplDLMcfEDt6umRCbfM16uxnqUKPvPFg/qtuzzId3SjAxZFoZRqK3pdtWt/C+VU6+zuX09NsoBs3MwobpU1yyoXZnzA1EmiMRS5GfJeLxV51/jSXrfgTWr1af9hwKvqCfSVHiQuk+uO/N16Cror2c1QlthM7WkS/86azhK3b47PG6f5TAJVtrK7g+zlR2boyKBV+QkdOXcfBDrI8yCciS3LktLb+d3gopE3R1QYFN1QWdQtrso2qK3+OTVYpTdPAfICTe9//3y/1+6mixIob4kfOI1WT3DxyD2ZuR06a6RPOPlftc/bZeqWqUtoqSetJlgP0AOBsOOeWqkpKJDtgP25CmIz+ZAo8+zwb3wI5ZD/0a7Qb7Q8Ag8HkWzhVQqzLFksA/nKSsR6hEu4tymzAQcZUDV4D2f17NbNSreHMVG0D1Knfa5n//prG6IzFVH7GSdEZn+1eEohVH5hmz6wxnj0biDxnMlq0fHQ2v7ogu8tEBnHaJICmVgLINf+jr4b/AVtDfPSZWelMen+u+pT60nu+9LrK0z0L/oyvC+kDtsi13AdC/i6pd29uB/1alOsA0Kc6N0wICwzbHkBQGJ94pBZ5TyKj7lzzUQ5CYn3Xp/cLhrJ2GpBakWmkymfeKcX2Vy2QEDcIxnju2369rf+l+H7E96GzyVs0gyDzUD0ipfKdmd7LN80sxjSiau/0PX2e7EMt4hNqThHEad9B1L44EDU1ZyFL+QJ0n1v7McxqupfO9zYGEBGJ0XxHdZmWuNKcV+0WJmzGd4y1qu3RfbunEBAQgZyBUWwjoXAwxk2XVRjBAy1jWcGsnb/Tu2oRKUbqGxHjFxUihoreyXW2M2ZnxkQYPfCorcVYq7rnrfuUV1ZYBNakboTPj+b+PLaIyFVsA5nmcP8ZS23WpTvTnSog5wfhixjwbRCqUZs5CmhOL9EgGmgj/26ysZ0jCMvtwDK2F7UktN2QnwoB1S1oLmpPmOrFf/CT8ITb/UkMLLqMjdVY/y/EH/MtrH9VkMaxM7mf8v/TkuD1ov5CqEgw9xvc/+8UXQ/+Idb2isH35w98+skf/i3b72L4ElozP8Dyc9wbdJcY70N/9F9PVz4uSI/nhcrSt21q/fpyf6UbWyso4Ds08/rSPGAcAJs8sBMCYualxyZxlLqfQnp9jYxdy/TQVs6vYmnTgEERAfmtB2No5xf8eqN4yCWgmnR91NQZQ4CmYCqijiU983mMTgUPedf8L8/XiCu9jbsDMIARuL0a0MZlq7lU2nxB8T+N/F7EFutvEuWhxf3XFlS0KcKMiAbpPy3gv/6r+NIQcVkdlqicBgiYOnzr6FjwJVz+QQxpM+uMAIW4F13oWQzNh95KZlI9LOFocgrLUo8g+i+ZNTor6ypk+7O/PlsJ9WsFhRgnLuNv5P2Isk25gqT6i2tMopOL1+RQcnRBuKZ06E8Ri4/BOrY/bQ4GAZPE+LXKsS5jTYjEl5jHNgnm+kjV9trqJ4C9pcDVxTWux8uovsXQUEYh9BP+NR07OqmcjOsakIEI/xofJioScCLW09tzJAVwZwgbQtVnkX3x8H1sI2y8Hs4AiQYfXRNklTmb9mn9RgbJl2yf19aSzCGZqFq79dXW791Na6an1ydMUb/LNp5HdEZkkmTAdP7EPMC563MSh6zxa+Bz5hMDuNq43JYIRJRIWCuNWvM1xTjf8XaHnVPKElBLyFDMJyWiSAElJ0FJVA++8CIBc8ItAWrxhecW+tOoGq4yReF6Dcz615ifhRWLpIOaf8WTs3zUcjEBS1JEXbIByQhm6+oAoTb3QPkok35qz9L2c/mp5WEuCJgerL5QCxMXUWHBJ80t+LevvZ65pBkFa72ITFw4oGQ05TynQJyDjU1AqBylBAdTE9uIflWo0b+xSUCJ9Ty3GlCggfasdT0PX/ue3w16GUfU+QVQddTm9XiY2Bckz2tKt2il7oUIGBRa7Ft5qJfrRIK3mVs9QsDo9higyTz0N9jmILeRhROdecjV44DDZzYnJNryISvfdIq2x4c2/8e2UXrlRm303TE6kxkQ/0kylxgtsQimZ/nb6jUaggIXXN+F2vyIqMGIuJXQR8yzdFIHknqeWFDgsdvcftmkZyWojcZc+ZFY4rua8nU3XuMNchfTDpBbrjMXsJGonJ+vKX0sZbNcoakrr9c9i+bj6uf6f4yNDdaiXLRhJrlh5zmfbkOGQkosfTqWYgpEKdYx2Kxfb+ZDz4Ufteybj63LzVc7oklSvXHh5Nab4+b8DeoXZihVLRZRCBJuj0J6zk3PtbkjaEH3sD3j6hHhwmufk+pBoGYd9qCJEFL21AmLzzHHktN9jW7GSpe1p91X10Bm5/Dhxo3BNex+EtiAFD3dTK0NcvT58F0IFIQIhgLP6s1MX8wofvtnPX1PQ/bLAwNP+ulKiokjXruRYKzTErNjFrvX5n6QD7oiRbOs3OQUswDgOxzcd+WwGZH1ONZJLEKk2T4VGPrrdkN9ncxP/oQ8UFvRbI7zGVrpNjlniCHT6nYmp7SlDcZ1XmS7tm9CXTMumh89LnaNuF3/wPVa/NLSE195Ntstwz1V2ZLc/sULMGaL4gdF3src9sR1Fh33/xiS3qOrJQlLpy2luR0/y+0q0RnVBBBe4yi4ueiNOdNAq/pR8JehYiEiu7YVJJcGBNBHlCOREQviO39dwxTxdulwW+UOO+OrXOskQ/csaLPIKxUOUHktlUtch/SkuaV5QD2G4vweAaCoSxMZ8k9jagIRR/irArsMUBBkvwQBZj1NYclQ1WtdeoYsd38CObL/DJksETohDEy6ZCixViSEPvNKiV1SSCwIiVk0dPGwTZxeNwPoA0BDhYNc4tIkej3DcTHVTS8W1vYFlURRUS4k2naQ5xI0fseTRBHJQ3WJ6Tn45afc9k9VffnLeTH+Kdd9X9Rnont4E39i8pr21YM+umrbIBTB8Ex2jNapeDYMPaeXACP6jpZnFy8NEyG2AF+Ega5vkvKIWjidXnkItArCkmeU63Fx+eg8KiP95JfLbUQus2hJTKPeGTz9b9A0TJtnTVcdJW15L/+3ZIOQ3jeoFsEuB9IGzxFY52ntO1vJvNdPQMJhXkvTNcRYz7Qz6l09rNUNGbfVNOW7tQgzdp42/0sZtnFW0+64nFJ127Niq3QLT8vwHYw3kOplK43u3yllVjU+RYv76vu3JMghXWGsSB0u3ESlir8CjF5ZIflzQoMn0xbP3qWknhPYHTAfu11TcndM/gV+npAK5/yKkwjnzWs5UXGXJHwAFo1FU99jtfiDBlqk9Xmq1YKsy7YkB5nOmw6dy9mjCqYT72Nz9S4+BsTCObdH/e/YZR3MzUt/j/sjQMujqJNOqABq9wAJCDwn/vwSbELgikVGYviA89VqCQjLBkWsMBf7qNjRT3hPXMbT+DM+fsTUEgPlFV5oq2qzdgZ6uAb0yK/szd/zKqTdSC0GlgQ//otU9TAFEtm4moY7QTBAIb2YdPBQAqhW1LevpeqAvf9tku0fT+IfpA8fDsqAOAQxGbPa0YLgAOIZRFlh3WHrFyBDcFLdrSJP+9Ikfv1V16ukcQt9i8sBbU/+m0SAUsjdTq6mtQfoeI7xPWpsP+1vTo73Rz8VnYLmgxaDWgOuNmD8+vxzpyCIC1upRk0+Wd7Z0smljU7G9IdJYlY5vyGTyzRkkN88RMEm9OKFJ4IHwBxzcQtMNeMUwwUATphdaafYwiPK8NptzFLY0dUIAFj2UVoHzUBmmTP1mWCmKvvesqnrG3hj+FHkfjO3nN+MaWXgorgAAA6K9IXTUD1+uwaqHXsEALRgD82K6GVuzjQznaC89QI2B34wNf1dPIwydDO38xCsAKCdf19/ePn1xejxPZgLmzLlTLvloYWMde1luC66/CFwUdwGF5iJ4QIAM5jvbl94r6EYr52H2W12SlcjAHBSzoVjusrp7UZh18Z/J+vwjQccSS/JBNE2b1adygAAyNgJ5P+bqz5+CPu24bqx6Gjcz84IAtVx2VEyBJTqrocOCI9I7r4vD7cz9L3AGZ6DBzEu36w6fQsAkN2IsmzCZWMxqbMTE75ymnyFiK09l327D2K9sywTANigkEkmLwTn4RqDiPxpy5HKA4aeYqbSoi0AUAKsGA5go3ZXjR0qpUsAoMWolyNxzyiIPZ+qsEM7QDgbHW9WJWwBADq5800tDEPPiPa6ialFj0uNAEDJEC4am4A/oPGPxmDmXdikl4cLKa8CgG7265rxY/wjtmbutfwJ6M9Mer8dKHyeZkalbAEA49jkE8MATNz+qKwsMOlGAEC+lkvGJh0ds/j5uNtg3tilTY+NTe/JnqF4N6uSDACAHKQP1Lht8vSzU7iEyzPjut2EPs/Y38IspIepXm+8s+bS2w8QPd+8ONuavlmV3gIAJLA8T+O2x6fBKOJyYweNq/YsVtd2SjETADgxiwkX4POo7fsmuHnc8rCP05hqlnABgBq023MivCisNnZRtK+sru0oXAIAK+fRHim5pkf85kL/YfPLQ/xReQkXAChjtR0XhfDJaiOHaB9ZXctR2AQARsyesDkUv0deoTWmffvT4f6SYAUA6+xXzrX3Smi6X8zthH22b/w19LM0XlWqr0rjAgAWs1Wq4T6AhPsAVGoEAAa5PpwVKjiHWlfJ2TZJf63FjF8SUG6KBOOL9A4PW3qOHE295pQyfVPIvxcJeU+CKduBk6Q+a2BAVtKhf4QnHrHLFpj6sNDUDvhCfNPmtn4pdDSUkHE1wPPrF1UvkQS/L1S52Zv0Sb/r9YK+jx51oWU+i39Owb1p4MDw3LcwvjpMvtDXPEWBlLcw4DNpOOC8f11nKez61/hc4txssbudIo5lL+aszAI1EiiSfkCetqOyBs4trCbou3jqJZ4diL4zvDnDBRgP+086X66Tvj3JOY1rJwmj/sJrubDrVb32PWhOs6BN+sJXQ+6nOZJTgPRg4PWz8sp/wWI3wsGBQoSU6tr0dWOkrwhDNCN5mfGAM5vfnawcoCdm2CdzIN0r72XbbDWqjom1cMjYh229sPnvzWLZAaSiQR3bSL1XjCwFH1wa4ZmmLeiaD4xutxAZfzu0FwMUkXTsvb7SX7TLM4zwjGg+HbjiaRWI92lgwaxTyKgiXbnThL9j7uBDihzuMULvXXes0e9x7PwRK+6mBLGD9z7PAt7b7va1J2EHu/zZfZ6JPoQVd849MZCk3RJOxd5Nsxi+O0lUD4Pochlk5+4naG1j6yiVRKBPobLOad//hDECeD1ORiB9M37JsSxMC6yAkKEdy7S1aRmXRGrLECneqByM8iQ8x6d71F1uhkYUi3WEjh/A9Yw//HCidh7pl7XD8vEkuN/f7XQ3+fhmSfR/9fHkNcRp4qCD13IGIBIAsQXtoDUnASJc+5H5f7YWufNDdZ3SiHJqVvKw8K1RNB/4mJi3YzQP47nmN2cw2BH4yKk+zk7wcLx2bVzeS773YW/7nMg8DMlWZGeYPJ8lYLzOnN4o/0fk9Fb9upq1yXbRyN7iDSRnOnj+kn3vLjHbn3NmA2tRwcfVd/KHGxPybUwcg9e742hY/XBtEgCQYe9Qh8t8fte6aEo1Lt7a9rryutsDxLxo0o9/lhdL/GMs9n3cCxZiuv3as0lchJm9dQGckDBOT/R+y2ft/W/eswB4NFnsqcrBTerQmx0BTPclttiZPF+ctHerFc2RW9MJzpuGOShqyTLCNsCjhPV3EtMF8nVQf2TL6GzI6EphQEjQgG6JrtMu/0zWg2e97o/uoTIf4ipUvVVM0KYey+VkMCWrFynVZh/hpTTXcm3+EV7yX7W6Ehrz8KON4P9MrENJx2msYomlnUT80OrH6Y1+KEfOWn8KyenbZuHQkjBZcDAx5+J64Aj6TSooLJw3anwLeZGOQeSSPXLe6dVY7MF7HhAl2HU9fwES3l2dLETAm5btht91AwjpdUoQghLn7RhAIRWFRVWJa2Jtc0Tm+dHRGiAvx6wG/OCGa7BsWuJ6U3LwfOzSY5qNsj3Qpt6+JyEhflEfl2YZ7jhjJ3y+3ehNh4IBG4eEmVuhYdlx/EQQvnVDqC5Lodj7NWEXjMFyT14tjF768alhticUJrdl3w6P7cKsF4rhxIKWxOSELDHpzaBPR0EgNZlKdZrSiJfPGaWK++nvRxwoo0gt4maZU1CAx33oq3e+NirCq8K514FHpLc0jbti5KzNlr3ttdqoSeYKrOsq+jS0w4q5Z2AMeYnbAgCra8oCHFF0wJ/PTdXUMVyIdTRhS8cJZVr5dTMliVhKm9/TZduaYLTA346l+ILCTo1es+CVq/f+2MU+XuX47AuupenBsoFCNMV/2ywHjCr2flEAWipfnI46tqmjq81ytF7IWoydKyHCSI4ew+k4+ATvUzq2buldaR6SAI4VKAMyMT7zkBkAMB00NLbwmtJqj2k7NAGAqHKufA41DAksWEk7A33esJTuBprShiAOZCMOdd72+E7b1umdzQCSOsdaB3BxZgCAIhUUSdbxYbW7MfnSRjQBAOeidlz5FgodFOhlNAn2jcFu6KmERUygbnHGMpnfdLZ+KTEVgF9WExaIcJy8hr/tp7Y+ofIvp0nKjrUMZqLMAMAsmaCWuxWW9dpVpoxoAgBXKtOVhyhPGCAhWFJty3Ija39F5udrAvbBC+QD+d2Qpx5Dhfh+FqLgzUW10AwAWChUQzuhruPOnJ3rUZXMdgmhZDvzdRCfX1UCN4/l/wPrk1X0qHN3KbpjTKBihdxy04nZgZFKr7EcDqvvSSpivzg7QGxmssgfLo5KZRV1TZtdbR+k3S/kYjTNfDUZyWrcFtxkiVhetaWfvcxumYBgVeSozNkvIgSbt+L/2Cl6TuiPToNFUi3gzvnWRxo0ES1a/Wjq0Zc47dikmBBXXE4/cj/BEnTUGU8vsXsssBsmrEbCzB27QqDQGPdcgFpmIb3VQSk9zfTyXFlADILp0V5qUnuHn2SAu8QszfXheW/UnD34sJXHTECWUYQhLc5QozwqlP1qnYO/j2pQmGU03C06s3d2EjlIdLNuy+Z0X9GIUUWCXDpwtAPYI/zXrF26ADyEpyyj5o5bn4GKoyNdkhskDGYenTTQ+fRqo0EL0yIqcAfyVOvo2jq3CjCRKOLgRzv8NZ30rd0sMLzpKrIwt866C8KrAes6AeYvDWFOdG2WjV8dNiG2wUyaYIU3T/cDo3COPFw8EPEFcIZAcCNE6BpH0CBPxefguDvpbTKPZF5TYE+uaLtxvaIUB3bIQI6/yK34JNzrQt1az5ucZEtXCMlBED4lW3rAfndm6l/kCGLzwMc1jaGqJo9VNR0VIO4dMQMAo+m4cpFwrKQXPzW3czk7Vehrc4bS6j+UCQBQhrljlDaOxR/+L+5R2jt6Tz+GWNGIJbKP1cd9mk9gzEk9hjdUxnNNvHTW4dOvtRS4MRoQDFpUwYuR+pe67JmTNfNtDqx7LG4zNLjh8a/7i6F+adgW4ci+DW1Ilf9ok+1zg/3+lfN6pK5X6QelSexeWGj2JnH1ym6sQa173zvfno297vUcHC6hAoTC/3enX+ej+9JNHu5RQubQD4++jHOK2fiK8Df3A4QC1LZSDmK46S0VdPvZ8VSJnWHbWlJDsshRGb3dyRkMr3d8VnqqBEcrMSKUyBqMsk6yUayfov2tM+rgwqxlrsiFu4pvawUNfFtcuWrc8FmGXzmz8Vn5LxfzeQoLfUX/JWNR9xC9tZZamjtBesX5eUAqtw7rpFfDcdbgXsMcsICLg6iqrNnoDTf4umgefPn5ZdXLAEaKmKr9K2jWq3EjfHsxMwBg48Ul4dwopQnV1GzvwQsXaQIAGfxz3b1L+LfNKAGAuxiMqmZyB+AYNU1XTRJXly88AYU39jt8cP2yet2jRRzcU6scgDEiEryUmuE0/9XcsZcfId18ZowZMT1Pn3IAxpBI9rrhhqfOkyl7L398ZNuIPH7ElH1o1LGcrV7PCOR1IzMAwAuoc0mYU0VR8SZmewtvuEATAGjx8Jyr7ndZRRabBAAakrqa1eFyutex5al/HR9+Pg/51BPSD406ljMQA8pRvJ9nBgCMQyre6J1RTDLuzPw1pAsbjcEeOqQ1rdTmu87PE3XTX6L5Gyznwp9PhH9fPkpGQ8UNREgtj619rgZb/3wPFNQVbHc/a4jvwl/8oBKYjqAA6N6ujHBoGb4ATrvhNBnDILjc0CJKnveWTCZsDPoCAtX87ot1zaqQIOzniFoY5+YhQw5B2c/phhnSAZA9ApFkx0IJ7sCLThlPpxnHyv9oR13WpgPR4gUqXIl2N4nXnTkJrp58Eu4njBlKzTOEZg8IxnUq8+sqOnQo9N2SE6jdRZ1z/fsQ3CJqNvCck7DRQdc3RveF/dc5mlOPI8T4uL+oz+Z8sJ9wZo/NELlDNct9N677yFvr2oYCQ3/83EfWnj06lnR27o268AYQhVTPo3RYYPpkhgyVUD50TQGcbIPBCGxagjGtFBjceJbYSX958r3v5q3JbgoA8LXamYl9ce+UOusgjorz1/LGw/LsWuxIqVZLUflBNNzqe8wfBnngUekITgge65Xj6xD8Ero1H/HAEgzxiww6j8ZB7I9hA4PQLxy2xTCSF3tJ/60ye1nRAiEhHZjEwgdaaD7HdmaDiTG4HD0ArtUhToud4pjcKlanIcEUD7j13JTtBA9u040VgeqfcMoXejWyk7YDcHR0TNJsYM2cyGylQEg654jKROckKeaXtByXo7DqAQhhd+e41CpRPIm6zoUBBU30L6veKGoHUvVujt12wrswKY0GCX7BAJ1ePs85euedVbtDdCFD6u6HVpjhIAJuyalS4D2EoUBc+OfKne64AHj8o92ql+v1XqI15bZv54pNU+xgh2zxoFup3vOQ40Jgk6wnrxfKqgVYJ8SCL5iRzYqxfYJEKQ6I4V7umobUg1tBdDZCI6wYso5GIsPj5aztuwBIib7SFoG3neHuUIkB0omw3HgYMqAVKWPKX3j0zEOeXOXa53uihs/cCwK2zTUdWfmdaBXGvP2ca3oubeEUEhTjUTjLD469sBTbSoNat4Q6NAHDoLn1d7TVHjJAmwfrggxygS3ojqv4siKiccTvzqizQ/sT37uxiPOJBH54kEryjipahqC4WYQ3Ztrduw39FZkaL80/Kl1M7mFa0VRxRoxS2hASYUpIdRLxT54CSsaACskZURcD6T7DueOjXevevtHYqtG2ZT+lHHVdNiMYIjJ4fu/nmbJp1zaOCONKPSKaP8J95Ije8V4Dnzyb3018HkdmaFbKBJDZMrXEB/VBy2mXVnq8WJSTK8CQuWPax3x8N3IdHtP+nKkRuXSj644Hnl38rAj9tk+2VVRuWRjNa1nsrvymeydN2VmUP4vo65rVvUozV8g+vFK0Pl3TTFjraGzjnpqnYj8fEn7y8xRGCb8o0PpJFDvkn5OOcISVLmQL98k0v89Y4snCvN8eEeM3lT34MjVzW2tBDx823AnRhLHF+wMcfn1USCfNH/y2+Nkmud//9f0xIbj11Zu5Zj4+4VjnVY/3brOKzwL+ejBmAOA47WPUljHF/2vcrorTjC9qauGcdjWqnl4Xqn61TABAfHiRvtpVT/BXt6udWv7G98iwegCujaC1eL1yhl59ATcUPRL3AaIOA+I5uupJcT1P8HWp2/hzT0Sgulz3jhhpRAGwRce+/k0LmNKMTfgx0HDnnYCoD4hwwcoVOwxDBCUhRKsQoCSRhCue2/9c9F4/djN/iU8vqQQAu2W7NleXuELigy7hrrH0ugYBzkBDFOm6hLH5gmTFDrY922J2jrjyFiDRWEKvovHJtvocMB+GdcfEc26nXAIxds31Zvyjgg9jDEkcu356cP45FQyWQ/2Xr9D3uuWTcP5rnCe2ZJ0E+rAzmSuB7q8l5kKexhJKIEgrqufzwt4z0Ma+6Z2Tc87Mxal5/108FsEkt5OMAUkkyPVYQvnEFI//BZi8mLGfYTCJKmKnPSOjj6PKKtrk9r4yTzXtIoLNfgCFXbO64O3y2dHOc0mB/cn4z5fkuA4VivPPReLcHVz8e0Cn05dLt14MyJdAU5yPV1oQSPcU194ylCH1I3Xt+oTMx7XGZgDuxpWddWvXNDuvgrl5OdL1SFnrVEM9U/0qfyz+6vo/VODmhzpDG/dFXZtJ7jTriHeSCKPhhLO5/uYBuSfw1POp6E8u60XdpKOROkyUcoWjqimnNyHhPDDdV1/7ND2Bh/7aiuxpFbYlYhwZNrk3v2ylTvyNsFmfuRontBwiqKx329Zob7jLYDIb9PrG+AWk4nN4QAF3naK32CroJjFK0dzBGBdbhqGvOwlO4Bqc2B+K8vMn9SgTYKOTXQpGthMF0aJQHsdrTiN+fG+eK6bKky6CiukeqBgoB0KYhl0ngc3MWhYQhR6ULDmmmrqvURCguRGH+xUW59GyJPI78e38CbKxEQpOnYlmZUheRl8+5Orw0KnDEZXpMdVzYEcr8V95gf54U3cS7adnQVQm9yAR5pkyblumE52RaVLbIouY4WxcNzoLJraAqsbN7CUaEyQRtqm83YVxgTXFBNPk2z9SfS/2mTSulgEfWUOYmQEfiAaWnX+P0ezKFz1BzO/T9SX4B8Sm7NUmDnbHI74izpe3Dq/k2jqvsxNBX7keI1eux798aA+Ee3pag6xpPDa7uIun6dXBDb9xrdpAFa1TYvlj/3iacVrXUYInG3OQv5lASKQr6Ok3CWTOFrkE3Ab4lFR8hbY0DZsgpiXw3Ic8YccFXomJeuZ+zNjq4CmlxYhcXQnrgtpWb2S+JXEp5JHh9APA4IjKN4hdm0qnHRzhSFfJCcOkg/RinGMzwtgNDahb4H/uNWjrIexsVRC9uYlMT3CCWCLeq12rSi3BlAQrnIAdFhL2INatBUy7ruc1TE+6eZ2XkZ/C6d6+CJrwouvF0ghjWDogxPbgxotmr56iGJoKnuwNF/VWHb037trPU+K8a9PCmGGWrqdiVkSOISAAc7D91xXG8Svq43DBvltxo/jeFylAbMWcCDXDm0rM6DbyRvFtLzAazwd/SPi1x5/NHyxHgX5VESDDn1tRHXzSlbjz2ulMvtv9Dp+Ic6KQZ3edNwa+9iZsx7kIwYF4aRfPuiAwhoYbkgvhVzlgwfF3Z5tX5KgmwkDs6AQdqyuZv1U3sFzdM7UxaJQ6JM5ELO+d+/k6PEylnYrwSOBlurpS2rECSHSp8S5Sbrm9jweZ44BxmkOBY4P5BmhH1PRRkCRcXYG91K0JRzOD/B1vQCcHf//8atBI/HuWuilLAbut+HwOMwBwqaIhe73RUkx4vCmUs4j6ALwz2cUa21NgLwszAYDj7hk5AvfEbG4HnKsavV0z2HZTPwBwNCiFQ3kIus/yxQ2assWZAi2zvyzAEU2C3XdnMwLHq7+vztaFd9UtqeZAqkKXkjoBs2vNdgByZS2cA1XNs70DCmO/0wQp1xWZZFWF8W3oy6uDaQnLF/YRxHk4rtJAAui5f4zymPhhpt+bgyGzSZdePfx3cSoXJIAuErW2pSJav7eSO0FL2bOd0eNgTenDatV0qcMQm4q085gBgJZgp6OlHCwNuT4pJjv46ZFji8t1ho8XaAIABIPsmTYL/HWV3harXQv7AQAWvtqIyuK3dJ+Cj9PGMb7K/JvB5xoGYzzTeucCQeXKMYa5Jh9EzhnyD3aGdQvU/FS1qMnjkPpyqtBQbX+HZgCANU1TteXcz9EMPZ0a78Xu1gxoX41fMf9Gx5SxOfgyF43WlePpTPS7KysCZeKjhxfH8OR2QZTGU8btjQNsDjEviJ5zZ659N/5Cs3tCTKjmg9XhwU2AieBC2CpJAc9MszqjvkvHbiHW4L7rMM9qMRXNBirYkwJvjoctYaKk80gNWxIUK2xDd1rykGGMhRq2glXBCIanrVbE4ctMSCncz7rDmN8J8+7xEr+37HpwPbbLV7DuIoUNODXiuNOYAYAdqqXg3NFSErZEqkops7NsF4dEt0pzJgBg3t6nyOT+ujWUO3o/HWboODheW/ZPjzH7Y2vJl5Vf1yz6cJxee134g1HHKtqNR06Yb1afnVoMAHh1fMz7KJmMuovLqpY/VRzDP+iqbrVar9VPSZxLCflzMZyzGDZ8juE3iuEfdIFWywg4UAxhvkt7H3Vz2Nmijfg10C3pDCGbW5HkGR033VTgXud+mVEqiPa0FRwBokdONicFMVWtN2cDyUBXkaaL5B06Dqt35stna5O88Hr68+Z+0vHQeOL7mZXCPby/RztHkz1eoTOcHLwcfGzDjP9lqtKlou5FzABAt+Kmy07cqDp8+QpF+lRyz702fCBvwQM5RRMAiMkiog3HhpH3/YCarpVzwsDVzQUBQNA83tWEAQVHZpGCKOs9UgWB0sS0CoJt+jEqKJxR4KigJF3udZC6mslAYLpqlIKwZZRLawYKHLe1OAacLM8+C5yT/b4tcDp1RVdidcVxOsa8Vfh2fiRZ4tPLrNuhQJAAyu8f42gdo2Z48/uSo/P29+J71n4oGiSAghLF0zoExPPe086JT6uNadoIQf+UfWOXtuWPNasWv/o8ZgCguhluxCuXg+UWd3uW2hGf5Yq3s0gTAMDia0wbFX5SKZfmYVwWGgQAHXyMEWXhV+k+Ar+tjd34iPkX4kOGQRqfp70XJHXkjm/sJ/ruOb4mSeuYnTfjCWFvoEcG4BwfnEtpFvRelrlGIum4+DYYBA7AtEQyHmxHxTHP/CVxmr/Sp7QXobUx4qP+rGJRXehvjg/uZD3fs2M5+cf7E5+fOPC8KOzGyYE0ZYwhuF0MBVh+MePAVk05a3djJn7kqrUyvLsOroqbM46Z+nM6JvdaGsEjVfwqoN2SfHc135EyJUq88XZEIX8I5nbsDEklYj4fVQqmNM/LjlmbbOv7O+qij/N1bqYrmUIugDHNlrEKYJjRKVYXlHSPdfyGYRC+RPqs64u/jo2ougiKUNbbpI+Db/x2xXsz0rs6VPAcqFgWBi/RYfXDhM5Ens0FyhIjELEM6DiViir7E6DJ9dNP4HqWVSnodz119e7ebZ8KbVAEGh++0g/ApiYn5VRNSkMFBkNiOgyUXPxXrPkCEEh32BdBNi3O8TCdjh1Kx36Mgtx2wdrve3T5Tblwg3Dy+gFH1Y8bEJ4Y8CpF3f2ifCSfFN4eSp3qgkZwRVzRWFGKT6KmfJbumRyGcIXhjcutiG3UCPipFIo5tES/QJQ4o5fA1zjdnptOZ6UTfGNOqVAk55iL3/7V9vAJgEzoLJTAOcpesyuSLJ9+IW+7q3ToWSR3w5Y1jIGVKSSunuyIIgcV81NlP/hsnTQRh8qFuSJCUR//D4NH89aIdvtqj5KNjOeCsW9jtsu+p9no9a8geJI1GJXPffb0anRpeUfz4mHRTMBWKl2PDpgKGxjEFyPzEZovmYVbBJqzI/RTaIuAbGwW7lIsDnvF2tLp7Hu1b3qfcsk+/G3PLnDBtaF3JHFxcZZjXgxceGu9ILgKdVl711k70N7xjW3vWAcAGE3Dl1+jmMZYWowjir3aY4c8NRZirPY0Ev1+E7PCsPpUUrFDWx5UL3Rodd/wKDQrtaeR5aVhbA3ILyE3ZJhjvRLYnEuAOyGwKzeB1SZsOJCWaGuT/p5rkM+b8QSzB+lVCEqxH0kxZyEM08yz5OVyjGpfkg0zhcnqroQ1mRg3mTReLxNIU9elAcNGtsPJ5lXSDFeEIunTdwmY2MhZ8LoROcH35TLh3OplkQ6JJnwA1CB9d6SN0ThG3scVgT6N+LHBf3cmMBRjqZn7XbXIGemgb/Xk8bt/mx5VZe42eAID680ptynUQBNR9Rf8HbSWhuPaSJA7qG83SvHE4ZU8OEZqIpGXZ2GlaMKbIbq4uiDYovInRvGODQYcpAO4zgeB4dnzqV7jSqHt230tB5CUBEsE9/4cJkpF0SBAh3k35zXTHvCenvz1Ud2TezFEu6rBNFZnsbQrAZqU7ErkypRSf6XKqPZigpk+a+0vsVaED2D3JhRNwxIY2pE+dvJNX6SJNv8AiFzDxFryAUsX4o48r+31f43Yzj4WI6eSDCeJu+GPFvJDu133wd1RnUutlzOH90ntQT/X7R/amKrLW7A0s7jEKi1VMJ5La3AvXzgwxMrp+bww7wFh1HKN3Xhvv+lKLFWQ4sUEOD0zd8CG7eucPfHjJI21YN1vyB1iSH3wVqtyGD321FZKYMEewOQgYKGh26SN3RxAK4uhux5ehCjaQ3GjyCMS4cIeECSG9Ami/Bv5lzzDc4SKixDRO7muxtyUi7xbSGtZIACJ1BYtKuVj8nKICZEkv6tAB0p5TtJpK/9/XVrKVqIC5Gn5Gl+0A2Rp6qk+LbeXn8lN20x2VCwnMxjORdqIQiITNmlKN5I4thKV3Ze3OPhGP46gumAIlPrjldf1dBKZVqhtblr7/oNQt+T9uE7exCNrEZu9oghu1pbzbmo/SpgGJQZbzXpocaLCH1LDy+GH68PkYGdP4CubBJyQ1g6E90ERC3NTSp0QBu/GHRqDgqyK3V2j9dxCEcVLFpXzSIB7on3SnT1kN8WtZr7ekIrjZi5f0VjZ7TRFA2LXcUfw+v714j3uPV07vb6V+Guqzup7wTfa5UOr6bDQ1T3NbY5CGPvUfib/szeX2BjA7h6u+ioHp1/cw2IrfMVok9S9Z7yhpsnxkOmq8Xo0MV1RmRf8bpBvDNH6cgLW961Vv5SeD4Jpn5HEoPWpbBq9Bpna680qtL7lTEt5D8J1k+uhkho8aCcB6XQ2X8v3eZNlMhvyPqR7PLF2hJCMfG8uj+rFeMWAK3akFPtO/o/VbnP2iGtkR7/rWe7ck92lDvk8q6oXiA3cZktHYFYSaLq/Wd2Evot7Yw3RHQToOu7B9UKkrATgIggmR6iaaXml2a1gHX2n548XA7GA0NQHEl1jZVE8ujv65YK5p+tg0LLvdzacpN/toxn+ebxUhZ9WrxYP/6fr9Dd/3jKT9qPcwb0ZHjwa/vmHOeZ72aED+8NvjT7aj4YMnL9DKEMLCLsQsf5EarQaDzcmTWgys8xKOyFBrbcOon9JCV+wNpa53kzxvzJ5O7bVGIgO402v5IAgHbO+6RUbSNbEWEGK5hXuh+Ctu9QahUtfNk/FnItXny1lltmcqOehqOIVT1blWCfzlpMrYeA2qZwB3KGKD+QmDdOALt20yVYVTB5tTj2+GmMDy7xkk08/ezZRHkiu8F0SYN6kOz01gIVGhx4PnxMBNNZ19oSmZ0G7FbhqlOWIIN2tq4hR3nQRsLN+eWFM6eCpGpYrQ5lDB1p4wKcLgCNRIbYX1syQAvEl1a7llGiQmb6ECq/7/nV3Xt89iAoMLWoQN9mTtC42bTObuALCdRI0FV310Ea36gJCuyQ4X4E50iOCXlEIKYZ45eU7UrnNCS17WqO8MCAmY/Yand6v9O4d4kmT7ZC6qk2ekv8GIkgTdUVpWwTWFjLkaZ6q9fkiCDJsYM825A3DCEUh5hZUZGJFNwjUOTlKo3HuGa4aRV7sQlx3cjhkPGRIchPPtePHjmm8Ip2DZR/q5o86FVBaF5Sk9XumrXpwRZPTIQ8bJxNId0kTDy1nEIPjmvYo3kUVH3D7CVqAmawsvm8JH2Z8KLO8/ycLE/DBQ4WvxhWo0Pph5K98UQLfVWZ/UytitHvuWl11gNnpSwBMZijoDMvuarjMIyi2buz2w3nFt2lpdsU17X3m7DfPdSAU9ozBqxNBx8mWf4WzrW5IfaqvHR+vH+6YsTi6rz0tLf4aYgt3gu05+/SiYYq5pqhILfws18fN2XL7xjVL8jw9EWjAFXcAuix8blRIvBCOgrr//dB0izhF6Q4oWfD+aK30NB7cqT/Opn3kXl2QFB4JyrpPrPt0JPzeIdIfbzbr/hE9plcxZZnOkVdFV/zSp8FxdslyWpjEPNJJXZ1ePgtW8Q+fbzcSjnd79KdsHHypr2ZwICYguSrAJJFHlydIA6Ttjc067yPgP6S3LV3rdJuwzy3VURPPHcEuBE9RKTDdFVjDOea4iMrycYG+WNjo2W4TIQg4t+3bQ0kjB2yZ4EE1MQaEyWQTd7kBeL8RFGoyLWXUR5C3g+NeYxfCxVsIvZVoBp9HFHTUJCbXacDeU4pAR7s52EfaGGusTdyg4bF2zu/jkG6jO2B4phg6J6GFn4PPaNgei5xBroUV92Oj5wuQfwYpJO3/plgv5Y0r80XSsnGEXuAWiWmZmY1lsQ8US4K1dYzPRcTy5Jlxw4fYlmKuVWTRbRMYKmuw1I33DmDEq1P8VP92Od4QKQnw9hFYWJPYbHR0xKSftb2WMjZ8tBAxQRPsko2tgFd8fyI6MCWnUbiNYeCpRs+YHAIoP5A+IMw7ilfD67stGzBQbPe0rkPkdzvafekGuhsTZkCc1If+8DSkV43eb9zvJrl1ePyIq5kn1iSK48mmVI5s6WKnHAb87PJYKWmHAK/LiVmO1GT1IDxFSZpp6kLIrQ7z8uqWdiM1+HzjCOwrqHqwKVQCrrOeaQZV3Cn2NWhvzqwXdibTusuLztkgAGUlBxHXhPHbYl7s4t/uGwwBytV2qw66lXlF+tFiQG8sAr/l2+r8X+oPmPxVda9IVEtMFPehuoD+szcvsVuBjanjPfYXvZ1sY08gp19W6SxEGa5MH9kyBEfRetwvbGSqFojHD2jSJn5jmQ3OFTtWNPaj6WgL4LGDmfRvLGMwm5o3lTJkx2kAkCf27T4iS0PfW7p0PeQeHjoPZ90eKsPWr9dxgOSg7PKMbAB5+v0/X3SUGA8BZjFKz+g1kLfK4vgHtHa9G7ODeBAEKJ7NZ+pZtitnlTsDdSbUu3PeQvYjt8EhRO0QBPg22kUkFv+JRStiXAXYTTqYAjjf+cCyqr7UJcxbMM371xP4jigI4Kub0l4rz7G2iqZkzSvv47XPVqmV/l/qyRaVUsyrWGaB8Foer1e7OepmcSpQxfAbod3dnOIX4z27UQXtQgJobSIkWYTYZkjCAP37uo9WcCNqL9w4NRW40ADhRMYBmRub96mtPmEO9KOezoayE3UFzDVvk8YxLZha/Bzt9LXEfY5sF/FVyV4e+iHBKpbaCoIB/I7Ntfnf+qFO6ZQlYjH5ecDmKYSk61/ngM7IN9BaZKepxqwDSNsMK7eQ/gnoyGTVPFcPQgoPz7GMBocsvBftsYYjogrg5iLJtK+2TCKSnAt8VEF6h8ypqi4A7HaAjqhK8eQZOfi9fjaw35vff2n6/3Hy5fs4iRuaT43Vwu+NN/BLTk6tyTyTsd6o3OFwet5g6ojRzhtMnS3peiBHGEcGtg2GVTrJWp2gIFIs5KPyrAophV8Onw+qo/HH+YrmB6vkPieGt7VPry2xQCKnJ+lVCQrgZd0AQMCqvBgQp+mYcCLJzoVtart15zDIVzi0momismLW61a7tTrqbvnlGgR2GxHMECE3111MlUkwFXYtx1vcYe3fbYFXXPoPAKAoMCf2s2xwctbtusDZ1cPHEXsrhg3/zviTN7gbp4AtQqyGI8COwAUt782BS/OxOwDrfsN2AABVtfQvvN+Hai79m45zarWdRnmo7b48HqADqqPphAJOcVWmE6TrpjEPAGAPOIiNuy1QkZ2ZPlALnj0c0LW8YUJQOzVQI7Hs7nij+oX37OGikkz/Wu24Xl39/yx0G2C/WP7edwTWwENB1ZgUIXWF4/F+Hr/JnytTZk0+iu+3VNsAqsF0OLj5/sh79nCxF2bkfPhkWvtMijpO7Xf5R9kf4nyPCXtlFsb3H7YCf10Rc171fYX4MvixfNsA9tosnsxd4BIi9GaGT9iv+W53tfpIK2XugXoVRKRQcdx53QCAj68BNFTUdcqnmZ0LqS3ukg5q5isckmNHUVkxdEhOiVRJXISuGBHtETFhrrvIs0ngCmrX4y0mW/s3YzC3S/8BgF4cqD32EwR0ZN2mDHppiwcL+sT+RgXMwSnAcSFsTduP80FQBb4rDv49Ge9DKs6aW2psI90rV4gcAt7Eced1AQDnKIrYj0f8uwKmfu8wMr+ex/at+DweCrbC59l7ZD2HUL4oysJnurkIaug40ygE01hSAAAwASJFtvhpiPUHId5mMwgZ6lpROiDZvVwHAFBCCGOLuZhnvWQqIkz3JdKaxm5xUzevRXZkZY2929k7imOvtveTwVj3lH3OvBEvfIB4tw9/pcogEIS51MV2nLx6pta2ufndi5N/XyuzHOp4tX07VU0OQJPa84WmSZDrrfWbtTcfv/T39LPko+c1rF7YEz9rM6U1rF96M59g9cktVllRpsCqYhx3PjcAsAqrGUXBMKXcZPANOTGTJeUMraxbO2swl+LlKxzaRURxdsUEzquwS5GzJE5olHIeIgAQaVnLCVY9BRMda0k5d/1pC0gNvOwfANA6kA2xHyfxZ0FOob30iIXKxTmcqD8XxRNkr+jI0nuOA5Q5l/Jq2URemRf4ru8IkTdlT1JNaolgiwm6GXecj6Cx55gVt7BVgStP9CpJzZzxZDKMpraMBPF149VfuDk5W+JGpq7KhshgFoHBMTY8t4SruiUqOBuCgtuPmODsnl5BFd3SdTQ73pZ8fnYEBJfWAo1wYJhoYDrBwFRigU2n1YOJBAYIBC6Vl740850tyXxjgoDL/nFsp8JEAHMIANYhIQCe+XZ6Ki4wtj9z4s37J596qh8oJuSRpUTYdqvLqsl1IUNgMbGRMMVQqerjwIoOBIvhvCkAwLkOnN3usRMeBy7stGOP+bpL3ptAVFwl49CpoGt7WR4AcBwjboIWbqo65luDaW/ux0yvmj+YTumfhIntczgdVuwSmAxrg0FquqAGm9CpGElDj+MzoaBJj1s1e8vq2PD8Ub2HA5/0xTXL6K5pu/r9MM/tLnWJod96/hO400WAK2z3904HZ8b1HBMZXTWZkKNVzTR4IrD65o26AQALhQp4AbG8mTGwc8Xd5VXAeQsBSI0FsgDUVRK44G+FVjUhAgAtQ+sCJ9jUbPh1vDfcvcq/u15rNNB14z8A4DLk6XV+vLY4F6t5HHCxBfFN67IRXJ6mvw0U11QrpXisIL3DrfdWpyz1CcoU42Cq6+fWA06z7mHXSHJldz1Bkhc25j3eTjWa2gGAlJE0ZPmG5u00UW83EtQFOSsNCaSuMQ8AcA48R8Oh45ZVgdmyMih2uCIF5pZlo6wCC7EG1KjAVndAsbwg4+KWFd314aQ4TlpwPkNrbKkHhuodKaKYFRv6GbIfc/DTIS/9MrZTgbEBVOVonNhbndOIfBT6ofxW+ho/Rk89QuxZWDnKVkL8bABfj2PvaSj90uinomMD2POweJQ+Be/a1Cs42xFUIjL6yvFiE2NViUHkDnHced0AwLTOPzTImzsFZKTtprPxkryFUOjqikroqCpQTJVErdB9TYgAQEPQ4oYTrGru8jzeG2ZV+zfX4LSW/gMAWhl0k/3EBfraag4BBtTFkzBTRYeW3rOkWslLmQW+pPdhq706C5QyfZhgboceEvIzWO9lEqQ/ZO9xT/HNeinsY643vp+BGEBexdfzbQAABp/qaNw2vRWCquO3vPmnlM4CUVXQ3ZaB1pHCzA0IZ/H5u0IIma4MsYIQth1nEYuQ0CoWEwAA0w7bVYgUzJcJKp0cm5hka1dmMgCz4uQadgCA2UKsWExpLWFdNnMDYE1LvDGwFmySEogbcIxKHHj06/lwe8wpUMf+TymTqZT6cQlfVbGD4QS7nmACn+6OoP3enWfJG24ruwwvWxvb68HL+c16gt2TNasMXmaRIQBw0wgS+ynUJluos5PourUM3SwnJ0+i6Jh8vnMBH/+0qCq7K1ACAtXukEDFAHoaEAEAAARd7lPLiAJJU3vVf9PRNLE6vfgfABhAc5D5sxXKqv6W3tzG39LG2/hb36bb5EtKrTsBavpEC4MXLK+L+eAi1n/VrN8H+SC7f/79K/05bxVuEMRc/u+Ca6A8krSyN+q8ZhSj3vrcZL3BMXZZjEh+4pkDr12cFHsL/559wPd/sIUbHivH/4Z5/tj48SgOcLjTe8v3zOSy2/2M/gD9GkMWsVtTdyTVvg+3W6uwXhxk1FmId6QMP/uZeku8OJb5sRrrttOGRRDG+lpD88P7L10woNhld50dJssC2L3OGDzF47ApDuFpTp8CAII2lRzF8nnl43Csejuv2TTXrZuiCoipt3LVOC0PABikV4MhsqosnJsXcqNaGTOB3Fwn21xB7shpsLqgtLcrKqoQbBdOMXxwF9rGKrzKaemo3h+DlyEn+EL3F9zk7rf19d/HjKBNRb3EHooiBcy33plc/Tq+s+a6zu92p3tcZQgAjDX4ErKRamcBDryZOGA15vzu1LqhQJ9MYfDu3aUOAXV1EvABnDIihDlXeK67OE1OtL0glpV/vEGwZDDsxn8AYCRou9f8WQRwqr+tN5f4C228xF9cW+ZKN5RiEvjuRGUEldYn6Vt6kYQpp0tCIGG2M1CioNRuuxtMQ+kqZyxYIdOdZe0AQFgFBdiWL2IhA6bbLuIhJbK0klBFVWCVpjwAgOXhVVVBBTZuakC27IxTIAme7VmQXt6QEkijCio1Ltwj4zaUKHzkPcM5RXxjvU0t/cBQqSFFqKKiiIIb/jhTMe8lrqmdy2oNoAJD4wToKYbsWyW9Ofg7we/ImDz9CLE/XaFI8Oi10pejA7vfHCY/l9oawP52tWFpigZrOPMgp/nE2huTszl7klaVCKxzoloEDgCk2x8faoc3NwRE0HbZXL8sZyH17dVYFBuoUp1EWUDHRgR6xv+f6y66tlSUkduLpmZr/6Z3ZEMdTFfjPwAwIDTXNH+2QtTUn9Ob2/hb2ngbf+vadq70glDzAu6AcGy/akkqsE1/TKEItTbUb1F8oT/nBx9PzPQmWmTCtfG1dm8LcVdwF5g4UxQft+VK5Nvoj208DiQ8dQu3/atIawDmRPJ43jNDVrWAFTJ0OAJEYJGQzpeDGKkybTYd5mukPmldavVcjb4/dyfi/gLd/Ozoq0tIKBWjJy2eLim1ITyuoX2Edm7GMqOichceVrfRhypP98e5uOAaIt1SMlMZ2IhIq6e3SphC+I/h0nbG27Ai2dMU2mYYBoNsoANzwdjT0gvkUj0hNRpsDGuJBYmO1C7D5OPki6qP4mLe/obk8oiOTLSuUWjYBtLtYyCHeyA5Tw3tYSJItv1hitwsHaSGHT2dNhvkLxqYUw9Hu7C9CIQD18omTNkPwc1IQXEGbuS07nkzR6JsqXjCoNSB/tnqWkLsaDcUAmA8z86JiEM/Ni+SODFvBxi1gEAWZHLIlnoB1VkBkOBrf239cXXlpVD8c2NFej6ddl8uARiyiGrmQ9Hka+APe1xY9NRUTfwzLfv6FcD5A6WEtXxtbID+ymrVY9/J4iwNREZjukGdhjkX8hGsswGUWk7vnC9l7ibCX6ASP04eueRlIMD4qCzdpyeVoe+2oS3Uyi7xW4CtNYNLneV35GHLjDUvqWAwFviZPsYXKd3Uqh3A9GlyAfPGM0WbZ5+eTm8XiG9bTN+ULlK8BXWhTt9eX0xw6fmhzbNPz7XywsmFvyOUfKx3j5Wv9QMd33Kp0ouJJv36ePfA/bGqXGotwjghbiLn9s4bFtrzcNYh5vdx9wS8PmsHjblJ8rX0ORBx4SCS1KvrdExAQ9xPWeNmlEJnwqBsif2jfm+PyTxBNaN3rYpFkTQK+0rrGNAOxWV/wBCJ0kwgxiXHwLVoG8NTIrrxMiIcUDX6olm6hzE3XbRZFf1Psjqff6ujR29sTcPei1pgfGRzvgAqIHDToyngNbDbYTzaHmDsZMwrhVALcC6VHdMmJNirZ+h4+Aqx1qof3sHNn848n6ekkUKtk4gQdIA2AD2rUSVwMTGA95YBHeotFyOYhipzN3srWpDN6Iflf14z5Ob9ObbbRt2rWegh7JrzO+k0WiiO3AYhqgJrXDZ2t8iMcJNlDZRCMV8DndlBfACGGHAiLJcZtnQk7PVJE6jP8ceelv9dOzC53kfXG+wBAH1T9CXY8UBfmYmhWLzTo5rAMblPkTRKEaBgtZkotQhQ7LLEKNFqfgwbPtog3XsLUMN2ClDrVbGAADVaNwDlEhNsrXS6Fh2BW9tuLbBiz44n5lsQyCo5cbubMgQ5d85YKiOkr0f5k9PV5zqcONcoRMnJkGJoUL1q4RSvmp3aVQeS0lXTQxLDB3tHSL1gYmoFOfhhlYFVoBnIPzXLs4M6sfAJNaRCERBjfr4x17J5b7xCQllj2FP/auE0VrHLhG4qKin4El9AiQ9IcW4M8pntZMUtXK5iTkRlzvjn7m0nwtCCXVkoqCIlK6MULVW0ja07CkDffd/ZVrm6DRDZeDQv+PL2Pp6XH5qd5BLchhHXRrowk70ZsWolmlycHZeoRNFvkmOKUHKbe+0bYAslGi3kgZycD86ZfTZmRG4vKBRMphUh1Fh9Fyxz3n5RsXa4Fg9wYMTpDx4t5qxHiwKc9GSKY51QEz8zu/ENXOaQh+f8YjWU34kzjdUuErVYbcqaQkD6BQqcfSpwev9ejYSyePgOtL5aFtgex6x8BCSSdarUMGq9tUM+h7pXYPAnPvxK/trfumJ1bVjGnipf9E19v5hwCkD6GkwAgIDA0KbHTMcJyqIElfmfNAhW0nXG7kKw5twCNhvBunaR2DIAlxHBWm6unYoAAIgDcKLFgUb0ddjaX3MDHDhqAAgAcgPyiv0YByqrMdO9MjKCLhXFyfWXFHSblSYEBzYKdrKXAAVHZQbsqWAE3rVVYFw1hFuLXOXsbizkapuNJcPbVzcNEAFAlmDqdN/2OGovNz01d7tgMgPJVU6FTCfNhAAAF8As2rgpAgylZ3bHfVXaGDx7r5hsZmUQhwMzqBE7mFVjglV1DsU4rHmlNPXnfG4FjY7fKtQNoFpGYwS66swnSb8lOekLqzlu++bV36rWDWBfvdqocZ33hBvhXyZ3r8G/Gvvp1d8mlzydVnUtBMW2bB4ObwAT5g2gVoMJAKBewCzTwzOGq2ZRAqr4HwQm2HQoY1SflfFGpgGCtzGSVHhyqa2mhdv52no9+aJxO0zx0cU1B1GL+QH6viaAAEAH/LX5A+GHWrPCAHcFsZJY9ojfZZZ68VGlgozuYRGP1v5ZE1vnlIRkfUa71ybJ9dO1uT3X5/5+4usJ2R6uGEEGCTDhlSIelpNdDXBgDfkhCBXLMqgScP45B8E35l8YsGcK4Fw7QxJghRXQANhjyxkDshs+AACXENSWw0JPISL192ZMEJPWDZvfcaNoUgUWr8my5pPkuicgZwfXzWjenE2FgLkUZ0UjcwqkCxvDOpLUmfI84zmoYq4lrtJtYlvE0Rg2OJGLBAwb6zDa3AKN0xtp9MFLGD3+0V35Odcp3O5aBh7+rXbNUcL9weBlnWkPdwtovF19Mk3c9umJgmBvNLbXy/I4RKcX1VEid0n29ti6Wru6riQeoFgn7W2ZsDdAig0mAEBqgOnh6eMB1GUAyrXvEuyg9owogT3MgADAXpZECI9aJAoAqCAKw4hoGqCovAslO1ssU2z+xIvrKK6WagMAKHdsYcxmqYUBGtQ1dLmFHLASXdRstJktG2pqLXHrVu9Km2j6dKTaNSRecmGA9qR1RQ8ybuAEjYHGvy5OlEYDp5devkvTF9419AjUSoOS5RqG+RsheEFXiOU99MAgRldcPnYA8spa/hAAHFTSddLyHYfI69FHjjvfTtr1GStXaUzA5sw2rd/bwkxqm3uXVrj2bTNHsIXt+zFbJgi2cKeKY9tlsEVYYQ+eGGyzT6kR88DR5/KUvrhw0VS4vVLkuHwZmhvWJcb9+vDTWxjn+VWHK/kX/SoUq3XqR0HBGTPh2QLmpsEEANhq4LoN9XPvOoKU+F8UBOnUn1Glx5gGAh7XSBLxrEWiAIAPYtCMiINxvTWehk9Wqi4xuspxDTzbEA8ATDcorOHi3J3Pg4quWM3oQAuaOJv+nCho05SaGjfypyDOlHa9bu2tZMVZa/9jA26ti1vDuy4Gt11HeEMwHM276IdGeBEfuyWDSxogAoBbgzdj++6Wwc3W3N0ddJriKpdNi1hptqqGbxb5nHT+/YIBNdzO2JKvoMZaZqCCOhrZIxV0H4OYKdDNGrFJoAbFpivYPtPh8zIXnWTb4NoMHX9Ry20AdRga5LxjHugH46M3mZujv7QGO7LVx3JrfbcB7NhWfIaTEPDHbemR6f1aLg16p7axgc96WnvDbFfX3mDZOmlPyYQ9BnxoMAEAfAGmwtNHAXhn/kkD4OGGbFt7xj6AHWZANMAelkQQj1wkCgDwIKrDiGiM3q4BivTrJaIktTL/gMNFewCAKzU3zCRFgIYLM84tHjj8KvxqvSnhc7TxCk/L23TBjwvXHiotEtbfKvw5+lkkFSKsNf9Thf0xxbdyL0dmfhsdeZV96q/qm31cL/cESbWfcYgVSXcZmWQwLWX/OcrSNJ3jpCS+0D1+A3c9q/MHX0J4ghoN41Frez4G87xwUEUa3SS4QtPiGQjKX3b3V3oW8PrArxQTyNmt9IIQV8IZNPPN+xiDR7jOYBlumI9m+ndavwQK8ml2TBDE7KrwJRJLIrn933ZRANS++RXGPp5aMdhSrynKLZVl246VVuF28T/3Hn5NBXZYO3PdwK5YwbGAq7bkp0NM8ZZ8AABTuwjFcFc0An8wqrLx71lPM8Nb7ER+vOdplI0sAMBin1K76Ch1eqH2yGZ2Lu3EDKrTZYurZ3nk8Y3q4OOG8SVdqLdVwHYO1puo1IsrUjqt6k1Phhu+CwaMh00+Km9c85JuEr71c6VVc6coTDYFApkwkL5KBMBGkf7cdn4lfi756Ou6Iy5S8+ndlkiwa9w/tg7BPXed8XgIXq2t5KXgpeNnDGFXYCAtFKodFqHWisX+NAQAQNKCjEjHjDI6QG/rdRLRB9bgS/YaTXsAQN9mECdZpIQpcB+s8gqBTWC2tJk4uAlsR0uMy9xNswksRi6FG5OXWJJ+ZU+6uIlKLJ8pQMyjuLRZO127IrQ5dg/uumPEImCZvK/Lml4CluX7+axh4z38jDODyjDNmCHlRwt7m+xaULzsS+/TFP+b2XbHspvwWjdkEDxXhn/+BvDZ6YmXQQ6sjdKFuQiUIcsugueudKltySz0EOPMn0RzN0l5hU0iIj7H5H1Gz+NIo14fqzygBDhyqr6EhzVel9pnCR4A5ye8oyUn4drLXgFM3DSeijXfhN5+ndLoizM2fjpdAmKqvn+Snqv+DW0Rk5GiKkcF03T2GfKlFk7koDmkTRmuCo6N/+zDxA9a0gLghsGHa3f7GzHXnwufk7RCTgAGCjS113fL3VyubGSz8C9VH+J/TK/wlYbHe0XiOoCssAqQhVkOS85pjRk2/zek1zm94jq4saDT5fWk/ic7uyhNxQaIu7LyxeJbA2YtXN1P8V+fA+oqF+5lf1IrZOQoEtY1WkB4fxbUSPoEY/6uc8T/1/ZhckpcKWjvprk6wVs6sg3IUODu0ZONHFcd5ZLmswfUJMfvlsiykJf3jDY0f+sAYIYjjho0sQ2dX8JZIXw89IAQsCMyZnx3zb0lYgpPOEjADm2GTHmEMGSyRfXChbWO2QPb1UZmJNavM3IH52+cZz5oByzl+TwmeeBoGVT4zh2AHcEd2CTOq5zP2JnU9ZIhEU3pEacXOubXNmPYT9Iyrz2PkZDbaY4WD/ht8sKMY9q9r4QvYas9aWviMNFJ7+q9aTPy/dt0kK9cnAfMlygmIvIQnsU/inaR6Tqd2tTz6bImJEJrFGYCwef/j8G584jsg7cSkZ1JF7UcWR22TCVpWf993SKBcqVNaP6vE2h0aYGTARq0Jjksjoe12bjEw032fDSJyPo4Bj9xi9L9O1yaT3PfAikuJrNzdXzglixr6TVyW9QzWhZk588b3VhVCbcC4xJTFxmnmDpX3GLqAY5jTDVTGFTkj1k0gaF7sdGOfOKJtC34HbEThv/ggIetpwlCFx6rmTp37GbqgujyqYuM7QyKgtJjP1OXKRb0zm/d6pY/XjR1aeJHUxcST5o6pzcy2PGmqQ5+/GnqIRKPmmph8ampSxavyhWCsQWKjmflDxIyLTn48a5yuvCMFxofIbGbU486JeA8t6yE1FZkNQufzUtrjxxFUZqkrRb2bTiFNhiUFOkCkzvjRVs3+aQn9s+dK3UXPLHo6UEST47bcLYJGx5JyYXpCWpTCk4rYnqgJwpNKUPiECRAmoNrbKSqfJtl4GbRdC1ZtfiNNVsnc5QVV2ZQiC+Z7KDjcoTZG7RxejediCl9yz/pDuqIWIO7v8c6o26FgDWcOKdW2qUNpk5wVqZ7ptFicadaSggAbPUME2/Blh11ariFwULd92UWmY1TY4TgZCMXELL7gAFASrd5nTm20qrowm2O0CZ0+fa8hEMp+VDfYeNfM73HtRrCU936vdKrvZ2nniDHEYbSlRIGzTajAABaAClphug+jeeCBFabf1QPM439WLly2aO58otQF1wCtUUMYVdgIk0EbBsR5Jmiu9MQAADJ1WMSuftRfQBU7eskAt2jRClNewAAeuaMqUxS2Iv5w5rVDXyc3mTjs7QxG59lTLGZgghu8cozqD3JijALFJ0U7Ukv0uFieJ16c5d/rCI8scluSbvbRFbhssluR6vflGlG6h44PE0v1L1aehIANKeQjcJSuwGgBUFNleVrp+PcBWxq45x6tt0YTNtUh6kya7DVlNJMCAAwAcZVyHWi8K1gynpm50IIyLOxByE6BoFriBHrxHhNcgY6eZNjNMYb9XN/jvYv8QwfriF/EQKegg4B6o66JycYhQ3/gt8TNnbp1ww6pQJB/iMzP1UdAlQoyG9/mDg3Ka+NJbtD+ZDoVVWZIP+3VeaOqpnlsf2PBdz2cZHwYETZAuOijAIAzNGsbHlXe4jpul6Isq3L6V9z+S53FV57s2dYur2pDXToHok04xKlpSclUQCAWtQQRD3ZgTpUnE1s0KhLewDAZF57QdJ1rqUPcxgOh3Kc2TpUDsTnTYZ6SZ26LYJIdt3145JnScv+tSRc8pb7FhtjgQf6vRj++ubchl+5sg5v9gEyLz1kYmWXk62IXeBlOdlNA7fTXAIA3BXC3dAN7g4qlnMQpmH+jUrIe5qxR/047jpiuT7FOGsrJx0bGcfNGL68lS4nhNEu+gAA5vImDjGNuCyDjgTaXTWQggSvl7IAAHABIkrMhex5e3g6EjGxmeQN2beiyFIsMcXT9hZ3iuyPG+xLwkZ0je1mWAbOHxQNfKQpTmx6utzIWX3CX3kE3jpVnVXcTXJZCUe/tcVqnzf82BTL1RHGinX5gk01owAAG7FypjoLb2AATgBlas80DSjLDDQENMWSNAH2VG67rHZ9nrYUejhRlKgUI1qpTGTGF3BJr5fDAwCcXlAK+1EKkkWrqewEvULy2BZrcEF5WZuGkObGuuqUfsEkKmkb9kSXnAomtUSlWMAa3PdzsXaHIWs4UdUo7dmdYd2c+PANkUj5mKNI0finPMZ+7Q5msZJbXywQAmte7Cnnh4AIx+4TS5oJIjFCTBcDy+MV4BASLz0JALBuJLJcajcA4MoQFrF8LJ1nmNgilrLejmU3h9yVoTCYvedGEsw0EgIAmCQ5IpvLtrRwFBa7UcG6ui3NGr1awncZ2ga+y4QwofRV11jkIzgc831wRyDcOfZ9wuF8ujaslSif6D1qlWhvh0erDpx815boU9Cr1KLjboNFyIRZ7GvDwHIUp6MAAAr20U0nSOBQBuBlksIR2mzXma6B0G67BToSoavmSDqPxezCtWtGuM/7f56GAACIsTlRYnxOZSIXyZlr1AYAeD1DEM6oqJj9aA7ScNpM7RakydliXc/yg6hZLqUDyUu6a/3qPrPClqjkqmgU9+kSttRiwKbAu9ie6H6RzVoltjmJKhJMBLfdpUCIcDlsFAMRicNDGRAxu/QkAKAiJHFZajcA0L1Iiqf7kq4xPKBUc8cMpKp2VgRSHNZiQgDg4oTUauPSAlHOYKZRT5Qgo9K2IKOGsPluuPIquJia7Nufg4G3vbzgle+an/rvjhIrkkdV8vSiyY9lgfZxkXAaK9ey5KKIAgDcpWVv9UHkSpghSn0tAS+jlbvU2vmzK/RObXBA79VIJ85ccydtbi5QRKe03cTCKVGigz/+PQ67vqfziSqw0toAQFIrt7eSTrjssPD1jSVsyFzDbt8UKhDfeknToq27Ma/VLILrCknIq1vdzfGkfZYf9ZBRkydeukarr4LTHYTj3U7fmBxSsz48bCRP1SNCuQWUAMCm2Vm6GwDqgOI+9x4Jq+Fm7uL3eAcFCoZBm/3YTPOXj3u/dodfCq9c7Sr9478LSSSCQ4BKAPnt8RFmePFS/GQXvScfH5UKAPnP/GhWjT2uNvJPhw2292QYi3DRA5VSAAABI9UbVTFgYAs7yjNoOSDSoKFslJSKOlgwcduCqmxaW6QsEoh8IsEsxgMAOUAVkBcEcwY0HxcY4dbg8Ddo5thf+Or2EaYtZpAaF1cr2j59eY/k8Naz34seqeGRQSO5bhwydxXC3YniHBMA4ASoiwakl6g5B2F5DHDHQOZqZ6YHyJWuHE6sOcdQmIotHwvYqf/lXd/fFAn/IrGkC+jKzMsKG72neWn9SgIMsZb0gFdVW3Mn8JjlLAAAywXOwHDZ61tZUxJXozMvs129AjtniVWVBoJQcfffVak6ZognkNVP0rE+MijVuHUtoVZ7UQkaA41/VZxg8FE/kVvCOfkeIhEmfDpSQocNvw/f8R4uGSfp859wPXeh6nPW+BNxc6zfmDBuANxFcVoKAOAKDfUecH0lwJr9vJReqfpsVeMvb9s02OAtTaQ9wIUHXWM8bJOTKS9s3l1+DE6Zs0mUO5/eFUA99zqJEK7rFSaF3oZ4AEB0V1IlN8J+jBxRODTKapqeY73IUFli805CgE9geLP0VnmSFnsYwPK13nD62MBJa2QKhKCqeZcDUHUPeuq1xJBt7MI8D3lu+yBlRJuYz75QuY4eDVN/v/mwJRiiwrOMep/u1Qw7Boqcn6jpOpjfhm/FvzwPNuLtrWabFcXgVWG9nBXG/FP3N5slV1GFVP2BcohbSVCoXrdT3gNr7w3KIMOut9BvxuXNTe3gami2d2hgW7A8QabjNRuaaAkZkGmRFSH76GMMtFKFF6VJ4Uk/YIv/iZQooCIDM7pFPSQzdF2/py+WDSQo9rU0Q+FWmX3+t1DKAxY3EyLKkl0CC6AJmtF4eRiEqgChrTDnsh09afuxJ9csBnUPYVk35msPV7WwyOp94BCpCvT7TvyTaqY33Lgq5XAIY5butFhBbjePXBgoRYpxNObIQbCz3csteRS/Y0EWHXc/4gp8MA6BCw/mcqvz8y4kSiAYbIJFhjzwzQ5mXg7Fgl1oFHSKB1FRQ8hxY/qFJ8RHJz0PfDInOMJNxcuVPWiQ7nfORkOaaKIRaKEL8U5h3cf9ad3HCa378I+OqNf707oPi3wrHIAew+4tfQMpqChw+0EvGZ7pow/ub0BNi5yLvx78hDIKKaXMOUxKEKYekUoU7gfrPoYWiBUR9j45q3jGPQsjh1z+aRO6Bjnjwzj8El9kRqyraAuDfhWNNQ5YuDmIVjteui6G2rVJChUNWOnidyteR21FVirTNPBOzlnqOQjmclsbhdH3SMKeoktqZ2QQN9OLakubJS8mIGcB6ZArqOPhJXwgFqOiuycvMyMcatrFJ2bLsKAkuMb6VQkBgNzKzcTMqga1eAGOsqz4cJdkgqKo+DSXZQdoUfENL38INKIyXfvk4erResTmPg3OhDBdBdj6neA1KyFTSxVNuut6XZv8wHE1H3xq5dEiRPGueZJ5Rcc973b8I5quLGvS5D43j6or2+R3nrqKnGvVGOqyeEDPD+BhmkwoL3CfTRF7Xy7xm3cRKhw82Kq1Pj/QfJWv0EPRiRbc7pTb4/FqWa1QYWdkMWH25IuiwN7lKAAA+xirKBDL0plFqEz+p7pvwFjp323tmUvrTwFczQxcAVxkSa7FQzfvAgAYCrfHiaZu5oNNxKFVidrrH3hHarggHgCwJBNl/lh7wezEKrysprWgqMLYkiX7du5JjKm9txJqr4mT1QxYuElUS9aFnrwhZ5MowM5E9BI4tkOgBoAT9bA6MclJo376/N/FYJSFy3Vtq9Pg7S4nEwDUZ0hNt6dijFSLjECcqns/By5c2VhxF0+UCkZbvbdr/l1EouPM7GRskga1MrxBptUsW21kOsMgpAZZyLlWnmwdqBH3a7xpiG2Or1z4XkcTYqL/hS6wEvOvVTF07bUi4dtd3LLXvdMoAIAd2XU6zZlKsiLAHY7bzur25s9ce/WXdtUGLrSrSnJxZtT9L14AwIgCS8SKibYoXIui2cQJTTG5BwBUkFlhUuoWP76pxp15Fmfyxt44BDPx6BBTS+2gpaP33O0xtsjH/u0dqSy6UrDhOtScTxxBQE3QhCgWxrJtPUglqWpkgJrdNmjmlsoEgA2EHFMdGkoQpICMiMBd70UycRc2MGvGYVenseu8jVaekEL8m87+AEIM8TtT5989vD9lOjZNbhqj8EIG707iqQ6t03YLLYYNTCkFABigpbpRrAF3odnps31ZQGus2EALOkrSgirxAgAGpi7aBZ1NHG7oS+4BAJ2y1DAplvwRTS9zEkQoPjdccYBcT79lBR7BfaDZv/E1qef/onV5e7KR/4/t5Pf0CzxQ+7+qPP1X9c3e17palAmNWjQBAEBUmGFzFJrYQS3VgFvoNTviIgDHfqowrVLB+DuZ89x+zu953TiSprj7L+uPO6uJPq+ykAMAwGhd3JJaGW1w8H+vYfXZpBdaAIAx+qZyuU4FDIaSBpx5o+tY6ysxMbXW16qJ1Ky7ir2RUMZ/T91WKEiT+YGjqL2fzz/hHILfaDlBfarPwwjhnUJLzm0XUgCAKtpWcUMPQxQHvSiOAIvWO0s3smfOL+MtDQuD0SJZ9hxfazCqOwGEaWJ5FwDYwWhcnFF0nEtLProykWAVXhQPAHDxO2UX1g2yB9WH9CYXH6ONBXysKSXi6/R3hO8yBBKo1cO62lMDdm6yBduZ2N4ApBwCGgaoOGw0l0/T/10MRq3AQdc2HYG8Xk4mANC3EM1tTzlZJK0wAs60sUxy4AJruYqsxlS0gppaSAgATGX59QrWroVjGumTixk0g3y31hdazoZb69vzNuQgxIbqyVTFeM7P+6EhF+CDRh6WG1wf8aE4lFQvVYwDFc3u36vTOeHtZ1Txj6ejAAAqHpVTX52cnsoEVDNxVTzzzJl/fWTlSgZjZOWMpmPYogCkcRcAwDY0BXKiaaaBlhOpxqpE9wPu/46kuCAeAPBKpmW6WJ08zIO+UIzW9O52o2RlLbHTzeQlNag5JhUWmJ3idbsKocmKUyj+t1EQOpJQLMML/fhSJRT3GnpuonCa23qVCFY4nxVWO+eES6PG/5PwV5JjFG7dsa2eQapKy8kEAKEbUrvbU3EbqfZ1DYpXwKHZijtb5BQxUUMhAMCrZcrpY3WczSBNPaNmkLaZLTJIrwkhk/HEninzMcz0nzcDTo/z2RgbWqo9Z7SJof1NQSycOWQ6SokUAEDreTj+aCM/Bim1SwLejgZ1eTeyo9Kb1chc3cWVuZ8pf51qVt20ijFR9yzwAgADdCsuygvaOvGcqcSH6r7VcArxAMBokSx+dgOFsgjDmpOoZFrk4+IqZD0cqFoKDc2yK2ooeL9eyzEOKIvgHULLrn0MflgNbjpRfbQkAbSgwnAK0XaYCiUZ/UPfWNntSHdWoUwAKC0SGHV0sLKDq762BIrdk9PYYeP5CxDvGAte8KL06EJC/1ygT2p9ANGGeH50zxuWpP5ojzHlEiqVIw0J+tOCHkYMZ4pvPTVWKQUAWBXij8Z7YJBSqQbcheYyaARKHBiAcBqgS7wAQICKizJDn4fqM59YXMdiPAAQQBUQFgRzBjQfFxgx1eCE77oT8aG1hn+95Xg+xvMXOaKLqezwhuK7lqc/qjx4YZa9HELc2NV1mT1F6MFFEwDAQMRt0IMacEC98/td9tQ8eRs4/GBSFZlDFMve1d00hqHsblKeWYuQ8FFBMdFaXny6/Jou6idliJ+l3XXWcr3WLGpPXXl5UI4NLWx4V8qNCa14+0nhSQkOEAKyd3GFiuo18uLGPC+8MGFqQrFj3kmpv67078hXk0stMi2+frECpzezP5xLzKqmaqr+BIwIAHlx0mWje/pBvMGCHABgKMRMgbHMHJOxRSGZoLLmvMLsI3mdZhYAQEVB8pTposztl6cjSUFspm4WH/1BKVsPVEEcQaWYe6LeHZzl1vpL29NBmCA2NVDrsLRGsA60Uofd2c0BR4OG3DvDvOoIWsBXqc8/KWXy6td56555jDWs9IKBNcgXZK0vttHbZw6L7aiJj0RqozCEw6v8WHSlmhJqSqRATNPjaCEl9KYqiKQ73l9EeRL00EAN3JG8B59DKynocr5jPTlSDj6WNkLiMEHZhGxGciDWQnd3go42qClbafoELdPTDKM+/PrHeW+Iw/tdlTu5vqxiVkqanOxXrlg9QVTfbdZysCRR6mYUAEAaARNohgUb1yYPJIVYNgHFLe4B1Ecxhi+XUo0zYqzdTqFdJCR8VF0j2qqN9Ezkg8Mkz2lYRF/L5PHRJp2uINr+hcNcT/RitpEddkKCh4aWVF3zLjXuXw4XTpe/KzfMNa6xwnwF58PaMBxDV0J+hKulnP6E252B+GxGD6U1Ert8FwDQhkHX8iPOnlG09fitJ2NRl2heeaMiTXRDPABgubJ8pQA2f8ICOpHC7tuRaXaYWygUb0dWXCARUGjejnK7Rt8MEGfsNzI1hCLFC0MgQ0BY5XgRU5MCyrcqE6eQko8PxIWUprVwkrL/pFCltM0XM0RKN3Xb2WPgTkOZADAgmNCi7pFBpg2Cqw3NMP+tdLTGyu48xidts5kQAHA53Y0gi23jPAUNdu3MONCwwrPHCw0JBjEpaJXpMtsRJaPsxNklyHI7eR6H+EyAFr+Wu1tt+t7CSZCs/r/ONq6YFQWqy4bqrYWpLdVSUwspAADFht6u04NaSe5T0RpQ5HuGETJrbi5gZQYBsMQLACyomOgGejrYU4n1xIuDldwDAJr07YFSVPQzFfQdrKC5A146CsG4RnTvQch3ggndi56+BzucCEwxwnndLnYfcElnIhsD7AwjcGUO7aN2GZtrQe0xRteBuq7ddhf+saFMAHALdK1FNZuBa+sGTUCphKGE9aQzzU53X4hSIQDQYIW4+iXXwQkyPbSiHrDIHnuw4wd7MHkyMNDhKrwhI9zDMe6C+OWIeUU66f88q+/5bW7dywGKJYYbYCkFACAwoaGjCxYFSTgRSEC5uQUnMwggJV4AoFF7WjR34OQTl+u6GA8ACGwBZLCYUyD5eAHV7zrQDF7gSAHQnu60i91p7NkG57E7n9gb3yRlBYFnVZ0DJdhGB0owrpauzG3XaTVwoUwAoBYNGLV0sHKDraU9FQquNhPfk9rG91ypqz/kOwT2Ff2wRbbifQr3p/RAgEhX/K4dAJNcD2hetJu2v4D6iES54v9LDbPOdVxpeGK4AJRSAAAAkeoFrAgEwNzcgMkMNuASLwBQ4ERFj2Z9C5NPHLAW4wEAESz5Ixpc0Gxo9DqIUKyDlO8LiF/T1n/2LCb8d+qfvfXzbgzq18A/vhj2xwCb7fLg95bz4BvVQeTDRAPfs50lK1CV+dDjBRMAYJZ2qrlhmsbZkYMtCwKQBbuE1bV75mcPPbrSByhaGu+r6q74MPzus25ffqCBnb4/swfE/1X++1BdqH41n57m2UV39mbKtBUa2mmbMo3pijBXLQnXETtN1rJbid0/qYtdNeobpJrXZAEACO6JN86opJvmSq6FXDqt6U59KTfLta0uNqRy3fe3l9E7xFJQxtJ6l5XlmwRl3FqUsjiR5/hA8mtVILxavKcfPQIzjR8zj6aU0NEUTq9YsFYCk4oaMWHNAbo0owAArgLCMdMz3fQbIcYmoPTE498wUXHN1csxAqmtFVQVYBekfFwGOzu1EwAIaI62uZxooaSCmmx1baLjCXe16l0UDwBM42vzP+c+S4rv0ZvT+KnCeCoMky8lrfE+wV/o7xv8lSlwh7fNvHCDt6hPxC3ekBPogDfibDrhjTmjzngztdu6sDq3oEwAqGKgk0bt4WGdKgd7GXRPCcU3pWykNMvNhACAJeBgC5e+hhWkArOyM1uuUIZptsCztwaaxTKI7YL2wm6yA8/1mfYPU3HjUuX1KQBnOHmBh/jMaqX+RvfOlLzGFyswVv/5nL+qwNpM09lQw1qYyv3LNLWUAgBQtGHq9EzXU+FMjE4ApdqfxL9n9oXJmpsjaq4W5B2kK+oCAAInIjqQ2unBmkoswqGsG+YS8QBAffvuICOXfWTvG9vkQmal8dMDHYybhpAOtnwH6OB6noLlW6xwckiCBU4vEsHwLvLqlxUipK5Eqiy5bXfAVCB3xgqbPjjaSZ3GT5erYy7mJPexY9tc83aj0UwmAKgPafrsqfd4u5kxCHwVTEoOXDSdkWJlivj2HlSaEAB4pvs7qADXNEPvQYaZdI7HwY6zdXAiCB3E1JznlOvllt0FxUOllxDdpDdXOB5bcZf9EyOGg9qlFABAB0CqB+UqkAd0bs4AZwZ5KC3qAgA+ELKIIPOJAqcUDwBMt+3DwhFADSZsdgrqHsYnHwss+W6wGTwghcCyITCnXeRuq6UdwSsTyWPjVv6TwOTENNl4g/AptNhBapOVjAWtZrcn3FAslgkABRanFo1XEGybnj8GlxCBkjV2ui/HdD9v/xrmsdqFjZTKBItmxfcSFEjigQDRrfhdewJmzdTXA9cuZRLtdCWyFf/LTuD5Jbfu9VpBi2EDU0oBABboSL3ZSWiBYsAdK8CCys0JRGZwARZ1AYAFOyrqvcdZiHwiwSzGAwA5MAKoAB85c+CyMWl88l1gMbhBsP/ga70JnBvwnJXpxVHhNbLd7ylG7fI9tRH4kDISAKY4gQate1Cx0nMYOyWmaQiB4cRZeURPolI7P5cY/UImFqe7Ptx3/mWSDm4C7Hlb3c4bwRCm6nPMAqbyj/fYoyx8Pw9W77Z5aBpW6sERWsYBCUkKeAXWLb65e3yvxWCRRWniEIzl7Qhf+rFTQr83mCUQtK1DrWnuwj82gX2cp0vK7f0a1a075sa4iCnp6FqsoRcVp9w98OxdpKHRn9KNK15VN3oEIzK7mIWuGWyVGuwGfH58x4KvDEIVM0FsFm8AgAZKzNwfK7L4dlFptgaVQf58X62yzAIAREdJlnTZznr7jw+6Pg3I4MydDgg9ICaG9wtI+lDr5R2brvFXBIEa4LFH1uJN5c04CEpJNg2d7DKdYo6NJnEgQMyzHVxKb9MEHa7ZW3tum9WxwijycNI0itQ3Tseox9mncAd3S9gKAAvg4Bnm8X2a85Vj852EwM6fX+PDqV2BaNC+L6ymBfnXy8rqC87WjZkp7GZJFwDoQGpBlNOxqx5QLjFd5xYHWdoDAHgoTxQohRMl2pWp/K6jBeWweQh21aMmGNsDM+swNzJw/yeYg+Hu8zVkjX+fYAocLnMQbIvFSa/aQg4ul2NGsexGKwqOblKi7ehmSjQe3Wzy20e35cUyAcDF5RmyattdanbQoEvjVCWcnnK8G+okCgGAnj2LpRmWQ8kVbNGZZfbQjsahpsg+HeLVEBA0midLc2eZLlBPJYeBwipvDhNL8B2sGeN2zkTsBPCbzBUA3k8zd8L5lf4BFAVeedXP+pya8zsaJwb9TGdSFwCQVIIoH5oY6ANyKjFlvHYQyT0A4BhVOFAKG5d0tLP8igqaDUJ5BxOGj1YfboqJfR5AB4FPSAB/fLBY0OHfW24JjfDS9pawJex8oti6E0lAtu5ZyUa27l3JSLZGKbstXjTAYpkAIDpOsWpYczY/GMiSKPMIuL37Qk/vHbvJxvCCOa4rQwAHxDJztFHfg4iyvb9wI4iMts1BTpQ5UHo49E7S3c/QD0Annn/AwVGYJm4FgAUF8Qzz+J76M3cZZcEisIDOzQVkZrAAFXUBgAIpiwwyn2ium2I8AABwRA/B8CZofHxssLIPARG8979uBxVQPFzcElzhpa13YUso+USxdXskAdm6c5KNbN1zkpFs3efsNnnRaBXLBADRMc2qYc1cfjCQKVFmF57dD83ptfkYPWNU0zVv76h7ErsCwMKnSJNzAFH4eD4jhDIktZVbYwT3W+YdReCT0BUAFmjG08zt698j/RelKpAHVG7OAGYGeSgu6gIAPhCySCDyieK6FOMBgAYjegA6bDb5hixcNhaNL/tgsMPrkauPZ5Hh/xTVx9cy8jhHMpzD47/4Fx99uptiNG6wG0M4Wxt16Kmzte735N/vgqq3BxDt4vuLXcuP+m5O/KrHNQOEt3e3r3MTR7zVhdiXtWt+OywrmazPDUA93Fd82qtWXlzDyREPXF0sFF2rpHiSRAqkm9O0vnks6JXW0auyN3kfrYqZzW01yFo6JSEMGEDoBHISrfXXnaGBn2PjjPi+NnGstVVr1s/TIu6iYgQ+YbAPYGN56wZnTGXU89pAVxIAAudXACJYLd7u5Hvn3hQsXE/1FcZ4gX0WQHXr/hQ/PRI6rf9AIZYYkUnwuCN2bL5AhOglScUiRHdVXGRT9J9hTa0H+dZKTgIfURn9ZCuJxD1q+feF48pEzVHxf6ZtDotC6aiPBpTXnYNmibyhxiWQ16hJGk2TTk5j49pcHznrISXLcPjoXjyL7qO12v4raIhVQOLpe8qCLLNZZPeMTX6tkvcoY1N+3Lg+clEl6S7CRFWURYeLjv0yT9uU/urrwkbNt+Ms+ysCjcAKz7N1tc6uFqHVQYvQoX32t/je8bVtNyQQP6rWCrvAa/vDNeWZ7nnOsDUxfEVIgQxzPmSaC5kFfrecfUoKW/lHUhGY0xBayFMsQBzRTW9d/5m3qdcTVj9/h9BZWAf9ScJkpocTjamoWmXZOJMEhuMGgWpWHGmUyE9msihjgijVMayAsVUeG8zpC7L6YqEHGeBIIiJpAW808RWYRE6HofNLAmKkXFs70Nxl/70AMe1jfUm+wKJJxLalbtlCU+ABmc2IWeVjgVYyuIh+SrLeyQ9DXUScL8SpKUA+bTEtCIgKOa3jvWSVu0B/3AqoqHepvrEA3nB0LSQxy3dMX8RpZJ5BSUMAqYumdWepHnuI/XQewBJXXw2mrjhzjlCehsGI6MSKvXqaNFQvncKU+fAmGIGsBHNDlRBk1eaU+3Gvu/yN+g7BRp1z0FUQkPXkZRjxEzE3VLJZQcFsxoJ5aAtb/zLKbBpk6aQYjInSGrQlnrnzuvOfOYV5qjQtT0XJd5oq+pYJmV39gxMgLlB9uLT9vNhCMpk7A9PJeasWPBbOUlxIJEBqorrIesY35MkdxrFj9WrFDCDCkeyg7Je92OW05tDhKwiEnIWGwKkRpXURVNugtDIoMtm/XAKxpYZnzkT0YYnwxifqwmBJbqW0PtTNZvDU3te/d6b0Pt0X6kNuuKGHIxKDnyDu2Nq9Y3DYcPzDEtHiWZFDck++iCdgE9esQsy40FLokvtZ61HRKCrLTUIfBssNEEmHqbqfik6yMHX2w3v8hqGXdqyQjp0LDb8qhT7G/2Nvu73a78QS+5pYL6H5r9inSqjp8DJNqLnqoP7NvdlQMYSs0W3lopkwOX8O678qIepfbHXEH+ZGCq6yLd6yUA98mJLRse4/6Keyoa+zBb+bnzYhVeddHdxu6zBFhgxX6d63qeoJ6K4wu/seG7C+x49C6HWkkMTli+C1RBMSUdnmAiFYPRAPDHtUHqLPeReao6lgFEeI3EhzfReP1gjC8KlrdklHZoSX7Bj1W0Jnj7Ymv5tnADH3FDh+nVIytDyo1grvA0Do1k1IpVgE7nU8bFBDGRZD69nFSy3UvJf1OWwFrIhmWt90NtqgBDvj0fNHycyDc9QRRGvvgGUshqGtX42vAsO4tSt1DvJQ6UkBEIc+aXWOTVa99+WbOxDhMwRyYCZY7zYk3oihjI4Bj3kL7zfJ+BKQWzHwKH3DpQTdqeg7ED9yoRnQNJDCf7jcillJGhJxBYjYAdKwAaBsJ18S6D9nXmo4/0Lh+nPA8d9ZmIKPXeTN3dBwYB9C0UZp3KYoqKdEXz9k9zMNeD/9a0DyAwKKOmik5CAYeynb8raKJhY0Hc1g6fuEgWwmDO1mktqcDtBQXN5nqXnccYk8F1vfqQz7LE8mGKhHfkgsgwrUyHhBBdQO9F0QmHPB9MQU/YoUL/aNBXi5wPbup2Oa7DLrnACEWxzoLQ9QcTySOhYFZXvgQXcG8zE6q7xukivOOz8H44YT7rJJikywt0kwt1viT6vxy5oDz83yTouI78Z9Ux4EDbiWewhiI0fXSWVKSd+nUSdo2ZnBazv9m/rI9l1cH06KAswFolWytH4qZgmUJoE+lawZcgBlmXclXECDeU123a198j4H7Sq6GWUOTmj6tmqPJxGlopoSbbSo04Ci+jsTiUrROSNhs29ox7p2O98gnnrWh0S6UopfF8fRVZG6/o0nMEt8YpJH0iYKH3oXtdURpgo+zZI0pOnsWBZ5ha+gCftYn2KLHKSbUFQMC49QBm31FifBBwFENHeL0iTllYE5hRs57GbQ0LCI/z+gc5v+qZGBUY9HHYBU100FmUDfBVpn2QrLNamEbNhNWA+ynkyYvoLkZw1HdlmJ0dBB4ZhdmB/+DXVx3/Te3NZymCwMGM4MACcAvRGom6bwE2eKhIqHYVOtV2TgmoQDYw3qHl2HwrD+tM2+1ULm12r5nr4QjRzihyLnP4/edfJtsQWxdvD9YyfJxv/OeGDXhlF0x59Xv+UVvZm9XWFedVoyfQH2I0ztSxo20r1ZKcNmYXJC6PmIRwpNZp9S6lYVLsiUe5jR7JE35OFk1Ozsgojavt1k1ER7IohaZnd7lG8tmreZuYf2C43UlDQOfKx3WICBfv2VmUMjfcmdMTRyJOZ+KZGQ1eolpSWsOZ4qVm/qTnxP/6pP528flWdyglLkU5m6vnxPWUUFAptK2lE3ulEYfoiUlKlzR2TZ4EbuZDYDZwBYRfpZzvraIWXfTgZGt9t5YGE4435gov8/AwAC69pNBjLaXTJwe7sSckCDL15JSOvAiswKkb8HZr4YSLFd4EOchsPx6SL4efP+zAj6uIh2tqyebeyKLeqWraPrvGNyalt0n0tqRy99JfD5NOIPi4QCuTSTZyCZN0z+k9JewzvYJKhG7Kvkb+C/VPzjt3To9L7d5CPHfeXJembyomMU6pqBrBpcPgBncB8GdHkXgBPdZwEt7v4AnFtN0Hgz+wBM4RpYtPUuANO+Bhal2K0/DeT3zp9CPzGBb5MOCQhmi0oUuC4oHJzeUqkCV1gI22uNUzTGm2htZcG/r5QHAIYtTE5JBObnIiy/e4LVSVwaKCltZzKRuLu3rqBNp/eIkDZylGZ5iKMqoI01UReLUOSCj7DIgoEucKMXV4qKb6PKqT8HAj1Djqx/H3a5Fs8Gi2FZ+QVnERFZbSKHHHUN4TdjKApEeG9djAnBN8VfZPXMWsKxZZFvEb/SfJZOfvylx66TqaA2UjxdEG3TyEsSoUQtvZGkAxmzSov9x5toHtyz8+LXAiW68vpsbSnysrUogBb735H6ym8QdV5goZgU/qlQSMj3zjAIVzuFlfZP67IzcKUqA9hWiySaQiksO6PW6oZFO+vkQXcTKJX+asdnsYO7k2364jUgyVxH4jyuT3jl4jOFaOd4PCYixU28cAzA9kxmxEccZ5W+vgP7GIguiEjJc8x5CBsyX2gGQXvtHjQN7C3qAzjYxrKe0y+8RXAt7c4qEQixhKmPGUrUVqHR1/z8iMlni/EVOA29I+fINkuIQEDH59HwqBSfmitPhR/PM0RfBOLM/nyc0Nog1BON5D3QWzrGkMLaEbEkwqTR+V8f3y5gv+n0zn5M850OGBtfAApiQVsVfwwXEJVCH4WQTAl/5dvKHUF8UwJeSWeMRFdgUTnArtnOOdusnXNyWne2c153bnJid8ad2TK4GVI/a0jjrGKyxNhJQC/g6u+U5vLvFLv+O8c+gM7ufQGdYZ+ANyA0BBLy/OULODoFRJg6VoJwIUpx1Q5ZlDeqYRIVFgcTza1wmBQ7Iff+Oo6b7nq0qyjgQSqJSbUwnrDfOQaHtLm1/1GHd/PueSO0kCCUiSxb2Meps4Bad7mIfw39a1lJi0VlI765sx+ESHyMMyLHtuOD0QTK2yLayTMT3spDbUne9K0rp5iUA6XTrEpMk0tzs16wkk8oZzMhe8OHHoWA0sJIJsVXdjWnatsyay3IZRzCeqwY671Eza1dvLGVDCRJOfQDe0TMcB+sHoNJQemqQa2jjXaNyVlbGbtDQ4rfXSh8VfcN6N4xFR1rcp5Z4Jn9OCXcM9NGjSWbZIrBesmF1/iN86BGWmtvuQKJcpVGyYqbTdqAscRuR7cAD1d0p9z5TtnBGAYDRwqt+9ySNJvONDrn2TsDj3pWzmhQWN9R2oF27vxz1ZstYWeyUfI8qFMm5r4MDo+Ctsr+87qX0hum3GVWMnQlG4XCKSnql5PcV/e1RK0sW6K3/viVL6QqwJZkrPRasrNa1YLJxCg+GZMCM0dGRTYrUwDWo88FEaDCcG70apOyr8mXjNXqk7Fa3i6NKI7DKxNmJAwVrMlqh+XWSFHUOrAlVO+1ZGKWliI9qia9ymoJ2UHZqqmWJNZPLdFzQEZDk2Q45f4dufuyS8o1FRlzScWW+ZMeT7YpV1TIuaDiCIr7ur3KycRbtD+jTZyQbYnxmJKzKZThW4vzhdl9lTFufS6uqRIakE5ZNJACeJEQBS5xGgvljbLLN12Dk46bL0dx8TVwgfyy8XfXztmllhRfw7TpInvu/If6SrqmIuEr9krZsr8Ejc0Ts7hEvkwtsUEfGUterwtS5J98OfW5N1wzR8RbUgdCYq9GpuZvp5gHNEM5lZAFJCgJXbElXuiGByUFsMUl/yzkL4nILR4EgzmP4SVD9vyBVOu+ppTAacGj+v65MAWLr55QTV9kMTCfw+GiTCPM25vmGY/4E9+yD9T4hx4XX8pG/iT80Mx8Svng1YFTYKHgtXYqFz4CoTLA647tVU4I7tyfqyMsZX3XHfbFqSVtvZbbn9Hy/ORLoKNYofGbgo28BLeJapnGfgPig6vMrYu9okWpg2IzOyG3fiXpFeW834Q9yuNjJRF0nRjE0fZ7vv05MmviuhRP1dQP13cpQY3Ikf2AJU6UujIlOM5LzEXAi7QYN+iv1OL4Jgwau3Tresb39peHUu+2w591fvm9jY/Ivs5d2VHqqf694D4e9Hb1JnH3/Sx7XOag75knrm9oEFkEfZOChrCJy6RxVY+mUo/OKE6M34npq4GyF8enXlZf1ZBQSj4p8X1PA7hdkMREmnEgCa4iE8CU/Bp4oVCI5sKRaYp+tlQKweAJoJHwJpU7fHwOEQmhk/ntgyLZIGJB6ASXF5aWA6pT76qitdCeKT2QTYcFbffZ1s/7pqnywq3rWziqIKyvGnWIqlexPNQ1nJ+UP3vNTEIzjQksk/Lvy7DvKzGlLMBK/bC2AFjt2Ce+g0kg8gXdVfVW2wk7bstlfOjQAniWAA5wENiA6eLHcmubmEzvObFM+m6z77tB2qlNNcF/EKZWYU4Ty5gjOB0uBgt0GiGcofPoxOJgI0rc4oZRvCWB88saKH8wK6IFCRf4WgmuKMa9kg85JXjvEFKptgC+bQC2ADkDIISw06Li6lgbBlzSOcTlSitaDvhmAdyg0eFisQYARUSlXyPXgqGZdImceg/s3rWzr6sweDPYfqBVDKbaAvh6ACJtg0lTqSZk3mJbZmQmr1qDjAD2hwMGW7fRK77mUitexpHlc1msfthDomF11HS+hC7iq4IvNJhUmg+ONqc8l5R0QmPL89cKWUdTS3zxP8T6bgBB/DPok2JZOob4BOVxrENbnShM98RMysmfaXwqnbBlKYEO54w9X4wABB1OY8eOc3zWgkCodEEh5HqSqJ+aWLVmE//JKkBVrlqdjiJD+Wp9ukD451E7eM/As1ZCpOO7NaSZ13mh8fqGkFptLBwQ5uZ/4mXwf+K7Z8hvL8UmOHxZ0xWokU6fXq0BbuFfC/Lcxv2btgYYUW/YWLekvdmoKxN6qXV8qmEZdfj9d+CAzJudUy91O1bu4og01lJkTOTFHFHRO9frAEkHTzydVJwAQFDCC5wh2TOK6+enMTnXwVNK5RvCOWAFB5I94RgXL4ALTyk1CHLVgmKpIH301fWB8ibto2hKqRhhxQbECESYwtmTffMwaPV5lDDippaKi6GcQVjSBboYG0AODD2g5xXgTQWzKvPV/4IUDNQtRxdMrVYCNU3lT7ZZT3nzCBBAYK8F8DEFjD3RHvLw3sIdSE0GBuhXAELBWbdzUzbxq1A+aYWnYEt7PIxyZgF61g81yJa18fRK+hEl8ifpxh+Piz/xC5QFTuGaOZJsaXYINUAved54PjbeFwUHS5w8kc28cYfGno4OJizliCkGweF0sazgAkhMF/MPxIfj6tWUe+Ve4CTZW2Azf+zx2dM5o8ufVzqdYIoJazr/+HB8sFhuUAJCZw7nm388giN/2eLT4QIzfDocTofzD0ekw8VwASqIMQUxBZ+gEsJMUTv36ivJg5fgcdKsCT6/7IFI7IlGfM7ZE0JF1ndZeh1c50uDytl1k5Gj+UagknbzWfiVteODp9prGD3Fgtek4I65leMugso978cunBIfI8221n9WdL51XyAVAoOdDcc23YDZPt2muhvoS+NhdIbUuylyusTq9HIafR4dP/1zwFurCzmnm6r14eC5Z5cyFG3Icp8oOmLk9xGiQ7ePyOWRv+CFxXxKHhWR9JXwYAj7aqzQy2HtFX4CAKDzUwop3Kj9nAr+BK8I6QgKQipCA4GIAB9BB09owkQtPHUtCgy3wfSvtCzG6sABoxRV4mtaLOZW1Nyhj+Xady2aLyn/yRJcP86JBX2JRXWvHh5fH0N0QTujs5anK1eD9TgfRhJQi3zDL8/hC/kPvW/l0yvzFWOuT7dGZWE4gdFVMT1mTkbBjApPlBihJORJxsYKbxSo6b8r2Ow9WrA3aoEFmxxLGinRqEjEp+FR0ClQN39bcNyzsT3m73wUWguBiACg+/yVXFrBKv9tCbcXUq5bz8Dppkjpq75IvmROd0fGWVSgyQXYJlmjUdOIYIfAQnCCHm64d9LUPqk6KO1NlLGPsiaBGjNqkikJxKGnpx6dEHNlRT7MBRZL1psDk4eR2gN+RXt4M6hZye2qt1iP3xyAkHb6qv2eABhSnUVPIfAUM0JHPAIAFsrs8V0BTIRzxLwph/SN1g9OfWku8e3rCXY36mYvCj41ooH7Y57cpc0s10f4Oc2+Fox36Xv2+QVnCiQEv17N4zMZZAhE/Z2259iqT2baI2Y86YwnA5225+mCdNl5YZKJpQNe8P2HzwAAL1Yz46XcICq45KiUaLaHEzNHIPyZX5f0fY21m899lfmKUfwwUbdx8cGO0E3mvTfUPUOIkNO9FDKA0ViJSQCz4h5bhvuCY2foju96LsPldrCrolih55QtV4rMRHaruo43hCnaOeKBljBczeXNkUm4E7CsEIgnWTyJHry2askAXIS+mt0TV/xV0QAA3W6/ay9u9c1uGkW+QTRnPMqcZXmIyAVr+mn7Ka8ERWFD/moxtAiEQoBTP4OmsArmMYz1Dmmyrt2cwUc0XF2mzHWHC8EeB12GF6FpolsFosagKaJ7Kz2/GlVi3QJxYC+R9Wslt/w6S03FSVwT7eXXXUpy9k0sEZAwcQZXhNsDTWX0SRffyIprm1dJhFynuhD2ObfW3jn50W86OT0J/r4XmCHpKqLHyQLjhhIcnVySdhY7Xv75xrapwWY/MFfwPTn1wjSgsSxdUgmDk7C9WAeMI8kjil2onrJLbrrkSXrasCGQ8p422/I3YfAiXoqnYd6LptEZDxLPS808G7YlzW3RG9ETZ50DN7Z7uevubJaamvpOn0qjdovkBBN3hkq8pcTk+Gv4L82LZQ6aETE7bBQJEB1takIqYVyKUPYZpkT/pbNOZ19smJMNSmTURiiK77wKlZvYu8LmXmQFWP7zwaDaHbgNzBdgNBa+vHgA4TtnwO9I5N2RXI7etwscg7GFisbJi5v6o+68k5pPCiuvaIPwvkjbzOn1smMR7lzRyUKHhGFpzmdRTfOTpKiTOng3ehoHW/5UFM2LkgUg2wgnbcjAmsh+y0zQJj03oA8HJVNColAPYW9cVszdrRntOO2c5OBNqqitHOD1ZP0TiiX+noPLDLTMsx+7FtpmpgUFUsK6clkVK5bnQTn0Dv1WRcoj5qmhf4DN6jPP0xBt/Kk2X5KxA7NmWjs+MBe/zQNFbF+2jvwy0QdG5m6jmaIAHigFhb5LobPU1/My/2TeurS61yasvwNNbVkdM8AgMPSx4oL0yRm1DPqYaWP63AR9vGtb+myCPnW3eX0OQV96Wre+GYK+EK1p3xzJm08RJniX4vz88O5aiH5EegRIWr1q7VMNjO4zY8TcR51Wb8Qp2sQwKeNCUcCG4X1Am0kK0Tfqpw5vLMnjBpLS7ZRUhu7wds3dlAu2/vlaiS6Q/s06h11CjxfxcaoUKzCcx45U9M900Flq4HaXoAEArBWC8LFJcl1vnB1BVAxuZnq9EbNEZ97cDDQ71cG+pUPMXnXtbE1DyZ3rkt0yPYWECgcR1x/UAEKmjYFkAgh3bQukI4DY3eZBLgLIPa0bNEUAmWhNoQH1On103C3+/K2r3vy17GFlcQub/XBW/focHAPICc6nUOAtQ3c/c2JLbrAERGZM0Lpy5F5igG4U8Nm8JoFojvsJL5M/y/zJAHjAg30e2srcWH5yx7VFylr1i2/ZzhZZkrIYSUIDZXLX2ofdKejVbE8P4SFaX9/O4HZ1/5+JuqXnUwfAtqGpuWHvC5xKQ0eqsoJAsLsJ5iBBYXlCAABvQdDJPcQYEAE6/9QOxDm1HaptpH1tL3YO6dAW+UAo1ji6WQ7UFbV/zRmoMWnr20fCpvF1ydcO72AMXxTviK93PFn74/M6cGg8L/4SUpNwwwPRWhMu4PzSBYGIvWfrCpnu+n43ONzQ3Zk/fJxmIOd9zufJ6nSP42x+nd7qB5jucv+YfcTQ3eHW2gCAuvGwtluFwQ2NkS/Ma2h+IvCbm8DcRuNyNZM9JfrMp/dmxbB/MPpW/vz0ri5dSwg03CgdFRnOih9cfEaCwD2nghM13EJ79R6hw220qMI4jTskJhIFOD6fLOn4CFxLB6rZBCJOikDM14zAhHtkDEHA73ediZn8qdYFg0kQ4veVe19nci5/dxNv9XfesugnyIdnOfOolbWxdO+x8K1Vh8mlxMtx05pL1G4i/gr+QYsdFK67TfrGLgV42nwEXlFA9qYaxEUB7WxqQTYU0N2mPOSWHqb8u92V6GFQv9ceTMFqXm4COKQ+yKsinh6LwZ/fAazWf6039dGtZH7/MZKprOkc4TOTLuBLVfOmjzX1OmDHkiQ/OfIHQN0bgVLX+JCYnHC/XhKS89DfbylLpxaALXq63RR6Hdaro05eyxyGixAO65PR7mY9V0iC3Lq3+x/10KBo9f65U0d+L020uPWOAMCdZaK9f9zrNROd+W3UJ4r16UbfnQqvELGaJe3VUPbXoL435ou+fzNxmkn96ZH3j6aQDix1jykaDGOGvv77oexh4UAmz9433Levmf0wG8+yc6l+DfW6db9XyeWvUveUTUiElu5dbconDnSvsKUKocJjqNTjN758m/v0EXl8NLp4fXpIEAHEFMfGE7oDWrlkQZ/Po2J1VRArAoi/nWy42Rbc8Y4AYEqLTvX3eoct7H7EEQV4rpTn0+DYhyu9ubVjWDPvhLU93kHs9bVwewDDhEv3POHt7LGDRL1L0ACARGKYBOcEJ1mFAcHdW6wN66vDMP3M9kxypRPQQ2XF95PTbu1g7aAt3TVPpRVEdmvJtLx081zfBkemU3w0Uyg7mi4hTVzCFr/uzbuyorQR+sOJaNI07YfeeCT+kO2QLDmbIkdBEaZZpTRxoZ2VJSZ8ixPahjMTfYjn1Bi4QxzlmOtyJo7SQ0nOqP2mKz8K6wO0v+3Pr9NmPctarUhmuybxustm3pwRt4U3XZ23xYB1Z4R598GfZWqGGhJXuTMCJ81CrgIuYGVuQH+t+y6oquVLm7wRNB5Kfw1Vg79mfCcKSFEWhPkO/nnQUa02yaStZCVle9twrJ0Qn4Dhxto9COnri5l3buRlSuCV5bDJScQkAbjcNSmWWj3oYJk0yZQvJT2/YoagJNO8d/cqfIpqvRSPdPTw/q0DPyDbIx0/oj8ryM9Ds/3se5JEONLqIfNfN39k/Sck41nltNPfT0eoWWoPvei5O1J3JG98l5d9XQGUrR9v8skdAU7/eDAwfzoVp5zDWL2qlHR4aw0o8xu4LBIWahVb3xrdY3U/rMBWW4UtkX/t2SJneC67unXOuL+WoV1QW2HXVnhQhqqJjdg0x5CoNpEtDZYzkGCh3XN2HcRyloIBAGyjZyaQbK+kpmKBskLNjj9sMKQJt9Nfk5iD6/O2BpoLa9i3hZhb1u5sB5recV6G2WOcbhayR3AGVuZ84Jasy52B7bR5rhq+5EIHY66O0WTgohNr0IytX6Pzn82lO5Pj4DZsqvvqF8pX1zgFiy92MTHTzFutXSjP6x5yRUiLdglda9JV3UKRebjnO3O8mtGEpg/3+tEWO3VSNBow98QxxFRb6m20rTF2V87GETJu/3C7EHanrSdKhGFw6Drh8Lpt5O4VoHiq6lPWdtQeZNdK5Fq7t2Ta/Onm3XzLZJhmXUetz7pM473r3/Ngxg6mfyDu6tqBuzn/46ZaAFIxCGd9OcrrmQYTWPdQ6dPvOO9Q0t6ah/IO7L8LxFEuvNyh4ui4VjpUqozjPGlAi/csEW1L4/ItJQ2VKu2Mg8B8bHLA9tT+XQ5Yu4vapWamWn/HXTGuEHKBdyV0gx7Y/UkDu+2QsKaBE1obNge4UevCHgK3afPYa77EvisIsP0oeZ21jY99atCOjxomXbp0CP+OIWojqOah3Fc7Ptw/Z3ucENRt/oTu7V+vrfvwL12zwA83rNQMBY2qkXr/G3dWIWGVfxfTxztWnIgF3Qx0hVxWDgrycMt53Ic8bV9QpwxBN51OGAAJdzqUMDFzgus1jJCss4fjQBjzMsTCEmx1+J/glnge3v0i/ZfWfw4TOuUAQxzSbfWEESzdc7GSf3e/tP7kMmE8lx2Wl1djmpDsuaxofeylk6uRUn3P1RV5tNF2FWgLuwcrvA3FcqgXDhDeeYIVIwH0q+sBcAQQNh+zntA1UIklhWbD7yHBWap9aHcHnhhGrEhHADAHFh6fG2SEI2Depj46r1hfr1+DC9+b5DUeRxlWorgfhYRAMTaueIhzxT0/o6CzeikYAHAO09k6zM1ce5VbOtGX6elmfqFunYzSZhGXeP2rvM5fp0VfMhH8iM/q++1T7zMjvNLGq77GtxUk5DTfShc7jXcuFq6k43LugpTtTrRgek3BNL21eW56lasMjDrLYDU3SbC9jPVqgJY4HGSATI2eZLxRHbt76J1qdswjQLGsioHIpQDFrGJh3KvDTkap6ncWW5yMUvOqdmYgRz8fz2wcR7ggYxe/Mf8ezLRz5+feSh19zQ78H1WkPNGOi6anWzbV9/zsswMAk1/Q/VF98LP7ICi2MyMGYfjyXAhXD6sz6vCuonwvt542Mj555mIAAMChF1qextCbMMFWgUSZzEe8Rfl8ggcp2D2LwQAAtBRQO8uqF+1sWr0zizuC3k5tXhPILbh+HSVoS67dAQIq5C6RIMNwQSwKMts2xq4d2cJ1mBrbYpPrMFPugu3u/kzaGVfH40XaSyfWs8XIu7wHu/IWsyVMufQn27tMau6ga1x301FEXmuXIwQAxw10rHIPz16kU2L9m4XS43t+FHCiNbi5tmKRgbbA9njZDVzi6B4ciK5t/7hoiNNs61UswkRfkbzRjkI6qg6T6MnT0woyu9LDg+E04AAAo1L/lBYm1eFtXpcwhQVRMKu36Z/L0e6S8NcLzQCAHbxFVOf2qLdiZIvlbZPOPxcWvFYdelcBR9XHNIC3+x1pAqzc6qcoJNXHR1LHgFptk2FAt3aZRtKY3+kgU4v3PT4YH5zcB2nkYFbzITgYih0dyWBcLPhsSKW+xwgmdCR40FllwEcX+NJyK6u/Ny4Pq3uUDxmwakvVBZUl0ar0jg1OPT748z/OHsb/N/QQW9nIqaS3xGeLozO2Yyn+Ox4zRMoVSJtBkrPcc41GIJFzgg0JpPWYdqUkl/Dk6MYxkbRJ0R49xencyZ+rwXV7A2EPl5nuLHAKByZQnnzpVkSyLpUMC0mLF52VOIkbmrJGjkDz7L1zUEh1VSRcHkOHXeXRrfZg8Kqu/FXXmgdU9+F5BFDfAGg8oRRQiSWFvsZNz7EX3MH5QnUv0RfGkhhx4yYBwA648h99YCxDF+aPC+EPPYOfz7YgOd5X0PveM+rnVYeeYebN0cFxLgYo0g1OKQwAOGhLxAazAn7dt/Vi8HdjwvO58/2vN28eex/g8+Ojzpg247mlzEXvHnkO6L1a8EQ7mfp8u5/bWN0WlsEAgI39HLsAKop0yqZxASEmnDHa2W0gvVbnDSTEqcfGHDMkZFK1s3iyid4ZXRAUAPWp2hjUFdQ3aFvQCNS3dhfQPCT66OqAGiRQ5y6DOcKBipTffBT4V5EN8S5pI0F7K92zQnQrUZwLAACcQMfuCAUwxwRFAmky5mwAzjB0xaAaDWEAgGuB6dJXy3HhN4tWbBccuAUPWpzq88QDSdSwuxugUbdjErpyuS4HNpTVcZApjmzAm8g1tDJT1zcCMSfrMk0o53EXprXK6ZjtDN0tnOX0No8dDiMJiZwlbBZib0wpsucGBtOlUcUMkHY8pLbtZ85Ff0GLW/5oYkm7Pl3J69NPs3ToB6fyNeec9ryRFkyjVxU/1ESapHn/HPpfIC3o6n9ga0B8t9HjaA9if1aBk/pt4n+TiT735J/uB3VtBZPBIkgcUvRt0pdw6AhxfiTbW7rS6i0Fccd6MLiqtSpbzKHBdWEVpsteyZ60f949yLPd1qduuSEK6fUajgI732mg7x6Rp2bP0XQOkKoGHAAg1WDQ+gULBjAKcXgas9qGGoCZze6MgYOGF5oBADS+XdmTpX9ZZ8zdYMOdsu6PDaT7tgadK8jorY1RBeDgbuQUNALs/qQlV4WRuG8Oc0NX2hojAt3VtphVkLvlLpjNTZoAO7LR7wUGJnmwLdDBXcYrNlgHnSB2E2KjLytsEcnWsp6eAjtzQe09gimCqhiCtU5lH5p5rUk+7voUhTcSAACmfN3EglP5WnlOf27UCaZ0UsUcJ2xFwWDKc8rFcC3HRzHQ67vA9PmIDZJumwMbnsrj0q1kxpdKJ4bs7Uusd8EMVYbh4AeBcP2f1BeHe7wGrdFkwRHt/Qx55GI5gxWbgWpnOx/NFqHnzk+1WF51H55HAHUGAMcKsjtgicWFdsHqgYvOLvrqAhXcYFQIPP99BACpoF3nP86CkwxzmD/qgrRs07u/vQ323ixbI/agZ9BkHWPhszOz3saCo5WDCphmCX3yYwMFR3umwTg3yf5t+GKKnbBsVgwbwAunu6/dLAk6eI2PfesKE3IlhU6A6alZGhR4mEJn2spewVO9EtdXbbp+gK4Z+3EXxK0rn2diuop4UpXBlfOT7Mm/h6Cq0fCpGuuCMNbAF7p/jYPNjVNqtzTO9tehdaLuTGqKWI/mxerjx3dlUfrb5k8odZ1dOCA31SR72qON0BuV4sZAXYnwU4lz9CbIK8JUKrKxzJD+YO7Oky2gbI0QVFciRHRbGSAg2tYFLCboQMbADgNOGTuGA3AZMyzCwdv87k1rgz9fVet7FU8S37rZz0jeHI13tRAAADiCauidCSjYENwrDie6eznGPAIgwzy3Ik4l4u+cDwYArJHeLoO/ZsFXM9MXCsX2ksMtMR6I0nKmQs/QV1ex+/DEyp00dHCZL6fjXiinUkYIFPIPNA1amWFD07Z1GQqaznCGoV3lmDsOqzyj1gvshC+x9kJUtSvFNERh640iMJCmOSAAyBpMkR9uGtracfuXbjBpy3JaUBlrMTbobns8d6AspjsSlGq2fyGCDHptvWnCvR+8hVdHMfZe4B/tXTon74qzugFIVLmic3EAANPLWhhy6W39XtL1Kk7XkgFdwRCzThHvaGbvgMQ2mQEAYoHB/g7Gl+D9uTjpH85JOXCH0iWXx3YEFZ0YPCv/rkHMVGspCbhJJq93UxmzBuS+K4UHptfubw2IJiNREcTE2mgaZK11cQ1IFGNwHwNj2dFgGFjiwaMDlr7HpDTIbhYPoggKubBEAXNb6rnxXRTZi0SnUHGq6qIOZjB9TR8BwGWBHRuP3d2sEKfuYjkNJiTjBSYNpHlXi5IJMMvLZWoJ3F07FVYBW26NtmuA1bX3225gDrUVVzd8jD6GKqe/rwqbW/B0BaH6A/X5+EICqPQAZE/IC9RiSaOn6fdQ4CJWFGgHo1SMqOhHALAEVzePfb1wB+OrgtQR8jmSTztL6bmcWLsArN9kc/XJY/fymgogbeUQAcMxz8eHnEnBGSwGAwDmfDqppmw9FWflwCmGc1X0volr9L5s5epn8vDVXuXB7Wm1jhZvVbGz5oM7/7t41favd++//fife+PD3MryGqE8eqfrGCrC1vDB7aZ/Jj9PVR/kUeB2m8EAgJRUAHv1BZwFvDTisim1C8yoPm+X4DZq2M8WlqjduRnQFAvJHOgbHTN6omAI7TLbDu+ESIwBc0iswXZYhcRmeSwLJG8Y8JXWufUDI4SzT0KlhiRtLyp+0u0OgVAdPDHMSMk4Q9tKq2OnGdr2uYJ2wIa93fI3DnPv6nAqeikTPYcfLgoDAIb0jrULqgA4l+I0rJTSalOfFzZoqCJsKjkXzc4FS7U7A1/8jPmyBi0YIQNxUlZm5phMVFqXZYMxGMOK4KacnS03uBOHdmuIJKcuHB6x6+9g/D+JsaX5lBZm/39/j/8BVLxy5pQarOp6I7QZFKo5IACAF+yJgSgmmpY0t2GFC5O2vOonjfFUSzB+8x6dl2D0ridY/z1EBbpiPJESKuiKNp4zHpeJV1HaBb6qAHTmZ6n4siYOSKIZD8NOmtL85JCj6wOtrwr2ybvCwo5Ar5pOAIDeYV/7mU784ZCoHIV+GR/CRFAPL9QOkByvHi0ghWdbBWq7yQwA8BKc7Zq2awCd4mMsAXTX/rkIcq8O3WNAdbUxvgEc3o3GDW2l7f7CeVOm7zgk3l1x0tbmHHAu1uXOwNa6C6kaZKrjGgVtZIpwggMOGOKuExMM5m64Kva/S+2MIbeM2f/f7xOhDQ/hwMsKWoSAas4DIeP62yK48qKaWhA5E0E3ypPl7xxgd6EAAGAO5GTzF3oa4lWVIJureE1ZSKJ9gdE10jjWongKGO9lJOVl/K7j/0W2bPvn+3Drf/Zg87cglrtXhSH+2u/j0eUE7tWHMJcWaev2ACFeKY0v4G8qGK5IOHMcvGEE309e79B28qscVtOAbHFUaAOitQzRWqgzcreZh7mtc89zi6zkIcitFNX5YABAHCa1VsHVm7mfqbPScKjh5fSCJH6tof9L+vv6uPWpryoJez6948M7VDedwe7TOwHYhCk4RqbQefQ028JPLQoDANJshCnrC6QDEhlxk46XAWtX6F3y8EFvrx6bRWbI/jU5A8tPcj0p92AAXOiEgF35XByxkDaGPYFYaetC9OB0RKwhYyAwVztJYvvdSNHjYmFPSMd/1inf0e94n36o999UHX7hvMxf+DFpaAZJ3DixlIcp9LeMkGwUlMDanPg3KPO7yidJvXHRM51hTgHm9AInwyWcx+nMtBcqprbQmQJxFAy6LLhGeoPfhZO3f3drbiY7O0+F6cwFJCihz3gfqmBuzgkDAManVVXL1tXYpdNM9sAMYNaEc5WLtbH2WZ03Ja1vath3ho1Nj5U2c1LV4B8WnIWoF+VQRBDGQbpSlMZe4NcU9Pwkb6gkkW/4w626ZtNJwsEQdJ2MuILsWTAF+mmyLvkD+FT+CcF6KjzIcWIF5ilc6IJsyy2DtpA2ZtGEttJty8KAtobuwiJCLrYdoNWgy7Wfs07s6sR67kNHNlTFkhFVIa+nUsRxKatAcw2McVFk5JJyeDqwp7p/rgAy8tsj+Dacpol4U+wY6DLrnxx0Pb68nYJ8ncLtWIvG1B0GdtEiNxu4Ga4L5IueC4oTC5idcW0bZsYWTy0ryP5e2hp2cR5588OvEuHeENRY/wd+gaeeWYu7vt+IW9mpx3H7/vE7nuFhh6dJ+hk2kGmcJwG+Yk+Lvxl6ssISfPkkku8QOKj9bMCC7cFvaZVAmUU44kCP7Tdfq9qV891AIPcirduHo/6FQM3C2UuI4Qe31FqOBmirjr3x0zsV+kUTqjOZFwuDbuIKErqcOddRgcA6615enHLHxd9maKDSF+uQPaWw02DtBsA17AAAIOxl9IuZQF9ANG5hrBOGxau3Ds9laKfwrYVmAEDEYKWKtjEI0hybAQVV/k1ABbXo0dJb2PNMkRdq8FUIc1daCFT4O4pxSx8/pYAf4JsBfOwui/DSrWrz4QlTBfEuVG+mVeWU7jNJwikAyk/rmxAKeqxL1NmGIQZwGCLsNhDndxRmvD/xE9jxX0Em4e73sSWhh7P/UEamG5x4W2wVR7nLnBdCOY4OkEOCxoXFAzAs1rNuYJuXVRYH2Bo3o4sgxzUGvOEiSxYAgK4x+f3x3g1u4To23FBX5jLZFCCOdYlRsSBvuwsldYCCrctVvNUSqzKuu+huF3KJtkUBkcvY2ieDPHbXY6TNDx+1z2YeTbjH/MG3u/tP3t5A/wy4kmwmZlNnR2+6fL7RrqjgVRaDAQAHFWxtaf0arm1WDEsK+X08a/PeNZbeF5+plr2+qoPbC3VOiNj21DhtJ3xTgatiR1OHtQK8YYNSXQBn85waBY0UJGsxGADAU4HwKgwG4Zvav9S7h5W2GH/Wx6FtviD4bl9sWIfRqM0p3N+B4TXUzU8Tvn9uHpmlQtxcqqJUtOIL5K16mGwnjg2HwpsiPhLsuo/p1Gmy5zIOKmiKih501YqKtFY9Zks2r674l5Mza8zV7P863Tf9qtocqqPvE6lvjPrvCS1CMmE85aWQGrogSERZGWnwxbZFrsMXGYOMKVxaynMOkIZspgcpn3msxvlWVvKtohruZL0wb4X8xZvQnmjBHQnbn27dMz0hEymQuGkAAEgWuJLWucyEOwpcDxe8bQQ65z4DAv3L8HOVd6+0qapgMxgAoDoVj11e10Hum0khZx63RBlVYu9UoXc9FWP4V/rqwNxExZVhNBwmZ4xMXmr2uQPtqhZKpcMMCzk5YuzpqLIyZ0DHsXU5BzruMIbzIM93DtDNlfLSdmhvG5CbxYlMRh0qOZYj5Y0h9smmUJVcsr1kdH1xdH1BdH0F0/X9dM02mim1eKOrJJrWiHLGyPaS0vUZdE3+c+J5S7f30zWf0lipRTpdicw5hwyG4EoTp/9qFFmowXUrqi5sIiXctrUgMitgEAtqjckGxMs5boKPauDcUn0a/JfNhvXuDr4Hth6qifu+cVjpsFpX6iP3w9nvMn6kutByExbVhJ/SNdOO1gJeZW7Ipz1W63zQxB3qwdoy9QaEqu1fHYVp/Gri/e6KOHn7adnAtAi3ntbhfA55EzzG5r6tk7c3peumADcvDO4wx//BTx/GbV8WDUzICZdkaFU7CrP6JMwdz94juFSDGQBwDIQWOtqAIWCtRslNnxn72RjpHylrpqZuJwPkxJqzqbCayr+75zVt6F1bMjW7qUSonjXO4tTpGIfMuaAslMgqbJIlP2Bm969s0afumU7bAed16vPQ6SSm8SMlNftvpt+Mmw2nHGGvCborDTRX6dNlr4W9nW1iVBqhGcmkU4A2Gq3amskcNO6zLjO9ch6iMdtdmGFtckZ0mOYE5IzPCZ6LoC0XLYITAySH69ALMfFlhbuGeCLrUadDt5NafUkVYwhKMQ1kR7Cb/NYmobmmBQAAg9HqJrcvITR7xNXIdIMYXChxB3mqLjG+CTQzXYuypekkgxbM5WrNbLSKL7k7CcEVq+4TXaVAcEXxfv1VZIJr7Kpivz64q731t+j/Fxo6l8QIL0AqRH8oQycvx+/ti+LoD5fGF//K4BOdT1Yb8CgTLB5c9sU2rQo9fS9Zv5v0uBAGAKS1WgHVuqarUe6NRjxCD9nr4mDgFzx87jRotXJwk1ITO8lV8B6phnXYS26ttapiQR29G6EPQ7wOgYkwAMBeAjIGjbaqORvgdN6Yw+tAsxWdUlS1ZPAoxBvmXbMYhSy9IR2dHGXcIZnaSWWxi+2kFg1KnaO+r8BbDTTHOuoT5q3GgHmUd57xSvpd47IX3BH6VLs8AABMo+bIMw2h5KDQgxg6JFMtVfJcSzSkn8s7O2XgdJK6JNZxbPf2VNhIrowqR00+TzroSXgd8Ow9j0LFHxkENkjCCHH3c37FPxcyK55oXS4AT2IMF3LnYmkCraLRXlmdKsfGsf7aJNoDp86UOoRHKpFVj9CtMhGNV41v1z/Inrll6QkVUakZbHOlPsi+t8gW2cecWnZ+LXuP9xKXaWc20ZiarTdyKmqGIQ4Npo737xDE9oXNWSS7bS1UBDtljaVFqqtMN96CufIkFnfH/qEKeZWz79wQNuQeUjkaBevufHF3x8nbKxaCFaypYbP3sUqpw3upuIfcR6oMd7uS83UAgOOKihhxJWXDcGXL1sMKctqZjvBq77lmAMCh+HRlW8IKTLYNV3r+X9/993aUoiTOkxT3rkDf3vyf+XuFrwKNetwKyrpbi5mL37uyfI+gu584vL2CPe/n9g+p6/ZK8lvvL3EGM65h3/n1lmjHmG0isu15X9ayVBOu+jMGSQa0yt4MjT/WLyP8nRLDJohSyuqdyXQLbtsN3kKBXbnbsBcUwXUig4O+uJwa787kARZ0EhHv5qIqNOjMg3MoFZH9V8Zg/DBPs/CTuGHgzR/VuAAADLa3/89oo68mV82D8cMcdAYuGgxG4o/DGhMACMt6j7LLU24G1vG294qtNL7OfjOxwkKXmXQVeJVKlN78UIqW05eszbSYwoX3iqAYXTQcCwAU1La2n53dhxUUOnr9O4hC1cNOsw+D3wAYL3TwmZFby4HQKCDI5I42+6Nm1egSFC+FAQA76O4ZhAAT9Gf3tufFyMuWvCbCx9+TPLq9NFjpDvZQvyLUayethS3ExXjkYr+CDltjn14/3tf6LDEPuU4fn5X2XBW3C81zF0yq4vZsDN4xtBZ0z60dAmu9qhaDAQAHh3ZnugtsGKG037Oa3r3Pll+Um9J8FkLXqs9zIUE7JZ1hrVzH3ESFbkDuvmPK9p+Z9uwH3aN7PJsq7vVNr12XGsSZ3Lp8MJNv/FXyVLkgXg3kCdsYXxvy3OoXX850St4uxuDLZMcoU4ADlJ7dZIrLY4PKISiTN6zw7qa+92GMz65grmcc0HEk+/cx+B5Jn4K/N4xmuXFldyOqsWn6kHCt0FcFP9XBzfcT+/kBXXUCnGLACoHI1sX/zqsV63KPoYQG1g3964Dbhv7VEmevBynsEMJs6aIH+A3YOQBjKIwXewqwhifIscrtDAY/vx2l+b0oHJ5DMsSJtRjMVe8PXU/djVB7XIFAzhYMeDSyuV3urD1142583+I32Z2NWc03BJI4Oo3ew1QLpql0kLYoFInsqzpYe/No6WJL4Dn5wZcML+kXj4sOt7LX9Ql5wU7+r0+eDSRPhFs9+kwzH0bC+4Q/pBCV/N9j99bG99MjXrah7FP888CcJRPL5hfHSwJBMXaHLgSlY4N0IzjVaoznicLGGehOWry0qR25IAwAcBzqHb7OglNVikjl5MVzhY6KDK8zL7uBMjNd8DkvInPTuZHbgrBoZ4BVas3fgLW0C8KuDiXagLW3bQy7loB1pH5h53pMxDpdY+cXvM5ujwPEprnO7qFLy+ZA27RDtFRDm6MjtVeBMuxHcppXmih/rS/rLcCctbfx7yMZ15v9SO74SiPnMQEAa8bfNMjlhDct5Rrvgenh+qeDXJqkLpj94kBMsHnaGi9trhsow2krprBQZvO9NzVDoivLjG2I855042Qv6qQGo5Mhh5/5ML3dtLnZge3OzGyH0JQryQo0I7gZxjW+LYQ5bWI52VmIp0k+Fmsz5PMLxRNdcW9QX9qJWIyVee04ez8dcvZGUVGVvkcKMONiZ7PfKgVm1xRcRheGApmY50MVnO7FYADAjApUp76gawCRPM8MvUGNnpbApPWVbtlHOz/R/mwbDbp1IG1Gf58TPI8RcnXELe94+9Qy08Ba1iXV6/hQ8iYuQwrQHxlA4H66IqtX5VibvGGOfThx5zD6y/G3a2GBG7kie5xiOfR6yhlFqJxXonHYV6G/PExfYCdvz6UDXYQ76syf6CFdhsdA9dW/5O0PcpEcBK+0WAEAKAHI6R1yhaEkiIUzSGr1TAM6BRAwz9VrsGQF6akykJ2bZD9B3YJnA0JEpG8MvbBYURHtVuglUAxXw2cQsVxJkYFwfS4Bu3CvEnywDFItJBPx10XMrDpvIz6qaOmFgXLEJ0wGmFVVHqhfDkdWnZysI+WchhO1CRrFpYYEtq/TaYqODxGZ5eqjqZUd7umoAICUu/DDgfPwtM0T27J+eeck+c1z4by4mQ3luluLQfW9RMBL2We4wPOaxnCciCR2ktU8FNj8Er/D/o/SH4be//bMaS23l3LG1IsVvXbULkuH3GzimLOp7o4iiFRRyXgWYAgi1VFKg+lm6J+s7cfOJnpd4D9SHW5RGABQBzTowDdhpnLYEjyPoZfC056d5+5GrnjrSvjmcHgxcZWt3DCg+GSGZM59b1DisTPZymsJIQfrklWuU38nU/qHYCyk1MgTCcO92bNlGD2Ewz/FffCn4E7Y9xMfuroecun6/G5w9+qUsx7/BdRn/2A/gOe49gdftOrTCi8BqAHSb1fOQydWHq5SsmL5ejYbTp5uaGQG1FxuBAYw5SccEFU98jfgGwcWPaqaSnh8TDp6BK7k+eWFeP++s3kQ6PK7sSSwZOMFX1iH5+gSOPi9XH+6b3Y/cBe/Njjxd3h9Lub2VIfg7m/Wkp+fFaehNuqdqY7ORDGO8ewz/p9h5vPT4qo55YurCjzaLX8STLKf3ya4xZamKR30krko8TSYZDFNOu0u7rmLOqZigLFAU5AvYd9lS8pn7Ic+RzyBW5/D3K5n5gsjJ6Lt2NBHfV5KuWVZWr71XOmHmOFbXqFzXlvpmWjWXY6UoLYL+SJh09cnt+Q3hubO8COP6War8uqA+M9XqMh1l2+vFpfL4TU4H7gWB1cBfE7g+UFteZ7vI05o+u3xUsP9UZK3bgCNNCoAAI0D6NY76sWwwgYZaQyKByN1wjQ1oHfxTuXzPe7tCgq3GAwAMFRgKBN+05NcZkfAmOepBTipzpueqSzvJEXPhN9wHt9IQGs3tlLAJ5EEH6A72McDtjmqTJBB2bEBO1WKjpk1YIdWdMvCgB2NYi6sDNhrt25EiT9gb/afYgEQx7Vvp94/l4lQs3y6CpjUYRYL6FszcVtDtcmxChhMZolEADDXAGfpIG4dgHO/+42ekjghnfPv9q0OWvv8q/5UZR8eYx/f3Bvb+L6w7/pON2u7fbO85b0+3MlVn3053tMWO4O5xmTC1TofFrnRPXjqV+QxerGjYvs5jkrsR0f07/RUYf0w5vURO62d6WOAT+g4YLNWNuULi6qrWhCPU+jskS+PeK7S4LlRhzWPfrpIJ9ILzzZo5yfpZcvwbpisaQijY3lrQK64Oq/nkHdP3AUr4aEYG/qyG18xuJYrb+j2zYsdi1sFzZjG586pDdm9b/ZVu28Ca8fKT3aktXL+4rMD4H4jsyPodkZvG7OjPnfMKFeh/TmbB1kgnkauWMd0NbZUxN/JXs5nzij+XXnBF2UTNX/7m3YL63UvByhLwwXhxY7E6cOb7J8rx/4V9POIDU/l+xnxOsT4TbQn6svnbM8VFhiirzobqG7CMllCe++j7cI3F2l9Fnpwe67vKl14wWIFACDG2yl0vCDbVVBV5mBCT8efBwLEyqMvkagiXnxaGABgxJsqw98xPJ0dgTkzzxVnlhvJ2jP0dummQxlAX+Xm2ef5idunR18xMJThcjCJIR0Cbqf687AUB0F1F29XYG9sDGpV4AjbgoYKnMQX0HSLaEPrRhmJjq0BI2ANl+jKA/LuN0k3zNWcDWcUnDBQ+h7AOTO5krUrz+cekJFCPLOL/0THPo/AKTDmixuvK0vq9Ulp3dBwnWkOLa/4R9nkfs4U+aMIo00vYzBL1SeYrb3XoZplSZPq1Mvt2iUSAcDShVxM8UOzkFaK9Q8CpveiHw20NW0tlmkafNyGfV41X7yO/PcUnp3XZ+c1DM43ifNdG/8MbPHaM7ctvH7Bfe58+qy89rq+m+ziscCOY86oWkGDYscthaWA1uVBK5rxV1p9XuVEpti6T79c8Tg7i9Gl/YPz9uvXa4xrQ7a9TcBvPdn3rNsxnjiOveaCMABAc/iioafZem8NEzrTrSm8MECeZ+JARW/YPKvz4gUe8cSeqK0GiQz5/ETRF6Y8InJsl0NmmKSmSUfPzGTmhZOJe7MtW4OchAbDdjJnvzG7bfu2xQH21EJsOTxPXp8nr2ExvnyIdPR26W1/eH5x+D6ensGb1zDs4OA6HwX4qryTBV9CT8HeStOs6KvOZqiL3kwhONHhH+b156T7iGeuqDX6s9CDb73cd5M5wHONCgCAF8CWip1N5zMV2J7S4Pq0qkRnTa1mH8XLjT6SpoF5dvCLXtcnl02dqpxH8t42gwEAvps8UZ92+ka2PkQKETOT9WOHRTjexQxntaCiMg97QDODWT2nPlXwjN+Y1fcVA0N5UfojCuMOSN76sUtoaYQkcZ5DsGRjMJweBbcIz226ZcYtwteaC7MqsHXtG6sALNASsNAEKkiqDCJpMGIJVNt96k6qusBNfp1x5rVkx2sHMvorxoZ/qfU/87VzW1T9Hqi2arYe58Xt4n/WAYCthkgunYswtQKy/iD02p+bEGyVpIofsiQOxfsnBW7rgr8iQaruFF3BbUh3SrUU7SwapCkq//ZDm2P8bd+VPw8n6NvuWj/1sZt6S3d2UOFzb/eMqosIfIhLKXYsxK2UBuOkVa1BZePpFoUBAO4YpoHRVhcsm4VdjefJ6W2KNzo7b6NS9I7T7Znw9o7D1lSeBafbBFm3W5CCM9Ayh2ZhH8yWdrkwmG2D4Qbcon3bPnDLNmLRzKJzqCt5Ps+lYuchzZfhu/7UP+Hl9g2YZmXOe1PfTU4BaSxWAADSzb7uLTXPFd7aGLxG8e7Ka2P60duYUxPgqIYwAGCKfdsWB6xcYPA2Rt4dkd5MZR4xM4ArA7QKq0uxr+YniqC4snpAsQ2CdBewJYTHQbA4DzigBqeqmNkYj/Ex+gWHh1HKDCfiYt/YBnFjC9iDgqriRCmDN7KbvaEhH7bV4/9o8iqpt0UijZeK23fqXPbwbLEu9l5qH4qOLfxsXPvOyZqOi7ptV29mkEylzceyh1rHKduSdPqEVtt98zl85h7vsomK8+M9/w++WIvOoaq8J3yCf7UYvCR8OKm+lE/yGH2CB+m5Dv6JidLoIU/mh/hiOQXtjzhatQ85YkdsD7v/8VPmJEog7ZUKj2jCxvO6LsXNCcLK7+niPQryHDEdafxurmo3xH/8VbK/jwV5rg03y/tvC9T1Rd8JKI2usEZSQgV1ss8+gJtjtpcD\",\"base64\")).toString()),z5}var RBe=new Map([[q.makeIdent(null,\"fsevents\").identHash,kBe],[q.makeIdent(null,\"resolve\").identHash,QBe],[q.makeIdent(null,\"typescript\").identHash,TBe]]),pSt={hooks:{registerPackageExtensions:async(t,e)=>{for(let[r,s]of V5)e(q.parseDescriptor(r,!0),s)},getBuiltinPatch:async(t,e)=>{let r=\"compat/\";if(!e.startsWith(r))return;let s=q.parseIdent(e.slice(r.length)),a=RBe.get(s.identHash)?.();return typeof a<\"u\"?a:null},reduceDependency:async(t,e,r,s)=>typeof RBe.get(t.identHash)>\"u\"?t:q.makeDescriptor(t,q.makeRange({protocol:\"patch:\",source:q.stringifyDescriptor(t),selector:`optional!builtin<compat/${q.stringifyIdent(t)}>`,params:null}))}},hSt=pSt;var g9={};Vt(g9,{ConstraintsCheckCommand:()=>XC,ConstraintsQueryCommand:()=>zC,ConstraintsSourceCommand:()=>ZC,default:()=>HSt});Ve();Ve();gS();var YC=class{constructor(e){this.project=e}createEnvironment(){let e=new WC([\"cwd\",\"ident\"]),r=new WC([\"workspace\",\"type\",\"ident\"]),s=new WC([\"ident\"]),a={manifestUpdates:new Map,reportedErrors:new Map},n=new Map,c=new Map;for(let f of this.project.storedPackages.values()){let p=Array.from(f.peerDependencies.values(),h=>[q.stringifyIdent(h),h.range]);n.set(f.locatorHash,{workspace:null,ident:q.stringifyIdent(f),version:f.version,dependencies:new Map,peerDependencies:new Map(p.filter(([h])=>f.peerDependenciesMeta.get(h)?.optional!==!0)),optionalPeerDependencies:new Map(p.filter(([h])=>f.peerDependenciesMeta.get(h)?.optional===!0))})}for(let f of this.project.storedPackages.values()){let p=n.get(f.locatorHash);p.dependencies=new Map(Array.from(f.dependencies.values(),h=>{let E=this.project.storedResolutions.get(h.descriptorHash);if(typeof E>\"u\")throw new Error(\"Assertion failed: The resolution should have been registered\");let C=n.get(E);if(typeof C>\"u\")throw new Error(\"Assertion failed: The package should have been registered\");return[q.stringifyIdent(h),C]})),p.dependencies.delete(p.ident)}for(let f of this.project.workspaces){let p=q.stringifyIdent(f.anchoredLocator),h=f.manifest.exportTo({}),E=n.get(f.anchoredLocator.locatorHash);if(typeof E>\"u\")throw new Error(\"Assertion failed: The package should have been registered\");let C=(R,N,{caller:U=ps.getCaller()}={})=>{let W=hS(R),te=je.getMapWithDefault(a.manifestUpdates,f.cwd),ie=je.getMapWithDefault(te,W),Ae=je.getSetWithDefault(ie,N);U!==null&&Ae.add(U)},S=R=>C(R,void 0,{caller:ps.getCaller()}),P=R=>{je.getArrayWithDefault(a.reportedErrors,f.cwd).push(R)},I=e.insert({cwd:f.relativeCwd,ident:p,manifest:h,pkg:E,set:C,unset:S,error:P});c.set(f,I);for(let R of Ht.allDependencies)for(let N of f.manifest[R].values()){let U=q.stringifyIdent(N),W=()=>{C([R,U],void 0,{caller:ps.getCaller()})},te=Ae=>{C([R,U],Ae,{caller:ps.getCaller()})},ie=null;if(R!==\"peerDependencies\"&&(R!==\"dependencies\"||!f.manifest.devDependencies.has(N.identHash))){let Ae=f.anchoredPackage.dependencies.get(N.identHash);if(Ae){if(typeof Ae>\"u\")throw new Error(\"Assertion failed: The dependency should have been registered\");let ce=this.project.storedResolutions.get(Ae.descriptorHash);if(typeof ce>\"u\")throw new Error(\"Assertion failed: The resolution should have been registered\");let me=n.get(ce);if(typeof me>\"u\")throw new Error(\"Assertion failed: The package should have been registered\");ie=me}}r.insert({workspace:I,ident:U,range:N.range,type:R,resolution:ie,update:te,delete:W,error:P})}}for(let f of this.project.storedPackages.values()){let p=this.project.tryWorkspaceByLocator(f);if(!p)continue;let h=c.get(p);if(typeof h>\"u\")throw new Error(\"Assertion failed: The workspace should have been registered\");let E=n.get(f.locatorHash);if(typeof E>\"u\")throw new Error(\"Assertion failed: The package should have been registered\");E.workspace=h}return{workspaces:e,dependencies:r,packages:s,result:a}}async process(){let e=this.createEnvironment(),r={Yarn:{workspace:a=>e.workspaces.find(a)[0]??null,workspaces:a=>e.workspaces.find(a),dependency:a=>e.dependencies.find(a)[0]??null,dependencies:a=>e.dependencies.find(a),package:a=>e.packages.find(a)[0]??null,packages:a=>e.packages.find(a)}},s=await this.project.loadUserConfig();return s?.constraints?(await s.constraints(r),e.result):null}};Ve();Ve();Wt();var zC=class extends ut{constructor(){super(...arguments);this.json=ge.Boolean(\"--json\",!1,{description:\"Format the output as an NDJSON stream\"});this.query=ge.String()}static{this.paths=[[\"constraints\",\"query\"]]}static{this.usage=ot.Usage({category:\"Constraints-related commands\",description:\"query the constraints fact database\",details:`\n      This command will output all matches to the given prolog query.\n    `,examples:[[\"List all dependencies throughout the workspace\",\"yarn constraints query 'workspace_has_dependency(_, DependencyName, _, _).'\"]]})}async execute(){let{Constraints:r}=await Promise.resolve().then(()=>(ES(),yS)),s=await ze.find(this.context.cwd,this.context.plugins),{project:a}=await Tt.find(s,this.context.cwd),n=await r.find(a),c=this.query;return c.endsWith(\".\")||(c=`${c}.`),(await Ot.start({configuration:s,json:this.json,stdout:this.context.stdout},async p=>{for await(let h of n.query(c)){let E=Array.from(Object.entries(h)),C=E.length,S=E.reduce((P,[I])=>Math.max(P,I.length),0);for(let P=0;P<C;P++){let[I,R]=E[P];p.reportInfo(null,`${_St(P,C)}${I.padEnd(S,\" \")} = ${MSt(R)}`)}p.reportJson(h)}})).exitCode()}};function MSt(t){return typeof t!=\"string\"?`${t}`:t.match(/^[a-zA-Z][a-zA-Z0-9_]+$/)?t:`'${t}'`}function _St(t,e){let r=t===0,s=t===e-1;return r&&s?\"\":r?\"\\u250C \":s?\"\\u2514 \":\"\\u2502 \"}Ve();Wt();var ZC=class extends ut{constructor(){super(...arguments);this.verbose=ge.Boolean(\"-v,--verbose\",!1,{description:\"Also print the fact database automatically compiled from the workspace manifests\"})}static{this.paths=[[\"constraints\",\"source\"]]}static{this.usage=ot.Usage({category:\"Constraints-related commands\",description:\"print the source code for the constraints\",details:\"\\n      This command will print the Prolog source code used by the constraints engine. Adding the `-v,--verbose` flag will print the *full* source code, including the fact database automatically compiled from the workspace manifests.\\n    \",examples:[[\"Prints the source code\",\"yarn constraints source\"],[\"Print the source code and the fact database\",\"yarn constraints source -v\"]]})}async execute(){let{Constraints:r}=await Promise.resolve().then(()=>(ES(),yS)),s=await ze.find(this.context.cwd,this.context.plugins),{project:a}=await Tt.find(s,this.context.cwd),n=await r.find(a);this.context.stdout.write(this.verbose?n.fullSource:n.source)}};Ve();Ve();Wt();gS();var XC=class extends ut{constructor(){super(...arguments);this.fix=ge.Boolean(\"--fix\",!1,{description:\"Attempt to automatically fix unambiguous issues, following a multi-pass process\"});this.json=ge.Boolean(\"--json\",!1,{description:\"Format the output as an NDJSON stream\"})}static{this.paths=[[\"constraints\"]]}static{this.usage=ot.Usage({category:\"Constraints-related commands\",description:\"check that the project constraints are met\",details:`\n      This command will run constraints on your project and emit errors for each one that is found but isn't met. If any error is emitted the process will exit with a non-zero exit code.\n\n      If the \\`--fix\\` flag is used, Yarn will attempt to automatically fix the issues the best it can, following a multi-pass process (with a maximum of 10 iterations). Some ambiguous patterns cannot be autofixed, in which case you'll have to manually specify the right resolution.\n\n      For more information as to how to write constraints, please consult our dedicated page on our website: https://yarnpkg.com/features/constraints.\n    `,examples:[[\"Check that all constraints are satisfied\",\"yarn constraints\"],[\"Autofix all unmet constraints\",\"yarn constraints --fix\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s}=await Tt.find(r,this.context.cwd);await s.restoreInstallState();let a=await s.loadUserConfig(),n;if(a?.constraints)n=new YC(s);else{let{Constraints:h}=await Promise.resolve().then(()=>(ES(),yS));n=await h.find(s)}let c,f=!1,p=!1;for(let h=this.fix?10:1;h>0;--h){let E=await n.process();if(!E)break;let{changedWorkspaces:C,remainingErrors:S}=oF(s,E,{fix:this.fix}),P=[];for(let[I,R]of C){let N=I.manifest.indent;I.manifest=new Ht,I.manifest.indent=N,I.manifest.load(R),P.push(I.persistManifest())}if(await Promise.all(P),!(C.size>0&&h>1)){c=UBe(S,{configuration:r}),f=!1,p=!0;for(let[,I]of S)for(let R of I)R.fixable?f=!0:p=!1}}if(c.children.length===0)return 0;if(f){let h=p?`Those errors can all be fixed by running ${he.pretty(r,\"yarn constraints --fix\",he.Type.CODE)}`:`Errors prefixed by '\\u2699' can be fixed by running ${he.pretty(r,\"yarn constraints --fix\",he.Type.CODE)}`;await Ot.start({configuration:r,stdout:this.context.stdout,includeNames:!1,includeFooter:!1},async E=>{E.reportInfo(0,h),E.reportSeparator()})}return c.children=je.sortMap(c.children,h=>h.value[1]),Qs.emitTree(c,{configuration:r,stdout:this.context.stdout,json:this.json,separators:1}),1}};gS();var USt={configuration:{enableConstraintsChecks:{description:\"If true, constraints will run during installs\",type:\"BOOLEAN\",default:!1},constraintsPath:{description:\"The path of the constraints file.\",type:\"ABSOLUTE_PATH\",default:\"./constraints.pro\"}},commands:[zC,ZC,XC],hooks:{async validateProjectAfterInstall(t,{reportError:e}){if(!t.configuration.get(\"enableConstraintsChecks\"))return;let r=await t.loadUserConfig(),s;if(r?.constraints)s=new YC(t);else{let{Constraints:c}=await Promise.resolve().then(()=>(ES(),yS));s=await c.find(t)}let a=await s.process();if(!a)return;let{remainingErrors:n}=oF(t,a);if(n.size!==0)if(t.configuration.isCI)for(let[c,f]of n)for(let p of f)e(84,`${he.pretty(t.configuration,c.anchoredLocator,he.Type.IDENT)}: ${p.text}`);else e(84,`Constraint check failed; run ${he.pretty(t.configuration,\"yarn constraints\",he.Type.CODE)} for more details`)}}},HSt=USt;var d9={};Vt(d9,{CreateCommand:()=>$C,DlxCommand:()=>ew,default:()=>qSt});Ve();Wt();var $C=class extends ut{constructor(){super(...arguments);this.pkg=ge.String(\"-p,--package\",{description:\"The package to run the provided command from\"});this.quiet=ge.Boolean(\"-q,--quiet\",!1,{description:\"Only report critical errors instead of printing the full install logs\"});this.command=ge.String();this.args=ge.Proxy()}static{this.paths=[[\"create\"]]}async execute(){let r=[];this.pkg&&r.push(\"--package\",this.pkg),this.quiet&&r.push(\"--quiet\");let s=this.command.replace(/^(@[^@/]+)(@|$)/,\"$1/create$2\"),a=q.parseDescriptor(s),n=a.name.match(/^create(-|$)/)?a:a.scope?q.makeIdent(a.scope,`create-${a.name}`):q.makeIdent(null,`create-${a.name}`),c=q.stringifyIdent(n);return a.range!==\"unknown\"&&(c+=`@${a.range}`),this.cli.run([\"dlx\",...r,c,...this.args])}};Ve();Ve();bt();Wt();var ew=class extends ut{constructor(){super(...arguments);this.packages=ge.Array(\"-p,--package\",{description:\"The package(s) to install before running the command\"});this.quiet=ge.Boolean(\"-q,--quiet\",!1,{description:\"Only report critical errors instead of printing the full install logs\"});this.command=ge.String();this.args=ge.Proxy()}static{this.paths=[[\"dlx\"]]}static{this.usage=ot.Usage({description:\"run a package in a temporary environment\",details:\"\\n      This command will install a package within a temporary environment, and run its binary script if it contains any. The binary will run within the current cwd.\\n\\n      By default Yarn will download the package named `command`, but this can be changed through the use of the `-p,--package` flag which will instruct Yarn to still run the same command but from a different package.\\n\\n      Using `yarn dlx` as a replacement of `yarn add` isn't recommended, as it makes your project non-deterministic (Yarn doesn't keep track of the packages installed through `dlx` - neither their name, nor their version).\\n    \",examples:[[\"Use create-vite to scaffold a new Vite project\",\"yarn dlx create-vite\"],[\"Install multiple packages for a single command\",`yarn dlx -p typescript -p ts-node ts-node --transpile-only -e \"console.log('hello!')\"`]]})}async execute(){return ze.telemetry=null,await le.mktempPromise(async r=>{let s=K.join(r,`dlx-${process.pid}`);await le.mkdirPromise(s),await le.writeFilePromise(K.join(s,\"package.json\"),`{}\n`),await le.writeFilePromise(K.join(s,\"yarn.lock\"),\"\");let a=K.join(s,\".yarnrc.yml\"),n=await ze.findProjectCwd(this.context.cwd),f={enableGlobalCache:!(await ze.find(this.context.cwd,null,{strict:!1})).get(\"enableGlobalCache\"),enableTelemetry:!1,logFilters:[{code:Vf(68),level:he.LogLevel.Discard}]},p=n!==null?K.join(n,\".yarnrc.yml\"):null;p!==null&&le.existsSync(p)?(await le.copyFilePromise(p,a),await ze.updateConfiguration(s,N=>{let U=je.toMerged(N,f);return Array.isArray(N.plugins)&&(U.plugins=N.plugins.map(W=>{let te=typeof W==\"string\"?W:W.path,ie=ue.isAbsolute(te)?te:ue.resolve(ue.fromPortablePath(n),te);return typeof W==\"string\"?ie:{path:ie,spec:W.spec}})),U})):await le.writeJsonPromise(a,f);let h=this.packages??[this.command],E=q.parseDescriptor(this.command).name,C=await this.cli.run([\"add\",\"--fixed\",\"--\",...h],{cwd:s,quiet:this.quiet});if(C!==0)return C;this.quiet||this.context.stdout.write(`\n`);let S=await ze.find(s,this.context.plugins),{project:P,workspace:I}=await Tt.find(S,s);if(I===null)throw new ar(P.cwd,s);await P.restoreInstallState();let R=await In.getWorkspaceAccessibleBinaries(I);return R.has(E)===!1&&R.size===1&&typeof this.packages>\"u\"&&(E=Array.from(R)[0][0]),await In.executeWorkspaceAccessibleBinary(I,E,this.args,{packageAccessibleBinaries:R,cwd:this.context.cwd,stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr})})}};var jSt={commands:[$C,ew]},qSt=jSt;var E9={};Vt(E9,{ExecFetcher:()=>CS,ExecResolver:()=>wS,default:()=>YSt,execUtils:()=>uF});Ve();Ve();bt();var fA=\"exec:\";var uF={};Vt(uF,{loadGeneratorFile:()=>IS,makeLocator:()=>y9,makeSpec:()=>Ave,parseSpec:()=>m9});Ve();bt();function m9(t){let{params:e,selector:r}=q.parseRange(t),s=ue.toPortablePath(r);return{parentLocator:e&&typeof e.locator==\"string\"?q.parseLocator(e.locator):null,path:s}}function Ave({parentLocator:t,path:e,generatorHash:r,protocol:s}){let a=t!==null?{locator:q.stringifyLocator(t)}:{},n=typeof r<\"u\"?{hash:r}:{};return q.makeRange({protocol:s,source:e,selector:e,params:{...n,...a}})}function y9(t,{parentLocator:e,path:r,generatorHash:s,protocol:a}){return q.makeLocator(t,Ave({parentLocator:e,path:r,generatorHash:s,protocol:a}))}async function IS(t,e,r){let{parentLocator:s,path:a}=q.parseFileStyleRange(t,{protocol:e}),n=K.isAbsolute(a)?{packageFs:new Sn(vt.root),prefixPath:vt.dot,localPath:vt.root}:await r.fetcher.fetch(s,r),c=n.localPath?{packageFs:new Sn(vt.root),prefixPath:K.relative(vt.root,n.localPath)}:n;n!==c&&n.releaseFs&&n.releaseFs();let f=c.packageFs,p=K.join(c.prefixPath,a);return await f.readFilePromise(p,\"utf8\")}var CS=class{supports(e,r){return!!e.reference.startsWith(fA)}getLocalPath(e,r){let{parentLocator:s,path:a}=q.parseFileStyleRange(e.reference,{protocol:fA});if(K.isAbsolute(a))return a;let n=r.fetcher.getLocalPath(s,r);return n===null?null:K.resolve(n,a)}async fetch(e,r){let s=r.checksums.get(e.locatorHash)||null,[a,n,c]=await r.cache.fetchPackageFromCache(e,s,{onHit:()=>r.report.reportCacheHit(e),onMiss:()=>r.report.reportCacheMiss(e),loader:()=>this.fetchFromDisk(e,r),...r.cacheOptions});return{packageFs:a,releaseFs:n,prefixPath:q.getIdentVendorPath(e),localPath:this.getLocalPath(e,r),checksum:c}}async fetchFromDisk(e,r){let s=await IS(e.reference,fA,r);return le.mktempPromise(async a=>{let n=K.join(a,\"generator.js\");return await le.writeFilePromise(n,s),le.mktempPromise(async c=>{if(await this.generatePackage(c,e,n,r),!le.existsSync(K.join(c,\"build\")))throw new Error(\"The script should have generated a build directory\");return await gs.makeArchiveFromDirectory(K.join(c,\"build\"),{prefixPath:q.getIdentVendorPath(e),compressionLevel:r.project.configuration.get(\"compressionLevel\")})})})}async generatePackage(e,r,s,a){return await le.mktempPromise(async n=>{let c=await In.makeScriptEnv({project:a.project,binFolder:n}),f=K.join(e,\"runtime.js\");return await le.mktempPromise(async p=>{let h=K.join(p,\"buildfile.log\"),E=K.join(e,\"generator\"),C=K.join(e,\"build\");await le.mkdirPromise(E),await le.mkdirPromise(C);let S={tempDir:ue.fromPortablePath(E),buildDir:ue.fromPortablePath(C),locator:q.stringifyLocator(r)};await le.writeFilePromise(f,`\n          // Expose 'Module' as a global variable\n          Object.defineProperty(global, 'Module', {\n            get: () => require('module'),\n            configurable: true,\n            enumerable: false,\n          });\n\n          // Expose non-hidden built-in modules as global variables\n          for (const name of Module.builtinModules.filter((name) => name !== 'module' && !name.startsWith('_'))) {\n            Object.defineProperty(global, name, {\n              get: () => require(name),\n              configurable: true,\n              enumerable: false,\n            });\n          }\n\n          // Expose the 'execEnv' global variable\n          Object.defineProperty(global, 'execEnv', {\n            value: {\n              ...${JSON.stringify(S)},\n            },\n            enumerable: true,\n          });\n        `);let P=c.NODE_OPTIONS||\"\",I=/\\s*--require\\s+\\S*\\.pnp\\.c?js\\s*/g;P=P.replace(I,\" \").trim(),c.NODE_OPTIONS=P;let{stdout:R,stderr:N}=a.project.configuration.getSubprocessStreams(h,{header:`# This file contains the result of Yarn generating a package (${q.stringifyLocator(r)})\n`,prefix:q.prettyLocator(a.project.configuration,r),report:a.report}),{code:U}=await Gr.pipevp(process.execPath,[\"--require\",ue.fromPortablePath(f),ue.fromPortablePath(s),q.stringifyIdent(r)],{cwd:e,env:c,stdin:null,stdout:R,stderr:N});if(U!==0)throw le.detachTemp(p),new Error(`Package generation failed (exit code ${U}, logs can be found here: ${he.pretty(a.project.configuration,h,he.Type.PATH)})`)})})}};Ve();Ve();var GSt=2,wS=class{supportsDescriptor(e,r){return!!e.range.startsWith(fA)}supportsLocator(e,r){return!!e.reference.startsWith(fA)}shouldPersistResolution(e,r){return!1}bindDescriptor(e,r,s){return q.bindDescriptor(e,{locator:q.stringifyLocator(r)})}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){if(!s.fetchOptions)throw new Error(\"Assertion failed: This resolver cannot be used unless a fetcher is configured\");let{path:a,parentLocator:n}=m9(e.range);if(n===null)throw new Error(\"Assertion failed: The descriptor should have been bound\");let c=await IS(q.makeRange({protocol:fA,source:a,selector:a,params:{locator:q.stringifyLocator(n)}}),fA,s.fetchOptions),f=Nn.makeHash(`${GSt}`,c).slice(0,6);return[y9(e,{parentLocator:n,path:a,generatorHash:f,protocol:fA})]}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){if(!r.fetchOptions)throw new Error(\"Assertion failed: This resolver cannot be used unless a fetcher is configured\");let s=await r.fetchOptions.fetcher.fetch(e,r.fetchOptions),a=await je.releaseAfterUseAsync(async()=>await Ht.find(s.prefixPath,{baseFs:s.packageFs}),s.releaseFs);return{...e,version:a.version||\"0.0.0\",languageName:a.languageName||r.project.configuration.get(\"defaultLanguageName\"),linkType:\"HARD\",conditions:a.getConditions(),dependencies:r.project.configuration.normalizeDependencyMap(a.dependencies),peerDependencies:a.peerDependencies,dependenciesMeta:a.dependenciesMeta,peerDependenciesMeta:a.peerDependenciesMeta,bin:a.bin}}};var WSt={fetchers:[CS],resolvers:[wS]},YSt=WSt;var C9={};Vt(C9,{FileFetcher:()=>DS,FileResolver:()=>bS,TarballFileFetcher:()=>PS,TarballFileResolver:()=>xS,default:()=>JSt,fileUtils:()=>Pm});Ve();bt();var tw=/^(?:[a-zA-Z]:[\\\\/]|\\.{0,2}\\/)/,BS=/^[^?]*\\.(?:tar\\.gz|tgz)(?:::.*)?$/,ts=\"file:\";var Pm={};Vt(Pm,{fetchArchiveFromLocator:()=>SS,makeArchiveFromLocator:()=>fF,makeBufferFromLocator:()=>I9,makeLocator:()=>rw,makeSpec:()=>pve,parseSpec:()=>vS});Ve();bt();function vS(t){let{params:e,selector:r}=q.parseRange(t),s=ue.toPortablePath(r);return{parentLocator:e&&typeof e.locator==\"string\"?q.parseLocator(e.locator):null,path:s}}function pve({parentLocator:t,path:e,hash:r,protocol:s}){let a=t!==null?{locator:q.stringifyLocator(t)}:{},n=typeof r<\"u\"?{hash:r}:{};return q.makeRange({protocol:s,source:e,selector:e,params:{...n,...a}})}function rw(t,{parentLocator:e,path:r,hash:s,protocol:a}){return q.makeLocator(t,pve({parentLocator:e,path:r,hash:s,protocol:a}))}async function SS(t,e){let{parentLocator:r,path:s}=q.parseFileStyleRange(t.reference,{protocol:ts}),a=K.isAbsolute(s)?{packageFs:new Sn(vt.root),prefixPath:vt.dot,localPath:vt.root}:await e.fetcher.fetch(r,e),n=a.localPath?{packageFs:new Sn(vt.root),prefixPath:K.relative(vt.root,a.localPath)}:a;a!==n&&a.releaseFs&&a.releaseFs();let c=n.packageFs,f=K.join(n.prefixPath,s);return await je.releaseAfterUseAsync(async()=>await c.readFilePromise(f),n.releaseFs)}async function fF(t,{protocol:e,fetchOptions:r,inMemory:s=!1}){let{parentLocator:a,path:n}=q.parseFileStyleRange(t.reference,{protocol:e}),c=K.isAbsolute(n)?{packageFs:new Sn(vt.root),prefixPath:vt.dot,localPath:vt.root}:await r.fetcher.fetch(a,r),f=c.localPath?{packageFs:new Sn(vt.root),prefixPath:K.relative(vt.root,c.localPath)}:c;c!==f&&c.releaseFs&&c.releaseFs();let p=f.packageFs,h=K.join(f.prefixPath,n);return await je.releaseAfterUseAsync(async()=>await gs.makeArchiveFromDirectory(h,{baseFs:p,prefixPath:q.getIdentVendorPath(t),compressionLevel:r.project.configuration.get(\"compressionLevel\"),inMemory:s}),f.releaseFs)}async function I9(t,{protocol:e,fetchOptions:r}){return(await fF(t,{protocol:e,fetchOptions:r,inMemory:!0})).getBufferAndClose()}var DS=class{supports(e,r){return!!e.reference.startsWith(ts)}getLocalPath(e,r){let{parentLocator:s,path:a}=q.parseFileStyleRange(e.reference,{protocol:ts});if(K.isAbsolute(a))return a;let n=r.fetcher.getLocalPath(s,r);return n===null?null:K.resolve(n,a)}async fetch(e,r){let s=r.checksums.get(e.locatorHash)||null,[a,n,c]=await r.cache.fetchPackageFromCache(e,s,{onHit:()=>r.report.reportCacheHit(e),onMiss:()=>r.report.reportCacheMiss(e,`${q.prettyLocator(r.project.configuration,e)} can't be found in the cache and will be fetched from the disk`),loader:()=>this.fetchFromDisk(e,r),...r.cacheOptions});return{packageFs:a,releaseFs:n,prefixPath:q.getIdentVendorPath(e),localPath:this.getLocalPath(e,r),checksum:c}}async fetchFromDisk(e,r){return fF(e,{protocol:ts,fetchOptions:r})}};Ve();Ve();var VSt=2,bS=class{supportsDescriptor(e,r){return e.range.match(tw)?!0:!!e.range.startsWith(ts)}supportsLocator(e,r){return!!e.reference.startsWith(ts)}shouldPersistResolution(e,r){return!1}bindDescriptor(e,r,s){return tw.test(e.range)&&(e=q.makeDescriptor(e,`${ts}${e.range}`)),q.bindDescriptor(e,{locator:q.stringifyLocator(r)})}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){if(!s.fetchOptions)throw new Error(\"Assertion failed: This resolver cannot be used unless a fetcher is configured\");let{path:a,parentLocator:n}=vS(e.range);if(n===null)throw new Error(\"Assertion failed: The descriptor should have been bound\");let c=await I9(q.makeLocator(e,q.makeRange({protocol:ts,source:a,selector:a,params:{locator:q.stringifyLocator(n)}})),{protocol:ts,fetchOptions:s.fetchOptions}),f=Nn.makeHash(`${VSt}`,c).slice(0,6);return[rw(e,{parentLocator:n,path:a,hash:f,protocol:ts})]}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){if(!r.fetchOptions)throw new Error(\"Assertion failed: This resolver cannot be used unless a fetcher is configured\");let s=await r.fetchOptions.fetcher.fetch(e,r.fetchOptions),a=await je.releaseAfterUseAsync(async()=>await Ht.find(s.prefixPath,{baseFs:s.packageFs}),s.releaseFs);return{...e,version:a.version||\"0.0.0\",languageName:a.languageName||r.project.configuration.get(\"defaultLanguageName\"),linkType:\"HARD\",conditions:a.getConditions(),dependencies:r.project.configuration.normalizeDependencyMap(a.dependencies),peerDependencies:a.peerDependencies,dependenciesMeta:a.dependenciesMeta,peerDependenciesMeta:a.peerDependenciesMeta,bin:a.bin}}};Ve();var PS=class{supports(e,r){return BS.test(e.reference)?!!e.reference.startsWith(ts):!1}getLocalPath(e,r){return null}async fetch(e,r){let s=r.checksums.get(e.locatorHash)||null,[a,n,c]=await r.cache.fetchPackageFromCache(e,s,{onHit:()=>r.report.reportCacheHit(e),onMiss:()=>r.report.reportCacheMiss(e,`${q.prettyLocator(r.project.configuration,e)} can't be found in the cache and will be fetched from the disk`),loader:()=>this.fetchFromDisk(e,r),...r.cacheOptions});return{packageFs:a,releaseFs:n,prefixPath:q.getIdentVendorPath(e),checksum:c}}async fetchFromDisk(e,r){let s=await SS(e,r);return await gs.convertToZip(s,{configuration:r.project.configuration,prefixPath:q.getIdentVendorPath(e),stripComponents:1})}};Ve();Ve();Ve();var xS=class{supportsDescriptor(e,r){return BS.test(e.range)?!!(e.range.startsWith(ts)||tw.test(e.range)):!1}supportsLocator(e,r){return BS.test(e.reference)?!!e.reference.startsWith(ts):!1}shouldPersistResolution(e,r){return!1}bindDescriptor(e,r,s){return tw.test(e.range)&&(e=q.makeDescriptor(e,`${ts}${e.range}`)),q.bindDescriptor(e,{locator:q.stringifyLocator(r)})}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){if(!s.fetchOptions)throw new Error(\"Assertion failed: This resolver cannot be used unless a fetcher is configured\");let{path:a,parentLocator:n}=vS(e.range);if(n===null)throw new Error(\"Assertion failed: The descriptor should have been bound\");let c=rw(e,{parentLocator:n,path:a,hash:\"\",protocol:ts}),f=await SS(c,s.fetchOptions),p=Nn.makeHash(f).slice(0,6);return[rw(e,{parentLocator:n,path:a,hash:p,protocol:ts})]}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){if(!r.fetchOptions)throw new Error(\"Assertion failed: This resolver cannot be used unless a fetcher is configured\");let s=await r.fetchOptions.fetcher.fetch(e,r.fetchOptions),a=await je.releaseAfterUseAsync(async()=>await Ht.find(s.prefixPath,{baseFs:s.packageFs}),s.releaseFs);return{...e,version:a.version||\"0.0.0\",languageName:a.languageName||r.project.configuration.get(\"defaultLanguageName\"),linkType:\"HARD\",conditions:a.getConditions(),dependencies:r.project.configuration.normalizeDependencyMap(a.dependencies),peerDependencies:a.peerDependencies,dependenciesMeta:a.dependenciesMeta,peerDependenciesMeta:a.peerDependenciesMeta,bin:a.bin}}};var KSt={fetchers:[PS,DS],resolvers:[xS,bS]},JSt=KSt;var v9={};Vt(v9,{GithubFetcher:()=>kS,default:()=>ZSt,githubUtils:()=>AF});Ve();bt();var AF={};Vt(AF,{invalidGithubUrlMessage:()=>dve,isGithubUrl:()=>w9,parseGithubUrl:()=>B9});var hve=et(Ie(\"querystring\")),gve=[/^https?:\\/\\/(?:([^/]+?)@)?github.com\\/([^/#]+)\\/([^/#]+)\\/tarball\\/([^/#]+)(?:#(.*))?$/,/^https?:\\/\\/(?:([^/]+?)@)?github.com\\/([^/#]+)\\/([^/#]+?)(?:\\.git)?(?:#(.*))?$/];function w9(t){return t?gve.some(e=>!!t.match(e)):!1}function B9(t){let e;for(let f of gve)if(e=t.match(f),e)break;if(!e)throw new Error(dve(t));let[,r,s,a,n=\"master\"]=e,{commit:c}=hve.default.parse(n);return n=c||n.replace(/[^:]*:/,\"\"),{auth:r,username:s,reponame:a,treeish:n}}function dve(t){return`Input cannot be parsed as a valid GitHub URL ('${t}').`}var kS=class{supports(e,r){return!!w9(e.reference)}getLocalPath(e,r){return null}async fetch(e,r){let s=r.checksums.get(e.locatorHash)||null,[a,n,c]=await r.cache.fetchPackageFromCache(e,s,{onHit:()=>r.report.reportCacheHit(e),onMiss:()=>r.report.reportCacheMiss(e,`${q.prettyLocator(r.project.configuration,e)} can't be found in the cache and will be fetched from GitHub`),loader:()=>this.fetchFromNetwork(e,r),...r.cacheOptions});return{packageFs:a,releaseFs:n,prefixPath:q.getIdentVendorPath(e),checksum:c}}async fetchFromNetwork(e,r){let s=await An.get(this.getLocatorUrl(e,r),{configuration:r.project.configuration});return await le.mktempPromise(async a=>{let n=new Sn(a);await gs.extractArchiveTo(s,n,{stripComponents:1});let c=Qa.splitRepoUrl(e.reference),f=K.join(a,\"package.tgz\");await In.prepareExternalProject(a,f,{configuration:r.project.configuration,report:r.report,workspace:c.extra.workspace,locator:e});let p=await le.readFilePromise(f);return await gs.convertToZip(p,{configuration:r.project.configuration,prefixPath:q.getIdentVendorPath(e),stripComponents:1})})}getLocatorUrl(e,r){let{auth:s,username:a,reponame:n,treeish:c}=B9(e.reference);return`https://${s?`${s}@`:\"\"}github.com/${a}/${n}/archive/${c}.tar.gz`}};var zSt={hooks:{async fetchHostedRepository(t,e,r){if(t!==null)return t;let s=new kS;if(!s.supports(e,r))return null;try{return await s.fetch(e,r)}catch{return null}}}},ZSt=zSt;var S9={};Vt(S9,{TarballHttpFetcher:()=>TS,TarballHttpResolver:()=>RS,default:()=>$St});Ve();function QS(t){let e;try{e=new URL(t)}catch{return!1}return!(e.protocol!==\"http:\"&&e.protocol!==\"https:\"||!e.pathname.match(/(\\.tar\\.gz|\\.tgz|\\/[^.]+)$/))}var TS=class{supports(e,r){return QS(e.reference)}getLocalPath(e,r){return null}async fetch(e,r){let s=r.checksums.get(e.locatorHash)||null,[a,n,c]=await r.cache.fetchPackageFromCache(e,s,{onHit:()=>r.report.reportCacheHit(e),onMiss:()=>r.report.reportCacheMiss(e,`${q.prettyLocator(r.project.configuration,e)} can't be found in the cache and will be fetched from the remote server`),loader:()=>this.fetchFromNetwork(e,r),...r.cacheOptions});return{packageFs:a,releaseFs:n,prefixPath:q.getIdentVendorPath(e),checksum:c}}async fetchFromNetwork(e,r){let s=await An.get(e.reference,{configuration:r.project.configuration});return await gs.convertToZip(s,{configuration:r.project.configuration,prefixPath:q.getIdentVendorPath(e),stripComponents:1})}};Ve();Ve();var RS=class{supportsDescriptor(e,r){return QS(e.range)}supportsLocator(e,r){return QS(e.reference)}shouldPersistResolution(e,r){return!0}bindDescriptor(e,r,s){return e}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){return[q.convertDescriptorToLocator(e)]}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){if(!r.fetchOptions)throw new Error(\"Assertion failed: This resolver cannot be used unless a fetcher is configured\");let s=await r.fetchOptions.fetcher.fetch(e,r.fetchOptions),a=await je.releaseAfterUseAsync(async()=>await Ht.find(s.prefixPath,{baseFs:s.packageFs}),s.releaseFs);return{...e,version:a.version||\"0.0.0\",languageName:a.languageName||r.project.configuration.get(\"defaultLanguageName\"),linkType:\"HARD\",conditions:a.getConditions(),dependencies:r.project.configuration.normalizeDependencyMap(a.dependencies),peerDependencies:a.peerDependencies,dependenciesMeta:a.dependenciesMeta,peerDependenciesMeta:a.peerDependenciesMeta,bin:a.bin}}};var XSt={fetchers:[TS],resolvers:[RS]},$St=XSt;var D9={};Vt(D9,{InitCommand:()=>J0,InitInitializerCommand:()=>nw,default:()=>tDt});Wt();Ve();Ve();bt();Wt();var J0=class extends ut{constructor(){super(...arguments);this.private=ge.Boolean(\"-p,--private\",!1,{description:\"Initialize a private package\"});this.workspace=ge.Boolean(\"-w,--workspace\",!1,{description:\"Initialize a workspace root with a `packages/` directory\"});this.install=ge.String(\"-i,--install\",!1,{tolerateBoolean:!0,description:\"Initialize a package with a specific bundle that will be locked in the project\"});this.name=ge.String(\"-n,--name\",{description:\"Initialize a package with the given name\"});this.usev2=ge.Boolean(\"-2\",!1,{hidden:!0});this.yes=ge.Boolean(\"-y,--yes\",{hidden:!0})}static{this.paths=[[\"init\"]]}static{this.usage=ot.Usage({description:\"create a new package\",details:\"\\n      This command will setup a new package in your local directory.\\n\\n      If the `-p,--private` or `-w,--workspace` options are set, the package will be private by default.\\n\\n      If the `-w,--workspace` option is set, the package will be configured to accept a set of workspaces in the `packages/` directory.\\n\\n      If the `-i,--install` option is given a value, Yarn will first download it using `yarn set version` and only then forward the init call to the newly downloaded bundle. Without arguments, the downloaded bundle will be `latest`.\\n\\n      The initial settings of the manifest can be changed by using the `initScope` and `initFields` configuration values. Additionally, Yarn will generate an EditorConfig file whose rules can be altered via `initEditorConfig`, and will initialize a Git repository in the current directory.\\n    \",examples:[[\"Create a new package in the local directory\",\"yarn init\"],[\"Create a new private package in the local directory\",\"yarn init -p\"],[\"Create a new package and store the Yarn release inside\",\"yarn init -i=latest\"],[\"Create a new private package and defines it as a workspace root\",\"yarn init -w\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),s=typeof this.install==\"string\"?this.install:this.usev2||this.install===!0?\"latest\":null;return s!==null?await this.executeProxy(r,s):await this.executeRegular(r)}async executeProxy(r,s){if(r.projectCwd!==null&&r.projectCwd!==this.context.cwd)throw new nt(\"Cannot use the --install flag from within a project subdirectory\");le.existsSync(this.context.cwd)||await le.mkdirPromise(this.context.cwd,{recursive:!0});let a=K.join(this.context.cwd,Er.lockfile);le.existsSync(a)||await le.writeFilePromise(a,\"\");let n=await this.cli.run([\"set\",\"version\",s],{quiet:!0});if(n!==0)return n;let c=[];return this.private&&c.push(\"-p\"),this.workspace&&c.push(\"-w\"),this.name&&c.push(`-n=${this.name}`),this.yes&&c.push(\"-y\"),await le.mktempPromise(async f=>{let{code:p}=await Gr.pipevp(\"yarn\",[\"init\",...c],{cwd:this.context.cwd,stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr,env:await In.makeScriptEnv({binFolder:f})});return p})}async initialize(){}async executeRegular(r){let s=null;try{s=(await Tt.find(r,this.context.cwd)).project}catch{s=null}le.existsSync(this.context.cwd)||await le.mkdirPromise(this.context.cwd,{recursive:!0});let a=await Ht.tryFind(this.context.cwd),n=a??new Ht,c=Object.fromEntries(r.get(\"initFields\").entries());n.load(c),n.name=n.name??q.makeIdent(r.get(\"initScope\"),this.name??K.basename(this.context.cwd)),n.packageManager=un&&je.isTaggedYarnVersion(un)?`yarn@${un}`:null,(!a&&this.workspace||this.private)&&(n.private=!0),this.workspace&&n.workspaceDefinitions.length===0&&(await le.mkdirPromise(K.join(this.context.cwd,\"packages\"),{recursive:!0}),n.workspaceDefinitions=[{pattern:\"packages/*\"}]);let f={};n.exportTo(f);let p=K.join(this.context.cwd,Ht.fileName);await le.changeFilePromise(p,`${JSON.stringify(f,null,2)}\n`,{automaticNewlines:!0});let h=[p],E=K.join(this.context.cwd,\"README.md\");if(le.existsSync(E)||(await le.writeFilePromise(E,`# ${q.stringifyIdent(n.name)}\n`),h.push(E)),!s||s.cwd===this.context.cwd){let C=K.join(this.context.cwd,Er.lockfile);le.existsSync(C)||(await le.writeFilePromise(C,\"\"),h.push(C));let P=[\".yarn/*\",\"!.yarn/patches\",\"!.yarn/plugins\",\"!.yarn/releases\",\"!.yarn/sdks\",\"!.yarn/versions\",\"\",\"# Whether you use PnP or not, the node_modules folder is often used to store\",\"# build artifacts that should be gitignored\",\"node_modules\",\"\",\"# Swap the comments on the following lines if you wish to use zero-installs\",\"# In that case, don't forget to run `yarn config set enableGlobalCache false`!\",\"# Documentation here: https://yarnpkg.com/features/caching#zero-installs\",\"\",\"#!.yarn/cache\",\".pnp.*\"].map(Ae=>`${Ae}\n`).join(\"\"),I=K.join(this.context.cwd,\".gitignore\");le.existsSync(I)||(await le.writeFilePromise(I,P),h.push(I));let N=[\"/.yarn/**            linguist-vendored\",\"/.yarn/releases/*    binary\",\"/.yarn/plugins/**/*  binary\",\"/.pnp.*              binary linguist-generated\"].map(Ae=>`${Ae}\n`).join(\"\"),U=K.join(this.context.cwd,\".gitattributes\");le.existsSync(U)||(await le.writeFilePromise(U,N),h.push(U));let W={\"*\":{charset:\"utf-8\",endOfLine:\"lf\",indentSize:2,indentStyle:\"space\",insertFinalNewline:!0}};je.mergeIntoTarget(W,r.get(\"initEditorConfig\"));let te=`root = true\n`;for(let[Ae,ce]of Object.entries(W)){te+=`\n[${Ae}]\n`;for(let[me,pe]of Object.entries(ce)){let Be=me.replace(/[A-Z]/g,Ce=>`_${Ce.toLowerCase()}`);te+=`${Be} = ${pe}\n`}}let ie=K.join(this.context.cwd,\".editorconfig\");le.existsSync(ie)||(await le.writeFilePromise(ie,te),h.push(ie)),await this.cli.run([\"install\"],{quiet:!0}),await this.initialize(),le.existsSync(K.join(this.context.cwd,\".git\"))||(await Gr.execvp(\"git\",[\"init\"],{cwd:this.context.cwd}),await Gr.execvp(\"git\",[\"add\",\"--\",...h],{cwd:this.context.cwd}),await Gr.execvp(\"git\",[\"commit\",\"--allow-empty\",\"-m\",\"First commit\"],{cwd:this.context.cwd}))}}};var nw=class extends J0{constructor(){super(...arguments);this.initializer=ge.String();this.argv=ge.Proxy()}static{this.paths=[[\"init\"]]}async initialize(){this.context.stdout.write(`\n`),await this.cli.run([\"dlx\",this.initializer,...this.argv],{quiet:!0})}};var eDt={configuration:{initScope:{description:\"Scope used when creating packages via the init command\",type:\"STRING\",default:null},initFields:{description:\"Additional fields to set when creating packages via the init command\",type:\"MAP\",valueDefinition:{description:\"\",type:\"ANY\"}},initEditorConfig:{description:\"Extra rules to define in the generator editorconfig\",type:\"MAP\",valueDefinition:{description:\"\",type:\"ANY\"}}},commands:[J0,nw]},tDt=eDt;var IY={};Vt(IY,{SearchCommand:()=>Iw,UpgradeInteractiveCommand:()=>Cw,default:()=>yQt});Ve();var yve=et(Ie(\"os\"));function iw({stdout:t}){if(yve.default.endianness()===\"BE\")throw new Error(\"Interactive commands cannot be used on big-endian systems because ink depends on yoga-layout-prebuilt which only supports little-endian architectures\");if(!t.isTTY)throw new Error(\"Interactive commands can only be used inside a TTY environment\")}Wt();var kSe=et(G9()),W9={appId:\"OFCNCOG2CU\",apiKey:\"6fe4476ee5a1832882e326b506d14126\",indexName:\"npm-search\"},Xbt=(0,kSe.default)(W9.appId,W9.apiKey).initIndex(W9.indexName),Y9=async(t,e=0)=>await Xbt.search(t,{analyticsTags:[\"yarn-plugin-interactive-tools\"],attributesToRetrieve:[\"name\",\"version\",\"owner\",\"repository\",\"humanDownloadsLast30Days\"],page:e,hitsPerPage:10});var QD=[\"regular\",\"dev\",\"peer\"],Iw=class extends ut{static{this.paths=[[\"search\"]]}static{this.usage=ot.Usage({category:\"Interactive commands\",description:\"open the search interface\",details:`\n    This command opens a fullscreen terminal interface where you can search for and install packages from the npm registry.\n    `,examples:[[\"Open the search window\",\"yarn search\"]]})}async execute(){iw(this.context);let{Gem:e}=await Promise.resolve().then(()=>(YF(),cY)),{ScrollableItems:r}=await Promise.resolve().then(()=>(zF(),JF)),{useKeypress:s}=await Promise.resolve().then(()=>(PD(),DPe)),{useMinistore:a}=await Promise.resolve().then(()=>(gY(),hY)),{renderForm:n}=await Promise.resolve().then(()=>(eN(),$F)),{default:c}=await Promise.resolve().then(()=>et(OPe())),{Box:f,Text:p}=await Promise.resolve().then(()=>et(Vc())),{default:h,useEffect:E,useState:C}=await Promise.resolve().then(()=>et(hn())),S=await ze.find(this.context.cwd,this.context.plugins),P=()=>h.createElement(f,{flexDirection:\"row\"},h.createElement(f,{flexDirection:\"column\",width:48},h.createElement(f,null,h.createElement(p,null,\"Press \",h.createElement(p,{bold:!0,color:\"cyanBright\"},\"<up>\"),\"/\",h.createElement(p,{bold:!0,color:\"cyanBright\"},\"<down>\"),\" to move between packages.\")),h.createElement(f,null,h.createElement(p,null,\"Press \",h.createElement(p,{bold:!0,color:\"cyanBright\"},\"<space>\"),\" to select a package.\")),h.createElement(f,null,h.createElement(p,null,\"Press \",h.createElement(p,{bold:!0,color:\"cyanBright\"},\"<space>\"),\" again to change the target.\"))),h.createElement(f,{flexDirection:\"column\"},h.createElement(f,{marginLeft:1},h.createElement(p,null,\"Press \",h.createElement(p,{bold:!0,color:\"cyanBright\"},\"<enter>\"),\" to install the selected packages.\")),h.createElement(f,{marginLeft:1},h.createElement(p,null,\"Press \",h.createElement(p,{bold:!0,color:\"cyanBright\"},\"<ctrl+c>\"),\" to abort.\")))),I=()=>h.createElement(h.Fragment,null,h.createElement(f,{width:15},h.createElement(p,{bold:!0,underline:!0,color:\"gray\"},\"Owner\")),h.createElement(f,{width:11},h.createElement(p,{bold:!0,underline:!0,color:\"gray\"},\"Version\")),h.createElement(f,{width:10},h.createElement(p,{bold:!0,underline:!0,color:\"gray\"},\"Downloads\"))),R=()=>h.createElement(f,{width:17},h.createElement(p,{bold:!0,underline:!0,color:\"gray\"},\"Target\")),N=({hit:pe,active:Be})=>{let[Ce,g]=a(pe.name,null);s({active:Be},(fe,se)=>{if(se.name!==\"space\")return;if(!Ce){g(QD[0]);return}let X=QD.indexOf(Ce)+1;X===QD.length?g(null):g(QD[X])},[Ce,g]);let we=q.parseIdent(pe.name),ye=q.prettyIdent(S,we);return h.createElement(f,null,h.createElement(f,{width:45},h.createElement(p,{bold:!0,wrap:\"wrap\"},ye)),h.createElement(f,{width:14,marginLeft:1},h.createElement(p,{bold:!0,wrap:\"truncate\"},pe.owner.name)),h.createElement(f,{width:10,marginLeft:1},h.createElement(p,{italic:!0,wrap:\"truncate\"},pe.version)),h.createElement(f,{width:16,marginLeft:1},h.createElement(p,null,pe.humanDownloadsLast30Days)))},U=({name:pe,active:Be})=>{let[Ce]=a(pe,null),g=q.parseIdent(pe);return h.createElement(f,null,h.createElement(f,{width:47},h.createElement(p,{bold:!0},\" - \",q.prettyIdent(S,g))),QD.map(we=>h.createElement(f,{key:we,width:14,marginLeft:1},h.createElement(p,null,\" \",h.createElement(e,{active:Ce===we}),\" \",h.createElement(p,{bold:!0},we)))))},W=()=>h.createElement(f,{marginTop:1},h.createElement(p,null,\"Powered by Algolia.\")),ie=await n(({useSubmit:pe})=>{let Be=a();pe(Be);let Ce=Array.from(Be.keys()).filter(j=>Be.get(j)!==null),[g,we]=C(\"\"),[ye,fe]=C(0),[se,X]=C([]),De=j=>{j.match(/\\t| /)||we(j)},Re=async()=>{fe(0);let j=await Y9(g);j.query===g&&X(j.hits)},dt=async()=>{let j=await Y9(g,ye+1);j.query===g&&j.page-1===ye&&(fe(j.page),X([...se,...j.hits]))};return E(()=>{g?Re():X([])},[g]),h.createElement(f,{flexDirection:\"column\"},h.createElement(P,null),h.createElement(f,{flexDirection:\"row\",marginTop:1},h.createElement(p,{bold:!0},\"Search: \"),h.createElement(f,{width:41},h.createElement(c,{value:g,onChange:De,placeholder:\"i.e. babel, webpack, react...\",showCursor:!1})),h.createElement(I,null)),se.length?h.createElement(r,{radius:2,loop:!1,children:se.map(j=>h.createElement(N,{key:j.name,hit:j,active:!1})),willReachEnd:dt}):h.createElement(p,{color:\"gray\"},\"Start typing...\"),h.createElement(f,{flexDirection:\"row\",marginTop:1},h.createElement(f,{width:49},h.createElement(p,{bold:!0},\"Selected:\")),h.createElement(R,null)),Ce.length?Ce.map(j=>h.createElement(U,{key:j,name:j,active:!1})):h.createElement(p,{color:\"gray\"},\"No selected packages...\"),h.createElement(W,null))},{},{stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr});if(typeof ie>\"u\")return 1;let Ae=Array.from(ie.keys()).filter(pe=>ie.get(pe)===\"regular\"),ce=Array.from(ie.keys()).filter(pe=>ie.get(pe)===\"dev\"),me=Array.from(ie.keys()).filter(pe=>ie.get(pe)===\"peer\");return Ae.length&&await this.cli.run([\"add\",...Ae]),ce.length&&await this.cli.run([\"add\",\"--dev\",...ce]),me&&await this.cli.run([\"add\",\"--peer\",...me]),0}};Ve();Wt();fG();var qPe=et(Ai()),jPe=/^((?:[\\^~]|>=?)?)([0-9]+)(\\.[0-9]+)(\\.[0-9]+)((?:-\\S+)?)$/;function GPe(t,e){return t.length>0?[t.slice(0,e)].concat(GPe(t.slice(e),e)):[]}var Cw=class extends ut{static{this.paths=[[\"upgrade-interactive\"]]}static{this.usage=ot.Usage({category:\"Interactive commands\",description:\"open the upgrade interface\",details:`\n      This command opens a fullscreen terminal interface where you can see any out of date packages used by your application, their status compared to the latest versions available on the remote registry, and select packages to upgrade.\n    `,examples:[[\"Open the upgrade window\",\"yarn upgrade-interactive\"]]})}async execute(){iw(this.context);let{ItemOptions:e}=await Promise.resolve().then(()=>(HPe(),UPe)),{Pad:r}=await Promise.resolve().then(()=>(EY(),_Pe)),{ScrollableItems:s}=await Promise.resolve().then(()=>(zF(),JF)),{useMinistore:a}=await Promise.resolve().then(()=>(gY(),hY)),{renderForm:n}=await Promise.resolve().then(()=>(eN(),$F)),{Box:c,Text:f}=await Promise.resolve().then(()=>et(Vc())),{default:p,useEffect:h,useRef:E,useState:C}=await Promise.resolve().then(()=>et(hn())),S=await ze.find(this.context.cwd,this.context.plugins),{project:P,workspace:I}=await Tt.find(S,this.context.cwd),R=await Jr.find(S);if(!I)throw new ar(P.cwd,this.context.cwd);await P.restoreInstallState({restoreResolutions:!1});let N=this.context.stdout.rows-7,U=(we,ye)=>{let fe=cCe(we,ye),se=\"\";for(let X of fe)X.added?se+=he.pretty(S,X.value,\"green\"):X.removed||(se+=X.value);return se},W=(we,ye)=>{if(we===ye)return ye;let fe=q.parseRange(we),se=q.parseRange(ye),X=fe.selector.match(jPe),De=se.selector.match(jPe);if(!X||!De)return U(we,ye);let Re=[\"gray\",\"red\",\"yellow\",\"green\",\"magenta\"],dt=null,j=\"\";for(let rt=1;rt<Re.length;++rt)dt!==null||X[rt]!==De[rt]?(dt===null&&(dt=Re[rt-1]),j+=he.pretty(S,De[rt],dt)):j+=De[rt];return j},te=async(we,ye,fe)=>{let se=await Xu.fetchDescriptorFrom(we,fe,{project:P,cache:R,preserveModifier:ye,workspace:I});return se!==null?se.range:we.range},ie=async we=>{let ye=qPe.default.valid(we.range)?`^${we.range}`:we.range,[fe,se]=await Promise.all([te(we,we.range,ye).catch(()=>null),te(we,we.range,\"latest\").catch(()=>null)]),X=[{value:null,label:we.range}];return fe&&fe!==we.range?X.push({value:fe,label:W(we.range,fe)}):X.push({value:null,label:\"\"}),se&&se!==fe&&se!==we.range?X.push({value:se,label:W(we.range,se)}):X.push({value:null,label:\"\"}),X},Ae=()=>p.createElement(c,{flexDirection:\"row\"},p.createElement(c,{flexDirection:\"column\",width:49},p.createElement(c,{marginLeft:1},p.createElement(f,null,\"Press \",p.createElement(f,{bold:!0,color:\"cyanBright\"},\"<up>\"),\"/\",p.createElement(f,{bold:!0,color:\"cyanBright\"},\"<down>\"),\" to select packages.\")),p.createElement(c,{marginLeft:1},p.createElement(f,null,\"Press \",p.createElement(f,{bold:!0,color:\"cyanBright\"},\"<left>\"),\"/\",p.createElement(f,{bold:!0,color:\"cyanBright\"},\"<right>\"),\" to select versions.\"))),p.createElement(c,{flexDirection:\"column\"},p.createElement(c,{marginLeft:1},p.createElement(f,null,\"Press \",p.createElement(f,{bold:!0,color:\"cyanBright\"},\"<enter>\"),\" to install.\")),p.createElement(c,{marginLeft:1},p.createElement(f,null,\"Press \",p.createElement(f,{bold:!0,color:\"cyanBright\"},\"<ctrl+c>\"),\" to abort.\")))),ce=()=>p.createElement(c,{flexDirection:\"row\",paddingTop:1,paddingBottom:1},p.createElement(c,{width:50},p.createElement(f,{bold:!0},p.createElement(f,{color:\"greenBright\"},\"?\"),\" Pick the packages you want to upgrade.\")),p.createElement(c,{width:17},p.createElement(f,{bold:!0,underline:!0,color:\"gray\"},\"Current\")),p.createElement(c,{width:17},p.createElement(f,{bold:!0,underline:!0,color:\"gray\"},\"Range\")),p.createElement(c,{width:17},p.createElement(f,{bold:!0,underline:!0,color:\"gray\"},\"Latest\"))),me=({active:we,descriptor:ye,suggestions:fe})=>{let[se,X]=a(ye.descriptorHash,null),De=q.stringifyIdent(ye),Re=Math.max(0,45-De.length);return p.createElement(p.Fragment,null,p.createElement(c,null,p.createElement(c,{width:45},p.createElement(f,{bold:!0},q.prettyIdent(S,ye)),p.createElement(r,{active:we,length:Re})),p.createElement(e,{active:we,options:fe,value:se,skewer:!0,onChange:X,sizes:[17,17,17]})))},pe=({dependencies:we})=>{let[ye,fe]=C(we.map(()=>null)),se=E(!0),X=async De=>{let Re=await ie(De);return Re.filter(dt=>dt.label!==\"\").length<=1?null:{descriptor:De,suggestions:Re}};return h(()=>()=>{se.current=!1},[]),h(()=>{let De=Math.trunc(N*1.75),Re=we.slice(0,De),dt=we.slice(De),j=GPe(dt,N),rt=Re.map(X).reduce(async(Fe,Ne)=>{await Fe;let Pe=await Ne;Pe!==null&&se.current&&fe(Ye=>{let ke=Ye.findIndex(_e=>_e===null),it=[...Ye];return it[ke]=Pe,it})},Promise.resolve());j.reduce((Fe,Ne)=>Promise.all(Ne.map(Pe=>Promise.resolve().then(()=>X(Pe)))).then(async Pe=>{Pe=Pe.filter(Ye=>Ye!==null),await Fe,se.current&&fe(Ye=>{let ke=Ye.findIndex(it=>it===null);return Ye.slice(0,ke).concat(Pe).concat(Ye.slice(ke+Pe.length))})}),rt).then(()=>{se.current&&fe(Fe=>Fe.filter(Ne=>Ne!==null))})},[]),ye.length?p.createElement(s,{radius:N>>1,children:ye.map((De,Re)=>De!==null?p.createElement(me,{key:Re,active:!1,descriptor:De.descriptor,suggestions:De.suggestions}):p.createElement(f,{key:Re},\"Loading...\"))}):p.createElement(f,null,\"No upgrades found\")},Ce=await n(({useSubmit:we})=>{we(a());let ye=new Map;for(let se of P.workspaces)for(let X of[\"dependencies\",\"devDependencies\"])for(let De of se.manifest[X].values())P.tryWorkspaceByDescriptor(De)===null&&(De.range.startsWith(\"link:\")||ye.set(De.descriptorHash,De));let fe=je.sortMap(ye.values(),se=>q.stringifyDescriptor(se));return p.createElement(c,{flexDirection:\"column\"},p.createElement(Ae,null),p.createElement(ce,null),p.createElement(pe,{dependencies:fe}))},{},{stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr});if(typeof Ce>\"u\")return 1;let g=!1;for(let we of P.workspaces)for(let ye of[\"dependencies\",\"devDependencies\"]){let fe=we.manifest[ye];for(let se of fe.values()){let X=Ce.get(se.descriptorHash);typeof X<\"u\"&&X!==null&&(fe.set(se.identHash,q.makeDescriptor(se,X)),g=!0)}}return g?await P.installWithNewReport({quiet:this.context.quiet,stdout:this.context.stdout},{cache:R}):0}};var mQt={commands:[Iw,Cw]},yQt=mQt;var wY={};Vt(wY,{default:()=>wQt});Ve();var RD=\"jsr:\";Ve();Ve();function ww(t){let e=t.range.slice(4);if(Or.validRange(e))return q.makeDescriptor(t,`npm:${q.stringifyIdent(q.wrapIdentIntoScope(t,\"jsr\"))}@${e}`);let r=q.tryParseDescriptor(e,!0);if(r!==null)return q.makeDescriptor(t,`npm:${q.stringifyIdent(q.wrapIdentIntoScope(r,\"jsr\"))}@${r.range}`);throw new Error(`Invalid range: ${t.range}`)}function Bw(t){return q.makeLocator(q.wrapIdentIntoScope(t,\"jsr\"),`npm:${t.reference.slice(4)}`)}function CY(t){return q.makeLocator(q.unwrapIdentFromScope(t,\"jsr\"),`jsr:${t.reference.slice(4)}`)}var tN=class{supports(e,r){return e.reference.startsWith(RD)}getLocalPath(e,r){let s=Bw(e);return r.fetcher.getLocalPath(s,r)}fetch(e,r){let s=Bw(e);return r.fetcher.fetch(s,r)}};var rN=class{supportsDescriptor(e,r){return!!e.range.startsWith(RD)}supportsLocator(e,r){return!!e.reference.startsWith(RD)}shouldPersistResolution(e,r){let s=Bw(e);return r.resolver.shouldPersistResolution(s,r)}bindDescriptor(e,r,s){return e}getResolutionDependencies(e,r){return{inner:ww(e)}}async getCandidates(e,r,s){let a=s.project.configuration.normalizeDependency(ww(e));return(await s.resolver.getCandidates(a,r,s)).map(c=>CY(c))}async getSatisfying(e,r,s,a){let n=a.project.configuration.normalizeDependency(ww(e));return a.resolver.getSatisfying(n,r,s,a)}async resolve(e,r){let s=Bw(e),a=await r.resolver.resolve(s,r);return{...a,...CY(a)}}};var EQt=[\"dependencies\",\"devDependencies\",\"peerDependencies\"];function IQt(t,e){for(let r of EQt)for(let s of t.manifest.getForScope(r).values()){if(!s.range.startsWith(\"jsr:\"))continue;let a=ww(s),n=r===\"dependencies\"?q.makeDescriptor(s,\"unknown\"):null,c=n!==null&&t.manifest.ensureDependencyMeta(n).optional?\"optionalDependencies\":r;e[c][q.stringifyIdent(s)]=a.range}}var CQt={hooks:{beforeWorkspacePacking:IQt},resolvers:[rN],fetchers:[tN]},wQt=CQt;var BY={};Vt(BY,{LinkFetcher:()=>FD,LinkResolver:()=>ND,PortalFetcher:()=>OD,PortalResolver:()=>LD,default:()=>vQt});Ve();bt();var ih=\"portal:\",sh=\"link:\";var FD=class{supports(e,r){return!!e.reference.startsWith(sh)}getLocalPath(e,r){let{parentLocator:s,path:a}=q.parseFileStyleRange(e.reference,{protocol:sh});if(K.isAbsolute(a))return a;let n=r.fetcher.getLocalPath(s,r);return n===null?null:K.resolve(n,a)}async fetch(e,r){let{parentLocator:s,path:a}=q.parseFileStyleRange(e.reference,{protocol:sh}),n=K.isAbsolute(a)?{packageFs:new Sn(vt.root),prefixPath:vt.dot,localPath:vt.root}:await r.fetcher.fetch(s,r),c=n.localPath?{packageFs:new Sn(vt.root),prefixPath:K.relative(vt.root,n.localPath),localPath:vt.root}:n;n!==c&&n.releaseFs&&n.releaseFs();let f=c.packageFs,p=K.resolve(c.localPath??c.packageFs.getRealPath(),c.prefixPath,a);return n.localPath?{packageFs:new Sn(p,{baseFs:f}),releaseFs:c.releaseFs,prefixPath:vt.dot,discardFromLookup:!0,localPath:p}:{packageFs:new jf(p,{baseFs:f}),releaseFs:c.releaseFs,prefixPath:vt.dot,discardFromLookup:!0}}};Ve();bt();var ND=class{supportsDescriptor(e,r){return!!e.range.startsWith(sh)}supportsLocator(e,r){return!!e.reference.startsWith(sh)}shouldPersistResolution(e,r){return!1}bindDescriptor(e,r,s){return q.bindDescriptor(e,{locator:q.stringifyLocator(r)})}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){let a=e.range.slice(sh.length);return[q.makeLocator(e,`${sh}${ue.toPortablePath(a)}`)]}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){return{...e,version:\"0.0.0\",languageName:r.project.configuration.get(\"defaultLanguageName\"),linkType:\"SOFT\",conditions:null,dependencies:new Map,peerDependencies:new Map,dependenciesMeta:new Map,peerDependenciesMeta:new Map,bin:new Map}}};Ve();bt();var OD=class{supports(e,r){return!!e.reference.startsWith(ih)}getLocalPath(e,r){let{parentLocator:s,path:a}=q.parseFileStyleRange(e.reference,{protocol:ih});if(K.isAbsolute(a))return a;let n=r.fetcher.getLocalPath(s,r);return n===null?null:K.resolve(n,a)}async fetch(e,r){let{parentLocator:s,path:a}=q.parseFileStyleRange(e.reference,{protocol:ih}),n=K.isAbsolute(a)?{packageFs:new Sn(vt.root),prefixPath:vt.dot,localPath:vt.root}:await r.fetcher.fetch(s,r),c=n.localPath?{packageFs:new Sn(vt.root),prefixPath:K.relative(vt.root,n.localPath),localPath:vt.root}:n;n!==c&&n.releaseFs&&n.releaseFs();let f=c.packageFs,p=K.resolve(c.localPath??c.packageFs.getRealPath(),c.prefixPath,a);return n.localPath?{packageFs:new Sn(p,{baseFs:f}),releaseFs:c.releaseFs,prefixPath:vt.dot,localPath:p}:{packageFs:new jf(p,{baseFs:f}),releaseFs:c.releaseFs,prefixPath:vt.dot}}};Ve();Ve();bt();var LD=class{supportsDescriptor(e,r){return!!e.range.startsWith(ih)}supportsLocator(e,r){return!!e.reference.startsWith(ih)}shouldPersistResolution(e,r){return!1}bindDescriptor(e,r,s){return q.bindDescriptor(e,{locator:q.stringifyLocator(r)})}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){let a=e.range.slice(ih.length);return[q.makeLocator(e,`${ih}${ue.toPortablePath(a)}`)]}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){if(!r.fetchOptions)throw new Error(\"Assertion failed: This resolver cannot be used unless a fetcher is configured\");let s=await r.fetchOptions.fetcher.fetch(e,r.fetchOptions),a=await je.releaseAfterUseAsync(async()=>await Ht.find(s.prefixPath,{baseFs:s.packageFs}),s.releaseFs);return{...e,version:a.version||\"0.0.0\",languageName:a.languageName||r.project.configuration.get(\"defaultLanguageName\"),linkType:\"SOFT\",conditions:a.getConditions(),dependencies:r.project.configuration.normalizeDependencyMap(a.dependencies),peerDependencies:a.peerDependencies,dependenciesMeta:a.dependenciesMeta,peerDependenciesMeta:a.peerDependenciesMeta,bin:a.bin}}};var BQt={fetchers:[FD,OD],resolvers:[ND,LD]},vQt=BQt;var oV={};Vt(oV,{NodeModulesLinker:()=>XD,NodeModulesMode:()=>rV,PnpLooseLinker:()=>$D,default:()=>HTt});bt();Ve();bt();bt();var SY=(t,e)=>`${t}@${e}`,WPe=(t,e)=>{let r=e.indexOf(\"#\"),s=r>=0?e.substring(r+1):e;return SY(t,s)};var VPe=(t,e={})=>{let r=e.debugLevel||Number(process.env.NM_DEBUG_LEVEL||-1),s=e.check||r>=9,a=e.hoistingLimits||new Map,n={check:s,debugLevel:r,hoistingLimits:a,fastLookupPossible:!0},c;n.debugLevel>=0&&(c=Date.now());let f=QQt(t,n),p=!1,h=0;do{let E=DY(f,[f],new Set([f.locator]),new Map,n);p=E.anotherRoundNeeded||E.isGraphChanged,n.fastLookupPossible=!1,h++}while(p);if(n.debugLevel>=0&&console.log(`hoist time: ${Date.now()-c}ms, rounds: ${h}`),n.debugLevel>=1){let E=MD(f);if(DY(f,[f],new Set([f.locator]),new Map,n).isGraphChanged)throw new Error(`The hoisting result is not terminal, prev tree:\n${E}, next tree:\n${MD(f)}`);let S=KPe(f);if(S)throw new Error(`${S}, after hoisting finished:\n${MD(f)}`)}return n.debugLevel>=2&&console.log(MD(f)),TQt(f)},SQt=t=>{let e=t[t.length-1],r=new Map,s=new Set,a=n=>{if(!s.has(n)){s.add(n);for(let c of n.hoistedDependencies.values())r.set(c.name,c);for(let c of n.dependencies.values())n.peerNames.has(c.name)||a(c)}};return a(e),r},DQt=t=>{let e=t[t.length-1],r=new Map,s=new Set,a=new Set,n=(c,f)=>{if(s.has(c))return;s.add(c);for(let h of c.hoistedDependencies.values())if(!f.has(h.name)){let E;for(let C of t)E=C.dependencies.get(h.name),E&&r.set(E.name,E)}let p=new Set;for(let h of c.dependencies.values())p.add(h.name);for(let h of c.dependencies.values())c.peerNames.has(h.name)||n(h,p)};return n(e,a),r},YPe=(t,e)=>{if(e.decoupled)return e;let{name:r,references:s,ident:a,locator:n,dependencies:c,originalDependencies:f,hoistedDependencies:p,peerNames:h,reasons:E,isHoistBorder:C,hoistPriority:S,dependencyKind:P,hoistedFrom:I,hoistedTo:R}=e,N={name:r,references:new Set(s),ident:a,locator:n,dependencies:new Map(c),originalDependencies:new Map(f),hoistedDependencies:new Map(p),peerNames:new Set(h),reasons:new Map(E),decoupled:!0,isHoistBorder:C,hoistPriority:S,dependencyKind:P,hoistedFrom:new Map(I),hoistedTo:new Map(R)},U=N.dependencies.get(r);return U&&U.ident==N.ident&&N.dependencies.set(r,N),t.dependencies.set(N.name,N),N},bQt=(t,e)=>{let r=new Map([[t.name,[t.ident]]]);for(let a of t.dependencies.values())t.peerNames.has(a.name)||r.set(a.name,[a.ident]);let s=Array.from(e.keys());s.sort((a,n)=>{let c=e.get(a),f=e.get(n);if(f.hoistPriority!==c.hoistPriority)return f.hoistPriority-c.hoistPriority;{let p=c.dependents.size+c.peerDependents.size;return f.dependents.size+f.peerDependents.size-p}});for(let a of s){let n=a.substring(0,a.indexOf(\"@\",1)),c=a.substring(n.length+1);if(!t.peerNames.has(n)){let f=r.get(n);f||(f=[],r.set(n,f)),f.indexOf(c)<0&&f.push(c)}}return r},vY=t=>{let e=new Set,r=(s,a=new Set)=>{if(!a.has(s)){a.add(s);for(let n of s.peerNames)if(!t.peerNames.has(n)){let c=t.dependencies.get(n);c&&!e.has(c)&&r(c,a)}e.add(s)}};for(let s of t.dependencies.values())t.peerNames.has(s.name)||r(s);return e},DY=(t,e,r,s,a,n=new Set)=>{let c=e[e.length-1];if(n.has(c))return{anotherRoundNeeded:!1,isGraphChanged:!1};n.add(c);let f=RQt(c),p=bQt(c,f),h=t==c?new Map:a.fastLookupPossible?SQt(e):DQt(e),E,C=!1,S=!1,P=new Map(Array.from(p.entries()).map(([R,N])=>[R,N[0]])),I=new Map;do{let R=kQt(t,e,r,h,P,p,s,I,a);R.isGraphChanged&&(S=!0),R.anotherRoundNeeded&&(C=!0),E=!1;for(let[N,U]of p)U.length>1&&!c.dependencies.has(N)&&(P.delete(N),U.shift(),P.set(N,U[0]),E=!0)}while(E);for(let R of c.dependencies.values())if(!c.peerNames.has(R.name)&&!r.has(R.locator)){r.add(R.locator);let N=DY(t,[...e,R],r,I,a);N.isGraphChanged&&(S=!0),N.anotherRoundNeeded&&(C=!0),r.delete(R.locator)}return{anotherRoundNeeded:C,isGraphChanged:S}},PQt=t=>{for(let[e,r]of t.dependencies)if(!t.peerNames.has(e)&&r.ident!==t.ident)return!0;return!1},xQt=(t,e,r,s,a,n,c,f,{outputReason:p,fastLookupPossible:h})=>{let E,C=null,S=new Set;p&&(E=`${Array.from(e).map(N=>Io(N)).join(\"\\u2192\")}`);let P=r[r.length-1],R=!(s.ident===P.ident);if(p&&!R&&(C=\"- self-reference\"),R&&(R=s.dependencyKind!==1,p&&!R&&(C=\"- workspace\")),R&&s.dependencyKind===2&&(R=!PQt(s),p&&!R&&(C=\"- external soft link with unhoisted dependencies\")),R&&(R=!t.peerNames.has(s.name),p&&!R&&(C=`- cannot shadow peer: ${Io(t.originalDependencies.get(s.name).locator)} at ${E}`)),R){let N=!1,U=a.get(s.name);if(N=!U||U.ident===s.ident,p&&!N&&(C=`- filled by: ${Io(U.locator)} at ${E}`),N)for(let W=r.length-1;W>=1;W--){let ie=r[W].dependencies.get(s.name);if(ie&&ie.ident!==s.ident){N=!1;let Ae=f.get(P);Ae||(Ae=new Set,f.set(P,Ae)),Ae.add(s.name),p&&(C=`- filled by ${Io(ie.locator)} at ${r.slice(0,W).map(ce=>Io(ce.locator)).join(\"\\u2192\")}`);break}}R=N}if(R&&(R=n.get(s.name)===s.ident,p&&!R&&(C=`- filled by: ${Io(c.get(s.name)[0])} at ${E}`)),R){let N=!0,U=new Set(s.peerNames);for(let W=r.length-1;W>=1;W--){let te=r[W];for(let ie of U){if(te.peerNames.has(ie)&&te.originalDependencies.has(ie))continue;let Ae=te.dependencies.get(ie);Ae&&t.dependencies.get(ie)!==Ae&&(W===r.length-1?S.add(Ae):(S=null,N=!1,p&&(C=`- peer dependency ${Io(Ae.locator)} from parent ${Io(te.locator)} was not hoisted to ${E}`))),U.delete(ie)}if(!N)break}R=N}if(R&&!h)for(let N of s.hoistedDependencies.values()){let U=a.get(N.name)||t.dependencies.get(N.name);if(!U||N.ident!==U.ident){R=!1,p&&(C=`- previously hoisted dependency mismatch, needed: ${Io(N.locator)}, available: ${Io(U?.locator)}`);break}}return S!==null&&S.size>0?{isHoistable:2,dependsOn:S,reason:C}:{isHoistable:R?0:1,reason:C}},nN=t=>`${t.name}@${t.locator}`,kQt=(t,e,r,s,a,n,c,f,p)=>{let h=e[e.length-1],E=new Set,C=!1,S=!1,P=(U,W,te,ie,Ae)=>{if(E.has(ie))return;let ce=[...W,nN(ie)],me=[...te,nN(ie)],pe=new Map,Be=new Map;for(let fe of vY(ie)){let se=xQt(h,r,[h,...U,ie],fe,s,a,n,f,{outputReason:p.debugLevel>=2,fastLookupPossible:p.fastLookupPossible});if(Be.set(fe,se),se.isHoistable===2)for(let X of se.dependsOn){let De=pe.get(X.name)||new Set;De.add(fe.name),pe.set(X.name,De)}}let Ce=new Set,g=(fe,se,X)=>{if(!Ce.has(fe)){Ce.add(fe),Be.set(fe,{isHoistable:1,reason:X});for(let De of pe.get(fe.name)||[])g(ie.dependencies.get(De),se,p.debugLevel>=2?`- peer dependency ${Io(fe.locator)} from parent ${Io(ie.locator)} was not hoisted`:\"\")}};for(let[fe,se]of Be)se.isHoistable===1&&g(fe,se,se.reason);let we=!1;for(let fe of Be.keys())if(!Ce.has(fe)){S=!0;let se=c.get(ie);se&&se.has(fe.name)&&(C=!0),we=!0,ie.dependencies.delete(fe.name),ie.hoistedDependencies.set(fe.name,fe),ie.reasons.delete(fe.name);let X=h.dependencies.get(fe.name);if(p.debugLevel>=2){let De=Array.from(W).concat([ie.locator]).map(dt=>Io(dt)).join(\"\\u2192\"),Re=h.hoistedFrom.get(fe.name);Re||(Re=[],h.hoistedFrom.set(fe.name,Re)),Re.push(De),ie.hoistedTo.set(fe.name,Array.from(e).map(dt=>Io(dt.locator)).join(\"\\u2192\"))}if(!X)h.ident!==fe.ident&&(h.dependencies.set(fe.name,fe),Ae.add(fe));else for(let De of fe.references)X.references.add(De)}if(ie.dependencyKind===2&&we&&(C=!0),p.check){let fe=KPe(t);if(fe)throw new Error(`${fe}, after hoisting dependencies of ${[h,...U,ie].map(se=>Io(se.locator)).join(\"\\u2192\")}:\n${MD(t)}`)}let ye=vY(ie);for(let fe of ye)if(Ce.has(fe)){let se=Be.get(fe);if((a.get(fe.name)===fe.ident||!ie.reasons.has(fe.name))&&se.isHoistable!==0&&ie.reasons.set(fe.name,se.reason),!fe.isHoistBorder&&me.indexOf(nN(fe))<0){E.add(ie);let De=YPe(ie,fe);P([...U,ie],ce,me,De,R),E.delete(ie)}}},I,R=new Set(vY(h)),N=Array.from(e).map(U=>nN(U));do{I=R,R=new Set;for(let U of I){if(U.locator===h.locator||U.isHoistBorder)continue;let W=YPe(h,U);P([],Array.from(r),N,W,R)}}while(R.size>0);return{anotherRoundNeeded:C,isGraphChanged:S}},KPe=t=>{let e=[],r=new Set,s=new Set,a=(n,c,f)=>{if(r.has(n)||(r.add(n),s.has(n)))return;let p=new Map(c);for(let h of n.dependencies.values())n.peerNames.has(h.name)||p.set(h.name,h);for(let h of n.originalDependencies.values()){let E=p.get(h.name),C=()=>`${Array.from(s).concat([n]).map(S=>Io(S.locator)).join(\"\\u2192\")}`;if(n.peerNames.has(h.name)){let S=c.get(h.name);(S!==E||!S||S.ident!==h.ident)&&e.push(`${C()} - broken peer promise: expected ${h.ident} but found ${S&&S.ident}`)}else{let S=f.hoistedFrom.get(n.name),P=n.hoistedTo.get(h.name),I=`${S?` hoisted from ${S.join(\", \")}`:\"\"}`,R=`${P?` hoisted to ${P}`:\"\"}`,N=`${C()}${I}`;E?E.ident!==h.ident&&e.push(`${N} - broken require promise for ${h.name}${R}: expected ${h.ident}, but found: ${E.ident}`):e.push(`${N} - broken require promise: no required dependency ${h.name}${R} found`)}}s.add(n);for(let h of n.dependencies.values())n.peerNames.has(h.name)||a(h,p,n);s.delete(n)};return a(t,t.dependencies,t),e.join(`\n`)},QQt=(t,e)=>{let{identName:r,name:s,reference:a,peerNames:n}=t,c={name:s,references:new Set([a]),locator:SY(r,a),ident:WPe(r,a),dependencies:new Map,originalDependencies:new Map,hoistedDependencies:new Map,peerNames:new Set(n),reasons:new Map,decoupled:!0,isHoistBorder:!0,hoistPriority:0,dependencyKind:1,hoistedFrom:new Map,hoistedTo:new Map},f=new Map([[t,c]]),p=(h,E)=>{let C=f.get(h),S=!!C;if(!C){let{name:P,identName:I,reference:R,peerNames:N,hoistPriority:U,dependencyKind:W}=h,te=e.hoistingLimits.get(E.locator);C={name:P,references:new Set([R]),locator:SY(I,R),ident:WPe(I,R),dependencies:new Map,originalDependencies:new Map,hoistedDependencies:new Map,peerNames:new Set(N),reasons:new Map,decoupled:!0,isHoistBorder:te?te.has(P):!1,hoistPriority:U||0,dependencyKind:W||0,hoistedFrom:new Map,hoistedTo:new Map},f.set(h,C)}if(E.dependencies.set(h.name,C),E.originalDependencies.set(h.name,C),S){let P=new Set,I=R=>{if(!P.has(R)){P.add(R),R.decoupled=!1;for(let N of R.dependencies.values())R.peerNames.has(N.name)||I(N)}};I(C)}else for(let P of h.dependencies)p(P,C)};for(let h of t.dependencies)p(h,c);return c},bY=t=>t.substring(0,t.indexOf(\"@\",1)),TQt=t=>{let e={name:t.name,identName:bY(t.locator),references:new Set(t.references),dependencies:new Set},r=new Set([t]),s=(a,n,c)=>{let f=r.has(a),p;if(n===a)p=c;else{let{name:h,references:E,locator:C}=a;p={name:h,identName:bY(C),references:E,dependencies:new Set}}if(c.dependencies.add(p),!f){r.add(a);for(let h of a.dependencies.values())a.peerNames.has(h.name)||s(h,a,p);r.delete(a)}};for(let a of t.dependencies.values())s(a,t,e);return e},RQt=t=>{let e=new Map,r=new Set([t]),s=c=>`${c.name}@${c.ident}`,a=c=>{let f=s(c),p=e.get(f);return p||(p={dependents:new Set,peerDependents:new Set,hoistPriority:0},e.set(f,p)),p},n=(c,f)=>{let p=!!r.has(f);if(a(f).dependents.add(c.ident),!p){r.add(f);for(let E of f.dependencies.values()){let C=a(E);C.hoistPriority=Math.max(C.hoistPriority,E.hoistPriority),f.peerNames.has(E.name)?C.peerDependents.add(f.ident):n(f,E)}}};for(let c of t.dependencies.values())t.peerNames.has(c.name)||n(t,c);return e},Io=t=>{if(!t)return\"none\";let e=t.indexOf(\"@\",1),r=t.substring(0,e);r.endsWith(\"$wsroot$\")&&(r=`wh:${r.replace(\"$wsroot$\",\"\")}`);let s=t.substring(e+1);if(s===\"workspace:.\")return\".\";if(s){let a=(s.indexOf(\"#\")>0?s.split(\"#\")[1]:s).replace(\"npm:\",\"\");return s.startsWith(\"virtual\")&&(r=`v:${r}`),a.startsWith(\"workspace\")&&(r=`w:${r}`,a=\"\"),`${r}${a?`@${a}`:\"\"}`}else return`${r}`};var MD=t=>{let e=0,r=(a,n,c=\"\")=>{if(e>5e4||n.has(a))return\"\";e++;let f=Array.from(a.dependencies.values()).sort((h,E)=>h.name===E.name?0:h.name>E.name?1:-1),p=\"\";n.add(a);for(let h=0;h<f.length;h++){let E=f[h];if(!a.peerNames.has(E.name)&&E!==a){let C=a.reasons.get(E.name),S=bY(E.locator);p+=`${c}${h<f.length-1?\"\\u251C\\u2500\":\"\\u2514\\u2500\"}${(n.has(E)?\">\":\"\")+(S!==E.name?`a:${E.name}:`:\"\")+Io(E.locator)+(C?` ${C}`:\"\")}\n`,p+=r(E,n,`${c}${h<f.length-1?\"\\u2502 \":\"  \"}`)}}return n.delete(a),p};return r(t,new Set)+(e>5e4?`\nTree is too large, part of the tree has been dunped\n`:\"\")};var _D=(s=>(s.WORKSPACES=\"workspaces\",s.DEPENDENCIES=\"dependencies\",s.NONE=\"none\",s))(_D||{}),JPe=\"node_modules\",tg=\"$wsroot$\";var UD=(t,e)=>{let{packageTree:r,hoistingLimits:s,errors:a,preserveSymlinksRequired:n}=NQt(t,e),c=null;if(a.length===0){let f=VPe(r,{hoistingLimits:s});c=LQt(t,f,e)}return{tree:c,errors:a,preserveSymlinksRequired:n}},gA=t=>`${t.name}@${t.reference}`,xY=t=>{let e=new Map;for(let[r,s]of t.entries())if(!s.dirList){let a=e.get(s.locator);a||(a={target:s.target,linkType:s.linkType,locations:[],aliases:s.aliases},e.set(s.locator,a)),a.locations.push(r)}for(let r of e.values())r.locations=r.locations.sort((s,a)=>{let n=s.split(K.delimiter).length,c=a.split(K.delimiter).length;return a===s?0:n!==c?c-n:a>s?1:-1});return e},zPe=(t,e)=>{let r=q.isVirtualLocator(t)?q.devirtualizeLocator(t):t,s=q.isVirtualLocator(e)?q.devirtualizeLocator(e):e;return q.areLocatorsEqual(r,s)},PY=(t,e,r,s)=>{if(t.linkType!==\"SOFT\")return!1;let a=ue.toPortablePath(r.resolveVirtual&&e.reference&&e.reference.startsWith(\"virtual:\")?r.resolveVirtual(t.packageLocation):t.packageLocation);return K.contains(s,a)===null},FQt=t=>{let e=t.getPackageInformation(t.topLevel);if(e===null)throw new Error(\"Assertion failed: Expected the top-level package to have been registered\");if(t.findPackageLocator(e.packageLocation)===null)throw new Error(\"Assertion failed: Expected the top-level package to have a physical locator\");let s=ue.toPortablePath(e.packageLocation.slice(0,-1)),a=new Map,n={children:new Map},c=t.getDependencyTreeRoots(),f=new Map,p=new Set,h=(S,P)=>{let I=gA(S);if(p.has(I))return;p.add(I);let R=t.getPackageInformation(S);if(R){let N=P?gA(P):\"\";if(gA(S)!==N&&R.linkType===\"SOFT\"&&!S.reference.startsWith(\"link:\")&&!PY(R,S,t,s)){let U=ZPe(R,S,t);(!f.get(U)||S.reference.startsWith(\"workspace:\"))&&f.set(U,S)}for(let[U,W]of R.packageDependencies)W!==null&&(R.packagePeers.has(U)||h(t.getLocator(U,W),S))}};for(let S of c)h(S,null);let E=s.split(K.sep);for(let S of f.values()){let P=t.getPackageInformation(S),R=ue.toPortablePath(P.packageLocation.slice(0,-1)).split(K.sep).slice(E.length),N=n;for(let U of R){let W=N.children.get(U);W||(W={children:new Map},N.children.set(U,W)),N=W}N.workspaceLocator=S}let C=(S,P)=>{if(S.workspaceLocator){let I=gA(P),R=a.get(I);R||(R=new Set,a.set(I,R)),R.add(S.workspaceLocator)}for(let I of S.children.values())C(I,S.workspaceLocator||P)};for(let S of n.children.values())C(S,n.workspaceLocator);return a},NQt=(t,e)=>{let r=[],s=!1,a=new Map,n=FQt(t),c=t.getPackageInformation(t.topLevel);if(c===null)throw new Error(\"Assertion failed: Expected the top-level package to have been registered\");let f=t.findPackageLocator(c.packageLocation);if(f===null)throw new Error(\"Assertion failed: Expected the top-level package to have a physical locator\");let p=ue.toPortablePath(c.packageLocation.slice(0,-1)),h={name:f.name,identName:f.name,reference:f.reference,peerNames:c.packagePeers,dependencies:new Set,dependencyKind:1},E=new Map,C=(P,I)=>`${gA(I)}:${P}`,S=(P,I,R,N,U,W,te,ie)=>{let Ae=C(P,R),ce=E.get(Ae),me=!!ce;!me&&R.name===f.name&&R.reference===f.reference&&(ce=h,E.set(Ae,h));let pe=PY(I,R,t,p);if(!ce){let fe=0;pe?fe=2:I.linkType===\"SOFT\"&&R.name.endsWith(tg)&&(fe=1),ce={name:P,identName:R.name,reference:R.reference,dependencies:new Set,peerNames:fe===1?new Set:I.packagePeers,dependencyKind:fe},E.set(Ae,ce)}let Be;if(pe?Be=2:U.linkType===\"SOFT\"?Be=1:Be=0,ce.hoistPriority=Math.max(ce.hoistPriority||0,Be),ie&&!pe){let fe=gA({name:N.identName,reference:N.reference}),se=a.get(fe)||new Set;a.set(fe,se),se.add(ce.name)}let Ce=new Map(I.packageDependencies);if(e.project){let fe=e.project.workspacesByCwd.get(ue.toPortablePath(I.packageLocation.slice(0,-1)));if(fe){let se=new Set([...Array.from(fe.manifest.peerDependencies.values(),X=>q.stringifyIdent(X)),...Array.from(fe.manifest.peerDependenciesMeta.keys())]);for(let X of se)Ce.has(X)||(Ce.set(X,W.get(X)||null),ce.peerNames.add(X))}}let g=gA({name:R.name.replace(tg,\"\"),reference:R.reference}),we=n.get(g);if(we)for(let fe of we)Ce.set(`${fe.name}${tg}`,fe.reference);(I!==U||I.linkType!==\"SOFT\"||!pe&&(!e.selfReferencesByCwd||e.selfReferencesByCwd.get(te)))&&N.dependencies.add(ce);let ye=R!==f&&I.linkType===\"SOFT\"&&!R.name.endsWith(tg)&&!pe;if(!me&&!ye){let fe=new Map;for(let[se,X]of Ce)if(X!==null){let De=t.getLocator(se,X),Re=t.getLocator(se.replace(tg,\"\"),X),dt=t.getPackageInformation(Re);if(dt===null)throw new Error(\"Assertion failed: Expected the package to have been registered\");let j=PY(dt,De,t,p);if(e.validateExternalSoftLinks&&e.project&&j){dt.packageDependencies.size>0&&(s=!0);for(let[Ye,ke]of dt.packageDependencies)if(ke!==null){let it=q.parseLocator(Array.isArray(ke)?`${ke[0]}@${ke[1]}`:`${Ye}@${ke}`);if(gA(it)!==gA(De)){let _e=Ce.get(Ye);if(_e){let x=q.parseLocator(Array.isArray(_e)?`${_e[0]}@${_e[1]}`:`${Ye}@${_e}`);zPe(x,it)||r.push({messageName:71,text:`Cannot link ${q.prettyIdent(e.project.configuration,q.parseIdent(De.name))} into ${q.prettyLocator(e.project.configuration,q.parseLocator(`${R.name}@${R.reference}`))} dependency ${q.prettyLocator(e.project.configuration,it)} conflicts with parent dependency ${q.prettyLocator(e.project.configuration,x)}`})}else{let x=fe.get(Ye);if(x){let w=x.target,b=q.parseLocator(Array.isArray(w)?`${w[0]}@${w[1]}`:`${Ye}@${w}`);zPe(b,it)||r.push({messageName:71,text:`Cannot link ${q.prettyIdent(e.project.configuration,q.parseIdent(De.name))} into ${q.prettyLocator(e.project.configuration,q.parseLocator(`${R.name}@${R.reference}`))} dependency ${q.prettyLocator(e.project.configuration,it)} conflicts with dependency ${q.prettyLocator(e.project.configuration,b)} from sibling portal ${q.prettyIdent(e.project.configuration,q.parseIdent(x.portal.name))}`})}else fe.set(Ye,{target:it.reference,portal:De})}}}}let rt=e.hoistingLimitsByCwd?.get(te),Fe=j?te:K.relative(p,ue.toPortablePath(dt.packageLocation))||vt.dot,Ne=e.hoistingLimitsByCwd?.get(Fe);S(se,dt,De,ce,I,Ce,Fe,rt===\"dependencies\"||Ne===\"dependencies\"||Ne===\"workspaces\")}}};return S(f.name,c,f,h,c,c.packageDependencies,vt.dot,!1),{packageTree:h,hoistingLimits:a,errors:r,preserveSymlinksRequired:s}};function ZPe(t,e,r){let s=r.resolveVirtual&&e.reference&&e.reference.startsWith(\"virtual:\")?r.resolveVirtual(t.packageLocation):t.packageLocation;return ue.toPortablePath(s||t.packageLocation)}function OQt(t,e,r){let s=e.getLocator(t.name.replace(tg,\"\"),t.reference),a=e.getPackageInformation(s);if(a===null)throw new Error(\"Assertion failed: Expected the package to be registered\");return r.pnpifyFs?{linkType:\"SOFT\",target:ue.toPortablePath(a.packageLocation)}:{linkType:a.linkType,target:ZPe(a,t,e)}}var LQt=(t,e,r)=>{let s=new Map,a=(E,C,S)=>{let{linkType:P,target:I}=OQt(E,t,r);return{locator:gA(E),nodePath:C,target:I,linkType:P,aliases:S}},n=E=>{let[C,S]=E.split(\"/\");return S?{scope:C,name:S}:{scope:null,name:C}},c=new Set,f=(E,C,S)=>{if(c.has(E))return;c.add(E);let P=Array.from(E.references).sort().join(\"#\");for(let I of E.dependencies){let R=Array.from(I.references).sort().join(\"#\");if(I.identName===E.identName.replace(tg,\"\")&&R===P)continue;let N=Array.from(I.references).sort(),U={name:I.identName,reference:N[0]},{name:W,scope:te}=n(I.name),ie=te?[te,W]:[W],Ae=K.join(C,JPe),ce=K.join(Ae,...ie),me=`${S}/${U.name}`,pe=a(U,S,N.slice(1)),Be=!1;if(pe.linkType===\"SOFT\"&&r.project){let Ce=r.project.workspacesByCwd.get(pe.target.slice(0,-1));Be=!!(Ce&&!Ce.manifest.name)}if(!I.name.endsWith(tg)&&!Be){let Ce=s.get(ce);if(Ce){if(Ce.dirList)throw new Error(`Assertion failed: ${ce} cannot merge dir node with leaf node`);{let ye=q.parseLocator(Ce.locator),fe=q.parseLocator(pe.locator);if(Ce.linkType!==pe.linkType)throw new Error(`Assertion failed: ${ce} cannot merge nodes with different link types ${Ce.nodePath}/${q.stringifyLocator(ye)} and ${S}/${q.stringifyLocator(fe)}`);if(ye.identHash!==fe.identHash)throw new Error(`Assertion failed: ${ce} cannot merge nodes with different idents ${Ce.nodePath}/${q.stringifyLocator(ye)} and ${S}/s${q.stringifyLocator(fe)}`);pe.aliases=[...pe.aliases,...Ce.aliases,q.parseLocator(Ce.locator).reference]}}s.set(ce,pe);let g=ce.split(\"/\"),we=g.indexOf(JPe);for(let ye=g.length-1;we>=0&&ye>we;ye--){let fe=ue.toPortablePath(g.slice(0,ye).join(K.sep)),se=g[ye],X=s.get(fe);if(!X)s.set(fe,{dirList:new Set([se])});else if(X.dirList){if(X.dirList.has(se))break;X.dirList.add(se)}}}f(I,pe.linkType===\"SOFT\"?pe.target:ce,me)}},p=a({name:e.name,reference:Array.from(e.references)[0]},\"\",[]),h=p.target;return s.set(h,p),f(e,h,\"\"),s};Ve();Ve();bt();bt();rA();Bc();var KY={};Vt(KY,{PnpInstaller:()=>jm,PnpLinker:()=>ig,UnplugCommand:()=>Sw,default:()=>pTt,getPnpPath:()=>sg,jsInstallUtils:()=>mA,pnpUtils:()=>ZD,quotePathIfNeeded:()=>Nxe});bt();var Fxe=Ie(\"url\");Ve();Ve();bt();bt();var XPe={DEFAULT:{collapsed:!1,next:{\"*\":\"DEFAULT\"}},TOP_LEVEL:{collapsed:!1,next:{fallbackExclusionList:\"FALLBACK_EXCLUSION_LIST\",packageRegistryData:\"PACKAGE_REGISTRY_DATA\",\"*\":\"DEFAULT\"}},FALLBACK_EXCLUSION_LIST:{collapsed:!1,next:{\"*\":\"FALLBACK_EXCLUSION_ENTRIES\"}},FALLBACK_EXCLUSION_ENTRIES:{collapsed:!0,next:{\"*\":\"FALLBACK_EXCLUSION_DATA\"}},FALLBACK_EXCLUSION_DATA:{collapsed:!0,next:{\"*\":\"DEFAULT\"}},PACKAGE_REGISTRY_DATA:{collapsed:!1,next:{\"*\":\"PACKAGE_REGISTRY_ENTRIES\"}},PACKAGE_REGISTRY_ENTRIES:{collapsed:!0,next:{\"*\":\"PACKAGE_STORE_DATA\"}},PACKAGE_STORE_DATA:{collapsed:!1,next:{\"*\":\"PACKAGE_STORE_ENTRIES\"}},PACKAGE_STORE_ENTRIES:{collapsed:!0,next:{\"*\":\"PACKAGE_INFORMATION_DATA\"}},PACKAGE_INFORMATION_DATA:{collapsed:!1,next:{packageDependencies:\"PACKAGE_DEPENDENCIES\",\"*\":\"DEFAULT\"}},PACKAGE_DEPENDENCIES:{collapsed:!1,next:{\"*\":\"PACKAGE_DEPENDENCY\"}},PACKAGE_DEPENDENCY:{collapsed:!0,next:{\"*\":\"DEFAULT\"}}};function MQt(t,e,r){let s=\"\";s+=\"[\";for(let a=0,n=t.length;a<n;++a)s+=iN(String(a),t[a],e,r).replace(/^ +/g,\"\"),a+1<n&&(s+=\", \");return s+=\"]\",s}function _Qt(t,e,r){let s=`${r}  `,a=\"\";a+=r,a+=`[\n`;for(let n=0,c=t.length;n<c;++n)a+=s+iN(String(n),t[n],e,s).replace(/^ +/,\"\"),n+1<c&&(a+=\",\"),a+=`\n`;return a+=r,a+=\"]\",a}function UQt(t,e,r){let s=Object.keys(t),a=\"\";a+=\"{\";for(let n=0,c=s.length,f=0;n<c;++n){let p=s[n],h=t[p];typeof h>\"u\"||(f!==0&&(a+=\", \"),a+=JSON.stringify(p),a+=\": \",a+=iN(p,h,e,r).replace(/^ +/g,\"\"),f+=1)}return a+=\"}\",a}function HQt(t,e,r){let s=Object.keys(t),a=`${r}  `,n=\"\";n+=r,n+=`{\n`;let c=0;for(let f=0,p=s.length;f<p;++f){let h=s[f],E=t[h];typeof E>\"u\"||(c!==0&&(n+=\",\",n+=`\n`),n+=a,n+=JSON.stringify(h),n+=\": \",n+=iN(h,E,e,a).replace(/^ +/g,\"\"),c+=1)}return c!==0&&(n+=`\n`),n+=r,n+=\"}\",n}function iN(t,e,r,s){let{next:a}=XPe[r],n=a[t]||a[\"*\"];return $Pe(e,n,s)}function $Pe(t,e,r){let{collapsed:s}=XPe[e];return Array.isArray(t)?s?MQt(t,e,r):_Qt(t,e,r):typeof t==\"object\"&&t!==null?s?UQt(t,e,r):HQt(t,e,r):JSON.stringify(t)}function exe(t){return $Pe(t,\"TOP_LEVEL\",\"\")}function HD(t,e){let r=Array.from(t);Array.isArray(e)||(e=[e]);let s=[];for(let n of e)s.push(r.map(c=>n(c)));let a=r.map((n,c)=>c);return a.sort((n,c)=>{for(let f of s){let p=f[n]<f[c]?-1:f[n]>f[c]?1:0;if(p!==0)return p}return 0}),a.map(n=>r[n])}function jQt(t){let e=new Map,r=HD(t.fallbackExclusionList||[],[({name:s,reference:a})=>s,({name:s,reference:a})=>a]);for(let{name:s,reference:a}of r){let n=e.get(s);typeof n>\"u\"&&e.set(s,n=new Set),n.add(a)}return Array.from(e).map(([s,a])=>[s,Array.from(a)])}function qQt(t){return HD(t.fallbackPool||[],([e])=>e)}function GQt(t){let e=[],r=t.dependencyTreeRoots.find(s=>t.packageRegistry.get(s.name)?.get(s.reference)?.packageLocation===\"./\");for(let[s,a]of HD(t.packageRegistry,([n])=>n===null?\"0\":`1${n}`)){if(s===null)continue;let n=[];e.push([s,n]);for(let[c,{packageLocation:f,packageDependencies:p,packagePeers:h,linkType:E,discardFromLookup:C}]of HD(a,([S])=>S===null?\"0\":`1${S}`)){if(c===null)continue;let S=[];s!==null&&c!==null&&!p.has(s)&&S.push([s,c]);for(let[U,W]of p)S.push([U,W]);let P=HD(S,([U])=>U),I=h&&h.size>0?Array.from(h):void 0,N={packageLocation:f,packageDependencies:P,packagePeers:I,linkType:E,discardFromLookup:C||void 0};n.push([c,N]),r&&s===r.name&&c===r.reference&&e.unshift([null,[[null,N]]])}}return e}function jD(t){return{__info:[\"This file is automatically generated. Do not touch it, or risk\",\"your modifications being lost.\"],dependencyTreeRoots:t.dependencyTreeRoots,enableTopLevelFallback:t.enableTopLevelFallback||!1,ignorePatternData:t.ignorePattern||null,pnpZipBackend:t.pnpZipBackend,fallbackExclusionList:jQt(t),fallbackPool:qQt(t),packageRegistryData:GQt(t)}}var nxe=et(rxe());function ixe(t,e){return[t?`${t}\n`:\"\",`/* eslint-disable */\n`,`// @ts-nocheck\n`,`\"use strict\";\n`,`\n`,e,`\n`,(0,nxe.default)()].join(\"\")}function WQt(t){return JSON.stringify(t,null,2)}function YQt(t){return`'${t.replace(/\\\\/g,\"\\\\\\\\\").replace(/'/g,\"\\\\'\").replace(/\\n/g,`\\\\\n`)}'`}function VQt(t){return[`const RAW_RUNTIME_STATE =\n`,`${YQt(exe(t))};\n\n`,`function $$SETUP_STATE(hydrateRuntimeState, basePath) {\n`,`  return hydrateRuntimeState(JSON.parse(RAW_RUNTIME_STATE), {basePath: basePath || __dirname});\n`,`}\n`].join(\"\")}function KQt(){return[`function $$SETUP_STATE(hydrateRuntimeState, basePath) {\n`,`  const fs = require('fs');\n`,`  const path = require('path');\n`,`  const pnpDataFilepath = path.resolve(__dirname, ${JSON.stringify(Er.pnpData)});\n`,`  return hydrateRuntimeState(JSON.parse(fs.readFileSync(pnpDataFilepath, 'utf8')), {basePath: basePath || __dirname});\n`,`}\n`].join(\"\")}function sxe(t){let e=jD(t),r=VQt(e);return ixe(t.shebang,r)}function oxe(t){let e=jD(t),r=KQt(),s=ixe(t.shebang,r);return{dataFile:WQt(e),loaderFile:s}}bt();function QY(t,{basePath:e}){let r=ue.toPortablePath(e),s=K.resolve(r),a=t.ignorePatternData!==null?new RegExp(t.ignorePatternData):null,n=new Map,c=new Map(t.packageRegistryData.map(([C,S])=>[C,new Map(S.map(([P,I])=>{if(C===null!=(P===null))throw new Error(\"Assertion failed: The name and reference should be null, or neither should\");let R=I.discardFromLookup??!1,N={name:C,reference:P},U=n.get(I.packageLocation);U?(U.discardFromLookup=U.discardFromLookup&&R,R||(U.locator=N)):n.set(I.packageLocation,{locator:N,discardFromLookup:R});let W=null;return[P,{packageDependencies:new Map(I.packageDependencies),packagePeers:new Set(I.packagePeers),linkType:I.linkType,discardFromLookup:R,get packageLocation(){return W||(W=K.join(s,I.packageLocation))}}]}))])),f=new Map(t.fallbackExclusionList.map(([C,S])=>[C,new Set(S)])),p=new Map(t.fallbackPool),h=t.dependencyTreeRoots,E=t.enableTopLevelFallback;return{basePath:r,dependencyTreeRoots:h,enableTopLevelFallback:E,fallbackExclusionList:f,pnpZipBackend:t.pnpZipBackend,fallbackPool:p,ignorePattern:a,packageLocatorsByLocations:n,packageRegistry:c}}bt();bt();var ah=Ie(\"module\"),Hm=Ie(\"url\"),HY=Ie(\"util\");var ra=Ie(\"url\");var uxe=et(Ie(\"assert\"));var TY=Array.isArray,qD=JSON.stringify,GD=Object.getOwnPropertyNames,Um=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),RY=(t,e)=>RegExp.prototype.exec.call(t,e),FY=(t,...e)=>RegExp.prototype[Symbol.replace].apply(t,e),rg=(t,...e)=>String.prototype.endsWith.apply(t,e),NY=(t,...e)=>String.prototype.includes.apply(t,e),OY=(t,...e)=>String.prototype.lastIndexOf.apply(t,e),WD=(t,...e)=>String.prototype.indexOf.apply(t,e),axe=(t,...e)=>String.prototype.replace.apply(t,e),ng=(t,...e)=>String.prototype.slice.apply(t,e),dA=(t,...e)=>String.prototype.startsWith.apply(t,e),lxe=Map,cxe=JSON.parse;function YD(t,e,r){return class extends r{constructor(...s){super(e(...s)),this.code=t,this.name=`${r.name} [${t}]`}}}var fxe=YD(\"ERR_PACKAGE_IMPORT_NOT_DEFINED\",(t,e,r)=>`Package import specifier \"${t}\" is not defined${e?` in package ${e}package.json`:\"\"} imported from ${r}`,TypeError),LY=YD(\"ERR_INVALID_MODULE_SPECIFIER\",(t,e,r=void 0)=>`Invalid module \"${t}\" ${e}${r?` imported from ${r}`:\"\"}`,TypeError),Axe=YD(\"ERR_INVALID_PACKAGE_TARGET\",(t,e,r,s=!1,a=void 0)=>{let n=typeof r==\"string\"&&!s&&r.length&&!dA(r,\"./\");return e===\".\"?((0,uxe.default)(s===!1),`Invalid \"exports\" main target ${qD(r)} defined in the package config ${t}package.json${a?` imported from ${a}`:\"\"}${n?'; targets must start with \"./\"':\"\"}`):`Invalid \"${s?\"imports\":\"exports\"}\" target ${qD(r)} defined for '${e}' in the package config ${t}package.json${a?` imported from ${a}`:\"\"}${n?'; targets must start with \"./\"':\"\"}`},Error),VD=YD(\"ERR_INVALID_PACKAGE_CONFIG\",(t,e,r)=>`Invalid package config ${t}${e?` while importing ${e}`:\"\"}${r?`. ${r}`:\"\"}`,Error),pxe=YD(\"ERR_PACKAGE_PATH_NOT_EXPORTED\",(t,e,r=void 0)=>e===\".\"?`No \"exports\" main defined in ${t}package.json${r?` imported from ${r}`:\"\"}`:`Package subpath '${e}' is not defined by \"exports\" in ${t}package.json${r?` imported from ${r}`:\"\"}`,Error);var oN=Ie(\"url\");function hxe(t,e){let r=Object.create(null);for(let s=0;s<e.length;s++){let a=e[s];Um(t,a)&&(r[a]=t[a])}return r}var sN=new lxe;function JQt(t,e,r,s){let a=sN.get(t);if(a!==void 0)return a;let n=s(t);if(n===void 0){let P={pjsonPath:t,exists:!1,main:void 0,name:void 0,type:\"none\",exports:void 0,imports:void 0};return sN.set(t,P),P}let c;try{c=cxe(n)}catch(P){throw new VD(t,(r?`\"${e}\" from `:\"\")+(0,oN.fileURLToPath)(r||e),P.message)}let{imports:f,main:p,name:h,type:E}=hxe(c,[\"imports\",\"main\",\"name\",\"type\"]),C=Um(c,\"exports\")?c.exports:void 0;(typeof f!=\"object\"||f===null)&&(f=void 0),typeof p!=\"string\"&&(p=void 0),typeof h!=\"string\"&&(h=void 0),E!==\"module\"&&E!==\"commonjs\"&&(E=\"none\");let S={pjsonPath:t,exists:!0,main:p,name:h,type:E,exports:C,imports:f};return sN.set(t,S),S}function gxe(t,e){let r=new URL(\"./package.json\",t);for(;;){let n=r.pathname;if(rg(n,\"node_modules/package.json\"))break;let c=JQt((0,oN.fileURLToPath)(r),t,void 0,e);if(c.exists)return c;let f=r;if(r=new URL(\"../package.json\",r),r.pathname===f.pathname)break}let s=(0,oN.fileURLToPath)(r),a={pjsonPath:s,exists:!1,main:void 0,name:void 0,type:\"none\",exports:void 0,imports:void 0};return sN.set(s,a),a}function zQt(t,e,r){throw new fxe(t,e&&(0,ra.fileURLToPath)(new URL(\".\",e)),(0,ra.fileURLToPath)(r))}function ZQt(t,e,r,s){let a=`request is not a valid subpath for the \"${r?\"imports\":\"exports\"}\" resolution of ${(0,ra.fileURLToPath)(e)}`;throw new LY(t,a,s&&(0,ra.fileURLToPath)(s))}function KD(t,e,r,s,a){throw typeof e==\"object\"&&e!==null?e=qD(e,null,\"\"):e=`${e}`,new Axe((0,ra.fileURLToPath)(new URL(\".\",r)),t,e,s,a&&(0,ra.fileURLToPath)(a))}var dxe=/(^|\\\\|\\/)((\\.|%2e)(\\.|%2e)?|(n|%6e|%4e)(o|%6f|%4f)(d|%64|%44)(e|%65|%45)(_|%5f)(m|%6d|%4d)(o|%6f|%4f)(d|%64|%44)(u|%75|%55)(l|%6c|%4c)(e|%65|%45)(s|%73|%53))(\\\\|\\/|$)/i,mxe=/\\*/g;function XQt(t,e,r,s,a,n,c,f){if(e!==\"\"&&!n&&t[t.length-1]!==\"/\"&&KD(r,t,s,c,a),!dA(t,\"./\")){if(c&&!dA(t,\"../\")&&!dA(t,\"/\")){let C=!1;try{new URL(t),C=!0}catch{}if(!C)return n?FY(mxe,t,()=>e):t+e}KD(r,t,s,c,a)}RY(dxe,ng(t,2))!==null&&KD(r,t,s,c,a);let p=new URL(t,s),h=p.pathname,E=new URL(\".\",s).pathname;if(dA(h,E)||KD(r,t,s,c,a),e===\"\")return p;if(RY(dxe,e)!==null){let C=n?axe(r,\"*\",()=>e):r+e;ZQt(C,s,c,a)}return n?new URL(FY(mxe,p.href,()=>e)):new URL(e,p)}function $Qt(t){let e=+t;return`${e}`!==t?!1:e>=0&&e<4294967295}function vw(t,e,r,s,a,n,c,f){if(typeof e==\"string\")return XQt(e,r,s,t,a,n,c,f);if(TY(e)){if(e.length===0)return null;let p;for(let h=0;h<e.length;h++){let E=e[h],C;try{C=vw(t,E,r,s,a,n,c,f)}catch(S){if(p=S,S.code===\"ERR_INVALID_PACKAGE_TARGET\")continue;throw S}if(C!==void 0){if(C===null){p=null;continue}return C}}if(p==null)return p;throw p}else if(typeof e==\"object\"&&e!==null){let p=GD(e);for(let h=0;h<p.length;h++){let E=p[h];if($Qt(E))throw new VD((0,ra.fileURLToPath)(t),a,'\"exports\" cannot contain numeric property keys.')}for(let h=0;h<p.length;h++){let E=p[h];if(E===\"default\"||f.has(E)){let C=e[E],S=vw(t,C,r,s,a,n,c,f);if(S===void 0)continue;return S}}return}else if(e===null)return null;KD(s,e,t,c,a)}function Exe(t,e){let r=WD(t,\"*\"),s=WD(e,\"*\"),a=r===-1?t.length:r+1,n=s===-1?e.length:s+1;return a>n?-1:n>a||r===-1?1:s===-1||t.length>e.length?-1:e.length>t.length?1:0}function eTt(t,e,r){if(typeof t==\"string\"||TY(t))return!0;if(typeof t!=\"object\"||t===null)return!1;let s=GD(t),a=!1,n=0;for(let c=0;c<s.length;c++){let f=s[c],p=f===\"\"||f[0]!==\".\";if(n++===0)a=p;else if(a!==p)throw new VD((0,ra.fileURLToPath)(e),r,`\"exports\" cannot contain some keys starting with '.' and some not. The exports object must either be an object of package subpath keys or an object of main entry condition name keys only.`)}return a}function MY(t,e,r){throw new pxe((0,ra.fileURLToPath)(new URL(\".\",e)),t,r&&(0,ra.fileURLToPath)(r))}var yxe=new Set;function tTt(t,e,r){let s=(0,ra.fileURLToPath)(e);yxe.has(s+\"|\"+t)||(yxe.add(s+\"|\"+t),process.emitWarning(`Use of deprecated trailing slash pattern mapping \"${t}\" in the \"exports\" field module resolution of the package at ${s}${r?` imported from ${(0,ra.fileURLToPath)(r)}`:\"\"}. Mapping specifiers ending in \"/\" is no longer supported.`,\"DeprecationWarning\",\"DEP0155\"))}function Ixe({packageJSONUrl:t,packageSubpath:e,exports:r,base:s,conditions:a}){if(eTt(r,t,s)&&(r={\".\":r}),Um(r,e)&&!NY(e,\"*\")&&!rg(e,\"/\")){let p=r[e],h=vw(t,p,\"\",e,s,!1,!1,a);return h==null&&MY(e,t,s),h}let n=\"\",c,f=GD(r);for(let p=0;p<f.length;p++){let h=f[p],E=WD(h,\"*\");if(E!==-1&&dA(e,ng(h,0,E))){rg(e,\"/\")&&tTt(e,t,s);let C=ng(h,E+1);e.length>=h.length&&rg(e,C)&&Exe(n,h)===1&&OY(h,\"*\")===E&&(n=h,c=ng(e,E,e.length-C.length))}}if(n){let p=r[n],h=vw(t,p,c,n,s,!0,!1,a);return h==null&&MY(e,t,s),h}MY(e,t,s)}function Cxe({name:t,base:e,conditions:r,readFileSyncFn:s}){if(t===\"#\"||dA(t,\"#/\")||rg(t,\"/\")){let c=\"is not a valid internal imports specifier name\";throw new LY(t,c,(0,ra.fileURLToPath)(e))}let a,n=gxe(e,s);if(n.exists){a=(0,ra.pathToFileURL)(n.pjsonPath);let c=n.imports;if(c)if(Um(c,t)&&!NY(t,\"*\")){let f=vw(a,c[t],\"\",t,e,!1,!0,r);if(f!=null)return f}else{let f=\"\",p,h=GD(c);for(let E=0;E<h.length;E++){let C=h[E],S=WD(C,\"*\");if(S!==-1&&dA(t,ng(C,0,S))){let P=ng(C,S+1);t.length>=C.length&&rg(t,P)&&Exe(f,C)===1&&OY(C,\"*\")===S&&(f=C,p=ng(t,S,t.length-P.length))}}if(f){let E=c[f],C=vw(a,E,p,f,e,!0,!0,r);if(C!=null)return C}}}zQt(t,a,e)}bt();var rTt=new Set([\"BUILTIN_NODE_RESOLUTION_FAILED\",\"MISSING_DEPENDENCY\",\"MISSING_PEER_DEPENDENCY\",\"QUALIFIED_PATH_RESOLUTION_FAILED\",\"UNDECLARED_DEPENDENCY\"]);function ms(t,e,r={},s){s??=rTt.has(t)?\"MODULE_NOT_FOUND\":t;let a={configurable:!0,writable:!0,enumerable:!1};return Object.defineProperties(new Error(e),{code:{...a,value:s},pnpCode:{...a,value:t},data:{...a,value:r}})}function cf(t){return ue.normalize(ue.fromPortablePath(t))}var Sxe=et(Bxe());function Dxe(t){return nTt(),UY[t]}var UY;function nTt(){UY||(UY={\"--conditions\":[],...vxe(iTt()),...vxe(process.execArgv)})}function vxe(t){return(0,Sxe.default)({\"--conditions\":[String],\"-C\":\"--conditions\"},{argv:t,permissive:!0})}function iTt(){let t=[],e=sTt(process.env.NODE_OPTIONS||\"\",t);return t.length,e}function sTt(t,e){let r=[],s=!1,a=!0;for(let n=0;n<t.length;++n){let c=t[n];if(c===\"\\\\\"&&s){if(n+1===t.length)return e.push(`invalid value for NODE_OPTIONS (invalid escape)\n`),r;c=t[++n]}else if(c===\" \"&&!s){a=!0;continue}else if(c==='\"'){s=!s;continue}a?(r.push(c),a=!1):r[r.length-1]+=c}return s&&e.push(`invalid value for NODE_OPTIONS (unterminated string)\n`),r}bt();var[yl,oh]=process.versions.node.split(\".\").map(t=>parseInt(t,10)),bxe=yl>19||yl===19&&oh>=2||yl===18&&oh>=13,pdr=yl===20&&oh<6||yl===19&&oh>=3,hdr=yl>19||yl===19&&oh>=6,gdr=yl>=21||yl===20&&oh>=10||yl===18&&oh>=19,ddr=yl>=21||yl===20&&oh>=10||yl===18&&oh>=20,mdr=yl>=22;function Pxe(t){if(process.env.WATCH_REPORT_DEPENDENCIES&&process.send)if(t=t.map(e=>ue.fromPortablePath(Ao.resolveVirtual(ue.toPortablePath(e)))),bxe)process.send({\"watch:require\":t});else for(let e of t)process.send({\"watch:require\":e})}function jY(t,e){let r=Number(process.env.PNP_ALWAYS_WARN_ON_FALLBACK)>0,s=Number(process.env.PNP_DEBUG_LEVEL),a=/^(?![a-zA-Z]:[\\\\/]|\\\\\\\\|\\.{0,2}(?:\\/|$))((?:node:)?(?:@[^/]+\\/)?[^/]+)\\/*(.*|)$/,n=/^(\\/|\\.{1,2}(\\/|$))/,c=/\\/$/,f=/^\\.{0,2}\\//,p={name:null,reference:null},h=[],E=new Set;if(t.enableTopLevelFallback===!0&&h.push(p),e.compatibilityMode!==!1)for(let Fe of[\"react-scripts\",\"gatsby\"]){let Ne=t.packageRegistry.get(Fe);if(Ne)for(let Pe of Ne.keys()){if(Pe===null)throw new Error(\"Assertion failed: This reference shouldn't be null\");h.push({name:Fe,reference:Pe})}}let{ignorePattern:C,packageRegistry:S,packageLocatorsByLocations:P}=t;function I(Fe,Ne){return{fn:Fe,args:Ne,error:null,result:null}}function R(Fe){let Ne=process.stderr?.hasColors?.()??process.stdout.isTTY,Pe=(it,_e)=>`\\x1B[${it}m${_e}\\x1B[0m`,Ye=Fe.error;console.error(Ye?Pe(\"31;1\",`\\u2716 ${Fe.error?.message.replace(/\\n.*/s,\"\")}`):Pe(\"33;1\",\"\\u203C Resolution\")),Fe.args.length>0&&console.error();for(let it of Fe.args)console.error(`  ${Pe(\"37;1\",\"In \\u2190\")} ${(0,HY.inspect)(it,{colors:Ne,compact:!0})}`);Fe.result&&(console.error(),console.error(`  ${Pe(\"37;1\",\"Out \\u2192\")} ${(0,HY.inspect)(Fe.result,{colors:Ne,compact:!0})}`));let ke=new Error().stack.match(/(?<=^ +)at.*/gm)?.slice(2)??[];if(ke.length>0){console.error();for(let it of ke)console.error(`  ${Pe(\"38;5;244\",it)}`)}console.error()}function N(Fe,Ne){if(e.allowDebug===!1)return Ne;if(Number.isFinite(s)){if(s>=2)return(...Pe)=>{let Ye=I(Fe,Pe);try{return Ye.result=Ne(...Pe)}catch(ke){throw Ye.error=ke}finally{R(Ye)}};if(s>=1)return(...Pe)=>{try{return Ne(...Pe)}catch(Ye){let ke=I(Fe,Pe);throw ke.error=Ye,R(ke),Ye}}}return Ne}function U(Fe){let Ne=g(Fe);if(!Ne)throw ms(\"INTERNAL\",\"Couldn't find a matching entry in the dependency tree for the specified parent (this is probably an internal error)\");return Ne}function W(Fe){if(Fe.name===null)return!0;for(let Ne of t.dependencyTreeRoots)if(Ne.name===Fe.name&&Ne.reference===Fe.reference)return!0;return!1}let te=new Set([\"node\",\"require\",...Dxe(\"--conditions\")]);function ie(Fe,Ne=te,Pe){let Ye=fe(K.join(Fe,\"internal.js\"),{resolveIgnored:!0,includeDiscardFromLookup:!0});if(Ye===null)throw ms(\"INTERNAL\",`The locator that owns the \"${Fe}\" path can't be found inside the dependency tree (this is probably an internal error)`);let{packageLocation:ke}=U(Ye),it=K.join(ke,Er.manifest);if(!e.fakeFs.existsSync(it))return null;let _e=JSON.parse(e.fakeFs.readFileSync(it,\"utf8\"));if(_e.exports==null)return null;let x=K.contains(ke,Fe);if(x===null)throw ms(\"INTERNAL\",\"unqualifiedPath doesn't contain the packageLocation (this is probably an internal error)\");x!==\".\"&&!f.test(x)&&(x=`./${x}`);try{let w=Ixe({packageJSONUrl:(0,Hm.pathToFileURL)(ue.fromPortablePath(it)),packageSubpath:x,exports:_e.exports,base:Pe?(0,Hm.pathToFileURL)(ue.fromPortablePath(Pe)):null,conditions:Ne});return ue.toPortablePath((0,Hm.fileURLToPath)(w))}catch(w){throw ms(\"EXPORTS_RESOLUTION_FAILED\",w.message,{unqualifiedPath:cf(Fe),locator:Ye,pkgJson:_e,subpath:cf(x),conditions:Ne},w.code)}}function Ae(Fe,Ne,{extensions:Pe}){let Ye;try{Ne.push(Fe),Ye=e.fakeFs.statSync(Fe)}catch{}if(Ye&&!Ye.isDirectory())return e.fakeFs.realpathSync(Fe);if(Ye&&Ye.isDirectory()){let ke;try{ke=JSON.parse(e.fakeFs.readFileSync(K.join(Fe,Er.manifest),\"utf8\"))}catch{}let it;if(ke&&ke.main&&(it=K.resolve(Fe,ke.main)),it&&it!==Fe){let _e=Ae(it,Ne,{extensions:Pe});if(_e!==null)return _e}}for(let ke=0,it=Pe.length;ke<it;ke++){let _e=`${Fe}${Pe[ke]}`;if(Ne.push(_e),e.fakeFs.existsSync(_e))return _e}if(Ye&&Ye.isDirectory())for(let ke=0,it=Pe.length;ke<it;ke++){let _e=K.format({dir:Fe,name:\"index\",ext:Pe[ke]});if(Ne.push(_e),e.fakeFs.existsSync(_e))return _e}return null}function ce(Fe){let Ne=new ah.Module(Fe,null);return Ne.filename=Fe,Ne.paths=ah.Module._nodeModulePaths(Fe),Ne}function me(Fe,Ne){return Ne.endsWith(\"/\")&&(Ne=K.join(Ne,\"internal.js\")),ah.Module._resolveFilename(ue.fromPortablePath(Fe),ce(ue.fromPortablePath(Ne)),!1,{plugnplay:!1})}function pe(Fe){if(C===null)return!1;let Ne=K.contains(t.basePath,Fe);return Ne===null?!1:!!C.test(Ne.replace(/\\/$/,\"\"))}let Be={std:3,resolveVirtual:1,getAllLocators:1},Ce=p;function g({name:Fe,reference:Ne}){let Pe=S.get(Fe);if(!Pe)return null;let Ye=Pe.get(Ne);return Ye||null}function we({name:Fe,reference:Ne}){let Pe=[];for(let[Ye,ke]of S)if(Ye!==null)for(let[it,_e]of ke)it===null||_e.packageDependencies.get(Fe)!==Ne||Ye===Fe&&it===Ne||Pe.push({name:Ye,reference:it});return Pe}function ye(Fe,Ne){let Pe=new Map,Ye=new Set,ke=_e=>{let x=JSON.stringify(_e.name);if(Ye.has(x))return;Ye.add(x);let w=we(_e);for(let b of w)if(U(b).packagePeers.has(Fe))ke(b);else{let F=Pe.get(b.name);typeof F>\"u\"&&Pe.set(b.name,F=new Set),F.add(b.reference)}};ke(Ne);let it=[];for(let _e of[...Pe.keys()].sort())for(let x of[...Pe.get(_e)].sort())it.push({name:_e,reference:x});return it}function fe(Fe,{resolveIgnored:Ne=!1,includeDiscardFromLookup:Pe=!1}={}){if(pe(Fe)&&!Ne)return null;let Ye=K.relative(t.basePath,Fe);Ye.match(n)||(Ye=`./${Ye}`),Ye.endsWith(\"/\")||(Ye=`${Ye}/`);do{let ke=P.get(Ye);if(typeof ke>\"u\"||ke.discardFromLookup&&!Pe){Ye=Ye.substring(0,Ye.lastIndexOf(\"/\",Ye.length-2)+1);continue}return ke.locator}while(Ye!==\"\");return null}function se(Fe){try{return e.fakeFs.readFileSync(ue.toPortablePath(Fe),\"utf8\")}catch(Ne){if(Ne.code===\"ENOENT\")return;throw Ne}}function X(Fe,Ne,{considerBuiltins:Pe=!0}={}){if(Fe.startsWith(\"#\"))throw new Error(\"resolveToUnqualified can not handle private import mappings\");if(Fe===\"pnpapi\")return ue.toPortablePath(e.pnpapiResolution);if(Pe&&(0,ah.isBuiltin)(Fe))return null;let Ye=cf(Fe),ke=Ne&&cf(Ne);if(Ne&&pe(Ne)&&(!K.isAbsolute(Fe)||fe(Fe)===null)){let x=me(Fe,Ne);if(x===!1)throw ms(\"BUILTIN_NODE_RESOLUTION_FAILED\",`The builtin node resolution algorithm was unable to resolve the requested module (it didn't go through the pnp resolver because the issuer was explicitely ignored by the regexp)\n\nRequire request: \"${Ye}\"\nRequired by: ${ke}\n`,{request:Ye,issuer:ke});return ue.toPortablePath(x)}let it,_e=Fe.match(a);if(_e){if(!Ne)throw ms(\"API_ERROR\",\"The resolveToUnqualified function must be called with a valid issuer when the path isn't a builtin nor absolute\",{request:Ye,issuer:ke});let[,x,w]=_e,b=fe(Ne);if(!b){let Te=me(Fe,Ne);if(Te===!1)throw ms(\"BUILTIN_NODE_RESOLUTION_FAILED\",`The builtin node resolution algorithm was unable to resolve the requested module (it didn't go through the pnp resolver because the issuer doesn't seem to be part of the Yarn-managed dependency tree).\n\nRequire path: \"${Ye}\"\nRequired by: ${ke}\n`,{request:Ye,issuer:ke});return ue.toPortablePath(Te)}let F=U(b).packageDependencies.get(x),z=null;if(F==null&&b.name!==null){let Te=t.fallbackExclusionList.get(b.name);if(!Te||!Te.has(b.reference)){for(let It=0,qt=h.length;It<qt;++It){let Pt=U(h[It]).packageDependencies.get(x);if(Pt!=null){r?z=Pt:F=Pt;break}}if(t.enableTopLevelFallback&&F==null&&z===null){let It=t.fallbackPool.get(x);It!=null&&(z=It)}}}let Z=null;if(F===null)if(W(b))Z=ms(\"MISSING_PEER_DEPENDENCY\",`Your application tried to access ${x} (a peer dependency); this isn't allowed as there is no ancestor to satisfy the requirement. Use a devDependency if needed.\n\nRequired package: ${x}${x!==Ye?` (via \"${Ye}\")`:\"\"}\nRequired by: ${ke}\n`,{request:Ye,issuer:ke,dependencyName:x});else{let Te=ye(x,b);Te.every(lt=>W(lt))?Z=ms(\"MISSING_PEER_DEPENDENCY\",`${b.name} tried to access ${x} (a peer dependency) but it isn't provided by your application; this makes the require call ambiguous and unsound.\n\nRequired package: ${x}${x!==Ye?` (via \"${Ye}\")`:\"\"}\nRequired by: ${b.name}@${b.reference} (via ${ke})\n${Te.map(lt=>`Ancestor breaking the chain: ${lt.name}@${lt.reference}\n`).join(\"\")}\n`,{request:Ye,issuer:ke,issuerLocator:Object.assign({},b),dependencyName:x,brokenAncestors:Te}):Z=ms(\"MISSING_PEER_DEPENDENCY\",`${b.name} tried to access ${x} (a peer dependency) but it isn't provided by its ancestors; this makes the require call ambiguous and unsound.\n\nRequired package: ${x}${x!==Ye?` (via \"${Ye}\")`:\"\"}\nRequired by: ${b.name}@${b.reference} (via ${ke})\n\n${Te.map(lt=>`Ancestor breaking the chain: ${lt.name}@${lt.reference}\n`).join(\"\")}\n`,{request:Ye,issuer:ke,issuerLocator:Object.assign({},b),dependencyName:x,brokenAncestors:Te})}else F===void 0&&(!Pe&&(0,ah.isBuiltin)(Fe)?W(b)?Z=ms(\"UNDECLARED_DEPENDENCY\",`Your application tried to access ${x}. While this module is usually interpreted as a Node builtin, your resolver is running inside a non-Node resolution context where such builtins are ignored. Since ${x} isn't otherwise declared in your dependencies, this makes the require call ambiguous and unsound.\n\nRequired package: ${x}${x!==Ye?` (via \"${Ye}\")`:\"\"}\nRequired by: ${ke}\n`,{request:Ye,issuer:ke,dependencyName:x}):Z=ms(\"UNDECLARED_DEPENDENCY\",`${b.name} tried to access ${x}. While this module is usually interpreted as a Node builtin, your resolver is running inside a non-Node resolution context where such builtins are ignored. Since ${x} isn't otherwise declared in ${b.name}'s dependencies, this makes the require call ambiguous and unsound.\n\nRequired package: ${x}${x!==Ye?` (via \"${Ye}\")`:\"\"}\nRequired by: ${ke}\n`,{request:Ye,issuer:ke,issuerLocator:Object.assign({},b),dependencyName:x}):W(b)?Z=ms(\"UNDECLARED_DEPENDENCY\",`Your application tried to access ${x}, but it isn't declared in your dependencies; this makes the require call ambiguous and unsound.\n\nRequired package: ${x}${x!==Ye?` (via \"${Ye}\")`:\"\"}\nRequired by: ${ke}\n`,{request:Ye,issuer:ke,dependencyName:x}):Z=ms(\"UNDECLARED_DEPENDENCY\",`${b.name} tried to access ${x}, but it isn't declared in its dependencies; this makes the require call ambiguous and unsound.\n\nRequired package: ${x}${x!==Ye?` (via \"${Ye}\")`:\"\"}\nRequired by: ${b.name}@${b.reference} (via ${ke})\n`,{request:Ye,issuer:ke,issuerLocator:Object.assign({},b),dependencyName:x}));if(F==null){if(z===null||Z===null)throw Z||new Error(\"Assertion failed: Expected an error to have been set\");F=z;let Te=Z.message.replace(/\\n.*/g,\"\");Z.message=Te,!E.has(Te)&&s!==0&&(E.add(Te),process.emitWarning(Z))}let $=Array.isArray(F)?{name:F[0],reference:F[1]}:{name:x,reference:F},oe=U($);if(!oe.packageLocation)throw ms(\"MISSING_DEPENDENCY\",`A dependency seems valid but didn't get installed for some reason. This might be caused by a partial install, such as dev vs prod.\n\nRequired package: ${$.name}@${$.reference}${$.name!==Ye?` (via \"${Ye}\")`:\"\"}\nRequired by: ${b.name}@${b.reference} (via ${ke})\n`,{request:Ye,issuer:ke,dependencyLocator:Object.assign({},$)});let xe=oe.packageLocation;w?it=K.join(xe,w):it=xe}else if(K.isAbsolute(Fe))it=K.normalize(Fe);else{if(!Ne)throw ms(\"API_ERROR\",\"The resolveToUnqualified function must be called with a valid issuer when the path isn't a builtin nor absolute\",{request:Ye,issuer:ke});let x=K.resolve(Ne);Ne.match(c)?it=K.normalize(K.join(x,Fe)):it=K.normalize(K.join(K.dirname(x),Fe))}return K.normalize(it)}function De(Fe,Ne,Pe=te,Ye){if(n.test(Fe))return Ne;let ke=ie(Ne,Pe,Ye);return ke?K.normalize(ke):Ne}function Re(Fe,{extensions:Ne=Object.keys(ah.Module._extensions)}={}){let Pe=[],Ye=Ae(Fe,Pe,{extensions:Ne});if(Ye)return K.normalize(Ye);{Pxe(Pe.map(_e=>ue.fromPortablePath(_e)));let ke=cf(Fe),it=fe(Fe);if(it){let{packageLocation:_e}=U(it),x=!0;try{e.fakeFs.accessSync(_e)}catch(w){if(w?.code===\"ENOENT\")x=!1;else{let b=(w?.message??w??\"empty exception thrown\").replace(/^[A-Z]/,y=>y.toLowerCase());throw ms(\"QUALIFIED_PATH_RESOLUTION_FAILED\",`Required package exists but could not be accessed (${b}).\n\nMissing package: ${it.name}@${it.reference}\nExpected package location: ${cf(_e)}\n`,{unqualifiedPath:ke,extensions:Ne})}}if(!x){let w=_e.includes(\"/unplugged/\")?\"Required unplugged package missing from disk. This may happen when switching branches without running installs (unplugged packages must be fully materialized on disk to work).\":\"Required package missing from disk. If you keep your packages inside your repository then restarting the Node process may be enough. Otherwise, try to run an install first.\";throw ms(\"QUALIFIED_PATH_RESOLUTION_FAILED\",`${w}\n\nMissing package: ${it.name}@${it.reference}\nExpected package location: ${cf(_e)}\n`,{unqualifiedPath:ke,extensions:Ne})}}throw ms(\"QUALIFIED_PATH_RESOLUTION_FAILED\",`Qualified path resolution failed: we looked for the following paths, but none could be accessed.\n\nSource path: ${ke}\n${Pe.map(_e=>`Not found: ${cf(_e)}\n`).join(\"\")}`,{unqualifiedPath:ke,extensions:Ne})}}function dt(Fe,Ne,Pe){if(!Ne)throw new Error(\"Assertion failed: An issuer is required to resolve private import mappings\");let Ye=Cxe({name:Fe,base:(0,Hm.pathToFileURL)(ue.fromPortablePath(Ne)),conditions:Pe.conditions??te,readFileSyncFn:se});if(Ye instanceof URL)return Re(ue.toPortablePath((0,Hm.fileURLToPath)(Ye)),{extensions:Pe.extensions});if(Ye.startsWith(\"#\"))throw new Error(\"Mapping from one private import to another isn't allowed\");return j(Ye,Ne,Pe)}function j(Fe,Ne,Pe={}){try{if(Fe.startsWith(\"#\"))return dt(Fe,Ne,Pe);let{considerBuiltins:Ye,extensions:ke,conditions:it}=Pe,_e=X(Fe,Ne,{considerBuiltins:Ye});if(Fe===\"pnpapi\")return _e;if(_e===null)return null;let x=()=>Ne!==null?pe(Ne):!1,w=(!Ye||!(0,ah.isBuiltin)(Fe))&&!x()?De(Fe,_e,it,Ne):_e;return Re(w,{extensions:ke})}catch(Ye){throw Object.hasOwn(Ye,\"pnpCode\")&&Object.assign(Ye.data,{request:cf(Fe),issuer:Ne&&cf(Ne)}),Ye}}function rt(Fe){let Ne=K.normalize(Fe),Pe=Ao.resolveVirtual(Ne);return Pe!==Ne?Pe:null}return{VERSIONS:Be,topLevel:Ce,getLocator:(Fe,Ne)=>Array.isArray(Ne)?{name:Ne[0],reference:Ne[1]}:{name:Fe,reference:Ne},getDependencyTreeRoots:()=>[...t.dependencyTreeRoots],getAllLocators(){let Fe=[];for(let[Ne,Pe]of S)for(let Ye of Pe.keys())Ne!==null&&Ye!==null&&Fe.push({name:Ne,reference:Ye});return Fe},getPackageInformation:Fe=>{let Ne=g(Fe);if(Ne===null)return null;let Pe=ue.fromPortablePath(Ne.packageLocation);return{...Ne,packageLocation:Pe}},findPackageLocator:Fe=>fe(ue.toPortablePath(Fe)),resolveToUnqualified:N(\"resolveToUnqualified\",(Fe,Ne,Pe)=>{let Ye=Ne!==null?ue.toPortablePath(Ne):null,ke=X(ue.toPortablePath(Fe),Ye,Pe);return ke===null?null:ue.fromPortablePath(ke)}),resolveUnqualified:N(\"resolveUnqualified\",(Fe,Ne)=>ue.fromPortablePath(Re(ue.toPortablePath(Fe),Ne))),resolveRequest:N(\"resolveRequest\",(Fe,Ne,Pe)=>{let Ye=Ne!==null?ue.toPortablePath(Ne):null,ke=j(ue.toPortablePath(Fe),Ye,Pe);return ke===null?null:ue.fromPortablePath(ke)}),resolveVirtual:N(\"resolveVirtual\",Fe=>{let Ne=rt(ue.toPortablePath(Fe));return Ne!==null?ue.fromPortablePath(Ne):null})}}bt();var xxe=(t,e,r)=>{let s=jD(t),a=QY(s,{basePath:e}),n=ue.join(e,Er.pnpCjs);return jY(a,{fakeFs:r,pnpapiResolution:n})};var GY=et(Qxe());Wt();var mA={};Vt(mA,{checkManifestCompatibility:()=>Txe,extractBuildRequest:()=>aN,getExtractHint:()=>WY,hasBindingGyp:()=>YY});Ve();bt();function Txe(t){return q.isPackageCompatible(t,ps.getArchitectureSet())}function aN(t,e,r,{configuration:s}){let a=[];for(let n of[\"preinstall\",\"install\",\"postinstall\"])e.manifest.scripts.has(n)&&a.push({type:0,script:n});return!e.manifest.scripts.has(\"install\")&&e.misc.hasBindingGyp&&a.push({type:1,script:\"node-gyp rebuild\"}),a.length===0?null:t.linkType!==\"HARD\"?{skipped:!0,explain:n=>n.reportWarningOnce(6,`${q.prettyLocator(s,t)} lists build scripts, but is referenced through a soft link. Soft links don't support build scripts, so they'll be ignored.`)}:r&&r.built===!1?{skipped:!0,explain:n=>n.reportInfoOnce(5,`${q.prettyLocator(s,t)} lists build scripts, but its build has been explicitly disabled through configuration.`)}:!s.get(\"enableScripts\")&&!r.built?{skipped:!0,explain:n=>n.reportWarningOnce(4,`${q.prettyLocator(s,t)} lists build scripts, but all build scripts have been disabled.`)}:Txe(t)?{skipped:!1,directives:a}:{skipped:!0,explain:n=>n.reportWarningOnce(76,`${q.prettyLocator(s,t)} The ${ps.getArchitectureName()} architecture is incompatible with this package, build skipped.`)}}var aTt=new Set([\".exe\",\".bin\",\".h\",\".hh\",\".hpp\",\".c\",\".cc\",\".cpp\",\".java\",\".jar\",\".node\"]);function WY(t){return t.packageFs.getExtractHint({relevantExtensions:aTt})}function YY(t){let e=K.join(t.prefixPath,\"binding.gyp\");return t.packageFs.existsSync(e)}var ZD={};Vt(ZD,{getUnpluggedPath:()=>zD});Ve();bt();function zD(t,{configuration:e}){return K.resolve(e.get(\"pnpUnpluggedFolder\"),q.slugifyLocator(t))}var lTt=new Set([q.makeIdent(null,\"open\").identHash,q.makeIdent(null,\"opn\").identHash]),ig=class{constructor(){this.mode=\"strict\";this.pnpCache=new Map}getCustomDataKey(){return JSON.stringify({name:\"PnpLinker\",version:2})}supportsPackage(e,r){return this.isEnabled(r)}async findPackageLocation(e,r){if(!this.isEnabled(r))throw new Error(\"Assertion failed: Expected the PnP linker to be enabled\");let s=sg(r.project).cjs;if(!le.existsSync(s))throw new nt(`The project in ${he.pretty(r.project.configuration,`${r.project.cwd}/package.json`,he.Type.PATH)} doesn't seem to have been installed - running an install there might help`);let a=je.getFactoryWithDefault(this.pnpCache,s,()=>je.dynamicRequire(s,{cachingStrategy:je.CachingStrategy.FsTime})),n={name:q.stringifyIdent(e),reference:e.reference},c=a.getPackageInformation(n);if(!c)throw new nt(`Couldn't find ${q.prettyLocator(r.project.configuration,e)} in the currently installed PnP map - running an install might help`);return ue.toPortablePath(c.packageLocation)}async findPackageLocator(e,r){if(!this.isEnabled(r))return null;let s=sg(r.project).cjs;if(!le.existsSync(s))return null;let n=je.getFactoryWithDefault(this.pnpCache,s,()=>je.dynamicRequire(s,{cachingStrategy:je.CachingStrategy.FsTime})).findPackageLocator(ue.fromPortablePath(e));return n?q.makeLocator(q.parseIdent(n.name),n.reference):null}makeInstaller(e){return new jm(e)}isEnabled(e){return!(e.project.configuration.get(\"nodeLinker\")!==\"pnp\"||e.project.configuration.get(\"pnpMode\")!==this.mode)}},jm=class{constructor(e){this.opts=e;this.mode=\"strict\";this.asyncActions=new je.AsyncActions(10);this.packageRegistry=new Map;this.virtualTemplates=new Map;this.isESMLoaderRequired=!1;this.customData={store:new Map};this.unpluggedPaths=new Set;this.opts=e}attachCustomData(e){this.customData=e}async installPackage(e,r,s){let a=q.stringifyIdent(e),n=e.reference,c=!!this.opts.project.tryWorkspaceByLocator(e),f=q.isVirtualLocator(e),p=e.peerDependencies.size>0&&!f,h=!p&&!c,E=!p&&e.linkType!==\"SOFT\",C,S;if(h||E){let te=f?q.devirtualizeLocator(e):e;C=this.customData.store.get(te.locatorHash),typeof C>\"u\"&&(C=await cTt(r),e.linkType===\"HARD\"&&this.customData.store.set(te.locatorHash,C)),C.manifest.type===\"module\"&&(this.isESMLoaderRequired=!0),S=this.opts.project.getDependencyMeta(te,e.version)}let P=h?aN(e,C,S,{configuration:this.opts.project.configuration}):null,I=E?await this.unplugPackageIfNeeded(e,C,r,S,s):r.packageFs;if(K.isAbsolute(r.prefixPath))throw new Error(`Assertion failed: Expected the prefix path (${r.prefixPath}) to be relative to the parent`);let R=K.resolve(I.getRealPath(),r.prefixPath),N=VY(this.opts.project.cwd,R),U=new Map,W=new Set;if(f){for(let te of e.peerDependencies.values())U.set(q.stringifyIdent(te),null),W.add(q.stringifyIdent(te));if(!c){let te=q.devirtualizeLocator(e);this.virtualTemplates.set(te.locatorHash,{location:VY(this.opts.project.cwd,Ao.resolveVirtual(R)),locator:te})}}return je.getMapWithDefault(this.packageRegistry,a).set(n,{packageLocation:N,packageDependencies:U,packagePeers:W,linkType:e.linkType,discardFromLookup:r.discardFromLookup||!1}),{packageLocation:R,buildRequest:P}}async attachInternalDependencies(e,r){let s=this.getPackageInformation(e);for(let[a,n]of r){let c=q.areIdentsEqual(a,n)?n.reference:[q.stringifyIdent(n),n.reference];s.packageDependencies.set(q.stringifyIdent(a),c)}}async attachExternalDependents(e,r){for(let s of r)this.getDiskInformation(s).packageDependencies.set(q.stringifyIdent(e),e.reference)}async finalizeInstall(){if(this.opts.project.configuration.get(\"pnpMode\")!==this.mode)return;let e=sg(this.opts.project);if(this.isEsmEnabled()||await le.removePromise(e.esmLoader),this.opts.project.configuration.get(\"nodeLinker\")!==\"pnp\"){await le.removePromise(e.cjs),await le.removePromise(e.data),await le.removePromise(e.esmLoader),await le.removePromise(this.opts.project.configuration.get(\"pnpUnpluggedFolder\"));return}for(let{locator:C,location:S}of this.virtualTemplates.values())je.getMapWithDefault(this.packageRegistry,q.stringifyIdent(C)).set(C.reference,{packageLocation:S,packageDependencies:new Map,packagePeers:new Set,linkType:\"SOFT\",discardFromLookup:!1});let r=this.opts.project.configuration.get(\"pnpFallbackMode\"),s=this.opts.project.workspaces.map(({anchoredLocator:C})=>({name:q.stringifyIdent(C),reference:C.reference})),a=r!==\"none\",n=[],c=new Map,f=je.buildIgnorePattern([\".yarn/sdks/**\",...this.opts.project.configuration.get(\"pnpIgnorePatterns\")]),p=this.packageRegistry,h=this.opts.project.configuration.get(\"pnpShebang\"),E=this.opts.project.configuration.get(\"pnpZipBackend\");if(r===\"dependencies-only\")for(let C of this.opts.project.storedPackages.values())this.opts.project.tryWorkspaceByLocator(C)&&n.push({name:q.stringifyIdent(C),reference:C.reference});return await this.asyncActions.wait(),await this.finalizeInstallWithPnp({dependencyTreeRoots:s,enableTopLevelFallback:a,fallbackExclusionList:n,fallbackPool:c,ignorePattern:f,pnpZipBackend:E,packageRegistry:p,shebang:h}),{customData:this.customData}}async transformPnpSettings(e){}isEsmEnabled(){if(this.opts.project.configuration.sources.has(\"pnpEnableEsmLoader\"))return this.opts.project.configuration.get(\"pnpEnableEsmLoader\");if(this.isESMLoaderRequired)return!0;for(let e of this.opts.project.workspaces)if(e.manifest.type===\"module\")return!0;return!1}async finalizeInstallWithPnp(e){let r=sg(this.opts.project),s=await this.locateNodeModules(e.ignorePattern);if(s.length>0){this.opts.report.reportWarning(31,\"One or more node_modules have been detected and will be removed. This operation may take some time.\");for(let n of s)await le.removePromise(n)}if(await this.transformPnpSettings(e),this.opts.project.configuration.get(\"pnpEnableInlining\")){let n=sxe(e);await le.changeFilePromise(r.cjs,n,{automaticNewlines:!0,mode:493}),await le.removePromise(r.data)}else{let{dataFile:n,loaderFile:c}=oxe(e);await le.changeFilePromise(r.cjs,c,{automaticNewlines:!0,mode:493}),await le.changeFilePromise(r.data,n,{automaticNewlines:!0,mode:420})}this.isEsmEnabled()&&(this.opts.report.reportWarning(0,\"ESM support for PnP uses the experimental loader API and is therefore experimental\"),await le.changeFilePromise(r.esmLoader,(0,GY.default)(),{automaticNewlines:!0,mode:420}));let a=this.opts.project.configuration.get(\"pnpUnpluggedFolder\");if(this.unpluggedPaths.size===0)await le.removePromise(a);else for(let n of await le.readdirPromise(a)){let c=K.resolve(a,n);this.unpluggedPaths.has(c)||await le.removePromise(c)}}async locateNodeModules(e){let r=[],s=e?new RegExp(e):null;for(let a of this.opts.project.workspaces){let n=K.join(a.cwd,\"node_modules\");if(s&&s.test(K.relative(this.opts.project.cwd,a.cwd))||!le.existsSync(n))continue;let c=await le.readdirPromise(n,{withFileTypes:!0}),f=c.filter(p=>!p.isDirectory()||p.name===\".bin\"||!p.name.startsWith(\".\"));if(f.length===c.length)r.push(n);else for(let p of f)r.push(K.join(n,p.name))}return r}async unplugPackageIfNeeded(e,r,s,a,n){return this.shouldBeUnplugged(e,r,a)?this.unplugPackage(e,s,n):s.packageFs}shouldBeUnplugged(e,r,s){return typeof s.unplugged<\"u\"?s.unplugged:lTt.has(e.identHash)||e.conditions!=null?!0:r.manifest.preferUnplugged!==null?r.manifest.preferUnplugged:!!(aN(e,r,s,{configuration:this.opts.project.configuration})?.skipped===!1||r.misc.extractHint)}async unplugPackage(e,r,s){let a=zD(e,{configuration:this.opts.project.configuration});return this.opts.project.disabledLocators.has(e.locatorHash)?new Hf(a,{baseFs:r.packageFs,pathUtils:K}):(this.unpluggedPaths.add(a),s.holdFetchResult(this.asyncActions.set(e.locatorHash,async()=>{let n=K.join(a,r.prefixPath,\".ready\");await le.existsPromise(n)||(this.opts.project.storedBuildState.delete(e.locatorHash),await le.mkdirPromise(a,{recursive:!0}),await le.copyPromise(a,vt.dot,{baseFs:r.packageFs,overwrite:!1}),await le.writeFilePromise(n,\"\"))})),new Sn(a))}getPackageInformation(e){let r=q.stringifyIdent(e),s=e.reference,a=this.packageRegistry.get(r);if(!a)throw new Error(`Assertion failed: The package information store should have been available (for ${q.prettyIdent(this.opts.project.configuration,e)})`);let n=a.get(s);if(!n)throw new Error(`Assertion failed: The package information should have been available (for ${q.prettyLocator(this.opts.project.configuration,e)})`);return n}getDiskInformation(e){let r=je.getMapWithDefault(this.packageRegistry,\"@@disk\"),s=VY(this.opts.project.cwd,e);return je.getFactoryWithDefault(r,s,()=>({packageLocation:s,packageDependencies:new Map,packagePeers:new Set,linkType:\"SOFT\",discardFromLookup:!1}))}};function VY(t,e){let r=K.relative(t,e);return r.match(/^\\.{0,2}\\//)||(r=`./${r}`),r.replace(/\\/?$/,\"/\")}async function cTt(t){let e=await Ht.tryFind(t.prefixPath,{baseFs:t.packageFs})??new Ht,r=new Set([\"preinstall\",\"install\",\"postinstall\"]);for(let s of e.scripts.keys())r.has(s)||e.scripts.delete(s);return{manifest:{scripts:e.scripts,preferUnplugged:e.preferUnplugged,type:e.type},misc:{extractHint:WY(t),hasBindingGyp:YY(t)}}}Ve();Ve();Wt();var Rxe=et(Sa());var Sw=class extends ut{constructor(){super(...arguments);this.all=ge.Boolean(\"-A,--all\",!1,{description:\"Unplug direct dependencies from the entire project\"});this.recursive=ge.Boolean(\"-R,--recursive\",!1,{description:\"Unplug both direct and transitive dependencies\"});this.json=ge.Boolean(\"--json\",!1,{description:\"Format the output as an NDJSON stream\"});this.patterns=ge.Rest()}static{this.paths=[[\"unplug\"]]}static{this.usage=ot.Usage({description:\"force the unpacking of a list of packages\",details:\"\\n      This command will add the selectors matching the specified patterns to the list of packages that must be unplugged when installed.\\n\\n      A package being unplugged means that instead of being referenced directly through its archive, it will be unpacked at install time in the directory configured via `pnpUnpluggedFolder`. Note that unpacking packages this way is generally not recommended because it'll make it harder to store your packages within the repository. However, it's a good approach to quickly and safely debug some packages, and can even sometimes be required depending on the context (for example when the package contains shellscripts).\\n\\n      Running the command will set a persistent flag inside your top-level `package.json`, in the `dependenciesMeta` field. As such, to undo its effects, you'll need to revert the changes made to the manifest and run `yarn install` to apply the modification.\\n\\n      By default, only direct dependencies from the current workspace are affected. If `-A,--all` is set, direct dependencies from the entire project are affected. Using the `-R,--recursive` flag will affect transitive dependencies as well as direct ones.\\n\\n      This command accepts glob patterns inside the scope and name components (not the range). Make sure to escape the patterns to prevent your own shell from trying to expand them.\\n    \",examples:[[\"Unplug the lodash dependency from the active workspace\",\"yarn unplug lodash\"],[\"Unplug all instances of lodash referenced by any workspace\",\"yarn unplug lodash -A\"],[\"Unplug all instances of lodash referenced by the active workspace and its dependencies\",\"yarn unplug lodash -R\"],[\"Unplug all instances of lodash, anywhere\",\"yarn unplug lodash -AR\"],[\"Unplug one specific version of lodash\",\"yarn unplug lodash@1.2.3\"],[\"Unplug all packages with the `@babel` scope\",\"yarn unplug '@babel/*'\"],[\"Unplug all packages (only for testing, not recommended)\",\"yarn unplug -R '*'\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Jr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);if(r.get(\"nodeLinker\")!==\"pnp\")throw new nt(\"This command can only be used if the `nodeLinker` option is set to `pnp`\");await s.restoreInstallState();let c=new Set(this.patterns),f=this.patterns.map(P=>{let I=q.parseDescriptor(P),R=I.range!==\"unknown\"?I:q.makeDescriptor(I,\"*\");if(!Or.validRange(R.range))throw new nt(`The range of the descriptor patterns must be a valid semver range (${q.prettyDescriptor(r,R)})`);return N=>{let U=q.stringifyIdent(N);return!Rxe.default.isMatch(U,q.stringifyIdent(R))||N.version&&!Or.satisfiesWithPrereleases(N.version,R.range)?!1:(c.delete(P),!0)}}),p=()=>{let P=[];for(let I of s.storedPackages.values())!s.tryWorkspaceByLocator(I)&&!q.isVirtualLocator(I)&&f.some(R=>R(I))&&P.push(I);return P},h=P=>{let I=new Set,R=[],N=(U,W)=>{if(I.has(U.locatorHash))return;let te=!!s.tryWorkspaceByLocator(U);if(!(W>0&&!this.recursive&&te)&&(I.add(U.locatorHash),!s.tryWorkspaceByLocator(U)&&f.some(ie=>ie(U))&&R.push(U),!(W>0&&!this.recursive)))for(let ie of U.dependencies.values()){let Ae=s.storedResolutions.get(ie.descriptorHash);if(!Ae)throw new Error(\"Assertion failed: The resolution should have been registered\");let ce=s.storedPackages.get(Ae);if(!ce)throw new Error(\"Assertion failed: The package should have been registered\");N(ce,W+1)}};for(let U of P)N(U.anchoredPackage,0);return R},E,C;if(this.all&&this.recursive?(E=p(),C=\"the project\"):this.all?(E=h(s.workspaces),C=\"any workspace\"):(E=h([a]),C=\"this workspace\"),c.size>1)throw new nt(`Patterns ${he.prettyList(r,c,he.Type.CODE)} don't match any packages referenced by ${C}`);if(c.size>0)throw new nt(`Pattern ${he.prettyList(r,c,he.Type.CODE)} doesn't match any packages referenced by ${C}`);E=je.sortMap(E,P=>q.stringifyLocator(P));let S=await Ot.start({configuration:r,stdout:this.context.stdout,json:this.json},async P=>{for(let I of E){let R=I.version??\"unknown\",N=s.topLevelWorkspace.manifest.ensureDependencyMeta(q.makeDescriptor(I,R));N.unplugged=!0,P.reportInfo(0,`Will unpack ${q.prettyLocator(r,I)} to ${he.pretty(r,zD(I,{configuration:r}),he.Type.PATH)}`),P.reportJson({locator:q.stringifyLocator(I),version:R})}await s.topLevelWorkspace.persistManifest(),this.json||P.reportSeparator()});return S.hasErrors()?S.exitCode():await s.installWithNewReport({json:this.json,stdout:this.context.stdout},{cache:n})}};var sg=t=>({cjs:K.join(t.cwd,Er.pnpCjs),data:K.join(t.cwd,Er.pnpData),esmLoader:K.join(t.cwd,Er.pnpEsmLoader)}),Nxe=t=>/\\s/.test(t)?JSON.stringify(t):t;async function uTt(t,e,r){let s=/\\s*--require\\s+\\S*\\.pnp\\.c?js\\s*/g,a=/\\s*--experimental-loader\\s+\\S*\\.pnp\\.loader\\.mjs\\s*/,n=(e.NODE_OPTIONS??\"\").replace(s,\" \").replace(a,\" \").trim();if(t.configuration.get(\"nodeLinker\")!==\"pnp\"){e.NODE_OPTIONS=n||void 0;return}let c=sg(t),f=`--require ${Nxe(ue.fromPortablePath(c.cjs))}`;le.existsSync(c.esmLoader)&&(f=`${f} --experimental-loader ${(0,Fxe.pathToFileURL)(ue.fromPortablePath(c.esmLoader)).href}`),le.existsSync(c.cjs)&&(e.NODE_OPTIONS=n?`${f} ${n}`:f)}async function fTt(t,e){let r=sg(t);e(r.cjs),e(r.data),e(r.esmLoader),e(t.configuration.get(\"pnpUnpluggedFolder\"))}var ATt={hooks:{populateYarnPaths:fTt,setupScriptEnvironment:uTt},configuration:{nodeLinker:{description:'The linker used for installing Node packages, one of: \"pnp\", \"pnpm\", or \"node-modules\"',type:\"STRING\",default:\"pnp\"},minizip:{description:\"Whether Yarn should use minizip to extract archives\",type:\"BOOLEAN\",default:!1},winLinkType:{description:\"Whether Yarn should use Windows Junctions or symlinks when creating links on Windows.\",type:\"STRING\",values:[\"junctions\",\"symlinks\"],default:\"junctions\"},pnpMode:{description:\"If 'strict', generates standard PnP maps. If 'loose', merges them with the n_m resolution.\",type:\"STRING\",default:\"strict\"},pnpShebang:{description:\"String to prepend to the generated PnP script\",type:\"STRING\",default:\"#!/usr/bin/env node\"},pnpIgnorePatterns:{description:\"Array of glob patterns; files matching them will use the classic resolution\",type:\"STRING\",default:[],isArray:!0},pnpZipBackend:{description:\"Whether to use the experimental js implementation for the ZipFS\",type:\"STRING\",values:[\"libzip\",\"js\"],default:\"libzip\"},pnpEnableEsmLoader:{description:\"If true, Yarn will generate an ESM loader (`.pnp.loader.mjs`). If this is not explicitly set Yarn tries to automatically detect whether ESM support is required.\",type:\"BOOLEAN\",default:!1},pnpEnableInlining:{description:\"If true, the PnP data will be inlined along with the generated loader\",type:\"BOOLEAN\",default:!0},pnpFallbackMode:{description:\"If true, the generated PnP loader will follow the top-level fallback rule\",type:\"STRING\",default:\"dependencies-only\"},pnpUnpluggedFolder:{description:\"Folder where the unplugged packages must be stored\",type:\"ABSOLUTE_PATH\",default:\"./.yarn/unplugged\"}},linkers:[ig],commands:[Sw]},pTt=ATt;var qxe=et(Uxe());Wt();var tV=et(Ie(\"crypto\")),Gxe=et(Ie(\"fs\")),Wxe=1,Ri=\"node_modules\",lN=\".bin\",Yxe=\".yarn-state.yml\",kTt=1e3,rV=(s=>(s.CLASSIC=\"classic\",s.HARDLINKS_LOCAL=\"hardlinks-local\",s.HARDLINKS_GLOBAL=\"hardlinks-global\",s))(rV||{}),XD=class{constructor(){this.installStateCache=new Map}getCustomDataKey(){return JSON.stringify({name:\"NodeModulesLinker\",version:3})}supportsPackage(e,r){return this.isEnabled(r)}async findPackageLocation(e,r){if(!this.isEnabled(r))throw new Error(\"Assertion failed: Expected the node-modules linker to be enabled\");let s=r.project.tryWorkspaceByLocator(e);if(s)return s.cwd;let a=await je.getFactoryWithDefault(this.installStateCache,r.project.cwd,async()=>await eV(r.project,{unrollAliases:!0}));if(a===null)throw new nt(\"Couldn't find the node_modules state file - running an install might help (findPackageLocation)\");let n=a.locatorMap.get(q.stringifyLocator(e));if(!n){let p=new nt(`Couldn't find ${q.prettyLocator(r.project.configuration,e)} in the currently installed node_modules map - running an install might help`);throw p.code=\"LOCATOR_NOT_INSTALLED\",p}let c=n.locations.sort((p,h)=>p.split(K.sep).length-h.split(K.sep).length),f=K.join(r.project.configuration.startingCwd,Ri);return c.find(p=>K.contains(f,p))||n.locations[0]}async findPackageLocator(e,r){if(!this.isEnabled(r))return null;let s=await je.getFactoryWithDefault(this.installStateCache,r.project.cwd,async()=>await eV(r.project,{unrollAliases:!0}));if(s===null)return null;let{locationRoot:a,segments:n}=cN(K.resolve(e),{skipPrefix:r.project.cwd}),c=s.locationTree.get(a);if(!c)return null;let f=c.locator;for(let p of n){if(c=c.children.get(p),!c)break;f=c.locator||f}return q.parseLocator(f)}makeInstaller(e){return new $Y(e)}isEnabled(e){return e.project.configuration.get(\"nodeLinker\")===\"node-modules\"}},$Y=class{constructor(e){this.opts=e;this.localStore=new Map;this.realLocatorChecksums=new Map;this.customData={store:new Map}}attachCustomData(e){this.customData=e}async installPackage(e,r){let s=K.resolve(r.packageFs.getRealPath(),r.prefixPath),a=this.customData.store.get(e.locatorHash);if(typeof a>\"u\"&&(a=await QTt(e,r),e.linkType===\"HARD\"&&this.customData.store.set(e.locatorHash,a)),!q.isPackageCompatible(e,this.opts.project.configuration.getSupportedArchitectures()))return{packageLocation:null,buildRequest:null};let n=new Map,c=new Set;n.has(q.stringifyIdent(e))||n.set(q.stringifyIdent(e),e.reference);let f=e;if(q.isVirtualLocator(e)){f=q.devirtualizeLocator(e);for(let E of e.peerDependencies.values())n.set(q.stringifyIdent(E),null),c.add(q.stringifyIdent(E))}let p={packageLocation:`${ue.fromPortablePath(s)}/`,packageDependencies:n,packagePeers:c,linkType:e.linkType,discardFromLookup:r.discardFromLookup??!1};this.localStore.set(e.locatorHash,{pkg:e,customPackageData:a,dependencyMeta:this.opts.project.getDependencyMeta(e,e.version),pnpNode:p});let h=r.checksum?r.checksum.substring(r.checksum.indexOf(\"/\")+1):null;return this.realLocatorChecksums.set(f.locatorHash,h),{packageLocation:s,buildRequest:null}}async attachInternalDependencies(e,r){let s=this.localStore.get(e.locatorHash);if(typeof s>\"u\")throw new Error(\"Assertion failed: Expected information object to have been registered\");for(let[a,n]of r){let c=q.areIdentsEqual(a,n)?n.reference:[q.stringifyIdent(n),n.reference];s.pnpNode.packageDependencies.set(q.stringifyIdent(a),c)}}async attachExternalDependents(e,r){throw new Error(\"External dependencies haven't been implemented for the node-modules linker\")}async finalizeInstall(){if(this.opts.project.configuration.get(\"nodeLinker\")!==\"node-modules\")return;let e=new Ao({baseFs:new tA({maxOpenFiles:80,readOnlyArchives:!0})}),r=await eV(this.opts.project),s=this.opts.project.configuration.get(\"nmMode\");(r===null||s!==r.nmMode)&&(this.opts.project.storedBuildState.clear(),r={locatorMap:new Map,binSymlinks:new Map,locationTree:new Map,nmMode:s,mtimeMs:0});let a=new Map(this.opts.project.workspaces.map(S=>{let P=this.opts.project.configuration.get(\"nmHoistingLimits\");try{P=je.validateEnum(_D,S.manifest.installConfig?.hoistingLimits??P)}catch{let I=q.prettyWorkspace(this.opts.project.configuration,S);this.opts.report.reportWarning(57,`${I}: Invalid 'installConfig.hoistingLimits' value. Expected one of ${Object.values(_D).join(\", \")}, using default: \"${P}\"`)}return[S.relativeCwd,P]})),n=new Map(this.opts.project.workspaces.map(S=>{let P=this.opts.project.configuration.get(\"nmSelfReferences\");return P=S.manifest.installConfig?.selfReferences??P,[S.relativeCwd,P]})),c={VERSIONS:{std:1},topLevel:{name:null,reference:null},getLocator:(S,P)=>Array.isArray(P)?{name:P[0],reference:P[1]}:{name:S,reference:P},getDependencyTreeRoots:()=>this.opts.project.workspaces.map(S=>{let P=S.anchoredLocator;return{name:q.stringifyIdent(P),reference:P.reference}}),getPackageInformation:S=>{let P=S.reference===null?this.opts.project.topLevelWorkspace.anchoredLocator:q.makeLocator(q.parseIdent(S.name),S.reference),I=this.localStore.get(P.locatorHash);if(typeof I>\"u\")throw new Error(\"Assertion failed: Expected the package reference to have been registered\");return I.pnpNode},findPackageLocator:S=>{let P=this.opts.project.tryWorkspaceByCwd(ue.toPortablePath(S));if(P!==null){let I=P.anchoredLocator;return{name:q.stringifyIdent(I),reference:I.reference}}throw new Error(\"Assertion failed: Unimplemented\")},resolveToUnqualified:()=>{throw new Error(\"Assertion failed: Unimplemented\")},resolveUnqualified:()=>{throw new Error(\"Assertion failed: Unimplemented\")},resolveRequest:()=>{throw new Error(\"Assertion failed: Unimplemented\")},resolveVirtual:S=>ue.fromPortablePath(Ao.resolveVirtual(ue.toPortablePath(S)))},{tree:f,errors:p,preserveSymlinksRequired:h}=UD(c,{pnpifyFs:!1,validateExternalSoftLinks:!0,hoistingLimitsByCwd:a,project:this.opts.project,selfReferencesByCwd:n});if(!f){for(let{messageName:S,text:P}of p)this.opts.report.reportError(S,P);return}let E=xY(f);await MTt(r,E,{baseFs:e,project:this.opts.project,report:this.opts.report,realLocatorChecksums:this.realLocatorChecksums,loadManifest:async S=>{let P=q.parseLocator(S),I=this.localStore.get(P.locatorHash);if(typeof I>\"u\")throw new Error(\"Assertion failed: Expected the slot to exist\");return I.customPackageData.manifest}});let C=[];for(let[S,P]of E.entries()){if(Jxe(S))continue;let I=q.parseLocator(S),R=this.localStore.get(I.locatorHash);if(typeof R>\"u\")throw new Error(\"Assertion failed: Expected the slot to exist\");if(this.opts.project.tryWorkspaceByLocator(R.pkg))continue;let N=mA.extractBuildRequest(R.pkg,R.customPackageData,R.dependencyMeta,{configuration:this.opts.project.configuration});N&&C.push({buildLocations:P.locations,locator:I,buildRequest:N})}return h&&this.opts.report.reportWarning(72,`The application uses portals and that's why ${he.pretty(this.opts.project.configuration,\"--preserve-symlinks\",he.Type.CODE)} Node option is required for launching it`),{customData:this.customData,records:C}}};async function QTt(t,e){let r=await Ht.tryFind(e.prefixPath,{baseFs:e.packageFs})??new Ht,s=new Set([\"preinstall\",\"install\",\"postinstall\"]);for(let a of r.scripts.keys())s.has(a)||r.scripts.delete(a);return{manifest:{bin:r.bin,scripts:r.scripts},misc:{hasBindingGyp:mA.hasBindingGyp(e)}}}async function TTt(t,e,r,s,{installChangedByUser:a}){let n=\"\";n+=`# Warning: This file is automatically generated. Removing it is fine, but will\n`,n+=`# cause your node_modules installation to become invalidated.\n`,n+=`\n`,n+=`__metadata:\n`,n+=`  version: ${Wxe}\n`,n+=`  nmMode: ${s.value}\n`;let c=Array.from(e.keys()).sort(),f=q.stringifyLocator(t.topLevelWorkspace.anchoredLocator);for(let E of c){let C=e.get(E);n+=`\n`,n+=`${JSON.stringify(E)}:\n`,n+=`  locations:\n`;for(let S of C.locations){let P=K.contains(t.cwd,S);if(P===null)throw new Error(`Assertion failed: Expected the path to be within the project (${S})`);n+=`    - ${JSON.stringify(P)}\n`}if(C.aliases.length>0){n+=`  aliases:\n`;for(let S of C.aliases)n+=`    - ${JSON.stringify(S)}\n`}if(E===f&&r.size>0){n+=`  bin:\n`;for(let[S,P]of r){let I=K.contains(t.cwd,S);if(I===null)throw new Error(`Assertion failed: Expected the path to be within the project (${S})`);n+=`    ${JSON.stringify(I)}:\n`;for(let[R,N]of P){let U=K.relative(K.join(S,Ri),N);n+=`      ${JSON.stringify(R)}: ${JSON.stringify(U)}\n`}}}}let p=t.cwd,h=K.join(p,Ri,Yxe);a&&await le.removePromise(h),await le.changeFilePromise(h,n,{automaticNewlines:!0})}async function eV(t,{unrollAliases:e=!1}={}){let r=t.cwd,s=K.join(r,Ri,Yxe),a;try{a=await le.statPromise(s)}catch{}if(!a)return null;let n=cs(await le.readFilePromise(s,\"utf8\"));if(n.__metadata.version>Wxe)return null;let c=n.__metadata.nmMode||\"classic\",f=new Map,p=new Map;delete n.__metadata;for(let[h,E]of Object.entries(n)){let C=E.locations.map(P=>K.join(r,P)),S=E.bin;if(S)for(let[P,I]of Object.entries(S)){let R=K.join(r,ue.toPortablePath(P)),N=je.getMapWithDefault(p,R);for(let[U,W]of Object.entries(I))N.set(U,ue.toPortablePath([R,Ri,W].join(K.sep)))}if(f.set(h,{target:vt.dot,linkType:\"HARD\",locations:C,aliases:E.aliases||[]}),e&&E.aliases)for(let P of E.aliases){let{scope:I,name:R}=q.parseLocator(h),N=q.makeLocator(q.makeIdent(I,R),P),U=q.stringifyLocator(N);f.set(U,{target:vt.dot,linkType:\"HARD\",locations:C,aliases:[]})}}return{locatorMap:f,binSymlinks:p,locationTree:Vxe(f,{skipPrefix:t.cwd}),nmMode:c,mtimeMs:a.mtimeMs}}var bw=async(t,e)=>{if(t.split(K.sep).indexOf(Ri)<0)throw new Error(`Assertion failed: trying to remove dir that doesn't contain node_modules: ${t}`);try{let r;if(!e.innerLoop&&(r=await le.lstatPromise(t),!r.isDirectory()&&!r.isSymbolicLink()||r.isSymbolicLink()&&!e.isWorkspaceDir)){await le.unlinkPromise(t);return}let s=await le.readdirPromise(t,{withFileTypes:!0});for(let n of s){let c=K.join(t,n.name);n.isDirectory()?(n.name!==Ri||e&&e.innerLoop)&&await bw(c,{innerLoop:!0,contentsOnly:!1}):await le.unlinkPromise(c)}let a=!e.innerLoop&&e.isWorkspaceDir&&r?.isSymbolicLink();!e.contentsOnly&&!a&&await le.rmdirPromise(t)}catch(r){if(r.code!==\"ENOENT\"&&r.code!==\"ENOTEMPTY\")throw r}},Hxe=4,cN=(t,{skipPrefix:e})=>{let r=K.contains(e,t);if(r===null)throw new Error(`Assertion failed: Writing attempt prevented to ${t} which is outside project root: ${e}`);let s=r.split(K.sep).filter(p=>p!==\"\"),a=s.indexOf(Ri),n=s.slice(0,a).join(K.sep),c=K.join(e,n),f=s.slice(a);return{locationRoot:c,segments:f}},Vxe=(t,{skipPrefix:e})=>{let r=new Map;if(t===null)return r;let s=()=>({children:new Map,linkType:\"HARD\"});for(let[a,n]of t.entries()){if(n.linkType===\"SOFT\"&&K.contains(e,n.target)!==null){let f=je.getFactoryWithDefault(r,n.target,s);f.locator=a,f.linkType=n.linkType}for(let c of n.locations){let{locationRoot:f,segments:p}=cN(c,{skipPrefix:e}),h=je.getFactoryWithDefault(r,f,s);for(let E=0;E<p.length;++E){let C=p[E];if(C!==\".\"){let S=je.getFactoryWithDefault(h.children,C,s);h.children.set(C,S),h=S}E===p.length-1&&(h.locator=a,h.linkType=n.linkType)}}}return r},nV=async(t,e,r)=>{if(process.platform===\"win32\"&&r===\"junctions\"){let s;try{s=await le.lstatPromise(t)}catch{}if(!s||s.isDirectory()){await le.symlinkPromise(t,e,\"junction\");return}}await le.symlinkPromise(K.relative(K.dirname(e),t),e)};async function Kxe(t,e,r){let s=K.join(t,`${tV.default.randomBytes(16).toString(\"hex\")}.tmp`);try{await le.writeFilePromise(s,r);try{await le.linkPromise(s,e)}catch{}}finally{await le.unlinkPromise(s)}}async function RTt({srcPath:t,dstPath:e,entry:r,globalHardlinksStore:s,baseFs:a,nmMode:n}){if(r.kind===\"file\"){if(n.value===\"hardlinks-global\"&&s&&r.digest){let f=K.join(s,r.digest.substring(0,2),`${r.digest.substring(2)}.dat`),p;try{let h=await le.statPromise(f);if(h&&(!r.mtimeMs||h.mtimeMs>r.mtimeMs||h.mtimeMs<r.mtimeMs-kTt))if(await Nn.checksumFile(f,{baseFs:le,algorithm:\"sha1\"})!==r.digest){let C=K.join(s,`${tV.default.randomBytes(16).toString(\"hex\")}.tmp`);await le.renamePromise(f,C);let S=await a.readFilePromise(t);await le.writeFilePromise(C,S);try{await le.linkPromise(C,f),r.mtimeMs=new Date().getTime(),await le.unlinkPromise(C)}catch{}}else r.mtimeMs||(r.mtimeMs=Math.ceil(h.mtimeMs));await le.linkPromise(f,e),p=!0}catch{p=!1}if(!p){let h=await a.readFilePromise(t);await Kxe(s,f,h),r.mtimeMs=new Date().getTime();try{await le.linkPromise(f,e)}catch(E){E&&E.code&&E.code==\"EXDEV\"&&(n.value=\"hardlinks-local\",await a.copyFilePromise(t,e))}}}else await a.copyFilePromise(t,e);let c=r.mode&511;c!==420&&await le.chmodPromise(e,c)}}var FTt=async(t,e,{baseFs:r,globalHardlinksStore:s,nmMode:a,windowsLinkType:n,packageChecksum:c})=>{await le.mkdirPromise(t,{recursive:!0});let f=async(E=vt.dot)=>{let C=K.join(e,E),S=await r.readdirPromise(C,{withFileTypes:!0}),P=new Map;for(let I of S){let R=K.join(E,I.name),N,U=K.join(C,I.name);if(I.isFile()){if(N={kind:\"file\",mode:(await r.lstatPromise(U)).mode},a.value===\"hardlinks-global\"){let W=await Nn.checksumFile(U,{baseFs:r,algorithm:\"sha1\"});N.digest=W}}else if(I.isDirectory())N={kind:\"directory\"};else if(I.isSymbolicLink())N={kind:\"symlink\",symlinkTo:await r.readlinkPromise(U)};else throw new Error(`Unsupported file type (file: ${U}, mode: 0o${await r.statSync(U).mode.toString(8).padStart(6,\"0\")})`);if(P.set(R,N),I.isDirectory()&&R!==Ri){let W=await f(R);for(let[te,ie]of W)P.set(te,ie)}}return P},p;if(a.value===\"hardlinks-global\"&&s&&c){let E=K.join(s,c.substring(0,2),`${c.substring(2)}.json`);try{p=new Map(Object.entries(JSON.parse(await le.readFilePromise(E,\"utf8\"))))}catch{p=await f()}}else p=await f();let h=!1;for(let[E,C]of p){let S=K.join(e,E),P=K.join(t,E);if(C.kind===\"directory\")await le.mkdirPromise(P,{recursive:!0});else if(C.kind===\"file\"){let I=C.mtimeMs;await RTt({srcPath:S,dstPath:P,entry:C,nmMode:a,baseFs:r,globalHardlinksStore:s}),C.mtimeMs!==I&&(h=!0)}else C.kind===\"symlink\"&&await nV(K.resolve(K.dirname(P),C.symlinkTo),P,n)}if(a.value===\"hardlinks-global\"&&s&&h&&c){let E=K.join(s,c.substring(0,2),`${c.substring(2)}.json`);await le.removePromise(E),await Kxe(s,E,Buffer.from(JSON.stringify(Object.fromEntries(p))))}};function NTt(t,e,r,s){let a=new Map,n=new Map,c=new Map,f=!1,p=(h,E,C,S,P)=>{let I=!0,R=K.join(h,E),N=new Set;if(E===Ri||E.startsWith(\"@\")){let W;try{W=le.statSync(R)}catch{}I=!!W,W?W.mtimeMs>r?(f=!0,N=new Set(le.readdirSync(R))):N=new Set(C.children.get(E).children.keys()):f=!0;let te=e.get(h);if(te){let ie=K.join(h,Ri,lN),Ae;try{Ae=le.statSync(ie)}catch{}if(!Ae)f=!0;else if(Ae.mtimeMs>r){f=!0;let ce=new Set(le.readdirSync(ie)),me=new Map;n.set(h,me);for(let[pe,Be]of te)ce.has(pe)&&me.set(pe,Be)}else n.set(h,te)}}else I=P.has(E);let U=C.children.get(E);if(I){let{linkType:W,locator:te}=U,ie={children:new Map,linkType:W,locator:te};if(S.children.set(E,ie),te){let Ae=je.getSetWithDefault(c,te);Ae.add(R),c.set(te,Ae)}for(let Ae of U.children.keys())p(R,Ae,U,ie,N)}else U.locator&&s.storedBuildState.delete(q.parseLocator(U.locator).locatorHash)};for(let[h,E]of t){let{linkType:C,locator:S}=E,P={children:new Map,linkType:C,locator:S};if(a.set(h,P),S){let I=je.getSetWithDefault(c,E.locator);I.add(h),c.set(E.locator,I)}E.children.has(Ri)&&p(h,Ri,E,P,new Set)}return{locationTree:a,binSymlinks:n,locatorLocations:c,installChangedByUser:f}}function Jxe(t){let e=q.parseDescriptor(t);return q.isVirtualDescriptor(e)&&(e=q.devirtualizeDescriptor(e)),e.range.startsWith(\"link:\")}async function OTt(t,e,r,{loadManifest:s}){let a=new Map;for(let[f,{locations:p}]of t){let h=Jxe(f)?null:await s(f,p[0]),E=new Map;if(h)for(let[C,S]of h.bin){let P=K.join(p[0],S);S!==\"\"&&le.existsSync(P)&&E.set(C,S)}a.set(f,E)}let n=new Map,c=(f,p,h)=>{let E=new Map,C=K.contains(r,f);if(h.locator&&C!==null){let S=a.get(h.locator);for(let[P,I]of S){let R=K.join(f,ue.toPortablePath(I));E.set(P,R)}for(let[P,I]of h.children){let R=K.join(f,P),N=c(R,R,I);N.size>0&&n.set(f,new Map([...n.get(f)||new Map,...N]))}}else for(let[S,P]of h.children){let I=c(K.join(f,S),p,P);for(let[R,N]of I)E.set(R,N)}return E};for(let[f,p]of e){let h=c(f,f,p);h.size>0&&n.set(f,new Map([...n.get(f)||new Map,...h]))}return n}var jxe=(t,e)=>{if(!t||!e)return t===e;let r=q.parseLocator(t);q.isVirtualLocator(r)&&(r=q.devirtualizeLocator(r));let s=q.parseLocator(e);return q.isVirtualLocator(s)&&(s=q.devirtualizeLocator(s)),q.areLocatorsEqual(r,s)};function iV(t){return K.join(t.get(\"globalFolder\"),\"store\")}function LTt(t,e){let r=s=>{let a=s.split(K.sep),n=a.lastIndexOf(Ri);if(n<0||n==a.length-1)throw new Error(`Assertion failed. Path is outside of any node_modules package ${s}`);return a.slice(0,n+(a[n+1].startsWith(\"@\")?3:2)).join(K.sep)};for(let s of t.values())for(let[a,n]of s)e.has(r(n))&&s.delete(a)}async function MTt(t,e,{baseFs:r,project:s,report:a,loadManifest:n,realLocatorChecksums:c}){let f=K.join(s.cwd,Ri),{locationTree:p,binSymlinks:h,locatorLocations:E,installChangedByUser:C}=NTt(t.locationTree,t.binSymlinks,t.mtimeMs,s),S=Vxe(e,{skipPrefix:s.cwd}),P=[],I=async({srcDir:Be,dstDir:Ce,linkType:g,globalHardlinksStore:we,nmMode:ye,windowsLinkType:fe,packageChecksum:se})=>{let X=(async()=>{try{g===\"SOFT\"?(await le.mkdirPromise(K.dirname(Ce),{recursive:!0}),await nV(K.resolve(Be),Ce,fe)):await FTt(Ce,Be,{baseFs:r,globalHardlinksStore:we,nmMode:ye,windowsLinkType:fe,packageChecksum:se})}catch(De){throw De.message=`While persisting ${Be} -> ${Ce} ${De.message}`,De}finally{ie.tick()}})().then(()=>P.splice(P.indexOf(X),1));P.push(X),P.length>Hxe&&await Promise.race(P)},R=async(Be,Ce,g)=>{let we=(async()=>{let ye=async(fe,se,X)=>{try{X.innerLoop||await le.mkdirPromise(se,{recursive:!0});let De=await le.readdirPromise(fe,{withFileTypes:!0});for(let Re of De){if(!X.innerLoop&&Re.name===lN)continue;let dt=K.join(fe,Re.name),j=K.join(se,Re.name);Re.isDirectory()?(Re.name!==Ri||X&&X.innerLoop)&&(await le.mkdirPromise(j,{recursive:!0}),await ye(dt,j,{...X,innerLoop:!0})):me.value===\"hardlinks-local\"||me.value===\"hardlinks-global\"?await le.linkPromise(dt,j):await le.copyFilePromise(dt,j,Gxe.default.constants.COPYFILE_FICLONE)}}catch(De){throw X.innerLoop||(De.message=`While cloning ${fe} -> ${se} ${De.message}`),De}finally{X.innerLoop||ie.tick()}};await ye(Be,Ce,g)})().then(()=>P.splice(P.indexOf(we),1));P.push(we),P.length>Hxe&&await Promise.race(P)},N=async(Be,Ce,g)=>{if(g)for(let[we,ye]of Ce.children){let fe=g.children.get(we);await N(K.join(Be,we),ye,fe)}else{Ce.children.has(Ri)&&await bw(K.join(Be,Ri),{contentsOnly:!1});let we=K.basename(Be)===Ri&&p.has(K.join(K.dirname(Be)));await bw(Be,{contentsOnly:Be===f,isWorkspaceDir:we})}};for(let[Be,Ce]of p){let g=S.get(Be);for(let[we,ye]of Ce.children){if(we===\".\")continue;let fe=g&&g.children.get(we),se=K.join(Be,we);await N(se,ye,fe)}}let U=async(Be,Ce,g)=>{if(g){jxe(Ce.locator,g.locator)||await bw(Be,{contentsOnly:Ce.linkType===\"HARD\"});for(let[we,ye]of Ce.children){let fe=g.children.get(we);await U(K.join(Be,we),ye,fe)}}else{Ce.children.has(Ri)&&await bw(K.join(Be,Ri),{contentsOnly:!0});let we=K.basename(Be)===Ri&&S.has(K.join(K.dirname(Be)));await bw(Be,{contentsOnly:Ce.linkType===\"HARD\",isWorkspaceDir:we})}};for(let[Be,Ce]of S){let g=p.get(Be);for(let[we,ye]of Ce.children){if(we===\".\")continue;let fe=g&&g.children.get(we);await U(K.join(Be,we),ye,fe)}}let W=new Map,te=[];for(let[Be,Ce]of E)for(let g of Ce){let{locationRoot:we,segments:ye}=cN(g,{skipPrefix:s.cwd}),fe=S.get(we),se=we;if(fe){for(let X of ye)if(se=K.join(se,X),fe=fe.children.get(X),!fe)break;if(fe){let X=jxe(fe.locator,Be),De=e.get(fe.locator),Re=De.target,dt=se,j=De.linkType;if(X)W.has(Re)||W.set(Re,dt);else if(Re!==dt){let rt=q.parseLocator(fe.locator);q.isVirtualLocator(rt)&&(rt=q.devirtualizeLocator(rt)),te.push({srcDir:Re,dstDir:dt,linkType:j,realLocatorHash:rt.locatorHash})}}}}for(let[Be,{locations:Ce}]of e.entries())for(let g of Ce){let{locationRoot:we,segments:ye}=cN(g,{skipPrefix:s.cwd}),fe=p.get(we),se=S.get(we),X=we,De=e.get(Be),Re=q.parseLocator(Be);q.isVirtualLocator(Re)&&(Re=q.devirtualizeLocator(Re));let dt=Re.locatorHash,j=De.target,rt=g;if(j===rt)continue;let Fe=De.linkType;for(let Ne of ye)se=se.children.get(Ne);if(!fe)te.push({srcDir:j,dstDir:rt,linkType:Fe,realLocatorHash:dt});else for(let Ne of ye)if(X=K.join(X,Ne),fe=fe.children.get(Ne),!fe){te.push({srcDir:j,dstDir:rt,linkType:Fe,realLocatorHash:dt});break}}let ie=ho.progressViaCounter(te.length),Ae=a.reportProgress(ie),ce=s.configuration.get(\"nmMode\"),me={value:ce},pe=s.configuration.get(\"winLinkType\");try{let Be=me.value===\"hardlinks-global\"?`${iV(s.configuration)}/v1`:null;if(Be&&!await le.existsPromise(Be)){await le.mkdirpPromise(Be);for(let g=0;g<256;g++)await le.mkdirPromise(K.join(Be,g.toString(16).padStart(2,\"0\")))}for(let g of te)(g.linkType===\"SOFT\"||!W.has(g.srcDir))&&(W.set(g.srcDir,g.dstDir),await I({...g,globalHardlinksStore:Be,nmMode:me,windowsLinkType:pe,packageChecksum:c.get(g.realLocatorHash)||null}));await Promise.all(P),P.length=0;for(let g of te){let we=W.get(g.srcDir);g.linkType!==\"SOFT\"&&g.dstDir!==we&&await R(we,g.dstDir,{nmMode:me})}await Promise.all(P),await le.mkdirPromise(f,{recursive:!0}),LTt(h,new Set(te.map(g=>g.dstDir)));let Ce=await OTt(e,S,s.cwd,{loadManifest:n});await _Tt(h,Ce,s.cwd,pe),await TTt(s,e,Ce,me,{installChangedByUser:C}),ce==\"hardlinks-global\"&&me.value==\"hardlinks-local\"&&a.reportWarningOnce(74,\"'nmMode' has been downgraded to 'hardlinks-local' due to global cache and install folder being on different devices\")}finally{Ae.stop()}}async function _Tt(t,e,r,s){for(let a of t.keys()){if(K.contains(r,a)===null)throw new Error(`Assertion failed. Excepted bin symlink location to be inside project dir, instead it was at ${a}`);if(!e.has(a)){let n=K.join(a,Ri,lN);await le.removePromise(n)}}for(let[a,n]of e){if(K.contains(r,a)===null)throw new Error(`Assertion failed. Excepted bin symlink location to be inside project dir, instead it was at ${a}`);let c=K.join(a,Ri,lN),f=t.get(a)||new Map;await le.mkdirPromise(c,{recursive:!0});for(let p of f.keys())n.has(p)||(await le.removePromise(K.join(c,p)),process.platform===\"win32\"&&await le.removePromise(K.join(c,`${p}.cmd`)));for(let[p,h]of n){let E=f.get(p),C=K.join(c,p);E!==h&&(process.platform===\"win32\"?await(0,qxe.default)(ue.fromPortablePath(h),ue.fromPortablePath(C),{createPwshFile:!1}):(await le.removePromise(C),await nV(h,C,s),K.contains(r,await le.realpathPromise(h))!==null&&await le.chmodPromise(h,493)))}}}Ve();bt();rA();var $D=class extends ig{constructor(){super(...arguments);this.mode=\"loose\"}makeInstaller(r){return new sV(r)}},sV=class extends jm{constructor(){super(...arguments);this.mode=\"loose\"}async transformPnpSettings(r){let s=new Ao({baseFs:new tA({maxOpenFiles:80,readOnlyArchives:!0})}),a=xxe(r,this.opts.project.cwd,s),{tree:n,errors:c}=UD(a,{pnpifyFs:!1,project:this.opts.project});if(!n){for(let{messageName:C,text:S}of c)this.opts.report.reportError(C,S);return}let f=new Map;r.fallbackPool=f;let p=(C,S)=>{let P=q.parseLocator(S.locator),I=q.stringifyIdent(P);I===C?f.set(C,P.reference):f.set(C,[I,P.reference])},h=K.join(this.opts.project.cwd,Er.nodeModules),E=n.get(h);if(!(typeof E>\"u\")){if(\"target\"in E)throw new Error(\"Assertion failed: Expected the root junction point to be a directory\");for(let C of E.dirList){let S=K.join(h,C),P=n.get(S);if(typeof P>\"u\")throw new Error(\"Assertion failed: Expected the child to have been registered\");if(\"target\"in P)p(C,P);else for(let I of P.dirList){let R=K.join(S,I),N=n.get(R);if(typeof N>\"u\")throw new Error(\"Assertion failed: Expected the subchild to have been registered\");if(\"target\"in N)p(`${C}/${I}`,N);else throw new Error(\"Assertion failed: Expected the leaf junction to be a package\")}}}}};var UTt={hooks:{cleanGlobalArtifacts:async t=>{let e=iV(t);await le.removePromise(e)}},configuration:{nmHoistingLimits:{description:\"Prevents packages to be hoisted past specific levels\",type:\"STRING\",values:[\"workspaces\",\"dependencies\",\"none\"],default:\"none\"},nmMode:{description:\"Defines in which measure Yarn must use hardlinks and symlinks when generated `node_modules` directories.\",type:\"STRING\",values:[\"classic\",\"hardlinks-local\",\"hardlinks-global\"],default:\"classic\"},nmSelfReferences:{description:\"Defines whether the linker should generate self-referencing symlinks for workspaces.\",type:\"BOOLEAN\",default:!0}},linkers:[XD,$D]},HTt=UTt;var oz={};Vt(oz,{NpmHttpFetcher:()=>rb,NpmRemapResolver:()=>nb,NpmSemverFetcher:()=>lh,NpmSemverResolver:()=>ib,NpmTagResolver:()=>sb,default:()=>ZHt,npmConfigUtils:()=>hi,npmHttpUtils:()=>an,npmPublishUtils:()=>B1});Ve();var nke=et(Ai());var oi=\"npm:\";var an={};Vt(an,{AuthType:()=>eke,customPackageError:()=>qm,del:()=>tRt,get:()=>Gm,getIdentUrl:()=>uN,getPackageMetadata:()=>kw,handleInvalidAuthenticationError:()=>og,post:()=>$Tt,put:()=>eRt});Ve();Ve();bt();var cV=et(nS()),Xxe=et(mG()),$xe=et(Ai());var hi={};Vt(hi,{RegistryType:()=>zxe,getAuditRegistry:()=>jTt,getAuthConfiguration:()=>lV,getDefaultRegistry:()=>eb,getPublishRegistry:()=>qTt,getRegistryConfiguration:()=>Zxe,getScopeConfiguration:()=>aV,getScopeRegistry:()=>Pw,normalizeRegistry:()=>zc});var zxe=(s=>(s.AUDIT_REGISTRY=\"npmAuditRegistry\",s.FETCH_REGISTRY=\"npmRegistryServer\",s.PUBLISH_REGISTRY=\"npmPublishRegistry\",s))(zxe||{});function zc(t){return t.replace(/\\/$/,\"\")}function jTt({configuration:t}){return eb({configuration:t,type:\"npmAuditRegistry\"})}function qTt(t,{configuration:e}){return t.publishConfig?.registry?zc(t.publishConfig.registry):t.name?Pw(t.name.scope,{configuration:e,type:\"npmPublishRegistry\"}):eb({configuration:e,type:\"npmPublishRegistry\"})}function Pw(t,{configuration:e,type:r=\"npmRegistryServer\"}){let s=aV(t,{configuration:e});if(s===null)return eb({configuration:e,type:r});let a=s.get(r);return a===null?eb({configuration:e,type:r}):zc(a)}function eb({configuration:t,type:e=\"npmRegistryServer\"}){let r=t.get(e);return zc(r!==null?r:t.get(\"npmRegistryServer\"))}function Zxe(t,{configuration:e}){let r=e.get(\"npmRegistries\"),s=zc(t),a=r.get(s);if(typeof a<\"u\")return a;let n=r.get(s.replace(/^[a-z]+:/,\"\"));return typeof n<\"u\"?n:null}var GTt=new Map([[\"npmRegistryServer\",\"https://npm.jsr.io/\"]]);function aV(t,{configuration:e}){if(t===null)return null;let s=e.get(\"npmScopes\").get(t);return s||(t===\"jsr\"?GTt:null)}function lV(t,{configuration:e,ident:r}){let s=r&&aV(r.scope,{configuration:e});return s?.get(\"npmAuthIdent\")||s?.get(\"npmAuthToken\")?s:Zxe(t,{configuration:e})||e}var eke=(a=>(a[a.NO_AUTH=0]=\"NO_AUTH\",a[a.BEST_EFFORT=1]=\"BEST_EFFORT\",a[a.CONFIGURATION=2]=\"CONFIGURATION\",a[a.ALWAYS_AUTH=3]=\"ALWAYS_AUTH\",a))(eke||{});async function og(t,{attemptedAs:e,registry:r,headers:s,configuration:a}){if(AN(t))throw new Yt(41,\"Invalid OTP token\");if(t.originalError?.name===\"HTTPError\"&&t.originalError?.response.statusCode===401)throw new Yt(41,`Invalid authentication (${typeof e!=\"string\"?`as ${await nRt(r,s,{configuration:a})}`:`attempted as ${e}`})`)}function qm(t,e){let r=t.response?.statusCode;return r?r===404?\"Package not found\":r>=500&&r<600?`The registry appears to be down (using a ${he.applyHyperlink(e,\"local cache\",\"https://yarnpkg.com/advanced/lexicon#local-cache\")} might have protected you against such outages)`:null:null}function uN(t){return t.scope?`/@${t.scope}%2f${t.name}`:`/${t.name}`}var tke=new Map,WTt=new Map;async function YTt(t){return await je.getFactoryWithDefault(tke,t,async()=>{let e=null;try{e=await le.readJsonPromise(t)}catch{}return e})}async function VTt(t,e,{configuration:r,cached:s,registry:a,headers:n,version:c,...f}){return await je.getFactoryWithDefault(WTt,t,async()=>await Gm(uN(e),{...f,customErrorMessage:qm,configuration:r,registry:a,ident:e,headers:{...n,\"If-None-Match\":s?.etag,\"If-Modified-Since\":s?.lastModified},wrapNetworkRequest:async p=>async()=>{let h=await p();if(h.statusCode===304){if(s===null)throw new Error(\"Assertion failed: cachedMetadata should not be null\");return{...h,body:s.metadata}}let E=JTt(JSON.parse(h.body.toString())),C={metadata:E,etag:h.headers.etag,lastModified:h.headers[\"last-modified\"]};return tke.set(t,Promise.resolve(C)),Promise.resolve().then(async()=>{let S=`${t}-${process.pid}.tmp`;await le.mkdirPromise(K.dirname(S),{recursive:!0}),await le.writeJsonPromise(S,C,{compact:!0}),await le.renamePromise(S,t)}).catch(()=>{}),{...h,body:E}}}))}function KTt(t){return t.scope!==null?`@${t.scope}-${t.name}-${t.scope.length}`:t.name}async function kw(t,{cache:e,project:r,registry:s,headers:a,version:n,...c}){let{configuration:f}=r;s=tb(f,{ident:t,registry:s});let p=ZTt(f,s),h=K.join(p,`${KTt(t)}.json`),E=null;if(!r.lockfileNeedsRefresh&&(E=await YTt(h),E)){if(typeof n<\"u\"&&typeof E.metadata.versions[n]<\"u\")return E.metadata;if(f.get(\"enableOfflineMode\")){let C=structuredClone(E.metadata),S=new Set;if(e){for(let I of Object.keys(C.versions)){let R=q.makeLocator(t,`npm:${I}`),N=e.getLocatorMirrorPath(R);(!N||!le.existsSync(N))&&(delete C.versions[I],S.add(I))}let P=C[\"dist-tags\"].latest;if(S.has(P)){let I=Object.keys(E.metadata.versions).sort($xe.default.compare),R=I.indexOf(P);for(;S.has(I[R])&&R>=0;)R-=1;R>=0?C[\"dist-tags\"].latest=I[R]:delete C[\"dist-tags\"].latest}}return C}}return await VTt(h,t,{...c,configuration:f,cached:E,registry:s,headers:a,version:n})}var rke=[\"name\",\"dist.tarball\",\"bin\",\"scripts\",\"os\",\"cpu\",\"libc\",\"dependencies\",\"dependenciesMeta\",\"optionalDependencies\",\"peerDependencies\",\"peerDependenciesMeta\",\"deprecated\"];function JTt(t){return{\"dist-tags\":t[\"dist-tags\"],versions:Object.fromEntries(Object.entries(t.versions).map(([e,r])=>[e,(0,Xxe.default)(r,rke)]))}}var zTt=Nn.makeHash(...rke).slice(0,6);function ZTt(t,e){let r=XTt(t),s=new URL(e);return K.join(r,zTt,s.hostname)}function XTt(t){return K.join(t.get(\"globalFolder\"),\"metadata/npm\")}async function Gm(t,{configuration:e,headers:r,ident:s,authType:a,registry:n,...c}){n=tb(e,{ident:s,registry:n}),s&&s.scope&&typeof a>\"u\"&&(a=1);let f=await fN(n,{authType:a,configuration:e,ident:s});f&&(r={...r,authorization:f});try{return await An.get(t.charAt(0)===\"/\"?`${n}${t}`:t,{configuration:e,headers:r,...c})}catch(p){throw await og(p,{registry:n,configuration:e,headers:r}),p}}async function $Tt(t,e,{attemptedAs:r,configuration:s,headers:a,ident:n,authType:c=3,registry:f,otp:p,...h}){f=tb(s,{ident:n,registry:f});let E=await fN(f,{authType:c,configuration:s,ident:n});E&&(a={...a,authorization:E}),p&&(a={...a,...xw(p)});try{return await An.post(f+t,e,{configuration:s,headers:a,...h})}catch(C){if(!AN(C)||p)throw await og(C,{attemptedAs:r,registry:f,configuration:s,headers:a}),C;p=await uV(C,{configuration:s});let S={...a,...xw(p)};try{return await An.post(`${f}${t}`,e,{configuration:s,headers:S,...h})}catch(P){throw await og(P,{attemptedAs:r,registry:f,configuration:s,headers:a}),P}}}async function eRt(t,e,{attemptedAs:r,configuration:s,headers:a,ident:n,authType:c=3,registry:f,otp:p,...h}){f=tb(s,{ident:n,registry:f});let E=await fN(f,{authType:c,configuration:s,ident:n});E&&(a={...a,authorization:E}),p&&(a={...a,...xw(p)});try{return await An.put(f+t,e,{configuration:s,headers:a,...h})}catch(C){if(!AN(C))throw await og(C,{attemptedAs:r,registry:f,configuration:s,headers:a}),C;p=await uV(C,{configuration:s});let S={...a,...xw(p)};try{return await An.put(`${f}${t}`,e,{configuration:s,headers:S,...h})}catch(P){throw await og(P,{attemptedAs:r,registry:f,configuration:s,headers:a}),P}}}async function tRt(t,{attemptedAs:e,configuration:r,headers:s,ident:a,authType:n=3,registry:c,otp:f,...p}){c=tb(r,{ident:a,registry:c});let h=await fN(c,{authType:n,configuration:r,ident:a});h&&(s={...s,authorization:h}),f&&(s={...s,...xw(f)});try{return await An.del(c+t,{configuration:r,headers:s,...p})}catch(E){if(!AN(E)||f)throw await og(E,{attemptedAs:e,registry:c,configuration:r,headers:s}),E;f=await uV(E,{configuration:r});let C={...s,...xw(f)};try{return await An.del(`${c}${t}`,{configuration:r,headers:C,...p})}catch(S){throw await og(S,{attemptedAs:e,registry:c,configuration:r,headers:s}),S}}}function tb(t,{ident:e,registry:r}){if(typeof r>\"u\"&&e)return Pw(e.scope,{configuration:t});if(typeof r!=\"string\")throw new Error(\"Assertion failed: The registry should be a string\");return zc(r)}async function fN(t,{authType:e=2,configuration:r,ident:s}){let a=lV(t,{configuration:r,ident:s}),n=rRt(a,e);if(!n)return null;let c=await r.reduceHook(f=>f.getNpmAuthenticationHeader,void 0,t,{configuration:r,ident:s});if(c)return c;if(a.get(\"npmAuthToken\"))return`Bearer ${a.get(\"npmAuthToken\")}`;if(a.get(\"npmAuthIdent\")){let f=a.get(\"npmAuthIdent\");return f.includes(\":\")?`Basic ${Buffer.from(f).toString(\"base64\")}`:`Basic ${f}`}if(n&&e!==1)throw new Yt(33,\"No authentication configured for request\");return null}function rRt(t,e){switch(e){case 2:return t.get(\"npmAlwaysAuth\");case 1:case 3:return!0;case 0:return!1;default:throw new Error(\"Unreachable\")}}async function nRt(t,e,{configuration:r}){if(typeof e>\"u\"||typeof e.authorization>\"u\")return\"an anonymous user\";try{return(await An.get(new URL(`${t}/-/whoami`).href,{configuration:r,headers:e,jsonResponse:!0})).username??\"an unknown user\"}catch{return\"an unknown user\"}}async function uV(t,{configuration:e}){let r=t.originalError?.response.headers[\"npm-notice\"];if(r&&(await Ot.start({configuration:e,stdout:process.stdout,includeFooter:!1},async a=>{if(a.reportInfo(0,r.replace(/(https?:\\/\\/\\S+)/g,he.pretty(e,\"$1\",he.Type.URL))),!process.env.YARN_IS_TEST_ENV){let n=r.match(/open (https?:\\/\\/\\S+)/i);if(n&&ps.openUrl){let{openNow:c}=await(0,cV.prompt)({type:\"confirm\",name:\"openNow\",message:\"Do you want to try to open this url now?\",required:!0,initial:!0,onCancel:()=>process.exit(130)});c&&(await ps.openUrl(n[1])||(a.reportSeparator(),a.reportWarning(0,\"We failed to automatically open the url; you'll have to open it yourself in your browser of choice.\")))}}}),process.stdout.write(`\n`)),process.env.YARN_IS_TEST_ENV)return process.env.YARN_INJECT_NPM_2FA_TOKEN||\"\";let{otp:s}=await(0,cV.prompt)({type:\"password\",name:\"otp\",message:\"One-time password:\",required:!0,onCancel:()=>process.exit(130)});return process.stdout.write(`\n`),s}function AN(t){if(t.originalError?.name!==\"HTTPError\")return!1;try{return(t.originalError?.response.headers[\"www-authenticate\"].split(/,\\s*/).map(r=>r.toLowerCase())).includes(\"otp\")}catch{return!1}}function xw(t){return{\"npm-otp\":t}}var rb=class{supports(e,r){if(!e.reference.startsWith(oi))return!1;let{selector:s,params:a}=q.parseRange(e.reference);return!(!nke.default.valid(s)||a===null||typeof a.__archiveUrl!=\"string\")}getLocalPath(e,r){return null}async fetch(e,r){let s=r.checksums.get(e.locatorHash)||null,[a,n,c]=await r.cache.fetchPackageFromCache(e,s,{onHit:()=>r.report.reportCacheHit(e),onMiss:()=>r.report.reportCacheMiss(e,`${q.prettyLocator(r.project.configuration,e)} can't be found in the cache and will be fetched from the remote server`),loader:()=>this.fetchFromNetwork(e,r),...r.cacheOptions});return{packageFs:a,releaseFs:n,prefixPath:q.getIdentVendorPath(e),checksum:c}}async fetchFromNetwork(e,r){let{params:s}=q.parseRange(e.reference);if(s===null||typeof s.__archiveUrl!=\"string\")throw new Error(\"Assertion failed: The archiveUrl querystring parameter should have been available\");let a=await Gm(s.__archiveUrl,{customErrorMessage:qm,configuration:r.project.configuration,ident:e});return await gs.convertToZip(a,{configuration:r.project.configuration,prefixPath:q.getIdentVendorPath(e),stripComponents:1})}};Ve();var nb=class{supportsDescriptor(e,r){return!(!e.range.startsWith(oi)||!q.tryParseDescriptor(e.range.slice(oi.length),!0))}supportsLocator(e,r){return!1}shouldPersistResolution(e,r){throw new Error(\"Unreachable\")}bindDescriptor(e,r,s){return e}getResolutionDependencies(e,r){let s=r.project.configuration.normalizeDependency(q.parseDescriptor(e.range.slice(oi.length),!0));return r.resolver.getResolutionDependencies(s,r)}async getCandidates(e,r,s){let a=s.project.configuration.normalizeDependency(q.parseDescriptor(e.range.slice(oi.length),!0));return await s.resolver.getCandidates(a,r,s)}async getSatisfying(e,r,s,a){let n=a.project.configuration.normalizeDependency(q.parseDescriptor(e.range.slice(oi.length),!0));return a.resolver.getSatisfying(n,r,s,a)}resolve(e,r){throw new Error(\"Unreachable\")}};Ve();Ve();var ike=et(Ai());var lh=class t{supports(e,r){if(!e.reference.startsWith(oi))return!1;let s=new URL(e.reference);return!(!ike.default.valid(s.pathname)||s.searchParams.has(\"__archiveUrl\"))}getLocalPath(e,r){return null}async fetch(e,r){let s=r.checksums.get(e.locatorHash)||null,[a,n,c]=await r.cache.fetchPackageFromCache(e,s,{onHit:()=>r.report.reportCacheHit(e),onMiss:()=>r.report.reportCacheMiss(e,`${q.prettyLocator(r.project.configuration,e)} can't be found in the cache and will be fetched from the remote registry`),loader:()=>this.fetchFromNetwork(e,r),...r.cacheOptions});return{packageFs:a,releaseFs:n,prefixPath:q.getIdentVendorPath(e),checksum:c}}async fetchFromNetwork(e,r){let s;try{s=await Gm(t.getLocatorUrl(e),{customErrorMessage:qm,configuration:r.project.configuration,ident:e})}catch{s=await Gm(t.getLocatorUrl(e).replace(/%2f/g,\"/\"),{customErrorMessage:qm,configuration:r.project.configuration,ident:e})}return await gs.convertToZip(s,{configuration:r.project.configuration,prefixPath:q.getIdentVendorPath(e),stripComponents:1})}static isConventionalTarballUrl(e,r,{configuration:s}){let a=Pw(e.scope,{configuration:s}),n=t.getLocatorUrl(e);return r=r.replace(/^https?:(\\/\\/(?:[^/]+\\.)?npmjs.org(?:$|\\/))/,\"https:$1\"),a=a.replace(/^https:\\/\\/registry\\.npmjs\\.org($|\\/)/,\"https://registry.yarnpkg.com$1\"),r=r.replace(/^https:\\/\\/registry\\.npmjs\\.org($|\\/)/,\"https://registry.yarnpkg.com$1\"),r===a+n||r===a+n.replace(/%2f/g,\"/\")}static getLocatorUrl(e){let r=Or.clean(e.reference.slice(oi.length));if(r===null)throw new Yt(10,\"The npm semver resolver got selected, but the version isn't semver\");return`${uN(e)}/-/${e.name}-${r}.tgz`}};Ve();Ve();Ve();var fV=et(Ai());var pN=q.makeIdent(null,\"node-gyp\"),iRt=/\\b(node-gyp|prebuild-install)\\b/,ib=class{supportsDescriptor(e,r){return e.range.startsWith(oi)?!!Or.validRange(e.range.slice(oi.length)):!1}supportsLocator(e,r){if(!e.reference.startsWith(oi))return!1;let{selector:s}=q.parseRange(e.reference);return!!fV.default.valid(s)}shouldPersistResolution(e,r){return!0}bindDescriptor(e,r,s){return e}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){let a=Or.validRange(e.range.slice(oi.length));if(a===null)throw new Error(`Expected a valid range, got ${e.range.slice(oi.length)}`);let n=await kw(e,{cache:s.fetchOptions?.cache,project:s.project,version:fV.default.valid(a.raw)?a.raw:void 0}),c=je.mapAndFilter(Object.keys(n.versions),h=>{try{let E=new Or.SemVer(h);if(a.test(E))return E}catch{}return je.mapAndFilter.skip}),f=c.filter(h=>!n.versions[h.raw].deprecated),p=f.length>0?f:c;return p.sort((h,E)=>-h.compare(E)),p.map(h=>{let E=q.makeLocator(e,`${oi}${h.raw}`),C=n.versions[h.raw].dist.tarball;return lh.isConventionalTarballUrl(E,C,{configuration:s.project.configuration})?E:q.bindLocator(E,{__archiveUrl:C})})}async getSatisfying(e,r,s,a){let n=Or.validRange(e.range.slice(oi.length));if(n===null)throw new Error(`Expected a valid range, got ${e.range.slice(oi.length)}`);return{locators:je.mapAndFilter(s,p=>{if(p.identHash!==e.identHash)return je.mapAndFilter.skip;let h=q.tryParseRange(p.reference,{requireProtocol:oi});if(!h)return je.mapAndFilter.skip;let E=new Or.SemVer(h.selector);return n.test(E)?{locator:p,version:E}:je.mapAndFilter.skip}).sort((p,h)=>-p.version.compare(h.version)).map(({locator:p})=>p),sorted:!0}}async resolve(e,r){let{selector:s}=q.parseRange(e.reference),a=Or.clean(s);if(a===null)throw new Yt(10,\"The npm semver resolver got selected, but the version isn't semver\");let n=await kw(e,{cache:r.fetchOptions?.cache,project:r.project,version:a});if(!Object.hasOwn(n,\"versions\"))throw new Yt(15,'Registry returned invalid data for - missing \"versions\" field');if(!Object.hasOwn(n.versions,a))throw new Yt(16,`Registry failed to return reference \"${a}\"`);let c=new Ht;if(c.load(n.versions[a]),!c.dependencies.has(pN.identHash)&&!c.peerDependencies.has(pN.identHash)){for(let f of c.scripts.values())if(f.match(iRt)){c.dependencies.set(pN.identHash,q.makeDescriptor(pN,\"latest\"));break}}return{...e,version:a,languageName:\"node\",linkType:\"HARD\",conditions:c.getConditions(),dependencies:r.project.configuration.normalizeDependencyMap(c.dependencies),peerDependencies:c.peerDependencies,dependenciesMeta:c.dependenciesMeta,peerDependenciesMeta:c.peerDependenciesMeta,bin:c.bin}}};Ve();Ve();var ske=et(Ai());var sb=class{supportsDescriptor(e,r){return!(!e.range.startsWith(oi)||!Up.test(e.range.slice(oi.length)))}supportsLocator(e,r){return!1}shouldPersistResolution(e,r){throw new Error(\"Unreachable\")}bindDescriptor(e,r,s){return e}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){let a=e.range.slice(oi.length),n=await kw(e,{cache:s.fetchOptions?.cache,project:s.project});if(!Object.hasOwn(n,\"dist-tags\"))throw new Yt(15,'Registry returned invalid data - missing \"dist-tags\" field');let c=n[\"dist-tags\"];if(!Object.hasOwn(c,a))throw new Yt(16,`Registry failed to return tag \"${a}\"`);let f=c[a],p=q.makeLocator(e,`${oi}${f}`),h=n.versions[f].dist.tarball;return lh.isConventionalTarballUrl(p,h,{configuration:s.project.configuration})?[p]:[q.bindLocator(p,{__archiveUrl:h})]}async getSatisfying(e,r,s,a){let n=[];for(let c of s){if(c.identHash!==e.identHash)continue;let f=q.tryParseRange(c.reference,{requireProtocol:oi});if(!(!f||!ske.default.valid(f.selector))){if(f.params?.__archiveUrl){let p=q.makeRange({protocol:oi,selector:f.selector,source:null,params:null}),[h]=await a.resolver.getCandidates(q.makeDescriptor(e,p),r,a);if(c.reference!==h.reference)continue}n.push(c)}}return{locators:n,sorted:!1}}async resolve(e,r){throw new Error(\"Unreachable\")}};var B1={};Vt(B1,{getGitHead:()=>JHt,getPublishAccess:()=>JNe,getReadmeContent:()=>zNe,makePublishBody:()=>KHt});Ve();Ve();bt();var $V={};Vt($V,{PackCommand:()=>Hw,default:()=>LNt,packUtils:()=>IA});Ve();Ve();Ve();bt();Wt();var IA={};Vt(IA,{genPackList:()=>ON,genPackStream:()=>XV,genPackageManifest:()=>OQe,hasPackScripts:()=>zV,prepareForPack:()=>ZV});Ve();bt();var JV=et(Sa()),FQe=et(kQe()),NQe=Ie(\"zlib\"),DNt=[\"/package.json\",\"/readme\",\"/readme.*\",\"/license\",\"/license.*\",\"/licence\",\"/licence.*\",\"/changelog\",\"/changelog.*\"],bNt=[\"/package.tgz\",\".github\",\".git\",\".hg\",\"node_modules\",\".npmignore\",\".gitignore\",\".#*\",\".DS_Store\"];async function zV(t){return!!(In.hasWorkspaceScript(t,\"prepack\")||In.hasWorkspaceScript(t,\"postpack\"))}async function ZV(t,{report:e},r){await In.maybeExecuteWorkspaceLifecycleScript(t,\"prepack\",{report:e});try{let s=K.join(t.cwd,Ht.fileName);await le.existsPromise(s)&&await t.manifest.loadFile(s,{baseFs:le}),await r()}finally{await In.maybeExecuteWorkspaceLifecycleScript(t,\"postpack\",{report:e})}}async function XV(t,e){typeof e>\"u\"&&(e=await ON(t));let r=new Set;for(let n of t.manifest.publishConfig?.executableFiles??new Set)r.add(K.normalize(n));for(let n of t.manifest.bin.values())r.add(K.normalize(n));let s=FQe.default.pack();process.nextTick(async()=>{for(let n of e){let c=K.normalize(n),f=K.resolve(t.cwd,c),p=K.join(\"package\",c),h=await le.lstatPromise(f),E={name:p,mtime:new Date(fi.SAFE_TIME*1e3)},C=r.has(c)?493:420,S,P,I=new Promise((N,U)=>{S=N,P=U}),R=N=>{N?P(N):S()};if(h.isFile()){let N;c===\"package.json\"?N=Buffer.from(JSON.stringify(await OQe(t),null,2)):N=await le.readFilePromise(f),s.entry({...E,mode:C,type:\"file\"},N,R)}else h.isSymbolicLink()?s.entry({...E,mode:C,type:\"symlink\",linkname:await le.readlinkPromise(f)},R):R(new Error(`Unsupported file type ${h.mode} for ${ue.fromPortablePath(c)}`));await I}s.finalize()});let a=(0,NQe.createGzip)();return s.pipe(a),a}async function OQe(t){let e=JSON.parse(JSON.stringify(t.manifest.raw));return await t.project.configuration.triggerHook(r=>r.beforeWorkspacePacking,t,e),e}async function ON(t){let e=t.project,r=e.configuration,s={accept:[],reject:[]};for(let C of bNt)s.reject.push(C);for(let C of DNt)s.accept.push(C);s.reject.push(r.get(\"rcFilename\"));let a=C=>{if(C===null||!C.startsWith(`${t.cwd}/`))return;let S=K.relative(t.cwd,C),P=K.resolve(vt.root,S);s.reject.push(P)};a(K.resolve(e.cwd,Er.lockfile)),a(r.get(\"cacheFolder\")),a(r.get(\"globalFolder\")),a(r.get(\"installStatePath\")),a(r.get(\"virtualFolder\")),a(r.get(\"yarnPath\")),await r.triggerHook(C=>C.populateYarnPaths,e,C=>{a(C)});for(let C of e.workspaces){let S=K.relative(t.cwd,C.cwd);S!==\"\"&&!S.match(/^(\\.\\.)?\\//)&&s.reject.push(`/${S}`)}let n={accept:[],reject:[]},c=t.manifest.publishConfig?.main??t.manifest.main,f=t.manifest.publishConfig?.module??t.manifest.module,p=t.manifest.publishConfig?.browser??t.manifest.browser,h=t.manifest.publishConfig?.bin??t.manifest.bin;c!=null&&n.accept.push(K.resolve(vt.root,c)),f!=null&&n.accept.push(K.resolve(vt.root,f)),typeof p==\"string\"&&n.accept.push(K.resolve(vt.root,p));for(let C of h.values())n.accept.push(K.resolve(vt.root,C));if(p instanceof Map)for(let[C,S]of p.entries())n.accept.push(K.resolve(vt.root,C)),typeof S==\"string\"&&n.accept.push(K.resolve(vt.root,S));let E=t.manifest.files!==null;if(E){n.reject.push(\"/*\");for(let C of t.manifest.files)LQe(n.accept,C,{cwd:vt.root})}return await PNt(t.cwd,{hasExplicitFileList:E,globalList:s,ignoreList:n})}async function PNt(t,{hasExplicitFileList:e,globalList:r,ignoreList:s}){let a=[],n=new jf(t),c=[[vt.root,[s]]];for(;c.length>0;){let[f,p]=c.pop(),h=await n.lstatPromise(f);if(!TQe(f,{globalList:r,ignoreLists:h.isDirectory()?null:p}))if(h.isDirectory()){let E=await n.readdirPromise(f),C=!1,S=!1;if(!e||f!==vt.root)for(let R of E)C=C||R===\".gitignore\",S=S||R===\".npmignore\";let P=S?await QQe(n,f,\".npmignore\"):C?await QQe(n,f,\".gitignore\"):null,I=P!==null?[P].concat(p):p;TQe(f,{globalList:r,ignoreLists:p})&&(I=[...p,{accept:[],reject:[\"**/*\"]}]);for(let R of E)c.push([K.resolve(f,R),I])}else(h.isFile()||h.isSymbolicLink())&&a.push(K.relative(vt.root,f))}return a.sort()}async function QQe(t,e,r){let s={accept:[],reject:[]},a=await t.readFilePromise(K.join(e,r),\"utf8\");for(let n of a.split(/\\n/g))LQe(s.reject,n,{cwd:e});return s}function xNt(t,{cwd:e}){let r=t[0]===\"!\";return r&&(t=t.slice(1)),t.match(/\\.{0,1}\\//)&&(t=K.resolve(e,t)),r&&(t=`!${t}`),t}function LQe(t,e,{cwd:r}){let s=e.trim();s===\"\"||s[0]===\"#\"||t.push(xNt(s,{cwd:r}))}function TQe(t,{globalList:e,ignoreLists:r}){let s=NN(t,e.accept);if(s!==0)return s===2;let a=NN(t,e.reject);if(a!==0)return a===1;if(r!==null)for(let n of r){let c=NN(t,n.accept);if(c!==0)return c===2;let f=NN(t,n.reject);if(f!==0)return f===1}return!1}function NN(t,e){let r=e,s=[];for(let a=0;a<e.length;++a)e[a][0]!==\"!\"?r!==e&&r.push(e[a]):(r===e&&(r=e.slice(0,a)),s.push(e[a].slice(1)));return RQe(t,s)?2:RQe(t,r)?1:0}function RQe(t,e){let r=e,s=[];for(let a=0;a<e.length;++a)e[a].includes(\"/\")?r!==e&&r.push(e[a]):(r===e&&(r=e.slice(0,a)),s.push(e[a]));return!!(JV.default.isMatch(t,r,{dot:!0,nocase:!0})||JV.default.isMatch(t,s,{dot:!0,basename:!0,nocase:!0}))}var Hw=class extends ut{constructor(){super(...arguments);this.installIfNeeded=ge.Boolean(\"--install-if-needed\",!1,{description:\"Run a preliminary `yarn install` if the package contains build scripts\"});this.dryRun=ge.Boolean(\"-n,--dry-run\",!1,{description:\"Print the file paths without actually generating the package archive\"});this.json=ge.Boolean(\"--json\",!1,{description:\"Format the output as an NDJSON stream\"});this.out=ge.String(\"-o,--out\",{description:\"Create the archive at the specified path\"});this.filename=ge.String(\"--filename\",{hidden:!0})}static{this.paths=[[\"pack\"]]}static{this.usage=ot.Usage({description:\"generate a tarball from the active workspace\",details:\"\\n      This command will turn the active workspace into a compressed archive suitable for publishing. The archive will by default be stored at the root of the workspace (`package.tgz`).\\n\\n      If the `-o,--out` is set the archive will be created at the specified path. The `%s` and `%v` variables can be used within the path and will be respectively replaced by the package name and version.\\n    \",examples:[[\"Create an archive from the active workspace\",\"yarn pack\"],[\"List the files that would be made part of the workspace's archive\",\"yarn pack --dry-run\"],[\"Name and output the archive in a dedicated folder\",\"yarn pack --out /artifacts/%s-%v.tgz\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);await zV(a)&&(this.installIfNeeded?await s.install({cache:await Jr.find(r),report:new Yi}):await s.restoreInstallState());let n=this.out??this.filename,c=typeof n<\"u\"?K.resolve(this.context.cwd,kNt(n,{workspace:a})):K.resolve(a.cwd,\"package.tgz\");return(await Ot.start({configuration:r,stdout:this.context.stdout,json:this.json},async p=>{await ZV(a,{report:p},async()=>{p.reportJson({base:ue.fromPortablePath(a.cwd)});let h=await ON(a);for(let E of h)p.reportInfo(null,ue.fromPortablePath(E)),p.reportJson({location:ue.fromPortablePath(E)});if(!this.dryRun){let E=await XV(a,h);await le.mkdirPromise(K.dirname(c),{recursive:!0});let C=le.createWriteStream(c);E.pipe(C),await new Promise(S=>{C.on(\"finish\",S)})}}),this.dryRun||(p.reportInfo(0,`Package archive generated in ${he.pretty(r,c,he.Type.PATH)}`),p.reportJson({output:ue.fromPortablePath(c)}))})).exitCode()}};function kNt(t,{workspace:e}){let r=t.replace(\"%s\",QNt(e)).replace(\"%v\",TNt(e));return ue.toPortablePath(r)}function QNt(t){return t.manifest.name!==null?q.slugifyIdent(t.manifest.name):\"package\"}function TNt(t){return t.manifest.version!==null?t.manifest.version:\"unknown\"}var RNt=[\"dependencies\",\"devDependencies\",\"peerDependencies\"],FNt=\"workspace:\",NNt=(t,e)=>{e.publishConfig&&(e.publishConfig.type&&(e.type=e.publishConfig.type),e.publishConfig.main&&(e.main=e.publishConfig.main),e.publishConfig.browser&&(e.browser=e.publishConfig.browser),e.publishConfig.module&&(e.module=e.publishConfig.module),e.publishConfig.exports&&(e.exports=e.publishConfig.exports),e.publishConfig.imports&&(e.imports=e.publishConfig.imports),e.publishConfig.bin&&(e.bin=e.publishConfig.bin));let r=t.project;for(let s of RNt)for(let a of t.manifest.getForScope(s).values()){let n=r.tryWorkspaceByDescriptor(a),c=q.parseRange(a.range);if(c.protocol===FNt)if(n===null){if(r.tryWorkspaceByIdent(a)===null)throw new Yt(21,`${q.prettyDescriptor(r.configuration,a)}: No local workspace found for this range`)}else{let f;q.areDescriptorsEqual(a,n.anchoredDescriptor)||c.selector===\"*\"?f=n.manifest.version??\"0.0.0\":c.selector===\"~\"||c.selector===\"^\"?f=`${c.selector}${n.manifest.version??\"0.0.0\"}`:f=c.selector;let p=s===\"dependencies\"?q.makeDescriptor(a,\"unknown\"):null,h=p!==null&&t.manifest.ensureDependencyMeta(p).optional?\"optionalDependencies\":s;e[h][q.stringifyIdent(a)]=f}}},ONt={hooks:{beforeWorkspacePacking:NNt},commands:[Hw]},LNt=ONt;var KNe=et(YQe());Ve();var YNe=et(WNe()),{env:Bt}=process,_Ht=\"application/vnd.in-toto+json\",UHt=\"https://in-toto.io/Statement/v0.1\",HHt=\"https://in-toto.io/Statement/v1\",jHt=\"https://slsa.dev/provenance/v0.2\",qHt=\"https://slsa.dev/provenance/v1\",GHt=\"https://github.com/actions/runner\",WHt=\"https://slsa-framework.github.io/github-actions-buildtypes/workflow/v1\",YHt=\"https://github.com/npm/cli/gitlab\",VHt=\"v0alpha1\",VNe=async(t,e)=>{let r;if(Bt.GITHUB_ACTIONS){if(!Bt.ACTIONS_ID_TOKEN_REQUEST_URL)throw new Yt(91,'Provenance generation in GitHub Actions requires \"write\" access to the \"id-token\" permission');let s=(Bt.GITHUB_WORKFLOW_REF||\"\").replace(`${Bt.GITHUB_REPOSITORY}/`,\"\"),a=s.indexOf(\"@\"),n=s.slice(0,a),c=s.slice(a+1);r={_type:HHt,subject:t,predicateType:qHt,predicate:{buildDefinition:{buildType:WHt,externalParameters:{workflow:{ref:c,repository:`${Bt.GITHUB_SERVER_URL}/${Bt.GITHUB_REPOSITORY}`,path:n}},internalParameters:{github:{event_name:Bt.GITHUB_EVENT_NAME,repository_id:Bt.GITHUB_REPOSITORY_ID,repository_owner_id:Bt.GITHUB_REPOSITORY_OWNER_ID}},resolvedDependencies:[{uri:`git+${Bt.GITHUB_SERVER_URL}/${Bt.GITHUB_REPOSITORY}@${Bt.GITHUB_REF}`,digest:{gitCommit:Bt.GITHUB_SHA}}]},runDetails:{builder:{id:`${GHt}/${Bt.RUNNER_ENVIRONMENT}`},metadata:{invocationId:`${Bt.GITHUB_SERVER_URL}/${Bt.GITHUB_REPOSITORY}/actions/runs/${Bt.GITHUB_RUN_ID}/attempts/${Bt.GITHUB_RUN_ATTEMPT}`}}}}}else if(Bt.GITLAB_CI){if(!Bt.SIGSTORE_ID_TOKEN)throw new Yt(91,`Provenance generation in GitLab CI requires \"SIGSTORE_ID_TOKEN\" with \"sigstore\" audience to be present in \"id_tokens\". For more info see:\nhttps://docs.gitlab.com/ee/ci/secrets/id_token_authentication.html`);r={_type:UHt,subject:t,predicateType:jHt,predicate:{buildType:`${YHt}/${VHt}`,builder:{id:`${Bt.CI_PROJECT_URL}/-/runners/${Bt.CI_RUNNER_ID}`},invocation:{configSource:{uri:`git+${Bt.CI_PROJECT_URL}`,digest:{sha1:Bt.CI_COMMIT_SHA},entryPoint:Bt.CI_JOB_NAME},parameters:{CI:Bt.CI,CI_API_GRAPHQL_URL:Bt.CI_API_GRAPHQL_URL,CI_API_V4_URL:Bt.CI_API_V4_URL,CI_BUILD_BEFORE_SHA:Bt.CI_BUILD_BEFORE_SHA,CI_BUILD_ID:Bt.CI_BUILD_ID,CI_BUILD_NAME:Bt.CI_BUILD_NAME,CI_BUILD_REF:Bt.CI_BUILD_REF,CI_BUILD_REF_NAME:Bt.CI_BUILD_REF_NAME,CI_BUILD_REF_SLUG:Bt.CI_BUILD_REF_SLUG,CI_BUILD_STAGE:Bt.CI_BUILD_STAGE,CI_COMMIT_BEFORE_SHA:Bt.CI_COMMIT_BEFORE_SHA,CI_COMMIT_BRANCH:Bt.CI_COMMIT_BRANCH,CI_COMMIT_REF_NAME:Bt.CI_COMMIT_REF_NAME,CI_COMMIT_REF_PROTECTED:Bt.CI_COMMIT_REF_PROTECTED,CI_COMMIT_REF_SLUG:Bt.CI_COMMIT_REF_SLUG,CI_COMMIT_SHA:Bt.CI_COMMIT_SHA,CI_COMMIT_SHORT_SHA:Bt.CI_COMMIT_SHORT_SHA,CI_COMMIT_TIMESTAMP:Bt.CI_COMMIT_TIMESTAMP,CI_COMMIT_TITLE:Bt.CI_COMMIT_TITLE,CI_CONFIG_PATH:Bt.CI_CONFIG_PATH,CI_DEFAULT_BRANCH:Bt.CI_DEFAULT_BRANCH,CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX:Bt.CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX,CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX:Bt.CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX,CI_DEPENDENCY_PROXY_SERVER:Bt.CI_DEPENDENCY_PROXY_SERVER,CI_DEPENDENCY_PROXY_USER:Bt.CI_DEPENDENCY_PROXY_USER,CI_JOB_ID:Bt.CI_JOB_ID,CI_JOB_NAME:Bt.CI_JOB_NAME,CI_JOB_NAME_SLUG:Bt.CI_JOB_NAME_SLUG,CI_JOB_STAGE:Bt.CI_JOB_STAGE,CI_JOB_STARTED_AT:Bt.CI_JOB_STARTED_AT,CI_JOB_URL:Bt.CI_JOB_URL,CI_NODE_TOTAL:Bt.CI_NODE_TOTAL,CI_PAGES_DOMAIN:Bt.CI_PAGES_DOMAIN,CI_PAGES_URL:Bt.CI_PAGES_URL,CI_PIPELINE_CREATED_AT:Bt.CI_PIPELINE_CREATED_AT,CI_PIPELINE_ID:Bt.CI_PIPELINE_ID,CI_PIPELINE_IID:Bt.CI_PIPELINE_IID,CI_PIPELINE_SOURCE:Bt.CI_PIPELINE_SOURCE,CI_PIPELINE_URL:Bt.CI_PIPELINE_URL,CI_PROJECT_CLASSIFICATION_LABEL:Bt.CI_PROJECT_CLASSIFICATION_LABEL,CI_PROJECT_DESCRIPTION:Bt.CI_PROJECT_DESCRIPTION,CI_PROJECT_ID:Bt.CI_PROJECT_ID,CI_PROJECT_NAME:Bt.CI_PROJECT_NAME,CI_PROJECT_NAMESPACE:Bt.CI_PROJECT_NAMESPACE,CI_PROJECT_NAMESPACE_ID:Bt.CI_PROJECT_NAMESPACE_ID,CI_PROJECT_PATH:Bt.CI_PROJECT_PATH,CI_PROJECT_PATH_SLUG:Bt.CI_PROJECT_PATH_SLUG,CI_PROJECT_REPOSITORY_LANGUAGES:Bt.CI_PROJECT_REPOSITORY_LANGUAGES,CI_PROJECT_ROOT_NAMESPACE:Bt.CI_PROJECT_ROOT_NAMESPACE,CI_PROJECT_TITLE:Bt.CI_PROJECT_TITLE,CI_PROJECT_URL:Bt.CI_PROJECT_URL,CI_PROJECT_VISIBILITY:Bt.CI_PROJECT_VISIBILITY,CI_REGISTRY:Bt.CI_REGISTRY,CI_REGISTRY_IMAGE:Bt.CI_REGISTRY_IMAGE,CI_REGISTRY_USER:Bt.CI_REGISTRY_USER,CI_RUNNER_DESCRIPTION:Bt.CI_RUNNER_DESCRIPTION,CI_RUNNER_ID:Bt.CI_RUNNER_ID,CI_RUNNER_TAGS:Bt.CI_RUNNER_TAGS,CI_SERVER_HOST:Bt.CI_SERVER_HOST,CI_SERVER_NAME:Bt.CI_SERVER_NAME,CI_SERVER_PORT:Bt.CI_SERVER_PORT,CI_SERVER_PROTOCOL:Bt.CI_SERVER_PROTOCOL,CI_SERVER_REVISION:Bt.CI_SERVER_REVISION,CI_SERVER_SHELL_SSH_HOST:Bt.CI_SERVER_SHELL_SSH_HOST,CI_SERVER_SHELL_SSH_PORT:Bt.CI_SERVER_SHELL_SSH_PORT,CI_SERVER_URL:Bt.CI_SERVER_URL,CI_SERVER_VERSION:Bt.CI_SERVER_VERSION,CI_SERVER_VERSION_MAJOR:Bt.CI_SERVER_VERSION_MAJOR,CI_SERVER_VERSION_MINOR:Bt.CI_SERVER_VERSION_MINOR,CI_SERVER_VERSION_PATCH:Bt.CI_SERVER_VERSION_PATCH,CI_TEMPLATE_REGISTRY_HOST:Bt.CI_TEMPLATE_REGISTRY_HOST,GITLAB_CI:Bt.GITLAB_CI,GITLAB_FEATURES:Bt.GITLAB_FEATURES,GITLAB_USER_ID:Bt.GITLAB_USER_ID,GITLAB_USER_LOGIN:Bt.GITLAB_USER_LOGIN,RUNNER_GENERATE_ARTIFACTS_METADATA:Bt.RUNNER_GENERATE_ARTIFACTS_METADATA},environment:{name:Bt.CI_RUNNER_DESCRIPTION,architecture:Bt.CI_RUNNER_EXECUTABLE_ARCH,server:Bt.CI_SERVER_URL,project:Bt.CI_PROJECT_PATH,job:{id:Bt.CI_JOB_ID},pipeline:{id:Bt.CI_PIPELINE_ID,ref:Bt.CI_CONFIG_PATH}}},metadata:{buildInvocationId:`${Bt.CI_JOB_URL}`,completeness:{parameters:!0,environment:!0,materials:!1},reproducible:!1},materials:[{uri:`git+${Bt.CI_PROJECT_URL}`,digest:{sha1:Bt.CI_COMMIT_SHA}}]}}}else throw new Yt(91,\"Provenance generation is only supported in GitHub Actions and GitLab CI\");return YNe.attest(Buffer.from(JSON.stringify(r)),_Ht,e)};async function KHt(t,e,{access:r,tag:s,registry:a,gitHead:n,provenance:c}){let f=t.manifest.name,p=t.manifest.version,h=q.stringifyIdent(f),E=KNe.default.fromData(e,{algorithms:[\"sha1\",\"sha512\"]}),C=r??JNe(t,f),S=await zNe(t),P=await IA.genPackageManifest(t),I=`${h}-${p}.tgz`,R=new URL(`${zc(a)}/${h}/-/${I}`),N={[I]:{content_type:\"application/octet-stream\",data:e.toString(\"base64\"),length:e.length}};if(c){let U={name:`pkg:npm/${h.replace(/^@/,\"%40\")}@${p}`,digest:{sha512:E.sha512[0].hexDigest()}},W=await VNe([U]),te=JSON.stringify(W);N[`${h}-${p}.sigstore`]={content_type:W.mediaType,data:te,length:te.length}}return{_id:h,_attachments:N,name:h,access:C,\"dist-tags\":{[s]:p},versions:{[p]:{...P,_id:`${h}@${p}`,name:h,version:p,gitHead:n,dist:{shasum:E.sha1[0].hexDigest(),integrity:E.sha512[0].toString(),tarball:R.toString()}}},readme:S}}async function JHt(t){try{let{stdout:e}=await Gr.execvp(\"git\",[\"rev-parse\",\"--revs-only\",\"HEAD\"],{cwd:t});return e.trim()===\"\"?void 0:e.trim()}catch{return}}function JNe(t,e){let r=t.project.configuration;return t.manifest.publishConfig&&typeof t.manifest.publishConfig.access==\"string\"?t.manifest.publishConfig.access:r.get(\"npmPublishAccess\")!==null?r.get(\"npmPublishAccess\"):e.scope?\"restricted\":\"public\"}async function zNe(t){let e=ue.toPortablePath(`${t.cwd}/README.md`),r=t.manifest.name,a=`# ${q.stringifyIdent(r)}\n`;try{a=await le.readFilePromise(e,\"utf8\")}catch(n){if(n.code===\"ENOENT\")return a;throw n}return a}var sz={npmAlwaysAuth:{description:\"URL of the selected npm registry (note: npm enterprise isn't supported)\",type:\"BOOLEAN\",default:!1},npmAuthIdent:{description:\"Authentication identity for the npm registry (_auth in npm and yarn v1)\",type:\"SECRET\",default:null},npmAuthToken:{description:\"Authentication token for the npm registry (_authToken in npm and yarn v1)\",type:\"SECRET\",default:null}},ZNe={npmAuditRegistry:{description:\"Registry to query for audit reports\",type:\"STRING\",default:null},npmPublishRegistry:{description:\"Registry to push packages to\",type:\"STRING\",default:null},npmRegistryServer:{description:\"URL of the selected npm registry (note: npm enterprise isn't supported)\",type:\"STRING\",default:\"https://registry.yarnpkg.com\"}},zHt={configuration:{...sz,...ZNe,npmScopes:{description:\"Settings per package scope\",type:\"MAP\",valueDefinition:{description:\"\",type:\"SHAPE\",properties:{...sz,...ZNe}}},npmRegistries:{description:\"Settings per registry\",type:\"MAP\",normalizeKeys:zc,valueDefinition:{description:\"\",type:\"SHAPE\",properties:{...sz}}}},fetchers:[rb,lh],resolvers:[nb,ib,sb]},ZHt=zHt;var gz={};Vt(gz,{NpmAuditCommand:()=>S1,NpmInfoCommand:()=>D1,NpmLoginCommand:()=>b1,NpmLogoutCommand:()=>x1,NpmPublishCommand:()=>k1,NpmTagAddCommand:()=>T1,NpmTagListCommand:()=>Q1,NpmTagRemoveCommand:()=>R1,NpmWhoamiCommand:()=>F1,default:()=>ijt,npmAuditTypes:()=>sP,npmAuditUtils:()=>QL});Ve();Ve();Wt();var fz=et(Sa());Ul();var sP={};Vt(sP,{Environment:()=>nP,Severity:()=>iP});var nP=(s=>(s.All=\"all\",s.Production=\"production\",s.Development=\"development\",s))(nP||{}),iP=(n=>(n.Info=\"info\",n.Low=\"low\",n.Moderate=\"moderate\",n.High=\"high\",n.Critical=\"critical\",n))(iP||{});var QL={};Vt(QL,{allSeverities:()=>v1,getPackages:()=>uz,getReportTree:()=>lz,getSeverityInclusions:()=>az,getTopLevelDependencies:()=>cz});Ve();var XNe=et(Ai());var v1=[\"info\",\"low\",\"moderate\",\"high\",\"critical\"];function az(t){if(typeof t>\"u\")return new Set(v1);let e=v1.indexOf(t),r=v1.slice(e);return new Set(r)}function lz(t){let e={},r={children:e};for(let[s,a]of je.sortMap(Object.entries(t),n=>n[0]))for(let n of je.sortMap(a,c=>`${c.id}`))e[`${s}/${n.id}`]={value:he.tuple(he.Type.IDENT,q.parseIdent(s)),children:{ID:typeof n.id<\"u\"&&{label:\"ID\",value:he.tuple(he.Type.ID,n.id)},Issue:{label:\"Issue\",value:he.tuple(he.Type.NO_HINT,n.title)},URL:typeof n.url<\"u\"&&{label:\"URL\",value:he.tuple(he.Type.URL,n.url)},Severity:{label:\"Severity\",value:he.tuple(he.Type.NO_HINT,n.severity)},\"Vulnerable Versions\":{label:\"Vulnerable Versions\",value:he.tuple(he.Type.RANGE,n.vulnerable_versions)},\"Tree Versions\":{label:\"Tree Versions\",children:[...n.versions].sort(XNe.default.compare).map(c=>({value:he.tuple(he.Type.REFERENCE,c)}))},Dependents:{label:\"Dependents\",children:je.sortMap(n.dependents,c=>q.stringifyLocator(c)).map(c=>({value:he.tuple(he.Type.LOCATOR,c)}))}}};return r}function cz(t,e,{all:r,environment:s}){let a=[],n=r?t.workspaces:[e],c=[\"all\",\"production\"].includes(s),f=[\"all\",\"development\"].includes(s);for(let p of n)for(let h of p.anchoredPackage.dependencies.values())(p.manifest.devDependencies.has(h.identHash)?!f:!c)||a.push({workspace:p,dependency:h});return a}function uz(t,e,{recursive:r}){let s=new Map,a=new Set,n=[],c=(f,p)=>{let h=t.storedResolutions.get(p.descriptorHash);if(typeof h>\"u\")throw new Error(\"Assertion failed: The resolution should have been registered\");if(!a.has(h))a.add(h);else return;let E=t.storedPackages.get(h);if(typeof E>\"u\")throw new Error(\"Assertion failed: The package should have been registered\");if(q.ensureDevirtualizedLocator(E).reference.startsWith(\"npm:\")&&E.version!==null){let S=q.stringifyIdent(E),P=je.getMapWithDefault(s,S);je.getArrayWithDefault(P,E.version).push(f)}if(r)for(let S of E.dependencies.values())n.push([E,S])};for(let{workspace:f,dependency:p}of e)n.push([f.anchoredLocator,p]);for(;n.length>0;){let[f,p]=n.shift();c(f,p)}return s}var S1=class extends ut{constructor(){super(...arguments);this.all=ge.Boolean(\"-A,--all\",!1,{description:\"Audit dependencies from all workspaces\"});this.recursive=ge.Boolean(\"-R,--recursive\",!1,{description:\"Audit transitive dependencies as well\"});this.environment=ge.String(\"--environment\",\"all\",{description:\"Which environments to cover\",validator:po(nP)});this.json=ge.Boolean(\"--json\",!1,{description:\"Format the output as an NDJSON stream\"});this.noDeprecations=ge.Boolean(\"--no-deprecations\",!1,{description:\"Don't warn about deprecated packages\"});this.severity=ge.String(\"--severity\",\"info\",{description:\"Minimal severity requested for packages to be displayed\",validator:po(iP)});this.excludes=ge.Array(\"--exclude\",[],{description:\"Array of glob patterns of packages to exclude from audit\"});this.ignores=ge.Array(\"--ignore\",[],{description:\"Array of glob patterns of advisory ID's to ignore in the audit report\"})}static{this.paths=[[\"npm\",\"audit\"]]}static{this.usage=ot.Usage({description:\"perform a vulnerability audit against the installed packages\",details:`\n      This command checks for known security reports on the packages you use. The reports are by default extracted from the npm registry, and may or may not be relevant to your actual program (not all vulnerabilities affect all code paths).\n\n      For consistency with our other commands the default is to only check the direct dependencies for the active workspace. To extend this search to all workspaces, use \\`-A,--all\\`. To extend this search to both direct and transitive dependencies, use \\`-R,--recursive\\`.\n\n      Applying the \\`--severity\\` flag will limit the audit table to vulnerabilities of the corresponding severity and above. Valid values are ${v1.map(r=>`\\`${r}\\``).join(\", \")}.\n\n      If the \\`--json\\` flag is set, Yarn will print the output exactly as received from the registry. Regardless of this flag, the process will exit with a non-zero exit code if a report is found for the selected packages.\n\n      If certain packages produce false positives for a particular environment, the \\`--exclude\\` flag can be used to exclude any number of packages from the audit. This can also be set in the configuration file with the \\`npmAuditExcludePackages\\` option.\n\n      If particular advisories are needed to be ignored, the \\`--ignore\\` flag can be used with Advisory ID's to ignore any number of advisories in the audit report. This can also be set in the configuration file with the \\`npmAuditIgnoreAdvisories\\` option.\n\n      To understand the dependency tree requiring vulnerable packages, check the raw report with the \\`--json\\` flag or use \\`yarn why package\\` to get more information as to who depends on them.\n    `,examples:[[\"Checks for known security issues with the installed packages. The output is a list of known issues.\",\"yarn npm audit\"],[\"Audit dependencies in all workspaces\",\"yarn npm audit --all\"],[\"Limit auditing to `dependencies` (excludes `devDependencies`)\",\"yarn npm audit --environment production\"],[\"Show audit report as valid JSON\",\"yarn npm audit --json\"],[\"Audit all direct and transitive dependencies\",\"yarn npm audit --recursive\"],[\"Output moderate (or more severe) vulnerabilities\",\"yarn npm audit --severity moderate\"],[\"Exclude certain packages\",\"yarn npm audit --exclude package1 --exclude package2\"],[\"Ignore specific advisories\",\"yarn npm audit --ignore 1234567 --ignore 7654321\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState();let n=cz(s,a,{all:this.all,environment:this.environment}),c=uz(s,n,{recursive:this.recursive}),f=Array.from(new Set([...r.get(\"npmAuditExcludePackages\"),...this.excludes])),p=Object.create(null);for(let[N,U]of c)f.some(W=>fz.default.isMatch(N,W))||(p[N]=[...U.keys()]);let h=hi.getAuditRegistry({configuration:r}),E,C=await uA.start({configuration:r,stdout:this.context.stdout},async()=>{let N=an.post(\"/-/npm/v1/security/advisories/bulk\",p,{authType:an.AuthType.BEST_EFFORT,configuration:r,jsonResponse:!0,registry:h}),U=this.noDeprecations?[]:await Promise.all(Array.from(Object.entries(p),async([te,ie])=>{let Ae=await an.getPackageMetadata(q.parseIdent(te),{project:s});return je.mapAndFilter(ie,ce=>{let{deprecated:me}=Ae.versions[ce];return me?[te,ce,me]:je.mapAndFilter.skip})})),W=await N;for(let[te,ie,Ae]of U.flat(1))Object.hasOwn(W,te)&&W[te].some(ce=>Or.satisfiesWithPrereleases(ie,ce.vulnerable_versions))||(W[te]??=[],W[te].push({id:`${te} (deprecation)`,title:(typeof Ae==\"string\"?Ae:\"\").trim()||\"This package has been deprecated.\",severity:\"moderate\",vulnerable_versions:ie}));E=W});if(C.hasErrors())return C.exitCode();let S=az(this.severity),P=Array.from(new Set([...r.get(\"npmAuditIgnoreAdvisories\"),...this.ignores])),I=Object.create(null);for(let[N,U]of Object.entries(E)){let W=U.filter(te=>!fz.default.isMatch(`${te.id}`,P)&&S.has(te.severity));W.length>0&&(I[N]=W.map(te=>{let ie=c.get(N);if(typeof ie>\"u\")throw new Error(\"Assertion failed: Expected the registry to only return packages that were requested\");let Ae=[...ie.keys()].filter(me=>Or.satisfiesWithPrereleases(me,te.vulnerable_versions)),ce=new Map;for(let me of Ae)for(let pe of ie.get(me))ce.set(pe.locatorHash,pe);return{...te,versions:Ae,dependents:[...ce.values()]}}))}let R=Object.keys(I).length>0;return R?(Qs.emitTree(lz(I),{configuration:r,json:this.json,stdout:this.context.stdout,separators:2}),1):(await Ot.start({configuration:r,includeFooter:!1,json:this.json,stdout:this.context.stdout},async N=>{N.reportInfo(1,\"No audit suggestions\")}),R?1:0)}};Ve();Ve();bt();Wt();var Az=et(Ai()),pz=Ie(\"util\"),D1=class extends ut{constructor(){super(...arguments);this.fields=ge.String(\"-f,--fields\",{description:\"A comma-separated list of manifest fields that should be displayed\"});this.json=ge.Boolean(\"--json\",!1,{description:\"Format the output as an NDJSON stream\"});this.packages=ge.Rest()}static{this.paths=[[\"npm\",\"info\"]]}static{this.usage=ot.Usage({category:\"Npm-related commands\",description:\"show information about a package\",details:\"\\n      This command fetches information about a package from the npm registry and prints it in a tree format.\\n\\n      The package does not have to be installed locally, but needs to have been published (in particular, local changes will be ignored even for workspaces).\\n\\n      Append `@<range>` to the package argument to provide information specific to the latest version that satisfies the range or to the corresponding tagged version. If the range is invalid or if there is no version satisfying the range, the command will print a warning and fall back to the latest version.\\n\\n      If the `-f,--fields` option is set, it's a comma-separated list of fields which will be used to only display part of the package information.\\n\\n      By default, this command won't return the `dist`, `readme`, and `users` fields, since they are often very long. To explicitly request those fields, explicitly list them with the `--fields` flag or request the output in JSON mode.\\n    \",examples:[[\"Show all available information about react (except the `dist`, `readme`, and `users` fields)\",\"yarn npm info react\"],[\"Show all available information about react as valid JSON (including the `dist`, `readme`, and `users` fields)\",\"yarn npm info react --json\"],[\"Show all available information about react@16.12.0\",\"yarn npm info react@16.12.0\"],[\"Show all available information about react@next\",\"yarn npm info react@next\"],[\"Show the description of react\",\"yarn npm info react --fields description\"],[\"Show all available versions of react\",\"yarn npm info react --fields versions\"],[\"Show the readme of react\",\"yarn npm info react --fields readme\"],[\"Show a few fields of react\",\"yarn npm info react --fields homepage,repository\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s}=await Tt.find(r,this.context.cwd),a=typeof this.fields<\"u\"?new Set([\"name\",...this.fields.split(/\\s*,\\s*/)]):null,n=[],c=!1,f=await Ot.start({configuration:r,includeFooter:!1,json:this.json,stdout:this.context.stdout},async p=>{for(let h of this.packages){let E;if(h===\".\"){let ie=s.topLevelWorkspace;if(!ie.manifest.name)throw new nt(`Missing ${he.pretty(r,\"name\",he.Type.CODE)} field in ${ue.fromPortablePath(K.join(ie.cwd,Er.manifest))}`);E=q.makeDescriptor(ie.manifest.name,\"unknown\")}else E=q.parseDescriptor(h);let C=an.getIdentUrl(E),S=hz(await an.get(C,{configuration:r,ident:E,jsonResponse:!0,customErrorMessage:an.customPackageError})),P=Object.keys(S.versions).sort(Az.default.compareLoose),R=S[\"dist-tags\"].latest||P[P.length-1],N=Or.validRange(E.range);if(N){let ie=Az.default.maxSatisfying(P,N);ie!==null?R=ie:(p.reportWarning(0,`Unmet range ${q.prettyRange(r,E.range)}; falling back to the latest version`),c=!0)}else Object.hasOwn(S[\"dist-tags\"],E.range)?R=S[\"dist-tags\"][E.range]:E.range!==\"unknown\"&&(p.reportWarning(0,`Unknown tag ${q.prettyRange(r,E.range)}; falling back to the latest version`),c=!0);let U=S.versions[R],W={...S,...U,version:R,versions:P},te;if(a!==null){te={};for(let ie of a){let Ae=W[ie];if(typeof Ae<\"u\")te[ie]=Ae;else{p.reportWarning(1,`The ${he.pretty(r,ie,he.Type.CODE)} field doesn't exist inside ${q.prettyIdent(r,E)}'s information`),c=!0;continue}}}else this.json||(delete W.dist,delete W.readme,delete W.users),te=W;p.reportJson(te),this.json||n.push(te)}});pz.inspect.styles.name=\"cyan\";for(let p of n)(p!==n[0]||c)&&this.context.stdout.write(`\n`),this.context.stdout.write(`${(0,pz.inspect)(p,{depth:1/0,colors:!0,compact:!1})}\n`);return f.exitCode()}};function hz(t){if(Array.isArray(t)){let e=[];for(let r of t)r=hz(r),r&&e.push(r);return e}else if(typeof t==\"object\"&&t!==null){let e={};for(let r of Object.keys(t)){if(r.startsWith(\"_\"))continue;let s=hz(t[r]);s&&(e[r]=s)}return e}else return t||null}Ve();Ve();Wt();var $Ne=et(nS()),b1=class extends ut{constructor(){super(...arguments);this.scope=ge.String(\"-s,--scope\",{description:\"Login to the registry configured for a given scope\"});this.publish=ge.Boolean(\"--publish\",!1,{description:\"Login to the publish registry\"});this.alwaysAuth=ge.Boolean(\"--always-auth\",{description:\"Set the npmAlwaysAuth configuration\"})}static{this.paths=[[\"npm\",\"login\"]]}static{this.usage=ot.Usage({category:\"Npm-related commands\",description:\"store new login info to access the npm registry\",details:\"\\n      This command will ask you for your username, password, and 2FA One-Time-Password (when it applies). It will then modify your local configuration (in your home folder, never in the project itself) to reference the new tokens thus generated.\\n\\n      Adding the `-s,--scope` flag will cause the authentication to be done against whatever registry is configured for the associated scope (see also `npmScopes`).\\n\\n      Adding the `--publish` flag will cause the authentication to be done against the registry used when publishing the package (see also `publishConfig.registry` and `npmPublishRegistry`).\\n    \",examples:[[\"Login to the default registry\",\"yarn npm login\"],[\"Login to the registry linked to the @my-scope registry\",\"yarn npm login --scope my-scope\"],[\"Login to the publish registry for the current package\",\"yarn npm login --publish\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),s=await TL({configuration:r,cwd:this.context.cwd,publish:this.publish,scope:this.scope});return(await Ot.start({configuration:r,stdout:this.context.stdout,includeFooter:!1},async n=>{let c=await ejt({configuration:r,registry:s,report:n,stdin:this.context.stdin,stdout:this.context.stdout}),f=await XHt(s,c,r);return await $Ht(s,f,{alwaysAuth:this.alwaysAuth,scope:this.scope}),n.reportInfo(0,\"Successfully logged in\")})).exitCode()}};async function TL({scope:t,publish:e,configuration:r,cwd:s}){return t&&e?hi.getScopeRegistry(t,{configuration:r,type:hi.RegistryType.PUBLISH_REGISTRY}):t?hi.getScopeRegistry(t,{configuration:r}):e?hi.getPublishRegistry((await eC(r,s)).manifest,{configuration:r}):hi.getDefaultRegistry({configuration:r})}async function XHt(t,e,r){let s=`/-/user/org.couchdb.user:${encodeURIComponent(e.name)}`,a={_id:`org.couchdb.user:${e.name}`,name:e.name,password:e.password,type:\"user\",roles:[],date:new Date().toISOString()},n={attemptedAs:e.name,configuration:r,registry:t,jsonResponse:!0,authType:an.AuthType.NO_AUTH};try{return(await an.put(s,a,n)).token}catch(E){if(!(E.originalError?.name===\"HTTPError\"&&E.originalError?.response.statusCode===409))throw E}let c={...n,authType:an.AuthType.NO_AUTH,headers:{authorization:`Basic ${Buffer.from(`${e.name}:${e.password}`).toString(\"base64\")}`}},f=await an.get(s,c);for(let[E,C]of Object.entries(f))(!a[E]||E===\"roles\")&&(a[E]=C);let p=`${s}/-rev/${a._rev}`;return(await an.put(p,a,c)).token}async function $Ht(t,e,{alwaysAuth:r,scope:s}){let a=c=>f=>{let p=je.isIndexableObject(f)?f:{},h=p[c],E=je.isIndexableObject(h)?h:{};return{...p,[c]:{...E,...r!==void 0?{npmAlwaysAuth:r}:{},npmAuthToken:e}}},n=s?{npmScopes:a(s)}:{npmRegistries:a(t)};return await ze.updateHomeConfiguration(n)}async function ejt({configuration:t,registry:e,report:r,stdin:s,stdout:a}){r.reportInfo(0,`Logging in to ${he.pretty(t,e,he.Type.URL)}`);let n=!1;if(e.match(/^https:\\/\\/npm\\.pkg\\.github\\.com(\\/|$)/)&&(r.reportInfo(0,\"You seem to be using the GitHub Package Registry. Tokens must be generated with the 'repo', 'write:packages', and 'read:packages' permissions.\"),n=!0),r.reportSeparator(),t.env.YARN_IS_TEST_ENV)return{name:t.env.YARN_INJECT_NPM_USER||\"\",password:t.env.YARN_INJECT_NPM_PASSWORD||\"\"};let c=await(0,$Ne.prompt)([{type:\"input\",name:\"name\",message:\"Username:\",required:!0,onCancel:()=>process.exit(130),stdin:s,stdout:a},{type:\"password\",name:\"password\",message:n?\"Token:\":\"Password:\",required:!0,onCancel:()=>process.exit(130),stdin:s,stdout:a}]);return r.reportSeparator(),c}Ve();Ve();Wt();var P1=new Set([\"npmAuthIdent\",\"npmAuthToken\"]),x1=class extends ut{constructor(){super(...arguments);this.scope=ge.String(\"-s,--scope\",{description:\"Logout of the registry configured for a given scope\"});this.publish=ge.Boolean(\"--publish\",!1,{description:\"Logout of the publish registry\"});this.all=ge.Boolean(\"-A,--all\",!1,{description:\"Logout of all registries\"})}static{this.paths=[[\"npm\",\"logout\"]]}static{this.usage=ot.Usage({category:\"Npm-related commands\",description:\"logout of the npm registry\",details:\"\\n      This command will log you out by modifying your local configuration (in your home folder, never in the project itself) to delete all credentials linked to a registry.\\n\\n      Adding the `-s,--scope` flag will cause the deletion to be done against whatever registry is configured for the associated scope (see also `npmScopes`).\\n\\n      Adding the `--publish` flag will cause the deletion to be done against the registry used when publishing the package (see also `publishConfig.registry` and `npmPublishRegistry`).\\n\\n      Adding the `-A,--all` flag will cause the deletion to be done against all registries and scopes.\\n    \",examples:[[\"Logout of the default registry\",\"yarn npm logout\"],[\"Logout of the @my-scope scope\",\"yarn npm logout --scope my-scope\"],[\"Logout of the publish registry for the current package\",\"yarn npm logout --publish\"],[\"Logout of all registries\",\"yarn npm logout --all\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),s=async()=>{let n=await TL({configuration:r,cwd:this.context.cwd,publish:this.publish,scope:this.scope}),c=await ze.find(this.context.cwd,this.context.plugins),f=q.makeIdent(this.scope??null,\"pkg\");return!hi.getAuthConfiguration(n,{configuration:c,ident:f}).get(\"npmAuthToken\")};return(await Ot.start({configuration:r,stdout:this.context.stdout},async n=>{if(this.all&&(await rjt(),n.reportInfo(0,\"Successfully logged out from everything\")),this.scope){await eOe(\"npmScopes\",this.scope),await s()?n.reportInfo(0,`Successfully logged out from ${this.scope}`):n.reportWarning(0,\"Scope authentication settings removed, but some other ones settings still apply to it\");return}let c=await TL({configuration:r,cwd:this.context.cwd,publish:this.publish});await eOe(\"npmRegistries\",c),await s()?n.reportInfo(0,`Successfully logged out from ${c}`):n.reportWarning(0,\"Registry authentication settings removed, but some other ones settings still apply to it\")})).exitCode()}};function tjt(t,e){let r=t[e];if(!je.isIndexableObject(r))return!1;let s=new Set(Object.keys(r));if([...P1].every(n=>!s.has(n)))return!1;for(let n of P1)s.delete(n);if(s.size===0)return t[e]=void 0,!0;let a={...r};for(let n of P1)delete a[n];return t[e]=a,!0}async function rjt(){let t=e=>{let r=!1,s=je.isIndexableObject(e)?{...e}:{};s.npmAuthToken&&(delete s.npmAuthToken,r=!0);for(let a of Object.keys(s))tjt(s,a)&&(r=!0);if(Object.keys(s).length!==0)return r?s:e};return await ze.updateHomeConfiguration({npmRegistries:t,npmScopes:t})}async function eOe(t,e){return await ze.updateHomeConfiguration({[t]:r=>{let s=je.isIndexableObject(r)?r:{};if(!Object.hasOwn(s,e))return r;let a=s[e],n=je.isIndexableObject(a)?a:{},c=new Set(Object.keys(n));if([...P1].every(p=>!c.has(p)))return r;for(let p of P1)c.delete(p);if(c.size===0)return Object.keys(s).length===1?void 0:{...s,[e]:void 0};let f={};for(let p of P1)f[p]=void 0;return{...s,[e]:{...n,...f}}}})}Ve();Wt();var k1=class extends ut{constructor(){super(...arguments);this.access=ge.String(\"--access\",{description:\"The access for the published package (public or restricted)\"});this.tag=ge.String(\"--tag\",\"latest\",{description:\"The tag on the registry that the package should be attached to\"});this.tolerateRepublish=ge.Boolean(\"--tolerate-republish\",!1,{description:\"Warn and exit when republishing an already existing version of a package\"});this.otp=ge.String(\"--otp\",{description:\"The OTP token to use with the command\"});this.provenance=ge.Boolean(\"--provenance\",!1,{description:\"Generate provenance for the package. Only available in GitHub Actions and GitLab CI. Can be set globally through the `npmPublishProvenance` setting or the `YARN_NPM_CONFIG_PROVENANCE` environment variable, or per-package through the `publishConfig.provenance` field in package.json.\"})}static{this.paths=[[\"npm\",\"publish\"]]}static{this.usage=ot.Usage({category:\"Npm-related commands\",description:\"publish the active workspace to the npm registry\",details:'\\n      This command will pack the active workspace into a fresh archive and upload it to the npm registry.\\n\\n      The package will by default be attached to the `latest` tag on the registry, but this behavior can be overridden by using the `--tag` option.\\n\\n      Note that for legacy reasons scoped packages are by default published with an access set to `restricted` (aka \"private packages\"). This requires you to register for a paid npm plan. In case you simply wish to publish a public scoped package to the registry (for free), just add the `--access public` flag. This behavior can be enabled by default through the `npmPublishAccess` settings.\\n    ',examples:[[\"Publish the active workspace\",\"yarn npm publish\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);if(a.manifest.private)throw new nt(\"Private workspaces cannot be published\");if(a.manifest.name===null||a.manifest.version===null)throw new nt(\"Workspaces must have valid names and versions to be published on an external registry\");await s.restoreInstallState();let n=a.manifest.name,c=a.manifest.version,f=hi.getPublishRegistry(a.manifest,{configuration:r});return(await Ot.start({configuration:r,stdout:this.context.stdout},async h=>{if(this.tolerateRepublish)try{let E=await an.get(an.getIdentUrl(n),{configuration:r,registry:f,ident:n,jsonResponse:!0});if(!Object.hasOwn(E,\"versions\"))throw new Yt(15,'Registry returned invalid data for - missing \"versions\" field');if(Object.hasOwn(E.versions,c)){h.reportWarning(0,`Registry already knows about version ${c}; skipping.`);return}}catch(E){if(E.originalError?.response?.statusCode!==404)throw E}await In.maybeExecuteWorkspaceLifecycleScript(a,\"prepublish\",{report:h}),await IA.prepareForPack(a,{report:h},async()=>{let E=await IA.genPackList(a);for(let N of E)h.reportInfo(null,N);let C=await IA.genPackStream(a,E),S=await je.bufferStream(C),P=await B1.getGitHead(a.cwd),I=!1;a.manifest.publishConfig&&\"provenance\"in a.manifest.publishConfig?(I=!!a.manifest.publishConfig.provenance,I?h.reportInfo(null,\"Generating provenance statement because `publishConfig.provenance` field is set.\"):h.reportInfo(null,\"Skipping provenance statement because `publishConfig.provenance` field is set to false.\")):this.provenance?(I=!0,h.reportInfo(null,\"Generating provenance statement because `--provenance` flag is set.\")):r.get(\"npmPublishProvenance\")&&(I=!0,h.reportInfo(null,\"Generating provenance statement because `npmPublishProvenance` setting is set.\"));let R=await B1.makePublishBody(a,S,{access:this.access,tag:this.tag,registry:f,gitHead:P,provenance:I});await an.put(an.getIdentUrl(n),R,{configuration:r,registry:f,ident:n,otp:this.otp,jsonResponse:!0})}),h.reportInfo(0,\"Package archive published\")})).exitCode()}};Ve();Wt();var tOe=et(Ai());Ve();bt();Wt();var Q1=class extends ut{constructor(){super(...arguments);this.json=ge.Boolean(\"--json\",!1,{description:\"Format the output as an NDJSON stream\"});this.package=ge.String({required:!1})}static{this.paths=[[\"npm\",\"tag\",\"list\"]]}static{this.usage=ot.Usage({category:\"Npm-related commands\",description:\"list all dist-tags of a package\",details:`\n      This command will list all tags of a package from the npm registry.\n\n      If the package is not specified, Yarn will default to the current workspace.\n    `,examples:[[\"List all tags of package `my-pkg`\",\"yarn npm tag list my-pkg\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n;if(typeof this.package<\"u\")n=q.parseIdent(this.package);else{if(!a)throw new ar(s.cwd,this.context.cwd);if(!a.manifest.name)throw new nt(`Missing 'name' field in ${ue.fromPortablePath(K.join(a.cwd,Er.manifest))}`);n=a.manifest.name}let c=await oP(n,r),p={children:je.sortMap(Object.entries(c),([h])=>h).map(([h,E])=>({value:he.tuple(he.Type.RESOLUTION,{descriptor:q.makeDescriptor(n,h),locator:q.makeLocator(n,E)})}))};return Qs.emitTree(p,{configuration:r,json:this.json,stdout:this.context.stdout})}};async function oP(t,e){let r=`/-/package${an.getIdentUrl(t)}/dist-tags`;return an.get(r,{configuration:e,ident:t,jsonResponse:!0,customErrorMessage:an.customPackageError})}var T1=class extends ut{constructor(){super(...arguments);this.package=ge.String();this.tag=ge.String()}static{this.paths=[[\"npm\",\"tag\",\"add\"]]}static{this.usage=ot.Usage({category:\"Npm-related commands\",description:\"add a tag for a specific version of a package\",details:`\n      This command will add a tag to the npm registry for a specific version of a package. If the tag already exists, it will be overwritten.\n    `,examples:[[\"Add a `beta` tag for version `2.3.4-beta.4` of package `my-pkg`\",\"yarn npm tag add my-pkg@2.3.4-beta.4 beta\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);let n=q.parseDescriptor(this.package,!0),c=n.range;if(!tOe.default.valid(c))throw new nt(`The range ${he.pretty(r,n.range,he.Type.RANGE)} must be a valid semver version`);let f=hi.getPublishRegistry(a.manifest,{configuration:r}),p=he.pretty(r,n,he.Type.IDENT),h=he.pretty(r,c,he.Type.RANGE),E=he.pretty(r,this.tag,he.Type.CODE);return(await Ot.start({configuration:r,stdout:this.context.stdout},async S=>{let P=await oP(n,r);Object.hasOwn(P,this.tag)&&P[this.tag]===c&&S.reportWarning(0,`Tag ${E} is already set to version ${h}`);let I=`/-/package${an.getIdentUrl(n)}/dist-tags/${encodeURIComponent(this.tag)}`;await an.put(I,c,{configuration:r,registry:f,ident:n,jsonRequest:!0,jsonResponse:!0}),S.reportInfo(0,`Tag ${E} added to version ${h} of package ${p}`)})).exitCode()}};Ve();Wt();var R1=class extends ut{constructor(){super(...arguments);this.package=ge.String();this.tag=ge.String()}static{this.paths=[[\"npm\",\"tag\",\"remove\"]]}static{this.usage=ot.Usage({category:\"Npm-related commands\",description:\"remove a tag from a package\",details:`\n      This command will remove a tag from a package from the npm registry.\n    `,examples:[[\"Remove the `beta` tag from package `my-pkg`\",\"yarn npm tag remove my-pkg beta\"]]})}async execute(){if(this.tag===\"latest\")throw new nt(\"The 'latest' tag cannot be removed.\");let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);let n=q.parseIdent(this.package),c=hi.getPublishRegistry(a.manifest,{configuration:r}),f=he.pretty(r,this.tag,he.Type.CODE),p=he.pretty(r,n,he.Type.IDENT),h=await oP(n,r);if(!Object.hasOwn(h,this.tag))throw new nt(`${f} is not a tag of package ${p}`);return(await Ot.start({configuration:r,stdout:this.context.stdout},async C=>{let S=`/-/package${an.getIdentUrl(n)}/dist-tags/${encodeURIComponent(this.tag)}`;await an.del(S,{configuration:r,registry:c,ident:n,jsonResponse:!0}),C.reportInfo(0,`Tag ${f} removed from package ${p}`)})).exitCode()}};Ve();Ve();Wt();var F1=class extends ut{constructor(){super(...arguments);this.scope=ge.String(\"-s,--scope\",{description:\"Print username for the registry configured for a given scope\"});this.publish=ge.Boolean(\"--publish\",!1,{description:\"Print username for the publish registry\"})}static{this.paths=[[\"npm\",\"whoami\"]]}static{this.usage=ot.Usage({category:\"Npm-related commands\",description:\"display the name of the authenticated user\",details:\"\\n      Print the username associated with the current authentication settings to the standard output.\\n\\n      When using `-s,--scope`, the username printed will be the one that matches the authentication settings of the registry associated with the given scope (those settings can be overriden using the `npmRegistries` map, and the registry associated with the scope is configured via the `npmScopes` map).\\n\\n      When using `--publish`, the registry we'll select will by default be the one used when publishing packages (`publishConfig.registry` or `npmPublishRegistry` if available, otherwise we'll fallback to the regular `npmRegistryServer`).\\n    \",examples:[[\"Print username for the default registry\",\"yarn npm whoami\"],[\"Print username for the registry on a given scope\",\"yarn npm whoami --scope company\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),s;return this.scope&&this.publish?s=hi.getScopeRegistry(this.scope,{configuration:r,type:hi.RegistryType.PUBLISH_REGISTRY}):this.scope?s=hi.getScopeRegistry(this.scope,{configuration:r}):this.publish?s=hi.getPublishRegistry((await eC(r,this.context.cwd)).manifest,{configuration:r}):s=hi.getDefaultRegistry({configuration:r}),(await Ot.start({configuration:r,stdout:this.context.stdout},async n=>{let c;try{c=await an.get(\"/-/whoami\",{configuration:r,registry:s,authType:an.AuthType.ALWAYS_AUTH,jsonResponse:!0,ident:this.scope?q.makeIdent(this.scope,\"\"):void 0})}catch(f){if(f.response?.statusCode===401||f.response?.statusCode===403){n.reportError(41,\"Authentication failed - your credentials may have expired\");return}else throw f}n.reportInfo(0,c.username)})).exitCode()}};var njt={configuration:{npmPublishAccess:{description:\"Default access of the published packages\",type:\"STRING\",default:null},npmPublishProvenance:{description:\"Whether to generate provenance for the published packages\",type:\"BOOLEAN\",default:!1},npmAuditExcludePackages:{description:\"Array of glob patterns of packages to exclude from npm audit\",type:\"STRING\",default:[],isArray:!0},npmAuditIgnoreAdvisories:{description:\"Array of glob patterns of advisory IDs to exclude from npm audit\",type:\"STRING\",default:[],isArray:!0}},commands:[S1,D1,b1,x1,k1,T1,Q1,R1,F1]},ijt=njt;var wz={};Vt(wz,{PatchCommand:()=>U1,PatchCommitCommand:()=>_1,PatchFetcher:()=>fP,PatchResolver:()=>AP,default:()=>wjt,patchUtils:()=>hy});Ve();Ve();bt();rA();var hy={};Vt(hy,{applyPatchFile:()=>FL,diffFolders:()=>Iz,ensureUnpatchedDescriptor:()=>dz,ensureUnpatchedLocator:()=>OL,extractPackageToDisk:()=>Ez,extractPatchFlags:()=>lOe,isParentRequired:()=>yz,isPatchDescriptor:()=>NL,isPatchLocator:()=>Tg,loadPatchFiles:()=>uP,makeDescriptor:()=>LL,makeLocator:()=>mz,makePatchHash:()=>Cz,parseDescriptor:()=>lP,parseLocator:()=>cP,parsePatchFile:()=>aP,unpatchDescriptor:()=>Ejt,unpatchLocator:()=>Ijt});Ve();bt();Ve();bt();var sjt=/^@@ -(\\d+)(,(\\d+))? \\+(\\d+)(,(\\d+))? @@.*/;function N1(t){return K.relative(vt.root,K.resolve(vt.root,ue.toPortablePath(t)))}function ojt(t){let e=t.trim().match(sjt);if(!e)throw new Error(`Bad header line: '${t}'`);return{original:{start:Math.max(Number(e[1]),1),length:Number(e[3]||1)},patched:{start:Math.max(Number(e[4]),1),length:Number(e[6]||1)}}}var ajt=420,ljt=493;var rOe=()=>({semverExclusivity:null,diffLineFromPath:null,diffLineToPath:null,oldMode:null,newMode:null,deletedFileMode:null,newFileMode:null,renameFrom:null,renameTo:null,beforeHash:null,afterHash:null,fromPath:null,toPath:null,hunks:null}),cjt=t=>({header:ojt(t),parts:[]}),ujt={\"@\":\"header\",\"-\":\"deletion\",\"+\":\"insertion\",\" \":\"context\",\"\\\\\":\"pragma\",undefined:\"context\"};function fjt(t){let e=[],r=rOe(),s=\"parsing header\",a=null,n=null;function c(){a&&(n&&(a.parts.push(n),n=null),r.hunks.push(a),a=null)}function f(){c(),e.push(r),r=rOe()}for(let p=0;p<t.length;p++){let h=t[p];if(s===\"parsing header\")if(h.startsWith(\"@@\"))s=\"parsing hunks\",r.hunks=[],p-=1;else if(h.startsWith(\"diff --git \")){r&&r.diffLineFromPath&&f();let E=h.match(/^diff --git a\\/(.*?) b\\/(.*?)\\s*$/);if(!E)throw new Error(`Bad diff line: ${h}`);r.diffLineFromPath=E[1],r.diffLineToPath=E[2]}else if(h.startsWith(\"old mode \"))r.oldMode=h.slice(9).trim();else if(h.startsWith(\"new mode \"))r.newMode=h.slice(9).trim();else if(h.startsWith(\"deleted file mode \"))r.deletedFileMode=h.slice(18).trim();else if(h.startsWith(\"new file mode \"))r.newFileMode=h.slice(14).trim();else if(h.startsWith(\"rename from \"))r.renameFrom=h.slice(12).trim();else if(h.startsWith(\"rename to \"))r.renameTo=h.slice(10).trim();else if(h.startsWith(\"index \")){let E=h.match(/(\\w+)\\.\\.(\\w+)/);if(!E)continue;r.beforeHash=E[1],r.afterHash=E[2]}else h.startsWith(\"semver exclusivity \")?r.semverExclusivity=h.slice(19).trim():h.startsWith(\"--- \")?r.fromPath=h.slice(6).trim():h.startsWith(\"+++ \")&&(r.toPath=h.slice(6).trim());else{let E=ujt[h[0]]||null;switch(E){case\"header\":c(),a=cjt(h);break;case null:s=\"parsing header\",f(),p-=1;break;case\"pragma\":{if(!h.startsWith(\"\\\\ No newline at end of file\"))throw new Error(`Unrecognized pragma in patch file: ${h}`);if(!n)throw new Error(\"Bad parser state: No newline at EOF pragma encountered without context\");n.noNewlineAtEndOfFile=!0}break;case\"context\":case\"deletion\":case\"insertion\":{if(!a)throw new Error(\"Bad parser state: Hunk lines encountered before hunk header\");n&&n.type!==E&&(a.parts.push(n),n=null),n||(n={type:E,lines:[],noNewlineAtEndOfFile:!1}),n.lines.push(h.slice(1))}break;default:je.assertNever(E);break}}}f();for(let{hunks:p}of e)if(p)for(let h of p)pjt(h);return e}function Ajt(t){let e=[];for(let r of t){let{semverExclusivity:s,diffLineFromPath:a,diffLineToPath:n,oldMode:c,newMode:f,deletedFileMode:p,newFileMode:h,renameFrom:E,renameTo:C,beforeHash:S,afterHash:P,fromPath:I,toPath:R,hunks:N}=r,U=E?\"rename\":p?\"file deletion\":h?\"file creation\":N&&N.length>0?\"patch\":\"mode change\",W=null;switch(U){case\"rename\":{if(!E||!C)throw new Error(\"Bad parser state: rename from & to not given\");e.push({type:\"rename\",semverExclusivity:s,fromPath:N1(E),toPath:N1(C)}),W=C}break;case\"file deletion\":{let te=a||I;if(!te)throw new Error(\"Bad parse state: no path given for file deletion\");e.push({type:\"file deletion\",semverExclusivity:s,hunk:N&&N[0]||null,path:N1(te),mode:RL(p),hash:S})}break;case\"file creation\":{let te=n||R;if(!te)throw new Error(\"Bad parse state: no path given for file creation\");e.push({type:\"file creation\",semverExclusivity:s,hunk:N&&N[0]||null,path:N1(te),mode:RL(h),hash:P})}break;case\"patch\":case\"mode change\":W=R||n;break;default:je.assertNever(U);break}W&&c&&f&&c!==f&&e.push({type:\"mode change\",semverExclusivity:s,path:N1(W),oldMode:RL(c),newMode:RL(f)}),W&&N&&N.length&&e.push({type:\"patch\",semverExclusivity:s,path:N1(W),hunks:N,beforeHash:S,afterHash:P})}if(e.length===0)throw new Error(\"Unable to parse patch file: No changes found. Make sure the patch is a valid UTF8 encoded string\");return e}function RL(t){let e=parseInt(t,8)&511;if(e!==ajt&&e!==ljt)throw new Error(`Unexpected file mode string: ${t}`);return e}function aP(t){let e=t.split(/\\n/g);return e[e.length-1]===\"\"&&e.pop(),Ajt(fjt(e))}function pjt(t){let e=0,r=0;for(let{type:s,lines:a}of t.parts)switch(s){case\"context\":r+=a.length,e+=a.length;break;case\"deletion\":e+=a.length;break;case\"insertion\":r+=a.length;break;default:je.assertNever(s);break}if(e!==t.header.original.length||r!==t.header.patched.length){let s=a=>a<0?a:`+${a}`;throw new Error(`hunk header integrity check failed (expected @@ ${s(t.header.original.length)} ${s(t.header.patched.length)} @@, got @@ ${s(e)} ${s(r)} @@)`)}}Ve();bt();var O1=class extends Error{constructor(r,s){super(`Cannot apply hunk #${r+1}`);this.hunk=s}};async function L1(t,e,r){let s=await t.lstatPromise(e),a=await r();typeof a<\"u\"&&(e=a),await t.lutimesPromise(e,s.atime,s.mtime)}async function FL(t,{baseFs:e=new Yn,dryRun:r=!1,version:s=null}={}){for(let a of t)if(!(a.semverExclusivity!==null&&s!==null&&!Or.satisfiesWithPrereleases(s,a.semverExclusivity)))switch(a.type){case\"file deletion\":if(r){if(!e.existsSync(a.path))throw new Error(`Trying to delete a file that doesn't exist: ${a.path}`)}else await L1(e,K.dirname(a.path),async()=>{await e.unlinkPromise(a.path)});break;case\"rename\":if(r){if(!e.existsSync(a.fromPath))throw new Error(`Trying to move a file that doesn't exist: ${a.fromPath}`)}else await L1(e,K.dirname(a.fromPath),async()=>{await L1(e,K.dirname(a.toPath),async()=>{await L1(e,a.fromPath,async()=>(await e.movePromise(a.fromPath,a.toPath),a.toPath))})});break;case\"file creation\":if(r){if(e.existsSync(a.path))throw new Error(`Trying to create a file that already exists: ${a.path}`)}else{let n=a.hunk?a.hunk.parts[0].lines.join(`\n`)+(a.hunk.parts[0].noNewlineAtEndOfFile?\"\":`\n`):\"\";await e.mkdirpPromise(K.dirname(a.path),{chmod:493,utimes:[fi.SAFE_TIME,fi.SAFE_TIME]}),await e.writeFilePromise(a.path,n,{mode:a.mode}),await e.utimesPromise(a.path,fi.SAFE_TIME,fi.SAFE_TIME)}break;case\"patch\":await L1(e,a.path,async()=>{await djt(a,{baseFs:e,dryRun:r})});break;case\"mode change\":{let c=(await e.statPromise(a.path)).mode;if(nOe(a.newMode)!==nOe(c))continue;await L1(e,a.path,async()=>{await e.chmodPromise(a.path,a.newMode)})}break;default:je.assertNever(a);break}}function nOe(t){return(t&64)>0}function iOe(t){return t.replace(/\\s+$/,\"\")}function gjt(t,e){return iOe(t)===iOe(e)}async function djt({hunks:t,path:e},{baseFs:r,dryRun:s=!1}){let a=await r.statSync(e).mode,c=(await r.readFileSync(e,\"utf8\")).split(/\\n/),f=[],p=0,h=0;for(let C of t){let S=Math.max(h,C.header.patched.start+p),P=Math.max(0,S-h),I=Math.max(0,c.length-S-C.header.original.length),R=Math.max(P,I),N=0,U=0,W=null;for(;N<=R;){if(N<=P&&(U=S-N,W=sOe(C,c,U),W!==null)){N=-N;break}if(N<=I&&(U=S+N,W=sOe(C,c,U),W!==null))break;N+=1}if(W===null)throw new O1(t.indexOf(C),C);f.push(W),p+=N,h=U+C.header.original.length}if(s)return;let E=0;for(let C of f)for(let S of C)switch(S.type){case\"splice\":{let P=S.index+E;c.splice(P,S.numToDelete,...S.linesToInsert),E+=S.linesToInsert.length-S.numToDelete}break;case\"pop\":c.pop();break;case\"push\":c.push(S.line);break;default:je.assertNever(S);break}await r.writeFilePromise(e,c.join(`\n`),{mode:a})}function sOe(t,e,r){let s=[];for(let a of t.parts)switch(a.type){case\"context\":case\"deletion\":{for(let n of a.lines){let c=e[r];if(c==null||!gjt(c,n))return null;r+=1}a.type===\"deletion\"&&(s.push({type:\"splice\",index:r-a.lines.length,numToDelete:a.lines.length,linesToInsert:[]}),a.noNewlineAtEndOfFile&&s.push({type:\"push\",line:\"\"}))}break;case\"insertion\":s.push({type:\"splice\",index:r,numToDelete:0,linesToInsert:a.lines}),a.noNewlineAtEndOfFile&&s.push({type:\"pop\"});break;default:je.assertNever(a.type);break}return s}var yjt=/^builtin<([^>]+)>$/;function M1(t,e){let{protocol:r,source:s,selector:a,params:n}=q.parseRange(t);if(r!==\"patch:\")throw new Error(\"Invalid patch range\");if(s===null)throw new Error(\"Patch locators must explicitly define their source\");let c=a?a.split(/&/).map(E=>ue.toPortablePath(E)):[],f=n&&typeof n.locator==\"string\"?q.parseLocator(n.locator):null,p=n&&typeof n.version==\"string\"?n.version:null,h=e(s);return{parentLocator:f,sourceItem:h,patchPaths:c,sourceVersion:p}}function NL(t){return t.range.startsWith(\"patch:\")}function Tg(t){return t.reference.startsWith(\"patch:\")}function lP(t){let{sourceItem:e,...r}=M1(t.range,q.parseDescriptor);return{...r,sourceDescriptor:e}}function cP(t){let{sourceItem:e,...r}=M1(t.reference,q.parseLocator);return{...r,sourceLocator:e}}function Ejt(t){let{sourceItem:e}=M1(t.range,q.parseDescriptor);return e}function Ijt(t){let{sourceItem:e}=M1(t.reference,q.parseLocator);return e}function dz(t){if(!NL(t))return t;let{sourceItem:e}=M1(t.range,q.parseDescriptor);return e}function OL(t){if(!Tg(t))return t;let{sourceItem:e}=M1(t.reference,q.parseLocator);return e}function oOe({parentLocator:t,sourceItem:e,patchPaths:r,sourceVersion:s,patchHash:a},n){let c=t!==null?{locator:q.stringifyLocator(t)}:{},f=typeof s<\"u\"?{version:s}:{},p=typeof a<\"u\"?{hash:a}:{};return q.makeRange({protocol:\"patch:\",source:n(e),selector:r.join(\"&\"),params:{...f,...p,...c}})}function LL(t,{parentLocator:e,sourceDescriptor:r,patchPaths:s}){return q.makeDescriptor(t,oOe({parentLocator:e,sourceItem:r,patchPaths:s},q.stringifyDescriptor))}function mz(t,{parentLocator:e,sourcePackage:r,patchPaths:s,patchHash:a}){return q.makeLocator(t,oOe({parentLocator:e,sourceItem:r,sourceVersion:r.version,patchPaths:s,patchHash:a},q.stringifyLocator))}function aOe({onAbsolute:t,onRelative:e,onProject:r,onBuiltin:s},a){let n=a.lastIndexOf(\"!\");n!==-1&&(a=a.slice(n+1));let c=a.match(yjt);return c!==null?s(c[1]):a.startsWith(\"~/\")?r(a.slice(2)):K.isAbsolute(a)?t(a):e(a)}function lOe(t){let e=t.lastIndexOf(\"!\");return{optional:(e!==-1?new Set(t.slice(0,e).split(/!/)):new Set).has(\"optional\")}}function yz(t){return aOe({onAbsolute:()=>!1,onRelative:()=>!0,onProject:()=>!1,onBuiltin:()=>!1},t)}async function uP(t,e,r){let s=t!==null?await r.fetcher.fetch(t,r):null,a=s&&s.localPath?{packageFs:new Sn(vt.root),prefixPath:K.relative(vt.root,s.localPath)}:s;s&&s!==a&&s.releaseFs&&s.releaseFs();let n=await je.releaseAfterUseAsync(async()=>await Promise.all(e.map(async c=>{let f=lOe(c),p=await aOe({onAbsolute:async h=>await le.readFilePromise(h,\"utf8\"),onRelative:async h=>{if(a===null)throw new Error(\"Assertion failed: The parent locator should have been fetched\");return await a.packageFs.readFilePromise(K.join(a.prefixPath,h),\"utf8\")},onProject:async h=>await le.readFilePromise(K.join(r.project.cwd,h),\"utf8\"),onBuiltin:async h=>await r.project.configuration.firstHook(E=>E.getBuiltinPatch,r.project,h)},c);return{...f,source:p}})));for(let c of n)typeof c.source==\"string\"&&(c.source=c.source.replace(/\\r\\n?/g,`\n`));return n}async function Ez(t,{cache:e,project:r}){let s=r.storedPackages.get(t.locatorHash);if(typeof s>\"u\")throw new Error(\"Assertion failed: Expected the package to be registered\");let a=OL(t),n=r.storedChecksums,c=new Yi,f=await le.mktempPromise(),p=K.join(f,\"source\"),h=K.join(f,\"user\"),E=K.join(f,\".yarn-patch.json\"),C=r.configuration.makeFetcher(),S=[];try{let P,I;if(t.locatorHash===a.locatorHash){let R=await C.fetch(t,{cache:e,project:r,fetcher:C,checksums:n,report:c});S.push(()=>R.releaseFs?.()),P=R,I=R}else P=await C.fetch(t,{cache:e,project:r,fetcher:C,checksums:n,report:c}),S.push(()=>P.releaseFs?.()),I=await C.fetch(t,{cache:e,project:r,fetcher:C,checksums:n,report:c}),S.push(()=>I.releaseFs?.());await Promise.all([le.copyPromise(p,P.prefixPath,{baseFs:P.packageFs}),le.copyPromise(h,I.prefixPath,{baseFs:I.packageFs}),le.writeJsonPromise(E,{locator:q.stringifyLocator(t),version:s.version})])}finally{for(let P of S)P()}return le.detachTemp(f),h}async function Iz(t,e){let r=ue.fromPortablePath(t).replace(/\\\\/g,\"/\"),s=ue.fromPortablePath(e).replace(/\\\\/g,\"/\"),{stdout:a,stderr:n}=await Gr.execvp(\"git\",[\"-c\",\"core.safecrlf=false\",\"diff\",\"--src-prefix=a/\",\"--dst-prefix=b/\",\"--ignore-cr-at-eol\",\"--full-index\",\"--no-index\",\"--no-renames\",\"--text\",r,s],{cwd:ue.toPortablePath(process.cwd()),env:{...process.env,GIT_CONFIG_NOSYSTEM:\"1\",HOME:\"\",XDG_CONFIG_HOME:\"\",USERPROFILE:\"\"}});if(n.length>0)throw new Error(`Unable to diff directories. Make sure you have a recent version of 'git' available in PATH.\nThe following error was reported by 'git':\n${n}`);let c=r.startsWith(\"/\")?f=>f.slice(1):f=>f;return a.replace(new RegExp(`(a|b)(${je.escapeRegExp(`/${c(r)}/`)})`,\"g\"),\"$1/\").replace(new RegExp(`(a|b)${je.escapeRegExp(`/${c(s)}/`)}`,\"g\"),\"$1/\").replace(new RegExp(je.escapeRegExp(`${r}/`),\"g\"),\"\").replace(new RegExp(je.escapeRegExp(`${s}/`),\"g\"),\"\")}function Cz(t,e){let r=[];for(let{source:s}of t){if(s===null)continue;let a=aP(s);for(let n of a){let{semverExclusivity:c,...f}=n;c!==null&&e!==null&&!Or.satisfiesWithPrereleases(e,c)||r.push(JSON.stringify(f))}}return Nn.makeHash(`${3}`,...r).slice(0,6)}Ve();function cOe(t,{configuration:e,report:r}){for(let s of t.parts)for(let a of s.lines)switch(s.type){case\"context\":r.reportInfo(null,`  ${he.pretty(e,a,\"grey\")}`);break;case\"deletion\":r.reportError(28,`- ${he.pretty(e,a,he.Type.REMOVED)}`);break;case\"insertion\":r.reportError(28,`+ ${he.pretty(e,a,he.Type.ADDED)}`);break;default:je.assertNever(s.type)}}var fP=class{supports(e,r){return!!Tg(e)}getLocalPath(e,r){return null}async fetch(e,r){let s=r.checksums.get(e.locatorHash)||null,[a,n,c]=await r.cache.fetchPackageFromCache(e,s,{onHit:()=>r.report.reportCacheHit(e),onMiss:()=>r.report.reportCacheMiss(e,`${q.prettyLocator(r.project.configuration,e)} can't be found in the cache and will be fetched from the disk`),loader:()=>this.patchPackage(e,r),...r.cacheOptions});return{packageFs:a,releaseFs:n,prefixPath:q.getIdentVendorPath(e),localPath:this.getLocalPath(e,r),checksum:c}}async patchPackage(e,r){let{parentLocator:s,sourceLocator:a,sourceVersion:n,patchPaths:c}=cP(e),f=await uP(s,c,r),p=await le.mktempPromise(),h=K.join(p,\"current.zip\"),E=await r.fetcher.fetch(a,r),C=q.getIdentVendorPath(e),S=new hs(h,{create:!0,level:r.project.configuration.get(\"compressionLevel\")});await je.releaseAfterUseAsync(async()=>{await S.copyPromise(C,E.prefixPath,{baseFs:E.packageFs,stableSort:!0})},E.releaseFs),S.saveAndClose();for(let{source:P,optional:I}of f){if(P===null)continue;let R=new hs(h,{level:r.project.configuration.get(\"compressionLevel\")}),N=new Sn(K.resolve(vt.root,C),{baseFs:R});try{await FL(aP(P),{baseFs:N,version:n})}catch(U){if(!(U instanceof O1))throw U;let W=r.project.configuration.get(\"enableInlineHunks\"),te=!W&&!I?\" (set enableInlineHunks for details)\":\"\",ie=`${q.prettyLocator(r.project.configuration,e)}: ${U.message}${te}`,Ae=ce=>{W&&cOe(U.hunk,{configuration:r.project.configuration,report:ce})};if(R.discardAndClose(),I){r.report.reportWarningOnce(66,ie,{reportExtra:Ae});continue}else throw new Yt(66,ie,Ae)}R.saveAndClose()}return new hs(h,{level:r.project.configuration.get(\"compressionLevel\")})}};Ve();var AP=class{supportsDescriptor(e,r){return!!NL(e)}supportsLocator(e,r){return!!Tg(e)}shouldPersistResolution(e,r){return!1}bindDescriptor(e,r,s){let{patchPaths:a}=lP(e);return a.every(n=>!yz(n))?e:q.bindDescriptor(e,{locator:q.stringifyLocator(r)})}getResolutionDependencies(e,r){let{sourceDescriptor:s}=lP(e);return{sourceDescriptor:r.project.configuration.normalizeDependency(s)}}async getCandidates(e,r,s){if(!s.fetchOptions)throw new Error(\"Assertion failed: This resolver cannot be used unless a fetcher is configured\");let{parentLocator:a,patchPaths:n}=lP(e),c=await uP(a,n,s.fetchOptions),f=r.sourceDescriptor;if(typeof f>\"u\")throw new Error(\"Assertion failed: The dependency should have been resolved\");let p=Cz(c,f.version);return[mz(e,{parentLocator:a,sourcePackage:f,patchPaths:n,patchHash:p})]}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){let{sourceLocator:s}=cP(e);return{...await r.resolver.resolve(s,r),...e}}};Ve();bt();Wt();var _1=class extends ut{constructor(){super(...arguments);this.save=ge.Boolean(\"-s,--save\",!1,{description:\"Add the patch to your resolution entries\"});this.patchFolder=ge.String()}static{this.paths=[[\"patch-commit\"]]}static{this.usage=ot.Usage({description:\"generate a patch out of a directory\",details:\"\\n      By default, this will print a patchfile on stdout based on the diff between the folder passed in and the original version of the package. Such file is suitable for consumption with the `patch:` protocol.\\n\\n      With the `-s,--save` option set, the patchfile won't be printed on stdout anymore and will instead be stored within a local file (by default kept within `.yarn/patches`, but configurable via the `patchFolder` setting). A `resolutions` entry will also be added to your top-level manifest, referencing the patched package via the `patch:` protocol.\\n\\n      Note that only folders generated by `yarn patch` are accepted as valid input for `yarn patch-commit`.\\n    \"})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState();let n=K.resolve(this.context.cwd,ue.toPortablePath(this.patchFolder)),c=K.join(n,\"../source\"),f=K.join(n,\"../.yarn-patch.json\");if(!le.existsSync(c))throw new nt(\"The argument folder didn't get created by 'yarn patch'\");let p=await Iz(c,n),h=await le.readJsonPromise(f),E=q.parseLocator(h.locator,!0);if(!s.storedPackages.has(E.locatorHash))throw new nt(\"No package found in the project for the given locator\");if(!this.save){this.context.stdout.write(p);return}let C=r.get(\"patchFolder\"),S=K.join(C,`${q.slugifyLocator(E)}.patch`);await le.mkdirPromise(C,{recursive:!0}),await le.writeFilePromise(S,p);let P=[],I=new Map;for(let R of s.storedPackages.values()){if(q.isVirtualLocator(R))continue;let N=R.dependencies.get(E.identHash);if(!N)continue;let U=q.ensureDevirtualizedDescriptor(N),W=dz(U),te=s.storedResolutions.get(W.descriptorHash);if(!te)throw new Error(\"Assertion failed: Expected the resolution to have been registered\");if(!s.storedPackages.get(te))throw new Error(\"Assertion failed: Expected the package to have been registered\");let Ae=s.tryWorkspaceByLocator(R);if(Ae)P.push(Ae);else{let ce=s.originalPackages.get(R.locatorHash);if(!ce)throw new Error(\"Assertion failed: Expected the original package to have been registered\");let me=ce.dependencies.get(N.identHash);if(!me)throw new Error(\"Assertion failed: Expected the original dependency to have been registered\");I.set(me.descriptorHash,me)}}for(let R of P)for(let N of Ht.hardDependencies){let U=R.manifest[N].get(E.identHash);if(!U)continue;let W=LL(U,{parentLocator:null,sourceDescriptor:q.convertLocatorToDescriptor(E),patchPaths:[K.join(Er.home,K.relative(s.cwd,S))]});R.manifest[N].set(U.identHash,W)}for(let R of I.values()){let N=LL(R,{parentLocator:null,sourceDescriptor:q.convertLocatorToDescriptor(E),patchPaths:[K.join(Er.home,K.relative(s.cwd,S))]});s.topLevelWorkspace.manifest.resolutions.push({pattern:{descriptor:{fullName:q.stringifyIdent(N),description:R.range}},reference:N.range})}await s.persist()}};Ve();bt();Wt();var U1=class extends ut{constructor(){super(...arguments);this.update=ge.Boolean(\"-u,--update\",!1,{description:\"Reapply local patches that already apply to this packages\"});this.json=ge.Boolean(\"--json\",!1,{description:\"Format the output as an NDJSON stream\"});this.package=ge.String()}static{this.paths=[[\"patch\"]]}static{this.usage=ot.Usage({description:\"prepare a package for patching\",details:\"\\n      This command will cause a package to be extracted in a temporary directory intended to be editable at will.\\n\\n      Once you're done with your changes, run `yarn patch-commit -s path` (with `path` being the temporary directory you received) to generate a patchfile and register it into your top-level manifest via the `patch:` protocol. Run `yarn patch-commit -h` for more details.\\n\\n      Calling the command when you already have a patch won't import it by default (in other words, the default behavior is to reset existing patches). However, adding the `-u,--update` flag will import any current patch.\\n    \"})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Jr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState();let c=q.parseLocator(this.package);if(c.reference===\"unknown\"){let f=je.mapAndFilter([...s.storedPackages.values()],p=>p.identHash!==c.identHash?je.mapAndFilter.skip:q.isVirtualLocator(p)?je.mapAndFilter.skip:Tg(p)!==this.update?je.mapAndFilter.skip:p);if(f.length===0)throw new nt(\"No package found in the project for the given locator\");if(f.length>1)throw new nt(`Multiple candidate packages found; explicitly choose one of them (use \\`yarn why <package>\\` to get more information as to who depends on them):\n${f.map(p=>`\n- ${q.prettyLocator(r,p)}`).join(\"\")}`);c=f[0]}if(!s.storedPackages.has(c.locatorHash))throw new nt(\"No package found in the project for the given locator\");await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout},async f=>{let p=OL(c),h=await Ez(c,{cache:n,project:s});f.reportJson({locator:q.stringifyLocator(p),path:ue.fromPortablePath(h)});let E=this.update?\" along with its current modifications\":\"\";f.reportInfo(0,`Package ${q.prettyLocator(r,p)} got extracted with success${E}!`),f.reportInfo(0,`You can now edit the following folder: ${he.pretty(r,ue.fromPortablePath(h),\"magenta\")}`),f.reportInfo(0,`Once you are done run ${he.pretty(r,`yarn patch-commit -s ${process.platform===\"win32\"?'\"':\"\"}${ue.fromPortablePath(h)}${process.platform===\"win32\"?'\"':\"\"}`,\"cyan\")} and Yarn will store a patchfile based on your changes.`)})}};var Cjt={configuration:{enableInlineHunks:{description:\"If true, the installs will print unmatched patch hunks\",type:\"BOOLEAN\",default:!1},patchFolder:{description:\"Folder where the patch files must be written\",type:\"ABSOLUTE_PATH\",default:\"./.yarn/patches\"}},commands:[_1,U1],fetchers:[fP],resolvers:[AP]},wjt=Cjt;var Sz={};Vt(Sz,{PnpmLinker:()=>pP,default:()=>Pjt});Ve();bt();Wt();var pP=class{getCustomDataKey(){return JSON.stringify({name:\"PnpmLinker\",version:3})}supportsPackage(e,r){return this.isEnabled(r)}async findPackageLocation(e,r){if(!this.isEnabled(r))throw new Error(\"Assertion failed: Expected the pnpm linker to be enabled\");let s=this.getCustomDataKey(),a=r.project.linkersCustomData.get(s);if(!a)throw new nt(`The project in ${he.pretty(r.project.configuration,`${r.project.cwd}/package.json`,he.Type.PATH)} doesn't seem to have been installed - running an install there might help`);let n=a.pathsByLocator.get(e.locatorHash);if(typeof n>\"u\")throw new nt(`Couldn't find ${q.prettyLocator(r.project.configuration,e)} in the currently installed pnpm map - running an install might help`);return n.packageLocation}async findPackageLocator(e,r){if(!this.isEnabled(r))return null;let s=this.getCustomDataKey(),a=r.project.linkersCustomData.get(s);if(!a)throw new nt(`The project in ${he.pretty(r.project.configuration,`${r.project.cwd}/package.json`,he.Type.PATH)} doesn't seem to have been installed - running an install there might help`);let n=e.match(/(^.*\\/node_modules\\/(@[^/]*\\/)?[^/]+)(\\/.*$)/);if(n){let p=a.locatorByPath.get(n[1]);if(p)return p}let c=e,f=e;do{f=c,c=K.dirname(f);let p=a.locatorByPath.get(f);if(p)return p}while(c!==f);return null}makeInstaller(e){return new Bz(e)}isEnabled(e){return e.project.configuration.get(\"nodeLinker\")===\"pnpm\"}},Bz=class{constructor(e){this.opts=e;this.asyncActions=new je.AsyncActions(10);this.customData={pathsByLocator:new Map,locatorByPath:new Map};this.indexFolderPromise=ax(le,{indexPath:K.join(e.project.configuration.get(\"globalFolder\"),\"index\")})}attachCustomData(e){}async installPackage(e,r,s){switch(e.linkType){case\"SOFT\":return this.installPackageSoft(e,r,s);case\"HARD\":return this.installPackageHard(e,r,s)}throw new Error(\"Assertion failed: Unsupported package link type\")}async installPackageSoft(e,r,s){let a=K.resolve(r.packageFs.getRealPath(),r.prefixPath),n=this.opts.project.tryWorkspaceByLocator(e)?K.join(a,Er.nodeModules):null;return this.customData.pathsByLocator.set(e.locatorHash,{packageLocation:a,dependenciesLocation:n}),{packageLocation:a,buildRequest:null}}async installPackageHard(e,r,s){let a=vjt(e,{project:this.opts.project}),n=a.packageLocation;this.customData.locatorByPath.set(n,q.stringifyLocator(e)),this.customData.pathsByLocator.set(e.locatorHash,a),s.holdFetchResult(this.asyncActions.set(e.locatorHash,async()=>{await le.mkdirPromise(n,{recursive:!0}),await le.copyPromise(n,r.prefixPath,{baseFs:r.packageFs,overwrite:!1,linkStrategy:{type:\"HardlinkFromIndex\",indexPath:await this.indexFolderPromise,autoRepair:!0}})}));let f=q.isVirtualLocator(e)?q.devirtualizeLocator(e):e,p={manifest:await Ht.tryFind(r.prefixPath,{baseFs:r.packageFs})??new Ht,misc:{hasBindingGyp:mA.hasBindingGyp(r)}},h=this.opts.project.getDependencyMeta(f,e.version),E=mA.extractBuildRequest(e,p,h,{configuration:this.opts.project.configuration});return{packageLocation:n,buildRequest:E}}async attachInternalDependencies(e,r){if(this.opts.project.configuration.get(\"nodeLinker\")!==\"pnpm\"||!uOe(e,{project:this.opts.project}))return;let s=this.customData.pathsByLocator.get(e.locatorHash);if(typeof s>\"u\")throw new Error(`Assertion failed: Expected the package to have been registered (${q.stringifyLocator(e)})`);let{dependenciesLocation:a}=s;a&&this.asyncActions.reduce(e.locatorHash,async n=>{await le.mkdirPromise(a,{recursive:!0});let c=await Sjt(a),f=new Map(c),p=[n],h=(C,S)=>{let P=S;uOe(S,{project:this.opts.project})||(this.opts.report.reportWarningOnce(0,\"The pnpm linker doesn't support providing different versions to workspaces' peer dependencies\"),P=q.devirtualizeLocator(S));let I=this.customData.pathsByLocator.get(P.locatorHash);if(typeof I>\"u\")throw new Error(`Assertion failed: Expected the package to have been registered (${q.stringifyLocator(S)})`);let R=q.stringifyIdent(C),N=K.join(a,R),U=K.relative(K.dirname(N),I.packageLocation),W=f.get(R);f.delete(R),p.push(Promise.resolve().then(async()=>{if(W){if(W.isSymbolicLink()&&await le.readlinkPromise(N)===U)return;await le.removePromise(N)}await le.mkdirpPromise(K.dirname(N)),process.platform==\"win32\"&&this.opts.project.configuration.get(\"winLinkType\")===\"junctions\"?await le.symlinkPromise(I.packageLocation,N,\"junction\"):await le.symlinkPromise(U,N)}))},E=!1;for(let[C,S]of r)C.identHash===e.identHash&&(E=!0),h(C,S);!E&&!this.opts.project.tryWorkspaceByLocator(e)&&h(q.convertLocatorToDescriptor(e),e),p.push(Djt(a,f)),await Promise.all(p)})}async attachExternalDependents(e,r){throw new Error(\"External dependencies haven't been implemented for the pnpm linker\")}async finalizeInstall(){let e=fOe(this.opts.project);if(this.opts.project.configuration.get(\"nodeLinker\")!==\"pnpm\")await le.removePromise(e);else{let r;try{r=new Set(await le.readdirPromise(e))}catch{r=new Set}for(let{dependenciesLocation:s}of this.customData.pathsByLocator.values()){if(!s)continue;let a=K.contains(e,s);if(a===null)continue;let[n]=a.split(K.sep);r.delete(n)}await Promise.all([...r].map(async s=>{await le.removePromise(K.join(e,s))}))}return await this.asyncActions.wait(),await vz(e),this.opts.project.configuration.get(\"nodeLinker\")!==\"node-modules\"&&await vz(Bjt(this.opts.project)),{customData:this.customData}}};function Bjt(t){return K.join(t.cwd,Er.nodeModules)}function fOe(t){return t.configuration.get(\"pnpmStoreFolder\")}function vjt(t,{project:e}){let r=q.slugifyLocator(t),s=fOe(e),a=K.join(s,r,\"package\"),n=K.join(s,r,Er.nodeModules);return{packageLocation:a,dependenciesLocation:n}}function uOe(t,{project:e}){return!q.isVirtualLocator(t)||!e.tryWorkspaceByLocator(t)}async function Sjt(t){let e=new Map,r=[];try{r=await le.readdirPromise(t,{withFileTypes:!0})}catch(s){if(s.code!==\"ENOENT\")throw s}try{for(let s of r)if(!s.name.startsWith(\".\"))if(s.name.startsWith(\"@\")){let a=await le.readdirPromise(K.join(t,s.name),{withFileTypes:!0});if(a.length===0)e.set(s.name,s);else for(let n of a)e.set(`${s.name}/${n.name}`,n)}else e.set(s.name,s)}catch(s){if(s.code!==\"ENOENT\")throw s}return e}async function Djt(t,e){let r=[],s=new Set;for(let a of e.keys()){r.push(le.removePromise(K.join(t,a)));let n=q.tryParseIdent(a)?.scope;n&&s.add(`@${n}`)}return Promise.all(r).then(()=>Promise.all([...s].map(a=>vz(K.join(t,a)))))}async function vz(t){try{await le.rmdirPromise(t)}catch(e){if(e.code!==\"ENOENT\"&&e.code!==\"ENOTEMPTY\")throw e}}var bjt={configuration:{pnpmStoreFolder:{description:\"By default, the store is stored in the 'node_modules/.store' of the project. Sometimes in CI scenario's it is convenient to store this in a different location so it can be cached and reused.\",type:\"ABSOLUTE_PATH\",default:\"./node_modules/.store\"}},linkers:[pP]},Pjt=bjt;var Tz={};Vt(Tz,{StageCommand:()=>H1,default:()=>_jt,stageUtils:()=>_L});Ve();bt();Wt();Ve();bt();var _L={};Vt(_L,{ActionType:()=>Dz,checkConsensus:()=>ML,expandDirectory:()=>xz,findConsensus:()=>kz,findVcsRoot:()=>bz,genCommitMessage:()=>Qz,getCommitPrefix:()=>AOe,isYarnFile:()=>Pz});bt();var Dz=(n=>(n[n.CREATE=0]=\"CREATE\",n[n.DELETE=1]=\"DELETE\",n[n.ADD=2]=\"ADD\",n[n.REMOVE=3]=\"REMOVE\",n[n.MODIFY=4]=\"MODIFY\",n))(Dz||{});async function bz(t,{marker:e}){do if(!le.existsSync(K.join(t,e)))t=K.dirname(t);else return t;while(t!==\"/\");return null}function Pz(t,{roots:e,names:r}){if(r.has(K.basename(t)))return!0;do if(!e.has(t))t=K.dirname(t);else return!0;while(t!==\"/\");return!1}function xz(t){let e=[],r=[t];for(;r.length>0;){let s=r.pop(),a=le.readdirSync(s);for(let n of a){let c=K.resolve(s,n);le.lstatSync(c).isDirectory()?r.push(c):e.push(c)}}return e}function ML(t,e){let r=0,s=0;for(let a of t)a!==\"wip\"&&(e.test(a)?r+=1:s+=1);return r>=s}function kz(t){let e=ML(t,/^(\\w\\(\\w+\\):\\s*)?\\w+s/),r=ML(t,/^(\\w\\(\\w+\\):\\s*)?[A-Z]/),s=ML(t,/^\\w\\(\\w+\\):/);return{useThirdPerson:e,useUpperCase:r,useComponent:s}}function AOe(t){return t.useComponent?\"chore(yarn): \":\"\"}var xjt=new Map([[0,\"create\"],[1,\"delete\"],[2,\"add\"],[3,\"remove\"],[4,\"update\"]]);function Qz(t,e){let r=AOe(t),s=[],a=e.slice().sort((n,c)=>n[0]-c[0]);for(;a.length>0;){let[n,c]=a.shift(),f=xjt.get(n);t.useUpperCase&&s.length===0&&(f=`${f[0].toUpperCase()}${f.slice(1)}`),t.useThirdPerson&&(f+=\"s\");let p=[c];for(;a.length>0&&a[0][0]===n;){let[,E]=a.shift();p.push(E)}p.sort();let h=p.shift();p.length===1?h+=\" (and one other)\":p.length>1&&(h+=` (and ${p.length} others)`),s.push(`${f} ${h}`)}return`${r}${s.join(\", \")}`}var kjt=\"Commit generated via `yarn stage`\",Qjt=11;async function pOe(t){let{code:e,stdout:r}=await Gr.execvp(\"git\",[\"log\",\"-1\",\"--pretty=format:%H\"],{cwd:t});return e===0?r.trim():null}async function Tjt(t,e){let r=[],s=e.filter(h=>K.basename(h.path)===\"package.json\");for(let{action:h,path:E}of s){let C=K.relative(t,E);if(h===4){let S=await pOe(t),{stdout:P}=await Gr.execvp(\"git\",[\"show\",`${S}:${C}`],{cwd:t,strict:!0}),I=await Ht.fromText(P),R=await Ht.fromFile(E),N=new Map([...R.dependencies,...R.devDependencies]),U=new Map([...I.dependencies,...I.devDependencies]);for(let[W,te]of U){let ie=q.stringifyIdent(te),Ae=N.get(W);Ae?Ae.range!==te.range&&r.push([4,`${ie} to ${Ae.range}`]):r.push([3,ie])}for(let[W,te]of N)U.has(W)||r.push([2,q.stringifyIdent(te)])}else if(h===0){let S=await Ht.fromFile(E);S.name?r.push([0,q.stringifyIdent(S.name)]):r.push([0,\"a package\"])}else if(h===1){let S=await pOe(t),{stdout:P}=await Gr.execvp(\"git\",[\"show\",`${S}:${C}`],{cwd:t,strict:!0}),I=await Ht.fromText(P);I.name?r.push([1,q.stringifyIdent(I.name)]):r.push([1,\"a package\"])}else throw new Error(\"Assertion failed: Unsupported action type\")}let{code:a,stdout:n}=await Gr.execvp(\"git\",[\"log\",`-${Qjt}`,\"--pretty=format:%s\"],{cwd:t}),c=a===0?n.split(/\\n/g).filter(h=>h!==\"\"):[],f=kz(c);return Qz(f,r)}var Rjt={0:[\" A \",\"?? \"],4:[\" M \"],1:[\" D \"]},Fjt={0:[\"A  \"],4:[\"M  \"],1:[\"D  \"]},hOe={async findRoot(t){return await bz(t,{marker:\".git\"})},async filterChanges(t,e,r,s){let{stdout:a}=await Gr.execvp(\"git\",[\"status\",\"-s\"],{cwd:t,strict:!0}),n=a.toString().split(/\\n/g),c=s?.staged?Fjt:Rjt;return[].concat(...n.map(p=>{if(p===\"\")return[];let h=p.slice(0,3),E=K.resolve(t,p.slice(3));if(!s?.staged&&h===\"?? \"&&p.endsWith(\"/\"))return xz(E).map(C=>({action:0,path:C}));{let S=[0,4,1].find(P=>c[P].includes(h));return S!==void 0?[{action:S,path:E}]:[]}})).filter(p=>Pz(p.path,{roots:e,names:r}))},async genCommitMessage(t,e){return await Tjt(t,e)},async makeStage(t,e){let r=e.map(s=>ue.fromPortablePath(s.path));await Gr.execvp(\"git\",[\"add\",\"--\",...r],{cwd:t,strict:!0})},async makeCommit(t,e,r){let s=e.map(a=>ue.fromPortablePath(a.path));await Gr.execvp(\"git\",[\"add\",\"-N\",\"--\",...s],{cwd:t,strict:!0}),await Gr.execvp(\"git\",[\"commit\",\"-m\",`${r}\n\n${kjt}\n`,\"--\",...s],{cwd:t,strict:!0})},async makeReset(t,e){let r=e.map(s=>ue.fromPortablePath(s.path));await Gr.execvp(\"git\",[\"reset\",\"HEAD\",\"--\",...r],{cwd:t,strict:!0})}};var Njt=[hOe],H1=class extends ut{constructor(){super(...arguments);this.commit=ge.Boolean(\"-c,--commit\",!1,{description:\"Commit the staged files\"});this.reset=ge.Boolean(\"-r,--reset\",!1,{description:\"Remove all files from the staging area\"});this.dryRun=ge.Boolean(\"-n,--dry-run\",!1,{description:\"Print the commit message and the list of modified files without staging / committing\"});this.update=ge.Boolean(\"-u,--update\",!1,{hidden:!0})}static{this.paths=[[\"stage\"]]}static{this.usage=ot.Usage({description:\"add all yarn files to your vcs\",details:\"\\n      This command will add to your staging area the files belonging to Yarn (typically any modified `package.json` and `.yarnrc.yml` files, but also linker-generated files, cache data, etc). It will take your ignore list into account, so the cache files won't be added if the cache is ignored in a `.gitignore` file (assuming you use Git).\\n\\n      Running `--reset` will instead remove them from the staging area (the changes will still be there, but won't be committed until you stage them back).\\n\\n      Since the staging area is a non-existent concept in Mercurial, Yarn will always create a new commit when running this command on Mercurial repositories. You can get this behavior when using Git by using the `--commit` flag which will directly create a commit.\\n    \",examples:[[\"Adds all modified project files to the staging area\",\"yarn stage\"],[\"Creates a new commit containing all modified project files\",\"yarn stage --commit\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s}=await Tt.find(r,this.context.cwd),{driver:a,root:n}=await Ojt(s.cwd),c=[r.get(\"cacheFolder\"),r.get(\"globalFolder\"),r.get(\"virtualFolder\"),r.get(\"yarnPath\")];await r.triggerHook(C=>C.populateYarnPaths,s,C=>{c.push(C)});let f=new Set;for(let C of c)for(let S of Ljt(n,C))f.add(S);let p=new Set([r.get(\"rcFilename\"),Er.lockfile,Er.manifest]),h=await a.filterChanges(n,f,p),E=await a.genCommitMessage(n,h);if(this.dryRun)if(this.commit)this.context.stdout.write(`${E}\n`);else for(let C of h)this.context.stdout.write(`${ue.fromPortablePath(C.path)}\n`);else if(this.reset){let C=await a.filterChanges(n,f,p,{staged:!0});C.length===0?this.context.stdout.write(\"No staged changes found!\"):await a.makeReset(n,C)}else h.length===0?this.context.stdout.write(\"No changes found!\"):this.commit?await a.makeCommit(n,h,E):(await a.makeStage(n,h),this.context.stdout.write(E))}};async function Ojt(t){let e=null,r=null;for(let s of Njt)if((r=await s.findRoot(t))!==null){e=s;break}if(e===null||r===null)throw new nt(\"No stage driver has been found for your current project\");return{driver:e,root:r}}function Ljt(t,e){let r=[];if(e===null)return r;for(;;){(e===t||e.startsWith(`${t}/`))&&r.push(e);let s;try{s=le.statSync(e)}catch{break}if(s.isSymbolicLink())e=K.resolve(K.dirname(e),le.readlinkSync(e));else break}return r}var Mjt={commands:[H1]},_jt=Mjt;var Rz={};Vt(Rz,{default:()=>Vjt});Ve();Ve();bt();var mOe=et(Ai());Ve();var gOe=et(G9()),Ujt=\"e8e1bd300d860104bb8c58453ffa1eb4\",Hjt=\"OFCNCOG2CU\",dOe=async(t,e)=>{let r=q.stringifyIdent(t),a=jjt(e).initIndex(\"npm-search\");try{return(await a.getObject(r,{attributesToRetrieve:[\"types\"]})).types?.ts===\"definitely-typed\"}catch{return!1}},jjt=t=>(0,gOe.default)(Hjt,Ujt,{requester:{async send(r){try{let s=await An.request(r.url,r.data||null,{configuration:t,headers:r.headers});return{content:s.body,isTimedOut:!1,status:s.statusCode}}catch(s){return{content:s.response.body,isTimedOut:!1,status:s.response.statusCode}}}}});var yOe=t=>t.scope?`${t.scope}__${t.name}`:`${t.name}`,qjt=async(t,e,r,s)=>{if(r.scope===\"types\")return;let{project:a}=t,{configuration:n}=a;if(!(n.get(\"tsEnableAutoTypes\")??(le.existsSync(K.join(t.cwd,\"tsconfig.json\"))||le.existsSync(K.join(a.cwd,\"tsconfig.json\")))))return;let f=n.makeResolver(),p={project:a,resolver:f,report:new Yi};if(!await dOe(r,n))return;let E=yOe(r),C=q.parseRange(r.range).selector;if(!Or.validRange(C)){let N=n.normalizeDependency(r),U=await f.getCandidates(N,{},p);C=q.parseRange(U[0].reference).selector}let S=mOe.default.coerce(C);if(S===null)return;let P=`${Xu.Modifier.CARET}${S.major}`,I=q.makeDescriptor(q.makeIdent(\"types\",E),P),R=je.mapAndFind(a.workspaces,N=>{let U=N.manifest.dependencies.get(r.identHash)?.descriptorHash,W=N.manifest.devDependencies.get(r.identHash)?.descriptorHash;if(U!==r.descriptorHash&&W!==r.descriptorHash)return je.mapAndFind.skip;let te=[];for(let ie of Ht.allDependencies){let Ae=N.manifest[ie].get(I.identHash);typeof Ae>\"u\"||te.push([ie,Ae])}return te.length===0?je.mapAndFind.skip:te});if(typeof R<\"u\")for(let[N,U]of R)t.manifest[N].set(U.identHash,U);else{try{let N=n.normalizeDependency(I);if((await f.getCandidates(N,{},p)).length===0)return}catch{return}t.manifest[Xu.Target.DEVELOPMENT].set(I.identHash,I)}},Gjt=async(t,e,r)=>{if(r.scope===\"types\")return;let{project:s}=t,{configuration:a}=s;if(!(a.get(\"tsEnableAutoTypes\")??(le.existsSync(K.join(t.cwd,\"tsconfig.json\"))||le.existsSync(K.join(s.cwd,\"tsconfig.json\")))))return;let c=yOe(r),f=q.makeIdent(\"types\",c);for(let p of Ht.allDependencies)typeof t.manifest[p].get(f.identHash)>\"u\"||t.manifest[p].delete(f.identHash)},Wjt=(t,e)=>{e.publishConfig&&e.publishConfig.typings&&(e.typings=e.publishConfig.typings),e.publishConfig&&e.publishConfig.types&&(e.types=e.publishConfig.types)},Yjt={configuration:{tsEnableAutoTypes:{description:\"Whether Yarn should auto-install @types/ dependencies on 'yarn add'\",type:\"BOOLEAN\",isNullable:!0,default:null}},hooks:{afterWorkspaceDependencyAddition:qjt,afterWorkspaceDependencyRemoval:Gjt,beforeWorkspacePacking:Wjt}},Vjt=Yjt;var Mz={};Vt(Mz,{VersionApplyCommand:()=>Y1,VersionCheckCommand:()=>V1,VersionCommand:()=>K1,default:()=>A6t,versionUtils:()=>W1});Ve();Ve();Wt();var W1={};Vt(W1,{Decision:()=>q1,applyPrerelease:()=>vOe,applyReleases:()=>Lz,applyStrategy:()=>HL,clearVersionFiles:()=>Fz,getUndecidedDependentWorkspaces:()=>gP,getUndecidedWorkspaces:()=>UL,openVersionFile:()=>G1,requireMoreDecisions:()=>c6t,resolveVersionFiles:()=>hP,suggestStrategy:()=>Oz,updateVersionFiles:()=>Nz,validateReleaseDecision:()=>j1});Ve();bt();Bc();Wt();var BOe=et(wOe()),TA=et(Ai()),l6t=/^(>=|[~^]|)(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(-(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(\\.(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\\+[0-9a-zA-Z-]+(\\.[0-9a-zA-Z-]+)*)?$/,q1=(c=>(c.UNDECIDED=\"undecided\",c.DECLINE=\"decline\",c.MAJOR=\"major\",c.MINOR=\"minor\",c.PATCH=\"patch\",c.PRERELEASE=\"prerelease\",c))(q1||{});function j1(t){let e=TA.default.valid(t);return e||je.validateEnum((0,BOe.default)(q1,\"UNDECIDED\"),t)}async function hP(t,{prerelease:e=null}={}){let r=new Map,s=t.configuration.get(\"deferredVersionFolder\");if(!le.existsSync(s))return r;let a=await le.readdirPromise(s);for(let n of a){if(!n.endsWith(\".yml\"))continue;let c=K.join(s,n),f=await le.readFilePromise(c,\"utf8\"),p=cs(f);for(let[h,E]of Object.entries(p.releases||{})){if(E===\"decline\")continue;let C=q.parseIdent(h),S=t.tryWorkspaceByIdent(C);if(S===null)throw new Error(`Assertion failed: Expected a release definition file to only reference existing workspaces (${K.basename(c)} references ${h})`);if(S.manifest.version===null)throw new Error(`Assertion failed: Expected the workspace to have a version (${q.prettyLocator(t.configuration,S.anchoredLocator)})`);let P=S.manifest.raw.stableVersion??S.manifest.version,I=r.get(S),R=HL(P,j1(E));if(R===null)throw new Error(`Assertion failed: Expected ${P} to support being bumped via strategy ${E}`);let N=typeof I<\"u\"?TA.default.gt(R,I)?R:I:R;r.set(S,N)}}return e&&(r=new Map([...r].map(([n,c])=>[n,vOe(c,{current:n.manifest.version,prerelease:e})]))),r}async function Fz(t){let e=t.configuration.get(\"deferredVersionFolder\");le.existsSync(e)&&await le.removePromise(e)}async function Nz(t,e){let r=new Set(e),s=t.configuration.get(\"deferredVersionFolder\");if(!le.existsSync(s))return;let a=await le.readdirPromise(s);for(let n of a){if(!n.endsWith(\".yml\"))continue;let c=K.join(s,n),f=await le.readFilePromise(c,\"utf8\"),p=cs(f),h=p?.releases;if(h){for(let E of Object.keys(h)){let C=q.parseIdent(E),S=t.tryWorkspaceByIdent(C);(S===null||r.has(S))&&delete p.releases[E]}Object.keys(p.releases).length>0?await le.changeFilePromise(c,il(new il.PreserveOrdering(p))):await le.unlinkPromise(c)}}}async function G1(t,{allowEmpty:e=!1}={}){let r=t.configuration;if(r.projectCwd===null)throw new nt(\"This command can only be run from within a Yarn project\");let s=await Qa.fetchRoot(r.projectCwd),a=s!==null?await Qa.fetchBase(s,{baseRefs:r.get(\"changesetBaseRefs\")}):null,n=s!==null?await Qa.fetchChangedFiles(s,{base:a.hash,project:t}):[],c=r.get(\"deferredVersionFolder\"),f=n.filter(P=>K.contains(c,P)!==null);if(f.length>1)throw new nt(`Your current branch contains multiple versioning files; this isn't supported:\n- ${f.map(P=>ue.fromPortablePath(P)).join(`\n- `)}`);let p=new Set(je.mapAndFilter(n,P=>{let I=t.tryWorkspaceByFilePath(P);return I===null?je.mapAndFilter.skip:I}));if(f.length===0&&p.size===0&&!e)return null;let h=f.length===1?f[0]:K.join(c,`${Nn.makeHash(Math.random().toString()).slice(0,8)}.yml`),E=le.existsSync(h)?await le.readFilePromise(h,\"utf8\"):\"{}\",C=cs(E),S=new Map;for(let P of C.declined||[]){let I=q.parseIdent(P),R=t.getWorkspaceByIdent(I);S.set(R,\"decline\")}for(let[P,I]of Object.entries(C.releases||{})){let R=q.parseIdent(P),N=t.getWorkspaceByIdent(R);S.set(N,j1(I))}return{project:t,root:s,baseHash:a!==null?a.hash:null,baseTitle:a!==null?a.title:null,changedFiles:new Set(n),changedWorkspaces:p,releaseRoots:new Set([...p].filter(P=>P.manifest.version!==null)),releases:S,async saveAll(){let P={},I=[],R=[];for(let N of t.workspaces){if(N.manifest.version===null)continue;let U=q.stringifyIdent(N.anchoredLocator),W=S.get(N);W===\"decline\"?I.push(U):typeof W<\"u\"?P[U]=j1(W):p.has(N)&&R.push(U)}await le.mkdirPromise(K.dirname(h),{recursive:!0}),await le.changeFilePromise(h,il(new il.PreserveOrdering({releases:Object.keys(P).length>0?P:void 0,declined:I.length>0?I:void 0,undecided:R.length>0?R:void 0})))}}}function c6t(t){return UL(t).size>0||gP(t).length>0}function UL(t){let e=new Set;for(let r of t.changedWorkspaces)r.manifest.version!==null&&(t.releases.has(r)||e.add(r));return e}function gP(t,{include:e=new Set}={}){let r=[],s=new Map(je.mapAndFilter([...t.releases],([n,c])=>c===\"decline\"?je.mapAndFilter.skip:[n.anchoredLocator.locatorHash,n])),a=new Map(je.mapAndFilter([...t.releases],([n,c])=>c!==\"decline\"?je.mapAndFilter.skip:[n.anchoredLocator.locatorHash,n]));for(let n of t.project.workspaces)if(!(!e.has(n)&&(a.has(n.anchoredLocator.locatorHash)||s.has(n.anchoredLocator.locatorHash)))&&n.manifest.version!==null)for(let c of Ht.hardDependencies)for(let f of n.manifest.getForScope(c).values()){let p=t.project.tryWorkspaceByDescriptor(f);p!==null&&s.has(p.anchoredLocator.locatorHash)&&r.push([n,p])}return r}function Oz(t,e){let r=TA.default.clean(e);for(let s of Object.values(q1))if(s!==\"undecided\"&&s!==\"decline\"&&TA.default.inc(t,s)===r)return s;return null}function HL(t,e){if(TA.default.valid(e))return e;if(t===null)throw new nt(`Cannot apply the release strategy \"${e}\" unless the workspace already has a valid version`);if(!TA.default.valid(t))throw new nt(`Cannot apply the release strategy \"${e}\" on a non-semver version (${t})`);let r=TA.default.inc(t,e);if(r===null)throw new nt(`Cannot apply the release strategy \"${e}\" on the specified version (${t})`);return r}function Lz(t,e,{report:r,exact:s}){let a=new Map;for(let n of t.workspaces)for(let c of Ht.allDependencies)for(let f of n.manifest[c].values()){let p=t.tryWorkspaceByDescriptor(f);if(p===null||!e.has(p))continue;je.getArrayWithDefault(a,p).push([n,c,f.identHash])}for(let[n,c]of e){let f=n.manifest.version;n.manifest.version=c,TA.default.prerelease(c)===null?delete n.manifest.raw.stableVersion:n.manifest.raw.stableVersion||(n.manifest.raw.stableVersion=f);let p=n.manifest.name!==null?q.stringifyIdent(n.manifest.name):null;r.reportInfo(0,`${q.prettyLocator(t.configuration,n.anchoredLocator)}: Bumped to ${c}`),r.reportJson({cwd:ue.fromPortablePath(n.cwd),ident:p,oldVersion:f,newVersion:c});let h=a.get(n);if(!(typeof h>\"u\"))for(let[E,C,S]of h){let P=E.manifest[C].get(S);if(typeof P>\"u\")throw new Error(\"Assertion failed: The dependency should have existed\");let I=P.range,R=!1;if(I.startsWith(Ei.protocol)&&(I=I.slice(Ei.protocol.length),R=!0,I===n.relativeCwd))continue;let N=I.match(l6t);if(!N){r.reportWarning(0,`Couldn't auto-upgrade range ${I} (in ${q.prettyLocator(t.configuration,E.anchoredLocator)})`);continue}let U=s?`${c}`:`${N[1]}${c}`;R&&(U=`${Ei.protocol}${U}`);let W=q.makeDescriptor(P,U);E.manifest[C].set(S,W)}}}var u6t=new Map([[\"%n\",{extract:t=>t.length>=1?[t[0],t.slice(1)]:null,generate:(t=0)=>`${t+1}`}]]);function vOe(t,{current:e,prerelease:r}){let s=new TA.default.SemVer(e),a=s.prerelease.slice(),n=[];s.prerelease=[],s.format()!==t&&(a.length=0);let c=!0,f=r.split(/\\./g);for(let p of f){let h=u6t.get(p);if(typeof h>\"u\")n.push(p),a[0]===p?a.shift():c=!1;else{let E=c?h.extract(a):null;E!==null&&typeof E[0]==\"number\"?(n.push(h.generate(E[0])),a=E[1]):(n.push(h.generate()),c=!1)}}return s.prerelease&&(s.prerelease=[]),`${t}-${n.join(\".\")}`}var Y1=class extends ut{constructor(){super(...arguments);this.all=ge.Boolean(\"--all\",!1,{description:\"Apply the deferred version changes on all workspaces\"});this.dryRun=ge.Boolean(\"--dry-run\",!1,{description:\"Print the versions without actually generating the package archive\"});this.prerelease=ge.String(\"--prerelease\",{description:\"Add a prerelease identifier to new versions\",tolerateBoolean:!0});this.exact=ge.Boolean(\"--exact\",!1,{description:\"Use the exact version of each package, removes any range. Useful for nightly releases where the range might match another version.\"});this.recursive=ge.Boolean(\"-R,--recursive\",{description:\"Release the transitive workspaces as well\"});this.json=ge.Boolean(\"--json\",!1,{description:\"Format the output as an NDJSON stream\"})}static{this.paths=[[\"version\",\"apply\"]]}static{this.usage=ot.Usage({category:\"Release-related commands\",description:\"apply all the deferred version bumps at once\",details:`\n      This command will apply the deferred version changes and remove their definitions from the repository.\n\n      Note that if \\`--prerelease\\` is set, the given prerelease identifier (by default \\`rc.%n\\`) will be used on all new versions and the version definitions will be kept as-is.\n\n      By default only the current workspace will be bumped, but you can configure this behavior by using one of:\n\n      - \\`--recursive\\` to also apply the version bump on its dependencies\n      - \\`--all\\` to apply the version bump on all packages in the repository\n\n      Note that this command will also update the \\`workspace:\\` references across all your local workspaces, thus ensuring that they keep referring to the same workspaces even after the version bump.\n    `,examples:[[\"Apply the version change to the local workspace\",\"yarn version apply\"],[\"Apply the version change to all the workspaces in the local workspace\",\"yarn version apply --all\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Jr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState({restoreResolutions:!1});let c=await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout},async f=>{let p=this.prerelease?typeof this.prerelease!=\"boolean\"?this.prerelease:\"rc.%n\":null,h=await hP(s,{prerelease:p}),E=new Map;if(this.all)E=h;else{let C=this.recursive?a.getRecursiveWorkspaceDependencies():[a];for(let S of C){let P=h.get(S);typeof P<\"u\"&&E.set(S,P)}}if(E.size===0){let C=h.size>0?\" Did you want to add --all?\":\"\";f.reportWarning(0,`The current workspace doesn't seem to require a version bump.${C}`);return}Lz(s,E,{report:f,exact:this.exact}),this.dryRun||(p||(this.all?await Fz(s):await Nz(s,[...E.keys()])),f.reportSeparator())});return this.dryRun||c.hasErrors()?c.exitCode():await s.installWithNewReport({json:this.json,stdout:this.context.stdout},{cache:n})}};Ve();bt();Wt();var jL=et(Ai());var V1=class extends ut{constructor(){super(...arguments);this.interactive=ge.Boolean(\"-i,--interactive\",{description:\"Open an interactive interface used to set version bumps\"})}static{this.paths=[[\"version\",\"check\"]]}static{this.usage=ot.Usage({category:\"Release-related commands\",description:\"check that all the relevant packages have been bumped\",details:\"\\n      **Warning:** This command currently requires Git.\\n\\n      This command will check that all the packages covered by the files listed in argument have been properly bumped or declined to bump.\\n\\n      In the case of a bump, the check will also cover transitive packages - meaning that should `Foo` be bumped, a package `Bar` depending on `Foo` will require a decision as to whether `Bar` will need to be bumped. This check doesn't cross packages that have declined to bump.\\n\\n      In case no arguments are passed to the function, the list of modified files will be generated by comparing the HEAD against `master`.\\n    \",examples:[[\"Check whether the modified packages need a bump\",\"yarn version check\"]]})}async execute(){return this.interactive?await this.executeInteractive():await this.executeStandard()}async executeInteractive(){iw(this.context);let{Gem:r}=await Promise.resolve().then(()=>(YF(),cY)),{ScrollableItems:s}=await Promise.resolve().then(()=>(zF(),JF)),{FocusRequest:a}=await Promise.resolve().then(()=>(fY(),PPe)),{useListInput:n}=await Promise.resolve().then(()=>(KF(),xPe)),{renderForm:c}=await Promise.resolve().then(()=>(eN(),$F)),{Box:f,Text:p}=await Promise.resolve().then(()=>et(Vc())),{default:h,useCallback:E,useState:C}=await Promise.resolve().then(()=>et(hn())),S=await ze.find(this.context.cwd,this.context.plugins),{project:P,workspace:I}=await Tt.find(S,this.context.cwd);if(!I)throw new ar(P.cwd,this.context.cwd);await P.restoreInstallState();let R=await G1(P);if(R===null||R.releaseRoots.size===0)return 0;if(R.root===null)throw new nt(\"This command can only be run on Git repositories\");let N=()=>h.createElement(f,{flexDirection:\"row\",paddingBottom:1},h.createElement(f,{flexDirection:\"column\",width:60},h.createElement(f,null,h.createElement(p,null,\"Press \",h.createElement(p,{bold:!0,color:\"cyanBright\"},\"<up>\"),\"/\",h.createElement(p,{bold:!0,color:\"cyanBright\"},\"<down>\"),\" to select workspaces.\")),h.createElement(f,null,h.createElement(p,null,\"Press \",h.createElement(p,{bold:!0,color:\"cyanBright\"},\"<left>\"),\"/\",h.createElement(p,{bold:!0,color:\"cyanBright\"},\"<right>\"),\" to select release strategies.\"))),h.createElement(f,{flexDirection:\"column\"},h.createElement(f,{marginLeft:1},h.createElement(p,null,\"Press \",h.createElement(p,{bold:!0,color:\"cyanBright\"},\"<enter>\"),\" to save.\")),h.createElement(f,{marginLeft:1},h.createElement(p,null,\"Press \",h.createElement(p,{bold:!0,color:\"cyanBright\"},\"<ctrl+c>\"),\" to abort.\")))),U=({workspace:me,active:pe,decision:Be,setDecision:Ce})=>{let g=me.manifest.raw.stableVersion??me.manifest.version;if(g===null)throw new Error(`Assertion failed: The version should have been set (${q.prettyLocator(S,me.anchoredLocator)})`);if(jL.default.prerelease(g)!==null)throw new Error(`Assertion failed: Prerelease identifiers shouldn't be found (${g})`);let we=[\"undecided\",\"decline\",\"patch\",\"minor\",\"major\"];n(Be,we,{active:pe,minus:\"left\",plus:\"right\",set:Ce});let ye=Be===\"undecided\"?h.createElement(p,{color:\"yellow\"},g):Be===\"decline\"?h.createElement(p,{color:\"green\"},g):h.createElement(p,null,h.createElement(p,{color:\"magenta\"},g),\" \\u2192 \",h.createElement(p,{color:\"green\"},jL.default.valid(Be)?Be:jL.default.inc(g,Be)));return h.createElement(f,{flexDirection:\"column\"},h.createElement(f,null,h.createElement(p,null,q.prettyLocator(S,me.anchoredLocator),\" - \",ye)),h.createElement(f,null,we.map(fe=>h.createElement(f,{key:fe,paddingLeft:2},h.createElement(p,null,h.createElement(r,{active:fe===Be}),\" \",fe)))))},W=me=>{let pe=new Set(R.releaseRoots),Be=new Map([...me].filter(([Ce])=>pe.has(Ce)));for(;;){let Ce=gP({project:R.project,releases:Be}),g=!1;if(Ce.length>0){for(let[we]of Ce)if(!pe.has(we)){pe.add(we),g=!0;let ye=me.get(we);typeof ye<\"u\"&&Be.set(we,ye)}}if(!g)break}return{relevantWorkspaces:pe,relevantReleases:Be}},te=()=>{let[me,pe]=C(()=>new Map(R.releases)),Be=E((Ce,g)=>{let we=new Map(me);g!==\"undecided\"?we.set(Ce,g):we.delete(Ce);let{relevantReleases:ye}=W(we);pe(ye)},[me,pe]);return[me,Be]},ie=({workspaces:me,releases:pe})=>{let Be=[];Be.push(`${me.size} total`);let Ce=0,g=0;for(let we of me){let ye=pe.get(we);typeof ye>\"u\"?g+=1:ye!==\"decline\"&&(Ce+=1)}return Be.push(`${Ce} release${Ce===1?\"\":\"s\"}`),Be.push(`${g} remaining`),h.createElement(p,{color:\"yellow\"},Be.join(\", \"))},ce=await c(({useSubmit:me})=>{let[pe,Be]=te();me(pe);let{relevantWorkspaces:Ce}=W(pe),g=new Set([...Ce].filter(se=>!R.releaseRoots.has(se))),[we,ye]=C(0),fe=E(se=>{switch(se){case a.BEFORE:ye(we-1);break;case a.AFTER:ye(we+1);break}},[we,ye]);return h.createElement(f,{flexDirection:\"column\"},h.createElement(N,null),h.createElement(f,null,h.createElement(p,{wrap:\"wrap\"},\"The following files have been modified in your local checkout.\")),h.createElement(f,{flexDirection:\"column\",marginTop:1,paddingLeft:2},[...R.changedFiles].map(se=>h.createElement(f,{key:se},h.createElement(p,null,h.createElement(p,{color:\"grey\"},ue.fromPortablePath(R.root)),ue.sep,ue.relative(ue.fromPortablePath(R.root),ue.fromPortablePath(se)))))),R.releaseRoots.size>0&&h.createElement(h.Fragment,null,h.createElement(f,{marginTop:1},h.createElement(p,{wrap:\"wrap\"},\"Because of those files having been modified, the following workspaces may need to be released again (note that private workspaces are also shown here, because even though they won't be published, releasing them will allow us to flag their dependents for potential re-release):\")),g.size>3?h.createElement(f,{marginTop:1},h.createElement(ie,{workspaces:R.releaseRoots,releases:pe})):null,h.createElement(f,{marginTop:1,flexDirection:\"column\"},h.createElement(s,{active:we%2===0,radius:1,size:2,onFocusRequest:fe},[...R.releaseRoots].map(se=>h.createElement(U,{key:se.cwd,workspace:se,decision:pe.get(se)||\"undecided\",setDecision:X=>Be(se,X)}))))),g.size>0?h.createElement(h.Fragment,null,h.createElement(f,{marginTop:1},h.createElement(p,{wrap:\"wrap\"},\"The following workspaces depend on other workspaces that have been marked for release, and thus may need to be released as well:\")),h.createElement(f,null,h.createElement(p,null,\"(Press \",h.createElement(p,{bold:!0,color:\"cyanBright\"},\"<tab>\"),\" to move the focus between the workspace groups.)\")),g.size>5?h.createElement(f,{marginTop:1},h.createElement(ie,{workspaces:g,releases:pe})):null,h.createElement(f,{marginTop:1,flexDirection:\"column\"},h.createElement(s,{active:we%2===1,radius:2,size:2,onFocusRequest:fe},[...g].map(se=>h.createElement(U,{key:se.cwd,workspace:se,decision:pe.get(se)||\"undecided\",setDecision:X=>Be(se,X)}))))):null)},{versionFile:R},{stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr});if(typeof ce>\"u\")return 1;R.releases.clear();for(let[me,pe]of ce)R.releases.set(me,pe);await R.saveAll()}async executeStandard(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);return await s.restoreInstallState(),(await Ot.start({configuration:r,stdout:this.context.stdout},async c=>{let f=await G1(s);if(f===null||f.releaseRoots.size===0)return;if(f.root===null)throw new nt(\"This command can only be run on Git repositories\");if(c.reportInfo(0,`Your PR was started right after ${he.pretty(r,f.baseHash.slice(0,7),\"yellow\")} ${he.pretty(r,f.baseTitle,\"magenta\")}`),f.changedFiles.size>0){c.reportInfo(0,\"You have changed the following files since then:\"),c.reportSeparator();for(let S of f.changedFiles)c.reportInfo(null,`${he.pretty(r,ue.fromPortablePath(f.root),\"gray\")}${ue.sep}${ue.relative(ue.fromPortablePath(f.root),ue.fromPortablePath(S))}`)}let p=!1,h=!1,E=UL(f);if(E.size>0){p||c.reportSeparator();for(let S of E)c.reportError(0,`${q.prettyLocator(r,S.anchoredLocator)} has been modified but doesn't have a release strategy attached`);p=!0}let C=gP(f);for(let[S,P]of C)h||c.reportSeparator(),c.reportError(0,`${q.prettyLocator(r,S.anchoredLocator)} doesn't have a release strategy attached, but depends on ${q.prettyWorkspace(r,P)} which is planned for release.`),h=!0;(p||h)&&(c.reportSeparator(),c.reportInfo(0,\"This command detected that at least some workspaces have received modifications without explicit instructions as to how they had to be released (if needed).\"),c.reportInfo(0,\"To correct these errors, run `yarn version check --interactive` then follow the instructions.\"))})).exitCode()}};Ve();Wt();var qL=et(Ai());var K1=class extends ut{constructor(){super(...arguments);this.deferred=ge.Boolean(\"-d,--deferred\",{description:\"Prepare the version to be bumped during the next release cycle\"});this.immediate=ge.Boolean(\"-i,--immediate\",{description:\"Bump the version immediately\"});this.strategy=ge.String()}static{this.paths=[[\"version\"]]}static{this.usage=ot.Usage({category:\"Release-related commands\",description:\"apply a new version to the current package\",details:\"\\n      This command will bump the version number for the given package, following the specified strategy:\\n\\n      - If `major`, the first number from the semver range will be increased (`X.0.0`).\\n      - If `minor`, the second number from the semver range will be increased (`0.X.0`).\\n      - If `patch`, the third number from the semver range will be increased (`0.0.X`).\\n      - If prefixed by `pre` (`premajor`, ...), a `-0` suffix will be set (`0.0.0-0`).\\n      - If `prerelease`, the suffix will be increased (`0.0.0-X`); the third number from the semver range will also be increased if there was no suffix in the previous version.\\n      - If `decline`, the nonce will be increased for `yarn version check` to pass without version bump.\\n      - If a valid semver range, it will be used as new version.\\n      - If unspecified, Yarn will ask you for guidance.\\n\\n      For more information about the `--deferred` flag, consult our documentation (https://yarnpkg.com/features/release-workflow#deferred-versioning).\\n    \",examples:[[\"Immediately bump the version to the next major\",\"yarn version major\"],[\"Prepare the version to be bumped to the next major\",\"yarn version major --deferred\"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);let n=r.get(\"preferDeferredVersions\");this.deferred&&(n=!0),this.immediate&&(n=!1);let c=qL.default.valid(this.strategy),f=this.strategy===\"decline\",p;if(c)if(a.manifest.version!==null){let E=Oz(a.manifest.version,this.strategy);E!==null?p=E:p=this.strategy}else p=this.strategy;else{let E=a.manifest.version;if(!f){if(E===null)throw new nt(\"Can't bump the version if there wasn't a version to begin with - use 0.0.0 as initial version then run the command again.\");if(typeof E!=\"string\"||!qL.default.valid(E))throw new nt(`Can't bump the version (${E}) if it's not valid semver`)}p=j1(this.strategy)}if(!n){let C=(await hP(s)).get(a);if(typeof C<\"u\"&&p!==\"decline\"){let S=HL(a.manifest.version,p);if(qL.default.lt(S,C))throw new nt(`Can't bump the version to one that would be lower than the current deferred one (${C})`)}}let h=await G1(s,{allowEmpty:!0});return h.releases.set(a,p),await h.saveAll(),n?0:await this.cli.run([\"version\",\"apply\"])}};var f6t={configuration:{deferredVersionFolder:{description:\"Folder where are stored the versioning files\",type:\"ABSOLUTE_PATH\",default:\"./.yarn/versions\"},preferDeferredVersions:{description:\"If true, running `yarn version` will assume the `--deferred` flag unless `--immediate` is set\",type:\"BOOLEAN\",default:!1}},commands:[Y1,V1,K1]},A6t=f6t;var _z={};Vt(_z,{WorkspacesFocusCommand:()=>J1,WorkspacesForeachCommand:()=>Z1,default:()=>g6t});Ve();Ve();Wt();var J1=class extends ut{constructor(){super(...arguments);this.json=ge.Boolean(\"--json\",!1,{description:\"Format the output as an NDJSON stream\"});this.production=ge.Boolean(\"--production\",!1,{description:\"Only install regular dependencies by omitting dev dependencies\"});this.all=ge.Boolean(\"-A,--all\",!1,{description:\"Install the entire project\"});this.workspaces=ge.Rest()}static{this.paths=[[\"workspaces\",\"focus\"]]}static{this.usage=ot.Usage({category:\"Workspace-related commands\",description:\"install a single workspace and its dependencies\",details:\"\\n      This command will run an install as if the specified workspaces (and all other workspaces they depend on) were the only ones in the project. If no workspaces are explicitly listed, the active one will be assumed.\\n\\n      Note that this command is only very moderately useful when using zero-installs, since the cache will contain all the packages anyway - meaning that the only difference between a full install and a focused install would just be a few extra lines in the `.pnp.cjs` file, at the cost of introducing an extra complexity.\\n\\n      If the `-A,--all` flag is set, the entire project will be installed. Combine with `--production` to replicate the old `yarn install --production`.\\n    \"})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Jr.find(r);await s.restoreInstallState({restoreResolutions:!1});let c;if(this.all)c=new Set(s.workspaces);else if(this.workspaces.length===0){if(!a)throw new ar(s.cwd,this.context.cwd);c=new Set([a])}else c=new Set(this.workspaces.map(f=>s.getWorkspaceByIdent(q.parseIdent(f))));for(let f of c)for(let p of this.production?[\"dependencies\"]:Ht.hardDependencies)for(let h of f.manifest.getForScope(p).values()){let E=s.tryWorkspaceByDescriptor(h);E!==null&&c.add(E)}for(let f of s.workspaces)c.has(f)?this.production&&f.manifest.devDependencies.clear():(f.manifest.installConfig=f.manifest.installConfig||{},f.manifest.installConfig.selfReferences=!1,f.manifest.dependencies.clear(),f.manifest.devDependencies.clear(),f.manifest.peerDependencies.clear(),f.manifest.scripts.clear());return await s.installWithNewReport({json:this.json,stdout:this.context.stdout},{cache:n,persistProject:!1})}};Ve();Ve();Ve();Wt();var z1=et(Sa()),DOe=et(Od());Ul();var Z1=class extends ut{constructor(){super(...arguments);this.from=ge.Array(\"--from\",{description:\"An array of glob pattern idents or paths from which to base any recursion\"});this.all=ge.Boolean(\"-A,--all\",{description:\"Run the command on all workspaces of a project\"});this.recursive=ge.Boolean(\"-R,--recursive\",{description:\"Run the command on the current workspace and all of its recursive dependencies\"});this.worktree=ge.Boolean(\"-W,--worktree\",{description:\"Run the command on all workspaces of the current worktree\"});this.verbose=ge.Counter(\"-v,--verbose\",{description:\"Increase level of logging verbosity up to 2 times\"});this.parallel=ge.Boolean(\"-p,--parallel\",!1,{description:\"Run the commands in parallel\"});this.interlaced=ge.Boolean(\"-i,--interlaced\",!1,{description:\"Print the output of commands in real-time instead of buffering it\"});this.jobs=ge.String(\"-j,--jobs\",{description:\"The maximum number of parallel tasks that the execution will be limited to; or `unlimited`\",validator:mU([po([\"unlimited\"]),$2(dU(),[EU(),yU(1)])])});this.topological=ge.Boolean(\"-t,--topological\",!1,{description:\"Run the command after all workspaces it depends on (regular) have finished\"});this.topologicalDev=ge.Boolean(\"--topological-dev\",!1,{description:\"Run the command after all workspaces it depends on (regular + dev) have finished\"});this.include=ge.Array(\"--include\",[],{description:\"An array of glob pattern idents or paths; only matching workspaces will be traversed\"});this.exclude=ge.Array(\"--exclude\",[],{description:\"An array of glob pattern idents or paths; matching workspaces won't be traversed\"});this.publicOnly=ge.Boolean(\"--no-private\",{description:\"Avoid running the command on private workspaces\"});this.since=ge.String(\"--since\",{description:\"Only include workspaces that have been changed since the specified ref.\",tolerateBoolean:!0});this.dryRun=ge.Boolean(\"-n,--dry-run\",{description:\"Print the commands that would be run, without actually running them\"});this.commandName=ge.String();this.args=ge.Proxy()}static{this.paths=[[\"workspaces\",\"foreach\"]]}static{this.usage=ot.Usage({category:\"Workspace-related commands\",description:\"run a command on all workspaces\",details:\"\\n      This command will run a given sub-command on current and all its descendant workspaces. Various flags can alter the exact behavior of the command:\\n\\n      - If `-p,--parallel` is set, the commands will be ran in parallel; they'll by default be limited to a number of parallel tasks roughly equal to half your core number, but that can be overridden via `-j,--jobs`, or disabled by setting `-j unlimited`.\\n\\n      - If `-p,--parallel` and `-i,--interlaced` are both set, Yarn will print the lines from the output as it receives them. If `-i,--interlaced` wasn't set, it would instead buffer the output from each process and print the resulting buffers only after their source processes have exited.\\n\\n      - If `-t,--topological` is set, Yarn will only run the command after all workspaces that it depends on through the `dependencies` field have successfully finished executing. If `--topological-dev` is set, both the `dependencies` and `devDependencies` fields will be considered when figuring out the wait points.\\n\\n      - If `-A,--all` is set, Yarn will run the command on all the workspaces of a project.\\n\\n      - If `-R,--recursive` is set, Yarn will find workspaces to run the command on by recursively evaluating `dependencies` and `devDependencies` fields, instead of looking at the `workspaces` fields.\\n\\n      - If `-W,--worktree` is set, Yarn will find workspaces to run the command on by looking at the current worktree.\\n\\n      - If `--from` is set, Yarn will use the packages matching the 'from' glob as the starting point for any recursive search.\\n\\n      - If `--since` is set, Yarn will only run the command on workspaces that have been modified since the specified ref. By default Yarn will use the refs specified by the `changesetBaseRefs` configuration option.\\n\\n      - If `--dry-run` is set, Yarn will explain what it would do without actually doing anything.\\n\\n      - The command may apply to only some workspaces through the use of `--include` which acts as a whitelist. The `--exclude` flag will do the opposite and will be a list of packages that mustn't execute the script. Both flags accept glob patterns (if valid Idents and supported by [micromatch](https://github.com/micromatch/micromatch)). Make sure to escape the patterns, to prevent your own shell from trying to expand them. You can also use the `--no-private` flag to avoid running the command in private workspaces.\\n\\n      The `-v,--verbose` flag can be passed up to twice: once to prefix output lines with the originating workspace's name, and again to include start/finish/timing log lines. Maximum verbosity is enabled by default in terminal environments.\\n\\n      If the command is `run` and the script being run does not exist the child workspace will be skipped without error.\\n    \",examples:[[\"Publish all packages\",\"yarn workspaces foreach -A --no-private npm publish --tolerate-republish\"],[\"Run the build script on all descendant packages\",\"yarn workspaces foreach -A run build\"],[\"Run the build script on current and all descendant packages in parallel, building package dependencies first\",\"yarn workspaces foreach -Apt run build\"],[\"Run the build script on several packages and all their dependencies, building dependencies first\",\"yarn workspaces foreach -Rpt --from '{workspace-a,workspace-b}' run build\"]]})}static{this.schema=[tB(\"all\",Wf.Forbids,[\"from\",\"recursive\",\"since\",\"worktree\"],{missingIf:\"undefined\"}),IU([\"all\",\"recursive\",\"since\",\"worktree\"],{missingIf:\"undefined\"})]}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!this.all&&!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState();let n=this.cli.process([this.commandName,...this.args]),c=n.path.length===1&&n.path[0]===\"run\"&&typeof n.scriptName<\"u\"?n.scriptName:null;if(n.path.length===0)throw new nt(\"Invalid subcommand name for iteration - use the 'run' keyword if you wish to execute a script\");let f=Ce=>{this.dryRun&&this.context.stdout.write(`${Ce}\n`)},p=()=>{let Ce=this.from.map(g=>z1.default.matcher(g));return s.workspaces.filter(g=>{let we=q.stringifyIdent(g.anchoredLocator),ye=g.relativeCwd;return Ce.some(fe=>fe(we)||fe(ye))})},h=[];if(this.since?(f(\"Option --since is set; selecting the changed workspaces as root for workspace selection\"),h=Array.from(await Qa.fetchChangedWorkspaces({ref:this.since,project:s}))):this.from?(f(\"Option --from is set; selecting the specified workspaces\"),h=[...p()]):this.worktree?(f(\"Option --worktree is set; selecting the current workspace\"),h=[a]):this.recursive?(f(\"Option --recursive is set; selecting the current workspace\"),h=[a]):this.all&&(f(\"Option --all is set; selecting all workspaces\"),h=[...s.workspaces]),this.dryRun&&!this.all){for(let Ce of h)f(`\n- ${Ce.relativeCwd}\n  ${q.prettyLocator(r,Ce.anchoredLocator)}`);h.length>0&&f(\"\")}let E;if(this.recursive?this.since?(f(\"Option --recursive --since is set; recursively selecting all dependent workspaces\"),E=new Set(h.map(Ce=>[...Ce.getRecursiveWorkspaceDependents()]).flat())):(f(\"Option --recursive is set; recursively selecting all transitive dependencies\"),E=new Set(h.map(Ce=>[...Ce.getRecursiveWorkspaceDependencies()]).flat())):this.worktree?(f(\"Option --worktree is set; recursively selecting all nested workspaces\"),E=new Set(h.map(Ce=>[...Ce.getRecursiveWorkspaceChildren()]).flat())):E=null,E!==null&&(h=[...new Set([...h,...E])],this.dryRun))for(let Ce of E)f(`\n- ${Ce.relativeCwd}\n  ${q.prettyLocator(r,Ce.anchoredLocator)}`);let C=[],S=!1;if(c?.includes(\":\")){for(let Ce of s.workspaces)if(Ce.manifest.scripts.has(c)&&(S=!S,S===!1))break}for(let Ce of h){if(c&&!Ce.manifest.scripts.has(c)&&!S&&!(await In.getWorkspaceAccessibleBinaries(Ce)).has(c)){f(`Excluding ${Ce.relativeCwd} because it doesn't have a \"${c}\" script`);continue}if(!(c===r.env.npm_lifecycle_event&&Ce.cwd===a.cwd)){if(this.include.length>0&&!z1.default.isMatch(q.stringifyIdent(Ce.anchoredLocator),this.include)&&!z1.default.isMatch(Ce.relativeCwd,this.include)){f(`Excluding ${Ce.relativeCwd} because it doesn't match the --include filter`);continue}if(this.exclude.length>0&&(z1.default.isMatch(q.stringifyIdent(Ce.anchoredLocator),this.exclude)||z1.default.isMatch(Ce.relativeCwd,this.exclude))){f(`Excluding ${Ce.relativeCwd} because it matches the --exclude filter`);continue}if(this.publicOnly&&Ce.manifest.private===!0){f(`Excluding ${Ce.relativeCwd} because it's a private workspace and --no-private was set`);continue}C.push(Ce)}}if(this.dryRun)return 0;let P=this.verbose??(this.context.stdout.isTTY?1/0:0),I=P>0,R=P>1,N=this.parallel?this.jobs===\"unlimited\"?1/0:Number(this.jobs)||Math.ceil(ps.availableParallelism()/2):1,U=N===1?!1:this.parallel,W=U?this.interlaced:!0,te=(0,DOe.default)(N),ie=new Map,Ae=new Set,ce=0,me=null,pe=!1,Be=await Ot.start({configuration:r,stdout:this.context.stdout,includePrefix:!1},async Ce=>{let g=async(we,{commandIndex:ye})=>{if(pe)return-1;!U&&R&&ye>1&&Ce.reportSeparator();let fe=p6t(we,{configuration:r,label:I,commandIndex:ye}),[se,X]=SOe(Ce,{prefix:fe,interlaced:W}),[De,Re]=SOe(Ce,{prefix:fe,interlaced:W});try{R&&Ce.reportInfo(null,`${fe?`${fe} `:\"\"}Process started`);let dt=Date.now(),j=await this.cli.run([this.commandName,...this.args],{cwd:we.cwd,stdout:se,stderr:De})||0;se.end(),De.end(),await X,await Re;let rt=Date.now();if(R){let Fe=r.get(\"enableTimers\")?`, completed in ${he.pretty(r,rt-dt,he.Type.DURATION)}`:\"\";Ce.reportInfo(null,`${fe?`${fe} `:\"\"}Process exited (exit code ${j})${Fe}`)}return j===130&&(pe=!0,me=j),j}catch(dt){throw se.end(),De.end(),await X,await Re,dt}};for(let we of C)ie.set(we.anchoredLocator.locatorHash,we);for(;ie.size>0&&!Ce.hasErrors();){let we=[];for(let[X,De]of ie){if(Ae.has(De.anchoredDescriptor.descriptorHash))continue;let Re=!0;if(this.topological||this.topologicalDev){let dt=this.topologicalDev?new Map([...De.manifest.dependencies,...De.manifest.devDependencies]):De.manifest.dependencies;for(let j of dt.values()){let rt=s.tryWorkspaceByDescriptor(j);if(Re=rt===null||!ie.has(rt.anchoredLocator.locatorHash),!Re)break}}if(Re&&(Ae.add(De.anchoredDescriptor.descriptorHash),we.push(te(async()=>{let dt=await g(De,{commandIndex:++ce});return ie.delete(X),Ae.delete(De.anchoredDescriptor.descriptorHash),{workspace:De,exitCode:dt}})),!U))break}if(we.length===0){let X=Array.from(ie.values()).map(De=>q.prettyLocator(r,De.anchoredLocator)).join(\", \");Ce.reportError(3,`Dependency cycle detected (${X})`);return}let ye=await Promise.all(we);ye.forEach(({workspace:X,exitCode:De})=>{De!==0&&Ce.reportError(0,`The command failed in workspace ${q.prettyLocator(r,X.anchoredLocator)} with exit code ${De}`)});let se=ye.map(X=>X.exitCode).find(X=>X!==0);(this.topological||this.topologicalDev)&&typeof se<\"u\"&&Ce.reportError(0,\"The command failed for workspaces that are depended upon by other workspaces; can't satisfy the dependency graph\")}});return me!==null?me:Be.exitCode()}};function SOe(t,{prefix:e,interlaced:r}){let s=t.createStreamReporter(e),a=new je.DefaultStream;a.pipe(s,{end:!1}),a.on(\"finish\",()=>{s.end()});let n=new Promise(f=>{s.on(\"finish\",()=>{f(a.active)})});if(r)return[a,n];let c=new je.BufferStream;return c.pipe(a,{end:!1}),c.on(\"finish\",()=>{a.end()}),[c,n]}function p6t(t,{configuration:e,commandIndex:r,label:s}){if(!s)return null;let n=`[${q.stringifyIdent(t.anchoredLocator)}]:`,c=[\"#2E86AB\",\"#A23B72\",\"#F18F01\",\"#C73E1D\",\"#CCE2A3\"],f=c[r%c.length];return he.pretty(e,n,f)}var h6t={commands:[J1,Z1]},g6t=h6t;var tC=()=>({modules:new Map([[\"@yarnpkg/cli\",$v],[\"@yarnpkg/core\",Xv],[\"@yarnpkg/fslib\",U2],[\"@yarnpkg/libzip\",Iv],[\"@yarnpkg/parsers\",K2],[\"@yarnpkg/shell\",Dv],[\"clipanion\",oB],[\"semver\",d6t],[\"typanion\",Ia],[\"@yarnpkg/plugin-essentials\",Y5],[\"@yarnpkg/plugin-compat\",Z5],[\"@yarnpkg/plugin-constraints\",g9],[\"@yarnpkg/plugin-dlx\",d9],[\"@yarnpkg/plugin-exec\",E9],[\"@yarnpkg/plugin-file\",C9],[\"@yarnpkg/plugin-git\",W5],[\"@yarnpkg/plugin-github\",v9],[\"@yarnpkg/plugin-http\",S9],[\"@yarnpkg/plugin-init\",D9],[\"@yarnpkg/plugin-interactive-tools\",IY],[\"@yarnpkg/plugin-jsr\",wY],[\"@yarnpkg/plugin-link\",BY],[\"@yarnpkg/plugin-nm\",oV],[\"@yarnpkg/plugin-npm\",oz],[\"@yarnpkg/plugin-npm-cli\",gz],[\"@yarnpkg/plugin-pack\",$V],[\"@yarnpkg/plugin-patch\",wz],[\"@yarnpkg/plugin-pnp\",KY],[\"@yarnpkg/plugin-pnpm\",Sz],[\"@yarnpkg/plugin-stage\",Tz],[\"@yarnpkg/plugin-typescript\",Rz],[\"@yarnpkg/plugin-version\",Mz],[\"@yarnpkg/plugin-workspace-tools\",_z]]),plugins:new Set([\"@yarnpkg/plugin-essentials\",\"@yarnpkg/plugin-compat\",\"@yarnpkg/plugin-constraints\",\"@yarnpkg/plugin-dlx\",\"@yarnpkg/plugin-exec\",\"@yarnpkg/plugin-file\",\"@yarnpkg/plugin-git\",\"@yarnpkg/plugin-github\",\"@yarnpkg/plugin-http\",\"@yarnpkg/plugin-init\",\"@yarnpkg/plugin-interactive-tools\",\"@yarnpkg/plugin-jsr\",\"@yarnpkg/plugin-link\",\"@yarnpkg/plugin-nm\",\"@yarnpkg/plugin-npm\",\"@yarnpkg/plugin-npm-cli\",\"@yarnpkg/plugin-pack\",\"@yarnpkg/plugin-patch\",\"@yarnpkg/plugin-pnp\",\"@yarnpkg/plugin-pnpm\",\"@yarnpkg/plugin-stage\",\"@yarnpkg/plugin-typescript\",\"@yarnpkg/plugin-version\",\"@yarnpkg/plugin-workspace-tools\"])});function xOe({cwd:t,pluginConfiguration:e}){let r=new wa({binaryLabel:\"Yarn Package Manager\",binaryName:\"yarn\",binaryVersion:un??\"<unknown>\"});return Object.assign(r,{defaultContext:{...wa.defaultContext,cwd:t,plugins:e,quiet:!1,stdin:process.stdin,stdout:process.stdout,stderr:process.stderr}})}function m6t(t){if(je.parseOptionalBoolean(process.env.YARN_IGNORE_NODE))return!0;let r=process.versions.node,s=\">=18.12.0\";if(Or.satisfiesWithPrereleases(r,s))return!0;let a=new nt(`This tool requires a Node version compatible with ${s} (got ${r}). Upgrade Node, or set \\`YARN_IGNORE_NODE=1\\` in your environment.`);return wa.defaultContext.stdout.write(t.error(a)),!1}async function kOe({selfPath:t,pluginConfiguration:e}){return await ze.find(ue.toPortablePath(process.cwd()),e,{strict:!1,usePathCheck:t})}function y6t(t,e,{yarnPath:r}){if(!le.existsSync(r))return t.error(new Error(`The \"yarn-path\" option has been set, but the specified location doesn't exist (${r}).`)),1;process.on(\"SIGINT\",()=>{});let s={stdio:\"inherit\",env:{...process.env,YARN_IGNORE_PATH:\"1\"}};try{(0,bOe.execFileSync)(process.execPath,[ue.fromPortablePath(r),...e],s)}catch(a){return a.status??1}return 0}function E6t(t,e){let r=null,s=e;return e.length>=2&&e[0]===\"--cwd\"?(r=ue.toPortablePath(e[1]),s=e.slice(2)):e.length>=1&&e[0].startsWith(\"--cwd=\")?(r=ue.toPortablePath(e[0].slice(6)),s=e.slice(1)):e[0]===\"add\"&&e[e.length-2]===\"--cwd\"&&(r=ue.toPortablePath(e[e.length-1]),s=e.slice(0,e.length-2)),t.defaultContext.cwd=r!==null?K.resolve(r):K.cwd(),s}function I6t(t,{configuration:e}){if(!e.get(\"enableTelemetry\")||POe.isCI||!process.stdout.isTTY)return;ze.telemetry=new XI(e,\"puba9cdc10ec5790a2cf4969dd413a47270\");let s=/^@yarnpkg\\/plugin-(.*)$/;for(let a of e.plugins.keys())$I.has(a.match(s)?.[1]??\"\")&&ze.telemetry?.reportPluginName(a);t.binaryVersion&&ze.telemetry.reportVersion(t.binaryVersion)}function QOe(t,{configuration:e}){for(let r of e.plugins.values())for(let s of r.commands||[])t.register(s)}async function C6t(t,e,{selfPath:r,pluginConfiguration:s}){if(!m6t(t))return 1;let a=await kOe({selfPath:r,pluginConfiguration:s}),n=a.get(\"yarnPath\"),c=a.get(\"ignorePath\");if(n&&!c)return y6t(t,e,{yarnPath:n});delete process.env.YARN_IGNORE_PATH;let f=E6t(t,e);I6t(t,{configuration:a}),QOe(t,{configuration:a});let p=t.process(f,t.defaultContext);return p.help||ze.telemetry?.reportCommandName(p.path.join(\" \")),await t.run(p,t.defaultContext)}async function XCe({cwd:t=K.cwd(),pluginConfiguration:e=tC()}={}){let r=xOe({cwd:t,pluginConfiguration:e}),s=await kOe({pluginConfiguration:e,selfPath:null});return QOe(r,{configuration:s}),r}async function KR(t,{cwd:e=K.cwd(),selfPath:r,pluginConfiguration:s}){let a=xOe({cwd:e,pluginConfiguration:s});function n(){wa.defaultContext.stdout.write(`ERROR: Yarn is terminating due to an unexpected empty event loop.\nPlease report this issue at https://github.com/yarnpkg/berry/issues.`)}process.once(\"beforeExit\",n);try{process.exitCode=42,process.exitCode=await C6t(a,t,{selfPath:r,pluginConfiguration:s})}catch(c){wa.defaultContext.stdout.write(a.error(c)),process.exitCode=1}finally{process.off(\"beforeExit\",n),await le.rmtempPromise()}}KR(process.argv.slice(2),{cwd:K.cwd(),selfPath:ue.toPortablePath(ue.resolve(process.argv[1])),pluginConfiguration:tC()});})();\n/**\n  @license\n  Copyright (c) 2015, Rebecca Turner\n\n  Permission to use, copy, modify, and/or distribute this software for any\n  purpose with or without fee is hereby granted, provided that the above\n  copyright notice and this permission notice appear in all copies.\n\n  THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n  REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\n  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n  LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n  OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n  PERFORMANCE OF THIS SOFTWARE.\n */\n/**\n  @license\n  Copyright Node.js contributors. All rights reserved.\n\n  Permission is hereby granted, free of charge, to any person obtaining a copy\n  of this software and associated documentation files (the \"Software\"), to\n  deal in the Software without restriction, including without limitation the\n  rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n  sell copies of the Software, and to permit persons to whom the Software is\n  furnished to do so, subject to the following conditions:\n\n  The above copyright notice and this permission notice shall be included in\n  all copies or substantial portions of the Software.\n\n  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n  IN THE SOFTWARE.\n*/\n/**\n  @license\n  The MIT License (MIT)\n\n  Copyright (c) 2014 Blake Embrey (hello@blakeembrey.com)\n\n  Permission is hereby granted, free of charge, to any person obtaining a copy\n  of this software and associated documentation files (the \"Software\"), to deal\n  in the Software without restriction, including without limitation the rights\n  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n  copies of the Software, and to permit persons to whom the Software is\n  furnished to do so, subject to the following conditions:\n\n  The above copyright notice and this permission notice shall be included in\n  all copies or substantial portions of the Software.\n\n  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n  THE SOFTWARE.\n*/\n/**\n  @license\n  Copyright Joyent, Inc. and other Node contributors.\n\n  Permission is hereby granted, free of charge, to any person obtaining a\n  copy of this software and associated documentation files (the\n  \"Software\"), to deal in the Software without restriction, including\n  without limitation the rights to use, copy, modify, merge, publish,\n  distribute, sublicense, and/or sell copies of the Software, and to permit\n  persons to whom the Software is furnished to do so, subject to the\n  following conditions:\n\n  The above copyright notice and this permission notice shall be included\n  in all copies or substantial portions of the Software.\n\n  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n  NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n  DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n  OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n  USE OR OTHER DEALINGS IN THE SOFTWARE.\n*/\n/*! Bundled license information:\n\nis-number/index.js:\n  (*!\n   * is-number <https://github.com/jonschlinkert/is-number>\n   *\n   * Copyright (c) 2014-present, Jon Schlinkert.\n   * Released under the MIT License.\n   *)\n\nto-regex-range/index.js:\n  (*!\n   * to-regex-range <https://github.com/micromatch/to-regex-range>\n   *\n   * Copyright (c) 2015-present, Jon Schlinkert.\n   * Released under the MIT License.\n   *)\n\nfill-range/index.js:\n  (*!\n   * fill-range <https://github.com/jonschlinkert/fill-range>\n   *\n   * Copyright (c) 2014-present, Jon Schlinkert.\n   * Licensed under the MIT License.\n   *)\n\nis-extglob/index.js:\n  (*!\n   * is-extglob <https://github.com/jonschlinkert/is-extglob>\n   *\n   * Copyright (c) 2014-2016, Jon Schlinkert.\n   * Licensed under the MIT License.\n   *)\n\nis-glob/index.js:\n  (*!\n   * is-glob <https://github.com/jonschlinkert/is-glob>\n   *\n   * Copyright (c) 2014-2017, Jon Schlinkert.\n   * Released under the MIT License.\n   *)\n\nqueue-microtask/index.js:\n  (*! queue-microtask. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> *)\n\nrun-parallel/index.js:\n  (*! run-parallel. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> *)\n\ngit-url-parse/lib/index.js:\n  (*!\n   * buildToken\n   * Builds OAuth token prefix (helper function)\n   *\n   * @name buildToken\n   * @function\n   * @param {GitUrl} obj The parsed Git url object.\n   * @return {String} token prefix\n   *)\n\nobject-assign/index.js:\n  (*\n  object-assign\n  (c) Sindre Sorhus\n  @license MIT\n  *)\n\nreact/cjs/react.production.min.js:\n  (** @license React v17.0.2\n   * react.production.min.js\n   *\n   * Copyright (c) Facebook, Inc. and its affiliates.\n   *\n   * This source code is licensed under the MIT license found in the\n   * LICENSE file in the root directory of this source tree.\n   *)\n\nscheduler/cjs/scheduler.production.min.js:\n  (** @license React v0.20.2\n   * scheduler.production.min.js\n   *\n   * Copyright (c) Facebook, Inc. and its affiliates.\n   *\n   * This source code is licensed under the MIT license found in the\n   * LICENSE file in the root directory of this source tree.\n   *)\n\nreact-reconciler/cjs/react-reconciler.production.min.js:\n  (** @license React v0.26.2\n   * react-reconciler.production.min.js\n   *\n   * Copyright (c) Facebook, Inc. and its affiliates.\n   *\n   * This source code is licensed under the MIT license found in the\n   * LICENSE file in the root directory of this source tree.\n   *)\n\nis-windows/index.js:\n  (*!\n   * is-windows <https://github.com/jonschlinkert/is-windows>\n   *\n   * Copyright © 2015-2018, Jon Schlinkert.\n   * Released under the MIT License.\n   *)\n*/\n"
  },
  {
    "path": "bindings/javascript/.yarnrc.yml",
    "content": "nodeLinker: node-modules\n\nyarnPath: .yarn/releases/yarn-4.9.2.cjs\nenableHardenedMode: false\nsupportedArchitectures:\n  cpu:\n    - current\n    - wasm32\n"
  },
  {
    "path": "bindings/javascript/Cargo.toml",
    "content": "[package]\nname = \"turso_node\"\nversion.workspace = true\nauthors.workspace = true\nedition.workspace = true\nlicense.workspace = true\nrepository.workspace = true\ndescription = \"The Turso database library Node bindings\"\n\n[lints]\nworkspace = true\n\n[lib]\ncrate-type = [\"cdylib\", \"lib\"]\n\n[dependencies]\nturso_core = { workspace = true, features = [\"fts\"] }\nturso_parser = { workspace = true }\nnapi = { version = \"3.1.3\", default-features = false, features = [\"napi6\"] }\nnapi-derive = { version = \"3.1.1\", default-features = true }\ntracing-subscriber = { workspace = true, features = [\"env-filter\"] }\ntracing.workspace = true\nchrono = { workspace = true, default-features = false, features = [\"clock\"] }\n\n[features]\nbrowser = []\ntracing_release = [\"turso_core/tracing_release\"]\n[build-dependencies]\nnapi-build = \"2.2.3\"\n"
  },
  {
    "path": "bindings/javascript/Makefile",
    "content": "pack-native:\n\tnpm publish --dry-run && npm pack\npack-wasm:\n\tcp package.json package.native.json\n\tcp package.browser.json package.json\n\tnpm publish --dry-run && npm pack; cp package.native.json package.json\n\npublish-native:\n\tnpm publish --access public\npublish-wasm:\n\tcp package.json package.native.json\n\tcp package.browser.json package.json\n\tnpm publish --access public; cp package.native.json package.json\n\npublish-native-next:\n\tnpm publish --tag next --access public\npublish-wasm-next:\n\tcp package.json package.native.json\n\tcp package.browser.json package.json\n\tnpm publish --tag next --access public; cp package.native.json package.json\n\n"
  },
  {
    "path": "bindings/javascript/README.md",
    "content": "<p align=\"center\">\n  <h1 align=\"center\">Turso Database for JavaScript</h1>\n</p>\n\n<p align=\"center\">\n  <a title=\"JavaScript\" target=\"_blank\" href=\"https://www.npmjs.com/package/@tursodatabase/database\"><img alt=\"npm\" src=\"https://img.shields.io/npm/v/@tursodatabase/database\"></a>\n  <a title=\"MIT\" target=\"_blank\" href=\"https://github.com/tursodatabase/turso/blob/main/LICENSE.md\"><img src=\"http://img.shields.io/badge/license-MIT-orange.svg?style=flat-square\"></a>\n</p>\n<p align=\"center\">\n  <a title=\"Users Discord\" target=\"_blank\" href=\"https://tur.so/discord\"><img alt=\"Chat with other users of Turso on Discord\" src=\"https://img.shields.io/discord/933071162680958986?label=Discord&logo=Discord&style=social\"></a>\n</p>\n\n---\n\n## About\n\nThis package is the Turso in-memory database library for JavaScript.\n\n> **⚠️ Warning:** This software is in BETA. It may still contain bugs and unexpected behavior. Use caution with production data and ensure you have backups.\n\n## Features\n\n- **SQLite compatible:** SQLite query language and file format support ([status](https://github.com/tursodatabase/turso/blob/main/COMPAT.md)).\n- **In-process**: No network overhead, runs directly in your Node.js process\n- **TypeScript support**: Full TypeScript definitions included\n- **Cross-platform**: Supports Linux (x86 and arm64), macOS, Windows and browsers (through WebAssembly)\n\n## Installation\n\n```bash\nnpm install @tursodatabase/database\n```\n\n## Getting Started\n\n### In-Memory Database\n\n```javascript\nimport { connect } from '@tursodatabase/database';\n\n// Create an in-memory database\nconst db = await connect(':memory:');\n\n// Create a table\ndb.exec('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)');\n\n// Insert data\nconst insert = db.prepare('INSERT INTO users (name, email) VALUES (?, ?)');\ninsert.run('Alice', 'alice@example.com');\ninsert.run('Bob', 'bob@example.com');\n\n// Query data\nconst users = db.prepare('SELECT * FROM users').all();\nconsole.log(users);\n// Output: [\n//   { id: 1, name: 'Alice', email: 'alice@example.com' },\n//   { id: 2, name: 'Bob', email: 'bob@example.com' }\n// ]\n```\n\n### File-Based Database\n\n```javascript\nimport { connect } from '@tursodatabase/database';\n\n// Create or open a database file\nconst db = await connect('my-database.db');\n\n// Create a table\ndb.exec(`\n  CREATE TABLE IF NOT EXISTS posts (\n    id INTEGER PRIMARY KEY AUTOINCREMENT,\n    title TEXT NOT NULL,\n    content TEXT,\n    created_at DATETIME DEFAULT CURRENT_TIMESTAMP\n  )\n`);\n\n// Insert a post\nconst insertPost = db.prepare('INSERT INTO posts (title, content) VALUES (?, ?)');\nconst result = insertPost.run('Hello World', 'This is my first blog post!');\n\nconsole.log(`Inserted post with ID: ${result.lastInsertRowid}`);\n```\n\n### Transactions\n\n```javascript\nimport { connect } from '@tursodatabase/database';\n\nconst db = await connect('transactions.db');\n\n// Using transactions for atomic operations\nconst transaction = db.transaction((users) => {\n  const insert = db.prepare('INSERT INTO users (name, email) VALUES (?, ?)');\n  for (const user of users) {\n    insert.run(user.name, user.email);\n  }\n});\n\n// Execute transaction\ntransaction([\n  { name: 'Alice', email: 'alice@example.com' },\n  { name: 'Bob', email: 'bob@example.com' }\n]);\n```\n\n### WebAssembly Support\n\nTurso Database can run in browsers using WebAssembly. Check the `browser.js` and WASM artifacts for browser usage.\n\n## API Reference\n\nFor complete API documentation, see [JavaScript API Reference](https://github.com/tursodatabase/turso/blob/main/docs/javascript-api-reference.md).\n\n## Related Packages\n\n* The [@tursodatabase/serverless](https://www.npmjs.com/package/@tursodatabase/serverless) package provides a serverless driver with the same API.\n* The [@tursodatabase/sync](https://www.npmjs.com/package/@tursodatabase/sync) package provides bidirectional sync between a local Turso database and Turso Cloud. \n\n## License\n\nThis project is licensed under the [MIT license](https://github.com/tursodatabase/turso/blob/main/LICENSE.md).\n\n## Support\n\n- [GitHub Issues](https://github.com/tursodatabase/turso/issues)\n- [Documentation](https://docs.turso.tech)\n- [Discord Community](https://tur.so/discord)\n"
  },
  {
    "path": "bindings/javascript/build.rs",
    "content": "extern crate napi_build;\n\nfn main() {\n    napi_build::setup();\n}\n"
  },
  {
    "path": "bindings/javascript/docs/API.md",
    "content": "# class Database\n\nThe `Database` class represents a connection that can prepare and execute SQL statements.\n\n## Methods\n\n### new Database(path, [options]) ⇒ Database\n\nCreates a new database connection.\n\n| Param   | Type                | Description               |\n| ------- | ------------------- | ------------------------- |\n| path    | <code>string</code> | Path to the database file |\n| options | <code>object</code> | Options.                  |\n\nThe `path` parameter points to the SQLite database file to open. If the file pointed to by `path` does not exists, it will be created.\nTo open an in-memory database, please pass `:memory:` as the `path` parameter.\n\nThe function returns a `Database` object.\n\n### prepare(sql) ⇒ Statement\n\nPrepares a SQL statement for execution.\n\n| Param | Type                | Description                          |\n| ----- | ------------------- | ------------------------------------ |\n| sql   | <code>string</code> | The SQL statement string to prepare. |\n\nThe function returns a `Statement` object.\n\n### transaction(function) ⇒ function\n\nReturns a function that runs the given function in a transaction.\n\n| Param    | Type                  | Description                           |\n| -------- | --------------------- | ------------------------------------- |\n| function | <code>function</code> | The function to run in a transaction. |\n\n### pragma(string, [options]) ⇒ results\n\nExecutes the given PRAGMA and returns its results.\n\n| Param    | Type                  | Description           |\n| -------- | --------------------- | ----------------------|\n| source   | <code>string</code>   | Pragma to be executed |\n| options  | <code>object</code>   | Options.              |\n\nMost PRAGMA return a single value, the `simple: boolean` option is provided to return the first column of the first row.\n\n```js\ndb.pragma('cache_size = 32000');\nconsole.log(db.pragma('cache_size', { simple: true })); // => 32000\n```\n\n### backup(destination, [options]) ⇒ promise\n\nThis function is currently not supported.\n\n### serialize([options]) ⇒ Buffer\n\nThis function is currently not supported.\n\n### function(name, [options], function) ⇒ this\n\nThis function is currently not supported.\n\n### aggregate(name, options) ⇒ this\n\nThis function is currently not supported.\n\n### table(name, definition) ⇒ this\n\nThis function is currently not supported.\n\n### loadExtension(path, [entryPoint]) ⇒ this\n\nLoads a SQLite3 extension.\n\n| Param | Type                | Description                             |\n| ----- | ------------------- | --------------------------------------- |\n| path  | <code>string</code> | The path to the extension to be loaded. |\n\n### exec(sql) ⇒ this\n\nExecutes a SQL statement.\n\n| Param | Type                | Description                          |\n| ----- | ------------------- | ------------------------------------ |\n| sql   | <code>string</code> | The SQL statement string to execute. |\n\nThis can execute strings that contain multiple SQL statements.\n\n### interrupt() ⇒ this\n\nCancel ongoing operations and make them return at earliest opportunity.\n\n**Note:** This is an extension in libSQL and not available in `better-sqlite3`.\n\nThis function is currently not supported.\n\n### close() ⇒ this\n\nCloses the database connection.\n\n# class Statement\n\n## Methods\n\n### run([...bindParameters]) ⇒ object\n\nExecutes the SQL statement and (currently) returns an array with results.\n\n**Note:** It should return an info object.\n\n| Param          | Type                          | Description                                      |\n| -------------- | ----------------------------- | ------------------------------------------------ |\n| bindParameters | <code>array of objects</code> | The bind parameters for executing the statement. |\n\nThe returned info object contains two properties: `changes` that describes the number of modified rows and `info.lastInsertRowid` that represents the `rowid` of the last inserted row.\n\nThis function is currently not supported.\n\n### get([...bindParameters]) ⇒ row\n\nExecutes the SQL statement and returns the first row.\n\n| Param          | Type                          | Description                                      |\n| -------------- | ----------------------------- | ------------------------------------------------ |\n| bindParameters | <code>array of objects</code> | The bind parameters for executing the statement. |\n\n### all([...bindParameters]) ⇒ array of rows\n\nExecutes the SQL statement and returns an array of the resulting rows.\n\n| Param          | Type                          | Description                                      |\n| -------------- | ----------------------------- | ------------------------------------------------ |\n| bindParameters | <code>array of objects</code> | The bind parameters for executing the statement. |\n\n### iterate([...bindParameters]) ⇒ iterator\n\nExecutes the SQL statement and returns an iterator to the resulting rows.\n\n| Param          | Type                          | Description                                      |\n| -------------- | ----------------------------- | ------------------------------------------------ |\n| bindParameters | <code>array of objects</code> | The bind parameters for executing the statement. |\n\n### pluck([toggleState]) ⇒ this\n\nMakes the prepared statement only return the value of the first column of any rows that it retrieves.\n\n| Param     | Type                 | Description                                                                            |\n| --------- | -------------------- | -------------------------------------------------------------------------------------- |\n| pluckMode | <code>boolean</code> | Enable of disable pluck mode. If you don't pass the paramenter, pluck mode is enabled. |\n\n```js\nstmt.pluck(); // plucking ON\nstmt.pluck(true); // plucking ON\nstmt.pluck(false); // plucking OFF\n```\n\n> NOTE: When plucking is turned on, raw mode is turned off (they are mutually exclusive options).\n\n### expand([toggleState]) ⇒ this\n\nThis function is currently not supported.\n\n### raw([rawMode]) ⇒ this\n\nMakes the prepared statement return rows as arrays instead of objects.\n\n| Param   | Type                 | Description                                                                       |\n| ------- | -------------------- | --------------------------------------------------------------------------------- |\n| rawMode | <code>boolean</code> | Enable or disable raw mode. If you don't pass the parameter, raw mode is enabled. |\n\nThis function enables or disables raw mode. Prepared statements return objects by default, but if raw mode is enabled, the functions return arrays instead.\n\n```js\nstmt.raw(); // raw mode ON\nstmt.raw(true); // raw mode ON\nstmt.raw(false); // raw mode OFF\n```\n\n> NOTE: When raw mode is turned on, plucking is turned off (they are mutually exclusive options).\n\n### columns() ⇒ array of objects\n\nReturns the columns in the result set returned by this prepared statement.\n\nThis function is currently not supported.\n\n### bind([...bindParameters]) ⇒ this\n\n| Param          | Type                          | Description                                      |\n| -------------- | ----------------------------- | ------------------------------------------------ |\n| bindParameters | <code>array of objects</code> | The bind parameters for executing the statement. |\n\nBinds **permanently** the given parameters to the statement. After a statement's parameters are bound this way, you may no longer provide it with execution-specific (temporary) bound parameters.\n\n"
  },
  {
    "path": "bindings/javascript/docs/CONTRIBUTING.md",
    "content": "# Contributing\n\nSo you want to contribute to Limbo's binding for the ~second~ best language in the world? Awesome.\n\nFirst things first you'll need to install [napi-rs](https://napi.rs/), follow the instructions [here](https://napi.rs/docs/introduction/getting-started) although is highly recommended to use `yarn` with:\n\n```sh\nyarn global add @napi-rs/cli\n```\n\nRun `yarn build` to build our napi project and run `yarn test` to run our test suite, if nothing breaks you're ready to start!\n\n## API\n\nYou can check the API docs [here](./API.md), it aims to be fully compatible with [better-sqlite](https://github.com/WiseLibs/better-sqlite3/) and borrows some things from [libsql](https://github.com/tursodatabase/libsql-js). So if you find some incompability in behaviour and/or lack of functions/attributes, that's an issue and you should work on it for a great good :)\n\n## Code Structure\n\nThe Rust code for the bind is on [lib.rs](../src/lib.rs). It's exposed to JS users through [wrapper](../wrapper.js), where you can\nuse some JS' ~weirdness~ facilities, for instance, since Rust doesn't have variadic functions the wrapper enables us to \"normalize\" `bindParameters` into an array.\n\nAll tests should be within the [__test__](../__test__/) folder.\n\n# Before open a PR\n\nPlease be assured that:\n\n- Your fix/feature has a test checking the new behaviour;\n- Your Rust code is formatted with `cargo fmt`;\n- Your JavaScript code is formatted with `tsserver` (VSCode's default);\n- If applicable, update the [API docs](./API.md) to match the current implementation;\n\n"
  },
  {
    "path": "bindings/javascript/package.json",
    "content": "{\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"npm run build --workspaces\",\n    \"build:native\": \"npm run build --workspace=packages/common --workspace=packages/native\",\n    \"tsc-build\": \"npm run tsc-build --workspaces\",\n    \"test\": \"npm run test --workspaces\"\n  },\n  \"workspaces\": [\n    \"packages/common\",\n    \"packages/wasm-common\",\n    \"packages/native\",\n    \"packages/wasm\",\n    \"sync/packages/common\",\n    \"sync/packages/native\",\n    \"sync/packages/wasm\"\n  ],\n  \"version\": \"0.6.0-pre.4\"\n}\n"
  },
  {
    "path": "bindings/javascript/packages/common/README.md",
    "content": "## About\n\nThis package is the Turso embedded database common JS library which is shared between final builds for Node and Browser.\n\nDo not use this package directly - instead you must use `@tursodatabase/database` or `@tursodatabase/database-wasm`.\n\n> **⚠️ Warning:** This software is in BETA. It may still contain bugs and unexpected behavior. Use caution with production data and ensure you have backups.\n\n"
  },
  {
    "path": "bindings/javascript/packages/common/async-lock.ts",
    "content": "export class AsyncLock {\n    locked: boolean;\n    queue: any[];\n    constructor() {\n        this.locked = false;\n        this.queue = []\n    }\n    async acquire() {\n        if (!this.locked) {\n            this.locked = true;\n            return Promise.resolve();\n        } else {\n            const block = new Promise(resolve => { this.queue.push(resolve) });\n            return block;\n        }\n    }\n    release() {\n        if (this.locked == false) {\n            throw new Error(\"invalid state: lock was already unlocked\");\n        }\n        const item = this.queue.shift();\n        if (item != null) {\n            this.locked = true;\n            item();\n        } else {\n            this.locked = false;\n        }\n    }\n}"
  },
  {
    "path": "bindings/javascript/packages/common/bind.ts",
    "content": "// Bind parameters to a statement.\n//\n// This function is used to bind parameters to a statement. It supports both\n// named and positional parameters, and nested arrays.\n//\n// The `stmt` parameter is a statement object.\n// The `params` parameter is an array of parameters.\n//\n// The function returns void.\nexport function bindParams(stmt, params) {\n  const len = params?.length;\n  if (len === 0) {\n    return;\n  }\n  if (len === 1) {\n    const param = params[0];    \n    if (isPlainObject(param)) {  \n      bindNamedParams(stmt, param);\n      return;\n    }\n    if (Array.isArray(param)) {\n      bindPositionalParams(stmt, [param]);\n      return;\n    }\n    bindValue(stmt, 1, param);\n    return;\n  }\n  bindPositionalParams(stmt, params);\n}\n\n// Check if object is plain (no prototype chain)\nfunction isPlainObject(obj) {\n  if (!obj || typeof obj !== 'object') return false;\n  const proto = Object.getPrototypeOf(obj);\n  return proto === Object.prototype || proto === null;\n}\n\n// Handle named parameters\nfunction bindNamedParams(stmt, paramObj) {\n  const paramCount = stmt.parameterCount();\n  \n  for (let i = 1; i <= paramCount; i++) {\n    const paramName = stmt.parameterName(i);\n    if (paramName) {\n      const key = paramName.substring(1); // Remove ':' or '$' prefix\n      const value = paramObj[key];\n      \n      if (value !== undefined) {\n        bindValue(stmt, i, value);\n      }\n    }\n  }\n}\n\n// Handle positional parameters (including nested arrays)\nfunction bindPositionalParams(stmt, params) {\n  let bindIndex = 1;\n  for (let i = 0; i < params.length; i++) {\n    const param = params[i];    \n    if (Array.isArray(param)) {\n      for (let j = 0; j < param.length; j++) {\n        bindValue(stmt, bindIndex++, param[j]);\n      }\n    } else {\n      bindValue(stmt, bindIndex++, param);\n    }\n  }\n}\n\nfunction bindValue(stmt, index, value) {\n  stmt.bindAt(index, value);\n}\n"
  },
  {
    "path": "bindings/javascript/packages/common/compat.ts",
    "content": "import { bindParams } from \"./bind.js\";\nimport { SqliteError } from \"./sqlite-error.js\";\nimport { NativeDatabase, NativeStatement, STEP_IO, STEP_ROW, STEP_DONE } from \"./types.js\";\n\nconst convertibleErrorTypes = { TypeError };\nconst CONVERTIBLE_ERROR_PREFIX = \"[TURSO_CONVERT_TYPE]\";\n\nfunction convertError(err) {\n  if ((err.code ?? \"\").startsWith(CONVERTIBLE_ERROR_PREFIX)) {\n    return createErrorByName(\n      err.code.substring(CONVERTIBLE_ERROR_PREFIX.length),\n      err.message,\n    );\n  }\n\n  return new SqliteError(err.message, err.code, err.rawCode);\n}\n\nfunction createErrorByName(name, message) {\n  const ErrorConstructor = convertibleErrorTypes[name];\n  if (!ErrorConstructor) {\n    throw new Error(`unknown error type ${name} from Turso`);\n  }\n\n  return new ErrorConstructor(message);\n}\n\n/**\n * Database represents a connection that can prepare and execute SQL statements.\n */\nclass Database {\n  name: string;\n  readonly: boolean;\n  open: boolean;\n  memory: boolean;\n  inTransaction: boolean;\n\n  private db: NativeDatabase;\n  private _inTransaction: boolean = false;\n\n  /**\n   * Creates a new database connection. If the database file pointed to by `path` does not exists, it will be created.\n   *\n   * @constructor\n   * @param {string} path - Path to the database file.\n   * @param {Object} opts - Options for database behavior.\n   * @param {boolean} [opts.readonly=false] - Open the database in read-only mode.\n   * @param {boolean} [opts.fileMustExist=false] - If true, throws if database file does not exist.\n   * @param {number} [opts.timeout=0] - Timeout duration in milliseconds for database operations. Defaults to 0 (no timeout).\n   */\n  constructor(db: NativeDatabase) {\n    this.db = db;\n    this.db.connectSync();\n\n    Object.defineProperties(this, {\n      name: { get: () => this.db.path },\n      readonly: { get: () => this.db.readonly },\n      open: { get: () => this.db.open },\n      memory: { get: () => this.db.memory },\n      inTransaction: { get: () => this._inTransaction },\n    });\n  }\n\n  /**\n   * Prepares a SQL statement for execution.\n   *\n   * @param {string} sql - The SQL statement string to prepare.\n   */\n  prepare(sql) {\n    if (!this.open) {\n      throw new TypeError(\"The database connection is not open\");\n    }\n    if (!sql) {\n      throw new RangeError(\"The supplied SQL string contains no statements\");\n    }\n\n    try {\n      return new Statement(this.db.prepare(sql), this.db);\n    } catch (err) {\n      throw convertError(err);\n    }\n  }\n\n  /**\n   * Returns a function that executes the given function in a transaction.\n   *\n   * @param {function} fn - The function to wrap in a transaction.\n   */\n  transaction(fn) {\n    if (typeof fn !== \"function\")\n      throw new TypeError(\"Expected first argument to be a function\");\n\n    const db = this;\n    const wrapTxn = (mode) => {\n      return (...bindParameters) => {\n        db.exec(\"BEGIN \" + mode);\n        db._inTransaction = true;\n        try {\n          const result = fn(...bindParameters);\n          db.exec(\"COMMIT\");\n          db._inTransaction = false;\n          return result;\n        } catch (err) {\n          db.exec(\"ROLLBACK\");\n          db._inTransaction = false;\n          throw err;\n        }\n      };\n    };\n    const properties = {\n      default: { value: wrapTxn(\"\") },\n      deferred: { value: wrapTxn(\"DEFERRED\") },\n      immediate: { value: wrapTxn(\"IMMEDIATE\") },\n      exclusive: { value: wrapTxn(\"EXCLUSIVE\") },\n      database: { value: this, enumerable: true },\n    };\n    Object.defineProperties(properties.default.value, properties);\n    Object.defineProperties(properties.deferred.value, properties);\n    Object.defineProperties(properties.immediate.value, properties);\n    Object.defineProperties(properties.exclusive.value, properties);\n    return properties.default.value;\n  }\n\n  pragma(source, options) {\n    if (options == null) options = {};\n\n    if (typeof source !== \"string\")\n      throw new TypeError(\"Expected first argument to be a string\");\n\n    if (typeof options !== \"object\")\n      throw new TypeError(\"Expected second argument to be an options object\");\n\n    const pragma = `PRAGMA ${source}`;\n\n    const stmt = this.prepare(pragma);\n    try {\n      const results = stmt.all();\n      return results;\n    } finally {\n      stmt.close();\n    }\n  }\n\n  backup(filename, options) {\n    throw new Error(\"not implemented\");\n  }\n\n  serialize(options) {\n    throw new Error(\"not implemented\");\n  }\n\n  function(name, options, fn) {\n    throw new Error(\"not implemented\");\n  }\n\n  aggregate(name, options) {\n    throw new Error(\"not implemented\");\n  }\n\n  table(name, factory) {\n    throw new Error(\"not implemented\");\n  }\n\n  loadExtension(path) {\n    throw new Error(\"not implemented\");\n  }\n\n  maxWriteReplicationIndex() {\n    throw new Error(\"not implemented\");\n  }\n\n  /**\n   * Executes the given SQL string\n   * Unlike prepared statements, this can execute strings that contain multiple SQL statements\n   *\n   * @param {string} sql - The string containing SQL statements to execute\n   */\n  exec(sql) {\n    if (!this.open) {\n      throw new TypeError(\"The database connection is not open\");\n    }\n    const exec = this.db.executor(sql);\n    try {\n      while (true) {\n        const stepResult = exec.stepSync();\n        if (stepResult === STEP_IO) {\n          this.db.ioLoopSync();\n          continue;\n        }\n        if (stepResult === STEP_DONE) {\n          break;\n        }\n        if (stepResult === STEP_ROW) {\n          // For exec(), we don't need the row data, just continue\n          continue;\n        }\n      }\n    } finally {\n      exec.reset();\n    }\n  }\n\n  /**\n   * Interrupts the database connection.\n   */\n  interrupt() {\n    throw new Error(\"not implemented\");\n  }\n\n  /**\n   * Sets the default safe integers mode for all statements from this database.\n   *\n   * @param {boolean} [toggle] - Whether to use safe integers by default.\n   */\n  defaultSafeIntegers(toggle) {\n    this.db.defaultSafeIntegers(toggle);\n  }\n\n  /**\n   * Closes the database connection.\n   */\n  close() {\n    this.db.close();\n  }\n}\n\n/**\n * Statement represents a prepared SQL statement that can be executed.\n */\nclass Statement {\n  stmt: NativeStatement;\n  db: NativeDatabase;\n\n  constructor(stmt: NativeStatement, db: NativeDatabase) {\n    this.stmt = stmt;\n    this.db = db;\n  }\n\n  /**\n   * Toggle raw mode.\n   *\n   * @param raw Enable or disable raw mode. If you don't pass the parameter, raw mode is enabled.\n   */\n  raw(raw) {\n    this.stmt.raw(raw);\n    return this;\n  }\n\n  /**\n   * Toggle pluck mode.\n   *\n   * @param pluckMode Enable or disable pluck mode. If you don't pass the parameter, pluck mode is enabled.\n   */\n  pluck(pluckMode) {\n    this.stmt.pluck(pluckMode);\n    return this;\n  }\n\n  /**\n   * Sets safe integers mode for this statement.\n   *\n   * @param {boolean} [toggle] - Whether to use safe integers.\n   */\n  safeIntegers(toggle) {\n    this.stmt.safeIntegers(toggle);\n    return this;\n  }\n\n  /**\n   * Get column information for the statement.\n   *\n   * @returns {Array} An array of column objects with name, column, table, database, and type properties.\n   */\n  columns() {\n    return this.stmt.columns();\n  }\n\n  get source() {\n    throw new Error(\"not implemented\");\n  }\n\n  get reader(): boolean {\n    return this.stmt.columns().length > 0;\n  }\n\n  get database() {\n    return this.db;\n  }\n\n  /**\n   * Executes the SQL statement and returns an info object.\n   */\n  run(...bindParameters) {\n    const totalChangesBefore = this.db.totalChanges();\n\n    this.stmt.reset();\n    bindParams(this.stmt, bindParameters);\n    for (; ;) {\n      const stepResult = this.stmt.stepSync();\n      if (stepResult === STEP_IO) {\n        this.db.ioLoopSync();\n        continue;\n      }\n      if (stepResult === STEP_DONE) {\n        break;\n      }\n      if (stepResult === STEP_ROW) {\n        // For run(), we don't need the row data, just continue\n        continue;\n      }\n    }\n\n    const lastInsertRowid = this.db.lastInsertRowid();\n    const changes = this.db.totalChanges() === totalChangesBefore ? 0 : this.db.changes();\n\n    return { changes, lastInsertRowid };\n  }\n\n  /**\n   * Executes the SQL statement and returns the first row.\n   *\n   * @param bindParameters - The bind parameters for executing the statement.\n   */\n  get(...bindParameters) {\n    this.stmt.reset();\n    bindParams(this.stmt, bindParameters);\n    let row = undefined;\n    for (; ;) {\n      const stepResult = this.stmt.stepSync();\n      if (stepResult === STEP_IO) {\n        this.db.ioLoopSync();\n        continue;\n      }\n      if (stepResult === STEP_DONE) {\n        break;\n      }\n      if (stepResult === STEP_ROW && row === undefined) {\n        row = this.stmt.row();\n      }\n    }\n    return row;\n  }\n\n  /**\n   * Executes the SQL statement and returns an iterator to the resulting rows.\n   *\n   * @param bindParameters - The bind parameters for executing the statement.\n   */\n  *iterate(...bindParameters) {\n    this.stmt.reset();\n    bindParams(this.stmt, bindParameters);\n\n    while (true) {\n      const stepResult = this.stmt.stepSync();\n      if (stepResult === STEP_IO) {\n        this.db.ioLoopSync();\n        continue;\n      }\n      if (stepResult === STEP_DONE) {\n        break;\n      }\n      if (stepResult === STEP_ROW) {\n        yield this.stmt.row();\n      }\n    }\n  }\n\n  /**\n   * Executes the SQL statement and returns an array of the resulting rows.\n   *\n   * @param bindParameters - The bind parameters for executing the statement.\n   */\n  all(...bindParameters) {\n    this.stmt.reset();\n    bindParams(this.stmt, bindParameters);\n    const rows: any[] = [];\n    for (; ;) {\n      const stepResult = this.stmt.stepSync();\n      if (stepResult === STEP_IO) {\n        this.db.ioLoopSync();\n        continue;\n      }\n      if (stepResult === STEP_DONE) {\n        break;\n      }\n      if (stepResult === STEP_ROW) {\n        rows.push(this.stmt.row());\n      }\n    }\n    return rows;\n  }\n\n  /**\n   * Interrupts the statement.\n   */\n  interrupt() {\n    throw new Error(\"not implemented\");\n  }\n\n\n  /**\n   * Binds the given parameters to the statement _permanently_\n   *\n   * @param bindParameters - The bind parameters for binding the statement.\n   * @returns this - Statement with binded parameters\n   */\n  bind(...bindParameters) {\n    try {\n      bindParams(this.stmt, bindParameters);\n      return this;\n    } catch (err) {\n      throw convertError(err);\n    }\n  }\n\n  close() {\n    this.stmt.finalize();\n  }\n}\n\nexport { Database, Statement }"
  },
  {
    "path": "bindings/javascript/packages/common/index.ts",
    "content": "import { NativeDatabase, NativeStatement, DatabaseOpts, EncryptionCipher, EncryptionOpts } from \"./types.js\";\nimport { Database as DatabaseCompat, Statement as StatementCompat } from \"./compat.js\";\nimport { Database as DatabasePromise, Statement as StatementPromise } from \"./promise.js\";\nimport { SqliteError } from \"./sqlite-error.js\";\nimport { AsyncLock } from \"./async-lock.js\";\n\nexport {\n    DatabaseOpts,\n    EncryptionCipher,\n    EncryptionOpts,\n    DatabaseCompat, StatementCompat,\n    DatabasePromise, StatementPromise,\n    NativeDatabase, NativeStatement,\n    SqliteError,\n    AsyncLock\n}\n"
  },
  {
    "path": "bindings/javascript/packages/common/package.json",
    "content": "{\n  \"name\": \"@tursodatabase/database-common\",\n  \"version\": \"0.6.0-pre.4\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/tursodatabase/turso\"\n  },\n  \"type\": \"module\",\n  \"license\": \"MIT\",\n  \"main\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"packageManager\": \"yarn@4.9.2\",\n  \"files\": [\n    \"dist/**\",\n    \"README.md\"\n  ],\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.2\",\n    \"vitest\": \"^3.2.4\"\n  },\n  \"scripts\": {\n    \"tsc-build\": \"npm exec tsc\",\n    \"build\": \"npm run tsc-build\",\n    \"test\": \"vitest --run\"\n  }\n}\n"
  },
  {
    "path": "bindings/javascript/packages/common/promise.test.ts",
    "content": "import { expect, test } from 'vitest'\nimport { maybePromise } from './promise.js'\n\ntest('drizzle-orm', async () => {\n    const lazy = maybePromise(() => fetch('http://google.com'));\n    let status, headers;\n    //@ts-ignore\n    lazy.apply(x => { status = x.status; })\n    //@ts-ignore\n    lazy.apply(x => { headers = x.headers; })\n    let response = await lazy.resolve();\n    expect(response).not.toBeNull();\n    expect(status).toBe(200);\n    expect(headers).not.toBeNull();\n})"
  },
  {
    "path": "bindings/javascript/packages/common/promise.ts",
    "content": "import { AsyncLock } from \"./async-lock.js\";\nimport { bindParams } from \"./bind.js\";\nimport { SqliteError } from \"./sqlite-error.js\";\nimport { NativeDatabase, NativeStatement, STEP_IO, STEP_ROW, STEP_DONE, DatabaseOpts } from \"./types.js\";\n\nconst convertibleErrorTypes = { TypeError };\nconst CONVERTIBLE_ERROR_PREFIX = \"[TURSO_CONVERT_TYPE]\";\n\nfunction convertError(err) {\n  if ((err.code ?? \"\").startsWith(CONVERTIBLE_ERROR_PREFIX)) {\n    return createErrorByName(\n      err.code.substring(CONVERTIBLE_ERROR_PREFIX.length),\n      err.message,\n    );\n  }\n\n  return new SqliteError(err.message, err.code, err.rawCode);\n}\n\nfunction createErrorByName(name, message) {\n  const ErrorConstructor = convertibleErrorTypes[name];\n  if (!ErrorConstructor) {\n    throw new Error(`unknown error type ${name} from Turso`);\n  }\n\n  return new ErrorConstructor(message);\n}\n\n/**\n * Database represents a connection that can prepare and execute SQL statements.\n */\nclass Database {\n  name: string;\n  readonly: boolean;\n  open: boolean;\n  memory: boolean;\n  inTransaction: boolean;\n\n  private db: NativeDatabase;\n  private ioStep: () => Promise<void>;\n  private execLock: AsyncLock;\n  private _inTransaction: boolean = false;\n  protected connected: boolean = false;\n\n  constructor(db: NativeDatabase, ioStep?: () => Promise<void>) {\n    this.db = db;\n    this.execLock = new AsyncLock();\n    this.ioStep = ioStep ?? (async () => { });\n    Object.defineProperties(this, {\n      name: { get: () => this.db.path },\n      readonly: { get: () => this.db.readonly },\n      open: { get: () => this.db.open },\n      memory: { get: () => this.db.memory },\n      inTransaction: { get: () => this._inTransaction },\n    });\n  }\n\n  /**\n   * connect database\n   */\n  async connect() {\n    if (this.connected) { return; }\n    await this.db.connectAsync();\n    this.connected = true;\n  }\n\n  /**\n   * Prepares a SQL statement for execution.\n   *\n   * @param {string} sql - The SQL statement string to prepare.\n   */\n  prepare(sql) {\n    // Only throw if we connected before but now the database is closed\n    // Allow implicit connection if not connected yet\n    if (this.connected && !this.open) {\n      throw new TypeError(\"The database connection is not open\");\n    }\n    if (!sql) {\n      throw new RangeError(\"The supplied SQL string contains no statements\");\n    }\n\n    try {\n      if (this.connected) {\n        return new Statement(maybeValue(this.db.prepare(sql)), this.db, this.execLock, this.ioStep);\n      } else {\n        return new Statement(maybePromise(() => this.connect().then(() => this.db.prepare(sql))), this.db, this.execLock, this.ioStep)\n      }\n    } catch (err) {\n      throw convertError(err);\n    }\n  }\n\n  /**\n   * Returns a function that executes the given function in a transaction.\n   *\n   * @param {function} fn - The function to wrap in a transaction.\n   */\n  transaction(fn: (...any) => Promise<any>) {\n    if (typeof fn !== \"function\")\n      throw new TypeError(\"Expected first argument to be a function\");\n\n    const db = this;\n    const wrapTxn = (mode) => {\n      return async (...bindParameters) => {\n        await db.exec(\"BEGIN \" + mode);\n        db._inTransaction = true;\n        try {\n          const result = await fn(...bindParameters);\n          await db.exec(\"COMMIT\");\n          db._inTransaction = false;\n          return result;\n        } catch (err) {\n          await db.exec(\"ROLLBACK\");\n          db._inTransaction = false;\n          throw err;\n        }\n      };\n    };\n    const properties = {\n      default: { value: wrapTxn(\"\") },\n      deferred: { value: wrapTxn(\"DEFERRED\") },\n      immediate: { value: wrapTxn(\"IMMEDIATE\") },\n      exclusive: { value: wrapTxn(\"EXCLUSIVE\") },\n      database: { value: this, enumerable: true },\n    };\n    Object.defineProperties(properties.default.value, properties);\n    Object.defineProperties(properties.deferred.value, properties);\n    Object.defineProperties(properties.immediate.value, properties);\n    Object.defineProperties(properties.exclusive.value, properties);\n    return properties.default.value;\n  }\n\n  async pragma(source, options) {\n    if (options == null) options = {};\n\n    if (typeof source !== \"string\")\n      throw new TypeError(\"Expected first argument to be a string\");\n\n    if (typeof options !== \"object\")\n      throw new TypeError(\"Expected second argument to be an options object\");\n\n    const pragma = `PRAGMA ${source}`;\n\n    const stmt = this.prepare(pragma);\n    try {\n      const results = await stmt.all();\n      return results;\n    } finally {\n      await stmt.close();\n    }\n  }\n\n  backup(filename, options) {\n    throw new Error(\"not implemented\");\n  }\n\n  serialize(options) {\n    throw new Error(\"not implemented\");\n  }\n\n  function(name, options, fn) {\n    throw new Error(\"not implemented\");\n  }\n\n  aggregate(name, options) {\n    throw new Error(\"not implemented\");\n  }\n\n  table(name, factory) {\n    throw new Error(\"not implemented\");\n  }\n\n  loadExtension(path) {\n    throw new Error(\"not implemented\");\n  }\n\n  maxWriteReplicationIndex() {\n    throw new Error(\"not implemented\");\n  }\n\n  /**\n   * Executes the given SQL string\n   * Unlike prepared statements, this can execute strings that contain multiple SQL statements\n   *\n   * @param {string} sql - The string containing SQL statements to execute\n   */\n  async exec(sql) {\n    if (!this.open) {\n      throw new TypeError(\"The database connection is not open\");\n    }\n    await this.execLock.acquire();\n    const exec = this.db.executor(sql);\n    try {\n      while (true) {\n        const stepResult = exec.stepSync();\n        if (stepResult === STEP_IO) {\n          await this.io();\n          continue;\n        }\n        if (stepResult === STEP_DONE) {\n          break;\n        }\n        if (stepResult === STEP_ROW) {\n          // For exec(), we don't need the row data, just continue\n          continue;\n        }\n      }\n    } finally {\n      exec.reset();\n      this.execLock.release();\n    }\n  }\n\n  /**\n   * Interrupts the database connection.\n   */\n  interrupt() {\n    throw new Error(\"not implemented\");\n  }\n\n  /**\n   * Sets the default safe integers mode for all statements from this database.\n   *\n   * @param {boolean} [toggle] - Whether to use safe integers by default.\n   */\n  defaultSafeIntegers(toggle) {\n    this.db.defaultSafeIntegers(toggle);\n  }\n\n  /**\n   * Closes the database connection.\n   */\n  async close() {\n    this.db.close();\n  }\n\n  async io() {\n    // For WASM browser builds, ioStep awaits a promise that resolves when\n    // the OPFS Worker completes the I/O (via IONotifier in wasm-common).\n    // For in-memory / Node.js builds, ioStep is a no-op since I/O is synchronous.\n    await this.ioStep();\n  }\n}\n\ninterface MaybeLazy<T> {\n  apply(fn: (value: T) => void);\n  resolve(): Promise<T>,\n  must(): T;\n}\n\nfunction maybePromise<T>(arg: () => Promise<T>): MaybeLazy<T> {\n  let lazy = arg;\n  let promise = null;\n  let value = null;\n  return {\n    apply(fn) {\n      let previous = lazy;\n      lazy = async () => {\n        const result = await previous();\n        fn(result);\n        return result;\n      }\n    },\n    async resolve() {\n      if (promise != null) {\n        return await promise;\n      }\n      let valueResolve, valueReject;\n      promise = new Promise((resolve, reject) => {\n        valueResolve = x => { resolve(x); value = x; }\n        valueReject = reject;\n      });\n      await lazy().then(valueResolve, valueReject);\n      return await promise;\n    },\n    must() {\n      if (value == null) {\n        throw new Error(`database must be connected before execution the function`)\n      }\n      return value;\n    },\n  }\n}\n\nfunction maybeValue<T>(value: T): MaybeLazy<T> {\n  return {\n    apply(fn) { fn(value); },\n    resolve() { return Promise.resolve(value); },\n    must() { return value; },\n  }\n}\n\n/**\n * Statement represents a prepared SQL statement that can be executed.\n */\nclass Statement {\n  private stmt: MaybeLazy<NativeStatement>;\n  private db: NativeDatabase;\n  private execLock: AsyncLock;\n  private ioStep: () => Promise<void>;\n\n  constructor(stmt: MaybeLazy<NativeStatement>, db: NativeDatabase, execLock: AsyncLock, ioStep: () => Promise<void>) {\n    this.stmt = stmt;\n    this.db = db;\n    this.execLock = execLock;\n    this.ioStep = ioStep;\n  }\n\n  /**\n   * Toggle raw mode.\n   *\n   * @param raw Enable or disable raw mode. If you don't pass the parameter, raw mode is enabled.\n   */\n  raw(raw) {\n    this.stmt.apply(s => s.raw(raw));\n    return this;\n  }\n\n  /**\n   * Toggle pluck mode.\n   *\n   * @param pluckMode Enable or disable pluck mode. If you don't pass the parameter, pluck mode is enabled.\n   */\n  pluck(pluckMode) {\n    this.stmt.apply(s => s.pluck(pluckMode));\n    return this;\n  }\n\n  /**\n   * Sets safe integers mode for this statement.\n   *\n   * @param {boolean} [toggle] - Whether to use safe integers.\n   */\n  safeIntegers(toggle) {\n    this.stmt.apply(s => s.safeIntegers(toggle));\n    return this;\n  }\n\n  /**\n   * Get column information for the statement.\n   *\n   * @returns {Array} An array of column objects with name, column, table, database, and type properties.\n   */\n  columns() {\n    return this.stmt.must().columns();\n  }\n\n  get source() {\n    throw new Error(\"not implemented\");\n  }\n\n  get reader(): boolean {\n    return this.stmt.must().columns().length > 0;\n  }\n\n  get database() {\n    return this.db;\n  }\n\n  /**\n   * Executes the SQL statement and returns an info object.\n   */\n  async run(...bindParameters) {\n    let stmt = await this.stmt.resolve();\n\n    bindParams(stmt, bindParameters);\n\n    const totalChangesBefore = this.db.totalChanges();\n    await this.execLock.acquire();\n    try {\n      while (true) {\n        const stepResult = await stmt.stepSync();\n        if (stepResult === STEP_IO) {\n          await this.io();\n          continue;\n        }\n        if (stepResult === STEP_DONE) {\n          break;\n        }\n        if (stepResult === STEP_ROW) {\n          // For run(), we don't need the row data, just continue\n          continue;\n        }\n      }\n\n      const lastInsertRowid = this.db.lastInsertRowid();\n      const changes = this.db.totalChanges() === totalChangesBefore ? 0 : this.db.changes();\n\n      return { changes, lastInsertRowid };\n    } finally {\n      stmt.reset();\n      this.execLock.release();\n    }\n  }\n\n  /**\n   * Executes the SQL statement and returns the first row.\n   *\n   * @param bindParameters - The bind parameters for executing the statement.\n   */\n  async get(...bindParameters) {\n    let stmt = await this.stmt.resolve();\n\n    bindParams(stmt, bindParameters);\n\n    await this.execLock.acquire();\n    let row = undefined;\n    try {\n      while (true) {\n        const stepResult = await stmt.stepSync();\n        if (stepResult === STEP_IO) {\n          await this.io();\n          continue;\n        }\n        if (stepResult === STEP_DONE) {\n          break;\n        }\n        if (stepResult === STEP_ROW && row === undefined) {\n          row = stmt.row();\n          continue;\n        }\n      }\n      return row;\n    } finally {\n      stmt.reset();\n      this.execLock.release();\n    }\n  }\n\n  /**\n   * Executes the SQL statement and returns an iterator to the resulting rows.\n   *\n   * @param bindParameters - The bind parameters for executing the statement.\n   */\n  async *iterate(...bindParameters) {\n    let stmt = await this.stmt.resolve();\n\n    bindParams(stmt, bindParameters);\n\n    await this.execLock.acquire();\n    try {\n      while (true) {\n        const stepResult = await stmt.stepSync();\n        if (stepResult === STEP_IO) {\n          await this.io();\n          continue;\n        }\n        if (stepResult === STEP_DONE) {\n          break;\n        }\n        if (stepResult === STEP_ROW) {\n          yield stmt.row();\n        }\n      }\n    } finally {\n      stmt.reset();\n      this.execLock.release();\n    }\n  }\n\n  /**\n   * Executes the SQL statement and returns an array of the resulting rows.\n   *\n   * @param bindParameters - The bind parameters for executing the statement.\n   */\n  async all(...bindParameters) {\n    let stmt = await this.stmt.resolve();\n\n    bindParams(stmt, bindParameters);\n    const rows: any[] = [];\n\n    await this.execLock.acquire();\n    try {\n      while (true) {\n        const stepResult = await stmt.stepSync();\n        if (stepResult === STEP_IO) {\n          await this.io();\n          continue;\n        }\n        if (stepResult === STEP_DONE) {\n          break;\n        }\n        if (stepResult === STEP_ROW) {\n          rows.push(stmt.row());\n        }\n      }\n      return rows;\n    }\n    finally {\n      stmt.reset();\n      this.execLock.release();\n    }\n  }\n\n  async io() {\n    await this.ioStep();\n  }\n\n  /**\n   * Interrupts the statement.\n   */\n  interrupt() {\n    throw new Error(\"not implemented\");\n  }\n\n\n  /**\n   * Binds the given parameters to the statement _permanently_\n   *\n   * @param bindParameters - The bind parameters for binding the statement.\n   * @returns this - Statement with binded parameters\n   */\n  bind(...bindParameters) {\n    try {\n      bindParams(this.stmt, bindParameters);\n      return this;\n    } catch (err) {\n      throw convertError(err);\n    }\n  }\n\n  close() {\n    let stmt;\n    try {\n      stmt = this.stmt.must();\n    } catch (e) {\n      // ignore error - if stmt wasn't initialized it's fine\n      return;\n    }\n    stmt.finalize();\n  }\n}\n\nexport { Database, Statement, maybePromise, maybeValue }"
  },
  {
    "path": "bindings/javascript/packages/common/sqlite-error.ts",
    "content": "export class SqliteError extends Error {\n  name: string;\n  code: string;\n  rawCode: string;\n  constructor(message, code, rawCode) {\n    super(message);\n    this.name = 'SqliteError';\n    this.code = code;\n    this.rawCode = rawCode;\n\n    (Error as any).captureStackTrace(this, SqliteError);\n  }\n}\n"
  },
  {
    "path": "bindings/javascript/packages/common/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"skipLibCheck\": true,\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"module\": \"nodenext\",\n    \"target\": \"esnext\",\n    \"outDir\": \"dist/\",\n    \"lib\": [\n      \"es2020\"\n    ],\n  },\n  \"include\": [\n    \"*\"\n  ]\n}"
  },
  {
    "path": "bindings/javascript/packages/common/types.ts",
    "content": "export type ExperimentalFeature = 'views' | 'strict' | 'encryption' | 'index_method' | 'autovacuum' | 'triggers' | 'attach';\n\n/** Supported encryption ciphers for local database encryption. */\nexport type EncryptionCipher = 'aes128gcm' | 'aes256gcm' | 'aegis256' | 'aegis256x2' | 'aegis128l' | 'aegis128x2' | 'aegis128x4'\n\n/** Encryption configuration for local encryption. */\nexport interface EncryptionOpts {\n    cipher: EncryptionCipher\n    /** The hex-encoded encryption key */\n    hexkey: string\n}\n\nexport interface DatabaseOpts {\n    readonly?: boolean,\n    fileMustExist?: boolean,\n    timeout?: number\n    tracing?: 'info' | 'debug' | 'trace'\n    /** Experimental features to enable */\n    experimental?: ExperimentalFeature[]\n    /** Optional local encryption configuration */\n    encryption?: EncryptionOpts\n}\n\nexport interface NativeDatabase {\n    memory: boolean,\n    path: string,\n    readonly: boolean;\n    open: boolean;\n    new(path: string): NativeDatabase;\n\n    connectSync();\n    connectAsync(): Promise<void>;\n\n    ioLoopSync();\n    ioLoopAsync(): Promise<void>;\n\n    prepare(sql: string): NativeStatement;\n    executor(sql: string): NativeExecutor;\n\n    defaultSafeIntegers(toggle: boolean);\n    totalChanges(): number;\n    changes(): number;\n    lastInsertRowid(): number;\n    close();\n}\n\n\n// Step result constants\nexport const STEP_ROW = 1;\nexport const STEP_DONE = 2;\nexport const STEP_IO = 3;\n\nexport interface TableColumn {\n    name: string,\n    type: string\n}\n\nexport interface NativeExecutor {\n    stepSync(): number;\n    reset();\n}\nexport interface NativeStatement {\n    stepAsync(): Promise<number>;\n    stepSync(): number;\n\n    pluck(pluckMode: boolean);\n    safeIntegers(toggle: boolean);\n    raw(toggle: boolean);\n    columns(): TableColumn[];\n    row(): any;\n    reset();\n    finalize();\n}"
  },
  {
    "path": "bindings/javascript/packages/native/README.md",
    "content": "<p align=\"center\">\n  <h1 align=\"center\">Turso Database for JavaScript in Node</h1>\n</p>\n\n<p align=\"center\">\n  <a title=\"JavaScript\" target=\"_blank\" href=\"https://www.npmjs.com/package/@tursodatabase/database\"><img alt=\"npm\" src=\"https://img.shields.io/npm/v/@tursodatabase/database\"></a>\n  <a title=\"MIT\" target=\"_blank\" href=\"https://github.com/tursodatabase/turso/blob/main/LICENSE.md\"><img src=\"http://img.shields.io/badge/license-MIT-orange.svg?style=flat-square\"></a>\n</p>\n<p align=\"center\">\n  <a title=\"Users Discord\" target=\"_blank\" href=\"https://tur.so/discord\"><img alt=\"Chat with other users of Turso on Discord\" src=\"https://img.shields.io/discord/933071162680958986?label=Discord&logo=Discord&style=social\"></a>\n</p>\n\n---\n\n## About\n\nThis package is the Turso embedded database library for JavaScript in Node.\n\n> **⚠️ Warning:** This software is in BETA. It may still contain bugs and unexpected behavior. Use caution with production data and ensure you have backups.\n\n## Features\n\n- **SQLite compatible:** SQLite query language and file format support ([status](https://github.com/tursodatabase/turso/blob/main/COMPAT.md)).\n- **In-process**: No network overhead, runs directly in your Node.js process\n- **TypeScript support**: Full TypeScript definitions included\n- **Cross-platform**: Supports Linux (x86 and arm64), macOS, Windows (browser is supported in the separate package `@tursodatabase/database-wasm` package)\n\n## Installation\n\n```bash\nnpm install @tursodatabase/database\n```\n\n## Getting Started\n\n### In-Memory Database\n\n```javascript\nimport { connect } from '@tursodatabase/database';\n\n// Create an in-memory database\nconst db = await connect(':memory:');\n\n// Create a table\nawait db.exec('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)');\n\n// Insert data\nconst insert = db.prepare('INSERT INTO users (name, email) VALUES (?, ?)');\nawait insert.run('Alice', 'alice@example.com');\nawait insert.run('Bob', 'bob@example.com');\n\n// Query data\nconst users = await db.prepare('SELECT * FROM users').all();\nconsole.log(users);\n// Output: [\n//   { id: 1, name: 'Alice', email: 'alice@example.com' },\n//   { id: 2, name: 'Bob', email: 'bob@example.com' }\n// ]\n```\n\n### File-Based Database\n\n```javascript\nimport { connect } from '@tursodatabase/database';\n\n// Create or open a database file\nconst db = await connect('my-database.db');\n\n// Create a table\nawait db.exec(`\n  CREATE TABLE IF NOT EXISTS posts (\n    id INTEGER PRIMARY KEY AUTOINCREMENT,\n    title TEXT NOT NULL,\n    content TEXT,\n    created_at DATETIME DEFAULT CURRENT_TIMESTAMP\n  )\n`);\n\n// Insert a post\nconst insertPost = db.prepare('INSERT INTO posts (title, content) VALUES (?, ?)');\nconst result = await insertPost.run('Hello World', 'This is my first blog post!');\n\nconsole.log(`Inserted post with ID: ${result.lastInsertRowid}`);\n```\n\n### Transactions\n\n```javascript\nimport { connect } from '@tursodatabase/database';\n\nconst db = await connect('transactions.db');\n\n// Using transactions for atomic operations\nconst transaction = db.transaction(async (users) => {\n  const insert = db.prepare('INSERT INTO users (name, email) VALUES (?, ?)');\n  for (const user of users) {\n    await insert.run(user.name, user.email);\n  }\n});\n\n// Execute transaction\nawait transaction([\n  { name: 'Alice', email: 'alice@example.com' },\n  { name: 'Bob', email: 'bob@example.com' }\n]);\n```\n\n## API Reference\n\nFor complete API documentation, see [JavaScript API Reference](https://github.com/tursodatabase/turso/blob/main/docs/javascript-api-reference.md).\n\n## Related Packages\n\n* The [@tursodatabase/serverless](https://www.npmjs.com/package/@tursodatabase/serverless) package provides a serverless driver with the same API.\n* The [@tursodatabase/sync](https://www.npmjs.com/package/@tursodatabase/sync) package provides bidirectional sync between a local Turso database and Turso Cloud. \n\n## License\n\nThis project is licensed under the [MIT license](https://github.com/tursodatabase/turso/blob/main/LICENSE.md)\n\n## Support\n\n- [GitHub Issues](https://github.com/tursodatabase/turso/issues)\n- [Documentation](https://docs.turso.tech)\n- [Discord Community](https://tur.so/discord)\n"
  },
  {
    "path": "bindings/javascript/packages/native/compat.test.ts",
    "content": "import { unlinkSync } from \"node:fs\";\nimport { expect, test } from 'vitest'\nimport { Database } from './compat.js'\n\ntest('insert returning test', () => {\n    const db = new Database(':memory:');\n    db.prepare(`create table t (x);`).run();\n    const x1 = db.prepare(`insert into t values (1), (2) returning x`).get();\n    const x2 = db.prepare(`insert into t values (3), (4) returning x`).get();\n    expect(x1).toEqual({ x: 1 });\n    expect(x2).toEqual({ x: 3 });\n    const all = db.prepare(`select * from t`).all();\n    expect(all).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }, { x: 4 }])\n})\n\ntest('in-memory db', () => {\n    const db = new Database(\":memory:\");\n    db.exec(\"CREATE TABLE t(x)\");\n    db.exec(\"INSERT INTO t VALUES (1), (2), (3)\");\n    const stmt = db.prepare(\"SELECT * FROM t WHERE x % 2 = ?\");\n    const rows = stmt.all([1]);\n    expect(rows).toEqual([{ x: 1 }, { x: 3 }]);\n})\n\ntest('exec multiple statements', async () => {\n    const db = new Database(\":memory:\");\n    db.exec(\"CREATE TABLE t(x); INSERT INTO t VALUES (1); INSERT INTO t VALUES (2)\");\n    const stmt = db.prepare(\"SELECT * FROM t\");\n    const rows = stmt.all();\n    expect(rows).toEqual([{ x: 1 }, { x: 2 }]);\n})\n\ntest('readonly-db', () => {\n    const path = `test-${(Math.random() * 10000) | 0}.db`;\n    try {\n        {\n            const rw = new Database(path);\n            rw.exec(\"CREATE TABLE t(x)\");\n            rw.exec(\"INSERT INTO t VALUES (1)\");\n            rw.close();\n        }\n        {\n            const ro = new Database(path, { readonly: true });\n            expect(() => ro.exec(\"INSERT INTO t VALUES (2)\")).toThrowError(/Resource is read-only/g);\n            expect(ro.prepare(\"SELECT * FROM t\").all()).toEqual([{ x: 1 }])\n            ro.close();\n        }\n    } finally {\n        unlinkSync(path);\n        unlinkSync(`${path}-wal`);\n    }\n})\n\ntest('file-must-exist', () => {\n    const path = `test-${(Math.random() * 10000) | 0}.db`;\n    expect(() => new Database(path, { fileMustExist: true })).toThrowError(/failed to open database/);\n})\n\ntest('on-disk db', () => {\n    const path = `test-${(Math.random() * 10000) | 0}.db`;\n    try {\n        const db1 = new Database(path);\n        db1.exec(\"CREATE TABLE t(x)\");\n        db1.exec(\"INSERT INTO t VALUES (1), (2), (3)\");\n        const stmt1 = db1.prepare(\"SELECT * FROM t WHERE x % 2 = ?\");\n        expect(stmt1.columns()).toEqual([{ name: \"x\", column: null, database: null, table: null, type: null }]);\n        const rows1 = stmt1.all([1]);\n        expect(rows1).toEqual([{ x: 1 }, { x: 3 }]);\n        db1.close();\n\n        const db2 = new Database(path);\n        const stmt2 = db2.prepare(\"SELECT * FROM t WHERE x % 2 = ?\");\n        expect(stmt2.columns()).toEqual([{ name: \"x\", column: null, database: null, table: null, type: null }]);\n        const rows2 = stmt2.all([1]);\n        expect(rows2).toEqual([{ x: 1 }, { x: 3 }]);\n        db2.close();\n    } finally {\n        unlinkSync(path);\n        unlinkSync(`${path}-wal`);\n    }\n})\n\ntest('attach', () => {\n    const path1 = `test-${(Math.random() * 10000) | 0}.db`;\n    const path2 = `test-${(Math.random() * 10000) | 0}.db`;\n    try {\n        const db1 = new Database(path1, { experimental: [\"attach\"] });\n        db1.exec(\"CREATE TABLE t(x)\");\n        db1.exec(\"INSERT INTO t VALUES (1), (2), (3)\");\n        const db2 = new Database(path2, { experimental: [\"attach\"] });\n        db2.exec(\"CREATE TABLE q(x)\");\n        db2.exec(\"INSERT INTO q VALUES (4), (5), (6)\");\n\n        db1.exec(`ATTACH '${path2}' as secondary`);\n\n        const stmt = db1.prepare(\"SELECT * FROM t UNION ALL SELECT * FROM secondary.q\");\n        expect(stmt.columns()).toEqual([{ name: \"x\", column: null, database: null, table: null, type: null }]);\n        const rows = stmt.all([1]);\n        expect(rows).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }, { x: 4 }, { x: 5 }, { x: 6 }]);\n    } finally {\n        unlinkSync(path1);\n        unlinkSync(`${path1}-wal`);\n        unlinkSync(path2);\n        unlinkSync(`${path2}-wal`);\n    }\n})\n\ntest('blobs', () => {\n    const db = new Database(\":memory:\");\n    const rows = db.prepare(\"SELECT x'1020' as x\").all();\n    expect(rows).toEqual([{ x: Buffer.from([16, 32]) }])\n})\n\ntest('encryption', () => {\n    const path = `test-encryption-${(Math.random() * 10000) | 0}.db`;\n    const hexkey = 'b1bbfda4f589dc9daaf004fe21111e00dc00c98237102f5c7002a5669fc76327';\n    const wrongKey = 'aaaaaaa4f589dc9daaf004fe21111e00dc00c98237102f5c7002a5669fc76327';\n    try {\n        const db = new Database(path, {\n            encryption: { cipher: 'aegis256', hexkey }\n        });\n        db.exec(\"CREATE TABLE t(x)\");\n        db.exec(\"INSERT INTO t SELECT 'secret' FROM generate_series(1, 1024)\");\n        db.exec(\"PRAGMA wal_checkpoint(truncate)\");\n        db.close();\n\n        // lets re-open with the same key\n        const db2 = new Database(path, {\n            encryption: { cipher: 'aegis256', hexkey }\n        });\n        const rows = db2.prepare(\"SELECT COUNT(*) as cnt FROM t\").all();\n        expect(rows).toEqual([{ cnt: 1024 }]);\n        db2.close();\n\n        // opening with wrong key MUST fail\n        expect(() => {\n            const db3 = new Database(path, {\n                encryption: { cipher: 'aegis256', hexkey: wrongKey }\n            });\n            db3.prepare(\"SELECT * FROM t\").all();\n        }).toThrow();\n\n        // opening without encryption MUST fail\n        expect(() => {\n            const db5 = new Database(path);\n            db5.prepare(\"SELECT * FROM t\").all();\n        }).toThrow();\n    } finally {\n        unlinkSync(path);\n    }\n})"
  },
  {
    "path": "bindings/javascript/packages/native/compat.ts",
    "content": "import { DatabaseCompat, NativeDatabase, SqliteError, DatabaseOpts, EncryptionCipher } from \"@tursodatabase/database-common\"\nimport { Database as NativeDB, EncryptionCipher as NativeEncryptionCipher } from \"#index\";\n\n// Map string cipher names to native enum values (lazy to avoid errors if native module lacks encryption)\nfunction getCipherValue(cipher: EncryptionCipher): number {\n    if (!NativeEncryptionCipher) {\n        throw new Error('Encryption is not supported in this build');\n    }\n    const cipherMap: Record<EncryptionCipher, number> = {\n        'aes128gcm': NativeEncryptionCipher.Aes128Gcm,\n        'aes256gcm': NativeEncryptionCipher.Aes256Gcm,\n        'aegis256': NativeEncryptionCipher.Aegis256,\n        'aegis256x2': NativeEncryptionCipher.Aegis256x2,\n        'aegis128l': NativeEncryptionCipher.Aegis128l,\n        'aegis128x2': NativeEncryptionCipher.Aegis128x2,\n        'aegis128x4': NativeEncryptionCipher.Aegis128x4,\n    };\n    return cipherMap[cipher];\n}\n\nclass Database extends DatabaseCompat {\n    constructor(path: string, opts: DatabaseOpts = {}) {\n        const nativeOpts: any = { ...opts };\n        if (opts.encryption) {\n            nativeOpts.encryption = {\n                cipher: getCipherValue(opts.encryption.cipher),\n                hexkey: opts.encryption.hexkey,\n            };\n        }\n        super(new NativeDB(path, nativeOpts) as unknown as NativeDatabase)\n    }\n}\n\nexport { Database, SqliteError }\n"
  },
  {
    "path": "bindings/javascript/packages/native/index.d.ts",
    "content": "/* auto-generated by NAPI-RS */\n/* eslint-disable */\nexport declare class BatchExecutor {\n  stepSync(): number\n  reset(): void\n}\n\n/** A database connection. */\nexport declare class Database {\n  /**\n   * Creates a new database instance.\n   *\n   * # Arguments\n   * * `path` - The path to the database file.\n   */\n  constructor(path: string, opts?: DatabaseOpts | undefined | null)\n  /**\n   * Connect the database synchronously\n   * This method is idempotent and can be called multiple times safely until the database will be closed\n   */\n  connectSync(): void\n  /**\n   * Connect the database asynchronously\n   * This method is idempotent and can be called multiple times safely until the database will be closed\n   */\n  connectAsync(): Promise<void>\n  /** Returns whether the database is in readonly-only mode. */\n  get readonly(): boolean\n  /** Returns whether the database is in memory-only mode. */\n  get memory(): boolean\n  /** Returns whether the database is in memory-only mode. */\n  get path(): string\n  /** Returns whether the database connection is open. */\n  get open(): boolean\n  /**\n   * Prepares a statement for execution.\n   *\n   * # Arguments\n   *\n   * * `sql` - The SQL statement to prepare.\n   *\n   * # Returns\n   *\n   * A `Statement` instance.\n   */\n  prepare(sql: string): Statement\n  executor(sql: string): BatchExecutor\n  /**\n   * Returns the rowid of the last row inserted.\n   *\n   * # Returns\n   *\n   * The rowid of the last row inserted.\n   */\n  lastInsertRowid(): number\n  /**\n   * Returns the number of changes made by the last statement.\n   *\n   * # Returns\n   *\n   * The number of changes made by the last statement.\n   */\n  changes(): number\n  /**\n   * Returns the total number of changes made by all statements.\n   *\n   * # Returns\n   *\n   * The total number of changes made by all statements.\n   */\n  totalChanges(): number\n  /**\n   * Closes the database connection.\n   *\n   * # Returns\n   *\n   * `Ok(())` if the database is closed successfully.\n   */\n  close(): void\n  /**\n   * Sets the default safe integers mode for all statements from this database.\n   *\n   * # Arguments\n   *\n   * * `toggle` - Whether to use safe integers by default.\n   */\n  defaultSafeIntegers(toggle?: boolean | undefined | null): void\n  /** Runs the I/O loop synchronously. */\n  ioLoopSync(): void\n  /** Runs the I/O loop asynchronously, returning a Promise. */\n  ioLoopAsync(): Promise<void>\n  /** Classify SQL statement. Returns \"read\", \"write\", \"begin\", \"commit\", or \"rollback\". */\n  classifySql(sql: string): string\n}\n\n/** A prepared statement. */\nexport declare class Statement {\n  reset(): void\n  /** Returns the number of parameters in the statement. */\n  parameterCount(): number\n  /**\n   * Returns the name of a parameter at a specific 1-based index.\n   *\n   * # Arguments\n   *\n   * * `index` - The 1-based parameter index.\n   */\n  parameterName(index: number): string | null\n  /**\n   * Binds a parameter at a specific 1-based index with explicit type.\n   *\n   * # Arguments\n   *\n   * * `index` - The 1-based parameter index.\n   * * `value_type` - The type constant (0=null, 1=int, 2=float, 3=text, 4=blob).\n   * * `value` - The value to bind.\n   */\n  bindAt(index: number, value: unknown): void\n  /**\n   * Step the statement and return result code (executed on the main thread):\n   * 1 = Row available, 2 = Done, 3 = I/O needed\n   */\n  stepSync(): number\n  /** Get the current row data according to the presentation mode */\n  row(): unknown\n  /** Sets the presentation mode to raw. */\n  raw(raw?: boolean | undefined | null): void\n  /** Sets the presentation mode to pluck. */\n  pluck(pluck?: boolean | undefined | null): void\n  /**\n   * Sets safe integers mode for this statement.\n   *\n   * # Arguments\n   *\n   * * `toggle` - Whether to use safe integers.\n   */\n  safeIntegers(toggle?: boolean | undefined | null): void\n  /** Get column information for the statement */\n  columns(): Promise<any>\n  /** Finalizes the statement. */\n  finalize(): void\n}\n\n/**\n * Most of the options are aligned with better-sqlite API\n * (see https://github.com/WiseLibs/better-sqlite3/blob/master/docs/api.md#new-databasepath-options)\n */\nexport interface DatabaseOpts {\n  readonly?: boolean\n  timeout?: number\n  fileMustExist?: boolean\n  tracing?: string\n  /** Experimental features to enable */\n  experimental?: Array<string>\n  /** Optional encryption configuration for local database encryption */\n  encryption?: EncryptionOpts\n}\n\n/** Supported encryption ciphers for local database encryption. */\nexport declare const enum EncryptionCipher {\n  Aes128Gcm = 0,\n  Aes256Gcm = 1,\n  Aegis256 = 2,\n  Aegis256x2 = 3,\n  Aegis128l = 4,\n  Aegis128x2 = 5,\n  Aegis128x4 = 6\n}\n\n/** Encryption configuration for local database encryption. */\nexport interface EncryptionOpts {\n  /** The cipher to use for encryption */\n  cipher: EncryptionCipher\n  /** The hex-encoded encryption key */\n  hexkey: string\n}\n"
  },
  {
    "path": "bindings/javascript/packages/native/index.js",
    "content": "// prettier-ignore\n/* eslint-disable */\n// @ts-nocheck\n/* auto-generated by NAPI-RS */\n\nimport { createRequire } from 'node:module'\nconst require = createRequire(import.meta.url)\nconst __dirname = new URL('.', import.meta.url).pathname\n\nconst { readFileSync } = require('node:fs')\nlet nativeBinding = null\nconst loadErrors = []\n\nconst isMusl = () => {\n  let musl = false\n  if (process.platform === 'linux') {\n    musl = isMuslFromFilesystem()\n    if (musl === null) {\n      musl = isMuslFromReport()\n    }\n    if (musl === null) {\n      musl = isMuslFromChildProcess()\n    }\n  }\n  return musl\n}\n\nconst isFileMusl = (f) => f.includes('libc.musl-') || f.includes('ld-musl-')\n\nconst isMuslFromFilesystem = () => {\n  try {\n    return readFileSync('/usr/bin/ldd', 'utf-8').includes('musl')\n  } catch {\n    return null\n  }\n}\n\nconst isMuslFromReport = () => {\n  let report = null\n  if (typeof process.report?.getReport === 'function') {\n    process.report.excludeNetwork = true\n    report = process.report.getReport()\n  }\n  if (!report) {\n    return null\n  }\n  if (report.header && report.header.glibcVersionRuntime) {\n    return false\n  }\n  if (Array.isArray(report.sharedObjects)) {\n    if (report.sharedObjects.some(isFileMusl)) {\n      return true\n    }\n  }\n  return false\n}\n\nconst isMuslFromChildProcess = () => {\n  try {\n    return require('child_process').execSync('ldd --version', { encoding: 'utf8' }).includes('musl')\n  } catch (e) {\n    // If we reach this case, we don't know if the system is musl or not, so is better to just fallback to false\n    return false\n  }\n}\n\nfunction requireNative() {\n  if (process.env.NAPI_RS_NATIVE_LIBRARY_PATH) {\n    try {\n      nativeBinding = require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH);\n    } catch (err) {\n      loadErrors.push(err)\n    }\n  } else if (process.platform === 'android') {\n    if (process.arch === 'arm64') {\n      try {\n        return require('./turso.android-arm64.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tursodatabase/database-android-arm64')\n        const bindingPackageVersion = require('@tursodatabase/database-android-arm64/package.json').version\n        if (bindingPackageVersion !== '0.5.0-pre.20' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.20 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else if (process.arch === 'arm') {\n      try {\n        return require('./turso.android-arm-eabi.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tursodatabase/database-android-arm-eabi')\n        const bindingPackageVersion = require('@tursodatabase/database-android-arm-eabi/package.json').version\n        if (bindingPackageVersion !== '0.5.0-pre.20' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.20 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else {\n      loadErrors.push(new Error(`Unsupported architecture on Android ${process.arch}`))\n    }\n  } else if (process.platform === 'win32') {\n    if (process.arch === 'x64') {\n      try {\n        return require('./turso.win32-x64-msvc.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tursodatabase/database-win32-x64-msvc')\n        const bindingPackageVersion = require('@tursodatabase/database-win32-x64-msvc/package.json').version\n        if (bindingPackageVersion !== '0.5.0-pre.20' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.20 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else if (process.arch === 'ia32') {\n      try {\n        return require('./turso.win32-ia32-msvc.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tursodatabase/database-win32-ia32-msvc')\n        const bindingPackageVersion = require('@tursodatabase/database-win32-ia32-msvc/package.json').version\n        if (bindingPackageVersion !== '0.5.0-pre.20' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.20 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else if (process.arch === 'arm64') {\n      try {\n        return require('./turso.win32-arm64-msvc.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tursodatabase/database-win32-arm64-msvc')\n        const bindingPackageVersion = require('@tursodatabase/database-win32-arm64-msvc/package.json').version\n        if (bindingPackageVersion !== '0.5.0-pre.20' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.20 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else {\n      loadErrors.push(new Error(`Unsupported architecture on Windows: ${process.arch}`))\n    }\n  } else if (process.platform === 'darwin') {\n    try {\n      return require('./turso.darwin-universal.node')\n    } catch (e) {\n      loadErrors.push(e)\n    }\n    try {\n      const binding = require('@tursodatabase/database-darwin-universal')\n      const bindingPackageVersion = require('@tursodatabase/database-darwin-universal/package.json').version\n      if (bindingPackageVersion !== '0.5.0-pre.20' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n        throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.20 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n      }\n      return binding\n    } catch (e) {\n      loadErrors.push(e)\n    }\n    if (process.arch === 'x64') {\n      try {\n        return require('./turso.darwin-x64.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tursodatabase/database-darwin-x64')\n        const bindingPackageVersion = require('@tursodatabase/database-darwin-x64/package.json').version\n        if (bindingPackageVersion !== '0.5.0-pre.20' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.20 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else if (process.arch === 'arm64') {\n      try {\n        return require('./turso.darwin-arm64.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tursodatabase/database-darwin-arm64')\n        const bindingPackageVersion = require('@tursodatabase/database-darwin-arm64/package.json').version\n        if (bindingPackageVersion !== '0.5.0-pre.20' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.20 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else {\n      loadErrors.push(new Error(`Unsupported architecture on macOS: ${process.arch}`))\n    }\n  } else if (process.platform === 'freebsd') {\n    if (process.arch === 'x64') {\n      try {\n        return require('./turso.freebsd-x64.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tursodatabase/database-freebsd-x64')\n        const bindingPackageVersion = require('@tursodatabase/database-freebsd-x64/package.json').version\n        if (bindingPackageVersion !== '0.5.0-pre.20' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.20 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else if (process.arch === 'arm64') {\n      try {\n        return require('./turso.freebsd-arm64.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tursodatabase/database-freebsd-arm64')\n        const bindingPackageVersion = require('@tursodatabase/database-freebsd-arm64/package.json').version\n        if (bindingPackageVersion !== '0.5.0-pre.20' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.20 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else {\n      loadErrors.push(new Error(`Unsupported architecture on FreeBSD: ${process.arch}`))\n    }\n  } else if (process.platform === 'linux') {\n    if (process.arch === 'x64') {\n      if (isMusl()) {\n        try {\n          return require('./turso.linux-x64-musl.node')\n        } catch (e) {\n          loadErrors.push(e)\n        }\n        try {\n          const binding = require('@tursodatabase/database-linux-x64-musl')\n          const bindingPackageVersion = require('@tursodatabase/database-linux-x64-musl/package.json').version\n          if (bindingPackageVersion !== '0.5.0-pre.20' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n            throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.20 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n          }\n          return binding\n        } catch (e) {\n          loadErrors.push(e)\n        }\n      } else {\n        try {\n          return require('./turso.linux-x64-gnu.node')\n        } catch (e) {\n          loadErrors.push(e)\n        }\n        try {\n          const binding = require('@tursodatabase/database-linux-x64-gnu')\n          const bindingPackageVersion = require('@tursodatabase/database-linux-x64-gnu/package.json').version\n          if (bindingPackageVersion !== '0.5.0-pre.20' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n            throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.20 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n          }\n          return binding\n        } catch (e) {\n          loadErrors.push(e)\n        }\n      }\n    } else if (process.arch === 'arm64') {\n      if (isMusl()) {\n        try {\n          return require('./turso.linux-arm64-musl.node')\n        } catch (e) {\n          loadErrors.push(e)\n        }\n        try {\n          const binding = require('@tursodatabase/database-linux-arm64-musl')\n          const bindingPackageVersion = require('@tursodatabase/database-linux-arm64-musl/package.json').version\n          if (bindingPackageVersion !== '0.5.0-pre.20' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n            throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.20 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n          }\n          return binding\n        } catch (e) {\n          loadErrors.push(e)\n        }\n      } else {\n        try {\n          return require('./turso.linux-arm64-gnu.node')\n        } catch (e) {\n          loadErrors.push(e)\n        }\n        try {\n          const binding = require('@tursodatabase/database-linux-arm64-gnu')\n          const bindingPackageVersion = require('@tursodatabase/database-linux-arm64-gnu/package.json').version\n          if (bindingPackageVersion !== '0.5.0-pre.20' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n            throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.20 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n          }\n          return binding\n        } catch (e) {\n          loadErrors.push(e)\n        }\n      }\n    } else if (process.arch === 'arm') {\n      if (isMusl()) {\n        try {\n          return require('./turso.linux-arm-musleabihf.node')\n        } catch (e) {\n          loadErrors.push(e)\n        }\n        try {\n          const binding = require('@tursodatabase/database-linux-arm-musleabihf')\n          const bindingPackageVersion = require('@tursodatabase/database-linux-arm-musleabihf/package.json').version\n          if (bindingPackageVersion !== '0.5.0-pre.20' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n            throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.20 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n          }\n          return binding\n        } catch (e) {\n          loadErrors.push(e)\n        }\n      } else {\n        try {\n          return require('./turso.linux-arm-gnueabihf.node')\n        } catch (e) {\n          loadErrors.push(e)\n        }\n        try {\n          const binding = require('@tursodatabase/database-linux-arm-gnueabihf')\n          const bindingPackageVersion = require('@tursodatabase/database-linux-arm-gnueabihf/package.json').version\n          if (bindingPackageVersion !== '0.5.0-pre.20' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n            throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.20 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n          }\n          return binding\n        } catch (e) {\n          loadErrors.push(e)\n        }\n      }\n    } else if (process.arch === 'riscv64') {\n      if (isMusl()) {\n        try {\n          return require('./turso.linux-riscv64-musl.node')\n        } catch (e) {\n          loadErrors.push(e)\n        }\n        try {\n          const binding = require('@tursodatabase/database-linux-riscv64-musl')\n          const bindingPackageVersion = require('@tursodatabase/database-linux-riscv64-musl/package.json').version\n          if (bindingPackageVersion !== '0.5.0-pre.20' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n            throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.20 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n          }\n          return binding\n        } catch (e) {\n          loadErrors.push(e)\n        }\n      } else {\n        try {\n          return require('./turso.linux-riscv64-gnu.node')\n        } catch (e) {\n          loadErrors.push(e)\n        }\n        try {\n          const binding = require('@tursodatabase/database-linux-riscv64-gnu')\n          const bindingPackageVersion = require('@tursodatabase/database-linux-riscv64-gnu/package.json').version\n          if (bindingPackageVersion !== '0.5.0-pre.20' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n            throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.20 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n          }\n          return binding\n        } catch (e) {\n          loadErrors.push(e)\n        }\n      }\n    } else if (process.arch === 'ppc64') {\n      try {\n        return require('./turso.linux-ppc64-gnu.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tursodatabase/database-linux-ppc64-gnu')\n        const bindingPackageVersion = require('@tursodatabase/database-linux-ppc64-gnu/package.json').version\n        if (bindingPackageVersion !== '0.5.0-pre.20' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.20 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else if (process.arch === 's390x') {\n      try {\n        return require('./turso.linux-s390x-gnu.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tursodatabase/database-linux-s390x-gnu')\n        const bindingPackageVersion = require('@tursodatabase/database-linux-s390x-gnu/package.json').version\n        if (bindingPackageVersion !== '0.5.0-pre.20' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.20 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else {\n      loadErrors.push(new Error(`Unsupported architecture on Linux: ${process.arch}`))\n    }\n  } else if (process.platform === 'openharmony') {\n    if (process.arch === 'arm64') {\n      try {\n        return require('./turso.openharmony-arm64.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tursodatabase/database-openharmony-arm64')\n        const bindingPackageVersion = require('@tursodatabase/database-openharmony-arm64/package.json').version\n        if (bindingPackageVersion !== '0.5.0-pre.20' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.20 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else if (process.arch === 'x64') {\n      try {\n        return require('./turso.openharmony-x64.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tursodatabase/database-openharmony-x64')\n        const bindingPackageVersion = require('@tursodatabase/database-openharmony-x64/package.json').version\n        if (bindingPackageVersion !== '0.5.0-pre.20' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.20 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else if (process.arch === 'arm') {\n      try {\n        return require('./turso.openharmony-arm.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tursodatabase/database-openharmony-arm')\n        const bindingPackageVersion = require('@tursodatabase/database-openharmony-arm/package.json').version\n        if (bindingPackageVersion !== '0.5.0-pre.20' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.20 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else {\n      loadErrors.push(new Error(`Unsupported architecture on OpenHarmony: ${process.arch}`))\n    }\n  } else {\n    loadErrors.push(new Error(`Unsupported OS: ${process.platform}, architecture: ${process.arch}`))\n  }\n}\n\nnativeBinding = requireNative()\n\nif (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) {\n  try {\n    nativeBinding = require('./turso.wasi.cjs')\n  } catch (err) {\n    if (process.env.NAPI_RS_FORCE_WASI) {\n      loadErrors.push(err)\n    }\n  }\n  if (!nativeBinding) {\n    try {\n      nativeBinding = require('@tursodatabase/database-wasm32-wasi')\n    } catch (err) {\n      if (process.env.NAPI_RS_FORCE_WASI) {\n        loadErrors.push(err)\n      }\n    }\n  }\n}\n\nif (!nativeBinding) {\n  if (loadErrors.length > 0) {\n    throw new Error(\n      `Cannot find native binding. ` +\n        `npm has a bug related to optional dependencies (https://github.com/npm/cli/issues/4828). ` +\n        'Please try `npm i` again after removing both package-lock.json and node_modules directory.',\n      { cause: loadErrors }\n    )\n  }\n  throw new Error(`Failed to load native binding`)\n}\n\nconst { BatchExecutor, Database, Statement, EncryptionCipher } = nativeBinding\nexport { BatchExecutor }\nexport { Database }\nexport { Statement }\nexport { EncryptionCipher }\n"
  },
  {
    "path": "bindings/javascript/packages/native/package.json",
    "content": "{\n  \"name\": \"@tursodatabase/database\",\n  \"version\": \"0.6.0-pre.4\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/tursodatabase/turso\"\n  },\n  \"license\": \"MIT\",\n  \"module\": \"./dist/promise.js\",\n  \"main\": \"./dist/promise.js\",\n  \"type\": \"module\",\n  \"exports\": {\n    \".\": \"./dist/promise.js\",\n    \"./compat\": \"./dist/compat.js\"\n  },\n  \"files\": [\n    \"index.js\",\n    \"dist/**\",\n    \"README.md\"\n  ],\n  \"packageManager\": \"yarn@4.9.2\",\n  \"devDependencies\": {\n    \"@napi-rs/cli\": \"^3.1.5\",\n    \"@types/node\": \"^24.3.1\",\n    \"better-sqlite3\": \"^12.2.0\",\n    \"drizzle-kit\": \"^0.31.4\",\n    \"drizzle-orm\": \"^0.44.5\",\n    \"typescript\": \"^5.9.2\",\n    \"vitest\": \"^3.2.4\"\n  },\n  \"scripts\": {\n    \"napi-build\": \"napi build --platform --profile release-official --esm --manifest-path ../../Cargo.toml --output-dir .\",\n    \"napi-dirs\": \"napi create-npm-dirs\",\n    \"napi-artifacts\": \"napi artifacts --output-dir .\",\n    \"tsc-build\": \"npm exec tsc\",\n    \"build\": \"npm run napi-build && npm run tsc-build\",\n    \"test\": \"vitest --run\",\n    \"prepublishOnly\": \"npm run napi-dirs && npm run napi-artifacts && napi prepublish -t npm\"\n  },\n  \"napi\": {\n    \"binaryName\": \"turso\",\n    \"targets\": [\n      \"x86_64-unknown-linux-gnu\",\n      \"x86_64-pc-windows-msvc\",\n      \"aarch64-apple-darwin\",\n      \"aarch64-unknown-linux-gnu\"\n    ]\n  },\n  \"dependencies\": {\n    \"@tursodatabase/database-common\": \"^0.6.0-pre.4\"\n  },\n  \"imports\": {\n    \"#index\": \"./index.js\"\n  }\n}\n"
  },
  {
    "path": "bindings/javascript/packages/native/promise.test.ts",
    "content": "import { unlinkSync } from \"node:fs\";\nimport { expect, test } from 'vitest'\nimport { Database, connect } from './promise.js'\nimport { sql } from 'drizzle-orm';\nimport { drizzle } from 'drizzle-orm/better-sqlite3';\n\ntest('drizzle-orm', async () => {\n    const path = `test-${(Math.random() * 10000) | 0}.db`;\n    try {\n        const conn = await connect(path);\n        const db = drizzle(conn);\n        await db.run('CREATE TABLE t(x, y)');\n        let tasks = [];\n        for (let i = 0; i < 1234; i++) {\n            tasks.push(db.run(sql`INSERT INTO t VALUES (${i}, randomblob(${i} * 5))`))\n        }\n        await Promise.all(tasks);\n        expect(await db.all(\"SELECT COUNT(*) as cnt FROM t\")).toEqual([{ cnt: 1234 }])\n    } finally {\n        unlinkSync(path);\n        unlinkSync(`${path}-wal`);\n    }\n})\n\ntest('in-memory-db-async', async () => {\n    const db = await connect(\":memory:\");\n    await db.exec(\"CREATE TABLE t(x)\");\n    await db.exec(\"INSERT INTO t VALUES (1), (2), (3)\");\n    const stmt = db.prepare(\"SELECT * FROM t WHERE x % 2 = ?\");\n    const rows = await stmt.all([1]);\n    expect(rows).toEqual([{ x: 1 }, { x: 3 }]);\n})\n\ntest('exec multiple statements', async () => {\n    const db = await connect(\":memory:\");\n    await db.exec(\"CREATE TABLE t(x); INSERT INTO t VALUES (1); INSERT INTO t VALUES (2)\");\n    const stmt = db.prepare(\"SELECT * FROM t\");\n    const rows = await stmt.all();\n    expect(rows).toEqual([{ x: 1 }, { x: 2 }]);\n})\n\ntest('readonly-db', async () => {\n    const path = `test-${(Math.random() * 10000) | 0}.db`;\n    try {\n        {\n            const rw = await connect(path);\n            await rw.exec(\"CREATE TABLE t(x)\");\n            await rw.exec(\"INSERT INTO t VALUES (1)\");\n            rw.close();\n        }\n        {\n            const ro = await connect(path, { readonly: true });\n            await expect(async () => await ro.exec(\"INSERT INTO t VALUES (2)\")).rejects.toThrowError(/Resource is read-only/g);\n            expect(await ro.prepare(\"SELECT * FROM t\").all()).toEqual([{ x: 1 }])\n            ro.close();\n        }\n    } finally {\n        unlinkSync(path);\n        unlinkSync(`${path}-wal`);\n    }\n})\n\ntest('file-must-exist', async () => {\n    const path = `test-${(Math.random() * 10000) | 0}.db`;\n    await expect(async () => await connect(path, { fileMustExist: true })).rejects.toThrowError(/failed to open database/);\n})\n\ntest('implicit connect', async () => {\n    const db = new Database(':memory:');\n    const defer = db.prepare(\"SELECT * FROM t\");\n    await expect(async () => await defer.all()).rejects.toThrowError(/no such table: t/);\n    expect(() => db.prepare(\"SELECT * FROM t\")).toThrowError(/no such table: t/);\n    expect(await db.prepare(\"SELECT 1 as x\").all()).toEqual([{ x: 1 }]);\n})\n\ntest('zero-limit-bug', async () => {\n    const db = await connect(':memory:');\n    const create = db.prepare(`CREATE TABLE users (name TEXT NOT NULL);`);\n    await create.run();\n\n    const insert = db.prepare(\n        `insert into \"users\" values (?), (?), (?);`,\n    );\n    await insert.run('John', 'Jane', 'Jack');\n\n    const stmt1 = db.prepare(`select * from \"users\" limit ?;`);\n    expect(await stmt1.all(0)).toEqual([]);\n    let rows = [{ name: 'John' }, { name: 'Jane' }, { name: 'Jack' }, { name: 'John' }, { name: 'Jane' }, { name: 'Jack' }];\n    for (const limit of [0, 1, 2, 3, 4, 5, 6, 7]) {\n        const stmt2 = db.prepare(`select * from \"users\" union all select * from \"users\" limit ?;`);\n        expect(await stmt2.all(limit)).toEqual(rows.slice(0, Math.min(limit, 6)));\n    }\n})\n\ntest('avg-bug', async () => {\n    const db = await connect(':memory:');\n    const create = db.prepare(`create table \"aggregate_table\" (\n        \"id\" integer primary key autoincrement not null,\n        \"name\" text not null,\n        \"a\" integer,\n        \"b\" integer,\n        \"c\" integer,\n        \"null_only\" integer\n    );`);\n\n    await create.run();\n    const insert = db.prepare(\n        `insert into \"aggregate_table\" (\"id\", \"name\", \"a\", \"b\", \"c\", \"null_only\") values (null, ?, ?, ?, ?, null), (null, ?, ?, ?, ?, null), (null, ?, ?, ?, ?, null), (null, ?, ?, ?, ?, null), (null, ?, ?, ?, ?, null), (null, ?, ?, ?, ?, null), (null, ?, ?, ?, ?, null);`,\n    );\n\n    await insert.run(\n        'value 1', 5, 10, 20,\n        'value 1', 5, 20, 30,\n        'value 2', 10, 50, 60,\n        'value 3', 20, 20, null,\n        'value 4', null, 90, 120,\n        'value 5', 80, 10, null,\n        'value 6', null, null, 150,\n    );\n\n    expect(await db.prepare(`select avg(\"a\") from \"aggregate_table\";`).get()).toEqual({ 'avg (aggregate_table.a)': 24 });\n    expect(await db.prepare(`select avg(\"null_only\") from \"aggregate_table\";`).get()).toEqual({ 'avg (aggregate_table.null_only)': null });\n    expect(await db.prepare(`select avg(distinct \"b\") from \"aggregate_table\";`).get()).toEqual({ 'avg (DISTINCT aggregate_table.b)': 42.5 });\n})\n\ntest('insert returning test', async () => {\n    const db = await connect(':memory:');\n    await db.prepare(`create table t (x);`).run();\n    const x1 = await db.prepare(`insert into t values (1), (2) returning x`).get();\n    const x2 = await db.prepare(`insert into t values (3), (4) returning x`).get();\n    expect(x1).toEqual({ x: 1 });\n    expect(x2).toEqual({ x: 3 });\n    const all = await db.prepare(`select * from t`).all();\n    expect(all).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }, { x: 4 }])\n})\n\ntest('offset-bug', async () => {\n    const db = await connect(\":memory:\");\n    await db.exec(`CREATE TABLE users (\n        id INTEGER PRIMARY KEY,\n        name TEXT NOT NULL,\n        verified integer not null default 0\n    );`);\n    const insert = db.prepare(`INSERT INTO users (name) VALUES (?),(?);`);\n    await insert.run('John', 'John1');\n\n    const stmt = db.prepare(`SELECT * FROM users LIMIT ? OFFSET ?;`);\n    expect(await stmt.all(1, 1)).toEqual([{ id: 2, name: 'John1', verified: 0 }])\n})\n\ntest('conflict-bug', async () => {\n    const db = await connect(':memory:');\n\n    const create = db.prepare(`create table \"conflict_chain_example\" (\n        id integer not null unique,\n        name text not null,\n        email text not null,\n        primary key (id, name)\n    )`);\n    await create.run();\n\n    await db.prepare(`insert into \"conflict_chain_example\" (\"id\", \"name\", \"email\") values (?, ?, ?), (?, ?, ?)`).run(\n        1,\n        'John',\n        'john@example.com',\n        2,\n        'John Second',\n        '2john@example.com',\n    );\n\n    const insert = db.prepare(\n        `insert into \"conflict_chain_example\" (\"id\", \"name\", \"email\") values (?, ?, ?), (?, ?, ?) on conflict (\"conflict_chain_example\".\"id\", \"conflict_chain_example\".\"name\") do update set \"email\" = ? on conflict (\"conflict_chain_example\".\"id\") do nothing`,\n    );\n    await insert.run(1, 'John', 'john@example.com', 2, 'Anthony', 'idthief@example.com', 'john1@example.com');\n\n    expect(await db.prepare(\"SELECT * FROM conflict_chain_example\").all()).toEqual([\n        { id: 1, name: 'John', email: 'john1@example.com' },\n        { id: 2, name: 'John Second', email: '2john@example.com' }\n    ]);\n})\n\ntest('on-disk db', async () => {\n    const path = `test-${(Math.random() * 10000) | 0}.db`;\n    try {\n        const db1 = await connect(path);\n        await db1.exec(\"CREATE TABLE t(x)\");\n        await db1.exec(\"INSERT INTO t VALUES (1), (2), (3)\");\n        const stmt1 = db1.prepare(\"SELECT * FROM t WHERE x % 2 = ?\");\n        expect(stmt1.columns()).toEqual([{ name: \"x\", column: null, database: null, table: null, type: null }]);\n        const rows1 = await stmt1.all([1]);\n        expect(rows1).toEqual([{ x: 1 }, { x: 3 }]);\n        db1.close();\n\n        const db2 = await connect(path);\n        const stmt2 = db2.prepare(\"SELECT * FROM t WHERE x % 2 = ?\");\n        expect(stmt2.columns()).toEqual([{ name: \"x\", column: null, database: null, table: null, type: null }]);\n        const rows2 = await stmt2.all([1]);\n        expect(rows2).toEqual([{ x: 1 }, { x: 3 }]);\n        db2.close();\n    } finally {\n        unlinkSync(path);\n        unlinkSync(`${path}-wal`);\n    }\n})\n\ntest('attach', async () => {\n    const path1 = `test-${(Math.random() * 10000) | 0}.db`;\n    const path2 = `test-${(Math.random() * 10000) | 0}.db`;\n    try {\n        const db1 = await connect(path1, { experimental: [\"attach\"] });\n        await db1.exec(\"CREATE TABLE t(x)\");\n        await db1.exec(\"INSERT INTO t VALUES (1), (2), (3)\");\n        const db2 = await connect(path2, { experimental: [\"attach\"] });\n        await db2.exec(\"CREATE TABLE q(x)\");\n        await db2.exec(\"INSERT INTO q VALUES (4), (5), (6)\");\n\n        await db1.exec(`ATTACH '${path2}' as secondary`);\n\n        const stmt = db1.prepare(\"SELECT * FROM t UNION ALL SELECT * FROM secondary.q\");\n        expect(stmt.columns()).toEqual([{ name: \"x\", column: null, database: null, table: null, type: null }]);\n        const rows = await stmt.all([1]);\n        expect(rows).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }, { x: 4 }, { x: 5 }, { x: 6 }]);\n    } finally {\n        unlinkSync(path1);\n        unlinkSync(`${path1}-wal`);\n        unlinkSync(path2);\n        unlinkSync(`${path2}-wal`);\n    }\n})\n\ntest('fts', async () => {\n    const db = await connect(\":memory:\", { experimental: [\"index_method\"] });\n    await db.exec(`\n        CREATE TABLE documents (id INTEGER PRIMARY KEY, title TEXT, body TEXT);\n        INSERT INTO documents VALUES (1, 'Introduction to Rust', 'Rust is a systems programming language focused on safety and performance');\n        INSERT INTO documents VALUES (2, 'JavaScript Guide', 'JavaScript is a dynamic programming language used for web development');\n        INSERT INTO documents VALUES (3, 'Database Internals', 'Understanding how databases store and retrieve data efficiently');\n        CREATE INDEX documents_fts ON documents USING fts (title, body);\n    `);\n\n    // fts_match search\n    const matchResults = await db.prepare(\n        \"SELECT id, title, fts_score(title, body, 'programming language') as score FROM documents WHERE fts_match(title, body, 'programming language')\"\n    ).all();\n    expect(matchResults.length).toBe(2);\n    expect(matchResults.map(r => r.id).sort()).toEqual([1, 2]);\n    for (const row of matchResults) {\n        expect(row.score).toBeGreaterThan(0);\n    }\n\n    // fts_highlight\n    const highlightResults = await db.prepare(\n        \"SELECT id, fts_highlight(title, '<b>', '</b>', 'Rust') as highlighted FROM documents WHERE fts_match(title, body, 'Rust')\"\n    ).all();\n    expect(highlightResults.length).toBe(1);\n    expect(highlightResults[0].id).toBe(1);\n    expect(highlightResults[0].highlighted).toContain('<b>');\n    expect(highlightResults[0].highlighted).toContain('Rust');\n\n    // no match\n    const noResults = await db.prepare(\n        \"SELECT * FROM documents WHERE fts_match(title, body, 'nonexistentterm')\"\n    ).all();\n    expect(noResults.length).toBe(0);\n})\n\ntest('blobs', async () => {\n    const db = await connect(\":memory:\");\n    const rows = await db.prepare(\"SELECT x'1020' as x\").all();\n    expect(rows).toEqual([{ x: Buffer.from([16, 32]) }])\n})\n\n\ntest('example-1', async () => {\n    const db = await connect(':memory:');\n    await db.exec('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)');\n\n    const insert = db.prepare('INSERT INTO users (name, email) VALUES (?, ?)');\n    await insert.run('Alice', 'alice@example.com');\n    await insert.run('Bob', 'bob@example.com');\n\n    const users = await db.prepare('SELECT * FROM users').all();\n    expect(users).toEqual([\n        { id: 1, name: 'Alice', email: 'alice@example.com' },\n        { id: 2, name: 'Bob', email: 'bob@example.com' }\n    ]);\n})\n\ntest('example-2', async () => {\n    const db = await connect(':memory:');\n    await db.exec('CREATE TABLE users (name, email)');\n    // Using transactions for atomic operations\n    const transaction = db.transaction(async (users) => {\n        const insert = db.prepare('INSERT INTO users (name, email) VALUES (?, ?)');\n        for (const user of users) {\n            await insert.run(user.name, user.email);\n        }\n    });\n\n    // Execute transaction\n    await transaction([\n        { name: 'Alice', email: 'alice@example.com' },\n        { name: 'Bob', email: 'bob@example.com' }\n    ]);\n\n    const rows = await db.prepare('SELECT * FROM users').all();\n    expect(rows).toEqual([\n        { name: 'Alice', email: 'alice@example.com' },\n        { name: 'Bob', email: 'bob@example.com' }\n    ]);\n})"
  },
  {
    "path": "bindings/javascript/packages/native/promise.ts",
    "content": "import { DatabasePromise, NativeDatabase, SqliteError, DatabaseOpts } from \"@tursodatabase/database-common\"\nimport { Database as NativeDB } from \"#index\";\n\nclass Database extends DatabasePromise {\n    constructor(path: string, opts: DatabaseOpts = {}) {\n        super(new NativeDB(path, opts) as unknown as NativeDatabase)\n    }\n}\n\n/**\n * Creates a new database connection asynchronously.\n * \n * @param {string} path - Path to the database file.\n * @param {Object} opts - Options for database behavior.\n * @returns {Promise<Database>} - A promise that resolves to a Database instance.\n */\nasync function connect(path: string, opts: DatabaseOpts = {}): Promise<Database> {\n    const db = new Database(path, opts);\n    await db.connect();\n    return db;\n}\n\nexport { connect, Database, SqliteError }\n"
  },
  {
    "path": "bindings/javascript/packages/native/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"skipLibCheck\": true,\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"module\": \"nodenext\",\n    \"target\": \"esnext\",\n    \"outDir\": \"dist/\",\n    \"lib\": [\n      \"es2020\"\n    ],\n    \"paths\": {\n      \"#index\": [\n        \"./index.js\"\n      ]\n    }\n  },\n  \"include\": [\n    \"*\"\n  ]\n}"
  },
  {
    "path": "bindings/javascript/packages/native/turso-sql-runner-split.test.ts",
    "content": "import { expect, test } from 'vitest'\nimport { splitStatements } from '../../turso-sql-split.mjs'\n\ntest('splitStatements keeps trigger body as one statement', () => {\n    const sql = `\n        CREATE TRIGGER log_insert AFTER INSERT ON users BEGIN\n            INSERT INTO log VALUES('inserted');\n            UPDATE stats SET count = count + 1;\n        END;\n    `;\n\n    expect(splitStatements(sql)).toEqual([\n        `CREATE TRIGGER log_insert AFTER INSERT ON users BEGIN\n            INSERT INTO log VALUES('inserted');\n            UPDATE stats SET count = count + 1;\n        END;`\n    ]);\n})\n\ntest('splitStatements handles trigger followed by select', () => {\n    const sql = `\n        CREATE TRIGGER log_insert AFTER INSERT ON users BEGIN\n            INSERT INTO log VALUES('inserted');\n        END;\n        SELECT 1;\n    `;\n\n    expect(splitStatements(sql)).toEqual([\n        `CREATE TRIGGER log_insert AFTER INSERT ON users BEGIN\n            INSERT INTO log VALUES('inserted');\n        END;`,\n        'SELECT 1;'\n    ]);\n})\n\ntest('splitStatements ignores END inside trigger string literal', () => {\n    const sql = `\n        CREATE TRIGGER log_insert AFTER INSERT ON users BEGIN\n            INSERT INTO log VALUES('END');\n        END;\n    `;\n\n    expect(splitStatements(sql)).toEqual([\n        `CREATE TRIGGER log_insert AFTER INSERT ON users BEGIN\n            INSERT INTO log VALUES('END');\n        END;`\n    ]);\n})\n\ntest('splitStatements handles create temp trigger and explain create trigger', () => {\n    const tempTrigger = `\n        CREATE TEMP TRIGGER log_insert AFTER INSERT ON users BEGIN\n            INSERT INTO log VALUES('inserted');\n        END;\n    `;\n\n    const explainTrigger = `\n        EXPLAIN CREATE TRIGGER log_insert AFTER INSERT ON users BEGIN\n            INSERT INTO log VALUES('inserted');\n        END;\n    `;\n\n    expect(splitStatements(tempTrigger)).toHaveLength(1);\n    expect(splitStatements(explainTrigger)).toHaveLength(1);\n})\n"
  },
  {
    "path": "bindings/javascript/packages/wasm/README.md",
    "content": "<p align=\"center\">\n  <h1 align=\"center\">Turso Database for JavaScript in Browser</h1>\n</p>\n\n<p align=\"center\">\n  <a title=\"JavaScript\" target=\"_blank\" href=\"https://www.npmjs.com/package/@tursodatabase/database\"><img alt=\"npm\" src=\"https://img.shields.io/npm/v/@tursodatabase/database\"></a>\n  <a title=\"MIT\" target=\"_blank\" href=\"https://github.com/tursodatabase/turso/blob/main/LICENSE.md\"><img src=\"http://img.shields.io/badge/license-MIT-orange.svg?style=flat-square\"></a>\n</p>\n<p align=\"center\">\n  <a title=\"Users Discord\" target=\"_blank\" href=\"https://tur.so/discord\"><img alt=\"Chat with other users of Turso on Discord\" src=\"https://img.shields.io/discord/933071162680958986?label=Discord&logo=Discord&style=social\"></a>\n</p>\n\n---\n\n## About\n\nThis package is the Turso embedded database library for JavaScript in Browser.\n\n> **⚠️ Warning:** This software is in BETA. It may still contain bugs and unexpected behavior. Use caution with production data and ensure you have backups.\n\n## Features\n\n- **SQLite compatible:** SQLite query language and file format support ([status](https://github.com/tursodatabase/turso/blob/main/COMPAT.md)).\n- **In-process**: No network overhead, runs directly in your Node.js process\n- **TypeScript support**: Full TypeScript definitions included\n\n## Installation\n\n```bash\nnpm install @tursodatabase/database-wasm\n```\n\n## Getting Started\n\n### In-Memory Database\n\n```javascript\nimport { connect } from '@tursodatabase/database-wasm';\n\n// Create an in-memory database\nconst db = await connect(':memory:');\n\n// Create a table\nawait db.exec('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)');\n\n// Insert data\nconst insert = db.prepare('INSERT INTO users (name, email) VALUES (?, ?)');\nawait insert.run('Alice', 'alice@example.com');\nawait insert.run('Bob', 'bob@example.com');\n\n// Query data\nconst users = await db.prepare('SELECT * FROM users').all();\nconsole.log(users);\n// Output: [\n//   { id: 1, name: 'Alice', email: 'alice@example.com' },\n//   { id: 2, name: 'Bob', email: 'bob@example.com' }\n// ]\n```\n\n### File-Based Database\n\n```javascript\nimport { connect } from '@tursodatabase/database-wasm';\n\n// Create or open a database file\nconst db = await connect('my-database.db');\n\n// Create a table\nawait db.exec(`\n  CREATE TABLE IF NOT EXISTS posts (\n    id INTEGER PRIMARY KEY AUTOINCREMENT,\n    title TEXT NOT NULL,\n    content TEXT,\n    created_at DATETIME DEFAULT CURRENT_TIMESTAMP\n  )\n`);\n\n// Insert a post\nconst insertPost = db.prepare('INSERT INTO posts (title, content) VALUES (?, ?)');\nconst result = await insertPost.run('Hello World', 'This is my first blog post!');\n\nconsole.log(`Inserted post with ID: ${result.lastInsertRowid}`);\n```\n\n### Transactions\n\n```javascript\nimport { connect } from '@tursodatabase/database-wasm';\n\nconst db = await connect('transactions.db');\n\n// Using transactions for atomic operations\nconst transaction = db.transaction(async (users) => {\n  const insert = db.prepare('INSERT INTO users (name, email) VALUES (?, ?)');\n  for (const user of users) {\n    await insert.run(user.name, user.email);\n  }\n});\n\n// Execute transaction\nawait transaction([\n  { name: 'Alice', email: 'alice@example.com' },\n  { name: 'Bob', email: 'bob@example.com' }\n]);\n```\n\n## API Reference\n\nFor complete API documentation, see [JavaScript API Reference](https://github.com/tursodatabase/turso/blob/main/docs/javascript-api-reference.md).\n\n## Related Packages\n\n* The [@tursodatabase/serverless](https://www.npmjs.com/package/@tursodatabase/serverless) package provides a serverless driver with the same API.\n* The [@tursodatabase/sync](https://www.npmjs.com/package/@tursodatabase/sync) package provides bidirectional sync between a local Turso database and Turso Cloud. \n\n## License\n\nThis project is licensed under the [MIT license](https://github.com/tursodatabase/turso/blob/main/LICENSE.md).\n\n## Support\n\n- [GitHub Issues](https://github.com/tursodatabase/turso/issues)\n- [Documentation](https://docs.turso.tech)\n- [Discord Community](https://tur.so/discord)\n"
  },
  {
    "path": "bindings/javascript/packages/wasm/index-bundle.ts",
    "content": "import { setupMainThread } from \"@tursodatabase/database-wasm-common\";\n//@ts-ignore\nimport TursoWorker from \"./worker.js?worker&inline\";\n\nconst __wasmUrl = new URL('./turso.wasm32-wasi.wasm', import.meta.url).href;\nconst __wasmFile = await fetch(__wasmUrl).then((res) => res.arrayBuffer())\n\nexport let MainWorker = null;\nconst napiModule = await setupMainThread(__wasmFile, () => {\n  const worker = new TursoWorker({\n    name: 'turso-database',\n    type: 'module',\n  })\n  MainWorker = worker;\n  return worker\n});\n\nexport default napiModule.exports\nexport const Database = napiModule.exports.Database\nexport const Opfs = napiModule.exports.Opfs\nexport const OpfsFile = napiModule.exports.OpfsFile\nexport const Statement = napiModule.exports.Statement\nexport const initThreadPool = napiModule.exports.initThreadPool\n"
  },
  {
    "path": "bindings/javascript/packages/wasm/index-default.ts",
    "content": "import { setupMainThread } from \"@tursodatabase/database-wasm-common\";\n\nconst __wasmUrl = new URL('./turso.wasm32-wasi.wasm', import.meta.url).href;\nconst __wasmFile = await fetch(__wasmUrl).then((res) => res.arrayBuffer())\n\nexport let MainWorker = null;\nconst napiModule = await setupMainThread(__wasmFile, () => {\n  const worker = new Worker(new URL('./worker.js', import.meta.url), {\n    name: 'turso-database',\n    type: 'module',\n  })\n  MainWorker = worker;\n  return worker\n});\n\nexport default napiModule.exports\nexport const Database = napiModule.exports.Database\nexport const Opfs = napiModule.exports.Opfs\nexport const OpfsFile = napiModule.exports.OpfsFile\nexport const Statement = napiModule.exports.Statement\nexport const initThreadPool = napiModule.exports.initThreadPool\n"
  },
  {
    "path": "bindings/javascript/packages/wasm/index-turbopack-hack.ts",
    "content": "import { setupMainThread } from \"@tursodatabase/database-wasm-common\";\nimport { tursoWasm } from \"./wasm-inline.js\";\n\n// Next (turbopack) has issues with loading wasm module: https://github.com/vercel/next.js/issues/82520\n// So, we inline wasm binary in the source code in order to avoid issues with loading it from the file\nconst __wasmFile = await tursoWasm();\n\nexport let MainWorker = null;\n\nconst napiModule = await setupMainThread(__wasmFile, () => {\n  const worker = new Worker(new URL('./worker.js', import.meta.url), {\n    name: 'turso-database',\n    type: 'module',\n  })\n  MainWorker = worker;\n  return worker\n});\n\nexport default napiModule.exports\nexport const Database = napiModule.exports.Database\nexport const Opfs = napiModule.exports.Opfs\nexport const OpfsFile = napiModule.exports.OpfsFile\nexport const Statement = napiModule.exports.Statement\nexport const initThreadPool = napiModule.exports.initThreadPool\n"
  },
  {
    "path": "bindings/javascript/packages/wasm/index-vite-dev-hack.ts",
    "content": "import { isWebWorker, setupMainThread, setupWebWorker } from \"@tursodatabase/database-wasm-common\";\nimport { tursoWasm } from \"./wasm-inline.js\";\n\nlet napiModule = {\n  exports: {\n    Database: {} as any,\n    Opfs: {} as any,\n    OpfsFile: {} as any,\n    Statement: {} as any,\n    initThreadPool: {} as any,\n  }\n};\n\nexport let MainWorker = null;\nif (isWebWorker()) {\n  setupWebWorker();\n} else {\n  // Vite has issues with loading wasm modules and worker in dev server: https://github.com/vitejs/vite/issues/8427\n  // So, the mitigation for dev server only is:\n  // 1. inline wasm binary in the source code in order to avoid issues with loading it from the file\n  // 2. use same file as worker entry point\n  const __wasmFile = await tursoWasm();\n\n  napiModule = await setupMainThread(__wasmFile, () => {\n    const worker = new Worker(import.meta.url, {\n      name: 'turso-database',\n      type: 'module',\n    })\n    MainWorker = worker;\n    return worker\n  });\n}\n\nexport default napiModule.exports\nexport const Database = napiModule.exports.Database\nexport const Opfs = napiModule.exports.Opfs\nexport const OpfsFile = napiModule.exports.OpfsFile\nexport const Statement = napiModule.exports.Statement\nexport const initThreadPool = napiModule.exports.initThreadPool\n"
  },
  {
    "path": "bindings/javascript/packages/wasm/package.json",
    "content": "{\n  \"name\": \"@tursodatabase/database-wasm\",\n  \"version\": \"0.6.0-pre.4\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/tursodatabase/turso\"\n  },\n  \"type\": \"module\",\n  \"license\": \"MIT\",\n  \"main\": \"./dist/promise-default.js\",\n  \"packageManager\": \"yarn@4.9.2\",\n  \"files\": [\n    \"dist/**\",\n    \"bundle/**\",\n    \"README.md\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"default\": \"./dist/promise-default.js\"\n    },\n    \"./bundle\": {\n      \"default\": \"./bundle/main.es.js\"\n    },\n    \"./vite\": {\n      \"development\": \"./dist/promise-vite-dev-hack.js\",\n      \"default\": \"./dist/promise-default.js\"\n    },\n    \"./turbopack\": {\n      \"default\": \"./dist/promise-turbopack-hack.js\"\n    }\n  },\n  \"devDependencies\": {\n    \"@napi-rs/cli\": \"^3.1.5\",\n    \"@vitest/browser\": \"^3.2.4\",\n    \"playwright\": \"^1.55.0\",\n    \"typescript\": \"^5.9.2\",\n    \"vite\": \"^7.1.5\",\n    \"vitest\": \"^3.2.4\"\n  },\n  \"scripts\": {\n    \"napi-build\": \"napi build --features browser --profile release-official --platform --target wasm32-wasip1-threads --no-js --manifest-path ../../Cargo.toml --output-dir . && rm index.d.ts turso.wasi* wasi* browser.js\",\n    \"tsc-build\": \"npm exec tsc && cp turso.wasm32-wasi.wasm ./dist/turso.wasm32-wasi.wasm && WASM_FILE=turso.wasm32-wasi.wasm JS_FILE=./dist/wasm-inline.js node ../../scripts/inline-wasm-base64.js && npm run bundle\",\n    \"bundle\": \"vite build\",\n    \"build\": \"npm run napi-build && npm run tsc-build\",\n    \"test\": \"CI=1 vitest --browser=chromium --run && CI=1 vitest --browser=firefox --run\"\n  },\n  \"napi\": {\n    \"binaryName\": \"turso\",\n    \"targets\": [\n      \"wasm32-wasip1-threads\"\n    ]\n  },\n  \"dependencies\": {\n    \"@tursodatabase/database-common\": \"^0.6.0-pre.4\",\n    \"@tursodatabase/database-wasm-common\": \"^0.6.0-pre.4\"\n  }\n}\n"
  },
  {
    "path": "bindings/javascript/packages/wasm/promise-bundle.ts",
    "content": "import { DatabasePromise, DatabaseOpts, SqliteError, } from \"@tursodatabase/database-common\"\nimport { registerFileAtWorker, unregisterFileAtWorker, ioNotifier } from \"@tursodatabase/database-wasm-common\";\nimport { initThreadPool, MainWorker, Database as NativeDatabase } from \"./index-bundle.js\";\n\nasync function init(): Promise<Worker> {\n    await initThreadPool();\n    if (MainWorker == null) {\n        throw new Error(\"panic: MainWorker is not initialized\");\n    }\n    return MainWorker;\n}\n\nclass Database extends DatabasePromise {\n    #worker: Worker | null;\n    constructor(path: string, opts: DatabaseOpts = {}) {\n        super(\n            new NativeDatabase(path, opts) as unknown as any,\n            () => ioNotifier.waitForCompletion(),\n        )\n    }\n    /**\n     * connect database and pre-open necessary files in the OPFS\n     */\n    override async connect() {\n        if (!this.memory) {\n            const worker = await init();\n            await Promise.all([\n                registerFileAtWorker(worker, this.name),\n                registerFileAtWorker(worker, `${this.name}-wal`)\n            ]);\n            this.#worker = worker;\n        }\n        await super.connect();\n    }\n    /**\n     * close the database and relevant files\n     */\n    async close() {\n        if (this.name != null && this.#worker != null) {\n            await Promise.all([\n                unregisterFileAtWorker(this.#worker, this.name),\n                unregisterFileAtWorker(this.#worker, `${this.name}-wal`)\n            ]);\n        }\n        await super.close();\n    }\n}\n\n/**\n * Creates a new database connection asynchronously.\n * \n * @param {string} path - Path to the database file.\n * @param {Object} opts - Options for database behavior.\n * @returns {Promise<Database>} - A promise that resolves to a Database instance.\n */\nasync function connect(path: string, opts: DatabaseOpts = {}): Promise<Database> {\n    const db = new Database(path, opts);\n    await db.connect();\n    return db;\n}\n\nexport { connect, Database, SqliteError }\n"
  },
  {
    "path": "bindings/javascript/packages/wasm/promise-default.ts",
    "content": "import { DatabasePromise, DatabaseOpts, SqliteError, } from \"@tursodatabase/database-common\"\nimport { registerFileAtWorker, unregisterFileAtWorker, ioNotifier } from \"@tursodatabase/database-wasm-common\";\nimport { initThreadPool, MainWorker, Database as NativeDatabase } from \"./index-default.js\";\n\nasync function init(): Promise<Worker> {\n    await initThreadPool();\n    if (MainWorker == null) {\n        throw new Error(\"panic: MainWorker is not initialized\");\n    }\n    return MainWorker;\n}\n\nclass Database extends DatabasePromise {\n    #worker: Worker | null;\n    constructor(path: string, opts: DatabaseOpts = {}) {\n        const nativeDb = new NativeDatabase(path, opts);\n        super(\n            nativeDb as unknown as any,\n            // In-memory databases use MemoryIO which completes I/O synchronously,\n            // so there's no OPFS Worker dispatch and the IONotifier would never fire.\n            // Use undefined (defaults to no-op) so the step loop retries immediately.\n            (nativeDb as any).memory ? undefined : () => ioNotifier.waitForCompletion(),\n        )\n    }\n    /**\n     * connect database and pre-open necessary files in the OPFS\n     */\n    override async connect() {\n        if (!this.memory) {\n            const worker = await init();\n            await Promise.all([\n                registerFileAtWorker(worker, this.name),\n                registerFileAtWorker(worker, `${this.name}-wal`)\n            ]);\n            this.#worker = worker;\n        }\n        await super.connect();\n    }\n    /**\n     * close the database and relevant files\n     */\n    async close() {\n        if (this.name != null && this.#worker != null) {\n            await Promise.all([\n                unregisterFileAtWorker(this.#worker, this.name),\n                unregisterFileAtWorker(this.#worker, `${this.name}-wal`)\n            ]);\n        }\n        await super.close();\n    }\n}\n\n/**\n * Creates a new database connection asynchronously.\n * \n * @param {string} path - Path to the database file.\n * @param {Object} opts - Options for database behavior.\n * @returns {Promise<Database>} - A promise that resolves to a Database instance.\n */\nasync function connect(path: string, opts: DatabaseOpts = {}): Promise<Database> {\n    const db = new Database(path, opts);\n    await db.connect();\n    return db;\n}\n\nexport { connect, Database, SqliteError }\n"
  },
  {
    "path": "bindings/javascript/packages/wasm/promise-turbopack-hack.ts",
    "content": "import { DatabasePromise, DatabaseOpts, SqliteError, } from \"@tursodatabase/database-common\"\nimport { registerFileAtWorker, unregisterFileAtWorker, ioNotifier } from \"@tursodatabase/database-wasm-common\";\nimport { initThreadPool, MainWorker, Database as NativeDatabase } from \"./index-turbopack-hack.js\";\n\nasync function init(): Promise<Worker> {\n    await initThreadPool();\n    if (MainWorker == null) {\n        throw new Error(\"panic: MainWorker is not initialized\");\n    }\n    return MainWorker;\n}\n\nclass Database extends DatabasePromise {\n    #worker: Worker | null;\n    constructor(path: string, opts: DatabaseOpts = {}) {\n        super(\n            new NativeDatabase(path, opts) as unknown as any,\n            () => ioNotifier.waitForCompletion(),\n        )\n    }\n    /**\n     * connect database and pre-open necessary files in the OPFS\n     */\n    override async connect() {\n        if (!this.memory) {\n            const worker = await init();\n            await Promise.all([\n                registerFileAtWorker(worker, this.name),\n                registerFileAtWorker(worker, `${this.name}-wal`)\n            ]);\n            this.#worker = worker;\n        }\n        await super.connect();\n    }\n    /**\n     * close the database and relevant files\n     */\n    async close() {\n        if (this.name != null && this.#worker != null) {\n            await Promise.all([\n                unregisterFileAtWorker(this.#worker, this.name),\n                unregisterFileAtWorker(this.#worker, `${this.name}-wal`)\n            ]);\n        }\n        await super.close();\n    }\n}\n\n/**\n * Creates a new database connection asynchronously.\n * \n * @param {string} path - Path to the database file.\n * @param {Object} opts - Options for database behavior.\n * @returns {Promise<Database>} - A promise that resolves to a Database instance.\n */\nasync function connect(path: string, opts: DatabaseOpts = {}): Promise<Database> {\n    const db = new Database(path, opts);\n    await db.connect();\n    return db;\n}\n\nexport { connect, Database, SqliteError }\n"
  },
  {
    "path": "bindings/javascript/packages/wasm/promise-vite-dev-hack.ts",
    "content": "import { DatabasePromise, DatabaseOpts, SqliteError, } from \"@tursodatabase/database-common\"\nimport { registerFileAtWorker, unregisterFileAtWorker, ioNotifier } from \"@tursodatabase/database-wasm-common\";\nimport { initThreadPool, MainWorker, Database as NativeDatabase } from \"./index-vite-dev-hack.js\";\n\nasync function init(): Promise<Worker> {\n    await initThreadPool();\n    if (MainWorker == null) {\n        throw new Error(\"panic: MainWorker is not initialized\");\n    }\n    return MainWorker;\n}\n\nclass Database extends DatabasePromise {\n    #worker: Worker | null;\n    constructor(path: string, opts: DatabaseOpts = {}) {\n        super(\n            new NativeDatabase(path, opts) as unknown as any,\n            () => ioNotifier.waitForCompletion(),\n        )\n    }\n    /**\n     * connect database and pre-open necessary files in the OPFS\n     */\n    override async connect() {\n        if (!this.memory) {\n            const worker = await init();\n            await Promise.all([\n                registerFileAtWorker(worker, this.name),\n                registerFileAtWorker(worker, `${this.name}-wal`)\n            ]);\n            this.#worker = worker;\n        }\n        await super.connect();\n    }\n    /**\n     * close the database and relevant files\n     */\n    async close() {\n        if (this.name != null && this.#worker != null) {\n            await Promise.all([\n                unregisterFileAtWorker(this.#worker, this.name),\n                unregisterFileAtWorker(this.#worker, `${this.name}-wal`)\n            ]);\n        }\n        await super.close();\n    }\n}\n\n/**\n * Creates a new database connection asynchronously.\n * \n * @param {string} path - Path to the database file.\n * @param {Object} opts - Options for database behavior.\n * @returns {Promise<Database>} - A promise that resolves to a Database instance.\n */\nasync function connect(path: string, opts: DatabaseOpts = {}): Promise<Database> {\n    const db = new Database(path, opts);\n    await db.connect();\n    return db;\n}\n\nexport { connect, Database, SqliteError }\n"
  },
  {
    "path": "bindings/javascript/packages/wasm/promise.test.ts",
    "content": "import { expect, test, afterAll, beforeEach, afterEach } from 'vitest'\nimport { connect, Database } from './promise-default.js'\nimport { MainWorker } from './index-default.js'\n\nbeforeEach((ctx) => {\n    console.log(`[test:start] ${ctx.task.name}`);\n})\n\nafterEach((ctx) => {\n    console.log(`[test:end] ${ctx.task.name} (${ctx.task.result?.state})`);\n})\n\nafterAll(() => {\n    console.log('[afterAll] terminating MainWorker');\n    MainWorker?.terminate();\n    console.log('[afterAll] MainWorker terminated');\n})\n\ntest('vector-test', async () => {\n    const db = await connect(\":memory:\");\n    const v1 = new Array(1024).fill(0).map((_, i) => i);\n    const v2 = new Array(1024).fill(0).map((_, i) => 1024 - i);\n    const result = await db.prepare(`SELECT\n        vector_distance_cos(vector32('${JSON.stringify(v1)}'), vector32('${JSON.stringify(v2)}')) as cosf32,\n        vector_distance_cos(vector64('${JSON.stringify(v1)}'), vector64('${JSON.stringify(v2)}')) as cosf64,\n        vector_distance_l2(vector32('${JSON.stringify(v1)}'), vector32('${JSON.stringify(v2)}')) as l2f32,\n        vector_distance_l2(vector64('${JSON.stringify(v1)}'), vector64('${JSON.stringify(v2)}')) as l2f64\n    `).all();\n    console.info(result);\n    await db.close();\n})\n\ntest('explain', async () => {\n    const db = await connect(\":memory:\");\n    const stmt = db.prepare(\"EXPLAIN SELECT 1\");\n    expect(stmt.columns()).toEqual([\n        {\n            \"name\": \"addr\",\n            \"type\": \"INTEGER\",\n        },\n        {\n            \"name\": \"opcode\",\n            \"type\": \"TEXT\",\n        },\n        {\n            \"name\": \"p1\",\n            \"type\": \"INTEGER\",\n        },\n        {\n            \"name\": \"p2\",\n            \"type\": \"INTEGER\",\n        },\n        {\n            \"name\": \"p3\",\n            \"type\": \"INTEGER\",\n        },\n        {\n            \"name\": \"p4\",\n            \"type\": \"TEXT\",\n        },\n        {\n            \"name\": \"p5\",\n            \"type\": \"INTEGER\",\n        },\n        {\n            \"name\": \"comment\",\n            \"type\": \"TEXT\",\n        },\n    ].map(x => ({ ...x, column: null, database: null, table: null })));\n    expect(await stmt.all()).toEqual([\n        {\n            \"addr\": 0,\n            \"comment\": \"Start at 3\",\n            \"opcode\": \"Init\",\n            \"p1\": 0,\n            \"p2\": 3,\n            \"p3\": 0,\n            \"p4\": \"\",\n            \"p5\": 0,\n        },\n        {\n            \"addr\": 1,\n            \"comment\": \"output=r[1]\",\n            \"opcode\": \"ResultRow\",\n            \"p1\": 1,\n            \"p2\": 1,\n            \"p3\": 0,\n            \"p4\": \"\",\n            \"p5\": 0,\n        },\n        {\n            \"addr\": 2,\n            \"comment\": \"\",\n            \"opcode\": \"Halt\",\n            \"p1\": 0,\n            \"p2\": 0,\n            \"p3\": 0,\n            \"p4\": \"\",\n            \"p5\": 0,\n        },\n        {\n            \"addr\": 3,\n            \"comment\": \"r[1]=1\",\n            \"opcode\": \"Integer\",\n            \"p1\": 1,\n            \"p2\": 1,\n            \"p3\": 0,\n            \"p4\": \"\",\n            \"p5\": 0,\n        },\n        {\n            \"addr\": 4,\n            \"comment\": \"\",\n            \"opcode\": \"Goto\",\n            \"p1\": 0,\n            \"p2\": 1,\n            \"p3\": 0,\n            \"p4\": \"\",\n            \"p5\": 0,\n        },\n    ]);\n    await db.close();\n})\n\ntest('in-memory db', async () => {\n    const db = await connect(\":memory:\");\n    await db.exec(\"CREATE TABLE t(x)\");\n    await db.exec(\"INSERT INTO t VALUES (1), (2), (3)\");\n    const stmt = db.prepare(\"SELECT * FROM t WHERE x % 2 = ?\");\n    const rows = await stmt.all([1]);\n    expect(rows).toEqual([{ x: 1 }, { x: 3 }]);\n    await db.close();\n})\n\n\ntest('implicit connect', async () => {\n    const db = new Database(':memory:');\n    const defer = db.prepare(\"SELECT * FROM t\");\n    await expect(async () => await defer.all()).rejects.toThrowError(/no such table: t/);\n    expect(() => db.prepare(\"SELECT * FROM t\")).toThrowError(/no such table: t/);\n    expect(await db.prepare(\"SELECT 1 as x\").all()).toEqual([{ x: 1 }]);\n    await db.close();\n})\n\ntest('on-disk db large inserts', async () => {\n    const path = `test-${(Math.random() * 10000) | 0}.db`;\n    const db1 = await connect(path);\n    await db1.prepare(\"CREATE TABLE t(x)\").run();\n    await db1.prepare(\"INSERT INTO t VALUES (randomblob(10 * 4096 + 0))\").run();\n    await db1.prepare(\"INSERT INTO t VALUES (randomblob(10 * 4096 + 1))\").run();\n    await db1.prepare(\"INSERT INTO t VALUES (randomblob(10 * 4096 + 2))\").run();\n    const stmt1 = db1.prepare(\"SELECT length(x) as l FROM t\");\n    expect(stmt1.columns()).toEqual([{ name: \"l\", column: null, database: null, table: null, type: null }]);\n    const rows1 = await stmt1.all();\n    expect(rows1).toEqual([{ l: 10 * 4096 }, { l: 10 * 4096 + 1 }, { l: 10 * 4096 + 2 }]);\n\n    await db1.exec(\"BEGIN\");\n    await db1.exec(\"INSERT INTO t VALUES (1)\");\n    await db1.exec(\"ROLLBACK\");\n\n    const rows2 = await db1.prepare(\"SELECT length(x) as l FROM t\").all();\n    expect(rows2).toEqual([{ l: 10 * 4096 }, { l: 10 * 4096 + 1 }, { l: 10 * 4096 + 2 }]);\n\n    await db1.prepare(\"PRAGMA wal_checkpoint(TRUNCATE)\").run();\n    await db1.close();\n})\n\ntest('on-disk db', async () => {\n    const path = `test-${(Math.random() * 10000) | 0}.db`;\n    const db1 = await connect(path);\n    await db1.exec(\"CREATE TABLE t(x)\");\n    await db1.exec(\"INSERT INTO t VALUES (1), (2), (3)\");\n    const stmt1 = db1.prepare(\"SELECT * FROM t WHERE x % 2 = ?\");\n    expect(stmt1.columns()).toEqual([{ name: \"x\", column: null, database: null, table: null, type: null }]);\n    const rows1 = await stmt1.all([1]);\n    expect(rows1).toEqual([{ x: 1 }, { x: 3 }]);\n    stmt1.close();\n    await db1.close();\n\n    const db2 = await connect(path);\n    const stmt2 = db2.prepare(\"SELECT * FROM t WHERE x % 2 = ?\");\n    expect(stmt2.columns()).toEqual([{ name: \"x\", column: null, database: null, table: null, type: null }]);\n    const rows2 = await stmt2.all([1]);\n    expect(rows2).toEqual([{ x: 1 }, { x: 3 }]);\n    await db2.close();\n})\n\n// attach is not supported in browser for now\n// test('attach', async () => {\n//     const path1 = `test-${(Math.random() * 10000) | 0}.db`;\n//     const path2 = `test-${(Math.random() * 10000) | 0}.db`;\n//     const db1 = await connect(path1);\n//     await db1.exec(\"CREATE TABLE t(x)\");\n//     await db1.exec(\"INSERT INTO t VALUES (1), (2), (3)\");\n//     const db2 = await connect(path2);\n//     await db2.exec(\"CREATE TABLE q(x)\");\n//     await db2.exec(\"INSERT INTO q VALUES (4), (5), (6)\");\n\n//     await db1.exec(`ATTACH '${path2}' as secondary`);\n\n//     const stmt = db1.prepare(\"SELECT * FROM t UNION ALL SELECT * FROM secondary.q\");\n//     expect(stmt.columns()).toEqual([{ name: \"x\", column: null, database: null, table: null, type: null }]);\n//     const rows = await stmt.all([1]);\n//     expect(rows).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }, { x: 4 }, { x: 5 }, { x: 6 }]);\n// })\n\ntest('blobs', async () => {\n    const db = await connect(\":memory:\");\n    const rows = await db.prepare(\"SELECT x'1020' as x\").all();\n    expect(rows).toEqual([{ x: new Uint8Array([16, 32]) }])\n    await db.close();\n})\n\n\ntest('example-1', async () => {\n    const db = await connect(':memory:');\n    await db.exec('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)');\n\n    const insert = db.prepare('INSERT INTO users (name, email) VALUES (?, ?)');\n    await insert.run('Alice', 'alice@example.com');\n    await insert.run('Bob', 'bob@example.com');\n\n    const users = await db.prepare('SELECT * FROM users').all();\n    expect(users).toEqual([\n        { id: 1, name: 'Alice', email: 'alice@example.com' },\n        { id: 2, name: 'Bob', email: 'bob@example.com' }\n    ]);\n    await db.close();\n})\n\ntest('example-2', async () => {\n    const db = await connect(':memory:');\n    await db.exec('CREATE TABLE users (name, email)');\n    // Using transactions for atomic operations\n    const transaction = db.transaction(async (users) => {\n        const insert = db.prepare('INSERT INTO users (name, email) VALUES (?, ?)');\n        for (const user of users) {\n            await insert.run(user.name, user.email);\n        }\n    });\n\n    // Execute transaction\n    await transaction([\n        { name: 'Alice', email: 'alice@example.com' },\n        { name: 'Bob', email: 'bob@example.com' }\n    ]);\n\n    const rows = await db.prepare('SELECT * FROM users').all();\n    expect(rows).toEqual([\n        { name: 'Alice', email: 'alice@example.com' },\n        { name: 'Bob', email: 'bob@example.com' }\n    ]);\n    await db.close();\n})\n\ntest('sorter-wasm', async () => {\n    const db = await connect(':memory:');\n    await db.exec('CREATE TABLE t (k, v)');\n    for (let i = 0; i < 1024; i++) {\n        await db.exec(`INSERT INTO t VALUES (${i}, randomblob(10 * 1024))`);\n    }\n\n    expect(await db.prepare(\"SELECT length(v) as l FROM (SELECT v FROM t ORDER BY k)\").all()).toEqual(new Array(1024).fill({ l: 10 * 1024 }));\n    await db.close();\n})\n\ntest('hash-join-wasm', { timeout: 60_000 }, async () => {\n    const db = await connect(':memory:');\n    await db.exec('CREATE TABLE a (k, v)');\n    await db.exec('CREATE TABLE b (k, v)');\n    for (let i = 0; i < 1024; i++) {\n        await db.exec(`INSERT INTO a VALUES (${i}, randomblob(100 * 1024))`);\n    }\n    for (let i = 0; i < 1024; i++) {\n        await db.exec(`INSERT INTO b VALUES (${i}, randomblob(100 * 1024))`);\n    }\n\n    expect(await db.prepare(\"SELECT length(a) as a, length(b) as b FROM (SELECT a.v as a, b.v as b FROM a INNER JOIN b ON a.k = b.k)\").all()).toEqual(new Array(1024).fill({ a: 100 * 1024, b: 100 * 1024 }));\n    await db.close();\n})"
  },
  {
    "path": "bindings/javascript/packages/wasm/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"skipLibCheck\": true,\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"module\": \"nodenext\",\n    \"target\": \"esnext\",\n    \"moduleResolution\": \"nodenext\",\n    \"outDir\": \"dist/\",\n    \"lib\": [\n      \"es2020\",\n      \"DOM\",\n      \"WebWorker\"\n    ]\n  },\n  \"include\": [\n    \"*\"\n  ]\n}"
  },
  {
    "path": "bindings/javascript/packages/wasm/vite",
    "content": ""
  },
  {
    "path": "bindings/javascript/packages/wasm/vite.config.js",
    "content": "import { resolve } from 'path';\nimport { defineConfig } from 'vite';\n\nexport default defineConfig({\n  base: './',\n  build: {\n    lib: {\n      entry: resolve(__dirname, 'promise-bundle.ts'),\n      name: 'database-wasm',\n      fileName: format => `main.${format}.js`,\n      formats: ['es'],\n    },\n    rollupOptions: {\n      output: {\n        dir: 'bundle',\n      }\n    },\n  },\n});\n"
  },
  {
    "path": "bindings/javascript/packages/wasm/vitest.config.ts",
    "content": "import { defineConfig } from 'vitest/config'\n\nexport default defineConfig({\n  define: {\n    'process.env.NODE_DEBUG_NATIVE': 'false',\n  },\n  server: {\n    headers: {\n      \"Cross-Origin-Embedder-Policy\": \"require-corp\",\n      \"Cross-Origin-Opener-Policy\": \"same-origin\"\n    },\n  },\n  test: {\n    testTimeout: 120_000,\n    browser: {\n      enabled: true,\n      provider: 'playwright',\n      instances: [\n        { browser: 'chromium' },\n        { browser: 'firefox' }\n      ],\n    },\n  },\n})\n"
  },
  {
    "path": "bindings/javascript/packages/wasm/wasm-inline.ts",
    "content": "const tursoWasmBase64 = '__PLACEHOLDER__';\nasync function convertBase64ToBinary(base64Url: string): Promise<ArrayBuffer> {\n    const blob = await fetch(base64Url).then(res => res.blob());\n    return await blob.arrayBuffer();\n}\n\nexport async function tursoWasm(): Promise<ArrayBuffer> {\n    return await convertBase64ToBinary(tursoWasmBase64);\n}"
  },
  {
    "path": "bindings/javascript/packages/wasm/worker.ts",
    "content": "import { setupWebWorker } from \"@tursodatabase/database-wasm-common\";\nsetupWebWorker();\n"
  },
  {
    "path": "bindings/javascript/packages/wasm-common/README.md",
    "content": "## About\n\nThis package is the Turso embedded database common JS library which is shared between final builds for Node and Browser.\n\nDo not use this package directly - instead you must use `@tursodatabase/database` or `@tursodatabase/database-wasm`.\n\n> **⚠️ Warning:** This software is in BETA. It may still contain bugs and unexpected behavior. Use caution with production data and ensure you have backups.\n\n"
  },
  {
    "path": "bindings/javascript/packages/wasm-common/index.ts",
    "content": "import {\n    createOnMessage as __wasmCreateOnMessageForFsProxy,\n    getDefaultContext as __emnapiGetDefaultContext,\n    instantiateNapiModule as __emnapiInstantiateNapiModule,\n    WASI as __WASI,\n    instantiateNapiModuleSync,\n    MessageHandler\n} from '@napi-rs/wasm-runtime'\n\nfunction getUint8ArrayFromMemory(memory: WebAssembly.Memory, ptr: number, len: number): Uint8Array {\n    ptr = ptr >>> 0;\n    return new Uint8Array(memory.buffer).subarray(ptr, ptr + len);\n}\n\nfunction getStringFromMemory(memory: WebAssembly.Memory, ptr: number, len: number): string {\n    const shared = getUint8ArrayFromMemory(memory, ptr, len);\n    const copy = new Uint8Array(shared.length);\n    copy.set(shared);\n    const decoder = new TextDecoder('utf-8');\n    return decoder.decode(copy);\n}\n\ninterface BrowserImports {\n    is_web_worker(): boolean;\n    lookup_file(ptr: number, len: number): number;\n    read(handle: number, ptr: number, len: number, offset: number): number;\n    read_async(handle: number, ptr: number, len: number, offset: number, c: number);\n    write(handle: number, ptr: number, len: number, offset: number): number;\n    write_async(handle: number, ptr: number, len: number, offset: number, c: number);\n    sync(handle: number): number;\n    sync_async(handle: number, c: number);\n    truncate(handle: number, len: number): number;\n    truncate_async(handle: number, len: number, c: number);\n    size(handle: number): number;\n}\n\nfunction panicMain(name): never {\n    throw new Error(`method ${name} must be invoked only from the worker thread`);\n}\n\nfunction panicWorker(name): never {\n    throw new Error(`method ${name} must be invoked only from the main thread`);\n}\n\nfunction mainImports(worker: Worker, completeOpfs: (c: any, r: any) => void): BrowserImports {\n    return {\n        is_web_worker(): boolean {\n            return false;\n        },\n        write_async(handle, ptr, len, offset, c) {\n            writeFileAtWorker(worker, handle, ptr, len, offset)\n                .then(result => {\n                    completeOpfs(c, result);\n                }, err => {\n                    console.error('write_async', err);\n                    completeOpfs(c, -1);\n                });\n        },\n        sync_async(handle, c) {\n            syncFileAtWorker(worker, handle)\n                .then(result => {\n                    completeOpfs(c, result);\n                }, err => {\n                    console.error('sync_async', err);\n                    completeOpfs(c, -1);\n                });\n        },\n        read_async(handle, ptr, len, offset, c) {\n            readFileAtWorker(worker, handle, ptr, len, offset)\n                .then(result => {\n                    completeOpfs(c, result);\n                }, err => {\n                    console.error('read_async', err);\n                    completeOpfs(c, -1);\n                });\n        },\n        truncate_async(handle, len, c) {\n            truncateFileAtWorker(worker, handle, len)\n                .then(result => {\n                    completeOpfs(c, result);\n                }, err => {\n                    console.error('truncate_async', err);\n                    completeOpfs(c, -1);\n                });\n        },\n        lookup_file(ptr, len): number {\n            panicMain(\"lookup_file\")\n        },\n        read(handle, ptr, len, offset): number {\n            panicMain(\"read\")\n        },\n        write(handle, ptr, len, offset): number {\n            panicMain(\"write\")\n        },\n        sync(handle): number {\n            panicMain(\"sync\")\n        },\n        truncate(handle, len): number {\n            panicMain(\"truncate\")\n        },\n        size(handle): number {\n            panicMain(\"size\")\n        }\n    };\n};\n\nfunction workerImports(opfs: OpfsDirectory, memory: WebAssembly.Memory): BrowserImports {\n    return {\n        is_web_worker(): boolean {\n            return true;\n        },\n        lookup_file(ptr, len): number {\n            try {\n                const handle = opfs.lookupFileHandle(getStringFromMemory(memory, ptr, len));\n                return handle == null ? -404 : handle;\n            } catch (e) {\n                return -1;\n            }\n        },\n        read(handle, ptr, len, offset): number {\n            try {\n                return opfs.read(handle, getUint8ArrayFromMemory(memory, ptr, len), offset);\n            } catch (e) {\n                return -1;\n            }\n        },\n        write(handle, ptr, len, offset): number {\n            try {\n                return opfs.write(handle, getUint8ArrayFromMemory(memory, ptr, len), offset)\n            } catch (e) {\n                return -1;\n            }\n        },\n        sync(handle): number {\n            try {\n                return opfs.sync(handle);\n            } catch (e) {\n                return -1;\n            }\n        },\n        truncate(handle, len): number {\n            try {\n                opfs.truncate(handle, len);\n                return 0;\n            } catch (e) {\n                return -1;\n            }\n        },\n        size(handle): number {\n            try {\n                return opfs.size(handle);\n            } catch (e) {\n                return -1;\n            }\n        },\n        read_async(handle, ptr, len, offset, completion) {\n            panicWorker(\"read_async\")\n        },\n        write_async(handle, ptr, len, offset, completion) {\n            panicWorker(\"write_async\")\n        },\n        sync_async(handle, completion) {\n            panicWorker(\"sync_async\")\n        },\n        truncate_async(handle, len, c) {\n            panicWorker(\"truncate_async\")\n        },\n    }\n}\n\nclass OpfsDirectory {\n    fileByPath: Map<String, { handle: number, sync: FileSystemSyncAccessHandle }>;\n    fileByHandle: Map<number, FileSystemSyncAccessHandle>;\n    fileHandleNo: number;\n\n    constructor() {\n        this.fileByPath = new Map();\n        this.fileByHandle = new Map();\n        this.fileHandleNo = 0;\n    }\n\n    async registerFile(path: string) {\n        if (this.fileByPath.has(path)) {\n            return;\n        }\n        const opfsRoot = await navigator.storage.getDirectory();\n        const opfsHandle = await opfsRoot.getFileHandle(path, { create: true });\n        const opfsSync = await opfsHandle.createSyncAccessHandle();\n        this.fileHandleNo += 1;\n        this.fileByPath.set(path, { handle: this.fileHandleNo, sync: opfsSync });\n        this.fileByHandle.set(this.fileHandleNo, opfsSync);\n    }\n\n    async unregisterFile(path: string) {\n        const file = this.fileByPath.get(path);\n        if (file == null) {\n            return;\n        }\n        this.fileByPath.delete(path);\n        this.fileByHandle.delete(file.handle);\n        file.sync.close();\n    }\n    lookupFileHandle(path: string): number | null {\n        try {\n            const file = this.fileByPath.get(path);\n            if (file == null) {\n                return null;\n            }\n            return file.handle;\n        } catch (e) {\n            console.error('lookupFile', path, e);\n            throw e;\n        }\n    }\n    read(handle: number, buffer: Uint8Array, offset: number): number {\n        try {\n            const file = this.fileByHandle.get(handle);\n            const result = file.read(buffer, { at: Number(offset) });\n            return result;\n        } catch (e) {\n            console.error('read', handle, buffer.length, offset, e);\n            throw e;\n        }\n    }\n    write(handle: number, buffer: Uint8Array, offset: number): number {\n        try {\n            const file = this.fileByHandle.get(handle);\n            const result = file.write(buffer, { at: Number(offset) });\n            return result;\n        } catch (e) {\n            console.error('write', handle, buffer.length, offset, e);\n            throw e;\n        }\n    }\n    sync(handle: number): number {\n        try {\n            const file = this.fileByHandle.get(handle);\n            file.flush();\n            return 0;\n        } catch (e) {\n            console.error('sync', handle, e);\n            throw e;\n        }\n    }\n    truncate(handle: number, size: number) {\n        try {\n            const file = this.fileByHandle.get(handle);\n            file.truncate(size);\n            return 0;\n        } catch (e) {\n            console.error('truncate', handle, size, e);\n            throw e;\n        }\n    }\n    size(handle: number): number {\n        try {\n            const file = this.fileByHandle.get(handle);\n            const size = file.getSize()\n            return size;\n        } catch (e) {\n            console.error('size', handle, e);\n            throw e;\n        }\n    }\n}\n\nlet workerRequestId = 0;\nfunction waitForWorkerResponse(worker: Worker, id: number, timeoutMs: number = 30_000): Promise<any> {\n    let waitResolve, waitReject;\n    let settled = false;\n    const callback = msg => {\n        if (msg.data.__turso__ && msg.data.id == id) {\n            settled = true;\n            clearTimeout(stallTimer);\n            if (msg.data.error != null) {\n                waitReject(msg.data.error)\n            } else {\n                waitResolve(msg.data.result)\n            }\n            cleanup();\n        }\n    };\n    const cleanup = () => worker.removeEventListener(\"message\", callback);\n    const stallTimer = setTimeout(() => {\n        if (!settled) {\n            settled = true;\n            cleanup();\n            waitReject(new Error(`[turso:worker] request id=${id} timed out after ${timeoutMs}ms`));\n        }\n    }, timeoutMs);\n\n    worker.addEventListener(\"message\", callback);\n    const result = new Promise((resolve, reject) => {\n        waitResolve = resolve;\n        waitReject = reject;\n    });\n    return result;\n}\n\nfunction readFileAtWorker(worker: Worker, handle: number, ptr: number, len: number, offset: number) {\n    workerRequestId += 1;\n    const currentId = workerRequestId;\n    const promise = waitForWorkerResponse(worker, currentId);\n    worker.postMessage({ __turso__: \"read_async\", handle: handle, ptr: ptr, len: len, offset: offset, id: currentId });\n    return promise;\n}\n\nfunction writeFileAtWorker(worker: Worker, handle: number, ptr: number, len: number, offset: number) {\n    workerRequestId += 1;\n    const currentId = workerRequestId;\n    const promise = waitForWorkerResponse(worker, currentId);\n    worker.postMessage({ __turso__: \"write_async\", handle: handle, ptr: ptr, len: len, offset: offset, id: currentId });\n    return promise;\n}\n\nfunction syncFileAtWorker(worker: Worker, handle: number) {\n    workerRequestId += 1;\n    const currentId = workerRequestId;\n    const promise = waitForWorkerResponse(worker, currentId);\n    worker.postMessage({ __turso__: \"sync_async\", handle: handle, id: currentId });\n    return promise;\n}\n\nfunction truncateFileAtWorker(worker: Worker, handle: number, len: number) {\n    workerRequestId += 1;\n    const currentId = workerRequestId;\n    const promise = waitForWorkerResponse(worker, currentId);\n    worker.postMessage({ __turso__: \"truncate_async\", handle: handle, len: len, id: currentId });\n    return promise;\n}\n\nfunction registerFileAtWorker(worker: Worker, path: string): Promise<void> {\n    workerRequestId += 1;\n    const currentId = workerRequestId;\n    const promise = waitForWorkerResponse(worker, currentId);\n    worker.postMessage({ __turso__: \"register\", path: path, id: currentId });\n    return promise;\n}\n\nfunction unregisterFileAtWorker(worker: Worker, path: string): Promise<void> {\n    workerRequestId += 1;\n    const currentId = workerRequestId;\n    const promise = waitForWorkerResponse(worker, currentId);\n    worker.postMessage({ __turso__: \"unregister\", path: path, id: currentId });\n    return promise;\n}\n\nfunction isWebWorker(): boolean {\n    return typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope;\n}\n\nfunction setupWebWorker() {\n    let opfs = new OpfsDirectory();\n    let memory = null;\n\n    const handler = new MessageHandler({\n        onLoad({ wasmModule, wasmMemory }) {\n            memory = wasmMemory;\n            const wasi = new __WASI({\n                print: function () {\n                    // eslint-disable-next-line no-console\n                    console.log.apply(console, arguments)\n                },\n                printErr: function () {\n                    // eslint-disable-next-line no-console\n                    console.error.apply(console, arguments)\n                },\n            })\n            return instantiateNapiModuleSync(wasmModule, {\n                childThread: true,\n                wasi,\n                overwriteImports(importObject) {\n                    importObject.env = {\n                        ...importObject.env,\n                        ...importObject.napi,\n                        ...importObject.emnapi,\n                        ...workerImports(opfs, memory),\n                        memory: wasmMemory,\n                    }\n                },\n            })\n        },\n    })\n\n    globalThis.onmessage = async function (e) {\n        if (e.data.__turso__ == 'register') {\n            try {\n                await opfs.registerFile(e.data.path);\n                self.postMessage({ __turso__: true, id: e.data.id });\n            } catch (error) {\n                self.postMessage({ __turso__: true, id: e.data.id, error: error });\n            }\n            return;\n        } else if (e.data.__turso__ == 'unregister') {\n            try {\n                await opfs.unregisterFile(e.data.path);\n                self.postMessage({ __turso__: true, id: e.data.id });\n            } catch (error) {\n                self.postMessage({ __turso__: true, id: e.data.id, error: error });\n            }\n            return;\n        } else if (e.data.__turso__ == 'read_async') {\n            let result = opfs.read(e.data.handle, getUint8ArrayFromMemory(memory, e.data.ptr, e.data.len), e.data.offset);\n            self.postMessage({ __turso__: true, id: e.data.id, result: result });\n        } else if (e.data.__turso__ == 'write_async') {\n            let result = opfs.write(e.data.handle, getUint8ArrayFromMemory(memory, e.data.ptr, e.data.len), e.data.offset);\n            self.postMessage({ __turso__: true, id: e.data.id, result: result });\n        } else if (e.data.__turso__ == 'sync_async') {\n            let result = opfs.sync(e.data.handle);\n            self.postMessage({ __turso__: true, id: e.data.id, result: result });\n        } else if (e.data.__turso__ == 'truncate_async') {\n            let result = opfs.truncate(e.data.handle, e.data.len);\n            self.postMessage({ __turso__: true, id: e.data.id, result: result });\n        }\n        handler.handle(e)\n    }\n}\n\nasync function setupMainThread(wasmFile: ArrayBuffer, factory: () => Worker): Promise<any> {\n    const worker = factory();\n    let completeOpfs = null;\n    const __emnapiContext = __emnapiGetDefaultContext()\n    const __wasi = new __WASI({\n        version: 'preview1',\n    })\n    const __sharedMemory = new WebAssembly.Memory({\n        initial: 4000,\n        maximum: 65536,\n        shared: true,\n    })\n    const {\n        instance: __napiInstance,\n        module: __wasiModule,\n        napiModule: __napiModule,\n    } = await __emnapiInstantiateNapiModule(wasmFile, {\n        context: __emnapiContext,\n        asyncWorkPoolSize: 1,\n        wasi: __wasi,\n        onCreateWorker() { return worker; },\n        overwriteImports(importObject) {\n            importObject.env = {\n                ...importObject.env,\n                ...importObject.napi,\n                ...importObject.emnapi,\n                ...mainImports(worker, (c, res) => completeOpfs(c, res)),\n                memory: __sharedMemory,\n            }\n            return importObject\n        },\n        beforeInit({ instance }) {\n            for (const name of Object.keys(instance.exports)) {\n                if (name.startsWith('__napi_register__')) {\n                    instance.exports[name]()\n                }\n            }\n        },\n    });\n    const nativeCompleteOpfs = __napiModule.exports.completeOpfs;\n    completeOpfs = (c, res) => {\n        nativeCompleteOpfs(c, res);\n        ioNotifier.notify();\n    };\n    return __napiModule;\n}\n\nclass IONotifier {\n    private waiters: Array<() => void> = [];\n\n    waitForCompletion(): Promise<void> {\n        return new Promise(resolve => {\n            this.waiters.push(resolve);\n        });\n    }\n\n    notify() {\n        const waiters = this.waiters;\n        this.waiters = [];\n        for (const resolve of waiters) {\n            resolve();\n        }\n    }\n}\n\nconst ioNotifier = new IONotifier();\n\nexport { OpfsDirectory, workerImports, mainImports as MainDummyImports, waitForWorkerResponse, registerFileAtWorker, unregisterFileAtWorker, isWebWorker, setupWebWorker, setupMainThread, ioNotifier }"
  },
  {
    "path": "bindings/javascript/packages/wasm-common/package.json",
    "content": "{\n  \"name\": \"@tursodatabase/database-wasm-common\",\n  \"version\": \"0.6.0-pre.4\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/tursodatabase/turso\"\n  },\n  \"type\": \"module\",\n  \"license\": \"MIT\",\n  \"main\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"import\": \"./dist/index.js\",\n      \"types\": \"./dist/index.d.ts\"\n    }\n  },\n  \"packageManager\": \"yarn@4.9.2\",\n  \"files\": [\n    \"dist/**\",\n    \"README.md\"\n  ],\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.2\"\n  },\n  \"scripts\": {\n    \"tsc-build\": \"npm exec tsc\",\n    \"build\": \"npm run tsc-build\",\n    \"test\": \"echo 'no tests'\"\n  },\n  \"dependencies\": {\n    \"@napi-rs/wasm-runtime\": \"^1.0.5\"\n  }\n}\n"
  },
  {
    "path": "bindings/javascript/packages/wasm-common/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"skipLibCheck\": true,\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"module\": \"nodenext\",\n    \"target\": \"esnext\",\n    \"moduleResolution\": \"nodenext\",\n    \"outDir\": \"dist/\",\n    \"lib\": [\n      \"es2020\",\n      \"DOM\",\n      \"WebWorker\"\n    ],\n  },\n  \"include\": [\n    \"*\"\n  ]\n}"
  },
  {
    "path": "bindings/javascript/perf/package.json",
    "content": "{\n  \"name\": \"turso-perf\",\n  \"type\": \"module\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"dependencies\": {\n    \"better-sqlite3\": \"^9.5.0\",\n    \"@tursodatabase/database\": \"../packages/native\",\n    \"mitata\": \"^0.1.11\"\n  }\n}\n"
  },
  {
    "path": "bindings/javascript/perf/perf-better-sqlite3.js",
    "content": "import { run, bench, group, baseline } from 'mitata';\n\nimport Database from 'better-sqlite3';\n\nconst db = new Database(':memory:');\n\ndb.exec(\"CREATE TABLE users (id INTEGER, name TEXT, email TEXT)\");\ndb.exec(\"INSERT INTO users (id, name, email) VALUES (1, 'Alice', 'alice@example.org')\");\n\nconst stmtSelect = db.prepare(\"SELECT * FROM users WHERE id = ?\");\nconst rawStmtSelect = db.prepare(\"SELECT * FROM users WHERE id = ?\").raw();\nconst stmtInsert = db.prepare(\"INSERT INTO users (id, name, email) VALUES (?, ?, ?)\");\n\nbench('Statement.get() with bind parameters [expanded]', () => {\n  stmtSelect.get(1);\n});\n\nbench('Statement.git() with bind parameters [raw]', () => {\n  rawStmtSelect.get(1);\n});\n\nbench('Statement.run() with bind parameters', () => {\n  stmtInsert.run([1, 'foobar', 'foobar@example.com']);\n});\n\nawait run({\n  units: false,\n  silent: false,\n  avg: true,\n  json: false,\n  colors: true,\n  min_max: true,\n  percentiles: true,\n});\n"
  },
  {
    "path": "bindings/javascript/perf/perf-turso.js",
    "content": "import { run, bench, group, baseline } from 'mitata';\n\nimport { Database } from '@tursodatabase/database';\n\nconst db = new Database(':memory:');\n\nawait db.exec(\"CREATE TABLE users (id INTEGER, name TEXT, email TEXT)\");\nawait db.exec(\"INSERT INTO users (id, name, email) VALUES (1, 'Alice', 'alice@example.org')\");\n\nconst stmtSelect = db.prepare(\"SELECT * FROM users WHERE id = ?\");\nconst rawStmtSelect = db.prepare(\"SELECT * FROM users WHERE id = ?\").raw();\nconst stmtInsert = db.prepare(\"INSERT INTO users (id, name, email) VALUES (?, ?, ?)\");\n\nbench('Statement.get() with bind parameters [expanded]', async () => {\n  await stmtSelect.get(1);\n});\n\nbench('Statement.get() with bind parameters [raw]', async () => {\n  await rawStmtSelect.get(1);\n});\n\nbench('Statement.run() with bind parameters', async () => {\n  await stmtInsert.run([1, 'foobar', 'foobar@example.com']);\n});\n\nawait run({\n  units: false,\n  silent: false,\n  avg: true,\n  json: false,\n  colors: true,\n  min_max: true,\n  percentiles: true,\n});\n"
  },
  {
    "path": "bindings/javascript/replace.sh",
    "content": "sed -i \"s/$NAME_FROM/$NAME_TO/g\" packages/common/package.json\nsed -i \"s/$NAME_FROM/$NAME_TO/g\" packages/native/package.json\nsed -i \"s/$NAME_FROM/$NAME_TO/g\" packages/browser/package.json\nsed -i \"s/$NAME_FROM/$NAME_TO/g\" packages/browser-common/package.json\nsed -i \"s/$NAME_FROM/$NAME_TO/g\" sync/packages/common/package.json\nsed -i \"s/$NAME_FROM/$NAME_TO/g\" sync/packages/native/package.json\nsed -i \"s/$NAME_FROM/$NAME_TO/g\" sync/packages/browser/package.json\n\nsed -i \"s/$VERSION_FROM/$VERSION_TO/g\" packages/common/package.json\nsed -i \"s/$VERSION_FROM/$VERSION_TO/g\" packages/native/package.json\nsed -i \"s/$VERSION_FROM/$VERSION_TO/g\" packages/browser/package.json\nsed -i \"s/$VERSION_FROM/$VERSION_TO/g\" packages/browser-common/package.json\nsed -i \"s/$VERSION_FROM/$VERSION_TO/g\" sync/packages/common/package.json\nsed -i \"s/$VERSION_FROM/$VERSION_TO/g\" sync/packages/native/package.json\nsed -i \"s/$VERSION_FROM/$VERSION_TO/g\" sync/packages/browser/package.json\n\nsed -i \"s/$NAME_FROM/$NAME_TO/g\" packages/native/promise.ts\nsed -i \"s/$NAME_FROM/$NAME_TO/g\" packages/native/compat.ts\n\nsed -i \"s/$NAME_FROM/$NAME_TO/g\" packages/browser-common/index.ts\nsed -i \"s/$NAME_FROM/$NAME_TO/g\" packages/browser/promise.ts\nsed -i \"s/$NAME_FROM/$NAME_TO/g\" packages/browser/promise-bundle.ts\nsed -i \"s/$NAME_FROM/$NAME_TO/g\" packages/browser/promise-default.ts\nsed -i \"s/$NAME_FROM/$NAME_TO/g\" packages/browser/promise-vite-dev-hack.ts\nsed -i \"s/$NAME_FROM/$NAME_TO/g\" packages/browser/promise-turbopack-hack.ts\nsed -i \"s/$NAME_FROM/$NAME_TO/g\" packages/browser/index-default.ts\nsed -i \"s/$NAME_FROM/$NAME_TO/g\" packages/browser/index-bundle.ts\nsed -i \"s/$NAME_FROM/$NAME_TO/g\" packages/browser/index-vite-dev-hack.ts\nsed -i \"s/$NAME_FROM/$NAME_TO/g\" packages/browser/index-turbopack-hack.ts\nsed -i \"s/$NAME_FROM/$NAME_TO/g\" packages/browser/worker.ts\n\nsed -i \"s/$NAME_FROM/$NAME_TO/g\" sync/packages/native/promise.ts\n\nsed -i \"s/$NAME_FROM/$NAME_TO/g\" sync/packages/browser/promise.ts\nsed -i \"s/$NAME_FROM/$NAME_TO/g\" sync/packages/browser/promise-bundle.ts\nsed -i \"s/$NAME_FROM/$NAME_TO/g\" sync/packages/browser/promise-default.ts\nsed -i \"s/$NAME_FROM/$NAME_TO/g\" sync/packages/browser/promise-vite-dev-hack.ts\nsed -i \"s/$NAME_FROM/$NAME_TO/g\" sync/packages/browser/promise-turbopack-hack.ts\nsed -i \"s/$NAME_FROM/$NAME_TO/g\" sync/packages/browser/index-default.ts\nsed -i \"s/$NAME_FROM/$NAME_TO/g\" sync/packages/browser/index-bundle.ts\nsed -i \"s/$NAME_FROM/$NAME_TO/g\" sync/packages/browser/index-vite-dev-hack.ts\nsed -i \"s/$NAME_FROM/$NAME_TO/g\" sync/packages/browser/index-turbopack-hack.ts\nsed -i \"s/$NAME_FROM/$NAME_TO/g\" sync/packages/browser/worker.ts\n"
  },
  {
    "path": "bindings/javascript/scripts/inline-wasm-base64.js",
    "content": "import { readFileSync, writeFileSync } from \"node:fs\";\n\nconst data = readFileSync(process.env.WASM_FILE);\nconst b64 = data.toString(\"base64\");\nconst dataUrl = \"data:application/wasm;base64,\" + b64;\nconst inlined = readFileSync(process.env.JS_FILE).toString(\"utf8\");\nconst replaced = inlined.replace(\"__PLACEHOLDER__\", dataUrl);\nwriteFileSync(process.env.JS_FILE, replaced);"
  },
  {
    "path": "bindings/javascript/src/browser.rs",
    "content": "use std::{cell::RefCell, collections::HashMap, sync::Arc};\n\nuse napi::bindgen_prelude::*;\nuse napi_derive::napi;\nuse turso_core::{Clock, Completion, File, MonotonicInstant, WallClockInstant, IO};\n\npub struct NoopTask;\n\nimpl Task for NoopTask {\n    type Output = ();\n    type JsValue = ();\n    fn compute(&mut self) -> Result<Self::Output> {\n        Ok(())\n    }\n    fn resolve(&mut self, _: Env, _: Self::Output) -> Result<Self::JsValue> {\n        Ok(())\n    }\n}\n\n#[napi]\n/// turso-db in the the browser requires explicit thread pool initialization\n/// so, we just put no-op task on the thread pool and force emnapi to allocate web worker\npub fn init_thread_pool() -> napi::Result<AsyncTask<NoopTask>> {\n    Ok(AsyncTask::new(NoopTask))\n}\n\n#[napi]\n#[derive(Clone)]\npub struct Opfs {\n    inner: Arc<OpfsInner>,\n}\n\npub struct OpfsInner {\n    completion_no: RefCell<u32>,\n    completions: RefCell<HashMap<u32, Completion>>,\n}\n\nthread_local! {\n    static OPFS: Arc<Opfs> = Arc::new(Opfs::default());\n}\n\n#[napi]\n#[derive(Clone)]\nstruct OpfsFile {\n    handle: i32,\n    opfs: Opfs,\n}\n\nunsafe impl Send for Opfs {}\nunsafe impl Sync for Opfs {}\n\n#[napi]\npub fn complete_opfs(completion_no: u32, result: i32) {\n    OPFS.with(|opfs| opfs.complete(completion_no, result))\n}\n\npub fn opfs() -> Arc<Opfs> {\n    OPFS.with(|opfs| opfs.clone())\n}\n\nimpl Opfs {\n    pub fn complete(&self, completion_no: u32, result: i32) {\n        let completion = {\n            let mut completions = self.inner.completions.borrow_mut();\n            completions.remove(&completion_no).unwrap()\n        };\n        completion.complete(result);\n    }\n\n    fn register_completion(&self, c: Completion) -> u32 {\n        let inner = &self.inner;\n        *inner.completion_no.borrow_mut() += 1;\n        let completion_no = *inner.completion_no.borrow();\n        tracing::debug!(\n            \"register completion: {} {:?}\",\n            completion_no,\n            Arc::as_ptr(inner)\n        );\n        inner.completions.borrow_mut().insert(completion_no, c);\n        completion_no\n    }\n}\n\nimpl Clock for Opfs {\n    fn current_time_monotonic(&self) -> MonotonicInstant {\n        MonotonicInstant::now()\n    }\n\n    fn current_time_wall_clock(&self) -> WallClockInstant {\n        WallClockInstant::now()\n    }\n}\n\nimpl Default for Opfs {\n    fn default() -> Self {\n        Self {\n            #[allow(clippy::arc_with_non_send_sync)]\n            inner: Arc::new(OpfsInner {\n                completion_no: RefCell::new(0),\n                completions: RefCell::new(HashMap::new()),\n            }),\n        }\n    }\n}\n\n#[link(wasm_import_module = \"env\")]\nextern \"C\" {\n    fn lookup_file(path: *const u8, path_len: usize) -> i32;\n    fn read(handle: i32, buffer: *mut u8, buffer_len: usize, offset: i32) -> i32;\n    fn write(handle: i32, buffer: *const u8, buffer_len: usize, offset: i32) -> i32;\n    fn sync(handle: i32) -> i32;\n    fn truncate(handle: i32, length: usize) -> i32;\n    fn size(handle: i32) -> i32;\n\n    fn write_async(handle: i32, buffer: *const u8, buffer_len: usize, offset: i32, c: u32);\n    fn sync_async(handle: i32, c: u32);\n    fn read_async(handle: i32, buffer: *mut u8, buffer_len: usize, offset: i32, c: u32);\n    fn truncate_async(handle: i32, length: usize, c: u32);\n    // fn size_async(handle: i32) -> i32;\n\n    fn is_web_worker() -> bool;\n}\n\nfn is_web_worker_safe() -> bool {\n    unsafe { is_web_worker() }\n}\n\nimpl IO for Opfs {\n    fn open_file(\n        &self,\n        path: &str,\n        _: turso_core::OpenFlags,\n        _: bool,\n    ) -> turso_core::Result<std::sync::Arc<dyn turso_core::File>> {\n        tracing::info!(\"open_file: {}\", path);\n        let result = unsafe { lookup_file(path.as_ptr(), path.len()) };\n        if result >= 0 {\n            Ok(Arc::new(OpfsFile {\n                handle: result,\n                opfs: Opfs {\n                    inner: self.inner.clone(),\n                },\n            }))\n        } else if result == -404 {\n            Err(turso_core::LimboError::InternalError(format!(\n                \"unexpected path {path}: files must be created in advance for OPFS IO\"\n            )))\n        } else {\n            Err(turso_core::LimboError::InternalError(format!(\n                \"unexpected file lookup error: {result}\"\n            )))\n        }\n    }\n\n    fn remove_file(&self, _: &str) -> turso_core::Result<()> {\n        Ok(())\n    }\n}\n\nimpl File for OpfsFile {\n    fn lock_file(&self, _: bool) -> turso_core::Result<()> {\n        Ok(())\n    }\n\n    fn unlock_file(&self) -> turso_core::Result<()> {\n        Ok(())\n    }\n\n    fn pread(\n        &self,\n        pos: u64,\n        c: turso_core::Completion,\n    ) -> turso_core::Result<turso_core::Completion> {\n        let web_worker = is_web_worker_safe();\n        tracing::debug!(\n            \"pread({}, is_web_worker={}): pos={}\",\n            self.handle,\n            web_worker,\n            pos\n        );\n        let handle = self.handle;\n        let read_c = c.as_read();\n        let buffer = read_c.buf_arc();\n        let buffer = buffer.as_mut_slice();\n        if web_worker {\n            let result = unsafe { read(handle, buffer.as_mut_ptr(), buffer.len(), pos as i32) };\n            c.complete(result as i32);\n        } else {\n            let completion_no = self.opfs.register_completion(c.clone());\n            unsafe {\n                read_async(\n                    handle,\n                    buffer.as_mut_ptr(),\n                    buffer.len(),\n                    pos as i32,\n                    completion_no,\n                )\n            };\n        }\n        Ok(c)\n    }\n\n    fn pwrite(\n        &self,\n        pos: u64,\n        buffer: Arc<turso_core::Buffer>,\n        c: turso_core::Completion,\n    ) -> turso_core::Result<turso_core::Completion> {\n        let web_worker = is_web_worker_safe();\n        tracing::debug!(\n            \"pwrite({}, is_web_worker={}): pos={}\",\n            self.handle,\n            web_worker,\n            pos\n        );\n        let handle = self.handle;\n        // Keep the buffer alive until the async write completes — write_async\n        // passes a raw pointer to JavaScript which may fire the callback later.\n        c.keep_write_buffer_alive(buffer.clone());\n        let buffer = buffer.as_slice();\n        if web_worker {\n            let result = unsafe { write(handle, buffer.as_ptr(), buffer.len(), pos as i32) };\n            c.complete(result as i32);\n        } else {\n            let completion_no = self.opfs.register_completion(c.clone());\n            unsafe {\n                write_async(\n                    handle,\n                    buffer.as_ptr(),\n                    buffer.len(),\n                    pos as i32,\n                    completion_no,\n                )\n            };\n        }\n        Ok(c)\n    }\n\n    fn sync(\n        &self,\n        c: turso_core::Completion,\n        _sync_type: turso_core::io::FileSyncType,\n    ) -> turso_core::Result<turso_core::Completion> {\n        let web_worker = is_web_worker_safe();\n        tracing::debug!(\"sync({}, is_web_worker={})\", self.handle, web_worker);\n        let handle = self.handle;\n        if web_worker {\n            let result = unsafe { sync(handle) };\n            c.complete(result as i32);\n        } else {\n            let completion_no = self.opfs.register_completion(c.clone());\n            unsafe { sync_async(handle, completion_no) };\n        }\n        Ok(c)\n    }\n\n    fn truncate(\n        &self,\n        len: u64,\n        c: turso_core::Completion,\n    ) -> turso_core::Result<turso_core::Completion> {\n        let web_worker = is_web_worker_safe();\n        tracing::debug!(\n            \"truncate({}, is_web_worker={}): len={}\",\n            self.handle,\n            web_worker,\n            len\n        );\n        let handle = self.handle;\n        if web_worker {\n            let result = unsafe { truncate(handle, len as usize) };\n            c.complete(result as i32);\n        } else {\n            let completion_no = self.opfs.register_completion(c.clone());\n            unsafe { truncate_async(handle, len as usize, completion_no) };\n        }\n        Ok(c)\n    }\n\n    fn size(&self) -> turso_core::Result<u64> {\n        if !is_web_worker_safe() {\n            return Err(turso_core::LimboError::InternalError(\n                \"size can be called only from web worker context\".to_string(),\n            ));\n        }\n        tracing::debug!(\"size({})\", self.handle);\n        let handle = self.handle;\n        let result = unsafe { size(handle) };\n        Ok(result as u64)\n    }\n}\n"
  },
  {
    "path": "bindings/javascript/src/lib.rs",
    "content": "//! JavaScript bindings for the Turso library.\n//!\n//! These bindings provide a thin layer that exposes Turso's Rust API to JavaScript,\n//! maintaining close alignment with the underlying implementation while offering\n//! the following core database operations:\n//!\n//! - Opening and closing database connections\n//! - Preparing SQL statements\n//! - Binding parameters to prepared statements\n//! - Iterating through query results\n//! - Managing the I/O event loop\n\n#[cfg(feature = \"browser\")]\npub mod browser;\n\nuse napi::bindgen_prelude::*;\nuse napi::{Env, Task};\nuse napi_derive::napi;\nuse std::sync::{Mutex, OnceLock};\nuse std::{\n    cell::{Cell, RefCell},\n    num::NonZeroUsize,\n    sync::Arc,\n};\nuse tracing_subscriber::filter::LevelFilter;\nuse tracing_subscriber::fmt::format::FmtSpan;\n\n/// Step result constants\nconst STEP_ROW: u32 = 1;\nconst STEP_DONE: u32 = 2;\nconst STEP_IO: u32 = 3;\n\n/// The presentation mode for rows.\n#[derive(Debug, Clone)]\nenum PresentationMode {\n    Expanded,\n    Raw,\n    Pluck,\n}\n\n/// A database connection.\n#[napi]\n#[derive(Clone)]\npub struct Database {\n    inner: Option<Arc<DatabaseInner>>,\n}\n\n/// database inner is Send to the worker for initial connection\n/// that's why we use OnceLock here - in order to make DatabaseInner Send and Sync\npub struct DatabaseInner {\n    path: String,\n    opts: Option<DatabaseOpts>,\n    io: Arc<dyn turso_core::IO>,\n    connect: OnceLock<DatabaseConnect>,\n    default_safe_integers: Mutex<bool>,\n}\n\npub struct DatabaseConnect {\n    // hold db reference in order to keep it alive\n    // _db can be None if DB is controlled externally (for example, by sync-engine)\n    _db: Option<Arc<turso_core::Database>>,\n    conn: Arc<turso_core::Connection>,\n}\n\npub(crate) fn is_memory(path: &str) -> bool {\n    path == \":memory:\"\n}\n\nstatic TRACING_INIT: OnceLock<()> = OnceLock::new();\npub(crate) fn init_tracing(level_filter: &Option<String>) {\n    let Some(level_filter) = level_filter else {\n        return;\n    };\n    let level_filter = match level_filter.as_ref() {\n        \"error\" => LevelFilter::ERROR,\n        \"warn\" => LevelFilter::WARN,\n        \"info\" => LevelFilter::INFO,\n        \"debug\" => LevelFilter::DEBUG,\n        \"trace\" => LevelFilter::TRACE,\n        _ => return,\n    };\n    TRACING_INIT.get_or_init(|| {\n        tracing_subscriber::fmt()\n            .with_ansi(false)\n            .with_thread_ids(true)\n            .with_span_events(FmtSpan::ACTIVE)\n            .with_max_level(level_filter)\n            .init();\n    });\n}\n\n// for now we make DbTask unsound as turso_core::Database and turso_core::Connection are not fully thread-safe\nunsafe impl Send for DbTask {}\n\npub enum DbTask {\n    Connect { db: Arc<DatabaseInner> },\n}\n\nimpl Task for DbTask {\n    type Output = u32;\n    type JsValue = u32;\n\n    fn compute(&mut self) -> Result<Self::Output> {\n        match self {\n            DbTask::Connect { db } => {\n                connect_sync(db)?;\n                Ok(0)\n            }\n        }\n    }\n\n    fn resolve(&mut self, _: Env, output: Self::Output) -> Result<Self::JsValue> {\n        Ok(output)\n    }\n}\n\n/// Supported encryption ciphers for local database encryption.\n#[napi]\n#[derive(Clone, Copy)]\npub enum EncryptionCipher {\n    Aes128Gcm,\n    Aes256Gcm,\n    Aegis256,\n    Aegis256x2,\n    Aegis128l,\n    Aegis128x2,\n    Aegis128x4,\n}\n\nimpl EncryptionCipher {\n    fn as_str(&self) -> &'static str {\n        match self {\n            EncryptionCipher::Aes128Gcm => \"aes128gcm\",\n            EncryptionCipher::Aes256Gcm => \"aes256gcm\",\n            EncryptionCipher::Aegis256 => \"aegis256\",\n            EncryptionCipher::Aegis256x2 => \"aegis256x2\",\n            EncryptionCipher::Aegis128l => \"aegis128l\",\n            EncryptionCipher::Aegis128x2 => \"aegis128x2\",\n            EncryptionCipher::Aegis128x4 => \"aegis128x4\",\n        }\n    }\n}\n\n/// Encryption configuration for local database encryption.\n#[napi(object)]\n#[derive(Clone)]\npub struct EncryptionOpts {\n    /// The cipher to use for encryption\n    pub cipher: EncryptionCipher,\n    /// The hex-encoded encryption key\n    pub hexkey: String,\n}\n\n/// Most of the options are aligned with better-sqlite API\n/// (see https://github.com/WiseLibs/better-sqlite3/blob/master/docs/api.md#new-databasepath-options)\n#[napi(object)]\n#[derive(Clone)]\npub struct DatabaseOpts {\n    pub readonly: Option<bool>,\n    pub timeout: Option<u32>,\n    pub file_must_exist: Option<bool>,\n    pub tracing: Option<String>,\n    /// Experimental features to enable\n    pub experimental: Option<Vec<String>>,\n    /// Optional encryption configuration for local database encryption\n    pub encryption: Option<EncryptionOpts>,\n}\n\nfn step_sync(stmt: &Arc<RefCell<turso_core::Statement>>) -> napi::Result<u32> {\n    let mut stmt = stmt.borrow_mut();\n    match stmt.step() {\n        Ok(turso_core::StepResult::Row) => Ok(STEP_ROW),\n        Ok(turso_core::StepResult::IO) => Ok(STEP_IO),\n        Ok(turso_core::StepResult::Done) => Ok(STEP_DONE),\n        Ok(turso_core::StepResult::Interrupt) => {\n            Err(create_generic_error(\"statement was interrupted\"))\n        }\n        Ok(turso_core::StepResult::Busy) => Err(create_generic_error(\"database is locked\")),\n        Err(e) => Err(to_generic_error(\"step failed\", e)),\n    }\n}\n\nfn to_generic_error<E: std::error::Error>(message: &str, e: E) -> napi::Error {\n    Error::new(Status::GenericFailure, format!(\"{message}: {e}\"))\n}\n\nfn to_error<E: std::error::Error>(status: napi::Status, message: &str, e: E) -> napi::Error {\n    Error::new(status, format!(\"{message}: {e}\"))\n}\n\nfn create_generic_error(message: &str) -> napi::Error {\n    Error::new(Status::GenericFailure, message)\n}\n\nfn create_error(status: napi::Status, message: &str) -> napi::Error {\n    Error::new(status, message)\n}\n\nfn connect_sync(db: &DatabaseInner) -> napi::Result<()> {\n    if db.connect.get().is_some() {\n        return Ok(());\n    }\n\n    let mut flags = turso_core::OpenFlags::Create;\n    let mut busy_timeout = None;\n    let mut core_opts = turso_core::DatabaseOpts::new();\n    let mut encryption_opts = None;\n    if let Some(opts) = &db.opts {\n        if opts.readonly == Some(true) {\n            flags.set(turso_core::OpenFlags::ReadOnly, true);\n            flags.set(turso_core::OpenFlags::Create, false);\n        }\n        if opts.file_must_exist == Some(true) {\n            flags.set(turso_core::OpenFlags::Create, false);\n        }\n        if let Some(timeout) = opts.timeout {\n            busy_timeout = Some(std::time::Duration::from_millis(timeout as u64));\n        }\n        if let Some(experimental) = &opts.experimental {\n            for feature in experimental {\n                core_opts = match feature.as_str() {\n                    \"views\" => core_opts.with_views(true),\n                    \"strict\" => core_opts, // strict is always enabled, kept for backwards compatibility\n                    \"custom_types\" => core_opts.with_custom_types(true),\n                    \"encryption\" => core_opts.with_encryption(true),\n                    \"index_method\" => core_opts.with_index_method(true),\n                    \"autovacuum\" => core_opts.with_autovacuum(true),\n                    \"attach\" => core_opts.with_attach(true),\n                    _ => core_opts,\n                };\n            }\n        }\n        if let Some(encryption) = &opts.encryption {\n            encryption_opts = Some(turso_core::EncryptionOpts {\n                cipher: encryption.cipher.as_str().to_string(),\n                hexkey: encryption.hexkey.clone(),\n            });\n            // Ensure encryption is enabled if encryption opts are provided\n            core_opts = core_opts.with_encryption(true);\n        }\n    }\n    let io = &db.io;\n    // Parse encryption key if encryption options are provided\n    let encryption_key = if let Some(opts) = &db.opts {\n        if let Some(encryption) = &opts.encryption {\n            Some(\n                turso_core::EncryptionKey::from_hex_string(&encryption.hexkey)\n                    .map_err(|e| to_generic_error(\"invalid encryption key\", e))?,\n            )\n        } else {\n            None\n        }\n    } else {\n        None\n    };\n\n    let db_core = turso_core::Database::open_file_with_flags(\n        io.clone(),\n        &db.path,\n        flags,\n        core_opts,\n        encryption_opts,\n    )\n    .map_err(|e| to_generic_error(&format!(\"failed to open database {}\", db.path), e))?;\n\n    // Use connect_with_encryption to properly set up encryption context\n    // before the pager reads page 1. This is required for encrypted databases.\n    let conn = db_core\n        .connect_with_encryption(encryption_key)\n        .map_err(|e| to_generic_error(\"failed to connect\", e))?;\n\n    if let Some(busy_timeout) = busy_timeout {\n        conn.set_busy_timeout(busy_timeout);\n    }\n\n    let connect = DatabaseConnect {\n        _db: Some(db_core),\n        conn,\n    };\n    // there can be races between concurrent connect - so let's ignore error in case of\n    let _ = db.connect.set(connect);\n    Ok(())\n}\n\n#[napi]\nimpl Database {\n    /// Creates a new database instance.\n    ///\n    /// # Arguments\n    /// * `path` - The path to the database file.\n    #[napi(constructor)]\n    pub fn new(path: String, opts: Option<DatabaseOpts>) -> napi::Result<Self> {\n        let io: Arc<dyn turso_core::IO> = if is_memory(&path) {\n            Arc::new(turso_core::MemoryIO::new())\n        } else {\n            #[cfg(not(feature = \"browser\"))]\n            {\n                Arc::new(\n                    turso_core::PlatformIO::new()\n                        .map_err(|e| to_generic_error(\"failed to create IO\", e))?,\n                )\n            }\n            #[cfg(feature = \"browser\")]\n            {\n                browser::opfs()\n            }\n        };\n        Self::new_with_io(path, io, opts)\n    }\n\n    pub fn new_with_io(\n        path: String,\n        io: Arc<dyn turso_core::IO>,\n        opts: Option<DatabaseOpts>,\n    ) -> napi::Result<Self> {\n        if let Some(opts) = &opts {\n            init_tracing(&opts.tracing);\n        }\n        Ok(Self {\n            #[allow(clippy::arc_with_non_send_sync)]\n            inner: Some(Arc::new(DatabaseInner {\n                path,\n                opts,\n                io,\n                connect: OnceLock::new(),\n                default_safe_integers: Mutex::new(false),\n            })),\n        })\n    }\n\n    pub fn set_connected(&self, conn: Arc<turso_core::Connection>) -> napi::Result<()> {\n        let inner = self.inner()?;\n        inner\n            .connect\n            .set(DatabaseConnect { _db: None, conn })\n            .map_err(|_| create_generic_error(\"database was already connected\"))?;\n\n        Ok(())\n    }\n\n    fn inner(&self) -> napi::Result<&Arc<DatabaseInner>> {\n        let Some(inner) = &self.inner else {\n            return Err(create_generic_error(\"database must be connected\"));\n        };\n        Ok(inner)\n    }\n\n    /// Connect the database synchronously\n    /// This method is idempotent and can be called multiple times safely until the database will be closed\n    #[napi]\n    pub fn connect_sync(&self) -> napi::Result<()> {\n        connect_sync(self.inner()?)\n    }\n\n    /// Connect the database asynchronously\n    /// This method is idempotent and can be called multiple times safely until the database will be closed\n    #[napi(ts_return_type = \"Promise<void>\")]\n    pub fn connect_async(&self) -> napi::Result<AsyncTask<DbTask>> {\n        Ok(AsyncTask::new(DbTask::Connect {\n            db: self.inner()?.clone(),\n        }))\n    }\n\n    fn conn(&self) -> Result<Arc<turso_core::Connection>> {\n        let Some(DatabaseConnect { conn, .. }) = self.inner()?.connect.get() else {\n            return Err(create_generic_error(\"database must be connected\"));\n        };\n        Ok(conn.clone())\n    }\n\n    /// Returns whether the database is in readonly-only mode.\n    #[napi(getter)]\n    pub fn readonly(&self) -> napi::Result<bool> {\n        Ok(self.conn()?.is_readonly(0))\n    }\n\n    /// Returns whether the database is in memory-only mode.\n    #[napi(getter)]\n    pub fn memory(&self) -> napi::Result<bool> {\n        Ok(is_memory(&self.inner()?.path))\n    }\n\n    /// Returns whether the database is in memory-only mode.\n    #[napi(getter)]\n    pub fn path(&self) -> napi::Result<String> {\n        Ok(self.inner()?.path.clone())\n    }\n\n    /// Returns whether the database connection is open.\n    #[napi(getter)]\n    pub fn open(&self) -> napi::Result<bool> {\n        if self.inner.is_none() {\n            return Ok(false);\n        }\n        Ok(self.inner()?.connect.get().is_some())\n    }\n\n    /// Prepares a statement for execution.\n    ///\n    /// # Arguments\n    ///\n    /// * `sql` - The SQL statement to prepare.\n    ///\n    /// # Returns\n    ///\n    /// A `Statement` instance.\n    #[napi]\n    pub fn prepare(&self, sql: String) -> napi::Result<Statement> {\n        let stmt = self\n            .conn()?\n            .prepare(&sql)\n            .map_err(|e| to_generic_error(\"prepare failed\", e))?;\n        let column_names: Vec<std::ffi::CString> = (0..stmt.num_columns())\n            .map(|i| std::ffi::CString::new(stmt.get_column_name(i).to_string()).unwrap())\n            .collect();\n        Ok(Statement {\n            #[allow(clippy::arc_with_non_send_sync)]\n            stmt: Some(Arc::new(RefCell::new(stmt))),\n            column_names,\n            mode: RefCell::new(PresentationMode::Expanded),\n            safe_integers: Cell::new(*self.inner()?.default_safe_integers.lock().unwrap()),\n        })\n    }\n\n    #[napi]\n    pub fn executor(&self, sql: String) -> napi::Result<BatchExecutor> {\n        Ok(BatchExecutor {\n            conn: Some(self.conn()?),\n            sql,\n            position: 0,\n            stmt: None,\n        })\n    }\n\n    /// Returns the rowid of the last row inserted.\n    ///\n    /// # Returns\n    ///\n    /// The rowid of the last row inserted.\n    #[napi]\n    pub fn last_insert_rowid(&self) -> napi::Result<i64> {\n        Ok(self.conn()?.last_insert_rowid())\n    }\n\n    /// Returns the number of changes made by the last statement.\n    ///\n    /// # Returns\n    ///\n    /// The number of changes made by the last statement.\n    #[napi]\n    pub fn changes(&self) -> napi::Result<i64> {\n        Ok(self.conn()?.changes())\n    }\n\n    /// Returns the total number of changes made by all statements.\n    ///\n    /// # Returns\n    ///\n    /// The total number of changes made by all statements.\n    #[napi]\n    pub fn total_changes(&self) -> napi::Result<i64> {\n        Ok(self.conn()?.total_changes())\n    }\n\n    /// Closes the database connection.\n    ///\n    /// # Returns\n    ///\n    /// `Ok(())` if the database is closed successfully.\n    #[napi]\n    pub fn close(&mut self) -> napi::Result<()> {\n        let _ = self.inner.take();\n        Ok(())\n    }\n\n    /// Sets the default safe integers mode for all statements from this database.\n    ///\n    /// # Arguments\n    ///\n    /// * `toggle` - Whether to use safe integers by default.\n    #[napi(js_name = \"defaultSafeIntegers\")]\n    pub fn default_safe_integers(&self, toggle: Option<bool>) -> napi::Result<()> {\n        *self.inner()?.default_safe_integers.lock().unwrap() = toggle.unwrap_or(true);\n        Ok(())\n    }\n\n    /// Runs the I/O loop synchronously.\n    #[napi]\n    pub fn io_loop_sync(&self) -> napi::Result<()> {\n        let io = &self.inner()?.io;\n        io.step().map_err(|e| to_generic_error(\"IO error\", e))?;\n        Ok(())\n    }\n\n    /// Runs the I/O loop asynchronously, returning a Promise.\n    #[napi(ts_return_type = \"Promise<void>\")]\n    pub fn io_loop_async(&self) -> napi::Result<AsyncTask<IoLoopTask>> {\n        let io = self.inner()?.io.clone();\n        Ok(AsyncTask::new(IoLoopTask { io }))\n    }\n\n    /// Classify SQL statement. Returns \"read\", \"write\", \"begin\", \"commit\", or \"rollback\".\n    #[napi(js_name = \"classifySql\")]\n    pub fn classify_sql(&self, sql: String) -> napi::Result<String> {\n        use turso_parser::{ast::Stmt, parser::Parser};\n        let mut parser = Parser::new(sql.as_bytes());\n        match parser.next_cmd() {\n            Ok(Some(cmd)) => {\n                if cmd.is_explain() {\n                    return Ok(\"read\".to_string());\n                }\n                let category = match cmd.stmt() {\n                    Stmt::Select(..)\n                    | Stmt::Pragma { .. }\n                    | Stmt::Attach { .. }\n                    | Stmt::Detach { .. }\n                    | Stmt::Reindex { .. } => \"read\",\n                    Stmt::Begin { .. } | Stmt::Savepoint { .. } => \"begin\",\n                    Stmt::Commit { .. } | Stmt::Release { .. } => \"commit\",\n                    Stmt::Rollback { .. } => \"rollback\",\n                    _ => \"write\",\n                };\n                Ok(category.to_string())\n            }\n            Ok(None) => Ok(\"read\".to_string()),\n            Err(e) => Err(napi::Error::from_reason(format!(\"classify failed: {e}\"))),\n        }\n    }\n}\n\n#[napi]\npub struct BatchExecutor {\n    conn: Option<Arc<turso_core::Connection>>,\n    sql: String,\n    position: usize,\n    stmt: Option<Arc<RefCell<turso_core::Statement>>>,\n}\n\n#[napi]\nimpl BatchExecutor {\n    #[napi]\n    pub fn step_sync(&mut self) -> Result<u32> {\n        loop {\n            if self.stmt.is_none() && self.position >= self.sql.len() {\n                return Ok(STEP_DONE);\n            }\n            if self.stmt.is_none() {\n                let conn = self.conn.as_ref().unwrap();\n                match conn.consume_stmt(&self.sql[self.position..]) {\n                    #[allow(clippy::arc_with_non_send_sync)]\n                    Ok(Some((stmt, offset))) => {\n                        self.position += offset;\n                        self.stmt = Some(Arc::new(RefCell::new(stmt)));\n                    }\n                    Ok(None) => return Ok(STEP_DONE),\n                    Err(err) => return Err(to_generic_error(\"failed to consume stmt\", err)),\n                }\n            }\n            let stmt = self.stmt.as_ref().unwrap();\n            match step_sync(stmt) {\n                Ok(STEP_DONE) => {\n                    let _ = self.stmt.take();\n                    continue;\n                }\n                result => return result,\n            }\n        }\n    }\n\n    #[napi]\n    pub fn reset(&mut self) {\n        let _ = self.conn.take();\n        let _ = self.stmt.take();\n    }\n}\n\n/// A prepared statement.\n#[napi]\npub struct Statement {\n    stmt: Option<Arc<RefCell<turso_core::Statement>>>,\n    column_names: Vec<std::ffi::CString>,\n    mode: RefCell<PresentationMode>,\n    safe_integers: Cell<bool>,\n}\n\n#[napi]\nimpl Statement {\n    pub fn stmt(&self) -> napi::Result<&Arc<RefCell<turso_core::Statement>>> {\n        self.stmt\n            .as_ref()\n            .ok_or_else(|| create_generic_error(\"statement has been finalized\"))\n    }\n    #[napi]\n    pub fn reset(&self) -> Result<()> {\n        self.stmt()?\n            .borrow_mut()\n            .reset()\n            .map_err(|e| to_generic_error(\"reset failed\", e))?;\n        Ok(())\n    }\n\n    /// Returns the number of parameters in the statement.\n    #[napi]\n    pub fn parameter_count(&self) -> Result<u32> {\n        Ok(self.stmt()?.borrow().parameters_count() as u32)\n    }\n\n    /// Returns the name of a parameter at a specific 1-based index.\n    ///\n    /// # Arguments\n    ///\n    /// * `index` - The 1-based parameter index.\n    #[napi]\n    pub fn parameter_name(&self, index: u32) -> Result<Option<String>> {\n        let non_zero_idx = NonZeroUsize::new(index as usize).ok_or_else(|| {\n            create_error(Status::InvalidArg, \"parameter index must be greater than 0\")\n        })?;\n\n        let stmt = self.stmt()?.borrow();\n        Ok(stmt.parameters().name(non_zero_idx))\n    }\n\n    /// Binds a parameter at a specific 1-based index with explicit type.\n    ///\n    /// # Arguments\n    ///\n    /// * `index` - The 1-based parameter index.\n    /// * `value_type` - The type constant (0=null, 1=int, 2=float, 3=text, 4=blob).\n    /// * `value` - The value to bind.\n    #[napi]\n    pub fn bind_at(&self, index: u32, value: Unknown) -> Result<()> {\n        let non_zero_idx = NonZeroUsize::new(index as usize).ok_or_else(|| {\n            create_error(Status::InvalidArg, \"parameter index must be greater than 0\")\n        })?;\n        let value_type = value.get_type()?;\n        let turso_value = match value_type {\n            ValueType::Null => turso_core::Value::Null,\n            ValueType::Number => {\n                let n: f64 = unsafe { value.cast()? };\n                if n.fract() == 0.0 && n >= i64::MIN as f64 && n <= i64::MAX as f64 {\n                    turso_core::Value::from_i64(n as i64)\n                } else {\n                    turso_core::Value::from_f64(n)\n                }\n            }\n            ValueType::BigInt => {\n                let bigint_str = value.coerce_to_string()?.into_utf8()?.as_str()?.to_owned();\n                let bigint_value = bigint_str\n                    .parse::<i64>()\n                    .map_err(|e| to_error(Status::NumberExpected, \"failed to parse BigInt\", e))?;\n                turso_core::Value::from_i64(bigint_value)\n            }\n            ValueType::String => {\n                let s = value.coerce_to_string()?.into_utf8()?;\n                turso_core::Value::Text(s.as_str()?.to_owned().into())\n            }\n            ValueType::Boolean => {\n                let b: bool = unsafe { value.cast()? };\n                turso_core::Value::from_i64(if b { 1 } else { 0 })\n            }\n            ValueType::Object => {\n                let obj = value.coerce_to_object()?;\n\n                if obj.is_buffer()? || obj.is_typedarray()? {\n                    let length = obj.get_named_property::<u32>(\"length\")?;\n                    let mut bytes = Vec::with_capacity(length as usize);\n                    for i in 0..length {\n                        let byte = obj.get_element::<u32>(i)?;\n                        bytes.push(byte as u8);\n                    }\n                    turso_core::Value::Blob(bytes)\n                } else {\n                    let s = value.coerce_to_string()?.into_utf8()?;\n                    turso_core::Value::Text(s.as_str()?.to_owned().into())\n                }\n            }\n            _ => {\n                let s = value.coerce_to_string()?.into_utf8()?;\n                turso_core::Value::Text(s.as_str()?.to_owned().into())\n            }\n        };\n\n        self.stmt()?.borrow_mut().bind_at(non_zero_idx, turso_value);\n        Ok(())\n    }\n\n    /// Step the statement and return result code (executed on the main thread):\n    /// 1 = Row available, 2 = Done, 3 = I/O needed\n    #[napi]\n    pub fn step_sync(&self) -> Result<u32> {\n        step_sync(self.stmt()?)\n    }\n\n    /// Get the current row data according to the presentation mode\n    #[napi]\n    pub fn row<'env>(&self, env: &'env Env) -> Result<Unknown<'env>> {\n        let stmt = self.stmt()?.borrow();\n        let row_data = stmt\n            .row()\n            .ok_or_else(|| create_generic_error(\"no row data available\"))?;\n\n        let mode = self.mode.borrow();\n        let safe_integers = self.safe_integers.get();\n        let row_value = match *mode {\n            PresentationMode::Raw => {\n                let mut raw_array = env.create_array(row_data.len() as u32)?;\n                for (idx, value) in row_data.get_values().enumerate() {\n                    let js_value = to_js_value(env, value, safe_integers)?;\n                    raw_array.set(idx as u32, js_value)?;\n                }\n                raw_array.coerce_to_object()?.to_unknown()\n            }\n            PresentationMode::Pluck => {\n                let (_, value) = row_data.get_values().enumerate().next().ok_or_else(|| {\n                    create_generic_error(\"pluck mode requires at least one column in the result\")\n                })?;\n                to_js_value(env, value, safe_integers)?\n            }\n            PresentationMode::Expanded => {\n                let row = Object::new(env)?;\n                let raw_row = row.raw();\n                let raw_env = env.raw();\n                for idx in 0..row_data.len() {\n                    let value = row_data.get_value(idx);\n                    let column_name = &self.column_names[idx];\n                    let js_value = to_js_value(env, value, safe_integers)?;\n                    unsafe {\n                        napi::sys::napi_set_named_property(\n                            raw_env,\n                            raw_row,\n                            column_name.as_ptr(),\n                            js_value.raw(),\n                        );\n                    }\n                }\n                row.to_unknown()\n            }\n        };\n\n        Ok(row_value)\n    }\n\n    /// Sets the presentation mode to raw.\n    #[napi]\n    pub fn raw(&mut self, raw: Option<bool>) {\n        self.mode = RefCell::new(match raw {\n            Some(false) => PresentationMode::Expanded,\n            _ => PresentationMode::Raw,\n        });\n    }\n\n    /// Sets the presentation mode to pluck.\n    #[napi]\n    pub fn pluck(&mut self, pluck: Option<bool>) {\n        self.mode = RefCell::new(match pluck {\n            Some(false) => PresentationMode::Expanded,\n            _ => PresentationMode::Pluck,\n        });\n    }\n\n    /// Sets safe integers mode for this statement.\n    ///\n    /// # Arguments\n    ///\n    /// * `toggle` - Whether to use safe integers.\n    #[napi(js_name = \"safeIntegers\")]\n    pub fn safe_integers(&self, toggle: Option<bool>) {\n        self.safe_integers.set(toggle.unwrap_or(true));\n    }\n\n    /// Get column information for the statement\n    #[napi(ts_return_type = \"Promise<any>\")]\n    pub fn columns<'env>(&self, env: &'env Env) -> Result<Array<'env>> {\n        let stmt = self.stmt()?.borrow();\n\n        let column_count = stmt.num_columns();\n        let mut js_array = env.create_array(column_count as u32)?;\n\n        for i in 0..column_count {\n            let mut js_obj = Object::new(env)?;\n            let column_name = stmt.get_column_name(i);\n            let column_type = stmt.get_column_type_name(i);\n\n            // Set the name property\n            js_obj.set(\"name\", column_name.as_ref())?;\n\n            // Set type property if available\n            match column_type {\n                Some(type_str) => js_obj.set(\"type\", type_str.as_str())?,\n                None => js_obj.set(\"type\", ToNapiValue::into_unknown(Null, env)?)?,\n            }\n\n            // For now, set other properties to null since turso_core doesn't provide this metadata\n            js_obj.set(\"column\", ToNapiValue::into_unknown(Null, env)?)?;\n            js_obj.set(\"table\", ToNapiValue::into_unknown(Null, env)?)?;\n            js_obj.set(\"database\", ToNapiValue::into_unknown(Null, env)?)?;\n\n            js_array.set(i as u32, js_obj)?;\n        }\n\n        Ok(js_array)\n    }\n\n    /// Finalizes the statement.\n    #[napi]\n    pub fn finalize(&mut self) -> Result<()> {\n        let _ = self.stmt.take();\n        Ok(())\n    }\n}\n\n/// Async task for running the I/O loop.\npub struct IoLoopTask {\n    // this field is public because it is also set in the sync package\n    pub io: Arc<dyn turso_core::IO>,\n}\n\nimpl Task for IoLoopTask {\n    type Output = ();\n    type JsValue = ();\n\n    fn compute(&mut self) -> napi::Result<Self::Output> {\n        self.io\n            .step()\n            .map_err(|e| to_generic_error(\"IO error\", e))?;\n        Ok(())\n    }\n\n    fn resolve(&mut self, _env: Env, _output: Self::Output) -> napi::Result<Self::JsValue> {\n        Ok(())\n    }\n}\n\n/// Convert a Turso value to a JavaScript value.\nfn to_js_value<'a>(\n    env: &'a napi::Env,\n    value: &turso_core::Value,\n    safe_integers: bool,\n) -> napi::Result<Unknown<'a>> {\n    match value {\n        turso_core::Value::Null => ToNapiValue::into_unknown(Null, env),\n        turso_core::Value::Numeric(turso_core::Numeric::Integer(i)) => {\n            if safe_integers {\n                let bigint = BigInt::from(*i);\n                ToNapiValue::into_unknown(bigint, env)\n            } else {\n                ToNapiValue::into_unknown(*i as f64, env)\n            }\n        }\n        turso_core::Value::Numeric(turso_core::Numeric::Float(f)) => {\n            ToNapiValue::into_unknown(f64::from(*f), env)\n        }\n        turso_core::Value::Text(s) => ToNapiValue::into_unknown(s.as_str(), env),\n        turso_core::Value::Blob(b) => {\n            #[cfg(not(feature = \"browser\"))]\n            {\n                let buffer = Buffer::from(b.as_slice());\n                ToNapiValue::into_unknown(buffer, env)\n            }\n            // emnapi do not support Buffer\n            #[cfg(feature = \"browser\")]\n            {\n                let buffer = Uint8Array::from(b.as_slice());\n                ToNapiValue::into_unknown(buffer, env)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "bindings/javascript/sync/Cargo.toml",
    "content": "[package]\nname = \"turso_sync_js\"\nversion.workspace = true\nauthors.workspace = true\nedition.workspace = true\nlicense.workspace = true\nrepository.workspace = true\n\n[lints]\nworkspace = true\n\n[lib]\ncrate-type = [\"cdylib\"]\n\n[dependencies]\nnapi = { version = \"3.1.3\", default-features = false, features = [\"napi6\"] }\nnapi-derive = { version = \"3.1.1\", default-features = true }\nturso_sync_engine = { workspace = true }\nturso_core = { workspace = true, features = [\"fts\"] }\nturso_node = { workspace = true }\ngenawaiter = { version = \"0.99.1\", default-features = false }\ntracing-subscriber = { workspace = true }\ntracing.workspace = true\n\n[build-dependencies]\nnapi-build = \"2.2.3\"\n\n[features]\nbrowser = [\"turso_node/browser\"]\n"
  },
  {
    "path": "bindings/javascript/sync/README.md",
    "content": "<p align=\"center\">\n  <h1 align=\"center\">Turso Sync for JavaScript</h1>\n</p>\n\n<p align=\"center\">\n  <a title=\"JavaScript\" target=\"_blank\" href=\"https://www.npmjs.com/package/@tursodatabase/sync\"><img alt=\"npm\" src=\"https://img.shields.io/npm/v/@tursodatabase/sync\"></a>\n  <a title=\"MIT\" target=\"_blank\" href=\"https://github.com/tursodatabase/turso/blob/main/LICENSE.md\"><img src=\"http://img.shields.io/badge/license-MIT-orange.svg?style=flat-square\"></a>\n</p>\n<p align=\"center\">\n  <a title=\"Users Discord\" target=\"_blank\" href=\"https://tur.so/discord\"><img alt=\"Chat with other users of Turso on Discord\" src=\"https://img.shields.io/discord/933071162680958986?label=Discord&logo=Discord&style=social\"></a>\n</p>\n\n---\n\n## About\n\nThis package is for syncing local Turso databases to the Turso Cloud and back.\n\n> **⚠️ Warning:** This software is in BETA. It may still contain bugs and unexpected behavior. Use caution with production data and ensure you have backups.\n\n## Installation\n\n```bash\nnpm install @tursodatabase/sync\n```\n\n## Getting Started\n\nTo sync a database hosted at [Turso Cloud](https://turso.tech):\n\n```js\nimport { connect } from '@tursodatabase/sync';\n\nconst db = await connect({\n    path: 'local.db',                // path used as a prefix for local files created by sync-engine\n    url: 'https://<db>.turso.io',    // URL of the remote database: turso db show <db>\n    authToken: '...',                // auth token issued from the Turso Cloud: turso db tokens create <db>\n    clientName: 'turso-sync-example' // arbitrary client name\n});\n\n// db has same functions as Database class from @tursodatabase/database package but adds few more methods for sync:\nawait db.pull(); // pull changes from the remote\nawait db.push(); // push changes to the remote\nawait db.sync(); // pull & push changes\n```\n\n## Related Packages\n\n* The [@tursodatabase/database](https://www.npmjs.com/package/@tursodatabase/database) package provides the Turso in-memory database, compatible with SQLite.\n* The [@tursodatabase/serverless](https://www.npmjs.com/package/@tursodatabase/serverless) package provides a serverless driver with the same API.\n\n## License\n\nThis project is licensed under the [MIT license](https://github.com/tursodatabase/turso/blob/main/LICENSE.md).\n\n## Support\n\n- [GitHub Issues](https://github.com/tursodatabase/turso/issues)\n- [Documentation](https://docs.turso.tech)\n- [Discord Community](https://tur.so/discord)\n"
  },
  {
    "path": "bindings/javascript/sync/build.rs",
    "content": "fn main() {\n    napi_build::setup();\n}\n"
  },
  {
    "path": "bindings/javascript/sync/packages/common/README.md",
    "content": "## About\n\nThis package is the Turso Sync common JS library which is shared between final builds for Node and Browser.\n\nDo not use this package directly - instead you must use `@tursodatabase/sync` or `@tursodatabase/sync-wasm`.\n\n> **⚠️ Warning:** This software is in BETA. It may still contain bugs and unexpected behavior. Use caution with production data and ensure you have backups.\n\n"
  },
  {
    "path": "bindings/javascript/sync/packages/common/index.ts",
    "content": "import { run, memoryIO, runner, SyncEngineGuards, Runner } from \"./run.js\"\nimport {\n    DatabaseOpts,\n    ProtocolIo,\n    RunOpts,\n    DatabaseRowMutation,\n    DatabaseRowStatement,\n    DatabaseRowTransformResult,\n    DatabaseStats,\n    DatabaseChangeType,\n    EncryptionOpts,\n} from \"./types.js\"\nimport { RemoteWriter, RemoteWriterConfig } from \"./remote-writer.js\"\nimport { RemoteWriteStatement } from \"./remote-write-statement.js\"\n\nexport { run, memoryIO, runner, SyncEngineGuards, Runner }\nexport { RemoteWriter, RemoteWriteStatement }\nexport type {\n    DatabaseStats,\n    DatabaseOpts,\n    DatabaseChangeType,\n    DatabaseRowMutation,\n    DatabaseRowStatement,\n    DatabaseRowTransformResult,\n    EncryptionOpts,\n    RemoteWriterConfig,\n\n    ProtocolIo,\n    RunOpts,\n}"
  },
  {
    "path": "bindings/javascript/sync/packages/common/package.json",
    "content": "{\n  \"name\": \"@tursodatabase/sync-common\",\n  \"version\": \"0.6.0-pre.4\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/tursodatabase/turso\"\n  },\n  \"type\": \"module\",\n  \"license\": \"MIT\",\n  \"main\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"packageManager\": \"yarn@4.9.2\",\n  \"files\": [\n    \"dist/**\",\n    \"README.md\"\n  ],\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.2\"\n  },\n  \"scripts\": {\n    \"tsc-build\": \"npm exec tsc\",\n    \"build\": \"npm run tsc-build\",\n    \"test\": \"echo 'no tests'\"\n  },\n  \"dependencies\": {\n    \"@tursodatabase/database-common\": \"^0.6.0-pre.4\",\n    \"@tursodatabase/serverless\": \"^0.2.2\"\n  }\n}\n"
  },
  {
    "path": "bindings/javascript/sync/packages/common/remote-write-statement.ts",
    "content": "import { RemoteWriter } from \"./remote-writer.js\";\n\n/**\n * Wraps a local Statement and routes execution to remote when appropriate.\n * - If remoteWriter.isInTransaction → all go remote\n * - If !stmt.readonly → remote + pull after execution\n * - Otherwise → local Statement\n */\nexport class RemoteWriteStatement {\n    private localStmt: any;\n    private sql: string;\n    private isStmtReadonly: boolean;\n    private remoteWriter: RemoteWriter;\n    private pullFn: () => Promise<boolean>;\n    private _boundArgs: any[] = [];\n\n    constructor(\n        localStmt: any,\n        sql: string,\n        isStmtReadonly: boolean,\n        remoteWriter: RemoteWriter,\n        pullFn: () => Promise<boolean>,\n    ) {\n        this.localStmt = localStmt;\n        this.sql = sql;\n        this.isStmtReadonly = isStmtReadonly;\n        this.remoteWriter = remoteWriter;\n        this.pullFn = pullFn;\n    }\n\n    private shouldGoRemote(): boolean {\n        return this.remoteWriter.isInTransaction || !this.isStmtReadonly;\n    }\n\n    private shouldPullAfter(): boolean {\n        // Pull after standalone writes (not in transaction)\n        return !this.isStmtReadonly && !this.remoteWriter.isInTransaction;\n    }\n\n    raw(toggle?: boolean) {\n        this.localStmt.raw(toggle);\n        return this;\n    }\n\n    pluck(toggle?: boolean) {\n        this.localStmt.pluck(toggle);\n        return this;\n    }\n\n    safeIntegers(toggle?: boolean) {\n        this.localStmt.safeIntegers(toggle);\n        return this;\n    }\n\n    columns() {\n        return this.localStmt.columns();\n    }\n\n    get reader(): boolean {\n        return this.localStmt.reader;\n    }\n\n    bind(...bindParameters: any[]) {\n        this._boundArgs = flattenArgs(bindParameters);\n        this.localStmt.bind(...bindParameters);\n        return this;\n    }\n\n    async run(...bindParameters: any[]) {\n        if (!this.shouldGoRemote()) {\n            return await this.localStmt.run(...bindParameters);\n        }\n        const args = bindParameters.length > 0 ? flattenArgs(bindParameters) : this._boundArgs;\n        const result = await this.remoteWriter.execute(this.sql, args);\n        if (this.shouldPullAfter()) {\n            await this.pullFn();\n        }\n        return {\n            changes: result.rowsAffected,\n            lastInsertRowid: result.lastInsertRowid,\n        };\n    }\n\n    async get(...bindParameters: any[]) {\n        if (!this.shouldGoRemote()) {\n            return await this.localStmt.get(...bindParameters);\n        }\n        const args = bindParameters.length > 0 ? flattenArgs(bindParameters) : this._boundArgs;\n        const result = await this.remoteWriter.execute(this.sql, args);\n        if (this.shouldPullAfter()) {\n            await this.pullFn();\n        }\n        return result.rows.length > 0 ? result.rows[0] : undefined;\n    }\n\n    async all(...bindParameters: any[]) {\n        if (!this.shouldGoRemote()) {\n            return await this.localStmt.all(...bindParameters);\n        }\n        const args = bindParameters.length > 0 ? flattenArgs(bindParameters) : this._boundArgs;\n        const result = await this.remoteWriter.execute(this.sql, args);\n        if (this.shouldPullAfter()) {\n            await this.pullFn();\n        }\n        return result.rows;\n    }\n\n    async *iterate(...bindParameters: any[]) {\n        if (!this.shouldGoRemote()) {\n            yield* this.localStmt.iterate(...bindParameters);\n            return;\n        }\n        const args = bindParameters.length > 0 ? flattenArgs(bindParameters) : this._boundArgs;\n        const result = await this.remoteWriter.execute(this.sql, args);\n        if (this.shouldPullAfter()) {\n            await this.pullFn();\n        }\n        for (const row of result.rows) {\n            yield row;\n        }\n    }\n\n    close() {\n        this.localStmt.close();\n    }\n}\n\nfunction flattenArgs(bindParameters: any[]): any[] {\n    if (bindParameters.length === 1 && Array.isArray(bindParameters[0])) {\n        return bindParameters[0];\n    }\n    return bindParameters;\n}\n"
  },
  {
    "path": "bindings/javascript/sync/packages/common/remote-writer.ts",
    "content": "import { Session, type SessionConfig } from \"@tursodatabase/serverless\";\n\nexport interface RemoteWriterConfig {\n    url: string;\n    authToken?: string | (() => Promise<string>);\n    remoteEncryptionKey?: string;\n}\n\n/**\n * Manages remote write routing using Session from @tursodatabase/serverless.\n * For standalone writes (not in txn), creates a temporary Session per write.\n * For transactions, reuses Session (baton-based) across all statements.\n */\nexport class RemoteWriter {\n    private session: Session | null = null;\n    private _inRemoteTxn: boolean = false;\n    private config: RemoteWriterConfig;\n\n    constructor(config: RemoteWriterConfig) {\n        this.config = config;\n    }\n\n    private async resolveAuthToken(): Promise<string | undefined> {\n        if (typeof this.config.authToken === \"function\") {\n            return await this.config.authToken();\n        }\n        return this.config.authToken;\n    }\n\n    private async createSession(): Promise<Session> {\n        const authToken = await this.resolveAuthToken();\n        const sessionConfig: SessionConfig = {\n            url: this.config.url,\n            authToken,\n            remoteEncryptionKey: this.config.remoteEncryptionKey,\n        };\n        return new Session(sessionConfig);\n    }\n\n    /**\n     * Execute single SQL on remote. Creates temp session if not in txn.\n     */\n    async execute(sql: string, args: any[] = []): Promise<{\n        columns: string[];\n        rows: any[];\n        rowsAffected: number;\n        lastInsertRowid: number | undefined;\n    }> {\n        if (this._inRemoteTxn) {\n            return await this.session!.execute(sql, args);\n        }\n        const session = await this.createSession();\n        try {\n            return await session.execute(sql, args);\n        } finally {\n            await session.close();\n        }\n    }\n\n    /**\n     * Execute multi-statement SQL on remote (for exec() path).\n     */\n    async sequence(sql: string): Promise<void> {\n        if (this._inRemoteTxn) {\n            await this.session!.sequence(sql);\n            return;\n        }\n        const session = await this.createSession();\n        try {\n            await session.sequence(sql);\n        } finally {\n            await session.close();\n        }\n    }\n\n    /**\n     * Begin a remote transaction. Creates a session and sends BEGIN.\n     */\n    async beginTransaction(mode: string): Promise<void> {\n        this.session = await this.createSession();\n        await this.session.sequence(\"BEGIN \" + mode);\n        this._inRemoteTxn = true;\n    }\n\n    /**\n     * Commit the remote transaction. Sends COMMIT and closes the session.\n     */\n    async commitTransaction(): Promise<void> {\n        if (!this.session) {\n            throw new Error(\"No active remote transaction\");\n        }\n        try {\n            await this.session.sequence(\"COMMIT\");\n        } finally {\n            this._inRemoteTxn = false;\n            await this.session.close();\n            this.session = null;\n        }\n    }\n\n    /**\n     * Rollback the remote transaction. Sends ROLLBACK and closes the session.\n     */\n    async rollbackTransaction(): Promise<void> {\n        if (!this.session) {\n            throw new Error(\"No active remote transaction\");\n        }\n        try {\n            await this.session.sequence(\"ROLLBACK\");\n        } finally {\n            this._inRemoteTxn = false;\n            await this.session.close();\n            this.session = null;\n        }\n    }\n\n    /**\n     * Execute SQL on remote with auto-detection of BEGIN/COMMIT/ROLLBACK.\n     * Manages session lifecycle based on SQL category.\n     */\n    async execRemote(sql: string, category: string): Promise<{ shouldPull: boolean }> {\n        if (category === \"begin\") {\n            this.session = await this.createSession();\n            await this.session.sequence(sql);\n            this._inRemoteTxn = true;\n            return { shouldPull: false };\n        }\n        if (category === \"commit\") {\n            if (!this.session) throw new Error(\"No active remote transaction\");\n            try { await this.session.sequence(sql); }\n            finally { this._inRemoteTxn = false; await this.session.close(); this.session = null; }\n            return { shouldPull: true };\n        }\n        if (category === \"rollback\") {\n            if (!this.session) throw new Error(\"No active remote transaction\");\n            try { await this.session.sequence(sql); }\n            finally { this._inRemoteTxn = false; await this.session.close(); this.session = null; }\n            return { shouldPull: false };\n        }\n        await this.sequence(sql);\n        return { shouldPull: !this._inRemoteTxn };\n    }\n\n    get isInTransaction(): boolean {\n        return this._inRemoteTxn;\n    }\n\n    async close(): Promise<void> {\n        if (this.session) {\n            try {\n                if (this._inRemoteTxn) {\n                    await this.session.sequence(\"ROLLBACK\");\n                }\n            } catch {\n                // ignore errors during cleanup\n            }\n            await this.session.close();\n            this.session = null;\n            this._inRemoteTxn = false;\n        }\n    }\n}\n"
  },
  {
    "path": "bindings/javascript/sync/packages/common/run.ts",
    "content": "\"use strict\";\n\nimport { GeneratorResponse, ProtocolIo, RunOpts } from \"./types.js\";\nimport { AsyncLock } from \"@tursodatabase/database-common\";\n\ninterface TrackPromise<T> {\n    promise: Promise<T>,\n    finished: boolean\n}\n\nfunction trackPromise<T>(p: Promise<T>): TrackPromise<T> {\n    let status = { promise: null, finished: false };\n    status.promise = p.finally(() => status.finished = true);\n    return status;\n}\n\nfunction timeoutMs(ms: number): Promise<void> {\n    return new Promise(resolve => setTimeout(resolve, ms))\n}\n\nfunction normalizeUrl(url: string): string {\n    return url.replace(/^libsql:\\/\\//, 'https://');\n}\n\nasync function process(opts: RunOpts, io: ProtocolIo, request: any) {\n    const requestType = request.request();\n    const completion = request.completion();\n    if (requestType.type == 'Http') {\n        let url: string | null = requestType.url;\n        if (typeof opts.url == \"function\") {\n            url = opts.url();\n        } else {\n            url = opts.url;\n        }\n        if (url == null) {\n            completion.poison(`url is empty - sync is paused`);\n            return;\n        }\n        url = normalizeUrl(url);\n        try {\n            let headers = typeof opts.headers === \"function\" ? await opts.headers() : opts.headers;\n            if (requestType.headers != null && requestType.headers.length > 0) {\n                headers = { ...headers };\n                for (let header of requestType.headers) {\n                    headers[header[0]] = header[1];\n                }\n            }\n            const response = await fetch(`${url}${requestType.path}`, {\n                method: requestType.method,\n                headers: headers,\n                body: requestType.body != null ? new Uint8Array(requestType.body) : null,\n            });\n            completion.status(response.status);\n            const reader = response.body.getReader();\n            while (true) {\n                const { done, value } = await reader.read();\n                if (done) {\n                    completion.done();\n                    break;\n                }\n                completion.pushBuffer(value);\n            }\n        } catch (error) {\n            completion.poison(`fetch error: ${error}`);\n        }\n    } else if (requestType.type == 'FullRead') {\n        try {\n            const metadata = await io.read(requestType.path);\n            if (metadata != null) {\n                completion.pushBuffer(metadata);\n            }\n            completion.done();\n        } catch (error) {\n            completion.poison(`metadata read error: ${error}`);\n        }\n    } else if (requestType.type == 'FullWrite') {\n        try {\n            await io.write(requestType.path, requestType.content);\n            completion.done();\n        } catch (error) {\n            completion.poison(`metadata write error: ${error}`);\n        }\n    } else if (requestType.type == 'Transform') {\n        if (opts.transform == null) {\n            completion.poison(\"transform is not set\");\n            return;\n        }\n        const results = [];\n        for (const mutation of requestType.mutations) {\n            const result = opts.transform(mutation);\n            if (result == null) {\n                results.push({ type: 'Keep' });\n            } else if (result.operation == 'skip') {\n                results.push({ type: 'Skip' });\n            } else if (result.operation == 'rewrite') {\n                results.push({ type: 'Rewrite', stmt: result.stmt });\n            } else {\n                completion.poison(\"unexpected transform operation\");\n                return;\n            }\n        }\n        completion.pushTransform(results);\n        completion.done();\n    }\n}\n\nexport function memoryIO(): ProtocolIo {\n    let values = new Map();\n    return {\n        async read(path: string): Promise<Buffer | Uint8Array | null> {\n            return values.get(path);\n        },\n        async write(path: string, data: Buffer | Uint8Array): Promise<void> {\n            values.set(path, data);\n        }\n    }\n};\n\nexport interface Runner {\n    wait(): Promise<void>;\n}\n\nexport function runner(opts: RunOpts, io: ProtocolIo, engine: any): Runner {\n    let tasks = [];\n    return {\n        async wait() {\n            for (let request = engine.protocolIo(); request != null; request = engine.protocolIo()) {\n                tasks.push(trackPromise(process(opts, io, request)));\n            }\n            const tasksRace = tasks.length == 0 ? Promise.resolve() : Promise.race([timeoutMs(opts.preemptionMs), ...tasks.map(t => t.promise)]);\n            await Promise.all([engine.ioLoopAsync(), tasksRace]);\n\n            tasks = tasks.filter(t => !t.finished);\n\n            engine.protocolIoStep();\n        },\n    }\n}\n\nexport async function run(runner: Runner, generator: any): Promise<any> {\n    while (true) {\n        const { type, ...rest }: GeneratorResponse = await generator.resumeAsync(null);\n        if (type == 'Done') {\n            return null;\n        }\n        if (type == 'SyncEngineStats') {\n            return rest;\n        }\n        if (type == 'SyncEngineChanges') {\n            //@ts-ignore\n            return rest.changes;\n        }\n        await runner.wait();\n    }\n}\n\nexport class SyncEngineGuards {\n    waitLock: AsyncLock;\n    pushLock: AsyncLock;\n    pullLock: AsyncLock;\n    checkpointLock: AsyncLock;\n    constructor() {\n        this.waitLock = new AsyncLock();\n        this.pushLock = new AsyncLock();\n        this.pullLock = new AsyncLock();\n        this.checkpointLock = new AsyncLock();\n    }\n    async wait(f: () => Promise<any>): Promise<any> {\n        try {\n            await this.waitLock.acquire();\n            return await f();\n        } finally {\n            this.waitLock.release();\n        }\n    }\n    async push(f: () => Promise<void>) {\n        try {\n            await this.pushLock.acquire();\n            await this.pullLock.acquire();\n            await this.checkpointLock.acquire();\n            return await f();\n        } finally {\n            this.pushLock.release();\n            this.pullLock.release();\n            this.checkpointLock.release();\n        }\n    }\n    async apply(f: () => Promise<void>) {\n        try {\n            await this.waitLock.acquire();\n            await this.pushLock.acquire();\n            await this.pullLock.acquire();\n            await this.checkpointLock.acquire();\n            return await f();\n        } finally {\n            this.waitLock.release();\n            this.pushLock.release();\n            this.pullLock.release();\n            this.checkpointLock.release();\n        }\n    }\n    async checkpoint(f: () => Promise<void>) {\n        try {\n            await this.waitLock.acquire();\n            await this.pushLock.acquire();\n            await this.pullLock.acquire();\n            await this.checkpointLock.acquire();\n            return await f();\n        } finally {\n            this.waitLock.release();\n            this.pushLock.release();\n            this.pullLock.release();\n            this.checkpointLock.release();\n        }\n    }\n}"
  },
  {
    "path": "bindings/javascript/sync/packages/common/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"skipLibCheck\": true,\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"module\": \"nodenext\",\n    \"target\": \"esnext\",\n    \"moduleResolution\": \"nodenext\",\n    \"outDir\": \"dist/\",\n    \"lib\": [\n      \"es2020\",\n      \"dom\"\n    ],\n  },\n  \"include\": [\n    \"*\"\n  ]\n}"
  },
  {
    "path": "bindings/javascript/sync/packages/common/types.ts",
    "content": "export declare const enum DatabaseChangeType {\n    Insert = 'insert',\n    Update = 'update',\n    Delete = 'delete'\n}\n\nexport interface DatabaseRowStatement {\n    /**\n     * SQL statements with positional placeholders (?)\n     */\n    sql: string\n    /**\n     * values to substitute placeholders\n     */\n    values: Array<any>\n}\n\n\n/**\n * transformation result:\n * - skip: ignore the mutation completely and do not apply it\n * - rewrite: replace mutation with the provided statement\n * - null: do not change mutation and keep it as is\n */\nexport type DatabaseRowTransformResult = { operation: 'skip' } | { operation: 'rewrite', stmt: DatabaseRowStatement } | null;\n\nexport interface DatabaseRowMutation {\n    /**\n     * unix seconds timestamp of the change\n     */\n    changeTime: number\n    /**\n     * table name of the change\n     */\n    tableName: string\n    /**\n     * rowid of the change\n     */\n    id: number\n    /**\n     * type of the change (insert/delete/update)\n     */\n    changeType: DatabaseChangeType\n    /**\n     * columns of the row before the change\n     */\n    before?: Record<string, any>\n    /**\n     * columns of the row after the change\n     */\n    after?: Record<string, any>\n    /**\n     * only updated columns of the row after the change\n     */\n    updates?: Record<string, any>\n}\n\nexport type Transform = (arg: DatabaseRowMutation) => DatabaseRowTransformResult;\n\nexport interface EncryptionOpts {\n    // base64 encoded encryption key (must be either 16 or 32 bytes depending on the cipher)\n    key: string,\n    // encryption cipher algorithm\n    // - aes256gcm, aes128gcm, chacha20poly1305: 28 reserved bytes\n    // - aegis128l, aegis128x2, aegis128x4: 32 reserved bytes\n    // - aegis256, aegis256x2, aegis256x4: 48 reserved bytes\n    cipher: 'aes256gcm' | 'aes128gcm' | 'chacha20poly1305' | 'aegis128l' | 'aegis128x2' | 'aegis128x4' | 'aegis256' | 'aegis256x2' | 'aegis256x4'\n}\nexport interface DatabaseOpts {\n    /**\n     * local path where to store all synced database files (e.g. local.db)\n     * note, that synced database will write several files with that prefix\n     * (e.g. local.db-info, local.db-wal, etc)\n     * */\n    path: string;\n    /**\n     * optional url of the remote database (e.g. libsql://db-org.turso.io)\n     * (if omitted - local-only database will be created)\n     * \n     * you can also provide function which will return URL or null\n     * in this case local database will be created and sync will be \"switched-on\" whenever the url will return non-empty value\n     * note, that all other parameters (like encryption) must be set in advance in order for the \"deferred\" sync to work properly\n     */\n    url?: string | (() => string | null);\n    /**\n     * auth token for the remote database\n     * (can be either static string or function which will provide short-lived credentials for every new request)\n     */\n    authToken?: string | (() => Promise<string>);\n    /**\n     * arbitrary client name which can be used to distinguish clients internally\n     * the library will gurantee uniquiness of the clientId by appending unique suffix to the clientName\n     */\n    clientName?: string;\n    /**\n     * optional remote encryption parameters if cloud database were encrypted by default\n     */\n    remoteEncryption?: EncryptionOpts;\n    /**\n     * optional callback which will be called for every mutation before sending it to the remote\n     * this callback can transform the update in order to support complex conflict resolution strategy\n     */\n    transform?: Transform,\n    /**\n     * optional long-polling timeout for pull operation\n     * if not set - no timeout is applied\n     */\n    longPollTimeoutMs?: number,\n    /**\n     * optional parameter to enable internal logging for the database\n     */\n    tracing?: 'error' | 'warn' | 'info' | 'debug' | 'trace',\n    /**\n     * When enabled, write statements execute on remote server instead of locally.\n     * After each write (or transaction commit), changes are pulled for read-your-writes consistency.\n     * Requires `url`. All explicit transactions go to remote.\n     * WARNING: This feature is EXPERIMENTAL\n     */\n    remoteWritesExperimental?: boolean;\n    /**\n     * optional parameter to enable partial sync for the database\n     * WARNING: This feature is EXPERIMENTAL\n     */\n    partialSyncExperimental?: {\n        /* bootstrap strategy configuration\n            - prefix strategy loads first N bytes locally at the startup\n            - query strategy loads pages touched by the provided SQL statement\n        */\n        bootstrapStrategy: { kind: 'prefix', length: number } | { kind: 'query', query: string },\n        /* optional segment size which makes sync engine to load pages in batches of segment_size bytes\n            (so, if loading page 1 with segment_size=128kb then 32 pages [1..32] will be loaded)\n        */\n        segmentSize?: number,\n        /* optional parameter which makes sync engine to prefetch pages which probably will be accessed soon */\n        prefetch?: boolean,\n    }\n}\nexport interface DatabaseStats {\n    /**\n     * amount of local changes not sent to the remote\n     */\n    cdcOperations: number;\n    /**\n     * size of the main WAL file in bytes\n     */\n    mainWalSize: number;\n    /**\n     * size of the revert WAL file in bytes\n     */\n    revertWalSize: number;\n    /**\n     * unix timestamp of last successful pull time\n     */\n    lastPullUnixTime: number;\n    /**\n     * unix timestamp of last successful push time\n     */\n    lastPushUnixTime: number | null;\n    /**\n     * opaque revision of the changes pulled locally from remote\n     * (can be used as e-tag, but string must not be interpreted in any way and must be used as opaque value)\n     */\n    revision: string | null;\n    /**\n     * total amount of sent bytes over the network\n     */\n    networkSentBytes: number;\n    /**\n     * total amount of received bytes over the network\n     */\n    networkReceivedBytes: number;\n}\n\n/* internal types used in the native/browser packages */\n\nexport interface RunOpts {\n    preemptionMs: number,\n    url: string | (() => string | null),\n    headers: { [K: string]: string } | (() => Promise<{ [K: string]: string }>)\n    transform?: Transform,\n}\n\nexport interface ProtocolIo {\n    read(path: string): Promise<Buffer | Uint8Array | null>;\n    write(path: string, content: Buffer | Uint8Array): Promise<void>;\n}\nexport type GeneratorResponse = { type: 'IO' } | { type: 'Done' } | ({ type: 'SyncEngineStats' } & DatabaseStats) | { type: 'SyncEngineChanges', changes: any }\n"
  },
  {
    "path": "bindings/javascript/sync/packages/native/README.md",
    "content": "<p align=\"center\">\n  <h1 align=\"center\">Turso Database for JavaScript in Node</h1>\n</p>\n\n<p align=\"center\">\n  <a title=\"JavaScript\" target=\"_blank\" href=\"https://www.npmjs.com/package/@tursodatabase/database\"><img alt=\"npm\" src=\"https://img.shields.io/npm/v/@tursodatabase/database\"></a>\n  <a title=\"MIT\" target=\"_blank\" href=\"https://github.com/tursodatabase/turso/blob/main/LICENSE.md\"><img src=\"http://img.shields.io/badge/license-MIT-orange.svg?style=flat-square\"></a>\n</p>\n<p align=\"center\">\n  <a title=\"Users Discord\" target=\"_blank\" href=\"https://tur.so/discord\"><img alt=\"Chat with other users of Turso on Discord\" src=\"https://img.shields.io/discord/933071162680958986?label=Discord&logo=Discord&style=social\"></a>\n</p>\n\n---\n\n## About\n\nThis package is the Turso embedded database library for JavaScript in Node.\n\n> **⚠️ Warning:** This software is in BETA. It may still contain bugs and unexpected behavior. Use caution with production data and ensure you have backups.\n\n## Features\n\n- **SQLite compatible:** SQLite query language and file format support ([status](https://github.com/tursodatabase/turso/blob/main/COMPAT.md)).\n- **In-process**: No network overhead, runs directly in your Node.js process\n- **TypeScript support**: Full TypeScript definitions included\n- **Cross-platform**: Supports Linux (x86 and arm64), macOS, Windows (browser is supported in the separate package `@tursodatabase/database-wasm` package)\n\n## Installation\n\n```bash\nnpm install @tursodatabase/database\n```\n\n## Getting Started\n\n### In-Memory Database\n\n```javascript\nimport { connect } from '@tursodatabase/database';\n\n// Create an in-memory database\nconst db = await connect(':memory:');\n\n// Create a table\nawait db.exec('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)');\n\n// Insert data\nconst insert = db.prepare('INSERT INTO users (name, email) VALUES (?, ?)');\nawait insert.run('Alice', 'alice@example.com');\nawait insert.run('Bob', 'bob@example.com');\n\n// Query data\nconst users = await db.prepare('SELECT * FROM users').all();\nconsole.log(users);\n// Output: [\n//   { id: 1, name: 'Alice', email: 'alice@example.com' },\n//   { id: 2, name: 'Bob', email: 'bob@example.com' }\n// ]\n```\n\n### File-Based Database\n\n```javascript\nimport { connect } from '@tursodatabase/database';\n\n// Create or open a database file\nconst db = await connect('my-database.db');\n\n// Create a table\nawait db.exec(`\n  CREATE TABLE IF NOT EXISTS posts (\n    id INTEGER PRIMARY KEY AUTOINCREMENT,\n    title TEXT NOT NULL,\n    content TEXT,\n    created_at DATETIME DEFAULT CURRENT_TIMESTAMP\n  )\n`);\n\n// Insert a post\nconst insertPost = db.prepare('INSERT INTO posts (title, content) VALUES (?, ?)');\nconst result = await insertPost.run('Hello World', 'This is my first blog post!');\n\nconsole.log(`Inserted post with ID: ${result.lastInsertRowid}`);\n```\n\n### Transactions\n\n```javascript\nimport { connect } from '@tursodatabase/database';\n\nconst db = await connect('transactions.db');\n\n// Using transactions for atomic operations\nconst transaction = db.transaction(async (users) => {\n  const insert = db.prepare('INSERT INTO users (name, email) VALUES (?, ?)');\n  for (const user of users) {\n    await insert.run(user.name, user.email);\n  }\n});\n\n// Execute transaction\nawait transaction([\n  { name: 'Alice', email: 'alice@example.com' },\n  { name: 'Bob', email: 'bob@example.com' }\n]);\n```\n\n## API Reference\n\nFor complete API documentation, see [JavaScript API Reference](https://github.com/tursodatabase/turso/blob/main/docs/javascript-api-reference.md).\n\n## Related Packages\n\n* The [@tursodatabase/serverless](https://www.npmjs.com/package/@tursodatabase/serverless) package provides a serverless driver with the same API.\n* The [@tursodatabase/sync](https://www.npmjs.com/package/@tursodatabase/sync) package provides bidirectional sync between a local Turso database and Turso Cloud. \n\n## License\n\nThis project is licensed under the [MIT license](https://github.com/tursodatabase/turso/blob/main/LICENSE.md).\n\n## Support\n\n- [GitHub Issues](https://github.com/tursodatabase/turso/issues)\n- [Documentation](https://docs.turso.tech)\n- [Discord Community](https://tur.so/discord)\n"
  },
  {
    "path": "bindings/javascript/sync/packages/native/index.d.ts",
    "content": "/* auto-generated by NAPI-RS */\n/* eslint-disable */\nexport declare class BatchExecutor {\n  stepSync(): number\n  reset(): void\n}\n\n/** A database connection. */\nexport declare class Database {\n  /**\n   * Creates a new database instance.\n   *\n   * # Arguments\n   * * `path` - The path to the database file.\n   */\n  constructor(path: string, opts?: DatabaseOpts | undefined | null)\n  /**\n   * Connect the database synchronously\n   * This method is idempotent and can be called multiple times safely until the database will be closed\n   */\n  connectSync(): void\n  /**\n   * Connect the database asynchronously\n   * This method is idempotent and can be called multiple times safely until the database will be closed\n   */\n  connectAsync(): Promise<void>\n  /** Returns whether the database is in readonly-only mode. */\n  get readonly(): boolean\n  /** Returns whether the database is in memory-only mode. */\n  get memory(): boolean\n  /** Returns whether the database is in memory-only mode. */\n  get path(): string\n  /** Returns whether the database connection is open. */\n  get open(): boolean\n  /**\n   * Prepares a statement for execution.\n   *\n   * # Arguments\n   *\n   * * `sql` - The SQL statement to prepare.\n   *\n   * # Returns\n   *\n   * A `Statement` instance.\n   */\n  prepare(sql: string): Statement\n  executor(sql: string): BatchExecutor\n  /**\n   * Returns the rowid of the last row inserted.\n   *\n   * # Returns\n   *\n   * The rowid of the last row inserted.\n   */\n  lastInsertRowid(): number\n  /**\n   * Returns the number of changes made by the last statement.\n   *\n   * # Returns\n   *\n   * The number of changes made by the last statement.\n   */\n  changes(): number\n  /**\n   * Returns the total number of changes made by all statements.\n   *\n   * # Returns\n   *\n   * The total number of changes made by all statements.\n   */\n  totalChanges(): number\n  /**\n   * Closes the database connection.\n   *\n   * # Returns\n   *\n   * `Ok(())` if the database is closed successfully.\n   */\n  close(): void\n  /**\n   * Sets the default safe integers mode for all statements from this database.\n   *\n   * # Arguments\n   *\n   * * `toggle` - Whether to use safe integers by default.\n   */\n  defaultSafeIntegers(toggle?: boolean | undefined | null): void\n  /** Runs the I/O loop synchronously. */\n  ioLoopSync(): void\n  /** Runs the I/O loop asynchronously, returning a Promise. */\n  ioLoopAsync(): Promise<void>\n  /** Classify SQL statement. Returns \"read\", \"write\", \"begin\", \"commit\", or \"rollback\". */\n  classifySql(sql: string): string\n}\n\n/** A prepared statement. */\nexport declare class Statement {\n  reset(): void\n  /** Returns the number of parameters in the statement. */\n  parameterCount(): number\n  /**\n   * Returns the name of a parameter at a specific 1-based index.\n   *\n   * # Arguments\n   *\n   * * `index` - The 1-based parameter index.\n   */\n  parameterName(index: number): string | null\n  /**\n   * Binds a parameter at a specific 1-based index with explicit type.\n   *\n   * # Arguments\n   *\n   * * `index` - The 1-based parameter index.\n   * * `value_type` - The type constant (0=null, 1=int, 2=float, 3=text, 4=blob).\n   * * `value` - The value to bind.\n   */\n  bindAt(index: number, value: unknown): void\n  /**\n   * Step the statement and return result code (executed on the main thread):\n   * 1 = Row available, 2 = Done, 3 = I/O needed\n   */\n  stepSync(): number\n  /** Get the current row data according to the presentation mode */\n  row(): unknown\n  /** Sets the presentation mode to raw. */\n  raw(raw?: boolean | undefined | null): void\n  /** Sets the presentation mode to pluck. */\n  pluck(pluck?: boolean | undefined | null): void\n  /**\n   * Sets safe integers mode for this statement.\n   *\n   * # Arguments\n   *\n   * * `toggle` - Whether to use safe integers.\n   */\n  safeIntegers(toggle?: boolean | undefined | null): void\n  /** Get column information for the statement */\n  columns(): Promise<any>\n  /** Finalizes the statement. */\n  finalize(): void\n}\n\n/**\n * Most of the options are aligned with better-sqlite API\n * (see https://github.com/WiseLibs/better-sqlite3/blob/master/docs/api.md#new-databasepath-options)\n */\nexport interface DatabaseOpts {\n  readonly?: boolean\n  timeout?: number\n  fileMustExist?: boolean\n  tracing?: string\n  /** Experimental features to enable */\n  experimental?: Array<string>\n  /** Optional encryption configuration for local database encryption */\n  encryption?: EncryptionOpts\n}\n\n/** Supported encryption ciphers for local database encryption. */\nexport declare const enum EncryptionCipher {\n  Aes128Gcm = 0,\n  Aes256Gcm = 1,\n  Aegis256 = 2,\n  Aegis256x2 = 3,\n  Aegis128l = 4,\n  Aegis128x2 = 5,\n  Aegis128x4 = 6\n}\n\n/** Encryption configuration for local database encryption. */\nexport interface EncryptionOpts {\n  /** The cipher to use for encryption */\n  cipher: EncryptionCipher\n  /** The hex-encoded encryption key */\n  hexkey: string\n}\nexport declare class GeneratorHolder {\n  resumeSync(error?: string | undefined | null): GeneratorResponse\n  resumeAsync(error?: string | undefined | null): Promise<unknown>\n}\n\nexport declare class JsDataCompletion {\n  poison(err: string): void\n  status(value: number): void\n  pushBuffer(value: Buffer): void\n  pushTransform(values: Array<DatabaseRowTransformResultJs>): void\n  done(): void\n}\n\nexport declare class JsProtocolIo {\n\n}\n\nexport declare class JsProtocolRequestBytes {\n  request(): JsProtocolRequest\n  completion(): JsDataCompletion\n}\n\nexport declare class SyncEngine {\n  constructor(opts: SyncEngineOpts)\n  connect(): GeneratorHolder\n  ioLoopSync(): void\n  /** Runs the I/O loop asynchronously, returning a Promise. */\n  ioLoopAsync(): Promise<void>\n  protocolIo(): JsProtocolRequestBytes | null\n  protocolIoStep(): void\n  push(): GeneratorHolder\n  stats(): GeneratorHolder\n  wait(): GeneratorHolder\n  apply(changes: SyncEngineChanges): GeneratorHolder\n  checkpoint(): GeneratorHolder\n  db(): Database\n  close(): void\n}\n\nexport declare class SyncEngineChanges {\n  empty(): boolean\n}\n\nexport declare const enum DatabaseChangeTypeJs {\n  Insert = 'insert',\n  Update = 'update',\n  Delete = 'delete'\n}\n\nexport interface DatabaseRowMutationJs {\n  changeTime: number\n  tableName: string\n  id: number\n  changeType: DatabaseChangeTypeJs\n  before?: Record<string, any>\n  after?: Record<string, any>\n  updates?: Record<string, any>\n}\n\nexport interface DatabaseRowStatementJs {\n  sql: string\n  values: Array<any>\n}\n\nexport type DatabaseRowTransformResultJs =\n  | { type: 'Keep' }\n  | { type: 'Skip' }\n  | { type: 'Rewrite', stmt: DatabaseRowStatementJs }\n\nexport type GeneratorResponse =\n  | { type: 'IO' }\n  | { type: 'Done' }\n  | { type: 'SyncEngineStats', cdcOperations: number, mainWalSize: number, revertWalSize: number, lastPullUnixTime?: number, lastPushUnixTime?: number, revision?: string, networkSentBytes: number, networkReceivedBytes: number }\n  | { type: 'SyncEngineChanges', changes: SyncEngineChanges }\n\nexport type JsPartialBootstrapStrategy =\n  | { type: 'Prefix', length: number }\n  | { type: 'Query', query: string }\n\nexport interface JsPartialSyncOpts {\n  bootstrapStrategy: JsPartialBootstrapStrategy\n  segmentSize?: number\n  prefetch?: boolean\n}\n\nexport type JsProtocolRequest =\n  | { type: 'Http', url?: string, method: string, path: string, body?: Array<number>, headers: Array<[string, string]> }\n  | { type: 'FullRead', path: string }\n  | { type: 'FullWrite', path: string, content: Array<number> }\n  | { type: 'Transform', mutations: Array<DatabaseRowMutationJs> }\n\nexport interface SyncEngineOpts {\n  path: string\n  remoteUrl?: string\n  clientName?: string\n  walPullBatchSize?: number\n  longPollTimeoutMs?: number\n  tracing?: string\n  tablesIgnore?: Array<string>\n  useTransform: boolean\n  protocolVersion?: SyncEngineProtocolVersion\n  bootstrapIfEmpty: boolean\n  /** Encryption cipher for the Turso Cloud database. */\n  remoteEncryptionCipher?: string\n  /**\n   * Base64-encoded encryption key for the Turso Cloud database.\n   * Must match the key used when creating the encrypted database.\n   */\n  remoteEncryptionKey?: string\n  partialSyncOpts?: JsPartialSyncOpts\n}\n\nexport declare const enum SyncEngineProtocolVersion {\n  Legacy = 'legacy',\n  V1 = 'v1'\n}\n"
  },
  {
    "path": "bindings/javascript/sync/packages/native/index.js",
    "content": "// prettier-ignore\n/* eslint-disable */\n// @ts-nocheck\n/* auto-generated by NAPI-RS */\n\nimport { createRequire } from 'node:module'\nconst require = createRequire(import.meta.url)\nconst __dirname = new URL('.', import.meta.url).pathname\n\nconst { readFileSync } = require('node:fs')\nlet nativeBinding = null\nconst loadErrors = []\n\nconst isMusl = () => {\n  let musl = false\n  if (process.platform === 'linux') {\n    musl = isMuslFromFilesystem()\n    if (musl === null) {\n      musl = isMuslFromReport()\n    }\n    if (musl === null) {\n      musl = isMuslFromChildProcess()\n    }\n  }\n  return musl\n}\n\nconst isFileMusl = (f) => f.includes('libc.musl-') || f.includes('ld-musl-')\n\nconst isMuslFromFilesystem = () => {\n  try {\n    return readFileSync('/usr/bin/ldd', 'utf-8').includes('musl')\n  } catch {\n    return null\n  }\n}\n\nconst isMuslFromReport = () => {\n  let report = null\n  if (typeof process.report?.getReport === 'function') {\n    process.report.excludeNetwork = true\n    report = process.report.getReport()\n  }\n  if (!report) {\n    return null\n  }\n  if (report.header && report.header.glibcVersionRuntime) {\n    return false\n  }\n  if (Array.isArray(report.sharedObjects)) {\n    if (report.sharedObjects.some(isFileMusl)) {\n      return true\n    }\n  }\n  return false\n}\n\nconst isMuslFromChildProcess = () => {\n  try {\n    return require('child_process').execSync('ldd --version', { encoding: 'utf8' }).includes('musl')\n  } catch (e) {\n    // If we reach this case, we don't know if the system is musl or not, so is better to just fallback to false\n    return false\n  }\n}\n\nfunction requireNative() {\n  if (process.env.NAPI_RS_NATIVE_LIBRARY_PATH) {\n    try {\n      nativeBinding = require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH);\n    } catch (err) {\n      loadErrors.push(err)\n    }\n  } else if (process.platform === 'android') {\n    if (process.arch === 'arm64') {\n      try {\n        return require('./sync.android-arm64.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tursodatabase/sync-android-arm64')\n        const bindingPackageVersion = require('@tursodatabase/sync-android-arm64/package.json').version\n        if (bindingPackageVersion !== '0.5.0-pre.12' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.12 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else if (process.arch === 'arm') {\n      try {\n        return require('./sync.android-arm-eabi.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tursodatabase/sync-android-arm-eabi')\n        const bindingPackageVersion = require('@tursodatabase/sync-android-arm-eabi/package.json').version\n        if (bindingPackageVersion !== '0.5.0-pre.12' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.12 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else {\n      loadErrors.push(new Error(`Unsupported architecture on Android ${process.arch}`))\n    }\n  } else if (process.platform === 'win32') {\n    if (process.arch === 'x64') {\n      try {\n        return require('./sync.win32-x64-msvc.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tursodatabase/sync-win32-x64-msvc')\n        const bindingPackageVersion = require('@tursodatabase/sync-win32-x64-msvc/package.json').version\n        if (bindingPackageVersion !== '0.5.0-pre.12' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.12 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else if (process.arch === 'ia32') {\n      try {\n        return require('./sync.win32-ia32-msvc.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tursodatabase/sync-win32-ia32-msvc')\n        const bindingPackageVersion = require('@tursodatabase/sync-win32-ia32-msvc/package.json').version\n        if (bindingPackageVersion !== '0.5.0-pre.12' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.12 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else if (process.arch === 'arm64') {\n      try {\n        return require('./sync.win32-arm64-msvc.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tursodatabase/sync-win32-arm64-msvc')\n        const bindingPackageVersion = require('@tursodatabase/sync-win32-arm64-msvc/package.json').version\n        if (bindingPackageVersion !== '0.5.0-pre.12' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.12 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else {\n      loadErrors.push(new Error(`Unsupported architecture on Windows: ${process.arch}`))\n    }\n  } else if (process.platform === 'darwin') {\n    try {\n      return require('./sync.darwin-universal.node')\n    } catch (e) {\n      loadErrors.push(e)\n    }\n    try {\n      const binding = require('@tursodatabase/sync-darwin-universal')\n      const bindingPackageVersion = require('@tursodatabase/sync-darwin-universal/package.json').version\n      if (bindingPackageVersion !== '0.5.0-pre.12' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n        throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.12 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n      }\n      return binding\n    } catch (e) {\n      loadErrors.push(e)\n    }\n    if (process.arch === 'x64') {\n      try {\n        return require('./sync.darwin-x64.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tursodatabase/sync-darwin-x64')\n        const bindingPackageVersion = require('@tursodatabase/sync-darwin-x64/package.json').version\n        if (bindingPackageVersion !== '0.5.0-pre.12' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.12 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else if (process.arch === 'arm64') {\n      try {\n        return require('./sync.darwin-arm64.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tursodatabase/sync-darwin-arm64')\n        const bindingPackageVersion = require('@tursodatabase/sync-darwin-arm64/package.json').version\n        if (bindingPackageVersion !== '0.5.0-pre.12' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.12 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else {\n      loadErrors.push(new Error(`Unsupported architecture on macOS: ${process.arch}`))\n    }\n  } else if (process.platform === 'freebsd') {\n    if (process.arch === 'x64') {\n      try {\n        return require('./sync.freebsd-x64.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tursodatabase/sync-freebsd-x64')\n        const bindingPackageVersion = require('@tursodatabase/sync-freebsd-x64/package.json').version\n        if (bindingPackageVersion !== '0.5.0-pre.12' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.12 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else if (process.arch === 'arm64') {\n      try {\n        return require('./sync.freebsd-arm64.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tursodatabase/sync-freebsd-arm64')\n        const bindingPackageVersion = require('@tursodatabase/sync-freebsd-arm64/package.json').version\n        if (bindingPackageVersion !== '0.5.0-pre.12' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.12 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else {\n      loadErrors.push(new Error(`Unsupported architecture on FreeBSD: ${process.arch}`))\n    }\n  } else if (process.platform === 'linux') {\n    if (process.arch === 'x64') {\n      if (isMusl()) {\n        try {\n          return require('./sync.linux-x64-musl.node')\n        } catch (e) {\n          loadErrors.push(e)\n        }\n        try {\n          const binding = require('@tursodatabase/sync-linux-x64-musl')\n          const bindingPackageVersion = require('@tursodatabase/sync-linux-x64-musl/package.json').version\n          if (bindingPackageVersion !== '0.5.0-pre.12' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n            throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.12 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n          }\n          return binding\n        } catch (e) {\n          loadErrors.push(e)\n        }\n      } else {\n        try {\n          return require('./sync.linux-x64-gnu.node')\n        } catch (e) {\n          loadErrors.push(e)\n        }\n        try {\n          const binding = require('@tursodatabase/sync-linux-x64-gnu')\n          const bindingPackageVersion = require('@tursodatabase/sync-linux-x64-gnu/package.json').version\n          if (bindingPackageVersion !== '0.5.0-pre.12' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n            throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.12 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n          }\n          return binding\n        } catch (e) {\n          loadErrors.push(e)\n        }\n      }\n    } else if (process.arch === 'arm64') {\n      if (isMusl()) {\n        try {\n          return require('./sync.linux-arm64-musl.node')\n        } catch (e) {\n          loadErrors.push(e)\n        }\n        try {\n          const binding = require('@tursodatabase/sync-linux-arm64-musl')\n          const bindingPackageVersion = require('@tursodatabase/sync-linux-arm64-musl/package.json').version\n          if (bindingPackageVersion !== '0.5.0-pre.12' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n            throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.12 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n          }\n          return binding\n        } catch (e) {\n          loadErrors.push(e)\n        }\n      } else {\n        try {\n          return require('./sync.linux-arm64-gnu.node')\n        } catch (e) {\n          loadErrors.push(e)\n        }\n        try {\n          const binding = require('@tursodatabase/sync-linux-arm64-gnu')\n          const bindingPackageVersion = require('@tursodatabase/sync-linux-arm64-gnu/package.json').version\n          if (bindingPackageVersion !== '0.5.0-pre.12' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n            throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.12 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n          }\n          return binding\n        } catch (e) {\n          loadErrors.push(e)\n        }\n      }\n    } else if (process.arch === 'arm') {\n      if (isMusl()) {\n        try {\n          return require('./sync.linux-arm-musleabihf.node')\n        } catch (e) {\n          loadErrors.push(e)\n        }\n        try {\n          const binding = require('@tursodatabase/sync-linux-arm-musleabihf')\n          const bindingPackageVersion = require('@tursodatabase/sync-linux-arm-musleabihf/package.json').version\n          if (bindingPackageVersion !== '0.5.0-pre.12' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n            throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.12 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n          }\n          return binding\n        } catch (e) {\n          loadErrors.push(e)\n        }\n      } else {\n        try {\n          return require('./sync.linux-arm-gnueabihf.node')\n        } catch (e) {\n          loadErrors.push(e)\n        }\n        try {\n          const binding = require('@tursodatabase/sync-linux-arm-gnueabihf')\n          const bindingPackageVersion = require('@tursodatabase/sync-linux-arm-gnueabihf/package.json').version\n          if (bindingPackageVersion !== '0.5.0-pre.12' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n            throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.12 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n          }\n          return binding\n        } catch (e) {\n          loadErrors.push(e)\n        }\n      }\n    } else if (process.arch === 'riscv64') {\n      if (isMusl()) {\n        try {\n          return require('./sync.linux-riscv64-musl.node')\n        } catch (e) {\n          loadErrors.push(e)\n        }\n        try {\n          const binding = require('@tursodatabase/sync-linux-riscv64-musl')\n          const bindingPackageVersion = require('@tursodatabase/sync-linux-riscv64-musl/package.json').version\n          if (bindingPackageVersion !== '0.5.0-pre.12' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n            throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.12 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n          }\n          return binding\n        } catch (e) {\n          loadErrors.push(e)\n        }\n      } else {\n        try {\n          return require('./sync.linux-riscv64-gnu.node')\n        } catch (e) {\n          loadErrors.push(e)\n        }\n        try {\n          const binding = require('@tursodatabase/sync-linux-riscv64-gnu')\n          const bindingPackageVersion = require('@tursodatabase/sync-linux-riscv64-gnu/package.json').version\n          if (bindingPackageVersion !== '0.5.0-pre.12' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n            throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.12 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n          }\n          return binding\n        } catch (e) {\n          loadErrors.push(e)\n        }\n      }\n    } else if (process.arch === 'ppc64') {\n      try {\n        return require('./sync.linux-ppc64-gnu.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tursodatabase/sync-linux-ppc64-gnu')\n        const bindingPackageVersion = require('@tursodatabase/sync-linux-ppc64-gnu/package.json').version\n        if (bindingPackageVersion !== '0.5.0-pre.12' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.12 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else if (process.arch === 's390x') {\n      try {\n        return require('./sync.linux-s390x-gnu.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tursodatabase/sync-linux-s390x-gnu')\n        const bindingPackageVersion = require('@tursodatabase/sync-linux-s390x-gnu/package.json').version\n        if (bindingPackageVersion !== '0.5.0-pre.12' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.12 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else {\n      loadErrors.push(new Error(`Unsupported architecture on Linux: ${process.arch}`))\n    }\n  } else if (process.platform === 'openharmony') {\n    if (process.arch === 'arm64') {\n      try {\n        return require('./sync.openharmony-arm64.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tursodatabase/sync-openharmony-arm64')\n        const bindingPackageVersion = require('@tursodatabase/sync-openharmony-arm64/package.json').version\n        if (bindingPackageVersion !== '0.5.0-pre.12' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.12 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else if (process.arch === 'x64') {\n      try {\n        return require('./sync.openharmony-x64.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tursodatabase/sync-openharmony-x64')\n        const bindingPackageVersion = require('@tursodatabase/sync-openharmony-x64/package.json').version\n        if (bindingPackageVersion !== '0.5.0-pre.12' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.12 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else if (process.arch === 'arm') {\n      try {\n        return require('./sync.openharmony-arm.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tursodatabase/sync-openharmony-arm')\n        const bindingPackageVersion = require('@tursodatabase/sync-openharmony-arm/package.json').version\n        if (bindingPackageVersion !== '0.5.0-pre.12' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 0.5.0-pre.12 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else {\n      loadErrors.push(new Error(`Unsupported architecture on OpenHarmony: ${process.arch}`))\n    }\n  } else {\n    loadErrors.push(new Error(`Unsupported OS: ${process.platform}, architecture: ${process.arch}`))\n  }\n}\n\nnativeBinding = requireNative()\n\nif (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) {\n  try {\n    nativeBinding = require('./sync.wasi.cjs')\n  } catch (err) {\n    if (process.env.NAPI_RS_FORCE_WASI) {\n      loadErrors.push(err)\n    }\n  }\n  if (!nativeBinding) {\n    try {\n      nativeBinding = require('@tursodatabase/sync-wasm32-wasi')\n    } catch (err) {\n      if (process.env.NAPI_RS_FORCE_WASI) {\n        loadErrors.push(err)\n      }\n    }\n  }\n}\n\nif (!nativeBinding) {\n  if (loadErrors.length > 0) {\n    throw new Error(\n      `Cannot find native binding. ` +\n        `npm has a bug related to optional dependencies (https://github.com/npm/cli/issues/4828). ` +\n        'Please try `npm i` again after removing both package-lock.json and node_modules directory.',\n      { cause: loadErrors }\n    )\n  }\n  throw new Error(`Failed to load native binding`)\n}\n\nconst { BatchExecutor, Database, Statement, EncryptionCipher, GeneratorHolder, JsDataCompletion, JsProtocolIo, JsProtocolRequestBytes, SyncEngine, SyncEngineChanges, DatabaseChangeTypeJs, SyncEngineProtocolVersion } = nativeBinding\nexport { BatchExecutor }\nexport { Database }\nexport { Statement }\nexport { EncryptionCipher }\nexport { GeneratorHolder }\nexport { JsDataCompletion }\nexport { JsProtocolIo }\nexport { JsProtocolRequestBytes }\nexport { SyncEngine }\nexport { SyncEngineChanges }\nexport { DatabaseChangeTypeJs }\nexport { SyncEngineProtocolVersion }\n"
  },
  {
    "path": "bindings/javascript/sync/packages/native/package.json",
    "content": "{\n  \"name\": \"@tursodatabase/sync\",\n  \"version\": \"0.6.0-pre.4\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/tursodatabase/turso\"\n  },\n  \"license\": \"MIT\",\n  \"module\": \"./dist/promise.js\",\n  \"main\": \"./dist/promise.js\",\n  \"type\": \"module\",\n  \"exports\": {\n    \".\": \"./dist/promise.js\",\n    \"./compat\": \"./dist/compat.js\"\n  },\n  \"files\": [\n    \"index.js\",\n    \"dist/**\",\n    \"README.md\"\n  ],\n  \"packageManager\": \"yarn@4.9.2\",\n  \"devDependencies\": {\n    \"@napi-rs/cli\": \"^3.1.5\",\n    \"@types/node\": \"^24.3.1\",\n    \"typescript\": \"^5.9.2\",\n    \"vitest\": \"^3.2.4\"\n  },\n  \"scripts\": {\n    \"napi-build\": \"napi build --platform --profile release-official --esm --manifest-path ../../Cargo.toml --output-dir .\",\n    \"napi-dirs\": \"napi create-npm-dirs\",\n    \"napi-artifacts\": \"napi artifacts --output-dir .\",\n    \"tsc-build\": \"npm exec tsc\",\n    \"build\": \"npm run napi-build && npm run tsc-build\",\n    \"test\": \"VITE_TURSO_DB_URL=http://jj--a--a.localhost:8080 vitest --run --exclude remote-write.test.ts\",\n    \"test:remote-write\": \"vitest --run remote-write.test.ts\",\n    \"prepublishOnly\": \"npm run napi-dirs && npm run napi-artifacts && napi prepublish -t npm\"\n  },\n  \"napi\": {\n    \"binaryName\": \"sync\",\n    \"targets\": [\n      \"x86_64-unknown-linux-gnu\",\n      \"x86_64-pc-windows-msvc\",\n      \"aarch64-apple-darwin\",\n      \"aarch64-unknown-linux-gnu\"\n    ]\n  },\n  \"dependencies\": {\n    \"@tursodatabase/database-common\": \"^0.6.0-pre.4\",\n    \"@tursodatabase/sync-common\": \"^0.6.0-pre.4\"\n  },\n  \"imports\": {\n    \"#index\": \"./index.js\"\n  }\n}\n"
  },
  {
    "path": "bindings/javascript/sync/packages/native/promise.test.ts",
    "content": "import { unlinkSync } from \"node:fs\";\nimport { expect, test } from 'vitest'\nimport { connect, Database, DatabaseRowMutation, DatabaseRowTransformResult } from './promise.js'\n\nconst localeCompare = (a, b) => a.x.localeCompare(b.x);\nconst intCompare = (a, b) => a.x - b.x;\n\nfunction cleanup(path) {\n    unlinkSync(path);\n    unlinkSync(`${path}-wal`);\n    unlinkSync(`${path}-info`);\n    unlinkSync(`${path}-changes`);\n    try { unlinkSync(`${path}-wal-revert`) } catch (e) { }\n}\n\ntest('partial sync concurrency', async () => {\n    {\n        const db = await connect({\n            path: ':memory:',\n            url: process.env.VITE_TURSO_DB_URL,\n            longPollTimeoutMs: 100,\n        });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS partial(value BLOB)\");\n        await db.exec(\"DELETE FROM partial\");\n        await db.exec(\"INSERT INTO partial SELECT randomblob(1024) FROM generate_series(1, 2000)\");\n        await db.push();\n        await db.close();\n    }\n\n    const dbs = [];\n    for (let i = 0; i < 16; i++) {\n        dbs.push(await connect({\n            path: 'partial-1.db',\n            url: process.env.VITE_TURSO_DB_URL,\n            longPollTimeoutMs: 100,\n            partialSyncExperimental: {\n                bootstrapStrategy: { kind: 'prefix', length: 128 * 1024 },\n                segmentSize: 128 * 1024,\n            },\n        }));\n    }\n    const qs = [];\n    for (const db of dbs) {\n        qs.push(db.prepare(\"SELECT COUNT(*) as cnt FROM partial\").all());\n    }\n    const values = await Promise.all(qs);\n    expect(values).toEqual(new Array(16).fill([{ cnt: 2000 }]))\n})\n\ntest('partial sync (prefix bootstrap strategy)', async () => {\n    {\n        const db = await connect({\n            path: ':memory:',\n            url: process.env.VITE_TURSO_DB_URL,\n            longPollTimeoutMs: 100,\n        });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS partial(value BLOB)\");\n        await db.exec(\"DELETE FROM partial\");\n        await db.exec(\"INSERT INTO partial SELECT randomblob(1024) FROM generate_series(1, 2000)\");\n        await db.push();\n        await db.close();\n    }\n\n    const db = await connect({\n        path: ':memory:',\n        url: process.env.VITE_TURSO_DB_URL,\n        longPollTimeoutMs: 100,\n        partialSyncExperimental: {\n            bootstrapStrategy: { kind: 'prefix', length: 128 * 1024 },\n            segmentSize: 4096,\n        },\n    });\n\n    // 128kb plus some overhead\n    expect((await db.stats()).networkReceivedBytes).toBeLessThanOrEqual(128 * (1024 + 128));\n\n    // select of one record shouldn't increase amount of received data\n    expect(await db.prepare(\"SELECT length(value) as length FROM partial LIMIT 1\").all()).toEqual([{ length: 1024 }]);\n    expect((await db.stats()).networkReceivedBytes).toBeLessThanOrEqual(128 * (1024 + 128));\n\n    await db.prepare(\"INSERT INTO partial VALUES (-1)\").run();\n\n    expect(await db.prepare(\"SELECT COUNT(*) as cnt FROM partial\").all()).toEqual([{ cnt: 2001 }]);\n    expect((await db.stats()).networkReceivedBytes).toBeGreaterThanOrEqual(2000 * 1024);\n})\n\ntest('partial sync (prefix bootstrap strategy; large segment size)', async () => {\n    {\n        const db = await connect({\n            path: ':memory:',\n            url: process.env.VITE_TURSO_DB_URL,\n            longPollTimeoutMs: 100,\n        });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS partial(value BLOB)\");\n        await db.exec(\"DELETE FROM partial\");\n        await db.exec(\"INSERT INTO partial SELECT randomblob(1024) FROM generate_series(1, 2000)\");\n        await db.push();\n        await db.close();\n    }\n\n    const db = await connect({\n        path: ':memory:',\n        url: process.env.VITE_TURSO_DB_URL,\n        longPollTimeoutMs: 100,\n        partialSyncExperimental: {\n            bootstrapStrategy: { kind: 'prefix', length: 128 * 1024 },\n            segmentSize: 128 * 1024,\n        },\n    });\n\n    // 128kb plus some overhead\n    expect((await db.stats()).networkReceivedBytes).toBeLessThanOrEqual(128 * (1024 + 128));\n\n    const startLast = performance.now();\n    // select of one record shouldn't increase amount of received data\n    expect(await db.prepare(\"SELECT length(value) as length FROM partial LIMIT 1\").all()).toEqual([{ length: 1024 }]);\n    console.info('select last', 'elapsed', performance.now() - startLast);\n\n    expect((await db.stats()).networkReceivedBytes).toBeLessThanOrEqual(2 * 128 * (1024 + 128));\n\n    await db.prepare(\"INSERT INTO partial VALUES (-1)\").run();\n\n    const startAll = performance.now();\n    expect(await db.prepare(\"SELECT COUNT(*) as cnt FROM partial\").all()).toEqual([{ cnt: 2001 }]);\n    console.info('select all', 'elapsed', performance.now() - startAll);\n\n    expect((await db.stats()).networkReceivedBytes).toBeGreaterThanOrEqual(2000 * 1024);\n})\n\ntest('partial sync (prefix bootstrap strategy; prefetch)', async () => {\n    {\n        const db = await connect({\n            path: ':memory:',\n            url: process.env.VITE_TURSO_DB_URL,\n            longPollTimeoutMs: 100,\n        });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS partial(value BLOB)\");\n        await db.exec(\"DELETE FROM partial\");\n        await db.exec(\"INSERT INTO partial SELECT randomblob(1024) FROM generate_series(1, 2000)\");\n        await db.push();\n        await db.close();\n    }\n\n    const db = await connect({\n        path: ':memory:',\n        url: process.env.VITE_TURSO_DB_URL,\n        longPollTimeoutMs: 100,\n        partialSyncExperimental: {\n            bootstrapStrategy: { kind: 'prefix', length: 128 * 1024 },\n            segmentSize: 4 * 1024,\n            prefetch: true,\n        },\n    });\n\n    // 128kb plus some overhead\n    expect((await db.stats()).networkReceivedBytes).toBeLessThanOrEqual(128 * (1024 + 128));\n\n    const startLast = performance.now();\n    // select of one record shouldn't increase amount of received data\n    expect(await db.prepare(\"SELECT length(value) as length FROM partial LIMIT 1\").all()).toEqual([{ length: 1024 }]);\n    console.info('select last', 'elapsed', performance.now() - startLast);\n\n    expect((await db.stats()).networkReceivedBytes).toBeLessThanOrEqual(10 * 128 * (1024 + 128));\n\n    await db.prepare(\"INSERT INTO partial VALUES (-1)\").run();\n\n    const startAll = performance.now();\n    expect(await db.prepare(\"SELECT COUNT(*) as cnt FROM partial\").all()).toEqual([{ cnt: 2001 }]);\n    console.info('select all', 'elapsed', performance.now() - startAll);\n\n    expect((await db.stats()).networkReceivedBytes).toBeGreaterThanOrEqual(2000 * 1024);\n})\n\ntest('partial sync (query bootstrap strategy)', async () => {\n    {\n        const db = await connect({\n            path: ':memory:',\n            url: process.env.VITE_TURSO_DB_URL,\n            longPollTimeoutMs: 100,\n        });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS partial_keyed(key INTEGER PRIMARY KEY, value BLOB)\");\n        await db.exec(\"DELETE FROM partial_keyed\");\n        await db.exec(\"INSERT INTO partial_keyed SELECT value, randomblob(1024) FROM generate_series(1, 2000)\");\n        await db.push();\n        await db.close();\n    }\n\n    const db = await connect({\n        path: ':memory:',\n        url: process.env.VITE_TURSO_DB_URL,\n        longPollTimeoutMs: 100,\n        partialSyncExperimental: {\n            bootstrapStrategy: { kind: 'query', query: 'SELECT * FROM partial_keyed WHERE key = 1000' },\n            segmentSize: 4096,\n        },\n    });\n\n    // we must sync only few pages\n    expect((await db.stats()).networkReceivedBytes).toBeLessThanOrEqual(10 * (4096 + 128));\n\n    // select of one record shouldn't increase amount of received data by a lot\n    expect(await db.prepare(\"SELECT length(value) as length FROM partial_keyed LIMIT 1\").all()).toEqual([{ length: 1024 }]);\n    expect((await db.stats()).networkReceivedBytes).toBeLessThanOrEqual(10 * (4096 + 128));\n\n    await db.prepare(\"INSERT INTO partial_keyed VALUES (-1, -1)\").run();\n    const n1 = await db.stats();\n\n    // same as bootstrap query - we shouldn't bring any more pages\n    expect(await db.prepare(\"SELECT length(value) as length FROM partial_keyed WHERE key = 1000\").all()).toEqual([{ length: 1024 }]);\n    const n2 = await db.stats();\n    expect(n1.networkReceivedBytes).toEqual(n2.networkReceivedBytes);\n})\n\ntest('concurrent-actions-consistency', async () => {\n    {\n        const db = await connect({\n            path: ':memory:',\n            url: process.env.VITE_TURSO_DB_URL,\n            longPollTimeoutMs: 100,\n        });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS rows(key TEXT PRIMARY KEY, value INTEGER)\");\n        await db.exec(\"DELETE FROM rows\");\n        await db.exec(\"INSERT INTO rows VALUES ('key', 0)\");\n        await db.push();\n        await db.close();\n    }\n    const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n    console.info('run_info', await db1.prepare(\"SELECT * FROM sqlite_master\").all());\n    await db1.exec(\"PRAGMA busy_timeout=100\");\n    const pull = async function (iterations: number) {\n        for (let i = 0; i < iterations; i++) {\n            console.info('pull', i);\n            try { await db1.pull(); }\n            catch (e) { console.error('pull', e); }\n            await new Promise(resolve => setTimeout(resolve, 0));\n        }\n    }\n    const push = async function (iterations: number) {\n        for (let i = 0; i < iterations; i++) {\n            await new Promise(resolve => setTimeout(resolve, (Math.random() + 1)));\n            console.info('push', i);\n            try {\n                if ((await db1.stats()).cdcOperations > 0) {\n                    const start = performance.now();\n                    await db1.push();\n                    console.info('push', performance.now() - start);\n                }\n            }\n            catch (e) { console.error('push', e); }\n        }\n    }\n    const run = async function (iterations: number) {\n        let rows = 0;\n        for (let i = 0; i < iterations; i++) {\n            // console.info('run', i, rows);\n            // console.info('run_info', 'update', 'start');\n            await db1.prepare(\"UPDATE rows SET value = value + 1 WHERE key = ?\").run('key');\n            // console.info('run_info', 'update', 'end');\n            rows += 1;\n            // console.info('run_info', 'select', 'start');\n            const { cnt } = await db1.prepare(\"SELECT value as cnt FROM rows WHERE key = ?\").get(['key']);\n            // console.info('run_info', 'select', 'end', cnt, '(', rows, ')');\n            expect(cnt).toBe(rows);\n            await new Promise(resolve => setTimeout(resolve, 10 * (Math.random() + 1)));\n        }\n    }\n    await Promise.all([pull(100), push(100), run(200)]);\n})\n\ntest('simple-db', async () => {\n    const db = new Database({ path: ':memory:' });\n    expect(await db.prepare(\"SELECT 1 as x\").all()).toEqual([{ x: 1 }])\n    await db.exec(\"CREATE TABLE t(x)\");\n    await db.exec(\"INSERT INTO t VALUES (1), (2), (3)\");\n    expect(await db.prepare(\"SELECT * FROM t\").all()).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }])\n    await expect(async () => await db.pull()).rejects.toThrowError(/sync is disabled as database was opened without sync support/);\n})\n\ntest('implicit connect', async () => {\n    const db = new Database({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n    const defer = db.prepare(\"SELECT * FROM not_found\");\n    await expect(async () => await defer.all()).rejects.toThrowError(/no such table: not_found/);\n    expect(() => db.prepare(\"SELECT * FROM not_found\")).toThrowError(/no such table: not_found/);\n    expect(await db.prepare(\"SELECT 1 as x\").all()).toEqual([{ x: 1 }]);\n})\n\ntest('defered sync', async () => {\n    {\n        const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS t(x)\");\n        await db.exec(\"DELETE FROM t\");\n        await db.exec(\"INSERT INTO t VALUES (100)\");\n        await db.push();\n        await db.close();\n    }\n\n    let url = null;\n    const db = new Database({ path: ':memory:', url: () => url });\n    await db.prepare(\"CREATE TABLE t(x)\").run();\n    await db.prepare(\"INSERT INTO t VALUES (1), (2), (3), (42)\").run();\n    expect(await db.prepare(\"SELECT * FROM t\").all()).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }, { x: 42 }]);\n    await expect(async () => await db.pull()).rejects.toThrow(/url is empty - sync is paused/);\n    url = process.env.VITE_TURSO_DB_URL;\n    await db.pull();\n    expect(await db.prepare(\"SELECT * FROM t\").all()).toEqual([{ x: 100 }, { x: 1 }, { x: 2 }, { x: 3 }, { x: 42 }]);\n})\n\ntest('encryption sync', async () => {\n    const KEY = 'l/FWopMfZisTLgBX4A42AergrCrYKjiO3BfkJUwv83I=';\n    const URL = 'http://encrypted--a--a.localhost:10000';\n    {\n        const db = await connect({ path: ':memory:', url: URL, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS t(x)\");\n        await db.exec(\"DELETE FROM t\");\n        await db.push();\n        await db.close();\n    }\n    const db1 = await connect({ path: ':memory:', url: URL, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } });\n    const db2 = await connect({ path: ':memory:', url: URL, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } });\n    await db1.exec(\"INSERT INTO t VALUES (1), (2), (3)\");\n    await db2.exec(\"INSERT INTO t VALUES (4), (5), (6)\");\n    expect(await db1.prepare(\"SELECT * FROM t\").all()).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }]);\n    expect(await db2.prepare(\"SELECT * FROM t\").all()).toEqual([{ x: 4 }, { x: 5 }, { x: 6 }]);\n    await Promise.all([db1.push(), db2.push()]);\n    await Promise.all([db1.pull(), db2.pull()]);\n    const expected = [{ x: 1 }, { x: 2 }, { x: 3 }, { x: 4 }, { x: 5 }, { x: 6 }];\n    expect((await db1.prepare(\"SELECT * FROM t\").all()).sort(intCompare)).toEqual(expected.sort(intCompare));\n    expect((await db2.prepare(\"SELECT * FROM t\").all()).sort(intCompare)).toEqual(expected.sort(intCompare));\n});\n\ntest('defered encryption sync', async () => {\n    const URL = 'http://encrypted--a--a.localhost:10000';\n    const KEY = 'l/FWopMfZisTLgBX4A42AergrCrYKjiO3BfkJUwv83I=';\n    let url = null;\n    {\n        const db = await connect({ path: ':memory:', url: URL, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS t(x)\");\n        await db.exec(\"DELETE FROM t\");\n        await db.exec(\"INSERT INTO t VALUES (100)\");\n        await db.push();\n        await db.close();\n    }\n    const db = await connect({ path: ':memory:', url: () => url, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } });\n    await db.exec(\"CREATE TABLE IF NOT EXISTS t(x)\");\n    await db.exec(\"INSERT INTO t VALUES (1), (2), (3)\");\n    expect(await db.prepare(\"SELECT * FROM t\").all()).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }]);\n\n    url = URL;\n    await db.pull();\n\n    const expected = [{ x: 100 }, { x: 1 }, { x: 2 }, { x: 3 }];\n    expect((await db.prepare(\"SELECT * FROM t\").all())).toEqual(expected);\n});\n\ntest('select-after-push', async () => {\n    {\n        const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS t(x)\");\n        await db.exec(\"DELETE FROM t\");\n        await db.push();\n        await db.close();\n    }\n    {\n        const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n        await db.exec(\"INSERT INTO t VALUES (1), (2), (3)\");\n        await db.push();\n    }\n    {\n        const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n        const rows = await db.prepare('SELECT * FROM t').all();\n        expect(rows).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }])\n    }\n})\n\ntest('select-without-push', async () => {\n    {\n        const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS t(x)\");\n        await db.exec(\"DELETE FROM t\");\n        await db.push();\n        await db.close();\n    }\n    {\n        const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n        await db.exec(\"INSERT INTO t VALUES (1), (2), (3)\");\n    }\n    {\n        const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n        const rows = await db.prepare('SELECT * FROM t').all();\n        expect(rows).toEqual([])\n    }\n})\n\ntest('merge-non-overlapping-keys', async () => {\n    {\n        const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)\");\n        await db.exec(\"DELETE FROM q\");\n        await db.push();\n        await db.close();\n    }\n    const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n    await db1.exec(\"INSERT INTO q VALUES ('k1', 'value1'), ('k2', 'value2')\");\n\n    const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n    await db2.exec(\"INSERT INTO q VALUES ('k3', 'value3'), ('k4', 'value4'), ('k5', 'value5')\");\n\n    await Promise.all([db1.push(), db2.push()]);\n    await Promise.all([db1.pull(), db2.pull()]);\n\n    const rows1 = await db1.prepare('SELECT * FROM q').all();\n    const rows2 = await db1.prepare('SELECT * FROM q').all();\n    const expected = [{ x: 'k1', y: 'value1' }, { x: 'k2', y: 'value2' }, { x: 'k3', y: 'value3' }, { x: 'k4', y: 'value4' }, { x: 'k5', y: 'value5' }];\n    expect(rows1.sort(localeCompare)).toEqual(expected.sort(localeCompare))\n    expect(rows2.sort(localeCompare)).toEqual(expected.sort(localeCompare))\n})\n\ntest('last-push-wins', async () => {\n    {\n        const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)\");\n        await db.exec(\"DELETE FROM q\");\n        await db.push();\n        await db.close();\n    }\n    const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n    await db1.exec(\"INSERT INTO q VALUES ('k1', 'value1'), ('k2', 'value2'), ('k4', 'value4')\");\n\n    const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n    await db2.exec(\"INSERT INTO q VALUES ('k1', 'value3'), ('k2', 'value4'), ('k3', 'value5')\");\n\n    await db2.push();\n    await db1.push();\n    await Promise.all([db1.pull(), db2.pull()]);\n\n    const rows1 = await db1.prepare('SELECT * FROM q').all();\n    const rows2 = await db1.prepare('SELECT * FROM q').all();\n    const expected = [{ x: 'k1', y: 'value1' }, { x: 'k2', y: 'value2' }, { x: 'k3', y: 'value5' }, { x: 'k4', y: 'value4' }];\n    expect(rows1.sort(localeCompare)).toEqual(expected.sort(localeCompare))\n    expect(rows2.sort(localeCompare)).toEqual(expected.sort(localeCompare))\n})\n\ntest('last-push-wins-with-delete', async () => {\n    {\n        const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)\");\n        await db.exec(\"DELETE FROM q\");\n        await db.push();\n        await db.close();\n    }\n    const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n    await db1.exec(\"INSERT INTO q VALUES ('k1', 'value1'), ('k2', 'value2'), ('k4', 'value4')\");\n    await db1.exec(\"DELETE FROM q\")\n\n    const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n    await db2.exec(\"INSERT INTO q VALUES ('k1', 'value3'), ('k2', 'value4'), ('k3', 'value5')\");\n\n    await db2.push();\n    await db1.push();\n    await Promise.all([db1.pull(), db2.pull()]);\n\n    const rows1 = await db1.prepare('SELECT * FROM q').all();\n    const rows2 = await db1.prepare('SELECT * FROM q').all();\n    const expected = [{ x: 'k3', y: 'value5' }];\n    expect(rows1).toEqual(expected)\n    expect(rows2).toEqual(expected)\n})\n\ntest('constraint-conflict', async () => {\n    {\n        const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS u(x TEXT PRIMARY KEY, y UNIQUE)\");\n        await db.exec(\"DELETE FROM u\");\n        await db.push();\n        await db.close();\n    }\n    const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n    await db1.exec(\"INSERT INTO u VALUES ('k1', 'value1')\");\n\n    const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n    await db2.exec(\"INSERT INTO u VALUES ('k2', 'value1')\");\n\n    await db1.push();\n    await expect(async () => await db2.push()).rejects.toThrow('SQLite error: UNIQUE constraint failed: u.y');\n})\n\ntest('checkpoint', async () => {\n    {\n        const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)\");\n        await db.exec(\"DELETE FROM q\");\n        await db.push();\n        await db.close();\n    }\n    const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n    for (let i = 0; i < 1000; i++) {\n        await db1.exec(`INSERT INTO q VALUES ('k${i}', 'v${i}')`);\n    }\n    expect((await db1.stats()).mainWalSize).toBeGreaterThan(4096 * 1000);\n    await db1.checkpoint();\n    expect((await db1.stats()).mainWalSize).toBe(0);\n    let revertWal = (await db1.stats()).revertWalSize;\n    expect(revertWal).toBeLessThan(4096 * 1000 / 50);\n\n    for (let i = 0; i < 1000; i++) {\n        await db1.exec(`UPDATE q SET y = 'u${i}' WHERE x = 'k${i}'`);\n    }\n    await db1.checkpoint();\n    expect((await db1.stats()).revertWalSize).toBe(revertWal);\n})\n\n\ntest('persistence-push', async () => {\n    {\n        const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)\");\n        await db.exec(\"DELETE FROM q\");\n        await db.push();\n        await db.close();\n    }\n    const path = `test-${(Math.random() * 10000) | 0}.db`;\n    try {\n        {\n            const db1 = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL });\n            await db1.exec(`INSERT INTO q VALUES ('k1', 'v1')`);\n            await db1.exec(`INSERT INTO q VALUES ('k2', 'v2')`);\n            await db1.close();\n        }\n\n        {\n            const db2 = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL });\n            await db2.exec(`INSERT INTO q VALUES ('k3', 'v3')`);\n            await db2.exec(`INSERT INTO q VALUES ('k4', 'v4')`);\n            const stmt = db2.prepare('SELECT * FROM q');\n            const rows = await stmt.all();\n            const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }, { x: 'k3', y: 'v3' }, { x: 'k4', y: 'v4' }];\n            expect(rows).toEqual(expected)\n            stmt.close();\n            await db2.close();\n        }\n\n        {\n            const db3 = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL });\n            await db3.push();\n            await db3.close();\n        }\n\n        {\n            const db4 = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL });\n            const rows = await db4.prepare('SELECT * FROM q').all();\n            const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }, { x: 'k3', y: 'v3' }, { x: 'k4', y: 'v4' }];\n            expect(rows).toEqual(expected)\n            await db4.close();\n        }\n    }\n    finally {\n        cleanup(path);\n    }\n})\n\ntest('persistence-offline', async () => {\n    {\n        const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)\");\n        await db.exec(\"DELETE FROM q\");\n        await db.push();\n        await db.close();\n    }\n    const path = `test-${(Math.random() * 10000) | 0}.db`;\n    try {\n        {\n            const db = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL });\n            await db.exec(`INSERT INTO q VALUES ('k1', 'v1')`);\n            await db.exec(`INSERT INTO q VALUES ('k2', 'v2')`);\n            await db.push();\n            await db.close();\n        }\n        {\n            const db = await connect({ path: path, url: \"https://not-valid-url.localhost\" });\n            const rows = await db.prepare(\"SELECT * FROM q\").all();\n            const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }];\n            expect(rows.sort(localeCompare)).toEqual(expected.sort(localeCompare))\n            await db.close();\n        }\n    } finally {\n        cleanup(path);\n    }\n})\n\ntest('persistence-pull-push', async () => {\n    {\n        const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)\");\n        await db.exec(\"DELETE FROM q\");\n        await db.push();\n        await db.close();\n    }\n    const path1 = `test-${(Math.random() * 10000) | 0}.db`;\n    const path2 = `test-${(Math.random() * 10000) | 0}.db`;\n    try {\n        const db1 = await connect({ path: path1, url: process.env.VITE_TURSO_DB_URL });\n        await db1.exec(`INSERT INTO q VALUES ('k1', 'v1')`);\n        await db1.exec(`INSERT INTO q VALUES ('k2', 'v2')`);\n        const stats1 = await db1.stats();\n\n        const db2 = await connect({ path: path2, url: process.env.VITE_TURSO_DB_URL });\n        await db2.exec(`INSERT INTO q VALUES ('k3', 'v3')`);\n        await db2.exec(`INSERT INTO q VALUES ('k4', 'v4')`);\n\n        await Promise.all([db1.push(), db2.push()]);\n        await Promise.all([db1.pull(), db2.pull()]);\n        const stats2 = await db1.stats();\n        console.info(stats1, stats2);\n        expect(stats1.revision).not.toBe(stats2.revision);\n\n        const rows1 = await db1.prepare('SELECT * FROM q').all();\n        const rows2 = await db2.prepare('SELECT * FROM q').all();\n        const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }, { x: 'k3', y: 'v3' }, { x: 'k4', y: 'v4' }];\n        expect(rows1.sort(localeCompare)).toEqual(expected.sort(localeCompare))\n        expect(rows2.sort(localeCompare)).toEqual(expected.sort(localeCompare))\n    } finally {\n        cleanup(path1);\n        cleanup(path2);\n    }\n})\n\ntest('update', async () => {\n    {\n        const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, longPollTimeoutMs: 5000 });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)\");\n        await db.exec(\"DELETE FROM q\");\n        await db.push();\n        await db.close();\n    }\n    const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n    await db.exec(\"INSERT INTO q VALUES ('1', '2')\")\n    await db.push();\n    await db.exec(\"INSERT INTO q VALUES ('1', '2') ON CONFLICT DO UPDATE SET y = '3'\")\n    await db.push();\n})\n\ntest('concurrent-updates', async () => {\n    {\n        const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, longPollTimeoutMs: 5000 });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)\");\n        await db.exec(\"DELETE FROM q\");\n        await db.push();\n        await db.close();\n    }\n    const db1 = await connect({\n        path: ':memory:',\n        url: process.env.VITE_TURSO_DB_URL,\n    });\n    await db1.exec(\"PRAGMA busy_timeout=100\");\n    async function pull(db) {\n        try {\n            await db.pull();\n        } catch (e) {\n            console.error('pull error', e);\n        } finally {\n            console.error('pull ok');\n            setTimeout(async () => await pull(db), 0);\n        }\n    }\n    async function push(db) {\n        try {\n            await db.push();\n        } catch (e) {\n            console.error('push error', e);\n        } finally {\n            console.error('push ok');\n            setTimeout(async () => await push(db), 0);\n        }\n    }\n\n    setTimeout(async () => await pull(db1), 0)\n    setTimeout(async () => await push(db1), 0)\n    for (let i = 0; i < 1000; i++) {\n        try {\n            await Promise.all([\n                db1.exec(`INSERT INTO q VALUES ('1', 0) ON CONFLICT DO UPDATE SET y = randomblob(1024)`),\n                db1.exec(`INSERT INTO q VALUES ('1', 0) ON CONFLICT DO UPDATE SET y = randomblob(1024)`)\n            ]);\n            console.info('insert ok');\n        } catch (e) {\n            console.error('insert error', e);\n        }\n        await new Promise(resolve => setTimeout(resolve, 1));\n    }\n})\n\ntest('corruption-bug-1', async () => {\n    {\n        const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, longPollTimeoutMs: 5000 });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)\");\n        await db.exec(\"DELETE FROM q\");\n        await db.push();\n        await db.close();\n    }\n    const db1 = await connect({\n        path: ':memory:',\n        url: process.env.VITE_TURSO_DB_URL,\n    });\n    for (let i = 0; i < 100; i++) {\n        await db1.exec(`INSERT INTO q VALUES ('1', 0) ON CONFLICT DO UPDATE SET y = randomblob(1024)`);\n    }\n    await db1.pull();\n    await db1.push();\n    for (let i = 0; i < 100; i++) {\n        await db1.exec(`INSERT INTO q VALUES ('1', 0) ON CONFLICT DO UPDATE SET y = randomblob(1024)`);\n    }\n    await db1.pull();\n    await db1.push();\n})\n\ntest('pull-push-concurrent', async () => {\n    {\n        const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, longPollTimeoutMs: 5000 });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)\");\n        await db.exec(\"DELETE FROM q\");\n        await db.push();\n        await db.close();\n    }\n    let pullResolve = null;\n    const pullFinish = new Promise(resolve => pullResolve = resolve);\n    let pushResolve = null;\n    const pushFinish = new Promise(resolve => pushResolve = resolve);\n    let stopPull = false;\n    let stopPush = false;\n    const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n    let pull = async () => {\n        try {\n            await db.pull();\n        } catch (e) {\n            console.error('pull', e);\n        } finally {\n            if (!stopPull) {\n                setTimeout(pull, 0);\n            } else {\n                pullResolve()\n            }\n        }\n    }\n    let push = async () => {\n        try {\n            if ((await db.stats()).cdcOperations > 0) {\n                await db.push();\n            }\n        } catch (e) {\n            console.error('push', e);\n        } finally {\n            if (!stopPush) {\n                setTimeout(push, 0);\n            } else {\n                pushResolve();\n            }\n        }\n    }\n    setTimeout(pull, 0);\n    setTimeout(push, 0);\n    for (let i = 0; i < 1000; i++) {\n        await db.exec(`INSERT INTO q VALUES ('k${i}', 'v${i}')`);\n    }\n    await new Promise(resolve => setTimeout(resolve, 1000));\n    stopPush = true;\n    await pushFinish;\n    stopPull = true;\n    await pullFinish;\n    console.info(await db.stats());\n})\n\ntest('checkpoint-and-actions', async () => {\n    {\n        const db = await connect({\n            path: ':memory:',\n            url: process.env.VITE_TURSO_DB_URL,\n            longPollTimeoutMs: 100,\n        });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS rows(key TEXT PRIMARY KEY, value INTEGER)\");\n        await db.exec(\"DELETE FROM rows\");\n        await db.exec(\"INSERT INTO rows VALUES ('key', 0)\");\n        await db.push();\n        await db.close();\n    }\n    const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n    await db1.exec(\"PRAGMA busy_timeout=100\");\n    const pull = async function (iterations: number) {\n        for (let i = 0; i < iterations; i++) {\n            try {\n                await db1.pull();\n            }\n            catch (e) { console.error('pull', e); }\n            await new Promise(resolve => setTimeout(resolve, 0));\n        }\n    }\n    const push = async function (iterations: number) {\n        for (let i = 0; i < iterations; i++) {\n            await new Promise(resolve => setTimeout(resolve, 5));\n            try {\n                if ((await db1.stats()).cdcOperations > 0) {\n                    const start = performance.now();\n                    await db1.push();\n                    console.info('push', performance.now() - start);\n                }\n            }\n            catch (e) { console.error('push', e); }\n        }\n    }\n    let rows = 0;\n    const run = async function (iterations: number) {\n        for (let i = 0; i < iterations; i++) {\n            await db1.prepare(\"UPDATE rows SET value = value + 1 WHERE key = ?\").run('key');\n            rows += 1;\n            const { cnt } = await db1.prepare(\"SELECT value as cnt FROM rows WHERE key = ?\").get(['key']);\n            console.info('CHECK', cnt, rows);\n            expect(cnt).toBe(rows);\n            await new Promise(resolve => setTimeout(resolve, 10 * (1 + Math.random())));\n        }\n    }\n    // await run(100);\n    await Promise.all([pull(40), push(40), run(100)]);\n})\n\ntest('transform', async () => {\n    {\n        const db = await connect({\n            path: ':memory:',\n            url: process.env.VITE_TURSO_DB_URL,\n        });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS counter(key TEXT PRIMARY KEY, value INTEGER)\");\n        await db.exec(\"DELETE FROM counter\");\n        await db.exec(\"INSERT INTO counter VALUES ('1', 0)\")\n        await db.push();\n        await db.close();\n    }\n    const transform = (m: DatabaseRowMutation) => ({\n        operation: 'rewrite',\n        stmt: {\n            sql: `UPDATE counter SET value = value + ? WHERE key = ?`,\n            values: [m.after.value - m.before.value, m.after.key]\n        }\n    } as DatabaseRowTransformResult);\n    const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, transform: transform });\n    const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, transform: transform });\n\n    await db1.exec(\"UPDATE counter SET value = value + 1 WHERE key = '1'\");\n    await db2.exec(\"UPDATE counter SET value = value + 1 WHERE key = '1'\");\n\n    await Promise.all([db1.push(), db2.push()]);\n    await Promise.all([db1.pull(), db2.pull()]);\n\n    const rows1 = await db1.prepare('SELECT * FROM counter').all();\n    const rows2 = await db2.prepare('SELECT * FROM counter').all();\n    expect(rows1).toEqual([{ key: '1', value: 2 }]);\n    expect(rows2).toEqual([{ key: '1', value: 2 }]);\n})\n\ntest('transform-many', async () => {\n    {\n        const db = await connect({\n            path: ':memory:',\n            url: process.env.VITE_TURSO_DB_URL,\n        });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS counter(key TEXT PRIMARY KEY, value INTEGER)\");\n        await db.exec(\"DELETE FROM counter\");\n        await db.exec(\"INSERT INTO counter VALUES ('1', 0)\")\n        await db.push();\n        await db.close();\n    }\n    const transform = (m: DatabaseRowMutation) => ({\n        operation: 'rewrite',\n        stmt: {\n            sql: `UPDATE counter SET value = value + ? WHERE key = ?`,\n            values: [m.after.value - m.before.value, m.after.key]\n        }\n    } as DatabaseRowTransformResult);\n    const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, transform: transform });\n    const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, transform: transform });\n\n    for (let i = 0; i < 1002; i++) {\n        await db1.exec(\"UPDATE counter SET value = value + 1 WHERE key = '1'\");\n    }\n    for (let i = 0; i < 1001; i++) {\n        await db2.exec(\"UPDATE counter SET value = value + 1 WHERE key = '1'\");\n    }\n\n    let start = performance.now();\n    await Promise.all([db1.push(), db2.push()]);\n    console.info('push', performance.now() - start);\n\n    start = performance.now();\n    await Promise.all([db1.pull(), db2.pull()]);\n    console.info('pull', performance.now() - start);\n\n    const rows1 = await db1.prepare('SELECT * FROM counter').all();\n    const rows2 = await db2.prepare('SELECT * FROM counter').all();\n    expect(rows1).toEqual([{ key: '1', value: 1001 + 1002 }]);\n    expect(rows2).toEqual([{ key: '1', value: 1001 + 1002 }]);\n})\n"
  },
  {
    "path": "bindings/javascript/sync/packages/native/promise.ts",
    "content": "import { DatabasePromise } from \"@tursodatabase/database-common\"\nimport { ProtocolIo, run, DatabaseOpts, EncryptionOpts, RunOpts, DatabaseRowMutation, DatabaseRowStatement, DatabaseRowTransformResult, DatabaseStats, SyncEngineGuards, Runner, runner, RemoteWriter, RemoteWriteStatement } from \"@tursodatabase/sync-common\";\nimport { SyncEngine, SyncEngineProtocolVersion, Database as NativeDatabase } from \"#index\";\nimport { promises } from \"node:fs\";\n\nlet NodeIO: ProtocolIo = {\n    async read(path: string): Promise<Buffer | Uint8Array | null> {\n        try {\n            return await promises.readFile(path);\n        } catch (error) {\n            if (error.code === 'ENOENT') {\n                return null;\n            }\n            throw error;\n        }\n    },\n    async write(path: string, data: Buffer | Uint8Array): Promise<void> {\n        const unix = Math.floor(Date.now() / 1000);\n        const nonce = Math.floor(Math.random() * 1000000000);\n        const tmp = `${path}.tmp.${unix}.${nonce}`;\n        await promises.writeFile(tmp, new Uint8Array(data));\n        try {\n            await promises.rename(tmp, path);\n        } catch (err) {\n            await promises.unlink(tmp);\n            throw err;\n        }\n    }\n};\n\nfunction memoryIO(): ProtocolIo {\n    let values = new Map();\n    return {\n        async read(path: string): Promise<Buffer | Uint8Array | null> {\n            return values.get(path);\n        },\n        async write(path: string, data: Buffer | Uint8Array): Promise<void> {\n            values.set(path, data);\n        }\n    }\n};\n\nfunction resolveUrl(url: string | (() => string | null)): string {\n    if (typeof url === \"function\") {\n        const resolved = url();\n        if (resolved == null) {\n            throw new Error(\"remoteWritesExperimental requires a non-null URL\");\n        }\n        return resolved;\n    }\n    return url;\n}\n\nclass Database extends DatabasePromise {\n    #engine: any;\n    #guards: SyncEngineGuards;\n    #runner: Runner;\n    #remoteWriter: RemoteWriter | null = null;\n    #db: any;\n    constructor(opts: DatabaseOpts) {\n        if (opts.url == null) {\n            const db = new NativeDatabase(opts.path, { tracing: opts.tracing }) as any;\n            super(db);\n            this.#db = db;\n            this.#engine = null;\n            return;\n        }\n\n        let partialSyncOpts = undefined;\n        if (opts.partialSyncExperimental != null) {\n            switch (opts.partialSyncExperimental.bootstrapStrategy.kind) {\n                case \"prefix\":\n                    partialSyncOpts = {\n                        bootstrapStrategy: { type: \"Prefix\", length: opts.partialSyncExperimental.bootstrapStrategy.length },\n                        segmentSize: opts.partialSyncExperimental.segmentSize,\n                        prefetch: opts.partialSyncExperimental.prefetch,\n                    };\n                    break;\n                case \"query\":\n                    partialSyncOpts = {\n                        bootstrapStrategy: { type: \"Query\", query: opts.partialSyncExperimental.bootstrapStrategy.query },\n                        segmentSize: opts.partialSyncExperimental.segmentSize,\n                        prefetch: opts.partialSyncExperimental.prefetch,\n                    };\n                    break;\n            }\n        }\n        const engine = new SyncEngine({\n            path: opts.path,\n            clientName: opts.clientName,\n            useTransform: opts.transform != null,\n            protocolVersion: SyncEngineProtocolVersion.V1,\n            longPollTimeoutMs: opts.longPollTimeoutMs,\n            tracing: opts.tracing,\n            bootstrapIfEmpty: typeof opts.url != \"function\" || opts.url() != null,\n            remoteEncryptionCipher: opts.remoteEncryption?.cipher,\n            remoteEncryptionKey: opts.remoteEncryption?.key,\n            partialSyncOpts: partialSyncOpts\n        });\n\n        let headers: { [K: string]: string } | (() => Promise<{ [K: string]: string }>);\n        if (typeof opts.authToken == \"function\") {\n            const authToken = opts.authToken;\n            headers = async () => ({\n                ...(opts.authToken != null && { \"Authorization\": `Bearer ${await authToken()}` }),\n                ...(opts.remoteEncryption != null && {\n                    \"x-turso-encryption-key\": opts.remoteEncryption.key,\n                    \"x-turso-encryption-cipher\": opts.remoteEncryption.cipher,\n                })\n            });\n        } else {\n            const authToken = opts.authToken;\n            headers = {\n                ...(opts.authToken != null && { \"Authorization\": `Bearer ${authToken}` }),\n                ...(opts.remoteEncryption != null && {\n                    \"x-turso-encryption-key\": opts.remoteEncryption.key,\n                    \"x-turso-encryption-cipher\": opts.remoteEncryption.cipher,\n                })\n            };\n        }\n        const runOpts = {\n            url: opts.url,\n            headers: headers,\n            preemptionMs: 1,\n            transform: opts.transform,\n        };\n        const db = engine.db() as unknown as any;\n        const memory = db.memory;\n        const io = memory ? memoryIO() : NodeIO;\n        const run = runner(runOpts, io, engine);\n\n        super(engine.db() as unknown as any, () => run.wait());\n\n        this.#db = engine.db() as unknown as any;\n        this.#runner = run;\n        this.#engine = engine;\n        this.#guards = new SyncEngineGuards();\n\n        // Initialize remote writer if remoteWrites is enabled\n        if (opts.remoteWritesExperimental && opts.url) {\n            const url = resolveUrl(opts.url);\n            this.#remoteWriter = new RemoteWriter({\n                url,\n                authToken: opts.authToken,\n                remoteEncryptionKey: opts.remoteEncryption?.key,\n            });\n        }\n    }\n    /**\n     * connect database and initialize it in case of clean start\n     */\n    override async connect() {\n        if (this.connected) {\n            return;\n        } else if (this.#engine == null) {\n            await super.connect();\n        } else {\n            await run(this.#runner, this.#engine.connect());\n        }\n        this.connected = true;\n    }\n    /**\n     * pull new changes from the remote database\n     * if {@link DatabaseOpts.longPollTimeoutMs} is set - then server will hold the connection open until either new changes will appear in the database or timeout occurs.\n     * @returns true if new changes were pulled from the remote\n     */\n    async pull() {\n        if (this.#engine == null) {\n            throw new Error(\"sync is disabled as database was opened without sync support\")\n        }\n        const changes = await this.#guards.wait(async () => await run(this.#runner, this.#engine.wait()));\n        if (changes.empty()) {\n            return false;\n        }\n        await this.#guards.apply(async () => await run(this.#runner, this.#engine.apply(changes)));\n        return true;\n    }\n    /**\n     * push new local changes to the remote database\n     * if {@link DatabaseOpts.transform} is set - then provided callback will be called for every mutation before sending it to the remote\n     */\n    async push() {\n        if (this.#engine == null) {\n            throw new Error(\"sync is disabled as database was opened without sync support\")\n        }\n        await this.#guards.push(async () => await run(this.#runner, this.#engine.push()));\n    }\n    /**\n     * checkpoint WAL for local database\n     */\n    async checkpoint() {\n        if (this.#engine == null) {\n            throw new Error(\"sync is disabled as database was opened without sync support\")\n        }\n        await this.#guards.checkpoint(async () => await run(this.#runner, this.#engine.checkpoint()));\n    }\n    /**\n     * @returns statistic of current local database\n     */\n    async stats(): Promise<DatabaseStats> {\n        if (this.#engine == null) {\n            throw new Error(\"sync is disabled as database was opened without sync support\")\n        }\n        return (await run(this.#runner, this.#engine.stats()));\n    }\n\n    /**\n     * Executes the given SQL string.\n     * When remoteWrites is enabled, write statements are sent to the remote server.\n     */\n    override async exec(sql: string) {\n        if (!this.#remoteWriter) return super.exec(sql);\n        const category = this.#db.classifySql(sql);\n\n        if (this.#remoteWriter.isInTransaction) {\n            const { shouldPull } = await this.#remoteWriter.execRemote(sql, category);\n            if (shouldPull) await this.pull();\n            return;\n        }\n\n        if (category === \"read\") return super.exec(sql);\n\n        const { shouldPull } = await this.#remoteWriter.execRemote(sql, category);\n        if (shouldPull) await this.pull();\n    }\n\n    /**\n     * Prepares a SQL statement for execution.\n     * When remoteWrites is enabled, returns a wrapper that routes writes to remote.\n     */\n    override prepare(sql: string) {\n        const localStmt = super.prepare(sql);\n\n        if (!this.#remoteWriter) {\n            return localStmt;\n        }\n\n        const category = this.#db.classifySql(sql);\n        const isReadonly = category === \"read\";\n        return new RemoteWriteStatement(\n            localStmt,\n            sql,\n            isReadonly,\n            this.#remoteWriter,\n            () => this.pull(),\n        ) as any;\n    }\n\n    /**\n     * Returns a function that executes the given function in a transaction.\n     * When remoteWrites is enabled, the entire transaction goes to remote.\n     */\n    override transaction(fn: (...any) => Promise<any>) {\n        if (typeof fn !== \"function\")\n            throw new TypeError(\"Expected first argument to be a function\");\n\n        if (!this.#remoteWriter) {\n            return super.transaction(fn);\n        }\n\n        const db = this;\n        const remoteWriter = this.#remoteWriter;\n        const wrapTxn = (mode: string) => {\n            return async (...bindParameters: any[]) => {\n                await remoteWriter.beginTransaction(mode);\n                try {\n                    const result = await fn(...bindParameters);\n                    await remoteWriter.commitTransaction();\n                    await db.pull();\n                    return result;\n                } catch (err) {\n                    await remoteWriter.rollbackTransaction();\n                    throw err;\n                }\n            };\n        };\n        const properties = {\n            default: { value: wrapTxn(\"\") },\n            deferred: { value: wrapTxn(\"DEFERRED\") },\n            immediate: { value: wrapTxn(\"IMMEDIATE\") },\n            exclusive: { value: wrapTxn(\"EXCLUSIVE\") },\n            database: { value: this, enumerable: true },\n        };\n        Object.defineProperties(properties.default.value, properties);\n        Object.defineProperties(properties.deferred.value, properties);\n        Object.defineProperties(properties.immediate.value, properties);\n        Object.defineProperties(properties.exclusive.value, properties);\n        return properties.default.value;\n    }\n\n    /**\n     * close the database\n     */\n    override async close(): Promise<void> {\n        if (this.#remoteWriter) {\n            await this.#remoteWriter.close();\n        }\n        await super.close();\n        if (this.#engine != null) {\n            this.#engine.close();\n        }\n    }\n}\n\n/**\n * Creates a new database connection asynchronously.\n *\n * @param {Object} opts - Options for database behavior.\n * @returns {Promise<Database>} - A promise that resolves to a Database instance.\n */\nasync function connect(opts: DatabaseOpts): Promise<Database> {\n    const db = new Database(opts);\n    await db.connect();\n    return db;\n}\n\nexport { connect, Database }\nexport type { DatabaseOpts, EncryptionOpts, DatabaseRowMutation, DatabaseRowStatement, DatabaseRowTransformResult }\n"
  },
  {
    "path": "bindings/javascript/sync/packages/native/remote-write.test.ts",
    "content": "import { expect, test } from 'vitest'\nimport { connect } from './promise.js'\n\nconst TURSO_URL = process.env.TURSO_DATABASE_URL;\nconst TURSO_TOKEN = process.env.TURSO_AUTH_TOKEN;\n\nfunction localSyncedDbOpts() {\n    if (!TURSO_URL) throw new Error('TURSO_DATABASE_URL env var is required');\n    return {\n        path: ':memory:',\n        url: TURSO_URL,\n        authToken: TURSO_TOKEN,\n    };\n}\n\nfunction remoteWriteOpts() {\n    return {\n        ...localSyncedDbOpts(),\n        remoteWritesExperimental: true,\n    };\n}\n\ntest('remote write: exec insert and read back', async () => {\n    const seed = await connect(localSyncedDbOpts());\n    await seed.exec(\"CREATE TABLE IF NOT EXISTS rw_exec(id INTEGER PRIMARY KEY, value TEXT)\");\n    await seed.exec(\"DELETE FROM rw_exec\");\n    await seed.push();\n    await seed.close();\n\n    const db = await connect(remoteWriteOpts());\n    await db.exec(\"INSERT INTO rw_exec VALUES (1, 'hello')\");\n\n    const rows = await db.prepare(\"SELECT * FROM rw_exec\").all();\n    expect(rows).toEqual([{ id: 1, value: 'hello' }]);\n    await db.close();\n})\n\ntest('remote write: prepared statement write', async () => {\n    const seed = await connect(localSyncedDbOpts());\n    await seed.exec(\"CREATE TABLE IF NOT EXISTS rw_prepared(id INTEGER PRIMARY KEY, x INTEGER)\");\n    await seed.exec(\"DELETE FROM rw_prepared\");\n    await seed.push();\n    await seed.close();\n\n    const db = await connect(remoteWriteOpts());\n    for (let i = 1; i <= 5; i++) {\n        await db.prepare(\"INSERT INTO rw_prepared(x) VALUES (?)\").run([i * 10]);\n    }\n\n    const rows = await db.prepare(\"SELECT x FROM rw_prepared ORDER BY x\").all();\n    expect(rows).toEqual([{ x: 10 }, { x: 20 }, { x: 30 }, { x: 40 }, { x: 50 }]);\n    await db.close();\n})\n\ntest('remote write: reads stay local', async () => {\n    const seed = await connect(localSyncedDbOpts());\n    await seed.exec(\"CREATE TABLE IF NOT EXISTS rw_read(id INTEGER PRIMARY KEY, value TEXT)\");\n    await seed.exec(\"DELETE FROM rw_read\");\n    await seed.exec(\"INSERT INTO rw_read VALUES (1, 'local')\");\n    await seed.push();\n    await seed.close();\n\n    const db = await connect(remoteWriteOpts());\n    const rows = await db.prepare(\"SELECT * FROM rw_read\").all();\n    expect(rows).toEqual([{ id: 1, value: 'local' }]);\n    await db.close();\n})\n\ntest('remote write: transaction commit', async () => {\n    const seed = await connect(localSyncedDbOpts());\n    await seed.exec(\"CREATE TABLE IF NOT EXISTS rw_txn(id INTEGER PRIMARY KEY, value INTEGER)\");\n    await seed.exec(\"DELETE FROM rw_txn\");\n    await seed.push();\n    await seed.close();\n\n    const db = await connect(remoteWriteOpts());\n    const txn = db.transaction(async () => {\n        await db.prepare(\"INSERT INTO rw_txn(value) VALUES (?)\").run([1]);\n        await db.prepare(\"INSERT INTO rw_txn(value) VALUES (?)\").run([2]);\n        await db.prepare(\"INSERT INTO rw_txn(value) VALUES (?)\").run([3]);\n    });\n    await txn();\n\n    const rows = await db.prepare(\"SELECT value FROM rw_txn ORDER BY value\").all();\n    expect(rows).toEqual([{ value: 1 }, { value: 2 }, { value: 3 }]);\n    await db.close();\n})\n\ntest('remote write: transaction rollback on error', async () => {\n    const seed = await connect(localSyncedDbOpts());\n    await seed.exec(\"CREATE TABLE IF NOT EXISTS rw_rollback(id INTEGER PRIMARY KEY, value INTEGER)\");\n    await seed.exec(\"DELETE FROM rw_rollback\");\n    await seed.exec(\"INSERT INTO rw_rollback VALUES (1, 100)\");\n    await seed.push();\n    await seed.close();\n\n    const db = await connect(remoteWriteOpts());\n    const txn = db.transaction(async () => {\n        await db.prepare(\"INSERT INTO rw_rollback(value) VALUES (?)\").run([200]);\n        throw new Error(\"deliberate rollback\");\n    });\n    await expect(txn()).rejects.toThrow(\"deliberate rollback\");\n\n    const rows = await db.prepare(\"SELECT value FROM rw_rollback ORDER BY value\").all();\n    expect(rows).toEqual([{ value: 100 }]);\n    await db.close();\n})\n\ntest('remote write: exec with BEGIN/COMMIT', async () => {\n    const seed = await connect(localSyncedDbOpts());\n    await seed.exec(\"CREATE TABLE IF NOT EXISTS rw_exec_txn(id INTEGER PRIMARY KEY, value TEXT)\");\n    await seed.exec(\"DELETE FROM rw_exec_txn\");\n    await seed.push();\n    await seed.close();\n\n    const db = await connect(remoteWriteOpts());\n    await db.exec(\"BEGIN\");\n    await db.exec(\"INSERT INTO rw_exec_txn VALUES (1, 'a')\");\n    await db.exec(\"INSERT INTO rw_exec_txn VALUES (2, 'b')\");\n    await db.exec(\"COMMIT\");\n\n    const rows = await db.prepare(\"SELECT value FROM rw_exec_txn ORDER BY id\").all();\n    expect(rows).toEqual([{ value: 'a' }, { value: 'b' }]);\n    await db.close();\n})\n\ntest('remote write: second client sees writes after pull', async () => {\n    const seed = await connect(localSyncedDbOpts());\n    await seed.exec(\"CREATE TABLE IF NOT EXISTS rw_visible(id INTEGER PRIMARY KEY, value TEXT)\");\n    await seed.exec(\"DELETE FROM rw_visible\");\n    await seed.push();\n    await seed.close();\n\n    const db1 = await connect(remoteWriteOpts());\n    await db1.exec(\"INSERT INTO rw_visible VALUES (1, 'from-remote-write')\");\n\n    // second client pulls and should see the remote write\n    const db2 = await connect(localSyncedDbOpts());\n\n    const rows = await db2.prepare(\"SELECT * FROM rw_visible\").all();\n    expect(rows).toEqual([{ id: 1, value: 'from-remote-write' }]);\n\n    await db1.close();\n    await db2.close();\n})\n\ntest('remote write: update and delete', async () => {\n    const seed = await connect(localSyncedDbOpts());\n    await seed.exec(\"CREATE TABLE IF NOT EXISTS rw_update(id INTEGER PRIMARY KEY, value TEXT)\");\n    await seed.exec(\"DELETE FROM rw_update\");\n    await seed.exec(\"INSERT INTO rw_update VALUES (1, 'original')\");\n    await seed.push();\n    await seed.close();\n\n    const db = await connect(remoteWriteOpts());\n    await db.exec(\"UPDATE rw_update SET value = 'updated' WHERE id = 1\");\n\n    let rows = await db.prepare(\"SELECT value FROM rw_update WHERE id = 1\").all();\n    expect(rows).toEqual([{ value: 'updated' }]);\n\n    await db.exec(\"DELETE FROM rw_update WHERE id = 1\");\n    rows = await db.prepare(\"SELECT COUNT(*) as cnt FROM rw_update\").all();\n    expect(rows).toEqual([{ cnt: 0 }]);\n    await db.close();\n})\n\ntest('remote write: unique conflict rejected at write time', async () => {\n    const seed = await connect(localSyncedDbOpts());\n    await seed.exec(\"CREATE TABLE IF NOT EXISTS rw_conflict(id INTEGER PRIMARY KEY, key TEXT, value TEXT UNIQUE)\");\n    await seed.exec(\"DELETE FROM rw_conflict\");\n    await seed.push();\n    await seed.close();\n\n    const db1 = await connect(remoteWriteOpts());\n    const db2 = await connect(remoteWriteOpts());\n\n    // first client inserts successfully\n    await db1.exec(\"INSERT INTO rw_conflict(key, value) VALUES ('a', 'taken')\");\n\n    // second client tries the same unique value — must fail at write time, not at push\n    await expect(\n        db2.exec(\"INSERT INTO rw_conflict(key, value) VALUES ('b', 'taken')\")\n    ).rejects.toThrow(/UNIQUE constraint failed/);\n\n    // both clients see exactly the same single row\n    const rows1 = await db1.prepare(\"SELECT key, value FROM rw_conflict\").all();\n    const rows2 = await db2.prepare(\"SELECT key, value FROM rw_conflict\").all();\n    expect(rows1).toEqual([{ key: 'a', value: 'taken' }]);\n    expect(rows2).toEqual([]);\n    await db2.pull();\n    const rows3 = await db2.prepare(\"SELECT key, value FROM rw_conflict\").all();\n    expect(rows3).toEqual([{ key: 'a', value: 'taken' }]);\n\n    await db1.close();\n    await db2.close();\n})\n\ntest('remote write: DDL create table and use it', async () => {\n    const seed = await connect(localSyncedDbOpts());\n    await seed.exec(\"DROP TABLE IF EXISTS rw_ddl\");\n    await seed.push();\n    await seed.close();\n\n    const db = await connect(remoteWriteOpts());\n    await db.exec(\"CREATE TABLE rw_ddl(id INTEGER PRIMARY KEY, name TEXT)\");\n    await db.exec(\"INSERT INTO rw_ddl(name) VALUES ('after-create')\");\n\n    const rows = await db.prepare(\"SELECT name FROM rw_ddl\").all();\n    expect(rows).toEqual([{ name: 'after-create' }]);\n\n    // second client should see the new table\n    const db2 = await connect(localSyncedDbOpts());\n    const rows2 = await db2.prepare(\"SELECT name FROM rw_ddl\").all();\n    expect(rows2).toEqual([{ name: 'after-create' }]);\n\n    await db.close();\n    await db2.close();\n})\n\ntest('remote write: DDL alter table adds column', async () => {\n    const seed = await connect(localSyncedDbOpts());\n    await seed.exec(\"DROP TABLE IF EXISTS rw_alter\");\n    await seed.exec(\"CREATE TABLE rw_alter(id INTEGER PRIMARY KEY, a TEXT)\");\n    await seed.exec(\"INSERT INTO rw_alter(a) VALUES ('before')\");\n    await seed.push();\n    await seed.close();\n\n    const db = await connect(remoteWriteOpts());\n    await db.exec(\"ALTER TABLE rw_alter ADD COLUMN b TEXT DEFAULT 'default_b'\");\n    await db.exec(\"INSERT INTO rw_alter(a, b) VALUES ('after', 'new_b')\");\n\n    const rows = await db.prepare(\"SELECT a, b FROM rw_alter ORDER BY a\").all();\n    expect(rows).toEqual([\n        { a: 'after', b: 'new_b' },\n        { a: 'before', b: 'default_b' },\n    ]);\n    await db.close();\n})\n\ntest('remote write: DDL drop table', async () => {\n    const seed = await connect(localSyncedDbOpts());\n    await seed.exec(\"CREATE TABLE IF NOT EXISTS rw_drop(id INTEGER PRIMARY KEY)\");\n    await seed.push();\n    await seed.close();\n\n    const db = await connect(remoteWriteOpts());\n    await db.exec(\"DROP TABLE rw_drop\");\n\n    await expect(() => db.prepare(\"SELECT * FROM rw_drop\")).toThrow(/no such table/);\n    await db.close();\n})\n\ntest('remote write: DDL create index', async () => {\n    const seed = await connect(localSyncedDbOpts());\n    await seed.exec(\"CREATE TABLE IF NOT EXISTS rw_idx(id INTEGER PRIMARY KEY, value TEXT)\");\n    await seed.exec(\"DELETE FROM rw_idx\");\n    await seed.push();\n    await seed.close();\n\n    const db = await connect(remoteWriteOpts());\n    await db.exec(\"CREATE INDEX IF NOT EXISTS idx_rw_value ON rw_idx(value)\");\n    await db.exec(\"INSERT INTO rw_idx(value) VALUES ('indexed')\");\n\n    const rows = await db.prepare(\"SELECT value FROM rw_idx\").all();\n    expect(rows).toEqual([{ value: 'indexed' }]);\n    await db.close();\n})\n\ntest('remote write: read-only transaction goes remote, not local', async () => {\n    const seed = await connect(localSyncedDbOpts());\n    await seed.exec(\"CREATE TABLE IF NOT EXISTS rw_read_txn(id INTEGER PRIMARY KEY, value TEXT)\");\n    await seed.exec(\"DELETE FROM rw_read_txn\");\n    await seed.exec(\"INSERT INTO rw_read_txn VALUES (1, 'original')\");\n    await seed.push();\n    await seed.close();\n\n    // db1 connects with remote writes — local replica has 'original'\n    const db1 = await connect(remoteWriteOpts());\n    const localRows = await db1.prepare(\"SELECT value FROM rw_read_txn\").all();\n    expect(localRows).toEqual([{ value: 'original' }]);\n\n    // another client updates the remote behind db1's back\n    const db2 = await connect(localSyncedDbOpts());\n    await db2.exec(\"UPDATE rw_read_txn SET value = 'updated' WHERE id = 1\");\n    await db2.push();\n    await db2.close();\n\n    // db1's local replica is stale — outside a txn, reads are local\n    const staleRows = await db1.prepare(\"SELECT value FROM rw_read_txn\").all();\n    expect(staleRows).toEqual([{ value: 'original' }]);\n\n    // inside a transaction, reads should go remote and see 'updated'\n    await db1.exec(\"BEGIN\");\n    const txnRows = await db1.prepare(\"SELECT value FROM rw_read_txn\").all();\n    expect(txnRows[0]).toMatchObject({ value: 'updated' });\n    await db1.exec(\"COMMIT\");\n\n    await db1.close();\n})\n\ntest('remote write: insert returning', async () => {\n    const seed = await connect(localSyncedDbOpts());\n    await seed.exec(\"DROP TABLE IF EXISTS rw_returning\");\n    await seed.exec(\"CREATE TABLE rw_returning(id INTEGER PRIMARY KEY, value TEXT)\");\n    await seed.push();\n    await seed.close();\n\n    const db = await connect(remoteWriteOpts());\n    const rows = await db.prepare(\"INSERT INTO rw_returning(value) VALUES (?) RETURNING id, value\").all(['hello']);\n    expect(rows).toMatchObject([{ id: 1, value: 'hello' }]);\n\n    expect(await db.prepare(\"SELECT * FROM rw_returning\").all()).toMatchObject([{ id: 1, value: 'hello' }]);\n\n    const rows2 = await db.prepare(\"INSERT INTO rw_returning(value) VALUES (?) RETURNING id, value\").all(['world']);\n    expect(rows2).toMatchObject([{ id: 2, value: 'world' }]);\n\n    expect(await db.prepare(\"SELECT * FROM rw_returning\").all()).toMatchObject([\n        { id: 1, value: 'hello' },\n        { id: 2, value: 'world' },\n    ]);\n\n    await db.close();\n})\n\ntest('remote write: blob round-trip', async () => {\n    const seed = await connect(localSyncedDbOpts());\n    await seed.exec(\"DROP TABLE IF EXISTS rw_blob\");\n    await seed.exec(\"CREATE TABLE rw_blob(id INTEGER PRIMARY KEY, data BLOB)\");\n    await seed.push();\n    await seed.close();\n\n    const db = await connect(remoteWriteOpts());\n    const blob = Buffer.from([0x00, 0x01, 0x02, 0xff, 0xfe, 0xfd]);\n    await db.prepare(\"INSERT INTO rw_blob(data) VALUES (?)\").run([blob]);\n\n    const rows = await db.prepare(\"SELECT data FROM rw_blob\").all();\n    expect(Buffer.from(rows[0].data)).toEqual(blob);\n    await db.close();\n})\n"
  },
  {
    "path": "bindings/javascript/sync/packages/native/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"skipLibCheck\": true,\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"module\": \"nodenext\",\n    \"target\": \"esnext\",\n    \"outDir\": \"dist/\",\n    \"lib\": [\n      \"es2020\"\n    ],\n    \"paths\": {\n      \"#index\": [\n        \"./index.d.ts\"\n      ]\n    }\n  },\n  \"include\": [\n    \"*\"\n  ],\n  \"exclude\": [\n    \"*.test.ts\"\n  ]\n}"
  },
  {
    "path": "bindings/javascript/sync/packages/wasm/README.md",
    "content": "<p align=\"center\">\n  <h1 align=\"center\">Turso Database for JavaScript in Browser</h1>\n</p>\n\n<p align=\"center\">\n  <a title=\"JavaScript\" target=\"_blank\" href=\"https://www.npmjs.com/package/@tursodatabase/database\"><img alt=\"npm\" src=\"https://img.shields.io/npm/v/@tursodatabase/database\"></a>\n  <a title=\"MIT\" target=\"_blank\" href=\"https://github.com/tursodatabase/turso/blob/main/LICENSE.md\"><img src=\"http://img.shields.io/badge/license-MIT-orange.svg?style=flat-square\"></a>\n</p>\n<p align=\"center\">\n  <a title=\"Users Discord\" target=\"_blank\" href=\"https://tur.so/discord\"><img alt=\"Chat with other users of Turso on Discord\" src=\"https://img.shields.io/discord/933071162680958986?label=Discord&logo=Discord&style=social\"></a>\n</p>\n\n---\n\n## About\n\nThis package is the Turso embedded database library for JavaScript in Browser.\n\n> **⚠️ Warning:** This software is in BETA. It may still contain bugs and unexpected behavior. Use caution with production data and ensure you have backups.\n\n## Features\n\n- **SQLite compatible:** SQLite query language and file format support ([status](https://github.com/tursodatabase/turso/blob/main/COMPAT.md)).\n- **In-process**: No network overhead, runs directly in your Node.js process\n- **TypeScript support**: Full TypeScript definitions included\n\n## Installation\n\n```bash\nnpm install @tursodatabase/database-wasm\n```\n\n## Getting Started\n\n### In-Memory Database\n\n```javascript\nimport { connect } from '@tursodatabase/database-wasm';\n\n// Create an in-memory database\nconst db = await connect(':memory:');\n\n// Create a table\nawait db.exec('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)');\n\n// Insert data\nconst insert = db.prepare('INSERT INTO users (name, email) VALUES (?, ?)');\nawait insert.run('Alice', 'alice@example.com');\nawait insert.run('Bob', 'bob@example.com');\n\n// Query data\nconst users = await db.prepare('SELECT * FROM users').all();\nconsole.log(users);\n// Output: [\n//   { id: 1, name: 'Alice', email: 'alice@example.com' },\n//   { id: 2, name: 'Bob', email: 'bob@example.com' }\n// ]\n```\n\n### File-Based Database\n\n```javascript\nimport { connect } from '@tursodatabase/database-wasm';\n\n// Create or open a database file\nconst db = await connect('my-database.db');\n\n// Create a table\nawait db.exec(`\n  CREATE TABLE IF NOT EXISTS posts (\n    id INTEGER PRIMARY KEY AUTOINCREMENT,\n    title TEXT NOT NULL,\n    content TEXT,\n    created_at DATETIME DEFAULT CURRENT_TIMESTAMP\n  )\n`);\n\n// Insert a post\nconst insertPost = db.prepare('INSERT INTO posts (title, content) VALUES (?, ?)');\nconst result = await insertPost.run('Hello World', 'This is my first blog post!');\n\nconsole.log(`Inserted post with ID: ${result.lastInsertRowid}`);\n```\n\n### Transactions\n\n```javascript\nimport { connect } from '@tursodatabase/database-wasm';\n\nconst db = await connect('transactions.db');\n\n// Using transactions for atomic operations\nconst transaction = db.transaction(async (users) => {\n  const insert = db.prepare('INSERT INTO users (name, email) VALUES (?, ?)');\n  for (const user of users) {\n    await insert.run(user.name, user.email);\n  }\n});\n\n// Execute transaction\nawait transaction([\n  { name: 'Alice', email: 'alice@example.com' },\n  { name: 'Bob', email: 'bob@example.com' }\n]);\n```\n\n## API Reference\n\nFor complete API documentation, see [JavaScript API Reference](https://github.com/tursodatabase/turso/blob/main/docs/javascript-api-reference.md).\n\n## Related Packages\n\n* The [@tursodatabase/serverless](https://www.npmjs.com/package/@tursodatabase/serverless) package provides a serverless driver with the same API.\n* The [@tursodatabase/sync](https://www.npmjs.com/package/@tursodatabase/sync) package provides bidirectional sync between a local Turso database and Turso Cloud. \n\n## License\n\nThis project is licensed under the [MIT license](https://github.com/tursodatabase/turso/blob/main/LICENSE.md).\n\n## Support\n\n- [GitHub Issues](https://github.com/tursodatabase/turso/issues)\n- [Documentation](https://docs.turso.tech)\n- [Discord Community](https://tur.so/discord)\n"
  },
  {
    "path": "bindings/javascript/sync/packages/wasm/cp-entrypoint.sh",
    "content": "sed 's/index-default.js/index-bundle.js/g' promise-default.ts > promise-bundle.ts\nsed 's/index-default.js/index-turbopack-hack.js/g' promise-default.ts > promise-turbopack-hack.ts\nsed 's/index-default.js/index-vite-dev-hack.js/g' promise-default.ts > promise-vite-dev-hack.ts\n"
  },
  {
    "path": "bindings/javascript/sync/packages/wasm/index-bundle.ts",
    "content": "import { setupMainThread } from \"@tursodatabase/database-wasm-common\";\n//@ts-ignore\nimport TursoWorker from \"./worker.js?worker&inline\";\n\nexport let MainWorker = null;\n\nconst __wasmUrl = new URL('./sync.wasm32-wasi.wasm', import.meta.url).href;\nconst __wasmFile = await fetch(__wasmUrl).then((res) => res.arrayBuffer())\n\nconst napiModule = await setupMainThread(__wasmFile, () => {\n  const worker = new TursoWorker({\n    name: 'turso-database-sync',\n    type: 'module',\n  })\n  MainWorker = worker;\n  return worker\n});\n\nexport default napiModule.exports\nexport const Database = napiModule.exports.Database\nexport const Statement = napiModule.exports.Statement\nexport const Opfs = napiModule.exports.Opfs\nexport const OpfsFile = napiModule.exports.OpfsFile\nexport const connect = napiModule.exports.connect\nexport const initThreadPool = napiModule.exports.initThreadPool\nexport const GeneratorHolder = napiModule.exports.GeneratorHolder\nexport const JsDataCompletion = napiModule.exports.JsDataCompletion\nexport const JsProtocolIo = napiModule.exports.JsProtocolIo\nexport const JsProtocolRequestBytes = napiModule.exports.JsProtocolRequestBytes\nexport const SyncEngine = napiModule.exports.SyncEngine\nexport const DatabaseChangeTypeJs = napiModule.exports.DatabaseChangeTypeJs\nexport const SyncEngineProtocolVersion = napiModule.exports.SyncEngineProtocolVersion\n\n"
  },
  {
    "path": "bindings/javascript/sync/packages/wasm/index-default.ts",
    "content": "import { isWebWorker, setupWebWorker, setupMainThread } from \"@tursodatabase/database-wasm-common\";\n\nexport let MainWorker = null;\n\nconst __wasmUrl = new URL('./sync.wasm32-wasi.wasm', import.meta.url).href;\nconst __wasmFile = await fetch(__wasmUrl).then((res) => res.arrayBuffer())\n\nconst napiModule = await setupMainThread(__wasmFile, () => {\n  const worker = new Worker(new URL('./worker.js', import.meta.url), {\n    name: 'turso-database-sync',\n    type: 'module',\n  })\n  MainWorker = worker;\n  return worker\n});\n\nexport default napiModule.exports\nexport const Database = napiModule.exports.Database\nexport const Statement = napiModule.exports.Statement\nexport const Opfs = napiModule.exports.Opfs\nexport const OpfsFile = napiModule.exports.OpfsFile\nexport const connect = napiModule.exports.connect\nexport const initThreadPool = napiModule.exports.initThreadPool\nexport const GeneratorHolder = napiModule.exports.GeneratorHolder\nexport const JsDataCompletion = napiModule.exports.JsDataCompletion\nexport const JsProtocolIo = napiModule.exports.JsProtocolIo\nexport const JsProtocolRequestBytes = napiModule.exports.JsProtocolRequestBytes\nexport const SyncEngine = napiModule.exports.SyncEngine\nexport const DatabaseChangeTypeJs = napiModule.exports.DatabaseChangeTypeJs\nexport const SyncEngineProtocolVersion = napiModule.exports.SyncEngineProtocolVersion\n\n"
  },
  {
    "path": "bindings/javascript/sync/packages/wasm/index-turbopack-hack.ts",
    "content": "import { setupMainThread } from \"@tursodatabase/database-wasm-common\";\nimport { tursoWasm } from \"./wasm-inline.js\";\n\n// Next (turbopack) has issues with loading wasm module: https://github.com/vercel/next.js/issues/82520\n// So, we inline wasm binary in the source code in order to avoid issues with loading it from the file\nconst __wasmFile = await tursoWasm();\n\nexport let MainWorker = null;\n\nconst napiModule = await setupMainThread(__wasmFile, () => {\n  const worker = new Worker(new URL('./worker.js', import.meta.url), {\n    name: 'turso-database-sync',\n    type: 'module',\n  })\n  MainWorker = worker;\n  return worker\n});\n\nexport default napiModule.exports\nexport const Database = napiModule.exports.Database\nexport const Statement = napiModule.exports.Statement\nexport const Opfs = napiModule.exports.Opfs\nexport const OpfsFile = napiModule.exports.OpfsFile\nexport const connect = napiModule.exports.connect\nexport const initThreadPool = napiModule.exports.initThreadPool\nexport const GeneratorHolder = napiModule.exports.GeneratorHolder\nexport const JsDataCompletion = napiModule.exports.JsDataCompletion\nexport const JsProtocolIo = napiModule.exports.JsProtocolIo\nexport const JsProtocolRequestBytes = napiModule.exports.JsProtocolRequestBytes\nexport const SyncEngine = napiModule.exports.SyncEngine\nexport const DatabaseChangeTypeJs = napiModule.exports.DatabaseChangeTypeJs\nexport const SyncEngineProtocolVersion = napiModule.exports.SyncEngineProtocolVersion\n"
  },
  {
    "path": "bindings/javascript/sync/packages/wasm/index-vite-dev-hack.ts",
    "content": "import { isWebWorker, setupMainThread, setupWebWorker } from \"@tursodatabase/database-wasm-common\";\nimport { tursoWasm } from \"./wasm-inline.js\";\n\nlet napiModule = {\n  exports: {\n    Database: {} as any,\n    Opfs: {} as any,\n    OpfsFile: {} as any,\n    Statement: {} as any,\n    connect: {} as any,\n    initThreadPool: {} as any,\n    GeneratorHolder: {} as any,\n    JsDataCompletion: {} as any,\n    JsProtocolIo: {} as any,\n    JsProtocolRequestBytes: {} as any,\n    SyncEngine: {} as any,\n    DatabaseChangeTypeJs: {} as any,\n    SyncEngineProtocolVersion: {} as any,\n  }\n};\n\nexport let MainWorker = null;\nif (isWebWorker()) {\n  setupWebWorker();\n} else {\n  // Vite has issues with loading wasm modules and worker in dev server: https://github.com/vitejs/vite/issues/8427\n  // So, the mitigation for dev server only is:\n  // 1. inline wasm binary in the source code in order to avoid issues with loading it from the file\n  // 2. use same file as worker entry point\n  const __wasmFile = await tursoWasm();\n\n  napiModule = await setupMainThread(__wasmFile, () => {\n    const worker = new Worker(import.meta.url, {\n      name: 'turso-database-sync',\n      type: 'module',\n    })\n    MainWorker = worker;\n    return worker\n  });\n}\n\nexport default napiModule.exports\nexport const Database = napiModule.exports.Database\nexport const Statement = napiModule.exports.Statement\nexport const Opfs = napiModule.exports.Opfs\nexport const OpfsFile = napiModule.exports.OpfsFile\nexport const connect = napiModule.exports.connect\nexport const initThreadPool = napiModule.exports.initThreadPool\nexport const GeneratorHolder = napiModule.exports.GeneratorHolder\nexport const JsDataCompletion = napiModule.exports.JsDataCompletion\nexport const JsProtocolIo = napiModule.exports.JsProtocolIo\nexport const JsProtocolRequestBytes = napiModule.exports.JsProtocolRequestBytes\nexport const SyncEngine = napiModule.exports.SyncEngine\nexport const DatabaseChangeTypeJs = napiModule.exports.DatabaseChangeTypeJs\nexport const SyncEngineProtocolVersion = napiModule.exports.SyncEngineProtocolVersion\n\n"
  },
  {
    "path": "bindings/javascript/sync/packages/wasm/package.json",
    "content": "{\n  \"name\": \"@tursodatabase/sync-wasm\",\n  \"version\": \"0.6.0-pre.4\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/tursodatabase/turso\"\n  },\n  \"type\": \"module\",\n  \"license\": \"MIT\",\n  \"main\": \"dist/promise.js\",\n  \"packageManager\": \"yarn@4.9.2\",\n  \"files\": [\n    \"dist/**\",\n    \"bundle/**\",\n    \"README.md\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"default\": \"./dist/promise-default.js\"\n    },\n    \"./bundle\": {\n      \"default\": \"./bundle/main.es.js\"\n    },\n    \"./vite\": {\n      \"development\": \"./dist/promise-vite-dev-hack.js\",\n      \"default\": \"./dist/promise-default.js\"\n    },\n    \"./turbopack\": {\n      \"default\": \"./dist/promise-turbopack-hack.js\"\n    }\n  },\n  \"devDependencies\": {\n    \"@napi-rs/cli\": \"^3.1.5\",\n    \"@vitest/browser\": \"^3.2.4\",\n    \"playwright\": \"^1.57.0\",\n    \"typescript\": \"^5.9.2\",\n    \"vite\": \"^7.1.5\",\n    \"vitest\": \"^3.2.4\"\n  },\n  \"scripts\": {\n    \"napi-build\": \"napi build --features browser --profile release-official --platform --target wasm32-wasip1-threads --no-js --manifest-path ../../Cargo.toml --output-dir . && rm index.d.ts sync.wasi* wasi* browser.js\",\n    \"tsc-build\": \"npm exec tsc && cp sync.wasm32-wasi.wasm ./dist/sync.wasm32-wasi.wasm && WASM_FILE=sync.wasm32-wasi.wasm JS_FILE=./dist/wasm-inline.js node ../../../scripts/inline-wasm-base64.js && npm run bundle\",\n    \"bundle\": \"vite build\",\n    \"build\": \"npm run napi-build && npm run tsc-build\",\n    \"test\": \"VITE_TURSO_DB_URL=http://f--a--a.localhost:10000 CI=1 vitest --testTimeout 30000 --browser=chromium --run && VITE_TURSO_DB_URL=http://f--a--a.localhost:10000 CI=1 vitest --testTimeout 30000 --browser=firefox --run\"\n  },\n  \"napi\": {\n    \"binaryName\": \"sync\",\n    \"targets\": [\n      \"wasm32-wasip1-threads\"\n    ]\n  },\n  \"imports\": {\n    \"#index\": \"./index.js\"\n  },\n  \"dependencies\": {\n    \"@tursodatabase/database-common\": \"^0.6.0-pre.4\",\n    \"@tursodatabase/database-wasm-common\": \"^0.6.0-pre.4\",\n    \"@tursodatabase/sync-common\": \"^0.6.0-pre.4\"\n  }\n}\n"
  },
  {
    "path": "bindings/javascript/sync/packages/wasm/promise-bundle.ts",
    "content": "import { registerFileAtWorker, unregisterFileAtWorker, ioNotifier } from \"@tursodatabase/database-wasm-common\"\nimport { DatabasePromise } from \"@tursodatabase/database-common\"\nimport { ProtocolIo, run, DatabaseOpts, EncryptionOpts, RunOpts, DatabaseRowMutation, DatabaseRowStatement, DatabaseRowTransformResult, DatabaseStats, SyncEngineGuards, Runner, runner, RemoteWriter, RemoteWriteStatement } from \"@tursodatabase/sync-common\";\nimport { SyncEngine, SyncEngineProtocolVersion, initThreadPool, MainWorker, Database as NativeDatabase } from \"./index-bundle.js\";\n\nlet BrowserIO: ProtocolIo = {\n    async read(path: string): Promise<Buffer | Uint8Array | null> {\n        const result = localStorage.getItem(path);\n        if (result == null) {\n            return null;\n        }\n        return new TextEncoder().encode(result);\n    },\n    async write(path: string, data: Buffer | Uint8Array): Promise<void> {\n        const array = new Uint8Array(data);\n        const value = new TextDecoder('utf-8').decode(array);\n        localStorage.setItem(path, value);\n    }\n};\n\nfunction memoryIO(): ProtocolIo {\n    let values = new Map();\n    return {\n        async read(path: string): Promise<Buffer | Uint8Array | null> {\n            return values.get(path);\n        },\n        async write(path: string, data: Buffer | Uint8Array): Promise<void> {\n            values.set(path, data);\n        }\n    }\n};\n\nasync function init(): Promise<Worker> {\n    await initThreadPool();\n    if (MainWorker == null) {\n        throw new Error(\"panic: MainWorker is not initialized\");\n    }\n    return MainWorker;\n}\n\nfunction resolveUrl(url: string | (() => string | null)): string {\n    if (typeof url === \"function\") {\n        const resolved = url();\n        if (resolved == null) {\n            throw new Error(\"remoteWritesExperimental requires a non-null URL\");\n        }\n        return resolved;\n    }\n    return url;\n}\n\nclass Database extends DatabasePromise {\n    #runner: Runner;\n    #engine: any;\n    #io: ProtocolIo;\n    #guards: SyncEngineGuards;\n    #worker: Worker | null;\n    #remoteWriter: RemoteWriter | null = null;\n    #db: any;\n    constructor(opts: DatabaseOpts) {\n        if (opts.url == null) {\n            const db = new NativeDatabase(opts.path, { tracing: opts.tracing }) as any;\n            super(\n                db,\n                () => ioNotifier.waitForCompletion(),\n            );\n            this.#db = db;\n            this.#engine = null;\n            return;\n        }\n\n        let partialSyncOpts = undefined;\n        if (opts.partialSyncExperimental != null) {\n            switch (opts.partialSyncExperimental.bootstrapStrategy.kind) {\n                case \"prefix\":\n                    partialSyncOpts = {\n                        bootstrapStrategy: { type: \"Prefix\", length: opts.partialSyncExperimental.bootstrapStrategy.length },\n                        segmentSize: opts.partialSyncExperimental.segmentSize,\n                        prefetch: opts.partialSyncExperimental.prefetch,\n                    };\n                    break;\n                case \"query\":\n                    partialSyncOpts = {\n                        bootstrapStrategy: { type: \"Query\", query: opts.partialSyncExperimental.bootstrapStrategy.query },\n                        segmentSize: opts.partialSyncExperimental.segmentSize,\n                        prefetch: opts.partialSyncExperimental.prefetch,\n                    };\n                    break;\n            }\n        }\n        const engine = new SyncEngine({\n            path: opts.path,\n            clientName: opts.clientName,\n            useTransform: opts.transform != null,\n            protocolVersion: SyncEngineProtocolVersion.V1,\n            longPollTimeoutMs: opts.longPollTimeoutMs,\n            tracing: opts.tracing,\n            bootstrapIfEmpty: typeof opts.url != \"function\" || opts.url() != null,\n            remoteEncryption: opts.remoteEncryption?.cipher,\n            partialSyncOpts: partialSyncOpts\n        });\n\n        let headers: { [K: string]: string } | (() => Promise<{ [K: string]: string }>);\n        if (typeof opts.authToken == \"function\") {\n            const authToken = opts.authToken;\n            headers = async () => ({\n                ...(opts.authToken != null && { \"Authorization\": `Bearer ${await authToken()}` }),\n                ...(opts.remoteEncryption != null && {\n                    \"x-turso-encryption-key\": opts.remoteEncryption.key,\n                    \"x-turso-encryption-cipher\": opts.remoteEncryption.cipher,\n                })\n            });\n        } else {\n            const authToken = opts.authToken;\n            headers = {\n                ...(opts.authToken != null && { \"Authorization\": `Bearer ${authToken}` }),\n                ...(opts.remoteEncryption != null && {\n                    \"x-turso-encryption-key\": opts.remoteEncryption.key,\n                    \"x-turso-encryption-cipher\": opts.remoteEncryption.cipher,\n                })\n            };\n        }\n        const runOpts = {\n            url: opts.url,\n            headers: headers,\n            preemptionMs: 1,\n            transform: opts.transform,\n        };\n        const db = engine.db() as unknown as any;\n        const memory = db.memory;\n        const io = memory ? memoryIO() : BrowserIO;\n        const run = runner(runOpts, io, engine);\n        super(db, () => run.wait());\n\n        this.#db = db;\n        this.#runner = run;\n        this.#engine = engine;\n        this.#io = io;\n        this.#guards = new SyncEngineGuards();\n\n        // Initialize remote writer if remoteWrites is enabled\n        if (opts.remoteWritesExperimental && opts.url) {\n            const url = resolveUrl(opts.url);\n            this.#remoteWriter = new RemoteWriter({\n                url,\n                authToken: opts.authToken,\n                remoteEncryptionKey: opts.remoteEncryption?.key,\n            });\n        }\n    }\n    /**\n     * connect database and initialize it in case of clean start\n     */\n    override async connect() {\n        if (this.connected) {\n            return;\n        } else if (this.#engine == null) {\n            if (!this.memory) {\n                const worker = await init();\n                await Promise.all([\n                    registerFileAtWorker(worker, this.name),\n                    registerFileAtWorker(worker, `${this.name}-wal`)\n                ]);\n                this.#worker = worker;\n            }\n            await super.connect();\n        } else {\n            if (!this.memory) {\n                this.#worker = await init();\n                await Promise.all([\n                    registerFileAtWorker(this.#worker, this.name),\n                    registerFileAtWorker(this.#worker, `${this.name}-wal`),\n                    registerFileAtWorker(this.#worker, `${this.name}-wal-revert`),\n                    registerFileAtWorker(this.#worker, `${this.name}-info`),\n                    registerFileAtWorker(this.#worker, `${this.name}-changes`),\n                ]);\n            }\n            await run(this.#runner, this.#engine.connect());\n        }\n        this.connected = true;\n    }\n    /**\n     * pull new changes from the remote database\n     * if {@link DatabaseOpts.longPollTimeoutMs} is set - then server will hold the connection open until either new changes will appear in the database or timeout occurs.\n     * @returns true if new changes were pulled from the remote\n     */\n    async pull() {\n        if (this.#engine == null) {\n            throw new Error(\"sync is disabled as database was opened without sync support\")\n        }\n        const changes = await this.#guards.wait(async () => await run(this.#runner, this.#engine.wait()));\n        if (changes.empty()) {\n            return false;\n        }\n        await this.#guards.apply(async () => await run(this.#runner, this.#engine.apply(changes)));\n        return true;\n    }\n    /**\n     * push new local changes to the remote database\n     * if {@link DatabaseOpts.transform} is set - then provided callback will be called for every mutation before sending it to the remote\n     */\n    async push() {\n        if (this.#engine == null) {\n            throw new Error(\"sync is disabled as database was opened without sync support\")\n        }\n        await this.#guards.push(async () => await run(this.#runner, this.#engine.push()));\n    }\n    /**\n     * checkpoint WAL for local database\n     */\n    async checkpoint() {\n        if (this.#engine == null) {\n            throw new Error(\"sync is disabled as database was opened without sync support\")\n        }\n        await this.#guards.checkpoint(async () => await run(this.#runner, this.#engine.checkpoint()));\n    }\n    /**\n     * @returns statistic of current local database\n     */\n    async stats(): Promise<DatabaseStats> {\n        if (this.#engine == null) {\n            throw new Error(\"sync is disabled as database was opened without sync support\")\n        }\n        return (await run(this.#runner, this.#engine.stats()));\n    }\n\n    /**\n     * Executes the given SQL string.\n     * When remoteWrites is enabled, write statements are sent to the remote server.\n     */\n    override async exec(sql: string) {\n        if (!this.#remoteWriter) return super.exec(sql);\n        const category = this.#db.classifySql(sql);\n\n        if (this.#remoteWriter.isInTransaction) {\n            const { shouldPull } = await this.#remoteWriter.execRemote(sql, category);\n            if (shouldPull) await this.pull();\n            return;\n        }\n\n        if (category === \"read\") return super.exec(sql);\n\n        const { shouldPull } = await this.#remoteWriter.execRemote(sql, category);\n        if (shouldPull) await this.pull();\n    }\n\n    /**\n     * Prepares a SQL statement for execution.\n     * When remoteWrites is enabled, returns a wrapper that routes writes to remote.\n     */\n    override prepare(sql: string) {\n        const localStmt = super.prepare(sql);\n\n        if (!this.#remoteWriter) {\n            return localStmt;\n        }\n\n        const category = this.#db.classifySql(sql);\n        const isReadonly = category === \"read\";\n        return new RemoteWriteStatement(\n            localStmt,\n            sql,\n            isReadonly,\n            this.#remoteWriter,\n            () => this.pull(),\n        ) as any;\n    }\n\n    /**\n     * Returns a function that executes the given function in a transaction.\n     * When remoteWrites is enabled, the entire transaction goes to remote.\n     */\n    override transaction(fn: (...any) => Promise<any>) {\n        if (typeof fn !== \"function\")\n            throw new TypeError(\"Expected first argument to be a function\");\n\n        if (!this.#remoteWriter) {\n            return super.transaction(fn);\n        }\n\n        const db = this;\n        const remoteWriter = this.#remoteWriter;\n        const wrapTxn = (mode: string) => {\n            return async (...bindParameters: any[]) => {\n                await remoteWriter.beginTransaction(mode);\n                try {\n                    const result = await fn(...bindParameters);\n                    await remoteWriter.commitTransaction();\n                    await db.pull();\n                    return result;\n                } catch (err) {\n                    await remoteWriter.rollbackTransaction();\n                    throw err;\n                }\n            };\n        };\n        const properties = {\n            default: { value: wrapTxn(\"\") },\n            deferred: { value: wrapTxn(\"DEFERRED\") },\n            immediate: { value: wrapTxn(\"IMMEDIATE\") },\n            exclusive: { value: wrapTxn(\"EXCLUSIVE\") },\n            database: { value: this, enumerable: true },\n        };\n        Object.defineProperties(properties.default.value, properties);\n        Object.defineProperties(properties.deferred.value, properties);\n        Object.defineProperties(properties.immediate.value, properties);\n        Object.defineProperties(properties.exclusive.value, properties);\n        return properties.default.value;\n    }\n\n    /**\n     * close the database and relevant files\n     */\n    async close() {\n        if (this.#remoteWriter) {\n            await this.#remoteWriter.close();\n        }\n        if (this.#engine != null) {\n            if (this.name != null && this.#worker != null) {\n                await Promise.all([\n                    unregisterFileAtWorker(this.#worker, this.name),\n                    unregisterFileAtWorker(this.#worker, `${this.name}-wal`),\n                    unregisterFileAtWorker(this.#worker, `${this.name}-wal-revert`),\n                    unregisterFileAtWorker(this.#worker, `${this.name}-info`),\n                    unregisterFileAtWorker(this.#worker, `${this.name}-changes`),\n                ]);\n            }\n            this.#engine.close();\n        }\n        await super.close();\n    }\n}\n\n/**\n * Creates a new database connection asynchronously.\n *\n * @param {Object} opts - Options for database behavior.\n * @returns {Promise<Database>} - A promise that resolves to a Database instance.\n */\nasync function connect(opts: DatabaseOpts): Promise<Database> {\n    const db = new Database(opts);\n    await db.connect();\n    return db;\n}\n\nexport { connect, Database }\nexport type { DatabaseOpts, EncryptionOpts, DatabaseRowMutation, DatabaseRowStatement, DatabaseRowTransformResult }\n"
  },
  {
    "path": "bindings/javascript/sync/packages/wasm/promise-default.ts",
    "content": "import { registerFileAtWorker, unregisterFileAtWorker, ioNotifier } from \"@tursodatabase/database-wasm-common\"\nimport { DatabasePromise } from \"@tursodatabase/database-common\"\nimport { ProtocolIo, run, DatabaseOpts, EncryptionOpts, RunOpts, DatabaseRowMutation, DatabaseRowStatement, DatabaseRowTransformResult, DatabaseStats, SyncEngineGuards, Runner, runner, RemoteWriter, RemoteWriteStatement } from \"@tursodatabase/sync-common\";\nimport { SyncEngine, SyncEngineProtocolVersion, initThreadPool, MainWorker, Database as NativeDatabase } from \"./index-default.js\";\n\nlet BrowserIO: ProtocolIo = {\n    async read(path: string): Promise<Buffer | Uint8Array | null> {\n        const result = localStorage.getItem(path);\n        if (result == null) {\n            return null;\n        }\n        return new TextEncoder().encode(result);\n    },\n    async write(path: string, data: Buffer | Uint8Array): Promise<void> {\n        const array = new Uint8Array(data);\n        const value = new TextDecoder('utf-8').decode(array);\n        localStorage.setItem(path, value);\n    }\n};\n\nfunction memoryIO(): ProtocolIo {\n    let values = new Map();\n    return {\n        async read(path: string): Promise<Buffer | Uint8Array | null> {\n            return values.get(path);\n        },\n        async write(path: string, data: Buffer | Uint8Array): Promise<void> {\n            values.set(path, data);\n        }\n    }\n};\n\nasync function init(): Promise<Worker> {\n    await initThreadPool();\n    if (MainWorker == null) {\n        throw new Error(\"panic: MainWorker is not initialized\");\n    }\n    return MainWorker;\n}\n\nfunction resolveUrl(url: string | (() => string | null)): string {\n    if (typeof url === \"function\") {\n        const resolved = url();\n        if (resolved == null) {\n            throw new Error(\"remoteWritesExperimental requires a non-null URL\");\n        }\n        return resolved;\n    }\n    return url;\n}\n\nclass Database extends DatabasePromise {\n    #runner: Runner;\n    #engine: any;\n    #io: ProtocolIo;\n    #guards: SyncEngineGuards;\n    #worker: Worker | null;\n    #remoteWriter: RemoteWriter | null = null;\n    #db: any;\n    constructor(opts: DatabaseOpts) {\n        if (opts.url == null) {\n            const db = new NativeDatabase(opts.path, { tracing: opts.tracing }) as any;\n            super(\n                db,\n                () => ioNotifier.waitForCompletion(),\n            );\n            this.#db = db;\n            this.#engine = null;\n            return;\n        }\n\n        let partialSyncOpts = undefined;\n        if (opts.partialSyncExperimental != null) {\n            switch (opts.partialSyncExperimental.bootstrapStrategy.kind) {\n                case \"prefix\":\n                    partialSyncOpts = {\n                        bootstrapStrategy: { type: \"Prefix\", length: opts.partialSyncExperimental.bootstrapStrategy.length },\n                        segmentSize: opts.partialSyncExperimental.segmentSize,\n                        prefetch: opts.partialSyncExperimental.prefetch,\n                    };\n                    break;\n                case \"query\":\n                    partialSyncOpts = {\n                        bootstrapStrategy: { type: \"Query\", query: opts.partialSyncExperimental.bootstrapStrategy.query },\n                        segmentSize: opts.partialSyncExperimental.segmentSize,\n                        prefetch: opts.partialSyncExperimental.prefetch,\n                    };\n                    break;\n            }\n        }\n        const engine = new SyncEngine({\n            path: opts.path,\n            clientName: opts.clientName,\n            useTransform: opts.transform != null,\n            protocolVersion: SyncEngineProtocolVersion.V1,\n            longPollTimeoutMs: opts.longPollTimeoutMs,\n            tracing: opts.tracing,\n            bootstrapIfEmpty: typeof opts.url != \"function\" || opts.url() != null,\n            remoteEncryption: opts.remoteEncryption?.cipher,\n            partialSyncOpts: partialSyncOpts\n        });\n\n        let headers: { [K: string]: string } | (() => Promise<{ [K: string]: string }>);\n        if (typeof opts.authToken == \"function\") {\n            const authToken = opts.authToken;\n            headers = async () => ({\n                ...(opts.authToken != null && { \"Authorization\": `Bearer ${await authToken()}` }),\n                ...(opts.remoteEncryption != null && {\n                    \"x-turso-encryption-key\": opts.remoteEncryption.key,\n                    \"x-turso-encryption-cipher\": opts.remoteEncryption.cipher,\n                })\n            });\n        } else {\n            const authToken = opts.authToken;\n            headers = {\n                ...(opts.authToken != null && { \"Authorization\": `Bearer ${authToken}` }),\n                ...(opts.remoteEncryption != null && {\n                    \"x-turso-encryption-key\": opts.remoteEncryption.key,\n                    \"x-turso-encryption-cipher\": opts.remoteEncryption.cipher,\n                })\n            };\n        }\n        const runOpts = {\n            url: opts.url,\n            headers: headers,\n            preemptionMs: 1,\n            transform: opts.transform,\n        };\n        const db = engine.db() as unknown as any;\n        const memory = db.memory;\n        const io = memory ? memoryIO() : BrowserIO;\n        const run = runner(runOpts, io, engine);\n        super(db, () => run.wait());\n\n        this.#db = db;\n        this.#runner = run;\n        this.#engine = engine;\n        this.#io = io;\n        this.#guards = new SyncEngineGuards();\n\n        // Initialize remote writer if remoteWrites is enabled\n        if (opts.remoteWritesExperimental && opts.url) {\n            const url = resolveUrl(opts.url);\n            this.#remoteWriter = new RemoteWriter({\n                url,\n                authToken: opts.authToken,\n                remoteEncryptionKey: opts.remoteEncryption?.key,\n            });\n        }\n    }\n    /**\n     * connect database and initialize it in case of clean start\n     */\n    override async connect() {\n        if (this.connected) {\n            return;\n        } else if (this.#engine == null) {\n            if (!this.memory) {\n                const worker = await init();\n                await Promise.all([\n                    registerFileAtWorker(worker, this.name),\n                    registerFileAtWorker(worker, `${this.name}-wal`)\n                ]);\n                this.#worker = worker;\n            }\n            await super.connect();\n        } else {\n            if (!this.memory) {\n                this.#worker = await init();\n                await Promise.all([\n                    registerFileAtWorker(this.#worker, this.name),\n                    registerFileAtWorker(this.#worker, `${this.name}-wal`),\n                    registerFileAtWorker(this.#worker, `${this.name}-wal-revert`),\n                    registerFileAtWorker(this.#worker, `${this.name}-info`),\n                    registerFileAtWorker(this.#worker, `${this.name}-changes`),\n                ]);\n            }\n            await run(this.#runner, this.#engine.connect());\n        }\n        this.connected = true;\n    }\n    /**\n     * pull new changes from the remote database\n     * if {@link DatabaseOpts.longPollTimeoutMs} is set - then server will hold the connection open until either new changes will appear in the database or timeout occurs.\n     * @returns true if new changes were pulled from the remote\n     */\n    async pull() {\n        if (this.#engine == null) {\n            throw new Error(\"sync is disabled as database was opened without sync support\")\n        }\n        const changes = await this.#guards.wait(async () => await run(this.#runner, this.#engine.wait()));\n        if (changes.empty()) {\n            return false;\n        }\n        await this.#guards.apply(async () => await run(this.#runner, this.#engine.apply(changes)));\n        return true;\n    }\n    /**\n     * push new local changes to the remote database\n     * if {@link DatabaseOpts.transform} is set - then provided callback will be called for every mutation before sending it to the remote\n     */\n    async push() {\n        if (this.#engine == null) {\n            throw new Error(\"sync is disabled as database was opened without sync support\")\n        }\n        await this.#guards.push(async () => await run(this.#runner, this.#engine.push()));\n    }\n    /**\n     * checkpoint WAL for local database\n     */\n    async checkpoint() {\n        if (this.#engine == null) {\n            throw new Error(\"sync is disabled as database was opened without sync support\")\n        }\n        await this.#guards.checkpoint(async () => await run(this.#runner, this.#engine.checkpoint()));\n    }\n    /**\n     * @returns statistic of current local database\n     */\n    async stats(): Promise<DatabaseStats> {\n        if (this.#engine == null) {\n            throw new Error(\"sync is disabled as database was opened without sync support\")\n        }\n        return (await run(this.#runner, this.#engine.stats()));\n    }\n\n    /**\n     * Executes the given SQL string.\n     * When remoteWrites is enabled, write statements are sent to the remote server.\n     */\n    override async exec(sql: string) {\n        if (!this.#remoteWriter) return super.exec(sql);\n        const category = this.#db.classifySql(sql);\n\n        if (this.#remoteWriter.isInTransaction) {\n            const { shouldPull } = await this.#remoteWriter.execRemote(sql, category);\n            if (shouldPull) await this.pull();\n            return;\n        }\n\n        if (category === \"read\") return super.exec(sql);\n\n        const { shouldPull } = await this.#remoteWriter.execRemote(sql, category);\n        if (shouldPull) await this.pull();\n    }\n\n    /**\n     * Prepares a SQL statement for execution.\n     * When remoteWrites is enabled, returns a wrapper that routes writes to remote.\n     */\n    override prepare(sql: string) {\n        const localStmt = super.prepare(sql);\n\n        if (!this.#remoteWriter) {\n            return localStmt;\n        }\n\n        const category = this.#db.classifySql(sql);\n        const isReadonly = category === \"read\";\n        return new RemoteWriteStatement(\n            localStmt,\n            sql,\n            isReadonly,\n            this.#remoteWriter,\n            () => this.pull(),\n        ) as any;\n    }\n\n    /**\n     * Returns a function that executes the given function in a transaction.\n     * When remoteWrites is enabled, the entire transaction goes to remote.\n     */\n    override transaction(fn: (...any) => Promise<any>) {\n        if (typeof fn !== \"function\")\n            throw new TypeError(\"Expected first argument to be a function\");\n\n        if (!this.#remoteWriter) {\n            return super.transaction(fn);\n        }\n\n        const db = this;\n        const remoteWriter = this.#remoteWriter;\n        const wrapTxn = (mode: string) => {\n            return async (...bindParameters: any[]) => {\n                await remoteWriter.beginTransaction(mode);\n                try {\n                    const result = await fn(...bindParameters);\n                    await remoteWriter.commitTransaction();\n                    await db.pull();\n                    return result;\n                } catch (err) {\n                    await remoteWriter.rollbackTransaction();\n                    throw err;\n                }\n            };\n        };\n        const properties = {\n            default: { value: wrapTxn(\"\") },\n            deferred: { value: wrapTxn(\"DEFERRED\") },\n            immediate: { value: wrapTxn(\"IMMEDIATE\") },\n            exclusive: { value: wrapTxn(\"EXCLUSIVE\") },\n            database: { value: this, enumerable: true },\n        };\n        Object.defineProperties(properties.default.value, properties);\n        Object.defineProperties(properties.deferred.value, properties);\n        Object.defineProperties(properties.immediate.value, properties);\n        Object.defineProperties(properties.exclusive.value, properties);\n        return properties.default.value;\n    }\n\n    /**\n     * close the database and relevant files\n     */\n    async close() {\n        if (this.#remoteWriter) {\n            await this.#remoteWriter.close();\n        }\n        if (this.#engine != null) {\n            if (this.name != null && this.#worker != null) {\n                await Promise.all([\n                    unregisterFileAtWorker(this.#worker, this.name),\n                    unregisterFileAtWorker(this.#worker, `${this.name}-wal`),\n                    unregisterFileAtWorker(this.#worker, `${this.name}-wal-revert`),\n                    unregisterFileAtWorker(this.#worker, `${this.name}-info`),\n                    unregisterFileAtWorker(this.#worker, `${this.name}-changes`),\n                ]);\n            }\n            this.#engine.close();\n        }\n        await super.close();\n    }\n}\n\n/**\n * Creates a new database connection asynchronously.\n *\n * @param {Object} opts - Options for database behavior.\n * @returns {Promise<Database>} - A promise that resolves to a Database instance.\n */\nasync function connect(opts: DatabaseOpts): Promise<Database> {\n    const db = new Database(opts);\n    await db.connect();\n    return db;\n}\n\nexport { connect, Database }\nexport type { DatabaseOpts, EncryptionOpts, DatabaseRowMutation, DatabaseRowStatement, DatabaseRowTransformResult }\n"
  },
  {
    "path": "bindings/javascript/sync/packages/wasm/promise-turbopack-hack.ts",
    "content": "import { registerFileAtWorker, unregisterFileAtWorker, ioNotifier } from \"@tursodatabase/database-wasm-common\"\nimport { DatabasePromise } from \"@tursodatabase/database-common\"\nimport { ProtocolIo, run, DatabaseOpts, EncryptionOpts, RunOpts, DatabaseRowMutation, DatabaseRowStatement, DatabaseRowTransformResult, DatabaseStats, SyncEngineGuards, Runner, runner, RemoteWriter, RemoteWriteStatement } from \"@tursodatabase/sync-common\";\nimport { SyncEngine, SyncEngineProtocolVersion, initThreadPool, MainWorker, Database as NativeDatabase } from \"./index-turbopack-hack.js\";\n\nlet BrowserIO: ProtocolIo = {\n    async read(path: string): Promise<Buffer | Uint8Array | null> {\n        const result = localStorage.getItem(path);\n        if (result == null) {\n            return null;\n        }\n        return new TextEncoder().encode(result);\n    },\n    async write(path: string, data: Buffer | Uint8Array): Promise<void> {\n        const array = new Uint8Array(data);\n        const value = new TextDecoder('utf-8').decode(array);\n        localStorage.setItem(path, value);\n    }\n};\n\nfunction memoryIO(): ProtocolIo {\n    let values = new Map();\n    return {\n        async read(path: string): Promise<Buffer | Uint8Array | null> {\n            return values.get(path);\n        },\n        async write(path: string, data: Buffer | Uint8Array): Promise<void> {\n            values.set(path, data);\n        }\n    }\n};\n\nasync function init(): Promise<Worker> {\n    await initThreadPool();\n    if (MainWorker == null) {\n        throw new Error(\"panic: MainWorker is not initialized\");\n    }\n    return MainWorker;\n}\n\nfunction resolveUrl(url: string | (() => string | null)): string {\n    if (typeof url === \"function\") {\n        const resolved = url();\n        if (resolved == null) {\n            throw new Error(\"remoteWritesExperimental requires a non-null URL\");\n        }\n        return resolved;\n    }\n    return url;\n}\n\nclass Database extends DatabasePromise {\n    #runner: Runner;\n    #engine: any;\n    #io: ProtocolIo;\n    #guards: SyncEngineGuards;\n    #worker: Worker | null;\n    #remoteWriter: RemoteWriter | null = null;\n    #db: any;\n    constructor(opts: DatabaseOpts) {\n        if (opts.url == null) {\n            const db = new NativeDatabase(opts.path, { tracing: opts.tracing }) as any;\n            super(\n                db,\n                () => ioNotifier.waitForCompletion(),\n            );\n            this.#db = db;\n            this.#engine = null;\n            return;\n        }\n\n        let partialSyncOpts = undefined;\n        if (opts.partialSyncExperimental != null) {\n            switch (opts.partialSyncExperimental.bootstrapStrategy.kind) {\n                case \"prefix\":\n                    partialSyncOpts = {\n                        bootstrapStrategy: { type: \"Prefix\", length: opts.partialSyncExperimental.bootstrapStrategy.length },\n                        segmentSize: opts.partialSyncExperimental.segmentSize,\n                        prefetch: opts.partialSyncExperimental.prefetch,\n                    };\n                    break;\n                case \"query\":\n                    partialSyncOpts = {\n                        bootstrapStrategy: { type: \"Query\", query: opts.partialSyncExperimental.bootstrapStrategy.query },\n                        segmentSize: opts.partialSyncExperimental.segmentSize,\n                        prefetch: opts.partialSyncExperimental.prefetch,\n                    };\n                    break;\n            }\n        }\n        const engine = new SyncEngine({\n            path: opts.path,\n            clientName: opts.clientName,\n            useTransform: opts.transform != null,\n            protocolVersion: SyncEngineProtocolVersion.V1,\n            longPollTimeoutMs: opts.longPollTimeoutMs,\n            tracing: opts.tracing,\n            bootstrapIfEmpty: typeof opts.url != \"function\" || opts.url() != null,\n            remoteEncryption: opts.remoteEncryption?.cipher,\n            partialSyncOpts: partialSyncOpts\n        });\n\n        let headers: { [K: string]: string } | (() => Promise<{ [K: string]: string }>);\n        if (typeof opts.authToken == \"function\") {\n            const authToken = opts.authToken;\n            headers = async () => ({\n                ...(opts.authToken != null && { \"Authorization\": `Bearer ${await authToken()}` }),\n                ...(opts.remoteEncryption != null && {\n                    \"x-turso-encryption-key\": opts.remoteEncryption.key,\n                    \"x-turso-encryption-cipher\": opts.remoteEncryption.cipher,\n                })\n            });\n        } else {\n            const authToken = opts.authToken;\n            headers = {\n                ...(opts.authToken != null && { \"Authorization\": `Bearer ${authToken}` }),\n                ...(opts.remoteEncryption != null && {\n                    \"x-turso-encryption-key\": opts.remoteEncryption.key,\n                    \"x-turso-encryption-cipher\": opts.remoteEncryption.cipher,\n                })\n            };\n        }\n        const runOpts = {\n            url: opts.url,\n            headers: headers,\n            preemptionMs: 1,\n            transform: opts.transform,\n        };\n        const db = engine.db() as unknown as any;\n        const memory = db.memory;\n        const io = memory ? memoryIO() : BrowserIO;\n        const run = runner(runOpts, io, engine);\n        super(db, () => run.wait());\n\n        this.#db = db;\n        this.#runner = run;\n        this.#engine = engine;\n        this.#io = io;\n        this.#guards = new SyncEngineGuards();\n\n        // Initialize remote writer if remoteWrites is enabled\n        if (opts.remoteWritesExperimental && opts.url) {\n            const url = resolveUrl(opts.url);\n            this.#remoteWriter = new RemoteWriter({\n                url,\n                authToken: opts.authToken,\n                remoteEncryptionKey: opts.remoteEncryption?.key,\n            });\n        }\n    }\n    /**\n     * connect database and initialize it in case of clean start\n     */\n    override async connect() {\n        if (this.connected) {\n            return;\n        } else if (this.#engine == null) {\n            if (!this.memory) {\n                const worker = await init();\n                await Promise.all([\n                    registerFileAtWorker(worker, this.name),\n                    registerFileAtWorker(worker, `${this.name}-wal`)\n                ]);\n                this.#worker = worker;\n            }\n            await super.connect();\n        } else {\n            if (!this.memory) {\n                this.#worker = await init();\n                await Promise.all([\n                    registerFileAtWorker(this.#worker, this.name),\n                    registerFileAtWorker(this.#worker, `${this.name}-wal`),\n                    registerFileAtWorker(this.#worker, `${this.name}-wal-revert`),\n                    registerFileAtWorker(this.#worker, `${this.name}-info`),\n                    registerFileAtWorker(this.#worker, `${this.name}-changes`),\n                ]);\n            }\n            await run(this.#runner, this.#engine.connect());\n        }\n        this.connected = true;\n    }\n    /**\n     * pull new changes from the remote database\n     * if {@link DatabaseOpts.longPollTimeoutMs} is set - then server will hold the connection open until either new changes will appear in the database or timeout occurs.\n     * @returns true if new changes were pulled from the remote\n     */\n    async pull() {\n        if (this.#engine == null) {\n            throw new Error(\"sync is disabled as database was opened without sync support\")\n        }\n        const changes = await this.#guards.wait(async () => await run(this.#runner, this.#engine.wait()));\n        if (changes.empty()) {\n            return false;\n        }\n        await this.#guards.apply(async () => await run(this.#runner, this.#engine.apply(changes)));\n        return true;\n    }\n    /**\n     * push new local changes to the remote database\n     * if {@link DatabaseOpts.transform} is set - then provided callback will be called for every mutation before sending it to the remote\n     */\n    async push() {\n        if (this.#engine == null) {\n            throw new Error(\"sync is disabled as database was opened without sync support\")\n        }\n        await this.#guards.push(async () => await run(this.#runner, this.#engine.push()));\n    }\n    /**\n     * checkpoint WAL for local database\n     */\n    async checkpoint() {\n        if (this.#engine == null) {\n            throw new Error(\"sync is disabled as database was opened without sync support\")\n        }\n        await this.#guards.checkpoint(async () => await run(this.#runner, this.#engine.checkpoint()));\n    }\n    /**\n     * @returns statistic of current local database\n     */\n    async stats(): Promise<DatabaseStats> {\n        if (this.#engine == null) {\n            throw new Error(\"sync is disabled as database was opened without sync support\")\n        }\n        return (await run(this.#runner, this.#engine.stats()));\n    }\n\n    /**\n     * Executes the given SQL string.\n     * When remoteWrites is enabled, write statements are sent to the remote server.\n     */\n    override async exec(sql: string) {\n        if (!this.#remoteWriter) return super.exec(sql);\n        const category = this.#db.classifySql(sql);\n\n        if (this.#remoteWriter.isInTransaction) {\n            const { shouldPull } = await this.#remoteWriter.execRemote(sql, category);\n            if (shouldPull) await this.pull();\n            return;\n        }\n\n        if (category === \"read\") return super.exec(sql);\n\n        const { shouldPull } = await this.#remoteWriter.execRemote(sql, category);\n        if (shouldPull) await this.pull();\n    }\n\n    /**\n     * Prepares a SQL statement for execution.\n     * When remoteWrites is enabled, returns a wrapper that routes writes to remote.\n     */\n    override prepare(sql: string) {\n        const localStmt = super.prepare(sql);\n\n        if (!this.#remoteWriter) {\n            return localStmt;\n        }\n\n        const category = this.#db.classifySql(sql);\n        const isReadonly = category === \"read\";\n        return new RemoteWriteStatement(\n            localStmt,\n            sql,\n            isReadonly,\n            this.#remoteWriter,\n            () => this.pull(),\n        ) as any;\n    }\n\n    /**\n     * Returns a function that executes the given function in a transaction.\n     * When remoteWrites is enabled, the entire transaction goes to remote.\n     */\n    override transaction(fn: (...any) => Promise<any>) {\n        if (typeof fn !== \"function\")\n            throw new TypeError(\"Expected first argument to be a function\");\n\n        if (!this.#remoteWriter) {\n            return super.transaction(fn);\n        }\n\n        const db = this;\n        const remoteWriter = this.#remoteWriter;\n        const wrapTxn = (mode: string) => {\n            return async (...bindParameters: any[]) => {\n                await remoteWriter.beginTransaction(mode);\n                try {\n                    const result = await fn(...bindParameters);\n                    await remoteWriter.commitTransaction();\n                    await db.pull();\n                    return result;\n                } catch (err) {\n                    await remoteWriter.rollbackTransaction();\n                    throw err;\n                }\n            };\n        };\n        const properties = {\n            default: { value: wrapTxn(\"\") },\n            deferred: { value: wrapTxn(\"DEFERRED\") },\n            immediate: { value: wrapTxn(\"IMMEDIATE\") },\n            exclusive: { value: wrapTxn(\"EXCLUSIVE\") },\n            database: { value: this, enumerable: true },\n        };\n        Object.defineProperties(properties.default.value, properties);\n        Object.defineProperties(properties.deferred.value, properties);\n        Object.defineProperties(properties.immediate.value, properties);\n        Object.defineProperties(properties.exclusive.value, properties);\n        return properties.default.value;\n    }\n\n    /**\n     * close the database and relevant files\n     */\n    async close() {\n        if (this.#remoteWriter) {\n            await this.#remoteWriter.close();\n        }\n        if (this.#engine != null) {\n            if (this.name != null && this.#worker != null) {\n                await Promise.all([\n                    unregisterFileAtWorker(this.#worker, this.name),\n                    unregisterFileAtWorker(this.#worker, `${this.name}-wal`),\n                    unregisterFileAtWorker(this.#worker, `${this.name}-wal-revert`),\n                    unregisterFileAtWorker(this.#worker, `${this.name}-info`),\n                    unregisterFileAtWorker(this.#worker, `${this.name}-changes`),\n                ]);\n            }\n            this.#engine.close();\n        }\n        await super.close();\n    }\n}\n\n/**\n * Creates a new database connection asynchronously.\n *\n * @param {Object} opts - Options for database behavior.\n * @returns {Promise<Database>} - A promise that resolves to a Database instance.\n */\nasync function connect(opts: DatabaseOpts): Promise<Database> {\n    const db = new Database(opts);\n    await db.connect();\n    return db;\n}\n\nexport { connect, Database }\nexport type { DatabaseOpts, EncryptionOpts, DatabaseRowMutation, DatabaseRowStatement, DatabaseRowTransformResult }\n"
  },
  {
    "path": "bindings/javascript/sync/packages/wasm/promise-vite-dev-hack.ts",
    "content": "import { registerFileAtWorker, unregisterFileAtWorker, ioNotifier } from \"@tursodatabase/database-wasm-common\"\nimport { DatabasePromise } from \"@tursodatabase/database-common\"\nimport { ProtocolIo, run, DatabaseOpts, EncryptionOpts, RunOpts, DatabaseRowMutation, DatabaseRowStatement, DatabaseRowTransformResult, DatabaseStats, SyncEngineGuards, Runner, runner, RemoteWriter, RemoteWriteStatement } from \"@tursodatabase/sync-common\";\nimport { SyncEngine, SyncEngineProtocolVersion, initThreadPool, MainWorker, Database as NativeDatabase } from \"./index-vite-dev-hack.js\";\n\nlet BrowserIO: ProtocolIo = {\n    async read(path: string): Promise<Buffer | Uint8Array | null> {\n        const result = localStorage.getItem(path);\n        if (result == null) {\n            return null;\n        }\n        return new TextEncoder().encode(result);\n    },\n    async write(path: string, data: Buffer | Uint8Array): Promise<void> {\n        const array = new Uint8Array(data);\n        const value = new TextDecoder('utf-8').decode(array);\n        localStorage.setItem(path, value);\n    }\n};\n\nfunction memoryIO(): ProtocolIo {\n    let values = new Map();\n    return {\n        async read(path: string): Promise<Buffer | Uint8Array | null> {\n            return values.get(path);\n        },\n        async write(path: string, data: Buffer | Uint8Array): Promise<void> {\n            values.set(path, data);\n        }\n    }\n};\n\nasync function init(): Promise<Worker> {\n    await initThreadPool();\n    if (MainWorker == null) {\n        throw new Error(\"panic: MainWorker is not initialized\");\n    }\n    return MainWorker;\n}\n\nfunction resolveUrl(url: string | (() => string | null)): string {\n    if (typeof url === \"function\") {\n        const resolved = url();\n        if (resolved == null) {\n            throw new Error(\"remoteWritesExperimental requires a non-null URL\");\n        }\n        return resolved;\n    }\n    return url;\n}\n\nclass Database extends DatabasePromise {\n    #runner: Runner;\n    #engine: any;\n    #io: ProtocolIo;\n    #guards: SyncEngineGuards;\n    #worker: Worker | null;\n    #remoteWriter: RemoteWriter | null = null;\n    #db: any;\n    constructor(opts: DatabaseOpts) {\n        if (opts.url == null) {\n            const db = new NativeDatabase(opts.path, { tracing: opts.tracing }) as any;\n            super(\n                db,\n                () => ioNotifier.waitForCompletion(),\n            );\n            this.#db = db;\n            this.#engine = null;\n            return;\n        }\n\n        let partialSyncOpts = undefined;\n        if (opts.partialSyncExperimental != null) {\n            switch (opts.partialSyncExperimental.bootstrapStrategy.kind) {\n                case \"prefix\":\n                    partialSyncOpts = {\n                        bootstrapStrategy: { type: \"Prefix\", length: opts.partialSyncExperimental.bootstrapStrategy.length },\n                        segmentSize: opts.partialSyncExperimental.segmentSize,\n                        prefetch: opts.partialSyncExperimental.prefetch,\n                    };\n                    break;\n                case \"query\":\n                    partialSyncOpts = {\n                        bootstrapStrategy: { type: \"Query\", query: opts.partialSyncExperimental.bootstrapStrategy.query },\n                        segmentSize: opts.partialSyncExperimental.segmentSize,\n                        prefetch: opts.partialSyncExperimental.prefetch,\n                    };\n                    break;\n            }\n        }\n        const engine = new SyncEngine({\n            path: opts.path,\n            clientName: opts.clientName,\n            useTransform: opts.transform != null,\n            protocolVersion: SyncEngineProtocolVersion.V1,\n            longPollTimeoutMs: opts.longPollTimeoutMs,\n            tracing: opts.tracing,\n            bootstrapIfEmpty: typeof opts.url != \"function\" || opts.url() != null,\n            remoteEncryption: opts.remoteEncryption?.cipher,\n            partialSyncOpts: partialSyncOpts\n        });\n\n        let headers: { [K: string]: string } | (() => Promise<{ [K: string]: string }>);\n        if (typeof opts.authToken == \"function\") {\n            const authToken = opts.authToken;\n            headers = async () => ({\n                ...(opts.authToken != null && { \"Authorization\": `Bearer ${await authToken()}` }),\n                ...(opts.remoteEncryption != null && {\n                    \"x-turso-encryption-key\": opts.remoteEncryption.key,\n                    \"x-turso-encryption-cipher\": opts.remoteEncryption.cipher,\n                })\n            });\n        } else {\n            const authToken = opts.authToken;\n            headers = {\n                ...(opts.authToken != null && { \"Authorization\": `Bearer ${authToken}` }),\n                ...(opts.remoteEncryption != null && {\n                    \"x-turso-encryption-key\": opts.remoteEncryption.key,\n                    \"x-turso-encryption-cipher\": opts.remoteEncryption.cipher,\n                })\n            };\n        }\n        const runOpts = {\n            url: opts.url,\n            headers: headers,\n            preemptionMs: 1,\n            transform: opts.transform,\n        };\n        const db = engine.db() as unknown as any;\n        const memory = db.memory;\n        const io = memory ? memoryIO() : BrowserIO;\n        const run = runner(runOpts, io, engine);\n        super(db, () => run.wait());\n\n        this.#db = db;\n        this.#runner = run;\n        this.#engine = engine;\n        this.#io = io;\n        this.#guards = new SyncEngineGuards();\n\n        // Initialize remote writer if remoteWrites is enabled\n        if (opts.remoteWritesExperimental && opts.url) {\n            const url = resolveUrl(opts.url);\n            this.#remoteWriter = new RemoteWriter({\n                url,\n                authToken: opts.authToken,\n                remoteEncryptionKey: opts.remoteEncryption?.key,\n            });\n        }\n    }\n    /**\n     * connect database and initialize it in case of clean start\n     */\n    override async connect() {\n        if (this.connected) {\n            return;\n        } else if (this.#engine == null) {\n            if (!this.memory) {\n                const worker = await init();\n                await Promise.all([\n                    registerFileAtWorker(worker, this.name),\n                    registerFileAtWorker(worker, `${this.name}-wal`)\n                ]);\n                this.#worker = worker;\n            }\n            await super.connect();\n        } else {\n            if (!this.memory) {\n                this.#worker = await init();\n                await Promise.all([\n                    registerFileAtWorker(this.#worker, this.name),\n                    registerFileAtWorker(this.#worker, `${this.name}-wal`),\n                    registerFileAtWorker(this.#worker, `${this.name}-wal-revert`),\n                    registerFileAtWorker(this.#worker, `${this.name}-info`),\n                    registerFileAtWorker(this.#worker, `${this.name}-changes`),\n                ]);\n            }\n            await run(this.#runner, this.#engine.connect());\n        }\n        this.connected = true;\n    }\n    /**\n     * pull new changes from the remote database\n     * if {@link DatabaseOpts.longPollTimeoutMs} is set - then server will hold the connection open until either new changes will appear in the database or timeout occurs.\n     * @returns true if new changes were pulled from the remote\n     */\n    async pull() {\n        if (this.#engine == null) {\n            throw new Error(\"sync is disabled as database was opened without sync support\")\n        }\n        const changes = await this.#guards.wait(async () => await run(this.#runner, this.#engine.wait()));\n        if (changes.empty()) {\n            return false;\n        }\n        await this.#guards.apply(async () => await run(this.#runner, this.#engine.apply(changes)));\n        return true;\n    }\n    /**\n     * push new local changes to the remote database\n     * if {@link DatabaseOpts.transform} is set - then provided callback will be called for every mutation before sending it to the remote\n     */\n    async push() {\n        if (this.#engine == null) {\n            throw new Error(\"sync is disabled as database was opened without sync support\")\n        }\n        await this.#guards.push(async () => await run(this.#runner, this.#engine.push()));\n    }\n    /**\n     * checkpoint WAL for local database\n     */\n    async checkpoint() {\n        if (this.#engine == null) {\n            throw new Error(\"sync is disabled as database was opened without sync support\")\n        }\n        await this.#guards.checkpoint(async () => await run(this.#runner, this.#engine.checkpoint()));\n    }\n    /**\n     * @returns statistic of current local database\n     */\n    async stats(): Promise<DatabaseStats> {\n        if (this.#engine == null) {\n            throw new Error(\"sync is disabled as database was opened without sync support\")\n        }\n        return (await run(this.#runner, this.#engine.stats()));\n    }\n\n    /**\n     * Executes the given SQL string.\n     * When remoteWrites is enabled, write statements are sent to the remote server.\n     */\n    override async exec(sql: string) {\n        if (!this.#remoteWriter) return super.exec(sql);\n        const category = this.#db.classifySql(sql);\n\n        if (this.#remoteWriter.isInTransaction) {\n            const { shouldPull } = await this.#remoteWriter.execRemote(sql, category);\n            if (shouldPull) await this.pull();\n            return;\n        }\n\n        if (category === \"read\") return super.exec(sql);\n\n        const { shouldPull } = await this.#remoteWriter.execRemote(sql, category);\n        if (shouldPull) await this.pull();\n    }\n\n    /**\n     * Prepares a SQL statement for execution.\n     * When remoteWrites is enabled, returns a wrapper that routes writes to remote.\n     */\n    override prepare(sql: string) {\n        const localStmt = super.prepare(sql);\n\n        if (!this.#remoteWriter) {\n            return localStmt;\n        }\n\n        const category = this.#db.classifySql(sql);\n        const isReadonly = category === \"read\";\n        return new RemoteWriteStatement(\n            localStmt,\n            sql,\n            isReadonly,\n            this.#remoteWriter,\n            () => this.pull(),\n        ) as any;\n    }\n\n    /**\n     * Returns a function that executes the given function in a transaction.\n     * When remoteWrites is enabled, the entire transaction goes to remote.\n     */\n    override transaction(fn: (...any) => Promise<any>) {\n        if (typeof fn !== \"function\")\n            throw new TypeError(\"Expected first argument to be a function\");\n\n        if (!this.#remoteWriter) {\n            return super.transaction(fn);\n        }\n\n        const db = this;\n        const remoteWriter = this.#remoteWriter;\n        const wrapTxn = (mode: string) => {\n            return async (...bindParameters: any[]) => {\n                await remoteWriter.beginTransaction(mode);\n                try {\n                    const result = await fn(...bindParameters);\n                    await remoteWriter.commitTransaction();\n                    await db.pull();\n                    return result;\n                } catch (err) {\n                    await remoteWriter.rollbackTransaction();\n                    throw err;\n                }\n            };\n        };\n        const properties = {\n            default: { value: wrapTxn(\"\") },\n            deferred: { value: wrapTxn(\"DEFERRED\") },\n            immediate: { value: wrapTxn(\"IMMEDIATE\") },\n            exclusive: { value: wrapTxn(\"EXCLUSIVE\") },\n            database: { value: this, enumerable: true },\n        };\n        Object.defineProperties(properties.default.value, properties);\n        Object.defineProperties(properties.deferred.value, properties);\n        Object.defineProperties(properties.immediate.value, properties);\n        Object.defineProperties(properties.exclusive.value, properties);\n        return properties.default.value;\n    }\n\n    /**\n     * close the database and relevant files\n     */\n    async close() {\n        if (this.#remoteWriter) {\n            await this.#remoteWriter.close();\n        }\n        if (this.#engine != null) {\n            if (this.name != null && this.#worker != null) {\n                await Promise.all([\n                    unregisterFileAtWorker(this.#worker, this.name),\n                    unregisterFileAtWorker(this.#worker, `${this.name}-wal`),\n                    unregisterFileAtWorker(this.#worker, `${this.name}-wal-revert`),\n                    unregisterFileAtWorker(this.#worker, `${this.name}-info`),\n                    unregisterFileAtWorker(this.#worker, `${this.name}-changes`),\n                ]);\n            }\n            this.#engine.close();\n        }\n        await super.close();\n    }\n}\n\n/**\n * Creates a new database connection asynchronously.\n *\n * @param {Object} opts - Options for database behavior.\n * @returns {Promise<Database>} - A promise that resolves to a Database instance.\n */\nasync function connect(opts: DatabaseOpts): Promise<Database> {\n    const db = new Database(opts);\n    await db.connect();\n    return db;\n}\n\nexport { connect, Database }\nexport type { DatabaseOpts, EncryptionOpts, DatabaseRowMutation, DatabaseRowStatement, DatabaseRowTransformResult }\n"
  },
  {
    "path": "bindings/javascript/sync/packages/wasm/promise.test.ts",
    "content": "import { expect, test, afterAll } from 'vitest'\nimport { Database, connect, DatabaseRowMutation, DatabaseRowTransformResult } from './promise-default.js'\nimport { MainWorker } from './index-default.js'\n\nafterAll(() => {\n    MainWorker?.terminate();\n})\n\nconst localeCompare = (a, b) => a.x.localeCompare(b.x);\nconst intCompare = (a, b) => a.x - b.x;\n\ntest('open non-sync db', async () => {\n    const db = await connect({ path: 'local.db' });\n    await db.exec(\"CREATE TABLE t(x)\");\n    await db.exec(\"INSERT INTO t VALUES (1), (2), (3)\");\n    expect(await (await db.prepare(\"SELECT * FROM t\").all())).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }])\n})\n\ntest('checkpoint-and-actions', async () => {\n    {\n        const db = await connect({\n            path: ':memory:',\n            url: process.env.VITE_TURSO_DB_URL,\n            longPollTimeoutMs: 100,\n        });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS rows(key TEXT PRIMARY KEY, value INTEGER)\");\n        await db.exec(\"DELETE FROM rows\");\n        await db.exec(\"INSERT INTO rows VALUES ('key', 0)\");\n        await db.push();\n        await db.close();\n    }\n    const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n    await db1.exec(\"PRAGMA busy_timeout=100\");\n    console.info('run_info', await db1.prepare(\"SELECT * FROM sqlite_master\").all());\n    const pull = async function (iterations: number) {\n        for (let i = 0; i < iterations; i++) {\n            console.info('pull', i);\n            try {\n                await db1.pull();\n            }\n            catch (e) { console.error('pull', e); }\n            await new Promise(resolve => setTimeout(resolve, 0));\n        }\n    }\n    const push = async function (iterations: number) {\n        for (let i = 0; i < iterations; i++) {\n            await new Promise(resolve => setTimeout(resolve, 10));\n            console.info('push', i);\n            try {\n                if ((await db1.stats()).cdcOperations > 0) {\n                    const start = performance.now();\n                    await db1.push();\n                    console.info('push', performance.now() - start);\n                }\n            }\n            catch (e) { console.error('push', e); }\n        }\n    }\n    const run = async function (iterations: number) {\n        let rows = 0;\n        for (let i = 0; i < iterations; i++) {\n            console.info('run', i, rows);\n            await db1.prepare(\"UPDATE rows SET value = value + 1 WHERE key = ?\").run('key');\n            rows += 1;\n            const { cnt } = await db1.prepare(\"SELECT value as cnt FROM rows WHERE key = ?\").get(['key']);\n            expect(cnt).toBe(rows);\n            await new Promise(resolve => setTimeout(resolve, 1));\n        }\n    }\n    await Promise.all([pull(20), push(20), run(1000)]);\n})\n\ntest('implicit connect', async () => {\n    const db = new Database({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n    const defer = db.prepare(\"SELECT * FROM not_found\");\n    await expect(async () => await defer.all()).rejects.toThrowError(/no such table: not_found/);\n    expect(() => db.prepare(\"SELECT * FROM not_found\")).toThrowError(/no such table: not_found/);\n    expect(await db.prepare(\"SELECT 1 as x\").all()).toEqual([{ x: 1 }]);\n})\n\ntest('simple-db', async () => {\n    const db = new Database({ path: ':memory:' });\n    expect(await db.prepare(\"SELECT 1 as x\").all()).toEqual([{ x: 1 }])\n    await db.exec(\"CREATE TABLE t(x)\");\n    await db.exec(\"INSERT INTO t VALUES (1), (2), (3)\");\n    expect(await db.prepare(\"SELECT * FROM t\").all()).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }])\n    await expect(async () => await db.pull()).rejects.toThrowError(/sync is disabled as database was opened without sync support/);\n})\n\ntest('reconnect-db', async () => {\n    {\n        const db = await connect({ path: 'local.db', url: process.env.VITE_TURSO_DB_URL });\n        const stmt = db.prepare(\"SELECT * FROM turso_cdc\");\n        expect(await stmt.all()).toEqual([])\n        stmt.close();\n    }\n    {\n        const db = await connect({ path: 'local.db', url: process.env.VITE_TURSO_DB_URL });\n        const stmt = db.prepare(\"SELECT * FROM turso_cdc\");\n        expect(await stmt.all()).toEqual([])\n        stmt.close();\n    }\n})\n\ntest('implicit connect', async () => {\n    const db = new Database({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n    const defer = db.prepare(\"SELECT * FROM not_found\");\n    await expect(async () => await defer.all()).rejects.toThrowError(/no such table: not_found/);\n    expect(() => db.prepare(\"SELECT * FROM not_found\")).toThrowError(/no such table: not_found/);\n    expect(await db.prepare(\"SELECT 1 as x\").all()).toEqual([{ x: 1 }]);\n})\n\ntest('defered sync', async () => {\n    {\n        const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS t(x)\");\n        await db.exec(\"DELETE FROM t\");\n        await db.exec(\"INSERT INTO t VALUES (100)\");\n        await db.push();\n        await db.close();\n    }\n\n    let url = null;\n    const db = new Database({ path: ':memory:', url: () => url });\n    await db.prepare(\"CREATE TABLE t(x)\").run();\n    await db.prepare(\"INSERT INTO t VALUES (1), (2), (3), (42)\").run();\n    expect(await db.prepare(\"SELECT * FROM t\").all()).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }, { x: 42 }]);\n    await expect(async () => await db.pull()).rejects.toThrow(/url is empty - sync is paused/);\n    url = process.env.VITE_TURSO_DB_URL;\n    await db.pull();\n    expect(await db.prepare(\"SELECT * FROM t\").all()).toEqual([{ x: 100 }, { x: 1 }, { x: 2 }, { x: 3 }, { x: 42 }]);\n})\n\ntest('encryption sync', async () => {\n    const KEY = 'l/FWopMfZisTLgBX4A42AergrCrYKjiO3BfkJUwv83I=';\n    const URL = 'http://encrypted--a--a.localhost:10000';\n    {\n        const db = await connect({ path: ':memory:', url: URL, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS t(x)\");\n        await db.exec(\"DELETE FROM t\");\n        await db.push();\n        await db.close();\n    }\n    const db1 = await connect({ path: ':memory:', url: URL, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } });\n    const db2 = await connect({ path: ':memory:', url: URL, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } });\n    await db1.exec(\"INSERT INTO t VALUES (1), (2), (3)\");\n    await db2.exec(\"INSERT INTO t VALUES (4), (5), (6)\");\n    expect(await db1.prepare(\"SELECT * FROM t\").all()).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }]);\n    expect(await db2.prepare(\"SELECT * FROM t\").all()).toEqual([{ x: 4 }, { x: 5 }, { x: 6 }]);\n    await Promise.all([db1.push(), db2.push()]);\n    await Promise.all([db1.pull(), db2.pull()]);\n    const expected = [{ x: 1 }, { x: 2 }, { x: 3 }, { x: 4 }, { x: 5 }, { x: 6 }];\n    expect((await db1.prepare(\"SELECT * FROM t\").all()).sort(intCompare)).toEqual(expected.sort(intCompare));\n    expect((await db2.prepare(\"SELECT * FROM t\").all()).sort(intCompare)).toEqual(expected.sort(intCompare));\n});\n\ntest('defered encryption sync', async () => {\n    const URL = 'http://encrypted--a--a.localhost:10000';\n    const KEY = 'l/FWopMfZisTLgBX4A42AergrCrYKjiO3BfkJUwv83I=';\n    let url = null;\n    {\n        const db = await connect({ path: ':memory:', url: URL, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS t(x)\");\n        await db.exec(\"DELETE FROM t\");\n        await db.exec(\"INSERT INTO t VALUES (100)\");\n        await db.push();\n        await db.close();\n    }\n    const db = await connect({ path: ':memory:', url: () => url, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } });\n    await db.exec(\"CREATE TABLE IF NOT EXISTS t(x)\");\n    await db.exec(\"INSERT INTO t VALUES (1), (2), (3)\");\n    expect(await db.prepare(\"SELECT * FROM t\").all()).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }]);\n\n    url = URL;\n    await db.pull();\n\n    const expected = [{ x: 100 }, { x: 1 }, { x: 2 }, { x: 3 }];\n    expect((await db.prepare(\"SELECT * FROM t\").all())).toEqual(expected);\n});\n\ntest('select-after-push', async () => {\n    {\n        const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS t(x)\");\n        await db.exec(\"DELETE FROM t\");\n        await db.push();\n        await db.close();\n    }\n    {\n        const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n        await db.exec(\"INSERT INTO t VALUES (1), (2), (3)\");\n        await db.push();\n    }\n    {\n        const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n        const rows = await db.prepare('SELECT * FROM t').all();\n        expect(rows).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }])\n    }\n})\n\ntest('select-without-push', async () => {\n    {\n        const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS t(x)\");\n        await db.exec(\"DELETE FROM t\");\n        await db.push();\n        await db.close();\n    }\n    {\n        const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n        await db.exec(\"INSERT INTO t VALUES (1), (2), (3)\");\n    }\n    {\n        const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n        const rows = await db.prepare('SELECT * FROM t').all();\n        expect(rows).toEqual([])\n    }\n})\n\ntest('merge-non-overlapping-keys', async () => {\n    {\n        const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)\");\n        await db.exec(\"DELETE FROM q\");\n        await db.push();\n        await db.close();\n    }\n    const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n    await db1.exec(\"INSERT INTO q VALUES ('k1', 'value1'), ('k2', 'value2')\");\n\n    const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n    await db2.exec(\"INSERT INTO q VALUES ('k3', 'value3'), ('k4', 'value4'), ('k5', 'value5')\");\n\n    await Promise.all([db1.push(), db2.push()]);\n    await Promise.all([db1.pull(), db2.pull()]);\n\n    const rows1 = await db1.prepare('SELECT * FROM q').all();\n    const rows2 = await db1.prepare('SELECT * FROM q').all();\n    const expected = [{ x: 'k1', y: 'value1' }, { x: 'k2', y: 'value2' }, { x: 'k3', y: 'value3' }, { x: 'k4', y: 'value4' }, { x: 'k5', y: 'value5' }];\n    expect(rows1.sort(localeCompare)).toEqual(expected.sort(localeCompare))\n    expect(rows2.sort(localeCompare)).toEqual(expected.sort(localeCompare))\n})\n\ntest('last-push-wins', async () => {\n    {\n        const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)\");\n        await db.exec(\"DELETE FROM q\");\n        await db.push();\n        await db.close();\n    }\n    const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n    await db1.exec(\"INSERT INTO q VALUES ('k1', 'value1'), ('k2', 'value2'), ('k4', 'value4')\");\n\n    const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n    await db2.exec(\"INSERT INTO q VALUES ('k1', 'value3'), ('k2', 'value4'), ('k3', 'value5')\");\n\n    await db2.push();\n    await db1.push();\n    await Promise.all([db1.pull(), db2.pull()]);\n\n    const rows1 = await db1.prepare('SELECT * FROM q').all();\n    const rows2 = await db1.prepare('SELECT * FROM q').all();\n    const expected = [{ x: 'k1', y: 'value1' }, { x: 'k2', y: 'value2' }, { x: 'k3', y: 'value5' }, { x: 'k4', y: 'value4' }];\n    expect(rows1.sort(localeCompare)).toEqual(expected.sort(localeCompare))\n    expect(rows2.sort(localeCompare)).toEqual(expected.sort(localeCompare))\n})\n\ntest('last-push-wins-with-delete', async () => {\n    {\n        const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)\");\n        await db.exec(\"DELETE FROM q\");\n        await db.push();\n        await db.close();\n    }\n    const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n    await db1.exec(\"INSERT INTO q VALUES ('k1', 'value1'), ('k2', 'value2'), ('k4', 'value4')\");\n    await db1.exec(\"DELETE FROM q\")\n\n    const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n    await db2.exec(\"INSERT INTO q VALUES ('k1', 'value3'), ('k2', 'value4'), ('k3', 'value5')\");\n\n    await db2.push();\n    await db1.push();\n    await Promise.all([db1.pull(), db2.pull()]);\n\n    const rows1 = await db1.prepare('SELECT * FROM q').all();\n    const rows2 = await db1.prepare('SELECT * FROM q').all();\n    const expected = [{ x: 'k3', y: 'value5' }];\n    expect(rows1).toEqual(expected)\n    expect(rows2).toEqual(expected)\n})\n\ntest('constraint-conflict', async () => {\n    {\n        const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS u(x TEXT PRIMARY KEY, y UNIQUE)\");\n        await db.exec(\"DELETE FROM u\");\n        await db.push();\n        await db.close();\n    }\n    const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n    await db1.exec(\"INSERT INTO u VALUES ('k1', 'value1')\");\n\n    const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n    await db2.exec(\"INSERT INTO u VALUES ('k2', 'value1')\");\n\n    await db1.push();\n    await expect(async () => await db2.push()).rejects.toThrow('SQLite error: UNIQUE constraint failed: u.y');\n})\n\ntest('checkpoint', async () => {\n    {\n        const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)\");\n        await db.exec(\"DELETE FROM q\");\n        await db.push();\n        await db.close();\n    }\n    const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n    for (let i = 0; i < 1000; i++) {\n        await db1.exec(`INSERT INTO q VALUES ('k${i}', 'v${i}')`);\n    }\n    expect((await db1.stats()).mainWalSize).toBeGreaterThan(4096 * 1000);\n    await db1.checkpoint();\n    expect((await db1.stats()).mainWalSize).toBe(0);\n    let revertWal = (await db1.stats()).revertWalSize;\n    expect(revertWal).toBeLessThan(4096 * 1000 / 100);\n\n    for (let i = 0; i < 1000; i++) {\n        await db1.exec(`UPDATE q SET y = 'u${i}' WHERE x = 'k${i}'`);\n    }\n    await db1.checkpoint();\n    expect((await db1.stats()).revertWalSize).toBe(revertWal);\n})\n\ntest('persistence-push', async () => {\n    {\n        const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)\");\n        await db.exec(\"DELETE FROM q\");\n        await db.push();\n        await db.close();\n    }\n    const path = `test-${(Math.random() * 10000) | 0}.db`;\n    {\n        const db1 = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL });\n        await db1.exec(`INSERT INTO q VALUES ('k1', 'v1')`);\n        await db1.exec(`INSERT INTO q VALUES ('k2', 'v2')`);\n        await db1.close();\n    }\n\n    {\n        const db2 = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL });\n        await db2.exec(`INSERT INTO q VALUES ('k3', 'v3')`);\n        await db2.exec(`INSERT INTO q VALUES ('k4', 'v4')`);\n        const stmt = db2.prepare('SELECT * FROM q');\n        const rows = await stmt.all();\n        const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }, { x: 'k3', y: 'v3' }, { x: 'k4', y: 'v4' }];\n        expect(rows).toEqual(expected)\n        stmt.close();\n        await db2.close();\n    }\n\n    {\n        const db3 = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL });\n        await db3.push();\n        await db3.close();\n    }\n\n    {\n        const db4 = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL });\n        const rows = await db4.prepare('SELECT * FROM q').all();\n        const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }, { x: 'k3', y: 'v3' }, { x: 'k4', y: 'v4' }];\n        expect(rows).toEqual(expected)\n        await db4.close();\n    }\n})\n\ntest('persistence-offline', async () => {\n    {\n        const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)\");\n        await db.exec(\"DELETE FROM q\");\n        await db.push();\n        await db.close();\n    }\n    const path = `test-${(Math.random() * 10000) | 0}.db`;\n    {\n        const db = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL });\n        await db.exec(`INSERT INTO q VALUES ('k1', 'v1')`);\n        await db.exec(`INSERT INTO q VALUES ('k2', 'v2')`);\n        await db.push();\n        await db.close();\n    }\n    {\n        const db = await connect({ path: path, url: \"https://not-valid-url.localhost\" });\n        const rows = await db.prepare(\"SELECT * FROM q\").all();\n        const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }];\n        expect(rows.sort(localeCompare)).toEqual(expected.sort(localeCompare))\n        await db.close();\n    }\n})\n\ntest('persistence-pull-push', async () => {\n    {\n        const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)\");\n        await db.exec(\"DELETE FROM q\");\n        await db.push();\n        await db.close();\n    }\n    const path1 = `test-${(Math.random() * 10000) | 0}.db`;\n    const path2 = `test-${(Math.random() * 10000) | 0}.db`;\n    const db1 = await connect({ path: path1, url: process.env.VITE_TURSO_DB_URL });\n    await db1.exec(`INSERT INTO q VALUES ('k1', 'v1')`);\n    await db1.exec(`INSERT INTO q VALUES ('k2', 'v2')`);\n    const stats1 = await db1.stats();\n\n    const db2 = await connect({ path: path2, url: process.env.VITE_TURSO_DB_URL });\n    await db2.exec(`INSERT INTO q VALUES ('k3', 'v3')`);\n    await db2.exec(`INSERT INTO q VALUES ('k4', 'v4')`);\n\n    await Promise.all([db1.push(), db2.push()]);\n    await Promise.all([db1.pull(), db2.pull()]);\n    const stats2 = await db1.stats();\n    console.info(stats1, stats2);\n    expect(stats1.revision).not.toBe(stats2.revision);\n\n    const rows1 = await db1.prepare('SELECT * FROM q').all();\n    const rows2 = await db2.prepare('SELECT * FROM q').all();\n    const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }, { x: 'k3', y: 'v3' }, { x: 'k4', y: 'v4' }];\n    expect(rows1.sort(localeCompare)).toEqual(expected.sort(localeCompare))\n    expect(rows2.sort(localeCompare)).toEqual(expected.sort(localeCompare))\n})\n\ntest('pull-push-concurrent', async () => {\n    {\n        const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, longPollTimeoutMs: 5000 });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)\");\n        await db.exec(\"DELETE FROM q\");\n        await db.push();\n        await db.close();\n    }\n    let pullResolve = null;\n    const pullFinish = new Promise(resolve => pullResolve = resolve);\n    let pushResolve = null;\n    const pushFinish = new Promise(resolve => pushResolve = resolve);\n    let stopPull = false;\n    let stopPush = false;\n    const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });\n    let pull = async () => {\n        try {\n            await db.pull();\n        } catch (e) {\n            console.error('pull', e);\n        } finally {\n            if (!stopPull) {\n                setTimeout(pull, 0);\n            } else {\n                pullResolve()\n            }\n        }\n    }\n    let push = async () => {\n        try {\n            if ((await db.stats()).cdcOperations > 0) {\n                await db.push();\n            }\n        } catch (e) {\n            console.error('push', e);\n        } finally {\n            if (!stopPush) {\n                setTimeout(push, 0);\n            } else {\n                pushResolve();\n            }\n        }\n    }\n    setTimeout(pull, 0);\n    setTimeout(push, 0);\n    for (let i = 0; i < 1000; i++) {\n        await db.exec(`INSERT INTO q VALUES ('k${i}', 'v${i}')`);\n    }\n    await new Promise(resolve => setTimeout(resolve, 1000));\n    stopPush = true;\n    await pushFinish;\n    stopPull = true;\n    await pullFinish;\n    console.info(await db.stats());\n})\n\ntest('concurrent-updates', { timeout: 60000 }, async () => {\n    {\n        const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, longPollTimeoutMs: 10 });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS three(x TEXT PRIMARY KEY, y, z)\");\n        await db.exec(\"DELETE FROM three\");\n        await db.push();\n        await db.close();\n    }\n    let stop = false;\n    const dbs = [];\n    for (let i = 0; i < 8; i++) {\n        dbs.push(await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }));\n    }\n    async function pull(db, i) {\n        try {\n            await db.pull();\n        } catch (e) {\n            console.error('pull', i, e);\n        } finally {\n            if (!stop) {\n                setTimeout(async () => await pull(db, i), 0);\n            }\n        }\n    }\n    async function push(db, i) {\n        try {\n            await db.push();\n        } catch (e) {\n            console.error('push', i, e);\n        } finally {\n            if (!stop) {\n                setTimeout(async () => await push(db, i), 0);\n            }\n        }\n    }\n    for (let i = 0; i < dbs.length; i++) {\n        setTimeout(async () => await pull(dbs[i], i), 0)\n        setTimeout(async () => await push(dbs[i], i), 0)\n    }\n    for (let i = 0; i < 1000; i++) {\n        try {\n            const tasks = [];\n            for (let s = 0; s < dbs.length; s++) {\n                tasks.push(dbs[s].exec(`INSERT INTO three VALUES ('${s}', 0, randomblob(128)) ON CONFLICT DO UPDATE SET y = y + 1, z = randomblob(128)`));\n            }\n            await Promise.all(tasks);\n        } catch (e) {\n            // ignore\n        }\n        await new Promise(resolve => setTimeout(resolve, 1));\n    }\n    stop = true;\n    await Promise.all(dbs.map(db => db.push()));\n    await Promise.all(dbs.map(db => db.pull()));\n    let results = [];\n    for (let i = 0; i < dbs.length; i++) {\n        results.push(await dbs[i].prepare('SELECT x, y FROM three').all());\n    }\n    for (let i = 0; i < dbs.length; i++) {\n        expect(results[i]).toEqual(results[0]);\n        for (let s = 0; s < dbs.length; s++) {\n            expect(results[i][s].y).toBeGreaterThan(500);\n        }\n    }\n})\n\ntest('transform', async () => {\n    {\n        const db = await connect({\n            path: ':memory:',\n            url: process.env.VITE_TURSO_DB_URL,\n        });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS counter(key TEXT PRIMARY KEY, value INTEGER)\");\n        await db.exec(\"DELETE FROM counter\");\n        await db.exec(\"INSERT INTO counter VALUES ('1', 0)\")\n        await db.push();\n        await db.close();\n    }\n    const transform = (m: DatabaseRowMutation) => ({\n        operation: 'rewrite',\n        stmt: {\n            sql: `UPDATE counter SET value = value + ? WHERE key = ?`,\n            values: [m.after.value - m.before.value, m.after.key]\n        }\n    } as DatabaseRowTransformResult);\n    const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, transform: transform });\n    const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, transform: transform });\n\n    await db1.exec(\"UPDATE counter SET value = value + 1 WHERE key = '1'\");\n    await db2.exec(\"UPDATE counter SET value = value + 1 WHERE key = '1'\");\n\n    await Promise.all([db1.push(), db2.push()]);\n    await Promise.all([db1.pull(), db2.pull()]);\n\n    const rows1 = await db1.prepare('SELECT * FROM counter').all();\n    const rows2 = await db2.prepare('SELECT * FROM counter').all();\n    expect(rows1).toEqual([{ key: '1', value: 2 }]);\n    expect(rows2).toEqual([{ key: '1', value: 2 }]);\n})\n\ntest('transform-many', async () => {\n    {\n        const db = await connect({\n            path: ':memory:',\n            url: process.env.VITE_TURSO_DB_URL,\n        });\n        await db.exec(\"CREATE TABLE IF NOT EXISTS counter(key TEXT PRIMARY KEY, value INTEGER)\");\n        await db.exec(\"DELETE FROM counter\");\n        await db.exec(\"INSERT INTO counter VALUES ('1', 0)\")\n        await db.push();\n        await db.close();\n    }\n    const transform = (m: DatabaseRowMutation) => ({\n        operation: 'rewrite',\n        stmt: {\n            sql: `UPDATE counter SET value = value + ? WHERE key = ?`,\n            values: [m.after.value - m.before.value, m.after.key]\n        }\n    } as DatabaseRowTransformResult);\n    const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, transform: transform });\n    const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, transform: transform });\n\n    for (let i = 0; i < 1002; i++) {\n        await db1.exec(\"UPDATE counter SET value = value + 1 WHERE key = '1'\");\n    }\n    for (let i = 0; i < 1001; i++) {\n        await db2.exec(\"UPDATE counter SET value = value + 1 WHERE key = '1'\");\n    }\n\n    let start = performance.now();\n    await Promise.all([db1.push(), db2.push()]);\n    console.info('push', performance.now() - start);\n\n    start = performance.now();\n    await Promise.all([db1.pull(), db2.pull()]);\n    console.info('pull', performance.now() - start);\n\n    const rows1 = await db1.prepare('SELECT * FROM counter').all();\n    const rows2 = await db2.prepare('SELECT * FROM counter').all();\n    expect(rows1).toEqual([{ key: '1', value: 1001 + 1002 }]);\n    expect(rows2).toEqual([{ key: '1', value: 1001 + 1002 }]);\n})\n"
  },
  {
    "path": "bindings/javascript/sync/packages/wasm/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"skipLibCheck\": true,\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"module\": \"nodenext\",\n    \"target\": \"esnext\",\n    \"moduleResolution\": \"nodenext\",\n    \"outDir\": \"dist/\",\n    \"lib\": [\n      \"es2020\",\n      \"DOM\",\n      \"WebWorker\"\n    ],\n    \"paths\": {\n      \"#index\": [\n        \"./index.js\"\n      ]\n    }\n  },\n  \"include\": [\n    \"*\"\n  ]\n}"
  },
  {
    "path": "bindings/javascript/sync/packages/wasm/vite.config.js",
    "content": "import { resolve } from 'path';\nimport { defineConfig } from 'vite';\n\nexport default defineConfig({\n  base: './',\n  build: {\n    lib: {\n      entry: resolve(__dirname, 'promise-bundle.ts'),\n      name: 'sync-wasm',\n      fileName: format => `main.${format}.js`,\n      formats: ['es'],\n    },\n    rollupOptions: {\n      output: {\n        dir: 'bundle',\n      }\n    },\n  },\n});\n"
  },
  {
    "path": "bindings/javascript/sync/packages/wasm/vitest.config.ts",
    "content": "import { defineConfig } from 'vitest/config'\n\nexport default defineConfig({\n  define: {\n    'process.env.NODE_DEBUG_NATIVE': 'false',\n  },\n  server: {\n    headers: {\n      \"Cross-Origin-Embedder-Policy\": \"require-corp\",\n      \"Cross-Origin-Opener-Policy\": \"same-origin\"\n    },\n  },\n  test: {\n    browser: {\n      enabled: true,\n      provider: 'playwright',\n      instances: [\n        { browser: 'chromium' },\n        { browser: 'firefox' }\n      ],\n    },\n  },\n})\n"
  },
  {
    "path": "bindings/javascript/sync/packages/wasm/wasm-inline.ts",
    "content": "const tursoWasmBase64 = '__PLACEHOLDER__';\nasync function convertBase64ToBinary(base64Url: string): Promise<ArrayBuffer> {\n    const blob = await fetch(base64Url).then(res => res.blob());\n    return await blob.arrayBuffer();\n}\n\nexport async function tursoWasm(): Promise<ArrayBuffer> {\n    return await convertBase64ToBinary(tursoWasmBase64);\n}"
  },
  {
    "path": "bindings/javascript/sync/packages/wasm/worker.ts",
    "content": "import { setupWebWorker } from \"@tursodatabase/database-wasm-common\";\nsetupWebWorker();\n"
  },
  {
    "path": "bindings/javascript/sync/src/generator.rs",
    "content": "use napi::{bindgen_prelude::AsyncTask, Env, Task};\nuse napi_derive::napi;\nuse std::{\n    future::Future,\n    sync::{Arc, Mutex},\n};\n\nuse turso_sync_engine::types::{DbChangesStatus, SyncEngineIoResult};\n\npub trait Generator {\n    fn resume(&mut self, result: Option<String>) -> napi::Result<GeneratorResponse>;\n}\n\nimpl<F: Future<Output = turso_sync_engine::Result<()>>> Generator\n    for genawaiter::sync::Gen<SyncEngineIoResult, turso_sync_engine::Result<()>, F>\n{\n    fn resume(&mut self, error: Option<String>) -> napi::Result<GeneratorResponse> {\n        let result = match error {\n            Some(err) => Err(turso_sync_engine::errors::Error::DatabaseSyncEngineError(\n                format!(\"JsSyncEngineIo error: {err}\"),\n            )),\n            None => Ok(()),\n        };\n        match self.resume_with(result) {\n            genawaiter::GeneratorState::Yielded(SyncEngineIoResult::IO) => {\n                Ok(GeneratorResponse::IO)\n            }\n            genawaiter::GeneratorState::Complete(Ok(())) => Ok(GeneratorResponse::Done),\n            genawaiter::GeneratorState::Complete(Err(err)) => Err(napi::Error::new(\n                napi::Status::GenericFailure,\n                format!(\"sync engine operation failed: {err}\"),\n            )),\n        }\n    }\n}\n\n#[napi]\npub struct SyncEngineChanges {\n    pub(crate) status: Box<Option<DbChangesStatus>>,\n}\n\n#[napi(discriminant = \"type\", object_from_js = false)]\npub enum GeneratorResponse {\n    IO,\n    Done,\n    SyncEngineStats {\n        cdc_operations: i64,\n        main_wal_size: i64,\n        revert_wal_size: i64,\n        last_pull_unix_time: Option<i64>,\n        last_push_unix_time: Option<i64>,\n        revision: Option<String>,\n        network_sent_bytes: i64,\n        network_received_bytes: i64,\n    },\n    SyncEngineChanges {\n        changes: SyncEngineChanges,\n    },\n}\n\n#[napi]\nimpl SyncEngineChanges {\n    #[napi]\n    pub fn empty(&self) -> bool {\n        let Some(changes) = self.status.as_ref() else {\n            return true;\n        };\n        changes.file_slot.is_none()\n    }\n}\n\n#[napi]\n#[derive(Clone)]\npub struct GeneratorHolder {\n    pub(crate) generator: Arc<Mutex<dyn Generator>>,\n    pub(crate) response: Arc<Mutex<Option<GeneratorResponse>>>,\n}\n\npub struct ResumeTask {\n    holder: GeneratorHolder,\n    error: Option<String>,\n}\n\nunsafe impl Send for ResumeTask {}\n\nimpl Task for ResumeTask {\n    type Output = GeneratorResponse;\n    type JsValue = GeneratorResponse;\n\n    fn compute(&mut self) -> napi::Result<Self::Output> {\n        resume_sync(&self.holder, self.error.take())\n    }\n\n    fn resolve(&mut self, _: Env, output: Self::Output) -> napi::Result<Self::JsValue> {\n        Ok(output)\n    }\n}\n\nfn resume_sync(holder: &GeneratorHolder, error: Option<String>) -> napi::Result<GeneratorResponse> {\n    let result = holder.generator.lock().unwrap().resume(error)?;\n    if let GeneratorResponse::Done = result {\n        let response = holder.response.lock().unwrap().take();\n        Ok(response.unwrap_or(GeneratorResponse::Done))\n    } else {\n        Ok(result)\n    }\n}\n\n#[napi]\nimpl GeneratorHolder {\n    #[napi]\n    pub fn resume_sync(&self, error: Option<String>) -> napi::Result<GeneratorResponse> {\n        resume_sync(self, error)\n    }\n\n    #[napi]\n    pub fn resume_async(&self, error: Option<String>) -> napi::Result<AsyncTask<ResumeTask>> {\n        Ok(AsyncTask::new(ResumeTask {\n            holder: self.clone(),\n            error,\n        }))\n    }\n}\n"
  },
  {
    "path": "bindings/javascript/sync/src/js_protocol_io.rs",
    "content": "#![deny(clippy::all)]\n\nuse std::{\n    collections::VecDeque,\n    sync::{Arc, Mutex, MutexGuard},\n};\n\nuse napi::bindgen_prelude::*;\nuse napi_derive::napi;\nuse turso_sync_engine::{\n    database_sync_engine_io::{DataCompletion, DataPollResult, SyncEngineIo},\n    types::{DatabaseRowTransformResult, DatabaseStatementReplay},\n};\n\nuse crate::{\n    core_change_type_to_js, core_values_map_to_js, js_value_to_core, DatabaseRowMutationJs,\n    DatabaseRowTransformResultJs,\n};\n\n#[napi]\npub enum JsProtocolRequest {\n    Http {\n        url: Option<String>,\n        method: String,\n        path: String,\n        body: Option<Vec<u8>>,\n        headers: Vec<(String, String)>,\n    },\n    FullRead {\n        path: String,\n    },\n    FullWrite {\n        path: String,\n        content: Vec<u8>,\n    },\n    Transform {\n        mutations: Vec<DatabaseRowMutationJs>,\n    },\n}\n\n#[derive(Clone)]\n#[napi]\npub struct JsDataCompletion(Arc<Mutex<JsDataCompletionInner>>);\n\npub struct JsBytesPollResult(Buffer);\n\nimpl DataPollResult<u8> for JsBytesPollResult {\n    fn data(&self) -> &[u8] {\n        &self.0\n    }\n}\npub struct JsTransformPollResult(Vec<DatabaseRowTransformResult>);\n\nimpl DataPollResult<DatabaseRowTransformResult> for JsTransformPollResult {\n    fn data(&self) -> &[DatabaseRowTransformResult] {\n        &self.0\n    }\n}\n\nstruct JsDataCompletionInner {\n    status: Option<u16>,\n    chunks: VecDeque<Buffer>,\n    transformed: VecDeque<DatabaseRowTransformResult>,\n    finished: bool,\n    err: Option<String>,\n}\n\nimpl JsDataCompletion {\n    fn inner(&self) -> turso_sync_engine::Result<MutexGuard<JsDataCompletionInner>> {\n        let inner = self.0.lock().unwrap();\n        if let Some(err) = &inner.err {\n            return Err(turso_sync_engine::errors::Error::DatabaseSyncEngineError(\n                err.clone(),\n            ));\n        }\n        Ok(inner)\n    }\n}\n\nimpl DataCompletion<u8> for JsDataCompletion {\n    type DataPollResult = JsBytesPollResult;\n\n    fn status(&self) -> turso_sync_engine::Result<Option<u16>> {\n        let inner = self.inner()?;\n        Ok(inner.status)\n    }\n\n    fn poll_data(&self) -> turso_sync_engine::Result<Option<Self::DataPollResult>> {\n        let mut inner = self.inner()?;\n        let chunk = inner.chunks.pop_front();\n        Ok(chunk.map(JsBytesPollResult))\n    }\n\n    fn is_done(&self) -> turso_sync_engine::Result<bool> {\n        let inner = self.inner()?;\n        Ok(inner.finished)\n    }\n}\n\nimpl DataCompletion<DatabaseRowTransformResult> for JsDataCompletion {\n    type DataPollResult = JsTransformPollResult;\n\n    fn status(&self) -> turso_sync_engine::Result<Option<u16>> {\n        let inner = self.inner()?;\n        Ok(inner.status)\n    }\n\n    fn poll_data(&self) -> turso_sync_engine::Result<Option<Self::DataPollResult>> {\n        let mut inner = self.inner()?;\n        let chunk = inner.transformed.drain(..).collect::<Vec<_>>();\n        if chunk.is_empty() {\n            Ok(None)\n        } else {\n            Ok(Some(JsTransformPollResult(chunk)))\n        }\n    }\n\n    fn is_done(&self) -> turso_sync_engine::Result<bool> {\n        let inner = self.inner()?;\n        Ok(inner.finished)\n    }\n}\n\n#[napi]\nimpl JsDataCompletion {\n    #[napi]\n    pub fn poison(&self, err: String) {\n        let mut completion = self.0.lock().unwrap();\n        completion.err = Some(err);\n    }\n\n    #[napi]\n    pub fn status(&self, value: u32) {\n        let mut completion = self.0.lock().unwrap();\n        completion.status = Some(value as u16);\n    }\n\n    #[napi]\n    pub fn push_buffer(&self, value: Buffer) {\n        let mut completion = self.0.lock().unwrap();\n        completion.chunks.push_back(value);\n    }\n\n    #[napi]\n    pub fn push_transform(&self, values: Vec<DatabaseRowTransformResultJs>) {\n        let mut completion = self.0.lock().unwrap();\n        for value in values {\n            completion.transformed.push_back(match value {\n                DatabaseRowTransformResultJs::Keep => DatabaseRowTransformResult::Keep,\n                DatabaseRowTransformResultJs::Skip => DatabaseRowTransformResult::Skip,\n                DatabaseRowTransformResultJs::Rewrite { stmt } => {\n                    DatabaseRowTransformResult::Rewrite(DatabaseStatementReplay {\n                        sql: stmt.sql,\n                        values: stmt.values.into_iter().map(js_value_to_core).collect(),\n                    })\n                }\n            });\n        }\n    }\n\n    #[napi]\n    pub fn done(&self) {\n        let mut completion = self.0.lock().unwrap();\n        completion.finished = true;\n    }\n}\n\n#[napi]\npub struct JsProtocolRequestBytes {\n    request: Arc<Mutex<Option<JsProtocolRequest>>>,\n    completion: JsDataCompletion,\n}\n\n#[napi]\nimpl JsProtocolRequestBytes {\n    #[napi]\n    pub fn request(&self) -> JsProtocolRequest {\n        let mut request = self.request.lock().unwrap();\n        request.take().unwrap()\n    }\n    #[napi]\n    pub fn completion(&self) -> JsDataCompletion {\n        self.completion.clone()\n    }\n}\n\nimpl SyncEngineIo for JsProtocolIo {\n    type DataCompletionBytes = JsDataCompletion;\n    type DataCompletionTransform = JsDataCompletion;\n\n    fn http(\n        &self,\n        url: Option<&str>,\n        method: &str,\n        path: &str,\n        body: Option<Vec<u8>>,\n        headers: &[(&str, &str)],\n    ) -> turso_sync_engine::Result<JsDataCompletion> {\n        Ok(self.add_request(JsProtocolRequest::Http {\n            url: url.map(|x| x.to_string()),\n            method: method.to_string(),\n            path: path.to_string(),\n            body,\n            headers: headers\n                .iter()\n                .map(|x| (x.0.to_string(), x.1.to_string()))\n                .collect(),\n        }))\n    }\n\n    fn full_read(&self, path: &str) -> turso_sync_engine::Result<Self::DataCompletionBytes> {\n        Ok(self.add_request(JsProtocolRequest::FullRead {\n            path: path.to_string(),\n        }))\n    }\n\n    fn full_write(\n        &self,\n        path: &str,\n        content: Vec<u8>,\n    ) -> turso_sync_engine::Result<Self::DataCompletionBytes> {\n        Ok(self.add_request(JsProtocolRequest::FullWrite {\n            path: path.to_string(),\n            content,\n        }))\n    }\n\n    fn transform(\n        &self,\n        mutations: Vec<turso_sync_engine::types::DatabaseRowMutation>,\n    ) -> turso_sync_engine::Result<Self::DataCompletionTransform> {\n        Ok(self.add_request(JsProtocolRequest::Transform {\n            mutations: mutations\n                .into_iter()\n                .filter_map(|mutation| {\n                    let change_type = core_change_type_to_js(mutation.change_type)?;\n                    Some(DatabaseRowMutationJs {\n                        change_time: mutation.change_time as i64,\n                        table_name: mutation.table_name,\n                        id: mutation.id,\n                        change_type,\n                        before: mutation.before.map(core_values_map_to_js),\n                        after: mutation.after.map(core_values_map_to_js),\n                        updates: mutation.updates.map(core_values_map_to_js),\n                    })\n                })\n                .collect(),\n        }))\n    }\n\n    fn add_io_callback(&self, callback: Box<dyn FnMut() -> bool + Send>) {\n        let mut work = self.work.lock().unwrap();\n        work.push_back(callback);\n    }\n\n    fn step_io_callbacks(&self) {\n        let mut items = {\n            let mut work = self.work.lock().unwrap();\n            work.drain(..).collect::<VecDeque<_>>()\n        };\n        let length = items.len();\n        for _ in 0..length {\n            let mut item = items.pop_front().unwrap();\n            if item() {\n                continue;\n            }\n            items.push_back(item);\n        }\n        {\n            let mut work = self.work.lock().unwrap();\n            work.extend(items);\n        }\n    }\n}\n\n#[napi]\npub struct JsProtocolIo {\n    requests: Mutex<Vec<JsProtocolRequestBytes>>,\n    work: Mutex<VecDeque<Box<dyn FnMut() -> bool + Send>>>,\n}\n\nimpl Default for JsProtocolIo {\n    fn default() -> Self {\n        Self {\n            requests: Mutex::new(Vec::new()),\n            work: Mutex::new(VecDeque::new()),\n        }\n    }\n}\n\nimpl JsProtocolIo {\n    pub fn take_request(&self) -> Option<JsProtocolRequestBytes> {\n        self.requests.lock().unwrap().pop()\n    }\n\n    fn add_request(&self, request: JsProtocolRequest) -> JsDataCompletion {\n        let completion = JsDataCompletionInner {\n            chunks: VecDeque::new(),\n            transformed: VecDeque::new(),\n            finished: false,\n            err: None,\n            status: None,\n        };\n        let completion = JsDataCompletion(Arc::new(Mutex::new(completion)));\n\n        let mut requests = self.requests.lock().unwrap();\n        requests.push(JsProtocolRequestBytes {\n            request: Arc::new(Mutex::new(Some(request))),\n            completion: completion.clone(),\n        });\n        completion\n    }\n}\n"
  },
  {
    "path": "bindings/javascript/sync/src/lib.rs",
    "content": "#![allow(clippy::await_holding_lock)]\n#![allow(clippy::type_complexity)]\n\npub mod generator;\npub mod js_protocol_io;\n\nuse std::{\n    collections::HashMap,\n    sync::{Arc, Mutex, RwLock, RwLockReadGuard},\n};\n\nuse napi::bindgen_prelude::{AsyncTask, Either5, Null};\nuse napi_derive::napi;\nuse turso_node::{DatabaseOpts, IoLoopTask};\nuse turso_sync_engine::{\n    database_sync_engine::{DatabaseSyncEngine, DatabaseSyncEngineOpts},\n    database_sync_engine_io::SyncEngineIo,\n    database_sync_operations::SyncEngineIoStats,\n    types::{\n        Coro, DatabaseChangeType, DatabaseSyncEngineProtocolVersion, PartialBootstrapStrategy,\n        PartialSyncOpts,\n    },\n};\n\nuse crate::{\n    generator::{GeneratorHolder, GeneratorResponse, SyncEngineChanges},\n    js_protocol_io::{JsProtocolIo, JsProtocolRequestBytes},\n};\n\n#[napi]\npub struct SyncEngine {\n    opts: SyncEngineOptsFilled,\n    io: Option<Arc<dyn turso_core::IO>>,\n    protocol: Option<Arc<JsProtocolIo>>,\n    sync_engine: Arc<RwLock<Option<DatabaseSyncEngine<JsProtocolIo>>>>,\n    db: Arc<Mutex<turso_node::Database>>,\n}\n\n#[napi(string_enum = \"lowercase\")]\npub enum DatabaseChangeTypeJs {\n    Insert,\n    Update,\n    Delete,\n}\n\n#[napi(string_enum = \"lowercase\")]\npub enum SyncEngineProtocolVersion {\n    Legacy,\n    V1,\n}\n\nfn core_change_type_to_js(value: DatabaseChangeType) -> Option<DatabaseChangeTypeJs> {\n    match value {\n        DatabaseChangeType::Delete => Some(DatabaseChangeTypeJs::Delete),\n        DatabaseChangeType::Update => Some(DatabaseChangeTypeJs::Update),\n        DatabaseChangeType::Insert => Some(DatabaseChangeTypeJs::Insert),\n        DatabaseChangeType::Commit => None,\n    }\n}\nfn js_value_to_core(value: Either5<Null, i64, f64, String, Vec<u8>>) -> turso_core::Value {\n    match value {\n        Either5::A(_) => turso_core::Value::Null,\n        Either5::B(value) => turso_core::Value::from_i64(value),\n        Either5::C(value) => turso_core::Value::from_f64(value),\n        Either5::D(value) => turso_core::Value::Text(turso_core::types::Text::new(value)),\n        Either5::E(value) => turso_core::Value::Blob(value),\n    }\n}\nfn core_value_to_js(value: turso_core::Value) -> Either5<Null, i64, f64, String, Vec<u8>> {\n    match value {\n        turso_core::Value::Null => Either5::<Null, i64, f64, String, Vec<u8>>::A(Null),\n        turso_core::Value::Numeric(turso_core::Numeric::Integer(value)) => {\n            Either5::<Null, i64, f64, String, Vec<u8>>::B(value)\n        }\n        turso_core::Value::Numeric(turso_core::Numeric::Float(value)) => {\n            Either5::<Null, i64, f64, String, Vec<u8>>::C(f64::from(value))\n        }\n        turso_core::Value::Text(value) => {\n            Either5::<Null, i64, f64, String, Vec<u8>>::D(value.as_str().to_string())\n        }\n        turso_core::Value::Blob(value) => Either5::<Null, i64, f64, String, Vec<u8>>::E(value),\n    }\n}\nfn core_values_map_to_js(\n    value: HashMap<String, turso_core::Value>,\n) -> HashMap<String, Either5<Null, i64, f64, String, Vec<u8>>> {\n    let mut result = HashMap::new();\n    for (key, value) in value {\n        result.insert(key, core_value_to_js(value));\n    }\n    result\n}\n\n#[napi(object)]\npub struct DatabaseRowMutationJs {\n    pub change_time: i64,\n    pub table_name: String,\n    pub id: i64,\n    pub change_type: DatabaseChangeTypeJs,\n    pub before: Option<HashMap<String, Either5<Null, i64, f64, String, Vec<u8>>>>,\n    pub after: Option<HashMap<String, Either5<Null, i64, f64, String, Vec<u8>>>>,\n    pub updates: Option<HashMap<String, Either5<Null, i64, f64, String, Vec<u8>>>>,\n}\n\n#[napi(object)]\n#[derive(Debug)]\npub struct DatabaseRowStatementJs {\n    pub sql: String,\n    pub values: Vec<Either5<Null, i64, f64, String, Vec<u8>>>,\n}\n\n#[napi(discriminant = \"type\")]\n#[derive(Debug)]\npub enum DatabaseRowTransformResultJs {\n    Keep,\n    Skip,\n    Rewrite { stmt: DatabaseRowStatementJs },\n}\n\n#[napi(discriminant = \"type\")]\n#[derive(Debug)]\npub enum JsPartialBootstrapStrategy {\n    Prefix { length: i64 },\n    Query { query: String },\n}\n\n#[napi(object)]\npub struct JsPartialSyncOpts {\n    pub bootstrap_strategy: JsPartialBootstrapStrategy,\n    pub segment_size: Option<i64>,\n    pub prefetch: Option<bool>,\n}\n\n#[napi(object, object_to_js = false)]\npub struct SyncEngineOpts {\n    pub path: String,\n    pub remote_url: Option<String>,\n    pub client_name: Option<String>,\n    pub wal_pull_batch_size: Option<u32>,\n    pub long_poll_timeout_ms: Option<u32>,\n    pub tracing: Option<String>,\n    pub tables_ignore: Option<Vec<String>>,\n    pub use_transform: bool,\n    pub protocol_version: Option<SyncEngineProtocolVersion>,\n    pub bootstrap_if_empty: bool,\n    /// Encryption cipher for the Turso Cloud database.\n    pub remote_encryption_cipher: Option<String>,\n    /// Base64-encoded encryption key for the Turso Cloud database.\n    /// Must match the key used when creating the encrypted database.\n    pub remote_encryption_key: Option<String>,\n    pub partial_sync_opts: Option<JsPartialSyncOpts>,\n}\n\nstruct SyncEngineOptsFilled {\n    pub path: String,\n    pub remote_url: Option<String>,\n    pub client_name: String,\n    pub wal_pull_batch_size: u32,\n    pub long_poll_timeout: Option<std::time::Duration>,\n    pub tables_ignore: Vec<String>,\n    pub use_transform: bool,\n    pub protocol_version: DatabaseSyncEngineProtocolVersion,\n    pub bootstrap_if_empty: bool,\n    pub remote_encryption_cipher: Option<CipherMode>,\n    pub remote_encryption_key: Option<String>,\n    pub partial_sync_opts: Option<PartialSyncOpts>,\n}\n\n#[derive(Debug, Clone, Copy)]\nenum CipherMode {\n    Aes256Gcm,\n    Aes128Gcm,\n    ChaCha20Poly1305,\n    Aegis128L,\n    Aegis128X2,\n    Aegis128X4,\n    Aegis256,\n    Aegis256X2,\n    Aegis256X4,\n}\n\nimpl CipherMode {\n    /// Returns the total metadata size (nonce + tag) for this cipher mode.\n    /// These values match the Turso Cloud encryption settings.\n    fn required_metadata_size(&self) -> usize {\n        match self {\n            CipherMode::Aes256Gcm | CipherMode::Aes128Gcm | CipherMode::ChaCha20Poly1305 => 28,\n            CipherMode::Aegis128L | CipherMode::Aegis128X2 | CipherMode::Aegis128X4 => 32,\n            CipherMode::Aegis256 | CipherMode::Aegis256X2 | CipherMode::Aegis256X4 => 48,\n        }\n    }\n}\n\n#[napi]\nimpl SyncEngine {\n    #[napi(constructor)]\n    pub fn new(opts: SyncEngineOpts) -> napi::Result<Self> {\n        let is_memory = opts.path == \":memory:\";\n        let io: Arc<dyn turso_core::IO> = if is_memory {\n            Arc::new(turso_core::MemoryIO::new())\n        } else {\n            #[cfg(all(target_os = \"linux\", not(feature = \"browser\")))]\n            {\n                if opts.partial_sync_opts.is_none() {\n                    Arc::new(turso_core::PlatformIO::new().map_err(|e| {\n                        napi::Error::new(\n                            napi::Status::GenericFailure,\n                            format!(\"Failed to create platform IO: {e}\"),\n                        )\n                    })?)\n                } else {\n                    use turso_sync_engine::sparse_io::SparseLinuxIo;\n\n                    Arc::new(SparseLinuxIo::new().map_err(|e| {\n                        napi::Error::new(\n                            napi::Status::GenericFailure,\n                            format!(\"Failed to create sparse IO: {e}\"),\n                        )\n                    })?)\n                }\n            }\n            #[cfg(all(not(target_os = \"linux\"), not(feature = \"browser\")))]\n            {\n                Arc::new(turso_core::PlatformIO::new().map_err(|e| {\n                    napi::Error::new(\n                        napi::Status::GenericFailure,\n                        format!(\"Failed to create platform IO: {e}\"),\n                    )\n                })?)\n            }\n            #[cfg(feature = \"browser\")]\n            {\n                turso_node::browser::opfs()\n            }\n        };\n        #[allow(clippy::arc_with_non_send_sync)]\n        let db = Arc::new(Mutex::new(turso_node::Database::new_with_io(\n            opts.path.clone(),\n            io.clone(),\n            Some(DatabaseOpts {\n                file_must_exist: None,\n                readonly: None,\n                timeout: None,\n                tracing: opts.tracing.clone(),\n                experimental: None,\n                encryption: None, // Local encryption not supported in sync mode\n            }),\n        )?));\n        let opts_filled = SyncEngineOptsFilled {\n            path: opts.path,\n            remote_url: opts.remote_url,\n            client_name: opts\n                .client_name\n                .unwrap_or_else(|| \"turso-sync-js\".to_string()),\n            wal_pull_batch_size: opts.wal_pull_batch_size.unwrap_or(100),\n            long_poll_timeout: opts\n                .long_poll_timeout_ms\n                .map(|x| std::time::Duration::from_millis(x as u64)),\n            tables_ignore: opts.tables_ignore.unwrap_or_default(),\n            use_transform: opts.use_transform,\n            protocol_version: match opts.protocol_version {\n                Some(SyncEngineProtocolVersion::Legacy) | None => {\n                    DatabaseSyncEngineProtocolVersion::Legacy\n                }\n                _ => DatabaseSyncEngineProtocolVersion::V1,\n            },\n            bootstrap_if_empty: opts.bootstrap_if_empty,\n            remote_encryption_cipher: match opts.remote_encryption_cipher.as_deref() {\n                Some(\"aes256gcm\") | Some(\"aes-256-gcm\") => Some(CipherMode::Aes256Gcm),\n                Some(\"aes128gcm\") | Some(\"aes-128-gcm\") => Some(CipherMode::Aes128Gcm),\n                Some(\"chacha20poly1305\") | Some(\"chacha20-poly1305\") => {\n                    Some(CipherMode::ChaCha20Poly1305)\n                }\n                Some(\"aegis128l\") | Some(\"aegis-128l\") => Some(CipherMode::Aegis128L),\n                Some(\"aegis128x2\") | Some(\"aegis-128x2\") => Some(CipherMode::Aegis128X2),\n                Some(\"aegis128x4\") | Some(\"aegis-128x4\") => Some(CipherMode::Aegis128X4),\n                Some(\"aegis256\") | Some(\"aegis-256\") => Some(CipherMode::Aegis256),\n                Some(\"aegis256x2\") | Some(\"aegis-256x2\") => Some(CipherMode::Aegis256X2),\n                Some(\"aegis256x4\") | Some(\"aegis-256x4\") => Some(CipherMode::Aegis256X4),\n                None => None,\n                _ => {\n                    return Err(napi::Error::new(\n                        napi::Status::GenericFailure,\n                        \"unsupported remote cipher. Supported: aes256gcm, aes128gcm, \\\n                         chacha20poly1305, aegis128l, aegis128x2, aegis128x4, aegis256, \\\n                         aegis256x2, aegis256x4\",\n                    ))\n                }\n            },\n            partial_sync_opts: match opts.partial_sync_opts {\n                Some(partial_sync_opts) => match partial_sync_opts.bootstrap_strategy {\n                    JsPartialBootstrapStrategy::Prefix { length } => Some(PartialSyncOpts {\n                        bootstrap_strategy: Some(PartialBootstrapStrategy::Prefix {\n                            length: length as usize,\n                        }),\n                        segment_size: partial_sync_opts.segment_size.unwrap_or(0) as usize,\n                        prefetch: partial_sync_opts.prefetch.unwrap_or(false),\n                    }),\n                    JsPartialBootstrapStrategy::Query { query } => Some(PartialSyncOpts {\n                        bootstrap_strategy: Some(PartialBootstrapStrategy::Query { query }),\n                        segment_size: partial_sync_opts.segment_size.unwrap_or(0) as usize,\n                        prefetch: partial_sync_opts.prefetch.unwrap_or(false),\n                    }),\n                },\n                None => None,\n            },\n            remote_encryption_key: opts.remote_encryption_key.clone(),\n        };\n        Ok(SyncEngine {\n            opts: opts_filled,\n            #[allow(clippy::arc_with_non_send_sync)]\n            sync_engine: Arc::new(RwLock::new(None)),\n            io: Some(io),\n            protocol: Some(Arc::new(JsProtocolIo::default())),\n            #[allow(clippy::arc_with_non_send_sync)]\n            db,\n        })\n    }\n\n    #[napi]\n    pub fn connect(&mut self) -> napi::Result<GeneratorHolder> {\n        let opts = DatabaseSyncEngineOpts {\n            client_name: self.opts.client_name.clone(),\n            remote_url: self.opts.remote_url.clone(),\n            wal_pull_batch_size: self.opts.wal_pull_batch_size as u64,\n            long_poll_timeout: self.opts.long_poll_timeout,\n            tables_ignore: self.opts.tables_ignore.clone(),\n            use_transform: self.opts.use_transform,\n            protocol_version_hint: self.opts.protocol_version,\n            bootstrap_if_empty: self.opts.bootstrap_if_empty,\n            reserved_bytes: self\n                .opts\n                .remote_encryption_cipher\n                .map(|x| x.required_metadata_size())\n                .unwrap_or(0),\n            partial_sync_opts: self.opts.partial_sync_opts.clone(),\n            remote_encryption_key: self.opts.remote_encryption_key.clone(),\n        };\n\n        let io = self.io()?;\n        let protocol = self.protocol()?;\n        let sync_engine = self.sync_engine.clone();\n        let db = self.db.clone();\n        let path = self.opts.path.clone();\n        let generator = genawaiter::sync::Gen::new(|coro| async move {\n            let coro = Coro::new((), coro);\n            let initialized = DatabaseSyncEngine::create_db(\n                &coro,\n                io.clone(),\n                SyncEngineIoStats::new(protocol),\n                &path,\n                opts,\n            )\n            .await?;\n            let connection = initialized.connect_rw(&coro).await?;\n\n            db.lock().unwrap().set_connected(connection).map_err(|e| {\n                turso_sync_engine::errors::Error::DatabaseSyncEngineError(format!(\n                    \"failed to connect sync engine: {e}\"\n                ))\n            })?;\n            *sync_engine.write().unwrap() = Some(initialized);\n\n            Ok(())\n        });\n        Ok(GeneratorHolder {\n            #[allow(clippy::arc_with_non_send_sync)]\n            generator: Arc::new(Mutex::new(generator)),\n            response: Arc::new(Mutex::new(None)),\n        })\n    }\n\n    #[napi]\n    pub fn io_loop_sync(&self) -> napi::Result<()> {\n        self.io()?.step().map_err(|e| {\n            napi::Error::new(napi::Status::GenericFailure, format!(\"IO error: {e}\"))\n        })?;\n        Ok(())\n    }\n\n    /// Runs the I/O loop asynchronously, returning a Promise.\n    #[napi(ts_return_type = \"Promise<void>\")]\n    pub fn io_loop_async(&self) -> napi::Result<AsyncTask<IoLoopTask>> {\n        let io = self.io()?;\n        Ok(AsyncTask::new(IoLoopTask { io }))\n    }\n\n    #[napi]\n    pub fn protocol_io(&self) -> napi::Result<Option<JsProtocolRequestBytes>> {\n        Ok(self.protocol()?.take_request())\n    }\n\n    #[napi]\n    pub fn protocol_io_step(&self) -> napi::Result<()> {\n        self.protocol()?.step_io_callbacks();\n        Ok(())\n    }\n\n    #[napi]\n    pub fn push(&self) -> GeneratorHolder {\n        self.run(async move |coro, guard| {\n            let sync_engine = try_read(guard)?;\n            let sync_engine = try_unwrap(&sync_engine)?;\n            sync_engine.push_changes_to_remote(coro).await?;\n            Ok(None)\n        })\n    }\n\n    #[napi]\n    pub fn stats(&self) -> GeneratorHolder {\n        self.run(async move |coro, guard| {\n            let sync_engine = try_read(guard)?;\n            let sync_engine = try_unwrap(&sync_engine)?;\n            let stats = sync_engine.stats(coro).await?;\n            Ok(Some(GeneratorResponse::SyncEngineStats {\n                cdc_operations: stats.cdc_operations,\n                main_wal_size: stats.main_wal_size as i64,\n                revert_wal_size: stats.revert_wal_size as i64,\n                last_pull_unix_time: stats.last_pull_unix_time,\n                last_push_unix_time: stats.last_push_unix_time,\n                revision: stats.revision,\n                network_sent_bytes: stats.network_sent_bytes as i64,\n                network_received_bytes: stats.network_received_bytes as i64,\n            }))\n        })\n    }\n\n    #[napi]\n    pub fn wait(&self) -> GeneratorHolder {\n        self.run(async move |coro, guard| {\n            let sync_engine = try_read(guard)?;\n            let sync_engine = try_unwrap(&sync_engine)?;\n            Ok(Some(GeneratorResponse::SyncEngineChanges {\n                changes: SyncEngineChanges {\n                    status: Box::new(Some(sync_engine.wait_changes_from_remote(coro).await?)),\n                },\n            }))\n        })\n    }\n\n    #[napi]\n    pub fn apply(&self, changes: &mut SyncEngineChanges) -> GeneratorHolder {\n        let status = changes.status.take().unwrap();\n        self.run(async move |coro, guard| {\n            let sync_engine = try_read(guard)?;\n            let sync_engine = try_unwrap(&sync_engine)?;\n            sync_engine.apply_changes_from_remote(coro, status).await?;\n            Ok(None)\n        })\n    }\n\n    #[napi]\n    pub fn checkpoint(&self) -> GeneratorHolder {\n        self.run(async move |coro, guard| {\n            let sync_engine = try_read(guard)?;\n            let sync_engine = try_unwrap(&sync_engine)?;\n            sync_engine.checkpoint(coro).await?;\n            Ok(None)\n        })\n    }\n\n    #[napi]\n    pub fn db(&self) -> napi::Result<turso_node::Database> {\n        Ok(self.db.lock().unwrap().clone())\n    }\n\n    #[napi]\n    pub fn close(&mut self) {\n        let _ = self.sync_engine.write().unwrap().take();\n        let _ = self.db.lock().unwrap().close();\n        let _ = self.io.take();\n        let _ = self.protocol.take();\n    }\n\n    fn io(&self) -> napi::Result<Arc<dyn turso_core::IO>> {\n        if self.io.is_none() {\n            return Err(napi::Error::new(\n                napi::Status::GenericFailure,\n                \"sync engine was closed\",\n            ));\n        }\n        Ok(self.io.as_ref().unwrap().clone())\n    }\n    fn protocol(&self) -> napi::Result<Arc<JsProtocolIo>> {\n        if self.protocol.is_none() {\n            return Err(napi::Error::new(\n                napi::Status::GenericFailure,\n                \"sync engine was closed\",\n            ));\n        }\n        Ok(self.protocol.as_ref().unwrap().clone())\n    }\n\n    fn run(\n        &self,\n        f: impl AsyncFnOnce(\n                &Coro<()>,\n                &Arc<RwLock<Option<DatabaseSyncEngine<JsProtocolIo>>>>,\n            ) -> turso_sync_engine::Result<Option<GeneratorResponse>>\n            + 'static,\n    ) -> GeneratorHolder {\n        let response = Arc::new(Mutex::new(None));\n        let sync_engine = self.sync_engine.clone();\n        #[allow(clippy::await_holding_lock)]\n        let generator = genawaiter::sync::Gen::new({\n            let response = response.clone();\n            |coro| async move {\n                let coro = Coro::new((), coro);\n                *response.lock().unwrap() = f(&coro, &sync_engine).await?;\n                Ok(())\n            }\n        });\n        GeneratorHolder {\n            generator: Arc::new(Mutex::new(generator)),\n            response,\n        }\n    }\n}\n\nfn try_read(\n    sync_engine: &RwLock<Option<DatabaseSyncEngine<JsProtocolIo>>>,\n) -> turso_sync_engine::Result<RwLockReadGuard<'_, Option<DatabaseSyncEngine<JsProtocolIo>>>> {\n    let Ok(sync_engine) = sync_engine.try_read() else {\n        let nasty_error = \"sync_engine is busy\".to_string();\n        return Err(turso_sync_engine::errors::Error::DatabaseSyncEngineError(\n            nasty_error,\n        ));\n    };\n    Ok(sync_engine)\n}\n\nfn try_unwrap<'a>(\n    sync_engine: &'a RwLockReadGuard<'_, Option<DatabaseSyncEngine<JsProtocolIo>>>,\n) -> turso_sync_engine::Result<&'a DatabaseSyncEngine<JsProtocolIo>> {\n    let Some(sync_engine) = sync_engine.as_ref() else {\n        let error = \"sync_engine must be initialized\".to_string();\n        return Err(turso_sync_engine::errors::Error::DatabaseSyncEngineError(\n            error,\n        ));\n    };\n    Ok(sync_engine)\n}\n"
  },
  {
    "path": "bindings/javascript/turso-sql-runner.mjs",
    "content": "#!/usr/bin/env node\n/**\n * SQL runner script for test-runner JavaScript backend.\n * Reads SQL from stdin, executes via @tursodatabase/database, outputs pipe-separated results.\n *\n * Usage: node turso-sql-runner.mjs <database_path> [--readonly]\n *\n * This script expects to be run from the bindings/javascript directory where\n * the @tursodatabase/database package is available.\n *\n * Known limitations:\n * - JavaScript's number type doesn't distinguish between 1 and 1.0, so float\n *   formatting may differ from the Rust backend for whole-number floats.\n * - Very large integers (exceeding i64) may have precision loss as JavaScript\n *   numbers are IEEE 754 doubles with 53 bits of mantissa precision.\n */\n\nimport { pathToFileURL } from 'node:url';\nimport { splitStatements } from './turso-sql-split.mjs';\n\nasync function readStdin() {\n    const chunks = [];\n    for await (const chunk of process.stdin) {\n        chunks.push(chunk);\n    }\n    return Buffer.concat(chunks).toString('utf-8');\n}\n\nfunction formatValue(value) {\n    if (value === null || value === undefined) {\n        return '';\n    }\n    if (typeof value === 'bigint') {\n        return value.toString();\n    }\n    if (typeof value === 'number') {\n        // Handle special float values to match SQLite output\n        if (value === Infinity) {\n            return 'Inf';\n        }\n        if (value === -Infinity) {\n            return '-Inf';\n        }\n        if (Number.isNaN(value)) {\n            return '';  // SQLite returns NULL for NaN\n        }\n        // For integers, use toString() directly\n        if (Number.isInteger(value)) {\n            return value.toString();\n        }\n        // SQLite uses %.15g format (15 significant digits, trailing zeros removed)\n        // toPrecision gives significant digits, parseFloat removes trailing zeros\n        return parseFloat(value.toPrecision(15)).toString();\n    }\n    if (value instanceof Uint8Array || Buffer.isBuffer(value)) {\n        // Output blob as raw bytes (matches SQLite/Rust backend behavior)\n        // This will display as text if the bytes are printable ASCII\n        return Buffer.from(value).toString('utf-8');\n    }\n    return String(value);\n}\n\nfunction formatRow(row) {\n    // Row is an array in raw mode\n    return row.map(formatValue).join('|');\n}\n\nasync function main() {\n    const args = process.argv.slice(2);\n    if (args.length < 1) {\n        console.error('Usage: turso-sql-runner.mjs <database_path> [--readonly]');\n        process.exit(1);\n    }\n\n    const dbPath = args[0];\n    const readonly = args.includes('--readonly');\n\n    const sql = await readStdin();\n    if (!sql.trim()) {\n        process.exit(0);\n    }\n\n    let db;\n    try {\n        const { connect } = await import('@tursodatabase/database');\n        db = await connect(dbPath, { readonly, experimental: ['triggers', 'attach'] });\n        // Enable safe integers to preserve precision for large integers\n        db.defaultSafeIntegers(true);\n    } catch (err) {\n        console.error(`Error: ${err.message}`);\n        process.exit(1);\n    }\n\n    try {\n        // Split into individual statements, filtering out comments and empty lines\n        const statements = splitStatements(sql);\n\n        // Accumulate results from ALL queries (matches Rust backend behavior)\n        const allResults = [];\n\n        for (const stmt of statements) {\n            const trimmed = stmt.trim();\n            if (!trimmed) continue;\n\n            const prepared = db.prepare(trimmed);\n            prepared.raw(true);\n            const rows = await prepared.all();\n            allResults.push(...rows);\n            prepared.close();\n        }\n\n        // Output all accumulated results\n        for (const row of allResults) {\n            console.log(formatRow(row));\n        }\n\n    } catch (err) {\n        // Output error in a format the test runner can detect\n        console.log(`Error: ${err.message}`);\n        process.exit(0); // Exit 0 so the error can be captured as output\n    } finally {\n        if (db) {\n            await db.close();\n        }\n    }\n}\n\nconst isMain = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;\n\nif (isMain) {\n    main().catch(err => {\n        console.error(`Error: ${err.message}`);\n        process.exit(1);\n    });\n}\n"
  },
  {
    "path": "bindings/javascript/turso-sql-split.mjs",
    "content": "const ReadState = Object.freeze({\n    Invalid: 'Invalid',\n    Start: 'Start',\n    Normal: 'Normal',\n    Explain: 'Explain',\n    Create: 'Create',\n    Trigger: 'Trigger',\n    Semi: 'Semi',\n    End: 'End',\n});\n\nconst Token = Object.freeze({\n    TkSemi: 'TkSemi',\n    TkWhitespace: 'TkWhitespace',\n    TkOther: 'TkOther',\n    TkExplain: 'TkExplain',\n    TkCreate: 'TkCreate',\n    TkTemp: 'TkTemp',\n    TkTrigger: 'TkTrigger',\n    TkEnd: 'TkEnd',\n});\n\nfunction isAsciiWhitespace(char) {\n    const code = char.charCodeAt(0);\n    return code === 0x20 || code === 0x09 || code === 0x0a || code === 0x0b || code === 0x0c || code === 0x0d;\n}\n\nfunction isAsciiAlpha(char) {\n    const code = char.charCodeAt(0);\n    return (code >= 0x41 && code <= 0x5a) || (code >= 0x61 && code <= 0x7a);\n}\n\nfunction isAsciiAlnum(char) {\n    const code = char.charCodeAt(0);\n    return isAsciiAlpha(char) || (code >= 0x30 && code <= 0x39);\n}\n\nfunction classifyKeyword(word) {\n    switch (word) {\n        case 'EXPLAIN':\n            return Token.TkExplain;\n        case 'CREATE':\n            return Token.TkCreate;\n        case 'TEMP':\n        case 'TEMPORARY':\n            return Token.TkTemp;\n        case 'TRIGGER':\n            return Token.TkTrigger;\n        case 'END':\n            return Token.TkEnd;\n        default:\n            return Token.TkOther;\n    }\n}\n\nfunction nextToken(sql, start) {\n    let i = start;\n\n    while (i < sql.length) {\n        const char = sql[i];\n        const nextChar = sql[i + 1];\n\n        if (char === '\\'' || char === '\"' || char === '`' || char === '[') {\n            const endChar = char === '[' ? ']' : char;\n            i++;\n            while (i < sql.length) {\n                const current = sql[i];\n                i++;\n                if (current === endChar) {\n                    break;\n                }\n            }\n            continue;\n        }\n\n        if (char === '-' && nextChar === '-') {\n            i += 2;\n            while (i < sql.length && sql[i] !== '\\n') {\n                i++;\n            }\n            continue;\n        }\n\n        if (char === '/' && nextChar === '*') {\n            i += 2;\n            let sawStar = false;\n            while (i < sql.length) {\n                const current = sql[i];\n                i++;\n                if (sawStar && current === '/') {\n                    break;\n                }\n                sawStar = current === '*';\n            }\n            continue;\n        }\n\n        if (char === ';') {\n            return {\n                token: Token.TkSemi,\n                nextIndex: i + 1,\n                tokenEnd: i + 1,\n            };\n        }\n\n        if (isAsciiWhitespace(char)) {\n            return {\n                token: Token.TkWhitespace,\n                nextIndex: i + 1,\n                tokenEnd: i + 1,\n            };\n        }\n\n        if (isAsciiAlpha(char) || char === '_') {\n            let end = i + 1;\n            while (end < sql.length && (isAsciiAlnum(sql[end]) || sql[end] === '_')) {\n                end++;\n            }\n\n            return {\n                token: classifyKeyword(sql.slice(i, end).toUpperCase()),\n                nextIndex: end,\n                tokenEnd: end,\n            };\n        }\n\n        return {\n            token: Token.TkOther,\n            nextIndex: i + 1,\n            tokenEnd: i + 1,\n        };\n    }\n\n    return null;\n}\n\nfunction transition(state, token) {\n    switch (state) {\n        case ReadState.Invalid:\n            switch (token) {\n                case Token.TkSemi:\n                    return ReadState.Start;\n                case Token.TkWhitespace:\n                    return ReadState.Invalid;\n                case Token.TkExplain:\n                    return ReadState.Explain;\n                case Token.TkCreate:\n                    return ReadState.Create;\n                default:\n                    return ReadState.Normal;\n            }\n        case ReadState.Start:\n            switch (token) {\n                case Token.TkSemi:\n                    return ReadState.Start;\n                case Token.TkWhitespace:\n                    return ReadState.Start;\n                case Token.TkExplain:\n                    return ReadState.Explain;\n                case Token.TkCreate:\n                    return ReadState.Create;\n                default:\n                    return ReadState.Normal;\n            }\n        case ReadState.Normal:\n            switch (token) {\n                case Token.TkSemi:\n                    return ReadState.Start;\n                case Token.TkWhitespace:\n                    return ReadState.Normal;\n                default:\n                    return ReadState.Normal;\n            }\n        case ReadState.Explain:\n            switch (token) {\n                case Token.TkSemi:\n                    return ReadState.Start;\n                case Token.TkWhitespace:\n                    return ReadState.Explain;\n                case Token.TkCreate:\n                    return ReadState.Create;\n                case Token.TkExplain:\n                case Token.TkTemp:\n                case Token.TkTrigger:\n                case Token.TkEnd:\n                    return ReadState.Normal;\n                default:\n                    return ReadState.Explain;\n            }\n        case ReadState.Create:\n            switch (token) {\n                case Token.TkSemi:\n                    return ReadState.Start;\n                case Token.TkWhitespace:\n                    return ReadState.Create;\n                case Token.TkTemp:\n                    return ReadState.Create;\n                case Token.TkTrigger:\n                    return ReadState.Trigger;\n                default:\n                    return ReadState.Normal;\n            }\n        case ReadState.Trigger:\n            switch (token) {\n                case Token.TkSemi:\n                    return ReadState.Semi;\n                case Token.TkWhitespace:\n                    return ReadState.Trigger;\n                default:\n                    return ReadState.Trigger;\n            }\n        case ReadState.Semi:\n            switch (token) {\n                case Token.TkSemi:\n                    return ReadState.Semi;\n                case Token.TkWhitespace:\n                    return ReadState.Semi;\n                case Token.TkEnd:\n                    return ReadState.End;\n                default:\n                    return ReadState.Trigger;\n            }\n        case ReadState.End:\n            switch (token) {\n                case Token.TkSemi:\n                    return ReadState.Start;\n                case Token.TkWhitespace:\n                    return ReadState.End;\n                default:\n                    return ReadState.Trigger;\n            }\n        default:\n            return ReadState.Invalid;\n    }\n}\n\nfunction hasMeaningfulSql(sql) {\n    let index = 0;\n    while (true) {\n        const token = nextToken(sql, index);\n        if (!token) {\n            return false;\n        }\n\n        if (token.token !== Token.TkWhitespace && token.token !== Token.TkSemi) {\n            return true;\n        }\n\n        index = token.nextIndex;\n    }\n}\n\n/**\n * Split SQL text into individual statements using sqlite3_complete-like semantics.\n *\n * This matches testing/sqltests/src/parser/sql_complete.rs so CREATE TRIGGER bodies\n * containing semicolons are treated as a single statement until the ;END; sentinel.\n */\nfunction splitStatements(sql) {\n    const statements = [];\n    let state = ReadState.Invalid;\n    let statementStart = 0;\n    let index = 0;\n\n    while (true) {\n        const tokenInfo = nextToken(sql, index);\n        if (!tokenInfo) {\n            break;\n        }\n\n        const newState = transition(state, tokenInfo.token);\n\n        if (newState === ReadState.Start && state !== ReadState.Start) {\n            const candidate = sql.slice(statementStart, tokenInfo.tokenEnd).trim();\n            if (candidate && hasMeaningfulSql(candidate)) {\n                statements.push(candidate);\n            }\n            statementStart = tokenInfo.tokenEnd;\n        }\n\n        state = newState;\n        index = tokenInfo.nextIndex;\n    }\n\n    const remainder = sql.slice(statementStart).trim();\n    if (remainder && hasMeaningfulSql(remainder)) {\n        statements.push(remainder);\n    }\n\n    return statements;\n}\n\nexport { splitStatements };\n"
  },
  {
    "path": "bindings/python/Cargo.toml",
    "content": "[package]\nname = \"py-turso\"\nversion.workspace = true\nauthors.workspace = true\nedition.workspace = true\nlicense.workspace = true\nrepository.workspace = true\npublish = false\n\n[lints]\nworkspace = true\n\n[lib]\nname = \"_turso\"\ncrate-type = [\"cdylib\"]\n\n[features]\n# must be enabled when building with `cargo build`, maturin enables this automatically\nextension-module = [\"pyo3/extension-module\"]\n\n[dependencies]\nanyhow = \"1.0\"\nturso_sdk_kit = { workspace = true }\nturso_sync_sdk_kit = { workspace = true }\npyo3 = { version = \"0.27.1\", features = [\"anyhow\"] }\n\n[build-dependencies]\nversion_check = \"0.9.5\"\n# used where logic has to be version/distribution specific, e.g. pypy\npyo3-build-config = { version = \"0.27.0\" }\n"
  },
  {
    "path": "bindings/python/README.md",
    "content": "<p align=\"center\">\n  <h1 align=\"center\">Turso Database for Python</h1>\n</p>\n\n<p align=\"center\">\n  <a title=\"Python\" target=\"_blank\" href=\"https://pypi.org/project/pyturso/\"><img alt=\"PyPI\" src=\"https://img.shields.io/pypi/v/pyturso\"></a>\n  <a title=\"MIT\" target=\"_blank\" href=\"https://github.com/tursodatabase/turso/blob/main/LICENSE.md\"><img src=\"http://img.shields.io/badge/license-MIT-orange.svg?style=flat-square\"></a>\n</p>\n<p align=\"center\">\n  <a title=\"Users Discord\" target=\"_blank\" href=\"https://tur.so/discord\"><img alt=\"Chat with other users of Turso on Discord\" src=\"https://img.shields.io/discord/933071162680958986?label=Discord&logo=Discord&style=social\"></a>\n</p>\n\n---\n\n## About\n\n> **⚠️ Warning:** This software is in BETA. It may still contain bugs and unexpected behavior. Use caution with production data and ensure you have backups.\n\n## Features\n\n- **SQLite compatible:** SQLite query language and file format support ([status](https://github.com/tursodatabase/turso/blob/main/COMPAT.md)).\n- **In-process**: No network overhead, runs directly in your Python process\n- **Cross-platform**: Supports Linux, macOS, Windows\n- **Remote partial sync**: Bootstrap from a remote database, pull remote changes, and push local changes when online &mdash; all while enjoying a fully operational database offline.\n- **Asyncio support**: Built-in integration with asyncio to ensure queries won’t block your event loop\n\n## Installation\n\n```bash\nuv pip install pyturso\n```\n\n## Database driver\n\nA minimal DB‑API 2.0 example using an in‑memory database:\n\n```python\nimport turso\n\n# Standard DB-API usage\nconn = turso.connect(\":memory:\")\ncur = conn.cursor()\n\ncur.execute(\"CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT)\")\ncur.execute(\"INSERT INTO users VALUES (1, 'alice'), (2, 'bob')\")\n\ncur.execute(\"SELECT * FROM users ORDER BY id\")\nrows = cur.fetchall()\nprint(rows)  # [(1, 'alice'), (2, 'bob')]\n\nconn.close()\n```\n\n## Database driver (asyncio)\n\nNon-blocking access with asyncio:\n\n```python\nimport asyncio\nimport turso.aio\n\nasync def main():\n    # Connect and use as an async context manager\n    async with turso.aio.connect(\":memory:\") as conn:\n        # Executes multiple statements\n        await conn.executescript(\"\"\"\n            CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);\n            INSERT INTO t(name) VALUES ('alice'), ('bob');\n        \"\"\")\n\n        # Use a cursor for parameterized queries\n        cur = conn.cursor()\n        await cur.execute(\"SELECT COUNT(*) FROM t WHERE name LIKE ?\", (\"a%\",))\n        count = (await cur.fetchone())[0]\n        print(count)  # 1\n\n        # JSON and generate_series also available\n        cur = conn.cursor()\n        await cur.execute(\"SELECT SUM(value) FROM generate_series(1, 10)\")\n        print((await cur.fetchone())[0])  # 55\n\nasyncio.run(main())\n```\n\n## Synchronization driver\n\nUse a remote Turso database while working locally. You can bootstrap local state from the remote, pull remote changes, and push local commits.\n\nNote: You need a Turso remote URL. See the Turso docs for provisioning and authentication.\n\n```python\nimport turso.sync\n\n# Connect a local database to a remote Turso database\nconn = turso.sync.connect(\n    \":memory:\",                          # local db path (or a file path)\n    remote_url=\"https://<db>.<region>.turso.io\"  # your remote URL\n)\n\n# Read data (fetched from remote if not present locally yet)\nrows = conn.execute(\"SELECT * FROM t\").fetchall()\nprint(rows)\n\n# Pull new changes from remote into local\nchanged = conn.pull()\nprint(\"Pulled:\", changed)  # True if there were new remote changes\n\n# Make local changes\nconn.execute(\"INSERT INTO t VALUES ('push works')\")\nconn.commit()\n\n# Push local commits to remote\nconn.push()\n\n# Optional: inspect and manage sync state\nstats = conn.stats()\nprint(\"Network received (bytes):\", stats.network_received_bytes)\nconn.checkpoint()  # compact local WAL after many writes\n\nconn.close()\n```\n\nPartial bootstrap to reduce initial network cost:\n\n```python\nimport turso.sync\n\nconn = turso.sync.connect(\n    \"local.db\",\n    remote_url=\"https://<db>.<region>.turso.io\",\n    # fetch first 128 KiB upfront\n    partial_sync_experimental=turso.sync.PartialSyncOpts(\n      bootstrap_strategy=turso.sync.PartialSyncPrefixBootstrap(length=128 * 1024),\n    ),\n)\n```\n\n## Synchronization driver (asyncio)\n\nThe same sync primitives, but fully async:\n\n```python\nimport asyncio\n\nasync def main():\n    conn = await turso.aio.sync.connect(\":memory:\", remote_url=\"https://<db>.<region>.turso.io\")\n\n    # Read data\n    rows = await (await conn.execute(\"SELECT * FROM t\")).fetchall()\n    print(rows)\n\n    # Pull and push\n    await conn.pull()\n    await conn.execute(\"INSERT INTO t VALUES ('hello from asyncio')\")\n    await conn.commit()\n    await conn.push()\n\n    # Stats and maintenance\n    stats = await conn.stats()\n    print(\"Main WAL size:\", stats.main_wal_size)\n    await conn.checkpoint()\n\n    await conn.close()\n\nasyncio.run(main())\n```\n\n## License\n\nThis project is licensed under the [MIT license](../../LICENSE.md).\n\n## Support\n\n- [GitHub Issues](https://github.com/tursodatabase/turso/issues)\n- [Documentation](https://docs.turso.tech)\n- [Discord Community](https://tur.so/discord)\n"
  },
  {
    "path": "bindings/python/SQLALCHEMY_DIALECT.md",
    "content": "# SQLAlchemy Dialect for Pyturso\n\nThis document describes the SQLAlchemy dialect implementation for pyturso.\n\n## Status: Implemented\n\nThe SQLAlchemy dialect is fully implemented with two dialects:\n- `sqlite+turso://` - Basic local database connections\n- `sqlite+turso_sync://` - Sync-enabled connections with remote database support\n\n## Installation\n\n```bash\npip install pyturso[sqlalchemy]\n```\n\n## Quick Start\n\n### Basic Local Connection\n\n```python\nfrom sqlalchemy import create_engine, text\n\n# In-memory database\nengine = create_engine(\"sqlite+turso:///:memory:\")\n\n# File-based database\nengine = create_engine(\"sqlite+turso:///path/to/database.db\")\n\nwith engine.connect() as conn:\n    conn.execute(text(\"CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)\"))\n    conn.execute(text(\"INSERT INTO users (name) VALUES ('Alice')\"))\n    conn.commit()\n\n    result = conn.execute(text(\"SELECT * FROM users\"))\n    for row in result:\n        print(row)\n```\n\n### Sync-Enabled Connection (Remote Sync)\n\n```python\nfrom sqlalchemy import create_engine, text\nfrom turso.sqlalchemy import get_sync_connection\n\n# Via URL query parameters\nengine = create_engine(\n    \"sqlite+turso_sync:///local.db\"\n    \"?remote_url=https://your-db.turso.io\"\n    \"&auth_token=your-token\"\n)\n\n# Or via connect_args (supports callables for dynamic tokens)\nengine = create_engine(\n    \"sqlite+turso_sync:///local.db\",\n    connect_args={\n        \"remote_url\": \"https://your-db.turso.io\",\n        \"auth_token\": lambda: get_fresh_token(),\n    }\n)\n\nwith engine.connect() as conn:\n    # Access sync operations\n    sync = get_sync_connection(conn)\n    sync.pull()  # Pull changes from remote\n\n    result = conn.execute(text(\"SELECT * FROM users\"))\n\n    conn.execute(text(\"INSERT INTO users (name) VALUES ('Bob')\"))\n    conn.commit()\n    sync.push()  # Push changes to remote\n```\n\n### ORM Usage\n\n```python\nfrom sqlalchemy import create_engine, Column, Integer, String\nfrom sqlalchemy.orm import declarative_base, Session\n\nBase = declarative_base()\n\nclass User(Base):\n    __tablename__ = \"users\"\n    id = Column(Integer, primary_key=True)\n    name = Column(String(100))\n\nengine = create_engine(\"sqlite+turso:///:memory:\")\nBase.metadata.create_all(engine)\n\nwith Session(engine) as session:\n    session.add(User(name=\"Alice\"))\n    session.commit()\n\n    users = session.query(User).all()\n```\n\n## URL Formats\n\n### Basic Dialect (`sqlite+turso://`)\n\n```\nsqlite+turso:///path/to/database.db\nsqlite+turso:///:memory:\nsqlite+turso:///db.db?isolation_level=IMMEDIATE\n```\n\nQuery parameters:\n- `isolation_level` - Transaction isolation level (DEFERRED, IMMEDIATE, EXCLUSIVE, AUTOCOMMIT)\n- `experimental_features` - Comma-separated feature flags\n\n### Sync Dialect (`sqlite+turso_sync://`)\n\n```\nsqlite+turso_sync:///local.db?remote_url=https://db.turso.io&auth_token=xxx\n```\n\nQuery parameters:\n- `remote_url` (required) - Remote Turso/libsql server URL\n- `auth_token` - Authentication token\n- `client_name` - Client identifier (default: turso-sqlalchemy)\n- `long_poll_timeout_ms` - Long poll timeout in milliseconds\n- `bootstrap_if_empty` - Bootstrap from remote if local empty (default: true)\n- `isolation_level` - Transaction isolation level\n- `experimental_features` - Comma-separated feature flags\n\nURL validation:\n- Username/password in URL raises `ValueError` (use `auth_token` instead)\n- Host/port in URL raises `ValueError` (use `remote_url` query param instead)\n- Unrecognized query parameters emit a `UserWarning`\n\n## Sync Operations\n\nThe `get_sync_connection()` helper provides access to sync-specific methods:\n\n```python\nfrom turso.sqlalchemy import get_sync_connection\n\nwith engine.connect() as conn:\n    sync = get_sync_connection(conn)\n\n    # Pull changes from remote (returns True if updates were pulled)\n    if sync.pull():\n        print(\"Pulled new changes!\")\n\n    # Push local changes to remote\n    sync.push()\n\n    # Checkpoint the WAL\n    sync.checkpoint()\n\n    # Get sync statistics\n    stats = sync.stats()\n    print(f\"Network received: {stats.network_received_bytes} bytes\")\n```\n\n`get_sync_connection()` raises `TypeError` if called on a non-sync connection (e.g. a plain `sqlite+turso://` or standard `sqlite://` engine).\n\n## Architecture\n\n```\n_TursoDialectMixin (reflection overrides)\n        │\n        │   SQLiteDialect_pysqlite (SQLAlchemy built-in)\n        │           │\n        ├───────────┤\n        │           │\n        ├── TursoDialect (sqlite+turso://)\n        │       ├── uses turso.connect()\n        │       └── pool: SingletonThreadPool (:memory:) / QueuePool (file)\n        │\n        └── TursoSyncDialect (sqlite+turso_sync://)\n                ├── uses turso.sync.connect()\n                ├── pool: SingletonThreadPool (:memory:) / QueuePool (file)\n                └── get_sync_connection() → ConnectionSync (pull/push/checkpoint/stats)\n```\n\nBoth dialects use Python MRO: `_TursoDialectMixin` provides PRAGMA-related overrides, `SQLiteDialect_pysqlite` provides core SQLite dialect behavior.\n\n## What Pyturso Provides\n\n| Requirement | Status |\n|-------------|--------|\n| `apilevel = \"2.0\"` | Provided |\n| `threadsafety = 1` | Provided |\n| `paramstyle = \"qmark\"` | Provided |\n| `sqlite_version` | Provided |\n| `sqlite_version_info` | Provided |\n| `connect()` function | Provided |\n| `Connection` class | Provided |\n| `Cursor` class | Provided |\n| Exception hierarchy | Provided |\n\nBoth `turso` and `turso.sync` modules expose the full DB-API 2.0 interface including exception hierarchy (`Warning`, `Error`, `InterfaceError`, `DatabaseError`, `DataError`, `OperationalError`, `IntegrityError`, `InternalError`, `ProgrammingError`, `NotSupportedError`).\n\n## Dialect Overrides\n\nBoth dialects share these overrides via `_TursoDialectMixin` and direct method implementations:\n\n### Class Attributes\n\n- `supports_statement_cache = True` - Enables SQLAlchemy statement caching for performance\n- `supports_native_datetime = False` - Turso handles datetime as strings, not native types\n\n### Method Overrides\n\n- `import_dbapi()` - Returns `turso` or `turso.sync` module\n- `create_connect_args()` - Parses URL to connection arguments\n- `on_connect()` - Returns `None` (skips REGEXP function setup that pysqlite does, since turso doesn't support `create_function`)\n- `get_isolation_level()` - Returns `SERIALIZABLE` (turso doesn't support `PRAGMA read_uncommitted`)\n- `set_isolation_level()` - No-op (isolation set at connection time via `isolation_level` param)\n- `get_pool_class()` - Returns `SingletonThreadPool` for `:memory:`, `QueuePool` for file databases\n\n### Reflection Overrides (via `_TursoDialectMixin`)\n\nSingle-table methods (return empty list):\n- `get_foreign_keys()` - `PRAGMA foreign_key_list` not supported\n- `get_indexes()` - `PRAGMA index_list` not supported\n- `get_unique_constraints()` - Relies on `PRAGMA index_list`\n- `get_check_constraints()` - `sqlite_master` parsing not fully supported\n\nMulti-table methods (return empty dict):\n- `get_multi_indexes()`\n- `get_multi_unique_constraints()`\n- `get_multi_foreign_keys()`\n- `get_multi_check_constraints()`\n\n## Limitations\n\n### Table Reflection\n\nTurso doesn't support some SQLite PRAGMAs used for table reflection:\n- `PRAGMA foreign_key_list` - Foreign key introspection\n- `PRAGMA index_list` - Index introspection\n\nThis means:\n- `inspector.get_foreign_keys()` returns empty list\n- `inspector.get_indexes()` returns empty list\n- `inspector.get_unique_constraints()` returns empty list\n- `inspector.get_check_constraints()` returns empty list\n- Foreign keys, indexes, and constraints still **work** at runtime, just can't be introspected\n- `inspector.get_table_names()` and `inspector.get_columns()` work normally\n\nThis doesn't affect normal usage including:\n- Pandas `df.to_sql()` with `if_exists='replace'`\n- SQLAlchemy ORM operations\n- Alembic migrations (when using `--autogenerate`, manually verify FK/index changes)\n\n### Native Datetime\n\n`supports_native_datetime` is set to `False`. Datetime columns should use `String` type and store ISO format strings. SQLAlchemy's `DateTime` type will still work but values are stored/retrieved as strings.\n\n## Entry Points\n\nDialects are registered via `pyproject.toml` entry points:\n\n```toml\n[project.entry-points.\"sqlalchemy.dialects\"]\n\"sqlite.turso\" = \"turso.sqlalchemy:TursoDialect\"\n\"sqlite.turso_sync\" = \"turso.sqlalchemy:TursoSyncDialect\"\n```\n\n## Files\n\n- `turso/sqlalchemy/__init__.py` - Module exports (`TursoDialect`, `TursoSyncDialect`, `get_sync_connection`)\n- `turso/sqlalchemy/dialect.py` - Dialect implementations and `_TursoDialectMixin`\n- `tests/test_sqlalchemy.py` - Tests (28 tests across 8 test classes)\n\n## References\n\n- [SQLAlchemy SQLite Dialect Docs](https://docs.sqlalchemy.org/en/20/dialects/sqlite.html)\n- [SQLAlchemy Dialect Creation Guide](https://github.com/sqlalchemy/sqlalchemy/blob/main/README.dialects.rst)\n- [pysqlite Dialect Source](https://github.com/sqlalchemy/sqlalchemy/blob/main/lib/sqlalchemy/dialects/sqlite/pysqlite.py)\n"
  },
  {
    "path": "bindings/python/build.rs",
    "content": "fn main() {\n    pyo3_build_config::use_pyo3_cfgs();\n}\n"
  },
  {
    "path": "bindings/python/py-bindings-db-aio.mdx",
    "content": "---\nname: 2025-11-26-py-bindings-async\n---\n\n<Output path=\"./turso/lib_aio.py\">\n\n<Code model=\"openai/gpt-5\" language=\"python\">\n\nTurso - is the SQLite compatible database written in Rust.\nOne of the important features of the Turso - is async IO execution which can be used with modern storage backend like IO uring.\n\nYour task is to generate ASYNC Python driver with the API similar aiosqlite (which is similar to DB-API 2 of sqlite module) by REUSING current sync driver imiplementation.\n\n# Rules\n\nGeneral rules for driver implementation you **MUST** follow and never go against these rules:\n- STRUCTURE of the implementation \n    * Declaration order of elements and semantic blocks MUST be exsactly the same\n    * (details and full enumerations omited in the example for brevity but you must generate full code)\n```py\n# all imports must be at the beginning - no imports in the middle of function\nfrom typing import ...\nfrom queue import SimpleQueue\n\nfrom .worker import Worker\nfrom .lib import ( \n    Connection as BlockingConnection,\n    Cursor as BlockingCursor,\n    ...\n)\n\n# Connection goes FIRST\nclass Connection:\n    def __init__(connector: Callable[[], BlockingConnection]): ...\n    # internal worker MUST be stopped when connection goes out of context scope\n    # close must wait for worker to be stopped\n    async def close(...): ...\n    # make Connection instance awaitable\n    def __await__(...): ...\n    async def __aenter__(...): ...\n    # just close the connection - do not add any extra logic\n    async def __aexit__(...): ...\n        await self.close()\n    ...\n\n# Cursor goes SECOND\nclass Cursor: ...\n\n# connect is not async because it returns awaitable Connection\n# same signature as in the lib.py\ndef connect(...): ...\n\n```\n- USE already implemented driver - DO NOT copy it\n- USE primitives from the queue module in order to send work to the separate Thread\n- USE per-connection thread to execute database work\n- FOLLOW the pattern:\n```py\n# create future for completion\nfuture = asyncio.get_event_loop().create_future()\n# put the work to the unbounded queue\nqueue.put_nowait((future, function))\n```\n- NEVER block main event loop (e.g., do not use Queue with block=True - prefer unbounded SimpleQueue instead)\n- REUSE data structures from lib.py if possible (e.g. exceptions)\n- DO NOT accept extra `loop` parameter - ALWAYS use `asyncio.get_event_loop()`\n- CREATE separate thread to execute Turso request and proxy all methods to these thread in order to not block event loop with database work\n- DO NOT reimplement logic of the execution - reuse methods and classes from lib.py\n- AVOID cryptic names - prefer short but concise names (wr is BAD, full_write is GOOD)\n- NEVER put import in the middle of a function - always put all necessary immports at the beginning of the file\n- FOCUS on code readability: if possible extract helper function but make sure that it will be used more than once and that it really contribute to the code readability\n- WATCH OUT for variables scopes and do not use variables which are no longer accessible\n- WATCH OUT for operations atomicity: DO NOT split atomic work in units which may be mixed with other work in async context - breaking atomicity guarantees\n- USE forward reference string in when return method type depends on its class:\n```py\nclass T: \n    def f(self) -> 'T':\n```\n\n# Implementation\n\n- Put compact citations from the official DB-API doc if this is helpful\n- Driver must implement context API for Python to be used like `with ... as conn:` ...\n\n# Blocking driver\n\nFor turso db execution you MUST reuse blocking implementation:\n\n<File path=\"./turso/lib.py\" />\n<File path=\"./turso/worker.py\" />\n<File path=\"./turso/__init__.py\" />\n\n# SQLite-like DB API\n\nMake driver API similar to the SQLite DB-API2 for the python.\n\nPay additional attention to the following aspects:\n* SQLite DB-API2 implementation implicitly opens transaction for DML queries in the execute(...) method:\n    > If autocommit is LEGACY_TRANSACTION_CONTROL, isolation_level is not None, sql is an INSERT, UPDATE, DELETE, or REPLACE statement, and there is no open transaction, a transaction is implicitly opened before executing sql.\n    - MAKE SURE this logic implemented properly\n* Implement .rowcount property correctly, be careful with `executemany(...)` methods as it must return rowcount of all executed statements (not just last statement)\n* Convert exceptions from rust layer to appropriate exceptions documented in the sqlite3 db-api2 docs\n* BE CAREFUL with implementation of transaction control. Make sure that in LEGACY_TRANSACTION_CONTROL mode implicit transaction will be properly commited in case of cursor close\n\nAlso, make types and API compatible with aiosqlite (note, that aiosqlite implements some other methods, which can be omitted now, like interrupt)\n\n<Shell cmd=\"python3 -m pydoc aiosqlite\" />\n\n</Code>\n\n</Output>"
  },
  {
    "path": "bindings/python/py-bindings-db.mdx",
    "content": "---\nname: 2025-11-26-py-bindings\n---\n\n<Output path=\"./turso/lib.py\">\n\n<Code model=\"openai/gpt-5\" language=\"python\">\n\nTurso - is the SQLite compatible database written in Rust.\nOne of the important features of the Turso - is async IO execution which can be used with modern storage backend like IO uring.\n\nYour task is to generate Python driver with the API similar to the SQLite DB-api2\n\n# Rules\n\nGeneral rules for driver implementation you **MUST** follow and never go against these rules:\n- STRUCTURE of the implementation \n    * Declaration order of elements and semantic blocks MUST be exsactly the same\n    * (details and full enumerations omited in the example for brevity but you must generate full code)\n```py\n# all imports must be at the beginning - no imports in the middle of function\nfrom typing import ...\nfrom ._turso import ( ... )\n\n\n# DB-API 2.0 module attributes\napilevel = \"2.0\" \n...\n\n# Exception hierarchy following DB-API 2.0\nclass Warning(Exception): ... # more\n...\n\ndef _map_turso_exception(exc: Exception) -> Exception:\n    \"\"\"Maps Turso-specific exceptions to DB-API 2.0 exception hierarchy\"\"\"\n    if isinstance(exc, Busy): ...\n    ...\n\n# Connection goes FIRST\nclass Connection: ...\n# Cursor goes SECOND\nclass Cursor: ...\n# Row goes THIRD\nclass Row: ...\n\ndef connect(\n    path: str,\n    *,\n    experimental_features: Optional[str] = None,\n    isolation_level: Optional[str] = \"DEFERRED\",\n    extra_io: Optional[Callable[[], None]] = None # extra IO which must be called after stmt.run_io() invocations in the driver\n): ...\n\n# Make it easy to enable logging with native `logging` Python module\n# (import logging only inside this function - make an exception here)\ndef setup_logging(level: Optional[int] = None) -> None: ...\n```\n- AVOID unnecessary FFI calls as their cost is non zero\n- AVOID unnecessary strings transformations - replace them with more efficient alternatives if possible\n- DO NOT ever mix `PyTursoStatement::execute` and `PyTursoStatement::step` methods: every statement must be either \"stepped\" or \"executed\"\n    * This is because `execute` ignores all rows\n- NEVER put import in the middle of a function - always put all necessary immports at the beginning of the file\n- SQL query can be arbitrary, be very careful writing the code which relies on properties derived from the simple string analysis\n    * ONLY ANALYZE SQL statement to detect DML statement and open implicit transaction\n    * DO NOT check for any symbols to detect multi statements, named parameters, etc - this is error prone. Use provided methods and avoid certain checks if they are impossible with current API provided from the Rust\n- FOCUS on code readability: if possible extract helper function but make sure that it will be used more than once and that it really contribute to the code readability\n- WATCH OUT for variables scopes and do not use variables which are no longer accessible\n- DO NOT TRACK transaction state manually and use `get_auto_commit` method - otherwise it can be hard to properly implement implicit transaction rules of DB API2\n- USE forward reference string in when return method type depends on its class:\n```py\nclass T: \n    def f(self) -> 'T':\n```\n\n# Implementation\n\n- Accept extra_io optional parameter in the driver which will run after stmt.run_io() whenever statement execution returned TURSO_IO status\n- Put compact citations from the official DB-API doc if this is helpful\n- Driver must implement context API for Python to be used like `with ... as conn:` ...\n- Driver implementation must be type-friendly - emit types everywhere at API boundary (public methods, class fields, etc)\n    * DO NOT forget that constructor of Row must have following signature: \n```py\nclass Row(Sequence[Any]):\n    def __new__(cls, cursor: Cursor, data: tuple[Any, ...], /) -> Self: ...\n```\n    * Make typings compatible with official types:\n    <Link url=\"https://raw.githubusercontent.com/python/typeshed/refs/heads/main/stdlib/sqlite3/__init__.pyi\" /> \n\n\n# Bindings\n\nYou must use bindings in the lib.rs written with `pyo3` library which has certain conventions.\n\n<File path=\"./src/turso.rs\" />\n<File path=\"./turso/__init__.py\" />\n\nRemember, that it can accept `py: Python` argument which will be passed implicitly and exported bindings will not have this extra arg\n\n# SQLite-like DB API\n\nMake driver API similar to the SQLite DB-API2 for the python.\n\nPay additional attention to the following aspects:\n* SQLite DB-API2 implementation implicitly opens transaction for DML queries in the execute(...) method:\n    > If autocommit is LEGACY_TRANSACTION_CONTROL, isolation_level is not None, sql is an INSERT, UPDATE, DELETE, or REPLACE statement, and there is no open transaction, a transaction is implicitly opened before executing sql.\n    - MAKE SURE this logic implemented properly\n* Implement .rowcount property correctly, be careful with `executemany(...)` methods as it must return rowcount of all executed statements (not just last statement)\n* Convert exceptions from rust layer to appropriate exceptions documented in the sqlite3 db-api2 docs\n* BE CAREFUL with implementation of transaction control. Make sure that in LEGACY_TRANSACTION_CONTROL mode implicit transaction will be properly commited in case of cursor close\n\n<Shell \n    cmd=\"curl https://docs.python.org/3/library/sqlite3.html | pandoc --from html --to markdown\" \n    selector=\"~Connection objects|~Cursor objects|~Row objects|~Exceptions|~Transaction\" \n    ext=\".md\"\n/>\n\n</Code>\n\n</Output>"
  },
  {
    "path": "bindings/python/py-bindings-sync-aio.mdx",
    "content": "---\nname: 2025-11-26-py-bindings-async-sync\n---\n\n<Output path=\"./turso/lib_sync_aio.py\">\n\n<Code model=\"openai/gpt-5\" language=\"python\">\n\nTurso - is the SQLite compatible database written in Rust.\nOne of the important features of the Turso - is native ability to sync database with the Cloud in both directions (push local changes and pull remote changes).\n\nYour task is to generate ASYNC wrapper for synchronization EXTRA functionality built on top of the existing Python driver which will extend regular embedded with sync capability.\nDo not modify existing driver - its already implemented in the lib.py and lib_sync.py.\nYour task is to write extra code which will use abstractions and provide async API support in the Python on top of it in the lib_sync_aio.py file.\n\n# Rules\n\nGeneral rules for driver implementation you **MUST** follow and never go against these rules:\n- USE already implemented drivers - DO NOT copy it\n- STRUCTURE of the implementation \n    * Declaration order of elements and semantic blocks MUST be exsactly the same\n    * (details and full enumerations omited in the example for brevity but you must generate full code)\n```py\n# ALL imports MUST be at the beginning - no imports in the middle of function\nfrom typing import ...\nfrom queue import SimpleQueue\n\nfrom .worker import Worker\nfrom .lib_sync import (\n    ConnectionSync as BlockingConnectionSync\n    ...\n)\nfrom .lib_aio import (\n    Connection as NonBlockingConnection\n    ...\n)\n\nclass ConnectionSync(NonBlockingConnection):\n    def __init__(connector: Callable[[], BlockingConnectionSync]): ...\n    # internal worker MUST be stopped when connection goes out of context scope\n    def close(...): ...\n    # make ConnectionSync instance awaitable\n    # emit proper typings because delegation of __await__ to the base class will change the return type and make extra methods (pull/push/etc) unavailable\n    def __await__(...): ...\n    async def __aenter__(...): ...\n    async def __aexit__(...):\n        await self.close()\n\n    # returns True of new updates were pulled; False if no new updates were fetched; determine changes by inspecting .empty() method of changes\n    async def pull(self) -> bool: ...\n    async def push(self) -> None: ...\n    async def checkpoint(self) -> None: ...\n    async def stats(self) -> None: ...\n\n# connect is not async because it returns awaitable ConnectionSync\n# same signature as in the lib_sync.py\ndef connect_sync(...) -> ConnectionSync: ...\n\n```\n- FOLLOW the pattern:\n```py\n# create future for completion\nfuture = asyncio.get_event_loop().create_future()\n# put the work to the unbounded queue\nqueue.put_nowait((future, function))\n```\n- AVOID cryptic names - prefer short but concise names (wr is BAD, full_write is GOOD)\n- NEVER EVER put import in the middle of a function - always put all necessary immports at the beginning of the file\n- FOCUS on code readability: extract helper functions if it will contribute to the code readability (do not overoptimize - it's fine to have some logic inlined especially if it is not repeated anywhere else)\n- WATCH OUT for variables scopes and do not use variables which are no longer accessible\n\n# Implementation\n\n- Annotate public API with types\n- Add comments about public API fields/functions to clarify meaning and usage scenarios\n\n# Blocking sync driver\n\nFor turso sync execution you MUST reuse blocking implementation:\n\n<File path=\"./turso/lib_sync.py\" />\n<File path=\"./turso/__init__.py\" />\n\n# Non-blocking db driver\n\nYou must integrate with non-blocking async db driver implementation:\n\n<File path=\"./turso/lib_aio.py\" />\n\n</Code>\n\n</Output>"
  },
  {
    "path": "bindings/python/py-bindings-sync.mdx",
    "content": "---\nname: 2025-11-26-py-bindings\n---\n\n<Output path=\"./turso/lib_sync.py\">\n\n<Code model=\"openai/gpt-5\" language=\"python\">\n\nTurso - is the SQLite compatible database written in Rust.\nOne of the important features of the Turso - is native ability to sync database with the Cloud in both directions (push local changes and pull remote changes).\n\nYour task is to generate EXTRA functionality on top of the existing Python driver which will extend regular embedded with sync capability.\nDo not modify existing driver - its already implemented in the lib.py\nYour task is to write extra code which will use abstractions lib.py and build sync support in the Python on top of it in the lib_sync.py file.\n\n# Rules\n\nGeneral rules for driver implementation you **MUST** follow and never go against these rules:\n\n- USE already implemented driver - DO NOT copy it\n- SET async_io=True for the driver database configuration - because partial sync support requires TURSO_IO to handled externally from the bindings\n- STRUCTURE of the implementation\n  - Declaration order of elements and semantic blocks MUST be exsactly the same\n  - (details and full enumerations omited in the example for brevity but you must generate full code)\n\n```py\n# ALL imports MUST be at the beginning - no imports in the middle of function\nfrom typing import ...\nfrom dataclasses import dataclass\n# for HTTP IO\nimport urllib.request\nimport urllib.error\n\nfrom .lib import Connection as _Connection\nfrom ._turso import ( ... )\n\nclass ConnectionSync(_Connection):\n    def __init__(...): ...\n\n    def pull(self) -> bool: ... # returns True of new updates were pulled; False if no new updates were fetched; determine changes by inspecting .empty() method of changes\n    def push(self) -> None: ...\n    def checkpoint(self) -> None: ...\n    def stats(self) -> None: ...\n\n@dataclass\nclass PartialSyncPrefixBootstrap:\n    # Bootstraps DB by fetching first N bytes/pages; enables partial sync\n    length: int\n\n\n@dataclass\nclass PartialSyncQueryBootstrap:\n    # Bootstraps DB by fetching pages touched by given SQL query on server\n    query: str\n\n@dataclass\nclass PartialSyncOpts:\n    bootstrap_strategy: Union[PartialSyncPrefixBootstrap, PartialSyncQueryBootstrap]\n    segment_size: Optional[int] = None\n    prefetch: Optional[bool] = None\n\ndef connect_sync(\n    path: str, # path to the main database file locally\n    # remote url for the sync - can be lambda which will be evaluated on every http request; if lambda returns None - internal http processing must return error which will bubble-up in the sync engine\n    # remote_url MUST be used in all sync engine operations: during bootstrap and all further operations\n    # remote_url must accept either http://, https:// or libsql:// protocol, where later must be just replaced with https:// under the hood for now\n    remote_url: Union[str, Callable[[], Optional[str]]],\n    *,\n    # token for remote authentication - can be lambda which will be evaluted on every http request; if lambda returns None - internal http processing must return error which will bubble-up in the sync engine\n    # auth token value (\"fixed\" or got from lambda) WILL not have any prefix and must be used as \"Authorization\" header prepended with \"Bearer \" prefix\n    auth_token: Optional[Union[str, Callable[[], Optional[str]]]],\n    client_name: Optional[str], # optional unique client name (library MUST use `turso-sync-py` if omitted)\n    long_poll_timeout_ms: Optional[number], # long polling timeout\n    bootstrap_if_empty: bool = True, # if not set, initial bootstrap phase will be skipped and caller must call .pull(...) explicitly in order to get initial state from remote\n    partial_sync_experimental: Optional[PartialSyncOpts] = None, # EXPERIMENTAL partial sync configuration\n    experimental_features: Optional[str] = None, # pass it as-is to the underlying connection\n    isolation_level: Optional[str] = \"DEFERRED\", # pass it as-is to the underlying connection\n) -> ConnectionSync: ...\n\n```\n\n- STREAM data from the http request to the completion in chunks and spin async operation in between in order to prevent loading whole response in memory\n\n```py\n# event loop for async operation \"op\"\nwhile True:\n    chunk = e.read(CHUNK_SIZE)\n    if not chunk:\n        break\n    io_item.push_buffer(chunk)\n    op.resume() # assert that None is returned\n```\n\n- AVOID unnecessary FFI calls as their cost is non zero\n- AVOID unnecessary strings transformations - replace them with more efficient alternatives if possible\n- AVOID cryptic names - prefer short but concise names (wr is BAD, full_write is GOOD)\n- AVOID duplication of IO processing between `connect_sync` function and methods of `ConnectionSync` class\n- DO NOT use getattr unless this is necessary - use simple field access through dot\n- NEVER EVER put import in the middle of a function - always put all necessary immports at the beginning of the file\n- FOCUS on code readability: extract helper functions if it will contribute to the code readability (do not overoptimize - it's fine to have some logic inlined especially if it is not repeated anywhere else)\n- WATCH OUT for variables scopes and do not use variables which are no longer accessible\n\n# Implementation\n\n- Annotate public API with types\n- Add comments about public API fields/functions to clarify meaning and usage scenarios\n- Use `create()` method for creation of the synced database for now - DO NOT use init + open pair\n- Access #[pyo3(get)] fields through dot (e.g. stats.cdc_operations)\n\n# Bindings\n\nYou must use bindings in the lib.rs written with `pyo3` library which has certain conventions.\n\n<File path=\"./src/lib.rs\" />\n<File path=\"./src/turso_sync.rs\" />\n\nRemember, that it can accept `py: Python` argument which will be passed implicitly and exported bindings will not have this extra arg\n\n# Driver\n\nYou must integrate with current driver implementation:\n\n<File path=\"./src/turso.rs\" />\n<File path=\"./turso/lib.py\" />\n\n</Code>\n\n</Output>\n"
  },
  {
    "path": "bindings/python/py-bindings-tests-aio.mdx",
    "content": "---\nname: 2025-11-26-py-bindings-tests\n---\n\n<Output path=\"./tests/test_database_aio.py\">\n\n<Code model=\"openai/gpt-5\" language=\"python\">\n\nTurso - is the **SQLite compatible** database written in Rust.\nYour task is to generate tests for Python driver with the API similar to the SQLite DB-api2\n\n# Rules\n\nGeneral rules for driver implementation you **MUST** follow and never go against these rules:\n- Inspect tests in the test_database.py file and ALWAYS append new tests in the end\n- DO NOT change current content of the test_database.py file - ONLY APPEND new tests\n- DO NOT duplicate already existing tests\n- DO NOT test not implemented features\n- DO COVER all essential methods currently implemented in the driver\n- FOLLOW programming style of the test_database_aio.py file\n<File path=\"./tests/test_database_aio.py\" />\n\n# Test case category 1: Driver\n\nGenerate tests which will cover API of the driver surface\n\n- DRIVER: cover the async API\n- DRIVER: create tests for async execution so database operations doesn't block main event loop\n\nInspect implementaton of the driver here:\n<File path=\"./turso/lib_aio.py\" />\n\n# Test case category 2: SQL\n\nGenerate tests which will cover generic use of SQL. \n**Non exhaustive** list of things to check:\n- Subqueries\n- INSERT ... RETURNING ...\n    * Make additional test case for scenario, where multiple values were inserted, but only one row were fetch\n    * Make sure that in this case transaction will be properly commited even when not all rows were consumed\n- CONFLICT clauses (and how driver inform caller about conflict)\n- Basic DDL statements (CREATE/DELETE)\n- More complex DDL statements (ALTER TABLE)\n- Builtin virtual tables (generate_series)\n- JOIN\n- JSON functions\n\n# Supported functions\n\n- SQLITE: generate_series is not enabled by default in the sqlite3 module\n- TURSO: ORDER BY is not supported for compound SELECTs yet\n- TURSO: Recursive CTEs are not yet supported\n- TURSO: Inspect compatibility file in order to understand what subset of SQLite query language is supported by the turso at the moment\n<File path=\"../../COMPAT.md\" />\n\n</Code>\n\n</Output>"
  },
  {
    "path": "bindings/python/py-bindings-tests.mdx",
    "content": "---\nname: 2025-11-26-py-bindings-tests\n---\n\n<Output path=\"./tests/test_database.py\">\n\n<Code model=\"openai/gpt-5\" language=\"python\">\n\nTurso - is the **SQLite compatible** database written in Rust.\nYour task is to generate tests for Python driver with the API similar to the SQLite DB-api2\n\n# Rules\n\nGeneral rules for driver implementation you **MUST** follow and never go against these rules:\n- Inspect tests in the test_database.py file and ALWAYS append new tests in the end\n- DO NOT change current content of the test_database.py file - ONLY APPEND new tests\n- DO NOT duplicate already existing tests\n- DO NOT test not implemented features\n- DO COVER all essential methods currently implemented in the driver\n- FOLLOW programming style of the test_database.py file\n<File path=\"./tests/test_database.py\" />\n\n# Test case category 1: DB API2\n\nGenerate tests which will cover API of the driver surface\n\nInspect implementaton of the driver here:\n<File path=\"./turso/lib.py\" />\n\n# Test case category 2: SQL\n\nGenerate tests which will cover generic use of SQL. \n**Non exhaustive** list of things to check:\n- Subqueries\n- INSERT ... RETURNING ...\n    * Make additional test case for scenario, where multiple values were inserted, but only one row were fetch\n    * Make sure that in this case transaction will be properly commited even when not all rows were consumed\n- CONFLICT clauses (and how driver inform caller about conflict)\n- Basic DDL statements (CREATE/DELETE)\n- More complex DDL statements (ALTER TABLE)\n- Builtin virtual tables (generate_series)\n- JOIN\n- JSON functions\n\n# Supported functions\n\n- DRIVER: .rowcount works correctly only for DML statements\n    * DO NOT test it with DQL/DDL statements\n- DRIVER: .lastrowid is not implemented right now\n    * DO NOT test it at all\n- SQLITE: generate_series is not enabled by default in the sqlite3 module\n- TURSO: ORDER BY is not supported for compound SELECTs yet\n- TURSO: Recursive CTEs are not yet supported\n- TURSO: Inspect compatibility file in order to understand what subset of SQLite query language is supported by the turso at the moment\n<File path=\"../../COMPAT.md\" />\n\n</Code>\n\n</Output>"
  },
  {
    "path": "bindings/python/pyproject.toml",
    "content": "[build-system]\nrequires = ['maturin>=1,<2', 'typing_extensions']\nbuild-backend = 'maturin'\n\n[project]\nname = 'pyturso'\ndescription = \"Turso is a work-in-progress, in-process OLTP database management system, compatible with SQLite.\"\nrequires-python = '>=3.9'\nclassifiers = [\n    'Development Status :: 3 - Alpha',\n    'Programming Language :: Python',\n    'Programming Language :: Python :: 3',\n    'Programming Language :: Python :: 3 :: Only',\n    'Programming Language :: Python :: 3.9',\n    'Programming Language :: Python :: 3.10',\n    'Programming Language :: Python :: 3.11',\n    'Programming Language :: Python :: 3.12',\n    'Programming Language :: Python :: 3.13',\n    'Programming Language :: Rust',\n    'License :: OSI Approved :: MIT License',\n    'Operating System :: POSIX :: Linux',\n    'Operating System :: Microsoft :: Windows',\n    'Operating System :: MacOS',\n    'Topic :: Database',\n    'Topic :: Software Development :: Libraries',\n    'Topic :: Software Development :: Libraries :: Python Modules',\n    'Topic :: Database :: Database Engines/Servers',\n]\ndependencies = ['typing-extensions >=4.6.0,!=4.7.0']\ndynamic = ['readme', 'version']\n\n[project.optional-dependencies]\ndev = [\n    \"mypy==1.11.0\",\n    \"pytest==8.3.1\",\n    \"pytest-cov==5.0.0\",\n    \"ruff==0.5.4\",\n    \"coverage==7.6.1\",\n    \"maturin==1.7.8\",\n]\nsqlalchemy = [\n    \"sqlalchemy>=2.0\",\n]\n\n[project.urls]\nHomepage = \"https://github.com/tursodatabase/turso\"\nSource = \"https://github.com/tursodatabase/turso\"\n\n[project.entry-points.\"sqlalchemy.dialects\"]\n\"sqlite.turso\" = \"turso.sqlalchemy:TursoDialect\"\n\"sqlite.turso_sync\" = \"turso.sqlalchemy:TursoSyncDialect\"\n\n[tool.maturin]\nbindings = 'pyo3'\nmodule-name = \"turso._turso\"\nfeatures = [\"pyo3/extension-module\"]\n\n[tool.pip-tools]\nstrip-extras = true\nheader = false\nupgrade = false\n\n[tool.pytest.ini_options]\ntestpaths = 'tests'\nlog_format = '%(name)s %(levelname)s: %(message)s'\nasyncio_default_fixture_loop_scope = \"function\"\n\n[tool.coverage.run]\nsource = ['turso']\nbranch = true\n\n[tool.coverage.report]\nprecision = 2\nexclude_lines = [\n    'pragma: no cover',\n    'raise NotImplementedError',\n    'if TYPE_CHECKING:',\n    '@overload',\n]\n\n[dependency-groups]\ndev = [\n    \"coverage>=7.6.1\",\n    \"iniconfig>=2.1.0\",\n    \"maturin>=1.7.8\",\n    \"mypy>=1.11.0\",\n    \"mypy-extensions>=1.1.0\",\n    \"pluggy>=1.6.0\",\n    \"pytest>=8.3.1\",\n    \"pytest-asyncio>=1.3.0\",\n    \"pytest-cov>=5.0.0\",\n    \"requests>=2.32.5\",\n    \"ruff>=0.5.4\",\n    \"typing-extensions>=4.13.0\",\n]\n"
  },
  {
    "path": "bindings/python/src/lib.rs",
    "content": "use pyo3::{\n    pymodule,\n    types::{PyModule, PyModuleMethods},\n    wrap_pyfunction, Bound, PyResult,\n};\nuse turso_sdk_kit::rsapi::TursoDatabase;\n\nuse crate::{\n    turso::{\n        py_turso_database_open, py_turso_setup, Busy, Constraint, Corrupt, DatabaseFull, Interrupt,\n        Misuse, NotAdb, PyTursoConnection, PyTursoDatabase, PyTursoDatabaseConfig,\n        PyTursoEncryptionConfig, PyTursoExecutionResult, PyTursoLog, PyTursoSetupConfig,\n        PyTursoStatement, PyTursoStatusCode, Readonly,\n    },\n    turso_sync::{\n        py_turso_sync_new, PyRemoteEncryptionCipher, PyTursoAsyncOperation,\n        PyTursoAsyncOperationResultKind, PyTursoPartialSyncOpts, PyTursoSyncDatabase,\n        PyTursoSyncDatabaseChanges, PyTursoSyncDatabaseConfig, PyTursoSyncDatabaseStats,\n        PyTursoSyncIoItem, PyTursoSyncIoItemRequestKind,\n    },\n};\n\npub mod turso;\npub mod turso_sync;\n\n#[pymodule]\nfn _turso(m: &Bound<PyModule>) -> PyResult<()> {\n    m.add(\"__version__\", TursoDatabase::version())?;\n    // database exports\n    m.add_function(wrap_pyfunction!(py_turso_setup, m)?)?;\n    m.add_function(wrap_pyfunction!(py_turso_database_open, m)?)?;\n    m.add_class::<PyTursoStatusCode>()?;\n    m.add_class::<PyTursoExecutionResult>()?;\n    m.add_class::<PyTursoLog>()?;\n    m.add_class::<PyTursoSetupConfig>()?;\n    m.add_class::<PyTursoDatabaseConfig>()?;\n    m.add_class::<PyTursoDatabase>()?;\n    m.add_class::<PyTursoConnection>()?;\n    m.add_class::<PyTursoStatement>()?;\n    m.add_class::<PyTursoEncryptionConfig>()?;\n\n    m.add(\"Busy\", m.py().get_type::<Busy>())?;\n    m.add(\"Interrupt\", m.py().get_type::<Interrupt>())?;\n    m.add(\"Error\", m.py().get_type::<crate::turso::Error>())?;\n    m.add(\"Misuse\", m.py().get_type::<Misuse>())?;\n    m.add(\"Constraint\", m.py().get_type::<Constraint>())?;\n    m.add(\"Readonly\", m.py().get_type::<Readonly>())?;\n    m.add(\"DatabaseFull\", m.py().get_type::<DatabaseFull>())?;\n    m.add(\"NotAdb\", m.py().get_type::<NotAdb>())?;\n    m.add(\"Corrupt\", m.py().get_type::<Corrupt>())?;\n\n    // sync exports\n    m.add_function(wrap_pyfunction!(py_turso_sync_new, m)?)?;\n    m.add_class::<PyTursoSyncDatabase>()?;\n    m.add_class::<PyTursoSyncDatabaseConfig>()?;\n    m.add_class::<PyTursoSyncDatabaseChanges>()?;\n    m.add_class::<PyTursoSyncIoItem>()?;\n    m.add_class::<PyTursoSyncDatabaseStats>()?;\n    m.add_class::<PyTursoSyncIoItemRequestKind>()?;\n    m.add_class::<PyTursoAsyncOperation>()?;\n    m.add_class::<PyTursoAsyncOperationResultKind>()?;\n    m.add_class::<PyTursoPartialSyncOpts>()?;\n    m.add_class::<PyRemoteEncryptionCipher>()?;\n    Ok(())\n}\n"
  },
  {
    "path": "bindings/python/src/turso.rs",
    "content": "use pyo3::{\n    prelude::*,\n    types::{PyBytes, PyTuple},\n};\nuse std::sync::Arc;\nuse turso_sdk_kit::rsapi::{\n    self, EncryptionOpts, Numeric, TursoError, TursoStatusCode, Value, ValueRef,\n};\n\nuse pyo3::create_exception;\nuse pyo3::exceptions::PyException;\n\n// support equality for status codes\n#[pyclass(eq, eq_int)]\n#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] // Add necessary traits for your use case\npub enum PyTursoStatusCode {\n    Ok = 0,\n    Done = 1,\n    Row = 2,\n    Io = 3,\n}\ncreate_exception!(turso, Busy, PyException, \"database is locked\");\ncreate_exception!(\n    turso,\n    BusySnapshot,\n    PyException,\n    \"database snapshot is stale\"\n);\ncreate_exception!(turso, Interrupt, PyException, \"interrupted\");\ncreate_exception!(turso, Error, PyException, \"generic error\");\ncreate_exception!(turso, Misuse, PyException, \"API misuse\");\ncreate_exception!(turso, Constraint, PyException, \"constraint error\");\ncreate_exception!(turso, Readonly, PyException, \"database is readonly\");\ncreate_exception!(turso, DatabaseFull, PyException, \"database is full\");\ncreate_exception!(turso, NotAdb, PyException, \"not a database`\");\ncreate_exception!(turso, Corrupt, PyException, \"database corrupted\");\ncreate_exception!(turso, IoError, PyException, \"I/O error\");\n\npub(crate) fn turso_error_to_py_err(err: TursoError) -> PyErr {\n    match err {\n        rsapi::TursoError::Busy(message) => Busy::new_err(message),\n        rsapi::TursoError::BusySnapshot(message) => BusySnapshot::new_err(message),\n        rsapi::TursoError::Interrupt(message) => Interrupt::new_err(message),\n        rsapi::TursoError::Error(message) => Error::new_err(message),\n        rsapi::TursoError::Misuse(message) => Misuse::new_err(message),\n        rsapi::TursoError::Constraint(message) => Constraint::new_err(message),\n        rsapi::TursoError::Readonly(message) => Readonly::new_err(message),\n        rsapi::TursoError::DatabaseFull(message) => DatabaseFull::new_err(message),\n        rsapi::TursoError::NotAdb(message) => NotAdb::new_err(message),\n        rsapi::TursoError::Corrupt(message) => Corrupt::new_err(message),\n        rsapi::TursoError::IoError(kind, op) => IoError::new_err(format!(\"{op}: {kind:?}\")),\n    }\n}\n\nfn turso_status_to_py(status: TursoStatusCode) -> PyTursoStatusCode {\n    match status {\n        TursoStatusCode::Done => PyTursoStatusCode::Done,\n        TursoStatusCode::Row => PyTursoStatusCode::Row,\n        TursoStatusCode::Io => PyTursoStatusCode::Io,\n    }\n}\n\n#[pyclass]\npub struct PyTursoExecutionResult {\n    #[pyo3(get)]\n    pub status: PyTursoStatusCode,\n    #[pyo3(get)]\n    pub rows_changed: u64,\n}\n\n#[pyclass]\npub struct PyTursoLog {\n    #[pyo3(get)]\n    pub message: String,\n    #[pyo3(get)]\n    pub target: String,\n    #[pyo3(get)]\n    pub file: String,\n    #[pyo3(get)]\n    pub timestamp: u64,\n    #[pyo3(get)]\n    pub line: usize,\n    #[pyo3(get)]\n    pub level: String,\n}\n\n#[pyclass]\npub struct PyTursoSetupConfig {\n    pub logger: Option<Py<PyAny>>,\n    pub log_level: Option<String>,\n}\n\n#[pymethods]\nimpl PyTursoSetupConfig {\n    #[new]\n    #[pyo3(signature = (logger, log_level))]\n    fn new(logger: Option<Py<PyAny>>, log_level: Option<String>) -> Self {\n        Self { logger, log_level }\n    }\n}\n\n#[pyclass]\n#[derive(Clone)]\npub struct PyTursoEncryptionConfig {\n    pub cipher: String,\n    pub hexkey: String,\n}\n\n#[pymethods]\nimpl PyTursoEncryptionConfig {\n    #[new]\n    #[pyo3(signature = (cipher, hexkey))]\n    fn new(cipher: String, hexkey: String) -> Self {\n        Self { cipher, hexkey }\n    }\n}\n\n#[pyclass]\npub struct PyTursoDatabaseConfig {\n    pub path: String,\n\n    /// comma-separated list of experimental features to enable\n    /// this field is intentionally just a string in order to make enablement of experimental features as flexible as possible\n    pub experimental_features: Option<String>,\n\n    /// optional VFS parameter explicitly specifying FS backend for the database.\n    /// Available options are:\n    /// - \"memory\": in-memory backend\n    /// - \"syscall\": generic syscall backend\n    /// - \"io_uring\": IO uring (supported only on Linux)\n    pub vfs: Option<String>,\n\n    /// optional encryption parameters\n    /// as encryption is experimental - experimental_features must have \"encryption\" in the list\n    pub encryption: Option<PyTursoEncryptionConfig>,\n}\n\n#[pymethods]\nimpl PyTursoDatabaseConfig {\n    #[new]\n    #[pyo3(signature = (path, experimental_features=None, vfs=None, encryption=None))]\n    fn new(\n        path: String,\n        experimental_features: Option<String>,\n        vfs: Option<String>,\n        encryption: Option<&PyTursoEncryptionConfig>,\n    ) -> Self {\n        Self {\n            path,\n            experimental_features,\n            vfs,\n            encryption: encryption.cloned(),\n        }\n    }\n}\n\n#[pyclass]\npub struct PyTursoDatabase {\n    database: Arc<rsapi::TursoDatabase>,\n}\n\n/// Setup logging for the turso globally\n/// Only first invocation has effect - all subsequent updates will be ignored\n#[pyfunction]\npub fn py_turso_setup(py: Python, config: &PyTursoSetupConfig) -> PyResult<()> {\n    rsapi::turso_setup(rsapi::TursoSetupConfig {\n        logger: if let Some(logger) = &config.logger {\n            let logger = logger.clone_ref(py);\n            Some(Box::new(move |log| {\n                Python::attach(|py| {\n                    let py_log = PyTursoLog {\n                        message: log.message.to_string(),\n                        target: log.target.to_string(),\n                        file: log.file.to_string(),\n                        timestamp: log.timestamp,\n                        line: log.line,\n                        level: log.level.to_string(),\n                    };\n                    logger.call1(py, (py_log,)).unwrap();\n                })\n            }))\n        } else {\n            None\n        },\n        log_level: config.log_level.clone(),\n    })\n    .map_err(turso_error_to_py_err)?;\n    Ok(())\n}\n\n/// Open the database\n#[pyfunction]\npub fn py_turso_database_open(config: &PyTursoDatabaseConfig) -> PyResult<PyTursoDatabase> {\n    let database = rsapi::TursoDatabase::new(rsapi::TursoDatabaseConfig {\n        path: config.path.clone(),\n        experimental_features: config.experimental_features.clone(),\n        async_io: false,\n        encryption: config.encryption.as_ref().map(|encryption| EncryptionOpts {\n            cipher: encryption.cipher.clone(),\n            hexkey: encryption.hexkey.clone(),\n        }),\n        vfs: config.vfs.clone(),\n        io: None,\n        db_file: None,\n    });\n    let result = database.open().map_err(turso_error_to_py_err)?;\n    // async_io is false - so db.open() will return result immediately\n    assert!(!result.is_io());\n    Ok(PyTursoDatabase { database })\n}\n\n#[pymethods]\nimpl PyTursoDatabase {\n    pub fn connect(&self) -> PyResult<PyTursoConnection> {\n        Ok(PyTursoConnection {\n            connection: self.database.connect().map_err(turso_error_to_py_err)?,\n        })\n    }\n}\n\n#[pyclass]\npub struct PyTursoConnection {\n    pub(crate) connection: Arc<rsapi::TursoConnection>,\n}\n\n#[pymethods]\nimpl PyTursoConnection {\n    /// prepare single statement from the string\n    pub fn prepare_single(&self, sql: &str) -> PyResult<PyTursoStatement> {\n        Ok(PyTursoStatement {\n            statement: self\n                .connection\n                .prepare_single(sql)\n                .map_err(turso_error_to_py_err)?,\n        })\n    }\n    /// prepare first statement from the string which can have multiple statements separated by semicolon\n    /// returns None if string has no statements\n    /// returns Some with prepared statement and position in the string right after the prepared statement end\n    pub fn prepare_first(&self, sql: &str) -> PyResult<Option<(PyTursoStatement, usize)>> {\n        match self\n            .connection\n            .prepare_first(sql)\n            .map_err(turso_error_to_py_err)?\n        {\n            Some((statement, tail_idx)) => Ok(Some((PyTursoStatement { statement }, tail_idx))),\n            None => Ok(None),\n        }\n    }\n    /// Get the auto_commmit mode for the connection\n    pub fn get_auto_commit(&self) -> PyResult<bool> {\n        Ok(self.connection.get_auto_commit())\n    }\n    /// Close the connection\n    /// (caller must ensure that no operations over connection or derived statements will happen after the call)\n    pub fn close(&self) -> PyResult<()> {\n        self.connection.close().map_err(turso_error_to_py_err)\n    }\n}\n\n#[pyclass]\npub struct PyTursoStatement {\n    statement: Box<rsapi::TursoStatement>,\n}\n\n#[pymethods]\nimpl PyTursoStatement {\n    /// binds positional parameters to the statement\n    pub fn bind(&mut self, parameters: Bound<PyTuple>) -> PyResult<()> {\n        let len = parameters.len();\n        for i in 0..len {\n            let parameter = parameters.get_item(i)?;\n            self.statement\n                .bind_positional(i + 1, py_to_db_value(parameter)?)\n                .map_err(turso_error_to_py_err)?;\n        }\n        Ok(())\n    }\n\n    /// step one iteration of the statement execution\n    /// Returns [PyTursoStatusCode::Done] when execution is finished\n    /// Returns [PyTursoStatusCode::Row] when execution generated a row which can be consumed with [Self::row] method\n    /// Returns [PyTursoStatusCode::Io] when async_io is set and execution needs IO in order to make progress\n    ///\n    /// The caller must always either use [Self::step] or [Self::execute] methods for single statement - but never mix them together\n    pub fn step(&mut self) -> PyResult<PyTursoStatusCode> {\n        Ok(turso_status_to_py(\n            self.statement.step(None).map_err(turso_error_to_py_err)?,\n        ))\n    }\n\n    /// execute statement and ignore all rows generated by it\n    /// Returns [PyTursoStatusCode::Done] when execution is finished\n    /// Returns [PyTursoStatusCode::Io] when async_io is set and execution needs IO in order to make progress\n    ///\n    /// Note, that execute never returns Row status code\n    ///\n    /// The caller must always either use [Self::step] or [Self::execute] methods for single statement - but never mix them together\n    pub fn execute(&mut self) -> PyResult<PyTursoExecutionResult> {\n        let result = self\n            .statement\n            .execute(None)\n            .map_err(turso_error_to_py_err)?;\n        Ok(PyTursoExecutionResult {\n            status: turso_status_to_py(result.status),\n            rows_changed: result.rows_changed,\n        })\n    }\n    /// Run one iteration of IO backend\n    pub fn run_io(&self) -> PyResult<()> {\n        self.statement.run_io().map_err(turso_error_to_py_err)?;\n        Ok(())\n    }\n    /// Get column names of the statement\n    pub fn columns(&self, py: Python) -> PyResult<Py<PyTuple>> {\n        let columns_count = self.statement.column_count();\n        let mut columns = Vec::with_capacity(columns_count);\n        for i in 0..columns_count {\n            columns.push(\n                self.statement\n                    .column_name(i)\n                    .map_err(turso_error_to_py_err)?\n                    .to_string(),\n            );\n        }\n        Ok(PyTuple::new(py, columns.into_iter())?.unbind())\n    }\n    /// Get tuple with current row values\n    /// This method is only valid to call after [Self::step] returned [PyTursoStatusCode::Row] status code\n    pub fn row(&self, py: Python) -> PyResult<Py<PyTuple>> {\n        let columns_count = self.statement.column_count();\n        let mut py_values = Vec::with_capacity(columns_count);\n        for i in 0..columns_count {\n            py_values.push(db_value_to_py(\n                py,\n                self.statement.row_value(i).map_err(turso_error_to_py_err)?,\n            )?);\n        }\n        Ok(PyTuple::new(py, &py_values)?.into_pyobject(py)?.into())\n    }\n    /// Finalize statement execution\n    /// This method must be called when statement is no longer need\n    /// It will perform necessary cleanup and run any unfinished statement operations to completion\n    /// (for example, in `INSERT INTO ... RETURNING ...` query, finalize is essential as it will make sure that all inserts will be completed, even if only few first rows were consumed by the caller)\n    ///\n    /// Note, that if statement wasn't started (no step / execute methods was called) - finalize will not execute the statement\n    pub fn finalize(&mut self) -> PyResult<PyTursoStatusCode> {\n        Ok(turso_status_to_py(\n            self.statement\n                .finalize(None)\n                .map_err(turso_error_to_py_err)?,\n        ))\n    }\n    /// Reset the statement by clearing bindings and reclaiming memory of the program from previous run\n    /// This will also abort last operation if any was unfinished (but if transaction was opened before this statement - its state will be untouched, reset will only affect operation within current statement)\n    pub fn reset(&mut self) -> PyResult<()> {\n        self.statement.reset().map_err(turso_error_to_py_err)?;\n        Ok(())\n    }\n}\n\nfn db_value_to_py(py: Python, value: rsapi::ValueRef) -> PyResult<Py<PyAny>> {\n    match value {\n        ValueRef::Null => Ok(py.None()),\n        ValueRef::Numeric(Numeric::Integer(i)) => Ok(i.into_pyobject(py)?.into()),\n        ValueRef::Numeric(Numeric::Float(f)) => Ok(f64::from(f).into_pyobject(py)?.into()),\n        ValueRef::Text(s) => Ok(s.as_str().into_pyobject(py)?.into()),\n        ValueRef::Blob(b) => Ok(PyBytes::new(py, b).into()),\n    }\n}\n\n/// Converts a Python object to a Turso Value\nfn py_to_db_value(obj: Bound<PyAny>) -> PyResult<Value> {\n    if obj.is_none() {\n        Ok(Value::Null)\n    } else if let Ok(integer) = obj.extract::<i64>() {\n        Ok(Value::from_i64(integer))\n    } else if let Ok(float) = obj.extract::<f64>() {\n        Ok(Value::from_f64(float))\n    } else if let Ok(string) = obj.extract::<String>() {\n        Ok(Value::Text(string.into()))\n    } else if let Ok(bytes) = obj.cast::<PyBytes>() {\n        Ok(Value::Blob(bytes.as_bytes().to_vec()))\n    } else {\n        Err(Error::new_err(\n            \"unexpected parameter value, only None, numbers, strings and bytes are supported\"\n                .to_string(),\n        ))\n    }\n}\n"
  },
  {
    "path": "bindings/python/src/turso_sync.rs",
    "content": "use std::sync::Arc;\n\nuse pyo3::{\n    pyclass, pyfunction, pymethods,\n    types::{PyBytes, PyList, PyTuple},\n    PyResult, Python,\n};\nuse turso_sdk_kit::rsapi::{TursoDatabaseConfig, TursoStatusCode};\nuse turso_sync_sdk_kit::{\n    rsapi::{\n        self, PartialBootstrapStrategy, PartialSyncOpts, TursoDatabaseSync,\n        TursoDatabaseSyncChanges,\n    },\n    sync_engine_io::SyncEngineIoQueueItem,\n    turso_async_operation::{TursoAsyncOperationResult, TursoDatabaseAsyncOperation},\n};\n\nuse crate::turso::{\n    turso_error_to_py_err, Error, Misuse, PyTursoConnection, PyTursoDatabaseConfig,\n};\n\n#[pyclass]\n#[derive(Clone)]\npub struct PyTursoPartialSyncOpts {\n    // prefix bootstrap strategy which will enable partial sync which lazily pull necessary pages on demand and bootstrap db with pages from first N bytes of the db\n    pub bootstrap_strategy_prefix: Option<usize>,\n    // query bootstrap strategy which will enable partial sync which lazily pull necessary pages on demand and bootstrap db with pages touched by the server with given SQL query\n    pub bootstrap_strategy_query: Option<String>,\n    pub segment_size: Option<usize>,\n    pub prefetch: Option<bool>,\n}\n\n#[pymethods]\nimpl PyTursoPartialSyncOpts {\n    #[new]\n    #[pyo3(signature = (\n        bootstrap_strategy_prefix=None,\n        bootstrap_strategy_query=None,\n        segment_size=None,\n        prefetch=None,\n    ))]\n    fn new(\n        bootstrap_strategy_prefix: Option<usize>,\n        bootstrap_strategy_query: Option<String>,\n        segment_size: Option<usize>,\n        prefetch: Option<bool>,\n    ) -> Self {\n        Self {\n            bootstrap_strategy_prefix,\n            bootstrap_strategy_query,\n            segment_size,\n            prefetch,\n        }\n    }\n}\n\n/// Encryption cipher for Turso Cloud remote encryption.\n/// These match the server-side encryption settings.\n#[pyclass(eq, eq_int)]\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum PyRemoteEncryptionCipher {\n    Aes256Gcm,\n    Aes128Gcm,\n    ChaCha20Poly1305,\n    Aegis128L,\n    Aegis128X2,\n    Aegis128X4,\n    Aegis256,\n    Aegis256X2,\n    Aegis256X4,\n}\n\nimpl PyRemoteEncryptionCipher {\n    /// Returns the total reserved bytes as required by the server\n    pub fn reserved_bytes(&self) -> usize {\n        match self {\n            Self::Aes256Gcm | Self::Aes128Gcm | Self::ChaCha20Poly1305 => 28,\n            Self::Aegis128L | Self::Aegis128X2 | Self::Aegis128X4 => 32,\n            Self::Aegis256 | Self::Aegis256X2 | Self::Aegis256X4 => 48,\n        }\n    }\n}\n\n#[pyclass]\npub struct PyTursoSyncDatabaseConfig {\n    // path to the main database file (auxilary files like metadata, WAL, revert, changes will derive names from this path)\n    pub path: String,\n    // optional remote url (libsql://..., https://... or http://...)\n    // this URL will be saved in the database metadata file in order to be able to reuse it if later client will be constructed without explicit remote url\n    pub remote_url: Option<String>,\n    // arbitrary client name which will be used as a prefix for unique client id\n    pub client_name: String,\n    // long poll timeout for pull method (if set, server will hold connection for the given timeout until new changes will appear)\n    pub long_poll_timeout_ms: Option<u32>,\n    // bootstrap db if empty; if set - client will be able to connect to fresh db only when network is online\n    pub bootstrap_if_empty: bool,\n    // reserved bytes which must be set for the database - necessary if remote encryption is set for the db in cloud\n    pub reserved_bytes: Option<usize>,\n    pub partial_sync: Option<PyTursoPartialSyncOpts>,\n    // base64-encoded encryption key for the encrypted Turso Cloud databases\n    pub remote_encryption_key: Option<String>,\n    // encryption cipher for the remote database (used to calculate reserved_bytes)\n    pub remote_encryption_cipher: Option<PyRemoteEncryptionCipher>,\n}\n\n#[pymethods]\nimpl PyTursoSyncDatabaseConfig {\n    #[new]\n    #[pyo3(signature = (\n        path,\n        client_name,\n        remote_url=None,\n        long_poll_timeout_ms=None,\n        bootstrap_if_empty=true,\n        reserved_bytes=None,\n        partial_sync=None,\n        remote_encryption_key=None,\n        remote_encryption_cipher=None,\n    ))]\n    #[allow(clippy::too_many_arguments)]\n    fn new(\n        path: String,\n        client_name: String,\n        remote_url: Option<String>,\n        long_poll_timeout_ms: Option<u32>,\n        bootstrap_if_empty: bool,\n        reserved_bytes: Option<usize>,\n        partial_sync: Option<&PyTursoPartialSyncOpts>,\n        remote_encryption_key: Option<String>,\n        remote_encryption_cipher: Option<PyRemoteEncryptionCipher>,\n    ) -> Self {\n        Self {\n            path,\n            remote_url,\n            client_name,\n            long_poll_timeout_ms,\n            bootstrap_if_empty,\n            reserved_bytes,\n            partial_sync: partial_sync.cloned(),\n            remote_encryption_key,\n            remote_encryption_cipher,\n        }\n    }\n}\n\n/// Creates database sync holder but do not open it\n#[pyfunction]\npub fn py_turso_sync_new(\n    db_config: &PyTursoDatabaseConfig,\n    sync_config: &PyTursoSyncDatabaseConfig,\n) -> PyResult<PyTursoSyncDatabase> {\n    let db_config = TursoDatabaseConfig {\n        path: db_config.path.clone(),\n        experimental_features: db_config.experimental_features.clone(),\n        async_io: true, // we will drive IO externally which is especially important for partial sync\n        encryption: None,\n        vfs: None,\n        io: None,\n        db_file: None,\n    };\n    // calculate and set reserved_bytes from cipher if necessary\n    let reserved_bytes = sync_config\n        .remote_encryption_cipher\n        .map(|c| c.reserved_bytes())\n        .or(sync_config.reserved_bytes);\n    let sync_config = rsapi::TursoDatabaseSyncConfig {\n        path: sync_config.path.clone(),\n        remote_url: sync_config.remote_url.clone(),\n        client_name: sync_config.client_name.clone(),\n        bootstrap_if_empty: sync_config.bootstrap_if_empty,\n        long_poll_timeout_ms: sync_config.long_poll_timeout_ms,\n        reserved_bytes,\n        partial_sync_opts: match &sync_config.partial_sync {\n            Some(config) => {\n                if let Some(length) = config.bootstrap_strategy_prefix {\n                    Some(PartialSyncOpts {\n                        bootstrap_strategy: Some(PartialBootstrapStrategy::Prefix { length }),\n                        segment_size: config.segment_size.unwrap_or(0),\n                        prefetch: config.prefetch.unwrap_or(false),\n                    })\n                } else {\n                    config\n                        .bootstrap_strategy_query\n                        .as_ref()\n                        .map(|query| PartialSyncOpts {\n                            bootstrap_strategy: Some(PartialBootstrapStrategy::Query {\n                                query: query.clone(),\n                            }),\n                            segment_size: config.segment_size.unwrap_or(0),\n                            prefetch: config.prefetch.unwrap_or(false),\n                        })\n                }\n            }\n            None => None,\n        },\n        remote_encryption_key: sync_config.remote_encryption_key.clone(),\n    };\n    let database =\n        TursoDatabaseSync::<Vec<u8>>::new(db_config, sync_config).map_err(turso_error_to_py_err)?;\n    Ok(PyTursoSyncDatabase { database })\n}\n\n#[pyclass]\npub struct PyTursoSyncDatabase {\n    database: Arc<rsapi::TursoDatabaseSync<Vec<u8>>>,\n}\n\n#[pyclass]\npub struct PyTursoAsyncOperation {\n    operation: Box<TursoDatabaseAsyncOperation>,\n}\n\n#[pyclass(eq, eq_int)]\n#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] // Add necessary traits for your use case\npub enum PyTursoAsyncOperationResultKind {\n    /// async operation has no return value (\"void\" operation)\n    /// Note, that Python bindings have \"No\" as value because it's impossible to use \"None\" language keyword here\n    No = 0,\n    /// async operation returned [PyTursoConnection] instance\n    Connection = 1,\n    /// async operation returned [PyTursoSyncDatabaseStats]\n    Stats = 2,\n    /// async operation returned [PyTursoSyncDatabaseChanges]\n    Changes = 3,\n}\n\n#[pyclass]\npub struct PyTursoSyncDatabaseStats {\n    #[pyo3(get)]\n    pub cdc_operations: i64,\n    #[pyo3(get)]\n    pub main_wal_size: u64,\n    #[pyo3(get)]\n    pub revert_wal_size: i64,\n    #[pyo3(get)]\n    pub last_pull_unix_time: Option<i64>,\n    #[pyo3(get)]\n    pub last_push_unix_time: Option<i64>,\n    #[pyo3(get)]\n    pub revision: Option<String>,\n    #[pyo3(get)]\n    pub network_sent_bytes: i64,\n    #[pyo3(get)]\n    pub network_received_bytes: i64,\n}\n\n#[pymethods]\nimpl PyTursoSyncDatabaseStats {\n    fn __repr__(&self) -> PyResult<String> {\n        Ok(format!(\n            \"PyTursoSyncDatabaseStats(\n    cdc_operations={}, \n    main_wal_size={}, \n    revert_wal_size={}, \n    last_pull_unix_time={}, \n    last_push_unix_time={}, \n    revision={}, \n    network_sent_bytes={}, \n    network_received_bytes={}\n)\",\n            self.cdc_operations,\n            self.main_wal_size,\n            self.revert_wal_size,\n            self.last_pull_unix_time\n                .map(|x| x.to_string())\n                .unwrap_or_else(|| \"None\".to_string()),\n            self.last_push_unix_time\n                .map(|x| x.to_string())\n                .unwrap_or_else(|| \"None\".to_string()),\n            self.revision\n                .as_ref()\n                .map(|x| format!(\"\\\"{x}\\\"\"))\n                .unwrap_or_else(|| \"None\".to_string()),\n            self.network_sent_bytes,\n            self.network_received_bytes,\n        ))\n    }\n\n    fn __str__(&self) -> PyResult<String> {\n        self.__repr__()\n    }\n}\n\n/// changes container fetched from remote; must be passed to the [PyTursoSyncDatabase::apply_changes] method\n#[pyclass]\npub struct PyTursoSyncDatabaseChanges {\n    changes: Option<Box<TursoDatabaseSyncChanges>>,\n}\n\n#[pymethods]\nimpl PyTursoSyncDatabaseChanges {\n    /// check if some changes were fetched from remote\n    pub fn empty(&self) -> PyResult<bool> {\n        let Some(changes) = &self.changes else {\n            return Err(Misuse::new_err(\"changes were already applied\".to_string()));\n        };\n        Ok(changes.empty())\n    }\n}\n\n#[pyclass]\npub struct PyTursoAsyncOperationResult {\n    #[pyo3(get)]\n    pub kind: PyTursoAsyncOperationResultKind,\n    #[pyo3(get)]\n    pub connection: Option<pyo3::Py<PyTursoConnection>>,\n    #[pyo3(get)]\n    pub changes: Option<pyo3::Py<PyTursoSyncDatabaseChanges>>,\n    #[pyo3(get)]\n    pub stats: Option<pyo3::Py<PyTursoSyncDatabaseStats>>,\n}\n\n#[pymethods]\nimpl PyTursoAsyncOperation {\n    /// Resume async operation execution\n    /// If returns Ok(false) - operation is not finished yet and must be resumed after one iteration of sync engine IO\n    /// If returns Ok(true) - operation is finished and final result can be inspected with [Self::take_result] method\n    /// It's safe to call resume multiple times even after operation completion (in case of repeat calls after completion - final result always will be returned)\n    pub fn resume(&self) -> PyResult<bool> {\n        let result = self.operation.resume().map_err(turso_error_to_py_err)?;\n        if result == TursoStatusCode::Io {\n            Ok(false)\n        } else if result == TursoStatusCode::Done {\n            Ok(true)\n        } else {\n            Err(Error::new_err(\"unexpected resume status\".to_string()))\n        }\n    }\n    /// Extract final result after operation completion\n    /// This function can be called at most once as final result will be consumed after first call\n    pub fn take_result(&self, py: Python) -> PyResult<PyTursoAsyncOperationResult> {\n        let result = self.operation.take_result();\n        match result {\n            Ok(TursoAsyncOperationResult::Changes { changes }) => Ok(PyTursoAsyncOperationResult {\n                kind: PyTursoAsyncOperationResultKind::Changes,\n                changes: Some(pyo3::Py::new(\n                    py,\n                    PyTursoSyncDatabaseChanges {\n                        changes: Some(changes),\n                    },\n                )?),\n                connection: None,\n                stats: None,\n            }),\n            Ok(TursoAsyncOperationResult::Connection { connection }) => {\n                Ok(PyTursoAsyncOperationResult {\n                    kind: PyTursoAsyncOperationResultKind::Connection,\n                    changes: None,\n                    connection: Some(pyo3::Py::new(py, PyTursoConnection { connection })?),\n                    stats: None,\n                })\n            }\n            Ok(TursoAsyncOperationResult::Stats { stats }) => Ok(PyTursoAsyncOperationResult {\n                kind: PyTursoAsyncOperationResultKind::Stats,\n                changes: None,\n                connection: None,\n                stats: Some(pyo3::Py::new(\n                    py,\n                    PyTursoSyncDatabaseStats {\n                        cdc_operations: stats.cdc_operations,\n                        main_wal_size: stats.main_wal_size,\n                        revert_wal_size: stats.revert_wal_size as i64,\n                        last_pull_unix_time: stats.last_pull_unix_time,\n                        last_push_unix_time: stats.last_push_unix_time,\n                        revision: stats.revision,\n                        network_sent_bytes: stats.network_sent_bytes as i64,\n                        network_received_bytes: stats.network_received_bytes as i64,\n                    },\n                )?),\n            }),\n            // The only possible error is Misuse in case when operation doesn't have any result\n            Err(..) => Ok(PyTursoAsyncOperationResult {\n                kind: PyTursoAsyncOperationResultKind::No,\n                changes: None,\n                connection: None,\n                stats: None,\n            }),\n        }\n    }\n}\n\n#[pyclass(eq, eq_int)]\n#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] // Add necessary traits for your use case\npub enum PyTursoSyncIoItemRequestKind {\n    /// HTTP IO operation\n    Http = 0,\n    /// Atomic read IO operation; in case of not found error - sync engine expects empty response (not error) from the caller\n    FullRead = 1,\n    /// Atomic write IO operation\n    FullWrite = 2,\n}\n\n#[pyclass]\npub struct PyTursoSyncIoItemHttpRequest {\n    /// optional HTTP url\n    #[pyo3(get)]\n    pub url: Option<String>,\n    /// HTTP method (e.g. POST / GET)\n    #[pyo3(get)]\n    pub method: String,\n    /// HTTP path (url is controlled outside of the sync engine)\n    #[pyo3(get)]\n    pub path: String,\n    /// optional body of the request\n    #[pyo3(get)]\n    pub body: Option<pyo3::Py<PyBytes>>,\n    /// Headers as list of tuples: list[(str, str)]\n    #[pyo3(get)]\n    pub headers: pyo3::Py<PyList>,\n}\n\n#[pyclass]\npub struct PyTursoSyncIoItemFullReadRequest {\n    /// path of the file to read\n    #[pyo3(get)]\n    pub path: String,\n}\n\n#[pyclass]\npub struct PyTursoSyncIoItemFullWriteRequest {\n    /// path of the file to write\n    #[pyo3(get)]\n    pub path: String,\n    /// content of the file to write\n    #[pyo3(get)]\n    pub content: pyo3::Py<PyBytes>,\n}\n\n#[pyclass]\npub struct PyTursoSyncIoItemRequest {\n    #[pyo3(get)]\n    pub kind: PyTursoSyncIoItemRequestKind,\n    #[pyo3(get)]\n    pub http: Option<pyo3::Py<PyTursoSyncIoItemHttpRequest>>,\n    #[pyo3(get)]\n    pub full_read: Option<pyo3::Py<PyTursoSyncIoItemFullReadRequest>>,\n    #[pyo3(get)]\n    pub full_write: Option<pyo3::Py<PyTursoSyncIoItemFullWriteRequest>>,\n}\n\n#[pyclass]\npub struct PyTursoSyncIoItem {\n    item: Box<SyncEngineIoQueueItem<Vec<u8>>>,\n}\n\n#[pymethods]\nimpl PyTursoSyncIoItem {\n    /// Get IO request representation from the sync engine IO queue item\n    pub fn request(&self, py: pyo3::Python) -> PyResult<PyTursoSyncIoItemRequest> {\n        match self.item.get_request() {\n            turso_sync_sdk_kit::sync_engine_io::SyncEngineIoRequest::Http {\n                url,\n                method,\n                path,\n                body,\n                headers,\n            } => Ok(PyTursoSyncIoItemRequest {\n                kind: PyTursoSyncIoItemRequestKind::Http,\n                full_read: None,\n                full_write: None,\n                http: Some(pyo3::Py::new(\n                    py,\n                    PyTursoSyncIoItemHttpRequest {\n                        url: url.clone(),\n                        method: method.clone(),\n                        path: path.clone(),\n                        body: body\n                            .as_ref()\n                            .map(|body| PyBytes::new(py, body.as_ref()).unbind()),\n                        headers: {\n                            let mut tuples = Vec::new();\n                            for (key, value) in headers {\n                                tuples\n                                    .push(PyTuple::new(py, [key.clone(), value.clone()])?.unbind());\n                            }\n                            PyList::new(py, tuples.into_iter())?.unbind()\n                        },\n                    },\n                )?),\n            }),\n            turso_sync_sdk_kit::sync_engine_io::SyncEngineIoRequest::FullRead { path } => {\n                Ok(PyTursoSyncIoItemRequest {\n                    kind: PyTursoSyncIoItemRequestKind::FullRead,\n                    full_read: Some(pyo3::Py::new(\n                        py,\n                        PyTursoSyncIoItemFullReadRequest { path: path.clone() },\n                    )?),\n                    full_write: None,\n                    http: None,\n                })\n            }\n            turso_sync_sdk_kit::sync_engine_io::SyncEngineIoRequest::FullWrite {\n                path,\n                content,\n            } => Ok(PyTursoSyncIoItemRequest {\n                kind: PyTursoSyncIoItemRequestKind::FullWrite,\n                full_read: None,\n                full_write: Some(pyo3::Py::new(\n                    py,\n                    PyTursoSyncIoItemFullWriteRequest {\n                        path: path.clone(),\n                        content: PyBytes::new(py, content.as_ref()).unbind(),\n                    },\n                )?),\n                http: None,\n            }),\n        }\n    }\n    /// set error as the final completion result of the IO queue item\n    pub fn poison(&self, error: String) {\n        self.item.get_completion().poison(error);\n    }\n    /// set IO completion as finished successfully ([Self::done] and [Self::poison] are mutually exclusive)\n    pub fn done(&self) {\n        self.item.get_completion().done();\n    }\n    /// push bytes to the IO completion\n    pub fn push_buffer(&self, buffer: &[u8]) {\n        self.item.get_completion().push_buffer(buffer.to_vec());\n    }\n    /// set HTTP status to the IO completion\n    pub fn status(&self, status: u32) {\n        self.item.get_completion().status(status);\n    }\n}\n\n#[pymethods]\nimpl PyTursoSyncDatabase {\n    /// Open prepared synced database, fail if no properly setup database exists\n    /// AsyncOperation returns No\n    pub fn open(&self) -> PyTursoAsyncOperation {\n        PyTursoAsyncOperation {\n            operation: self.database.open(),\n        }\n    }\n    /// Prepare synced database and open it\n    /// AsyncOperation returns No\n    pub fn create(&self) -> PyTursoAsyncOperation {\n        PyTursoAsyncOperation {\n            operation: self.database.create(),\n        }\n    }\n    /// Create [PyTursoConnection] connection\n    /// synced database must be opened before that operation (with either turso_database_sync_create or turso_database_sync_open)\n    /// AsyncOperation returns Connection\n    pub fn connect(&self) -> PyTursoAsyncOperation {\n        PyTursoAsyncOperation {\n            operation: self.database.connect(),\n        }\n    }\n    /// Collect stats about synced database\n    /// AsyncOperation returns Stats\n    pub fn stats(&self) -> PyTursoAsyncOperation {\n        PyTursoAsyncOperation {\n            operation: self.database.stats(),\n        }\n    }\n    /// Checkpoint WAL of the synced database\n    /// AsyncOperation returns No\n    pub fn checkpoint(&self) -> PyTursoAsyncOperation {\n        PyTursoAsyncOperation {\n            operation: self.database.checkpoint(),\n        }\n    }\n    /// Push local changes to remote\n    /// AsyncOperation returns No\n    pub fn push_changes(&self) -> PyTursoAsyncOperation {\n        PyTursoAsyncOperation {\n            operation: self.database.push_changes(),\n        }\n    }\n    /// Wait for remote changes\n    /// AsyncOperation returns Changes\n    pub fn wait_changes(&self) -> PyTursoAsyncOperation {\n        PyTursoAsyncOperation {\n            operation: self.database.wait_changes(),\n        }\n    }\n    /// Apply remote changes locally\n    /// AsyncOperation returns No\n    pub fn apply_changes(\n        &self,\n        changes: &mut PyTursoSyncDatabaseChanges,\n    ) -> PyResult<PyTursoAsyncOperation> {\n        let Some(changes) = changes.changes.take() else {\n            return Err(Misuse::new_err(\n                \"changes were already applied before\".to_string(),\n            ));\n        };\n        Ok(PyTursoAsyncOperation {\n            operation: self.database.apply_changes(changes),\n        })\n    }\n    /// Run extra database callbacks after IO execution\n    pub fn step_io_callbacks(&self) {\n        self.database.step_io_callbacks();\n    }\n    /// Try to take IO request from the sync engine IO queue\n    pub fn take_io_item(&self) -> Option<PyTursoSyncIoItem> {\n        self.database\n            .take_io_item()\n            .map(|t| PyTursoSyncIoItem { item: t })\n    }\n}\n"
  },
  {
    "path": "bindings/python/tests/__init__.py",
    "content": ""
  },
  {
    "path": "bindings/python/tests/test_database.py",
    "content": "import logging\nimport os\nimport sqlite3\n\nimport pytest\nimport turso\n\nlogging.basicConfig(level=logging.INFO, format=\"%(asctime)s - %(levelname)s - %(message)s\", force=True)\n\n\ndef connect(provider, database):\n    if provider == \"turso\":\n        return turso.connect(database)\n    if provider == \"sqlite3\":\n        return sqlite3.connect(database)\n    raise Exception(f\"Provider `{provider}` is not supported\")\n\n\n@pytest.fixture(autouse=True)\ndef setup_database():\n    db_path = \"tests/database.db\"\n    db_wal_path = \"tests/database.db-wal\"\n\n    # Ensure the database file is created fresh for each test\n    try:\n        if os.path.exists(db_path):\n            os.remove(db_path)\n        if os.path.exists(db_wal_path):\n            os.remove(db_wal_path)\n    except PermissionError as e:\n        print(f\"Failed to clean up: {e}\")\n\n    # Create a new database file\n    conn = sqlite3.connect(db_path)\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE IF NOT EXISTS users (id INT PRIMARY KEY, username TEXT)\")\n    cursor.execute(\"\"\"\n        INSERT INTO users (id, username)\n        SELECT 1, 'alice'\n        WHERE NOT EXISTS (SELECT 1 FROM users WHERE id = 1)\n    \"\"\")\n    cursor.execute(\"\"\"\n        INSERT INTO users (id, username)\n        SELECT 2, 'bob'\n        WHERE NOT EXISTS (SELECT 1 FROM users WHERE id = 2)\n    \"\"\")\n    conn.commit()\n    conn.close()\n\n    yield db_path\n\n    # Cleanup after the test\n    try:\n        if os.path.exists(db_path):\n            os.remove(db_path)\n        if os.path.exists(db_wal_path):\n            os.remove(db_wal_path)\n    except PermissionError as e:\n        print(f\"Failed to clean up: {e}\")\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_fetchall_select_all_users(provider, setup_database):\n    conn = connect(provider, setup_database)\n    cursor = conn.cursor()\n    cursor.execute(\"SELECT * FROM users\")\n\n    users = cursor.fetchall()\n\n    conn.close()\n    assert users\n    assert users == [(1, \"alice\"), (2, \"bob\")]\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_fetchall_select_user_ids(provider):\n    conn = connect(provider, \"tests/database.db\")\n    cursor = conn.cursor()\n    cursor.execute(\"SELECT id FROM users\")\n\n    user_ids = cursor.fetchall()\n\n    conn.close()\n    assert user_ids\n    assert user_ids == [(1,), (2,)]\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_in_memory_fetchone_select_all_users(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT)\")\n    cursor.execute(\"INSERT INTO users VALUES (1, 'alice')\")\n\n    cursor.execute(\"SELECT * FROM users\")\n\n    alice = cursor.fetchone()\n\n    conn.close()\n    assert alice\n    assert alice == (1, \"alice\")\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_in_memory_index(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE users (name TEXT PRIMARY KEY, email TEXT)\")\n    cursor.execute(\"CREATE INDEX email_idx ON users(email)\")\n    cursor.execute(\"INSERT INTO users VALUES ('alice', 'a@b.c'), ('bob', 'b@d.e')\")\n\n    cursor.execute(\"SELECT * FROM users WHERE email = 'a@b.c'\")\n    alice = cursor.fetchall()\n\n    cursor.execute(\"SELECT * FROM users WHERE email = 'b@d.e'\")\n    bob = cursor.fetchall()\n\n    conn.close()\n    assert alice == [(\"alice\", \"a@b.c\")]\n    assert bob == [(\"bob\", \"b@d.e\")]\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_fetchone_select_all_users(provider):\n    conn = connect(provider, \"tests/database.db\")\n    cursor = conn.cursor()\n    cursor.execute(\"SELECT * FROM users\")\n\n    alice = cursor.fetchone()\n    assert alice\n    assert alice == (1, \"alice\")\n\n    bob = cursor.fetchone()\n\n    conn.close()\n    assert bob\n    assert bob == (2, \"bob\")\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_fetchone_select_max_user_id(provider):\n    conn = connect(provider, \"tests/database.db\")\n    cursor = conn.cursor()\n    cursor.execute(\"SELECT MAX(id) FROM users\")\n\n    max_id = cursor.fetchone()\n\n    conn.close()\n    assert max_id\n    assert max_id == (2,)\n\n\n# Test case for: https://github.com/tursodatabase/turso/issues/494\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_commit(provider):\n    conn = connect(provider, \"tests/database.db\")\n    cur = conn.cursor()\n\n    cur.execute(\"\"\"\n        CREATE TABLE IF NOT EXISTS users_b (\n            id INTEGER PRIMARY KEY AUTOINCREMENT,\n            username TEXT NOT NULL,\n            email TEXT NOT NULL,\n            role TEXT NOT NULL,\n            created_at DATETIME NOT NULL DEFAULT (datetime('now'))\n        )\n    \"\"\")\n\n    conn.commit()\n\n    sample_users = [\n        (\"alice\", \"alice@example.com\", \"admin\"),\n        (\"bob\", \"bob@example.com\", \"user\"),\n        (\"charlie\", \"charlie@example.com\", \"moderator\"),\n        (\"diana\", \"diana@example.com\", \"user\"),\n    ]\n\n    for username, email, role in sample_users:\n        cur.execute(\"INSERT INTO users_b (username, email, role) VALUES (?, ?, ?)\", (username, email, role))\n\n    conn.commit()\n\n    # Now query the table\n    res = cur.execute(\"SELECT * FROM users_b\")\n    record = res.fetchone()\n\n    conn.close()\n    assert record\n\n\n# Test case for: https://github.com/tursodatabase/turso/issues/2002\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_first_rollback(provider, tmp_path):\n    db_file = tmp_path / \"test_first_rollback.db\"\n\n    conn = connect(provider, str(db_file))\n    cur = conn.cursor()\n    cur.execute(\"CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT)\")\n    cur.execute(\"INSERT INTO users VALUES (1, 'alice')\")\n    cur.execute(\"INSERT INTO users VALUES (2, 'bob')\")\n\n    conn.rollback()\n\n    cur.execute(\"SELECT * FROM users\")\n    users = cur.fetchall()\n\n    assert users == []\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_with_statement(provider):\n    with connect(provider, \"tests/database.db\") as conn:\n        cursor = conn.cursor()\n        cursor.execute(\"SELECT MAX(id) FROM users\")\n\n        max_id = cursor.fetchone()\n\n        assert max_id\n        assert max_id == (2,)\n\n\n# DB-API 2.0 tests\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_cursor_description(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER, name TEXT, value REAL)\")\n    cursor.execute(\"INSERT INTO test VALUES (1, 'test', 3.14)\")\n    cursor.execute(\"SELECT * FROM test\")\n\n    assert cursor.description is not None\n    assert len(cursor.description) == 3\n    assert cursor.description[0][0] == \"id\"\n    assert cursor.description[1][0] == \"name\"\n    assert cursor.description[2][0] == \"value\"\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_cursor_rowcount_insert(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER, name TEXT)\")\n    cursor.execute(\"INSERT INTO test VALUES (1, 'alice'), (2, 'bob'), (3, 'charlie')\")\n\n    assert cursor.rowcount == 3\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_cursor_rowcount_update(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER, name TEXT)\")\n    cursor.execute(\"INSERT INTO test VALUES (1, 'alice'), (2, 'bob')\")\n    cursor.execute(\"UPDATE test SET name = 'updated' WHERE id = 1\")\n\n    assert cursor.rowcount == 1\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_cursor_rowcount_delete(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER, name TEXT)\")\n    cursor.execute(\"INSERT INTO test VALUES (1, 'alice'), (2, 'bob'), (3, 'charlie')\")\n    cursor.execute(\"DELETE FROM test WHERE id > 1\")\n\n    assert cursor.rowcount == 2\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_cursor_fetchmany(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER)\")\n    cursor.execute(\"INSERT INTO test VALUES (1), (2), (3), (4), (5)\")\n    cursor.execute(\"SELECT * FROM test\")\n\n    cursor.arraysize = 2\n    rows = cursor.fetchmany()\n    assert len(rows) == 2\n    assert rows == [(1,), (2,)]\n\n    rows = cursor.fetchmany(3)\n    assert len(rows) == 3\n    assert rows == [(3,), (4,), (5,)]\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_cursor_iterator(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER)\")\n    cursor.execute(\"INSERT INTO test VALUES (1), (2), (3)\")\n    cursor.execute(\"SELECT * FROM test\")\n\n    rows = list(cursor)\n    assert rows == [(1,), (2,), (3,)]\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_cursor_close(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER)\")\n    cursor.close()\n\n    with pytest.raises(Exception):\n        cursor.execute(\"SELECT * FROM test\")\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_connection_execute(provider):\n    conn = connect(provider, \":memory:\")\n    conn.execute(\"CREATE TABLE test (id INTEGER, name TEXT)\")\n    cursor = conn.execute(\"INSERT INTO test VALUES (?, ?)\", (1, \"alice\"))\n\n    assert cursor.rowcount == 1\n\n    cursor = conn.execute(\"SELECT * FROM test\")\n    rows = cursor.fetchall()\n    assert rows == [(1, \"alice\")]\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_connection_executemany(provider):\n    conn = connect(provider, \":memory:\")\n    conn.execute(\"CREATE TABLE test (id INTEGER, name TEXT)\")\n\n    data = [(1, \"alice\"), (2, \"bob\"), (3, \"charlie\")]\n    cursor = conn.executemany(\"INSERT INTO test VALUES (?, ?)\", data)\n\n    assert cursor.rowcount == 3\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_connection_executescript(provider):\n    conn = connect(provider, \":memory:\")\n    script = \"\"\"\n        CREATE TABLE test (id INTEGER, name TEXT);\n        INSERT INTO test VALUES (1, 'alice');\n        INSERT INTO test VALUES (2, 'bob');\n    \"\"\"\n    conn.executescript(script)\n\n    cursor = conn.execute(\"SELECT * FROM test\")\n    rows = cursor.fetchall()\n    assert len(rows) == 2\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_row_factory(provider):\n    conn = connect(provider, \":memory:\")\n    conn.row_factory = turso.Row if provider == \"turso\" else sqlite3.Row\n\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER, name TEXT)\")\n    cursor.execute(\"INSERT INTO test VALUES (1, 'alice')\")\n    cursor.execute(\"SELECT * FROM test\")\n\n    row = cursor.fetchone()\n    assert row[\"id\"] == 1\n    assert row[\"name\"] == \"alice\"\n    assert row[0] == 1\n    assert row[1] == \"alice\"\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_row_factory_keys(provider):\n    conn = connect(provider, \":memory:\")\n    conn.row_factory = turso.Row if provider == \"turso\" else sqlite3.Row\n\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER, name TEXT, value REAL)\")\n    cursor.execute(\"INSERT INTO test VALUES (1, 'alice', 3.14)\")\n    cursor.execute(\"SELECT * FROM test\")\n\n    row = cursor.fetchone()\n    keys = row.keys()\n    assert keys == [\"id\", \"name\", \"value\"]\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_parameterized_query(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER, name TEXT)\")\n    cursor.execute(\"INSERT INTO test VALUES (?, ?)\", (1, \"alice\"))\n    cursor.execute(\"INSERT INTO test VALUES (?, ?)\", (2, \"bob\"))\n\n    cursor.execute(\"SELECT * FROM test WHERE id = ?\", (1,))\n    row = cursor.fetchone()\n    assert row == (1, \"alice\")\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_executemany_with_parameters(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER, name TEXT)\")\n\n    data = [(1, \"alice\"), (2, \"bob\"), (3, \"charlie\")]\n    cursor.executemany(\"INSERT INTO test VALUES (?, ?)\", data)\n\n    cursor.execute(\"SELECT COUNT(*) FROM test\")\n    count = cursor.fetchone()[0]\n    assert count == 3\n\n    conn.close()\n\n\n# SQL tests\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_subquery(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER, value INTEGER)\")\n    cursor.execute(\"INSERT INTO test VALUES (1, 10), (2, 20), (3, 30)\")\n\n    cursor.execute(\"SELECT id FROM test WHERE value > (SELECT AVG(value) FROM test)\")\n    rows = cursor.fetchall()\n    assert rows == [(3,)]\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_insert_returning(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)\")\n    cursor.execute(\"INSERT INTO test (id, name) VALUES (1, 'alice') RETURNING id, name\")\n\n    row = cursor.fetchone()\n    assert row == (1, \"alice\")\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_insert_returning_partial_fetch(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)\")\n    cursor.execute(\"INSERT INTO test (id, name) VALUES (1, 'alice'), (2, 'bob'), (3, 'charlie') RETURNING id, name\")\n\n    row = cursor.fetchone()\n    assert row == (1, \"alice\")\n\n    cursor.close()\n\n    cursor = conn.cursor()\n    cursor.execute(\"SELECT COUNT(*) FROM test\")\n    count = cursor.fetchone()[0]\n    assert count == 3\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_conflict_clause_ignore(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)\")\n    cursor.execute(\"INSERT INTO test VALUES (1, 'alice')\")\n    cursor.execute(\"INSERT OR IGNORE INTO test VALUES (1, 'bob')\")\n\n    cursor.execute(\"SELECT * FROM test\")\n    rows = cursor.fetchall()\n    assert rows == [(1, \"alice\")]\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_conflict_clause_replace(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)\")\n    cursor.execute(\"INSERT INTO test VALUES (1, 'alice')\")\n    cursor.execute(\"INSERT OR REPLACE INTO test VALUES (1, 'bob')\")\n\n    cursor.execute(\"SELECT * FROM test\")\n    rows = cursor.fetchall()\n    assert rows == [(1, \"bob\")]\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_conflict_clause_rollback(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)\")\n    cursor.execute(\"INSERT INTO test VALUES (1, 'alice')\")\n\n    try:\n        cursor.execute(\"INSERT OR ROLLBACK INTO test VALUES (1, 'bob')\")\n    except Exception:\n        pass\n\n    cursor.execute(\"SELECT * FROM test\")\n    rows = cursor.fetchall()\n    assert len(rows) <= 1\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_drop_table(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER)\")\n    cursor.execute(\"DROP TABLE test\")\n\n    try:\n        cursor.execute(\"SELECT * FROM test\")\n        assert False, \"Table should not exist\"\n    except Exception:\n        pass\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_alter_table_add_column(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER)\")\n    cursor.execute(\"INSERT INTO test VALUES (1)\")\n    cursor.execute(\"ALTER TABLE test ADD COLUMN name TEXT\")\n    cursor.execute(\"UPDATE test SET name = 'alice' WHERE id = 1\")\n\n    cursor.execute(\"SELECT * FROM test\")\n    row = cursor.fetchone()\n    assert row == (1, \"alice\")\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_alter_table_rename(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER)\")\n    cursor.execute(\"INSERT INTO test VALUES (1)\")\n    cursor.execute(\"ALTER TABLE test RENAME TO new_test\")\n\n    cursor.execute(\"SELECT * FROM new_test\")\n    row = cursor.fetchone()\n    assert row == (1,)\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_inner_join(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE users (id INTEGER, name TEXT)\")\n    cursor.execute(\"CREATE TABLE orders (id INTEGER, user_id INTEGER, item TEXT)\")\n    cursor.execute(\"INSERT INTO users VALUES (1, 'alice'), (2, 'bob')\")\n    cursor.execute(\"INSERT INTO orders VALUES (1, 1, 'book'), (2, 1, 'pen'), (3, 2, 'notebook')\")\n\n    cursor.execute(\"\"\"\n        SELECT users.name, orders.item\n        FROM users\n        INNER JOIN orders ON users.id = orders.user_id\n        WHERE users.id = 1\n    \"\"\")\n    rows = cursor.fetchall()\n    assert rows == [(\"alice\", \"book\"), (\"alice\", \"pen\")]\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_left_join(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE users (id INTEGER, name TEXT)\")\n    cursor.execute(\"CREATE TABLE orders (id INTEGER, user_id INTEGER, item TEXT)\")\n    cursor.execute(\"INSERT INTO users VALUES (1, 'alice'), (2, 'bob'), (3, 'charlie')\")\n    cursor.execute(\"INSERT INTO orders VALUES (1, 1, 'book'), (2, 2, 'pen')\")\n\n    cursor.execute(\"\"\"\n        SELECT users.name, orders.item\n        FROM users\n        LEFT JOIN orders ON users.id = orders.user_id\n    \"\"\")\n    rows = cursor.fetchall()\n    assert len(rows) == 3\n    assert (\"charlie\", None) in rows\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_json_extract(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER, data TEXT)\")\n    cursor.execute('INSERT INTO test VALUES (1, \\'{\"name\": \"alice\", \"age\": 30}\\')')\n\n    cursor.execute(\"SELECT json_extract(data, '$.name') FROM test\")\n    row = cursor.fetchone()\n    assert row[0] == \"alice\"\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_json_array(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"SELECT json_array(1, 2, 3)\")\n\n    row = cursor.fetchone()\n    assert row[0] == \"[1,2,3]\"\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_json_object(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"SELECT json_object('name', 'alice', 'age', 30)\")\n\n    row = cursor.fetchone()\n    assert \"alice\" in row[0]\n    assert \"30\" in row[0]\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_aggregate_functions(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (value INTEGER)\")\n    cursor.execute(\"INSERT INTO test VALUES (10), (20), (30), (40)\")\n\n    cursor.execute(\"SELECT AVG(value), SUM(value), MIN(value), MAX(value), COUNT(*) FROM test\")\n    row = cursor.fetchone()\n    assert row == (25.0, 100, 10, 40, 4)\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_group_by(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (category TEXT, value INTEGER)\")\n    cursor.execute(\"INSERT INTO test VALUES ('A', 10), ('A', 20), ('B', 30), ('B', 40)\")\n\n    cursor.execute(\"SELECT category, SUM(value) FROM test GROUP BY category\")\n    rows = cursor.fetchall()\n    assert rows == [(\"A\", 30), (\"B\", 70)]\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_having_clause(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (category TEXT, value INTEGER)\")\n    cursor.execute(\"INSERT INTO test VALUES ('A', 10), ('A', 20), ('B', 5), ('B', 10)\")\n\n    cursor.execute(\"SELECT category, SUM(value) as total FROM test GROUP BY category HAVING total > 20\")\n    rows = cursor.fetchall()\n    assert rows == [(\"A\", 30)]\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_create_view(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER, value INTEGER)\")\n    cursor.execute(\"INSERT INTO test VALUES (1, 10), (2, 20), (3, 30)\")\n    cursor.execute(\"CREATE VIEW test_view AS SELECT id, value * 2 as doubled FROM test\")\n\n    cursor.execute(\"SELECT * FROM test_view WHERE id = 2\")\n    row = cursor.fetchone()\n    assert row == (2, 40)\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_drop_view(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER)\")\n    cursor.execute(\"CREATE VIEW test_view AS SELECT * FROM test\")\n    cursor.execute(\"DROP VIEW test_view\")\n\n    try:\n        cursor.execute(\"SELECT * FROM test_view\")\n        assert False, \"View should not exist\"\n    except Exception:\n        pass\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_with_cte(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER, value INTEGER)\")\n    cursor.execute(\"INSERT INTO test VALUES (1, 10), (2, 20), (3, 30)\")\n\n    cursor.execute(\"\"\"\n        WITH doubled AS (SELECT id, value * 2 as doubled_value FROM test)\n        SELECT * FROM doubled WHERE doubled_value > 30\n    \"\"\")\n    rows = cursor.fetchall()\n    assert rows == [(2, 40), (3, 60)]\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_case_expression(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER, value INTEGER)\")\n    cursor.execute(\"INSERT INTO test VALUES (1, 10), (2, 20), (3, 30)\")\n\n    cursor.execute(\"\"\"\n        SELECT id,\n               CASE\n                   WHEN value < 15 THEN 'low'\n                   WHEN value < 25 THEN 'medium'\n                   ELSE 'high'\n               END as category\n        FROM test\n    \"\"\")\n    rows = cursor.fetchall()\n    assert rows == [(1, \"low\"), (2, \"medium\"), (3, \"high\")]\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_between_operator(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER, value INTEGER)\")\n    cursor.execute(\"INSERT INTO test VALUES (1, 10), (2, 20), (3, 30), (4, 40)\")\n\n    cursor.execute(\"SELECT * FROM test WHERE value BETWEEN 15 AND 35\")\n    rows = cursor.fetchall()\n    assert rows == [(2, 20), (3, 30)]\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_in_operator(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER, name TEXT)\")\n    cursor.execute(\"INSERT INTO test VALUES (1, 'alice'), (2, 'bob'), (3, 'charlie')\")\n\n    cursor.execute(\"SELECT * FROM test WHERE name IN ('alice', 'charlie')\")\n    rows = cursor.fetchall()\n    assert rows == [(1, \"alice\"), (3, \"charlie\")]\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_like_operator(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER, name TEXT)\")\n    cursor.execute(\"INSERT INTO test VALUES (1, 'alice'), (2, 'bob'), (3, 'alicia')\")\n\n    cursor.execute(\"SELECT * FROM test WHERE name LIKE 'ali%'\")\n    rows = cursor.fetchall()\n    assert len(rows) == 2\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_glob_operator(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER, name TEXT)\")\n    cursor.execute(\"INSERT INTO test VALUES (1, 'alice'), (2, 'bob'), (3, 'alicia')\")\n\n    cursor.execute(\"SELECT * FROM test WHERE name GLOB 'ali*'\")\n    rows = cursor.fetchall()\n    assert len(rows) == 2\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_exists_operator(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE users (id INTEGER, name TEXT)\")\n    cursor.execute(\"CREATE TABLE orders (id INTEGER, user_id INTEGER)\")\n    cursor.execute(\"INSERT INTO users VALUES (1, 'alice'), (2, 'bob')\")\n    cursor.execute(\"INSERT INTO orders VALUES (1, 1)\")\n\n    cursor.execute(\"\"\"\n        SELECT name FROM users\n        WHERE EXISTS (SELECT 1 FROM orders WHERE orders.user_id = users.id)\n    \"\"\")\n    rows = cursor.fetchall()\n    assert rows == [(\"alice\",)]\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_transaction_begin_commit(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER, value TEXT)\")\n\n    cursor.execute(\"BEGIN\")\n    cursor.execute(\"INSERT INTO test VALUES (1, 'test')\")\n    cursor.execute(\"COMMIT\")\n\n    cursor.execute(\"SELECT * FROM test\")\n    rows = cursor.fetchall()\n    assert rows == [(1, \"test\")]\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_transaction_begin_rollback(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER, value TEXT)\")\n\n    cursor.execute(\"BEGIN\")\n    cursor.execute(\"INSERT INTO test VALUES (2, 'rollback')\")\n    cursor.execute(\"ROLLBACK\")\n\n    cursor.execute(\"SELECT * FROM test\")\n    rows = cursor.fetchall()\n    assert rows == []\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_multiple_cursors_same_connection(provider):\n    conn = connect(provider, \":memory:\")\n    cursor1 = conn.cursor()\n    cursor2 = conn.cursor()\n\n    cursor1.execute(\"CREATE TABLE test (id INTEGER)\")\n    cursor1.execute(\"INSERT INTO test VALUES (1), (2)\")\n\n    cursor2.execute(\"SELECT * FROM test\")\n    rows = cursor2.fetchall()\n    assert len(rows) == 2\n\n    cursor1.close()\n    cursor2.close()\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_cursor_description_before_execute(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n\n    assert cursor.description is None\n\n    cursor.execute(\"CREATE TABLE test (id INTEGER)\")\n    cursor.execute(\"SELECT * FROM test\")\n\n    assert cursor.description is not None\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_cursor_arraysize_default(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n\n    assert cursor.arraysize == 1\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_empty_fetchall(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER)\")\n    cursor.execute(\"SELECT * FROM test\")\n\n    rows = cursor.fetchall()\n    assert rows == []\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_empty_fetchone(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER)\")\n    cursor.execute(\"SELECT * FROM test\")\n\n    row = cursor.fetchone()\n    assert row is None\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_empty_fetchmany(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER)\")\n    cursor.execute(\"SELECT * FROM test\")\n\n    rows = cursor.fetchmany(5)\n    assert rows == []\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_unicode_data(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER, text TEXT)\")\n    cursor.execute(\"INSERT INTO test VALUES (?, ?)\", (1, \"Hello 世界 🌍\"))\n\n    cursor.execute(\"SELECT text FROM test\")\n    row = cursor.fetchone()\n    assert row[0] == \"Hello 世界 🌍\"\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_null_values(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER, value TEXT)\")\n    cursor.execute(\"INSERT INTO test VALUES (1, NULL)\")\n\n    cursor.execute(\"SELECT * FROM test\")\n    row = cursor.fetchone()\n    assert row == (1, None)\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_blob_data(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER, data BLOB)\")\n\n    blob_data = b\"\\x00\\x01\\x02\\x03\\x04\"\n    cursor.execute(\"INSERT INTO test VALUES (?, ?)\", (1, blob_data))\n\n    cursor.execute(\"SELECT data FROM test\")\n    row = cursor.fetchone()\n    assert row[0] == blob_data\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_limit_offset(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER)\")\n    cursor.execute(\"INSERT INTO test VALUES (1), (2), (3), (4), (5)\")\n\n    cursor.execute(\"SELECT * FROM test LIMIT 2 OFFSET 2\")\n    rows = cursor.fetchall()\n    assert rows == [(3,), (4,)]\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_order_by_desc(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER, value INTEGER)\")\n    cursor.execute(\"INSERT INTO test VALUES (1, 30), (2, 10), (3, 20)\")\n\n    cursor.execute(\"SELECT * FROM test ORDER BY value DESC\")\n    rows = cursor.fetchall()\n    assert rows == [(1, 30), (3, 20), (2, 10)]\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_distinct(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (value TEXT)\")\n    cursor.execute(\"INSERT INTO test VALUES ('a'), ('b'), ('a'), ('c'), ('b')\")\n\n    cursor.execute(\"SELECT DISTINCT value FROM test ORDER BY value\")\n    rows = cursor.fetchall()\n    assert rows == [(\"a\",), (\"b\",), (\"c\",)]\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_coalesce_function(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER, a TEXT, b TEXT)\")\n    cursor.execute(\"INSERT INTO test VALUES (1, NULL, 'fallback'), (2, 'value', 'fallback')\")\n\n    cursor.execute(\"SELECT COALESCE(a, b) FROM test ORDER BY id\")\n    rows = cursor.fetchall()\n    assert rows == [(\"fallback\",), (\"value\",)]\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_union_operator(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE t1 (value INTEGER)\")\n    cursor.execute(\"CREATE TABLE t2 (value INTEGER)\")\n    cursor.execute(\"INSERT INTO t1 VALUES (1), (2)\")\n    cursor.execute(\"INSERT INTO t2 VALUES (2), (3)\")\n\n    cursor.execute(\"SELECT value FROM t1 UNION SELECT value FROM t2\")\n    rows = cursor.fetchall()\n    assert len(rows) == 3\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_union_all_operator(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE t1 (value INTEGER)\")\n    cursor.execute(\"CREATE TABLE t2 (value INTEGER)\")\n    cursor.execute(\"INSERT INTO t1 VALUES (1), (2)\")\n    cursor.execute(\"INSERT INTO t2 VALUES (2), (3)\")\n\n    cursor.execute(\"SELECT value FROM t1 UNION ALL SELECT value FROM t2\")\n    rows = cursor.fetchall()\n    assert len(rows) == 4\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_is_null_operator(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER, value TEXT)\")\n    cursor.execute(\"INSERT INTO test VALUES (1, 'a'), (2, NULL), (3, 'c')\")\n\n    cursor.execute(\"SELECT id FROM test WHERE value IS NULL\")\n    rows = cursor.fetchall()\n    assert rows == [(2,)]\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_is_not_null_operator(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER, value TEXT)\")\n    cursor.execute(\"INSERT INTO test VALUES (1, 'a'), (2, NULL), (3, 'c')\")\n\n    cursor.execute(\"SELECT id FROM test WHERE value IS NOT NULL ORDER BY id\")\n    rows = cursor.fetchall()\n    assert rows == [(1,), (3,)]\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_not_in_operator(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER, name TEXT)\")\n    cursor.execute(\"INSERT INTO test VALUES (1, 'alice'), (2, 'bob'), (3, 'charlie')\")\n\n    cursor.execute(\"SELECT * FROM test WHERE name NOT IN ('alice', 'charlie') ORDER BY id\")\n    rows = cursor.fetchall()\n    assert rows == [(2, \"bob\")]\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_not_exists_operator(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE users (id INTEGER, name TEXT)\")\n    cursor.execute(\"CREATE TABLE orders (id INTEGER, user_id INTEGER)\")\n    cursor.execute(\"INSERT INTO users VALUES (1, 'alice'), (2, 'bob')\")\n    cursor.execute(\"INSERT INTO orders VALUES (1, 1)\")\n\n    cursor.execute(\"\"\"\n        SELECT name FROM users\n        WHERE NOT EXISTS (SELECT 1 FROM orders WHERE orders.user_id = users.id)\n    \"\"\")\n    rows = cursor.fetchall()\n    assert rows == [(\"bob\",)]\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_substr_function(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"SELECT substr('Hello World', 1, 5)\")\n\n    row = cursor.fetchone()\n    assert row[0] == \"Hello\"\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_length_function(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"SELECT length('Hello')\")\n\n    row = cursor.fetchone()\n    assert row[0] == 5\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_upper_lower_functions(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"SELECT upper('hello'), lower('WORLD')\")\n\n    row = cursor.fetchone()\n    assert row == (\"HELLO\", \"world\")\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_trim_functions(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"SELECT trim('  hello  '), ltrim('  hello'), rtrim('hello  ')\")\n\n    row = cursor.fetchone()\n    assert row == (\"hello\", \"hello\", \"hello\")\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_replace_function(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"SELECT replace('Hello World', 'World', 'Python')\")\n\n    row = cursor.fetchone()\n    assert row[0] == \"Hello Python\"\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_abs_function(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"SELECT abs(-42), abs(42)\")\n\n    row = cursor.fetchone()\n    assert row == (42, 42)\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_typeof_function(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"SELECT typeof(123), typeof('text'), typeof(NULL), typeof(3.14)\")\n\n    row = cursor.fetchone()\n    assert row == (\"integer\", \"text\", \"null\", \"real\")\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_create_table_if_not_exists(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE IF NOT EXISTS test (id INTEGER)\")\n    cursor.execute(\"CREATE TABLE IF NOT EXISTS test (id INTEGER)\")\n\n    cursor.execute(\"SELECT name FROM sqlite_master WHERE type='table' AND name='test'\")\n    row = cursor.fetchone()\n    assert row is not None\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_drop_table_if_exists(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER)\")\n    cursor.execute(\"DROP TABLE IF EXISTS test\")\n    cursor.execute(\"DROP TABLE IF EXISTS test\")\n\n    cursor.execute(\"SELECT name FROM sqlite_master WHERE type='table' AND name='test'\")\n    row = cursor.fetchone()\n    assert row is None\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_multiple_ctes(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (value INTEGER)\")\n    cursor.execute(\"INSERT INTO test VALUES (10), (20), (30)\")\n\n    cursor.execute(\"\"\"\n        WITH\n            doubled AS (SELECT value * 2 as v FROM test),\n            tripled AS (SELECT value * 3 as v FROM test)\n        SELECT doubled.v, tripled.v FROM doubled, tripled WHERE doubled.v = 20 AND tripled.v = 30\n    \"\"\")\n    row = cursor.fetchone()\n    assert row == (20, 30)\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_nested_subqueries(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER, value INTEGER)\")\n    cursor.execute(\"INSERT INTO test VALUES (1, 10), (2, 20), (3, 30), (4, 40)\")\n\n    cursor.execute(\"\"\"\n        SELECT id FROM test\n        WHERE value > (\n            SELECT AVG(value) FROM test\n            WHERE value > (SELECT MIN(value) FROM test)\n        )\n    \"\"\")\n    rows = cursor.fetchall()\n    assert len(rows) > 0\n\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_correlated_subquery(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE test (id INTEGER, category TEXT, value INTEGER)\")\n    cursor.execute(\"INSERT INTO test VALUES (1, 'A', 10), (2, 'A', 20), (3, 'B', 15), (4, 'B', 25)\")\n\n    cursor.execute(\"\"\"\n        SELECT t1.id, t1.value\n        FROM test t1\n        WHERE t1.value > (SELECT AVG(t2.value) FROM test t2 WHERE t2.category = t1.category)\n    \"\"\")\n    rows = cursor.fetchall()\n    assert len(rows) == 2\n\n    conn.close()\n\n\n# Additional tests appended\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_executemany_requires_dml(provider):\n    conn = connect(provider, \":memory:\")\n    cur = conn.cursor()\n    with pytest.raises(Exception):\n        cur.executemany(\"SELECT 1\", [()])\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_execute_multiple_statements_prohibited(provider):\n    conn = connect(provider, \":memory:\")\n    cur = conn.cursor()\n    with pytest.raises(Exception):\n        cur.execute(\"SELECT 1; SELECT 2\")\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_description_none_after_insert(provider):\n    conn = connect(provider, \":memory:\")\n    cur = conn.cursor()\n    cur.execute(\"CREATE TABLE test (id INTEGER)\")\n    cur.execute(\"INSERT INTO test VALUES (1), (2)\")\n    assert cur.description is None\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_rowcount_select_is_minus_one(provider):\n    conn = connect(provider, \":memory:\")\n    cur = conn.cursor()\n    cur.execute(\"CREATE TABLE test (id INTEGER)\")\n    cur.execute(\"INSERT INTO test VALUES (1), (2)\")\n    cur.execute(\"SELECT * FROM test\")\n    assert cur.rowcount == -1\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_cursor_setinput_output_size_noop(provider):\n    conn = connect(provider, \":memory:\")\n    cur = conn.cursor()\n    # Should not raise\n    cur.setinputsizes([None])\n    cur.setoutputsize(1024, 0)\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_custom_row_factory_callable(provider):\n    conn = connect(provider, \":memory:\")\n\n    # row factory that returns a dict for each row\n    def dict_factory(cursor, row):\n        return {cursor.description[i][0]: row[i] for i in range(len(row))}\n\n    conn.row_factory = dict_factory\n    cur = conn.cursor()\n    cur.execute(\"CREATE TABLE test (id INTEGER, name TEXT)\")\n    cur.execute(\"INSERT INTO test VALUES (1, 'alice')\")\n    cur.execute(\"SELECT * FROM test\")\n    row = cur.fetchone()\n    assert isinstance(row, dict)\n    assert row[\"id\"] == 1\n    assert row[\"name\"] == \"alice\"\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_in_transaction_toggle_with_commit(provider):\n    conn = connect(provider, \":memory:\")\n    cur = conn.cursor()\n    cur.execute(\"CREATE TABLE test (id INTEGER)\")\n    # Before DML, should not be in a transaction\n    assert not conn.in_transaction\n    # DML should start a transaction in legacy mode\n    cur.execute(\"INSERT INTO test VALUES (1)\")\n    assert conn.in_transaction\n    conn.commit()\n    assert not conn.in_transaction\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_isolation_level_none_autocommit(provider, tmp_path):\n    db_file = tmp_path / \"auto_commit.db\"\n\n    if provider == \"turso\":\n        conn = turso.connect(str(db_file), isolation_level=None)\n    else:\n        conn = sqlite3.connect(str(db_file))\n        conn.isolation_level = None\n\n    cur = conn.cursor()\n    cur.execute(\"CREATE TABLE test (id INTEGER)\")\n    cur.execute(\"INSERT INTO test VALUES (1)\")\n    # No explicit commit; in autocommit mode data should persist\n    conn.close()\n\n    conn2 = connect(provider, str(db_file))\n    cur2 = conn2.cursor()\n    cur2.execute(\"SELECT COUNT(*) FROM test\")\n    count = cur2.fetchone()[0]\n    assert count == 1\n    conn2.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_generate_series_virtual_table(provider):\n    conn = connect(provider, \":memory:\")\n    cur = conn.cursor()\n    if provider == \"turso\":\n        cur.execute(\"SELECT value FROM generate_series(1, 3)\")\n        rows = cur.fetchall()\n        assert rows == [(1,), (2,), (3,)]\n    else:\n        with pytest.raises(Exception):\n            cur.execute(\"SELECT value FROM generate_series(1, 3)\")\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_connection_exception_attributes_present(provider):\n    conn = connect(provider, \":memory:\")\n    # Ensure DB-API exception classes are exposed on the connection\n    assert issubclass(conn.Error, Exception)\n    assert issubclass(conn.DatabaseError, Exception)\n    assert issubclass(conn.ProgrammingError, Exception)\n    conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_insert_returning_single_and_multiple_commit_without_consuming(provider):\n    # turso.setup_logging(level=logging.DEBUG)\n    conn = connect(provider, \":memory:\")\n    try:\n        cur = conn.cursor()\n        cur.execute(\"CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT)\")\n        cur.execute(\n            \"INSERT INTO t(name) VALUES (?), (?) RETURNING id\",\n            (\"bob\", \"alice\"),\n        )\n        cur.fetchone()\n        with pytest.raises(Exception):\n            conn.commit()\n    finally:\n        conn.close()\n\n\n@pytest.mark.parametrize(\"provider\", [\"sqlite3\", \"turso\"])\ndef test_pragma_integrity_check(provider):\n    conn = connect(provider, \":memory:\")\n    cursor = conn.cursor()\n    cursor.execute(\"PRAGMA integrity_check\")\n\n    # Verify fetchone returns the expected result, not None\n    # Bug: missing add_pragma_result_column in translate_integrity_check\n    # caused column_count to be 0, making execute() finalize the statement\n    # and leaving fetchone() to return None\n    row = cursor.fetchone()\n    assert row is not None, \"PRAGMA integrity_check should return a row\"\n    assert row == (\"ok\",)\n\n    conn.close()\n\ndef test_encryption_enabled(tmp_path):\n    tmp_path = tmp_path / \"local.db\"\n    conn = turso.connect(\n        str(tmp_path),\n        experimental_features=\"encryption\",\n        encryption=turso.EncryptionOpts(\n            cipher=\"aegis256\", hexkey=\"b1bbfda4f589dc9daaf004fe21111e00dc00c98237102f5c7002a5669fc76327\"\n        ),\n    )\n    cursor = conn.cursor()\n    cursor.execute(\"create table t(x)\")\n    cursor.execute(\"insert into t select 'secret' from generate_series(1, 1024)\")\n    conn.commit()\n    cursor.execute(\"pragma wal_checkpoint(truncate)\").fetchall()\n    conn.commit()\n\n    content = open(tmp_path, \"rb\").read()\n    assert len(content) > 16 * 1024\n    assert b\"secret\" not in content\n\n\ndef test_encryption_disabled(tmp_path):\n    tmp_path = tmp_path / \"local.db\"\n    conn = turso.connect(\n        str(tmp_path),\n    )\n    cursor = conn.cursor()\n    cursor.execute(\"create table t(x)\")\n    cursor.execute(\"insert into t select 'secret' from generate_series(1, 1024)\")\n    conn.commit()\n    cursor.execute(\"pragma wal_checkpoint(truncate)\").fetchall()\n    conn.commit()\n\n    content = open(tmp_path, \"rb\").read()\n    assert len(content) > 16 * 1024\n    assert b\"secret\" in content\n\n\ndef test_encryption(tmp_path):\n    tmp_path = tmp_path / \"local.db\"\n    hexkey = \"b1bbfda4f589dc9daaf004fe21111e00dc00c98237102f5c7002a5669fc76327\"\n    wrong_key = \"aaaaaaa4f589dc9daaf004fe21111e00dc00c98237102f5c7002a5669fc76327\"\n\n    conn = turso.connect(\n        str(tmp_path),\n        experimental_features=\"encryption\",\n        encryption=turso.EncryptionOpts(cipher=\"aegis256\", hexkey=hexkey),\n    )\n    cursor = conn.cursor()\n    cursor.execute(\"create table t(x)\")\n    cursor.execute(\"insert into t select 'secret' from generate_series(1, 1024)\")\n    conn.commit()\n    cursor.execute(\"pragma wal_checkpoint(truncate)\").fetchall()\n    conn.commit()\n    conn.close()\n\n    # verify we can re-open with the same key\n    conn2 = turso.connect(\n        str(tmp_path),\n        experimental_features=\"encryption\",\n        encryption=turso.EncryptionOpts(cipher=\"aegis256\", hexkey=hexkey),\n    )\n    cursor2 = conn2.cursor()\n    cursor2.execute(\"select count(*) from t\")\n    assert cursor2.fetchone()[0] == 1024\n    conn2.close()\n\n    # verify opening with wrong key fails\n    with pytest.raises(Exception):\n        conn3 = turso.connect(\n            str(tmp_path),\n            experimental_features=\"encryption\",\n            encryption=turso.EncryptionOpts(cipher=\"aegis256\", hexkey=wrong_key),\n        )\n        cursor3 = conn3.cursor()\n        cursor3.execute(\"select * from t\")\n        cursor3.fetchone()  # trigger actual data read to cause decryption error\n\n    # verify opening without encryption fails\n    with pytest.raises(Exception):\n        conn5 = turso.connect(str(tmp_path))\n        cursor5 = conn5.cursor()\n        cursor5.execute(\"select * from t\")\n        cursor5.fetchone()  # trigger actual data read to cause decryption error\n"
  },
  {
    "path": "bindings/python/tests/test_database_aio.py",
    "content": "import asyncio\nimport logging\nimport os\nimport sqlite3\n\nimport pytest\nimport turso.aio\n\nlogging.basicConfig(level=logging.INFO, format=\"%(asctime)s - %(levelname)s - %(message)s\", force=True)\n\n\n@pytest.fixture(autouse=True)\ndef setup_database():\n    db_path = \"tests/database.db\"\n    db_wal_path = \"tests/database.db-wal\"\n\n    # Ensure the database file is created fresh for each test\n    try:\n        if os.path.exists(db_path):\n            os.remove(db_path)\n        if os.path.exists(db_wal_path):\n            os.remove(db_wal_path)\n    except PermissionError as e:\n        print(f\"Failed to clean up: {e}\")\n\n    # Create a new database file\n    conn = sqlite3.connect(db_path)\n    cursor = conn.cursor()\n    cursor.execute(\"CREATE TABLE IF NOT EXISTS users (id INT PRIMARY KEY, username TEXT)\")\n    cursor.execute(\"\"\"\n        INSERT INTO users (id, username)\n        SELECT 1, 'alice'\n        WHERE NOT EXISTS (SELECT 1 FROM users WHERE id = 1)\n    \"\"\")\n    cursor.execute(\"\"\"\n        INSERT INTO users (id, username)\n        SELECT 2, 'bob'\n        WHERE NOT EXISTS (SELECT 1 FROM users WHERE id = 2)\n    \"\"\")\n    conn.commit()\n    conn.close()\n\n    yield db_path\n\n    # Cleanup after the test\n    try:\n        if os.path.exists(db_path):\n            os.remove(db_path)\n        if os.path.exists(db_wal_path):\n            os.remove(db_wal_path)\n    except PermissionError as e:\n        print(f\"Failed to clean up: {e}\")\n\n\n@pytest.mark.asyncio\nasync def test_connection_execute_helpers_and_context_manager():\n    async with turso.aio.connect(\":memory:\") as conn:\n        await conn.executescript(\"\"\"\n            CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);\n            INSERT INTO t(name) VALUES ('alice');\n        \"\"\")\n        cur = await conn.execute(\"SELECT COUNT(*) FROM t\")\n        count = (await cur.fetchone())[0]\n        assert count == 1\n\n\n@pytest.mark.asyncio\nasync def test_subqueries_and_join():\n    conn = await turso.aio.connect(\":memory:\")\n    try:\n        cur = conn.cursor()\n        await cur.executescript(\"\"\"\n            CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT);\n            CREATE TABLE profiles (user_id INTEGER, city TEXT);\n            INSERT INTO users (id, username) VALUES (1, 'alice'), (2, 'bob'), (3, 'adam');\n            INSERT INTO profiles (user_id, city) VALUES (1, 'NY'), (2, 'SF'), (3, 'LA');\n        \"\"\")\n\n        # Subquery in WHERE\n        await cur.execute(\"\"\"\n            SELECT username FROM users\n            WHERE id IN (SELECT id FROM users WHERE username LIKE 'a%')\n            ORDER BY username\n        \"\"\")\n        rows = await cur.fetchall()\n        assert [r[0] for r in rows] == [\"adam\", \"alice\"]\n\n        # JOIN with subquery\n        await cur.execute(\n            \"\"\"\n            SELECT u.username, p.city\n            FROM users u\n            JOIN (SELECT user_id, city FROM profiles) p\n            ON u.id = p.user_id\n            WHERE u.username = ?\n        \"\"\",\n            (\"alice\",),\n        )\n        row = await cur.fetchone()\n        assert row == (\"alice\", \"NY\")\n    finally:\n        await conn.close()\n\n\n@pytest.mark.asyncio\nasync def test_conflict_do_nothing_and_rowcount():\n    conn = await turso.aio.connect(\":memory:\")\n    try:\n        cur = conn.cursor()\n        await cur.execute(\"CREATE TABLE t (name TEXT PRIMARY KEY, val INT)\")\n        await cur.execute(\"INSERT INTO t(name, val) VALUES (?, ?)\", (\"x\", 1))\n        await conn.commit()\n\n        # Conflict should not raise and rowcount should reflect 0 affected rows\n        await cur.execute(\"INSERT INTO t(name, val) VALUES (?, ?) ON CONFLICT(name) DO NOTHING\", (\"x\", 2))\n        assert cur.rowcount == 0\n\n        await cur.execute(\"SELECT val FROM t WHERE name = ?\", (\"x\",))\n        val = (await cur.fetchone())[0]\n        assert val == 1\n    finally:\n        await conn.close()\n\n\n@pytest.mark.asyncio\nasync def test_ddl_alter_table_add_column_with_default():\n    conn = await turso.aio.connect(\":memory:\")\n    try:\n        cur = conn.cursor()\n        await cur.executescript(\"\"\"\n            CREATE TABLE items (id INT PRIMARY KEY, name TEXT);\n            ALTER TABLE items ADD COLUMN price INT DEFAULT 0;\n            INSERT INTO items (id, name) VALUES (1, 'a'), (2, 'b');\n        \"\"\")\n        await cur.execute(\"SELECT price FROM items WHERE id = 1\")\n        assert (await cur.fetchone())[0] == 0\n\n        # Update and verify\n        await cur.execute(\"UPDATE items SET price = ? WHERE id = ?\", (10, 2))\n        await conn.commit()\n        await cur.execute(\"SELECT price FROM items WHERE id = 2\")\n        assert (await cur.fetchone())[0] == 10\n    finally:\n        await conn.close()\n\n\n@pytest.mark.asyncio\nasync def test_generate_series_virtual_table_and_join():\n    conn = await turso.aio.connect(\":memory:\")\n    try:\n        cur = conn.cursor()\n\n        # Simple usage\n        await cur.execute(\"SELECT sum(value) FROM generate_series(1, 100)\")\n        total = (await cur.fetchone())[0]\n        assert total == 5050\n\n        # Join with a real table\n        await cur.executescript(\"\"\"\n            CREATE TABLE nums (n INT PRIMARY KEY);\n            INSERT INTO nums (n) VALUES (1), (3), (5);\n        \"\"\")\n        await cur.execute(\"\"\"\n            SELECT gs.value\n            FROM generate_series(1, 5) AS gs\n            JOIN nums ON nums.n = gs.value\n            ORDER BY gs.value\n        \"\"\")\n        assert [r[0] for r in await cur.fetchall()] == [1, 3, 5]\n    finally:\n        await conn.close()\n\n\n@pytest.mark.asyncio\nasync def test_json_functions_extract_patch_and_array_length():\n    conn = await turso.aio.connect(\":memory:\")\n    try:\n        cur = conn.cursor()\n        await cur.execute(\"CREATE TABLE docs (id INT PRIMARY KEY, doc TEXT)\")\n        obj = '{\"a\":1,\"b\":{\"c\":2},\"arr\":[10,20]}'\n        await cur.execute(\"INSERT INTO docs (id, doc) VALUES (?, ?)\", (1, obj))\n\n        await cur.execute(\"SELECT json_extract(doc, '$.b.c') FROM docs WHERE id = 1\")\n        assert (await cur.fetchone())[0] == 2\n\n        await cur.execute(\"SELECT json_extract(json_patch(doc, '{\\\"a\\\":5}'), '$.a') FROM docs WHERE id = 1\")\n        assert (await cur.fetchone())[0] == 5\n\n        await cur.execute(\"SELECT json_array_length(doc, '$.arr') FROM docs WHERE id = 1\")\n        assert (await cur.fetchone())[0] == 2\n    finally:\n        await conn.close()\n\n\n@pytest.mark.asyncio\nasync def test_async_operations_do_not_block_event_loop():\n    import time\n\n    async with turso.aio.connect(\":memory:\") as conn:\n        count = 1_000_000\n        await conn.execute(\"CREATE TABLE t (id INTEGER)\")\n        cur = await conn.execute(f\"\"\"SELECT SUM(value) FROM generate_series(1, {count})\"\"\")\n        start = time.time()\n        task = asyncio.create_task(cur.fetchone())\n        assert (time.time() - start) < 0.001\n        assert await task == (count * (count + 1) // 2,)\n\n\n@pytest.mark.asyncio\nasync def test_operation_after_connection_close_raises():\n    import turso as _t\n\n    conn = await turso.aio.connect(\":memory:\")\n    cur = conn.cursor()\n    await cur.execute(\"CREATE TABLE t (id INT)\")\n    await conn.close()\n\n    with pytest.raises(_t.ProgrammingError):\n        await conn.execute(\"SELECT 1\")\n\n\n@pytest.mark.asyncio\nasync def test_cursor_async_context_manager_closes_cursor():\n    import turso as _t\n\n    conn = await turso.aio.connect(\":memory:\")\n    try:\n        async with conn.cursor() as cur:\n            await cur.execute(\"SELECT 1\")\n            assert (await cur.fetchone())[0] == 1\n\n        # Cursor is closed after context manager exit\n        with pytest.raises(_t.ProgrammingError):\n            await cur.fetchone()\n    finally:\n        await conn.close()\n"
  },
  {
    "path": "bindings/python/tests/test_database_sync.py",
    "content": "import logging\nimport multiprocessing\nimport os\nimport tempfile\nimport time\n\nimport turso\nimport turso.sync\n\nfrom .utils import TursoServer\n\nlogging.basicConfig(level=logging.INFO, format=\"%(asctime)s - %(levelname)s - %(message)s\", force=True)\n\n\ndef test_bootstrap():\n    with TursoServer() as server:\n        server.db_sql(\"CREATE TABLE t(x)\")\n        server.db_sql(\"INSERT INTO t VALUES ('hello'), ('turso'), ('sync')\")\n        server.db_sql(\"SELECT * FROM t\")\n\n        conn = turso.sync.connect(\":memory:\", server.db_url())\n        rows = conn.execute(\"SELECT * FROM t\").fetchall()\n        assert rows == [(\"hello\",), (\"turso\",), (\"sync\",)]\n\n\ndef test_pull():\n    with TursoServer() as server:\n        server.db_sql(\"CREATE TABLE t(x)\")\n        server.db_sql(\"INSERT INTO t VALUES ('hello'), ('turso'), ('sync')\")\n        server.db_sql(\"SELECT * FROM t\")\n\n        conn = turso.sync.connect(\":memory:\", server.db_url())\n        rows = conn.execute(\"SELECT * FROM t\").fetchall()\n        assert rows == [(\"hello\",), (\"turso\",), (\"sync\",)]\n\n        server.db_sql(\"INSERT INTO t VALUES ('pull works')\")\n\n        rows = conn.execute(\"SELECT * FROM t\").fetchall()\n        assert rows == [(\"hello\",), (\"turso\",), (\"sync\",)]\n\n        assert conn.pull()\n\n        rows = conn.execute(\"SELECT * FROM t\").fetchall()\n        assert rows == [(\"hello\",), (\"turso\",), (\"sync\",), (\"pull works\",)]\n\n        assert not conn.pull()\n\n\ndef test_push():\n    with TursoServer() as server:\n        server.db_sql(\"CREATE TABLE t(x)\")\n        server.db_sql(\"INSERT INTO t VALUES ('hello'), ('turso'), ('sync')\")\n        server.db_sql(\"SELECT * FROM t\")\n\n        conn = turso.sync.connect(\":memory:\", server.db_url())\n        rows = conn.execute(\"SELECT * FROM t\").fetchall()\n        assert rows == [(\"hello\",), (\"turso\",), (\"sync\",)]\n\n        conn.execute(\"INSERT INTO t VALUES ('push works')\")\n        conn.commit()\n\n        r1 = server.db_sql(\"SELECT * FROM t\")\n        assert r1 == [[\"hello\"], [\"turso\"], [\"sync\"]]\n\n        conn.push()\n\n        r2 = server.db_sql(\"SELECT * FROM t\")\n        assert r2 == [[\"hello\"], [\"turso\"], [\"sync\"], [\"push works\"]]\n\n\ndef test_checkpoint():\n    # turso.setup_logging(level=logging.DEBUG)\n\n    with TursoServer() as server:\n        conn = turso.sync.connect(\":memory:\", remote_url=server.db_url())\n        conn.execute(\"CREATE TABLE t(x)\")\n        conn.commit()\n        for i in range(1024):\n            conn.execute(f\"INSERT INTO t VALUES ({i})\")\n            conn.commit()\n        stats1 = conn.stats()\n        conn.checkpoint()\n        stats2 = conn.stats()\n\n        assert stats1.main_wal_size > 1024 * 1024\n        assert stats1.revert_wal_size == 0\n\n        assert stats2.main_wal_size == 0\n        assert stats2.revert_wal_size < 8 * 1024\n\n        conn.push()\n\n        assert server.db_sql(\"SELECT SUM(x) FROM t\") == [[f\"{1024 * 1023 // 2}\"]]\n\n\ndef test_partial_sync():\n    # turso.setup_logging(level=logging.DEBUG)\n\n    with TursoServer() as server:\n        server.db_sql(\"CREATE TABLE t(x)\")\n        server.db_sql(\"INSERT INTO t SELECT randomblob(1024) FROM generate_series(1, 2000)\")\n\n        conn_full = turso.sync.connect(\":memory:\", remote_url=server.db_url())\n        assert conn_full.execute(\"SELECT LENGTH(x) FROM t LIMIT 1\").fetchall() == [(1024,)]\n        assert conn_full.stats().network_received_bytes > 2000 * 1024\n        assert conn_full.execute(\"SELECT SUM(LENGTH(x)) FROM t\").fetchall() == [(2000 * 1024,)]\n\n        conn_partial = turso.sync.connect(\n            \":memory:\",\n            remote_url=server.db_url(),\n            partial_sync_experimental=turso.sync.PartialSyncOpts(\n                bootstrap_strategy=turso.sync.PartialSyncPrefixBootstrap(length=128 * 1024),\n            ),\n        )\n        assert conn_partial.execute(\"SELECT LENGTH(x) FROM t LIMIT 1\").fetchall() == [(1024,)]\n        assert conn_partial.stats().network_received_bytes < 256 * (1024 + 10)\n\n        start = time.time()\n        assert conn_partial.execute(\"SELECT SUM(LENGTH(x)) FROM t\").fetchall() == [(2000 * 1024,)]\n        print(time.time() - start)\n        assert conn_partial.stats().network_received_bytes > 2000 * 1024\n\n\ndef test_partial_sync_segment_size():\n    # turso.setup_logging(level=logging.DEBUG)\n\n    with TursoServer() as server:\n        server.db_sql(\"CREATE TABLE t(x)\")\n        server.db_sql(\"INSERT INTO t SELECT randomblob(1024) FROM generate_series(1, 256)\")\n\n        conn_full = turso.sync.connect(\":memory:\", remote_url=server.db_url())\n        assert conn_full.execute(\"SELECT LENGTH(x) FROM t LIMIT 1\").fetchall() == [(1024,)]\n        assert conn_full.stats().network_received_bytes > 256 * 1024\n        assert conn_full.execute(\"SELECT SUM(LENGTH(x)) FROM t\").fetchall() == [(256 * 1024,)]\n\n        conn_partial = turso.sync.connect(\n            \":memory:\",\n            remote_url=server.db_url(),\n            partial_sync_experimental=turso.sync.PartialSyncOpts(\n                bootstrap_strategy=turso.sync.PartialSyncPrefixBootstrap(length=128 * 1024),\n                segment_size=4 * 1024,\n            ),\n        )\n        assert conn_partial.execute(\"SELECT LENGTH(x) FROM t LIMIT 1\").fetchall() == [(1024,)]\n        assert conn_partial.stats().network_received_bytes < 128 * 1024 * 1.5\n\n        start = time.time()\n        assert conn_partial.execute(\"SELECT SUM(LENGTH(x)) FROM t\").fetchall() == [(256 * 1024,)]\n        print(time.time() - start)\n        assert conn_partial.stats().network_received_bytes > 256 * 1024\n\n\ndef test_partial_sync_prefetch():\n    # turso.setup_logging(level=logging.DEBUG)\n\n    with TursoServer() as server:\n        server.db_sql(\"CREATE TABLE t(x)\")\n        server.db_sql(\"INSERT INTO t SELECT randomblob(1024) FROM generate_series(1, 2000)\")\n\n        conn_full = turso.sync.connect(\":memory:\", remote_url=server.db_url())\n        assert conn_full.execute(\"SELECT LENGTH(x) FROM t LIMIT 1\").fetchall() == [(1024,)]\n        assert conn_full.stats().network_received_bytes > 2000 * 1024\n        assert conn_full.execute(\"SELECT SUM(LENGTH(x)) FROM t\").fetchall() == [(2000 * 1024,)]\n\n        conn_partial = turso.sync.connect(\n            \":memory:\",\n            remote_url=server.db_url(),\n            partial_sync_experimental=turso.sync.PartialSyncOpts(\n                bootstrap_strategy=turso.sync.PartialSyncPrefixBootstrap(length=128 * 1024),\n                segment_size=4 * 1024,\n                prefetch=True,\n            ),\n        )\n        assert conn_partial.execute(\"SELECT LENGTH(x) FROM t LIMIT 1\").fetchall() == [(1024,)]\n        assert conn_partial.stats().network_received_bytes < 1200 * 1024\n\n        start = time.time()\n        assert conn_partial.execute(\"SELECT SUM(LENGTH(x)) FROM t\").fetchall() == [(2000 * 1024,)]\n        print(time.time() - start)\n        assert conn_partial.stats().network_received_bytes > 2000 * 1024\n\n\ndef run_full(path: str, remote_url: str, barrier: any):\n    barrier.wait(timeout=60)\n    try:\n        print(turso.sync.connect(path, remote_url=remote_url))\n    except Exception as e:\n        print(\"valid error\", e, type(e), isinstance(e, turso.Error), turso.Error)\n\n\ndef test_bootstrap_concurrency():\n    # turso.setup_logging(level=logging.DEBUG)\n\n    with TursoServer() as server:\n        server.db_sql(\"CREATE TABLE t(x)\")\n        server.db_sql(\"INSERT INTO t SELECT randomblob(1024) FROM generate_series(1, 2000)\")\n\n        with tempfile.TemporaryDirectory(prefix=\"pyturso-\") as dir:\n            path = os.path.join(dir, \"local.db\")\n            print(path)\n            barrier = multiprocessing.Barrier(2)\n            t1 = multiprocessing.Process(target=run_full, args=(path, server.db_url(), barrier))\n            t2 = multiprocessing.Process(target=run_full, args=(path, server.db_url(), barrier))\n\n            t1.start()\n            t2.start()\n\n            t1.join(timeout=120)\n            t2.join(timeout=120)\n\n            if t1.is_alive():\n                t1.kill()\n                t1.join()\n            if t2.is_alive():\n                t2.kill()\n                t2.join()\n\n            assert t1.exitcode == 0, f\"t1 exitcode: {t1.exitcode}\"\n            assert t2.exitcode == 0, f\"t2 exitcode: {t2.exitcode}\"\n\n\ndef test_configuration_persistence():\n    with TursoServer() as server:\n        server.db_sql(\"CREATE TABLE t(x)\")\n        server.db_sql(\"INSERT INTO t VALUES (42)\")\n\n        with tempfile.TemporaryDirectory(prefix=\"pyturso-\") as dir:\n            path = os.path.join(dir, \"local.db\")\n            print(path)\n            conn1 = turso.sync.connect(path, remote_url=server.db_url())\n            assert conn1.execute(\"SELECT * FROM t\").fetchall() == [(42,)]\n            conn1.close()\n\n            server.db_sql(\"INSERT INTO t VALUES (43)\")\n\n            assert \"http://localhost\" in open(f\"{path}-info\", \"r\").read()\n\n            conn2 = turso.sync.connect(path)\n            conn2.pull()\n            assert conn2.execute(\"SELECT * FROM t\").fetchall() == [(42,), (43,)]\n            conn2.close()\n"
  },
  {
    "path": "bindings/python/tests/test_database_sync_aio.py",
    "content": "import logging\nimport time\n\nimport pytest\nimport turso.aio.sync\n\nfrom .utils import TursoServer\n\nlogging.basicConfig(level=logging.INFO, format=\"%(asctime)s - %(levelname)s - %(message)s\", force=True)\n\n\n@pytest.mark.asyncio\nasync def test_bootstrap():\n    with TursoServer() as server:\n        server.db_sql(\"CREATE TABLE t(x)\")\n        server.db_sql(\"INSERT INTO t VALUES ('hello'), ('turso'), ('sync')\")\n        server.db_sql(\"SELECT * FROM t\")\n\n        conn = await turso.aio.sync.connect(\":memory:\", server.db_url())\n        rows = await (await conn.execute(\"SELECT * FROM t\")).fetchall()\n        assert rows == [(\"hello\",), (\"turso\",), (\"sync\",)]\n\n\n@pytest.mark.asyncio\nasync def test_pull():\n    with TursoServer() as server:\n        server.db_sql(\"CREATE TABLE t(x)\")\n        server.db_sql(\"INSERT INTO t VALUES ('hello'), ('turso'), ('sync')\")\n        server.db_sql(\"SELECT * FROM t\")\n\n        conn = await turso.aio.sync.connect(\":memory:\", server.db_url())\n        rows = await (await conn.execute(\"SELECT * FROM t\")).fetchall()\n        assert rows == [(\"hello\",), (\"turso\",), (\"sync\",)]\n\n        server.db_sql(\"INSERT INTO t VALUES ('pull works')\")\n\n        rows = await (await conn.execute(\"SELECT * FROM t\")).fetchall()\n        assert rows == [(\"hello\",), (\"turso\",), (\"sync\",)]\n\n        assert await conn.pull()\n\n        rows = await (await conn.execute(\"SELECT * FROM t\")).fetchall()\n        assert rows == [(\"hello\",), (\"turso\",), (\"sync\",), (\"pull works\",)]\n\n        assert not await conn.pull()\n\n\n@pytest.mark.asyncio\nasync def test_push():\n    with TursoServer() as server:\n        server.db_sql(\"CREATE TABLE t(x)\")\n        server.db_sql(\"INSERT INTO t VALUES ('hello'), ('turso'), ('sync')\")\n        server.db_sql(\"SELECT * FROM t\")\n\n        conn = await turso.aio.sync.connect(\":memory:\", server.db_url())\n        rows = await (await conn.execute(\"SELECT * FROM t\")).fetchall()\n        assert rows == [(\"hello\",), (\"turso\",), (\"sync\",)]\n\n        await conn.execute(\"INSERT INTO t VALUES ('push works')\")\n        await conn.commit()\n\n        r1 = server.db_sql(\"SELECT * FROM t\")\n        assert r1 == [[\"hello\"], [\"turso\"], [\"sync\"]]\n\n        await conn.push()\n\n        r2 = server.db_sql(\"SELECT * FROM t\")\n        assert r2 == [[\"hello\"], [\"turso\"], [\"sync\"], [\"push works\"]]\n\n\n@pytest.mark.asyncio\nasync def test_checkpoint():\n    # turso.setup_logging(level=logging.DEBUG)\n\n    with TursoServer() as server:\n        conn = await turso.aio.sync.connect(\":memory:\", remote_url=server.db_url())\n        await conn.execute(\"CREATE TABLE t(x)\")\n        await conn.commit()\n        for i in range(1024):\n            await conn.execute(f\"INSERT INTO t VALUES ({i})\")\n            await conn.commit()\n        stats1 = await conn.stats()\n        await conn.checkpoint()\n        stats2 = await conn.stats()\n\n        assert stats1.main_wal_size > 1024 * 1024\n        assert stats1.revert_wal_size == 0\n\n        assert stats2.main_wal_size == 0\n        assert stats2.revert_wal_size < 8 * 1024\n\n        await conn.push()\n\n        assert server.db_sql(\"SELECT SUM(x) FROM t\") == [[f\"{1024 * 1023 // 2}\"]]\n\n\n@pytest.mark.asyncio\nasync def test_partial_sync():\n    # turso.setup_logging(level=logging.DEBUG)\n\n    with TursoServer() as server:\n        server.db_sql(\"CREATE TABLE t(x)\")\n        server.db_sql(\"INSERT INTO t SELECT randomblob(1024) FROM generate_series(1, 2000)\")\n\n        conn_full = await turso.aio.sync.connect(\":memory:\", remote_url=server.db_url())\n        assert await (await conn_full.execute(\"SELECT LENGTH(x) FROM t LIMIT 1\")).fetchall() == [(1024,)]\n        assert (await conn_full.stats()).network_received_bytes > 2000 * 1024\n        assert await (await conn_full.execute(\"SELECT SUM(LENGTH(x)) FROM t\")).fetchall() == [(2000 * 1024,)]\n\n        conn_partial = await turso.aio.sync.connect(\n            \":memory:\",\n            remote_url=server.db_url(),\n            partial_sync_experimental=turso.aio.sync.PartialSyncOpts(\n                bootstrap_strategy=turso.aio.sync.PartialSyncPrefixBootstrap(length=128 * 1024),\n            ),\n        )\n        assert await (await conn_partial.execute(\"SELECT LENGTH(x) FROM t LIMIT 1\")).fetchall() == [(1024,)]\n        assert (await conn_partial.stats()).network_received_bytes < 256 * (1024 + 10)\n\n        start = time.time()\n        assert await (await conn_partial.execute(\"SELECT SUM(LENGTH(x)) FROM t\")).fetchall() == [(2000 * 1024,)]\n        print(time.time() - start)\n        assert (await conn_partial.stats()).network_received_bytes > 2000 * 1024\n\n\n@pytest.mark.asyncio\nasync def test_partial_sync_segment_size():\n    # turso.setup_logging(level=logging.DEBUG)\n\n    with TursoServer() as server:\n        server.db_sql(\"CREATE TABLE t(x)\")\n        server.db_sql(\"INSERT INTO t SELECT randomblob(1024) FROM generate_series(1, 256)\")\n\n        conn_full = await turso.aio.sync.connect(\":memory:\", remote_url=server.db_url())\n        assert await (await conn_full.execute(\"SELECT LENGTH(x) FROM t LIMIT 1\")).fetchall() == [(1024,)]\n        assert (await conn_full.stats()).network_received_bytes > 256 * 1024\n        assert await (await conn_full.execute(\"SELECT SUM(LENGTH(x)) FROM t\")).fetchall() == [(256 * 1024,)]\n\n        conn_partial = await turso.aio.sync.connect(\n            \":memory:\",\n            remote_url=server.db_url(),\n            partial_sync_experimental=turso.aio.sync.PartialSyncOpts(\n                bootstrap_strategy=turso.aio.sync.PartialSyncPrefixBootstrap(length=128 * 1024),\n                segment_size=4 * 1024,\n            ),\n        )\n        assert await (await conn_partial.execute(\"SELECT LENGTH(x) FROM t LIMIT 1\")).fetchall() == [(1024,)]\n        assert (await conn_partial.stats()).network_received_bytes < 128 * 1024 * 1.5\n\n        start = time.time()\n        assert await (await conn_partial.execute(\"SELECT SUM(LENGTH(x)) FROM t\")).fetchall() == [(256 * 1024,)]\n        print(time.time() - start)\n        assert (await conn_partial.stats()).network_received_bytes > 256 * 1024\n\n\n@pytest.mark.asyncio\nasync def test_partial_sync_prefetch():\n    # turso.setup_logging(level=logging.DEBUG)\n\n    with TursoServer() as server:\n        server.db_sql(\"CREATE TABLE t(x)\")\n        server.db_sql(\"INSERT INTO t SELECT randomblob(1024) FROM generate_series(1, 2000)\")\n\n        conn_full = await turso.aio.sync.connect(\":memory:\", remote_url=server.db_url())\n        assert await (await conn_full.execute(\"SELECT LENGTH(x) FROM t LIMIT 1\")).fetchall() == [(1024,)]\n        assert (await conn_full.stats()).network_received_bytes > 2000 * 1024\n        assert await (await conn_full.execute(\"SELECT SUM(LENGTH(x)) FROM t\")).fetchall() == [(2000 * 1024,)]\n\n        # turso.setup_logging(logging.DEBUG)\n        conn_partial = await turso.aio.sync.connect(\n            \":memory:\",\n            remote_url=server.db_url(),\n            partial_sync_experimental=turso.aio.sync.PartialSyncOpts(\n                bootstrap_strategy=turso.aio.sync.PartialSyncPrefixBootstrap(length=128 * 1024),\n                segment_size=4 * 1024,\n                prefetch=True,\n            ),\n        )\n        assert await (await conn_partial.execute(\"SELECT LENGTH(x) FROM t LIMIT 1\")).fetchall() == [(1024,)]\n        assert (await conn_partial.stats()).network_received_bytes < 1200 * 1024\n\n        start = time.time()\n        assert await (await conn_partial.execute(\"SELECT SUM(LENGTH(x)) FROM t\")).fetchall() == [(2000 * 1024,)]\n        print(time.time() - start)\n        assert (await conn_partial.stats()).network_received_bytes > 2000 * 1024\n"
  },
  {
    "path": "bindings/python/tests/test_sqlalchemy.py",
    "content": "\"\"\"Tests for the SQLAlchemy dialect.\"\"\"\n\nimport pytest\n\n# Skip all tests if SQLAlchemy is not installed\nsqlalchemy = pytest.importorskip(\"sqlalchemy\")\n\nfrom sqlalchemy import Column, Integer, String, create_engine, text  # noqa: E402\nfrom sqlalchemy.engine import URL  # noqa: E402\nfrom sqlalchemy.orm import Session, declarative_base  # noqa: E402\nfrom turso.sqlalchemy import TursoDialect, TursoSyncDialect, get_sync_connection  # noqa: E402\n\n\nclass TestTursoDialectImport:\n    \"\"\"Test TursoDialect module imports.\"\"\"\n\n    def test_import_dbapi(self):\n        \"\"\"Test that import_dbapi returns turso module.\"\"\"\n        dbapi = TursoDialect.import_dbapi()\n        assert hasattr(dbapi, \"connect\")\n        assert hasattr(dbapi, \"Connection\")\n        assert hasattr(dbapi, \"Cursor\")\n        assert dbapi.apilevel == \"2.0\"\n\n    def test_dialect_attributes(self):\n        \"\"\"Test dialect class attributes.\"\"\"\n        assert TursoDialect.name == \"sqlite\"\n        assert TursoDialect.driver == \"turso\"\n\n\nclass TestTursoSyncDialectImport:\n    \"\"\"Test TursoSyncDialect module imports.\"\"\"\n\n    def test_import_dbapi(self):\n        \"\"\"Test that import_dbapi returns turso.sync module.\"\"\"\n        dbapi = TursoSyncDialect.import_dbapi()\n        assert hasattr(dbapi, \"connect\")\n        assert hasattr(dbapi, \"ConnectionSync\")\n\n    def test_dialect_attributes(self):\n        \"\"\"Test dialect class attributes.\"\"\"\n        assert TursoSyncDialect.name == \"sqlite\"\n        assert TursoSyncDialect.driver == \"turso_sync\"\n\n\nclass TestTursoDialectURLParsing:\n    \"\"\"Test URL parsing for basic TursoDialect.\"\"\"\n\n    def test_memory_database(self):\n        \"\"\"Test in-memory database URL.\"\"\"\n        dialect = TursoDialect()\n        url = URL.create(\"sqlite+turso\", database=\":memory:\")\n\n        args, kwargs = dialect.create_connect_args(url)\n\n        assert args == [\":memory:\"]\n        assert kwargs == {}\n\n    def test_file_database(self):\n        \"\"\"Test file-based database URL.\"\"\"\n        dialect = TursoDialect()\n        url = URL.create(\"sqlite+turso\", database=\"/path/to/db.db\")\n\n        args, kwargs = dialect.create_connect_args(url)\n\n        assert args == [\"/path/to/db.db\"]\n\n    def test_isolation_level_param(self):\n        \"\"\"Test isolation_level query parameter.\"\"\"\n        dialect = TursoDialect()\n        url = URL.create(\n            \"sqlite+turso\", database=\"test.db\", query={\"isolation_level\": \"IMMEDIATE\"}\n        )\n\n        args, kwargs = dialect.create_connect_args(url)\n\n        assert args == [\"test.db\"]\n        assert kwargs[\"isolation_level\"] == \"IMMEDIATE\"\n\n    def test_autocommit_isolation_level(self):\n        \"\"\"Test AUTOCOMMIT isolation level converts to None.\"\"\"\n        dialect = TursoDialect()\n        url = URL.create(\n            \"sqlite+turso\", database=\"test.db\", query={\"isolation_level\": \"AUTOCOMMIT\"}\n        )\n\n        args, kwargs = dialect.create_connect_args(url)\n\n        assert kwargs[\"isolation_level\"] is None\n\n\nclass TestTursoSyncDialectURLParsing:\n    \"\"\"Test URL parsing for TursoSyncDialect.\"\"\"\n\n    def test_basic_url_parsing(self):\n        \"\"\"Test basic URL with path and remote_url.\"\"\"\n        dialect = TursoSyncDialect()\n        url = URL.create(\n            \"sqlite+turso_sync\",\n            database=\"/path/to/db.db\",\n            query={\"remote_url\": \"https://db.turso.io\"},\n        )\n\n        args, kwargs = dialect.create_connect_args(url)\n\n        assert args == [\"/path/to/db.db\", \"https://db.turso.io\"]\n        assert kwargs.get(\"client_name\") == \"turso-sqlalchemy\"\n\n    def test_memory_database(self):\n        \"\"\"Test in-memory database URL.\"\"\"\n        dialect = TursoSyncDialect()\n        url = URL.create(\n            \"sqlite+turso_sync\",\n            database=\":memory:\",\n            query={\"remote_url\": \"https://db.turso.io\"},\n        )\n\n        args, kwargs = dialect.create_connect_args(url)\n\n        assert args[0] == \":memory:\"\n\n    def test_all_query_params(self):\n        \"\"\"Test all supported query parameters.\"\"\"\n        dialect = TursoSyncDialect()\n        url = URL.create(\n            \"sqlite+turso_sync\",\n            database=\"test.db\",\n            query={\n                \"remote_url\": \"https://db.turso.io\",\n                \"auth_token\": \"secret\",\n                \"client_name\": \"my-app\",\n                \"long_poll_timeout_ms\": \"5000\",\n                \"bootstrap_if_empty\": \"false\",\n                \"isolation_level\": \"IMMEDIATE\",\n            },\n        )\n\n        args, kwargs = dialect.create_connect_args(url)\n\n        assert args == [\"test.db\", \"https://db.turso.io\"]\n        assert kwargs[\"auth_token\"] == \"secret\"\n        assert kwargs[\"client_name\"] == \"my-app\"\n        assert kwargs[\"long_poll_timeout_ms\"] == 5000\n        assert kwargs[\"bootstrap_if_empty\"] is False\n        assert kwargs[\"isolation_level\"] == \"IMMEDIATE\"\n\n    def test_bootstrap_if_empty_variations(self):\n        \"\"\"Test various boolean string representations.\"\"\"\n        dialect = TursoSyncDialect()\n\n        for true_val in [\"true\", \"True\", \"TRUE\", \"1\", \"yes\"]:\n            url = URL.create(\n                \"sqlite+turso_sync\",\n                database=\"test.db\",\n                query={\"remote_url\": \"https://db.turso.io\", \"bootstrap_if_empty\": true_val},\n            )\n            _, kwargs = dialect.create_connect_args(url)\n            assert kwargs[\"bootstrap_if_empty\"] is True, f\"Failed for {true_val}\"\n\n        for false_val in [\"false\", \"False\", \"FALSE\", \"0\", \"no\"]:\n            url = URL.create(\n                \"sqlite+turso_sync\",\n                database=\"test.db\",\n                query={\"remote_url\": \"https://db.turso.io\", \"bootstrap_if_empty\": false_val},\n            )\n            _, kwargs = dialect.create_connect_args(url)\n            assert kwargs[\"bootstrap_if_empty\"] is False, f\"Failed for {false_val}\"\n\n    def test_rejects_username_password(self):\n        \"\"\"Test that username/password in URL raises error.\"\"\"\n        dialect = TursoSyncDialect()\n        url = URL.create(\n            \"sqlite+turso_sync\",\n            username=\"user\",\n            password=\"pass\",\n            database=\"test.db\",\n            query={\"remote_url\": \"https://db.turso.io\"},\n        )\n\n        with pytest.raises(ValueError, match=\"username/password\"):\n            dialect.create_connect_args(url)\n\n    def test_rejects_host_port(self):\n        \"\"\"Test that host/port in URL raises error.\"\"\"\n        dialect = TursoSyncDialect()\n        url = URL.create(\n            \"sqlite+turso_sync\",\n            host=\"localhost\",\n            port=5432,\n            database=\"test.db\",\n            query={\"remote_url\": \"https://db.turso.io\"},\n        )\n\n        with pytest.raises(ValueError, match=\"host/port\"):\n            dialect.create_connect_args(url)\n\n    def test_warns_on_unknown_params(self):\n        \"\"\"Test that unknown query parameters trigger a warning.\"\"\"\n        dialect = TursoSyncDialect()\n        url = URL.create(\n            \"sqlite+turso_sync\",\n            database=\"test.db\",\n            query={\n                \"remote_url\": \"https://db.turso.io\",\n                \"unknown_param\": \"value\",\n            },\n        )\n\n        with pytest.warns(UserWarning, match=\"unknown_param\"):\n            dialect.create_connect_args(url)\n\n    def test_sync_url_query_param_compat(self):\n        \"\"\"Test that sync_url is accepted as alias for remote_url in URL query params.\"\"\"\n        dialect = TursoSyncDialect()\n        url = URL.create(\n            \"sqlite+turso_sync\",\n            database=\"/path/to/db.db\",\n            query={\"sync_url\": \"https://db.turso.io\"},\n        )\n\n        args, kwargs = dialect.create_connect_args(url)\n\n        assert args == [\"/path/to/db.db\", \"https://db.turso.io\"]\n\n    def test_remote_url_takes_precedence_over_sync_url(self):\n        \"\"\"Test that remote_url is preferred when both are provided.\"\"\"\n        dialect = TursoSyncDialect()\n        url = URL.create(\n            \"sqlite+turso_sync\",\n            database=\"test.db\",\n            query={\n                \"remote_url\": \"https://primary.turso.io\",\n                \"sync_url\": \"https://fallback.turso.io\",\n            },\n        )\n\n        args, _ = dialect.create_connect_args(url)\n\n        assert args == [\"test.db\", \"https://primary.turso.io\"]\n\n    def test_sync_url_connect_args_compat(self):\n        \"\"\"Test that sync_url in connect_args is remapped to remote_url.\"\"\"\n        dialect = TursoSyncDialect()\n\n        # Simulate what SQLAlchemy does: dialect.connect(*cargs, **cparams)\n        # where cparams includes connect_args merged in.\n        # We can't call connect() directly without a real DB, but we can\n        # verify the remapping logic by checking the method exists and\n        # testing the URL param path covers the same alias.\n        url = URL.create(\n            \"sqlite+turso_sync\",\n            database=\"test.db\",\n            query={\"sync_url\": \"https://db.turso.io\", \"auth_token\": \"secret\"},\n        )\n\n        args, kwargs = dialect.create_connect_args(url)\n\n        assert args == [\"test.db\", \"https://db.turso.io\"]\n        assert kwargs[\"auth_token\"] == \"secret\"\n\n\nclass TestPoolClass:\n    \"\"\"Test connection pool class selection.\"\"\"\n\n    def test_memory_uses_singleton_pool(self):\n        \"\"\"Test that :memory: databases use SingletonThreadPool.\"\"\"\n        from sqlalchemy import pool\n\n        dialect = TursoDialect()\n        url = URL.create(\"sqlite+turso\", database=\":memory:\")\n\n        pool_class = dialect.get_pool_class(url)\n\n        assert pool_class is pool.SingletonThreadPool\n\n    def test_file_uses_queue_pool(self):\n        \"\"\"Test that file databases use QueuePool.\"\"\"\n        from sqlalchemy import pool\n\n        dialect = TursoDialect()\n        url = URL.create(\"sqlite+turso\", database=\"test.db\")\n\n        pool_class = dialect.get_pool_class(url)\n\n        assert pool_class is pool.QueuePool\n\n\nclass TestGetSyncConnectionErrors:\n    \"\"\"Test get_sync_connection error handling.\"\"\"\n\n    def test_raises_for_non_sync_dialect(self):\n        \"\"\"Test that get_sync_connection raises for non-turso connections.\"\"\"\n        engine = create_engine(\"sqlite:///:memory:\")\n\n        with engine.connect() as conn:\n            with pytest.raises(TypeError, match=\"ConnectionSync\"):\n                get_sync_connection(conn)\n\n\nclass TestBasicDialectIntegration:\n    \"\"\"Integration tests for the basic TursoDialect.\"\"\"\n\n    def test_basic_crud(self):\n        \"\"\"Test basic CRUD operations through SQLAlchemy.\"\"\"\n        engine = create_engine(\"sqlite+turso:///:memory:\")\n\n        with engine.connect() as conn:\n            conn.execute(text(\"CREATE TABLE test (id INTEGER, name TEXT)\"))\n            conn.execute(text(\"INSERT INTO test VALUES (1, 'alice')\"))\n            conn.commit()\n\n            result = conn.execute(text(\"SELECT * FROM test\"))\n            rows = result.fetchall()\n\n            assert rows == [(1, \"alice\")]\n\n    def test_orm_usage(self):\n        \"\"\"Test ORM usage with basic dialect.\"\"\"\n        Base = declarative_base()\n\n        class Item(Base):\n            __tablename__ = \"items\"\n            id = Column(Integer, primary_key=True)\n            name = Column(String(100))\n\n        engine = create_engine(\"sqlite+turso:///:memory:\")\n        Base.metadata.create_all(engine)\n\n        with Session(engine) as session:\n            session.add(Item(name=\"Widget\"))\n            session.commit()\n\n            items = session.query(Item).all()\n            assert len(items) == 1\n            assert items[0].name == \"Widget\"\n\n    def test_pandas_to_sql(self):\n        \"\"\"Test Pandas DataFrame.to_sql() with if_exists='replace'.\"\"\"\n        pd = pytest.importorskip(\"pandas\")\n\n        df = pd.DataFrame({\n            \"id\": [1, 2, 3],\n            \"name\": [\"Alice\", \"Bob\", \"Charlie\"],\n        })\n\n        engine = create_engine(\"sqlite+turso:///:memory:\")\n\n        with engine.connect() as conn:\n            # First insert\n            df.to_sql(\"test_table\", conn, if_exists=\"replace\", index=False)\n\n            # Verify\n            result = pd.read_sql(\"SELECT * FROM test_table\", conn)\n            assert len(result) == 3\n\n            # Replace (this tests table reflection which was failing)\n            df.to_sql(\"test_table\", conn, if_exists=\"replace\", index=False)\n\n            # Verify again\n            result = pd.read_sql(\"SELECT * FROM test_table\", conn)\n            assert len(result) == 3\n\n    def test_table_reflection_returns_empty(self):\n        \"\"\"Test that table reflection methods return empty (not error).\"\"\"\n        engine = create_engine(\"sqlite+turso:///:memory:\")\n\n        with engine.connect() as conn:\n            conn.execute(text(\"CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)\"))\n            conn.commit()\n\n            # Get inspector\n            from sqlalchemy import inspect\n            inspector = inspect(engine)\n\n            # These should return empty lists, not raise errors\n            fks = inspector.get_foreign_keys(\"test\")\n            assert fks == []\n\n            indexes = inspector.get_indexes(\"test\")\n            assert indexes == []\n\n            ucs = inspector.get_unique_constraints(\"test\")\n            assert ucs == []\n\n\nclass TestSyncDialectIntegration:\n    \"\"\"Integration tests for sync dialect with real sync server.\n\n    These tests require a running sync server. They will be skipped\n    if the server is not available.\n    \"\"\"\n\n    @pytest.fixture\n    def server(self):\n        \"\"\"Create a TursoServer for testing.\"\"\"\n        pytest.importorskip(\"requests\")\n        from tests.utils import TursoServer\n\n        try:\n            with TursoServer() as s:\n                yield s\n        except Exception as e:\n            pytest.skip(f\"TursoServer not available: {e}\")\n\n    def test_basic_sync_connection(self, server):\n        \"\"\"Test creating a sync connection through SQLAlchemy.\"\"\"\n        engine = create_engine(\n            \"sqlite+turso_sync:///:memory:\",\n            connect_args={\"remote_url\": server.db_url()},\n        )\n\n        with engine.connect() as conn:\n            conn.execute(text(\"CREATE TABLE test (id INTEGER, name TEXT)\"))\n            conn.execute(text(\"INSERT INTO test VALUES (1, 'alice')\"))\n            conn.commit()\n\n            result = conn.execute(text(\"SELECT * FROM test\"))\n            rows = result.fetchall()\n\n            assert rows == [(1, \"alice\")]\n\n    def test_get_sync_connection(self, server):\n        \"\"\"Test that get_sync_connection returns ConnectionSync.\"\"\"\n        from turso.lib_sync import ConnectionSync\n\n        engine = create_engine(\n            \"sqlite+turso_sync:///:memory:\",\n            connect_args={\"remote_url\": server.db_url()},\n        )\n\n        with engine.connect() as conn:\n            sync = get_sync_connection(conn)\n            assert isinstance(sync, ConnectionSync)\n\n    def test_sync_operations(self, server):\n        \"\"\"Test pull/push through SQLAlchemy connection.\"\"\"\n        # Create initial data on server\n        server.db_sql(\"CREATE TABLE t(x)\")\n        server.db_sql(\"INSERT INTO t VALUES ('remote')\")\n\n        engine = create_engine(\n            \"sqlite+turso_sync:///:memory:\",\n            connect_args={\"remote_url\": server.db_url()},\n        )\n\n        with engine.connect() as conn:\n            sync = get_sync_connection(conn)\n\n            # Data should be bootstrapped\n            result = conn.execute(text(\"SELECT * FROM t\"))\n            assert result.fetchall() == [(\"remote\",)]\n\n            # Insert locally\n            conn.execute(text(\"INSERT INTO t VALUES ('local')\"))\n            conn.commit()\n\n            # Push to remote\n            sync.push()\n\n            # Verify on remote\n            remote_rows = server.db_sql(\"SELECT * FROM t\")\n            assert [\"local\"] in remote_rows\n\n    def test_orm_with_sync(self, server):\n        \"\"\"Test ORM usage with sync dialect.\"\"\"\n        Base = declarative_base()\n\n        class Item(Base):\n            __tablename__ = \"items\"\n            id = Column(Integer, primary_key=True)\n            name = Column(String(100))\n\n        engine = create_engine(\n            \"sqlite+turso_sync:///:memory:\",\n            connect_args={\"remote_url\": server.db_url()},\n        )\n\n        Base.metadata.create_all(engine)\n\n        with Session(engine) as session:\n            session.add(Item(name=\"Widget\"))\n            session.commit()\n\n            items = session.query(Item).all()\n            assert len(items) == 1\n            assert items[0].name == \"Widget\"\n\n    def test_url_with_remote_url_param(self, server):\n        \"\"\"Test passing remote_url as URL query parameter.\"\"\"\n        db_url = server.db_url()\n        engine = create_engine(f\"sqlite+turso_sync:///:memory:?remote_url={db_url}\")\n\n        with engine.connect() as conn:\n            conn.execute(text(\"CREATE TABLE test (x TEXT)\"))\n            conn.execute(text(\"INSERT INTO test VALUES ('hello')\"))\n            conn.commit()\n\n            result = conn.execute(text(\"SELECT * FROM test\"))\n            assert result.fetchall() == [(\"hello\",)]\n\n\n# ── Phase 1: Missing Unit Tests ──────────────────────────────────\n\n\nclass TestTursoDialectMixin:\n    \"\"\"Test the _TursoDialectMixin methods via inspector and directly.\"\"\"\n\n    @pytest.fixture\n    def engine(self):\n        return create_engine(\"sqlite+turso:///:memory:\")\n\n    @pytest.fixture\n    def inspector(self, engine):\n        from sqlalchemy import inspect\n\n        with engine.connect() as conn:\n            conn.execute(text(\"CREATE TABLE alpha (id INTEGER PRIMARY KEY, name TEXT)\"))\n            conn.execute(text(\"CREATE TABLE beta (id INTEGER PRIMARY KEY, val REAL)\"))\n            conn.commit()\n\n        return inspect(engine)\n\n    def test_get_check_constraints_returns_empty(self, inspector):\n        \"\"\"get_check_constraints returns empty list for any table.\"\"\"\n        assert inspector.get_check_constraints(\"alpha\") == []\n\n    def test_get_table_names_works(self, inspector):\n        \"\"\"inspector.get_table_names() returns the created tables.\"\"\"\n        tables = inspector.get_table_names()\n        assert \"alpha\" in tables\n        assert \"beta\" in tables\n\n    def test_get_columns_works(self, inspector):\n        \"\"\"inspector.get_columns() returns column info.\"\"\"\n        cols = inspector.get_columns(\"alpha\")\n        col_names = [c[\"name\"] for c in cols]\n        assert \"id\" in col_names\n        assert \"name\" in col_names\n\n    def test_multi_indexes_returns_empty(self, inspector):\n        \"\"\"get_multi_indexes returns empty dict for multiple tables.\"\"\"\n        dialect = TursoDialect()\n        with inspector.bind.connect() as conn:\n            result = dialect.get_multi_indexes(conn, filter_names=[\"alpha\", \"beta\"])\n        assert result == {}\n\n    def test_multi_unique_constraints_returns_empty(self, inspector):\n        \"\"\"get_multi_unique_constraints returns empty dict.\"\"\"\n        dialect = TursoDialect()\n        with inspector.bind.connect() as conn:\n            result = dialect.get_multi_unique_constraints(conn, filter_names=[\"alpha\", \"beta\"])\n        assert result == {}\n\n    def test_multi_foreign_keys_returns_empty(self, inspector):\n        \"\"\"get_multi_foreign_keys returns empty dict.\"\"\"\n        dialect = TursoDialect()\n        with inspector.bind.connect() as conn:\n            result = dialect.get_multi_foreign_keys(conn, filter_names=[\"alpha\", \"beta\"])\n        assert result == {}\n\n    def test_multi_check_constraints_returns_empty(self, inspector):\n        \"\"\"get_multi_check_constraints returns empty dict.\"\"\"\n        dialect = TursoDialect()\n        with inspector.bind.connect() as conn:\n            result = dialect.get_multi_check_constraints(conn, filter_names=[\"alpha\", \"beta\"])\n        assert result == {}\n\n    def test_multi_table_reflection(self, inspector):\n        \"\"\"Reflection with multiple tables: all tables visible, constraints empty.\"\"\"\n        tables = inspector.get_table_names()\n        assert len(tables) >= 2\n        for table in [\"alpha\", \"beta\"]:\n            assert inspector.get_foreign_keys(table) == []\n            assert inspector.get_indexes(table) == []\n            assert inspector.get_unique_constraints(table) == []\n            assert inspector.get_check_constraints(table) == []\n\n\nclass TestTursoDialectMethods:\n    \"\"\"Test TursoDialect methods not covered by integration tests.\"\"\"\n\n    def test_on_connect_returns_none(self):\n        \"\"\"on_connect returns None — skips REGEXP setup.\"\"\"\n        dialect = TursoDialect()\n        assert dialect.on_connect() is None\n\n    def test_get_isolation_level_returns_serializable(self):\n        \"\"\"get_isolation_level returns SERIALIZABLE.\"\"\"\n        dialect = TursoDialect()\n        assert dialect.get_isolation_level(None) == \"SERIALIZABLE\"\n\n    def test_set_isolation_level_is_noop(self):\n        \"\"\"set_isolation_level doesn't raise for any value.\"\"\"\n        dialect = TursoDialect()\n        dialect.set_isolation_level(None, \"SERIALIZABLE\")\n        dialect.set_isolation_level(None, \"READ UNCOMMITTED\")\n        # No assertion needed — just verify no exception\n\n    def test_supports_statement_cache(self):\n        \"\"\"Statement caching is enabled.\"\"\"\n        assert TursoDialect.supports_statement_cache is True\n\n    def test_supports_native_datetime_false(self):\n        \"\"\"Native datetime is disabled (turso handles datetime differently).\"\"\"\n        assert TursoDialect.supports_native_datetime is False\n\n    def test_experimental_features_param(self):\n        \"\"\"experimental_features query parameter is passed through.\"\"\"\n        dialect = TursoDialect()\n        url = URL.create(\n            \"sqlite+turso\", database=\"test.db\", query={\"experimental_features\": \"feat1,feat2\"}\n        )\n        args, kwargs = dialect.create_connect_args(url)\n        assert kwargs[\"experimental_features\"] == \"feat1,feat2\"\n\n    def test_default_database_memory(self):\n        \"\"\"URL with no database defaults to :memory:.\"\"\"\n        dialect = TursoDialect()\n        url = URL.create(\"sqlite+turso\")\n        args, kwargs = dialect.create_connect_args(url)\n        assert args == [\":memory:\"]\n\n\nclass TestTursoSyncDialectMethods:\n    \"\"\"Test TursoSyncDialect methods parallel to TursoDialect.\"\"\"\n\n    def test_on_connect_returns_none(self):\n        \"\"\"Sync on_connect also returns None.\"\"\"\n        dialect = TursoSyncDialect()\n        assert dialect.on_connect() is None\n\n    def test_get_isolation_level_returns_serializable(self):\n        \"\"\"Sync get_isolation_level returns SERIALIZABLE.\"\"\"\n        dialect = TursoSyncDialect()\n        assert dialect.get_isolation_level(None) == \"SERIALIZABLE\"\n\n    def test_set_isolation_level_is_noop(self):\n        \"\"\"Sync set_isolation_level is also a no-op.\"\"\"\n        dialect = TursoSyncDialect()\n        dialect.set_isolation_level(None, \"SERIALIZABLE\")\n\n    def test_supports_statement_cache(self):\n        assert TursoSyncDialect.supports_statement_cache is True\n\n    def test_supports_native_datetime_false(self):\n        assert TursoSyncDialect.supports_native_datetime is False\n\n\nclass TestTursoSyncDialectEdgeCases:\n    \"\"\"Edge cases for TursoSyncDialect URL parsing.\"\"\"\n\n    def test_autocommit_isolation_level(self):\n        \"\"\"AUTOCOMMIT converts to None in sync dialect URL.\"\"\"\n        dialect = TursoSyncDialect()\n        url = URL.create(\n            \"sqlite+turso_sync\",\n            database=\"test.db\",\n            query={\"remote_url\": \"https://db.turso.io\", \"isolation_level\": \"AUTOCOMMIT\"},\n        )\n        _, kwargs = dialect.create_connect_args(url)\n        assert kwargs[\"isolation_level\"] is None\n\n    def test_no_remote_url(self):\n        \"\"\"URL without remote_url returns single-element positional args.\"\"\"\n        dialect = TursoSyncDialect()\n        url = URL.create(\"sqlite+turso_sync\", database=\"test.db\")\n        args, kwargs = dialect.create_connect_args(url)\n        assert args == [\"test.db\"]\n        assert \"remote_url\" not in kwargs\n\n    def test_experimental_features_param(self):\n        \"\"\"experimental_features in sync URL is passed through.\"\"\"\n        dialect = TursoSyncDialect()\n        url = URL.create(\n            \"sqlite+turso_sync\",\n            database=\"test.db\",\n            query={\n                \"remote_url\": \"https://db.turso.io\",\n                \"experimental_features\": \"mvcc\",\n            },\n        )\n        _, kwargs = dialect.create_connect_args(url)\n        assert kwargs[\"experimental_features\"] == \"mvcc\"\n\n    def test_long_poll_timeout_ms_integer_conversion(self):\n        \"\"\"long_poll_timeout_ms is converted from string to int.\"\"\"\n        dialect = TursoSyncDialect()\n        url = URL.create(\n            \"sqlite+turso_sync\",\n            database=\"test.db\",\n            query={\n                \"remote_url\": \"https://db.turso.io\",\n                \"long_poll_timeout_ms\": \"3000\",\n            },\n        )\n        _, kwargs = dialect.create_connect_args(url)\n        assert kwargs[\"long_poll_timeout_ms\"] == 3000\n        assert isinstance(kwargs[\"long_poll_timeout_ms\"], int)\n\n    def test_default_client_name(self):\n        \"\"\"Default client_name is 'turso-sqlalchemy' when not specified.\"\"\"\n        dialect = TursoSyncDialect()\n        url = URL.create(\n            \"sqlite+turso_sync\",\n            database=\"test.db\",\n            query={\"remote_url\": \"https://db.turso.io\"},\n        )\n        _, kwargs = dialect.create_connect_args(url)\n        assert kwargs[\"client_name\"] == \"turso-sqlalchemy\"\n\n    def test_sync_pool_class_memory(self):\n        \"\"\"Sync dialect uses SingletonThreadPool for :memory:.\"\"\"\n        from sqlalchemy import pool\n\n        dialect = TursoSyncDialect()\n        url = URL.create(\"sqlite+turso_sync\", database=\":memory:\")\n        assert dialect.get_pool_class(url) is pool.SingletonThreadPool\n\n    def test_sync_pool_class_file(self):\n        \"\"\"Sync dialect uses QueuePool for file databases.\"\"\"\n        from sqlalchemy import pool\n\n        dialect = TursoSyncDialect()\n        url = URL.create(\"sqlite+turso_sync\", database=\"test.db\")\n        assert dialect.get_pool_class(url) is pool.QueuePool\n\n\nclass TestDBAPI2ModuleAttributes:\n    \"\"\"Test DB-API 2.0 module-level attributes for both modules.\"\"\"\n\n    def test_turso_module_attributes(self):\n        \"\"\"turso module has all required DB-API 2.0 attributes.\"\"\"\n        import turso\n\n        assert turso.apilevel == \"2.0\"\n        assert turso.paramstyle == \"qmark\"\n        assert turso.threadsafety == 1\n\n    def test_turso_sync_module_attributes(self):\n        \"\"\"turso.sync module has all DB-API 2.0 attributes.\"\"\"\n        import turso.sync\n\n        assert turso.sync.apilevel == \"2.0\"\n        assert turso.sync.paramstyle == \"qmark\"\n        assert turso.sync.threadsafety == 1\n\n    def test_turso_sync_exception_hierarchy(self):\n        \"\"\"Exception classes are properly re-exported in turso.sync.\"\"\"\n        import turso.sync\n\n        # All DB-API 2.0 required exception classes\n        assert issubclass(turso.sync.Warning, Exception)\n        assert issubclass(turso.sync.Error, Exception)\n        assert issubclass(turso.sync.InterfaceError, turso.sync.Error)\n        assert issubclass(turso.sync.DatabaseError, turso.sync.Error)\n        assert issubclass(turso.sync.DataError, turso.sync.DatabaseError)\n        assert issubclass(turso.sync.OperationalError, turso.sync.DatabaseError)\n        assert issubclass(turso.sync.IntegrityError, turso.sync.DatabaseError)\n        assert issubclass(turso.sync.InternalError, turso.sync.DatabaseError)\n        assert issubclass(turso.sync.ProgrammingError, turso.sync.DatabaseError)\n        assert issubclass(turso.sync.NotSupportedError, turso.sync.DatabaseError)\n\n    def test_turso_sync_sqlite_version(self):\n        \"\"\"sqlite_version and sqlite_version_info are available in turso.sync.\"\"\"\n        import turso.sync\n\n        assert isinstance(turso.sync.sqlite_version, str)\n        assert \".\" in turso.sync.sqlite_version\n\n        assert isinstance(turso.sync.sqlite_version_info, tuple)\n        assert len(turso.sync.sqlite_version_info) == 3\n        assert all(isinstance(p, int) for p in turso.sync.sqlite_version_info)\n\n\nclass TestEntryPointRegistration:\n    \"\"\"Test SQLAlchemy dialect entry points resolve correctly.\"\"\"\n\n    def test_turso_dialect_resolves(self):\n        \"\"\"create_engine('sqlite+turso://') resolves to TursoDialect.\"\"\"\n        engine = create_engine(\"sqlite+turso:///:memory:\")\n        assert isinstance(engine.dialect, TursoDialect)\n\n    def test_turso_sync_dialect_resolves(self):\n        \"\"\"create_engine('sqlite+turso_sync://') resolves to TursoSyncDialect.\"\"\"\n        # Verify the dialect class resolves via URL scheme\n        dialect = TursoSyncDialect()\n        assert dialect.name == \"sqlite\"\n        assert dialect.driver == \"turso_sync\"\n\n        # Also verify the entry point is loadable\n        url = URL.create(\"sqlite+turso_sync\", database=\":memory:\")\n        dialect_cls = url.get_dialect()\n        assert dialect_cls is TursoSyncDialect\n\n\n# ── Phase 2: Extended Integration Tests ──────────────────────────\n\n\nclass TestSQLFeatureCoverage:\n    \"\"\"Test SQL features work through SQLAlchemy with turso dialect.\"\"\"\n\n    @pytest.fixture\n    def engine(self):\n        return create_engine(\"sqlite+turso:///:memory:\")\n\n    def test_transaction_commit_rollback(self, engine):\n        \"\"\"Explicit commit and rollback work correctly.\"\"\"\n        with engine.connect() as conn:\n            conn.execute(text(\"CREATE TABLE txn_test (id INTEGER, val TEXT)\"))\n            conn.commit()\n\n            # Insert and rollback\n            conn.execute(text(\"INSERT INTO txn_test VALUES (1, 'should_vanish')\"))\n            conn.rollback()\n\n            result = conn.execute(text(\"SELECT COUNT(*) FROM txn_test\"))\n            assert result.scalar() == 0\n\n            # Insert and commit\n            conn.execute(text(\"INSERT INTO txn_test VALUES (2, 'should_stay')\"))\n            conn.commit()\n\n            result = conn.execute(text(\"SELECT val FROM txn_test WHERE id = 2\"))\n            assert result.scalar() == \"should_stay\"\n\n    def test_multiple_tables_with_join(self, engine):\n        \"\"\"ORM with multiple related tables and JOIN queries.\"\"\"\n        from sqlalchemy import ForeignKey\n        from sqlalchemy.orm import relationship\n\n        Base = declarative_base()\n\n        class Author(Base):\n            __tablename__ = \"authors\"\n            id = Column(Integer, primary_key=True)\n            name = Column(String(100))\n            books = relationship(\"Book\", back_populates=\"author\")\n\n        class Book(Base):\n            __tablename__ = \"books\"\n            id = Column(Integer, primary_key=True)\n            title = Column(String(200))\n            author_id = Column(Integer, ForeignKey(\"authors.id\"))\n            author = relationship(\"Author\", back_populates=\"books\")\n\n        Base.metadata.create_all(engine)\n\n        with Session(engine) as session:\n            a = Author(name=\"Tolkien\")\n            a.books = [Book(title=\"The Hobbit\"), Book(title=\"LOTR\")]\n            session.add(a)\n            session.commit()\n\n            result = (\n                session.query(Book)\n                .join(Author)\n                .filter(Author.name == \"Tolkien\")\n                .all()\n            )\n            assert len(result) == 2\n            assert {b.title for b in result} == {\"The Hobbit\", \"LOTR\"}\n\n    def test_batch_insert_via_orm(self, engine):\n        \"\"\"Adding multiple ORM objects in one session.\"\"\"\n        Base = declarative_base()\n\n        class Record(Base):\n            __tablename__ = \"records\"\n            id = Column(Integer, primary_key=True)\n            value = Column(Integer)\n\n        Base.metadata.create_all(engine)\n\n        with Session(engine) as session:\n            session.add_all([Record(value=i) for i in range(100)])\n            session.commit()\n\n            count = session.query(Record).count()\n            assert count == 100\n\n    def test_null_handling(self, engine):\n        \"\"\"NULL values round-trip correctly.\"\"\"\n        with engine.connect() as conn:\n            conn.execute(text(\"CREATE TABLE nullable (id INTEGER, val TEXT)\"))\n            conn.execute(text(\"INSERT INTO nullable VALUES (1, NULL)\"))\n            conn.commit()\n\n            result = conn.execute(text(\"SELECT val FROM nullable WHERE id = 1\"))\n            assert result.scalar() is None\n\n    def test_unicode_data(self, engine):\n        \"\"\"Unicode strings round-trip correctly.\"\"\"\n        with engine.connect() as conn:\n            conn.execute(text(\"CREATE TABLE uni (id INTEGER, val TEXT)\"))\n            conn.execute(text(\"INSERT INTO uni VALUES (1, '日本語テスト')\"))\n            conn.execute(text(\"INSERT INTO uni VALUES (2, '🚀🎉')\"))\n            conn.commit()\n\n            rows = conn.execute(text(\"SELECT val FROM uni ORDER BY id\")).fetchall()\n            assert rows[0][0] == \"日本語テスト\"\n            assert rows[1][0] == \"🚀🎉\"\n\n    def test_datetime_handling(self, engine):\n        \"\"\"Datetime values round-trip as strings since supports_native_datetime=False.\"\"\"\n        from datetime import datetime\n\n        Base = declarative_base()\n\n        class Event(Base):\n            __tablename__ = \"events\"\n            id = Column(Integer, primary_key=True)\n            name = Column(String(100))\n            ts = Column(String)  # Store as string — no native datetime\n\n        Base.metadata.create_all(engine)\n\n        now = datetime.now().isoformat()\n        with Session(engine) as session:\n            session.add(Event(name=\"launch\", ts=now))\n            session.commit()\n\n            event = session.query(Event).first()\n            assert event.ts == now\n\n    def test_sql_syntax_error(self, engine):\n        \"\"\"SQL errors propagate correctly through SQLAlchemy.\"\"\"\n        from sqlalchemy.exc import DatabaseError as SADatabaseError\n\n        with engine.connect() as conn:\n            with pytest.raises(SADatabaseError):\n                conn.execute(text(\"SELEKT * FORM nonexistent\"))\n\n    def test_integrity_error_propagation(self, engine):\n        \"\"\"Unique constraint violations raise IntegrityError.\"\"\"\n        from sqlalchemy.exc import IntegrityError as SAIntegrityError\n\n        with engine.connect() as conn:\n            conn.execute(text(\"CREATE TABLE uniq (id INTEGER PRIMARY KEY, email TEXT UNIQUE)\"))\n            conn.execute(text(\"INSERT INTO uniq VALUES (1, 'a@b.com')\"))\n            conn.commit()\n\n            with pytest.raises(SAIntegrityError):\n                conn.execute(text(\"INSERT INTO uniq VALUES (2, 'a@b.com')\"))\n\n    def test_multiple_connections_same_engine(self, engine):\n        \"\"\"Engine handles multiple sequential connections.\"\"\"\n        with engine.connect() as conn:\n            conn.execute(text(\"CREATE TABLE multi (id INTEGER)\"))\n            conn.execute(text(\"INSERT INTO multi VALUES (1)\"))\n            conn.commit()\n\n        with engine.connect() as conn:\n            result = conn.execute(text(\"SELECT * FROM multi\"))\n            assert result.fetchall() == [(1,)]\n\n    def test_context_manager_cleanup(self, engine):\n        \"\"\"engine.connect() as context manager properly cleans up.\"\"\"\n        # Should not leak connections or raise on exit\n        for _ in range(10):\n            with engine.connect() as conn:\n                conn.execute(text(\"SELECT 1\"))\n\n    def test_large_text_data(self, engine):\n        \"\"\"Large text values round-trip correctly.\"\"\"\n        with engine.connect() as conn:\n            conn.execute(text(\"CREATE TABLE big (id INTEGER, content TEXT)\"))\n            large = \"x\" * 100_000\n            conn.execute(text(\"INSERT INTO big VALUES (1, :content)\"), {\"content\": large})\n            conn.commit()\n\n            result = conn.execute(text(\"SELECT content FROM big WHERE id = 1\"))\n            assert result.scalar() == large\n"
  },
  {
    "path": "bindings/python/tests/utils.py",
    "content": "import os\nimport random\nimport string\nimport subprocess\nimport time\n\nimport requests\n\n\ndef random_str() -> str:\n    return \"\".join([random.choice(string.ascii_letters) for _ in range(8)])\n\n\ndef handle_response(r):\n    if r.status_code == 400 and \"already exists\" in r.text:\n        return\n    r.raise_for_status()\n\n\nADMIN_URL = \"http://localhost:8081\"\nUSER_URL = \"http://localhost:8080\"\n\n\nclass TursoServer:\n    def __init__(self):\n        if \"LOCAL_SYNC_SERVER\" not in os.environ:\n            name = random_str()\n            tokens = USER_URL.split(\"://\")\n            handle_response(requests.post(ADMIN_URL + f\"/v1/tenants/{name}\"))\n            handle_response(requests.post(ADMIN_URL + f\"/v1/tenants/{name}/groups/{name}\"))\n            handle_response(requests.post(ADMIN_URL + f\"/v1/tenants/{name}/groups/{name}/databases/{name}\"))\n            self._user_url = USER_URL\n            self._db_url = f\"{tokens[0]}://{name}--{name}--{name}.{tokens[1]}\"\n            self._host = f\"{name}--{name}--{name}.localhost\"\n            self._server = None\n        else:\n            # Retry with different ports in case the chosen port is\n            # unavailable (common on Windows where OS reserves port ranges).\n            max_attempts = 5\n            for attempt in range(max_attempts):\n                port = random.randint(10_000, 65535)\n                self._server = subprocess.Popen(\n                    [os.environ[\"LOCAL_SYNC_SERVER\"], \"--sync-server\", f\"0.0.0.0:{port}\"],\n                    stdout=subprocess.PIPE,\n                    stderr=subprocess.PIPE,\n                )\n                self._user_url = f\"http://localhost:{port}\"\n                self._db_url = f\"http://localhost:{port}\"\n                self._host = \"\"\n                # wait for server to be available\n                deadline = time.time() + 30\n                while time.time() < deadline:\n                    rc = self._server.poll()\n                    if rc is not None:\n                        stderr = self._server.stderr.read().decode(errors=\"replace\")\n                        if (\n                            \"os error 10013\" in stderr\n                            or \"os error 10048\" in stderr\n                            or \"address already in use\" in stderr.lower()\n                        ):\n                            break  # retry with a different port\n                        raise RuntimeError(\n                            f\"sync server exited with code {rc} before accepting connections\\nstderr: {stderr}\"\n                        )\n                    try:\n                        requests.get(self._user_url, timeout=5)\n                        break\n                    except Exception:\n                        time.sleep(0.1)\n                else:\n                    stderr = \"\"\n                    if self._server.poll() is not None:\n                        stderr = self._server.stderr.read().decode(errors=\"replace\")\n                    self._server.kill()\n                    raise TimeoutError(\n                        f\"sync server did not become available within 30s\\nstderr: {stderr}\"\n                    )\n                # If the inner loop broke out due to a port conflict, retry\n                if self._server.poll() is not None:\n                    if attempt == max_attempts - 1:\n                        raise RuntimeError(\n                            f\"sync server failed to bind after {max_attempts} port attempts\"\n                        )\n                    continue\n                break  # server is up\n\n    def __enter__(self):\n        return self\n\n    def __exit__(self, type, value, traceback):\n        if self._server:\n            self._server.kill()\n\n    def db_url(self) -> str:\n        return self._db_url\n\n    def db_sql(self, sql: str):\n        result = requests.post(\n            self._user_url + \"/v2/pipeline\",\n            json={\"requests\": [{\"type\": \"execute\", \"stmt\": {\"sql\": sql}}]},\n            headers={\"Host\": self._host},\n        )\n        result.raise_for_status()\n        result = result.json()\n        if result[\"results\"][0][\"type\"] != \"ok\":\n            raise Exception(f\"remote sql execution failed: {result}\")\n        return [[cell[\"value\"] for cell in row] for row in result[\"results\"][0][\"response\"][\"result\"][\"rows\"]]\n"
  },
  {
    "path": "bindings/python/turso/__init__.py",
    "content": "import logging\n\nlogging.getLogger(__name__).addHandler(logging.NullHandler())\n\nfrom .lib import (  # noqa: E402\n    Connection,\n    Cursor,\n    DatabaseError,\n    DataError,\n    EncryptionOpts,\n    Error,\n    IntegrityError,\n    InterfaceError,\n    InternalError,\n    NotSupportedError,\n    OperationalError,\n    ProgrammingError,\n    Row,\n    Warning,\n    apilevel,\n    connect,\n    paramstyle,\n    setup_logging,\n    sqlite_version,\n    sqlite_version_info,\n    threadsafety,\n)\n\n__all__ = [\n    \"Connection\",\n    \"Cursor\",\n    \"Row\",\n    \"connect\",\n    \"setup_logging\",\n    \"Warning\",\n    \"DatabaseError\",\n    \"DataError\",\n    \"Error\",\n    \"IntegrityError\",\n    \"InterfaceError\",\n    \"InternalError\",\n    \"NotSupportedError\",\n    \"OperationalError\",\n    \"ProgrammingError\",\n    \"apilevel\",\n    \"paramstyle\",\n    \"sqlite_version\",\n    \"sqlite_version_info\",\n    \"threadsafety\",\n    \"EncryptionOpts\",\n]\n"
  },
  {
    "path": "bindings/python/turso/aio/__init__.py",
    "content": "from ..lib_aio import Connection, Cursor, connect\n\n__all__ = [\n    \"connect\",\n    \"Connection\",\n    \"Cursor\",\n]\n"
  },
  {
    "path": "bindings/python/turso/aio/sync/__init__.py",
    "content": "from ...lib_sync import (\n    ConnectionSync,\n    PartialSyncOpts,\n    PartialSyncPrefixBootstrap,\n    PartialSyncQueryBootstrap,\n)\nfrom ...lib_sync_aio import (\n    connect_sync as connect,\n)\n\n__all__ = [\n    \"connect\",\n    \"ConnectionSync\",\n    \"PartialSyncOpts\",\n    \"PartialSyncPrefixBootstrap\",\n    \"PartialSyncQueryBootstrap\",\n]\n"
  },
  {
    "path": "bindings/python/turso/lib.py",
    "content": "from __future__ import annotations\n\nfrom collections.abc import Iterable, Iterator, Mapping, Sequence\nfrom dataclasses import dataclass\nfrom types import TracebackType\nfrom typing import Any, Callable, Optional, TypeVar\n\nfrom ._turso import (\n    Busy,\n    Constraint,\n    Corrupt,\n    DatabaseFull,\n    Interrupt,\n    Misuse,\n    NotAdb,\n    PyTursoConnection,\n    PyTursoDatabase,\n    PyTursoDatabaseConfig,\n    PyTursoEncryptionConfig,\n    PyTursoExecutionResult,\n    PyTursoLog,\n    PyTursoSetupConfig,\n    PyTursoStatement,\n    PyTursoStatusCode,\n    py_turso_database_open,\n    py_turso_setup,\n)\nfrom ._turso import (\n    Error as TursoError,\n)\nfrom ._turso import (\n    PyTursoStatusCode as Status,\n)\n\n# DB-API 2.0 module attributes\napilevel = \"2.0\"\nthreadsafety = 1  # 1 means: Threads may share the module, but not connections.\nparamstyle = \"qmark\"  # Only positional parameters are supported.\n\n\ndef _get_sqlite_version() -> tuple[str, tuple[int, int, int]]:\n    \"\"\"Get SQLite version from a temporary connection.\"\"\"\n    try:\n        cfg = PyTursoDatabaseConfig(path=\":memory:\")\n        db = py_turso_database_open(cfg)\n        conn = db.connect()\n        stmt = conn.prepare(\"SELECT sqlite_version()\")\n        result = stmt.step()\n        version_str = result[0]\n        parts = tuple(int(p) for p in version_str.split(\".\"))\n        # Ensure we have exactly 3 parts\n        while len(parts) < 3:\n            parts = (*parts, 0)\n        return version_str, parts[:3]\n    except Exception:\n        # Fallback to a known compatible version\n        return \"3.45.0\", (3, 45, 0)\n\n\nsqlite_version, sqlite_version_info = _get_sqlite_version()\n\n\n# Exception hierarchy following DB-API 2.0\nclass Warning(Exception):\n    pass\n\n\nclass Error(Exception):\n    pass\n\n\nclass InterfaceError(Error):\n    pass\n\n\nclass DatabaseError(Error):\n    pass\n\n\nclass DataError(DatabaseError):\n    pass\n\n\nclass OperationalError(DatabaseError):\n    pass\n\n\nclass IntegrityError(DatabaseError):\n    pass\n\n\nclass InternalError(DatabaseError):\n    pass\n\n\nclass ProgrammingError(DatabaseError):\n    pass\n\n\nclass NotSupportedError(DatabaseError):\n    pass\n\n\ndef _map_turso_exception(exc: Exception) -> Exception:\n    \"\"\"Maps Turso-specific exceptions to DB-API 2.0 exception hierarchy\"\"\"\n    if isinstance(exc, Busy):\n        return OperationalError(str(exc))\n    if isinstance(exc, Interrupt):\n        return OperationalError(str(exc))\n    if isinstance(exc, Misuse):\n        return InterfaceError(str(exc))\n    if isinstance(exc, Constraint):\n        return IntegrityError(str(exc))\n    if isinstance(exc, TursoError):\n        # Generic Turso error -> DatabaseError\n        return DatabaseError(str(exc))\n    if isinstance(exc, DatabaseFull):\n        return OperationalError(str(exc))\n    if isinstance(exc, NotAdb):\n        return DatabaseError(str(exc))\n    if isinstance(exc, Corrupt):\n        return DatabaseError(str(exc))\n    return exc\n\n\n# Internal helpers\n\n_DBCursorT = TypeVar(\"_DBCursorT\", bound=\"Cursor\")\n\n\ndef _first_keyword(sql: str) -> str:\n    \"\"\"\n    Return the first SQL keyword (uppercased) ignoring leading whitespace\n    and single-line and multi-line comments.\n\n    This is intentionally minimal and only used to detect DML for implicit\n    transaction handling. It may not handle all edge cases (e.g. complex WITH).\n    \"\"\"\n    i = 0\n    n = len(sql)\n    while i < n:\n        c = sql[i]\n        if c.isspace():\n            i += 1\n            continue\n        if c == \"-\" and i + 1 < n and sql[i + 1] == \"-\":\n            # line comment\n            i += 2\n            while i < n and sql[i] not in (\"\\r\", \"\\n\"):\n                i += 1\n            continue\n        if c == \"/\" and i + 1 < n and sql[i + 1] == \"*\":\n            # block comment\n            i += 2\n            while i + 1 < n and not (sql[i] == \"*\" and sql[i + 1] == \"/\"):\n                i += 1\n            i = min(i + 2, n)\n            continue\n        break\n    # read token\n    j = i\n    while j < n and (sql[j].isalpha() or sql[j] == \"_\"):\n        j += 1\n    return sql[i:j].upper()\n\n\ndef _is_dml(sql: str) -> bool:\n    kw = _first_keyword(sql)\n    if kw in (\"INSERT\", \"UPDATE\", \"DELETE\", \"REPLACE\"):\n        return True\n    # \"WITH\" can also prefix DML, but we conservatively skip it to avoid false positives.\n    return False\n\n\ndef _is_insert_or_replace(sql: str) -> bool:\n    kw = _first_keyword(sql)\n    return kw in (\"INSERT\", \"REPLACE\")\n\n\ndef _run_execute_with_io(stmt: PyTursoStatement, extra_io: Optional[Callable[[], None]]) -> PyTursoExecutionResult:\n    \"\"\"\n    Run PyTursoStatement.execute() handling potential async IO loops.\n    \"\"\"\n    while True:\n        result = stmt.execute()\n        status = result.status\n        if status == Status.Io:\n            stmt.run_io()\n            if extra_io:\n                extra_io()\n            continue\n        return result\n\n\ndef _step_once_with_io(stmt: PyTursoStatement, extra_io: Optional[Callable[[], None]]) -> PyTursoStatusCode:\n    \"\"\"\n    Run PyTursoStatement.step() once handling potential async IO loops.\n    \"\"\"\n    while True:\n        status = stmt.step()\n        if status == Status.Io:\n            stmt.run_io()\n            if extra_io:\n                extra_io()\n            continue\n        return status\n\n\n@dataclass\nclass _Prepared:\n    stmt: PyTursoStatement\n    tail_index: int\n    has_columns: bool\n    column_names: tuple[str, ...]\n\n\n# Connection goes FIRST\nclass Connection:\n    \"\"\"\n    A connection to a Turso (SQLite-compatible) database.\n\n    Similar to sqlite3.Connection with a subset of features focusing on DB-API 2.0.\n    \"\"\"\n\n    # Expose exception classes as attributes like sqlite3.Connection does\n    @property\n    def DataError(self) -> type[DataError]:\n        return DataError\n\n    @property\n    def DatabaseError(self) -> type[DatabaseError]:\n        return DatabaseError\n\n    @property\n    def Error(self) -> type[Error]:\n        return Error\n\n    @property\n    def IntegrityError(self) -> type[IntegrityError]:\n        return IntegrityError\n\n    @property\n    def InterfaceError(self) -> type[InterfaceError]:\n        return InterfaceError\n\n    @property\n    def InternalError(self) -> type[InternalError]:\n        return InternalError\n\n    @property\n    def NotSupportedError(self) -> type[NotSupportedError]:\n        return NotSupportedError\n\n    @property\n    def OperationalError(self) -> type[OperationalError]:\n        return OperationalError\n\n    @property\n    def ProgrammingError(self) -> type[ProgrammingError]:\n        return ProgrammingError\n\n    @property\n    def Warning(self) -> type[Warning]:\n        return Warning\n\n    def __init__(\n        self,\n        conn: PyTursoConnection,\n        *,\n        isolation_level: Optional[str] = \"DEFERRED\",\n        extra_io: Optional[Callable[[], None]] = None,\n    ) -> None:\n        self._conn: PyTursoConnection = conn\n        # autocommit behavior:\n        # - True: SQLite autocommit mode; commit/rollback are no-ops.\n        # - False: PEP 249 compliant: ensure a transaction is always open.\n        #   We'll use BEGIN DEFERRED after commit/rollback.\n        # - \"LEGACY\": implicit transactions on DML when isolation_level is not None.\n        self._autocommit_mode: object | bool = \"LEGACY\"\n        self.isolation_level: Optional[str] = isolation_level\n        self.row_factory: Callable[[Cursor, Row], object] | type[Row] | None = None\n        self.text_factory: Any = str\n        self.extra_io = extra_io\n\n        # If autocommit is False, ensure a transaction is open\n        if self._autocommit_mode is False:\n            self._ensure_transaction_open()\n\n    def _ensure_transaction_open(self) -> None:\n        \"\"\"\n        Ensure a transaction is open when autocommit is False.\n        \"\"\"\n        try:\n            if self._conn.get_auto_commit():\n                # No transaction active -> open new one according to isolation_level (default to DEFERRED)\n                level = self.isolation_level or \"DEFERRED\"\n                self._exec_ddl_only(f\"BEGIN {level}\")\n        except Exception as exc:  # noqa: BLE001\n            raise _map_turso_exception(exc)\n\n    def _exec_ddl_only(self, sql: str) -> None:\n        \"\"\"\n        Execute a SQL statement that does not produce rows and ignore any result rows.\n        \"\"\"\n        try:\n            stmt = self._conn.prepare_single(sql)\n            _run_execute_with_io(stmt, self.extra_io)\n            # finalize to ensure completion; finalize never mixes with execute\n            stmt.finalize()\n        except Exception as exc:  # noqa: BLE001\n            raise _map_turso_exception(exc)\n\n    def _prepare_first(self, sql: str) -> _Prepared:\n        \"\"\"\n        Prepare the first statement in the given SQL string and return metadata.\n        \"\"\"\n        try:\n            opt = self._conn.prepare_first(sql)\n        except Exception as exc:  # noqa: BLE001\n            raise _map_turso_exception(exc)\n        if opt is None:\n            raise ProgrammingError(\"no SQL statements to execute\")\n\n        stmt, tail_idx = opt\n        # Determine whether statement returns columns (rows)\n        try:\n            columns = tuple(stmt.columns())\n        except Exception as exc:  # noqa: BLE001\n            # Clean up statement before re-raising\n            try:\n                stmt.finalize()\n            except Exception:\n                pass\n            raise _map_turso_exception(exc)\n        has_cols = len(columns) > 0\n        return _Prepared(stmt=stmt, tail_index=tail_idx, has_columns=has_cols, column_names=columns)\n\n    def _raise_if_multiple_statements(self, sql: str, tail_index: int) -> None:\n        \"\"\"\n        Ensure there is no second statement after the first one; otherwise raise ProgrammingError.\n        \"\"\"\n        # Skip any trailing whitespace/comments after tail_index, and check if another statement exists.\n        rest = sql[tail_index:]\n        try:\n            nxt = self._conn.prepare_first(rest)\n            if nxt is not None:\n                # Clean-up the prepared second statement immediately\n                second_stmt, _ = nxt\n                try:\n                    second_stmt.finalize()\n                except Exception:\n                    pass\n                raise ProgrammingError(\"You can only execute one statement at a time\")\n        except ProgrammingError:\n            raise\n        except Exception as exc:  # noqa: BLE001\n            raise _map_turso_exception(exc)\n\n    @property\n    def in_transaction(self) -> bool:\n        try:\n            return not self._conn.get_auto_commit()\n        except Exception as exc:  # noqa: BLE001\n            raise _map_turso_exception(exc)\n\n    # Provide autocommit property for sqlite3-like API (optional)\n    @property\n    def autocommit(self) -> object | bool:\n        return self._autocommit_mode\n\n    @autocommit.setter\n    def autocommit(self, val: object | bool) -> None:\n        # Accept True, False, or \"LEGACY\"\n        if val not in (True, False, \"LEGACY\"):\n            raise ProgrammingError(\"autocommit must be True, False, or 'LEGACY'\")\n        self._autocommit_mode = val\n        # If switching to False, ensure a transaction is open\n        if val is False:\n            self._ensure_transaction_open()\n        # If switching to True or LEGACY, nothing else to do immediately.\n\n    def close(self) -> None:\n        # In sqlite3: If autocommit is False, pending transaction is implicitly rolled back.\n        try:\n            if self._autocommit_mode is False and self.in_transaction:\n                try:\n                    self._exec_ddl_only(\"ROLLBACK\")\n                except Exception:\n                    # As sqlite3 does, ignore rollback failure on close\n                    pass\n            self._conn.close()\n        except Exception as exc:  # noqa: BLE001\n            raise _map_turso_exception(exc)\n\n    def commit(self) -> None:\n        try:\n            if self._autocommit_mode is True:\n                # No-op in SQLite autocommit mode\n                return\n            if self.in_transaction:\n                self._exec_ddl_only(\"COMMIT\")\n            if self._autocommit_mode is False:\n                # Re-open a transaction to maintain PEP 249 behavior\n                self._ensure_transaction_open()\n        except Exception as exc:  # noqa: BLE001\n            raise _map_turso_exception(exc)\n\n    def rollback(self) -> None:\n        try:\n            if self._autocommit_mode is True:\n                # No-op in SQLite autocommit mode\n                return\n            if self.in_transaction:\n                self._exec_ddl_only(\"ROLLBACK\")\n            if self._autocommit_mode is False:\n                # Re-open a transaction to maintain PEP 249 behavior\n                self._ensure_transaction_open()\n        except Exception as exc:  # noqa: BLE001\n            raise _map_turso_exception(exc)\n\n    def _maybe_implicit_begin(self, sql: str) -> None:\n        \"\"\"\n        Implement sqlite3 legacy implicit transaction behavior:\n\n        If autocommit is LEGACY_TRANSACTION_CONTROL, isolation_level is not None, sql is a DML\n        (INSERT/UPDATE/DELETE/REPLACE), and there is no open transaction, issue:\n            BEGIN <isolation_level>\n        \"\"\"\n        if self._autocommit_mode == \"LEGACY\" and self.isolation_level is not None:\n            if not self.in_transaction and _is_dml(sql):\n                level = self.isolation_level or \"DEFERRED\"\n                self._exec_ddl_only(f\"BEGIN {level}\")\n\n    def cursor(self, factory: Optional[Callable[[Connection], _DBCursorT]] = None) -> _DBCursorT | Cursor:\n        if factory is None:\n            return Cursor(self)\n        return factory(self)\n\n    def execute(self, sql: str, parameters: Sequence[Any] | Mapping[str, Any] = ()) -> Cursor:\n        cur = self.cursor()\n        cur.execute(sql, parameters)\n        return cur\n\n    def executemany(self, sql: str, parameters: Iterable[Sequence[Any] | Mapping[str, Any]]) -> Cursor:\n        cur = self.cursor()\n        cur.executemany(sql, parameters)\n        return cur\n\n    def executescript(self, sql_script: str) -> Cursor:\n        cur = self.cursor()\n        cur.executescript(sql_script)\n        return cur\n\n    def __call__(self, sql: str) -> PyTursoStatement:\n        # Shortcut to prepare a single statement\n        try:\n            return self._conn.prepare_single(sql)\n        except Exception as exc:  # noqa: BLE001\n            raise _map_turso_exception(exc)\n\n    def __enter__(self) -> \"Connection\":\n        return self\n\n    def __exit__(\n        self,\n        type: type[BaseException] | None,\n        value: BaseException | None,\n        traceback: TracebackType | None,\n    ) -> bool:\n        # sqlite3 behavior: In context manager, if no exception -> commit, else rollback (legacy and PEP 249 modes)\n        try:\n            if type is None:\n                self.commit()\n            else:\n                self.rollback()\n        finally:\n            # Always propagate exceptions (returning False)\n            return False\n\n\n# Cursor goes SECOND\nclass Cursor:\n    arraysize: int\n\n    def __init__(self, connection: Connection, /) -> None:\n        self._connection: Connection = connection\n        self.arraysize = 1\n        self.row_factory: Callable[[Cursor, Row], object] | type[Row] | None = connection.row_factory\n\n        # State for the last executed statement\n        self._active_stmt: Optional[PyTursoStatement] = None\n        self._active_has_rows: bool = False\n        self._description: Optional[tuple[tuple[str, None, None, None, None, None, None], ...]] = None\n        self._lastrowid: Optional[int] = None\n        self._rowcount: int = -1\n        self._closed: bool = False\n\n    @property\n    def connection(self) -> Connection:\n        return self._connection\n\n    def close(self) -> None:\n        if self._closed:\n            return\n        try:\n            # Finalize any active statement to ensure completion.\n            if self._active_stmt is not None:\n                try:\n                    self._active_stmt.finalize()\n                except Exception:\n                    pass\n        finally:\n            self._active_stmt = None\n            self._active_has_rows = False\n            self._closed = True\n\n    def _ensure_open(self) -> None:\n        if self._closed:\n            raise ProgrammingError(\"Cannot operate on a closed cursor\")\n\n    @property\n    def description(self) -> tuple[tuple[str, None, None, None, None, None, None], ...] | None:\n        return self._description\n\n    @property\n    def lastrowid(self) -> int | None:\n        return self._lastrowid\n\n    @property\n    def rowcount(self) -> int:\n        return self._rowcount\n\n    def _reset_last_result(self) -> None:\n        # Ensure any previous statement is finalized to not leak resources\n        if self._active_stmt is not None:\n            try:\n                self._active_stmt.finalize()\n            except Exception:\n                pass\n        self._active_stmt = None\n        self._active_has_rows = False\n        self._description = None\n        self._rowcount = -1\n        # Do not reset lastrowid here; sqlite3 preserves lastrowid until next insert.\n\n    @staticmethod\n    def _to_positional_params(parameters: Sequence[Any] | Mapping[str, Any]) -> tuple[Any, ...]:\n        if isinstance(parameters, Mapping):\n            # Named placeholders are not supported\n            raise ProgrammingError(\"Named parameters are not supported; use positional parameters with '?'\")\n        if parameters is None:\n            return ()\n        if isinstance(parameters, tuple):\n            return parameters\n        # Convert arbitrary sequences to tuple efficiently\n        return tuple(parameters)\n\n    def _maybe_implicit_begin(self, sql: str) -> None:\n        self._connection._maybe_implicit_begin(sql)\n\n    def _prepare_single_statement(self, sql: str) -> _Prepared:\n        prepared = self._connection._prepare_first(sql)\n        # Ensure there are no further statements\n        self._connection._raise_if_multiple_statements(sql, prepared.tail_index)\n        return prepared\n\n    def execute(self, sql: str, parameters: Sequence[Any] | Mapping[str, Any] = ()) -> \"Cursor\":\n        self._ensure_open()\n        self._reset_last_result()\n\n        # Implement legacy implicit transactions if needed\n        self._maybe_implicit_begin(sql)\n\n        # Prepare exactly one statement\n        prepared = self._prepare_single_statement(sql)\n\n        stmt = prepared.stmt\n        try:\n            # Bind positional parameters\n            params = self._to_positional_params(parameters)\n            if params:\n                stmt.bind(params)\n\n            if prepared.has_columns:\n                # Stepped statement (e.g., SELECT or DML with RETURNING)\n                self._active_stmt = stmt\n                self._active_has_rows = True\n                # Set description immediately (even if there are no rows)\n                self._description = tuple((name, None, None, None, None, None, None) for name in prepared.column_names)\n                # For statements that return rows, DB-API specifies rowcount is -1\n                self._rowcount = -1\n                # Do not compute lastrowid here\n            else:\n                # Executed statement (no rows returned)\n                result = _run_execute_with_io(stmt, self._connection.extra_io)\n                # rows_changed from execution result\n                self._rowcount = int(result.rows_changed)\n                # Set description to None\n                self._description = None\n                # Set lastrowid for INSERT/REPLACE (best-effort)\n                self._lastrowid = self._fetch_last_insert_rowid_if_needed(sql, result.rows_changed)\n                # Finalize the statement to release resources\n                stmt.finalize()\n        except Exception as exc:  # noqa: BLE001\n            # Ensure cleanup on error\n            try:\n                stmt.finalize()\n            except Exception:\n                pass\n            raise _map_turso_exception(exc)\n\n        return self\n\n    def _fetch_last_insert_rowid_if_needed(self, sql: str, rows_changed: int) -> Optional[int]:\n        if rows_changed <= 0 or not _is_insert_or_replace(sql):\n            return self._lastrowid\n        # Query last_insert_rowid(); this is connection-scoped and cheap\n        try:\n            q = self._connection._conn.prepare_single(\"SELECT last_insert_rowid()\")\n            # No parameters; this produces a single-row single-column result\n            # Use stepping to fetch the row\n            status = _step_once_with_io(q, self._connection.extra_io)\n            if status == Status.Row:\n                py_row = q.row()\n                # row() returns a Python tuple with one element\n                # We avoid complex conversions: take first item\n                value = tuple(py_row)[0]  # type: ignore[call-arg]\n                # Finalize to complete\n                q.finalize()\n                if isinstance(value, int):\n                    return value\n                try:\n                    return int(value)\n                except Exception:\n                    return self._lastrowid\n            # Finalize anyway\n            q.finalize()\n        except Exception:\n            # Ignore errors; lastrowid remains unchanged on failure\n            pass\n        return self._lastrowid\n\n    def executemany(self, sql: str, seq_of_parameters: Iterable[Sequence[Any] | Mapping[str, Any]]) -> \"Cursor\":\n        self._ensure_open()\n        self._reset_last_result()\n\n        # executemany only accepts DML; enforce this to match sqlite3 semantics\n        if not _is_dml(sql):\n            raise ProgrammingError(\"executemany() requires a single DML (INSERT/UPDATE/DELETE/REPLACE) statement\")\n\n        # Implement legacy implicit transaction: same as execute()\n        self._maybe_implicit_begin(sql)\n\n        prepared = self._prepare_single_statement(sql)\n        stmt = prepared.stmt\n        try:\n            # For executemany, discard any rows produced (even if RETURNING was used)\n            # Therefore we ALWAYS use execute() path per-iteration.\n            for parameters in seq_of_parameters:\n                # Reset previous bindings and program memory before reusing\n                stmt.reset()\n                params = self._to_positional_params(parameters)\n                if params:\n                    stmt.bind(params)\n                result = _run_execute_with_io(stmt, self._connection.extra_io)\n                # rowcount is \"the number of modified rows\" for the LAST executed statement only\n                self._rowcount = int(result.rows_changed) + (self._rowcount if self._rowcount != -1 else 0)\n            # After loop, finalize statement\n            stmt.finalize()\n            # Cursor description is None for DML executed via executemany()\n            self._description = None\n            # sqlite3 leaves lastrowid unchanged for executemany\n        except Exception as exc:  # noqa: BLE001\n            try:\n                stmt.finalize()\n            except Exception:\n                pass\n            raise _map_turso_exception(exc)\n        return self\n\n    def executescript(self, sql_script: str) -> \"Cursor\":\n        self._ensure_open()\n        self._reset_last_result()\n\n        # sqlite3 behavior: If autocommit is LEGACY and there is a pending transaction, implicitly COMMIT first\n        if self._connection._autocommit_mode == \"LEGACY\" and self._connection.in_transaction:\n            try:\n                self._connection._exec_ddl_only(\"COMMIT\")\n            except Exception as exc:  # noqa: BLE001\n                raise _map_turso_exception(exc)\n\n        # Iterate over statements in the script and execute them, discarding rows\n        sql = sql_script\n        total_rowcount = -1\n        try:\n            offset = 0\n            while True:\n                opt = self._connection._conn.prepare_first(sql[offset:])\n                if opt is None:\n                    break\n                stmt, tail = opt\n                # Note: per DB-API, any resulting rows are discarded\n                result = _run_execute_with_io(stmt, self._connection.extra_io)\n                total_rowcount = int(result.rows_changed) if result.rows_changed > 0 else total_rowcount\n                # finalize to ensure completion\n                stmt.finalize()\n                offset += tail\n        except Exception as exc:  # noqa: BLE001\n            raise _map_turso_exception(exc)\n\n        self._description = None\n        self._rowcount = total_rowcount\n        return self\n\n    def _fetchone_tuple(self) -> Optional[tuple[Any, ...]]:\n        \"\"\"\n        Fetch one row as a plain Python tuple, or return None if no more rows.\n        \"\"\"\n        if not self._active_has_rows or self._active_stmt is None:\n            return None\n        try:\n            status = _step_once_with_io(self._active_stmt, self._connection.extra_io)\n            if status == Status.Row:\n                row_tuple = tuple(self._active_stmt.row())  # type: ignore[call-arg]\n                return row_tuple\n            # status == Done: finalize and clean up\n            self._active_stmt.finalize()\n            self._active_stmt = None\n            self._active_has_rows = False\n            return None\n        except Exception as exc:  # noqa: BLE001\n            # Finalize and clean up on error\n            try:\n                if self._active_stmt is not None:\n                    self._active_stmt.finalize()\n            except Exception:\n                pass\n            self._active_stmt = None\n            self._active_has_rows = False\n            raise _map_turso_exception(exc)\n\n    def _apply_row_factory(self, row_values: tuple[Any, ...]) -> Any:\n        rf = self.row_factory\n        if rf is None:\n            return row_values\n        if isinstance(rf, type) and issubclass(rf, Row):\n            return rf(self, Row(self, row_values))  # type: ignore[call-arg]\n        if callable(rf):\n            return rf(self, Row(self, row_values))  # type: ignore[misc]\n        # Fallback: return tuple\n        return row_values\n\n    def fetchone(self) -> Any:\n        self._ensure_open()\n        row = self._fetchone_tuple()\n        if row is None:\n            return None\n        return self._apply_row_factory(row)\n\n    def fetchmany(self, size: Optional[int] = None) -> list[Any]:\n        self._ensure_open()\n        if size is None:\n            size = self.arraysize\n        if size < 0:\n            raise ValueError(\"size must be non-negative\")\n        result: list[Any] = []\n        for _ in range(size):\n            row = self._fetchone_tuple()\n            if row is None:\n                break\n            result.append(self._apply_row_factory(row))\n        return result\n\n    def fetchall(self) -> list[Any]:\n        self._ensure_open()\n        result: list[Any] = []\n        while True:\n            row = self._fetchone_tuple()\n            if row is None:\n                break\n            result.append(self._apply_row_factory(row))\n        return result\n\n    def setinputsizes(self, sizes: Any, /) -> None:\n        # No-op for DB-API compliance\n        return None\n\n    def setoutputsize(self, size: Any, column: Any = None, /) -> None:\n        # No-op for DB-API compliance\n        return None\n\n    def __iter__(self) -> \"Cursor\":\n        return self\n\n    def __next__(self) -> Any:\n        row = self.fetchone()\n        if row is None:\n            raise StopIteration\n        return row\n\n\n# Row goes THIRD\nclass Row(Sequence[Any]):\n    \"\"\"\n    sqlite3.Row-like container supporting index and name-based access.\n    \"\"\"\n\n    def __new__(cls, cursor: Cursor, data: tuple[Any, ...], /) -> \"Row\":\n        obj = super().__new__(cls)\n        # Attach metadata\n        obj._cursor = cursor\n        obj._data = data\n        # Build mapping from column name to index\n        desc = cursor.description or ()\n        obj._keys = tuple(col[0] for col in desc)\n        obj._index = {name: idx for idx, name in enumerate(obj._keys)}\n        return obj\n\n    def keys(self) -> list[str]:\n        return list(self._keys)\n\n    def __getitem__(self, key: int | str | slice, /) -> Any:\n        if isinstance(key, slice):\n            return self._data[key]\n        if isinstance(key, int):\n            return self._data[key]\n        # key is column name\n        idx = self._index.get(key)\n        if idx is None:\n            raise KeyError(key)\n        return self._data[idx]\n\n    def __hash__(self) -> int:\n        return hash((self._keys, self._data))\n\n    def __iter__(self) -> Iterator[Any]:\n        return iter(self._data)\n\n    def __len__(self) -> int:\n        return len(self._data)\n\n    def __eq__(self, value: object, /) -> bool:\n        if not isinstance(value, Row):\n            return NotImplemented  # type: ignore[return-value]\n        return self._keys == value._keys and self._data == value._data\n\n    def __ne__(self, value: object, /) -> bool:\n        if not isinstance(value, Row):\n            return NotImplemented  # type: ignore[return-value]\n        return not self.__eq__(value)\n\n    # The rest return NotImplemented for non-Row comparisons\n    def __lt__(self, value: object, /) -> bool:\n        if not isinstance(value, Row):\n            return NotImplemented  # type: ignore[return-value]\n        return (self._keys, self._data) < (value._keys, value._data)\n\n    def __le__(self, value: object, /) -> bool:\n        if not isinstance(value, Row):\n            return NotImplemented  # type: ignore[return-value]\n        return (self._keys, self._data) <= (value._keys, value._data)\n\n    def __gt__(self, value: object, /) -> bool:\n        if not isinstance(value, Row):\n            return NotImplemented  # type: ignore[return-value]\n        return (self._keys, self._data) > (value._keys, value._data)\n\n    def __ge__(self, value: object, /) -> bool:\n        if not isinstance(value, Row):\n            return NotImplemented  # type: ignore[return-value]\n        return (self._keys, self._data) >= (value._keys, value._data)\n\n\n@dataclass\nclass EncryptionOpts:\n    cipher: str\n    hexkey: str\n\n\ndef connect(\n    database: str,\n    *,\n    experimental_features: Optional[str] = None,\n    vfs: Optional[str] = None,\n    encryption: Optional[EncryptionOpts] = None,\n    isolation_level: Optional[str] = \"DEFERRED\",\n    extra_io: Optional[Callable[[], None]] = None,\n) -> Connection:\n    \"\"\"\n    Open a Turso (SQLite-compatible) database and return a Connection.\n\n    Parameters:\n    - database: path or identifier of the database.\n    - experimental_features: comma-separated list of features to enable.\n    - isolation_level: one of \"DEFERRED\" (default), \"IMMEDIATE\", \"EXCLUSIVE\", or None.\n    \"\"\"\n    try:\n        cfg = PyTursoDatabaseConfig(\n            path=database,\n            experimental_features=experimental_features,\n            vfs=vfs,\n            encryption=PyTursoEncryptionConfig(cipher=encryption.cipher, hexkey=encryption.hexkey)\n            if encryption\n            else None,\n        )\n        db: PyTursoDatabase = py_turso_database_open(cfg)\n        conn: PyTursoConnection = db.connect()\n        return Connection(conn, isolation_level=isolation_level, extra_io=extra_io)\n    except Exception as exc:  # noqa: BLE001\n        raise _map_turso_exception(exc)\n\n\n# Make it easy to enable logging with native `logging` Python module\ndef setup_logging(level: Optional[int] = None) -> None:\n    \"\"\"\n    Setup Turso logging to integrate with Python's logging module.\n\n    Usage:\n        import turso\n        turso.setup_logging(logging.DEBUG)\n    \"\"\"\n    import logging\n\n    level = level or logging.INFO\n    logger = logging.getLogger(\"turso\")\n    logger.setLevel(level)\n\n    def _py_logger(log: PyTursoLog) -> None:\n        # Map Rust/Turso log level strings to Python logging levels (best-effort)\n        lvl_map = {\n            \"ERROR\": logging.ERROR,\n            \"WARN\": logging.WARNING,\n            \"INFO\": logging.INFO,\n            \"DEBUG\": logging.DEBUG,\n            \"TRACE\": logging.DEBUG,\n        }\n        py_level = lvl_map.get(log.level.upper(), level)\n        logger.log(\n            py_level,\n            \"%s [%s:%s] %s\",\n            log.target,\n            log.file,\n            log.line,\n            log.message,\n        )\n\n    try:\n        py_turso_setup(\n            PyTursoSetupConfig(\n                logger=_py_logger,\n                log_level={\n                    logging.ERROR: \"error\",\n                    logging.WARN: \"warn\",\n                    logging.INFO: \"info\",\n                    logging.DEBUG: \"debug\",\n                }[level],\n            )\n        )\n    except Exception as exc:  # noqa: BLE001\n        raise _map_turso_exception(exc)\n"
  },
  {
    "path": "bindings/python/turso/lib_aio.py",
    "content": "from __future__ import annotations\n\nimport asyncio\nfrom queue import SimpleQueue\nfrom typing import Any, Callable, Iterable, Mapping, Optional, Sequence\n\nfrom .lib import (\n    Connection as BlockingConnection,\n)\nfrom .lib import (\n    Cursor as BlockingCursor,\n)\nfrom .lib import (\n    ProgrammingError,\n)\nfrom .lib import (\n    connect as blocking_connect,\n)\nfrom .worker import STOP_RUNNING_SENTINEL, Worker\n\n\n# Connection goes FIRST\nclass Connection:\n    def __init__(self, connector: Callable[[], BlockingConnection]) -> None:\n        # Event loop and per-connection worker thread state\n        self._loop = asyncio.get_event_loop()\n        self._queue: SimpleQueue[tuple[asyncio.Future, Callable[[], Any]]] = SimpleQueue()\n        self._worker = Worker(self._queue, self._loop)\n        self._connector = connector\n\n        # Underlying blocking connection created in worker thread\n        self._conn: Optional[BlockingConnection] = None\n        self._closed: bool = False\n\n        # Schedule connection creation as the very first job in the worker\n        self._open_future: asyncio.Future[Connection] = self._loop.create_future()\n\n        def _open() -> Connection:\n            # Create the blocking connection inside the worker thread once\n            if self._conn is None:\n                self._conn = self._connector()\n            return self\n\n        self._queue.put_nowait((self._open_future, _open))\n        self._worker.start()\n\n        # Cached properties mirrored to the underlying connection.\n        # Setters will enqueue mutation jobs; we keep local cache for getters.\n        self._isolation_level_cache: Optional[str] = None\n        self._row_factory_cache: Any = None\n        self._text_factory_cache: Any = None\n        self._autocommit_cache: object | bool | None = None\n\n    async def close(self) -> None:\n        if self._closed:\n            return\n\n        # Ensure underlying Connection.close() is called in worker thread\n        def _do_close() -> None:\n            if self._conn is not None:\n                self._conn.close()\n\n        await self._run(lambda: (_do_close(), None)[1])  # schedule and await completion of close\n\n        # Request worker stop; we must not block the event loop while waiting.\n        # Note: STOP_RUNNING_SENTINEL item will terminate the worker loop.\n        stop_future = self._loop.create_future()\n        self._queue.put_nowait((stop_future, lambda: STOP_RUNNING_SENTINEL))\n\n        # Wait for the worker thread to terminate without blocking the loop\n        await self._loop.run_in_executor(None, self._worker.join)\n        self._closed = True\n\n    def __await__(self):\n        async def _await_open() -> \"Connection\":\n            await self._open_future\n            return self\n\n        return _await_open().__await__()\n\n    async def __aenter__(self) -> \"Connection\":\n        await self\n        return self\n\n    async def __aexit__(self, exc_type, exc, tb) -> None:\n        # Just close the connection - do not add any extra logic\n        await self.close()\n\n    # Internal helper: schedule a callable to run in the worker thread and await its result.\n    async def _run(self, func: Callable[[], Any]) -> Any:\n        if self._closed:\n            raise ProgrammingError(\"Cannot operate on a closed connection\")\n        fut = self._loop.create_future()\n        self._queue.put_nowait((fut, func))\n        return await fut\n\n    # Internal helper: enqueue a callable but do not await completion (used for property setters).\n    def _run_nowait(self, func: Callable[[], Any]) -> None:\n        if self._closed:\n            raise ProgrammingError(\"Cannot operate on a closed connection\")\n        fut = self._loop.create_future()\n        self._queue.put_nowait((fut, func))\n\n    # Cursor factory returning async Cursor wrapper\n    def cursor(self, factory: Optional[Callable[[BlockingConnection], BlockingCursor]] = None) -> \"Cursor\":\n        # Creation of the underlying blocking cursor is enqueued to preserve thread affinity.\n        return Cursor(self, factory=factory)\n\n    # Helpers similar to aiosqlite\n    async def execute(self, sql: str, parameters: Sequence[Any] | Mapping[str, Any] = ()) -> \"Cursor\":\n        cur = self.cursor()\n        await cur.execute(sql, parameters)\n        return cur\n\n    async def executemany(self, sql: str, parameters: Iterable[Sequence[Any] | Mapping[str, Any]]) -> \"Cursor\":\n        cur = self.cursor()\n        await cur.executemany(sql, parameters)\n        return cur\n\n    async def executescript(self, sql_script: str) -> \"Cursor\":\n        cur = self.cursor()\n        await cur.executescript(sql_script)\n        return cur\n\n    async def commit(self) -> None:\n        await self._run(lambda: self._conn.commit())  # type: ignore[union-attr]\n\n    async def rollback(self) -> None:\n        await self._run(lambda: self._conn.rollback())  # type: ignore[union-attr]\n\n    # Read/write properties mirrored to the underlying blocking connection.\n    # DB-API hints:\n    # - isolation_level controls implicit transactions (BEGIN DEFERRED/IMMEDIATE/EXCLUSIVE or None for autocommit).\n    @property\n    def isolation_level(self) -> Optional[str]:\n        return self._isolation_level_cache\n\n    @isolation_level.setter\n    def isolation_level(self, value: Optional[str]) -> None:\n        self._isolation_level_cache = value\n\n        def _set() -> None:\n            # Will be applied in the worker thread before subsequent operations\n            self._conn.isolation_level = value  # type: ignore[union-attr]\n\n        self._run_nowait(_set)\n\n    @property\n    def row_factory(self) -> Any:\n        return self._row_factory_cache\n\n    @row_factory.setter\n    def row_factory(self, value: Any) -> None:\n        self._row_factory_cache = value\n\n        def _set() -> None:\n            self._conn.row_factory = value  # type: ignore[union-attr]\n\n        self._run_nowait(_set)\n\n    @property\n    def text_factory(self) -> Any:\n        return self._text_factory_cache\n\n    @text_factory.setter\n    def text_factory(self, value: Any) -> None:\n        self._text_factory_cache = value\n\n        def _set() -> None:\n            self._conn.text_factory = value  # type: ignore[union-attr]\n\n        self._run_nowait(_set)\n\n    @property\n    def autocommit(self) -> object | bool | None:\n        return self._autocommit_cache\n\n    @autocommit.setter\n    def autocommit(self, value: object | bool) -> None:\n        self._autocommit_cache = value\n\n        def _set() -> None:\n            self._conn.autocommit = value  # type: ignore[union-attr]\n\n        self._run_nowait(_set)\n\n\n# Cursor goes SECOND\nclass Cursor:\n    def __init__(\n        self, connection: Connection, factory: Optional[Callable[[BlockingConnection], BlockingCursor]] = None\n    ):\n        self._connection: Connection = connection\n        self._loop = asyncio.get_event_loop()\n\n        # Underlying blocking cursor and its creation job\n        self._cursor_created: bool = False\n        self._bcursor: Optional[BlockingCursor] = None\n\n        # Cursor attributes (DB-API)\n        self.arraysize: int = 1\n        self._description: Optional[tuple[tuple[str, None, None, None, None, None, None], ...]] = None\n        self._lastrowid: Optional[int] = None\n        self._rowcount: int = -1\n        self._closed: bool = False\n\n        # Enqueue creation of the underlying blocking cursor in the worker thread\n        def _create() -> None:\n            if self._cursor_created:\n                return\n            if factory is None:\n                self._bcursor = self._connection._conn.cursor()  # type: ignore[union-attr]\n            else:\n                # Use provided factory to create BlockingCursor from BlockingConnection\n                self._bcursor = factory(self._connection._conn)  # type: ignore[union-attr]\n            self._cursor_created = True\n            # Apply initial arraysize if any\n            self._bcursor.arraysize = self.arraysize  # type: ignore[union-attr]\n\n        self._connection._run_nowait(_create)\n\n    @property\n    def connection(self) -> Connection:\n        return self._connection\n\n    async def close(self) -> None:\n        if self._closed:\n            return\n\n        def _close() -> None:\n            if self._bcursor is not None:\n                self._bcursor.close()\n\n        await self._connection._run(_close)\n        self._closed = True\n\n    # Internal helpers for updating cached metadata after execute-like calls\n    def _update_meta_cache(self, description, lastrowid, rowcount) -> None:\n        self._description = description\n        self._lastrowid = lastrowid\n        self._rowcount = rowcount if rowcount is not None else -1\n\n    async def execute(self, sql: str, parameters: Sequence[Any] | Mapping[str, Any] = ()) -> \"Cursor\":\n        self._ensure_open()\n\n        def _exec() -> tuple[Any, Any, Any]:\n            # Perform the execute and collect metadata\n            cur = self._bcursor  # type: ignore[assignment]\n            cur.execute(sql, parameters)\n            return (cur.description, cur.lastrowid, cur.rowcount)\n\n        description, lastrowid, rowcount = await self._connection._run(_exec)\n        self._update_meta_cache(description, lastrowid, rowcount)\n        return self\n\n    async def executemany(self, sql: str, parameters: Iterable[Sequence[Any] | Mapping[str, Any]]) -> \"Cursor\":\n        self._ensure_open()\n\n        def _execm() -> tuple[Any, Any, Any]:\n            cur = self._bcursor  # type: ignore[assignment]\n            cur.executemany(sql, parameters)\n            return (cur.description, cur.lastrowid, cur.rowcount)\n\n        description, lastrowid, rowcount = await self._connection._run(_execm)\n        self._update_meta_cache(description, lastrowid, rowcount)\n        return self\n\n    async def executescript(self, sql_script: str) -> \"Cursor\":\n        self._ensure_open()\n\n        def _execs() -> tuple[Any, Any, Any]:\n            cur = self._bcursor  # type: ignore[assignment]\n            cur.executescript(sql_script)\n            return (cur.description, cur.lastrowid, cur.rowcount)\n\n        description, lastrowid, rowcount = await self._connection._run(_execs)\n        self._update_meta_cache(description, lastrowid, rowcount)\n        return self\n\n    async def fetchone(self) -> Any:\n        self._ensure_open()\n\n        def _one() -> Any:\n            return self._bcursor.fetchone()  # type: ignore[union-attr]\n\n        return await self._connection._run(_one)\n\n    async def fetchmany(self, size: Optional[int] = None) -> list[Any]:\n        self._ensure_open()\n\n        def _many() -> list[Any]:\n            n = self.arraysize if size is None else size\n            return list(self._bcursor.fetchmany(n))  # type: ignore[union-attr]\n\n        return await self._connection._run(_many)\n\n    async def fetchall(self) -> list[Any]:\n        self._ensure_open()\n\n        def _all() -> list[Any]:\n            return list(self._bcursor.fetchall())  # type: ignore[union-attr]\n\n        return await self._connection._run(_all)\n\n    def _ensure_open(self) -> None:\n        if self._closed:\n            raise ProgrammingError(\"Cannot operate on a closed cursor\")\n\n    # Properties reflecting DB-API attributes of the last executed statement\n    @property\n    def description(self) -> tuple[tuple[str, None, None, None, None, None, None], ...] | None:\n        return self._description\n\n    @property\n    def lastrowid(self) -> int | None:\n        return self._lastrowid\n\n    @property\n    def rowcount(self) -> int:\n        return self._rowcount\n\n    # Make cursor usable as async context manager, similar to aiosqlite\n    async def __aenter__(self) -> \"Cursor\":\n        return self\n\n    async def __aexit__(self, exc_type, exc, tb) -> None:\n        await self.close()\n\n\n# connect is not async because it returns awaitable Connection\n# same signature as in the lib.py\ndef connect(\n    database: str,\n    *,\n    experimental_features: Optional[str] = None,\n    isolation_level: Optional[str] = \"DEFERRED\",\n    extra_io: Optional[Callable[[], None]] = None,\n) -> Connection:\n    # Create a connector that opens a blocking Connection using the existing driver.\n    def _connector() -> BlockingConnection:\n        conn = blocking_connect(\n            database,\n            experimental_features=experimental_features,\n            isolation_level=isolation_level,\n            extra_io=extra_io,\n        )\n        return conn\n\n    return Connection(_connector)\n"
  },
  {
    "path": "bindings/python/turso/lib_sync.py",
    "content": "from __future__ import annotations\n\nimport os\nimport urllib.error\n\n# for HTTP IO\nimport urllib.request\nfrom dataclasses import dataclass\nfrom typing import Any, Callable, Iterable, Optional, Tuple, Union\n\nfrom ._turso import (\n    Misuse,\n    PyTursoAsyncOperation,\n    PyTursoAsyncOperationResultKind,\n    PyTursoConnection,\n    PyTursoDatabaseConfig,\n    PyTursoPartialSyncOpts,\n    PyTursoSyncDatabase,\n    PyTursoSyncDatabaseConfig,\n    PyTursoSyncDatabaseStats,\n    PyTursoSyncIoItem,\n    PyTursoSyncIoItemRequestKind,\n    py_turso_sync_new,\n)\nfrom ._turso import (\n    PyRemoteEncryptionCipher as RemoteEncryptionCipher,\n)\nfrom .lib import Connection as _Connection\nfrom .lib import _map_turso_exception\n\n# Constants\n_HTTP_CHUNK_SIZE = 64 * 1024  # 64 KiB\n\n\n@dataclass\nclass PartialSyncPrefixBootstrap:\n    # Bootstraps DB by fetching first N bytes/pages; enables partial sync\n    length: int\n\n\n@dataclass\nclass PartialSyncQueryBootstrap:\n    # Bootstraps DB by fetching pages touched by given SQL query on server\n    query: str\n\n\n@dataclass\nclass PartialSyncOpts:\n    bootstrap_strategy: Union[PartialSyncPrefixBootstrap, PartialSyncQueryBootstrap]\n    segment_size: Optional[int] = None\n    prefetch: Optional[bool] = None\n\n\nclass _HttpContext:\n    \"\"\"\n    Resolved network/auth configuration used by sync engine IO handler.\n    remote_url and auth_token can be static strings or callables (evaluated per request).\n    \"\"\"\n\n    def __init__(\n        self,\n        remote_url: Optional[Union[str, Callable[[], Optional[str]]]],\n        auth_token: Optional[Union[str, Callable[[], Optional[str]]]],\n        client_name: str,\n    ) -> None:\n        self.remote_url = remote_url\n        self.auth_token = auth_token\n        self.client_name = client_name\n\n    def _eval(self, v: Optional[Union[str, Callable[[], Optional[str]]]]) -> Optional[str]:\n        if callable(v):\n            return v()\n        return v\n\n    def base_url(self) -> Optional[str]:\n        return self._eval(self.remote_url)\n\n    def token(self) -> Optional[str]:\n        if self.auth_token is None:\n            return None\n        return self._eval(self.auth_token)\n\n\ndef _join_url(base: str, path: str) -> str:\n    if not base:\n        return path\n    if base.endswith(\"/\") and path.startswith(\"/\"):\n        return base[:-1] + path\n    if not base.endswith(\"/\") and not path.startswith(\"/\"):\n        return base + \"/\" + path\n    return base + path\n\n\ndef _headers_iter_to_pairs(headers: Iterable[Tuple[str, str]]) -> list[tuple[str, str]]:\n    pairs: list[tuple[str, str]] = []\n    for h in headers:\n        try:\n            k, v = h\n        except Exception:\n            # best-effort skip invalid headers\n            continue\n        pairs.append((str(k), str(v)))\n    return pairs\n\n\n# ruff: noqa: C901\ndef _process_http_item(\n    sync: PyTursoSyncDatabase,\n    io_item: PyTursoSyncIoItem,\n    req_kind: Any,\n    ctx: _HttpContext,\n    current_op: Optional[PyTursoAsyncOperation],\n) -> None:\n    \"\"\"\n    Execute HTTP request, stream response to sync io completion.\n    \"\"\"\n    # Access request fields\n    method = req_kind.method\n    path = req_kind.path\n    body: Optional[bytes] = None\n    if req_kind.body is not None:\n        # req_kind.body is PyBytes -> bytes\n        body = bytes(req_kind.body)\n\n    headers_list = []\n    if req_kind.headers is not None:\n        headers_list = _headers_iter_to_pairs(req_kind.headers)  # list[(k,v)]\n\n    try:\n        base_url = ctx.base_url()\n    except Exception as e:\n        io_item.poison(f\"remote url unavailable: {e}\")\n        return\n\n    # Build full URL\n    url = base_url if base_url else req_kind.url\n    if not url:\n        io_item.poison(\"remote url unavailable\")\n        raise RuntimeError(\"remote_url is not available\")\n    url = _join_url(url, path)\n\n    # Build request\n    request = urllib.request.Request(url=url, data=body, method=method)\n    # Add provided headers\n    seen_auth = False\n    for k, v in headers_list:\n        request.add_header(k, v)\n        if k.lower() == \"authorization\":\n            seen_auth = True\n\n    # Add Authorization if not present and token provided\n    token = None\n    try:\n        token = ctx.token()\n    except Exception:\n        # token resolver failure -> bubble up as IO error\n        io_item.poison(\"auth token resolver failed\")\n        return\n\n    if token is None and not seen_auth:\n        # No token provided; some endpoints can be public; proceed without it.\n        pass\n    elif token is not None and not seen_auth:\n        request.add_header(\"Authorization\", f\"Bearer {token}\")\n\n    # Add a clear user-agent to help server logs\n    if \"User-Agent\" not in request.headers:\n        request.add_header(\"User-Agent\", f\"{ctx.client_name}\")\n\n    # Perform request\n    try:\n        with urllib.request.urlopen(request) as resp:\n            status = getattr(resp, \"status\", None)\n            if status is None:\n                try:\n                    status = resp.getcode()\n                except Exception:\n                    status = 200\n            io_item.status(int(status))\n            # Stream response in chunks\n            while True:\n                chunk = resp.read(_HTTP_CHUNK_SIZE)\n                if not chunk:\n                    break\n                io_item.push_buffer(chunk)\n                if current_op is not None:\n                    # The operation should still be waiting for IO\n                    r = current_op.resume()\n                    # Per contract, while streaming response operation must not finish\n                    # We don't raise if it did, but assert in debug builds\n                    try:\n                        assert r is None\n                    except Exception:\n                        # continue anyway\n                        pass\n            io_item.done()\n    except urllib.error.HTTPError as e:\n        # HTTPError has a response body we may stream to completion\n        status = getattr(e, \"code\", 500)\n        io_item.status(int(status))\n        try:\n            # e.read() may not be available in all Python versions; use e.fp if present\n            stream = e\n            # Attempt to read the error body and forward it\n            while True:\n                chunk = stream.read(_HTTP_CHUNK_SIZE)\n                if not chunk:\n                    break\n                io_item.push_buffer(chunk)\n                if current_op is not None:\n                    r = current_op.resume()\n                    try:\n                        assert r is None\n                    except Exception:\n                        pass\n        except Exception:\n            # ignore body read failures\n            pass\n        finally:\n            io_item.done()\n    except urllib.error.URLError as e:\n        io_item.poison(f\"network error: {e.reason}\")\n    except Exception as e:\n        io_item.poison(f\"http error: {e}\")\n\n\ndef _process_full_read_item(io_item: PyTursoSyncIoItem, req_kind: Any) -> None:\n    \"\"\"\n    Fulfill full file read request by streaming file content if exists.\n    On not found - send empty response (not error).\n    \"\"\"\n    path = req_kind.path\n    try:\n        with open(path, \"rb\") as f:\n            while True:\n                chunk = f.read(_HTTP_CHUNK_SIZE)\n                if not chunk:\n                    break\n                io_item.push_buffer(chunk)\n        io_item.done()\n    except FileNotFoundError:\n        # On not found engine expects empty response, not error\n        io_item.done()\n    except Exception as e:\n        io_item.poison(f\"fs read error: {e}\")\n\n\ndef _process_full_write_item(io_item: PyTursoSyncIoItem, req_kind: Any) -> None:\n    \"\"\"\n    Fulfill full file write request by writing provided content atomically.\n    \"\"\"\n    path = req_kind.path\n    content: bytes = bytes(req_kind.content) if req_kind.content is not None else b\"\"\n    # Ensure parent directory exists\n    try:\n        parent = os.path.dirname(path)\n        if parent and not os.path.exists(parent):\n            os.makedirs(parent, exist_ok=True)\n    except Exception:\n        # ignore directory creation errors, attempt to write anyway\n        pass\n\n    try:\n        with open(path, \"wb\") as f:\n            # Write in chunks if content is large\n            view = memoryview(content)\n            offset = 0\n            length = len(view)\n            while offset < length:\n                end = min(offset + _HTTP_CHUNK_SIZE, length)\n                f.write(view[offset:end])\n                offset = end\n        io_item.done()\n    except Exception as e:\n        io_item.poison(f\"fs write error: {e}\")\n\n\ndef _drain_sync_io(\n    sync: PyTursoSyncDatabase,\n    ctx: _HttpContext,\n    *,\n    current_op: Optional[PyTursoAsyncOperation] = None,\n) -> None:\n    \"\"\"\n    Drain all pending IO items from sync engine queue and process them.\n    \"\"\"\n    while True:\n        item = sync.take_io_item()\n        try:\n            # tricky: we must do step_io_callbacks even if there is no IO in the queue\n            if item is None:\n                break\n            req = item.request()\n            if req.kind == PyTursoSyncIoItemRequestKind.Http and req.http is not None:\n                _process_http_item(sync, item, req.http, ctx, current_op)\n            elif req.kind == PyTursoSyncIoItemRequestKind.FullRead and req.full_read is not None:\n                _process_full_read_item(item, req.full_read)\n            elif req.kind == PyTursoSyncIoItemRequestKind.FullWrite and req.full_write is not None:\n                _process_full_write_item(item, req.full_write)\n            else:\n                item.poison(\"unknown io request kind\")\n        except Exception as e:\n            # Safety net: poison unexpected failures\n            try:\n                item.poison(f\"io processing error: {e}\")\n            except Exception:\n                pass\n        finally:\n            # Allow engine to run any post-io callbacks\n            sync.step_io_callbacks()\n\n\ndef _run_op(\n    sync: PyTursoSyncDatabase,\n    op: PyTursoAsyncOperation,\n    ctx: _HttpContext,\n) -> Any:\n    \"\"\"\n    Drive async operation to completion, servicing sync engine IO in between.\n    Returns operation result payload depending on kind:\n      - No: returns None\n      - Connection: returns PyTursoConnection\n      - Changes: returns PyTursoSyncDatabaseChanges\n      - Stats: returns PyTursoSyncDatabaseStats\n    \"\"\"\n    while True:\n        try:\n            finished = op.resume()\n        except Exception as exc:  # noqa: BLE001\n            raise _map_turso_exception(exc)\n        if not finished:\n            # Needs IO\n            _drain_sync_io(sync, ctx, current_op=op)\n            continue\n        # Finished\n        res = op.take_result()\n        if res.kind == PyTursoAsyncOperationResultKind.No:\n            return None\n        if res.kind == PyTursoAsyncOperationResultKind.Connection and res.connection is not None:\n            return res.connection\n        if res.kind == PyTursoAsyncOperationResultKind.Changes and res.changes is not None:\n            return res.changes\n        if res.kind == PyTursoAsyncOperationResultKind.Stats and res.stats is not None:\n            return res.stats\n        # Unexpected; return None\n        return None\n\n\nclass ConnectionSync(_Connection):\n    \"\"\"\n    Synchronized connection that extends regular embedded driver with\n    push/pull and remote bootstrap capabilities.\n    \"\"\"\n\n    def __init__(\n        self,\n        conn: PyTursoConnection,\n        *,\n        sync: PyTursoSyncDatabase,\n        http_ctx: _HttpContext,\n        isolation_level: Optional[str] = \"DEFERRED\",\n    ) -> None:\n        # Provide extra_io hook so statements can make progress with sync engine (partial sync)\n        def _extra_io() -> None:\n            _drain_sync_io(sync, http_ctx, current_op=None)\n\n        super().__init__(conn, isolation_level=isolation_level, extra_io=_extra_io)\n        self._sync: PyTursoSyncDatabase = sync\n        self._http_ctx: _HttpContext = http_ctx\n\n    def pull(self) -> bool:\n        \"\"\"\n        Pull remote changes and apply locally.\n        Returns True if new updates were pulled; False otherwise.\n        \"\"\"\n        # Wait for changes\n        changes = _run_op(self._sync, self._sync.wait_changes(), self._http_ctx)\n        # determine if empty before applying\n        if changes is None:\n            # Should not happen; treat as no changes\n            return False\n        is_empty = bool(changes.empty())\n        if is_empty:\n            return False\n        # Apply non-empty changes\n        op = self._sync.apply_changes(changes)\n        _run_op(self._sync, op, self._http_ctx)\n        return True\n\n    def push(self) -> None:\n        \"\"\"\n        Push local changes to remote.\n        \"\"\"\n        _run_op(self._sync, self._sync.push_changes(), self._http_ctx)\n\n    def checkpoint(self) -> None:\n        \"\"\"\n        Checkpoint the WAL of the synced database.\n        \"\"\"\n        _run_op(self._sync, self._sync.checkpoint(), self._http_ctx)\n\n    def stats(self) -> PyTursoSyncDatabaseStats:\n        \"\"\"\n        Collect stats about the synced database.\n        \"\"\"\n        stats = _run_op(self._sync, self._sync.stats(), self._http_ctx)\n        return stats\n\n\ndef connect_sync(\n    path: str,\n    remote_url: Optional[Union[str, Callable[[], Optional[str]]]] = None,\n    *,\n    auth_token: Optional[Union[str, Callable[[], Optional[str]]]] = None,\n    client_name: Optional[str] = None,\n    long_poll_timeout_ms: Optional[int] = None,\n    bootstrap_if_empty: bool = True,\n    partial_sync_experimental: Optional[PartialSyncOpts] = None,\n    experimental_features: Optional[str] = None,\n    isolation_level: Optional[str] = \"DEFERRED\",\n    remote_encryption_key: Optional[str] = None,\n    remote_encryption_cipher: Optional[RemoteEncryptionCipher] = None,\n) -> ConnectionSync:\n    \"\"\"\n    Create and open a synchronized database connection.\n\n    - path: path to the main database file locally\n    - remote_url: remote url for the sync - can be lambda evaluated on every http request\n    - auth_token: optional token or lambda returning token, used as Authorization: Bearer <token>\n    - client_name: optional unique client name (defaults to 'turso-sync-py')\n    - long_poll_timeout_ms: timeout for long polling during pull\n    - bootstrap_if_empty: if True and db empty, bootstrap from remote during create()\n    - partial_sync_experimental: EXPERIMENTAL partial sync configuration\n    - experimental_features, isolation_level: passed to underlying connection\n    - remote_encryption_key: base64-encoded encryption key for encrypted Turso Cloud databases\n    - remote_encryption_cipher: encryption cipher for the remote database (used to calculate reserved_bytes)\n    \"\"\"\n    # Resolve client name\n    cname = client_name or \"turso-sync-py\"\n    if remote_url and isinstance(remote_url, str) and remote_url.startswith(\"libsql://\"):\n        remote_url = remote_url.replace(\"libsql://\", \"https://\", 1)\n    http_ctx = _HttpContext(remote_url=remote_url, auth_token=auth_token, client_name=cname)\n\n    # Database config: async_io must be True to let Python drive IO\n    db_cfg = PyTursoDatabaseConfig(\n        path=path,\n        experimental_features=experimental_features,\n    )\n\n    # Sync config with optional partial bootstrap strategy\n    prefix_len: Optional[int] = None\n    query_str: Optional[str] = None\n    if partial_sync_experimental is not None and isinstance(\n        partial_sync_experimental.bootstrap_strategy, PartialSyncPrefixBootstrap\n    ):\n        prefix_len = int(partial_sync_experimental.bootstrap_strategy.length)\n    elif partial_sync_experimental is not None and isinstance(\n        partial_sync_experimental.bootstrap_strategy, PartialSyncQueryBootstrap\n    ):\n        query_str = str(partial_sync_experimental.bootstrap_strategy.query)\n\n    sync_cfg = PyTursoSyncDatabaseConfig(\n        path=path,\n        remote_url=remote_url,\n        client_name=cname,\n        long_poll_timeout_ms=long_poll_timeout_ms,\n        bootstrap_if_empty=bootstrap_if_empty,\n        reserved_bytes=None,\n        partial_sync=PyTursoPartialSyncOpts(\n            bootstrap_strategy_prefix=prefix_len,\n            bootstrap_strategy_query=query_str,\n            segment_size=partial_sync_experimental.segment_size,\n            prefetch=partial_sync_experimental.prefetch,\n        )\n        if partial_sync_experimental is not None\n        else None,\n        remote_encryption_key=remote_encryption_key,\n        remote_encryption_cipher=remote_encryption_cipher,\n    )\n\n    # Create sync database holder\n    sync_db: PyTursoSyncDatabase = py_turso_sync_new(db_cfg, sync_cfg)\n\n    # Prepare + open the database with create()\n    _run_op(sync_db, sync_db.create(), http_ctx)\n\n    # Connect to obtain PyTursoConnection\n    conn_obj = _run_op(sync_db, sync_db.connect(), http_ctx)\n    if not isinstance(conn_obj, PyTursoConnection):\n        raise Misuse(\"sync connect did not return a connection\")\n\n    # Wrap into ConnectionSync that integrates sync IO into DB operations\n    return ConnectionSync(conn_obj, sync=sync_db, http_ctx=http_ctx, isolation_level=isolation_level)\n"
  },
  {
    "path": "bindings/python/turso/lib_sync_aio.py",
    "content": "from __future__ import annotations\n\nfrom typing import Callable, Optional, Union, cast\n\nfrom .lib_aio import (\n    Connection as NonBlockingConnection,\n)\nfrom .lib_sync import (\n    ConnectionSync as BlockingConnectionSync,\n)\nfrom .lib_sync import (\n    PartialSyncOpts,\n    PyTursoSyncDatabaseStats,\n)\nfrom .lib_sync import (\n    connect_sync as blocking_connect_sync,\n)\n\n\nclass ConnectionSync(NonBlockingConnection):\n    def __init__(self, connector: Callable[[], BlockingConnectionSync]) -> None:\n        # Use the non-blocking driver base - runs a background worker thread\n        # that owns the underlying blocking connection instance.\n        super().__init__(connector)\n\n    async def close(self) -> None:\n        # Ensure worker is shut down and underlying blocking connection closed\n        await super().close()\n\n    # Make ConnectionSync instance awaitable with correct return typing\n    def __await__(self):\n        async def _await_open() -> \"ConnectionSync\":\n            await self._open_future\n            return self  # the underlying connection is created at this point\n\n        return _await_open().__await__()\n\n    async def __aenter__(self) -> \"ConnectionSync\":\n        await self\n        return self\n\n    async def __aexit__(self, exc_type, exc, tb) -> None:\n        await self.close()\n\n    # Synchronization API (async wrappers scheduling work on the worker thread)\n\n    async def pull(self) -> bool:\n        # Pull remote changes and apply locally; returns True if any updates were fetched\n        return await self._run(lambda: cast(BlockingConnectionSync, self._conn).pull())  # type: ignore[union-attr]\n\n    async def push(self) -> None:\n        # Push local changes to the remote\n        await self._run(lambda: cast(BlockingConnectionSync, self._conn).push())  # type: ignore[union-attr]\n\n    async def checkpoint(self) -> None:\n        # Checkpoint the WAL of the synced database\n        await self._run(lambda: cast(BlockingConnectionSync, self._conn).checkpoint())  # type: ignore[union-attr]\n\n    async def stats(self) -> PyTursoSyncDatabaseStats:\n        # Collect stats about the synced database\n        return await self._run(lambda: cast(BlockingConnectionSync, self._conn).stats())  # type: ignore[union-attr]\n\n\n# connect is not async because it returns awaitable ConnectionSync\n# Same signature as in the lib_sync.connect_sync\ndef connect_sync(\n    path: str,\n    remote_url: Union[str, Callable[[], Optional[str]]],\n    *,\n    auth_token: Optional[Union[str, Callable[[], Optional[str]]]] = None,\n    client_name: Optional[str] = None,\n    long_poll_timeout_ms: Optional[int] = None,\n    bootstrap_if_empty: bool = True,\n    partial_sync_experimental: Optional[PartialSyncOpts] = None,\n    experimental_features: Optional[str] = None,\n    isolation_level: Optional[str] = \"DEFERRED\",\n) -> ConnectionSync:\n    # Connector creating the blocking synchronized connection in the worker thread\n    def _connector() -> BlockingConnectionSync:\n        return blocking_connect_sync(\n            path,\n            remote_url,\n            auth_token=auth_token,\n            client_name=client_name,\n            long_poll_timeout_ms=long_poll_timeout_ms,\n            bootstrap_if_empty=bootstrap_if_empty,\n            partial_sync_experimental=partial_sync_experimental,\n            experimental_features=experimental_features,\n            isolation_level=isolation_level,\n        )\n\n    # Return awaitable async wrapper with sync extras\n    return ConnectionSync(_connector)\n"
  },
  {
    "path": "bindings/python/turso/py.typed",
    "content": ""
  },
  {
    "path": "bindings/python/turso/sqlalchemy/__init__.py",
    "content": "\"\"\"SQLAlchemy dialect for pyturso.\n\nThis module provides SQLAlchemy integration for pyturso:\n- TursoDialect: Basic local database connections (sqlite+turso://)\n- TursoSyncDialect: Sync-enabled connections with remote support (sqlite+turso_sync://)\n- get_sync_connection: Helper to access sync methods from SQLAlchemy connections\n\nUsage:\n    from sqlalchemy import create_engine, text\n\n    # Basic local connection\n    engine = create_engine(\"sqlite+turso:///app.db\")\n\n    # Sync-enabled connection with remote\n    engine = create_engine(\n        \"sqlite+turso_sync:///local.db\"\n        \"?remote_url=https://my-db.turso.io\"\n        \"&auth_token=your-token\"\n    )\n\n    # Access sync operations\n    from turso.sqlalchemy import get_sync_connection\n\n    with engine.connect() as conn:\n        sync = get_sync_connection(conn)\n        sync.pull()  # Pull remote changes\n        result = conn.execute(text(\"SELECT * FROM users\"))\n        conn.commit()\n        sync.push()  # Push local changes\n\"\"\"\n\nfrom .dialect import TursoDialect, TursoSyncDialect, get_sync_connection\n\n__all__ = [\n    \"TursoDialect\",\n    \"TursoSyncDialect\",\n    \"get_sync_connection\",\n]\n"
  },
  {
    "path": "bindings/python/turso/sqlalchemy/dialect.py",
    "content": "\"\"\"SQLAlchemy dialects for pyturso.\n\nThis module provides two SQLAlchemy dialects:\n- TursoDialect: Basic local database connections (sqlite+turso://)\n- TursoSyncDialect: Sync-enabled connections with remote support (sqlite+turso_sync://)\n\"\"\"\n\nfrom __future__ import annotations\n\nimport logging\nfrom typing import TYPE_CHECKING, Any, Dict, List\n\nfrom sqlalchemy import pool\nfrom sqlalchemy.dialects.sqlite.pysqlite import SQLiteDialect_pysqlite\nfrom sqlalchemy.engine import URL\nfrom sqlalchemy.engine.reflection import ObjectKind\n\nif TYPE_CHECKING:\n    from sqlalchemy.engine.interfaces import ConnectArgsType, ReflectedForeignKeyConstraint, ReflectedIndex\n    from sqlalchemy.pool import Pool\n\nlogger = logging.getLogger(__name__)\n\n\nclass _TursoDialectMixin:\n    \"\"\"\n    Mixin providing Turso-specific overrides for SQLAlchemy dialect.\n\n    Turso doesn't support all SQLite PRAGMAs. This mixin overrides methods\n    that would otherwise fail due to unsupported PRAGMAs:\n    - PRAGMA foreign_key_list (not supported)\n    - PRAGMA index_list (not supported)\n    \"\"\"\n\n    def get_foreign_keys(\n        self,\n        connection,\n        table_name,\n        schema=None,\n        **kw,\n    ) -> List[ReflectedForeignKeyConstraint]:\n        \"\"\"\n        Return foreign keys for a table.\n\n        Turso doesn't support PRAGMA foreign_key_list, so we return an empty list.\n        Foreign key constraints are still enforced at write time if defined.\n        \"\"\"\n        logger.debug(\n            \"PRAGMA foreign_key_list not supported; foreign key reflection unavailable for table '%s'\",\n            table_name,\n        )\n        return []\n\n    def get_indexes(\n        self,\n        connection,\n        table_name,\n        schema=None,\n        **kw,\n    ) -> List[ReflectedIndex]:\n        \"\"\"\n        Return indexes for a table.\n\n        Turso doesn't support PRAGMA index_list, so we return an empty list.\n        Indexes still exist and are used for query optimization.\n        \"\"\"\n        logger.debug(\n            \"PRAGMA index_list not supported; index reflection unavailable for table '%s'\",\n            table_name,\n        )\n        return []\n\n    def get_unique_constraints(\n        self,\n        connection,\n        table_name,\n        schema=None,\n        **kw,\n    ) -> List[Dict[str, Any]]:\n        \"\"\"\n        Return unique constraints for a table.\n\n        This also relies on PRAGMA index_list which Turso doesn't support.\n        \"\"\"\n        logger.debug(\n            \"PRAGMA index_list not supported; unique constraint reflection unavailable for table '%s'\",\n            table_name,\n        )\n        return []\n\n    def get_check_constraints(\n        self,\n        connection,\n        table_name,\n        schema=None,\n        **kw,\n    ) -> List[Dict[str, Any]]:\n        \"\"\"\n        Return check constraints for a table.\n\n        SQLite stores these in sqlite_master which Turso may not fully support.\n        \"\"\"\n        logger.debug(\n            \"check constraint reflection not supported for table '%s'\",\n            table_name,\n        )\n        return []\n\n    def get_multi_indexes(\n        self,\n        connection,\n        schema=None,\n        filter_names=None,\n        kind=ObjectKind.TABLE,\n        scope=None,\n        **kw,\n    ) -> Dict[Any, List[ReflectedIndex]]:\n        \"\"\"Return indexes for multiple tables.\"\"\"\n        logger.debug(\"PRAGMA index_list not supported; multi-index reflection unavailable\")\n        return {}\n\n    def get_multi_unique_constraints(\n        self,\n        connection,\n        schema=None,\n        filter_names=None,\n        kind=ObjectKind.TABLE,\n        scope=None,\n        **kw,\n    ) -> Dict[Any, List[Dict[str, Any]]]:\n        \"\"\"Return unique constraints for multiple tables.\"\"\"\n        logger.debug(\"PRAGMA index_list not supported; multi-unique-constraint reflection unavailable\")\n        return {}\n\n    def get_multi_foreign_keys(\n        self,\n        connection,\n        schema=None,\n        filter_names=None,\n        kind=ObjectKind.TABLE,\n        scope=None,\n        **kw,\n    ) -> Dict[Any, List[ReflectedForeignKeyConstraint]]:\n        \"\"\"Return foreign keys for multiple tables.\"\"\"\n        logger.debug(\"PRAGMA foreign_key_list not supported; multi-foreign-key reflection unavailable\")\n        return {}\n\n    def get_multi_check_constraints(\n        self,\n        connection,\n        schema=None,\n        filter_names=None,\n        kind=ObjectKind.TABLE,\n        scope=None,\n        **kw,\n    ) -> Dict[Any, List[Dict[str, Any]]]:\n        \"\"\"Return check constraints for multiple tables.\"\"\"\n        logger.debug(\"multi-check-constraint reflection not supported\")\n        return {}\n\n    def get_temp_table_names(self, connection, **kw) -> List[str]:\n        \"\"\"Return temporary table names.\n\n        Turso doesn't support sqlite_temp_master, so we return an empty list.\n        \"\"\"\n        logger.debug(\"sqlite_temp_master not supported; temp table reflection unavailable\")\n        return []\n\n    def get_temp_view_names(self, connection, **kw) -> List[str]:\n        \"\"\"Return temporary view names.\n\n        Turso doesn't support sqlite_temp_master, so we return an empty list.\n        \"\"\"\n        logger.debug(\"sqlite_temp_master not supported; temp view reflection unavailable\")\n        return []\n\n\nclass TursoDialect(_TursoDialectMixin, SQLiteDialect_pysqlite):\n    \"\"\"\n    SQLAlchemy dialect for pyturso local database connections.\n\n    This dialect uses turso.connect() for local SQLite-compatible databases.\n\n    Usage:\n        from sqlalchemy import create_engine\n\n        # File-based database\n        engine = create_engine(\"sqlite+turso:///path/to/database.db\")\n\n        # In-memory database\n        engine = create_engine(\"sqlite+turso:///:memory:\")\n\n        # With options\n        engine = create_engine(\n            \"sqlite+turso:///db.db\",\n            connect_args={\"isolation_level\": \"IMMEDIATE\"}\n        )\n    \"\"\"\n\n    name = \"sqlite\"\n    driver = \"turso\"\n\n    # Enable statement caching for better performance\n    supports_statement_cache = True\n    # Disable native_datetime since turso handles datetime differently\n    supports_native_datetime = False\n\n    @classmethod\n    def import_dbapi(cls):\n        \"\"\"Import the turso module as DBAPI.\"\"\"\n        import turso\n\n        return turso\n\n    def on_connect(self):\n        \"\"\"\n        Return a callable to run on each new connection.\n\n        We override this to skip the REGEXP function setup that pysqlite does,\n        since turso doesn't support create_function.\n        \"\"\"\n        # Skip the parent's on_connect which tries to register REGEXP\n        # Return None to indicate no special connection setup needed\n        return None\n\n    def get_isolation_level(self, dbapi_connection):\n        \"\"\"\n        Return the current isolation level.\n\n        Turso doesn't support PRAGMA read_uncommitted, so we return\n        SERIALIZABLE as the default (which is what SQLite uses).\n        \"\"\"\n        return \"SERIALIZABLE\"\n\n    def set_isolation_level(self, dbapi_connection, level):\n        \"\"\"\n        Set the isolation level.\n\n        Turso handles isolation through the isolation_level connection parameter,\n        not through PRAGMA statements. This is a no-op since the isolation level\n        is set at connection time.\n        \"\"\"\n        # No-op: turso handles isolation via connection parameter\n        pass\n\n    def create_connect_args(self, url: URL) -> ConnectArgsType:\n        \"\"\"\n        Create connection arguments from SQLAlchemy URL.\n\n        The URL format is:\n            sqlite+turso:///path/to/database.db\n\n        Query parameters supported:\n            - isolation_level: Transaction isolation level\n            - experimental_features: Comma-separated feature flags\n        \"\"\"\n        opts = url.translate_connect_args()\n\n        # 'database' key becomes the positional argument\n        database = opts.pop(\"database\", \":memory:\")\n\n        # Remove unsupported URL components\n        opts.pop(\"username\", None)\n        opts.pop(\"password\", None)\n        opts.pop(\"host\", None)\n        opts.pop(\"port\", None)\n\n        # Extract query parameters\n        query_params = dict(url.query)\n\n        kwargs: Dict[str, Any] = {}\n\n        # Handle isolation_level\n        isolation_level = query_params.pop(\"isolation_level\", None)\n        if isolation_level:\n            if isolation_level.upper() == \"AUTOCOMMIT\":\n                kwargs[\"isolation_level\"] = None\n            else:\n                kwargs[\"isolation_level\"] = isolation_level\n\n        # Handle experimental_features\n        experimental_features = query_params.pop(\"experimental_features\", None)\n        if experimental_features:\n            kwargs[\"experimental_features\"] = experimental_features\n\n        return ([database], kwargs)\n\n    def get_pool_class(self, url: URL) -> type[Pool]:\n        \"\"\"Return the connection pool class.\"\"\"\n        if url.database == \":memory:\":\n            return pool.SingletonThreadPool\n        return pool.QueuePool\n\n\nclass TursoSyncDialect(_TursoDialectMixin, SQLiteDialect_pysqlite):\n    \"\"\"\n    SQLAlchemy dialect for pyturso sync-enabled connections.\n\n    This dialect uses turso.sync.connect() which provides:\n    - Local SQLite database with remote sync capabilities\n    - pull() - Pull changes from remote\n    - push() - Push changes to remote\n    - checkpoint() - Checkpoint the WAL\n    - stats() - Get sync statistics\n\n    Usage:\n        from sqlalchemy import create_engine\n\n        engine = create_engine(\n            \"sqlite+turso_sync:///local.db\"\n            \"?remote_url=https://your-db.turso.io\"\n            \"&auth_token=your-token\"\n        )\n\n        # Or with connect_args:\n        engine = create_engine(\n            \"sqlite+turso_sync:///local.db\",\n            connect_args={\n                \"remote_url\": \"https://your-db.turso.io\",\n                \"auth_token\": \"your-token\",\n            }\n        )\n\n        # Access sync operations:\n        from turso.sqlalchemy import get_sync_connection\n\n        with engine.connect() as conn:\n            sync = get_sync_connection(conn)\n            sync.pull()  # Pull remote changes\n            # ... execute queries ...\n            sync.push()  # Push local changes\n    \"\"\"\n\n    name = \"sqlite\"\n    driver = \"turso_sync\"\n\n    # Enable statement caching for better performance\n    supports_statement_cache = True\n    # Disable native_datetime since turso handles datetime differently\n    supports_native_datetime = False\n\n    @classmethod\n    def import_dbapi(cls):\n        \"\"\"Import the turso.sync module as DBAPI.\"\"\"\n        import turso.sync\n\n        return turso.sync\n\n    def connect(self, *cargs, **cparams):\n        \"\"\"Remap sync_url to remote_url for libsql-sqlalchemy compatibility.\"\"\"\n        if \"sync_url\" in cparams and \"remote_url\" not in cparams:\n            cparams[\"remote_url\"] = cparams.pop(\"sync_url\")\n        return super().connect(*cargs, **cparams)\n\n    def on_connect(self):\n        \"\"\"\n        Return a callable to run on each new connection.\n\n        We override this to skip the REGEXP function setup that pysqlite does,\n        since turso doesn't support create_function.\n        \"\"\"\n        return None\n\n    def get_isolation_level(self, dbapi_connection):\n        \"\"\"\n        Return the current isolation level.\n\n        Turso doesn't support PRAGMA read_uncommitted, so we return\n        SERIALIZABLE as the default.\n        \"\"\"\n        return \"SERIALIZABLE\"\n\n    def set_isolation_level(self, dbapi_connection, level):\n        \"\"\"\n        Set the isolation level.\n\n        Turso handles isolation through the isolation_level connection parameter.\n        This is a no-op since the isolation level is set at connection time.\n        \"\"\"\n        pass\n\n    @staticmethod\n    def _validate_sync_url(opts: Dict[str, Any]) -> None:\n        \"\"\"Reject URL components that TursoSyncDialect doesn't support.\"\"\"\n        if opts.get(\"username\") or opts.get(\"password\"):\n            raise ValueError(\n                \"TursoSyncDialect does not support username/password in URL. \"\n                \"Use auth_token query parameter or connect_args instead.\"\n            )\n        if opts.get(\"host\") or opts.get(\"port\"):\n            raise ValueError(\n                \"TursoSyncDialect does not support host/port in URL. \"\n                \"The local database path goes after ':///', and remote_url \"\n                \"is specified as a query parameter.\"\n            )\n\n    @staticmethod\n    def _extract_sync_params(query_params: Dict[str, str]) -> Dict[str, Any]:\n        \"\"\"Extract and convert sync-specific query parameters into kwargs.\"\"\"\n        kwargs: Dict[str, Any] = {}\n\n        auth_token = query_params.pop(\"auth_token\", None)\n        if auth_token:\n            kwargs[\"auth_token\"] = auth_token\n\n        client_name = query_params.pop(\"client_name\", None)\n        kwargs[\"client_name\"] = client_name or \"turso-sqlalchemy\"\n\n        long_poll_timeout_ms = query_params.pop(\"long_poll_timeout_ms\", None)\n        if long_poll_timeout_ms:\n            kwargs[\"long_poll_timeout_ms\"] = int(long_poll_timeout_ms)\n\n        bootstrap_if_empty = query_params.pop(\"bootstrap_if_empty\", None)\n        if bootstrap_if_empty is not None:\n            kwargs[\"bootstrap_if_empty\"] = bootstrap_if_empty.lower() in (\n                \"true\",\n                \"1\",\n                \"yes\",\n            )\n\n        return kwargs\n\n    def create_connect_args(self, url: URL) -> ConnectArgsType:\n        \"\"\"\n        Create connection arguments from SQLAlchemy URL.\n\n        The URL format is:\n            sqlite+turso_sync:///path/to/local.db?remote_url=...&auth_token=...\n\n        Query parameters:\n            - remote_url (required): Remote Turso/libsql server URL\n            - auth_token: Authentication token\n            - client_name: Client identifier (default: turso-sqlalchemy)\n            - long_poll_timeout_ms: Long poll timeout in milliseconds\n            - bootstrap_if_empty: Bootstrap from remote if local empty (default: true)\n            - isolation_level: Transaction isolation level\n            - experimental_features: Comma-separated feature flags\n        \"\"\"\n        opts = url.translate_connect_args()\n        path = opts.pop(\"database\", \":memory:\")\n        self._validate_sync_url(opts)\n\n        query_params = dict(url.query)\n        # Accept both remote_url and sync_url (libsql-sqlalchemy compat)\n        remote_url = query_params.pop(\"remote_url\", None) or query_params.pop(\"sync_url\", None)\n        kwargs = self._extract_sync_params(query_params)\n\n        # Handle isolation_level\n        isolation_level = query_params.pop(\"isolation_level\", None)\n        if isolation_level:\n            if isolation_level.upper() == \"AUTOCOMMIT\":\n                kwargs[\"isolation_level\"] = None\n            else:\n                kwargs[\"isolation_level\"] = isolation_level\n\n        # Handle experimental_features\n        experimental_features = query_params.pop(\"experimental_features\", None)\n        if experimental_features:\n            kwargs[\"experimental_features\"] = experimental_features\n\n        # Warn about unused query parameters\n        if query_params:\n            import warnings\n\n            warnings.warn(\n                f\"Unrecognized query parameters ignored: {list(query_params.keys())}\",\n                UserWarning,\n                stacklevel=2,\n            )\n\n        # Return (args, kwargs) for turso.sync.connect(path, remote_url, **kwargs)\n        if remote_url:\n            return ([path, remote_url], kwargs)\n        else:\n            # If no remote_url provided, let turso.sync.connect raise the error\n            # This allows connect_args to provide remote_url instead\n            return ([path], kwargs)\n\n    def get_pool_class(self, url: URL) -> type[Pool]:\n        \"\"\"\n        Return the connection pool class.\n\n        For sync connections with file databases, use QueuePool.\n        For :memory: databases, use SingletonThreadPool.\n        \"\"\"\n        if url.database == \":memory:\":\n            return pool.SingletonThreadPool\n        return pool.QueuePool\n\n\ndef get_sync_connection(connection):\n    \"\"\"\n    Get the underlying turso.sync.ConnectionSync from a SQLAlchemy connection.\n\n    This provides access to sync-specific methods:\n    - pull() - Pull changes from remote, returns True if updates were pulled\n    - push() - Push changes to remote\n    - checkpoint() - Checkpoint the WAL\n    - stats() - Get sync statistics\n\n    Usage:\n        from turso.sqlalchemy import get_sync_connection\n\n        with engine.connect() as conn:\n            sync = get_sync_connection(conn)\n\n            # Pull latest changes before querying\n            sync.pull()\n\n            result = conn.execute(text(\"SELECT * FROM users\"))\n\n            # After modifications, push to remote\n            conn.execute(text(\"INSERT INTO users ...\"))\n            conn.commit()\n            sync.push()\n\n    Args:\n        connection: A SQLAlchemy Connection object\n\n    Returns:\n        The underlying turso.sync.ConnectionSync object\n\n    Raises:\n        TypeError: If the connection is not a Turso sync connection\n    \"\"\"\n    from turso.lib_sync import ConnectionSync\n\n    # Get the raw DBAPI connection\n    # SQLAlchemy 2.0: connection.connection.dbapi_connection\n    # SQLAlchemy 1.4: connection.connection\n    raw_conn = getattr(connection, \"connection\", None)\n    if raw_conn is None:\n        raise TypeError(\"Cannot get raw connection from SQLAlchemy connection\")\n\n    # Handle SQLAlchemy 2.0 pooled connection wrapper\n    dbapi_conn = getattr(raw_conn, \"dbapi_connection\", raw_conn)\n\n    if not isinstance(dbapi_conn, ConnectionSync):\n        raise TypeError(\n            f\"Expected turso.sync.ConnectionSync, got {type(dbapi_conn).__name__}. \"\n            \"This function only works with sqlite+turso_sync:// connections.\"\n        )\n\n    return dbapi_conn\n"
  },
  {
    "path": "bindings/python/turso/sync/__init__.py",
    "content": "from ..lib import (\n    # Exception classes\n    DatabaseError,\n    DataError,\n    Error,\n    IntegrityError,\n    InterfaceError,\n    InternalError,\n    NotSupportedError,\n    OperationalError,\n    ProgrammingError,\n    Warning,\n    # DB-API 2.0 module-level attributes required by SQLAlchemy\n    apilevel,\n    paramstyle,\n    sqlite_version,\n    sqlite_version_info,\n    threadsafety,\n)\nfrom ..lib_sync import (\n    ConnectionSync,\n    PartialSyncOpts,\n    PartialSyncPrefixBootstrap,\n    PartialSyncQueryBootstrap,\n    RemoteEncryptionCipher,\n)\nfrom ..lib_sync import (\n    connect_sync as connect,\n)\n\n__all__ = [\n    \"connect\",\n    \"ConnectionSync\",\n    \"PartialSyncOpts\",\n    \"PartialSyncPrefixBootstrap\",\n    \"PartialSyncQueryBootstrap\",\n    \"RemoteEncryptionCipher\",\n    # DB-API 2.0 module attributes\n    \"apilevel\",\n    \"paramstyle\",\n    \"threadsafety\",\n    \"sqlite_version\",\n    \"sqlite_version_info\",\n    # Exception classes\n    \"Warning\",\n    \"Error\",\n    \"InterfaceError\",\n    \"DatabaseError\",\n    \"DataError\",\n    \"OperationalError\",\n    \"IntegrityError\",\n    \"InternalError\",\n    \"ProgrammingError\",\n    \"NotSupportedError\",\n]\n"
  },
  {
    "path": "bindings/python/turso/worker.py",
    "content": "import asyncio\nfrom queue import SimpleQueue\nfrom threading import Thread\nfrom typing import Any, Callable\n\nSTOP_RUNNING_SENTINEL = object()\n\n\nclass Worker(Thread):\n    \"\"\"\n    Dedicated worker thread executing database operations sequentially.\n\n    The worker consumes (future, callable) items from the unbounded SimpleQueue.\n    It executes the callable, then sets result or mapped exception on the future\n    using loop.call_soon_threadsafe to synchronize with the event loop thread.\n\n    If work item return STOP_RUNNING_SENTINEL value - it stops the execution\n    (e.g. this can be used to stop worker when connection is about to close)\n    \"\"\"\n\n    def __init__(\n        self,\n        queue: SimpleQueue[tuple[asyncio.Future, Callable[[], Any]] | None],\n        loop: asyncio.AbstractEventLoop,\n    ) -> None:\n        super().__init__(name=\"turso-async-worker\", daemon=True)\n        self._queue = queue\n        self._loop = loop\n\n    def run(self) -> None:\n        while True:\n            item = self._queue.get()\n            fut, func = item\n            if fut.cancelled():\n                # Still consume but skip execution if already cancelled\n                continue\n            try:\n                result = func()\n                if result is STOP_RUNNING_SENTINEL:\n                    break\n            except Exception as e:\n                self._loop.call_soon_threadsafe(fut.set_exception, e)\n            else:\n                self._loop.call_soon_threadsafe(fut.set_result, result)\n"
  },
  {
    "path": "bindings/react-native/.gitignore",
    "content": "# Node\nnode_modules/\nlib/\n\n# Built Rust libraries\nlibs/\n\n# iOS\nios/Pods/\n*.xcworkspace\n*.xcuserdata\n\n# Android\nandroid/build/\nandroid/.gradle/\nandroid/.cxx/\n*.apk\n*.aab\n*.dex\n*.class\n\n# IDE\n.idea/\n.vscode/\n*.swp\n*.swo\n\n# OS\n.DS_Store\nThumbs.db\n\n# Logs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n!CMakeLists.txt\n"
  },
  {
    "path": "bindings/react-native/Makefile",
    "content": "OS := $(shell uname)\nCFLAGS := -Iinclude\nLDFLAGS := -lm\nARCHS_IOS = aarch64-apple-ios aarch64-apple-ios-sim\nARCHS_ANDROID = aarch64-linux-android armv7-linux-androideabi x86_64-linux-android i686-linux-android\nIOS_LIB_FILE = libturso_sync_sdk_kit.dylib\nIOS_FINAL_LIB_FILE = turso-sync-sdk-kit\nLIB_FILE = libturso_sync_sdk_kit.so\nHEADERS = turso_sync.h turso.h\nSDK_KIT_DIR = ../../sdk-kit\nSYNC_SDK_KIT_DIR = ../../sync/sdk-kit\n\n\nifeq ($(OS),Darwin)\n\tCFLAGS += -framework Security -framework CoreServices\nendif\n\n.PHONY: all $(ARCHS_IOS) ios $(ARCHS_ANDROID) android\n\nnotify:\n\tosascript -e 'display notification \"Build completed\" with title \"turso\"'\n\techo \"🟢 Build Completed!\"\n\nlint:\n\tcargo clippy --all-features --all-targets -- --no-deps -D warnings\n\nfix:\n\tcargo clippy --all-targets --all-features --fix --allow-dirty --allow-staged\n\tcargo fmt --all\n\nnuke:\n\trm -rf ./target\n\nios: clean ios-build package-dylibs notify\n\nclean:\n\trm -rf libs/ios\n\npackage-dylibs:\n\tmkdir -p libs/ios\n\trm -rf libs/ios/turso*.xcframework\n\tcp -r templates/turso*.xcframework libs/ios\n\tcp \"$(SDK_KIT_DIR)/turso.h\" libs/ios/\n\tcp \"$(SYNC_SDK_KIT_DIR)/turso_sync.h\" libs/ios/\n\n# Pack device dylibs\n\tcp ../../target/aarch64-apple-ios/release/$(IOS_LIB_FILE) libs/ios/$(IOS_FINAL_LIB_FILE)\n\n\tcp libs/ios/$(IOS_FINAL_LIB_FILE) libs/ios/turso-sync-sdk-kit.xcframework/ios-arm64/turso-sync-sdk-kit.framework/\n\tinstall_name_tool -id @rpath/turso-sync-sdk-kit.framework/$(IOS_FINAL_LIB_FILE) libs/ios/turso-sync-sdk-kit.xcframework/ios-arm64/turso-sync-sdk-kit.framework/$(IOS_FINAL_LIB_FILE)\n\tcodesign -f -s - --identifier com.turso.turso-sync-sdk-kit libs/ios/turso-sync-sdk-kit.xcframework/ios-arm64/turso-sync-sdk-kit.framework/$(IOS_FINAL_LIB_FILE)\n\n\tmkdir -p libs/ios/turso-sync-sdk-kit.xcframework/ios-arm64/turso-sync-sdk-kit.framework/Headers\n\tcp \"$(SDK_KIT_DIR)/turso.h\" libs/ios/turso-sync-sdk-kit.xcframework/ios-arm64/turso-sync-sdk-kit.framework/Headers/\n\tcp \"$(SYNC_SDK_KIT_DIR)/turso_sync.h\" libs/ios/turso-sync-sdk-kit.xcframework/ios-arm64/turso-sync-sdk-kit.framework/Headers/\n\n# Pack simulator dylibs\n\tcp ../../target/aarch64-apple-ios-sim/release/$(IOS_LIB_FILE) libs/ios/$(IOS_FINAL_LIB_FILE)\n\n\tcp libs/ios/$(IOS_FINAL_LIB_FILE) libs/ios/turso-sync-sdk-kit.xcframework/ios-arm64-simulator/turso-sync-sdk-kit.framework/\n\tinstall_name_tool -id @rpath/turso-sync-sdk-kit.framework/$(IOS_FINAL_LIB_FILE) libs/ios/turso-sync-sdk-kit.xcframework/ios-arm64-simulator/turso-sync-sdk-kit.framework/$(IOS_FINAL_LIB_FILE)\n\tcodesign -f -s - --identifier com.turso.turso-sync-sdk-kit libs/ios/turso-sync-sdk-kit.xcframework/ios-arm64-simulator/turso-sync-sdk-kit.framework/$(IOS_FINAL_LIB_FILE)\n\n\tmkdir -p libs/ios/turso-sync-sdk-kit.xcframework/ios-arm64-simulator/turso-sync-sdk-kit.framework/Headers\n\tcp \"$(SDK_KIT_DIR)/turso.h\" libs/ios/turso-sync-sdk-kit.xcframework/ios-arm64-simulator/turso-sync-sdk-kit.framework/Headers/\n\tcp \"$(SYNC_SDK_KIT_DIR)/turso_sync.h\" libs/ios/turso-sync-sdk-kit.xcframework/ios-arm64-simulator/turso-sync-sdk-kit.framework/Headers/\n\nios-build:\n\trustup target add aarch64-apple-ios\n\tcargo build --release --target aarch64-apple-ios --package turso_sync_sdk_kit\n\trustup target add aarch64-apple-ios-sim\n\tcargo build --release --target aarch64-apple-ios-sim --package turso_sync_sdk_kit\n\nandroid: $(ARCHS_ANDROID)\n\trm -rf libs/android\n\tmkdir -p libs/android/arm64-v8a\n\tmkdir -p libs/android/armeabi-v7a\n\tmkdir -p libs/android/x86_64\n\tmkdir -p libs/android/x86\n\n\tcp ../../target/aarch64-linux-android/release/$(LIB_FILE) libs/android/arm64-v8a/$(LIB_FILE)\n\tcp ../../target/armv7-linux-androideabi/release/$(LIB_FILE) libs/android/armeabi-v7a/$(LIB_FILE)\n\tcp ../../target/x86_64-linux-android/release/$(LIB_FILE) libs/android/x86_64/$(LIB_FILE)\n\tcp ../../target/i686-linux-android/release/$(LIB_FILE) libs/android/x86/$(LIB_FILE)\n\tcp \"$(SDK_KIT_DIR)/turso.h\" libs/android\n\tcp \"$(SYNC_SDK_KIT_DIR)/turso_sync.h\" libs/android\n\n$(ARCHS_ANDROID): %:\n\trustup target add $@\n\tcargo ndk --target $@ --platform 31 build --package turso_sync_sdk_kit --release\n"
  },
  {
    "path": "bindings/react-native/README.md",
    "content": "# Turso Sync React Native SDK\n\nReact Native bindings for Turso embedded replicas - sync your local SQLite database with Turso cloud.\n\n## Installation\n\n```bash\nnpm install @tursodatabase/sync-react-native\n```\n\n### iOS\n\n```bash\ncd ios && pod install\n```\n\n### Android\n\nRequires `minSdkVersion` 21+ in `android/build.gradle`.\n\n## Quick Start\n\n```typescript\nimport { Database, getDbPath } from '@tursodatabase/sync-react-native';\n\n// Get platform-specific writable path\nconst dbPath = getDbPath('myapp.db');\n\n// Create database with sync\nconst db = new Database({\n  path: dbPath,\n  url: 'libsql://your-db.turso.io',\n  authToken: 'your-auth-token',\n});\n\n// Connect (bootstraps from remote if empty)\nawait db.connect();\n\n// Query local replica (fast)\nconst users = await db.all('SELECT * FROM users');\n\n// Make local changes\nawait db.run('INSERT INTO users (name) VALUES (?)', ['Alice']);\n\n// Sync with remote\nawait db.push();  // Push local changes\nawait db.pull();  // Pull remote changes\n\n// Close when done\nawait db.close();\n```\n\n## Local-Only Database\n\n```typescript\nconst db = new Database({ path: getDbPath('local.db') });\nawait db.connect();\n\nawait db.exec('CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)');\nawait db.run('INSERT INTO users (name) VALUES (?)', ['Bob']);\nconst user = await db.get('SELECT * FROM users WHERE id = ?', [1]);\n\nawait db.close();\n```\n\n## Encrypted Remote Database\n\n```typescript\nconst db = new Database({\n  path: getDbPath('encrypted.db'),\n  url: 'libsql://your-db.turso.io',\n  authToken: 'your-auth-token',\n  remoteEncryption: {\n    cipher: 'aes256gcm',\n    key: 'base64-encoded-key',\n  },\n});\n```\n\n## API\n\n### Database Methods\n\n| Method | Description |\n|--------|-------------|\n| `connect()` | Open/bootstrap the database |\n| `exec(sql)` | Execute SQL (no results) |\n| `run(sql, params?)` | Execute SQL, return `{ changes, lastInsertRowid }` |\n| `get(sql, params?)` | Query single row |\n| `all(sql, params?)` | Query all rows |\n| `prepare(sql)` | Create prepared statement |\n| `close()` | Close database |\n\n### Sync Methods (when `url` is provided)\n\n| Method | Description |\n|--------|-------------|\n| `push()` | Push local changes to remote |\n| `pull()` | Pull remote changes to local |\n| `sync()` | Push then pull |\n| `stats()` | Get sync statistics |\n\n### Transactions\n\n```typescript\nawait db.transaction(async () => {\n  await db.run('INSERT INTO users (name) VALUES (?)', ['Alice']);\n  await db.run('INSERT INTO users (name) VALUES (?)', ['Bob']);\n  // Commits on success, rolls back on error\n});\n```\n## License\n\nThis project is licensed under the [MIT license](https://github.com/tursodatabase/turso/blob/main/LICENSE.md).\n\n## Links\n\n- [Turso Documentation](https://docs.turso.tech)\n- [GitHub](https://github.com/tursodatabase/turso)\n- [npm](https://www.npmjs.com/package/@tursodatabase/sync-react-native)\n"
  },
  {
    "path": "bindings/react-native/android/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.22)\nproject(turso-sync-react-native)\n\nset(CMAKE_CXX_STANDARD 20)\nset(CMAKE_CXX_STANDARD_REQUIRED ON)\n\n# Find packages\nfind_package(ReactAndroid REQUIRED CONFIG)\nfind_package(fbjni REQUIRED CONFIG)\n\n# Source files\nset(TURSO_SOURCES\n    cpp-adapter.cpp\n    ../cpp/TursoHostObject.cpp\n    ../cpp/TursoDatabaseHostObject.cpp\n    ../cpp/TursoConnectionHostObject.cpp\n    ../cpp/TursoStatementHostObject.cpp\n    ../cpp/TursoSyncDatabaseHostObject.cpp\n    ../cpp/TursoSyncOperationHostObject.cpp\n    ../cpp/TursoSyncIoItemHostObject.cpp\n    ../cpp/TursoSyncChangesHostObject.cpp\n)\n\n# Create shared library\nadd_library(${PROJECT_NAME} SHARED ${TURSO_SOURCES})\n\n# 16KB page alignment for Android 15+ compatibility\ntarget_link_options(${PROJECT_NAME} PRIVATE \"-Wl,-z,max-page-size=16384\")\n\n# Include directories\ntarget_include_directories(${PROJECT_NAME} PRIVATE\n    ../cpp\n    ../libs/android\n)\n\n# Pre-built Rust shared library (.so) loaded at runtime\nadd_library(turso_sync_sdk_kit SHARED IMPORTED)\n\n# IMPORTED_NO_SONAME is important to properly adjust import paths for runtime by linker (so they will not be reused from build time)\nset_target_properties(turso_sync_sdk_kit PROPERTIES\n    IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/../libs/android/${ANDROID_ABI}/libturso_sync_sdk_kit.so\n    IMPORTED_NO_SONAME TRUE\n)\n\n# Link libraries (record runtime dependency on Rust .so)\ntarget_link_libraries(${PROJECT_NAME}\n    ReactAndroid::jsi\n    ReactAndroid::reactnative\n    fbjni::fbjni\n    turso_sync_sdk_kit\n    android\n    log\n)\n"
  },
  {
    "path": "bindings/react-native/android/build.gradle",
    "content": "buildscript {\n    repositories {\n        google()\n        mavenCentral()\n    }\n    dependencies {\n        classpath \"com.android.tools.build:gradle:8.2.0\"\n    }\n}\n\nplugins {\n    id \"com.android.library\"\n}\n\ndef reactNativeArchitectures() {\n    def value = project.getProperties().get(\"reactNativeArchitectures\")\n    return value ? value.split(\",\") : [\"armeabi-v7a\", \"arm64-v8a\", \"x86\", \"x86_64\"]\n}\n\nandroid {\n    namespace \"com.turso.sync.reactnative\"\n    compileSdk 34\n\n    defaultConfig {\n        minSdk 24\n        targetSdk 34\n\n        externalNativeBuild {\n            cmake {\n                cppFlags \"-O2 -frtti -fexceptions -Wall -fstack-protector-all\"\n                abiFilters(*reactNativeArchitectures())\n                arguments \"-DANDROID_STL=c++_shared\"\n            }\n        }\n    }\n\n    buildFeatures {\n        prefab true\n        buildConfig true\n    }\n\n    externalNativeBuild {\n        cmake {\n            path \"CMakeLists.txt\"\n        }\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled false\n        }\n    }\n\n    packaging {\n        excludes += [\n            \"**/libjsi.so\",\n            \"**/libfbjni.so\",\n            \"**/libc++_shared.so\",\n            \"**/libreactnative.so\"\n        ]\n    }\n    sourceSets {\n        main {\n            jniLibs.srcDirs = [\"../libs/android\"]\n        }\n    }\n}\n\nrepositories {\n    google()\n    mavenCentral()\n}\n\ndependencies {\n    compileOnly \"com.facebook.react:react-android\"\n}\n\n// Task to build Rust library for Android\ntask buildRustLibrary(type: Exec) {\n    workingDir \"${projectDir}/..\"\n    commandLine \"make\", \"android\"\n}\n\npreBuild.dependsOn buildRustLibrary\n"
  },
  {
    "path": "bindings/react-native/android/cpp-adapter.cpp",
    "content": "#include \"TursoHostObject.h\"\n#include <ReactCommon/CallInvokerHolder.h>\n#include <fbjni/fbjni.h>\n#include <jni.h>\n#include <jsi/jsi.h>\n#include <typeinfo>\n\nnamespace jsi = facebook::jsi;\nnamespace react = facebook::react;\nnamespace jni = facebook::jni;\n\n// This file is not using raw jni but rather fbjni, do not change how the native\n// functions are registered\n// https://github.com/facebookincubator/fbjni/blob/main/docs/quickref.md\nstruct TursoBridge : jni::JavaClass<TursoBridge>\n{\n    static constexpr auto kJavaDescriptor = \"Lcom/turso/sync/reactnative/TursoBridge;\";\n\n    static void registerNatives()\n    {\n        javaClassStatic()->registerNatives(\n            {makeNativeMethod(\"installNativeJsi\", TursoBridge::installNativeJsi),\n             makeNativeMethod(\"clearStateNativeJsi\", TursoBridge::clearStateNativeJsi)});\n    }\n\nprivate:\n    static void installNativeJsi(\n        jni::alias_ref<jni::JObject> thiz, jlong jsiRuntimePtr,\n        jni::alias_ref<react::CallInvokerHolder::javaobject> jsCallInvokerHolder,\n        jni::alias_ref<jni::JString> dbPath)\n    {\n        auto jsiRuntime = reinterpret_cast<jsi::Runtime *>(jsiRuntimePtr);\n        auto jsCallInvoker = jsCallInvokerHolder->cthis()->getCallInvoker();\n        std::string dbPathStr = dbPath->toStdString();\n\n        turso::install(*jsiRuntime, jsCallInvoker, dbPathStr.c_str());\n    }\n\n    static void clearStateNativeJsi(jni::alias_ref<jni::JObject> thiz)\n    {\n        turso::invalidate();\n    }\n};\n\nJNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *)\n{\n    return jni::initialize(vm, []\n                           { TursoBridge::registerNatives(); });\n}\n"
  },
  {
    "path": "bindings/react-native/android/src/main/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n</manifest>\n"
  },
  {
    "path": "bindings/react-native/android/src/main/java/com/turso/sync/reactnative/TursoBridge.java",
    "content": "package com.turso.sync.reactnative;\n\nimport com.facebook.react.bridge.ReactContext;\nimport com.facebook.react.turbomodule.core.CallInvokerHolderImpl;\n\nimport java.lang.Exception;\n\n// bridge layer, installNativeJsi/clearStateNativeJsi methods are set through cpp-adapter.cpp\npublic class TursoBridge {\n  private static final TursoBridge instance = new TursoBridge();\n\n  public static TursoBridge getInstance() {\n    return instance;\n  }\n\n  private TursoBridge() {\n  }\n\n  private native void installNativeJsi(\n      long jsContextNativePointer,\n      CallInvokerHolderImpl jsCallInvokerHolder,\n      String dbPath);\n\n  private native void clearStateNativeJsi();\n\n  public void install(ReactContext context) throws Exception {\n    long jsContextPointer = context.getJavaScriptContextHolder().get();\n    if (jsContextPointer == 0) {\n      throw new Exception(\"jsContextPointer == 0\");\n    }\n    CallInvokerHolderImpl jsCallInvokerHolder = (CallInvokerHolderImpl) context.getCatalystInstance()\n        .getJSCallInvokerHolder();\n\n    // getDatabasePath(...) returns file path - so we pass dummy value and remove it\n    // after to get directory path\n    String dbPath = context.getDatabasePath(\"tursoDatabaseFile\").getAbsolutePath().replace(\"tursoDatabaseFile\", \"\");\n\n    installNativeJsi(jsContextPointer, jsCallInvokerHolder, dbPath);\n  }\n\n  public void invalidate() {\n    clearStateNativeJsi();\n  }\n}"
  },
  {
    "path": "bindings/react-native/android/src/main/java/com/turso/sync/reactnative/TursoModule.java",
    "content": "package com.turso.sync.reactnative;\n\nimport java.io.File;\nimport java.lang.Exception;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport androidx.annotation.NonNull;\n\nimport com.facebook.react.bridge.ReactApplicationContext;\nimport com.facebook.react.bridge.ReactContextBaseJavaModule;\nimport com.facebook.react.bridge.ReactMethod;\nimport com.facebook.react.module.annotations.ReactModule;\nimport com.facebook.react.turbomodule.core.CallInvokerHolderImpl;\n\n// The React Native bridge - exposes methods to JavaScript\n@ReactModule(name = TursoModule.NAME)\npublic class TursoModule extends ReactContextBaseJavaModule {\n    public static final String NAME = \"Turso\";\n\n    static {\n        System.loadLibrary(\"turso_sync_sdk_kit\");\n        System.loadLibrary(\"turso-sync-react-native\");\n    }\n\n    public TursoModule(ReactApplicationContext reactContext) {\n        super(reactContext);\n    }\n\n    @Override\n    @NonNull\n    public String getName() {\n        return NAME;\n    }\n\n    @Override\n    public Map<String, Object> getConstants() {\n        final Map<String, Object> constants = new HashMap<>();\n        ReactApplicationContext context = getReactApplicationContext();\n\n        // getDatabasePath(...) returns file path - so we pass dummy value and remove it\n        // after to get directory path\n        String dbPath = context.getDatabasePath(\"tursoDatabaseFile\").getAbsolutePath().replace(\"tursoDatabaseFile\", \"\");\n        constants.put(\"ANDROID_DATABASE_PATH\", dbPath);\n\n        String filesPath = context.getFilesDir().getAbsolutePath();\n        constants.put(\"ANDROID_FILES_PATH\", filesPath);\n\n        File externalFilesDir = context.getExternalFilesDir(null);\n        constants.put(\"ANDROID_EXTERNAL_FILES_PATH\",\n                externalFilesDir != null ? externalFilesDir.getAbsolutePath() : null);\n\n        // populate Android and IOS constants to simplify JS code (e.g.\n        // IOS_DOCUMENT_PATH ?? ANDROID_DATABASE_PATH)\n        constants.put(\"IOS_DOCUMENT_PATH\", null);\n        constants.put(\"IOS_LIBRARY_PATH\", null);\n\n        return constants;\n    }\n\n    @ReactMethod(isBlockingSynchronousMethod = true)\n    public boolean install() {\n        try {\n            ReactApplicationContext context = getReactApplicationContext();\n            // Install native module\n            TursoBridge.getInstance().install(context);\n            return true;\n        } catch (Exception e) {\n            return false;\n        }\n    }\n\n    @Override\n    public void invalidate() {\n        super.invalidate();\n        TursoBridge.getInstance().invalidate();\n    }\n\n    private native void installNativeJsi(long jsiRuntimePtr, Object callInvokerHolder, String dbPath);\n\n    private native void clearStateNativeJsi();\n}\n"
  },
  {
    "path": "bindings/react-native/android/src/main/java/com/turso/sync/reactnative/TursoPackage.java",
    "content": "package com.turso.sync.reactnative;\n\nimport androidx.annotation.NonNull;\n\nimport com.facebook.react.ReactPackage;\nimport com.facebook.react.bridge.NativeModule;\nimport com.facebook.react.bridge.ReactApplicationContext;\nimport com.facebook.react.uimanager.ViewManager;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n// Entry point for React Native's module registration system\npublic class TursoPackage implements ReactPackage {\n    @NonNull\n    @Override\n    public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {\n        List<NativeModule> modules = new ArrayList<>();\n        modules.add(new TursoModule(reactContext));\n        return modules;\n    }\n\n    @NonNull\n    @Override\n    public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {\n        return Collections.emptyList();\n    }\n}\n"
  },
  {
    "path": "bindings/react-native/cpp/TursoConnectionHostObject.cpp",
    "content": "#include \"TursoConnectionHostObject.h\"\n#include \"TursoStatementHostObject.h\"\n\nextern \"C\" {\n#include <turso.h>\n}\n\nnamespace turso {\n\nTursoConnectionHostObject::~TursoConnectionHostObject() {\n    if (conn_) {\n        turso_connection_deinit(conn_);\n        conn_ = nullptr;\n    }\n}\n\nvoid TursoConnectionHostObject::throwError(jsi::Runtime &rt, const char *error) {\n    throw jsi::JSError(rt, error ? error : \"Unknown error\");\n}\n\njsi::Value TursoConnectionHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) {\n    auto propName = name.utf8(rt);\n\n    if (propName == \"prepareSingle\") {\n        return jsi::Function::createFromHostFunction(\n            rt, name, 1,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value {\n                return this->prepareSingle(rt, args, count);\n            }\n        );\n    }\n\n    if (propName == \"prepareFirst\") {\n        return jsi::Function::createFromHostFunction(\n            rt, name, 1,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value {\n                return this->prepareFirst(rt, args, count);\n            }\n        );\n    }\n\n    if (propName == \"lastInsertRowid\") {\n        return jsi::Function::createFromHostFunction(\n            rt, name, 0,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value {\n                return this->lastInsertRowid(rt);\n            }\n        );\n    }\n\n    if (propName == \"getAutocommit\") {\n        return jsi::Function::createFromHostFunction(\n            rt, name, 0,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value {\n                return this->getAutocommit(rt);\n            }\n        );\n    }\n\n    if (propName == \"setBusyTimeout\") {\n        return jsi::Function::createFromHostFunction(\n            rt, name, 1,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value {\n                return this->setBusyTimeout(rt, args, count);\n            }\n        );\n    }\n\n    if (propName == \"close\") {\n        return jsi::Function::createFromHostFunction(\n            rt, name, 0,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value {\n                return this->close(rt);\n            }\n        );\n    }\n\n    return jsi::Value::undefined();\n}\n\nvoid TursoConnectionHostObject::set(jsi::Runtime &rt, const jsi::PropNameID &name, const jsi::Value &value) {\n    // Read-only object\n}\n\nstd::vector<jsi::PropNameID> TursoConnectionHostObject::getPropertyNames(jsi::Runtime &rt) {\n    std::vector<jsi::PropNameID> props;\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"prepareSingle\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"prepareFirst\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"lastInsertRowid\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"getAutocommit\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"setBusyTimeout\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"close\"));\n    return props;\n}\n\n// 1:1 C API mapping - NO logic, just calls through to C API\njsi::Value TursoConnectionHostObject::prepareSingle(jsi::Runtime &rt, const jsi::Value *args, size_t count) {\n    if (count < 1 || !args[0].isString()) {\n        throw jsi::JSError(rt, \"prepareSingle: expected string argument\");\n    }\n\n    std::string sql = args[0].asString(rt).utf8(rt);\n    turso_statement_t* statement = nullptr;\n    const char* error = nullptr;\n\n    turso_status_code_t status = turso_connection_prepare_single(conn_, sql.c_str(), &statement, &error);\n\n    if (status != TURSO_OK) {\n        throwError(rt, error);\n    }\n\n    // Wrap statement in TursoStatementHostObject\n    auto statementObj = std::make_shared<TursoStatementHostObject>(statement);\n    return jsi::Object::createFromHostObject(rt, statementObj);\n}\n\njsi::Value TursoConnectionHostObject::prepareFirst(jsi::Runtime &rt, const jsi::Value *args, size_t count) {\n    if (count < 1 || !args[0].isString()) {\n        throw jsi::JSError(rt, \"prepareFirst: expected string argument\");\n    }\n\n    std::string sql = args[0].asString(rt).utf8(rt);\n    turso_statement_t* statement = nullptr;\n    size_t tail_idx = 0;\n    const char* error = nullptr;\n\n    turso_status_code_t status = turso_connection_prepare_first(conn_, sql.c_str(), &statement, &tail_idx, &error);\n\n    if (status != TURSO_OK) {\n        throwError(rt, error);\n    }\n\n    // If statement is null, return null (no valid statement parsed)\n    if (!statement) {\n        return jsi::Value::null();\n    }\n\n    // Return object with statement and tail_idx\n    jsi::Object result(rt);\n    auto statementObj = std::make_shared<TursoStatementHostObject>(statement);\n    result.setProperty(rt, \"statement\", jsi::Object::createFromHostObject(rt, statementObj));\n    result.setProperty(rt, \"tailIdx\", jsi::Value(static_cast<double>(tail_idx)));\n\n    return result;\n}\n\njsi::Value TursoConnectionHostObject::lastInsertRowid(jsi::Runtime &rt) {\n    int64_t rowid = turso_connection_last_insert_rowid(conn_);\n    return jsi::Value(static_cast<double>(rowid));\n}\n\njsi::Value TursoConnectionHostObject::getAutocommit(jsi::Runtime &rt) {\n    bool autocommit = turso_connection_get_autocommit(conn_);\n    return jsi::Value(autocommit);\n}\n\njsi::Value TursoConnectionHostObject::setBusyTimeout(jsi::Runtime &rt, const jsi::Value *args, size_t count) {\n    if (count < 1 || !args[0].isNumber()) {\n        throw jsi::JSError(rt, \"setBusyTimeout: expected number argument\");\n    }\n\n    int64_t timeout_ms = static_cast<int64_t>(args[0].asNumber());\n    turso_connection_set_busy_timeout_ms(conn_, timeout_ms);\n\n    return jsi::Value::undefined();\n}\n\njsi::Value TursoConnectionHostObject::close(jsi::Runtime &rt) {\n    if (!conn_) {\n        return jsi::Value::undefined();\n    }\n\n    const char* error = nullptr;\n    turso_status_code_t status = turso_connection_close(conn_, &error);\n    conn_ = nullptr; // Prevent destructor from calling deinit on closed connection\n\n    if (status != TURSO_OK) {\n        throwError(rt, error);\n    }\n\n    return jsi::Value::undefined();\n}\n\n} // namespace turso\n"
  },
  {
    "path": "bindings/react-native/cpp/TursoConnectionHostObject.h",
    "content": "#pragma once\n\n#include <jsi/jsi.h>\n#include <memory>\n#include <string>\n\n// Forward declarations for Turso C API types\nextern \"C\" {\n    struct turso_connection;\n    struct turso_statement;\n    typedef struct turso_connection turso_connection_t;\n    typedef struct turso_statement turso_statement_t;\n}\n\nnamespace turso {\n\nusing namespace facebook;\n\n/**\n * TursoConnectionHostObject wraps turso_connection_t* (core SDK-KIT type for database connection).\n * This is a THIN wrapper - 1:1 mapping of SDK-KIT C API with NO logic.\n * All logic belongs in TypeScript or Rust, not here.\n */\nclass TursoConnectionHostObject : public jsi::HostObject {\npublic:\n    TursoConnectionHostObject(turso_connection_t* conn) : conn_(conn) {}\n    ~TursoConnectionHostObject();\n\n    // JSI HostObject interface\n    jsi::Value get(jsi::Runtime &rt, const jsi::PropNameID &name) override;\n    void set(jsi::Runtime &rt, const jsi::PropNameID &name, const jsi::Value &value) override;\n    std::vector<jsi::PropNameID> getPropertyNames(jsi::Runtime &rt) override;\n\n    // Direct access to wrapped pointer (for internal use)\n    turso_connection_t* getConnection() const { return conn_; }\n\nprivate:\n    turso_connection_t* conn_ = nullptr;\n\n    // Helper to throw JS errors\n    void throwError(jsi::Runtime &rt, const char *error);\n\n    // 1:1 C API mapping methods (NO logic - just calls through to C API)\n    jsi::Value prepareSingle(jsi::Runtime &rt, const jsi::Value *args, size_t count);\n    jsi::Value prepareFirst(jsi::Runtime &rt, const jsi::Value *args, size_t count);\n    jsi::Value lastInsertRowid(jsi::Runtime &rt);\n    jsi::Value getAutocommit(jsi::Runtime &rt);\n    jsi::Value setBusyTimeout(jsi::Runtime &rt, const jsi::Value *args, size_t count);\n    jsi::Value close(jsi::Runtime &rt);\n};\n\n} // namespace turso\n"
  },
  {
    "path": "bindings/react-native/cpp/TursoDatabaseHostObject.cpp",
    "content": "#include \"TursoDatabaseHostObject.h\"\n#include \"TursoConnectionHostObject.h\"\n\nextern \"C\" {\n#include <turso.h>\n}\n\nnamespace turso {\n\nTursoDatabaseHostObject::~TursoDatabaseHostObject() {\n    if (db_) {\n        turso_database_deinit(db_);\n        db_ = nullptr;\n    }\n}\n\nvoid TursoDatabaseHostObject::throwError(jsi::Runtime &rt, const char *error) {\n    throw jsi::JSError(rt, error ? error : \"Unknown error\");\n}\n\njsi::Value TursoDatabaseHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) {\n    auto propName = name.utf8(rt);\n\n    if (propName == \"open\") {\n        return jsi::Function::createFromHostFunction(\n            rt, name, 0,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value {\n                return this->open(rt);\n            }\n        );\n    }\n\n    if (propName == \"connect\") {\n        return jsi::Function::createFromHostFunction(\n            rt, name, 0,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value {\n                return this->connect(rt);\n            }\n        );\n    }\n\n    if (propName == \"close\") {\n        return jsi::Function::createFromHostFunction(\n            rt, name, 0,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value {\n                return this->close(rt);\n            }\n        );\n    }\n\n    return jsi::Value::undefined();\n}\n\nvoid TursoDatabaseHostObject::set(jsi::Runtime &rt, const jsi::PropNameID &name, const jsi::Value &value) {\n    // Read-only object\n}\n\nstd::vector<jsi::PropNameID> TursoDatabaseHostObject::getPropertyNames(jsi::Runtime &rt) {\n    std::vector<jsi::PropNameID> props;\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"open\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"connect\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"close\"));\n    return props;\n}\n\n// 1:1 C API mapping - NO logic, just calls through to C API\njsi::Value TursoDatabaseHostObject::open(jsi::Runtime &rt) {\n    const char* error = nullptr;\n    turso_status_code_t status = turso_database_open(db_, &error);\n\n    if (status != TURSO_OK) {\n        throwError(rt, error);\n    }\n\n    return jsi::Value::undefined();\n}\n\njsi::Value TursoDatabaseHostObject::connect(jsi::Runtime &rt) {\n    turso_connection_t* connection = nullptr;\n    const char* error = nullptr;\n    turso_status_code_t status = turso_database_connect(db_, &connection, &error);\n\n    if (status != TURSO_OK) {\n        throwError(rt, error);\n    }\n\n    // Wrap connection in TursoConnectionHostObject\n    auto connectionObj = std::make_shared<TursoConnectionHostObject>(connection);\n    return jsi::Object::createFromHostObject(rt, connectionObj);\n}\n\njsi::Value TursoDatabaseHostObject::close(jsi::Runtime &rt) {\n    // turso_database_close doesn't exist in the C API\n    // Closing happens in destructor via turso_database_deinit\n    return jsi::Value::undefined();\n}\n\n} // namespace turso\n"
  },
  {
    "path": "bindings/react-native/cpp/TursoDatabaseHostObject.h",
    "content": "#pragma once\n\n#include <jsi/jsi.h>\n#include <memory>\n#include <string>\n\n// Forward declarations for Turso C API types\nextern \"C\" {\n    struct turso_database;\n    struct turso_connection;\n    typedef struct turso_database turso_database_t;\n    typedef struct turso_connection turso_connection_t;\n}\n\nnamespace turso {\n\nusing namespace facebook;\n\n/**\n * TursoDatabaseHostObject wraps turso_database_t* (core SDK-KIT type for local database).\n * This is a THIN wrapper - 1:1 mapping of SDK-KIT C API with NO logic.\n * All logic belongs in TypeScript or Rust, not here.\n */\nclass TursoDatabaseHostObject : public jsi::HostObject {\npublic:\n    TursoDatabaseHostObject(turso_database_t* db) : db_(db) {}\n    ~TursoDatabaseHostObject();\n\n    // JSI HostObject interface\n    jsi::Value get(jsi::Runtime &rt, const jsi::PropNameID &name) override;\n    void set(jsi::Runtime &rt, const jsi::PropNameID &name, const jsi::Value &value) override;\n    std::vector<jsi::PropNameID> getPropertyNames(jsi::Runtime &rt) override;\n\n    // Direct access to wrapped pointer (for internal use)\n    turso_database_t* getDatabase() const { return db_; }\n\nprivate:\n    turso_database_t* db_ = nullptr;\n\n    // Helper to throw JS errors\n    void throwError(jsi::Runtime &rt, const char *error);\n\n    // 1:1 C API mapping methods (NO logic - just calls through to C API)\n    jsi::Value open(jsi::Runtime &rt);\n    jsi::Value connect(jsi::Runtime &rt);\n    jsi::Value close(jsi::Runtime &rt);\n};\n\n} // namespace turso\n"
  },
  {
    "path": "bindings/react-native/cpp/TursoHostObject.cpp",
    "content": "#include \"TursoHostObject.h\"\n#include \"TursoDatabaseHostObject.h\"\n#include \"TursoSyncDatabaseHostObject.h\"\n\n#include <cstdio>   // For FILE, fopen, fread, fwrite, fclose, fseek, ftell, remove, rename\n#include <cstdlib>  // For additional standard library functions\n#include <cstring>  // For strrchr\n#include <unistd.h> // For fsync, close\n#include <fcntl.h>  // For open, O_RDONLY\n#include <mutex>    // For std::mutex, std::lock_guard\n\nextern \"C\" {\n#include <turso.h>\n#include <turso_sync.h>\n}\n\nnamespace turso\n{\n\n    /**\n     * Durable fsync: on Apple, fsync() only flushes to disk cache,\n     * so we need F_FULLFSYNC for true persistence. On Linux/Android,\n     * plain fsync() is sufficient.\n     */\n    static int durable_fsync(int fd)\n    {\n#ifdef __APPLE__\n        return fcntl(fd, F_FULLFSYNC);\n#else\n        return fsync(fd);\n#endif\n    }\n\n    using namespace facebook;\n\n    // Global base path for database files\n    static std::string g_basePath;\n\n    // Logger callback state — the Rust tracing subscriber may fire from any thread,\n    // so we copy log data in the C callback and schedule JS execution via CallInvoker.\n    static jsi::Runtime* g_runtime = nullptr;\n    static std::shared_ptr<react::CallInvoker> g_callInvoker;\n    static std::shared_ptr<jsi::Function> g_loggerFn;\n    static std::mutex g_loggerMutex;\n\n    /**\n     * Normalize a database path:\n     * - If path is absolute (starts with '/'), use as-is\n     * - If path is ':memory:', use as-is\n     * - Otherwise, prepend basePath\n     */\n    static std::string normalizePath(const std::string &path)\n    {\n        // Special cases: absolute path or in-memory\n        if (path.empty() || path[0] == '/' || path == \":memory:\")\n        {\n            return path;\n        }\n\n        // Relative path - prepend basePath\n        if (g_basePath.empty())\n        {\n            return path;\n        }\n\n        // Combine basePath + path\n        if (g_basePath[g_basePath.length() - 1] == '/')\n        {\n            return g_basePath + path;\n        }\n        else\n        {\n            return g_basePath + \"/\" + path;\n        }\n    }\n\n    /**\n     * Map turso_tracing_level_t enum to JS-friendly string.\n     */\n    static const char* tracingLevelToString(turso_tracing_level_t level)\n    {\n        switch (level)\n        {\n            case TURSO_TRACING_LEVEL_ERROR: return \"error\";\n            case TURSO_TRACING_LEVEL_WARN:  return \"warn\";\n            case TURSO_TRACING_LEVEL_INFO:  return \"info\";\n            case TURSO_TRACING_LEVEL_DEBUG: return \"debug\";\n            case TURSO_TRACING_LEVEL_TRACE: return \"trace\";\n            default:                        return \"error\";\n        }\n    }\n\n    /**\n     * C callback invoked by the Rust tracing subscriber (possibly from any thread).\n     * Copies all string data synchronously, then schedules a JS call on the JS thread.\n     */\n    static void turso_logger_callback(const turso_log_t *log)\n    {\n        std::lock_guard<std::mutex> lock(g_loggerMutex);\n        if (!g_loggerFn || !g_callInvoker || !g_runtime)\n        {\n            return;\n        }\n\n        // Copy all data — the turso_log_t fields are only valid during this callback.\n        std::string message = log->message ? log->message : \"\";\n        std::string target  = log->target  ? log->target  : \"\";\n        std::string file    = log->file    ? log->file    : \"\";\n        uint64_t timestamp  = log->timestamp;\n        size_t line         = log->line;\n        const char* level   = tracingLevelToString(log->level);\n        std::string levelStr(level);\n\n        // Prevent captures from preventing cleanup — capture shared_ptr copies\n        auto callInvoker = g_callInvoker;\n        auto loggerFn    = g_loggerFn;\n\n        callInvoker->invokeAsync(\n            [loggerFn, message = std::move(message), target = std::move(target),\n             file = std::move(file), timestamp, line, levelStr = std::move(levelStr)]\n            (jsi::Runtime &rt)\n            {\n                try\n                {\n                    jsi::Object logObj(rt);\n                    logObj.setProperty(rt, \"message\",   jsi::String::createFromUtf8(rt, message));\n                    logObj.setProperty(rt, \"target\",    jsi::String::createFromUtf8(rt, target));\n                    logObj.setProperty(rt, \"file\",      jsi::String::createFromUtf8(rt, file));\n                    logObj.setProperty(rt, \"timestamp\", static_cast<double>(timestamp));\n                    logObj.setProperty(rt, \"line\",      static_cast<double>(line));\n                    logObj.setProperty(rt, \"level\",     jsi::String::createFromUtf8(rt, levelStr));\n\n                    loggerFn->call(rt, logObj);\n                }\n                catch (...)\n                {\n                    // Logger must never crash the app — swallow all exceptions.\n                }\n            });\n    }\n\n    void install(\n        jsi::Runtime &rt,\n        const std::shared_ptr<react::CallInvoker> &invoker,\n        const char *basePath)\n    {\n        g_basePath = basePath ? basePath : \"\";\n        g_runtime = &rt;\n        g_callInvoker = invoker;\n\n        // Create the module object\n        jsi::Object module(rt);\n\n        // newDatabase(path, dbConfig) -> TursoDatabaseHostObject\n        // Factory for creating local-only databases\n        auto newDatabase = jsi::Function::createFromHostFunction(\n            rt,\n            jsi::PropNameID::forAscii(rt, \"newDatabase\"),\n            1, // min args\n            [](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value\n            {\n                if (count < 1 || !args[0].isString())\n                {\n                    throw jsi::JSError(rt, \"newDatabase() requires path string as first argument\");\n                }\n\n                std::string path = args[0].asString(rt).utf8(rt);\n\n                // Normalize path (prepend basePath if relative)\n                std::string normalizedPath = normalizePath(path);\n\n                // Build database config\n                turso_database_config_t db_config = {0};\n                db_config.async_io = 1;  // Default to async IO for React Native\n                db_config.path = normalizedPath.c_str();\n                db_config.experimental_features = nullptr;\n                db_config.vfs = nullptr;\n                db_config.encryption_cipher = nullptr;\n                db_config.encryption_hexkey = nullptr;\n\n                // Parse optional dbConfig object (second argument)\n                if (count >= 2 && args[1].isObject())\n                {\n                    jsi::Object config = args[1].asObject(rt);\n\n                    // Parse async_io if provided\n                    if (config.hasProperty(rt, \"async_io\"))\n                    {\n                        db_config.async_io = config.getProperty(rt, \"async_io\").getBool() ? 1 : 0;\n                    }\n                }\n\n                // Create database instance\n                const turso_database_t* database = nullptr;\n                const char* error = nullptr;\n                turso_status_code_t status = turso_database_new(&db_config, &database, &error);\n\n                if (status != TURSO_OK)\n                {\n                    std::string errorMsg = error ? error : \"Failed to create database\";\n                    throw jsi::JSError(rt, errorMsg);\n                }\n\n                // Wrap in TursoDatabaseHostObject\n                auto dbObj = std::make_shared<TursoDatabaseHostObject>(\n                    const_cast<turso_database_t*>(database)\n                );\n                return jsi::Object::createFromHostObject(rt, dbObj);\n            });\n\n        // newSyncDatabase(dbConfig, syncConfig) -> TursoSyncDatabaseHostObject\n        // Factory for creating sync-enabled embedded replica databases\n        auto newSyncDatabase = jsi::Function::createFromHostFunction(\n            rt,\n            jsi::PropNameID::forAscii(rt, \"newSyncDatabase\"),\n            2, // min args\n            [](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value\n            {\n                if (count < 2 || !args[0].isObject() || !args[1].isObject())\n                {\n                    throw jsi::JSError(rt, \"newSyncDatabase() requires dbConfig and syncConfig objects\");\n                }\n\n                jsi::Object dbConfigObj = args[0].asObject(rt);\n                jsi::Object syncConfigObj = args[1].asObject(rt);\n\n                // Parse dbConfig\n                if (!dbConfigObj.hasProperty(rt, \"path\"))\n                {\n                    throw jsi::JSError(rt, \"dbConfig must have 'path' property\");\n                }\n                std::string path = dbConfigObj.getProperty(rt, \"path\").asString(rt).utf8(rt);\n\n                // Normalize path (prepend basePath if relative)\n                std::string normalizedPath = normalizePath(path);\n\n                turso_database_config_t db_config = {0};\n                db_config.async_io = 1;  // Default to async IO for React Native\n                db_config.path = normalizedPath.c_str();\n\n                // Parse async_io if provided in dbConfig\n                if (dbConfigObj.hasProperty(rt, \"async_io\"))\n                {\n                    db_config.async_io = dbConfigObj.getProperty(rt, \"async_io\").getBool() ? 1 : 0;\n                }\n                db_config.experimental_features = nullptr;\n                db_config.vfs = nullptr;\n                db_config.encryption_cipher = nullptr;\n                db_config.encryption_hexkey = nullptr;\n\n                // Parse syncConfig\n                turso_sync_database_config_t sync_config = {0};\n\n                // path (already set in db_config, but sync_config also needs it)\n                sync_config.path = normalizedPath.c_str();\n\n                // remoteUrl (optional)\n                std::string remoteUrl;\n                if (syncConfigObj.hasProperty(rt, \"remoteUrl\"))\n                {\n                    jsi::Value remoteUrlVal = syncConfigObj.getProperty(rt, \"remoteUrl\");\n                    if (!remoteUrlVal.isNull() && !remoteUrlVal.isUndefined())\n                    {\n                        remoteUrl = remoteUrlVal.asString(rt).utf8(rt);\n                        sync_config.remote_url = remoteUrl.c_str();\n                    }\n                    else\n                    {\n                        sync_config.remote_url = nullptr;\n                    }\n                }\n                else\n                {\n                    sync_config.remote_url = nullptr;\n                }\n\n                // clientName (optional)\n                std::string clientName;\n                if (syncConfigObj.hasProperty(rt, \"clientName\"))\n                {\n                    jsi::Value clientNameVal = syncConfigObj.getProperty(rt, \"clientName\");\n                    if (!clientNameVal.isNull() && !clientNameVal.isUndefined())\n                    {\n                        clientName = clientNameVal.asString(rt).utf8(rt);\n                        sync_config.client_name = clientName.c_str();\n                    }\n                    else\n                    {\n                        sync_config.client_name = nullptr;\n                    }\n                }\n                else\n                {\n                    sync_config.client_name = nullptr;\n                }\n\n                // longPollTimeoutMs\n                if (syncConfigObj.hasProperty(rt, \"longPollTimeoutMs\"))\n                {\n                    jsi::Value longPollVal = syncConfigObj.getProperty(rt, \"longPollTimeoutMs\");\n                    if (!longPollVal.isNull() && !longPollVal.isUndefined())\n                    {\n                        sync_config.long_poll_timeout_ms = static_cast<int32_t>(longPollVal.asNumber());\n                    }\n                    else\n                    {\n                        sync_config.long_poll_timeout_ms = 0;\n                    }\n                }\n                else\n                {\n                    sync_config.long_poll_timeout_ms = 0;\n                }\n\n                // bootstrapIfEmpty\n                if (syncConfigObj.hasProperty(rt, \"bootstrapIfEmpty\"))\n                {\n                    jsi::Value bootstrapVal = syncConfigObj.getProperty(rt, \"bootstrapIfEmpty\");\n                    if (!bootstrapVal.isNull() && !bootstrapVal.isUndefined())\n                    {\n                        sync_config.bootstrap_if_empty = bootstrapVal.getBool();\n                    }\n                    else\n                    {\n                        sync_config.bootstrap_if_empty = false;\n                    }\n                }\n                else\n                {\n                    sync_config.bootstrap_if_empty = false;\n                }\n\n                // reservedBytes\n                if (syncConfigObj.hasProperty(rt, \"reservedBytes\"))\n                {\n                    jsi::Value reservedVal = syncConfigObj.getProperty(rt, \"reservedBytes\");\n                    if (!reservedVal.isNull() && !reservedVal.isUndefined())\n                    {\n                        sync_config.reserved_bytes = static_cast<int32_t>(reservedVal.asNumber());\n                    }\n                    else\n                    {\n                        sync_config.reserved_bytes = 0;\n                    }\n                }\n                else\n                {\n                    sync_config.reserved_bytes = 0;\n                }\n\n                // Partial sync options\n                if (syncConfigObj.hasProperty(rt, \"partialBootstrapStrategyPrefix\"))\n                {\n                    jsi::Value prefixVal = syncConfigObj.getProperty(rt, \"partialBootstrapStrategyPrefix\");\n                    if (!prefixVal.isNull() && !prefixVal.isUndefined())\n                    {\n                        sync_config.partial_bootstrap_strategy_prefix = static_cast<int32_t>(prefixVal.asNumber());\n                    }\n                    else\n                    {\n                        sync_config.partial_bootstrap_strategy_prefix = 0;\n                    }\n                }\n                else\n                {\n                    sync_config.partial_bootstrap_strategy_prefix = 0;\n                }\n\n                std::string partialBootstrapStrategyQuery;\n                if (syncConfigObj.hasProperty(rt, \"partialBootstrapStrategyQuery\"))\n                {\n                    jsi::Value queryVal = syncConfigObj.getProperty(rt, \"partialBootstrapStrategyQuery\");\n                    if (!queryVal.isNull() && !queryVal.isUndefined())\n                    {\n                        partialBootstrapStrategyQuery = queryVal.asString(rt).utf8(rt);\n                        sync_config.partial_bootstrap_strategy_query = partialBootstrapStrategyQuery.c_str();\n                    }\n                    else\n                    {\n                        sync_config.partial_bootstrap_strategy_query = nullptr;\n                    }\n                }\n                else\n                {\n                    sync_config.partial_bootstrap_strategy_query = nullptr;\n                }\n\n                if (syncConfigObj.hasProperty(rt, \"partialBootstrapSegmentSize\"))\n                {\n                    jsi::Value segmentVal = syncConfigObj.getProperty(rt, \"partialBootstrapSegmentSize\");\n                    if (!segmentVal.isNull() && !segmentVal.isUndefined())\n                    {\n                        sync_config.partial_bootstrap_segment_size = static_cast<size_t>(segmentVal.asNumber());\n                    }\n                    else\n                    {\n                        sync_config.partial_bootstrap_segment_size = 0;\n                    }\n                }\n                else\n                {\n                    sync_config.partial_bootstrap_segment_size = 0;\n                }\n\n                if (syncConfigObj.hasProperty(rt, \"partialBootstrapPrefetch\"))\n                {\n                    jsi::Value prefetchVal = syncConfigObj.getProperty(rt, \"partialBootstrapPrefetch\");\n                    if (!prefetchVal.isNull() && !prefetchVal.isUndefined())\n                    {\n                        sync_config.partial_bootstrap_prefetch = prefetchVal.getBool();\n                    }\n                    else\n                    {\n                        sync_config.partial_bootstrap_prefetch = false;\n                    }\n                }\n                else\n                {\n                    sync_config.partial_bootstrap_prefetch = false;\n                }\n\n                // Remote encryption options\n                std::string remoteEncryptionKey;\n                if (syncConfigObj.hasProperty(rt, \"remoteEncryptionKey\"))\n                {\n                    jsi::Value keyVal = syncConfigObj.getProperty(rt, \"remoteEncryptionKey\");\n                    if (!keyVal.isNull() && !keyVal.isUndefined())\n                    {\n                        remoteEncryptionKey = keyVal.asString(rt).utf8(rt);\n                        sync_config.remote_encryption_key = remoteEncryptionKey.c_str();\n                    }\n                    else\n                    {\n                        sync_config.remote_encryption_key = nullptr;\n                    }\n                }\n                else\n                {\n                    sync_config.remote_encryption_key = nullptr;\n                }\n\n                std::string remoteEncryptionCipher;\n                if (syncConfigObj.hasProperty(rt, \"remoteEncryptionCipher\"))\n                {\n                    jsi::Value cipherVal = syncConfigObj.getProperty(rt, \"remoteEncryptionCipher\");\n                    if (!cipherVal.isNull() && !cipherVal.isUndefined())\n                    {\n                        remoteEncryptionCipher = cipherVal.asString(rt).utf8(rt);\n                        sync_config.remote_encryption_cipher = remoteEncryptionCipher.c_str();\n                    }\n                    else\n                    {\n                        sync_config.remote_encryption_cipher = nullptr;\n                    }\n                }\n                else\n                {\n                    sync_config.remote_encryption_cipher = nullptr;\n                }\n\n                // Create sync database instance\n                const turso_sync_database_t* database = nullptr;\n                const char* error = nullptr;\n                turso_status_code_t status = turso_sync_database_new(&db_config, &sync_config, &database, &error);\n\n                if (status != TURSO_OK)\n                {\n                    std::string errorMsg = error ? error : \"Failed to create sync database\";\n                    throw jsi::JSError(rt, errorMsg);\n                }\n\n                // Wrap in TursoSyncDatabaseHostObject\n                auto dbObj = std::make_shared<TursoSyncDatabaseHostObject>(\n                    const_cast<turso_sync_database_t*>(database)\n                );\n                return jsi::Object::createFromHostObject(rt, dbObj);\n            });\n\n        // version() -> string\n        auto version = jsi::Function::createFromHostFunction(\n            rt,\n            jsi::PropNameID::forAscii(rt, \"version\"),\n            0,\n            [](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value\n            {\n                const char *ver = turso_version();\n                return jsi::String::createFromUtf8(rt, ver);\n            });\n\n        // setup(options) -> void\n        auto setup = jsi::Function::createFromHostFunction(\n            rt,\n            jsi::PropNameID::forAscii(rt, \"setup\"),\n            1,\n            [](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value\n            {\n                if (count < 1 || !args[0].isObject())\n                {\n                    throw jsi::JSError(rt, \"setup() requires an options object\");\n                }\n\n                jsi::Object options = args[0].asObject(rt);\n\n                std::string logLevelStr;\n\n                // Get log level if provided\n                if (options.hasProperty(rt, \"logLevel\"))\n                {\n                    jsi::Value logLevelVal = options.getProperty(rt, \"logLevel\");\n                    if (logLevelVal.isString())\n                    {\n                        logLevelStr = logLevelVal.asString(rt).utf8(rt);\n                    }\n                }\n\n                turso_config_t config = {nullptr, logLevelStr.empty() ? nullptr : logLevelStr.c_str()};\n\n                // Wire up logger callback if provided\n                if (options.hasProperty(rt, \"logger\"))\n                {\n                    jsi::Value loggerVal = options.getProperty(rt, \"logger\");\n                    if (loggerVal.isObject() && loggerVal.asObject(rt).isFunction(rt))\n                    {\n                        {\n                            std::lock_guard<std::mutex> lock(g_loggerMutex);\n                            g_loggerFn = std::make_shared<jsi::Function>(\n                                loggerVal.asObject(rt).asFunction(rt));\n                        }\n                        config.logger = turso_logger_callback;\n                    }\n                }\n\n                // Call turso_setup\n                const char *error = nullptr;\n                turso_status_code_t status = turso_setup(&config, &error);\n\n                if (status != TURSO_OK)\n                {\n                    std::string errorMsg = error ? error : \"Unknown error in turso_setup\";\n                    throw jsi::JSError(rt, errorMsg);\n                }\n\n                return jsi::Value::undefined();\n            });\n\n        // fsReadFile(path) -> ArrayBuffer\n        auto fsReadFile = jsi::Function::createFromHostFunction(\n            rt,\n            jsi::PropNameID::forAscii(rt, \"fsReadFile\"),\n            1,\n            [](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value\n            {\n                if (count < 1 || !args[0].isString())\n                {\n                    throw jsi::JSError(rt, \"fsReadFile() requires path string\");\n                }\n\n                std::string path = args[0].asString(rt).utf8(rt);\n\n                // Open file for reading\n                FILE* file = fopen(path.c_str(), \"rb\");\n                if (!file)\n                {\n                    // File not found - return null (caller will handle as empty)\n                    return jsi::Value::null();\n                }\n\n                // Get file size\n                fseek(file, 0, SEEK_END);\n                long size = ftell(file);\n                fseek(file, 0, SEEK_SET);\n\n                if (size <= 0)\n                {\n                    fclose(file);\n                    // Empty file - return empty ArrayBuffer\n                    jsi::Function arrayBufferCtor = rt.global().getPropertyAsFunction(rt, \"ArrayBuffer\");\n                    jsi::Object arrayBuffer = arrayBufferCtor.callAsConstructor(rt, 0).asObject(rt);\n                    return arrayBuffer;\n                }\n\n                // Read file contents\n                jsi::Function arrayBufferCtor = rt.global().getPropertyAsFunction(rt, \"ArrayBuffer\");\n                jsi::Object arrayBuffer = arrayBufferCtor.callAsConstructor(rt, static_cast<int>(size)).asObject(rt);\n                jsi::ArrayBuffer buf = arrayBuffer.getArrayBuffer(rt);\n\n                size_t bytesRead = fread(buf.data(rt), 1, size, file);\n                fclose(file);\n\n                if (bytesRead != static_cast<size_t>(size))\n                {\n                    throw jsi::JSError(rt, \"Failed to read complete file\");\n                }\n\n                return arrayBuffer;\n            });\n\n        // fsWriteFile(path, arrayBuffer) -> void\n        auto fsWriteFile = jsi::Function::createFromHostFunction(\n            rt,\n            jsi::PropNameID::forAscii(rt, \"fsWriteFile\"),\n            2,\n            [](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value\n            {\n                if (count < 2 || !args[0].isString() || !args[1].isObject())\n                {\n                    throw jsi::JSError(rt, \"fsWriteFile() requires path string and ArrayBuffer\");\n                }\n\n                std::string path = args[0].asString(rt).utf8(rt);\n                jsi::ArrayBuffer buffer = args[1].asObject(rt).getArrayBuffer(rt);\n\n                // Write atomically: write to temp, fsync, rename, fsync dir\n                std::string tempPath = path + \".tmp\";\n\n                FILE* file = fopen(tempPath.c_str(), \"wb\");\n                if (!file)\n                {\n                    throw jsi::JSError(rt, \"Failed to open file for writing\");\n                }\n\n                size_t size = buffer.size(rt);\n                if (size > 0)\n                {\n                    size_t written = fwrite(buffer.data(rt), 1, size, file);\n                    if (written != size)\n                    {\n                        fclose(file);\n                        remove(tempPath.c_str());\n                        throw jsi::JSError(rt, \"Failed to write complete file\");\n                    }\n                }\n\n                // Flush to OS and sync to disk before rename\n                if (fflush(file) != 0 || durable_fsync(fileno(file)) != 0)\n                {\n                    fclose(file);\n                    remove(tempPath.c_str());\n                    throw jsi::JSError(rt, \"Failed to sync file to disk\");\n                }\n                fclose(file);\n\n                // Atomic rename (replaces old file)\n                if (rename(tempPath.c_str(), path.c_str()) != 0)\n                {\n                    remove(tempPath.c_str());\n                    throw jsi::JSError(rt, \"Failed to rename temp file\");\n                }\n\n                // Fsync parent directory to ensure rename is durable\n                std::string dirPath = path;\n                auto lastSlash = dirPath.rfind('/');\n                if (lastSlash != std::string::npos)\n                {\n                    dirPath.resize(lastSlash);\n                    int dirFd = open(dirPath.c_str(), O_RDONLY);\n                    if (dirFd >= 0)\n                    {\n                        durable_fsync(dirFd);\n                        close(dirFd);\n                    }\n                }\n\n                return jsi::Value::undefined();\n            });\n\n        module.setProperty(rt, \"newDatabase\", std::move(newDatabase));\n        module.setProperty(rt, \"newSyncDatabase\", std::move(newSyncDatabase));\n        module.setProperty(rt, \"version\", std::move(version));\n        module.setProperty(rt, \"setup\", std::move(setup));\n        module.setProperty(rt, \"fsReadFile\", std::move(fsReadFile));\n        module.setProperty(rt, \"fsWriteFile\", std::move(fsWriteFile));\n\n        // Install as global __TursoProxy\n        rt.global().setProperty(rt, \"__TursoProxy\", std::move(module));\n    }\n\n    void invalidate()\n    {\n        std::lock_guard<std::mutex> lock(g_loggerMutex);\n        g_loggerFn.reset();\n        g_callInvoker.reset();\n        g_runtime = nullptr;\n    }\n\n} // namespace turso\n"
  },
  {
    "path": "bindings/react-native/cpp/TursoHostObject.h",
    "content": "#pragma once\n\n#include <jsi/jsi.h>\n#include <ReactCommon/CallInvoker.h>\n#include <memory>\n#include <string>\n\nnamespace turso {\n\nusing namespace facebook;\n\n/**\n * Install the Turso module into the JSI runtime.\n * This creates a global __TursoProxy object with the open() function.\n */\nvoid install(\n    jsi::Runtime &rt,\n    const std::shared_ptr<react::CallInvoker> &invoker,\n    const char *basePath\n);\n\nvoid invalidate();\n\n} // namespace turso\n"
  },
  {
    "path": "bindings/react-native/cpp/TursoStatementHostObject.cpp",
    "content": "#include \"TursoStatementHostObject.h\"\n\nextern \"C\" {\n#include <turso.h>\n}\n\nnamespace turso {\n\nTursoStatementHostObject::~TursoStatementHostObject() {\n    if (stmt_) {\n        turso_statement_deinit(stmt_);\n        stmt_ = nullptr;\n    }\n}\n\nvoid TursoStatementHostObject::throwError(jsi::Runtime &rt, const char *error) {\n    throw jsi::JSError(rt, error ? error : \"Unknown error\");\n}\n\njsi::Value TursoStatementHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) {\n    auto propName = name.utf8(rt);\n\n    if (propName == \"bindPositionalNull\") {\n        return jsi::Function::createFromHostFunction(rt, name, 1,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value {\n                return this->bindPositionalNull(rt, args, count);\n            });\n    }\n    if (propName == \"bindPositionalInt\") {\n        return jsi::Function::createFromHostFunction(rt, name, 2,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value {\n                return this->bindPositionalInt(rt, args, count);\n            });\n    }\n    if (propName == \"bindPositionalDouble\") {\n        return jsi::Function::createFromHostFunction(rt, name, 2,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value {\n                return this->bindPositionalDouble(rt, args, count);\n            });\n    }\n    if (propName == \"bindPositionalBlob\") {\n        return jsi::Function::createFromHostFunction(rt, name, 2,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value {\n                return this->bindPositionalBlob(rt, args, count);\n            });\n    }\n    if (propName == \"bindPositionalText\") {\n        return jsi::Function::createFromHostFunction(rt, name, 2,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value {\n                return this->bindPositionalText(rt, args, count);\n            });\n    }\n    if (propName == \"execute\") {\n        return jsi::Function::createFromHostFunction(rt, name, 0,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value {\n                return this->execute(rt);\n            });\n    }\n    if (propName == \"step\") {\n        return jsi::Function::createFromHostFunction(rt, name, 0,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value {\n                return this->step(rt);\n            });\n    }\n    if (propName == \"runIo\") {\n        return jsi::Function::createFromHostFunction(rt, name, 0,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value {\n                return this->runIo(rt);\n            });\n    }\n    if (propName == \"reset\") {\n        return jsi::Function::createFromHostFunction(rt, name, 0,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value {\n                return this->reset(rt);\n            });\n    }\n    if (propName == \"finalize\") {\n        return jsi::Function::createFromHostFunction(rt, name, 0,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value {\n                return this->finalize(rt);\n            });\n    }\n    if (propName == \"nChange\") {\n        return jsi::Function::createFromHostFunction(rt, name, 0,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value {\n                return this->nChange(rt);\n            });\n    }\n    if (propName == \"columnCount\") {\n        return jsi::Function::createFromHostFunction(rt, name, 0,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value {\n                return this->columnCount(rt);\n            });\n    }\n    if (propName == \"columnName\") {\n        return jsi::Function::createFromHostFunction(rt, name, 1,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value {\n                return this->columnName(rt, args, count);\n            });\n    }\n    if (propName == \"rowValueKind\") {\n        return jsi::Function::createFromHostFunction(rt, name, 1,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value {\n                return this->rowValueKind(rt, args, count);\n            });\n    }\n    if (propName == \"rowValueBytesCount\") {\n        return jsi::Function::createFromHostFunction(rt, name, 1,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value {\n                return this->rowValueBytesCount(rt, args, count);\n            });\n    }\n    if (propName == \"rowValueBytesPtr\") {\n        return jsi::Function::createFromHostFunction(rt, name, 1,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value {\n                return this->rowValueBytesPtr(rt, args, count);\n            });\n    }\n    if (propName == \"rowValueText\") {\n        return jsi::Function::createFromHostFunction(rt, name, 1,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value {\n                return this->rowValueText(rt, args, count);\n            });\n    }\n    if (propName == \"rowValueInt\") {\n        return jsi::Function::createFromHostFunction(rt, name, 1,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value {\n                return this->rowValueInt(rt, args, count);\n            });\n    }\n    if (propName == \"rowValueDouble\") {\n        return jsi::Function::createFromHostFunction(rt, name, 1,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value {\n                return this->rowValueDouble(rt, args, count);\n            });\n    }\n    if (propName == \"namedPosition\") {\n        return jsi::Function::createFromHostFunction(rt, name, 1,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value {\n                return this->namedPosition(rt, args, count);\n            });\n    }\n    if (propName == \"parametersCount\") {\n        return jsi::Function::createFromHostFunction(rt, name, 0,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value {\n                return this->parametersCount(rt);\n            });\n    }\n    if (propName == \"getAllRows\") {\n        return jsi::Function::createFromHostFunction(rt, name, 0,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value {\n                return this->getAllRows(rt);\n            });\n    }\n\n    return jsi::Value::undefined();\n}\n\nvoid TursoStatementHostObject::set(jsi::Runtime &rt, const jsi::PropNameID &name, const jsi::Value &value) {\n    // Read-only object\n}\n\nstd::vector<jsi::PropNameID> TursoStatementHostObject::getPropertyNames(jsi::Runtime &rt) {\n    std::vector<jsi::PropNameID> props;\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"bindPositionalNull\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"bindPositionalInt\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"bindPositionalDouble\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"bindPositionalBlob\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"bindPositionalText\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"execute\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"step\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"runIo\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"reset\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"finalize\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"nChange\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"columnCount\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"columnName\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"rowValueKind\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"rowValueBytesCount\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"rowValueBytesPtr\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"rowValueText\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"rowValueInt\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"rowValueDouble\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"namedPosition\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"parametersCount\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"getAllRows\"));\n    return props;\n}\n\n// 1:1 C API mapping - NO logic, just calls through to C API\n\njsi::Value TursoStatementHostObject::bindPositionalNull(jsi::Runtime &rt, const jsi::Value *args, size_t count) {\n    if (count < 1 || !args[0].isNumber()) {\n        throw jsi::JSError(rt, \"bindPositionalNull: expected number argument (position)\");\n    }\n    size_t position = static_cast<size_t>(args[0].asNumber());\n    turso_status_code_t status = turso_statement_bind_positional_null(stmt_, position);\n    return jsi::Value(static_cast<int>(status));\n}\n\njsi::Value TursoStatementHostObject::bindPositionalInt(jsi::Runtime &rt, const jsi::Value *args, size_t count) {\n    if (count < 2 || !args[0].isNumber() || !args[1].isNumber()) {\n        throw jsi::JSError(rt, \"bindPositionalInt: expected two number arguments (position, value)\");\n    }\n    size_t position = static_cast<size_t>(args[0].asNumber());\n    int64_t value = static_cast<int64_t>(args[1].asNumber());\n    turso_status_code_t status = turso_statement_bind_positional_int(stmt_, position, value);\n    return jsi::Value(static_cast<int>(status));\n}\n\njsi::Value TursoStatementHostObject::bindPositionalDouble(jsi::Runtime &rt, const jsi::Value *args, size_t count) {\n    if (count < 2 || !args[0].isNumber() || !args[1].isNumber()) {\n        throw jsi::JSError(rt, \"bindPositionalDouble: expected two number arguments (position, value)\");\n    }\n    size_t position = static_cast<size_t>(args[0].asNumber());\n    double value = args[1].asNumber();\n    turso_status_code_t status = turso_statement_bind_positional_double(stmt_, position, value);\n    return jsi::Value(static_cast<int>(status));\n}\n\njsi::Value TursoStatementHostObject::bindPositionalBlob(jsi::Runtime &rt, const jsi::Value *args, size_t count) {\n    if (count < 2 || !args[0].isNumber() || !args[1].isObject()) {\n        throw jsi::JSError(rt, \"bindPositionalBlob: expected number and ArrayBuffer arguments\");\n    }\n    size_t position = static_cast<size_t>(args[0].asNumber());\n\n    // Get ArrayBuffer\n    auto arrayBuffer = args[1].asObject(rt).getArrayBuffer(rt);\n    const char* data = reinterpret_cast<const char*>(arrayBuffer.data(rt));\n    size_t len = arrayBuffer.size(rt);\n\n    turso_status_code_t status = turso_statement_bind_positional_blob(stmt_, position, data, len);\n    return jsi::Value(static_cast<int>(status));\n}\n\njsi::Value TursoStatementHostObject::bindPositionalText(jsi::Runtime &rt, const jsi::Value *args, size_t count) {\n    if (count < 2 || !args[0].isNumber() || !args[1].isString()) {\n        throw jsi::JSError(rt, \"bindPositionalText: expected number and string arguments\");\n    }\n    size_t position = static_cast<size_t>(args[0].asNumber());\n    std::string value = args[1].asString(rt).utf8(rt);\n    turso_status_code_t status = turso_statement_bind_positional_text(stmt_, position, value.c_str(), value.length());\n    return jsi::Value(static_cast<int>(status));\n}\n\njsi::Value TursoStatementHostObject::execute(jsi::Runtime &rt) {\n    uint64_t rows_changed = 0;\n    const char* error = nullptr;\n    turso_status_code_t status = turso_statement_execute(stmt_, &rows_changed, &error);\n\n    if (status != TURSO_OK && status != TURSO_DONE && status != TURSO_IO) {\n        throwError(rt, error);\n    }\n\n    // Return object with status and rows_changed\n    jsi::Object result(rt);\n    result.setProperty(rt, \"status\", jsi::Value(static_cast<int>(status)));\n    result.setProperty(rt, \"rowsChanged\", jsi::Value(static_cast<double>(rows_changed)));\n    return result;\n}\n\njsi::Value TursoStatementHostObject::step(jsi::Runtime &rt) {\n    const char* error = nullptr;\n    turso_status_code_t status = turso_statement_step(stmt_, &error);\n\n    if (status != TURSO_OK && status != TURSO_DONE && status != TURSO_ROW && status != TURSO_IO) {\n        throwError(rt, error);\n    }\n\n    return jsi::Value(static_cast<int>(status));\n}\n\njsi::Value TursoStatementHostObject::runIo(jsi::Runtime &rt) {\n    const char* error = nullptr;\n    turso_status_code_t status = turso_statement_run_io(stmt_, &error);\n\n    if (status != TURSO_OK) {\n        throwError(rt, error);\n    }\n\n    return jsi::Value(static_cast<int>(status));\n}\n\njsi::Value TursoStatementHostObject::reset(jsi::Runtime &rt) {\n    const char* error = nullptr;\n    turso_status_code_t status = turso_statement_reset(stmt_, &error);\n\n    if (status != TURSO_OK) {\n        throwError(rt, error);\n    }\n\n    return jsi::Value::undefined();\n}\n\njsi::Value TursoStatementHostObject::finalize(jsi::Runtime &rt) {\n    if (!stmt_) {\n        return jsi::Value(static_cast<int>(TURSO_DONE));\n    }\n\n    const char* error = nullptr;\n    turso_status_code_t status = turso_statement_finalize(stmt_, &error);\n\n    if (status == TURSO_DONE) {\n        stmt_ = nullptr; // Prevent destructor from calling deinit on finalized statement\n    }\n\n    if (status != TURSO_DONE && status != TURSO_IO) {\n        throwError(rt, error);\n    }\n\n    return jsi::Value(static_cast<int>(status));\n}\n\njsi::Value TursoStatementHostObject::nChange(jsi::Runtime &rt) {\n    int64_t n = turso_statement_n_change(stmt_);\n    return jsi::Value(static_cast<double>(n));\n}\n\njsi::Value TursoStatementHostObject::columnCount(jsi::Runtime &rt) {\n    int64_t count = turso_statement_column_count(stmt_);\n    return jsi::Value(static_cast<double>(count));\n}\n\njsi::Value TursoStatementHostObject::columnName(jsi::Runtime &rt, const jsi::Value *args, size_t count) {\n    if (count < 1 || !args[0].isNumber()) {\n        throw jsi::JSError(rt, \"columnName: expected number argument (index)\");\n    }\n    size_t index = static_cast<size_t>(args[0].asNumber());\n    const char* name = turso_statement_column_name(stmt_, index);\n\n    if (!name) {\n        return jsi::Value::null();\n    }\n\n    std::string nameStr(name);\n    turso_str_deinit(name);  // Free the C string\n\n    return jsi::String::createFromUtf8(rt, nameStr);\n}\n\njsi::Value TursoStatementHostObject::rowValueKind(jsi::Runtime &rt, const jsi::Value *args, size_t count) {\n    if (count < 1 || !args[0].isNumber()) {\n        throw jsi::JSError(rt, \"rowValueKind: expected number argument (index)\");\n    }\n    size_t index = static_cast<size_t>(args[0].asNumber());\n    turso_type_t kind = turso_statement_row_value_kind(stmt_, index);\n    return jsi::Value(static_cast<int>(kind));\n}\n\njsi::Value TursoStatementHostObject::rowValueBytesCount(jsi::Runtime &rt, const jsi::Value *args, size_t count) {\n    if (count < 1 || !args[0].isNumber()) {\n        throw jsi::JSError(rt, \"rowValueBytesCount: expected number argument (index)\");\n    }\n    size_t index = static_cast<size_t>(args[0].asNumber());\n    int64_t bytes = turso_statement_row_value_bytes_count(stmt_, index);\n    return jsi::Value(static_cast<double>(bytes));\n}\n\njsi::Value TursoStatementHostObject::rowValueBytesPtr(jsi::Runtime &rt, const jsi::Value *args, size_t count) {\n    if (count < 1 || !args[0].isNumber()) {\n        throw jsi::JSError(rt, \"rowValueBytesPtr: expected number argument (index)\");\n    }\n    size_t index = static_cast<size_t>(args[0].asNumber());\n    const char* ptr = turso_statement_row_value_bytes_ptr(stmt_, index);\n    int64_t bytes = turso_statement_row_value_bytes_count(stmt_, index);\n\n    if (!ptr || bytes <= 0) {\n        return jsi::Value::null();\n    }\n\n    // Create ArrayBuffer and copy data\n    jsi::Function arrayBufferCtor = rt.global().getPropertyAsFunction(rt, \"ArrayBuffer\");\n    jsi::Object arrayBuffer = arrayBufferCtor.callAsConstructor(rt, static_cast<int>(bytes)).asObject(rt);\n    jsi::ArrayBuffer buf = arrayBuffer.getArrayBuffer(rt);\n    memcpy(buf.data(rt), ptr, bytes);\n\n    return arrayBuffer;\n}\n\njsi::Value TursoStatementHostObject::rowValueText(jsi::Runtime &rt, const jsi::Value *args, size_t count) {\n    if (count < 1 || !args[0].isNumber()) {\n        throw jsi::JSError(rt, \"rowValueText: expected number argument (index)\");\n    }\n    size_t index = static_cast<size_t>(args[0].asNumber());\n    const char* ptr = turso_statement_row_value_bytes_ptr(stmt_, index);\n    int64_t bytes = turso_statement_row_value_bytes_count(stmt_, index);\n\n    if (!ptr || bytes < 0) {\n        return jsi::String::createFromUtf8(rt, \"\");\n    }\n\n    // Create jsi::String directly from UTF-8 bytes\n    // This avoids the round-trip through ArrayBuffer and decoding in JS\n    return jsi::String::createFromUtf8(rt, reinterpret_cast<const uint8_t*>(ptr), static_cast<size_t>(bytes));\n}\n\njsi::Value TursoStatementHostObject::rowValueInt(jsi::Runtime &rt, const jsi::Value *args, size_t count) {\n    if (count < 1 || !args[0].isNumber()) {\n        throw jsi::JSError(rt, \"rowValueInt: expected number argument (index)\");\n    }\n    size_t index = static_cast<size_t>(args[0].asNumber());\n    int64_t value = turso_statement_row_value_int(stmt_, index);\n    return jsi::Value(static_cast<double>(value));\n}\n\njsi::Value TursoStatementHostObject::rowValueDouble(jsi::Runtime &rt, const jsi::Value *args, size_t count) {\n    if (count < 1 || !args[0].isNumber()) {\n        throw jsi::JSError(rt, \"rowValueDouble: expected number argument (index)\");\n    }\n    size_t index = static_cast<size_t>(args[0].asNumber());\n    double value = turso_statement_row_value_double(stmt_, index);\n    return jsi::Value(value);\n}\n\njsi::Value TursoStatementHostObject::namedPosition(jsi::Runtime &rt, const jsi::Value *args, size_t count) {\n    if (count < 1 || !args[0].isString()) {\n        throw jsi::JSError(rt, \"namedPosition: expected string argument (name)\");\n    }\n    std::string name = args[0].asString(rt).utf8(rt);\n    int64_t position = turso_statement_named_position(stmt_, name.c_str());\n    return jsi::Value(static_cast<double>(position));\n}\n\njsi::Value TursoStatementHostObject::parametersCount(jsi::Runtime &rt) {\n    int64_t count = turso_statement_parameters_count(stmt_);\n    return jsi::Value(static_cast<double>(count));\n}\n\njsi::Value TursoStatementHostObject::getAllRows(jsi::Runtime &rt) {\n    int64_t colCount = turso_statement_column_count(stmt_);\n    if (colCount < 0) {\n        // defensive fallback to 0 to not allocate a vector with a negative size\n        colCount = 0;\n    }\n\n    // Cache column names upfront (one allocation per query, not per row)\n    std::vector<std::string> colNames(colCount);\n    for (int64_t i = 0; i < colCount; ++i) {\n        const char* name = turso_statement_column_name(stmt_, static_cast<size_t>(i));\n        if (name) {\n            colNames[i] = std::string(name);\n            turso_str_deinit(name);\n        }\n    }\n\n    // Collect rows into a vector first, then build the jsi::Array at the end\n    // (jsi::Array needs a size upfront)\n    std::vector<jsi::Object> rowObjects;\n\n    while (true) {\n        const char* error = nullptr;\n        turso_status_code_t status = turso_statement_step(stmt_, &error);\n\n        if (status == TURSO_DONE) {\n            break;\n        }\n\n        if (status == TURSO_IO) {\n            // Return partial results so JS can handle IO and fall back\n            jsi::Array rows(rt, rowObjects.size());\n            for (size_t i = 0; i < rowObjects.size(); ++i) {\n                rows.setValueAtIndex(rt, i, std::move(rowObjects[i]));\n            }\n            jsi::Object result(rt);\n            result.setProperty(rt, \"status\", jsi::Value(static_cast<int>(TURSO_IO)));\n            result.setProperty(rt, \"rows\", std::move(rows));\n            return result;\n        }\n\n        if (status != TURSO_ROW) {\n            throwError(rt, error);\n        }\n\n        // Read all columns for this row\n        jsi::Object row(rt);\n        for (int64_t i = 0; i < colCount; ++i) {\n            size_t idx = static_cast<size_t>(i);\n            turso_type_t kind = turso_statement_row_value_kind(stmt_, idx);\n\n            switch (kind) {\n                case TURSO_TYPE_INTEGER: {\n                    int64_t val = turso_statement_row_value_int(stmt_, idx);\n                    row.setProperty(rt, colNames[i].c_str(), jsi::Value(static_cast<double>(val)));\n                    break;\n                }\n                case TURSO_TYPE_REAL: {\n                    double val = turso_statement_row_value_double(stmt_, idx);\n                    row.setProperty(rt, colNames[i].c_str(), jsi::Value(val));\n                    break;\n                }\n                case TURSO_TYPE_TEXT: {\n                    const char* ptr = turso_statement_row_value_bytes_ptr(stmt_, idx);\n                    int64_t len = turso_statement_row_value_bytes_count(stmt_, idx);\n                    if (ptr && len >= 0) {\n                        row.setProperty(rt, colNames[i].c_str(),\n                            jsi::String::createFromUtf8(rt, reinterpret_cast<const uint8_t*>(ptr), static_cast<size_t>(len)));\n                    } else {\n                        row.setProperty(rt, colNames[i].c_str(), jsi::String::createFromUtf8(rt, \"\"));\n                    }\n                    break;\n                }\n                case TURSO_TYPE_BLOB: {\n                    const char* ptr = turso_statement_row_value_bytes_ptr(stmt_, idx);\n                    int64_t len = turso_statement_row_value_bytes_count(stmt_, idx);\n                    size_t blobLen = (len > 0) ? static_cast<size_t>(len) : 0;\n                    jsi::Function arrayBufferCtor = rt.global().getPropertyAsFunction(rt, \"ArrayBuffer\");\n                    jsi::Object arrayBuffer = arrayBufferCtor.callAsConstructor(rt, static_cast<int>(blobLen)).asObject(rt);\n                    if (ptr && blobLen > 0) {\n                        jsi::ArrayBuffer buf = arrayBuffer.getArrayBuffer(rt);\n                        memcpy(buf.data(rt), ptr, blobLen);\n                    }\n                    row.setProperty(rt, colNames[i].c_str(), std::move(arrayBuffer));\n                    break;\n                }\n                case TURSO_TYPE_NULL:\n                default:\n                    row.setProperty(rt, colNames[i].c_str(), jsi::Value::null());\n                    break;\n            }\n        }\n\n        rowObjects.push_back(std::move(row));\n    }\n\n    // Build final array\n    jsi::Array rows(rt, rowObjects.size());\n    for (size_t i = 0; i < rowObjects.size(); ++i) {\n        rows.setValueAtIndex(rt, i, std::move(rowObjects[i]));\n    }\n\n    jsi::Object result(rt);\n    result.setProperty(rt, \"status\", jsi::Value(static_cast<int>(TURSO_DONE)));\n    result.setProperty(rt, \"rows\", std::move(rows));\n    return result;\n}\n\n} // namespace turso\n"
  },
  {
    "path": "bindings/react-native/cpp/TursoStatementHostObject.h",
    "content": "#pragma once\n\n#include <jsi/jsi.h>\n#include <memory>\n#include <string>\n\n// Forward declarations for Turso C API types\nextern \"C\" {\n    struct turso_statement;\n    typedef struct turso_statement turso_statement_t;\n}\n\nnamespace turso {\n\nusing namespace facebook;\n\n/**\n * TursoStatementHostObject wraps turso_statement_t* (core SDK-KIT type for prepared statement).\n * This is a THIN wrapper - 1:1 mapping of SDK-KIT C API with NO logic.\n * All logic belongs in TypeScript or Rust, not here.\n */\nclass TursoStatementHostObject : public jsi::HostObject {\npublic:\n    TursoStatementHostObject(turso_statement_t* stmt) : stmt_(stmt) {}\n    ~TursoStatementHostObject();\n\n    // JSI HostObject interface\n    jsi::Value get(jsi::Runtime &rt, const jsi::PropNameID &name) override;\n    void set(jsi::Runtime &rt, const jsi::PropNameID &name, const jsi::Value &value) override;\n    std::vector<jsi::PropNameID> getPropertyNames(jsi::Runtime &rt) override;\n\n    // Direct access to wrapped pointer (for internal use)\n    turso_statement_t* getStatement() const { return stmt_; }\n\nprivate:\n    turso_statement_t* stmt_ = nullptr;\n\n    // Helper to throw JS errors\n    void throwError(jsi::Runtime &rt, const char *error);\n\n    // 1:1 C API mapping methods (NO logic - just calls through to C API)\n    jsi::Value bindPositionalNull(jsi::Runtime &rt, const jsi::Value *args, size_t count);\n    jsi::Value bindPositionalInt(jsi::Runtime &rt, const jsi::Value *args, size_t count);\n    jsi::Value bindPositionalDouble(jsi::Runtime &rt, const jsi::Value *args, size_t count);\n    jsi::Value bindPositionalBlob(jsi::Runtime &rt, const jsi::Value *args, size_t count);\n    jsi::Value bindPositionalText(jsi::Runtime &rt, const jsi::Value *args, size_t count);\n    jsi::Value execute(jsi::Runtime &rt);\n    jsi::Value step(jsi::Runtime &rt);\n    jsi::Value runIo(jsi::Runtime &rt);\n    jsi::Value reset(jsi::Runtime &rt);\n    jsi::Value finalize(jsi::Runtime &rt);\n    jsi::Value nChange(jsi::Runtime &rt);\n    jsi::Value columnCount(jsi::Runtime &rt);\n    jsi::Value columnName(jsi::Runtime &rt, const jsi::Value *args, size_t count);\n    jsi::Value rowValueKind(jsi::Runtime &rt, const jsi::Value *args, size_t count);\n    jsi::Value rowValueBytesCount(jsi::Runtime &rt, const jsi::Value *args, size_t count);\n    jsi::Value rowValueBytesPtr(jsi::Runtime &rt, const jsi::Value *args, size_t count);\n    jsi::Value rowValueText(jsi::Runtime &rt, const jsi::Value *args, size_t count);\n    jsi::Value rowValueInt(jsi::Runtime &rt, const jsi::Value *args, size_t count);\n    jsi::Value rowValueDouble(jsi::Runtime &rt, const jsi::Value *args, size_t count);\n    jsi::Value namedPosition(jsi::Runtime &rt, const jsi::Value *args, size_t count);\n    jsi::Value parametersCount(jsi::Runtime &rt);\n    jsi::Value getAllRows(jsi::Runtime &rt);\n};\n\n} // namespace turso\n"
  },
  {
    "path": "bindings/react-native/cpp/TursoSyncChangesHostObject.cpp",
    "content": "#include \"TursoSyncChangesHostObject.h\"\n\nextern \"C\" {\n#include <turso_sync.h>\n}\n\nnamespace turso {\n\nTursoSyncChangesHostObject::~TursoSyncChangesHostObject() {\n    // Only deinit if not consumed (ownership not transferred to applyChanges)\n    if (changes_ && !consumed_) {\n        turso_sync_changes_deinit(changes_);\n        changes_ = nullptr;\n    }\n}\n\nvoid TursoSyncChangesHostObject::throwError(jsi::Runtime &rt, const char *error) {\n    throw jsi::JSError(rt, error ? error : \"Unknown error\");\n}\n\njsi::Value TursoSyncChangesHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) {\n    auto propName = name.utf8(rt);\n\n    // Currently, turso_sync_changes_t is mostly opaque in the C API\n    // No methods exposed yet (like isEmpty)\n    // This object is primarily meant to be passed to applyChanges()\n\n    // If the C API adds methods in the future, they can be added here\n\n    return jsi::Value::undefined();\n}\n\nvoid TursoSyncChangesHostObject::set(jsi::Runtime &rt, const jsi::PropNameID &name, const jsi::Value &value) {\n    // Read-only object\n}\n\nstd::vector<jsi::PropNameID> TursoSyncChangesHostObject::getPropertyNames(jsi::Runtime &rt) {\n    return {};\n}\n\n} // namespace turso\n"
  },
  {
    "path": "bindings/react-native/cpp/TursoSyncChangesHostObject.h",
    "content": "#pragma once\n\n#include <jsi/jsi.h>\n#include <memory>\n#include <string>\n\n// Forward declarations for Turso C API types\nextern \"C\" {\n    struct turso_sync_changes;\n    typedef struct turso_sync_changes turso_sync_changes_t;\n}\n\nnamespace turso {\n\nusing namespace facebook;\n\n/**\n * TursoSyncChangesHostObject wraps turso_sync_changes_t* (changes fetched from remote).\n * This is a THIN wrapper - 1:1 mapping of SDK-KIT C API with NO logic.\n * All logic belongs in TypeScript or Rust, not here.\n *\n * IMPORTANT: This object can be consumed by applyChanges(), after which it must NOT be used.\n */\nclass TursoSyncChangesHostObject : public jsi::HostObject {\npublic:\n    TursoSyncChangesHostObject(turso_sync_changes_t* changes) : changes_(changes), consumed_(false) {}\n    ~TursoSyncChangesHostObject();\n\n    // JSI HostObject interface\n    jsi::Value get(jsi::Runtime &rt, const jsi::PropNameID &name) override;\n    void set(jsi::Runtime &rt, const jsi::PropNameID &name, const jsi::Value &value) override;\n    std::vector<jsi::PropNameID> getPropertyNames(jsi::Runtime &rt) override;\n\n    // Direct access to wrapped pointer (for internal use)\n    turso_sync_changes_t* getChanges() const { return changes_; }\n\n    // Mark this object as consumed (ownership transferred to applyChanges)\n    void markConsumed() { consumed_ = true; }\n\nprivate:\n    turso_sync_changes_t* changes_ = nullptr;\n    bool consumed_ = false;  // If true, changes ownership was transferred\n\n    // Helper to throw JS errors\n    void throwError(jsi::Runtime &rt, const char *error);\n\n    // 1:1 C API mapping methods (NO logic - just calls through to C API)\n    // Currently, there are no operations on turso_sync_changes_t other than passing it to applyChanges\n    // The C API doesn't expose methods like isEmpty() yet, so this object is mostly opaque\n};\n\n} // namespace turso\n"
  },
  {
    "path": "bindings/react-native/cpp/TursoSyncDatabaseHostObject.cpp",
    "content": "#include \"TursoSyncDatabaseHostObject.h\"\n#include \"TursoSyncOperationHostObject.h\"\n#include \"TursoSyncIoItemHostObject.h\"\n#include \"TursoSyncChangesHostObject.h\"\n\nextern \"C\" {\n#include <turso_sync.h>\n}\n\nnamespace turso {\n\nTursoSyncDatabaseHostObject::~TursoSyncDatabaseHostObject() {\n    if (db_) {\n        turso_sync_database_deinit(db_);\n        db_ = nullptr;\n    }\n}\n\nvoid TursoSyncDatabaseHostObject::throwError(jsi::Runtime &rt, const char *error) {\n    throw jsi::JSError(rt, error ? error : \"Unknown error\");\n}\n\njsi::Value TursoSyncDatabaseHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) {\n    auto propName = name.utf8(rt);\n\n    if (propName == \"open\") {\n        return jsi::Function::createFromHostFunction(\n            rt, name, 0,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value {\n                return this->open(rt);\n            }\n        );\n    }\n\n    if (propName == \"create\") {\n        return jsi::Function::createFromHostFunction(\n            rt, name, 0,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value {\n                return this->create(rt);\n            }\n        );\n    }\n\n    if (propName == \"connect\") {\n        return jsi::Function::createFromHostFunction(\n            rt, name, 0,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value {\n                return this->connect(rt);\n            }\n        );\n    }\n\n    if (propName == \"stats\") {\n        return jsi::Function::createFromHostFunction(\n            rt, name, 0,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value {\n                return this->stats(rt);\n            }\n        );\n    }\n\n    if (propName == \"checkpoint\") {\n        return jsi::Function::createFromHostFunction(\n            rt, name, 0,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value {\n                return this->checkpoint(rt);\n            }\n        );\n    }\n\n    if (propName == \"pushChanges\") {\n        return jsi::Function::createFromHostFunction(\n            rt, name, 0,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value {\n                return this->pushChanges(rt);\n            }\n        );\n    }\n\n    if (propName == \"waitChanges\") {\n        return jsi::Function::createFromHostFunction(\n            rt, name, 0,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value {\n                return this->waitChanges(rt);\n            }\n        );\n    }\n\n    if (propName == \"applyChanges\") {\n        return jsi::Function::createFromHostFunction(\n            rt, name, 1,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value {\n                return this->applyChanges(rt, args, count);\n            }\n        );\n    }\n\n    if (propName == \"ioTakeItem\") {\n        return jsi::Function::createFromHostFunction(\n            rt, name, 0,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value {\n                return this->ioTakeItem(rt);\n            }\n        );\n    }\n\n    if (propName == \"ioStepCallbacks\") {\n        return jsi::Function::createFromHostFunction(\n            rt, name, 0,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value {\n                return this->ioStepCallbacks(rt);\n            }\n        );\n    }\n\n    if (propName == \"close\") {\n        return jsi::Function::createFromHostFunction(\n            rt, name, 0,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value {\n                return this->close(rt);\n            }\n        );\n    }\n\n    return jsi::Value::undefined();\n}\n\nvoid TursoSyncDatabaseHostObject::set(jsi::Runtime &rt, const jsi::PropNameID &name, const jsi::Value &value) {\n    // Read-only object\n}\n\nstd::vector<jsi::PropNameID> TursoSyncDatabaseHostObject::getPropertyNames(jsi::Runtime &rt) {\n    std::vector<jsi::PropNameID> props;\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"open\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"create\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"connect\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"stats\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"checkpoint\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"pushChanges\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"waitChanges\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"applyChanges\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"ioTakeItem\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"ioStepCallbacks\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"close\"));\n    return props;\n}\n\n// 1:1 C API mapping - NO logic, just calls through to C API\n\njsi::Value TursoSyncDatabaseHostObject::open(jsi::Runtime &rt) {\n    const turso_sync_operation_t* operation = nullptr;\n    const char* error = nullptr;\n    turso_status_code_t status = turso_sync_database_open(db_, &operation, &error);\n\n    if (status != TURSO_OK) {\n        throwError(rt, error);\n    }\n\n    // Wrap operation in TursoSyncOperationHostObject\n    // Cast away const since we're transferring ownership\n    auto operationObj = std::make_shared<TursoSyncOperationHostObject>(\n        const_cast<turso_sync_operation_t*>(operation)\n    );\n    return jsi::Object::createFromHostObject(rt, operationObj);\n}\n\njsi::Value TursoSyncDatabaseHostObject::create(jsi::Runtime &rt) {\n    const turso_sync_operation_t* operation = nullptr;\n    const char* error = nullptr;\n    turso_status_code_t status = turso_sync_database_create(db_, &operation, &error);\n\n    if (status != TURSO_OK) {\n        throwError(rt, error);\n    }\n\n    auto operationObj = std::make_shared<TursoSyncOperationHostObject>(\n        const_cast<turso_sync_operation_t*>(operation)\n    );\n    return jsi::Object::createFromHostObject(rt, operationObj);\n}\n\njsi::Value TursoSyncDatabaseHostObject::connect(jsi::Runtime &rt) {\n    const turso_sync_operation_t* operation = nullptr;\n    const char* error = nullptr;\n    turso_status_code_t status = turso_sync_database_connect(db_, &operation, &error);\n\n    if (status != TURSO_OK) {\n        throwError(rt, error);\n    }\n\n    auto operationObj = std::make_shared<TursoSyncOperationHostObject>(\n        const_cast<turso_sync_operation_t*>(operation)\n    );\n    return jsi::Object::createFromHostObject(rt, operationObj);\n}\n\njsi::Value TursoSyncDatabaseHostObject::stats(jsi::Runtime &rt) {\n    const turso_sync_operation_t* operation = nullptr;\n    const char* error = nullptr;\n    turso_status_code_t status = turso_sync_database_stats(db_, &operation, &error);\n\n    if (status != TURSO_OK) {\n        throwError(rt, error);\n    }\n\n    auto operationObj = std::make_shared<TursoSyncOperationHostObject>(\n        const_cast<turso_sync_operation_t*>(operation)\n    );\n    return jsi::Object::createFromHostObject(rt, operationObj);\n}\n\njsi::Value TursoSyncDatabaseHostObject::checkpoint(jsi::Runtime &rt) {\n    const turso_sync_operation_t* operation = nullptr;\n    const char* error = nullptr;\n    turso_status_code_t status = turso_sync_database_checkpoint(db_, &operation, &error);\n\n    if (status != TURSO_OK) {\n        throwError(rt, error);\n    }\n\n    auto operationObj = std::make_shared<TursoSyncOperationHostObject>(\n        const_cast<turso_sync_operation_t*>(operation)\n    );\n    return jsi::Object::createFromHostObject(rt, operationObj);\n}\n\njsi::Value TursoSyncDatabaseHostObject::pushChanges(jsi::Runtime &rt) {\n    const turso_sync_operation_t* operation = nullptr;\n    const char* error = nullptr;\n    turso_status_code_t status = turso_sync_database_push_changes(db_, &operation, &error);\n\n    if (status != TURSO_OK) {\n        throwError(rt, error);\n    }\n\n    auto operationObj = std::make_shared<TursoSyncOperationHostObject>(\n        const_cast<turso_sync_operation_t*>(operation)\n    );\n    return jsi::Object::createFromHostObject(rt, operationObj);\n}\n\njsi::Value TursoSyncDatabaseHostObject::waitChanges(jsi::Runtime &rt) {\n    const turso_sync_operation_t* operation = nullptr;\n    const char* error = nullptr;\n    turso_status_code_t status = turso_sync_database_wait_changes(db_, &operation, &error);\n\n    if (status != TURSO_OK) {\n        throwError(rt, error);\n    }\n\n    auto operationObj = std::make_shared<TursoSyncOperationHostObject>(\n        const_cast<turso_sync_operation_t*>(operation)\n    );\n    return jsi::Object::createFromHostObject(rt, operationObj);\n}\n\njsi::Value TursoSyncDatabaseHostObject::applyChanges(jsi::Runtime &rt, const jsi::Value *args, size_t count) {\n    if (count < 1 || !args[0].isObject()) {\n        throw jsi::JSError(rt, \"applyChanges: expected TursoSyncChanges object\");\n    }\n\n    // Extract the TursoSyncChangesHostObject from the JSI object\n    auto changesHostObj = std::dynamic_pointer_cast<TursoSyncChangesHostObject>(\n        args[0].asObject(rt).asHostObject(rt)\n    );\n\n    if (!changesHostObj) {\n        throw jsi::JSError(rt, \"applyChanges: invalid TursoSyncChanges object\");\n    }\n\n    const turso_sync_changes_t* changes = changesHostObj->getChanges();\n    const turso_sync_operation_t* operation = nullptr;\n    const char* error = nullptr;\n\n    turso_status_code_t status = turso_sync_database_apply_changes(db_, changes, &operation, &error);\n\n    // Note: changes ownership is transferred to turso_sync_database_apply_changes\n    // Mark the changes object as consumed so it won't try to deinit\n    changesHostObj->markConsumed();\n\n    if (status != TURSO_OK) {\n        throwError(rt, error);\n    }\n\n    auto operationObj = std::make_shared<TursoSyncOperationHostObject>(\n        const_cast<turso_sync_operation_t*>(operation)\n    );\n    return jsi::Object::createFromHostObject(rt, operationObj);\n}\n\njsi::Value TursoSyncDatabaseHostObject::ioTakeItem(jsi::Runtime &rt) {\n    const turso_sync_io_item_t* item = nullptr;\n    const char* error = nullptr;\n    turso_status_code_t status = turso_sync_database_io_take_item(db_, &item, &error);\n\n    if (status != TURSO_OK) {\n        throwError(rt, error);\n    }\n\n    // If no item available, return null\n    if (!item) {\n        return jsi::Value::null();\n    }\n\n    auto itemObj = std::make_shared<TursoSyncIoItemHostObject>(\n        const_cast<turso_sync_io_item_t*>(item)\n    );\n    return jsi::Object::createFromHostObject(rt, itemObj);\n}\n\njsi::Value TursoSyncDatabaseHostObject::ioStepCallbacks(jsi::Runtime &rt) {\n    const char* error = nullptr;\n    turso_status_code_t status = turso_sync_database_io_step_callbacks(db_, &error);\n\n    if (status != TURSO_OK) {\n        throwError(rt, error);\n    }\n\n    return jsi::Value::undefined();\n}\n\njsi::Value TursoSyncDatabaseHostObject::close(jsi::Runtime &rt) {\n    // turso_sync_database_close doesn't exist in the C API\n    // Closing happens in destructor via turso_sync_database_deinit\n    return jsi::Value::undefined();\n}\n\n} // namespace turso\n"
  },
  {
    "path": "bindings/react-native/cpp/TursoSyncDatabaseHostObject.h",
    "content": "#pragma once\n\n#include <jsi/jsi.h>\n#include <memory>\n#include <string>\n\n// Forward declarations for Turso C API types\nextern \"C\" {\n    struct turso_sync_database;\n    struct turso_sync_operation;\n    struct turso_sync_io_item;\n    struct turso_sync_changes;\n    typedef struct turso_sync_database turso_sync_database_t;\n    typedef struct turso_sync_operation turso_sync_operation_t;\n    typedef struct turso_sync_io_item turso_sync_io_item_t;\n    typedef struct turso_sync_changes turso_sync_changes_t;\n}\n\nnamespace turso {\n\nusing namespace facebook;\n\n/**\n * TursoSyncDatabaseHostObject wraps turso_sync_database_t* (sync SDK-KIT type for embedded replica).\n * This is a THIN wrapper - 1:1 mapping of SDK-KIT C API with NO logic.\n * All logic belongs in TypeScript or Rust, not here.\n */\nclass TursoSyncDatabaseHostObject : public jsi::HostObject {\npublic:\n    TursoSyncDatabaseHostObject(turso_sync_database_t* db) : db_(db) {}\n    ~TursoSyncDatabaseHostObject();\n\n    // JSI HostObject interface\n    jsi::Value get(jsi::Runtime &rt, const jsi::PropNameID &name) override;\n    void set(jsi::Runtime &rt, const jsi::PropNameID &name, const jsi::Value &value) override;\n    std::vector<jsi::PropNameID> getPropertyNames(jsi::Runtime &rt) override;\n\n    // Direct access to wrapped pointer (for internal use)\n    turso_sync_database_t* getSyncDatabase() const { return db_; }\n\nprivate:\n    turso_sync_database_t* db_ = nullptr;\n\n    // Helper to throw JS errors\n    void throwError(jsi::Runtime &rt, const char *error);\n\n    // 1:1 C API mapping methods (NO logic - just calls through to C API)\n    jsi::Value open(jsi::Runtime &rt);\n    jsi::Value create(jsi::Runtime &rt);\n    jsi::Value connect(jsi::Runtime &rt);\n    jsi::Value stats(jsi::Runtime &rt);\n    jsi::Value checkpoint(jsi::Runtime &rt);\n    jsi::Value pushChanges(jsi::Runtime &rt);\n    jsi::Value waitChanges(jsi::Runtime &rt);\n    jsi::Value applyChanges(jsi::Runtime &rt, const jsi::Value *args, size_t count);\n    jsi::Value ioTakeItem(jsi::Runtime &rt);\n    jsi::Value ioStepCallbacks(jsi::Runtime &rt);\n    jsi::Value close(jsi::Runtime &rt);\n};\n\n} // namespace turso\n"
  },
  {
    "path": "bindings/react-native/cpp/TursoSyncIoItemHostObject.cpp",
    "content": "#include \"TursoSyncIoItemHostObject.h\"\n\nextern \"C\" {\n#include <turso_sync.h>\n}\n\nnamespace turso {\n\nTursoSyncIoItemHostObject::~TursoSyncIoItemHostObject() {\n    if (item_) {\n        turso_sync_database_io_item_deinit(item_);\n        item_ = nullptr;\n    }\n}\n\nvoid TursoSyncIoItemHostObject::throwError(jsi::Runtime &rt, const char *error) {\n    throw jsi::JSError(rt, error ? error : \"Unknown error\");\n}\n\njsi::Value TursoSyncIoItemHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) {\n    auto propName = name.utf8(rt);\n\n    if (propName == \"getKind\") {\n        return jsi::Function::createFromHostFunction(\n            rt, name, 0,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value {\n                return this->getKind(rt);\n            }\n        );\n    }\n\n    if (propName == \"getHttpRequest\") {\n        return jsi::Function::createFromHostFunction(\n            rt, name, 0,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value {\n                return this->getHttpRequest(rt);\n            }\n        );\n    }\n\n    if (propName == \"getFullReadPath\") {\n        return jsi::Function::createFromHostFunction(\n            rt, name, 0,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value {\n                return this->getFullReadPath(rt);\n            }\n        );\n    }\n\n    if (propName == \"getFullWriteRequest\") {\n        return jsi::Function::createFromHostFunction(\n            rt, name, 0,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value {\n                return this->getFullWriteRequest(rt);\n            }\n        );\n    }\n\n    if (propName == \"poison\") {\n        return jsi::Function::createFromHostFunction(\n            rt, name, 1,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value {\n                return this->poison(rt, args, count);\n            }\n        );\n    }\n\n    if (propName == \"setStatus\") {\n        return jsi::Function::createFromHostFunction(\n            rt, name, 1,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value {\n                return this->setStatus(rt, args, count);\n            }\n        );\n    }\n\n    if (propName == \"pushBuffer\") {\n        return jsi::Function::createFromHostFunction(\n            rt, name, 1,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value {\n                return this->pushBuffer(rt, args, count);\n            }\n        );\n    }\n\n    if (propName == \"done\") {\n        return jsi::Function::createFromHostFunction(\n            rt, name, 0,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value {\n                return this->done(rt);\n            }\n        );\n    }\n\n    return jsi::Value::undefined();\n}\n\nvoid TursoSyncIoItemHostObject::set(jsi::Runtime &rt, const jsi::PropNameID &name, const jsi::Value &value) {\n    // Read-only object\n}\n\nstd::vector<jsi::PropNameID> TursoSyncIoItemHostObject::getPropertyNames(jsi::Runtime &rt) {\n    std::vector<jsi::PropNameID> props;\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"getKind\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"getHttpRequest\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"getFullReadPath\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"getFullWriteRequest\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"poison\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"setStatus\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"pushBuffer\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"done\"));\n    return props;\n}\n\n// 1:1 C API mapping - NO logic, just calls through to C API\n\njsi::Value TursoSyncIoItemHostObject::getKind(jsi::Runtime &rt) {\n    turso_sync_io_request_type_t kind = turso_sync_database_io_request_kind(item_);\n\n    // Return string representation\n    switch (kind) {\n        case TURSO_SYNC_IO_HTTP:\n            return jsi::String::createFromAscii(rt, \"HTTP\");\n        case TURSO_SYNC_IO_FULL_READ:\n            return jsi::String::createFromAscii(rt, \"FULL_READ\");\n        case TURSO_SYNC_IO_FULL_WRITE:\n            return jsi::String::createFromAscii(rt, \"FULL_WRITE\");\n        default:\n            return jsi::String::createFromAscii(rt, \"NONE\");\n    }\n}\n\njsi::Value TursoSyncIoItemHostObject::getHttpRequest(jsi::Runtime &rt) {\n    turso_sync_io_http_request_t request;\n    turso_status_code_t status = turso_sync_database_io_request_http(item_, &request);\n\n    if (status != TURSO_OK) {\n        throw jsi::JSError(rt, \"Failed to get HTTP request\");\n    }\n\n    jsi::Object result(rt);\n\n    // URL (may be null/empty)\n    if (request.url.ptr && request.url.len > 0) {\n        std::string url(static_cast<const char*>(request.url.ptr), request.url.len);\n        result.setProperty(rt, \"url\", jsi::String::createFromUtf8(rt, url));\n    } else {\n        result.setProperty(rt, \"url\", jsi::Value::null());\n    }\n\n    // Method\n    if (request.method.ptr && request.method.len > 0) {\n        std::string method(static_cast<const char*>(request.method.ptr), request.method.len);\n        result.setProperty(rt, \"method\", jsi::String::createFromUtf8(rt, method));\n    }\n\n    // Path\n    if (request.path.ptr && request.path.len > 0) {\n        std::string path(static_cast<const char*>(request.path.ptr), request.path.len);\n        result.setProperty(rt, \"path\", jsi::String::createFromUtf8(rt, path));\n    }\n\n    // Body (may be empty)\n    if (request.body.ptr && request.body.len > 0) {\n        // Create ArrayBuffer for body\n        jsi::Function arrayBufferCtor = rt.global().getPropertyAsFunction(rt, \"ArrayBuffer\");\n        jsi::Object arrayBuffer = arrayBufferCtor.callAsConstructor(rt, static_cast<int>(request.body.len)).asObject(rt);\n        jsi::ArrayBuffer buf = arrayBuffer.getArrayBuffer(rt);\n        memcpy(buf.data(rt), request.body.ptr, request.body.len);\n        result.setProperty(rt, \"body\", arrayBuffer);\n    } else {\n        result.setProperty(rt, \"body\", jsi::Value::null());\n    }\n\n    // Headers\n    jsi::Object headers(rt);\n    for (int32_t i = 0; i < request.headers; i++) {\n        turso_sync_io_http_header_t header;\n        turso_status_code_t header_status = turso_sync_database_io_request_http_header(item_, i, &header);\n\n        if (header_status == TURSO_OK && header.key.ptr && header.value.ptr) {\n            std::string key(static_cast<const char*>(header.key.ptr), header.key.len);\n            std::string value(static_cast<const char*>(header.value.ptr), header.value.len);\n            headers.setProperty(rt, key.c_str(), jsi::String::createFromUtf8(rt, value));\n        }\n    }\n    result.setProperty(rt, \"headers\", headers);\n\n    return result;\n}\n\njsi::Value TursoSyncIoItemHostObject::getFullReadPath(jsi::Runtime &rt) {\n    turso_sync_io_full_read_request_t request;\n    turso_status_code_t status = turso_sync_database_io_request_full_read(item_, &request);\n\n    if (status != TURSO_OK) {\n        throw jsi::JSError(rt, \"Failed to get full read request\");\n    }\n\n    if (request.path.ptr && request.path.len > 0) {\n        std::string path(static_cast<const char*>(request.path.ptr), request.path.len);\n        return jsi::String::createFromUtf8(rt, path);\n    }\n\n    return jsi::Value::null();\n}\n\njsi::Value TursoSyncIoItemHostObject::getFullWriteRequest(jsi::Runtime &rt) {\n    turso_sync_io_full_write_request_t request;\n    turso_status_code_t status = turso_sync_database_io_request_full_write(item_, &request);\n\n    if (status != TURSO_OK) {\n        throw jsi::JSError(rt, \"Failed to get full write request\");\n    }\n\n    jsi::Object result(rt);\n\n    // Path\n    if (request.path.ptr && request.path.len > 0) {\n        std::string path(static_cast<const char*>(request.path.ptr), request.path.len);\n        result.setProperty(rt, \"path\", jsi::String::createFromUtf8(rt, path));\n    }\n\n    // Content\n    if (request.content.ptr && request.content.len > 0) {\n        jsi::Function arrayBufferCtor = rt.global().getPropertyAsFunction(rt, \"ArrayBuffer\");\n        jsi::Object arrayBuffer = arrayBufferCtor.callAsConstructor(rt, static_cast<int>(request.content.len)).asObject(rt);\n        jsi::ArrayBuffer buf = arrayBuffer.getArrayBuffer(rt);\n        memcpy(buf.data(rt), request.content.ptr, request.content.len);\n        result.setProperty(rt, \"content\", arrayBuffer);\n    } else {\n        result.setProperty(rt, \"content\", jsi::Value::null());\n    }\n\n    return result;\n}\n\njsi::Value TursoSyncIoItemHostObject::poison(jsi::Runtime &rt, const jsi::Value *args, size_t count) {\n    if (count < 1 || !args[0].isString()) {\n        throw jsi::JSError(rt, \"poison: expected string argument (error message)\");\n    }\n\n    std::string error = args[0].asString(rt).utf8(rt);\n    turso_slice_ref_t error_slice;\n    error_slice.ptr = error.c_str();\n    error_slice.len = error.length();\n\n    turso_status_code_t status = turso_sync_database_io_poison(item_, &error_slice);\n\n    if (status != TURSO_OK) {\n        throw jsi::JSError(rt, \"Failed to poison IO item\");\n    }\n\n    return jsi::Value::undefined();\n}\n\njsi::Value TursoSyncIoItemHostObject::setStatus(jsi::Runtime &rt, const jsi::Value *args, size_t count) {\n    if (count < 1 || !args[0].isNumber()) {\n        throw jsi::JSError(rt, \"setStatus: expected number argument (status code)\");\n    }\n\n    int32_t status_code = static_cast<int32_t>(args[0].asNumber());\n    turso_status_code_t status = turso_sync_database_io_status(item_, status_code);\n\n    if (status != TURSO_OK) {\n        throw jsi::JSError(rt, \"Failed to set IO status\");\n    }\n\n    return jsi::Value::undefined();\n}\n\njsi::Value TursoSyncIoItemHostObject::pushBuffer(jsi::Runtime &rt, const jsi::Value *args, size_t count) {\n    if (count < 1 || !args[0].isObject()) {\n        throw jsi::JSError(rt, \"pushBuffer: expected ArrayBuffer argument\");\n    }\n\n    auto arrayBuffer = args[0].asObject(rt).getArrayBuffer(rt);\n    const char* data = reinterpret_cast<const char*>(arrayBuffer.data(rt));\n    size_t len = arrayBuffer.size(rt);\n\n    turso_slice_ref_t buffer_slice;\n    buffer_slice.ptr = data;\n    buffer_slice.len = len;\n\n    turso_status_code_t status = turso_sync_database_io_push_buffer(item_, &buffer_slice);\n\n    if (status != TURSO_OK) {\n        throw jsi::JSError(rt, \"Failed to push buffer to IO item\");\n    }\n\n    return jsi::Value::undefined();\n}\n\njsi::Value TursoSyncIoItemHostObject::done(jsi::Runtime &rt) {\n    turso_status_code_t status = turso_sync_database_io_done(item_);\n\n    if (status != TURSO_OK) {\n        throw jsi::JSError(rt, \"Failed to mark IO item as done\");\n    }\n\n    return jsi::Value::undefined();\n}\n\n} // namespace turso\n"
  },
  {
    "path": "bindings/react-native/cpp/TursoSyncIoItemHostObject.h",
    "content": "#pragma once\n\n#include <jsi/jsi.h>\n#include <memory>\n#include <string>\n\n// Forward declarations for Turso C API types\nextern \"C\" {\n    struct turso_sync_io_item;\n    typedef struct turso_sync_io_item turso_sync_io_item_t;\n}\n\nnamespace turso {\n\nusing namespace facebook;\n\n/**\n * TursoSyncIoItemHostObject wraps turso_sync_io_item_t* (IO queue item for fetch/fs operations).\n * This is a THIN wrapper - 1:1 mapping of SDK-KIT C API with NO logic.\n * All logic belongs in TypeScript or Rust, not here.\n */\nclass TursoSyncIoItemHostObject : public jsi::HostObject {\npublic:\n    TursoSyncIoItemHostObject(turso_sync_io_item_t* item) : item_(item) {}\n    ~TursoSyncIoItemHostObject();\n\n    // JSI HostObject interface\n    jsi::Value get(jsi::Runtime &rt, const jsi::PropNameID &name) override;\n    void set(jsi::Runtime &rt, const jsi::PropNameID &name, const jsi::Value &value) override;\n    std::vector<jsi::PropNameID> getPropertyNames(jsi::Runtime &rt) override;\n\n    // Direct access to wrapped pointer (for internal use)\n    turso_sync_io_item_t* getIoItem() const { return item_; }\n\nprivate:\n    turso_sync_io_item_t* item_ = nullptr;\n\n    // Helper to throw JS errors\n    void throwError(jsi::Runtime &rt, const char *error);\n\n    // 1:1 C API mapping methods (NO logic - just calls through to C API)\n    jsi::Value getKind(jsi::Runtime &rt);\n    jsi::Value getHttpRequest(jsi::Runtime &rt);\n    jsi::Value getFullReadPath(jsi::Runtime &rt);\n    jsi::Value getFullWriteRequest(jsi::Runtime &rt);\n    jsi::Value poison(jsi::Runtime &rt, const jsi::Value *args, size_t count);\n    jsi::Value setStatus(jsi::Runtime &rt, const jsi::Value *args, size_t count);\n    jsi::Value pushBuffer(jsi::Runtime &rt, const jsi::Value *args, size_t count);\n    jsi::Value done(jsi::Runtime &rt);\n};\n\n} // namespace turso\n"
  },
  {
    "path": "bindings/react-native/cpp/TursoSyncOperationHostObject.cpp",
    "content": "#include \"TursoSyncOperationHostObject.h\"\n#include \"TursoConnectionHostObject.h\"\n#include \"TursoSyncChangesHostObject.h\"\n\nextern \"C\" {\n#include <turso_sync.h>\n}\n\nnamespace turso {\n\nTursoSyncOperationHostObject::~TursoSyncOperationHostObject() {\n    if (op_) {\n        turso_sync_operation_deinit(op_);\n        op_ = nullptr;\n    }\n}\n\nvoid TursoSyncOperationHostObject::throwError(jsi::Runtime &rt, const char *error) {\n    throw jsi::JSError(rt, error ? error : \"Unknown error\");\n}\n\njsi::Value TursoSyncOperationHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) {\n    auto propName = name.utf8(rt);\n\n    if (propName == \"resume\") {\n        return jsi::Function::createFromHostFunction(\n            rt, name, 0,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value {\n                return this->resume(rt);\n            }\n        );\n    }\n\n    if (propName == \"resultKind\") {\n        return jsi::Function::createFromHostFunction(\n            rt, name, 0,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value {\n                return this->resultKind(rt);\n            }\n        );\n    }\n\n    if (propName == \"extractConnection\") {\n        return jsi::Function::createFromHostFunction(\n            rt, name, 0,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value {\n                return this->extractConnection(rt);\n            }\n        );\n    }\n\n    if (propName == \"extractChanges\") {\n        return jsi::Function::createFromHostFunction(\n            rt, name, 0,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value {\n                return this->extractChanges(rt);\n            }\n        );\n    }\n\n    if (propName == \"extractStats\") {\n        return jsi::Function::createFromHostFunction(\n            rt, name, 0,\n            [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value {\n                return this->extractStats(rt);\n            }\n        );\n    }\n\n    return jsi::Value::undefined();\n}\n\nvoid TursoSyncOperationHostObject::set(jsi::Runtime &rt, const jsi::PropNameID &name, const jsi::Value &value) {\n    // Read-only object\n}\n\nstd::vector<jsi::PropNameID> TursoSyncOperationHostObject::getPropertyNames(jsi::Runtime &rt) {\n    std::vector<jsi::PropNameID> props;\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"resume\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"resultKind\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"extractConnection\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"extractChanges\"));\n    props.emplace_back(jsi::PropNameID::forAscii(rt, \"extractStats\"));\n    return props;\n}\n\n// 1:1 C API mapping - NO logic, just calls through to C API\n\njsi::Value TursoSyncOperationHostObject::resume(jsi::Runtime &rt) {\n    const char* error = nullptr;\n    turso_status_code_t status = turso_sync_operation_resume(op_, &error);\n\n    if (status != TURSO_OK && status != TURSO_DONE && status != TURSO_IO) {\n        throwError(rt, error);\n    }\n\n    // Return status code (TURSO_DONE, TURSO_IO, etc.)\n    return jsi::Value(static_cast<int>(status));\n}\n\njsi::Value TursoSyncOperationHostObject::resultKind(jsi::Runtime &rt) {\n    turso_sync_operation_result_type_t kind = turso_sync_operation_result_kind(op_);\n    return jsi::Value(static_cast<int>(kind));\n}\n\njsi::Value TursoSyncOperationHostObject::extractConnection(jsi::Runtime &rt) {\n    const turso_connection_t* connection = nullptr;\n    turso_status_code_t status = turso_sync_operation_result_extract_connection(op_, &connection);\n\n    if (status != TURSO_OK) {\n        throw jsi::JSError(rt, \"Failed to extract connection from operation result\");\n    }\n\n    auto connectionObj = std::make_shared<TursoConnectionHostObject>(\n        const_cast<turso_connection_t*>(connection)\n    );\n    return jsi::Object::createFromHostObject(rt, connectionObj);\n}\n\njsi::Value TursoSyncOperationHostObject::extractChanges(jsi::Runtime &rt) {\n    const turso_sync_changes_t* changes = nullptr;\n    turso_status_code_t status = turso_sync_operation_result_extract_changes(op_, &changes);\n\n    if (status != TURSO_OK) {\n        throw jsi::JSError(rt, \"Failed to extract changes from operation result\");\n    }\n\n    // If no changes, return null\n    if (!changes) {\n        return jsi::Value::null();\n    }\n\n    auto changesObj = std::make_shared<TursoSyncChangesHostObject>(\n        const_cast<turso_sync_changes_t*>(changes)\n    );\n    return jsi::Object::createFromHostObject(rt, changesObj);\n}\n\njsi::Value TursoSyncOperationHostObject::extractStats(jsi::Runtime &rt) {\n    turso_sync_stats_t stats;\n    turso_status_code_t status = turso_sync_operation_result_extract_stats(op_, &stats);\n\n    if (status != TURSO_OK) {\n        throw jsi::JSError(rt, \"Failed to extract stats from operation result\");\n    }\n\n    // Convert stats to JS object\n    jsi::Object result(rt);\n    result.setProperty(rt, \"cdcOperations\", jsi::Value(static_cast<double>(stats.cdc_operations)));\n    result.setProperty(rt, \"mainWalSize\", jsi::Value(static_cast<double>(stats.main_wal_size)));\n    result.setProperty(rt, \"revertWalSize\", jsi::Value(static_cast<double>(stats.revert_wal_size)));\n    result.setProperty(rt, \"lastPullUnixTime\", jsi::Value(static_cast<double>(stats.last_pull_unix_time)));\n    result.setProperty(rt, \"lastPushUnixTime\", jsi::Value(static_cast<double>(stats.last_push_unix_time)));\n    result.setProperty(rt, \"networkSentBytes\", jsi::Value(static_cast<double>(stats.network_sent_bytes)));\n    result.setProperty(rt, \"networkReceivedBytes\", jsi::Value(static_cast<double>(stats.network_received_bytes)));\n\n    // Convert revision slice to string (if available)\n    if (stats.revision.ptr && stats.revision.len > 0) {\n        std::string revision(static_cast<const char*>(stats.revision.ptr), stats.revision.len);\n        result.setProperty(rt, \"revision\", jsi::String::createFromUtf8(rt, revision));\n    } else {\n        result.setProperty(rt, \"revision\", jsi::Value::null());\n    }\n\n    return result;\n}\n\n} // namespace turso\n"
  },
  {
    "path": "bindings/react-native/cpp/TursoSyncOperationHostObject.h",
    "content": "#pragma once\n\n#include <jsi/jsi.h>\n#include <memory>\n#include <string>\n\n// Forward declarations for Turso C API types\nextern \"C\" {\n    struct turso_sync_operation;\n    struct turso_connection;\n    struct turso_sync_changes;\n    typedef struct turso_sync_operation turso_sync_operation_t;\n    typedef struct turso_connection turso_connection_t;\n    typedef struct turso_sync_changes turso_sync_changes_t;\n}\n\nnamespace turso {\n\nusing namespace facebook;\n\n/**\n * TursoSyncOperationHostObject wraps turso_sync_operation_t* (async operation handle).\n * This is a THIN wrapper - 1:1 mapping of SDK-KIT C API with NO logic.\n * All logic belongs in TypeScript or Rust, not here.\n */\nclass TursoSyncOperationHostObject : public jsi::HostObject {\npublic:\n    TursoSyncOperationHostObject(turso_sync_operation_t* op) : op_(op) {}\n    ~TursoSyncOperationHostObject();\n\n    // JSI HostObject interface\n    jsi::Value get(jsi::Runtime &rt, const jsi::PropNameID &name) override;\n    void set(jsi::Runtime &rt, const jsi::PropNameID &name, const jsi::Value &value) override;\n    std::vector<jsi::PropNameID> getPropertyNames(jsi::Runtime &rt) override;\n\n    // Direct access to wrapped pointer (for internal use)\n    turso_sync_operation_t* getOperation() const { return op_; }\n\nprivate:\n    turso_sync_operation_t* op_ = nullptr;\n\n    // Helper to throw JS errors\n    void throwError(jsi::Runtime &rt, const char *error);\n\n    // 1:1 C API mapping methods (NO logic - just calls through to C API)\n    jsi::Value resume(jsi::Runtime &rt);\n    jsi::Value resultKind(jsi::Runtime &rt);\n    jsi::Value extractConnection(jsi::Runtime &rt);\n    jsi::Value extractChanges(jsi::Runtime &rt);\n    jsi::Value extractStats(jsi::Runtime &rt);\n};\n\n} // namespace turso\n"
  },
  {
    "path": "bindings/react-native/ios/TursoModule.h",
    "content": "#import <Foundation/Foundation.h>\n#import <React/RCTBridgeModule.h>\n\n@interface Turso : NSObject <RCTBridgeModule>\n\n@property (nonatomic, assign) BOOL setBridgeOnMainQueue;\n\n@end\n"
  },
  {
    "path": "bindings/react-native/ios/TursoModule.mm",
    "content": "#import \"TursoModule.h\"\n\n#import <React/RCTBridge+Private.h>\n#import <React/RCTUtils.h>\n#import <ReactCommon/RCTTurboModule.h>\n#import <jsi/jsi.h>\n\n#import \"TursoHostObject.h\"\n\n@implementation Turso\n\n@synthesize bridge = _bridge;\n\nRCT_EXPORT_MODULE()\n\n+ (BOOL)requiresMainQueueSetup {\n    return YES;\n}\n\n- (NSDictionary *)constantsToExport {\n    // Get documents directory\n    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);\n    NSString *documentsPath = [paths firstObject];\n\n    // Get library directory\n    NSArray *libraryPaths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);\n    NSString *libraryPath = [libraryPaths firstObject];\n\n    // Check for app group (for sharing data between app and extensions)\n    NSString *appGroup = [[NSBundle mainBundle] objectForInfoDictionaryKey:@\"Turso_AppGroup\"];\n    if (appGroup) {\n        NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:appGroup];\n        if (containerURL) {\n            documentsPath = [containerURL path];\n        }\n    }\n\n    return @{\n        @\"IOS_DOCUMENT_PATH\": documentsPath ?: [NSNull null],\n        @\"IOS_LIBRARY_PATH\": libraryPath ?: [NSNull null],\n        @\"ANDROID_DATABASE_PATH\": [NSNull null],\n        @\"ANDROID_FILES_PATH\": [NSNull null],\n        @\"ANDROID_EXTERNAL_FILES_PATH\": [NSNull null]\n    };\n}\n\n- (void)setBridge:(RCTBridge *)bridge {\n    _bridge = bridge;\n\n    RCTCxxBridge *cxxBridge = (RCTCxxBridge *)self.bridge;\n    if (!cxxBridge.runtime) {\n        return;\n    }\n\n    [self installLibrary];\n}\n\n- (void)installLibrary {\n    RCTCxxBridge *cxxBridge = (RCTCxxBridge *)self.bridge;\n    if (!cxxBridge.runtime) {\n        return;\n    }\n\n    facebook::jsi::Runtime *runtime = (facebook::jsi::Runtime *)cxxBridge.runtime;\n    if (!runtime) {\n        return;\n    }\n\n    // Get the documents directory for database storage\n    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);\n    NSString *documentsPath = [paths firstObject];\n\n    // Check for app group (for sharing data between app and extensions)\n    NSString *appGroup = [[NSBundle mainBundle] objectForInfoDictionaryKey:@\"Turso_AppGroup\"];\n    if (appGroup) {\n        NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:appGroup];\n        if (containerURL) {\n            documentsPath = [containerURL path];\n        }\n    }\n\n    // Get the call invoker\n    auto callInvoker = _bridge.jsCallInvoker;\n\n    // Install the Turso module\n    turso::install(*runtime, callInvoker, [documentsPath UTF8String]);\n}\n\n// Synchronous method to check if the module is installed\nRCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(install) {\n    [self installLibrary];\n    return @YES;\n}\n\n@end\n"
  },
  {
    "path": "bindings/react-native/package.json",
    "content": "{\n  \"name\": \"@tursodatabase/sync-react-native\",\n  \"version\": \"0.6.0-pre.4\",\n  \"description\": \"React Native bindings for TursoDB\",\n  \"main\": \"lib/commonjs/index.js\",\n  \"module\": \"lib/module/index.js\",\n  \"types\": \"lib/typescript/index.d.ts\",\n  \"react-native\": \"src/index.ts\",\n  \"source\": \"src/index.ts\",\n  \"files\": [\n    \"src\",\n    \"lib\",\n    \"libs\",\n    \"cpp\",\n    \"ios\",\n    \"android\",\n    \"node/dist\",\n    \"node/package.json\",\n    \"*.podspec\",\n    \"*.rb\",\n    \"!ios/build\",\n    \"!android/build\",\n    \"!android/gradle\",\n    \"!android/gradlew\",\n    \"!android/gradlew.bat\",\n    \"!android/local.properties\",\n    \"!**/__tests__\",\n    \"!**/__fixtures__\",\n    \"!**/__mocks__\",\n    \"!**/.*\"\n  ],\n  \"scripts\": {\n    \"typescript\": \"tsc --noEmit\",\n    \"lint\": \"eslint \\\"src/**/*.{ts,tsx}\\\"\",\n    \"clean\": \"del-cli lib\",\n    \"build\": \"bob build\",\n    \"build:rust:ios\": \"make ios\",\n    \"build:rust:android\": \"make android\",\n    \"prepare\": \"bob build\"\n  },\n  \"keywords\": [\n    \"react-native\",\n    \"ios\",\n    \"android\",\n    \"sqlite\",\n    \"database\",\n    \"turso\",\n    \"tursodb\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/tursodatabase/turso.git\",\n    \"directory\": \"bindings/react-native\"\n  },\n  \"author\": \"Turso <info@turso.tech> (https://turso.tech)\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/tursodatabase/turso/issues\"\n  },\n  \"homepage\": \"https://github.com/tursodatabase/turso#readme\",\n  \"publishConfig\": {\n    \"registry\": \"https://registry.npmjs.org/\"\n  },\n  \"devDependencies\": {\n    \"@types/react\": \"^18.2.0\",\n    \"del-cli\": \"^5.1.0\",\n    \"eslint\": \"^8.57.0\",\n    \"react\": \"^18.2.0\",\n    \"react-native\": \"^0.76.0\",\n    \"react-native-builder-bob\": \"^0.30.0\",\n    \"typescript\": \"^5.3.0\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.25.0\"\n  },\n  \"peerDependencies\": {\n    \"react\": \">=18.0.0\",\n    \"react-native\": \">=0.76.0\"\n  },\n  \"engines\": {\n    \"node\": \">=18\"\n  },\n  \"react-native-builder-bob\": {\n    \"source\": \"src\",\n    \"output\": \"lib\",\n    \"targets\": [\n      \"commonjs\",\n      \"module\",\n      [\n        \"typescript\",\n        {\n          \"project\": \"tsconfig.build.json\"\n        }\n      ]\n    ]\n  }\n}"
  },
  {
    "path": "bindings/react-native/src/AsyncLock.ts",
    "content": "/**\n * AsyncLock\n *\n * Simple FIFO queue-based async lock to serialize database operations.\n * Prevents concurrent access to the native SQLite connection.\n */\nexport class AsyncLock {\n  locked: boolean;\n  queue: any[];\n\n  constructor() {\n    this.locked = false;\n    this.queue = [];\n  }\n\n  async acquire() {\n    if (!this.locked) {\n      this.locked = true;\n      return Promise.resolve();\n    } else {\n      const block = new Promise((resolve) => {\n        this.queue.push(resolve);\n      });\n      return block;\n    }\n  }\n\n  release() {\n    if (this.locked == false) {\n      throw new Error('invalid state: lock was already unlocked');\n    }\n    const item = this.queue.shift();\n    if (item != null) {\n      this.locked = true;\n      item();\n    } else {\n      this.locked = false;\n    }\n  }\n}\n"
  },
  {
    "path": "bindings/react-native/src/Database.ts",
    "content": "/**\n * Database\n *\n * Unified high-level API for both local and sync databases.\n * Constructor determines whether to use local-only or sync mode based on config.\n */\n\nimport { AsyncLock } from './AsyncLock';\nimport { Statement } from './Statement';\nimport type {\n  NativeDatabase,\n  NativeSyncDatabase,\n  NativeConnection,\n  Row,\n  RunResult,\n\n  BindParams,\n  DatabaseOpts,\n  SyncStats,\n  EncryptionOpts,\n} from './types';\nimport {\n  driveVoidOperation,\n  driveConnectionOperation,\n  driveChangesOperation,\n  driveStatsOperation,\n} from './internal/asyncOperation';\nimport { drainSyncIo } from './internal/ioProcessor';\n\n/**\n * Check if config has sync properties (url field)\n */\nfunction isSyncConfig(opts: DatabaseOpts): boolean {\n  return opts.url !== undefined && opts.url !== null;\n}\n\n/**\n * Calculate reserved bytes based on encryption cipher.\n * These values match the Turso Cloud encryption settings.\n */\nfunction getReservedBytesForCipher(encryption: EncryptionOpts | undefined): number {\n  if (!encryption) {\n    return 0;\n  }\n\n  switch (encryption.cipher) {\n    case 'aes256gcm':\n    case 'aes128gcm':\n    case 'chacha20poly1305':\n      return 28;\n    case 'aegis128l':\n    case 'aegis128x2':\n    case 'aegis128x4':\n      return 32;\n    case 'aegis256':\n    case 'aegis256x2':\n    case 'aegis256x4':\n      return 48;\n    default:\n      return 0;\n  }\n}\n\n/**\n * Database class - works for both local-only and sync databases\n *\n * All database operations are async to properly handle IO requirements:\n * - For local databases: async allows yielding to JS event loop\n * - For sync databases: async required for network operations\n * - For partial sync: async required to load missing pages on-demand\n */\nexport class Database {\n  private _opts: DatabaseOpts;\n  private _nativeDb: NativeDatabase | null = null;\n  private _nativeSyncDb: NativeSyncDatabase | null = null;\n  private _connection: NativeConnection | null = null;\n  private _isSync = false;\n  private _connected = false;\n  private _closed = false;\n  private _execLock: AsyncLock;\n  private _extraIo?: () => Promise<void>;\n  private _ioContext?: {\n    authToken?: string | (() => string | Promise<string> | null);\n    baseUrl?: string | (() => string | null);\n  };\n\n  /**\n   * Create a new database (doesn't connect yet - call connect())\n   *\n   * @param opts - Database options\n   */\n  constructor(opts: DatabaseOpts) {\n    this._opts = opts;\n    this._isSync = isSyncConfig(opts);\n    this._execLock = new AsyncLock();\n  }\n\n  /**\n   * Connect to the database (matches JavaScript bindings)\n   * For local databases: opens immediately\n   * For sync databases: bootstraps if needed\n   */\n  async connect(): Promise<void> {\n    if (this._connected) {\n      return;\n    }\n\n    if (this._isSync) {\n      await this.initSyncDatabase();\n    } else {\n      this.initLocalDatabase();\n    }\n\n    this._connected = true;\n  }\n\n  /**\n   * Initialize local-only database\n   */\n  private initLocalDatabase(): void {\n    if (typeof __TursoProxy === 'undefined') {\n      throw new Error('Turso native module not loaded');\n    }\n\n    const dbConfig = {\n      path: this._opts.path,\n      async_io: false, // use blocking IO for local database\n    };\n\n    // Create native database (path normalization happens in C++ JSI layer)\n    this._nativeDb = __TursoProxy.newDatabase(this._opts.path, dbConfig);\n\n    // Open database\n    this._nativeDb.open();\n\n    // Get connection\n    this._connection = this._nativeDb.connect();\n  }\n\n  /**\n   * Initialize sync database\n   */\n  private async initSyncDatabase(): Promise<void> {\n    if (typeof __TursoProxy === 'undefined') {\n      throw new Error('Turso native module not loaded');\n    }\n\n    // Get URL (can be string or function)\n    let url: string | null = null;\n    if (typeof this._opts.url === 'function') {\n      url = this._opts.url();\n    } else if (typeof this._opts.url === 'string') {\n      url = this._opts.url;\n    }\n\n    // Build dbConfig (path normalization happens in C++ JSI layer)\n    const dbConfig = {\n      path: this._opts.path,\n      async_io: true, // use async IO for synced database as we have network IO loop externally from the turso core\n    };\n\n    // Calculate reserved bytes from cipher\n    const reservedBytes = getReservedBytesForCipher(this._opts.remoteEncryption);\n\n    // Build syncConfig with all options\n    const syncConfig: any = {\n      remoteUrl: url,\n      clientName: this._opts.clientName || 'turso-sync-react-native',\n      longPollTimeoutMs: this._opts.longPollTimeoutMs,\n      bootstrapIfEmpty: this._opts.bootstrapIfEmpty ?? true,\n      reservedBytes: reservedBytes,\n      // Remote encryption options (key is passed to sync engine for HTTP headers)\n      remoteEncryptionKey: this._opts.remoteEncryption?.key,\n      remoteEncryptionCipher: this._opts.remoteEncryption?.cipher,\n    };\n\n    // Add partial sync options if present\n    if (this._opts.partialSyncExperimental) {\n      const partial = this._opts.partialSyncExperimental;\n      if (partial.bootstrapStrategy.kind === 'prefix') {\n        syncConfig.partialBootstrapStrategyPrefix = partial.bootstrapStrategy.length;\n      } else if (partial.bootstrapStrategy.kind === 'query') {\n        syncConfig.partialBootstrapStrategyQuery = partial.bootstrapStrategy.query;\n      }\n      syncConfig.partialBootstrapSegmentSize = partial.segmentSize;\n      syncConfig.partialBootstrapPrefetch = partial.prefetch;\n    }\n\n    // Create native sync database\n    this._nativeSyncDb = __TursoProxy.newSyncDatabase(dbConfig, syncConfig);\n\n    // Create IO context with auth token and base URL\n    this._ioContext = {\n      authToken: this._opts.authToken,\n      baseUrl: this._opts.url,\n    };\n\n    // Create extraIo callback for partial sync support\n    // This callback drains the sync engine's IO queue during statement execution\n    this._extraIo = async () => {\n      if (this._nativeSyncDb && this._ioContext) {\n        await drainSyncIo(this._nativeSyncDb, this._ioContext);\n      }\n    };\n\n    // Bootstrap/open database\n    const operation = this._nativeSyncDb.create();\n    await driveVoidOperation(operation, this._nativeSyncDb, this._ioContext);\n\n    // Get connection\n    const connOperation = this._nativeSyncDb.connect();\n    this._connection = await driveConnectionOperation(connOperation, this._nativeSyncDb, this._ioContext);\n  }\n\n  /**\n   * Prepare a SQL statement\n   *\n   * @param sql - SQL statement to prepare\n   * @returns Prepared statement\n   */\n  prepare(sql: string): Statement {\n    this.checkOpen();\n\n    if (!this._connection) {\n      throw new Error('No connection available');\n    }\n\n    const nativeStmt = this._connection.prepareSingle(sql);\n    return new Statement(nativeStmt, this._connection!, this._execLock, this._extraIo);\n  }\n\n  /**\n   * Execute SQL without returning results (for DDL, multi-statement SQL)\n   *\n   * @param sql - SQL to execute\n   */\n  async exec(sql: string): Promise<void> {\n    this.checkOpen();\n\n    if (!this._connection) {\n      throw new Error('No connection available');\n    }\n\n    await this._execLock.acquire();\n    try {\n      // Use prepareFirst to handle multiple statements\n      let remaining = sql.trim();\n\n      while (remaining.length > 0) {\n        const result = this._connection.prepareFirst(remaining);\n\n        if (!result) {\n          break; // No more statements (C++ returns null when nothing to parse)\n        }\n\n        // Wrap in Statement to get IO handling (no lock — we already hold it)\n        const stmt = new Statement(result.statement, this._connection!, null, this._extraIo);\n        try {\n          // Execute - will handle IO if needed\n          await stmt.rawRun();\n        } finally {\n          stmt.finalize();\n        }\n\n        // Move to next statement\n        remaining = sql.substring(result.tailIdx).trim();\n      }\n    } finally {\n      this._execLock.release();\n    }\n  }\n\n  /**\n   * Execute statement and return result info\n   *\n   * @param sql - SQL statement\n   * @param params - Bind parameters\n   * @returns Run result with changes and lastInsertRowid\n   */\n  async run(sql: string, ...params: BindParams[]): Promise<RunResult> {\n    const stmt = this.prepare(sql);\n    try {\n      return await stmt.run(...params);\n    } finally {\n      stmt.finalize();\n    }\n  }\n\n  /**\n   * Execute query and return first row\n   *\n   * @param sql - SQL query\n   * @param params - Bind parameters\n   * @returns First row or undefined\n   */\n  async get(sql: string, ...params: BindParams[]): Promise<Row | undefined> {\n    const stmt = this.prepare(sql);\n    try {\n      return await stmt.get(...params);\n    } finally {\n      stmt.finalize();\n    }\n  }\n\n  /**\n   * Execute query and return all rows\n   *\n   * @param sql - SQL query\n   * @param params - Bind parameters\n   * @returns All rows\n   */\n  async all(sql: string, ...params: BindParams[]): Promise<Row[]> {\n    const stmt = this.prepare(sql);\n    try {\n      return await stmt.all(...params);\n    } finally {\n      stmt.finalize();\n    }\n  }\n\n  /**\n   * Execute function within a transaction\n   *\n   * @param fn - Function to execute\n   * @returns Function result\n   */\n  async transaction<T>(fn: () => T | Promise<T>): Promise<T> {\n    this.checkOpen();\n    await this.exec('BEGIN');\n    try {\n      const result = await fn();\n      await this.exec('COMMIT');\n      return result;\n    } catch (error) {\n      await this.exec('ROLLBACK');\n      throw error;\n    }\n  }\n\n  /**\n   * Push local changes to remote (sync databases only)\n   */\n  async push(): Promise<void> {\n    if (!this._isSync || !this._nativeSyncDb || !this._ioContext) {\n      throw new Error('push() is only available for sync databases');\n    }\n\n    const operation = this._nativeSyncDb.pushChanges();\n    await driveVoidOperation(operation, this._nativeSyncDb, this._ioContext);\n  }\n\n  /**\n   * Pull remote changes and apply locally (sync databases only)\n   *\n   * @returns true if changes were applied, false if no changes\n   */\n  async pull(): Promise<boolean> {\n    if (!this._isSync || !this._nativeSyncDb || !this._ioContext) {\n      throw new Error('pull() is only available for sync databases');\n    }\n\n    // Wait for changes\n    const waitOperation = this._nativeSyncDb.waitChanges();\n    const changes = await driveChangesOperation(waitOperation, this._nativeSyncDb, this._ioContext);\n\n    // If no changes, return false\n    if (!changes) {\n      return false;\n    }\n\n    // Apply changes\n    const applyOperation = this._nativeSyncDb.applyChanges(changes);\n    await driveVoidOperation(applyOperation, this._nativeSyncDb, this._ioContext);\n\n    return true;\n  }\n\n  /**\n   * Get sync statistics (sync databases only)\n   *\n   * @returns Sync stats\n   */\n  async stats(): Promise<SyncStats> {\n    if (!this._isSync || !this._nativeSyncDb || !this._ioContext) {\n      throw new Error('stats() is only available for sync databases');\n    }\n\n    const operation = this._nativeSyncDb.stats();\n    return driveStatsOperation(operation, this._nativeSyncDb, this._ioContext);\n  }\n\n  /**\n   * Checkpoint database (sync databases only)\n   */\n  async checkpoint(): Promise<void> {\n    if (!this._isSync || !this._nativeSyncDb || !this._ioContext) {\n      throw new Error('checkpoint() is only available for sync databases');\n    }\n\n    const operation = this._nativeSyncDb.checkpoint();\n    await driveVoidOperation(operation, this._nativeSyncDb, this._ioContext);\n  }\n\n  /**\n   * Close the database\n   */\n  close(): void {\n    if (this._closed) {\n      return;\n    }\n\n    if (this._connection) {\n      this._connection.close();\n      this._connection = null;\n    }\n\n    if (this._nativeDb) {\n      this._nativeDb.close();\n      this._nativeDb = null;\n    }\n\n    if (this._nativeSyncDb) {\n      this._nativeSyncDb.close();\n      this._nativeSyncDb = null;\n    }\n\n    this._connected = false;\n    this._closed = true;\n  }\n\n  /**\n   * Get database path\n   */\n  get path(): string {\n    return this._opts.path;\n  }\n\n  /**\n   * Check if database is a sync database\n   */\n  get isSync(): boolean {\n    return this._isSync;\n  }\n\n  /**\n   * Check if database is open\n   */\n  get open(): boolean {\n    return !this._closed && this._connection !== null;\n  }\n\n  /**\n   * Check if in transaction\n   */\n  get inTransaction(): boolean {\n    if (!this._connection) {\n      return false;\n    }\n    return !this._connection.getAutocommit();\n  }\n\n  /**\n   * Get last insert rowid\n   */\n  get lastInsertRowid(): number {\n    if (!this._connection) {\n      return 0;\n    }\n    return this._connection.lastInsertRowid();\n  }\n\n  /**\n   * Check if open and throw if not\n   */\n  private checkOpen(): void {\n    if (this._closed) {\n      throw new Error('Database is closed');\n    }\n    if (!this._connected || !this._connection) {\n      throw new Error('Database not connected. Call connect() first.');\n    }\n  }\n}\n"
  },
  {
    "path": "bindings/react-native/src/Statement.ts",
    "content": "/**\n * Statement\n *\n * High-level wrapper around NativeStatement providing a clean API.\n * Handles parameter binding, row conversion, and result collection.\n */\n\nimport type { AsyncLock } from './AsyncLock';\nimport type {\n  NativeConnection,\n  NativeStatement,\n  SQLiteValue,\n  BindParams,\n  Row,\n  RunResult,\n} from './types';\nimport { TursoStatus, TursoType } from './types';\n\n/**\n * Prepared SQL statement\n */\nexport class Statement {\n  private _statement: NativeStatement;\n  private _connection: NativeConnection;\n  private _execLock: AsyncLock | null;\n  private _finalized = false;\n  private _extraIo?: () => Promise<void>;\n\n  constructor(statement: NativeStatement, connection: NativeConnection, execLock: AsyncLock | null, extraIo?: () => Promise<void>) {\n    this._statement = statement;\n    this._connection = connection;\n    this._execLock = execLock;\n    this._extraIo = extraIo;\n  }\n\n  /**\n   * Bind parameters to the statement\n   *\n   * @param params - Parameters to bind (array, object, or single value)\n   * @returns this for chaining\n   */\n  bind(...params: BindParams[]): this {\n    if (this._finalized) {\n      throw new Error('Statement has been finalized');\n    }\n\n    // Flatten parameters if single array passed\n    let flatParams: SQLiteValue[];\n    if (params.length === 1 && Array.isArray(params[0])) {\n      flatParams = params[0];\n    } else if (params.length === 1 && typeof params[0] === 'object' && params[0] !== null) {\n      // Named parameters\n      const namedParams = params[0] as Record<string, SQLiteValue>;\n      this.bindNamed(namedParams);\n      return this;\n    } else {\n      flatParams = params as SQLiteValue[];\n    }\n\n    // Bind positional parameters\n    this.bindPositional(flatParams);\n    return this;\n  }\n\n  /**\n   * Bind positional parameters (1-indexed)\n   *\n   * @param params - Array of values to bind\n   */\n  private bindPositional(params: SQLiteValue[]): void {\n    for (let i = 0; i < params.length; i++) {\n      const position = i + 1; // 1-indexed\n      const value = params[i]!;\n\n      this.bindValue(position, value);\n    }\n  }\n\n  /**\n   * Bind named parameters\n   *\n   * @param params - Object with named parameters\n   */\n  private bindNamed(params: Record<string, SQLiteValue>): void {\n    for (const [name, value] of Object.entries(params)) {\n      // Get position for named parameter\n      const position = this._statement.namedPosition(name);\n      if (position < 0) {\n        throw new Error(`Unknown parameter name: ${name}`);\n      }\n\n      this.bindValue(position, value);\n    }\n  }\n\n  /**\n   * Bind a single value at a position\n   *\n   * @param position - 1-indexed position\n   * @param value - Value to bind\n   */\n  private bindValue(position: number, value: SQLiteValue): void {\n    if (value === null || value === undefined) {\n      this._statement.bindPositionalNull(position);\n    } else if (typeof value === 'number') {\n      // Check if integer or float\n      if (Number.isInteger(value)) {\n        this._statement.bindPositionalInt(position, value);\n      } else {\n        this._statement.bindPositionalDouble(position, value);\n      }\n    } else if (typeof value === 'string') {\n      this._statement.bindPositionalText(position, value);\n    } else if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {\n      const buffer = value as unknown as ArrayBuffer;\n      this._statement.bindPositionalBlob(position, buffer);\n    } else {\n      throw new Error(`Unsupported parameter type: ${typeof value}`);\n    }\n  }\n\n  /**\n   * Execute statement without returning rows (for INSERT, UPDATE, DELETE)\n   *\n   * @param params - Optional parameters to bind\n   * @returns Result with changes and lastInsertRowid\n   */\n  async run(...params: BindParams[]): Promise<RunResult> {\n    if (this._finalized) {\n      throw new Error('Statement has been finalized');\n    }\n\n    if (this._execLock) {\n      await this._execLock.acquire();\n    }\n    try {\n      // Bind parameters inside the lock to prevent concurrent bind/execute races\n      if (params.length > 0) {\n        this.bind(...params);\n      }\n      return await this._runInner();\n    } finally {\n      this._statement.reset();\n      if (this._execLock) {\n        this._execLock.release();\n      }\n    }\n  }\n\n  /**\n   * Execute without acquiring the lock (caller already holds it).\n   * Used by Database.exec() and Transaction which acquire the lock once.\n   */\n  async rawRun(): Promise<RunResult> {\n    if (this._finalized) {\n      throw new Error('Statement has been finalized');\n    }\n\n    try {\n      return await this._runInner();\n    } finally {\n      this._statement.reset();\n    }\n  }\n\n  private async _runInner(): Promise<RunResult> {\n    // Execute statement with IO handling\n    const result = await this.executeWithIo();\n\n    return {\n      changes: result.rowsChanged,\n      lastInsertRowid: this._connection ? this._connection.lastInsertRowid() : 0,\n    };\n  }\n\n  /**\n   * Execute statement handling potential IO (for partial sync)\n   * Matches Python's _run_execute_with_io pattern\n   *\n   * @returns Execution result\n   */\n  private async executeWithIo(): Promise<{ status: number; rowsChanged: number }> {\n    while (true) {\n      const result = this._statement.execute();\n\n      if (result.status === TursoStatus.IO) {\n        // Statement needs IO (e.g., loading missing pages with partial sync)\n        this._statement.runIo();\n\n        // Drain sync engine IO queue\n        if (this._extraIo) {\n          await this._extraIo();\n        }\n\n        continue;\n      }\n\n      if (result.status !== TursoStatus.DONE) {\n        throw new Error(`Statement execution failed with status: ${result.status}`);\n      }\n\n      return result;\n    }\n  }\n\n  /**\n   * Step statement once handling potential IO (for partial sync)\n   * Matches Python's _step_once_with_io pattern\n   *\n   * @returns Status code\n   */\n  private async stepWithIo(): Promise<number> {\n    while (true) {\n      const status = this._statement.step();\n\n      if (status === TursoStatus.IO) {\n        // Statement needs IO (e.g., loading missing pages with partial sync)\n        this._statement.runIo();\n\n        // Drain sync engine IO queue\n        if (this._extraIo) {\n          await this._extraIo();\n        }\n\n        continue;\n      }\n\n      return status;\n    }\n  }\n\n  /**\n   * Execute statement and return first row\n   *\n   * @param params - Optional parameters to bind\n   * @returns First row or undefined\n   */\n  async get(...params: BindParams[]): Promise<Row | undefined> {\n    if (this._finalized) {\n      throw new Error('Statement has been finalized');\n    }\n\n    if (this._execLock) {\n      await this._execLock.acquire();\n    }\n    try {\n      // Bind parameters inside the lock to prevent concurrent bind/execute races\n      if (params.length > 0) {\n        this.bind(...params);\n      }\n\n      // Step once with async IO handling\n      const status = await this.stepWithIo();\n\n      if (status === TursoStatus.ROW) {\n        const row = this.readRow();\n        return row;\n      }\n\n      if (status === TursoStatus.DONE) {\n        return undefined;\n      }\n\n      throw new Error(`Statement step failed with status: ${status}`);\n    } finally {\n      this._statement.reset();\n      if (this._execLock) {\n        this._execLock.release();\n      }\n    }\n  }\n\n  /**\n   * Execute statement and return all rows\n   *\n   * @param params - Optional parameters to bind\n   * @returns Array of rows\n   */\n  async all(...params: BindParams[]): Promise<Row[]> {\n    if (this._finalized) {\n      throw new Error('Statement has been finalized');\n    }\n\n    if (this._execLock) {\n      await this._execLock.acquire();\n    }\n    try {\n      // Bind parameters inside the lock to prevent concurrent bind/execute races\n      if (params.length > 0) {\n        this.bind(...params);\n      }\n\n      // Fast path: native bulk read (handles step+read loop in C++)\n      // Re-enters native after each IO resolution to stay on the fast path\n      let rows: Row[] = [];\n      const MAX_IO_RETRIES = 1000000;\n\n      for (let ioRetries = 0; ioRetries < MAX_IO_RETRIES; ioRetries++) {\n        const bulk = this._statement.getAllRows();\n        if (bulk.rows && bulk.rows.length > 0) {\n          rows = rows.concat(bulk.rows);\n        }\n\n        if (bulk.status === TursoStatus.DONE) {\n          return rows;\n        }\n\n        if (bulk.status === TursoStatus.IO) {\n          this._statement.runIo();\n          if (this._extraIo) {\n            await this._extraIo();\n          }\n          continue;\n        }\n\n        throw new Error(`getAllRows failed with status: ${bulk.status}`);\n      }\n\n      throw new Error(`getAllRows: exceeded ${MAX_IO_RETRIES} IO retries`);\n    } finally {\n      this._statement.reset();\n      if (this._execLock) {\n        this._execLock.release();\n      }\n    }\n  }\n\n  /**\n   * Read current row into an object\n   *\n   * @returns Row object with column name keys\n   */\n  private readRow(): Row {\n    const row: Row = {};\n    const columnCount = this._statement.columnCount();\n\n    for (let i = 0; i < columnCount; i++) {\n      const name = this._statement.columnName(i);\n      if (!name) {\n        throw new Error(`Failed to get column name at index ${i}`);\n      }\n\n      const value = this.readColumnValue(i);\n      row[name] = value;\n    }\n\n    return row;\n  }\n\n  /**\n   * Read value at column index\n   *\n   * @param index - Column index\n   * @returns Column value\n   */\n  private readColumnValue(index: number): SQLiteValue {\n    const kind = this._statement.rowValueKind(index);\n\n    switch (kind) {\n      case TursoType.NULL:\n        return null;\n\n      case TursoType.INTEGER:\n        return this._statement.rowValueInt(index);\n\n      case TursoType.REAL:\n        return this._statement.rowValueDouble(index);\n\n      case TursoType.TEXT:\n        // Use rowValueText which directly returns a string from C++ (avoids encoding issues)\n        return this._statement.rowValueText(index);\n\n      case TursoType.BLOB:\n        return this._statement.rowValueBytesPtr(index) || new ArrayBuffer(0);\n\n      default:\n        throw new Error(`Unknown column type: ${kind}`);\n    }\n  }\n\n  /**\n   * Reset statement for re-execution\n   *\n   * @returns this for chaining\n   */\n  reset(): this {\n    if (this._finalized) {\n      throw new Error('Statement has been finalized');\n    }\n\n    this._statement.reset();\n    return this;\n  }\n\n  /**\n   * Finalize and release statement resources\n   */\n  async finalize(): Promise<void> {\n    if (this._finalized) {\n      return;\n    }\n\n    if (this._execLock) {\n      await this._execLock.acquire();\n    }\n    try {\n      while (true) {\n        const status = this._statement.finalize();\n\n        if (status === TursoStatus.IO) {\n          // Statement needs IO (e.g., loading missing pages with partial sync)\n          this._statement.runIo();\n\n          // Drain sync engine IO queue\n          if (this._extraIo) {\n            await this._extraIo();\n          }\n\n          continue;\n        }\n\n        if (status !== TursoStatus.DONE) {\n          throw new Error(`Statement finalization failed with status: ${status}`);\n        }\n        break;\n      }\n      this._finalized = true;\n    } finally {\n      if (this._execLock) {\n        this._execLock.release();\n      }\n    }\n  }\n\n  /**\n   * Check if statement has been finalized\n   */\n  get finalized(): boolean {\n    return this._finalized;\n  }\n}\n"
  },
  {
    "path": "bindings/react-native/src/index.ts",
    "content": "/**\n * Turso React Native SDK\n *\n * Main entry point for the SDK. Supports both local-only and sync databases.\n */\n\nimport { NativeModules } from 'react-native';\nimport { Database } from './Database';\nimport type {\n  DatabaseOpts,\n  TursoLoggerFn,\n  TursoNativeModule,\n  TursoProxy as TursoProxyType,\n} from './types';\nimport { setFileSystemImpl } from './internal/ioProcessor';\n\n// Re-export all public types\nexport type {\n  // Core types\n  SQLiteValue,\n  BindParams,\n  Row,\n  RunResult,\n\n  // Database config\n  DatabaseOpts,\n  EncryptionOpts,\n\n  // Sync types\n  SyncStats,\n\n  // Logging\n  TursoLog,\n  TursoLoggerFn,\n  TursoTracingLevel,\n\n  // Enums\n  TursoStatus,\n  TursoType,\n} from './types';\n\n// Re-export classes\nexport { Database } from './Database';\nexport { Statement } from './Statement';\n\n// Export file system configuration function\nexport { setFileSystemImpl } from './internal/ioProcessor';\n\n// Get the native module\nconst TursoNative: TursoNativeModule | undefined = NativeModules.Turso;\n\n// Check if native module is available\nif (!TursoNative) {\n  throw new Error(\n    `@tursodatabase/sync-react-native: Native module not found. Make sure you have properly linked the library.\\n` +\n    `- iOS: Run 'pod install' in your ios directory\\n` +\n    `- Android: Make sure the package is properly included in your MainApplication.java`\n  );\n}\n\n// Install the JSI bindings\nconst installed = TursoNative.install();\nif (!installed) {\n  throw new Error(\n    '@tursodatabase/sync-react-native: Failed to install JSI bindings. Make sure the New Architecture is enabled.'\n  );\n}\n\n// Get the proxy that was installed on the global object\n// __TursoProxy is declared globally in types.ts\nconst TursoProxy: TursoProxyType = __TursoProxy;\n\nif (!TursoProxy) {\n  throw new Error(\n    '@tursodatabase/sync-react-native: JSI bindings not found on global object. This is a bug.'\n  );\n}\n\n/**\n * Helper function to construct a database path in a writable directory.\n *\n * @param filename - Database filename (e.g., 'mydb.db')\n * @returns Absolute path to the database file\n *\n * @example\n * ```ts\n * import { getDbPath, connect } from '@tursodatabase/sync-react-native';\n *\n * const dbPath = getDbPath('mydb.db');\n * const db = await connect({ path: dbPath });\n * ```\n */\nexport function getDbPath(filename: string): string {\n  const basePath = paths.database;\n  if (!basePath || basePath === '.') {\n    throw new Error(\n      'Unable to get database path for this platform. ' +\n      'Make sure the native module is properly loaded.'\n    );\n  }\n  return `${basePath}/${filename}`;\n}\n\n/**\n * Connect to a database asynchronously (matches JavaScript bindings API)\n *\n * This is the main entry point for the SDK, matching the API from\n * @tursodatabase/sync-native and @tursodatabase/database-native.\n *\n * **Path handling**: Relative paths are automatically placed in writable directories:\n * - Android: app's database directory (`/data/data/com.app/databases/`)\n * - iOS: app's documents directory\n *\n * Absolute paths and `:memory:` are used as-is.\n *\n * @param opts - Database options\n * @returns Promise resolving to Database instance\n *\n * @example Local database (relative path)\n * ```ts\n * import { connect } from '@tursodatabase/sync-react-native';\n *\n * // Relative path automatically placed in writable directory\n * const db = await connect({ path: 'local.db' });\n * await db.exec('CREATE TABLE users (id INTEGER, name TEXT)');\n * ```\n *\n * @example Using :memory: for in-memory database\n * ```ts\n * const db = await connect({ path: ':memory:' });\n * ```\n *\n * @example Sync database\n * ```ts\n * const db = await connect({\n *   path: 'replica.db',\n *   url: 'libsql://mydb.turso.io',\n *   authToken: 'token-here',\n * });\n * const users = await db.all('SELECT * FROM users');\n * await db.push();\n * await db.pull();\n * ```\n *\n * @example Using absolute path (advanced)\n * ```ts\n * import { connect, paths } from '@tursodatabase/sync-react-native';\n *\n * const db = await connect({ path: `${paths.database}/mydb.db` });\n * ```\n */\nexport async function connect(opts: DatabaseOpts): Promise<Database> {\n  const db = new Database(opts);\n  await db.connect();\n  return db;\n}\n\n/**\n * Returns the Turso library version.\n */\nexport function version(): string {\n  return TursoProxy.version();\n}\n\n/**\n * Configure Turso settings such as logging.\n * Should be called before any database operations.\n *\n * @param options - Configuration options\n * @example\n * ```ts\n * import { setup } from '@tursodatabase/sync-react-native';\n *\n * setup({\n *   logLevel: 'debug',\n *   logger: (log) => {\n *     console.log(`[${log.level}] ${log.target}: ${log.message}`);\n *   },\n * });\n * ```\n */\nexport function setup(options: {logLevel?: string; logger?: TursoLoggerFn}): void {\n  TursoProxy.setup(options);\n}\n\n/**\n * Platform-specific writable directory path for database files.\n *\n * NOTE: With automatic path normalization, you typically don't need this.\n * Just pass relative paths like 'mydb.db' and they'll be placed in the correct directory.\n *\n * @example\n * ```ts\n * import { paths, connect } from '@tursodatabase/sync-react-native';\n *\n * const dbPath = `${paths.database}/mydb.db`;\n * const db = await connect({ path: dbPath });\n * ```\n */\nexport const paths = {\n  /**\n   * Writable directory for database files.\n   * - iOS: App's Documents directory\n   * - Android: App's database directory\n   */\n  get database(): string {\n    return TursoNative?.IOS_DOCUMENT_PATH || TursoNative?.ANDROID_DATABASE_PATH || '.';\n  },\n};\n\n// Default export\nexport default {\n  connect,\n  version,\n  setup,\n  setFileSystemImpl,\n  getDbPath,\n  paths,\n  Database,\n};\n"
  },
  {
    "path": "bindings/react-native/src/internal/asyncOperation.ts",
    "content": "/**\n * Async Operation Driver\n *\n * Drives async operations returned by sync SDK-KIT methods.\n * This is where ALL the async logic lives - the C++ layer is just a thin bridge.\n *\n * Key responsibilities:\n * - Call resume() in a loop until DONE\n * - When IO is needed, process all pending IO items\n * - Extract and return the final result\n */\n\nimport type {\n  NativeSyncOperation,\n  NativeSyncDatabase,\n  NativeConnection,\n  NativeSyncChanges,\n  SyncStats,\n} from '../types';\nimport { TursoStatus, SyncOperationResultType } from '../types';\nimport { processIoItem } from './ioProcessor';\nimport type { IoContext } from './ioProcessor';\n\n/**\n * Drive an async operation to completion\n *\n * @param operation - The native operation to drive\n * @param database - The native sync database (for IO queue access)\n * @param context - IO context with auth and URL information\n * @returns Promise that resolves when operation completes\n */\nexport async function driveOperation<T = void>(\n  operation: NativeSyncOperation,\n  database: NativeSyncDatabase,\n  context: IoContext\n): Promise<T> {\n  while (true) {\n    // Resume the operation\n    const status = operation.resume();\n\n    // Operation completed successfully\n    if (status === TursoStatus.DONE) {\n      // Extract and return the result based on result type\n      const resultKind = operation.resultKind();\n\n      switch (resultKind) {\n        case SyncOperationResultType.NONE:\n          return undefined as T;\n\n        case SyncOperationResultType.CONNECTION:\n          return operation.extractConnection() as T;\n\n        case SyncOperationResultType.CHANGES:\n          return operation.extractChanges() as T;\n\n        case SyncOperationResultType.STATS:\n          return operation.extractStats() as T;\n\n        default:\n          throw new Error(`Unknown result type: ${resultKind}`);\n      }\n    }\n\n    // Operation needs IO\n    if (status === TursoStatus.IO) {\n      // Process all pending IO items\n      await processIoQueue(database, context);\n\n      // Step callbacks after IO processing\n      database.ioStepCallbacks();\n\n      // Continue resume loop\n      continue;\n    }\n\n    // Any other status is an error\n    throw new Error(`Unexpected status from operation.resume(): ${status}`);\n  }\n}\n\n/**\n * Process all pending IO items in the queue\n *\n * @param database - The native sync database\n * @param context - IO context with auth and URL information\n */\nasync function processIoQueue(database: NativeSyncDatabase, context: IoContext): Promise<void> {\n  const promises: Promise<void>[] = [];\n\n  // Take all available IO items from the queue\n  while (true) {\n    const ioItem = database.ioTakeItem();\n    if (!ioItem) {\n      break; // No more items\n    }\n\n    // Process each item (potentially in parallel)\n    promises.push(processIoItem(ioItem, context));\n  }\n\n  // Wait for all IO operations to complete\n  await Promise.all(promises);\n}\n\n/**\n * Helper type for operations that return connections\n */\nexport async function driveConnectionOperation(\n  operation: NativeSyncOperation,\n  database: NativeSyncDatabase,\n  context: IoContext\n): Promise<NativeConnection> {\n  return driveOperation<NativeConnection>(operation, database, context);\n}\n\n/**\n * Helper type for operations that return changes\n */\nexport async function driveChangesOperation(\n  operation: NativeSyncOperation,\n  database: NativeSyncDatabase,\n  context: IoContext\n): Promise<NativeSyncChanges | null> {\n  return driveOperation<NativeSyncChanges | null>(operation, database, context);\n}\n\n/**\n * Helper type for operations that return stats\n */\nexport async function driveStatsOperation(\n  operation: NativeSyncOperation,\n  database: NativeSyncDatabase,\n  context: IoContext\n): Promise<SyncStats> {\n  return driveOperation<SyncStats>(operation, database, context);\n}\n\n/**\n * Helper type for operations that return void\n */\nexport async function driveVoidOperation(\n  operation: NativeSyncOperation,\n  database: NativeSyncDatabase,\n  context: IoContext\n): Promise<void> {\n  return driveOperation<void>(operation, database, context);\n}\n"
  },
  {
    "path": "bindings/react-native/src/internal/ioProcessor.ts",
    "content": "/**\n * IO Processor\n *\n * Processes IO requests from the sync engine using JavaScript APIs.\n * This is the key benefit of the thin JSI layer - all IO is handled by\n * React Native's standard APIs (fetch, file system), not C++ code.\n *\n * Benefits:\n * - Network requests visible in React Native debugger\n * - Can customize fetch (add proxies, custom headers, etc.)\n * - Easier to test (can mock fetch)\n * - Uses platform-native networking (not C++ HTTP libraries)\n */\n\nimport type { NativeSyncIoItem, NativeSyncDatabase } from '../types';\n\n/**\n * IO context contains auth and URL information for HTTP requests\n */\nexport interface IoContext {\n  /** Auth token for HTTP requests */\n  authToken?: string | (() => string | Promise<string> | null);\n  /** Base URL for normalization (e.g., 'libsql://mydb.turso.io') */\n  baseUrl?: string | (() => string | null);\n}\n\n// Allow users to optionally override file system implementation\nlet fsReadFileOverride: ((path: string) => Promise<Uint8Array>) | null = null;\nlet fsWriteFileOverride: ((path: string, data: Uint8Array) => Promise<void>) | null = null;\n\n/**\n * Set custom file system implementation (optional)\n * By default, uses built-in JSI file system functions.\n * Only call this if you need custom behavior (e.g., encryption, compression).\n *\n * @param readFile - Function to read file as Uint8Array\n * @param writeFile - Function to write Uint8Array to file\n */\nexport function setFileSystemImpl(\n  readFile: (path: string) => Promise<Uint8Array>,\n  writeFile: (path: string, data: Uint8Array) => Promise<void>\n): void {\n  fsReadFileOverride = readFile;\n  fsWriteFileOverride = writeFile;\n}\n\n/**\n * Read file using custom implementation or built-in JSI function\n */\nasync function fsReadFile(path: string): Promise<Uint8Array> {\n  if (fsReadFileOverride) {\n    return fsReadFileOverride(path);\n  }\n\n  // Use built-in JSI function\n  const buffer = __TursoProxy.fsReadFile(path);\n  if (buffer === null) {\n    // File not found - return empty\n    return new Uint8Array(0);\n  }\n  return new Uint8Array(buffer);\n}\n\n/**\n * Write file using custom implementation or built-in JSI function\n */\nasync function fsWriteFile(path: string, data: Uint8Array): Promise<void> {\n  if (fsWriteFileOverride) {\n    return fsWriteFileOverride(path, data);\n  }\n\n  // Use built-in JSI function\n  __TursoProxy.fsWriteFile(path, data.buffer as ArrayBuffer);\n}\n\n/**\n * Process a single IO item\n *\n * @param item - The IO item to process\n * @param context - IO context with auth and URL information\n */\nexport async function processIoItem(item: NativeSyncIoItem, context: IoContext): Promise<void> {\n  try {\n    const kind = item.getKind();\n\n    switch (kind) {\n      case 'HTTP':\n        await processHttpRequest(item, context);\n        break;\n\n      case 'FULL_READ':\n        await processFullRead(item);\n        break;\n\n      case 'FULL_WRITE':\n        await processFullWrite(item);\n        break;\n\n      case 'NONE':\n      default:\n        // Nothing to do\n        item.done();\n        break;\n    }\n  } catch (error) {\n    // Poison the item with error message\n    const errorMsg = error instanceof Error ? error.message : String(error);\n    item.poison(errorMsg);\n  }\n}\n\n/**\n * Normalize URL from libsql:// to https://\n */\nfunction normalizeUrl(url: string): string {\n  if (url.startsWith('libsql://')) {\n    return url.replace('libsql://', 'https://');\n  }\n  return url;\n}\n\n/**\n * Get auth token from context (handles both string and async function)\n */\nasync function getAuthToken(context: IoContext): Promise<string | null> {\n  if (!context.authToken) {\n    return null;\n  }\n\n  if (typeof context.authToken === 'function') {\n    const result = await context.authToken();\n    return result ?? null;\n  }\n\n  return context.authToken;\n}\n\n/**\n * Get base URL from context (handles both string and function)\n */\nfunction getBaseUrl(context: IoContext): string | null {\n  if (!context.baseUrl) {\n    return null;\n  }\n\n  if (typeof context.baseUrl === 'function') {\n    return context.baseUrl() ?? null;\n  }\n\n  return context.baseUrl;\n}\n\n/**\n * Process an HTTP request using fetch()\n *\n * @param item - The IO item\n * @param context - IO context with auth and URL information\n */\nasync function processHttpRequest(item: NativeSyncIoItem, context: IoContext): Promise<void> {\n  const request = item.getHttpRequest();\n\n  // Resolve base URL: prefer context.baseUrl (from opts), fall back to request.url\n  const rawBaseUrl = getBaseUrl(context) ?? request.url;\n  if (!rawBaseUrl) {\n    throw new Error('HTTP request missing URL: no URL in request and no baseUrl in context');\n  }\n\n  // Normalize URL (libsql:// -> https://)\n  const baseUrl = normalizeUrl(rawBaseUrl);\n\n  // Build full URL by combining base URL with path\n  let fullUrl = baseUrl;\n  if (request.path) {\n    // Ensure proper URL formatting (avoid double slashes, ensure single slash)\n    if (baseUrl.endsWith('/') && request.path.startsWith('/')) {\n      fullUrl = baseUrl + request.path.substring(1);\n    } else if (!baseUrl.endsWith('/') && !request.path.startsWith('/')) {\n      fullUrl = baseUrl + '/' + request.path;\n    } else {\n      fullUrl = baseUrl + request.path;\n    }\n  }\n\n  // Build fetch options\n  const options: RequestInit = {\n    method: request.method,\n    headers: { ...request.headers },\n  };\n\n  // Inject Authorization header if auth token is available\n  const authToken = await getAuthToken(context);\n  if (authToken) {\n    (options.headers as Record<string, string>)['Authorization'] = `Bearer ${authToken}`;\n  }\n\n  // Add body if present\n  if (request.body) {\n    options.body = request.body;\n  }\n\n  // Make the HTTP request\n  let response;\n  try {\n    response = await fetch(fullUrl, options);\n  } catch (e) {\n    // Detailed error logging\n    const errorDetails = {\n      url: fullUrl,\n      method: request.method,\n      hasBody: !!request.body,\n      bodySize: request.body ? request.body.byteLength : 0,\n      bodyType: request.body ? Object.prototype.toString.call(options.body) : 'none',\n      error: e instanceof Error ? {\n        message: e.message,\n        name: e.name,\n        stack: e.stack,\n      } : String(e),\n    };\n    console.error('[Turso HTTP] Request failed:', JSON.stringify(errorDetails, null, 2));\n    throw new Error(`HTTP request failed: ${e instanceof Error ? e.message : String(e)}. URL: ${fullUrl}, Method: ${request.method}, Body size: ${request.body ? request.body.byteLength : 0} bytes`);\n  }\n\n\n  // Set status code\n  item.setStatus(response.status);\n\n  // Read response body and push to item\n  const responseData = await response.arrayBuffer();\n  if (responseData.byteLength > 0) {\n    item.pushBuffer(responseData);\n  }\n\n  // Mark as done\n  item.done();\n}\n\n/**\n * Process a full read request (atomic file read)\n *\n * @param item - The IO item\n */\nasync function processFullRead(item: NativeSyncIoItem): Promise<void> {\n  const path = item.getFullReadPath();\n\n  try {\n    // Read the file (uses built-in JSI function or custom override)\n    const data = await fsReadFile(path);\n\n    // Push data to item\n    if (data.byteLength > 0) {\n      item.pushBuffer(data.buffer as ArrayBuffer);\n    }\n\n    // Mark as done\n    item.done();\n  } catch (error) {\n    // File not found is okay - treat as empty file\n    if (isFileNotFoundError(error)) {\n      // Empty file - just mark as done without pushing data\n      item.done();\n    } else {\n      throw error;\n    }\n  }\n}\n\n/**\n * Process a full write request (atomic file write)\n *\n * @param item - The IO item\n */\nasync function processFullWrite(item: NativeSyncIoItem): Promise<void> {\n  const request = item.getFullWriteRequest();\n\n  // Convert ArrayBuffer to Uint8Array\n  const data = request.content ? new Uint8Array(request.content) : new Uint8Array(0);\n\n  // Write the file atomically (uses built-in JSI function or custom override)\n  await fsWriteFile(request.path, data);\n\n  // Mark as done\n  item.done();\n}\n\n/**\n * Check if error is a file-not-found error\n *\n * @param error - The error to check\n * @returns true if file not found\n */\nfunction isFileNotFoundError(error: unknown): boolean {\n  if (error instanceof Error) {\n    const message = error.message.toLowerCase();\n    return (\n      message.includes('enoent') ||\n      message.includes('not found') ||\n      message.includes('no such file')\n    );\n  }\n  return false;\n}\n\n/**\n * Drain all pending IO items from sync engine queue and process them.\n * This is called during statement execution when partial sync needs to load missing pages.\n *\n * @param database - The native sync database\n * @param context - IO context with auth and URL information\n */\nexport async function drainSyncIo(database: NativeSyncDatabase, context: IoContext): Promise<void> {\n  const promises: Promise<void>[] = [];\n\n  // Take all available IO items from the queue\n  while (true) {\n    const ioItem = database.ioTakeItem();\n    if (!ioItem) {\n      break; // No more items\n    }\n\n    // Process each item\n    promises.push(processIoItem(ioItem, context));\n  }\n\n  // Wait for all IO operations to complete\n  await Promise.all(promises);\n\n  // Step callbacks after IO processing\n  // This allows the sync engine to run any post-IO callbacks\n  database.ioStepCallbacks();\n}\n"
  },
  {
    "path": "bindings/react-native/src/types.ts",
    "content": "/**\n * Turso React Native SDK-KIT Types\n *\n * Clean TypeScript types matching the SDK-KIT C API patterns.\n * All logic lives in TypeScript or Rust - the C++ layer is just a thin bridge.\n */\n\n// ============================================================================\n// Core SDK-KIT Types (Local Database)\n// ============================================================================\n\n/**\n * Native database interface (local-only)\n * Thin wrapper around TursoDatabaseHostObject\n */\nexport interface NativeDatabase {\n  open(): void;\n  connect(): NativeConnection;\n  close(): void;\n}\n\n/**\n * Native connection interface\n * Thin wrapper around TursoConnectionHostObject\n */\nexport interface NativeConnection {\n  prepareSingle(sql: string): NativeStatement;\n  prepareFirst(sql: string): { statement: NativeStatement; tailIdx: number } | null;\n  lastInsertRowid(): number;\n  getAutocommit(): boolean;\n  setBusyTimeout(timeoutMs: number): void;\n  close(): void;\n}\n\n/**\n * Native statement interface\n * Thin wrapper around TursoStatementHostObject\n */\nexport interface NativeStatement {\n  // Bind methods\n  bindPositionalNull(position: number): number;\n  bindPositionalInt(position: number, value: number): number;\n  bindPositionalDouble(position: number, value: number): number;\n  bindPositionalBlob(position: number, value: ArrayBuffer): number;\n  bindPositionalText(position: number, value: string): number;\n\n  // Execution methods\n  execute(): { status: number; rowsChanged: number };\n  step(): number;  // Returns status code\n  runIo(): number;\n  reset(): void;\n  finalize(): number;\n\n  // Query methods\n  nChange(): number;\n  columnCount(): number;\n  columnName(index: number): string | null;\n  rowValueKind(index: number): number;  // TursoType enum\n  rowValueBytesCount(index: number): number;\n  rowValueBytesPtr(index: number): ArrayBuffer | null;\n  rowValueText(index: number): string;\n  rowValueInt(index: number): number;\n  rowValueDouble(index: number): number;\n\n  // Parameter methods\n  namedPosition(name: string): number;\n  parametersCount(): number;\n\n  // Bulk row reading (native-side step+read loop)\n  getAllRows(): { status: number; rows: Row[] };\n}\n\n// ============================================================================\n// Sync SDK-KIT Types (Embedded Replica)\n// ============================================================================\n\n/**\n * Native sync database interface (embedded replica)\n * Thin wrapper around TursoSyncDatabaseHostObject\n */\nexport interface NativeSyncDatabase {\n  // Async operations - return NativeSyncOperation\n  open(): NativeSyncOperation;\n  create(): NativeSyncOperation;\n  connect(): NativeSyncOperation;\n  stats(): NativeSyncOperation;\n  checkpoint(): NativeSyncOperation;\n  pushChanges(): NativeSyncOperation;\n  waitChanges(): NativeSyncOperation;\n  applyChanges(changes: NativeSyncChanges): NativeSyncOperation;\n\n  // IO queue management\n  ioTakeItem(): NativeSyncIoItem | null;\n  ioStepCallbacks(): void;\n\n  close(): void;\n}\n\n/**\n * Native sync operation interface\n * Thin wrapper around TursoSyncOperationHostObject\n * Represents an async operation that must be driven by calling resume()\n */\nexport interface NativeSyncOperation {\n  resume(): number;  // Returns status code (TURSO_DONE, TURSO_IO, etc.)\n  resultKind(): number;  // Returns result type enum\n  extractConnection(): NativeConnection;\n  extractChanges(): NativeSyncChanges | null;\n  extractStats(): SyncStats;\n}\n\n/**\n * Native sync IO item interface\n * Thin wrapper around TursoSyncIoItemHostObject\n * Represents an IO request that JavaScript must process using fetch() or fs\n */\nexport interface NativeSyncIoItem {\n  getKind(): 'HTTP' | 'FULL_READ' | 'FULL_WRITE' | 'NONE';\n  getHttpRequest(): HttpRequest;\n  getFullReadPath(): string;\n  getFullWriteRequest(): FullWriteRequest;\n\n  // Completion methods\n  poison(error: string): void;\n  setStatus(statusCode: number): void;\n  pushBuffer(data: ArrayBuffer): void;\n  done(): void;\n}\n\n/**\n * Native sync changes interface\n * Thin wrapper around TursoSyncChangesHostObject\n * Represents changes fetched from remote (opaque, passed to applyChanges)\n */\nexport interface NativeSyncChanges {\n  // Mostly opaque - just passed to applyChanges()\n}\n\n// ============================================================================\n// Turso Status Codes\n// ============================================================================\n\nexport enum TursoStatus {\n  OK = 0,\n  DONE = 1,\n  ROW = 2,\n  IO = 3,\n  BUSY = 4,\n  INTERRUPT = 5,\n  BUSY_SNAPSHOT = 6,\n  ERROR = 127,\n  MISUSE = 128,\n  CONSTRAINT = 129,\n  READONLY = 130,\n  DATABASE_FULL = 131,\n  NOTADB = 132,\n  CORRUPT = 133,\n  IOERR = 134,\n}\n\n// ============================================================================\n// Turso Value Types\n// ============================================================================\n\nexport enum TursoType {\n  UNKNOWN = 0,\n  INTEGER = 1,\n  REAL = 2,\n  TEXT = 3,\n  BLOB = 4,\n  NULL = 5,\n}\n\n// ============================================================================\n// Tracing / Logging Types\n// ============================================================================\n\n/**\n * Log level for Turso tracing\n */\nexport type TursoTracingLevel = 'error' | 'warn' | 'info' | 'debug' | 'trace';\n\n/**\n * A single log entry emitted by the Turso engine\n */\nexport interface TursoLog {\n  message: string;\n  target: string;\n  file: string;\n  timestamp: number;\n  line: number;\n  level: TursoTracingLevel;\n}\n\n/**\n * Logger callback function\n */\nexport type TursoLoggerFn = (log: TursoLog) => void;\n\n// ============================================================================\n// Sync Operation Result Types\n// ============================================================================\n\nexport enum SyncOperationResultType {\n  NONE = 0,\n  CONNECTION = 1,\n  CHANGES = 2,\n  STATS = 3,\n}\n\n// ============================================================================\n// Public API Types (High-level TypeScript)\n// ============================================================================\n\n/**\n * Supported SQLite value types for the public API\n */\nexport type SQLiteValue = null | number | string | ArrayBuffer;\n\n/**\n * Parameters that can be bound to SQL statements\n */\nexport type BindParams =\n  | SQLiteValue[]\n  | Record<string, SQLiteValue>\n  | SQLiteValue;\n\n/**\n * Result of a run() or exec() operation\n */\nexport interface RunResult {\n  /** Number of rows changed by the statement */\n  changes: number;\n  /** Last inserted row ID */\n  lastInsertRowid: number;\n}\n\n/**\n * A row returned from a query\n */\nexport type Row = Record<string, SQLiteValue>;\n\n/**\n * Encryption options (matches JavaScript bindings)\n */\nexport interface EncryptionOpts {\n  /** base64 encoded encryption key (must be either 16 or 32 bytes depending on cipher) */\n  key: string;\n  /**\n   * encryption cipher algorithm\n   * - aes256gcm, aes128gcm, chacha20poly1305: 28 reserved bytes\n   * - aegis128l, aegis128x2, aegis128x4: 32 reserved bytes\n   * - aegis256, aegis256x2, aegis256x4: 48 reserved bytes\n   */\n  cipher:\n    | 'aes256gcm'\n    | 'aes128gcm'\n    | 'chacha20poly1305'\n    | 'aegis128l'\n    | 'aegis128x2'\n    | 'aegis128x4'\n    | 'aegis256'\n    | 'aegis256x2'\n    | 'aegis256x4';\n}\n\n/**\n * Database options (matches JavaScript bindings)\n * Single unified config for both local and sync databases\n */\nexport interface DatabaseOpts {\n  /**\n   * Local path where to store database file (e.g. local.db)\n   * Sync database will write several files with that prefix\n   * (e.g. local.db-info, local.db-wal, etc)\n   */\n  path: string;\n\n  /**\n   * Optional URL of the remote database (e.g. libsql://db-org.turso.io)\n   * If omitted - local-only database will be created\n   *\n   * You can also provide function which will return URL or null\n   * In this case local database will be created and sync will be \"switched-on\"\n   * whenever the url returns non-empty value\n   */\n  url?: string | (() => string | null);\n\n  /**\n   * Auth token for the remote database\n   * (can be either static string or function which will provide short-lived credentials)\n   */\n  authToken?: string | (() => Promise<string>);\n\n  /**\n   * Arbitrary client name which can be used to distinguish clients internally\n   * The library will guarantee uniqueness of the clientId by appending unique suffix\n   */\n  clientName?: string;\n\n  /**\n   * Optional remote encryption parameters if cloud database was encrypted\n   */\n  remoteEncryption?: EncryptionOpts;\n\n  /**\n   * Optional long-polling timeout for pull operation\n   * If not set - no timeout is applied\n   */\n  longPollTimeoutMs?: number;\n\n  /**\n   * Bootstrap database if empty; if set - client will be able to connect\n   * to fresh db only when network is online\n   */\n  bootstrapIfEmpty?: boolean;\n\n  /**\n   * Optional parameter to enable partial sync for the database\n   * WARNING: This feature is EXPERIMENTAL\n   */\n  partialSyncExperimental?: {\n    /**\n     * Bootstrap strategy configuration\n     * - prefix strategy loads first N bytes locally at startup\n     * - query strategy loads pages touched by the provided SQL statement\n     */\n    bootstrapStrategy:\n      | { kind: 'prefix'; length: number }\n      | { kind: 'query'; query: string };\n    /**\n     * Optional segment size which makes sync engine load pages in batches\n     * (so, if loading page 1 with segment_size=128kb then 32 pages [1..32] will be loaded)\n     */\n    segmentSize?: number;\n    /**\n     * Optional parameter which makes sync engine prefetch pages which probably\n     * will be accessed soon\n     */\n    prefetch?: boolean;\n  };\n}\n\n\n/**\n * Sync stats returned by stats() operation\n */\nexport interface SyncStats {\n  cdcOperations: number;\n  mainWalSize: number;\n  revertWalSize: number;\n  lastPullUnixTime: number;\n  lastPushUnixTime: number;\n  networkSentBytes: number;\n  networkReceivedBytes: number;\n  revision: string | null;\n}\n\n/**\n * HTTP request from sync engine\n */\nexport interface HttpRequest {\n  url: string | null;\n  method: string;\n  path: string;\n  headers: Record<string, string>;\n  body: ArrayBuffer | null;\n}\n\n/**\n * Full write request from sync engine\n */\nexport interface FullWriteRequest {\n  path: string;\n  content: ArrayBuffer | null;\n}\n\n// ============================================================================\n// Global Turso Proxy Interface\n// ============================================================================\n\n/**\n * Native proxy interface exposed via JSI\n */\nexport interface TursoProxy {\n  newDatabase(path: string, config?: any): NativeDatabase;\n  newSyncDatabase(dbConfig: any, syncConfig: any): NativeSyncDatabase;\n  version(): string;\n  setup(options: { logLevel?: string; logger?: TursoLoggerFn }): void;\n  fsReadFile(path: string): ArrayBuffer | null;\n  fsWriteFile(path: string, data: ArrayBuffer): void;\n}\n\n/**\n * Native module interface (React Native bridge)\n */\nexport interface TursoNativeModule {\n  install(): boolean;\n  // Constants exposed by native module\n  ANDROID_DATABASE_PATH?: string;\n  ANDROID_FILES_PATH?: string;\n  ANDROID_EXTERNAL_FILES_PATH?: string;\n  IOS_DOCUMENT_PATH?: string;\n  IOS_LIBRARY_PATH?: string;\n}\n\n/**\n * Global __TursoProxy object injected by native code\n */\ndeclare global {\n  // eslint-disable-next-line no-var\n  var __TursoProxy: TursoProxy;\n}\n\nexport {};\n"
  },
  {
    "path": "bindings/react-native/templates/turso-sync-sdk-kit.xcframework/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>AvailableLibraries</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>LibraryIdentifier</key>\n\t\t\t<string>ios-arm64</string>\n\t\t\t<key>LibraryPath</key>\n\t\t\t<string>turso-sync-sdk-kit.framework</string>\n\t\t\t<key>SupportedArchitectures</key>\n\t\t\t<array>\n\t\t\t\t<string>arm64</string>\n\t\t\t</array>\n\t\t\t<key>SupportedPlatform</key>\n\t\t\t<string>ios</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>LibraryIdentifier</key>\n\t\t\t<string>ios-arm64-simulator</string>\n\t\t\t<key>LibraryPath</key>\n\t\t\t<string>turso-sync-sdk-kit.framework</string>\n\t\t\t<key>SupportedArchitectures</key>\n\t\t\t<array>\n\t\t\t\t<string>arm64</string>\n\t\t\t</array>\n\t\t\t<key>SupportedPlatform</key>\n\t\t\t<string>ios</string>\n\t\t\t<key>SupportedPlatformVariant</key>\n\t\t\t<string>simulator</string>\n\t\t</dict>\n\t</array>\n\t<key>CFBundlePackageType</key>\n\t<string>XFWK</string>\n\t<key>XCFrameworkFormatVersion</key>\n\t<string>1.0</string>\n\t<key>CFBundleVersion</key>\n\t<string>1.0.0</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>1.0.0</string>\n</dict>\n</plist>"
  },
  {
    "path": "bindings/react-native/templates/turso-sync-sdk-kit.xcframework/ios-arm64/turso-sync-sdk-kit.framework/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n  <key>CFBundleDevelopmentRegion</key>\n  <string>en</string>\n  <key>CFBundleExecutable</key>\n  <string>turso-sync-sdk-kit</string>\n  <key>CFBundleIdentifier</key>\n  <string>com.turso.turso-sync-sdk-kit</string>\n  <key>CFBundleInfoDictionaryVersion</key>\n  <string>6.0</string>\n  <key>CFBundlePackageType</key>\n  <string>FMWK</string>\n  <key>CFBundleSignature</key>\n  <string>????</string>\n  <key>CFBundleVersion</key>\n  <string>1.0.0</string>\n  <key>CFBundleShortVersionString</key>\n  <string>1.0.0</string>\n  <key>MinimumOSVersion</key>\n  <string>13.0</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "bindings/react-native/templates/turso-sync-sdk-kit.xcframework/ios-arm64-simulator/turso-sync-sdk-kit.framework/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n  <key>CFBundleDevelopmentRegion</key>\n  <string>en</string>\n  <key>CFBundleExecutable</key>\n  <string>turso-sync-sdk-kit</string>\n  <key>CFBundleIdentifier</key>\n  <string>com.turso.turso-sync-sdk-kit</string>\n  <key>CFBundleInfoDictionaryVersion</key>\n  <string>6.0</string>\n  <key>CFBundlePackageType</key>\n  <string>FMWK</string>\n  <key>CFBundleSignature</key>\n  <string>????</string>\n  <key>CFBundleVersion</key>\n  <string>1.0.0</string>\n  <key>CFBundleShortVersionString</key>\n  <string>1.0.0</string>\n  <key>MinimumOSVersion</key>\n  <string>13.0</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "bindings/react-native/tsconfig.build.json",
    "content": "{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"outDir\": \"lib/typescript\",\n    \"rootDir\": \"src\"\n  },\n  \"include\": [\"src\"],\n  \"exclude\": [\"**/__tests__\", \"**/__mocks__\"]\n}\n"
  },
  {
    "path": "bindings/react-native/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"rootDir\": \".\",\n    \"allowUnreachableCode\": false,\n    \"allowUnusedLabels\": false,\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"jsx\": \"react\",\n    \"lib\": [\"ES2020\"],\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"node\",\n    \"noFallthroughCasesInSwitch\": true,\n    \"noImplicitReturns\": true,\n    \"noImplicitUseStrict\": false,\n    \"noStrictGenericChecks\": false,\n    \"noUncheckedIndexedAccess\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"resolveJsonModule\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"target\": \"ES2020\",\n    \"verbatimModuleSyntax\": true\n  }\n}\n"
  },
  {
    "path": "bindings/react-native/turso-sync-react-native.podspec",
    "content": "require \"json\"\n\npackage = JSON.parse(File.read(File.join(__dir__, \"package.json\")))\n\nPod::Spec.new do |s|\n  s.name         = \"turso-sync-react-native\"\n  s.version      = package[\"version\"]\n  s.summary      = package[\"description\"]\n  s.homepage     = package[\"homepage\"]\n  s.license      = package[\"license\"]\n  s.authors      = package[\"author\"]\n\n  s.platforms    = { :ios => \"13.0\" }\n  s.source       = { :git => \"https://github.com/tursodatabase/turso.git\", :tag => \"#{s.version}\" }\n\n  s.source_files = [\n    \"ios/**/*.{h,m,mm}\",\n    \"cpp/**/*.{h,hpp,cpp}\"\n  ]\n\n  # Vendored Rust XCFramework (handles device + simulator automatically)\n  s.vendored_frameworks = \"libs/ios/turso-sync-sdk-kit.xcframework\"\n\n  # Explicitly export C headers from the vendored xcframework\n  s.static_framework = true\n  s.public_header_files = \"libs/ios/turso-sync-sdk-kit.xcframework/**/Headers/*.h\"\n  s.header_mappings_dir = \"libs/ios/turso-sync-sdk-kit.xcframework\"\n\n  # Header search paths\n  s.pod_target_xcconfig = {\n    \"CLANG_CXX_LANGUAGE_STANDARD\" => \"c++20\",\n    \"HEADER_SEARCH_PATHS\" => [\n      \"$(PODS_TARGET_SRCROOT)/cpp\",\n      \"$(PODS_TARGET_SRCROOT)/libs/ios\"\n    ].join(\" \"),\n    \"OTHER_LDFLAGS\" => \"-lc++\",\n    \"DEFINES_MODULE\" => \"YES\",\n    \"GCC_PRECOMPILE_PREFIX_HEADER\" => \"NO\"\n  }\n\n  # User header search paths for consumers\n  s.user_target_xcconfig = {\n    \"HEADER_SEARCH_PATHS\" => \"$(PODS_ROOT)/turso-sync-react-native/cpp $(PODS_ROOT)/turso-sync-react-native/libs/ios\"\n  }\n\n  # Build script to compile Rust\n  s.script_phase = {\n    :name => \"Build Turso Rust Library\",\n    :script => 'set -e; cd \"${PODS_TARGET_SRCROOT}\"; if [ ! -d \"libs/ios/turso-sync-sdk-kit.xcframework\" ]; then echo \"Building Rust library for iOS...\"; make ios; fi',\n    :execution_position => :before_compile,\n    :shell_path => \"/bin/bash\"\n  }\n\n  # Install React Native module dependencies (includes React-Core, turbomodule, etc.)\n  install_modules_dependencies(s)\nend\n"
  },
  {
    "path": "bindings/rust/Cargo.toml",
    "content": "# Copyright 2025 the Turso authors. All rights reserved. MIT license.\n\n[package]\nname = \"turso\"\nversion.workspace = true\nauthors.workspace = true\nedition.workspace = true\nlicense.workspace = true\nrepository.workspace = true\ndescription = \"Turso Rust API\"\n\n[lints]\nworkspace = true\n\n[features]\ndefault = [\"mimalloc\"]\nmimalloc = [\"dep:mimalloc\"]\nsync = [\n    \"dep:hyper\",\n    \"dep:tokio\",\n    \"dep:hyper-tls\",\n    \"dep:hyper-util\",\n    \"dep:http-body-util\",\n    \"dep:bytes\",\n]\n\n[dependencies]\nturso_sdk_kit = { workspace = true }\nturso_sync_sdk_kit = { workspace = true }\nthiserror = { workspace = true }\ntracing-subscriber.workspace = true\ntracing.workspace = true\nmimalloc = { workspace = true, optional = true }\n\nhyper = { version = \"1.8.1\", features = [\"http1\"], optional = true }\ntokio = { workspace = true, features = [\"full\"], optional = true }\nhyper-tls = { version = \"0.6.0\", optional = true }\nhyper-util = { version = \"0.1.19\", features = [\n    \"http1\",\n    \"tokio\",\n], optional = true }\nhttp-body-util = { version = \"0.1.3\", optional = true }\nbytes = { version = \"1.11.0\", optional = true }\n\n[[example]]\nname = \"sync_example\"\nrequired-features = [\"sync\"]\n\n[[example]]\nname = \"concurrent_writes\"\n\n[dev-dependencies]\ntempfile = { workspace = true }\ntokio = { workspace = true, features = [\"full\"] }\nrand.workspace = true\nrand_chacha = { workspace = true }\nanyhow.workspace = true\nreqwest = { version = \"0.12.28\", features = [\"json\"] }\nserde_json.workspace = true\n\n"
  },
  {
    "path": "bindings/rust/README.md",
    "content": "# turso\n\nThe next evolution of SQLite: A high-performance, SQLite-compatible database library for Rust\n\n## Features\n\n- **SQLite Compatible**: Similar interface to rusqlite with familiar API apart from using async Rust\n- **High Performance**: Built with Rust for maximum speed and efficiency  \n- **Async/Await Support**: Native async operations with tokio support\n- **In-Process**: No network overhead, runs directly in your application\n- **Cross-Platform**: Supports Linux, macOS, and Windows\n- **Transaction Support**: Full ACID transactions with rollback support\n- **Prepared Statements**: Optimized query execution with parameter binding\n- **Cloud Sync**: Sync with Turso Cloud using `Builder::new_remote()` (optional `sync` feature)\n\n## Installation\n\nAdd this to your `Cargo.toml`:\n\n```toml\n[dependencies]\nturso = \"0.4.3\"\ntokio = { version = \"1.0\", features = [\"full\"] }\n```\n\nFor cloud sync capabilities, enable the `sync` feature:\n\n```toml\n[dependencies]\nturso = { version = \"0.4.3\", features = [\"sync\"] }\ntokio = { version = \"1.0\", features = [\"full\"] }\n```\n\n## Quick Start\n\n### In-Memory Database\n\n```rust\nuse turso::Builder;\n\n#[tokio::main]\nasync fn main() -> turso::Result<()> {\n    // Create an in-memory database\n    let db = Builder::new_local(\":memory:\").build().await?;\n    let conn = db.connect()?;\n\n    // Create a table\n    conn.execute(\n        \"CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)\",\n        ()\n    ).await?;\n\n    // Insert data\n    conn.execute(\n        \"INSERT INTO users (name, email) VALUES (?1, ?2)\",\n        [\"Alice\", \"alice@example.com\"]\n    ).await?;\n\n    conn.execute(\n        \"INSERT INTO users (name, email) VALUES (?1, ?2)\", \n        [\"Bob\", \"bob@example.com\"]\n    ).await?;\n\n    // Query data\n    let mut rows = conn.query(\"SELECT * FROM users\", ()).await?;\n    \n    while let Some(row) = rows.next().await? {\n        let id = row.get_value(0)?;\n        let name = row.get_value(1)?;\n        let email = row.get_value(2)?;\n        println!(\"User: {} - {} ({})\", \n            id.as_integer().unwrap_or(&0), \n            name.as_text().unwrap_or(&\"\".to_string()), \n            email.as_text().unwrap_or(&\"\".to_string())\n        );\n    }\n\n    Ok(())\n}\n```\n\n### File-Based Database\n\n```rust\nuse turso::Builder;\n\n#[tokio::main] \nasync fn main() -> turso::Result<()> {\n    // Create or open a database file\n    let db = Builder::new_local(\"my-database.db\").build().await?;\n    let conn = db.connect()?;\n\n    // Create a table\n    conn.execute(\n        r#\"CREATE TABLE IF NOT EXISTS posts (\n            id INTEGER PRIMARY KEY AUTOINCREMENT,\n            title TEXT NOT NULL,\n            content TEXT,\n            created_at DATETIME DEFAULT CURRENT_TIMESTAMP\n        )\"#,\n        ()\n    ).await?;\n\n    // Insert a post\n    let rows_affected = conn.execute(\n        \"INSERT INTO posts (title, content) VALUES (?1, ?2)\",\n        [\"Hello World\", \"This is my first blog post!\"]\n    ).await?;\n\n    println!(\"Inserted {} rows\", rows_affected);\n\n    Ok(())\n}\n```\n\n### Synced Database\n\nSync your local database with Turso Cloud:\n\n```rust\nuse turso::sync::Builder;\n\n#[tokio::main]\nasync fn main() -> turso::Result<()> {\n    // Create a synced database\n    let db = Builder::new_remote(\"local.db\")\n        .with_remote_url(\"libsql://your-database.turso.io\")\n        .with_auth_token(\"your-token\")\n        .build()\n        .await?;\n\n    let conn = db.connect().await?;\n\n    // Create a table and insert data\n    conn.execute(\n        \"CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, content TEXT)\",\n        ()\n    ).await?;\n\n    conn.execute(\n        \"INSERT INTO notes (content) VALUES (?1)\",\n        [\"My first synced note\"]\n    ).await?;\n\n    // Push local changes to remote\n    db.push().await?;\n\n    // Pull remote changes to local\n    db.pull().await?;\n\n    Ok(())\n}\n```\n\n## API Reference\n\n### Builder\n\nCreate a new database:\n\n```rust\nlet db = Builder::new_local(\":memory:\").build().await?;\nlet db = Builder::new_local(\"data.db\").build().await?;\n```\n\n### Connection\n\nExecute queries and statements:\n\n```rust\n// Execute SQL directly\nlet rows_affected = conn.execute(\"INSERT INTO users (name) VALUES (?1)\", [\"Alice\"]).await?;\n\n// Query for multiple rows\nlet mut rows = conn.query(\"SELECT * FROM users WHERE age > ?1\", [18]).await?;\n\n// Prepare statements for reuse\nlet mut stmt = conn.prepare(\"SELECT * FROM users WHERE id = ?1\").await?;\nlet mut rows = stmt.query([42]).await?;\n\n// Execute prepared statements\nlet rows_affected = stmt.execute([\"Alice\"]).await?;\n```\n\n### Working with Results\n\n```rust\nuse futures_util::TryStreamExt;\n\nlet mut rows = conn.query(\"SELECT name, email FROM users\", ()).await?;\n\nwhile let Some(row) = rows.try_next().await? {\n    let name = row.get_value(0)?.as_text().unwrap_or(&\"\".to_string());\n    let email = row.get_value(1)?.as_text().unwrap_or(&\"\".to_string());\n    println!(\"{}: {}\", name, email);\n}\n```\n\n### Sync API Reference\n\n#### sync::Builder\n\nCreate a synced database that synchronizes with Turso Cloud:\n\n```rust\nuse turso::sync::Builder;\n\nlet db = Builder::new_remote(\"local.db\")       // Local database path (or \":memory:\")\n    .with_remote_url(\"libsql://db.turso.io\")   // Remote URL (https://, http://, or libsql://)\n    .with_auth_token(\"your-token\")              // Authorization token\n    .bootstrap_if_empty(true)                   // Download schema on first sync (default: true)\n    .with_remote_encryption(\"base64-encoded-key\", RemoteEncryptionCipher::Aes256Gcm) // Optional remote encryption\n    .build()\n    .await?;\n```\n\n#### sync::Database\n\nOperations for synced databases:\n\n```rust\n// Push local changes to remote\ndb.push().await?;\n\n// Pull remote changes (returns true if changes were applied)\nlet had_changes = db.pull().await?;\n\n// Force WAL checkpoint\ndb.checkpoint().await?;\n\n// Get sync statistics\nlet stats = db.stats().await?;\nprintln!(\"Received: {} bytes\", stats.network_received_bytes);\nprintln!(\"Sent: {} bytes\", stats.network_sent_bytes);\nprintln!(\"WAL size: {} bytes\", stats.main_wal_size);\n```\n\n## License\n\nMIT\n\n## Support\n\n- [GitHub Issues](https://github.com/tursodatabase/turso/issues)\n- [Discord Community](https://discord.gg/turso)\n"
  },
  {
    "path": "bindings/rust/examples/README.md",
    "content": "# Rust Examples\n\n```bash\ncargo run --package turso --example example\ncargo run --package turso --example example_struct\ncargo run --package turso --example concurrent_writes\ncargo run --package turso --example sync_example --features sync  # requires Turso Cloud\n```\n\n## Examples\n\n| Example | Description |\n|---------|-------------|\n| `example` | Basic queries, prepared statements, and pragma usage |\n| `example_struct` | Mapping rows to structs using transactions |\n| `concurrent_writes` | MVCC mode: 16 concurrent writers using `BEGIN CONCURRENT` |\n| `sync_example` | Syncing with Turso Cloud (set `TURSO_REMOTE_URL` / `TURSO_AUTH_TOKEN`) |\n"
  },
  {
    "path": "bindings/rust/examples/concurrent_writes.rs",
    "content": "//! Concurrent writes with MVCC\n//!\n//! `BEGIN CONCURRENT` lets multiple connections write at the same time without\n//! holding an exclusive lock.  Conflicts are detected at commit time: if two\n//! transactions worked on same rows, the later one receives a conflict\n//! error and must roll back and retry.\n\nuse rand::Rng;\nuse tempfile::NamedTempFile;\nuse turso::{Builder, Error};\n\nfn is_retryable(e: &Error) -> bool {\n    matches!(e, Error::Busy(_) | Error::BusySnapshot(_))\n        || matches!(e, Error::Error(msg) if msg.contains(\"conflict\"))\n}\n\n#[tokio::main]\nasync fn main() -> Result<(), Error> {\n    let tmp = NamedTempFile::new().expect(\"failed to create temp file\");\n    let db = Builder::new_local(tmp.path().to_str().unwrap())\n        .build()\n        .await?;\n\n    let conn = db.connect()?;\n    conn.pragma_update(\"journal_mode\", \"'mvcc'\").await?;\n    conn.execute(\"CREATE TABLE hits (val INTEGER)\", ()).await?;\n\n    let mut handles = Vec::new();\n    for _ in 0..16 {\n        let db = db.clone();\n        handles.push(tokio::spawn(async move {\n            let val = rand::rng().random_range(1..=100);\n            let conn = db.connect()?;\n            loop {\n                conn.execute(\"BEGIN CONCURRENT\", ()).await?;\n                let result = conn\n                    .execute(&format!(\"INSERT INTO hits VALUES ({val})\"), ())\n                    .await\n                    .and(conn.execute(\"COMMIT\", ()).await);\n                match result {\n                    Ok(_) => return Ok::<_, Error>(val),\n                    Err(ref e) if is_retryable(e) => {\n                        let _ = conn.execute(\"ROLLBACK\", ()).await;\n                        tokio::task::yield_now().await;\n                    }\n                    Err(e) => {\n                        let _ = conn.execute(\"ROLLBACK\", ()).await;\n                        return Err(e);\n                    }\n                }\n            }\n        }));\n    }\n\n    for handle in handles {\n        let val = handle.await.expect(\"task panicked\")?;\n        println!(\"inserted val={val}\");\n    }\n\n    let mut rows = conn.query(\"SELECT COUNT(*) FROM hits\", ()).await?;\n    if let Some(row) = rows.next().await? {\n        println!(\"total rows: {}\", row.get::<i64>(0)?);\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "bindings/rust/examples/example.rs",
    "content": "use turso::{Builder, Error};\n\n#[tokio::main]\nasync fn main() -> Result<(), Error> {\n    let db = Builder::new_local(\":memory:\")\n        .build()\n        .await\n        .expect(\"Turso Failed to Build memory db\");\n\n    let conn = db.connect()?;\n\n    conn.query(\"select 1; select 1;\", ()).await?;\n\n    conn.execute(\n        \"CREATE TABLE IF NOT EXISTS users (email TEXT, age INTEGER)\",\n        (),\n    )\n    .await?;\n\n    conn.pragma_query(\"journal_mode\", |row| {\n        println!(\"{:?}\", row.get_value(0));\n        Ok(())\n    })\n    .await?;\n\n    let mut stmt = conn\n        .prepare(\"INSERT INTO users (email, age) VALUES (?1, ?2)\")\n        .await?;\n\n    stmt.execute([\"foo@example.com\", &21.to_string()]).await?;\n\n    let mut stmt = conn.prepare(\"SELECT * FROM users WHERE email = ?1\").await?;\n\n    let mut rows = stmt.query([\"foo@example.com\"]).await?;\n\n    let row = rows.next().await?;\n\n    assert!(\n        row.is_some(),\n        \"The row that was just inserted hasn't been found\"\n    );\n\n    if let Some(row_values) = row {\n        let email = row_values.get_value(0)?;\n        let age = row_values.get_value(1)?;\n        println!(\"Row: {email:?} {age:?}\");\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "bindings/rust/examples/example_struct.rs",
    "content": "use turso::{transaction::Transaction, Builder, Connection, Error};\n\n#[derive(Debug)]\nstruct User {\n    email: String,\n    age: i32,\n}\n\nasync fn create_tables(conn: &Connection) -> Result<(), Error> {\n    conn.execute(\n        \"CREATE TABLE IF NOT EXISTS users (email TEXT, age INTEGER)\",\n        (),\n    )\n    .await?;\n    Ok(())\n}\n\nasync fn insert_users(tx: &Transaction<'_>) -> Result<(), Error> {\n    let mut stmt = tx\n        .prepare(\"INSERT INTO users (email, age) VALUES (?1, ?2)\")\n        .await?;\n    stmt.execute([\"foo@example.com\", &21.to_string()]).await?;\n    stmt.execute([\"bar@example.com\", &22.to_string()]).await?;\n    Ok(())\n}\n\nasync fn list_users(conn: &Connection) -> Result<(), Error> {\n    let mut stmt = conn\n        .prepare(\"SELECT * FROM users WHERE email like ?1\")\n        .await?;\n\n    let mut rows = stmt.query([\"%@example.com\"]).await?;\n\n    while let Some(row) = rows.next().await? {\n        let u: User = User {\n            email: row.get(0)?,\n            age: row.get(1)?,\n        };\n        println!(\"Row: {} {}\", u.email, u.age);\n    }\n    Ok(())\n}\n\n#[tokio::main]\nasync fn main() -> Result<(), Error> {\n    let db = Builder::new_local(\":memory:\")\n        .build()\n        .await\n        .expect(\"Turso Failed to Build memory db\");\n\n    let mut conn = db.connect()?;\n\n    create_tables(&conn).await?;\n    let tx = conn.transaction().await?;\n    insert_users(&tx).await?;\n    tx.commit().await?;\n    list_users(&conn).await?;\n\n    Ok(())\n}\n"
  },
  {
    "path": "bindings/rust/examples/sync_example.rs",
    "content": "//! Turso Database Sync example with Turso Cloud (with optional remote encryption)\n//!\n//! Environment variables:\n//!   TURSO_REMOTE_URL              - Remote database URL (default: http://localhost:8080)\n//!   TURSO_AUTH_TOKEN              - Auth token (optional)\n//!   TURSO_REMOTE_ENCRYPTION_KEY   - Base64-encoded encryption key (optional)\n//!   TURSO_REMOTE_ENCRYPTION_CIPHER - Cipher name (default: aes256gcm)\n//!\n\nuse std::env;\n\nuse turso::sync::{Builder, RemoteEncryptionCipher};\nuse turso::Error;\n\n#[tokio::main]\nasync fn main() -> Result<(), Error> {\n    let remote_url =\n        env::var(\"TURSO_REMOTE_URL\").unwrap_or_else(|_| \"http://localhost:8080\".to_string());\n    let auth_token = env::var(\"TURSO_AUTH_TOKEN\").ok();\n    let encryption_key = env::var(\"TURSO_REMOTE_ENCRYPTION_KEY\").ok();\n    let encryption_cipher = env::var(\"TURSO_REMOTE_ENCRYPTION_CIPHER\")\n        .unwrap_or_else(|_| \"aes256gcm\".to_string())\n        .parse::<RemoteEncryptionCipher>()\n        .expect(\"invalid cipher\");\n\n    println!(\"Remote URL: {remote_url}\");\n    println!(\"Auth Token: {}\", auth_token.is_some());\n    println!(\"Encryption: {}\", encryption_key.is_some());\n    if encryption_key.is_some() {\n        println!(\"Cipher: {encryption_cipher:?}\");\n    }\n\n    let mut builder = Builder::new_remote(\":memory:\").with_remote_url(&remote_url);\n\n    if let Some(token) = auth_token {\n        builder = builder.with_auth_token(token);\n    }\n\n    if let Some(key) = encryption_key {\n        builder = builder.with_remote_encryption(key, encryption_cipher);\n    }\n\n    let db = builder.build().await?;\n    let conn = db.connect().await?;\n\n    conn.execute(\"CREATE TABLE IF NOT EXISTS t (x TEXT)\", ())\n        .await?;\n\n    let mut stmt = conn.prepare(\"SELECT COUNT(*) FROM t\").await?;\n    let mut rows = stmt.query(()).await?;\n    let count: i64 = if let Some(row) = rows.next().await? {\n        row.get(0)?\n    } else {\n        0\n    };\n    let next = count + 1;\n    conn.execute(&format!(\"INSERT INTO t VALUES ('hello sync #{next}')\"), ())\n        .await?;\n    db.push().await?;\n\n    println!(\"\\nTest table contents:\");\n    let mut stmt = conn.prepare(\"SELECT * FROM t\").await?;\n    let mut rows = stmt.query(()).await?;\n    while let Some(row) = rows.next().await? {\n        println!(\"  Row: {:?}\", row.get_value(0)?);\n    }\n\n    // query sqlite_master for all tables\n    println!(\"\\nDatabase tables:\");\n    let mut stmt = conn\n        .prepare(\"SELECT name, type FROM sqlite_master WHERE type='table'\")\n        .await?;\n    let mut rows = stmt.query(()).await?;\n    while let Some(row) = rows.next().await? {\n        let name = row.get_value(0)?;\n        let typ = row.get_value(1)?;\n        println!(\"  - {typ:?}: {name:?}\");\n    }\n\n    // sho database stats\n    let stats = db.stats().await?;\n    println!(\"\\nDatabase stats:\");\n    println!(\"  Network received: {} bytes\", stats.network_received_bytes);\n    println!(\"  Network sent: {} bytes\", stats.network_sent_bytes);\n    println!(\"  Main WAL size: {} bytes\", stats.main_wal_size);\n\n    println!(\"\\nDone!\");\n    Ok(())\n}\n"
  },
  {
    "path": "bindings/rust/rust-driver-sync.mdx",
    "content": "---\nname: 2025-12-24-rust-driver-sync\n---\n\n<Code id=\"sync\" model=\"openai/gpt-5\" language=\"rust\" output=\"./src/sync.rs\">\n\nTurso - is the SQLite compatible database written in Rust.\nOne of the important features of the Turso - is native ability to sync database with the Cloud in both directions (push local changes and pull remote changes).\n\nYour task is to generate EXTRA functionality on top of the existing Rust driver which will extend regular embedded database with sync capability.\nDo not modify existing driver - its already implemented in the lib.rs\nYour task is to write extra code which will use abstractions from lib.rs and build sync support in the Rust on top of it in the lib.rs file.\n\n# Rules\n\nGeneral rules for driver implementation you **MUST** follow and never go against these rules:\n- USE already implemented driver - DO NOT copy it\n- SET async_io=True for the driver database configuration - because partial sync support requires TURSO_IO to handled externally from the bindings\n- STRUCTURE of the implementation \n    * Declaration order of elements and semantic blocks MUST be exsactly the same\n    * (details and full enumerations omited in the example for brevity but you must generate full code)\n```rs\n/// A builder for `Database`.\npub struct Builder {\n    path: String,\n    remote_url: String,\n    auth_token: Option<String>,\n    client_name: Option<String>,\n    long_poll_timeout: Option<Duration>,\n    bootstrap_if_empty: bool,\n    partial_sync_config_experimental: Option<PartialSyncOpts>,\n}\n\npub struct Database { ... }\n\nimpl Database {\n    pub async fn push(&self) -> crate::Result<()> { ... }\n    pub async fn pull(&self) -> crate::Result<bool> { ... }\n    pub async fn checkpoint(&self) -> crate::Result<()> { ... }\n    pub async fn stats(&self) -> crate::Result<DatabaseSyncStats> { ... }\n    pub async fn connect(&self) -> crate::Result<Connection> { ... }\n}\n\nimpl Builder {\n    pub fn new_remote(path: &str, remote_url: &str) -> Self { ... }\n    /// ... more methods to configure builder with extra parameters ...\n    \n    /// Build the synced database.\n    pub async fn build(self) -> crate::Result<Database> { ... }\n}\n\n```\n- STREAM data from the http request to the completion in chunks and spin async operation in between in order to prevent loading whole response in memory\n- FOCUS on code readability: extract helper functions if it will contribute to the code readability (do not overoptimize - it's fine to have some logic inlined especially if it is not repeated anywhere else)\n- WATCH OUT for variables scopes and do not use variables which are no longer accessible\n- USE hyper to perform HTTP(s) requests\n- ACCEPT https://, http:// protocols and also extra libsql:// protocol which internally should be translated to https://\n- You MUST implement custom poll future to interact with `TursoDatabaseAsyncOperation`\n- You MUST create separate tokio thread for processing HTTP requests queue\n- You MUST provide extra_io callback which will send IO request from the SyncEngineIO queue to separate IO thread\n- You MUST call `step_io_callbacks` in the IO thread after making some progress with IO (pushing more data, finishing request, etc)\n- As IO worker don't have a guaranteed way to track relation between IO and request - you MUST await all futures when IO progressed from IO thread\n    * Do this in the IO worker main loop\n- EXTRACT ALL constants at the top of the sync file\n- DO NOT LOAD full http response body in memory - instead stream it to the completion with `push_buffer` method\n- You MUST support TLS (HTTPS) and add necessary TLS connnector for that purpose\n\n# Implementation\n\n- Annotate public API with types\n- Add comments about public API fields/functions to clarify meaning and usage scenarios\n- Use `turso_sync_database_create()` method for creation of the synced database for now - DO NOT use init + open pair\n- Be careful with hyper typing: `RequestBuilder` is typed by the `body` type and this can cause conflict if you are using `Full` and `Empty` as body\n- Implement proper waking machinery which will connect IoWorker with pending futures\n- Use `Client<HttpsConnector<HttpConnector>, Full<Bytes>>` for hyper client\n    * With `HttpConnector` from `hyper_util::client::legacy::connect::HttpConnector`\n- Use `turso-sync-rust` as client_name if not set by user\n- Write and Read files (FullRead / FullWrite) in NON-CHUNKED mode - since files are usually small (but HTTP payloads can be huge)\n\n# driver\n\nCurrent dreiver implementation consist of following main components:\n\n<File path=\"./src/lib.rs\" />\n<File path=\"./src/connection.rs\" />\n\n# sdk-kit\n\nYour implementation must use sdk-kit for embedded db and sync extension\nLook at the rust API of the kit here:\n\n<File path=\"../../sdk-kit/src/rsapi.rs\" />\n<File path=\"../../sync/sdk-kit/src/rsapi.rs\" />\n\nSync engine provide simple \"step\"-based API and you MUST integrate it to the Rust driver async:\n<File path=\"../../sync/sdk-kit/src/turso_async_operation.rs\" />\n<File path=\"../../sync/sdk-kit/src/sync_engine_io.rs\" />\n\nBe careful with TursoDatabaseAsyncOperation ownership as it is wrapped in unique Box container. \nYou must use it from single place.\n\n# hyper\n\nUse following example to get up-to date understanding of Hyper API:\n\n<Link url=\"https://raw.githubusercontent.com/hyperium/hyper/refs/heads/master/examples/client.rs\" />\n\nFor HTTPS project already installed `hyper_tls` dep. You can inspect example here:\n\n<Link url=\"https://raw.githubusercontent.com/hyperium/hyper-tls/refs/heads/master/examples/client.rs\" />\n\n</Code>"
  },
  {
    "path": "bindings/rust/src/connection.rs",
    "content": "use crate::assert_send_sync;\nuse crate::transaction::DropBehavior;\nuse crate::transaction::TransactionBehavior;\nuse crate::Error;\nuse crate::IntoParams;\nuse crate::Row;\nuse crate::Rows;\nuse crate::Statement;\nuse std::fmt::Debug;\nuse std::sync::atomic::AtomicU8;\nuse std::sync::atomic::Ordering;\nuse std::sync::Arc;\nuse std::sync::Mutex;\nuse std::task::Waker;\npub type Result<T> = std::result::Result<T, Error>;\n\n/// Atomic wrapper for [DropBehavior]\npub(crate) struct AtomicDropBehavior {\n    inner: AtomicU8,\n}\n\nimpl AtomicDropBehavior {\n    fn new(behavior: DropBehavior) -> Self {\n        Self {\n            inner: AtomicU8::new(behavior.into()),\n        }\n    }\n\n    fn load(&self, ordering: Ordering) -> DropBehavior {\n        self.inner.load(ordering).into()\n    }\n\n    pub(crate) fn store(&self, behavior: DropBehavior, ordering: Ordering) {\n        self.inner.store(behavior.into(), ordering);\n    }\n}\n\n// A database connection.\npub struct Connection {\n    /// Inner is an Option so that when a Connection is dropped we can take the inner\n    /// (Actual connection) out of it and put it back into the ConnectionPool\n    /// the only time inner will be None is just before the Connection is freed after the\n    /// inner connection has been recyled into the connection pool\n    inner: Option<Arc<turso_sdk_kit::rsapi::TursoConnection>>,\n    pub(crate) transaction_behavior: TransactionBehavior,\n    /// If there is a dangling transaction after it was dropped without being finished,\n    /// [Connection::dangling_tx] will be set to the [DropBehavior] of the dangling transaction,\n    /// and the corresponding action will be taken when a new transaction is requested\n    /// or the connection queries/executes.\n    /// We cannot do this eagerly on Drop because drop is not async.\n    ///\n    /// By default, the value is [DropBehavior::Ignore] which effectively does nothing.\n    pub(crate) dangling_tx: AtomicDropBehavior,\n    pub(crate) extra_io: Option<Arc<dyn Fn(Waker) -> Result<()> + Send + Sync>>,\n}\n\nassert_send_sync!(Connection);\n\nimpl Clone for Connection {\n    fn clone(&self) -> Self {\n        Self {\n            inner: self.inner.clone(),\n            transaction_behavior: self.transaction_behavior,\n            dangling_tx: AtomicDropBehavior::new(self.dangling_tx.load(Ordering::SeqCst)),\n            extra_io: self.extra_io.clone(),\n        }\n    }\n}\n\nimpl Connection {\n    pub fn create(\n        conn: Arc<turso_sdk_kit::rsapi::TursoConnection>,\n        extra_io: Option<Arc<dyn Fn(Waker) -> Result<()> + Send + Sync>>,\n    ) -> Self {\n        #[allow(clippy::arc_with_non_send_sync)]\n        let connection = Connection {\n            inner: Some(conn),\n            transaction_behavior: TransactionBehavior::Deferred,\n            dangling_tx: AtomicDropBehavior::new(DropBehavior::Ignore),\n            extra_io,\n        };\n        connection\n    }\n\n    pub(crate) async fn maybe_handle_dangling_tx(&self) -> Result<()> {\n        match self.dangling_tx.load(Ordering::SeqCst) {\n            DropBehavior::Rollback => {\n                let mut stmt = self.prepare(\"ROLLBACK\").await?;\n                stmt.execute(()).await?;\n                self.dangling_tx\n                    .store(DropBehavior::Ignore, Ordering::SeqCst);\n            }\n            DropBehavior::Commit => {\n                let mut stmt = self.prepare(\"COMMIT\").await?;\n                stmt.execute(()).await?;\n                self.dangling_tx\n                    .store(DropBehavior::Ignore, Ordering::SeqCst);\n            }\n            DropBehavior::Ignore => {}\n            DropBehavior::Panic => {\n                panic!(\"Transaction dropped unexpectedly.\");\n            }\n        }\n        Ok(())\n    }\n\n    /// Query the database with SQL.\n    pub async fn query(&self, sql: impl AsRef<str>, params: impl IntoParams) -> Result<Rows> {\n        self.maybe_handle_dangling_tx().await?;\n        let mut stmt = self.prepare(sql).await?;\n        stmt.query(params).await\n    }\n\n    /// Execute SQL statement on the database.\n    pub async fn execute(&self, sql: impl AsRef<str>, params: impl IntoParams) -> Result<u64> {\n        self.maybe_handle_dangling_tx().await?;\n        let mut stmt = self.prepare(sql).await?;\n        stmt.execute(params).await\n    }\n\n    /// get the inner connection\n    fn get_inner_connection(&self) -> Result<Arc<turso_sdk_kit::rsapi::TursoConnection>> {\n        match &self.inner {\n            Some(inner) => Ok(inner.clone()),\n            None => Err(Error::Misuse(\"inner connection must be set\".to_string())),\n        }\n    }\n\n    /// Execute a batch of SQL statements on the database.\n    pub async fn execute_batch(&self, sql: impl AsRef<str>) -> Result<()> {\n        self.maybe_handle_dangling_tx().await?;\n        self.prepare_execute_batch(sql).await?;\n        Ok(())\n    }\n\n    /// Prepare a SQL statement for later execution.\n    pub async fn prepare(&self, sql: impl AsRef<str>) -> Result<Statement> {\n        let conn = self.get_inner_connection()?;\n        let stmt = conn.prepare_single(sql)?;\n\n        #[allow(clippy::arc_with_non_send_sync)]\n        let statement = Statement {\n            conn: self.clone(),\n            inner: Arc::new(Mutex::new(stmt)),\n        };\n        Ok(statement)\n    }\n\n    /// Prepare a SQL statement for later execution, caching it in the connection.\n    pub async fn prepare_cached(&self, sql: impl AsRef<str>) -> Result<Statement> {\n        let conn = self.get_inner_connection()?;\n        let stmt = conn.prepare_cached(sql)?;\n\n        #[allow(clippy::arc_with_non_send_sync)]\n        let statement = Statement {\n            conn: self.clone(),\n            inner: Arc::new(Mutex::new(stmt)),\n        };\n        Ok(statement)\n    }\n\n    async fn prepare_execute_batch(&self, sql: impl AsRef<str>) -> Result<()> {\n        self.maybe_handle_dangling_tx().await?;\n        let conn = self.get_inner_connection()?;\n        let mut sql = sql.as_ref();\n        while let Some((stmt, offset)) = conn.prepare_first(sql)? {\n            let mut stmt = Statement {\n                conn: self.clone(),\n                inner: Arc::new(Mutex::new(stmt)),\n            };\n            let _ = stmt.execute(()).await?;\n            sql = &sql[offset..];\n        }\n        Ok(())\n    }\n\n    /// Query a pragma.\n    pub async fn pragma_query<F>(&self, pragma_name: &str, mut f: F) -> Result<()>\n    where\n        F: FnMut(&Row) -> std::result::Result<(), turso_sdk_kit::rsapi::TursoError>,\n    {\n        let sql = format!(\"PRAGMA {pragma_name}\");\n        let mut stmt = self.prepare(&sql).await?;\n        let mut rows = stmt.query(()).await?;\n        while let Some(row) = rows.next().await? {\n            f(&row)?;\n        }\n        Ok(())\n    }\n\n    /// Set a pragma value.\n    pub async fn pragma_update<V: std::fmt::Display>(\n        &self,\n        pragma_name: &str,\n        pragma_value: V,\n    ) -> Result<Vec<Row>> {\n        let sql = format!(\"PRAGMA {pragma_name} = {pragma_value}\");\n        let mut stmt = self.prepare(&sql).await?;\n        let mut rows = stmt.query(()).await?;\n        let mut collected = Vec::new();\n        while let Some(row) = rows.next().await? {\n            collected.push(row);\n        }\n        Ok(collected)\n    }\n\n    /// Returns the rowid of the last row inserted.\n    pub fn last_insert_rowid(&self) -> i64 {\n        let conn = self.get_inner_connection().unwrap();\n        conn.last_insert_rowid()\n    }\n\n    /// Flush dirty pages to disk.\n    /// This will write the dirty pages to the WAL.\n    pub fn cacheflush(&self) -> Result<()> {\n        let conn = self.get_inner_connection()?;\n        conn.cacheflush()?;\n        Ok(())\n    }\n\n    pub fn is_autocommit(&self) -> Result<bool> {\n        let conn = self.get_inner_connection()?;\n        Ok(conn.get_auto_commit())\n    }\n\n    /// Sets maximum total accumuated timeout. If the duration is None or Zero, we unset the busy handler for this Connection\n    ///\n    /// This api defers slighty from: https://www.sqlite.org/c3ref/busy_timeout.html\n    ///\n    /// Instead of sleeping for linear amount of time specified by the user,\n    /// we will sleep in phases, until the the total amount of time is reached.\n    /// This means we first sleep of 1ms, then if we still return busy, we sleep for 2 ms, and repeat until a maximum of 100 ms per phase.\n    ///\n    /// Example:\n    /// 1. Set duration to 5ms\n    /// 2. Step through query -> returns Busy -> sleep/yield for 1 ms\n    /// 3. Step through query -> returns Busy -> sleep/yield for 2 ms\n    /// 4. Step through query -> returns Busy -> sleep/yield for 2 ms (totaling 5 ms of sleep)\n    /// 5. Step through query -> returns Busy -> return Busy to user\n    pub fn busy_timeout(&self, duration: std::time::Duration) -> Result<()> {\n        let conn = self.get_inner_connection()?;\n        conn.set_busy_timeout(duration);\n        Ok(())\n    }\n}\n\nimpl Debug for Connection {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"Connection\").finish()\n    }\n}\n"
  },
  {
    "path": "bindings/rust/src/lib.rs",
    "content": "//! # Turso bindings for Rust\n//!\n//! Turso is an in-process SQL database engine, compatible with SQLite.\n//!\n//! ## Getting Started\n//!\n//! To get started, you first need to create a [`Database`] object and then open a [`Connection`] to it, which you use to query:\n//!\n//! ```rust,no_run\n//! # async fn run() {\n//! use turso::Builder;\n//!\n//! let db = Builder::new_local(\":memory:\").build().await.unwrap();\n//! let conn = db.connect().unwrap();\n//! conn.execute(\"CREATE TABLE IF NOT EXISTS users (email TEXT)\", ()).await.unwrap();\n//! conn.execute(\"INSERT INTO users (email) VALUES ('alice@example.org')\", ()).await.unwrap();\n//! # }\n//! ```\n//!\n//! You can also prepare statements with the [`Connection`] object and then execute the [`Statement`] objects:\n//!\n//! ```rust,no_run\n//! # async fn run() {\n//! # use turso::Builder;\n//! # let db = Builder::new_local(\":memory:\").build().await.unwrap();\n//! # let conn = db.connect().unwrap();\n//! let mut stmt = conn.prepare(\"SELECT * FROM users WHERE email = ?1\").await.unwrap();\n//! let mut rows = stmt.query([\"foo@example.com\"]).await.unwrap();\n//! let row = rows.next().await.unwrap().unwrap();\n//! let value = row.get_value(0).unwrap();\n//! println!(\"Row: {:?}\", value);\n//! # }\n//! ```\n\n#[cfg(all(feature = \"mimalloc\", not(target_family = \"wasm\"), not(miri)))]\n#[global_allocator]\nstatic GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;\n\npub mod connection;\npub mod params;\nmod rows;\npub mod transaction;\npub mod value;\n\n#[cfg(feature = \"sync\")]\npub mod sync;\n\npub use connection::Connection;\nuse turso_sdk_kit::rsapi::TursoError;\npub use value::Value;\n\npub use params::params_from_iter;\npub use params::IntoParams;\n\nuse std::fmt::Debug;\nuse std::future::Future;\nuse std::sync::Arc;\nuse std::sync::Mutex;\nuse std::task::Poll;\n\n// Re-exports rows\npub use crate::rows::{Row, Rows};\n\n/// Assert that a type implements both Send and Sync at compile time.\n/// Usage: assert_send_sync!(MyType);\n/// Usage: assert_send_sync!(Type1, Type2, Type3);\nmacro_rules! assert_send_sync {\n    ($($t:ty),+ $(,)?) => {\n        #[cfg(test)]\n        $(const _: () = {\n            const fn _assert_send<T: ?Sized + Send>() {}\n            const fn _assert_sync<T: ?Sized + Sync>() {}\n            _assert_send::<$t>();\n            _assert_sync::<$t>();\n        };)+\n    };\n}\n\npub(crate) use assert_send_sync;\n\n#[derive(Debug, thiserror::Error)]\npub enum Error {\n    #[error(\"SQL conversion failure: `{0}`\")]\n    ToSqlConversionFailure(BoxError),\n    #[error(\"Query returned no rows\")]\n    QueryReturnedNoRows,\n    #[error(\"Conversion failure: `{0}`\")]\n    ConversionFailure(String),\n    #[error(\"{0}\")]\n    Busy(String),\n    #[error(\"{0}\")]\n    BusySnapshot(String),\n    #[error(\"{0}\")]\n    Interrupt(String),\n    #[error(\"{0}\")]\n    Error(String),\n    #[error(\"{0}\")]\n    Misuse(String),\n    #[error(\"{0}\")]\n    Constraint(String),\n    #[error(\"{0}\")]\n    Readonly(String),\n    #[error(\"{0}\")]\n    DatabaseFull(String),\n    #[error(\"{0}\")]\n    NotAdb(String),\n    #[error(\"{0}\")]\n    Corrupt(String),\n    #[error(\"I/O error ({1}): {0}\")]\n    IoError(std::io::ErrorKind, &'static str),\n}\n\nimpl From<turso_sdk_kit::rsapi::TursoError> for Error {\n    fn from(value: turso_sdk_kit::rsapi::TursoError) -> Self {\n        match value {\n            turso_sdk_kit::rsapi::TursoError::Busy(err) => Error::Busy(err),\n            turso_sdk_kit::rsapi::TursoError::BusySnapshot(err) => Error::BusySnapshot(err),\n            turso_sdk_kit::rsapi::TursoError::Interrupt(err) => Error::Interrupt(err),\n            turso_sdk_kit::rsapi::TursoError::Error(err) => Error::Error(err),\n            turso_sdk_kit::rsapi::TursoError::Misuse(err) => Error::Misuse(err),\n            turso_sdk_kit::rsapi::TursoError::Constraint(err) => Error::Constraint(err),\n            turso_sdk_kit::rsapi::TursoError::Readonly(err) => Error::Readonly(err),\n            turso_sdk_kit::rsapi::TursoError::DatabaseFull(err) => Error::DatabaseFull(err),\n            turso_sdk_kit::rsapi::TursoError::NotAdb(err) => Error::NotAdb(err),\n            turso_sdk_kit::rsapi::TursoError::Corrupt(err) => Error::Corrupt(err),\n            turso_sdk_kit::rsapi::TursoError::IoError(kind, op) => Error::IoError(kind, op),\n        }\n    }\n}\n\npub(crate) type BoxError = Box<dyn std::error::Error + Send + Sync>;\n\npub type Result<T> = std::result::Result<T, Error>;\npub type EncryptionOpts = turso_sdk_kit::rsapi::EncryptionOpts;\n\n/// A builder for `Database`.\npub struct Builder {\n    path: String,\n    enable_encryption: bool,\n    enable_attach: bool,\n    enable_custom_types: bool,\n    enable_index_method: bool,\n    enable_materialized_views: bool,\n    vfs: Option<String>,\n    encryption_opts: Option<turso_sdk_kit::rsapi::EncryptionOpts>,\n}\n\nimpl Builder {\n    /// Create a new local database.\n    pub fn new_local(path: &str) -> Self {\n        Self {\n            path: path.to_string(),\n            enable_encryption: false,\n            enable_attach: false,\n            enable_custom_types: false,\n            enable_index_method: false,\n            enable_materialized_views: false,\n            vfs: None,\n            encryption_opts: None,\n        }\n    }\n\n    pub fn experimental_encryption(mut self, encryption_enabled: bool) -> Self {\n        self.enable_encryption = encryption_enabled;\n        self\n    }\n\n    pub fn with_encryption(mut self, opts: turso_sdk_kit::rsapi::EncryptionOpts) -> Self {\n        self.encryption_opts = Some(opts);\n        self\n    }\n\n    /// Kept for backwards compatibility. Triggers are now always enabled.\n    pub fn experimental_triggers(self, _triggers_enabled: bool) -> Self {\n        self\n    }\n\n    pub fn experimental_attach(mut self, attach_enabled: bool) -> Self {\n        self.enable_attach = attach_enabled;\n        self\n    }\n\n    /// Kept for backwards compatibility. Strict tables are now always enabled.\n    pub fn experimental_strict(self, _strict_enabled: bool) -> Self {\n        self\n    }\n\n    pub fn experimental_custom_types(mut self, custom_types_enabled: bool) -> Self {\n        self.enable_custom_types = custom_types_enabled;\n        self\n    }\n\n    pub fn experimental_index_method(mut self, index_method_enabled: bool) -> Self {\n        self.enable_index_method = index_method_enabled;\n        self\n    }\n\n    pub fn experimental_materialized_views(mut self, enabled: bool) -> Self {\n        self.enable_materialized_views = enabled;\n        self\n    }\n\n    pub fn with_io(mut self, vfs: String) -> Self {\n        self.vfs = Some(vfs);\n        self\n    }\n    fn build_features_string(&self) -> Option<String> {\n        let mut features = Vec::new();\n        if self.enable_encryption {\n            features.push(\"encryption\");\n        }\n        if self.enable_attach {\n            features.push(\"attach\");\n        }\n        if self.enable_custom_types {\n            features.push(\"custom_types\");\n        }\n        if self.enable_index_method {\n            features.push(\"index_method\");\n        }\n        if self.enable_materialized_views {\n            features.push(\"views\");\n        }\n        if features.is_empty() {\n            return None;\n        }\n        Some(features.join(\",\"))\n    }\n\n    /// Build the database.\n    #[allow(unused_variables, clippy::arc_with_non_send_sync)]\n    pub async fn build(self) -> Result<Database> {\n        let features = self.build_features_string();\n        let db =\n            turso_sdk_kit::rsapi::TursoDatabase::new(turso_sdk_kit::rsapi::TursoDatabaseConfig {\n                path: self.path,\n                experimental_features: features,\n                async_io: true,\n                encryption: self.encryption_opts,\n                vfs: self.vfs,\n                io: None,\n                db_file: None,\n            });\n        while let Some(io_c) = db.open()?.io() {\n            // At this point IO must already be created\n            let io = db\n                .io()\n                .expect(\"IO must have been set on the first call to db open\");\n            io_c.wait_async(io.as_ref())\n                .await\n                .map_err(TursoError::from)?;\n        }\n        Ok(Database { inner: db })\n    }\n}\n\n/// A database.\n///\n/// The `Database` object points to a database and allows you to connect to it\n#[derive(Clone)]\npub struct Database {\n    inner: Arc<turso_sdk_kit::rsapi::TursoDatabase>,\n}\n\nimpl Debug for Database {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"Database\").finish()\n    }\n}\n\nimpl Database {\n    /// Connect to the database.\n    pub fn connect(&self) -> Result<Connection> {\n        let conn = self.inner.connect()?;\n        Ok(Connection::create(conn, None))\n    }\n}\n\n/// A prepared statement.\n#[derive(Clone)]\npub struct Statement {\n    conn: Connection,\n    inner: Arc<Mutex<Box<turso_sdk_kit::rsapi::TursoStatement>>>,\n}\n\nstruct Execute {\n    stmt: Statement,\n}\n\nassert_send_sync!(Execute);\n\nimpl Future for Execute {\n    type Output = Result<u64>;\n\n    fn poll(\n        self: std::pin::Pin<&mut Self>,\n        cx: &mut std::task::Context<'_>,\n    ) -> std::task::Poll<Self::Output> {\n        match self.stmt.step(None, cx)? {\n            Poll::Ready(_) => {\n                let n_change = self.stmt.inner.lock().unwrap().n_change();\n                Poll::Ready(Ok(n_change as u64))\n            }\n            Poll::Pending => Poll::Pending,\n        }\n    }\n}\n\nimpl Statement {\n    fn step(\n        &self,\n        columns: Option<usize>,\n        cx: &mut std::task::Context<'_>,\n    ) -> Poll<Result<Option<Row>>> {\n        let mut stmt = self.inner.lock().unwrap();\n        match stmt.step(Some(cx.waker()))? {\n            turso_sdk_kit::rsapi::TursoStatusCode::Row => {\n                if let Some(columns) = columns {\n                    let mut values = Vec::with_capacity(columns);\n                    for i in 0..columns {\n                        let value = stmt.row_value(i)?;\n                        values.push(value.to_owned());\n                    }\n                    Poll::Ready(Ok(Some(Row { values })))\n                } else {\n                    Poll::Ready(Err(Error::Misuse(\n                        \"unexpected row during execution\".to_string(),\n                    )))\n                }\n            }\n            turso_sdk_kit::rsapi::TursoStatusCode::Done => Poll::Ready(Ok(None)),\n            turso_sdk_kit::rsapi::TursoStatusCode::Io => {\n                stmt.run_io()?;\n                if let Some(extra_io) = &self.conn.extra_io {\n                    extra_io(cx.waker().clone())?;\n                }\n                Poll::Pending\n            }\n        }\n    }\n    /// Query the database with this prepared statement.\n    pub async fn query(&mut self, params: impl IntoParams) -> Result<Rows> {\n        self.reset()?;\n\n        let mut stmt = self.inner.lock().unwrap();\n        let params = params.into_params()?;\n        match params {\n            params::Params::None => (),\n            params::Params::Positional(values) => {\n                for (i, value) in values.into_iter().enumerate() {\n                    stmt.bind_positional(i + 1, value.into())?;\n                }\n            }\n            params::Params::Named(values) => {\n                for (name, value) in values.into_iter() {\n                    let position = stmt.named_position(name)?;\n                    stmt.bind_positional(position, value.into())?;\n                }\n            }\n        }\n        let rows = Rows::new(self.clone());\n        Ok(rows)\n    }\n\n    /// Execute this prepared statement.\n    pub async fn execute(&mut self, params: impl IntoParams) -> Result<u64> {\n        {\n            // Reset the statement before executing\n            self.inner.lock().unwrap().reset()?;\n        }\n        let params = params.into_params()?;\n        match params {\n            params::Params::None => (),\n            params::Params::Positional(values) => {\n                for (i, value) in values.into_iter().enumerate() {\n                    let mut stmt = self.inner.lock().unwrap();\n                    stmt.bind_positional(i + 1, value.into())?;\n                }\n            }\n            params::Params::Named(values) => {\n                for (name, value) in values.into_iter() {\n                    let mut stmt = self.inner.lock().unwrap();\n                    let position = stmt.named_position(name)?;\n                    stmt.bind_positional(position, value.into())?;\n                }\n            }\n        }\n\n        let execute = Execute { stmt: self.clone() };\n        execute.await\n    }\n\n    /// Returns the number of columns in the result set.\n    pub fn column_count(&self) -> usize {\n        self.inner.lock().unwrap().column_count()\n    }\n\n    /// Returns the name of the column at the given index.\n    pub fn column_name(&self, idx: usize) -> Result<String> {\n        let stmt = self.inner.lock().unwrap();\n        if idx >= stmt.column_count() {\n            return Err(Error::Misuse(format!(\n                \"column index {idx} out of bounds (statement has {} columns)\",\n                stmt.column_count()\n            )));\n        }\n        Ok(stmt\n            .column_name(idx)\n            .expect(\"column index must be within valid range\")\n            .into_owned())\n    }\n\n    /// Returns the names of all columns in the result set.\n    pub fn column_names(&self) -> Vec<String> {\n        let stmt = self.inner.lock().unwrap();\n        let n = stmt.column_count();\n        (0..n)\n            .map(|i| {\n                stmt.column_name(i)\n                    .expect(\"column index must be within valid range\")\n                    .into_owned()\n            })\n            .collect()\n    }\n\n    /// Returns the index of the column with the given name.\n    pub fn column_index(&self, name: &str) -> Result<usize> {\n        let stmt = self.inner.lock().unwrap();\n        let n = stmt.column_count();\n        for i in 0..n {\n            let col_name = stmt\n                .column_name(i)\n                .expect(\"column index must be within valid range\");\n            if col_name.eq_ignore_ascii_case(name) {\n                return Ok(i);\n            }\n        }\n        Err(Error::Misuse(format!(\n            \"column '{name}' not found in result set\"\n        )))\n    }\n\n    /// Returns columns of the result of this prepared statement.\n    pub fn columns(&self) -> Vec<Column> {\n        let stmt = self.inner.lock().unwrap();\n\n        let n = stmt.column_count();\n\n        let mut cols = Vec::with_capacity(n);\n\n        for i in 0..n {\n            let name = stmt\n                .column_name(i)\n                .expect(\"column index must be within valid range\")\n                .into_owned();\n            let decl_type = stmt.column_decltype(i);\n            cols.push(Column { name, decl_type });\n        }\n\n        cols\n    }\n\n    /// Reset internal statement state after previous execution so it can be reused again\n    pub fn reset(&self) -> Result<()> {\n        let mut stmt = self.inner.lock().unwrap();\n        stmt.reset()?;\n        Ok(())\n    }\n\n    /// Execute a query that returns the first [`Row`].\n    ///\n    /// # Errors\n    ///\n    /// - Returns `QueryReturnedNoRows` if no rows were returned.\n    pub async fn query_row(&mut self, params: impl IntoParams) -> Result<Row> {\n        let mut rows = self.query(params).await?;\n\n        let first_row = rows.next().await?.ok_or(Error::QueryReturnedNoRows)?;\n        // Discard remaining rows so that the statement is executed to completion\n        // Otherwise Drop of the statement will cause transaction rollback\n        while rows.next().await?.is_some() {}\n        Ok(first_row)\n    }\n}\n\n/// Column information.\npub struct Column {\n    name: String,\n    decl_type: Option<String>,\n}\n\nimpl Column {\n    /// Return the name of the column.\n    pub fn name(&self) -> &str {\n        &self.name\n    }\n\n    /// Returns the type of the column.\n    pub fn decl_type(&self) -> Option<&str> {\n        self.decl_type.as_deref()\n    }\n}\n\npub trait IntoValue {\n    fn into_value(self) -> Result<Value>;\n}\n\n#[derive(Debug, Clone)]\npub enum Params {\n    None,\n    Positional(Vec<Value>),\n    Named(Vec<(String, Value)>),\n}\n\npub struct Transaction {}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use tempfile::NamedTempFile;\n\n    #[tokio::test]\n    async fn test_database_persistence() -> Result<()> {\n        let temp_file = NamedTempFile::new().unwrap();\n        let db_path = temp_file.path().to_str().unwrap();\n\n        // First, create the database, a table, and insert some data\n        {\n            let db = Builder::new_local(db_path).build().await?;\n            let conn = db.connect()?;\n            conn.execute(\n                \"CREATE TABLE test_persistence (id INTEGER PRIMARY KEY, name TEXT NOT NULL);\",\n                (),\n            )\n            .await?;\n            conn.execute(\"INSERT INTO test_persistence (name) VALUES ('Alice');\", ())\n                .await?;\n            conn.execute(\"INSERT INTO test_persistence (name) VALUES ('Bob');\", ())\n                .await?;\n        } // db and conn are dropped here, simulating closing\n\n        // Now, re-open the database and check if the data is still there\n        let db = Builder::new_local(db_path).build().await?;\n        let conn = db.connect()?;\n\n        let mut rows = conn\n            .query(\"SELECT name FROM test_persistence ORDER BY id;\", ())\n            .await?;\n\n        let row1 = rows.next().await?.expect(\"Expected first row\");\n        assert_eq!(row1.get_value(0)?, Value::Text(\"Alice\".to_string()));\n\n        let row2 = rows.next().await?.expect(\"Expected second row\");\n        assert_eq!(row2.get_value(0)?, Value::Text(\"Bob\".to_string()));\n\n        assert!(rows.next().await?.is_none(), \"Expected no more rows\");\n\n        Ok(())\n    }\n\n    #[tokio::test]\n    async fn test_database_persistence_many_frames() -> Result<()> {\n        let temp_file = NamedTempFile::new().unwrap();\n        let db_path = temp_file.path().to_str().unwrap();\n\n        const NUM_INSERTS: usize = 100;\n        const TARGET_STRING_LEN: usize = 1024; // 1KB\n\n        let mut original_data = Vec::with_capacity(NUM_INSERTS);\n        for i in 0..NUM_INSERTS {\n            let prefix = format!(\"test_string_{i:04}_\");\n            let padding_len = TARGET_STRING_LEN.saturating_sub(prefix.len());\n            let padding: String = \"A\".repeat(padding_len);\n            original_data.push(format!(\"{prefix}{padding}\"));\n        }\n\n        // First, create the database, a table, and insert many large strings\n        {\n            let db = Builder::new_local(db_path).build().await?;\n            let conn = db.connect()?;\n            conn.execute(\n                \"CREATE TABLE test_large_persistence (id INTEGER PRIMARY KEY AUTOINCREMENT, data TEXT NOT NULL);\",\n                (),\n            )\n            .await?;\n\n            for data_val in &original_data {\n                conn.execute(\n                    \"INSERT INTO test_large_persistence (data) VALUES (?);\",\n                    params::Params::Positional(vec![Value::Text(data_val.clone())]),\n                )\n                .await?;\n            }\n        } // db and conn are dropped here, simulating closing\n\n        {\n            // Now, re-open the database and check if the data is still there\n            let db = Builder::new_local(db_path).build().await?;\n            let conn = db.connect()?;\n\n            let mut rows = conn\n                .query(\"SELECT data FROM test_large_persistence ORDER BY id;\", ())\n                .await?;\n\n            for (i, value) in original_data.iter().enumerate().take(NUM_INSERTS) {\n                let row = rows\n                    .next()\n                    .await?\n                    .unwrap_or_else(|| panic!(\"Expected row {i} but found None\"));\n                assert_eq!(\n                    row.get_value(0)?,\n                    Value::Text(value.clone()),\n                    \"Mismatch in retrieved data for row {i}\"\n                );\n            }\n\n            assert!(\n                rows.next().await?.is_none(),\n                \"Expected no more rows after retrieving all inserted data\"\n            );\n\n            // Delete the WAL file only and try to re-open and query\n            let wal_path = format!(\"{db_path}-wal\");\n            std::fs::remove_file(&wal_path)\n                .map_err(|e| eprintln!(\"Warning: Failed to delete WAL file for test: {e}\"))\n                .unwrap();\n        }\n\n        // Attempt to re-open the database after deleting WAL and assert that table is missing.\n        let db_after_wal_delete = Builder::new_local(db_path).build().await?;\n        let conn_after_wal_delete = db_after_wal_delete.connect()?;\n\n        let query_result_after_wal_delete = conn_after_wal_delete\n            .query(\"SELECT data FROM test_large_persistence ORDER BY id;\", ())\n            .await;\n\n        match query_result_after_wal_delete {\n            Ok(_) => panic!(\"Query succeeded after WAL deletion and DB reopen, but was expected to fail because the table definition should have been in the WAL.\"),\n            Err(Error::Error(msg)) => {\n                assert!(\n                    msg.contains(\"no such table: test_large_persistence\"),\n                    \"Expected 'test_large_persistence not found' error, but got: {msg}\"\n                );\n            }\n            Err(e) => panic!(\n                \"Expected SqlExecutionFailure for 'no such table', but got a different error: {e:?}\"\n            ),\n        }\n\n        Ok(())\n    }\n\n    #[tokio::test]\n    async fn test_rows_column_names() -> Result<()> {\n        let db = Builder::new_local(\":memory:\").build().await?;\n        let conn = db.connect()?;\n        conn.execute(\n            \"CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT);\",\n            (),\n        )\n        .await?;\n        conn.execute(\n            \"INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.org');\",\n            (),\n        )\n        .await?;\n\n        let rows = conn.query(\"SELECT id, name, email FROM users;\", ()).await?;\n\n        // columns()\n        let columns = rows.columns();\n        let names: Vec<&str> = columns.iter().map(|c| c.name()).collect();\n        assert_eq!(names, vec![\"id\", \"name\", \"email\"]);\n\n        // column_count()\n        assert_eq!(rows.column_count(), 3);\n\n        // column_name()\n        assert_eq!(rows.column_name(0)?, \"id\");\n        assert_eq!(rows.column_name(1)?, \"name\");\n        assert_eq!(rows.column_name(2)?, \"email\");\n        assert!(rows.column_name(3).is_err());\n\n        // column_names()\n        assert_eq!(rows.column_names(), vec![\"id\", \"name\", \"email\"]);\n\n        // column_index()\n        assert_eq!(rows.column_index(\"id\")?, 0);\n        assert_eq!(rows.column_index(\"name\")?, 1);\n        assert_eq!(rows.column_index(\"email\")?, 2);\n        assert_eq!(rows.column_index(\"EMAIL\")?, 2); // case-insensitive\n        assert!(rows.column_index(\"nonexistent\").is_err());\n\n        Ok(())\n    }\n\n    #[tokio::test]\n    async fn test_database_persistence_write_one_frame_many_times() -> Result<()> {\n        let temp_file = NamedTempFile::new().unwrap();\n        let db_path = temp_file.path().to_str().unwrap();\n\n        for i in 0..100 {\n            {\n                let db = Builder::new_local(db_path).build().await?;\n                let conn = db.connect()?;\n\n                conn.execute(\"CREATE TABLE IF NOT EXISTS test_persistence (id INTEGER PRIMARY KEY, name TEXT NOT NULL);\", ()).await?;\n                conn.execute(\"INSERT INTO test_persistence (name) VALUES ('Alice');\", ())\n                    .await?;\n            }\n            {\n                let db = Builder::new_local(db_path).build().await?;\n                let conn = db.connect()?;\n\n                let mut rows_iter = conn\n                    .query(\"SELECT count(*) FROM test_persistence;\", ())\n                    .await?;\n                let rows = rows_iter.next().await?.unwrap();\n                assert_eq!(rows.get_value(0)?, Value::Integer(i as i64 + 1));\n                assert!(rows_iter.next().await?.is_none());\n            }\n        }\n\n        Ok(())\n    }\n\n    #[tokio::test]\n    async fn test_parallel_writes_and_wal_size() -> Result<()> {\n        let temp_dir = tempfile::tempdir().unwrap();\n        let db_path = temp_dir.path().join(\"test.db\");\n        let db_path_str = db_path.to_str().unwrap();\n\n        let db = Builder::new_local(db_path_str).build().await?;\n        let conn = db.connect()?;\n        conn.execute(\n            \"CREATE TABLE test_data (id INTEGER PRIMARY KEY AUTOINCREMENT, payload TEXT NOT NULL);\",\n            (),\n        )\n        .await?;\n\n        // Generate a ~200KB payload\n        let payload = \"X\".repeat(200 * 1024);\n\n        // Parallel writes: spawn 8 connections, each inserting 5 rows\n        let mut handles = Vec::new();\n        for conn_id in 0..8u32 {\n            let db = db.clone();\n            let payload = payload.clone();\n            handles.push(tokio::spawn(async move {\n                let conn = db.connect().unwrap();\n                for row_id in 0..5u32 {\n                    let tag = format!(\"conn{conn_id}_row{row_id}\");\n                    let data = format!(\"{tag}_{payload}\");\n                    loop {\n                        match conn\n                            .execute(\n                                \"INSERT INTO test_data (payload) VALUES (?);\",\n                                params::Params::Positional(vec![Value::Text(data.clone())]),\n                            )\n                            .await\n                        {\n                            Ok(_) => break,\n                            Err(Error::Busy(_)) => {\n                                tokio::time::sleep(std::time::Duration::from_millis(10)).await;\n                                continue;\n                            }\n                            Err(e) => panic!(\"Insert failed: {e:?}\"),\n                        }\n                    }\n                }\n            }));\n        }\n        for h in handles {\n            h.await.unwrap();\n        }\n\n        // Sequential writes: 3 more large inserts\n        for i in 0..3 {\n            let data = format!(\"sequential_{i}_{payload}\");\n            conn.execute(\n                \"INSERT INTO test_data (payload) VALUES (?);\",\n                params::Params::Positional(vec![Value::Text(data)]),\n            )\n            .await?;\n        }\n\n        // Verify row count: 8*5 + 3 = 43\n        let mut rows = conn.query(\"SELECT count(*) FROM test_data;\", ()).await?;\n        let row = rows.next().await?.unwrap();\n        assert_eq!(row.get_value(0)?, Value::Integer(43));\n\n        // Report WAL size\n        let wal_path = format!(\"{db_path_str}-wal\");\n        let wal_size = std::fs::metadata(&wal_path).map(|m| m.len()).unwrap_or(0);\n        eprintln!(\n            \"WAL size after all writes: {} bytes ({:.2} KB)\",\n            wal_size,\n            wal_size as f64 / 1024.0\n        );\n        assert!(wal_size > 0, \"WAL file should exist and be non-empty\");\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "bindings/rust/src/params.rs",
    "content": "//! This module contains all `Param` related utilities and traits.\n\nuse std::borrow::Cow;\n\nuse crate::{Error, Result, Value};\n\nmod sealed {\n    pub trait Sealed {}\n}\n\nuse sealed::Sealed;\n\n/// Converts some type into parameters that can be passed\n/// to libsql.\n///\n/// The trait is sealed and not designed to be implemented by hand\n/// but instead provides a few ways to use it.\n///\n/// # Passing parameters to libsql\n///\n/// Many functions in this library let you pass parameters to libsql. Doing this\n/// lets you avoid any risk of SQL injection, and is simpler than escaping\n/// things manually. These functions generally contain some parameter that generically\n/// accepts some implementation this trait.\n///\n/// # Positional parameters\n///\n/// These can be supplied in a few ways:\n///\n/// - For heterogeneous parameter lists of 16 or less items a tuple syntax is supported\n///   by doing `(1, \"foo\")`.\n/// - For hetergeneous parameter lists of 16 or greater, the [`turso::params!`] is supported\n///   by doing `turso::params![1, \"foo\"]`.\n/// - For homogeneous parameter types (where they are all the same type), const arrays are\n///   supported by doing `[1, 2, 3]`.\n///\n/// # Example (positional)\n///\n/// ```rust,no_run\n/// # use turso::{Connection, params};\n/// # async fn run(conn: Connection) -> turso::Result<()> {\n/// let mut stmt = conn.prepare(\"INSERT INTO test (a, b) VALUES (?1, ?2)\").await?;\n///\n/// // Using a tuple:\n/// stmt.execute((0, \"foobar\")).await?;\n///\n/// // Using `turso::params!`:\n/// stmt.execute(params![1i32, \"blah\"]).await?;\n///\n/// // array literal — non-references\n/// stmt.execute([2i32, 3i32]).await?;\n///\n/// // array literal — references\n/// stmt.execute([\"foo\", \"bar\"]).await?;\n///\n/// // Slice literal, references:\n/// stmt.execute([2i32, 3i32]).await?;\n///\n/// #    Ok(())\n/// # }\n/// ```\n///\n/// # Named parameters\n///\n/// Named parameter keys must include the SQL prefix used in the statement,\n/// for example `:name`, `@name`, `$name`, or `?1`.\n///\n/// - For heterogeneous parameter lists of 16 or less items a tuple syntax is supported\n///   by doing `((\":key1\", 1), (\":key2\", \"foo\"))`.\n/// - For heterogeneous parameter lists of 16 or greater, the [`turso::params!`] is supported\n///   by doing `turso::named_params![\":key1\": 1, \":key2\": \"foo\"]`.\n/// - For homogeneous parameter types (where they are all the same type), const arrays are\n///   supported by doing `[(\":key1\", 1), (\":key2\", 2), (\":key3\", 3)]`.\n///\n/// # Example (named)\n///\n/// ```rust,no_run\n/// # use turso::{Connection, named_params};\n/// # async fn run(conn: Connection) -> turso::Result<()> {\n/// let mut stmt = conn.prepare(\"INSERT INTO test (a, b) VALUES (:key1, :key2)\").await?;\n///\n/// // Using a tuple:\n/// stmt.execute(((\":key1\", 0), (\":key2\", \"foobar\"))).await?;\n///\n/// // Using `turso::named_params!`:\n/// stmt.execute(named_params! {\":key1\": 1i32, \":key2\": \"blah\" }).await?;\n///\n/// // const array:\n/// stmt.execute([(\":key1\", 2i32), (\":key2\", 3i32)]).await?;\n///\n/// #   Ok(())\n/// # }\n/// ```\npub trait IntoParams: Sealed {\n    // Hide this because users should not be implementing this\n    // themselves. We should consider sealing this trait.\n    #[doc(hidden)]\n    fn into_params(self) -> Result<Params>;\n}\n\n#[derive(Debug, Clone)]\n#[doc(hidden)]\npub enum Params {\n    None,\n    Positional(Vec<Value>),\n    Named(Vec<(Cow<'static, str>, Value)>),\n}\n\n/// Convert an owned iterator into Params.\n///\n/// # Example\n///\n/// ```rust\n/// # use turso::{Connection, params_from_iter, Rows};\n/// # async fn run(conn: &Connection) {\n///\n/// let iter = vec![1, 2, 3];\n///\n/// conn.query(\n///     \"SELECT * FROM users WHERE id IN (?1, ?2, ?3)\",\n///     params_from_iter(iter)\n/// )\n/// .await\n/// .unwrap();\n/// # }\n/// ```\npub fn params_from_iter<I>(iter: I) -> impl IntoParams\nwhere\n    I: IntoIterator,\n    I::Item: IntoValue,\n{\n    iter.into_iter().collect::<Vec<_>>()\n}\n\nimpl Sealed for () {}\nimpl IntoParams for () {\n    fn into_params(self) -> Result<Params> {\n        Ok(Params::None)\n    }\n}\n\nimpl Sealed for Params {}\nimpl IntoParams for Params {\n    fn into_params(self) -> Result<Params> {\n        Ok(self)\n    }\n}\n\nimpl<T: IntoValue> Sealed for Vec<T> {}\nimpl<T: IntoValue> IntoParams for Vec<T> {\n    fn into_params(self) -> Result<Params> {\n        let values = self\n            .into_iter()\n            .map(|i| i.into_value())\n            .collect::<Result<Vec<_>>>()?;\n\n        Ok(Params::Positional(values))\n    }\n}\n\nimpl<T: IntoValue> Sealed for Vec<(String, T)> {}\nimpl<T: IntoValue> IntoParams for Vec<(String, T)> {\n    fn into_params(self) -> Result<Params> {\n        let values = self\n            .into_iter()\n            .map(|(k, v)| Ok((Cow::Owned(k), v.into_value()?)))\n            .collect::<Result<Vec<_>>>()?;\n\n        Ok(Params::Named(values))\n    }\n}\n\nimpl<T: IntoValue, const N: usize> Sealed for [T; N] {}\nimpl<T: IntoValue, const N: usize> IntoParams for [T; N] {\n    fn into_params(self) -> Result<Params> {\n        self.into_iter().collect::<Vec<_>>().into_params()\n    }\n}\n\n// Named parameters with static string keys to avoid String allocations.\nimpl<T: IntoValue, const N: usize> Sealed for [(&'static str, T); N] {}\nimpl<T: IntoValue, const N: usize> IntoParams for [(&'static str, T); N] {\n    fn into_params(self) -> Result<Params> {\n        let values = self\n            .into_iter()\n            .map(|(k, v)| Ok((Cow::Borrowed(k), v.into_value()?)))\n            .collect::<Result<Vec<_>>>()?;\n\n        Ok(Params::Named(values))\n    }\n}\n\nimpl<T: IntoValue + Clone, const N: usize> Sealed for &[T; N] {}\nimpl<T: IntoValue + Clone, const N: usize> IntoParams for &[T; N] {\n    fn into_params(self) -> Result<Params> {\n        self.iter().cloned().collect::<Vec<_>>().into_params()\n    }\n}\n\n// NOTICE: heavily inspired by rusqlite\nmacro_rules! tuple_into_params {\n    ($count:literal : $(($field:tt $ftype:ident)),* $(,)?) => {\n        impl<$($ftype,)*> Sealed for ($($ftype,)*) where $($ftype: IntoValue,)* {}\n        impl<$($ftype,)*> IntoParams for ($($ftype,)*) where $($ftype: IntoValue,)* {\n            fn into_params(self) -> Result<Params> {\n                let params = Params::Positional(vec![$(self.$field.into_value()?),*]);\n                Ok(params)\n            }\n        }\n    }\n}\n\nmacro_rules! named_tuple_into_params {\n    ($count:literal : $(($field:tt $ftype:ident)),* $(,)?) => {\n        impl<$($ftype,)*> Sealed for ($((&'static str, $ftype),)*) where $($ftype: IntoValue,)* {}\n        impl<$($ftype,)*> IntoParams for ($((&'static str, $ftype),)*) where $($ftype: IntoValue,)* {\n            fn into_params(self) -> Result<Params> {\n                let params = Params::Named(vec![$((Cow::Borrowed(self.$field.0), self.$field.1.into_value()?)),*]);\n                Ok(params)\n            }\n        }\n    }\n}\n\nnamed_tuple_into_params!(1: (0 A));\nnamed_tuple_into_params!(2: (0 A), (1 B));\nnamed_tuple_into_params!(3: (0 A), (1 B), (2 C));\nnamed_tuple_into_params!(4: (0 A), (1 B), (2 C), (3 D));\nnamed_tuple_into_params!(5: (0 A), (1 B), (2 C), (3 D), (4 E));\nnamed_tuple_into_params!(6: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F));\nnamed_tuple_into_params!(7: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G));\nnamed_tuple_into_params!(8: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H));\nnamed_tuple_into_params!(9: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I));\nnamed_tuple_into_params!(10: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J));\nnamed_tuple_into_params!(11: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K));\nnamed_tuple_into_params!(12: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L));\nnamed_tuple_into_params!(13: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M));\nnamed_tuple_into_params!(14: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M), (13 N));\nnamed_tuple_into_params!(15: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M), (13 N), (14 O));\nnamed_tuple_into_params!(16: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M), (13 N), (14 O), (15 P));\n\ntuple_into_params!(1: (0 A));\ntuple_into_params!(2: (0 A), (1 B));\ntuple_into_params!(3: (0 A), (1 B), (2 C));\ntuple_into_params!(4: (0 A), (1 B), (2 C), (3 D));\ntuple_into_params!(5: (0 A), (1 B), (2 C), (3 D), (4 E));\ntuple_into_params!(6: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F));\ntuple_into_params!(7: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G));\ntuple_into_params!(8: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H));\ntuple_into_params!(9: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I));\ntuple_into_params!(10: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J));\ntuple_into_params!(11: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K));\ntuple_into_params!(12: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L));\ntuple_into_params!(13: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M));\ntuple_into_params!(14: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M), (13 N));\ntuple_into_params!(15: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M), (13 N), (14 O));\ntuple_into_params!(16: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M), (13 N), (14 O), (15 P));\n\n// TODO: Should we rename this to `ToSql` which makes less sense but\n// matches the error variant we have in `Error`. Or should we change the\n// error variant to match this breaking the few people that currently use\n// this error variant.\npub trait IntoValue {\n    fn into_value(self) -> Result<Value>;\n}\n\nimpl<T> IntoValue for T\nwhere\n    T: TryInto<Value>,\n    T::Error: Into<crate::BoxError>,\n{\n    fn into_value(self) -> Result<Value> {\n        self.try_into()\n            .map_err(|e| Error::ToSqlConversionFailure(e.into()))\n    }\n}\n\nimpl IntoValue for Result<Value> {\n    fn into_value(self) -> Result<Value> {\n        self\n    }\n}\n\n/// Construct positional params from a heterogeneous set of params types.\n#[macro_export]\nmacro_rules! params {\n    () => {\n       ()\n    };\n    ($($value:expr),* $(,)?) => {{\n        use $crate::params::IntoValue;\n        [$($value.into_value()),*]\n\n    }};\n}\n\n/// Construct named params from a heterogeneous set of params types.\n#[macro_export]\nmacro_rules! named_params {\n    () => {\n        ()\n    };\n    ($($param_name:literal: $value:expr),* $(,)?) => {{\n        use $crate::params::IntoValue;\n        [$(($param_name, $value.into_value())),*]\n    }};\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::Value;\n\n    #[test]\n    fn test_serialize_array() {\n        assert_eq!(\n            params!([0; 16])[0].as_ref().unwrap(),\n            &Value::Blob(vec![0; 16])\n        );\n    }\n}\n"
  },
  {
    "path": "bindings/rust/src/rows.rs",
    "content": "use crate::{assert_send_sync, Column, Error, Result, Statement, Value};\nuse std::fmt::Debug;\nuse std::future::Future;\n\n/// Results of a prepared statement query.\npub struct Rows {\n    inner: Statement,\n}\n\nimpl Rows {\n    pub(crate) fn new(inner: Statement) -> Self {\n        Self { inner }\n    }\n\n    /// Returns the number of columns in the result set.\n    pub fn column_count(&self) -> usize {\n        self.inner.column_count()\n    }\n\n    /// Returns the name of the column at the given index.\n    pub fn column_name(&self, idx: usize) -> Result<String> {\n        self.inner.column_name(idx)\n    }\n\n    /// Returns the names of all columns in the result set.\n    pub fn column_names(&self) -> Vec<String> {\n        self.inner.column_names()\n    }\n\n    /// Returns the index of the column with the given name.\n    pub fn column_index(&self, name: &str) -> Result<usize> {\n        self.inner.column_index(name)\n    }\n\n    /// Returns columns of the result set.\n    pub fn columns(&self) -> Vec<Column> {\n        self.inner.columns()\n    }\n\n    /// Fetch the next row of this result set.\n    pub async fn next(&mut self) -> Result<Option<Row>> {\n        struct Next {\n            columns: usize,\n            stmt: Statement,\n        }\n\n        impl Future for Next {\n            type Output = Result<Option<Row>>;\n\n            fn poll(\n                self: std::pin::Pin<&mut Self>,\n                cx: &mut std::task::Context<'_>,\n            ) -> std::task::Poll<Self::Output> {\n                self.stmt.step(Some(self.columns), cx)\n            }\n        }\n\n        assert_send_sync!(Next);\n\n        let next = Next {\n            columns: self.inner.inner.lock().unwrap().column_count(),\n            stmt: self.inner.clone(),\n        };\n\n        next.await\n    }\n}\n\n/// Query result row.\n#[derive(Debug, PartialEq)]\npub struct Row {\n    pub(crate) values: Vec<turso_sdk_kit::rsapi::Value>,\n}\n\nimpl Row {\n    pub fn get_value(&self, idx: usize) -> Result<Value> {\n        let val = self.values.get(idx).ok_or_else(|| {\n            Error::Misuse(format!(\n                \"column index {idx} out of bounds (row has {} columns)\",\n                self.values.len()\n            ))\n        })?;\n        match val {\n            turso_sdk_kit::rsapi::Value::Numeric(turso_sdk_kit::rsapi::Numeric::Integer(i)) => {\n                Ok(Value::Integer(*i))\n            }\n            turso_sdk_kit::rsapi::Value::Numeric(turso_sdk_kit::rsapi::Numeric::Float(f)) => {\n                Ok(Value::Real(f64::from(*f)))\n            }\n            turso_sdk_kit::rsapi::Value::Null => Ok(Value::Null),\n            turso_sdk_kit::rsapi::Value::Text(text) => {\n                Ok(Value::Text(text.value.clone().into_owned()))\n            }\n            turso_sdk_kit::rsapi::Value::Blob(items) => Ok(Value::Blob(items.to_vec())),\n        }\n    }\n\n    pub fn get<T>(&self, idx: usize) -> Result<T>\n    where\n        T: turso_sdk_kit::rsapi::FromValue,\n    {\n        let val = self.values.get(idx).ok_or_else(|| {\n            Error::Misuse(format!(\n                \"column index {idx} out of bounds (row has {} columns)\",\n                self.values.len()\n            ))\n        })?;\n        T::from_sql(val.clone()).map_err(|err| Error::ConversionFailure(err.to_string()))\n    }\n\n    pub fn column_count(&self) -> usize {\n        self.values.len()\n    }\n}\n"
  },
  {
    "path": "bindings/rust/src/sync.rs",
    "content": "use std::{\n    future::Future,\n    io::ErrorKind,\n    pin::Pin,\n    sync::{Arc, Mutex},\n    task::{Context, Poll, Waker},\n    time::Duration,\n};\n\nuse bytes::Bytes;\nuse http_body_util::{BodyExt, Full};\nuse hyper::{header::AUTHORIZATION, Request};\nuse hyper_tls::HttpsConnector;\nuse hyper_util::{\n    client::legacy::{connect::HttpConnector, Client},\n    rt::TokioExecutor,\n};\nuse tokio::sync::mpsc;\n\nuse crate::{connection::Connection, Error, Result};\n\n// Public re-exports of sync types for users of this crate.\npub use turso_sync_sdk_kit::rsapi::DatabaseSyncStats;\npub use turso_sync_sdk_kit::rsapi::PartialBootstrapStrategy;\npub use turso_sync_sdk_kit::rsapi::PartialSyncOpts;\n\n// Constants used across the sync module\nconst DEFAULT_CLIENT_NAME: &str = \"turso-sync-rust\";\n\n/// Encryption cipher for Turso Cloud remote encryption.\n/// These match the server-side encryption settings.\n#[derive(Debug, Clone, Copy)]\npub enum RemoteEncryptionCipher {\n    Aes256Gcm,\n    Aes128Gcm,\n    ChaCha20Poly1305,\n    Aegis128L,\n    Aegis128X2,\n    Aegis128X4,\n    Aegis256,\n    Aegis256X2,\n    Aegis256X4,\n}\n\nimpl RemoteEncryptionCipher {\n    /// Returns the total reserved bytes as required by the server\n    pub fn reserved_bytes(&self) -> usize {\n        match self {\n            Self::Aes256Gcm | Self::Aes128Gcm | Self::ChaCha20Poly1305 => 28,\n            Self::Aegis128L | Self::Aegis128X2 | Self::Aegis128X4 => 32,\n            Self::Aegis256 | Self::Aegis256X2 | Self::Aegis256X4 => 48,\n        }\n    }\n}\n\nimpl std::str::FromStr for RemoteEncryptionCipher {\n    type Err = String;\n\n    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {\n        match s.to_lowercase().as_str() {\n            \"aes256gcm\" | \"aes-256-gcm\" => Ok(Self::Aes256Gcm),\n            \"aes128gcm\" | \"aes-128-gcm\" => Ok(Self::Aes128Gcm),\n            \"chacha20poly1305\" | \"chacha20-poly1305\" => Ok(Self::ChaCha20Poly1305),\n            \"aegis128l\" | \"aegis-128l\" => Ok(Self::Aegis128L),\n            \"aegis128x2\" | \"aegis-128x2\" => Ok(Self::Aegis128X2),\n            \"aegis128x4\" | \"aegis-128x4\" => Ok(Self::Aegis128X4),\n            \"aegis256\" | \"aegis-256\" => Ok(Self::Aegis256),\n            \"aegis256x2\" | \"aegis-256x2\" => Ok(Self::Aegis256X2),\n            \"aegis256x4\" | \"aegis-256x4\" => Ok(Self::Aegis256X4),\n            _ => Err(format!(\n                \"unknown cipher: '{s}'. Supported: aes256gcm, aes128gcm, chacha20poly1305, \\\n                 aegis128l, aegis128x2, aegis128x4, aegis256, aegis256x2, aegis256x4\"\n            )),\n        }\n    }\n}\n\n// Builder for a synced database.\npub struct Builder {\n    // Absolute or relative path to local database file (\":memory:\" is supported).\n    path: String,\n    // Remote URL base. Supports https://, http:// and libsql:// (translated to https://).\n    remote_url: Option<String>,\n    // Optional authorization token (e.g., Bearer token).\n    auth_token: Option<String>,\n    // Optional custom client identifier used by the sync engine for telemetry/tracing.\n    client_name: Option<String>,\n    // Optional long-poll timeout when waiting for server changes.\n    long_poll_timeout: Option<Duration>,\n    // Whether to bootstrap a database if it's empty (download schema and initial data).\n    bootstrap_if_empty: bool,\n    // Partial sync configuration (EXPERIMENTAL).\n    partial_sync_config_experimental: Option<PartialSyncOpts>,\n    // Encryption key (base64-encoded) for the Turso Cloud database\n    remote_encryption_key: Option<String>,\n    // Encryption cipher for the Turso Cloud database\n    remote_encryption_cipher: Option<RemoteEncryptionCipher>,\n}\n\nimpl Builder {\n    // Create a new Builder for a synced database.\n    pub fn new_remote(path: &str) -> Self {\n        Self {\n            path: path.to_string(),\n            remote_url: None,\n            auth_token: None,\n            client_name: None,\n            long_poll_timeout: None,\n            bootstrap_if_empty: true,\n            partial_sync_config_experimental: None,\n            remote_encryption_key: None,\n            remote_encryption_cipher: None,\n        }\n    }\n\n    // Set remote_url for HTTP requests.\n    // If remote_url omitted in configuration - tursodb will try to load it from the metadata file\n    pub fn with_remote_url(mut self, remote_url: impl Into<String>) -> Self {\n        self.remote_url = Some(remote_url.into());\n        self\n    }\n\n    // Set optional authorization token for HTTP requests.\n    pub fn with_auth_token(mut self, token: impl Into<String>) -> Self {\n        self.auth_token = Some(token.into());\n        self\n    }\n\n    // Set custom client name (defaults to 'turso-sync-rust').\n    pub fn with_client_name(mut self, name: impl Into<String>) -> Self {\n        self.client_name = Some(name.into());\n        self\n    }\n\n    // Set long poll timeout for waiting remote changes.\n    pub fn with_long_poll_timeout(mut self, timeout: Duration) -> Self {\n        self.long_poll_timeout = Some(timeout);\n        self\n    }\n\n    // Configure bootstrap behavior for empty databases.\n    pub fn bootstrap_if_empty(mut self, enable: bool) -> Self {\n        self.bootstrap_if_empty = enable;\n        self\n    }\n\n    // Set experimental partial sync configuration.\n    pub fn with_partial_sync_opts_experimental(mut self, opts: PartialSyncOpts) -> Self {\n        self.partial_sync_config_experimental = Some(opts);\n        self\n    }\n\n    /// Set encryption key (base64-encoded) and cipher for the Turso Cloud database.\n    /// The cipher is used to calculate the correct reserved_bytes for the database.\n    pub fn with_remote_encryption(\n        mut self,\n        base64_key: impl Into<String>,\n        cipher: RemoteEncryptionCipher,\n    ) -> Self {\n        self.remote_encryption_key = Some(base64_key.into());\n        self.remote_encryption_cipher = Some(cipher);\n        self\n    }\n\n    /// Set encryption key (base64-encoded) for the Turso Cloud database.\n    /// The key will be sent as x-turso-encryption-key header with sync HTTP requests.\n    /// Note: For deferred sync (no initial bootstrap), use with_remote_encryption() instead\n    /// to also specify the cipher for correct reserved_bytes calculation.\n    pub fn with_remote_encryption_key(mut self, base64_key: impl Into<String>) -> Self {\n        self.remote_encryption_key = Some(base64_key.into());\n        self\n    }\n\n    // Build the synced database object, initialize and open it.\n    pub async fn build(self) -> Result<Database> {\n        // Build core database config for the embedded engine.\n        let db_config = turso_sdk_kit::rsapi::TursoDatabaseConfig {\n            path: self.path.clone(),\n            experimental_features: None,\n            // IMPORTANT: async IO must be turned on to delegate IO to this layer.\n            async_io: true,\n            encryption: None,\n            vfs: None,\n            io: None,\n            db_file: None,\n        };\n\n        let url = if let Some(remote_url) = &self.remote_url {\n            Some(normalize_base_url(remote_url).map_err(Error::Error)?)\n        } else {\n            None\n        };\n\n        // Calculate reserved_bytes from cipher if provided.\n        let reserved_bytes = self\n            .remote_encryption_cipher\n            .map(|cipher| cipher.reserved_bytes());\n\n        // Build sync engine config.\n        let sync_config = turso_sync_sdk_kit::rsapi::TursoDatabaseSyncConfig {\n            path: self.path.clone(),\n            remote_url: url.clone(),\n            client_name: self\n                .client_name\n                .clone()\n                .unwrap_or_else(|| DEFAULT_CLIENT_NAME.to_string()),\n            long_poll_timeout_ms: self\n                .long_poll_timeout\n                .map(|d| d.as_millis().min(u32::MAX as u128) as u32),\n            bootstrap_if_empty: self.bootstrap_if_empty,\n            reserved_bytes,\n            partial_sync_opts: self.partial_sync_config_experimental.clone(),\n            remote_encryption_key: self.remote_encryption_key.clone(),\n        };\n\n        // Create sync wrapper.\n        let sync =\n            turso_sync_sdk_kit::rsapi::TursoDatabaseSync::<Bytes>::new(db_config, sync_config)\n                .map_err(Error::from)?;\n\n        // IO worker will process SyncEngine IO queue on a dedicated tokio thread.\n        let io_worker = IoWorker::spawn(sync.clone(), url, self.auth_token.clone());\n\n        // Create (bootstrap + open) database in one go.\n        let op = sync.create();\n        drive_operation(op, io_worker.clone()).await?;\n\n        Ok(Database {\n            sync,\n            io: io_worker,\n        })\n    }\n}\n\n// Synced Database handle.\n#[derive(Clone)]\npub struct Database {\n    sync: Arc<turso_sync_sdk_kit::rsapi::TursoDatabaseSync<Bytes>>,\n    io: Arc<IoWorker>,\n}\n\nimpl Database {\n    // Push local changes to the remote.\n    pub async fn push(&self) -> Result<()> {\n        let op = self.sync.push_changes();\n        drive_operation(op, self.io.clone()).await?;\n        Ok(())\n    }\n\n    // Pull remote changes; returns true if any changes were applied.\n    pub async fn pull(&self) -> Result<bool> {\n        // First, wait for changes...\n        let op = self.sync.wait_changes();\n        let result = drive_operation_result(op, self.io.clone()).await?;\n        let mut has_changes = false;\n\n        if let Some(\n            turso_sync_sdk_kit::turso_async_operation::TursoAsyncOperationResult::Changes {\n                changes,\n            },\n        ) = result\n        {\n            if !changes.empty() {\n                has_changes = true;\n                // Then, apply them.\n                let op_apply = self.sync.apply_changes(changes);\n                drive_operation(op_apply, self.io.clone()).await?;\n            }\n        }\n\n        Ok(has_changes)\n    }\n\n    // Force WAL checkpoint for the main database.\n    pub async fn checkpoint(&self) -> Result<()> {\n        let op = self.sync.checkpoint();\n        drive_operation(op, self.io.clone()).await?;\n        Ok(())\n    }\n\n    // Retrieve sync statistics for the database.\n    pub async fn stats(&self) -> Result<DatabaseSyncStats> {\n        let op = self.sync.stats();\n        let result = drive_operation_result(op, self.io.clone()).await?;\n        match result {\n            Some(turso_sync_sdk_kit::turso_async_operation::TursoAsyncOperationResult::Stats {\n                stats,\n            }) => Ok(stats),\n            _ => Err(Error::Misuse(\n                \"unexpected result type from stats operation\".to_string(),\n            )),\n        }\n    }\n\n    // Create a SQL connection to the synced database.\n    pub async fn connect(&self) -> Result<Connection> {\n        let op = self.sync.connect();\n        let result = drive_operation_result(op, self.io.clone()).await?;\n        match result {\n            Some(\n                turso_sync_sdk_kit::turso_async_operation::TursoAsyncOperationResult::Connection {\n                    connection,\n                },\n            ) => {\n                // Provide extra_io callback to kick IO worker when driver needs to make progress.\n                let io = self.io.clone();\n                let extra_io = Arc::new(move |waker| {\n                    io.register(waker);\n                    io.kick();\n                    Ok(())\n                });\n                Ok(Connection::create(connection, Some(extra_io)))\n            }\n            _ => Err(Error::Misuse(\n                \"unexpected result type from connect operation\".to_string(),\n            )),\n        }\n    }\n}\n\n// Drive an operation that has no result (returns None when done).\nasync fn drive_operation(\n    op: Box<turso_sync_sdk_kit::turso_async_operation::TursoDatabaseAsyncOperation>,\n    io: Arc<IoWorker>,\n) -> Result<()> {\n    let fut = AsyncOpFuture::new(op, io);\n    fut.await.map(|_| ())\n}\n\n// Drive an operation and retrieve its result (if any).\nasync fn drive_operation_result(\n    op: Box<turso_sync_sdk_kit::turso_async_operation::TursoDatabaseAsyncOperation>,\n    io: Arc<IoWorker>,\n) -> Result<Option<turso_sync_sdk_kit::turso_async_operation::TursoAsyncOperationResult>> {\n    let fut = AsyncOpFuture::new(op, io);\n    fut.await\n}\n\n// Custom Future that integrates with TursoDatabaseAsyncOperation and our IO worker.\nstruct AsyncOpFuture {\n    op: Option<Box<turso_sync_sdk_kit::turso_async_operation::TursoDatabaseAsyncOperation>>,\n    io: Arc<IoWorker>,\n}\n\nimpl AsyncOpFuture {\n    fn new(\n        op: Box<turso_sync_sdk_kit::turso_async_operation::TursoDatabaseAsyncOperation>,\n        io: Arc<IoWorker>,\n    ) -> Self {\n        Self { op: Some(op), io }\n    }\n}\n\nimpl Future for AsyncOpFuture {\n    type Output =\n        Result<Option<turso_sync_sdk_kit::turso_async_operation::TursoAsyncOperationResult>>;\n\n    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {\n        let this = unsafe { self.get_unchecked_mut() };\n        let Some(op) = &this.op else {\n            return Poll::Ready(Err(Error::Misuse(\n                \"operation future has been already completed\".to_string(),\n            )));\n        };\n\n        this.io.register(cx.waker().clone());\n\n        // Try to resume the operation.\n        match op.resume() {\n            Ok(turso_sdk_kit::rsapi::TursoStatusCode::Done) => {\n                // Try to take the result (may be None).\n                let result = op.take_result().map(Some).or_else(|err| match err {\n                    turso_sdk_kit::rsapi::TursoError::Misuse(msg)\n                        if msg.contains(\"operation has no result\") =>\n                    {\n                        Ok(None)\n                    }\n                    other => Err(Error::from(other)),\n                })?;\n                // Drop the op and complete.\n                this.op.take();\n                Poll::Ready(Ok(result))\n            }\n            Ok(turso_sdk_kit::rsapi::TursoStatusCode::Io) => {\n                // Kick IO worker to process queued IO.\n                this.io.kick();\n                // Wait until IO worker makes progress and wakes us.\n                Poll::Pending\n            }\n            Ok(turso_sdk_kit::rsapi::TursoStatusCode::Row) => {\n                // Not expected from top-level sync operations.\n                Poll::Ready(Err(Error::Misuse(\n                    \"unexpected row status in sync operation\".to_string(),\n                )))\n            }\n            Err(e) => Poll::Ready(Err(Error::from(e))),\n        }\n    }\n}\n\n// Normalize remote base URL, mapping libsql:// to https:// and validating allowed schemes.\nfn normalize_base_url(input: &str) -> std::result::Result<String, String> {\n    let s = input.trim();\n    let s = if let Some(rest) = s.strip_prefix(\"libsql://\") {\n        format!(\"https://{rest}\")\n    } else {\n        s.to_string()\n    };\n    // Accept http or https only\n    if !(s.starts_with(\"https://\") || s.starts_with(\"http://\")) {\n        return Err(format!(\"unsupported remote URL scheme: {input}\"));\n    }\n    // Ensure no trailing slash to make join predictable.\n    let base = s.trim_end_matches('/').to_string();\n    Ok(base)\n}\n\n// The IO worker owns a dedicated Tokio runtime on a separate thread, and processes\n// the SyncEngine IO queue (HTTP and atomic file operations).\nstruct IoWorker {\n    // Reference to the sync database to pull IO items from its queue.\n    sync: Arc<turso_sync_sdk_kit::rsapi::TursoDatabaseSync<Bytes>>,\n    // Normalized base URL (http/https).\n    base_url: Option<String>,\n    // Optional auth token.\n    auth_token: Option<String>,\n    // Channel to wake the worker to process IO.\n    tx: mpsc::UnboundedSender<()>,\n    // Wakers to notify pending futures when IO makes progress.\n    wakers: Arc<Mutex<Vec<Waker>>>,\n}\n\nimpl IoWorker {\n    fn spawn(\n        sync: Arc<turso_sync_sdk_kit::rsapi::TursoDatabaseSync<Bytes>>,\n        base_url: Option<String>,\n        auth_token: Option<String>,\n    ) -> Arc<Self> {\n        let (tx, rx) = mpsc::unbounded_channel::<()>();\n        let wakers = Arc::new(Mutex::new(Vec::new()));\n\n        let worker = Arc::new(Self {\n            sync,\n            base_url,\n            auth_token,\n            tx,\n            wakers: wakers.clone(),\n        });\n\n        // Spin a separate Tokio runtime on its own thread to process IO queue.\n        let worker_clone = worker.clone();\n        std::thread::Builder::new()\n            .name(\"turso-sync-io\".to_string())\n            .spawn(move || {\n                let rt = tokio::runtime::Builder::new_current_thread()\n                    .enable_all()\n                    .build()\n                    .expect(\"failed to build IO runtime\");\n\n                rt.block_on(async move {\n                    IoWorker::run_loop(worker_clone, rx, wakers).await;\n                });\n            })\n            .expect(\"failed to spawn IO worker thread\");\n\n        worker\n    }\n\n    // Register a waker to be awakened upon IO progress.\n    fn register(&self, waker: Waker) {\n        let mut wakers = self.wakers.lock().unwrap();\n        wakers.push(waker);\n    }\n\n    // Kick the IO worker to process IO queue.\n    fn kick(&self) {\n        let _ = self.tx.send(());\n    }\n\n    // Called from the IO thread once progress has been made to notify all pending futures.\n    fn notify_progress(wakers: &Arc<Mutex<Vec<Waker>>>) {\n        let wakers = {\n            let mut guard = wakers.lock().unwrap();\n            std::mem::take(&mut *guard)\n        };\n        for w in wakers {\n            w.wake();\n        }\n    }\n\n    async fn run_loop(\n        this: Arc<IoWorker>,\n        mut rx: mpsc::UnboundedReceiver<()>,\n        wakers: Arc<Mutex<Vec<Waker>>>,\n    ) {\n        // Create HTTPS-capable Hyper client.\n        let mut http_connector = HttpConnector::new();\n        http_connector.enforce_http(false);\n        let https: HttpsConnector<HttpConnector> = HttpsConnector::new();\n        let client: Client<HttpsConnector<HttpConnector>, Full<Bytes>> =\n            Client::builder(TokioExecutor::new()).build::<_, Full<Bytes>>(https);\n\n        while rx.recv().await.is_some() {\n            // Process all pending items in the sync IO queue.\n            let mut made_progress = false;\n            loop {\n                let item = this.sync.take_io_item();\n                let Some(item) = item else {\n                    this.sync.step_io_callbacks();\n                    IoWorker::notify_progress(&wakers);\n                    break;\n                };\n\n                made_progress = true;\n\n                match item.get_request() {\n                    turso_sync_sdk_kit::sync_engine_io::SyncEngineIoRequest::Http {\n                        url,\n                        method,\n                        path,\n                        body,\n                        headers,\n                    } => {\n                        IoWorker::process_http(\n                            &this,\n                            &client,\n                            url.as_deref(),\n                            method,\n                            path,\n                            body.as_ref().map(|v| Bytes::from(v.clone())),\n                            headers,\n                            item.get_completion().clone(),\n                        )\n                        .await;\n                    }\n                    turso_sync_sdk_kit::sync_engine_io::SyncEngineIoRequest::FullRead { path } => {\n                        IoWorker::process_full_read(\n                            path,\n                            item.get_completion().clone(),\n                            &this.sync,\n                        )\n                        .await;\n                    }\n                    turso_sync_sdk_kit::sync_engine_io::SyncEngineIoRequest::FullWrite {\n                        path,\n                        content,\n                    } => {\n                        IoWorker::process_full_write(\n                            path,\n                            content,\n                            item.get_completion().clone(),\n                            &this.sync,\n                        )\n                        .await;\n                    }\n                }\n            }\n\n            // Run queued IO callbacks and wake all pending ops, yielding control\n            // to allow them to make progress before we loop again.\n            if made_progress {\n                this.sync.step_io_callbacks();\n                IoWorker::notify_progress(&wakers);\n                // Let waiting tasks run on their executors.\n                tokio::task::yield_now().await;\n            }\n        }\n    }\n\n    #[allow(clippy::too_many_arguments)]\n    async fn process_http(\n        this: &Arc<IoWorker>,\n        client: &Client<HttpsConnector<HttpConnector>, Full<Bytes>>,\n        url: Option<&str>,\n        method: &str,\n        path: &str,\n        body: Option<Bytes>,\n        headers: &[(String, String)],\n        completion: turso_sync_sdk_kit::sync_engine_io::SyncEngineIoCompletion<Bytes>,\n    ) {\n        // Build full URL.\n        let full_url = if path.starts_with(\"http://\") || path.starts_with(\"https://\") {\n            path.to_string()\n        } else {\n            // Ensure the path begins with '/'\n            let p = if path.starts_with('/') {\n                path.to_string()\n            } else {\n                format!(\"/{path}\")\n            };\n            let Some(url) = this.base_url.as_deref().or(url) else {\n                completion.poison(\"remote_url is not available\".to_string());\n                return;\n            };\n            format!(\"{url}{p}\")\n        };\n\n        let mut builder = Request::builder().method(method).uri(&full_url);\n\n        // Set headers from request\n        if let Some(headers_map) = builder.headers_mut() {\n            for (k, v) in headers {\n                if let Ok(name) = hyper::header::HeaderName::try_from(k.as_str()) {\n                    if let Ok(value) = hyper::header::HeaderValue::try_from(v.as_str()) {\n                        headers_map.insert(name, value);\n                    }\n                }\n            }\n            // Add Authorization header if not already set\n            if let Some(token) = &this.auth_token {\n                if !headers_map.contains_key(AUTHORIZATION) {\n                    let value = format!(\"Bearer {token}\");\n                    if let Ok(hv) = hyper::header::HeaderValue::try_from(value.as_str()) {\n                        headers_map.insert(AUTHORIZATION, hv);\n                    }\n                }\n            }\n        }\n\n        // Body must be Full<Bytes> to match the client type.\n        let req_body = Full::new(body.unwrap_or_default());\n\n        let request = match builder.body(req_body) {\n            Ok(r) => r,\n            Err(err) => {\n                completion.poison(format!(\"failed to build request: {err}\"));\n                this.sync.step_io_callbacks();\n                return;\n            }\n        };\n\n        let mut response = match client.request(request).await {\n            Ok(r) => r,\n            Err(err) => {\n                completion.poison(format!(\"http request failed: {err}\"));\n                this.sync.step_io_callbacks();\n                return;\n            }\n        };\n\n        // Propagate status\n        let status = response.status().as_u16();\n        completion.status(status as u32);\n        this.sync.step_io_callbacks();\n        IoWorker::notify_progress(&this.wakers);\n\n        // Stream response body in chunks\n        while let Some(frame_res) = response.body_mut().frame().await {\n            match frame_res {\n                Ok(frame) => {\n                    if let Some(chunk) = frame.data_ref() {\n                        completion.push_buffer(chunk.clone());\n                        this.sync.step_io_callbacks();\n                        IoWorker::notify_progress(&this.wakers);\n                    }\n                }\n                Err(err) => {\n                    completion.poison(format!(\"error reading response body: {err}\"));\n                    this.sync.step_io_callbacks();\n                    IoWorker::notify_progress(&this.wakers);\n                    return;\n                }\n            }\n        }\n\n        // Done streaming\n        completion.done();\n        this.sync.step_io_callbacks();\n        IoWorker::notify_progress(&this.wakers);\n    }\n\n    async fn process_full_read(\n        path: &str,\n        completion: turso_sync_sdk_kit::sync_engine_io::SyncEngineIoCompletion<Bytes>,\n        sync: &Arc<turso_sync_sdk_kit::rsapi::TursoDatabaseSync<Bytes>>,\n    ) {\n        match tokio::fs::read(path).await {\n            Ok(content) => {\n                completion.push_buffer(Bytes::from(content));\n                completion.done();\n            }\n            Err(err) if err.kind() == ErrorKind::NotFound => completion.done(),\n            Err(err) => {\n                completion.poison(format!(\"full read failed for {path}: {err}\"));\n            }\n        }\n        // Step callbacks after progress.\n        sync.step_io_callbacks();\n    }\n\n    async fn process_full_write(\n        path: &str,\n        content: &Vec<u8>,\n        completion: turso_sync_sdk_kit::sync_engine_io::SyncEngineIoCompletion<Bytes>,\n        sync: &Arc<turso_sync_sdk_kit::rsapi::TursoDatabaseSync<Bytes>>,\n    ) {\n        // Write the whole content in one go (non-chunked)\n        match tokio::fs::write(path, content).await {\n            Ok(_) => {\n                // For full write there is no data to stream back; just finish.\n                completion.done();\n            }\n            Err(err) => {\n                completion.poison(format!(\"full write failed for {path}: {err}\"));\n            }\n        }\n        // Step callbacks after progress.\n        sync.step_io_callbacks();\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use anyhow::{anyhow, Context, Result};\n    use rand::{distr::Alphanumeric, Rng};\n    use reqwest::Client;\n    use serde_json::json;\n    use std::{\n        env,\n        process::{Child, Command, Stdio},\n        thread::sleep,\n        time::Duration,\n    };\n    use tempfile::TempDir;\n    use turso_sync_sdk_kit::rsapi::PartialBootstrapStrategy;\n\n    use crate::sync::PartialSyncOpts;\n    use crate::{Rows, Value};\n\n    const ADMIN_URL: &str = \"http://localhost:8081\";\n    const USER_URL: &str = \"http://localhost:8080\";\n\n    fn random_str() -> String {\n        rand::rng()\n            .sample_iter(&Alphanumeric)\n            .take(8)\n            .map(char::from)\n            .collect()\n    }\n\n    async fn handle_response(resp: reqwest::Response) -> Result<()> {\n        let status = resp.status();\n        let text = resp.text().await.unwrap_or_default();\n\n        if status == 400 && text.contains(\"already exists\") {\n            return Ok(());\n        }\n\n        if !status.is_success() {\n            return Err(anyhow!(\"request failed: {status} {text}\"));\n        }\n\n        Ok(())\n    }\n\n    pub struct TursoServer {\n        user_url: String,\n        db_url: String,\n        host: String,\n        server: Option<Child>,\n        client: Client,\n    }\n\n    impl TursoServer {\n        pub async fn new() -> Result<Self> {\n            let client = Client::new();\n\n            if env::var(\"LOCAL_SYNC_SERVER\").is_err() {\n                let name = random_str();\n                let tokens: Vec<&str> = USER_URL.split(\"://\").collect();\n\n                handle_response(\n                    client\n                        .post(format!(\"{ADMIN_URL}/v1/tenants/{name}\"))\n                        .send()\n                        .await?,\n                )\n                .await?;\n                handle_response(\n                    client\n                        .post(format!(\"{ADMIN_URL}/v1/tenants/{name}/groups/{name}\"))\n                        .send()\n                        .await?,\n                )\n                .await?;\n                handle_response(\n                    client\n                        .post(format!(\n                            \"{ADMIN_URL}/v1/tenants/{name}/groups/{name}/databases/{name}\"\n                        ))\n                        .send()\n                        .await?,\n                )\n                .await?;\n\n                Ok(Self {\n                    user_url: USER_URL.to_string(),\n                    db_url: format!(\"{}://{}--{}--{}.{}\", tokens[0], name, name, name, tokens[1]),\n                    host: format!(\"{name}--{name}--{name}.localhost\"),\n                    server: None,\n                    client,\n                })\n            } else {\n                let port: u16 = rand::rng().random_range(10_000..=65_535);\n                let server_bin = env::var(\"LOCAL_SYNC_SERVER\").unwrap();\n\n                let child = Command::new(server_bin)\n                    .args([\"--sync-server\", &format!(\"0.0.0.0:{port}\")])\n                    .stdout(Stdio::piped())\n                    .stderr(Stdio::piped())\n                    .spawn()\n                    .context(\"failed to spawn local sync server\")?;\n\n                let user_url = format!(\"http://localhost:{port}\");\n\n                // wait for server readiness\n                loop {\n                    if client.get(&user_url).send().await.is_ok() {\n                        break;\n                    }\n                    sleep(Duration::from_millis(100));\n                }\n\n                Ok(Self {\n                    user_url: user_url.clone(),\n                    db_url: user_url,\n                    host: String::new(),\n                    server: Some(child),\n                    client,\n                })\n            }\n        }\n\n        pub fn db_url(&self) -> &str {\n            &self.db_url\n        }\n\n        pub async fn db_sql(&self, sql: &str) -> Result<Vec<Vec<Value>>> {\n            let resp = self\n                .client\n                .post(format!(\"{}/v2/pipeline\", self.user_url))\n                .header(\"Host\", &self.host)\n                .json(&json!({\n                    \"requests\": [{\n                        \"type\": \"execute\",\n                        \"stmt\": { \"sql\": sql }\n                    }]\n                }))\n                .send()\n                .await?\n                .error_for_status()?;\n\n            let value: serde_json::Value = resp.json().await?;\n\n            let result = &value[\"results\"][0];\n            if result[\"type\"] != \"ok\" {\n                return Err(anyhow!(\"remote sql execution failed: {value}\"));\n            }\n\n            let rows = result[\"response\"][\"result\"][\"rows\"]\n                .as_array()\n                .ok_or_else(|| anyhow!(\"invalid response shape\"))?;\n\n            Ok(rows\n                .iter()\n                .map(|row| {\n                    row.as_array()\n                        .unwrap()\n                        .iter()\n                        .map(|cell| match cell[\"value\"].clone() {\n                            serde_json::Value::Null => Value::Null,\n                            serde_json::Value::Number(number) => {\n                                if number.is_i64() {\n                                    Value::Integer(number.as_i64().unwrap())\n                                } else {\n                                    Value::Real(number.as_f64().unwrap())\n                                }\n                            }\n                            serde_json::Value::String(s) => Value::Text(s),\n                            _ => panic!(\"unexpected json output\"),\n                        })\n                        .collect()\n                })\n                .collect())\n        }\n    }\n\n    impl Drop for TursoServer {\n        fn drop(&mut self) {\n            if let Some(child) = &mut self.server {\n                let _ = child.kill();\n            }\n        }\n    }\n\n    async fn all_rows(mut rows: Rows) -> Result<Vec<Vec<Value>>> {\n        let mut result = Vec::new();\n        while let Some(row) = rows.next().await? {\n            result.push(row.values.into_iter().map(|x| x.into()).collect());\n        }\n        Ok(result)\n    }\n\n    #[tokio::test]\n    pub async fn test_sync_bootstrap() {\n        let _ = tracing_subscriber::fmt::try_init();\n        let server = TursoServer::new().await.unwrap();\n        server.db_sql(\"CREATE TABLE t(x)\").await.unwrap();\n        server\n            .db_sql(\"INSERT INTO t VALUES ('hello'), ('turso'), ('sync')\")\n            .await\n            .unwrap();\n        server.db_sql(\"SELECT * FROM t\").await.unwrap();\n        let db = crate::sync::Builder::new_remote(\":memory:\")\n            .with_remote_url(server.db_url())\n            .build()\n            .await\n            .unwrap();\n        let conn = db.connect().await.unwrap();\n        let rows = conn.query(\"SELECT * FROM t\", ()).await.unwrap();\n        let all = all_rows(rows).await.unwrap();\n        assert_eq!(\n            all,\n            vec![\n                vec![Value::Text(\"hello\".to_string())],\n                vec![Value::Text(\"turso\".to_string())],\n                vec![Value::Text(\"sync\".to_string())],\n            ]\n        );\n    }\n\n    #[tokio::test]\n    pub async fn test_sync_bootstrap_persistence() {\n        let _ = tracing_subscriber::fmt::try_init();\n        let dir = TempDir::new().unwrap();\n        let server = TursoServer::new().await.unwrap();\n        server.db_sql(\"CREATE TABLE t(x)\").await.unwrap();\n        server\n            .db_sql(\"INSERT INTO t VALUES ('hello'), ('turso'), ('sync')\")\n            .await\n            .unwrap();\n        server.db_sql(\"SELECT * FROM t\").await.unwrap();\n        let db = crate::sync::Builder::new_remote(dir.path().join(\"local.db\").to_str().unwrap())\n            .with_remote_url(server.db_url())\n            .build()\n            .await\n            .unwrap();\n        let conn = db.connect().await.unwrap();\n        let rows = conn.query(\"SELECT * FROM t\", ()).await.unwrap();\n        let all = all_rows(rows).await.unwrap();\n        assert_eq!(\n            all,\n            vec![\n                vec![Value::Text(\"hello\".to_string())],\n                vec![Value::Text(\"turso\".to_string())],\n                vec![Value::Text(\"sync\".to_string())],\n            ]\n        );\n    }\n\n    #[tokio::test]\n    pub async fn test_sync_config_persistence() {\n        let _ = tracing_subscriber::fmt::try_init();\n        let dir = TempDir::new().unwrap();\n        let server = TursoServer::new().await.unwrap();\n        server.db_sql(\"CREATE TABLE t(x)\").await.unwrap();\n        server.db_sql(\"INSERT INTO t VALUES (42)\").await.unwrap();\n        {\n            let db1 =\n                crate::sync::Builder::new_remote(dir.path().join(\"local.db\").to_str().unwrap())\n                    .with_remote_url(server.db_url())\n                    .build()\n                    .await\n                    .unwrap();\n            let conn = db1.connect().await.unwrap();\n            let rows = conn.query(\"SELECT * FROM t\", ()).await.unwrap();\n            let all = all_rows(rows).await.unwrap();\n            assert_eq!(all, vec![vec![Value::Integer(42)],]);\n        }\n        server.db_sql(\"INSERT INTO t VALUES (41)\").await.unwrap();\n        {\n            let db2 =\n                crate::sync::Builder::new_remote(dir.path().join(\"local.db\").to_str().unwrap())\n                    .build()\n                    .await\n                    .unwrap();\n            db2.pull().await.unwrap();\n            let conn = db2.connect().await.unwrap();\n            let rows = conn.query(\"SELECT * FROM t\", ()).await.unwrap();\n            let all = all_rows(rows).await.unwrap();\n            assert_eq!(\n                all,\n                vec![vec![Value::Integer(42)], vec![Value::Integer(41)],]\n            );\n        }\n    }\n\n    #[tokio::test]\n    pub async fn test_sync_pull() {\n        let _ = tracing_subscriber::fmt::try_init();\n        let server = TursoServer::new().await.unwrap();\n        server.db_sql(\"CREATE TABLE t(x)\").await.unwrap();\n        server\n            .db_sql(\"INSERT INTO t VALUES ('hello'), ('turso'), ('sync')\")\n            .await\n            .unwrap();\n        server.db_sql(\"SELECT * FROM t\").await.unwrap();\n        let db = crate::sync::Builder::new_remote(\":memory:\")\n            .with_remote_url(server.db_url())\n            .build()\n            .await\n            .unwrap();\n        let conn = db.connect().await.unwrap();\n        let rows = conn.query(\"SELECT * FROM t\", ()).await.unwrap();\n        let all = all_rows(rows).await.unwrap();\n        assert_eq!(\n            all,\n            vec![\n                vec![Value::Text(\"hello\".to_string())],\n                vec![Value::Text(\"turso\".to_string())],\n                vec![Value::Text(\"sync\".to_string())],\n            ]\n        );\n\n        server\n            .db_sql(\"INSERT INTO t VALUES ('pull works')\")\n            .await\n            .unwrap();\n\n        let rows = conn.query(\"SELECT * FROM t\", ()).await.unwrap();\n        let all = all_rows(rows).await.unwrap();\n        assert_eq!(\n            all,\n            vec![\n                vec![Value::Text(\"hello\".to_string())],\n                vec![Value::Text(\"turso\".to_string())],\n                vec![Value::Text(\"sync\".to_string())],\n            ]\n        );\n\n        db.pull().await.unwrap();\n\n        let rows = conn.query(\"SELECT * FROM t\", ()).await.unwrap();\n        let all = all_rows(rows).await.unwrap();\n        assert_eq!(\n            all,\n            vec![\n                vec![Value::Text(\"hello\".to_string())],\n                vec![Value::Text(\"turso\".to_string())],\n                vec![Value::Text(\"sync\".to_string())],\n                vec![Value::Text(\"pull works\".to_string())],\n            ]\n        );\n    }\n\n    #[tokio::test]\n    pub async fn test_sync_push() {\n        let _ = tracing_subscriber::fmt::try_init();\n        let server = TursoServer::new().await.unwrap();\n        server.db_sql(\"CREATE TABLE t(x)\").await.unwrap();\n        server\n            .db_sql(\"INSERT INTO t VALUES ('hello'), ('turso'), ('sync')\")\n            .await\n            .unwrap();\n        server.db_sql(\"SELECT * FROM t\").await.unwrap();\n        let db = crate::sync::Builder::new_remote(\":memory:\")\n            .with_remote_url(server.db_url())\n            .build()\n            .await\n            .unwrap();\n        let conn = db.connect().await.unwrap();\n        let rows = conn.query(\"SELECT * FROM t\", ()).await.unwrap();\n        let all = all_rows(rows).await.unwrap();\n        assert_eq!(\n            all,\n            vec![\n                vec![Value::Text(\"hello\".to_string())],\n                vec![Value::Text(\"turso\".to_string())],\n                vec![Value::Text(\"sync\".to_string())],\n            ]\n        );\n\n        conn.execute(\"INSERT INTO t VALUES ('push works')\", ())\n            .await\n            .unwrap();\n\n        let all = server.db_sql(\"SELECT * FROM t\").await.unwrap();\n        assert_eq!(\n            all,\n            vec![\n                vec![Value::Text(\"hello\".to_string())],\n                vec![Value::Text(\"turso\".to_string())],\n                vec![Value::Text(\"sync\".to_string())],\n            ]\n        );\n\n        db.push().await.unwrap();\n\n        let rows = conn.query(\"SELECT * FROM t\", ()).await.unwrap();\n        let all = all_rows(rows).await.unwrap();\n        assert_eq!(\n            all,\n            vec![\n                vec![Value::Text(\"hello\".to_string())],\n                vec![Value::Text(\"turso\".to_string())],\n                vec![Value::Text(\"sync\".to_string())],\n                vec![Value::Text(\"push works\".to_string())],\n            ]\n        );\n    }\n\n    #[tokio::test]\n    pub async fn test_sync_checkpoint() {\n        let _ = tracing_subscriber::fmt::try_init();\n        let server = TursoServer::new().await.unwrap();\n        let db = crate::sync::Builder::new_remote(\":memory:\")\n            .with_remote_url(server.db_url())\n            .build()\n            .await\n            .unwrap();\n        let conn = db.connect().await.unwrap();\n        conn.execute(\"CREATE TABLE t(x)\", ()).await.unwrap();\n        for i in 0..1024 {\n            conn.execute(\"INSERT INTO t VALUES (?)\", (i,))\n                .await\n                .unwrap();\n        }\n\n        let stats1 = db.stats().await.unwrap();\n        assert!(stats1.main_wal_size > 1024 * 1024);\n        db.checkpoint().await.unwrap();\n        let stats2 = db.stats().await.unwrap();\n        assert!(stats2.main_wal_size < 8 * 1024);\n    }\n\n    #[tokio::test]\n    pub async fn test_sync_partial() {\n        let _ = tracing_subscriber::fmt::try_init();\n        let server = TursoServer::new().await.unwrap();\n        server.db_sql(\"CREATE TABLE t(x)\").await.unwrap();\n        server\n            .db_sql(\"INSERT INTO t SELECT randomblob(1024) FROM generate_series(1, 2000)\")\n            .await\n            .unwrap();\n        {\n            let full_db = crate::sync::Builder::new_remote(\":memory:\")\n                .with_remote_url(server.db_url())\n                .build()\n                .await\n                .unwrap();\n            let conn = full_db.connect().await.unwrap();\n            let _ = all_rows(\n                conn.query(\"SELECT LENGTH(x) FROM t LIMIT 1\", ())\n                    .await\n                    .unwrap(),\n            )\n            .await\n            .unwrap();\n            assert!(full_db.stats().await.unwrap().network_received_bytes > 2000 * 1024);\n        }\n        {\n            let partial_db = crate::sync::Builder::new_remote(\":memory:\")\n                .with_remote_url(server.db_url())\n                .with_partial_sync_opts_experimental(PartialSyncOpts {\n                    bootstrap_strategy: Some(PartialBootstrapStrategy::Prefix {\n                        length: 128 * 1024,\n                    }),\n                    segment_size: 128 * 1024,\n                    prefetch: false,\n                })\n                .build()\n                .await\n                .unwrap();\n            let conn = partial_db.connect().await.unwrap();\n            let _ = all_rows(\n                conn.query(\"SELECT LENGTH(x) FROM t LIMIT 1\", ())\n                    .await\n                    .unwrap(),\n            )\n            .await\n            .unwrap();\n            assert!(partial_db.stats().await.unwrap().network_received_bytes < 256 * (1024 + 10));\n            let before = tokio::time::Instant::now();\n            let all = all_rows(\n                conn.query(\"SELECT SUM(LENGTH(x)) FROM t\", ())\n                    .await\n                    .unwrap(),\n            )\n            .await\n            .unwrap();\n            println!(\n                \"duration: {:?}\",\n                tokio::time::Instant::now().duration_since(before)\n            );\n            assert_eq!(all, vec![vec![Value::Integer(2000 * 1024)]]);\n            assert!(partial_db.stats().await.unwrap().network_received_bytes > 2000 * 1024);\n        }\n    }\n\n    #[tokio::test]\n    pub async fn test_sync_partial_segment_size() {\n        let _ = tracing_subscriber::fmt::try_init();\n        let server = TursoServer::new().await.unwrap();\n        server.db_sql(\"CREATE TABLE t(x)\").await.unwrap();\n        server\n            .db_sql(\"INSERT INTO t SELECT randomblob(1024) FROM generate_series(1, 256)\")\n            .await\n            .unwrap();\n        {\n            let full_db = crate::sync::Builder::new_remote(\":memory:\")\n                .with_remote_url(server.db_url())\n                .build()\n                .await\n                .unwrap();\n            let conn = full_db.connect().await.unwrap();\n            let _ = all_rows(\n                conn.query(\"SELECT LENGTH(x) FROM t LIMIT 1\", ())\n                    .await\n                    .unwrap(),\n            )\n            .await\n            .unwrap();\n            assert!(full_db.stats().await.unwrap().network_received_bytes > 256 * 1024);\n        }\n        {\n            let partial_db = crate::sync::Builder::new_remote(\":memory:\")\n                .with_remote_url(server.db_url())\n                .with_partial_sync_opts_experimental(PartialSyncOpts {\n                    bootstrap_strategy: Some(PartialBootstrapStrategy::Prefix {\n                        length: 128 * 1024,\n                    }),\n                    segment_size: 4 * 1024,\n                    prefetch: false,\n                })\n                .build()\n                .await\n                .unwrap();\n            let conn = partial_db.connect().await.unwrap();\n            let _ = all_rows(\n                conn.query(\"SELECT LENGTH(x) FROM t LIMIT 1\", ())\n                    .await\n                    .unwrap(),\n            )\n            .await\n            .unwrap();\n            assert!(partial_db.stats().await.unwrap().network_received_bytes < 128 * 1024 * 3 / 2);\n            let before = tokio::time::Instant::now();\n            let all = all_rows(\n                conn.query(\"SELECT SUM(LENGTH(x)) FROM t\", ())\n                    .await\n                    .unwrap(),\n            )\n            .await\n            .unwrap();\n            println!(\n                \"duration segment size: {:?}\",\n                tokio::time::Instant::now().duration_since(before)\n            );\n            assert_eq!(all, vec![vec![Value::Integer(256 * 1024)]]);\n            assert!(partial_db.stats().await.unwrap().network_received_bytes > 256 * 1024);\n        }\n    }\n\n    #[tokio::test(flavor = \"multi_thread\", worker_threads = 2)]\n    pub async fn test_sync_partial_prefetch() {\n        let _ = tracing_subscriber::fmt::try_init();\n        let server = TursoServer::new().await.unwrap();\n        server.db_sql(\"CREATE TABLE t(x)\").await.unwrap();\n        server\n            .db_sql(\"INSERT INTO t SELECT randomblob(1024) FROM generate_series(1, 2000)\")\n            .await\n            .unwrap();\n        {\n            let full_db = crate::sync::Builder::new_remote(\":memory:\")\n                .with_remote_url(server.db_url())\n                .build()\n                .await\n                .unwrap();\n            let conn = full_db.connect().await.unwrap();\n            let _ = all_rows(\n                conn.query(\"SELECT LENGTH(x) FROM t LIMIT 1\", ())\n                    .await\n                    .unwrap(),\n            )\n            .await\n            .unwrap();\n            assert!(full_db.stats().await.unwrap().network_received_bytes > 2000 * 1024);\n        }\n        {\n            let partial_db = crate::sync::Builder::new_remote(\":memory:\")\n                .with_remote_url(server.db_url())\n                .with_partial_sync_opts_experimental(PartialSyncOpts {\n                    bootstrap_strategy: Some(PartialBootstrapStrategy::Prefix {\n                        length: 128 * 1024,\n                    }),\n                    segment_size: 128 * 1024,\n                    prefetch: true,\n                })\n                .build()\n                .await\n                .unwrap();\n            let conn = partial_db.connect().await.unwrap();\n            let _ = all_rows(\n                conn.query(\"SELECT LENGTH(x) FROM t LIMIT 1\", ())\n                    .await\n                    .unwrap(),\n            )\n            .await\n            .unwrap();\n            assert!(partial_db.stats().await.unwrap().network_received_bytes < 1300 * (1024 + 10));\n            let before = tokio::time::Instant::now();\n            let all = all_rows(\n                conn.query(\"SELECT SUM(LENGTH(x)) FROM t\", ())\n                    .await\n                    .unwrap(),\n            )\n            .await\n            .unwrap();\n            println!(\n                \"duration prefetch: {:?}\",\n                tokio::time::Instant::now().duration_since(before)\n            );\n            assert_eq!(all, vec![vec![Value::Integer(2000 * 1024)]]);\n            assert!(partial_db.stats().await.unwrap().network_received_bytes > 2000 * 1024);\n        }\n    }\n\n    #[tokio::test(flavor = \"multi_thread\", worker_threads = 4)]\n    pub async fn test_sync_parallel_writes_with_sync_ops() {\n        use std::sync::atomic::{AtomicBool, Ordering};\n        use std::sync::Arc;\n        use tokio::sync::Mutex as TokioMutex;\n\n        let _ = tracing_subscriber::fmt::try_init();\n        let server = TursoServer::new().await.unwrap();\n\n        let db = crate::sync::Builder::new_remote(\":memory:\")\n            .with_remote_url(server.db_url())\n            .build()\n            .await\n            .unwrap();\n\n        let conn = db.connect().await.unwrap();\n        conn.execute(\n            \"CREATE TABLE test_data (id INTEGER PRIMARY KEY AUTOINCREMENT, payload TEXT NOT NULL)\",\n            (),\n        )\n        .await\n        .unwrap();\n\n        // ~200KB payload per row\n        let payload = \"X\".repeat(200 * 1024);\n\n        let done = Arc::new(AtomicBool::new(false));\n        let sync_lock = Arc::new(TokioMutex::new(()));\n\n        // Spawn periodic push/pull/checkpoint task (sequential, guarded by sync_lock)\n        let sync_db = db.clone();\n        let sync_done = done.clone();\n        let sync_lock_clone = sync_lock.clone();\n        let sync_task = tokio::spawn(async move {\n            let mut cycle = 0u32;\n            while !sync_done.load(Ordering::Relaxed) {\n                tokio::time::sleep(Duration::from_millis(100)).await;\n                let _guard = sync_lock_clone.lock().await;\n                eprintln!(\"sync cycle {cycle}: push\");\n                if let Err(e) = sync_db.push().await {\n                    eprintln!(\"push error (cycle {cycle}): {e}\");\n                }\n                eprintln!(\"sync cycle {cycle}: pull\");\n                if let Err(e) = sync_db.pull().await {\n                    eprintln!(\"pull error (cycle {cycle}): {e}\");\n                }\n                eprintln!(\"sync cycle {cycle}: checkpoint\");\n                if let Err(e) = sync_db.checkpoint().await {\n                    eprintln!(\"checkpoint error (cycle {cycle}): {e}\");\n                }\n                cycle += 1;\n            }\n            cycle\n        });\n\n        // Parallel writes: 4 connections, each inserting 5 rows (~200KB each)\n        let mut write_handles = Vec::new();\n        let mut connections = Vec::new();\n        let (conn_cnt, iterations_cnt, after_cnt) = (8u32, 100u32, 100u32);\n        for _ in 0..conn_cnt {\n            let db = db.clone();\n            let conn = db.connect().await.unwrap();\n            conn.execute(\"PRAGMA busy_timeout=5000\", ()).await.unwrap();\n            connections.push(Some((db, conn)));\n        }\n        for conn_id in 0..conn_cnt {\n            let (_, conn) = connections[conn_id as usize].take().unwrap();\n            let payload = payload.clone();\n            write_handles.push(tokio::spawn(async move {\n                for row_id in 0..iterations_cnt {\n                    let tag = format!(\"conn{conn_id}_row{row_id}\");\n                    let data = format!(\"{tag}_{payload}\");\n                    loop {\n                        match conn\n                            .execute(\n                                \"INSERT INTO test_data (payload) VALUES (?)\",\n                                crate::params::Params::Positional(vec![Value::Text(data.clone())]),\n                            )\n                            .await\n                        {\n                            Ok(_) => break,\n                            Err(crate::Error::Busy(_)) => {\n                                tokio::time::sleep(Duration::from_millis(10)).await;\n                                continue;\n                            }\n                            Err(e) => panic!(\"insert failed (conn{conn_id}, row{row_id}): {e:?}\"),\n                        }\n                    }\n                }\n            }));\n        }\n        for h in write_handles {\n            h.await.unwrap();\n        }\n\n        // Sequential writes: 3 more large inserts\n        for i in 0..after_cnt {\n            let data = format!(\"sequential_{i}_{payload}\");\n            conn.execute(\n                \"INSERT INTO test_data (payload) VALUES (?)\",\n                crate::params::Params::Positional(vec![Value::Text(data)]),\n            )\n            .await\n            .unwrap();\n        }\n\n        // Signal sync task to stop and wait for it\n        done.store(true, Ordering::Relaxed);\n        let sync_cycles = sync_task.await.unwrap();\n        eprintln!(\"completed {sync_cycles} sync cycles during writes\");\n\n        let rows = conn\n            .query(\"SELECT count(*) FROM test_data\", ())\n            .await\n            .unwrap();\n        let all = all_rows(rows).await.unwrap();\n        assert_eq!(\n            all,\n            vec![vec![Value::Integer(\n                (after_cnt + conn_cnt * iterations_cnt) as i64\n            )]]\n        );\n\n        // Report WAL size via stats\n        let stats = db.stats().await.unwrap();\n        eprintln!(\n            \"WAL size after all writes: {} bytes ({:.2} KB)\",\n            stats.main_wal_size,\n            stats.main_wal_size as f64 / 1024.0\n        );\n    }\n}\n"
  },
  {
    "path": "bindings/rust/src/transaction.rs",
    "content": "use std::{ops::Deref, sync::atomic::Ordering};\n\nuse crate::{Connection, Result, Statement};\n\n/// Options for transaction behavior. See [BEGIN\n/// TRANSACTION](http://www.sqlite.org/lang_transaction.html) for details.\n#[derive(Copy, Clone)]\n#[non_exhaustive]\npub enum TransactionBehavior {\n    /// DEFERRED means that the transaction does not actually start until the\n    /// database is first accessed.\n    Deferred,\n    /// IMMEDIATE cause the database connection to start a new write\n    /// immediately, without waiting for a writes statement.\n    Immediate,\n    /// EXCLUSIVE prevents other database connections from reading the database\n    /// while the transaction is underway.\n    Exclusive,\n}\n\n/// Options for how a Transaction should behave when it is dropped.\n#[derive(Copy, Clone, Debug, PartialEq, Eq)]\n#[non_exhaustive]\npub enum DropBehavior {\n    /// Roll back the changes. This is the default.\n    Rollback,\n\n    /// Commit the changes.\n    Commit,\n\n    /// Do not commit or roll back changes - this will leave the transaction or\n    /// savepoint open, so should be used with care.\n    Ignore,\n\n    /// Panic. Used to enforce intentional behavior during development.\n    Panic,\n}\n\nimpl From<DropBehavior> for u8 {\n    fn from(behavior: DropBehavior) -> Self {\n        match behavior {\n            DropBehavior::Rollback => 0,\n            DropBehavior::Commit => 1,\n            DropBehavior::Ignore => 2,\n            DropBehavior::Panic => 3,\n        }\n    }\n}\n\nimpl From<u8> for DropBehavior {\n    fn from(value: u8) -> Self {\n        match value {\n            0 => DropBehavior::Rollback,\n            1 => DropBehavior::Commit,\n            2 => DropBehavior::Ignore,\n            3 => DropBehavior::Panic,\n            _ => panic!(\"Invalid drop behavior: {value}\"),\n        }\n    }\n}\n\n/// Represents a transaction on a database connection.\n///\n/// ## Note\n///\n/// Transactions will roll back by default. Use `commit` method to explicitly\n/// commit the transaction, or use `set_drop_behavior` to change what happens\n/// on the next access to the connection after the transaction is dropped.\n///\n/// ## Example\n///\n/// ```rust,no_run\n/// # use turso::{Connection, Result};\n/// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }\n/// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }\n/// async fn perform_queries(conn: &mut Connection) -> Result<()> {\n///     let tx = conn.transaction().await?;\n///\n///     do_queries_part_1(&tx)?; // tx causes rollback if this fails\n///     do_queries_part_2(&tx)?; // tx causes rollback if this fails\n///\n///     tx.commit().await\n/// }\n/// ```\n#[derive(Debug)]\npub struct Transaction<'conn> {\n    conn: &'conn Connection,\n    drop_behavior: DropBehavior,\n    in_progress: bool,\n}\n\nimpl Transaction<'_> {\n    /// Begin a new transaction. Cannot be nested;\n    ///\n    /// Even though we don't mutate the connection, we take a `&mut Connection`\n    /// to prevent nested transactions on the same connection. For cases\n    /// where this is unacceptable, [`Transaction::new_unchecked`] is available.\n    #[inline]\n    pub async fn new(\n        conn: &mut Connection,\n        behavior: TransactionBehavior,\n    ) -> Result<Transaction<'_>> {\n        Self::new_unchecked(conn, behavior).await\n    }\n\n    /// Begin a new transaction, failing if a transaction is open.\n    ///\n    /// If a transaction is already open, this will return an error. Where\n    /// possible, [`Transaction::new`] should be preferred, as it provides a\n    /// compile-time guarantee that transactions are not nested.\n    #[inline]\n    pub async fn new_unchecked(\n        conn: &Connection,\n        behavior: TransactionBehavior,\n    ) -> Result<Transaction<'_>> {\n        let query = match behavior {\n            TransactionBehavior::Deferred => \"BEGIN DEFERRED\",\n            TransactionBehavior::Immediate => \"BEGIN IMMEDIATE\",\n            TransactionBehavior::Exclusive => \"BEGIN EXCLUSIVE\",\n        };\n        // TODO: Use execute_batch instead\n        conn.execute(query, ()).await.map(move |_| Transaction {\n            conn,\n            drop_behavior: DropBehavior::Rollback,\n            in_progress: true,\n        })\n    }\n\n    // Use the Connection to Prepare a statement.\n    // This allows a database update function to be passed a transaction,\n    // prepare a statement, and use it without needing direct access to the\n    // Connection\n    pub async fn prepare(&self, sql: &str) -> Result<Statement> {\n        self.conn.prepare(sql).await\n    }\n\n    /// Get the current setting for what happens to the transaction when it is\n    /// dropped.\n    #[inline]\n    #[must_use]\n    pub fn drop_behavior(&self) -> DropBehavior {\n        self.drop_behavior\n    }\n\n    /// Configure the transaction to perform the specified action when it is\n    /// dropped.\n    #[inline]\n    pub fn set_drop_behavior(&mut self, drop_behavior: DropBehavior) {\n        self.drop_behavior = drop_behavior;\n    }\n\n    /// A convenience method which consumes and commits a transaction.\n    #[inline]\n    pub async fn commit(mut self) -> Result<()> {\n        self._commit().await\n    }\n\n    #[inline]\n    async fn _commit(&mut self) -> Result<()> {\n        self.conn.execute(\"COMMIT\", ()).await?;\n        self.in_progress = false;\n        Ok(())\n    }\n\n    /// A convenience method which consumes and rolls back a transaction.\n    #[inline]\n    pub async fn rollback(mut self) -> Result<()> {\n        self._rollback().await\n    }\n\n    #[inline]\n    async fn _rollback(&mut self) -> Result<()> {\n        self.conn.execute(\"ROLLBACK\", ()).await?;\n        self.in_progress = false;\n        Ok(())\n    }\n\n    /// Consumes the transaction, committing or rolling back according to the\n    /// current setting (see `drop_behavior`).\n    ///\n    /// Functionally equivalent to the `Drop` implementation, but allows\n    /// callers to see any errors that occur.\n    #[inline]\n    pub async fn finish(mut self) -> Result<()> {\n        self._finish().await\n    }\n\n    #[inline]\n    async fn _finish(&mut self) -> Result<()> {\n        if self.conn.is_autocommit()? {\n            return Ok(());\n        }\n        match self.drop_behavior() {\n            DropBehavior::Commit => {\n                if (self._commit().await).is_err() {\n                    self._rollback().await\n                } else {\n                    Ok(())\n                }\n            }\n            DropBehavior::Rollback => self._rollback().await,\n            DropBehavior::Ignore => Ok(()),\n            DropBehavior::Panic => panic!(\"Transaction dropped unexpectedly.\"),\n        }\n    }\n}\n\nimpl Deref for Transaction<'_> {\n    type Target = Connection;\n\n    #[inline]\n    fn deref(&self) -> &Connection {\n        self.conn\n    }\n}\n\nimpl Drop for Transaction<'_> {\n    #[inline]\n    fn drop(&mut self) {\n        if self.in_progress {\n            self.conn\n                .dangling_tx\n                .store(self.drop_behavior(), Ordering::SeqCst);\n        } else {\n            self.conn\n                .dangling_tx\n                .store(DropBehavior::Ignore, Ordering::SeqCst);\n        }\n    }\n}\n\nimpl Connection {\n    /// Begin a new transaction with the default behavior (DEFERRED).\n    ///\n    /// The transaction defaults to rolling back on the next access to the connection\n    /// if it is not finished when the transaction is dropped. If you\n    /// want the transaction to commit, you must call\n    /// [`commit`](Transaction::commit) or\n    /// [`set_drop_behavior(DropBehavior::Commit)`](Transaction::set_drop_behavior).\n    ///\n    /// ## Example\n    ///\n    /// ```rust,no_run\n    /// # use turso::{Connection, Result};\n    /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }\n    /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }\n    /// async fn perform_queries(conn: &mut Connection) -> Result<()> {\n    ///     let tx = conn.transaction().await?;\n    ///\n    ///     do_queries_part_1(&tx)?; // tx causes rollback if this fails\n    ///     do_queries_part_2(&tx)?; // tx causes rollback if this fails\n    ///\n    ///     tx.commit().await\n    /// }\n    /// ```\n    ///\n    /// # Failure\n    ///\n    /// Will return `Err` if the call fails.\n    #[inline]\n    pub async fn transaction(&mut self) -> Result<Transaction<'_>> {\n        self.transaction_with_behavior(self.transaction_behavior)\n            .await\n    }\n\n    /// Begin a new transaction with a specified behavior.\n    ///\n    /// See [`transaction`](Connection::transaction).\n    ///\n    /// # Failure\n    ///\n    /// Will return `Err` if the call fails.\n    #[inline]\n    pub async fn transaction_with_behavior(\n        &mut self,\n        behavior: TransactionBehavior,\n    ) -> Result<Transaction<'_>> {\n        self.maybe_handle_dangling_tx().await?;\n        Transaction::new(self, behavior).await\n    }\n\n    /// Begin a new transaction with the default behavior (DEFERRED).\n    ///\n    /// Attempt to open a nested transaction will result in a SQLite error.\n    /// `Connection::transaction` prevents this at compile time by taking `&mut\n    /// self`, but `Connection::unchecked_transaction()` may be used to defer\n    /// the checking until runtime.\n    ///\n    /// See [`Connection::transaction`] and [`Transaction::new_unchecked`]\n    /// (which can be used if the default transaction behavior is undesirable).\n    ///\n    /// ## Example\n    ///\n    /// ```rust,no_run\n    /// # use turso::{Connection, Result};\n    /// # use std::rc::Rc;\n    /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }\n    /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }\n    /// async fn perform_queries(conn: Rc<Connection>) -> Result<()> {\n    ///     let tx = conn.unchecked_transaction().await?;\n    ///\n    ///     do_queries_part_1(&tx)?; // tx causes rollback if this fails\n    ///     do_queries_part_2(&tx)?; // tx causes rollback if this fails\n    ///\n    ///     tx.commit().await\n    /// }\n    /// ```\n    ///\n    /// # Failure\n    ///\n    /// Will return `Err` if the underlying SQLite call fails. The specific\n    /// error returned if transactions are nested is currently unspecified.\n    pub async fn unchecked_transaction(&self) -> Result<Transaction<'_>> {\n        Transaction::new_unchecked(self, self.transaction_behavior).await\n    }\n\n    /// Set the default transaction behavior for the connection.\n    ///\n    /// ## Note\n    ///\n    /// This will only apply to transactions initiated by [`transaction`](Connection::transaction)\n    /// or [`unchecked_transaction`](Connection::unchecked_transaction).\n    ///\n    /// ## Example\n    ///\n    /// ```rust,no_run\n    /// # use turso::{Connection, Result};\n    /// # use turso::transaction::TransactionBehavior;\n    /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }\n    /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }\n    /// async fn perform_queries(conn: &mut Connection) -> Result<()> {\n    ///     conn.set_transaction_behavior(TransactionBehavior::Immediate);\n    ///\n    ///     let tx = conn.transaction().await?;\n    ///\n    ///     do_queries_part_1(&tx)?; // tx causes rollback if this fails\n    ///     do_queries_part_2(&tx)?; // tx causes rollback if this fails\n    ///\n    ///     tx.commit().await\n    /// }\n    /// ```\n    pub fn set_transaction_behavior(&mut self, behavior: TransactionBehavior) {\n        self.transaction_behavior = behavior;\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use crate::{Builder, Connection, Error, Result};\n\n    use super::DropBehavior;\n\n    async fn checked_memory_handle() -> Result<Connection> {\n        let db = Builder::new_local(\":memory:\").build().await?;\n        let conn = db.connect()?;\n        conn.execute(\"CREATE TABLE foo (x INTEGER)\", ()).await?;\n        Ok(conn)\n    }\n\n    #[tokio::test]\n    async fn test_drop_rollback_on_new_transaction() {\n        let mut conn = checked_memory_handle().await.unwrap();\n        {\n            let tx = conn.transaction().await.unwrap();\n            tx.execute(\"INSERT INTO foo VALUES(?)\", &[1]).await.unwrap();\n            // Drop without finish - should be rolled back when next transaction starts\n        }\n\n        // Start a new transaction - this should rollback the dangling one\n        let tx = conn.transaction().await.unwrap();\n        tx.execute(\"INSERT INTO foo VALUES(?)\", &[2]).await.unwrap();\n        let result = tx\n            .prepare(\"SELECT SUM(x) FROM foo\")\n            .await\n            .unwrap()\n            .query_row(())\n            .await\n            .unwrap();\n\n        // The insert from the dropped transaction should have been rolled back\n        assert_eq!(2, result.get::<i32>(0).unwrap());\n        tx.finish().await.unwrap();\n    }\n\n    #[tokio::test]\n    async fn test_drop_rollback_on_query() {\n        let mut conn = checked_memory_handle().await.unwrap();\n        {\n            let tx = conn.transaction().await.unwrap();\n            tx.execute(\"INSERT INTO foo VALUES(?)\", &[1]).await.unwrap();\n            // Drop without finish - should be rolled back when conn.query is called\n        }\n\n        // Using conn.query should rollback the dangling transaction\n        let mut rows = conn.query(\"SELECT count(*) FROM foo\", ()).await.unwrap();\n        let result = rows.next().await.unwrap().unwrap();\n\n        // The insert from the dropped transaction should have been rolled back\n        assert_eq!(0, result.get::<i32>(0).unwrap());\n    }\n\n    #[tokio::test]\n    async fn test_drop_rollback_on_execute() {\n        let mut conn = checked_memory_handle().await.unwrap();\n        {\n            let tx = conn.transaction().await.unwrap();\n            tx.execute(\"INSERT INTO foo VALUES(?)\", &[1]).await.unwrap();\n            // Drop without finish - should be rolled back when conn.execute is called\n        }\n\n        // Using conn.execute should rollback the dangling transaction\n        conn.execute(\"INSERT INTO foo VALUES(?)\", &[2])\n            .await\n            .unwrap();\n\n        let mut rows = conn.query(\"SELECT count(*) FROM foo\", ()).await.unwrap();\n        let result = rows.next().await.unwrap().unwrap();\n\n        // The insert from the dropped transaction should have been rolled back\n        assert_eq!(1, result.get::<i32>(0).unwrap());\n    }\n\n    #[tokio::test]\n    async fn test_drop() -> Result<()> {\n        let _ = tracing_subscriber::fmt::try_init();\n        let mut conn = checked_memory_handle().await?;\n        {\n            let tx = conn.transaction().await?;\n            tx.execute(\"INSERT INTO foo VALUES(?)\", &[1]).await?;\n            // default: rollback\n        }\n        {\n            let mut tx = conn.transaction().await?;\n            tx.execute(\"INSERT INTO foo VALUES(?)\", &[2]).await?;\n            tx.set_drop_behavior(DropBehavior::Commit);\n        }\n        {\n            let tx = conn.transaction().await?;\n            let result = tx\n                .prepare(\"SELECT SUM(x) FROM foo\")\n                .await?\n                .query_row(())\n                .await?;\n\n            assert_eq!(2, result.get::<i32>(0)?);\n        }\n        Ok(())\n    }\n\n    fn assert_nested_tx_error(e: Error) {\n        if let Error::Error(e) = &e {\n            assert!(e.contains(\"transaction\"));\n        } else {\n            panic!(\"Unexpected error type: {e:?}\");\n        }\n    }\n\n    #[tokio::test]\n    async fn test_unchecked_nesting() -> Result<()> {\n        let conn = checked_memory_handle().await?;\n\n        {\n            let tx = conn.unchecked_transaction().await?;\n            let e = tx.unchecked_transaction().await.unwrap_err();\n            assert_nested_tx_error(e);\n            tx.finish().await?;\n            // default: rollback\n        }\n        {\n            let tx = conn.unchecked_transaction().await?;\n            tx.execute(\"INSERT INTO foo VALUES(?)\", &[1]).await?;\n            // Ensure this doesn't interfere with ongoing transaction\n            let e = tx.unchecked_transaction().await.unwrap_err();\n            assert_nested_tx_error(e);\n\n            tx.execute(\"INSERT INTO foo VALUES(?)\", &[1]).await?;\n            tx.commit().await?;\n        }\n\n        let result = conn\n            .prepare(\"SELECT SUM(x) FROM foo\")\n            .await?\n            .query_row(())\n            .await?;\n        assert_eq!(2, result.get::<i32>(0)?);\n        Ok(())\n    }\n\n    #[tokio::test]\n    async fn test_explicit_rollback_commit() -> Result<()> {\n        let mut conn = checked_memory_handle().await?;\n        {\n            let tx = conn.transaction().await?;\n            tx.execute(\"INSERT INTO foo VALUES(?)\", &[1]).await?;\n            tx.rollback().await?;\n\n            // This is a current Turso's limitation.\n            // Since we don't have support for savepoints yet,\n            // a rollback ends with a transaction so we need to immediately open a new one.\n            let tx = conn.transaction().await?;\n            tx.execute(\"INSERT INTO foo VALUES(?)\", &[2]).await?;\n            tx.commit().await?;\n        }\n        {\n            let tx = conn.transaction().await?;\n            tx.execute(\"INSERT INTO foo VALUES(?)\", &[4]).await?;\n            tx.commit().await?;\n        }\n        {\n            let result = conn\n                .prepare(\"SELECT SUM(x) FROM foo\")\n                .await?\n                .query_row(())\n                .await?;\n            assert_eq!(6, result.get::<i32>(0)?);\n        }\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "bindings/rust/src/value.rs",
    "content": "use std::str::FromStr;\n\nuse crate::{Error, Result};\n\n#[derive(Clone, Debug, PartialEq)]\npub enum Value {\n    Null,\n    Integer(i64),\n    Real(f64),\n    Text(String),\n    Blob(Vec<u8>),\n}\n\n/// The possible types a column can be in libsql.\n#[derive(Debug, Copy, Clone)]\npub enum ValueType {\n    Integer = 1,\n    Real,\n    Text,\n    Blob,\n    Null,\n}\n\nimpl FromStr for ValueType {\n    type Err = ();\n\n    fn from_str(s: &str) -> std::result::Result<ValueType, Self::Err> {\n        match s {\n            \"TEXT\" => Ok(ValueType::Text),\n            \"INTEGER\" => Ok(ValueType::Integer),\n            \"BLOB\" => Ok(ValueType::Blob),\n            \"NULL\" => Ok(ValueType::Null),\n            \"REAL\" => Ok(ValueType::Real),\n            _ => Err(()),\n        }\n    }\n}\n\nimpl Value {\n    /// Returns `true` if the value is [`Null`].\n    ///\n    /// [`Null`]: Value::Null\n    #[must_use]\n    pub fn is_null(&self) -> bool {\n        matches!(self, Self::Null)\n    }\n\n    /// Returns `true` if the value is [`Integer`].\n    ///\n    /// [`Integer`]: Value::Integer\n    #[must_use]\n    pub fn is_integer(&self) -> bool {\n        matches!(self, Self::Integer(..))\n    }\n\n    /// Returns `true` if the value is [`Real`].\n    ///\n    /// [`Real`]: Value::Real\n    #[must_use]\n    pub fn is_real(&self) -> bool {\n        matches!(self, Self::Real(..))\n    }\n\n    pub fn as_real(&self) -> Option<&f64> {\n        if let Self::Real(v) = self {\n            Some(v)\n        } else {\n            None\n        }\n    }\n\n    /// Returns `true` if the value is [`Text`].\n    ///\n    /// [`Text`]: Value::Text\n    #[must_use]\n    pub fn is_text(&self) -> bool {\n        matches!(self, Self::Text(..))\n    }\n\n    pub fn as_text(&self) -> Option<&String> {\n        if let Self::Text(v) = self {\n            Some(v)\n        } else {\n            None\n        }\n    }\n\n    pub fn as_integer(&self) -> Option<&i64> {\n        if let Self::Integer(v) = self {\n            Some(v)\n        } else {\n            None\n        }\n    }\n\n    /// Returns `true` if the value is [`Blob`].\n    ///\n    /// [`Blob`]: Value::Blob\n    #[must_use]\n    pub fn is_blob(&self) -> bool {\n        matches!(self, Self::Blob(..))\n    }\n\n    pub fn as_blob(&self) -> Option<&Vec<u8>> {\n        if let Self::Blob(v) = self {\n            Some(v)\n        } else {\n            None\n        }\n    }\n}\n\nimpl From<turso_sdk_kit::rsapi::Value> for Value {\n    fn from(val: turso_sdk_kit::rsapi::Value) -> Self {\n        match val {\n            turso_sdk_kit::rsapi::Value::Null => Value::Null,\n            turso_sdk_kit::rsapi::Value::Numeric(turso_sdk_kit::rsapi::Numeric::Integer(n)) => {\n                Value::Integer(n)\n            }\n            turso_sdk_kit::rsapi::Value::Numeric(turso_sdk_kit::rsapi::Numeric::Float(n)) => {\n                Value::Real(f64::from(n))\n            }\n            turso_sdk_kit::rsapi::Value::Text(t) => Value::Text(t.into()),\n            turso_sdk_kit::rsapi::Value::Blob(items) => Value::Blob(items),\n        }\n    }\n}\n\nimpl From<Value> for turso_sdk_kit::rsapi::Value {\n    fn from(val: Value) -> Self {\n        match val {\n            Value::Null => turso_sdk_kit::rsapi::Value::Null,\n            Value::Integer(n) => turso_sdk_kit::rsapi::Value::from_i64(n),\n            Value::Real(n) => turso_sdk_kit::rsapi::Value::from_f64(n),\n            Value::Text(t) => turso_sdk_kit::rsapi::Value::from_text(t),\n            Value::Blob(items) => turso_sdk_kit::rsapi::Value::from_blob(items),\n        }\n    }\n}\n\nimpl From<i8> for Value {\n    fn from(value: i8) -> Value {\n        Value::Integer(value as i64)\n    }\n}\n\nimpl From<i16> for Value {\n    fn from(value: i16) -> Value {\n        Value::Integer(value as i64)\n    }\n}\n\nimpl From<i32> for Value {\n    fn from(value: i32) -> Value {\n        Value::Integer(value as i64)\n    }\n}\n\nimpl From<i64> for Value {\n    fn from(value: i64) -> Value {\n        Value::Integer(value)\n    }\n}\n\nimpl From<u8> for Value {\n    fn from(value: u8) -> Value {\n        Value::Integer(value as i64)\n    }\n}\n\nimpl From<u16> for Value {\n    fn from(value: u16) -> Value {\n        Value::Integer(value as i64)\n    }\n}\n\nimpl From<u32> for Value {\n    fn from(value: u32) -> Value {\n        Value::Integer(value as i64)\n    }\n}\n\nimpl TryFrom<u64> for Value {\n    type Error = Error;\n\n    fn try_from(value: u64) -> Result<Value> {\n        if value > i64::MAX as u64 {\n            Err(Error::ToSqlConversionFailure(\n                \"u64 is too large to fit in an i64\".into(),\n            ))\n        } else {\n            Ok(Value::Integer(value as i64))\n        }\n    }\n}\n\nimpl From<f32> for Value {\n    fn from(value: f32) -> Value {\n        Value::Real(value as f64)\n    }\n}\n\nimpl From<f64> for Value {\n    fn from(value: f64) -> Value {\n        Value::Real(value)\n    }\n}\n\nimpl From<&str> for Value {\n    fn from(value: &str) -> Value {\n        Value::Text(value.to_owned())\n    }\n}\n\nimpl From<String> for Value {\n    fn from(value: String) -> Value {\n        Value::Text(value)\n    }\n}\n\nimpl From<&[u8]> for Value {\n    fn from(value: &[u8]) -> Value {\n        Value::Blob(value.to_owned())\n    }\n}\n\nimpl From<Vec<u8>> for Value {\n    fn from(value: Vec<u8>) -> Value {\n        Value::Blob(value)\n    }\n}\n\nimpl From<bool> for Value {\n    fn from(value: bool) -> Value {\n        Value::Integer(value as i64)\n    }\n}\n\nimpl<T> From<Option<T>> for Value\nwhere\n    T: Into<Value>,\n{\n    fn from(value: Option<T>) -> Self {\n        match value {\n            Some(inner) => inner.into(),\n            None => Value::Null,\n        }\n    }\n}\n\n/// A borrowed version of `Value`.\n#[derive(Debug)]\npub enum ValueRef<'a> {\n    Null,\n    Integer(i64),\n    Real(f64),\n    Text(&'a [u8]),\n    Blob(&'a [u8]),\n}\n\nimpl ValueRef<'_> {\n    pub fn data_type(&self) -> ValueType {\n        match *self {\n            ValueRef::Null => ValueType::Null,\n            ValueRef::Integer(_) => ValueType::Integer,\n            ValueRef::Real(_) => ValueType::Real,\n            ValueRef::Text(_) => ValueType::Text,\n            ValueRef::Blob(_) => ValueType::Blob,\n        }\n    }\n\n    /// Returns `true` if the value ref is [`Null`].\n    ///\n    /// [`Null`]: ValueRef::Null\n    #[must_use]\n    pub fn is_null(&self) -> bool {\n        matches!(self, Self::Null)\n    }\n\n    /// Returns `true` if the value ref is [`Integer`].\n    ///\n    /// [`Integer`]: ValueRef::Integer\n    #[must_use]\n    pub fn is_integer(&self) -> bool {\n        matches!(self, Self::Integer(..))\n    }\n\n    pub fn as_integer(&self) -> Option<&i64> {\n        if let Self::Integer(v) = self {\n            Some(v)\n        } else {\n            None\n        }\n    }\n\n    /// Returns `true` if the value ref is [`Real`].\n    ///\n    /// [`Real`]: ValueRef::Real\n    #[must_use]\n    pub fn is_real(&self) -> bool {\n        matches!(self, Self::Real(..))\n    }\n\n    pub fn as_real(&self) -> Option<&f64> {\n        if let Self::Real(v) = self {\n            Some(v)\n        } else {\n            None\n        }\n    }\n\n    /// Returns `true` if the value ref is [`Text`].\n    ///\n    /// [`Text`]: ValueRef::Text\n    #[must_use]\n    pub fn is_text(&self) -> bool {\n        matches!(self, Self::Text(..))\n    }\n\n    pub fn as_text(&self) -> Option<&[u8]> {\n        if let Self::Text(v) = self {\n            Some(v)\n        } else {\n            None\n        }\n    }\n\n    /// Returns `true` if the value ref is [`Blob`].\n    ///\n    /// [`Blob`]: ValueRef::Blob\n    #[must_use]\n    pub fn is_blob(&self) -> bool {\n        matches!(self, Self::Blob(..))\n    }\n\n    pub fn as_blob(&self) -> Option<&[u8]> {\n        if let Self::Blob(v) = self {\n            Some(v)\n        } else {\n            None\n        }\n    }\n}\n\nimpl From<ValueRef<'_>> for Value {\n    fn from(vr: ValueRef<'_>) -> Value {\n        match vr {\n            ValueRef::Null => Value::Null,\n            ValueRef::Integer(i) => Value::Integer(i),\n            ValueRef::Real(r) => Value::Real(r),\n            ValueRef::Text(s) => Value::Text(String::from_utf8_lossy(s).to_string()),\n            ValueRef::Blob(b) => Value::Blob(b.to_vec()),\n        }\n    }\n}\n\nimpl<'a> From<&'a str> for ValueRef<'a> {\n    fn from(s: &str) -> ValueRef<'_> {\n        ValueRef::Text(s.as_bytes())\n    }\n}\n\nimpl<'a> From<&'a [u8]> for ValueRef<'a> {\n    fn from(s: &[u8]) -> ValueRef<'_> {\n        ValueRef::Blob(s)\n    }\n}\n\nimpl<'a> From<&'a Value> for ValueRef<'a> {\n    fn from(v: &'a Value) -> ValueRef<'a> {\n        match *v {\n            Value::Null => ValueRef::Null,\n            Value::Integer(i) => ValueRef::Integer(i),\n            Value::Real(r) => ValueRef::Real(r),\n            Value::Text(ref s) => ValueRef::Text(s.as_bytes()),\n            Value::Blob(ref b) => ValueRef::Blob(b),\n        }\n    }\n}\n\nimpl<'a, T> From<Option<T>> for ValueRef<'a>\nwhere\n    T: Into<ValueRef<'a>>,\n{\n    #[inline]\n    fn from(s: Option<T>) -> ValueRef<'a> {\n        match s {\n            Some(x) => x.into(),\n            None => ValueRef::Null,\n        }\n    }\n}\n"
  },
  {
    "path": "bindings/rust/tests/integration_tests.rs",
    "content": "use tokio::fs;\nuse turso::{Builder, EncryptionOpts, Error, Value};\n\n#[tokio::test]\nasync fn test_rows_next() {\n    let builder = Builder::new_local(\":memory:\");\n    let db = builder.build().await.unwrap();\n    let conn = db.connect().unwrap();\n    conn.execute(\"CREATE TABLE test (x INTEGER)\", ())\n        .await\n        .unwrap();\n    conn.execute(\"INSERT INTO test (x) VALUES (1)\", ())\n        .await\n        .unwrap();\n    assert_eq!(conn.last_insert_rowid(), 1);\n    conn.execute(\"INSERT INTO test (x) VALUES (2)\", ())\n        .await\n        .unwrap();\n    assert_eq!(conn.last_insert_rowid(), 2);\n    conn.execute(\n        \"INSERT INTO test (x) VALUES (:x)\",\n        vec![(\":x\".to_string(), Value::Integer(3))],\n    )\n    .await\n    .unwrap();\n    assert_eq!(conn.last_insert_rowid(), 3);\n    conn.execute(\n        \"INSERT INTO test (x) VALUES (@x)\",\n        vec![(\"@x\".to_string(), Value::Integer(4))],\n    )\n    .await\n    .unwrap();\n    assert_eq!(conn.last_insert_rowid(), 4);\n    conn.execute(\n        \"INSERT INTO test (x) VALUES ($x)\",\n        vec![(\"$x\".to_string(), Value::Integer(5))],\n    )\n    .await\n    .unwrap();\n    assert_eq!(conn.last_insert_rowid(), 5);\n    let mut res = conn.query(\"SELECT * FROM test\", ()).await.unwrap();\n    assert_eq!(\n        res.next().await.unwrap().unwrap().get_value(0).unwrap(),\n        1.into()\n    );\n    assert_eq!(\n        res.next().await.unwrap().unwrap().get_value(0).unwrap(),\n        2.into()\n    );\n    assert_eq!(\n        res.next().await.unwrap().unwrap().get_value(0).unwrap(),\n        3.into()\n    );\n    assert_eq!(\n        res.next().await.unwrap().unwrap().get_value(0).unwrap(),\n        4.into()\n    );\n    assert_eq!(\n        res.next().await.unwrap().unwrap().get_value(0).unwrap(),\n        5.into()\n    );\n    assert!(res.next().await.unwrap().is_none());\n}\n\n#[tokio::test]\nasync fn test_cacheflush() {\n    let builder = Builder::new_local(\"test.db\");\n    let db = builder.build().await.unwrap();\n\n    let conn = db.connect().unwrap();\n\n    conn.execute(\"CREATE TABLE IF NOT EXISTS asdf (x INTEGER)\", ())\n        .await\n        .unwrap();\n\n    // Tests if cache flush breaks transaction isolation\n    conn.execute(\"BEGIN\", ()).await.unwrap();\n    conn.execute(\"INSERT INTO asdf (x) VALUES (1)\", ())\n        .await\n        .unwrap();\n    conn.cacheflush().unwrap();\n    conn.execute(\"ROLLBACK\", ()).await.unwrap();\n\n    conn.execute(\"INSERT INTO asdf (x) VALUES (2)\", ())\n        .await\n        .unwrap();\n    conn.execute(\"INSERT INTO asdf (x) VALUES (3)\", ())\n        .await\n        .unwrap();\n\n    let mut res = conn.query(\"SELECT * FROM asdf\", ()).await.unwrap();\n\n    assert_eq!(\n        res.next().await.unwrap().unwrap().get_value(0).unwrap(),\n        2.into()\n    );\n    assert_eq!(\n        res.next().await.unwrap().unwrap().get_value(0).unwrap(),\n        3.into()\n    );\n\n    // Tests if cache flush doesn't break a committed transaction\n    conn.execute(\"BEGIN\", ()).await.unwrap();\n    conn.execute(\"INSERT INTO asdf (x) VALUES (1)\", ())\n        .await\n        .unwrap();\n    conn.cacheflush().unwrap();\n    conn.execute(\"COMMIT\", ()).await.unwrap();\n\n    let mut res = conn\n        .query(\"SELECT * FROM asdf WHERE x = 1\", ())\n        .await\n        .unwrap();\n\n    assert_eq!(\n        res.next().await.unwrap().unwrap().get_value(0).unwrap(),\n        1.into()\n    );\n\n    fs::remove_file(\"test.db\").await.unwrap();\n    fs::remove_file(\"test.db-wal\").await.unwrap();\n}\n\n#[tokio::test]\nasync fn test_rows_returned() {\n    let db = Builder::new_local(\":memory:\").build().await.unwrap();\n    let conn = db.connect().unwrap();\n\n    //--- CRUD Operations ---//\n    conn.execute(\"CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT)\", ())\n        .await\n        .unwrap();\n    let changed = conn\n        .execute(\"INSERT INTO t VALUES (1,'hello')\", ())\n        .await\n        .unwrap();\n    let changed1 = conn\n        .execute(\"INSERT INTO t VALUES (2,'hi')\", ())\n        .await\n        .unwrap();\n    let changed2 = conn\n        .execute(\"UPDATE t SET val='hi' WHERE id=1\", ())\n        .await\n        .unwrap();\n    let changed3 = conn\n        .execute(\"DELETE FROM t WHERE val='hi'\", ())\n        .await\n        .unwrap();\n    assert_eq!(changed, 1);\n    assert_eq!(changed1, 1);\n    assert_eq!(changed2, 1);\n    assert_eq!(changed3, 2);\n\n    //--- A more complicated example of insert with a select join subquery ---//\n    conn.execute(\n        \"CREATE TABLE authors ( id INTEGER PRIMARY KEY, name TEXT NOT NULL);\n       \",\n        (),\n    )\n    .await\n    .unwrap();\n\n    conn.execute(\n       \"CREATE TABLE books ( id INTEGER PRIMARY KEY, author_id INTEGER NOT NULL REFERENCES authors(id), title TEXT NOT NULL); \"\n       ,()\n   ).await.unwrap();\n\n    conn.execute(\n        \"CREATE TABLE prize_winners ( book_id INTEGER PRIMARY KEY, author_name TEXT NOT NULL);\",\n        (),\n    )\n    .await\n    .unwrap();\n\n    conn.execute(\n        \"INSERT INTO authors (id, name) VALUES (1, 'Alice'), (2, 'Bob');\",\n        (),\n    )\n    .await\n    .unwrap();\n\n    conn.execute(\n       \"INSERT INTO books (id, author_id, title) VALUES (1, 1, 'Rust in Action'), (2, 1, 'Async Adventures'), (3, 1, 'Fearless Concurrency'), (4, 1, 'Unsafe Tales'), (5, 1, 'Zero-Cost Futures'), (6, 2, 'Learning SQL');\",\n       ()\n   ).await.unwrap();\n\n    let rows_changed = conn\n        .execute(\n            \"\n       INSERT INTO prize_winners (book_id, author_name)\n       SELECT b.id, a.name\n       FROM   books b\n       JOIN   authors a ON a.id = b.author_id\n       WHERE  a.id = 1;       -- Alice's five books\n       \",\n            (),\n        )\n        .await\n        .unwrap();\n\n    assert_eq!(rows_changed, 5);\n}\n\n#[tokio::test]\npub async fn test_execute_batch() {\n    let db = Builder::new_local(\":memory:\").build().await.unwrap();\n    let conn = db.connect().unwrap();\n    conn.execute_batch(\"CREATE TABLE authors ( id INTEGER PRIMARY KEY, name TEXT NOT NULL);CREATE TABLE books ( id INTEGER PRIMARY KEY, author_id INTEGER NOT NULL REFERENCES authors(id), title TEXT NOT NULL); INSERT INTO authors (id, name) VALUES (1, 'Alice'), (2, 'Bob');\")\n        .await\n        .unwrap();\n    let mut rows = conn\n        .query(\"SELECT COUNT(*) FROM authors;\", ())\n        .await\n        .unwrap();\n    if let Some(row) = rows.next().await.unwrap() {\n        assert_eq!(row.get_value(0).unwrap(), Value::Integer(2));\n    }\n}\n\n#[tokio::test]\nasync fn test_query_row_returns_first_row() {\n    let db = Builder::new_local(\":memory:\").build().await.unwrap();\n    let conn = db.connect().unwrap();\n\n    conn.execute(\"CREATE TABLE users (id INTEGER, name TEXT)\", ())\n        .await\n        .unwrap();\n\n    conn.execute(\"INSERT INTO users VALUES (1, 'Frodo')\", ())\n        .await\n        .unwrap();\n\n    let row = conn\n        .prepare(\"SELECT id FROM users WHERE name = ?\")\n        .await\n        .unwrap()\n        .query_row(&[\"Frodo\"])\n        .await\n        .unwrap();\n\n    let id: i64 = row.get(0).unwrap();\n    assert_eq!(id, 1);\n}\n\n#[tokio::test]\nasync fn test_query_row_returns_no_rows_error() {\n    let db = Builder::new_local(\":memory:\").build().await.unwrap();\n    let conn = db.connect().unwrap();\n\n    conn.execute(\"CREATE TABLE users (id INTEGER, name TEXT)\", ())\n        .await\n        .unwrap();\n\n    let result = conn\n        .prepare(\"SELECT id FROM users WHERE name = ?\")\n        .await\n        .unwrap()\n        .query_row(&[\"Ghost\"])\n        .await;\n\n    assert!(matches!(result, Err(Error::QueryReturnedNoRows)));\n}\n\n#[tokio::test]\nasync fn test_row_get_column_typed() {\n    let db = Builder::new_local(\":memory:\").build().await.unwrap();\n    let conn = db.connect().unwrap();\n\n    conn.execute(\"CREATE TABLE v (n INTEGER, label TEXT)\", ())\n        .await\n        .unwrap();\n\n    conn.execute(\"INSERT INTO v VALUES (42, 'answer')\", ())\n        .await\n        .unwrap();\n\n    let mut rows = conn.query(\"SELECT * FROM v\", ()).await.unwrap();\n    let row = rows.next().await.unwrap().unwrap();\n\n    let n: i64 = row.get(0).unwrap();\n    let label: String = row.get(1).unwrap();\n\n    assert_eq!(n, 42);\n    assert_eq!(label, \"answer\");\n}\n\n#[tokio::test]\nasync fn test_row_get_conversion_error() {\n    let db = Builder::new_local(\":memory:\").build().await.unwrap();\n    let conn = db.connect().unwrap();\n\n    conn.execute(\"CREATE TABLE t (x TEXT)\", ()).await.unwrap();\n\n    conn.execute(\"INSERT INTO t VALUES (NULL)\", ())\n        .await\n        .unwrap();\n\n    let mut rows = conn.query(\"SELECT x FROM t\", ()).await.unwrap();\n    let row = rows.next().await.unwrap().unwrap();\n\n    // Attempt to convert TEXT into integer (should fail)\n    let result: Result<u32, _> = row.get(0);\n    assert!(matches!(result, Err(Error::ConversionFailure(_))));\n}\n\n#[tokio::test]\nasync fn test_index() {\n    let db = Builder::new_local(\":memory:\").build().await.unwrap();\n    let conn = db.connect().unwrap();\n\n    conn.execute(\"CREATE TABLE users (name TEXT PRIMARY KEY, email TEXT)\", ())\n        .await\n        .unwrap();\n    conn.execute(\"CREATE INDEX email_idx ON users(email)\", ())\n        .await\n        .unwrap();\n    conn.execute(\n        \"INSERT INTO users VALUES ('alice', 'a@b.c'), ('bob', 'b@d.e')\",\n        (),\n    )\n    .await\n    .unwrap();\n\n    let mut rows = conn\n        .query(\"SELECT * FROM users WHERE email = 'a@b.c'\", ())\n        .await\n        .unwrap();\n    let row = rows.next().await.unwrap().unwrap();\n    assert!(row.get::<String>(0).unwrap() == \"alice\");\n    assert!(row.get::<String>(1).unwrap() == \"a@b.c\");\n    assert!(rows.next().await.unwrap().is_none());\n\n    let mut rows = conn\n        .query(\"SELECT * FROM users WHERE email = 'b@d.e'\", ())\n        .await\n        .unwrap();\n    let row = rows.next().await.unwrap().unwrap();\n    assert!(row.get::<String>(0).unwrap() == \"bob\");\n    assert!(row.get::<String>(1).unwrap() == \"b@d.e\");\n    assert!(rows.next().await.unwrap().is_none());\n}\n\n#[tokio::test]\n/// Tests that concurrent statements that error out and rollback can do so without panicking\nasync fn test_concurrent_unique_constraint_regression() {\n    use std::sync::Arc;\n    use tokio::sync::Barrier;\n\n    let db = Builder::new_local(\":memory:\").build().await.unwrap();\n    let conn = db.connect().unwrap();\n\n    conn.execute(\n        \"CREATE TABLE users (\n            id INTEGER PRIMARY KEY AUTOINCREMENT,\n            email TEXT UNIQUE,\n            created_at DATETIME\n        )\",\n        (),\n    )\n    .await\n    .unwrap();\n\n    // Insert initial seed data\n    conn.execute(\n        \"INSERT INTO users (email, created_at) VALUES (:email, :created_at)\",\n        vec![\n            (\":email\".to_string(), Value::Text(\"seed@example.com\".into())),\n            (\":created_at\".to_string(), Value::Text(\"whatever\".into())),\n        ],\n    )\n    .await\n    .unwrap();\n\n    let barrier = Arc::new(Barrier::new(8));\n    let mut handles = Vec::new();\n\n    // Spawn 8 concurrent workers\n    for _ in 0..8 {\n        let conn = db.connect().unwrap();\n        let barrier = barrier.clone();\n\n        handles.push(tokio::spawn(async move {\n            barrier.wait().await;\n\n            let mut prepared_stmt = conn\n                .prepare(\"INSERT INTO users (email, created_at) VALUES (:email, :created_at)\")\n                .await\n                .unwrap();\n            for i in 0..1000 {\n                let email = match i % 3 {\n                    0 => \"seed@example.com\",\n                    1 => \"dup@example.com\",\n                    2 => \"dapper@example.com\",\n                    _ => panic!(\"Invalid email index: {i}\"),\n                };\n                let result = prepared_stmt\n                    .execute(vec![\n                        (\":email\".to_string(), Value::Text(email.into())),\n                        (\":created_at\".to_string(), Value::Text(\"whatever\".into())),\n                    ])\n                    .await;\n                match result {\n                    Ok(_) => (),\n                    Err(Error::Constraint(e)) if e.contains(\"UNIQUE constraint failed\") => {}\n                    Err(Error::Busy(e)) if e.contains(\"database is locked\") => {}\n                    Err(e) => {\n                        panic!(\"Error executing statement: {e:?}\");\n                    }\n                }\n            }\n        }));\n    }\n\n    // Wait for all workers to complete\n    for handle in handles {\n        handle.await.unwrap();\n    }\n}\n\n#[tokio::test]\nasync fn test_statement_query_resets_before_execution() {\n    let db = Builder::new_local(\":memory:\").build().await.unwrap();\n    let conn = db.connect().unwrap();\n\n    conn.execute(\"CREATE TABLE t (id INTEGER PRIMARY KEY, value TEXT)\", ())\n        .await\n        .unwrap();\n\n    for i in 0..5 {\n        conn.execute(&format!(\"INSERT INTO t VALUES ({i}, 'value_{i}')\"), ())\n            .await\n            .unwrap();\n    }\n\n    let mut stmt = conn\n        .prepare(\"SELECT id, value FROM t ORDER BY id\")\n        .await\n        .unwrap();\n\n    let mut rows = stmt.query(()).await.unwrap();\n    let mut count = 0;\n    while let Some(row) = rows.next().await.unwrap() {\n        let id: i64 = row.get(0).unwrap();\n        assert_eq!(id, count);\n        count += 1;\n    }\n    assert_eq!(count, 5);\n\n    let mut rows = stmt.query(()).await.unwrap();\n    let mut count = 0;\n    while let Some(row) = rows.next().await.unwrap() {\n        let id: i64 = row.get(0).unwrap();\n        assert_eq!(id, count);\n        count += 1;\n    }\n    // this will return 0 rows if query() does not reset the statement\n    assert_eq!(count, 5, \"Second query() should return all rows again\");\n}\n\n#[tokio::test]\nasync fn test_encryption() {\n    let temp_dir = tempfile::tempdir().unwrap();\n    let db_file = temp_dir.path().join(\"test-encrypted.db\");\n    let db_file = db_file.to_str().unwrap();\n    let hexkey = \"b1bbfda4f589dc9daaf004fe21111e00dc00c98237102f5c7002a5669fc76327\";\n    let wrong_key = \"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\";\n    let encryption_opts = EncryptionOpts {\n        hexkey: hexkey.to_string(),\n        cipher: \"aegis256\".to_string(),\n    };\n\n    // 1. Create encrypted database and insert data\n    {\n        let builder = Builder::new_local(db_file)\n            .experimental_encryption(true)\n            .with_encryption(encryption_opts.clone());\n        let db = builder.build().await.unwrap();\n        let conn = db.connect().unwrap();\n        conn.execute(\n            \"CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT);\",\n            (),\n        )\n        .await\n        .unwrap();\n        conn.execute(\"INSERT INTO test (value) VALUES ('secret_data')\", ())\n            .await\n            .unwrap();\n        let mut row_count = 0;\n        let mut rows = conn.query(\"SELECT * FROM test\", ()).await.unwrap();\n        while let Some(row) = rows.next().await.unwrap() {\n            assert_eq!(row.get::<i64>(0).unwrap(), 1);\n            assert_eq!(row.get::<String>(1).unwrap(), \"secret_data\");\n            row_count += 1;\n        }\n        assert_eq!(row_count, 1);\n\n        // Checkpoint to ensure data is written to main db file\n        let mut rows = conn\n            .query(\"PRAGMA wal_checkpoint(TRUNCATE)\", ())\n            .await\n            .unwrap();\n        while rows.next().await.unwrap().is_some() {}\n    }\n\n    // 2. Verify data is encrypted on disk\n    let content = std::fs::read(db_file).unwrap();\n    assert!(content.len() > 1024);\n    assert!(\n        !content.windows(11).any(|w| w == b\"secret_data\"),\n        \"Plaintext should not appear in encrypted database file\"\n    );\n\n    // 3. Reopen with correct key and verify data\n    {\n        let builder = Builder::new_local(db_file)\n            .experimental_encryption(true)\n            .with_encryption(encryption_opts.clone());\n        let db = builder.build().await.unwrap();\n        let conn = db.connect().unwrap();\n\n        let mut row_count = 0;\n        let mut rows = conn.query(\"SELECT * FROM test\", ()).await.unwrap();\n        while let Some(row) = rows.next().await.unwrap() {\n            assert_eq!(row.get::<i64>(0).unwrap(), 1);\n            assert_eq!(row.get::<String>(1).unwrap(), \"secret_data\");\n            row_count += 1;\n        }\n        assert_eq!(row_count, 1);\n    }\n\n    // 4. Verify opening with wrong key fails\n    {\n        let wrong_opts = EncryptionOpts {\n            hexkey: wrong_key.to_string(),\n            cipher: \"aegis256\".to_string(),\n        };\n        let builder = Builder::new_local(db_file)\n            .experimental_encryption(true)\n            .with_encryption(wrong_opts);\n        let result = builder.build().await;\n        assert!(result.is_err(), \"Opening with wrong key should fail\");\n    }\n\n    // 5. Verify opening without encryption fails\n    {\n        let builder = Builder::new_local(db_file).experimental_encryption(true);\n        let result = builder.build().await;\n        assert!(\n            result.is_err(),\n            \"Opening encrypted database without key should fail\"\n        );\n    }\n}\n\n#[tokio::test]\n/// This results in a panic if the query isn't correctly reset\nasync fn test_query_without_reset_does_not_panic() {\n    let tempfile = tempfile::NamedTempFile::new().unwrap();\n    let db = Builder::new_local(tempfile.path().to_str().unwrap())\n        .build()\n        .await\n        .unwrap();\n    let conn = db.connect().unwrap();\n\n    conn.execute(\"CREATE TABLE t (id INTEGER PRIMARY KEY, value TEXT)\", ())\n        .await\n        .unwrap();\n\n    for i in 0..10 {\n        conn.execute(&format!(\"INSERT INTO t VALUES ({i}, 'val')\"), ())\n            .await\n            .unwrap();\n    }\n\n    let mut stmts: Vec<Option<turso::Statement>> = Vec::new();\n\n    for round in 0..4 {\n        for i in 0..30 {\n            let id = 100 + round * 100 + i;\n            let sql = match i % 4 {\n                0 => format!(\"INSERT INTO t VALUES ({id}, 'new')\"),\n                1 => \"SELECT * FROM t\".to_string(),\n                2 => format!(\"UPDATE t SET value = 'upd' WHERE id = {}\", i % 10),\n                _ => format!(\"DELETE FROM t WHERE id = {}\", 1000 + i),\n            };\n            if let Ok(s) = conn.prepare(&sql).await {\n                stmts.push(Some(s));\n            }\n        }\n\n        for i in (0..stmts.len()).step_by(7) {\n            if let Some(Some(stmt)) = stmts.get_mut(i) {\n                if let Ok(mut rows) = stmt.query(()).await {\n                    let _ = rows.next().await;\n                }\n            }\n        }\n\n        for i in 0..3 {\n            let _ = conn\n                .execute(\n                    &format!(\"INSERT INTO t VALUES ({}, 'x')\", 2000 + round * 10 + i),\n                    (),\n                )\n                .await;\n        }\n\n        for i in (0..stmts.len()).step_by(13) {\n            stmts[i] = None;\n        }\n\n        for i in (0..stmts.len()).step_by(5) {\n            if let Some(Some(stmt)) = stmts.get_mut(i) {\n                if let Ok(mut rows) = stmt.query(()).await {\n                    let _ = rows.next().await;\n                }\n            }\n        }\n    }\n}\n\n// Test Transaction.prepare\n#[tokio::test]\nasync fn test_transaction_prepared_statement() {\n    let db = Builder::new_local(\":memory:\").build().await.unwrap();\n    let mut conn = db.connect().unwrap();\n\n    conn.execute(\"CREATE TABLE users (id INTEGER, name TEXT)\", ())\n        .await\n        .unwrap();\n\n    let tx = conn.transaction().await.unwrap();\n    let mut stmt = tx\n        .prepare(\"INSERT INTO users VALUES (?1, ?2)\")\n        .await\n        .unwrap();\n    stmt.execute([\"1\", \"Frodo\"]).await.unwrap();\n    tx.commit().await.unwrap();\n\n    let row = conn\n        .prepare(\"SELECT id FROM users WHERE name = ?\")\n        .await\n        .unwrap()\n        .query_row(&[\"Frodo\"])\n        .await\n        .unwrap();\n\n    let id: i64 = row.get(0).unwrap();\n    assert_eq!(id, 1);\n}\n\n#[tokio::test]\nasync fn test_row_get_value_out_of_bounds() {\n    let db = Builder::new_local(\":memory:\").build().await.unwrap();\n    let conn = db.connect().unwrap();\n\n    conn.execute(\"CREATE TABLE t (x INTEGER)\", ())\n        .await\n        .unwrap();\n    conn.execute(\"INSERT INTO t VALUES (1)\", ()).await.unwrap();\n\n    let mut rows = conn.query(\"SELECT x FROM t\", ()).await.unwrap();\n    let row = rows.next().await.unwrap().unwrap();\n\n    // Valid index works\n    assert!(row.get_value(0).is_ok());\n\n    // Out of bounds returns error instead of panicking\n    let result = row.get_value(999);\n    assert!(matches!(result, Err(Error::Misuse(_))));\n\n    // Also test get<T>() for OOB\n    let result: Result<i64, _> = row.get(999);\n    assert!(matches!(result, Err(Error::Misuse(_))));\n}\n\n// Test Connection clone\n#[tokio::test]\nasync fn test_connection_clone() {\n    let db = Builder::new_local(\":memory:\").build().await.unwrap();\n    let mut conn = db.connect().unwrap();\n\n    conn.execute(\"CREATE TABLE users (id INTEGER, name TEXT)\", ())\n        .await\n        .unwrap();\n\n    let tx = conn.transaction().await.unwrap();\n    let mut stmt = tx\n        .prepare(\"INSERT INTO users VALUES (?1, ?2)\")\n        .await\n        .unwrap();\n    stmt.execute([\"1\", \"Frodo\"]).await.unwrap();\n    tx.commit().await.unwrap();\n\n    let conn2 = conn.clone();\n    let row = conn2\n        .prepare(\"SELECT id FROM users WHERE name = ?\")\n        .await\n        .unwrap()\n        .query_row(&[\"Frodo\"])\n        .await\n        .unwrap();\n\n    let id: i64 = row.get(0).unwrap();\n    assert_eq!(id, 1);\n}\n\n#[tokio::test]\nasync fn test_insert_returning_partial_consume() {\n    // Regression test for: INSERT...RETURNING should insert all rows even if\n    // only some RETURNING values are consumed before the statement is dropped/reset.\n    // This matches the sqlite3 bindings fix in commit e39e60ef1.\n    let db = Builder::new_local(\":memory:\").build().await.unwrap();\n    let conn = db.connect().unwrap();\n\n    conn.execute(\"CREATE TABLE t (x INTEGER)\", ())\n        .await\n        .unwrap();\n\n    // Use query() to get RETURNING values, but only consume first row\n    let mut stmt = conn\n        .prepare(\"INSERT INTO t (x) VALUES (1), (2), (3) RETURNING x\")\n        .await\n        .unwrap();\n    let mut rows = stmt.query(()).await.unwrap();\n\n    // Only consume first row\n    let first_row = rows.next().await.unwrap().unwrap();\n    assert_eq!(first_row.get::<i64>(0).unwrap(), 1);\n\n    // Drop the rows iterator without consuming remaining rows\n    drop(rows);\n    drop(stmt);\n\n    // All 3 rows should have been inserted despite only consuming 1 RETURNING value\n    let mut count_rows = conn.query(\"SELECT COUNT(*) FROM t\", ()).await.unwrap();\n    let count: i64 = count_rows.next().await.unwrap().unwrap().get(0).unwrap();\n    assert_eq!(\n        count, 3,\n        \"All 3 rows should be inserted even if RETURNING was partially consumed\"\n    );\n}\n\n#[tokio::test]\nasync fn test_transaction_commit_without_mvcc() {\n    // Regression test: COMMIT should work for non-MVCC transactions.\n    // The op_auto_commit function must check TransactionState, not just MVCC tx.\n    let db = Builder::new_local(\":memory:\").build().await.unwrap();\n    let conn = db.connect().unwrap();\n\n    conn.execute(\"CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)\", ())\n        .await\n        .unwrap();\n\n    // Begin explicit transaction\n    conn.execute(\"BEGIN IMMEDIATE TRANSACTION\", ())\n        .await\n        .unwrap();\n\n    // Insert data within transaction\n    conn.execute(\"INSERT INTO test (id, value) VALUES (1, 'hello')\", ())\n        .await\n        .unwrap();\n\n    // Commit should succeed\n    conn.execute(\"COMMIT\", ())\n        .await\n        .expect(\"COMMIT should succeed for non-MVCC transactions\");\n\n    // Verify data was committed\n    let mut rows = conn\n        .query(\"SELECT value FROM test WHERE id = 1\", ())\n        .await\n        .unwrap();\n    let row = rows.next().await.unwrap().unwrap();\n    let value: String = row.get(0).unwrap();\n    assert_eq!(value, \"hello\", \"Data should be committed\");\n}\n\n#[tokio::test]\nasync fn test_transaction_with_insert_returning_then_commit() {\n    // Regression test: Combining INSERT...RETURNING (partial consume) with explicit transaction.\n    // This tests the interaction between the reset-to-completion fix and transaction commit.\n    let db = Builder::new_local(\":memory:\").build().await.unwrap();\n    let conn = db.connect().unwrap();\n\n    conn.execute(\"CREATE TABLE t (x INTEGER)\", ())\n        .await\n        .unwrap();\n\n    // Begin transaction\n    conn.execute(\"BEGIN IMMEDIATE TRANSACTION\", ())\n        .await\n        .unwrap();\n\n    // INSERT...RETURNING, only consume first row\n    let mut stmt = conn\n        .prepare(\"INSERT INTO t (x) VALUES (1), (2), (3) RETURNING x\")\n        .await\n        .unwrap();\n    let mut rows = stmt.query(()).await.unwrap();\n    let first = rows.next().await.unwrap().unwrap();\n    assert_eq!(first.get::<i64>(0).unwrap(), 1);\n    drop(rows);\n    drop(stmt);\n\n    // Commit should succeed even after partial RETURNING consumption\n    conn.execute(\"COMMIT\", ())\n        .await\n        .expect(\"COMMIT should succeed after INSERT...RETURNING\");\n\n    // Verify all 3 rows were inserted\n    let mut count_rows = conn.query(\"SELECT COUNT(*) FROM t\", ()).await.unwrap();\n    let count: i64 = count_rows.next().await.unwrap().unwrap().get(0).unwrap();\n    assert_eq!(count, 3, \"All rows should be committed\");\n}\n\n#[tokio::test]\nasync fn test_prepare_cached_basic() {\n    let db = Builder::new_local(\":memory:\").build().await.unwrap();\n    let conn = db.connect().unwrap();\n\n    conn.execute(\"CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)\", ())\n        .await\n        .unwrap();\n\n    // First call should cache the statement\n    let mut stmt1 = conn\n        .prepare_cached(\"SELECT * FROM users WHERE id = ?\")\n        .await\n        .unwrap();\n\n    // Insert some data and query\n    conn.execute(\"INSERT INTO users VALUES (1, 'Alice')\", ())\n        .await\n        .unwrap();\n\n    let mut rows = stmt1.query(vec![Value::Integer(1)]).await.unwrap();\n    let row = rows.next().await.unwrap().unwrap();\n    assert_eq!(row.get::<i64>(0).unwrap(), 1);\n    assert_eq!(row.get::<String>(1).unwrap(), \"Alice\");\n    drop(rows);\n    drop(stmt1);\n\n    // Second call should use cached statement\n    let mut stmt2 = conn\n        .prepare_cached(\"SELECT * FROM users WHERE id = ?\")\n        .await\n        .unwrap();\n\n    let mut rows = stmt2.query(vec![Value::Integer(1)]).await.unwrap();\n    let row = rows.next().await.unwrap().unwrap();\n    assert_eq!(row.get::<i64>(0).unwrap(), 1);\n    assert_eq!(row.get::<String>(1).unwrap(), \"Alice\");\n}\n\n#[tokio::test]\nasync fn test_prepare_cached_reprepare_on_query_only_change() {\n    let db = Builder::new_local(\":memory:\").build().await.unwrap();\n    let conn = db.connect().unwrap();\n\n    conn.execute(\"CREATE TABLE t (id INTEGER)\", ())\n        .await\n        .unwrap();\n\n    let mut stmt = conn\n        .prepare_cached(\"INSERT INTO t VALUES (?)\")\n        .await\n        .unwrap();\n\n    conn.execute(\"PRAGMA query_only=1\", ()).await.unwrap();\n\n    let err = stmt.execute(vec![Value::Integer(1)]).await.unwrap_err();\n    assert!(err.to_string().to_ascii_lowercase().contains(\"query_only\"));\n\n    let mut rows = conn.query(\"SELECT COUNT(*) FROM t\", ()).await.unwrap();\n    let count: i64 = rows.next().await.unwrap().unwrap().get(0).unwrap();\n    assert_eq!(count, 0);\n}\n\n#[tokio::test]\nasync fn test_prepare_cached_batch_insert_delete_pattern() {\n    #[derive(Clone)]\n    struct Host {\n        name: String,\n        app: String,\n        address: String,\n        namespace: String,\n        cloud_cluster_name: String,\n        allowed_ips: Vec<String>,\n        updated_at: std::time::SystemTime,\n        deleted: bool,\n    }\n\n    fn serialize_allowed_ips(allowed_ips: &[String]) -> String {\n        allowed_ips.join(\",\")\n    }\n\n    fn system_time_to_unix_seconds(ts: std::time::SystemTime) -> i64 {\n        let duration = ts\n            .duration_since(std::time::UNIX_EPOCH)\n            .expect(\"system time should be after unix epoch\");\n        duration.as_secs() as i64\n    }\n\n    async fn insert_hosts(conn: &turso::Connection, hosts: &[Host]) -> Result<(), Error> {\n        if hosts.is_empty() {\n            return Ok(());\n        }\n\n        conn.execute(\"BEGIN\", ()).await?;\n\n        let mut insert_stmt = conn\n            .prepare_cached(\n                \"INSERT INTO hosts (name, app, address, namespace, cloud_cluster_name, allowed_ips, updated_at)\n                 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)\n                 ON CONFLICT(name) DO UPDATE SET\n                 app = excluded.app,\n                 address = excluded.address,\n                 namespace = excluded.namespace,\n                 cloud_cluster_name = excluded.cloud_cluster_name,\n                 allowed_ips = excluded.allowed_ips,\n                 updated_at = excluded.updated_at\",\n            )\n            .await?;\n        let mut delete_stmt = conn\n            .prepare_cached(\"DELETE FROM hosts WHERE name = ?1\")\n            .await?;\n\n        let result = async {\n            for host in hosts {\n                if host.deleted {\n                    delete_stmt.execute([host.name.as_str()]).await?;\n                    continue;\n                }\n\n                let allowed_ips = serialize_allowed_ips(&host.allowed_ips);\n                let updated_at = system_time_to_unix_seconds(host.updated_at);\n                insert_stmt\n                    .execute((\n                        host.name.as_str(),\n                        host.app.as_str(),\n                        host.address.as_str(),\n                        host.namespace.as_str(),\n                        host.cloud_cluster_name.as_str(),\n                        allowed_ips,\n                        updated_at,\n                    ))\n                    .await?;\n            }\n            Ok(())\n        }\n        .await;\n\n        match result {\n            Ok(()) => {\n                conn.execute(\"COMMIT\", ()).await?;\n                Ok(())\n            }\n            Err(err) => {\n                let _ = conn.execute(\"ROLLBACK\", ()).await;\n                Err(err)\n            }\n        }\n    }\n\n    let db = Builder::new_local(\":memory:\").build().await.unwrap();\n    let conn = db.connect().unwrap();\n\n    conn.execute(\n        \"CREATE TABLE hosts (\n            name TEXT PRIMARY KEY,\n            app TEXT,\n            address TEXT,\n            namespace TEXT,\n            cloud_cluster_name TEXT,\n            allowed_ips TEXT,\n            updated_at INTEGER\n        )\",\n        (),\n    )\n    .await\n    .unwrap();\n\n    let base_time = std::time::UNIX_EPOCH + std::time::Duration::from_secs(1_700_000_000);\n    let hosts = vec![\n        Host {\n            name: \"a\".to_string(),\n            app: \"app_a\".to_string(),\n            address: \"10.0.0.1\".to_string(),\n            namespace: \"ns\".to_string(),\n            cloud_cluster_name: \"cluster\".to_string(),\n            allowed_ips: vec![\"10.0.0.0/24\".to_string()],\n            updated_at: base_time,\n            deleted: false,\n        },\n        Host {\n            name: \"b\".to_string(),\n            app: \"app_b\".to_string(),\n            address: \"10.0.0.2\".to_string(),\n            namespace: \"ns\".to_string(),\n            cloud_cluster_name: \"cluster\".to_string(),\n            allowed_ips: vec![\"10.0.1.0/24\".to_string()],\n            updated_at: base_time,\n            deleted: false,\n        },\n        Host {\n            name: \"a\".to_string(),\n            app: \"app_a\".to_string(),\n            address: \"10.0.0.1\".to_string(),\n            namespace: \"ns\".to_string(),\n            cloud_cluster_name: \"cluster\".to_string(),\n            allowed_ips: vec![\"10.0.0.0/24\".to_string()],\n            updated_at: base_time,\n            deleted: true,\n        },\n    ];\n\n    insert_hosts(&conn, &hosts).await.unwrap();\n\n    let mut rows = conn\n        .query(\"SELECT name FROM hosts ORDER BY name\", ())\n        .await\n        .unwrap();\n    let first = rows\n        .next()\n        .await\n        .unwrap()\n        .unwrap()\n        .get::<String>(0)\n        .unwrap();\n    assert_eq!(first, \"b\");\n    assert!(rows.next().await.unwrap().is_none());\n}\n\n#[tokio::test]\nasync fn test_prepare_cached_multiple_statements() {\n    let db = Builder::new_local(\":memory:\").build().await.unwrap();\n    let conn = db.connect().unwrap();\n\n    conn.execute(\"CREATE TABLE t (id INTEGER, value TEXT)\", ())\n        .await\n        .unwrap();\n\n    // Cache multiple different statements\n    let queries = vec![\n        \"SELECT * FROM t WHERE id = ?\",\n        \"SELECT * FROM t WHERE value = ?\",\n        \"INSERT INTO t VALUES (?, ?)\",\n    ];\n\n    for query in &queries {\n        let _ = conn.prepare_cached(*query).await.unwrap();\n    }\n\n    // All should be cached and work correctly\n    let mut stmt1 = conn.prepare_cached(queries[0]).await.unwrap();\n    let mut stmt2 = conn.prepare_cached(queries[1]).await.unwrap();\n    let mut stmt3 = conn.prepare_cached(queries[2]).await.unwrap();\n\n    // Insert data\n    stmt3\n        .execute(vec![Value::Integer(1), Value::Text(\"test\".into())])\n        .await\n        .unwrap();\n\n    // Query using both cached SELECT statements\n    let mut rows = stmt1.query(vec![Value::Integer(1)]).await.unwrap();\n    let row = rows.next().await.unwrap().unwrap();\n    assert_eq!(row.get::<i64>(0).unwrap(), 1);\n    drop(rows);\n\n    let mut rows = stmt2.query(vec![Value::Text(\"test\".into())]).await.unwrap();\n    let row = rows.next().await.unwrap().unwrap();\n    assert_eq!(row.get::<String>(1).unwrap(), \"test\");\n}\n\n#[tokio::test]\nasync fn test_prepare_cached_independent_state() {\n    // Verify that each cached statement has independent execution state\n    let db = Builder::new_local(\":memory:\").build().await.unwrap();\n    let conn = db.connect().unwrap();\n\n    conn.execute(\"CREATE TABLE t (id INTEGER)\", ())\n        .await\n        .unwrap();\n\n    for i in 1..=5 {\n        conn.execute(&format!(\"INSERT INTO t VALUES ({i})\"), ())\n            .await\n            .unwrap();\n    }\n\n    let query = \"SELECT * FROM t ORDER BY id\";\n\n    // Get two statements from cache\n    let mut stmt1 = conn.prepare_cached(query).await.unwrap();\n    let mut stmt2 = conn.prepare_cached(query).await.unwrap();\n\n    // Start iterating with stmt1\n    let mut rows1 = stmt1.query(()).await.unwrap();\n    let row1 = rows1.next().await.unwrap().unwrap();\n    assert_eq!(row1.get::<i64>(0).unwrap(), 1);\n\n    // Start iterating with stmt2 - should have its own state\n    let mut rows2 = stmt2.query(()).await.unwrap();\n    let row2 = rows2.next().await.unwrap().unwrap();\n    assert_eq!(row2.get::<i64>(0).unwrap(), 1);\n\n    // Continue with stmt1 - should be at next row\n    let row1 = rows1.next().await.unwrap().unwrap();\n    assert_eq!(row1.get::<i64>(0).unwrap(), 2);\n\n    // Continue with stmt2 - should also be at next row (independent state)\n    let row2 = rows2.next().await.unwrap().unwrap();\n    assert_eq!(row2.get::<i64>(0).unwrap(), 2);\n}\n\n#[tokio::test]\nasync fn test_prepare_cached_with_parameters() {\n    let db = Builder::new_local(\":memory:\").build().await.unwrap();\n    let conn = db.connect().unwrap();\n\n    conn.execute(\n        \"CREATE TABLE users (id INTEGER, name TEXT, age INTEGER)\",\n        (),\n    )\n    .await\n    .unwrap();\n\n    conn.execute(\"INSERT INTO users VALUES (1, 'Alice', 30)\", ())\n        .await\n        .unwrap();\n    conn.execute(\"INSERT INTO users VALUES (2, 'Bob', 25)\", ())\n        .await\n        .unwrap();\n    conn.execute(\"INSERT INTO users VALUES (3, 'Charlie', 35)\", ())\n        .await\n        .unwrap();\n\n    let query = \"SELECT name FROM users WHERE age > ?\";\n\n    // Use cached statement with different parameters\n    let mut stmt = conn.prepare_cached(query).await.unwrap();\n\n    let mut rows = stmt.query(vec![Value::Integer(25)]).await.unwrap();\n    let mut names = Vec::new();\n    while let Some(row) = rows.next().await.unwrap() {\n        names.push(row.get::<String>(0).unwrap());\n    }\n    assert_eq!(names.len(), 2);\n    assert!(names.contains(&\"Alice\".to_string()));\n    assert!(names.contains(&\"Charlie\".to_string()));\n    drop(rows);\n\n    // Reuse cached statement with different parameter\n    let mut rows = stmt.query(vec![Value::Integer(30)]).await.unwrap();\n    let mut names = Vec::new();\n    while let Some(row) = rows.next().await.unwrap() {\n        names.push(row.get::<String>(0).unwrap());\n    }\n    assert_eq!(names.len(), 1);\n    assert_eq!(names[0], \"Charlie\");\n}\n\n#[tokio::test]\nasync fn test_prepare_cached_stress() {\n    // Stress test to ensure cache works correctly under repeated use\n    let db = Builder::new_local(\":memory:\").build().await.unwrap();\n    let conn = db.connect().unwrap();\n\n    conn.execute(\"CREATE TABLE t (id INTEGER PRIMARY KEY, value TEXT)\", ())\n        .await\n        .unwrap();\n\n    let insert_query = \"INSERT INTO t (id, value) VALUES (?, ?)\";\n    let select_query = \"SELECT value FROM t WHERE id = ?\";\n\n    // Insert many rows using cached statement\n    for i in 0..100 {\n        let mut stmt = conn.prepare_cached(insert_query).await.unwrap();\n        stmt.execute(vec![Value::Integer(i), Value::Text(format!(\"value_{i}\"))])\n            .await\n            .unwrap();\n    }\n\n    // Query many times using cached statement\n    for i in 0..100 {\n        let mut stmt = conn.prepare_cached(select_query).await.unwrap();\n        let mut rows = stmt.query(vec![Value::Integer(i)]).await.unwrap();\n        let row = rows.next().await.unwrap().unwrap();\n        assert_eq!(row.get::<String>(0).unwrap(), format!(\"value_{i}\"));\n    }\n}\n\n#[tokio::test]\nasync fn test_prepare_vs_prepare_cached_equivalence() {\n    // Verify that prepare_cached produces same results as prepare\n    let db = Builder::new_local(\":memory:\").build().await.unwrap();\n    let conn = db.connect().unwrap();\n\n    conn.execute(\"CREATE TABLE t (x INTEGER, y TEXT)\", ())\n        .await\n        .unwrap();\n\n    conn.execute(\"INSERT INTO t VALUES (1, 'a'), (2, 'b'), (3, 'c')\", ())\n        .await\n        .unwrap();\n\n    let query = \"SELECT * FROM t ORDER BY x\";\n\n    // Results from prepare\n    let mut stmt1 = conn.prepare(query).await.unwrap();\n    let mut rows1 = stmt1.query(()).await.unwrap();\n    let mut results1 = Vec::new();\n    while let Some(row) = rows1.next().await.unwrap() {\n        results1.push((row.get::<i64>(0).unwrap(), row.get::<String>(1).unwrap()));\n    }\n\n    // Results from prepare_cached\n    let mut stmt2 = conn.prepare_cached(query).await.unwrap();\n    let mut rows2 = stmt2.query(()).await.unwrap();\n    let mut results2 = Vec::new();\n    while let Some(row) = rows2.next().await.unwrap() {\n        results2.push((row.get::<i64>(0).unwrap(), row.get::<String>(1).unwrap()));\n    }\n\n    // Should produce identical results\n    assert_eq!(results1, results2);\n    assert_eq!(\n        results1,\n        vec![\n            (1, \"a\".to_string()),\n            (2, \"b\".to_string()),\n            (3, \"c\".to_string()),\n        ]\n    );\n}\n\n/// This will fail if self.once is not reset in ProgramState::reset.\n#[tokio::test]\nasync fn test_once_not_cleared_on_reset_with_coroutine() {\n    let db = Builder::new_local(\":memory:\").build().await.unwrap();\n    let conn = db.connect().unwrap();\n\n    // This query generates bytecode with Once inside a coroutine:\n    // The outer FROM-clause subquery creates a coroutine, and the inner\n    // scalar subquery (SELECT 1) uses Once to evaluate only once per execution.\n    let mut stmt = conn\n        .prepare(\"SELECT * FROM (SELECT (SELECT 1))\")\n        .await\n        .unwrap();\n\n    let mut rows = stmt.query(()).await.unwrap();\n    let row = rows.next().await.unwrap().unwrap();\n    let value: i64 = row.get(0).unwrap();\n    assert_eq!(value, 1);\n    assert!(rows.next().await.unwrap().is_none());\n    drop(rows);\n\n    stmt.reset().unwrap();\n\n    let mut rows = stmt.query(()).await.unwrap();\n    let row = rows.next().await.unwrap().unwrap();\n\n    assert_eq!(\n        row.get_value(0).unwrap(),\n        Value::Integer(1),\n        \"Second execution should return 1, not Null. Bug: state.once not cleared in reset()\"\n    );\n}\n\n#[tokio::test]\nasync fn test_strict_tables() {\n    let db = Builder::new_local(\":memory:\").build().await.unwrap();\n    let conn = db.connect().unwrap();\n\n    // Create a STRICT table\n    conn.execute(\n        \"CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT) STRICT\",\n        (),\n    )\n    .await\n    .unwrap();\n\n    // Insert valid data\n    conn.execute(\"INSERT INTO users VALUES (1, 'Alice')\", ())\n        .await\n        .unwrap();\n\n    // Query the data\n    let mut rows = conn.query(\"SELECT id, name FROM users\", ()).await.unwrap();\n    let row = rows.next().await.unwrap().unwrap();\n    assert_eq!(row.get::<i64>(0).unwrap(), 1);\n    assert_eq!(row.get::<String>(1).unwrap(), \"Alice\");\n}\n\n// Helper to collect all integer values from a single-column query.\nasync fn collect_ids(conn: &turso::Connection, sql: &str) -> Vec<i64> {\n    let mut rows = conn.query(sql, ()).await.unwrap();\n    let mut ids = Vec::new();\n    while let Some(row) = rows.next().await.unwrap() {\n        let id: i64 = row.get(0).unwrap();\n        ids.push(id);\n    }\n    ids\n}\n\n#[tokio::test]\nasync fn test_check_on_conflict_fail() {\n    // FAIL: error on the violating statement, transaction stays active.\n    // Prior inserts within the transaction are preserved and can be committed.\n    let db = Builder::new_local(\":memory:\").build().await.unwrap();\n    let conn = db.connect().unwrap();\n\n    conn.execute(\n        \"CREATE TABLE t(id INTEGER PRIMARY KEY, value INTEGER CHECK(value > 0))\",\n        (),\n    )\n    .await\n    .unwrap();\n    conn.execute(\"BEGIN\", ()).await.unwrap();\n    conn.execute(\"INSERT INTO t VALUES(1, 10)\", ())\n        .await\n        .unwrap();\n\n    // This should fail but keep the transaction active\n    let err = conn\n        .execute(\"INSERT OR FAIL INTO t VALUES(2, -5)\", ())\n        .await;\n    assert!(\n        err.is_err(),\n        \"INSERT OR FAIL should error on CHECK violation\"\n    );\n\n    // Transaction is still active — commit it\n    conn.execute(\"COMMIT\", ()).await.unwrap();\n\n    // Row 1 should have survived\n    let ids = collect_ids(&conn, \"SELECT id FROM t ORDER BY id\").await;\n    assert_eq!(ids, vec![1]);\n}\n\n#[tokio::test]\nasync fn test_check_on_conflict_abort() {\n    // ABORT (default): error on the violating statement, transaction stays active.\n    let db = Builder::new_local(\":memory:\").build().await.unwrap();\n    let conn = db.connect().unwrap();\n\n    conn.execute(\n        \"CREATE TABLE t(id INTEGER PRIMARY KEY, value INTEGER CHECK(value > 0))\",\n        (),\n    )\n    .await\n    .unwrap();\n    conn.execute(\"BEGIN\", ()).await.unwrap();\n    conn.execute(\"INSERT INTO t VALUES(1, 10)\", ())\n        .await\n        .unwrap();\n\n    let err = conn\n        .execute(\"INSERT OR ABORT INTO t VALUES(2, -5)\", ())\n        .await;\n    assert!(\n        err.is_err(),\n        \"INSERT OR ABORT should error on CHECK violation\"\n    );\n\n    conn.execute(\"COMMIT\", ()).await.unwrap();\n\n    let ids = collect_ids(&conn, \"SELECT id FROM t ORDER BY id\").await;\n    assert_eq!(ids, vec![1]);\n}\n\n#[tokio::test]\nasync fn test_check_on_conflict_rollback() {\n    // ROLLBACK: rolls back the entire transaction.\n    // Prior inserts within the transaction are lost, but committed rows survive.\n    let db = Builder::new_local(\":memory:\").build().await.unwrap();\n    let conn = db.connect().unwrap();\n\n    conn.execute(\n        \"CREATE TABLE t(id INTEGER PRIMARY KEY, value INTEGER CHECK(value > 0))\",\n        (),\n    )\n    .await\n    .unwrap();\n    // Commit row 1 outside the transaction\n    conn.execute(\"INSERT INTO t VALUES(1, 10)\", ())\n        .await\n        .unwrap();\n\n    conn.execute(\"BEGIN\", ()).await.unwrap();\n    conn.execute(\"INSERT INTO t VALUES(2, 20)\", ())\n        .await\n        .unwrap();\n\n    // This should fail AND roll back the transaction\n    let err = conn\n        .execute(\"INSERT OR ROLLBACK INTO t VALUES(3, -5)\", ())\n        .await;\n    assert!(\n        err.is_err(),\n        \"INSERT OR ROLLBACK should error on CHECK violation\"\n    );\n\n    // Transaction was rolled back — row 2 is lost, row 1 survives\n    let ids = collect_ids(&conn, \"SELECT id FROM t ORDER BY id\").await;\n    assert_eq!(ids, vec![1]);\n}\n\n#[tokio::test]\nasync fn test_check_on_conflict_replace() {\n    // REPLACE: for CHECK constraints, behaves like ABORT.\n    // Error, transaction stays active.\n    let db = Builder::new_local(\":memory:\").build().await.unwrap();\n    let conn = db.connect().unwrap();\n\n    conn.execute(\n        \"CREATE TABLE t(id INTEGER PRIMARY KEY, value INTEGER CHECK(value > 0))\",\n        (),\n    )\n    .await\n    .unwrap();\n    conn.execute(\"BEGIN\", ()).await.unwrap();\n    conn.execute(\"INSERT INTO t VALUES(1, 10)\", ())\n        .await\n        .unwrap();\n\n    let err = conn\n        .execute(\"INSERT OR REPLACE INTO t VALUES(1, -5)\", ())\n        .await;\n    assert!(\n        err.is_err(),\n        \"INSERT OR REPLACE should error on CHECK violation\"\n    );\n\n    conn.execute(\"COMMIT\", ()).await.unwrap();\n\n    let ids = collect_ids(&conn, \"SELECT id FROM t ORDER BY id\").await;\n    assert_eq!(ids, vec![1]);\n}\n\nuse std::sync::atomic::{AtomicBool, AtomicI64, Ordering};\nuse std::sync::Arc;\nuse std::time::Duration;\nuse tempfile::tempdir;\nuse tokio::sync::Barrier;\n\n#[tokio::test(flavor = \"multi_thread\", worker_threads = 8)]\nasync fn test_lost_updates() {\n    let (db, _dir) = setup_mvcc_db(\n        \"CREATE TABLE counter(id INTEGER PRIMARY KEY, val INTEGER);\n         INSERT INTO counter VALUES(1, 0);\",\n    )\n    .await;\n\n    let num_workers: usize = 16;\n    let rounds: i64 = 100;\n    let total_committed = Arc::new(AtomicI64::new(0));\n\n    for _round in 0..rounds {\n        let barrier = Arc::new(Barrier::new(num_workers));\n        let mut handles = Vec::new();\n\n        for _ in 0..num_workers {\n            let conn = db.connect().unwrap();\n            let barrier = barrier.clone();\n            let total_committed = total_committed.clone();\n            handles.push(tokio::spawn(async move {\n                barrier.wait().await;\n                if conn.execute(\"BEGIN CONCURRENT\", ()).await.is_err() {\n                    return;\n                }\n                if conn\n                    .execute(\"UPDATE counter SET val = val + 1 WHERE id = 1\", ())\n                    .await\n                    .is_err()\n                {\n                    let _ = conn.execute(\"ROLLBACK\", ()).await;\n                    return;\n                }\n                match conn.execute(\"COMMIT\", ()).await {\n                    Ok(_) => {\n                        total_committed.fetch_add(1, Ordering::Relaxed);\n                    }\n                    Err(_) => {\n                        let _ = conn.execute(\"ROLLBACK\", ()).await;\n                    }\n                }\n            }));\n        }\n        for handle in handles {\n            handle.await.unwrap();\n        }\n    }\n\n    let conn = db.connect().unwrap();\n    let val = query_i64(&conn, \"SELECT val FROM counter WHERE id = 1\").await;\n    let committed = total_committed.load(Ordering::Relaxed);\n    assert_eq!(\n        val, committed,\n        \"Lost updates! counter={val} but {committed} transactions committed successfully\"\n    );\n}\n\n/// Helper: create MVCC-enabled file-backed database with given schema\nasync fn setup_mvcc_db(schema: &str) -> (turso::Database, tempfile::TempDir) {\n    setup_mvcc_db_with_options(schema).await\n}\n\n/// Helper: create MVCC-enabled file-backed database with options\nasync fn setup_mvcc_db_with_options(schema: &str) -> (turso::Database, tempfile::TempDir) {\n    let dir = tempdir().unwrap();\n    let db_path = dir.path().join(\"test.db\");\n    let builder = Builder::new_local(db_path.to_str().unwrap());\n    let db = builder.build().await.unwrap();\n    let conn = db.connect().unwrap();\n    // PRAGMA journal_mode returns a row, so use query() to consume it\n    let mut rows = conn\n        .query(\"PRAGMA journal_mode = 'mvcc'\", ())\n        .await\n        .unwrap();\n    while let Ok(Some(_)) = rows.next().await {}\n    drop(rows);\n    if !schema.is_empty() {\n        conn.execute_batch(schema).await.unwrap();\n    }\n    (db, dir)\n}\n\n/// Helper: query a single i64 value\nasync fn query_i64(conn: &turso::Connection, sql: &str) -> i64 {\n    let mut rows = conn.query(sql, ()).await.unwrap();\n    let row = rows.next().await.unwrap().unwrap();\n    row.get::<i64>(0).unwrap()\n}\n\n#[tokio::test(flavor = \"multi_thread\", worker_threads = 8)]\n#[ignore = \"FIXME: This test hangs on main\"]\nasync fn test_deadlock_join_during_writes() {\n    let (db, _dir) = setup_mvcc_db(\n        \"CREATE TABLE orders(id INTEGER PRIMARY KEY, customer_id INTEGER, amount INTEGER);\n         CREATE TABLE customers(id INTEGER PRIMARY KEY, name TEXT);\n         INSERT INTO customers VALUES(1, 'alice');\n         INSERT INTO customers VALUES(2, 'bob');\n         INSERT INTO customers VALUES(3, 'charlie');\",\n    )\n    .await;\n\n    let done = Arc::new(AtomicBool::new(false));\n    let mut handles = vec![];\n\n    // Writers: insert orders for various customers\n    for w in 0..4 {\n        let db = db.clone();\n        let done = done.clone();\n        handles.push(tokio::spawn(async move {\n            let conn = db.connect().unwrap();\n            let mut i = 0u64;\n            while !done.load(Ordering::Relaxed) {\n                let id = (w as u64) * 100000 + i;\n                let cust = (i % 3) + 1;\n                let _ = conn.execute(\"BEGIN CONCURRENT\", ()).await;\n                let _ = conn\n                    .execute(\n                        &format!(\"INSERT INTO orders VALUES({}, {}, {})\", id, cust, 10),\n                        (),\n                    )\n                    .await;\n                let _ = conn.execute(\"COMMIT\", ()).await;\n                i += 1;\n            }\n        }));\n    }\n\n    // Readers: do JOINs (THIS IS WHAT TRIGGERS THE HANG)\n    for _ in 0..4 {\n        let db = db.clone();\n        let done = done.clone();\n        handles.push(tokio::spawn(async move {\n            let conn = db.connect().unwrap();\n            while !done.load(Ordering::Relaxed) {\n                let _ = conn.execute(\"BEGIN CONCURRENT\", ()).await;\n                let _orphans = match conn\n                    .query(\n                        \"SELECT COUNT(*) FROM orders o LEFT JOIN customers c ON o.customer_id = c.id WHERE c.id IS NULL\",\n                        (),\n                    )\n                    .await\n                {\n                    Ok(mut rows) => match rows.next().await {\n                        Ok(Some(row)) => row.get::<i64>(0).unwrap_or(0),\n                        _ => 0,\n                    },\n                    Err(_) => 0,\n                };\n                let _ = conn.execute(\"COMMIT\", ()).await;\n            }\n        }));\n    }\n\n    // If this test hangs here, the bug is confirmed.\n    tokio::time::sleep(Duration::from_secs(3)).await;\n    done.store(true, Ordering::Relaxed);\n    for handle in handles {\n        // This await will never return if threads are deadlocked\n        handle.await.unwrap();\n    }\n}\n\n#[tokio::test(flavor = \"multi_thread\", worker_threads = 8)]\nasync fn test_snapshot_isolation_violation() {\n    let (db, _dir) = setup_mvcc_db(\"CREATE TABLE t(id INTEGER PRIMARY KEY, val INTEGER)\").await;\n\n    let done = Arc::new(AtomicBool::new(false));\n    let violation_found = Arc::new(AtomicBool::new(false));\n    let mut handles = Vec::new();\n\n    // 4 writers: continuously insert batches of 5 rows\n    for w in 0..4i64 {\n        let conn = db.connect().unwrap();\n        let done = done.clone();\n        handles.push(tokio::spawn(async move {\n            let mut i = 0i64;\n            while !done.load(Ordering::Relaxed) {\n                if conn.execute(\"BEGIN CONCURRENT\", ()).await.is_err() {\n                    continue;\n                }\n                let mut ok = true;\n                for j in 0..5i64 {\n                    let id = w * 100_000 + i * 5 + j;\n                    if conn\n                        .execute(&format!(\"INSERT INTO t VALUES({id}, {id})\"), ())\n                        .await\n                        .is_err()\n                    {\n                        ok = false;\n                        break;\n                    }\n                }\n                if ok {\n                    if conn.execute(\"COMMIT\", ()).await.is_err() {\n                        let _ = conn.execute(\"ROLLBACK\", ()).await;\n                    }\n                } else {\n                    let _ = conn.execute(\"ROLLBACK\", ()).await;\n                }\n                i += 1;\n            }\n        }));\n    }\n\n    // 4 readers: open snapshot, read COUNT(*) twice, assert they match\n    for _ in 0..4 {\n        let conn = db.connect().unwrap();\n        let done = done.clone();\n        let violation_found = violation_found.clone();\n        handles.push(tokio::spawn(async move {\n            while !done.load(Ordering::Relaxed) {\n                if conn.execute(\"BEGIN CONCURRENT\", ()).await.is_err() {\n                    continue;\n                }\n                let count1 = query_i64(&conn, \"SELECT COUNT(*) FROM t\").await;\n                tokio::task::yield_now().await; // Let writers commit between reads\n                let count2 = query_i64(&conn, \"SELECT COUNT(*) FROM t\").await;\n                let _ = conn.execute(\"COMMIT\", ()).await;\n                if count1 != count2 {\n                    violation_found.store(true, Ordering::Relaxed);\n                    eprintln!(\n                        \"VIOLATION: COUNT changed {} -> {} within same txn (delta={})\",\n                        count1,\n                        count2,\n                        count2 - count1\n                    );\n                }\n            }\n        }));\n    }\n\n    tokio::time::sleep(Duration::from_secs(3)).await;\n    done.store(true, Ordering::Relaxed);\n    for handle in handles {\n        let _ = handle.await;\n    }\n\n    assert!(\n        !violation_found.load(Ordering::Relaxed),\n        \"Snapshot isolation violated: COUNT(*) changed within a single BEGIN CONCURRENT txn\"\n    );\n}\n\n#[tokio::test(flavor = \"multi_thread\", worker_threads = 8)]\nasync fn test_ghost_commits() {\n    for iteration in 0..500 {\n        if iteration % 100 == 0 {\n            eprintln!(\"test_ghost_commits: Iteration {iteration}\");\n        }\n        let (db, _dir) = setup_mvcc_db(\"CREATE TABLE t(id INTEGER PRIMARY KEY, val INTEGER)\").await;\n\n        let num_workers: usize = 8;\n        let ops_per_worker: i64 = 100;\n        let barrier = Arc::new(Barrier::new(num_workers));\n        let mut handles = Vec::new();\n\n        for worker_id in 0..num_workers as i64 {\n            let conn = db.connect().unwrap();\n            let barrier = barrier.clone();\n            handles.push(tokio::spawn(async move {\n                barrier.wait().await;\n                let mut successes = 0i64;\n                let mut errors = 0i64;\n                for i in 0..ops_per_worker {\n                    let id = worker_id * 10_000 + i;\n                    // Autocommit INSERT (no explicit BEGIN/COMMIT)\n                    match conn\n                        .execute(&format!(\"INSERT INTO t VALUES({id}, {i})\"), ())\n                        .await\n                    {\n                        Ok(_) => successes += 1,\n                        Err(turso::Error::Busy(_) | turso::Error::BusySnapshot(_)) => errors += 1, // Busy(\"database is locked\")\n                        Err(e) => panic!(\"unexpected error: {e:?}\"),\n                    }\n                }\n                (successes, errors)\n            }));\n        }\n\n        let mut total_successes = 0i64;\n        let mut total_errors = 0i64;\n        for handle in handles {\n            let (s, e) = handle.await.unwrap();\n            total_successes += s;\n            total_errors += e;\n        }\n\n        let conn = db.connect().unwrap();\n        let actual_rows = query_i64(&conn, \"SELECT COUNT(*) FROM t\").await;\n        if iteration % 100 == 0 {\n            eprintln!(\"test_ghost_commits: Iteration {iteration}, actual_rows={actual_rows}, total_successes={total_successes}, total_errors={total_errors}\");\n        }\n        assert_eq!(\n            actual_rows,\n            total_successes,\n            \"Ghost commits! {actual_rows} rows in DB but only {total_successes} reported as Ok ({total_errors} errors). \\\n             {} inserts committed despite returning Busy.\",\n            total_successes - actual_rows,\n        );\n    }\n}\n\n/// AUTOINCREMENT is not supported in MVCC mode. Verify that CREATE TABLE\n/// with AUTOINCREMENT fails with a clear error message.\n#[tokio::test(flavor = \"multi_thread\", worker_threads = 2)]\nasync fn test_autoincrement_blocked_in_mvcc() {\n    let (db, _dir) = setup_mvcc_db(\"\").await;\n    let conn = db.connect().unwrap();\n\n    // CREATE TABLE with AUTOINCREMENT should fail\n    let result = conn\n        .execute(\n            \"CREATE TABLE t(a INTEGER PRIMARY KEY AUTOINCREMENT, b TEXT)\",\n            (),\n        )\n        .await;\n    assert!(\n        result.is_err(),\n        \"CREATE TABLE with AUTOINCREMENT should fail in MVCC mode\"\n    );\n    let err = result.unwrap_err().to_string();\n    assert!(\n        err.contains(\"AUTOINCREMENT is not supported in MVCC mode\"),\n        \"unexpected error: {err}\"\n    );\n\n    // Regular tables without AUTOINCREMENT should still work\n    conn.execute(\"CREATE TABLE t(a INTEGER PRIMARY KEY, b TEXT)\", ())\n        .await\n        .unwrap();\n    conn.execute(\"INSERT INTO t VALUES (1, 'hello')\", ())\n        .await\n        .unwrap();\n    let count = query_i64(&conn, \"SELECT COUNT(*) FROM t\").await;\n    assert_eq!(count, 1);\n}\n"
  },
  {
    "path": "bindings/rust/tests/test_deadlock_join.rs",
    "content": "use std::sync::atomic::{AtomicBool, Ordering};\nuse std::sync::Arc;\nuse std::time::Duration;\nuse tempfile::tempdir;\nuse turso::Builder;\n\nasync fn setup_mvcc_db(schema: &str) -> (turso::Database, tempfile::TempDir) {\n    let dir = tempdir().unwrap();\n    let db_path = dir.path().join(\"test.db\");\n    let db = Builder::new_local(db_path.to_str().unwrap())\n        .build()\n        .await\n        .unwrap();\n    let conn = db.connect().unwrap();\n    let mut rows = conn\n        .query(\"PRAGMA journal_mode = 'mvcc'\", ())\n        .await\n        .unwrap();\n    while let Ok(Some(_)) = rows.next().await {}\n    drop(rows);\n    if !schema.is_empty() {\n        conn.execute_batch(schema).await.unwrap();\n    }\n    (db, dir)\n}\n\n#[tokio::test(flavor = \"multi_thread\", worker_threads = 8)]\nasync fn test_deadlock_join_during_writes() {\n    let (db, _dir) = setup_mvcc_db(\n        \"CREATE TABLE orders(id INTEGER PRIMARY KEY, customer_id INTEGER, amount INTEGER);\n         CREATE TABLE customers(id INTEGER PRIMARY KEY, name TEXT);\n         INSERT INTO customers VALUES(1, 'alice');\n         INSERT INTO customers VALUES(2, 'bob');\n         INSERT INTO customers VALUES(3, 'charlie');\",\n    )\n    .await;\n\n    let done = Arc::new(AtomicBool::new(false));\n    let mut handles = vec![];\n\n    // Writers: insert orders for various customers\n    for w in 0..4 {\n        let db = db.clone();\n        let done = done.clone();\n        handles.push(tokio::spawn(async move {\n            let conn = db.connect().unwrap();\n            let mut i = 0u64;\n            while !done.load(Ordering::Relaxed) {\n                let id = (w as u64) * 100000 + i;\n                let cust = (i % 3) + 1;\n                let _ = conn.execute(\"BEGIN CONCURRENT\", ()).await;\n                let _ = conn\n                    .execute(\n                        &format!(\"INSERT INTO orders VALUES({}, {}, {})\", id, cust, 10),\n                        (),\n                    )\n                    .await;\n                let _ = conn.execute(\"COMMIT\", ()).await;\n                i += 1;\n            }\n        }));\n    }\n\n    // Readers: do JOINs (THIS IS WHAT TRIGGERS THE HANG)\n    for _ in 0..4 {\n        let db = db.clone();\n        let done = done.clone();\n        handles.push(tokio::spawn(async move {\n            let conn = db.connect().unwrap();\n            while !done.load(Ordering::Relaxed) {\n                let _ = conn.execute(\"BEGIN CONCURRENT\", ()).await;\n                let _orphans = match conn\n                    .query(\n                        \"SELECT COUNT(*) FROM orders o LEFT JOIN customers c ON o.customer_id = c.id WHERE c.id IS NULL\",\n                        (),\n                    )\n                    .await\n                {\n                    Ok(mut rows) => match rows.next().await {\n                        Ok(Some(row)) => row.get::<i64>(0).unwrap_or(0),\n                        _ => 0,\n                    },\n                    Err(_) => 0,\n                };\n                let _ = conn.execute(\"COMMIT\", ()).await;\n            }\n        }));\n    }\n\n    // If this test hangs here, the bug is confirmed.\n    tokio::time::sleep(Duration::from_secs(3)).await;\n    done.store(true, Ordering::Relaxed);\n    for handle in handles {\n        // This await will never return if threads are deadlocked\n        handle.await.unwrap();\n    }\n}\n"
  },
  {
    "path": "bindings/tcl/Makefile",
    "content": "# Makefile for the Turso TCL extension.\n#\n# The shared library wraps libturso_sqlite3 and exposes an in-process sqlite3\n# Tcl command compatible with the upstream SQLite Tcl binding, enabling the\n# testing/sqlite3 harness to run without a subprocess per statement.\n#\n# Primary targets (Linux / Docker — no host toolchain required):\n#   docker-build   Build libturso_tcl.so inside a Debian container.\n#   docker-test    Build and run the probe tests inside a Debian container.\n#\n# Local targets (requires TCL dev headers on the host):\n#   build          Build for the current platform (macOS → .dylib, Linux → .so).\n#   clean          Remove built artefacts.\n#\n# Usage:\n#   make docker-build            # produce bindings/tcl/libturso_tcl.so\n#   make docker-test             # build + run probe tests\n#   make build                   # local build when TCL headers are available\n#   make clean\n\nROOT_DIR  := $(shell cd ../.. && pwd)\nTURSO_LIB := $(ROOT_DIR)/target/debug\nSRC       := $(ROOT_DIR)/bindings/tcl/turso_tcl.c\nINCLUDE   := $(ROOT_DIR)/sqlite3/include\n\n# Default target — local build (no Docker required).\n.PHONY: all\nall: build\n\n# ---- Docker -----------------------------------------------------------------\n\nDOCKER_IMAGE ?= rust:bookworm\n\n.PHONY: docker-build\ndocker-build:\n\tdocker run --rm \\\n\t  -v \"$(ROOT_DIR):/workspace\" \\\n\t  -w /workspace \\\n\t  $(DOCKER_IMAGE) \\\n\t  bash -c '\\\n\t    apt-get update -qq && \\\n\t    DEBIAN_FRONTEND=noninteractive apt-get install -y -qq tcl-dev && \\\n\t    cargo build -p turso_sqlite3 2>&1 && \\\n\t    gcc -shared -fPIC -o bindings/tcl/libturso_tcl.so \\\n\t        bindings/tcl/turso_tcl.c \\\n\t        -I sqlite3/include \\\n\t        -I /usr/include/tcl8.6 \\\n\t        -L target/debug \\\n\t        -lturso_sqlite3 \\\n\t        -Wl,-rpath,'\"'\"'$$ORIGIN/../../target/debug'\"'\"' \\\n\t        2>&1 && \\\n\t    echo \"Build OK: bindings/tcl/libturso_tcl.so\" \\\n\t  '\n\n# Build the shared library and run the probe tests in one container so the\n# apt install and cargo build happen only once.\n.PHONY: docker-test\ndocker-test:\n\tdocker run --rm \\\n\t  -v \"$(ROOT_DIR):/workspace\" \\\n\t  -w /workspace \\\n\t  $(DOCKER_IMAGE) \\\n\t  bash -c '\\\n\t    apt-get update -qq && \\\n\t    DEBIAN_FRONTEND=noninteractive apt-get install -y -qq tcl-dev && \\\n\t    cargo build -p turso_sqlite3 2>&1 && \\\n\t    gcc -shared -fPIC -o bindings/tcl/libturso_tcl.so \\\n\t        bindings/tcl/turso_tcl.c \\\n\t        -I sqlite3/include \\\n\t        -I /usr/include/tcl8.6 \\\n\t        -L target/debug \\\n\t        -lturso_sqlite3 \\\n\t        -Wl,-rpath,'\"'\"'$$ORIGIN/../../target/debug'\"'\"' \\\n\t        2>&1 && \\\n\t    echo \"Build OK: bindings/tcl/libturso_tcl.so\" && \\\n\t    LD_LIBRARY_PATH=target/debug tclsh bindings/tcl/test_probes.tcl \\\n\t  '\n\n# ---- Local build (macOS / Linux with TCL dev headers) -----------------------\n\nUNAME_S := $(shell uname -s 2>/dev/null)\n\nifeq ($(UNAME_S),Darwin)\n  # Prefer Homebrew tcl-tk via pkg-config; fall back to the Xcode SDK\n  # Tcl.framework headers present on any machine with Command Line Tools.\n  _TCL_SDK_HDR := $(shell xcrun --show-sdk-path 2>/dev/null)/System/Library/Frameworks/Tcl.framework/Versions/8.5/Headers\n  TCL_INCLUDE  ?= $(shell pkg-config --cflags tcl 2>/dev/null || echo \"-I$(_TCL_SDK_HDR)\")\n  TCL_LIBS     ?= $(shell pkg-config --libs   tcl 2>/dev/null || echo \"-framework Tcl\")\n  SHARED_FLAG   = -dynamiclib\n  TARGET        = $(ROOT_DIR)/bindings/tcl/libturso_tcl.dylib\nelse\n  TCL_INCLUDE  ?= $(shell pkg-config --cflags tcl 2>/dev/null || echo \"-I/usr/include/tcl8.6\")\n  TCL_LIBS     ?= $(shell pkg-config --libs   tcl 2>/dev/null || echo \"-ltcl8.6\")\n  SHARED_FLAG   = -shared -fPIC\n  TARGET        = $(ROOT_DIR)/bindings/tcl/libturso_tcl.so\nendif\n\n.PHONY: build\nbuild:\n\tcargo build -p turso_sqlite3\n\tgcc $(SHARED_FLAG) -o $(TARGET) \\\n\t    $(SRC) \\\n\t    $(TCL_INCLUDE) \\\n\t    -I$(INCLUDE) \\\n\t    -L$(TURSO_LIB) \\\n\t    -lturso_sqlite3 \\\n\t    $(TCL_LIBS) \\\n\t    -Wl,-rpath,'$(TURSO_LIB)'\n\t@echo \"Built: $(TARGET)\"\n\n# ---- Clean ------------------------------------------------------------------\n\n.PHONY: clean\nclean:\n\trm -f $(ROOT_DIR)/bindings/tcl/libturso_tcl.so \\\n\t      $(ROOT_DIR)/bindings/tcl/libturso_tcl.dylib\n"
  },
  {
    "path": "bindings/tcl/test_probes.tcl",
    "content": "# Probe tests for the native Turso TCL extension (bindings/tcl/turso_tcl.c).\n#\n# Validates the three capabilities that the subprocess shim cannot provide:\n#   1. Real engine error codes via [db errorcode].\n#   2. Accurate DML change counters via [db changes] / [db total_changes].\n#   3. In-process Tcl function registration via [db func].\n#\n# Run via:\n#   LD_LIBRARY_PATH=target/debug tclsh bindings/tcl/test_probes.tcl\n#\n# Exit code: 0 on success, 1 on any failure.\n\nset pass 0\nset fail 0\n\nproc ok {label} {\n    puts \"PASS  $label\"\n    incr ::pass\n}\n\nproc fail {label want got} {\n    puts \"FAIL  $label\"\n    puts \"      want: $want\"\n    puts \"      got:  $got\"\n    incr ::fail\n}\n\nproc assert_eq {label want got} {\n    if {$got eq $want} { ok $label } else { fail $label $want $got }\n}\n\nproc assert_ne {label not_want got} {\n    if {$got ne $not_want} {\n        ok $label\n    } else {\n        fail $label \"anything other than $not_want\" $got\n    }\n}\n\n# ---------------------------------------------------------------------------\n# Load the native extension.\n# ---------------------------------------------------------------------------\n\nset here [file dirname [file normalize [info script]]]\nset lib  [file join $here libturso_tcl.so]\n\nif {![file exists $lib]} {\n    puts \"ERROR: $lib not found — run 'make docker-build' first\"\n    exit 1\n}\n\nif {[catch {load $lib Tursotcl} err]} {\n    puts \"ERROR: failed to load $lib: $err\"\n    exit 1\n}\n\n# Use an in-memory database so tests leave no files on disk.\nsqlite3 db :memory:\n\n# ---------------------------------------------------------------------------\n# Probe 1: error code fidelity.\n# The subprocess shim always returned 0; the native module returns the real\n# SQLite result code from the engine.\n# ---------------------------------------------------------------------------\n\ncatch {db eval {SELECT * FROM no_such_table;}}\nassert_ne \"errorcode is non-zero after bad query\" 0 [db errorcode]\n\n# ---------------------------------------------------------------------------\n# Probe 2: DML change counters.\n# The subprocess shim always returned 0; the native module tracks them via\n# sqlite3_changes() and sqlite3_total_changes().\n# ---------------------------------------------------------------------------\n\ndb eval {CREATE TABLE tc(x);}\ndb eval {INSERT INTO tc VALUES(1);}\nassert_eq \"changes is 1 after single INSERT\"   1 [db changes]\ndb eval {INSERT INTO tc VALUES(2);}\nassert_ne \"total_changes accumulates across stmts\" 0 [db total_changes]\n\n# ---------------------------------------------------------------------------\n# Probe 3: in-process Tcl function registration.\n# The subprocess shim accepted [db func] but did not wire the callback into\n# the SQL engine; the native module routes calls through sqlite3_create_function.\n# ---------------------------------------------------------------------------\n\ndb func my_echo {x} { return $x }\nset result [db eval {SELECT my_echo(42);}]\nassert_eq \"db func result echoed back\" 42 $result\n\ndb func add2 {a b} { expr {$a + $b} }\nset result [db eval {SELECT add2(3, 7);}]\nassert_eq \"db func with two args\" 10 $result\n\n# ---------------------------------------------------------------------------\n# Summary\n# ---------------------------------------------------------------------------\n\ndb close\nputs \"\"\nputs \"$pass passed, $fail failed\"\n\nif {$fail > 0} { exit 1 }\n"
  },
  {
    "path": "bindings/tcl/turso_tcl.c",
    "content": "/*\n * turso_tcl.c — Native Tcl extension for Turso/Limbo database.\n *\n * Provides the `sqlite3` Tcl command that creates in-process database\n * connections, replacing the subprocess-based shim in testing/sqlite3/tester.tcl.\n *\n * Supported db sub-commands:\n *   eval SQL ?array? ?script?   — execute SQL, return results as list\n *   one  SQL                    — return first column of first row\n *   exists SQL                  — return 1 if query returns any row\n *   changes                     — rows affected by last DML\n *   total_changes               — total rows changed since open\n *   last_insert_rowid           — rowid of last INSERT\n *   errorcode                   — most recent error code\n *   errmsg                      — most recent error message\n *   null ?value?                — get/set NULL representation string\n *   func name ?arg...? body     — register a Tcl-backed scalar SQL function\n *   close                       — close database and delete command\n *   limit ...                   — stub returning a default value\n */\n\n#include <tcl.h>\n#include <sqlite3.h>\n#include <string.h>\n#include <stdlib.h>\n\n#define TURSO_TCL_VERSION \"1.0\"\n#define MAX_FUNC_ARGS 64\n\n/* ------------------------------------------------------------------ */\n/* TursoDb — state for a single open database connection               */\n/* ------------------------------------------------------------------ */\ntypedef struct TursoDb {\n    sqlite3    *db;\n    Tcl_Interp *interp;\n    Tcl_Obj    *null_obj;   /* replacement string for NULL values */\n} TursoDb;\n\n/* ------------------------------------------------------------------ */\n/* TclFuncData — state for a Tcl-backed scalar SQL function            */\n/* ------------------------------------------------------------------ */\ntypedef struct TclFuncData {\n    Tcl_Interp *interp;\n    Tcl_Obj    *script;                    /* function body */\n    int         n_args;\n    Tcl_Obj    *arg_names[MAX_FUNC_ARGS];  /* argument variable names */\n} TclFuncData;\n\n/* ------------------------------------------------------------------ */\n/* Value helpers                                                        */\n/* ------------------------------------------------------------------ */\n\n/* Convert a column value to a Tcl_Obj. */\nstatic Tcl_Obj *column_to_obj(sqlite3_stmt *stmt, int i, const char *null_str)\n{\n    int ctype = sqlite3_column_type(stmt, i);\n    switch (ctype) {\n    case SQLITE_INTEGER:\n        return Tcl_NewWideIntObj((Tcl_WideInt)sqlite3_column_int64(stmt, i));\n    case SQLITE_FLOAT:\n        return Tcl_NewDoubleObj(sqlite3_column_double(stmt, i));\n    case SQLITE_TEXT: {\n        const char *text = (const char *)sqlite3_column_text(stmt, i);\n        return Tcl_NewStringObj(text ? text : \"\", -1);\n    }\n    case SQLITE_BLOB: {\n        const void *blob = sqlite3_column_blob(stmt, i);\n        int nbytes = sqlite3_column_bytes(stmt, i);\n        return Tcl_NewByteArrayObj((const unsigned char *)blob, nbytes);\n    }\n    default: /* NULL */\n        return Tcl_NewStringObj(null_str ? null_str : \"\", -1);\n    }\n}\n\n/* Convert a function argument (sqlite3_value*) to a Tcl_Obj. */\nstatic Tcl_Obj *value_to_obj(void *argv_i)\n{\n    int vtype = sqlite3_value_type(argv_i);\n    switch (vtype) {\n    case SQLITE_INTEGER:\n        return Tcl_NewWideIntObj((Tcl_WideInt)sqlite3_value_int64(argv_i));\n    case SQLITE_FLOAT:\n        return Tcl_NewDoubleObj(sqlite3_value_double(argv_i));\n    case SQLITE_TEXT: {\n        const char *text = (const char *)sqlite3_value_text(argv_i);\n        return Tcl_NewStringObj(text ? text : \"\", -1);\n    }\n    case SQLITE_BLOB: {\n        const void *blob = sqlite3_value_blob(argv_i);\n        int nbytes = sqlite3_value_bytes(argv_i);\n        return Tcl_NewByteArrayObj((const unsigned char *)blob, nbytes);\n    }\n    default: /* NULL */\n        return Tcl_NewStringObj(\"\", 0);\n    }\n}\n\n/* ------------------------------------------------------------------ */\n/* Tcl scalar function bridge                                           */\n/* ------------------------------------------------------------------ */\n\nstatic void tcl_scalar_bridge(void *ctx, int argc, void **argv)\n{\n    TclFuncData *func = (TclFuncData *)sqlite3_user_data(ctx);\n    Tcl_Interp  *interp = func->interp;\n    int          i, rc;\n\n    /* Bind argument variables in the calling scope. */\n    for (i = 0; i < argc && i < func->n_args; i++) {\n        Tcl_Obj *val = value_to_obj(argv[i]);\n        if (Tcl_ObjSetVar2(interp, func->arg_names[i], NULL, val,\n                           TCL_LEAVE_ERR_MSG) == NULL) {\n            sqlite3_result_error(ctx, Tcl_GetString(Tcl_GetObjResult(interp)), -1);\n            return;\n        }\n    }\n\n    /* Evaluate the script body. */\n    rc = Tcl_EvalObjEx(interp, func->script, 0);\n\n    if (rc == TCL_ERROR) {\n        const char *err = Tcl_GetString(Tcl_GetObjResult(interp));\n        sqlite3_result_error(ctx, err, -1);\n        return;\n    }\n\n    /* Convert the Tcl result to an SQL value. */\n    Tcl_Obj    *result = Tcl_GetObjResult(interp);\n    Tcl_WideInt ival;\n    double      dval;\n\n    if (Tcl_GetWideIntFromObj(NULL, result, &ival) == TCL_OK) {\n        sqlite3_result_int64(ctx, (int64_t)ival);\n    } else if (Tcl_GetDoubleFromObj(NULL, result, &dval) == TCL_OK) {\n        sqlite3_result_double(ctx, dval);\n    } else {\n        int         slen;\n        const char *str = Tcl_GetStringFromObj(result, &slen);\n        sqlite3_result_text(ctx, str, slen, SQLITE_TRANSIENT);\n    }\n}\n\nstatic void tcl_func_destroy(void *pApp)\n{\n    TclFuncData *func = (TclFuncData *)pApp;\n    int i;\n    if (!func) return;\n    if (func->script) Tcl_DecrRefCount(func->script);\n    for (i = 0; i < func->n_args; i++) {\n        if (func->arg_names[i]) Tcl_DecrRefCount(func->arg_names[i]);\n    }\n    Tcl_Free((char *)func);\n}\n\n/* ------------------------------------------------------------------ */\n/* Multi-statement SQL execution helpers                                */\n/* ------------------------------------------------------------------ */\n\n/*\n * Execute all statements in `sql`, collecting result rows from the last\n * statement that returns rows into `result_list`.\n * Returns TCL_OK or TCL_ERROR; sets the interpreter result on error.\n */\nstatic int exec_sql_collect(Tcl_Interp *interp, sqlite3 *db,\n                             const char *sql, const char *null_str,\n                             Tcl_Obj **result_list_out)\n{\n    Tcl_Obj    *result_list = Tcl_NewListObj(0, NULL);\n    Tcl_IncrRefCount(result_list);\n    const char *remaining   = sql;\n    int         rc;\n\n    while (remaining && *remaining) {\n        /* skip leading whitespace and bare semicolons */\n        while (*remaining == ' ' || *remaining == '\\n' ||\n               *remaining == '\\t' || *remaining == '\\r' ||\n               *remaining == ';') {\n            remaining++;\n        }\n        if (!*remaining) break;\n\n        sqlite3_stmt *stmt = NULL;\n        const char   *tail = NULL;\n\n        rc = sqlite3_prepare_v2(db, remaining, -1, &stmt, &tail);\n        if (rc != SQLITE_OK) {\n            Tcl_DecrRefCount(result_list);\n            Tcl_SetResult(interp, (char *)sqlite3_errmsg(db), TCL_VOLATILE);\n            return TCL_ERROR;\n        }\n        if (!stmt) {\n            /* empty / comment-only statement */\n            remaining = tail;\n            continue;\n        }\n\n        /* reset the list for each non-empty statement so the caller\n           sees the results of the final one (matches SQLite tclsqlite behaviour) */\n        Tcl_DecrRefCount(result_list);\n        result_list = Tcl_NewListObj(0, NULL);\n        Tcl_IncrRefCount(result_list);\n\n        int ncols = sqlite3_column_count(stmt);\n\n        while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {\n            int i;\n            for (i = 0; i < ncols; i++) {\n                Tcl_Obj *val = column_to_obj(stmt, i, null_str);\n                Tcl_ListObjAppendElement(interp, result_list, val);\n            }\n        }\n\n        sqlite3_finalize(stmt);\n\n        if (rc != SQLITE_DONE) {\n            Tcl_DecrRefCount(result_list);\n            Tcl_SetResult(interp, (char *)sqlite3_errmsg(db), TCL_VOLATILE);\n            return TCL_ERROR;\n        }\n\n        remaining = tail;\n    }\n\n    *result_list_out = result_list;\n    return TCL_OK;\n}\n\n/* ------------------------------------------------------------------ */\n/* db command dispatcher                                                */\n/* ------------------------------------------------------------------ */\n\nstatic void TursoDbFree(ClientData cd)\n{\n    TursoDb *tdb = (TursoDb *)cd;\n    if (!tdb) return;\n    if (tdb->db)       sqlite3_close(tdb->db);\n    if (tdb->null_obj) Tcl_DecrRefCount(tdb->null_obj);\n    Tcl_Free((char *)tdb);\n}\n\nstatic int TursoDbCmd(ClientData cd, Tcl_Interp *interp,\n                      int objc, Tcl_Obj *const objv[])\n{\n    TursoDb    *tdb = (TursoDb *)cd;\n    static const char *cmds[] = {\n        \"eval\", \"one\", \"exists\", \"changes\", \"total_changes\",\n        \"last_insert_rowid\", \"errorcode\", \"errmsg\", \"null\",\n        \"func\", \"function\", \"close\", \"limit\",\n        NULL\n    };\n    enum {\n        CMD_EVAL, CMD_ONE, CMD_EXISTS, CMD_CHANGES, CMD_TOTAL_CHANGES,\n        CMD_LAST_INSERT_ROWID, CMD_ERRORCODE, CMD_ERRMSG, CMD_NULL,\n        CMD_FUNC, CMD_FUNCTION, CMD_CLOSE, CMD_LIMIT\n    };\n    int cmdIdx;\n\n    if (objc < 2) {\n        Tcl_WrongNumArgs(interp, 1, objv, \"subcommand ?args?\");\n        return TCL_ERROR;\n    }\n\n    if (Tcl_GetIndexFromObj(interp, objv[1], cmds, \"subcommand\", 0,\n                            &cmdIdx) != TCL_OK) {\n        return TCL_ERROR;\n    }\n\n    switch (cmdIdx) {\n\n    /* ---- simple counters / metadata ---- */\n\n    case CMD_CHANGES:\n        Tcl_SetObjResult(interp, Tcl_NewIntObj(sqlite3_changes(tdb->db)));\n        return TCL_OK;\n\n    case CMD_TOTAL_CHANGES:\n        Tcl_SetObjResult(interp, Tcl_NewIntObj(sqlite3_total_changes(tdb->db)));\n        return TCL_OK;\n\n    case CMD_LAST_INSERT_ROWID:\n        Tcl_SetObjResult(interp,\n            Tcl_NewWideIntObj((Tcl_WideInt)sqlite3_last_insert_rowid(tdb->db)));\n        return TCL_OK;\n\n    case CMD_ERRORCODE:\n        Tcl_SetObjResult(interp, Tcl_NewIntObj(sqlite3_errcode(tdb->db)));\n        return TCL_OK;\n\n    case CMD_ERRMSG:\n        Tcl_SetResult(interp, (char *)sqlite3_errmsg(tdb->db), TCL_VOLATILE);\n        return TCL_OK;\n\n    /* ---- null value string ---- */\n\n    case CMD_NULL:\n        if (objc == 3) {\n            if (tdb->null_obj) Tcl_DecrRefCount(tdb->null_obj);\n            tdb->null_obj = objv[2];\n            Tcl_IncrRefCount(tdb->null_obj);\n        }\n        Tcl_SetObjResult(interp,\n            tdb->null_obj ? tdb->null_obj : Tcl_NewStringObj(\"\", 0));\n        return TCL_OK;\n\n    /* ---- close ---- */\n\n    case CMD_CLOSE:\n        Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));\n        return TCL_OK;\n\n    /* ---- limit (stub) ---- */\n\n    case CMD_LIMIT:\n        Tcl_SetObjResult(interp, Tcl_NewIntObj(1000000));\n        return TCL_OK;\n\n    /* ---- eval ---- */\n\n    case CMD_EVAL: {\n        if (objc < 3 || objc > 5) {\n            Tcl_WrongNumArgs(interp, 2, objv, \"sql ?array? ?script?\");\n            return TCL_ERROR;\n        }\n\n        const char *sql      = Tcl_GetString(objv[2]);\n        const char *null_str = tdb->null_obj\n                               ? Tcl_GetString(tdb->null_obj) : \"\";\n\n        /* db eval sql — collect all result values into a flat list */\n        if (objc == 3) {\n            Tcl_Obj *result_list = NULL;\n            int rc = exec_sql_collect(interp, tdb->db, sql, null_str,\n                                      &result_list);\n            if (rc != TCL_OK) return rc;\n            Tcl_SetObjResult(interp, result_list);\n            Tcl_DecrRefCount(result_list);\n            return TCL_OK;\n        }\n\n        /* db eval sql array script — per-row callback */\n        if (objc == 5) {\n            Tcl_Obj *array_name = objv[3];\n            Tcl_Obj *script     = objv[4];\n\n            const char   *remaining = sql;\n            int           loop_rc   = TCL_OK;\n\n            while (remaining && *remaining) {\n                while (*remaining == ' ' || *remaining == '\\n' ||\n                       *remaining == '\\t' || *remaining == '\\r' ||\n                       *remaining == ';') {\n                    remaining++;\n                }\n                if (!*remaining) break;\n\n                sqlite3_stmt *stmt = NULL;\n                const char   *tail = NULL;\n\n                int rc = sqlite3_prepare_v2(tdb->db, remaining, -1, &stmt, &tail);\n                if (rc != SQLITE_OK) {\n                    Tcl_SetResult(interp, (char *)sqlite3_errmsg(tdb->db),\n                                  TCL_VOLATILE);\n                    return TCL_ERROR;\n                }\n                if (!stmt) { remaining = tail; continue; }\n\n                int ncols = sqlite3_column_count(stmt);\n\n                /* Set array(*) to the list of column names. */\n                Tcl_Obj *col_list = Tcl_NewListObj(0, NULL);\n                int i;\n                for (i = 0; i < ncols; i++) {\n                    const char *col = sqlite3_column_name(stmt, i);\n                    Tcl_ListObjAppendElement(interp, col_list,\n                        Tcl_NewStringObj(col ? col : \"\", -1));\n                }\n                Tcl_ObjSetVar2(interp, array_name,\n                               Tcl_NewStringObj(\"*\", 1), col_list, 0);\n\n                while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {\n                    for (i = 0; i < ncols; i++) {\n                        const char *col = sqlite3_column_name(stmt, i);\n                        Tcl_Obj *val = column_to_obj(stmt, i, null_str);\n                        Tcl_ObjSetVar2(interp, array_name,\n                                       Tcl_NewStringObj(col ? col : \"\", -1),\n                                       val, 0);\n                    }\n\n                    loop_rc = Tcl_EvalObjEx(interp, script, 0);\n                    if (loop_rc == TCL_BREAK) {\n                        loop_rc = TCL_OK;\n                        break;\n                    } else if (loop_rc == TCL_CONTINUE) {\n                        loop_rc = TCL_OK;\n                    } else if (loop_rc != TCL_OK) {\n                        break;\n                    }\n                }\n\n                sqlite3_finalize(stmt);\n\n                if (loop_rc != TCL_OK) return loop_rc;\n\n                if (rc != SQLITE_DONE && rc != SQLITE_ROW) {\n                    Tcl_SetResult(interp, (char *)sqlite3_errmsg(tdb->db),\n                                  TCL_VOLATILE);\n                    return TCL_ERROR;\n                }\n\n                remaining = tail;\n            }\n\n            Tcl_ResetResult(interp);\n            return TCL_OK;\n        }\n\n        /* objc == 4: not a standard form we support */\n        Tcl_WrongNumArgs(interp, 2, objv, \"sql ?array script?\");\n        return TCL_ERROR;\n    }\n\n    /* ---- one ---- */\n\n    case CMD_ONE: {\n        if (objc != 3) {\n            Tcl_WrongNumArgs(interp, 2, objv, \"sql\");\n            return TCL_ERROR;\n        }\n        const char *sql      = Tcl_GetString(objv[2]);\n        const char *null_str = tdb->null_obj\n                               ? Tcl_GetString(tdb->null_obj) : \"\";\n\n        sqlite3_stmt *stmt = NULL;\n        int rc = sqlite3_prepare_v2(tdb->db, sql, -1, &stmt, NULL);\n        if (rc != SQLITE_OK) {\n            Tcl_SetResult(interp, (char *)sqlite3_errmsg(tdb->db), TCL_VOLATILE);\n            return TCL_ERROR;\n        }\n\n        Tcl_Obj *result = Tcl_NewStringObj(null_str, -1);\n        if (sqlite3_step(stmt) == SQLITE_ROW) {\n            result = column_to_obj(stmt, 0, null_str);\n        }\n        sqlite3_finalize(stmt);\n        Tcl_SetObjResult(interp, result);\n        return TCL_OK;\n    }\n\n    /* ---- exists ---- */\n\n    case CMD_EXISTS: {\n        if (objc != 3) {\n            Tcl_WrongNumArgs(interp, 2, objv, \"sql\");\n            return TCL_ERROR;\n        }\n        const char *sql = Tcl_GetString(objv[2]);\n\n        sqlite3_stmt *stmt = NULL;\n        int rc = sqlite3_prepare_v2(tdb->db, sql, -1, &stmt, NULL);\n        if (rc != SQLITE_OK) {\n            Tcl_SetResult(interp, (char *)sqlite3_errmsg(tdb->db), TCL_VOLATILE);\n            return TCL_ERROR;\n        }\n        int exists = (sqlite3_step(stmt) == SQLITE_ROW) ? 1 : 0;\n        sqlite3_finalize(stmt);\n        Tcl_SetObjResult(interp, Tcl_NewBooleanObj(exists));\n        return TCL_OK;\n    }\n\n    /* ---- func / function ---- */\n\n    case CMD_FUNC:\n    case CMD_FUNCTION: {\n        /*\n         * db func name ?arglist? body\n         * db function name ?arglist? body\n         *\n         * Registers a Tcl proc body as a scalar SQL function.  The arglist\n         * mirrors proc syntax: it may be a single Tcl list object ({a b}) or\n         * multiple individual words (a b) — both result in named variables\n         * being bound before the body is evaluated.\n         *\n         *   objv[2]         = function name\n         *   objv[3..objc-2] = argument variable names, OR a single Tcl list\n         *   objv[objc-1]    = script body\n         */\n        if (objc < 4) {\n            Tcl_WrongNumArgs(interp, 2, objv, \"name ?arglist? body\");\n            return TCL_ERROR;\n        }\n\n        const char *func_name = Tcl_GetString(objv[2]);\n        Tcl_Obj    *body      = objv[objc - 1];\n        int         i;\n\n        /* Resolve the argument variable names.\n         *\n         * objc == 4: db func name body          → no named args\n         * objc == 5: db func name argspec body  → argspec is a Tcl list\n         * objc >= 6: db func name a b … body    → each word is a name\n         */\n        int         n_args   = 0;\n        Tcl_Obj   **arg_objs = NULL;\n\n        if (objc == 5) {\n            /* Single argspec object — split it as a Tcl list so that both\n             * `db func f x body` and `db func f {x y} body` work. */\n            if (Tcl_ListObjGetElements(interp, objv[3],\n                                       &n_args, &arg_objs) != TCL_OK) {\n                return TCL_ERROR;\n            }\n        } else if (objc > 5) {\n            n_args   = objc - 4;\n            arg_objs = (Tcl_Obj **)&objv[3];\n        }\n\n        TclFuncData *func_data =\n            (TclFuncData *)Tcl_Alloc(sizeof(TclFuncData));\n        memset(func_data, 0, sizeof(TclFuncData));\n        func_data->interp  = interp;\n        func_data->script  = body;\n        Tcl_IncrRefCount(body);\n        func_data->n_args  = (n_args < MAX_FUNC_ARGS) ? n_args : MAX_FUNC_ARGS;\n\n        for (i = 0; i < func_data->n_args; i++) {\n            func_data->arg_names[i] = arg_objs[i];\n            Tcl_IncrRefCount(func_data->arg_names[i]);\n        }\n\n        int sql_n_args = (n_args == 0) ? -1 : n_args;\n        int rc = sqlite3_create_function_v2(\n            tdb->db,\n            func_name,\n            sql_n_args,\n            0, /* SQLITE_UTF8 */\n            (void *)func_data,\n            (void (*)(void))tcl_scalar_bridge,\n            NULL, NULL,\n            (void (*)(void))tcl_func_destroy\n        );\n\n        if (rc != SQLITE_OK) {\n            tcl_func_destroy(func_data);\n            Tcl_SetResult(interp,\n                (char *)sqlite3_errmsg(tdb->db), TCL_VOLATILE);\n            return TCL_ERROR;\n        }\n        return TCL_OK;\n    }\n\n    } /* switch */\n\n    return TCL_OK;\n}\n\n/* ------------------------------------------------------------------ */\n/* sqlite3 open command                                                 */\n/* ------------------------------------------------------------------ */\n\nstatic int TursoOpenCmd(ClientData cd, Tcl_Interp *interp,\n                        int objc, Tcl_Obj *const objv[])\n{\n    (void)cd;\n\n    if (objc < 3) {\n        Tcl_WrongNumArgs(interp, 1, objv, \"name filename ?options?\");\n        return TCL_ERROR;\n    }\n\n    const char *handle_name = Tcl_GetString(objv[1]);\n    const char *filename    = Tcl_GetString(objv[2]);\n\n    sqlite3 *db  = NULL;\n    int      rc  = sqlite3_open(filename, &db);\n\n    if (rc != SQLITE_OK) {\n        const char *errmsg = db ? sqlite3_errmsg(db) : \"out of memory\";\n        Tcl_SetResult(interp, (char *)errmsg, TCL_VOLATILE);\n        if (db) sqlite3_close(db);\n        return TCL_ERROR;\n    }\n\n    TursoDb *tdb = (TursoDb *)Tcl_Alloc(sizeof(TursoDb));\n    tdb->db       = db;\n    tdb->interp   = interp;\n    tdb->null_obj = NULL;\n\n    Tcl_CreateObjCommand(interp, handle_name, TursoDbCmd,\n                         (ClientData)tdb, TursoDbFree);\n    Tcl_SetResult(interp, (char *)handle_name, TCL_VOLATILE);\n    return TCL_OK;\n}\n\n/* ------------------------------------------------------------------ */\n/* Extension initialisation                                             */\n/* ------------------------------------------------------------------ */\n\nint Tursotcl_Init(Tcl_Interp *interp)\n{\n    if (Tcl_InitStubs(interp, TCL_VERSION, 0) == NULL) {\n        return TCL_ERROR;\n    }\n\n    Tcl_CreateObjCommand(interp, \"sqlite3\", TursoOpenCmd, NULL, NULL);\n\n    Tcl_PkgProvide(interp, \"tursotcl\", TURSO_TCL_VERSION);\n    return TCL_OK;\n}\n"
  },
  {
    "path": "cli/Cargo.toml",
    "content": "# Copyright 2023 the Limbo authors. All rights reserved. MIT license.\n\n[package]\nauthors.workspace = true\ndefault-run = \"tursodb\"\ndescription = \"The Turso interactive SQL shell\"\nedition.workspace = true\nlicense.workspace = true\nname = \"turso_cli\"\nrepository.workspace = true\nversion.workspace = true\n\n[package.metadata.dist]\ndist = true\n\n[[bin]]\nname = \"tursodb\"\npath = \"main.rs\"\n\n[dependencies]\nanyhow.workspace = true\ncfg-if = { workspace = true }\nclap = { workspace = true, features = [\"derive\"] }\nclap_complete = { version = \"=4.5.47\", features = [\"unstable-dynamic\"] }\ncomfy-table = \"7.1.4\"\ncsv = \"1.3.1\"\nctrlc = \"3.4.4\"\ndirs = \"5.0.1\"\nenv_logger = { workspace = true }\nlibc = \"0.2.172\"\nturso_core = { workspace = true, default-features = true, features = [\n    \"cli_only\",\n    \"conn_raw_api\",\n    \"fs\",\n\t\"fts\",\n] }\nturso_sync_engine = { workspace = true }\nlimbo_completion = { workspace = true, features = [\"static\"] }\nmiette = { workspace = true, features = [\"fancy\"] }\nnu-ansi-term = { version = \"0.50.1\", features = [\n    \"serde\",\n    \"derive_serde_style\",\n] }\nrustyline = { version = \"15.0.0\", default-features = true, features = [\n    \"derive\",\n] }\nshlex = \"1.3.0\"\nsyntect = { git = \"https://github.com/trishume/syntect.git\", rev = \"64644ffe064457265cbcee12a0c1baf9485ba6ee\", default-features = false, features = [\"default-fancy\"] }\ntracing = { workspace = true }\ntracing-appender = { workspace = true }\ntracing-subscriber = { workspace = true, features = [\"env-filter\"] }\ntoml = { version = \"0.8.20\", features = [\"preserve_order\"] }\nschemars = { version = \"0.8.22\", features = [\"preserve_order\"] }\nserde = { workspace = true, features = [\"derive\"] }\nvalidator = { version = \"0.20.0\", features = [\"derive\"] }\ntoml_edit = { version = \"0.22.24\", features = [\"serde\"] }\nserde_json = \"1.0\"\ntermimad = \"0.30\"\ninclude_dir = \"0.7\"\nrand = \"0.8\"\nmimalloc = { workspace = true, optional = true }\nprost = \"0.14.1\"\nroaring = \"0.11.2\"\nbytes = \"1.11.0\"\nitertools.workspace = true\n\n[features]\ndefault = [\"io_uring\", \"mimalloc\"]\nio_uring = [\"turso_core/io_uring\"]\nexperimental_win_iocp = [\"turso_core/experimental_win_iocp\"]\ntracing_release = [\"turso_core/tracing_release\"]\nmimalloc = [\"dep:mimalloc\"]\nfts = [\"turso_core/fts\"]\nmvcc_repl = []\n\n[build-dependencies]\nsyntect = { git = \"https://github.com/trishume/syntect.git\", rev = \"64644ffe064457265cbcee12a0c1baf9485ba6ee\", default-features = false, features = [\"default-fancy\"] }\ninclude_dir = \"0.7\"\n"
  },
  {
    "path": "cli/SQL.sublime-syntax",
    "content": "%YAML 1.2\n---\nname: SQL\nscope: source.sql\nversion: 2\nhidden: true\n\nvariables:\n  string_escape: (?:\\\\.)\n  simple_identifier: (?:\\w+)\n  simple_identifier_break: (?!\\w)\n\n  toplevel_reserved: |-\n    (?xi: alter | analyze | create | cross | delete | drop | explain | from | grant | group | inner | insert | join\n    | left | on | order | outer | right | select | set | truncate | union\n    | update | where )\n  additional_toplevel_reserved: (?!)\n  # TODO: not all are supported by all dialects!\n  ddl_target: |-\n    (?xi: {{ddl_target_other}}\n    | (?: {{ddl_target_function_modifier}} \\s+ )? {{ddl_target_function}}\n    | (?: {{ddl_target_index_modifier}} \\s+ )? index\n    | (?: {{ddl_target_table_modifier}} \\s+ )? table )\n  ddl_target_function: |-\n    (?xi: procedure | function )\n  ddl_target_other: |-\n    (?xi: aggregate | column | constraint | conversion | database | domain | group\n    | language | member | operator\\s+class | operator | role | rule | schema | sequence\n    | tablespace | trigger | type | user | view )\n\n  ddl_target_inside_alter_table: |-\n    (?xi: constraint )\n\n  ddl_target_function_modifier: |-\n    (?xi: aggregate )\n  ddl_target_index_modifier: |-\n    (?xi: clustered | fulltext | spatial | unique )\n  ddl_target_table_modifier: |-\n    (?xi: temp(?:orary)? )\n\n  function_parameter_modifier: |-\n    (?xi: inout | in | out )\n\n  simple_types: |-\n    (?xi: boolean | bool | year )\n\n  types_with_optional_number: |-\n    (?xi: bit | datetime | int | number | n?(?:var)?char | varbinary )\n\n  type_modifiers: |-\n    (?xi: signed | unsigned | zerofill )\n\n  builtin_scalar_functions: |-\n    (?xi: current_(?: date | time(?:stamp)? ) )\n\n  builtin_user_functions: |-\n    (?xi: (?: current | session | system )_user )\n\ncontexts:\n  prototype:\n    - include: comments\n\n  main:\n    - include: sql\n\n  sql:\n    - include: statements\n    - include: statement-terminators\n    - include: expressions-or-column-names\n\n  statements:\n    - include: create-statements\n    - include: drop-statements\n    - include: alter-statements\n    - include: dml-statements\n    - include: grant-statements\n    - include: revoke-statements\n    - include: explain-statements\n    - include: analyze-statements\n    - include: other-statements\n\n  ###[ COMMENTS ]################################################################\n\n  comments:\n    - include: double-dash-comments\n    - include: block-comments\n\n  double-dash-comments:\n    - match: \"--\"\n      scope: punctuation.definition.comment.sql\n      push: inside-double-dash-comment\n\n  block-comments:\n    - match: /\\*(?:\\*(?!/))+\n      scope: punctuation.definition.comment.begin.sql\n      push: inside-comment-docblock\n    - match: /\\*\n      scope: punctuation.definition.comment.begin.sql\n      push: inside-comment-block\n\n  inside-double-dash-comment:\n    - meta_include_prototype: false\n    - meta_scope: comment.line.double-dash.sql\n    - match: \\n\n      pop: 1\n\n  inside-comment-docblock:\n    - meta_include_prototype: false\n    - meta_scope: comment.block.documentation.sql\n    - match: \\*+/\n      scope: punctuation.definition.comment.end.sql\n      pop: 1\n    - match: ^\\s*(\\*)(?!\\**/)\n      captures:\n        1: punctuation.definition.comment.sql\n\n  inside-comment-block:\n    - meta_include_prototype: false\n    - meta_scope: comment.block.sql\n    - match: \\*+/\n      scope: punctuation.definition.comment.end.sql\n      pop: 1\n\n  ###[ DDL CREATE STATEMENTS ]###################################################\n\n  create-statements:\n    - match: \\b(?i:create)\\b\n      scope: keyword.other.ddl.sql\n      push:\n        - create-meta\n        - create-target\n\n  create-meta:\n    - meta_include_prototype: false\n    - meta_scope: meta.statement.create.sql\n    - include: immediately-pop\n\n  create-target:\n    - include: create-function\n    - include: create-index\n    - include: create-table\n    - include: create-other\n    - include: else-pop\n\n  create-function:\n    - match: \\b(?:({{ddl_target_function_modifier}})\\s+)?({{ddl_target_function}})\\b\n      captures:\n        1: keyword.other.ddl.sql\n        2: keyword.other.ddl.sql\n      set:\n        - expect-function-characteristics\n        - expect-function-parameters\n        - expect-function-creation-name\n        - create-function-condition\n\n  create-function-condition:\n    - meta_include_prototype: false\n    - include: maybe-condition\n\n  create-index:\n    - match: \\b(?i:(?:({{ddl_target_index_modifier}})\\s+)?(index))\\b\n      captures:\n        1: keyword.other.ddl.sql\n        2: keyword.other.ddl.sql\n      set:\n        - create-index-args\n        - expect-index-creation-name\n        - create-index-condition\n\n  create-index-condition:\n    - meta_include_prototype: false\n    - include: maybe-condition\n\n  create-index-args:\n    - meta_scope: meta.index.sql\n    - include: on-table-names\n    - include: create-common-args\n\n  create-table:\n    - match: \\b(?i:(?:({{ddl_target_table_modifier}})\\s+)?(table))\\b\n      captures:\n        1: keyword.other.ddl.sql\n        2: keyword.other.ddl.sql\n      set:\n        - create-table-args\n        - maybe-column-declaration-list\n        - expect-table-creation-name\n        - create-table-condition\n\n  create-table-condition:\n    - meta_include_prototype: false\n    - include: maybe-condition\n\n  create-table-args:\n    - meta_scope: meta.table.sql\n    - include: create-common-args\n\n  create-other:\n    - match: \\b{{ddl_target_other}}\\b\n      scope: keyword.other.ddl.sql\n      set:\n        - create-other-args\n        - expect-other-creation-name\n        - create-other-condition\n\n  create-other-condition:\n    - meta_include_prototype: false\n    - include: maybe-condition\n\n  create-other-args:\n    - match: \\b(?i:(as)\\s+(table))\\b\n      captures:\n        1: keyword.context.block.sql\n        2: support.type.sql\n      set: maybe-column-declaration-list\n    - match: \\b(?i:as)\\b\n      scope: keyword.context.block.sql\n      pop: 1\n    - include: grant\n    - include: on-table-names\n    - include: create-common-args\n\n  create-common-args:\n    - include: expressions\n    - include: pop-on-top-level-reserved-word\n\n  ###[ DDL DROP STATEMENTS ]#####################################################\n\n  drop-statements:\n    - match: \\b(?i:drop)\\b\n      scope: keyword.other.ddl.sql\n      push:\n        - drop-meta\n        - drop-target\n\n  drop-meta:\n    - meta_include_prototype: false\n    - meta_scope: meta.statement.drop.sql\n    - include: immediately-pop\n\n  drop-target:\n    - include: drop-function\n    - include: drop-index\n    - include: drop-table\n    - include: drop-other\n    - include: else-pop\n\n  drop-function:\n    - match: \\b{{ddl_target_function}}\\b\n      scope: keyword.other.ddl.sql\n      set:\n        - drop-function-args\n        - expect-function-name\n        - drop-function-condition\n\n  drop-function-condition:\n    - meta_include_prototype: false\n    - include: maybe-condition\n\n  drop-function-args:\n    - meta_include_prototype: false\n    - meta_scope: meta.function.sql\n    - include: immediately-pop\n\n  drop-index:\n    - match: \\b(?i:index)\\b\n      scope: keyword.other.ddl.sql\n      set:\n        - drop-index-args\n        - expect-index-name\n        - drop-index-condition\n\n  drop-index-condition:\n    - meta_include_prototype: false\n    - include: maybe-condition\n\n  drop-index-args:\n    - meta_scope: meta.index.sql\n    - include: maybe-on-table-name\n\n  drop-table:\n    - match: \\b(?i:(?:({{ddl_target_table_modifier}})\\s+)?(table))\\b\n      captures:\n        1: keyword.other.ddl.sql\n        2: keyword.other.ddl.sql\n      set:\n        - drop-table-args\n        - expect-table-name\n        - drop-table-condition\n\n  drop-table-condition:\n    - meta_include_prototype: false\n    - include: maybe-condition\n\n  drop-table-args:\n    - meta_include_prototype: false\n    - meta_scope: meta.table.sql\n    - include: immediately-pop\n\n  drop-other:\n    - match: \\b(?i:user)\\b\n      scope: storage.type.sql\n      set:\n        - expect-user-name\n        - maybe-condition\n    - match: \\b{{ddl_target_other}}\\b\n      scope: keyword.other.ddl.sql\n      set:\n        - drop-other-args\n        - expect-other-name\n        - drop-other-condition\n\n  drop-other-condition:\n    - meta_include_prototype: false\n    - include: maybe-condition\n\n  drop-other-args:\n    - include: maybe-on-table-name\n\n  ###[ DDL ALTER STATEMENTS ]####################################################\n\n  alter-statements:\n    - match: \\b(?i:alter)\\b\n      scope: keyword.other.ddl.sql\n      push:\n        - alter-meta\n        - alter-target\n\n  alter-meta:\n    - meta_include_prototype: false\n    - meta_scope: meta.statement.alter.sql\n    - include: immediately-pop\n\n  alter-target:\n    - include: alter-function\n    - include: alter-index\n    - include: alter-table\n    - include: alter-other\n    - include: else-pop\n\n  ###[ DDL ALTER FUNCTION STATEMENTS ]###########################################\n\n  alter-function:\n    - match: \\b(?:({{ddl_target_function_modifier}})\\s+)?({{ddl_target_function}})\\b\n      captures:\n        1: keyword.other.ddl.sql\n        2: keyword.other.ddl.sql\n      set:\n        - expect-function-characteristics\n        - expect-function-parameters\n        - expect-function-name\n        - alter-function-condition\n\n  alter-function-condition:\n    - meta_include_prototype: false\n    - include: maybe-condition\n\n  ###[ DDL ALTER INDEX STATEMENTS ]##############################################\n\n  alter-index:\n    - match: \\b(?i:(?:({{ddl_target_index_modifier}})\\s+)?(index))\\b\n      captures:\n        1: keyword.other.ddl.sql\n        2: keyword.other.ddl.sql\n      set:\n        - alter-index-args\n        - expect-index-name\n        - alter-index-condition\n\n  alter-index-condition:\n    - meta_include_prototype: false\n    - include: maybe-condition\n\n  alter-index-args:\n    - include: alter-common\n    - include: pop-on-top-level-reserved-word\n    - include: expressions-or-column-names\n\n  ###[ DDL ALTER TABLE STATEMENTS ]##############################################\n\n  alter-table:\n    - match: \\b(?i:(?:({{ddl_target_table_modifier}})\\s+)?(table))\\b\n      captures:\n        1: keyword.other.ddl.sql\n        2: keyword.other.ddl.sql\n      set:\n        - alter-table-args\n        - expect-table-name\n        - alter-table-condition\n\n  alter-table-condition:\n    - meta_include_prototype: false\n    - include: maybe-condition\n\n  alter-table-args:\n    - include: alter-columns\n    - include: alter-common\n    - include: pop-on-top-level-reserved-word\n    - include: expressions-or-column-names\n\n  alter-other:\n    - match: \\b{{ddl_target_other}}\\b\n      scope: keyword.other.ddl.sql\n      set:\n        - alter-other-args\n        - expect-other-name\n        - alter-other-condition\n\n  alter-other-condition:\n    - meta_include_prototype: false\n    - include: maybe-condition\n\n  alter-other-args:\n    - include: alter-common\n    - include: else-pop\n\n  alter-columns:\n    - match: \\b(?i:(?:(add|alter)\\b(?:\\s+(column)\\b|(?!\\s+(?:table|{{ddl_target_inside_alter_table}})\\b))))\n      captures:\n        1: keyword.other.ddl.sql\n        2: keyword.other.ddl.sql\n      push:\n        - expect-type\n        - expect-column-name-declaration\n    - match: \\b(?i:(drop)(?:\\s+(column)\\b|(?!\\s+{{ddl_target}}\\b)))\n      scope: keyword.other.ddl.sql\n      push: expect-column-name\n\n  alter-common:\n    - match: \\b(?i:(add)\\s+(constraint))\\b\n      captures:\n        1: keyword.other.ddl.sql\n        2: keyword.other.ddl.sql\n      push:\n        - maybe-column-modifier\n        - expect-constraint-name\n    - match: \\b(?i:(add)\\s+(?:({{ddl_target_index_modifier}})\\s+)?(index))\\b\n      captures:\n        1: keyword.other.ddl.sql\n        2: keyword.other.ddl.sql\n        3: keyword.other.ddl.sql\n    - match: \\b(?i:(add)\\s+(primary|foreign)\\s+(key))\\b\n      captures:\n        1: keyword.other.ddl.sql\n        2: keyword.other.ddl.sql\n        3: keyword.other.ddl.sql\n\n  ###[ DDL STATEMENT PROTOTYPES ]################################################\n\n  maybe-on-table-name:\n    - include: on-table-names\n    - include: else-pop\n\n  on-table-names:\n    - match: \\b(?i:on)\\b\n      scope: keyword.other.sql\n      push: expect-table-name\n\n  ###[ DML STATEMENTS ]##########################################################\n\n  dml-statements:\n    - match: \\b(?i:select)\\b\n      scope: keyword.other.dml.sql\n    - match: \\b(?i:union(?:\\s+all)?)\\b\n      scope: keyword.other.dml.sql\n    - match: \\b(?i:(?:delete(?:\\s+from)?))\\b\n      scope: keyword.other.dml.sql\n      push: dml-delete\n    - match: \\b(?i:update)\\b\n      scope: keyword.other.dml.sql\n      push: dml-update\n    - match: \\b(?i:(?:insert\\s+into|truncate))\\b\n      scope: keyword.other.dml.sql\n      push: expect-table-name\n    - include: set-statements\n    # expressions\n    - match: \\b(?i:(?:default\\s+)?values)\\b\n      scope: keyword.other.dml.II.sql\n    - include: distinct\n    - include: join-expressions\n    - match: \\b(?i:group\\s+by|order\\s+by|having|where)\\b\n      scope: keyword.other.dml.sql\n    - match: \\b(?i:from)\\b\n      scope: keyword.other.dml.sql\n      push: table-name-or-subquery\n    - match: \\b(?i:asc|desc)\\b\n      scope: keyword.other.order.sql\n\n  dml-delete:\n    - include: expect-table-name\n\n  dml-update:\n    - match: (?={{simple_identifier}}\\s*=)\n      pop: 1\n    - include: expect-table-name\n\n  distinct:\n    - match: \\b(?i:distinct)\\b\n      scope: keyword.other.dml.sql\n\n  join-expressions:\n    - match: \\b(?i:(?:(?:cross|inner|(?:full|left|right)(?:\\s+outer)?)\\s+)?join)\\b\n      scope: keyword.other.dml.sql\n      push:\n        - join-condition\n        - table-name-or-subquery\n\n  join-condition:\n    - match: \\b(?i:on)\\b\n      scope: keyword.control.conditional.sql\n      set: conditional-expression\n    - include: else-pop\n\n  conditional-expression:\n    - match: (?=[,;)}]|\\b(?:{{toplevel_reserved}}|{{additional_toplevel_reserved}})\\b)\n      pop: 1\n    - include: expressions\n    - include: expect-column-names\n\n  ###[ DML SET STATEMENTS ]######################################################\n\n  set-statements:\n    - match: \\b(?i:set)\\b(?!\\s*\\()\n      scope: keyword.other.dml.sql\n      push:\n        - set-meta\n        - set-target\n\n  set-meta:\n    - meta_include_prototype: false\n    - meta_scope: meta.statement.set.sql\n    - include: immediately-pop\n\n  set-target:\n    - include: else-pop\n\n  ###[ GRANT STATEMENTS ]########################################################\n\n  grant-statements:\n    - match: \\b(?i:grant(?:\\s+with\\s+grant\\s+option)?)\\b\n      scope: keyword.other.authorization.sql\n      push:\n        - grant-meta\n        - grant\n\n  grant-meta:\n    - meta_include_prototype: false\n    - meta_scope: meta.statement.grant.sql\n    - include: immediately-pop\n\n  ###[ REVOKE STATEMENTS ]#######################################################\n\n  revoke-statements:\n    - match: \\b(?i:revoke)\\b\n      scope: keyword.other.ddl.sql\n      push:\n        - revoke-meta\n        - grant\n\n  revoke-meta:\n    - meta_include_prototype: false\n    - meta_scope: meta.statement.revoke.sql\n    - include: immediately-pop\n\n  ###[ EXPLAIN STATEMENTS ]######################################################\n\n  explain-statements:\n    - match: \\b(?i:explain)\\b\n      scope: keyword.other.ddl.sql\n\n  ###[ ANALYZE STATEMENTS ]######################################################\n\n  analyze-statements:\n    - match: \\b(?i:analyze)\\b\n      scope: keyword.other.ddl.sql\n      set: expect-table-name\n\n  ###[ OTHER STATEMENTS ]########################################################\n\n  other-statements: []\n\n  ###[ EXPRESSIONS ]#############################################################\n\n  expressions-or-column-names:\n    - include: wildcard-identifiers\n    - include: expressions\n    - include: expect-column-names\n\n  expressions:\n    - include: groups\n    - include: comma-separators\n    - include: operators\n    - include: column-alias-expressions\n    - include: case-expressions\n    - include: collate-expressions\n    - include: constraint-expressions\n    - include: literals-and-variables\n    - include: function-calls\n    - include: illegal-stray-brackets\n    - include: illegal-stray-parens\n    - match: (?=;)\n      pop: 1\n\n  column-alias-expressions:\n    - match: \\b(?i:as)\\b\n      scope: keyword.operator.assignment.alias.sql\n      push: expect-column-alias-name\n\n  table-alias-expression:\n    - match: \\b(?i:as)\\b\n      scope: keyword.operator.assignment.alias.sql\n      set: expect-table-alias-name\n\n  case-expressions:\n    - match: \\b(?i:case)\\b\n      scope: keyword.control.conditional.case.sql\n      push: inside-case-expression\n\n  inside-case-expression:\n    - meta_scope: meta.statement.conditional.case.sql\n    - match: \\b(?i:end)\\b\n      scope: keyword.control.conditional.end.sql\n      pop: 1\n    - match: \\b(?i:(case)\\s+(when))\\b\n      captures:\n        1: keyword.control.conditional.case.sql\n        2: keyword.control.conditional.when.sql\n    - match: \\b(?i:when)\\b\n      scope: keyword.control.conditional.when.sql\n    - match: \\b(?i:then)\\b\n      scope: keyword.control.conditional.then.sql\n    - match: \\b(?i:else)\\b\n      scope: keyword.control.conditional.else.sql\n    - include: expressions-or-column-names\n\n  collate-expressions:\n    - match: \\b(?i:collate)\\b\n      scope: keyword.other.sql\n      push: inside-collate-expression\n\n  inside-collate-expression:\n    - match: \"{{simple_identifier}}\"\n      scope: support.constant.sql\n      pop: 1\n    - include: else-pop\n\n  constraint-expressions:\n    - match: \\b(?i:constraint)\\b\n      scope: storage.modifier.sql\n      push: expect-constraint-name\n\n  maybe-check:\n    - match: \\b(?i:check)\\b\n      scope: keyword.other.sql\n      set: maybe-group\n    - include: else-pop\n\n  maybe-column:\n    - match: \\b(?i:column)\\b\n      scope: keyword.other.ddl.sql\n      pop: 1\n    - include: else-pop\n\n  maybe-to:\n    - match: \\b(?i:to)\\b\n      scope: keyword.other.ddl.sql\n      pop: 1\n    - include: else-pop\n\n  ###[ FUNCTION EXPRESSIONS ]####################################################\n\n  function-calls:\n    - include: built-in-aggregate-function-calls\n    - include: built-in-scalar-function-calls\n    - include: built-in-user-function-calls\n    - include: user-defined-function-calls\n\n  built-in-aggregate-function-calls:\n    # List of SQL99 built-in functions from http://www.oreilly.com/catalog/sqlnut/chapter/ch04.html\n    - match: \\b(?i:AVG|COUNT|MIN|MAX|SUM)(?=\\s*\\()\n      scope: support.function.aggregate.sql\n      push: function-call-arguments\n\n  built-in-scalar-function-calls:\n    # List of SQL99 built-in functions from http://www.oreilly.com/catalog/sqlnut/chapter/ch04.html\n    - match: \\b{{builtin_scalar_functions}}\\b\n      scope: support.function.scalar.sql\n      push: function-call-arguments\n\n  built-in-user-function-calls:\n    - match: \\b{{builtin_user_functions}}\\b\n      scope: support.function.user.sql\n      push: function-call-arguments\n\n  built-in-user-function-call:\n    - match: \\b{{builtin_user_functions}}\\b\n      scope: support.function.user.sql\n      set: function-call-arguments\n\n  user-defined-function-calls:\n    - match: \\b{{simple_identifier}}(?=\\s*\\()\n      scope: support.function.sql\n      push: function-call-arguments\n\n  function-call-arguments:\n    - meta_include_prototype: false\n    - meta_scope: meta.function-call.sql\n    - match: \\(\n      scope: meta.group.sql punctuation.section.arguments.begin.sql\n      set: inside-function-call-arguments\n    - include: else-pop\n\n  inside-function-call-arguments:\n    - meta_content_scope: meta.function-call.sql meta.group.sql\n    - match: \\)\n      scope: meta.function-call.sql meta.group.sql punctuation.section.arguments.end.sql\n      pop: 1\n    - match: \",\"\n      scope: punctuation.separator.arguments.sql\n    - include: distinct\n    - include: expressions-or-column-names\n\n  ###[ GROUPS EXPRESSIONS ]######################################################\n\n  maybe-group:\n    - include: group\n    - include: else-pop\n\n  group:\n    - match: \\(\n      scope: punctuation.section.group.begin.sql\n      set: inside-group\n\n  groups:\n    - match: \\(\n      scope: punctuation.section.group.begin.sql\n      push: inside-group\n\n  inside-group:\n    - meta_scope: meta.group.sql\n    - match: \\)\n      scope: punctuation.section.group.end.sql\n      pop: 1\n    - include: sql\n\n  ###[ COLUMN EXPRESSIONS ]######################################################\n\n  expect-column-declaration:\n    - include: column-declaration-list\n    - match: (?=\\S)\n      set:\n        - maybe-column-modifier\n        - after-type\n        - expect-type\n        - column-name-declaration\n        - single-identifier\n\n  maybe-column-declaration-list:\n    - include: column-declaration-list\n    - include: else-pop\n\n  column-declaration-list:\n    - match: \\(\n      scope: punctuation.section.group.begin.sql\n      set: inside-column-declaration-list\n\n  inside-column-declaration-list:\n    - meta_scope: meta.group.table-columns.sql\n    - match: \\)\n      scope: punctuation.section.group.end.sql\n      pop: 1\n    - include: column-modifiers\n    - include: expressions\n    - include: expect-column-declarations\n\n  expect-column-declarations:\n    - match: (?=\\S)\n      push:\n        - maybe-column-modifier\n        - after-type\n        - expect-type\n        - column-name-declaration\n        - single-identifier\n\n  maybe-column-modifier:\n    - include: column-modifiers\n    - include: else-pop\n\n  column-modifiers:\n    - match: \\b(?i:check)\\b\n      scope: keyword.other.sql\n    - match: |-\n        \\b(?xi:\n          (?: (?: fulltext | primary | unique ) \\s+ )? key\n        | on \\s+ (?: delete | update ) (?: \\s+ cascade )?\n        | default\n        )\\b\n      scope: storage.modifier.sql\n    - match: |-\n        \\b(?xi:\n          foreign\\s+key\n        )\\b\n      scope: storage.modifier.sql\n      push: column-name-list\n    - match: \\b(?i:unique)\\b\n      scope: storage.modifier.sql\n      push: maybe-column-name-list\n    - match: \\b(?i:references)\\b\n      scope: storage.modifier.sql\n      push:\n        - maybe-column-name-list\n        - expect-table-name\n\n  maybe-column-name-list:\n    - include: column-name-list\n    - include: else-pop\n\n  column-name-list:\n    - match: \\(\n      scope: punctuation.section.group.begin.sql\n      set: inside-column-name-list\n\n  column-name-lists:\n    - match: \\(\n      scope: punctuation.section.group.begin.sql\n      push: inside-column-name-list\n\n  inside-column-name-list:\n    - meta_scope: meta.group.table-columns.sql\n    - match: \\)\n      scope: punctuation.section.group.end.sql\n      pop: 1\n    - include: expressions-or-column-names\n\n  ###[ FUNCTION EXPRESSIONS ]####################################################\n\n  expect-function-parameters:\n    - match: \\(\n      scope: punctuation.section.group.begin.sql\n      set: inside-function-parameters\n    - include: else-pop\n\n  inside-function-parameters:\n    - clear_scopes: 1\n    - meta_scope: meta.function.parameters.sql meta.group.sql\n    - match: \\)\n      scope: punctuation.section.group.end.sql\n      pop: 1\n    - include: comma-separators\n    - match: (?=\\S)\n      push:\n        - expect-type\n        - expect-parameter-name\n        - maybe-parameter-modifier\n\n  expect-parameter-name:\n    - match: \"{{simple_identifier}}\"\n      scope: variable.parameter.sql\n      pop: 1\n    - include: else-pop\n\n  maybe-parameter-modifier:\n    - match: \\b{{function_parameter_modifier}}\\b\n      scope: storage.modifier.sql\n      pop: 1\n    - include: else-pop\n\n  expect-function-characteristics:\n    - meta_scope: meta.function.sql\n    - match: \\b(?i:as|return)\\b\n      scope: keyword.context.block.sql\n      pop: 1\n    - match: \\b(?i:language)\\b\n      scope: storage.modifier.sql\n      push: expect-function-language-name\n    - match: \\b(?i:returns)\\b\n      scope: keyword.other.ddl.sql\n      push: expect-type\n    - include: create-common-args\n\n  expect-function-language-name:\n    - match: \"{{simple_identifier}}\"\n      scope: constant.other.language.sql\n      pop: 1\n    - include: else-pop\n\n  ###[ USER MANAGEMENT EXPRESSIONS ]#############################################\n\n  grant:\n    - match: \\b(?i:to)\\b\n      scope: keyword.context.sql\n      push: expect-user-name\n    - include: comma-separators\n    - include: user-privileges\n    - include: pop-on-top-level-reserved-word\n\n  user-privileges:\n    - include: column-name-lists\n    - match: \\b(?i:all(?:\\s+privileges)?)\\b\n      scope: constant.language.sql\n    - match: \\b(?i:(?:alter|create|drop|grant|revoke)\\s+{{ddl_target}})\\b\n      scope: constant.language.sql\n    - match: \\b(?i:select|insert|update|delete|truncate|execute)\\b\n      scope: constant.language.sql\n\n  ###[ TABLE NAMES OR SUBQUERIES ]###############################################\n\n  table-name-or-subquery:\n    - meta_include_prototype: false\n    - include: table-subquery\n    - include: table-name-or-function-call\n\n  table-subquery:\n    - match: (?=\\()\n      set:\n        - maybe-table-alias\n        - group\n\n  table-name-or-function-call:\n    - match: (?=\\S)\n      pop: 1 # pop `table-name-or-subquery` before evaluating branches\n      branch_point: table-name-or-function-call\n      branch:\n        - table-name-not-function-call\n        - table-valued-function-call\n\n  table-name-not-function-call:\n    - meta_include_prototype: false\n    - match: \"\"\n      set:\n        - maybe-table-alias\n        - table-name-fail-if-function-call\n        - table-name\n        - single-identifier\n\n  table-name-fail-if-function-call:\n    - meta_include_prototype: false\n    - match: (?=\\()\n      fail: table-name-or-function-call\n    - match: (?=\\S)\n      pop: 1\n\n  table-valued-function-call:\n    - meta_include_prototype: false\n    - match: \"\"\n      set:\n        - maybe-table-alias\n        - function-call-arguments\n        - table-valued-function-name\n        - single-identifier\n\n  table-valued-function-name:\n    - meta_include_prototype: false\n    - meta_content_scope: meta.table-valued-function-name.sql\n    - include: immediately-pop\n\n  maybe-table-alias:\n    - include: pop-on-top-level-reserved-word\n    - include: table-timespecs\n    - include: table-alias-expression\n    - include: expect-table-alias-name\n\n  table-timespecs:\n    - match: \\b(?i:for\\s+system_time)\\b\n      scope: keyword.other.dml.sql\n      push: table-timespec-args\n\n  table-timespec-args:\n    - match: \\b(?i:as\\s+of|between|and|from|to)\\b\n      scope: keyword.operator.logical.sql\n      push:\n        - table-timespec-expression\n        - table-timespec-type\n    - match: \\b(?i:all)\\b\n      scope: constant.other.sql\n      pop: 1\n    - include: else-pop\n\n  table-timespec-type:\n    - match: \\b(?i:timestamp|transaction)\\b\n      scope: storage.type.sql\n      set: else-pop\n    - include: else-pop\n\n  table-timespec-expression:\n    - include: expressions\n    - include: immediately-pop\n\n  ###[ TYPES ]###################################################################\n\n  expect-type:\n    - meta_include_prototype: false\n    - include: comments\n    - include: built-in-type\n    - include: expect-user-type\n\n  built-in-types:\n    - match: \\b(?i:enum)\\b\n      scope: storage.type.sql\n      push:\n        - after-type\n        - maybe-group\n    - match: |-\n        (?x)\n        \\b(?: {{simple_types}} | {{types_with_optional_number}} )\n        (?: ((\\()(\\d+)(?:\\s*(,)\\s*(\\d+))?(\\)) | \\b(?!\\s*\\() ) )\n      scope: storage.type.sql\n      captures:\n        1: meta.parens.sql\n        2: punctuation.definition.parens.begin.sql\n        3: meta.number.integer.decimal.sql constant.numeric.value.sql\n        4: punctuation.separator.sequence.sql\n        5: meta.number.integer.decimal.sql constant.numeric.value.sql\n        6: punctuation.definition.parens.end.sql\n      push: after-type\n    - match: \\b{{type_modifiers}}\\b\n      scope: storage.modifier.sql\n\n  built-in-type:\n    - match: \\b(?i:enum)\\b\n      scope: storage.type.sql\n      set:\n        - after-type\n        - maybe-group\n    - match: |-\n        (?x)\n        \\b(?: {{simple_types}} | {{types_with_optional_number}} )\n        (?: ((\\()(\\d+)(?:\\s*(,)\\s*(\\d+))?(\\)) | \\b(?!\\s*\\() ) )\n      scope: storage.type.sql\n      captures:\n        1: meta.parens.sql\n        2: punctuation.definition.parens.begin.sql\n        3: meta.number.integer.decimal.sql constant.numeric.value.sql\n        4: punctuation.separator.sequence.sql\n        5: meta.number.integer.decimal.sql constant.numeric.value.sql\n        6: punctuation.definition.parens.end.sql\n      set: after-type\n\n  expect-user-type:\n    - match: (?=\\S)\n      set: [maybe-group, after-type, inside-user-type]\n\n  inside-user-type:\n    # note: may contain foreign variable interpolation\n    - meta_scope: support.type.sql\n    - match: \"{{simple_identifier_break}}\"\n      pop: 1\n\n  after-type:\n    - match: \\b{{type_modifiers}}\\b\n      scope: storage.modifier.sql\n      pop: 1\n    - include: assignment-operators\n    - include: else-pop\n\n  ###[ IDENTIFIERS ]#############################################################\n\n  expect-table-alias-name:\n    # prevent prototypes from inheriting syntaxes\n    - meta_include_prototype: false\n    - include: comments\n    - match: (?=\\S)\n      set: [table-alias-name, single-identifier]\n\n  expect-column-alias-name:\n    # prevent prototypes from inheriting syntaxes\n    - meta_include_prototype: false\n    - include: comments\n    - match: (?=\\S)\n      set: [column-alias-name, single-identifier]\n\n  table-alias-name:\n    - meta_include_prototype: false\n    - meta_content_scope: meta.alias.table.sql\n    - include: immediately-pop\n\n  column-alias-name:\n    - meta_include_prototype: false\n    - meta_content_scope: meta.alias.column.sql\n    - include: immediately-pop\n\n  expect-column-names:\n    - match: (?=\\S)\n      push: [maybe-operator, column-name, single-identifier]\n\n  expect-column-name:\n    # prevent prototypes from inheriting syntaxes\n    - meta_include_prototype: false\n    - include: comments\n    - match: (?=\\S)\n      set: [column-name, single-identifier]\n\n  column-name:\n    - meta_include_prototype: false\n    - meta_content_scope: meta.column-name.sql\n    - include: immediately-pop\n\n  expect-column-name-declaration:\n    # prevent prototypes from inheriting syntaxes\n    - meta_include_prototype: false\n    - include: comments\n    - match: (?=\\S)\n      set: [column-name-declaration, single-identifier]\n\n  column-name-declaration:\n    - meta_include_prototype: false\n    - meta_content_scope: meta.column-name.sql variable.other.member.declaration.sql\n    - include: immediately-pop\n\n  expect-constraint-name:\n    # prevent prototypes from inheriting syntaxes\n    - meta_include_prototype: false\n    - match: (?=(?i:check|foreign|primary|unique|index|key|using|with)\\b)\n      pop: 1\n    - include: comments\n    - match: (?=\\S)\n      set: [constraint-name, single-identifier]\n\n  constraint-name:\n    - meta_include_prototype: false\n    - meta_content_scope: meta.constraint-name.sql\n    - include: immediately-pop\n\n  expect-database-creation-name:\n    # prevent prototypes from inheriting syntaxes\n    - meta_include_prototype: false\n    - include: comments\n    - match: (?=\\S)\n      set: [database-creation-name, single-identifier]\n\n  database-creation-name:\n    - meta_include_prototype: false\n    - meta_content_scope: entity.name.struct.database.sql\n    - include: immediately-pop\n\n  expect-database-name:\n    # prevent prototypes from inheriting syntaxes\n    - meta_include_prototype: false\n    - include: comments\n    - match: (?=\\S)\n      set: [database-name, single-identifier]\n\n  database-name:\n    - meta_include_prototype: false\n    - meta_content_scope: meta.database-name.sql\n    - include: immediately-pop\n\n  expect-event-creation-name:\n    # prevent prototypes from inheriting syntaxes\n    - meta_include_prototype: false\n    - include: comments\n    - match: (?=\\S)\n      set: [event-creation-name, single-identifier]\n\n  event-creation-name:\n    - meta_include_prototype: false\n    - meta_scope: entity.name.event.sql\n    - include: immediately-pop\n\n  expect-event-name:\n    # prevent prototypes from inheriting syntaxes\n    - meta_include_prototype: false\n    - include: comments\n    - match: (?=\\S)\n      set: [event-name, single-identifier]\n\n  event-name:\n    - meta_include_prototype: false\n    - meta_scope: meta.event-name.sql\n    - include: immediately-pop\n\n  expect-index-creation-name:\n    # prevent prototypes from inheriting syntaxes\n    - meta_include_prototype: false\n    - include: comments\n    - match: (?=\\S)\n      set: [index-creation-name, single-identifier]\n\n  index-creation-name:\n    - meta_include_prototype: false\n    - meta_scope: entity.name.struct.index.sql\n    - include: immediately-pop\n\n  expect-index-names:\n    - match: (?=\\S)\n      push: [index-name, single-identifier]\n\n  expect-index-name:\n    # prevent prototypes from inheriting syntaxes\n    - meta_include_prototype: false\n    - include: comments\n    - match: (?=\\S)\n      set: [index-name, single-identifier]\n\n  index-name:\n    - meta_include_prototype: false\n    - meta_scope: meta.index-name.sql\n    - include: immediately-pop\n\n  expect-function-creation-name:\n    # prevent prototypes from inheriting syntaxes\n    - meta_include_prototype: false\n    - include: comments\n    - match: (?=\\S)\n      set: [procedure-creation-name, single-identifier]\n\n  expect-partition-creation-name:\n    # prevent prototypes from inheriting syntaxes\n    - meta_include_prototype: false\n    - include: comments\n    - match: (?=\\S)\n      set: [partition-creation-name, single-identifier]\n\n  partition-creation-name:\n    - meta_include_prototype: false\n    - meta_scope: entity.name.struct.partition.sql\n    - include: immediately-pop\n\n  expect-partition-names:\n    - match: (?=\\S)\n      push: [partition-name, single-identifier]\n\n  expect-partition-name:\n    # prevent prototypes from inheriting syntaxes\n    - meta_include_prototype: false\n    - include: comments\n    - match: (?=\\S)\n      set: [partition-name, single-identifier]\n\n  partition-name:\n    - meta_include_prototype: false\n    - meta_scope: meta.partition-name.sql\n    - include: immediately-pop\n\n  procedure-creation-name:\n    - meta_include_prototype: false\n    - meta_content_scope: entity.name.function.sql\n    - include: immediately-pop\n\n  expect-function-name:\n    # prevent prototypes from inheriting syntaxes\n    - meta_include_prototype: false\n    - include: comments\n    - match: (?=\\S)\n      set: [procedure-name, single-identifier]\n\n  procedure-name:\n    - meta_include_prototype: false\n    - meta_content_scope: meta.procedure-name.sql\n    - include: immediately-pop\n\n  expect-table-creation-name:\n    # prevent prototypes from inheriting syntaxes\n    - meta_include_prototype: false\n    - include: comments\n    - match: (?=\\S)\n      set: [table-creation-name, single-identifier]\n\n  table-creation-name:\n    - meta_include_prototype: false\n    - meta_scope: entity.name.struct.table.sql\n    - include: immediately-pop\n\n  expect-table-names:\n    - match: (?=\\S)\n      push: [table-name, single-identifier]\n\n  expect-table-name:\n    # prevent prototypes from inheriting syntaxes\n    - meta_include_prototype: false\n    - include: comments\n    - match: (?=\\S)\n      set: [table-name, single-identifier]\n\n  table-name:\n    - meta_include_prototype: false\n    - meta_content_scope: meta.table-name.sql\n    - include: immediately-pop\n\n  expect-user-name:\n    # prevent prototypes from inheriting syntaxes\n    - meta_include_prototype: false\n    - include: comments\n    - include: built-in-user-function-call\n    - match: (?=\\S)\n      set: [user-name, single-identifier]\n\n  user-name:\n    - meta_include_prototype: false\n    - meta_content_scope: meta.username.sql\n    - include: immediately-pop\n\n  expect-user-name-declaration:\n    # prevent prototypes from inheriting syntaxes\n    - meta_include_prototype: false\n    - include: comments\n    - match: (?=\\S)\n      set: [user-name-declaration, single-identifier]\n\n  user-name-declaration:\n    - meta_include_prototype: false\n    - meta_content_scope: entity.name.user.sql\n    - include: immediately-pop\n\n  expect-type-creation-name:\n    # prevent prototypes from inheriting syntaxes\n    - meta_include_prototype: false\n    - include: comments\n    - match: (?=\\S)\n      set: [type-creation-name, single-identifier]\n\n  type-creation-name:\n    - meta_include_prototype: false\n    - meta_scope: entity.name.type.cql\n    - include: immediately-pop\n\n  expect-other-creation-name:\n    # prevent prototypes from inheriting syntaxes\n    - meta_include_prototype: false\n    - include: comments\n    - match: (?=\\S)\n      set: [other-creation-name, single-identifier]\n\n  other-creation-name:\n    - meta_include_prototype: false\n    - meta_scope: entity.name.struct.other.sql\n    - include: immediately-pop\n\n  expect-other-name:\n    # prevent prototypes from inheriting syntaxes\n    - meta_include_prototype: false\n    - include: comments\n    - match: (?=\\S)\n      set: [other-name, single-identifier]\n\n  other-name:\n    - meta_include_prototype: false\n    - meta_scope: meta.other-name.sql\n    - include: immediately-pop\n\n  single-identifier:\n    - meta_include_prototype: false\n    - include: pop-on-top-level-reserved-word\n    - match: \"\"\n      set:\n        - maybe-identifier-accessor\n        - identifier-part\n\n  maybe-identifier-accessor:\n    - meta_include_prototype: false\n    - match: \\s*(\\.)\\s*(\\*)\n      captures:\n        1: punctuation.accessor.dot.sql\n        2: constant.other.wildcard.asterisk.sql\n      pop: 1\n    - match: \\s*(\\.)\n      captures:\n        1: punctuation.accessor.dot.sql\n      set: single-identifier\n    - include: immediately-pop\n\n  identifier-part:\n    - meta_include_prototype: false\n    - include: backtick-quoted-identifier-part\n    - include: double-quoted-identifier-part\n    - include: single-quoted-identifier-part\n    - include: simple-identifier-part\n\n  backtick-quoted-identifier-part:\n    - match: \\`\n      scope: punctuation.definition.identifier.begin.sql\n      set: inside-backtick-quoted-identifier-part\n\n  inside-backtick-quoted-identifier-part:\n    # note: may contain foreign variable interpolation\n    - match: \\`\n      scope: punctuation.definition.identifier.end.sql\n      pop: 1\n\n  double-quoted-identifier-part:\n    - match: \\\"\n      scope: punctuation.definition.identifier.begin.sql\n      set: inside-double-quoted-identifier-part\n\n  inside-double-quoted-identifier-part:\n    # note: may contain foreign variable interpolation\n    - match: \\\"\n      scope: punctuation.definition.identifier.end.sql\n      pop: 1\n\n  single-quoted-identifier-part:\n    - match: \\'\n      scope: punctuation.definition.identifier.begin.sql\n      set: inside-single-quoted-identifier-part\n\n  inside-single-quoted-identifier-part:\n    # note: may contain foreign variable interpolation\n    - match: \\'\n      scope: punctuation.definition.identifier.end.sql\n      pop: 1\n\n  simple-identifier-part:\n    - match: (?=\\S)\n      set: inside-simple-identifier-part\n\n  inside-simple-identifier-part:\n    # note: may contain foreign variable interpolation\n    - match: \"{{simple_identifier_break}}\"\n      pop: 1\n\n  wildcard-identifiers:\n    - match: \\*\n      scope: constant.other.wildcard.asterisk.sql\n\n  ###[ LITERALS ]################################################################\n\n  literals-and-variables:\n    - include: built-in-types\n    - include: constants\n    - include: numbers\n    - include: strings\n\n  constants:\n    - match: \\b(?i:null)\\b\n      scope: constant.language.null.sql\n\n  booleans:\n    - match: \\b(?i:true)\\b\n      scope: constant.language.boolean.true.sql\n    - match: \\b(?i:false)\\b\n      scope: constant.language.boolean.false.sql\n\n  numbers:\n    - match: \\b\\d+(\\.)\\d+\\b\n      scope: meta.number.float.decimal.sql constant.numeric.value.sql\n      captures:\n        1: punctuation.separator.decimal.sql\n    - match: \\b\\d+\\b\n      scope: meta.number.integer.decimal.sql constant.numeric.value.sql\n\n  strings:\n    - match: \\'\n      scope: punctuation.definition.string.begin.sql\n      push: inside-single-quoted-string\n\n  inside-single-quoted-string:\n    - meta_include_prototype: false\n    - meta_scope: meta.string.sql string.quoted.single.sql\n    - match: \\'\\'\n      scope: constant.character.escape.sql\n    - match: \\'\n      scope: punctuation.definition.string.end.sql\n      pop: 1\n    - include: string-escapes\n\n  string-escapes:\n    - match: \"{{string_escape}}\"\n      scope: constant.character.escape.sql\n\n  ###[ LIKE EXPRESSIONS ]########################################################\n\n  like-expressions:\n    - match: \\b(?i:like)\\b\n      scope: keyword.operator.logical.sql\n      branch_point: like-expressions\n      branch:\n        - like-string-not-followed-by-escape\n        - like-string-followed-by-escape-slash\n        - like-string-followed-by-escape-caret\n        - like-string-followed-by-escape-hash\n        - like-string-followed-by-unknown-escape\n\n  like-string-not-followed-by-escape:\n    - meta_include_prototype: false\n    - match: \\'\n      scope: punctuation.definition.string.begin.sql\n      set:\n        - like-escape-fail\n        - inside-like-single-quoted-string\n    - include: else-pop\n\n  like-string-followed-by-escape-slash:\n    - meta_include_prototype: false\n    - match: \\'\n      scope: punctuation.definition.string.begin.sql\n      set:\n        - like-escape-character-slash\n        - like-escape\n        - inside-like-single-quoted-string-slash-escape\n    - include: else-pop\n\n  like-string-followed-by-escape-caret:\n    - meta_include_prototype: false\n    - match: \\'\n      scope: punctuation.definition.string.begin.sql\n      set:\n        - like-escape-character-caret\n        - like-escape\n        - inside-like-single-quoted-string-caret-escape\n    - include: else-pop\n\n  like-string-followed-by-escape-hash:\n    - meta_include_prototype: false\n    - match: \\'\n      scope: punctuation.definition.string.begin.sql\n      set:\n        - like-escape-character-hash\n        - like-escape\n        - inside-like-single-quoted-string-hash-escape\n    - include: else-pop\n\n  like-string-followed-by-unknown-escape:\n    - meta_include_prototype: false\n    - match: \\'\n      scope: punctuation.definition.string.begin.sql\n      set:\n        - like-escape-character-any\n        - like-escape\n        - inside-like-single-quoted-string\n    - include: else-pop\n\n  inside-like-single-quoted-string-slash-escape:\n    - meta_include_prototype: false\n    - meta_scope: meta.string.like.sql string.quoted.single.sql\n    - match: \\\\.\n      scope: constant.character.escape.sql\n    - include: inside-like-single-quoted-string\n\n  inside-like-single-quoted-string-caret-escape:\n    - meta_include_prototype: false\n    - meta_scope: meta.string.like.sql string.quoted.single.sql\n    - match: \\^.\n      scope: constant.character.escape.sql\n    - include: inside-like-single-quoted-string\n\n  inside-like-single-quoted-string-hash-escape:\n    - meta_include_prototype: false\n    - meta_scope: meta.string.like.sql string.quoted.single.sql\n    - match: \"#.\"\n      scope: constant.character.escape.sql\n    - include: inside-like-single-quoted-string\n\n  inside-like-single-quoted-string:\n    - meta_include_prototype: false\n    - meta_scope: meta.string.like.sql string.quoted.single.sql\n    - match: \\'\n      scope: punctuation.definition.string.end.sql\n      pop: 1\n    - match: \"%\"\n      scope: constant.other.wildcard.percent.sql\n    - match: \"_\"\n      scope: constant.other.wildcard.underscore.sql\n\n  like-else-fail:\n    - match: (?=\\S)\n      fail: like-expressions\n\n  like-escape-fail:\n    - match: \\b(?i:escape)\\b\n      fail: like-expressions\n    - include: else-pop\n\n  like-escape:\n    - match: \\b(?i:escape)\\b\n      scope: keyword.operator.word.sql\n      pop: 1\n    - include: else-pop\n\n  like-escape-character-any:\n    - meta_include_prototype: false\n    - match: (\\')([^'])(\\')\n      scope: meta.string.escape.sql string.quoted.single.sql\n      captures:\n        1: punctuation.definition.string.begin.sql\n        2: constant.character.escape.sql\n        3: punctuation.definition.string.end.sql\n      pop: 1\n    - include: else-pop\n\n  like-escape-character-caret:\n    - meta_include_prototype: false\n    - match: (\\')(\\^)(\\')\n      scope: meta.string.escape.sql string.quoted.single.sql\n      captures:\n        1: punctuation.definition.string.begin.sql\n        2: constant.character.escape.sql\n        3: punctuation.definition.string.end.sql\n      pop: 1\n    - include: like-else-fail\n\n  like-escape-character-slash:\n    - meta_include_prototype: false\n    - match: (\\')(\\\\)(\\')\n      scope: meta.string.escape.sql string.quoted.single.sql\n      captures:\n        1: punctuation.definition.string.begin.sql\n        2: constant.character.escape.sql\n        3: punctuation.definition.string.end.sql\n      pop: 1\n    - include: like-else-fail\n\n  like-escape-character-hash:\n    - meta_include_prototype: false\n    - match: (\\')(#)(\\')\n      scope: meta.string.escape.sql string.quoted.single.sql\n      captures:\n        1: punctuation.definition.string.begin.sql\n        2: constant.character.escape.sql\n        3: punctuation.definition.string.end.sql\n      pop: 1\n    - include: like-else-fail\n\n  ###[ OPERATORS ]###############################################################\n\n  maybe-condition:\n    - meta_include_prototype: false\n    - include: conditions\n    - include: else-pop\n\n  conditions:\n    - match: \\b(?i:if)\\b\n      scope: keyword.control.conditional.if.sql\n    - include: logical-operators\n\n  maybe-operator:\n    - meta_include_prototype: false\n    - match: \"<=>|[!<>]?=|<>|<|>\"\n      scope: keyword.operator.comparison.sql\n      pop: 1\n    - match: \"[-+/*]\"\n      scope: keyword.operator.arithmetic.sql\n      pop: 1\n    - match: \\b(?i:and|or|having|exists|between|in|not|is)\\b\n      scope: keyword.operator.logical.sql\n      pop: 1\n    - include: assignment-operators\n    - include: else-pop\n\n  operators:\n    - match: \"<=>|[!<>]?=|<>|<|>\"\n      scope: keyword.operator.comparison.sql\n    - match: \"[-+/*]\"\n      scope: keyword.operator.arithmetic.sql\n    - include: logical-operators\n\n  assignment-operators:\n    - match: \"=\"\n      scope: keyword.operator.assignment.sql\n\n  logical-operators:\n    - match: \\b(?i:and|or|having|exists|between|in|not|is)\\b\n      scope: keyword.operator.logical.sql\n\n  comma-separators:\n    - match: \",\"\n      scope: punctuation.separator.sequence.sql\n\n  statement-terminators:\n    - match: \";\"\n      scope: punctuation.terminator.statement.sql\n\n  ###[ ILLEGALS ]################################################################\n\n  illegal-stray-brackets:\n    - match: \\]\n      scope: invalid.illegal.stray.sql\n\n  illegal-stray-parens:\n    - match: \\)\n      scope: invalid.illegal.stray.sql\n\n  ###[ PROTOTYPES ]##############################################################\n\n  else-pop:\n    - match: (?=\\S)\n      pop: 1\n\n  immediately-pop:\n    - match: \"\"\n      pop: 1\n\n  pop-on-top-level-reserved-word:\n    - match: (?=[;)}]|\\b(?:{{toplevel_reserved}}|{{additional_toplevel_reserved}})\\b)\n      pop: 1\n"
  },
  {
    "path": "cli/app.rs",
    "content": "use crate::{\n    commands::{\n        args::{EchoMode, HeadersMode, ParameterArgs, ParameterCommand, TimerMode},\n        import::ImportFile,\n        Command, CommandParser,\n    },\n    config::Config,\n    helper::LimboHelper,\n    input::{\n        get_io, get_writer, ApplyWriter, DbLocation, NoopProgress, OutputMode, ProgressSink,\n        Settings, StderrProgress,\n    },\n    manual,\n    opcodes_dictionary::OPCODE_DESCRIPTIONS,\n    read_state_machine::ReadState,\n    HISTORY_FILE,\n};\nuse anyhow::{anyhow, Context};\nuse clap::Parser;\nuse comfy_table::{Attribute, Cell, CellAlignment, ContentArrangement, Row, Table};\nuse rustyline::{error::ReadlineError, history::DefaultHistory, Editor};\nuse std::num::NonZeroUsize;\nuse std::{\n    fs::File,\n    io::{self, BufRead, BufReader, IsTerminal, Write},\n    mem::{forget, ManuallyDrop},\n    path::PathBuf,\n    sync::{\n        atomic::{AtomicUsize, Ordering},\n        Arc,\n    },\n    time::{Duration, Instant},\n};\n\nuse tracing_appender::non_blocking::WorkerGuard;\nuse tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};\nuse turso_core::{\n    io_error, Connection, Database, LimboError, Numeric, OpenFlags, QueryMode, Statement, Value,\n};\n\n#[derive(Parser, Debug)]\n#[command(name = \"Turso\")]\n#[command(author, version, about, long_about = None)]\npub struct Opts {\n    #[clap(index = 1, help = \"SQLite database file\", default_value = \":memory:\")]\n    pub database: Option<PathBuf>,\n    #[clap(index = 2, help = \"Optional SQL command to execute\")]\n    pub sql: Option<String>,\n    #[clap(short = 'm', long, default_value_t = OutputMode::Pretty)]\n    pub output_mode: OutputMode,\n    #[clap(short, long, default_value = \"\")]\n    pub output: String,\n    #[clap(\n        short,\n        long,\n        help = \"don't display program information on start\",\n        default_value_t = false\n    )]\n    pub quiet: bool,\n    #[clap(short, long, help = \"Print commands before execution\")]\n    pub echo: bool,\n    #[clap(\n        short = 'v',\n        long,\n        help = \"Select VFS. options are io_uring (if feature enabled), experimental_win_iocp (if feature enabled on windows), memory, and syscall\"\n    )]\n    pub vfs: Option<String>,\n    #[clap(long, help = \"Open the database in read-only mode\")]\n    pub readonly: bool,\n    #[clap(long, help = \"Enable experimental views feature\")]\n    pub experimental_views: bool,\n    #[clap(\n        long,\n        help = \"Enable experimental custom types (CREATE TYPE / DROP TYPE)\"\n    )]\n    pub experimental_custom_types: bool,\n    #[clap(short = 't', long, help = \"specify output file for log traces\")]\n    pub tracing_output: Option<String>,\n    #[clap(long, help = \"Start MCP server instead of interactive shell\")]\n    pub mcp: bool,\n    #[clap(\n        long,\n        help = \"Start sync server instead of interactive shell and listen at given address (e.g. 0.0.0.0:8080)\"\n    )]\n    pub sync_server: Option<String>,\n    #[clap(long, help = \"Enable experimental encryption feature\")]\n    pub experimental_encryption: bool,\n    #[clap(long, help = \"Enable experimental index method feature\")]\n    pub experimental_index_method: bool,\n    #[clap(long, help = \"Enable experimental autovacuum feature\")]\n    pub experimental_autovacuum: bool,\n    #[clap(long, help = \"Enable experimental attach feature\")]\n    pub experimental_attach: bool,\n    #[cfg(feature = \"mvcc_repl\")]\n    #[clap(long, help = \"Start MVCC concurrent transaction harness\")]\n    pub mvcc: bool,\n    #[clap(\n        long,\n        help = \"Enable unsafe testing features (e.g. sqlite_dbpage writes)\"\n    )]\n    pub unsafe_testing: bool,\n}\n\nconst PROMPT: &str = \"turso> \";\n\npub struct Limbo {\n    pub prompt: String,\n    io: Arc<dyn turso_core::IO>,\n    writer: Option<Box<dyn Write>>,\n    conn: Arc<turso_core::Connection>,\n    pub interrupt_count: Arc<AtomicUsize>,\n    input_buff: ManuallyDrop<String>,\n    pub(crate) opts: Settings,\n    db_opts: turso_core::DatabaseOpts,\n    read_state: ReadState,\n    pub rl: Option<Editor<LimboHelper, DefaultHistory>>,\n    config: Option<Config>,\n    had_query_error: bool,\n    parameter_bindings: Vec<ParameterBinding>,\n}\n\n#[derive(Clone)]\nstruct ParameterBinding {\n    name: Box<str>,\n    index: Option<NonZeroUsize>,\n    value: Value,\n}\n\nstruct QueryStatistics {\n    io_time_elapsed_samples: Vec<Duration>,\n    execute_time_elapsed_samples: Vec<Duration>,\n}\n\n/// A lending iterator over query result rows with optional statistics tracking.\nstruct RowStepper<'a> {\n    rows: &'a mut Statement,\n    stats: Option<std::cell::RefCell<&'a mut QueryStatistics>>,\n}\n\nimpl<'a> RowStepper<'a> {\n    fn new(rows: &'a mut Statement, stats: Option<&'a mut QueryStatistics>) -> Self {\n        Self {\n            rows,\n            stats: stats.map(std::cell::RefCell::new),\n        }\n    }\n\n    /// Advances to the next row, returning it if available.\n    /// Returns Ok(Some(row)) for each row, Ok(None) when done, or Err on failure.\n    fn next_row(&mut self) -> Result<Option<&turso_core::Row>, LimboError> {\n        let execution_time = std::cell::Cell::new(Instant::now());\n        let io_time = std::cell::Cell::new(Instant::now());\n\n        let result = self.rows.run_one_step_blocking(\n            || {\n                // Push execution sample to not count IO time in execution\n                if let Some(stats) = self.stats.as_ref() {\n                    stats\n                        .borrow_mut()\n                        .execute_time_elapsed_samples\n                        .push(execution_time.get().elapsed());\n                }\n                // Start io timer\n                io_time.set(Instant::now());\n                Ok(())\n            },\n            || {\n                // Push sample when we end IO\n                if let Some(stats) = self.stats.as_ref() {\n                    stats\n                        .borrow_mut()\n                        .io_time_elapsed_samples\n                        .push(io_time.get().elapsed());\n                }\n                // Restart Execution timer\n                execution_time.set(Instant::now());\n                Ok(())\n            },\n        );\n\n        match result {\n            Ok(row_opt) => {\n                if let Some(stats) = self.stats.as_ref() {\n                    stats\n                        .borrow_mut()\n                        .execute_time_elapsed_samples\n                        .push(execution_time.get().elapsed());\n                }\n                Ok(row_opt)\n            }\n            Err(e) => {\n                if let Some(stats) = self.stats.as_ref() {\n                    stats\n                        .borrow_mut()\n                        .execute_time_elapsed_samples\n                        .push(execution_time.get().elapsed());\n                }\n                Err(e)\n            }\n        }\n    }\n}\n\n/// metadata from db, fetched from pragmas\nstruct DbMetadata {\n    page_size: i64,\n    page_count: i64,\n    filename: String,\n}\n\nstruct DbPage<'a> {\n    pgno: i64,\n    data: &'a [u8],\n}\n\nimpl Limbo {\n    pub fn new() -> anyhow::Result<(Self, WorkerGuard)> {\n        let mut opts = Opts::parse();\n        let guard = Self::init_tracing(&opts)?;\n\n        let db_file = opts\n            .database\n            .as_ref()\n            .map_or(\":memory:\".to_string(), |p| p.to_string_lossy().to_string());\n\n        let db_opts = turso_core::DatabaseOpts::new()\n            .with_views(opts.experimental_views)\n            .with_custom_types(opts.experimental_custom_types)\n            .with_encryption(opts.experimental_encryption)\n            .with_index_method(opts.experimental_index_method)\n            .with_autovacuum(opts.experimental_autovacuum)\n            .with_attach(opts.experimental_attach)\n            .with_unsafe_testing(opts.unsafe_testing);\n\n        let db_file = normalize_db_path(db_file);\n\n        let (io, conn) = if db_file.starts_with(\"file:\") {\n            Connection::from_uri(&db_file, db_opts)?\n        } else {\n            let flags = if opts.readonly {\n                OpenFlags::default().union(OpenFlags::ReadOnly)\n            } else {\n                OpenFlags::default()\n            };\n            let (io, db) = Database::open_new(\n                &db_file,\n                opts.vfs.as_ref(),\n                flags,\n                db_opts.turso_cli(),\n                None,\n            )?;\n            let conn = db.connect()?;\n            (io, conn)\n        };\n        unsafe {\n            let mut ext_api = conn._build_turso_ext();\n            if !limbo_completion::register_extension_static(&mut ext_api).is_ok() {\n                return Err(anyhow!(\n                    \"Failed to register completion extension\".to_string()\n                ));\n            }\n            conn._free_extension_ctx(ext_api);\n        }\n        let interrupt_count = Arc::new(AtomicUsize::new(0));\n        {\n            let interrupt_count: Arc<AtomicUsize> = Arc::clone(&interrupt_count);\n            ctrlc::set_handler(move || {\n                // Increment the interrupt count on Ctrl-C\n                interrupt_count.fetch_add(1, Ordering::Release);\n            })\n            .expect(\"Error setting Ctrl-C handler\");\n        }\n        let sql = opts.sql.take();\n        let has_sql = sql.is_some();\n        let quiet = opts.quiet || !IsTerminal::is_terminal(&std::io::stdin());\n        let config = Config::for_output_mode(opts.output_mode);\n        let mut app = Self {\n            prompt: PROMPT.to_string(),\n            io,\n            writer: Some(get_writer(&opts.output)),\n            conn,\n            interrupt_count,\n            input_buff: ManuallyDrop::new(sql.unwrap_or_default()),\n            read_state: ReadState::default(),\n            opts: Settings::from(opts),\n            db_opts,\n            rl: None,\n            config: Some(config),\n            had_query_error: false,\n            parameter_bindings: Vec::new(),\n        };\n        app.first_run(has_sql, quiet)?;\n        Ok((app, guard))\n    }\n\n    pub fn with_config(mut self, config: Config) -> Self {\n        self.config = Some(config);\n        self\n    }\n\n    pub fn with_readline(mut self, mut rl: Editor<LimboHelper, DefaultHistory>) -> Self {\n        let h = LimboHelper::new(\n            self.conn.clone(),\n            self.config.as_ref().map(|c| c.highlight.clone()),\n        );\n        rl.set_helper(Some(h));\n        self.rl = Some(rl);\n        self\n    }\n\n    fn first_run(&mut self, has_sql: bool, quiet: bool) -> Result<(), LimboError> {\n        // Skip startup messages and SQL execution in MCP/SyncServer mode\n        if self.is_mcp_mode() || self.is_sync_server_mode() {\n            return Ok(());\n        }\n\n        if has_sql {\n            self.handle_first_input()?;\n        }\n        if !quiet {\n            self.writeln_fmt(format_args!(\"Turso v{}\", env!(\"CARGO_PKG_VERSION\")))\n                .map_err(|e| io_error(e, \"write\"))?;\n            self.writeln(\"Enter \\\".help\\\" for usage hints.\")\n                .map_err(|e| io_error(e, \"write\"))?;\n\n            // Add random feature hint\n            if let Some(hint) = manual::get_random_feature_hint() {\n                self.writeln(&hint).map_err(|e| io_error(e, \"write\"))?;\n            }\n\n            self.writeln(\n                \"This software is in BETA, use caution with production data and ensure you have backups.\"\n            ).map_err(|e| io_error(e, \"write\"))?;\n            self.display_in_memory().map_err(|e| io_error(e, \"write\"))?;\n        }\n        Ok(())\n    }\n\n    fn handle_first_input(&mut self) -> Result<(), LimboError> {\n        self.consume(true);\n        self.close_conn()?;\n        std::process::exit(i32::from(self.had_query_error));\n    }\n\n    fn set_multiline_prompt(&mut self) {\n        self.prompt = match self.input_buff.chars().fold(0, |acc, c| match c {\n            '(' => acc + 1,\n            ')' => acc - 1,\n            _ => acc,\n        }) {\n            n if n < 0 => String::from(\")x!...>\"),\n            0 => String::from(\"   ...> \"),\n            n if n < 10 => format!(\"(x{n}...> \"),\n            _ => String::from(\"(.....> \"),\n        };\n    }\n\n    #[cfg(not(target_family = \"wasm\"))]\n    fn handle_load_extension(&mut self, path: &str) -> Result<(), String> {\n        let ext_path = turso_core::resolve_ext_path(path).map_err(|e| e.to_string())?;\n        self.conn\n            .load_extension(ext_path)\n            .map_err(|e| e.to_string())\n    }\n\n    fn display_in_memory(&mut self) -> io::Result<()> {\n        if self.opts.db_file == \":memory:\" {\n            self.writeln(\"Connected to a transient in-memory database.\")?;\n            self.writeln(\"Use \\\".open FILENAME\\\" to reopen on a persistent database\")?;\n        }\n        Ok(())\n    }\n\n    fn show_info(&mut self) -> io::Result<()> {\n        let opts = format!(\"{}\", self.opts);\n        self.writeln(opts)\n    }\n\n    fn display_stats(&mut self, args: crate::commands::args::StatsArgs) -> io::Result<()> {\n        use crate::commands::args::StatsToggle;\n\n        // Handle on/off toggle\n        if let Some(toggle) = args.toggle {\n            match toggle {\n                StatsToggle::On => {\n                    self.opts.stats = true;\n                    self.writeln(\"Stats display enabled.\")?;\n                }\n                StatsToggle::Off => {\n                    self.opts.stats = false;\n                    self.writeln(\"Stats display disabled.\")?;\n                }\n            }\n            return Ok(());\n        }\n\n        // Display all metrics\n        let output = {\n            let metrics = self.conn.metrics.read();\n            format!(\"{metrics}\")\n        };\n\n        self.writeln(output)?;\n\n        if args.reset {\n            self.conn.metrics.write().reset();\n            self.writeln(\"Statistics reset.\")?;\n        }\n\n        Ok(())\n    }\n\n    pub fn reset_input(&mut self) {\n        self.prompt = PROMPT.to_string();\n        self.input_buff.clear();\n        self.read_state = ReadState::default();\n    }\n\n    pub fn close_conn(&mut self) -> Result<(), LimboError> {\n        self.conn.close()\n    }\n\n    pub fn get_connection(&self) -> Arc<turso_core::Connection> {\n        self.conn.clone()\n    }\n\n    pub fn is_mcp_mode(&self) -> bool {\n        self.opts.mcp\n    }\n\n    pub fn is_sync_server_mode(&self) -> bool {\n        self.opts.sync_server_address.is_some()\n    }\n\n    pub fn get_interrupt_count(&self) -> Arc<AtomicUsize> {\n        self.interrupt_count.clone()\n    }\n\n    pub fn has_query_error(&self) -> bool {\n        self.had_query_error\n    }\n\n    fn toggle_echo(&mut self, arg: EchoMode) {\n        match arg {\n            EchoMode::On => self.opts.echo = true,\n            EchoMode::Off => self.opts.echo = false,\n        }\n    }\n\n    fn open_db(&mut self, path: &str, vfs_name: Option<&str>) -> anyhow::Result<()> {\n        self.conn.close()?;\n        let (io, db) = if let Some(vfs_name) = vfs_name {\n            self.conn.open_new(path, vfs_name)?\n        } else {\n            let io = {\n                match path {\n                    \":memory:\" => get_io(DbLocation::Memory, &self.opts.io.to_string())?,\n                    _path => get_io(DbLocation::Path, &self.opts.io.to_string())?,\n                }\n            };\n            (\n                io.clone(),\n                Database::open_file_with_flags(\n                    io.clone(),\n                    path,\n                    OpenFlags::default(),\n                    self.db_opts,\n                    None,\n                )?,\n            )\n        };\n        self.io = io;\n        self.conn = db.connect()?;\n        self.opts.db_file = path.to_string();\n        Ok(())\n    }\n\n    fn set_output_file(&mut self, path: &str) -> Result<(), String> {\n        if path.is_empty() || path.trim().eq_ignore_ascii_case(\"stdout\") {\n            self.set_output_stdout();\n            return Ok(());\n        }\n        match std::fs::File::create(path) {\n            Ok(file) => {\n                self.writer = Some(Box::new(file));\n                self.opts.is_stdout = false;\n                self.opts.output_mode = OutputMode::List;\n                self.opts.output_filename = path.to_string();\n                Ok(())\n            }\n            Err(e) => Err(e.to_string()),\n        }\n    }\n\n    fn set_output_stdout(&mut self) {\n        let _ = self.writer.as_mut().unwrap().flush();\n        self.writer = Some(Box::new(io::stdout()));\n        self.opts.is_stdout = true;\n    }\n\n    fn set_mode(&mut self, mode: OutputMode) -> Result<(), String> {\n        if mode == OutputMode::Pretty && !self.opts.is_stdout {\n            Err(\"pretty output can only be written to a tty\".to_string())\n        } else {\n            self.opts.output_mode = mode;\n            Ok(())\n        }\n    }\n\n    fn write_fmt(&mut self, fmt: std::fmt::Arguments) -> io::Result<()> {\n        self.writer.as_mut().unwrap().write_fmt(fmt)\n    }\n\n    fn writeln_fmt(&mut self, fmt: std::fmt::Arguments) -> io::Result<()> {\n        self.writer.as_mut().unwrap().write_fmt(fmt)?;\n        self.writer.as_mut().unwrap().write_all(b\"\\n\")\n    }\n\n    fn write<D: AsRef<[u8]>>(&mut self, data: D) -> io::Result<()> {\n        self.writer.as_mut().unwrap().write_all(data.as_ref())\n    }\n\n    fn writeln<D: AsRef<[u8]>>(&mut self, data: D) -> io::Result<()> {\n        self.writer.as_mut().unwrap().write_all(data.as_ref())?;\n        self.writer.as_mut().unwrap().write_all(b\"\\n\")\n    }\n\n    fn run_query(&mut self, input: &str) {\n        let echo = self.opts.echo;\n        if echo {\n            let _ = self.writeln(input);\n        }\n\n        let start = Instant::now();\n        let mut stats = if self.opts.timer {\n            Some(QueryStatistics {\n                io_time_elapsed_samples: vec![],\n                execute_time_elapsed_samples: vec![],\n            })\n        } else {\n            None\n        };\n\n        let conn = self.conn.clone();\n        let runner = conn.query_runner(input.as_bytes());\n        let had_error_before = self.had_query_error;\n        let capture_stats = self.opts.stats;\n        let mut last_stmt_metrics = None;\n        for mut output in runner {\n            if let Ok(Some(ref mut stmt)) = output {\n                self.apply_parameter_bindings(stmt);\n            }\n            if self\n                .print_query_result(input, &mut output, stats.as_mut())\n                .is_err()\n                || self.had_query_error != had_error_before\n            {\n                self.had_query_error = true;\n                break;\n            }\n            // Capture metrics after stepping, before the Statement is dropped\n            if capture_stats {\n                if let Ok(Some(ref stmt)) = output {\n                    last_stmt_metrics = Some(stmt.metrics().clone());\n                }\n            }\n        }\n\n        self.print_query_performance_stats(start, stats.as_ref());\n\n        // Display stats if enabled\n        if let Some(ref last) = last_stmt_metrics {\n            let _ = self.writeln(format!(\"\\n{last}\"));\n        }\n    }\n\n    fn apply_parameter_bindings(&self, stmt: &mut Statement) {\n        for binding in &self.parameter_bindings {\n            if let Some(index) = binding.index {\n                if stmt.parameters().has_slot(index) {\n                    stmt.bind_at(index, binding.value.clone());\n                }\n                continue;\n            }\n\n            if let Some(index) = stmt.parameter_index(&binding.name) {\n                stmt.bind_at(index, binding.value.clone());\n            }\n        }\n    }\n\n    fn handle_parameter_command(&mut self, args: ParameterArgs) -> Result<(), String> {\n        match args.command {\n            ParameterCommand::Set(args) => {\n                validate_parameter_name(&args.name)?;\n                let index = parameter_name_to_index(&args.name);\n                let value = parse_parameter_value(&args.value)?;\n\n                if let Some(existing) = self\n                    .parameter_bindings\n                    .iter_mut()\n                    .find(|binding| binding.name.as_ref() == args.name)\n                {\n                    existing.index = index;\n                    existing.value = value;\n                } else {\n                    self.parameter_bindings.push(ParameterBinding {\n                        name: args.name.into_boxed_str(),\n                        index,\n                        value,\n                    });\n                }\n                Ok(())\n            }\n            ParameterCommand::List => self.list_parameter_bindings(),\n            ParameterCommand::Clear(args) => {\n                if let Some(name) = args.name {\n                    validate_parameter_name(&name)?;\n                    self.parameter_bindings\n                        .retain(|binding| binding.name.as_ref() != name);\n                } else {\n                    self.parameter_bindings.clear();\n                }\n                Ok(())\n            }\n        }\n    }\n\n    fn list_parameter_bindings(&mut self) -> Result<(), String> {\n        if self.parameter_bindings.is_empty() {\n            return Ok(());\n        }\n\n        let writer = self\n            .writer\n            .as_mut()\n            .ok_or_else(|| \"writer is not initialized\".to_string())?;\n\n        for binding in &self.parameter_bindings {\n            writer\n                .write_fmt(format_args!(\"{} = {}\\n\", binding.name, binding.value))\n                .map_err(|e| e.to_string())?;\n        }\n        Ok(())\n    }\n\n    fn print_query_performance_stats(&mut self, start: Instant, stats: Option<&QueryStatistics>) {\n        let elapsed_as_str = |duration: Duration| {\n            if duration.as_secs() >= 1 {\n                format!(\"{} s\", duration.as_secs_f64())\n            } else if duration.as_millis() >= 1 {\n                format!(\"{} ms\", duration.as_millis() as f64)\n            } else if duration.as_micros() >= 1 {\n                format!(\"{} us\", duration.as_micros() as f64)\n            } else {\n                format!(\"{} ns\", duration.as_nanos())\n            }\n        };\n        let sample_stats_as_str = |name: &str, samples: &Vec<Duration>| {\n            if samples.is_empty() {\n                return format!(\"{name}: No samples available\");\n            }\n            let avg_time_spent = samples.iter().sum::<Duration>() / samples.len() as u32;\n            let total_time = samples.iter().fold(Duration::ZERO, |acc, x| acc + *x);\n            format!(\n                \"{}: avg={}, total={}\",\n                name,\n                elapsed_as_str(avg_time_spent),\n                elapsed_as_str(total_time),\n            )\n        };\n        if self.opts.timer {\n            if let Some(stats) = stats {\n                let _ = self.writeln(\"Command stats:\\n----------------------------\");\n                let _ = self.writeln(format!(\n                    \"total: {} (this includes parsing/coloring of cli app)\\n\",\n                    elapsed_as_str(start.elapsed())\n                ));\n\n                let _ = self.writeln(\"query execution stats:\\n----------------------------\");\n                let _ = self.writeln(sample_stats_as_str(\n                    \"Execution\",\n                    &stats.execute_time_elapsed_samples,\n                ));\n                let _ = self.writeln(sample_stats_as_str(\"I/O\", &stats.io_time_elapsed_samples));\n            }\n        }\n    }\n\n    fn reset_line(&mut self) {\n        // Entry is auto added to history\n        // self.rl.add_history_entry(line.to_owned())?;\n        self.interrupt_count.store(0, Ordering::Release);\n    }\n\n    // consume will consume `input_buff`\n    pub fn consume(&mut self, flush: bool) {\n        if self.input_buff.trim().is_empty() {\n            return;\n        }\n\n        self.reset_line();\n\n        // we are taking ownership of input_buff here\n        // its always safe because we split the string in two parts\n        fn take_usable_part(app: &mut Limbo) -> (String, usize) {\n            let ptr = app.input_buff.as_mut_ptr();\n            let (len, cap) = (app.input_buff.len(), app.input_buff.capacity());\n            app.input_buff =\n                ManuallyDrop::new(unsafe { String::from_raw_parts(ptr.add(len), 0, cap - len) });\n            (unsafe { String::from_raw_parts(ptr, len, len) }, unsafe {\n                ptr.add(len).addr()\n            })\n        }\n\n        fn concat_usable_part(app: &mut Limbo, mut part: String, old_address: usize) {\n            let ptr = app.input_buff.as_mut_ptr();\n            let (len, cap) = (app.input_buff.len(), app.input_buff.capacity());\n\n            // if the address is not the same, meaning the string has been reallocated\n            // so we just drop the part we took earlier\n            if ptr.addr() != old_address || !app.input_buff.is_empty() {\n                return;\n            }\n\n            let head_ptr = part.as_mut_ptr();\n            let (head_len, head_cap) = (part.len(), part.capacity());\n            forget(part); // move this part into `input_buff`\n            app.input_buff = ManuallyDrop::new(unsafe {\n                String::from_raw_parts(head_ptr, head_len + len, head_cap + cap)\n            });\n        }\n\n        let value = self.input_buff.trim();\n        let is_dot_command = value.starts_with('.');\n        let is_complete = self.read_state.is_complete();\n\n        match (is_dot_command, is_complete) {\n            (true, _) => {\n                let (owned_value, old_address) = take_usable_part(self);\n                self.handle_dot_command(owned_value.trim().strip_prefix('.').unwrap());\n                concat_usable_part(self, owned_value, old_address);\n                self.reset_input();\n            }\n            (false, true) => {\n                let (owned_value, old_address) = take_usable_part(self);\n                self.run_query(owned_value.trim());\n                concat_usable_part(self, owned_value, old_address);\n                self.reset_input();\n            }\n            (false, false) if flush => {\n                let (owned_value, old_address) = take_usable_part(self);\n                self.run_query(owned_value.trim());\n                concat_usable_part(self, owned_value, old_address);\n                self.reset_input();\n            }\n            (false, false) => {\n                self.set_multiline_prompt();\n            }\n        }\n    }\n\n    pub fn handle_dot_command(&mut self, line: &str) {\n        let first = line.split_whitespace().next();\n        let parse = match first {\n            Some(\"parameter\") | Some(\"param\") => {\n                let args = shlex::split(line).unwrap_or_else(|| {\n                    line.split_whitespace()\n                        .map(str::to_owned)\n                        .collect::<Vec<_>>()\n                });\n                if args.is_empty() {\n                    return;\n                }\n                CommandParser::try_parse_from(args)\n            }\n            _ => {\n                let args = line.split_whitespace();\n                CommandParser::try_parse_from(args)\n            }\n        };\n        match parse {\n            Err(err) => {\n                // Let clap print with Styled Colors instead\n                let _ = err.print();\n            }\n            Ok(cmd) => match cmd.command {\n                Command::Exit(args) => {\n                    self.save_history();\n                    std::process::exit(args.code);\n                }\n                Command::Quit => {\n                    let _ = self.writeln(\"Exiting Turso SQL Shell.\");\n                    let _ = self.close_conn();\n                    self.save_history();\n                    std::process::exit(0)\n                }\n                Command::Open(args) => {\n                    if let Err(e) = self.open_db(&args.path, args.vfs_name.as_deref()) {\n                        let _ = self.writeln(e.to_string());\n                    }\n                }\n                Command::Schema(args) => {\n                    if let Err(e) = self.display_schema(args.table_name.as_deref()) {\n                        let _ = self.writeln(e.to_string());\n                    }\n                }\n                Command::Tables(args) => {\n                    if let Err(e) = self.display_tables(args.pattern.as_deref()) {\n                        let _ = self.writeln(e.to_string());\n                    }\n                }\n                Command::Databases => {\n                    if let Err(e) = self.display_databases() {\n                        let _ = self.writeln(e.to_string());\n                    }\n                }\n                Command::Opcodes(args) => {\n                    if let Some(opcode) = args.opcode {\n                        for op in &OPCODE_DESCRIPTIONS {\n                            if op.name.eq_ignore_ascii_case(opcode.trim()) {\n                                let _ = self.writeln_fmt(format_args!(\"{op}\"));\n                            }\n                        }\n                    } else {\n                        for op in &OPCODE_DESCRIPTIONS {\n                            let _ = self.writeln_fmt(format_args!(\"{op}\\n\"));\n                        }\n                    }\n                }\n                Command::NullValue(args) => {\n                    self.opts.null_value = args.value;\n                }\n                Command::OutputMode(args) => {\n                    if let Err(e) = self.set_mode(args.mode) {\n                        let _ = self.writeln_fmt(format_args!(\"Error: {e}\"));\n                    }\n                }\n                Command::SetOutput(args) => {\n                    if let Some(path) = args.path {\n                        if let Err(e) = self.set_output_file(&path) {\n                            let _ = self.writeln_fmt(format_args!(\"Error: {e}\"));\n                        }\n                    } else {\n                        self.set_output_stdout();\n                    }\n                }\n                Command::Echo(args) => {\n                    self.toggle_echo(args.mode);\n                }\n                Command::Cwd(args) => {\n                    let _ = std::env::set_current_dir(args.directory);\n                }\n                Command::ShowInfo => {\n                    let _ = self.show_info();\n                }\n                Command::Stats(args) => {\n                    if let Err(e) = self.display_stats(args) {\n                        let _ = self.writeln(e.to_string());\n                    }\n                }\n                Command::Import(args) => {\n                    let w = self.writer.as_mut().unwrap();\n                    let mut import_file = ImportFile::new(self.conn.clone(), w);\n                    import_file.import(args)\n                }\n                Command::LoadExtension(args) => {\n                    #[cfg(not(target_family = \"wasm\"))]\n                    if let Err(e) = self.handle_load_extension(&args.path) {\n                        let _ = self.writeln(&e);\n                    }\n                }\n                Command::Dump => {\n                    if let Err(e) = self.dump_database() {\n                        let _ = self.writeln_fmt(format_args!(\"/****** ERROR: {e} ******/\"));\n                    }\n                }\n                Command::DbConfig(_args) => {\n                    let _ = self.writeln(\"dbconfig currently ignored\");\n                }\n                Command::ListVfs => {\n                    let _ = self.writeln(\"Available VFS modules:\");\n                    self.conn.list_vfs().iter().for_each(|v| {\n                        let _ = self.writeln(v);\n                    });\n                }\n                Command::ListIndexes(args) => {\n                    if let Err(e) = self.display_indexes(args.tbl_name) {\n                        let _ = self.writeln(e.to_string());\n                    }\n                }\n                Command::Timer(timer_mode) => {\n                    self.opts.timer = match timer_mode.mode {\n                        TimerMode::On => true,\n                        TimerMode::Off => false,\n                    };\n                }\n                Command::Headers(headers_mode) => {\n                    self.opts.headers = match headers_mode.mode {\n                        HeadersMode::On => true,\n                        HeadersMode::Off => false,\n                    };\n                }\n                Command::Clone(args) => {\n                    if let Err(e) = self.clone_database(&args.output_file) {\n                        let _ = self.writeln(e.to_string());\n                    }\n                }\n                Command::Manual(args) => {\n                    let w = self.writer.as_mut().unwrap();\n                    if let Err(e) = manual::display_manual(args.page.as_deref(), w) {\n                        let _ = self.writeln(e.to_string());\n                    }\n                }\n                Command::Read(args) => {\n                    if let Err(e) = self.read_sql_file(&args.path) {\n                        let _ = self.writeln(e.to_string());\n                    }\n                }\n                Command::Parameter(args) => {\n                    if let Err(e) = self.handle_parameter_command(args) {\n                        let _ = self.writeln_fmt(format_args!(\"Error: {e}\"));\n                    }\n                }\n                Command::Dbtotxt(args) => {\n                    if let Err(e) = self.dump_database_as_text(args.page_no) {\n                        let _ = self.writeln_fmt(format_args!(\"ERROR:{e}\"));\n                    }\n                }\n            },\n        }\n    }\n\n    fn print_query_result(\n        &mut self,\n        sql: &str,\n        output: &mut Result<Option<Statement>, LimboError>,\n        statistics: Option<&mut QueryStatistics>,\n    ) -> anyhow::Result<()> {\n        match output {\n            Ok(Some(ref mut rows)) => {\n                let query_mode = rows.get_query_mode();\n                let output_mode = self.opts.output_mode;\n\n                match (output_mode, query_mode) {\n                    (_, QueryMode::ExplainQueryPlan) => {\n                        self.print_explain_query_plan(rows, statistics)?;\n                    }\n                    (_, QueryMode::Explain) => {\n                        self.print_explain(rows, statistics)?;\n                    }\n                    (OutputMode::List, _) => {\n                        self.print_list_mode(rows, statistics)?;\n                    }\n                    (OutputMode::Pretty, _) => {\n                        self.print_pretty_mode(rows, statistics)?;\n                    }\n                    (OutputMode::Line, _) => {\n                        self.print_line_mode(rows, statistics)?;\n                    }\n                }\n            }\n            Ok(None) => {}\n\n            Err(ref err) => {\n                match err {\n                    LimboError::Busy => {}\n                    LimboError::Interrupt => {}\n                    _ => {\n                        let report =\n                            miette::Error::from(err.clone()).with_source_code(sql.to_owned());\n                        let _ = self.writeln_fmt(format_args!(\"{report:?}\"));\n                    }\n                }\n                anyhow::bail!(\"We have to throw here, even if we printed error\");\n            }\n        }\n        Ok(())\n    }\n\n    fn print_explain_query_plan(\n        &mut self,\n        rows: &mut Statement,\n        statistics: Option<&mut QueryStatistics>,\n    ) -> turso_core::Result<()> {\n        struct Entry {\n            id: usize,\n            detail: String,\n            child_prefix: String,\n            children: Vec<Entry>,\n        }\n\n        fn add_children(id: usize, parent_id: usize, detail: String, current: &mut Entry) -> bool {\n            if current.id == parent_id {\n                current.children.push(Entry {\n                    id,\n                    detail,\n                    child_prefix: current.child_prefix.clone() + \"   \",\n                    children: vec![],\n                });\n                if current.children.len() > 1 {\n                    let idx = current.children.len() - 2;\n                    current.children[idx].child_prefix = current.child_prefix.clone() + \"|  \";\n                }\n                return false;\n            }\n            for child in &mut current.children {\n                if !add_children(id, parent_id, detail.clone(), child) {\n                    return false;\n                }\n            }\n            true\n        }\n\n        fn print_entry(app: &mut Limbo, entry: &Entry, prefix: &str) {\n            writeln!(app, \"{}{}\", prefix, entry.detail).unwrap();\n            for (i, child) in entry.children.iter().enumerate() {\n                let is_last = i == entry.children.len() - 1;\n                let child_prefix = format!(\n                    \"{}{}\",\n                    entry.child_prefix,\n                    if is_last { \"`--\" } else { \"|--\" }\n                );\n                print_entry(app, child, child_prefix.as_str());\n            }\n        }\n\n        let mut root = Entry {\n            id: 0,\n            detail: \"QUERY PLAN\".to_owned(),\n            child_prefix: \"\".to_owned(),\n            children: vec![],\n        };\n\n        let mut stepper = RowStepper::new(rows, statistics);\n        loop {\n            match stepper.next_row() {\n                Ok(Some(row)) => {\n                    let id = row.get_value(0).as_uint() as usize;\n                    let parent_id = row.get_value(1).as_uint() as usize;\n                    let detail = row.get_value(3).to_string();\n                    add_children(id, parent_id, detail, &mut root);\n                }\n                Ok(None) => break,\n                Err(e) => {\n                    self.handle_step_error(e);\n                    break;\n                }\n            }\n        }\n\n        print_entry(self, &root, \"\");\n        Ok(())\n    }\n\n    fn print_explain(\n        &mut self,\n        rows: &mut Statement,\n        statistics: Option<&mut QueryStatistics>,\n    ) -> turso_core::Result<()> {\n        fn get_explain_indent(\n            indent_count: usize,\n            curr_insn: &str,\n            prev_insn: &str,\n            p1: &str,\n            unclosed_begin_subrtns: &mut Vec<String>,\n        ) -> usize {\n            let indent_count = match prev_insn {\n                \"Rewind\" | \"Last\" | \"SorterSort\" | \"SeekGE\" | \"SeekGT\" | \"SeekLE\" | \"SeekLT\"\n                | \"BeginSubrtn\" | \"IndexMethodQuery\" => indent_count + 1,\n                _ => indent_count,\n            };\n\n            if curr_insn == \"BeginSubrtn\" {\n                unclosed_begin_subrtns.push(p1.to_string());\n            }\n\n            match curr_insn {\n                \"Next\" | \"SorterNext\" | \"Prev\" => indent_count.saturating_sub(1),\n                \"Return\" => {\n                    let matching = unclosed_begin_subrtns.iter().position(|b| b == p1);\n                    if let Some(idx) = matching {\n                        unclosed_begin_subrtns.remove(idx);\n                        indent_count.saturating_sub(1)\n                    } else {\n                        indent_count\n                    }\n                }\n                _ => indent_count,\n            }\n        }\n\n        let _ =\n            self.writeln(\"addr  opcode             p1    p2    p3    p4             p5  comment\");\n        let _ =\n            self.writeln(\"----  -----------------  ----  ----  ----  -------------  --  -------\");\n\n        let mut prev_insn = String::new();\n        let mut indent_count = 0;\n        let indent = \"  \";\n        let mut unclosed_begin_subrtns = vec![];\n\n        let mut stepper = RowStepper::new(rows, statistics);\n        loop {\n            match stepper.next_row() {\n                Ok(Some(row)) => {\n                    let insn = row.get_value(1).to_string();\n                    let p1 = row.get_value(2).to_string();\n                    indent_count = get_explain_indent(\n                        indent_count,\n                        &insn,\n                        &prev_insn,\n                        &p1,\n                        &mut unclosed_begin_subrtns,\n                    );\n                    let _ = self.writeln(format!(\n                        \"{:<4}  {:<17}  {:<4}  {:<4}  {:<4}  {:<13}  {:<2}  {}\",\n                        row.get_value(0).to_string(),\n                        &(indent.repeat(indent_count) + &insn),\n                        p1,\n                        row.get_value(3).to_string(),\n                        row.get_value(4).to_string(),\n                        row.get_value(5).to_string(),\n                        row.get_value(6).to_string(),\n                        row.get_value(7),\n                    ));\n                    prev_insn = insn;\n                }\n                Ok(None) => break,\n                Err(e) => {\n                    self.handle_step_error(e);\n                    break;\n                }\n            }\n        }\n        Ok(())\n    }\n\n    fn print_list_mode(\n        &mut self,\n        rows: &mut Statement,\n        statistics: Option<&mut QueryStatistics>,\n    ) -> turso_core::Result<()> {\n        let num_columns = rows.num_columns();\n        let column_names: Vec<String> = (0..num_columns)\n            .map(|i| rows.get_column_name(i).to_string())\n            .collect();\n        let print_headers = self.opts.headers;\n        let null_value = self.opts.null_value.clone();\n\n        let mut headers_printed = false;\n        let mut stepper = RowStepper::new(rows, statistics);\n        loop {\n            match stepper.next_row() {\n                Ok(Some(row)) => {\n                    if print_headers && !headers_printed {\n                        for (i, name) in column_names.iter().enumerate() {\n                            if i > 0 {\n                                let _ = self.write(b\"|\");\n                            }\n                            let _ = self.write(name.as_bytes());\n                        }\n                        let _ = self.writeln(\"\");\n                        headers_printed = true;\n                    }\n\n                    for (i, value) in row.get_values().enumerate() {\n                        if i > 0 {\n                            let _ = self.write(b\"|\");\n                        }\n                        if matches!(value, Value::Null) {\n                            let _ = self.write(null_value.as_bytes());\n                        } else {\n                            write!(self, \"{value}\").map_err(|e| io_error(e, \"write\"))?;\n                        }\n                    }\n                    let _ = self.writeln(\"\");\n                }\n                Ok(None) => break,\n                Err(e) => {\n                    self.handle_step_error(e);\n                    break;\n                }\n            }\n        }\n        Ok(())\n    }\n\n    fn print_pretty_mode(\n        &mut self,\n        rows: &mut Statement,\n        statistics: Option<&mut QueryStatistics>,\n    ) -> turso_core::Result<()> {\n        let config = self.config.as_ref().unwrap();\n        let null_value = self.opts.null_value.clone();\n        let num_columns = rows.num_columns();\n        let column_names: Vec<String> = (0..num_columns)\n            .map(|i| rows.get_column_name(i).to_string())\n            .collect();\n        let header_color = config.table.header_color.as_comfy_table_color();\n        let column_colors: Vec<_> = config\n            .table\n            .column_colors\n            .iter()\n            .map(|c| c.as_comfy_table_color())\n            .collect();\n\n        let mut table = Table::new();\n        table\n            .set_content_arrangement(ContentArrangement::Dynamic)\n            .set_truncation_indicator(\"…\")\n            .apply_modifier(\"││──├─┼┤│─┼├┤┬┴┌┐└┘\");\n\n        if num_columns > 0 {\n            let header = column_names\n                .iter()\n                .map(|name| {\n                    Cell::new(name)\n                        .add_attribute(Attribute::Bold)\n                        .fg(header_color)\n                })\n                .collect::<Vec<_>>();\n            table.set_header(header);\n        }\n\n        let mut stepper = RowStepper::new(rows, statistics);\n        loop {\n            match stepper.next_row() {\n                Ok(Some(row)) => {\n                    let mut table_row = Row::new();\n                    table_row.max_height(1);\n                    for (idx, value) in row.get_values().enumerate() {\n                        let (content, alignment) = match value {\n                            Value::Null => (null_value.clone(), CellAlignment::Left),\n                            Value::Numeric(_) => (format!(\"{value}\"), CellAlignment::Right),\n                            Value::Text(_) => (format!(\"{value}\"), CellAlignment::Left),\n                            Value::Blob(_) => (format!(\"{value}\"), CellAlignment::Left),\n                        };\n                        table_row.add_cell(\n                            Cell::new(content)\n                                .set_alignment(alignment)\n                                .fg(column_colors[idx % column_colors.len()]),\n                        );\n                    }\n                    table.add_row(table_row);\n                }\n                Ok(None) => break,\n                Err(e) => {\n                    self.handle_step_error(e);\n                    break;\n                }\n            }\n        }\n\n        if !table.is_empty() {\n            writeln!(self, \"{table}\").map_err(|e| io_error(e, \"write\"))?;\n        }\n        Ok(())\n    }\n\n    fn print_line_mode(\n        &mut self,\n        rows: &mut Statement,\n        statistics: Option<&mut QueryStatistics>,\n    ) -> turso_core::Result<()> {\n        let num_columns = rows.num_columns();\n        let column_names: Vec<String> = (0..num_columns)\n            .map(|i| rows.get_column_name(i).to_string())\n            .collect();\n        let max_width = column_names.iter().map(|n| n.len()).max().unwrap_or(0);\n        let formatted_columns: Vec<String> = column_names\n            .iter()\n            .map(|n| format!(\"{n:>max_width$}\"))\n            .collect();\n        let null_value = self.opts.null_value.clone();\n\n        let mut first_row_printed = false;\n        let mut stepper = RowStepper::new(rows, statistics);\n        loop {\n            match stepper.next_row() {\n                Ok(Some(row)) => {\n                    if first_row_printed {\n                        self.writeln(\"\").map_err(|e| io_error(e, \"write\"))?;\n                    } else {\n                        first_row_printed = true;\n                    }\n\n                    for (i, value) in row.get_values().enumerate() {\n                        self.write(&formatted_columns[i])\n                            .map_err(|e| io_error(e, \"write\"))?;\n                        self.write(b\" = \").map_err(|e| io_error(e, \"write\"))?;\n                        if matches!(value, Value::Null) {\n                            self.write(null_value.as_bytes())\n                                .map_err(|e| io_error(e, \"write\"))?;\n                        } else {\n                            write!(self, \"{value}\").map_err(|e| io_error(e, \"write\"))?;\n                        }\n                        self.writeln(\"\").map_err(|e| io_error(e, \"write\"))?;\n                    }\n                }\n                Ok(None) => break,\n                Err(e) => {\n                    self.handle_step_error(e);\n                    break;\n                }\n            }\n        }\n        Ok(())\n    }\n\n    fn handle_step_error(&mut self, err: LimboError) {\n        self.had_query_error = true;\n        match err {\n            LimboError::Interrupt => {\n                let _ = self.writeln(LimboError::Interrupt.to_string());\n            }\n            LimboError::Busy => {\n                let _ = self.writeln(\"database is busy\");\n            }\n            _ => {\n                let _ = self.writeln_fmt(format_args!(\"Error: {err}\"));\n            }\n        }\n    }\n\n    pub fn init_tracing(opts: &Opts) -> Result<WorkerGuard, std::io::Error> {\n        let ((non_blocking, guard), should_emit_ansi) = if let Some(file) = &opts.tracing_output {\n            (\n                tracing_appender::non_blocking(\n                    std::fs::File::options()\n                        .append(true)\n                        .create(true)\n                        .open(file)?,\n                ),\n                false,\n            )\n        } else {\n            (\n                tracing_appender::non_blocking(std::io::stderr()),\n                IsTerminal::is_terminal(&std::io::stderr()),\n            )\n        };\n        let default_env_filter = EnvFilter::builder()\n            .with_default_directive(tracing::level_filters::LevelFilter::WARN.into())\n            .from_env_lossy();\n\n        // Disable rustyline traces\n        if let Err(e) = tracing_subscriber::registry()\n            .with(\n                tracing_subscriber::fmt::layer()\n                    .with_writer(non_blocking)\n                    .with_line_number(true)\n                    .with_thread_ids(true)\n                    .with_ansi(should_emit_ansi),\n            )\n            .with(default_env_filter.add_directive(\"rustyline=off\".parse().unwrap()))\n            .try_init()\n        {\n            println!(\"Unable to setup tracing appender: {e:?}\");\n        }\n        Ok(guard)\n    }\n\n    fn print_schema_entry(&mut self, db_display_name: &str, row: &turso_core::Row) -> bool {\n        if let (Ok(Value::Text(schema)), Ok(Value::Text(obj_type)), Ok(Value::Text(obj_name))) = (\n            row.get::<&Value>(0),\n            row.get::<&Value>(1),\n            row.get::<&Value>(2),\n        ) {\n            let modified_schema = if db_display_name == \"main\" {\n                schema.as_str().to_string()\n            } else {\n                // We need to modify the SQL to include the database prefix in table names\n                // This is a simple approach - for CREATE TABLE statements, insert db name after \"TABLE \"\n                // For CREATE INDEX statements, insert db name after \"ON \"\n                let schema_str = schema.as_str();\n                if schema_str.to_uppercase().contains(\"CREATE TABLE \") {\n                    // Find \"CREATE TABLE \" and insert database name after it\n                    if let Some(pos) = schema_str.to_uppercase().find(\"CREATE TABLE \") {\n                        let before = &schema_str[..pos + \"CREATE TABLE \".len()];\n                        let after = &schema_str[pos + \"CREATE TABLE \".len()..];\n                        format!(\"{before}{db_display_name}.{after}\")\n                    } else {\n                        schema_str.to_string()\n                    }\n                } else if schema_str.to_uppercase().contains(\" ON \") {\n                    // For indexes, find \" ON \" and insert database name after it\n                    if let Some(pos) = schema_str.to_uppercase().find(\" ON \") {\n                        let before = &schema_str[..pos + \" ON \".len()];\n                        let after = &schema_str[pos + \" ON \".len()..];\n                        format!(\"{before}{db_display_name}.{after}\")\n                    } else {\n                        schema_str.to_string()\n                    }\n                } else {\n                    schema_str.to_string()\n                }\n            };\n            let _ = self.writeln_fmt(format_args!(\"{modified_schema};\"));\n            // For views, add the column comment like SQLite does\n            if obj_type.as_str() == \"view\" {\n                let columns = self\n                    .get_view_columns(obj_name.as_str())\n                    .unwrap_or_else(|_| \"x\".to_string());\n                let _ = self.writeln_fmt(format_args!(\"/* {}({}) */\", obj_name.as_str(), columns));\n            }\n            true\n        } else {\n            false\n        }\n    }\n\n    /// Get column names for a view to generate the SQLite-compatible comment\n    fn get_view_columns(&mut self, view_name: &str) -> anyhow::Result<String> {\n        // Get column information using PRAGMA table_info\n        let pragma_sql = format!(\"PRAGMA table_info({view_name})\");\n\n        let mut columns = Vec::new();\n        let handler = |row: &turso_core::Row| {\n            // Column name is in the second column (index 1) of PRAGMA table_info\n            if let Ok(Value::Text(col_name)) = row.get::<&Value>(1) {\n                columns.push(col_name.as_str().to_string());\n            }\n            Ok(())\n        };\n        if let Err(err) = self.handle_row(&pragma_sql, handler) {\n            return Err(anyhow::anyhow!(\n                \"Error retrieving columns for view '{}': {}\",\n                view_name,\n                err\n            ));\n        }\n        if columns.is_empty() {\n            anyhow::bail!(\"PRAGMA table_info returned no columns for view '{}'. The view may be corrupted or the database schema is invalid.\", view_name);\n        }\n        Ok(columns.join(\",\"))\n    }\n\n    fn query_one_table_schema(\n        &mut self,\n        db_prefix: &str,\n        db_display_name: &str,\n        table_name: &str,\n    ) -> anyhow::Result<bool> {\n        // Yeah, sqlite also has this hardcoded: https://github.com/sqlite/sqlite/blob/31efe5a0f2f80a263457a1fc6524783c0c45769b/src/shell.c.in#L10765\n        match table_name {\n            \"sqlite_master\" | \"sqlite_schema\" | \"sqlite_temp_master\" | \"sqlite_temp_schema\" => {\n                let schema = format!(\n                                    \"CREATE TABLE {table_name} (\\n type text,\\n name text,\\n tbl_name text,\\n rootpage integer,\\n sql text\\n);\",\n                                );\n                let _ = self.writeln(&schema);\n                return Ok(true);\n            }\n            _ => {}\n        }\n        let sql = format!(\n            \"SELECT sql, type, name FROM {db_prefix}.sqlite_schema WHERE type IN ('table', 'index', 'view') AND (tbl_name = '{table_name}' OR name = '{table_name}') AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '__turso_internal_%' ORDER BY CASE type WHEN 'table' THEN 1 WHEN 'view' THEN 2 WHEN 'index' THEN 3 END, rowid\"\n        );\n\n        let mut found = false;\n        match self.conn.query(&sql) {\n            Ok(Some(ref mut rows)) => {\n                let res = rows.run_with_row_callback(|row| {\n                    found |= self.print_schema_entry(db_display_name, row);\n                    Ok(())\n                });\n                match res {\n                    Ok(_) => {}\n                    Err(LimboError::Interrupt) => {\n                        let _ = self.writeln(LimboError::Interrupt.to_string());\n                    }\n                    Err(LimboError::Busy) => {\n                        let _ = self.writeln(\"database is busy\");\n                    }\n                    Err(err) => return Err(anyhow::anyhow!(err)),\n                }\n            }\n            Ok(None) => {}\n            Err(_) => {} // Table not found in this database\n        }\n        Ok(found)\n    }\n\n    fn query_all_tables_schema(\n        &mut self,\n        db_prefix: &str,\n        db_display_name: &str,\n    ) -> anyhow::Result<()> {\n        let sql = format!(\"SELECT sql, type, name FROM {db_prefix}.sqlite_schema WHERE type IN ('table', 'index', 'view') AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '__turso_internal_%' ORDER BY CASE type WHEN 'table' THEN 1 WHEN 'view' THEN 2 WHEN 'index' THEN 3 END, rowid\");\n\n        match self.conn.query(&sql) {\n            Ok(Some(ref mut rows)) => {\n                let res = rows.run_with_row_callback(|row| {\n                    self.print_schema_entry(db_display_name, row);\n                    Ok(())\n                });\n                match res {\n                    Ok(_) => {}\n                    Err(LimboError::Busy) => {\n                        let _ = self.writeln(\"database is busy\");\n                    }\n                    Err(LimboError::Interrupt) => {\n                        let _ = self.writeln(LimboError::Interrupt.to_string());\n                    }\n                    Err(err) => return Err(anyhow!(err)),\n                }\n            }\n            Ok(None) => {}\n            Err(err) => {\n                // If we can't access this database's schema, just skip it\n                if !err.to_string().contains(\"no such table\") {\n                    eprintln!(\n                        \"Warning: Could not query schema for database '{db_display_name}': {err}\"\n                    );\n                }\n            }\n        }\n        Ok(())\n    }\n\n    fn display_schema(&mut self, table: Option<&str>) -> anyhow::Result<()> {\n        match table {\n            Some(table_spec) => {\n                // Parse table name to handle database prefixes (e.g., \"db.table\")\n                let clean_table_spec = table_spec.trim_end_matches(';');\n\n                let (target_db, table_name) =\n                    if let Some((db, tbl)) = clean_table_spec.split_once('.') {\n                        (db, tbl)\n                    } else {\n                        (\"main\", clean_table_spec)\n                    };\n\n                // Query only the specific table in the specific database\n                if target_db == \"main\" {\n                    self.query_one_table_schema(\"main\", \"main\", table_name)?;\n                } else {\n                    // Check if the database is attached\n                    let attached_databases = self.conn.list_attached_databases();\n                    if attached_databases.contains(&target_db.to_string()) {\n                        self.query_one_table_schema(target_db, target_db, table_name)?;\n                    }\n                }\n            }\n            None => {\n                // Show schema for all tables in all databases\n                let attached_databases = self.conn.list_attached_databases();\n\n                // Query main database first\n                self.query_all_tables_schema(\"main\", \"main\")?;\n\n                // Query all attached databases\n                for db_name in attached_databases {\n                    self.query_all_tables_schema(&db_name, &db_name)?;\n                }\n            }\n        }\n\n        Ok(())\n    }\n\n    fn display_indexes(&mut self, maybe_table: Option<String>) -> anyhow::Result<()> {\n        let mut indexes = String::new();\n\n        for name in self.database_names()? {\n            let prefix = (name != \"main\").then_some(&name);\n            let sql = match maybe_table {\n                Some(ref tbl_name) => format!(\n                    \"SELECT name FROM {name}.sqlite_schema WHERE type='index' AND tbl_name = '{tbl_name}' ORDER BY 1\"\n                ),\n                None => format!(\"SELECT name FROM {name}.sqlite_schema WHERE type='index' ORDER BY 1\"),\n            };\n            let handler = |row: &turso_core::Row| {\n                if let Ok(Value::Text(idx)) = row.get::<&Value>(0) {\n                    if let Some(prefix) = prefix {\n                        indexes.push_str(prefix);\n                        indexes.push('.');\n                    }\n                    indexes.push_str(idx.as_str());\n                    indexes.push(' ');\n                }\n                Ok(())\n            };\n            if let Err(err) = self.handle_row(&sql, handler) {\n                if err.to_string().contains(\"no such table: sqlite_schema\") {\n                    return Err(anyhow::anyhow!(\"Unable to access database schema. The database may be using an older SQLite version or may not be properly initialized.\"));\n                } else {\n                    return Err(anyhow::anyhow!(\"Error querying schema: {}\", err));\n                }\n            }\n        }\n        if !indexes.is_empty() {\n            let _ = self.writeln(indexes.trim_end().as_bytes());\n        }\n        Ok(())\n    }\n\n    fn display_tables(&mut self, pattern: Option<&str>) -> anyhow::Result<()> {\n        let mut tables = String::new();\n\n        for name in self.database_names()? {\n            let prefix = (name != \"main\").then_some(&name);\n            let sql = match pattern {\n                Some(pattern) => format!(\n                    \"SELECT name FROM {name}.sqlite_schema WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name LIKE '{pattern}' ORDER BY 1\"\n                ),\n                None => format!(\n                    \"SELECT name FROM {name}.sqlite_schema WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY 1\"\n                ),\n            };\n            let handler = |row: &turso_core::Row| {\n                if let Ok(Value::Text(table)) = row.get::<&Value>(0) {\n                    if let Some(prefix) = prefix {\n                        tables.push_str(prefix);\n                        tables.push('.');\n                    }\n                    tables.push_str(table.as_str());\n                    tables.push(' ');\n                }\n                Ok(())\n            };\n            if let Err(e) = self.handle_row(&sql, handler) {\n                if e.to_string().contains(\"no such table: sqlite_schema\") {\n                    return Err(anyhow::anyhow!(\"Unable to access database schema. The database may be using an older SQLite version or may not be properly initialized.\"));\n                } else {\n                    return Err(anyhow::anyhow!(\"Error querying schema: {}\", e));\n                }\n            }\n        }\n        if !tables.is_empty() {\n            let _ = self.writeln(tables.trim_end().as_bytes());\n        } else if let Some(pattern) = pattern {\n            let _ = self.writeln_fmt(format_args!(\n                \"Error: Tables with pattern '{pattern}' not found.\"\n            ));\n        } else {\n            let _ = self.writeln(b\"No tables found in the database.\");\n        }\n        Ok(())\n    }\n\n    fn database_names(&mut self) -> anyhow::Result<Vec<String>> {\n        let sql = \"PRAGMA database_list\";\n        let mut db_names: Vec<String> = Vec::new();\n        let handler = |row: &turso_core::Row| {\n            if let Ok(Value::Text(name)) = row.get::<&Value>(1) {\n                db_names.push(name.to_string());\n            }\n            Ok(())\n        };\n        match self.handle_row(sql, handler) {\n            Ok(_) => Ok(db_names),\n            Err(e) => Err(anyhow::anyhow!(\"Error in database list: {}\", e)),\n        }\n    }\n\n    fn handle_row<F>(&mut self, sql: &str, handler: F) -> anyhow::Result<()>\n    where\n        F: FnMut(&turso_core::Row) -> turso_core::Result<()>,\n    {\n        match self.conn.query(sql) {\n            Ok(Some(ref mut rows)) => {\n                let res = rows.run_with_row_callback(handler);\n                match res {\n                    Ok(_) => {}\n                    Err(LimboError::Busy) => {\n                        let _ = self.writeln(\"database is busy\");\n                    }\n                    Err(LimboError::Interrupt) => {\n                        let _ = self.writeln(LimboError::Interrupt.to_string());\n                    }\n                    Err(err) => return Err(anyhow!(err)),\n                }\n            }\n            Ok(None) => {\n                let _ = self.writeln(\"No results returned from the query.\");\n            }\n            Err(err) => {\n                return Err(anyhow::anyhow!(\"Error querying database: {}\", err));\n            }\n        }\n        Ok(())\n    }\n\n    fn display_databases(&mut self) -> anyhow::Result<()> {\n        let sql = \"PRAGMA database_list\";\n        let conn = self.conn.clone();\n        let mut databases = Vec::new();\n        self.handle_row(sql, |row| {\n            if let (\n                Ok(Value::Numeric(Numeric::Integer(seq))),\n                Ok(Value::Text(name)),\n                Ok(file_value),\n            ) = (\n                row.get::<&Value>(0),\n                row.get::<&Value>(1),\n                row.get::<&Value>(2),\n            ) {\n                let file = match file_value {\n                    Value::Text(path) => path.as_str(),\n                    Value::Null => \"\",\n                    _ => \"\",\n                };\n\n                // Format like SQLite: \"main: /path/to/file r/w\"\n                let file_display = if file.is_empty() {\n                    \"\\\"\\\"\".to_string()\n                } else {\n                    file.to_string()\n                };\n\n                // Detect readonly mode from connection\n                let mode = if conn.is_readonly(*seq as usize) {\n                    \"r/o\"\n                } else {\n                    \"r/w\"\n                };\n\n                databases.push(format!(\"{}: {} {}\", name.as_str(), file_display, mode));\n            }\n            Ok(())\n        })?;\n\n        for db in databases {\n            let _ = self.writeln(db);\n        }\n\n        Ok(())\n    }\n\n    // readline will read inputs from rustyline or stdin\n    // and write it to input_buff.\n    pub fn readline(&mut self) -> Result<(), ReadlineError> {\n        use std::fmt::Write;\n\n        if let Some(rl) = &mut self.rl {\n            let result = rl.readline(&self.prompt)?;\n            self.read_state.process(&result);\n            let _ = self.input_buff.write_str(result.as_str());\n        } else {\n            let mut reader = std::io::stdin().lock();\n            let prev_len = self.input_buff.len();\n            if reader.read_line(&mut self.input_buff)? == 0 {\n                return Err(ReadlineError::Eof);\n            }\n            self.read_state.process(&self.input_buff[prev_len..]);\n        }\n\n        if !self.input_buff.ends_with(char::is_whitespace) {\n            let _ = self.input_buff.write_char('\\n');\n        }\n        Ok(())\n    }\n\n    pub fn dump_database_from_conn<W: Write, P: ProgressSink>(\n        fk: bool,\n        conn: Arc<Connection>,\n        out: &mut W,\n        mut progress: P,\n    ) -> anyhow::Result<()> {\n        // Snapshot for consistency\n        Self::exec_all_conn(&conn, \"BEGIN\")?;\n        // FIXME: we don't yet support PRAGMA foreign_keys=OFF internally,\n        // so for now this hacky boolean that decides not to emit it when cloning\n        if fk {\n            writeln!(out, \"PRAGMA foreign_keys=OFF;\")?;\n        }\n        writeln!(out, \"BEGIN TRANSACTION;\")?;\n        // FIXME: At this point, SQLite executes the following:\n        // sqlite3_exec(p->db, \"SAVEPOINT dump; PRAGMA writable_schema=ON\", 0, 0, 0);\n        // we don't have those yet, so don't.\n        // Emit CREATE TYPE statements from __turso_internal_types before table DDL,\n        // so that tables referencing custom types can be restored correctly.\n        Self::dump_custom_types(&conn, out)?;\n        let q_tables = r#\"\n        SELECT name, sql\n        FROM sqlite_schema\n        WHERE type='table' AND sql NOT NULL\n        ORDER BY tbl_name = 'sqlite_sequence', rowid\n    \"#;\n        if let Some(mut rows) = conn.query(q_tables)? {\n            rows.run_with_row_callback(|row| {\n                let name: &str = row.get::<&str>(0)?;\n                // Skip sqlite_sequence and internal types metadata table\n                if name == \"sqlite_sequence\" || name == turso_core::schema::TURSO_TYPES_TABLE_NAME {\n                    return Ok(());\n                }\n                let ddl: &str = row.get::<&str>(1)?;\n                writeln!(out, \"{ddl};\").map_err(|e| io_error(e, \"write\"))?;\n                Self::dump_table_from_conn(&conn, out, name, &mut progress)?;\n                progress.on(name);\n                Ok(())\n            })?;\n        }\n        Self::dump_sqlite_sequence(&conn, out)?;\n        Self::dump_schema_objects(&conn, out, &mut progress)?;\n        Self::exec_all_conn(&conn, \"COMMIT\")?;\n        writeln!(out, \"COMMIT;\")?;\n        Ok(())\n    }\n\n    fn exec_all_conn(conn: &Arc<Connection>, sql: &str) -> turso_core::Result<()> {\n        if let Some(mut rows) = conn.query(sql)? {\n            rows.run_with_row_callback(|_| Ok(()))?;\n        }\n        Ok(())\n    }\n\n    fn dump_table_from_conn<W: Write, P: ProgressSink>(\n        conn: &Arc<Connection>,\n        out: &mut W,\n        table_name: &str,\n        progress: &mut P,\n    ) -> turso_core::Result<()> {\n        let pragma = format!(\"PRAGMA table_info({})\", quote_ident(table_name));\n        let (mut cols, mut types) = (Vec::new(), Vec::new());\n\n        if let Some(mut rows) = conn.query(pragma)? {\n            rows.run_with_row_callback(|row| {\n                let ty = row.get::<&str>(2)?.to_string();\n                let name = row.get::<&str>(1)?.to_string();\n                match ty.as_str() {\n                    \"index\" => progress.on(&name),\n                    \"view\" => progress.on(&name),\n                    \"trigger\" => progress.on(&name),\n                    _ => {}\n                }\n                cols.push(name);\n                types.push(ty);\n                Ok(())\n            })?;\n        }\n        // FIXME: sqlite has logic to check rowid and optionally preserve it, but it requires\n        // pragma index_list, and it seems to be relevant only for indexes.\n        let cols_str = cols\n            .iter()\n            .map(|c| quote_ident(c))\n            .collect::<Vec<_>>()\n            .join(\", \");\n        let select = format!(\"SELECT {cols_str} FROM {}\", quote_ident(table_name));\n        if let Some(mut rows) = conn.query(select)? {\n            rows.run_with_row_callback(|row| {\n                write!(out, \"INSERT INTO {} VALUES(\", quote_ident(table_name))\n                    .map_err(|e| io_error(e, \"write\"))?;\n                for i in 0..cols.len() {\n                    if i > 0 {\n                        out.write_all(b\",\").map_err(|e| io_error(e, \"write\"))?;\n                    }\n                    let v = row.get::<&Value>(i)?;\n                    Self::write_sql_value_from_value(out, v).map_err(|e| io_error(e, \"write\"))?;\n                }\n                out.write_all(b\");\\n\").map_err(|e| io_error(e, \"write\"))?;\n                Ok(())\n            })?;\n        }\n        Ok(())\n    }\n\n    fn dump_custom_types<W: Write>(conn: &Arc<Connection>, out: &mut W) -> anyhow::Result<()> {\n        // Check if the internal types table exists before querying it.\n        let check = format!(\n            \"SELECT 1 FROM sqlite_schema WHERE name='{}' AND type='table'\",\n            turso_core::schema::TURSO_TYPES_TABLE_NAME\n        );\n        let mut has_types = false;\n        if let Some(mut rows) = conn.query(&check)? {\n            rows.run_with_row_callback(|_| {\n                has_types = true;\n                Ok(())\n            })?;\n        }\n        if !has_types {\n            return Ok(());\n        }\n        let q = format!(\n            \"SELECT sql FROM {} ORDER BY rowid\",\n            turso_core::schema::TURSO_TYPES_TABLE_NAME\n        );\n        if let Some(mut rows) = conn.query(&q)? {\n            rows.run_with_row_callback(|row| {\n                let sql: &str = row.get::<&str>(0)?;\n                writeln!(out, \"{sql};\").map_err(|e| io_error(e, \"write\"))?;\n                Ok(())\n            })?;\n        }\n        Ok(())\n    }\n\n    fn dump_sqlite_sequence<W: Write>(conn: &Arc<Connection>, out: &mut W) -> anyhow::Result<()> {\n        let mut has_seq = false;\n        if let Some(mut rows) =\n            conn.query(\"SELECT 1 FROM sqlite_schema WHERE name='sqlite_sequence' AND type='table'\")?\n        {\n            rows.run_with_row_callback(|_| {\n                has_seq = true;\n                Ok(())\n            })?;\n        }\n        if !has_seq {\n            return Ok(());\n        }\n        writeln!(out, \"DELETE FROM sqlite_sequence;\")?;\n        if let Some(mut rows) = conn.query(\"SELECT name, seq FROM sqlite_sequence\")? {\n            rows.run_with_row_callback(|r| {\n                let name = r.get::<&str>(0)?;\n                let seq = r.get::<i64>(1)?;\n                writeln!(\n                    out,\n                    \"INSERT INTO sqlite_sequence(name,seq) VALUES({},{});\",\n                    sql_quote_string(name),\n                    seq\n                )\n                .map_err(|e| io_error(e, \"write\"))?;\n                Ok(())\n            })?;\n        }\n        Ok(())\n    }\n\n    fn dump_schema_objects<W: Write, P: ProgressSink>(\n        conn: &Arc<Connection>,\n        out: &mut W,\n        progress: &mut P,\n    ) -> anyhow::Result<()> {\n        // SQLite’s shell usually emits views after tables.\n        // Emit only user objects: sql NOT NULL and name NOT LIKE 'sqlite_%'\n        let sql = r#\"\n            SELECT name, sql FROM sqlite_schema\n            WHERE sql NOT NULL\n              AND name NOT LIKE 'sqlite_%'\n              AND type IN ('index','trigger','view')\n            ORDER BY CASE type WHEN 'view' THEN 1 WHEN 'index' THEN 2 WHEN 'trigger' THEN 3 END, rowid\n        \"#;\n        if let Some(mut rows) = conn.query(sql)? {\n            rows.run_with_row_callback(|row| {\n                let ddl: &str = row.get::<&str>(1)?;\n                let name: &str = row.get::<&str>(0)?;\n                progress.on(name);\n                writeln!(out, \"{ddl};\").map_err(|e| io_error(e, \"write\"))?;\n                Ok(())\n            })?;\n        }\n        Ok(())\n    }\n\n    fn write_sql_value_from_value<W: Write>(out: &mut W, v: &Value) -> io::Result<()> {\n        match v {\n            Value::Null => out.write_all(b\"NULL\"),\n            Value::Numeric(Numeric::Integer(i)) => out.write_all(format!(\"{i}\").as_bytes()),\n            Value::Numeric(Numeric::Float(f)) => write!(out, \"{}\", f64::from(*f)).map(|_| ()),\n            Value::Text(s) => {\n                out.write_all(b\"'\")?;\n                let bytes = s.value.as_bytes();\n                let mut i = 0;\n                while i < bytes.len() {\n                    let b = bytes[i];\n                    if b == b'\\'' {\n                        out.write_all(b\"''\")?;\n                    } else {\n                        out.write_all(&[b])?;\n                    }\n                    i += 1;\n                }\n                out.write_all(b\"'\")\n            }\n            Value::Blob(b) => {\n                out.write_all(b\"X'\")?;\n                const HEX: &[u8; 16] = b\"0123456789abcdef\";\n                for &byte in b {\n                    out.write_all(&[HEX[(byte >> 4) as usize], HEX[(byte & 0x0F) as usize]])?;\n                }\n                out.write_all(b\"'\")\n            }\n        }\n    }\n\n    fn dump_database(&mut self) -> anyhow::Result<()> {\n        // Move writer out so we don’t hold a field-borrow of self during the call.\n        let mut out = std::mem::take(&mut self.writer).unwrap();\n        let conn = self.conn.clone();\n        // dont print progress because it would interfere with piping output of .dump\n        let res = Self::dump_database_from_conn(true, conn, &mut out, NoopProgress);\n        // Put writer back\n        self.writer = Some(out);\n        res\n    }\n\n    fn clone_database(&mut self, output_file: &str) -> anyhow::Result<()> {\n        use std::path::Path;\n        if Path::new(output_file).exists() {\n            anyhow::bail!(\"Refusing to overwrite existing file: {output_file}\");\n        }\n        let io: Arc<dyn turso_core::IO> = Arc::new(turso_core::PlatformIO::new()?);\n        let db = Database::open_file(io.clone(), output_file)?;\n        let target = db.connect()?;\n\n        let mut applier = ApplyWriter::new(&target);\n        Self::dump_database_from_conn(false, self.conn.clone(), &mut applier, StderrProgress)?;\n        applier.finish()?;\n        Ok(())\n    }\n\n    fn read_sql_file(&mut self, path: &str) -> anyhow::Result<()> {\n        let file =\n            File::open(path).map_err(|e| anyhow!(\"Error: cannot open \\\"{}\\\" – {}\", path, e))?;\n        let reader = BufReader::new(file);\n\n        let mut query_buffer = String::new();\n        let mut state = ReadState::default();\n\n        for line in reader.lines() {\n            let line = line\n                .map_err(|e| anyhow!(\"Error: file \\\"{}\\\" is not valid UTF-8 text – {}\", path, e))?;\n\n            if !query_buffer.is_empty() {\n                query_buffer.push('\\n');\n            }\n            query_buffer.push_str(&line);\n\n            state.process(&line);\n\n            if state.is_complete() {\n                self.run_query(&query_buffer);\n                query_buffer.clear();\n                state = ReadState::default();\n            }\n        }\n\n        let remaining = query_buffer.trim();\n        if !remaining.is_empty() {\n            self.run_query(remaining);\n        }\n        query_buffer.clear();\n        Ok(())\n    }\n\n    fn save_history(&mut self) {\n        if let Some(rl) = &mut self.rl {\n            let _ = rl.save_history(HISTORY_FILE.as_path());\n        }\n    }\n\n    fn fetch_db_metadata(&mut self) -> anyhow::Result<DbMetadata> {\n        let page_size: i64 = if let Some(mut rows) = self.conn.query(\"PRAGMA page_size\")? {\n            fetch_single_i64(&mut rows).context(\"Failed to execute PRAGMA page_size\")?\n        } else {\n            anyhow::bail!(\"Failed to prepare PRAGMA page_size\");\n        };\n\n        let page_count: i64 = if let Some(mut rows) = self.conn.query(\"PRAGMA page_count\")? {\n            fetch_single_i64(&mut rows).context(\"Failed to execute PRAGMA page_count\")?\n        } else {\n            anyhow::bail!(\"Failed to prepare PRAGMA page_count\");\n        };\n\n        let filename = PathBuf::from(self.opts.db_file.clone())\n            .file_name()\n            .unwrap_or_default()\n            .to_string_lossy()\n            .to_string();\n\n        Ok(DbMetadata {\n            page_size,\n            page_count,\n            filename,\n        })\n    }\n\n    fn write_page_hexdump(&mut self, page: &DbPage, page_size: i64) -> anyhow::Result<()> {\n        let mut seen_page_label = false;\n\n        for (i, chunk) in page.data.chunks(16).enumerate() {\n            if chunk.iter().all(|&b| b == 0) {\n                continue;\n            }\n\n            if !seen_page_label {\n                writeln!(\n                    self,\n                    \"| page {} offset {}\",\n                    page.pgno,\n                    (page.pgno - 1) * page_size\n                )?;\n                seen_page_label = true;\n            }\n\n            // Line offset\n            write!(self, \"|  {:5}:\", i * 16)?;\n\n            // Hex bytes\n            for byte in chunk {\n                write!(self, \" {byte:02x}\")?;\n            }\n            for _ in 0..(16 - chunk.len()) {\n                write!(self, \"   \")?; // Pad partial lines\n            }\n\n            write!(self, \"   \")?;\n\n            // ASCII\n            for &byte in chunk {\n                let ch = match byte {\n                    b' '..=b'~' if ![b'{', b'}', b'\"', b'\\\\'].contains(&byte) => byte as char,\n                    _ => '.',\n                };\n                write!(self, \"{ch}\")?;\n            }\n            writeln!(self)?;\n        }\n        Ok(())\n    }\n\n    fn dump_database_as_text(&mut self, page_no: Option<i64>) -> anyhow::Result<()> {\n        let metadata = self.fetch_db_metadata()?;\n        tracing::debug!(\n            page_size = metadata.page_size,\n            page_count = metadata.page_count,\n            \"Fetched metadata\"\n        );\n\n        if let Some(pgno) = page_no {\n            if pgno <= 0 {\n                anyhow::bail!(\"Page number must be a positive integer.\");\n            }\n            if pgno > metadata.page_count {\n                anyhow::bail!(\n                    \"Page number {pgno} is out of bounds. The database only has {} pages.\",\n                    metadata.page_count\n                );\n            }\n        }\n\n        writeln!(\n            self,\n            \"| size {} pagesize {} filename {}\",\n            metadata.page_count * metadata.page_size,\n            metadata.page_size,\n            &metadata.filename\n        )?;\n\n        let dump_sql = if let Some(pgno) = page_no {\n            format!(\"SELECT pgno, data FROM sqlite_dbpage WHERE pgno = {pgno}\")\n        } else {\n            \"SELECT pgno, data FROM sqlite_dbpage ORDER BY pgno\".to_string()\n        };\n\n        let mut pages: Vec<(i64, Vec<u8>)> = Vec::new();\n        if let Some(mut rows) = self.conn.query(&dump_sql)? {\n            rows.run_with_row_callback(|row| {\n                let pgno: i64 = row.get(0)?;\n                let value: &Value = row.get(1)?;\n                let data: Vec<u8> = match value {\n                    Value::Blob(bytes) => bytes.clone(),\n                    _ => vec![],\n                };\n                pages.push((pgno, data));\n                Ok(())\n            })?;\n        }\n\n        for (pgno, data) in &pages {\n            let page = DbPage { pgno: *pgno, data };\n            self.write_page_hexdump(&page, metadata.page_size)?;\n        }\n\n        writeln!(self, \"| end {}\", &metadata.filename)?;\n\n        Ok(())\n    }\n}\n\nfn quote_ident(s: &str) -> String {\n    let mut out = String::with_capacity(s.len() + 2);\n    out.push('\"');\n    for ch in s.chars() {\n        if ch == '\"' {\n            out.push('\"');\n        }\n        out.push(ch);\n    }\n    out.push('\"');\n    out\n}\n\nfn sql_quote_string(s: &str) -> String {\n    let mut out = String::with_capacity(s.len() + 2);\n    out.push('\\'');\n    for ch in s.chars() {\n        if ch == '\\'' {\n            out.push('\\'');\n        }\n        out.push(ch);\n    }\n    out.push('\\'');\n    out\n}\n\nfn validate_parameter_name(name: &str) -> Result<(), String> {\n    if name.is_empty() {\n        return Err(\"parameter name cannot be empty\".to_string());\n    }\n\n    match name.as_bytes()[0] {\n        b':' | b'@' | b'$' | b'#' => Ok(()),\n        b'?' => {\n            let Some(rest) = name.strip_prefix('?') else {\n                return Err(\"invalid parameter name\".to_string());\n            };\n            if rest.is_empty() {\n                return Err(\"parameter name '?N' must include digits\".to_string());\n            }\n            if rest.parse::<usize>().ok().filter(|idx| *idx > 0).is_none() {\n                return Err(\"parameter name '?N' must use an index >= 1\".to_string());\n            }\n            Ok(())\n        }\n        _ => Err(\"parameter name must start with one of ':', '@', '$', '#', '?'\".to_string()),\n    }\n}\n\nfn parameter_name_to_index(name: &str) -> Option<NonZeroUsize> {\n    let value = name.strip_prefix('?')?.parse::<usize>().ok()?;\n    value.try_into().ok()\n}\n\nfn parse_parameter_value(value: &str) -> Result<Value, String> {\n    let value = value.trim();\n    if value.eq_ignore_ascii_case(\"null\") {\n        return Ok(Value::Null);\n    }\n\n    if let Ok(integer) = value.parse::<i64>() {\n        return Ok(Value::from_i64(integer));\n    }\n\n    if value.contains(['.', 'e', 'E']) {\n        if let Ok(float) = value.parse::<f64>() {\n            return Ok(Value::from_f64(float));\n        }\n    }\n\n    if let Some(hex) = value\n        .strip_prefix(\"x'\")\n        .or_else(|| value.strip_prefix(\"X'\"))\n        .and_then(|stripped| stripped.strip_suffix('\\''))\n    {\n        return parse_hex_blob(hex).map(Value::from_blob);\n    }\n\n    if let Some(inner) = value\n        .strip_prefix('\\'')\n        .and_then(|stripped| stripped.strip_suffix('\\''))\n    {\n        return Ok(Value::build_text(unescape_single_quoted(inner)));\n    }\n\n    Ok(Value::build_text(value.to_owned()))\n}\n\nfn parse_hex_blob(hex: &str) -> Result<Vec<u8>, String> {\n    if !hex.len().is_multiple_of(2) {\n        return Err(\"hex blob literal must contain an even number of digits\".to_string());\n    }\n\n    let mut out = Vec::with_capacity(hex.len() / 2);\n    let mut bytes = hex.as_bytes().iter().copied();\n    while let (Some(hi), Some(lo)) = (bytes.next(), bytes.next()) {\n        let h = decode_hex_nibble(hi)?;\n        let l = decode_hex_nibble(lo)?;\n        out.push((h << 4) | l);\n    }\n    Ok(out)\n}\n\nfn decode_hex_nibble(byte: u8) -> Result<u8, String> {\n    match byte {\n        b'0'..=b'9' => Ok(byte - b'0'),\n        b'a'..=b'f' => Ok(10 + (byte - b'a')),\n        b'A'..=b'F' => Ok(10 + (byte - b'A')),\n        _ => Err(\"hex blob literal contains non-hex characters\".to_string()),\n    }\n}\n\nfn unescape_single_quoted(s: &str) -> String {\n    if !s.contains(\"''\") {\n        return s.to_owned();\n    }\n\n    s.replace(\"''\", \"'\")\n}\n\nimpl Drop for Limbo {\n    fn drop(&mut self) {\n        self.save_history();\n        unsafe {\n            ManuallyDrop::drop(&mut self.input_buff);\n        }\n    }\n}\n\nfn fetch_single_i64(rows: &mut turso_core::Statement) -> anyhow::Result<i64> {\n    let mut result: Option<i64> = None;\n    rows.run_with_row_callback(|row| {\n        result = Some(row.get(0)?);\n        Ok(())\n    })?;\n    result.ok_or_else(|| anyhow!(\"query did not return a row\"))\n}\n\n/// Normalize `path?key=val` to `file:path?key=val` so query parameters\n/// are parsed as URI options (e.g. `?locking=shared_reads`) instead of\n/// being treated as part of the filename.\n///\n/// Only the *last* `?` that introduces a valid `key=value` query string is\n/// treated as the query separator. Earlier `?` characters are\n/// percent-encoded (`%3F`) so they remain part of the filename.\n/// A trailing `?` with no `key=value` pair is left alone (it is just part\n/// of the filename).\nfn normalize_db_path(db_file: String) -> String {\n    if db_file.starts_with(\"file:\") {\n        return db_file;\n    }\n\n    // Walk from the right to find the last '?' whose suffix looks like\n    // query parameters (contains at least one '=').\n    if let Some(pos) = db_file.rfind('?') {\n        let query = &db_file[pos + 1..];\n        if query.contains('=') {\n            let path = &db_file[..pos];\n            // Percent-encode any '?' inside the path portion so the URI\n            // parser does not mistake them for the query separator.\n            let encoded_path = path.replace('?', \"%3F\");\n            return format!(\"file:{encoded_path}?{query}\");\n        }\n    }\n\n    db_file\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_normalize_db_path_adds_file_prefix_for_query_params() {\n        assert_eq!(\n            normalize_db_path(\"test.db?locking=shared_reads\".into()),\n            \"file:test.db?locking=shared_reads\"\n        );\n    }\n\n    #[test]\n    fn test_normalize_db_path_preserves_existing_file_prefix() {\n        assert_eq!(\n            normalize_db_path(\"file:test.db?mode=ro\".into()),\n            \"file:test.db?mode=ro\"\n        );\n    }\n\n    #[test]\n    fn test_normalize_db_path_preserves_file_triple_slash() {\n        assert_eq!(\n            normalize_db_path(\"file:///tmp/test.db?mode=ro\".into()),\n            \"file:///tmp/test.db?mode=ro\"\n        );\n    }\n\n    #[test]\n    fn test_normalize_db_path_plain_path_unchanged() {\n        assert_eq!(normalize_db_path(\"test.db\".into()), \"test.db\");\n    }\n\n    #[test]\n    fn test_normalize_db_path_memory_unchanged() {\n        assert_eq!(normalize_db_path(\":memory:\".into()), \":memory:\");\n    }\n\n    #[test]\n    fn test_normalize_db_path_multiple_query_params() {\n        assert_eq!(\n            normalize_db_path(\"test.db?locking=shared_reads&cache=shared\".into()),\n            \"file:test.db?locking=shared_reads&cache=shared\"\n        );\n    }\n\n    #[test]\n    fn test_normalize_db_path_absolute_path_with_query() {\n        assert_eq!(\n            normalize_db_path(\"/tmp/my.db?mode=ro\".into()),\n            \"file:/tmp/my.db?mode=ro\"\n        );\n    }\n\n    #[test]\n    fn test_normalize_db_path_question_mark_in_filename_no_query() {\n        // '?' is legitimately part of the filename, no key=value follows\n        assert_eq!(normalize_db_path(\"what?.db\".into()), \"what?.db\");\n    }\n\n    #[test]\n    fn test_normalize_db_path_filename_contains_question_mark_with_query() {\n        // File is literally \"foo.bar?mode=ro\", opened with ?mode=ro query.\n        // The '?' in the filename must be percent-encoded so the URI parser\n        // treats only the last ?mode=ro as the query string.\n        assert_eq!(\n            normalize_db_path(\"foo.bar?mode=ro?mode=ro\".into()),\n            \"file:foo.bar%3Fmode=ro?mode=ro\"\n        );\n    }\n}\n"
  },
  {
    "path": "cli/build.rs",
    "content": "//! Build.rs script to generate a binary syntax set for syntect\n//! based on the SQL.sublime-syntax file.\n\nuse std::env;\nuse std::path::Path;\n\nuse syntect::dumps::dump_to_uncompressed_file;\nuse syntect::parsing::SyntaxDefinition;\nuse syntect::parsing::SyntaxSet;\n\nfn main() {\n    println!(\"cargo::rerun-if-changed=SQL.sublime-syntax\");\n    println!(\"cargo::rerun-if-changed=build.rs\");\n    println!(\"cargo::rerun-if-changed=manuals\");\n\n    let out_dir = env::var_os(\"OUT_DIR\").unwrap();\n    let syntax =\n        SyntaxDefinition::load_from_str(include_str!(\"./SQL.sublime-syntax\"), false, None).unwrap();\n    let mut ps = SyntaxSet::new().into_builder();\n    ps.add(syntax);\n    let ps = ps.build();\n    dump_to_uncompressed_file(\n        &ps,\n        Path::new(&out_dir).join(\"SQL_syntax_set_dump.packdump\"),\n    )\n    .unwrap();\n}\n"
  },
  {
    "path": "cli/commands/args.rs",
    "content": "use clap::{Args, ValueEnum};\nuse clap_complete::{ArgValueCompleter, CompletionCandidate, PathCompleter};\n\nuse crate::{input::OutputMode, opcodes_dictionary::OPCODE_DESCRIPTIONS};\n\n#[derive(Debug, Clone, Args)]\npub struct IndexesArgs {\n    /// Name of table\n    pub tbl_name: Option<String>,\n}\n\n#[derive(Debug, Clone, Args)]\npub struct ExitArgs {\n    /// Exit code\n    #[arg(default_value_t = 0)]\n    pub code: i32,\n}\n\n#[derive(Debug, Clone, Args)]\npub struct OpenArgs {\n    /// Path to open database\n    #[arg(add = ArgValueCompleter::new(PathCompleter::file()))]\n    pub path: String,\n    // TODO see how to have this completed with the output of List Vfs function\n    // Currently not possible to pass arbitrary\n    /// Name of VFS\n    pub vfs_name: Option<String>,\n}\n\n#[derive(Debug, Clone, Args)]\npub struct SchemaArgs {\n    // TODO depends on PRAGMA table_list for completions\n    /// Table name to visualize schema\n    pub table_name: Option<String>,\n}\n\n#[derive(Debug, Clone, Args)]\npub struct SetOutputArgs {\n    /// File path to send output to\n    #[arg(add = ArgValueCompleter::new(PathCompleter::file()))]\n    pub path: Option<String>,\n}\n\n#[derive(Debug, Clone, Args)]\npub struct OutputModeArgs {\n    #[arg(value_enum)]\n    pub mode: OutputMode,\n}\n\nfn opcodes_completer(current: &std::ffi::OsStr) -> Vec<CompletionCandidate> {\n    let mut completions = vec![];\n\n    let Some(current) = current.to_str() else {\n        return completions;\n    };\n\n    let current = current.to_lowercase();\n\n    let opcodes = &OPCODE_DESCRIPTIONS;\n\n    for op in opcodes {\n        // TODO if someone know how to do prefix_match with case insensitve in Rust\n        // without converting the String to lowercase first, please fix this.\n        let op_name = op.name.to_ascii_lowercase();\n        if op_name.starts_with(&current) {\n            completions.push(CompletionCandidate::new(op.name).help(Some(op.description.into())));\n        }\n    }\n\n    completions\n}\n\n#[derive(Debug, Clone, Args)]\npub struct OpcodesArgs {\n    /// Opcode to display description\n    #[arg(add = ArgValueCompleter::new(opcodes_completer))]\n    pub opcode: Option<String>,\n}\n\n#[derive(Debug, Clone, Args)]\npub struct CwdArgs {\n    /// Target directory\n    #[arg(add = ArgValueCompleter::new(PathCompleter::dir()))]\n    pub directory: String,\n}\n\n#[derive(Debug, Clone, Args)]\npub struct NullValueArgs {\n    pub value: String,\n}\n\n#[derive(Debug, Clone, Args)]\npub struct StatsArgs {\n    /// Toggle stats mode: on or off\n    #[arg(value_enum)]\n    pub toggle: Option<StatsToggle>,\n    /// Reset stats after displaying\n    #[arg(long, short, default_value_t = false)]\n    pub reset: bool,\n}\n\n#[derive(Debug, ValueEnum, Clone)]\npub enum StatsToggle {\n    /// Enable automatic stats display after each statement\n    On,\n    /// Disable automatic stats display\n    Off,\n}\n\n#[derive(Debug, Clone, Args)]\npub struct EchoArgs {\n    #[arg(value_enum)]\n    pub mode: EchoMode,\n}\n\n#[derive(Debug, ValueEnum, Clone)]\npub enum EchoMode {\n    On,\n    Off,\n}\n\n#[derive(Debug, Clone, Args)]\npub struct TablesArgs {\n    pub pattern: Option<String>,\n}\n\n#[derive(Debug, Clone, Args)]\npub struct LoadExtensionArgs {\n    /// Path to extension file\n    #[arg(add = ArgValueCompleter::new(PathCompleter::file()))]\n    pub path: String,\n}\n\n#[derive(Debug, ValueEnum, Clone)]\npub enum TimerMode {\n    On,\n    Off,\n}\n\n#[derive(Debug, Clone, Args)]\npub struct TimerArgs {\n    #[arg(value_enum)]\n    pub mode: TimerMode,\n}\n\n#[derive(Debug, ValueEnum, Clone)]\npub enum DbConfigMode {\n    On,\n    Off,\n}\n\n#[derive(Debug, Clone, Args)]\npub struct DbConfigArgs {\n    pub config: Option<String>,\n    #[arg(value_enum)]\n    pub mode: Option<DbConfigMode>,\n}\n\n#[derive(Debug, Clone, Args)]\npub struct HeadersArgs {\n    pub mode: HeadersMode,\n}\n\n#[derive(Debug, Clone, Args)]\npub struct CloneArgs {\n    pub output_file: String,\n}\n\n#[derive(Debug, Clone, Args)]\npub struct ManualArgs {\n    /// The manual page to display (e.g., \"mcp\")\n    pub page: Option<String>,\n}\n\n#[derive(Debug, Clone, Args)]\npub struct ReadArgs {\n    /// Path to the SQL file to execute\n    #[arg(add = ArgValueCompleter::new(PathCompleter::file()))]\n    pub path: String,\n}\n\n#[derive(Debug, Clone, Args)]\npub struct ParameterArgs {\n    #[command(subcommand)]\n    pub command: ParameterCommand,\n}\n\n#[derive(Debug, Clone, clap::Subcommand)]\npub enum ParameterCommand {\n    /// Set a parameter value\n    Set(ParameterSetArgs),\n    /// List all stored parameters\n    List,\n    /// Clear one parameter or all parameters\n    Clear(ParameterClearArgs),\n}\n\n#[derive(Debug, Clone, Args)]\npub struct ParameterSetArgs {\n    /// Parameter name like :name, @name, $name, ?1\n    pub name: String,\n    /// Parameter value\n    pub value: String,\n}\n\n#[derive(Debug, Clone, Args)]\npub struct ParameterClearArgs {\n    /// Parameter name to clear. If omitted, clears all.\n    pub name: Option<String>,\n}\n\n#[derive(ValueEnum, Copy, Clone, Debug, PartialEq, Eq)]\npub enum HeadersMode {\n    On,\n    Off,\n}\n\n#[derive(Debug, Clone, clap::Parser)]\npub struct DbtotxtArgs {\n    #[clap(long = \"page\")]\n    pub page_no: Option<i64>,\n}\n"
  },
  {
    "path": "cli/commands/import.rs",
    "content": "use clap::Args;\nuse clap_complete::{ArgValueCompleter, PathCompleter};\nuse std::{fs::File, io::Write, path::PathBuf, sync::Arc};\nuse turso_core::{Connection, LimboError};\n\n#[derive(Debug, Clone, Args)]\npub struct ImportArgs {\n    /// Use , and \\n as column and row separators\n    #[arg(long, default_value = \"true\")]\n    csv: bool,\n    /// \"Verbose\" - increase auxiliary output\n    #[arg(short, default_value = \"false\")]\n    verbose: bool,\n    /// Skip the first N rows of input\n    #[arg(long, default_value = \"0\")]\n    skip: u64,\n    #[arg(add = ArgValueCompleter::new(PathCompleter::file()))]\n    file: PathBuf,\n    table: String,\n}\n\npub struct ImportFile<'a> {\n    conn: Arc<Connection>,\n    writer: &'a mut dyn Write,\n}\n\nimpl<'a> ImportFile<'a> {\n    pub fn new(conn: Arc<Connection>, writer: &'a mut dyn Write) -> Self {\n        Self { conn, writer }\n    }\n\n    pub fn import(&mut self, args: ImportArgs) {\n        self.import_csv(args);\n    }\n\n    pub fn import_csv(&mut self, args: ImportArgs) {\n        // Check if the target table exists\n        let table_check_query = format!(\n            \"SELECT name FROM sqlite_master WHERE type='table' AND name='{}';\",\n            args.table\n        );\n\n        let mut table_exists = false;\n\n        match self.conn.query(table_check_query) {\n            Ok(rows) => {\n                if let Some(mut rows) = rows {\n                    let res = rows.run_with_row_callback(|_| {\n                        table_exists = true;\n                        Ok(())\n                    });\n                    if let Err(e) = res {\n                        let _ = self.writer.write_all(\n                            format!(\"Error checking table existence: {e:?}\\n\").as_bytes(),\n                        );\n                        return;\n                    }\n                }\n            }\n            Err(e) => {\n                let _ = self\n                    .writer\n                    .write_all(format!(\"Error checking table existence: {e:?}\\n\").as_bytes());\n                return;\n            }\n        }\n\n        let file = match File::open(args.file) {\n            Ok(file) => file,\n            Err(e) => {\n                let _ = self.writer.write_all(format!(\"{e:?}\\n\").as_bytes());\n                return;\n            }\n        };\n\n        let mut rdr = csv::ReaderBuilder::new()\n            .has_headers(false)\n            .from_reader(file);\n\n        let mut success_rows = 0u64;\n        let mut failed_rows = 0u64;\n\n        let mut records = rdr.records().skip(args.skip as usize).peekable();\n\n        // If table doesn't exist, use first row as header to create table\n        if !table_exists {\n            if let Some(Ok(header)) = records.next() {\n                let columns = header\n                    .iter()\n                    .map(normalize_ident)\n                    .collect::<Vec<_>>()\n                    .join(\", \");\n                let create_table = format!(\"CREATE TABLE {} ({});\", args.table, columns);\n\n                let rows = match self.conn.query(create_table) {\n                    Ok(rows) => rows,\n                    Err(e) => {\n                        let _ = self\n                            .writer\n                            .write_all(format!(\"Error creating table: {e:?}\\n\").as_bytes());\n                        return;\n                    }\n                };\n                let Some(mut rows) = rows else {\n                    let _ = self.writer.write_all(b\"Error creating table\\n\");\n                    return;\n                };\n\n                let res = rows.run_with_row_callback(|_| {\n                    // Not expected for CREATE TABLE\n                    panic!(\"Unexpected row for CREATE TABLE\");\n                });\n                match res {\n                    Ok(_) => {}\n                    Err(LimboError::Busy | LimboError::Interrupt) => {\n                        let _ = self\n                            .writer\n                            .write_all(\"Error creating table: interrupted / busy\\n\".as_bytes());\n                        return;\n                    }\n                    Err(e) => {\n                        let _ = self.writer.write_all(\n                            format!(\"Error checking table existence: {e:?}\\n\").as_bytes(),\n                        );\n                        return;\n                    }\n                }\n            } else {\n                let _ = self.writer.write_all(b\"Error: Empty input file\\n\");\n                return;\n            }\n        }\n\n        /// TODO: should this be in a single transaction (i.e. all or nothing)?\n        const CSV_INSERT_BATCH_SIZE: usize = 1000;\n        let mut batch = Vec::with_capacity(CSV_INSERT_BATCH_SIZE);\n        for result in records {\n            let record = match result {\n                Ok(r) => r,\n                Err(e) => {\n                    failed_rows += 1;\n                    let _ = self\n                        .writer\n                        .write_all(format!(\"Error reading row: {e:?}\\n\").as_bytes());\n                    continue;\n                }\n            };\n\n            if !record.is_empty() {\n                let values: Vec<String> = record\n                    .iter()\n                    .map(|r| format!(\"'{}'\", r.replace(\"'\", \"''\")))\n                    .collect();\n                batch.push(values.join(\",\"));\n\n                if batch.len() >= CSV_INSERT_BATCH_SIZE {\n                    println!(\"Inserting batch of {} rows\", batch.len());\n                    let insert_string =\n                        format!(\"INSERT INTO {} VALUES ({});\", args.table, batch.join(\"),(\"));\n\n                    match self.conn.query(insert_string) {\n                        Ok(rows) => {\n                            if let Some(mut rows) = rows {\n                                let res = rows.run_with_row_callback(|_| {\n                                    panic!(\"Unexpected row for INSERT\");\n                                });\n                                match res {\n                                    Ok(_) => {\n                                        success_rows += batch.len() as u64;\n                                    }\n                                    Err(LimboError::Interrupt) => {\n                                        let _ = self.writer.write_all(b\"interrupt\\n\");\n\n                                        failed_rows += batch.len() as u64;\n                                    }\n                                    Err(LimboError::Busy) => {\n                                        let _ = self.writer.write_all(b\"database is busy\\n\");\n\n                                        failed_rows += batch.len() as u64;\n                                    }\n                                    Err(e) => {\n                                        let _ = self.writer.write_all(\n                                            format!(\"Error executing query: {e:?}\\n\").as_bytes(),\n                                        );\n                                        failed_rows += batch.len() as u64;\n                                    }\n                                }\n                            } else {\n                                success_rows += batch.len() as u64;\n                            }\n                        }\n                        Err(e) => {\n                            let _ = self\n                                .writer\n                                .write_all(format!(\"Error executing query: {e:?}\\n\").as_bytes());\n                            failed_rows += batch.len() as u64;\n                        }\n                    }\n                    batch.clear();\n                }\n            }\n        }\n\n        // Insert remaining records\n        if !batch.is_empty() {\n            let insert_string =\n                format!(\"INSERT INTO {} VALUES ({});\", args.table, batch.join(\"),(\"));\n\n            match self.conn.query(insert_string) {\n                Ok(rows) => {\n                    if let Some(mut rows) = rows {\n                        let res = rows.run_with_row_callback(|_| {\n                            panic!(\"Unexpected row for INSERT\");\n                        });\n                        match res {\n                            Ok(_) => {\n                                success_rows += batch.len() as u64;\n                            }\n                            Err(LimboError::Interrupt) => {\n                                let _ = self.writer.write_all(b\"interrupt\\n\");\n\n                                failed_rows += batch.len() as u64;\n                            }\n                            Err(LimboError::Busy) => {\n                                let _ = self.writer.write_all(b\"database is busy\\n\");\n\n                                failed_rows += batch.len() as u64;\n                            }\n                            Err(e) => {\n                                let _ = self.writer.write_all(\n                                    format!(\"Error executing query: {e:?}\\n\").as_bytes(),\n                                );\n                                failed_rows += batch.len() as u64;\n                            }\n                        }\n                    } else {\n                        success_rows += batch.len() as u64;\n                    }\n                }\n                Err(e) => {\n                    let _ = self\n                        .writer\n                        .write_all(format!(\"Error executing query: {e:?}\\n\").as_bytes());\n                    failed_rows += batch.len() as u64;\n                }\n            }\n        }\n\n        if args.verbose {\n            let _ = self.writer.write_all(\n                format!(\n                    \"Added {} rows with {} errors using {} lines of input\",\n                    success_rows,\n                    failed_rows,\n                    success_rows + failed_rows,\n                )\n                .as_bytes(),\n            );\n        }\n    }\n}\n\n// https://sqlite.org/lang_keywords.html\nconst QUOTE_PAIRS: &[(char, char)] = &[('\"', '\"'), ('[', ']'), ('`', '`')];\n\npub fn normalize_ident(identifier: &str) -> String {\n    let quote_pair = QUOTE_PAIRS\n        .iter()\n        .find(|&(start, end)| identifier.starts_with(*start) && identifier.ends_with(*end));\n\n    if let Some(&(_, _)) = quote_pair {\n        &identifier[1..identifier.len() - 1]\n    } else {\n        identifier\n    }\n    .to_lowercase()\n}\n"
  },
  {
    "path": "cli/commands/mod.rs",
    "content": "pub mod args;\npub mod import;\n\nuse args::{\n    CwdArgs, DbConfigArgs, DbtotxtArgs, EchoArgs, ExitArgs, HeadersArgs, IndexesArgs,\n    LoadExtensionArgs, ManualArgs, NullValueArgs, OpcodesArgs, OpenArgs, OutputModeArgs,\n    ParameterArgs, ReadArgs, SchemaArgs, SetOutputArgs, StatsArgs, TablesArgs, TimerArgs,\n};\nuse clap::Parser;\nuse import::ImportArgs;\n\nuse crate::{\n    commands::args::CloneArgs,\n    input::{AFTER_HELP_MSG, BEFORE_HELP_MSG},\n};\n\n#[derive(Parser, Debug)]\n#[command(\n    multicall = true,\n    arg_required_else_help(false),\n    before_help(BEFORE_HELP_MSG),\n    after_help(AFTER_HELP_MSG),\n    // help_template(HELP_TEMPLATE)\n)]\npub struct CommandParser {\n    #[command(subcommand)]\n    pub command: Command,\n}\n\n#[derive(Debug, Clone, clap::Subcommand)]\n#[command(disable_help_flag(false), disable_version_flag(true))]\npub enum Command {\n    /// Exit this program with return-code CODE\n    #[command(display_name = \".exit\", alias = \"ex\", alias = \"exi\")]\n    Exit(ExitArgs),\n    /// Quit the shell\n    #[command(display_name = \".quit\", alias = \"q\", alias = \"qu\", alias = \"qui\")]\n    Quit,\n    /// Open a database file\n    #[command(display_name = \".open\")]\n    Open(OpenArgs),\n    /// Display schema for a table\n    #[command(display_name = \".schema\")]\n    Schema(SchemaArgs),\n    /// Set output file (or stdout if empty)\n    #[command(name = \"output\", display_name = \".output\")]\n    SetOutput(SetOutputArgs),\n    /// Set output display mode\n    #[command(name = \"mode\", display_name = \".mode\", arg_required_else_help(false))]\n    OutputMode(OutputModeArgs),\n    /// Show vdbe opcodes\n    #[command(name = \"opcodes\", display_name = \".opcodes\")]\n    Opcodes(OpcodesArgs),\n    /// Change the current working directory\n    #[command(name = \"cd\", display_name = \".cd\")]\n    Cwd(CwdArgs),\n    /// Display information about settings\n    #[command(name = \"show\", display_name = \".show\")]\n    ShowInfo,\n    /// Set the value of NULL to be printed in 'list' mode\n    #[command(name = \"nullvalue\", display_name = \".nullvalue\")]\n    NullValue(NullValueArgs),\n    /// Toggle 'echo' mode to repeat commands before execution\n    #[command(display_name = \".echo\")]\n    Echo(EchoArgs),\n    /// Display tables\n    Tables(TablesArgs),\n    /// Display attached databases\n    Databases,\n    /// Import data from FILE into TABLE\n    #[command(name = \"import\", display_name = \".import\")]\n    Import(ImportArgs),\n    /// Loads an extension library\n    #[command(name = \"load\", display_name = \".load\")]\n    LoadExtension(LoadExtensionArgs),\n    /// Dump the current database as a list of SQL statements\n    Dump,\n    /// Print or set the current configuration for the database. Currently ignored.\n    #[command(name = \"dbconfig\", display_name = \".dbconfig\")]\n    DbConfig(DbConfigArgs),\n    /// Display database statistics\n    #[command(name = \"stats\", display_name = \".stats\")]\n    Stats(StatsArgs),\n    /// List vfs modules available\n    #[command(name = \"vfslist\", display_name = \".vfslist\")]\n    ListVfs,\n    /// Show names of indexes\n    #[command(name = \"indexes\", display_name = \".indexes\")]\n    ListIndexes(IndexesArgs),\n    #[command(name = \"timer\", display_name = \".timer\")]\n    Timer(TimerArgs),\n    /// Toggle column headers on/off in list mode\n    #[command(name = \"headers\", display_name = \".headers\")]\n    Headers(HeadersArgs),\n    #[command(name = \"clone\", display_name = \".clone\")]\n    Clone(CloneArgs),\n    /// Display manual pages for features\n    #[command(name = \"manual\", display_name = \".manual\", alias = \"man\")]\n    Manual(ManualArgs),\n    /// Execute SQL statements from a file\n    #[command(name = \"read\", display_name = \".read\")]\n    Read(ReadArgs),\n    /// Manage SQL parameter bindings\n    #[command(name = \"parameter\", display_name = \".parameter\", alias = \"param\")]\n    Parameter(ParameterArgs),\n    #[command(name = \"dbtotxt\", display_name = \".dbtotxt\")]\n    Dbtotxt(DbtotxtArgs),\n}\n\nconst _HELP_TEMPLATE: &str = \"{before-help}{name}\n{usage-heading} {usage}\n\n{all-args}{after-help}\n\";\n\n#[cfg(test)]\nmod tests {\n    use super::CommandParser;\n\n    #[test]\n    fn cli_assert() {\n        use clap::CommandFactory;\n        CommandParser::command().debug_assert();\n    }\n}\n"
  },
  {
    "path": "cli/config/mod.rs",
    "content": "mod palette;\nmod terminal;\n\nuse crate::input::OutputMode;\nuse crate::HOME_DIR;\nuse nu_ansi_term::Color;\nuse palette::LimboColor;\nuse schemars::JsonSchema;\nuse serde::{Deserialize, Deserializer};\nuse std::fmt::Debug;\nuse std::fs::read_to_string;\nuse std::path::PathBuf;\nuse std::sync::LazyLock;\nuse terminal::{TerminalDetector, TerminalTheme};\nuse validator::Validate;\n\npub static CONFIG_DIR: LazyLock<PathBuf> = LazyLock::new(|| HOME_DIR.join(\".config/limbo\"));\n\nfn ok_or_default<'de, T, D>(deserializer: D) -> Result<T, D::Error>\nwhere\n    T: Deserialize<'de> + Default + Validate + Debug,\n    D: Deserializer<'de>,\n{\n    let v: toml::Value = Deserialize::deserialize(deserializer)?;\n    let x = T::deserialize(v)\n        .map(|v| {\n            let validate = v.validate();\n            if validate.is_err() {\n                tracing::error!(\n                    \"Invalid value for {}.\\n Original config value: {:?}\",\n                    validate.unwrap_err(),\n                    v\n                );\n                T::default()\n            } else {\n                v\n            }\n        })\n        .unwrap_or_default();\n    Ok(x)\n}\n\n#[derive(Debug, Deserialize, Clone, Default, JsonSchema)]\n#[serde(default, deny_unknown_fields)]\npub struct Config {\n    #[serde(deserialize_with = \"ok_or_default\")]\n    pub table: TableConfig,\n    pub highlight: HighlightConfig,\n}\n\nimpl Config {\n    pub fn from_config_file(path: PathBuf) -> Self {\n        if let Some(config) = Self::read_config_str(path) {\n            Self::from_config_str(&config)\n        } else {\n            Self::default()\n        }\n    }\n\n    pub fn from_config_str(config: &str) -> Self {\n        toml::from_str(config)\n            .inspect_err(|err| tracing::error!(\"{}\", err))\n            .unwrap_or_default()\n    }\n\n    fn read_config_str(path: PathBuf) -> Option<String> {\n        if path.exists() {\n            tracing::trace!(\"Trying to read from {:?}\", path);\n\n            let result = read_to_string(path);\n\n            if result.is_err() {\n                tracing::debug!(\"Error reading file: {:?}\", result);\n            } else {\n                tracing::trace!(\"File read successfully\");\n            };\n\n            result.ok()\n        } else {\n            None\n        }\n    }\n\n    pub fn for_output_mode(mode: OutputMode) -> Self {\n        let table = if mode == OutputMode::Pretty {\n            TableConfig::adaptive_colors()\n        } else {\n            TableConfig::no_colors()\n        };\n        Self {\n            table,\n            highlight: HighlightConfig::default(),\n        }\n    }\n}\n\n#[derive(Debug, Deserialize, Clone, JsonSchema, Validate)]\n#[serde(default, deny_unknown_fields)]\npub struct TableConfig {\n    #[serde(default = \"TableConfig::default_header_color\")]\n    pub header_color: LimboColor,\n    #[serde(default = \"TableConfig::default_column_colors\")]\n    #[validate(length(min = 1))]\n    pub column_colors: Vec<LimboColor>,\n}\n\nimpl Default for TableConfig {\n    fn default() -> Self {\n        // Always use adaptive colors based on terminal theme\n        Self::adaptive_colors()\n    }\n}\n\nimpl TableConfig {\n    // These methods are needed for serde default attributes\n    fn default_header_color() -> LimboColor {\n        // Use adaptive colors for serde defaults too\n        Self::adaptive_colors().header_color\n    }\n\n    fn default_column_colors() -> Vec<LimboColor> {\n        // Use adaptive colors for serde defaults too\n        Self::adaptive_colors().column_colors\n    }\n\n    /// Get adaptive colors based on detected terminal theme\n    pub fn adaptive_colors() -> Self {\n        let theme = TerminalDetector::detect_theme();\n        match theme {\n            TerminalTheme::Light => Self::light_theme_colors(),\n            TerminalTheme::Dark => Self::dark_theme_colors(),\n            TerminalTheme::Unknown => Self::no_colors(), // No colors for unsupported platforms\n        }\n    }\n\n    /// No colors configuration - for Windows or when detection fails\n    fn no_colors() -> Self {\n        Self {\n            header_color: LimboColor(Color::Default),\n            column_colors: vec![LimboColor(Color::Default)],\n        }\n    }\n\n    /// Colors optimized for light terminal backgrounds\n    fn light_theme_colors() -> Self {\n        Self {\n            header_color: LimboColor(Color::Black),\n            column_colors: vec![\n                LimboColor(Color::Fixed(22)), // Dark green\n                LimboColor(Color::Fixed(17)), // Dark blue\n                LimboColor(Color::Fixed(88)), // Dark red\n                LimboColor(Color::Fixed(94)), // Orange\n                LimboColor(Color::Fixed(55)), // Purple\n            ],\n        }\n    }\n\n    /// Colors optimized for dark terminal backgrounds\n    fn dark_theme_colors() -> Self {\n        Self {\n            header_color: LimboColor(Color::LightGray),\n            column_colors: vec![\n                LimboColor(Color::LightGreen),\n                LimboColor(Color::LightBlue),\n                LimboColor(Color::LightCyan),\n                LimboColor(Color::LightYellow),\n                LimboColor(Color::LightMagenta),\n            ],\n        }\n    }\n}\n\n#[derive(Debug, Deserialize, Clone, JsonSchema)]\n#[serde(default, deny_unknown_fields)]\npub struct HighlightConfig {\n    pub enable: bool,\n    pub theme: String,\n    pub prompt: LimboColor,\n    pub hint: LimboColor,\n    pub candidate: LimboColor,\n}\n\nimpl Default for HighlightConfig {\n    fn default() -> Self {\n        Self {\n            enable: true,\n            theme: \"base16-ocean.dark\".to_string(),\n            prompt: LimboColor(Color::Rgb(34u8, 197u8, 94u8)),\n            hint: LimboColor(Color::Rgb(107u8, 114u8, 128u8)),\n            candidate: LimboColor(Color::Green),\n        }\n    }\n}\n"
  },
  {
    "path": "cli/config/palette.rs",
    "content": "use core::fmt;\nuse std::{\n    fmt::Display,\n    ops::{Deref, DerefMut},\n};\n\nuse nu_ansi_term::Color;\nuse schemars::JsonSchema;\nuse serde::{\n    de::{self, Visitor},\n    Deserialize, Deserializer, Serialize,\n};\nuse tracing::trace;\nuse validator::Validate;\n\n#[derive(Debug, Clone, Serialize)]\npub struct LimboColor(pub Color);\n\nimpl TryFrom<&str> for LimboColor {\n    type Error = String;\n\n    fn try_from(value: &str) -> Result<Self, Self::Error> {\n        // Parse RGB hex values\n        trace!(\"Parsing color_string: {}\", value);\n\n        let color = match value.chars().collect::<Vec<_>>()[..] {\n            // #rrggbb hex color\n            ['#', r1, r2, g1, g2, b1, b2] => {\n                let r = u8::from_str_radix(&format!(\"{r1}{r2}\"), 16).map_err(|e| e.to_string())?;\n                let g = u8::from_str_radix(&format!(\"{g1}{g2}\"), 16).map_err(|e| e.to_string())?;\n                let b = u8::from_str_radix(&format!(\"{b1}{b2}\"), 16).map_err(|e| e.to_string())?;\n                Some(Color::Rgb(r, g, b))\n            }\n            // #rgb shorthand hex color\n            ['#', r, g, b] => {\n                let r = u8::from_str_radix(&format!(\"{r}{r}\"), 16).map_err(|e| e.to_string())?;\n                let g = u8::from_str_radix(&format!(\"{g}{g}\"), 16).map_err(|e| e.to_string())?;\n                let b = u8::from_str_radix(&format!(\"{b}{b}\"), 16).map_err(|e| e.to_string())?;\n                Some(Color::Rgb(r, g, b))\n            }\n            // 0-255 color code\n            [c1, c2, c3] => {\n                if let Ok(ansi_color_num) = str::parse::<u8>(&format!(\"{c1}{c2}{c3}\")) {\n                    Some(Color::Fixed(ansi_color_num))\n                } else {\n                    None\n                }\n            }\n            [c1, c2] => {\n                if let Ok(ansi_color_num) = str::parse::<u8>(&format!(\"{c1}{c2}\")) {\n                    Some(Color::Fixed(ansi_color_num))\n                } else {\n                    None\n                }\n            }\n            [c1] => {\n                if let Ok(ansi_color_num) = str::parse::<u8>(&format!(\"{c1}\")) {\n                    Some(Color::Fixed(ansi_color_num))\n                } else {\n                    None\n                }\n            }\n            // unknown format\n            _ => None,\n        };\n\n        if let Some(color) = color {\n            return Ok(LimboColor(color));\n        }\n\n        // Check for any predefined color strings\n        // There are no predefined enums for bright colors, so we use Color::Fixed\n        let predefined_color = match value.to_lowercase().as_str() {\n            \"black\" => Color::Black,\n            \"red\" => Color::Red,\n            \"green\" => Color::Green,\n            \"yellow\" => Color::Yellow,\n            \"blue\" => Color::Blue,\n            \"purple\" => Color::Purple,\n            \"cyan\" => Color::Cyan,\n            \"magenta\" => Color::Magenta,\n            \"white\" => Color::White,\n            \"bright-black\" => Color::DarkGray, // \"bright-black\" is dark grey\n            \"bright-red\" => Color::LightRed,\n            \"bright-green\" => Color::LightGreen,\n            \"bright-yellow\" => Color::LightYellow,\n            \"bright-blue\" => Color::LightBlue,\n            \"bright-cyan\" => Color::LightCyan,\n            \"birght-magenta\" => Color::LightMagenta,\n            \"bright-white\" => Color::LightGray,\n            \"dark-red\" => Color::Fixed(1),\n            \"dark-green\" => Color::Fixed(2),\n            \"dark-yellow\" => Color::Fixed(3),\n            \"dark-blue\" => Color::Fixed(4),\n            \"dark-magenta\" => Color::Fixed(5),\n            \"dark-cyan\" => Color::Fixed(6),\n            \"grey\" => Color::Fixed(7),\n            \"dark-grey\" => Color::Fixed(8),\n            _ => return Err(format!(\"Could not parse color in string: {value}\")),\n        };\n\n        trace!(\"Read predefined color: {}\", value);\n        Ok(LimboColor(predefined_color))\n    }\n}\n\nimpl Display for LimboColor {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let val = match self.0 {\n            Color::Black => \"black\".to_string(),\n            Color::Red => \"red\".to_string(),\n            Color::Green => \"green\".to_string(),\n            Color::Yellow => \"yellow\".to_string(),\n            Color::Blue => \"blue\".to_string(),\n            Color::Purple => \"purple\".to_string(),\n            Color::Cyan => \"cyan\".to_string(),\n            Color::Magenta => \"magenta\".to_string(),\n            Color::White => \"white\".to_string(),\n            Color::DarkGray => \"bright-black\".to_string(), // \"bright-black\" is dark grey\n            Color::LightRed => \"bright-red\".to_string(),\n            Color::LightGreen => \"bright-green\".to_string(),\n            Color::LightYellow => \"bright-yellow\".to_string(),\n            Color::LightBlue => \"bright-blue\".to_string(),\n            Color::LightCyan => \"bright-cyan\".to_string(),\n            Color::LightMagenta | Color::LightPurple => \"bright-magenta\".to_string(),\n            Color::LightGray => \"bright-white\".to_string(),\n            Color::Fixed(1) => \"dark-red\".to_string(),\n            Color::Fixed(2) => \"dark-green\".to_string(),\n            Color::Fixed(3) => \"dark-yellow\".to_string(),\n            Color::Fixed(4) => \"dark-blue\".to_string(),\n            Color::Fixed(5) => \"dark-magenta\".to_string(),\n            Color::Fixed(6) => \"dark-cyan\".to_string(),\n            Color::Fixed(7) => \"grey\".to_string(),\n            Color::Fixed(8) => \"dark-grey\".to_string(),\n            Color::Rgb(r, g, b) => format!(\"#{r:x}{g:x}{b:X}\"),\n            Color::Fixed(ansi_color_num) => format!(\"{ansi_color_num}\"),\n            Color::Default => unreachable!(),\n        };\n        write!(f, \"{val}\")\n    }\n}\n\nimpl From<comfy_table::Color> for LimboColor {\n    fn from(value: comfy_table::Color) -> Self {\n        let color = match value {\n            comfy_table::Color::Rgb { r, g, b } => Color::Rgb(r, g, b),\n            comfy_table::Color::AnsiValue(ansi_color_num) => Color::Fixed(ansi_color_num),\n            comfy_table::Color::Black => Color::Black,\n            comfy_table::Color::Red => Color::Red,\n            comfy_table::Color::Green => Color::Green,\n            comfy_table::Color::Yellow => Color::Yellow,\n            comfy_table::Color::Blue => Color::Blue,\n            comfy_table::Color::Cyan => Color::Cyan,\n            comfy_table::Color::Magenta => Color::Magenta,\n            comfy_table::Color::White => Color::White,\n            comfy_table::Color::DarkRed => Color::Fixed(1),\n            comfy_table::Color::DarkGreen => Color::Fixed(2),\n            comfy_table::Color::DarkYellow => Color::Fixed(3),\n            comfy_table::Color::DarkBlue => Color::Fixed(4),\n            comfy_table::Color::DarkMagenta => Color::Fixed(5),\n            comfy_table::Color::DarkCyan => Color::Fixed(6),\n            comfy_table::Color::Grey => Color::Fixed(7),\n            comfy_table::Color::DarkGrey => Color::Fixed(8),\n            comfy_table::Color::Reset => unreachable!(), // Should never have Reset Color here\n        };\n        LimboColor(color)\n    }\n}\n\nimpl<'de> Deserialize<'de> for LimboColor {\n    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n    where\n        D: Deserializer<'de>,\n    {\n        struct LimboColorVisitor;\n\n        impl Visitor<'_> for LimboColorVisitor {\n            type Value = LimboColor;\n\n            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {\n                formatter.write_str(\"struct LimboColor\")\n            }\n\n            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>\n            where\n                E: de::Error,\n            {\n                LimboColor::try_from(v).map_err(de::Error::custom)\n            }\n        }\n\n        deserializer.deserialize_str(LimboColorVisitor)\n    }\n}\n\nimpl JsonSchema for LimboColor {\n    fn schema_name() -> String {\n        \"LimboColor\".into()\n    }\n\n    fn schema_id() -> std::borrow::Cow<'static, str> {\n        // Include the module, in case a type with the same name is in another module/crate\n        std::borrow::Cow::Borrowed(concat!(module_path!(), \"::LimboColor\"))\n    }\n\n    fn json_schema(generator: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {\n        generator.subschema_for::<LimboColor>()\n    }\n}\n\nimpl Deref for LimboColor {\n    type Target = Color;\n    fn deref(&self) -> &Self::Target {\n        &self.0\n    }\n}\n\nimpl DerefMut for LimboColor {\n    fn deref_mut(&mut self) -> &mut Self::Target {\n        &mut self.0\n    }\n}\n\nimpl Validate for LimboColor {\n    fn validate(&self) -> Result<(), validator::ValidationErrors> {\n        Ok(())\n    }\n}\n\nimpl LimboColor {\n    pub fn as_comfy_table_color(&self) -> comfy_table::Color {\n        match self.0 {\n            Color::Black => comfy_table::Color::Black,\n            Color::Red => comfy_table::Color::Red,\n            Color::Green => comfy_table::Color::Green,\n            Color::Yellow => comfy_table::Color::Yellow,\n            Color::Blue => comfy_table::Color::Blue,\n            Color::Magenta | Color::Purple => comfy_table::Color::Magenta,\n            Color::Cyan => comfy_table::Color::Cyan,\n            Color::White | Color::Default => comfy_table::Color::White,\n            Color::Fixed(1) => comfy_table::Color::DarkRed,\n            Color::Fixed(2) => comfy_table::Color::DarkGreen,\n            Color::Fixed(3) => comfy_table::Color::DarkYellow,\n            Color::Fixed(4) => comfy_table::Color::DarkBlue,\n            Color::Fixed(5) => comfy_table::Color::DarkMagenta,\n            Color::Fixed(6) => comfy_table::Color::DarkCyan,\n            Color::Fixed(7) => comfy_table::Color::Grey,\n            Color::Fixed(8) => comfy_table::Color::DarkGrey,\n            Color::DarkGray => comfy_table::Color::AnsiValue(241),\n            Color::LightRed => comfy_table::Color::AnsiValue(9),\n            Color::LightGreen => comfy_table::Color::AnsiValue(10),\n            Color::LightYellow => comfy_table::Color::AnsiValue(11),\n            Color::LightBlue => comfy_table::Color::AnsiValue(12),\n            Color::LightMagenta | Color::LightPurple => comfy_table::Color::AnsiValue(13),\n            Color::LightCyan => comfy_table::Color::AnsiValue(14),\n            Color::LightGray => comfy_table::Color::AnsiValue(15),\n            Color::Rgb(r, g, b) => comfy_table::Color::Rgb { r, g, b },\n            Color::Fixed(ansi_color_num) => comfy_table::Color::AnsiValue(ansi_color_num),\n        }\n    }\n}\n"
  },
  {
    "path": "cli/config/terminal.rs",
    "content": "#[cfg(unix)]\nuse std::io::{self, IsTerminal, Read, Write};\n#[cfg(unix)]\nuse std::time::Duration;\n\n#[derive(Debug, Clone, Copy, PartialEq)]\npub enum TerminalTheme {\n    Light,\n    Dark,\n    Unknown, // No colors - can't detect or unsupported platform\n}\n\npub struct TerminalDetector;\n\n#[cfg(target_os = \"windows\")]\nimpl TerminalDetector {\n    /// Windows: Always return Unknown (no colors)\n    /// Terminal detection is unreliable on Windows, so we disable colors entirely\n    pub fn detect_theme() -> TerminalTheme {\n        TerminalTheme::Unknown\n    }\n}\n\n#[cfg(unix)]\nimpl TerminalDetector {\n    /// Detects terminal background using ANSI escape sequences on Unix systems\n    pub fn detect_theme() -> TerminalTheme {\n        // Only works on interactive terminals where both stdin and stdout are terminals\n        if !io::stdin().is_terminal() || !io::stdout().is_terminal() {\n            return TerminalTheme::Unknown; // No colors for non-interactive\n        }\n\n        // Try ANSI escape sequence method\n        if let Some(theme) = Self::detect_via_ansi_query() {\n            return theme;\n        }\n\n        // Fallback - return Unknown (no colors) if detection fails\n        TerminalTheme::Unknown\n    }\n\n    /// Query terminal background color using ANSI escape sequence OSC 11\n    fn detect_via_ansi_query() -> Option<TerminalTheme> {\n        // Save current terminal settings\n        let original_termios = Self::save_terminal_settings()?;\n\n        // Set terminal to raw mode\n        Self::set_raw_mode()?;\n\n        // Send query and read response\n        let theme = Self::query_background_color();\n\n        // Restore terminal settings\n        Self::restore_terminal_settings(&original_termios);\n\n        theme\n    }\n\n    /// Save current terminal settings\n    fn save_terminal_settings() -> Option<libc::termios> {\n        use std::os::unix::io::AsRawFd;\n\n        let stdin_fd = io::stdin().as_raw_fd();\n        let mut termios = unsafe { std::mem::zeroed::<libc::termios>() };\n\n        unsafe {\n            if libc::tcgetattr(stdin_fd, &mut termios) == 0 {\n                Some(termios)\n            } else {\n                None\n            }\n        }\n    }\n\n    /// Set terminal to raw mode\n    fn set_raw_mode() -> Option<()> {\n        use std::os::unix::io::AsRawFd;\n\n        let stdin_fd = io::stdin().as_raw_fd();\n        let mut termios = unsafe { std::mem::zeroed::<libc::termios>() };\n\n        unsafe {\n            if libc::tcgetattr(stdin_fd, &mut termios) != 0 {\n                return None;\n            }\n\n            // Set raw mode: disable canonical mode, echo, and signals\n            termios.c_lflag &= !(libc::ICANON | libc::ECHO | libc::ISIG);\n            termios.c_iflag &= !(libc::IXON | libc::ICRNL);\n            termios.c_oflag &= !libc::OPOST;\n\n            // Set minimum characters to read and timeout\n            termios.c_cc[libc::VMIN] = 0;\n            termios.c_cc[libc::VTIME] = 1; // 0.1 second timeout\n\n            if libc::tcsetattr(stdin_fd, libc::TCSANOW, &termios) == 0 {\n                Some(())\n            } else {\n                None\n            }\n        }\n    }\n\n    /// Restore terminal settings\n    fn restore_terminal_settings(original: &libc::termios) {\n        use std::os::unix::io::AsRawFd;\n\n        let stdin_fd = io::stdin().as_raw_fd();\n        unsafe {\n            libc::tcsetattr(stdin_fd, libc::TCSANOW, original);\n        }\n    }\n\n    /// Send background color query and read response\n    fn query_background_color() -> Option<TerminalTheme> {\n        // Send OSC 11 query: ESC ] 11 ; ? ESC \\\n        print!(\"\\x1b]11;?\\x1b\\\\\");\n        io::stdout().flush().ok()?;\n\n        // Read response with timeout\n        let mut buffer = [0u8; 256];\n        let mut total_read = 0;\n\n        // Try to read response for up to 500ms\n        let start_time = std::time::Instant::now();\n        while start_time.elapsed() < Duration::from_millis(500) {\n            match io::stdin().read(&mut buffer[total_read..]) {\n                Ok(0) => {\n                    // No data available, sleep briefly and continue\n                    std::thread::sleep(Duration::from_millis(10));\n                    continue;\n                }\n                Ok(bytes_read) => {\n                    total_read += bytes_read;\n\n                    let response = String::from_utf8_lossy(&buffer[..total_read]);\n\n                    // Look for end of response (ESC \\ or BEL)\n                    if response.contains('\\x07') || response.contains(\"\\x1b\\\\\") {\n                        return Self::parse_ansi_color_response(&response);\n                    }\n\n                    // Prevent buffer overflow\n                    if total_read >= buffer.len() - 1 {\n                        break;\n                    }\n                }\n                Err(_) => {\n                    // Error reading, sleep briefly and continue\n                    std::thread::sleep(Duration::from_millis(10));\n                }\n            }\n        }\n\n        None\n    }\n\n    /// Parse ANSI color response to determine if background is light or dark\n    fn parse_ansi_color_response(response: &str) -> Option<TerminalTheme> {\n        // Look for patterns like: ]11;rgb:RRRR/GGGG/BBBB or ]11;#RRGGBB\n\n        // Try hex format first: ]11;#RRGGBB\n        if let Some(start) = response.find(\"]11;#\") {\n            let color_part = &response[start + 5..];\n            if let Some(hex_end) = color_part.find(|c: char| !c.is_ascii_hexdigit()) {\n                let hex_color = &color_part[..hex_end];\n                if hex_color.len() >= 6 {\n                    return Self::parse_hex_color(&hex_color[..6]);\n                }\n            }\n        }\n\n        // Try rgb: format: ]11;rgb:RRRR/GGGG/BBBB\n        if let Some(start) = response.find(\"rgb:\") {\n            let color_part = &response[start + 4..];\n\n            // Parse RGB values (format: RRRR/GGGG/BBBB)\n            let parts: Vec<&str> = color_part.split('/').take(3).collect();\n            if parts.len() == 3 {\n                if let (Ok(r), Ok(g), Ok(b)) = (\n                    u16::from_str_radix(&parts[0][..parts[0].len().min(4)], 16),\n                    u16::from_str_radix(&parts[1][..parts[1].len().min(4)], 16),\n                    u16::from_str_radix(&parts[2][..parts[2].len().min(4)], 16),\n                ) {\n                    // Convert to 0-255 range\n                    let r = (r >> 8) as u8;\n                    let g = (g >> 8) as u8;\n                    let b = (b >> 8) as u8;\n\n                    return Some(Self::classify_color_brightness(r, g, b));\n                }\n            }\n        }\n\n        None\n    }\n\n    /// Parse hex color format (#RRGGBB)\n    fn parse_hex_color(hex: &str) -> Option<TerminalTheme> {\n        if hex.len() != 6 {\n            return None;\n        }\n\n        let r = u8::from_str_radix(&hex[0..2], 16).ok()?;\n        let g = u8::from_str_radix(&hex[2..4], 16).ok()?;\n        let b = u8::from_str_radix(&hex[4..6], 16).ok()?;\n\n        Some(Self::classify_color_brightness(r, g, b))\n    }\n\n    /// Classify color brightness using perceived luminance\n    fn classify_color_brightness(r: u8, g: u8, b: u8) -> TerminalTheme {\n        // Use ITU-R BT.709 luma coefficients for perceived brightness\n        let luminance = 0.2126 * r as f32 + 0.7152 * g as f32 + 0.0722 * b as f32;\n\n        // Threshold around 128 (middle gray)\n        if luminance > 128.0 {\n            TerminalTheme::Light\n        } else {\n            TerminalTheme::Dark\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[cfg(unix)]\n    mod unix_tests {\n        use super::*;\n\n        #[test]\n        fn test_hex_color_parsing() {\n            // Test light colors\n            assert_eq!(\n                TerminalDetector::parse_hex_color(\"ffffff\"),\n                Some(TerminalTheme::Light)\n            );\n            assert_eq!(\n                TerminalDetector::parse_hex_color(\"f0f0f0\"),\n                Some(TerminalTheme::Light)\n            );\n\n            // Test dark colors\n            assert_eq!(\n                TerminalDetector::parse_hex_color(\"000000\"),\n                Some(TerminalTheme::Dark)\n            );\n            assert_eq!(\n                TerminalDetector::parse_hex_color(\"202020\"),\n                Some(TerminalTheme::Dark)\n            );\n\n            // Test invalid input\n            assert_eq!(TerminalDetector::parse_hex_color(\"invalid\"), None);\n            assert_eq!(TerminalDetector::parse_hex_color(\"12345\"), None);\n        }\n\n        #[test]\n        fn test_brightness_classification() {\n            // Pure white\n            assert_eq!(\n                TerminalDetector::classify_color_brightness(255, 255, 255),\n                TerminalTheme::Light\n            );\n\n            // Pure black\n            assert_eq!(\n                TerminalDetector::classify_color_brightness(0, 0, 0),\n                TerminalTheme::Dark\n            );\n\n            // Medium gray (should be close to threshold)\n            assert_eq!(\n                TerminalDetector::classify_color_brightness(128, 128, 128),\n                TerminalTheme::Dark // Slightly below threshold\n            );\n\n            // Light gray\n            assert_eq!(\n                TerminalDetector::classify_color_brightness(200, 200, 200),\n                TerminalTheme::Light\n            );\n        }\n\n        #[test]\n        fn test_ansi_response_parsing() {\n            // Test hex format response\n            let hex_response = \"\\x1b]11;#ffffff\\x1b\\\\\";\n            assert_eq!(\n                TerminalDetector::parse_ansi_color_response(hex_response),\n                Some(TerminalTheme::Light)\n            );\n\n            // Test rgb format response\n            let rgb_response = \"\\x1b]11;rgb:0000/0000/0000\\x1b\\\\\";\n            assert_eq!(\n                TerminalDetector::parse_ansi_color_response(rgb_response),\n                Some(TerminalTheme::Dark)\n            );\n\n            // Test invalid response\n            let invalid_response = \"invalid response\";\n            assert_eq!(\n                TerminalDetector::parse_ansi_color_response(invalid_response),\n                None\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "cli/docs/config.md",
    "content": "# Config\n\nConfig folder should be located at `$HOME/.config/limbo`. The config file inside should be named `limbo.toml`. Optionally you can have a `themes` folder whithin to store `.tmTheme` files to be discovered by the CLI on startup. \n\nDescribes the Limbo Config file for the CLI\\\n\n**Note**: Colors can be inputted as\n- Rrggbb string -> `\"#010101\"`\n- Rgb string -> `\"#A3F\"`\n- 256 Ansi Color -> `\"100\"`\n- Predefined Color Names:\n  - `\"black\"`\n  - `\"red\"`\n  - `\"green\"`\n  - `\"yellow\"`\n  - `\"blue\"`\n  - `\"purple\"`\n  - `\"cyan\"`\n  - `\"magenta\"`\n  - `\"white\"`\n  - `\"grey\"`\n  - `\"bright-black\"`\n  - `\"bright-red\"`\n  - `\"bright-green\"`\n  - `\"bright-yellow\"`\n  - `\"bright-blue\"`\n  - `\"bright-cyan\"`\n  - `\"bright-magenta\"`\n  - `\"bright-white\"`\n  - `\"dark-red\"`\n  - `\"dark-green\"`\n  - `\"dark-yellow\"`\n  - `\"dark-blue\"`\n  - `\"dark-magenta\"`\n  - `\"dark-cyan\"`\n  - `\"dark-grey\"`\n\n## `table`\n\n### `column_colors`\n**Type**: `List[Color]`\\\n*Example*: `[\"cyan\"]`\n\n### `header_color`\n**Type**: `Color`\\\n*Example*: `\"red\"`\n\n## `highlight`\n\n### `enable`\n**Type**: `bool`\\\n*Example*: `true`\n\n### `theme`\n**Type**: `String`\\\n*Example*: `\"base16-ocean.dark\"`\n\nPreloaded themes:\n- `base16-ocean.dark`\n- `base16-eighties.dark`\n- `base16-mocha.dark`\n- `base16-ocean.light`\n\nYou can reference a custom theme in your `themes` folder directly by name from the config file.\n\n*Example*: \n\nFolder structure\n\n```\nlimbo\n├── limbo.toml\n└── themes\n    └── Amy.tmTheme\n```\n\n`limbo.toml`\n\n```toml\n[highlight]\ntheme = \"Amy\"\n```\n\n### `prompt`\n**Type**: `Color`\\\n*Example*: `\"green\"`\n\n### `hint`\n**Type**: `Color`\\\n*Example*: `\"grey\"`\n\n### `candidate`\n**Type**: `Color`\\\n*Example*: `\"yellow\"`\n\n## Example `limbo.toml`\n\n```toml\n[table]\ncolumn_colors = [\"cyan\", \"black\", \"#010101\"]\nheader_color = \"red\"\n\n[highlight]\nenable = true\nprompt = \"bright-blue\"\ntheme = \"base16-ocean.light\"\nhint = \"123\"\ncandidate = \"dark-yellow\"\n```\n\n"
  },
  {
    "path": "cli/docs/internal/commands.md",
    "content": "# Cli Internal Docs\n\n## Repl Custom Commands Arg Parser \n\nTo distinguish between normal SQL queries and custom commands, we prefix a \".\" before our desired command. This signals to the to the REPL that you intend to use a custom command. \n\nTo implement this we use CLAP with multicall. It is very important we use multicall, else this will not work\n\n## Adding new Commands\n\nTo add new commands, we need to modify three places: \n- `commands/mod.rs` \n- `commands/args.rs` or create a new file under `commands` that will describe how you will use the Args for your command\n- `app.rs` to handle the execution of the command\n\n`commands/mod.rs`\n```rust\n\n   pub enum Command {\n    ...\n    /// Descriptive Message for your command\n    Example(ExampleArgs),\n   }\n```\n\n`commands/args.rs`\n```rust \n\n   #[derive(Debug, Clone, Args)]\n    pub struct ExampleArgs {\n        /// Example arg\n        pub example: String,\n    }\n```\n\n`app.rs` \n```rust \n\n    pub fn handle_dot_command(&mut self, line: &str) {\n        ...\n        Ok(cmd) => match cmd.command {\n            ...\n            Command::Example(args) => {\n                println!(\"{}\", args.example);\n            }\n        }\n    }\n\n```\n\nEvery single option that is available to CLAP is available here. To facilitate the creation of more helpful help\nmessages, please use '///' in your args and command creation, so that CLAP can capture them in the codegen and create their descriptions.\n\n"
  },
  {
    "path": "cli/helper.rs",
    "content": "use clap::Parser;\nuse nu_ansi_term::{Color, Style};\nuse rustyline::completion::{extract_word, Completer, Pair};\nuse rustyline::highlight::Highlighter;\nuse rustyline::hint::HistoryHinter;\nuse rustyline::{Completer, Helper, Hinter, Validator};\nuse shlex::Shlex;\nuse std::cell::RefCell;\nuse std::marker::PhantomData;\nuse std::sync::Arc;\nuse std::{ffi::OsString, path::PathBuf, str::FromStr as _};\nuse syntect::dumps::from_uncompressed_data;\nuse syntect::easy::HighlightLines;\nuse syntect::highlighting::ThemeSet;\nuse syntect::parsing::{Scope, SyntaxSet};\nuse syntect::util::{as_24_bit_terminal_escaped, LinesWithEndings};\nuse turso_core::Connection;\n\nuse crate::commands::CommandParser;\nuse crate::config::{HighlightConfig, CONFIG_DIR};\n\nmacro_rules! try_result {\n    ($expr:expr, $err:expr) => {\n        match $expr {\n            Ok(val) => val,\n            Err(_) => return Ok($err),\n        }\n    };\n}\n\n#[derive(Helper, Completer, Hinter, Validator)]\npub struct LimboHelper {\n    #[rustyline(Completer)]\n    completer: SqlCompleter<CommandParser>,\n    syntax_set: SyntaxSet,\n    theme_set: ThemeSet,\n    syntax_config: HighlightConfig,\n    #[rustyline(Hinter)]\n    hinter: HistoryHinter,\n}\n\nimpl LimboHelper {\n    pub fn new(conn: Arc<Connection>, syntax_config: Option<HighlightConfig>) -> Self {\n        // Load only predefined syntax\n        let ps = from_uncompressed_data(include_bytes!(concat!(\n            env!(\"OUT_DIR\"),\n            \"/SQL_syntax_set_dump.packdump\"\n        )))\n        .unwrap();\n        let mut ts = ThemeSet::load_defaults();\n        let theme_dir = CONFIG_DIR.join(\"themes\");\n        if theme_dir.exists() {\n            if let Err(err) = ts.add_from_folder(theme_dir) {\n                tracing::error!(\"{err}\");\n            }\n        }\n        LimboHelper {\n            completer: SqlCompleter::new(conn),\n            syntax_set: ps,\n            theme_set: ts,\n            syntax_config: syntax_config.unwrap_or_default(),\n            hinter: HistoryHinter::new(),\n        }\n    }\n}\n\nimpl Highlighter for LimboHelper {\n    fn highlight<'l>(&self, line: &'l str, pos: usize) -> std::borrow::Cow<'l, str> {\n        let _ = pos;\n        if self.syntax_config.enable {\n            // TODO use lifetimes to store highlight lines\n            let syntax = self\n                .syntax_set\n                .find_syntax_by_scope(Scope::new(\"source.sql\").unwrap())\n                .unwrap();\n            let theme = self\n                .theme_set\n                .themes\n                .get(&self.syntax_config.theme)\n                .unwrap_or(&self.theme_set.themes[\"base16-ocean.dark\"]);\n            let mut h = HighlightLines::new(syntax, theme);\n            let ranges = {\n                let mut ret_ranges = Vec::new();\n                for new_line in LinesWithEndings::from(line) {\n                    let ranges: Vec<(syntect::highlighting::Style, &str)> =\n                        h.highlight_line(new_line, &self.syntax_set).unwrap();\n                    ret_ranges.extend(ranges);\n                }\n                ret_ranges\n            };\n            let mut ret_line = as_24_bit_terminal_escaped(&ranges[..], false);\n            // Push this escape sequence to reset terminal color modes at the end of the string\n            ret_line.push_str(\"\\x1b[0m\");\n            std::borrow::Cow::Owned(ret_line)\n        } else {\n            // Appease Pekka in syntax highlighting\n            let style = Style::new().fg(Color::White); // Standard shell text color\n            let styled_str = style.paint(line);\n            std::borrow::Cow::Owned(styled_str.to_string())\n        }\n    }\n\n    fn highlight_prompt<'b, 's: 'b, 'p: 'b>(\n        &'s self,\n        prompt: &'p str,\n        default: bool,\n    ) -> std::borrow::Cow<'b, str> {\n        let _ = default;\n        // Dark emerald green for prompt\n        let style = Style::new().bold().fg(self.syntax_config.prompt.0);\n        let styled_str = style.paint(prompt);\n        std::borrow::Cow::Owned(styled_str.to_string())\n    }\n\n    fn highlight_hint<'h>(&self, hint: &'h str) -> std::borrow::Cow<'h, str> {\n        let style = Style::new().bold().fg(self.syntax_config.hint.0); // Brighter dark grey for hints\n        let styled_str = style.paint(hint);\n        std::borrow::Cow::Owned(styled_str.to_string())\n    }\n\n    fn highlight_candidate<'c>(\n        &self,\n        candidate: &'c str,\n        completion: rustyline::CompletionType,\n    ) -> std::borrow::Cow<'c, str> {\n        let _ = completion;\n        let style = Style::new().fg(self.syntax_config.candidate.0);\n        let styled_str = style.paint(candidate);\n        std::borrow::Cow::Owned(styled_str.to_string())\n    }\n\n    fn highlight_char(&self, line: &str, pos: usize, kind: rustyline::highlight::CmdKind) -> bool {\n        let _ = (line, pos);\n        !matches!(kind, rustyline::highlight::CmdKind::MoveCursor)\n    }\n}\n\npub struct SqlCompleter<C: Parser + Send + Sync + 'static> {\n    conn: Arc<Connection>,\n    // Has to be a ref cell as Rustyline takes immutable reference to self\n    // This problem would be solved with Reedline as it uses &mut self for completions\n    cmd: RefCell<clap::Command>,\n    _cmd_phantom: PhantomData<C>,\n}\n\nimpl<C: Parser + Send + Sync + 'static> SqlCompleter<C> {\n    pub fn new(conn: Arc<Connection>) -> Self {\n        Self {\n            conn,\n            cmd: C::command().into(),\n            _cmd_phantom: PhantomData,\n        }\n    }\n\n    fn dot_completion(\n        &self,\n        mut line: &str,\n        mut pos: usize,\n    ) -> rustyline::Result<(usize, Vec<Pair>)> {\n        // TODO maybe check to see if the line is empty and then just output the command names\n        line = &line[1..];\n        pos -= 1;\n\n        let (prefix_pos, _) = extract_word(line, pos, ESCAPE_CHAR, default_break_chars);\n\n        let args = Shlex::new(line);\n        let mut args = std::iter::once(\"\".to_owned())\n            .chain(args)\n            .map(OsString::from)\n            .collect::<Vec<_>>();\n        if line.ends_with(' ') {\n            args.push(OsString::new());\n        }\n        let arg_index = args.len() - 1;\n        // dbg!(&pos, line, &args, arg_index);\n\n        let mut cmd = self.cmd.borrow_mut();\n        match clap_complete::engine::complete(\n            &mut cmd,\n            args,\n            arg_index,\n            PathBuf::from_str(\".\").ok().as_deref(),\n        ) {\n            Ok(candidates) => {\n                let candidates = candidates\n                    .iter()\n                    .map(|candidate| Pair {\n                        display: candidate.get_value().to_string_lossy().into_owned(),\n                        replacement: candidate.get_value().to_string_lossy().into_owned(),\n                    })\n                    .collect::<Vec<Pair>>();\n\n                Ok((prefix_pos + 1, candidates))\n            }\n            Err(_) => Ok((prefix_pos + 1, Vec::new())),\n        }\n    }\n\n    fn sql_completion(&self, line: &str, pos: usize) -> rustyline::Result<(usize, Vec<Pair>)> {\n        // TODO: have to differentiate words if they are enclosed in single of double quotes\n        let (prefix_pos, prefix) = extract_word(line, pos, ESCAPE_CHAR, default_break_chars);\n        let mut candidates = Vec::new();\n\n        let query = try_result!(\n            self.conn.query(format!(\n                \"SELECT candidate FROM completion('{prefix}', '{line}') ORDER BY 1;\"\n            )),\n            (prefix_pos, candidates)\n        );\n\n        if let Some(mut rows) = query {\n            try_result!(\n                rows.run_with_row_callback(|row| {\n                    let completion: &str = row.get::<&str>(0)?;\n                    let pair = Pair {\n                        display: completion.to_string(),\n                        replacement: completion.to_string(),\n                    };\n                    candidates.push(pair);\n                    Ok(())\n                }),\n                (prefix_pos, candidates)\n            );\n        }\n\n        Ok((prefix_pos, candidates))\n    }\n}\n\n// Got this from the FilenameCompleter.\n// TODO have to see what chars break words in Sqlite\ncfg_if::cfg_if! {\n    if #[cfg(unix)] {\n        // rl_basic_word_break_characters, rl_completer_word_break_characters\n        const fn default_break_chars(c : char) -> bool {\n            matches!(c, ' ' | '\\t' | '\\n' | '\"' | '\\\\' | '\\'' | '`' | '@' | '$' | '>' | '<' | '=' | ';' | '|' | '&' |\n            '{' | '(' | '\\0')\n        }\n        const ESCAPE_CHAR: Option<char> = Some('\\\\');\n        // In double quotes, not all break_chars need to be escaped\n        // https://www.gnu.org/software/bash/manual/html_node/Double-Quotes.html\n        #[allow(dead_code)]\n        const fn double_quotes_special_chars(c: char) -> bool { matches!(c, '\"' | '$' | '\\\\' | '`') }\n    } else if #[cfg(windows)] {\n        // Remove \\ to make file completion works on windows\n        const fn default_break_chars(c: char) -> bool {\n            matches!(c, ' ' | '\\t' | '\\n' | '\"' | '\\'' | '`' | '@' | '$' | '>' | '<' | '=' | ';' | '|' | '&' | '{' |\n            '(' | '\\0')\n        }\n        const ESCAPE_CHAR: Option<char> = None;\n        #[allow(dead_code)]\n        const fn double_quotes_special_chars(c: char) -> bool { c == '\"' } // TODO Validate: only '\"' ?\n    } else if #[cfg(target_arch = \"wasm32\")] {\n        const fn default_break_chars(c: char) -> bool { false }\n        const ESCAPE_CHAR: Option<char> = None;\n        #[allow(dead_code)]\n        const fn double_quotes_special_chars(c: char) -> bool { false }\n    }\n}\n\nimpl<C: Parser + Send + Sync + 'static> Completer for SqlCompleter<C> {\n    type Candidate = Pair;\n\n    fn complete(\n        &self,\n        line: &str,\n        pos: usize,\n        _ctx: &rustyline::Context<'_>,\n    ) -> rustyline::Result<(usize, Vec<Self::Candidate>)> {\n        if line.starts_with(\".\") {\n            self.dot_completion(line, pos)\n        } else {\n            self.sql_completion(line, pos)\n        }\n    }\n}\n"
  },
  {
    "path": "cli/input.rs",
    "content": "use crate::app::Opts;\nuse clap::ValueEnum;\nuse std::{\n    fmt::{Display, Formatter},\n    io::{self, Write},\n    sync::Arc,\n};\nuse turso_core::LimboError;\n\n#[derive(Copy, Clone)]\npub enum DbLocation {\n    Memory,\n    Path,\n}\n\n#[allow(clippy::enum_variant_names)]\n#[derive(Clone, Debug)]\npub enum Io {\n    Syscall,\n    #[cfg(all(target_os = \"linux\", feature = \"io_uring\"))]\n    IoUring,\n    #[cfg(all(target_os = \"windows\", feature = \"experimental_win_iocp\"))]\n    WindowsIOCP,\n    External(String),\n    Memory,\n}\n\nimpl Display for Io {\n    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Io::Memory => write!(f, \"memory\"),\n            Io::Syscall => write!(f, \"syscall\"),\n            #[cfg(all(target_os = \"linux\", feature = \"io_uring\"))]\n            Io::IoUring => write!(f, \"io_uring\"),\n            #[cfg(all(target_os = \"windows\", feature = \"experimental_win_iocp\"))]\n            Io::WindowsIOCP => write!(f, \"experimental_win_iocp\"),\n            Io::External(str) => write!(f, \"{str}\"),\n        }\n    }\n}\n\nimpl Default for Io {\n    /// Custom Default impl with cfg! macro, to provide compile-time default to Clap based on platform\n    /// The cfg! could be elided, but Clippy complains\n    /// The default value can still be overridden with the Clap argument\n    fn default() -> Self {\n        match cfg!(all(target_os = \"linux\", feature = \"io_uring\")) {\n            true => {\n                #[cfg(all(target_os = \"linux\", feature = \"io_uring\"))]\n                {\n                    Io::Syscall // FIXME: make io_uring faster so it can be the default\n                }\n                #[cfg(any(\n                    not(target_os = \"linux\"),\n                    all(target_os = \"linux\", not(feature = \"io_uring\"))\n                ))]\n                {\n                    Io::Syscall\n                }\n            }\n            false => Io::Syscall,\n        }\n    }\n}\n\n#[derive(ValueEnum, Copy, Clone, Debug, PartialEq, Eq)]\npub enum OutputMode {\n    List,\n    Pretty,\n    Line,\n}\n\nimpl std::fmt::Display for OutputMode {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        self.to_possible_value()\n            .expect(\"no values are skipped\")\n            .get_name()\n            .fmt(f)\n    }\n}\n\npub struct Settings {\n    pub output_filename: String,\n    pub db_file: String,\n    pub null_value: String,\n    pub output_mode: OutputMode,\n    pub echo: bool,\n    pub is_stdout: bool,\n    pub io: Io,\n    pub timer: bool,\n    pub headers: bool,\n    pub mcp: bool,\n    pub sync_server_address: Option<String>,\n    pub stats: bool,\n}\n\nimpl From<Opts> for Settings {\n    fn from(opts: Opts) -> Self {\n        Self {\n            null_value: String::new(),\n            output_mode: opts.output_mode,\n            echo: false,\n            is_stdout: opts.output.is_empty(),\n            output_filename: opts.output,\n            db_file: opts\n                .database\n                .as_ref()\n                .map_or(\":memory:\".to_string(), |p| p.to_string_lossy().to_string()),\n            io: match opts.vfs.as_ref().unwrap_or(&String::new()).as_str() {\n                \"memory\" | \":memory:\" => Io::Memory,\n                \"syscall\" => Io::Syscall,\n                #[cfg(all(target_os = \"linux\", feature = \"io_uring\"))]\n                \"io_uring\" => Io::IoUring,\n                #[cfg(all(target_os = \"windows\", feature = \"experimental_win_iocp\"))]\n                \"experimental_win_iocp\" => Io::WindowsIOCP,\n                \"\" => Io::default(),\n                vfs => Io::External(vfs.to_string()),\n            },\n            timer: false,\n            headers: false,\n            mcp: opts.mcp,\n            sync_server_address: opts.sync_server,\n            stats: false,\n        }\n    }\n}\n\nimpl std::fmt::Display for Settings {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(\n            f,\n            \"Settings:\\nOutput mode: {}\\nDB: {}\\nOutput: {}\\nNull value: {}\\nCWD: {}\\nEcho: {}\\nHeaders: {}\",\n            self.output_mode,\n            self.db_file,\n            match self.is_stdout {\n                true => \"STDOUT\",\n                false => &self.output_filename,\n            },\n            self.null_value,\n            std::env::current_dir().unwrap().display(),\n            match self.echo {\n                true => \"on\",\n                false => \"off\",\n            },\n            match self.headers {\n                true => \"on\",\n                false => \"off\",\n            }\n        )\n    }\n}\n\npub fn get_writer(output: &str) -> Box<dyn Write> {\n    match output {\n        \"\" => Box::new(io::stdout()),\n        _ => match std::fs::File::create(output) {\n            Ok(file) => Box::new(file),\n            Err(e) => {\n                eprintln!(\"Error: {e}\");\n                Box::new(io::stdout())\n            }\n        },\n    }\n}\n\npub fn get_io(db_location: DbLocation, io_choice: &str) -> anyhow::Result<Arc<dyn turso_core::IO>> {\n    Ok(match db_location {\n        DbLocation::Memory => Arc::new(turso_core::MemoryIO::new()),\n        DbLocation::Path => {\n            match io_choice {\n                \"memory\" => Arc::new(turso_core::MemoryIO::new()),\n                \"syscall\" => {\n                    // We are building for Linux/macOS and syscall backend has been selected\n                    #[cfg(target_family = \"unix\")]\n                    {\n                        Arc::new(turso_core::UnixIO::new()?)\n                    }\n                    // We are not building for Linux/macOS and syscall backend has been selected\n                    #[cfg(not(target_family = \"unix\"))]\n                    {\n                        Arc::new(turso_core::PlatformIO::new()?)\n                    }\n                }\n                // We are building for Linux and io_uring backend has been selected\n                #[cfg(all(target_os = \"linux\", feature = \"io_uring\"))]\n                \"io_uring\" => Arc::new(turso_core::UringIO::new()?),\n                #[cfg(all(target_os = \"windows\", feature = \"experimental_win_iocp\"))]\n                \"experimental_win_iocp\" => Arc::new(turso_core::WindowsIOCP::new()?),\n                _ => Arc::new(turso_core::PlatformIO::new()?),\n            }\n        }\n    })\n}\n\npub struct ApplyWriter<'a> {\n    target: &'a Arc<turso_core::Connection>,\n    // accumulate raw bytes to support non-utf8 BLOB types\n    buf: Vec<u8>,\n}\n\nimpl<'a> ApplyWriter<'a> {\n    pub fn new(target: &'a Arc<turso_core::Connection>) -> Self {\n        Self {\n            target,\n            buf: Vec::new(),\n        }\n    }\n\n    // Find the next statement terminator ;\\n or ;\\r\\n in a byte buffer.\n    // Returns (end_idx_inclusive, drain_len), where drain_len includes the newline(s).\n    fn find_stmt_end(buf: &[u8]) -> Option<(usize, usize)> {\n        let mut i = 0;\n        while i < buf.len() {\n            // Look for ';'\n            if buf[i] == b';' {\n                // Accept ;\\n\n                if i + 1 < buf.len() && buf[i + 1] == b'\\n' {\n                    return Some((i, 2));\n                }\n                // Accept ;\\r\\n\n                if i + 2 < buf.len() && buf[i + 1] == b'\\r' && buf[i + 2] == b'\\n' {\n                    return Some((i, 3));\n                }\n            }\n            i += 1;\n        }\n        None\n    }\n\n    pub fn flush_complete_statements(&mut self) -> io::Result<()> {\n        while let Some((end_inclusive, drain_len)) = Self::find_stmt_end(&self.buf) {\n            // Copy stmt bytes [0..=end_inclusive]\n            let stmt_bytes = self.buf[..=end_inclusive].to_vec();\n            // Drain including the trailing newline(s)\n            self.buf.drain(..end_inclusive + drain_len);\n            self.exec_stmt_bytes(&stmt_bytes)?;\n        }\n        Ok(())\n    }\n\n    // Handle final trailing statement that ends with ';' followed only by ASCII whitespace.\n    pub fn finish(mut self) -> io::Result<()> {\n        // Skip if buffer empty or no ';'\n        if let Some(semicolon_pos) = self.buf.iter().rposition(|&b| b == b';') {\n            // Are all bytes after ';' ASCII whitespace?\n            if self.buf[semicolon_pos + 1..]\n                .iter()\n                .all(|&b| matches!(b, b' ' | b'\\t' | b'\\r' | b'\\n'))\n            {\n                let stmt_bytes = self.buf[..=semicolon_pos].to_vec();\n                self.buf.clear();\n                self.exec_stmt_bytes(&stmt_bytes)?;\n            }\n        }\n        Ok(())\n    }\n\n    fn exec_stmt_bytes(&self, stmt_bytes: &[u8]) -> io::Result<()> {\n        // SQL must be UTF-8. If not, surface a clear error.\n        let sql = std::str::from_utf8(stmt_bytes).map_err(|e| {\n            io::Error::new(io::ErrorKind::InvalidData, format!(\"non-UTF8 SQL: {e}\"))\n        })?;\n        self.exec_stmt(sql)\n            .map_err(|e| io::Error::other(e.to_string()))\n    }\n\n    fn exec_stmt(&self, sql: &str) -> Result<(), LimboError> {\n        match self.target.query(sql) {\n            Ok(Some(mut rows)) => {\n                rows.run_with_row_callback(|_| Ok(()))?;\n            }\n            Ok(None) => {}\n            Err(e) => return Err(e),\n        }\n        Ok(())\n    }\n}\n\nimpl<'a> Write for ApplyWriter<'a> {\n    fn write(&mut self, data: &[u8]) -> io::Result<usize> {\n        self.buf.extend_from_slice(data);\n        self.flush_complete_statements()?;\n        Ok(data.len())\n    }\n    fn flush(&mut self) -> io::Result<()> {\n        self.flush_complete_statements()\n    }\n}\n\npub trait ProgressSink {\n    fn on<S: Display>(&mut self, _p: S) {}\n}\n\npub struct NoopProgress;\nimpl ProgressSink for NoopProgress {}\n\npub struct StderrProgress;\n\nimpl ProgressSink for StderrProgress {\n    fn on<S: Display>(&mut self, s: S) {\n        eprintln!(\"{s}... done\");\n    }\n}\n\npub const BEFORE_HELP_MSG: &str = r#\"\n\nTurso SQL Shell Help\n==============\nWelcome to the Turso SQL Shell! You can execute any standard SQL command here.\nIn addition to standard SQL commands, the following special commands are available:\"#;\npub const AFTER_HELP_MSG: &str = r#\"Usage Examples:\n---------------\n1. To quit the Turso SQL Shell:\n   .quit\n\n2. To open a database file at path './employees.db':\n   .open employees.db\n\n3. To view the schema of a table named 'employees':\n   .schema employees\n\n4. To list all tables:\n   .tables\n\n5. To list all databases:\n   .databases\n\n6. To list all available SQL opcodes:\n   .opcodes\n\n7. To change the current output mode to 'pretty':\n   .mode pretty\n\n8. Send output to STDOUT if no file is specified:\n   .output\n\n9. To change the current working directory to '/tmp':\n   .cd /tmp\n\n10. Show the current values of settings:\n   .show\n\n11. To import csv file 'sample.csv' into 'csv_table' table:\n   .import --csv sample.csv csv_table\n\n12. To display the database contents as SQL:\n   .dump\n\n13. To load an extension library:\n   .load /target/debug/liblimbo_regexp\n\n14. To list all available VFS:\n   .listvfs\n\n15. To show names of indexes:\n   .indexes ?TABLE?\n\n16. To turn on column headers in list mode:\n   .headers on\n\n17. To turn off column headers in list mode:\n   .headers off\n\n18. To clone the open database to another file:\n   .clone output_file.db\n\n19. To view manual pages for features:\n   .manual mcp    # View MCP server documentation\n   .man           # List all available manuals\n\n20. To bind parameters for subsequent SQL statements:\n   .parameter set :name alice\n   .parameter list\n   .parameter clear :name\n\nNote:\n- All SQL commands must end with a semicolon (;).\n- Special commands start with a dot (.) and are not required to end with a semicolon.\"#;\n"
  },
  {
    "path": "cli/main.rs",
    "content": "#![allow(clippy::arc_with_non_send_sync)]\nmod app;\nmod commands;\nmod config;\nmod helper;\nmod input;\nmod manual;\nmod mcp_server;\nmod opcodes_dictionary;\nmod read_state_machine;\nmod sync_server;\n\n#[cfg(feature = \"mvcc_repl\")]\nmod mvcc_repl;\n\nuse config::CONFIG_DIR;\nuse mcp_server::TursoMcpServer;\nuse rustyline::{error::ReadlineError, Config, Editor};\nuse std::{\n    path::PathBuf,\n    sync::{atomic::Ordering, LazyLock},\n};\n\nuse crate::sync_server::TursoSyncServer;\n\n#[cfg(all(feature = \"mimalloc\", not(target_family = \"wasm\"), not(miri)))]\n#[global_allocator]\nstatic GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;\n\nfn rustyline_config() -> Config {\n    Config::builder()\n        .completion_type(rustyline::CompletionType::List)\n        .auto_add_history(true)\n        .build()\n}\n\npub static HOME_DIR: LazyLock<PathBuf> =\n    LazyLock::new(|| dirs::home_dir().expect(\"Could not determine home directory\"));\n\npub static HISTORY_FILE: LazyLock<PathBuf> = LazyLock::new(|| HOME_DIR.join(\".limbo_history\"));\n\nfn run_mcp_server(app: app::Limbo) -> anyhow::Result<()> {\n    let conn = app.get_connection();\n    let interrupt_count = app.get_interrupt_count();\n    let mcp_server = TursoMcpServer::new(conn, interrupt_count);\n\n    mcp_server.run()\n}\n\nfn run_sync_server(app: app::Limbo) -> anyhow::Result<()> {\n    let address = app.opts.sync_server_address.clone().unwrap();\n    let conn = app.get_connection();\n    let interrupt_count = app.get_interrupt_count();\n    let sync_server = TursoSyncServer::new(address, conn, interrupt_count);\n\n    sync_server.run()\n}\n\nfn main() -> anyhow::Result<()> {\n    #[cfg(feature = \"mvcc_repl\")]\n    {\n        use clap::Parser as _;\n        let opts = app::Opts::parse();\n        if opts.mvcc {\n            let path = opts\n                .database\n                .as_ref()\n                .and_then(|p| p.to_str())\n                .unwrap_or(\":memory:\")\n                .to_owned();\n            return mvcc_repl::run(&path);\n        }\n    }\n\n    let (mut app, _guard) = app::Limbo::new()?;\n\n    if app.is_mcp_mode() {\n        return run_mcp_server(app);\n    }\n    if app.is_sync_server_mode() {\n        return run_sync_server(app);\n    }\n\n    let interactive_stdin = std::io::IsTerminal::is_terminal(&std::io::stdin());\n    if interactive_stdin {\n        let mut rl = Editor::with_config(rustyline_config())?;\n        if HISTORY_FILE.exists() {\n            rl.load_history(HISTORY_FILE.as_path())?;\n        }\n        let config_file = CONFIG_DIR.join(\"limbo.toml\");\n\n        let config = config::Config::from_config_file(config_file);\n        tracing::info!(\"Configuration: {:?}\", config);\n        app = app.with_config(config);\n\n        app = app.with_readline(rl);\n    } else {\n        tracing::debug!(\"not in tty\");\n    }\n\n    loop {\n        match app.readline() {\n            Ok(_) => app.consume(false),\n            Err(ReadlineError::Interrupted) => {\n                // At prompt, increment interrupt count\n                if app.interrupt_count.fetch_add(1, Ordering::SeqCst) >= 1 {\n                    eprintln!(\"Interrupted. Exiting...\");\n                    let _ = app.close_conn();\n                    break;\n                }\n                println!(\"Use .quit to exit or press Ctrl-C again to force quit.\");\n                app.reset_input();\n                continue;\n            }\n            Err(ReadlineError::Eof) => {\n                // consume remaining input before exit\n                app.consume(true);\n                let _ = app.close_conn();\n                break;\n            }\n            Err(err) => {\n                let _ = app.close_conn();\n                anyhow::bail!(err)\n            }\n        }\n    }\n    if !interactive_stdin && app.has_query_error() {\n        std::process::exit(1);\n    }\n    Ok(())\n}\n"
  },
  {
    "path": "cli/manual.rs",
    "content": "use include_dir::{include_dir, Dir};\nuse rand::seq::SliceRandom;\nuse std::io::{stdout, IsTerminal, Write};\n\nuse termimad::{\n    crossterm::{\n        event::{read, Event, KeyCode},\n        queue,\n        terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},\n    },\n    Area, MadSkin, MadView,\n};\n\nstatic MANUAL_DIR: Dir = include_dir!(\"$CARGO_MANIFEST_DIR/manuals\");\n\n/// Get a random feature to highlight from available manuals\npub fn get_random_feature_hint() -> Option<String> {\n    let features: Vec<(&str, String)> = MANUAL_DIR\n        .files()\n        .filter_map(|file| {\n            let path = file.path();\n            let name = path.file_stem()?.to_str()?;\n\n            if name == \"index\" {\n                return None;\n            }\n\n            let content = file.contents_utf8()?;\n            let display_name = extract_display_name(content).unwrap_or_else(|| name.to_string());\n            Some((name, display_name))\n        })\n        .collect();\n\n    if features.is_empty() {\n        return None;\n    }\n\n    features\n        .choose(&mut rand::thread_rng())\n        .map(|(feature, display_name)| {\n            format!(\"Did you know that Turso supports {display_name}? Type .manual {feature} to learn more.\")\n        })\n}\n\nfn extract_display_name(content: &str) -> Option<String> {\n    if !content.starts_with(\"---\") {\n        return None;\n    }\n\n    let lines: Vec<&str> = content.lines().collect();\n    let end_idx = lines[1..].iter().position(|&line| line == \"---\")? + 1;\n\n    for line in &lines[1..end_idx] {\n        if let Some(display_name) = line.strip_prefix(\"display_name: \") {\n            return Some(display_name.trim_matches('\"').to_string());\n        }\n    }\n\n    None\n}\n\nfn strip_frontmatter(content: &str) -> &str {\n    if !content.starts_with(\"---\") {\n        return content;\n    }\n\n    if let Some(end_pos) = content[3..].find(\"\\n---\\n\") {\n        &content[end_pos + 7..]\n    } else {\n        content\n    }\n}\n\n// not ideal but enough for our usecase , probably overkill maybe.\nfn levenshtein(a: &str, b: &str) -> usize {\n    let a_chars: Vec<_> = a.chars().collect();\n    let b_chars: Vec<_> = b.chars().collect();\n    let (a_len, b_len) = (a_chars.len(), b_chars.len());\n    if a_len == 0 {\n        return b_len;\n    }\n    if b_len == 0 {\n        return a_len;\n    }\n    let mut prev_row: Vec<usize> = (0..=b_len).collect();\n    let mut current_row = vec![0; b_len + 1];\n    for i in 1..=a_len {\n        current_row[0] = i;\n        for j in 1..=b_len {\n            let substitution_cost = if a_chars[i - 1] == b_chars[j - 1] {\n                0\n            } else {\n                1\n            };\n            current_row[j] = (prev_row[j] + 1)\n                .min(current_row[j - 1] + 1)\n                .min(prev_row[j - 1] + substitution_cost);\n        }\n        prev_row.clone_from_slice(&current_row);\n    }\n    prev_row[b_len]\n}\n\nfn find_closest_manual_page<'a>(\n    page_name: &str,\n    available_pages: impl Iterator<Item = &'a str>,\n) -> Option<&'a str> {\n    const RELATIVE_SIMILARITY_THRESHOLD: f64 = 0.4;\n\n    available_pages\n        .filter_map(|candidate| {\n            let distance = levenshtein(page_name, candidate);\n            let longer_len = std::cmp::max(page_name.chars().count(), candidate.chars().count());\n            if longer_len == 0 {\n                return None;\n            }\n            let relative_distance = distance as f64 / longer_len as f64;\n\n            if relative_distance < RELATIVE_SIMILARITY_THRESHOLD {\n                Some((candidate, distance))\n            } else {\n                None\n            }\n        })\n        .min_by_key(|&(_, score)| score)\n        .map(|(name, _)| name)\n}\n\npub fn display_manual(page: Option<&str>, writer: &mut dyn Write) -> anyhow::Result<()> {\n    let page_name = page.unwrap_or(\"index\");\n    let file_name = format!(\"{page_name}.md\");\n\n    if let Some(file) = MANUAL_DIR.get_file(&file_name) {\n        let content = file\n            .contents_utf8()\n            .ok_or_else(|| anyhow::anyhow!(\"Failed to read manual page: {}\", page_name))?;\n        let content = strip_frontmatter(content);\n        if IsTerminal::is_terminal(&std::io::stdout()) {\n            render_in_terminal(content)?;\n        } else {\n            writeln!(writer, \"{content}\")?;\n        }\n        Ok(())\n    } else if page.is_none() {\n        // If no page specified, list available pages\n        return list_available_manuals(writer);\n    } else {\n        let available_pages = MANUAL_DIR\n            .files()\n            .filter_map(|file| file.path().file_stem().and_then(|stem| stem.to_str()));\n        let mut error_message = format!(\"Manual page not found: {page_name}\");\n        if let Some(suggestion) = find_closest_manual_page(page_name, available_pages) {\n            error_message.push_str(&format!(\"\\n\\nDid you mean '.manual {suggestion}'?\"));\n        }\n        Err(anyhow::anyhow!(error_message))\n    }\n}\n\nfn render_in_terminal(content: &str) -> anyhow::Result<()> {\n    // Create a skin with nice styling\n    let mut skin = MadSkin::default();\n\n    // Customize the skin for better appearance\n    skin.set_headers_fg(termimad::crossterm::style::Color::Cyan);\n    skin.bold.set_fg(termimad::crossterm::style::Color::Yellow);\n    skin.italic\n        .set_fg(termimad::crossterm::style::Color::Magenta);\n    skin.inline_code\n        .set_fg(termimad::crossterm::style::Color::Green);\n    skin.code_block\n        .set_fg(termimad::crossterm::style::Color::Green);\n\n    let mut w = stdout();\n    queue!(w, EnterAlternateScreen)?;\n    enable_raw_mode()?;\n\n    let area = Area::full_screen();\n    let mut view = MadView::from(content.to_string(), area, skin);\n\n    loop {\n        view.write_on(&mut w)?;\n        w.flush()?;\n\n        match read()? {\n            Event::Key(key) => match key.code {\n                KeyCode::Up | KeyCode::Char('k') => view.try_scroll_lines(-1),\n                KeyCode::Down | KeyCode::Char('j') => view.try_scroll_lines(1),\n                KeyCode::PageUp => view.try_scroll_pages(-1),\n                KeyCode::PageDown => view.try_scroll_pages(1),\n                KeyCode::Char('g') => view.scroll = 0,\n                KeyCode::Char('G') => view.try_scroll_lines(i32::MAX),\n\n                KeyCode::Esc | KeyCode::Char('q') | KeyCode::Enter => break,\n\n                _ => {}\n            },\n            Event::Resize(width, height) => {\n                let new_area = Area::new(0, 0, width, height);\n                view.resize(&new_area);\n            }\n            _ => {}\n        }\n    }\n\n    disable_raw_mode()?;\n    queue!(w, LeaveAlternateScreen)?;\n    w.flush()?;\n\n    Ok(())\n}\n\nfn list_available_manuals(writer: &mut dyn Write) -> anyhow::Result<()> {\n    writeln!(writer, \"Available manual pages:\")?;\n    writeln!(writer)?;\n    let mut pages: Vec<String> = MANUAL_DIR\n        .files()\n        .filter_map(|file| file.path().file_stem()?.to_str().map(String::from))\n        .collect();\n    pages.sort();\n\n    for page in &pages {\n        writeln!(writer, \"  .manual {page}  # or .man {page}\")?;\n    }\n\n    if pages.is_empty() {\n        writeln!(writer, \"  (No manual pages found)\")?;\n    }\n    writeln!(writer)?;\n    writeln!(writer, \"Usage: .manual <page>  or  .man <page>\")?;\n\n    Ok(())\n}\n"
  },
  {
    "path": "cli/manuals/cdc.md",
    "content": "---\ndisplay_name: \"Change Data Capture\"\n---\n\n# CDC - Change Data Capture\n\n## Overview\n\nChange Data Capture (CDC) allows you to track and capture all data changes (inserts, updates, deletes) made to your database tables. This is useful for building reactive applications, syncing data between systems, replication, auditing, and more.\n\n## Enabling CDC\n\nCDC is enabled per connection using the PRAGMA command:\n\n```sql\nPRAGMA capture_data_changes_conn('<mode>[,<table_name>]');\n```\n\n### Parameters\n\n- **mode**: The capture mode (see below)\n- **table_name**: Optional custom table name for storing changes (defaults to `turso_cdc`)\n\n### Capture Modes\n\n- **`off`**: Disable CDC for this connection\n- **`id`**: Capture only the primary key/rowid of changed rows\n- **`before`**: Capture row state before changes (for updates/deletes)\n- **`after`**: Capture row state after changes (for inserts/updates)\n- **`full`**: Capture both before and after states, plus update details\n\n## Examples\n\n### Basic Usage\n\nEnable CDC with ID mode (captures primary keys only):\n```sql\nPRAGMA capture_data_changes_conn('id');\n```\n\n### Using Different Modes\n\nCapture the state before changes:\n```sql\nPRAGMA capture_data_changes_conn('before');\n```\n\nCapture the state after changes:\n```sql\nPRAGMA capture_data_changes_conn('after');\n```\n\nCapture complete change information:\n```sql\nPRAGMA capture_data_changes_conn('full');\n```\n\n### Custom CDC Table\n\nStore changes in a custom table instead of the default `turso_cdc`:\n```sql\nPRAGMA capture_data_changes_conn('full,my_changes_table');\n```\n\n### Disable CDC\n\nTurn off CDC for the current connection:\n```sql\nPRAGMA capture_data_changes_conn('off');\n```\n\n## CDC Table Structure\n\nThe CDC table (default name: `turso_cdc`) contains the following columns:\n\n| Column | Type | Description |\n|--------|------|-------------|\n| `change_id` | INTEGER | Auto-incrementing unique identifier for each change |\n| `change_time` | INTEGER | Timestamp of the change (Unix epoch) |\n| `change_type` | INTEGER | Type of change: 1 (INSERT), 0 (UPDATE), -1 (DELETE) |\n| `table_name` | TEXT | Name of the table that was changed |\n| `id` | varies | Primary key/rowid of the changed row |\n| `before` | BLOB | Row data before the change (for modes: before, full) |\n| `after` | BLOB | Row data after the change (for modes: after, full) |\n| `updates` | BLOB | Details of updated columns (for mode: full) |\n\n## Querying Changes\n\nOnce CDC is enabled, you can query the changes table like any other table:\n\n```sql\n-- View all captured changes\nSELECT * FROM turso_cdc;\n\n-- View only inserts\nSELECT * FROM turso_cdc WHERE change_type = 1;\n\n-- View only updates\nSELECT * FROM turso_cdc WHERE change_type = 0;\n\n-- View only deletes\nSELECT * FROM turso_cdc WHERE change_type = -1;\n\n-- View changes for a specific table\nSELECT * FROM turso_cdc WHERE table_name = 'users';\n\n-- View recent changes (last hour)\nSELECT * FROM turso_cdc\nWHERE change_time > unixepoch() - 3600;\n```\n\n## Practical Example\n\n```sql\n-- Create a table\nCREATE TABLE users (\n    id INTEGER PRIMARY KEY,\n    name TEXT,\n    email TEXT\n);\n\n-- Enable full CDC\nPRAGMA capture_data_changes_conn('full');\n\n-- Make some changes\nINSERT INTO users VALUES (1, 'Alice', 'alice@example.com');\nINSERT INTO users VALUES (2, 'Bob', 'bob@example.com');\nUPDATE users SET email = 'alice@newdomain.com' WHERE id = 1;\nDELETE FROM users WHERE id = 2;\n\n-- View the captured changes\nSELECT change_type, table_name, id\nFROM turso_cdc;\n\n-- Results will show:\n-- 1 (INSERT) for Alice\n-- 1 (INSERT) for Bob\n-- 0 (UPDATE) for Alice's email change\n-- -1 (DELETE) for Bob\n```\n\n## Multiple Connections\n\nEach connection can have its own CDC configuration:\n\n```sql\n-- Connection 1: Capture to 'audit_log' table\nPRAGMA capture_data_changes_conn('full,audit_log');\n\n-- Connection 2: Capture to 'sync_queue' table\nPRAGMA capture_data_changes_conn('id,sync_queue');\n\n-- Changes from Connection 1 go to 'audit_log'\n-- Changes from Connection 2 go to 'sync_queue'\n```\n\n## Transactions\n\nCDC respects transaction boundaries. Changes are only recorded when a transaction commits:\n\n```sql\nBEGIN;\nINSERT INTO users VALUES (3, 'Charlie', 'charlie@example.com');\nUPDATE users SET name = 'Charles' WHERE id = 3;\n-- CDC table is not yet updated\n\nCOMMIT;\n-- Now both the INSERT and UPDATE appear in the CDC table\n```\n\nIf a transaction rolls back, no CDC entries are created for those changes.\n\n## Schema Changes\n\nCDC also tracks schema changes when using full mode:\n\n```sql\nPRAGMA capture_data_changes_conn('full');\n\nCREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT);\n-- Recorded in CDC as change to sqlite_schema\n\nDROP TABLE products;\n-- Also recorded as a schema change\n```"
  },
  {
    "path": "cli/manuals/custom-types.md",
    "content": "---\ndisplay_name: \"custom types\"\n---\n\n# Custom Types\n\n## Overview\n\nTurso extends SQLite's STRICT table type system with user-defined custom types. Custom types let you define how values are encoded before storage and decoded when read, enforce domain constraints at the storage layer, attach operators, and provide defaults — all declared in pure SQL.\n\nCustom types work only with **STRICT** tables (which are always enabled):\n\n```\ntursodb mydb.db\n```\n\nWithout this flag, `CREATE TYPE`, `DROP TYPE`, the `sqlite_turso_types` virtual table, and all built-in custom types (date, varchar, numeric, etc.) are unavailable. `PRAGMA list_types` will only show the five base SQLite types (INTEGER, REAL, TEXT, BLOB, ANY).\n\n## Creating a Type\n\n```sql\nCREATE TYPE type_name BASE base_type\n    ENCODE encode_expr\n    DECODE decode_expr\n    [OPERATOR 'op' [function_name] ...]\n    [DEFAULT default_expr];\n```\n\n- **BASE** — The underlying SQLite storage type (`text`, `integer`, `real`, `blob`).\n- **ENCODE** — Expression applied to `value` before writing to disk.\n- **DECODE** — Expression applied to `value` when reading from disk.\n- **OPERATOR** — (Optional) Custom operator overloads for the type. If `function_name` is omitted, the base type's built-in comparison is used (see [Ordering](#ordering) below).\n- **DEFAULT** — (Optional) Default value for columns of this type when no value is supplied.\n\nThe special identifier `value` refers to the input being encoded or decoded.\n\n## Dropping a Type\n\n```sql\nDROP TYPE type_name;\nDROP TYPE IF EXISTS type_name;\n```\n\nA type cannot be dropped while any table has a column using it.\n\n## Basic Examples\n\n### Identity Type (Passthrough)\n\nThe simplest custom type — stores and reads values unchanged:\n\n```sql\nCREATE TYPE passthrough BASE text ENCODE value DECODE value;\nCREATE TABLE t1(val passthrough) STRICT;\nINSERT INTO t1 VALUES ('hello');\nSELECT val FROM t1;\n-- hello\n```\n\n### Reversed Text\n\nEncode reverses the string for storage; decode reverses it back:\n\n```sql\nCREATE TYPE reversed BASE text\n    ENCODE string_reverse(value)\n    DECODE string_reverse(value);\nCREATE TABLE t1(val reversed) STRICT;\nINSERT INTO t1 VALUES ('hello');\nSELECT val FROM t1;\n-- hello  (stored on disk as 'olleh')\n```\n\n### Cents (Expression-Based Encode/Decode)\n\nStore monetary values as integers (cents) but present them as whole units:\n\n```sql\nCREATE TYPE cents BASE integer ENCODE value * 100 DECODE value / 100;\nCREATE TABLE prices(amount cents) STRICT;\nINSERT INTO prices VALUES (42);\nSELECT amount FROM prices;\n-- 42  (stored on disk as 4200)\n```\n\n### JSON Validation\n\nUse `json()` as the encoder to reject malformed JSON at insert time:\n\n```sql\nCREATE TYPE jsontype BASE text ENCODE json(value) DECODE value;\nCREATE TABLE t1(val jsontype) STRICT;\nINSERT INTO t1 VALUES ('{\"key\": 1}');  -- OK\nINSERT INTO t1 VALUES ('not json');    -- Error: malformed JSON\n```\n\n## Operators\n\nCustom types can overload SQL operators so expressions like `val + val` or `val < 10` call user-defined functions:\n\n```sql\nCREATE TYPE uint BASE text\n    ENCODE test_uint_encode(value)\n    DECODE test_uint_decode(value)\n    OPERATOR '+' (uint) -> test_uint_add\n    OPERATOR '<' (uint) -> test_uint_lt\n    OPERATOR '=' (uint) -> test_uint_eq;\n\nCREATE TABLE t1(val uint) STRICT;\nINSERT INTO t1 VALUES (20);\nINSERT INTO t1 VALUES (30);\n\nSELECT val + val FROM t1;\n-- 40\n-- 60\n\nSELECT val FROM t1 WHERE val < 25;\n-- 20\n```\n\n## Ordering\n\nSorting and indexing always operate on **encoded (on-disk) values**, not decoded values. DECODE is purely a presentation layer — it controls how values appear in query results, but has no effect on sort order or index structure.\n\nCustom types that support ordering must declare `OPERATOR '<'`. Without it, `ORDER BY` and `CREATE INDEX` on columns of that type are **forbidden** — attempting either produces a clear error.\n\n### Naked `OPERATOR '<'` (Base Type Comparison)\n\nA naked `OPERATOR '<'` (no function name) tells Turso to compare encoded values using the base type's built-in comparison. This works correctly when the encoding preserves the desired sort order:\n\n```sql\n-- ENCODE value * 100 is monotonic: 10→100, 20→200, 30→300.\n-- Sorting encoded integers preserves numeric order.\nCREATE TYPE cents BASE integer\n    ENCODE value * 100\n    DECODE value / 100\n    OPERATOR '<';\n\nCREATE TABLE prices(id INTEGER PRIMARY KEY, amount cents) STRICT;\nINSERT INTO prices VALUES (1, 30), (2, 10), (3, 20);\nSELECT amount FROM prices ORDER BY amount;\n-- 10\n-- 20\n-- 30\n```\n\nIf the encoding does **not** preserve order, the sort will reflect the encoded representation:\n\n```sql\n-- string_reverse is NOT monotonic: encoded text sorts differently than decoded.\n-- Encoded: apple→elppa, banana→ananab, cherry→yrrehc.\n-- Encoded text sort: ananab < elppa < yrrehc → display: banana, apple, cherry.\nCREATE TYPE reversed BASE text\n    ENCODE string_reverse(value)\n    DECODE string_reverse(value)\n    OPERATOR '<';\n\nCREATE TABLE t(id INTEGER PRIMARY KEY, val reversed) STRICT;\nINSERT INTO t VALUES (1, 'apple'), (2, 'banana'), (3, 'cherry');\nSELECT val FROM t ORDER BY val;\n-- banana\n-- apple\n-- cherry\n```\n\n### `OPERATOR '<'` with a Function (Custom Comparator)\n\nFor types where the base type comparison on encoded values is not suitable, provide a custom comparator function. The comparator transforms encoded values before comparing:\n\n```sql\n-- numeric stores values as blobs; standard blob comparison is wrong.\n-- numeric_lt knows how to compare encoded blobs numerically.\nCREATE TYPE numeric(precision, scale) BASE blob\n    ENCODE numeric_encode(value, precision, scale)\n    DECODE numeric_decode(value)\n    OPERATOR '<' numeric_lt;\n```\n\nA comparator can also recover a desired sort order from a non-order-preserving encoding:\n\n```sql\n-- Same encoding as above, but the comparator reverses encoded values\n-- before comparing, recovering alphabetical order.\nCREATE TYPE reversed_alpha BASE text\n    ENCODE string_reverse(value)\n    DECODE string_reverse(value)\n    OPERATOR '<' string_reverse;\n\nCREATE TABLE t(id INTEGER PRIMARY KEY, val reversed_alpha) STRICT;\nINSERT INTO t VALUES (1, 'apple'), (2, 'banana'), (3, 'cherry');\nSELECT val FROM t ORDER BY val;\n-- apple\n-- banana\n-- cherry\n```\n\n### Non-Orderable Types\n\nTypes without `OPERATOR '<'` cannot be used in `ORDER BY` or `CREATE INDEX`:\n\n```sql\nCREATE TYPE mytype BASE text ENCODE value DECODE value;\nCREATE TABLE t(val mytype) STRICT;\n\nSELECT val FROM t ORDER BY val;\n-- Error: cannot ORDER BY column 'val' of type 'mytype': type does not declare OPERATOR '<'\n\nCREATE INDEX idx ON t(val);\n-- Error: cannot create index on column 'val' of type 'mytype': type does not declare OPERATOR '<'\n```\n\nExpression indexes that compute a regular value from a non-orderable column are still allowed:\n\n```sql\nCREATE INDEX idx ON t(length(val));  -- OK: length() returns an integer\n```\n\n### Built-In Types with Ordering\n\nThe following built-in types declare `OPERATOR '<'` and support `ORDER BY` and indexing: `date`, `time`, `timestamp`, `varchar`, `smallint`, `boolean`, `uuid`, `bytea`, `numeric`.\n\nTypes without ordering support: `json`, `jsonb`, `inet`.\n\n## Defaults\n\n### Type-Level Default\n\nA default defined on the type applies to all columns of that type unless overridden:\n\n```sql\nCREATE TYPE uint BASE text\n    ENCODE test_uint_encode(value)\n    DECODE test_uint_decode(value)\n    DEFAULT 0;\n\nCREATE TABLE t1(id INTEGER PRIMARY KEY, val uint) STRICT;\nINSERT INTO t1(id) VALUES (1);\nSELECT id, val FROM t1;\n-- 1|0\n```\n\n### Column-Level Override\n\nA column definition can override the type's default:\n\n```sql\nCREATE TABLE t1(id INTEGER PRIMARY KEY, val uint DEFAULT 42) STRICT;\nINSERT INTO t1(id) VALUES (1);\nSELECT id, val FROM t1;\n-- 1|42\n```\n\n### Function Default\n\nThe default can be an expression or function call:\n\n```sql\nCREATE TYPE reversed BASE text\n    ENCODE string_reverse(value)\n    DECODE string_reverse(value)\n    DEFAULT string_reverse('auto');\n\nCREATE TABLE t1(id INTEGER PRIMARY KEY, val reversed) STRICT;\nINSERT INTO t1(id) VALUES (1);\nSELECT id, val FROM t1;\n-- 1|otua\n```\n\n## Validation with CASE/RAISE\n\nUse `CASE ... ELSE RAISE(ABORT, ...)` in the ENCODE expression to validate values and reject invalid input with a clear error message:\n\n```sql\nCREATE TYPE positive_int BASE integer\n    ENCODE CASE WHEN value > 0 THEN value\n                ELSE RAISE(ABORT, 'value must be positive') END\n    DECODE value;\n\nCREATE TABLE t1(val positive_int) STRICT;\nINSERT INTO t1 VALUES (42);   -- OK\nINSERT INTO t1 VALUES (-1);   -- Error: value must be positive\n```\n\nThis pattern is how built-in types like `varchar` and `smallint` enforce their constraints:\n\n```sql\n-- varchar checks length against the maxlen parameter\nCREATE TYPE varchar(maxlen) BASE text\n    ENCODE CASE WHEN length(value) <= maxlen THEN value\n                ELSE RAISE(ABORT, 'value too long for varchar') END\n    DECODE value;\n\n-- smallint checks the integer range\nCREATE TYPE smallint BASE integer\n    ENCODE CASE WHEN value BETWEEN -32768 AND 32767 THEN value\n                ELSE RAISE(ABORT, 'integer out of range for smallint') END\n    DECODE value;\n```\n\n## Parametric Types\n\nTypes can declare parameters that are substituted into ENCODE/DECODE expressions. Parameters are specified in parentheses after the type name:\n\n```sql\nCREATE TYPE varchar(maxlen) BASE text\n    ENCODE CASE WHEN length(value) <= maxlen THEN value\n                ELSE RAISE(ABORT, 'value too long for varchar') END\n    DECODE value;\n\nCREATE TABLE t1(name varchar(10)) STRICT;\nINSERT INTO t1 VALUES ('hello');      -- OK (length 5 <= 10)\nINSERT INTO t1 VALUES ('toolongname'); -- Error: value too long for varchar\n```\n\nWhen a column is declared as `varchar(10)`, the parameter `maxlen` is replaced with `10` in the ENCODE expression.\n\n## Encode Validation\n\nEncoding runs **before** constraint checks (NOT NULL, type affinity). If an encode function returns NULL for a NOT NULL or PRIMARY KEY column, the insert is rejected:\n\n```sql\nCREATE TYPE my_uuid BASE text ENCODE uuid_blob(value) DECODE uuid_str(value);\nCREATE TABLE t1(id my_uuid PRIMARY KEY, name TEXT) STRICT;\nINSERT INTO t1 VALUES ('invalid-uuid', 'bad');\n-- Error: NOT NULL constraint failed (uuid_blob returned NULL)\n```\n\n## CHECK Constraints\n\nIn STRICT tables, CHECK constraint comparisons are type-checked at table creation time. A custom type column cannot be directly compared to a raw literal — the types must match. Use `CAST` to convert literals to the custom type:\n\n```sql\n-- ERROR: type mismatch in CHECK constraint (cents vs INTEGER)\nCREATE TABLE t1(amount cents CHECK(amount < 50)) STRICT;\n\n-- OK: CAST converts the literal to cents, both sides have the same type\nCREATE TABLE t1(amount cents CHECK(amount < CAST(50 AS cents))) STRICT;\n```\n\nThis rule applies to all comparisons in STRICT tables, not just custom types:\n\n```sql\n-- ERROR: type mismatch (INTEGER vs TEXT)\nCREATE TABLE t1(age INTEGER CHECK(age < 'old')) STRICT;\n\n-- OK: same types\nCREATE TABLE t1(age INTEGER CHECK(age >= 18)) STRICT;\n```\n\nFunction calls in CHECK expressions also require CAST, because the return type cannot be determined at table creation time:\n\n```sql\n-- ERROR: cannot determine return type of length()\nCREATE TABLE t1(name TEXT CHECK(length(name) < 10)) STRICT;\n\n-- OK: CAST makes the type explicit\nCREATE TABLE t1(name TEXT CHECK(CAST(length(name) AS INTEGER) < 10)) STRICT;\n```\n\n## NULL Handling\n\nNULL values bypass encoding and decoding entirely:\n\n```sql\nCREATE TYPE uint BASE text\n    ENCODE test_uint_encode(value)\n    DECODE test_uint_decode(value);\nCREATE TABLE t1(val uint) STRICT;\nINSERT INTO t1 VALUES (NULL);\nSELECT COALESCE(val, 'IS_NULL') FROM t1;\n-- IS_NULL\n```\n\n## CAST Support\n\nYou can cast values to a custom type, which applies the encode function:\n\n```sql\nCREATE TYPE reversed BASE text\n    ENCODE string_reverse(value)\n    DECODE string_reverse(value);\nSELECT CAST('hello' AS reversed);\n-- olleh\n```\n\n## Inspecting Types\n\n### PRAGMA list_types\n\nList all available types (built-in and custom) with their metadata:\n\n```sql\nPRAGMA list_types;\n-- type      | parent | encode                | decode                | default | operators\n-- INTEGER   |        |                       |                       |         |\n-- REAL      |        |                       |                       |         |\n-- TEXT      |        |                       |                       |         |\n-- BLOB      |        |                       |                       |         |\n-- ANY       |        |                       |                       |         |\n-- uint      | text   | test_uint_encode(...) | test_uint_decode(...) | 0       | +(uint) -> test_uint_add\n```\n\n### sqlite_turso_types\n\nAll types (built-in and user-defined) are available through the `sqlite_turso_types` virtual table:\n\n```sql\nSELECT name, sql FROM sqlite_turso_types;\n```\n\n## Using with ALTER TABLE\n\nCustom types work with `ALTER TABLE ADD COLUMN`:\n\n```sql\nCREATE TYPE uint BASE text\n    ENCODE test_uint_encode(value)\n    DECODE test_uint_decode(value);\nCREATE TABLE t1(id INTEGER PRIMARY KEY) STRICT;\nALTER TABLE t1 ADD COLUMN val uint;\nINSERT INTO t1 VALUES (1, 42);\nSELECT id, val FROM t1;\n-- 1|42\n```\n\n## Restrictions\n\n- Custom types require **STRICT** tables.\n- A type cannot be dropped while any table column uses it.\n- `CREATE TYPE IF NOT EXISTS` silently succeeds if the type already exists.\n- Encode/decode expressions use the identifier `value` to reference the input.\n"
  },
  {
    "path": "cli/manuals/encryption.md",
    "content": "---\ndisplay_name: \"encryption at-rest\"\n---\n\n# Encryption - At-Rest Database Encryption\n\n## Overview\n\nTurso supports transparent at-rest encryption to protect your database files from unauthorized access. When enabled, all data written to disk is automatically encrypted, and decrypted when read, with no changes required to your application code.\n\n## Supported Ciphers\n\nTurso supports multiple encryption algorithms with different performance and security characteristics:\n\n### AES-GCM Family\n- **`aes128gcm`** - AES-128 in Galois/Counter Mode (16-byte key)\n- **`aes256gcm`** - AES-256 in Galois/Counter Mode (32-byte key)\n\n### AEGIS Family (High Performance)\n- **`aegis256`** - AEGIS-256 (32-byte key) - Recommended for most use cases\n- **`aegis128l`** - AEGIS-128L (16-byte key)\n- **`aegis128x2`** - AEGIS-128 with 2x parallelization (16-byte key)\n- **`aegis128x4`** - AEGIS-128 with 4x parallelization (16-byte key)\n- **`aegis256x2`** - AEGIS-256 with 2x parallelization (32-byte key)\n- **`aegis256x4`** - AEGIS-256 with 4x parallelization (32-byte key)\n\n**Note:** AEGIS ciphers generally offer better performance than AES-GCM while maintaining excellent security properties. AEGIS-256 is recommended as the default choice.\n\n## Generating Encryption Keys\n\nGenerate a secure encryption key using OpenSSL:\n\n```bash\n# For 32-byte key (256-bit) - use with aes256gcm, aegis256, etc.\nopenssl rand -hex 32\n\n# For 16-byte key (128-bit) - use with aes128gcm, aegis128l, etc.\nopenssl rand -hex 16\n```\n\nExample output:\n```\n2d7a30108d3eb3e45c90a732041fe54778bdcf707c76749fab7da335d1b39c1d\n```\n\n**Important:** Store your encryption key securely. If you lose the key, your encrypted data cannot be recovered.\n\n## Creating an Encrypted Database\n\n### Method 1: Using PRAGMAs\n\nStart Turso and set encryption parameters before creating tables. Do note that encryption is an experimental feature that must be explicitly enabled:\n\n```bash\ntursodb --experimental-encryption database.db\n```\n\nThen in the SQL shell:\n```sql\nPRAGMA cipher = 'aegis256';\nPRAGMA hexkey = '2d7a30108d3eb3e45c90a732041fe54778bdcf707c76749fab7da335d1b39c1d';\n\n-- Now create your tables and insert data\nCREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT);\nINSERT INTO users VALUES (1, 'Alice');\n```\n\n### Method 2: Using URI Parameters\n\nSpecify encryption parameters directly in the database URI:\n\n```bash\ntursodb --experimental-encryption \"file:database.db?cipher=aegis256&hexkey=2d7a30108d3eb3e45c90a732041fe54778bdcf707c76749fab7da335d1b39c1d\"\n```\n\n## Opening an Encrypted Database\n\n**Important:** To open an existing encrypted database, you MUST provide the cipher and key as URI parameters:\n\n```bash\ntursodb --experimental-encryption \"file:database.db?cipher=aegis256&hexkey=2d7a30108d3eb3e45c90a732041fe54778bdcf707c76749fab7da335d1b39c1d\"\n```\n\nAttempting to open an encrypted database without the correct cipher and key will fail.\n\n## Troubleshooting\n\n### \"Database is encrypted or is not a database\"\nThis error occurs when:\n- Opening an encrypted database without providing cipher/key\n- Using the wrong cipher or key\n- The database file is corrupted\n\n### \"Invalid hex string\"\n- Ensure your key is valid hexadecimal (0-9, a-f)\n- Check the key length matches your cipher (32 hex chars for 16 bytes, 64 for 32 bytes)\n"
  },
  {
    "path": "cli/manuals/index.md",
    "content": "# Turso Manual Pages\n\nWelcome to the Turso manual pages. These pages provide detailed documentation for various features and capabilities.\n\n## Available Manuals\n\n### custom-types - Custom Types for STRICT Tables\nDefine user-defined types with custom encode/decode logic, operator overloads, and defaults for STRICT tables.\n\n```\n.manual custom-types\n```\n\n### cdc - Change Data Capture\nTrack and capture all data changes made to your database tables for replication, syncing, and reactive applications.\n\n```\n.manual cdc\n```\n\n### encryption - At-Rest Database Encryption\nProtect your database files with transparent encryption using AES-GCM or high-performance AEGIS ciphers.\n\n```\n.manual encryption\n```\n\n### mcp - Model Context Protocol\nLearn about Turso's built-in MCP server that enables AI assistants and other tools to interact with your databases.\n\n```\n.manual mcp\n```\n\n### vector - Vector Search\nBuild similarity search and semantic search applications using vector embeddings and distance functions.\n\n```\n.manual vector\n```\n\n### materialized-views - Live Materialized Views\nCreate automatically updating views that use Incremental View Maintenance to stay current with minimal overhead.\n\n```\n.manual materialized-views\n```\n\n## Usage\n\nTo view a manual page, use the `.manual` or `.man` command:\n\n```\n.manual <page>    # Full command\n.man <page>       # Short alias\n```\n\n### Examples\n\n```\n.manual mcp       # View the MCP server documentation\n.man mcp          # Same as above, using the alias\n```\n\n## Adding More Manuals\n\nAdditional manual pages will be added for other features as they become available."
  },
  {
    "path": "cli/manuals/materialized-views.md",
    "content": "---\ndisplay_name: \"live materialized views\"\n---\n\n# Live Materialized Views\n\nLive materialized views in Turso are automatically updating database objects that maintain query results in real-time. Unlike traditional materialized views that require manual refresh, Turso's live materialized views use Incremental View Maintenance (IVM) to stay current with minimal overhead.\n\n## Enabling Materialized Views\n\nMaterialized views are an experimental feature that must be explicitly enabled:\n\n```bash\ntursodb --experimental-views your_database.db\n```\n\n## What Makes Them Special\n\nTraditional materialized views store a snapshot of query results that becomes stale as the underlying data changes. You must manually refresh them, which often means re-executing the entire query. That is a costly operation for large datasets.\n\nTurso's materialized views are different. They automatically update themselves by tracking only the changes to the underlying tables. When you insert, update, or delete a row, the materialized view calculates just the incremental changes needed to stay current. This means:\n\n- **No manual refresh required** - Views are always up-to-date\n- **Efficient updates** - Only processes changed data, not the entire dataset\n- **Real-time consistency** - Changes are reflected immediately\n- **Scalable performance** - Update cost is proportional to the size of changes, not the size of the table\n\n## How Incremental View Maintenance Works\n\nInstead of re-computing the entire view when data changes, IVM tracks what has changed and updates only the affected portions of the materialized view. For example:\n\n- When you insert a new row, IVM adds only that row's contribution to the view\n- When you delete a row, IVM removes only that row's contribution\n- When you update a row, IVM treats it as a delete of the old value followed by an insert of the new value\n\nThis approach is particularly powerful for aggregations. If you have a view that calculates the sum of millions of rows, adding one new row only requires adding that single value to the existing sum—not re-summing all million rows.\n\n## Transactional Consistency\n\nBecause live materialized views are instantly updated, they are fully transactional. Views are updated inside the same transaction as the base table modifications, ensuring:\n\n- **Atomic updates** - View changes are committed or rolled back together with base table changes\n- **Consistency** - Views never show partial updates or inconsistent state\n- **Isolation** - Other transactions see either the complete change or none of it\n- **Durability** - View updates are persisted with the same guarantees as regular tables\n\nIf a transaction rolls back, all changes—including those to materialized views—are rolled back together.\n\n## Creating Materialized Views\n\nCreate a materialized view using standard SQL syntax:\n\n```sql\nCREATE MATERIALIZED VIEW sales_summary AS\nSELECT\n    product_id,\n    COUNT(*) as total_sales,\n    SUM(amount) as revenue,\n    AVG(amount) as avg_sale_amount\nFROM sales\nGROUP BY product_id;\n```\n\nOnce created, you can query the materialized view like any table:\n\n```sql\nSELECT * FROM sales_summary WHERE revenue > 10000;\n```\n\n## Use Cases\n\nMaterialized views excel in scenarios where:\n\n1. **Dashboard queries** - Complex aggregations that power real-time dashboards\n2. **Reporting** - Pre-computed summaries for business intelligence\n3. **Denormalization** - Maintaining denormalized data without manual updates\n4. **Performance optimization** - Expensive joins or aggregations that are frequently queried\n\n## Current Limitations\n\nAs an experimental feature, materialized views in Turso currently have some limitations:\n\n- Not all SQL functions are supported in view definitions\n- Views cannot reference other views\n\n## Performance Considerations\n\nWhile materialized views provide excellent query performance, they do add overhead to write operations. Each insert, update, or delete must also update any dependent materialized views. Consider this trade-off when designing your schema:\n\n- Use materialized views for frequently-read, infrequently-written data\n- Avoid creating too many materialized views on highly volatile tables\n- Monitor the performance impact on write operations\n"
  },
  {
    "path": "cli/manuals/mcp.md",
    "content": "---\ndisplay_name: \"a built-in MCP server\"\n---\n\n# MCP Server - Model Context Protocol\n\n## Overview\n\nTurso includes a built-in MCP (Model Context Protocol) server that allows AI assistants and other tools to interact with your databases programmatically.\n\n## Starting the MCP Server\n\nTo start Turso in MCP server mode, use the `--mcp` flag:\n\n```bash\n/path/to/tursodb --mcp\n```\n\nThis will start an MCP server that listens on stdio for commands. The server starts without a database connection, allowing you to select or create databases using MCP commands.\n\n## Available Tools\n\nThe MCP server exposes the following tools:\n\n### `query`\nExecute a SQL query and get results.\n\n**Parameters:**\n- `sql` (string, required): The SQL query to execute\n\n**Example:**\n```json\n{\n  \"tool\": \"query\",\n  \"arguments\": {\n    \"sql\": \"SELECT * FROM users WHERE age > 21\"\n  }\n}\n```\n\n### `execute`\nExecute a SQL statement that modifies data (INSERT, UPDATE, DELETE).\n\n**Parameters:**\n- `sql` (string, required): The SQL statement to execute\n\n**Example:**\n```json\n{\n  \"tool\": \"execute\",\n  \"arguments\": {\n    \"sql\": \"INSERT INTO users (name, age) VALUES ('Alice', 30)\"\n  }\n}\n```\n\n### `list_tables`\nList all tables in the database.\n\n**Example:**\n```json\n{\n  \"tool\": \"list_tables\",\n  \"arguments\": {}\n}\n```\n\n### `describe_table`\nGet the schema of a specific table.\n\n**Parameters:**\n- `table` (string, required): The name of the table to describe\n\n**Example:**\n```json\n{\n  \"tool\": \"describe_table\",\n  \"arguments\": {\n    \"table\": \"users\"\n  }\n}\n```\n\n## Integration with AI Assistants\n\n### Claude Desktop\n\nTo use with Claude Desktop, add the following to your Claude Desktop configuration:\n\n```json\n{\n  \"mcpServers\": {\n    \"turso\": {\n      \"command\": \"/path/to/tursodb\",\n      \"args\": [\"--mcp\"]\n    }\n  }\n}\n```\n\nNote: You must use the full path to the tursodb executable as Claude Desktop may not recognize items in your PATH.\n\n### Other MCP Clients\n\nThe Turso MCP server follows the standard MCP protocol and can be used with any MCP-compatible client.\n\n## Example Session\n\nHere's an example of using the MCP server:\n\n1. **Start the server:**\n   ```bash\n   /path/to/tursodb --mcp\n   ```\n\n2. **Query data:**\n   ```\n   > What tables are in the database?\n   [Uses list_tables tool]\n\n   > Show me all users older than 25\n   [Uses query tool with \"SELECT * FROM users WHERE age > 25\"]\n   ```\n\n3. **Modify data:**\n   ```\n   > Add a new user named Bob who is 28 years old\n   [Uses execute tool with INSERT statement]\n   ```\n\n## Troubleshooting\n\n### Server doesn't start\n- Ensure the tursodb executable path is correct\n- Check that you're using the full path to the executable\n\n### Commands fail\n- Verify SQL syntax is correct\n- Check that tables and columns exist\n- Ensure you have write permissions if modifying data\n\n## See Also\n\n- MCP Protocol Documentation: https://modelcontextprotocol.io\n- Turso Documentation: https://turso.tech/docs"
  },
  {
    "path": "cli/manuals/vector.md",
    "content": "---\ndisplay_name: \"vector search\"\n---\n\n# Vector Search\n\n## Overview\n\nTurso supports vector operations for building similarity search and semantic search applications. Vectors are stored as BLOBs and can be searched using distance functions to find similar items.\n\n**Important:** Vector indexes are not yet supported. All vector searches currently use brute-force scanning, which means searching scales linearly with the number of rows.\n\n## Vector Types\n\nTurso supports two vector formats:\n\n- **`vector32`** - 32-bit floating-point vectors (4 bytes per dimension)\n- **`vector64`** - 64-bit floating-point vectors (8 bytes per dimension)\n\n## Creating and Storing Vectors\n\nVectors are stored in vector columns as-is, and represented on-disk as BLOBs. Embeddings are interpreted and validated at runtime. In order for embedding to be valid, it must be either JSON array of float values OR binary blob created with turso vector functiosn `vector32` / `vector64`. \n\n### Basic Example\n\n```sql\n-- Create a table with vector embeddings\nCREATE TABLE documents (\n    id INTEGER PRIMARY KEY,\n    content TEXT,\n    embedding BLOB  -- Store vector as BLOB\n);\n\n-- Insert vectors using vector32() or vector64()\nINSERT INTO documents VALUES\n    (1, 'Introduction to databases', vector32('[0.1, 0.2, 0.3, 0.4]')),\n    (2, 'SQL query optimization', vector32('[0.2, 0.1, 0.4, 0.3]')),\n    (3, 'Vector similarity search', vector32('[0.4, 0.3, 0.2, 0.1]'));\n```\n\n### Working with Higher Dimensions\n\nReal embeddings typically have hundreds or thousands of dimensions:\n\n```sql\n-- Example with 1536-dimensional embeddings (like OpenAI's ada-002)\nCREATE TABLE embeddings (\n    id INTEGER PRIMARY KEY,\n    text TEXT,\n    vector BLOB\n);\n\n-- Insert a 1536-dimensional vector\nINSERT INTO embeddings VALUES\n    (1, 'Sample text', vector32('[0.001, 0.002, ..., 0.1536]'));\n```\n\n## Vector Functions\n\n### Creation Functions\n\n- **`vector32(text)`** - Create a 32-bit float vector from JSON array\n- **`vector64(text)`** - Create a 64-bit float vector from JSON array\n\n### Distance Functions\n\n- **`vector_distance_l2(v1, v2)`** - Euclidean (L2) distance between vectors\n- **`vector_distance_cos(v1, v2)`** - Cosine distance (1 - cosine similarity)\n\n### Utility Functions\n\n- **`vector_extract(blob)`** - Convert vector BLOB back to JSON text\n- **`vector_concat(v1, v2)`** - Concatenate two vectors\n- **`vector_slice(v, start, end)`** - Extract a portion of a vector\n\n## Similarity Search Examples\n\n### Finding Similar Documents\n\n```sql\n-- Find documents similar to a query vector\nWITH query AS (\n    SELECT vector32('[0.15, 0.25, 0.35, 0.45]') AS query_vector\n)\nSELECT\n    id,\n    content,\n    vector_distance_l2(embedding, query_vector) AS distance\nFROM documents, query\nORDER BY distance\nLIMIT 5;\n```\n\n### Cosine Similarity Search\n\nCosine similarity is often preferred for text embeddings:\n\n```sql\n-- Find semantically similar documents using cosine distance\nWITH query AS (\n    SELECT vector32('[0.15, 0.25, 0.35, 0.45]') AS query_vector\n)\nSELECT\n    id,\n    content,\n    vector_distance_cos(embedding, query_vector) AS cosine_distance\nFROM documents, query\nORDER BY cosine_distance\nLIMIT 5;\n```\n\n### Threshold-Based Search\n\nFind all vectors within a certain distance:\n\n```sql\n-- Find all documents within distance threshold\nWITH query AS (\n    SELECT vector32('[0.15, 0.25, 0.35, 0.45]') AS query_vector\n)\nSELECT\n    id,\n    content,\n    vector_distance_l2(embedding, query_vector) AS distance\nFROM documents, query\nWHERE vector_distance_l2(embedding, query_vector) < 0.5\nORDER BY distance;\n```\n\n## Working with Vector Data\n\n### Inspecting Vectors\n\n```sql\n-- Extract and view vector data as JSON\nSELECT id, vector_extract(embedding) AS vector_json\nFROM documents\nLIMIT 3;\n```\n\n### Vector Operations\n\n```sql\n-- Concatenate two vectors\nSELECT vector_concat(\n    vector32('[1.0, 2.0]'),\n    vector32('[3.0, 4.0]')\n) AS concatenated;\n\n-- Slice a vector (extract dimensions 2-4)\nSELECT vector_slice(\n    vector32('[1.0, 2.0, 3.0, 4.0, 5.0]'),\n    2, 4\n) AS sliced;\n```\n\n## Building a Semantic Search Application\n\nHere's a complete example of a semantic search application:\n\n```sql\n-- 1. Create schema\nCREATE TABLE articles (\n    id INTEGER PRIMARY KEY,\n    title TEXT,\n    content TEXT,\n    embedding BLOB\n);\n\n-- 2. Insert pre-computed embeddings\nINSERT INTO articles VALUES\n    (1, 'Database Fundamentals', 'An introduction to relational databases...',\n     vector32('[0.12, -0.34, 0.56, ...]')),\n    (2, 'Machine Learning Basics', 'Understanding neural networks and deep learning...',\n     vector32('[0.23, 0.45, -0.67, ...]')),\n    (3, 'Web Development Guide', 'Modern web applications with JavaScript...',\n     vector32('[0.34, -0.12, 0.78, ...]'));\n\n-- 3. Search for similar articles\nWITH search_embedding AS (\n    -- This would come from your embedding model for the search query\n    SELECT vector32('[0.15, -0.30, 0.60, ...]') AS query_vec\n)\nSELECT\n    a.id,\n    a.title,\n    vector_distance_cos(a.embedding, s.query_vec) AS similarity_score\nFROM articles a, search_embedding s\nORDER BY similarity_score\nLIMIT 10;\n```\n\n## Performance Considerations\n\nSince vector indexes are not yet implemented, keep in mind:\n\n- **Linear scan**: Every search examines all rows in the table\n- **Memory usage**: Vectors consume significant space (4 bytes × dimensions for vector32)\n- **Optimization tips**:\n  - Use smaller dimensions when possible\n  - Pre-filter data with WHERE clauses before distance calculations\n  - Consider partitioning large datasets\n  - Use vector32 instead of vector64 unless high precision is needed\n\n## Common Use Cases\n\n- **Semantic search**: Find documents by meaning rather than keywords\n- **Recommendation systems**: Find similar items based on embeddings\n- **Duplicate detection**: Identify near-duplicate content\n- **Image similarity**: Search for similar images using visual embeddings\n- **Anomaly detection**: Find outliers in high-dimensional data\n"
  },
  {
    "path": "cli/mcp_server.rs",
    "content": "use anyhow::Result;\nuse serde::{Deserialize, Serialize};\nuse serde_json::{json, Value};\nuse std::io::{self, BufRead, BufReader, Write};\nuse std::path::PathBuf;\nuse std::sync::atomic::{AtomicUsize, Ordering};\nuse std::sync::mpsc;\nuse std::sync::{Arc, Mutex};\nuse std::thread;\nuse std::time::Duration;\nuse turso_core::{Connection, Database, DatabaseOpts, Numeric, OpenFlags, Value as DbValue};\n\n#[derive(Debug, Serialize, Deserialize)]\nstruct JsonRpcRequest {\n    jsonrpc: String,\n    id: Option<Value>,\n    method: String,\n    params: Option<Value>,\n}\n\n#[derive(Debug, Serialize, Deserialize)]\nstruct JsonRpcResponse {\n    jsonrpc: String,\n    id: Option<Value>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    result: Option<Value>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    error: Option<JsonRpcError>,\n}\n\n#[derive(Debug, Serialize, Deserialize)]\nstruct JsonRpcError {\n    code: i32,\n    message: String,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    data: Option<Value>,\n}\n\n#[derive(Debug, Serialize, Deserialize)]\nstruct InitializeRequest {\n    #[serde(rename = \"protocolVersion\")]\n    protocol_version: String,\n    capabilities: Value,\n    #[serde(rename = \"clientInfo\")]\n    client_info: Value,\n}\n\n#[derive(Debug, Serialize, Deserialize)]\nstruct CallToolRequest {\n    name: String,\n    arguments: Option<Value>,\n}\n\npub struct TursoMcpServer {\n    conn: Arc<Mutex<Arc<Connection>>>,\n    interrupt_count: Arc<AtomicUsize>,\n    current_db_path: Arc<Mutex<Option<String>>>,\n}\n\nimpl TursoMcpServer {\n    pub fn new(conn: Arc<Connection>, interrupt_count: Arc<AtomicUsize>) -> Self {\n        Self {\n            conn: Arc::new(Mutex::new(conn)),\n            interrupt_count,\n            current_db_path: Arc::new(Mutex::new(None)),\n        }\n    }\n\n    pub fn run(&self) -> Result<()> {\n        let stdout = io::stdout();\n        let mut stdout_lock = stdout.lock();\n\n        // Create a channel to receive lines from stdin\n        let (tx, rx) = mpsc::channel();\n\n        // Spawn a thread to read from stdin\n        thread::spawn(move || {\n            let stdin = io::stdin();\n            let reader = BufReader::new(stdin);\n\n            for line in reader.lines() {\n                match line {\n                    Ok(line) => {\n                        if tx.send(Ok(line)).is_err() {\n                            break; // Main thread has dropped the receiver\n                        }\n                    }\n                    Err(e) => {\n                        let _ = tx.send(Err(e));\n                        break;\n                    }\n                }\n            }\n        });\n\n        loop {\n            // Check if we've been interrupted\n            if self.interrupt_count.load(Ordering::SeqCst) > 0 {\n                eprintln!(\"MCP server interrupted, shutting down...\");\n                break;\n            }\n\n            // Try to receive a line with a timeout so we can check for interruption\n            match rx.recv_timeout(Duration::from_millis(100)) {\n                Ok(Ok(line)) => {\n                    if line.trim().is_empty() {\n                        continue;\n                    }\n\n                    let request: JsonRpcRequest = match serde_json::from_str(&line) {\n                        Ok(req) => req,\n                        Err(e) => {\n                            eprintln!(\"Failed to parse JSON-RPC request: {e}\");\n                            continue;\n                        }\n                    };\n\n                    let response = self.handle_request(request);\n                    // Don't send a response for notifications (when id is None)\n                    if response.id.is_some() || response.error.is_some() {\n                        let response_json = serde_json::to_string(&response)?;\n                        writeln!(stdout_lock, \"{response_json}\")?;\n                        stdout_lock.flush()?;\n                    }\n                }\n                Ok(Err(_)) => {\n                    // Error reading from stdin\n                    break;\n                }\n                Err(mpsc::RecvTimeoutError::Timeout) => {\n                    // Timeout - continue loop to check for interruption\n                    continue;\n                }\n                Err(mpsc::RecvTimeoutError::Disconnected) => {\n                    // Stdin thread has finished (EOF)\n                    break;\n                }\n            }\n        }\n\n        Ok(())\n    }\n\n    fn handle_request(&self, request: JsonRpcRequest) -> JsonRpcResponse {\n        // Check if this is a notification (no id field means it's a notification)\n        // Notifications should not receive a response according to JSON-RPC spec\n        if request.id.is_none() {\n            // For notifications, we return a special response that the caller should ignore\n            return JsonRpcResponse {\n                jsonrpc: \"2.0\".to_string(),\n                id: None,\n                result: None,\n                error: None,\n            };\n        }\n\n        match request.method.as_str() {\n            \"initialize\" => self.handle_initialize(request),\n            \"tools/list\" => self.handle_list_tools(request),\n            \"tools/call\" => self.handle_call_tool(request),\n            _ => JsonRpcResponse {\n                jsonrpc: \"2.0\".to_string(),\n                id: request.id,\n                result: None,\n                error: Some(JsonRpcError {\n                    code: -32601,\n                    message: \"Method not found\".to_string(),\n                    data: None,\n                }),\n            },\n        }\n    }\n\n    fn handle_initialize(&self, request: JsonRpcRequest) -> JsonRpcResponse {\n        JsonRpcResponse {\n            jsonrpc: \"2.0\".to_string(),\n            id: request.id,\n            result: Some(json!({\n                \"protocolVersion\": \"2024-11-05\",\n                \"capabilities\": {\n                    \"tools\": {}\n                },\n                \"serverInfo\": {\n                    \"name\": \"turso-mcp\",\n                    \"version\": \"1.0.0\"\n                }\n            })),\n            error: None,\n        }\n    }\n\n    fn handle_list_tools(&self, request: JsonRpcRequest) -> JsonRpcResponse {\n        JsonRpcResponse {\n            jsonrpc: \"2.0\".to_string(),\n            id: request.id,\n            result: Some(json!({\n                \"tools\": [\n                    {\n                        \"name\": \"open_database\",\n                        \"description\": \"Open or create a database file. Creates parent directories if needed.\",\n                        \"inputSchema\": {\n                            \"type\": \"object\",\n                            \"properties\": {\n                                \"path\": {\n                                    \"type\": \"string\",\n                                    \"description\": \"Path to the database file (absolute or relative). Use ':memory:' for in-memory database.\"\n                                }\n                            },\n                            \"required\": [\"path\"]\n                        }\n                    },\n                    {\n                        \"name\": \"current_database\",\n                        \"description\": \"Get the path of the currently open database\",\n                        \"inputSchema\": {\n                            \"type\": \"object\",\n                            \"properties\": {},\n                            \"required\": []\n                        }\n                    },\n                    {\n                        \"name\": \"list_tables\",\n                        \"description\": \"List all tables in the database\",\n                        \"inputSchema\": {\n                            \"type\": \"object\",\n                            \"properties\": {},\n                            \"required\": []\n                        }\n                    },\n                    {\n                        \"name\": \"describe_table\",\n                        \"description\": \"Describe the structure of a specific table\",\n                        \"inputSchema\": {\n                            \"type\": \"object\",\n                            \"properties\": {\n                                \"table_name\": {\n                                    \"type\": \"string\",\n                                    \"description\": \"Name of the table to describe\"\n                                }\n                            },\n                            \"required\": [\"table_name\"]\n                        }\n                    },\n                    {\n                        \"name\": \"execute_query\",\n                        \"description\": \"Execute a read-only SELECT query\",\n                        \"inputSchema\": {\n                            \"type\": \"object\",\n                            \"properties\": {\n                                \"query\": {\n                                    \"type\": \"string\",\n                                    \"description\": \"The SELECT query to execute\"\n                                }\n                            },\n                            \"required\": [\"query\"]\n                        }\n                    },\n                    {\n                        \"name\": \"insert_data\",\n                        \"description\": \"Insert new data into a table\",\n                        \"inputSchema\": {\n                            \"type\": \"object\",\n                            \"properties\": {\n                                \"query\": {\n                                    \"type\": \"string\",\n                                    \"description\": \"The INSERT statement to execute\"\n                                }\n                            },\n                            \"required\": [\"query\"]\n                        }\n                    },\n                    {\n                        \"name\": \"update_data\",\n                        \"description\": \"Update existing data in a table\",\n                        \"inputSchema\": {\n                            \"type\": \"object\",\n                            \"properties\": {\n                                \"query\": {\n                                    \"type\": \"string\",\n                                    \"description\": \"The UPDATE statement to execute\"\n                                }\n                            },\n                            \"required\": [\"query\"]\n                        }\n                    },\n                    {\n                        \"name\": \"delete_data\",\n                        \"description\": \"Delete data from a table\",\n                        \"inputSchema\": {\n                            \"type\": \"object\",\n                            \"properties\": {\n                                \"query\": {\n                                    \"type\": \"string\",\n                                    \"description\": \"The DELETE statement to execute\"\n                                }\n                            },\n                            \"required\": [\"query\"]\n                        }\n                    },\n                    {\n                        \"name\": \"schema_change\",\n                        \"description\": \"Execute schema modification statements (CREATE TABLE, ALTER TABLE, DROP TABLE)\",\n                        \"inputSchema\": {\n                            \"type\": \"object\",\n                            \"properties\": {\n                                \"query\": {\n                                    \"type\": \"string\",\n                                    \"description\": \"The schema modification statement to execute\"\n                                }\n                            },\n                            \"required\": [\"query\"]\n                        }\n                    }\n                ]\n            })),\n            error: None,\n        }\n    }\n\n    fn handle_call_tool(&self, request: JsonRpcRequest) -> JsonRpcResponse {\n        let tool_request: CallToolRequest = match request.params.as_ref() {\n            Some(params) => match serde_json::from_value(params.clone()) {\n                Ok(req) => req,\n                Err(e) => {\n                    return JsonRpcResponse {\n                        jsonrpc: \"2.0\".to_string(),\n                        id: request.id,\n                        result: None,\n                        error: Some(JsonRpcError {\n                            code: -32602,\n                            message: format!(\"Invalid params: {e}\"),\n                            data: None,\n                        }),\n                    };\n                }\n            },\n            None => {\n                return JsonRpcResponse {\n                    jsonrpc: \"2.0\".to_string(),\n                    id: request.id,\n                    result: None,\n                    error: Some(JsonRpcError {\n                        code: -32602,\n                        message: \"Missing params\".to_string(),\n                        data: None,\n                    }),\n                };\n            }\n        };\n\n        let result = match tool_request.name.as_str() {\n            \"open_database\" => self.open_database(&tool_request.arguments),\n            \"current_database\" => self.current_database(),\n            \"list_tables\" => self.list_tables(),\n            \"describe_table\" => self.describe_table(&tool_request.arguments),\n            \"execute_query\" => self.execute_query(&tool_request.arguments),\n            \"insert_data\" => self.insert_data(&tool_request.arguments),\n            \"update_data\" => self.update_data(&tool_request.arguments),\n            \"delete_data\" => self.delete_data(&tool_request.arguments),\n            \"schema_change\" => self.schema_change(&tool_request.arguments),\n            _ => {\n                return JsonRpcResponse {\n                    jsonrpc: \"2.0\".to_string(),\n                    id: request.id,\n                    result: None,\n                    error: Some(JsonRpcError {\n                        code: -32601,\n                        message: format!(\"Unknown tool: {}\", tool_request.name),\n                        data: None,\n                    }),\n                };\n            }\n        };\n\n        JsonRpcResponse {\n            jsonrpc: \"2.0\".to_string(),\n            id: request.id,\n            result: Some(json!({\n                \"content\": [{\n                    \"type\": \"text\",\n                    \"text\": result\n                }]\n            })),\n            error: None,\n        }\n    }\n\n    fn open_database(&self, arguments: &Option<Value>) -> String {\n        let path = match arguments {\n            Some(args) => match args.get(\"path\") {\n                Some(Value::String(p)) => p.clone(),\n                _ => return \"Missing or invalid path parameter\".to_string(),\n            },\n            None => return \"Missing path parameter\".to_string(),\n        };\n\n        // Create parent directories if needed\n        if path != \":memory:\" {\n            let db_path = PathBuf::from(&path);\n            if let Some(parent) = db_path.parent() {\n                if !parent.exists() {\n                    if let Err(e) = std::fs::create_dir_all(parent) {\n                        return format!(\"Failed to create parent directories: {e}\");\n                    }\n                }\n            }\n        }\n\n        // Open the new database connection\n        let conn = if path == \":memory:\" || path.contains([':', '?', '&', '#']) {\n            match Connection::from_uri(&path, DatabaseOpts::default()) {\n                Ok((_io, c)) => c,\n                Err(e) => return format!(\"Failed to open database '{path}': {e}\"),\n            }\n        } else {\n            match Database::open_new(\n                &path,\n                None::<&str>,\n                OpenFlags::default(),\n                DatabaseOpts::new().with_autovacuum(false),\n                None,\n            ) {\n                Ok((_io, db)) => match db.connect() {\n                    Ok(c) => c,\n                    Err(e) => return format!(\"Failed to connect to database '{path}': {e}\"),\n                },\n                Err(e) => return format!(\"Failed to open database '{path}': {e}\"),\n            }\n        };\n\n        // Update the connection and path\n        *self.conn.lock().unwrap() = conn;\n        *self.current_db_path.lock().unwrap() = Some(path.clone());\n\n        format!(\"Successfully opened database: {path}\")\n    }\n\n    fn current_database(&self) -> String {\n        match &*self.current_db_path.lock().unwrap() {\n            Some(path) => format!(\"Current database: {path}\"),\n            None => \"Current database: :memory: (default)\".to_string(),\n        }\n    }\n\n    fn list_tables(&self) -> String {\n        let query = \"SELECT name FROM sqlite_schema WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY 1\";\n\n        let conn = self.conn.lock().unwrap().clone();\n        match conn.query(query) {\n            Ok(Some(mut rows)) => {\n                let mut tables = Vec::new();\n\n                let res = rows.run_with_row_callback(|row| {\n                    if let Ok(DbValue::Text(table)) = row.get::<&DbValue>(0) {\n                        tables.push(table.to_string());\n                    }\n                    Ok(())\n                });\n                if let Err(err) = res {\n                    return err.to_string();\n                }\n\n                if tables.is_empty() {\n                    \"No tables found in the database\".to_string()\n                } else {\n                    tables.join(\", \")\n                }\n            }\n            Ok(None) => \"No results returned from the query\".to_string(),\n            Err(e) => format!(\"Error querying database: {e}\"),\n        }\n    }\n\n    fn describe_table(&self, arguments: &Option<Value>) -> String {\n        let table_name = match arguments {\n            Some(args) => match args.get(\"table_name\") {\n                Some(Value::String(name)) => name,\n                _ => return \"Missing or invalid table_name parameter\".to_string(),\n            },\n            None => return \"Missing table_name parameter\".to_string(),\n        };\n\n        let query = format!(\"PRAGMA table_info({table_name})\");\n\n        let conn = self.conn.lock().unwrap().clone();\n        match conn.query(&query) {\n            Ok(Some(mut rows)) => {\n                let mut columns = Vec::new();\n                let res = rows.run_with_row_callback(|row| {\n                    if let (Ok(col_name), Ok(col_type), Ok(not_null), Ok(default_value), Ok(pk)) = (\n                        row.get::<&DbValue>(1),\n                        row.get::<&DbValue>(2),\n                        row.get::<&DbValue>(3),\n                        row.get::<&DbValue>(4),\n                        row.get::<&DbValue>(5),\n                    ) {\n                        let default_str = if matches!(default_value, DbValue::Null) {\n                            \"\".to_string()\n                        } else {\n                            format!(\"DEFAULT {default_value}\")\n                        };\n\n                        columns.push(\n                            format!(\n                                \"{} {} {} {} {}\",\n                                col_name,\n                                col_type,\n                                if matches!(not_null, DbValue::Numeric(Numeric::Integer(1))) {\n                                    \"NOT NULL\"\n                                } else {\n                                    \"NULL\"\n                                },\n                                default_str,\n                                if matches!(pk, DbValue::Numeric(Numeric::Integer(1))) {\n                                    \"PRIMARY KEY\"\n                                } else {\n                                    \"\"\n                                }\n                            )\n                            .trim()\n                            .to_string(),\n                        );\n                    }\n                    Ok(())\n                });\n\n                if let Err(err) = res {\n                    return err.to_string();\n                }\n                if columns.is_empty() {\n                    format!(\"Table '{table_name}' not found\")\n                } else {\n                    format!(\"Table '{table_name}' columns:\\n{}\", columns.join(\"\\n\"))\n                }\n            }\n            Ok(None) => format!(\"Table '{table_name}' not found\"),\n            Err(e) => format!(\"Error querying database: {e}\"),\n        }\n    }\n\n    fn execute_query(&self, arguments: &Option<Value>) -> String {\n        let query = match arguments {\n            Some(args) => match args.get(\"query\") {\n                Some(Value::String(q)) => q,\n                _ => return \"Missing or invalid query parameter\".to_string(),\n            },\n            None => return \"Missing query parameter\".to_string(),\n        };\n\n        // Basic validation to ensure it's a read-only query\n        let trimmed_query = query.trim().to_lowercase();\n        if !trimmed_query.starts_with(\"select\") {\n            return \"Only SELECT queries are allowed\".to_string();\n        }\n\n        let conn = self.conn.lock().unwrap().clone();\n        match conn.query(query) {\n            Ok(Some(mut rows)) => {\n                let mut results = Vec::new();\n\n                // Get column names\n                let headers: Vec<String> = (0..rows.num_columns())\n                    .map(|i| rows.get_column_name(i).to_string())\n                    .collect();\n\n                // Get the data\n                let res = rows.run_with_row_callback(|row| {\n                    let mut row_data = Vec::new();\n\n                    for value in row.get_values() {\n                        row_data.push(value.to_string());\n                    }\n\n                    results.push(row_data);\n                    Ok(())\n                });\n\n                if let Err(err) = res {\n                    return err.to_string();\n                }\n\n                // Format results as text table\n                let mut output = String::new();\n                if !headers.is_empty() {\n                    output.push_str(&headers.join(\" | \"));\n                    output.push('\\n');\n                    output.push_str(&\"-\".repeat(headers.join(\" | \").len()));\n                    output.push('\\n');\n                }\n\n                for row in results {\n                    output.push_str(&row.join(\" | \"));\n                    output.push('\\n');\n                }\n\n                if output.is_empty() {\n                    \"No results returned from the query\".to_string()\n                } else {\n                    output\n                }\n            }\n            Ok(None) => \"No results returned from the query\".to_string(),\n            Err(e) => format!(\"Error executing query: {e}\"),\n        }\n    }\n\n    fn insert_data(&self, arguments: &Option<Value>) -> String {\n        let query = match arguments {\n            Some(args) => match args.get(\"query\") {\n                Some(Value::String(q)) => q,\n                _ => return \"Missing or invalid query parameter\".to_string(),\n            },\n            None => return \"Missing query parameter\".to_string(),\n        };\n\n        // Basic validation to ensure it's an INSERT query\n        let trimmed_query = query.trim().to_lowercase();\n        if !trimmed_query.starts_with(\"insert\") {\n            return \"Only INSERT statements are allowed\".to_string();\n        }\n\n        let conn = self.conn.lock().unwrap().clone();\n        match conn.execute(query) {\n            Ok(()) => \"INSERT successful.\".to_string(),\n            Err(e) => format!(\"Error executing INSERT: {e}\"),\n        }\n    }\n\n    fn update_data(&self, arguments: &Option<Value>) -> String {\n        let query = match arguments {\n            Some(args) => match args.get(\"query\") {\n                Some(Value::String(q)) => q,\n                _ => return \"Missing or invalid query parameter\".to_string(),\n            },\n            None => return \"Missing query parameter\".to_string(),\n        };\n\n        // Basic validation to ensure it's an UPDATE query\n        let trimmed_query = query.trim().to_lowercase();\n        if !trimmed_query.starts_with(\"update\") {\n            return \"Only UPDATE statements are allowed\".to_string();\n        }\n\n        let conn = self.conn.lock().unwrap().clone();\n        match conn.execute(query) {\n            Ok(()) => \"UPDATE successful.\".to_string(),\n            Err(e) => format!(\"Error executing UPDATE: {e}\"),\n        }\n    }\n\n    fn delete_data(&self, arguments: &Option<Value>) -> String {\n        let query = match arguments {\n            Some(args) => match args.get(\"query\") {\n                Some(Value::String(q)) => q,\n                _ => return \"Missing or invalid query parameter\".to_string(),\n            },\n            None => return \"Missing query parameter\".to_string(),\n        };\n\n        // Basic validation to ensure it's a DELETE query\n        let trimmed_query = query.trim().to_lowercase();\n        if !trimmed_query.starts_with(\"delete\") {\n            return \"Only DELETE statements are allowed\".to_string();\n        }\n\n        let conn = self.conn.lock().unwrap().clone();\n        match conn.execute(query) {\n            Ok(()) => \"DELETE successful.\".to_string(),\n            Err(e) => format!(\"Error executing DELETE: {e}\"),\n        }\n    }\n\n    fn schema_change(&self, arguments: &Option<Value>) -> String {\n        let query = match arguments {\n            Some(args) => match args.get(\"query\") {\n                Some(Value::String(q)) => q,\n                _ => return \"Missing or invalid query parameter\".to_string(),\n            },\n            None => return \"Missing query parameter\".to_string(),\n        };\n\n        // Basic validation to ensure it's a schema modification query\n        let trimmed_query = query.trim().to_lowercase();\n        if !trimmed_query.starts_with(\"create\")\n            && !trimmed_query.starts_with(\"alter\")\n            && !trimmed_query.starts_with(\"drop\")\n        {\n            return \"Only CREATE, ALTER, and DROP statements are allowed\".to_string();\n        }\n\n        let conn = self.conn.lock().unwrap().clone();\n        match conn.execute(query) {\n            Ok(()) => \"Schema change successful.\".to_string(),\n            Err(e) => format!(\"Error executing schema change: {e}\"),\n        }\n    }\n}\n"
  },
  {
    "path": "cli/mvcc_repl.rs",
    "content": "//! MVCC Concurrent Transaction REPL\n//!\n//! Utility for interactive testing of concurrent MVCC transactions.\n//!\n//! # Overview\n//!\n//! This provides an interactive REPL where you can drive multiple in-process database\n//! connections and test concurrent transaction behavior, conflict detection, and isolation\n//! semantics. Connections are created lazily, when they are first used.\n//!\n//! # Invocation\n//!\n//! ```bash\n//! cargo run --bin tursodb --features mvcc_repl -- --mvcc [path]\n//! ```\n//!\n//! # Usage Examples\n//!\n//! Once started, you'll see a prompt where you can run SQL on specific connections:\n//!\n//! ```text\n//! mvcc> conn1 CREATE TABLE t(x INT)\n//! [conn1] OK\n//!\n//! mvcc> conn1 BEGIN CONCURRENT\n//! [conn1] OK\n//!\n//! mvcc> conn2 BEGIN CONCURRENT\n//! [conn2] OK\n//!\n//! mvcc> conn1 INSERT INTO t VALUES (42)\n//! [conn1] OK\n//!\n//! mvcc> conn2 INSERT INTO t VALUES (42)\n//! [conn2] ERROR: write-write conflict (transaction rolled back)\n//! ```\nuse anyhow::Context as _;\nuse rustyline::DefaultEditor;\nuse std::{collections::HashMap, sync::Arc};\nuse turso_core::{Connection, Database, DatabaseOpts, LimboError, OpenFlags, Value};\n\npub fn run(path: &str) -> anyhow::Result<()> {\n    let interactive_stdin = std::io::IsTerminal::is_terminal(&std::io::stdin());\n    if interactive_stdin {\n        println!(\"MVCC REPL\");\n        println!(\"Type `connN SQL` to run SQL on connection N (e.g. `conn1 BEGIN CONCURRENT;`)\");\n        println!(\"Connections are auto-created on first use\");\n        println!();\n    }\n\n    let db = open_mvcc_db(path)?;\n    let mut connections: HashMap<String, Arc<Connection>> = HashMap::new();\n    let mut rl = DefaultEditor::new()?;\n\n    loop {\n        match rl.readline(\"mvcc> \") {\n            Ok(line) => {\n                let line = line.trim();\n                if line.is_empty() {\n                    continue;\n                }\n                if line == \".quit\" {\n                    break;\n                }\n\n                if let Some((conn_name, sql)) = parse_conn_input(line) {\n                    let conn = connections\n                        .entry(conn_name.clone())\n                        .or_insert_with(|| db.connect().expect(\"failed to create connection\"));\n\n                    match execute_and_display(conn, sql, &conn_name) {\n                        Ok(()) => {}\n                        Err(e) => {\n                            eprintln!(\"[{conn_name}] ERROR: {e}\");\n                        }\n                    }\n                } else {\n                    eprintln!(\"Error: format is `connN SQL` (e.g. `conn1 SELECT * FROM t`)\");\n                }\n            }\n            Err(rustyline::error::ReadlineError::Eof) => break,\n            Err(rustyline::error::ReadlineError::Interrupted) => {\n                println!();\n                continue;\n            }\n            Err(e) => anyhow::bail!(e),\n        }\n    }\n\n    Ok(())\n}\n\nfn open_mvcc_db(path: &str) -> anyhow::Result<Arc<Database>> {\n    let (_, db) = Database::open_new::<&str>(\n        path,\n        None,\n        OpenFlags::default(),\n        DatabaseOpts::default(),\n        None,\n    )\n    .context(\"failed to open database\")?;\n\n    let boot = db.connect()?;\n    boot.execute(\"PRAGMA journal_mode = 'mvcc'\")?;\n    boot.close()?;\n\n    Ok(db)\n}\n\nfn parse_conn_input(line: &str) -> Option<(String, &str)> {\n    let (first, rest) = line.split_once(char::is_whitespace)?;\n\n    if !first.starts_with(\"conn\") {\n        return None;\n    }\n\n    let num_part = &first[4..];\n    num_part.parse::<u32>().ok()?;\n\n    Some((first.to_string(), rest.trim()))\n}\n\nfn execute_and_display(conn: &Arc<Connection>, sql: &str, conn_name: &str) -> anyhow::Result<()> {\n    let mut stmt = conn.prepare(sql).map_err(|e| anyhow::anyhow!(\"{}\", e))?;\n\n    match stmt.run_collect_rows() {\n        Ok(rows) => {\n            if rows.is_empty() {\n                println!(\"[{conn_name}] OK\");\n            } else {\n                for row in rows {\n                    let formatted: Vec<String> = row.iter().map(fmt_value).collect();\n                    println!(\"[{conn_name}] {}\", formatted.join(\" | \"));\n                }\n            }\n            Ok(())\n        }\n        Err(LimboError::WriteWriteConflict) => Err(anyhow::anyhow!(\n            \"write-write conflict (transaction rolled back)\"\n        )),\n        Err(e) => Err(anyhow::anyhow!(\"{e}\")),\n    }\n}\n\nfn fmt_value(v: &Value) -> String {\n    use turso_core::Numeric;\n    match v {\n        Value::Null => \"NULL\".to_string(),\n        Value::Numeric(Numeric::Integer(i)) => i.to_string(),\n        Value::Numeric(Numeric::Float(f)) => {\n            let fval: f64 = (*f).into();\n            // Format floats without trailing zeros for cleaner display\n            if fval.fract() == 0.0 && fval.abs() < 1e10 {\n                format!(\"{fval:.1}\")\n            } else {\n                format!(\"{fval}\")\n            }\n        }\n        Value::Text(s) => s.to_string(),\n        Value::Blob(b) => format!(\"<blob {} bytes>\", b.len()),\n    }\n}\n"
  },
  {
    "path": "cli/opcodes_dictionary.rs",
    "content": "// This source code is derived from SQLite project, which is in public domain:\n//\n/// https://www.sqlite.org/copyright.html\nuse std::fmt::Display;\n\npub struct OpCodeDescription {\n    pub name: &'static str,\n    pub description: &'static str,\n}\n\nimpl Display for OpCodeDescription {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{}\\n-------\\n{}\", self.name, self.description)\n    }\n}\n\n// https://www.sqlite.org/opcode.html\npub const OPCODE_DESCRIPTIONS: [OpCodeDescription; 189] = [\n    OpCodeDescription { name: \"Abortable\", description: \"Verify that an Abort can happen. Assert if an Abort at this point might cause database corruption. This opcode only appears in debugging builds. An Abort is safe if either there have been no writes, or if there is an active statement journal.\" },\n    OpCodeDescription { name: \"Add\", description: \"Add the value in register P1 to the value in register P2 and store the result in register P3. If either input is NULL, the result is NULL.\" },\n    OpCodeDescription { name: \"AddImm\", description: \"Add the constant P2 to the value in register P1. The result is always an integer. To force any register to be an integer, just add 0.\" },\n    OpCodeDescription { name: \"Affinity\", description: \"Apply affinities to a range of P2 registers starting with P1. P4 is a string that is P2 characters long. The N-th character of the string indicates the column affinity that should be used for the N-th memory cell in the range.\" },\n    OpCodeDescription { name: \"AggFinal\", description: \"P1 is the memory location that is the accumulator for an aggregate or window function. Execute the finalizer function for an aggregate and store the result in P1. P2 is the number of arguments that the step function takes and P4 is a pointer to the FuncDef for this function. The P2 argument is not used by this opcode. It is only there to disambiguate functions that can take varying numbers of arguments. The P4 argument is only needed for the case where the step function was not previously called.\" },\n    OpCodeDescription { name: \"AggInverse\", description: \"Execute the xInverse function for an aggregate. The function has P5 arguments. P4 is a pointer to the FuncDef structure that specifies the function. Register P3 is the accumulator. The P5 arguments are taken from register P2 and its successors.\" },\n    OpCodeDescription { name: \"AggStep\", description: \"Execute the xStep function for an aggregate. The function has P5 arguments. P4 is a pointer to the FuncDef structure that specifies the function. Register P3 is the accumulator. The P5 arguments are taken from register P2 and its successors.\" },\n    OpCodeDescription { name: \"AggStep1\", description: \"Execute the xStep (if P1==0) or xInverse (if P1!=0) function for an aggregate. The function has P5 arguments. P4 is a pointer to the FuncDef structure that specifies the function. Register P3 is the accumulator. The P5 arguments are taken from register P2 and its successors. This opcode is initially coded as OP_AggStep0. On first evaluation, the FuncDef stored in P4 is converted into an sqlite3_context and the opcode is changed. In this way, the initialization of the sqlite3_context only happens once, instead of on each call to the step function.\" },\n    OpCodeDescription { name: \"AggValue\", description: \"Invoke the xValue() function and store the result in register P3. P2 is the number of arguments that the step function takes and P4 is a pointer to the FuncDef for this function. The P2 argument is not used by this opcode. It is only there to disambiguate functions that can take varying numbers of arguments. The P4 argument is only needed for the case where the step function was not previously called.\" },\n    OpCodeDescription { name: \"And\", description: \"Take the logical AND of the values in registers P1 and P2 and write the result into register P3. If either P1 or P2 is 0 (false) then the result is 0 even if the other input is NULL. A NULL and true or two NULLs give a NULL output.\" },\n    OpCodeDescription { name: \"AutoCommit\", description: \"Set the database auto-commit flag to P1 (1 or 0). If P2 is true, roll back any currently active btree transactions. If there are any active VMs (apart from this one), then a ROLLBACK fails. A COMMIT fails if there are active writing VMs or active VMs that use shared cache. This instruction causes the VM to halt.\" },\n    OpCodeDescription { name: \"BeginSubrtn\", description: \"Mark the beginning of a subroutine that can be entered in-line or that can be called using Gosub. The subroutine should be terminated by an Return instruction that has a P1 operand that is the same as the P2 operand to this opcode and that has P3 set to 1. If the subroutine is entered in-line, then the Return will simply fall through. But if the subroutine is entered using Gosub, then the Return will jump back to the first instruction after the Gosub. This routine works by loading a NULL into the P2 register. When the return address register contains a NULL, the Return instruction is a no-op that simply falls through to the next instruction (assuming that the Return opcode has a P3 value of 1). Thus if the subroutine is entered in-line, then the Return will cause in-line execution to continue. But if the subroutine is entered via Gosub, then the Return will cause a return to the address following the Gosub. This opcode is identical to Null. It has a different name only to make the byte code easier to read and verify.\" },\n    OpCodeDescription { name: \"BitAnd\", description: \"Take the bit-wise AND of the values in register P1 and P2 and store the result in register P3. If either input is NULL, the result is NULL.\" },\n    OpCodeDescription { name: \"BitNot\", description: \"Interpret the content of register P1 as an integer. Store the ones-complement of the P1 value into register P2. If P1 holds a NULL then store a NULL in P2.\" },\n    OpCodeDescription { name: \"BitOr\", description: \"Take the bit-wise OR of the values in register P1 and P2 and store the result in register P3. If either input is NULL, the result is NULL.\" },\n    OpCodeDescription { name: \"Blob\", description: \"P4 points to a blob of data P1 bytes long. Store this blob in register P2. If P4 is a NULL pointer, then construct a zero-filled blob that is P1 bytes long in P2.\" },\n    OpCodeDescription { name: \"Cast\", description: \"Force the value in register P1 to be the type defined by P2.\n    P2=='A' → BLOB\n    P2=='B' → TEXT\n    P2=='C' → NUMERIC\n    P2=='D' → INTEGER\n    P2=='E' → REAL A NULL value is not changed by this routine. It remains NULL.\" },\n    OpCodeDescription { name: \"Checkpoint\", description: \"Checkpoint database P1. This is a no-op if P1 is not currently in WAL mode. Parameter P2 is one of SQLITE_CHECKPOINT_PASSIVE, FULL, RESTART, or TRUNCATE. Write 1 or 0 into mem[P3] if the checkpoint returns SQLITE_BUSY or not, respectively. Write the number of pages in the WAL after the checkpoint into mem[P3+1] and the number of pages in the WAL that have been checkpointed after the checkpoint completes into mem[P3+2]. However on an error, mem[P3+1] and mem[P3+2] are initialized to -1.\" },\n    OpCodeDescription { name: \"Clear\", description: \"Delete all contents of the database table or index whose root page in the database file is given by P1. But, unlike Destroy, do not remove the table or index from the database file. The table being cleared is in the main database file if P2==0. If P2==1 then the table to be cleared is in the auxiliary database file that is used to store tables create using CREATE TEMPORARY TABLE. If the P3 value is non-zero, then the row change count is incremented by the number of rows in the table being cleared. If P3 is greater than zero, then the value stored in register P3 is also incremented by the number of rows in the table being cleared. See also: Destroy\" },\n    OpCodeDescription { name: \"Close\", description: \"Close a cursor previously opened as P1. If P1 is not currently open, this instruction is a no-op.\" },\n    OpCodeDescription { name: \"ClrSubtype\", description: \"Clear the subtype from register P1.\" },\n    OpCodeDescription { name: \"CollSeq\", description: \"P4 is a pointer to a CollSeq object. If the next call to a user function or aggregate calls sqlite3GetFuncCollSeq(), this collation sequence will be returned. This is used by the built-in min(), max() and nullif() functions. If P1 is not zero, then it is a register that a subsequent min() or max() aggregate will set to 1 if the current row is not the minimum or maximum. The P1 register is initialized to 0 by this instruction. The interface used by the implementation of the aforementioned functions to retrieve the collation sequence set by this opcode is not available publicly. Only built-in functions have access to this feature.\" },\n    OpCodeDescription { name: \"Column\", description: \"Interpret the data that cursor P1 points to as a structure built using the MakeRecord instruction. (See the MakeRecord opcode for additional information about the format of the data.) Extract the P2-th column from this record. If there are less than (P2+1) values in the record, extract a NULL. The value extracted is stored in register P3. If the record contains fewer than P2 fields, then extract a NULL. Or, if the P4 argument is a P4_MEM use the value of the P4 argument as the result. If the OPFLAG_LENGTHARG bit is set in P5 then the result is guaranteed to only be used by the length() function or the equivalent. The content of large blobs is not loaded, thus saving CPU cycles. If the OPFLAG_TYPEOFARG bit is set then the result will only be used by the typeof() function or the IS NULL or IS NOT NULL operators or the equivalent. In this case, all content loading can be omitted.\" },\n    OpCodeDescription { name: \"ColumnsUsed\", description: \"This opcode (which only exists if SQLite was compiled with SQLITE_ENABLE_COLUMN_USED_MASK) identifies which columns of the table or index for cursor P1 are used. P4 is a 64-bit integer (P4_INT64) in which the first 63 bits are one for each of the first 63 columns of the table or index that are actually used by the cursor. The high-order bit is set if any column after the 64th is used.\" },\n    OpCodeDescription { name: \"Compare\", description: \"Compare two vectors of registers in reg(P1)..reg(P1+P3-1) (call this vector \\\"A\\\") and in reg(P2)..reg(P2+P3-1) (\\\"B\\\"). Save the result of the comparison for use by the next Jump instruct. If P5 has the OPFLAG_PERMUTE bit set, then the order of comparison is determined by the most recent Permutation operator. If the OPFLAG_PERMUTE bit is clear, then register are compared in sequential order. P4 is a KeyInfo structure that defines collating sequences and sort orders for the comparison. The permutation applies to registers only. The KeyInfo elements are used sequentially. The comparison is a sort comparison, so NULLs compare equal, NULLs are less than numbers, numbers are less than strings, and strings are less than blobs. This opcode must be immediately followed by an Jump opcode.\" },\n    OpCodeDescription { name: \"Concat\", description: \"Add the text in register P1 onto the end of the text in register P2 and store the result in register P3. If either the P1 or P2 text are NULL then store NULL in P3. P3 = P2 || P1 It is illegal for P1 and P3 to be the same register. Sometimes, if P3 is the same register as P2, the implementation is able to avoid a memcpy().\" },\n    OpCodeDescription { name: \"Copy\", description: \"Make a copy of registers P1..P1+P3 into registers P2..P2+P3. If the 0x0002 bit of P5 is set then also clear the MEM_Subtype flag in the destination. The 0x0001 bit of P5 indicates that this Copy opcode cannot be merged. The 0x0001 bit is used by the query planner and does not come into play during query execution. This instruction makes a deep copy of the value. A duplicate is made of any string or blob constant. See also SCopy.\" },\n    OpCodeDescription { name: \"Count\", description: \"Store the number of entries (an integer value) in the table or index opened by cursor P1 in register P2. If P3==0, then an exact count is obtained, which involves visiting every btree page of the table. But if P3 is non-zero, an estimate is returned based on the current cursor position.\" },\n    OpCodeDescription { name: \"CreateBtree\", description: \"Allocate a new b-tree in the main database file if P1==0 or in the TEMP database file if P1==1 or in an attached database if P1>1. The P3 argument must be 1 (BTREE_INTKEY) for a rowid table it must be 2 (BTREE_BLOBKEY) for an index or WITHOUT ROWID table. The root page number of the new b-tree is stored in register P2.\" },\n    OpCodeDescription { name: \"CursorHint\", description: \"Provide a hint to cursor P1 that it only needs to return rows that satisfy the Expr in P4. TK_REGISTER terms in the P4 expression refer to values currently held in registers. TK_COLUMN terms in the P4 expression refer to columns in the b-tree to which cursor P1 is pointing.\" },\n    OpCodeDescription { name: \"CursorLock\", description: \"Lock the btree to which cursor P1 is pointing so that the btree cannot be written by an other cursor.\" },\n    OpCodeDescription { name: \"CursorUnlock\", description: \"Unlock the btree to which cursor P1 is pointing so that it can be written by other cursors.\" },\n    OpCodeDescription { name: \"DecrJumpZero\", description: \"Register P1 must hold an integer. Decrement the value in P1 and jump to P2 if the new value is exactly zero.\" },\n    OpCodeDescription { name: \"DeferredSeek\", description: \"P1 is an open index cursor and P3 is a cursor on the corresponding table. This opcode does a deferred seek of the P3 table cursor to the row that corresponds to the current row of P1. This is a deferred seek. Nothing actually happens until the cursor is used to read a record. That way, if no reads occur, no unnecessary I/O happens. P4 may be an array of integers (type P4_INTARRAY) containing one entry for each column in the P3 table. If array entry a(i) is non-zero, then reading column a(i)-1 from cursor P3 is equivalent to performing the deferred seek and then reading column i from P1. This information is stored in P3 and used to redirect reads against P3 over to P1, thus possibly avoiding the need to seek and read cursor P3.\" },\n    OpCodeDescription { name: \"Delete\", description: \"Delete the record at which the P1 cursor is currently pointing. If the OPFLAG_SAVEPOSITION bit of the P5 parameter is set, then the cursor will be left pointing at either the next or the previous record in the table. If it is left pointing at the next record, then the next Next instruction will be a no-op. As a result, in this case it is ok to delete a record from within a Next loop. If OPFLAG_SAVEPOSITION bit of P5 is clear, then the cursor will be left in an undefined state. If the OPFLAG_AUXDELETE bit is set on P5, that indicates that this delete is one of several associated with deleting a table row and all its associated index entries. Exactly one of those deletes is the \\\"primary\\\" delete. The others are all on OPFLAG_FORDELETE cursors or else are marked with the AUXDELETE flag. If the OPFLAG_NCHANGE (0x01) flag of P2 (NB: P2 not P5) is set, then the row change count is incremented (otherwise not). If the OPFLAG_ISNOOP (0x40) flag of P2 (not P5!) is set, then the pre-update-hook for deletes is run, but the btree is otherwise unchanged. This happens when the Delete is to be shortly followed by an Insert with the same key, causing the btree entry to be overwritten. P1 must not be pseudo-table. It has to be a real table with multiple rows. If P4 is not NULL then it points to a Table object. In this case either the update or pre-update hook, or both, may be invoked. The P1 cursor must have been positioned using NotFound prior to invoking this opcode in this case. Specifically, if one is configured, the pre-update hook is invoked if P4 is not NULL. The update-hook is invoked if one is configured, P4 is not NULL, and the OPFLAG_NCHANGE flag is set in P2. If the OPFLAG_ISUPDATE flag is set in P2, then P3 contains the address of the memory cell that contains the value that the rowid of the row will be set to by the update.\" },\n    OpCodeDescription { name: \"Destroy\", description: \"Delete an entire database table or index whose root page in the database file is given by P1. The table being destroyed is in the main database file if P3==0. If P3==1 then the table to be destroyed is in the auxiliary database file that is used to store tables create using CREATE TEMPORARY TABLE. If AUTOVACUUM is enabled then it is possible that another root page might be moved into the newly deleted root page in order to keep all root pages contiguous at the beginning of the database. The former value of the root page that moved - its value before the move occurred - is stored in register P2. If no page movement was required (because the table being dropped was already the last one in the database) then a zero is stored in register P2. If AUTOVACUUM is disabled then a zero is stored in register P2. This opcode throws an error if there are any active reader VMs when it is invoked. This is done to avoid the difficulty associated with updating existing cursors when a root page is moved in an AUTOVACUUM database. This error is thrown even if the database is not an AUTOVACUUM db in order to avoid introducing an incompatibility between autovacuum and non-autovacuum modes. See also: Clear\" },\n    OpCodeDescription { name: \"Divide\", description: \"Divide the value in register P1 by the value in register P2 and store the result in register P3 (P3=P2/P1). If the value in register P1 is zero, then the result is NULL. If either input is NULL, the result is NULL.\" },\n    OpCodeDescription { name: \"DropIndex\", description: \"Remove the internal (in-memory) data structures that describe the index named P4 in database P1. This is called after an index is dropped from disk (using the Destroy opcode) in order to keep the internal representation of the schema consistent with what is on disk.\" },\n    OpCodeDescription { name: \"DropTable\", description: \"Remove the internal (in-memory) data structures that describe the table named P4 in database P1. This is called after a table is dropped from disk (using the Destroy opcode) in order to keep the internal representation of the schema consistent with what is on disk.\" },\n    OpCodeDescription { name: \"DropTrigger\", description: \"Remove the internal (in-memory) data structures that describe the trigger named P4 in database P1. This is called after a trigger is dropped from disk (using the Destroy opcode) in order to keep the internal representation of the schema consistent with what is on disk.\" },\n    OpCodeDescription { name: \"ElseEq\", description: \"This opcode must follow an Lt or Gt comparison operator. There can be zero or more OP_ReleaseReg opcodes intervening, but no other opcodes are allowed to occur between this instruction and the previous Lt or Gt. If the result of an Eq comparison on the same two operands as the prior Lt or Gt would have been true, then jump to P2. If the result of an Eq comparison on the two previous operands would have been false or NULL, then fall through.\" },\n    OpCodeDescription { name: \"EndCoroutine\", description: \"The instruction at the address in register P1 is a Yield. Jump to the P2 parameter of that Yield. After the jump, the value register P1 is left with a value such that subsequent OP_Yields go back to the this same EndCoroutine instruction. See also: InitCoroutine\" },\n    OpCodeDescription { name: \"Eq\", description: \"Compare the values in register P1 and P3. If reg(P3)==reg(P1) then jump to address P2. The SQLITE_AFF_MASK portion of P5 must be an affinity character - SQLITE_AFF_TEXT, SQLITE_AFF_INTEGER, and so forth. An attempt is made to coerce both inputs according to this affinity before the comparison is made. If the SQLITE_AFF_MASK is 0x00, then numeric affinity is used. Note that the affinity conversions are stored back into the input registers P1 and P3. So this opcode can cause persistent changes to registers P1 and P3. Once any conversions have taken place, and neither value is NULL, the values are compared. If both values are blobs then memcmp() is used to determine the results of the comparison. If both values are text, then the appropriate collating function specified in P4 is used to do the comparison. If P4 is not specified then memcmp() is used to compare text string. If both values are numeric, then a numeric comparison is used. If the two values are of different types, then numbers are considered less than strings and strings are considered less than blobs. If SQLITE_NULLEQ is set in P5 then the result of comparison is always either true or false and is never NULL. If both operands are NULL then the result of comparison is true. If either operand is NULL then the result is false. If neither operand is NULL the result is the same as it would be if the SQLITE_NULLEQ flag were omitted from P5. This opcode saves the result of comparison for use by the new Jump opcode.\" },\n    OpCodeDescription { name: \"Expire\", description: \"Cause precompiled statements to expire. When an expired statement is executed using sqlite3_step() it will either automatically reprepare itself (if it was originally created using sqlite3_prepare_v2()) or it will fail with SQLITE_SCHEMA. If P1 is 0, then all SQL statements become expired. If P1 is non-zero, then only the currently executing statement is expired. If P2 is 0, then SQL statements are expired immediately. If P2 is 1, then running SQL statements are allowed to continue to run to completion. The P2==1 case occurs when a CREATE INDEX or similar schema change happens that might help the statement run faster but which does not affect the correctness of operation.\" },\n    OpCodeDescription { name: \"Filter\", description: \"Compute a hash on the key contained in the P4 registers starting with r[P3]. Check to see if that hash is found in the bloom filter hosted by register P1. If it is not present then maybe jump to P2. Otherwise fall through. False negatives are harmless. It is always safe to fall through, even if the value is in the bloom filter. A false negative causes more CPU cycles to be used, but it should still yield the correct answer. However, an incorrect answer may well arise from a false positive - if the jump is taken when it should fall through.\" },\n    OpCodeDescription { name: \"FilterAdd\", description: \"Compute a hash on the P4 registers starting with r[P3] and add that hash to the bloom filter contained in r[P1].\" },\n    OpCodeDescription { name: \"FinishSeek\", description: \"If cursor P1 was previously moved via DeferredSeek, complete that seek operation now, without further delay. If the cursor seek has already occurred, this instruction is a no-op.\" },\n    OpCodeDescription { name: \"FkCheck\", description: \"Halt with an SQLITE_CONSTRAINT error if there are any unresolved foreign key constraint violations. If there are no foreign key constraint violations, this is a no-op. FK constraint violations are also checked when the prepared statement exits. This opcode is used to raise foreign key constraint errors prior to returning results such as a row change count or the result of a RETURNING clause.\" },\n    OpCodeDescription { name: \"FkCounter\", description: \"Increment a \\\"constraint counter\\\" by P2 (P2 may be negative or positive). If P1 is non-zero, the database constraint counter is incremented (deferred foreign key constraints). Otherwise, if P1 is zero, the statement counter is incremented (immediate foreign key constraints).\" },\n    OpCodeDescription { name: \"FkIfZero\", description: \"This opcode tests if a foreign key constraint-counter is currently zero. If so, jump to instruction P2. Otherwise, fall through to the next instruction. If P1 is non-zero, then the jump is taken if the database constraint-counter is zero (the one that counts deferred constraint violations). If P1 is zero, the jump is taken if the statement constraint-counter is zero (immediate foreign key constraint violations).\" },\n    OpCodeDescription { name: \"Found\", description: \"If P4==0 then register P3 holds a blob constructed by MakeRecord. If P4>0 then register P3 is the first of P4 registers that form an unpacked record. Cursor P1 is on an index btree. If the record identified by P3 and P4 is a prefix of any entry in P1 then a jump is made to P2 and P1 is left pointing at the matching entry. This operation leaves the cursor in a state where it can be advanced in the forward direction. The Next instruction will work, but not the Prev instruction. See also: NotFound, NoConflict, NotExists. SeekGe\" },\n    OpCodeDescription { name: \"Function\", description: \"Invoke a user function (P4 is a pointer to an sqlite3_context object that contains a pointer to the function to be run) with arguments taken from register P2 and successors. The number of arguments is in the sqlite3_context object that P4 points to. The result of the function is stored in register P3. Register P3 must not be one of the function inputs. P1 is a 32-bit bitmask indicating whether or not each argument to the function was determined to be constant at compile time. If the first argument was constant then bit 0 of P1 is set. This is used to determine whether meta data associated with a user function argument using the sqlite3_set_auxdata() API may be safely retained until the next invocation of this opcode. See also: AggStep, AggFinal, PureFunc\" },\n    OpCodeDescription { name: \"Ge\", description: \"This works just like the Lt opcode except that the jump is taken if the content of register P3 is greater than or equal to the content of register P1. See the Lt opcode for additional information.\" },\n    OpCodeDescription { name: \"GetSubtype\", description: \"Extract the subtype value from register P1 and write that subtype into register P2. If P1 has no subtype, then P1 gets a NULL.\" },\n    OpCodeDescription { name: \"Gosub\", description: \"Write the current address onto register P1 and then jump to address P2.\" },\n    OpCodeDescription { name: \"Goto\", description: \"An unconditional jump to address P2. The next instruction executed will be the one at index P2 from the beginning of the program. The P1 parameter is not actually used by this opcode. However, it is sometimes set to 1 instead of 0 as a hint to the command-line shell that this Goto is the bottom of a loop and that the lines from P2 down to the current line should be indented for EXPLAIN output.\" },\n    OpCodeDescription { name: \"Gt\", description: \"This works just like the Lt opcode except that the jump is taken if the content of register P3 is greater than the content of register P1. See the Lt opcode for additional information.\" },\n    OpCodeDescription { name: \"Halt\", description: \"Exit immediately. All open cursors, etc are closed automatically. P1 is the result code returned by sqlite3_exec(), sqlite3_reset(), or sqlite3_finalize(). For a normal halt, this should be SQLITE_OK (0). For errors, it can be some other value. If P1!=0 then P2 will determine whether or not to rollback the current transaction. Do not rollback if P2==OE_Fail. Do the rollback if P2==OE_Rollback. If P2==OE_Abort, then back out all changes that have occurred during this execution of the VDBE, but do not rollback the transaction. If P4 is not null then it is an error message string. P5 is a value between 0 and 4, inclusive, that modifies the P4 string. 0: (no change) 1: NOT NULL constraint failed: P4 2: UNIQUE constraint failed: P4 3: CHECK constraint failed: P4 4: FOREIGN KEY constraint failed: P4 If P5 is not zero and P4 is NULL, then everything after the \\\":\\\" is omitted. There is an implied \\\"Halt 0 0 0\\\" instruction inserted at the very end of every program. So a jump past the last instruction of the program is the same as executing Halt.\" },\n    OpCodeDescription { name: \"HaltIfNull\", description: \"Check the value in register P3. If it is NULL then Halt using parameter P1, P2, and P4 as if this were a Halt instruction. If the value in register P3 is not NULL, then this routine is a no-op. The P5 parameter should be 1.\" },\n    OpCodeDescription { name: \"IdxDelete\", description: \"The content of P3 registers starting at register P2 form an unpacked index key. This opcode removes that entry from the index opened by cursor P1. If P5 is not zero, then raise an SQLITE_CORRUPT_INDEX error if no matching index entry is found. This happens when running an UPDATE or DELETE statement and the index entry to be updated or deleted is not found. For some uses of IdxDelete (example: the EXCEPT operator) it does not matter that no matching entry is found. For those cases, P5 is zero. Also, do not raise this (self-correcting and non-critical) error if in writable_schema mode.\" },\n    OpCodeDescription { name: \"IdxGE\", description: \"The P4 register values beginning with P3 form an unpacked index key that omits the PRIMARY KEY. Compare this key value against the index that P1 is currently pointing to, ignoring the PRIMARY KEY or ROWID fields at the end. If the P1 index entry is greater than or equal to the key value then jump to P2. Otherwise fall through to the next instruction.\" },\n    OpCodeDescription { name: \"IdxGT\", description: \"The P4 register values beginning with P3 form an unpacked index key that omits the PRIMARY KEY. Compare this key value against the index that P1 is currently pointing to, ignoring the PRIMARY KEY or ROWID fields at the end. If the P1 index entry is greater than the key value then jump to P2. Otherwise fall through to the next instruction.\" },\n    OpCodeDescription { name: \"IdxInsert\", description: \"Register P2 holds an SQL index key made using the MakeRecord instructions. This opcode writes that key into the index P1. Data for the entry is nil. If P4 is not zero, then it is the number of values in the unpacked key of reg(P2). In that case, P3 is the index of the first register for the unpacked key. The availability of the unpacked key can sometimes be an optimization. If P5 has the OPFLAG_APPEND bit set, that is a hint to the b-tree layer that this insert is likely to be an append. If P5 has the OPFLAG_NCHANGE bit set, then the change counter is incremented by this instruction. If the OPFLAG_NCHANGE bit is clear, then the change counter is unchanged. If the OPFLAG_USESEEKRESULT flag of P5 is set, the implementation might run faster by avoiding an unnecessary seek on cursor P1. However, the OPFLAG_USESEEKRESULT flag must only be set if there have been no prior seeks on the cursor or if the most recent seek used a key equivalent to P2. This instruction only works for indices. The equivalent instruction for tables is Insert.\" },\n    OpCodeDescription { name: \"IdxLE\", description: \"The P4 register values beginning with P3 form an unpacked index key that omits the PRIMARY KEY or ROWID. Compare this key value against the index that P1 is currently pointing to, ignoring the PRIMARY KEY or ROWID on the P1 index. If the P1 index entry is less than or equal to the key value then jump to P2. Otherwise fall through to the next instruction.\" },\n    OpCodeDescription { name: \"IdxLT\", description: \"The P4 register values beginning with P3 form an unpacked index key that omits the PRIMARY KEY or ROWID. Compare this key value against the index that P1 is currently pointing to, ignoring the PRIMARY KEY or ROWID on the P1 index. If the P1 index entry is less than the key value then jump to P2. Otherwise fall through to the next instruction.\" },\n    OpCodeDescription { name: \"IdxRowid\", description: \"Write into register P2 an integer which is the last entry in the record at the end of the index key pointed to by cursor P1. This integer should be the rowid of the table entry to which this index entry points. See also: Rowid, MakeRecord.\" },\n    OpCodeDescription { name: \"If\", description: \"Jump to P2 if the value in register P1 is true. The value is considered true if it is numeric and non-zero. If the value in P1 is NULL then take the jump if and only if P3 is non-zero.\" },\n    OpCodeDescription { name: \"IfNoHope\", description: \"Register P3 is the first of P4 registers that form an unpacked record. Cursor P1 is an index btree. P2 is a jump destination. In other words, the operands to this opcode are the same as the operands to NotFound and IdxGT. This opcode is an optimization attempt only. If this opcode always falls through, the correct answer is still obtained, but extra work is performed. A value of N in the seekHit flag of cursor P1 means that there exists a key P3:N that will match some record in the index. We want to know if it is possible for a record P3:P4 to match some record in the index. If it is not possible, we can skip some work. So if seekHit is less than P4, attempt to find out if a match is possible by running NotFound. This opcode is used in IN clause processing for a multi-column key. If an IN clause is attached to an element of the key other than the left-most element, and if there are no matches on the most recent seek over the whole key, then it might be that one of the key element to the left is prohibiting a match, and hence there is \\\"no hope\\\" of any match regardless of how many IN clause elements are checked. In such a case, we abandon the IN clause search early, using this opcode. The opcode name comes from the fact that the jump is taken if there is \\\"no hope\\\" of achieving a match. See also: NotFound, SeekHit\" },\n    OpCodeDescription { name: \"IfNot\", description: \"Jump to P2 if the value in register P1 is False. The value is considered false if it has a numeric value of zero. If the value in P1 is NULL then take the jump if and only if P3 is non-zero.\" },\n    OpCodeDescription { name: \"IfNotOpen\", description: \"If cursor P1 is not open or if P1 is set to a NULL row using the NullRow opcode, then jump to instruction P2. Otherwise, fall through.\" },\n    OpCodeDescription { name: \"IfNotZero\", description: \"Register P1 must contain an integer. If the content of register P1 is initially greater than zero, then decrement the value in register P1. If it is non-zero (negative or positive) and then also jump to P2. If register P1 is initially zero, leave it unchanged and fall through.\" },\n    OpCodeDescription { name: \"IfNullRow\", description: \"Check the cursor P1 to see if it is currently pointing at a NULL row. If it is, then set register P3 to NULL and jump immediately to P2. If P1 is not on a NULL row, then fall through without making any changes. If P1 is not an open cursor, then this opcode is a no-op.\" },\n    OpCodeDescription { name: \"IfPos\", description: \"Register P1 must contain an integer. If the value of register P1 is 1 or greater, subtract P3 from the value in P1 and jump to P2. If the initial value of register P1 is less than 1, then the value is unchanged and control passes through to the next instruction.\" },\n    OpCodeDescription { name: \"IfSizeBetween\", description: \"Let N be the approximate number of rows in the table or index with cursor P1 and let X be 10*log2(N) if N is positive or -1 if N is zero. Jump to P2 if X is in between P3 and P4, inclusive.\" },\n    OpCodeDescription { name: \"IncrVacuum\", description: \"Perform a single step of the incremental vacuum procedure on the P1 database. If the vacuum has finished, jump to instruction P2. Otherwise, fall through to the next instruction.\" },\n    OpCodeDescription { name: \"Init\", description: \"Programs contain a single instance of this opcode as the very first opcode. If tracing is enabled (by the sqlite3_trace()) interface, then the UTF-8 string contained in P4 is emitted on the trace callback. Or if P4 is blank, use the string returned by sqlite3_sql(). If P2 is not zero, jump to instruction P2. Increment the value of P1 so that Once opcodes will jump the first time they are evaluated for this run. If P3 is not zero, then it is an address to jump to if an SQLITE_CORRUPT error is encountered.\" },\n    OpCodeDescription { name: \"InitCoroutine\", description: \"Set up register P1 so that it will Yield to the coroutine located at address P3. If P2!=0 then the coroutine implementation immediately follows this opcode. So jump over the coroutine implementation to address P2. See also: EndCoroutine\" },\n    OpCodeDescription { name: \"Insert\", description: \"Write an entry into the table of cursor P1. A new entry is created if it doesn't already exist or the data for an existing entry is overwritten. The data is the value MEM_Blob stored in register number P2. The key is stored in register P3. The key must be a MEM_Int. If the OPFLAG_NCHANGE flag of P5 is set, then the row change count is incremented (otherwise not). If the OPFLAG_LASTROWID flag of P5 is set, then rowid is stored for subsequent return by the sqlite3_last_insert_rowid() function (otherwise it is unmodified). If the OPFLAG_USESEEKRESULT flag of P5 is set, the implementation might run faster by avoiding an unnecessary seek on cursor P1. However, the OPFLAG_USESEEKRESULT flag must only be set if there have been no prior seeks on the cursor or if the most recent seek used a key equal to P3. If the OPFLAG_ISUPDATE flag is set, then this opcode is part of an UPDATE operation. Otherwise (if the flag is clear) then this opcode is part of an INSERT operation. The difference is only important to the update hook. Parameter P4 may point to a Table structure, or may be NULL. If it is not NULL, then the update-hook (sqlite3.xUpdateCallback) is invoked following a successful insert. (WARNING/TODO: If P1 is a pseudo-cursor and P2 is dynamically allocated, then ownership of P2 is transferred to the pseudo-cursor and register P2 becomes ephemeral. If the cursor is changed, the value of register P2 will then change. Make sure this does not cause any problems.) This instruction only works on tables. The equivalent instruction for indices is IdxInsert.\" },\n    OpCodeDescription { name: \"Int64\", description: \"P4 is a pointer to a 64-bit integer value. Write that value into register P2.\" },\n    OpCodeDescription { name: \"IntCopy\", description: \"Transfer the integer value held in register P1 into register P2. This is an optimized version of SCopy that works only for integer values.\" },\n    OpCodeDescription { name: \"Integer\", description: \"The 64-bit integer value P1 is written into register P2. This is different from SQLite, where this opcode is used for 32-bit integers\" },\n    OpCodeDescription { name: \"IntegrityCk\", description: \"Do an analysis of the currently open database. Store in register (P1+1) the text of an error message describing any problems. If no problems are found, store a NULL in register (P1+1). The register (P1) contains one less than the maximum number of allowed errors. At most reg(P1) errors will be reported. In other words, the analysis stops as soon as reg(P1) errors are seen. Reg(P1) is updated with the number of errors remaining. The root page numbers of all tables in the database are integers stored in P4_INTARRAY argument. If P5 is not zero, the check is done on the auxiliary database file, not the main database file. This opcode is used to implement the integrity_check pragma.\" },\n    OpCodeDescription { name: \"IsNull\", description: \"Jump to P2 if the value in register P1 is NULL.\" },\n    OpCodeDescription { name: \"IsTrue\", description: \"This opcode implements the IS TRUE, IS FALSE, IS NOT TRUE, and IS NOT FALSE operators. Interpret the value in register P1 as a boolean value. Store that boolean (a 0 or 1) in register P2. Or if the value in register P1 is NULL, then the P3 is stored in register P2. Invert the answer if P4 is 1. The logic is summarized like this:\n    If P3==0 and P4==0 then r[P2] := r[P1] IS TRUE\n    If P3==1 and P4==1 then r[P2] := r[P1] IS FALSE\n    If P3==0 and P4==1 then r[P2] := r[P1] IS NOT TRUE\n    If P3==1 and P4==0 then r[P2] := r[P1] IS NOT FALSE\"\n    },\n    OpCodeDescription { name: \"sType\", description: \"Jump to P2 if the type of a column in a btree is one of the types specified by the P5 bitmask. P1 is normally a cursor on a btree for which the row decode cache is valid through at least column P3. In other words, there should have been a prior Column for column P3 or greater. If the cursor is not valid, then this opcode might give spurious results. The the btree row has fewer than P3 columns, then use P4 as the datatype. If P1 is -1, then P3 is a register number and the datatype is taken from the value in that register. P5 is a bitmask of data types. SQLITE_INTEGER is the least significant (0x01) bit. SQLITE_FLOAT is the 0x02 bit. SQLITE_TEXT is 0x04. SQLITE_BLOB is 0x08. SQLITE_NULL is 0x10. WARNING: This opcode does not reliably distinguish between NULL and REAL when P1>=0. If the database contains a NaN value, this opcode will think that the datatype is REAL when it should be NULL. When P1<0 and the value is already stored in register P3, then this opcode does reliably distinguish between NULL and REAL. The problem only arises then P1>=0. Take the jump to address P2 if and only if the datatype of the value determined by P1 and P3 corresponds to one of the bits in the P5 bitmask.\" },\n    OpCodeDescription { name: \"JournalMode\", description: \"Change the journal mode of database P1 to P3. P3 must be one of the PAGER_JOURNALMODE_XXX values. If changing between the various rollback modes (delete, truncate, persist, off and memory), this is a simple operation. No IO is required. If changing into or out of WAL mode the procedure is more complicated. Write a string containing the final journal-mode to register P2.\" },\n    OpCodeDescription { name: \"Jump\", description: \"Jump to the instruction at address P1, P2, or P3 depending on whether in the most recent Compare instruction the P1 vector was less than, equal to, or greater than the P2 vector, respectively. This opcode must immediately follow an Compare opcode.\" },\n    OpCodeDescription { name: \"Last\", description: \"The next use of the Rowid or Column or Prev instruction for P1 will refer to the last entry in the database table or index. If the table or index is empty and P2>0, then jump immediately to P2. If P2 is 0 or if the table or index is not empty, fall through to the following instruction. This opcode leaves the cursor configured to move in reverse order, from the end toward the beginning. In other words, the cursor is configured to use Prev, not Next.\" },\n    OpCodeDescription { name: \"Le\", description: \"This works just like the Lt opcode except that the jump is taken if the content of register P3 is less than or equal to the content of register P1. See the Lt opcode for additional information.\" },\n    OpCodeDescription { name: \"LoadAnalysis\", description: \"Read the sqlite_stat1 table for database P1 and load the content of that table into the internal index hash table. This will cause the analysis to be used when preparing all subsequent queries.\" },\n    OpCodeDescription { name: \"Lt\", description: \"Compare the values in register P1 and P3. If reg(P3)<reg(P1) then jump to address P2. If the SQLITE_JUMPIFNULL bit of P5 is set and either reg(P1) or reg(P3) is NULL then the take the jump. If the SQLITE_JUMPIFNULL bit is clear then fall through if either operand is NULL. The SQLITE_AFF_MASK portion of P5 must be an affinity character - SQLITE_AFF_TEXT, SQLITE_AFF_INTEGER, and so forth. An attempt is made to coerce both inputs according to this affinity before the comparison is made. If the SQLITE_AFF_MASK is 0x00, then numeric affinity is used. Note that the affinity conversions are stored back into the input registers P1 and P3. So this opcode can cause persistent changes to registers P1 and P3. Once any conversions have taken place, and neither value is NULL, the values are compared. If both values are blobs then memcmp() is used to determine the results of the comparison. If both values are text, then the appropriate collating function specified in P4 is used to do the comparison. If P4 is not specified then memcmp() is used to compare text string. If both values are numeric, then a numeric comparison is used. If the two values are of different types, then numbers are considered less than strings and strings are considered less than blobs. This opcode saves the result of comparison for use by the new Jump opcode.\" },\n    OpCodeDescription { name: \"MakeRecord\", description: \"Convert P2 registers beginning with P1 into the record format use as a data record in a database table or as a key in an index. The Column opcode can decode the record later. P4 may be a string that is P2 characters long. The N-th character of the string indicates the column affinity that should be used for the N-th field of the index key. The mapping from character to affinity is given by the SQLITE_AFF_ macros defined in sqliteInt.h. If P4 is NULL then all index fields have the affinity BLOB. The meaning of P5 depends on whether or not the SQLITE_ENABLE_NULL_TRIM compile-time option is enabled: * If SQLITE_ENABLE_NULL_TRIM is enabled, then the P5 is the index of the right-most table that can be null-trimmed. * If SQLITE_ENABLE_NULL_TRIM is omitted, then P5 has the value OPFLAG_NOCHNG_MAGIC if the MakeRecord opcode is allowed to accept no-change records with serial_type 10. This value is only used inside an assert() and does not affect the end result.\" },\n    OpCodeDescription { name: \"MaxPgcnt\", description: \"Try to set the maximum page count for database P1 to the value in P3. Do not let the maximum page count fall below the current page count and do not change the maximum page count value if P3==0. Store the maximum page count after the change in register P2.\" },\n    OpCodeDescription { name: \"MemMax\", description: \"P1 is a register in the root frame of this VM (the root frame is different from the current frame if this instruction is being executed within a sub-program). Set the value of register P1 to the maximum of its current value and the value in register P2. This instruction throws an error if the memory cell is not initially an integer.\" },\n    OpCodeDescription { name: \"Move\", description: \"Move the P3 values in register P1..P1+P3-1 over into registers P2..P2+P3-1. Registers P1..P1+P3-1 are left holding a NULL. It is an error for register ranges P1..P1+P3-1 and P2..P2+P3-1 to overlap. It is an error for P3 to be less than 1.\" },\n    OpCodeDescription { name: \"Multiply\", description: \"Multiply the value in register P1 by the value in register P2 and store the result in register P3. If either input is NULL, the result is NULL.\" },\n    OpCodeDescription { name: \"MustBeInt\", description: \"Force the value in register P1 to be an integer. If the value in P1 is not an integer and cannot be converted into an integer without data loss, then jump immediately to P2, or if P2==0 raise an SQLITE_MISMATCH exception.\" },\n    OpCodeDescription { name: \"Ne\", description: \"This works just like the Eq opcode except that the jump is taken if the operands in registers P1 and P3 are not equal. See the Eq opcode for additional information.\" },\n    OpCodeDescription { name: \"NewRowid\", description: \"Get a new integer record number (a.k.a \\\"rowid\\\") used as the key to a table. The record number is not previously used as a key in the database table that cursor P1 points to. The new record number is written written to register P2. If P3>0 then P3 is a register in the root frame of this VDBE that holds the largest previously generated record number. No new record numbers are allowed to be less than this value. When this value reaches its maximum, an SQLITE_FULL error is generated. The P3 register is updated with the ' generated record number. This P3 mechanism is used to help implement the AUTOINCREMENT feature.\" },\n    OpCodeDescription { name: \"Next\", description: \"Advance cursor P1 so that it points to the next key/data pair in its table or index. If there are no more key/value pairs then fall through to the following instruction. But if the cursor advance was successful, jump immediately to P2. The Next opcode is only valid following an SeekGT, SeekGE, or Rewind opcode used to position the cursor. Next is not allowed to follow SeekLT, SeekLE, or Last. The P1 cursor must be for a real table, not a pseudo-table. P1 must have been opened prior to this opcode or the program will segfault. The P3 value is a hint to the btree implementation. If P3==1, that means P1 is an SQL index and that this instruction could have been omitted if that index had been unique. P3 is usually 0. P3 is always either 0 or 1. If P5 is positive and the jump is taken, then event counter number P5-1 in the prepared statement is incremented. See also: Prev\" },\n    OpCodeDescription { name: \"NoConflict\", description: \"If P4==0 then register P3 holds a blob constructed by MakeRecord. If P4>0 then register P3 is the first of P4 registers that form an unpacked record. Cursor P1 is on an index btree. If the record identified by P3 and P4 contains any NULL value, jump immediately to P2. If all terms of the record are not-NULL then a check is done to determine if any row in the P1 index btree has a matching key prefix. If there are no matches, jump immediately to P2. If there is a match, fall through and leave the P1 cursor pointing to the matching row. This opcode is similar to NotFound with the exceptions that the branch is always taken if any part of the search key input is NULL. This operation leaves the cursor in a state where it cannot be advanced in either direction. In other words, the Next and Prev opcodes do not work after this operation. See also: NotFound, Found, NotExists\" },\n    OpCodeDescription { name: \"Noop\", description: \"Do nothing. This instruction is often useful as a jump destination.\" },\n    OpCodeDescription { name: \"Not\", description: \"Interpret the value in register P1 as a boolean value. Store the boolean complement in register P2. If the value in register P1 is NULL, then a NULL is stored in P2.\" },\n    OpCodeDescription { name: \"NotExists\", description: \"P1 is the index of a cursor open on an SQL table btree (with integer keys). P3 is an integer rowid. If P1 does not contain a record with rowid P3 then jump immediately to P2. Or, if P2 is 0, raise an SQLITE_CORRUPT error. If P1 does contain a record with rowid P3 then leave the cursor pointing at that record and fall through to the next instruction. The SeekRowid opcode performs the same operation but also allows the P3 register to contain a non-integer value, in which case the jump is always taken. This opcode requires that P3 always contain an integer. The NotFound opcode performs the same operation on index btrees (with arbitrary multi-value keys). This opcode leaves the cursor in a state where it cannot be advanced in either direction. In other words, the Next and Prev opcodes will not work following this opcode. See also: Found, NotFound, NoConflict, SeekRowid\" },\n    OpCodeDescription { name: \"NotFound\", description: \"If P4==0 then register P3 holds a blob constructed by MakeRecord. If P4>0 then register P3 is the first of P4 registers that form an unpacked record. Cursor P1 is on an index btree. If the record identified by P3 and P4 is not the prefix of any entry in P1 then a jump is made to P2. If P1 does contain an entry whose prefix matches the P3/P4 record then control falls through to the next instruction and P1 is left pointing at the matching entry. This operation leaves the cursor in a state where it cannot be advanced in either direction. In other words, the Next and Prev opcodes do not work after this operation. See also: Found, NotExists, NoConflict, IfNoHope\" },\n    OpCodeDescription { name: \"NotNull\", description: \"Jump to P2 if the value in register P1 is not NULL.\" },\n    OpCodeDescription { name: \"Null\", description: \"Write a NULL into registers P2. If P3 greater than P2, then also write NULL into register P3 and every register in between P2 and P3. If P3 is less than P2 (typically P3 is zero) then only register P2 is set to NULL. If the P1 value is non-zero, then also set the MEM_Cleared flag so that NULL values will not compare equal even if SQLITE_NULLEQ is set on Ne or Eq.\" },\n    OpCodeDescription { name: \"NullRow\", description: \"Move the cursor P1 to a null row. Any Column operations that occur while the cursor is on the null row will always write a NULL. If cursor P1 is not previously opened, open it now to a special pseudo-cursor that always returns NULL for every column.\" },\n    OpCodeDescription { name: \"Offset\", description: \"Store in register r[P3] the byte offset into the database file that is the start of the payload for the record at which that cursor P1 is currently pointing. P2 is the column number for the argument to the sqlite_offset() function. This opcode does not use P2 itself, but the P2 value is used by the code generator. The P1, P2, and P3 operands to this opcode are the same as for Column. This opcode is only available if SQLite is compiled with the -DSQLITE_ENABLE_OFFSET_SQL_FUNC option.\" },\n    OpCodeDescription { name: \"OffsetLimit\", description: \"This opcode performs a commonly used computation associated with LIMIT and OFFSET processing. r[P1] holds the limit counter. r[P3] holds the offset counter. The opcode computes the combined value of the LIMIT and OFFSET and stores that value in r[P2]. The r[P2] value computed is the total number of rows that will need to be visited in order to complete the query. If r[P3] is zero or negative, that means there is no OFFSET and r[P2] is set to be the value of the LIMIT, r[P1]. if r[P1] is zero or negative, that means there is no LIMIT and r[P2] is set to -1. Otherwise, r[P2] is set to the sum of r[P1] and r[P3].\" },\n    OpCodeDescription { name: \"Once\", description: \"Fall through to the next instruction the first time this opcode is encountered on each invocation of the byte-code program. Jump to P2 on the second and all subsequent encounters during the same invocation. Top-level programs determine first invocation by comparing the P1 operand against the P1 operand on the Init opcode at the beginning of the program. If the P1 values differ, then fall through and make the P1 of this opcode equal to the P1 of Init. If P1 values are the same then take the jump. For subprograms, there is a bitmask in the VdbeFrame that determines whether or not the jump should be taken. The bitmask is necessary because the self-altering code trick does not work for recursive triggers.\" },\n    OpCodeDescription { name: \"OpenAutoindex\", description: \"This opcode works the same as OpenEphemeral. It has a different name to distinguish its use. Tables created using by this opcode will be used for automatically created transient indices in joins.\" },\n    OpCodeDescription { name: \"OpenDup\", description: \"Open a new cursor P1 that points to the same ephemeral table as cursor P2. The P2 cursor must have been opened by a prior OpenEphemeral opcode. Only ephemeral cursors may be duplicated. Duplicate ephemeral cursors are used for self-joins of materialized views.\" },\n    OpCodeDescription { name: \"OpenEphemeral\", description: \"Open a new cursor P1 to a transient table. The cursor is always opened read/write even if the main database is read-only. The ephemeral table is deleted automatically when the cursor is closed. If the cursor P1 is already opened on an ephemeral table, the table is cleared (all content is erased). P2 is the number of columns in the ephemeral table. The cursor points to a BTree table if P4==0 and to a BTree index if P4 is not 0. If P4 is not NULL, it points to a KeyInfo structure that defines the format of keys in the index. The P5 parameter can be a mask of the BTREE_* flags defined in btree.h. These flags control aspects of the operation of the btree. The BTREE_OMIT_JOURNAL and BTREE_SINGLE flags are added automatically. If P3 is positive, then reg[P3] is modified slightly so that it can be used as zero-length data for Insert. This is an optimization that avoids an extra Blob opcode to initialize that register.\" },\n    OpCodeDescription { name: \"OpenPseudo\", description: \"Open a new cursor that points to a fake table that contains a single row of data. The content of that one row is the content of memory register P2. In other words, cursor P1 becomes an alias for the MEM_Blob content contained in register P2. A pseudo-table created by this opcode is used to hold a single row output from the sorter so that the row can be decomposed into individual columns using the Column opcode. The Column opcode is the only cursor opcode that works with a pseudo-table. P3 is the number of fields in the records that will be stored by the pseudo-table. If P2 is 0 or negative then the pseudo-cursor will return NULL for every column.\"},\n    OpCodeDescription { name: \"OpenRead\", description: \"Open a read-only cursor for the database table whose root page is P2 in a database file. The database file is determined by P3. P3==0 means the main database, P3==1 means the database used for temporary tables, and P3>1 means used the corresponding attached database. Give the new cursor an identifier of P1. The P1 values need not be contiguous but all P1 values should be small integers. It is an error for P1 to be negative. Allowed P5 bits:\n    0x02 OPFLAG_SEEKEQ: This cursor will only be used for equality lookups (implemented as a pair of opcodes SeekGE/IdxGT of SeekLE/IdxLT) The P4 value may be either an integer (P4_INT32) or a pointer to a KeyInfo structure (P4_KEYINFO). If it is a pointer to a KeyInfo object, description:  then table being opened must be an index b-tree where the KeyInfo object defines the content and collating sequence of that index b-tree. Otherwise, if P4 is an integer value, then the table being opened must be a table b-tree with a number of columns no less than the value of P4. See also: OpenWrite, ReopenIdx\"\n    },\n    OpCodeDescription { name: \"OpenWrite\", description: \"Open a read/write cursor named P1 on the table or index whose root page is P2 (or whose root page is held in register P2 if the OPFLAG_P2ISREG bit is set in P5 - see below). The P4 value may be either an integer (P4_INT32) or a pointer to a KeyInfo structure (P4_KEYINFO). If it is a pointer to a KeyInfo object, then table being opened must be an index b-tree where the KeyInfo object defines the content and collating sequence of that index b-tree. Otherwise, if P4 is an integer value, then the table being opened must be a table b-tree with a number of columns no less than the value of P4. Allowed P5 bits:  },\n    0x02 OPFLAG_SEEKEQ: This cursor will only be used for equality lookups (implemented as a pair of opcodes SeekGE/IdxGT of SeekLE/IdxLT)\n    0x08 OPFLAG_FORDELETE: This cursor is used only to seek and subsequently delete entries in an index btree. This is a hint to the storage engine that the storage engine is allowed to ignore. The hint is not used by the official SQLite b*tree storage engine, but is used by COMDB2.\n    0x10 OPFLAG_P2ISREG: Use the content of register P2 as the root page, not the value of P2 itself. This instruction works like OpenRead except that it opens the cursor in read/write mode. See also: OpenRead, ReopenIdx\"\n    },\n    OpCodeDescription { name: \"Or\", description: \"Take the logical OR of the values in register P1 and P2 and store the answer in register P3. If either P1 or P2 is nonzero (true) then the result is 1 (true) even if the other input is NULL. A NULL and false or two NULLs give a NULL output.\" },\n    OpCodeDescription { name: \"Pagecount\", description: \"Write the current number of pages in database P1 to memory cell P2.\" },\n    OpCodeDescription { name: \"Param\", description: \"This opcode is only ever present in sub-programs called via the Program instruction. Copy a value currently stored in a memory cell of the calling (parent) frame to cell P2 in the current frames address space. This is used by trigger programs to access the new.* and old.* values. The address of the cell in the parent frame is determined by adding the value of the P1 argument to the value of the P1 argument to the calling Program instruction.\" },\n    OpCodeDescription { name: \"ParseSchema\", description: \"Read and parse all entries from the schema table of database P1 that match the WHERE clause P4. If P4 is a NULL pointer, then the entire schema for P1 is reparsed. This opcode invokes the parser to create a new virtual machine, then runs the new virtual machine. It is thus a re-entrant opcode.\" },\n    OpCodeDescription { name: \"Permutation\", description: \"Set the permutation used by the Compare operator in the next instruction. The permutation is stored in the P4 operand. The permutation is only valid for the next opcode which must be an Compare that has the OPFLAG_PERMUTE bit set in P5. The first integer in the P4 integer array is the length of the array and does not become part of the permutation.\" },\n    OpCodeDescription { name: \"Prev\", description: \"Back up cursor P1 so that it points to the previous key/data pair in its table or index. If there is no previous key/value pairs then fall through to the following instruction. But if the cursor backup was successful, jump immediately to P2. The Prev opcode is only valid following an SeekLT, SeekLE, or Last opcode used to position the cursor. Prev is not allowed to follow SeekGT, SeekGE, or Rewind. The P1 cursor must be for a real table, not a pseudo-table. If P1 is not open then the behavior is undefined. The P3 value is a hint to the btree implementation. If P3==1, that means P1 is an SQL index and that this instruction could have been omitted if that index had been unique. P3 is usually 0. P3 is always either 0 or 1. If P5 is positive and the jump is taken, then event counter number P5-1 in the prepared statement is incremented.\" },\n    OpCodeDescription { name: \"Program\", description: \"Execute the trigger program passed as P4 (type P4_SUBPROGRAM). P1 contains the address of the memory cell that contains the first memory cell in an array of values used as arguments to the sub-program. P2 contains the address to jump to if the sub-program throws an IGNORE exception using the RAISE() function. P2 might be zero, if there is no possibility that an IGNORE exception will be raised. Register P3 contains the address of a memory cell in this (the parent) VM that is used to allocate the memory required by the sub-vdbe at runtime. P4 is a pointer to the VM containing the trigger program. If P5 is non-zero, then recursive program invocation is enabled.\" },\n    OpCodeDescription { name: \"PureFunc\", description: \"Invoke a user function (P4 is a pointer to an sqlite3_context object that contains a pointer to the function to be run) with arguments taken from register P2 and successors. The number of arguments is in the sqlite3_context object that P4 points to. The result of the function is stored in register P3. Register P3 must not be one of the function inputs. P1 is a 32-bit bitmask indicating whether or not each argument to the function was determined to be constant at compile time. If the first argument was constant then bit 0 of P1 is set. This is used to determine whether meta data associated with a user function argument using the sqlite3_set_auxdata() API may be safely retained until the next invocation of this opcode. This opcode works exactly like Function. The only difference is in its name. This opcode is used in places where the function must be purely non-deterministic. Some built-in date/time functions can be either deterministic of non-deterministic, depending on their arguments. When those function are used in a non-deterministic way, they will check to see if they were called using PureFunc instead of Function, and if they were, they throw an error. See also: AggStep, AggFinal, Function\" },\n    OpCodeDescription { name: \"ReadCookie\", description: \"Read cookie number P3 from database P1 and write it into register P2. P3==1 is the schema version. P3==2 is the database format. P3==3 is the recommended pager cache size, and so forth. P1==0 is the main database file and P1==1 is the database file used to store temporary tables. There must be a read-lock on the database (either a transaction must be started or there must be an open cursor) before executing this instruction.\" },\n    OpCodeDescription { name: \"Real\", description: \"P4 is a pointer to a 64-bit floating point value. Write that value into register P2.\" },\n    OpCodeDescription { name: \"RealAffinity\", description: \"If register P1 holds an integer convert it to a real value. This opcode is used when extracting information from a column that has REAL affinity. Such column values may still be stored as integers, for space efficiency, but after extraction we want them to have only a real value.\" },\n    OpCodeDescription { name: \"ReleaseReg\", description: \"Release registers from service. Any content that was in the the registers is unreliable after this opcode completes. The registers released will be the P2 registers starting at P1, except if bit ii of P3 set, then do not release register P1+ii. In other words, P3 is a mask of registers to preserve. Releasing a register clears the Mem.pScopyFrom pointer. That means that if the content of the released register was set using SCopy, a change to the value of the source register for the SCopy will no longer generate an assertion fault in sqlite3VdbeMemAboutToChange(). If P5 is set, then all released registers have their type set to MEM_Undefined so that any subsequent attempt to read the released register (before it is reinitialized) will generate an assertion fault. P5 ought to be set on every call to this opcode. However, there are places in the code generator will release registers before their are used, under the (valid) assumption that the registers will not be reallocated for some other purpose before they are used and hence are safe to release. This opcode is only available in testing and debugging builds. It is not generated for release builds. The purpose of this opcode is to help validate the generated bytecode. This opcode does not actually contribute to computing an answer.\" },\n    OpCodeDescription { name: \"Remainder\", description: \"Compute the remainder after integer register P2 is divided by register P1 and store the result in register P3. If the value in register P1 is zero the result is NULL. If either operand is NULL, the result is NULL.\" },\n    OpCodeDescription { name: \"ReopenIdx\", description: \"The ReopenIdx opcode works like OpenRead except that it first checks to see if the cursor on P1 is already open on the same b-tree and if it is this opcode becomes a no-op. In other words, if the cursor is already open, do not reopen it. The ReopenIdx opcode may only be used with P5==0 or P5==OPFLAG_SEEKEQ and with P4 being a P4_KEYINFO object. Furthermore, the P3 value must be the same as every other ReopenIdx or OpenRead for the same cursor number. Allowed P5 bits:\n    0x02 OPFLAG_SEEKEQ: This cursor will only be used for equality lookups (implemented as a pair of opcodes SeekGE/IdxGT of SeekLE/IdxLT) See also: OpenRead, description:  OpenWrite\" },\n    OpCodeDescription { name: \"ResetCount\", description: \"The value of the change counter is copied to the database handle change counter (returned by subsequent calls to sqlite3_changes()). Then the VMs internal change counter resets to 0. This is used by trigger programs.\" },\n    OpCodeDescription { name: \"ResetSorter\", description: \"Delete all contents from the ephemeral table or sorter that is open on cursor P1. This opcode only works for cursors used for sorting and opened with OpenEphemeral or SorterOpen.\" },\n    OpCodeDescription { name: \"ResultRow\", description: \"The registers P1 through P1+P2-1 contain a single row of results. This opcode causes the sqlite3_step() call to terminate with an SQLITE_ROW return code and it sets up the sqlite3_stmt structure to provide access to the r(P1)..r(P1+P2-1) values as the result row.\" },\n    OpCodeDescription { name: \"Return\", description: \"Jump to the address stored in register P1. If P1 is a return address register, then this accomplishes a return from a subroutine. If P3 is 1, then the jump is only taken if register P1 holds an integer values, otherwise execution falls through to the next opcode, and the Return becomes a no-op. If P3 is 0, then register P1 must hold an integer or else an assert() is raised. P3 should be set to 1 when this opcode is used in combination with BeginSubrtn, and set to 0 otherwise. The value in register P1 is unchanged by this opcode. P2 is not used by the byte-code engine. However, if P2 is positive and also less than the current address, then the \\\"EXPLAIN\\\" output formatter in the CLI will indent all opcodes from the P2 opcode up to be not including the current Return. P2 should be the first opcode in the subroutine from which this opcode is returning. Thus the P2 value is a byte-code indentation hint. See tag-20220407a in wherecode.c and shell.c.\" },\n    OpCodeDescription { name: \"Rewind\", description: \"The next use of the Rowid or Column or Next instruction for P1 will refer to the first entry in the database table or index. If the table or index is empty, jump immediately to P2. If the table or index is not empty, fall through to the following instruction. If P2 is zero, that is an assertion that the P1 table is never empty and hence the jump will never be taken. This opcode leaves the cursor configured to move in forward order, from the beginning toward the end. In other words, the cursor is configured to use Next, not Prev.\" },\n    OpCodeDescription { name: \"RowCell\", description: \"P1 and P2 are both open cursors. Both must be opened on the same type of table - intkey or index. This opcode is used as part of copying the current row from P2 into P1. If the cursors are opened on intkey tables, register P3 contains the rowid to use with the new record in P1. If they are opened on index tables, P3 is not used. This opcode must be followed by either an Insert or InsertIdx opcode with the OPFLAG_PREFORMAT flag set to complete the insert operation.\" },\n    OpCodeDescription { name: \"RowData\", description: \"Write into register P2 the complete row content for the row at which cursor P1 is currently pointing. There is no interpretation of the data. It is just copied onto the P2 register exactly as it is found in the database file. If cursor P1 is an index, then the content is the key of the row. If cursor P2 is a table, then the content extracted is the data. If the P1 cursor must be pointing to a valid row (not a NULL row) of a real table, not a pseudo-table. If P3!=0 then this opcode is allowed to make an ephemeral pointer into the database page. That means that the content of the output register will be invalidated as soon as the cursor moves - including moves caused by other cursors that \\\"save\\\" the current cursors position in order that they can write to the same table. If P3==0 then a copy of the data is made into memory. P3!=0 is faster, but P3==0 is safer. If P3!=0 then the content of the P2 register is unsuitable for use in OP_Result and any OP_Result will invalidate the P2 register content. The P2 register content is invalidated by opcodes like Function or by any use of another cursor pointing to the same table.\" },\n    OpCodeDescription { name: \"Rowid\", description: \"Store in register P2 an integer which is the key of the table entry that P1 is currently point to. P1 can be either an ordinary table or a virtual table. There used to be a separate OP_VRowid opcode for use with virtual tables, but this one opcode now works for both table types.\" },\n    OpCodeDescription { name: \"RowSetAdd\", description: \"Insert the integer value held by register P2 into a RowSet object held in register P1. An assertion fails if P2 is not an integer.\" },\n    OpCodeDescription { name: \"RowSetRead\", description: \"Extract the smallest value from the RowSet object in P1 and put that value into register P3. Or, if RowSet object P1 is initially empty, leave P3 unchanged and jump to instruction P2.\" },\n    OpCodeDescription { name: \"RowSetTest\", description: \"Register P3 is assumed to hold a 64-bit integer value. If register P1 contains a RowSet object and that RowSet object contains the value held in P3, jump to register P2. Otherwise, insert the integer in P3 into the RowSet and continue on to the next opcode. The RowSet object is optimized for the case where sets of integers are inserted in distinct phases, which each set contains no duplicates. Each set is identified by a unique P4 value. The first set must have P4==0, the final set must have P4==-1, and for all other sets must have P4>0. This allows optimizations: (a) when P4==0 there is no need to test the RowSet object for P3, as it is guaranteed not to contain it, (b) when P4==-1 there is no need to insert the value, as it will never be tested for, and (c) when a value that is part of set X is inserted, there is no need to search to see if the same value was previously inserted as part of set X (only if it was previously inserted as part of some other set).\" },\n    OpCodeDescription { name: \"Savepoint\", description: \"Open, release or rollback the savepoint named by parameter P4, depending on the value of P1. To open a new savepoint set P1==0 (SAVEPOINT_BEGIN). To release (commit) an existing savepoint set P1==1 (SAVEPOINT_RELEASE). To rollback an existing savepoint set P1==2 (SAVEPOINT_ROLLBACK).\" },\n    OpCodeDescription { name: \"SCopy\", description: \"Make a shallow copy of register P1 into register P2. This instruction makes a shallow copy of the value. If the value is a string or blob, then the copy is only a pointer to the original and hence if the original changes so will the copy. Worse, if the original is deallocated, the copy becomes invalid. Thus the program must guarantee that the original will not change during the lifetime of the copy. Use Copy to make a complete copy.\" },\n    OpCodeDescription { name: \"SeekEnd\", description: \"Position cursor P1 at the end of the btree for the purpose of appending a new entry onto the btree. It is assumed that the cursor is used only for appending and so if the cursor is valid, then the cursor must already be pointing at the end of the btree and so no changes are made to the cursor.\" },\n    OpCodeDescription { name: \"SeekGE\", description: \"If cursor P1 refers to an SQL table (B-Tree that uses integer keys), use the value in register P3 as the key. If cursor P1 refers to an SQL index, then P3 is the first in an array of P4 registers that are used as an unpacked index key. Reposition cursor P1 so that it points to the smallest entry that is greater than or equal to the key value. If there are no records greater than or equal to the key and P2 is not zero, then jump to P2. If the cursor P1 was opened using the OPFLAG_SEEKEQ flag, then this opcode will either land on a record that exactly matches the key, or else it will cause a jump to P2. When the cursor is OPFLAG_SEEKEQ, this opcode must be followed by an IdxLE opcode with the same arguments. The IdxGT opcode will be skipped if this opcode succeeds, but the IdxGT opcode will be used on subsequent loop iterations. The OPFLAG_SEEKEQ flags is a hint to the btree layer to say that this is an equality search. This opcode leaves the cursor configured to move in forward order, from the beginning toward the end. In other words, the cursor is configured to use Next, not Prev. See also: Found, NotFound, SeekLt, SeekGt, SeekLe\" },\n    OpCodeDescription { name: \"SeekGT\", description: \"If cursor P1 refers to an SQL table (B-Tree that uses integer keys), use the value in register P3 as a key. If cursor P1 refers to an SQL index, then P3 is the first in an array of P4 registers that are used as an unpacked index key. Reposition cursor P1 so that it points to the smallest entry that is greater than the key value. If there are no records greater than the key and P2 is not zero, then jump to P2. This opcode leaves the cursor configured to move in forward order, from the beginning toward the end. In other words, the cursor is configured to use Next, not Prev. See also: Found, NotFound, SeekLt, SeekGe, SeekLe\" },\n    OpCodeDescription { name: \"SeekHit\", description: \"Increase or decrease the seekHit value for cursor P1, if necessary, so that it is no less than P2 and no greater than P3. The seekHit integer represents the maximum of terms in an index for which there is known to be at least one match. If the seekHit value is smaller than the total number of equality terms in an index lookup, then the IfNoHope opcode might run to see if the IN loop can be abandoned early, thus saving work. This is part of the IN-early-out optimization. P1 must be a valid b-tree cursor.\" },\n    OpCodeDescription { name: \"SeekLE\", description: \"If cursor P1 refers to an SQL table (B-Tree that uses integer keys), use the value in register P3 as a key. If cursor P1 refers to an SQL index, then P3 is the first in an array of P4 registers that are used as an unpacked index key. Reposition cursor P1 so that it points to the largest entry that is less than or equal to the key value. If there are no records less than or equal to the key and P2 is not zero, then jump to P2. This opcode leaves the cursor configured to move in reverse order, from the end toward the beginning. In other words, the cursor is configured to use Prev, not Next. If the cursor P1 was opened using the OPFLAG_SEEKEQ flag, then this opcode will either land on a record that exactly matches the key, or else it will cause a jump to P2. When the cursor is OPFLAG_SEEKEQ, this opcode must be followed by an IdxLE opcode with the same arguments. The IdxGE opcode will be skipped if this opcode succeeds, but the IdxGE opcode will be used on subsequent loop iterations. The OPFLAG_SEEKEQ flags is a hint to the btree layer to say that this is an equality search. See also: Found, NotFound, SeekGt, SeekGe, SeekLt\" },\n    OpCodeDescription { name: \"SeekLT\", description: \"If cursor P1 refers to an SQL table (B-Tree that uses integer keys), use the value in register P3 as a key. If cursor P1 refers to an SQL index, then P3 is the first in an array of P4 registers that are used as an unpacked index key. Reposition cursor P1 so that it points to the largest entry that is less than the key value. If there are no records less than the key and P2 is not zero, then jump to P2. This opcode leaves the cursor configured to move in reverse order, from the end toward the beginning. In other words, the cursor is configured to use Prev, not Next. See also: Found, NotFound, SeekGt, SeekGe, SeekLe\" },\n    OpCodeDescription { name: \"SeekRowid\", description: \"P1 is the index of a cursor open on an SQL table btree (with integer keys). If register P3 does not contain an integer or if P1 does not contain a record with rowid P3 then jump immediately to P2. Or, if P2 is 0, raise an SQLITE_CORRUPT error. If P1 does contain a record with rowid P3 then leave the cursor pointing at that record and fall through to the next instruction. The NotExists opcode performs the same operation, but with NotExists the P3 register must be guaranteed to contain an integer value. With this opcode, register P3 might not contain an integer. The NotFound opcode performs the same operation on index btrees (with arbitrary multi-value keys). This opcode leaves the cursor in a state where it cannot be advanced in either direction. In other words, the Next and Prev opcodes will not work following this opcode. See also: Found, NotFound, NoConflict, SeekRowid\" },\n    OpCodeDescription { name: \"SeekScan\", description: \"This opcode is a prefix opcode to SeekGE. In other words, this opcode must be immediately followed by SeekGE. This constraint is checked by assert() statements. This opcode uses the P1 through P4 operands of the subsequent SeekGE. In the text that follows, the operands of the subsequent SeekGE opcode are denoted as SeekOP.P1 through SeekOP.P4. Only the P1, P2 and P5 operands of this opcode are also used, and are called This.P1, This.P2 and This.P5. This opcode helps to optimize IN operators on a multi-column index where the IN operator is on the later terms of the index by avoiding unnecessary seeks on the btree, substituting steps to the next row of the b-tree instead. A correct answer is obtained if this opcode is omitted or is a no-op. The SeekGE.P3 and SeekGE.P4 operands identify an unpacked key which is the desired entry that we want the cursor SeekGE.P1 to be pointing to. Call this SeekGE.P3/P4 row the \\\"target\\\". If the SeekGE.P1 cursor is not currently pointing to a valid row, then this opcode is a no-op and control passes through into the SeekGE. If the SeekGE.P1 cursor is pointing to a valid row, then that row might be the target row, or it might be near and slightly before the target row, or it might be after the target row. If the cursor is currently before the target row, then this opcode attempts to position the cursor on or after the target row by invoking sqlite3BtreeStep() on the cursor between 1 and This.P1 times. The This.P5 parameter is a flag that indicates what to do if the cursor ends up pointing at a valid row that is past the target row. If This.P5 is false (0) then a jump is made to SeekGE.P2. If This.P5 is true (non-zero) then a jump is made to This.P2. The P5==0 case occurs when there are no inequality constraints to the right of the IN constraint. The jump to SeekGE.P2 ends the loop. The P5!=0 case occurs when there are inequality constraints to the right of the IN operator. In that case, the This.P2 will point either directly to or to setup code prior to the IdxGT or IdxGE opcode that checks for loop terminate. Possible outcomes from this opcode:\n    If the cursor is initially not pointed to any valid row, description:  then fall through into the subsequent SeekGE opcode.\n    If the cursor is left pointing to a row that is before the target row, description:  even after making as many as This.P1 calls to sqlite3BtreeNext(), then also fall through into SeekGE.\n    If the cursor is left pointing at the target row, description:  either because it was at the target row to begin with or because one or more sqlite3BtreeNext() calls moved the cursor to the target row, then jump to This.P2..,\n    If the cursor started out before the target row and a call to to sqlite3BtreeNext() moved the cursor off the end of the index (indicating that the target row definitely does not exist in the btree) then jump to SeekGE.P2, description:  ending the loop.\n    If the cursor ends up on a valid row that is past the target row (indicating that the target row does not exist in the btree) then jump to SeekOP.P2 if This.P5==0 or to This.P2 if This.P5>0.\"\n    },\n    OpCodeDescription { name: \"Sequence\", description: \"Find the next available sequence number for cursor P1. Write the sequence number into register P2. The sequence number on the cursor is incremented after this instruction.\" },\n    OpCodeDescription { name: \"SequenceTest\", description: \"P1 is a sorter cursor. If the sequence counter is currently zero, jump to P2. Regardless of whether or not the jump is taken, increment the the sequence value.\" },\n    OpCodeDescription { name: \"SetCookie\", description: \"Write the integer value P3 into cookie number P2 of database P1. P2==1 is the schema version. P2==2 is the database format. P2==3 is the recommended pager cache size, and so forth. P1==0 is the main database file and P1==1 is the database file used to store temporary tables. A transaction must be started before executing this opcode. If P2 is the SCHEMA_VERSION cookie (cookie number 1) then the internal schema version is set to P3-P5. The \\\"PRAGMA schema_version=N\\\" statement has P5 set to 1, so that the internal schema version will be different from the database schema version, resulting in a schema reset.\" },\n    OpCodeDescription { name: \"SetSubtype\", description: \"Set the subtype value of register P2 to the integer from register P1. If P1 is NULL, clear the subtype from p2.\" },\n    OpCodeDescription { name: \"ShiftLeft\", description: \"Shift the integer value in register P2 to the left by the number of bits specified by the integer in register P1. Store the result in register P3. If either input is NULL, the result is NULL.\" },\n    OpCodeDescription { name: \"ShiftRight\", description: \"Shift the integer value in register P2 to the right by the number of bits specified by the integer in register P1. Store the result in register P3. If either input is NULL, the result is NULL.\" },\n    OpCodeDescription { name: \"SoftNull\", description: \"Set register P1 to have the value NULL as seen by the MakeRecord instruction, but do not free any string or blob memory associated with the register, so that if the value was a string or blob that was previously copied using SCopy, the copies will continue to be valid.\" },\n    OpCodeDescription { name: \"Sort\", description: \"This opcode does exactly the same thing as Rewind except that it increments an undocumented global variable used for testing. Sorting is accomplished by writing records into a sorting index, then rewinding that index and playing it back from beginning to end. We use the Sort opcode instead of Rewind to do the rewinding so that the global variable will be incremented and regression tests can determine whether or not the optimizer is correctly optimizing out sorts.\" },\n    OpCodeDescription { name: \"SorterCompare\", description: \"P1 is a sorter cursor. This instruction compares a prefix of the record blob in register P3 against a prefix of the entry that the sorter cursor currently points to. Only the first P4 fields of r[P3] and the sorter record are compared. If either P3 or the sorter contains a NULL in one of their significant fields (not counting the P4 fields at the end which are ignored) then the comparison is assumed to be equal. Fall through to next instruction if the two records compare equal to each other. Jump to P2 if they are different.\" },\n    OpCodeDescription { name: \"SorterData\", description: \"Write into register P2 the current sorter data for sorter cursor P1. Then clear the column header cache on cursor P3. This opcode is normally used to move a record out of the sorter and into a register that is the source for a pseudo-table cursor created using OpenPseudo. That pseudo-table cursor is the one that is identified by parameter P3. Clearing the P3 column cache as part of this opcode saves us from having to issue a separate NullRow instruction to clear that cache.\" },\n    OpCodeDescription { name: \"SorterInsert\", description: \"Register P2 holds an SQL index key made using the MakeRecord instructions. This opcode writes that key into the sorter P1. Data for the entry is nil.\" },\n    OpCodeDescription { name: \"SorterNext\", description: \"This opcode works just like Next except that P1 must be a sorter object for which the SorterSort opcode has been invoked. This opcode advances the cursor to the next sorted record, or jumps to P2 if there are no more sorted records.\" },\n    OpCodeDescription { name: \"SorterOpen\", description: \"This opcode works like OpenEphemeral except that it opens a transient index that is specifically designed to sort large tables using an external merge-sort algorithm. If argument P3 is non-zero, then it indicates that the sorter may assume that a stable sort considering the first P3 fields of each key is sufficient to produce the required results.\" },\n    OpCodeDescription { name: \"SorterSort\", description: \"After all records have been inserted into the Sorter object identified by P1, invoke this opcode to actually do the sorting. Jump to P2 if there are no records to be sorted. This opcode is an alias for Sort and Rewind that is used for Sorter objects.\" },\n    OpCodeDescription { name: \"SqlExec\", description: \"Run the SQL statement or statements specified in the P4 string. The P1 parameter is a bitmask of options: 0x0001 Disable Auth and Trace callbacks while the statements in P4 are running. 0x0002 Set db->nAnalysisLimit to P2 while the statements in P4 are running.\" },\n    OpCodeDescription { name: \"String\", description: \"The string value P4 of length P1 (bytes) is stored in register P2. If P3 is not zero and the content of register P3 is equal to P5, then the datatype of the register P2 is converted to BLOB. The content is the same sequence of bytes, it is merely interpreted as a BLOB instead of a string, as if it had been CAST. In other words: if( P3!=0 and reg[P3]==P5 ) reg[P2] := CAST(reg[P2] as BLOB)\" },\n    OpCodeDescription { name: \"String8\", description: \"P4 points to a nul terminated UTF-8 string. This opcode is transformed into a String opcode before it is executed for the first time. During this transformation, the length of string P4 is computed and stored as the P1 parameter.\" },\n    OpCodeDescription { name: \"Subtract\", description: \"Subtract the value in register P1 from the value in register P2 and store the result in register P3. If either input is NULL, the result is NULL.\" },\n    OpCodeDescription { name: \"TableLock\", description: \"Obtain a lock on a particular table. This instruction is only used when the shared-cache feature is enabled. P1 is the index of the database in sqlite3.aDb[] of the database on which the lock is acquired. A readlock is obtained if P3==0 or a write lock if P3==1. P2 contains the root-page of the table to lock. P4 contains a pointer to the name of the table being locked. This is only used to generate an error message if the lock cannot be obtained.\" },\n    OpCodeDescription { name: \"Trace\", description: \"Write P4 on the statement trace output if statement tracing is enabled. Operand P1 must be 0x7fffffff and P2 must positive.\" },\n    OpCodeDescription { name: \"Transaction\", description: \"Begin a transaction on database P1 if a transaction is not already active. If P2 is non-zero, then a write-transaction is started, or if a read-transaction is already active, it is upgraded to a write-transaction. If P2 is zero, then a read-transaction is started. If P2 is 2 or more then an exclusive transaction is started. P1 is the index of the database file on which the transaction is started. Index 0 is the main database file and index 1 is the file used for temporary tables. Indices of 2 or more are used for attached databases. If a write-transaction is started and the Vdbe.usesStmtJournal flag is true (this flag is set if the Vdbe may modify more than one row and may throw an ABORT exception), a statement transaction may also be opened. More specifically, a statement transaction is opened iff the database connection is currently not in autocommit mode, or if there are other active statements. A statement transaction allows the changes made by this VDBE to be rolled back after an error without having to roll back the entire transaction. If no error is encountered, the statement transaction will automatically commit when the VDBE halts. If P5!=0 then this opcode also checks the schema cookie against P3 and the schema generation counter against P4. The cookie changes its value whenever the database schema changes. This operation is used to detect when that the cookie has changed and that the current process needs to reread the schema. If the schema cookie in P3 differs from the schema cookie in the database header or if the schema generation counter in P4 differs from the current generation counter, then an SQLITE_SCHEMA error is raised and execution halts. The sqlite3_step() wrapper function might then reprepare the statement and rerun it from the beginning.\" },\n    OpCodeDescription { name: \"TypeCheck\", description: \"Apply affinities to the range of P2 registers beginning with P1. Take the affinities from the Table object in P4. If any value cannot be coerced into the correct type, then raise an error. This opcode is similar to Affinity except that this opcode forces the register type to the Table column type. This is used to implement \\\"strict affinity\\\". GENERATED ALWAYS AS ... STATIC columns are only checked if P3 is zero. When P3 is non-zero, no type checking occurs for static generated columns. Virtual columns are computed at query time and so they are never checked. Preconditions:\n    P2 should be the number of non-virtual columns in the table of P4.\n    Table P4 should be a STRICT table. If any precondition is false, an assertion fault occurs.\"\n    },\n    OpCodeDescription { name: \"Vacuum\", description: \"Vacuum the entire database P1. P1 is 0 for \\\"main\\\", and 2 or more for an attached database. The \\\"temp\\\" database may not be vacuumed. If P2 is not zero, then it is a register holding a string which is the file into which the result of vacuum should be written. When P2 is zero, the vacuum overwrites the original database.\" },\n    OpCodeDescription { name: \"Variable\", description: \"Transfer the values of bound parameter P1 into register P2\" },\n    OpCodeDescription { name: \"VBegin\", description: \"P4 may be a pointer to an sqlite3_vtab structure. If so, call the xBegin method for that table. Also, whether or not P4 is set, check that this is not being called from within a callback to a virtual table xSync() method. If it is, the error code will be set to SQLITE_LOCKED.\" },\n    OpCodeDescription { name: \"VCheck\", description: \"P4 is a pointer to a Table object that is a virtual table in schema P1 that supports the xIntegrity() method. This opcode runs the xIntegrity() method for that virtual table, using P3 as the integer argument. If an error is reported back, the table name is prepended to the error message and that message is stored in P2. If no errors are seen, register P2 is set to NULL.\" },\n    OpCodeDescription { name: \"VColumn\", description: \"Store in register P3 the value of the P2-th column of the current row of the virtual-table of cursor P1. If the VColumn opcode is being used to fetch the value of an unchanging column during an UPDATE operation, then the P5 value is OPFLAG_NOCHNG. This will cause the sqlite3_vtab_nochange() function to return true inside the xColumn method of the virtual table implementation. The P5 column might also contain other bits (OPFLAG_LENGTHARG or OPFLAG_TYPEOFARG) but those bits are unused by VColumn.\" },\n    OpCodeDescription { name: \"VCreate\", description: \"P2 is a register that holds the name of a virtual table in database P1. Call the xCreate method for that table.\" },\n    OpCodeDescription { name: \"VDestroy\", description: \"P4 is the name of a virtual table in database P1. Call the xDestroy method of that table.\" },\n    OpCodeDescription { name: \"VFilter\", description: \"P1 is a cursor opened using VOpen. P2 is an address to jump to if the filtered result set is empty. P4 is either NULL or a string that was generated by the xBestIndex method of the module. The interpretation of the P4 string is left to the module implementation. This opcode invokes the xFilter method on the virtual table specified by P1. The integer query plan parameter to xFilter is stored in register P3. Register P3+1 stores the argc parameter to be passed to the xFilter method. Registers P3+2..P3+1+argc are the argc additional parameters which are passed to xFilter as argv. Register P3+2 becomes argv[0] when passed to xFilter. A jump is made to P2 if the result set after filtering would be empty.\" },\n    OpCodeDescription { name: \"VInitIn\", description: \"Set register P2 to be a pointer to a ValueList object for cursor P1 with cache register P3 and output register P3+1. This ValueList object can be used as the first argument to sqlite3_vtab_in_first() and sqlite3_vtab_in_next() to extract all of the values stored in the P1 cursor. Register P3 is used to hold the values returned by sqlite3_vtab_in_first() and sqlite3_vtab_in_next().\" },\n    OpCodeDescription { name: \"VNext\", description: \"Advance virtual table P1 to the next row in its result set and jump to instruction P2. Or, if the virtual table has reached the end of its result set, then fall through to the next instruction.\" },\n    OpCodeDescription { name: \"VOpen\", description: \"P4 is a pointer to a virtual table object, an sqlite3_vtab structure. P1 is a cursor number. This opcode opens a cursor to the virtual table and stores that cursor in P1.\" },\n    OpCodeDescription { name: \"VRename\", description: \"P4 is a pointer to a virtual table object, an sqlite3_vtab structure. This opcode invokes the corresponding xRename method. The value in register P1 is passed as the zName argument to the xRename method.\" },\n    OpCodeDescription { name: \"VUpdate\", description: \"P4 is a pointer to a virtual table object, an sqlite3_vtab structure. This opcode invokes the corresponding xUpdate method. P2 values are contiguous memory cells starting at P3 to pass to the xUpdate invocation. The value in register (P3+P2-1) corresponds to the p2th element of the argv array passed to xUpdate. The xUpdate method will do a DELETE or an INSERT or both. The argv[0] element (which corresponds to memory cell P3) is the rowid of a row to delete. If argv[0] is NULL then no deletion occurs. The argv[1] element is the rowid of the new row. This can be NULL to have the virtual table select the new rowid for itself. The subsequent elements in the array are the values of columns in the new row. If P2==1 then no insert is performed. argv[0] is the rowid of a row to delete. P1 is a boolean flag. If it is set to true and the xUpdate call is successful, then the value returned by sqlite3_last_insert_rowid() is set to the value of the rowid for the row just inserted. P5 is the error actions (OE_Replace, OE_Fail, OE_Ignore, etc) to apply in the case of a constraint failure on an insert or update.\" },\n    OpCodeDescription { name: \"Yield\", description: \"Swap the program counter with the value in register P1. This has the effect of yielding to a coroutine. If the coroutine that is launched by this instruction ends with Yield or Return then continue to the next instruction. But if the coroutine launched by this instruction ends with EndCoroutine, then jump to P2 rather than continuing with the next instruction. See also: InitCoroutine\" },\n    OpCodeDescription { name: \"ZeroOrNull\", description: \"If both registers P1 and P3 are NOT NULL, then store a zero in register P2. If either registers P1 or P3 are NULL then put a NULL in register P2.\" },\n];\n"
  },
  {
    "path": "cli/read_state_machine.rs",
    "content": "use std::ops::ControlFlow;\n\nuse itertools::Itertools;\n\n/// State machine for determining if a SQL statement is complete.\n/// Based on SQLite's `sqlite3_complete()` from src/complete.c\n///\n/// This handles the tricky case of triggers which contain semicolons\n/// in their body but should only be considered complete when the\n/// `;END;` pattern is seen.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]\npub enum ReadState {\n    /// No non-whitespace seen yet (initial state)\n    #[default]\n    Invalid,\n    /// A complete statement was just finished (terminal state)\n    Start,\n    /// In the middle of an ordinary statement\n    Normal,\n    /// Saw EXPLAIN at the start, watching for CREATE\n    Explain,\n    /// Saw CREATE (possibly after EXPLAIN), watching for TRIGGER\n    Create,\n    /// Inside a trigger definition, need ;END; to escape\n    Trigger,\n    /// Just saw a semicolon inside a trigger, looking for END\n    Semi,\n    /// Saw ;END in trigger, one more semicolon completes it\n    End,\n}\n\n/// Token types recognized by the state machine\n#[expect(clippy::enum_variant_names)]\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\nenum Token {\n    TkSemi,\n    TkWhitespace,\n    TkOther,\n    TkExplain,\n    TkCreate,\n    TkTemp,\n    TkTrigger,\n    TkEnd,\n}\n\nstruct Tokenizer<'a> {\n    chars: std::iter::Peekable<std::str::Chars<'a>>,\n}\n\nimpl<'a> Tokenizer<'a> {\n    fn new(chars: std::iter::Peekable<std::str::Chars<'a>>) -> Self {\n        Self { chars }\n    }\n\n    /// Read an identifier/keyword and classify it\n    fn read_keyword(&mut self, first: char) -> Token {\n        let word: String = std::iter::once(first)\n            .chain(\n                self.chars\n                    .peeking_take_while(|c| c.is_ascii_alphanumeric() || *c == '_'),\n            )\n            .collect();\n\n        match word.to_ascii_uppercase().as_str() {\n            \"EXPLAIN\" => Token::TkExplain,\n            \"CREATE\" => Token::TkCreate,\n            \"TEMP\" | \"TEMPORARY\" => Token::TkTemp,\n            \"TRIGGER\" => Token::TkTrigger,\n            \"END\" => Token::TkEnd,\n            _ => Token::TkOther,\n        }\n    }\n}\n\nimpl<'a> Iterator for Tokenizer<'a> {\n    type Item = Token;\n\n    fn next(&mut self) -> Option<Token> {\n        loop {\n            let c = self.chars.next()?;\n\n            let token = match c {\n                '\\'' | '\"' | '`' | '[' => {\n                    let end_char = if c == '[' { ']' } else { c };\n                    // Consumes all tokens between the delimeters\n                    self.chars\n                        .by_ref()\n                        .take_while_inclusive(|&ch| ch != end_char)\n                        .for_each(drop);\n                    continue;\n                }\n                // Handle Comments\n                '-' if self.chars.peek() == Some(&'-') => {\n                    self.chars.next(); // Consume second `-`\n                                       // Consume until you find a new line\n                    self.chars.by_ref().find(|&ch| ch == '\\n');\n                    continue;\n                }\n                '/' if self.chars.peek() == Some(&'*') => {\n                    // Consumes until you find a `*/`\n                    let _ = self.chars.by_ref().try_fold(false, |saw_star, c| {\n                        if saw_star && c == '/' {\n                            ControlFlow::Break(())\n                        } else {\n                            ControlFlow::Continue(c == '*')\n                        }\n                    });\n                    continue;\n                }\n                ';' => Token::TkSemi,\n                c if c.is_ascii_whitespace() => Token::TkWhitespace,\n                c if c.is_ascii_alphabetic() || c == '_' => self.read_keyword(c),\n                _ => Token::TkOther,\n            };\n\n            break Some(token);\n        }\n    }\n}\n\nimpl ReadState {\n    /// Returns true if the state machine is in a \"complete\" state,\n    /// meaning the accumulated SQL forms a complete statement.\n    pub fn is_complete(&self) -> bool {\n        matches!(self, ReadState::Start)\n    }\n\n    // Copied form SQLite\n    /// Process a single character and return the new state.\n    /// This should be called for each character in the input.\n    fn transition(&self, token: Token) -> ReadState {\n        use ReadState::*;\n        use Token::*;\n\n        match (self, token) {\n            // State 0: INVALID - nothing meaningful seen yet\n            (Invalid, TkSemi) => Start,\n            (Invalid, TkWhitespace) => Invalid,\n            (Invalid, TkOther) => Normal,\n            (Invalid, TkExplain) => Explain,\n            (Invalid, TkCreate) => Create,\n            (Invalid, TkTemp) => Normal,\n            (Invalid, TkTrigger) => Normal,\n            (Invalid, TkEnd) => Normal,\n\n            // State 1: START - complete statement, ready for new one\n            (Start, TkSemi) => Start,\n            (Start, TkWhitespace) => Start,\n            (Start, TkOther) => Normal,\n            (Start, TkExplain) => Explain,\n            (Start, TkCreate) => Create,\n            (Start, TkTemp) => Normal,\n            (Start, TkTrigger) => Normal,\n            (Start, TkEnd) => Normal,\n\n            // State 2: NORMAL - in middle of ordinary statement\n            (Normal, TkSemi) => Start,\n            (Normal, TkWhitespace) => Normal,\n            (Normal, _) => Normal,\n\n            // State 3: EXPLAIN - saw EXPLAIN, watching for CREATE\n            (Explain, TkSemi) => Start,\n            (Explain, TkWhitespace) => Explain,\n            (Explain, TkOther) => Explain,\n            (Explain, TkExplain) => Normal,\n            (Explain, TkCreate) => Create,\n            (Explain, TkTemp) => Normal,\n            (Explain, TkTrigger) => Normal,\n            (Explain, TkEnd) => Normal,\n\n            // State 4: CREATE - saw CREATE, watching for TRIGGER\n            (Create, TkSemi) => Start,\n            (Create, TkWhitespace) => Create,\n            (Create, TkOther) => Normal,\n            (Create, TkExplain) => Normal,\n            (Create, TkCreate) => Normal,\n            (Create, TkTemp) => Create,     // CREATE TEMP still watching\n            (Create, TkTrigger) => Trigger, // Enter trigger mode!\n            (Create, TkEnd) => Normal,\n\n            // State 5: TRIGGER - inside trigger body, need ;END; to escape\n            (Trigger, TkSemi) => Semi,\n            (Trigger, TkWhitespace) => Trigger,\n            (Trigger, _) => Trigger,\n\n            // State 6: SEMI - saw ; in trigger, looking for END\n            (Semi, TkSemi) => Semi,\n            (Semi, TkWhitespace) => Semi,\n            (Semi, TkEnd) => End,\n            (Semi, _) => Trigger, // false alarm, back to body\n\n            // State 7: END - saw ;END, one more ; completes\n            (End, TkSemi) => Start, // ;END; - COMPLETE!\n            (End, TkWhitespace) => End,\n            (End, _) => Trigger, // false alarm\n        }\n    }\n\n    /// Process a SQL string and update the state.\n    /// Returns the new state after processing all input.\n    pub fn process(&mut self, sql: &str) {\n        let chars = sql.chars().peekable();\n\n        *self = Tokenizer::new(chars).fold(*self, |state, token| state.transition(token));\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    fn is_complete(sql: &str) -> bool {\n        let mut state = ReadState::default();\n        state.process(sql);\n        state.is_complete()\n    }\n\n    #[test]\n    fn test_simple_statements() {\n        assert!(is_complete(\"SELECT 1;\"));\n        assert!(is_complete(\"SELECT * FROM foo;\"));\n        assert!(is_complete(\"INSERT INTO foo VALUES (1, 2, 3);\"));\n        assert!(!is_complete(\"SELECT 1\"));\n        assert!(!is_complete(\"SELECT * FROM\"));\n    }\n\n    #[test]\n    fn test_multiple_statements() {\n        assert!(is_complete(\"SELECT 1; SELECT 2;\"));\n        assert!(!is_complete(\"SELECT 1; SELECT 2\"));\n    }\n\n    #[test]\n    fn test_string_with_semicolon() {\n        assert!(!is_complete(\"SELECT ';'\"));\n        assert!(is_complete(\"SELECT ';';\"));\n        assert!(!is_complete(\"SELECT 'test;test'\"));\n        assert!(is_complete(\"SELECT 'test;test';\"));\n    }\n\n    #[test]\n    fn test_comments() {\n        assert!(is_complete(\"SELECT 1; -- comment\"));\n        assert!(!is_complete(\"SELECT 1 -- comment;\"));\n        assert!(is_complete(\"SELECT /* ; */ 1;\"));\n        assert!(!is_complete(\"SELECT 1 /* ; */\"));\n    }\n\n    #[test]\n    fn test_simple_trigger() {\n        let trigger = r#\"\n            CREATE TRIGGER log_insert AFTER INSERT ON users BEGIN\n                INSERT INTO log VALUES('inserted');\n            END;\n        \"#;\n        assert!(is_complete(trigger));\n    }\n\n    #[test]\n    fn test_trigger_incomplete() {\n        let trigger = r#\"\n            CREATE TRIGGER log_insert AFTER INSERT ON users BEGIN\n                INSERT INTO log VALUES('inserted');\n        \"#;\n        assert!(!is_complete(trigger));\n    }\n\n    #[test]\n    fn test_trigger_multiple_statements() {\n        let trigger = r#\"\n            CREATE TRIGGER log_insert AFTER INSERT ON users BEGIN\n                INSERT INTO log VALUES('inserted');\n                UPDATE stats SET count = count + 1;\n            END;\n        \"#;\n        assert!(is_complete(trigger));\n    }\n\n    #[test]\n    fn test_create_temp_trigger() {\n        let trigger = r#\"\n            CREATE TEMP TRIGGER log_insert AFTER INSERT ON users BEGIN\n                INSERT INTO log VALUES('inserted');\n            END;\n        \"#;\n        assert!(is_complete(trigger));\n    }\n\n    #[test]\n    fn test_create_temporary_trigger() {\n        let trigger = r#\"\n            CREATE TEMPORARY TRIGGER log_insert AFTER INSERT ON users BEGIN\n                INSERT INTO log VALUES('inserted');\n            END;\n        \"#;\n        assert!(is_complete(trigger));\n    }\n\n    #[test]\n    fn test_explain_create_trigger() {\n        let trigger = r#\"\n            EXPLAIN CREATE TRIGGER log_insert AFTER INSERT ON users BEGIN\n                INSERT INTO log VALUES('inserted');\n            END;\n        \"#;\n        assert!(is_complete(trigger));\n    }\n\n    #[test]\n    fn test_end_in_string_inside_trigger() {\n        // END inside a string shouldn't end the trigger\n        let trigger = r#\"\n            CREATE TRIGGER log_insert AFTER INSERT ON users BEGIN\n                INSERT INTO log VALUES('END');\n            END;\n        \"#;\n        assert!(is_complete(trigger));\n    }\n\n    #[test]\n    fn test_create_table_not_trigger() {\n        assert!(is_complete(\"CREATE TABLE foo (id INT);\"));\n        assert!(!is_complete(\"CREATE TABLE foo (id INT)\"));\n    }\n\n    #[test]\n    fn test_empty_and_whitespace() {\n        assert!(!is_complete(\"\"));\n        assert!(!is_complete(\"   \"));\n        assert!(!is_complete(\"\\n\\t\\n\"));\n        assert!(is_complete(\";\"));\n        assert!(is_complete(\"  ;  \"));\n    }\n\n    #[test]\n    fn test_quoted_identifiers() {\n        assert!(is_complete(r#\"SELECT \"column;name\" FROM foo;\"#));\n        assert!(is_complete(\"SELECT `column;name` FROM foo;\"));\n        assert!(is_complete(\"SELECT [column;name] FROM foo;\"));\n    }\n\n    #[test]\n    fn test_escaped_quotes() {\n        assert!(is_complete(\"SELECT 'it''s';\"));\n        assert!(is_complete(r#\"SELECT \"col\"\"name\";\"#));\n    }\n\n    #[test]\n    fn test_non_terminated_literal() {\n        assert!(!is_complete(\n            \"create virtual table t1 using csv(data=\\\"12');\"\n        ));\n    }\n}\n\n// create virtual table t1 using csv(data=\\\"12');\n"
  },
  {
    "path": "cli/sync_server.mdx",
    "content": "---\nname: local-sync-server\n---\n\n<Code model=\"anthropic/claude-opus-4-5\" language=\"rust\" output=\"sync_server.rs\" version=\"0.0.1\" id=\"code\">\n    <Output ref=\"prompt\" />\n</Code>\n\n<Doc model=\"anthropic/sonnet-4.5\" output=\"../../turso-docs/sync/spec.mdx\" id=\"doc\">\n    Generate MDX documentation file in Mintlify format for the sync engine open protocol\n\n    You will be provided with a prompt which is used to generate simple implementation of the server.\n    Your task is to extract protocol implementation from this prompt and compose a documentation with PROTOCOL description:\n    - Document endpoint which MUST be implemented to implement sync engine protocol\n    - Document sql over http protocol which is used for `/v2/pipeline` endpoint\n    - Document `/pull-updates` endpoint\n    - The documentation MUST ONLY mention contracts, logic and flow. No code should be mentioned\n\n    Structure documentation well and Use Mintlify rich formatting tools in order to make documentation easy to read and navigatable:\n    * Give high level overview of sync protocol and its main components in the beginning\n        * This is protocol to support bidirectional of sqlite-compatible database between server and client\n    * Describe /v2/pipeline endpoint\n        * List contract spec\n    * Describe /pull-updates endpoint\n    * Use mintlify components if appropriate\n        * ```....``` code blocks to emit contracts like protobuf schemas\n        * Callouts (`<Info>`, `<Warning>`, etc) to extract portion of information into separate block visible to the reader\n    * Use markdown markup features if appropriate\n        * Headers and subheaders for structuring the docs and automatically build table of content by Mintlify\n        * Text emphasis (bold, italic) to highlight important words\n    <Output ref=\"prompt\" />\n</Doc>\n\n<Text id=\"prompt\">\n\n# Goal\n\nGenerate simple implementation of sync server using tursodatabase - rewrite of the SQLite.\nThe implementation must maintain turso database file locally at given path and disable checkpoint for it.\n\n```rs\nuse anyhow::Result;\n// ... more imports here ...\n\npub struct TursoSyncServer {\n    // listen address (e.g. 0.0.0.0:8080)\n    address: String,\n    conn: Arc<Mutex<Arc<Connection>>>,\n    // stop server if interrupt_count > 0 (do this check in the main server event loop)\n    interrupt_count: Arc<AtomicUsize>,\n}\n\nimpl TursoSyncServer {\n    pub fn new(address: String, conn: Arc<Connection>, interrupt_count: Arc<AtomicUsize>) -> Self {\n        Self {\n            address,\n            conn: Arc::new(Mutex::new(conn)),\n            interrupt_count,\n        }\n    }\n\n    pub fn run(&self) -> Result<()> {\n        // implement logic here\n    }\n\n    fn sql_over_http(&self, query: ...) -> Result<()> { ... }\n    fn pull_updates(&self, query: ...) -> Result<()> { ... }\n}\n```\n\n- Use `<T as prost::Message>::decode(...)` to access methods associated with the trait `prost::Message`\n- `from_i32` method in `prost` is deprecated\n    * `use of deprecated associated function `turso_sync_engine::server_proto::PageUpdatesEncodingReq::from_i32`: Use the TryFrom<i32> implementation instead`\n- Start separate thread to monitor `interrupt_count` because otherwise server will be blocked at syscall and will be unable to shutdown\n- Add logging\n    * Trace all request information with tracing::info!(...)\n    * Add debug logs for more low level info about sync server internal logic\n    * Add error logs if SQL over http execution statement failed or something bad happened\n    * Add debug logs with executed SQL statements to simplify tracing execution\n- Use `run_collect_rows`/`run_ignore_rows` helpers to execute `Statement`\n- Implement simple non-chunked http/1.1 server - always respond with full Content-Length \n- Use `application/protobuf` as content type for protobuf payloads\n- Format HTTP response only in one place - all internal functions must return either raw body (without headers and http preamble) or some Rust structs\n- Server must process one request at the time (`/v2/pipeline` or `/pull-updates`) in order to provide simple and safe concurrency guarantees\n    * Note, that you MUST hold the lock for the whole duration of request execution\n    * If you will drop lock guard soon - you will allow multiple connections to be processed at the same time\n    ```rs\n    let conn = { self.conn.lock().unwrap().clone() }; // this is wrong!\n    ```\n- USE rocket library for http server\n- DO NOT use tokio - use simple threads instead\n\n# Endpoints\n\nSync server must support 2 endpoints:\n1. POST /v2/pipeline\n    - Method executes hrana (SQL-over-HTTP) commands encoded with JSON\n2. POST /pull-updates endpoint which fetch page updates since revision provided by the client\n    - Method expects `PullUpdatesRequest` protobuf message and respond with sequence of length-delimited messages\n    - First it respond with `PullUpdatesResponseHeader`\n    - After, it sends multiple `PullUpdatesPageData`\n    - The method recognizes protobuf contracts sent by HTTP/1.1\n    - Implement simple http/1.1 server but use binary protobuf as payloads:\n    - You MUST return an error if zstd encoding is used\n    - You MUST ignore `server_query_selector` field\n    - You MUST ignore client_pages field\n    - You MUST decode `server_pages_selector` and send only pages from the selector if it is set\n    - BE CAREFUL: sync protocol use zero-based page identifers while core API sometimes uses 1-based indexing\n\nThe contracts which client uses to interact with server (protobufs and JSONs) are listed here:\n<File path=\"../sync/engine/src/server_proto.rs\" />\n\n# Core API\n\nThe main database API is in the lib.rs:\n<Outline model=\"openai/gpt-4.1\" id=\"api\">\n\n- Generate outline of the turso core API which later can be used to write code easily without errors\n- Include information about main Database/Connection/Statement methods\n- Includ Rust signatures in order to use the outline for code generation\n- Document extra WAL API as it will be important later\n\n<File path=\"../core/lib.rs\" />\n<File path=\"../core/types.rs\" selector=\"WalState|WalFrameInfo|Value|Text\" />\n<File path=\"../core/storage/sqlite3_ondisk.rs\" selector=\"PageSize\" />\n\n</Outline>\n\n# pull updates\n\n- In order to implement pull-updates endpoint use extra WAL API exposed by the core database api: `wal_state` and `wal_get_frame`\n- Use offset in WAL files as a simplest revision string\n- `wal_get_frame` reads the frame from the WAL which has additional 24 bytes header with extra meta\n    * pub const WAL_FRAME_HEADER_SIZE: usize = 24;\n    * Do not forget to cut this meta to get page content\n- sync server must work only with page_size = 4096 (4kb)\n- If server_revision is not set - take latest commited frame offset\n- If client_revision is not set - use zero for this\n- Pull updates logic should take all frames between [client_revision..server_revision] and send latest versions of unique pages changed in this range\n    * Iterates backward and maintain HashSet of changes pages in memory during request execution\n    * Filter out pages which are not included in the server_pages_selector if it was set\n\n# sql over http\n\n- Implement batch conditions - they are important especiall auto-commit handling\n- Implement only positional arguments - named parameters are not used in the sync protocol at client side\n\n# dependencies\n\nCurrently available dependencies are:\n<File path=\"Cargo.toml\" />\n\n</Text>"
  },
  {
    "path": "cli/sync_server.rs",
    "content": "use std::collections::HashSet;\nuse std::io::{Read, Write};\nuse std::net::{TcpListener, TcpStream};\nuse std::sync::atomic::{AtomicUsize, Ordering};\nuse std::sync::{Arc, Mutex};\nuse std::thread;\n\nuse anyhow::{anyhow, Result};\nuse bytes::Bytes;\nuse prost::Message;\nuse roaring::RoaringBitmap;\nuse tracing::{debug, error, info};\n\nuse turso_core::{Connection, Value as CoreValue};\nuse turso_sync_engine::server_proto::{\n    BatchCond, BatchResult, BatchStep, BatchStreamReq, BatchStreamResp, Col, Error,\n    ExecuteStreamReq, ExecuteStreamResp, PageData, PageSetRawEncodingProto, PageUpdatesEncodingReq,\n    PipelineReqBody, PipelineRespBody, PullUpdatesReqProtoBody, PullUpdatesRespProtoBody, Row,\n    StmtResult, StreamRequest, StreamResponse, StreamResult, Value,\n};\n\nconst WAL_FRAME_HEADER_SIZE: usize = 24;\nconst PAGE_SIZE: usize = 4096;\n\npub struct TursoSyncServer {\n    address: String,\n    conn: Arc<Mutex<Arc<Connection>>>,\n    interrupt_count: Arc<AtomicUsize>,\n}\n\nimpl TursoSyncServer {\n    pub fn new(address: String, conn: Arc<Connection>, interrupt_count: Arc<AtomicUsize>) -> Self {\n        conn.wal_auto_checkpoint_disable();\n        Self {\n            address,\n            conn: Arc::new(Mutex::new(conn)),\n            interrupt_count,\n        }\n    }\n\n    pub fn run(&self) -> Result<()> {\n        info!(\"Starting TursoSyncServer on {}\", self.address);\n\n        let listener = TcpListener::bind(&self.address)?;\n        listener.set_nonblocking(true)?;\n\n        let interrupt_count = self.interrupt_count.clone();\n        let shutdown_flag = Arc::new(std::sync::atomic::AtomicBool::new(false));\n        let shutdown_flag_clone = shutdown_flag.clone();\n\n        let monitor_handle = thread::spawn(move || loop {\n            if interrupt_count.load(Ordering::SeqCst) > 0 {\n                debug!(\"Interrupt detected, signaling shutdown\");\n                shutdown_flag_clone.store(true, Ordering::SeqCst);\n                break;\n            }\n            thread::sleep(std::time::Duration::from_millis(100));\n        });\n\n        loop {\n            if shutdown_flag.load(Ordering::SeqCst) {\n                info!(\"Shutdown signal received, stopping server\");\n                break;\n            }\n\n            match listener.accept() {\n                Ok((stream, addr)) => {\n                    info!(\"Accepted connection from {}\", addr);\n                    if let Err(e) = self.handle_connection(stream) {\n                        error!(\"Error handling connection: {}\", e);\n                    }\n                }\n                Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {\n                    thread::sleep(std::time::Duration::from_millis(10));\n                    continue;\n                }\n                Err(e) => {\n                    error!(\"Error accepting connection: {}\", e);\n                }\n            }\n        }\n\n        let _ = monitor_handle.join();\n        info!(\"TursoSyncServer stopped\");\n        Ok(())\n    }\n\n    fn handle_connection(&self, mut stream: TcpStream) -> Result<()> {\n        stream.set_nonblocking(false)?;\n        stream.set_read_timeout(Some(std::time::Duration::from_secs(30)))?;\n\n        let mut buffer = [0u8; 8192];\n        let mut request_data = Vec::new();\n\n        loop {\n            let n = stream.read(&mut buffer)?;\n            if n == 0 {\n                break;\n            }\n            request_data.extend_from_slice(&buffer[..n]);\n\n            if let Some(header_end) = find_header_end(&request_data) {\n                let headers = String::from_utf8_lossy(&request_data[..header_end]);\n                if let Some(content_length) = parse_content_length(&headers) {\n                    let body_start = header_end + 4;\n                    let total_expected = body_start + content_length;\n                    while request_data.len() < total_expected {\n                        let n = stream.read(&mut buffer)?;\n                        if n == 0 {\n                            break;\n                        }\n                        request_data.extend_from_slice(&buffer[..n]);\n                    }\n                }\n                break;\n            }\n        }\n\n        let (method, path, body) = parse_http_request(&request_data)?;\n        info!(\"Request: {} {}\", method, path);\n\n        let response = match (method.as_str(), path.as_str()) {\n            (\"POST\", \"/v2/pipeline\") => {\n                debug!(\"Handling /v2/pipeline request\");\n                self.handle_pipeline(&body)\n            }\n            (\"POST\", \"/pull-updates\") => {\n                debug!(\"Handling /pull-updates request\");\n                self.handle_pull_updates(&body)\n            }\n            _ => {\n                info!(\"Unknown endpoint: {} {}\", method, path);\n                Ok(HttpResponse {\n                    status: 404,\n                    content_type: \"text/plain\".to_string(),\n                    body: b\"Not Found\".to_vec(),\n                })\n            }\n        };\n\n        let http_response = match response {\n            Ok(resp) => resp,\n            Err(e) => {\n                error!(\"Request error: {}\", e);\n                HttpResponse {\n                    status: 500,\n                    content_type: \"text/plain\".to_string(),\n                    body: format!(\"Internal Server Error: {e}\").into_bytes(),\n                }\n            }\n        };\n\n        let response_bytes = format_http_response(&http_response);\n        stream.write_all(&response_bytes)?;\n        stream.flush()?;\n\n        Ok(())\n    }\n\n    fn handle_pipeline(&self, body: &[u8]) -> Result<HttpResponse> {\n        let req: PipelineReqBody = serde_json::from_slice(body)\n            .map_err(|e| anyhow!(\"Failed to parse pipeline request: {}\", e))?;\n\n        debug!(\"Pipeline request: {:?}\", req);\n\n        let conn = self.conn.lock().unwrap();\n\n        let mut results = Vec::new();\n\n        for request in req.requests {\n            let result = match request {\n                StreamRequest::Execute(exec_req) => self.execute_statement(&conn, &exec_req),\n                StreamRequest::Batch(batch_req) => self.execute_batch(&conn, &batch_req),\n                StreamRequest::None => StreamResult::Error {\n                    error: Error {\n                        message: \"Unknown request type\".to_string(),\n                        code: \"UNKNOWN\".to_string(),\n                    },\n                },\n            };\n            results.push(result);\n        }\n\n        let resp = PipelineRespBody {\n            baton: req.baton,\n            base_url: None,\n            results,\n        };\n\n        let body = serde_json::to_vec(&resp)?;\n\n        Ok(HttpResponse {\n            status: 200,\n            content_type: \"application/json\".to_string(),\n            body,\n        })\n    }\n\n    fn execute_statement(&self, conn: &Arc<Connection>, req: &ExecuteStreamReq) -> StreamResult {\n        let sql = match &req.stmt.sql {\n            Some(s) => s.clone(),\n            None => {\n                return StreamResult::Error {\n                    error: Error {\n                        message: \"No SQL provided\".to_string(),\n                        code: \"NO_SQL\".to_string(),\n                    },\n                }\n            }\n        };\n\n        debug!(\"Executing SQL: {}\", sql);\n\n        let mut stmt = match conn.prepare(&sql) {\n            Ok(s) => s,\n            Err(e) => {\n                error!(\"Failed to prepare statement: {}\", e);\n                return StreamResult::Error {\n                    error: Error {\n                        message: e.to_string(),\n                        code: \"PREPARE_ERROR\".to_string(),\n                    },\n                };\n            }\n        };\n\n        for (i, arg) in req.stmt.args.iter().enumerate() {\n            let core_value = convert_value_to_core(arg);\n            stmt.bind_at(std::num::NonZero::new(i + 1).unwrap(), core_value);\n        }\n\n        let want_rows = req.stmt.want_rows.unwrap_or(true);\n\n        if want_rows {\n            match stmt.run_collect_rows() {\n                Ok(rows) => {\n                    let cols: Vec<Col> = (0..stmt.num_columns())\n                        .map(|i| Col {\n                            name: Some(stmt.get_column_name(i).to_string()),\n                            decltype: stmt.get_column_decltype(i),\n                        })\n                        .collect();\n\n                    let result_rows: Vec<Row> = rows\n                        .into_iter()\n                        .map(|row| Row {\n                            values: row.into_iter().map(convert_core_to_value).collect(),\n                        })\n                        .collect();\n\n                    StreamResult::Ok {\n                        response: StreamResponse::Execute(ExecuteStreamResp {\n                            result: StmtResult {\n                                cols,\n                                rows: result_rows,\n                                affected_row_count: 0,\n                                last_insert_rowid: None,\n                                replication_index: None,\n                                rows_read: 0,\n                                rows_written: 0,\n                                query_duration_ms: 0.0,\n                            },\n                        }),\n                    }\n                }\n                Err(e) => {\n                    error!(\"Failed to execute statement: {}\", e);\n                    StreamResult::Error {\n                        error: Error {\n                            message: e.to_string(),\n                            code: \"EXECUTE_ERROR\".to_string(),\n                        },\n                    }\n                }\n            }\n        } else {\n            match stmt.run_ignore_rows() {\n                Ok(()) => StreamResult::Ok {\n                    response: StreamResponse::Execute(ExecuteStreamResp {\n                        result: StmtResult {\n                            cols: vec![],\n                            rows: vec![],\n                            affected_row_count: 0,\n                            last_insert_rowid: None,\n                            replication_index: None,\n                            rows_read: 0,\n                            rows_written: 0,\n                            query_duration_ms: 0.0,\n                        },\n                    }),\n                },\n                Err(e) => {\n                    error!(\"Failed to execute statement: {}\", e);\n                    StreamResult::Error {\n                        error: Error {\n                            message: e.to_string(),\n                            code: \"EXECUTE_ERROR\".to_string(),\n                        },\n                    }\n                }\n            }\n        }\n    }\n\n    fn execute_batch(&self, conn: &Arc<Connection>, req: &BatchStreamReq) -> StreamResult {\n        let batch = &req.batch;\n        let mut step_results: Vec<Option<StmtResult>> = Vec::with_capacity(batch.steps.len());\n        let mut step_errors: Vec<Option<Error>> = Vec::with_capacity(batch.steps.len());\n\n        for (step_idx, step) in batch.steps.iter().enumerate() {\n            let should_execute = match &step.condition {\n                None => true,\n                Some(cond) => Self::evaluate_condition(cond, &step_results, &step_errors, conn),\n            };\n\n            if should_execute {\n                let result = self.execute_batch_step(conn, step);\n                match result {\n                    Ok(stmt_result) => {\n                        step_results.push(Some(stmt_result));\n                        step_errors.push(None);\n                    }\n                    Err(e) => {\n                        error!(\"Batch step {} failed: {}\", step_idx, e);\n                        step_results.push(None);\n                        step_errors.push(Some(Error {\n                            message: e.to_string(),\n                            code: \"BATCH_STEP_ERROR\".to_string(),\n                        }));\n                    }\n                }\n            } else {\n                step_results.push(None);\n                step_errors.push(None);\n            }\n        }\n\n        StreamResult::Ok {\n            response: StreamResponse::Batch(BatchStreamResp {\n                result: BatchResult {\n                    step_results,\n                    step_errors,\n                    replication_index: None,\n                },\n            }),\n        }\n    }\n\n    fn evaluate_condition(\n        cond: &BatchCond,\n        step_results: &[Option<StmtResult>],\n        step_errors: &[Option<Error>],\n        conn: &Arc<Connection>,\n    ) -> bool {\n        match cond {\n            BatchCond::None => true,\n            BatchCond::Ok { step } => {\n                let idx = *step as usize;\n                idx < step_results.len() && step_results[idx].is_some()\n            }\n            BatchCond::Error { step } => {\n                let idx = *step as usize;\n                idx < step_errors.len() && step_errors[idx].is_some()\n            }\n            BatchCond::Not { cond } => {\n                !Self::evaluate_condition(cond, step_results, step_errors, conn)\n            }\n            BatchCond::And(list) => list\n                .conds\n                .iter()\n                .all(|c| Self::evaluate_condition(c, step_results, step_errors, conn)),\n            BatchCond::Or(list) => list\n                .conds\n                .iter()\n                .any(|c| Self::evaluate_condition(c, step_results, step_errors, conn)),\n            BatchCond::IsAutocommit {} => conn.get_auto_commit(),\n        }\n    }\n\n    fn execute_batch_step(&self, conn: &Arc<Connection>, step: &BatchStep) -> Result<StmtResult> {\n        let sql = step\n            .stmt\n            .sql\n            .as_ref()\n            .ok_or_else(|| anyhow!(\"No SQL in batch step\"))?;\n\n        debug!(\"Executing batch step SQL: {}\", sql);\n\n        let mut stmt = conn.prepare(sql)?;\n\n        for (i, arg) in step.stmt.args.iter().enumerate() {\n            let core_value = convert_value_to_core(arg);\n            stmt.bind_at(std::num::NonZero::new(i + 1).unwrap(), core_value);\n        }\n\n        let want_rows = step.stmt.want_rows.unwrap_or(true);\n\n        if want_rows {\n            let rows = stmt.run_collect_rows()?;\n\n            let cols: Vec<Col> = (0..stmt.num_columns())\n                .map(|i| Col {\n                    name: Some(stmt.get_column_name(i).to_string()),\n                    decltype: stmt.get_column_decltype(i),\n                })\n                .collect();\n\n            let result_rows: Vec<Row> = rows\n                .into_iter()\n                .map(|row| Row {\n                    values: row.into_iter().map(convert_core_to_value).collect(),\n                })\n                .collect();\n\n            Ok(StmtResult {\n                cols,\n                rows: result_rows,\n                affected_row_count: 0,\n                last_insert_rowid: None,\n                replication_index: None,\n                rows_read: 0,\n                rows_written: 0,\n                query_duration_ms: 0.0,\n            })\n        } else {\n            stmt.run_ignore_rows()?;\n            Ok(StmtResult {\n                cols: vec![],\n                rows: vec![],\n                affected_row_count: 0,\n                last_insert_rowid: None,\n                replication_index: None,\n                rows_read: 0,\n                rows_written: 0,\n                query_duration_ms: 0.0,\n            })\n        }\n    }\n\n    fn handle_pull_updates(&self, body: &[u8]) -> Result<HttpResponse> {\n        let req = <PullUpdatesReqProtoBody as Message>::decode(body)\n            .map_err(|e| anyhow!(\"Failed to decode PullUpdatesRequest: {}\", e))?;\n\n        debug!(\n            \"Pull updates request: server_revision={}, client_revision={}\",\n            req.server_revision, req.client_revision\n        );\n\n        let encoding =\n            PageUpdatesEncodingReq::try_from(req.encoding).unwrap_or(PageUpdatesEncodingReq::Raw);\n\n        if encoding == PageUpdatesEncodingReq::Zstd {\n            return Err(anyhow!(\"Zstd encoding is not supported\"));\n        }\n\n        let conn = self.conn.lock().unwrap();\n\n        let wal_state = conn.wal_state()?;\n        debug!(\"WAL state: max_frame={}\", wal_state.max_frame);\n\n        let server_revision: u64 = if req.server_revision.is_empty() {\n            wal_state.max_frame\n        } else {\n            req.server_revision.parse().unwrap_or(wal_state.max_frame)\n        };\n\n        let client_revision: u64 = if req.client_revision.is_empty() {\n            0\n        } else {\n            req.client_revision.parse().unwrap_or(0)\n        };\n\n        debug!(\n            \"Using server_revision={}, client_revision={}\",\n            server_revision, client_revision\n        );\n\n        let pages_selector: Option<RoaringBitmap> = if !req.server_pages_selector.is_empty() {\n            Some(\n                RoaringBitmap::deserialize_from(&req.server_pages_selector[..])\n                    .map_err(|e| anyhow!(\"Failed to parse server_pages_selector: {}\", e))?,\n            )\n        } else {\n            None\n        };\n\n        let mut seen_pages: HashSet<u32> = HashSet::new();\n        let mut pages_to_send: Vec<(u32, Vec<u8>)> = Vec::new();\n\n        let frame_size = WAL_FRAME_HEADER_SIZE + PAGE_SIZE;\n        let mut frame_buffer = vec![0u8; frame_size];\n\n        debug!(\n            \"pull-updates: scanning WAL frames {}..={} (client_revision={}, server_revision={})\",\n            client_revision + 1,\n            server_revision,\n            client_revision,\n            server_revision\n        );\n\n        if server_revision > client_revision {\n            for frame_no in (client_revision + 1..=server_revision).rev() {\n                let frame_info = conn.wal_get_frame(frame_no, &mut frame_buffer)?;\n\n                let page_no = frame_info.page_no;\n                // WAL uses 1-based page numbers, sync protocol uses 0-based\n                let page_id = page_no - 1;\n\n                if seen_pages.contains(&page_no) {\n                    continue;\n                }\n\n                if let Some(ref selector) = pages_selector {\n                    if !selector.contains(page_id) {\n                        continue;\n                    }\n                }\n\n                seen_pages.insert(page_no);\n\n                let type_byte = frame_buffer[WAL_FRAME_HEADER_SIZE];\n                debug!(\n                    \"pull-updates: including page_no={}, frame_no={}, type_byte={}, db_size={}\",\n                    page_no, frame_no, type_byte, frame_info.db_size\n                );\n\n                let page_data = frame_buffer[WAL_FRAME_HEADER_SIZE..].to_vec();\n                pages_to_send.push((page_id, page_data));\n            }\n        }\n\n        debug!(\n            \"pull-updates: sending {} pages, seen_pages={:?}\",\n            pages_to_send.len(),\n            seen_pages\n        );\n        pages_to_send.reverse();\n\n        let db_size = if wal_state.max_frame > 0 {\n            let mut last_frame = vec![0u8; frame_size];\n            let last_info = conn.wal_get_frame(wal_state.max_frame, &mut last_frame)?;\n            last_info.db_size as u64\n        } else {\n            0\n        };\n\n        let header = PullUpdatesRespProtoBody {\n            server_revision: server_revision.to_string(),\n            db_size,\n            raw_encoding: Some(PageSetRawEncodingProto {}),\n            zstd_encoding: None,\n        };\n\n        let mut response_body = Vec::new();\n\n        let header_bytes = header.encode_to_vec();\n        encode_length_delimited(&mut response_body, &header_bytes);\n\n        for (page_id, page_data) in pages_to_send {\n            let page_msg = PageData {\n                page_id: page_id as u64,\n                encoded_page: Bytes::from(page_data),\n            };\n            let page_bytes = page_msg.encode_to_vec();\n            encode_length_delimited(&mut response_body, &page_bytes);\n        }\n\n        debug!(\n            \"Sending {} bytes in pull-updates response\",\n            response_body.len()\n        );\n\n        Ok(HttpResponse {\n            status: 200,\n            content_type: \"application/protobuf\".to_string(),\n            body: response_body,\n        })\n    }\n}\n\nstruct HttpResponse {\n    status: u16,\n    content_type: String,\n    body: Vec<u8>,\n}\n\nfn find_header_end(data: &[u8]) -> Option<usize> {\n    (0..data.len().saturating_sub(3)).find(|&i| &data[i..i + 4] == b\"\\r\\n\\r\\n\")\n}\n\nfn parse_content_length(headers: &str) -> Option<usize> {\n    for line in headers.lines() {\n        let lower = line.to_lowercase();\n        if lower.starts_with(\"content-length:\") {\n            let value = line.split(':').nth(1)?.trim();\n            return value.parse().ok();\n        }\n    }\n    None\n}\n\nfn parse_http_request(data: &[u8]) -> Result<(String, String, Vec<u8>)> {\n    let header_end = find_header_end(data).ok_or_else(|| anyhow!(\"Invalid HTTP request\"))?;\n    let headers = String::from_utf8_lossy(&data[..header_end]);\n\n    let first_line = headers\n        .lines()\n        .next()\n        .ok_or_else(|| anyhow!(\"Empty request\"))?;\n    let parts: Vec<&str> = first_line.split_whitespace().collect();\n\n    if parts.len() < 2 {\n        return Err(anyhow!(\"Invalid request line\"));\n    }\n\n    let method = parts[0].to_string();\n    let path = parts[1].to_string();\n    let body = data[header_end + 4..].to_vec();\n\n    Ok((method, path, body))\n}\n\nfn format_http_response(resp: &HttpResponse) -> Vec<u8> {\n    let status_text = match resp.status {\n        200 => \"OK\",\n        404 => \"Not Found\",\n        500 => \"Internal Server Error\",\n        _ => \"Unknown\",\n    };\n\n    let header = format!(\n        \"HTTP/1.1 {} {}\\r\\nContent-Type: {}\\r\\nContent-Length: {}\\r\\nConnection: close\\r\\n\\r\\n\",\n        resp.status,\n        status_text,\n        resp.content_type,\n        resp.body.len()\n    );\n\n    let mut result = header.into_bytes();\n    result.extend_from_slice(&resp.body);\n    result\n}\n\nfn encode_length_delimited(output: &mut Vec<u8>, data: &[u8]) {\n    let mut len = data.len();\n    while len >= 0x80 {\n        output.push((len as u8) | 0x80);\n        len >>= 7;\n    }\n    output.push(len as u8);\n    output.extend_from_slice(data);\n}\n\nfn convert_value_to_core(value: &Value) -> CoreValue {\n    match value {\n        Value::None | Value::Null => CoreValue::Null,\n        Value::Integer { value } => CoreValue::from_i64(*value),\n        Value::Float { value } => CoreValue::from_f64(*value),\n        Value::Text { value } => CoreValue::Text(turso_core::types::Text {\n            value: std::borrow::Cow::Owned(value.clone()),\n            subtype: turso_core::types::TextSubtype::Text,\n        }),\n        Value::Blob { value } => CoreValue::Blob(value.to_vec()),\n    }\n}\n\nfn convert_core_to_value(value: CoreValue) -> Value {\n    match value {\n        CoreValue::Null => Value::Null,\n        CoreValue::Numeric(turso_core::Numeric::Integer(v)) => Value::Integer { value: v },\n        CoreValue::Numeric(turso_core::Numeric::Float(v)) => Value::Float {\n            value: f64::from(v),\n        },\n        CoreValue::Text(t) => Value::Text {\n            value: t.value.to_string(),\n        },\n        CoreValue::Blob(b) => Value::Blob {\n            value: Bytes::from(b),\n        },\n    }\n}\n"
  },
  {
    "path": "cli/tests/non_interactive_exit_code.rs",
    "content": "use std::io::Write;\nuse std::process::{Command, Stdio};\n\n// ---------------------------------------------------------------------------\n// A. SQL argument mode\n// ---------------------------------------------------------------------------\n\n/// A1: Success path returns 0\n#[test]\nfn sql_argument_returns_exit_code_zero_on_success() {\n    let status = Command::new(env!(\"CARGO_BIN_EXE_tursodb\"))\n        .arg(\":memory:\")\n        .arg(\"select 'one'; select 'two';\")\n        .status()\n        .expect(\"failed to run tursodb\");\n\n    assert_eq!(status.code(), Some(0));\n}\n\n/// A2: Parse/prepare failure returns non-zero\n#[test]\nfn sql_argument_returns_exit_code_one_on_query_failure() {\n    let status = Command::new(env!(\"CARGO_BIN_EXE_tursodb\"))\n        .arg(\":memory:\")\n        .arg(\"select 'one'; select * from t; select 'two';\")\n        .status()\n        .expect(\"failed to run tursodb\");\n\n    assert_eq!(status.code(), Some(1));\n}\n\n/// A3: Fail-fast on parse/prepare failure — statements after error do not execute\n#[test]\nfn sql_argument_stops_execution_after_first_error() {\n    let output = Command::new(env!(\"CARGO_BIN_EXE_tursodb\"))\n        .arg(\":memory:\")\n        .arg(\"select 'one'; select * from t; select 'two';\")\n        .output()\n        .expect(\"failed to run tursodb\");\n\n    let stdout = String::from_utf8_lossy(&output.stdout);\n    assert!(stdout.contains(\"one\"), \"first query should execute\");\n    assert!(\n        !stdout.contains(\"two\"),\n        \"query after error should not execute\"\n    );\n    assert_eq!(output.status.code(), Some(1));\n}\n\n/// A4: Runtime/step failure (constraint violation) returns non-zero\n#[test]\nfn sql_argument_runtime_error_returns_nonzero() {\n    let sql = \"create table t(x integer primary key); \\\n               insert into t values(1); \\\n               insert into t values(1); \\\n               select 'after';\";\n    let status = Command::new(env!(\"CARGO_BIN_EXE_tursodb\"))\n        .arg(\":memory:\")\n        .arg(sql)\n        .status()\n        .expect(\"failed to run tursodb\");\n\n    assert_eq!(status.code(), Some(1));\n}\n\n/// A5: Fail-fast on runtime/step failure — statements after constraint violation do not execute\n#[test]\nfn sql_argument_runtime_error_stops_execution() {\n    let sql = \"create table t(x integer primary key); \\\n               insert into t values(1); \\\n               insert into t values(1); \\\n               select 'after';\";\n    let output = Command::new(env!(\"CARGO_BIN_EXE_tursodb\"))\n        .arg(\":memory:\")\n        .arg(sql)\n        .output()\n        .expect(\"failed to run tursodb\");\n\n    let stdout = String::from_utf8_lossy(&output.stdout);\n    assert!(\n        !stdout.contains(\"after\"),\n        \"query after runtime error should not execute\"\n    );\n    assert_eq!(output.status.code(), Some(1));\n}\n\n/// A6: Syntax error returns non-zero\n#[test]\nfn sql_argument_syntax_error_returns_nonzero() {\n    let status = Command::new(env!(\"CARGO_BIN_EXE_tursodb\"))\n        .arg(\":memory:\")\n        .arg(\"select from;\")\n        .status()\n        .expect(\"failed to run tursodb\");\n\n    assert_eq!(status.code(), Some(1));\n}\n\n/// A7: Empty SQL string returns 0\n#[test]\nfn sql_argument_empty_string_returns_zero() {\n    let status = Command::new(env!(\"CARGO_BIN_EXE_tursodb\"))\n        .arg(\":memory:\")\n        .arg(\"\")\n        .status()\n        .expect(\"failed to run tursodb\");\n\n    assert_eq!(status.code(), Some(0));\n}\n\n/// A8: sqlite_dbpage updates require unsafe testing mode\n#[test]\nfn sqlite_dbpage_update_requires_unsafe_testing() {\n    let sql = \"create table t(x); update sqlite_dbpage set data = data where pgno = 1; select 'after_update';\";\n    let output = Command::new(env!(\"CARGO_BIN_EXE_tursodb\"))\n        .arg(\":memory:\")\n        .arg(sql)\n        .output()\n        .expect(\"failed to run tursodb\");\n\n    let stdout = String::from_utf8_lossy(&output.stdout);\n    assert!(\n        !stdout.contains(\"after_update\"),\n        \"query after sqlite_dbpage update should not execute without unsafe testing\"\n    );\n    assert_eq!(output.status.code(), Some(1));\n}\n\n/// A9: sqlite_dbpage updates succeed with unsafe testing mode\n#[test]\nfn sqlite_dbpage_update_allows_unsafe_testing() {\n    let sql = \"create table t(x); update sqlite_dbpage set data = data where pgno = 1; select 'after_update';\";\n    let output = Command::new(env!(\"CARGO_BIN_EXE_tursodb\"))\n        .arg(\"--unsafe-testing\")\n        .arg(\":memory:\")\n        .arg(sql)\n        .output()\n        .expect(\"failed to run tursodb\");\n\n    let stdout = String::from_utf8_lossy(&output.stdout);\n    assert!(\n        stdout.contains(\"after_update\"),\n        \"expected query after update to run\"\n    );\n    assert_eq!(output.status.code(), Some(0));\n}\n\n// ---------------------------------------------------------------------------\n// B. Piped stdin mode\n// ---------------------------------------------------------------------------\n\n/// B8: Success path returns 0\n#[test]\nfn piped_stdin_returns_exit_code_zero_on_success() {\n    let mut child = Command::new(env!(\"CARGO_BIN_EXE_tursodb\"))\n        .arg(\":memory:\")\n        .stdin(Stdio::piped())\n        .stdout(Stdio::null())\n        .stderr(Stdio::null())\n        .spawn()\n        .expect(\"failed to run tursodb\");\n\n    let mut stdin = child.stdin.take().unwrap();\n    stdin.write_all(b\"select 1;\\n\").unwrap();\n    drop(stdin);\n\n    let status = child.wait().expect(\"failed to wait\");\n    assert_eq!(status.code(), Some(0));\n}\n\n/// B9: Parse/prepare failure returns non-zero\n#[test]\nfn piped_stdin_returns_exit_code_one_on_query_failure() {\n    let mut child = Command::new(env!(\"CARGO_BIN_EXE_tursodb\"))\n        .arg(\":memory:\")\n        .stdin(Stdio::piped())\n        .stdout(Stdio::null())\n        .stderr(Stdio::null())\n        .spawn()\n        .expect(\"failed to run tursodb\");\n\n    let mut stdin = child.stdin.take().unwrap();\n    stdin.write_all(b\"select * from nonexistent;\\n\").unwrap();\n    drop(stdin);\n\n    let status = child.wait().expect(\"failed to wait\");\n    assert_eq!(status.code(), Some(1));\n}\n\n/// B10: Fail-fast in piped multi-statement failure\n#[test]\nfn piped_stdin_stops_execution_after_first_error() {\n    let mut child = Command::new(env!(\"CARGO_BIN_EXE_tursodb\"))\n        .arg(\":memory:\")\n        .stdin(Stdio::piped())\n        .stdout(Stdio::piped())\n        .stderr(Stdio::null())\n        .spawn()\n        .expect(\"failed to run tursodb\");\n\n    let mut stdin = child.stdin.take().unwrap();\n    stdin\n        .write_all(b\"select 'one'; select * from missing; select 'two';\\n\")\n        .unwrap();\n    drop(stdin);\n\n    let output = child.wait_with_output().expect(\"failed to wait\");\n    let stdout = String::from_utf8_lossy(&output.stdout);\n    assert!(stdout.contains(\"one\"), \"first query should execute\");\n    assert!(\n        !stdout.contains(\"two\"),\n        \"query after error should not execute\"\n    );\n    assert_eq!(output.status.code(), Some(1));\n}\n\n/// B11: Runtime/step failure in piped mode returns non-zero\n#[test]\nfn piped_stdin_runtime_error_returns_nonzero() {\n    let mut child = Command::new(env!(\"CARGO_BIN_EXE_tursodb\"))\n        .arg(\":memory:\")\n        .stdin(Stdio::piped())\n        .stdout(Stdio::null())\n        .stderr(Stdio::null())\n        .spawn()\n        .expect(\"failed to run tursodb\");\n\n    let mut stdin = child.stdin.take().unwrap();\n    stdin\n        .write_all(\n            b\"create table t(x integer primary key);\\n\\\n              insert into t values(1);\\n\\\n              insert into t values(1);\\n\",\n        )\n        .unwrap();\n    drop(stdin);\n\n    let status = child.wait().expect(\"failed to wait\");\n    assert_eq!(status.code(), Some(1));\n}\n\n/// C1: .read handles multi-line CREATE TRIGGER correctly\n#[test]\nfn dot_read_handles_trigger_statements() {\n    let sql = \"\\\nCREATE TABLE t(id INTEGER PRIMARY KEY, val TEXT);\\n\\\nCREATE TABLE log(msg TEXT);\\n\\\nCREATE TRIGGER tr1 AFTER INSERT ON t BEGIN\\n\\\n    INSERT INTO log VALUES ('inserted ' || NEW.val);\\n\\\nEND;\\n\\\nINSERT INTO t VALUES (1, 'hello');\\n\\\nSELECT msg FROM log;\\n\";\n\n    let sql_path = std::env::temp_dir().join(\"limbo_test_dot_read_trigger.sql\");\n    std::fs::write(&sql_path, sql).expect(\"failed to write sql file\");\n\n    let dot_read = format!(\".read {}\", sql_path.display());\n    let mut child = Command::new(env!(\"CARGO_BIN_EXE_tursodb\"))\n        .arg(\":memory:\")\n        .stdin(Stdio::piped())\n        .stdout(Stdio::piped())\n        .stderr(Stdio::piped())\n        .spawn()\n        .expect(\"failed to run tursodb\");\n\n    let mut stdin = child.stdin.take().unwrap();\n    stdin.write_all(dot_read.as_bytes()).unwrap();\n    stdin.write_all(b\"\\n\").unwrap();\n    drop(stdin);\n\n    let output = child.wait_with_output().expect(\"failed to wait\");\n    let stdout = String::from_utf8_lossy(&output.stdout);\n    let stderr = String::from_utf8_lossy(&output.stderr);\n\n    std::fs::remove_file(&sql_path).ok();\n\n    assert!(\n        !stderr.contains(\"unexpected end of file\"),\n        \"trigger should not produce parse errors, stderr: {stderr}\"\n    );\n    assert!(\n        !stderr.contains(\"no such column\"),\n        \"NEW.val should be resolved inside trigger, stderr: {stderr}\"\n    );\n    assert!(\n        stdout.contains(\"inserted hello\"),\n        \"trigger should fire and insert into log, stdout: {stdout}\"\n    );\n}\n\n/// B12: Empty piped stdin returns 0\n#[test]\nfn piped_stdin_empty_returns_zero() {\n    let mut child = Command::new(env!(\"CARGO_BIN_EXE_tursodb\"))\n        .arg(\":memory:\")\n        .stdin(Stdio::piped())\n        .stdout(Stdio::null())\n        .stderr(Stdio::null())\n        .spawn()\n        .expect(\"failed to run tursodb\");\n\n    // Close stdin immediately — no input\n    drop(child.stdin.take());\n\n    let status = child.wait().expect(\"failed to wait\");\n    assert_eq!(status.code(), Some(0));\n}\n"
  },
  {
    "path": "cli/tests/parameter_bindings.rs",
    "content": "use std::io::Write;\nuse std::process::{Command, Output, Stdio};\n\nfn run_cli(input: &[u8]) -> Output {\n    let mut child = Command::new(env!(\"CARGO_BIN_EXE_tursodb\"))\n        .arg(\":memory:\")\n        .stdin(Stdio::piped())\n        .stdout(Stdio::piped())\n        .stderr(Stdio::null())\n        .spawn()\n        .expect(\"failed to run tursodb\");\n\n    let mut stdin = child.stdin.take().expect(\"failed to take stdin\");\n    stdin.write_all(input).expect(\"failed to write stdin\");\n    drop(stdin);\n\n    child.wait_with_output().expect(\"failed to wait for output\")\n}\n\nfn stdout_lines(output: &Output) -> Vec<&str> {\n    let s = std::str::from_utf8(&output.stdout).expect(\"non-utf8 stdout\");\n    s.lines().collect()\n}\n\n#[test]\nfn parameter_set_binds_named_slot() {\n    let output = run_cli(b\".mode list\\n.parameter set :x 41\\nselect :x;\\n\");\n\n    assert_eq!(output.status.code(), Some(0));\n    assert_eq!(stdout_lines(&output), vec![\"41\"]);\n}\n\n#[test]\nfn parameter_set_binds_positional_slot() {\n    let output = run_cli(b\".mode list\\n.parameter set ?1 9\\nselect ?1;\\n\");\n\n    assert_eq!(output.status.code(), Some(0));\n    assert_eq!(stdout_lines(&output), vec![\"9\"]);\n}\n\n#[test]\nfn parameter_clear_removes_binding() {\n    let output = run_cli(b\".mode list\\n.parameter set :x 41\\n.parameter clear :x\\nselect :x;\\n\");\n\n    assert_eq!(output.status.code(), Some(0));\n    assert_eq!(stdout_lines(&output), vec![\"\"]);\n}\n\n#[test]\nfn parameter_set_rejects_bare_name() {\n    let output = run_cli(b\".mode list\\n.parameter set x 41\\nselect :x;\\n\");\n\n    assert_eq!(output.status.code(), Some(0));\n    let lines = stdout_lines(&output);\n    assert!(\n        lines\n            .iter()\n            .any(|l| l.contains(\"Error: parameter name must start with one of\")),\n        \"expected bare-name validation error, got: {lines:?}\"\n    );\n}\n\n#[test]\nfn parameter_set_supports_quoted_multi_word_text() {\n    let output = run_cli(b\".mode list\\n.parameter set :msg \\\"hello world\\\"\\nselect :msg;\\n\");\n\n    assert_eq!(output.status.code(), Some(0));\n    assert_eq!(stdout_lines(&output), vec![\"hello world\"]);\n}\n\n#[test]\nfn parameter_clear_only_removes_requested_name() {\n    let output = run_cli(\n        b\".mode list\\n.parameter set :x 1\\n.parameter set @x 2\\n.parameter clear :x\\nselect :x, @x;\\n\",\n    );\n\n    assert_eq!(output.status.code(), Some(0));\n    assert_eq!(stdout_lines(&output), vec![\"|2\"]);\n}\n\n#[test]\nfn parameter_set_rejects_zero_positional_index() {\n    let output = run_cli(b\".mode list\\n.parameter set ?0 41\\n\");\n\n    assert_eq!(output.status.code(), Some(0));\n    let lines = stdout_lines(&output);\n    assert!(\n        lines\n            .iter()\n            .any(|l| l.contains(\"?N' must use an index >= 1\")),\n        \"expected positional index bounds validation error, got: {lines:?}\"\n    );\n}\n\n#[test]\nfn parameter_set_mixed_named_and_positional() {\n    let output = run_cli(\n        b\".mode list\\n.parameter set :name alice\\n.parameter set ?2 30\\nselect :name, ?2;\\n\",\n    );\n\n    assert_eq!(output.status.code(), Some(0));\n    assert_eq!(stdout_lines(&output), vec![\"alice|30\"]);\n}\n\n#[test]\nfn parameter_set_anonymous_positional() {\n    let output =\n        run_cli(b\".mode list\\n.parameter set ?1 first\\n.parameter set ?2 second\\nselect ?, ?;\\n\");\n\n    assert_eq!(output.status.code(), Some(0));\n    assert_eq!(stdout_lines(&output), vec![\"first|second\"]);\n}\n\n#[test]\nfn parameter_set_mixed_named_and_anonymous_positional() {\n    let output = run_cli(\n        b\".mode list\\n.parameter set :name alice\\n.parameter set ?2 30\\nselect :name, ?;\\n\",\n    );\n\n    assert_eq!(output.status.code(), Some(0));\n    assert_eq!(stdout_lines(&output), vec![\"alice|30\"]);\n}\n\n#[test]\nfn parameter_set_parses_hex_blob_literal() {\n    let output = run_cli(b\".mode list\\n.parameter set :blob \\\"x'4142'\\\"\\nselect :blob;\\n\");\n\n    assert_eq!(output.status.code(), Some(0));\n    assert_eq!(stdout_lines(&output), vec![\"AB\"]);\n}\n"
  },
  {
    "path": "core/Cargo.toml",
    "content": "# Copyright 2023-2025 the Turso authors. All rights reserved. MIT license.\n\n[package]\nname = \"turso_core\"\nversion.workspace = true\nauthors.workspace = true\nedition.workspace = true\nlicense.workspace = true\nrepository.workspace = true\ndescription = \"The Turso database library\"\n\n[lints]\nworkspace = true\n\n[lib]\nname = \"turso_core\"\npath = \"lib.rs\"\n\n[features]\ndefault = [\"fs\", \"uuid\", \"time\", \"json\", \"series\", \"encryption\"]\ntracing_release = [\"tracing/release_max_level_info\"]\nconn_raw_api = []\nfs = [\"turso_ext/vfs\"]\njson = []\nuuid = [\"dep:uuid\"]\nio_uring = [\"dep:io-uring\", \"rustix/io_uring\"]\nexperimental_win_iocp = []\ntime = []\nfuzz = []\nomit_autovacuum = []\nsimulator = [\"fuzz\", \"serde\"]\nserde = [\"dep:serde\"]\nseries = []\nencryption = []\nchecksum = []\ncli_only = []\ntest_helper = []\nbench = []\nnanosecond-bench = [\"bench\"]\nfts = [\"dep:tantivy\"]\ncodspeed = [\"bench\"]\noptimizer_params = [\"serde\", \"dep:serde_json\"]\n\n[target.'cfg(target_os = \"windows\")'.dependencies]\nwindows-sys = { version = \"0.61.2\", features = [\n    \"Win32_System_IO\",\n    \"Win32_Storage_FileSystem\",\n    \"Win32_Security\",\n    \"Win32_System_Diagnostics_Debug\",\n] }\n\n[target.'cfg(target_os = \"linux\")'.dependencies]\nio-uring = { version = \"0.7.5\", optional = true }\nlibc = { version = \"0.2.172\" }\n\n[target.'cfg(target_family = \"unix\")'.dependencies]\npolling = \"3.7.4\"\nrustix = { version = \"1.0.5\", features = [\"fs\"] }\nlibc = { version = \"0.2.172\" }\n\n[target.'cfg(not(target_family = \"wasm\"))'.dependencies]\nlibloading = \"0.8.6\"\ntantivy = { version = \"0.25.0\", optional = true }\n\n[dependencies]\nturso_ext = { workspace = true, features = [\"core_only\"] }\ncfg_block = \"0.1.1\"\nfallible-iterator = { workspace = true }\nhex = { workspace = true }\nthiserror = { workspace = true }\nregex = { workspace = true }\nregex-syntax = { workspace = true, default-features = false, features = [\n    \"unicode\",\n] }\nchrono = { workspace = true, default-features = false, features = [\"clock\"] }\nrand = { workspace = true }\nlibm = \"0.2\"\nturso_macros = { workspace = true }\nmiette = { workspace = true }\nstrum = { workspace = true }\nparking_lot = { workspace = true, features = [\"arc_lock\"] }\ncrossbeam-skiplist = \"0.1.3\"\ntracing = { workspace = true }\nryu = \"1.0.19\"\nuncased = \"0.9.10\"\nstrum_macros = { workspace = true }\nbitflags = { workspace = true }\nserde = { workspace = true, optional = true, features = [\"derive\"] }\nserde_json = { workspace = true, optional = true }\npastey = \"0.2.1\"\nuuid = { version = \"1.11.0\", features = [\"v4\", \"v5\", \"v7\"], optional = true }\ntempfile = { workspace = true }\npack1 = { version = \"1.0.0\", features = [\"bytemuck\"] }\nbytemuck = \"1.23.1\"\naes-gcm = { version = \"0.10.3\" }\naes = { version = \"0.8.4\" }\nturso_parser = { workspace = true }\ntwox-hash = \"2.1.1\"\nintrusive-collections = \"0.9.7\"\nroaring = \"0.11.2\"\narc-swap = \"1.7\"\nrustc-hash = \"2.0\"\neither = { workspace = true }\ntracing-subscriber.workspace = true\nrapidhash = \"4.1.1\"\nbranches = { version = \"0.4.3\", default-features = false }\nbumpalo = { version = \"3\", features = [\"collections\"] }\nsmallvec = \"1.15.1\"\nfastbloom = \"0.14.1\"\ncrc32c = \"0.6.8\"\nbigdecimal = \"0.4\"\nnum-bigint = \"0.4\"\nnum-traits = \"0.2\"\n\n[target.'cfg(not(any(target_family = \"wasm\", all(target_os = \"windows\", target_arch = \"aarch64\"))))'.dependencies]\nsimsimd = \"6.5.3\"\n\n[target.'cfg(antithesis)'.dependencies]\nantithesis_sdk = { workspace = true, features = [\"full\"] }\nserde_json = { workspace = true }\n\n[target.'cfg(loom)'.dependencies]\nloom = { workspace = true }\n\n[target.'cfg(shuttle)'.dependencies]\nshuttle = { workspace = true }\n\n# Use pure-rust for Android and MacOS to avoid C cross-compilation issues\n[target.'cfg(any(target_os = \"android\", target_os = \"macos\"))'.dependencies]\naegis = { version = \"0.9.5\", features = [\"pure-rust\"] }\n\n[target.'cfg(not(any(target_os = \"android\", target_os = \"macos\")))'.dependencies]\naegis = \"0.9.5\"\n\n[build-dependencies]\nchrono = { workspace = true, default-features = false }\nbuilt = { version = \"0.7.5\", features = [\"chrono\"] }\n\n[target.'cfg(not(target_family = \"windows\"))'.dev-dependencies]\npprof = { version = \"0.14.0\", features = [\"criterion\", \"flamegraph\"] }\n\n[dev-dependencies]\nmemory-stats = \"1.2.0\"\ncriterion = { workspace = true, features = [\n    \"html_reports\",\n    \"async\",\n    \"async_futures\",\n] }\ncodspeed-criterion-compat = { workspace = true, features = [\n    \"html_reports\",\n    \"async\",\n    \"async_futures\",\n] }\nrstest = \"0.18.2\"\nrusqlite = { workspace = true, features = [\"series\"] }\nquickcheck = { version = \"1.0\", default-features = false }\nquickcheck_macros = { version = \"1.0\", default-features = false }\nrand_chacha = { workspace = true }\nenv_logger = { workspace = true }\ntest-log = { version = \"0.2.17\", features = [\"trace\"] }\nsorted-vec = \"0.8.6\"\nmimalloc = { workspace = true, default-features = false }\ndivan.workspace = true\n\n\n[[bench]]\nname = \"benchmark\"\nharness = false\n\n[[bench]]\nname = \"mvcc_benchmark\"\nharness = false\n\n[[bench]]\nname = \"json_benchmark\"\nharness = false\n\n[[bench]]\nname = \"tpc_h_benchmark\"\nharness = false\n\n[[bench]]\nname = \"sql_functions\"\nharness = false\nrequired-features = [\"bench\"]\n\n[[bench]]\nname = \"hash_spill_benchmark\"\nharness = false\nrequired-features = [\"bench\"]\n\n[[bench]]\nname = \"write_perf_benchmark\"\nharness = false\n\n[[bench]]\nname = \"fts_benchmark\"\nharness = false\nrequired-features = [\"fts\"]\n\n[[bench]]\nname = \"graph_queries_benchmark\"\nharness = false\n"
  },
  {
    "path": "core/assert.rs",
    "content": "/// Assert that a type implements Send at compile time.\n/// Usage: assert_send!(MyType);\n/// Usage: assert_send!(Type1, Type2, Type3);\nmacro_rules! assert_send {\n    ($($t:ty),+ $(,)?) => {\n        #[cfg(test)]\n        $(const _: () = {\n            const fn _assert_send<T: ?Sized + Send>() {}\n            _assert_send::<$t>();\n        };)+\n    };\n}\n\npub(crate) use assert_send;\n\n/// Assert that a type implements Sync at compile time.\n/// Usage: assert_sync!(MyType);\n/// Usage: assert_sync!(Type1, Type2, Type3);\nmacro_rules! assert_sync {\n    ($($t:ty),+ $(,)?) => {\n        #[cfg(test)]\n        $(const _: () = {\n            const fn _assert_sync<T: ?Sized + Sync>() {}\n            _assert_sync::<$t>();\n        };)+\n    };\n}\npub(crate) use assert_sync;\n\n/// Assert that a type implements both Send and Sync at compile time.\n/// Usage: assert_send_sync!(MyType);\n/// Usage: assert_send_sync!(Type1, Type2, Type3);\nmacro_rules! assert_send_sync {\n    ($($t:ty),+ $(,)?) => {\n        #[cfg(test)]\n        $(const _: () = {\n            const fn _assert_send<T: ?Sized + Send>() {}\n            const fn _assert_sync<T: ?Sized + Sync>() {}\n            _assert_send::<$t>();\n            _assert_sync::<$t>();\n        };)+\n    };\n}\npub(crate) use assert_send_sync;\n"
  },
  {
    "path": "core/benches/benchmark.rs",
    "content": "#[cfg(not(feature = \"codspeed\"))]\nuse criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};\n#[cfg(not(feature = \"codspeed\"))]\nuse pprof::criterion::{Output, PProfProfiler};\n\n#[cfg(feature = \"codspeed\")]\nuse codspeed_criterion_compat::{\n    black_box, criterion_group, criterion_main, BenchmarkId, Criterion,\n};\nuse regex::Regex;\nuse std::{\n    sync::Arc,\n    time::{Duration, Instant},\n};\nuse tempfile::TempDir;\nuse turso_core::{Database, LimboError, PlatformIO, StepResult};\n\n#[cfg(not(target_family = \"wasm\"))]\n#[global_allocator]\nstatic GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;\n\nfn rusqlite_open() -> rusqlite::Connection {\n    let sqlite_conn = rusqlite::Connection::open(\"../testing/system/testing.db\").unwrap();\n    sqlite_conn\n        .pragma_update(None, \"locking_mode\", \"EXCLUSIVE\")\n        .unwrap();\n    sqlite_conn\n}\n\nfn setup_rusqlite(temp_dir: &TempDir, query: &str) -> rusqlite::Connection {\n    let db_path = temp_dir.path().join(\"bench.db\");\n    let sqlite_conn = rusqlite::Connection::open(db_path).unwrap();\n    sqlite_conn\n        .pragma_update(None, \"synchronous\", \"FULL\")\n        .unwrap();\n    sqlite_conn\n        .pragma_update(None, \"journal_mode\", \"WAL\")\n        .unwrap();\n    sqlite_conn\n        .pragma_update(None, \"locking_mode\", \"EXCLUSIVE\")\n        .unwrap();\n    let journal_mode = sqlite_conn\n        .pragma_query_value(None, \"journal_mode\", |row| row.get::<_, String>(0))\n        .unwrap();\n    assert_eq!(journal_mode.to_lowercase(), \"wal\");\n    let synchronous = sqlite_conn\n        .pragma_query_value(None, \"synchronous\", |row| row.get::<_, usize>(0))\n        .unwrap();\n    const FULL: usize = 2;\n    assert_eq!(synchronous, FULL);\n\n    // load the generate_series extension\n    rusqlite::vtab::series::load_module(&sqlite_conn).unwrap();\n\n    // Create test table\n    sqlite_conn.execute(query, []).unwrap();\n    sqlite_conn\n}\n\nfn bench_open(criterion: &mut Criterion) {\n    // https://github.com/tursodatabase/turso/issues/174\n    // The rusqlite benchmark crashes on Mac M1 when using the flamegraph features\n    let enable_rusqlite = std::env::var(\"DISABLE_RUSQLITE_BENCHMARK\").is_err();\n\n    if !std::fs::exists(\"../testing/system/schema_5k.db\").unwrap() {\n        #[allow(clippy::arc_with_non_send_sync)]\n        let io = Arc::new(PlatformIO::new().unwrap());\n        let db = Database::open_file(io, \"../testing/system/schema_5k.db\").unwrap();\n        let conn = db.connect().unwrap();\n\n        for i in 0..5000 {\n            conn.execute(\n                format!(\"CREATE TABLE table_{i} ( id INTEGER PRIMARY KEY, name TEXT, value INTEGER, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP )\")\n            ).unwrap();\n        }\n    }\n\n    let mut group = criterion.benchmark_group(\"Open/Connect\");\n\n    group.bench_function(BenchmarkId::new(\"limbo_schema\", \"\"), |b| {\n        b.iter(|| {\n            #[allow(clippy::arc_with_non_send_sync)]\n            let io = Arc::new(PlatformIO::new().unwrap());\n            let db = Database::open_file(io, \"../testing/system/schema_5k.db\").unwrap();\n            let conn = db.connect().unwrap();\n            conn.execute(\"SELECT * FROM table_0\").unwrap();\n        });\n    });\n\n    if enable_rusqlite {\n        group.bench_function(BenchmarkId::new(\"sqlite_schema\", \"\"), |b| {\n            b.iter(|| {\n                let conn = rusqlite::Connection::open(\"../testing/system/schema_5k.db\").unwrap();\n                conn.execute(\"SELECT * FROM table_0\", ()).unwrap();\n            });\n        });\n    }\n\n    group.finish();\n}\n\nfn bench_alter(criterion: &mut Criterion) {\n    // https://github.com/tursodatabase/turso/issues/174\n    // The rusqlite benchmark crashes on Mac M1 when using the flamegraph features\n    let enable_rusqlite = std::env::var(\"DISABLE_RUSQLITE_BENCHMARK\").is_err();\n\n    if !std::fs::exists(\"../testing/system/schema_5k.db\").unwrap() {\n        #[allow(clippy::arc_with_non_send_sync)]\n        let io = Arc::new(PlatformIO::new().unwrap());\n        let db = Database::open_file(io, \"../testing/system/schema_5k.db\").unwrap();\n        let conn = db.connect().unwrap();\n\n        for i in 0..5000 {\n            conn.execute(\n                format!(\"CREATE TABLE table_{i} ( id INTEGER PRIMARY KEY, name TEXT, value INTEGER, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP )\")\n            ).unwrap();\n        }\n    }\n\n    let mut group = criterion.benchmark_group(\"`ALTER TABLE _ RENAME TO _`\");\n\n    group.bench_function(BenchmarkId::new(\"limbo_rename_table\", \"\"), |b| {\n        #[allow(clippy::arc_with_non_send_sync)]\n        let io = Arc::new(PlatformIO::new().unwrap());\n        let db = Database::open_file(io, \"../testing/system/schema_5k.db\").unwrap();\n        let conn = db.connect().unwrap();\n        b.iter_custom(|iters| {\n            (0..iters)\n                .map(|_| {\n                    conn.execute(\"CREATE TABLE x(a)\").unwrap();\n                    let elapsed = {\n                        let start = Instant::now();\n                        conn.execute(\"ALTER TABLE x RENAME TO y\").unwrap();\n                        start.elapsed()\n                    };\n                    conn.execute(\"DROP TABLE y\").unwrap();\n                    elapsed\n                })\n                .sum::<Duration>()\n        });\n    });\n\n    if enable_rusqlite {\n        group.bench_function(BenchmarkId::new(\"sqlite_rename_table\", \"\"), |b| {\n            let conn = rusqlite::Connection::open(\"../testing/system/schema_5k.db\").unwrap();\n            b.iter_custom(|iters| {\n                (0..iters)\n                    .map(|_| {\n                        conn.execute(\"CREATE TABLE x(a)\", ()).unwrap();\n                        let elapsed = {\n                            let start = Instant::now();\n                            conn.execute(\"ALTER TABLE x RENAME TO y\", ()).unwrap();\n                            start.elapsed()\n                        };\n                        conn.execute(\"DROP TABLE y\", ()).unwrap();\n                        elapsed\n                    })\n                    .sum::<Duration>()\n            });\n        });\n    }\n\n    group.finish();\n\n    let mut group = criterion.benchmark_group(\"`ALTER TABLE _ RENAME COLUMN _ TO _`\");\n\n    group.bench_function(BenchmarkId::new(\"limbo_rename_column\", \"\"), |b| {\n        #[allow(clippy::arc_with_non_send_sync)]\n        let io = Arc::new(PlatformIO::new().unwrap());\n        let db = Database::open_file(io, \"../testing/system/schema_5k.db\").unwrap();\n        let conn = db.connect().unwrap();\n        b.iter_custom(|iters| {\n            (0..iters)\n                .map(|_| {\n                    conn.execute(\"CREATE TABLE x(a)\").unwrap();\n                    let elapsed = {\n                        let start = Instant::now();\n                        conn.execute(\"ALTER TABLE x RENAME COLUMN a TO b\").unwrap();\n                        start.elapsed()\n                    };\n                    conn.execute(\"DROP TABLE x\").unwrap();\n                    elapsed\n                })\n                .sum::<Duration>()\n        });\n    });\n\n    if enable_rusqlite {\n        group.bench_function(BenchmarkId::new(\"sqlite_rename_column\", \"\"), |b| {\n            let conn = rusqlite::Connection::open(\"../testing/system/schema_5k.db\").unwrap();\n            b.iter_custom(|iters| {\n                (0..iters)\n                    .map(|_| {\n                        conn.execute(\"CREATE TABLE x(a)\", ()).unwrap();\n                        let elapsed = {\n                            let start = Instant::now();\n                            conn.execute(\"ALTER TABLE x RENAME COLUMN a TO b\", ())\n                                .unwrap();\n                            start.elapsed()\n                        };\n                        conn.execute(\"DROP TABLE x\", ()).unwrap();\n                        elapsed\n                    })\n                    .sum::<Duration>()\n            });\n        });\n    }\n\n    group.finish();\n\n    let mut group = criterion.benchmark_group(\"`ALTER TABLE _ ADD COLUMN _`\");\n\n    group.bench_function(BenchmarkId::new(\"limbo_add_column\", \"\"), |b| {\n        #[allow(clippy::arc_with_non_send_sync)]\n        let io = Arc::new(PlatformIO::new().unwrap());\n        let db = Database::open_file(io, \"../testing/system/schema_5k.db\").unwrap();\n        let conn = db.connect().unwrap();\n        b.iter_custom(|iters| {\n            (0..iters)\n                .map(|_| {\n                    conn.execute(\"CREATE TABLE x(a)\").unwrap();\n                    let elapsed = {\n                        let start = Instant::now();\n                        conn.execute(\"ALTER TABLE x ADD COLUMN b\").unwrap();\n                        start.elapsed()\n                    };\n                    conn.execute(\"DROP TABLE x\").unwrap();\n                    elapsed\n                })\n                .sum::<Duration>()\n        });\n    });\n\n    if enable_rusqlite {\n        group.bench_function(BenchmarkId::new(\"sqlite_add_column\", \"\"), |b| {\n            let conn = rusqlite::Connection::open(\"../testing/system/schema_5k.db\").unwrap();\n            b.iter_custom(|iters| {\n                (0..iters)\n                    .map(|_| {\n                        conn.execute(\"CREATE TABLE x(a)\", ()).unwrap();\n                        let elapsed = {\n                            let start = Instant::now();\n                            conn.execute(\"ALTER TABLE x ADD COLUMN b\", ()).unwrap();\n                            start.elapsed()\n                        };\n                        conn.execute(\"DROP TABLE x\", ()).unwrap();\n                        elapsed\n                    })\n                    .sum::<Duration>()\n            });\n        });\n    }\n\n    group.finish();\n\n    let mut group = criterion.benchmark_group(\"`ALTER TABLE _ DROP COLUMN _`\");\n\n    group.bench_function(BenchmarkId::new(\"limbo_drop_column\", \"\"), |b| {\n        #[allow(clippy::arc_with_non_send_sync)]\n        let io = Arc::new(PlatformIO::new().unwrap());\n        let db = Database::open_file(io, \"../testing/system/schema_5k.db\").unwrap();\n        let conn = db.connect().unwrap();\n        b.iter_custom(|iters| {\n            (0..iters)\n                .map(|_| {\n                    conn.execute(\"CREATE TABLE x(a, b)\").unwrap();\n                    let elapsed = {\n                        let start = Instant::now();\n                        conn.execute(\"ALTER TABLE x DROP COLUMN b\").unwrap();\n                        start.elapsed()\n                    };\n                    conn.execute(\"DROP TABLE x\").unwrap();\n                    elapsed\n                })\n                .sum::<Duration>()\n        });\n    });\n\n    if enable_rusqlite {\n        group.bench_function(BenchmarkId::new(\"sqlite_drop_column\", \"\"), |b| {\n            let conn = rusqlite::Connection::open(\"../testing/system/schema_5k.db\").unwrap();\n            b.iter_custom(|iters| {\n                (0..iters)\n                    .map(|_| {\n                        conn.execute(\"CREATE TABLE x(a, b)\", ()).unwrap();\n                        let elapsed = {\n                            let start = Instant::now();\n                            conn.execute(\"ALTER TABLE x DROP COLUMN b\", ()).unwrap();\n                            start.elapsed()\n                        };\n                        conn.execute(\"DROP TABLE x\", ()).unwrap();\n                        elapsed\n                    })\n                    .sum::<Duration>()\n            });\n        });\n    }\n\n    group.finish();\n}\n\nfn bench_prepare_query(criterion: &mut Criterion) {\n    // https://github.com/tursodatabase/turso/issues/174\n    // The rusqlite benchmark crashes on Mac M1 when using the flamegraph features\n    let enable_rusqlite = std::env::var(\"DISABLE_RUSQLITE_BENCHMARK\").is_err();\n\n    #[allow(clippy::arc_with_non_send_sync)]\n    let io = Arc::new(PlatformIO::new().unwrap());\n    let db = Database::open_file(io, \"../testing/system/testing.db\").unwrap();\n    let limbo_conn = db.connect().unwrap();\n\n    let queries = [\n        \"SELECT 1\",\n        \"SELECT * FROM users LIMIT 1\",\n        \"SELECT first_name, count(1) FROM users GROUP BY first_name HAVING count(1) > 1 ORDER BY count(1)  LIMIT 1\",\n        \"SELECT\n            first_name,\n            last_name,\n            state,\n            city,\n            age + 10,\n            LENGTH(email),\n            UPPER(first_name),\n            LOWER(last_name),\n            SUBSTR(phone_number, 1, 3),\n            zipcode || '-' || state,\n            AVG(age) + 5,\n            MAX(age) - MIN(age),\n            ROUND(AVG(age), 1),\n            SUM(age) / COUNT(*),\n            COUNT(*),\n            COUNT(email),\n            SUM(age),\n            AVG(age),\n            MIN(age),\n            MAX(age),\n            SUM(CASE WHEN age >= 18 THEN 1 ELSE 0 END),\n            SUM(CASE WHEN age < 18 THEN 1 ELSE 0 END),\n            AVG(CASE WHEN age >= 18 THEN age ELSE NULL END),\n            MAX(CASE WHEN age >= 18 THEN age ELSE NULL END)\n        FROM users\n        GROUP BY state, city\",\n    ];\n\n    let whitespace_re = Regex::new(r\"\\s+\").unwrap();\n    for query in queries.iter() {\n        // Normalize whitespace in the query string by replacing all sequences of whitespace with a single space.\n        let query = whitespace_re.replace_all(query, \" \").to_string();\n        let query = query.as_str();\n\n        let byte_index: usize = query.chars().take(50).map(|c| c.len_utf8()).sum();\n\n        let mut group = criterion.benchmark_group(format!(\"Prepare `{query}`\"));\n\n        group.bench_with_input(\n            // Limit the size of the benchmark id so that Codspeed does not through errors\n            BenchmarkId::new(\"limbo_parse_query\", &query[..byte_index]),\n            query,\n            |b, query| {\n                b.iter(|| {\n                    limbo_conn.prepare(query).unwrap();\n                });\n            },\n        );\n\n        if enable_rusqlite {\n            let sqlite_conn = rusqlite_open();\n\n            group.bench_with_input(\n                BenchmarkId::new(\"sqlite_parse_query\", &query[..byte_index]),\n                query,\n                |b, query| {\n                    b.iter(|| {\n                        sqlite_conn.prepare(query).unwrap();\n                    });\n                },\n            );\n        }\n\n        group.finish();\n    }\n}\n\nfn bench_execute_select_rows(criterion: &mut Criterion) {\n    // https://github.com/tursodatabase/turso/issues/174\n    // The rusqlite benchmark crashes on Mac M1 when using the flamegraph features\n    let enable_rusqlite = std::env::var(\"DISABLE_RUSQLITE_BENCHMARK\").is_err();\n\n    #[allow(clippy::arc_with_non_send_sync)]\n    let io = Arc::new(PlatformIO::new().unwrap());\n    let db = Database::open_file(io, \"../testing/system/testing.db\").unwrap();\n    let limbo_conn = db.connect().unwrap();\n\n    let mut group = criterion.benchmark_group(\"Execute `SELECT * FROM users LIMIT ?`\");\n\n    for i in [1, 10, 50, 100] {\n        group.bench_with_input(\n            BenchmarkId::new(\"limbo_execute_select_rows\", i),\n            &i,\n            |b, i| {\n                // TODO: LIMIT doesn't support query parameters.\n                let mut stmt = limbo_conn\n                    .prepare(format!(\"SELECT * FROM users LIMIT {}\", *i))\n                    .unwrap();\n                b.iter(|| {\n                    loop {\n                        match stmt.step().unwrap() {\n                            turso_core::StepResult::Row => {\n                                black_box(stmt.row());\n                            }\n                            turso_core::StepResult::IO => {\n                                db.io.step().unwrap();\n                            }\n                            turso_core::StepResult::Done => {\n                                break;\n                            }\n                            turso_core::StepResult::Interrupt | turso_core::StepResult::Busy => {\n                                unreachable!();\n                            }\n                        }\n                    }\n                    stmt.reset().unwrap();\n                });\n            },\n        );\n\n        if enable_rusqlite {\n            let sqlite_conn = rusqlite_open();\n\n            group.bench_with_input(\n                BenchmarkId::new(\"sqlite_execute_select_rows\", i),\n                &i,\n                |b, i| {\n                    // TODO: Use parameters once we fix the above.\n                    let mut stmt = sqlite_conn\n                        .prepare(&format!(\"SELECT * FROM users LIMIT {}\", *i))\n                        .unwrap();\n                    b.iter(|| {\n                        let mut rows = stmt.raw_query();\n                        while let Some(row) = rows.next().unwrap() {\n                            black_box(row);\n                        }\n                    });\n                },\n            );\n        }\n    }\n\n    group.finish();\n}\n\nfn bench_execute_select_1(criterion: &mut Criterion) {\n    // https://github.com/tursodatabase/turso/issues/174\n    // The rusqlite benchmark crashes on Mac M1 when using the flamegraph features\n    let enable_rusqlite = std::env::var(\"DISABLE_RUSQLITE_BENCHMARK\").is_err();\n\n    #[allow(clippy::arc_with_non_send_sync)]\n    let io = Arc::new(PlatformIO::new().unwrap());\n    let db = Database::open_file(io, \"../testing/system/testing.db\").unwrap();\n    let limbo_conn = db.connect().unwrap();\n\n    let mut group = criterion.benchmark_group(\"Execute `SELECT 1`\");\n\n    group.bench_function(\"limbo_execute_select_1\", |b| {\n        let mut stmt = limbo_conn.prepare(\"SELECT 1\").unwrap();\n        b.iter(|| {\n            loop {\n                match stmt.step().unwrap() {\n                    turso_core::StepResult::Row => {\n                        black_box(stmt.row());\n                    }\n                    turso_core::StepResult::IO => {\n                        db.io.step().unwrap();\n                    }\n                    turso_core::StepResult::Done => {\n                        break;\n                    }\n                    turso_core::StepResult::Interrupt | turso_core::StepResult::Busy => {\n                        unreachable!();\n                    }\n                }\n            }\n            stmt.reset().unwrap();\n        });\n    });\n\n    if enable_rusqlite {\n        let sqlite_conn = rusqlite_open();\n\n        group.bench_function(\"sqlite_execute_select_1\", |b| {\n            let mut stmt = sqlite_conn.prepare(\"SELECT 1\").unwrap();\n            b.iter(|| {\n                let mut rows = stmt.raw_query();\n                while let Some(row) = rows.next().unwrap() {\n                    black_box(row);\n                }\n            });\n        });\n    }\n\n    group.finish();\n}\n\nfn bench_execute_select_count(criterion: &mut Criterion) {\n    // https://github.com/tursodatabase/turso/issues/174\n    // The rusqlite benchmark crashes on Mac M1 when using the flamegraph features\n    let enable_rusqlite = std::env::var(\"DISABLE_RUSQLITE_BENCHMARK\").is_err();\n\n    #[allow(clippy::arc_with_non_send_sync)]\n    let io = Arc::new(PlatformIO::new().unwrap());\n    let db = Database::open_file(io, \"../testing/system/testing.db\").unwrap();\n    let limbo_conn = db.connect().unwrap();\n\n    let mut group = criterion.benchmark_group(\"Execute `SELECT count() FROM users`\");\n\n    group.bench_function(\"limbo_execute_select_count\", |b| {\n        let mut stmt = limbo_conn.prepare(\"SELECT count() FROM users\").unwrap();\n        b.iter(|| {\n            loop {\n                match stmt.step().unwrap() {\n                    turso_core::StepResult::Row => {\n                        black_box(stmt.row());\n                    }\n                    turso_core::StepResult::IO => {\n                        db.io.step().unwrap();\n                    }\n                    turso_core::StepResult::Done => {\n                        break;\n                    }\n                    turso_core::StepResult::Interrupt | turso_core::StepResult::Busy => {\n                        unreachable!();\n                    }\n                }\n            }\n            stmt.reset().unwrap();\n        });\n    });\n\n    if enable_rusqlite {\n        let sqlite_conn = rusqlite_open();\n\n        group.bench_function(\"sqlite_execute_select_count\", |b| {\n            let mut stmt = sqlite_conn.prepare(\"SELECT count() FROM users\").unwrap();\n            b.iter(|| {\n                let mut rows = stmt.raw_query();\n                while let Some(row) = rows.next().unwrap() {\n                    black_box(row);\n                }\n            });\n        });\n    }\n\n    group.finish();\n}\n\nfn bench_insert_rows(criterion: &mut Criterion) {\n    // The rusqlite benchmark crashes on Mac M1 when using the flamegraph features\n    let enable_rusqlite = std::env::var(\"DISABLE_RUSQLITE_BENCHMARK\").is_err();\n\n    let mut group = criterion.benchmark_group(\"Insert rows in batches\");\n\n    // Test different batch sizes\n    for batch_size in [1, 10, 100] {\n        let temp_dir = tempfile::tempdir().unwrap();\n        let db_path = temp_dir.path().join(\"bench.db\");\n\n        #[allow(clippy::arc_with_non_send_sync)]\n        let io = Arc::new(PlatformIO::new().unwrap());\n        let db = Database::open_file(io.clone(), db_path.to_str().unwrap()).unwrap();\n        let limbo_conn = db.connect().unwrap();\n\n        let mut stmt = limbo_conn\n            .query(\"CREATE TABLE test (id INTEGER, value TEXT)\")\n            .unwrap()\n            .unwrap();\n\n        loop {\n            match stmt.step().unwrap() {\n                turso_core::StepResult::IO => {\n                    db.io.step().unwrap();\n                }\n                turso_core::StepResult::Done => {\n                    break;\n                }\n                turso_core::StepResult::Row => {\n                    unreachable!();\n                }\n                turso_core::StepResult::Interrupt | turso_core::StepResult::Busy => {\n                    unreachable!();\n                }\n            }\n        }\n\n        group.bench_function(format!(\"limbo_insert_{batch_size}_rows\"), |b| {\n            let mut values = String::from(\"INSERT INTO test VALUES \");\n            for i in 0..batch_size {\n                if i > 0 {\n                    values.push(',');\n                }\n                values.push_str(&format!(\"({}, '{}')\", i, format_args!(\"value_{i}\")));\n            }\n            let mut stmt = limbo_conn.prepare(&values).unwrap();\n            b.iter(|| {\n                loop {\n                    match stmt.step().unwrap() {\n                        turso_core::StepResult::IO => {\n                            db.io.step().unwrap();\n                        }\n                        turso_core::StepResult::Done => {\n                            break;\n                        }\n                        turso_core::StepResult::Row => {\n                            unreachable!();\n                        }\n                        turso_core::StepResult::Interrupt | turso_core::StepResult::Busy => {\n                            unreachable!();\n                        }\n                    }\n                }\n                stmt.reset().unwrap();\n            });\n        });\n\n        if enable_rusqlite {\n            let temp_dir = tempfile::tempdir().unwrap();\n            let db_path = temp_dir.path().join(\"bench.db\");\n            let sqlite_conn = rusqlite::Connection::open(db_path).unwrap();\n            sqlite_conn\n                .pragma_update(None, \"synchronous\", \"FULL\")\n                .unwrap();\n            sqlite_conn\n                .pragma_update(None, \"journal_mode\", \"WAL\")\n                .unwrap();\n            sqlite_conn\n                .pragma_update(None, \"locking_mode\", \"EXCLUSIVE\")\n                .unwrap();\n            let journal_mode = sqlite_conn\n                .pragma_query_value(None, \"journal_mode\", |row| row.get::<_, String>(0))\n                .unwrap();\n            assert_eq!(journal_mode.to_lowercase(), \"wal\");\n            let synchronous = sqlite_conn\n                .pragma_query_value(None, \"synchronous\", |row| row.get::<_, usize>(0))\n                .unwrap();\n            const FULL: usize = 2;\n            assert_eq!(synchronous, FULL);\n\n            // Create test table\n            sqlite_conn\n                .execute(\"CREATE TABLE test (id INTEGER, value TEXT)\", [])\n                .unwrap();\n            sqlite_conn\n                .pragma_update(None, \"locking_mode\", \"EXCLUSIVE\")\n                .unwrap();\n\n            group.bench_function(format!(\"sqlite_insert_{batch_size}_rows\"), |b| {\n                let mut values = String::from(\"INSERT INTO test VALUES \");\n                for i in 0..batch_size {\n                    if i > 0 {\n                        values.push(',');\n                    }\n                    values.push_str(&format!(\"({}, '{}')\", i, format_args!(\"value_{i}\")));\n                }\n                let mut stmt = sqlite_conn.prepare(&values).unwrap();\n                b.iter(|| {\n                    let mut rows = stmt.raw_query();\n                    while let Some(row) = rows.next().unwrap() {\n                        black_box(row);\n                    }\n                });\n            });\n        }\n    }\n\n    group.finish();\n}\n\n#[inline(never)]\nfn bench_limbo(\n    mvcc: bool,\n    num_connections: i64,\n    num_batch_inserts: i64,\n    num_inserts_per_batch: usize,\n) {\n    struct ConnectionState {\n        conn: Arc<turso_core::Connection>,\n        inserts: Vec<String>,\n        current_statement: Option<turso_core::Statement>,\n    }\n    #[allow(clippy::arc_with_non_send_sync)]\n    let io = Arc::new(PlatformIO::new().unwrap());\n    let temp_dir = tempfile::tempdir().unwrap();\n    let path = temp_dir.path().join(\"bench.db\");\n    let db = Database::open_file(io, path.to_str().unwrap()).unwrap();\n    let mut connecitons = Vec::new();\n    {\n        let conn = db.connect().unwrap();\n        if mvcc {\n            conn.execute(\"PRAGMA journal_mode = 'mvcc'\").unwrap();\n        }\n        conn.execute(\"CREATE TABLE test (x)\").unwrap();\n        conn.close().unwrap();\n    }\n    let inserts =\n        generate_inserts_per_connection(num_connections, num_batch_inserts, num_inserts_per_batch);\n    for i in 0..num_connections {\n        let conn = db.connect().unwrap();\n        let inserts = inserts[i as usize].clone();\n        connecitons.push(ConnectionState {\n            conn,\n            inserts,\n            current_statement: None,\n        });\n    }\n    loop {\n        let mut all_finished = true;\n        for conn in &mut connecitons {\n            if !conn.inserts.is_empty() || conn.current_statement.is_some() {\n                all_finished = false;\n                break;\n            }\n        }\n        for conn in connecitons.iter_mut() {\n            if conn.current_statement.is_none() && !conn.inserts.is_empty() {\n                let write = conn.inserts.pop().unwrap();\n                conn.current_statement = Some(conn.conn.prepare(&write).unwrap());\n            }\n            if conn.current_statement.is_none() {\n                continue;\n            }\n            let stmt = conn.current_statement.as_mut().unwrap();\n            match stmt.step().unwrap() {\n                // These you be only possible cases in write concurrency.\n                // No rows because insert doesn't return\n                // No interrupt because insert doesn't interrupt\n                // No busy because insert in mvcc should be multi concurrent write\n                StepResult::Done => {\n                    conn.current_statement = None;\n                }\n                StepResult::IO => {\n                    // let's skip doing I/O here, we want to perform io only after all the statements are stepped\n                }\n                StepResult::Busy => {\n                    // We need to restart statement\n                    if mvcc {\n                        unreachable!();\n                    }\n                    stmt.reset().unwrap();\n                }\n                _ => {\n                    unreachable!()\n                }\n            }\n        }\n        db.io.step().unwrap();\n\n        if all_finished {\n            break;\n        }\n    }\n}\n\n#[inline(never)]\nfn bench_limbo_mvcc(\n    mvcc: bool,\n    num_connections: i64,\n    num_batch_inserts: i64,\n    num_inserts_per_batch: usize,\n) {\n    struct ConnectionState {\n        conn: Arc<turso_core::Connection>,\n        inserts: Vec<String>,\n        current_statement: Option<turso_core::Statement>,\n        current_insert: Option<String>,\n    }\n    #[allow(clippy::arc_with_non_send_sync)]\n    let io = Arc::new(PlatformIO::new().unwrap());\n    let temp_dir = tempfile::tempdir().unwrap();\n    let path = temp_dir.path().join(\"bench.db\");\n    let db = Database::open_file(io, path.to_str().unwrap()).unwrap();\n    let mut connecitons = Vec::new();\n    let conn0 = db.connect().unwrap();\n    if mvcc {\n        conn0.execute(\"PRAGMA journal_mode = 'mvcc'\").unwrap();\n    }\n    conn0.execute(\"CREATE TABLE test (x)\").unwrap();\n\n    let inserts =\n        generate_inserts_per_connection(num_connections, num_batch_inserts, num_inserts_per_batch);\n    for i in 0..num_connections {\n        let conn = db.connect().unwrap();\n        let inserts = inserts[i as usize].clone();\n        connecitons.push(ConnectionState {\n            conn,\n            inserts,\n            current_statement: None,\n            current_insert: None,\n        });\n    }\n    loop {\n        let all_finished = connecitons\n            .iter()\n            .all(|conn| conn.inserts.is_empty() && conn.current_statement.is_none());\n        for conn in connecitons.iter_mut() {\n            if conn.current_statement.is_none() && !conn.inserts.is_empty() {\n                let write = conn.inserts.pop().unwrap();\n                conn.conn.execute(\"BEGIN CONCURRENT\").unwrap();\n                conn.current_statement = Some(conn.conn.prepare(&write).unwrap());\n                conn.current_insert = Some(write);\n            }\n            if conn.current_statement.is_none() {\n                continue;\n            }\n            let stmt = conn.current_statement.as_mut().unwrap();\n            let is_commit = stmt.get_sql() == \"COMMIT\";\n            match stmt.step() {\n                // These you be only possible cases in write concurrency.\n                // No rows because insert doesn't return\n                // No interrupt because insert doesn't interrupt\n                // No busy because insert in mvcc should be multi concurrent write\n                Ok(StepResult::Done) => {\n                    if is_commit {\n                        // COMMIT finished, clear statement to start next transaction\n                        conn.current_statement = None;\n                        conn.current_insert = None;\n                    } else {\n                        // INSERT finished, now do commit\n                        conn.current_statement = Some(conn.conn.prepare(\"COMMIT\").unwrap());\n                    }\n                }\n                Ok(StepResult::IO) => {\n                    // let's skip doing I/O here, we want to perform io only after all the statements are stepped\n                }\n                Ok(StepResult::Busy) => {\n                    // We need to restart statement\n                    if mvcc {\n                        unreachable!();\n                    }\n                    println!(\"resetting statement\");\n                    stmt.reset().unwrap();\n                }\n                Err(err) => {\n                    if let LimboError::SchemaUpdated = err {\n                        conn.current_statement = Some(\n                            conn.conn\n                                .prepare(conn.current_insert.clone().as_ref().unwrap())\n                                .unwrap(),\n                        );\n                        continue;\n                    }\n                    panic!(\"unexpected error: {err:?}\");\n                }\n                _ => {\n                    unreachable!()\n                }\n            }\n        }\n        db.io.step().unwrap();\n\n        if all_finished {\n            break;\n        }\n    }\n}\n\nfn generate_inserts_per_connection(\n    num_connections: i64,\n    num_batch_inserts: i64,\n    num_inserts_per_batch: usize,\n) -> Vec<Vec<String>> {\n    let mut inserts = vec![];\n    for i in 0..num_connections {\n        let mut inserts_per_connection = vec![];\n        for j in 0..num_batch_inserts {\n            inserts_per_connection.push(generate_batch_insert(\n                num_batch_inserts * (i + j),\n                num_inserts_per_batch,\n            ));\n        }\n        inserts.push(inserts_per_connection);\n    }\n    inserts\n}\n\nfn generate_batch_insert(start: i64, num: usize) -> String {\n    let mut inserts = String::from(\"INSERT INTO test (x) VALUES \");\n    for i in 0..num {\n        inserts.push_str(&format!(\"({})\", start + i as i64));\n        if i < num - 1 {\n            inserts.push(',');\n        }\n    }\n    inserts\n}\n\nfn bench_concurrent_writes(criterion: &mut Criterion) {\n    let mut group = criterion.benchmark_group(\"Concurrent writes\");\n\n    let num_connections = 4;\n    let num_batch_inserts = 50;\n    let num_inserts_per_batch = 50_usize;\n\n    group.bench_function(\"limbo_wal_concurrent_writes\", |b| {\n        b.iter(|| {\n            bench_limbo(\n                false,\n                num_connections,\n                num_batch_inserts,\n                num_inserts_per_batch,\n            );\n        });\n    });\n    group.bench_function(\"limbo_mvcc_concurrent_writes\", |b| {\n        b.iter(|| {\n            bench_limbo_mvcc(\n                true,\n                num_connections,\n                num_batch_inserts,\n                num_inserts_per_batch,\n            );\n        });\n    });\n    group.bench_function(\"sqlite_concurrent_writes\", |b| {\n        let inserts = generate_inserts_per_connection(\n            num_connections,\n            num_batch_inserts,\n            num_inserts_per_batch,\n        );\n        b.iter(|| {\n            let temp_dir = tempfile::tempdir().unwrap();\n            let path = temp_dir.path().join(\"bench.db\");\n            {\n                let conn = rusqlite::Connection::open(path.to_str().unwrap()).unwrap();\n                conn.pragma_update(None, \"synchronous\", \"FULL\").unwrap();\n                conn.pragma_update(None, \"journal_mode\", \"WAL\").unwrap();\n                conn.pragma_update(None, \"locking_mode\", \"EXCLUSIVE\")\n                    .unwrap();\n                conn.execute(\"CREATE TABLE test (x INTEGER)\", []).unwrap();\n            }\n\n            for i in 0..num_connections {\n                let conn = rusqlite::Connection::open(path.to_str().unwrap()).unwrap();\n                for j in 0..num_batch_inserts {\n                    conn.execute(&inserts[i as usize][j as usize], []).unwrap();\n                }\n            }\n        });\n    });\n}\n\nfn bench_insert_randomblob(criterion: &mut Criterion) {\n    // The rusqlite benchmark crashes on Mac M1 when using the flamegraph features\n    let enable_rusqlite = std::env::var(\"DISABLE_RUSQLITE_BENCHMARK\").is_err();\n\n    let mut group = criterion.benchmark_group(\"Insert rows in batches\");\n\n    // Test different batch sizes\n    for batch_size in [1, 10, 100] {\n        let temp_dir = tempfile::tempdir().unwrap();\n        let db_path = temp_dir.path().join(\"bench.db\");\n\n        #[allow(clippy::arc_with_non_send_sync)]\n        let io = Arc::new(PlatformIO::new().unwrap());\n        let db = Database::open_file(io.clone(), db_path.to_str().unwrap()).unwrap();\n        let limbo_conn = db.connect().unwrap();\n\n        let mut stmt = limbo_conn.query(\"CREATE TABLE test(x)\").unwrap().unwrap();\n\n        loop {\n            match stmt.step().unwrap() {\n                turso_core::StepResult::IO => {\n                    db.io.step().unwrap();\n                }\n                turso_core::StepResult::Done => {\n                    break;\n                }\n                turso_core::StepResult::Row => {\n                    unreachable!();\n                }\n                turso_core::StepResult::Interrupt | turso_core::StepResult::Busy => {\n                    unreachable!();\n                }\n            }\n        }\n\n        let random_blob = format!(\n            \"INSERT INTO test select randomblob(1024 * 100) from generate_series(1, {batch_size});\"\n        );\n\n        group.bench_function(format!(\"limbo_insert_{batch_size}_randomblob\"), |b| {\n            let mut stmt = limbo_conn.prepare(&random_blob).unwrap();\n            b.iter(|| {\n                loop {\n                    match stmt.step().unwrap() {\n                        turso_core::StepResult::IO => {\n                            db.io.step().unwrap();\n                        }\n                        turso_core::StepResult::Done => {\n                            break;\n                        }\n                        turso_core::StepResult::Row => {\n                            unreachable!();\n                        }\n                        turso_core::StepResult::Interrupt | turso_core::StepResult::Busy => {\n                            unreachable!();\n                        }\n                    }\n                }\n                stmt.reset().unwrap();\n            });\n        });\n\n        if enable_rusqlite {\n            let temp_dir = tempfile::tempdir().unwrap();\n            let sqlite_conn = setup_rusqlite(&temp_dir, \"CREATE TABLE test(x)\");\n\n            group.bench_function(format!(\"sqlite_insert_{batch_size}_randomblob\"), |b| {\n                let mut stmt = sqlite_conn.prepare(&random_blob).unwrap();\n                b.iter(|| {\n                    let mut rows = stmt.raw_query();\n                    while let Some(row) = rows.next().unwrap() {\n                        black_box(row);\n                    }\n                });\n            });\n        }\n    }\n\n    group.finish();\n}\n\n#[cfg(not(feature = \"codspeed\"))]\ncriterion_group! {\n    name = benches;\n    config = Criterion::default().with_profiler(PProfProfiler::new(100, Output::Flamegraph(None)));\n    targets = bench_open, bench_alter, bench_prepare_query, bench_execute_select_1, bench_execute_select_rows, bench_execute_select_count, bench_insert_rows, bench_concurrent_writes, bench_insert_randomblob\n}\n\n#[cfg(feature = \"codspeed\")]\ncriterion_group! {\n    name = benches;\n    config = Criterion::default();\n    targets = bench_open, bench_alter, bench_prepare_query, bench_execute_select_1, bench_execute_select_rows, bench_execute_select_count, bench_insert_rows, bench_concurrent_writes, bench_insert_randomblob\n}\n\ncriterion_main!(benches);\n"
  },
  {
    "path": "core/benches/fts_benchmark.rs",
    "content": "//! FTS Query Performance Benchmarks\n//!\n//! Measures full-text search query performance including:\n//! - Cold query (first query after index creation, no cached directory)\n//! - Warm query (repeated queries with cached directory)\n//! - Insert + query lifecycle (write, commit, query)\n//!\n//! Run with: cargo bench --bench fts_benchmark --features fts\n\n#[cfg(not(feature = \"codspeed\"))]\nuse criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};\n#[cfg(not(feature = \"codspeed\"))]\nuse pprof::criterion::{Output, PProfProfiler};\n\n#[cfg(feature = \"codspeed\")]\nuse codspeed_criterion_compat::{criterion_group, criterion_main, BenchmarkId, Criterion};\n\nuse std::sync::Arc;\nuse tempfile::TempDir;\nuse turso_core::{Database, DatabaseOpts, OpenFlags, PlatformIO, StepResult};\n\n#[cfg(not(target_family = \"wasm\"))]\n#[global_allocator]\nstatic GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;\n\n/// Helper to execute a statement to completion, stepping through IO.\nfn run_to_completion(\n    stmt: &mut turso_core::Statement,\n    db: &Arc<Database>,\n) -> turso_core::Result<()> {\n    loop {\n        match stmt.step()? {\n            StepResult::IO => {\n                db.io.step()?;\n            }\n            StepResult::Done => break,\n            StepResult::Row => {}\n            StepResult::Interrupt | StepResult::Busy => {\n                panic!(\"Unexpected step result\");\n            }\n        }\n    }\n    Ok(())\n}\n\n/// Helper to step a statement and count result rows.\nfn run_and_count_rows(\n    stmt: &mut turso_core::Statement,\n    db: &Arc<Database>,\n) -> turso_core::Result<usize> {\n    let mut count = 0;\n    loop {\n        match stmt.step()? {\n            StepResult::IO => {\n                db.io.step()?;\n            }\n            StepResult::Done => break,\n            StepResult::Row => {\n                count += 1;\n            }\n            StepResult::Interrupt | StepResult::Busy => {\n                panic!(\"Unexpected step result\");\n            }\n        }\n    }\n    Ok(count)\n}\n\n/// Setup a database with an FTS-indexed table populated with `row_count` rows.\nfn setup_fts_db(temp_dir: &TempDir, row_count: usize) -> Arc<Database> {\n    let db_path = temp_dir.path().join(\"fts_bench.db\");\n    #[allow(clippy::arc_with_non_send_sync)]\n    let io = Arc::new(PlatformIO::new().unwrap());\n    let opts = DatabaseOpts::new().with_index_method(true);\n    let db = Database::open_file_with_flags(\n        io,\n        db_path.to_str().unwrap(),\n        OpenFlags::default(),\n        opts,\n        None,\n    )\n    .unwrap();\n    let conn = db.connect().unwrap();\n\n    // Create table and FTS index\n    conn.execute(\"CREATE TABLE docs (id INTEGER PRIMARY KEY, title TEXT, body TEXT)\")\n        .unwrap();\n    conn.execute(\"CREATE INDEX docs_fts ON docs USING fts (title, body)\")\n        .unwrap();\n\n    // Insert rows in batches of 500\n    let batch_size = 500;\n    for batch_start in (0..row_count).step_by(batch_size) {\n        let batch_end = (batch_start + batch_size).min(row_count);\n        let mut sql = String::from(\"INSERT INTO docs (id, title, body) VALUES \");\n        for i in batch_start..batch_end {\n            if i > batch_start {\n                sql.push(',');\n            }\n            // Vary content so term dictionaries have realistic distribution\n            let word_a = match i % 7 {\n                0 => \"database\",\n                1 => \"performance\",\n                2 => \"optimization\",\n                3 => \"benchmark\",\n                4 => \"storage\",\n                5 => \"indexing\",\n                _ => \"computing\",\n            };\n            let word_b = match i % 5 {\n                0 => \"systems\",\n                1 => \"analysis\",\n                2 => \"engineering\",\n                3 => \"architecture\",\n                _ => \"design\",\n            };\n            sql.push_str(&format!(\n                \"({i}, '{word_a} document {i}', 'This is the body of document {i} about {word_a} and {word_b} with additional text for realistic content size')\"\n            ));\n        }\n        conn.execute(&sql).unwrap();\n    }\n\n    db\n}\n\n/// Benchmark: Cold FTS query (no cached directory — measures full loading pipeline)\n///\n/// This measures the worst-case: open_read must scan the BTree catalog,\n/// load hot files, create the Tantivy Index, build a Reader+Searcher,\n/// parse the query, and execute the search. Each iteration uses a fresh\n/// connection to avoid directory cache hits.\nfn bench_fts_cold_query(criterion: &mut Criterion) {\n    let mut group = criterion.benchmark_group(\"FTS Cold Query\");\n    group.sample_size(20); // Cold queries are slow; reduce samples\n\n    for row_count in [1000, 5000, 10000] {\n        let temp_dir = tempfile::tempdir().unwrap();\n        let db = setup_fts_db(&temp_dir, row_count);\n\n        group.bench_function(\n            BenchmarkId::new(\"cold_query\", format!(\"{row_count}_rows\")),\n            |b| {\n                b.iter_custom(|iters| {\n                    let mut total = std::time::Duration::ZERO;\n                    for _ in 0..iters {\n                        // Fresh connection = no cached directory\n                        let conn = db.connect().unwrap();\n                        let start = std::time::Instant::now();\n                        let mut stmt = conn\n                            .query(\n                                \"SELECT id, title FROM docs WHERE (title, body) MATCH 'database'\",\n                            )\n                            .unwrap()\n                            .unwrap();\n                        let _rows = run_and_count_rows(&mut stmt, &db).unwrap();\n                        total += start.elapsed();\n                    }\n                    total\n                });\n            },\n        );\n    }\n\n    group.finish();\n}\n\n/// Benchmark: Warm FTS query (cached directory — measures query-only path)\n///\n/// After the first query loads and caches the directory, subsequent queries\n/// skip the catalog scan and PreloadingEssentials entirely. This measures\n/// the pure query execution path: Index::open (from cached directory),\n/// Reader+Searcher creation, query parsing, and search.\nfn bench_fts_warm_query(criterion: &mut Criterion) {\n    let mut group = criterion.benchmark_group(\"FTS Warm Query\");\n\n    for row_count in [1000, 5000, 10000] {\n        let temp_dir = tempfile::tempdir().unwrap();\n        let db = setup_fts_db(&temp_dir, row_count);\n        let conn = db.connect().unwrap();\n\n        // Warm up: run one query to populate the directory cache\n        let mut stmt = conn\n            .query(\"SELECT id FROM docs WHERE (title, body) MATCH 'database'\")\n            .unwrap()\n            .unwrap();\n        run_to_completion(&mut stmt, &db).unwrap();\n\n        group.bench_function(\n            BenchmarkId::new(\"warm_query\", format!(\"{row_count}_rows\")),\n            |b| {\n                b.iter_custom(|iters| {\n                    let mut total = std::time::Duration::ZERO;\n                    for _ in 0..iters {\n                        let start = std::time::Instant::now();\n                        let mut stmt = conn\n                            .query(\n                                \"SELECT id, title FROM docs WHERE (title, body) MATCH 'database'\",\n                            )\n                            .unwrap()\n                            .unwrap();\n                        let _rows = run_and_count_rows(&mut stmt, &db).unwrap();\n                        total += start.elapsed();\n                    }\n                    total\n                });\n            },\n        );\n    }\n\n    group.finish();\n}\n\n/// Benchmark: FTS query with different search selectivity\n///\n/// Measures how the number of matching documents affects query time.\n/// \"database\" matches ~1/7 of docs, \"performance\" matches ~1/7,\n/// \"database performance\" (AND) matches fewer.\nfn bench_fts_query_selectivity(criterion: &mut Criterion) {\n    let mut group = criterion.benchmark_group(\"FTS Query Selectivity\");\n\n    let row_count = 10000;\n    let temp_dir = tempfile::tempdir().unwrap();\n    let db = setup_fts_db(&temp_dir, row_count);\n    let conn = db.connect().unwrap();\n\n    // Warm up\n    let mut stmt = conn\n        .query(\"SELECT id FROM docs WHERE (title, body) MATCH 'database'\")\n        .unwrap()\n        .unwrap();\n    run_to_completion(&mut stmt, &db).unwrap();\n\n    let queries = [\n        (\"single_common_term\", \"database\"),\n        (\"single_uncommon_term\", \"optimization\"),\n        (\"two_term_and\", \"database engineering\"),\n        (\"phrase_query\", \"\\\"database document\\\"\"),\n    ];\n\n    for (name, query_term) in queries {\n        let sql = format!(\"SELECT id, title FROM docs WHERE (title, body) MATCH '{query_term}'\");\n\n        group.bench_function(BenchmarkId::new(\"selectivity\", name), |b| {\n            b.iter_custom(|iters| {\n                let mut total = std::time::Duration::ZERO;\n                for _ in 0..iters {\n                    let start = std::time::Instant::now();\n                    let mut stmt = conn.query(&sql).unwrap().unwrap();\n                    let _rows = run_and_count_rows(&mut stmt, &db).unwrap();\n                    total += start.elapsed();\n                }\n                total\n            });\n        });\n    }\n\n    group.finish();\n}\n\n/// Benchmark: Insert + query lifecycle\n///\n/// Measures the cost of inserting new rows, committing, and then querying.\n/// This exercises the full write path (IndexWriter, segment creation, BTree flush)\n/// followed by directory cache invalidation and a cold re-query.\nfn bench_fts_insert_then_query(criterion: &mut Criterion) {\n    let mut group = criterion.benchmark_group(\"FTS Insert+Query Lifecycle\");\n    group.sample_size(20);\n\n    for row_count in [1000, 5000] {\n        let temp_dir = tempfile::tempdir().unwrap();\n        let db = setup_fts_db(&temp_dir, row_count);\n        let conn = db.connect().unwrap();\n\n        // Use a shared counter that persists across warmup + sampling invocations\n        let counter = std::cell::Cell::new(row_count + 1_000_000);\n\n        group.bench_function(\n            BenchmarkId::new(\"insert_query\", format!(\"{row_count}_rows\")),\n            |b| {\n                b.iter_custom(|iters| {\n                    let mut total = std::time::Duration::ZERO;\n                    for _ in 0..iters {\n                        let start = std::time::Instant::now();\n\n                        // Insert 10 new rows (use rowid=NULL to auto-assign)\n                        let c = counter.get();\n                        let mut sql = String::from(\"INSERT INTO docs (id, title, body) VALUES \");\n                        for j in 0..10 {\n                            if j > 0 {\n                                sql.push(',');\n                            }\n                            let id = c + j;\n                            sql.push_str(&format!(\n                                \"({id}, 'new document {id}', 'freshly inserted content about database systems')\"\n                            ));\n                        }\n                        counter.set(c + 10);\n                        conn.execute(&sql).unwrap();\n\n                        // Query (exercises cache invalidation + re-query)\n                        let mut stmt = conn\n                            .query(\n                                \"SELECT id, title FROM docs WHERE (title, body) MATCH 'database'\",\n                            )\n                            .unwrap()\n                            .unwrap();\n                        let _rows = run_and_count_rows(&mut stmt, &db).unwrap();\n\n                        total += start.elapsed();\n                    }\n                    total\n                });\n            },\n        );\n    }\n\n    group.finish();\n}\n\n#[cfg(not(feature = \"codspeed\"))]\ncriterion_group! {\n    name = fts_benches;\n    config = Criterion::default()\n        .with_profiler(PProfProfiler::new(100, Output::Flamegraph(None)))\n        .sample_size(50);\n    targets = bench_fts_cold_query, bench_fts_warm_query, bench_fts_query_selectivity, bench_fts_insert_then_query\n}\n\n#[cfg(feature = \"codspeed\")]\ncriterion_group! {\n    name = fts_benches;\n    config = Criterion::default().sample_size(50);\n    targets = bench_fts_cold_query, bench_fts_warm_query, bench_fts_query_selectivity, bench_fts_insert_then_query\n}\n\ncriterion_main!(fts_benches);\n"
  },
  {
    "path": "core/benches/graph_queries_benchmark.rs",
    "content": "use std::sync::Arc;\n\n#[cfg(not(feature = \"codspeed\"))]\nuse criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, SamplingMode};\n#[cfg(not(feature = \"codspeed\"))]\nuse pprof::criterion::{Output, PProfProfiler};\n\n#[cfg(feature = \"codspeed\")]\nuse codspeed_criterion_compat::{\n    black_box, criterion_group, criterion_main, BenchmarkId, Criterion, SamplingMode,\n};\n\nuse turso_core::{Database, PlatformIO};\n\nconst DB_PATH: &str = \"../perf/graph-queries/graph-queries.db\";\nconst DB_PATH_ANALYZED: &str = \"../perf/graph-queries/graph-queries-analyzed.db\";\n\nmacro_rules! gq_query {\n    ($name:literal) => {\n        (\n            $name,\n            include_str!(concat!(\"../../perf/graph-queries/queries/\", $name, \".sql\")),\n        )\n    };\n}\n\nfn rusqlite_open(path: &str) -> rusqlite::Connection {\n    let conn = rusqlite::Connection::open(path).unwrap();\n    conn.pragma_update(None, \"locking_mode\", \"EXCLUSIVE\")\n        .unwrap();\n    conn\n}\n\nfn bench_graph_queries(criterion: &mut Criterion) {\n    let enable_rusqlite = std::env::var(\"DISABLE_RUSQLITE_BENCHMARK\").is_err();\n\n    #[allow(clippy::arc_with_non_send_sync)]\n    let io = Arc::new(PlatformIO::new().unwrap());\n    let db = Database::open_file(io.clone(), DB_PATH).unwrap();\n    let limbo_conn = db.connect().unwrap();\n\n    let db_analyzed = Database::open_file(io, DB_PATH_ANALYZED).unwrap();\n    let limbo_conn_analyzed = db_analyzed.connect().unwrap();\n\n    let queries = [\n        gq_query!(\"a_cooccurrence\"),\n        gq_query!(\"b_or_join\"),\n        gq_query!(\"c_edge_counts\"),\n        gq_query!(\"d_inlist_union\"),\n        gq_query!(\"e_activity_agg\"),\n        gq_query!(\"f1_streak_current\"),\n        gq_query!(\"f2_streak_longest\"),\n        gq_query!(\"3_aggregate_or_in\"),\n    ];\n\n    for (name, query) in queries.iter() {\n        let mut group = criterion.benchmark_group(format!(\"GraphQuery `{name}`\"));\n        group.sampling_mode(SamplingMode::Flat);\n        group.sample_size(10);\n\n        group.bench_with_input(BenchmarkId::new(\"limbo\", name), query, |b, query| {\n            let mut stmt = limbo_conn.prepare(query).unwrap();\n            b.iter(|| {\n                loop {\n                    match stmt.step().unwrap() {\n                        turso_core::StepResult::Row => {\n                            black_box(stmt.row());\n                        }\n                        turso_core::StepResult::IO => {\n                            db.io.step().unwrap();\n                        }\n                        turso_core::StepResult::Done => {\n                            break;\n                        }\n                        turso_core::StepResult::Interrupt | turso_core::StepResult::Busy => {\n                            unreachable!();\n                        }\n                    }\n                }\n                stmt.reset().unwrap();\n            });\n        });\n\n        group.bench_with_input(\n            BenchmarkId::new(\"limbo_analyzed\", name),\n            query,\n            |b, query| {\n                let mut stmt = limbo_conn_analyzed.prepare(query).unwrap();\n                b.iter(|| {\n                    loop {\n                        match stmt.step().unwrap() {\n                            turso_core::StepResult::Row => {\n                                black_box(stmt.row());\n                            }\n                            turso_core::StepResult::IO => {\n                                db_analyzed.io.step().unwrap();\n                            }\n                            turso_core::StepResult::Done => {\n                                break;\n                            }\n                            turso_core::StepResult::Interrupt | turso_core::StepResult::Busy => {\n                                unreachable!();\n                            }\n                        }\n                    }\n                    stmt.reset().unwrap();\n                });\n            },\n        );\n\n        if enable_rusqlite {\n            let sqlite_conn = rusqlite_open(DB_PATH);\n\n            group.bench_with_input(BenchmarkId::new(\"sqlite\", name), query, |b, query| {\n                let mut stmt = sqlite_conn.prepare(query).unwrap();\n                b.iter(|| {\n                    let mut rows = stmt.raw_query();\n                    while let Some(row) = rows.next().unwrap() {\n                        black_box(row);\n                    }\n                });\n            });\n\n            let sqlite_conn_analyzed = rusqlite_open(DB_PATH_ANALYZED);\n\n            group.bench_with_input(\n                BenchmarkId::new(\"sqlite_analyzed\", name),\n                query,\n                |b, query| {\n                    let mut stmt = sqlite_conn_analyzed.prepare(query).unwrap();\n                    b.iter(|| {\n                        let mut rows = stmt.raw_query();\n                        while let Some(row) = rows.next().unwrap() {\n                            black_box(row);\n                        }\n                    });\n                },\n            );\n        }\n\n        group.finish();\n    }\n}\n\n#[cfg(not(feature = \"codspeed\"))]\ncriterion_group! {\n    name = benches;\n    config = Criterion::default().with_profiler(PProfProfiler::new(100, Output::Flamegraph(None)));\n    targets = bench_graph_queries\n}\n\n#[cfg(feature = \"codspeed\")]\ncriterion_group! {\n    name = benches;\n    config = Criterion::default();\n    targets = bench_graph_queries\n}\n\ncriterion_main!(benches);\n"
  },
  {
    "path": "core/benches/hash_spill_benchmark.rs",
    "content": "#[cfg(not(feature = \"codspeed\"))]\nuse criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};\n#[cfg(not(feature = \"codspeed\"))]\nuse pprof::criterion::{Output, PProfProfiler};\n\n#[cfg(feature = \"codspeed\")]\nuse codspeed_criterion_compat::{\n    black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput,\n};\n\nuse std::sync::Arc;\nuse turso_core::types::Value;\nuse turso_core::vdbe::hash_table::{HashTable, HashTableConfig};\nuse turso_core::vdbe::CollationSeq;\nuse turso_core::{IOResult, MemoryIO, Numeric};\n\n#[cfg(not(target_family = \"wasm\"))]\n#[global_allocator]\nstatic GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;\n\n/// Create a hash table with the given memory budget\nfn create_hash_table(mem_budget: usize) -> HashTable {\n    let io = Arc::new(MemoryIO::new());\n    let config = HashTableConfig {\n        initial_buckets: 64,\n        mem_budget,\n        num_keys: 1,\n        collations: vec![CollationSeq::Binary],\n        temp_store: turso_core::TempStore::Default,\n        track_matched: false,\n        partition_count: None,\n    };\n    HashTable::new(config, io)\n}\n\n/// Insert entries with integer keys and no payload\nfn insert_integer_entries(ht: &mut HashTable, count: usize) {\n    for i in 0..count {\n        let key = vec![Value::from_i64(i as i64)];\n        let _ = ht.insert(key, i as i64, vec![], None);\n    }\n}\n\n/// Insert entries with integer keys and text payload\nfn insert_entries_with_text_payload(ht: &mut HashTable, count: usize, text_size: usize) {\n    let payload_text: String = \"x\".repeat(text_size);\n    for i in 0..count {\n        let key = vec![Value::from_i64(i as i64)];\n        let payload = vec![Value::Text(payload_text.clone().into())];\n        let _ = ht.insert(key, i as i64, payload, None);\n    }\n}\n\n/// Insert entries with text keys (for NOCASE hash testing)\nfn insert_text_key_entries(ht: &mut HashTable, count: usize) {\n    for i in 0..count {\n        let key = vec![Value::Text(format!(\"key_{i}\").into())];\n        let _ = ht.insert(key, i as i64, vec![], None);\n    }\n}\n\n/// Benchmark: Build phase with tight memory budget (forces frequent spilling)\nfn bench_build_tight_budget(c: &mut Criterion) {\n    let mut group = c.benchmark_group(\"HashTable Build (Tight Budget)\");\n\n    for count in [1000, 5000, 10000] {\n        group.throughput(Throughput::Elements(count as u64));\n\n        // 32KB budget - will spill frequently\n        group.bench_with_input(\n            BenchmarkId::new(\"integer_keys\", count),\n            &count,\n            |b, &count| {\n                b.iter(|| {\n                    let mut ht = create_hash_table(32 * 1024);\n                    insert_integer_entries(&mut ht, count);\n                    let _ = ht.finalize_build(None);\n                    black_box(ht.has_spilled())\n                });\n            },\n        );\n\n        // With 100-byte text payload per entry\n        group.bench_with_input(\n            BenchmarkId::new(\"with_100b_payload\", count),\n            &count,\n            |b, &count| {\n                b.iter(|| {\n                    let mut ht = create_hash_table(32 * 1024);\n                    insert_entries_with_text_payload(&mut ht, count, 100);\n                    let _ = ht.finalize_build(None);\n                    black_box(ht.has_spilled())\n                });\n            },\n        );\n    }\n\n    group.finish();\n}\n\n/// Benchmark: Build phase with relaxed memory budget (occasional spilling)\nfn bench_build_relaxed_budget(c: &mut Criterion) {\n    let mut group = c.benchmark_group(\"HashTable Build (Relaxed Budget)\");\n\n    for count in [1000, 5000, 10000] {\n        group.throughput(Throughput::Elements(count as u64));\n\n        // 256KB budget - will spill less frequently\n        group.bench_with_input(\n            BenchmarkId::new(\"integer_keys\", count),\n            &count,\n            |b, &count| {\n                b.iter(|| {\n                    let mut ht = create_hash_table(256 * 1024);\n                    insert_integer_entries(&mut ht, count);\n                    let _ = ht.finalize_build(None);\n                    black_box(ht.has_spilled())\n                });\n            },\n        );\n\n        // With 100-byte text payload\n        group.bench_with_input(\n            BenchmarkId::new(\"with_100b_payload\", count),\n            &count,\n            |b, &count| {\n                b.iter(|| {\n                    let mut ht = create_hash_table(256 * 1024);\n                    insert_entries_with_text_payload(&mut ht, count, 100);\n                    let _ = ht.finalize_build(None);\n                    black_box(ht.has_spilled())\n                });\n            },\n        );\n    }\n\n    group.finish();\n}\n\n/// Benchmark: Build + Probe with spilling\nfn bench_build_and_probe(c: &mut Criterion) {\n    let mut group = c.benchmark_group(\"HashTable Build+Probe\");\n\n    for count in [1000, 5000] {\n        group.throughput(Throughput::Elements(count as u64 * 2)); // build + probe\n\n        group.bench_with_input(\n            BenchmarkId::new(\"tight_budget\", count),\n            &count,\n            |b, &count| {\n                b.iter(|| {\n                    // Build phase\n                    let mut ht = create_hash_table(32 * 1024);\n                    insert_integer_entries(&mut ht, count);\n                    let _ = ht.finalize_build(None);\n\n                    // Probe phase - look up every key\n                    let mut found = 0;\n                    for i in 0..count {\n                        let key = vec![Value::Numeric(Numeric::Integer(i as i64))];\n                        if ht.has_spilled() {\n                            let partition_idx = ht.partition_for_keys(&key);\n                            if !ht.is_partition_loaded(partition_idx) {\n                                while let Ok(IOResult::IO(_)) =\n                                    ht.load_spilled_partition(partition_idx, None)\n                                {\n                                }\n                            }\n                            if ht.probe_partition(partition_idx, &key, None).is_some() {\n                                found += 1;\n                            }\n                        } else if ht.probe(key, None).is_some() {\n                            found += 1;\n                        }\n                    }\n                    black_box(found)\n                });\n            },\n        );\n\n        group.bench_with_input(\n            BenchmarkId::new(\"relaxed_budget\", count),\n            &count,\n            |b, &count| {\n                b.iter(|| {\n                    // Build phase\n                    let mut ht = create_hash_table(256 * 1024);\n                    insert_integer_entries(&mut ht, count);\n                    let _ = ht.finalize_build(None);\n\n                    // Probe phase\n                    let mut found = 0;\n                    for i in 0..count {\n                        let key = vec![Value::from_i64(i as i64)];\n                        if ht.has_spilled() {\n                            let partition_idx = ht.partition_for_keys(&key);\n                            if !ht.is_partition_loaded(partition_idx) {\n                                while let Ok(IOResult::IO(_)) =\n                                    ht.load_spilled_partition(partition_idx, None)\n                                {\n                                }\n                            }\n                            if ht.probe_partition(partition_idx, &key, None).is_some() {\n                                found += 1;\n                            }\n                        } else if ht.probe(key, None).is_some() {\n                            found += 1;\n                        }\n                    }\n                    black_box(found)\n                });\n            },\n        );\n    }\n\n    group.finish();\n}\n\n/// Benchmark: Text key hashing (tests NOCASE optimization)\nfn bench_text_key_hashing(c: &mut Criterion) {\n    let mut group = c.benchmark_group(\"HashTable Text Keys\");\n\n    for count in [1000, 5000] {\n        group.throughput(Throughput::Elements(count as u64));\n\n        // Binary collation\n        group.bench_with_input(\n            BenchmarkId::new(\"binary_collation\", count),\n            &count,\n            |b, &count| {\n                b.iter(|| {\n                    let io = Arc::new(MemoryIO::new());\n                    let config = HashTableConfig {\n                        initial_buckets: 64,\n                        mem_budget: 64 * 1024,\n                        num_keys: 1,\n                        collations: vec![CollationSeq::Binary],\n                        temp_store: turso_core::TempStore::Default,\n                        track_matched: false,\n                        partition_count: None,\n                    };\n                    let mut ht = HashTable::new(config, io);\n                    insert_text_key_entries(&mut ht, count);\n                    let _ = ht.finalize_build(None);\n                    black_box(ht.has_spilled())\n                });\n            },\n        );\n\n        // NOCASE collation (tests allocation-free hash optimization)\n        group.bench_with_input(\n            BenchmarkId::new(\"nocase_collation\", count),\n            &count,\n            |b, &count| {\n                b.iter(|| {\n                    let io = Arc::new(MemoryIO::new());\n                    let config = HashTableConfig {\n                        initial_buckets: 64,\n                        mem_budget: 64 * 1024,\n                        num_keys: 1,\n                        collations: vec![CollationSeq::NoCase],\n                        temp_store: turso_core::TempStore::Default,\n                        track_matched: false,\n                        partition_count: None,\n                    };\n                    let mut ht = HashTable::new(config, io);\n                    insert_text_key_entries(&mut ht, count);\n                    let _ = ht.finalize_build(None);\n                    black_box(ht.has_spilled())\n                });\n            },\n        );\n    }\n\n    group.finish();\n}\n\n/// Benchmark: Large payload serialization\nfn bench_large_payload_spill(c: &mut Criterion) {\n    let mut group = c.benchmark_group(\"HashTable Large Payload Spill\");\n\n    for payload_size in [100, 500, 1000] {\n        let count = 1000;\n        group.throughput(Throughput::Bytes((count * payload_size) as u64));\n\n        group.bench_with_input(\n            BenchmarkId::new(\"payload_bytes\", payload_size),\n            &payload_size,\n            |b, &payload_size| {\n                b.iter(|| {\n                    let mut ht = create_hash_table(32 * 1024); // Tight budget to force spilling\n                    insert_entries_with_text_payload(&mut ht, count, payload_size);\n                    let _ = ht.finalize_build(None);\n                    black_box(ht.has_spilled())\n                });\n            },\n        );\n    }\n\n    group.finish();\n}\n\n#[cfg(not(feature = \"codspeed\"))]\ncriterion_group! {\n    name = benches;\n    config = Criterion::default().with_profiler(PProfProfiler::new(100, Output::Flamegraph(None)));\n    targets = bench_build_tight_budget, bench_build_relaxed_budget, bench_build_and_probe, bench_text_key_hashing, bench_large_payload_spill\n}\n\n#[cfg(feature = \"codspeed\")]\ncriterion_group! {\n    name = benches;\n    config = Criterion::default();\n    targets = bench_build_tight_budget, bench_build_relaxed_budget, bench_build_and_probe, bench_text_key_hashing, bench_large_payload_spill\n}\n\ncriterion_main!(benches);\n"
  },
  {
    "path": "core/benches/json_benchmark.rs",
    "content": "#[cfg(not(feature = \"codspeed\"))]\nuse criterion::{black_box, criterion_group, criterion_main, Criterion};\n#[cfg(not(feature = \"codspeed\"))]\nuse pprof::{\n    criterion::{Output, PProfProfiler},\n    flamegraph::Options,\n};\n\n#[cfg(feature = \"codspeed\")]\nuse codspeed_criterion_compat::{black_box, criterion_group, criterion_main, Criterion};\nuse std::sync::Arc;\nuse turso_core::{Database, PlatformIO};\n\n// Title: JSONB Function Benchmarking\n\nfn rusqlite_open() -> rusqlite::Connection {\n    let sqlite_conn = rusqlite::Connection::open(\"../testing/system/testing.db\").unwrap();\n    sqlite_conn\n        .pragma_update(None, \"locking_mode\", \"EXCLUSIVE\")\n        .unwrap();\n    sqlite_conn\n}\n\nfn bench(criterion: &mut Criterion) {\n    // Flag to disable rusqlite benchmarks if needed\n    let enable_rusqlite = std::env::var(\"DISABLE_RUSQLITE_BENCHMARK\").is_err();\n\n    #[allow(clippy::arc_with_non_send_sync)]\n    let io = Arc::new(PlatformIO::new().unwrap());\n    let db = Database::open_file(io, \"../testing/system/testing.db\").unwrap();\n    let limbo_conn = db.connect().unwrap();\n\n    // Benchmark JSONB with different payload sizes\n    let json_sizes = [\n        (\"Small\", r#\"{\"id\": 1, \"name\": \"Test\"}\"#),\n        (\n            \"Medium\",\n            r#\"{\"id\": 1, \"name\": \"Test\", \"attributes\": {\"color\": \"blue\", \"size\": \"medium\", \"tags\": [\"tag1\", \"tag2\", \"tag3\"]}}\"#,\n        ),\n        (\n            \"Large\",\n            r#\"[{\"metadata\":{\"title\":\"Standard JSON Test File\",\"description\":\"A complex JSON file for testing parsers and serializers (Standard JSON only)\",\"version\":\"1.0.0\",\"generated\":\"2025-03-12T12:00:00Z\",\"author\":\"Claude AI\"},\"primitives\":{\"null_value\":null,\"boolean_values\":{\"true_value\":true,\"false_value\":false},\"number_values\":{\"integer\":42,\"negative\":-273,\"zero\":0,\"large_integer\":9007199254740991,\"small_integer\":-9007199254740991,\"decimal\":3.14159265358979,\"negative_decimal\":-2.71828,\"exponent_positive\":6.022e+23,\"exponent_negative\":1.602e-19},\"string_values\":{\"empty\":\"\",\"simple\":\"Hello, world!\",\"unicode\":\"你好，世界！😀🌍🚀\",\"quotes\":\"She said \\\"Hello!\\\" to me.\",\"backslash\":\"C:\\\\Program Files\\\\App\\\\\",\"controls\":\"Line1\\nLine2\\tTabbed\\rCarriage\\bBackspace\\fForm-feed\",\"unicode_escapes\":\"Copyright: ©, Emoji: 😀\",\"all_escapes\":\"\\\\b\\\\f\\\\n\\\\r\\\\t\\\\\\\"\\\\\\\\\"}},\"arrays\":{\"empty_array\":[],\"homogeneous\":[1,2,3,4,5,6,7,8,9,10],\"heterogeneous\":[null,true,42,\"string\",{\"key\":\"value\"},[1,2,3]],\"nested\":[[1,2,3],[4,5,6],[7,8,9]],\"deep\":[[[[[[[[[[\"Very deep\"]]]]]]]]]],\"large\":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99]},\"objects\":{\"empty_object\":{},\"simple_object\":{\"key1\":\"value1\",\"key2\":\"value2\"},\"nested_object\":{\"level1\":{\"level2\":{\"level3\":{\"level4\":{\"level5\":\"Deep nesting\"}}}}},\"complex_keys\":{\"simple\":\"value\",\"with spaces\":\"value\",\"with-dash\":\"value\",\"with_underscore\":\"value\",\"with.dot\":\"value\",\"with:colon\":\"value\",\"with@symbol\":\"value\",\"withUnicode\":\"value\",\"withEmoji\":\"value\",\"withQuotes\":\"value\",\"withBackslashes\":\"value\"}},\"edge_cases\":{\"zero_byte_string\":\"\",\"one_byte_string\":\"x\",\"long_string\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\",\"almost_too_deep\":{\"a\":{\"b\":{\"c\":{\"d\":{\"e\":{\"f\":{\"g\":{\"h\":{\"i\":{\"j\":{\"k\":{\"l\":{\"m\":{\"n\":{\"o\":{\"p\":{\"q\":{\"r\":{\"s\":{\"t\":{\"u\":{\"v\":{\"w\":{\"x\":{\"y\":{\"z\":\"Deep nesting test\"}}}}}}}}}}}}}}}}}}}}}}}}}}},\"many_properties\":{\"prop01\":1,\"prop02\":2,\"prop03\":3,\"prop04\":4,\"prop05\":5,\"prop06\":6,\"prop07\":7,\"prop08\":8,\"prop09\":9,\"prop10\":10,\"prop11\":11,\"prop12\":12,\"prop13\":13,\"prop14\":14,\"prop15\":15,\"prop16\":16,\"prop17\":17,\"prop18\":18,\"prop19\":19,\"prop20\":20,\"prop21\":21,\"prop22\":22,\"prop23\":23,\"prop24\":24,\"prop25\":25,\"prop26\":26,\"prop27\":27,\"prop28\":28,\"prop29\":29,\"prop30\":30,\"prop31\":31,\"prop32\":32,\"prop33\":33,\"prop34\":34,\"prop35\":35,\"prop36\":36,\"prop37\":37,\"prop38\":38,\"prop39\":39,\"prop40\":40,\"prop41\":41,\"prop42\":42,\"prop43\":43,\"prop44\":44,\"prop45\":45,\"prop46\":46,\"prop47\":47,\"prop48\":48,\"prop49\":49,\"prop50\":50}},{\"standard_features\":{\"numeric_literals\":{\"decimal_integer\":12345,\"negative_integer\":-12345,\"decimal_fraction\":123.45,\"negative_fraction\":-123.45,\"exponential_positive\":123400,\"exponential_negative\":0.00001234},\"string_escapes\":{\"quotation_mark\":\"Quote: \\\"Hello\\\"\",\"reverse_solidus\":\"Backslash: \\\\\",\"solidus\":\"Slash: / (optional escape)\",\"backspace\":\"Control: \\b\",\"formfeed\":\"Control: \\f\",\"newline\":\"Control: \\n\",\"carriage_return\":\"Control: \\r\",\"tab\":\"Control: \\t\",\"unicode\":\"Unicode: © € ☃\"}},\"generated_data\":{\"people\":[{\"id\":1,\"name\":\"John Smith\",\"email\":\"john.smith@example.com\",\"age\":42,\"address\":{\"street\":\"123 Main St\",\"city\":\"Anytown\",\"state\":\"CA\",\"zip\":\"12345\"},\"phone_numbers\":[{\"type\":\"home\",\"number\":\"555-1234\"},{\"type\":\"work\",\"number\":\"555-5678\"}],\"tags\":[\"employee\",\"manager\",\"developer\"],\"active\":true},{\"id\":2,\"name\":\"Jane Doe\",\"email\":\"jane.doe@example.com\",\"age\":36,\"address\":{\"street\":\"456 Elm St\",\"city\":\"Othertown\",\"state\":\"NY\",\"zip\":\"67890\"},\"phone_numbers\":[{\"type\":\"mobile\",\"number\":\"555-9012\"}],\"tags\":[\"employee\",\"designer\"],\"active\":true},{\"id\":3,\"name\":\"Bob Johnson\",\"email\":\"bob.johnson@example.com\",\"age\":51,\"address\":{\"street\":\"789 Oak St\",\"city\":\"Somewhere\",\"state\":\"TX\",\"zip\":\"45678\"},\"phone_numbers\":[{\"type\":\"home\",\"number\":\"555-3456\"},{\"type\":\"work\",\"number\":\"555-7890\"},{\"type\":\"mobile\",\"number\":\"555-1234\"}],\"tags\":[\"employee\",\"manager\",\"sales\"],\"active\":false}],\"products\":[{\"id\":\"P001\",\"name\":\"Smartphone\",\"category\":\"Electronics\",\"price\":799.99,\"features\":[\"5G\",\"Dual Camera\",\"Fast Charging\"],\"specifications\":{\"dimensions\":{\"width\":71.5,\"height\":146.7,\"depth\":7.4},\"weight\":174,\"display\":{\"type\":\"OLED\",\"size\":6.1,\"resolution\":\"1170x2532\"},\"processor\":\"A14 Bionic\",\"memory\":128},\"in_stock\":true,\"release_date\":\"2023-09-15\"},{\"id\":\"P002\",\"name\":\"Laptop\",\"category\":\"Electronics\",\"price\":1299.99,\"features\":[\"16GB RAM\",\"512GB SSD\",\"Retina Display\"],\"specifications\":{\"dimensions\":{\"width\":304.1,\"height\":212.4,\"depth\":15.6},\"weight\":1400,\"display\":{\"type\":\"IPS\",\"size\":13.3,\"resolution\":\"2560x1600\"},\"processor\":\"Intel Core i7\",\"memory\":512},\"in_stock\":true,\"release_date\":\"2023-06-10\"},{\"id\":\"P003\",\"name\":\"Wireless Headphones\",\"category\":\"Audio\",\"price\":249.99,\"features\":[\"Noise Cancellation\",\"20h Battery\",\"Bluetooth 5.0\"],\"specifications\":{\"dimensions\":{\"width\":168,\"height\":162,\"depth\":83},\"weight\":254,\"driver\":{\"type\":\"Dynamic\",\"size\":40},\"battery\":{\"capacity\":500,\"life\":20}},\"in_stock\":false,\"release_date\":\"2023-03-22\"}],\"orders\":[{\"id\":\"ORD-2023-001\",\"customer_id\":1,\"date\":\"2023-01-15T10:30:00Z\",\"items\":[{\"product_id\":\"P001\",\"quantity\":1,\"price\":799.99},{\"product_id\":\"P003\",\"quantity\":2,\"price\":249.99}],\"total\":1299.97,\"status\":\"delivered\",\"shipping\":{\"address\":{\"street\":\"123 Main St\",\"city\":\"Anytown\",\"state\":\"CA\",\"zip\":\"12345\"},\"method\":\"express\",\"cost\":15.99,\"tracking_number\":\"SHP-123456789\"},\"payment\":{\"method\":\"credit_card\",\"transaction_id\":\"TRX-987654321\",\"status\":\"completed\"}},{\"id\":\"ORD-2023-002\",\"customer_id\":2,\"date\":\"2023-02-20T14:45:00Z\",\"items\":[{\"product_id\":\"P002\",\"quantity\":1,\"price\":1299.99}],\"total\":1299.99,\"status\":\"shipped\",\"shipping\":{\"address\":{\"street\":\"456 Elm St\",\"city\":\"Othertown\",\"state\":\"NY\",\"zip\":\"67890\"},\"method\":\"standard\",\"cost\":9.99,\"tracking_number\":\"SHP-234567890\"},\"payment\":{\"method\":\"paypal\",\"transaction_id\":\"TRX-876543210\",\"status\":\"completed\"}},{\"id\":\"ORD-2023-003\",\"customer_id\":3,\"date\":\"2023-03-05T09:15:00Z\",\"items\":[{\"product_id\":\"P001\",\"quantity\":1,\"price\":799.99},{\"product_id\":\"P002\",\"quantity\":1,\"price\":1299.99},{\"product_id\":\"P003\",\"quantity\":1,\"price\":249.99}],\"total\":2349.97,\"status\":\"processing\",\"shipping\":{\"address\":{\"street\":\"789 Oak St\",\"city\":\"Somewhere\",\"state\":\"TX\",\"zip\":\"45678\"},\"method\":\"express\",\"cost\":25.99,\"tracking_number\":null},\"payment\":{\"method\":\"bank_transfer\",\"transaction_id\":\"TRX-765432109\",\"status\":\"pending\"}}]},\"repeated_property_names\":{\"data\":{\"data\":{\"data\":{\"data\":\"Nested properties with the same name\"}}}},\"large_strings\":{\"base64_data\":\"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAJ5SURBVHjalFJbSBRRGP7OzO7sbmrqbmo6lom5CYVEkiBIUKEPvRQFRlEEPvTSa0+9By+9BhFkIAhCQSJhWlEpaKVpZaSVpplpbre1vM26u7ObzpyZ0892MJMM6oOf833nO/f/ZwghWE86mOIq7YSQs3r0kqDgr8I2zlKb3k/G57fQz8Z9fOh6HDUsL9AcC5m2Zt4ZBBnLEbBCMfDNVv8s0T8f5sJaMCuW/51sDBQKYOcSUA68JgLhODzT8ToQh1ZeyhKtVMlgXl3NWZbDqcl3aOrtRFPvC/i5XJYk0UTjdiVduTuOleRCUomTiMdho+MxNvX1YlPfSwRMZthsdtllBUAIY27BkRWAbCx+jn+IQJB9NKwMQ0ehFYEVUIMQAh3DQKfRQqPTQR6FAPF4ImuYUQh7eGEuY+MJASMHg8kEvVYLPcsukBACVgiK9iS8ZtMBAQFDMeBp8NJQr9WAp03JOkgHBBZvitg8lkQRbr8fJJHMe5AgYj7iJYcPXPfNexf5rDFK0eFwQM+wYCgWLM2s1IWgYDqPrcVpXJZzUSY+Bp9t82wy6YG5sAJVlRtw0eWCw2RKt5oZiCQSYCkWT5bMOCK/AQgF1eofSDgOa4tKcKCuTnawGTFxLMrZGBpW38Md7SYI6Rts1lrZPRZ0g9d+w5LiiMx2lRVYjO15lTjbfw9sNIw9xdXYXVGBzTq99AVpcPncAz/f0cHnsojQaU3JF4jxhKaFQNUl4PJN4OJ5ICQAeg4jnvmMWWcnCErB5rHyD7Rmf3d1KbH+cOQx2XTkLJ5vPYL+8lrUL30FBjwYtDkw4nBmxNmKPwMAKrUbALKE+vwAAAAASUVORK5CYII=\",\"lorem_ipsum\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?\"},\"binary_data_sizes\":{\"small_payload\":{\"description\":\"Small payload (0-11 bytes in header)\",\"data\":[1,2,3,4,5]},\"medium_payload\":{\"description\":\"Medium payload (1-byte size in header)\",\"data\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50]},\"large_payload\":{\"description\":\"Simulation of large payload (2-byte size in header)\",\"data_description\":\"Would normally contain 256-65535 bytes\"},\"extra_large_payload\":{\"description\":\"Simulation of extra large payload (4-byte size in header)\",\"data_description\":\"Would normally contain >65535 bytes\"}},\"stress_test\":{\"recursive_structure\":{\"name\":\"Level 1\",\"children\":[{\"name\":\"Level 1.1\",\"children\":[{\"name\":\"Level 1.1.1\",\"children\":[]},{\"name\":\"Level 1.1.2\",\"children\":[{\"name\":\"Level 1.1.2.1\",\"children\":[]}]}]},{\"name\":\"Level 1.2\",\"children\":[{\"name\":\"Level 1.2.1\",\"children\":[]}]}]},\"long_array_nested_objects\":[{\"id\":1,\"data\":{\"value\":\"test1\"}},{\"id\":2,\"data\":{\"value\":\"test2\"}},{\"id\":3,\"data\":{\"value\":\"test3\"}},{\"id\":4,\"data\":{\"value\":\"test4\"}},{\"id\":5,\"data\":{\"value\":\"test5\"}},{\"id\":6,\"data\":{\"value\":\"test6\"}},{\"id\":7,\"data\":{\"value\":\"test7\"}},{\"id\":8,\"data\":{\"value\":\"test8\"}},{\"id\":9,\"data\":{\"value\":\"test9\"}},{\"id\":10,\"data\":{\"value\":\"test10\"}},{\"id\":11,\"data\":{\"value\":\"test11\"}},{\"id\":12,\"data\":{\"value\":\"test12\"}},{\"id\":13,\"data\":{\"value\":\"test13\"}},{\"id\":14,\"data\":{\"value\":\"test14\"}},{\"id\":15,\"data\":{\"value\":\"test15\"}},{\"id\":16,\"data\":{\"value\":\"test16\"}},{\"id\":17,\"data\":{\"value\":\"test17\"}},{\"id\":18,\"data\":{\"value\":\"test18\"}},{\"id\":19,\"data\":{\"value\":\"test19\"}},{\"id\":20,\"data\":{\"value\":\"test20\"}}]}}]\"#,\n        ), // Generate a larger JSON object with 20 nested items\n        (\n            \"Real world json #1\",\n            r#\"{\n          \"user\": {\n            \"id\": \"usr_7f8d3a2e\",\n            \"name\": \"Jane Smith\",\n            \"email\": \"jane.smith@example.com\",\n            \"verified\": true,\n            \"created_at\": \"2023-05-12T15:42:31Z\",\n            \"preferences\": {\n              \"theme\": \"dark\",\n              \"notifications\": {\n                \"email\": true,\n                \"push\": false,\n                \"sms\": true\n              },\n              \"language\": \"en-US\"\n            },\n            \"subscription\": {\n              \"plan\": \"premium\",\n              \"status\": \"active\",\n              \"next_billing_date\": \"2024-05-12\"\n            },\n            \"address\": {\n              \"street\": \"123 Main St\",\n              \"city\": \"Boston\",\n              \"state\": \"MA\",\n              \"zip\": \"02108\",\n              \"country\": \"USA\"\n            }\n          },\n          \"meta\": {\n            \"request_id\": \"req_9d7e6c5b4a3f2e1d\",\n            \"timestamp\": 1683905123\n          }\n        }\"#,\n        ),\n        (\n            \"Real world json 2\",\n            r#\"{\n          \"products\": [\n            {\n              \"id\": \"p-1001\",\n              \"name\": \"Wireless Headphones\",\n              \"price\": 79.99,\n              \"currency\": \"USD\",\n              \"in_stock\": true,\n              \"quantity\": 45,\n              \"categories\": [\"electronics\", \"audio\", \"wireless\"],\n              \"ratings\": {\n                \"average\": 4.7,\n                \"count\": 238\n              },\n              \"specs\": {\n                \"brand\": \"SoundMax\",\n                \"color\": \"black\",\n                \"connectivity\": \"Bluetooth 5.0\",\n                \"battery_life\": \"20 hours\"\n              },\n              \"images\": [\n                \"https://example.com/products/headphones-1.jpg\",\n                \"https://example.com/products/headphones-2.jpg\"\n              ]\n            },\n            {\n              \"id\": \"p-1002\",\n              \"name\": \"Smart Watch\",\n              \"price\": 149.99,\n              \"currency\": \"USD\",\n              \"in_stock\": true,\n              \"quantity\": 28,\n              \"categories\": [\"electronics\", \"wearables\", \"fitness\"],\n              \"ratings\": {\n                \"average\": 4.3,\n                \"count\": 182\n              },\n              \"specs\": {\n                \"brand\": \"TechFit\",\n                \"color\": \"silver\",\n                \"display\": \"AMOLED\",\n                \"waterproof\": true\n              },\n              \"images\": [\n                \"https://example.com/products/smartwatch-1.jpg\",\n                \"https://example.com/products/smartwatch-2.jpg\"\n              ]\n            }\n          ],\n          \"pagination\": {\n            \"total\": 237,\n            \"page\": 1,\n            \"per_page\": 2,\n            \"next_page\": 2\n          }\n        }\n          \"#,\n        ),\n        (\n            \"Real world json 3\",\n            r#\"{\n          \"app_name\": \"DataProcessor\",\n          \"version\": \"2.1.3\",\n          \"environment\": \"production\",\n          \"debug\": false,\n          \"log_level\": \"info\",\n          \"database\": {\n            \"main\": {\n              \"host\": \"db-primary.internal\",\n              \"port\": 5432,\n              \"name\": \"app_production\",\n              \"user\": \"app_user\",\n              \"max_connections\": 50,\n              \"timeout_ms\": 5000\n            },\n            \"replica\": {\n              \"host\": \"db-replica.internal\",\n              \"port\": 5432,\n              \"name\": \"app_production_replica\",\n              \"user\": \"app_readonly\",\n              \"max_connections\": 25,\n              \"timeout_ms\": 3000\n            }\n          },\n          \"cache\": {\n            \"enabled\": true,\n            \"ttl_seconds\": 3600,\n            \"max_size_mb\": 512\n          },\n          \"api\": {\n            \"host\": \"0.0.0.0\",\n            \"port\": 8080,\n            \"rate_limit\": {\n              \"requests_per_minute\": 120,\n              \"burst\": 30\n            },\n            \"timeouts\": {\n              \"read_ms\": 5000,\n              \"write_ms\": 10000,\n              \"idle_ms\": 60000\n            }\n          },\n          \"feature_flags\": {\n            \"new_dashboard\": true,\n            \"beta_analytics\": false,\n            \"improved_search\": true\n          }\n        }\n          \"#,\n        ),\n        (\n            \"Real world json 4\",\n            r#\"{\n          \"app_name\": \"DataProcessor\",\n          \"version\": \"2.1.3\",\n          \"environment\": \"production\",\n          \"debug\": false,\n          \"log_level\": \"info\",\n          \"database\": {\n            \"main\": {\n              \"host\": \"db-primary.internal\",\n              \"port\": 5432,\n              \"name\": \"app_production\",\n              \"user\": \"app_user\",\n              \"max_connections\": 50,\n              \"timeout_ms\": 5000\n            },\n            \"replica\": {\n              \"host\": \"db-replica.internal\",\n              \"port\": 5432,\n              \"name\": \"app_production_replica\",\n              \"user\": \"app_readonly\",\n              \"max_connections\": 25,\n              \"timeout_ms\": 3000\n            }\n          },\n          \"cache\": {\n            \"enabled\": true,\n            \"ttl_seconds\": 3600,\n            \"max_size_mb\": 512\n          },\n          \"api\": {\n            \"host\": \"0.0.0.0\",\n            \"port\": 8080,\n            \"rate_limit\": {\n              \"requests_per_minute\": 120,\n              \"burst\": 30\n            },\n            \"timeouts\": {\n              \"read_ms\": 5000,\n              \"write_ms\": 10000,\n              \"idle_ms\": 60000\n            }\n          },\n          \"feature_flags\": {\n            \"new_dashboard\": true,\n            \"beta_analytics\": false,\n            \"improved_search\": true\n          }\n        }\"#,\n        ),\n        (\n            \"Real world json 5\",\n            r#\"\n          {\n            \"app_name\": \"DataProcessor\",\n            \"version\": \"2.1.3\",\n            \"environment\": \"production\",\n            \"debug\": false,\n            \"log_level\": \"info\",\n            \"database\": {\n              \"main\": {\n                \"host\": \"db-primary.internal\",\n                \"port\": 5432,\n                \"name\": \"app_production\",\n                \"user\": \"app_user\",\n                \"max_connections\": 50,\n                \"timeout_ms\": 5000\n              },\n              \"replica\": {\n                \"host\": \"db-replica.internal\",\n                \"port\": 5432,\n                \"name\": \"app_production_replica\",\n                \"user\": \"app_readonly\",\n                \"max_connections\": 25,\n                \"timeout_ms\": 3000\n              }\n            },\n            \"cache\": {\n              \"enabled\": true,\n              \"ttl_seconds\": 3600,\n              \"max_size_mb\": 512\n            },\n            \"api\": {\n              \"host\": \"0.0.0.0\",\n              \"port\": 8080,\n              \"rate_limit\": {\n                \"requests_per_minute\": 120,\n                \"burst\": 30\n              },\n              \"timeouts\": {\n                \"read_ms\": 5000,\n                \"write_ms\": 10000,\n                \"idle_ms\": 60000\n              }\n            },\n            \"feature_flags\": {\n              \"new_dashboard\": true,\n              \"beta_analytics\": false,\n              \"improved_search\": true\n            }\n          }\"#,\n        ),\n        (\n            \"Real world json 6\",\n            r#\"\n          {\n            \"event_id\": \"evt_0ab1cde23f4g5h6i\",\n            \"event_type\": \"page_view\",\n            \"timestamp\": \"2024-03-12T08:14:27.345Z\",\n            \"user\": {\n              \"id\": \"u_789012\",\n              \"anonymous_id\": \"anon_6c7d8e9f0a\",\n              \"device_id\": \"dev_3e4f5g6h7i\",\n              \"session_id\": \"sess_1b2c3d4e5f\"\n            },\n            \"context\": {\n              \"page\": {\n                \"url\": \"https://example.com/products/smart-home\",\n                \"title\": \"Smart Home Products | Example Store\",\n                \"referrer\": \"https://google.com\",\n                \"path\": \"/products/smart-home\"\n              },\n              \"device\": {\n                \"type\": \"desktop\",\n                \"manufacturer\": \"Apple\",\n                \"model\": \"MacBook Pro\",\n                \"screen\": {\n                  \"width\": 1440,\n                  \"height\": 900\n                }\n              },\n              \"browser\": {\n                \"name\": \"Chrome\",\n                \"version\": \"99.0.4844.51\"\n              },\n              \"os\": {\n                \"name\": \"macOS\",\n                \"version\": \"12.3\"\n              },\n              \"location\": {\n                \"country\": \"US\",\n                \"region\": \"CA\",\n                \"city\": \"San Francisco\",\n                \"timezone\": \"America/Los_Angeles\"\n              }\n            },\n            \"properties\": {\n              \"duration_ms\": 5327,\n              \"is_logged_in\": true,\n              \"tags\": [\"homepage\", \"featured\", \"promo\"],\n              \"utm\": {\n                \"source\": \"newsletter\",\n                \"medium\": \"email\",\n                \"campaign\": \"spring_sale_2024\"\n              }\n            }\n          }\n          \"#,\n        ),\n        (\n            \"Deeply nested\",\n            r#\"{\n          \"config\": {\n            \"level1\": {\n              \"level2\": {\n                \"level3\": {\n                  \"level4\": {\n                    \"level5\": {\n                      \"level6\": {\n                        \"level7\": {\n                          \"value\": \"deeply nested value\",\n                          \"enabled\": true,\n                          \"numbers\": [1, 2, 3, 4, 5],\n                          \"settings\": {\n                            \"mode\": \"advanced\",\n                            \"retries\": 3\n                          }\n                        }\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          }\n        }\"#,\n        ),\n        (\n            \"Array heavy\",\n            r#\"{\n          \"feed\": {\n            \"user_id\": \"u_12345\",\n            \"posts\": [\n              {\n                \"id\": \"post_001\",\n                \"author\": \"user_789\",\n                \"content\": \"Just launched our new product! Check it out at example.com/new\",\n                \"timestamp\": \"2024-03-13T14:27:32Z\",\n                \"likes\": 24,\n                \"comments\": [\n                  {\n                    \"id\": \"comment_001\",\n                    \"author\": \"user_456\",\n                    \"content\": \"Looks amazing! Cant wait to try it.\",\n                    \"timestamp\": \"2024-03-13T14:35:12Z\",\n                    \"likes\": 3\n                  },\n                  {\n                    \"id\": \"comment_002\",\n                    \"author\": \"user_789\",\n                    \"content\": \"Thanks! Let me know what you think after youve tried it.\",\n                    \"timestamp\": \"2024-03-13T14:42:45Z\",\n                    \"likes\": 1\n                  }\n                ],\n                \"tags\": [\"product\", \"launch\", \"technology\"]\n              },\n              {\n                \"id\": \"post_002\",\n                \"author\": \"user_123\",\n                \"content\": \"Beautiful day for hiking! #nature #outdoors\",\n                \"timestamp\": \"2024-03-13T11:15:22Z\",\n                \"likes\": 57,\n                \"comments\": [\n                  {\n                    \"id\": \"comment_003\",\n                    \"author\": \"user_345\",\n                    \"content\": \"Where is this? So beautiful!\",\n                    \"timestamp\": \"2024-03-13T11:22:05Z\",\n                    \"likes\": 2\n                  },\n                  {\n                    \"id\": \"comment_004\",\n                    \"author\": \"user_123\",\n                    \"content\": \"Mount Rainier National Park!\",\n                    \"timestamp\": \"2024-03-13T11:30:16Z\",\n                    \"likes\": 3\n                  }\n                ],\n                \"tags\": [\"nature\", \"outdoors\", \"hiking\"],\n                \"location\": {\n                  \"name\": \"Mount Rainier National Park\",\n                  \"latitude\": 46.8800,\n                  \"longitude\": -121.7269\n                }\n              }\n            ],\n            \"has_more\": true,\n            \"next_cursor\": \"cursor_xyz123\"\n          }\n        }\"#,\n        ),\n    ];\n\n    for (size_name, json_payload) in json_sizes.iter() {\n        let query = format!(\"SELECT jsonb('{}')\", json_payload.replace(\"'\", \"\\\\'\"));\n\n        let mut group = criterion.benchmark_group(format!(\"JSONB Size - {size_name}\"));\n\n        group.bench_function(\"Limbo\", |b| {\n            let mut stmt = limbo_conn.prepare(&query).unwrap();\n            b.iter(|| {\n                loop {\n                    match stmt.step().unwrap() {\n                        turso_core::StepResult::Row => {}\n                        turso_core::StepResult::IO => {\n                            db.io.step().unwrap();\n                        }\n                        turso_core::StepResult::Done => {\n                            break;\n                        }\n                        turso_core::StepResult::Interrupt | turso_core::StepResult::Busy => {\n                            unreachable!();\n                        }\n                    }\n                }\n                stmt.reset().unwrap();\n            });\n        });\n\n        if enable_rusqlite {\n            let sqlite_conn = rusqlite_open();\n\n            group.bench_function(\"Sqlite3\", |b| {\n                let mut stmt = sqlite_conn.prepare(&query).unwrap();\n                b.iter(|| {\n                    let mut rows = stmt.raw_query();\n                    while let Some(row) = rows.next().unwrap() {\n                        black_box(row);\n                    }\n                });\n            });\n        }\n\n        group.finish();\n    }\n}\n\nfn bench_sequential_jsonb(criterion: &mut Criterion) {\n    // Flag to disable rusqlite benchmarks if needed\n    let enable_rusqlite = std::env::var(\"DISABLE_RUSQLITE_BENCHMARK\").is_err();\n\n    #[allow(clippy::arc_with_non_send_sync)]\n    let io = Arc::new(PlatformIO::new().unwrap());\n    let db = Database::open_file(io, \"../testing/system/testing.db\").unwrap();\n    let limbo_conn = db.connect().unwrap();\n\n    // Select a subset of JSON payloads to use in the sequential test\n    let json_payloads = [\n        (\"Small\", r#\"{\"id\": 1, \"name\": \"Test\"}\"#),\n        (\n            \"Medium\",\n            r#\"{\"id\": 1, \"name\": \"Test\", \"attributes\": {\"color\": \"blue\", \"size\": \"medium\", \"tags\": [\"tag1\", \"tag2\", \"tag3\"]}}\"#,\n        ),\n        (\n            \"Real world json #1\",\n            r#\"{\n          \"user\": {\n            \"id\": \"usr_7f8d3a2e\",\n            \"name\": \"Jane Smith\",\n            \"email\": \"jane.smith@example.com\",\n            \"verified\": true,\n            \"created_at\": \"2023-05-12T15:42:31Z\",\n            \"preferences\": {\n              \"theme\": \"dark\",\n              \"notifications\": {\n                \"email\": true,\n                \"push\": false,\n                \"sms\": true\n              },\n              \"language\": \"en-US\"\n            }\n          }\n        }\"#,\n        ),\n        (\n            \"Real world json #2\",\n            r#\"{\n      \"feed\": {\n        \"user_id\": \"u_12345\",\n        \"posts\": [\n          {\n            \"id\": \"post_001\",\n            \"author\": \"user_789\",\n            \"content\": \"Just launched our new product! Check it out at example.com/new\",\n            \"timestamp\": \"2024-03-13T14:27:32Z\",\n            \"likes\": 24,\n            \"comments\": [\n              {\n                \"id\": \"comment_001\",\n                \"author\": \"user_456\",\n                \"content\": \"Looks amazing! Cant wait to try it.\",\n                \"timestamp\": \"2024-03-13T14:35:12Z\",\n                \"likes\": 3\n              },\n              {\n                \"id\": \"comment_002\",\n                \"author\": \"user_789\",\n                \"content\": \"Thanks! Let me know what you think after youve tried it.\",\n                \"timestamp\": \"2024-03-13T14:42:45Z\",\n                \"likes\": 1\n              }\n            ],\n            \"tags\": [\"product\", \"launch\", \"technology\"]\n          },\n          {\n            \"id\": \"post_002\",\n            \"author\": \"user_123\",\n            \"content\": \"Beautiful day for hiking! #nature #outdoors\",\n            \"timestamp\": \"2024-03-13T11:15:22Z\",\n            \"likes\": 57,\n            \"comments\": [\n              {\n                \"id\": \"comment_003\",\n                \"author\": \"user_345\",\n                \"content\": \"Where is this? So beautiful!\",\n                \"timestamp\": \"2024-03-13T11:22:05Z\",\n                \"likes\": 2\n              },\n              {\n                \"id\": \"comment_004\",\n                \"author\": \"user_123\",\n                \"content\": \"Mount Rainier National Park!\",\n                \"timestamp\": \"2024-03-13T11:30:16Z\",\n                \"likes\": 3\n              }\n            ],\n            \"tags\": [\"nature\", \"outdoors\", \"hiking\"],\n            \"location\": {\n              \"name\": \"Mount Rainier National Park\",\n              \"latitude\": 46.8800,\n              \"longitude\": -121.7269\n            }\n          }\n        ],\n        \"has_more\": true,\n        \"next_cursor\": \"cursor_xyz123\"\n      }\n    }\"#,\n        ),\n    ];\n\n    // Create a query that calls jsonb() multiple times in sequence\n    let query = format!(\n        \"SELECT jsonb('{}'), jsonb('{}'), jsonb('{}'), jsonb('{}'), jsonb('{}'), jsonb('{}'), jsonb('{}'), jsonb('{}')\",\n        json_payloads[0].1.replace(\"'\", \"\\\\'\"),\n        json_payloads[1].1.replace(\"'\", \"\\\\'\"),\n        json_payloads[2].1.replace(\"'\", \"\\\\'\"),\n        json_payloads[3].1.replace(\"'\", \"\\\\'\"),\n        json_payloads[0].1.replace(\"'\", \"\\\\'\"),\n        json_payloads[1].1.replace(\"'\", \"\\\\'\"),\n        json_payloads[2].1.replace(\"'\", \"\\\\'\"),\n        json_payloads[3].1.replace(\"'\", \"\\\\'\"),\n    );\n\n    let mut group = criterion.benchmark_group(\"Sequential JSONB Calls\");\n\n    group.bench_function(\"Limbo - Sequential\", |b| {\n        let mut stmt = limbo_conn.prepare(&query).unwrap();\n        b.iter(|| {\n            loop {\n                match stmt.step().unwrap() {\n                    turso_core::StepResult::Row => {}\n                    turso_core::StepResult::IO => {\n                        db.io.step().unwrap();\n                    }\n                    turso_core::StepResult::Done => {\n                        break;\n                    }\n                    turso_core::StepResult::Interrupt | turso_core::StepResult::Busy => {\n                        unreachable!();\n                    }\n                }\n            }\n            stmt.reset().unwrap();\n        });\n    });\n\n    if enable_rusqlite {\n        let sqlite_conn = rusqlite_open();\n\n        group.bench_function(\"Sqlite3 - Sequential\", |b| {\n            let mut stmt = sqlite_conn.prepare(&query).unwrap();\n            b.iter(|| {\n                let mut rows = stmt.raw_query();\n                while let Some(row) = rows.next().unwrap() {\n                    black_box(row);\n                }\n            });\n        });\n    }\n\n    group.finish();\n}\n\nfn bench_json_patch(criterion: &mut Criterion) {\n    let enable_rusqlite = std::env::var(\"DISABLE_RUSQLITE_BENCHMARK\").is_err();\n\n    #[allow(clippy::arc_with_non_send_sync)]\n    let io = Arc::new(PlatformIO::new().unwrap());\n    let db = Database::open_file(io, \"../testing/system/testing.db\").unwrap();\n    let limbo_conn = db.connect().unwrap();\n\n    let json_patch_cases = [\n        (\n            \"Simple Property Update\",\n            r#\"{\"name\": \"Original\", \"value\": 42}\"#,\n            r#\"{\"name\": \"Updated\", \"value\": 100}\"#,\n        ),\n        (\n            \"Add New Property\",\n            r#\"{\"name\": \"Original\", \"value\": 42}\"#,\n            r#\"{\"name\": \"Original\", \"value\": 42, \"description\": \"Added field\"}\"#,\n        ),\n        (\n            \"Remove Property\",\n            r#\"{\"name\": \"Original\", \"value\": 42, \"toRemove\": true}\"#,\n            r#\"{\"name\": \"Original\", \"value\": 42}\"#,\n        ),\n        (\n            \"Nested Property Update\",\n            r#\"{\"name\": \"Original\", \"value\": 42, \"nested\": {\"a\": 1, \"b\": 2}}\"#,\n            r#\"{\"name\": \"Updated\", \"value\": 42, \"nested\": {\"a\": 10, \"b\": 2, \"c\": 3}}\"#,\n        ),\n        (\n            \"Array Update\",\n            r#\"{\"items\": [\"apple\", \"banana\", \"cherry\"]}\"#,\n            r#\"{\"items\": [\"avocado\", \"cherry\", \"dragon fruit\"]}\"#,\n        ),\n        (\n            \"Complex User Object Update\",\n            r#\"{\n                \"user\": {\n                    \"id\": \"usr_7f8d3a2e\",\n                    \"name\": \"Jane Smith\",\n                    \"email\": \"jane.smith@example.com\",\n                    \"verified\": true,\n                    \"preferences\": {\n                        \"theme\": \"dark\",\n                        \"notifications\": {\n                            \"email\": true,\n                            \"push\": false,\n                            \"sms\": true\n                        },\n                        \"language\": \"en-US\"\n                    }\n                }\n            }\"#,\n            r#\"{\n                \"user\": {\n                    \"id\": \"usr_7f8d3a2e\",\n                    \"name\": \"Jane Doe\",\n                    \"email\": \"jane.doe@example.com\",\n                    \"verified\": true,\n                    \"preferences\": {\n                        \"theme\": \"light\",\n                        \"notifications\": {\n                            \"email\": true,\n                            \"push\": true,\n                            \"sms\": false\n                        },\n                        \"language\": \"en-US\"\n                    },\n                    \"subscription\": {\n                        \"plan\": \"premium\",\n                        \"status\": \"active\"\n                    }\n                }\n            }\"#,\n        ),\n        (\n            \"Large Config Update\",\n            r#\"{\n                \"app_name\": \"DataProcessor\",\n                \"version\": \"2.1.3\",\n                \"environment\": \"production\",\n                \"debug\": false,\n                \"log_level\": \"info\",\n                \"database\": {\n                    \"main\": {\n                        \"host\": \"db-primary.internal\",\n                        \"port\": 5432,\n                        \"name\": \"app_production\",\n                        \"user\": \"app_user\",\n                        \"max_connections\": 50,\n                        \"timeout_ms\": 5000\n                    },\n                    \"replica\": {\n                        \"host\": \"db-replica.internal\",\n                        \"port\": 5432,\n                        \"name\": \"app_production_replica\",\n                        \"user\": \"app_readonly\",\n                        \"max_connections\": 25,\n                        \"timeout_ms\": 3000\n                    }\n                },\n                \"cache\": {\n                    \"enabled\": true,\n                    \"ttl_seconds\": 3600,\n                    \"max_size_mb\": 512\n                },\n                \"api\": {\n                    \"host\": \"0.0.0.0\",\n                    \"port\": 8080,\n                    \"rate_limit\": {\n                        \"requests_per_minute\": 120,\n                        \"burst\": 30\n                    },\n                    \"timeouts\": {\n                        \"read_ms\": 5000,\n                        \"write_ms\": 10000,\n                        \"idle_ms\": 60000\n                    }\n                },\n                \"feature_flags\": {\n                    \"new_dashboard\": true,\n                    \"beta_analytics\": false,\n                    \"improved_search\": true\n                }\n            }\"#,\n            r#\"{\n                \"app_name\": \"DataProcessor\",\n                \"version\": \"2.2.0\",\n                \"environment\": \"production\",\n                \"debug\": true,\n                \"log_level\": \"debug\",\n                \"database\": {\n                    \"main\": {\n                        \"host\": \"db-primary.internal\",\n                        \"port\": 5432,\n                        \"name\": \"app_production\",\n                        \"user\": \"app_user\",\n                        \"max_connections\": 100,\n                        \"timeout_ms\": 5000\n                    },\n                    \"replica\": {\n                        \"host\": \"db-replica.internal\",\n                        \"port\": 5432,\n                        \"name\": \"app_production_replica\",\n                        \"user\": \"app_readonly\",\n                        \"max_connections\": 25,\n                        \"timeout_ms\": 3000\n                    },\n                    \"backup\": {\n                        \"host\": \"db-backup.internal\",\n                        \"port\": 5432\n                    }\n                },\n                \"cache\": {\n                    \"enabled\": true,\n                    \"ttl_seconds\": 3600,\n                    \"max_size_mb\": 1024\n                },\n                \"api\": {\n                    \"host\": \"0.0.0.0\",\n                    \"port\": 8080,\n                    \"rate_limit\": {\n                        \"requests_per_minute\": 240,\n                        \"burst\": 30\n                    },\n                    \"timeouts\": {\n                        \"read_ms\": 5000,\n                        \"write_ms\": 10000,\n                        \"idle_ms\": 60000\n                    }\n                },\n                \"feature_flags\": {\n                    \"new_dashboard\": true,\n                    \"beta_analytics\": true,\n                    \"improved_search\": true,\n                    \"ai_recommendations\": true\n                }\n            }\"#,\n        ),\n        (\n            \"Deeply Nested Social Feed Update\",\n            r#\"{\n                \"feed\": {\n                    \"user_id\": \"u_12345\",\n                    \"posts\": [\n                        {\n                            \"id\": \"post_001\",\n                            \"author\": \"user_789\",\n                            \"content\": \"Just launched our new product!\",\n                            \"likes\": 24,\n                            \"comments\": [\n                                {\n                                    \"id\": \"comment_001\",\n                                    \"author\": \"user_456\",\n                                    \"content\": \"Looks amazing!\",\n                                    \"likes\": 3\n                                },\n                                {\n                                    \"id\": \"comment_002\",\n                                    \"author\": \"user_789\",\n                                    \"content\": \"Thanks!\",\n                                    \"likes\": 1\n                                }\n                            ]\n                        }\n                    ]\n                }\n            }\"#,\n            r#\"{\n                \"feed\": {\n                    \"user_id\": \"u_12345\",\n                    \"posts\": [\n                        {\n                            \"id\": \"post_001\",\n                            \"author\": \"user_789\",\n                            \"content\": \"Updated product announcement!\",\n                            \"likes\": 35,\n                            \"comments\": [\n                                {\n                                    \"id\": \"comment_001\",\n                                    \"author\": \"user_456\",\n                                    \"content\": \"This is incredible!\",\n                                    \"likes\": 5\n                                },\n                                {\n                                    \"id\": \"comment_002\",\n                                    \"author\": \"user_789\",\n                                    \"content\": \"Thanks!\",\n                                    \"likes\": 1\n                                },\n                                {\n                                    \"id\": \"comment_003\",\n                                    \"author\": \"user_555\",\n                                    \"content\": \"Just ordered one!\",\n                                    \"likes\": 0\n                                }\n                            ],\n                            \"tags\": [\"product\", \"launch\"]\n                        }\n                    ]\n                }\n            }\"#,\n        ),\n    ];\n\n    for (case_name, target_json, patch_json) in json_patch_cases.iter() {\n        let query = format!(\n            \"SELECT json_patch('{}', '{}')\",\n            target_json.replace(\"'\", \"''\"),\n            patch_json.replace(\"'\", \"''\")\n        );\n\n        let mut group = criterion.benchmark_group(format!(\"JSON Patch - {case_name}\"));\n\n        group.bench_function(\"Limbo\", |b| {\n            let mut stmt = limbo_conn.prepare(&query).unwrap();\n            b.iter(|| {\n                loop {\n                    match stmt.step().unwrap() {\n                        turso_core::StepResult::Row => {}\n                        turso_core::StepResult::IO => {\n                            db.io.step().unwrap();\n                        }\n                        turso_core::StepResult::Done => {\n                            break;\n                        }\n                        turso_core::StepResult::Interrupt | turso_core::StepResult::Busy => {\n                            unreachable!();\n                        }\n                    }\n                }\n                stmt.reset().unwrap();\n            });\n        });\n\n        if enable_rusqlite {\n            let sqlite_conn = rusqlite_open();\n\n            group.bench_function(\"Sqlite3\", |b| {\n                let mut stmt = sqlite_conn.prepare(&query).unwrap();\n                b.iter(|| {\n                    let mut rows = stmt.raw_query();\n                    while let Some(row) = rows.next().unwrap() {\n                        black_box(row);\n                    }\n                });\n            });\n        }\n\n        group.finish();\n    }\n}\n\n#[cfg(not(feature = \"codspeed\"))]\ncriterion_group! {\n    name = benches;\n    config = Criterion::default().with_profiler(PProfProfiler::new(100, Output::Flamegraph(Some(Options::default()))));\n    targets = bench, bench_sequential_jsonb, bench_json_patch\n}\n\n#[cfg(feature = \"codspeed\")]\ncriterion_group! {\n    name = benches;\n    config = Criterion::default();\n    targets = bench, bench_sequential_jsonb, bench_json_patch\n}\n\ncriterion_main!(benches);\n"
  },
  {
    "path": "core/benches/mvcc_benchmark.rs",
    "content": "use std::sync::Arc;\n\n#[cfg(not(feature = \"codspeed\"))]\nuse criterion::{\n    async_executor::FuturesExecutor, criterion_group, criterion_main, Criterion, Throughput,\n};\n#[cfg(not(feature = \"codspeed\"))]\nuse pprof::criterion::{Output, PProfProfiler};\n\n#[cfg(feature = \"codspeed\")]\nuse codspeed_criterion_compat::{\n    async_executor::FuturesExecutor, criterion_group, criterion_main, Criterion, Throughput,\n};\n\nuse turso_core::mvcc::clock::MvccClock;\nuse turso_core::mvcc::database::{MvStore, Row, RowID, RowKey};\nuse turso_core::types::{IOResult, ImmutableRecord, Text};\nuse turso_core::{Connection, Database, MemoryIO, Value};\n\nstruct BenchDb {\n    _db: Arc<Database>,\n    conn: Arc<Connection>,\n    mvcc_store: Arc<MvStore<MvccClock>>,\n}\n\nfn bench_db() -> BenchDb {\n    let io = Arc::new(MemoryIO::new());\n    let db = Database::open_file(io, \":memory:\").unwrap();\n    let conn = db.connect().unwrap();\n    // Enable MVCC via PRAGMA\n    conn.execute(\"PRAGMA journal_mode = 'mvcc'\").unwrap();\n    let mvcc_store = db.get_mv_store().clone().unwrap();\n    BenchDb {\n        _db: db,\n        conn,\n        mvcc_store,\n    }\n}\n\nfn bench(c: &mut Criterion) {\n    let mut group = c.benchmark_group(\"mvcc-ops-throughput\");\n    group.throughput(Throughput::Elements(1));\n\n    group.bench_function(\"begin_tx + rollback_tx\", |b| {\n        let db = bench_db();\n        b.to_async(FuturesExecutor).iter(|| async {\n            let conn = db.conn.clone();\n            let tx_id = db.mvcc_store.begin_tx(conn.get_pager()).unwrap();\n            db.mvcc_store\n                .rollback_tx(tx_id, conn.get_pager(), &conn, turso_core::MAIN_DB_ID);\n        })\n    });\n\n    let db = bench_db();\n    group.bench_function(\"begin_tx + commit_tx\", |b| {\n        b.to_async(FuturesExecutor).iter(|| async {\n            let conn = &db.conn;\n            let tx_id = db.mvcc_store.begin_tx(conn.get_pager()).unwrap();\n            let mv_store = &db.mvcc_store;\n            let mut sm = mv_store.commit_tx(tx_id, conn).unwrap();\n            // TODO: sync IO hack\n            loop {\n                let res = sm.step(mv_store).unwrap();\n                match res {\n                    IOResult::IO(io) => io.wait(db._db.io.as_ref()).unwrap(),\n                    IOResult::Done(_) => break,\n                }\n            }\n        })\n    });\n\n    let db = bench_db();\n    group.bench_function(\"begin_tx-read-commit_tx\", |b| {\n        b.to_async(FuturesExecutor).iter(|| async {\n            let conn = &db.conn;\n            let tx_id = db.mvcc_store.begin_tx(conn.get_pager()).unwrap();\n            db.mvcc_store\n                .read(\n                    tx_id,\n                    &RowID {\n                        table_id: (-2).into(),\n                        row_id: RowKey::Int(1),\n                    },\n                )\n                .unwrap();\n            let mv_store = &db.mvcc_store;\n            let mut sm = mv_store.commit_tx(tx_id, conn).unwrap();\n            // TODO: sync IO hack\n            loop {\n                let res = sm.step(mv_store).unwrap();\n                match res {\n                    IOResult::IO(io) => io.wait(db._db.io.as_ref()).unwrap(),\n                    IOResult::Done(_) => break,\n                }\n            }\n        })\n    });\n\n    let db = bench_db();\n    let record = ImmutableRecord::from_values(&vec![Value::Text(Text::new(\"World\"))], 1);\n    let record_data = record.as_blob();\n    group.bench_function(\"begin_tx-update-commit_tx\", |b| {\n        b.to_async(FuturesExecutor).iter(|| async {\n            let conn = &db.conn;\n            let tx_id = db.mvcc_store.begin_tx(conn.get_pager()).unwrap();\n            db.mvcc_store\n                .update(\n                    tx_id,\n                    Row::new_table_row(\n                        RowID::new((-2).into(), RowKey::Int(1)),\n                        record_data.clone(),\n                        1,\n                    ),\n                )\n                .unwrap();\n            let mv_store = &db.mvcc_store;\n            let mut sm = mv_store.commit_tx(tx_id, conn).unwrap();\n            // TODO: sync IO hack\n            loop {\n                let res = sm.step(mv_store).unwrap();\n                match res {\n                    IOResult::IO(io) => io.wait(db._db.io.as_ref()).unwrap(),\n                    IOResult::Done(_) => break,\n                }\n            }\n        })\n    });\n\n    let db = bench_db();\n    let tx_id = db.mvcc_store.begin_tx(db.conn.get_pager()).unwrap();\n    db.mvcc_store\n        .insert(\n            tx_id,\n            Row::new_table_row(\n                RowID::new((-2).into(), RowKey::Int(1)),\n                record_data.clone(),\n                1,\n            ),\n        )\n        .unwrap();\n    group.bench_function(\"read\", |b| {\n        b.to_async(FuturesExecutor).iter(|| async {\n            db.mvcc_store\n                .read(\n                    tx_id,\n                    &RowID {\n                        table_id: (-2).into(),\n                        row_id: RowKey::Int(1),\n                    },\n                )\n                .unwrap();\n        })\n    });\n\n    let db = bench_db();\n    let tx_id = db.mvcc_store.begin_tx(db.conn.get_pager()).unwrap();\n    db.mvcc_store\n        .insert(\n            tx_id,\n            Row::new_table_row(\n                RowID::new((-2).into(), RowKey::Int(1)),\n                record_data.clone(),\n                1,\n            ),\n        )\n        .unwrap();\n    group.bench_function(\"update\", |b| {\n        b.to_async(FuturesExecutor).iter(|| async {\n            db.mvcc_store\n                .update(\n                    tx_id,\n                    Row::new_table_row(\n                        RowID::new((-2).into(), RowKey::Int(1)),\n                        record_data.clone(),\n                        1,\n                    ),\n                )\n                .unwrap();\n        })\n    });\n}\n\n#[cfg(not(feature = \"codspeed\"))]\ncriterion_group! {\n    name = benches;\n    config = Criterion::default().with_profiler(PProfProfiler::new(100, Output::Flamegraph(None)));\n    targets = bench\n}\n\n#[cfg(feature = \"codspeed\")]\ncriterion_group! {\n    name = benches;\n    config = Criterion::default();\n    targets = bench\n}\n\ncriterion_main!(benches);\n"
  },
  {
    "path": "core/benches/sql_functions/datetime.rs",
    "content": "use divan::{black_box, Bencher};\nuse turso_core::functions::datetime::{\n    exec_date, exec_datetime_full, exec_julianday, exec_strftime, exec_time, exec_timediff,\n    exec_unixepoch,\n};\nuse turso_core::types::Value;\n\n// =============================================================================\n// Fast Path Parsing Benchmarks\n// These formats use the optimized custom parser (no chrono overhead)\n// =============================================================================\n\n#[divan::bench]\nfn fast_path_date_only(bencher: Bencher) {\n    // YYYY-MM-DD (10 chars) - fast path\n    let args = [Value::build_text(\"2024-07-21\")];\n    bencher.bench_local(|| black_box(exec_date(black_box(&args))));\n}\n\n#[divan::bench]\nfn fast_path_datetime_hhmm(bencher: Bencher) {\n    // YYYY-MM-DD HH:MM (16 chars) - fast path\n    let args = [Value::build_text(\"2024-07-21 14:30\")];\n    bencher.bench_local(|| black_box(exec_datetime_full(black_box(&args))));\n}\n\n#[divan::bench]\nfn fast_path_datetime_hhmmss(bencher: Bencher) {\n    // YYYY-MM-DD HH:MM:SS (19 chars) - fast path\n    let args = [Value::build_text(\"2024-07-21 14:30:45\")];\n    bencher.bench_local(|| black_box(exec_datetime_full(black_box(&args))));\n}\n\n#[divan::bench]\nfn fast_path_datetime_with_frac(bencher: Bencher) {\n    // YYYY-MM-DD HH:MM:SS.fff (23 chars) - fast path\n    let args = [Value::build_text(\"2024-07-21 14:30:45.123\")];\n    bencher.bench_local(|| black_box(exec_datetime_full(black_box(&args))));\n}\n\n#[divan::bench]\nfn fast_path_datetime_t_separator(bencher: Bencher) {\n    // YYYY-MM-DDTHH:MM:SS - fast path with T separator\n    let args = [Value::build_text(\"2024-07-21T14:30:45\")];\n    bencher.bench_local(|| black_box(exec_datetime_full(black_box(&args))));\n}\n\n#[divan::bench]\nfn fast_path_time_hhmm(bencher: Bencher) {\n    // HH:MM (5 chars) - fast path\n    let args = [Value::build_text(\"14:30\")];\n    bencher.bench_local(|| black_box(exec_time(black_box(&args))));\n}\n\n#[divan::bench]\nfn fast_path_time_hhmmss(bencher: Bencher) {\n    // HH:MM:SS (8 chars) - fast path\n    let args = [Value::build_text(\"14:30:45\")];\n    bencher.bench_local(|| black_box(exec_time(black_box(&args))));\n}\n\n#[divan::bench]\nfn fast_path_time_with_frac(bencher: Bencher) {\n    // HH:MM:SS.fff - fast path\n    let args = [Value::build_text(\"14:30:45.123\")];\n    bencher.bench_local(|| black_box(exec_time(black_box(&args))));\n}\n\n// =============================================================================\n// Slow Path Parsing Benchmarks\n// These formats require chrono's parser (timezone handling)\n// =============================================================================\n\n#[divan::bench]\nfn slow_path_datetime_utc_z(bencher: Bencher) {\n    // Ends with Z - triggers slow path for timezone\n    let args = [Value::build_text(\"2024-07-21 14:30:45Z\")];\n    bencher.bench_local(|| black_box(exec_datetime_full(black_box(&args))));\n}\n\n#[divan::bench]\nfn slow_path_datetime_tz_offset(bencher: Bencher) {\n    // Has timezone offset - slow path\n    let args = [Value::build_text(\"2024-07-21 14:30:45+02:00\")];\n    bencher.bench_local(|| black_box(exec_datetime_full(black_box(&args))));\n}\n\n#[divan::bench]\nfn slow_path_datetime_negative_tz(bencher: Bencher) {\n    // Negative timezone offset - slow path\n    let args = [Value::build_text(\"2024-07-21 14:30:45-05:00\")];\n    bencher.bench_local(|| black_box(exec_datetime_full(black_box(&args))));\n}\n\n#[divan::bench]\nfn slow_path_time_with_tz(bencher: Bencher) {\n    // Time with timezone - slow path\n    let args = [Value::build_text(\"14:30:45+02:00\")];\n    bencher.bench_local(|| black_box(exec_time(black_box(&args))));\n}\n\n// =============================================================================\n// Numeric Input Benchmarks (Julian Day)\n// =============================================================================\n\n#[divan::bench]\nfn julian_day_float_input(bencher: Bencher) {\n    // Float julian day value\n    let args = [Value::from_f64(2460512.5)];\n    bencher.bench_local(|| black_box(exec_date(black_box(&args))));\n}\n\n#[divan::bench]\nfn julian_day_integer_input(bencher: Bencher) {\n    // Integer julian day value\n    let args = [Value::from_i64(2460513)];\n    bencher.bench_local(|| black_box(exec_date(black_box(&args))));\n}\n\n#[divan::bench]\nfn julian_day_string_numeric(bencher: Bencher) {\n    // Numeric string that parses as julian day\n    let args = [Value::build_text(\"2460512.5\")];\n    bencher.bench_local(|| black_box(exec_date(black_box(&args))));\n}\n\n// =============================================================================\n// Output Type Benchmarks\n// =============================================================================\n\n#[divan::bench]\nfn output_date(bencher: Bencher) {\n    let args = [Value::build_text(\"2024-07-21 14:30:45\")];\n    bencher.bench_local(|| black_box(exec_date(black_box(&args))));\n}\n\n#[divan::bench]\nfn output_time(bencher: Bencher) {\n    let args = [Value::build_text(\"2024-07-21 14:30:45\")];\n    bencher.bench_local(|| black_box(exec_time(black_box(&args))));\n}\n\n#[divan::bench]\nfn output_datetime(bencher: Bencher) {\n    let args = [Value::build_text(\"2024-07-21 14:30:45\")];\n    bencher.bench_local(|| black_box(exec_datetime_full(black_box(&args))));\n}\n\n#[divan::bench]\nfn output_julianday(bencher: Bencher) {\n    let args = [Value::build_text(\"2024-07-21 14:30:45\")];\n    bencher.bench_local(|| black_box(exec_julianday(black_box(&args))));\n}\n\n#[divan::bench]\nfn output_unixepoch(bencher: Bencher) {\n    let args = [Value::build_text(\"2024-07-21 14:30:45\")];\n    bencher.bench_local(|| black_box(exec_unixepoch(black_box(&args))));\n}\n\n// =============================================================================\n// strftime Benchmarks\n// =============================================================================\n\n#[divan::bench]\nfn strftime_simple_format(bencher: Bencher) {\n    let args = [\n        Value::build_text(\"%Y-%m-%d\"),\n        Value::build_text(\"2024-07-21 14:30:45\"),\n    ];\n    bencher.bench_local(|| black_box(exec_strftime(black_box(&args))));\n}\n\n#[divan::bench]\nfn strftime_complex_format(bencher: Bencher) {\n    let args = [\n        Value::build_text(\"%Y-%m-%d %H:%M:%S\"),\n        Value::build_text(\"2024-07-21 14:30:45\"),\n    ];\n    bencher.bench_local(|| black_box(exec_strftime(black_box(&args))));\n}\n\n#[divan::bench]\nfn strftime_with_julian(bencher: Bencher) {\n    // %J is SQLite-specific julian day format\n    let args = [\n        Value::build_text(\"%J\"),\n        Value::build_text(\"2024-07-21 14:30:45\"),\n    ];\n    bencher.bench_local(|| black_box(exec_strftime(black_box(&args))));\n}\n\n#[divan::bench]\nfn strftime_weekday_format(bencher: Bencher) {\n    let args = [\n        Value::build_text(\"%w %W\"),\n        Value::build_text(\"2024-07-21 14:30:45\"),\n    ];\n    bencher.bench_local(|| black_box(exec_strftime(black_box(&args))));\n}\n\n// =============================================================================\n// Modifier Benchmarks\n// =============================================================================\n\n#[divan::bench]\nfn modifier_add_days(bencher: Bencher) {\n    let args = [\n        Value::build_text(\"2024-07-21\"),\n        Value::build_text(\"+5 days\"),\n    ];\n    bencher.bench_local(|| black_box(exec_date(black_box(&args))));\n}\n\n#[divan::bench]\nfn modifier_add_fractional_days(bencher: Bencher) {\n    // Fractional modifier (new feature from PR)\n    let args = [\n        Value::build_text(\"2024-07-21 12:00:00\"),\n        Value::build_text(\"+1.5 days\"),\n    ];\n    bencher.bench_local(|| black_box(exec_datetime_full(black_box(&args))));\n}\n\n#[divan::bench]\nfn modifier_add_months(bencher: Bencher) {\n    let args = [\n        Value::build_text(\"2024-07-21\"),\n        Value::build_text(\"+3 months\"),\n    ];\n    bencher.bench_local(|| black_box(exec_date(black_box(&args))));\n}\n\n#[divan::bench]\nfn modifier_start_of_month(bencher: Bencher) {\n    let args = [\n        Value::build_text(\"2024-07-21 14:30:45\"),\n        Value::build_text(\"start of month\"),\n    ];\n    bencher.bench_local(|| black_box(exec_date(black_box(&args))));\n}\n\n#[divan::bench]\nfn modifier_start_of_year(bencher: Bencher) {\n    let args = [\n        Value::build_text(\"2024-07-21 14:30:45\"),\n        Value::build_text(\"start of year\"),\n    ];\n    bencher.bench_local(|| black_box(exec_date(black_box(&args))));\n}\n\n#[divan::bench]\nfn modifier_weekday(bencher: Bencher) {\n    let args = [\n        Value::build_text(\"2024-07-21\"),\n        Value::build_text(\"weekday 0\"),\n    ];\n    bencher.bench_local(|| black_box(exec_date(black_box(&args))));\n}\n\n#[divan::bench]\nfn modifier_unixepoch(bencher: Bencher) {\n    // unixepoch modifier for numeric input\n    let args = [Value::from_i64(1721577045), Value::build_text(\"unixepoch\")];\n    bencher.bench_local(|| black_box(exec_datetime_full(black_box(&args))));\n}\n\n#[divan::bench]\nfn modifier_auto_unixepoch(bencher: Bencher) {\n    // auto modifier detecting unix epoch\n    let args = [Value::from_i64(1721577045), Value::build_text(\"auto\")];\n    bencher.bench_local(|| black_box(exec_datetime_full(black_box(&args))));\n}\n\n#[divan::bench]\nfn modifier_auto_julianday(bencher: Bencher) {\n    // auto modifier detecting julian day\n    let args = [Value::from_f64(2460512.5), Value::build_text(\"auto\")];\n    bencher.bench_local(|| black_box(exec_datetime_full(black_box(&args))));\n}\n\n#[divan::bench]\nfn modifier_localtime(bencher: Bencher) {\n    let args = [\n        Value::build_text(\"2024-07-21 14:30:45\"),\n        Value::build_text(\"localtime\"),\n    ];\n    bencher.bench_local(|| black_box(exec_datetime_full(black_box(&args))));\n}\n\n#[divan::bench]\nfn modifier_chain_multiple(bencher: Bencher) {\n    // Multiple modifiers chained\n    let args = [\n        Value::build_text(\"2024-07-21\"),\n        Value::build_text(\"+1 month\"),\n        Value::build_text(\"start of month\"),\n        Value::build_text(\"+7 days\"),\n    ];\n    bencher.bench_local(|| black_box(exec_date(black_box(&args))));\n}\n\n// =============================================================================\n// timediff Benchmarks\n// =============================================================================\n\n#[divan::bench]\nfn timediff_same_day(bencher: Bencher) {\n    let args = [\n        Value::build_text(\"2024-07-21 14:30:45\"),\n        Value::build_text(\"2024-07-21 10:15:30\"),\n    ];\n    bencher.bench_local(|| black_box(exec_timediff(black_box(&args))));\n}\n\n#[divan::bench]\nfn timediff_different_days(bencher: Bencher) {\n    let args = [\n        Value::build_text(\"2024-07-25 14:30:45\"),\n        Value::build_text(\"2024-07-21 10:15:30\"),\n    ];\n    bencher.bench_local(|| black_box(exec_timediff(black_box(&args))));\n}\n\n#[divan::bench]\nfn timediff_different_years(bencher: Bencher) {\n    let args = [\n        Value::build_text(\"2024-07-21 14:30:45\"),\n        Value::build_text(\"2020-01-15 10:15:30\"),\n    ];\n    bencher.bench_local(|| black_box(exec_timediff(black_box(&args))));\n}\n\n// =============================================================================\n// Special Cases\n// =============================================================================\n\n#[divan::bench]\nfn special_now(bencher: Bencher) {\n    let args = [Value::build_text(\"now\")];\n    bencher.bench_local(|| black_box(exec_datetime_full(black_box(&args))));\n}\n\n#[divan::bench]\nfn special_no_args_current_time(bencher: Bencher) {\n    // No arguments - returns current time\n    let args: [Value; 0] = [];\n    bencher.bench_local(|| black_box(exec_datetime_full(black_box(&args))));\n}\n\n#[divan::bench]\nfn special_subsec_modifier(bencher: Bencher) {\n    let args = [\n        Value::build_text(\"2024-07-21 14:30:45.123456\"),\n        Value::build_text(\"subsec\"),\n    ];\n    bencher.bench_local(|| black_box(exec_time(black_box(&args))));\n}\n\n#[divan::bench]\nfn special_date_overflow(bencher: Bencher) {\n    // Invalid date that overflows (Feb 30 -> Mar 2)\n    let args = [Value::build_text(\"2024-02-30\")];\n    bencher.bench_local(|| black_box(exec_date(black_box(&args))));\n}\n\n#[divan::bench]\nfn special_negative_year(bencher: Bencher) {\n    // Negative year (BCE dates)\n    let args = [Value::build_text(\"-0044-03-15\")];\n    bencher.bench_local(|| black_box(exec_date(black_box(&args))));\n}\n"
  },
  {
    "path": "core/benches/sql_functions/likeop.rs",
    "content": "use divan::{black_box, Bencher};\nuse turso_core::types::Value;\n\n// =============================================================================\n// LIKE Pattern Matching Benchmarks\n// =============================================================================\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn like_simple_exact_match(bencher: Bencher) {\n    bencher.bench_local(|| {\n        black_box(Value::exec_like(\n            black_box(\"hello\"),\n            black_box(\"hello\"),\n            Some('\\\\'),\n        ))\n        .unwrap()\n    });\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn like_simple_no_match(bencher: Bencher) {\n    bencher.bench_local(|| {\n        black_box(Value::exec_like(\n            black_box(\"hello\"),\n            black_box(\"world\"),\n            Some('\\\\'),\n        ))\n        .unwrap()\n    });\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn like_percent_prefix(bencher: Bencher) {\n    // Pattern: %world - matches anything ending with \"world\"\n    bencher.bench_local(|| {\n        black_box(Value::exec_like(\n            black_box(\"%world\"),\n            black_box(\"hello world\"),\n            Some('\\\\'),\n        ))\n        .unwrap()\n    });\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn like_percent_suffix(bencher: Bencher) {\n    // Pattern: hello% - matches anything starting with \"hello\"\n    bencher.bench_local(|| {\n        black_box(Value::exec_like(\n            black_box(\"hello%\"),\n            black_box(\"hello world\"),\n            Some('\\\\'),\n        ))\n        .unwrap()\n    });\n}\n\n#[divan::bench]\nfn like_percent_both(bencher: Bencher) {\n    // Pattern: %llo wor% - matches anything containing \"llo wor\"\n    bencher.bench_local(|| {\n        black_box(Value::exec_like(\n            black_box(\"%llo wor%\"),\n            black_box(\"hello world\"),\n            Some('\\\\'),\n        ))\n        .unwrap()\n    });\n}\n\n#[divan::bench]\nfn like_underscore_single(bencher: Bencher) {\n    // Pattern: h_llo - matches \"hello\", \"hallo\", etc.\n    bencher.bench_local(|| {\n        black_box(Value::exec_like(\n            black_box(\"h_llo\"),\n            black_box(\"hello\"),\n            Some('\\\\'),\n        ))\n        .unwrap()\n    });\n}\n\n#[divan::bench]\nfn like_underscore_multiple(bencher: Bencher) {\n    // Pattern: h___o - matches 5 character words starting with h, ending with o\n    bencher.bench_local(|| {\n        black_box(Value::exec_like(\n            black_box(\"h___o\"),\n            black_box(\"hello\"),\n            Some('\\\\'),\n        ))\n        .unwrap()\n    });\n}\n\n#[divan::bench]\nfn like_mixed_wildcards(bencher: Bencher) {\n    // Pattern: %h_llo% - complex pattern\n    bencher.bench_local(|| {\n        black_box(Value::exec_like(\n            black_box(\"%h_llo%\"),\n            black_box(\"say hello world\"),\n            Some('\\\\'),\n        ))\n        .unwrap()\n    });\n}\n\n#[divan::bench]\nfn like_escape_percent(bencher: Bencher) {\n    // Testing escaped percent sign\n    bencher.bench_local(|| {\n        black_box(Value::exec_like(\n            black_box(\"100\\\\%\"),\n            black_box(\"100%\"),\n            Some('\\\\'),\n        ))\n        .unwrap()\n    });\n}\n\n#[divan::bench]\nfn like_escape_underscore(bencher: Bencher) {\n    // Testing escaped underscore\n    bencher.bench_local(|| {\n        black_box(Value::exec_like(\n            black_box(\"file\\\\_name\"),\n            black_box(\"file_name\"),\n            Some('\\\\'),\n        ))\n        .unwrap()\n    });\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn like_case_insensitive(bencher: Bencher) {\n    // LIKE is case-insensitive by default\n    bencher.bench_local(|| {\n        black_box(Value::exec_like(\n            black_box(\"HELLO\"),\n            black_box(\"hello\"),\n            Some('\\\\'),\n        ))\n        .unwrap()\n    });\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn like_long_pattern(bencher: Bencher) {\n    let pattern = \"The quick brown fox %\";\n    let text = \"The quick brown fox jumps over the lazy dog\";\n    bencher.bench_local(|| {\n        black_box(Value::exec_like(\n            black_box(pattern),\n            black_box(text),\n            Some('\\\\'),\n        ))\n        .unwrap()\n    });\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn like_long_text_short_pattern(bencher: Bencher) {\n    let pattern = \"%dog\";\n    let text = \"The quick brown fox jumps over the lazy dog\";\n    bencher.bench_local(|| {\n        black_box(Value::exec_like(\n            black_box(pattern),\n            black_box(text),\n            Some('\\\\'),\n        ))\n        .unwrap()\n    });\n}\n\n#[divan::bench]\nfn like_many_percent_wildcards(bencher: Bencher) {\n    // Pattern with multiple % wildcards - can be expensive\n    let pattern = \"%quick%fox%lazy%\";\n    let text = \"The quick brown fox jumps over the lazy dog\";\n    bencher.bench_local(|| {\n        black_box(Value::exec_like(\n            black_box(pattern),\n            black_box(text),\n            Some('\\\\'),\n        ))\n        .unwrap()\n    });\n}\n\n// =============================================================================\n// GLOB Pattern Matching Benchmarks\n// =============================================================================\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn glob_simple_exact_match(bencher: Bencher) {\n    bencher.bench_local(|| black_box(Value::exec_glob(black_box(\"hello\"), black_box(\"hello\"))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn glob_simple_no_match(bencher: Bencher) {\n    bencher.bench_local(|| black_box(Value::exec_glob(black_box(\"hello\"), black_box(\"world\"))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn glob_star_prefix(bencher: Bencher) {\n    // Pattern: *world - matches anything ending with \"world\"\n    bencher.bench_local(|| {\n        black_box(Value::exec_glob(\n            black_box(\"*world\"),\n            black_box(\"hello world\"),\n        ))\n    });\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn glob_star_suffix(bencher: Bencher) {\n    // Pattern: hello* - matches anything starting with \"hello\"\n    bencher.bench_local(|| {\n        black_box(Value::exec_glob(\n            black_box(\"hello*\"),\n            black_box(\"hello world\"),\n        ))\n    });\n}\n\n#[divan::bench]\nfn glob_star_both(bencher: Bencher) {\n    // Pattern: *llo wor* - matches anything containing \"llo wor\"\n    bencher.bench_local(|| {\n        black_box(Value::exec_glob(\n            black_box(\"*llo wor*\"),\n            black_box(\"hello world\"),\n        ))\n    });\n}\n\n#[divan::bench]\nfn glob_question_single(bencher: Bencher) {\n    // Pattern: h?llo - matches \"hello\", \"hallo\", etc.\n    bencher.bench_local(|| black_box(Value::exec_glob(black_box(\"h?llo\"), black_box(\"hello\"))));\n}\n\n#[divan::bench]\nfn glob_question_multiple(bencher: Bencher) {\n    // Pattern: h???o - matches 5 character words starting with h, ending with o\n    bencher.bench_local(|| black_box(Value::exec_glob(black_box(\"h???o\"), black_box(\"hello\"))));\n}\n\n#[divan::bench]\nfn glob_character_class(bencher: Bencher) {\n    // Pattern: [abc]* - matches words starting with a, b, or c\n    bencher.bench_local(|| black_box(Value::exec_glob(black_box(\"[abc]*\"), black_box(\"apple\"))));\n}\n\n#[divan::bench]\nfn glob_character_class_range(bencher: Bencher) {\n    // Pattern: [a-z]* - matches words starting with lowercase letter\n    bencher.bench_local(|| black_box(Value::exec_glob(black_box(\"[a-z]*\"), black_box(\"hello\"))));\n}\n\n#[divan::bench]\nfn glob_character_class_negation(bencher: Bencher) {\n    // Pattern: [^0-9]* - matches words not starting with digit\n    bencher.bench_local(|| black_box(Value::exec_glob(black_box(\"[^0-9]*\"), black_box(\"hello\"))));\n}\n\n#[divan::bench]\nfn glob_mixed_wildcards(bencher: Bencher) {\n    // Complex pattern with multiple wildcard types\n    bencher.bench_local(|| {\n        black_box(Value::exec_glob(\n            black_box(\"*h?llo*\"),\n            black_box(\"say hello world\"),\n        ))\n    });\n}\n\n#[divan::bench]\nfn glob_file_path_pattern(bencher: Bencher) {\n    // Common use case: file path matching\n    bencher.bench_local(|| {\n        black_box(Value::exec_glob(\n            black_box(\"*/src/*.rs\"),\n            black_box(\"/home/user/src/main.rs\"),\n        ))\n    });\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn glob_long_pattern(bencher: Bencher) {\n    let pattern = \"The quick brown fox *\";\n    let text = \"The quick brown fox jumps over the lazy dog\";\n    bencher.bench_local(|| black_box(Value::exec_glob(black_box(pattern), black_box(text))));\n}\n\n#[divan::bench]\nfn glob_many_star_wildcards(bencher: Bencher) {\n    // Pattern with multiple * wildcards\n    let pattern = \"*quick*fox*lazy*\";\n    let text = \"The quick brown fox jumps over the lazy dog\";\n    bencher.bench_local(|| black_box(Value::exec_glob(black_box(pattern), black_box(text))));\n}\n\n// =============================================================================\n// GLOB with Cache Benchmarks\n// =============================================================================\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn glob_with_cache_first_call(bencher: Bencher) {\n    bencher.bench_local(|| {\n        black_box(Value::exec_glob(\n            black_box(\"hello*\"),\n            black_box(\"hello world\"),\n        ))\n    });\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn glob_with_cache_cached_hit(bencher: Bencher) {\n    // Warm up the cache\n\n    bencher.bench_local(|| {\n        black_box(Value::exec_glob(\n            black_box(\"hello*\"),\n            black_box(\"hello world\"),\n        ))\n    });\n}\n\n#[divan::bench]\nfn glob_complex_pattern_cached(bencher: Bencher) {\n    let pattern = \"*quick*fox*lazy*\";\n    let text = \"The quick brown fox jumps over the lazy dog\";\n    // Warm up the cache\n\n    bencher.bench_local(|| black_box(Value::exec_glob(black_box(pattern), black_box(text))));\n}\n\n// =============================================================================\n// Edge Cases and Special Patterns\n// =============================================================================\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn like_empty_pattern(bencher: Bencher) {\n    bencher.bench_local(|| {\n        black_box(Value::exec_like(black_box(\"\"), black_box(\"\"), Some('\\\\'))).unwrap()\n    });\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn like_only_percent(bencher: Bencher) {\n    // % matches everything\n    bencher.bench_local(|| {\n        black_box(Value::exec_like(\n            black_box(\"%\"),\n            black_box(\"any string at all\"),\n            Some('\\\\'),\n        ))\n        .unwrap()\n    });\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn glob_only_star(bencher: Bencher) {\n    // * matches everything\n    bencher.bench_local(|| {\n        black_box(Value::exec_glob(\n            black_box(\"*\"),\n            black_box(\"any string at all\"),\n        ))\n    });\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn like_special_regex_chars(bencher: Bencher) {\n    // Pattern with characters that are special in regex (checking for regression/bugs)\n    bencher.bench_local(|| {\n        black_box(Value::exec_like(\n            black_box(\"test.file\"),\n            black_box(\"test.file\"),\n            Some('\\\\'),\n        ))\n        .unwrap()\n    });\n}\n\n#[divan::bench]\nfn glob_bracket_special_cases(bencher: Bencher) {\n    // Test bracket edge cases\n    bencher.bench_local(|| black_box(Value::exec_glob(black_box(\"a[]]b\"), black_box(\"a]b\"))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn like_unicode_pattern(bencher: Bencher) {\n    bencher.bench_local(|| {\n        black_box(Value::exec_like(\n            black_box(\"héllo%\"),\n            black_box(\"héllo world\"),\n            Some('\\\\'),\n        ))\n        .unwrap()\n    });\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn glob_unicode_pattern(bencher: Bencher) {\n    bencher.bench_local(|| {\n        black_box(Value::exec_glob(\n            black_box(\"héllo*\"),\n            black_box(\"héllo world\"),\n        ))\n    });\n}\n"
  },
  {
    "path": "core/benches/sql_functions/main.rs",
    "content": "mod datetime;\nmod likeop;\nmod numeric;\nmod value;\n\nuse divan::AllocProfiler;\nuse mimalloc::MiMalloc;\n\n#[global_allocator]\nstatic ALLOC: AllocProfiler<MiMalloc> = AllocProfiler::new(MiMalloc);\n\nfn main() {\n    divan::main();\n}\n"
  },
  {
    "path": "core/benches/sql_functions/numeric.rs",
    "content": "use divan::{black_box, Bencher};\n#[cfg(feature = \"nanosecond-bench\")]\nuse turso_core::numeric::str_to_i64;\nuse turso_core::numeric::{format_float, str_to_f64, Numeric};\nuse turso_core::types::Value;\n\n// =============================================================================\n// str_to_i64 Benchmarks - Integer String Parsing\n// =============================================================================\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn str_to_i64_simple_positive(bencher: Bencher) {\n    bencher.bench_local(|| black_box(str_to_i64(black_box(\"12345\"))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn str_to_i64_simple_negative(bencher: Bencher) {\n    bencher.bench_local(|| black_box(str_to_i64(black_box(\"-12345\"))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn str_to_i64_with_plus_sign(bencher: Bencher) {\n    bencher.bench_local(|| black_box(str_to_i64(black_box(\"+12345\"))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn str_to_i64_max_value(bencher: Bencher) {\n    bencher.bench_local(|| black_box(str_to_i64(black_box(\"9223372036854775807\"))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn str_to_i64_min_value(bencher: Bencher) {\n    bencher.bench_local(|| black_box(str_to_i64(black_box(\"-9223372036854775808\"))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn str_to_i64_overflow(bencher: Bencher) {\n    // Should return i64::MAX\n    bencher.bench_local(|| black_box(str_to_i64(black_box(\"99999999999999999999999\"))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn str_to_i64_with_whitespace(bencher: Bencher) {\n    bencher.bench_local(|| black_box(str_to_i64(black_box(\"  12345  \"))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn str_to_i64_zero(bencher: Bencher) {\n    bencher.bench_local(|| black_box(str_to_i64(black_box(\"0\"))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn str_to_i64_empty(bencher: Bencher) {\n    bencher.bench_local(|| black_box(str_to_i64(black_box(\"\"))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn str_to_i64_non_numeric(bencher: Bencher) {\n    bencher.bench_local(|| black_box(str_to_i64(black_box(\"abc\"))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn str_to_i64_mixed_content(bencher: Bencher) {\n    // Should parse leading digits\n    bencher.bench_local(|| black_box(str_to_i64(black_box(\"123abc456\"))));\n}\n\n// =============================================================================\n// str_to_f64 Benchmarks - Float String Parsing\n// =============================================================================\n\n#[divan::bench]\nfn str_to_f64_simple_integer(bencher: Bencher) {\n    bencher.bench_local(|| black_box(str_to_f64(black_box(\"12345\"))));\n}\n\n#[divan::bench]\nfn str_to_f64_simple_decimal(bencher: Bencher) {\n    bencher.bench_local(|| black_box(str_to_f64(black_box(\"123.456\"))));\n}\n\n#[divan::bench]\nfn str_to_f64_negative_decimal(bencher: Bencher) {\n    bencher.bench_local(|| black_box(str_to_f64(black_box(\"-123.456\"))));\n}\n\n#[divan::bench]\nfn str_to_f64_scientific_positive_exp(bencher: Bencher) {\n    bencher.bench_local(|| black_box(str_to_f64(black_box(\"1.23e10\"))));\n}\n\n#[divan::bench]\nfn str_to_f64_scientific_negative_exp(bencher: Bencher) {\n    bencher.bench_local(|| black_box(str_to_f64(black_box(\"1.23e-10\"))));\n}\n\n#[divan::bench]\nfn str_to_f64_scientific_uppercase(bencher: Bencher) {\n    bencher.bench_local(|| black_box(str_to_f64(black_box(\"1.23E10\"))));\n}\n\n#[divan::bench]\nfn str_to_f64_very_small(bencher: Bencher) {\n    bencher.bench_local(|| black_box(str_to_f64(black_box(\"0.000000000001\"))));\n}\n\n#[divan::bench]\nfn str_to_f64_very_large(bencher: Bencher) {\n    bencher.bench_local(|| black_box(str_to_f64(black_box(\"999999999999999999999\"))));\n}\n\n#[divan::bench]\nfn str_to_f64_leading_decimal(bencher: Bencher) {\n    bencher.bench_local(|| black_box(str_to_f64(black_box(\".456\"))));\n}\n\n#[divan::bench]\nfn str_to_f64_trailing_decimal(bencher: Bencher) {\n    bencher.bench_local(|| black_box(str_to_f64(black_box(\"123.\"))));\n}\n\n#[divan::bench]\nfn str_to_f64_with_whitespace(bencher: Bencher) {\n    bencher.bench_local(|| black_box(str_to_f64(black_box(\"  123.456  \"))));\n}\n\n#[divan::bench]\nfn str_to_f64_zero(bencher: Bencher) {\n    bencher.bench_local(|| black_box(str_to_f64(black_box(\"0.0\"))));\n}\n\n#[divan::bench]\nfn str_to_f64_many_decimal_places(bencher: Bencher) {\n    bencher.bench_local(|| black_box(str_to_f64(black_box(\"3.141592653589793238462643383279\"))));\n}\n\n#[divan::bench]\nfn str_to_f64_prefix_only(bencher: Bencher) {\n    // Should parse leading number\n    bencher.bench_local(|| black_box(str_to_f64(black_box(\"123.456abc\"))));\n}\n\n// =============================================================================\n// format_float Benchmarks - Float to String Formatting\n// =============================================================================\n\n#[divan::bench]\nfn format_float_simple(bencher: Bencher) {\n    bencher.bench_local(|| black_box(format_float(black_box(123.456))));\n}\n\n#[divan::bench]\nfn format_float_integer(bencher: Bencher) {\n    bencher.bench_local(|| black_box(format_float(black_box(12345.0))));\n}\n\n#[divan::bench]\nfn format_float_negative(bencher: Bencher) {\n    bencher.bench_local(|| black_box(format_float(black_box(-123.456))));\n}\n\n#[divan::bench]\nfn format_float_zero(bencher: Bencher) {\n    bencher.bench_local(|| black_box(format_float(black_box(0.0))));\n}\n\n#[divan::bench]\nfn format_float_very_small(bencher: Bencher) {\n    bencher.bench_local(|| black_box(format_float(black_box(1e-100))));\n}\n\n#[divan::bench]\nfn format_float_very_large(bencher: Bencher) {\n    bencher.bench_local(|| black_box(format_float(black_box(1e100))));\n}\n\n#[divan::bench]\nfn format_float_scientific_needed(bencher: Bencher) {\n    // Should trigger scientific notation\n    bencher.bench_local(|| black_box(format_float(black_box(9.93e-322))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn format_float_nan(bencher: Bencher) {\n    bencher.bench_local(|| black_box(format_float(black_box(f64::NAN))));\n}\n\n#[divan::bench]\nfn format_float_infinity(bencher: Bencher) {\n    bencher.bench_local(|| black_box(format_float(black_box(f64::INFINITY))));\n}\n\n#[divan::bench]\nfn format_float_neg_infinity(bencher: Bencher) {\n    bencher.bench_local(|| black_box(format_float(black_box(f64::NEG_INFINITY))));\n}\n\n#[divan::bench]\nfn format_float_precision_edge(bencher: Bencher) {\n    // Test precision handling\n    bencher.bench_local(|| black_box(format_float(black_box(0.1 + 0.2))));\n}\n\n// =============================================================================\n// Numeric Type Conversion Benchmarks\n// =============================================================================\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn numeric_from_integer_value(bencher: Bencher) {\n    let value = Value::from_i64(12345);\n    bencher.bench_local(|| black_box(Numeric::from_value(black_box(&value))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn numeric_from_float_value(bencher: Bencher) {\n    let value = Value::from_f64(123.456);\n    bencher.bench_local(|| black_box(Numeric::from_value(black_box(&value))));\n}\n\n#[divan::bench]\nfn numeric_from_text_integer(bencher: Bencher) {\n    let value = Value::build_text(\"12345\");\n    bencher.bench_local(|| black_box(Numeric::from_value(black_box(&value))));\n}\n\n#[divan::bench]\nfn numeric_from_text_float(bencher: Bencher) {\n    let value = Value::build_text(\"123.456\");\n    bencher.bench_local(|| black_box(Numeric::from_value(black_box(&value))));\n}\n\n#[divan::bench]\nfn numeric_from_text_scientific(bencher: Bencher) {\n    let value = Value::build_text(\"1.23e10\");\n    bencher.bench_local(|| black_box(Numeric::from_value(black_box(&value))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn numeric_from_null(bencher: Bencher) {\n    let value = Value::Null;\n    bencher.bench_local(|| black_box(Numeric::from_value(black_box(&value))));\n}\n\n#[divan::bench]\nfn numeric_from_blob(bencher: Bencher) {\n    let value = Value::Blob(b\"12345\".to_vec());\n    bencher.bench_local(|| black_box(Numeric::from_value(black_box(&value))));\n}\n\n#[divan::bench]\nfn numeric_from_string_ref(bencher: Bencher) {\n    bencher.bench_local(|| black_box(Numeric::from(black_box(\"123.456\"))));\n}\n\n// =============================================================================\n// Numeric Arithmetic Benchmarks\n// =============================================================================\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn numeric_add_integers(bencher: Bencher) {\n    let a = Numeric::Integer(1000);\n    let b = Numeric::Integer(2000);\n    bencher.bench_local(|| black_box(black_box(a).checked_add(black_box(b))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn numeric_add_floats(bencher: Bencher) {\n    let a = Numeric::from(\"100.5\");\n    let b = Numeric::from(\"200.5\");\n    bencher.bench_local(|| black_box(black_box(a).checked_add(black_box(b))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn numeric_add_mixed(bencher: Bencher) {\n    let a = Numeric::Integer(100);\n    let b = Numeric::from(\"200.5\");\n    bencher.bench_local(|| black_box(black_box(a).checked_add(black_box(b))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn numeric_sub_integers(bencher: Bencher) {\n    let a = Numeric::Integer(2000);\n    let b = Numeric::Integer(1000);\n    bencher.bench_local(|| black_box(black_box(a).checked_sub(black_box(b))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn numeric_mul_integers(bencher: Bencher) {\n    let a = Numeric::Integer(100);\n    let b = Numeric::Integer(200);\n    bencher.bench_local(|| black_box(black_box(a).checked_mul(black_box(b))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn numeric_div_integers(bencher: Bencher) {\n    let a = Numeric::Integer(1000);\n    let b = Numeric::Integer(10);\n    bencher.bench_local(|| black_box(black_box(a).checked_div(black_box(b))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn numeric_neg_integer(bencher: Bencher) {\n    let a = Numeric::Integer(12345);\n    bencher.bench_local(|| black_box(-black_box(a)));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn numeric_add_overflow(bencher: Bencher) {\n    // Should overflow to float\n    let a = Numeric::Integer(i64::MAX);\n    let b = Numeric::Integer(1);\n    bencher.bench_local(|| black_box(black_box(a).checked_add(black_box(b))));\n}\n\n// =============================================================================\n// Numeric Strict Conversion Benchmarks\n// =============================================================================\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn numeric_strict_from_integer(bencher: Bencher) {\n    let value = Value::from_i64(12345);\n    bencher.bench_local(|| black_box(Numeric::from_value_strict(black_box(&value))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn numeric_strict_from_float(bencher: Bencher) {\n    let value = Value::from_f64(123.456);\n    bencher.bench_local(|| black_box(Numeric::from_value_strict(black_box(&value))));\n}\n\n#[divan::bench]\nfn numeric_strict_from_text_valid(bencher: Bencher) {\n    let value = Value::build_text(\"123.456\");\n    bencher.bench_local(|| black_box(Numeric::from_value_strict(black_box(&value))));\n}\n\n#[divan::bench]\nfn numeric_strict_from_text_invalid_prefix(bencher: Bencher) {\n    // Should return Null in strict mode\n    let value = Value::build_text(\"123abc\");\n    bencher.bench_local(|| black_box(Numeric::from_value_strict(black_box(&value))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn numeric_strict_from_blob(bencher: Bencher) {\n    // Blob is always Null in strict mode\n    let value = Value::Blob(b\"12345\".to_vec());\n    bencher.bench_local(|| black_box(Numeric::from_value_strict(black_box(&value))));\n}\n"
  },
  {
    "path": "core/benches/sql_functions/value.rs",
    "content": "use divan::{black_box, Bencher};\nuse turso_core::types::Value;\n\n// =============================================================================\n// String Case Functions\n// =============================================================================\n\n#[divan::bench]\nfn lower_short_string(bencher: Bencher) {\n    let value = Value::build_text(\"HELLO\");\n    bencher.bench_local(|| black_box(black_box(&value).exec_lower()));\n}\n\n#[divan::bench]\nfn lower_long_string(bencher: Bencher) {\n    let value = Value::build_text(\"THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG\");\n    bencher.bench_local(|| black_box(black_box(&value).exec_lower()));\n}\n\n#[divan::bench]\nfn lower_integer(bencher: Bencher) {\n    let value = Value::from_i64(12345);\n    bencher.bench_local(|| black_box(black_box(&value).exec_lower()));\n}\n\n#[divan::bench]\nfn upper_short_string(bencher: Bencher) {\n    let value = Value::build_text(\"hello\");\n    bencher.bench_local(|| black_box(black_box(&value).exec_upper()));\n}\n\n#[divan::bench]\nfn upper_long_string(bencher: Bencher) {\n    let value = Value::build_text(\"the quick brown fox jumps over the lazy dog\");\n    bencher.bench_local(|| black_box(black_box(&value).exec_upper()));\n}\n\n#[divan::bench]\nfn upper_integer(bencher: Bencher) {\n    let value = Value::from_i64(12345);\n    bencher.bench_local(|| black_box(black_box(&value).exec_upper()));\n}\n\n// =============================================================================\n// Length Functions\n// =============================================================================\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn length_short_text(bencher: Bencher) {\n    let value = Value::build_text(\"hello\");\n    bencher.bench_local(|| black_box(black_box(&value).exec_length()));\n}\n\n#[divan::bench]\nfn length_long_text(bencher: Bencher) {\n    let value = Value::build_text(\"the quick brown fox jumps over the lazy dog\");\n    bencher.bench_local(|| black_box(black_box(&value).exec_length()));\n}\n\n#[divan::bench]\nfn length_unicode_text(bencher: Bencher) {\n    let value = Value::build_text(\"héllo wörld 你好世界\");\n    bencher.bench_local(|| black_box(black_box(&value).exec_length()));\n}\n\n#[divan::bench]\nfn length_integer(bencher: Bencher) {\n    let value = Value::from_i64(123456789);\n    bencher.bench_local(|| black_box(black_box(&value).exec_length()));\n}\n\n#[divan::bench]\nfn length_float(bencher: Bencher) {\n    let value = Value::from_f64(123.456789);\n    bencher.bench_local(|| black_box(black_box(&value).exec_length()));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn length_blob(bencher: Bencher) {\n    let value = Value::Blob(vec![0u8; 100]);\n    bencher.bench_local(|| black_box(black_box(&value).exec_length()));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn octet_length_text(bencher: Bencher) {\n    let value = Value::build_text(\"héllo wörld\");\n    bencher.bench_local(|| black_box(black_box(&value).exec_octet_length()));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn octet_length_unicode(bencher: Bencher) {\n    let value = Value::build_text(\"你好世界\");\n    bencher.bench_local(|| black_box(black_box(&value).exec_octet_length()));\n}\n\n// =============================================================================\n// Trim Functions\n// =============================================================================\n\n#[divan::bench]\nfn trim_spaces(bencher: Bencher) {\n    let value = Value::build_text(\"     hello world     \");\n    bencher.bench_local(|| black_box(black_box(&value).exec_trim(None)));\n}\n\n#[divan::bench]\nfn trim_with_pattern(bencher: Bencher) {\n    let value = Value::build_text(\"xxxhello worldxxx\");\n    let pattern = Value::build_text(\"x\");\n    bencher.bench_local(|| black_box(black_box(&value).exec_trim(Some(black_box(&pattern)))));\n}\n\n#[divan::bench]\nfn ltrim_spaces(bencher: Bencher) {\n    let value = Value::build_text(\"     hello world\");\n    bencher.bench_local(|| black_box(black_box(&value).exec_ltrim(None)));\n}\n\n#[divan::bench]\nfn ltrim_with_pattern(bencher: Bencher) {\n    let value = Value::build_text(\"xxxhello world\");\n    let pattern = Value::build_text(\"x\");\n    bencher.bench_local(|| black_box(black_box(&value).exec_ltrim(Some(black_box(&pattern)))));\n}\n\n#[divan::bench]\nfn rtrim_spaces(bencher: Bencher) {\n    let value = Value::build_text(\"hello world     \");\n    bencher.bench_local(|| black_box(black_box(&value).exec_rtrim(None)));\n}\n\n#[divan::bench]\nfn rtrim_with_pattern(bencher: Bencher) {\n    let value = Value::build_text(\"hello worldxxx\");\n    let pattern = Value::build_text(\"x\");\n    bencher.bench_local(|| black_box(black_box(&value).exec_rtrim(Some(black_box(&pattern)))));\n}\n\n// =============================================================================\n// Substring Function\n// =============================================================================\n\n#[divan::bench]\nfn substring_simple(bencher: Bencher) {\n    let value = Value::build_text(\"hello world\");\n    let start = Value::from_i64(1);\n    let length = Value::from_i64(5);\n    bencher.bench_local(|| {\n        black_box(Value::exec_substring(\n            black_box(&value),\n            black_box(&start),\n            Some(black_box(&length)),\n        ))\n    });\n}\n\n#[divan::bench]\nfn substring_long_text(bencher: Bencher) {\n    let value = Value::build_text(\"the quick brown fox jumps over the lazy dog\");\n    let start = Value::from_i64(5);\n    let length = Value::from_i64(15);\n    bencher.bench_local(|| {\n        black_box(Value::exec_substring(\n            black_box(&value),\n            black_box(&start),\n            Some(black_box(&length)),\n        ))\n    });\n}\n\n#[divan::bench]\nfn substring_unicode(bencher: Bencher) {\n    let value = Value::build_text(\"héllo wörld 你好\");\n    let start = Value::from_i64(1);\n    let length = Value::from_i64(10);\n    bencher.bench_local(|| {\n        black_box(Value::exec_substring(\n            black_box(&value),\n            black_box(&start),\n            Some(black_box(&length)),\n        ))\n    });\n}\n\n#[divan::bench]\nfn substring_negative_start(bencher: Bencher) {\n    let value = Value::build_text(\"hello world\");\n    let start = Value::from_i64(-5);\n    let length = Value::from_i64(5);\n    bencher.bench_local(|| {\n        black_box(Value::exec_substring(\n            black_box(&value),\n            black_box(&start),\n            Some(black_box(&length)),\n        ))\n    });\n}\n\n#[divan::bench]\nfn substring_blob(bencher: Bencher) {\n    let value = Value::Blob(b\"hello world\".to_vec());\n    let start = Value::from_i64(1);\n    let length = Value::from_i64(5);\n    bencher.bench_local(|| {\n        black_box(Value::exec_substring(\n            black_box(&value),\n            black_box(&start),\n            Some(black_box(&length)),\n        ))\n    });\n}\n\n// =============================================================================\n// Instr Function\n// =============================================================================\n\n#[divan::bench]\nfn instr_found_early(bencher: Bencher) {\n    let value = Value::build_text(\"hello world\");\n    let pattern = Value::build_text(\"ell\");\n    bencher.bench_local(|| black_box(black_box(&value).exec_instr(black_box(&pattern))));\n}\n\n#[divan::bench]\nfn instr_found_late(bencher: Bencher) {\n    let value = Value::build_text(\"the quick brown fox jumps over the lazy dog\");\n    let pattern = Value::build_text(\"dog\");\n    bencher.bench_local(|| black_box(black_box(&value).exec_instr(black_box(&pattern))));\n}\n\n#[divan::bench]\nfn instr_not_found(bencher: Bencher) {\n    let value = Value::build_text(\"hello world\");\n    let pattern = Value::build_text(\"xyz\");\n    bencher.bench_local(|| black_box(black_box(&value).exec_instr(black_box(&pattern))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn instr_blob(bencher: Bencher) {\n    let value = Value::Blob(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);\n    let pattern = Value::Blob(vec![5, 6, 7]);\n    bencher.bench_local(|| black_box(black_box(&value).exec_instr(black_box(&pattern))));\n}\n\n// =============================================================================\n// Replace Function\n// =============================================================================\n\n#[divan::bench]\nfn replace_single_occurrence(bencher: Bencher) {\n    let source = Value::build_text(\"hello world\");\n    let pattern = Value::build_text(\"world\");\n    let replacement = Value::build_text(\"there\");\n    bencher.bench_local(|| {\n        black_box(Value::exec_replace(\n            black_box(&source),\n            black_box(&pattern),\n            black_box(&replacement),\n        ))\n    });\n}\n\n#[divan::bench]\nfn replace_multiple_occurrences(bencher: Bencher) {\n    let source = Value::build_text(\"banana banana banana\");\n    let pattern = Value::build_text(\"banana\");\n    let replacement = Value::build_text(\"apple\");\n    bencher.bench_local(|| {\n        black_box(Value::exec_replace(\n            black_box(&source),\n            black_box(&pattern),\n            black_box(&replacement),\n        ))\n    });\n}\n\n#[divan::bench]\nfn replace_empty_pattern(bencher: Bencher) {\n    let source = Value::build_text(\"hello world\");\n    let pattern = Value::build_text(\"\");\n    let replacement = Value::build_text(\"x\");\n    bencher.bench_local(|| {\n        black_box(Value::exec_replace(\n            black_box(&source),\n            black_box(&pattern),\n            black_box(&replacement),\n        ))\n    });\n}\n\n// =============================================================================\n// Quote Function\n// =============================================================================\n\n#[divan::bench]\nfn quote_text(bencher: Bencher) {\n    let value = Value::build_text(\"hello world\");\n    bencher.bench_local(|| black_box(black_box(&value).exec_quote()));\n}\n\n#[divan::bench]\nfn quote_text_with_quotes(bencher: Bencher) {\n    let value = Value::build_text(\"hello'world\");\n    bencher.bench_local(|| black_box(black_box(&value).exec_quote()));\n}\n\n#[divan::bench]\nfn quote_integer(bencher: Bencher) {\n    let value = Value::from_i64(12345);\n    bencher.bench_local(|| black_box(black_box(&value).exec_quote()));\n}\n\n#[divan::bench]\nfn quote_blob(bencher: Bencher) {\n    let value = Value::Blob(vec![0x01, 0x02, 0xAB, 0xCD, 0xEF]);\n    bencher.bench_local(|| black_box(black_box(&value).exec_quote()));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn quote_null(bencher: Bencher) {\n    let value = Value::Null;\n    bencher.bench_local(|| black_box(black_box(&value).exec_quote()));\n}\n\n// =============================================================================\n// Soundex Function\n// =============================================================================\n\n#[divan::bench]\nfn soundex_simple(bencher: Bencher) {\n    let value = Value::build_text(\"Robert\");\n    bencher.bench_local(|| black_box(black_box(&value).exec_soundex()));\n}\n\n#[divan::bench]\nfn soundex_complex(bencher: Bencher) {\n    let value = Value::build_text(\"Ashcraft\");\n    bencher.bench_local(|| black_box(black_box(&value).exec_soundex()));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn soundex_non_ascii(bencher: Bencher) {\n    let value = Value::build_text(\"闪电五连鞭\");\n    bencher.bench_local(|| black_box(black_box(&value).exec_soundex()));\n}\n\n// =============================================================================\n// Type Functions\n// =============================================================================\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn typeof_integer(bencher: Bencher) {\n    let value = Value::from_i64(12345);\n    bencher.bench_local(|| black_box(black_box(&value).exec_typeof()));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn typeof_float(bencher: Bencher) {\n    let value = Value::from_f64(123.456);\n    bencher.bench_local(|| black_box(black_box(&value).exec_typeof()));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn typeof_text(bencher: Bencher) {\n    let value = Value::build_text(\"hello\");\n    bencher.bench_local(|| black_box(black_box(&value).exec_typeof()));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn typeof_blob(bencher: Bencher) {\n    let value = Value::Blob(vec![1, 2, 3]);\n    bencher.bench_local(|| black_box(black_box(&value).exec_typeof()));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn typeof_null(bencher: Bencher) {\n    let value = Value::Null;\n    bencher.bench_local(|| black_box(black_box(&value).exec_typeof()));\n}\n\n// =============================================================================\n// Cast Function\n// =============================================================================\n\n#[divan::bench]\nfn cast_integer_to_text(bencher: Bencher) {\n    let value = Value::from_i64(12345);\n    bencher.bench_local(|| black_box(black_box(&value).exec_cast(\"TEXT\")));\n}\n\n#[divan::bench]\nfn cast_float_to_integer(bencher: Bencher) {\n    let value = Value::from_f64(123.456);\n    bencher.bench_local(|| black_box(black_box(&value).exec_cast(\"INT\")));\n}\n\n#[divan::bench]\nfn cast_text_to_integer(bencher: Bencher) {\n    let value = Value::build_text(\"12345\");\n    bencher.bench_local(|| black_box(black_box(&value).exec_cast(\"INT\")));\n}\n\n#[divan::bench]\nfn cast_text_to_real(bencher: Bencher) {\n    let value = Value::build_text(\"123.456\");\n    bencher.bench_local(|| black_box(black_box(&value).exec_cast(\"REAL\")));\n}\n\n#[divan::bench]\nfn cast_text_to_blob(bencher: Bencher) {\n    let value = Value::build_text(\"hello world\");\n    bencher.bench_local(|| black_box(black_box(&value).exec_cast(\"BLOB\")));\n}\n\n#[divan::bench]\nfn cast_text_to_numeric(bencher: Bencher) {\n    let value = Value::build_text(\"123.456\");\n    bencher.bench_local(|| black_box(black_box(&value).exec_cast(\"NUMERIC\")));\n}\n\n// =============================================================================\n// Hex/Unhex Functions\n// =============================================================================\n\n#[divan::bench]\nfn hex_text(bencher: Bencher) {\n    let value = Value::build_text(\"hello\");\n    bencher.bench_local(|| black_box(black_box(&value).exec_hex()));\n}\n\n#[divan::bench]\nfn hex_blob(bencher: Bencher) {\n    let value = Value::Blob(vec![0x01, 0x02, 0xAB, 0xCD, 0xEF]);\n    bencher.bench_local(|| black_box(black_box(&value).exec_hex()));\n}\n\n#[divan::bench]\nfn hex_integer(bencher: Bencher) {\n    let value = Value::from_i64(255);\n    bencher.bench_local(|| black_box(black_box(&value).exec_hex()));\n}\n\n#[divan::bench]\nfn unhex_valid(bencher: Bencher) {\n    let value = Value::build_text(\"48656C6C6F\");\n    bencher.bench_local(|| black_box(black_box(&value).exec_unhex(None)));\n}\n\n#[divan::bench]\nfn unhex_with_ignored(bencher: Bencher) {\n    let value = Value::build_text(\"  48656C6C6F  \");\n    let ignore = Value::build_text(\" \");\n    bencher.bench_local(|| black_box(black_box(&value).exec_unhex(Some(black_box(&ignore)))));\n}\n\n// =============================================================================\n// Unicode Function\n// =============================================================================\n\n#[divan::bench]\nfn unicode_ascii(bencher: Bencher) {\n    let value = Value::build_text(\"A\");\n    bencher.bench_local(|| black_box(black_box(&value).exec_unicode()));\n}\n\n#[divan::bench]\nfn unicode_emoji(bencher: Bencher) {\n    let value = Value::build_text(\"😊\");\n    bencher.bench_local(|| black_box(black_box(&value).exec_unicode()));\n}\n\n#[divan::bench]\nfn unicode_cjk(bencher: Bencher) {\n    let value = Value::build_text(\"你\");\n    bencher.bench_local(|| black_box(black_box(&value).exec_unicode()));\n}\n\n// =============================================================================\n// Numeric Functions\n// =============================================================================\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn abs_positive_integer(bencher: Bencher) {\n    let value = Value::from_i64(12345);\n    bencher.bench_local(|| black_box(black_box(&value).exec_abs()));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn abs_negative_integer(bencher: Bencher) {\n    let value = Value::from_i64(-12345);\n    bencher.bench_local(|| black_box(black_box(&value).exec_abs()));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn abs_float(bencher: Bencher) {\n    let value = Value::from_f64(-123.456);\n    bencher.bench_local(|| black_box(black_box(&value).exec_abs()));\n}\n\n#[divan::bench]\nfn abs_text_numeric(bencher: Bencher) {\n    let value = Value::build_text(\"-123.456\");\n    bencher.bench_local(|| black_box(black_box(&value).exec_abs()));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn sign_positive(bencher: Bencher) {\n    let value = Value::from_i64(42);\n    bencher.bench_local(|| black_box(black_box(&value).exec_sign()));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn sign_negative(bencher: Bencher) {\n    let value = Value::from_i64(-42);\n    bencher.bench_local(|| black_box(black_box(&value).exec_sign()));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn sign_zero(bencher: Bencher) {\n    let value = Value::from_i64(0);\n    bencher.bench_local(|| black_box(black_box(&value).exec_sign()));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn sign_float(bencher: Bencher) {\n    let value = Value::from_f64(-42.5);\n    bencher.bench_local(|| black_box(black_box(&value).exec_sign()));\n}\n\n// =============================================================================\n// Round Function\n// =============================================================================\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn round_no_precision(bencher: Bencher) {\n    let value = Value::from_f64(123.456);\n    bencher.bench_local(|| black_box(black_box(&value).exec_round(None)));\n}\n\n#[divan::bench]\nfn round_with_precision(bencher: Bencher) {\n    let value = Value::from_f64(123.456789);\n    let precision = Value::from_i64(2);\n    bencher.bench_local(|| black_box(black_box(&value).exec_round(Some(black_box(&precision)))));\n}\n\n#[divan::bench]\nfn round_high_precision(bencher: Bencher) {\n    let value = Value::from_f64(std::f64::consts::PI);\n    let precision = Value::from_i64(10);\n    bencher.bench_local(|| black_box(black_box(&value).exec_round(Some(black_box(&precision)))));\n}\n\n// =============================================================================\n// Log Function\n// =============================================================================\n\n#[divan::bench]\nfn log_base_10(bencher: Bencher) {\n    let value = Value::from_f64(100.0);\n    bencher.bench_local(|| black_box(black_box(&value).exec_math_log(None)));\n}\n\n#[divan::bench]\nfn log_base_2(bencher: Bencher) {\n    let value = Value::from_f64(8.0);\n    let base = Value::from_f64(2.0);\n    bencher.bench_local(|| black_box(black_box(&value).exec_math_log(Some(black_box(&base)))));\n}\n\n#[divan::bench]\nfn log_arbitrary_base(bencher: Bencher) {\n    let value = Value::from_f64(100.0);\n    let base = Value::from_f64(7.0);\n    bencher.bench_local(|| black_box(black_box(&value).exec_math_log(Some(black_box(&base)))));\n}\n\n// =============================================================================\n// Arithmetic Operations\n// =============================================================================\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn add_integers(bencher: Bencher) {\n    let a = Value::from_i64(1000);\n    let b = Value::from_i64(2000);\n    bencher.bench_local(|| black_box(black_box(&a).exec_add(black_box(&b))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn add_floats(bencher: Bencher) {\n    let a = Value::from_f64(100.5);\n    let b = Value::from_f64(200.5);\n    bencher.bench_local(|| black_box(black_box(&a).exec_add(black_box(&b))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn add_mixed(bencher: Bencher) {\n    let a = Value::from_i64(100);\n    let b = Value::from_f64(200.5);\n    bencher.bench_local(|| black_box(black_box(&a).exec_add(black_box(&b))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn subtract_integers(bencher: Bencher) {\n    let a = Value::from_i64(2000);\n    let b = Value::from_i64(1000);\n    bencher.bench_local(|| black_box(black_box(&a).exec_subtract(black_box(&b))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn multiply_integers(bencher: Bencher) {\n    let a = Value::from_i64(100);\n    let b = Value::from_i64(200);\n    bencher.bench_local(|| black_box(black_box(&a).exec_multiply(black_box(&b))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn divide_integers(bencher: Bencher) {\n    let a = Value::from_i64(1000);\n    let b = Value::from_i64(10);\n    bencher.bench_local(|| black_box(black_box(&a).exec_divide(black_box(&b))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn remainder_integers(bencher: Bencher) {\n    let a = Value::from_i64(17);\n    let b = Value::from_i64(5);\n    bencher.bench_local(|| black_box(black_box(&a).exec_remainder(black_box(&b))));\n}\n\n// =============================================================================\n// Bitwise Operations\n// =============================================================================\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn bit_and(bencher: Bencher) {\n    let a = Value::from_i64(0b11110000);\n    let b = Value::from_i64(0b10101010);\n    bencher.bench_local(|| black_box(black_box(&a).exec_bit_and(black_box(&b))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn bit_or(bencher: Bencher) {\n    let a = Value::from_i64(0b11110000);\n    let b = Value::from_i64(0b00001111);\n    bencher.bench_local(|| black_box(black_box(&a).exec_bit_or(black_box(&b))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn bit_not(bencher: Bencher) {\n    let a = Value::from_i64(0b11110000);\n    bencher.bench_local(|| black_box(black_box(&a).exec_bit_not()));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn shift_left(bencher: Bencher) {\n    let a = Value::from_i64(1);\n    let b = Value::from_i64(8);\n    bencher.bench_local(|| black_box(black_box(&a).exec_shift_left(black_box(&b))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn shift_right(bencher: Bencher) {\n    let a = Value::from_i64(256);\n    let b = Value::from_i64(4);\n    bencher.bench_local(|| black_box(black_box(&a).exec_shift_right(black_box(&b))));\n}\n\n// =============================================================================\n// Boolean Operations\n// =============================================================================\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn boolean_not_true(bencher: Bencher) {\n    let value = Value::from_i64(1);\n    bencher.bench_local(|| black_box(black_box(&value).exec_boolean_not()));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn boolean_not_false(bencher: Bencher) {\n    let value = Value::from_i64(0);\n    bencher.bench_local(|| black_box(black_box(&value).exec_boolean_not()));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn and_true_true(bencher: Bencher) {\n    let a = Value::from_i64(1);\n    let b = Value::from_i64(1);\n    bencher.bench_local(|| black_box(black_box(&a).exec_and(black_box(&b))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn and_true_false(bencher: Bencher) {\n    let a = Value::from_i64(1);\n    let b = Value::from_i64(0);\n    bencher.bench_local(|| black_box(black_box(&a).exec_and(black_box(&b))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn or_false_false(bencher: Bencher) {\n    let a = Value::from_i64(0);\n    let b = Value::from_i64(0);\n    bencher.bench_local(|| black_box(black_box(&a).exec_or(black_box(&b))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn or_true_false(bencher: Bencher) {\n    let a = Value::from_i64(1);\n    let b = Value::from_i64(0);\n    bencher.bench_local(|| black_box(black_box(&a).exec_or(black_box(&b))));\n}\n\n// =============================================================================\n// Concat Functions\n// =============================================================================\n\n#[divan::bench]\nfn concat_two_strings(bencher: Bencher) {\n    let a = Value::build_text(\"hello \");\n    let b = Value::build_text(\"world\");\n    bencher.bench_local(|| black_box(black_box(&a).exec_concat(black_box(&b))));\n}\n\n#[divan::bench]\nfn concat_string_integer(bencher: Bencher) {\n    let a = Value::build_text(\"count: \");\n    let b = Value::from_i64(42);\n    bencher.bench_local(|| black_box(black_box(&a).exec_concat(black_box(&b))));\n}\n\n#[divan::bench]\nfn concat_blobs(bencher: Bencher) {\n    let a = Value::Blob(b\"hello \".to_vec());\n    let b = Value::Blob(b\"world\".to_vec());\n    bencher.bench_local(|| black_box(black_box(&a).exec_concat(black_box(&b))));\n}\n\n#[divan::bench]\nfn concat_strings_multiple(bencher: Bencher) {\n    let values = [\n        Value::build_text(\"the \"),\n        Value::build_text(\"quick \"),\n        Value::build_text(\"brown \"),\n        Value::build_text(\"fox\"),\n    ];\n    bencher.bench_local(|| black_box(Value::exec_concat_strings(black_box(values.iter()))));\n}\n\n#[divan::bench]\nfn concat_ws_strings(bencher: Bencher) {\n    let values = [\n        Value::build_text(\", \"),\n        Value::build_text(\"apple\"),\n        Value::build_text(\"banana\"),\n        Value::build_text(\"cherry\"),\n    ];\n    bencher.bench_local(|| black_box(Value::exec_concat_ws(black_box(values.iter()))));\n}\n\n// =============================================================================\n// Char Function\n// =============================================================================\n\n#[divan::bench]\nfn char_single(bencher: Bencher) {\n    let values = [Value::from_i64(65)];\n    bencher.bench_local(|| black_box(Value::exec_char(black_box(values.iter()))));\n}\n\n#[divan::bench]\nfn char_multiple(bencher: Bencher) {\n    let values = [\n        Value::from_i64(72),\n        Value::from_i64(101),\n        Value::from_i64(108),\n        Value::from_i64(108),\n        Value::from_i64(111),\n    ];\n    bencher.bench_local(|| black_box(Value::exec_char(black_box(values.iter()))));\n}\n\n// =============================================================================\n// Min/Max Functions\n// =============================================================================\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn min_integers(bencher: Bencher) {\n    let values = [\n        Value::from_i64(5),\n        Value::from_i64(3),\n        Value::from_i64(8),\n        Value::from_i64(1),\n        Value::from_i64(9),\n    ];\n    bencher.bench_local(|| black_box(Value::exec_min(black_box(values.iter()))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn max_integers(bencher: Bencher) {\n    let values = [\n        Value::from_i64(5),\n        Value::from_i64(3),\n        Value::from_i64(8),\n        Value::from_i64(1),\n        Value::from_i64(9),\n    ];\n    bencher.bench_local(|| black_box(Value::exec_max(black_box(values.iter()))));\n}\n\n#[divan::bench]\nfn min_strings(bencher: Bencher) {\n    let values = [\n        Value::build_text(\"banana\"),\n        Value::build_text(\"apple\"),\n        Value::build_text(\"cherry\"),\n    ];\n    bencher.bench_local(|| black_box(Value::exec_min(black_box(values.iter()))));\n}\n\n#[divan::bench]\nfn max_strings(bencher: Bencher) {\n    let values = [\n        Value::build_text(\"banana\"),\n        Value::build_text(\"apple\"),\n        Value::build_text(\"cherry\"),\n    ];\n    bencher.bench_local(|| black_box(Value::exec_max(black_box(values.iter()))));\n}\n\n// =============================================================================\n// Nullif Function\n// =============================================================================\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn nullif_equal(bencher: Bencher) {\n    let a = Value::from_i64(42);\n    let b = Value::from_i64(42);\n    bencher.bench_local(|| black_box(black_box(&a).exec_nullif(black_box(&b))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn nullif_not_equal(bencher: Bencher) {\n    let a = Value::from_i64(42);\n    let b = Value::from_i64(100);\n    bencher.bench_local(|| black_box(black_box(&a).exec_nullif(black_box(&b))));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn nullif_strings(bencher: Bencher) {\n    let a = Value::build_text(\"hello\");\n    let b = Value::build_text(\"hello\");\n    bencher.bench_local(|| black_box(black_box(&a).exec_nullif(black_box(&b))));\n}\n\n// =============================================================================\n// Zeroblob Function\n// =============================================================================\n\n#[divan::bench]\nfn zeroblob_small(bencher: Bencher) {\n    let value = Value::from_i64(10);\n    bencher.bench_local(|| black_box(black_box(&value).exec_zeroblob().unwrap()));\n}\n\n#[divan::bench]\nfn zeroblob_medium(bencher: Bencher) {\n    let value = Value::from_i64(1000);\n    bencher.bench_local(|| black_box(black_box(&value).exec_zeroblob().unwrap()));\n}\n\n#[divan::bench]\nfn zeroblob_large(bencher: Bencher) {\n    let value = Value::from_i64(10000);\n    bencher.bench_local(|| black_box(black_box(&value).exec_zeroblob().unwrap()));\n}\n\n// =============================================================================\n// If/Conditional Function\n// =============================================================================\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn exec_if_true(bencher: Bencher) {\n    let value = Value::from_i64(1);\n    bencher.bench_local(|| black_box(black_box(&value).exec_if(false, false)));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn exec_if_false(bencher: Bencher) {\n    let value = Value::from_i64(0);\n    bencher.bench_local(|| black_box(black_box(&value).exec_if(false, false)));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn exec_if_null(bencher: Bencher) {\n    let value = Value::Null;\n    bencher.bench_local(|| black_box(black_box(&value).exec_if(true, false)));\n}\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn exec_if_not(bencher: Bencher) {\n    let value = Value::from_i64(1);\n    bencher.bench_local(|| black_box(black_box(&value).exec_if(false, true)));\n}\n\n// =============================================================================\n// LIKE Pattern\n// =============================================================================\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn construct_like_exact(bencher: Bencher) {\n    bencher.bench_local(|| {\n        black_box(Value::exec_like(\n            black_box(\"hello\"),\n            black_box(\"hello\"),\n            None,\n        ))\n        .unwrap()\n    });\n}\n\n#[divan::bench]\nfn construct_like_contains(bencher: Bencher) {\n    bencher.bench_local(|| {\n        black_box(Value::exec_like(\n            black_box(\"%hello%\"),\n            black_box(\"hello\"),\n            None,\n        ))\n        .unwrap()\n    });\n}\n\n#[divan::bench]\nfn construct_like_with_single_wildcard(bencher: Bencher) {\n    bencher.bench_local(|| {\n        black_box(Value::exec_like(\n            black_box(\"h_llo\"),\n            black_box(\"hello\"),\n            None,\n        ))\n        .unwrap()\n    });\n}\n\n#[divan::bench]\nfn construct_like_complex(bencher: Bencher) {\n    bencher.bench_local(|| {\n        black_box(Value::exec_like(\n            black_box(\"%h_llo%w_rld%\"),\n            black_box(\"hello world\"),\n            None,\n        ))\n        .unwrap()\n    });\n}\n\n// =============================================================================\n// Random Functions\n// =============================================================================\n\n#[cfg(feature = \"nanosecond-bench\")]\n#[divan::bench]\nfn exec_random(bencher: Bencher) {\n    bencher.bench_local(|| black_box(Value::exec_random(|| 42)));\n}\n\n#[divan::bench]\nfn exec_randomblob_small(bencher: Bencher) {\n    let length = Value::from_i64(10);\n    bencher.bench_local(|| {\n        black_box(\n            black_box(&length)\n                .exec_randomblob(|buf| buf.fill(0))\n                .unwrap(),\n        )\n    });\n}\n\n#[divan::bench]\nfn exec_randomblob_medium(bencher: Bencher) {\n    let length = Value::from_i64(100);\n    bencher.bench_local(|| {\n        black_box(\n            black_box(&length)\n                .exec_randomblob(|buf| buf.fill(0))\n                .unwrap(),\n        )\n    });\n}\n\n#[divan::bench]\nfn exec_randomblob_large(bencher: Bencher) {\n    let length = Value::from_i64(1000);\n    bencher.bench_local(|| {\n        black_box(\n            black_box(&length)\n                .exec_randomblob(|buf| buf.fill(0))\n                .unwrap(),\n        )\n    });\n}\n"
  },
  {
    "path": "core/benches/tpc_h_benchmark.rs",
    "content": "use std::sync::Arc;\n\n#[cfg(not(feature = \"codspeed\"))]\nuse criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, SamplingMode};\n#[cfg(not(feature = \"codspeed\"))]\nuse pprof::criterion::{Output, PProfProfiler};\n\n#[cfg(feature = \"codspeed\")]\nuse codspeed_criterion_compat::{\n    black_box, criterion_group, criterion_main, BenchmarkId, Criterion, SamplingMode,\n};\n\nuse turso_core::{Database, PlatformIO};\n\nconst TPC_H_PATH: &str = \"../perf/tpc-h/TPC-H.db\";\n\nmacro_rules! tpc_query {\n    ($num:literal) => {\n        (\n            $num,\n            include_str!(concat!(\"../../perf/tpc-h/queries/\", $num, \".sql\")),\n        )\n    };\n}\n\nfn rusqlite_open_tpc_h() -> rusqlite::Connection {\n    let sqlite_conn = rusqlite::Connection::open(TPC_H_PATH).unwrap();\n    sqlite_conn\n        .pragma_update(None, \"locking_mode\", \"EXCLUSIVE\")\n        .unwrap();\n    sqlite_conn\n}\n\nfn bench_tpc_h_queries(criterion: &mut Criterion) {\n    // https://github.com/tursodatabase/turso/issues/174\n    // The rusqlite benchmark crashes on Mac M1 when using the flamegraph features\n    let enable_rusqlite = std::env::var(\"DISABLE_RUSQLITE_BENCHMARK\").is_err();\n\n    #[allow(clippy::arc_with_non_send_sync)]\n    let io = Arc::new(PlatformIO::new().unwrap());\n    let db = Database::open_file(io, TPC_H_PATH).unwrap();\n    let limbo_conn = db.connect().unwrap();\n\n    let queries = [\n        tpc_query!(1),\n        // tpc_query!(2), // Skipped as subquery in bind_column references is todo!\n        tpc_query!(3),\n        // thread 'main' panicked at core/translate/planner.rs:256:28:\n        // not yet implemented\n        // tpc_query!(4),\n        tpc_query!(5),\n        tpc_query!(6),\n        tpc_query!(7),\n        tpc_query!(8),\n        tpc_query!(9),\n        tpc_query!(10),\n        // tpc_query!(11), // Skipped not implemented\n        tpc_query!(12),\n        // thread 'main' panicked at core/storage/btree.rs:3233:26:\n        // overflow cell with divider cell was not found\n        // tpc_query!(13),\n        tpc_query!(14),\n        // thread 'main' panicked at core/benches/tpc_h_benchmark.rs:71:62:\n        // called `Result::unwrap()` on an `Err` value: ParseError(\"CREATE VIEW not supported yet\")\n        // tpc_query!(15),\n\n        // thread 'main' panicked at core/translate/planner.rs:267:34:\n        // not yet implemented\n        // tpc_query!(16),\n\n        // thread 'main' panicked at core/translate/planner.rs:291:30:\n        // not yet implemented\n        // tpc_query!(17),\n\n        // thread 'main' panicked at core/translate/planner.rs:267:34:\n        // not yet implemented\n        // tpc_query!(18),\n        tpc_query!(19),\n        // thread 'main' panicked at core/translate/planner.rs:267:34:\n        // not yet implemented\n        // tpc_query!(20),\n\n        // thread 'main' panicked at core/translate/planner.rs:256:28:\n        // not yet implemented\n        // tpc_query!(21),\n        // thread 'main' panicked at core/translate/planner.rs:291:30:\n        // not yet implemented\n        // tpc_query!(22),\n    ];\n\n    for (idx, query) in queries.iter() {\n        let mut group = criterion.benchmark_group(format!(\"Query `{idx}` \"));\n        group.sampling_mode(SamplingMode::Flat);\n        group.sample_size(10);\n\n        group.bench_with_input(\n            BenchmarkId::new(\"limbo_tpc_h_query\", idx),\n            query,\n            |b, query| {\n                b.iter(|| {\n                    let mut stmt = limbo_conn.prepare(query).unwrap();\n                    loop {\n                        match stmt.step().unwrap() {\n                            turso_core::StepResult::Row => {\n                                black_box(stmt.row());\n                            }\n                            turso_core::StepResult::IO => {\n                                db.io.step().unwrap();\n                            }\n                            turso_core::StepResult::Done => {\n                                break;\n                            }\n                            turso_core::StepResult::Interrupt | turso_core::StepResult::Busy => {\n                                unreachable!();\n                            }\n                        }\n                    }\n                    stmt.reset().unwrap();\n                });\n            },\n        );\n\n        if enable_rusqlite {\n            let sqlite_conn = rusqlite_open_tpc_h();\n\n            group.bench_with_input(\n                BenchmarkId::new(\"sqlite_tpc_h_query\", idx),\n                query,\n                |b, query| {\n                    let mut stmt = sqlite_conn.prepare(query).unwrap();\n                    b.iter(|| {\n                        let mut rows = stmt.raw_query();\n                        while let Some(row) = rows.next().unwrap() {\n                            black_box(row);\n                        }\n                    });\n                },\n            );\n        }\n\n        group.finish();\n    }\n}\n\n#[cfg(not(feature = \"codspeed\"))]\ncriterion_group! {\n    name = benches;\n    config = Criterion::default().with_profiler(PProfProfiler::new(100, Output::Flamegraph(None)));\n    targets = bench_tpc_h_queries\n}\n\n#[cfg(feature = \"codspeed\")]\ncriterion_group! {\n    name = benches;\n    config = Criterion::default();\n    targets = bench_tpc_h_queries\n}\n\ncriterion_main!(benches);\n"
  },
  {
    "path": "core/benches/write_perf_benchmark.rs",
    "content": "//! Write Performance Benchmarks\n//!\n//! This module contains benchmarks specifically designed to measure and identify\n//! write/INSERT performance bottlenecks, including:\n//! - Index overhead impact\n//! - Transaction size impact\n//! - Sequential vs random key patterns\n//! - UPDATE vs INSERT performance\n//!\n//! Run with: cargo bench --bench write_perf_benchmark\n\n#[cfg(not(feature = \"codspeed\"))]\nuse criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};\n#[cfg(not(feature = \"codspeed\"))]\nuse pprof::criterion::{Output, PProfProfiler};\n\n#[cfg(feature = \"codspeed\")]\nuse codspeed_criterion_compat::{\n    criterion_group, criterion_main, BenchmarkId, Criterion, Throughput,\n};\n\nuse std::sync::Arc;\nuse tempfile::TempDir;\nuse turso_core::{Database, PlatformIO, StepResult};\n\n#[cfg(not(target_family = \"wasm\"))]\n#[global_allocator]\nstatic GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;\n\n/// Helper to execute a statement to completion\nfn run_to_completion(\n    stmt: &mut turso_core::Statement,\n    db: &Arc<Database>,\n) -> turso_core::Result<()> {\n    loop {\n        match stmt.step()? {\n            StepResult::IO => {\n                db.io.step()?;\n            }\n            StepResult::Done => break,\n            StepResult::Row => {}\n            StepResult::Interrupt | StepResult::Busy => {\n                panic!(\"Unexpected step result\");\n            }\n        }\n    }\n    Ok(())\n}\n\n/// Helper to setup a limbo database with the given schema\nfn setup_limbo(temp_dir: &TempDir, schema: &str) -> Arc<Database> {\n    setup_limbo_with_sync(temp_dir, schema, true)\n}\n\n/// Helper to setup a limbo database with optional sync\nfn setup_limbo_with_sync(temp_dir: &TempDir, schema: &str, sync_on: bool) -> Arc<Database> {\n    let db_path = temp_dir.path().join(\"bench.db\");\n    #[allow(clippy::arc_with_non_send_sync)]\n    let io = Arc::new(PlatformIO::new().unwrap());\n    let db = Database::open_file(io, db_path.to_str().unwrap()).unwrap();\n    let conn = db.connect().unwrap();\n\n    // Set synchronous mode\n    let sync_mode = if sync_on { \"FULL\" } else { \"OFF\" };\n    let mut stmt = conn\n        .query(format!(\"PRAGMA synchronous = {sync_mode}\"))\n        .unwrap()\n        .unwrap();\n    run_to_completion(&mut stmt, &db).unwrap();\n\n    // Execute schema\n    let mut stmt = conn.query(schema).unwrap().unwrap();\n    run_to_completion(&mut stmt, &db).unwrap();\n\n    db\n}\n\n/// Helper to setup rusqlite with the given schema\nfn setup_rusqlite(temp_dir: &TempDir, schema: &str) -> rusqlite::Connection {\n    let db_path = temp_dir.path().join(\"bench.db\");\n    let conn = rusqlite::Connection::open(db_path).unwrap();\n    conn.pragma_update(None, \"synchronous\", \"FULL\").unwrap();\n    conn.pragma_update(None, \"journal_mode\", \"WAL\").unwrap();\n    conn.pragma_update(None, \"locking_mode\", \"EXCLUSIVE\")\n        .unwrap();\n    // Use execute_batch to handle multiple statements (e.g., CREATE TABLE + CREATE INDEX)\n    conn.execute_batch(schema).unwrap();\n    conn\n}\n\n/// Benchmark: Impact of indexes on INSERT performance\n///\n/// This benchmark measures how adding indexes affects INSERT throughput.\n/// Each index adds overhead due to:\n/// 1. Seek operations for uniqueness checks\n/// 2. Additional B-tree insertions\n/// 3. Page splits on index pages\nfn bench_index_impact(criterion: &mut Criterion) {\n    let enable_rusqlite =\n        std::env::var(\"DISABLE_RUSQLITE_BENCHMARK\").is_err() && !cfg!(feature = \"codspeed\");\n    let batch_size = 100;\n\n    let mut group = criterion.benchmark_group(\"Index Impact on INSERT\");\n    group.throughput(Throughput::Elements(batch_size as u64));\n\n    // Test configurations: (name, schema, insert_sql)\n    let configs = [\n        (\n            \"0_indexes\",\n            \"CREATE TABLE test (id INTEGER, val1 TEXT, val2 INTEGER, val3 REAL)\",\n            \"INSERT INTO test VALUES \",\n        ),\n        (\n            \"1_index_pk\",\n            \"CREATE TABLE test (id INTEGER PRIMARY KEY, val1 TEXT, val2 INTEGER, val3 REAL)\",\n            \"INSERT INTO test VALUES \",\n        ),\n        (\n            \"2_indexes\",\n            \"CREATE TABLE test (id INTEGER PRIMARY KEY, val1 TEXT, val2 INTEGER, val3 REAL); \\\n             CREATE INDEX idx_val2 ON test(val2)\",\n            \"INSERT INTO test VALUES \",\n        ),\n        (\n            \"3_indexes\",\n            \"CREATE TABLE test (id INTEGER PRIMARY KEY, val1 TEXT, val2 INTEGER, val3 REAL); \\\n             CREATE INDEX idx_val2 ON test(val2); \\\n             CREATE INDEX idx_val3 ON test(val3)\",\n            \"INSERT INTO test VALUES \",\n        ),\n    ];\n\n    for (name, schema, insert_prefix) in configs {\n        // Build multi-row insert statement\n        let mut values = String::from(insert_prefix);\n        for i in 0..batch_size {\n            if i > 0 {\n                values.push(',');\n            }\n            values.push_str(&format!(\"({}, 'value_{}', {}, {}.5)\", i, i, i * 10, i));\n        }\n\n        // Limbo benchmark\n        let temp_dir = tempfile::tempdir().unwrap();\n        let db = setup_limbo(&temp_dir, schema);\n        let conn = db.connect().unwrap();\n\n        group.bench_function(BenchmarkId::new(\"limbo\", name), |b| {\n            let mut insert_stmt = conn.prepare(&values).unwrap();\n            let mut delete_stmt = conn.query(\"DELETE FROM test\").unwrap().unwrap();\n            b.iter_custom(|iters| {\n                let mut total = std::time::Duration::ZERO;\n                for _ in 0..iters {\n                    let start = std::time::Instant::now();\n                    run_to_completion(&mut insert_stmt, &db).unwrap();\n                    total += start.elapsed();\n                    insert_stmt.reset().unwrap();\n                    // Clear table for next iteration (not timed)\n                    run_to_completion(&mut delete_stmt, &db).unwrap();\n                    delete_stmt.reset().unwrap();\n                }\n                total\n            });\n        });\n\n        // SQLite benchmark\n        if enable_rusqlite {\n            let temp_dir = tempfile::tempdir().unwrap();\n            let sqlite_conn = setup_rusqlite(&temp_dir, schema);\n\n            group.bench_function(BenchmarkId::new(\"sqlite\", name), |b| {\n                let mut stmt = sqlite_conn.prepare(&values).unwrap();\n                b.iter_custom(|iters| {\n                    let mut total = std::time::Duration::ZERO;\n                    for _ in 0..iters {\n                        let start = std::time::Instant::now();\n                        stmt.raw_execute().unwrap();\n                        total += start.elapsed();\n                        // Clear table for next iteration (not timed)\n                        sqlite_conn.execute(\"DELETE FROM test\", []).unwrap();\n                    }\n                    total\n                });\n            });\n        }\n    }\n\n    group.finish();\n}\n\n/// Benchmark: Transaction size impact on INSERT throughput\n///\n/// Measures how the number of rows per transaction affects throughput.\n/// Larger transactions amortize commit overhead but increase memory pressure.\nfn bench_transaction_size(criterion: &mut Criterion) {\n    let enable_rusqlite =\n        std::env::var(\"DISABLE_RUSQLITE_BENCHMARK\").is_err() && !cfg!(feature = \"codspeed\");\n\n    let mut group = criterion.benchmark_group(\"Transaction Size Impact\");\n\n    // Different transaction sizes (rows per transaction)\n    let tx_sizes = [1, 10, 50, 100, 500, 1000];\n\n    for tx_size in tx_sizes {\n        group.throughput(Throughput::Elements(tx_size as u64));\n\n        // Build multi-row insert\n        let mut values = String::from(\"INSERT INTO test VALUES \");\n        for i in 0..tx_size {\n            if i > 0 {\n                values.push(',');\n            }\n            values.push_str(&format!(\"({i}, 'data_{i}')\"));\n        }\n\n        // Limbo benchmark\n        let temp_dir = tempfile::tempdir().unwrap();\n        let db = setup_limbo(\n            &temp_dir,\n            \"CREATE TABLE test (id INTEGER PRIMARY KEY, data TEXT)\",\n        );\n        let conn = db.connect().unwrap();\n\n        group.bench_function(BenchmarkId::new(\"limbo\", format!(\"{tx_size}_rows\")), |b| {\n            let mut begin = conn.query(\"BEGIN\").unwrap().unwrap();\n            let mut commit = conn.query(\"COMMIT\").unwrap().unwrap();\n            let mut insert_stmt = conn.prepare(&values).unwrap();\n            let mut delete_stmt = conn.query(\"DELETE FROM test\").unwrap().unwrap();\n\n            b.iter_custom(|iters| {\n                let mut total = std::time::Duration::ZERO;\n                for _ in 0..iters {\n                    let start = std::time::Instant::now();\n                    run_to_completion(&mut begin, &db).unwrap();\n                    begin.reset().unwrap();\n                    run_to_completion(&mut insert_stmt, &db).unwrap();\n                    insert_stmt.reset().unwrap();\n                    run_to_completion(&mut commit, &db).unwrap();\n                    commit.reset().unwrap();\n                    total += start.elapsed();\n                    // Clear for next iteration (not timed)\n                    run_to_completion(&mut delete_stmt, &db).unwrap();\n                    delete_stmt.reset().unwrap();\n                }\n                total\n            });\n        });\n\n        // SQLite benchmark\n        if enable_rusqlite {\n            let temp_dir = tempfile::tempdir().unwrap();\n            let sqlite_conn = setup_rusqlite(\n                &temp_dir,\n                \"CREATE TABLE test (id INTEGER PRIMARY KEY, data TEXT)\",\n            );\n\n            group.bench_function(BenchmarkId::new(\"sqlite\", format!(\"{tx_size}_rows\")), |b| {\n                b.iter_custom(|iters| {\n                    let mut total = std::time::Duration::ZERO;\n                    for _ in 0..iters {\n                        let start = std::time::Instant::now();\n                        sqlite_conn.execute(\"BEGIN\", []).unwrap();\n                        sqlite_conn.execute(&values, []).unwrap();\n                        sqlite_conn.execute(\"COMMIT\", []).unwrap();\n                        total += start.elapsed();\n                        // Clear for next iteration (not timed)\n                        sqlite_conn.execute(\"DELETE FROM test\", []).unwrap();\n                    }\n                    total\n                });\n            });\n        }\n    }\n\n    group.finish();\n}\n\n/// Benchmark: Sequential vs Random key insertion patterns\n///\n/// Sequential keys (monotonically increasing) are typically faster because:\n/// 1. Balance quick path can be used (appending to rightmost leaf)\n/// 2. Better cache locality\n/// 3. Fewer page splits\nfn bench_key_pattern(criterion: &mut Criterion) {\n    let enable_rusqlite =\n        std::env::var(\"DISABLE_RUSQLITE_BENCHMARK\").is_err() && !cfg!(feature = \"codspeed\");\n    let batch_size = 100;\n\n    let mut group = criterion.benchmark_group(\"Key Pattern Impact\");\n    group.throughput(Throughput::Elements(batch_size as u64));\n\n    // Generate random keys (pre-computed for consistency)\n    let random_keys: Vec<i64> = (0..batch_size)\n        .map(|i| {\n            // Simple LCG for reproducible \"random\" keys\n            let mut x = (i as i64 * 1103515245 + 12345) % (1 << 31);\n            x = x.abs();\n            x\n        })\n        .collect();\n\n    // Sequential insert\n    let mut seq_values = String::from(\"INSERT INTO test VALUES \");\n    for i in 0..batch_size {\n        if i > 0 {\n            seq_values.push(',');\n        }\n        seq_values.push_str(&format!(\"({i}, 'data_{i}')\"));\n    }\n\n    // Random insert\n    let mut rand_values = String::from(\"INSERT INTO test VALUES \");\n    for (i, key) in random_keys.iter().enumerate() {\n        if i > 0 {\n            rand_values.push(',');\n        }\n        rand_values.push_str(&format!(\"({key}, 'data_{i}')\"));\n    }\n\n    // Limbo sequential\n    let temp_dir = tempfile::tempdir().unwrap();\n    let db = setup_limbo(\n        &temp_dir,\n        \"CREATE TABLE test (id INTEGER PRIMARY KEY, data TEXT)\",\n    );\n    let conn = db.connect().unwrap();\n\n    group.bench_function(BenchmarkId::new(\"limbo\", \"sequential_keys\"), |b| {\n        let mut stmt = conn.prepare(&seq_values).unwrap();\n        let mut delete_stmt = conn.query(\"DELETE FROM test\").unwrap().unwrap();\n        b.iter_custom(|iters| {\n            let mut total = std::time::Duration::ZERO;\n            for _ in 0..iters {\n                let start = std::time::Instant::now();\n                run_to_completion(&mut stmt, &db).unwrap();\n                total += start.elapsed();\n                stmt.reset().unwrap();\n                run_to_completion(&mut delete_stmt, &db).unwrap();\n                delete_stmt.reset().unwrap();\n            }\n            total\n        });\n    });\n\n    // Limbo random\n    let temp_dir = tempfile::tempdir().unwrap();\n    let db = setup_limbo(\n        &temp_dir,\n        \"CREATE TABLE test (id INTEGER PRIMARY KEY, data TEXT)\",\n    );\n    let conn = db.connect().unwrap();\n\n    group.bench_function(BenchmarkId::new(\"limbo\", \"random_keys\"), |b| {\n        let mut stmt = conn.prepare(&rand_values).unwrap();\n        let mut delete_stmt = conn.query(\"DELETE FROM test\").unwrap().unwrap();\n        b.iter_custom(|iters| {\n            let mut total = std::time::Duration::ZERO;\n            for _ in 0..iters {\n                let start = std::time::Instant::now();\n                run_to_completion(&mut stmt, &db).unwrap();\n                total += start.elapsed();\n                stmt.reset().unwrap();\n                run_to_completion(&mut delete_stmt, &db).unwrap();\n                delete_stmt.reset().unwrap();\n            }\n            total\n        });\n    });\n\n    if enable_rusqlite {\n        // SQLite sequential\n        let temp_dir = tempfile::tempdir().unwrap();\n        let sqlite_conn = setup_rusqlite(\n            &temp_dir,\n            \"CREATE TABLE test (id INTEGER PRIMARY KEY, data TEXT)\",\n        );\n\n        group.bench_function(BenchmarkId::new(\"sqlite\", \"sequential_keys\"), |b| {\n            let mut stmt = sqlite_conn.prepare(&seq_values).unwrap();\n            b.iter_custom(|iters| {\n                let mut total = std::time::Duration::ZERO;\n                for _ in 0..iters {\n                    let start = std::time::Instant::now();\n                    stmt.raw_execute().unwrap();\n                    total += start.elapsed();\n                    sqlite_conn.execute(\"DELETE FROM test\", []).unwrap();\n                }\n                total\n            });\n        });\n\n        // SQLite random\n        let temp_dir = tempfile::tempdir().unwrap();\n        let sqlite_conn = setup_rusqlite(\n            &temp_dir,\n            \"CREATE TABLE test (id INTEGER PRIMARY KEY, data TEXT)\",\n        );\n\n        group.bench_function(BenchmarkId::new(\"sqlite\", \"random_keys\"), |b| {\n            let mut stmt = sqlite_conn.prepare(&rand_values).unwrap();\n            b.iter_custom(|iters| {\n                let mut total = std::time::Duration::ZERO;\n                for _ in 0..iters {\n                    let start = std::time::Instant::now();\n                    stmt.raw_execute().unwrap();\n                    total += start.elapsed();\n                    sqlite_conn.execute(\"DELETE FROM test\", []).unwrap();\n                }\n                total\n            });\n        });\n    }\n\n    group.finish();\n}\n\n/// Benchmark: UPDATE vs INSERT performance\n///\n/// Updates may have different performance characteristics due to:\n/// 1. Required seek to find existing row\n/// 2. Potential in-place update vs delete+insert\n/// 3. Index maintenance on modified columns\nfn bench_update_performance(criterion: &mut Criterion) {\n    let enable_rusqlite =\n        std::env::var(\"DISABLE_RUSQLITE_BENCHMARK\").is_err() && !cfg!(feature = \"codspeed\");\n\n    let mut group = criterion.benchmark_group(\"UPDATE Performance\");\n\n    // Pre-populate table and measure update throughput\n    let row_count = 1000;\n    let update_count = 100;\n\n    group.throughput(Throughput::Elements(update_count as u64));\n\n    // Build initial data insert\n    let mut initial_insert = String::from(\"INSERT INTO test VALUES \");\n    for i in 0..row_count {\n        if i > 0 {\n            initial_insert.push(',');\n        }\n        initial_insert.push_str(&format!(\"({i}, 'initial_{i}')\"));\n    }\n\n    // Build batch update (updates middle rows)\n    let mut batch_update = String::new();\n    let start = row_count / 2 - update_count / 2;\n    for i in 0..update_count {\n        if i > 0 {\n            batch_update.push_str(\"; \");\n        }\n        batch_update.push_str(&format!(\n            \"UPDATE test SET data = 'updated_{}' WHERE id = {}\",\n            i,\n            start + i\n        ));\n    }\n\n    // Limbo benchmark\n    let temp_dir = tempfile::tempdir().unwrap();\n    let db = setup_limbo(\n        &temp_dir,\n        \"CREATE TABLE test (id INTEGER PRIMARY KEY, data TEXT)\",\n    );\n    let conn = db.connect().unwrap();\n\n    // Insert initial data\n    let mut stmt = conn.prepare(&initial_insert).unwrap();\n    run_to_completion(&mut stmt, &db).unwrap();\n\n    group.bench_function(BenchmarkId::new(\"limbo\", \"batch_update\"), |b| {\n        let mut stmt = conn.prepare(&batch_update).unwrap();\n        b.iter(|| {\n            run_to_completion(&mut stmt, &db).unwrap();\n            stmt.reset().unwrap();\n        });\n    });\n\n    // SQLite benchmark\n    if enable_rusqlite {\n        let temp_dir = tempfile::tempdir().unwrap();\n        let sqlite_conn = setup_rusqlite(\n            &temp_dir,\n            \"CREATE TABLE test (id INTEGER PRIMARY KEY, data TEXT)\",\n        );\n        sqlite_conn.execute(&initial_insert, []).unwrap();\n\n        group.bench_function(BenchmarkId::new(\"sqlite\", \"batch_update\"), |b| {\n            b.iter(|| {\n                sqlite_conn.execute_batch(&batch_update).unwrap();\n            });\n        });\n    }\n\n    group.finish();\n}\n\n/// Benchmark: DELETE performance\n///\n/// Measures DELETE throughput with different patterns\nfn bench_delete_performance(criterion: &mut Criterion) {\n    let enable_rusqlite =\n        std::env::var(\"DISABLE_RUSQLITE_BENCHMARK\").is_err() && !cfg!(feature = \"codspeed\");\n\n    let mut group = criterion.benchmark_group(\"DELETE Performance\");\n\n    let row_count = 1000;\n    let delete_count = 100;\n\n    group.throughput(Throughput::Elements(delete_count as u64));\n\n    // Build initial data insert\n    let mut initial_insert = String::from(\"INSERT INTO test VALUES \");\n    for i in 0..row_count {\n        if i > 0 {\n            initial_insert.push(',');\n        }\n        initial_insert.push_str(&format!(\"({i}, 'data_{i}')\"));\n    }\n\n    // Build range delete\n    let start = row_count / 2 - delete_count / 2;\n    let end = start + delete_count;\n    let range_delete = format!(\"DELETE FROM test WHERE id >= {start} AND id < {end}\");\n\n    // Limbo benchmark\n    let temp_dir = tempfile::tempdir().unwrap();\n    let db = setup_limbo(\n        &temp_dir,\n        \"CREATE TABLE test (id INTEGER PRIMARY KEY, data TEXT)\",\n    );\n    let conn = db.connect().unwrap();\n\n    group.bench_function(BenchmarkId::new(\"limbo\", \"range_delete\"), |b| {\n        b.iter_custom(|iters| {\n            let mut total = std::time::Duration::ZERO;\n            for _ in 0..iters {\n                // Re-insert data\n                let mut stmt = conn.prepare(&initial_insert).unwrap();\n                run_to_completion(&mut stmt, &db).unwrap();\n\n                // Time only the delete\n                let start = std::time::Instant::now();\n                let mut stmt = conn.prepare(&range_delete).unwrap();\n                run_to_completion(&mut stmt, &db).unwrap();\n                total += start.elapsed();\n\n                // Clean up for next iteration\n                let mut stmt = conn.query(\"DELETE FROM test\").unwrap().unwrap();\n                run_to_completion(&mut stmt, &db).unwrap();\n            }\n            total\n        });\n    });\n\n    // SQLite benchmark\n    if enable_rusqlite {\n        let temp_dir = tempfile::tempdir().unwrap();\n        let sqlite_conn = setup_rusqlite(\n            &temp_dir,\n            \"CREATE TABLE test (id INTEGER PRIMARY KEY, data TEXT)\",\n        );\n\n        group.bench_function(BenchmarkId::new(\"sqlite\", \"range_delete\"), |b| {\n            b.iter_custom(|iters| {\n                let mut total = std::time::Duration::ZERO;\n                for _ in 0..iters {\n                    // Re-insert data\n                    sqlite_conn.execute(&initial_insert, []).unwrap();\n\n                    // Time only the delete\n                    let start = std::time::Instant::now();\n                    sqlite_conn.execute(&range_delete, []).unwrap();\n                    total += start.elapsed();\n\n                    // Clean up for next iteration\n                    sqlite_conn.execute(\"DELETE FROM test\", []).unwrap();\n                }\n                total\n            });\n        });\n    }\n\n    group.finish();\n}\n\n/// Benchmark: Large transaction commit (many dirty pages)\n///\n/// Specifically targets the commit_dirty_pages path with many pages\nfn bench_large_transaction_commit(criterion: &mut Criterion) {\n    let enable_rusqlite =\n        std::env::var(\"DISABLE_RUSQLITE_BENCHMARK\").is_err() && !cfg!(feature = \"codspeed\");\n\n    let mut group = criterion.benchmark_group(\"Large Transaction Commit\");\n\n    // Insert enough rows to dirty many pages (assuming 4KB pages, ~100 rows per page for small rows)\n    let row_counts = [100, 1000, 5000, 10000];\n\n    for row_count in row_counts {\n        group.throughput(Throughput::Elements(row_count as u64));\n\n        // Build large insert\n        let mut values = String::from(\"INSERT INTO test VALUES \");\n        for i in 0..row_count {\n            if i > 0 {\n                values.push(',');\n            }\n            // Use larger row size to dirty more pages\n            values.push_str(&format!(\n                \"({}, '{}', {})\",\n                i,\n                \"x\".repeat(100), // 100 byte string per row\n                i * 10\n            ));\n        }\n\n        // Limbo benchmark\n        let temp_dir = tempfile::tempdir().unwrap();\n        let db = setup_limbo(\n            &temp_dir,\n            \"CREATE TABLE test (id INTEGER PRIMARY KEY, data TEXT, val INTEGER)\",\n        );\n        let conn = db.connect().unwrap();\n\n        group.bench_function(\n            BenchmarkId::new(\"limbo\", format!(\"{row_count}_rows\")),\n            |b| {\n                b.iter_custom(|iters| {\n                    let mut total = std::time::Duration::ZERO;\n                    for _ in 0..iters {\n                        // BEGIN\n                        let mut stmt = conn.query(\"BEGIN\").unwrap().unwrap();\n                        run_to_completion(&mut stmt, &db).unwrap();\n\n                        // INSERT (not timed separately - we want full transaction)\n                        let start = std::time::Instant::now();\n                        let mut stmt = conn.prepare(&values).unwrap();\n                        run_to_completion(&mut stmt, &db).unwrap();\n\n                        // COMMIT\n                        let mut stmt = conn.query(\"COMMIT\").unwrap().unwrap();\n                        run_to_completion(&mut stmt, &db).unwrap();\n                        total += start.elapsed();\n\n                        // Clean up\n                        let mut stmt = conn.query(\"DELETE FROM test\").unwrap().unwrap();\n                        run_to_completion(&mut stmt, &db).unwrap();\n                    }\n                    total\n                });\n            },\n        );\n\n        // SQLite benchmark\n        if enable_rusqlite {\n            let temp_dir = tempfile::tempdir().unwrap();\n            let sqlite_conn = setup_rusqlite(\n                &temp_dir,\n                \"CREATE TABLE test (id INTEGER PRIMARY KEY, data TEXT, val INTEGER)\",\n            );\n\n            group.bench_function(\n                BenchmarkId::new(\"sqlite\", format!(\"{row_count}_rows\")),\n                |b| {\n                    b.iter_custom(|iters| {\n                        let mut total = std::time::Duration::ZERO;\n                        for _ in 0..iters {\n                            sqlite_conn.execute(\"BEGIN\", []).unwrap();\n\n                            let start = std::time::Instant::now();\n                            sqlite_conn.execute(&values, []).unwrap();\n                            sqlite_conn.execute(\"COMMIT\", []).unwrap();\n                            total += start.elapsed();\n\n                            sqlite_conn.execute(\"DELETE FROM test\", []).unwrap();\n                        }\n                        total\n                    });\n                },\n            );\n        }\n    }\n\n    group.finish();\n}\n\n/// Benchmark: Fsync overhead isolation\n///\n/// Compares INSERT performance with sync=FULL vs sync=OFF to isolate fsync cost\nfn bench_fsync_overhead(criterion: &mut Criterion) {\n    let enable_rusqlite =\n        std::env::var(\"DISABLE_RUSQLITE_BENCHMARK\").is_err() && !cfg!(feature = \"codspeed\");\n    let batch_size = 100;\n\n    let mut group = criterion.benchmark_group(\"Fsync Overhead\");\n    group.throughput(Throughput::Elements(batch_size as u64));\n\n    // Build insert statement\n    let mut values = String::from(\"INSERT INTO test VALUES \");\n    for i in 0..batch_size {\n        if i > 0 {\n            values.push(',');\n        }\n        values.push_str(&format!(\"({i}, 'data_{i}')\"));\n    }\n\n    // Limbo with sync=FULL\n    let temp_dir = tempfile::tempdir().unwrap();\n    let db = setup_limbo_with_sync(\n        &temp_dir,\n        \"CREATE TABLE test (id INTEGER PRIMARY KEY, data TEXT)\",\n        true,\n    );\n    let conn = db.connect().unwrap();\n\n    group.bench_function(BenchmarkId::new(\"limbo\", \"sync_FULL\"), |b| {\n        let mut insert_stmt = conn.prepare(&values).unwrap();\n        let mut delete_stmt = conn.query(\"DELETE FROM test\").unwrap().unwrap();\n        b.iter_custom(|iters| {\n            let mut total = std::time::Duration::ZERO;\n            for _ in 0..iters {\n                let start = std::time::Instant::now();\n                run_to_completion(&mut insert_stmt, &db).unwrap();\n                total += start.elapsed();\n                insert_stmt.reset().unwrap();\n                run_to_completion(&mut delete_stmt, &db).unwrap();\n                delete_stmt.reset().unwrap();\n            }\n            total\n        });\n    });\n\n    // Limbo with sync=OFF\n    let temp_dir = tempfile::tempdir().unwrap();\n    let db = setup_limbo_with_sync(\n        &temp_dir,\n        \"CREATE TABLE test (id INTEGER PRIMARY KEY, data TEXT)\",\n        false,\n    );\n    let conn = db.connect().unwrap();\n\n    group.bench_function(BenchmarkId::new(\"limbo\", \"sync_OFF\"), |b| {\n        let mut insert_stmt = conn.prepare(&values).unwrap();\n        let mut delete_stmt = conn.query(\"DELETE FROM test\").unwrap().unwrap();\n        b.iter_custom(|iters| {\n            let mut total = std::time::Duration::ZERO;\n            for _ in 0..iters {\n                let start = std::time::Instant::now();\n                run_to_completion(&mut insert_stmt, &db).unwrap();\n                total += start.elapsed();\n                insert_stmt.reset().unwrap();\n                run_to_completion(&mut delete_stmt, &db).unwrap();\n                delete_stmt.reset().unwrap();\n            }\n            total\n        });\n    });\n\n    if enable_rusqlite {\n        // SQLite with sync=FULL\n        let temp_dir = tempfile::tempdir().unwrap();\n        let sqlite_conn = setup_rusqlite(\n            &temp_dir,\n            \"CREATE TABLE test (id INTEGER PRIMARY KEY, data TEXT)\",\n        );\n\n        group.bench_function(BenchmarkId::new(\"sqlite\", \"sync_FULL\"), |b| {\n            let mut stmt = sqlite_conn.prepare(&values).unwrap();\n            b.iter_custom(|iters| {\n                let mut total = std::time::Duration::ZERO;\n                for _ in 0..iters {\n                    let start = std::time::Instant::now();\n                    stmt.raw_execute().unwrap();\n                    total += start.elapsed();\n                    sqlite_conn.execute(\"DELETE FROM test\", []).unwrap();\n                }\n                total\n            });\n        });\n\n        // SQLite with sync=OFF\n        let temp_dir = tempfile::tempdir().unwrap();\n        let db_path = temp_dir.path().join(\"bench.db\");\n        let sqlite_conn = rusqlite::Connection::open(db_path).unwrap();\n        sqlite_conn\n            .pragma_update(None, \"synchronous\", \"OFF\")\n            .unwrap();\n        sqlite_conn\n            .pragma_update(None, \"journal_mode\", \"WAL\")\n            .unwrap();\n        sqlite_conn\n            .pragma_update(None, \"locking_mode\", \"EXCLUSIVE\")\n            .unwrap();\n        sqlite_conn\n            .execute(\"CREATE TABLE test (id INTEGER PRIMARY KEY, data TEXT)\", [])\n            .unwrap();\n\n        group.bench_function(BenchmarkId::new(\"sqlite\", \"sync_OFF\"), |b| {\n            let mut stmt = sqlite_conn.prepare(&values).unwrap();\n            b.iter_custom(|iters| {\n                let mut total = std::time::Duration::ZERO;\n                for _ in 0..iters {\n                    let start = std::time::Instant::now();\n                    stmt.raw_execute().unwrap();\n                    total += start.elapsed();\n                    sqlite_conn.execute(\"DELETE FROM test\", []).unwrap();\n                }\n                total\n            });\n        });\n    }\n\n    group.finish();\n}\n\n#[cfg(not(feature = \"codspeed\"))]\ncriterion_group! {\n    name = write_perf_benches;\n    config = Criterion::default()\n        .with_profiler(PProfProfiler::new(100, Output::Flamegraph(None)))\n        .sample_size(50);\n    targets = bench_index_impact, bench_transaction_size, bench_key_pattern, bench_update_performance, bench_delete_performance, bench_large_transaction_commit, bench_fsync_overhead\n}\n\n#[cfg(feature = \"codspeed\")]\ncriterion_group! {\n    name = write_perf_benches;\n    config = Criterion::default().sample_size(50);\n    targets = bench_index_impact, bench_transaction_size, bench_key_pattern, bench_update_performance, bench_delete_performance, bench_large_transaction_commit, bench_fsync_overhead\n}\n\ncriterion_main!(write_perf_benches);\n"
  },
  {
    "path": "core/btree_dump.rs",
    "content": "use crate::schema::Schema;\nuse crate::storage::btree::BTreeCursor;\nuse crate::storage::btree::CursorTrait;\nuse crate::storage::pager::Pager;\nuse crate::sync::Arc;\nuse crate::sync::RwLock;\nuse crate::util::IOExt;\nuse crate::vtab::{InternalVirtualTable, InternalVirtualTableCursor};\nuse crate::{Connection, Result, Value};\nuse turso_ext::{\n    ConstraintInfo, ConstraintOp, ConstraintUsage, IndexInfo, OrderByInfo, ResultCode,\n};\n\n#[derive(Debug)]\npub struct BtreeDumpTable;\n\nimpl Default for BtreeDumpTable {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl BtreeDumpTable {\n    pub fn new() -> Self {\n        Self\n    }\n}\n\nimpl InternalVirtualTable for BtreeDumpTable {\n    fn name(&self) -> String {\n        \"btree_dump\".to_string()\n    }\n\n    fn sql(&self) -> String {\n        \"CREATE TABLE btree_dump(record BLOB, name TEXT HIDDEN)\".to_string()\n    }\n\n    fn open(&self, conn: Arc<Connection>) -> Result<Arc<RwLock<dyn InternalVirtualTableCursor>>> {\n        let pager = conn.get_pager();\n        let schema = conn.schema.read().clone();\n        let cursor = BtreeDumpCursor::new(pager, schema);\n        Ok(Arc::new(RwLock::new(cursor)))\n    }\n\n    fn best_index(\n        &self,\n        constraints: &[ConstraintInfo],\n        _order_by: &[OrderByInfo],\n    ) -> std::result::Result<IndexInfo, ResultCode> {\n        let mut usages = vec![\n            ConstraintUsage {\n                argv_index: None,\n                omit: false,\n            };\n            constraints.len()\n        ];\n\n        let mut name_idx: Option<usize> = None;\n        for (i, c) in constraints.iter().enumerate() {\n            if !c.usable || c.op != ConstraintOp::Eq {\n                continue;\n            }\n            // column 1 is the hidden `name` column\n            if c.column_index == 1 {\n                name_idx = Some(i);\n            }\n        }\n\n        if let Some(idx) = name_idx {\n            usages[idx] = ConstraintUsage {\n                argv_index: Some(1),\n                omit: true,\n            };\n        }\n\n        let (cost, rows) = if name_idx.is_some() {\n            (1.0, 100)\n        } else {\n            (f64::MAX, 100)\n        };\n\n        Ok(IndexInfo {\n            idx_num: if name_idx.is_some() { 1 } else { 0 },\n            idx_str: None,\n            order_by_consumed: false,\n            estimated_cost: cost,\n            estimated_rows: rows,\n            constraint_usages: usages,\n        })\n    }\n}\n\npub struct BtreeDumpCursor {\n    pager: Arc<Pager>,\n    schema: Arc<Schema>,\n    cursor: Option<BTreeCursor>,\n    current_record: Option<Vec<u8>>,\n    row_idx: i64,\n}\n\nimpl BtreeDumpCursor {\n    fn new(pager: Arc<Pager>, schema: Arc<Schema>) -> Self {\n        Self {\n            pager,\n            schema,\n            cursor: None,\n            current_record: None,\n            row_idx: 0,\n        }\n    }\n\n    /// Look up the root page for a given name (index first, then table).\n    fn find_root_page(&self, name: &str) -> Option<i64> {\n        // Search indexes first (indexes are stored by table name, so iterate all)\n        for indexes in self.schema.indexes.values() {\n            for index in indexes {\n                if index.name.eq_ignore_ascii_case(name) {\n                    return Some(index.root_page);\n                }\n            }\n        }\n        // Then search tables\n        if let Some(table) = self.schema.get_table(name) {\n            return table.get_root_page().ok();\n        }\n        None\n    }\n\n    fn read_current_record(&mut self) -> Result<()> {\n        self.current_record = None;\n        if let Some(ref mut cursor) = self.cursor {\n            let payload = self.pager.io.block(|| {\n                let record = cursor.record()?;\n                match record {\n                    crate::types::IOResult::Done(Some(rec)) => Ok(crate::types::IOResult::Done(\n                        Some(rec.get_payload().to_vec()),\n                    )),\n                    crate::types::IOResult::Done(None) => Ok(crate::types::IOResult::Done(None)),\n                    crate::types::IOResult::IO(io) => Ok(crate::types::IOResult::IO(io)),\n                }\n            })?;\n            self.current_record = payload;\n        }\n        Ok(())\n    }\n}\n\nimpl InternalVirtualTableCursor for BtreeDumpCursor {\n    fn filter(&mut self, args: &[Value], _idx_str: Option<String>, idx_num: i32) -> Result<bool> {\n        self.cursor = None;\n        self.current_record = None;\n        self.row_idx = 0;\n\n        if idx_num != 1 {\n            // No name constraint provided — return no rows\n            return Ok(false);\n        }\n\n        let name = match args.first() {\n            Some(Value::Text(s)) => s.as_str(),\n            _ => return Ok(false),\n        };\n\n        let root_page = match self.find_root_page(name) {\n            Some(rp) if rp > 0 => rp,\n            Some(_) => {\n                return Err(crate::LimboError::InternalError(format!(\n                    \"btree_dump: '{name}' has no physical btree (MVCC non-checkpointed)\",\n                )));\n            }\n            None => {\n                return Err(crate::LimboError::InternalError(format!(\n                    \"btree_dump: no such table or index: '{name}'\",\n                )));\n            }\n        };\n\n        let mut btree_cursor = BTreeCursor::new(self.pager.clone(), root_page, 0);\n        self.pager.io.block(|| btree_cursor.rewind())?;\n        self.cursor = Some(btree_cursor);\n        self.read_current_record()?;\n        Ok(self.current_record.is_some())\n    }\n\n    fn next(&mut self) -> Result<bool> {\n        if let Some(ref mut cursor) = self.cursor {\n            self.pager.io.block(|| cursor.next())?;\n            self.row_idx += 1;\n            self.read_current_record()?;\n            Ok(self.current_record.is_some())\n        } else {\n            Ok(false)\n        }\n    }\n\n    fn column(&self, column: usize) -> Result<Value> {\n        match column {\n            0 => match &self.current_record {\n                Some(data) => Ok(Value::from_blob(data.clone())),\n                None => Ok(Value::Null),\n            },\n            _ => Ok(Value::Null),\n        }\n    }\n\n    fn rowid(&self) -> i64 {\n        self.row_idx\n    }\n}\n"
  },
  {
    "path": "core/build.rs",
    "content": "use chrono::{TimeZone, Utc};\nuse std::path::PathBuf;\nuse std::process::Command;\nuse std::{env, fs};\n\nfn main() {\n    // Ensure Cargo reruns when this script or the reproducibility seed changes.\n    println!(\"cargo::rerun-if-changed=build.rs\");\n    println!(\"cargo::rerun-if-env-changed=SOURCE_DATE_EPOCH\");\n\n    // Tell cargo to rebuild when git HEAD changes, so sqlite_source_id() stays current.\n    // We use `git rev-parse --git-dir` instead of hardcoding \".git\" to support worktrees,\n    // where the git directory lives elsewhere (e.g., ../.git/worktrees/my-worktree).\n    // Silently ignored if git unavailable (e.g., building from tarball).\n    // Resolve git dir dynamically to support worktrees.\n    let git_dir = run_git(&[\"rev-parse\", \"--git-dir\"]).map(PathBuf::from);\n    if let Some(git_dir) = git_dir.as_ref() {\n        // Common dir holds refs for worktrees; fall back to git_dir if unavailable.\n        let git_common_dir = run_git(&[\"rev-parse\", \"--git-common-dir\"]).map(PathBuf::from);\n        let head_path = git_dir.join(\"HEAD\");\n        // HEAD changes on checkout/switch\n        println!(\"cargo::rerun-if-changed={}\", head_path.display());\n        // The ref file (e.g., refs/heads/main) changes on commit\n        if let Ok(head_content) = fs::read_to_string(&head_path) {\n            if let Some(ref_path) = head_content.strip_prefix(\"ref: \") {\n                let ref_base = git_common_dir.as_deref().unwrap_or(git_dir.as_path());\n                let ref_path = ref_base.join(ref_path.trim());\n                println!(\"cargo::rerun-if-changed={}\", ref_path.display());\n                if !ref_path.exists() {\n                    let packed_refs = ref_base.join(\"packed-refs\");\n                    println!(\"cargo::rerun-if-changed={}\", packed_refs.display());\n                }\n            }\n        }\n    }\n\n    let out_dir = PathBuf::from(env::var(\"OUT_DIR\").unwrap());\n    // Write to a temp file first, then only update built.rs if contents changed.\n    let built_file = out_dir.join(\"built.rs\");\n    let temp_file = out_dir.join(\"built.rs.tmp\");\n\n    // We shell out to git instead of using libgit2 (via the `built` crate's git2 feature)\n    // because libgit2-sys adds ~18s to clean release builds. The git CLI is always available\n    // in dev environments and CI. Falls back to None if git unavailable.\n    // Commit hash is used for sqlite_source_id() and to derive a stable timestamp.\n    let git_hash = run_git(&[\"rev-parse\", \"HEAD\"]);\n    let git_commit_epoch = run_git(&[\"show\", \"-s\", \"--format=%ct\", \"HEAD\"])\n        .and_then(|epoch| epoch.parse::<i64>().ok());\n\n    let git_hash_code = match git_hash {\n        Some(hash) => format!(\"pub const GIT_COMMIT_HASH: Option<&str> = Some(\\\"{hash}\\\");\"),\n        None => \"pub const GIT_COMMIT_HASH: Option<&str> = None;\".to_string(),\n    };\n\n    // Honor reproducible-builds if set; otherwise seed it from git commit time.\n    let source_date_epoch = env::var(\"SOURCE_DATE_EPOCH\")\n        .ok()\n        .and_then(|epoch| epoch.parse::<i64>().ok());\n\n    if source_date_epoch.is_none() {\n        if let Some(epoch) = git_commit_epoch {\n            env::set_var(\"SOURCE_DATE_EPOCH\", epoch.to_string());\n        }\n    }\n\n    // Pre-format the timestamp so sqlite_source_id() doesn't need chrono at runtime.\n    // Prefer SOURCE_DATE_EPOCH (reproducible builds), then git commit time, and fall back to now.\n    let sqlite_date = source_date_epoch\n        .or(git_commit_epoch)\n        .and_then(|epoch| Utc.timestamp_opt(epoch, 0).single())\n        .unwrap_or_else(Utc::now)\n        .format(\"%Y-%m-%d %H:%M:%S\")\n        .to_string();\n\n    // Generate built metadata and append our extra constants.\n    built::write_built_file_with_opts(&temp_file)\n        .expect(\"Failed to acquire build-time information\");\n    let built_contents = fs::read_to_string(&temp_file).expect(\"Failed to read built metadata\");\n    let new_contents = format!(\n        \"{built_contents}\\npub const BUILT_TIME_SQLITE: &str = \\\"{sqlite_date}\\\";\\n{git_hash_code}\\n\"\n    );\n\n    // Avoid touching built.rs when content is unchanged to prevent rebuild loops.\n    let existing_contents = fs::read_to_string(&built_file).ok();\n    if existing_contents.as_deref() != Some(new_contents.as_str()) {\n        fs::write(&built_file, new_contents).expect(\"Failed to write built file\");\n    }\n    let _ = fs::remove_file(&temp_file);\n}\n\nfn run_git(args: &[&str]) -> Option<String> {\n    let output = Command::new(\"git\").args(args).output().ok()?;\n    if !output.status.success() {\n        return None;\n    }\n    let stdout = String::from_utf8(output.stdout).ok()?;\n    let trimmed = stdout.trim();\n    if trimmed.is_empty() {\n        None\n    } else {\n        Some(trimmed.to_string())\n    }\n}\n"
  },
  {
    "path": "core/busy.rs",
    "content": "use crate::MonotonicInstant;\nuse std::time::Duration;\n\n/// Type alias for busy handler callback function.\n///\n/// The callback receives:\n/// - `count`: The number of times the busy handler has been invoked for the same locking event\n///\n/// Returns:\n/// - `0` to stop retrying and return SQLITE_BUSY to the application.\n/// - Non-zero to retry the database access.\n///\n/// # Safety Notes (per SQLite spec)\n/// - The callback MUST NOT modify the database connection that invoked it.\n/// - The callback MUST NOT close the connection or any prepared statement.\n/// - The callback is NOT reentrant.\npub type BusyHandlerCallback = Box<dyn Fn(i32) -> i32 + Send + Sync>;\n\n#[derive(Default)]\n/// Represents the busy handler configuration for a connection.\npub enum BusyHandler {\n    #[default]\n    /// No busy handler: return SQLITE_BUSY immediately on lock contention.\n    None,\n    /// Default timeout-based handler (implements sqliteDefaultBusyCallback)\n    /// The duration is the maximum total time to wait before giving up\n    Timeout(Duration),\n    /// Custom user-defined callback handler\n    Custom { callback: BusyHandlerCallback },\n}\n\nimpl std::fmt::Debug for BusyHandler {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            BusyHandler::None => write!(f, \"BusyHandler::None\"),\n            BusyHandler::Timeout(d) => write!(f, \"BusyHandler::Timeout({d:?}\"),\n            BusyHandler::Custom { .. } => write!(f, \"BusyHandler::Custom\"),\n        }\n    }\n}\n\n/// Tracks the state of busy handler invocations for a statement.\n///\n/// This implements a yield-based busy handling mechanism that integrates with\n/// the async event loop. Instead of blocking with `thread::sleep`, the statement\n/// yields back to the caller with `StepResult::IO` and a timeout. When `step()`\n/// is called again after the timeout has passed, it retries the operation.\n///\n/// Uses increasing delays. After 12 iterations, continues with 100ms delays until max duration is reached.\n#[derive(Debug)]\npub struct BusyHandlerState {\n    /// Number of times the busy handler has been invoked for this locking event\n    invocation_count: i32,\n    /// For timeout-based handlers: the next timeout instant to wait until\n    timeout: MonotonicInstant,\n    /// For timeout-based handlers: the current iteration index into DELAYS\n    iteration: usize,\n}\n\nimpl BusyHandlerState {\n    /// Delay schedule for timeout-based busy handler (sqliteDefaultBusyCallback)\n    const DELAYS: [Duration; 12] = [\n        Duration::from_millis(1),\n        Duration::from_millis(2),\n        Duration::from_millis(5),\n        Duration::from_millis(10),\n        Duration::from_millis(15),\n        Duration::from_millis(20),\n        Duration::from_millis(25),\n        Duration::from_millis(25),\n        Duration::from_millis(25),\n        Duration::from_millis(50),\n        Duration::from_millis(50),\n        Duration::from_millis(100),\n    ];\n\n    /// Cumulative totals for each iteration (for calculating remaining time)\n    const TOTALS: [Duration; 12] = [\n        Duration::from_millis(0),\n        Duration::from_millis(1),\n        Duration::from_millis(3),\n        Duration::from_millis(8),\n        Duration::from_millis(18),\n        Duration::from_millis(33),\n        Duration::from_millis(53),\n        Duration::from_millis(78),\n        Duration::from_millis(103),\n        Duration::from_millis(128),\n        Duration::from_millis(178),\n        Duration::from_millis(228),\n    ];\n\n    /// Create a new busy handler state\n    pub fn new(now: MonotonicInstant) -> Self {\n        Self {\n            invocation_count: 0,\n            timeout: now,\n            iteration: 0,\n        }\n    }\n\n    /// Reset the state for a new locking event\n    pub fn reset(&mut self, now: MonotonicInstant) {\n        self.invocation_count = 0;\n        self.timeout = now;\n        self.iteration = 0;\n    }\n\n    /// Get the current timeout instant\n    pub fn timeout(&self) -> MonotonicInstant {\n        self.timeout\n    }\n\n    /// Invoke the busy handler and determine whether to retry.\n    ///\n    /// Returns `true` if the operation should be retried, `false` if SQLITE_BUSY\n    /// should be returned to the application.\n    ///\n    /// For timeout-based handlers, this also updates the internal timeout instant.\n    /// For custom handlers, this invokes the callback and respects its return value.\n    pub fn invoke(&mut self, handler: &BusyHandler, now: MonotonicInstant) -> bool {\n        match handler {\n            BusyHandler::None => {\n                // No handler: return BUSY immediately\n                false\n            }\n            BusyHandler::Timeout(max_duration) => self.invoke_timeout_handler(*max_duration, now),\n            BusyHandler::Custom { callback } => {\n                let result = callback(self.invocation_count);\n                self.invocation_count += 1;\n                if result != 0 {\n                    // Retry with a small delay\n                    self.timeout = now + Duration::from_millis(1);\n                    true\n                } else {\n                    false\n                }\n            }\n        }\n    }\n\n    /// Implements sqliteDefaultBusyCallback logic for timeout-based handling.\n    ///\n    /// This uses an exponentially increasing delay schedule, capped at 100ms per iteration.\n    fn invoke_timeout_handler(&mut self, max_duration: Duration, now: MonotonicInstant) -> bool {\n        let idx = self.iteration.min(11);\n        let mut delay = Self::DELAYS[idx];\n        let mut prior = Self::TOTALS[idx];\n\n        // After 12 iterations, each additional iteration adds 100ms\n        if self.iteration >= 12 {\n            prior += delay * (self.iteration as u32 - 11);\n        }\n\n        // Check if we've exceeded or would exceed the max duration\n        if prior + delay > max_duration {\n            delay = max_duration.saturating_sub(prior);\n            if delay.is_zero() {\n                return false;\n            }\n        }\n\n        self.iteration = self.iteration.saturating_add(1);\n        self.invocation_count += 1;\n        self.timeout = now + delay;\n        true\n    }\n\n    /// Get the delay duration that should be waited before the next retry.\n    ///\n    /// This returns the duration between `now` and the timeout instant.\n    /// Returns `Duration::ZERO` if the timeout has already passed.\n    pub fn get_delay(&self, now: MonotonicInstant) -> Duration {\n        if now >= self.timeout {\n            Duration::ZERO\n        } else {\n            self.timeout.duration_since(now)\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    fn test_instant() -> MonotonicInstant {\n        MonotonicInstant::now()\n    }\n\n    #[test]\n    fn test_busy_handler_timeout_basic() {\n        let handler = BusyHandler::Timeout(Duration::from_millis(100));\n        let now = test_instant();\n        let mut state = BusyHandlerState::new(now);\n\n        // First invocation should return true (retry)\n        assert!(state.invoke(&handler, now));\n        // Timeout should be set to 1ms from now\n        assert_eq!(state.timeout(), now + Duration::from_millis(1));\n    }\n\n    #[test]\n    fn test_busy_handler_timeout_exhausted() {\n        let handler = BusyHandler::Timeout(Duration::from_millis(0));\n        let now = test_instant();\n        let mut state = BusyHandlerState::new(now);\n\n        // Zero timeout should return false immediately\n        assert!(!state.invoke(&handler, now));\n    }\n\n    #[test]\n    fn test_busy_handler_custom_callback() {\n        // Callback that retries 3 times then gives up\n        let callback: BusyHandlerCallback = Box::new(|count| if count < 3 { 1 } else { 0 });\n        let handler = BusyHandler::Custom { callback };\n        let now = test_instant();\n        let mut state = BusyHandlerState::new(now);\n\n        // First 3 invocations should retry\n        assert!(state.invoke(&handler, now));\n        assert!(state.invoke(&handler, now));\n        assert!(state.invoke(&handler, now));\n        // 4th invocation should return false\n        assert!(!state.invoke(&handler, now));\n    }\n\n    #[test]\n    fn test_busy_handler_none_returns_false_immediately() {\n        let handler = BusyHandler::None;\n        let now = test_instant();\n        let mut state = BusyHandlerState::new(now);\n\n        // None handler should always return false (don't retry)\n        assert!(!state.invoke(&handler, now));\n        // Even on subsequent invocations\n        assert!(!state.invoke(&handler, now));\n    }\n\n    #[test]\n    fn test_custom_callback_receives_correct_count() {\n        use std::sync::{Arc, Mutex};\n\n        // Track the counts passed to callback (using Arc+Mutex for Send+Sync)\n        let counts = Arc::new(Mutex::new(Vec::new()));\n        let counts_clone = counts.clone();\n\n        let callback: BusyHandlerCallback = Box::new(move |count| {\n            counts_clone.lock().unwrap().push(count);\n            if count < 5 {\n                1\n            } else {\n                0\n            }\n        });\n\n        let handler = BusyHandler::Custom { callback };\n        let now = test_instant();\n        let mut state = BusyHandlerState::new(now);\n\n        // Invoke 6 times\n        for _ in 0..6 {\n            state.invoke(&handler, now);\n        }\n\n        // Verify counts were 0, 1, 2, 3, 4, 5\n        assert_eq!(*counts.lock().unwrap(), vec![0, 1, 2, 3, 4, 5]);\n    }\n\n    #[test]\n    fn test_custom_callback_always_retry() {\n        // Callback that always retries\n        let callback: BusyHandlerCallback = Box::new(|_| 1);\n        let handler = BusyHandler::Custom { callback };\n        let now = test_instant();\n        let mut state = BusyHandlerState::new(now);\n\n        // Should always return true\n        for _ in 0..100 {\n            assert!(state.invoke(&handler, now));\n        }\n    }\n\n    #[test]\n    fn test_custom_callback_never_retry() {\n        // Callback that never retries\n        let callback: BusyHandlerCallback = Box::new(|_| 0);\n        let handler = BusyHandler::Custom { callback };\n        let now = test_instant();\n        let mut state = BusyHandlerState::new(now);\n\n        // First invocation should return false\n        assert!(!state.invoke(&handler, now));\n    }\n\n    #[test]\n    fn test_custom_callback_sets_timeout() {\n        let callback: BusyHandlerCallback = Box::new(|_| 1);\n        let handler = BusyHandler::Custom { callback };\n        let now = test_instant();\n        let mut state = BusyHandlerState::new(now);\n\n        assert!(state.invoke(&handler, now));\n        // Custom callback sets 1ms timeout\n        assert_eq!(state.timeout(), now + Duration::from_millis(1));\n    }\n\n    #[test]\n    fn test_timeout_delay_schedule() {\n        let handler = BusyHandler::Timeout(Duration::from_secs(10)); // Long timeout\n        let now = test_instant();\n        let mut state = BusyHandlerState::new(now);\n\n        // Expected delays per iteration: 1, 2, 5, 10, 15, 20, 25, 25, 25, 50, 50, 100ms\n        // The timeout is set to `now + delay` each time, so we check individual delays\n        let expected_delays_ms: [u64; 12] = [1, 2, 5, 10, 15, 20, 25, 25, 25, 50, 50, 100];\n\n        for (i, expected_ms) in expected_delays_ms.iter().enumerate() {\n            assert!(state.invoke(&handler, now), \"iteration {i} should retry\");\n            let timeout = state.timeout();\n            assert_eq!(\n                timeout,\n                now + Duration::from_millis(*expected_ms),\n                \"iteration {i} should have delay of {expected_ms}ms\"\n            );\n        }\n    }\n\n    #[test]\n    fn test_timeout_caps_at_max_duration() {\n        // 5ms timeout - should only allow a few iterations\n        let handler = BusyHandler::Timeout(Duration::from_millis(5));\n        let now = test_instant();\n        let mut state = BusyHandlerState::new(now);\n\n        // First iteration: 1ms delay (total: 1ms)\n        assert!(state.invoke(&handler, now));\n        // Second iteration: 2ms delay (total: 3ms)\n        assert!(state.invoke(&handler, now));\n        // Third iteration: would be 5ms but only 2ms left (total would be 8ms > 5ms)\n        // So delay is capped to 2ms\n        assert!(state.invoke(&handler, now));\n        // Fourth iteration: no time left\n        assert!(!state.invoke(&handler, now));\n    }\n\n    #[test]\n    fn test_state_reset() {\n        let handler = BusyHandler::Timeout(Duration::from_millis(100));\n        let now = test_instant();\n        let mut state = BusyHandlerState::new(now);\n\n        // Invoke a few times\n        state.invoke(&handler, now);\n        state.invoke(&handler, now);\n        state.invoke(&handler, now);\n\n        // Reset\n        let later = MonotonicInstant::now();\n        state.reset(later);\n\n        // Should be back to initial state\n        assert_eq!(state.timeout(), later);\n        assert!(state.invoke(&handler, later));\n        // First delay after reset should be 1ms\n        assert_eq!(state.timeout(), later + Duration::from_millis(1));\n    }\n\n    #[test]\n    fn test_get_delay_when_timeout_passed() {\n        let now = MonotonicInstant::now();\n        let state = BusyHandlerState::new(now);\n\n        // Timeout is at `now`, so any time >= now should return zero delay\n        assert_eq!(state.get_delay(now), Duration::ZERO);\n\n        // A later time should also return zero\n        std::thread::sleep(Duration::from_micros(10));\n        let later = MonotonicInstant::now();\n        assert_eq!(state.get_delay(later), Duration::ZERO);\n    }\n\n    #[test]\n    fn test_get_delay_calculates_remaining_time() {\n        let now = MonotonicInstant::now();\n        let mut state = BusyHandlerState::new(now);\n\n        let handler = BusyHandler::Timeout(Duration::from_millis(100));\n        state.invoke(&handler, now); // Sets timeout to now + 1ms\n\n        // Check delay from `now` - should be 1ms\n        let delay = state.get_delay(now);\n        assert_eq!(delay, Duration::from_millis(1));\n    }\n}\n"
  },
  {
    "path": "core/connection.rs",
    "content": "use crate::error::io_error;\nuse crate::storage::journal_mode;\nuse crate::sync::{\n    atomic::{AtomicBool, AtomicI32, AtomicI64, AtomicIsize, AtomicU16, AtomicU64, Ordering},\n    Arc, RwLock,\n};\nuse crate::turso_assert;\n#[cfg(all(feature = \"fs\", feature = \"conn_raw_api\"))]\nuse crate::types::{WalFrameInfo, WalState};\n#[cfg(feature = \"fs\")]\nuse crate::util::{OpenMode, OpenOptions};\n#[cfg(all(feature = \"fs\", feature = \"conn_raw_api\"))]\nuse crate::Page;\nuse crate::MAIN_DB_ID;\nuse crate::{\n    ast, function,\n    io::{MemoryIO, IO},\n    parse_schema_rows, refresh_analyze_stats, translate,\n    util::IOExt,\n    vdbe, AllViewsTxState, AtomicCipherMode, AtomicSyncMode, AtomicTempStore, BusyHandler,\n    BusyHandlerCallback, CaptureDataChangesInfo, CheckpointMode, CheckpointResult, CipherMode, Cmd,\n    Completion, ConnectionMetrics, Database, DatabaseCatalog, DatabaseOpts, Duration,\n    EncryptionKey, EncryptionOpts, IndexMethod, LimboError, MvStore, OpenFlags, PageSize, Pager,\n    Parser, QueryMode, QueryRunner, Result, Schema, Statement, SyncMode, TransactionMode, Trigger,\n    Value, VirtualTable,\n};\nuse arc_swap::ArcSwap;\nuse rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};\nuse smallvec::SmallVec;\nuse std::fmt::Display;\nuse std::ops::Deref;\nuse tracing::{instrument, Level};\nuse turso_macros::AtomicEnum;\n\n#[derive(Clone, AtomicEnum, Copy, PartialEq, Eq, Debug)]\npub(crate) enum TransactionState {\n    Write {\n        schema_did_change: bool,\n    },\n    Read,\n    /// PendingUpgrade remembers what transaction state was before upgrade to write (has_read_txn is true if before transaction were in Read state)\n    /// This is important, because if we failed to initialize write transaction immediatley - we need to end implicitly started read txn (e.g. for simiple INSERT INTO operation)\n    /// But for late upgrade of transaction we should keep read transaction active (e.g. BEGIN; SELECT ...; INSERT INTO ...)\n    PendingUpgrade {\n        has_read_txn: bool,\n    },\n    None,\n}\n\n/// Database connection handle.\n///\n/// If you add a setting that affects SQL compilation or execution, call\n/// `bump_prepare_context_generation()` in its setter so cached prepared\n/// statements know they need to be reprepared.\npub struct Connection {\n    pub(crate) db: Arc<Database>,\n    pub(crate) pager: ArcSwap<Pager>,\n    pub(crate) schema: RwLock<Arc<Schema>>,\n    /// Per-database schema cache (database_index -> schema)\n    /// Loaded lazily to avoid copying all schemas on connection open\n    pub(super) database_schemas: RwLock<HashMap<usize, Arc<Schema>>>,\n    /// Whether to automatically commit transaction\n    pub(crate) auto_commit: AtomicBool,\n    pub(super) transaction_state: AtomicTransactionState,\n    pub(super) last_insert_rowid: AtomicI64,\n    pub(crate) last_change: AtomicI64,\n    pub(crate) total_changes: AtomicI64,\n    pub(crate) syms: parking_lot::RwLock<SymbolTable>,\n    pub(super) _shared_cache: bool,\n    pub(super) cache_size: AtomicI32,\n    /// page size used for an uninitialized database or the next vacuum command.\n    /// it's not always equal to the current page size of the database\n    pub(super) page_size: AtomicU16,\n    /// Disable automatic checkpoint behaviour when DB is shutted down or WAL reach certain size\n    /// Client still can manually execute PRAGMA wal_checkpoint(...) commands\n    pub(super) wal_auto_checkpoint_disabled: AtomicBool,\n    pub(super) capture_data_changes: RwLock<Option<CaptureDataChangesInfo>>,\n    /// CDC v2: transaction ID for grouping CDC records by transaction.\n    /// -1 means unset (will be assigned on first CDC write in the transaction).\n    pub(crate) cdc_transaction_id: AtomicI64,\n    pub(super) closed: AtomicBool,\n    /// Attached databases\n    pub(super) attached_databases: RwLock<DatabaseCatalog>,\n    pub(super) query_only: AtomicBool,\n    /// If enabled, the UPDATE/DELETE statements must have a WHERE clause\n    pub(super) dml_require_where: AtomicBool,\n    pub(crate) mv_tx: RwLock<Option<(crate::mvcc::database::TxID, TransactionMode)>>,\n    /// Per-attached-database MVCC transactions.\n    /// Main DB uses `mv_tx` above for zero-cost hot path access.\n    pub(crate) attached_mv_txs:\n        RwLock<HashMap<usize, (crate::mvcc::database::TxID, TransactionMode)>>,\n\n    /// Per-connection view transaction states for uncommitted changes. This represents\n    /// one entry per view that was touched in the transaction.\n    pub(crate) view_transaction_states: AllViewsTxState,\n    /// Connection-level metrics aggregation\n    pub metrics: RwLock<ConnectionMetrics>,\n    /// Greater than zero if connection executes a program within a program\n    /// This is necessary in order for connection to not \"finalize\" transaction (commit/abort) when program ends\n    /// (because parent program is still pending and it will handle \"finalization\" instead)\n    ///\n    /// The state is integer as we may want to spawn deep nested programs (e.g. Root -[run]-> S1 -[run]-> S2 -[run]-> ...)\n    /// and we need to track current nestedness depth in order to properly understand when we will reach the root back again\n    pub(super) nestedness: AtomicI32,\n    /// Stack of currently compiling triggers to prevent recursive trigger subprogram compilation\n    pub(super) compiling_triggers: RwLock<Vec<Arc<Trigger>>>,\n    /// Stack of currently executing triggers to prevent recursive trigger execution\n    /// Only prevents the same trigger from firing again, allowing different triggers on the same table to fire\n    pub(super) executing_triggers: RwLock<Vec<Arc<Trigger>>>,\n    pub(crate) encryption_key: RwLock<Option<EncryptionKey>>,\n    pub(super) encryption_cipher_mode: AtomicCipherMode,\n    pub(super) sync_mode: AtomicSyncMode,\n    pub(super) temp_store: AtomicTempStore,\n    pub(super) data_sync_retry: AtomicBool,\n    /// Busy handler for lock contention\n    /// Default is BusyHandler::None (return SQLITE_BUSY immediately)\n    pub(super) busy_handler: RwLock<BusyHandler>,\n    /// Whether this is an internal connection used for MVCC bootstrap\n    pub(super) is_mvcc_bootstrap_connection: AtomicBool,\n    /// Whether pragma foreign_keys=ON for this connection\n    pub(super) fk_pragma: AtomicBool,\n    pub(crate) fk_deferred_violations: AtomicIsize,\n    /// Number of active write statements on this connection.\n    pub(crate) n_active_writes: AtomicI32,\n    /// Whether pragma ignore_check_constraints=ON for this connection\n    pub(super) check_constraints_pragma: AtomicBool,\n    /// Track when each virtual table instance is currently in transaction.\n    pub(crate) vtab_txn_states: RwLock<HashSet<u64>>,\n    /// Generation counter bumped whenever any setting that affects PrepareContext\n    /// changes. Allows prepared statements to cheaply detect when they need to be\n    /// reprepared (single u64 comparison instead of rebuilding the full context).\n    /// IMPORTANT: this is a bit of a regression landmine because the generation\n    /// MUST be incremented whenever any setting that affects PrepareContext changes,\n    /// and this is not currently centralized; each setter bumps the generation individually.\n    pub(crate) prepare_context_generation: AtomicU64,\n}\n\n// SAFETY: This needs to be audited for thread safety.\n// See: https://github.com/tursodatabase/turso/issues/1552\ncrate::assert::assert_send_sync!(Connection);\n\nimpl Drop for Connection {\n    fn drop(&mut self) {\n        if !self.is_closed() {\n            // Roll back any active MVCC transactions so that MvStore entries\n            // don't leak and block future checkpoints.  The tx may have\n            // already been committed/aborted externally (e.g. by tests that\n            // manipulate MvStore directly), so only rollback if still active.\n            if let Some(mv_store) = self.db.get_mv_store().as_ref() {\n                if let Some(tx_id) = self.get_mv_tx_id() {\n                    let pager = self.pager.load();\n                    if mv_store.is_tx_rollbackable(tx_id) {\n                        mv_store.rollback_tx(tx_id, pager.clone(), self, MAIN_DB_ID);\n                    } else {\n                        self.set_mv_tx(None);\n                    }\n                    pager.end_read_tx();\n                }\n            }\n            self.rollback_attached_mvcc_txs(false);\n\n            // Release any WAL locks the connection might be holding.\n            // This prevents deadlocks if a connection is dropped (e.g., due to a panic)\n            // while holding a read or write lock.\n            let pager = self.pager.load();\n            if let Some(wal) = &pager.wal {\n                if wal.holds_write_lock() {\n                    wal.end_write_tx();\n                }\n                if wal.holds_read_lock() {\n                    wal.end_read_tx();\n                }\n            }\n\n            // Also release WAL locks on all attached database pagers\n            for attached_pager in self.get_all_attached_pagers() {\n                if let Some(wal) = &attached_pager.wal {\n                    if wal.holds_write_lock() {\n                        wal.end_write_tx();\n                    }\n                    if wal.holds_read_lock() {\n                        wal.end_read_tx();\n                    }\n                }\n            }\n\n            // if connection wasn't properly closed, decrement the connection counter\n            self.db\n                .n_connections\n                .fetch_sub(1, crate::sync::atomic::Ordering::SeqCst);\n        }\n    }\n}\n\nimpl Connection {\n    /// Bump the prepare context generation counter. Must be called whenever any\n    /// connection setting that is tracked in `PrepareContext` changes, so that\n    /// prepared statements know they need to be reprepared.\n    #[inline]\n    pub(crate) fn bump_prepare_context_generation(&self) {\n        self.prepare_context_generation\n            .fetch_add(1, Ordering::Release);\n    }\n\n    #[inline]\n    pub(crate) fn prepare_context_generation(&self) -> u64 {\n        self.prepare_context_generation.load(Ordering::Acquire)\n    }\n\n    /// check if connection executes nested program (so it must not do any \"finalization\" work as parent program will handle it)\n    pub fn is_nested_stmt(&self) -> bool {\n        self.nestedness.load(Ordering::SeqCst) > 0\n    }\n    /// starts nested program execution\n    pub fn start_nested(&self) {\n        self.nestedness.fetch_add(1, Ordering::SeqCst);\n    }\n    /// ends nested program execution\n    pub fn end_nested(&self) {\n        self.nestedness.fetch_add(-1, Ordering::SeqCst);\n    }\n\n    /// Check if a specific trigger is currently compiling (for recursive trigger prevention)\n    pub fn trigger_is_compiling(&self, trigger: impl AsRef<Trigger>) -> bool {\n        let compiling = self.compiling_triggers.read();\n        if let Some(trigger) = compiling.iter().find(|t| t.name == trigger.as_ref().name) {\n            tracing::debug!(\"Trigger is already compiling: {}\", trigger.name);\n            return true;\n        }\n        false\n    }\n\n    pub fn start_trigger_compilation(&self, trigger: Arc<Trigger>) {\n        tracing::debug!(\"Starting trigger compilation: {}\", trigger.name);\n        self.compiling_triggers.write().push(trigger);\n    }\n\n    pub fn end_trigger_compilation(&self) {\n        tracing::debug!(\n            \"Ending trigger compilation: {:?}\",\n            self.compiling_triggers.read().last().map(|t| &t.name)\n        );\n        self.compiling_triggers.write().pop();\n    }\n\n    /// Check if a specific trigger is currently executing (for recursive trigger prevention)\n    pub fn is_trigger_executing(&self, trigger: impl AsRef<Trigger>) -> bool {\n        let executing = self.executing_triggers.read();\n        if let Some(trigger) = executing.iter().find(|t| t.name == trigger.as_ref().name) {\n            tracing::debug!(\"Trigger is already executing: {}\", trigger.name);\n            return true;\n        }\n        false\n    }\n\n    pub fn start_trigger_execution(&self, trigger: Arc<Trigger>) {\n        tracing::debug!(\"Starting trigger execution: {}\", trigger.name);\n        self.executing_triggers.write().push(trigger);\n    }\n\n    pub fn end_trigger_execution(&self) {\n        tracing::debug!(\n            \"Ending trigger execution: {:?}\",\n            self.executing_triggers.read().last().map(|t| &t.name)\n        );\n        self.executing_triggers.write().pop();\n    }\n    pub fn prepare(self: &Arc<Connection>, sql: impl AsRef<str>) -> Result<Statement> {\n        self._prepare(sql)\n    }\n\n    #[instrument(skip_all, level = Level::INFO)]\n    pub fn _prepare(self: &Arc<Connection>, sql: impl AsRef<str>) -> Result<Statement> {\n        if self.is_closed() {\n            return Err(LimboError::InternalError(\"Connection closed\".to_string()));\n        }\n        if sql.as_ref().is_empty() {\n            return Err(LimboError::InvalidArgument(\n                \"The supplied SQL string contains no statements\".to_string(),\n            ));\n        }\n\n        let sql = sql.as_ref();\n        tracing::debug!(\"Preparing: {}\", sql);\n        let mut parser = Parser::new(sql.as_bytes());\n        let cmd = match parser.next_cmd()? {\n            Some(cmd) => cmd,\n            None => {\n                return Err(LimboError::InvalidArgument(\n                    \"The supplied SQL string contains no statements\".to_string(),\n                ));\n            }\n        };\n        let syms = self.syms.read();\n        let byte_offset_end = parser.offset();\n        let input = str::from_utf8(&sql.as_bytes()[..byte_offset_end])\n            .unwrap()\n            .trim();\n        self.maybe_update_schema();\n        let pager = self.pager.load().clone();\n        let mode = QueryMode::new(&cmd);\n        let (Cmd::Stmt(stmt) | Cmd::Explain(stmt) | Cmd::ExplainQueryPlan(stmt)) = cmd;\n\n        // Read lock + Arc::Clone the schema here to avoid a possible recursive read lock in `op_parse_schema`,\n        // where we try to read the schema again there\n        let schema = self.schema.read().clone();\n\n        let program = translate::translate(\n            &schema,\n            stmt,\n            pager.clone(),\n            self.clone(),\n            &syms,\n            mode,\n            input,\n        )?;\n        Ok(Statement::new(program, pager, mode, byte_offset_end))\n    }\n\n    /// Prepare a statement from an AST node directly, skipping SQL parsing.\n    /// This is more efficient when AST is already available or constructed programmatically.\n    pub fn prepare_stmt(self: &Arc<Connection>, stmt: ast::Stmt) -> Result<Statement> {\n        if self.is_closed() {\n            return Err(LimboError::InternalError(\"Connection closed\".to_string()));\n        }\n        self.maybe_update_schema();\n        let syms = self.syms.read();\n        let pager = self.pager.load().clone();\n        let mode = QueryMode::Normal;\n        let schema = self.schema.read().clone();\n        let program = translate::translate(\n            &schema,\n            stmt,\n            pager.clone(),\n            self.clone(),\n            &syms,\n            mode,\n            \"<ast>\", // No SQL input string available\n        )?;\n        Ok(Statement::new(program, pager, mode, 0))\n    }\n\n    /// Whether this is an internal connection used for MVCC bootstrap\n    pub fn is_mvcc_bootstrap_connection(&self) -> bool {\n        self.is_mvcc_bootstrap_connection.load(Ordering::SeqCst)\n    }\n\n    /// Promote MVCC bootstrap connection to a regular connection so it reads from the MV store again.\n    pub fn promote_to_regular_connection(&self) {\n        assert!(self.is_mvcc_bootstrap_connection.load(Ordering::SeqCst));\n        self.is_mvcc_bootstrap_connection\n            .store(false, Ordering::SeqCst);\n    }\n\n    /// Demote regular connection to MVCC bootstrap connection so it does not read from the MV store.\n    pub fn demote_to_mvcc_connection(&self) {\n        assert!(!self.is_mvcc_bootstrap_connection.load(Ordering::SeqCst));\n        self.is_mvcc_bootstrap_connection\n            .store(true, Ordering::SeqCst);\n    }\n\n    /// Parse schema from scratch if version of schema for the connection differs from the schema cookie in the root page\n    /// This function must be called outside of any transaction because internally it will start transaction session by itself\n    #[allow(dead_code)]\n    fn maybe_reparse_schema(self: &Arc<Connection>) -> Result<()> {\n        let pager = self.pager.load().clone();\n\n        // first, quickly read schema_version from the root page in order to check if schema changed\n        pager.begin_read_tx()?;\n        let on_disk_schema_version = pager\n            .io\n            .block(|| pager.with_header(|header| header.schema_cookie));\n\n        let on_disk_schema_version = match on_disk_schema_version {\n            Ok(db_schema_version) => db_schema_version.get(),\n            Err(LimboError::Page1NotAlloc) => {\n                // this means this is a fresh db, so return a schema version of 0\n                0\n            }\n            Err(err) => {\n                pager.end_read_tx();\n                return Err(err);\n            }\n        };\n        pager.end_read_tx();\n\n        let db_schema_version = self.db.schema.lock().schema_version;\n        tracing::debug!(\n            \"path: {}, db_schema_version={} vs on_disk_schema_version={}\",\n            self.db.path,\n            db_schema_version,\n            on_disk_schema_version\n        );\n        // if schema_versions matches - exit early\n        if db_schema_version == on_disk_schema_version {\n            return Ok(());\n        }\n        // maybe_reparse_schema must be called outside of any transaction\n        turso_assert!(\n            self.get_tx_state() == TransactionState::None,\n            \"unexpected start transaction\"\n        );\n        // start read transaction manually, because we will read schema cookie once again and\n        // we must be sure that it will consistent with schema content\n        //\n        // from now on we must be very careful with errors propagation\n        // in order to not accidentally keep read transaction opened\n        pager.begin_read_tx()?;\n        self.set_tx_state(TransactionState::Read);\n\n        let reparse_result = self.reparse_schema();\n\n        let previous = self.transaction_state.swap(TransactionState::None);\n        turso_assert!(\n            matches!(previous, TransactionState::None | TransactionState::Read),\n            \"unexpected end transaction state\"\n        );\n        // close opened transaction if it was kept open\n        // (in most cases, it will be automatically closed if stmt was executed properly)\n        if previous == TransactionState::Read {\n            pager.end_read_tx();\n        }\n\n        reparse_result?;\n\n        let schema = self.schema.read().clone();\n        self.db.update_schema_if_newer(schema);\n        Ok(())\n    }\n\n    pub(crate) fn reparse_schema(self: &Arc<Connection>) -> Result<()> {\n        let pager = self.pager.load().clone();\n\n        // read cookie before consuming statement program - otherwise we can end up reading cookie with closed transaction state\n        let cookie = pager\n            .io\n            .block(|| pager.with_header(|header| header.schema_cookie))?\n            .get();\n\n        // create fresh schema as some objects can be deleted\n        let mut fresh = Schema::with_options(self.experimental_custom_types_enabled());\n        fresh.schema_version = cookie;\n\n        // Preserve existing views to avoid expensive repopulation.\n        // TODO: We may not need to do this if we materialize our views.\n        let existing_views = self.schema.read().incremental_views.clone();\n\n        // TODO: this is hack to avoid a cyclical problem with schema reprepare\n        // The problem here is that we prepare a statement here, but when the statement tries\n        // to execute it, it first checks the schema cookie to see if it needs to reprepare the statement.\n        // But in this occasion it will always reprepare, and we get an error. So we trick the statement by swapping our schema\n        // with a new clean schema that has the same header cookie.\n        self.with_schema_mut(|schema| {\n            *schema = fresh.clone();\n        });\n\n        let stmt = self.prepare(\"SELECT * FROM sqlite_schema\")?;\n\n        // MVCC bootstrap connection gets the \"baseline\" from the DB file and ignores anything in MV store\n        let mv_tx = if self.is_mvcc_bootstrap_connection() {\n            None\n        } else {\n            self.get_mv_tx()\n        };\n        // TODO: This function below is synchronous, make it async\n        parse_schema_rows(stmt, &mut fresh, &self.syms.read(), mv_tx, existing_views)?;\n\n        // Load custom types from __turso_internal_types if the table exists\n        // and custom types are enabled. Type loading errors are non-fatal: we log\n        // warnings and continue with whatever types loaded successfully.\n        if self.experimental_custom_types_enabled()\n            && fresh\n                .tables\n                .contains_key(crate::schema::TURSO_TYPES_TABLE_NAME)\n        {\n            // Temporarily install the schema so we can prepare a query against it\n            self.with_schema_mut(|schema| {\n                *schema = fresh.clone();\n            });\n            let load_result: Result<()> = (|| {\n                let type_sqls = self.query_stored_type_definitions()?;\n                fresh.load_type_definitions(&type_sqls)?;\n                Ok(())\n            })();\n            if let Err(e) = load_result {\n                tracing::warn!(\"Failed to load custom types: {}\", e);\n            }\n        }\n\n        // Best-effort load stats if sqlite_stat1 is present and DB is initialized.\n        refresh_analyze_stats(self);\n\n        tracing::debug!(\n            \"reparse_schema: schema_version={}, tables={:?}\",\n            fresh.schema_version,\n            fresh.tables.keys()\n        );\n        self.with_schema_mut(|schema| {\n            *schema = fresh;\n        });\n        Result::Ok(())\n    }\n\n    #[instrument(skip_all, level = Level::INFO)]\n    pub fn prepare_execute_batch(self: &Arc<Connection>, sql: impl AsRef<str>) -> Result<()> {\n        if self.is_closed() {\n            return Err(LimboError::InternalError(\"Connection closed\".to_string()));\n        }\n        if sql.as_ref().is_empty() {\n            return Err(LimboError::InvalidArgument(\n                \"The supplied SQL string contains no statements\".to_string(),\n            ));\n        }\n        self.maybe_update_schema();\n        let sql = sql.as_ref();\n        tracing::trace!(\"Preparing and executing batch: {}\", sql);\n        let mut parser = Parser::new(sql.as_bytes());\n        while let Some(cmd) = parser.next_cmd()? {\n            let syms = self.syms.read();\n            let pager = self.pager.load().clone();\n            let byte_offset_end = parser.offset();\n            let input = str::from_utf8(&sql.as_bytes()[..byte_offset_end])\n                .unwrap()\n                .trim();\n            let mode = QueryMode::new(&cmd);\n            let (Cmd::Stmt(stmt) | Cmd::Explain(stmt) | Cmd::ExplainQueryPlan(stmt)) = cmd;\n            let schema = self.schema.read().clone();\n            let program = translate::translate(\n                &schema,\n                stmt,\n                pager.clone(),\n                self.clone(),\n                &syms,\n                mode,\n                input,\n            )?;\n            Statement::new(program, pager.clone(), mode, 0).run_ignore_rows()?;\n        }\n        Ok(())\n    }\n\n    #[instrument(skip_all, level = Level::INFO)]\n    pub fn query(self: &Arc<Connection>, sql: impl AsRef<str>) -> Result<Option<Statement>> {\n        if self.is_closed() {\n            return Err(LimboError::InternalError(\"Connection closed\".to_string()));\n        }\n        let sql = sql.as_ref();\n        self.maybe_update_schema();\n        tracing::trace!(\"Querying: {}\", sql);\n        let mut parser = Parser::new(sql.as_bytes());\n        let cmd = parser.next_cmd()?;\n        let byte_offset_end = parser.offset();\n        let input = str::from_utf8(&sql.as_bytes()[..byte_offset_end])\n            .unwrap()\n            .trim();\n        match cmd {\n            Some(cmd) => self.run_cmd(cmd, input),\n            None => Ok(None),\n        }\n    }\n\n    #[instrument(skip_all, level = Level::INFO)]\n    pub(crate) fn run_cmd(\n        self: &Arc<Connection>,\n        cmd: Cmd,\n        input: &str,\n    ) -> Result<Option<Statement>> {\n        if self.is_closed() {\n            return Err(LimboError::InternalError(\"Connection closed\".to_string()));\n        }\n        let syms = self.syms.read();\n        let pager = self.pager.load().clone();\n        let mode = QueryMode::new(&cmd);\n        let (Cmd::Stmt(stmt) | Cmd::Explain(stmt) | Cmd::ExplainQueryPlan(stmt)) = cmd;\n        let schema = self.schema.read().clone();\n        let program = translate::translate(\n            &schema,\n            stmt,\n            pager.clone(),\n            self.clone(),\n            &syms,\n            mode,\n            input,\n        )?;\n        let stmt = Statement::new(program, pager, mode, 0);\n        Ok(Some(stmt))\n    }\n\n    pub fn query_runner<'a>(self: &'a Arc<Connection>, sql: &'a [u8]) -> QueryRunner<'a> {\n        QueryRunner::new(self, sql)\n    }\n\n    /// Execute will run a query from start to finish taking ownership of I/O because it will run pending I/Os if it didn't finish.\n    /// TODO: make this api async\n    #[instrument(skip_all, level = Level::INFO)]\n    pub fn execute(self: &Arc<Connection>, sql: impl AsRef<str>) -> Result<()> {\n        if self.is_closed() {\n            return Err(LimboError::InternalError(\"Connection closed\".to_string()));\n        }\n        let sql = sql.as_ref();\n        self.maybe_update_schema();\n        let mut parser = Parser::new(sql.as_bytes());\n        while let Some(cmd) = parser.next_cmd()? {\n            let syms = self.syms.read();\n            let pager = self.pager.load().clone();\n            let byte_offset_end = parser.offset();\n            let input = str::from_utf8(&sql.as_bytes()[..byte_offset_end])\n                .unwrap()\n                .trim();\n            let mode = QueryMode::new(&cmd);\n            let (Cmd::Stmt(stmt) | Cmd::Explain(stmt) | Cmd::ExplainQueryPlan(stmt)) = cmd;\n            let schema = self.schema.read().clone();\n            let program = translate::translate(\n                &schema,\n                stmt,\n                pager.clone(),\n                self.clone(),\n                &syms,\n                mode,\n                input,\n            )?;\n            Statement::new(program, pager.clone(), mode, 0).run_ignore_rows()?;\n        }\n        Ok(())\n    }\n\n    #[instrument(skip_all, level = Level::INFO)]\n    pub fn consume_stmt(\n        self: &Arc<Connection>,\n        sql: impl AsRef<str>,\n    ) -> Result<Option<(Statement, usize)>> {\n        let mut parser = Parser::new(sql.as_ref().as_bytes());\n        let Some(cmd) = parser.next_cmd()? else {\n            return Ok(None);\n        };\n        let syms = self.syms.read();\n        let pager = self.pager.load().clone();\n        let byte_offset_end = parser.offset();\n        let input = str::from_utf8(&sql.as_ref().as_bytes()[..byte_offset_end])\n            .unwrap()\n            .trim();\n        let mode = QueryMode::new(&cmd);\n        let (Cmd::Stmt(stmt) | Cmd::Explain(stmt) | Cmd::ExplainQueryPlan(stmt)) = cmd;\n        let schema = self.schema.read().clone();\n        let program = translate::translate(\n            &schema,\n            stmt,\n            pager.clone(),\n            self.clone(),\n            &syms,\n            mode,\n            input,\n        )?;\n        let stmt = Statement::new(program, pager, mode, 0);\n        Ok(Some((stmt, parser.offset())))\n    }\n\n    #[cfg(feature = \"fs\")]\n    pub fn from_uri(uri: &str, db_opts: DatabaseOpts) -> Result<(Arc<dyn IO>, Arc<Connection>)> {\n        use crate::util::MEMORY_PATH;\n        let opts = OpenOptions::parse(uri)?;\n        let flags = opts.get_flags()?;\n        if opts.path == MEMORY_PATH || matches!(opts.mode, OpenMode::Memory) {\n            let io = Arc::new(MemoryIO::new());\n            let db = Database::open_file_with_flags(io.clone(), MEMORY_PATH, flags, db_opts, None)?;\n            let conn = db.connect()?;\n            return Ok((io, conn));\n        }\n        let encryption_opts = match (opts.cipher.clone(), opts.hexkey.clone()) {\n            (Some(cipher), Some(hexkey)) => Some(EncryptionOpts { cipher, hexkey }),\n            (Some(_), None) => {\n                return Err(LimboError::InvalidArgument(\n                    \"hexkey is required when cipher is provided\".to_string(),\n                ))\n            }\n            (None, Some(_)) => {\n                return Err(LimboError::InvalidArgument(\n                    \"cipher is required when hexkey is provided\".to_string(),\n                ))\n            }\n            (None, None) => None,\n        };\n        let (io, db) = Database::open_new(\n            &opts.path,\n            opts.vfs.as_ref(),\n            flags,\n            db_opts,\n            encryption_opts,\n        )?;\n        if let Some(modeof) = opts.modeof {\n            let perms = std::fs::metadata(modeof).map_err(|e| io_error(e, \"metadata\"))?;\n            std::fs::set_permissions(&opts.path, perms.permissions())\n                .map_err(|e| io_error(e, \"set_permissions\"))?;\n        }\n        let conn = db.connect()?;\n        if let Some(cipher) = opts.cipher {\n            let _ = conn.pragma_update(\"cipher\", format!(\"'{cipher}'\"));\n        }\n        if let Some(hexkey) = opts.hexkey {\n            let _ = conn.pragma_update(\"hexkey\", format!(\"'{hexkey}'\"));\n        }\n        Ok((io, conn))\n    }\n\n    #[cfg(feature = \"fs\")]\n    fn from_uri_attached(\n        uri: &str,\n        mut db_opts: DatabaseOpts,\n        main_db_flags: OpenFlags,\n        io: Arc<dyn IO>,\n    ) -> Result<(Arc<Database>, Option<EncryptionOpts>)> {\n        let opts = OpenOptions::parse(uri)?;\n        let mut flags = opts.get_flags()?;\n        if main_db_flags.contains(OpenFlags::ReadOnly) {\n            flags |= OpenFlags::ReadOnly;\n        }\n        let encryption_opts = match (opts.cipher.clone(), opts.hexkey.clone()) {\n            (Some(cipher), Some(hexkey)) => Some(EncryptionOpts { cipher, hexkey }),\n            (Some(_), None) => {\n                return Err(LimboError::InvalidArgument(\n                    \"hexkey is required when cipher is provided\".to_string(),\n                ))\n            }\n            (None, Some(_)) => {\n                return Err(LimboError::InvalidArgument(\n                    \"cipher is required when hexkey is provided\".to_string(),\n                ))\n            }\n            (None, None) => None,\n        };\n        if encryption_opts.is_some() {\n            db_opts = db_opts.with_encryption(true);\n        }\n        let io = opts.vfs.map(Database::io_for_vfs).unwrap_or(Ok(io))?;\n        let db = Database::open_file_with_flags(\n            io.clone(),\n            &opts.path,\n            flags,\n            db_opts,\n            encryption_opts.clone(),\n        )?;\n        if let Some(modeof) = opts.modeof {\n            let perms = std::fs::metadata(modeof).map_err(|e| io_error(e, \"metadata\"))?;\n            std::fs::set_permissions(&opts.path, perms.permissions())\n                .map_err(|e| io_error(e, \"set_permissions\"))?;\n        }\n        Ok((db, encryption_opts))\n    }\n\n    pub fn set_foreign_keys_enabled(&self, enable: bool) {\n        self.fk_pragma.store(enable, Ordering::Release);\n        self.bump_prepare_context_generation();\n    }\n\n    pub fn foreign_keys_enabled(&self) -> bool {\n        self.fk_pragma.load(Ordering::Acquire)\n    }\n\n    pub fn set_check_constraints_ignored(&self, ignore: bool) {\n        self.check_constraints_pragma\n            .store(ignore, Ordering::Release);\n    }\n\n    pub fn check_constraints_ignored(&self) -> bool {\n        self.check_constraints_pragma.load(Ordering::Acquire)\n    }\n\n    pub(crate) fn clear_deferred_foreign_key_violations(&self) -> isize {\n        self.fk_deferred_violations.swap(0, Ordering::Release)\n    }\n\n    pub(crate) fn get_deferred_foreign_key_violations(&self) -> isize {\n        self.fk_deferred_violations.load(Ordering::Acquire)\n    }\n\n    pub(crate) fn increment_deferred_foreign_key_violations(&self, v: isize) {\n        self.fk_deferred_violations.fetch_add(v, Ordering::AcqRel);\n    }\n\n    /// Query the CREATE TYPE SQL definitions stored in __turso_internal_types.\n    /// The connection's schema must already contain the table definitions so\n    /// that `prepare` can resolve the table name. Returns an empty Vec if the\n    /// types table does not exist.\n    pub(crate) fn query_stored_type_definitions(self: &Arc<Connection>) -> Result<Vec<String>> {\n        let has_types_table = {\n            let s = self.schema.read();\n            s.tables.contains_key(crate::schema::TURSO_TYPES_TABLE_NAME)\n        };\n        if !has_types_table {\n            return Ok(Vec::new());\n        }\n        let mut type_stmt = self.prepare(format!(\n            \"SELECT name, sql FROM {}\",\n            crate::schema::TURSO_TYPES_TABLE_NAME\n        ))?;\n        let mut type_rows = Vec::new();\n        type_stmt.run_with_row_callback(|row| {\n            type_rows.push(row.get::<&str>(1)?.to_string());\n            Ok(())\n        })?;\n        Ok(type_rows)\n    }\n\n    pub fn maybe_update_schema(&self) {\n        let current_schema_version = self.schema.read().schema_version;\n        let schema = self.db.schema.lock();\n        if matches!(self.get_tx_state(), TransactionState::None)\n            && current_schema_version != schema.schema_version\n        {\n            *self.schema.write() = schema.clone();\n        }\n    }\n\n    /// Read schema version at current transaction\n    #[cfg(all(feature = \"fs\", feature = \"conn_raw_api\"))]\n    pub fn read_schema_version(&self) -> Result<u32> {\n        let pager = self.pager.load();\n        pager\n            .io\n            .block(|| pager.with_header(|header| header.schema_cookie))\n            .map(|version| version.get())\n    }\n\n    /// Update schema version to the new value within opened write transaction\n    ///\n    /// New version of the schema must be strictly greater than previous one - otherwise method will panic\n    /// Write transaction must be opened in advance - otherwise method will panic\n    #[cfg(all(feature = \"fs\", feature = \"conn_raw_api\"))]\n    pub fn write_schema_version(self: &Arc<Connection>, version: u32) -> Result<()> {\n        let TransactionState::Write { .. } = self.get_tx_state() else {\n            return Err(LimboError::InternalError(\n                \"write_schema_version must be called from within Write transaction\".to_string(),\n            ));\n        };\n        let pager = self.pager.load();\n        pager.io.block(|| {\n            pager.with_header_mut(|header| {\n                turso_assert!(\n                    header.schema_cookie.get() < version,\n                    \"cookie can't go back in time\"\n                );\n                self.set_tx_state(TransactionState::Write {\n                    schema_did_change: true,\n                });\n                self.with_schema_mut(|schema| schema.schema_version = version);\n                header.schema_cookie = version.into();\n            })\n        })?;\n        self.reparse_schema()?;\n        Ok(())\n    }\n\n    /// Try to read page with given ID with fixed WAL watermark position\n    /// This method return false if page is not found (so, this is probably new page created after watermark position which wasn't checkpointed to the DB file yet)\n    #[cfg(all(feature = \"fs\", feature = \"conn_raw_api\"))]\n    pub fn try_wal_watermark_read_page(\n        &self,\n        page_idx: u32,\n        page: &mut [u8],\n        frame_watermark: Option<u64>,\n    ) -> Result<bool> {\n        let Some((page_ref, c)) =\n            self.try_wal_watermark_read_page_begin(page_idx, frame_watermark)?\n        else {\n            return Ok(false);\n        };\n        match self.get_pager().io.wait_for_completion(c) {\n            #[cfg(all(target_os = \"windows\", feature = \"experimental_win_iocp\"))]\n            Err(LimboError::CompletionError(crate::error::CompletionError::IOError(\n                std::io::ErrorKind::UnexpectedEof,\n                _,\n            ))) => {\n                return Ok(false);\n            }\n            Err(e) => return Err(e),\n            _ => {}\n        }\n\n        self.try_wal_watermark_read_page_end(page, page_ref)\n    }\n\n    #[cfg(all(feature = \"fs\", feature = \"conn_raw_api\"))]\n    pub fn try_wal_watermark_read_page_begin(\n        &self,\n        page_idx: u32,\n        frame_watermark: Option<u64>,\n    ) -> Result<Option<(Arc<Page>, Completion)>> {\n        let pager = self.pager.load();\n        let (page_ref, c) = match pager.read_page_no_cache(page_idx as i64, frame_watermark, true) {\n            Ok(result) => result,\n            // on windows, zero read will trigger UnexpectedEof\n            #[cfg(target_os = \"windows\")]\n            Err(LimboError::CompletionError(crate::error::CompletionError::IOError(\n                std::io::ErrorKind::UnexpectedEof,\n                _,\n            ))) => return Ok(None),\n            Err(err) => return Err(err),\n        };\n\n        Ok(Some((page_ref, c)))\n    }\n\n    #[cfg(all(feature = \"fs\", feature = \"conn_raw_api\"))]\n    pub fn try_wal_watermark_read_page_end(\n        &self,\n        page: &mut [u8],\n        page_ref: Arc<Page>,\n    ) -> Result<bool> {\n        let content = page_ref.get_contents();\n        // empty read - attempt to read absent page\n        if content.buffer.as_ref().is_none_or(|b| b.is_empty()) {\n            return Ok(false);\n        }\n        page.copy_from_slice(content.as_ptr());\n        Ok(true)\n    }\n\n    /// Return unique set of page numbers changes after WAL watermark position in the current WAL session\n    /// (so, if concurrent connection wrote something to the WAL - this method will not see this change)\n    #[cfg(all(feature = \"fs\", feature = \"conn_raw_api\"))]\n    pub fn wal_changed_pages_after(&self, frame_watermark: u64) -> Result<Vec<u32>> {\n        self.pager.load().wal_changed_pages_after(frame_watermark)\n    }\n\n    #[cfg(all(feature = \"fs\", feature = \"conn_raw_api\"))]\n    pub fn wal_state(&self) -> Result<WalState> {\n        self.pager.load().wal_state()\n    }\n\n    #[cfg(all(feature = \"fs\", feature = \"conn_raw_api\"))]\n    pub fn wal_get_frame(&self, frame_no: u64, frame: &mut [u8]) -> Result<WalFrameInfo> {\n        use crate::storage::sqlite3_ondisk::parse_wal_frame_header;\n\n        let c = self.pager.load().wal_get_frame(frame_no, frame)?;\n        self.db.io.wait_for_completion(c)?;\n        let (header, _) = parse_wal_frame_header(frame);\n        Ok(WalFrameInfo {\n            page_no: header.page_number,\n            db_size: header.db_size,\n        })\n    }\n\n    /// Insert `frame` (header included) at the position `frame_no` in the WAL\n    /// If WAL already has frame at that position - turso-db will compare content of the page and either report conflict or return OK\n    /// If attempt to write frame at the position `frame_no` will create gap in the WAL - method will return error\n    #[cfg(all(feature = \"fs\", feature = \"conn_raw_api\"))]\n    pub fn wal_insert_frame(&self, frame_no: u64, frame: &[u8]) -> Result<WalFrameInfo> {\n        self.pager.load().wal_insert_frame(frame_no, frame)\n    }\n\n    /// Start WAL session by initiating read+write transaction for this connection\n    #[cfg(all(feature = \"fs\", feature = \"conn_raw_api\"))]\n    pub fn wal_insert_begin(&self) -> Result<()> {\n        let pager = self.pager.load();\n        pager.begin_read_tx()?;\n        pager.io.block(|| pager.begin_write_tx()).inspect_err(|_| {\n            pager.end_read_tx();\n        })?;\n\n        // start write transaction and disable auto-commit mode as SQL can be executed within WAL session (at caller own risk)\n        self.set_tx_state(TransactionState::Write {\n            schema_did_change: false,\n        });\n        self.auto_commit.store(false, Ordering::SeqCst);\n\n        Ok(())\n    }\n\n    /// Finish WAL session by ending read+write transaction taken in the [Self::wal_insert_begin] method\n    /// All frames written after last commit frame (db_size > 0) within the session will be rolled back\n    #[cfg(all(feature = \"fs\", feature = \"conn_raw_api\"))]\n    pub fn wal_insert_end(self: &Arc<Connection>, force_commit: bool) -> Result<()> {\n        use crate::{return_if_io, types::IOResult};\n\n        {\n            let pager = self.pager.load();\n\n            let Some(wal) = pager.wal.as_ref() else {\n                return Err(LimboError::InternalError(\n                    \"wal_insert_end called without a wal\".to_string(),\n                ));\n            };\n\n            let commit_err = if force_commit {\n                pager\n                    .io\n                    .block(|| {\n                        return_if_io!(pager.commit_dirty_pages(\n                            true,\n                            self.get_sync_mode(),\n                            self.get_data_sync_retry(),\n                        ));\n                        pager.commit_dirty_pages_end();\n                        Ok(IOResult::Done(()))\n                    })\n                    .err()\n            } else {\n                None\n            };\n\n            self.auto_commit.store(true, Ordering::SeqCst);\n            self.set_tx_state(TransactionState::None);\n            wal.end_write_tx();\n            wal.end_read_tx();\n\n            if !force_commit {\n                // remove all non-commited changes in case if WAL session left some suffix without commit frame\n                if let Some(mv_store) = self.mv_store().as_ref() {\n                    if let Some(tx_id) = self.get_mv_tx_id() {\n                        mv_store.rollback_tx(tx_id, pager.clone(), self, MAIN_DB_ID);\n                    }\n                }\n                pager.rollback(false, self, true);\n            }\n            if let Some(err) = commit_err {\n                return Err(err);\n            }\n        }\n\n        // let's re-parse schema from scratch if schema cookie changed compared to the our in-memory view of schema\n        self.maybe_reparse_schema()?;\n        Ok(())\n    }\n\n    /// Flush dirty pages to disk.\n    pub fn cacheflush(&self) -> Result<Vec<Completion>> {\n        if self.is_closed() {\n            return Err(LimboError::InternalError(\"Connection closed\".to_string()));\n        }\n        let pager = self.pager.load();\n        pager.io.block(|| pager.cacheflush())\n    }\n\n    pub fn checkpoint(self: &Arc<Self>, mode: CheckpointMode) -> Result<CheckpointResult> {\n        use crate::mvcc::database::CheckpointStateMachine;\n        use crate::state_machine::{StateTransition, TransitionResult};\n        if self.is_closed() {\n            return Err(LimboError::InternalError(\"Connection closed\".to_string()));\n        }\n        if let Some(mv_store) = self.mv_store().as_ref() {\n            let pager = self.pager.load().clone();\n            let io = pager.io.clone();\n            let mut ckpt_sm = CheckpointStateMachine::new(\n                pager,\n                mv_store.clone(),\n                self.clone(),\n                true,\n                self.get_sync_mode(),\n            );\n            loop {\n                match ckpt_sm.step(&()) {\n                    Ok(TransitionResult::Continue) => {}\n                    Ok(TransitionResult::Done(result)) => return Ok(result),\n                    Ok(TransitionResult::Io(iocompletions)) => {\n                        if let Err(err) = iocompletions.wait(io.as_ref()) {\n                            ckpt_sm.cleanup_after_external_io_error();\n                            return Err(err);\n                        }\n                    }\n                    Err(err) => return Err(err),\n                }\n            }\n        } else {\n            self.pager\n                .load()\n                .blocking_checkpoint(mode, self.get_sync_mode())\n        }\n    }\n\n    /// Close a connection and checkpoint.\n    pub fn close(&self) -> Result<()> {\n        if self.is_closed() {\n            return Ok(());\n        }\n        self.closed.store(true, Ordering::SeqCst);\n        let pager = self.pager.load();\n\n        match self.get_tx_state() {\n            TransactionState::None => {\n                // No active transaction\n            }\n            _ => {\n                if self.mvcc_enabled() {\n                    if let Some(mv_store) = self.mv_store().as_ref() {\n                        if let Some(tx_id) = self.get_mv_tx_id() {\n                            mv_store.rollback_tx(tx_id, pager.clone(), self, MAIN_DB_ID);\n                        }\n                    }\n                    pager.end_read_tx();\n                } else {\n                    pager.rollback_tx(self);\n                }\n                // Roll back all attached DB transactions regardless of main\n                // DB mode — a :memory: attached DB may use WAL even when the\n                // main DB uses MVCC.\n                self.rollback_attached_mvcc_txs(false);\n                self.rollback_attached_wal_txns();\n                self.set_tx_state(TransactionState::None);\n            }\n        }\n\n        if self.db.n_connections.fetch_sub(1, Ordering::SeqCst).eq(&1) && !self.db.is_readonly() {\n            self.pager.load().checkpoint_shutdown(\n                self.is_wal_auto_checkpoint_disabled(),\n                self.get_sync_mode(),\n            )?;\n        };\n        Ok(())\n    }\n\n    pub fn wal_auto_checkpoint_disable(&self) {\n        self.wal_auto_checkpoint_disabled\n            .store(true, Ordering::SeqCst);\n    }\n\n    pub fn is_wal_auto_checkpoint_disabled(&self) -> bool {\n        self.wal_auto_checkpoint_disabled.load(Ordering::SeqCst) || self.db.get_mv_store().is_some()\n    }\n\n    pub fn last_insert_rowid(&self) -> i64 {\n        self.last_insert_rowid.load(Ordering::SeqCst)\n    }\n\n    pub(crate) fn update_last_rowid(&self, rowid: i64) {\n        self.last_insert_rowid.store(rowid, Ordering::SeqCst);\n    }\n\n    pub fn set_changes(&self, nchange: i64) {\n        self.last_change.store(nchange, Ordering::SeqCst);\n        self.total_changes.fetch_add(nchange, Ordering::SeqCst);\n    }\n\n    pub fn changes(&self) -> i64 {\n        self.last_change.load(Ordering::SeqCst)\n    }\n\n    pub fn total_changes(&self) -> i64 {\n        self.total_changes.load(Ordering::SeqCst)\n    }\n\n    pub fn get_cache_size(&self) -> i32 {\n        self.cache_size.load(Ordering::SeqCst)\n    }\n    pub fn set_cache_size(&self, size: i32) {\n        self.cache_size.store(size, Ordering::SeqCst);\n        self.bump_prepare_context_generation();\n    }\n\n    pub fn get_capture_data_changes_info(\n        &self,\n    ) -> crate::sync::RwLockReadGuard<'_, Option<CaptureDataChangesInfo>> {\n        self.capture_data_changes.read()\n    }\n    pub fn set_capture_data_changes_info(&self, opts: Option<CaptureDataChangesInfo>) {\n        *self.capture_data_changes.write() = opts;\n        self.bump_prepare_context_generation();\n    }\n    pub fn get_cdc_transaction_id(&self) -> i64 {\n        self.cdc_transaction_id.load(Ordering::SeqCst)\n    }\n    pub fn set_cdc_transaction_id(&self, id: i64) {\n        self.cdc_transaction_id.store(id, Ordering::SeqCst);\n    }\n    pub fn get_page_size(&self) -> PageSize {\n        let value = self.page_size.load(Ordering::SeqCst);\n        PageSize::new_from_header_u16(value).unwrap_or_default()\n    }\n\n    pub fn is_closed(&self) -> bool {\n        self.closed.load(Ordering::SeqCst)\n    }\n\n    pub fn is_query_only(&self) -> bool {\n        self.query_only.load(Ordering::SeqCst)\n    }\n\n    pub fn get_database_canonical_path(&self) -> String {\n        if self.db.path == \":memory:\" {\n            // For in-memory databases, SQLite shows empty string\n            String::new()\n        } else {\n            // For file databases, try show the full absolute path if that doesn't fail\n            match std::fs::canonicalize(&self.db.path) {\n                Ok(abs_path) => abs_path.to_string_lossy().to_string(),\n                Err(_) => self.db.path.to_string(),\n            }\n        }\n    }\n\n    /// Check if a specific attached database is read only or not, by its index\n    pub fn is_readonly(&self, index: usize) -> bool {\n        if index <= 1 {\n            self.db.is_readonly()\n        } else {\n            let db = self.attached_databases.read().get_database_by_index(index);\n            db.expect(\"Should never have called this without being sure the database exists\")\n                .is_readonly()\n        }\n    }\n\n    /// Reset the page size for the current connection.\n    ///\n    /// Specifying a new page size does not change the page size immediately.\n    /// Instead, the new page size is remembered and is used to set the page size when the database\n    /// is first created, if it does not already exist when the page_size pragma is issued,\n    /// or at the next VACUUM command that is run on the same database connection while not in WAL mode.\n    pub fn reset_page_size(&self, size: u32) -> Result<()> {\n        if self.db.initialized() {\n            return Ok(());\n        }\n        let Some(size) = PageSize::new(size) else {\n            return Ok(());\n        };\n\n        self.page_size.store(size.get_raw(), Ordering::SeqCst);\n        self.pager.load().set_initial_page_size(size)?;\n        self.bump_prepare_context_generation();\n\n        Ok(())\n    }\n\n    #[cfg(feature = \"fs\")]\n    pub fn open_new(&self, path: &str, vfs: &str) -> Result<(Arc<dyn IO>, Arc<Database>)> {\n        Database::open_with_vfs(&self.db, path, vfs)\n    }\n\n    pub fn list_vfs(&self) -> Vec<String> {\n        #[allow(unused_mut)]\n        let mut all_vfs = vec![String::from(\"memory\")];\n        #[cfg(feature = \"fs\")]\n        {\n            #[cfg(target_family = \"unix\")]\n            {\n                all_vfs.push(\"syscall\".to_string());\n            }\n            #[cfg(all(target_os = \"linux\", feature = \"io_uring\"))]\n            {\n                all_vfs.push(\"io_uring\".to_string());\n            }\n            #[cfg(all(target_os = \"windows\", feature = \"experimental_win_iocp\"))]\n            {\n                all_vfs.push(\"experimental_win_iocp\".to_string());\n            }\n            all_vfs.extend(crate::ext::list_vfs_modules());\n        }\n        all_vfs\n    }\n\n    pub fn get_auto_commit(&self) -> bool {\n        self.auto_commit.load(Ordering::SeqCst)\n    }\n\n    pub fn reparse_schema_after_extension_load(self: &Arc<Connection>) -> Result<()> {\n        if self.is_closed() {\n            return Err(LimboError::InternalError(\"Connection closed\".to_string()));\n        }\n        // Collect row data from the Statement first, then drop the Statement\n        // before taking the schema write lock. This prevents a deadlock in MVCC\n        // mode where Statement::drop -> abort -> rollback_tx -> schema.read()\n        // would deadlock against the schema write lock.\n        let mut rows_data: Vec<(String, String, String, i64, Option<String>)> = Vec::new();\n        {\n            let mut rows = self\n                .query(\"SELECT * FROM sqlite_schema\")?\n                .expect(\"query must be parsed to statement\");\n            rows.run_with_row_callback(|row| {\n                let ty = row.get::<&str>(0)?.to_string();\n                let name = row.get::<&str>(1)?.to_string();\n                let table_name = row.get::<&str>(2)?.to_string();\n                let root_page = row.get::<i64>(3)?;\n                let sql = row.get::<&str>(4).ok().map(|s| s.to_string());\n                rows_data.push((ty, name, table_name, root_page, sql));\n                Ok(())\n            })?;\n        } // Statement dropped here, before schema write lock\n\n        let syms = self.syms.read();\n        self.with_schema_mut(|schema| -> Result<()> {\n            // Incremental re-parse after extension loading. The schema already has\n            // tables/indices/views from initial parse. We only need to pick up\n            // entries that previously failed (e.g. virtual tables whose module\n            // wasn't loaded yet). \"Already exists\" errors are expected and skipped.\n            let mut from_sql_indexes = Vec::new();\n            let mut automatic_indices = HashMap::default();\n            let mut dbsp_state_roots = HashMap::default();\n            let mut dbsp_state_index_roots = HashMap::default();\n            let mut materialized_view_info = HashMap::default();\n\n            for (ty, name, table_name, root_page, sql) in &rows_data {\n                match schema.handle_schema_row(\n                    ty,\n                    name,\n                    table_name,\n                    *root_page,\n                    sql.as_deref(),\n                    &syms,\n                    &mut from_sql_indexes,\n                    &mut automatic_indices,\n                    &mut dbsp_state_roots,\n                    &mut dbsp_state_index_roots,\n                    &mut materialized_view_info,\n                ) {\n                    Ok(()) => {}\n                    Err(LimboError::ParseError(msg)) if msg.contains(\"already exists\") => {}\n                    Err(LimboError::ExtensionError(msg)) => {\n                        eprintln!(\"Warning: {msg}\");\n                    }\n                    Err(e) => return Err(e),\n                }\n            }\n\n            match schema.populate_indices(&syms, from_sql_indexes, automatic_indices, false) {\n                Ok(()) => {}\n                Err(LimboError::ParseError(msg)) if msg.contains(\"already exists\") => {}\n                Err(LimboError::ExtensionError(msg)) => eprintln!(\"Warning: {msg}\"),\n                Err(e) => return Err(e),\n            }\n            match schema.populate_materialized_views(\n                materialized_view_info,\n                dbsp_state_roots,\n                dbsp_state_index_roots,\n            ) {\n                Ok(()) => {}\n                Err(LimboError::ExtensionError(msg)) => eprintln!(\"Warning: {msg}\"),\n                Err(e) => return Err(e),\n            }\n            Ok(())\n        })\n    }\n\n    // Clearly there is something to improve here, Vec<Vec<Value>> isn't a couple of tea\n    /// Query the current rows/values of `pragma_name`.\n    pub fn pragma_query(self: &Arc<Connection>, pragma_name: &str) -> Result<Vec<Vec<Value>>> {\n        if self.is_closed() {\n            return Err(LimboError::InternalError(\"Connection closed\".to_string()));\n        }\n        let pragma = format!(\"PRAGMA {pragma_name}\");\n        let mut stmt = self.prepare(pragma)?;\n        stmt.run_collect_rows()\n    }\n\n    /// Set a new value to `pragma_name`.\n    ///\n    /// Some pragmas will return the updated value which cannot be retrieved\n    /// with this method.\n    pub fn pragma_update<V: Display>(\n        self: &Arc<Connection>,\n        pragma_name: &str,\n        pragma_value: V,\n    ) -> Result<Vec<Vec<Value>>> {\n        if self.is_closed() {\n            return Err(LimboError::InternalError(\"Connection closed\".to_string()));\n        }\n        let pragma = format!(\"PRAGMA {pragma_name} = {pragma_value}\");\n        let mut stmt = self.prepare(pragma)?;\n        stmt.run_collect_rows()\n    }\n\n    pub fn experimental_views_enabled(&self) -> bool {\n        self.db.experimental_views_enabled()\n    }\n\n    pub fn experimental_index_method_enabled(&self) -> bool {\n        self.db.experimental_index_method_enabled()\n    }\n\n    pub fn experimental_custom_types_enabled(&self) -> bool {\n        self.db.experimental_custom_types_enabled()\n    }\n\n    pub fn experimental_attach_enabled(&self) -> bool {\n        self.db.experimental_attach_enabled()\n    }\n\n    pub fn mvcc_enabled(&self) -> bool {\n        self.db.mvcc_enabled()\n    }\n\n    pub fn mv_store(&self) -> impl Deref<Target = Option<Arc<MvStore>>> {\n        struct TransparentWrapper<T>(T);\n\n        impl<T> Deref for TransparentWrapper<T> {\n            type Target = T;\n\n            fn deref(&self) -> &Self::Target {\n                &self.0\n            }\n        }\n\n        // Never use MV store for bootstrapping - we read state directly from sqlite_schema in the DB file.\n        if !self.is_mvcc_bootstrap_connection() {\n            either::Left(self.db.get_mv_store())\n        } else {\n            either::Right(TransparentWrapper(None))\n        }\n    }\n\n    /// Query the current value(s) of `pragma_name` associated to\n    /// `pragma_value`.\n    ///\n    /// This method can be used with query-only pragmas which need an argument\n    /// (e.g. `table_info('one_tbl')`) or pragmas which returns value(s)\n    /// (e.g. `integrity_check`).\n    pub fn pragma<V: Display>(\n        self: &Arc<Connection>,\n        pragma_name: &str,\n        pragma_value: V,\n    ) -> Result<Vec<Vec<Value>>> {\n        if self.is_closed() {\n            return Err(LimboError::InternalError(\"Connection closed\".to_string()));\n        }\n        let pragma = format!(\"PRAGMA {pragma_name}({pragma_value})\");\n        let mut stmt = self.prepare(pragma)?;\n        let mut results = Vec::new();\n        loop {\n            match stmt.step()? {\n                vdbe::StepResult::Row => {\n                    let row: Vec<Value> = stmt.row().unwrap().get_values().cloned().collect();\n                    results.push(row);\n                }\n                vdbe::StepResult::Interrupt | vdbe::StepResult::Busy => {\n                    return Err(LimboError::Busy);\n                }\n                _ => break,\n            }\n        }\n\n        Ok(results)\n    }\n\n    #[inline]\n    pub fn with_schema_mut<T>(&self, f: impl FnOnce(&mut Schema) -> T) -> T {\n        let mut schema_ref = self.schema.write();\n        let schema = Arc::make_mut(&mut *schema_ref);\n        f(schema)\n    }\n\n    /// Mutate the schema for a specific database (main or attached).\n    pub(crate) fn with_database_schema_mut<T>(\n        &self,\n        database_id: usize,\n        f: impl FnOnce(&mut Schema) -> T,\n    ) -> T {\n        if !crate::is_attached_db(database_id) {\n            self.with_schema_mut(f)\n        } else {\n            // For attached databases, update a connection-local copy of the schema.\n            // We don't update the shared db.schema until after the WAL commit, so\n            // other connections won't see uncommitted schema changes (which would\n            // cause SchemaUpdated mismatches).\n            let mut schemas = self.database_schemas.write();\n            let schema_arc = schemas.entry(database_id).or_insert_with(|| {\n                // Lazily copy from the shared Database schema\n                let attached_dbs = self.attached_databases.read();\n                let (db, _pager) = attached_dbs\n                    .index_to_data\n                    .get(&database_id)\n                    .expect(\"Database ID should be valid\");\n                let schema = db.schema.lock().clone();\n                schema\n            });\n            let schema = Arc::make_mut(schema_arc);\n            f(schema)\n        }\n    }\n\n    pub fn is_db_initialized(&self) -> bool {\n        self.db.initialized()\n    }\n\n    pub(crate) fn get_pager_from_database_index(&self, index: &usize) -> Arc<Pager> {\n        if *index < 2 {\n            self.pager.load().clone()\n        } else {\n            self.attached_databases.read().get_pager_by_index(index)\n        }\n    }\n\n    /// Get the database name for a given database index.\n    /// Returns \"main\" for index 0, \"temp\" for index 1, and the alias for attached databases.\n    pub(crate) fn get_database_name_by_index(&self, index: usize) -> Option<String> {\n        match index {\n            0 => Some(\"main\".to_string()),\n            1 => Some(\"temp\".to_string()),\n            _ => self.attached_databases.read().get_name_by_index(index),\n        }\n    }\n\n    #[cfg(feature = \"fs\")]\n    fn is_attached(&self, alias: &str) -> bool {\n        self.attached_databases\n            .read()\n            .name_to_index\n            .contains_key(alias)\n    }\n\n    /// Attach a database file with the given alias name\n    #[cfg(not(feature = \"fs\"))]\n    pub(crate) fn attach_database(&self, _path: &str, _alias: &str) -> Result<()> {\n        return Err(LimboError::InvalidArgument(format!(\n            \"attach not available in this build (no-fs)\"\n        )));\n    }\n\n    /// Attach a database file with the given alias name\n    #[cfg(feature = \"fs\")]\n    pub(crate) fn attach_database(&self, path: &str, alias: &str) -> Result<()> {\n        if self.is_closed() {\n            return Err(LimboError::InternalError(\"Connection closed\".to_string()));\n        }\n\n        if self.is_attached(alias) {\n            return Err(LimboError::InvalidArgument(format!(\n                \"database {alias} is already in use\"\n            )));\n        }\n\n        // Check for reserved database names\n        if alias.eq_ignore_ascii_case(\"main\") || alias.eq_ignore_ascii_case(\"temp\") {\n            return Err(LimboError::InvalidArgument(format!(\n                \"reserved name {alias} is already in use\"\n            )));\n        }\n\n        let use_views = self.db.experimental_views_enabled();\n        let use_custom_types = self.db.experimental_custom_types_enabled();\n\n        let db_opts = DatabaseOpts::new()\n            .with_views(use_views)\n            .with_custom_types(use_custom_types);\n        // Select the IO layer for the attached database:\n        // - :memory: databases always get a fresh MemoryIO\n        // - File-based databases reuse the parent's IO when the parent is also\n        //   file-based (important for simulator fault injection and WAL coordination)\n        // - If the parent is :memory: (MemoryIO) but the attached DB is file-based,\n        //   we need a file-capable IO layer since MemoryIO can't read real files\n        let is_memory_db =\n            path == \":memory:\" || path.starts_with(\"file::memory:\") || path.is_empty();\n        let io: Arc<dyn IO> = if is_memory_db {\n            Arc::new(MemoryIO::new())\n        } else if self.db.path.starts_with(\":memory:\") {\n            Database::io_for_path(path)?\n        } else {\n            self.db.io.clone()\n        };\n        let main_db_flags = self.db.open_flags;\n        let (db, encryption_opts) = Self::from_uri_attached(path, db_opts, main_db_flags, io)?;\n        // Build encryption key from URI opts to pass to _init for decrypting page 1.\n        let encryption_key = if let Some(ref enc) = encryption_opts {\n            Some(EncryptionKey::from_hex_string(&enc.hexkey)?)\n        } else {\n            None\n        };\n        let pager = Arc::new(db._init(encryption_key.as_ref())?);\n        // In-memory attached databases inherit the main database's journal mode.\n        // A fresh :memory: DB defaults to WAL, so when main is MVCC we need to\n        // create an MvStore for the attached DB so it runs in the same mode.\n        if is_memory_db && self.mvcc_enabled() && !db.mvcc_enabled() {\n            // todo(v): pass required encryption ctx to enable encryption with mvcc\n            let mv_store = journal_mode::open_mv_store(\n                db.io.clone(),\n                &db.path,\n                db.open_flags,\n                db.durable_storage.clone(),\n                None,\n            )?;\n            db.mv_store.store(Some(mv_store.clone()));\n            let bootstrap_conn = db._connect(true, Some(pager.clone()), encryption_key)?;\n            mv_store.bootstrap(bootstrap_conn)?;\n        }\n        // Reject incompatible journal modes for file-based databases: we cannot\n        // silently convert the header (the user may have attached read-only).\n        if self.mvcc_enabled() != db.mvcc_enabled() {\n            let main_mode = if self.mvcc_enabled() { \"MVCC\" } else { \"WAL\" };\n            let attached_mode = if db.mvcc_enabled() { \"MVCC\" } else { \"WAL\" };\n            return Err(LimboError::InvalidArgument(format!(\n                \"cannot attach database '{alias}': main database uses {main_mode} journal mode \\\n                 but attached database uses {attached_mode}. Both must use the same journal mode.\"\n            )));\n        }\n        // Reject mismatched page sizes: ephemeral tables and cross-database\n        // operations assume a uniform page size across all attached databases.\n        let main_pager = self.pager.load();\n        if let (Some(main_ps), Some(attached_ps)) =\n            (main_pager.get_page_size(), pager.get_page_size())\n        {\n            if main_ps != attached_ps {\n                return Err(LimboError::InvalidArgument(format!(\n                    \"cannot attach database '{alias}': page size mismatch \\\n                     (main={main_ps:?}, attached={attached_ps:?})\"\n                )));\n            }\n        }\n        self.attached_databases.write().insert(alias, (db, pager));\n        self.bump_prepare_context_generation();\n\n        Ok(())\n    }\n\n    // Detach a database by alias name\n    pub(crate) fn detach_database(&self, alias: &str) -> Result<()> {\n        if self.is_closed() {\n            return Err(LimboError::InternalError(\"Connection closed\".to_string()));\n        }\n\n        if alias == \"main\" || alias == \"temp\" {\n            return Err(LimboError::InvalidArgument(format!(\n                \"cannot detach database: {alias}\"\n            )));\n        }\n\n        // Look up the database index first, then rollback any MVCC transaction\n        // *before* removing the database from the catalog.  mv_store_for_db\n        // and get_pager_from_database_index read `attached_databases`, so we\n        // must not hold the write lock during the rollback.\n        let database_id = {\n            let attached_dbs = self.attached_databases.read();\n            match attached_dbs.name_to_index.get(alias).copied() {\n                Some(id) => id,\n                None => {\n                    return Err(LimboError::InvalidArgument(format!(\n                        \"no such database: {alias}\"\n                    )));\n                }\n            }\n        };\n\n        // Rollback any active transaction on this database before detaching.\n        // After the Database is removed from the catalog, the MvStore / Pager\n        // become unreachable and the transaction would leak forever.\n        let pager = self.get_pager_from_database_index(&database_id);\n        if let Some((tx_id, _mode)) = self.get_mv_tx_for_db(database_id) {\n            if let Some(mv_store) = self.mv_store_for_db(database_id) {\n                mv_store.rollback_tx(tx_id, pager.clone(), self, database_id);\n                pager.end_read_tx();\n            }\n            self.set_mv_tx_for_db(database_id, None);\n        } else {\n            // Non-MVCC attached DB (e.g. :memory:) — rollback WAL state.\n            pager.rollback_attached();\n        }\n\n        // Remove from catalog. The write lock must be released before\n        // acquiring database_schemas.write() to maintain consistent lock\n        // ordering (attached_databases before database_schemas).\n        {\n            let mut attached_dbs = self.attached_databases.write();\n            attached_dbs.remove(alias);\n        }\n\n        // Invalidate the cached schema for this database index so that a future\n        // ATTACH reusing the same index won't see stale schema entries.\n        self.database_schemas.write().remove(&database_id);\n        self.bump_prepare_context_generation();\n\n        Ok(())\n    }\n\n    /// List all attached database aliases\n    pub fn list_attached_databases(&self) -> Vec<String> {\n        self.attached_databases\n            .read()\n            .name_to_index\n            .keys()\n            .cloned()\n            .collect()\n    }\n\n    /// Get all attached database pagers (excludes main/temp databases)\n    pub fn get_all_attached_pagers(&self) -> Vec<Arc<Pager>> {\n        let catalog = self.attached_databases.read();\n        catalog\n            .index_to_data\n            .values()\n            .map(|(_db, pager)| pager.clone())\n            .collect()\n    }\n\n    /// Get all attached database (index, pager) pairs (excludes main/temp databases)\n    pub(crate) fn get_all_attached_pagers_with_index(&self) -> Vec<(usize, Arc<Pager>)> {\n        let catalog = self.attached_databases.read();\n        catalog\n            .index_to_data\n            .iter()\n            .map(|(&idx, (_db, pager))| (idx, pager.clone()))\n            .collect()\n    }\n\n    pub(crate) fn database_schemas(&self) -> &RwLock<HashMap<usize, Arc<Schema>>> {\n        &self.database_schemas\n    }\n\n    /// Publish a connection-local attached DB schema to the shared Database instance.\n    /// Called after the attached pager's WAL commit succeeds, so other connections\n    /// can now see the schema changes.\n    pub(crate) fn publish_attached_schema(&self, database_id: usize) {\n        let mut schemas = self.database_schemas.write();\n        if let Some(local_schema) = schemas.remove(&database_id) {\n            let attached_dbs = self.attached_databases.read();\n            if let Some((db, _pager)) = attached_dbs.index_to_data.get(&database_id) {\n                *db.schema.lock() = local_schema;\n            }\n        }\n    }\n\n    pub(crate) fn attached_databases(&self) -> &RwLock<DatabaseCatalog> {\n        &self.attached_databases\n    }\n\n    /// Access schema for a database using a closure pattern to avoid cloning\n    pub(crate) fn with_schema<T>(&self, database_id: usize, f: impl FnOnce(&Schema) -> T) -> T {\n        match database_id {\n            crate::MAIN_DB_ID | crate::TEMP_DB_ID => {\n                // Main database - use connection's schema which should be kept in sync\n                // NOTE: for Temp databases, for now they can use the connection-local schema\n                // but this will change in the future\n                let schema = self.schema.read();\n                f(&schema)\n            }\n            _ => {\n                // Attached database: prefer the connection-local copy (which may contain\n                // uncommitted schema changes from this connection's transaction), falling\n                // back to the shared Database schema (last committed state).\n                let schemas = self.database_schemas.read();\n                if let Some(local_schema) = schemas.get(&database_id) {\n                    return f(local_schema);\n                }\n                drop(schemas);\n\n                let attached_dbs = self.attached_databases.read();\n                let (db, _pager) = attached_dbs\n                    .index_to_data\n                    .get(&database_id)\n                    .expect(\"Database ID should be valid after resolve_database_id\");\n\n                let schema = db.schema.lock().clone();\n                f(&schema)\n            }\n        }\n    }\n\n    // Get the canonical path for a database given its Database object\n    fn get_canonical_path_for_database(db: &Database) -> String {\n        if db.path == \":memory:\" {\n            // For in-memory databases, SQLite shows empty string\n            String::new()\n        } else {\n            // For file databases, try to show the full absolute path if that doesn't fail\n            match std::fs::canonicalize(&db.path) {\n                Ok(abs_path) => abs_path.to_string_lossy().to_string(),\n                Err(_) => db.path.to_string(),\n            }\n        }\n    }\n\n    /// List all databases (main + attached) with their sequence numbers, names, and file paths\n    /// Returns a vector of tuples: (seq_number, name, file_path)\n    pub fn list_all_databases(&self) -> Vec<(usize, String, String)> {\n        let mut databases = Vec::new();\n\n        // Add main database (always seq=0, name=\"main\")\n        let main_path = Self::get_canonical_path_for_database(&self.db);\n        databases.push((0, \"main\".to_string(), main_path));\n\n        // Add attached databases\n        let attached_dbs = self.attached_databases.read();\n        for (alias, &seq_number) in attached_dbs.name_to_index.iter() {\n            let file_path = if let Some((db, _pager)) = attached_dbs.index_to_data.get(&seq_number)\n            {\n                Self::get_canonical_path_for_database(db)\n            } else {\n                String::new()\n            };\n            databases.push((seq_number, alias.clone(), file_path));\n        }\n\n        // Sort by sequence number to ensure consistent ordering\n        databases.sort_by_key(|&(seq, _, _)| seq);\n        databases\n    }\n\n    pub fn get_pager(&self) -> Arc<Pager> {\n        self.pager.load().clone()\n    }\n\n    pub fn get_query_only(&self) -> bool {\n        self.is_query_only()\n    }\n\n    pub fn set_query_only(&self, value: bool) {\n        self.query_only.store(value, Ordering::SeqCst);\n        self.bump_prepare_context_generation();\n    }\n\n    pub fn get_dml_require_where(&self) -> bool {\n        self.dml_require_where.load(Ordering::SeqCst)\n    }\n\n    pub fn set_dml_require_where(&self, value: bool) {\n        self.dml_require_where.store(value, Ordering::SeqCst);\n    }\n\n    pub fn get_sync_mode(&self) -> SyncMode {\n        self.sync_mode.get()\n    }\n\n    pub fn set_sync_mode(&self, mode: SyncMode) {\n        self.sync_mode.set(mode);\n        self.bump_prepare_context_generation();\n    }\n\n    pub fn get_temp_store(&self) -> crate::TempStore {\n        self.temp_store.get()\n    }\n\n    pub fn set_temp_store(&self, value: crate::TempStore) {\n        self.temp_store.set(value);\n    }\n\n    pub fn get_data_sync_retry(&self) -> bool {\n        self.data_sync_retry\n            .load(crate::sync::atomic::Ordering::SeqCst)\n    }\n\n    pub fn set_data_sync_retry(&self, value: bool) {\n        self.data_sync_retry\n            .store(value, crate::sync::atomic::Ordering::SeqCst);\n        self.bump_prepare_context_generation();\n    }\n\n    /// Get the sync type setting.\n    pub fn get_sync_type(&self) -> crate::io::FileSyncType {\n        self.pager.load().get_sync_type()\n    }\n\n    /// Set the sync type (for PRAGMA fullfsync).\n    pub fn set_sync_type(&self, value: crate::io::FileSyncType) {\n        self.pager.load().set_sync_type(value);\n    }\n\n    /// Creates a HashSet of modules that have been loaded\n    pub fn get_syms_vtab_mods(&self) -> HashSet<String> {\n        self.syms.read().vtab_modules.keys().cloned().collect()\n    }\n\n    /// Returns external (extension) functions: (name, is_aggregate, argc)\n    pub fn get_syms_functions(&self) -> Vec<(String, bool, i32)> {\n        self.syms\n            .read()\n            .functions\n            .values()\n            .map(|f| {\n                let is_agg = matches!(f.func, function::ExtFunc::Aggregate { .. });\n                let argc = match &f.func {\n                    function::ExtFunc::Aggregate { argc, .. } => *argc as i32,\n                    function::ExtFunc::Scalar(_) => -1,\n                };\n                (f.name.clone(), is_agg, argc)\n            })\n            .collect()\n    }\n\n    pub(crate) fn database_ptr(&self) -> usize {\n        Arc::as_ptr(&self.db) as usize\n    }\n\n    pub fn set_encryption_key(&self, key: EncryptionKey) -> Result<()> {\n        tracing::trace!(\"setting encryption key for connection\");\n        *self.encryption_key.write() = Some(key);\n        self.bump_prepare_context_generation();\n        self.set_encryption_context()\n    }\n\n    pub fn set_encryption_cipher(&self, cipher_mode: CipherMode) -> Result<()> {\n        tracing::trace!(\"setting encryption cipher for connection\");\n        self.encryption_cipher_mode.set(cipher_mode);\n        self.bump_prepare_context_generation();\n        self.set_encryption_context()\n    }\n\n    pub fn set_reserved_bytes(&self, reserved_bytes: u8) -> Result<()> {\n        let pager = self.pager.load();\n        pager.set_reserved_space_bytes(reserved_bytes);\n        Ok(())\n    }\n\n    /// Get the reserved bytes value from the pager cache.\n    /// Returns None if not yet set (database not initialized).\n    pub fn get_reserved_bytes(&self) -> Option<u8> {\n        let pager = self.pager.load();\n        pager.get_reserved_space()\n    }\n\n    pub fn get_encryption_cipher_mode(&self) -> Option<CipherMode> {\n        match self.encryption_cipher_mode.get() {\n            CipherMode::None => None,\n            mode => Some(mode),\n        }\n    }\n\n    // if both key and cipher are set, set encryption context on pager\n    fn set_encryption_context(&self) -> Result<()> {\n        let key_guard = self.encryption_key.read();\n        let Some(key) = key_guard.as_ref() else {\n            return Ok(());\n        };\n        let cipher_mode = self.get_encryption_cipher_mode();\n        let Some(cipher_mode) = cipher_mode else {\n            return Ok(());\n        };\n        tracing::trace!(\"setting encryption ctx for connection\");\n        let pager = self.pager.load();\n        if pager.is_encryption_ctx_set() {\n            return Err(LimboError::InvalidArgument(\n                \"cannot reset encryption attributes if already set in the session\".to_string(),\n            ));\n        }\n        pager.set_encryption_context(cipher_mode, key)\n    }\n\n    /// Sets a custom busy handler callback.\n    pub fn set_busy_handler(&self, handler: Option<BusyHandlerCallback>) {\n        *self.busy_handler.write() = match handler {\n            Some(callback) => BusyHandler::Custom { callback },\n            None => BusyHandler::None,\n        };\n        self.bump_prepare_context_generation();\n    }\n\n    /// Sets maximum total accumulated timeout. If the duration is Zero, we unset the busy handler.\n    pub fn set_busy_timeout(&self, duration: Duration) {\n        *self.busy_handler.write() = if duration.is_zero() {\n            BusyHandler::None\n        } else {\n            BusyHandler::Timeout(duration)\n        };\n        self.bump_prepare_context_generation();\n    }\n\n    /// Get the busy timeout duration.\n    pub fn get_busy_timeout(&self) -> Duration {\n        match &*self.busy_handler.read() {\n            BusyHandler::Timeout(d) => *d,\n            _ => Duration::ZERO,\n        }\n    }\n\n    /// Get a reference to the busy handler.\n    pub fn get_busy_handler(&self) -> crate::sync::RwLockReadGuard<'_, BusyHandler> {\n        self.busy_handler.read()\n    }\n\n    pub(crate) fn set_tx_state(&self, state: TransactionState) {\n        self.transaction_state.set(state);\n    }\n\n    pub(crate) fn get_tx_state(&self) -> TransactionState {\n        self.transaction_state.get()\n    }\n\n    /// Returns true if the connection is currently in a write transaction.\n    /// Used by index methods to determine if it's safe to flush writes.\n    pub fn is_in_write_tx(&self) -> bool {\n        matches!(self.get_tx_state(), TransactionState::Write { .. })\n    }\n\n    pub(crate) fn get_mv_tx_id(&self) -> Option<u64> {\n        self.mv_tx.read().map(|(tx_id, _)| tx_id)\n    }\n\n    pub(crate) fn get_mv_tx(&self) -> Option<(u64, TransactionMode)> {\n        *self.mv_tx.read()\n    }\n\n    #[inline(always)]\n    pub(crate) fn set_mv_tx(&self, tx_id_and_mode: Option<(u64, TransactionMode)>) {\n        tracing::debug!(\"set_mv_tx: {:?}\", tx_id_and_mode);\n        *self.mv_tx.write() = tx_id_and_mode;\n    }\n\n    /// Get MVCC transaction ID for a specific database.\n    /// Uses fast path for main DB, O(1) HashMap lookup for attached DBs.\n    pub(crate) fn get_mv_tx_id_for_db(&self, db: usize) -> Option<u64> {\n        if !crate::is_attached_db(db) {\n            self.get_mv_tx_id()\n        } else {\n            self.attached_mv_txs\n                .read()\n                .get(&db)\n                .map(|(tx_id, _)| *tx_id)\n        }\n    }\n\n    /// Get MVCC transaction ID and mode for a specific database.\n    pub(crate) fn get_mv_tx_for_db(&self, db: usize) -> Option<(u64, TransactionMode)> {\n        if !crate::is_attached_db(db) {\n            self.get_mv_tx()\n        } else {\n            self.attached_mv_txs.read().get(&db).copied()\n        }\n    }\n\n    /// Set MVCC transaction for a specific database.\n    pub(crate) fn set_mv_tx_for_db(&self, db: usize, val: Option<(u64, TransactionMode)>) {\n        if !crate::is_attached_db(db) {\n            self.set_mv_tx(val);\n        } else {\n            let mut txs = self.attached_mv_txs.write();\n            match val {\n                Some(v) => {\n                    txs.insert(db, v);\n                }\n                None => {\n                    txs.remove(&db);\n                }\n            }\n        }\n    }\n\n    /// Rollback MVCC transactions on all attached databases and clear the\n    /// attached transaction list.  When `clear_schemas` is true the\n    /// connection-local schema cache for each attached DB is also removed so\n    /// that post-rollback queries see the committed schema.\n    ///\n    /// This is the single source of truth for attached-MVCC rollback logic —\n    /// callers in `close()`, `rollback_current_txn()`, and `op_auto_commit`\n    /// should all delegate here.\n    pub(crate) fn rollback_attached_mvcc_txs(&self, clear_schemas: bool) {\n        let txs: HashMap<usize, _> = self.attached_mv_txs.read().clone();\n        for (&db_id, &(tx_id, _mode)) in &txs {\n            if let Some(attached_mv_store) = self.mv_store_for_db(db_id) {\n                let attached_pager = self.get_pager_from_database_index(&db_id);\n                if attached_mv_store.is_tx_rollbackable(tx_id) {\n                    attached_mv_store.rollback_tx(tx_id, attached_pager.clone(), self, db_id);\n                } else {\n                    self.set_mv_tx_for_db(db_id, None);\n                }\n                if clear_schemas {\n                    self.database_schemas().write().remove(&db_id);\n                }\n                attached_pager.end_read_tx();\n            }\n        }\n        self.attached_mv_txs.write().clear();\n    }\n\n    /// Rollback WAL-mode transactions on all attached databases and discard\n    /// their connection-local schema caches.  MVCC-enabled attached databases\n    /// are skipped — those are handled by `rollback_attached_mvcc_txs`.\n    pub(crate) fn rollback_attached_wal_txns(&self) {\n        let attached_pagers = self.get_all_attached_pagers_with_index();\n        // Collect WAL-mode db_ids first, then batch the schema removal under\n        // a single write lock to avoid per-iteration lock contention.\n        let wal_pagers: SmallVec<[(usize, Arc<Pager>); 4]> = attached_pagers\n            .into_iter()\n            .filter(|(db_id, _)| self.mv_store_for_db(*db_id).is_none())\n            .collect();\n        if !wal_pagers.is_empty() {\n            let mut schemas = self.database_schemas().write();\n            for (db_id, _) in &wal_pagers {\n                schemas.remove(db_id);\n            }\n        }\n        for (_, attached_pager) in &wal_pagers {\n            attached_pager.rollback_attached();\n        }\n    }\n\n    /// Iterate over all attached MVCC transactions, calling `f(db_id, tx_id)` for each.\n    pub(crate) fn for_each_attached_mv_tx(&self, mut f: impl FnMut(usize, u64)) {\n        let txs = self.attached_mv_txs.read();\n        for (&db_id, &(tx_id, _)) in txs.iter() {\n            f(db_id, tx_id);\n        }\n    }\n\n    /// Get the next attached MVCC transaction.\n    /// Returns an arbitrary entry from `attached_mv_txs`, or `None` if empty.\n    pub(crate) fn next_attached_mv_tx(&self) -> Option<(usize, u64, TransactionMode)> {\n        self.attached_mv_txs\n            .read()\n            .iter()\n            .next()\n            .map(|(&db_id, &(tx_id, mode))| (db_id, tx_id, mode))\n    }\n\n    /// Get the MvStore for a specific database.\n    /// Returns None for databases without MVCC or for bootstrap connections.\n    pub(crate) fn mv_store_for_db(&self, db: usize) -> Option<Arc<MvStore>> {\n        if self.is_mvcc_bootstrap_connection() {\n            return None;\n        }\n        if !crate::is_attached_db(db) {\n            self.db.get_mv_store().as_ref().cloned()\n        } else {\n            let catalog = self.attached_databases.read();\n            catalog\n                .index_to_data\n                .get(&db)\n                .and_then(|(db, _)| db.get_mv_store().as_ref().cloned())\n        }\n    }\n\n    pub(crate) fn set_mvcc_checkpoint_threshold(&self, threshold: i64) -> Result<()> {\n        match self.db.get_mv_store().as_ref() {\n            Some(mv_store) => {\n                mv_store.set_checkpoint_threshold(threshold);\n                self.bump_prepare_context_generation();\n                Ok(())\n            }\n            None => Err(LimboError::InternalError(\"MVCC not enabled\".into())),\n        }\n    }\n\n    pub(crate) fn mvcc_checkpoint_threshold(&self) -> Result<i64> {\n        match self.db.get_mv_store().as_ref() {\n            Some(mv_store) => Ok(mv_store.checkpoint_threshold()),\n            None => Err(LimboError::InternalError(\"MVCC not enabled\".into())),\n        }\n    }\n}\n\npub type Row = vdbe::Row;\n\npub type StepResult = vdbe::StepResult;\n\n#[derive(Default)]\npub struct SymbolTable {\n    pub functions: HashMap<String, Arc<function::ExternalFunc>>,\n    pub vtabs: HashMap<String, Arc<VirtualTable>>,\n    pub vtab_modules: HashMap<String, Arc<crate::ext::VTabImpl>>,\n    pub index_methods: HashMap<String, Arc<dyn IndexMethod>>,\n}\n\nimpl std::fmt::Debug for SymbolTable {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"SymbolTable\")\n            .field(\"functions\", &self.functions)\n            .finish()\n    }\n}\n\nfn is_shared_library(path: &std::path::Path) -> bool {\n    path.extension()\n        .is_some_and(|ext| ext == \"so\" || ext == \"dylib\" || ext == \"dll\")\n}\n\npub fn resolve_ext_path(extpath: &str) -> Result<std::path::PathBuf> {\n    let path = std::path::Path::new(extpath);\n    if !path.exists() {\n        if is_shared_library(path) {\n            return Err(LimboError::ExtensionError(format!(\n                \"Extension file not found: {extpath}\"\n            )));\n        };\n        let maybe = path.with_extension(std::env::consts::DLL_EXTENSION);\n        maybe.exists().then_some(maybe).ok_or_else(|| {\n            LimboError::ExtensionError(format!(\"Extension file not found: {extpath}\"))\n        })\n    } else {\n        Ok(path.to_path_buf())\n    }\n}\n\nimpl SymbolTable {\n    pub fn new() -> Self {\n        Self {\n            functions: HashMap::default(),\n            vtabs: HashMap::default(),\n            vtab_modules: HashMap::default(),\n            index_methods: HashMap::default(),\n        }\n    }\n    pub fn resolve_function(\n        &self,\n        name: &str,\n        _arg_count: usize,\n    ) -> Option<Arc<function::ExternalFunc>> {\n        self.functions.get(name).cloned()\n    }\n\n    pub fn extend(&mut self, other: &SymbolTable) {\n        for (name, func) in &other.functions {\n            self.functions.insert(name.clone(), func.clone());\n        }\n        for (name, vtab) in &other.vtabs {\n            self.vtabs.insert(name.clone(), vtab.clone());\n        }\n        for (name, module) in &other.vtab_modules {\n            self.vtab_modules.insert(name.clone(), module.clone());\n        }\n        for (name, module) in &other.index_methods {\n            self.index_methods.insert(name.clone(), module.clone());\n        }\n    }\n}\n"
  },
  {
    "path": "core/dbpage.rs",
    "content": "use crate::storage::pager::Pager;\nuse crate::sync::Arc;\nuse crate::sync::RwLock;\nuse crate::util::IOExt;\nuse crate::vtab::{InternalVirtualTable, InternalVirtualTableCursor};\nuse crate::{Connection, LimboError, Result, Value};\nuse turso_ext::{\n    ConstraintInfo, ConstraintOp, ConstraintUsage, IndexInfo, OrderByInfo, ResultCode,\n};\n\npub const DBPAGE_TABLE_NAME: &str = \"sqlite_dbpage\";\n\n#[derive(Debug)]\npub struct DbPageTable;\n\nimpl Default for DbPageTable {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl DbPageTable {\n    pub fn new() -> Self {\n        Self\n    }\n}\n\nimpl InternalVirtualTable for DbPageTable {\n    fn name(&self) -> String {\n        DBPAGE_TABLE_NAME.to_string()\n    }\n\n    fn sql(&self) -> String {\n        \"CREATE TABLE sqlite_dbpage(pgno INTEGER PRIMARY KEY, data BLOB, schema HIDDEN)\".to_string()\n    }\n\n    fn open(&self, conn: Arc<Connection>) -> Result<Arc<RwLock<dyn InternalVirtualTableCursor>>> {\n        let pager = conn.get_pager();\n        let cursor = DbPageCursor::new(pager);\n        Ok(Arc::new(RwLock::new(cursor)))\n    }\n\n    /// TODO: sqlite does where_onerow optimization using idx_flag, we should do that eventually.. probably not needed for now.\n    /// Analyzes query constraints and returns cost estimates to help pick the best query plan.\n    ///\n    /// We encode constraint info into `idx_num` as a bitmask, which `filter()` later uses:\n    /// - Bit 0 (0x1): equality on `pgno` - enables single-page lookup\n    /// - Bit 1 (0x2): equality on `schema` - we only support \"main\", so we let Turso handle filtering if the user provides a different schema.\n    fn best_index(\n        &self,\n        constraints: &[ConstraintInfo],\n        _order_by: &[OrderByInfo],\n    ) -> std::result::Result<IndexInfo, ResultCode> {\n        let mut idx_num = 0;\n        let mut estimated_cost = 1_000_000.0;\n\n        let constraint_usages = constraints\n            .iter()\n            .map(|constraint| {\n                let mut usage = ConstraintUsage {\n                    argv_index: None,\n                    omit: false,\n                };\n                if constraint.op == ConstraintOp::Eq {\n                    match constraint.column_index {\n                        // pgno column\n                        0 => {\n                            idx_num |= 1;\n                            usage.argv_index = Some(1);\n                            usage.omit = true;\n                            estimated_cost = 1.0;\n                        }\n                        // schema column\n                        2 => {\n                            idx_num |= 2;\n                            usage.argv_index = Some(if (idx_num & 1) != 0 { 2 } else { 1 });\n                            usage.omit = true;\n                        }\n                        _ => {}\n                    }\n                }\n                usage\n            })\n            .collect();\n\n        let index_info = IndexInfo {\n            idx_num,\n            idx_str: None,\n            order_by_consumed: false,\n            estimated_cost,\n            estimated_rows: if (idx_num & 1) != 0 { 1 } else { 1_000_000 },\n            constraint_usages,\n        };\n\n        Ok(index_info)\n    }\n}\n\npub struct DbPageCursor {\n    pager: Arc<Pager>,\n    pgno: i64,\n    mx_pgno: i64,\n    /// If true, schema constraint was for non-\"main\" schema, so return no rows\n    schema_mismatch: bool,\n}\n\nimpl DbPageCursor {\n    fn new(pager: Arc<Pager>) -> Self {\n        Self {\n            pager,\n            pgno: 1,\n            mx_pgno: 0,\n            schema_mismatch: false,\n        }\n    }\n}\n\nimpl InternalVirtualTableCursor for DbPageCursor {\n    /// iterates based on constraints identified by `best_index()`.\n    ///\n    /// When `idx_num` has bit 0 set, we do a single-page lookup using `args[0]` as the page number.\n    /// If the requested page is out of range (≤0 or beyond db size), the scan returns empty.\n    /// Otherwise, we do a full table scan over all pages starting from page 1.\n    fn filter(&mut self, args: &[Value], _idx_str: Option<String>, idx_num: i32) -> Result<bool> {\n        self.schema_mismatch = false;\n\n        let db_size = self\n            .pager\n            .io\n            .block(|| self.pager.with_header(|header| header.database_size.get()))?;\n\n        self.mx_pgno = db_size as i64;\n\n        let mut arg_idx = 0;\n\n        if (idx_num & 1) != 0 {\n            let pgno = if let Some(Value::Numeric(crate::numeric::Numeric::Integer(val))) =\n                args.get(arg_idx)\n            {\n                *val\n            } else {\n                0\n            };\n            arg_idx += 1;\n\n            if pgno > 0 && pgno <= self.mx_pgno {\n                self.pgno = pgno;\n                self.mx_pgno = pgno;\n            } else {\n                self.mx_pgno = 0;\n            }\n        } else {\n            self.pgno = 1;\n        }\n\n        if (idx_num & 2) != 0 {\n            if let Some(Value::Text(schema)) = args.get(arg_idx) {\n                if schema.as_str() != \"main\" {\n                    self.schema_mismatch = true;\n                    return Ok(false);\n                }\n            }\n        }\n\n        Ok(self.pgno <= self.mx_pgno)\n    }\n\n    fn next(&mut self) -> Result<bool> {\n        if self.schema_mismatch {\n            return Ok(false);\n        }\n        self.pgno += 1;\n        Ok(self.pgno <= self.mx_pgno)\n    }\n\n    fn column(&self, column: usize) -> Result<Value> {\n        match column {\n            0 => Ok(Value::from_i64(self.pgno)),\n            1 => {\n                // check for the pending byte page  - this only needs when db is more than 1 gb.\n                if let Some(pending_page) = self.pager.pending_byte_page_id() {\n                    if self.pgno == pending_page as i64 {\n                        let page_size = self.pager.usable_space()\n                            + self.pager.get_reserved_space().unwrap_or(0) as usize;\n                        return Ok(Value::from_blob(vec![0u8; page_size]));\n                    }\n                }\n\n                let (page_ref, completion) = self.pager.read_page(self.pgno)?;\n                if let Some(c) = completion {\n                    self.pager.io.wait_for_completion(c)?;\n                }\n\n                let page_contents = page_ref.get_contents();\n                let data_slice = page_contents.as_ptr();\n                Ok(Value::from_blob(data_slice.to_vec()))\n            }\n            2 => Ok(Value::from_text(\"main\")), // we don't support multiple databases - todo when we do\n            _ => Ok(Value::Null),\n        }\n    }\n\n    fn rowid(&self) -> i64 {\n        self.pgno\n    }\n}\n\nfn parse_rowid(value: &Value) -> Result<Option<i64>> {\n    match value {\n        Value::Null => Ok(None),\n        Value::Numeric(crate::numeric::Numeric::Integer(i)) => Ok(Some(*i)),\n        _ => Err(LimboError::InvalidArgument(\n            \"sqlite_dbpage rowid must be an integer\".to_string(),\n        )),\n    }\n}\n\nfn ensure_main_schema(value: &Value) -> Result<()> {\n    match value {\n        Value::Null => Ok(()),\n        Value::Text(schema) if schema.as_str() == \"main\" => Ok(()),\n        Value::Text(schema) => Err(LimboError::InvalidArgument(format!(\n            \"sqlite_dbpage only supports main schema (got {})\",\n            schema.as_str()\n        ))),\n        _ => Err(LimboError::InvalidArgument(\n            \"sqlite_dbpage schema must be text or null\".to_string(),\n        )),\n    }\n}\n\npub(crate) fn update_dbpage(pager: &Arc<Pager>, args: &[Value]) -> Result<Option<i64>> {\n    if args.len() < 2 {\n        return Err(LimboError::InternalError(\n            \"sqlite_dbpage update expects at least 2 arguments\".to_string(),\n        ));\n    }\n\n    let old_rowid = parse_rowid(&args[0])?;\n    let new_rowid = parse_rowid(&args[1])?;\n\n    if old_rowid.is_some() && new_rowid.is_none() {\n        return Err(LimboError::InvalidArgument(\n            \"sqlite_dbpage does not support DELETE\".to_string(),\n        ));\n    }\n\n    let columns = if args.len() > 2 { &args[2..] } else { &[] };\n    let column_pgno = columns.first().and_then(|value| value.as_int());\n    let column_data = columns.get(1);\n    let column_schema = columns.get(2);\n\n    let target_pgno = match (new_rowid, old_rowid, column_pgno) {\n        (Some(rowid), _, Some(pgno)) if rowid != pgno => {\n            return Err(LimboError::InvalidArgument(\n                \"sqlite_dbpage pgno does not match rowid\".to_string(),\n            ));\n        }\n        (Some(rowid), _, _) => rowid,\n        (None, Some(rowid), Some(pgno)) if rowid != pgno => {\n            return Err(LimboError::InvalidArgument(\n                \"sqlite_dbpage pgno does not match rowid\".to_string(),\n            ));\n        }\n        (None, Some(rowid), _) => rowid,\n        (None, None, Some(pgno)) => pgno,\n        _ => {\n            return Err(LimboError::InvalidArgument(\n                \"sqlite_dbpage requires a target page number\".to_string(),\n            ))\n        }\n    };\n\n    if target_pgno <= 0 {\n        return Err(LimboError::InvalidArgument(\n            \"sqlite_dbpage pgno must be positive\".to_string(),\n        ));\n    }\n\n    if let Some(schema) = column_schema {\n        ensure_main_schema(schema)?;\n    }\n\n    let data = match column_data {\n        Some(Value::Blob(blob)) => blob.as_slice(),\n        Some(Value::Null) | None => {\n            return Err(LimboError::InvalidArgument(\n                \"sqlite_dbpage requires data for updates\".to_string(),\n            ))\n        }\n        _ => {\n            return Err(LimboError::InvalidArgument(\n                \"sqlite_dbpage data must be a blob\".to_string(),\n            ))\n        }\n    };\n\n    let db_size = pager\n        .io\n        .block(|| pager.with_header(|header| header.database_size.get()))?;\n    if target_pgno as u64 > db_size as u64 {\n        return Err(LimboError::InvalidArgument(format!(\n            \"sqlite_dbpage pgno {target_pgno} is out of range\"\n        )));\n    }\n\n    if let Some(pending_page) = pager.pending_byte_page_id() {\n        if target_pgno == pending_page as i64 {\n            return Err(LimboError::InvalidArgument(\n                \"sqlite_dbpage cannot write the pending byte page\".to_string(),\n            ));\n        }\n    }\n\n    let expected_len = pager.usable_space() + pager.get_reserved_space().unwrap_or(0) as usize;\n    if data.len() != expected_len {\n        return Err(LimboError::InvalidArgument(format!(\n            \"sqlite_dbpage data length must be {expected_len} bytes\"\n        )));\n    }\n\n    let (page_ref, completion) = pager.read_page(target_pgno)?;\n    if let Some(c) = completion {\n        pager.io.wait_for_completion(c)?;\n    }\n\n    pager.add_dirty(&page_ref)?;\n    let contents = page_ref.get_contents();\n    let buffer = contents\n        .buffer\n        .as_ref()\n        .expect(\"sqlite_dbpage page buffer should be loaded\");\n    buffer.as_mut_slice().copy_from_slice(data);\n\n    let is_insert = old_rowid.is_none();\n    Ok(if is_insert { Some(target_pgno) } else { None })\n}\n"
  },
  {
    "path": "core/error.rs",
    "content": "use thiserror::Error;\n\nuse crate::storage::page_cache::CacheError;\n\n#[derive(Debug, Clone, Error, miette::Diagnostic)]\npub enum LimboError {\n    #[error(\"Corrupt database: {0}\")]\n    Corrupt(String),\n    #[error(\"File is not a database\")]\n    NotADB,\n    #[error(\"Internal error: {0}\")]\n    InternalError(String),\n    #[error(transparent)]\n    CacheError(#[from] CacheError),\n    #[error(\"Database is full: {0}\")]\n    DatabaseFull(String),\n    #[error(\"Parse error: {0}\")]\n    ParseError(String),\n    #[error(transparent)]\n    #[diagnostic(transparent)]\n    LexerError(#[from] turso_parser::error::Error),\n    #[error(\"Conversion error: {0}\")]\n    ConversionError(String),\n    #[error(\"Env variable error: {0}\")]\n    EnvVarError(#[from] std::env::VarError),\n    #[error(\"Transaction error: {0}\")]\n    TxError(String),\n    #[error(transparent)]\n    CompletionError(#[from] CompletionError),\n    #[error(\"Locking error: {0}\")]\n    LockingError(String),\n    #[error(\"Parse error: {0}\")]\n    ParseIntError(#[from] std::num::ParseIntError),\n    #[error(\"Parse error: {0}\")]\n    ParseFloatError(#[from] std::num::ParseFloatError),\n    #[error(\"Parse error: {0}\")]\n    InvalidDate(String),\n    #[error(\"Parse error: {0}\")]\n    InvalidTime(String),\n    #[error(\"Modifier parsing error: {0}\")]\n    InvalidModifier(String),\n    #[error(\"Invalid argument supplied: {0}\")]\n    InvalidArgument(String),\n    #[error(\"Invalid formatter supplied: {0}\")]\n    InvalidFormatter(String),\n    #[error(\"Runtime error: {0}\")]\n    Constraint(String),\n    #[error(\"Runtime error: {0}\")]\n    /// We need to specify for ROLLBACK|FAIL resolve types when to roll the tx back\n    /// so instead of matching on the string, we introduce a specific ForeignKeyConstraint error\n    ForeignKeyConstraint(String),\n    #[error(\"Runtime error: {1}\")]\n    Raise(turso_parser::ast::ResolveType, String),\n    #[error(\"RaiseIgnore\")]\n    RaiseIgnore,\n    #[error(\"Extension error: {0}\")]\n    ExtensionError(String),\n    #[error(\"Runtime error: integer overflow\")]\n    IntegerOverflow,\n    #[error(\"Runtime error: string or blob too big\")]\n    TooBig,\n    #[error(\"Runtime error: database table is locked\")]\n    TableLocked,\n    #[error(\"Error: Resource is read-only\")]\n    ReadOnly,\n    #[error(\"Database is busy\")]\n    Busy,\n    #[error(\"interrupt\")]\n    Interrupt,\n    #[error(\"Database snapshot is stale. You must rollback and retry the whole transaction.\")]\n    BusySnapshot,\n    #[error(\"Conflict: {0}\")]\n    Conflict(String),\n    #[error(\"Database schema changed\")]\n    SchemaUpdated,\n    #[error(\"Database schema conflict\")]\n    SchemaConflict,\n    #[error(\n        \"Database is empty, header does not exist - page 1 should've been allocated before this\"\n    )]\n    Page1NotAlloc,\n    #[error(\"Transaction terminated\")]\n    TxTerminated,\n    #[error(\"Write-write conflict\")]\n    WriteWriteConflict,\n    #[error(\"Commit dependency aborted\")]\n    CommitDependencyAborted,\n    #[error(\"No such transaction ID: {0}\")]\n    NoSuchTransactionID(String),\n    #[error(\"Null value\")]\n    NullValue,\n    #[error(\"invalid column type\")]\n    InvalidColumnType,\n    #[error(\"Invalid blob size, expected {0}\")]\n    InvalidBlobSize(usize),\n    #[error(\"Planning error: {0}\")]\n    PlanningError(String),\n    #[error(\"Checkpoint failed: {0}\")]\n    CheckpointFailed(String),\n    #[error(\"Unsupported text encoding: {0}. Only UTF-8 is supported.\")]\n    UnsupportedEncoding(String),\n}\n\n#[cfg(target_family = \"unix\")]\nimpl From<rustix::io::Errno> for LimboError {\n    fn from(value: rustix::io::Errno) -> Self {\n        CompletionError::from(value).into()\n    }\n}\n\n#[cfg(all(target_os = \"linux\", feature = \"io_uring\"))]\nimpl From<&'static str> for LimboError {\n    fn from(value: &'static str) -> Self {\n        CompletionError::UringIOError(value).into()\n    }\n}\n\n#[derive(Debug, Copy, Clone, PartialEq, Error)]\npub enum CompletionError {\n    #[error(\"I/O error ({1}): {0}\")]\n    IOError(std::io::ErrorKind, &'static str),\n    #[cfg(target_family = \"unix\")]\n    #[error(\"I/O error: {0}\")]\n    RustixIOError(#[from] rustix::io::Errno),\n    #[cfg(all(target_os = \"linux\", feature = \"io_uring\"))]\n    #[error(\"I/O error: {0}\")]\n    // TODO: if needed create an enum for IO Uring errors so that we don't have to pass strings around\n    UringIOError(&'static str),\n    #[error(\"Completion was aborted\")]\n    Aborted,\n    #[error(\"Decryption failed for page={page_idx}\")]\n    DecryptionError { page_idx: usize },\n    #[error(\"I/O error: partial write\")]\n    ShortWrite,\n    #[error(\"I/O error: short read on page {page_idx}: expected {expected} bytes, got {actual}\")]\n    ShortRead {\n        page_idx: usize,\n        expected: usize,\n        actual: usize,\n    },\n    #[error(\"I/O error: short read on WAL frame at offset {offset}: expected {expected} bytes, got {actual}\")]\n    ShortReadWalFrame {\n        offset: u64,\n        expected: usize,\n        actual: usize,\n    },\n    #[error(\"Checksum mismatch on page {page_id}: expected {expected}, got {actual}\")]\n    ChecksumMismatch {\n        page_id: usize,\n        expected: u64,\n        actual: u64,\n    },\n    #[error(\"tursodb not compiled with checksum feature\")]\n    ChecksumNotEnabled,\n}\n\n/// Convert a `std::io::Error` into a `LimboError` with an operation label.\npub fn io_error(e: std::io::Error, op: &'static str) -> LimboError {\n    LimboError::CompletionError(CompletionError::IOError(e.kind(), op))\n}\n\n#[cold]\n// makes all branches that return errors marked as unlikely\npub(crate) const fn cold_return<T>(v: T) -> T {\n    v\n}\n\n#[macro_export]\nmacro_rules! bail_parse_error {\n    ($($arg:tt)*) => {\n        return $crate::error::cold_return(Err($crate::error::LimboError::ParseError(format!($($arg)*))))\n    };\n}\n\n#[macro_export]\nmacro_rules! bail_corrupt_error {\n    ($($arg:tt)*) => {\n        return $crate::error::cold_return(Err($crate::error::LimboError::Corrupt(format!($($arg)*))))\n    };\n}\n\n/// Bounds-checked buffer slicing that returns `LimboError::Corrupt` on out-of-bounds.\n///\n/// Accepts any range expression: `buf, pos..`, `buf, start..end`, etc.\n#[macro_export]\nmacro_rules! slice_in_bounds_or_corrupt {\n    ($buf:expr, $range:expr) => {\n        $buf.get($range).ok_or_else(|| {\n            $crate::error::cold_return($crate::error::LimboError::Corrupt(format!(\n                \"range {:?} out of bounds for buffer size {}\",\n                $range,\n                $buf.len()\n            )))\n        })?\n    };\n}\n\n/// Asserts a condition or bails with `LimboError::Corrupt`.\n///\n/// Usage:\n///   `assert_or_bail_corrupt!(condition, \"message {}\", arg)`\n#[macro_export]\nmacro_rules! assert_or_bail_corrupt {\n    ($cond:expr, $($arg:tt)*) => {\n        if !($cond) {\n            $crate::bail_corrupt_error!($($arg)*);\n        }\n    };\n}\n\n#[macro_export]\nmacro_rules! bail_constraint_error {\n    ($($arg:tt)*) => {\n        return $crate::error::cold_return(Err($crate::error::LimboError::Constraint(format!($($arg)*))))\n    };\n}\n\nimpl From<turso_ext::ResultCode> for LimboError {\n    fn from(err: turso_ext::ResultCode) -> Self {\n        cold_return(LimboError::ExtensionError(err.to_string()))\n    }\n}\n\npub const SQLITE_ERROR: usize = 1;\npub const SQLITE_CONSTRAINT: usize = 19;\npub const SQLITE_CONSTRAINT_CHECK: usize = SQLITE_CONSTRAINT | (1 << 8);\npub const SQLITE_CONSTRAINT_PRIMARYKEY: usize = SQLITE_CONSTRAINT | (6 << 8);\n#[allow(dead_code)]\npub const SQLITE_CONSTRAINT_FOREIGNKEY: usize = SQLITE_CONSTRAINT | (3 << 8);\npub const SQLITE_CONSTRAINT_NOTNULL: usize = SQLITE_CONSTRAINT | (5 << 8);\npub const SQLITE_CONSTRAINT_TRIGGER: usize = SQLITE_CONSTRAINT | (7 << 8);\npub const SQLITE_FULL: usize = 13; // we want this in autoincrement - incase if user inserts max allowed int\npub const SQLITE_CONSTRAINT_UNIQUE: usize = 2067;\n"
  },
  {
    "path": "core/ext/dynamic.rs",
    "content": "use crate::{\n    ext::{register_aggregate_function, register_scalar_function, register_vtab_module},\n    Connection, LimboError,\n};\n#[cfg(not(target_family = \"wasm\"))]\nuse libloading::{Library, Symbol};\nuse std::{\n    ffi::{c_char, CString},\n    sync::{Arc, Mutex, OnceLock},\n};\nuse turso_ext::{ExtensionApi, ExtensionApiRef, ExtensionEntryPoint, ResultCode, VfsImpl};\n\n#[cfg(not(target_family = \"wasm\"))]\ntype ExtensionStore = Vec<(Arc<Library>, ExtensionApiRef)>;\n#[cfg(not(target_family = \"wasm\"))]\nstatic EXTENSIONS: OnceLock<Arc<Mutex<ExtensionStore>>> = OnceLock::new();\n#[cfg(not(target_family = \"wasm\"))]\npub fn get_extension_libraries() -> Arc<Mutex<ExtensionStore>> {\n    EXTENSIONS\n        .get_or_init(|| Arc::new(Mutex::new(Vec::new())))\n        .clone()\n}\n\ntype Vfs = (String, Arc<VfsMod>);\nstatic VFS_MODULES: OnceLock<Mutex<Vec<Vfs>>> = OnceLock::new();\n\n#[derive(Clone, Debug)]\npub struct VfsMod {\n    pub ctx: *const VfsImpl,\n}\n\nunsafe impl Send for VfsMod {}\nunsafe impl Sync for VfsMod {}\ncrate::assert::assert_send_sync!(VfsMod);\n\nimpl Connection {\n    #[cfg(not(target_family = \"wasm\"))]\n    pub fn load_extension<P: AsRef<std::ffi::OsStr>>(\n        self: &Arc<Connection>,\n        path: P,\n    ) -> crate::Result<()> {\n        use turso_ext::ExtensionApiRef;\n\n        let api = Box::new(unsafe { self._build_turso_ext() });\n        let lib =\n            unsafe { Library::new(path).map_err(|e| LimboError::ExtensionError(e.to_string()))? };\n        let entry: Symbol<ExtensionEntryPoint> = unsafe {\n            lib.get(b\"register_extension\")\n                .map_err(|e| LimboError::ExtensionError(e.to_string()))?\n        };\n        let api_ptr: *const ExtensionApi = Box::into_raw(api);\n        let api_ref = ExtensionApiRef { api: api_ptr };\n        let result_code = unsafe { entry(api_ptr) };\n        if result_code.is_ok() {\n            let extensions = get_extension_libraries();\n            extensions\n                .lock()\n                .map_err(|_| {\n                    LimboError::ExtensionError(\"Error locking extension libraries\".to_string())\n                })?\n                .push((Arc::new(lib), api_ref));\n            if self.is_db_initialized() {\n                self.reparse_schema_after_extension_load()?;\n            }\n            Ok(())\n        } else {\n            if !api_ptr.is_null() {\n                let _ = unsafe { Box::from_raw(api_ptr.cast_mut()) };\n            }\n            Err(LimboError::ExtensionError(\n                \"Extension registration failed\".to_string(),\n            ))\n        }\n    }\n}\n\n#[allow(clippy::arc_with_non_send_sync)]\npub(crate) unsafe extern \"C\" fn register_vfs(\n    name: *const c_char,\n    vfs: *const VfsImpl,\n) -> ResultCode {\n    if name.is_null() || vfs.is_null() {\n        return ResultCode::Error;\n    }\n    let c_str = unsafe { CString::from_raw(name as *mut _) };\n    let name_str = match c_str.to_str() {\n        Ok(s) => s.to_string(),\n        Err(_) => return ResultCode::Error,\n    };\n    add_vfs_module(name_str, Arc::new(VfsMod { ctx: vfs }));\n    ResultCode::OK\n}\n\n/// Get pointers to all the vfs extensions that need to be built in at compile time.\n/// any other types that are defined in the same extension will not be registered\n/// until the database file is opened and `register_builtins` is called.\n#[cfg(feature = \"fs\")]\n#[allow(clippy::arc_with_non_send_sync)]\npub fn add_builtin_vfs_extensions(\n    api: Option<ExtensionApi>,\n) -> crate::Result<Vec<(String, Arc<VfsMod>)>> {\n    use turso_ext::VfsInterface;\n\n    let mut vfslist: Vec<*const VfsImpl> = Vec::new();\n    let mut api = match api {\n        None => ExtensionApi {\n            ctx: std::ptr::null_mut(),\n            register_scalar_function,\n            register_aggregate_function,\n            register_vtab_module,\n            vfs_interface: VfsInterface {\n                register_vfs,\n                builtin_vfs: vfslist.as_mut_ptr(),\n                builtin_vfs_count: 0,\n            },\n        },\n        Some(mut api) => {\n            api.vfs_interface.builtin_vfs = vfslist.as_mut_ptr();\n            api\n        }\n    };\n    register_static_vfs_modules(&mut api);\n    let mut vfslist = Vec::with_capacity(api.vfs_interface.builtin_vfs_count as usize);\n    let slice = unsafe {\n        std::slice::from_raw_parts_mut(\n            api.vfs_interface.builtin_vfs,\n            api.vfs_interface.builtin_vfs_count as usize,\n        )\n    };\n    for vfs in slice {\n        if vfs.is_null() {\n            continue;\n        }\n        let vfsimpl = unsafe { &**vfs };\n        let name = unsafe {\n            CString::from_raw(vfsimpl.name as *mut _)\n                .to_str()\n                .map_err(|_| {\n                    LimboError::ExtensionError(\"unable to register vfs extension\".to_string())\n                })?\n                .to_string()\n        };\n        vfslist.push((\n            name,\n            Arc::new(VfsMod {\n                ctx: vfsimpl as *const _,\n            }),\n        ));\n    }\n    Ok(vfslist)\n}\n\n#[allow(dead_code)]\n#[cfg(feature = \"fs\")]\nfn register_static_vfs_modules(_api: &mut ExtensionApi) {\n    /* Placeholder for any VFS modules to build in at compile time */\n}\n\npub fn add_vfs_module(name: String, vfs: Arc<VfsMod>) {\n    let mut modules = VFS_MODULES\n        .get_or_init(|| Mutex::new(Vec::new()))\n        .lock()\n        .unwrap();\n    if !modules.iter().any(|v| v.0 == name) {\n        modules.push((name, vfs));\n    }\n}\n\npub fn list_vfs_modules() -> Vec<String> {\n    VFS_MODULES\n        .get_or_init(|| Mutex::new(Vec::new()))\n        .lock()\n        .unwrap()\n        .iter()\n        .map(|v| v.0.clone())\n        .collect()\n}\n\npub fn get_vfs_modules() -> Vec<Vfs> {\n    VFS_MODULES\n        .get_or_init(|| Mutex::new(Vec::new()))\n        .lock()\n        .unwrap()\n        .clone()\n}\n"
  },
  {
    "path": "core/ext/mod.rs",
    "content": "#[cfg(feature = \"fs\")]\nmod dynamic;\nmod vtab_xconnect;\nuse crate::index_method::backing_btree::BackingBtreeIndexMethod;\n#[cfg(all(feature = \"fts\", not(target_family = \"wasm\")))]\nuse crate::index_method::fts::{FtsIndexMethod, FTS_INDEX_METHOD_NAME};\nuse crate::index_method::toy_vector_sparse_ivf::VectorSparseInvertedIndexMethod;\nuse crate::index_method::{\n    BACKING_BTREE_INDEX_METHOD_NAME, TOY_VECTOR_SPARSE_IVF_INDEX_METHOD_NAME,\n};\nuse crate::schema::{Schema, Table};\nuse crate::sync::atomic::{AtomicU64, Ordering};\nuse crate::sync::Mutex;\n#[cfg(all(target_os = \"linux\", feature = \"io_uring\", not(miri)))]\nuse crate::UringIO;\n#[cfg(all(target_os = \"windows\", feature = \"experimental_win_iocp\", not(miri)))]\nuse crate::WindowsIOCP;\n\nuse crate::{function::ExternalFunc, Connection, Database};\nuse crate::{vtab::VirtualTable, SymbolTable};\n#[cfg(feature = \"fs\")]\nuse crate::{LimboError, IO};\n#[cfg(feature = \"fs\")]\npub use dynamic::{add_builtin_vfs_extensions, add_vfs_module, list_vfs_modules, VfsMod};\nuse std::{\n    ffi::{c_char, c_void, CStr, CString},\n    sync::Arc,\n};\nuse turso_ext::{\n    ExtensionApi, InitAggFunction, ResultCode, ScalarFunction, VTabKind, VTabModuleImpl,\n};\npub use turso_ext::{FinalizeFunction, StepFunction, Value as ExtValue, ValueType as ExtValueType};\npub use vtab_xconnect::{execute, prepare_stmt};\n\n/// The context passed to extensions to register with Core\n/// along with the function pointers\n#[repr(C)]\npub struct ExtensionCtx {\n    syms: *mut SymbolTable,\n    schema: *mut c_void,\n    /// We must bump the prepare context generation so prepared statements\n    /// know they need to be reprepared after extension registration.\n    prepare_context_generation: *const AtomicU64,\n}\n\npub(crate) unsafe extern \"C\" fn register_vtab_module(\n    ctx: *mut c_void,\n    name: *const c_char,\n    module: VTabModuleImpl,\n    kind: VTabKind,\n) -> ResultCode {\n    if name.is_null() || ctx.is_null() {\n        return ResultCode::Error;\n    }\n\n    let c_str = unsafe { CString::from_raw(name as *mut c_char) };\n    let name_str = match c_str.to_str() {\n        Ok(s) => s.to_string(),\n        Err(_) => return ResultCode::Error,\n    };\n\n    let ext_ctx = unsafe { &mut *(ctx as *mut ExtensionCtx) };\n    let module = Arc::new(module);\n    let vmodule = VTabImpl {\n        module_kind: kind,\n        implementation: module,\n    };\n\n    unsafe {\n        let syms = &mut *ext_ctx.syms;\n        syms.vtab_modules.insert(name_str.clone(), vmodule.into());\n        if !ext_ctx.prepare_context_generation.is_null() {\n            (*ext_ctx.prepare_context_generation).fetch_add(1, Ordering::Release);\n        }\n\n        if kind == VTabKind::TableValuedFunction {\n            if let Ok(vtab) = VirtualTable::function(&name_str, syms) {\n                let table = Arc::new(Table::Virtual(vtab));\n                let mutex = &*(ext_ctx.schema as *mut Mutex<Arc<Schema>>);\n                let mut guard = mutex.lock();\n                let schema = Arc::make_mut(&mut *guard);\n                schema.tables.insert(name_str, table);\n            } else {\n                return ResultCode::Error;\n            }\n        }\n    }\n    ResultCode::OK\n}\n\n#[derive(Clone)]\npub struct VTabImpl {\n    pub module_kind: VTabKind,\n    pub implementation: Arc<VTabModuleImpl>,\n}\n\npub(crate) unsafe extern \"C\" fn register_scalar_function(\n    ctx: *mut c_void,\n    name: *const c_char,\n    func: ScalarFunction,\n) -> ResultCode {\n    let c_str = unsafe { CStr::from_ptr(name) };\n    let name_str = match c_str.to_str() {\n        Ok(s) => s.to_string(),\n        Err(_) => return ResultCode::InvalidArgs,\n    };\n    if ctx.is_null() {\n        return ResultCode::Error;\n    }\n    let ext_ctx = unsafe { &mut *(ctx as *mut ExtensionCtx) };\n    unsafe {\n        (*ext_ctx.syms).functions.insert(\n            name_str.clone(),\n            Arc::new(ExternalFunc::new_scalar(name_str, func)),\n        );\n        if !ext_ctx.prepare_context_generation.is_null() {\n            (*ext_ctx.prepare_context_generation).fetch_add(1, Ordering::Release);\n        }\n    }\n    ResultCode::OK\n}\n\npub(crate) unsafe extern \"C\" fn register_aggregate_function(\n    ctx: *mut c_void,\n    name: *const c_char,\n    args: i32,\n    init_func: InitAggFunction,\n    step_func: StepFunction,\n    finalize_func: FinalizeFunction,\n) -> ResultCode {\n    let c_str = unsafe { CStr::from_ptr(name) };\n    let name_str = match c_str.to_str() {\n        Ok(s) => s.to_string(),\n        Err(_) => return ResultCode::InvalidArgs,\n    };\n    if ctx.is_null() {\n        return ResultCode::Error;\n    }\n    let ext_ctx = unsafe { &mut *(ctx as *mut ExtensionCtx) };\n    unsafe {\n        (*ext_ctx.syms).functions.insert(\n            name_str.clone(),\n            Arc::new(ExternalFunc::new_aggregate(\n                name_str,\n                args,\n                (init_func, step_func, finalize_func),\n            )),\n        );\n        if !ext_ctx.prepare_context_generation.is_null() {\n            (*ext_ctx.prepare_context_generation).fetch_add(1, Ordering::Release);\n        }\n    }\n    ResultCode::OK\n}\n\nimpl Database {\n    #[cfg(feature = \"fs\")]\n    #[allow(clippy::arc_with_non_send_sync, dead_code)]\n    pub fn open_with_vfs(\n        &self,\n        path: &str,\n        vfs: &str,\n    ) -> crate::Result<(Arc<dyn IO>, Arc<Database>)> {\n        use crate::{MemoryIO, SyscallIO};\n        use dynamic::get_vfs_modules;\n\n        let io: Arc<dyn IO> = match vfs {\n            \"memory\" => Arc::new(MemoryIO::new()),\n            \"syscall\" => Arc::new(SyscallIO::new()?),\n            #[cfg(all(target_os = \"linux\", feature = \"io_uring\", not(miri)))]\n            \"io_uring\" => Arc::new(UringIO::new()?),\n            #[cfg(all(target_os = \"windows\", feature = \"experimental_win_iocp\", not(miri)))]\n            \"experimental_win_iocp\" => Arc::new(WindowsIOCP::new()?),\n            other => match get_vfs_modules().iter().find(|v| v.0 == vfs) {\n                Some((_, vfs)) => vfs.clone(),\n                None => {\n                    return Err(LimboError::InvalidArgument(format!(\"no such VFS: {other}\")));\n                }\n            },\n        };\n        let db = Self::open_file(io.clone(), path)?;\n        Ok((io, db))\n    }\n\n    /// Register any built-in extensions that can be stored on the Database so we do not have\n    /// to register these once-per-connection, and the connection can just extend its symbol table\n    pub fn register_global_builtin_extensions(&self) -> Result<(), String> {\n        {\n            let mut syms = self.builtin_syms.write();\n            syms.index_methods.insert(\n                TOY_VECTOR_SPARSE_IVF_INDEX_METHOD_NAME.to_string(),\n                Arc::new(VectorSparseInvertedIndexMethod),\n            );\n            syms.index_methods.insert(\n                BACKING_BTREE_INDEX_METHOD_NAME.to_string(),\n                Arc::new(BackingBtreeIndexMethod),\n            );\n            #[cfg(all(feature = \"fts\", not(target_family = \"wasm\")))]\n            syms.index_methods\n                .insert(FTS_INDEX_METHOD_NAME.to_string(), Arc::new(FtsIndexMethod));\n        }\n        let syms = self.builtin_syms.data_ptr();\n        // Pass the mutex pointer and the appropriate handler\n        let schema_mutex_ptr =\n            &*self.schema as *const Mutex<Arc<Schema>> as *mut Mutex<Arc<Schema>>;\n        let ctx = Box::into_raw(Box::new(ExtensionCtx {\n            syms,\n            schema: schema_mutex_ptr as *mut c_void,\n            prepare_context_generation: std::ptr::null(),\n        }));\n        #[allow(unused)]\n        let mut ext_api = ExtensionApi {\n            ctx: ctx as *mut c_void,\n            register_scalar_function,\n            register_aggregate_function,\n            register_vtab_module,\n            #[cfg(feature = \"fs\")]\n            vfs_interface: turso_ext::VfsInterface {\n                register_vfs: dynamic::register_vfs,\n                builtin_vfs: std::ptr::null_mut(),\n                builtin_vfs_count: 0,\n            },\n        };\n\n        #[cfg(feature = \"uuid\")]\n        crate::uuid::register_extension(&mut ext_api);\n        #[cfg(feature = \"series\")]\n        crate::series::register_extension(&mut ext_api);\n        #[cfg(feature = \"time\")]\n        crate::time::register_extension(&mut ext_api);\n        crate::regexp::register_extension(&mut ext_api);\n        #[cfg(feature = \"fs\")]\n        {\n            let vfslist = add_builtin_vfs_extensions(Some(ext_api)).map_err(|e| e.to_string())?;\n            for (name, vfs) in vfslist {\n                add_vfs_module(name, vfs);\n            }\n        }\n        let _ = unsafe { Box::from_raw(ctx) };\n        Ok(())\n    }\n}\n\nimpl Connection {\n    /// Build the connection's extension api context for manually registering an extension.\n    /// you probably want to use `Connection::load_extension(path)`.\n    ///\n    /// # Safety\n    /// Only to be used when registering a staticly linked extension manually.\n    /// You should only ever call this method on your applications startup,\n    /// The caller is responsible for calling `_free_extension_ctx` after registering the\n    /// extension.\n    ///\n    /// usage:\n    /// ```ignore\n    /// let ext_api = conn._build_turso_ext();\n    /// unsafe {\n    ///     my_extension::register_extension(&mut ext_api);\n    ///     conn._free_extension_ctx(ext_api);\n    /// }\n    ///```\n    pub unsafe fn _build_turso_ext(&self) -> ExtensionApi {\n        let schema_mutex_ptr =\n            &*self.db.schema as *const Mutex<Arc<Schema>> as *mut Mutex<Arc<Schema>>;\n        let ctx = ExtensionCtx {\n            syms: self.syms.data_ptr(),\n            schema: schema_mutex_ptr as *mut c_void,\n            prepare_context_generation: &self.prepare_context_generation as *const _,\n        };\n        let ctx = Box::into_raw(Box::new(ctx)) as *mut c_void;\n        ExtensionApi {\n            ctx,\n            register_scalar_function,\n            register_aggregate_function,\n            register_vtab_module,\n            #[cfg(feature = \"fs\")]\n            vfs_interface: turso_ext::VfsInterface {\n                register_vfs: dynamic::register_vfs,\n                builtin_vfs: std::ptr::null_mut(),\n                builtin_vfs_count: 0,\n            },\n        }\n    }\n\n    /// Free the connection's extension libary context after registering an extension manually.\n    /// # Safety\n    /// Only to be used if you have previously called Connection::build_turso_ext\n    pub unsafe fn _free_extension_ctx(&self, api: ExtensionApi) {\n        if api.ctx.is_null() {\n            return;\n        }\n        let _ = unsafe { Box::from_raw(api.ctx as *mut ExtensionCtx) };\n    }\n}\n"
  },
  {
    "path": "core/ext/vtab_xconnect.rs",
    "content": "use crate::{types::Value, Connection, LimboError, Statement};\nuse std::{\n    boxed::Box,\n    ffi::{c_char, c_void, CStr, CString},\n    num::NonZeroUsize,\n    ptr,\n    sync::Weak,\n};\nuse turso_ext::{Conn as ExtConn, ResultCode, Stmt, Value as ExtValue};\n\n/// Wrapper around core Connection::execute with optional arguments to bind\n/// to the statment This function takes ownership of the optional turso_ext::Value array if provided\npub unsafe extern \"C\" fn execute(\n    ctx: *mut ExtConn,\n    sql: *const c_char,\n    args: *mut ExtValue,\n    arg_count: i32,\n    last_insert_rowid: *mut i64,\n) -> ResultCode {\n    let c_str = unsafe { CStr::from_ptr(sql as *mut c_char) };\n    let sql_str = match c_str.to_str() {\n        Ok(s) => s.to_string(),\n        Err(_) => {\n            tracing::error!(\"query: failed to convert sql to string\");\n            return ResultCode::Error;\n        }\n    };\n    let Ok(extcon) = ExtConn::from_ptr(ctx) else {\n        tracing::error!(\"query: null connection\");\n        return ResultCode::Error;\n    };\n    if extcon._ctx.is_null() {\n        tracing::error!(\"execute: connection ctx is null\");\n        return ResultCode::Error;\n    }\n    let weak_box = extcon._ctx as *const Weak<Connection>;\n    let weak = unsafe { &*weak_box };\n    if let Some(conn) = weak.upgrade() {\n        match conn.query(&sql_str) {\n            Ok(Some(mut stmt)) => {\n                if arg_count > 0 {\n                    let args_slice = &mut std::slice::from_raw_parts_mut(args, arg_count as usize);\n                    for (i, val) in args_slice.iter_mut().enumerate() {\n                        stmt.bind_at(\n                            NonZeroUsize::new(i + 1).unwrap(),\n                            Value::from_ffi(std::mem::take(val)).unwrap_or(Value::Null),\n                        );\n                    }\n                }\n                let result = stmt.run_with_row_callback(|_| {\n                    Err(crate::LimboError::InternalError(String::from(\n                        \"execute used for query returning a row\",\n                    )))\n                });\n                let rc = match result {\n                    Ok(_) => {\n                        *last_insert_rowid = conn.last_insert_rowid();\n                        ResultCode::OK\n                    }\n                    Err(err) => match err {\n                        crate::LimboError::Busy => ResultCode::Busy,\n                        crate::LimboError::Interrupt => ResultCode::Interrupt,\n                        _ => {\n                            tracing::error!(\"execute: failed to execute query: {:?}\", err);\n                            ResultCode::Error\n                        }\n                    },\n                };\n                return rc;\n            }\n            Ok(None) => tracing::error!(\"query: no statement returned\"),\n            Err(e) => tracing::error!(\"query: failed to execute query: {:?}\", e),\n        };\n    }\n    ResultCode::Error\n}\n\n/// Wraps core Connection::prepare with a custom Stmt object with the necessary function pointers.\n/// This object is boxed/leaked and the caller is responsible for freeing the memory.\npub unsafe extern \"C\" fn prepare_stmt(ctx: *mut ExtConn, sql: *const c_char) -> *mut Stmt {\n    let c_str = unsafe { CStr::from_ptr(sql as *mut c_char) };\n    let sql_str = match c_str.to_str() {\n        Ok(s) => s.to_string(),\n        Err(_) => {\n            tracing::error!(\"prepare_stmt: failed to convert sql to string\");\n            return ptr::null_mut();\n        }\n    };\n    let Ok(extcon) = ExtConn::from_ptr(ctx) else {\n        tracing::error!(\"prepare_stmt: null connection\");\n        return ptr::null_mut();\n    };\n    if extcon._ctx.is_null() {\n        tracing::error!(\"prepare_stmt: null connection ctx\");\n        return ptr::null_mut();\n    }\n    let weak_box = extcon._ctx as *const Weak<Connection>;\n    let weak = unsafe { &*weak_box };\n    if let Some(conn) = weak.upgrade() {\n        match conn.prepare(&sql_str) {\n            Ok(stmt) => {\n                let raw_stmt = Box::into_raw(Box::new(stmt)) as *mut c_void;\n                Box::into_raw(Box::new(Stmt::new(\n                    extcon._ctx,\n                    raw_stmt,\n                    stmt_bind_args_fn,\n                    stmt_step,\n                    stmt_get_row,\n                    stmt_get_column_names,\n                    stmt_free_current_row,\n                    stmt_close,\n                )))\n            }\n            Err(e) => {\n                tracing::error!(\"prepare_stmt: failed to prepare statement: {:?}\", e);\n                ptr::null_mut()\n            }\n        }\n    } else {\n        tracing::error!(\"failed to upgrade stored connection on vtable module\");\n        ptr::null_mut()\n    }\n}\n\n/// This function expects 1 based indexing. Wraps core statement bind_at functionality\n/// this function does not take ownership of the provided arg value\npub unsafe extern \"C\" fn stmt_bind_args_fn(ctx: *mut Stmt, idx: i32, arg: ExtValue) -> ResultCode {\n    let Ok(stmt) = Stmt::from_ptr(ctx) else {\n        tracing::error!(\"prepare_stmt: null stmt pointer\");\n        return ResultCode::Error;\n    };\n    let stmt_ctx: &mut Statement = unsafe { &mut *(stmt._ctx as *mut Statement) };\n    // from_ffi takes ownership\n    let Ok(owned_val) = Value::from_ffi(arg) else {\n        tracing::error!(\"stmt_bind_args_fn: failed to convert arg to Value\");\n        return ResultCode::Error;\n    };\n    let Some(idx) = NonZeroUsize::new(idx as usize) else {\n        tracing::error!(\"stmt_bind_args_fn: invalid index\");\n        return ResultCode::Error;\n    };\n    stmt_ctx.bind_at(idx, owned_val);\n    ResultCode::OK\n}\n\n/// Wraps the functionality of the core Statement::step function,\n/// preferring to handle the IO step result internally to prevent having to expose\n/// run_once. Returns the equivalent ResultCode which then maps to an external StepResult.\n/// This function is blocking\npub unsafe extern \"C\" fn stmt_step(stmt: *mut Stmt) -> ResultCode {\n    let Ok(stmt) = Stmt::from_ptr(stmt) else {\n        tracing::error!(\"stmt_step: failed to convert stmt to Stmt\");\n        return ResultCode::Error;\n    };\n    if stmt._conn.is_null() || stmt._ctx.is_null() {\n        tracing::error!(\"stmt_step: null connection or context\");\n        return ResultCode::Error;\n    }\n    let stmt_ctx: &mut Statement = unsafe { &mut *(stmt._ctx as *mut Statement) };\n    let res = stmt_ctx.run_one_step_blocking(|| Ok(()), || Ok(()));\n    match res {\n        Ok(Some(_)) => ResultCode::Row,\n        Ok(None) => {\n            // Done\n            ResultCode::EOF\n        }\n        Err(LimboError::Interrupt) => ResultCode::Interrupt,\n        Err(LimboError::Busy) => ResultCode::Busy,\n        Err(_) => ResultCode::Error,\n    }\n}\n\n/// Instead of returning a pointer to the row, sets the Stmt's 'cursor'/current_row\n/// to the next result row, and then the caller can access the resulting value on the Stmt.\npub unsafe extern \"C\" fn stmt_get_row(ctx: *mut Stmt) {\n    let Ok(stmt) = Stmt::from_ptr(ctx) else {\n        tracing::error!(\"stmt_get_row: failed to convert stmt to Stmt\");\n        return;\n    };\n    if !stmt.current_row.is_null() {\n        stmt.free_current_row();\n    }\n    let stmt_ctx: &mut Statement = unsafe { &mut *(stmt._ctx as *mut Statement) };\n    if let Some(row) = stmt_ctx.row() {\n        let values = row.get_values();\n        let mut owned_values = Vec::with_capacity(row.len());\n        for value in values {\n            owned_values.push(Value::to_ffi(value));\n        }\n        stmt.current_row = Box::into_raw(owned_values.into_boxed_slice()) as *mut ExtValue;\n        stmt.current_row_len = row.len() as i32;\n    } else {\n        stmt.current_row_len = 0;\n    }\n}\n\n/// Free the memory of the current row/cursor of the Stmt object.\npub unsafe extern \"C\" fn stmt_free_current_row(ctx: *mut Stmt) {\n    let Ok(stmt) = Stmt::from_ptr(ctx) else {\n        return;\n    };\n    if !stmt.current_row.is_null() {\n        let values: &mut [ExtValue] =\n            std::slice::from_raw_parts_mut(stmt.current_row, stmt.current_row_len as usize);\n        for value in values.iter_mut() {\n            let owned_value = std::mem::take(value);\n            owned_value.__free_internal_type();\n        }\n        let _ = Box::from_raw(stmt.current_row);\n    }\n}\n\n/// Provides an easier API to get all the result column names associated with\n/// the prepared Statement. The caller is responsible for freeing the memory\npub unsafe extern \"C\" fn stmt_get_column_names(\n    ctx: *mut Stmt,\n    count: *mut i32,\n) -> *mut *mut c_char {\n    if !count.is_null() {\n        *count = 0;\n    }\n    let Ok(stmt) = Stmt::from_ptr(ctx) else {\n        tracing::error!(\"stmt_get_column_names: null Stmt pointer\");\n        return ptr::null_mut();\n    };\n    let stmt_ctx: &mut Statement = unsafe { &mut *(stmt._ctx as *mut Statement) };\n    let num_cols = stmt_ctx.num_columns();\n    if num_cols == 0 {\n        tracing::info!(\"stmt_get_column_names: no columns\");\n        return ptr::null_mut();\n    }\n    let mut names: Vec<*mut c_char> = Vec::with_capacity(num_cols);\n    // collect all the column names and convert them to C strings to send back\n    for i in 0..num_cols {\n        let name = stmt_ctx.get_column_name(i);\n        match CString::new(name.as_bytes()) {\n            Ok(cstr) => names.push(cstr.into_raw()),\n            Err(_) => {\n                // fall-back: free what we allocated so far\n                for p in names {\n                    let _ = CString::from_raw(p);\n                }\n                return std::ptr::null_mut();\n            }\n        }\n    }\n\n    if !count.is_null() {\n        *count = names.len() as i32;\n    }\n    Box::into_raw(names.into_boxed_slice()) as *mut *mut c_char\n}\n\n/// Ffi/extension wrapper around core Statement::reset and\n/// cleans up resources associated with the Statement\npub unsafe extern \"C\" fn stmt_close(stmt: *mut Stmt) {\n    if stmt.is_null() {\n        return;\n    }\n    let mut wrapper = Box::from_raw(stmt);\n    if wrapper._ctx.is_null() {\n        // already closed\n        return;\n    }\n    // clean up the current row if it exists\n    if !wrapper.current_row.is_null() {\n        wrapper.free_current_row();\n    }\n    // free the managed internal context\n    let mut internal = Box::<Statement>::from_raw(wrapper._ctx.cast());\n    internal.reset_best_effort();\n}\n"
  },
  {
    "path": "core/fast_lock.rs",
    "content": "use crate::sync::atomic::{AtomicBool, Ordering};\nuse crate::thread::spin_loop;\nuse std::{\n    cell::UnsafeCell,\n    ops::{Deref, DerefMut},\n};\n\n#[derive(Debug)]\npub struct SpinLock<T: ?Sized> {\n    locked: AtomicBool,\n    value: UnsafeCell<T>,\n}\n\npub struct SpinLockGuard<'a, T> {\n    lock: &'a SpinLock<T>,\n}\n\nimpl<T> Drop for SpinLockGuard<'_, T> {\n    fn drop(&mut self) {\n        self.lock.locked.store(false, Ordering::Release);\n    }\n}\n\nimpl<T> Deref for SpinLockGuard<'_, T> {\n    type Target = T;\n\n    fn deref(&self) -> &Self::Target {\n        unsafe { &*self.lock.value.get() }\n    }\n}\n\nimpl<T> DerefMut for SpinLockGuard<'_, T> {\n    fn deref_mut(&mut self) -> &mut T {\n        unsafe { &mut *self.lock.value.get() }\n    }\n}\n\nunsafe impl<T: ?Sized + Send> Send for SpinLock<T> {}\nunsafe impl<T: ?Sized + Send> Sync for SpinLock<T> {}\n\nimpl<T> SpinLock<T> {\n    pub fn new(value: T) -> Self {\n        Self {\n            locked: AtomicBool::new(false),\n            value: UnsafeCell::new(value),\n        }\n    }\n\n    pub fn lock(&self) -> SpinLockGuard<'_, T> {\n        while self.locked.swap(true, Ordering::Acquire) {\n            spin_loop();\n        }\n        SpinLockGuard { lock: self }\n    }\n\n    pub fn into_inner(self) -> UnsafeCell<T> {\n        self.value\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::sync::Arc;\n\n    use super::SpinLock;\n\n    #[test]\n    fn test_fast_lock_multiple_thread_sum() {\n        let lock = Arc::new(SpinLock::new(0));\n        let mut threads = vec![];\n        const NTHREADS: usize = 1000;\n        for _ in 0..NTHREADS {\n            let lock = lock.clone();\n            threads.push(std::thread::spawn(move || {\n                let mut guard = lock.lock();\n                *guard += 1;\n            }));\n        }\n        for thread in threads {\n            thread.join().unwrap();\n        }\n        assert_eq!(*lock.lock(), NTHREADS);\n    }\n}\n\n#[cfg(all(shuttle, test))]\nmod shuttle_tests {\n    use super::*;\n    use crate::sync::*;\n    use crate::thread;\n\n    /// Test basic mutual exclusion with counter increment\n    #[test]\n    fn shuttle_spinlock_counter() {\n        shuttle::check_random(\n            || {\n                let lock = Arc::new(SpinLock::new(0));\n                let mut threads = vec![];\n                const NTHREADS: usize = 3;\n                for _ in 0..NTHREADS {\n                    let lock = lock.clone();\n                    threads.push(thread::spawn(move || {\n                        let mut guard = lock.lock();\n                        *guard += 1;\n                    }));\n                }\n                for thread in threads {\n                    thread.join().unwrap();\n                }\n                assert_eq!(*lock.lock(), NTHREADS);\n            },\n            1000,\n        );\n    }\n\n    /// Test that lock provides mutual exclusion - no two threads hold lock simultaneously\n    #[test]\n    fn shuttle_spinlock_mutual_exclusion() {\n        shuttle::check_random(\n            || {\n                let lock = Arc::new(SpinLock::new(()));\n                let in_critical_section = Arc::new(AtomicBool::new(false));\n\n                let mut threads = vec![];\n                for _ in 0..3 {\n                    let lock = lock.clone();\n                    let in_cs = in_critical_section.clone();\n                    threads.push(thread::spawn(move || {\n                        let _guard = lock.lock();\n                        // If another thread is in critical section, this is a bug\n                        assert!(\n                            !in_cs.swap(true, Ordering::SeqCst),\n                            \"Two threads in critical section!\"\n                        );\n                        // Simulate some work\n                        thread::yield_now();\n                        in_cs.store(false, Ordering::SeqCst);\n                    }));\n                }\n                for thread in threads {\n                    thread.join().unwrap();\n                }\n            },\n            1000,\n        );\n    }\n\n    /// Test multiple lock/unlock cycles per thread\n    #[test]\n    fn shuttle_spinlock_multiple_cycles() {\n        shuttle::check_random(\n            || {\n                let lock = Arc::new(SpinLock::new(0i32));\n\n                let mut threads = vec![];\n                for _ in 0..2 {\n                    let lock = lock.clone();\n                    threads.push(thread::spawn(move || {\n                        for _ in 0..3 {\n                            let mut guard = lock.lock();\n                            *guard += 1;\n                            // Guard dropped here, releasing lock\n                        }\n                    }));\n                }\n                for thread in threads {\n                    thread.join().unwrap();\n                }\n                // 2 threads * 3 iterations = 6\n                assert_eq!(*lock.lock(), 6);\n            },\n            1000,\n        );\n    }\n\n    /// Test that guard properly releases lock on drop\n    #[test]\n    fn shuttle_spinlock_guard_drop() {\n        shuttle::check_random(\n            || {\n                let lock = Arc::new(SpinLock::new(0));\n\n                let lock1 = lock.clone();\n                let t1 = thread::spawn(move || {\n                    {\n                        let mut guard = lock1.lock();\n                        *guard = 1;\n                        // guard dropped here\n                    }\n                    // After drop, another thread should be able to acquire\n                });\n\n                let lock2 = lock.clone();\n                let t2 = thread::spawn(move || {\n                    let mut guard = lock2.lock();\n                    *guard = 2;\n                });\n\n                t1.join().unwrap();\n                t2.join().unwrap();\n\n                // Value should be 1 or 2 depending on order, but lock should be acquirable\n                let val = *lock.lock();\n                assert!(val == 1 || val == 2);\n            },\n            1000,\n        );\n    }\n\n    /// Test read-modify-write pattern under contention\n    #[test]\n    fn shuttle_spinlock_read_modify_write() {\n        shuttle::check_random(\n            || {\n                let lock = Arc::new(SpinLock::new(vec![0i32; 3]));\n\n                let mut threads = vec![];\n                for i in 0..3 {\n                    let lock = lock.clone();\n                    threads.push(thread::spawn(move || {\n                        let mut guard = lock.lock();\n                        // Read current value, modify, write back\n                        guard[i] += 1;\n                    }));\n                }\n                for thread in threads {\n                    thread.join().unwrap();\n                }\n                let guard = lock.lock();\n                assert_eq!(*guard, vec![1, 1, 1]);\n            },\n            1000,\n        );\n    }\n\n    /// Test lock acquisition order doesn't cause starvation (probabilistic)\n    #[test]\n    fn shuttle_spinlock_no_starvation() {\n        shuttle::check_random(\n            || {\n                let lock = Arc::new(SpinLock::new(Vec::<usize>::new()));\n\n                let mut threads = vec![];\n                for id in 0..3 {\n                    let lock = lock.clone();\n                    threads.push(thread::spawn(move || {\n                        let mut guard = lock.lock();\n                        guard.push(id);\n                    }));\n                }\n                for thread in threads {\n                    thread.join().unwrap();\n                }\n                // All threads should have acquired the lock exactly once\n                let guard = lock.lock();\n                assert_eq!(guard.len(), 3);\n                let mut sorted = guard.clone();\n                sorted.sort();\n                assert_eq!(sorted, vec![0, 1, 2]);\n            },\n            1000,\n        );\n    }\n\n    /// Test nested-style access pattern (reacquire after release)\n    #[test]\n    fn shuttle_spinlock_reacquire() {\n        shuttle::check_random(\n            || {\n                let lock = Arc::new(SpinLock::new(0));\n\n                let lock1 = lock.clone();\n                let t1 = thread::spawn(move || {\n                    {\n                        let mut guard = lock1.lock();\n                        *guard += 1;\n                    }\n                    // Release and reacquire\n                    {\n                        let mut guard = lock1.lock();\n                        *guard += 1;\n                    }\n                });\n\n                let lock2 = lock.clone();\n                let t2 = thread::spawn(move || {\n                    let mut guard = lock2.lock();\n                    *guard += 10;\n                });\n\n                t1.join().unwrap();\n                t2.join().unwrap();\n\n                // Should be 12 (1 + 1 + 10)\n                assert_eq!(*lock.lock(), 12);\n            },\n            1000,\n        );\n    }\n}\n"
  },
  {
    "path": "core/function.rs",
    "content": "use crate::sync::Arc;\nuse std::fmt;\nuse std::fmt::{Debug, Display};\nuse strum::IntoEnumIterator;\nuse turso_ext::{FinalizeFunction, InitAggFunction, ScalarFunction, StepFunction};\n\nuse crate::LimboError;\n\npub trait Deterministic: std::fmt::Display {\n    fn is_deterministic(&self) -> bool;\n}\n\npub struct ExternalFunc {\n    pub name: String,\n    pub func: ExtFunc,\n}\n\nimpl Deterministic for ExternalFunc {\n    fn is_deterministic(&self) -> bool {\n        // external functions can be whatever so let's just default to false\n        false\n    }\n}\n\n#[derive(Debug, Clone)]\npub enum ExtFunc {\n    Scalar(ScalarFunction),\n    Aggregate {\n        argc: usize,\n        init: InitAggFunction,\n        step: StepFunction,\n        finalize: FinalizeFunction,\n    },\n}\n\nimpl ExtFunc {\n    pub fn agg_args(&self) -> Result<usize, ()> {\n        if let ExtFunc::Aggregate { argc, .. } = self {\n            return Ok(*argc);\n        }\n        Err(())\n    }\n}\n\nimpl ExternalFunc {\n    pub fn new_scalar(name: String, func: ScalarFunction) -> Self {\n        Self {\n            name,\n            func: ExtFunc::Scalar(func),\n        }\n    }\n\n    pub fn new_aggregate(\n        name: String,\n        argc: i32,\n        func: (InitAggFunction, StepFunction, FinalizeFunction),\n    ) -> Self {\n        Self {\n            name,\n            func: ExtFunc::Aggregate {\n                argc: argc as usize,\n                init: func.0,\n                step: func.1,\n                finalize: func.2,\n            },\n        }\n    }\n}\n\nimpl Debug for ExternalFunc {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"{}\", self.name)\n    }\n}\n\nimpl Display for ExternalFunc {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"{}\", self.name)\n    }\n}\n\n#[cfg(feature = \"json\")]\n#[derive(Debug, Clone, PartialEq, strum::EnumIter)]\npub enum JsonFunc {\n    Json,\n    Jsonb,\n    JsonArray,\n    JsonbArray,\n    JsonArrayLength,\n    JsonArrowExtract,\n    JsonArrowShiftExtract,\n    JsonExtract,\n    JsonbExtract,\n    JsonObject,\n    JsonbObject,\n    JsonType,\n    JsonErrorPosition,\n    JsonValid,\n    JsonPatch,\n    JsonbPatch,\n    JsonRemove,\n    JsonbRemove,\n    JsonReplace,\n    JsonbReplace,\n    JsonInsert,\n    JsonbInsert,\n    JsonPretty,\n    JsonSet,\n    JsonbSet,\n    JsonQuote,\n}\n\n#[cfg(feature = \"json\")]\nimpl Deterministic for JsonFunc {\n    fn is_deterministic(&self) -> bool {\n        true\n    }\n}\n\n#[cfg(feature = \"json\")]\nimpl Display for JsonFunc {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(\n            f,\n            \"{}\",\n            match self {\n                Self::Json => \"json\",\n                Self::Jsonb => \"jsonb\",\n                Self::JsonArray => \"json_array\",\n                Self::JsonbArray => \"jsonb_array\",\n                Self::JsonExtract => \"json_extract\",\n                Self::JsonbExtract => \"jsonb_extract\",\n                Self::JsonArrayLength => \"json_array_length\",\n                Self::JsonArrowExtract => \"->\",\n                Self::JsonArrowShiftExtract => \"->>\",\n                Self::JsonObject => \"json_object\",\n                Self::JsonbObject => \"jsonb_object\",\n                Self::JsonType => \"json_type\",\n                Self::JsonErrorPosition => \"json_error_position\",\n                Self::JsonValid => \"json_valid\",\n                Self::JsonPatch => \"json_patch\",\n                Self::JsonbPatch => \"jsonb_patch\",\n                Self::JsonRemove => \"json_remove\",\n                Self::JsonbRemove => \"jsonb_remove\",\n                Self::JsonReplace => \"json_replace\",\n                Self::JsonbReplace => \"jsonb_replace\",\n                Self::JsonInsert => \"json_insert\",\n                Self::JsonbInsert => \"jsonb_insert\",\n                Self::JsonPretty => \"json_pretty\",\n                Self::JsonSet => \"json_set\",\n                Self::JsonbSet => \"jsonb_set\",\n                Self::JsonQuote => \"json_quote\",\n            }\n        )\n    }\n}\n\n#[cfg(feature = \"json\")]\nimpl JsonFunc {\n    /// Returns true for operator-style entries that should not appear in PRAGMA function_list.\n    pub fn is_internal(&self) -> bool {\n        matches!(self, Self::JsonArrowExtract | Self::JsonArrowShiftExtract)\n    }\n\n    pub fn arities(&self) -> &'static [i32] {\n        match self {\n            Self::Json\n            | Self::Jsonb\n            | Self::JsonQuote\n            | Self::JsonErrorPosition\n            | Self::JsonValid => &[1],\n            Self::JsonPatch | Self::JsonbPatch => &[2],\n            Self::JsonArrayLength | Self::JsonType => &[1, 2],\n            // Operators — filtered out, arity doesn't matter\n            Self::JsonArrowExtract | Self::JsonArrowShiftExtract => &[2],\n            // Variable-arg\n            _ => &[-1],\n        }\n    }\n}\n\n#[derive(Debug, Clone, strum::EnumIter)]\npub enum VectorFunc {\n    Vector,\n    Vector32,\n    Vector32Sparse,\n    Vector64,\n    Vector8,\n    Vector1Bit,\n    VectorExtract,\n    VectorDistanceCos,\n    VectorDistanceL2,\n    VectorDistanceJaccard,\n    VectorDistanceDot,\n    VectorConcat,\n    VectorSlice,\n}\n\nimpl Deterministic for VectorFunc {\n    fn is_deterministic(&self) -> bool {\n        true\n    }\n}\n\nimpl Display for VectorFunc {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        let str = match self {\n            Self::Vector => \"vector\",\n            Self::Vector32 => \"vector32\",\n            Self::Vector32Sparse => \"vector32_sparse\",\n            Self::Vector64 => \"vector64\",\n            Self::Vector8 => \"vector8\",\n            Self::Vector1Bit => \"vector1bit\",\n            Self::VectorExtract => \"vector_extract\",\n            Self::VectorDistanceCos => \"vector_distance_cos\",\n            Self::VectorDistanceL2 => \"vector_distance_l2\",\n            Self::VectorDistanceJaccard => \"vector_distance_jaccard\",\n            Self::VectorDistanceDot => \"vector_distance_dot\",\n            Self::VectorConcat => \"vector_concat\",\n            Self::VectorSlice => \"vector_slice\",\n        };\n        write!(f, \"{str}\")\n    }\n}\n\nimpl VectorFunc {\n    pub fn arities(&self) -> &'static [i32] {\n        match self {\n            Self::Vector\n            | Self::Vector32\n            | Self::Vector32Sparse\n            | Self::Vector64\n            | Self::Vector8\n            | Self::Vector1Bit\n            | Self::VectorExtract => &[1],\n            Self::VectorDistanceCos\n            | Self::VectorDistanceL2\n            | Self::VectorDistanceJaccard\n            | Self::VectorDistanceDot => &[2],\n            Self::VectorSlice => &[3],\n            Self::VectorConcat => &[-1],\n        }\n    }\n}\n\n/// Full-text search functions\n#[cfg(all(feature = \"fts\", not(target_family = \"wasm\")))]\n#[derive(Debug, Clone, PartialEq, strum::EnumIter)]\npub enum FtsFunc {\n    /// fts_score(col1, col2, ..., query): computes FTS relevance score\n    /// When used with an FTS index, the optimizer routes through the index method\n    Score,\n    /// fts_match(col1, col2, ..., query): returns true if document matches query\n    /// Used in WHERE clause for filtering rows by FTS match\n    Match,\n    /// fts_highlight(text, query, before_tag, after_tag): returns text with matching terms highlighted\n    /// Wraps matching query terms in the text with before_tag and after_tag markers\n    Highlight,\n}\n\n#[cfg(all(feature = \"fts\", not(target_family = \"wasm\")))]\nimpl FtsFunc {\n    pub fn is_deterministic(&self) -> bool {\n        true\n    }\n\n    pub fn arities(&self) -> &'static [i32] {\n        match self {\n            Self::Highlight => &[4],\n            // Score and Match take variable columns + query\n            Self::Score | Self::Match => &[-1],\n        }\n    }\n}\n\n#[cfg(all(feature = \"fts\", not(target_family = \"wasm\")))]\nimpl Display for FtsFunc {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        let str = match self {\n            Self::Score => \"fts_score\",\n            Self::Match => \"fts_match\",\n            Self::Highlight => \"fts_highlight\",\n        };\n        write!(f, \"{str}\")\n    }\n}\n\n#[derive(Debug, Clone, strum::EnumIter)]\npub enum AggFunc {\n    Avg,\n    Count,\n    Count0,\n    GroupConcat,\n    Max,\n    Min,\n    StringAgg,\n    Sum,\n    Total,\n    #[cfg(feature = \"json\")]\n    JsonbGroupArray,\n    #[cfg(feature = \"json\")]\n    JsonGroupArray,\n    #[cfg(feature = \"json\")]\n    JsonbGroupObject,\n    #[cfg(feature = \"json\")]\n    JsonGroupObject,\n    ArrayAgg,\n    #[strum(disabled)]\n    External(Arc<ExtFunc>),\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::EnumIter)]\npub enum WindowFunc {\n    RowNumber,\n}\n\nimpl WindowFunc {\n    pub fn arities(&self) -> &'static [i32] {\n        match self {\n            Self::RowNumber => &[0],\n        }\n    }\n}\n\nimpl Deterministic for WindowFunc {\n    fn is_deterministic(&self) -> bool {\n        true\n    }\n}\n\nimpl std::fmt::Display for WindowFunc {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match self {\n            Self::RowNumber => write!(f, \"row_number\"),\n        }\n    }\n}\n\nimpl PartialEq for AggFunc {\n    fn eq(&self, other: &Self) -> bool {\n        match (self, other) {\n            (Self::Avg, Self::Avg)\n            | (Self::Count, Self::Count)\n            | (Self::GroupConcat, Self::GroupConcat)\n            | (Self::Max, Self::Max)\n            | (Self::Min, Self::Min)\n            | (Self::StringAgg, Self::StringAgg)\n            | (Self::Sum, Self::Sum)\n            | (Self::Total, Self::Total)\n            | (Self::ArrayAgg, Self::ArrayAgg) => true,\n            (Self::External(a), Self::External(b)) => Arc::ptr_eq(a, b),\n            _ => false,\n        }\n    }\n}\n\nimpl Deterministic for AggFunc {\n    fn is_deterministic(&self) -> bool {\n        false // consider aggregate functions nondeterministic since they depend on the number of rows, not only the input arguments\n    }\n}\nimpl std::fmt::Display for AggFunc {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"{}\", self.as_str())\n    }\n}\n\nimpl AggFunc {\n    pub fn num_args(&self) -> usize {\n        match self {\n            Self::Avg => 1,\n            Self::Count0 => 0,\n            Self::Count => 1,\n            Self::GroupConcat => 1,\n            Self::Max => 1,\n            Self::Min => 1,\n            Self::StringAgg => 2,\n            Self::Sum => 1,\n            Self::Total => 1,\n            Self::ArrayAgg => 1,\n            #[cfg(feature = \"json\")]\n            Self::JsonGroupArray | Self::JsonbGroupArray => 1,\n            #[cfg(feature = \"json\")]\n            Self::JsonGroupObject | Self::JsonbGroupObject => 2,\n            Self::External(func) => func.agg_args().unwrap_or(0),\n        }\n    }\n\n    /// Returns all valid arities for this aggregate function.\n    /// Most aggregates have a single arity, but group_concat accepts 1 or 2 args.\n    pub fn arities(&self) -> &'static [i32] {\n        match self {\n            Self::Avg => &[1],\n            Self::Count0 => &[0],\n            Self::Count => &[1],\n            Self::GroupConcat => &[1, 2],\n            Self::Max => &[1],\n            Self::Min => &[1],\n            Self::StringAgg => &[2],\n            Self::Sum => &[1],\n            Self::Total => &[1],\n            Self::ArrayAgg => &[1],\n            #[cfg(feature = \"json\")]\n            Self::JsonGroupArray | Self::JsonbGroupArray => &[1],\n            #[cfg(feature = \"json\")]\n            Self::JsonGroupObject | Self::JsonbGroupObject => &[2],\n            Self::External(_) => &[-1],\n        }\n    }\n\n    pub fn as_str(&self) -> &'static str {\n        match self {\n            Self::Avg => \"avg\",\n            Self::Count0 => \"count\",\n            Self::Count => \"count\",\n            Self::GroupConcat => \"group_concat\",\n            Self::Max => \"max\",\n            Self::Min => \"min\",\n            Self::StringAgg => \"string_agg\",\n            Self::Sum => \"sum\",\n            Self::Total => \"total\",\n            Self::ArrayAgg => \"array_agg\",\n            #[cfg(feature = \"json\")]\n            Self::JsonbGroupArray => \"jsonb_group_array\",\n            #[cfg(feature = \"json\")]\n            Self::JsonGroupArray => \"json_group_array\",\n            #[cfg(feature = \"json\")]\n            Self::JsonbGroupObject => \"jsonb_group_object\",\n            #[cfg(feature = \"json\")]\n            Self::JsonGroupObject => \"json_group_object\",\n            Self::External(_) => \"extension function\",\n        }\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, strum::EnumIter)]\npub enum ScalarFunc {\n    Cast,\n    Changes,\n    Char,\n    Coalesce,\n    Concat,\n    ConcatWs,\n    Glob,\n    IfNull,\n    Iif,\n    Instr,\n    Like,\n    Abs,\n    Upper,\n    Lower,\n    Random,\n    RandomBlob,\n    Trim,\n    LTrim,\n    RTrim,\n    Round,\n    Length,\n    OctetLength,\n    Min,\n    Max,\n    Nullif,\n    Sign,\n    Substr,\n    Substring,\n    Soundex,\n    Date,\n    Time,\n    TotalChanges,\n    DateTime,\n    Typeof,\n    Unicode,\n    Quote,\n    SqliteVersion,\n    TursoVersion,\n    SqliteSourceId,\n    UnixEpoch,\n    JulianDay,\n    Hex,\n    Unhex,\n    ZeroBlob,\n    LastInsertRowid,\n    Replace,\n    #[cfg(feature = \"fs\")]\n    #[cfg(not(target_family = \"wasm\"))]\n    LoadExtension,\n    StrfTime,\n    Printf,\n    Likely,\n    TimeDiff,\n    Likelihood,\n    TableColumnsJsonArray,\n    BinRecordJsonObject,\n    Attach,\n    Detach,\n    Unlikely,\n    StatInit,\n    StatPush,\n    StatGet,\n    ConnTxnId,\n    IsAutocommit,\n    // Test type functions (for custom type system testing)\n    TestUintEncode,\n    TestUintDecode,\n    TestUintAdd,\n    TestUintSub,\n    TestUintMul,\n    TestUintDiv,\n    TestUintLt,\n    TestUintEq,\n    StringReverse,\n    // Built-in type support functions\n    BooleanToInt,\n    IntToBoolean,\n    ValidateIpAddr,\n    // Numeric type functions\n    NumericEncode,\n    NumericDecode,\n    NumericAdd,\n    NumericSub,\n    NumericMul,\n    NumericDiv,\n    NumericLt,\n    NumericEq,\n    // Array construction / element access (desugared from ARRAY[…] and expr[n] syntax)\n    Array,\n    ArrayElement,\n    ArraySetElement,\n    // Array utility functions\n    ArrayLength,\n    ArrayAppend,\n    ArrayPrepend,\n    ArrayCat,\n    ArrayRemove,\n    ArrayContains,\n    ArrayPosition,\n    ArraySlice,\n    StringToArray,\n    ArrayToString,\n    ArrayOverlap,\n    ArrayContainsAll,\n}\n\nimpl Deterministic for ScalarFunc {\n    fn is_deterministic(&self) -> bool {\n        match self {\n            ScalarFunc::Cast => true,\n            ScalarFunc::Changes => false, // depends on DB state\n            ScalarFunc::Char => true,\n            ScalarFunc::Coalesce => true,\n            ScalarFunc::Concat => true,\n            ScalarFunc::ConcatWs => true,\n            ScalarFunc::Glob => true,\n            ScalarFunc::IfNull => true,\n            ScalarFunc::Iif => true,\n            ScalarFunc::Instr => true,\n            ScalarFunc::Like => true,\n            ScalarFunc::Abs => true,\n            ScalarFunc::Upper => true,\n            ScalarFunc::Lower => true,\n            ScalarFunc::Random => false,     // duh\n            ScalarFunc::RandomBlob => false, // duh\n            ScalarFunc::Trim => true,\n            ScalarFunc::LTrim => true,\n            ScalarFunc::RTrim => true,\n            ScalarFunc::Round => true,\n            ScalarFunc::Length => true,\n            ScalarFunc::OctetLength => true,\n            ScalarFunc::Min => true,\n            ScalarFunc::Max => true,\n            ScalarFunc::Nullif => true,\n            ScalarFunc::Sign => true,\n            ScalarFunc::Substr => true,\n            ScalarFunc::Substring => true,\n            ScalarFunc::Soundex => true,\n            ScalarFunc::Date => false,\n            ScalarFunc::Time => false,\n            ScalarFunc::TotalChanges => false,\n            ScalarFunc::DateTime => false,\n            ScalarFunc::Typeof => true,\n            ScalarFunc::Unicode => true,\n            ScalarFunc::Quote => true,\n            ScalarFunc::SqliteVersion => false,\n            ScalarFunc::TursoVersion => false,\n            ScalarFunc::SqliteSourceId => false,\n            ScalarFunc::UnixEpoch => false,\n            ScalarFunc::JulianDay => false,\n            ScalarFunc::Hex => true,\n            ScalarFunc::Unhex => true,\n            ScalarFunc::ZeroBlob => true,\n            ScalarFunc::LastInsertRowid => false,\n            ScalarFunc::Replace => true,\n            #[cfg(feature = \"fs\")]\n            #[cfg(not(target_family = \"wasm\"))]\n            ScalarFunc::LoadExtension => false,\n            ScalarFunc::StrfTime => false,\n            ScalarFunc::Printf => true,\n            ScalarFunc::Likely => true,\n            ScalarFunc::TimeDiff => false,\n            ScalarFunc::Likelihood => true,\n            ScalarFunc::TableColumnsJsonArray => true, // while columns of the table can change with DDL statements, within single query plan it's static\n            ScalarFunc::BinRecordJsonObject => true,\n            ScalarFunc::Attach => false, // changes database state\n            ScalarFunc::Detach => false, // changes database state\n            ScalarFunc::Unlikely => true,\n            ScalarFunc::StatInit => false, // internal ANALYZE function\n            ScalarFunc::StatPush => false, // internal ANALYZE function\n            ScalarFunc::StatGet => false,  // internal ANALYZE function\n            ScalarFunc::ConnTxnId => false, // depends on connection state\n            ScalarFunc::IsAutocommit => false, // depends on connection state\n            ScalarFunc::TestUintEncode\n            | ScalarFunc::TestUintDecode\n            | ScalarFunc::TestUintAdd\n            | ScalarFunc::TestUintSub\n            | ScalarFunc::TestUintMul\n            | ScalarFunc::TestUintDiv\n            | ScalarFunc::TestUintLt\n            | ScalarFunc::TestUintEq\n            | ScalarFunc::StringReverse => true,\n            ScalarFunc::BooleanToInt\n            | ScalarFunc::IntToBoolean\n            | ScalarFunc::ValidateIpAddr\n            | ScalarFunc::NumericEncode\n            | ScalarFunc::NumericDecode\n            | ScalarFunc::NumericAdd\n            | ScalarFunc::NumericSub\n            | ScalarFunc::NumericMul\n            | ScalarFunc::NumericDiv\n            | ScalarFunc::NumericLt\n            | ScalarFunc::NumericEq => true,\n            ScalarFunc::Array\n            | ScalarFunc::ArrayElement\n            | ScalarFunc::ArraySetElement\n            | ScalarFunc::ArrayLength\n            | ScalarFunc::ArrayAppend\n            | ScalarFunc::ArrayPrepend\n            | ScalarFunc::ArrayCat\n            | ScalarFunc::ArrayRemove\n            | ScalarFunc::ArrayContains\n            | ScalarFunc::ArrayPosition\n            | ScalarFunc::ArraySlice\n            | ScalarFunc::StringToArray\n            | ScalarFunc::ArrayToString\n            | ScalarFunc::ArrayOverlap\n            | ScalarFunc::ArrayContainsAll => true,\n        }\n    }\n}\n\nimpl ScalarFunc {\n    /// Returns true if this function returns a record-format array blob\n    /// that needs ArrayDecode for display.\n    ///\n    /// FIXME: ideally every function would declare its return type via a\n    /// `return_type()` method, and this whitelist would be replaced by a\n    /// generic check. Postponed for now — the set of array-returning\n    /// functions is small and controlled by us.\n    pub fn returns_array_blob(&self) -> bool {\n        matches!(\n            self,\n            Self::Array\n                | Self::ArraySetElement\n                | Self::ArrayAppend\n                | Self::ArrayPrepend\n                | Self::ArrayCat\n                | Self::ArrayRemove\n                | Self::ArraySlice\n                | Self::StringToArray\n        )\n    }\n}\n\nimpl Display for ScalarFunc {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        let str = match self {\n            Self::Cast => \"cast\",\n            Self::Changes => \"changes\",\n            Self::Char => \"char\",\n            Self::Coalesce => \"coalesce\",\n            Self::Concat => \"concat\",\n            Self::ConcatWs => \"concat_ws\",\n            Self::Glob => \"glob\",\n            Self::IfNull => \"ifnull\",\n            Self::Iif => \"iif\",\n            Self::Instr => \"instr\",\n            Self::Like => \"like\",\n            Self::Abs => \"abs\",\n            Self::Upper => \"upper\",\n            Self::Lower => \"lower\",\n            Self::Random => \"random\",\n            Self::RandomBlob => \"randomblob\",\n            Self::Trim => \"trim\",\n            Self::LTrim => \"ltrim\",\n            Self::RTrim => \"rtrim\",\n            Self::Round => \"round\",\n            Self::Length => \"length\",\n            Self::OctetLength => \"octet_length\",\n            Self::Min => \"min\",\n            Self::Max => \"max\",\n            Self::Nullif => \"nullif\",\n            Self::Sign => \"sign\",\n            Self::Substr => \"substr\",\n            Self::Substring => \"substring\",\n            Self::Soundex => \"soundex\",\n            Self::Date => \"date\",\n            Self::Time => \"time\",\n            Self::TotalChanges => \"total_changes\",\n            Self::Typeof => \"typeof\",\n            Self::Unicode => \"unicode\",\n            Self::Quote => \"quote\",\n            Self::SqliteVersion => \"sqlite_version\",\n            Self::TursoVersion => \"turso_version\",\n            Self::SqliteSourceId => \"sqlite_source_id\",\n            Self::JulianDay => \"julianday\",\n            Self::UnixEpoch => \"unixepoch\",\n            Self::Hex => \"hex\",\n            Self::Unhex => \"unhex\",\n            Self::ZeroBlob => \"zeroblob\",\n            Self::LastInsertRowid => \"last_insert_rowid\",\n            Self::Replace => \"replace\",\n            Self::DateTime => \"datetime\",\n            #[cfg(feature = \"fs\")]\n            #[cfg(not(target_family = \"wasm\"))]\n            Self::LoadExtension => \"load_extension\",\n            Self::StrfTime => \"strftime\",\n            Self::Printf => \"printf\",\n            Self::Likely => \"likely\",\n            Self::TimeDiff => \"timediff\",\n            Self::Likelihood => \"likelihood\",\n            Self::TableColumnsJsonArray => \"table_columns_json_array\",\n            Self::BinRecordJsonObject => \"bin_record_json_object\",\n            Self::Attach => \"attach\",\n            Self::Detach => \"detach\",\n            Self::Unlikely => \"unlikely\",\n            Self::StatInit => \"stat_init\",\n            Self::StatPush => \"stat_push\",\n            Self::StatGet => \"stat_get\",\n            Self::ConnTxnId => \"conn_txn_id\",\n            Self::IsAutocommit => \"is_autocommit\",\n            Self::TestUintEncode => \"test_uint_encode\",\n            Self::TestUintDecode => \"test_uint_decode\",\n            Self::TestUintAdd => \"test_uint_add\",\n            Self::TestUintSub => \"test_uint_sub\",\n            Self::TestUintMul => \"test_uint_mul\",\n            Self::TestUintDiv => \"test_uint_div\",\n            Self::TestUintLt => \"test_uint_lt\",\n            Self::TestUintEq => \"test_uint_eq\",\n            Self::StringReverse => \"string_reverse\",\n            Self::BooleanToInt => \"boolean_to_int\",\n            Self::IntToBoolean => \"int_to_boolean\",\n            Self::ValidateIpAddr => \"validate_ipaddr\",\n            Self::NumericEncode => \"numeric_encode\",\n            Self::NumericDecode => \"numeric_decode\",\n            Self::NumericAdd => \"numeric_add\",\n            Self::NumericSub => \"numeric_sub\",\n            Self::NumericMul => \"numeric_mul\",\n            Self::NumericDiv => \"numeric_div\",\n            Self::NumericLt => \"numeric_lt\",\n            Self::NumericEq => \"numeric_eq\",\n            Self::Array => \"array\",\n            Self::ArrayElement => \"array_element\",\n            Self::ArraySetElement => \"array_set_element\",\n            Self::ArrayLength => \"array_length\",\n            Self::ArrayAppend => \"array_append\",\n            Self::ArrayPrepend => \"array_prepend\",\n            Self::ArrayCat => \"array_cat\",\n            Self::ArrayRemove => \"array_remove\",\n            Self::ArrayContains => \"array_contains\",\n            Self::ArrayPosition => \"array_position\",\n            Self::ArraySlice => \"array_slice\",\n            Self::StringToArray => \"string_to_array\",\n            Self::ArrayToString => \"array_to_string\",\n            Self::ArrayOverlap => \"array_overlap\",\n            Self::ArrayContainsAll => \"array_contains_all\",\n        };\n        write!(f, \"{str}\")\n    }\n}\n\nimpl ScalarFunc {\n    /// Returns true for internal functions that should not appear in PRAGMA function_list.\n    pub fn is_internal(&self) -> bool {\n        matches!(\n            self,\n            Self::Cast\n                | Self::Array\n                | Self::ArrayElement\n                | Self::ArraySetElement\n                | Self::StatInit\n                | Self::StatPush\n                | Self::StatGet\n                | Self::Attach\n                | Self::Detach\n                | Self::TableColumnsJsonArray\n                | Self::BinRecordJsonObject\n                | Self::ConnTxnId\n                | Self::IsAutocommit\n        )\n    }\n\n    /// Returns the valid arities for this function.\n    /// Each value becomes a separate row in PRAGMA function_list.\n    /// -1 means truly variable arguments (e.g. coalesce, printf).\n    pub fn arities(&self) -> &'static [i32] {\n        match self {\n            // 0-arg\n            Self::Changes\n            | Self::LastInsertRowid\n            | Self::Random\n            | Self::SqliteVersion\n            | Self::TursoVersion\n            | Self::SqliteSourceId\n            | Self::TotalChanges => &[0],\n            // 1-arg\n            Self::Abs\n            | Self::Hex\n            | Self::Length\n            | Self::Lower\n            | Self::OctetLength\n            | Self::Quote\n            | Self::RandomBlob\n            | Self::Sign\n            | Self::Soundex\n            | Self::Typeof\n            | Self::Unicode\n            | Self::Upper\n            | Self::ZeroBlob\n            | Self::Likely\n            | Self::Unlikely => &[1],\n            // 2-arg\n            Self::Glob\n            | Self::Instr\n            | Self::Nullif\n            | Self::IfNull\n            | Self::Likelihood\n            | Self::TimeDiff => &[2],\n            // 3-arg\n            Self::Iif | Self::Replace => &[3],\n            // Multi-arity (one row per valid arity)\n            Self::Like => &[2, 3],\n            Self::Trim | Self::LTrim | Self::RTrim | Self::Round | Self::Unhex => &[1, 2],\n            Self::Substr | Self::Substring => &[2, 3],\n            // Truly variable-arg\n            Self::Char\n            | Self::Coalesce\n            | Self::Concat\n            | Self::ConcatWs\n            | Self::Date\n            | Self::Time\n            | Self::DateTime\n            | Self::UnixEpoch\n            | Self::JulianDay\n            | Self::StrfTime\n            | Self::Printf => &[-1],\n            #[cfg(feature = \"fs\")]\n            #[cfg(not(target_family = \"wasm\"))]\n            Self::LoadExtension => &[-1],\n            // Internal functions — arity doesn't matter since they're filtered out\n            Self::Cast\n            | Self::StatInit\n            | Self::StatPush\n            | Self::StatGet\n            | Self::Attach\n            | Self::Detach\n            | Self::TableColumnsJsonArray\n            | Self::BinRecordJsonObject\n            | Self::ConnTxnId\n            | Self::IsAutocommit => &[0],\n            // Scalar max/min (multi-arg)\n            Self::Max | Self::Min => &[-1],\n            // Test functions for custom types (1-arg encode/decode, 2-arg operators)\n            Self::TestUintEncode | Self::TestUintDecode | Self::StringReverse => &[1],\n            Self::TestUintAdd\n            | Self::TestUintSub\n            | Self::TestUintMul\n            | Self::TestUintDiv\n            | Self::TestUintLt\n            | Self::TestUintEq => &[2],\n            // Built-in type functions\n            Self::BooleanToInt\n            | Self::IntToBoolean\n            | Self::ValidateIpAddr\n            | Self::NumericDecode => &[1],\n            Self::NumericAdd\n            | Self::NumericSub\n            | Self::NumericMul\n            | Self::NumericDiv\n            | Self::NumericLt\n            | Self::NumericEq => &[2],\n            Self::NumericEncode => &[3],\n            // Array construction / element access\n            Self::Array => &[-1], // variable arity\n            Self::ArrayElement => &[2],\n            Self::ArraySetElement => &[3],\n            // Array functions\n            Self::ArrayLength => &[1, 2],\n            Self::ArrayAppend\n            | Self::ArrayPrepend\n            | Self::ArrayCat\n            | Self::ArrayRemove\n            | Self::ArrayContains\n            | Self::ArrayPosition\n            | Self::ArrayOverlap\n            | Self::ArrayContainsAll => &[2],\n            Self::ArraySlice => &[3],\n            Self::StringToArray => &[2, 3],\n            Self::ArrayToString => &[2, 3],\n        }\n    }\n\n    /// Returns true for functions that can turn NULL arguments into a non-NULL result.\n    ///\n    /// This is used by planner/optimizer logic that needs to reason about whether\n    /// predicates are null-rejecting for outer-join simplification.\n    pub fn can_mask_nulls(&self) -> bool {\n        matches!(self, Self::Coalesce | Self::IfNull)\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, strum::EnumIter)]\npub enum MathFunc {\n    Acos,\n    Acosh,\n    Asin,\n    Asinh,\n    Atan,\n    Atan2,\n    Atanh,\n    Ceil,\n    Ceiling,\n    Cos,\n    Cosh,\n    Degrees,\n    Exp,\n    Floor,\n    Ln,\n    Log,\n    Log10,\n    Log2,\n    Mod,\n    Pi,\n    Pow,\n    Power,\n    Radians,\n    Sin,\n    Sinh,\n    Sqrt,\n    Tan,\n    Tanh,\n    Trunc,\n}\n\npub enum MathFuncArity {\n    Nullary,\n    Unary,\n    Binary,\n    UnaryOrBinary,\n}\n\nimpl Deterministic for MathFunc {\n    fn is_deterministic(&self) -> bool {\n        true\n    }\n}\n\nimpl MathFunc {\n    pub fn arity(&self) -> MathFuncArity {\n        match self {\n            Self::Pi => MathFuncArity::Nullary,\n            Self::Acos\n            | Self::Acosh\n            | Self::Asin\n            | Self::Asinh\n            | Self::Atan\n            | Self::Atanh\n            | Self::Ceil\n            | Self::Ceiling\n            | Self::Cos\n            | Self::Cosh\n            | Self::Degrees\n            | Self::Exp\n            | Self::Floor\n            | Self::Ln\n            | Self::Log10\n            | Self::Log2\n            | Self::Radians\n            | Self::Sin\n            | Self::Sinh\n            | Self::Sqrt\n            | Self::Tan\n            | Self::Tanh\n            | Self::Trunc => MathFuncArity::Unary,\n\n            Self::Atan2 | Self::Mod | Self::Pow | Self::Power => MathFuncArity::Binary,\n\n            Self::Log => MathFuncArity::UnaryOrBinary,\n        }\n    }\n\n    pub fn arities(&self) -> &'static [i32] {\n        match self.arity() {\n            MathFuncArity::Nullary => &[0],\n            MathFuncArity::Unary => &[1],\n            MathFuncArity::Binary => &[2],\n            MathFuncArity::UnaryOrBinary => &[1, 2],\n        }\n    }\n}\n\nimpl Display for MathFunc {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        let str = match self {\n            Self::Acos => \"acos\",\n            Self::Acosh => \"acosh\",\n            Self::Asin => \"asin\",\n            Self::Asinh => \"asinh\",\n            Self::Atan => \"atan\",\n            Self::Atan2 => \"atan2\",\n            Self::Atanh => \"atanh\",\n            Self::Ceil => \"ceil\",\n            Self::Ceiling => \"ceiling\",\n            Self::Cos => \"cos\",\n            Self::Cosh => \"cosh\",\n            Self::Degrees => \"degrees\",\n            Self::Exp => \"exp\",\n            Self::Floor => \"floor\",\n            Self::Ln => \"ln\",\n            Self::Log => \"log\",\n            Self::Log10 => \"log10\",\n            Self::Log2 => \"log2\",\n            Self::Mod => \"mod\",\n            Self::Pi => \"pi\",\n            Self::Pow => \"pow\",\n            Self::Power => \"power\",\n            Self::Radians => \"radians\",\n            Self::Sin => \"sin\",\n            Self::Sinh => \"sinh\",\n            Self::Sqrt => \"sqrt\",\n            Self::Tan => \"tan\",\n            Self::Tanh => \"tanh\",\n            Self::Trunc => \"trunc\",\n        };\n        write!(f, \"{str}\")\n    }\n}\n\n#[derive(Debug, Clone)]\npub enum AlterTableFunc {\n    RenameTable,\n    AlterColumn,\n    RenameColumn,\n}\n\nimpl Display for AlterTableFunc {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match self {\n            AlterTableFunc::RenameTable => write!(f, \"limbo_rename_table\"),\n            AlterTableFunc::RenameColumn => write!(f, \"limbo_rename_column\"),\n            AlterTableFunc::AlterColumn => write!(f, \"limbo_alter_column\"),\n        }\n    }\n}\n\n#[derive(Debug, Clone)]\npub enum Func {\n    Agg(AggFunc),\n    Window(WindowFunc),\n    Scalar(ScalarFunc),\n    Math(MathFunc),\n    Vector(VectorFunc),\n    #[cfg(all(feature = \"fts\", not(target_family = \"wasm\")))]\n    Fts(FtsFunc),\n    #[cfg(feature = \"json\")]\n    Json(JsonFunc),\n    AlterTable(AlterTableFunc),\n    External(Arc<ExternalFunc>),\n}\n\nimpl Display for Func {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match self {\n            Self::Agg(agg_func) => write!(f, \"{}\", agg_func.as_str()),\n            Self::Window(window_func) => write!(f, \"{window_func}\"),\n            Self::Scalar(scalar_func) => write!(f, \"{scalar_func}\"),\n            Self::Math(math_func) => write!(f, \"{math_func}\"),\n            Self::Vector(vector_func) => write!(f, \"{vector_func}\"),\n            #[cfg(all(feature = \"fts\", not(target_family = \"wasm\")))]\n            Self::Fts(fts_func) => write!(f, \"{fts_func}\"),\n            #[cfg(feature = \"json\")]\n            Self::Json(json_func) => write!(f, \"{json_func}\"),\n            Self::External(generic_func) => write!(f, \"{generic_func}\"),\n            Self::AlterTable(alter_func) => write!(f, \"{alter_func}\"),\n        }\n    }\n}\n\n#[derive(Debug, Clone)]\npub struct FuncCtx {\n    pub func: Func,\n    pub arg_count: usize,\n}\n\nimpl Deterministic for Func {\n    fn is_deterministic(&self) -> bool {\n        match self {\n            Self::Agg(agg_func) => agg_func.is_deterministic(),\n            Self::Window(window_func) => window_func.is_deterministic(),\n            Self::Scalar(scalar_func) => scalar_func.is_deterministic(),\n            Self::Math(math_func) => math_func.is_deterministic(),\n            Self::Vector(vector_func) => vector_func.is_deterministic(),\n            #[cfg(all(feature = \"fts\", not(target_family = \"wasm\")))]\n            Self::Fts(fts_func) => fts_func.is_deterministic(),\n            #[cfg(feature = \"json\")]\n            Self::Json(json_func) => json_func.is_deterministic(),\n            Self::External(external_func) => external_func.is_deterministic(),\n            Self::AlterTable(_) => true,\n        }\n    }\n}\n\nimpl Func {\n    pub fn supports_star_syntax(&self) -> bool {\n        // Functions that need star expansion also support star syntax\n        if self.needs_star_expansion() {\n            return true;\n        }\n        match self {\n            Self::Scalar(scalar_func) => {\n                matches!(\n                    scalar_func,\n                    ScalarFunc::Changes\n                        | ScalarFunc::Random\n                        | ScalarFunc::TotalChanges\n                        | ScalarFunc::SqliteVersion\n                        | ScalarFunc::TursoVersion\n                        | ScalarFunc::SqliteSourceId\n                        | ScalarFunc::LastInsertRowid\n                )\n            }\n            Self::Math(math_func) => {\n                matches!(math_func.arity(), MathFuncArity::Nullary)\n            }\n            // Aggregate functions with (*) syntax are handled separately in the planner\n            Self::Agg(_) => false,\n            Self::Window(_) => false,\n            _ => false,\n        }\n    }\n\n    /// Returns true for functions that can turn NULL arguments into a non-NULL result.\n    ///\n    /// This metadata is currently used by optimizer null-rejection analysis.\n    pub fn can_mask_nulls(&self) -> bool {\n        match self {\n            Self::Scalar(scalar_func) => scalar_func.can_mask_nulls(),\n            _ => false,\n        }\n    }\n\n    /// Returns true if the function needs the `*` to be expanded to all columns\n    /// from the referenced tables. This is used for functions like `json_object(*)`\n    /// and `jsonb_object(*)` which create a JSON object with column names as keys\n    /// and column values as values.\n    #[cfg(feature = \"json\")]\n    pub fn needs_star_expansion(&self) -> bool {\n        matches!(\n            self,\n            Self::Json(JsonFunc::JsonObject) | Self::Json(JsonFunc::JsonbObject)\n        )\n    }\n\n    #[cfg(not(feature = \"json\"))]\n    pub fn needs_star_expansion(&self) -> bool {\n        false\n    }\n    pub fn resolve_function(name: &str, arg_count: usize) -> Result<Self, LimboError> {\n        let normalized_name = crate::util::normalize_ident(name);\n        match normalized_name.as_str() {\n            \"avg\" => {\n                if arg_count != 1 {\n                    crate::bail_parse_error!(\"wrong number of arguments to function {}()\", name)\n                }\n                Ok(Self::Agg(AggFunc::Avg))\n            }\n            \"count\" => {\n                // Handle both COUNT() and COUNT(expr) cases\n                if arg_count == 0 {\n                    Ok(Self::Agg(AggFunc::Count0)) // COUNT() case\n                } else if arg_count == 1 {\n                    Ok(Self::Agg(AggFunc::Count)) // COUNT(expr) case\n                } else {\n                    crate::bail_parse_error!(\"wrong number of arguments to function {}()\", name)\n                }\n            }\n            \"group_concat\" => {\n                if arg_count != 1 && arg_count != 2 {\n                    println!(\"{arg_count}\");\n                    crate::bail_parse_error!(\"wrong number of arguments to function {}()\", name)\n                }\n                Ok(Self::Agg(AggFunc::GroupConcat))\n            }\n            \"max\" if arg_count > 1 => Ok(Self::Scalar(ScalarFunc::Max)),\n            \"max\" => {\n                if arg_count < 1 {\n                    crate::bail_parse_error!(\"wrong number of arguments to function {}()\", name)\n                }\n                Ok(Self::Agg(AggFunc::Max))\n            }\n            \"min\" if arg_count > 1 => Ok(Self::Scalar(ScalarFunc::Min)),\n            \"min\" => {\n                if arg_count < 1 {\n                    crate::bail_parse_error!(\"wrong number of arguments to function {}()\", name)\n                }\n                Ok(Self::Agg(AggFunc::Min))\n            }\n            \"nullif\" if arg_count == 2 => Ok(Self::Scalar(ScalarFunc::Nullif)),\n            \"string_agg\" => {\n                if arg_count != 2 {\n                    crate::bail_parse_error!(\"wrong number of arguments to function {}()\", name)\n                }\n                Ok(Self::Agg(AggFunc::StringAgg))\n            }\n            \"sum\" => {\n                if arg_count != 1 {\n                    crate::bail_parse_error!(\"wrong number of arguments to function {}()\", name)\n                }\n                Ok(Self::Agg(AggFunc::Sum))\n            }\n            \"total\" => {\n                if arg_count != 1 {\n                    crate::bail_parse_error!(\"wrong number of arguments to function {}()\", name)\n                }\n                Ok(Self::Agg(AggFunc::Total))\n            }\n            \"row_number\" => {\n                if arg_count != 0 {\n                    crate::bail_parse_error!(\"wrong number of arguments to function {}()\", name)\n                }\n                Ok(Self::Window(WindowFunc::RowNumber))\n            }\n            \"timediff\" => {\n                if arg_count != 2 {\n                    crate::bail_parse_error!(\"wrong number of arguments to function {}()\", name)\n                }\n                Ok(Self::Scalar(ScalarFunc::TimeDiff))\n            }\n            \"array_agg\" => Ok(Self::Agg(AggFunc::ArrayAgg)),\n            #[cfg(feature = \"json\")]\n            \"jsonb_group_array\" => Ok(Self::Agg(AggFunc::JsonbGroupArray)),\n            #[cfg(feature = \"json\")]\n            \"json_group_array\" => Ok(Self::Agg(AggFunc::JsonGroupArray)),\n            #[cfg(feature = \"json\")]\n            \"jsonb_group_object\" => Ok(Self::Agg(AggFunc::JsonbGroupObject)),\n            #[cfg(feature = \"json\")]\n            \"json_group_object\" => Ok(Self::Agg(AggFunc::JsonGroupObject)),\n            \"char\" => Ok(Self::Scalar(ScalarFunc::Char)),\n            \"coalesce\" => Ok(Self::Scalar(ScalarFunc::Coalesce)),\n            \"concat\" => Ok(Self::Scalar(ScalarFunc::Concat)),\n            \"concat_ws\" => Ok(Self::Scalar(ScalarFunc::ConcatWs)),\n            \"changes\" => Ok(Self::Scalar(ScalarFunc::Changes)),\n            \"total_changes\" => Ok(Self::Scalar(ScalarFunc::TotalChanges)),\n            \"glob\" => Ok(Self::Scalar(ScalarFunc::Glob)),\n            \"ifnull\" => Ok(Self::Scalar(ScalarFunc::IfNull)),\n            \"if\" | \"iif\" => Ok(Self::Scalar(ScalarFunc::Iif)),\n            \"instr\" => Ok(Self::Scalar(ScalarFunc::Instr)),\n            \"like\" => Ok(Self::Scalar(ScalarFunc::Like)),\n            \"abs\" => Ok(Self::Scalar(ScalarFunc::Abs)),\n            \"upper\" => Ok(Self::Scalar(ScalarFunc::Upper)),\n            \"lower\" => Ok(Self::Scalar(ScalarFunc::Lower)),\n            \"random\" => Ok(Self::Scalar(ScalarFunc::Random)),\n            \"randomblob\" => Ok(Self::Scalar(ScalarFunc::RandomBlob)),\n            \"trim\" => Ok(Self::Scalar(ScalarFunc::Trim)),\n            \"ltrim\" => Ok(Self::Scalar(ScalarFunc::LTrim)),\n            \"rtrim\" => Ok(Self::Scalar(ScalarFunc::RTrim)),\n            \"round\" => Ok(Self::Scalar(ScalarFunc::Round)),\n            \"length\" => Ok(Self::Scalar(ScalarFunc::Length)),\n            \"octet_length\" => Ok(Self::Scalar(ScalarFunc::OctetLength)),\n            \"sign\" => Ok(Self::Scalar(ScalarFunc::Sign)),\n            \"substr\" => Ok(Self::Scalar(ScalarFunc::Substr)),\n            \"substring\" => Ok(Self::Scalar(ScalarFunc::Substring)),\n            \"date\" => Ok(Self::Scalar(ScalarFunc::Date)),\n            \"time\" => Ok(Self::Scalar(ScalarFunc::Time)),\n            \"datetime\" => Ok(Self::Scalar(ScalarFunc::DateTime)),\n            \"typeof\" => Ok(Self::Scalar(ScalarFunc::Typeof)),\n            \"last_insert_rowid\" => Ok(Self::Scalar(ScalarFunc::LastInsertRowid)),\n            \"unicode\" => Ok(Self::Scalar(ScalarFunc::Unicode)),\n            \"quote\" => Ok(Self::Scalar(ScalarFunc::Quote)),\n            \"sqlite_version\" => Ok(Self::Scalar(ScalarFunc::SqliteVersion)),\n            \"turso_version\" => Ok(Self::Scalar(ScalarFunc::TursoVersion)),\n            \"sqlite_source_id\" => Ok(Self::Scalar(ScalarFunc::SqliteSourceId)),\n            \"replace\" => Ok(Self::Scalar(ScalarFunc::Replace)),\n            \"likely\" => Ok(Self::Scalar(ScalarFunc::Likely)),\n            \"likelihood\" => Ok(Self::Scalar(ScalarFunc::Likelihood)),\n            \"unlikely\" => Ok(Self::Scalar(ScalarFunc::Unlikely)),\n            #[cfg(feature = \"json\")]\n            \"json\" => Ok(Self::Json(JsonFunc::Json)),\n            #[cfg(feature = \"json\")]\n            \"jsonb\" => Ok(Self::Json(JsonFunc::Jsonb)),\n            #[cfg(feature = \"json\")]\n            \"json_array_length\" => Ok(Self::Json(JsonFunc::JsonArrayLength)),\n            #[cfg(feature = \"json\")]\n            \"json_array\" => Ok(Self::Json(JsonFunc::JsonArray)),\n            #[cfg(feature = \"json\")]\n            \"jsonb_array\" => Ok(Self::Json(JsonFunc::JsonbArray)),\n            #[cfg(feature = \"json\")]\n            \"json_extract\" => Ok(Func::Json(JsonFunc::JsonExtract)),\n            #[cfg(feature = \"json\")]\n            \"jsonb_extract\" => Ok(Func::Json(JsonFunc::JsonbExtract)),\n            #[cfg(feature = \"json\")]\n            \"json_object\" => Ok(Func::Json(JsonFunc::JsonObject)),\n            #[cfg(feature = \"json\")]\n            \"jsonb_object\" => Ok(Func::Json(JsonFunc::JsonbObject)),\n            #[cfg(feature = \"json\")]\n            \"json_type\" => Ok(Func::Json(JsonFunc::JsonType)),\n            #[cfg(feature = \"json\")]\n            \"json_error_position\" => Ok(Self::Json(JsonFunc::JsonErrorPosition)),\n            #[cfg(feature = \"json\")]\n            \"json_valid\" => Ok(Self::Json(JsonFunc::JsonValid)),\n            #[cfg(feature = \"json\")]\n            \"json_patch\" => Ok(Self::Json(JsonFunc::JsonPatch)),\n            #[cfg(feature = \"json\")]\n            \"json_remove\" => Ok(Self::Json(JsonFunc::JsonRemove)),\n            #[cfg(feature = \"json\")]\n            \"jsonb_remove\" => Ok(Self::Json(JsonFunc::JsonbRemove)),\n            #[cfg(feature = \"json\")]\n            \"json_replace\" => Ok(Self::Json(JsonFunc::JsonReplace)),\n            #[cfg(feature = \"json\")]\n            \"json_insert\" => Ok(Self::Json(JsonFunc::JsonInsert)),\n            #[cfg(feature = \"json\")]\n            \"jsonb_insert\" => Ok(Self::Json(JsonFunc::JsonbInsert)),\n            #[cfg(feature = \"json\")]\n            \"jsonb_replace\" => Ok(Self::Json(JsonFunc::JsonReplace)),\n            #[cfg(feature = \"json\")]\n            \"json_pretty\" => Ok(Self::Json(JsonFunc::JsonPretty)),\n            #[cfg(feature = \"json\")]\n            \"json_set\" => Ok(Self::Json(JsonFunc::JsonSet)),\n            #[cfg(feature = \"json\")]\n            \"jsonb_set\" => Ok(Self::Json(JsonFunc::JsonbSet)),\n            #[cfg(feature = \"json\")]\n            \"json_quote\" => Ok(Self::Json(JsonFunc::JsonQuote)),\n            \"unixepoch\" => Ok(Self::Scalar(ScalarFunc::UnixEpoch)),\n            \"julianday\" => Ok(Self::Scalar(ScalarFunc::JulianDay)),\n            \"hex\" => Ok(Self::Scalar(ScalarFunc::Hex)),\n            \"unhex\" => Ok(Self::Scalar(ScalarFunc::Unhex)),\n            \"zeroblob\" => Ok(Self::Scalar(ScalarFunc::ZeroBlob)),\n            \"soundex\" => Ok(Self::Scalar(ScalarFunc::Soundex)),\n            \"table_columns_json_array\" => Ok(Self::Scalar(ScalarFunc::TableColumnsJsonArray)),\n            \"bin_record_json_object\" => Ok(Self::Scalar(ScalarFunc::BinRecordJsonObject)),\n            \"conn_txn_id\" => Ok(Self::Scalar(ScalarFunc::ConnTxnId)),\n            \"is_autocommit\" => Ok(Self::Scalar(ScalarFunc::IsAutocommit)),\n            \"acos\" => Ok(Self::Math(MathFunc::Acos)),\n            \"acosh\" => Ok(Self::Math(MathFunc::Acosh)),\n            \"asin\" => Ok(Self::Math(MathFunc::Asin)),\n            \"asinh\" => Ok(Self::Math(MathFunc::Asinh)),\n            \"atan\" => Ok(Self::Math(MathFunc::Atan)),\n            \"atan2\" => Ok(Self::Math(MathFunc::Atan2)),\n            \"atanh\" => Ok(Self::Math(MathFunc::Atanh)),\n            \"ceil\" => Ok(Self::Math(MathFunc::Ceil)),\n            \"ceiling\" => Ok(Self::Math(MathFunc::Ceiling)),\n            \"cos\" => Ok(Self::Math(MathFunc::Cos)),\n            \"cosh\" => Ok(Self::Math(MathFunc::Cosh)),\n            \"degrees\" => Ok(Self::Math(MathFunc::Degrees)),\n            \"exp\" => Ok(Self::Math(MathFunc::Exp)),\n            \"floor\" => Ok(Self::Math(MathFunc::Floor)),\n            \"ln\" => Ok(Self::Math(MathFunc::Ln)),\n            \"log\" => Ok(Self::Math(MathFunc::Log)),\n            \"log10\" => Ok(Self::Math(MathFunc::Log10)),\n            \"log2\" => Ok(Self::Math(MathFunc::Log2)),\n            \"mod\" => Ok(Self::Math(MathFunc::Mod)),\n            \"pi\" => Ok(Self::Math(MathFunc::Pi)),\n            \"pow\" => Ok(Self::Math(MathFunc::Pow)),\n            \"power\" => Ok(Self::Math(MathFunc::Power)),\n            \"radians\" => Ok(Self::Math(MathFunc::Radians)),\n            \"sin\" => Ok(Self::Math(MathFunc::Sin)),\n            \"sinh\" => Ok(Self::Math(MathFunc::Sinh)),\n            \"sqrt\" => Ok(Self::Math(MathFunc::Sqrt)),\n            \"tan\" => Ok(Self::Math(MathFunc::Tan)),\n            \"tanh\" => Ok(Self::Math(MathFunc::Tanh)),\n            \"trunc\" => Ok(Self::Math(MathFunc::Trunc)),\n            #[cfg(feature = \"fs\")]\n            #[cfg(not(target_family = \"wasm\"))]\n            \"load_extension\" => Ok(Self::Scalar(ScalarFunc::LoadExtension)),\n            \"strftime\" => Ok(Self::Scalar(ScalarFunc::StrfTime)),\n            \"printf\" | \"format\" => Ok(Self::Scalar(ScalarFunc::Printf)),\n            \"vector\" => Ok(Self::Vector(VectorFunc::Vector)),\n            \"vector32\" => Ok(Self::Vector(VectorFunc::Vector32)),\n            \"vector32_sparse\" => Ok(Self::Vector(VectorFunc::Vector32Sparse)),\n            \"vector64\" => Ok(Self::Vector(VectorFunc::Vector64)),\n            \"vector8\" => Ok(Self::Vector(VectorFunc::Vector8)),\n            \"vector1bit\" => Ok(Self::Vector(VectorFunc::Vector1Bit)),\n            \"vector_extract\" => Ok(Self::Vector(VectorFunc::VectorExtract)),\n            \"vector_distance_cos\" => Ok(Self::Vector(VectorFunc::VectorDistanceCos)),\n            \"vector_distance_l2\" => Ok(Self::Vector(VectorFunc::VectorDistanceL2)),\n            \"vector_distance_jaccard\" => Ok(Self::Vector(VectorFunc::VectorDistanceJaccard)),\n            \"vector_distance_dot\" => Ok(Self::Vector(VectorFunc::VectorDistanceDot)),\n            \"vector_concat\" => Ok(Self::Vector(VectorFunc::VectorConcat)),\n            \"vector_slice\" => Ok(Self::Vector(VectorFunc::VectorSlice)),\n            // FTS functions\n            #[cfg(all(feature = \"fts\", not(target_family = \"wasm\")))]\n            \"fts_score\" => Ok(Self::Fts(FtsFunc::Score)),\n            #[cfg(all(feature = \"fts\", not(target_family = \"wasm\")))]\n            \"fts_match\" => Ok(Self::Fts(FtsFunc::Match)),\n            #[cfg(all(feature = \"fts\", not(target_family = \"wasm\")))]\n            \"fts_highlight\" => Ok(Self::Fts(FtsFunc::Highlight)),\n            // Test type functions (for custom type system testing)\n            \"test_uint_encode\" => Ok(Self::Scalar(ScalarFunc::TestUintEncode)),\n            \"test_uint_decode\" => Ok(Self::Scalar(ScalarFunc::TestUintDecode)),\n            \"test_uint_add\" => Ok(Self::Scalar(ScalarFunc::TestUintAdd)),\n            \"test_uint_sub\" => Ok(Self::Scalar(ScalarFunc::TestUintSub)),\n            \"test_uint_mul\" => Ok(Self::Scalar(ScalarFunc::TestUintMul)),\n            \"test_uint_div\" => Ok(Self::Scalar(ScalarFunc::TestUintDiv)),\n            \"test_uint_lt\" => Ok(Self::Scalar(ScalarFunc::TestUintLt)),\n            \"test_uint_eq\" => Ok(Self::Scalar(ScalarFunc::TestUintEq)),\n            \"string_reverse\" => Ok(Self::Scalar(ScalarFunc::StringReverse)),\n            // Built-in type support functions\n            \"boolean_to_int\" => Ok(Self::Scalar(ScalarFunc::BooleanToInt)),\n            \"int_to_boolean\" => Ok(Self::Scalar(ScalarFunc::IntToBoolean)),\n            \"validate_ipaddr\" => Ok(Self::Scalar(ScalarFunc::ValidateIpAddr)),\n            \"numeric_encode\" => Ok(Self::Scalar(ScalarFunc::NumericEncode)),\n            \"numeric_decode\" => Ok(Self::Scalar(ScalarFunc::NumericDecode)),\n            \"numeric_add\" => Ok(Self::Scalar(ScalarFunc::NumericAdd)),\n            \"numeric_sub\" => Ok(Self::Scalar(ScalarFunc::NumericSub)),\n            \"numeric_mul\" => Ok(Self::Scalar(ScalarFunc::NumericMul)),\n            \"numeric_div\" => Ok(Self::Scalar(ScalarFunc::NumericDiv)),\n            \"numeric_lt\" => Ok(Self::Scalar(ScalarFunc::NumericLt)),\n            \"numeric_eq\" => Ok(Self::Scalar(ScalarFunc::NumericEq)),\n            // Array construction / element access (desugared from syntax)\n            \"array\" => Ok(Self::Scalar(ScalarFunc::Array)),\n            \"array_element\" => Ok(Self::Scalar(ScalarFunc::ArrayElement)),\n            \"array_set_element\" => Ok(Self::Scalar(ScalarFunc::ArraySetElement)),\n            // Array functions\n            \"array_length\" => Ok(Self::Scalar(ScalarFunc::ArrayLength)),\n            \"array_append\" => Ok(Self::Scalar(ScalarFunc::ArrayAppend)),\n            \"array_prepend\" => Ok(Self::Scalar(ScalarFunc::ArrayPrepend)),\n            \"array_cat\" => Ok(Self::Scalar(ScalarFunc::ArrayCat)),\n            \"array_remove\" => Ok(Self::Scalar(ScalarFunc::ArrayRemove)),\n            \"array_contains\" => Ok(Self::Scalar(ScalarFunc::ArrayContains)),\n            \"array_position\" => Ok(Self::Scalar(ScalarFunc::ArrayPosition)),\n            \"array_slice\" => Ok(Self::Scalar(ScalarFunc::ArraySlice)),\n            \"string_to_array\" => Ok(Self::Scalar(ScalarFunc::StringToArray)),\n            \"array_to_string\" => Ok(Self::Scalar(ScalarFunc::ArrayToString)),\n            \"array_overlap\" | \"array_overlaps\" => Ok(Self::Scalar(ScalarFunc::ArrayOverlap)),\n            \"array_contains_all\" => Ok(Self::Scalar(ScalarFunc::ArrayContainsAll)),\n            _ => crate::bail_parse_error!(\"no such function: {}\", name),\n        }\n    }\n\n    /// Returns a list of all built-in functions for PRAGMA function_list.\n    /// Derives the list from enum iteration so it stays in sync automatically.\n    /// Functions with multiple valid arities get one row per arity.\n    pub fn builtin_function_list() -> Vec<FunctionListEntry> {\n        let mut funcs = Vec::new();\n\n        // Helper: push one entry per arity for a function\n        let mut push = |name: String, func_type: &'static str, arities: &[i32], det: bool| {\n            for &narg in arities {\n                funcs.push(FunctionListEntry {\n                    name: name.clone(),\n                    func_type,\n                    narg,\n                    deterministic: det,\n                });\n            }\n        };\n\n        // Scalar functions (filter out internal-only variants)\n        for f in ScalarFunc::iter() {\n            if f.is_internal() {\n                continue;\n            }\n            push(f.to_string(), \"s\", f.arities(), f.is_deterministic());\n        }\n\n        // Aggregate functions (External is #[strum(disabled)], skipped automatically).\n        // SQLite reports built-in aggregates as \"w\" (window-capable) since they\n        // can all be used with OVER clauses.\n        for f in AggFunc::iter() {\n            push(f.to_string(), \"w\", f.arities(), f.is_deterministic());\n        }\n\n        // Window functions.\n        for f in WindowFunc::iter() {\n            push(f.to_string(), \"w\", f.arities(), f.is_deterministic());\n        }\n\n        // Math functions (all scalar)\n        for f in MathFunc::iter() {\n            push(f.to_string(), \"s\", f.arities(), f.is_deterministic());\n        }\n\n        // Vector functions (all scalar)\n        for f in VectorFunc::iter() {\n            push(f.to_string(), \"s\", f.arities(), f.is_deterministic());\n        }\n\n        // JSON functions (feature-gated, filter out operator-style entries)\n        #[cfg(feature = \"json\")]\n        for f in JsonFunc::iter() {\n            if f.is_internal() {\n                continue;\n            }\n            push(f.to_string(), \"s\", f.arities(), f.is_deterministic());\n        }\n\n        // FTS functions (feature-gated)\n        #[cfg(all(feature = \"fts\", not(target_family = \"wasm\")))]\n        for f in FtsFunc::iter() {\n            push(f.to_string(), \"s\", f.arities(), f.is_deterministic());\n        }\n\n        // Aliases: functions callable under multiple names.\n        // These are additional names that resolve_function() accepts\n        // but that map to existing enum variants.\n        funcs.push(FunctionListEntry {\n            name: \"format\".into(),\n            func_type: \"s\",\n            narg: -1,\n            deterministic: true,\n        });\n        funcs.push(FunctionListEntry {\n            name: \"if\".into(),\n            func_type: \"s\",\n            narg: 3,\n            deterministic: true,\n        });\n\n        funcs\n    }\n}\n\npub struct FunctionListEntry {\n    pub name: String,\n    pub func_type: &'static str, // \"s\" = scalar, \"a\" = aggregate, \"w\" = window\n    pub narg: i32,               // -1 = variable\n    pub deterministic: bool,\n}\n"
  },
  {
    "path": "core/functions/datetime.rs",
    "content": "use crate::numeric::Numeric;\nuse crate::types::AsValueRef;\nuse crate::types::Value;\nuse crate::LimboError::InvalidModifier;\nuse crate::{Result, ValueRef};\n// chrono isn't used more due to incompatibility with sqlite\nuse chrono::{Local, Offset, TimeZone};\nuse std::borrow::Cow;\nuse std::fmt::Write;\n\nconst JD_TO_MS: i64 = 86_400_000;\nconst MAX_JD: i64 = 464269060799999; // 9999-12-31 23:59:59.999\n\n#[derive(Debug, Clone, Copy)]\nstruct DateTime {\n    i_jd: i64, // The julian day number times 86400000\n    y: i32,\n    m: i32,\n    d: i32,\n    h: i32,\n    min: i32,\n    s: f64,\n    tz: i32, // Timezone offset in minutes\n    n_floor: i32,\n    valid_jd: bool,\n    valid_ymd: bool,\n    valid_hms: bool,\n    raw_s: bool, // Raw numeric value stored in s\n    is_error: bool,\n    use_subsec: bool,\n    is_utc: bool,\n    is_local: bool,\n}\n\nimpl Default for DateTime {\n    fn default() -> Self {\n        DateTime {\n            i_jd: 0,\n            y: 2000,\n            m: 1,\n            d: 1,\n            h: 0,\n            min: 0,\n            s: 0.0,\n            tz: 0,\n            n_floor: 0,\n            valid_jd: false,\n            valid_ymd: false,\n            valid_hms: false,\n            raw_s: false,\n            is_error: false,\n            use_subsec: false,\n            is_utc: false,\n            is_local: false,\n        }\n    }\n}\n\nimpl DateTime {\n    fn set_error(&mut self) {\n        *self = DateTime::default();\n        self.is_error = true;\n    }\n\n    fn compute_jd(&mut self) {\n        if self.valid_jd {\n            return;\n        }\n        let mut y: i32;\n        let mut m: i32;\n        let d: i32;\n        if self.valid_ymd {\n            y = self.y;\n            m = self.m;\n            d = self.d;\n        } else {\n            y = 2000;\n            m = 1;\n            d = 1;\n        }\n        if !(-4713..=9999).contains(&y) || self.raw_s {\n            self.set_error();\n            return;\n        }\n        if m <= 2 {\n            y -= 1;\n            m += 12;\n        }\n        let a = (y + 4800) / 100;\n        let b = 38 - a + (a / 4);\n        let x1 = 36525 * (y + 4716) / 100;\n        let x2 = 306001 * (m + 1) / 10000;\n        self.i_jd = (x1 as i64 + x2 as i64 + d as i64 + b as i64) * 86400000 - 131716800000;\n        self.valid_jd = true;\n        if self.valid_hms {\n            self.i_jd += self.h as i64 * 3_600_000\n                + self.min as i64 * 60_000\n                + (self.s * 1000.0 + 0.5) as i64;\n            if self.tz != 0 {\n                self.i_jd -= self.tz as i64 * 60_000;\n                self.valid_ymd = false;\n                self.valid_hms = false;\n                self.tz = 0;\n                self.is_utc = true;\n                self.is_local = false;\n            }\n        }\n    }\n\n    fn compute_ymd(&mut self) {\n        if self.valid_ymd {\n            return;\n        }\n        if !self.valid_jd {\n            self.y = 2000;\n            self.m = 1;\n            self.d = 1;\n        } else if self.i_jd < 0 || self.i_jd > MAX_JD {\n            self.set_error();\n            return;\n        } else {\n            let z = ((self.i_jd + 43200000) / JD_TO_MS) as i32;\n            let alpha = ((z as f64 + 32044.75) / 36524.25) as i32 - 52;\n            let a = z + 1 + alpha - ((alpha + 100) / 4) + 25;\n            let b = a + 1524;\n            let c = ((b as f64 - 122.1) / 365.25) as i32;\n            let d_calc = (36525 * (c & 32767)) / 100;\n            let e = ((b - d_calc) as f64 / 30.6001) as i32;\n            let x1 = (30.6001 * e as f64) as i32;\n\n            self.d = b - d_calc - x1;\n            self.m = if e < 14 { e - 1 } else { e - 13 };\n            self.y = if self.m > 2 { c - 4716 } else { c - 4715 };\n        }\n        self.valid_ymd = true;\n    }\n\n    fn compute_hms(&mut self) {\n        if self.valid_hms {\n            return;\n        }\n        self.compute_jd();\n        let day_ms = ((self.i_jd + 43200000) % 86400000) as i32;\n        self.s = (day_ms % 60000) as f64 / 1000.0;\n        let day_min = day_ms / 60000;\n        self.min = day_min % 60;\n        self.h = day_min / 60;\n        self.raw_s = false;\n        self.valid_hms = true;\n    }\n\n    fn compute_ymd_hms(&mut self) {\n        self.compute_ymd();\n        self.compute_hms();\n    }\n\n    fn clear_ymd_hms_tz(&mut self) {\n        self.valid_ymd = false;\n        self.valid_hms = false;\n        self.tz = 0;\n    }\n\n    fn compute_floor(&mut self) {\n        assert!(self.valid_ymd || self.is_error);\n        assert!(self.d >= 0 && self.d <= 31);\n        assert!(self.m >= 0 && self.m <= 12);\n        if self.d <= 28 || ((1 << self.m) & 0x15aa) != 0 {\n            self.n_floor = 0;\n        } else if self.m != 2 {\n            self.n_floor = if self.d == 31 { 1 } else { 0 };\n        } else if self.y % 4 != 0 || (self.y % 100 == 0 && self.y % 400 != 0) {\n            self.n_floor = self.d - 28;\n        } else {\n            self.n_floor = self.d - 29;\n        }\n    }\n}\n\nfn get_digits(z: &str, digits: usize, min_val: i32, max_val: i32) -> Option<(i32, &str)> {\n    if z.len() < digits {\n        return None;\n    }\n    if !z.is_char_boundary(digits) {\n        return None;\n    }\n    let bytes = z.as_bytes();\n    if !bytes.iter().take(digits).all(|b| b.is_ascii_digit()) {\n        return None;\n    }\n    let slice = &z[..digits];\n    let val = slice.parse::<i32>().ok()?;\n    if val < min_val || val > max_val {\n        return None;\n    }\n    Some((val, &z[digits..]))\n}\n\nfn set_to_current(p: &mut DateTime) {\n    let now = std::time::SystemTime::now();\n    let duration = now\n        .duration_since(std::time::UNIX_EPOCH)\n        .unwrap_or_default();\n    const UNIX_EPOCH_IJD: i64 = 210866760000000;\n    p.i_jd = UNIX_EPOCH_IJD + duration.as_millis() as i64;\n    p.valid_jd = true;\n    p.is_utc = true;\n    p.is_local = false;\n    p.clear_ymd_hms_tz();\n}\n\nfn parse_date_or_time(value: &str, p: &mut DateTime) -> Result<()> {\n    if parse_yyyy_mm_dd(value, p) {\n        return Ok(());\n    }\n    if parse_hh_mm_ss(value, p) {\n        return Ok(());\n    }\n    if value.eq_ignore_ascii_case(\"now\") {\n        set_to_current(p);\n        return Ok(());\n    }\n    if let Ok(val) = value.parse::<f64>() {\n        p.s = val;\n        p.raw_s = true;\n        if (0.0..5373484.5).contains(&val) {\n            p.i_jd = (val * JD_TO_MS as f64 + 0.5) as i64;\n            p.valid_jd = true;\n        }\n        return Ok(());\n    }\n    if value.eq_ignore_ascii_case(\"subsec\") || value.eq_ignore_ascii_case(\"subsecond\") {\n        p.use_subsec = true;\n        set_to_current(p);\n        return Ok(());\n    }\n    Err(crate::LimboError::InvalidModifier(\"Parse Failed\".into()))\n}\n\nfn parse_yyyy_mm_dd(mut z: &str, p: &mut DateTime) -> bool {\n    let y: i32;\n    let m: i32;\n    let d: i32;\n    let neg: bool;\n\n    if z.starts_with('-') {\n        z = &z[1..];\n        neg = true;\n    } else {\n        neg = false;\n    }\n\n    if let Some((val, rem)) = get_digits(z, 4, 0, 9999) {\n        y = val;\n        z = rem;\n    } else {\n        return false;\n    }\n\n    if !z.starts_with('-') {\n        return false;\n    }\n    z = &z[1..];\n\n    if let Some((val, rem)) = get_digits(z, 2, 1, 12) {\n        m = val;\n        z = rem;\n    } else {\n        return false;\n    }\n\n    if !z.starts_with('-') {\n        return false;\n    }\n    z = &z[1..];\n\n    if let Some((val, rem)) = get_digits(z, 2, 1, 31) {\n        d = val;\n        z = rem;\n    } else {\n        return false;\n    }\n\n    while !z.is_empty() {\n        let c = z.as_bytes()[0] as char;\n        if c.is_ascii_whitespace() || c == 'T' {\n            z = &z[1..];\n        } else {\n            break;\n        }\n    }\n\n    if parse_hh_mm_ss(z, p) {\n    } else if z.is_empty() {\n        p.valid_hms = false;\n    } else {\n        return false;\n    }\n\n    p.valid_jd = false;\n    p.valid_ymd = true;\n    p.y = if neg { -y } else { y };\n    p.m = m;\n    p.d = d;\n\n    p.compute_floor();\n\n    if p.tz != 0 {\n        p.compute_jd();\n    }\n    true\n}\n\nfn parse_hh_mm_ss(mut z: &str, p: &mut DateTime) -> bool {\n    let h: i32;\n    let m: i32;\n    let s: i32;\n    let mut ms: f64 = 0.0;\n\n    if let Some((val, rem)) = get_digits(z, 2, 0, 24) {\n        h = val;\n        z = rem;\n    } else {\n        return false;\n    }\n\n    if !z.starts_with(':') {\n        return false;\n    }\n    z = &z[1..];\n\n    if let Some((val, rem)) = get_digits(z, 2, 0, 59) {\n        m = val;\n        z = rem;\n    } else {\n        return false;\n    }\n\n    if z.starts_with(':') {\n        z = &z[1..];\n\n        if let Some((val, rem)) = get_digits(z, 2, 0, 59) {\n            s = val;\n            z = rem;\n        } else {\n            return false;\n        }\n\n        if z.starts_with('.') && z.len() > 1 && z.as_bytes()[1].is_ascii_digit() {\n            let mut r_scale = 1.0;\n            z = &z[1..]; // Skip '.'\n\n            while !z.is_empty() && z.as_bytes()[0].is_ascii_digit() {\n                let digit = (z.as_bytes()[0] - b'0') as f64;\n                ms = ms * 10.0 + digit;\n                r_scale *= 10.0;\n                z = &z[1..];\n            }\n            ms /= r_scale;\n\n            if ms > 0.999 {\n                ms = 0.999;\n            }\n        }\n    } else {\n        s = 0;\n    }\n\n    p.valid_jd = false;\n    p.raw_s = false;\n    p.valid_hms = true;\n    p.h = h;\n    p.min = m;\n    p.s = s as f64 + ms;\n\n    if parse_timezone(z, p) {\n        return false;\n    }\n    true\n}\n\nfn parse_timezone(mut z: &str, p: &mut DateTime) -> bool {\n    while !z.is_empty() {\n        let c = z.as_bytes()[0] as char;\n        if c.is_ascii_whitespace() {\n            z = &z[1..];\n        } else {\n            break;\n        }\n    }\n\n    p.tz = 0;\n\n    if z.is_empty() {\n        return false;\n    }\n\n    let c = z.as_bytes()[0] as char;\n    let sgn: i32;\n\n    if c == '-' {\n        sgn = -1;\n    } else if c == '+' {\n        sgn = 1;\n    } else if c == 'Z' || c == 'z' {\n        z = &z[1..];\n        p.is_local = false;\n        p.is_utc = true;\n        return check_trailing_garbage(z);\n    } else {\n        return true;\n    }\n\n    z = &z[1..];\n\n    let n_hr: i32;\n    if let Some((val, rem)) = get_digits(z, 2, 0, 14) {\n        n_hr = val;\n        z = rem;\n    } else {\n        return true;\n    }\n\n    if !z.starts_with(':') {\n        return true;\n    }\n    z = &z[1..];\n\n    let n_mn: i32;\n    if let Some((val, rem)) = get_digits(z, 2, 0, 59) {\n        n_mn = val;\n        z = rem;\n    } else {\n        return true;\n    }\n\n    p.tz = sgn * (n_mn + n_hr * 60);\n\n    if p.tz == 0 {\n        p.is_local = false;\n        p.is_utc = true;\n    }\n\n    check_trailing_garbage(z)\n}\n\n// Helper to mimic the \"zulu_time\" label logic:\n// while( sqlite3Isspace(*zDate) ){ zDate++; }\n// return *zDate!=0;\nfn check_trailing_garbage(mut z: &str) -> bool {\n    while !z.is_empty() {\n        let c = z.as_bytes()[0] as char;\n        if c.is_ascii_whitespace() {\n            z = &z[1..];\n        } else {\n            break;\n        }\n    }\n    // Return true if garbage remains (Error), false if empty (Success)\n    !z.is_empty()\n}\n\nfn auto_adjust_date(p: &mut DateTime) {\n    if !p.raw_s || p.valid_jd {\n        p.raw_s = false;\n    } else if p.s >= -210866760000.0 && p.s <= 253402300799.0 {\n        let r = p.s * 1000.0 + 210866760000000.0;\n        p.i_jd = (r + 0.5) as i64;\n        p.valid_jd = true;\n        p.raw_s = false;\n        p.clear_ymd_hms_tz();\n    }\n}\n\nfn parse_modifier(p: &mut DateTime, z: &str, idx: usize) -> Result<()> {\n    let mut chars = z.chars();\n    let first_char = match chars.next() {\n        Some(c) => c.to_ascii_lowercase(),\n        None => return Err(InvalidModifier(format!(\"Unknown modifier: {z}\"))),\n    };\n\n    match first_char {\n        'a' if z.eq_ignore_ascii_case(\"auto\") => {\n            if idx > 0 {\n                return Err(InvalidModifier(format!(\n                    \"Modifier 'auto' must be first: {z}\"\n                )));\n            }\n            auto_adjust_date(p);\n            Ok(())\n        }\n        'c' if z.eq_ignore_ascii_case(\"ceiling\") => {\n            p.compute_jd();\n            p.clear_ymd_hms_tz();\n            p.n_floor = 0;\n            Ok(())\n        }\n        'f' if z.eq_ignore_ascii_case(\"floor\") => {\n            p.compute_jd();\n            if p.n_floor != 0 {\n                p.i_jd -= p.n_floor as i64 * JD_TO_MS;\n                p.n_floor = 0;\n            }\n            p.clear_ymd_hms_tz();\n            Ok(())\n        }\n        'j' if z.eq_ignore_ascii_case(\"julianday\") => {\n            if idx > 0 {\n                return Err(InvalidModifier(format!(\n                    \"Modifier 'julianday' must be first: {z}\"\n                )));\n            }\n            if p.valid_jd && p.raw_s {\n                p.raw_s = false;\n                Ok(())\n            } else {\n                Err(InvalidModifier(format!(\n                    \"Invalid use of julianday modifier: {z}\"\n                )))\n            }\n        }\n        'l' if z.eq_ignore_ascii_case(\"localtime\") => {\n            if !p.is_local {\n                p.compute_jd();\n                let timestamp = (p.i_jd - 210866760000000) / 1000;\n                let offset_sec = match Local.timestamp_opt(timestamp, 0) {\n                    chrono::LocalResult::Single(dt) => dt.offset().fix().local_minus_utc(),\n                    _ => 0,\n                };\n                p.i_jd += (offset_sec as i64) * 1000;\n                p.clear_ymd_hms_tz();\n                p.is_local = true;\n                p.is_utc = false;\n            }\n            Ok(())\n        }\n        'u' if z.eq_ignore_ascii_case(\"unixepoch\") => {\n            if idx > 0 {\n                return Err(InvalidModifier(format!(\n                    \"Modifier 'unixepoch' must be first: {z}\"\n                )));\n            }\n            if p.raw_s {\n                let r = p.s * 1000.0 + 210866760000000.0;\n                p.i_jd = (r + 0.5) as i64;\n                p.valid_jd = true;\n                p.raw_s = false;\n                p.clear_ymd_hms_tz();\n                Ok(())\n            } else {\n                Err(InvalidModifier(format!(\n                    \"Invalid use of unixepoch modifier: {z}\"\n                )))\n            }\n        }\n        'u' if z.eq_ignore_ascii_case(\"utc\") => {\n            if !p.is_utc {\n                p.compute_jd();\n                let timestamp = (p.i_jd - 210866760000000) / 1000;\n                let offset_sec = match Local.timestamp_opt(timestamp, 0) {\n                    chrono::LocalResult::Single(dt) => dt.offset().fix().local_minus_utc(),\n                    _ => 0,\n                };\n                p.i_jd -= (offset_sec as i64) * 1000;\n                p.clear_ymd_hms_tz();\n                p.is_utc = true;\n                p.is_local = false;\n            }\n            Ok(())\n        }\n        'w' if z\n            .get(..8)\n            .is_some_and(|s| s.eq_ignore_ascii_case(\"weekday \")) =>\n        {\n            if let Ok(val) = z[8..].trim().parse::<f64>() {\n                if (0.0..7.0).contains(&val) && (val as i64 as f64) == val {\n                    let n = val as i64;\n                    p.compute_ymd_hms();\n                    p.valid_jd = false;\n                    p.compute_jd();\n                    let mut z = ((p.i_jd + 129600000) / 86400000) % 7;\n                    if z > n {\n                        z -= 7;\n                    }\n                    p.i_jd += (n - z) * 86400000;\n                    p.clear_ymd_hms_tz();\n                    return Ok(());\n                }\n            }\n            Err(InvalidModifier(format!(\"Invalid weekday: {z}\")))\n        }\n        's' => {\n            if z.eq_ignore_ascii_case(\"subsec\") || z.eq_ignore_ascii_case(\"subsecond\") {\n                p.use_subsec = true;\n                Ok(())\n            } else if z\n                .get(..9)\n                .is_some_and(|s| s.eq_ignore_ascii_case(\"start of \"))\n            {\n                if !p.valid_jd && !p.valid_ymd && !p.valid_hms {\n                    return Err(InvalidModifier(format!(\"Invalid start of: {z}\")));\n                }\n                p.compute_ymd();\n                p.valid_hms = true;\n                p.h = 0;\n                p.min = 0;\n                p.s = 0.0;\n                p.raw_s = false;\n                p.valid_jd = false;\n                p.tz = 0;\n                p.n_floor = 0;\n\n                let suffix = &z[9..];\n                if suffix.eq_ignore_ascii_case(\"month\") {\n                    p.d = 1;\n                    Ok(())\n                } else if suffix.eq_ignore_ascii_case(\"year\") {\n                    p.m = 1;\n                    p.d = 1;\n                    Ok(())\n                } else if suffix.eq_ignore_ascii_case(\"day\") {\n                    Ok(())\n                } else {\n                    Err(InvalidModifier(format!(\"Invalid start of: {z}\")))\n                }\n            } else {\n                Err(InvalidModifier(format!(\"Unknown modifier: {z}\")))\n            }\n        }\n        '+' | '-' | '0'..='9' => parse_arithmetic_modifier(p, z),\n        _ => Err(InvalidModifier(format!(\"Unknown modifier: {z}\"))),\n    }\n}\n\nfn parse_arithmetic_modifier(p: &mut DateTime, z: &str) -> Result<()> {\n    let z = z.trim();\n    let is_neg = z.starts_with('-');\n    let sign = if is_neg { -1 } else { 1 };\n\n    let clean_z = if z.starts_with('+') || z.starts_with('-') {\n        &z[1..]\n    } else {\n        z\n    };\n\n    // Case 1: YYYY-MM-DD Arithmetic\n    if clean_z.len() >= 10\n        && clean_z.as_bytes().get(4) == Some(&b'-')\n        && clean_z.as_bytes().get(7) == Some(&b'-')\n        && clean_z.is_char_boundary(4)\n        && clean_z.is_char_boundary(5)\n        && clean_z.is_char_boundary(7)\n        && clean_z.is_char_boundary(8)\n        && clean_z.is_char_boundary(10)\n    {\n        let y_res = get_digits(&clean_z[0..4], 4, 0, 9999);\n        let m_res = get_digits(&clean_z[5..7], 2, 0, 11);\n        let d_res = get_digits(&clean_z[8..10], 2, 0, 30);\n\n        if let (Some((y, _)), Some((m, _)), Some((d, _))) = (y_res, m_res, d_res) {\n            let rem = &clean_z[10..];\n            let mut valid_format = true;\n            let mut time_str = None;\n\n            if !rem.is_empty() {\n                if rem.starts_with(' ') {\n                    time_str = Some(rem.trim_start());\n                } else {\n                    valid_format = false;\n                }\n            }\n\n            if valid_format {\n                p.compute_ymd_hms();\n                p.valid_jd = false;\n\n                let y_adj = y as i64;\n                let m_adj = m as i64;\n                let d_adj = d as i64;\n\n                if is_neg {\n                    p.y = p.y.wrapping_sub(y_adj as i32);\n                    p.m = p.m.wrapping_sub(m_adj as i32);\n                } else {\n                    p.y = p.y.wrapping_add(y_adj as i32);\n                    p.m = p.m.wrapping_add(m_adj as i32);\n                }\n\n                // Normalize months\n                let m_current = p.m as i64;\n                let x = if m_current > 0 {\n                    (m_current - 1) / 12\n                } else {\n                    (m_current - 12) / 12\n                };\n                p.y = p.y.wrapping_add(x as i32);\n                p.m = (m_current - x * 12) as i32;\n\n                p.compute_floor();\n                p.compute_jd();\n\n                // Apply day offset\n                let day_diff = if is_neg { -d_adj } else { d_adj };\n                p.i_jd = p.i_jd.wrapping_add(day_diff.wrapping_mul(JD_TO_MS));\n\n                // Apply time offset if present\n                if let Some(t_val) = time_str {\n                    let mut tx = DateTime::default();\n                    if parse_hh_mm_ss(t_val, &mut tx) {\n                        tx.compute_jd();\n                        let ms = (tx.h as i64 * 3600000)\n                            + (tx.min as i64 * 60000)\n                            + (tx.s * 1000.0) as i64;\n                        p.i_jd = p.i_jd.wrapping_add((sign as i64).wrapping_mul(ms));\n                    } else {\n                        // If time parsing failed, the whole modifier is invalid\n                        return Err(InvalidModifier(format!(\n                            \"Invalid time in arithmetic modifier: {z}\"\n                        )));\n                    }\n                }\n\n                p.clear_ymd_hms_tz();\n                return Ok(());\n            }\n        }\n    }\n\n    // Case 2: HH:MM:SS Arithmetic\n    if z.contains(':') {\n        let mut tx = DateTime::default();\n        let time_str = if z.starts_with('+') || z.starts_with('-') {\n            &z[1..]\n        } else {\n            z\n        };\n        if parse_hh_mm_ss(time_str, &mut tx) {\n            tx.compute_jd();\n            let ms = (tx.h as i64 * 3600000) + (tx.min as i64 * 60000) + (tx.s * 1000.0) as i64;\n            p.compute_jd();\n            p.i_jd = p.i_jd.wrapping_add((sign as i64).wrapping_mul(ms));\n            p.clear_ymd_hms_tz();\n            return Ok(());\n        }\n    }\n\n    // Case 3: NNN Units\n    let mut parts = z.split_whitespace();\n    if let Some(val_str) = parts.next() {\n        if let Ok(val) = val_str.parse::<f64>() {\n            if let Some(unit) = parts.next() {\n                let limit_check = |v: f64, limit: f64| v.abs() < limit;\n                if unit.eq_ignore_ascii_case(\"day\") || unit.eq_ignore_ascii_case(\"days\") {\n                    if !limit_check(val, 5373485.0) {\n                        return Err(InvalidModifier(format!(\"Modifier out of range: {z}\")));\n                    }\n                    p.compute_jd();\n                    let ms = val * 86400000.0;\n                    let rounder = if ms < 0.0 { -0.5 } else { 0.5 };\n                    p.i_jd = p.i_jd.wrapping_add((ms + rounder) as i64);\n                    p.n_floor = 0;\n                    p.clear_ymd_hms_tz();\n                    return Ok(());\n                } else if unit.eq_ignore_ascii_case(\"hour\") || unit.eq_ignore_ascii_case(\"hours\") {\n                    if !limit_check(val, 1.2897e+11) {\n                        return Err(InvalidModifier(format!(\"Modifier out of range: {z}\")));\n                    }\n                    p.compute_jd();\n                    let ms = val * 3600000.0;\n                    let rounder = if ms < 0.0 { -0.5 } else { 0.5 };\n                    p.i_jd = p.i_jd.wrapping_add((ms + rounder) as i64);\n                    p.n_floor = 0;\n                    p.clear_ymd_hms_tz();\n                    return Ok(());\n                } else if unit.eq_ignore_ascii_case(\"minute\")\n                    || unit.eq_ignore_ascii_case(\"minutes\")\n                {\n                    if !limit_check(val, 7.7379e+12) {\n                        return Err(InvalidModifier(format!(\"Modifier out of range: {z}\")));\n                    }\n                    p.compute_jd();\n                    let ms = val * 60000.0;\n                    let rounder = if ms < 0.0 { -0.5 } else { 0.5 };\n                    p.i_jd = p.i_jd.wrapping_add((ms + rounder) as i64);\n                    p.n_floor = 0;\n                    p.clear_ymd_hms_tz();\n                    return Ok(());\n                } else if unit.eq_ignore_ascii_case(\"second\")\n                    || unit.eq_ignore_ascii_case(\"seconds\")\n                {\n                    if !limit_check(val, 4.6427e+14) {\n                        return Err(InvalidModifier(format!(\"Modifier out of range: {z}\")));\n                    }\n                    p.compute_jd();\n                    let ms = val * 1000.0;\n                    let rounder = if ms < 0.0 { -0.5 } else { 0.5 };\n                    p.i_jd = p.i_jd.wrapping_add((ms + rounder) as i64);\n                    p.n_floor = 0;\n                    p.clear_ymd_hms_tz();\n                    return Ok(());\n                } else if unit.eq_ignore_ascii_case(\"month\") || unit.eq_ignore_ascii_case(\"months\")\n                {\n                    if !limit_check(val, 176546.0) {\n                        return Err(InvalidModifier(format!(\"Modifier out of range: {z}\")));\n                    }\n                    p.compute_ymd_hms();\n                    let int_months = val as i64;\n                    let frac_months = val - int_months as f64;\n\n                    let total_months = (p.m as i64) + int_months;\n                    let x = if total_months > 0 {\n                        (total_months - 1) / 12\n                    } else {\n                        (total_months - 12) / 12\n                    };\n                    p.y = p.y.wrapping_add(x as i32);\n                    p.m = (total_months - x * 12) as i32;\n\n                    p.compute_floor();\n                    p.valid_jd = false;\n                    p.compute_jd();\n\n                    if frac_months.abs() > f64::EPSILON {\n                        let ms = frac_months * 30.0 * JD_TO_MS as f64;\n                        let rounder = if ms < 0.0 { -0.5 } else { 0.5 };\n                        p.i_jd = p.i_jd.wrapping_add((ms + rounder) as i64);\n                    }\n                    p.clear_ymd_hms_tz();\n                    return Ok(());\n                } else if unit.eq_ignore_ascii_case(\"year\") || unit.eq_ignore_ascii_case(\"years\") {\n                    if !limit_check(val, 14713.0) {\n                        return Err(InvalidModifier(format!(\"Modifier out of range: {z}\")));\n                    }\n                    p.compute_ymd_hms();\n                    let int_years = val as i64;\n                    let frac_years = val - int_years as f64;\n\n                    p.y = p.y.wrapping_add(int_years as i32);\n\n                    p.compute_floor();\n                    p.valid_jd = false;\n                    p.compute_jd();\n\n                    if frac_years.abs() > f64::EPSILON {\n                        let ms = frac_years * 365.0 * JD_TO_MS as f64;\n                        let rounder = if ms < 0.0 { -0.5 } else { 0.5 };\n                        p.i_jd = p.i_jd.wrapping_add((ms + rounder) as i64);\n                    }\n                    p.clear_ymd_hms_tz();\n                    return Ok(());\n                }\n            }\n        }\n    }\n\n    Err(InvalidModifier(format!(\"Invalid arithmetic modifier: {z}\")))\n}\n\npub fn exec_datetime_general<I, E, V>(values: I, func_type: &str) -> Value\nwhere\n    V: AsValueRef,\n    E: ExactSizeIterator<Item = V>,\n    I: IntoIterator<IntoIter = E, Item = V>,\n{\n    let mut values = values.into_iter();\n    let mut p = DateTime::default();\n    let mut has_modifier = false;\n\n    if values.len() == 0 {\n        set_to_current(&mut p);\n    } else {\n        let first = values.next().unwrap();\n        match first.as_value_ref() {\n            ValueRef::Text(s) => {\n                if parse_date_or_time(s.as_str(), &mut p).is_err() {\n                    return Value::Null;\n                }\n            }\n            ValueRef::Numeric(Numeric::Integer(i)) => {\n                p.s = i as f64;\n                p.raw_s = true;\n                if p.s >= 0.0 && p.s < 5373484.5 {\n                    p.i_jd = (p.s * JD_TO_MS as f64 + 0.5) as i64;\n                    p.valid_jd = true;\n                }\n            }\n            ValueRef::Numeric(Numeric::Float(f)) => {\n                p.s = f64::from(f);\n                p.raw_s = true;\n                if p.s >= 0.0 && p.s < 5373484.5 {\n                    p.i_jd = (p.s * JD_TO_MS as f64 + 0.5) as i64;\n                    p.valid_jd = true;\n                }\n            }\n            _ => return Value::Null,\n        }\n    }\n\n    for (i, val) in values.enumerate() {\n        has_modifier = true;\n        if let ValueRef::Text(s) = val.as_value_ref() {\n            if parse_modifier(&mut p, s.as_str(), i).is_err() {\n                return Value::Null;\n            }\n        } else {\n            return Value::Null;\n        }\n    }\n\n    p.compute_jd();\n    if p.is_error || p.i_jd < 0 || p.i_jd > MAX_JD {\n        return Value::Null;\n    }\n\n    if !has_modifier && p.valid_ymd && p.d > 28 {\n        p.valid_ymd = false;\n    }\n\n    match func_type {\n        \"julianday\" => Value::from_f64(p.i_jd as f64 / 86400000.0),\n        \"unixepoch\" => {\n            let unix = (p.i_jd - 210866760000000) / 1000;\n            if p.use_subsec {\n                let ms = (p.i_jd - 210866760000000) as f64 / 1000.0;\n                Value::from_f64(ms)\n            } else {\n                Value::from_i64(unix)\n            }\n        }\n        _ => {\n            p.compute_ymd_hms();\n            if p.is_error {\n                return Value::Null;\n            }\n\n            let mut res = String::new();\n            if func_type == \"date\" {\n                if p.y < 0 {\n                    write!(res, \"-{:04}-{:02}-{:02}\", p.y.abs(), p.m, p.d).unwrap();\n                } else {\n                    write!(res, \"{:04}-{:02}-{:02}\", p.y, p.m, p.d).unwrap();\n                }\n            } else if func_type == \"time\" {\n                write!(res, \"{:02}:{:02}\", p.h, p.min).unwrap();\n                if p.use_subsec {\n                    write!(res, \":{:06.3}\", p.s).unwrap();\n                } else {\n                    write!(res, \":{:02}\", p.s as i32).unwrap();\n                }\n            } else {\n                if p.y < 0 {\n                    write!(\n                        res,\n                        \"-{:04}-{:02}-{:02} {:02}:{:02}\",\n                        p.y.abs(),\n                        p.m,\n                        p.d,\n                        p.h,\n                        p.min\n                    )\n                    .unwrap();\n                } else {\n                    write!(\n                        res,\n                        \"{:04}-{:02}-{:02} {:02}:{:02}\",\n                        p.y, p.m, p.d, p.h, p.min\n                    )\n                    .unwrap();\n                }\n\n                if p.use_subsec {\n                    write!(res, \":{:06.3}\", p.s).unwrap();\n                } else {\n                    write!(res, \":{:02}\", p.s as i32).unwrap();\n                }\n            }\n            Value::from_text(res)\n        }\n    }\n}\n\npub fn exec_date<I, E, V>(values: I) -> Value\nwhere\n    V: AsValueRef,\n    E: ExactSizeIterator<Item = V>,\n    I: IntoIterator<IntoIter = E, Item = V>,\n{\n    exec_datetime_general(values, \"date\")\n}\n\npub fn exec_time<I, E, V>(values: I) -> Value\nwhere\n    V: AsValueRef,\n    E: ExactSizeIterator<Item = V>,\n    I: IntoIterator<IntoIter = E, Item = V>,\n{\n    exec_datetime_general(values, \"time\")\n}\n\npub fn exec_datetime_full<I, E, V>(values: I) -> Value\nwhere\n    V: AsValueRef,\n    E: ExactSizeIterator<Item = V>,\n    I: IntoIterator<IntoIter = E, Item = V>,\n{\n    exec_datetime_general(values, \"datetime\")\n}\n\npub fn exec_julianday<I, E, V>(values: I) -> Value\nwhere\n    V: AsValueRef,\n    E: ExactSizeIterator<Item = V>,\n    I: IntoIterator<IntoIter = E, Item = V>,\n{\n    exec_datetime_general(values, \"julianday\")\n}\n\npub fn exec_unixepoch<I, E, V>(values: I) -> Value\nwhere\n    V: AsValueRef,\n    E: ExactSizeIterator<Item = V>,\n    I: IntoIterator<IntoIter = E, Item = V>,\n{\n    exec_datetime_general(values, \"unixepoch\")\n}\n\npub fn exec_timediff<I, E, V>(values: I) -> Value\nwhere\n    V: AsValueRef,\n    E: ExactSizeIterator<Item = V>,\n    I: IntoIterator<IntoIter = E, Item = V>,\n{\n    let mut values = values.into_iter();\n    if values.len() < 2 {\n        return Value::Null;\n    }\n\n    let mut d1 = DateTime::default();\n    let mut d2 = DateTime::default();\n\n    // Parse first argument (d1)\n    let val1 = values.next().unwrap();\n    match val1.as_value_ref() {\n        ValueRef::Text(s) => {\n            if parse_date_or_time(s.as_str(), &mut d1).is_err() {\n                return Value::Null;\n            }\n        }\n        ValueRef::Numeric(Numeric::Integer(i)) => {\n            d1.s = i as f64;\n            d1.raw_s = true;\n            if d1.s >= 0.0 && d1.s < 5373484.5 {\n                d1.i_jd = (d1.s * JD_TO_MS as f64 + 0.5) as i64;\n                d1.valid_jd = true;\n            }\n        }\n        ValueRef::Numeric(Numeric::Float(f)) => {\n            d1.s = f64::from(f);\n            d1.raw_s = true;\n            if d1.s >= 0.0 && d1.s < 5373484.5 {\n                d1.i_jd = (d1.s * JD_TO_MS as f64 + 0.5) as i64;\n                d1.valid_jd = true;\n            }\n        }\n        _ => return Value::Null,\n    }\n\n    // Parse second argument (d2)\n    let val2 = values.next().unwrap();\n    match val2.as_value_ref() {\n        ValueRef::Text(s) => {\n            if parse_date_or_time(s.as_str(), &mut d2).is_err() {\n                return Value::Null;\n            }\n        }\n        ValueRef::Numeric(Numeric::Integer(i)) => {\n            d2.s = i as f64;\n            d2.raw_s = true;\n            if d2.s >= 0.0 && d2.s < 5373484.5 {\n                d2.i_jd = (d2.s * JD_TO_MS as f64 + 0.5) as i64;\n                d2.valid_jd = true;\n            }\n        }\n        ValueRef::Numeric(Numeric::Float(f)) => {\n            d2.s = f64::from(f);\n            d2.raw_s = true;\n            if d2.s >= 0.0 && d2.s < 5373484.5 {\n                d2.i_jd = (d2.s * JD_TO_MS as f64 + 0.5) as i64;\n                d2.valid_jd = true;\n            }\n        }\n        _ => return Value::Null,\n    }\n\n    d1.compute_jd();\n    d2.compute_jd();\n\n    // Validate inputs after computation\n    if d1.is_error || d2.is_error {\n        return Value::Null;\n    }\n\n    d1.compute_ymd_hms();\n    d2.compute_ymd_hms();\n\n    let sign: char;\n    if d1.i_jd >= d2.i_jd {\n        sign = '+';\n    } else {\n        sign = '-';\n        std::mem::swap(&mut d1, &mut d2);\n    }\n\n    let mut y = d1.y - d2.y;\n    let mut m = d1.m - d2.m;\n\n    if m < 0 {\n        y -= 1;\n        m += 12;\n    }\n\n    let mut temp = d2;\n    temp.y += y;\n    temp.m += m;\n\n    // Normalize months\n    while temp.m > 12 {\n        temp.m -= 12;\n        temp.y += 1;\n    }\n    while temp.m < 1 {\n        temp.m += 12;\n        temp.y -= 1;\n    }\n\n    temp.valid_jd = false;\n    temp.compute_jd();\n\n    // Adjust if the Y/M shift overshot d1\n    while temp.i_jd > d1.i_jd {\n        m -= 1;\n        if m < 0 {\n            m = 11;\n            y -= 1;\n        }\n        temp = d2;\n        temp.y += y;\n        temp.m += m;\n        while temp.m > 12 {\n            temp.m -= 12;\n            temp.y += 1;\n        }\n        while temp.m < 1 {\n            temp.m += 12;\n            temp.y -= 1;\n        }\n        temp.valid_jd = false;\n        temp.compute_jd();\n    }\n\n    let diff_ms = d1.i_jd - temp.i_jd;\n    let days = diff_ms / 86400000;\n    let rem_ms = diff_ms % 86400000;\n    let hours = rem_ms / 3600000;\n    let rem_ms = rem_ms % 3600000;\n    let mins = rem_ms / 60000;\n    let rem_ms = rem_ms % 60000;\n    let secs = rem_ms as f64 / 1000.0;\n\n    let mut res = String::new();\n    write!(\n        res,\n        \"{sign}{y:04}-{m:02}-{days:02} {hours:02}:{mins:02}:{secs:06.3}\"\n    )\n    .unwrap();\n\n    Value::from_text(res)\n}\n\npub fn exec_strftime<I, E, V>(values: I) -> Value\nwhere\n    V: AsValueRef,\n    E: ExactSizeIterator<Item = V>,\n    I: IntoIterator<IntoIter = E, Item = V>,\n{\n    let mut values = values.into_iter();\n    if values.len() < 1 {\n        return Value::Null;\n    }\n\n    let fmt_val = values.next().unwrap();\n    let fmt_str = match fmt_val.as_value_ref() {\n        ValueRef::Text(s) => Cow::Borrowed(s.as_str()),\n        ValueRef::Null => return Value::Null,\n        val => Cow::Owned(val.to_string()),\n    };\n\n    let mut p = DateTime::default();\n    if values.len() == 0 {\n        set_to_current(&mut p);\n    } else {\n        let init_val = values.next().unwrap();\n        match init_val.as_value_ref() {\n            ValueRef::Text(s) => {\n                let s_str = s.as_str();\n                if s_str.eq_ignore_ascii_case(\"now\") {\n                    set_to_current(&mut p);\n                } else if let Ok(val) = s_str.parse::<f64>() {\n                    p.s = val;\n                    p.raw_s = true;\n                    if p.s >= 0.0 && p.s < 5373484.5 {\n                        p.i_jd = (p.s * JD_TO_MS as f64 + 0.5) as i64;\n                        p.valid_jd = true;\n                    }\n                } else {\n                    let mut temp_p = DateTime::default();\n                    if parse_date_or_time(s_str, &mut temp_p).is_ok() {\n                        p = temp_p;\n                    } else {\n                        return Value::Null;\n                    }\n                }\n            }\n            ValueRef::Numeric(Numeric::Integer(i)) => {\n                p.s = i as f64;\n                p.raw_s = true;\n                if p.s >= 0.0 && p.s < 5373484.5 {\n                    p.i_jd = (p.s * JD_TO_MS as f64 + 0.5) as i64;\n                    p.valid_jd = true;\n                }\n            }\n            ValueRef::Numeric(Numeric::Float(f)) => {\n                p.s = f64::from(f);\n                p.raw_s = true;\n                if p.s >= 0.0 && p.s < 5373484.5 {\n                    p.i_jd = (p.s * JD_TO_MS as f64 + 0.5) as i64;\n                    p.valid_jd = true;\n                }\n            }\n            _ => return Value::Null,\n        }\n\n        for (i, val) in values.enumerate() {\n            if let ValueRef::Text(s) = val.as_value_ref() {\n                if parse_modifier(&mut p, s.as_str(), i).is_err() {\n                    return Value::Null;\n                }\n            } else {\n                return Value::Null;\n            }\n        }\n    }\n\n    p.compute_jd();\n    if p.is_error {\n        return Value::Null;\n    }\n\n    p.compute_ymd_hms();\n\n    let mut res = String::new();\n    let mut chars = fmt_str.chars().peekable();\n\n    let days_after_jan1 = |curr: &DateTime| -> i64 {\n        let jan1 = DateTime {\n            y: curr.y,\n            m: 1,\n            d: 1,\n            valid_ymd: true,\n            ..Default::default()\n        };\n        let mut j1 = jan1;\n        j1.compute_jd();\n        let curr_norm = DateTime {\n            y: curr.y,\n            m: curr.m,\n            d: curr.d,\n            valid_ymd: true,\n            ..Default::default()\n        };\n        let mut c1 = curr_norm;\n        c1.compute_jd();\n        (c1.i_jd - j1.i_jd) / JD_TO_MS\n    };\n\n    let days_after_mon = |curr: &DateTime| -> i64 { ((curr.i_jd + 43200000) / JD_TO_MS) % 7 };\n    let days_after_sun = |curr: &DateTime| -> i64 { ((curr.i_jd + 129600000) / JD_TO_MS) % 7 };\n\n    while let Some(c) = chars.next() {\n        if c != '%' {\n            res.push(c);\n            continue;\n        }\n\n        match chars.next() {\n            Some('d') => write!(res, \"{:02}\", p.d).unwrap(),\n            Some('e') => write!(res, \"{:2}\", p.d).unwrap(),\n            Some('F') => write!(res, \"{:04}-{:02}-{:02}\", p.y, p.m, p.d).unwrap(),\n            Some('f') => {\n                let mut s = p.s;\n                if s > 59.999 {\n                    s = 59.999;\n                }\n                write!(res, \"{s:06.3}\").unwrap()\n            }\n            Some('g') => {\n                let mut y_iso = p;\n                y_iso.i_jd += (3 - days_after_mon(&p)) * 86400000;\n                y_iso.valid_ymd = false;\n                y_iso.compute_ymd();\n                write!(res, \"{:02}\", y_iso.y % 100).unwrap();\n            }\n            Some('G') => {\n                let mut y_iso = p;\n                y_iso.i_jd += (3 - days_after_mon(&p)) * 86400000;\n                y_iso.valid_ymd = false;\n                y_iso.compute_ymd();\n                write!(res, \"{:04}\", y_iso.y).unwrap();\n            }\n            Some('H') => write!(res, \"{:02}\", p.h).unwrap(),\n            Some('I') => {\n                let h = if p.h % 12 == 0 { 12 } else { p.h % 12 };\n                write!(res, \"{h:02}\").unwrap();\n            }\n            Some('j') => {\n                write!(res, \"{:03}\", days_after_jan1(&p) + 1).unwrap();\n            }\n            Some('J') => {\n                let val = p.i_jd as f64 / 86400000.0;\n                if val.abs() >= 1_000_000.0 && val.abs() < 10_000_000.0 {\n                    let s = format!(\"{val:.9}\");\n                    let trimmed = s.trim_end_matches('0').trim_end_matches('.');\n                    write!(res, \"{trimmed}\").unwrap();\n                } else {\n                    write!(res, \"{val}\").unwrap();\n                }\n            }\n            Some('k') => write!(res, \"{:2}\", p.h).unwrap(),\n            Some('l') => {\n                let h = if p.h % 12 == 0 { 12 } else { p.h % 12 };\n                write!(res, \"{h:2}\").unwrap();\n            }\n            Some('m') => write!(res, \"{:02}\", p.m).unwrap(),\n            Some('M') => write!(res, \"{:02}\", p.min).unwrap(),\n            Some('p') => write!(res, \"{}\", if p.h >= 12 { \"PM\" } else { \"AM\" }).unwrap(),\n            Some('P') => write!(res, \"{}\", if p.h >= 12 { \"pm\" } else { \"am\" }).unwrap(),\n            Some('R') => write!(res, \"{:02}:{:02}\", p.h, p.min).unwrap(),\n            Some('s') => {\n                if p.use_subsec {\n                    write!(res, \"{:.3}\", (p.i_jd - 210866760000000) as f64 / 1000.0).unwrap();\n                } else {\n                    write!(res, \"{}\", (p.i_jd - 210866760000000) / 1000).unwrap();\n                }\n            }\n            Some('S') => write!(res, \"{:02}\", p.s as i32).unwrap(),\n            Some('T') => write!(res, \"{:02}:{:02}:{:02}\", p.h, p.min, p.s as i32).unwrap(),\n            Some('u') => {\n                let mut w = days_after_sun(&p);\n                if w == 0 {\n                    w = 7;\n                }\n                write!(res, \"{w}\").unwrap();\n            }\n            Some('U') => {\n                let w = (days_after_jan1(&p) - days_after_sun(&p) + 7) / 7;\n                write!(res, \"{w:02}\").unwrap();\n            }\n            Some('V') => {\n                let mut temp = p;\n                temp.i_jd += (3 - days_after_mon(&p)) * 86400000;\n                temp.valid_ymd = false;\n                temp.compute_ymd();\n                let w = days_after_jan1(&temp) / 7 + 1;\n                write!(res, \"{w:02}\").unwrap();\n            }\n            Some('w') => {\n                write!(res, \"{}\", days_after_sun(&p)).unwrap();\n            }\n            Some('W') => {\n                let w = (days_after_jan1(&p) - days_after_mon(&p) + 7) / 7;\n                write!(res, \"{w:02}\").unwrap();\n            }\n            Some('Y') => write!(res, \"{:04}\", p.y).unwrap(),\n            Some('%') => res.push('%'),\n            _ => return Value::Null,\n        }\n    }\n\n    Value::from_text(res)\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_valid_get_date_from_time_value() {\n        let now = chrono::Local::now().to_utc().format(\"%Y-%m-%d\").to_string();\n\n        let prev_date_str = \"2024-07-20\";\n        let test_date_str = \"2024-07-21\";\n        let next_date_str = \"2024-07-22\";\n\n        let test_cases: Vec<(Value, &str)> = vec![\n            // Format 1: YYYY-MM-DD (no timezone applicable)\n            (Value::build_text(\"2024-07-21\"), test_date_str),\n            // Format 2: YYYY-MM-DD HH:MM\n            (Value::build_text(\"2024-07-21 22:30\"), test_date_str),\n            (Value::build_text(\"2024-07-21 22:30+02:00\"), test_date_str),\n            (Value::build_text(\"2024-07-21 22:30-05:00\"), next_date_str),\n            (Value::build_text(\"2024-07-21 01:30+05:00\"), prev_date_str),\n            (Value::build_text(\"2024-07-21 22:30Z\"), test_date_str),\n            // Format 3: YYYY-MM-DD HH:MM:SS\n            (Value::build_text(\"2024-07-21 22:30:45\"), test_date_str),\n            (\n                Value::build_text(\"2024-07-21 22:30:45+02:00\"),\n                test_date_str,\n            ),\n            (\n                Value::build_text(\"2024-07-21 22:30:45-05:00\"),\n                next_date_str,\n            ),\n            (\n                Value::build_text(\"2024-07-21 01:30:45+05:00\"),\n                prev_date_str,\n            ),\n            (Value::build_text(\"2024-07-21 22:30:45Z\"), test_date_str),\n            // Format 4: YYYY-MM-DD HH:MM:SS.SSS\n            (Value::build_text(\"2024-07-21 22:30:45.123\"), test_date_str),\n            (\n                Value::build_text(\"2024-07-21 22:30:45.123+02:00\"),\n                test_date_str,\n            ),\n            (\n                Value::build_text(\"2024-07-21 22:30:45.123-05:00\"),\n                next_date_str,\n            ),\n            (\n                Value::build_text(\"2024-07-21 01:30:45.123+05:00\"),\n                prev_date_str,\n            ),\n            (Value::build_text(\"2024-07-21 22:30:45.123Z\"), test_date_str),\n            // Format 5: YYYY-MM-DDTHH:MM\n            (Value::build_text(\"2024-07-21T22:30\"), test_date_str),\n            (Value::build_text(\"2024-07-21T22:30+02:00\"), test_date_str),\n            (Value::build_text(\"2024-07-21T22:30-05:00\"), next_date_str),\n            (Value::build_text(\"2024-07-21T01:30+05:00\"), prev_date_str),\n            (Value::build_text(\"2024-07-21T22:30Z\"), test_date_str),\n            // Format 6: YYYY-MM-DDTHH:MM:SS\n            (Value::build_text(\"2024-07-21T22:30:45\"), test_date_str),\n            (\n                Value::build_text(\"2024-07-21T22:30:45+02:00\"),\n                test_date_str,\n            ),\n            (\n                Value::build_text(\"2024-07-21T22:30:45-05:00\"),\n                next_date_str,\n            ),\n            (\n                Value::build_text(\"2024-07-21T01:30:45+05:00\"),\n                prev_date_str,\n            ),\n            (Value::build_text(\"2024-07-21T22:30:45Z\"), test_date_str),\n            // Format 7: YYYY-MM-DDTHH:MM:SS.SSS\n            (Value::build_text(\"2024-07-21T22:30:45.123\"), test_date_str),\n            (\n                Value::build_text(\"2024-07-21T22:30:45.123+02:00\"),\n                test_date_str,\n            ),\n            (\n                Value::build_text(\"2024-07-21T22:30:45.123-05:00\"),\n                next_date_str,\n            ),\n            (\n                Value::build_text(\"2024-07-21T01:30:45.123+05:00\"),\n                prev_date_str,\n            ),\n            (Value::build_text(\"2024-07-21T22:30:45.123Z\"), test_date_str),\n            // Format 8: HH:MM\n            (Value::build_text(\"22:30\"), \"2000-01-01\"),\n            (Value::build_text(\"22:30+02:00\"), \"2000-01-01\"),\n            (Value::build_text(\"22:30-05:00\"), \"2000-01-02\"),\n            (Value::build_text(\"01:30+05:00\"), \"1999-12-31\"),\n            (Value::build_text(\"22:30Z\"), \"2000-01-01\"),\n            // Format 9: HH:MM:SS\n            (Value::build_text(\"22:30:45\"), \"2000-01-01\"),\n            (Value::build_text(\"22:30:45+02:00\"), \"2000-01-01\"),\n            (Value::build_text(\"22:30:45-05:00\"), \"2000-01-02\"),\n            (Value::build_text(\"01:30:45+05:00\"), \"1999-12-31\"),\n            (Value::build_text(\"22:30:45Z\"), \"2000-01-01\"),\n            // Format 10: HH:MM:SS.SSS\n            (Value::build_text(\"22:30:45.123\"), \"2000-01-01\"),\n            (Value::build_text(\"22:30:45.123+02:00\"), \"2000-01-01\"),\n            (Value::build_text(\"22:30:45.123-05:00\"), \"2000-01-02\"),\n            (Value::build_text(\"01:30:45.123+05:00\"), \"1999-12-31\"),\n            (Value::build_text(\"22:30:45.123Z\"), \"2000-01-01\"),\n            // Test Format 11: 'now'\n            (Value::build_text(\"now\"), &now),\n            // Format 12: DDDDDDDDDD (Julian date as float or integer)\n            (Value::from_f64(2460512.5), test_date_str),\n            (Value::from_i64(2460513), test_date_str),\n        ];\n\n        for (input, expected) in test_cases {\n            let result = exec_date(&[input.clone()]);\n            assert_eq!(\n                result,\n                Value::build_text(expected.to_string()),\n                \"Failed for input: {input:?}\"\n            );\n        }\n    }\n\n    #[test]\n    fn test_invalid_get_date_from_time_value() {\n        let invalid_cases = vec![\n            Value::build_text(\"2024-07-21 25:00\"),    // Invalid hour\n            Value::build_text(\"2024-07-21 25:00:00\"), // Invalid hour\n            Value::build_text(\"2024-07-21 23:60:00\"), // Invalid minute\n            Value::build_text(\"2024-07-21 22:58:60\"), // Invalid second\n            // Note: Invalid days now overflow like SQLite (2024-07-32 -> 2024-08-01)\n            Value::build_text(\"2024-13-01\"),   // Invalid month\n            Value::build_text(\"invalid_date\"), // Completely invalid string\n            Value::build_text(\"\"),             // Empty string\n            Value::from_i64(i64::MAX),         // Large Julian day\n            Value::from_i64(-1),               // Negative Julian day\n            Value::from_f64(f64::MAX),         // Large float\n            Value::from_f64(-1.0),             // Negative Julian day as float\n            Value::from_f64(f64::NAN),         // NaN\n            Value::from_f64(f64::INFINITY),    // Infinity\n            Value::Null,                       // Null value\n            Value::Blob(vec![1, 2, 3]),        // Blob (unsupported type)\n            // Invalid timezone tests\n            Value::build_text(\"2024-07-21T12:00:00+24:00\"), // Invalid timezone offset (too large)\n            Value::build_text(\"2024-07-21T12:00:00-24:00\"), // Invalid timezone offset (too small)\n            Value::build_text(\"2024-07-21T12:00:00+00:60\"), // Invalid timezone minutes\n            Value::build_text(\"2024-07-21T12:00:00+00:00:00\"), // Invalid timezone format (extra seconds)\n            Value::build_text(\"2024-07-21T12:00:00+\"),         // Incomplete timezone\n            Value::build_text(\"2024-07-21T12:00:00+Z\"),        // Invalid timezone format\n            Value::build_text(\"2024-07-21T12:00:00+00:00Z\"),   // Mixing offset and Z\n            Value::build_text(\"2024-07-21T12:00:00UTC\"),       // Named timezone (not supported)\n        ];\n\n        for case in invalid_cases.iter() {\n            let result = exec_date([case]);\n            assert_eq!(result, Value::Null);\n        }\n    }\n\n    #[test]\n    fn test_valid_get_time_from_datetime_value() {\n        let test_time_str = \"22:30:45\";\n        let prev_time_str = \"20:30:45\";\n        let next_time_str = \"03:30:45\";\n\n        let test_cases = vec![\n            // Format 1: YYYY-MM-DD (no timezone applicable)\n            (Value::build_text(\"2024-07-21\"), \"00:00:00\"),\n            // Format 2: YYYY-MM-DD HH:MM\n            (Value::build_text(\"2024-07-21 22:30\"), \"22:30:00\"),\n            (Value::build_text(\"2024-07-21 22:30+02:00\"), \"20:30:00\"),\n            (Value::build_text(\"2024-07-21 22:30-05:00\"), \"03:30:00\"),\n            (Value::build_text(\"2024-07-21 22:30Z\"), \"22:30:00\"),\n            // Format 3: YYYY-MM-DD HH:MM:SS\n            (Value::build_text(\"2024-07-21 22:30:45\"), test_time_str),\n            (\n                Value::build_text(\"2024-07-21 22:30:45+02:00\"),\n                prev_time_str,\n            ),\n            (\n                Value::build_text(\"2024-07-21 22:30:45-05:00\"),\n                next_time_str,\n            ),\n            (Value::build_text(\"2024-07-21 22:30:45Z\"), test_time_str),\n            // Format 4: YYYY-MM-DD HH:MM:SS.SSS\n            (Value::build_text(\"2024-07-21 22:30:45.123\"), test_time_str),\n            (\n                Value::build_text(\"2024-07-21 22:30:45.123+02:00\"),\n                prev_time_str,\n            ),\n            (\n                Value::build_text(\"2024-07-21 22:30:45.123-05:00\"),\n                next_time_str,\n            ),\n            (Value::build_text(\"2024-07-21 22:30:45.123Z\"), test_time_str),\n            // Format 5: YYYY-MM-DDTHH:MM\n            (Value::build_text(\"2024-07-21T22:30\"), \"22:30:00\"),\n            (Value::build_text(\"2024-07-21T22:30+02:00\"), \"20:30:00\"),\n            (Value::build_text(\"2024-07-21T22:30-05:00\"), \"03:30:00\"),\n            (Value::build_text(\"2024-07-21T22:30Z\"), \"22:30:00\"),\n            // Format 6: YYYY-MM-DDTHH:MM:SS\n            (Value::build_text(\"2024-07-21T22:30:45\"), test_time_str),\n            (\n                Value::build_text(\"2024-07-21T22:30:45+02:00\"),\n                prev_time_str,\n            ),\n            (\n                Value::build_text(\"2024-07-21T22:30:45-05:00\"),\n                next_time_str,\n            ),\n            (Value::build_text(\"2024-07-21T22:30:45Z\"), test_time_str),\n            // Format 7: YYYY-MM-DDTHH:MM:SS.SSS\n            (Value::build_text(\"2024-07-21T22:30:45.123\"), test_time_str),\n            (\n                Value::build_text(\"2024-07-21T22:30:45.123+02:00\"),\n                prev_time_str,\n            ),\n            (\n                Value::build_text(\"2024-07-21T22:30:45.123-05:00\"),\n                next_time_str,\n            ),\n            (Value::build_text(\"2024-07-21T22:30:45.123Z\"), test_time_str),\n            // Format 8: HH:MM\n            (Value::build_text(\"22:30\"), \"22:30:00\"),\n            (Value::build_text(\"22:30+02:00\"), \"20:30:00\"),\n            (Value::build_text(\"22:30-05:00\"), \"03:30:00\"),\n            (Value::build_text(\"22:30Z\"), \"22:30:00\"),\n            // Format 9: HH:MM:SS\n            (Value::build_text(\"22:30:45\"), test_time_str),\n            (Value::build_text(\"22:30:45+02:00\"), prev_time_str),\n            (Value::build_text(\"22:30:45-05:00\"), next_time_str),\n            (Value::build_text(\"22:30:45Z\"), test_time_str),\n            // Format 10: HH:MM:SS.SSS\n            (Value::build_text(\"22:30:45.123\"), test_time_str),\n            (Value::build_text(\"22:30:45.123+02:00\"), prev_time_str),\n            (Value::build_text(\"22:30:45.123-05:00\"), next_time_str),\n            (Value::build_text(\"22:30:45.123Z\"), test_time_str),\n            // Format 12: DDDDDDDDDD (Julian date as float or integer)\n            (Value::from_f64(2460082.1), \"14:24:00\"),\n            (Value::from_i64(2460082), \"12:00:00\"),\n        ];\n\n        for (input, expected) in test_cases {\n            let result = exec_time(&[input]);\n            if let Value::Text(result_str) = result {\n                assert_eq!(result_str.as_str(), expected);\n            } else {\n                panic!(\"Expected Value::Text, but got: {result:?}\");\n            }\n        }\n    }\n\n    #[test]\n    fn test_invalid_get_time_from_datetime_value() {\n        let invalid_cases = vec![\n            Value::build_text(\"2024-07-21 25:00\"),    // Invalid hour\n            Value::build_text(\"2024-07-21 25:00:00\"), // Invalid hour\n            Value::build_text(\"2024-07-21 23:60:00\"), // Invalid minute\n            Value::build_text(\"2024-07-21 22:58:60\"), // Invalid second\n            // Note: Invalid days now overflow like SQLite (2024-07-32 -> 2024-08-01)\n            Value::build_text(\"2024-13-01\"),   // Invalid month\n            Value::build_text(\"invalid_date\"), // Completely invalid string\n            Value::build_text(\"\"),             // Empty string\n            Value::from_i64(i64::MAX),         // Large Julian day\n            Value::from_i64(-1),               // Negative Julian day\n            Value::from_f64(f64::MAX),         // Large float\n            Value::from_f64(-1.0),             // Negative Julian day as float\n            Value::from_f64(f64::NAN),         // NaN\n            Value::from_f64(f64::INFINITY),    // Infinity\n            Value::Null,                       // Null value\n            Value::Blob(vec![1, 2, 3]),        // Blob (unsupported type)\n            // Invalid timezone tests\n            Value::build_text(\"2024-07-21T12:00:00+24:00\"), // Invalid timezone offset (too large)\n            Value::build_text(\"2024-07-21T12:00:00-24:00\"), // Invalid timezone offset (too small)\n            Value::build_text(\"2024-07-21T12:00:00+00:60\"), // Invalid timezone minutes\n            Value::build_text(\"2024-07-21T12:00:00+00:00:00\"), // Invalid timezone format (extra seconds)\n            Value::build_text(\"2024-07-21T12:00:00+\"),         // Incomplete timezone\n            Value::build_text(\"2024-07-21T12:00:00+Z\"),        // Invalid timezone format\n            Value::build_text(\"2024-07-21T12:00:00+00:00Z\"),   // Mixing offset and Z\n            Value::build_text(\"2024-07-21T12:00:00UTC\"),       // Named timezone (not supported)\n        ];\n\n        for case in invalid_cases {\n            let result = exec_time(&[case.clone()]);\n            assert_eq!(result, Value::Null);\n        }\n    }\n\n    #[test]\n    fn test_parse_days() {\n        let get_days = |s: &str| -> f64 {\n            let mut p = DateTime::default();\n            p.compute_jd();\n            let start_jd = p.i_jd;\n            parse_modifier(&mut p, s, 1).expect(\"Failed to parse modifier\");\n            (p.i_jd - start_jd) as f64 / 86_400_000.0\n        };\n\n        assert_eq!(get_days(\"5 days\"), 5.0);\n        assert_eq!(get_days(\"-3 days\"), -3.0);\n        assert_eq!(get_days(\"+2 days\"), 2.0);\n        assert_eq!(get_days(\"4  days\"), 4.0);\n        assert_eq!(get_days(\"6   DAYS\"), 6.0);\n        assert_eq!(get_days(\"+5  DAYS\"), 5.0);\n        // Fractional days\n        assert_eq!(get_days(\"1.5 days\"), 1.5);\n        assert_eq!(get_days(\"-0.25 days\"), -0.25);\n    }\n\n    #[test]\n    fn test_parse_hours() {\n        let get_hours = |s: &str| -> f64 {\n            let mut p = DateTime::default();\n            p.compute_jd();\n            let start_jd = p.i_jd;\n            parse_modifier(&mut p, s, 1).expect(\"Failed to parse modifier\");\n            (p.i_jd - start_jd) as f64 / 3_600_000.0\n        };\n\n        assert_eq!(get_hours(\"12 hours\"), 12.0);\n        assert_eq!(get_hours(\"-2 hours\"), -2.0);\n        assert_eq!(get_hours(\"+3  HOURS\"), 3.0);\n        // Fractional hours\n        assert_eq!(get_hours(\"0.5 hours\"), 0.5);\n    }\n\n    #[test]\n    fn test_parse_minutes() {\n        let get_minutes = |s: &str| -> f64 {\n            let mut p = DateTime::default();\n            p.compute_jd();\n            let start_jd = p.i_jd;\n            parse_modifier(&mut p, s, 1).expect(\"Failed to parse modifier\");\n            (p.i_jd - start_jd) as f64 / 60_000.0\n        };\n\n        assert_eq!(get_minutes(\"30 minutes\"), 30.0);\n        assert_eq!(get_minutes(\"-15 minutes\"), -15.0);\n        assert_eq!(get_minutes(\"+45  MINUTES\"), 45.0);\n    }\n\n    #[test]\n    fn test_parse_seconds() {\n        let get_seconds = |s: &str| -> f64 {\n            let mut p = DateTime::default();\n            p.compute_jd();\n            let start_jd = p.i_jd;\n            parse_modifier(&mut p, s, 1).expect(\"Failed to parse modifier\");\n            (p.i_jd - start_jd) as f64 / 1000.0\n        };\n\n        assert_eq!(get_seconds(\"45 seconds\"), 45.0);\n        assert_eq!(get_seconds(\"-10 seconds\"), -10.0);\n        assert_eq!(get_seconds(\"+20  SECONDS\"), 20.0);\n    }\n\n    #[test]\n    fn test_parse_months() {\n        let get_months = |s: &str| -> f64 {\n            let mut p = DateTime::default();\n            let start_y = p.y;\n            let start_m = p.m;\n            parse_modifier(&mut p, s, 1).expect(\"Failed to parse modifier\");\n            ((p.y - start_y) * 12 + (p.m - start_m)) as f64\n        };\n\n        assert_eq!(get_months(\"3 months\"), 3.0);\n        assert_eq!(get_months(\"-1 months\"), -1.0);\n        assert_eq!(get_months(\"+6  MONTHS\"), 6.0);\n    }\n\n    #[test]\n    fn test_parse_years() {\n        let get_years = |s: &str| -> f64 {\n            let mut p = DateTime::default();\n            let start_y = p.y;\n            parse_modifier(&mut p, s, 1).expect(\"Failed to parse modifier\");\n            (p.y - start_y) as f64\n        };\n\n        assert_eq!(get_years(\"2 years\"), 2.0);\n        assert_eq!(get_years(\"-1 years\"), -1.0);\n        assert_eq!(get_years(\"+10  YEARS\"), 10.0);\n    }\n\n    #[test]\n    fn test_parse_time_offset() {\n        let get_ms_change = |s: &str| -> i64 {\n            let mut p = DateTime::default();\n            p.compute_jd();\n            let start_jd = p.i_jd;\n            parse_modifier(&mut p, s, 1).expect(\"Failed to parse modifier\");\n            p.i_jd - start_jd\n        };\n\n        // +01:30 = 90 mins = 5,400,000 ms\n        assert_eq!(get_ms_change(\"+01:30\"), 5_400_000);\n        // -00:45 = -45 mins = -2,700,000 ms\n        assert_eq!(get_ms_change(\"-00:45\"), -2_700_000);\n        // +02:15:30 = 8,130,000 ms\n        assert_eq!(get_ms_change(\"+02:15:30\"), 8_130_000);\n        // +02:15:30.250 = 8,130,250 ms\n        assert_eq!(get_ms_change(\"+02:15:30.250\"), 8_130_250);\n    }\n    #[test]\n    fn test_parse_date_offset() {\n        let run = |modifier: &str| -> String {\n            let args = vec![\n                Value::build_text(\"2000-01-01 00:00:00\".to_string()),\n                Value::build_text(modifier.to_string()),\n            ];\n            let val = exec_datetime_full(args);\n            val.to_text().unwrap().to_string()\n        };\n\n        assert_eq!(run(\"+2023-05-15\"), \"4023-06-16 00:00:00\");\n        assert_eq!(run(\"-2023-05-15\"), \"-0024-07-17 00:00:00\");\n    }\n\n    #[test]\n    fn test_parse_date_time_offset() {\n        let run = |modifier: &str| -> String {\n            let args = vec![\n                Value::build_text(\"2000-01-01 00:00:00\".to_string()),\n                Value::build_text(modifier.to_string()),\n            ];\n            let val = exec_datetime_full(args);\n            val.to_text().unwrap().to_string()\n        };\n\n        assert_eq!(run(\"+2023-05-15 14:30\"), \"4023-06-16 14:30:00\");\n        assert_eq!(run(\"-0001-05-15 14:30\"), \"1998-07-16 09:30:00\");\n    }\n\n    #[test]\n    fn test_parse_start_of() {\n        let run = |start: &str, modifier: &str| -> String {\n            let args = vec![\n                Value::build_text(start.to_string()),\n                Value::build_text(modifier.to_string()),\n            ];\n            let val = exec_datetime_full(args);\n            val.to_text().unwrap().to_string()\n        };\n\n        let base = \"2023-06-15 12:30:45\";\n        assert_eq!(run(base, \"start of month\"), \"2023-06-01 00:00:00\");\n        assert_eq!(run(base, \"START OF MONTH\"), \"2023-06-01 00:00:00\");\n        assert_eq!(run(base, \"start of year\"), \"2023-01-01 00:00:00\");\n        assert_eq!(run(base, \"START OF YEAR\"), \"2023-01-01 00:00:00\");\n        assert_eq!(run(base, \"start of day\"), \"2023-06-15 00:00:00\");\n        assert_eq!(run(base, \"START OF DAY\"), \"2023-06-15 00:00:00\");\n    }\n\n    #[test]\n    fn test_parse_weekday() {\n        let run = |start: &str, modifier: &str| -> String {\n            let args = vec![\n                Value::build_text(start.to_string()),\n                Value::build_text(modifier.to_string()),\n            ];\n            let val = exec_date(args);\n            val.to_text().unwrap().to_string()\n        };\n\n        // 2023-01-01 was a Sunday (0)\n        assert_eq!(run(\"2023-01-01\", \"weekday 0\"), \"2023-01-01\"); // No change\n        assert_eq!(run(\"2023-01-01\", \"weekday 1\"), \"2023-01-02\"); // Next Monday\n        assert_eq!(run(\"2023-01-01\", \"WEEKDAY 6\"), \"2023-01-07\"); // Next Saturday\n    }\n\n    #[test]\n    fn test_parse_ceiling_modifier() {\n        let mut p = DateTime::default();\n        assert!(parse_modifier(&mut p, \"ceiling\", 1).is_ok());\n        assert!(parse_modifier(&mut p, \"CEILING\", 1).is_ok());\n    }\n\n    #[test]\n    fn test_parse_other_modifiers() {\n        // Setup state for modifiers that require specific preconditions\n        let mut p = DateTime {\n            valid_jd: true,\n            raw_s: true,\n            ..DateTime::default()\n        };\n\n        // Modifiers that should just parse OK\n        assert!(parse_modifier(&mut p, \"localtime\", 1).is_ok());\n        assert!(parse_modifier(&mut p, \"LOCALTIME\", 1).is_ok());\n        assert!(parse_modifier(&mut p, \"utc\", 1).is_ok());\n        assert!(parse_modifier(&mut p, \"UTC\", 1).is_ok());\n        assert!(parse_modifier(&mut p, \"subsec\", 1).is_ok());\n        assert!(parse_modifier(&mut p, \"SUBSEC\", 1).is_ok());\n        assert!(parse_modifier(&mut p, \"subsecond\", 1).is_ok());\n        assert!(parse_modifier(&mut p, \"SUBSECOND\", 1).is_ok());\n\n        // These must be at index 0 to parse validly\n        assert!(parse_modifier(&mut p, \"unixepoch\", 0).is_ok());\n        p.raw_s = true;\n        assert!(parse_modifier(&mut p, \"UNIXEPOCH\", 0).is_ok());\n        p.raw_s = true;\n        assert!(parse_modifier(&mut p, \"julianday\", 0).is_ok());\n        p.raw_s = true;\n        assert!(parse_modifier(&mut p, \"JULIANDAY\", 0).is_ok());\n        p.raw_s = true;\n        assert!(parse_modifier(&mut p, \"auto\", 0).is_ok());\n        p.raw_s = true;\n        assert!(parse_modifier(&mut p, \"AUTO\", 0).is_ok());\n    }\n\n    #[test]\n    fn test_parse_invalid_modifier() {\n        let mut p = DateTime::default();\n        assert!(parse_modifier(&mut p, \"invalid modifier\", 1).is_err());\n        assert!(parse_modifier(&mut p, \"5\", 1).is_err());\n        assert!(parse_modifier(&mut p, \"days\", 1).is_err());\n        assert!(parse_modifier(&mut p, \"++5 days\", 1).is_err());\n        assert!(parse_modifier(&mut p, \"weekday 7\", 1).is_err());\n    }\n\n    #[test]\n    fn test_apply_modifier_days() {\n        let run = |mod_str: &str| -> String {\n            let args = vec![\n                Value::build_text(\"2023-06-15 12:30:45\".to_string()),\n                Value::build_text(mod_str.to_string()),\n            ];\n            exec_datetime_full(args).to_text().unwrap().to_string()\n        };\n\n        assert_eq!(run(\"5 days\"), \"2023-06-20 12:30:45\");\n        assert_eq!(run(\"-3 days\"), \"2023-06-12 12:30:45\");\n    }\n\n    #[test]\n    fn test_apply_modifier_hours() {\n        let run = |mod_str: &str| -> String {\n            let args = vec![\n                Value::build_text(\"2023-06-15 12:30:45\".to_string()),\n                Value::build_text(mod_str.to_string()),\n            ];\n            exec_datetime_full(args).to_text().unwrap().to_string()\n        };\n\n        assert_eq!(run(\"6 hours\"), \"2023-06-15 18:30:45\");\n        assert_eq!(run(\"-2 hours\"), \"2023-06-15 10:30:45\");\n    }\n\n    #[test]\n    fn test_apply_modifier_minutes() {\n        let run = |mod_str: &str| -> String {\n            let args = vec![\n                Value::build_text(\"2023-06-15 12:30:45\".to_string()),\n                Value::build_text(mod_str.to_string()),\n            ];\n            exec_datetime_full(args).to_text().unwrap().to_string()\n        };\n\n        assert_eq!(run(\"45 minutes\"), \"2023-06-15 13:15:45\");\n        assert_eq!(run(\"-15 minutes\"), \"2023-06-15 12:15:45\");\n    }\n\n    #[test]\n    fn test_apply_modifier_seconds() {\n        let run = |mod_str: &str| -> String {\n            let args = vec![\n                Value::build_text(\"2023-06-15 12:30:45\".to_string()),\n                Value::build_text(mod_str.to_string()),\n            ];\n            exec_datetime_full(args).to_text().unwrap().to_string()\n        };\n\n        assert_eq!(run(\"30 seconds\"), \"2023-06-15 12:31:15\");\n        assert_eq!(run(\"-20 seconds\"), \"2023-06-15 12:30:25\");\n    }\n\n    #[test]\n    fn test_apply_modifier_time_offset() {\n        let run = |mod_str: &str| -> String {\n            let args = vec![\n                Value::build_text(\"2023-06-15 12:30:45\".to_string()),\n                Value::build_text(mod_str.to_string()),\n            ];\n            exec_datetime_full(args).to_text().unwrap().to_string()\n        };\n\n        assert_eq!(run(\"+01:30\"), \"2023-06-15 14:00:45\");\n        assert_eq!(run(\"-00:45\"), \"2023-06-15 11:45:45\");\n    }\n\n    #[test]\n    fn test_apply_modifier_date_time_offset() {\n        let run = |mod_str: &str| -> String {\n            let args = vec![\n                Value::build_text(\"2023-06-15 12:30:45\".to_string()),\n                Value::build_text(mod_str.to_string()),\n            ];\n            exec_datetime_full(args).to_text().unwrap().to_string()\n        };\n\n        assert_eq!(run(\"+0001-01-01 01:01\"), \"2024-07-16 13:31:45\");\n        assert_eq!(run(\"-0001-01-01 01:01\"), \"2022-05-14 11:29:45\");\n        assert_eq!(run(\"+0002-03-04 05:06\"), \"2025-09-19 17:36:45\");\n        assert_eq!(run(\"-0002-03-04 05:06\"), \"2021-03-11 07:24:45\");\n    }\n\n    #[test]\n    fn test_apply_modifier_start_of_year() {\n        let res = exec_datetime_full(&[\n            Value::build_text(\"2023-06-15 12:30:45\"),\n            Value::build_text(\"start of year\"),\n        ]);\n        assert_eq!(res.to_text().unwrap(), \"2023-01-01 00:00:00\");\n    }\n\n    #[test]\n    fn test_apply_modifier_start_of_day() {\n        let res = exec_datetime_full(&[\n            Value::build_text(\"2023-06-15 12:30:45\"),\n            Value::build_text(\"start of day\"),\n        ]);\n        assert_eq!(res.to_text().unwrap(), \"2023-06-15 00:00:00\");\n    }\n\n    #[test]\n    fn test_single_modifier() {\n        let res = exec_datetime_full(&[\n            Value::build_text(\"2023-06-15 12:30:45\"),\n            Value::build_text(\"-1 day\"),\n        ]);\n        assert_eq!(res.to_text().unwrap(), \"2023-06-14 12:30:45\");\n    }\n\n    #[test]\n    fn test_multiple_modifiers() {\n        let res = exec_datetime_full(&[\n            Value::build_text(\"2023-06-15 12:30:45\"),\n            Value::build_text(\"-1 day\"),\n            Value::build_text(\"+3 hours\"),\n        ]);\n        assert_eq!(res.to_text().unwrap(), \"2023-06-14 15:30:45\");\n    }\n\n    #[test]\n    fn test_subsec_modifier() {\n        let res = exec_datetime_general(\n            &[\n                Value::build_text(\"2023-06-15 12:30:45\"),\n                Value::build_text(\"subsec\"),\n            ],\n            \"time\",\n        );\n        assert_eq!(res.to_text().unwrap(), \"12:30:45.000\");\n    }\n\n    #[test]\n    fn test_start_of_day_modifier() {\n        let res = exec_datetime_full(&[\n            Value::build_text(\"2023-06-15 12:30:45\"),\n            Value::build_text(\"start of day\"),\n            Value::build_text(\"-1 day\"),\n        ]);\n        assert_eq!(res.to_text().unwrap(), \"2023-06-14 00:00:00\");\n    }\n\n    #[test]\n    fn test_start_of_month_modifier() {\n        let res = exec_datetime_full(&[\n            Value::build_text(\"2023-06-15 12:30:45\"),\n            Value::build_text(\"start of month\"),\n            Value::build_text(\"+1 day\"),\n        ]);\n        assert_eq!(res.to_text().unwrap(), \"2023-06-02 00:00:00\");\n    }\n\n    #[test]\n    fn test_start_of_year_modifier() {\n        let res = exec_datetime_full(&[\n            Value::build_text(\"2023-06-15 12:30:45\"),\n            Value::build_text(\"start of year\"),\n            Value::build_text(\"+30 days\"),\n            Value::build_text(\"+5 hours\"),\n        ]);\n        assert_eq!(res.to_text().unwrap(), \"2023-01-31 05:00:00\");\n    }\n\n    #[test]\n    fn test_timezone_modifiers() {\n        let base_str = \"2023-06-15 12:30:45\";\n        let naive = chrono::NaiveDate::from_ymd_opt(2023, 6, 15)\n            .unwrap()\n            .and_hms_opt(12, 30, 45)\n            .unwrap();\n\n        // 1. Test 'localtime' modifier: Input (assumed UTC) -> Output (Local)\n        let args_local = vec![\n            Value::build_text(base_str.to_string()),\n            Value::build_text(\"localtime\".to_string()),\n        ];\n        let res_local = exec_datetime_full(args_local);\n\n        // Expected calculation: Treat naive as UTC, convert to Local\n        let utc_dt = chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset(naive, chrono::Utc);\n        let expected_local = utc_dt\n            .with_timezone(&chrono::Local)\n            .format(\"%Y-%m-%d %H:%M:%S\")\n            .to_string();\n\n        assert_eq!(\n            res_local.to_text().unwrap(),\n            expected_local,\n            \"localtime modifier mismatch\"\n        );\n\n        // 2. Test 'utc' modifier: Input (assumed Local) -> Output (UTC)\n        let args_utc = vec![\n            Value::build_text(base_str.to_string()),\n            Value::build_text(\"utc\".to_string()),\n        ];\n        let res_utc = exec_datetime_full(args_utc);\n\n        // Expected calculation: Treat naive as Local, convert to UTC\n        // We handle potential Local ambiguities (though 2023-06-15 is typically safe)\n        match chrono::Local.from_local_datetime(&naive) {\n            chrono::LocalResult::Single(local_input) => {\n                let expected_utc = local_input\n                    .with_timezone(&chrono::Utc)\n                    .format(\"%Y-%m-%d %H:%M:%S\")\n                    .to_string();\n                assert_eq!(\n                    res_utc.to_text().unwrap(),\n                    expected_utc,\n                    \"utc modifier mismatch\"\n                );\n            }\n            _ => {\n                // Fallback if local time is ambiguous/invalid in test environment\n                // Ensure result is at least a valid string and not Null\n                assert!(res_utc.to_text().is_some());\n                assert_ne!(res_utc, Value::Null);\n            }\n        }\n    }\n\n    #[test]\n    fn test_combined_modifiers() {\n        let args = vec![\n            Value::build_text(\"2000-01-01 00:00:00\".to_string()),\n            Value::build_text(\"-1 day\".to_string()),\n            Value::build_text(\"+5 hours\".to_string()),\n            Value::build_text(\"+30 minutes\".to_string()),\n            Value::build_text(\"+15 seconds\".to_string()),\n            Value::build_text(\"subsec\".to_string()),\n        ];\n        let result = exec_datetime_full(args);\n        assert_eq!(result.to_text().unwrap(), \"1999-12-31 05:30:15.000\");\n    }\n\n    #[test]\n    fn test_max_datetime_limit() {\n        let args = vec![Value::build_text(\"9999-12-31 23:59:59\".to_string())];\n        let result = exec_datetime_full(args);\n        assert_eq!(result.to_text().unwrap(), \"9999-12-31 23:59:59\");\n    }\n\n    #[test]\n    fn test_leap_second_ignored() {\n        let args = vec![Value::build_text(\"2024-06-30 23:59:60\".to_string())];\n        let result = exec_datetime_full(args);\n        assert_eq!(result, Value::Null);\n    }\n\n    #[test]\n    fn test_already_on_weekday_no_change() {\n        let args = vec![\n            Value::build_text(\"2023-01-01 12:00:00\".to_string()),\n            Value::build_text(\"weekday 0\".to_string()),\n        ];\n        let result = exec_datetime_full(args);\n        assert_eq!(result.to_text().unwrap(), \"2023-01-01 12:00:00\");\n    }\n\n    #[test]\n    fn test_move_forward_if_different() {\n        let args1 = vec![\n            Value::build_text(\"2023-01-01 12:00:00\".to_string()),\n            Value::build_text(\"weekday 1\".to_string()),\n        ];\n        let res1 = exec_datetime_full(args1);\n        assert_eq!(res1.to_text().unwrap(), \"2023-01-02 12:00:00\");\n\n        let args2 = vec![\n            Value::build_text(\"2023-01-03 12:00:00\".to_string()),\n            Value::build_text(\"weekday 5\".to_string()),\n        ];\n        let res2 = exec_datetime_full(args2);\n        assert_eq!(res2.to_text().unwrap(), \"2023-01-06 12:00:00\");\n    }\n\n    #[test]\n    fn test_wrap_around_weekend() {\n        let args1 = vec![\n            Value::build_text(\"2023-01-06 12:00:00\".to_string()),\n            Value::build_text(\"weekday 0\".to_string()),\n        ];\n        let res1 = exec_datetime_full(args1);\n        assert_eq!(res1.to_text().unwrap(), \"2023-01-08 12:00:00\");\n\n        let args2 = vec![\n            Value::build_text(\"2023-01-08 12:00:00\".to_string()),\n            Value::build_text(\"weekday 0\".to_string()),\n        ];\n        let res2 = exec_datetime_full(args2);\n        assert_eq!(res2.to_text().unwrap(), \"2023-01-08 12:00:00\");\n    }\n\n    #[test]\n    fn test_same_day_stays_put() {\n        let args = vec![\n            Value::build_text(\"2023-01-05 12:00:00\".to_string()),\n            Value::build_text(\"weekday 4\".to_string()),\n        ];\n        let res = exec_datetime_full(args);\n        assert_eq!(res.to_text().unwrap(), \"2023-01-05 12:00:00\");\n    }\n\n    #[test]\n    fn test_already_on_friday_no_change() {\n        let args = vec![\n            Value::build_text(\"2023-01-06 12:00:00\".to_string()),\n            Value::build_text(\"weekday 5\".to_string()),\n        ];\n        let res = exec_datetime_full(args);\n        assert_eq!(res.to_text().unwrap(), \"2023-01-06 12:00:00\");\n    }\n\n    #[test]\n    fn test_apply_modifier_julianday() {\n        let jd_args = vec![Value::build_text(\"2000-01-01 12:00:00\".to_string())];\n        let jd_val = exec_julianday(jd_args);\n\n        let dt_args = vec![jd_val, Value::build_text(\"auto\".to_string())];\n        let dt_res = exec_datetime_full(dt_args);\n        assert_eq!(dt_res.to_text().unwrap(), \"2000-01-01 12:00:00\");\n    }\n\n    #[test]\n    fn test_apply_modifier_start_of_month() {\n        let args = vec![\n            Value::build_text(\"2023-06-15 12:30:45\".to_string()),\n            Value::build_text(\"start of month\".to_string()),\n        ];\n        let res = exec_datetime_full(args);\n        assert_eq!(res.to_text().unwrap(), \"2023-06-01 00:00:00\");\n    }\n\n    #[test]\n    fn test_apply_modifier_subsec() {\n        let args = vec![\n            Value::build_text(\"2023-06-15 12:30:45\".to_string()),\n            Value::build_text(\"subsec\".to_string()),\n        ];\n        let res = exec_datetime_general(args, \"datetime\");\n        assert_eq!(res.to_text().unwrap(), \"2023-06-15 12:30:45.000\");\n    }\n\n    #[test]\n    fn test_apply_modifier_floor_modifier_n_floor_gt_0() {\n        let args = vec![\n            Value::build_text(\"2023-01-31\".to_string()),\n            Value::build_text(\"+1 month\".to_string()),\n            Value::build_text(\"floor\".to_string()),\n        ];\n        let res = exec_datetime_full(args);\n        assert_eq!(res.to_text().unwrap(), \"2023-02-28 00:00:00\");\n    }\n\n    #[test]\n    fn test_apply_modifier_floor_modifier_n_floor_le_0() {\n        let args = vec![\n            Value::build_text(\"2023-01-15\".to_string()),\n            Value::build_text(\"+1 month\".to_string()),\n            Value::build_text(\"floor\".to_string()),\n        ];\n        let res = exec_datetime_full(args);\n        assert_eq!(res.to_text().unwrap(), \"2023-02-15 00:00:00\");\n    }\n\n    #[test]\n    fn test_apply_modifier_ceiling_modifier_sets_n_floor_to_zero() {\n        let args = vec![\n            Value::build_text(\"2023-01-31\".to_string()),\n            Value::build_text(\"ceiling\".to_string()),\n            Value::build_text(\"+1 month\".to_string()),\n        ];\n        let res = exec_datetime_full(args);\n        assert_eq!(res.to_text().unwrap(), \"2023-03-03 00:00:00\");\n    }\n\n    #[test]\n    fn test_apply_modifier_start_of_month_basic() {\n        let args = vec![\n            Value::build_text(\"2023-06-15 12:30:45\".to_string()),\n            Value::build_text(\"start of month\".to_string()),\n        ];\n        let res = exec_datetime_full(args);\n        assert_eq!(res.to_text().unwrap(), \"2023-06-01 00:00:00\");\n    }\n\n    #[test]\n    fn test_apply_modifier_start_of_month_already_at_first() {\n        let args = vec![\n            Value::build_text(\"2023-06-01 00:00:00\".to_string()),\n            Value::build_text(\"start of month\".to_string()),\n        ];\n        let res = exec_datetime_full(args);\n        assert_eq!(res.to_text().unwrap(), \"2023-06-01 00:00:00\");\n    }\n\n    #[test]\n    fn test_apply_modifier_start_of_month_edge_case() {\n        let args = vec![\n            Value::build_text(\"2023-07-31 23:59:59\".to_string()),\n            Value::build_text(\"start of month\".to_string()),\n        ];\n        let res = exec_datetime_full(args);\n        assert_eq!(res.to_text().unwrap(), \"2023-07-01 00:00:00\");\n    }\n\n    #[test]\n    fn test_apply_modifier_subsec_no_change() {\n        let args = vec![\n            Value::build_text(\"2023-06-15 12:30:45.123\".to_string()),\n            Value::build_text(\"subsec\".to_string()),\n        ];\n        let res = exec_datetime_full(args);\n        assert_eq!(res.to_text().unwrap(), \"2023-06-15 12:30:45.123\");\n    }\n\n    #[test]\n    fn test_apply_modifier_subsec_preserves_fractional_seconds() {\n        let args = vec![\n            Value::build_text(\"2025-01-02 04:12:21.891\".to_string()),\n            Value::build_text(\"subsec\".to_string()),\n        ];\n        let res = exec_datetime_full(args);\n        assert_eq!(res.to_text().unwrap(), \"2025-01-02 04:12:21.891\");\n    }\n\n    #[test]\n    fn test_apply_modifier_subsec_no_fractional_seconds() {\n        let args = vec![\n            Value::build_text(\"2025-01-02 04:12:21\".to_string()),\n            Value::build_text(\"subsec\".to_string()),\n        ];\n        let res = exec_datetime_full(args);\n        assert_eq!(res.to_text().unwrap(), \"2025-01-02 04:12:21.000\");\n    }\n\n    #[test]\n    fn test_apply_modifier_subsec_truncate_to_milliseconds() {\n        let args = vec![\n            Value::build_text(\"2025-01-02 04:12:21.891123456\".to_string()),\n            Value::build_text(\"subsec\".to_string()),\n        ];\n        let res = exec_datetime_full(args);\n        assert_eq!(res.to_text().unwrap(), \"2025-01-02 04:12:21.891\");\n    }\n\n    #[test]\n    fn test_strftime() {\n        let fmt = Value::build_text(\"%Y-%m-%d\".to_string());\n        let date = Value::build_text(\"2023-10-25 14:30:00\".to_string());\n        let expected = Value::build_text(\"2023-10-25\".to_string());\n        assert_eq!(exec_strftime(&[fmt, date]), expected);\n\n        let fmt = Value::build_text(\"%H:%M:%S\".to_string());\n        let date = Value::build_text(\"2023-10-25 14:30:45\".to_string());\n        let expected = Value::build_text(\"14:30:45\".to_string());\n        assert_eq!(exec_strftime(&[fmt, date]), expected);\n\n        let fmt = Value::build_text(\"Date: %Y-%m-%d, Time: %H:%M\".to_string());\n        let date = Value::build_text(\"2023-10-25 14:30:45\".to_string());\n        let expected = Value::build_text(\"Date: 2023-10-25, Time: 14:30\".to_string());\n        assert_eq!(exec_strftime(&[fmt, date]), expected);\n\n        let fmt = Value::build_text(\"%Y-%m-%d\".to_string());\n        let date = Value::build_text(\"2023-10-25\".to_string());\n        let mod1 = Value::build_text(\"start of month\".to_string());\n        let expected = Value::build_text(\"2023-10-01\".to_string());\n        assert_eq!(exec_strftime(&[fmt, date, mod1]), expected);\n\n        let fmt = Value::build_text(\"%Y-%m-%d\".to_string());\n        let date = Value::build_text(\"2023-10-25\".to_string());\n        let mod1 = Value::build_text(\"+5 days\".to_string());\n        let expected = Value::build_text(\"2023-10-30\".to_string());\n        assert_eq!(exec_strftime(&[fmt, date, mod1]), expected);\n\n        let fmt = Value::build_text(\"%J\".to_string());\n        let date = Value::build_text(\"2023-01-01 12:00:00\".to_string());\n        let expected = Value::build_text(\"2459946\".to_string());\n        assert_eq!(exec_strftime(&[fmt, date]), expected);\n\n        let fmt = Value::build_text(\"%s\".to_string());\n        let date = Value::build_text(\"2023-01-01 00:00:00\".to_string());\n        let expected = Value::build_text(\"1672531200\".to_string());\n        assert_eq!(exec_strftime(&[fmt, date]), expected);\n\n        let fmt = Value::build_text(\"%S.%f\".to_string());\n        let date = Value::build_text(\"2023-01-01 12:00:05.123\".to_string());\n        let expected = Value::build_text(\"05.05.123\".to_string());\n        assert_eq!(exec_strftime(&[fmt, date]), expected);\n\n        let fmt = Value::build_text(\"%w\".to_string());\n        let date = Value::build_text(\"2023-01-01\".to_string());\n        let expected = Value::build_text(\"0\".to_string());\n        assert_eq!(exec_strftime(&[fmt, date]), expected);\n\n        let fmt = Value::build_text(\"%j\".to_string());\n        let date = Value::build_text(\"2023-02-01\".to_string());\n        let expected = Value::build_text(\"032\".to_string());\n        assert_eq!(exec_strftime(&[fmt, date]), expected);\n\n        let fmt = Value::Null;\n        let date = Value::build_text(\"now\".to_string());\n        assert_eq!(exec_strftime(&[fmt, date]), Value::Null);\n\n        let fmt = Value::build_text(\"%Y\".to_string());\n        let date = Value::Null;\n        let expected = Value::Null;\n        assert_eq!(exec_strftime(&[fmt, date]), expected);\n\n        let fmt = Value::build_text(\"%Y\".to_string());\n        let date = Value::build_text(\"invalid-date\".to_string());\n        assert_eq!(exec_strftime(&[fmt, date]), Value::Null);\n\n        let fmt = Value::build_text(\"100%%\".to_string());\n        let date = Value::build_text(\"2023-01-01\".to_string());\n        let expected = Value::build_text(\"100%\".to_string());\n        assert_eq!(exec_strftime(&[fmt, date]), expected);\n    }\n\n    #[test]\n    fn test_exec_timediff() {\n        let start = Value::build_text(\"12:00:00\");\n        let end = Value::build_text(\"14:30:45\");\n        let expected = Value::build_text(\"-0000-00-00 02:30:45.000\");\n        assert_eq!(exec_timediff(&[start, end]), expected);\n\n        let start = Value::build_text(\"14:30:45\");\n        let end = Value::build_text(\"12:00:00\");\n        let expected = Value::build_text(\"+0000-00-00 02:30:45.000\");\n        assert_eq!(exec_timediff(&[start, end]), expected);\n\n        let start = Value::build_text(\"12:00:01.300\");\n        let end = Value::build_text(\"12:00:00.500\");\n        let expected = Value::build_text(\"+0000-00-00 00:00:00.800\");\n        assert_eq!(exec_timediff(&[start, end]), expected);\n\n        let start = Value::build_text(\"13:30:00\");\n        let end = Value::build_text(\"16:45:30\");\n        let expected = Value::build_text(\"-0000-00-00 03:15:30.000\");\n        assert_eq!(exec_timediff(&[start, end]), expected);\n\n        let start = Value::build_text(\"2023-05-10 23:30:00\");\n        let end = Value::build_text(\"2023-05-11 01:15:00\");\n        let expected = Value::build_text(\"-0000-00-00 01:45:00.000\");\n        assert_eq!(exec_timediff(&[start, end]), expected);\n\n        let start = Value::Null;\n        let end = Value::build_text(\"12:00:00\");\n        let expected = Value::Null;\n        assert_eq!(exec_timediff(&[start, end]), expected);\n\n        let start = Value::build_text(\"not a time\");\n        let end = Value::build_text(\"12:00:00\");\n        let expected = Value::Null;\n        assert_eq!(exec_timediff(&[start, end]), expected);\n\n        // Test identical times - should return zero duration, not Null\n        let start = Value::build_text(\"12:00:00\");\n        let end = Value::build_text(\"12:00:00\");\n        let expected = Value::build_text(\"+0000-00-00 00:00:00.000\");\n        assert_eq!(exec_timediff(&[start, end]), expected);\n    }\n\n    #[test]\n    fn test_subsec_fixed_time_expansion() {\n        let args = vec![\n            Value::build_text(\"2024-01-01 12:00:00\".to_string()),\n            Value::build_text(\"subsec\".to_string()),\n        ];\n        let result = exec_datetime_full(args);\n        assert_eq!(result.to_text().unwrap(), \"2024-01-01 12:00:00.000\");\n    }\n\n    #[test]\n    fn test_subsec_date_only_expansion() {\n        let args = vec![\n            Value::build_text(\"2024-01-01\".to_string()),\n            Value::build_text(\"subsec\".to_string()),\n        ];\n        let result = exec_datetime_full(args);\n        assert_eq!(result.to_text().unwrap(), \"2024-01-01 00:00:00.000\");\n    }\n\n    #[test]\n    fn test_subsec_iso_separator() {\n        let args = vec![\n            Value::build_text(\"2024-01-01T15:30:00\".to_string()),\n            Value::build_text(\"subsec\".to_string()),\n        ];\n        let result = exec_datetime_full(args);\n        assert_eq!(result.to_text().unwrap(), \"2024-01-01 15:30:00.000\");\n    }\n\n    #[test]\n    fn test_subsec_chaining_before_math() {\n        let args = vec![\n            Value::build_text(\"2024-01-01 12:00:00\".to_string()),\n            Value::build_text(\"subsec\".to_string()),\n            Value::build_text(\"+1 hour\".to_string()),\n        ];\n        let result = exec_datetime_full(args);\n        assert_eq!(result.to_text().unwrap(), \"2024-01-01 13:00:00.000\");\n    }\n\n    #[test]\n    fn test_subsec_chaining_after_math() {\n        let args = vec![\n            Value::build_text(\"2024-01-01 12:00:00\".to_string()),\n            Value::build_text(\"+1 hour\".to_string()),\n            Value::build_text(\"subsec\".to_string()),\n        ];\n        let result = exec_datetime_full(args);\n        assert_eq!(result.to_text().unwrap(), \"2024-01-01 13:00:00.000\");\n    }\n\n    #[test]\n    fn test_subsec_rollover_math() {\n        let args = vec![\n            Value::build_text(\"2024-01-01 12:00:00.999\".to_string()),\n            Value::build_text(\"+1 second\".to_string()),\n            Value::build_text(\"subsec\".to_string()),\n        ];\n        let result = exec_datetime_full(args);\n        assert_eq!(result.to_text().unwrap(), \"2024-01-01 12:00:01.999\");\n    }\n\n    #[test]\n    fn test_subsec_case_insensitivity() {\n        let args = vec![\n            Value::build_text(\"2024-01-01 12:00:00\".to_string()),\n            Value::build_text(\"SuBsEc\".to_string()),\n        ];\n        let result = exec_datetime_full(args);\n        assert_eq!(result.to_text().unwrap(), \"2024-01-01 12:00:00.000\");\n    }\n\n    #[test]\n    fn test_parse_modifier_unicode_no_panic() {\n        let unicode_inputs = [\"!*\\u{ea37}\", \"\\u{1F600}\", \"日本語\", \"中\", \"\\u{0080}\", \"\"];\n\n        for input in unicode_inputs {\n            let args = vec![\n                Value::build_text(\"now\".to_string()),\n                Value::build_text(input.to_string()),\n            ];\n            let result = exec_datetime_full(args);\n            // Expect Null for invalid modifiers, but no panic\n            assert_eq!(result, Value::Null);\n        }\n    }\n\n    #[test]\n    fn test_unixepoch_basic_usage() {\n        let result = exec_unixepoch(vec![Value::build_text(\"1970-01-01 00:00:00\".to_string())]);\n        assert_eq!(result, Value::from_i64(0));\n\n        let result = exec_unixepoch(vec![Value::build_text(\"2023-01-01 00:00:00\".to_string())]);\n        assert_eq!(result, Value::from_i64(1672531200));\n\n        let result = exec_unixepoch(vec![Value::build_text(\"1969-12-31 23:59:59\".to_string())]);\n        assert_eq!(result, Value::from_i64(-1));\n\n        let result = exec_unixepoch(vec![Value::from_f64(2440587.5)]);\n        assert_eq!(result, Value::from_i64(0));\n    }\n\n    #[test]\n    fn test_unixepoch_numeric_modifiers_unixepoch() {\n        let res1 = exec_unixepoch(vec![\n            Value::from_i64(1672531200),\n            Value::build_text(\"unixepoch\".to_string()),\n        ]);\n        assert_eq!(res1, Value::from_i64(1672531200));\n\n        let res2 = exec_unixepoch(vec![\n            Value::from_i64(0),\n            Value::build_text(\"unixepoch\".to_string()),\n        ]);\n        assert_eq!(res2, Value::from_i64(0));\n\n        let res3 = exec_unixepoch(vec![\n            Value::from_i64(1672531200),\n            Value::build_text(\"unixepoch\".to_string()),\n            Value::build_text(\"start of year\".to_string()),\n        ]);\n        assert_eq!(res3, Value::from_i64(1672531200));\n    }\n\n    #[test]\n    fn test_unixepoch_numeric_modifiers_julianday() {\n        let res1 = exec_unixepoch(vec![\n            Value::from_f64(2440587.5),\n            Value::build_text(\"julianday\".to_string()),\n        ]);\n        assert_eq!(res1, Value::from_i64(0));\n\n        let res2 = exec_unixepoch(vec![\n            Value::from_f64(2460311.5),\n            Value::build_text(\"julianday\".to_string()),\n        ]);\n        assert_eq!(res2, Value::from_i64(1704153600));\n\n        let res3 = exec_unixepoch(vec![\n            Value::from_f64(0.0),\n            Value::build_text(\"julianday\".to_string()),\n        ]);\n        match res3 {\n            Value::Numeric(Numeric::Integer(i)) => assert_eq!(i, -210866760000),\n            _ => panic!(\"Expected Integer result for JD 0\"),\n        }\n    }\n\n    #[test]\n    fn test_unixepoch_numeric_modifiers_auto() {\n        let res1 = exec_unixepoch(vec![\n            Value::from_f64(2440587.5),\n            Value::build_text(\"auto\".to_string()),\n        ]);\n        assert_eq!(res1, Value::from_i64(0));\n\n        let res2 = exec_unixepoch(vec![\n            Value::from_i64(1672531200),\n            Value::build_text(\"auto\".to_string()),\n        ]);\n        assert_eq!(res2, Value::from_i64(1672531200));\n\n        let res3 = exec_unixepoch(vec![\n            Value::from_f64(0.0),\n            Value::build_text(\"auto\".to_string()),\n        ]);\n        match res3 {\n            Value::Numeric(Numeric::Integer(i)) => assert!(i < 0),\n            _ => panic!(\"Expected Integer result\"),\n        }\n    }\n\n    #[test]\n    fn test_unixepoch_invalid_usage() {\n        let res1 = exec_unixepoch(vec![\n            Value::from_i64(0),\n            Value::build_text(\"start of year\".to_string()),\n            Value::build_text(\"unixepoch\".to_string()),\n        ]);\n        assert_eq!(res1, Value::Null);\n\n        let res2 = exec_unixepoch(vec![\n            Value::build_text(\"2023-01-01\".to_string()),\n            Value::build_text(\"unixepoch\".to_string()),\n        ]);\n        assert_eq!(res2, Value::Null);\n\n        let res3 = exec_unixepoch(vec![\n            Value::from_i64(0),\n            Value::build_text(\"unixepoch\".to_string()),\n            Value::build_text(\"julianday\".to_string()),\n        ]);\n        assert_eq!(res3, Value::Null);\n    }\n\n    #[test]\n    fn test_unixepoch_complex_calculations() {\n        let res1 = exec_unixepoch(vec![\n            Value::from_f64(2440587.5),\n            Value::build_text(\"julianday\".to_string()),\n            Value::build_text(\"+1 day\".to_string()),\n        ]);\n        assert_eq!(res1, Value::from_i64(86400));\n\n        let res2 = exec_unixepoch(vec![\n            Value::from_f64(2460311.5),\n            Value::build_text(\"auto\".to_string()),\n            Value::build_text(\"start of month\".to_string()),\n            Value::build_text(\"+1 month\".to_string()),\n        ]);\n        assert_eq!(res2, Value::from_i64(1706745600));\n    }\n\n    #[test]\n    fn test_unixepoch_subsecond_precision() {\n        let res1 = exec_unixepoch(vec![\n            Value::build_text(\"1970-01-01 00:00:00.0006\".to_string()),\n            Value::build_text(\"subsec\".to_string()),\n        ]);\n        match res1 {\n            Value::Numeric(Numeric::Float(f)) => {\n                assert!((f64::from(f) - 0.001).abs() < f64::EPSILON)\n            }\n            _ => panic!(\"Expected Float result\"),\n        }\n\n        let res2 = exec_unixepoch(vec![\n            Value::build_text(\"1970-01-01 00:00:00.9996\".to_string()),\n            Value::build_text(\"subsec\".to_string()),\n        ]);\n        match res2 {\n            Value::Numeric(Numeric::Float(f)) => {\n                assert!((f64::from(f) - 0.999).abs() < f64::EPSILON)\n            }\n            _ => panic!(\"Expected Float result\"),\n        }\n    }\n\n    #[test]\n    fn test_fast_path_date_only() {\n        assert_eq!(\n            exec_date(vec![Value::build_text(\"2024-01-01\".to_string())])\n                .to_text()\n                .unwrap(),\n            \"2024-01-01\"\n        );\n        assert_eq!(\n            exec_date(vec![Value::build_text(\"0001-01-01\".to_string())])\n                .to_text()\n                .unwrap(),\n            \"0001-01-01\"\n        );\n        assert_eq!(\n            exec_date(vec![Value::build_text(\"9999-12-31\".to_string())])\n                .to_text()\n                .unwrap(),\n            \"9999-12-31\"\n        );\n        assert_eq!(\n            exec_date(vec![Value::build_text(\"2024-02-29\".to_string())])\n                .to_text()\n                .unwrap(),\n            \"2024-02-29\"\n        );\n        assert_eq!(\n            exec_date(vec![Value::build_text(\"2023-02-29\".to_string())])\n                .to_text()\n                .unwrap(),\n            \"2023-03-01\"\n        );\n\n        assert_eq!(\n            exec_date(vec![Value::build_text(\"2024-00-01\".to_string())]),\n            Value::Null\n        );\n        assert_eq!(\n            exec_date(vec![Value::build_text(\"2024-13-01\".to_string())]),\n            Value::Null\n        );\n        assert_eq!(\n            exec_date(vec![Value::build_text(\"2024-01-00\".to_string())]),\n            Value::Null\n        );\n\n        assert_eq!(\n            exec_date(vec![Value::build_text(\"2024-01-32\".to_string())]),\n            Value::Null\n        );\n\n        assert_eq!(\n            exec_date(vec![Value::build_text(\"2024/01/01\".to_string())]),\n            Value::Null\n        );\n        assert_eq!(\n            exec_date(vec![Value::build_text(\"2024.01.01\".to_string())]),\n            Value::Null\n        );\n        assert_eq!(\n            exec_date(vec![Value::build_text(\"202X-01-01\".to_string())]),\n            Value::Null\n        );\n        assert_eq!(\n            exec_date(vec![Value::build_text(\"2024-0a-01\".to_string())]),\n            Value::Null\n        );\n    }\n\n    #[test]\n    fn test_fast_path_datetime_formats() {\n        assert_eq!(\n            exec_datetime_full(vec![Value::build_text(\"2024-01-15 10:30\".to_string())])\n                .to_text()\n                .unwrap(),\n            \"2024-01-15 10:30:00\"\n        );\n        assert_eq!(\n            exec_datetime_full(vec![Value::build_text(\"2024-01-15T10:30\".to_string())])\n                .to_text()\n                .unwrap(),\n            \"2024-01-15 10:30:00\"\n        );\n        assert_eq!(\n            exec_datetime_full(vec![Value::build_text(\"2024-01-15X10:30\".to_string())]),\n            Value::Null\n        );\n        assert_eq!(\n            exec_datetime_full(vec![Value::build_text(\"2024-01-15 10:30:45\".to_string())])\n                .to_text()\n                .unwrap(),\n            \"2024-01-15 10:30:45\"\n        );\n        assert_eq!(\n            exec_datetime_full(vec![Value::build_text(\"2024-01-15T10:30:45\".to_string())])\n                .to_text()\n                .unwrap(),\n            \"2024-01-15 10:30:45\"\n        );\n\n        assert_eq!(\n            exec_datetime_full(vec![Value::build_text(\"2024-01-15 25:30:45\".to_string())]),\n            Value::Null\n        );\n        assert_eq!(\n            exec_datetime_full(vec![Value::build_text(\"2024-01-15 10:60:45\".to_string())]),\n            Value::Null\n        );\n        assert_eq!(\n            exec_datetime_full(vec![Value::build_text(\"2024-01-15 10:30:60\".to_string())]),\n            Value::Null\n        );\n    }\n\n    #[test]\n    fn test_fast_path_time_only() {\n        assert_eq!(\n            exec_time(vec![Value::build_text(\"10:30\".to_string())])\n                .to_text()\n                .unwrap(),\n            \"10:30:00\"\n        );\n        assert_eq!(\n            exec_time(vec![Value::build_text(\"00:00\".to_string())])\n                .to_text()\n                .unwrap(),\n            \"00:00:00\"\n        );\n        assert_eq!(\n            exec_time(vec![Value::build_text(\"23:59\".to_string())])\n                .to_text()\n                .unwrap(),\n            \"23:59:00\"\n        );\n        assert_eq!(\n            exec_time(vec![Value::build_text(\"24:00\".to_string())])\n                .to_text()\n                .unwrap(),\n            \"24:00:00\"\n        );\n        assert_eq!(\n            exec_time(vec![Value::build_text(\"10:60\".to_string())]),\n            Value::Null\n        );\n\n        assert_eq!(\n            exec_time(vec![Value::build_text(\"10:30:45\".to_string())])\n                .to_text()\n                .unwrap(),\n            \"10:30:45\"\n        );\n        assert_eq!(\n            exec_time(vec![Value::build_text(\"00:00:00\".to_string())])\n                .to_text()\n                .unwrap(),\n            \"00:00:00\"\n        );\n        assert_eq!(\n            exec_time(vec![Value::build_text(\"23:59:59\".to_string())])\n                .to_text()\n                .unwrap(),\n            \"23:59:59\"\n        );\n\n        let res1 = exec_datetime_general(\n            vec![\n                Value::build_text(\"10:30:45.123\".to_string()),\n                Value::build_text(\"subsec\".to_string()),\n            ],\n            \"time\",\n        );\n        assert_eq!(res1.to_text().unwrap(), \"10:30:45.123\");\n\n        let res2 = exec_datetime_general(\n            vec![\n                Value::build_text(\"10:30:45.1\".to_string()),\n                Value::build_text(\"subsec\".to_string()),\n            ],\n            \"time\",\n        );\n        assert_eq!(res2.to_text().unwrap(), \"10:30:45.100\");\n    }\n\n    #[test]\n    fn test_fast_path_skips_timezone_strings() {\n        assert_eq!(\n            exec_datetime_full(vec![Value::build_text(\"2024-01-15 10:30:45Z\".to_string())])\n                .to_text()\n                .unwrap(),\n            \"2024-01-15 10:30:45\"\n        );\n        assert_eq!(\n            exec_datetime_full(vec![Value::build_text(\n                \"2024-01-15 10:30:45+02:00\".to_string()\n            )])\n            .to_text()\n            .unwrap(),\n            \"2024-01-15 08:30:45\"\n        );\n        assert_eq!(\n            exec_datetime_full(vec![Value::build_text(\n                \"2024-01-15 10:30:45-05:00\".to_string()\n            )])\n            .to_text()\n            .unwrap(),\n            \"2024-01-15 15:30:45\"\n        );\n        assert_eq!(\n            exec_time(vec![Value::build_text(\"10:30:45+02:00\".to_string())])\n                .to_text()\n                .unwrap(),\n            \"08:30:45\"\n        );\n    }\n\n    #[test]\n    fn test_fast_path_fractional_seconds_precision() {\n        let args1 = vec![\n            Value::build_text(\"2024-01-15 10:30:45.123456789\".to_string()),\n            Value::build_text(\"subsec\".to_string()),\n        ];\n        assert_eq!(\n            exec_datetime_full(args1).to_text().unwrap(),\n            \"2024-01-15 10:30:45.123\"\n        );\n\n        let args2 = vec![\n            Value::build_text(\"2024-01-15 10:30:45.1\".to_string()),\n            Value::build_text(\"subsec\".to_string()),\n        ];\n        assert_eq!(\n            exec_datetime_full(args2).to_text().unwrap(),\n            \"2024-01-15 10:30:45.100\"\n        );\n\n        let args3 = vec![\n            Value::build_text(\"2024-01-15 10:30:45.100\".to_string()),\n            Value::build_text(\"subsec\".to_string()),\n        ];\n        assert_eq!(\n            exec_datetime_full(args3).to_text().unwrap(),\n            \"2024-01-15 10:30:45.100\"\n        );\n\n        let args4 = vec![\n            Value::build_text(\"2024-01-15 10:30:45.000\".to_string()),\n            Value::build_text(\"subsec\".to_string()),\n        ];\n        assert_eq!(\n            exec_datetime_full(args4).to_text().unwrap(),\n            \"2024-01-15 10:30:45.000\"\n        );\n    }\n\n    #[test]\n    fn test_fast_path_month_day_boundaries() {\n        let run = |s: &str| -> String {\n            exec_datetime_full(&[Value::build_text(s.to_string())])\n                .to_text()\n                .unwrap()\n                .to_string()\n        };\n\n        assert_eq!(run(\"2024-04-30\"), \"2024-04-30 00:00:00\");\n        assert_eq!(run(\"2024-04-31\"), \"2024-05-01 00:00:00\");\n        assert_eq!(run(\"2024-01-31\"), \"2024-01-31 00:00:00\");\n        assert_eq!(run(\"2024-03-31\"), \"2024-03-31 00:00:00\");\n        assert_eq!(run(\"2024-02-29\"), \"2024-02-29 00:00:00\");\n        assert_eq!(run(\"2023-02-29\"), \"2023-03-01 00:00:00\");\n        assert_eq!(run(\"2024-02-28\"), \"2024-02-28 00:00:00\");\n    }\n\n    #[test]\n    fn test_fast_path_edge_cases() {\n        // Helper for cases expected to fail (return Null)\n        let run = |s: &str| -> Value { exec_datetime_full(&[Value::build_text(s.to_string())]) };\n\n        // Helper for cases expected to succeed (return String)\n        let run_str = |s: &str| -> String { run(s).to_text().unwrap().to_string() };\n\n        assert_eq!(run(\"\"), Value::Null);\n        assert_eq!(run(\"a\"), Value::Null);\n        assert_eq!(run(\"ab\"), Value::Null);\n        assert_eq!(run(\"abc\"), Value::Null);\n        assert_eq!(run(\"abcd\"), Value::Null);\n        assert_eq!(run_str(\"0000-01-01\"), \"0000-01-01 00:00:00\");\n        assert_eq!(run(\" 2024-01-01\"), Value::Null);\n        assert_eq!(run_str(\"2024-01-01 \"), \"2024-01-01 00:00:00\");\n        assert_eq!(run_str(\"2024-01-15\\t10:30:45\"), \"2024-01-15 10:30:45\");\n        assert_eq!(run(\"2024-1-01\"), Value::Null);\n        assert_eq!(run(\"2024-01-1\"), Value::Null);\n        assert_eq!(run(\"aaaa-bb-cc\"), Value::Null);\n        assert_eq!(run(\"2024-01-01abc\"), Value::Null);\n        assert_eq!(run_str(\"-2024-01-01\"), \"-2024-01-01 00:00:00\");\n        assert_eq!(run(\"10:30:45.12abc\"), Value::Null);\n        assert_eq!(run(\"2024-01-15 10:30:45.123xyz\"), Value::Null);\n\n        // Manual check for subsec since it requires 2 arguments\n        let dt_args = &[\n            Value::build_text(\"10:30:45.12\".to_string()),\n            Value::build_text(\"subsec\".to_string()),\n        ];\n        assert_eq!(\n            exec_datetime_general(dt_args, \"time\").to_text().unwrap(),\n            \"10:30:45.120\"\n        );\n    }\n\n    // Regression test for fuzzing crash: strftime with non-char-boundary UTF-8 modifiers\n    // The modifier \"swww\\0\\u{1}\\t\\0\\u{fffd}\\u{fffd}\\u{f}W\" has multi-byte chars where\n    // byte index 9 is not a valid char boundary, causing panic on slice.\n    #[test]\n    fn test_strftime_invalid_utf8_boundary_modifier() {\n        // This modifier starts with 's' so it matches the 's' => branch,\n        // but byte 9 falls inside a multi-byte character\n        let modifier_with_multibyte = \"swww\\0\\u{1}\\t\\0\\u{fffd}\\u{fffd}\\u{f}W\";\n        let args = &[\n            Value::build_text(\"\".to_string()),\n            Value::from_f64(-1.8041807844761696e230),\n            Value::build_text(modifier_with_multibyte.to_string()),\n        ];\n        // Should not panic, just return an error or null\n        let _ = exec_strftime(args.iter());\n\n        // Also test the 'w' => weekday branch with similar input\n        let weekday_modifier = \"weekda\\u{fffd}\\u{fffd}\";\n        let args2 = &[\n            Value::build_text(\"\".to_string()),\n            Value::from_f64(0.0),\n            Value::build_text(weekday_modifier.to_string()),\n        ];\n        let _ = exec_strftime(args2.iter());\n    }\n}\n"
  },
  {
    "path": "core/functions/mod.rs",
    "content": "pub mod datetime;\npub mod printf;\n"
  },
  {
    "path": "core/functions/printf.rs",
    "content": "use std::fmt::Write;\nuse std::iter::{repeat_n, Peekable};\nuse std::str;\nuse std::str::Chars;\n\nuse crate::numeric::{format_float, str_to_i64, Numeric};\nuse crate::types::Value;\nuse crate::vdbe::Register;\n\n#[derive(Default, Clone)]\nstruct FormatFlags {\n    left_justify: bool,\n    force_sign: bool,\n    space_sign: bool,\n    zero_pad: bool,\n    alternate: bool,\n    comma_sep: bool,\n    alt_form_2: bool,\n}\n\nstruct FormatSpec {\n    flags: FormatFlags,\n    width: Option<usize>,\n    precision: Option<usize>,\n    spec_type: char,\n}\n\n/// Consume flag characters after '%', returning the FormatFlags.\n/// In SQLite, later flags override earlier ones for +/space conflicts.\nfn parse_flags(chars: &mut Peekable<Chars>) -> FormatFlags {\n    let mut flags = FormatFlags::default();\n    while let Some(&c) = chars.peek() {\n        match c {\n            '-' => {\n                flags.left_justify = true;\n            }\n            '+' => {\n                flags.force_sign = true;\n                flags.space_sign = false;\n            }\n            ' ' => {\n                flags.space_sign = true;\n                flags.force_sign = false;\n            }\n            '0' => {\n                flags.zero_pad = true;\n            }\n            '#' => {\n                flags.alternate = true;\n            }\n            ',' => {\n                flags.comma_sep = true;\n            }\n            '!' => {\n                flags.alt_form_2 = true;\n            }\n            _ => break,\n        }\n        chars.next();\n    }\n    flags\n}\n\n/// Parse a decimal integer from the character stream.\nfn parse_number(chars: &mut Peekable<Chars>) -> Option<usize> {\n    let mut n: usize = 0;\n    let mut found = false;\n    while let Some(&c) = chars.peek() {\n        if let Some(d) = c.to_digit(10) {\n            n = n.saturating_mul(10).saturating_add(d as usize);\n            found = true;\n            chars.next();\n        } else {\n            break;\n        }\n    }\n    if found {\n        Some(n)\n    } else {\n        None\n    }\n}\n\n/// Maximum width/precision to prevent OOM from adversarial format strings.\n/// SQLite uses int for width and caps output at SQLITE_MAX_LENGTH (1 billion),\n/// but we use a tighter limit since f64 only has ~17 meaningful digits anyway.\nconst MAX_WIDTH: usize = 1_000_000;\n\n/// Parse a full format specifier after the initial '%'.\n/// May consume arguments from `values` for `*` width/precision.\nfn parse_format_spec(\n    chars: &mut Peekable<Chars>,\n    values: &[Register],\n    args_index: &mut usize,\n) -> FormatSpec {\n    let mut flags = parse_flags(chars);\n\n    // Width — SQLite casts * width to C int (int32)\n    let width = if chars.peek() == Some(&'*') {\n        chars.next();\n        let w = get_arg_i64(values, args_index) as i32;\n        if w < 0 {\n            // Negative width means left-justify\n            flags.left_justify = true;\n            // SQLite: width = width >= -2147483647 ? -width : 0\n            if w > i32::MIN {\n                Some(((-w) as usize).min(MAX_WIDTH))\n            } else {\n                Some(0)\n            }\n        } else {\n            Some((w as usize).min(MAX_WIDTH))\n        }\n    } else {\n        parse_number(chars).map(|w| w.min(MAX_WIDTH))\n    };\n\n    // Precision — SQLite casts * precision to C int (int32), then negates if negative.\n    // For i32::MIN, negation wraps back to i32::MIN (still negative), treated as 0.\n    let precision = if chars.peek() == Some(&'.') {\n        chars.next();\n        if chars.peek() == Some(&'*') {\n            chars.next();\n            let p = get_arg_i64(values, args_index) as i32;\n            let p = if p < 0 { p.wrapping_neg() } else { p };\n            Some((p.max(0) as usize).min(MAX_WIDTH))\n        } else {\n            Some(parse_number(chars).unwrap_or(0).min(MAX_WIDTH))\n        }\n    } else {\n        None\n    };\n\n    // Skip length modifiers (l, ll, h, hh) - ignored in SQL context\n    while matches!(chars.peek(), Some(&'l') | Some(&'h')) {\n        chars.next();\n    }\n\n    // Type character\n    let spec_type = chars.next().unwrap_or('\\0');\n\n    FormatSpec {\n        flags,\n        width,\n        precision,\n        spec_type,\n    }\n}\n\n// ── Coercion helpers ────────────────────────────────────────────\n\n/// Get the next argument as i64 (for * width/precision), advancing args_index.\nfn get_arg_i64(values: &[Register], args_index: &mut usize) -> i64 {\n    if *args_index >= values.len() {\n        return 0;\n    }\n    let val = coerce_to_i64(values[*args_index].get_value());\n    *args_index += 1;\n    val\n}\n\n/// Coerce a Value to i64 for integer specifiers.\nfn coerce_to_i64(value: &Value) -> i64 {\n    match value {\n        Value::Numeric(Numeric::Integer(i)) => *i,\n        Value::Numeric(Numeric::Float(f)) => f64::from(*f) as i64,\n        Value::Text(t) => str_to_i64(t.as_str()).unwrap_or(0),\n        Value::Blob(b) => {\n            let s = String::from_utf8_lossy(b);\n            str_to_i64(s.as_ref()).unwrap_or(0)\n        }\n        Value::Null => 0,\n    }\n}\n\n/// Coerce a Value to f64 for float specifiers.\nfn coerce_to_f64(value: &Value) -> f64 {\n    match value {\n        Value::Numeric(Numeric::Float(f)) => f64::from(*f),\n        _ => match Option::<Numeric>::from(value) {\n            Some(Numeric::Integer(i)) => i as f64,\n            Some(Numeric::Float(f)) => f.into(),\n            None => 0.0,\n        },\n    }\n}\n\n/// Coerce a Value to String for string specifiers.\nfn coerce_to_string(value: &Value) -> String {\n    match value {\n        Value::Null => String::new(),\n        Value::Numeric(Numeric::Integer(i)) => i.to_string(),\n        Value::Numeric(Numeric::Float(f)) => format_float(f64::from(*f)),\n        Value::Text(t) => t.as_str().to_string(),\n        Value::Blob(b) => String::from_utf8_lossy(b).to_string(),\n    }\n}\n\n// ── Formatting helpers ──────────────────────────────────────────\n\n/// Insert comma separators into a digit string (e.g. \"1234567\" → \"1,234,567\").\nfn insert_commas(digits: &str) -> String {\n    let bytes = digits.as_bytes();\n    let len = bytes.len();\n    if len <= 3 {\n        return digits.to_string();\n    }\n    let mut result = String::with_capacity(len + len / 3);\n    let first_group = len % 3;\n    if first_group > 0 {\n        result.push_str(&digits[..first_group]);\n    }\n    for chunk in digits.as_bytes()[first_group..].chunks(3) {\n        if !result.is_empty() {\n            result.push(',');\n        }\n        // digits is always ASCII [0-9], guaranteed valid UTF-8\n        result.push_str(str::from_utf8(chunk).expect(\"digit string is ASCII\"));\n    }\n    result\n}\n\n/// Apply width padding to content. `sign_prefix` is prepended before content\n/// and is part of the total width calculation.\n/// `zero_overrides_left`: when true, the `0` flag takes priority over `-` (SQLite\n/// integer specifiers %d/%u/%x/%o). For float specifiers, pass false so that `-`\n/// overrides `0` per standard C behavior.\nfn apply_width(\n    output: &mut String,\n    sign_prefix: &str,\n    content: &str,\n    width: Option<usize>,\n    flags: &FormatFlags,\n    zero_overrides_left: bool,\n) {\n    // Use character count, not byte count, for width — SQLite counts characters.\n    let total_len = sign_prefix.chars().count() + content.chars().count();\n    let w = width.unwrap_or(0);\n    if total_len >= w {\n        output.push_str(sign_prefix);\n        output.push_str(content);\n        return;\n    }\n    let pad_len = w - total_len;\n    if flags.zero_pad && (zero_overrides_left || !flags.left_justify) {\n        output.push_str(sign_prefix);\n        for _ in 0..pad_len {\n            output.push('0');\n        }\n        output.push_str(content);\n    } else if flags.left_justify {\n        output.push_str(sign_prefix);\n        output.push_str(content);\n        for _ in 0..pad_len {\n            output.push(' ');\n        }\n    } else {\n        for _ in 0..pad_len {\n            output.push(' ');\n        }\n        output.push_str(sign_prefix);\n        output.push_str(content);\n    }\n}\n\n/// Compute sign prefix for a numeric value.\nfn sign_prefix(negative: bool, flags: &FormatFlags) -> &'static str {\n    if negative {\n        \"-\"\n    } else if flags.force_sign {\n        \"+\"\n    } else if flags.space_sign {\n        \" \"\n    } else {\n        \"\"\n    }\n}\n\n/// Strip trailing zeros from a decimal number string, but always keep at least\n/// one digit after the decimal point. E.g. \"3.140000\" → \"3.14\", \"1.000000\" → \"1.0\".\n/// If there is no decimal point, appends \".0\".\nfn ensure_decimal_strip_zeros(s: &str) -> String {\n    if s.contains('.') {\n        let trimmed = s.trim_end_matches('0');\n        // Keep at least one digit after '.'\n        if trimmed.ends_with('.') {\n            format!(\"{trimmed}0\")\n        } else {\n            trimmed.to_string()\n        }\n    } else {\n        format!(\"{s}.0\")\n    }\n}\n\n/// Build exponential mantissa from decoded digits: first digit, optional '.',\n/// then `precision` more digits (sqlite3.c:32581-32602 in etEXP path).\n/// Returns (mantissa_string, flag_dp).\nfn build_exp_mantissa(digits: &[u8], precision: usize, flags: &FormatFlags) -> (String, bool) {\n    let mut mantissa = String::new();\n    mantissa.push((b'0' + digits.first().copied().unwrap_or(0)) as char);\n\n    let flag_dp = precision > 0 || flags.alternate || flags.alt_form_2;\n    if flag_dp {\n        mantissa.push('.');\n    }\n    let mut j = 1_usize;\n    for _ in 0..precision {\n        if j < digits.len() {\n            mantissa.push((b'0' + digits[j]) as char);\n            j += 1;\n        } else {\n            mantissa.push('0');\n        }\n    }\n    (mantissa, flag_dp)\n}\n\n/// SQLite RTZ (Remove Trailing Zeros) — strip trailing zeros after the decimal\n/// point. If `keep_dot_zero` is true (alt_form_2 / `!` flag), a bare \".\" becomes\n/// \".0\"; otherwise the dot is also removed. (sqlite3.c:32604-32613)\nfn strip_trailing_zeros(s: &mut String, keep_dot_zero: bool) {\n    let trimmed = s.trim_end_matches('0');\n    *s = if trimmed.ends_with('.') {\n        if keep_dot_zero {\n            format!(\"{trimmed}0\")\n        } else {\n            trimmed.trim_end_matches('.').to_string()\n        }\n    } else {\n        trimmed.to_string()\n    };\n}\n\n/// Pad a digit string to at least `min_digits` characters with leading zeros.\nfn pad_with_precision(digits: String, precision: Option<usize>) -> String {\n    let min_digits = precision.unwrap_or(1);\n    if digits.len() < min_digits {\n        \"0\".repeat(min_digits - digits.len()) + &digits\n    } else {\n        digits\n    }\n}\n\n/// Dekker-style double-double multiplication, ported from SQLite's `dekkerMul2`\n/// (sqlite3.c:36334). Multiplies the double-double number x = (x[0], x[1])\n/// by the double-double constant (y, yy).\nfn dekker_mul2(x: &mut [f64; 2], y: f64, yy: f64) {\n    let hx = f64::from_bits(x[0].to_bits() & 0xffff_ffff_fc00_0000);\n    let tx = x[0] - hx;\n    let hy = f64::from_bits(y.to_bits() & 0xffff_ffff_fc00_0000);\n    let ty = y - hy;\n    let p = hx * hy;\n    let q = hx * ty + tx * hy;\n    let c = p + q;\n    let cc = p - c + q + tx * ty;\n    let cc = x[0] * yy + x[1] * y + cc;\n    x[0] = c + cc;\n    x[1] = c - x[0] + cc;\n}\n\n/// Decode a positive finite float into significant decimal digits and a\n/// decimal point position, then round to `i_round` significant digits\n/// (capped at `max_round`).\n///\n/// This is a faithful port of SQLite's `sqlite3FpDecode` (sqlite3.c:36884).\n/// It uses Dekker-style double-double arithmetic to scale the value into a\n/// u64-representable range, producing the same digit sequences as SQLite.\n///\n/// * `i_round` – for `%f` pass `-(precision as i32)`, for `%e` pass\n///   `precision + 1`, for `%g` pass `precision`.\n/// * `max_round` – typically 16 (or 26 with `!` flag).\n///\n/// Returns `(digits, iDP)` where `digits` is a non-empty vector of digit\n/// values 0–9 (trailing zeros stripped) and `iDP` is the number of digits\n/// before the decimal point.\nfn fp_decode(r: f64, i_round: i32, max_round: usize) -> (Vec<u8>, i32) {\n    debug_assert!(r > 0.0);\n\n    // SQLite (sqlite3.c:32502-32505): infinity with zero-pad is represented\n    // as digit '9' at decimal position 1000 (i.e. 9 * 10^999).\n    if r.is_infinite() {\n        return (vec![9], 1000);\n    }\n\n    let mut rr = [r, 0.0_f64];\n    let mut exp: i32 = 0;\n\n    // Scale r into [9.223_372_036_854_774_784e17, 9.223_372_036_854_774_784e18]\n    // using Dekker multiplication with error-compensation constants.\n    // Constants are copied verbatim from sqlite3.c:36930-36955.\n    #[allow(clippy::excessive_precision)]\n    if rr[0] > 9.223_372_036_854_774_784e+18 {\n        while rr[0] > 9.223_372_036_854_774_784e+118 {\n            exp += 100;\n            dekker_mul2(&mut rr, 1.0e-100, -1.999_189_980_260_288_361_96e-117);\n        }\n        while rr[0] > 9.223_372_036_854_774_784e+28 {\n            exp += 10;\n            dekker_mul2(&mut rr, 1.0e-10, -3.643_219_731_549_774_157_9e-27);\n        }\n        while rr[0] > 9.223_372_036_854_774_784e+18 {\n            exp += 1;\n            dekker_mul2(&mut rr, 1.0e-01, -5.551_115_123_125_782_702_1e-18);\n        }\n    } else {\n        while rr[0] < 9.223_372_036_854_774_784e-83 {\n            exp -= 100;\n            dekker_mul2(&mut rr, 1.0e+100, -1.590_289_110_975_991_804_6e+83);\n        }\n        while rr[0] < 9.223_372_036_854_774_784e+07 {\n            exp -= 10;\n            dekker_mul2(&mut rr, 1.0e+10, 0.0);\n        }\n        while rr[0] < 9.223_372_036_854_774_78e+17 {\n            exp -= 1;\n            dekker_mul2(&mut rr, 1.0e+01, 0.0);\n        }\n    }\n\n    // Convert double-double to u64\n    let v: u64 = if rr[1] < 0.0 {\n        (rr[0] as u64).wrapping_sub((-rr[1]) as u64)\n    } else {\n        (rr[0] as u64).wrapping_add(rr[1] as u64)\n    };\n\n    // Extract decimal digits from u64\n    let mut buf = Vec::with_capacity(20);\n    let mut temp = v;\n    while temp > 0 {\n        buf.push((temp % 10) as u8);\n        temp /= 10;\n    }\n    buf.reverse();\n\n    let n = buf.len();\n    let mut dp = n as i32 + exp;\n\n    // ── Rounding (sqlite3.c:36968-36997) ──────────────────────────\n    let mut i_round = i_round;\n    if i_round <= 0 {\n        i_round = dp - i_round;\n        if i_round == 0 && !buf.is_empty() && buf[0] >= 5 {\n            buf.insert(0, 0);\n            dp += 1;\n            i_round = 1;\n        }\n    }\n\n    let n = buf.len();\n    if i_round > 0 && ((i_round as usize) < n || n > max_round) {\n        let i_round = if (i_round as usize) > max_round {\n            max_round\n        } else {\n            i_round as usize\n        };\n\n        let mut carried_past = false;\n        if i_round < n && buf[i_round] >= 5 {\n            let mut j = i_round;\n            loop {\n                if j == 0 {\n                    buf.insert(0, 1);\n                    dp += 1;\n                    carried_past = true;\n                    break;\n                }\n                j -= 1;\n                buf[j] += 1;\n                if buf[j] <= 9 {\n                    break;\n                }\n                buf[j] = 0;\n            }\n        }\n\n        let keep = if carried_past { i_round + 1 } else { i_round };\n        buf.truncate(keep);\n    }\n\n    // Trim trailing zeros (sqlite3.c:37001-37003)\n    while buf.len() > 1 && *buf.last().unwrap() == 0 {\n        buf.pop();\n    }\n\n    (buf, dp)\n}\n\n/// Build a fixed-point decimal string from a positive float, extracting\n/// significant digits via `fp_decode` (a faithful port of SQLite's\n/// `sqlite3FpDecode`) then placing them according to the `etFLOAT` layout.\nfn format_fixed_from_digits(abs_f: f64, precision: usize, max_sig: usize) -> String {\n    if abs_f == 0.0 {\n        return if precision == 0 {\n            \"0\".to_string()\n        } else {\n            format!(\"0.{}\", \"0\".repeat(precision))\n        };\n    }\n\n    let i_round = -(precision as i32);\n    let (digits, dp) = fp_decode(abs_f, i_round, max_sig);\n\n    // ── Integer part (sqlite3.c:32581-32588) ───────────────────────\n    let mut result = String::new();\n    let mut j: usize = 0;\n    if dp <= 0 {\n        result.push('0');\n    } else {\n        for _ in 0..dp {\n            if j < digits.len() {\n                result.push((b'0' + digits[j]) as char);\n                j += 1;\n            } else {\n                result.push('0');\n            }\n        }\n    }\n\n    if precision == 0 {\n        return result;\n    }\n\n    // ── Fractional part (sqlite3.c:32591-32602) ────────────────────\n    result.push('.');\n\n    // Leading zeros for numbers < 1 (e2 < 0 in SQLite, dp <= 0 here)\n    let mut e2 = dp - 1;\n    let mut frac_remaining = precision;\n    e2 += 1; // mirrors the for(e2++;...) in SQLite\n    while e2 < 0 && frac_remaining > 0 {\n        result.push('0');\n        frac_remaining -= 1;\n        e2 += 1;\n    }\n\n    // Significant digits\n    while frac_remaining > 0 {\n        if j < digits.len() {\n            result.push((b'0' + digits[j]) as char);\n            j += 1;\n        } else {\n            result.push('0');\n        }\n        frac_remaining -= 1;\n    }\n\n    result\n}\n\n/// Limit a formatted numeric string to `max_sig` significant digits, rounding\n/// at the boundary. This matches SQLite's behavior of not showing IEEE 754\n/// mantissa noise beyond the float's representable precision.\n#[cfg(test)]\nfn limit_significant_digits(s: &str, max_sig: usize) -> String {\n    let chars: Vec<char> = s.chars().collect();\n    let mut result: Vec<char> = chars.clone();\n\n    // Find positions of all digits and track significant digit count\n    let mut digit_positions: Vec<usize> = Vec::new();\n    let mut sig_count = 0;\n    let mut first_nonzero = false;\n    for (i, &c) in chars.iter().enumerate() {\n        if !c.is_ascii_digit() {\n            continue;\n        }\n        if c != '0' || first_nonzero {\n            first_nonzero = true;\n            sig_count += 1;\n        }\n        digit_positions.push(i);\n    }\n\n    if sig_count <= max_sig {\n        return s.to_string();\n    }\n\n    // Find the index in digit_positions where the (max_sig+1)th significant digit is\n    let mut sig_seen = 0;\n    let mut round_pos = None; // position of the (max_sig+1)th sig digit\n    let mut last_sig_pos = None; // position of the max_sig-th sig digit\n    let mut first_nonzero2 = false;\n    for &pos in &digit_positions {\n        let c = chars[pos];\n        if c != '0' || first_nonzero2 {\n            first_nonzero2 = true;\n            sig_seen += 1;\n        }\n        if sig_seen == max_sig {\n            last_sig_pos = Some(pos);\n        }\n        if sig_seen == max_sig + 1 {\n            round_pos = Some(pos);\n            break;\n        }\n    }\n\n    let (Some(round_pos), Some(_last_sig_pos)) = (round_pos, last_sig_pos) else {\n        return s.to_string();\n    };\n\n    // Check if we need to round up (digit at round_pos >= 5)\n    let round_digit = chars[round_pos].to_digit(10).unwrap();\n\n    // Zero out all digits from round_pos onward\n    for &pos in &digit_positions {\n        if pos >= round_pos {\n            result[pos] = '0';\n        }\n    }\n\n    // If round digit >= 5, propagate carry backwards\n    if round_digit >= 5 {\n        // Walk backwards through digit positions before round_pos\n        let mut carry = true;\n        for &pos in digit_positions.iter().rev() {\n            if pos >= round_pos {\n                continue;\n            }\n            if !carry {\n                break;\n            }\n            let d = result[pos].to_digit(10).unwrap() + 1;\n            if d >= 10 {\n                result[pos] = '0';\n            } else {\n                result[pos] = char::from_digit(d, 10).unwrap();\n                carry = false;\n            }\n        }\n        // If carry propagated past all digits, insert a '1' before the first digit\n        if carry {\n            let first_digit_pos = digit_positions[0];\n            result.insert(first_digit_pos, '1');\n        }\n    }\n\n    result.into_iter().collect()\n}\n\n/// Handle NaN and non-zero_pad Infinity for float specifiers.\n/// NaN: zero_pad → \"null\", otherwise → \"NaN\"\n/// Infinity (non-zero_pad): \"Inf\"/\"-Inf\"/\"+Inf\"\n/// Infinity with zero_pad is NOT handled here — it falls through to normal\n/// formatting where fp_decode returns digits=[9], dp=1000 (sqlite3.c:32502-32505).\nfn format_special_float(output: &mut String, f: f64, spec: &FormatSpec) {\n    // Width padding uses spaces only (SQLite breaks out before zero-pad code).\n    let mut space_flags = spec.flags.clone();\n    space_flags.zero_pad = false;\n\n    if f.is_nan() {\n        let text = if spec.flags.zero_pad { \"null\" } else { \"NaN\" };\n        apply_width(output, \"\", text, spec.width, &space_flags, false);\n        return;\n    }\n\n    // Non-zero_pad infinity\n    let prefix = sign_prefix(f < 0.0, &spec.flags);\n    apply_width(output, prefix, \"Inf\", spec.width, &space_flags, false);\n}\n\n// ── Per-specifier formatters ────────────────────────────────────\n\nfn format_signed_int(output: &mut String, value: &Value, spec: &FormatSpec) {\n    let i = coerce_to_i64(value);\n    let negative = i < 0;\n    let digits = i.unsigned_abs().to_string();\n\n    let mut padded = pad_with_precision(digits, spec.precision);\n\n    let prefix = sign_prefix(negative, &spec.flags);\n\n    if spec.flags.comma_sep && spec.flags.zero_pad {\n        // SQLite: zero-pad digits to (width - prefix.len()), then insert commas.\n        // Commas are not counted in the width. Left-justify is ignored when\n        // both comma and zero-pad are set.\n        let w = spec.width.unwrap_or(0);\n        let digit_target = w.saturating_sub(prefix.len());\n        if padded.len() < digit_target {\n            padded = \"0\".repeat(digit_target - padded.len()) + &padded;\n        }\n        output.push_str(prefix);\n        output.push_str(&insert_commas(&padded));\n    } else if spec.flags.comma_sep {\n        padded = insert_commas(&padded);\n        apply_width(output, prefix, &padded, spec.width, &spec.flags, true);\n    } else {\n        apply_width(output, prefix, &padded, spec.width, &spec.flags, true);\n    }\n}\n\nfn format_unsigned_int(output: &mut String, value: &Value, spec: &FormatSpec) {\n    let i = coerce_to_i64(value);\n    let u = i as u64;\n    let digits = u.to_string();\n\n    let mut padded = pad_with_precision(digits, spec.precision);\n\n    if spec.flags.comma_sep && spec.flags.zero_pad {\n        // SQLite: zero-pad digits to width, then insert commas.\n        // Commas are not counted in the width. Left-justify is ignored when\n        // both comma and zero-pad are set.\n        let w = spec.width.unwrap_or(0);\n        if padded.len() < w {\n            padded = \"0\".repeat(w - padded.len()) + &padded;\n        }\n        output.push_str(&insert_commas(&padded));\n    } else if spec.flags.comma_sep {\n        padded = insert_commas(&padded);\n        apply_width(output, \"\", &padded, spec.width, &spec.flags, true);\n    } else {\n        apply_width(output, \"\", &padded, spec.width, &spec.flags, true);\n    }\n}\n\nfn format_hex(output: &mut String, value: &Value, spec: &FormatSpec, uppercase: bool) {\n    let i = coerce_to_i64(value);\n    let u = i as u64;\n    let digits = if uppercase {\n        format!(\"{u:X}\")\n    } else {\n        format!(\"{u:x}\")\n    };\n\n    let padded = pad_with_precision(digits, spec.precision);\n\n    let prefix = if spec.flags.alternate && u != 0 {\n        // SQLite: %p always uses lowercase \"0x\" prefix even with uppercase digits.\n        // %X uses \"0X\". Both from aPrefix[] in sqlite3.c:32037.\n        if uppercase && spec.spec_type != 'p' {\n            \"0X\"\n        } else {\n            \"0x\"\n        }\n    } else {\n        \"\"\n    };\n\n    // In SQLite, when # and 0 flags are both set, width applies to digits only\n    // and the prefix is added on top (not counted in width).\n    if spec.flags.alternate && spec.flags.zero_pad && !prefix.is_empty() {\n        let w = spec.width.unwrap_or(0);\n        let zero_padded = if padded.len() < w {\n            \"0\".repeat(w - padded.len()) + &padded\n        } else {\n            padded\n        };\n        output.push_str(prefix);\n        output.push_str(&zero_padded);\n    } else {\n        apply_width(output, prefix, &padded, spec.width, &spec.flags, true);\n    }\n}\n\nfn format_octal(output: &mut String, value: &Value, spec: &FormatSpec) {\n    let i = coerce_to_i64(value);\n    let u = i as u64;\n    let digits = format!(\"{u:o}\");\n\n    let padded = pad_with_precision(digits, spec.precision);\n\n    // SQLite always adds \"0\" prefix for octal with # flag when value is non-zero,\n    // even if precision padding already added leading zeros.\n    let prefix = if spec.flags.alternate && u != 0 {\n        \"0\"\n    } else {\n        \"\"\n    };\n\n    // In SQLite, when # and 0 flags are both set, width applies to digits only\n    // and the prefix is added on top (not counted in width).\n    if spec.flags.alternate && spec.flags.zero_pad && !prefix.is_empty() {\n        let w = spec.width.unwrap_or(0);\n        let zero_padded = if padded.len() < w {\n            \"0\".repeat(w - padded.len()) + &padded\n        } else {\n            padded\n        };\n        output.push_str(prefix);\n        output.push_str(&zero_padded);\n    } else {\n        apply_width(output, prefix, &padded, spec.width, &spec.flags, true);\n    }\n}\n\nfn format_float_decimal(output: &mut String, value: &Value, spec: &FormatSpec) {\n    let f = coerce_to_f64(value);\n\n    // SQLite source (sqlite3.c:32497-32518): special float handling.\n    // NaN and non-zero_pad Inf break out before normal formatting.\n    // Inf with zero_pad falls through to normal code with digits=[9], dp=1000.\n    if f.is_nan() || (f.is_infinite() && !spec.flags.zero_pad) {\n        format_special_float(output, f, spec);\n        return;\n    }\n\n    // Cap precision to avoid extreme allocations\n    let precision = spec.precision.unwrap_or(6).min(1000);\n    let negative = f < 0.0;\n    let abs_f = f.abs();\n\n    // SQLite source: sqlite3FpDecode uses 16 sig digits, or 26 with ! flag\n    let max_sig = if spec.flags.alt_form_2 { 26 } else { 16 };\n\n    // Build the base decimal string using digit extraction (matches SQLite's\n    // sqlite3FpDecode + etFLOAT formatting).  This replaces the previous\n    // approach of Rust's format! + limit_significant_digits, which could\n    // round the leading digit differently for very large numbers.\n    let formatted = if spec.flags.alt_form_2 && precision == 0 {\n        // %!.0f: force decimal point with one zero, e.g. \"3.0\"\n        let mut s = format_fixed_from_digits(abs_f, 0, max_sig);\n        s.push_str(\".0\");\n        s\n    } else if precision == 0 {\n        let mut s = format_fixed_from_digits(abs_f, 0, max_sig);\n        if spec.flags.alternate {\n            s.push('.');\n        }\n        s\n    } else {\n        let s = format_fixed_from_digits(abs_f, precision, max_sig);\n        if spec.flags.alt_form_2 {\n            ensure_decimal_strip_zeros(&s)\n        } else {\n            s\n        }\n    };\n\n    // Apply comma separator to integer part\n    let content = if spec.flags.comma_sep {\n        if let Some(dot_pos) = formatted.find('.') {\n            let int_part = &formatted[..dot_pos];\n            let frac_part = &formatted[dot_pos..];\n            insert_commas(int_part) + frac_part\n        } else {\n            insert_commas(&formatted)\n        }\n    } else {\n        formatted\n    };\n\n    // SQLite 3.51+ (sqlite3.c:32520-32532): Suppress minus sign for %f with #\n    // when displayed value is zero and no +/space flag is set.\n    let negative =\n        if negative && spec.flags.alternate && !spec.flags.force_sign && !spec.flags.space_sign {\n            !content.bytes().all(|b| b == b'0' || b == b'.' || b == b',')\n        } else {\n            negative\n        };\n\n    let prefix = sign_prefix(negative, &spec.flags);\n    apply_width(output, prefix, &content, spec.width, &spec.flags, false);\n}\n\nfn format_exponential(output: &mut String, value: &Value, spec: &FormatSpec, uppercase: bool) {\n    let f = coerce_to_f64(value);\n\n    if f.is_nan() || (f.is_infinite() && !spec.flags.zero_pad) {\n        format_special_float(output, f, spec);\n        return;\n    }\n\n    format_exponential_inner(output, f, spec, uppercase);\n}\n\nfn format_exponential_inner(output: &mut String, f: f64, spec: &FormatSpec, uppercase: bool) {\n    let precision = spec.precision.unwrap_or(6).min(1000);\n    let negative = f < 0.0;\n    let abs_f = f.abs();\n    let e_char = if uppercase { 'E' } else { 'e' };\n    let max_sig = if spec.flags.alt_form_2 { 26 } else { 16 };\n\n    // Handle zero specially (fp_decode requires positive finite input)\n    if abs_f == 0.0 {\n        let flag_dp = precision > 0 || spec.flags.alternate || spec.flags.alt_form_2;\n        let mut mantissa = \"0\".to_string();\n        if flag_dp {\n            mantissa.push('.');\n            for _ in 0..precision {\n                mantissa.push('0');\n            }\n        }\n        if spec.flags.alt_form_2 {\n            mantissa = ensure_decimal_strip_zeros(&mantissa);\n        }\n        let content = format!(\"{mantissa}{e_char}+00\");\n        let prefix = sign_prefix(negative, &spec.flags);\n        apply_width(output, prefix, &content, spec.width, &spec.flags, false);\n        return;\n    }\n\n    // Use fp_decode with iRound = precision+1 (total significant digits for %e)\n    let i_round = (precision + 1) as i32;\n    let (digits, dp) = fp_decode(abs_f, i_round, max_sig);\n    let exp = dp - 1;\n\n    let (mut mantissa, flag_dp) = build_exp_mantissa(&digits, precision, &spec.flags);\n\n    // RTZ (remove trailing zeros): only with ! flag for %e (sqlite3.c:32557)\n    if spec.flags.alt_form_2 && flag_dp {\n        strip_trailing_zeros(&mut mantissa, true);\n    }\n\n    let content = format!(\"{mantissa}{e_char}{exp:+03}\");\n    let prefix = sign_prefix(negative, &spec.flags);\n    apply_width(output, prefix, &content, spec.width, &spec.flags, false);\n}\n\nfn format_general(output: &mut String, value: &Value, spec: &FormatSpec, uppercase: bool) {\n    let f = coerce_to_f64(value);\n\n    if f.is_nan() || (f.is_infinite() && !spec.flags.zero_pad) {\n        format_special_float(output, f, spec);\n        return;\n    }\n\n    format_general_inner(output, f, spec, uppercase);\n}\n\nfn format_general_inner(output: &mut String, f: f64, spec: &FormatSpec, uppercase: bool) {\n    // SQLite: if precision == 0, set to 1 (line 32491)\n    let precision = spec.precision.unwrap_or(6).clamp(1, 1000);\n    let negative = f < 0.0;\n    let abs_f = f.abs();\n    let e_char = if uppercase { 'E' } else { 'e' };\n    let max_sig = if spec.flags.alt_form_2 { 26 } else { 16 };\n\n    // Handle zero specially\n    if abs_f == 0.0 {\n        let flag_rtz = !spec.flags.alternate;\n        let flag_dp = precision > 1 || spec.flags.alternate || spec.flags.alt_form_2;\n        let mut s = \"0\".to_string();\n        if flag_dp {\n            s.push('.');\n            for _ in 1..precision {\n                s.push('0');\n            }\n        }\n        if flag_rtz && flag_dp {\n            strip_trailing_zeros(&mut s, spec.flags.alt_form_2);\n        }\n        let prefix = sign_prefix(negative, &spec.flags);\n        apply_width(output, prefix, &s, spec.width, &spec.flags, false);\n        return;\n    }\n\n    // Call fp_decode with iRound = precision (significant digits for %g)\n    let (digits, dp) = fp_decode(abs_f, precision as i32, max_sig);\n    let exp = dp - 1;\n\n    // SQLite: precision-- then check (lines 32547-32555)\n    let precision = precision - 1;\n    // flag_rtz for generic: ON unless # flag (line 32549)\n    let flag_rtz = !spec.flags.alternate;\n\n    let use_exp = exp < -4 || exp > precision as i32;\n\n    let content = if use_exp {\n        // ── Exponential notation ──────────────────────────────────\n        let (mut mantissa, flag_dp) = build_exp_mantissa(&digits, precision, &spec.flags);\n        if flag_rtz && flag_dp {\n            strip_trailing_zeros(&mut mantissa, spec.flags.alt_form_2);\n        }\n        format!(\"{mantissa}{e_char}{exp:+03}\")\n    } else {\n        // ── Fixed-point notation ──────────────────────────────────\n        // SQLite: precision = precision - exp (line 32553), giving digits after decimal\n        let frac_precision = if precision as i32 > exp {\n            (precision as i32 - exp) as usize\n        } else {\n            0\n        };\n\n        // Build fixed-point string from decoded digits\n        let mut s = String::new();\n        let mut j: usize = 0;\n\n        // Integer part\n        if dp <= 0 {\n            s.push('0');\n        } else {\n            for _ in 0..dp {\n                if j < digits.len() {\n                    s.push((b'0' + digits[j]) as char);\n                    j += 1;\n                } else {\n                    s.push('0');\n                }\n            }\n        }\n\n        let flag_dp = frac_precision > 0 || spec.flags.alternate || spec.flags.alt_form_2;\n        if flag_dp {\n            s.push('.');\n        }\n\n        // Leading zeros for numbers < 1\n        let mut e2 = dp - 1;\n        let mut frac_remaining = frac_precision;\n        e2 += 1;\n        while e2 < 0 && frac_remaining > 0 {\n            s.push('0');\n            frac_remaining -= 1;\n            e2 += 1;\n        }\n\n        // Significant digits in fractional part\n        while frac_remaining > 0 {\n            if j < digits.len() {\n                s.push((b'0' + digits[j]) as char);\n                j += 1;\n            } else {\n                s.push('0');\n            }\n            frac_remaining -= 1;\n        }\n\n        if flag_rtz && flag_dp {\n            strip_trailing_zeros(&mut s, spec.flags.alt_form_2);\n        }\n\n        s\n    };\n\n    // Apply comma separator to integer part (only meaningful for fixed-point)\n    let content = if spec.flags.comma_sep {\n        if let Some(dot_pos) = content.find('.') {\n            let int_part = &content[..dot_pos];\n            let frac_part = &content[dot_pos..];\n            insert_commas(int_part) + frac_part\n        } else if !content.contains('e') && !content.contains('E') {\n            insert_commas(&content)\n        } else {\n            content\n        }\n    } else {\n        content\n    };\n\n    let prefix = sign_prefix(negative, &spec.flags);\n    apply_width(output, prefix, &content, spec.width, &spec.flags, false);\n}\n\nfn format_string(output: &mut String, value: &Value, spec: &FormatSpec) {\n    // For blobs, truncate at first NUL byte (SQLite behavior)\n    let s = match value {\n        Value::Blob(b) => {\n            let end = b.iter().position(|&byte| byte == 0).unwrap_or(b.len());\n            String::from_utf8_lossy(&b[..end]).to_string()\n        }\n        _ => coerce_to_string(value),\n    };\n    let truncated = if let Some(prec) = spec.precision {\n        // Truncate by character count (not bytes). SQLite uses bytes by default\n        // and chars with !, but since blobs are already lossy-converted to UTF-8,\n        // character-based truncation avoids mid-char splits.\n        if let Some((byte_idx, _)) = s.char_indices().nth(prec) {\n            &s[..byte_idx]\n        } else {\n            &s\n        }\n    } else {\n        &s\n    };\n\n    // Zero-pad flag is ignored for string specifiers\n    let mut flags = spec.flags.clone();\n    flags.zero_pad = false;\n    apply_width(output, \"\", truncated, spec.width, &flags, false);\n}\n\nfn format_char(output: &mut String, value: &Value, spec: &FormatSpec) {\n    // In SQLite SQL context, %c takes the first character of the string representation\n    let c = match value {\n        Value::Text(t) => t.value.chars().next().unwrap_or('\\0'),\n        _ => {\n            let s = coerce_to_string(value);\n            s.chars().next().unwrap_or('\\0')\n        }\n    };\n\n    // NUL character produces no output (matches SQLite behavior)\n    if c == '\\0' {\n        return;\n    }\n\n    // Precision on %c repeats the character that many times (default 1)\n    let repeat = spec.precision.unwrap_or(1).max(1);\n    let s: String = repeat_n(c, repeat).collect();\n\n    // Zero-pad flag is ignored for char specifiers\n    let mut flags = spec.flags.clone();\n    flags.zero_pad = false;\n    apply_width(output, \"\", &s, spec.width, &flags, false);\n}\n\n/// Escape control characters (0x00-0x1f, 0x7f) as \\uXXXX for %#q/%#Q.\nfn escape_control_chars(s: &str) -> String {\n    let mut result = String::with_capacity(s.len());\n    for c in s.chars() {\n        if c.is_ascii_control() {\n            write!(result, \"\\\\u{:04x}\", c as u32).unwrap();\n        } else if c == '\\\\' {\n            result.push_str(\"\\\\\\\\\");\n        } else {\n            result.push(c);\n        }\n    }\n    result\n}\n\nfn format_sql_quote(output: &mut String, value: &Value, spec: &FormatSpec) {\n    // %q: double single quotes; NULL → (NULL). Supports width/precision.\n    // %#q: also escape control characters as \\uXXXX.\n    let mut flags = spec.flags.clone();\n    flags.zero_pad = false;\n    match value {\n        Value::Null => {\n            let truncated = truncate_to_precision(\"(NULL)\", spec.precision);\n            apply_width(output, \"\", truncated, spec.width, &flags, false);\n        }\n        _ => {\n            let s = coerce_to_string(value);\n            let truncated = truncate_to_precision(&s, spec.precision);\n            let mut escaped = truncated.replace('\\'', \"''\");\n            if spec.flags.alternate {\n                escaped = escape_control_chars(&escaped);\n            }\n            apply_width(output, \"\", &escaped, spec.width, &flags, false);\n        }\n    }\n}\n\nfn format_sql_quote_wrap(output: &mut String, value: &Value, spec: &FormatSpec) {\n    // %Q: like %q but wrapped in quotes; NULL → unquoted NULL. Supports width/precision.\n    // %#Q: also escape control characters as \\uXXXX.\n    let mut flags = spec.flags.clone();\n    flags.zero_pad = false;\n    match value {\n        Value::Null => {\n            let truncated = truncate_to_precision(\"NULL\", spec.precision);\n            apply_width(output, \"\", truncated, spec.width, &flags, false);\n        }\n        _ => {\n            let s = coerce_to_string(value);\n            let truncated = truncate_to_precision(&s, spec.precision);\n            let mut escaped = truncated.replace('\\'', \"''\");\n            // %#Q: escape control chars and wrap with unistr('...') if any are present.\n            let use_unistr = if spec.flags.alternate {\n                let has_ctrl = escaped.bytes().any(|b| b <= 0x1f);\n                if has_ctrl {\n                    escaped = escape_control_chars(&escaped);\n                    true\n                } else {\n                    false\n                }\n            } else {\n                false\n            };\n            let mut quoted = String::with_capacity(escaped.len() + 10);\n            if use_unistr {\n                quoted.push_str(\"unistr('\");\n            } else {\n                quoted.push('\\'');\n            }\n            quoted.push_str(&escaped);\n            if use_unistr {\n                quoted.push_str(\"')\");\n            } else {\n                quoted.push('\\'');\n            }\n            apply_width(output, \"\", &quoted, spec.width, &flags, false);\n        }\n    }\n}\n\nfn format_sql_identifier(output: &mut String, value: &Value, spec: &FormatSpec) {\n    // %w: double double-quotes (no wrapping quotes in SQL context); NULL → (NULL).\n    let mut flags = spec.flags.clone();\n    flags.zero_pad = false;\n    match value {\n        Value::Null => {\n            let truncated = truncate_to_precision(\"(NULL)\", spec.precision);\n            apply_width(output, \"\", truncated, spec.width, &flags, false);\n        }\n        _ => {\n            let s = coerce_to_string(value);\n            let truncated = truncate_to_precision(&s, spec.precision);\n            let escaped = truncated.replace('\"', \"\\\"\\\"\");\n            apply_width(output, \"\", &escaped, spec.width, &flags, false);\n        }\n    }\n}\n\nfn format_ordinal(output: &mut String, value: &Value, spec: &FormatSpec) {\n    // %r: format integer as ordinal (1st, 2nd, 3rd, 4th, ...)\n    let i = coerce_to_i64(value);\n    let negative = i < 0;\n    let abs = i.unsigned_abs();\n    let suffix = match (abs % 100, abs % 10) {\n        (11..=13, _) => \"th\",\n        (_, 1) => \"st\",\n        (_, 2) => \"nd\",\n        (_, 3) => \"rd\",\n        _ => \"th\",\n    };\n    let mut digits = abs.to_string();\n    // Precision is the total width of digits+suffix, not just digits.\n    let digit_prec = spec.precision.map(|p| p.saturating_sub(suffix.len()));\n    digits = pad_with_precision(digits, digit_prec);\n    let prefix = sign_prefix(negative, &spec.flags);\n\n    // Zero-pad: pad the digit portion to fill width (0 overrides - like integers)\n    if spec.flags.zero_pad {\n        let w = spec.width.unwrap_or(0);\n        let content_chars = prefix.len() + digits.len() + suffix.len();\n        if content_chars < w {\n            digits = \"0\".repeat(w - content_chars) + &digits;\n        }\n        output.push_str(prefix);\n        output.push_str(&digits);\n        output.push_str(suffix);\n    } else {\n        let content = format!(\"{digits}{suffix}\");\n        apply_width(output, prefix, &content, spec.width, &spec.flags, false);\n    }\n}\n\n/// Truncate a string to at most `precision` characters.\nfn truncate_to_precision(s: &str, precision: Option<usize>) -> &str {\n    match precision {\n        Some(prec) => {\n            // Truncate by character count. SQLite uses byte count by default\n            // and character count with the ! flag, but since Turso is UTF-8 only,\n            // character-based truncation is always correct for our strings.\n            if let Some((byte_idx, _)) = s.char_indices().nth(prec) {\n                &s[..byte_idx]\n            } else {\n                s\n            }\n        }\n        _ => s,\n    }\n}\n\n// ── Main entry point ────────────────────────────────────────────\n\npub fn exec_printf(values: &[Register]) -> crate::Result<Value> {\n    if values.is_empty() {\n        return Ok(Value::Null);\n    }\n\n    // SQLite converts the format argument to text if not already text.\n    let format_value = values[0].get_value();\n    let fmt_owned: String;\n    let format_str = match &format_value {\n        Value::Text(t) => t.as_str(),\n        Value::Null => return Ok(Value::Null),\n        Value::Numeric(Numeric::Integer(i)) => {\n            fmt_owned = i.to_string();\n            fmt_owned.as_str()\n        }\n        Value::Numeric(Numeric::Float(f)) => {\n            fmt_owned = format_float(f64::from(*f));\n            fmt_owned.as_str()\n        }\n        Value::Blob(b) => {\n            fmt_owned = String::from_utf8_lossy(b).to_string();\n            fmt_owned.as_str()\n        }\n    };\n\n    let mut result = String::new();\n    let mut args_index = 1;\n    let mut chars = format_str.chars().peekable();\n    // Track whether any output or specifier processing happened. SQLite's\n    // internal StrAccum buffer stays NULL until something triggers allocation\n    // (any literal text, %%, trailing %, or any specifier including %n).\n    // When an unknown specifier triggers early return before any allocation,\n    // the result is NULL. Otherwise it's the accumulated text (possibly \"\").\n    let mut touched = false;\n\n    while let Some(c) = chars.next() {\n        if c != '%' {\n            touched = true;\n            result.push(c);\n            continue;\n        }\n\n        // Check for %%\n        if chars.peek() == Some(&'%') {\n            touched = true;\n            chars.next();\n            result.push('%');\n            continue;\n        }\n\n        // Trailing '%' at end of format string is preserved\n        if chars.peek().is_none() {\n            result.push('%');\n            break;\n        }\n\n        // Parse the full format specifier\n        let spec = parse_format_spec(&mut chars, values, &mut args_index);\n\n        // Get the argument value (or use NULL if missing)\n        let needs_arg = !matches!(spec.spec_type, 'n' | '\\0');\n        let null_val = Value::Null;\n        let arg = if needs_arg {\n            if args_index < values.len() {\n                let v = values[args_index].get_value();\n                args_index += 1;\n                v\n            } else {\n                &null_val\n            }\n        } else {\n            &null_val\n        };\n\n        match spec.spec_type {\n            'd' | 'i' => format_signed_int(&mut result, arg, &spec),\n            'u' => format_unsigned_int(&mut result, arg, &spec),\n            'f' => format_float_decimal(&mut result, arg, &spec),\n            'e' => format_exponential(&mut result, arg, &spec, false),\n            'E' => format_exponential(&mut result, arg, &spec, true),\n            'g' => format_general(&mut result, arg, &spec, false),\n            'G' => format_general(&mut result, arg, &spec, true),\n            'x' => format_hex(&mut result, arg, &spec, false),\n            'X' => format_hex(&mut result, arg, &spec, true),\n            'o' => format_octal(&mut result, arg, &spec),\n            'p' => format_hex(&mut result, arg, &spec, true),\n            's' | 'z' => format_string(&mut result, arg, &spec),\n            'c' => format_char(&mut result, arg, &spec),\n            'q' => format_sql_quote(&mut result, arg, &spec),\n            'Q' => format_sql_quote_wrap(&mut result, arg, &spec),\n            'w' => format_sql_identifier(&mut result, arg, &spec),\n            'r' => format_ordinal(&mut result, arg, &spec),\n            'n' => { /* silently ignored, no arg consumed */ }\n            _ => {\n                // Unknown specifier: return NULL if nothing was processed\n                // before this point, otherwise return accumulated text.\n                // This matches SQLite where the internal buffer (zText) stays\n                // NULL until any append is attempted.\n                if !touched {\n                    return Ok(Value::Null);\n                }\n                break;\n            }\n        }\n        touched = true;\n    }\n\n    Ok(Value::build_text(result))\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    fn text(value: &str) -> Register {\n        Register::Value(Value::build_text(value.to_string()))\n    }\n\n    fn integer(value: i64) -> Register {\n        Register::Value(Value::from_i64(value))\n    }\n\n    fn float(value: f64) -> Register {\n        Register::Value(Value::from_f64(value))\n    }\n\n    #[test]\n    fn test_printf_no_args() {\n        assert_eq!(exec_printf(&[]).unwrap(), Value::Null);\n    }\n\n    #[test]\n    fn test_printf_basic_string() {\n        assert_eq!(\n            exec_printf(&[text(\"Hello World\")]).unwrap(),\n            *text(\"Hello World\").get_value()\n        );\n    }\n\n    #[test]\n    fn test_printf_string_formatting() {\n        let test_cases = vec![\n            (\n                vec![text(\"Hello, %s!\"), text(\"World\")],\n                text(\"Hello, World!\"),\n            ),\n            (\n                vec![text(\"%s %s!\"), text(\"Hello\"), text(\"World\")],\n                text(\"Hello World!\"),\n            ),\n            (\n                vec![text(\"Hello, %s!\"), Register::Value(Value::Null)],\n                text(\"Hello, !\"),\n            ),\n            (vec![text(\"Value: %s\"), integer(42)], text(\"Value: 42\")),\n            (vec![text(\"100%% complete\")], text(\"100% complete\")),\n        ];\n        for (input, output) in test_cases {\n            assert_eq!(exec_printf(&input).unwrap(), *output.get_value());\n        }\n    }\n\n    #[test]\n    fn test_printf_integer_formatting() {\n        let test_cases = vec![\n            (vec![text(\"Number: %d\"), integer(42)], text(\"Number: 42\")),\n            (vec![text(\"Number: %d\"), integer(-42)], text(\"Number: -42\")),\n            (\n                vec![text(\"%d + %d = %d\"), integer(2), integer(3), integer(5)],\n                text(\"2 + 3 = 5\"),\n            ),\n            (\n                vec![text(\"Number: %d\"), text(\"not a number\")],\n                text(\"Number: 0\"),\n            ),\n            (\n                vec![text(\"Truncated float: %d\"), float(3.9)],\n                text(\"Truncated float: 3\"),\n            ),\n            (vec![text(\"Number: %i\"), integer(42)], text(\"Number: 42\")),\n        ];\n        for (input, output) in test_cases {\n            assert_eq!(exec_printf(&input).unwrap(), *output.get_value());\n        }\n    }\n\n    #[test]\n    fn test_printf_unsigned_integer_formatting() {\n        let test_cases = vec![\n            (vec![text(\"Number: %u\"), integer(42)], text(\"Number: 42\")),\n            (\n                vec![text(\"Negative: %u\"), integer(-1)],\n                text(\"Negative: 18446744073709551615\"),\n            ),\n            (vec![text(\"NaN: %u\"), text(\"not a number\")], text(\"NaN: 0\")),\n        ];\n        for (input, output) in test_cases {\n            assert_eq!(exec_printf(&input).unwrap(), *output.get_value());\n        }\n    }\n\n    #[test]\n    fn test_printf_float_formatting() {\n        let test_cases = vec![\n            (\n                vec![text(\"Number: %f\"), float(42.5)],\n                text(\"Number: 42.500000\"),\n            ),\n            (\n                vec![text(\"Number: %f\"), float(-42.5)],\n                text(\"Number: -42.500000\"),\n            ),\n            (\n                vec![text(\"Number: %f\"), integer(42)],\n                text(\"Number: 42.000000\"),\n            ),\n            (\n                vec![text(\"Number: %f\"), text(\"not a number\")],\n                text(\"Number: 0.000000\"),\n            ),\n        ];\n\n        // Huge finite float must not overflow rounding to produce \"inf\"\n        let huge = exec_printf(&[text(\"%f\"), float(1e308)]).unwrap();\n        let huge_str = match &huge {\n            Value::Text(t) => t.as_str().to_string(),\n            _ => panic!(\"expected text\"),\n        };\n        assert!(huge_str.starts_with(\"9999999999999999\"));\n        assert!(huge_str.ends_with(\".000000\"));\n        assert!(!huge_str.contains(\"inf\"));\n        for (input, expected) in test_cases {\n            assert_eq!(exec_printf(&input).unwrap(), *expected.get_value());\n        }\n    }\n\n    #[test]\n    fn test_printf_width_precision() {\n        let test_cases = vec![\n            (vec![text(\"%.2f\"), float(4.002)], text(\"4.00\")),\n            (vec![text(\"%05d\"), integer(42)], text(\"00042\")),\n            (vec![text(\"%.5d\"), integer(42)], text(\"00042\")),\n            (vec![text(\"%+d\"), integer(42)], text(\"+42\")),\n            (vec![text(\"%.3s\"), text(\"hello\")], text(\"hel\")),\n            (vec![text(\"%08x\"), integer(255)], text(\"000000ff\")),\n            (vec![text(\"%#x\"), integer(255)], text(\"0xff\")),\n        ];\n        for (input, expected) in test_cases {\n            assert_eq!(exec_printf(&input).unwrap(), *expected.get_value());\n        }\n    }\n\n    #[test]\n    fn test_printf_dynamic_width() {\n        assert_eq!(\n            exec_printf(&[text(\"%.*f\"), integer(2), float(3.14258)]).unwrap(),\n            *text(\"3.14\").get_value()\n        );\n    }\n\n    #[test]\n    fn test_printf_character_formatting() {\n        let test_cases = vec![\n            (vec![text(\"character: %c\"), text(\"a\")], text(\"character: a\")),\n            (\n                vec![text(\"character: %c\"), text(\"this is a test\")],\n                text(\"character: t\"),\n            ),\n            (\n                vec![text(\"character: %c\"), integer(123)],\n                text(\"character: 1\"),\n            ),\n            (\n                vec![text(\"character: %c\"), float(42.5)],\n                text(\"character: 4\"),\n            ),\n            // Empty string → NUL char → no output (matches SQLite)\n            (vec![text(\"character: %c\"), text(\"\")], text(\"character: \")),\n            // NULL → coerces to empty string → NUL → no output\n            (\n                vec![text(\"character: %c\"), Register::Value(Value::Null)],\n                text(\"character: \"),\n            ),\n        ];\n        for (input, expected) in test_cases {\n            assert_eq!(exec_printf(&input).unwrap(), *expected.get_value());\n        }\n    }\n\n    #[test]\n    fn test_printf_exponential_formatting() {\n        let test_cases = vec![\n            (\n                vec![text(\"Exp: %e\"), float(23000000.0)],\n                text(\"Exp: 2.300000e+07\"),\n            ),\n            (\n                vec![text(\"Exp: %e\"), float(-23000000.0)],\n                text(\"Exp: -2.300000e+07\"),\n            ),\n            (vec![text(\"Exp: %e\"), float(0.0)], text(\"Exp: 0.000000e+00\")),\n        ];\n        for (input, expected) in test_cases {\n            assert_eq!(exec_printf(&input).unwrap(), *expected.get_value());\n        }\n    }\n\n    #[test]\n    fn test_printf_general_formatting() {\n        let test_cases = vec![\n            (vec![text(\"%g\"), float(100.0)], text(\"100\")),\n            (vec![text(\"%g\"), float(0.00123)], text(\"0.00123\")),\n            (vec![text(\"%g\"), float(1.0)], text(\"1\")),\n            (vec![text(\"%g\"), float(1.5)], text(\"1.5\")),\n            (vec![text(\"%g\"), float(0.0)], text(\"0\")),\n            (vec![text(\"%g\"), integer(42)], text(\"42\")),\n            // Comma separator applies to %G decimal notation\n            (vec![text(\"%,G\"), integer(1000)], text(\"1,000\")),\n            (\n                vec![text(\"%,.20G\"), float(1234567.89)],\n                text(\"1,234,567.89\"),\n            ),\n        ];\n        for (input, expected) in test_cases {\n            assert_eq!(exec_printf(&input).unwrap(), *expected.get_value());\n        }\n    }\n\n    #[test]\n    fn test_printf_sql_quoting() {\n        assert_eq!(\n            exec_printf(&[text(\"%q\"), text(\"it's\")]).unwrap(),\n            *text(\"it''s\").get_value()\n        );\n        assert_eq!(\n            exec_printf(&[text(\"%Q\"), text(\"it's\")]).unwrap(),\n            *text(\"'it''s'\").get_value()\n        );\n        assert_eq!(\n            exec_printf(&[text(\"%Q\"), Register::Value(Value::Null)]).unwrap(),\n            *text(\"NULL\").get_value()\n        );\n        assert_eq!(\n            exec_printf(&[text(\"%q\"), Register::Value(Value::Null)]).unwrap(),\n            *text(\"(NULL)\").get_value()\n        );\n    }\n\n    #[test]\n    fn test_printf_comma_separator() {\n        assert_eq!(\n            exec_printf(&[text(\"%,d\"), integer(1234567)]).unwrap(),\n            *text(\"1,234,567\").get_value()\n        );\n        assert_eq!(\n            exec_printf(&[text(\"%,d\"), integer(-1234567)]).unwrap(),\n            *text(\"-1,234,567\").get_value()\n        );\n    }\n\n    #[test]\n    fn test_printf_edge_cases() {\n        let test_cases = vec![\n            (vec![text(\"\")], text(\"\")),\n            (vec![text(\"%%%%\")], text(\"%%\")),\n            (vec![text(\"No substitutions\")], text(\"No substitutions\")),\n            (\n                vec![text(\"%d%d%d\"), integer(1), integer(2), integer(3)],\n                text(\"123\"),\n            ),\n            // Trailing % is preserved\n            (vec![text(\"test%\")], text(\"test%\")),\n            // Unknown specifier: NULL if nothing processed before, else accumulated text\n            (vec![text(\"%d%j\"), integer(42)], text(\"42\")),\n            (vec![text(\"hello%j\")], text(\"hello\")),\n            (vec![text(\"%n%j\")], text(\"\")),\n            // Negative zero should not show minus sign\n            (vec![text(\"%f\"), float(-0.0)], text(\"0.000000\")),\n        ];\n        for (input, expected) in test_cases {\n            assert_eq!(exec_printf(&input).unwrap(), *expected.get_value());\n        }\n    }\n\n    #[test]\n    fn test_printf_hexadecimal_formatting() {\n        let test_cases = vec![\n            (vec![text(\"hex: %x\"), integer(4)], text(\"hex: 4\")),\n            (\n                vec![text(\"hex: %X\"), integer(15565303546)],\n                text(\"hex: 39FC3AEFA\"),\n            ),\n            (\n                vec![text(\"hex: %x\"), integer(-15565303546)],\n                text(\"hex: fffffffc603c5106\"),\n            ),\n            (vec![text(\"hex: %x\"), float(42.5)], text(\"hex: 2a\")),\n            (vec![text(\"hex: %x\"), text(\"42\")], text(\"hex: 2a\")),\n            (vec![text(\"hex: %x\"), text(\"\")], text(\"hex: 0\")),\n        ];\n        for (input, expected) in test_cases {\n            assert_eq!(exec_printf(&input).unwrap(), *expected.get_value());\n        }\n    }\n\n    #[test]\n    fn test_printf_octal_formatting() {\n        let test_cases = vec![\n            (vec![text(\"octal: %o\"), integer(4)], text(\"octal: 4\")),\n            (vec![text(\"octal: %o\"), float(42.5)], text(\"octal: 52\")),\n            (vec![text(\"octal: %o\"), text(\"42\")], text(\"octal: 52\")),\n            // # flag always adds \"0\" prefix when value is non-zero\n            (vec![text(\"%#o\"), integer(8)], text(\"010\")),\n            (vec![text(\"%#o\"), integer(0)], text(\"0\")),\n            // # flag with precision: \"0\" prefix added even if precision pads with zeros\n            (vec![text(\"%#.5o\"), integer(8)], text(\"000010\")),\n            (\n                vec![text(\"%#.20o\"), integer(1000)],\n                text(\"000000000000000001750\"),\n            ),\n        ];\n        for (input, expected) in test_cases {\n            assert_eq!(exec_printf(&input).unwrap(), *expected.get_value());\n        }\n    }\n\n    // ── Bug fix regression tests ────────────────────────────────────\n\n    #[test]\n    fn test_rounding_half_away_from_zero() {\n        // Bug 1: SQLite uses half-away-from-zero, not half-to-even\n        assert_eq!(\n            exec_printf(&[text(\"%.0f\"), float(0.5)]).unwrap(),\n            *text(\"1\").get_value()\n        );\n        assert_eq!(\n            exec_printf(&[text(\"%.0f\"), float(2.5)]).unwrap(),\n            *text(\"3\").get_value()\n        );\n        assert_eq!(\n            exec_printf(&[text(\"%.0f\"), float(-0.5)]).unwrap(),\n            *text(\"-1\").get_value()\n        );\n        assert_eq!(\n            exec_printf(&[text(\"%.0e\"), float(2.5)]).unwrap(),\n            *text(\"3e+00\").get_value()\n        );\n    }\n\n    #[test]\n    fn test_alt_hex_zero_pad_width() {\n        // Bug 2: # flag with 0 flag - prefix not counted in width\n        assert_eq!(\n            exec_printf(&[text(\"%#08x\"), integer(255)]).unwrap(),\n            *text(\"0x000000ff\").get_value()\n        );\n        assert_eq!(\n            exec_printf(&[text(\"%#04x\"), integer(255)]).unwrap(),\n            *text(\"0x00ff\").get_value()\n        );\n        assert_eq!(\n            exec_printf(&[text(\"%#08o\"), integer(8)]).unwrap(),\n            *text(\"000000010\").get_value()\n        );\n    }\n\n    #[test]\n    fn test_alt_flag_forces_decimal_point() {\n        // Bug 3: # flag forces decimal point on %e and %g\n        assert_eq!(\n            exec_printf(&[text(\"%#.0e\"), float(1.0)]).unwrap(),\n            *text(\"1.e+00\").get_value()\n        );\n        assert_eq!(\n            exec_printf(&[text(\"%#.0g\"), float(1.0)]).unwrap(),\n            *text(\"1.\").get_value()\n        );\n        assert_eq!(\n            exec_printf(&[text(\"%#g\"), float(100000.0)]).unwrap(),\n            *text(\"100000.\").get_value()\n        );\n    }\n\n    #[test]\n    fn test_g_threshold_rounding() {\n        // Bug 4: %g pre-rounding changes the exponent threshold\n        assert_eq!(\n            exec_printf(&[text(\"%g\"), float(999999.5)]).unwrap(),\n            *text(\"1e+06\").get_value()\n        );\n        assert_eq!(\n            exec_printf(&[text(\"%.1g\"), float(9.5)]).unwrap(),\n            *text(\"1e+01\").get_value()\n        );\n    }\n\n    #[test]\n    fn test_zero_pad_ignored_for_strings() {\n        // Bug 5: 0 flag should be ignored for %s and %c\n        assert_eq!(\n            exec_printf(&[text(\"%05s\"), text(\"hi\")]).unwrap(),\n            *text(\"   hi\").get_value()\n        );\n        assert_eq!(\n            exec_printf(&[text(\"%05c\"), text(\"A\")]).unwrap(),\n            *text(\"    A\").get_value()\n        );\n    }\n\n    #[test]\n    fn test_q_width_precision() {\n        // Bug 6: %q/%Q/%w should respect width and precision\n        assert_eq!(\n            exec_printf(&[text(\"%.2q\"), text(\"hello\")]).unwrap(),\n            *text(\"he\").get_value()\n        );\n        assert_eq!(\n            exec_printf(&[text(\"%10q\"), text(\"hi\")]).unwrap(),\n            *text(\"        hi\").get_value()\n        );\n        assert_eq!(\n            exec_printf(&[text(\"%10Q\"), text(\"hi\")]).unwrap(),\n            *text(\"      'hi'\").get_value()\n        );\n    }\n\n    #[test]\n    fn test_infinity_handling() {\n        // SQLite source (sqlite3.c:32502): infinity + flag_zeropad → 9-fill\n        // infinity without flag_zeropad → \"Inf\"\n        let inf_f = exec_printf(&[text(\"%020f\"), float(f64::INFINITY)]).unwrap();\n        let inf_str = match &inf_f {\n            Value::Text(t) => t.as_str().to_string(),\n            _ => panic!(\"expected text\"),\n        };\n        assert!(inf_str.starts_with(\"9000\"));\n        assert_eq!(inf_str.len(), 1007); // 9 + 999 zeros + \".000000\"\n\n        assert_eq!(\n            exec_printf(&[text(\"%020e\"), float(f64::INFINITY)]).unwrap(),\n            *text(\"00000009.000000e+999\").get_value()\n        );\n        assert_eq!(\n            exec_printf(&[text(\"%020g\"), float(f64::INFINITY)]).unwrap(),\n            *text(\"000000000000009e+999\").get_value()\n        );\n        // Without zero-pad → \"Inf\" (not 9-fill)\n        assert_eq!(\n            exec_printf(&[text(\"%e\"), float(f64::INFINITY)]).unwrap(),\n            *text(\"Inf\").get_value()\n        );\n        assert_eq!(\n            exec_printf(&[text(\"%G\"), float(f64::INFINITY)]).unwrap(),\n            *text(\"Inf\").get_value()\n        );\n        assert_eq!(\n            exec_printf(&[text(\"%f\"), float(f64::INFINITY)]).unwrap(),\n            *text(\"Inf\").get_value()\n        );\n        // With zero-pad but no width still triggers 9-fill\n        assert_eq!(\n            exec_printf(&[text(\"%0G\"), float(f64::INFINITY)]).unwrap(),\n            *text(\"9E+999\").get_value()\n        );\n        assert_eq!(\n            exec_printf(&[text(\"%0,G\"), float(f64::INFINITY)]).unwrap(),\n            *text(\"9E+999\").get_value()\n        );\n        // Negative infinity\n        assert_eq!(\n            exec_printf(&[text(\"%e\"), float(f64::NEG_INFINITY)]).unwrap(),\n            *text(\"-Inf\").get_value()\n        );\n        assert_eq!(\n            exec_printf(&[text(\"%020e\"), float(f64::NEG_INFINITY)]).unwrap(),\n            *text(\"-0000009.000000e+999\").get_value()\n        );\n        // # flag with %g infinity: RTZ disabled, so trailing zeros remain\n        assert_eq!(\n            exec_printf(&[text(\"%#0g\"), float(f64::INFINITY)]).unwrap(),\n            *text(\"9.00000e+999\").get_value()\n        );\n        // ! flag with %e infinity: RTZ enabled, strips to .0\n        assert_eq!(\n            exec_printf(&[text(\"%!0e\"), float(f64::INFINITY)]).unwrap(),\n            *text(\"9.0e+999\").get_value()\n        );\n        // ! flag with %f infinity: strips trailing fractional zeros\n        let inf_bang_f = exec_printf(&[text(\"%!0f\"), float(f64::INFINITY)]).unwrap();\n        let inf_bang_str = match &inf_bang_f {\n            Value::Text(t) => t.as_str().to_string(),\n            _ => panic!(\"expected text\"),\n        };\n        assert!(\n            inf_bang_str.ends_with(\".0\"),\n            \"Infinity with %!0f should end with .0, got: ...{}\",\n            &inf_bang_str[inf_bang_str.len().saturating_sub(10)..]\n        );\n    }\n\n    #[test]\n    fn test_significant_digits_limiting() {\n        // Default: 16 significant digits (hide IEEE noise)\n        assert_eq!(\n            exec_printf(&[text(\"%.20f\"), float(1.0 / 3.0)]).unwrap(),\n            *text(\"0.33333333333333330000\").get_value()\n        );\n        assert_eq!(\n            exec_printf(&[text(\"%.20e\"), float(1.0 / 3.0)]).unwrap(),\n            *text(\"3.33333333333333300000e-01\").get_value()\n        );\n        // ! flag: 26 significant digits max, trailing zeros stripped (sqlite3.c:32496).\n        // fp_decode extracts 19 digits from the u64; the ! flag's RTZ then strips\n        // the trailing '0', yielding 19 fractional characters.\n        assert_eq!(\n            exec_printf(&[text(\"%!.20f\"), float(1.0 / 3.0)]).unwrap(),\n            *text(\"0.3333333333333333148\").get_value()\n        );\n    }\n\n    #[test]\n    fn test_nan_handling() {\n        // Value::from_f64(NaN) returns Value::Null (NonNan rejects NaN),\n        // so NaN is treated as NULL which coerces to 0.0 for float formats.\n        // The NaN-specific formatting code (NaN/null output) is defense-in-depth\n        // that can't be triggered through the Value system.\n        assert_eq!(\n            exec_printf(&[text(\"%f\"), float(f64::NAN)]).unwrap(),\n            *text(\"0.000000\").get_value()\n        );\n        assert_eq!(\n            exec_printf(&[text(\"%e\"), float(f64::NAN)]).unwrap(),\n            *text(\"0.000000e+00\").get_value()\n        );\n        assert_eq!(\n            exec_printf(&[text(\"%g\"), float(f64::NAN)]).unwrap(),\n            *text(\"0\").get_value()\n        );\n    }\n\n    #[test]\n    fn test_blob_nul_truncation() {\n        // Bug 9: %s on blobs truncates at first NUL byte\n        let blob_val = Register::Value(Value::Blob(vec![0x48, 0x00, 0x4C])); // H\\0L\n        let result = exec_printf(&[text(\"%s\"), blob_val]).unwrap();\n        assert_eq!(result, *text(\"H\").get_value());\n\n        let blob_hello = Register::Value(Value::Blob(b\"Hello\".to_vec()));\n        assert_eq!(\n            exec_printf(&[text(\"%s\"), blob_hello]).unwrap(),\n            *text(\"Hello\").get_value()\n        );\n    }\n\n    #[test]\n    fn test_limit_significant_digits_rounding() {\n        // Verify the rounding behavior of limit_significant_digits\n        assert_eq!(limit_significant_digits(\"123456789\", 5), \"123460000\");\n        assert_eq!(limit_significant_digits(\"1.23456789\", 5), \"1.23460000\");\n        assert_eq!(limit_significant_digits(\"0.001234\", 3), \"0.001230\");\n        assert_eq!(limit_significant_digits(\"9.9999\", 3), \"10.0000\");\n        assert_eq!(limit_significant_digits(\"0.099999\", 4), \"0.100000\");\n    }\n\n    #[test]\n    fn test_i32_star_precision_wrapping() {\n        // i32::MIN as * precision wraps back to itself after negation → treated as 0\n        assert_eq!(\n            exec_printf(&[text(\"%.*d\"), integer(-2147483648), integer(42)]).unwrap(),\n            *text(\"42\").get_value()\n        );\n        // 4294967295 as i64 → -1 as i32 → wrapping_neg → 1\n        assert_eq!(\n            exec_printf(&[text(\"%.*d\"), integer(4294967295), integer(42)]).unwrap(),\n            *text(\"42\").get_value()\n        );\n    }\n\n    #[test]\n    fn test_comma_zero_pad_interaction() {\n        // When comma + zero_pad: zero-pad digits to width, then insert commas\n        // Width 15 = 15 digit positions, commas added on top\n        assert_eq!(\n            exec_printf(&[text(\"%0,15d\"), integer(42)]).unwrap(),\n            *text(\"000,000,000,000,042\").get_value()\n        );\n        assert_eq!(\n            exec_printf(&[text(\"%0,15u\"), integer(42)]).unwrap(),\n            *text(\"000,000,000,000,042\").get_value()\n        );\n        // Left-justify is ignored when comma + zero-pad are both set\n        assert_eq!(\n            exec_printf(&[text(\"%-0,15d\"), integer(42)]).unwrap(),\n            *text(\"000,000,000,000,042\").get_value()\n        );\n    }\n\n    #[test]\n    fn test_ordinal_format() {\n        assert_eq!(\n            exec_printf(&[text(\"%r\"), integer(1)]).unwrap(),\n            *text(\"1st\").get_value()\n        );\n        assert_eq!(\n            exec_printf(&[text(\"%r\"), integer(2)]).unwrap(),\n            *text(\"2nd\").get_value()\n        );\n        assert_eq!(\n            exec_printf(&[text(\"%r\"), integer(3)]).unwrap(),\n            *text(\"3rd\").get_value()\n        );\n        assert_eq!(\n            exec_printf(&[text(\"%r\"), integer(11)]).unwrap(),\n            *text(\"11th\").get_value()\n        );\n        assert_eq!(\n            exec_printf(&[text(\"%r\"), integer(112)]).unwrap(),\n            *text(\"112th\").get_value()\n        );\n        assert_eq!(\n            exec_printf(&[text(\"%.5r\"), integer(-39)]).unwrap(),\n            *text(\"-039th\").get_value()\n        );\n        assert_eq!(\n            exec_printf(&[text(\"% r\"), integer(42)]).unwrap(),\n            *text(\" 42nd\").get_value()\n        );\n        // Zero-pad pads the digits before the suffix\n        assert_eq!(\n            exec_printf(&[text(\"%010r\"), integer(0)]).unwrap(),\n            *text(\"00000000th\").get_value()\n        );\n    }\n\n    #[test]\n    fn test_q_null_precision_truncation() {\n        // Precision truncates the NULL literal representation\n        assert_eq!(\n            exec_printf(&[text(\"%.0q\"), Register::Value(Value::Null)]).unwrap(),\n            *text(\"\").get_value()\n        );\n        assert_eq!(\n            exec_printf(&[text(\"%.3q\"), Register::Value(Value::Null)]).unwrap(),\n            *text(\"(NU\").get_value()\n        );\n        assert_eq!(\n            exec_printf(&[text(\"%.0Q\"), Register::Value(Value::Null)]).unwrap(),\n            *text(\"\").get_value()\n        );\n    }\n\n    #[test]\n    fn test_q_null_width_padding() {\n        // Width applies to the NULL representation\n        assert_eq!(\n            exec_printf(&[text(\"%-10q\"), Register::Value(Value::Null)]).unwrap(),\n            *text(\"(NULL)    \").get_value()\n        );\n    }\n\n    #[test]\n    fn test_unknown_specifier_returns_early() {\n        // Unknown specifier as first thing → NULL (SQLite's StrAccum never allocated)\n        assert_eq!(\n            exec_printf(&[text(\"%b\"), integer(42)]).unwrap(),\n            Value::Null,\n        );\n        // Unknown specifier after literal text → accumulated text\n        assert_eq!(\n            exec_printf(&[text(\"hello%b\"), integer(42)]).unwrap(),\n            *text(\"hello\").get_value()\n        );\n        // Unknown specifier after %n → \"\" (StrAccum was allocated by %n processing)\n        assert_eq!(\n            exec_printf(&[text(\"%n%b\"), integer(42)]).unwrap(),\n            *text(\"\").get_value(),\n        );\n    }\n\n    #[test]\n    fn test_control_char_escaping_with_hash_q() {\n        // %#q escapes control characters as \\uXXXX and doubles backslashes\n        assert_eq!(\n            exec_printf(&[text(\"%#q\"), text(\"a\\nb\")]).unwrap(),\n            *text(\"a\\\\u000ab\").get_value()\n        );\n        assert_eq!(\n            exec_printf(&[text(\"%#q\"), text(\"a\\tb\")]).unwrap(),\n            *text(\"a\\\\u0009b\").get_value()\n        );\n        // Backslash is doubled in escape mode\n        assert_eq!(\n            exec_printf(&[text(\"%#q\"), text(\"a\\\\b\")]).unwrap(),\n            *text(\"a\\\\\\\\b\").get_value()\n        );\n    }\n\n    #[test]\n    fn test_hash_q_upper_unistr_wrapping() {\n        // %#Q wraps with unistr('...') when control chars are present\n        assert_eq!(\n            exec_printf(&[text(\"%#Q\"), text(\"a\\nb\")]).unwrap(),\n            *text(\"unistr('a\\\\u000ab')\").get_value()\n        );\n        // %#Q without control chars — no unistr wrapping\n        assert_eq!(\n            exec_printf(&[text(\"%#Q\"), text(\"hello\")]).unwrap(),\n            *text(\"'hello'\").get_value()\n        );\n        // %Q without # — no unistr wrapping even with control chars\n        assert_eq!(\n            exec_printf(&[text(\"%Q\"), text(\"a\\nb\")]).unwrap(),\n            *text(\"'a\\nb'\").get_value()\n        );\n    }\n\n    #[test]\n    fn test_very_small_float_no_nan() {\n        // 1e-300 with %G should not produce NaN — round_half_away_e must handle\n        // subnormal scale values from 10^(-309+) without dividing by ~0.\n        let result = exec_printf(&[text(\"%.*G\"), integer(10), float(1e-300)]).unwrap();\n        assert_eq!(result, *text(\"1E-300\").get_value());\n\n        // Also test with %e\n        let result = exec_printf(&[text(\"%.10e\"), float(1e-300)]).unwrap();\n        assert!(\n            !result.to_string().contains(\"NaN\"),\n            \"1e-300 with %e should not produce NaN\"\n        );\n\n        // And %g\n        let result = exec_printf(&[text(\"%.10g\"), float(1e-300)]).unwrap();\n        assert!(\n            !result.to_string().contains(\"NaN\"),\n            \"1e-300 with %g should not produce NaN\"\n        );\n    }\n\n    #[test]\n    fn test_large_float_f_format() {\n        // 1e308 with %f must produce leading digits \"9999...\" (matching SQLite's\n        // sqlite3FpDecode), NOT \"1000...\" (which Rust's format! produces).\n        let result = exec_printf(&[text(\"%.0f\"), float(1e308)]).unwrap();\n        let s = result.to_string();\n        assert!(\n            s.starts_with(\"99999999999999990\"),\n            \"1e308 with %.0f should start with 9999..., got: {}\",\n            &s[..s.len().min(40)]\n        );\n\n        // With commas too\n        let result = exec_printf(&[text(\"%,f\"), float(1e308)]).unwrap();\n        let s = result.to_string();\n        assert!(\n            s.starts_with(\"99,999,999,999,999,990\"),\n            \"1e308 with %,f should start with 99,999..., got: {}\",\n            &s[..s.len().min(40)]\n        );\n    }\n\n    #[test]\n    fn test_negative_zero_suppression() {\n        // SQLite 3.51+ (sqlite3.c:32520-32532): With # flag (no + or space),\n        // %f suppresses minus sign when displayed value rounds to zero.\n\n        // -0.0000001 with %#f displays as 0.000000 — suppress minus\n        let result = exec_printf(&[text(\"%#f\"), float(-0.0000001)]).unwrap();\n        assert_eq!(result.to_string(), \"0.000000\");\n\n        // Same without # flag — keep minus\n        let result = exec_printf(&[text(\"%f\"), float(-0.0000001)]).unwrap();\n        assert_eq!(result.to_string(), \"-0.000000\");\n\n        // With + flag, # doesn't suppress (flag_prefix is set)\n        let result = exec_printf(&[text(\"%#+f\"), float(-0.0000001)]).unwrap();\n        assert_eq!(result.to_string(), \"-0.000000\");\n\n        // -0.5 rounds to -1, not zero — keep minus\n        let result = exec_printf(&[text(\"%#.0f\"), float(-0.5)]).unwrap();\n        assert_eq!(result.to_string(), \"-1.\");\n\n        // -0.4 rounds to 0 — suppress\n        let result = exec_printf(&[text(\"%#.0f\"), float(-0.4)]).unwrap();\n        assert_eq!(result.to_string(), \"0.\");\n\n        // -0.0000001 with comma separator\n        let result = exec_printf(&[text(\"%#,f\"), float(-0.0000001)]).unwrap();\n        assert_eq!(result.to_string(), \"0.000000\");\n    }\n}\n"
  },
  {
    "path": "core/incremental/aggregate_operator.rs",
    "content": "// Aggregate operator for DBSP-style incremental computation\n\nuse crate::function::{AggFunc, Func};\nuse crate::incremental::dbsp::Hash128;\nuse crate::incremental::dbsp::{Delta, DeltaPair, HashableRow};\nuse crate::incremental::operator::{\n    generate_storage_id, ComputationTracker, DbspStateCursors, EvalState, IncrementalOperator,\n};\nuse crate::incremental::persistence::{ReadRecord, WriteRow};\nuse crate::numeric::Numeric;\nuse crate::storage::btree::CursorTrait;\nuse crate::sync::Arc;\nuse crate::sync::Mutex;\nuse crate::types::{IOResult, ImmutableRecord, SeekKey, SeekOp, SeekResult, ValueRef};\nuse crate::{return_and_restore_if_io, return_if_io, LimboError, Result, Value};\nuse rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};\nuse std::collections::BTreeMap;\nuse std::fmt::{self, Display};\n\n// Architecture of the Aggregate Operator\n// ========================================\n//\n// This operator implements SQL aggregations (GROUP BY, DISTINCT, COUNT, SUM, AVG, MIN, MAX)\n// using DBSP-style incremental computation. The key insight is that all these operations\n// can be expressed as operations on weighted sets (Z-sets) stored in persistent BTrees.\n//\n// ## Storage Strategy\n//\n// We use three different storage encodings (identified by 2-bit type codes in storage IDs):\n// - **Regular aggregates** (COUNT/SUM/AVG): Store accumulated state as a blob\n// - **MIN/MAX aggregates**: Store individual values; BTree ordering gives us min/max efficiently\n// - **DISTINCT tracking**: Store distinct values with weights (positive = present, zero = deleted)\n//\n// ## MIN/MAX Handling\n//\n// MIN/MAX are special because they're not fully incrementalizable:\n// - **Inserts**: Can be computed incrementally (new_min = min(old_min, new_value))\n// - **Deletes**: Must recompute from the BTree when the current min/max is deleted\n//\n// Our approach:\n// 1. Store each value with its weight in a BTree (leveraging natural ordering)\n// 2. On insert: Simply compare with current min/max (incremental)\n// 3. On delete of current min/max: Scan the BTree to find the next min/max\n//    - For MIN: scan forward from the beginning to find first value with positive weight\n//    - For MAX: scan backward from the end to find last value with positive weight\n//\n// ## DISTINCT Handling\n//\n// DISTINCT operations (COUNT(DISTINCT), SUM(DISTINCT), etc.) are implemented using the\n// weighted set pattern:\n// - Each distinct value is stored with a weight (occurrence count)\n// - Weight > 0 means the value exists in the current dataset\n// - Weight = 0 means the value has been deleted (we may clean these up)\n// - We track transitions: when a value's weight crosses zero (appears/disappears)\n//\n// ## Plain DISTINCT (SELECT DISTINCT)\n//\n// A clever reuse of infrastructure: SELECT DISTINCT x, y, z is compiled to:\n// - GROUP BY x, y, z (making each unique row combination a group)\n// - Empty aggregates vector (no actual aggregations to compute)\n// - The groups themselves become the distinct rows\n//\n// This allows us to reuse all the incremental machinery for DISTINCT without special casing.\n// The `is_distinct_only` flag indicates this pattern, where the groups ARE the output rows.\n//\n// ## State Machines\n//\n// The operator uses async-ready state machines to handle I/O operations:\n// - **Eval state machine**: Fetches existing state, applies deltas, recomputes MIN/MAX\n// - **Commit state machine**: Persists updated state back to storage\n// - Each state represents a resumption point for when I/O operations yield\n\n/// Constants for aggregate type encoding in storage IDs (2 bits)\npub const AGG_TYPE_REGULAR: u8 = 0b00; // COUNT/SUM/AVG\npub const AGG_TYPE_MINMAX: u8 = 0b01; // MIN/MAX (BTree ordering gives both)\npub const AGG_TYPE_DISTINCT: u8 = 0b10; // DISTINCT values tracking\n\n/// Hash a Value to generate an element_id for DISTINCT storage\n/// Uses HashableRow with column_idx as rowid for consistent hashing\nfn hash_value(value: &Value, column_idx: usize) -> Hash128 {\n    // Use column_idx as rowid to ensure different columns with same value get different hashes\n    let row = HashableRow::new(column_idx as i64, vec![value.clone()]);\n    row.cached_hash()\n}\n\n// Serialization type codes for aggregate functions\nconst AGG_FUNC_COUNT: i64 = 0;\nconst AGG_FUNC_SUM: i64 = 1;\nconst AGG_FUNC_AVG: i64 = 2;\nconst AGG_FUNC_MIN: i64 = 3;\nconst AGG_FUNC_MAX: i64 = 4;\nconst AGG_FUNC_COUNT_DISTINCT: i64 = 5;\nconst AGG_FUNC_SUM_DISTINCT: i64 = 6;\nconst AGG_FUNC_AVG_DISTINCT: i64 = 7;\n\n#[derive(Debug, Clone, PartialEq)]\npub enum AggregateFunction {\n    Count,\n    CountDistinct(usize), // COUNT(DISTINCT column_index)\n    Sum(usize),           // Column index\n    SumDistinct(usize),   // SUM(DISTINCT column_index)\n    Avg(usize),           // Column index\n    AvgDistinct(usize),   // AVG(DISTINCT column_index)\n    Min(usize),           // Column index\n    Max(usize),           // Column index\n}\n\nimpl Display for AggregateFunction {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match self {\n            AggregateFunction::Count => write!(f, \"COUNT(*)\"),\n            AggregateFunction::CountDistinct(idx) => write!(f, \"COUNT(DISTINCT col{idx})\"),\n            AggregateFunction::Sum(idx) => write!(f, \"SUM(col{idx})\"),\n            AggregateFunction::SumDistinct(idx) => write!(f, \"SUM(DISTINCT col{idx})\"),\n            AggregateFunction::Avg(idx) => write!(f, \"AVG(col{idx})\"),\n            AggregateFunction::AvgDistinct(idx) => write!(f, \"AVG(DISTINCT col{idx})\"),\n            AggregateFunction::Min(idx) => write!(f, \"MIN(col{idx})\"),\n            AggregateFunction::Max(idx) => write!(f, \"MAX(col{idx})\"),\n        }\n    }\n}\n\nimpl AggregateFunction {\n    /// Get the default output column name for this aggregate function\n    #[inline]\n    pub fn default_output_name(&self) -> String {\n        self.to_string()\n    }\n\n    /// Serialize this aggregate function to a Value\n    /// Returns a vector of values: [type_code, optional_column_index]\n    pub fn to_values(&self) -> Vec<Value> {\n        match self {\n            AggregateFunction::Count => vec![Value::Numeric(Numeric::Integer(AGG_FUNC_COUNT))],\n            AggregateFunction::CountDistinct(idx) => {\n                vec![\n                    Value::Numeric(Numeric::Integer(AGG_FUNC_COUNT_DISTINCT)),\n                    Value::from_i64(*idx as i64),\n                ]\n            }\n            AggregateFunction::Sum(idx) => {\n                vec![\n                    Value::Numeric(Numeric::Integer(AGG_FUNC_SUM)),\n                    Value::from_i64(*idx as i64),\n                ]\n            }\n            AggregateFunction::SumDistinct(idx) => {\n                vec![\n                    Value::Numeric(Numeric::Integer(AGG_FUNC_SUM_DISTINCT)),\n                    Value::from_i64(*idx as i64),\n                ]\n            }\n            AggregateFunction::Avg(idx) => {\n                vec![\n                    Value::Numeric(Numeric::Integer(AGG_FUNC_AVG)),\n                    Value::from_i64(*idx as i64),\n                ]\n            }\n            AggregateFunction::AvgDistinct(idx) => {\n                vec![\n                    Value::Numeric(Numeric::Integer(AGG_FUNC_AVG_DISTINCT)),\n                    Value::from_i64(*idx as i64),\n                ]\n            }\n            AggregateFunction::Min(idx) => {\n                vec![\n                    Value::Numeric(Numeric::Integer(AGG_FUNC_MIN)),\n                    Value::from_i64(*idx as i64),\n                ]\n            }\n            AggregateFunction::Max(idx) => {\n                vec![\n                    Value::Numeric(Numeric::Integer(AGG_FUNC_MAX)),\n                    Value::from_i64(*idx as i64),\n                ]\n            }\n        }\n    }\n\n    /// Deserialize an aggregate function from values\n    /// Consumes values from the cursor and returns the aggregate function\n    pub fn from_values(values: &[Value], cursor: &mut usize) -> Result<Self> {\n        let type_code = values\n            .get(*cursor)\n            .ok_or_else(|| LimboError::InternalError(\"Missing aggregate type code\".into()))?;\n\n        let agg_fn = match type_code {\n            Value::Numeric(Numeric::Integer(AGG_FUNC_COUNT)) => {\n                *cursor += 1;\n                AggregateFunction::Count\n            }\n            Value::Numeric(Numeric::Integer(AGG_FUNC_COUNT_DISTINCT)) => {\n                *cursor += 1;\n                let idx = values.get(*cursor).ok_or_else(|| {\n                    LimboError::InternalError(\"Missing COUNT(DISTINCT) column index\".into())\n                })?;\n                if let Value::Numeric(Numeric::Integer(idx)) = idx {\n                    *cursor += 1;\n                    AggregateFunction::CountDistinct(*idx as usize)\n                } else {\n                    return Err(LimboError::InternalError(format!(\n                        \"Expected Integer for COUNT(DISTINCT) column index, got {idx:?}\"\n                    )));\n                }\n            }\n            Value::Numeric(Numeric::Integer(AGG_FUNC_SUM)) => {\n                *cursor += 1;\n                let idx = values\n                    .get(*cursor)\n                    .ok_or_else(|| LimboError::InternalError(\"Missing SUM column index\".into()))?;\n                if let Value::Numeric(Numeric::Integer(idx)) = idx {\n                    *cursor += 1;\n                    AggregateFunction::Sum(*idx as usize)\n                } else {\n                    return Err(LimboError::InternalError(format!(\n                        \"Expected Integer for SUM column index, got {idx:?}\"\n                    )));\n                }\n            }\n            Value::Numeric(Numeric::Integer(AGG_FUNC_SUM_DISTINCT)) => {\n                *cursor += 1;\n                let idx = values.get(*cursor).ok_or_else(|| {\n                    LimboError::InternalError(\"Missing SUM(DISTINCT) column index\".into())\n                })?;\n                if let Value::Numeric(Numeric::Integer(idx)) = idx {\n                    *cursor += 1;\n                    AggregateFunction::SumDistinct(*idx as usize)\n                } else {\n                    return Err(LimboError::InternalError(format!(\n                        \"Expected Integer for SUM(DISTINCT) column index, got {idx:?}\"\n                    )));\n                }\n            }\n            Value::Numeric(Numeric::Integer(AGG_FUNC_AVG)) => {\n                *cursor += 1;\n                let idx = values\n                    .get(*cursor)\n                    .ok_or_else(|| LimboError::InternalError(\"Missing AVG column index\".into()))?;\n                if let Value::Numeric(Numeric::Integer(idx)) = idx {\n                    *cursor += 1;\n                    AggregateFunction::Avg(*idx as usize)\n                } else {\n                    return Err(LimboError::InternalError(format!(\n                        \"Expected Integer for AVG column index, got {idx:?}\"\n                    )));\n                }\n            }\n            Value::Numeric(Numeric::Integer(AGG_FUNC_AVG_DISTINCT)) => {\n                *cursor += 1;\n                let idx = values.get(*cursor).ok_or_else(|| {\n                    LimboError::InternalError(\"Missing AVG(DISTINCT) column index\".into())\n                })?;\n                if let Value::Numeric(Numeric::Integer(idx)) = idx {\n                    *cursor += 1;\n                    AggregateFunction::AvgDistinct(*idx as usize)\n                } else {\n                    return Err(LimboError::InternalError(format!(\n                        \"Expected Integer for AVG(DISTINCT) column index, got {idx:?}\"\n                    )));\n                }\n            }\n            Value::Numeric(Numeric::Integer(AGG_FUNC_MIN)) => {\n                *cursor += 1;\n                let idx = values\n                    .get(*cursor)\n                    .ok_or_else(|| LimboError::InternalError(\"Missing MIN column index\".into()))?;\n                if let Value::Numeric(Numeric::Integer(idx)) = idx {\n                    *cursor += 1;\n                    AggregateFunction::Min(*idx as usize)\n                } else {\n                    return Err(LimboError::InternalError(format!(\n                        \"Expected Integer for MIN column index, got {idx:?}\"\n                    )));\n                }\n            }\n            Value::Numeric(Numeric::Integer(AGG_FUNC_MAX)) => {\n                *cursor += 1;\n                let idx = values\n                    .get(*cursor)\n                    .ok_or_else(|| LimboError::InternalError(\"Missing MAX column index\".into()))?;\n                if let Value::Numeric(Numeric::Integer(idx)) = idx {\n                    *cursor += 1;\n                    AggregateFunction::Max(*idx as usize)\n                } else {\n                    return Err(LimboError::InternalError(format!(\n                        \"Expected Integer for MAX column index, got {idx:?}\"\n                    )));\n                }\n            }\n            _ => {\n                return Err(LimboError::InternalError(format!(\n                    \"Unknown aggregate type code: {type_code:?}\"\n                )))\n            }\n        };\n\n        Ok(agg_fn)\n    }\n\n    /// Create an AggregateFunction from a SQL function and its arguments\n    /// Returns None if the function is not a supported aggregate\n    pub fn from_sql_function(\n        func: &crate::function::Func,\n        input_column_idx: Option<usize>,\n    ) -> Option<Self> {\n        match func {\n            Func::Agg(agg_func) => {\n                match agg_func {\n                    AggFunc::Count | AggFunc::Count0 => Some(AggregateFunction::Count),\n                    AggFunc::Sum => input_column_idx.map(AggregateFunction::Sum),\n                    AggFunc::Avg => input_column_idx.map(AggregateFunction::Avg),\n                    AggFunc::Min => input_column_idx.map(AggregateFunction::Min),\n                    AggFunc::Max => input_column_idx.map(AggregateFunction::Max),\n                    _ => None, // Other aggregate functions not yet supported in DBSP\n                }\n            }\n            _ => None, // Not an aggregate function\n        }\n    }\n}\n\n/// Information about a column that has MIN/MAX aggregations\n#[derive(Debug, Clone)]\npub struct AggColumnInfo {\n    /// Index used for storage key generation\n    pub index: usize,\n    /// Whether this column has a MIN aggregate\n    pub has_min: bool,\n    /// Whether this column has a MAX aggregate\n    pub has_max: bool,\n}\n\n// group_key_str -> (group_key, state)\ntype ComputedStates = HashMap<String, (Vec<Value>, AggregateState)>;\n// group_key_str -> (column_index, value_as_hashable_row) -> accumulated_weight\npub type MinMaxDeltas = HashMap<String, HashMap<(usize, HashableRow), isize>>;\n\n/// Type for tracking distinct values within a batch\n/// Maps: group_key_str -> (column_idx, HashableRow) -> accumulated_weight\n/// HashableRow contains the value with column_idx as rowid for proper hashing\ntype DistinctDeltas = HashMap<String, HashMap<(usize, HashableRow), isize>>;\n\n/// Return type for merge_delta_with_existing function\ntype MergeResult = (Delta, HashMap<String, (Vec<Value>, AggregateState)>);\n\n/// Information about distinct value transitions for a single column\n#[derive(Debug, Clone)]\npub struct DistinctTransition {\n    pub transition_type: TransitionType,\n    pub transitioned_value: Value, // The value that was added/removed\n}\n\n#[derive(Debug, Clone, PartialEq)]\npub enum TransitionType {\n    Added,   // Value added to distinct set\n    Removed, // Value removed from distinct set\n}\n\n#[derive(Debug)]\nenum AggregateCommitState {\n    Idle,\n    Eval {\n        eval_state: EvalState,\n    },\n    PersistDelta {\n        delta: Delta,\n        computed_states: ComputedStates,\n        old_states: HashMap<String, i64>, // Track old counts for plain DISTINCT\n        current_idx: usize,\n        write_row: WriteRow,\n        min_max_deltas: MinMaxDeltas,\n        distinct_deltas: DistinctDeltas,\n        input_delta: Delta, // Keep original input delta for distinct processing\n    },\n    PersistMinMax {\n        delta: Delta,\n        min_max_persist_state: MinMaxPersistState,\n        distinct_deltas: DistinctDeltas,\n    },\n    PersistDistinctValues {\n        delta: Delta,\n        distinct_persist_state: DistinctPersistState,\n    },\n    Done {\n        delta: Delta,\n    },\n    Invalid,\n}\n\n// Aggregate-specific eval states\n#[derive(Debug)]\npub enum AggregateEvalState {\n    FetchKey {\n        delta: Delta, // Keep original delta for merge operation\n        current_idx: usize,\n        groups_to_read: Vec<(String, Vec<Value>)>, // Changed to Vec for index-based access\n        existing_groups: HashMap<String, AggregateState>,\n        old_values: HashMap<String, Vec<Value>>,\n        pre_existing_groups: HashSet<String>, // Track groups that existed before this delta\n    },\n    FetchAggregateState {\n        delta: Delta, // Keep original delta for merge operation\n        current_idx: usize,\n        groups_to_read: Vec<(String, Vec<Value>)>, // Changed to Vec for index-based access\n        existing_groups: HashMap<String, AggregateState>,\n        old_values: HashMap<String, Vec<Value>>,\n        rowid: Option<i64>, // Rowid found by FetchKey (None if not found)\n        read_record_state: Box<ReadRecord>,\n        pre_existing_groups: HashSet<String>, // Track groups that existed before this delta\n    },\n    FetchDistinctValues {\n        delta: Delta, // Keep original delta for merge operation\n        current_idx: usize,\n        groups_to_read: Vec<(String, Vec<Value>)>, // Changed to Vec for index-based access\n        existing_groups: HashMap<String, AggregateState>,\n        old_values: HashMap<String, Vec<Value>>,\n        fetch_distinct_state: Box<FetchDistinctState>,\n        pre_existing_groups: HashSet<String>, // Track groups that existed before this delta\n    },\n    RecomputeMinMax {\n        delta: Delta,\n        existing_groups: HashMap<String, AggregateState>,\n        old_values: HashMap<String, Vec<Value>>,\n        recompute_state: Box<RecomputeMinMax>,\n        pre_existing_groups: HashSet<String>, // Track groups that existed before this delta\n    },\n    Done {\n        output: (Delta, ComputedStates),\n    },\n}\n\n/// Note that the AggregateOperator essentially implements a ZSet, even\n/// though the ZSet structure is never used explicitly. The on-disk btree\n/// plays the role of the set!\n#[derive(Debug)]\npub struct AggregateOperator {\n    // Unique operator ID for indexing in persistent storage\n    pub operator_id: i64,\n    // GROUP BY column indices\n    group_by: Vec<usize>,\n    // Aggregate functions to compute (including MIN/MAX)\n    pub aggregates: Vec<AggregateFunction>,\n    // Column names from input\n    pub input_column_names: Vec<String>,\n    // Map from column index to aggregate info for quick lookup\n    pub column_min_max: HashMap<usize, AggColumnInfo>,\n    // Set of column indices that have distinct aggregates\n    pub distinct_columns: HashSet<usize>,\n    tracker: Option<Arc<Mutex<ComputationTracker>>>,\n\n    // State machine for commit operation\n    commit_state: AggregateCommitState,\n\n    // SELECT DISTINCT x,y,z.... with no aggregations.\n    is_distinct_only: bool,\n}\n\n/// State for a single group's aggregates\n#[derive(Debug, Clone, Default)]\npub struct AggregateState {\n    // For COUNT: just the count\n    pub count: i64,\n    // For SUM: column_index -> sum value\n    pub sums: HashMap<usize, f64>,\n    // For AVG: column_index -> (sum, count) for computing average\n    pub avgs: HashMap<usize, (f64, i64)>,\n    // For MIN: column_index -> minimum value\n    pub mins: HashMap<usize, Value>,\n    // For MAX: column_index -> maximum value\n    pub maxs: HashMap<usize, Value>,\n    // For DISTINCT aggregates: column_index -> computed value\n    // These are populated during eval when we scan the BTree (or in-memory map)\n    pub distinct_counts: HashMap<usize, i64>,\n    pub distinct_sums: HashMap<usize, f64>,\n\n    // Weights of specific distinct values needed for current delta processing\n    // (column_index, value) -> weight\n    // Populated during FetchKey for values mentioned in the delta\n    pub(crate) distinct_value_weights: HashMap<(usize, HashableRow), i64>,\n}\n\nimpl AggregateEvalState {\n    /// Process a delta through the aggregate state machine.\n    ///\n    /// Control flow is strictly linear for maintainability:\n    /// 1. FetchKey → FetchAggregateState (always)\n    /// 2. FetchAggregateState → FetchKey (always, loops until all groups processed)\n    /// 3. FetchKey (when done) → FetchDistinctValues (always)\n    /// 4. FetchDistinctValues → RecomputeMinMax (always)\n    /// 5. RecomputeMinMax → Done (always)\n    ///\n    /// Some states may be no-ops depending on the operator configuration:\n    /// - FetchAggregateState: For plain DISTINCT, skips reading aggregate blob (no aggregates to fetch)\n    /// - FetchDistinctValues: No-op if no distinct columns exist (distinct_columns is empty)\n    /// - RecomputeMinMax: No-op if no MIN/MAX aggregates exist (has_min_max() returns false)\n    ///\n    /// This deterministic flow ensures each state always transitions to the same next state,\n    /// making the state machine easier to understand and debug.\n    fn process_delta(\n        &mut self,\n        operator: &mut AggregateOperator,\n        cursors: &mut DbspStateCursors,\n    ) -> Result<IOResult<(Delta, ComputedStates)>> {\n        loop {\n            match self {\n                AggregateEvalState::FetchKey {\n                    delta,\n                    current_idx,\n                    groups_to_read,\n                    existing_groups,\n                    old_values,\n                    pre_existing_groups,\n                } => {\n                    if *current_idx >= groups_to_read.len() {\n                        // All groups have been fetched, move to FetchDistinctValues\n                        // Create FetchDistinctState based on the delta and existing groups\n                        let fetch_distinct_state = FetchDistinctState::new(\n                            delta,\n                            &operator.distinct_columns,\n                            |values| operator.extract_group_key(values),\n                            AggregateOperator::group_key_to_string,\n                            existing_groups,\n                            operator.is_distinct_only,\n                        );\n\n                        *self = AggregateEvalState::FetchDistinctValues {\n                            delta: std::mem::take(delta),\n                            current_idx: 0,\n                            groups_to_read: std::mem::take(groups_to_read),\n                            existing_groups: std::mem::take(existing_groups),\n                            old_values: std::mem::take(old_values),\n                            fetch_distinct_state: Box::new(fetch_distinct_state),\n                            pre_existing_groups: std::mem::take(pre_existing_groups),\n                        };\n                    } else {\n                        // Get the current group to read\n                        let (group_key_str, _group_key) = &groups_to_read[*current_idx];\n\n                        // For plain DISTINCT, we still need to transition to FetchAggregateState\n                        // to add the group to existing_groups, but we won't read any aggregate blob\n\n                        // Build the key for regular aggregate state: (operator_id, zset_hash, element_id=0)\n                        let operator_storage_id =\n                            generate_storage_id(operator.operator_id, 0, AGG_TYPE_REGULAR);\n                        let zset_hash = operator.generate_group_hash(group_key_str);\n                        let element_id = Hash128::new(0, 0); // Always zeros for aggregate state\n\n                        // Create index key values\n                        let index_key_values = vec![\n                            Value::from_i64(operator_storage_id),\n                            zset_hash.to_value(),\n                            element_id.to_value(),\n                        ];\n\n                        // Create an immutable record for the index key\n                        let index_record =\n                            ImmutableRecord::from_values(&index_key_values, index_key_values.len());\n\n                        // Seek in the index to find if this row exists\n                        let seek_result = return_if_io!(cursors.index_cursor.seek(\n                            SeekKey::IndexKey(&index_record),\n                            SeekOp::GE { eq_only: true }\n                        ));\n\n                        let rowid = if matches!(seek_result, SeekResult::Found) {\n                            // Found in index, get the table rowid\n                            // The btree code handles extracting the rowid from the index record for has_rowid indexes\n                            return_if_io!(cursors.index_cursor.rowid())\n                        } else {\n                            // Not found in index, no existing state\n                            None\n                        };\n\n                        // Always transition to FetchAggregateState\n                        let taken_existing = std::mem::take(existing_groups);\n                        let taken_old_values = std::mem::take(old_values);\n                        let next_state = AggregateEvalState::FetchAggregateState {\n                            delta: std::mem::take(delta),\n                            current_idx: *current_idx,\n                            groups_to_read: std::mem::take(groups_to_read),\n                            existing_groups: taken_existing,\n                            old_values: taken_old_values,\n                            rowid,\n                            read_record_state: Box::new(ReadRecord::new()),\n                            pre_existing_groups: std::mem::take(pre_existing_groups), // Pass through existing\n                        };\n                        *self = next_state;\n                    }\n                }\n                AggregateEvalState::FetchAggregateState {\n                    delta,\n                    current_idx,\n                    groups_to_read,\n                    existing_groups,\n                    old_values,\n                    rowid,\n                    read_record_state,\n                    pre_existing_groups,\n                } => {\n                    // Get the current group to read\n                    let (group_key_str, group_key) = &groups_to_read[*current_idx];\n\n                    // For plain DISTINCT, skip aggregate state fetch entirely\n                    // The distinct values are handled separately in FetchDistinctValues\n                    if operator.is_distinct_only {\n                        // Always insert the group key so FetchDistinctState will process it\n                        // The count will be set properly when we fetch distinct values\n                        existing_groups.insert(group_key_str.clone(), AggregateState::default());\n                    } else if let Some(rowid) = rowid {\n                        let key = SeekKey::TableRowId(*rowid);\n                        // Regular aggregates - read the blob\n                        let state = return_if_io!(\n                            read_record_state.read_record(key, &mut cursors.table_cursor)\n                        );\n                        // Process the fetched state\n                        if let Some(state) = state {\n                            let mut old_row = group_key.clone();\n                            old_row.extend(state.to_values(&operator.aggregates));\n                            old_values.insert(group_key_str.clone(), old_row);\n                            existing_groups.insert(group_key_str.clone(), state);\n                            // Track that this group exists in storage\n                            pre_existing_groups.insert(group_key_str.clone());\n                        }\n                    }\n                    // If no rowid, there's no existing state for this group\n\n                    // Always move to next group via FetchKey\n                    let next_idx = *current_idx + 1;\n\n                    let taken_existing = std::mem::take(existing_groups);\n                    let taken_old_values = std::mem::take(old_values);\n                    let taken_pre_existing_groups = std::mem::take(pre_existing_groups);\n                    let next_state = AggregateEvalState::FetchKey {\n                        delta: std::mem::take(delta),\n                        current_idx: next_idx,\n                        groups_to_read: std::mem::take(groups_to_read),\n                        existing_groups: taken_existing,\n                        old_values: taken_old_values,\n                        pre_existing_groups: taken_pre_existing_groups,\n                    };\n                    *self = next_state;\n                }\n                AggregateEvalState::FetchDistinctValues {\n                    delta,\n                    current_idx: _,\n                    groups_to_read: _,\n                    existing_groups,\n                    old_values,\n                    fetch_distinct_state,\n                    pre_existing_groups,\n                } => {\n                    // Use FetchDistinctState to read distinct values from BTree storage\n                    return_if_io!(fetch_distinct_state.fetch_distinct_values(\n                        operator.operator_id,\n                        existing_groups,\n                        cursors,\n                        |group_key| operator.generate_group_hash(group_key),\n                        operator.is_distinct_only\n                    ));\n\n                    // For plain DISTINCT, mark groups as \"from storage\" if they have distinct values\n                    if operator.is_distinct_only {\n                        for (group_key_str, state) in existing_groups.iter() {\n                            // Check if this group has any distinct values with positive weight\n                            let has_values = state.distinct_value_weights.values().any(|&w| w > 0);\n                            if has_values {\n                                pre_existing_groups.insert(group_key_str.clone());\n                            }\n                        }\n                    }\n\n                    // Extract MIN/MAX deltas for recomputation\n                    let min_max_deltas = operator.extract_min_max_deltas(delta);\n\n                    // Create RecomputeMinMax before moving existing_groups\n                    let recompute_state = Box::new(RecomputeMinMax::new(\n                        min_max_deltas,\n                        existing_groups,\n                        operator,\n                    ));\n\n                    // Transition to RecomputeMinMax\n                    let next_state = AggregateEvalState::RecomputeMinMax {\n                        delta: std::mem::take(delta),\n                        existing_groups: std::mem::take(existing_groups),\n                        old_values: std::mem::take(old_values),\n                        recompute_state,\n                        pre_existing_groups: std::mem::take(pre_existing_groups),\n                    };\n                    *self = next_state;\n                }\n                AggregateEvalState::RecomputeMinMax {\n                    delta,\n                    existing_groups,\n                    old_values,\n                    recompute_state,\n                    pre_existing_groups,\n                } => {\n                    if operator.has_min_max() {\n                        // Process MIN/MAX recomputation - this will update existing_groups with correct MIN/MAX\n                        return_if_io!(recompute_state.process(existing_groups, operator, cursors));\n                    }\n\n                    // Now compute final output with updated MIN/MAX values\n                    let (output_delta, computed_states) = operator.merge_delta_with_existing(\n                        delta,\n                        existing_groups,\n                        old_values,\n                        pre_existing_groups,\n                    );\n\n                    *self = AggregateEvalState::Done {\n                        output: (output_delta, computed_states),\n                    };\n                }\n                AggregateEvalState::Done { output } => {\n                    let (delta, computed_states) = output.clone();\n                    return Ok(IOResult::Done((delta, computed_states)));\n                }\n            }\n        }\n    }\n}\n\nimpl AggregateState {\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    /// Convert the aggregate state to a vector of Values for unified serialization\n    /// Format: [count, num_aggregates, (agg_metadata, agg_state)...]\n    /// Each aggregate includes its type and column index for proper deserialization\n    pub fn to_value_vector(&self, aggregates: &[AggregateFunction]) -> Vec<Value> {\n        let mut values = Vec::new();\n\n        // Include count first\n        values.push(Value::from_i64(self.count));\n\n        // Store number of aggregates\n        values.push(Value::from_i64(aggregates.len() as i64));\n\n        // Add each aggregate's metadata and state\n        for agg in aggregates {\n            // First, add the aggregate function metadata (type and column index)\n            values.extend(agg.to_values());\n\n            // Then add the state for this aggregate\n            match agg {\n                AggregateFunction::Count => {\n                    // Count state is already stored at the beginning\n                }\n                AggregateFunction::CountDistinct(col_idx) => {\n                    // Store the distinct count for this column\n                    let count = self.distinct_counts.get(col_idx).copied().unwrap_or(0);\n                    values.push(Value::from_i64(count));\n                }\n                AggregateFunction::Sum(col_idx) => {\n                    let sum = self.sums.get(col_idx).copied().unwrap_or(0.0);\n                    values.push(Value::from_f64(sum));\n                }\n                AggregateFunction::SumDistinct(col_idx) => {\n                    // Store both the distinct count and sum for this column\n                    let count = self.distinct_counts.get(col_idx).copied().unwrap_or(0);\n                    let sum = self.distinct_sums.get(col_idx).copied().unwrap_or(0.0);\n                    values.push(Value::from_i64(count));\n                    values.push(Value::from_f64(sum));\n                }\n                AggregateFunction::Avg(col_idx) => {\n                    let (sum, count) = self.avgs.get(col_idx).copied().unwrap_or((0.0, 0));\n                    values.push(Value::from_f64(sum));\n                    values.push(Value::from_i64(count));\n                }\n                AggregateFunction::AvgDistinct(col_idx) => {\n                    // Store both the distinct count and sum for this column\n                    let count = self.distinct_counts.get(col_idx).copied().unwrap_or(0);\n                    let sum = self.distinct_sums.get(col_idx).copied().unwrap_or(0.0);\n                    values.push(Value::from_i64(count));\n                    values.push(Value::from_f64(sum));\n                }\n                AggregateFunction::Min(col_idx) => {\n                    if let Some(min_val) = self.mins.get(col_idx) {\n                        values.push(Value::from_i64(1)); // Has value\n                        values.push(min_val.clone());\n                    } else {\n                        values.push(Value::from_i64(0)); // No value\n                    }\n                }\n                AggregateFunction::Max(col_idx) => {\n                    if let Some(max_val) = self.maxs.get(col_idx) {\n                        values.push(Value::from_i64(1)); // Has value\n                        values.push(max_val.clone());\n                    } else {\n                        values.push(Value::from_i64(0)); // No value\n                    }\n                }\n            }\n        }\n\n        values\n    }\n\n    /// Reconstruct aggregate state from a vector of Values\n    pub fn from_value_vector(values: &[Value]) -> Result<Self> {\n        let mut cursor = 0;\n        let mut state = Self::new();\n\n        // Read count\n        let count = values\n            .get(cursor)\n            .ok_or_else(|| LimboError::InternalError(\"Aggregate state missing count\".into()))?;\n        if let Value::Numeric(Numeric::Integer(count)) = count {\n            state.count = *count;\n            cursor += 1;\n        } else {\n            return Err(LimboError::InternalError(format!(\n                \"Expected Integer for count, got {count:?}\"\n            )));\n        }\n\n        // Read number of aggregates\n        let num_aggregates = values\n            .get(cursor)\n            .ok_or_else(|| LimboError::InternalError(\"Missing number of aggregates\".into()))?;\n        let num_aggregates = match num_aggregates {\n            Value::Numeric(Numeric::Integer(n)) => *n as usize,\n            _ => {\n                return Err(LimboError::InternalError(format!(\n                    \"Expected Integer for aggregate count, got {num_aggregates:?}\"\n                )))\n            }\n        };\n        cursor += 1;\n\n        // Read each aggregate's state with type and column index\n        for _ in 0..num_aggregates {\n            // Deserialize the aggregate function metadata\n            let agg_fn = AggregateFunction::from_values(values, &mut cursor)?;\n\n            // Read the state for this aggregate\n            match agg_fn {\n                AggregateFunction::Count => {\n                    // Count state is already stored at the beginning\n                }\n                AggregateFunction::CountDistinct(col_idx) => {\n                    let count = values.get(cursor).ok_or_else(|| {\n                        LimboError::InternalError(\"Missing COUNT(DISTINCT) value\".into())\n                    })?;\n                    if let Value::Numeric(Numeric::Integer(count)) = count {\n                        state.distinct_counts.insert(col_idx, *count);\n                        cursor += 1;\n                    } else {\n                        return Err(LimboError::InternalError(format!(\n                            \"Expected Integer for COUNT(DISTINCT) value, got {count:?}\"\n                        )));\n                    }\n                }\n                AggregateFunction::SumDistinct(col_idx) => {\n                    let count = values.get(cursor).ok_or_else(|| {\n                        LimboError::InternalError(\"Missing SUM(DISTINCT) count\".into())\n                    })?;\n                    if let Value::Numeric(Numeric::Integer(count)) = count {\n                        state.distinct_counts.insert(col_idx, *count);\n                        cursor += 1;\n                    } else {\n                        return Err(LimboError::InternalError(format!(\n                            \"Expected Integer for SUM(DISTINCT) count, got {count:?}\"\n                        )));\n                    }\n\n                    let sum = values.get(cursor).ok_or_else(|| {\n                        LimboError::InternalError(\"Missing SUM(DISTINCT) sum\".into())\n                    })?;\n                    if let Value::Numeric(Numeric::Float(sum)) = sum {\n                        state.distinct_sums.insert(col_idx, f64::from(*sum));\n                        cursor += 1;\n                    } else {\n                        return Err(LimboError::InternalError(format!(\n                            \"Expected Float for SUM(DISTINCT) sum, got {sum:?}\"\n                        )));\n                    }\n                }\n                AggregateFunction::AvgDistinct(col_idx) => {\n                    let count = values.get(cursor).ok_or_else(|| {\n                        LimboError::InternalError(\"Missing AVG(DISTINCT) count\".into())\n                    })?;\n                    if let Value::Numeric(Numeric::Integer(count)) = count {\n                        state.distinct_counts.insert(col_idx, *count);\n                        cursor += 1;\n                    } else {\n                        return Err(LimboError::InternalError(format!(\n                            \"Expected Integer for AVG(DISTINCT) count, got {count:?}\"\n                        )));\n                    }\n\n                    let sum = values.get(cursor).ok_or_else(|| {\n                        LimboError::InternalError(\"Missing AVG(DISTINCT) sum\".into())\n                    })?;\n                    if let Value::Numeric(Numeric::Float(sum)) = sum {\n                        state.distinct_sums.insert(col_idx, f64::from(*sum));\n                        cursor += 1;\n                    } else {\n                        return Err(LimboError::InternalError(format!(\n                            \"Expected Float for AVG(DISTINCT) sum, got {sum:?}\"\n                        )));\n                    }\n                }\n                AggregateFunction::Sum(col_idx) => {\n                    let sum = values\n                        .get(cursor)\n                        .ok_or_else(|| LimboError::InternalError(\"Missing SUM value\".into()))?;\n                    if let Value::Numeric(Numeric::Float(sum)) = sum {\n                        state.sums.insert(col_idx, f64::from(*sum));\n                        cursor += 1;\n                    } else {\n                        return Err(LimboError::InternalError(format!(\n                            \"Expected Float for SUM value, got {sum:?}\"\n                        )));\n                    }\n                }\n                AggregateFunction::Avg(col_idx) => {\n                    let sum = values\n                        .get(cursor)\n                        .ok_or_else(|| LimboError::InternalError(\"Missing AVG sum value\".into()))?;\n                    let sum = match sum {\n                        Value::Numeric(Numeric::Float(f)) => f64::from(*f),\n                        _ => {\n                            return Err(LimboError::InternalError(format!(\n                                \"Expected Float for AVG sum, got {sum:?}\"\n                            )))\n                        }\n                    };\n                    cursor += 1;\n\n                    let count = values.get(cursor).ok_or_else(|| {\n                        LimboError::InternalError(\"Missing AVG count value\".into())\n                    })?;\n                    let count = match count {\n                        Value::Numeric(Numeric::Integer(i)) => *i,\n                        _ => {\n                            return Err(LimboError::InternalError(format!(\n                                \"Expected Integer for AVG count, got {count:?}\"\n                            )))\n                        }\n                    };\n                    cursor += 1;\n\n                    state.avgs.insert(col_idx, (sum, count));\n                }\n                AggregateFunction::Min(col_idx) => {\n                    let has_value = values.get(cursor).ok_or_else(|| {\n                        LimboError::InternalError(\"Missing MIN has_value flag\".into())\n                    })?;\n                    if let Value::Numeric(Numeric::Integer(has_value)) = has_value {\n                        cursor += 1;\n                        if *has_value == 1 {\n                            let min_val = values\n                                .get(cursor)\n                                .ok_or_else(|| {\n                                    LimboError::InternalError(\"Missing MIN value\".into())\n                                })?\n                                .clone();\n                            cursor += 1;\n                            state.mins.insert(col_idx, min_val);\n                        }\n                    } else {\n                        return Err(LimboError::InternalError(format!(\n                            \"Expected Integer for MIN has_value flag, got {has_value:?}\"\n                        )));\n                    }\n                }\n                AggregateFunction::Max(col_idx) => {\n                    let has_value = values.get(cursor).ok_or_else(|| {\n                        LimboError::InternalError(\"Missing MAX has_value flag\".into())\n                    })?;\n                    if let Value::Numeric(Numeric::Integer(has_value)) = has_value {\n                        cursor += 1;\n                        if *has_value == 1 {\n                            let max_val = values\n                                .get(cursor)\n                                .ok_or_else(|| {\n                                    LimboError::InternalError(\"Missing MAX value\".into())\n                                })?\n                                .clone();\n                            cursor += 1;\n                            state.maxs.insert(col_idx, max_val);\n                        }\n                    } else {\n                        return Err(LimboError::InternalError(format!(\n                            \"Expected Integer for MAX has_value flag, got {has_value:?}\"\n                        )));\n                    }\n                }\n            }\n        }\n\n        Ok(state)\n    }\n\n    fn to_blob(&self, aggregates: &[AggregateFunction], group_key: &[Value]) -> Vec<u8> {\n        let mut all_values = Vec::new();\n        // Store the group key size first\n        all_values.push(Value::from_i64(group_key.len() as i64));\n        all_values.extend_from_slice(group_key);\n        all_values.extend(self.to_value_vector(aggregates));\n\n        let record = ImmutableRecord::from_values(&all_values, all_values.len());\n        record.as_blob().clone()\n    }\n\n    pub fn from_blob(blob: &[u8]) -> Result<(Self, Vec<Value>)> {\n        let record = ImmutableRecord::from_bin_record(blob.to_vec());\n        let mut all_values: Vec<Value> = record.get_values_owned()?;\n\n        if all_values.is_empty() {\n            return Err(LimboError::InternalError(\n                \"Aggregate state blob is empty\".into(),\n            ));\n        }\n\n        // Read the group key size\n        let group_key_count = match &all_values[0] {\n            Value::Numeric(Numeric::Integer(n)) if *n >= 0 => *n as usize,\n            Value::Numeric(Numeric::Integer(n)) => {\n                return Err(LimboError::InternalError(format!(\n                    \"Negative group key count: {n}\"\n                )))\n            }\n            other => {\n                return Err(LimboError::InternalError(format!(\n                    \"Expected Integer for group key count, got {other:?}\"\n                )))\n            }\n        };\n\n        // Remove the group key count from the values\n        all_values.remove(0);\n\n        if all_values.len() < group_key_count {\n            return Err(LimboError::InternalError(format!(\n                \"Blob too short: expected at least {} values for group key, got {}\",\n                group_key_count,\n                all_values.len()\n            )));\n        }\n\n        // Split into group key and state values\n        let group_key = all_values[..group_key_count].to_vec();\n        let state_values = &all_values[group_key_count..];\n\n        // Reconstruct the aggregate state\n        let state = Self::from_value_vector(state_values)?;\n\n        Ok((state, group_key))\n    }\n\n    /// Apply a delta to this aggregate state\n    fn apply_delta(\n        &mut self,\n        values: &[Value],\n        weight: isize,\n        aggregates: &[AggregateFunction],\n        _column_names: &[String], // No longer needed\n        distinct_transitions: &HashMap<usize, DistinctTransition>,\n    ) {\n        // Update COUNT\n        self.count += weight as i64;\n\n        // Track which columns have had their distinct counts/sums updated\n        // This prevents double-counting when multiple distinct aggregates\n        // operate on the same column (e.g., COUNT(DISTINCT col), SUM(DISTINCT col), AVG(DISTINCT col))\n        let mut processed_counts: HashSet<usize> = HashSet::default();\n        let mut processed_sums: HashSet<usize> = HashSet::default();\n\n        // Update distinct aggregate state\n        for agg in aggregates {\n            match agg {\n                AggregateFunction::Count => {\n                    // Already handled above\n                }\n                AggregateFunction::CountDistinct(col_idx) => {\n                    // Only update count if we haven't processed this column yet\n                    if !processed_counts.contains(col_idx) {\n                        if let Some(transition) = distinct_transitions.get(col_idx) {\n                            let current_count =\n                                self.distinct_counts.get(col_idx).copied().unwrap_or(0);\n                            let new_count = match transition.transition_type {\n                                TransitionType::Added => current_count + 1,\n                                TransitionType::Removed => current_count - 1,\n                            };\n                            self.distinct_counts.insert(*col_idx, new_count);\n                            processed_counts.insert(*col_idx);\n                        }\n                    }\n                }\n                AggregateFunction::SumDistinct(col_idx)\n                | AggregateFunction::AvgDistinct(col_idx) => {\n                    if let Some(transition) = distinct_transitions.get(col_idx) {\n                        // Update count if not already processed (needed for AVG)\n                        if !processed_counts.contains(col_idx) {\n                            let current_count =\n                                self.distinct_counts.get(col_idx).copied().unwrap_or(0);\n                            let new_count = match transition.transition_type {\n                                TransitionType::Added => current_count + 1,\n                                TransitionType::Removed => current_count - 1,\n                            };\n                            self.distinct_counts.insert(*col_idx, new_count);\n                            processed_counts.insert(*col_idx);\n                        }\n\n                        // Update sum if not already processed\n                        if !processed_sums.contains(col_idx) {\n                            let current_sum =\n                                self.distinct_sums.get(col_idx).copied().unwrap_or(0.0);\n                            let value_as_float = match &transition.transitioned_value {\n                                Value::Numeric(Numeric::Integer(i)) => *i as f64,\n                                Value::Numeric(Numeric::Float(f)) => f64::from(*f),\n                                _ => 0.0,\n                            };\n\n                            let new_sum = match transition.transition_type {\n                                TransitionType::Added => current_sum + value_as_float,\n                                TransitionType::Removed => current_sum - value_as_float,\n                            };\n                            self.distinct_sums.insert(*col_idx, new_sum);\n                            processed_sums.insert(*col_idx);\n                        }\n                    }\n                }\n                AggregateFunction::Sum(col_idx) => {\n                    if let Some(val) = values.get(*col_idx) {\n                        let num_val = match val {\n                            Value::Numeric(Numeric::Integer(i)) => *i as f64,\n                            Value::Numeric(Numeric::Float(f)) => f64::from(*f),\n                            _ => 0.0,\n                        };\n                        *self.sums.entry(*col_idx).or_insert(0.0) += num_val * weight as f64;\n                    }\n                }\n                AggregateFunction::Avg(col_idx) => {\n                    if let Some(val) = values.get(*col_idx) {\n                        let num_val = match val {\n                            Value::Numeric(Numeric::Integer(i)) => *i as f64,\n                            Value::Numeric(Numeric::Float(f)) => f64::from(*f),\n                            _ => 0.0,\n                        };\n                        let (sum, count) = self.avgs.entry(*col_idx).or_insert((0.0, 0));\n                        *sum += num_val * weight as f64;\n                        *count += weight as i64;\n                    }\n                }\n                AggregateFunction::Min(_col_name) | AggregateFunction::Max(_col_name) => {\n                    // MIN/MAX cannot be handled incrementally in apply_delta because:\n                    //\n                    // 1. For insertions: We can't just keep the minimum/maximum value.\n                    //    We need to track ALL values to handle future deletions correctly.\n                    //\n                    // 2. For deletions (retractions): If we delete the current MIN/MAX,\n                    //    we need to find the next best value, which requires knowing all\n                    //    other values in the group.\n                    //\n                    // Example: Consider MIN(price) with values [10, 20, 30]\n                    // - Current MIN = 10\n                    // - Delete 10 (weight = -1)\n                    // - New MIN should be 20, but we can't determine this without\n                    //   having tracked all values [20, 30]\n                    //\n                    // Therefore, MIN/MAX processing is handled separately:\n                    // - All input values are persisted to the index via persist_min_max()\n                    // - When aggregates have MIN/MAX, we unconditionally transition to\n                    //   the RecomputeMinMax state machine (see EvalState::RecomputeMinMax)\n                    // - RecomputeMinMax checks if the current MIN/MAX was deleted, and if so,\n                    //   scans the index to find the new MIN/MAX from remaining values\n                    //\n                    // This ensures correctness for incremental computation at the cost of\n                    // additional I/O for MIN/MAX operations.\n                }\n            }\n        }\n    }\n\n    /// Convert aggregate state to output values\n    ///\n    /// Note: SQLite returns INTEGER for SUM when all inputs are integers, and REAL when any input is REAL.\n    /// However, in an incremental system like DBSP, we cannot track whether all current values are integers\n    /// after deletions. For example:\n    /// - Initial: SUM(10, 20, 30.5) = 60.5 (REAL)\n    /// - After DELETE 30.5: SUM(10, 20) = 30 (SQLite returns INTEGER, but we only know the sum is 30.0)\n    ///\n    /// Therefore, we always return REAL for SUM operations.\n    pub fn to_values(&self, aggregates: &[AggregateFunction]) -> Vec<Value> {\n        let mut result = Vec::new();\n\n        for agg in aggregates {\n            match agg {\n                AggregateFunction::Count => {\n                    result.push(Value::from_i64(self.count));\n                }\n                AggregateFunction::CountDistinct(col_idx) => {\n                    // Return the computed DISTINCT count\n                    let count = self.distinct_counts.get(col_idx).copied().unwrap_or(0);\n                    result.push(Value::from_i64(count));\n                }\n                AggregateFunction::Sum(col_idx) => {\n                    let sum = self.sums.get(col_idx).copied().unwrap_or(0.0);\n                    result.push(Value::from_f64(sum));\n                }\n                AggregateFunction::SumDistinct(col_idx) => {\n                    // Return the computed SUM(DISTINCT)\n                    let sum = self.distinct_sums.get(col_idx).copied().unwrap_or(0.0);\n                    result.push(Value::from_f64(sum));\n                }\n                AggregateFunction::Avg(col_idx) => {\n                    if let Some((sum, count)) = self.avgs.get(col_idx) {\n                        if *count > 0 {\n                            result.push(Value::from_f64(sum / *count as f64));\n                        } else {\n                            result.push(Value::Null);\n                        }\n                    } else {\n                        result.push(Value::Null);\n                    }\n                }\n                AggregateFunction::AvgDistinct(col_idx) => {\n                    // Compute AVG from SUM(DISTINCT) / COUNT(DISTINCT)\n                    let count = self.distinct_counts.get(col_idx).copied().unwrap_or(0);\n                    if count > 0 {\n                        let sum = self.distinct_sums.get(col_idx).copied().unwrap_or(0.0);\n                        let avg = sum / count as f64;\n                        // AVG always returns a float value for consistency with SQLite\n                        result.push(Value::from_f64(avg));\n                    } else {\n                        result.push(Value::Null);\n                    }\n                }\n                AggregateFunction::Min(col_idx) => {\n                    // Return the MIN value from our state\n                    result.push(self.mins.get(col_idx).cloned().unwrap_or(Value::Null));\n                }\n                AggregateFunction::Max(col_idx) => {\n                    // Return the MAX value from our state\n                    result.push(self.maxs.get(col_idx).cloned().unwrap_or(Value::Null));\n                }\n            }\n        }\n\n        result\n    }\n}\n\nimpl AggregateOperator {\n    /// Detect if a distinct value crosses the zero boundary (using pre-fetched weights and batch-accumulated weights)\n    fn detect_distinct_value_transition(\n        col_idx: usize,\n        val: &Value,\n        weight: isize,\n        existing_state: &AggregateState,\n        group_distinct_deltas: Option<&HashMap<(usize, HashableRow), isize>>,\n    ) -> Option<DistinctTransition> {\n        let hashable_row = HashableRow::new(col_idx as i64, vec![val.clone()]);\n\n        // Get the weight from storage (pre-fetched in AggregateState)\n        let storage_count = existing_state\n            .distinct_value_weights\n            .get(&(col_idx, hashable_row.clone()))\n            .copied()\n            .unwrap_or(0);\n\n        // Get the accumulated weight from the current batch (before this row)\n        let batch_accumulated = if let Some(deltas) = group_distinct_deltas {\n            deltas.get(&(col_idx, hashable_row)).copied().unwrap_or(0)\n        } else {\n            0\n        };\n\n        // The old count is storage + batch accumulated so far (before this row)\n        let old_count = storage_count + batch_accumulated as i64;\n        // The new count includes the current weight\n        let new_count = old_count + weight as i64;\n\n        // Detect transitions\n        if old_count <= 0 && new_count > 0 {\n            // Value added to distinct set\n            Some(DistinctTransition {\n                transition_type: TransitionType::Added,\n                transitioned_value: val.clone(),\n            })\n        } else if old_count > 0 && new_count <= 0 {\n            // Value removed from distinct set\n            Some(DistinctTransition {\n                transition_type: TransitionType::Removed,\n                transitioned_value: val.clone(),\n            })\n        } else {\n            // No transition\n            None\n        }\n    }\n\n    /// Detect distinct value transitions for a single row\n    fn detect_distinct_transitions(\n        &self,\n        row_values: &[Value],\n        weight: isize,\n        existing_state: &AggregateState,\n        group_distinct_deltas: Option<&HashMap<(usize, HashableRow), isize>>,\n    ) -> HashMap<usize, DistinctTransition> {\n        let mut transitions = HashMap::default();\n\n        // Plain Distinct doesn't track individual values, so no transitions needed\n        if self.is_distinct_only {\n            // Distinct is handled by the count alone in apply_delta\n            return transitions;\n        }\n\n        // Process each distinct column\n        for &col_idx in &self.distinct_columns {\n            let val = match row_values.get(col_idx) {\n                Some(v) => v,\n                None => continue,\n            };\n\n            // Skip null values\n            if val == &Value::Null {\n                continue;\n            }\n\n            if let Some(transition) = Self::detect_distinct_value_transition(\n                col_idx,\n                val,\n                weight,\n                existing_state,\n                group_distinct_deltas,\n            ) {\n                transitions.insert(col_idx, transition);\n            }\n        }\n\n        transitions\n    }\n\n    pub fn new(\n        operator_id: i64,\n        group_by: Vec<usize>,\n        aggregates: Vec<AggregateFunction>,\n        input_column_names: Vec<String>,\n    ) -> Result<Self> {\n        // Precompute flags for runtime efficiency\n        // Plain DISTINCT is indicated by empty aggregates vector\n        let is_distinct_only = aggregates.is_empty();\n\n        // Build map of column indices to their MIN/MAX info\n        let mut column_min_max = HashMap::default();\n        let mut storage_indices = HashMap::default();\n        let mut current_index = 0;\n\n        // First pass: assign storage indices to unique MIN/MAX columns\n        for agg in &aggregates {\n            match agg {\n                AggregateFunction::Min(col_idx) | AggregateFunction::Max(col_idx) => {\n                    storage_indices.entry(*col_idx).or_insert_with(|| {\n                        let idx = current_index;\n                        current_index += 1;\n                        idx\n                    });\n                }\n                _ => {}\n            }\n        }\n\n        // Second pass: build the column info map for MIN/MAX\n        for agg in &aggregates {\n            match agg {\n                AggregateFunction::Min(col_idx) => {\n                    let storage_index = *storage_indices.get(col_idx).ok_or_else(|| {\n                        LimboError::InternalError(\n                            \"storage index for MIN column should exist from first pass\".to_string(),\n                        )\n                    })?;\n                    let entry = column_min_max.entry(*col_idx).or_insert(AggColumnInfo {\n                        index: storage_index,\n                        has_min: false,\n                        has_max: false,\n                    });\n                    entry.has_min = true;\n                }\n                AggregateFunction::Max(col_idx) => {\n                    let storage_index = *storage_indices.get(col_idx).ok_or_else(|| {\n                        LimboError::InternalError(\n                            \"storage index for MAX column should exist from first pass\".to_string(),\n                        )\n                    })?;\n                    let entry = column_min_max.entry(*col_idx).or_insert(AggColumnInfo {\n                        index: storage_index,\n                        has_min: false,\n                        has_max: false,\n                    });\n                    entry.has_max = true;\n                }\n                _ => {}\n            }\n        }\n\n        // Build the distinct columns set\n        let mut distinct_columns = HashSet::default();\n        for agg in &aggregates {\n            match agg {\n                AggregateFunction::CountDistinct(col_idx)\n                | AggregateFunction::SumDistinct(col_idx)\n                | AggregateFunction::AvgDistinct(col_idx) => {\n                    distinct_columns.insert(*col_idx);\n                }\n                _ => {}\n            }\n        }\n\n        Ok(Self {\n            operator_id,\n            group_by,\n            aggregates,\n            input_column_names,\n            column_min_max,\n            distinct_columns,\n            tracker: None,\n            commit_state: AggregateCommitState::Idle,\n            is_distinct_only,\n        })\n    }\n\n    pub fn has_min_max(&self) -> bool {\n        !self.column_min_max.is_empty()\n    }\n\n    /// Check if this operator has any DISTINCT aggregates or plain DISTINCT\n    pub fn has_distinct(&self) -> bool {\n        !self.distinct_columns.is_empty() || self.is_distinct_only\n    }\n\n    fn eval_internal(\n        &mut self,\n        state: &mut EvalState,\n        cursors: &mut DbspStateCursors,\n    ) -> Result<IOResult<(Delta, ComputedStates)>> {\n        match state {\n            EvalState::Uninitialized => {\n                panic!(\"Cannot eval AggregateOperator with Uninitialized state\");\n            }\n            EvalState::Init { deltas } => {\n                // Aggregate operators only use left_delta, right_delta must be empty\n                assert!(\n                    deltas.right.is_empty(),\n                    \"AggregateOperator expects right_delta to be empty\"\n                );\n\n                if deltas.left.changes.is_empty() {\n                    *state = EvalState::Done;\n                    return Ok(IOResult::Done((Delta::new(), HashMap::default())));\n                }\n\n                let mut groups_to_read = BTreeMap::new();\n                for (row, _weight) in &deltas.left.changes {\n                    let group_key = self.extract_group_key(&row.values);\n                    let group_key_str = Self::group_key_to_string(&group_key);\n                    groups_to_read.insert(group_key_str, group_key);\n                }\n\n                let delta = std::mem::take(&mut deltas.left);\n                *state = EvalState::Aggregate(Box::new(AggregateEvalState::FetchKey {\n                    delta,\n                    current_idx: 0,\n                    groups_to_read: groups_to_read.into_iter().collect(),\n                    existing_groups: HashMap::default(),\n                    old_values: HashMap::default(),\n                    pre_existing_groups: HashSet::default(), // Initialize empty\n                }));\n            }\n            EvalState::Aggregate(_agg_state) => {\n                // Already in progress, continue processing below.\n            }\n            EvalState::Done => {\n                panic!(\"unreachable state! should have returned\");\n            }\n            EvalState::Join(_) => {\n                panic!(\"Join state should not appear in aggregate operator\");\n            }\n        }\n\n        // Process the delta through the aggregate state machine\n        match state {\n            EvalState::Aggregate(agg_state) => {\n                let result = return_if_io!(agg_state.process_delta(self, cursors));\n                Ok(IOResult::Done(result))\n            }\n            _ => panic!(\"Invalid state for aggregate processing\"),\n        }\n    }\n\n    fn merge_delta_with_existing(\n        &mut self,\n        delta: &Delta,\n        existing_groups: &mut HashMap<String, AggregateState>,\n        old_values: &mut HashMap<String, Vec<Value>>,\n        pre_existing_groups: &HashSet<String>,\n    ) -> MergeResult {\n        let mut output_delta = Delta::new();\n        let mut temp_keys: HashMap<String, Vec<Value>> = HashMap::default();\n\n        // Track distinct value weights as we process the batch\n        let mut batch_distinct_weights: HashMap<String, HashMap<(usize, HashableRow), isize>> =\n            HashMap::default();\n\n        // Process each change in the delta\n        for (row, weight) in delta.changes.iter() {\n            if let Some(tracker) = &self.tracker {\n                tracker.lock().record_aggregation();\n            }\n\n            // Extract group key\n            let group_key = self.extract_group_key(&row.values);\n            let group_key_str = Self::group_key_to_string(&group_key);\n\n            // Get or create the state for this group\n            let state = existing_groups.entry(group_key_str.clone()).or_default();\n\n            // Get batch weights for this group\n            let group_batch_weights = batch_distinct_weights.get(&group_key_str);\n\n            // Detect distinct transitions using the existing state and batch-accumulated weights\n            let distinct_transitions = if self.has_distinct() {\n                self.detect_distinct_transitions(&row.values, *weight, state, group_batch_weights)\n            } else {\n                HashMap::default()\n            };\n\n            // Update batch weights after detecting transitions\n            if self.has_distinct() {\n                for &col_idx in &self.distinct_columns {\n                    if let Some(val) = row.values.get(col_idx) {\n                        if val != &Value::Null {\n                            let hashable_row = HashableRow::new(col_idx as i64, vec![val.clone()]);\n                            let group_entry = batch_distinct_weights\n                                .entry(group_key_str.clone())\n                                .or_default();\n                            let weight_entry =\n                                group_entry.entry((col_idx, hashable_row)).or_insert(0);\n                            *weight_entry += weight;\n                        }\n                    }\n                }\n            }\n\n            temp_keys.insert(group_key_str.clone(), group_key.clone());\n\n            // Apply the delta to the state with pre-computed transitions\n            state.apply_delta(\n                &row.values,\n                *weight,\n                &self.aggregates,\n                &self.input_column_names,\n                &distinct_transitions,\n            );\n        }\n\n        // Generate output delta from temporary states and collect final states\n        let mut final_states = HashMap::default();\n\n        for (group_key_str, state) in existing_groups.iter() {\n            let group_key = if let Some(key) = temp_keys.get(group_key_str) {\n                key.clone()\n            } else if let Some(old_row) = old_values.get(group_key_str) {\n                // Extract group key from old row (first N columns where N = group_by.len())\n                old_row[0..self.group_by.len()].to_vec()\n            } else {\n                vec![]\n            };\n\n            // Generate synthetic rowid for this group\n            let result_key = self.generate_group_rowid(group_key_str);\n\n            // Always store the state for persistence (even if count=0, we need to delete it)\n            final_states.insert(group_key_str.clone(), (group_key.clone(), state.clone()));\n\n            // Check if we only have DISTINCT (no other aggregates)\n            if self.is_distinct_only {\n                // For plain DISTINCT, we output each distinct VALUE (not group)\n                // state.count tells us how many distinct values have positive weight\n\n                // Check if this group had any values before\n                let old_existed = pre_existing_groups.contains(group_key_str);\n                let new_exists = state.count > 0;\n\n                if old_existed && !new_exists {\n                    // All distinct values removed: output deletion\n                    if let Some(old_row_values) = old_values.get(group_key_str) {\n                        let old_row = HashableRow::new(result_key, old_row_values.clone());\n                        output_delta.changes.push((old_row, -1));\n                    } else {\n                        // For plain DISTINCT, the old row is just the group key itself\n                        let old_row = HashableRow::new(result_key, group_key.clone());\n                        output_delta.changes.push((old_row, -1));\n                    }\n                } else if !old_existed && new_exists {\n                    // First distinct value added: output insertion\n                    let output_values = group_key.clone();\n                    // DISTINCT doesn't add aggregate values - just the group key\n                    let output_row = HashableRow::new(result_key, output_values.clone());\n                    output_delta.changes.push((output_row, 1));\n                }\n                // No output if staying positive or staying at zero\n            } else {\n                // Normal aggregates: output deletions and insertions as before\n                if let Some(old_row_values) = old_values.get(group_key_str) {\n                    let old_row = HashableRow::new(result_key, old_row_values.clone());\n                    output_delta.changes.push((old_row, -1));\n                }\n\n                // Only include groups with count > 0 in the output delta\n                if state.count > 0 {\n                    // Build output row: group_by columns + aggregate values\n                    let mut output_values = group_key.clone();\n                    let aggregate_values = state.to_values(&self.aggregates);\n                    output_values.extend(aggregate_values);\n\n                    let output_row = HashableRow::new(result_key, output_values.clone());\n                    output_delta.changes.push((output_row, 1));\n                }\n            }\n        }\n\n        (output_delta, final_states)\n    }\n\n    /// Extract distinct values from delta changes for batch tracking\n    fn extract_distinct_deltas(&self, delta: &Delta) -> DistinctDeltas {\n        let mut distinct_deltas: DistinctDeltas = HashMap::default();\n\n        for (row, weight) in &delta.changes {\n            let group_key = self.extract_group_key(&row.values);\n            let group_key_str = Self::group_key_to_string(&group_key);\n\n            // Get or create entry for this group\n            let group_entry = distinct_deltas.entry(group_key_str.clone()).or_default();\n\n            if self.is_distinct_only {\n                // For plain DISTINCT, the group itself is what we're tracking\n                // We store a single entry that represents \"this group exists N times\"\n                // Use column index 0 with the group_key_str as the value\n                // For group key, use 0 as column index\n                let key = (\n                    0,\n                    HashableRow::new(0, vec![Value::Text(group_key_str.clone().into())]),\n                );\n                let value_entry = group_entry.entry(key).or_insert(0);\n                *value_entry += weight;\n            } else {\n                // For DISTINCT aggregates, track individual column values\n                for &col_idx in &self.distinct_columns {\n                    if let Some(val) = row.values.get(col_idx) {\n                        // Skip NULL values\n                        if val == &Value::Null {\n                            continue;\n                        }\n\n                        let key = (col_idx, HashableRow::new(col_idx as i64, vec![val.clone()]));\n                        let value_entry = group_entry.entry(key).or_insert(0);\n                        *value_entry += weight;\n                    }\n                }\n            }\n        }\n\n        distinct_deltas\n    }\n\n    /// Extract MIN/MAX values from delta changes for persistence to index\n    fn extract_min_max_deltas(&self, delta: &Delta) -> MinMaxDeltas {\n        let mut min_max_deltas: MinMaxDeltas = HashMap::default();\n\n        for (row, weight) in &delta.changes {\n            let group_key = self.extract_group_key(&row.values);\n            let group_key_str = Self::group_key_to_string(&group_key);\n\n            for agg in &self.aggregates {\n                match agg {\n                    AggregateFunction::Min(col_idx) | AggregateFunction::Max(col_idx) => {\n                        if let Some(val) = row.values.get(*col_idx) {\n                            // Skip NULL values - they don't participate in MIN/MAX\n                            if val == &Value::Null {\n                                continue;\n                            }\n                            // Create a HashableRow with just this value\n                            // Use 0 as rowid since we only care about the value for comparison\n                            let hashable_value = HashableRow::new(0, vec![val.clone()]);\n                            let key = (*col_idx, hashable_value);\n\n                            let group_entry =\n                                min_max_deltas.entry(group_key_str.clone()).or_default();\n\n                            let value_entry = group_entry.entry(key).or_insert(0);\n\n                            // Accumulate the weight\n                            *value_entry += weight;\n                        }\n                    }\n                    _ => {} // Ignore non-MIN/MAX aggregates\n                }\n            }\n        }\n\n        min_max_deltas\n    }\n\n    pub fn set_tracker(&mut self, tracker: Arc<Mutex<ComputationTracker>>) {\n        self.tracker = Some(tracker);\n    }\n\n    /// Generate a hash for a group\n    /// For no GROUP BY: returns a zero hash\n    /// For GROUP BY: returns a 128-bit hash of the group key string\n    pub fn generate_group_hash(&self, group_key_str: &str) -> Hash128 {\n        if self.group_by.is_empty() {\n            Hash128::new(0, 0)\n        } else {\n            Hash128::hash_str(group_key_str)\n        }\n    }\n\n    /// Generate a rowid for a group (for output rows)\n    /// This is NOT the hash used for storage (that's generate_group_hash which returns full 128-bit).\n    /// This is a synthetic rowid used in place of SQLite's rowid for aggregate output rows.\n    /// We truncate the 128-bit hash to 64 bits for SQLite rowid compatibility.\n    pub fn generate_group_rowid(&self, group_key_str: &str) -> i64 {\n        let hash = self.generate_group_hash(group_key_str);\n        hash.as_i64()\n    }\n\n    /// Extract group key values from a row\n    pub fn extract_group_key(&self, values: &[Value]) -> Vec<Value> {\n        let mut key = Vec::new();\n\n        for &idx in &self.group_by {\n            if let Some(val) = values.get(idx) {\n                key.push(val.clone());\n            } else {\n                key.push(Value::Null);\n            }\n        }\n\n        key\n    }\n\n    /// Convert group key to string for indexing (since Value doesn't implement Hash)\n    pub fn group_key_to_string(key: &[Value]) -> String {\n        key.iter()\n            .map(|v| format!(\"{v:?}\"))\n            .collect::<Vec<_>>()\n            .join(\",\")\n    }\n}\n\nimpl IncrementalOperator for AggregateOperator {\n    fn eval(\n        &mut self,\n        state: &mut EvalState,\n        cursors: &mut DbspStateCursors,\n    ) -> Result<IOResult<Delta>> {\n        let (delta, _) = return_if_io!(self.eval_internal(state, cursors));\n        Ok(IOResult::Done(delta))\n    }\n\n    fn commit(\n        &mut self,\n        mut deltas: DeltaPair,\n        cursors: &mut DbspStateCursors,\n    ) -> Result<IOResult<Delta>> {\n        // Aggregate operator only uses left delta, right must be empty\n        assert!(\n            deltas.right.is_empty(),\n            \"AggregateOperator expects right delta to be empty in commit\"\n        );\n        let delta = std::mem::take(&mut deltas.left);\n        loop {\n            // Note: because we std::mem::replace here (without it, the borrow checker goes nuts,\n            // because we call self.eval_interval, which requires a mutable borrow), we have to\n            // restore the state if we return I/O. So we can't use return_if_io!\n            let mut state =\n                std::mem::replace(&mut self.commit_state, AggregateCommitState::Invalid);\n            match &mut state {\n                AggregateCommitState::Invalid => {\n                    panic!(\"Reached invalid state! State was replaced, and not replaced back\");\n                }\n                AggregateCommitState::Idle => {\n                    let eval_state = EvalState::from_delta(delta.clone());\n                    self.commit_state = AggregateCommitState::Eval { eval_state };\n                }\n                AggregateCommitState::Eval { ref mut eval_state } => {\n                    // Clone the delta for MIN/MAX processing before eval consumes it\n                    // We need to get the delta from the eval_state if it's still in Init\n                    let input_delta = match eval_state {\n                        EvalState::Init { deltas } => deltas.left.clone(),\n                        _ => Delta::new(), // Empty delta if already processed\n                    };\n\n                    // Extract MIN/MAX and DISTINCT deltas before any I/O operations\n                    let min_max_deltas = self.extract_min_max_deltas(&input_delta);\n                    // For plain DISTINCT, we need to extract deltas too\n                    let distinct_deltas = if self.has_distinct() || self.is_distinct_only {\n                        self.extract_distinct_deltas(&input_delta)\n                    } else {\n                        HashMap::default()\n                    };\n\n                    // Get old counts before eval modifies the states\n                    // We need to extract this from the eval_state before it's consumed\n                    let old_states = HashMap::default(); // TODO: Extract from eval_state\n\n                    let (output_delta, computed_states) = return_and_restore_if_io!(\n                        &mut self.commit_state,\n                        state,\n                        self.eval_internal(eval_state, cursors)\n                    );\n\n                    self.commit_state = AggregateCommitState::PersistDelta {\n                        delta: output_delta,\n                        computed_states,\n                        old_states,\n                        current_idx: 0,\n                        write_row: WriteRow::new(),\n                        min_max_deltas,  // Store for later use\n                        distinct_deltas, // Store for distinct processing\n                        input_delta,     // Store original input\n                    };\n                }\n                AggregateCommitState::PersistDelta {\n                    delta,\n                    computed_states,\n                    old_states,\n                    current_idx,\n                    write_row,\n                    min_max_deltas,\n                    distinct_deltas,\n                    input_delta,\n                } => {\n                    let states_vec: Vec<_> = computed_states.iter().collect();\n\n                    if *current_idx >= states_vec.len() {\n                        // Use the min_max_deltas we extracted earlier from the input delta\n                        self.commit_state = AggregateCommitState::PersistMinMax {\n                            delta: delta.clone(),\n                            min_max_persist_state: MinMaxPersistState::new(min_max_deltas.clone()),\n                            distinct_deltas: distinct_deltas.clone(),\n                        };\n                    } else {\n                        let (group_key_str, (group_key, agg_state)) = states_vec[*current_idx];\n\n                        // Skip aggregate state persistence for plain DISTINCT\n                        // Plain DISTINCT only uses the distinct value weights, not aggregate state\n                        if self.is_distinct_only {\n                            // Skip to next - distinct values are handled in PersistDistinctValues\n                            // We still need to transition states properly\n                            let next_idx = *current_idx + 1;\n                            if next_idx >= states_vec.len() {\n                                // Done with all groups, move to PersistMinMax\n                                self.commit_state = AggregateCommitState::PersistMinMax {\n                                    delta: std::mem::take(delta),\n                                    min_max_persist_state: MinMaxPersistState::new(std::mem::take(\n                                        min_max_deltas,\n                                    )),\n                                    distinct_deltas: std::mem::take(distinct_deltas),\n                                };\n                            } else {\n                                // Move to next group\n                                self.commit_state = AggregateCommitState::PersistDelta {\n                                    delta: std::mem::take(delta),\n                                    computed_states: std::mem::take(computed_states),\n                                    old_states: std::mem::take(old_states),\n                                    current_idx: next_idx,\n                                    write_row: WriteRow::new(),\n                                    min_max_deltas: std::mem::take(min_max_deltas),\n                                    distinct_deltas: std::mem::take(distinct_deltas),\n                                    input_delta: std::mem::take(input_delta),\n                                };\n                            }\n                            continue;\n                        }\n\n                        // Build the key components for regular aggregates\n                        let operator_storage_id =\n                            generate_storage_id(self.operator_id, 0, AGG_TYPE_REGULAR);\n                        let zset_hash = self.generate_group_hash(group_key_str);\n                        let element_id = Hash128::new(0, 0); // Always zeros for regular aggregates\n\n                        // Determine weight: 1 if exists, -1 if deleted\n                        let weight = if agg_state.count == 0 { -1 } else { 1 };\n\n                        // Serialize the aggregate state (only for regular aggregates, not plain DISTINCT)\n                        let state_blob = agg_state.to_blob(&self.aggregates, group_key);\n                        let blob_value = Value::Blob(state_blob);\n\n                        // Build the aggregate storage format: [operator_id, zset_hash, element_id, value, weight]\n                        let operator_id_val = Value::from_i64(operator_storage_id);\n                        let zset_hash_val = zset_hash.to_value();\n                        let element_id_val = element_id.to_value();\n                        let blob_val = blob_value.clone();\n\n                        // Create index key - the first 3 columns of our primary key\n                        let index_key = vec![\n                            operator_id_val.clone(),\n                            zset_hash_val.clone(),\n                            element_id_val.clone(),\n                        ];\n\n                        // Record values (without weight)\n                        let record_values =\n                            vec![operator_id_val, zset_hash_val, element_id_val, blob_val];\n\n                        return_and_restore_if_io!(\n                            &mut self.commit_state,\n                            state,\n                            write_row.write_row(cursors, index_key, record_values, weight)\n                        );\n\n                        let delta = std::mem::take(delta);\n                        let computed_states = std::mem::take(computed_states);\n                        let min_max_deltas = std::mem::take(min_max_deltas);\n                        let distinct_deltas = std::mem::take(distinct_deltas);\n                        let input_delta = std::mem::take(input_delta);\n\n                        self.commit_state = AggregateCommitState::PersistDelta {\n                            delta,\n                            computed_states,\n                            old_states: std::mem::take(old_states),\n                            current_idx: *current_idx + 1,\n                            write_row: WriteRow::new(), // Reset for next write\n                            min_max_deltas,\n                            distinct_deltas,\n                            input_delta,\n                        };\n                    }\n                }\n                AggregateCommitState::PersistMinMax {\n                    delta,\n                    min_max_persist_state,\n                    distinct_deltas,\n                } => {\n                    if self.has_min_max() {\n                        return_and_restore_if_io!(\n                            &mut self.commit_state,\n                            state,\n                            min_max_persist_state.persist_min_max(\n                                self.operator_id,\n                                &self.column_min_max,\n                                cursors,\n                                |group_key_str| self.generate_group_hash(group_key_str)\n                            )\n                        );\n                    }\n\n                    // Transition to PersistDistinctValues\n                    let delta = std::mem::take(delta);\n                    let distinct_deltas = std::mem::take(distinct_deltas);\n                    let distinct_persist_state = DistinctPersistState::new(distinct_deltas);\n                    self.commit_state = AggregateCommitState::PersistDistinctValues {\n                        delta,\n                        distinct_persist_state,\n                    };\n                }\n                AggregateCommitState::PersistDistinctValues {\n                    delta,\n                    distinct_persist_state,\n                } => {\n                    if self.has_distinct() {\n                        // Use the state machine to persist distinct values to BTree\n                        return_and_restore_if_io!(\n                            &mut self.commit_state,\n                            state,\n                            distinct_persist_state.persist_distinct_values(\n                                self.operator_id,\n                                cursors,\n                                |group_key_str| self.generate_group_hash(group_key_str)\n                            )\n                        );\n                    }\n\n                    // Transition to Done\n                    let delta = std::mem::take(delta);\n                    self.commit_state = AggregateCommitState::Done { delta };\n                }\n                AggregateCommitState::Done { delta } => {\n                    self.commit_state = AggregateCommitState::Idle;\n                    let delta = std::mem::take(delta);\n                    return Ok(IOResult::Done(delta));\n                }\n            }\n        }\n    }\n\n    fn set_tracker(&mut self, tracker: Arc<Mutex<ComputationTracker>>) {\n        self.tracker = Some(tracker);\n    }\n}\n\n/// State machine for recomputing MIN/MAX values after deletion\n#[derive(Debug)]\npub enum RecomputeMinMax {\n    ProcessElements {\n        /// Current column being processed\n        current_column_idx: usize,\n        /// Columns to process (combined MIN and MAX)\n        columns_to_process: Vec<(String, usize, bool)>, // (group_key, column_name, is_min)\n        /// MIN/MAX deltas for checking values and weights\n        min_max_deltas: MinMaxDeltas,\n    },\n    Scan {\n        /// Columns still to process\n        columns_to_process: Vec<(String, usize, bool)>,\n        /// Current index in columns_to_process (will resume from here)\n        current_column_idx: usize,\n        /// MIN/MAX deltas for checking values and weights\n        min_max_deltas: MinMaxDeltas,\n        /// Current group key being processed\n        group_key: String,\n        /// Current column name being processed\n        column_name: usize,\n        /// Whether we're looking for MIN (true) or MAX (false)\n        is_min: bool,\n        /// The scan state machine for finding the new MIN/MAX\n        scan_state: Box<ScanState>,\n    },\n    Done,\n}\n\nimpl RecomputeMinMax {\n    pub fn new(\n        min_max_deltas: MinMaxDeltas,\n        existing_groups: &HashMap<String, AggregateState>,\n        operator: &AggregateOperator,\n    ) -> Self {\n        let mut groups_to_check: HashSet<(String, usize, bool)> = HashSet::default();\n\n        // Remember the min_max_deltas are essentially just the only column that is affected by\n        // this min/max, in delta (actually ZSet - consolidated delta) format. This makes it easier\n        // for us to consume it in here.\n        //\n        // The most challenging case is the case where there is a retraction, since we need to go\n        // back to the index.\n        for (group_key_str, values) in &min_max_deltas {\n            for ((col_name, hashable_row), weight) in values {\n                let col_info = operator.column_min_max.get(col_name);\n\n                let value = &hashable_row.values[0];\n\n                if *weight < 0 {\n                    // Deletion detected - check if it's the current MIN/MAX\n                    if let Some(state) = existing_groups.get(group_key_str) {\n                        // Check for MIN\n                        if let Some(current_min) = state.mins.get(col_name) {\n                            if current_min == value {\n                                groups_to_check.insert((group_key_str.clone(), *col_name, true));\n                            }\n                        }\n                        // Check for MAX\n                        if let Some(current_max) = state.maxs.get(col_name) {\n                            if current_max == value {\n                                groups_to_check.insert((group_key_str.clone(), *col_name, false));\n                            }\n                        }\n                    }\n                } else if *weight > 0 {\n                    // If it is not found in the existing groups, then we only need to care\n                    // about this if this is a new record being inserted\n                    if let Some(info) = col_info {\n                        if info.has_min {\n                            groups_to_check.insert((group_key_str.clone(), *col_name, true));\n                        }\n                        if info.has_max {\n                            groups_to_check.insert((group_key_str.clone(), *col_name, false));\n                        }\n                    }\n                }\n            }\n        }\n\n        if groups_to_check.is_empty() {\n            // No recomputation or initialization needed\n            Self::Done\n        } else {\n            // Convert HashSet to Vec for indexed processing\n            let groups_to_check_vec: Vec<_> = groups_to_check.into_iter().collect();\n            Self::ProcessElements {\n                current_column_idx: 0,\n                columns_to_process: groups_to_check_vec,\n                min_max_deltas,\n            }\n        }\n    }\n\n    pub fn process(\n        &mut self,\n        existing_groups: &mut HashMap<String, AggregateState>,\n        operator: &AggregateOperator,\n        cursors: &mut DbspStateCursors,\n    ) -> Result<IOResult<()>> {\n        loop {\n            match self {\n                RecomputeMinMax::ProcessElements {\n                    current_column_idx,\n                    columns_to_process,\n                    min_max_deltas,\n                } => {\n                    if *current_column_idx >= columns_to_process.len() {\n                        *self = RecomputeMinMax::Done;\n                        return Ok(IOResult::Done(()));\n                    }\n\n                    let (group_key, column_name, is_min) =\n                        columns_to_process[*current_column_idx].clone();\n\n                    // Column name is already the index\n                    // Get the storage index from column_min_max map\n                    let column_info = operator\n                        .column_min_max\n                        .get(&column_name)\n                        .expect(\"Column should exist in column_min_max map\");\n                    let storage_index = column_info.index;\n\n                    // Get current value from existing state\n                    let current_value = existing_groups.get(&group_key).and_then(|state| {\n                        if is_min {\n                            state.mins.get(&column_name).cloned()\n                        } else {\n                            state.maxs.get(&column_name).cloned()\n                        }\n                    });\n\n                    // Create storage keys for index lookup\n                    let storage_id =\n                        generate_storage_id(operator.operator_id, storage_index, AGG_TYPE_MINMAX);\n                    let zset_hash = operator.generate_group_hash(&group_key);\n\n                    // Get the values for this group from min_max_deltas\n                    let group_values = min_max_deltas.get(&group_key).cloned().unwrap_or_default();\n\n                    let columns_to_process = std::mem::take(columns_to_process);\n                    let min_max_deltas = std::mem::take(min_max_deltas);\n\n                    let scan_state = if is_min {\n                        Box::new(ScanState::new_for_min(\n                            current_value,\n                            group_key.clone(),\n                            column_name,\n                            storage_id,\n                            zset_hash,\n                            group_values,\n                        ))\n                    } else {\n                        Box::new(ScanState::new_for_max(\n                            current_value,\n                            group_key.clone(),\n                            column_name,\n                            storage_id,\n                            zset_hash,\n                            group_values,\n                        ))\n                    };\n\n                    *self = RecomputeMinMax::Scan {\n                        columns_to_process,\n                        current_column_idx: *current_column_idx,\n                        min_max_deltas,\n                        group_key,\n                        column_name,\n                        is_min,\n                        scan_state,\n                    };\n                }\n                RecomputeMinMax::Scan {\n                    columns_to_process,\n                    current_column_idx,\n                    min_max_deltas,\n                    group_key,\n                    column_name,\n                    is_min,\n                    scan_state,\n                } => {\n                    // Find new value using the scan state machine\n                    let new_value = return_if_io!(scan_state.find_new_value(cursors));\n\n                    // Update the state with new value (create if doesn't exist)\n                    let state = existing_groups.entry(group_key.clone()).or_default();\n\n                    if *is_min {\n                        if let Some(min_val) = new_value {\n                            state.mins.insert(*column_name, min_val);\n                        } else {\n                            state.mins.remove(column_name);\n                        }\n                    } else if let Some(max_val) = new_value {\n                        state.maxs.insert(*column_name, max_val);\n                    } else {\n                        state.maxs.remove(column_name);\n                    }\n\n                    // Move to next column\n                    let min_max_deltas = std::mem::take(min_max_deltas);\n                    let columns_to_process = std::mem::take(columns_to_process);\n                    *self = RecomputeMinMax::ProcessElements {\n                        current_column_idx: *current_column_idx + 1,\n                        columns_to_process,\n                        min_max_deltas,\n                    };\n                }\n                RecomputeMinMax::Done => {\n                    return Ok(IOResult::Done(()));\n                }\n            }\n        }\n    }\n}\n\n/// State machine for scanning through the index to find new MIN/MAX values\n#[derive(Debug)]\npub enum ScanState {\n    CheckCandidate {\n        /// Current candidate value for MIN/MAX\n        candidate: Option<Value>,\n        /// Group key being processed\n        group_key: String,\n        /// Column name being processed\n        column_name: usize,\n        /// Storage ID for the index seek\n        storage_id: i64,\n        /// ZSet ID for the group\n        zset_hash: Hash128,\n        /// Group values from MinMaxDeltas: (column_name, HashableRow) -> weight\n        group_values: HashMap<(usize, HashableRow), isize>,\n        /// Whether we're looking for MIN (true) or MAX (false)\n        is_min: bool,\n    },\n    FetchNextCandidate {\n        /// Current candidate to seek past\n        current_candidate: Value,\n        /// Group key being processed\n        group_key: String,\n        /// Column name being processed\n        column_name: usize,\n        /// Storage ID for the index seek\n        storage_id: i64,\n        /// ZSet ID for the group\n        zset_hash: Hash128,\n        /// Group values from MinMaxDeltas: (column_name, HashableRow) -> weight\n        group_values: HashMap<(usize, HashableRow), isize>,\n        /// Whether we're looking for MIN (true) or MAX (false)\n        is_min: bool,\n    },\n    Done {\n        /// The final MIN/MAX value found\n        result: Option<Value>,\n    },\n}\n\nimpl ScanState {\n    pub fn new_for_min(\n        current_min: Option<Value>,\n        group_key: String,\n        column_name: usize,\n        storage_id: i64,\n        zset_hash: Hash128,\n        group_values: HashMap<(usize, HashableRow), isize>,\n    ) -> Self {\n        Self::CheckCandidate {\n            candidate: current_min,\n            group_key,\n            column_name,\n            storage_id,\n            zset_hash,\n            group_values,\n            is_min: true,\n        }\n    }\n\n    // Extract a new candidate from the index. It is possible that, when searching,\n    // we end up going into a different operator altogether. That means we have\n    // exhausted this operator (or group) entirely, and no good candidate was found\n    fn extract_new_candidate(\n        cursors: &mut DbspStateCursors,\n        index_record: &ImmutableRecord,\n        seek_op: SeekOp,\n        storage_id: i64,\n        zset_hash: Hash128,\n    ) -> Result<IOResult<Option<Value>>> {\n        let seek_result = return_if_io!(cursors\n            .index_cursor\n            .seek(SeekKey::IndexKey(index_record), seek_op));\n        if !matches!(seek_result, SeekResult::Found) {\n            return Ok(IOResult::Done(None));\n        }\n\n        let record = return_if_io!(cursors.index_cursor.record()).ok_or_else(|| {\n            LimboError::InternalError(\n                \"Record found on the cursor, but could not be read\".to_string(),\n            )\n        })?;\n\n        let mut values = record.iter()?;\n\n        let Some(rec_storage_id) = values.next() else {\n            return Ok(IOResult::Done(None));\n        };\n\n        let Some(rec_zset_hash) = values.next() else {\n            return Ok(IOResult::Done(None));\n        };\n\n        // Check if we're still in the same group\n        if let ValueRef::Numeric(Numeric::Integer(rec_sid)) = rec_storage_id? {\n            if rec_sid != storage_id {\n                return Ok(IOResult::Done(None));\n            }\n        } else {\n            return Ok(IOResult::Done(None));\n        }\n\n        // Compare zset_hash as blob\n        if let ValueRef::Blob(rec_zset_blob) = rec_zset_hash? {\n            if let Some(rec_hash) = Hash128::from_blob(rec_zset_blob) {\n                if rec_hash != zset_hash {\n                    return Ok(IOResult::Done(None));\n                }\n            } else {\n                return Ok(IOResult::Done(None));\n            }\n        } else {\n            return Ok(IOResult::Done(None));\n        }\n\n        let third = values.next();\n        let Some(third) = third else {\n            return Ok(IOResult::Done(None));\n        };\n\n        // Get the value (3rd element)\n        Ok(IOResult::Done(Some(third?.to_owned())))\n    }\n\n    pub fn new_for_max(\n        current_max: Option<Value>,\n        group_key: String,\n        column_name: usize,\n        storage_id: i64,\n        zset_hash: Hash128,\n        group_values: HashMap<(usize, HashableRow), isize>,\n    ) -> Self {\n        Self::CheckCandidate {\n            candidate: current_max,\n            group_key,\n            column_name,\n            storage_id,\n            zset_hash,\n            group_values,\n            is_min: false,\n        }\n    }\n\n    pub fn find_new_value(\n        &mut self,\n        cursors: &mut DbspStateCursors,\n    ) -> Result<IOResult<Option<Value>>> {\n        loop {\n            match self {\n                ScanState::CheckCandidate {\n                    candidate,\n                    group_key,\n                    column_name,\n                    storage_id,\n                    zset_hash,\n                    group_values,\n                    is_min,\n                } => {\n                    // First, check if we have a candidate\n                    if let Some(cand_val) = candidate {\n                        // Check if the candidate is retracted (weight <= 0)\n                        // Create a HashableRow to look up the weight\n                        let hashable_cand = HashableRow::new(0, vec![cand_val.clone()]);\n                        let key = (*column_name, hashable_cand);\n                        let is_retracted =\n                            group_values.get(&key).is_some_and(|weight| *weight <= 0);\n\n                        if is_retracted {\n                            // Candidate is retracted, need to fetch next from index\n                            *self = ScanState::FetchNextCandidate {\n                                current_candidate: cand_val.clone(),\n                                group_key: std::mem::take(group_key),\n                                column_name: std::mem::take(column_name),\n                                storage_id: *storage_id,\n                                zset_hash: *zset_hash,\n                                group_values: std::mem::take(group_values),\n                                is_min: *is_min,\n                            };\n                            continue;\n                        }\n                    }\n\n                    // Candidate is valid or we have no candidate\n                    // Now find the best value from insertions in group_values\n                    let mut best_from_zset = None;\n                    for ((col, hashable_val), weight) in group_values.iter() {\n                        if col == column_name && *weight > 0 {\n                            let value = &hashable_val.values[0];\n                            // Skip NULL values - they don't participate in MIN/MAX\n                            if value == &Value::Null {\n                                continue;\n                            }\n                            // This is an insertion for our column\n                            if let Some(ref current_best) = best_from_zset {\n                                if *is_min {\n                                    if value.cmp(current_best) == std::cmp::Ordering::Less {\n                                        best_from_zset = Some(value.clone());\n                                    }\n                                } else if value.cmp(current_best) == std::cmp::Ordering::Greater {\n                                    best_from_zset = Some(value.clone());\n                                }\n                            } else {\n                                best_from_zset = Some(value.clone());\n                            }\n                        }\n                    }\n\n                    // Compare candidate with best from ZSet, filtering out NULLs\n                    let result = match (&candidate, &best_from_zset) {\n                        (Some(cand), Some(zset_val)) if cand != &Value::Null => {\n                            if *is_min {\n                                if zset_val.cmp(cand) == std::cmp::Ordering::Less {\n                                    Some(zset_val.clone())\n                                } else {\n                                    Some(cand.clone())\n                                }\n                            } else if zset_val.cmp(cand) == std::cmp::Ordering::Greater {\n                                Some(zset_val.clone())\n                            } else {\n                                Some(cand.clone())\n                            }\n                        }\n                        (Some(cand), None) if cand != &Value::Null => Some(cand.clone()),\n                        (None, Some(zset_val)) => Some(zset_val.clone()),\n                        (Some(cand), Some(_)) if cand == &Value::Null => best_from_zset,\n                        _ => None,\n                    };\n\n                    *self = ScanState::Done { result };\n                }\n\n                ScanState::FetchNextCandidate {\n                    current_candidate,\n                    group_key,\n                    column_name,\n                    storage_id,\n                    zset_hash,\n                    group_values,\n                    is_min,\n                } => {\n                    // Seek to the next value in the index\n                    let index_key = vec![\n                        Value::from_i64(*storage_id),\n                        zset_hash.to_value(),\n                        current_candidate.clone(),\n                    ];\n                    let index_record = ImmutableRecord::from_values(&index_key, index_key.len());\n\n                    let seek_op = if *is_min {\n                        SeekOp::GT // For MIN, seek greater than current\n                    } else {\n                        SeekOp::LT // For MAX, seek less than current\n                    };\n\n                    let new_candidate = return_if_io!(Self::extract_new_candidate(\n                        cursors,\n                        &index_record,\n                        seek_op,\n                        *storage_id,\n                        *zset_hash\n                    ));\n\n                    *self = ScanState::CheckCandidate {\n                        candidate: new_candidate,\n                        group_key: std::mem::take(group_key),\n                        column_name: std::mem::take(column_name),\n                        storage_id: *storage_id,\n                        zset_hash: *zset_hash,\n                        group_values: std::mem::take(group_values),\n                        is_min: *is_min,\n                    };\n                }\n\n                ScanState::Done { result } => {\n                    return Ok(IOResult::Done(result.clone()));\n                }\n            }\n        }\n    }\n}\n\n/// State machine for persisting Min/Max values to storage\n#[derive(Debug)]\npub enum MinMaxPersistState {\n    Init {\n        min_max_deltas: MinMaxDeltas,\n        group_keys: Vec<String>,\n    },\n    ProcessGroup {\n        min_max_deltas: MinMaxDeltas,\n        group_keys: Vec<String>,\n        group_idx: usize,\n        value_idx: usize,\n    },\n    WriteValue {\n        min_max_deltas: MinMaxDeltas,\n        group_keys: Vec<String>,\n        group_idx: usize,\n        value_idx: usize,\n        value: Value,\n        column_name: usize,\n        weight: isize,\n        write_row: WriteRow,\n    },\n    Done,\n}\n\n/// State machine for fetching distinct values from BTree storage\n#[derive(Debug)]\npub enum FetchDistinctState {\n    Init {\n        groups_to_fetch: Vec<(String, HashMap<usize, HashSet<HashableRow>>)>,\n    },\n    FetchGroup {\n        groups_to_fetch: Vec<(String, HashMap<usize, HashSet<HashableRow>>)>,\n        group_idx: usize,\n        value_idx: usize,\n        values_to_fetch: Vec<(usize, Value)>,\n    },\n    ReadValue {\n        groups_to_fetch: Vec<(String, HashMap<usize, HashSet<HashableRow>>)>,\n        group_idx: usize,\n        value_idx: usize,\n        values_to_fetch: Vec<(usize, Value)>,\n        group_key: String,\n        column_idx: usize,\n        value: Value,\n    },\n    Done,\n}\n\nimpl FetchDistinctState {\n    /// Add fetch entry for plain DISTINCT - the group itself is the distinct value\n    fn add_plain_distinct_fetch(\n        group_entry: &mut HashMap<usize, HashSet<HashableRow>>,\n        group_key_str: &str,\n    ) {\n        let group_value = Value::Text(group_key_str.to_string().into());\n        group_entry\n            .entry(0)\n            .or_default()\n            .insert(HashableRow::new(0, vec![group_value]));\n    }\n\n    /// Add fetch entries for DISTINCT aggregates - individual column values\n    fn add_aggregate_distinct_fetch(\n        group_entry: &mut HashMap<usize, HashSet<HashableRow>>,\n        row_values: &[Value],\n        distinct_columns: &HashSet<usize>,\n    ) {\n        for &col_idx in distinct_columns {\n            if let Some(val) = row_values.get(col_idx) {\n                if val != &Value::Null {\n                    group_entry\n                        .entry(col_idx)\n                        .or_default()\n                        .insert(HashableRow::new(col_idx as i64, vec![val.clone()]));\n                }\n            }\n        }\n    }\n\n    pub fn new(\n        delta: &Delta,\n        distinct_columns: &HashSet<usize>,\n        extract_group_key: impl Fn(&[Value]) -> Vec<Value>,\n        group_key_to_string: impl Fn(&[Value]) -> String,\n        existing_groups: &HashMap<String, AggregateState>,\n        is_plain_distinct: bool,\n    ) -> Self {\n        let mut groups_to_fetch: HashMap<String, HashMap<usize, HashSet<HashableRow>>> =\n            HashMap::default();\n\n        for (row, _weight) in &delta.changes {\n            let group_key = extract_group_key(&row.values);\n            let group_key_str = group_key_to_string(&group_key);\n\n            // Skip groups we don't need to fetch\n            // For DISTINCT aggregates, only fetch for existing groups\n            if !is_plain_distinct && !existing_groups.contains_key(&group_key_str) {\n                continue;\n            }\n\n            let group_entry = groups_to_fetch.entry(group_key_str.clone()).or_default();\n\n            if is_plain_distinct {\n                Self::add_plain_distinct_fetch(group_entry, &group_key_str);\n            } else {\n                Self::add_aggregate_distinct_fetch(group_entry, &row.values, distinct_columns);\n            }\n        }\n\n        let groups_to_fetch: Vec<_> = groups_to_fetch.into_iter().collect();\n\n        if groups_to_fetch.is_empty() {\n            Self::Done\n        } else {\n            Self::Init { groups_to_fetch }\n        }\n    }\n\n    pub fn fetch_distinct_values(\n        &mut self,\n        operator_id: i64,\n        existing_groups: &mut HashMap<String, AggregateState>,\n        cursors: &mut DbspStateCursors,\n        generate_group_hash: impl Fn(&str) -> Hash128,\n        is_plain_distinct: bool,\n    ) -> Result<IOResult<()>> {\n        loop {\n            match self {\n                FetchDistinctState::Init { groups_to_fetch } => {\n                    if groups_to_fetch.is_empty() {\n                        *self = FetchDistinctState::Done;\n                        continue;\n                    }\n\n                    let groups = std::mem::take(groups_to_fetch);\n                    *self = FetchDistinctState::FetchGroup {\n                        groups_to_fetch: groups,\n                        group_idx: 0,\n                        value_idx: 0,\n                        values_to_fetch: Vec::new(),\n                    };\n                }\n                FetchDistinctState::FetchGroup {\n                    groups_to_fetch,\n                    group_idx,\n                    value_idx,\n                    values_to_fetch,\n                } => {\n                    if *group_idx >= groups_to_fetch.len() {\n                        *self = FetchDistinctState::Done;\n                        continue;\n                    }\n\n                    // Build list of values to fetch for current group if not done\n                    if values_to_fetch.is_empty() && *group_idx < groups_to_fetch.len() {\n                        let (_group_key, cols_values) = &groups_to_fetch[*group_idx];\n                        for (col_idx, values) in cols_values {\n                            for hashable_row in values {\n                                // Extract the value from HashableRow\n                                let value = hashable_row.values.first().ok_or_else(|| {\n                                    LimboError::InternalError(\n                                        \"hashable_row should have at least one value\".to_string(),\n                                    )\n                                })?;\n                                values_to_fetch.push((*col_idx, value.clone()));\n                            }\n                        }\n                    }\n\n                    if *value_idx >= values_to_fetch.len() {\n                        // Move to next group\n                        *group_idx += 1;\n                        *value_idx = 0;\n                        values_to_fetch.clear();\n                        continue;\n                    }\n\n                    // Fetch current value\n                    let (group_key, _) = groups_to_fetch[*group_idx].clone();\n                    let (column_idx, value) = values_to_fetch[*value_idx].clone();\n\n                    let groups = std::mem::take(groups_to_fetch);\n                    let values = std::mem::take(values_to_fetch);\n                    *self = FetchDistinctState::ReadValue {\n                        groups_to_fetch: groups,\n                        group_idx: *group_idx,\n                        value_idx: *value_idx,\n                        values_to_fetch: values,\n                        group_key,\n                        column_idx,\n                        value,\n                    };\n                }\n                FetchDistinctState::ReadValue {\n                    groups_to_fetch,\n                    group_idx,\n                    value_idx,\n                    values_to_fetch,\n                    group_key,\n                    column_idx,\n                    value,\n                } => {\n                    // Read the record from BTree using the same pattern as WriteRow:\n                    // 1. Seek in index to find the entry\n                    // 2. Get rowid from index cursor\n                    // 3. Use rowid to read from table cursor\n                    let storage_id =\n                        generate_storage_id(operator_id, *column_idx, AGG_TYPE_DISTINCT);\n                    let zset_hash = generate_group_hash(group_key);\n                    let element_id = hash_value(value, *column_idx);\n\n                    // First, seek in the index cursor\n                    let index_key = vec![\n                        Value::from_i64(storage_id),\n                        zset_hash.to_value(),\n                        element_id.to_value(),\n                    ];\n                    let index_record = ImmutableRecord::from_values(&index_key, index_key.len());\n\n                    let seek_result = return_if_io!(cursors.index_cursor.seek(\n                        SeekKey::IndexKey(&index_record),\n                        SeekOp::GE { eq_only: true }\n                    ));\n\n                    // Early exit if not found in index\n                    if !matches!(seek_result, SeekResult::Found) {\n                        let groups = std::mem::take(groups_to_fetch);\n                        let values = std::mem::take(values_to_fetch);\n                        *self = FetchDistinctState::FetchGroup {\n                            groups_to_fetch: groups,\n                            group_idx: *group_idx,\n                            value_idx: *value_idx + 1,\n                            values_to_fetch: values,\n                        };\n                        continue;\n                    }\n\n                    // Get the rowid from the index cursor\n                    let rowid = return_if_io!(cursors.index_cursor.rowid());\n\n                    // Early exit if no rowid\n                    let rowid = match rowid {\n                        Some(id) => id,\n                        None => {\n                            let groups = std::mem::take(groups_to_fetch);\n                            let values = std::mem::take(values_to_fetch);\n                            *self = FetchDistinctState::FetchGroup {\n                                groups_to_fetch: groups,\n                                group_idx: *group_idx,\n                                value_idx: *value_idx + 1,\n                                values_to_fetch: values,\n                            };\n                            continue;\n                        }\n                    };\n\n                    // Now seek in the table cursor using the rowid\n                    let table_result = return_if_io!(cursors\n                        .table_cursor\n                        .seek(SeekKey::TableRowId(rowid), SeekOp::GE { eq_only: true }));\n\n                    // Early exit if not found in table\n                    if !matches!(table_result, SeekResult::Found) {\n                        let groups = std::mem::take(groups_to_fetch);\n                        let values = std::mem::take(values_to_fetch);\n                        *self = FetchDistinctState::FetchGroup {\n                            groups_to_fetch: groups,\n                            group_idx: *group_idx,\n                            value_idx: *value_idx + 1,\n                            values_to_fetch: values,\n                        };\n                        continue;\n                    }\n\n                    // Read the actual record from the table cursor\n                    let record = return_if_io!(cursors.table_cursor.record());\n\n                    if let Some(r) = record {\n                        // The table has 5 columns: storage_id, zset_hash, element_id, blob, weight\n                        // The weight is at index 4\n                        if let Some(weight) = r.get_value_opt(4) {\n                            // Get the weight directly from column 5(index 4)\n                            let weight = match weight.to_owned() {\n                                Value::Numeric(Numeric::Integer(w)) => w,\n                                _ => 0,\n                            };\n\n                            // Store the weight in the existing group's state\n                            let state = existing_groups.entry(group_key.clone()).or_default();\n                            state.distinct_value_weights.insert(\n                                (\n                                    *column_idx,\n                                    HashableRow::new(*column_idx as i64, vec![value.clone()]),\n                                ),\n                                weight,\n                            );\n                        }\n                    }\n\n                    // Move to next value\n                    let groups = std::mem::take(groups_to_fetch);\n                    let values = std::mem::take(values_to_fetch);\n                    *self = FetchDistinctState::FetchGroup {\n                        groups_to_fetch: groups,\n                        group_idx: *group_idx,\n                        value_idx: *value_idx + 1,\n                        values_to_fetch: values,\n                    };\n                }\n                FetchDistinctState::Done => {\n                    // For plain DISTINCT, construct AggregateState from the weights we fetched\n                    if is_plain_distinct {\n                        for (_group_key_str, state) in existing_groups.iter_mut() {\n                            // For plain DISTINCT, sum all the weights to get total count\n                            // Each weight represents how many times the distinct value appears\n                            let total_weight: i64 = state.distinct_value_weights.values().sum();\n\n                            // Set the count based on total weight\n                            state.count = total_weight;\n                        }\n                    }\n                    return Ok(IOResult::Done(()));\n                }\n            }\n        }\n    }\n}\n\n/// State machine for persisting distinct values to BTree storage\n#[derive(Debug)]\npub enum DistinctPersistState {\n    Init {\n        distinct_deltas: DistinctDeltas,\n        group_keys: Vec<String>,\n    },\n    ProcessGroup {\n        distinct_deltas: DistinctDeltas,\n        group_keys: Vec<String>,\n        group_idx: usize,\n        value_keys: Vec<(usize, HashableRow)>, // (col_idx, value) pairs for current group\n        value_idx: usize,\n    },\n    WriteValue {\n        distinct_deltas: DistinctDeltas,\n        group_keys: Vec<String>,\n        group_idx: usize,\n        value_keys: Vec<(usize, HashableRow)>,\n        value_idx: usize,\n        group_key: String,\n        col_idx: usize,\n        value: Value,\n        weight: isize,\n        write_row: WriteRow,\n    },\n    Done,\n}\n\nimpl DistinctPersistState {\n    pub fn new(distinct_deltas: DistinctDeltas) -> Self {\n        let group_keys: Vec<String> = distinct_deltas.keys().cloned().collect();\n        Self::Init {\n            distinct_deltas,\n            group_keys,\n        }\n    }\n\n    pub fn persist_distinct_values(\n        &mut self,\n        operator_id: i64,\n        cursors: &mut DbspStateCursors,\n        generate_group_hash: impl Fn(&str) -> Hash128,\n    ) -> Result<IOResult<()>> {\n        loop {\n            match self {\n                DistinctPersistState::Init {\n                    distinct_deltas,\n                    group_keys,\n                } => {\n                    let distinct_deltas = std::mem::take(distinct_deltas);\n                    let group_keys = std::mem::take(group_keys);\n                    *self = DistinctPersistState::ProcessGroup {\n                        distinct_deltas,\n                        group_keys,\n                        group_idx: 0,\n                        value_keys: Vec::new(),\n                        value_idx: 0,\n                    };\n                }\n                DistinctPersistState::ProcessGroup {\n                    distinct_deltas,\n                    group_keys,\n                    group_idx,\n                    value_keys,\n                    value_idx,\n                } => {\n                    // Check if we're past all groups\n                    if *group_idx >= group_keys.len() {\n                        *self = DistinctPersistState::Done;\n                        continue;\n                    }\n\n                    // Check if we need to get value_keys for current group\n                    if value_keys.is_empty() && *group_idx < group_keys.len() {\n                        let group_key_str = &group_keys[*group_idx];\n                        if let Some(group_values) = distinct_deltas.get(group_key_str) {\n                            *value_keys = group_values.keys().cloned().collect();\n                        }\n                    }\n\n                    // Check if we have more values in current group\n                    if *value_idx >= value_keys.len() {\n                        *group_idx += 1;\n                        *value_idx = 0;\n                        value_keys.clear();\n                        continue;\n                    }\n\n                    // Process current value\n                    let group_key = group_keys[*group_idx].clone();\n                    let (col_idx, hashable_row) = value_keys[*value_idx].clone();\n                    let weight = distinct_deltas[&group_key][&(col_idx, hashable_row.clone())];\n                    // Extract the value from HashableRow (it's the first element in values vector)\n                    let value = hashable_row\n                        .values\n                        .first()\n                        .ok_or_else(|| {\n                            LimboError::InternalError(\n                                \"hashable_row should have at least one value\".to_string(),\n                            )\n                        })?\n                        .clone();\n\n                    let distinct_deltas = std::mem::take(distinct_deltas);\n                    let group_keys = std::mem::take(group_keys);\n                    let value_keys = std::mem::take(value_keys);\n                    *self = DistinctPersistState::WriteValue {\n                        distinct_deltas,\n                        group_keys,\n                        group_idx: *group_idx,\n                        value_keys,\n                        value_idx: *value_idx,\n                        group_key,\n                        col_idx,\n                        value,\n                        weight,\n                        write_row: WriteRow::new(),\n                    };\n                }\n                DistinctPersistState::WriteValue {\n                    distinct_deltas,\n                    group_keys,\n                    group_idx,\n                    value_keys,\n                    value_idx,\n                    group_key,\n                    col_idx,\n                    value,\n                    weight,\n                    write_row,\n                } => {\n                    // Build the key components for DISTINCT storage\n                    let storage_id = generate_storage_id(operator_id, *col_idx, AGG_TYPE_DISTINCT);\n                    let zset_hash = generate_group_hash(group_key);\n\n                    // For DISTINCT, element_id is a hash of the value\n                    let element_id = hash_value(value, *col_idx);\n\n                    // Create index key\n                    let index_key = vec![\n                        Value::from_i64(storage_id),\n                        zset_hash.to_value(),\n                        element_id.to_value(),\n                    ];\n\n                    // Record values (operator_id, zset_hash, element_id, weight_blob)\n                    // Store weight as a minimal AggregateState blob so ReadRecord can parse it\n                    let weight_state = AggregateState {\n                        count: *weight as i64,\n                        ..Default::default()\n                    };\n                    let weight_blob = weight_state.to_blob(&[], &[]);\n\n                    let record_values = vec![\n                        Value::from_i64(storage_id),\n                        zset_hash.to_value(),\n                        element_id.to_value(),\n                        Value::Blob(weight_blob),\n                    ];\n\n                    // Write to BTree\n                    return_if_io!(write_row.write_row(cursors, index_key, record_values, *weight));\n\n                    // Move to next value\n                    let distinct_deltas = std::mem::take(distinct_deltas);\n                    let group_keys = std::mem::take(group_keys);\n                    let value_keys = std::mem::take(value_keys);\n                    *self = DistinctPersistState::ProcessGroup {\n                        distinct_deltas,\n                        group_keys,\n                        group_idx: *group_idx,\n                        value_keys,\n                        value_idx: *value_idx + 1,\n                    };\n                }\n                DistinctPersistState::Done => {\n                    return Ok(IOResult::Done(()));\n                }\n            }\n        }\n    }\n}\n\nimpl MinMaxPersistState {\n    pub fn new(min_max_deltas: MinMaxDeltas) -> Self {\n        let group_keys: Vec<String> = min_max_deltas.keys().cloned().collect();\n        Self::Init {\n            min_max_deltas,\n            group_keys,\n        }\n    }\n\n    pub fn persist_min_max(\n        &mut self,\n        operator_id: i64,\n        column_min_max: &HashMap<usize, AggColumnInfo>,\n        cursors: &mut DbspStateCursors,\n        generate_group_hash: impl Fn(&str) -> Hash128,\n    ) -> Result<IOResult<()>> {\n        loop {\n            match self {\n                MinMaxPersistState::Init {\n                    min_max_deltas,\n                    group_keys,\n                } => {\n                    let min_max_deltas = std::mem::take(min_max_deltas);\n                    let group_keys = std::mem::take(group_keys);\n                    *self = MinMaxPersistState::ProcessGroup {\n                        min_max_deltas,\n                        group_keys,\n                        group_idx: 0,\n                        value_idx: 0,\n                    };\n                }\n                MinMaxPersistState::ProcessGroup {\n                    min_max_deltas,\n                    group_keys,\n                    group_idx,\n                    value_idx,\n                } => {\n                    // Check if we're past all groups\n                    if *group_idx >= group_keys.len() {\n                        *self = MinMaxPersistState::Done;\n                        continue;\n                    }\n\n                    let group_key_str = &group_keys[*group_idx];\n                    let values = &min_max_deltas[group_key_str]; // This should always exist\n\n                    // Convert HashMap to Vec for indexed access\n                    let values_vec: Vec<_> = values.iter().collect();\n\n                    // Check if we have more values in current group\n                    if *value_idx >= values_vec.len() {\n                        *group_idx += 1;\n                        *value_idx = 0;\n                        // Continue to check if we're past all groups now\n                        continue;\n                    }\n\n                    // Process current value and extract what we need before taking ownership\n                    let ((column_name, hashable_row), weight) = values_vec[*value_idx];\n                    let column_name = *column_name;\n                    let value = hashable_row.values[0].clone(); // Extract the Value from HashableRow\n                    let weight = *weight;\n\n                    let min_max_deltas = std::mem::take(min_max_deltas);\n                    let group_keys = std::mem::take(group_keys);\n                    *self = MinMaxPersistState::WriteValue {\n                        min_max_deltas,\n                        group_keys,\n                        group_idx: *group_idx,\n                        value_idx: *value_idx,\n                        column_name,\n                        value,\n                        weight,\n                        write_row: WriteRow::new(),\n                    };\n                }\n                MinMaxPersistState::WriteValue {\n                    min_max_deltas,\n                    group_keys,\n                    group_idx,\n                    value_idx,\n                    value,\n                    column_name,\n                    weight,\n                    write_row,\n                } => {\n                    // Should have exited in the previous state\n                    assert!(*group_idx < group_keys.len());\n\n                    let group_key_str = &group_keys[*group_idx];\n\n                    // Get the column info from the pre-computed map\n                    let column_info = column_min_max\n                        .get(column_name)\n                        .expect(\"Column should exist in column_min_max map\");\n                    let column_index = column_info.index;\n\n                    // Build the key components for MinMax storage using new encoding\n                    let storage_id =\n                        generate_storage_id(operator_id, column_index, AGG_TYPE_MINMAX);\n                    let zset_hash = generate_group_hash(group_key_str);\n\n                    // element_id is the actual value for Min/Max\n                    let element_id_val = value.clone();\n\n                    // Create index key\n                    let index_key = vec![\n                        Value::from_i64(storage_id),\n                        zset_hash.to_value(),\n                        element_id_val.clone(),\n                    ];\n\n                    // Record values (operator_id, zset_hash, element_id, unused_placeholder)\n                    // For MIN/MAX, the element_id IS the value, so we use NULL for the 4th column\n                    let record_values = vec![\n                        Value::from_i64(storage_id),\n                        zset_hash.to_value(),\n                        element_id_val.clone(),\n                        Value::Null, // Placeholder - not used for MIN/MAX\n                    ];\n\n                    return_if_io!(write_row.write_row(\n                        cursors,\n                        index_key.clone(),\n                        record_values,\n                        *weight\n                    ));\n\n                    // Move to next value\n                    let min_max_deltas = std::mem::take(min_max_deltas);\n                    let group_keys = std::mem::take(group_keys);\n                    *self = MinMaxPersistState::ProcessGroup {\n                        min_max_deltas,\n                        group_keys,\n                        group_idx: *group_idx,\n                        value_idx: *value_idx + 1,\n                    };\n                }\n                MinMaxPersistState::Done => {\n                    return Ok(IOResult::Done(()));\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "core/incremental/compiler.rs",
    "content": "//! DBSP Compiler: Converts Logical Plans to DBSP Circuits\n//!\n//! This module implements compilation from SQL logical plans to DBSP circuits.\n//! The initial version supports only filter and projection operators.\n//!\n//! Based on the DBSP paper: \"DBSP: Automatic Incremental View Maintenance for Rich Query Languages\"\n\nuse crate::incremental::aggregate_operator::AggregateOperator;\nuse crate::incremental::dbsp::{Delta, DeltaPair};\nuse crate::incremental::expr_compiler::CompiledExpression;\nuse crate::incremental::operator::{\n    create_dbsp_state_index, DbspStateCursors, EvalState, FilterOperator, FilterPredicate,\n    IncrementalOperator, InputOperator, JoinOperator, JoinType, ProjectOperator,\n};\nuse crate::schema::Type;\nuse crate::storage::btree::{BTreeCursor, BTreeKey, CursorTrait};\n// Note: logical module must be made pub(crate) in translate/mod.rs\nuse crate::numeric::Numeric;\nuse crate::sync::{atomic::Ordering, Arc};\nuse crate::translate::logical::{\n    BinaryOperator, Column, ColumnInfo, JoinType as LogicalJoinType, LogicalExpr, LogicalPlan,\n    LogicalSchema, SchemaRef,\n};\nuse crate::types::{IOResult, ImmutableRecord, SeekKey, SeekOp, SeekResult, Value};\nuse crate::Pager;\nuse crate::{return_and_restore_if_io, return_if_io, LimboError, Result};\nuse rustc_hash::FxHashMap as HashMap;\nuse std::fmt::{self, Display, Formatter};\n\n// The state table has 5 columns: operator_id, zset_id, element_id, value, weight\nconst OPERATOR_COLUMNS: usize = 5;\n\n/// State machine for writing rows to simple materialized views (table-only, no index)\n#[derive(Debug, Default)]\npub enum WriteRowView {\n    #[default]\n    GetRecord,\n    Delete,\n    Insert {\n        final_weight: isize,\n    },\n    Done,\n}\n\nimpl WriteRowView {\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    /// Write a row with weight management for table-only storage.\n    ///\n    /// # Arguments\n    /// * `cursor` - BTree cursor for the storage\n    /// * `key` - The key to seek (TableRowId)\n    /// * `build_record` - Function that builds the record values to insert.\n    ///   Takes the final_weight and returns the complete record values.\n    /// * `weight` - The weight delta to apply\n    pub fn write_row(\n        &mut self,\n        cursor: &mut BTreeCursor,\n        key: SeekKey,\n        build_record: impl Fn(isize) -> Vec<Value>,\n        weight: isize,\n    ) -> Result<IOResult<()>> {\n        loop {\n            match self {\n                WriteRowView::GetRecord => {\n                    let res = return_if_io!(cursor.seek(key.clone(), SeekOp::GE { eq_only: true }));\n                    if !matches!(res, SeekResult::Found) {\n                        *self = WriteRowView::Insert {\n                            final_weight: weight,\n                        };\n                    } else {\n                        let existing_record = return_if_io!(cursor.record());\n                        let r = existing_record.ok_or_else(|| {\n                            LimboError::InternalError(format!(\n                                \"Found key {key:?} in storage but could not read record\"\n                            ))\n                        })?;\n                        let last = r.iter()?.last();\n\n                        // Weight is always the last value\n                        let existing_weight = match last {\n                            Some(val) => match val?.to_owned() {\n                                Value::Numeric(Numeric::Integer(w)) => w as isize,\n                                _ => {\n                                    return Err(LimboError::InternalError(format!(\n                                        \"Invalid weight value in storage for key {key:?}\"\n                                    )))\n                                }\n                            },\n                            None => {\n                                return Err(LimboError::InternalError(format!(\n                                    \"No weight value found in storage for key {key:?}\"\n                                )))\n                            }\n                        };\n\n                        let final_weight = existing_weight + weight;\n                        if final_weight <= 0 {\n                            *self = WriteRowView::Delete\n                        } else {\n                            *self = WriteRowView::Insert { final_weight }\n                        }\n                    }\n                }\n                WriteRowView::Delete => {\n                    // Mark as Done before delete to avoid retry on I/O\n                    *self = WriteRowView::Done;\n                    return_if_io!(cursor.delete());\n                }\n                WriteRowView::Insert { final_weight } => {\n                    return_if_io!(cursor.seek(key.clone(), SeekOp::GE { eq_only: true }));\n\n                    // Extract the row ID from the key\n                    let key_i64 = match key {\n                        SeekKey::TableRowId(id) => id,\n                        _ => {\n                            return Err(LimboError::InternalError(\n                                \"Expected TableRowId for storage\".to_string(),\n                            ))\n                        }\n                    };\n\n                    // Build the record values using the provided function\n                    let record_values = build_record(*final_weight);\n\n                    // Create an ImmutableRecord from the values\n                    let immutable_record =\n                        ImmutableRecord::from_values(&record_values, record_values.len());\n                    let btree_key = BTreeKey::new_table_rowid(key_i64, Some(&immutable_record));\n\n                    // Mark as Done before insert to avoid retry on I/O\n                    *self = WriteRowView::Done;\n                    return_if_io!(cursor.insert(&btree_key));\n                }\n                WriteRowView::Done => {\n                    return Ok(IOResult::Done(()));\n                }\n            }\n        }\n    }\n}\n\n/// State machine for commit operations\npub enum CommitState {\n    /// Initial state - ready to start commit\n    Init,\n\n    /// Running circuit with commit_operators flag set to true\n    CommitOperators {\n        /// Execute state for running the circuit\n        execute_state: Box<ExecuteState>,\n        /// Persistent cursors for operator state (table and index)\n        state_cursors: Box<DbspStateCursors>,\n    },\n\n    /// Updating the materialized view with the delta\n    UpdateView {\n        /// Delta to write to the view\n        delta: Delta,\n        /// Current index in delta.changes being processed\n        current_index: usize,\n        /// State for writing individual rows\n        write_row_state: WriteRowView,\n        /// Cursor for view data btree - created fresh for each row\n        view_cursor: Box<BTreeCursor>,\n    },\n}\n\nimpl std::fmt::Debug for CommitState {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Self::Init => write!(f, \"Init\"),\n            Self::CommitOperators { execute_state, .. } => f\n                .debug_struct(\"CommitOperators\")\n                .field(\"execute_state\", execute_state)\n                .field(\"has_state_table_cursor\", &true)\n                .field(\"has_state_index_cursor\", &true)\n                .finish(),\n            Self::UpdateView {\n                delta,\n                current_index,\n                write_row_state,\n                ..\n            } => f\n                .debug_struct(\"UpdateView\")\n                .field(\"delta\", delta)\n                .field(\"current_index\", current_index)\n                .field(\"write_row_state\", write_row_state)\n                .field(\"has_view_cursor\", &true)\n                .finish(),\n        }\n    }\n}\n\n/// State machine for circuit execution across I/O operations\n/// Similar to EvalState but for tracking execution state through the circuit\n#[derive(Debug)]\npub enum ExecuteState {\n    /// Empty state so we can allocate the space without executing\n    Uninitialized,\n\n    /// Initial state - starting circuit execution\n    Init {\n        /// Input deltas to process\n        input_data: DeltaSet,\n    },\n\n    /// Processing multiple inputs (for recursive node processing)\n    ProcessingInputs {\n        /// Collection of (node_id, state) pairs to process\n        input_states: Vec<(i64, ExecuteState)>,\n        /// Current index being processed\n        current_index: usize,\n        /// Collected deltas from processed inputs\n        input_deltas: Vec<Delta>,\n    },\n\n    /// Processing a specific node in the circuit\n    ProcessingNode {\n        /// Node's evaluation state (includes the delta in its Init state)\n        eval_state: Box<EvalState>,\n    },\n}\n\n/// A set of deltas for multiple tables/operators\n/// This provides a cleaner API for passing deltas through circuit execution\n#[derive(Debug, Clone, Default)]\npub struct DeltaSet {\n    /// Deltas keyed by table/operator name\n    deltas: HashMap<String, Delta>,\n}\n\nimpl DeltaSet {\n    /// Create a new empty delta set\n    pub fn new() -> Self {\n        Self {\n            deltas: HashMap::default(),\n        }\n    }\n\n    /// Create an empty delta set (more semantic for \"no changes\")\n    pub fn empty() -> Self {\n        Self {\n            deltas: HashMap::default(),\n        }\n    }\n\n    /// Create a DeltaSet from a HashMap\n    pub fn from_map(deltas: HashMap<String, Delta>) -> Self {\n        Self { deltas }\n    }\n\n    /// Add a delta for a table\n    pub fn insert(&mut self, table_name: String, delta: Delta) {\n        self.deltas.insert(table_name, delta);\n    }\n\n    /// Get delta for a table, returns empty delta if not found\n    pub fn get(&self, table_name: &str) -> Delta {\n        self.deltas\n            .get(table_name)\n            .cloned()\n            .unwrap_or_else(Delta::new)\n    }\n\n    /// Convert DeltaSet into the underlying HashMap\n    pub fn into_map(self) -> HashMap<String, Delta> {\n        self.deltas\n    }\n\n    /// Check if all deltas in the set are empty\n    pub fn is_empty(&self) -> bool {\n        self.deltas.values().all(|d| d.is_empty())\n    }\n}\n\n/// Represents a DBSP operator in the compiled circuit\n#[derive(Debug, Clone, PartialEq)]\npub enum DbspOperator {\n    /// Filter operator (σ) - filters records based on a predicate\n    Filter { predicate: DbspExpr },\n    /// Projection operator (π) - projects specific columns\n    Projection {\n        exprs: Vec<DbspExpr>,\n        schema: SchemaRef,\n    },\n    /// Aggregate operator (γ) - performs grouping and aggregation\n    Aggregate {\n        group_exprs: Vec<DbspExpr>,\n        aggr_exprs: Vec<crate::incremental::operator::AggregateFunction>,\n        schema: SchemaRef,\n    },\n    /// Join operator (⋈) - joins two relations\n    Join {\n        join_type: JoinType,\n        on_exprs: Vec<(DbspExpr, DbspExpr)>,\n        schema: SchemaRef,\n    },\n    /// Input operator - source of data\n    Input { name: String, schema: SchemaRef },\n    /// Merge operator for combining streams (used in recursive CTEs and UNION)\n    Merge { schema: SchemaRef },\n    /// Distinct operator - removes duplicates\n    Distinct { schema: SchemaRef },\n}\n\n/// Represents an expression in DBSP\n#[derive(Debug, Clone, PartialEq)]\npub enum DbspExpr {\n    /// Column reference\n    Column(String),\n    /// Literal value\n    Literal(Value),\n    /// Binary expression\n    BinaryExpr {\n        left: Box<DbspExpr>,\n        op: BinaryOperator,\n        right: Box<DbspExpr>,\n    },\n}\n\n/// A node in the DBSP circuit DAG\npub struct DbspNode {\n    /// Unique identifier for this node\n    pub id: i64,\n    /// The operator metadata\n    pub operator: DbspOperator,\n    /// Input nodes (edges in the DAG)\n    pub inputs: Vec<i64>,\n    /// The actual executable operator\n    pub executable: Box<dyn IncrementalOperator>,\n}\n\n// SAFETY: This needs to be audited for thread safety.\n// See: https://github.com/tursodatabase/turso/issues/1552\nunsafe impl Send for DbspNode {}\nunsafe impl Sync for DbspNode {}\ncrate::assert::assert_send_sync!(DbspNode);\n\nimpl std::fmt::Debug for DbspNode {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"DbspNode\")\n            .field(\"id\", &self.id)\n            .field(\"operator\", &self.operator)\n            .field(\"inputs\", &self.inputs)\n            .field(\"has_executable\", &true)\n            .finish()\n    }\n}\n\nimpl DbspNode {\n    fn process_node(\n        &mut self,\n        eval_state: &mut EvalState,\n        commit_operators: bool,\n        cursors: &mut DbspStateCursors,\n    ) -> Result<IOResult<Delta>> {\n        // Process delta using the executable operator\n        let op = &mut self.executable;\n\n        let state = if commit_operators {\n            // Clone the deltas from eval_state - don't extract them\n            // in case we need to re-execute due to I/O\n            let deltas = match eval_state {\n                EvalState::Init { deltas } => deltas.clone(),\n                _ => panic!(\"commit can only be called when eval_state is in Init state\"),\n            };\n            let result = return_if_io!(op.commit(deltas, cursors));\n            // After successful commit, move state to Done\n            *eval_state = EvalState::Done;\n            result\n        } else {\n            return_if_io!(op.eval(eval_state, cursors))\n        };\n        Ok(IOResult::Done(state))\n    }\n}\n\n/// Version number for the DBSP circuit format\n/// This should be incremented when the circuit structure changes\npub const DBSP_CIRCUIT_VERSION: u32 = 1;\n\n/// Represents a complete DBSP circuit (DAG of operators)\n#[derive(Debug)]\npub struct DbspCircuit {\n    /// All nodes in the circuit, indexed by their ID\n    pub(super) nodes: HashMap<i64, DbspNode>,\n    /// Counter for generating unique node IDs\n    next_id: i64,\n    /// Root node ID (the final output)\n    pub(super) root: Option<i64>,\n    /// Output schema of the circuit (schema of the root node)\n    pub(super) output_schema: SchemaRef,\n\n    /// State machine for commit operation\n    commit_state: CommitState,\n\n    /// Root page for the main materialized view data\n    pub(super) main_data_root: i64,\n    /// Root page for internal DBSP state table\n    pub(super) internal_state_root: i64,\n    /// Root page for the DBSP state table's primary key index\n    pub(super) internal_state_index_root: i64,\n}\n\n// SAFETY: This needs to be audited for thread safety.\n// See: https://github.com/tursodatabase/turso/issues/1552\nunsafe impl Send for DbspCircuit {}\nunsafe impl Sync for DbspCircuit {}\ncrate::assert::assert_send_sync!(DbspCircuit);\n\nimpl DbspCircuit {\n    /// Create a new empty circuit with initial empty schema\n    /// The actual output schema will be set when the root node is established\n    pub fn new(\n        main_data_root: i64,\n        internal_state_root: i64,\n        internal_state_index_root: i64,\n    ) -> Self {\n        // Start with an empty schema - will be updated when root is set\n        let empty_schema = Arc::new(LogicalSchema::new(vec![]));\n        Self {\n            nodes: HashMap::default(),\n            next_id: 1, // Start from 1 to reserve 0 for metadata\n            root: None,\n            output_schema: empty_schema,\n            commit_state: CommitState::Init,\n            main_data_root,\n            internal_state_root,\n            internal_state_index_root,\n        }\n    }\n\n    /// Set the root node and update the output schema\n    fn set_root(&mut self, root_id: i64, schema: SchemaRef) {\n        self.root = Some(root_id);\n        self.output_schema = schema;\n    }\n\n    /// Get the current materialized state by reading from btree\n    /// Add a node to the circuit\n    fn add_node(\n        &mut self,\n        operator: DbspOperator,\n        inputs: Vec<i64>,\n        executable: Box<dyn IncrementalOperator>,\n    ) -> i64 {\n        let id = self.next_id;\n        self.next_id += 1;\n\n        let node = DbspNode {\n            id,\n            operator,\n            inputs,\n            executable,\n        };\n\n        self.nodes.insert(id, node);\n        id\n    }\n\n    pub fn run_circuit(\n        &mut self,\n        execute_state: &mut ExecuteState,\n        pager: &Arc<Pager>,\n        state_cursors: &mut DbspStateCursors,\n        commit_operators: bool,\n    ) -> Result<IOResult<Delta>> {\n        if let Some(root_id) = self.root {\n            self.execute_node(\n                root_id,\n                pager.clone(),\n                execute_state,\n                commit_operators,\n                state_cursors,\n            )\n        } else {\n            Err(LimboError::ParseError(\n                \"Circuit has no root node\".to_string(),\n            ))\n        }\n    }\n\n    /// Execute the circuit with incremental input data (deltas).\n    ///\n    /// # Arguments\n    /// * `pager` - Pager for btree access\n    /// * `context` - Execution context for tracking operator states\n    /// * `execute_state` - State machine containing input deltas and tracking execution progress\n    pub fn execute(\n        &mut self,\n        pager: Arc<Pager>,\n        execute_state: &mut ExecuteState,\n    ) -> Result<IOResult<Delta>> {\n        if let Some(root_id) = self.root {\n            // Create temporary cursors for execute (non-commit) operations\n            let table_cursor =\n                BTreeCursor::new_table(pager.clone(), self.internal_state_root, OPERATOR_COLUMNS);\n            let index_def = create_dbsp_state_index(self.internal_state_index_root);\n            let index_cursor = BTreeCursor::new_index(\n                pager.clone(),\n                self.internal_state_index_root,\n                &index_def,\n                3,\n            );\n            let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n            self.execute_node(root_id, pager, execute_state, false, &mut cursors)\n        } else {\n            Err(LimboError::ParseError(\n                \"Circuit has no root node\".to_string(),\n            ))\n        }\n    }\n\n    /// Commit deltas to the circuit, updating internal operator state and persisting to btree.\n    /// This should be called after execute() when you want to make changes permanent.\n    ///\n    /// # Arguments\n    /// * `input_data` - The deltas to commit (same as what was passed to execute)\n    /// * `pager` - Pager for creating cursors to the btrees\n    pub fn commit(\n        &mut self,\n        input_data: HashMap<String, Delta>,\n        pager: Arc<Pager>,\n    ) -> Result<IOResult<Delta>> {\n        // No root means nothing to commit\n        if self.root.is_none() {\n            return Ok(IOResult::Done(Delta::new()));\n        }\n\n        // Get btree root pages\n        let main_data_root = self.main_data_root;\n\n        // Add 1 for the weight column that we store in the btree\n        let num_columns = self.output_schema.columns.len() + 1;\n\n        // Convert input_data to DeltaSet once, outside the loop\n        let input_delta_set = DeltaSet::from_map(input_data);\n\n        loop {\n            // Take ownership of the state for processing, to avoid borrow checker issues (we have\n            // to call run_circuit, which takes &mut self. Because of that, cannot use\n            // return_if_io. We have to use the version that restores the state before returning.\n            let mut state = std::mem::replace(&mut self.commit_state, CommitState::Init);\n            match &mut state {\n                CommitState::Init => {\n                    // Create state cursors when entering CommitOperators state\n                    let state_table_cursor = BTreeCursor::new_table(\n                        pager.clone(),\n                        self.internal_state_root,\n                        OPERATOR_COLUMNS,\n                    );\n                    let index_def = create_dbsp_state_index(self.internal_state_index_root);\n                    let state_index_cursor = BTreeCursor::new_index(\n                        pager.clone(),\n                        self.internal_state_index_root,\n                        &index_def,\n                        3, // Index on first 3 columns\n                    );\n\n                    let state_cursors = Box::new(DbspStateCursors::new(\n                        state_table_cursor,\n                        state_index_cursor,\n                    ));\n\n                    self.commit_state = CommitState::CommitOperators {\n                        execute_state: Box::new(ExecuteState::Init {\n                            input_data: input_delta_set.clone(),\n                        }),\n                        state_cursors,\n                    };\n                }\n                CommitState::CommitOperators {\n                    ref mut execute_state,\n                    ref mut state_cursors,\n                } => {\n                    let delta = return_and_restore_if_io!(\n                        &mut self.commit_state,\n                        state,\n                        self.run_circuit(execute_state, &pager, state_cursors, true,)\n                    );\n\n                    // Create view cursor when entering UpdateView state\n                    let view_cursor = Box::new(BTreeCursor::new_table(\n                        pager.clone(),\n                        main_data_root,\n                        num_columns,\n                    ));\n\n                    self.commit_state = CommitState::UpdateView {\n                        delta,\n                        current_index: 0,\n                        write_row_state: WriteRowView::new(),\n                        view_cursor,\n                    };\n                }\n                CommitState::UpdateView {\n                    delta,\n                    current_index,\n                    write_row_state,\n                    view_cursor,\n                } => {\n                    if *current_index >= delta.changes.len() {\n                        self.commit_state = CommitState::Init;\n                        let delta = std::mem::take(delta);\n                        return Ok(IOResult::Done(delta));\n                    } else {\n                        let (row, weight) = delta.changes[*current_index].clone();\n\n                        // If we're starting a new row (GetRecord state), we need a fresh cursor\n                        // due to btree cursor state machine limitations\n                        if matches!(write_row_state, WriteRowView::GetRecord) {\n                            *view_cursor = Box::new(BTreeCursor::new_table(\n                                pager.clone(),\n                                main_data_root,\n                                num_columns,\n                            ));\n                        }\n\n                        // Build the view row format: row values + weight\n                        let key = SeekKey::TableRowId(row.rowid);\n                        let row_values = row.values.clone();\n                        let build_fn = move |final_weight: isize| -> Vec<Value> {\n                            let mut values = row_values.clone();\n                            values.push(Value::from_i64(final_weight as i64));\n                            values\n                        };\n\n                        return_and_restore_if_io!(\n                            &mut self.commit_state,\n                            state,\n                            write_row_state.write_row(view_cursor, key, build_fn, weight)\n                        );\n\n                        // Move to next row\n                        let delta = std::mem::take(delta);\n                        // Take ownership of view_cursor - we'll create a new one for next row if needed\n                        let view_cursor = std::mem::replace(\n                            view_cursor,\n                            Box::new(BTreeCursor::new_table(\n                                pager.clone(),\n                                main_data_root,\n                                num_columns,\n                            )),\n                        );\n\n                        self.commit_state = CommitState::UpdateView {\n                            delta,\n                            current_index: *current_index + 1,\n                            write_row_state: WriteRowView::new(),\n                            view_cursor,\n                        };\n                    }\n                }\n            }\n        }\n    }\n\n    /// Execute a specific node in the circuit\n    fn execute_node(\n        &mut self,\n        node_id: i64,\n        pager: Arc<Pager>,\n        execute_state: &mut ExecuteState,\n        commit_operators: bool,\n        cursors: &mut DbspStateCursors,\n    ) -> Result<IOResult<Delta>> {\n        loop {\n            match execute_state {\n                ExecuteState::Uninitialized => {\n                    panic!(\"Trying to execute an uninitialized ExecuteState state machine\");\n                }\n                ExecuteState::Init { input_data } => {\n                    let node = self\n                        .nodes\n                        .get(&node_id)\n                        .ok_or_else(|| LimboError::ParseError(\"Node not found\".to_string()))?;\n\n                    // Check if this is an Input node\n                    match &node.operator {\n                        DbspOperator::Input { name, .. } => {\n                            // Input nodes get their delta directly from input_data\n                            let delta = input_data.get(name);\n                            *execute_state = ExecuteState::ProcessingNode {\n                                eval_state: Box::new(EvalState::Init {\n                                    deltas: delta.into(),\n                                }),\n                            };\n                        }\n                        _ => {\n                            // Non-input nodes need to process their inputs\n                            let input_data = std::mem::take(input_data);\n                            let input_node_ids = node.inputs.clone();\n\n                            let input_states: Vec<(i64, ExecuteState)> = input_node_ids\n                                .iter()\n                                .map(|&input_id| {\n                                    (\n                                        input_id,\n                                        ExecuteState::Init {\n                                            input_data: input_data.clone(),\n                                        },\n                                    )\n                                })\n                                .collect();\n\n                            *execute_state = ExecuteState::ProcessingInputs {\n                                input_states,\n                                current_index: 0,\n                                input_deltas: Vec::new(),\n                            };\n                        }\n                    }\n                }\n                ExecuteState::ProcessingInputs {\n                    input_states,\n                    current_index,\n                    input_deltas,\n                } => {\n                    if *current_index >= input_states.len() {\n                        // All inputs processed\n                        let left_delta = input_deltas.first().cloned().unwrap_or_else(Delta::new);\n                        let right_delta = input_deltas.get(1).cloned().unwrap_or_else(Delta::new);\n\n                        *execute_state = ExecuteState::ProcessingNode {\n                            eval_state: Box::new(EvalState::Init {\n                                deltas: DeltaPair::new(left_delta, right_delta),\n                            }),\n                        };\n                    } else {\n                        // Get the (node_id, state) pair for the current index\n                        let (input_node_id, input_state) = &mut input_states[*current_index];\n\n                        // Create temporary cursors for the recursive call\n                        let temp_table_cursor = BTreeCursor::new_table(\n                            pager.clone(),\n                            self.internal_state_root,\n                            OPERATOR_COLUMNS,\n                        );\n                        let index_def = create_dbsp_state_index(self.internal_state_index_root);\n                        let temp_index_cursor = BTreeCursor::new_index(\n                            pager.clone(),\n                            self.internal_state_index_root,\n                            &index_def,\n                            3,\n                        );\n                        let mut temp_cursors =\n                            DbspStateCursors::new(temp_table_cursor, temp_index_cursor);\n\n                        let delta = return_if_io!(self.execute_node(\n                            *input_node_id,\n                            pager.clone(),\n                            input_state,\n                            commit_operators,\n                            &mut temp_cursors\n                        ));\n                        input_deltas.push(delta);\n                        *current_index += 1;\n                    }\n                }\n                ExecuteState::ProcessingNode { eval_state } => {\n                    // Get mutable reference to node for eval\n                    let node = self\n                        .nodes\n                        .get_mut(&node_id)\n                        .ok_or_else(|| LimboError::ParseError(\"Node not found\".to_string()))?;\n\n                    let output_delta =\n                        return_if_io!(node.process_node(eval_state, commit_operators, cursors));\n                    return Ok(IOResult::Done(output_delta));\n                }\n            }\n        }\n    }\n}\n\nimpl Display for DbspCircuit {\n    fn fmt(&self, f: &mut Formatter) -> fmt::Result {\n        writeln!(f, \"DBSP Circuit:\")?;\n        if let Some(root_id) = self.root {\n            self.fmt_node(f, root_id, 0)?;\n        }\n        Ok(())\n    }\n}\n\nimpl DbspCircuit {\n    fn fmt_node(&self, f: &mut Formatter, node_id: i64, depth: usize) -> fmt::Result {\n        let indent = \"  \".repeat(depth);\n        if let Some(node) = self.nodes.get(&node_id) {\n            match &node.operator {\n                DbspOperator::Filter { predicate } => {\n                    writeln!(f, \"{indent}Filter[{node_id}]: {predicate:?}\")?;\n                }\n                DbspOperator::Projection { exprs, .. } => {\n                    writeln!(f, \"{indent}Projection[{node_id}]: {exprs:?}\")?;\n                }\n                DbspOperator::Aggregate {\n                    group_exprs,\n                    aggr_exprs,\n                    ..\n                } => {\n                    writeln!(\n                        f,\n                        \"{indent}Aggregate[{node_id}]: GROUP BY {group_exprs:?}, AGGR {aggr_exprs:?}\"\n                    )?;\n                }\n                DbspOperator::Join {\n                    join_type,\n                    on_exprs,\n                    ..\n                } => {\n                    writeln!(f, \"{indent}Join[{node_id}]: {join_type:?} ON {on_exprs:?}\")?;\n                }\n                DbspOperator::Input { name, .. } => {\n                    writeln!(f, \"{indent}Input[{node_id}]: {name}\")?;\n                }\n                DbspOperator::Merge { schema } => {\n                    writeln!(\n                        f,\n                        \"{indent}Merge[{node_id}]: UNION/Recursive (schema: {} columns)\",\n                        schema.columns.len()\n                    )?;\n                }\n                DbspOperator::Distinct { schema } => {\n                    writeln!(\n                        f,\n                        \"{indent}Distinct[{node_id}]: (schema: {} columns)\",\n                        schema.columns.len()\n                    )?;\n                }\n            }\n\n            for input_id in &node.inputs {\n                self.fmt_node(f, *input_id, depth + 1)?;\n            }\n        }\n        Ok(())\n    }\n}\n\n/// Compiler from LogicalPlan to DBSP Circuit\npub struct DbspCompiler {\n    circuit: DbspCircuit,\n}\n\nimpl DbspCompiler {\n    /// Create a new DBSP compiler\n    pub fn new(\n        main_data_root: i64,\n        internal_state_root: i64,\n        internal_state_index_root: i64,\n    ) -> Self {\n        Self {\n            circuit: DbspCircuit::new(\n                main_data_root,\n                internal_state_root,\n                internal_state_index_root,\n            ),\n        }\n    }\n\n    /// Resolve join condition columns to determine which side each column belongs to.\n    ///\n    /// Returns (left_column, left_index, right_column, right_index) where:\n    /// - left_column/right_column are the Column references\n    /// - left_index/right_index are the column indices in their respective schemas\n    ///\n    /// Handles cases where:\n    /// - Columns are in normal order (left table column = right table column)\n    /// - Columns are swapped (right table column = left table column)\n    /// - One or both columns have table qualifiers\n    /// - Column names exist in both tables but are disambiguated by qualifiers\n    fn resolve_join_columns(\n        first_col: &Column,\n        second_col: &Column,\n        left_schema: &LogicalSchema,\n        right_schema: &LogicalSchema,\n    ) -> Result<(Column, usize, Column, usize)> {\n        // Check all four possibilities to handle ambiguous column names\n        let first_in_left = left_schema.find_column(&first_col.name, first_col.table.as_deref());\n        let first_in_right = right_schema.find_column(&first_col.name, first_col.table.as_deref());\n        let second_in_left = left_schema.find_column(&second_col.name, second_col.table.as_deref());\n        let second_in_right =\n            right_schema.find_column(&second_col.name, second_col.table.as_deref());\n\n        // Determine the correct pairing: one column must be from left, one from right\n        if first_in_left.is_some() && second_in_right.is_some() {\n            // first is from left, second is from right\n            let (left_idx, _) = first_in_left.ok_or_else(|| {\n                LimboError::InternalError(\"first_in_left should exist\".to_string())\n            })?;\n            let (right_idx, _) = second_in_right.ok_or_else(|| {\n                LimboError::InternalError(\"second_in_right should exist\".to_string())\n            })?;\n            Ok((first_col.clone(), left_idx, second_col.clone(), right_idx))\n        } else if first_in_right.is_some() && second_in_left.is_some() {\n            // first is from right, second is from left\n            let (left_idx, _) = second_in_left.ok_or_else(|| {\n                LimboError::InternalError(\"second_in_left should exist\".to_string())\n            })?;\n            let (right_idx, _) = first_in_right.ok_or_else(|| {\n                LimboError::InternalError(\"first_in_right should exist\".to_string())\n            })?;\n            Ok((second_col.clone(), left_idx, first_col.clone(), right_idx))\n        } else {\n            // Provide specific error messages for different failure cases\n            if first_in_left.is_none() && first_in_right.is_none() {\n                Err(LimboError::ParseError(format!(\n                    \"Join condition column '{}' not found in either input\",\n                    first_col.name\n                )))\n            } else if second_in_left.is_none() && second_in_right.is_none() {\n                Err(LimboError::ParseError(format!(\n                    \"Join condition column '{}' not found in either input\",\n                    second_col.name\n                )))\n            } else {\n                Err(LimboError::ParseError(format!(\n                    \"Join condition columns '{}' and '{}' must come from different input tables\",\n                    first_col.name, second_col.name\n                )))\n            }\n        }\n    }\n\n    /// Compile a logical plan to a DBSP circuit\n    pub fn compile(mut self, plan: &LogicalPlan) -> Result<DbspCircuit> {\n        let root_id = self.compile_plan(plan)?;\n        let output_schema = plan.schema().clone();\n        self.circuit.set_root(root_id, output_schema);\n        Ok(self.circuit)\n    }\n\n    /// Recursively compile a logical plan node\n    fn compile_plan(&mut self, plan: &LogicalPlan) -> Result<i64> {\n        match plan {\n            LogicalPlan::Projection(proj) => {\n                // Compile the input first\n                let input_id = self.compile_plan(&proj.input)?;\n\n                // Get input column names for the ProjectOperator\n                let input_schema = proj.input.schema();\n                let input_column_names: Vec<String> = input_schema.columns.iter()\n                    .map(|col| col.name.clone())\n                    .collect();\n\n                // Convert logical expressions to DBSP expressions\n                let dbsp_exprs = proj.exprs.iter()\n                    .map(Self::compile_expr)\n                    .collect::<Result<Vec<_>>>()?;\n\n                // Compile logical expressions to CompiledExpressions\n                let mut compiled_exprs = Vec::new();\n                let mut aliases = Vec::new();\n                for expr in &proj.exprs {\n                    let (compiled, alias) = Self::compile_expression(expr, input_schema)?;\n                    compiled_exprs.push(compiled);\n                    aliases.push(alias);\n                }\n\n                // Get output column names from the projection schema\n                let output_column_names: Vec<String> = proj.schema.columns.iter()\n                    .map(|col| col.name.clone())\n                    .collect();\n\n                // Create the ProjectOperator\n                let executable: Box<dyn IncrementalOperator> =\n                    Box::new(ProjectOperator::from_compiled(compiled_exprs, aliases, input_column_names, output_column_names)?);\n\n                // Create projection node\n                let node_id = self.circuit.add_node(\n                    DbspOperator::Projection {\n                        exprs: dbsp_exprs,\n                        schema: proj.schema.clone(),\n                    },\n                    vec![input_id],\n                    executable,\n                );\n                Ok(node_id)\n            }\n            LogicalPlan::Filter(filter) => {\n                // Compile the input first\n                let input_id = self.compile_plan(&filter.input)?;\n\n                // Get input schema for column resolution\n                let input_schema = filter.input.schema();\n\n                // Check if the predicate contains expressions that need to be computed\n                if Self::predicate_needs_projection(&filter.predicate) {\n                    // Complex expression in WHERE clause - need to add projection first\n                    // 1. Create projection that adds the computed expression as a new column\n\n                    // First, get all existing columns\n                    let mut projection_exprs = Vec::new();\n                    let mut dbsp_exprs = Vec::new();\n\n                    for col in &input_schema.columns {\n                        projection_exprs.push(LogicalExpr::Column(Column {\n                            name: col.name.clone(),\n                            table: None,\n                        }));\n                        dbsp_exprs.push(DbspExpr::Column(col.name.clone()));\n                    }\n\n                    // Now add the expression as a computed column\n                    let temp_column_name = \"__temp_filter_expr\";\n                    let computed_expr = Self::extract_expression_from_predicate(&filter.predicate)?;\n                    projection_exprs.push(computed_expr);\n\n                    // Compile the projection expressions\n                    let mut compiled_exprs = Vec::new();\n                    let mut aliases = Vec::new();\n                    let mut output_names = Vec::new();\n                    for (i, expr) in projection_exprs.iter().enumerate() {\n                        let (compiled, _alias) = Self::compile_expression(expr, input_schema)?;\n                        compiled_exprs.push(compiled);\n                        if i < input_schema.columns.len() {\n                            aliases.push(None);\n                            output_names.push(input_schema.columns[i].name.clone());\n                        } else {\n                            aliases.push(Some(temp_column_name.to_string()));\n                            output_names.push(temp_column_name.to_string());\n                        }\n                    }\n\n                    // Get input column names for ProjectOperator\n                    let input_column_names: Vec<String> = input_schema.columns.iter()\n                        .map(|col| col.name.clone())\n                        .collect();\n\n                    // Create projection operator\n                    let proj_executable: Box<dyn IncrementalOperator> =\n                        Box::new(ProjectOperator::from_compiled(\n                            compiled_exprs.clone(),\n                            aliases.clone(),\n                            input_column_names,\n                            output_names.clone()\n                        )?);\n\n                    // Create updated schema for the projection output\n                    let mut proj_schema_columns = input_schema.columns.clone();\n                    proj_schema_columns.push(ColumnInfo {\n                        name: temp_column_name.to_string(),\n                        table: None,\n                        database: None,\n                        table_alias: None,\n                        ty: Type::Integer,  // Computed expressions default to Integer\n                    });\n                    let proj_schema = SchemaRef::new(LogicalSchema {\n                        columns: proj_schema_columns,\n                    });\n\n                    // Add projection node\n                    let proj_id = self.circuit.add_node(\n                        DbspOperator::Projection {\n                            exprs: dbsp_exprs.clone(),\n                            schema: proj_schema.clone(),\n                        },\n                        vec![input_id],\n                        proj_executable,\n                    );\n\n                    // Now create a filter that replaces the complex expression with the temp column\n                    // but keeps all other conditions intact\n                    let replaced_predicate = Self::replace_complex_with_temp(&filter.predicate, temp_column_name)?;\n                    let filter_predicate = Self::compile_filter_predicate(&replaced_predicate, &proj_schema)?;\n\n                    let filter_executable: Box<dyn IncrementalOperator> =\n                        Box::new(FilterOperator::new(filter_predicate));\n\n                    // Create filter node\n                    let filter_id = self.circuit.add_node(\n                        DbspOperator::Filter { predicate: Self::compile_expr(&replaced_predicate)? },\n                        vec![proj_id],\n                        filter_executable,\n                    );\n\n                    // Finally, project again to remove the temporary column\n                    let mut final_exprs = Vec::new();\n                    let mut final_aliases = Vec::new();\n                    let mut final_names = Vec::new();\n                    let mut final_dbsp_exprs = Vec::new();\n\n                    for (i, column) in input_schema.columns.iter().enumerate() {\n                        let col_name = &column.name;\n                        final_exprs.push(compiled_exprs[i].clone());\n                        final_aliases.push(None);\n                        final_names.push(col_name.clone());\n                        final_dbsp_exprs.push(DbspExpr::Column(col_name.clone()));\n                    }\n\n                    // Input names for the final projection include the temp column\n                    let filter_output_names = output_names.clone();\n\n                    let final_proj_executable: Box<dyn IncrementalOperator> =\n                        Box::new(ProjectOperator::from_compiled(\n                            final_exprs,\n                            final_aliases,\n                            filter_output_names,\n                            final_names.clone()\n                        )?);\n\n                    let final_id = self.circuit.add_node(\n                        DbspOperator::Projection {\n                            exprs: final_dbsp_exprs,\n                            schema: input_schema.clone(),  // Back to original schema\n                        },\n                        vec![filter_id],\n                        final_proj_executable,\n                    );\n\n                    Ok(final_id)\n                } else {\n                    // Simple filter - use existing implementation\n                    // Convert predicate to DBSP expression\n                    let dbsp_predicate = Self::compile_expr(&filter.predicate)?;\n\n                    // Convert to FilterPredicate\n                    let filter_predicate = Self::compile_filter_predicate(&filter.predicate, input_schema)?;\n\n                    // Create executable operator\n                    let executable: Box<dyn IncrementalOperator> =\n                        Box::new(FilterOperator::new(filter_predicate));\n\n                    // Create filter node\n                    let node_id = self.circuit.add_node(\n                        DbspOperator::Filter { predicate: dbsp_predicate },\n                        vec![input_id],\n                        executable,\n                    );\n                    Ok(node_id)\n                }\n            }\n            LogicalPlan::Aggregate(agg) => {\n                // Compile the input first\n                let input_id = self.compile_plan(&agg.input)?;\n\n                // Get input column names\n                let input_schema = agg.input.schema();\n                let input_column_names: Vec<String> = input_schema.columns.iter()\n                    .map(|col| col.name.clone())\n                    .collect();\n\n                // Compile group by expressions to column indices\n                let mut group_by_indices = Vec::new();\n                let mut dbsp_group_exprs = Vec::new();\n                for expr in &agg.group_expr {\n                    // For now, only support simple column references in GROUP BY\n                    if let LogicalExpr::Column(col) = expr {\n                        // Find the column index in the input schema using qualified lookup\n                        let (col_idx, _) = input_schema.find_column(&col.name, col.table.as_deref())\n                            .ok_or_else(|| LimboError::ParseError(\n                                format!(\"GROUP BY column '{}' not found in input\", col.name)\n                            ))?;\n                        group_by_indices.push(col_idx);\n                        dbsp_group_exprs.push(DbspExpr::Column(col.name.clone()));\n                    } else {\n                        return Err(LimboError::ParseError(\n                            \"Only column references are supported in GROUP BY for incremental views\".to_string()\n                        ));\n                    }\n                }\n\n                // Compile aggregate expressions (both DISTINCT and regular)\n                let mut aggregate_functions = Vec::new();\n                for expr in &agg.aggr_expr {\n                    if let LogicalExpr::AggregateFunction { fun, args, distinct } = expr {\n                        use crate::function::AggFunc;\n                        use crate::incremental::aggregate_operator::AggregateFunction;\n\n                        match fun {\n                            AggFunc::Count | AggFunc::Count0 => {\n                                if *distinct {\n                                    // COUNT(DISTINCT col)\n                                    if args.is_empty() {\n                                        return Err(LimboError::ParseError(\"COUNT(DISTINCT) requires an argument\".to_string()));\n                                    }\n                                    if let LogicalExpr::Column(col) = &args[0] {\n                                        let (col_idx, _) = input_schema.find_column(&col.name, col.table.as_deref())\n                                            .ok_or_else(|| LimboError::ParseError(\n                                                format!(\"COUNT(DISTINCT) column '{}' not found in input\", col.name)\n                                            ))?;\n                                        aggregate_functions.push(AggregateFunction::CountDistinct(col_idx));\n                                    } else {\n                                        return Err(LimboError::ParseError(\n                                            \"Only column references are supported in aggregate functions for incremental views\".to_string()\n                                        ));\n                                    }\n                                } else {\n                                    aggregate_functions.push(AggregateFunction::Count);\n                                }\n                            }\n                            AggFunc::Sum => {\n                                if args.is_empty() {\n                                    return Err(LimboError::ParseError(\"SUM requires an argument\".to_string()));\n                                }\n                                // Extract column index from the argument\n                                if let LogicalExpr::Column(col) = &args[0] {\n                                    let (col_idx, _) = input_schema.find_column(&col.name, col.table.as_deref())\n                                        .ok_or_else(|| LimboError::ParseError(\n                                            format!(\"SUM column '{}' not found in input\", col.name)\n                                        ))?;\n                                    if *distinct {\n                                        aggregate_functions.push(AggregateFunction::SumDistinct(col_idx));\n                                    } else {\n                                        aggregate_functions.push(AggregateFunction::Sum(col_idx));\n                                    }\n                                } else {\n                                    return Err(LimboError::ParseError(\n                                        \"Only column references are supported in aggregate functions for incremental views\".to_string()\n                                    ));\n                                }\n                            }\n                            AggFunc::Avg => {\n                                if args.is_empty() {\n                                    return Err(LimboError::ParseError(\"AVG requires an argument\".to_string()));\n                                }\n                                if let LogicalExpr::Column(col) = &args[0] {\n                                    let (col_idx, _) = input_schema.find_column(&col.name, col.table.as_deref())\n                                        .ok_or_else(|| LimboError::ParseError(\n                                            format!(\"AVG column '{}' not found in input\", col.name)\n                                        ))?;\n                                    if *distinct {\n                                        aggregate_functions.push(AggregateFunction::AvgDistinct(col_idx));\n                                    } else {\n                                        aggregate_functions.push(AggregateFunction::Avg(col_idx));\n                                    }\n                                } else {\n                                    return Err(LimboError::ParseError(\n                                        \"Only column references are supported in aggregate functions for incremental views\".to_string()\n                                    ));\n                                }\n                            }\n                            AggFunc::Min => {\n                                if args.is_empty() {\n                                    return Err(LimboError::ParseError(\"MIN requires an argument\".to_string()));\n                                }\n                                if let LogicalExpr::Column(col) = &args[0] {\n                                    let (col_idx, _) = input_schema.find_column(&col.name, col.table.as_deref())\n                                        .ok_or_else(|| LimboError::ParseError(\n                                            format!(\"MIN column '{}' not found in input\", col.name)\n                                        ))?;\n                                    aggregate_functions.push(AggregateFunction::Min(col_idx));\n                                } else {\n                                    return Err(LimboError::ParseError(\n                                        \"Only column references are supported in MIN for incremental views\".to_string()\n                                    ));\n                                }\n                            }\n                            AggFunc::Max => {\n                                if args.is_empty() {\n                                    return Err(LimboError::ParseError(\"MAX requires an argument\".to_string()));\n                                }\n                                if let LogicalExpr::Column(col) = &args[0] {\n                                    let (col_idx, _) = input_schema.find_column(&col.name, col.table.as_deref())\n                                        .ok_or_else(|| LimboError::ParseError(\n                                            format!(\"MAX column '{}' not found in input\", col.name)\n                                        ))?;\n                                    aggregate_functions.push(AggregateFunction::Max(col_idx));\n                                } else {\n                                    return Err(LimboError::ParseError(\n                                        \"Only column references are supported in MAX for incremental views\".to_string()\n                                    ));\n                                }\n                            }\n                            _ => {\n                                return Err(LimboError::ParseError(\n                                    format!(\"Unsupported aggregate function in DBSP compiler: {fun:?}\")\n                                ));\n                            }\n                        }\n                    } else {\n                        return Err(LimboError::ParseError(\n                            \"Expected aggregate function in aggregate expressions\".to_string()\n                        ));\n                    }\n                }\n\n                let operator_id = self.circuit.next_id;\n\n                use crate::incremental::aggregate_operator::AggregateOperator;\n                let executable: Box<dyn IncrementalOperator> = Box::new(AggregateOperator::new(\n                    operator_id,\n                    group_by_indices.clone(),\n                    aggregate_functions.clone(),\n                    input_column_names,\n                )?);\n\n                let result_node_id = self.circuit.add_node(\n                    DbspOperator::Aggregate {\n                        group_exprs: dbsp_group_exprs,\n                        aggr_exprs: aggregate_functions,\n                        schema: agg.schema.clone(),\n                    },\n                    vec![input_id],\n                    executable,\n                );\n\n                Ok(result_node_id)\n            }\n            LogicalPlan::Join(join) => {\n                // Compile left and right inputs\n                let left_id = self.compile_plan(&join.left)?;\n                let right_id = self.compile_plan(&join.right)?;\n\n                // Get schemas from inputs\n                let left_schema = join.left.schema();\n                let right_schema = join.right.schema();\n\n                // Get column names from left and right\n                let left_columns: Vec<String> = left_schema.columns.iter()\n                    .map(|col| col.name.clone())\n                    .collect();\n                let right_columns: Vec<String> = right_schema.columns.iter()\n                    .map(|col| col.name.clone())\n                    .collect();\n\n                // Check if there are any non-equijoin conditions in the filter\n                if join.filter.is_some() {\n                    return Err(LimboError::ParseError(\n                        \"Non-equijoin conditions are not supported in materialized views. Only equality joins (=) are allowed.\".to_string()\n                    ));\n                }\n\n                // Check if we have at least one equijoin condition\n                if join.on.is_empty() {\n                    return Err(LimboError::ParseError(\n                        \"Joins in materialized views must have at least one equality condition.\".to_string()\n                    ));\n                }\n\n                // Extract join key indices from join conditions\n                // For now, we only support equijoin conditions\n                let mut left_key_indices = Vec::new();\n                let mut right_key_indices = Vec::new();\n                let mut dbsp_on_exprs = Vec::new();\n\n                for (left_expr, right_expr) in &join.on {\n                    // Extract column indices from join expressions\n                    // We expect simple column references in join conditions\n                    if let (LogicalExpr::Column(first_col), LogicalExpr::Column(second_col)) = (left_expr, right_expr) {\n                        let (actual_left_col, actual_left_idx, actual_right_col, actual_right_idx) =\n                            Self::resolve_join_columns(first_col, second_col, left_schema, right_schema)?;\n\n                        left_key_indices.push(actual_left_idx);\n                        right_key_indices.push(actual_right_idx);\n\n                        // Convert to DBSP expressions\n                        dbsp_on_exprs.push((\n                            DbspExpr::Column(actual_left_col.name.clone()),\n                            DbspExpr::Column(actual_right_col.name.clone())\n                        ));\n                    } else {\n                        return Err(LimboError::ParseError(\n                            \"Only simple column references are supported in join conditions for incremental views\".to_string()\n                        ));\n                    }\n                }\n\n                // Convert logical join type to operator join type\n                let operator_join_type = match join.join_type {\n                    LogicalJoinType::Inner => JoinType::Inner,\n                    LogicalJoinType::Left => JoinType::Left,\n                    LogicalJoinType::Right => JoinType::Right,\n                    LogicalJoinType::Full => JoinType::Full,\n                    LogicalJoinType::Cross => JoinType::Cross,\n                };\n\n                // Create JoinOperator\n                let operator_id = self.circuit.next_id;\n                let executable: Box<dyn IncrementalOperator> = Box::new(JoinOperator::new(\n                    operator_id,\n                    operator_join_type.clone(),\n                    left_key_indices,\n                    right_key_indices,\n                    left_columns,\n                    right_columns,\n                )?);\n\n                // Create join node\n                let node_id = self.circuit.add_node(\n                    DbspOperator::Join {\n                        join_type: operator_join_type,\n                        on_exprs: dbsp_on_exprs,\n                        schema: join.schema.clone(),\n                    },\n                    vec![left_id, right_id],\n                    executable,\n                );\n                Ok(node_id)\n            }\n            LogicalPlan::TableScan(scan) => {\n                // Create input node with InputOperator for uniform handling\n                let executable: Box<dyn IncrementalOperator> =\n                    Box::new(InputOperator::new(scan.table_name.clone()));\n\n                let node_id = self.circuit.add_node(\n                    DbspOperator::Input {\n                        name: scan.table_name.clone(),\n                        schema: scan.schema.clone(),\n                    },\n                    vec![],\n                    executable,\n                );\n                Ok(node_id)\n            }\n            LogicalPlan::Union(union) => {\n                // Handle UNION and UNION ALL\n                self.compile_union(union)\n            }\n            LogicalPlan::Distinct(distinct) => {\n                // DISTINCT is implemented as GROUP BY all columns with a special aggregate\n                let input_id = self.compile_plan(&distinct.input)?;\n                let input_schema = distinct.input.schema();\n\n                // Create GROUP BY indices for all columns\n                let group_by: Vec<usize> = (0..input_schema.columns.len()).collect();\n\n                // Column names for the operator\n                let input_column_names: Vec<String> = input_schema.columns.iter()\n                    .map(|col| col.name.clone())\n                    .collect();\n\n                // Create the aggregate operator with DISTINCT mode\n                let operator_id = self.circuit.next_id;\n                let executable: Box<dyn IncrementalOperator> = Box::new(\n                    AggregateOperator::new(\n                        operator_id,\n                        group_by,\n                        vec![], // Empty aggregates indicates plain DISTINCT\n                        input_column_names,\n                    )?,\n                );\n\n                // Add the node to the circuit\n                let node_id = self.circuit.add_node(\n                    DbspOperator::Distinct {\n                        schema: input_schema.clone(),\n                    },\n                    vec![input_id],\n                    executable,\n                );\n\n                Ok(node_id)\n            }\n            _ => Err(LimboError::ParseError(\n                format!(\"Unsupported operator in DBSP compiler: only Filter, Projection, Join, Aggregate, and Union are supported, got: {:?}\",\n                    match plan {\n                        LogicalPlan::Sort(_) => \"Sort\",\n                        LogicalPlan::Limit(_) => \"Limit\",\n                        LogicalPlan::Union(_) => \"Union\",\n                                    LogicalPlan::EmptyRelation(_) => \"EmptyRelation\",\n                        LogicalPlan::Values(_) => \"Values\",\n                        LogicalPlan::WithCTE(_) => \"WithCTE\",\n                        LogicalPlan::CTERef(_) => \"CTERef\",\n                        _ => \"Unknown\",\n                    }\n                )\n            )),\n        }\n    }\n\n    /// Extract a representative table name from a logical plan (for UNION ALL identification)\n    /// Returns a string that uniquely identifies the source of the data\n    fn extract_source_identifier(plan: &LogicalPlan) -> String {\n        match plan {\n            LogicalPlan::TableScan(scan) => {\n                // Direct table scan - use the table name\n                scan.table_name.clone()\n            }\n            LogicalPlan::Projection(proj) => {\n                // Pass through to input\n                Self::extract_source_identifier(&proj.input)\n            }\n            LogicalPlan::Filter(filter) => {\n                // Pass through to input\n                Self::extract_source_identifier(&filter.input)\n            }\n            LogicalPlan::Aggregate(agg) => {\n                // Aggregate of a table\n                format!(\"agg_{}\", Self::extract_source_identifier(&agg.input))\n            }\n            LogicalPlan::Sort(sort) => {\n                // Pass through to input\n                Self::extract_source_identifier(&sort.input)\n            }\n            LogicalPlan::Limit(limit) => {\n                // Pass through to input\n                Self::extract_source_identifier(&limit.input)\n            }\n            LogicalPlan::Join(join) => {\n                // Join of two sources - combine their identifiers\n                let left_id = Self::extract_source_identifier(&join.left);\n                let right_id = Self::extract_source_identifier(&join.right);\n                format!(\"join_{left_id}_{right_id}\")\n            }\n            LogicalPlan::Union(union) => {\n                // Union of multiple sources\n                if union.inputs.is_empty() {\n                    \"union_empty\".to_string()\n                } else {\n                    let identifiers: Vec<String> = union\n                        .inputs\n                        .iter()\n                        .map(|input| Self::extract_source_identifier(input))\n                        .collect();\n                    format!(\"union_{}\", identifiers.join(\"_\"))\n                }\n            }\n            LogicalPlan::Distinct(distinct) => {\n                // Distinct of a source\n                format!(\n                    \"distinct_{}\",\n                    Self::extract_source_identifier(&distinct.input)\n                )\n            }\n            LogicalPlan::WithCTE(with_cte) => {\n                // CTE body\n                Self::extract_source_identifier(&with_cte.body)\n            }\n            LogicalPlan::CTERef(cte_ref) => {\n                // CTE reference - use the CTE name\n                format!(\"cte_{}\", cte_ref.name)\n            }\n            LogicalPlan::EmptyRelation(_) => \"empty\".to_string(),\n            LogicalPlan::Values(_) => \"values\".to_string(),\n        }\n    }\n\n    /// Compile a UNION operator\n    fn compile_union(&mut self, union: &crate::translate::logical::Union) -> Result<i64> {\n        if union.inputs.len() != 2 {\n            return Err(LimboError::ParseError(format!(\n                \"UNION requires exactly 2 inputs, got {}\",\n                union.inputs.len()\n            )));\n        }\n\n        // Extract source identifiers from each input (for UNION ALL)\n        let left_source = Self::extract_source_identifier(&union.inputs[0]);\n        let right_source = Self::extract_source_identifier(&union.inputs[1]);\n\n        // Compile left and right inputs\n        let left_id = self.compile_plan(&union.inputs[0])?;\n        let right_id = self.compile_plan(&union.inputs[1])?;\n\n        use crate::incremental::merge_operator::{MergeOperator, UnionMode};\n\n        // Create a merge operator that handles the rowid transformation\n        let operator_id = self.circuit.next_id;\n        let mode = if union.all {\n            // For UNION ALL, pass the source identifiers\n            UnionMode::All {\n                left_table: left_source,\n                right_table: right_source,\n            }\n        } else {\n            UnionMode::Distinct\n        };\n        let merge_operator = Box::new(MergeOperator::new(operator_id, mode));\n\n        let merge_id = self.circuit.add_node(\n            DbspOperator::Merge {\n                schema: union.schema.clone(),\n            },\n            vec![left_id, right_id],\n            merge_operator,\n        );\n\n        Ok(merge_id)\n    }\n\n    /// Convert a logical expression to a DBSP expression\n    fn compile_expr(expr: &LogicalExpr) -> Result<DbspExpr> {\n        match expr {\n            LogicalExpr::Column(col) => Ok(DbspExpr::Column(col.name.clone())),\n\n            LogicalExpr::Literal(val) => Ok(DbspExpr::Literal(val.clone())),\n\n            LogicalExpr::BinaryExpr { left, op, right } => {\n                let left_expr = Self::compile_expr(left)?;\n                let right_expr = Self::compile_expr(right)?;\n\n                Ok(DbspExpr::BinaryExpr {\n                    left: Box::new(left_expr),\n                    op: *op,\n                    right: Box::new(right_expr),\n                })\n            }\n\n            LogicalExpr::Alias { expr, .. } => {\n                // For aliases, compile the underlying expression\n                Self::compile_expr(expr)\n            }\n\n            // For complex expressions (functions, etc), we can't represent them as DbspExpr\n            // but that's OK - they'll be handled by the ProjectOperator's VDBE compilation\n            // For now, just use a placeholder\n            _ => {\n                // Use a literal null as placeholder - the actual execution will use the compiled VDBE\n                Ok(DbspExpr::Literal(Value::Null))\n            }\n        }\n    }\n\n    /// Compile a logical expression to a CompiledExpression and optional alias\n    fn compile_expression(\n        expr: &LogicalExpr,\n        input_schema: &LogicalSchema,\n    ) -> Result<(CompiledExpression, Option<String>)> {\n        // Check for alias first\n        if let LogicalExpr::Alias { expr, alias } = expr {\n            // For aliases, compile the underlying expression and return with alias\n            let (compiled, _) = Self::compile_expression(expr, input_schema)?;\n            return Ok((compiled, Some(alias.clone())));\n        }\n\n        // Convert LogicalExpr to AST Expr with proper column resolution\n        let ast_expr = Self::logical_to_ast_expr_with_schema(expr, input_schema)?;\n\n        // Extract column names from schema for CompiledExpression::compile\n        let input_column_names: Vec<String> = input_schema\n            .columns\n            .iter()\n            .map(|col| col.name.clone())\n            .collect();\n\n        // For all expressions (simple or complex), use CompiledExpression::compile\n        // This handles both trivial cases and complex VDBE compilation\n        // We need to set up the necessary context\n        use crate::sync::Arc;\n        use crate::{Database, MemoryIO, SymbolTable};\n\n        // Create an internal connection for expression compilation\n        let io = Arc::new(MemoryIO::new());\n        let db = Database::open_file(io, \":memory:\")?;\n        let internal_conn = db.connect()?;\n        internal_conn.set_query_only(true);\n        internal_conn.auto_commit.store(false, Ordering::SeqCst);\n\n        // Create temporary symbol table\n        let temp_syms = SymbolTable::new();\n\n        // Get a minimal schema for compilation (we don't need the full schema for expressions)\n        let schema = crate::schema::Schema::new();\n\n        // Compile the expression using the existing CompiledExpression::compile\n        let compiled = CompiledExpression::compile(\n            &ast_expr,\n            &input_column_names,\n            &schema,\n            &temp_syms,\n            internal_conn,\n        )?;\n\n        Ok((compiled, None))\n    }\n\n    /// Convert LogicalExpr to AST Expr with qualified column resolution\n    fn logical_to_ast_expr_with_schema(\n        expr: &LogicalExpr,\n        schema: &LogicalSchema,\n    ) -> Result<turso_parser::ast::Expr> {\n        use turso_parser::ast;\n\n        match expr {\n            LogicalExpr::Column(col) => {\n                // Find the column index using qualified lookup\n                let (idx, _) = schema\n                    .find_column(&col.name, col.table.as_deref())\n                    .ok_or_else(|| {\n                        LimboError::ParseError(format!(\n                            \"Column '{}' with table {:?} not found in schema\",\n                            col.name, col.table\n                        ))\n                    })?;\n                // Return a Register expression with the correct index\n                Ok(ast::Expr::Register(idx))\n            }\n            LogicalExpr::Literal(val) => {\n                let lit = match val {\n                    Value::Numeric(Numeric::Integer(i)) => ast::Literal::Numeric(i.to_string()),\n                    Value::Numeric(Numeric::Float(f)) => {\n                        ast::Literal::Numeric(f64::from(*f).to_string())\n                    }\n                    Value::Text(t) => {\n                        // Add quotes for string literals as translate_expr expects them\n                        // Also escape any single quotes in the string\n                        let escaped = t.to_string().replace('\\'', \"''\");\n                        ast::Literal::String(format!(\"'{escaped}'\"))\n                    }\n                    Value::Blob(b) => ast::Literal::Blob(format!(\"{b:?}\")),\n                    Value::Null => ast::Literal::Null,\n                };\n                Ok(ast::Expr::Literal(lit))\n            }\n            LogicalExpr::BinaryExpr { left, op, right } => {\n                let left_expr = Self::logical_to_ast_expr_with_schema(left, schema)?;\n                let right_expr = Self::logical_to_ast_expr_with_schema(right, schema)?;\n                Ok(ast::Expr::Binary(\n                    Box::new(left_expr),\n                    *op,\n                    Box::new(right_expr),\n                ))\n            }\n            LogicalExpr::ScalarFunction { fun, args } => {\n                let ast_args: Result<Vec<_>> = args\n                    .iter()\n                    .map(|arg| Self::logical_to_ast_expr_with_schema(arg, schema))\n                    .collect();\n                let ast_args: Vec<Box<ast::Expr>> = ast_args?.into_iter().map(Box::new).collect();\n                Ok(ast::Expr::FunctionCall {\n                    name: ast::Name::exact(fun.clone()),\n                    distinctness: None,\n                    args: ast_args,\n                    order_by: Vec::new(),\n                    filter_over: ast::FunctionTail {\n                        filter_clause: None,\n                        over_clause: None,\n                    },\n                })\n            }\n            LogicalExpr::Alias { expr, .. } => {\n                // For conversion to AST, ignore the alias and convert the inner expression\n                Self::logical_to_ast_expr_with_schema(expr, schema)\n            }\n            LogicalExpr::AggregateFunction {\n                fun,\n                args,\n                distinct,\n            } => {\n                // Convert aggregate function to AST\n                let ast_args: Result<Vec<_>> = args\n                    .iter()\n                    .map(|arg| Self::logical_to_ast_expr_with_schema(arg, schema))\n                    .collect();\n                let ast_args: Vec<Box<ast::Expr>> = ast_args?.into_iter().map(Box::new).collect();\n\n                // Get the function name based on the aggregate type\n                let func_name = match fun {\n                    crate::function::AggFunc::Count => \"COUNT\",\n                    crate::function::AggFunc::Sum => \"SUM\",\n                    crate::function::AggFunc::Avg => \"AVG\",\n                    crate::function::AggFunc::Min => \"MIN\",\n                    crate::function::AggFunc::Max => \"MAX\",\n                    _ => {\n                        return Err(LimboError::ParseError(format!(\n                            \"Unsupported aggregate function: {fun:?}\"\n                        )))\n                    }\n                };\n\n                Ok(ast::Expr::FunctionCall {\n                    name: ast::Name::exact(func_name.to_string()),\n                    distinctness: if *distinct {\n                        Some(ast::Distinctness::Distinct)\n                    } else {\n                        None\n                    },\n                    args: ast_args,\n                    order_by: Vec::new(),\n                    filter_over: ast::FunctionTail {\n                        filter_clause: None,\n                        over_clause: None,\n                    },\n                })\n            }\n            LogicalExpr::Between {\n                expr,\n                low,\n                high,\n                negated,\n            } => {\n                // BETWEEN x AND y is rewritten as (expr >= x AND expr <= y)\n                // NOT BETWEEN x AND y is rewritten as (expr < x OR expr > y)\n                let expr_ast = Self::logical_to_ast_expr_with_schema(expr, schema)?;\n                let low_ast = Self::logical_to_ast_expr_with_schema(low, schema)?;\n                let high_ast = Self::logical_to_ast_expr_with_schema(high, schema)?;\n\n                if *negated {\n                    // NOT BETWEEN: (expr < low OR expr > high)\n                    Ok(ast::Expr::Binary(\n                        Box::new(ast::Expr::Binary(\n                            Box::new(expr_ast.clone()),\n                            ast::Operator::Less,\n                            Box::new(low_ast),\n                        )),\n                        ast::Operator::Or,\n                        Box::new(ast::Expr::Binary(\n                            Box::new(expr_ast),\n                            ast::Operator::Greater,\n                            Box::new(high_ast),\n                        )),\n                    ))\n                } else {\n                    // BETWEEN: (expr >= low AND expr <= high)\n                    Ok(ast::Expr::Binary(\n                        Box::new(ast::Expr::Binary(\n                            Box::new(expr_ast.clone()),\n                            ast::Operator::GreaterEquals,\n                            Box::new(low_ast),\n                        )),\n                        ast::Operator::And,\n                        Box::new(ast::Expr::Binary(\n                            Box::new(expr_ast),\n                            ast::Operator::LessEquals,\n                            Box::new(high_ast),\n                        )),\n                    ))\n                }\n            }\n            LogicalExpr::InList {\n                expr,\n                list,\n                negated,\n            } => {\n                let lhs = Box::new(Self::logical_to_ast_expr_with_schema(expr, schema)?);\n                let values: Result<Vec<_>> = list\n                    .iter()\n                    .map(|item| {\n                        let ast_expr = Self::logical_to_ast_expr_with_schema(item, schema)?;\n                        Ok(Box::new(ast_expr))\n                    })\n                    .collect();\n                Ok(ast::Expr::InList {\n                    lhs,\n                    not: *negated,\n                    rhs: values?,\n                })\n            }\n            LogicalExpr::Like {\n                expr,\n                pattern,\n                escape,\n                negated,\n            } => {\n                let lhs = Box::new(Self::logical_to_ast_expr_with_schema(expr, schema)?);\n                let rhs = Box::new(Self::logical_to_ast_expr_with_schema(pattern, schema)?);\n                let escape_expr = escape\n                    .map(|c| Box::new(ast::Expr::Literal(ast::Literal::String(c.to_string()))));\n                Ok(ast::Expr::Like {\n                    lhs,\n                    not: *negated,\n                    op: ast::LikeOperator::Like,\n                    rhs,\n                    escape: escape_expr,\n                })\n            }\n            LogicalExpr::IsNull { expr, negated } => {\n                let inner_expr = Box::new(Self::logical_to_ast_expr_with_schema(expr, schema)?);\n                if *negated {\n                    // IS NOT NULL needs to be represented differently\n                    Ok(ast::Expr::Unary(\n                        ast::UnaryOperator::Not,\n                        Box::new(ast::Expr::IsNull(inner_expr)),\n                    ))\n                } else {\n                    Ok(ast::Expr::IsNull(inner_expr))\n                }\n            }\n            LogicalExpr::Cast { expr, type_name } => {\n                let inner_expr = Box::new(Self::logical_to_ast_expr_with_schema(expr, schema)?);\n                Ok(ast::Expr::Cast {\n                    expr: inner_expr,\n                    type_name: type_name.clone(),\n                })\n            }\n            _ => Err(LimboError::ParseError(format!(\n                \"Cannot convert LogicalExpr to AST Expr: {expr:?}\"\n            ))),\n        }\n    }\n\n    /// Check if a predicate contains expressions that need projection\n    fn predicate_needs_projection(expr: &LogicalExpr) -> bool {\n        match expr {\n            LogicalExpr::BinaryExpr { left, op, right } => {\n                // Only these specific simple patterns DON'T need projection\n                match (left.as_ref(), right.as_ref()) {\n                    // Simple column to literal comparisons\n                    (LogicalExpr::Column(_), LogicalExpr::Literal(_))\n                        if matches!(\n                            op,\n                            BinaryOperator::Equals\n                                | BinaryOperator::NotEquals\n                                | BinaryOperator::Greater\n                                | BinaryOperator::GreaterEquals\n                                | BinaryOperator::Less\n                                | BinaryOperator::LessEquals\n                        ) =>\n                    {\n                        false\n                    }\n\n                    // Simple column to column comparisons\n                    (LogicalExpr::Column(_), LogicalExpr::Column(_))\n                        if matches!(\n                            op,\n                            BinaryOperator::Equals\n                                | BinaryOperator::NotEquals\n                                | BinaryOperator::Greater\n                                | BinaryOperator::GreaterEquals\n                                | BinaryOperator::Less\n                                | BinaryOperator::LessEquals\n                        ) =>\n                    {\n                        false\n                    }\n\n                    // AND/OR of simple expressions - check recursively\n                    _ if matches!(op, BinaryOperator::And | BinaryOperator::Or) => {\n                        Self::predicate_needs_projection(left)\n                            || Self::predicate_needs_projection(right)\n                    }\n\n                    // Everything else needs projection\n                    _ => true,\n                }\n            }\n            // These simple cases don't need projection\n            LogicalExpr::Column(_) | LogicalExpr::Literal(_) => false,\n\n            // Default: assume we need projection for safety\n            // This includes: Between, InList, Like, IsNull, Cast, ScalarFunction, Case,\n            // InSubquery, Exists, ScalarSubquery, and any future expression types\n            _ => true,\n        }\n    }\n\n    /// Extract the expression part from a predicate that needs to be computed\n    fn extract_expression_from_predicate(expr: &LogicalExpr) -> Result<LogicalExpr> {\n        match expr {\n            LogicalExpr::BinaryExpr { left, op, right } => {\n                // Handle AND/OR - recursively find the complex expression\n                if matches!(op, BinaryOperator::And | BinaryOperator::Or) {\n                    // Check left side first\n                    if Self::predicate_needs_projection(left) {\n                        return Self::extract_expression_from_predicate(left);\n                    }\n                    // Then check right side\n                    if Self::predicate_needs_projection(right) {\n                        return Self::extract_expression_from_predicate(right);\n                    }\n                    // Neither side needs projection (shouldn't happen if predicate_needs_projection was true)\n                    return Ok(expr.clone());\n                }\n\n                // For comparison expressions, check if we need to extract a subexpression\n                if matches!(\n                    op,\n                    BinaryOperator::Greater\n                        | BinaryOperator::GreaterEquals\n                        | BinaryOperator::Less\n                        | BinaryOperator::LessEquals\n                        | BinaryOperator::Equals\n                        | BinaryOperator::NotEquals\n                ) {\n                    // If the left side is complex (not a column), extract it\n                    if !matches!(\n                        left.as_ref(),\n                        LogicalExpr::Column(_) | LogicalExpr::Literal(_)\n                    ) {\n                        return Ok((**left).clone());\n                    }\n                    // If the right side is complex (not a literal), extract it\n                    if !matches!(\n                        right.as_ref(),\n                        LogicalExpr::Column(_) | LogicalExpr::Literal(_)\n                    ) {\n                        return Ok((**right).clone());\n                    }\n                    // Both sides are simple but the expression as a whole might need projection\n                    // (e.g., for arithmetic operations)\n                    Ok(expr.clone())\n                } else {\n                    // For other binary operators (arithmetic, etc.), return the whole expression\n                    Ok(expr.clone())\n                }\n            }\n            // For non-binary expressions (BETWEEN, IN, LIKE, functions, etc.),\n            // we need to compute the whole expression as a boolean\n            _ => Ok(expr.clone()),\n        }\n    }\n\n    /// Replace complex expressions in the predicate with references to the temp column\n    fn replace_complex_with_temp(\n        expr: &LogicalExpr,\n        temp_column_name: &str,\n    ) -> Result<LogicalExpr> {\n        match expr {\n            LogicalExpr::BinaryExpr { left, op, right } => {\n                // Handle AND/OR - recursively process both sides\n                if matches!(op, BinaryOperator::And | BinaryOperator::Or) {\n                    let new_left = Self::replace_complex_with_temp(left, temp_column_name)?;\n                    let new_right = Self::replace_complex_with_temp(right, temp_column_name)?;\n                    return Ok(LogicalExpr::BinaryExpr {\n                        left: Box::new(new_left),\n                        op: *op,\n                        right: Box::new(new_right),\n                    });\n                }\n\n                // Check if this is a complex comparison that needs replacement\n                if Self::predicate_needs_projection(expr) {\n                    // Determine which side is complex and needs replacement\n                    let left_is_simple = matches!(\n                        left.as_ref(),\n                        LogicalExpr::Column(_) | LogicalExpr::Literal(_)\n                    );\n                    let right_is_simple = matches!(\n                        right.as_ref(),\n                        LogicalExpr::Column(_) | LogicalExpr::Literal(_)\n                    );\n\n                    if !left_is_simple {\n                        // Left side is complex - replace it with temp column\n                        return Ok(LogicalExpr::BinaryExpr {\n                            left: Box::new(LogicalExpr::Column(Column {\n                                name: temp_column_name.to_string(),\n                                table: None,\n                            })),\n                            op: *op,\n                            right: right.clone(),\n                        });\n                    } else if !right_is_simple {\n                        // Right side is complex - replace it with temp column\n                        return Ok(LogicalExpr::BinaryExpr {\n                            left: left.clone(),\n                            op: *op,\n                            right: Box::new(LogicalExpr::Column(Column {\n                                name: temp_column_name.to_string(),\n                                table: None,\n                            })),\n                        });\n                    } else {\n                        // Both sides are simple, but the expression as a whole needs projection\n                        // This shouldn't happen normally, but keep the expression as-is\n                        return Ok(expr.clone());\n                    }\n                }\n\n                // Simple comparison - keep as is\n                Ok(expr.clone())\n            }\n            // For non-binary expressions that need projection (BETWEEN, IN, etc.),\n            // replace the whole expression with a column reference to the temp column\n            // The temp column will hold the boolean result of evaluating the expression\n            _ if Self::predicate_needs_projection(expr) => {\n                // The complex expression result is in the temp column\n                // We need to check if it's true (non-zero)\n                Ok(LogicalExpr::BinaryExpr {\n                    left: Box::new(LogicalExpr::Column(Column {\n                        name: temp_column_name.to_string(),\n                        table: None,\n                    })),\n                    op: BinaryOperator::Equals,\n                    right: Box::new(LogicalExpr::Literal(Value::from_i64(1))), // true = 1 in SQL\n                })\n            }\n            _ => Ok(expr.clone()),\n        }\n    }\n\n    /// Compile a logical expression to a FilterPredicate for execution\n    fn compile_filter_predicate(\n        expr: &LogicalExpr,\n        schema: &LogicalSchema,\n    ) -> Result<FilterPredicate> {\n        match expr {\n            LogicalExpr::BinaryExpr { left, op, right } => {\n                // Extract column name and value for simple predicates\n                // First check for column-to-column comparisons\n                if let (LogicalExpr::Column(left_col), LogicalExpr::Column(right_col)) =\n                    (left.as_ref(), right.as_ref())\n                {\n                    // Resolve both column names to indices\n                    let left_idx = schema\n                        .columns\n                        .iter()\n                        .position(|c| c.name == left_col.name)\n                        .ok_or_else(|| {\n                            crate::LimboError::ParseError(format!(\n                                \"Column '{}' not found in schema for filter\",\n                                left_col.name\n                            ))\n                        })?;\n\n                    let right_idx = schema\n                        .columns\n                        .iter()\n                        .position(|c| c.name == right_col.name)\n                        .ok_or_else(|| {\n                            crate::LimboError::ParseError(format!(\n                                \"Column '{}' not found in schema for filter\",\n                                right_col.name\n                            ))\n                        })?;\n\n                    match op {\n                        BinaryOperator::Equals => Ok(FilterPredicate::ColumnEquals {\n                            left_idx,\n                            right_idx,\n                        }),\n                        BinaryOperator::NotEquals => Ok(FilterPredicate::ColumnNotEquals {\n                            left_idx,\n                            right_idx,\n                        }),\n                        BinaryOperator::Greater => Ok(FilterPredicate::ColumnGreaterThan {\n                            left_idx,\n                            right_idx,\n                        }),\n                        BinaryOperator::GreaterEquals => {\n                            Ok(FilterPredicate::ColumnGreaterThanOrEqual {\n                                left_idx,\n                                right_idx,\n                            })\n                        }\n                        BinaryOperator::Less => Ok(FilterPredicate::ColumnLessThan {\n                            left_idx,\n                            right_idx,\n                        }),\n                        BinaryOperator::LessEquals => Ok(FilterPredicate::ColumnLessThanOrEqual {\n                            left_idx,\n                            right_idx,\n                        }),\n                        BinaryOperator::And | BinaryOperator::Or => {\n                            // Handle logical operators recursively\n                            let left_pred = Self::compile_filter_predicate(left, schema)?;\n                            let right_pred = Self::compile_filter_predicate(right, schema)?;\n                            match op {\n                                BinaryOperator::And => Ok(FilterPredicate::And(\n                                    Box::new(left_pred),\n                                    Box::new(right_pred),\n                                )),\n                                BinaryOperator::Or => Ok(FilterPredicate::Or(\n                                    Box::new(left_pred),\n                                    Box::new(right_pred),\n                                )),\n                                _ => unreachable!(),\n                            }\n                        }\n                        _ => Err(LimboError::ParseError(format!(\n                            \"Unsupported operator in filter: {op:?}\"\n                        ))),\n                    }\n                } else if let (LogicalExpr::Column(col), LogicalExpr::Literal(val)) =\n                    (left.as_ref(), right.as_ref())\n                {\n                    // Column-to-literal comparisons\n                    let column_idx = schema\n                        .columns\n                        .iter()\n                        .position(|c| c.name == col.name)\n                        .ok_or_else(|| {\n                            crate::LimboError::ParseError(format!(\n                                \"Column '{}' not found in schema for filter\",\n                                col.name\n                            ))\n                        })?;\n\n                    match op {\n                        BinaryOperator::Equals => Ok(FilterPredicate::Equals {\n                            column_idx,\n                            value: val.clone(),\n                        }),\n                        BinaryOperator::NotEquals => Ok(FilterPredicate::NotEquals {\n                            column_idx,\n                            value: val.clone(),\n                        }),\n                        BinaryOperator::Greater => Ok(FilterPredicate::GreaterThan {\n                            column_idx,\n                            value: val.clone(),\n                        }),\n                        BinaryOperator::GreaterEquals => Ok(FilterPredicate::GreaterThanOrEqual {\n                            column_idx,\n                            value: val.clone(),\n                        }),\n                        BinaryOperator::Less => Ok(FilterPredicate::LessThan {\n                            column_idx,\n                            value: val.clone(),\n                        }),\n                        BinaryOperator::LessEquals => Ok(FilterPredicate::LessThanOrEqual {\n                            column_idx,\n                            value: val.clone(),\n                        }),\n                        BinaryOperator::And => {\n                            // Handle AND of two predicates\n                            let left_pred = Self::compile_filter_predicate(left, schema)?;\n                            let right_pred = Self::compile_filter_predicate(right, schema)?;\n                            Ok(FilterPredicate::And(\n                                Box::new(left_pred),\n                                Box::new(right_pred),\n                            ))\n                        }\n                        BinaryOperator::Or => {\n                            // Handle OR of two predicates\n                            let left_pred = Self::compile_filter_predicate(left, schema)?;\n                            let right_pred = Self::compile_filter_predicate(right, schema)?;\n                            Ok(FilterPredicate::Or(\n                                Box::new(left_pred),\n                                Box::new(right_pred),\n                            ))\n                        }\n                        _ => Err(LimboError::ParseError(format!(\n                            \"Unsupported operator in filter: {op:?}\"\n                        ))),\n                    }\n                } else if matches!(op, BinaryOperator::And | BinaryOperator::Or) {\n                    // Handle logical operators\n                    let left_pred = Self::compile_filter_predicate(left, schema)?;\n                    let right_pred = Self::compile_filter_predicate(right, schema)?;\n                    match op {\n                        BinaryOperator::And => Ok(FilterPredicate::And(\n                            Box::new(left_pred),\n                            Box::new(right_pred),\n                        )),\n                        BinaryOperator::Or => Ok(FilterPredicate::Or(\n                            Box::new(left_pred),\n                            Box::new(right_pred),\n                        )),\n                        _ => unreachable!(),\n                    }\n                } else {\n                    Err(LimboError::ParseError(\n                        \"Filter predicate must be column op value or column op column\".to_string(),\n                    ))\n                }\n            }\n            LogicalExpr::IsNull { expr, negated } => {\n                // Extract column index from the inner expression\n                if let LogicalExpr::Column(col) = expr.as_ref() {\n                    let column_idx = schema\n                        .columns\n                        .iter()\n                        .position(|c| c.name == col.name)\n                        .ok_or_else(|| {\n                            LimboError::ParseError(format!(\n                                \"Column '{}' not found in schema for IS NULL filter\",\n                                col.name\n                            ))\n                        })?;\n\n                    if *negated {\n                        Ok(FilterPredicate::IsNotNull { column_idx })\n                    } else {\n                        Ok(FilterPredicate::IsNull { column_idx })\n                    }\n                } else {\n                    Err(LimboError::ParseError(\n                        \"IS NULL/IS NOT NULL expects a column reference\".to_string(),\n                    ))\n                }\n            }\n            _ => Err(LimboError::ParseError(format!(\n                \"Unsupported filter expression: {expr:?}\"\n            ))),\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::incremental::dbsp::Delta;\n    use crate::incremental::operator::{FilterOperator, FilterPredicate};\n    use crate::schema::{BTreeTable, ColDef, Column as SchemaColumn, Schema, Type};\n    use crate::storage::pager::CreateBTreeFlags;\n    use crate::sync::Arc;\n    use crate::translate::logical::{ColumnInfo, LogicalPlanBuilder, LogicalSchema};\n    use crate::util::IOExt;\n    use crate::{Database, MemoryIO, Pager, IO};\n    use rustc_hash::FxHashSet as HashSet;\n    use turso_parser::ast;\n    use turso_parser::parser::Parser;\n\n    // Macro to create a test schema with a users table\n    macro_rules! test_schema {\n        () => {{\n            let mut schema = Schema::new();\n            let users_table = BTreeTable {\n                name: \"users\".to_string(),\n                root_page: 2,\n                primary_key_columns: vec![(\"id\".to_string(), turso_parser::ast::SortOrder::Asc)],\n                columns: vec![\n                    SchemaColumn::new(\n                        Some(\"id\".to_string()),\n                        \"INTEGER\".to_string(),\n                        None,\n                        None,\n                        Type::Integer,\n                        None,\n                        ColDef {\n                            primary_key: true,\n                            rowid_alias: true,\n                            notnull: true,\n                            ..Default::default()\n                        },\n                    ),\n                    SchemaColumn::new_default_text(\n                        Some(\"name\".to_string()),\n                        \"TEXT\".to_string(),\n                        None,\n                    ),\n                    SchemaColumn::new_default_integer(\n                        Some(\"age\".to_string()),\n                        \"INTEGER\".to_string(),\n                        None,\n                    ),\n                ],\n                has_rowid: true,\n                is_strict: false,\n                has_autoincrement: false,\n                unique_sets: vec![],\n                foreign_keys: vec![],\n                check_constraints: vec![],\n                rowid_alias_conflict_clause: None,\n            };\n            schema\n                .add_btree_table(Arc::new(users_table))\n                .expect(\"Test setup: failed to add users table\");\n\n            // Add products table for join tests\n            let products_table = BTreeTable {\n                name: \"products\".to_string(),\n                root_page: 3,\n                primary_key_columns: vec![(\n                    \"product_id\".to_string(),\n                    turso_parser::ast::SortOrder::Asc,\n                )],\n                columns: vec![\n                    SchemaColumn::new(\n                        Some(\"product_id\".to_string()),\n                        \"INTEGER\".to_string(),\n                        None,\n                        None,\n                        Type::Integer,\n                        None,\n                        ColDef {\n                            primary_key: true,\n                            rowid_alias: true,\n                            notnull: true,\n                            ..Default::default()\n                        },\n                    ),\n                    SchemaColumn::new_default_text(\n                        Some(\"product_name\".to_string()),\n                        \"TEXT\".to_string(),\n                        None,\n                    ),\n                    SchemaColumn::new_default_integer(\n                        Some(\"price\".to_string()),\n                        \"INTEGER\".to_string(),\n                        None,\n                    ),\n                ],\n                has_rowid: true,\n                is_strict: false,\n                has_autoincrement: false,\n                unique_sets: vec![],\n                foreign_keys: vec![],\n                check_constraints: vec![],\n                rowid_alias_conflict_clause: None,\n            };\n            schema\n                .add_btree_table(Arc::new(products_table))\n                .expect(\"Test setup: failed to add products table\");\n\n            // Add orders table for join tests\n            let orders_table = BTreeTable {\n                name: \"orders\".to_string(),\n                root_page: 4,\n                primary_key_columns: vec![(\n                    \"order_id\".to_string(),\n                    turso_parser::ast::SortOrder::Asc,\n                )],\n                columns: vec![\n                    SchemaColumn::new(\n                        Some(\"order_id\".to_string()),\n                        \"INTEGER\".to_string(),\n                        None,\n                        None,\n                        Type::Integer,\n                        None,\n                        ColDef {\n                            primary_key: true,\n                            rowid_alias: true,\n                            notnull: true,\n                            ..Default::default()\n                        },\n                    ),\n                    SchemaColumn::new_default_integer(\n                        Some(\"user_id\".to_string()),\n                        \"INTEGER\".to_string(),\n                        None,\n                    ),\n                    SchemaColumn::new_default_integer(\n                        Some(\"product_id\".to_string()),\n                        \"INTEGER\".to_string(),\n                        None,\n                    ),\n                    SchemaColumn::new_default_integer(\n                        Some(\"quantity\".to_string()),\n                        \"INTEGER\".to_string(),\n                        None,\n                    ),\n                ],\n                has_rowid: true,\n                has_autoincrement: false,\n                is_strict: false,\n                unique_sets: vec![],\n                foreign_keys: vec![],\n                check_constraints: vec![],\n                rowid_alias_conflict_clause: None,\n            };\n            schema\n                .add_btree_table(Arc::new(orders_table))\n                .expect(\"Test setup: failed to add orders table\");\n\n            // Add customers table with id and name for testing column ambiguity\n            let customers_table = BTreeTable {\n                name: \"customers\".to_string(),\n                root_page: 6,\n                primary_key_columns: vec![(\"id\".to_string(), turso_parser::ast::SortOrder::Asc)],\n                columns: vec![\n                    SchemaColumn::new(\n                        Some(\"id\".to_string()),\n                        \"INTEGER\".to_string(),\n                        None,\n                        None,\n                        Type::Integer,\n                        None,\n                        ColDef {\n                            primary_key: true,\n                            rowid_alias: true,\n                            notnull: true,\n                            ..Default::default()\n                        },\n                    ),\n                    SchemaColumn::new_default_text(\n                        Some(\"name\".to_string()),\n                        \"TEXT\".to_string(),\n                        None,\n                    ),\n                ],\n                has_rowid: true,\n                is_strict: false,\n                has_autoincrement: false,\n                unique_sets: vec![],\n                foreign_keys: vec![],\n                check_constraints: vec![],\n                rowid_alias_conflict_clause: None,\n            };\n            schema\n                .add_btree_table(Arc::new(customers_table))\n                .expect(\"Test setup: failed to add customers table\");\n\n            // Add purchases table (junction table for three-way join)\n            let purchases_table = BTreeTable {\n                name: \"purchases\".to_string(),\n                root_page: 7,\n                primary_key_columns: vec![(\"id\".to_string(), turso_parser::ast::SortOrder::Asc)],\n                columns: vec![\n                    SchemaColumn::new(\n                        Some(\"id\".to_string()),\n                        \"INTEGER\".to_string(),\n                        None,\n                        None,\n                        Type::Integer,\n                        None,\n                        ColDef {\n                            primary_key: true,\n                            rowid_alias: true,\n                            notnull: true,\n                            ..Default::default()\n                        },\n                    ),\n                    SchemaColumn::new_default_integer(\n                        Some(\"customer_id\".to_string()),\n                        \"INTEGER\".to_string(),\n                        None,\n                    ),\n                    SchemaColumn::new_default_integer(\n                        Some(\"vendor_id\".to_string()),\n                        \"INTEGER\".to_string(),\n                        None,\n                    ),\n                    SchemaColumn::new_default_integer(\n                        Some(\"quantity\".to_string()),\n                        \"INTEGER\".to_string(),\n                        None,\n                    ),\n                ],\n                has_rowid: true,\n                is_strict: false,\n                has_autoincrement: false,\n                unique_sets: vec![],\n                foreign_keys: vec![],\n                check_constraints: vec![],\n                rowid_alias_conflict_clause: None,\n            };\n            schema\n                .add_btree_table(Arc::new(purchases_table))\n                .expect(\"Test setup: failed to add purchases table\");\n\n            // Add vendors table with id, name, and price (ambiguous columns with customers)\n            let vendors_table = BTreeTable {\n                name: \"vendors\".to_string(),\n                root_page: 8,\n                primary_key_columns: vec![(\"id\".to_string(), turso_parser::ast::SortOrder::Asc)],\n                columns: vec![\n                    SchemaColumn::new(\n                        Some(\"id\".to_string()),\n                        \"INTEGER\".to_string(),\n                        None,\n                        None,\n                        Type::Integer,\n                        None,\n                        ColDef {\n                            primary_key: true,\n                            rowid_alias: true,\n                            notnull: true,\n                            ..Default::default()\n                        },\n                    ),\n                    SchemaColumn::new_default_text(\n                        Some(\"name\".to_string()),\n                        \"TEXT\".to_string(),\n                        None,\n                    ),\n                    SchemaColumn::new_default_integer(\n                        Some(\"price\".to_string()),\n                        \"INTEGER\".to_string(),\n                        None,\n                    ),\n                ],\n                has_rowid: true,\n                is_strict: false,\n                has_autoincrement: false,\n                unique_sets: vec![],\n                foreign_keys: vec![],\n                check_constraints: vec![],\n                rowid_alias_conflict_clause: None,\n            };\n            schema\n                .add_btree_table(Arc::new(vendors_table))\n                .expect(\"Test setup: failed to add vendors table\");\n\n            let sales_table = BTreeTable {\n                name: \"sales\".to_string(),\n                root_page: 2,\n                primary_key_columns: vec![],\n                columns: vec![\n                    SchemaColumn::new_default_integer(\n                        Some(\"product_id\".to_string()),\n                        \"INTEGER\".to_string(),\n                        None,\n                    ),\n                    SchemaColumn::new_default_integer(\n                        Some(\"amount\".to_string()),\n                        \"INTEGER\".to_string(),\n                        None,\n                    ),\n                ],\n                has_rowid: true,\n                is_strict: false,\n                has_autoincrement: false,\n                unique_sets: vec![],\n                foreign_keys: vec![],\n                check_constraints: vec![],\n                rowid_alias_conflict_clause: None,\n            };\n            schema\n                .add_btree_table(Arc::new(sales_table))\n                .expect(\"Test setup: failed to add sales table\");\n\n            schema\n        }};\n    }\n\n    fn setup_btree_for_circuit() -> (Arc<Pager>, i64, i64, i64) {\n        let io: Arc<dyn IO> = Arc::new(MemoryIO::new());\n        let db = Database::open_file(io.clone(), \":memory:\").unwrap();\n        let conn = db.connect().unwrap();\n        let pager = conn.pager.load().clone();\n\n        let _ = pager.io.block(|| pager.allocate_page1()).unwrap();\n\n        let main_root_page = pager\n            .io\n            .block(|| pager.btree_create(&CreateBTreeFlags::new_table()))\n            .unwrap() as i64;\n\n        let dbsp_state_page = pager\n            .io\n            .block(|| pager.btree_create(&CreateBTreeFlags::new_table()))\n            .unwrap() as i64;\n\n        let dbsp_state_index_page = pager\n            .io\n            .block(|| pager.btree_create(&CreateBTreeFlags::new_index()))\n            .unwrap() as i64;\n\n        (\n            pager,\n            main_root_page,\n            dbsp_state_page,\n            dbsp_state_index_page,\n        )\n    }\n\n    // Macro to compile SQL to DBSP circuit\n    macro_rules! compile_sql {\n        ($sql:expr) => {{\n            let (pager, main_root_page, dbsp_state_page, dbsp_state_index_page) =\n                setup_btree_for_circuit();\n            let schema = test_schema!();\n            let mut parser = Parser::new($sql.as_bytes());\n            let cmd = parser\n                .next()\n                .unwrap() // This returns Option<Result<Cmd, Error>>\n                .unwrap(); // This unwraps the Result\n\n            match cmd {\n                ast::Cmd::Stmt(stmt) => {\n                    let mut builder = LogicalPlanBuilder::new(&schema);\n                    let logical_plan = builder.build_statement(&stmt).unwrap();\n                    (\n                        DbspCompiler::new(main_root_page, dbsp_state_page, dbsp_state_index_page)\n                            .compile(&logical_plan)\n                            .unwrap(),\n                        pager,\n                    )\n                }\n                _ => panic!(\"Only SQL statements are supported\"),\n            }\n        }};\n    }\n\n    // Macro to assert circuit structure\n    macro_rules! assert_circuit {\n        ($circuit:expr, depth: $depth:expr, root: $root_type:ident) => {\n            assert_eq!($circuit.nodes.len(), $depth);\n            let node = get_node_at_level(&$circuit, 0);\n            assert!(matches!(node.operator, DbspOperator::$root_type { .. }));\n        };\n    }\n\n    // Macro to assert operator properties\n    macro_rules! assert_operator {\n        ($circuit:expr, $level:expr, Input { name: $name:expr }) => {{\n            let node = get_node_at_level(&$circuit, $level);\n            match &node.operator {\n                DbspOperator::Input { name, .. } => assert_eq!(name, $name),\n                _ => panic!(\"Expected Input operator at level {}\", $level),\n            }\n        }};\n        ($circuit:expr, $level:expr, Filter) => {{\n            let node = get_node_at_level(&$circuit, $level);\n            assert!(matches!(node.operator, DbspOperator::Filter { .. }));\n        }};\n        ($circuit:expr, $level:expr, Projection { columns: [$($col:expr),*] }) => {{\n            let node = get_node_at_level(&$circuit, $level);\n            match &node.operator {\n                DbspOperator::Projection { exprs, .. } => {\n                    let expected_cols = vec![$($col),*];\n                    let actual_cols: Vec<String> = exprs.iter().map(|e| {\n                        match e {\n                            DbspExpr::Column(name) => name.clone(),\n                            _ => \"expr\".to_string(),\n                        }\n                    }).collect();\n                    assert_eq!(actual_cols, expected_cols);\n                }\n                _ => panic!(\"Expected Projection operator at level {}\", $level),\n            }\n        }};\n    }\n\n    // Macro to assert filter predicate\n    macro_rules! assert_filter_predicate {\n        ($circuit:expr, $level:expr, $col:literal > $val:literal) => {{\n            let node = get_node_at_level(&$circuit, $level);\n            match &node.operator {\n                DbspOperator::Filter { predicate } => match predicate {\n                    DbspExpr::BinaryExpr { left, op, right } => {\n                        assert!(matches!(op, ast::Operator::Greater));\n                        assert!(matches!(&**left, DbspExpr::Column(name) if name == $col));\n                        assert!(matches!(&**right, DbspExpr::Literal(Value::Numeric(Numeric::Integer($val)))));\n                    }\n                    _ => panic!(\"Expected binary expression in filter\"),\n                },\n                _ => panic!(\"Expected Filter operator at level {}\", $level),\n            }\n        }};\n        ($circuit:expr, $level:expr, $col:literal < $val:literal) => {{\n            let node = get_node_at_level(&$circuit, $level);\n            match &node.operator {\n                DbspOperator::Filter { predicate } => match predicate {\n                    DbspExpr::BinaryExpr { left, op, right } => {\n                        assert!(matches!(op, ast::Operator::Less));\n                        assert!(matches!(&**left, DbspExpr::Column(name) if name == $col));\n                        assert!(matches!(&**right, DbspExpr::Literal(Value::Numeric(Numeric::Integer($val)))));\n                    }\n                    _ => panic!(\"Expected binary expression in filter\"),\n                },\n                _ => panic!(\"Expected Filter operator at level {}\", $level),\n            }\n        }};\n        ($circuit:expr, $level:expr, $col:literal = $val:literal) => {{\n            let node = get_node_at_level(&$circuit, $level);\n            match &node.operator {\n                DbspOperator::Filter { predicate } => match predicate {\n                    DbspExpr::BinaryExpr { left, op, right } => {\n                        assert!(matches!(op, ast::Operator::Equals));\n                        assert!(matches!(&**left, DbspExpr::Column(name) if name == $col));\n                        assert!(matches!(&**right, DbspExpr::Literal(Value::Numeric(Numeric::Integer($val)))));\n                    }\n                    _ => panic!(\"Expected binary expression in filter\"),\n                },\n                _ => panic!(\"Expected Filter operator at level {}\", $level),\n            }\n        }};\n    }\n\n    // Helper to get node at specific level from root\n    fn get_node_at_level(circuit: &DbspCircuit, level: usize) -> &DbspNode {\n        let mut current_id = circuit.root.expect(\"Circuit has no root\");\n        for _ in 0..level {\n            let node = circuit.nodes.get(&current_id).expect(\"Node not found\");\n            if node.inputs.is_empty() {\n                panic!(\"No more levels available, requested level {level}\");\n            }\n            current_id = node.inputs[0];\n        }\n        circuit.nodes.get(&current_id).expect(\"Node not found\")\n    }\n\n    // Helper function for tests to execute circuit and extract the Delta result\n    #[cfg(test)]\n    fn test_execute(\n        circuit: &mut DbspCircuit,\n        inputs: HashMap<String, Delta>,\n        pager: Arc<Pager>,\n    ) -> Result<Delta> {\n        let mut execute_state = ExecuteState::Init {\n            input_data: DeltaSet::from_map(inputs),\n        };\n        match circuit.execute(pager, &mut execute_state)? {\n            IOResult::Done(delta) => Ok(delta),\n            IOResult::IO(_) => panic!(\"Unexpected I/O in test\"),\n        }\n    }\n\n    // Helper to get the committed BTree state from main_data_root\n    // This reads the actual persisted data from the BTree\n    #[cfg(test)]\n    fn get_current_state(pager: Arc<Pager>, circuit: &DbspCircuit) -> Result<Delta> {\n        use crate::storage::btree::CursorTrait;\n\n        let mut delta = Delta::new();\n\n        let main_data_root = circuit.main_data_root;\n        let num_columns = circuit.output_schema.columns.len() + 1;\n\n        // Create a cursor to read the btree\n        let mut btree_cursor = BTreeCursor::new_table(pager.clone(), main_data_root, num_columns);\n\n        // Rewind to the beginning\n        pager.io.block(|| btree_cursor.rewind())?;\n\n        // Read all rows from the BTree\n        loop {\n            // Check if cursor is empty (no more rows)\n            if btree_cursor.is_empty() {\n                break;\n            }\n\n            // Get the rowid\n            let rowid = pager.io.block(|| btree_cursor.rowid()).unwrap().unwrap();\n\n            // Get the record at this position\n            let record = loop {\n                match btree_cursor.record().unwrap() {\n                    IOResult::Done(r) => break r,\n                    IOResult::IO(io) => io.wait(&*pager.io).unwrap(),\n                }\n            }\n            .unwrap()\n            .to_owned();\n\n            let num_data_columns = record.column_count() - 1;\n\n            let mut values = Vec::with_capacity(num_data_columns);\n            let mut values_iter = record.iter()?;\n\n            for _ in 0..num_data_columns {\n                let value = values_iter.next().expect(\"we already checked bounds\")?;\n                values.push(value.to_owned());\n            }\n\n            delta.insert(rowid, values);\n            pager.io.block(|| btree_cursor.next()).unwrap();\n        }\n        Ok(delta)\n    }\n\n    #[test]\n    fn test_simple_projection() {\n        let (circuit, _) = compile_sql!(\"SELECT name FROM users\");\n\n        // Circuit has 2 nodes with Projection at root\n        assert_circuit!(circuit, depth: 2, root: Projection);\n\n        // Verify operators at each level\n        assert_operator!(circuit, 0, Projection { columns: [\"name\"] });\n        assert_operator!(circuit, 1, Input { name: \"users\" });\n    }\n\n    #[test]\n    fn test_filter_with_projection() {\n        let (circuit, _) = compile_sql!(\"SELECT name FROM users WHERE age > 18\");\n\n        // Circuit has 3 nodes with Projection at root\n        assert_circuit!(circuit, depth: 3, root: Projection);\n\n        // Verify operators at each level\n        assert_operator!(circuit, 0, Projection { columns: [\"name\"] });\n        assert_operator!(circuit, 1, Filter);\n        assert_filter_predicate!(circuit, 1, \"age\" > 18);\n        assert_operator!(circuit, 2, Input { name: \"users\" });\n    }\n\n    #[test]\n    fn test_select_star() {\n        let (mut circuit, pager) = compile_sql!(\"SELECT * FROM users\");\n\n        // Create test data\n        let mut input_delta = Delta::new();\n        input_delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Alice\".into()),\n                Value::from_i64(25),\n            ],\n        );\n        input_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Bob\".into()),\n                Value::from_i64(17),\n            ],\n        );\n\n        // Create input map\n        let mut inputs = HashMap::default();\n        inputs.insert(\"users\".to_string(), input_delta);\n\n        let result = test_execute(&mut circuit, inputs.clone(), pager.clone()).unwrap();\n        pager\n            .io\n            .block(|| circuit.commit(inputs.clone(), pager.clone()))\n            .unwrap();\n\n        // Should have all rows with all columns\n        assert_eq!(result.changes.len(), 2);\n\n        // Verify both rows are present with all columns\n        for (row, weight) in &result.changes {\n            assert_eq!(*weight, 1);\n            assert_eq!(row.values.len(), 3); // id, name, age\n        }\n    }\n\n    #[test]\n    fn test_execute_filter() {\n        let (mut circuit, pager) = compile_sql!(\"SELECT * FROM users WHERE age > 18\");\n\n        // Create test data\n        let mut input_delta = Delta::new();\n        input_delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Alice\".into()),\n                Value::from_i64(25),\n            ],\n        );\n        input_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Bob\".into()),\n                Value::from_i64(17),\n            ],\n        );\n        input_delta.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::Text(\"Charlie\".into()),\n                Value::from_i64(30),\n            ],\n        );\n\n        // Create input map\n        let mut inputs = HashMap::default();\n        inputs.insert(\"users\".to_string(), input_delta);\n\n        let result = test_execute(&mut circuit, inputs.clone(), pager.clone()).unwrap();\n        pager\n            .io\n            .block(|| circuit.commit(inputs.clone(), pager.clone()))\n            .unwrap();\n\n        // Should only have Alice and Charlie (age > 18)\n        assert_eq!(\n            result.changes.len(),\n            2,\n            \"Expected 2 rows after filtering, got {}\",\n            result.changes.len()\n        );\n\n        // Check that the filtered rows are correct\n        let names: Vec<String> = result\n            .changes\n            .iter()\n            .filter_map(|(row, weight)| {\n                if *weight > 0 && row.values.len() > 1 {\n                    if let Value::Text(name) = &row.values[1] {\n                        Some(name.to_string())\n                    } else {\n                        None\n                    }\n                } else {\n                    None\n                }\n            })\n            .collect();\n\n        assert!(\n            names.contains(&\"Alice\".to_string()),\n            \"Alice should be in results\"\n        );\n        assert!(\n            names.contains(&\"Charlie\".to_string()),\n            \"Charlie should be in results\"\n        );\n        assert!(\n            !names.contains(&\"Bob\".to_string()),\n            \"Bob should not be in results\"\n        );\n    }\n\n    #[test]\n    fn test_simple_column_projection() {\n        let (mut circuit, pager) = compile_sql!(\"SELECT name, age FROM users\");\n\n        // Create test data\n        let mut input_delta = Delta::new();\n        input_delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Alice\".into()),\n                Value::from_i64(25),\n            ],\n        );\n        input_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Bob\".into()),\n                Value::from_i64(17),\n            ],\n        );\n\n        // Create input map\n        let mut inputs = HashMap::default();\n        inputs.insert(\"users\".to_string(), input_delta);\n\n        let result = test_execute(&mut circuit, inputs.clone(), pager.clone()).unwrap();\n        pager\n            .io\n            .block(|| circuit.commit(inputs.clone(), pager.clone()))\n            .unwrap();\n\n        // Should have all rows but only 2 columns (name, age)\n        assert_eq!(result.changes.len(), 2);\n\n        for (row, _) in &result.changes {\n            assert_eq!(row.values.len(), 2); // Only name and age\n                                             // First value should be name (Text)\n            assert!(matches!(&row.values[0], Value::Text(_)));\n            // Second value should be age (Integer)\n            assert!(matches!(\n                &row.values[1],\n                Value::Numeric(Numeric::Integer(_))\n            ));\n        }\n    }\n\n    #[test]\n    fn test_simple_aggregation() {\n        // Test COUNT(*) with GROUP BY\n        let (mut circuit, pager) = compile_sql!(\"SELECT age, COUNT(*) FROM users GROUP BY age\");\n\n        // Create test data\n        let mut input_delta = Delta::new();\n        input_delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Alice\".into()),\n                Value::from_i64(25),\n            ],\n        );\n        input_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Bob\".into()),\n                Value::from_i64(25),\n            ],\n        );\n        input_delta.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::Text(\"Charlie\".into()),\n                Value::from_i64(30),\n            ],\n        );\n\n        // Create input map\n        let mut inputs = HashMap::default();\n        inputs.insert(\"users\".to_string(), input_delta);\n\n        let result = test_execute(&mut circuit, inputs.clone(), pager.clone()).unwrap();\n        pager\n            .io\n            .block(|| circuit.commit(inputs.clone(), pager.clone()))\n            .unwrap();\n\n        // Should have 2 groups: age 25 with count 2, age 30 with count 1\n        assert_eq!(result.changes.len(), 2);\n\n        // Check the results\n        let mut found_25 = false;\n        let mut found_30 = false;\n\n        for (row, weight) in &result.changes {\n            assert_eq!(*weight, 1);\n            assert_eq!(row.values.len(), 2); // age, count\n\n            if let (\n                Value::Numeric(Numeric::Integer(age)),\n                Value::Numeric(Numeric::Integer(count)),\n            ) = (&row.values[0], &row.values[1])\n            {\n                if *age == 25 {\n                    assert_eq!(*count, 2, \"Age 25 should have count 2\");\n                    found_25 = true;\n                } else if *age == 30 {\n                    assert_eq!(*count, 1, \"Age 30 should have count 1\");\n                    found_30 = true;\n                }\n            }\n        }\n\n        assert!(found_25, \"Should have group for age 25\");\n        assert!(found_30, \"Should have group for age 30\");\n    }\n\n    #[test]\n    fn test_sum_aggregation() {\n        // Test SUM with GROUP BY\n        let (mut circuit, pager) = compile_sql!(\"SELECT name, SUM(age) FROM users GROUP BY name\");\n\n        // Create test data - some names appear multiple times\n        let mut input_delta = Delta::new();\n        input_delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Alice\".into()),\n                Value::from_i64(25),\n            ],\n        );\n        input_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Alice\".into()),\n                Value::from_i64(30),\n            ],\n        );\n        input_delta.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::Text(\"Bob\".into()),\n                Value::from_i64(20),\n            ],\n        );\n\n        // Create input map\n        let mut inputs = HashMap::default();\n        inputs.insert(\"users\".to_string(), input_delta);\n\n        let result = test_execute(&mut circuit, inputs.clone(), pager.clone()).unwrap();\n        pager\n            .io\n            .block(|| circuit.commit(inputs.clone(), pager.clone()))\n            .unwrap();\n\n        // Should have 2 groups: Alice with sum 55, Bob with sum 20\n        assert_eq!(result.changes.len(), 2);\n\n        for (row, weight) in &result.changes {\n            assert_eq!(*weight, 1);\n            assert_eq!(row.values.len(), 2); // name, sum\n\n            if let (Value::Text(name), Value::Numeric(Numeric::Float(sum))) =\n                (&row.values[0], &row.values[1])\n            {\n                if name.as_str() == \"Alice\" {\n                    assert_eq!(*sum, 55.0, \"Alice should have sum 55\");\n                } else if name.as_str() == \"Bob\" {\n                    assert_eq!(*sum, 20.0, \"Bob should have sum 20\");\n                }\n            }\n        }\n    }\n\n    #[test]\n    fn test_aggregation_without_group_by() {\n        // Test aggregation without GROUP BY - should produce a single row\n        let (mut circuit, pager) = compile_sql!(\"SELECT COUNT(*), SUM(age), AVG(age) FROM users\");\n\n        // Create test data\n        let mut input_delta = Delta::new();\n        input_delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Alice\".into()),\n                Value::from_i64(25),\n            ],\n        );\n        input_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Bob\".into()),\n                Value::from_i64(30),\n            ],\n        );\n        input_delta.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::Text(\"Charlie\".into()),\n                Value::from_i64(20),\n            ],\n        );\n\n        // Create input map\n        let mut inputs = HashMap::default();\n        inputs.insert(\"users\".to_string(), input_delta);\n\n        let result = test_execute(&mut circuit, inputs.clone(), pager.clone()).unwrap();\n        pager\n            .io\n            .block(|| circuit.commit(inputs.clone(), pager.clone()))\n            .unwrap();\n\n        // Should have exactly 1 row with all aggregates\n        assert_eq!(\n            result.changes.len(),\n            1,\n            \"Should have exactly one result row\"\n        );\n\n        let (row, weight) = result.changes.first().unwrap();\n        assert_eq!(*weight, 1);\n        assert_eq!(row.values.len(), 3); // count, sum, avg\n\n        // Check aggregate results\n        // COUNT should be Integer\n        if let Value::Numeric(Numeric::Integer(count)) = &row.values[0] {\n            assert_eq!(*count, 3, \"COUNT(*) should be 3\");\n        } else {\n            panic!(\"COUNT should be Integer, got {:?}\", row.values[0]);\n        }\n\n        // SUM can be Integer (if whole number) or Float\n        match &row.values[1] {\n            Value::Numeric(Numeric::Integer(sum)) => assert_eq!(*sum, 75, \"SUM(age) should be 75\"),\n            Value::Numeric(Numeric::Float(sum)) => {\n                assert_eq!(f64::from(*sum), 75.0, \"SUM(age) should be 75.0\")\n            }\n            other => panic!(\"SUM should be Integer or Float, got {other:?}\"),\n        }\n\n        // AVG should be Float\n        if let Value::Numeric(Numeric::Float(avg)) = &row.values[2] {\n            assert_eq!(f64::from(*avg), 25.0, \"AVG(age) should be 25.0\");\n        } else {\n            panic!(\"AVG should be Float, got {:?}\", row.values[2]);\n        }\n    }\n\n    #[test]\n    fn test_expression_projection_execution() {\n        // Test that complex expressions work through VDBE compilation\n        let (mut circuit, pager) = compile_sql!(\"SELECT hex(id) FROM users\");\n\n        // Create test data\n        let mut input_delta = Delta::new();\n        input_delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Alice\".into()),\n                Value::from_i64(25),\n            ],\n        );\n        input_delta.insert(\n            2,\n            vec![\n                Value::from_i64(255),\n                Value::Text(\"Bob\".into()),\n                Value::from_i64(17),\n            ],\n        );\n\n        // Create input map\n        let mut inputs = HashMap::default();\n        inputs.insert(\"users\".to_string(), input_delta);\n\n        let result = test_execute(&mut circuit, inputs.clone(), pager.clone()).unwrap();\n        pager\n            .io\n            .block(|| circuit.commit(inputs.clone(), pager.clone()))\n            .unwrap();\n\n        assert_eq!(result.changes.len(), 2);\n\n        let hex_values: HashMap<i64, String> = result\n            .changes\n            .iter()\n            .map(|(row, _)| {\n                let rowid = row.rowid;\n                if let Value::Text(text) = &row.values[0] {\n                    (rowid, text.to_string())\n                } else {\n                    panic!(\"Expected Text value for hex() result\");\n                }\n            })\n            .collect();\n\n        assert_eq!(\n            hex_values.get(&1).unwrap(),\n            \"31\",\n            \"hex(1) should return '31' (hex of ASCII '1')\"\n        );\n\n        assert_eq!(\n            hex_values.get(&2).unwrap(),\n            \"323535\",\n            \"hex(255) should return '323535' (hex of ASCII '2', '5', '5')\"\n        );\n    }\n\n    // TODO: This test currently fails on incremental updates.\n    // The initial execution works correctly, but incremental updates produce\n    // incorrect results (3 changes instead of 2, with wrong values).\n    // This tests that the aggregate operator correctly handles incremental\n    // updates when it's sandwiched between projection operators.\n    #[test]\n    fn test_projection_aggregation_projection_pattern() {\n        // Test pattern: projection -> aggregation -> projection\n        // Query: SELECT HEX(SUM(age + 2)) FROM users\n        let (mut circuit, pager) = compile_sql!(\"SELECT HEX(SUM(age + 2)) FROM users\");\n\n        // Initial input data\n        let mut input_delta = Delta::new();\n        input_delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Alice\".to_string().into()),\n                Value::from_i64(25),\n            ],\n        );\n        input_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Bob\".to_string().into()),\n                Value::from_i64(30),\n            ],\n        );\n        input_delta.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::Text(\"Charlie\".to_string().into()),\n                Value::from_i64(35),\n            ],\n        );\n\n        let mut input_data = HashMap::default();\n        input_data.insert(\"users\".to_string(), input_delta);\n\n        let result = test_execute(&mut circuit, input_data.clone(), pager.clone()).unwrap();\n        pager\n            .io\n            .block(|| circuit.commit(input_data.clone(), pager.clone()))\n            .unwrap();\n\n        // Expected: SUM(age + 2) = (25+2) + (30+2) + (35+2) = 27 + 32 + 37 = 96\n        // HEX(96) should be the hex representation of the string \"96\" = \"3936\"\n        assert_eq!(result.changes.len(), 1);\n        let (row, _weight) = &result.changes[0];\n        assert_eq!(row.values.len(), 1);\n\n        // The hex function converts the number to string first, then to hex\n        // SUM now returns Float, so 96.0 as string is \"96.0\", which in hex is \"39362E30\"\n        // (hex of ASCII '9', '6', '.', '0')\n        assert_eq!(\n            row.values[0],\n            Value::Text(\"39362E30\".to_string().into()),\n            \"HEX(SUM(age + 2)) should return '39362E30' for sum of 96.0\"\n        );\n\n        // Test incremental update: add a new user\n        let mut input_delta = Delta::new();\n        input_delta.insert(\n            4,\n            vec![\n                Value::from_i64(4),\n                Value::Text(\"David\".to_string().into()),\n                Value::from_i64(40),\n            ],\n        );\n\n        let mut input_data = HashMap::default();\n        input_data.insert(\"users\".to_string(), input_delta);\n\n        let result = test_execute(&mut circuit, input_data, pager).unwrap();\n\n        // Expected: new SUM(age + 2) = 96.0 + (40+2) = 138.0\n        // HEX(138.0) = hex of \"138.0\" = \"3133382E30\"\n        assert_eq!(result.changes.len(), 2);\n\n        // First change: remove old aggregate (96.0)\n        let (row, weight) = &result.changes[0];\n        assert_eq!(*weight, -1);\n        assert_eq!(row.values[0], Value::Text(\"39362E30\".to_string().into()));\n\n        // Second change: add new aggregate (138.0)\n        let (row, weight) = &result.changes[1];\n        assert_eq!(*weight, 1);\n        assert_eq!(\n            row.values[0],\n            Value::Text(\"3133382E30\".to_string().into()),\n            \"HEX(SUM(age + 2)) should return '3133382E30' for sum of 138.0\"\n        );\n    }\n\n    #[test]\n    fn test_nested_projection_with_groupby() {\n        // Test pattern: projection -> aggregation with GROUP BY -> projection\n        // Query: SELECT name, HEX(SUM(age * 2)) FROM users GROUP BY name\n        let (mut circuit, pager) =\n            compile_sql!(\"SELECT name, HEX(SUM(age * 2)) FROM users GROUP BY name\");\n\n        // Initial input data\n        let mut input_delta = Delta::new();\n        input_delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Alice\".to_string().into()),\n                Value::from_i64(25),\n            ],\n        );\n        input_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Bob\".to_string().into()),\n                Value::from_i64(30),\n            ],\n        );\n        input_delta.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::Text(\"Alice\".to_string().into()),\n                Value::from_i64(35),\n            ],\n        );\n\n        let mut input_data = HashMap::default();\n        input_data.insert(\"users\".to_string(), input_delta);\n\n        let result = test_execute(&mut circuit, input_data.clone(), pager.clone()).unwrap();\n        pager\n            .io\n            .block(|| circuit.commit(input_data.clone(), pager.clone()))\n            .unwrap();\n\n        // Expected results:\n        // Alice: SUM(25*2 + 35*2) = 50 + 70 = 120.0, HEX(\"120.0\") = \"3132302E30\"\n        // Bob: SUM(30*2) = 60.0, HEX(\"60.0\") = \"36302E30\"\n        assert_eq!(result.changes.len(), 2);\n\n        let results: HashMap<String, String> = result\n            .changes\n            .iter()\n            .map(|(row, _weight)| {\n                let name = match &row.values[0] {\n                    Value::Text(t) => t.to_string(),\n                    _ => panic!(\"Expected text for name\"),\n                };\n                let hex_sum = match &row.values[1] {\n                    Value::Text(t) => t.to_string(),\n                    _ => panic!(\"Expected text for hex value\"),\n                };\n                (name, hex_sum)\n            })\n            .collect();\n\n        assert_eq!(\n            results.get(\"Alice\").unwrap(),\n            \"3132302E30\",\n            \"Alice's HEX(SUM(age * 2)) should be '3132302E30' (120.0)\"\n        );\n        assert_eq!(\n            results.get(\"Bob\").unwrap(),\n            \"36302E30\",\n            \"Bob's HEX(SUM(age * 2)) should be '36302E30' (60.0)\"\n        );\n    }\n\n    #[test]\n    fn test_transaction_context() {\n        // Test that uncommitted changes are visible within a transaction\n        // but don't affect the operator's internal state\n        let (mut circuit, pager) = compile_sql!(\"SELECT * FROM users WHERE age > 18\");\n\n        // Initialize with some data\n        let mut init_data = HashMap::default();\n        let mut delta = Delta::new();\n        delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Alice\".into()),\n                Value::from_i64(25),\n            ],\n        );\n        delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Bob\".into()),\n                Value::from_i64(17),\n            ],\n        );\n        init_data.insert(\"users\".to_string(), delta);\n\n        let _ = test_execute(&mut circuit, init_data.clone(), pager.clone()).unwrap();\n        let state = pager\n            .io\n            .block(|| circuit.commit(init_data.clone(), pager.clone()))\n            .unwrap();\n\n        // Verify initial delta : only Alice (age > 18)\n        assert_eq!(state.changes.len(), 1);\n        assert_eq!(state.changes[0].0.values[1], Value::Text(\"Alice\".into()));\n\n        // Create uncommitted changes that would be visible in a transaction\n        let mut uncommitted = HashMap::default();\n        let mut uncommitted_delta = Delta::new();\n        // Add Charlie (age 30) - should be visible in transaction\n        uncommitted_delta.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::Text(\"Charlie\".into()),\n                Value::from_i64(30),\n            ],\n        );\n        // Add David (age 15) - should NOT be visible (filtered out)\n        uncommitted_delta.insert(\n            4,\n            vec![\n                Value::from_i64(4),\n                Value::Text(\"David\".into()),\n                Value::from_i64(15),\n            ],\n        );\n        uncommitted.insert(\"users\".to_string(), uncommitted_delta);\n\n        // Execute with uncommitted data - this simulates processing the uncommitted changes\n        // through the circuit to see what would be visible\n        let tx_result = test_execute(&mut circuit, uncommitted.clone(), pager.clone()).unwrap();\n\n        // The result should show Charlie being added (passes filter, age > 18)\n        // David is filtered out (age 15 < 18)\n        assert_eq!(tx_result.changes.len(), 1, \"Should see Charlie added\");\n        assert_eq!(\n            tx_result.changes[0].0.values[1],\n            Value::Text(\"Charlie\".into())\n        );\n\n        // Now actually commit Charlie (without uncommitted context)\n        let mut commit_data = HashMap::default();\n        let mut commit_delta = Delta::new();\n        commit_delta.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::Text(\"Charlie\".into()),\n                Value::from_i64(30),\n            ],\n        );\n        commit_data.insert(\"users\".to_string(), commit_delta);\n\n        let commit_result = test_execute(&mut circuit, commit_data.clone(), pager.clone()).unwrap();\n\n        // The commit result should show Charlie being added\n        assert_eq!(commit_result.changes.len(), 1, \"Should see Charlie added\");\n        assert_eq!(\n            commit_result.changes[0].0.values[1],\n            Value::Text(\"Charlie\".into())\n        );\n\n        // Commit the change to make it permanent\n        pager\n            .io\n            .block(|| circuit.commit(commit_data.clone(), pager.clone()))\n            .unwrap();\n\n        // Now if we execute again with no changes, we should see no delta\n        let empty_result = test_execute(&mut circuit, HashMap::default(), pager).unwrap();\n        assert_eq!(empty_result.changes.len(), 0, \"No changes when no new data\");\n    }\n\n    #[test]\n    fn test_uncommitted_delete() {\n        // Test that uncommitted deletes are handled correctly without affecting operator state\n        let (mut circuit, pager) = compile_sql!(\"SELECT * FROM users WHERE age > 18\");\n\n        // Initialize with some data\n        let mut init_data = HashMap::default();\n        let mut delta = Delta::new();\n        delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Alice\".into()),\n                Value::from_i64(25),\n            ],\n        );\n        delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Bob\".into()),\n                Value::from_i64(30),\n            ],\n        );\n        delta.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::Text(\"Charlie\".into()),\n                Value::from_i64(20),\n            ],\n        );\n        init_data.insert(\"users\".to_string(), delta);\n\n        let _ = test_execute(&mut circuit, init_data.clone(), pager.clone()).unwrap();\n        let state = pager\n            .io\n            .block(|| circuit.commit(init_data.clone(), pager.clone()))\n            .unwrap();\n\n        // Verify initial delta: Alice, Bob, Charlie (all age > 18)\n        assert_eq!(state.changes.len(), 3);\n\n        // Create uncommitted delete for Bob\n        let mut uncommitted = HashMap::default();\n        let mut uncommitted_delta = Delta::new();\n        uncommitted_delta.delete(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Bob\".into()),\n                Value::from_i64(30),\n            ],\n        );\n        uncommitted.insert(\"users\".to_string(), uncommitted_delta);\n\n        // Execute with uncommitted delete\n        let tx_result = test_execute(&mut circuit, uncommitted.clone(), pager.clone()).unwrap();\n\n        // Result should show the deleted row that passed the filter\n        assert_eq!(\n            tx_result.changes.len(),\n            1,\n            \"Should see the uncommitted delete\"\n        );\n\n        // Verify operator's internal state is unchanged (still has all 3 users)\n        let state_after = get_current_state(pager.clone(), &circuit).unwrap();\n        assert_eq!(\n            state_after.changes.len(),\n            3,\n            \"Internal state should still have all 3 users\"\n        );\n\n        // Now actually commit the delete\n        let mut commit_data = HashMap::default();\n        let mut commit_delta = Delta::new();\n        commit_delta.delete(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Bob\".into()),\n                Value::from_i64(30),\n            ],\n        );\n        commit_data.insert(\"users\".to_string(), commit_delta);\n\n        let commit_result = test_execute(&mut circuit, commit_data.clone(), pager.clone()).unwrap();\n\n        // Actually commit the delete to update operator state\n        pager\n            .io\n            .block(|| circuit.commit(commit_data.clone(), pager.clone()))\n            .unwrap();\n\n        // The commit result should show Bob being deleted\n        assert_eq!(commit_result.changes.len(), 1, \"Should see Bob deleted\");\n        assert_eq!(\n            commit_result.changes[0].1, -1,\n            \"Delete should have weight -1\"\n        );\n        assert_eq!(\n            commit_result.changes[0].0.values[1],\n            Value::Text(\"Bob\".into())\n        );\n\n        // After commit, internal state should have only Alice and Charlie\n        let final_state = get_current_state(pager, &circuit).unwrap();\n        assert_eq!(\n            final_state.changes.len(),\n            2,\n            \"After commit, should have Alice and Charlie\"\n        );\n\n        let names: Vec<String> = final_state\n            .changes\n            .iter()\n            .map(|(row, _)| {\n                if let Value::Text(name) = &row.values[1] {\n                    name.to_string()\n                } else {\n                    panic!(\"Expected text value\");\n                }\n            })\n            .collect();\n        assert!(names.contains(&\"Alice\".to_string()));\n        assert!(names.contains(&\"Charlie\".to_string()));\n        assert!(!names.contains(&\"Bob\".to_string()));\n    }\n\n    #[test]\n    fn test_uncommitted_update() {\n        // Test that uncommitted updates (delete + insert) are handled correctly\n        let (mut circuit, pager) = compile_sql!(\"SELECT * FROM users WHERE age > 18\");\n\n        // Initialize with some data\n        let mut init_data = HashMap::default();\n        let mut delta = Delta::new();\n        delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Alice\".into()),\n                Value::from_i64(25),\n            ],\n        );\n        delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Bob\".into()),\n                Value::from_i64(17),\n            ],\n        ); // Bob is 17, filtered out\n        init_data.insert(\"users\".to_string(), delta);\n\n        let _ = test_execute(&mut circuit, init_data.clone(), pager.clone()).unwrap();\n        pager\n            .io\n            .block(|| circuit.commit(init_data.clone(), pager.clone()))\n            .unwrap();\n\n        // Create uncommitted update: Bob turns 19 (update from 17 to 19)\n        // This is modeled as delete + insert\n        let mut uncommitted = HashMap::default();\n        let mut uncommitted_delta = Delta::new();\n        uncommitted_delta.delete(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Bob\".into()),\n                Value::from_i64(17),\n            ],\n        );\n        uncommitted_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Bob\".into()),\n                Value::from_i64(19),\n            ],\n        );\n        uncommitted.insert(\"users\".to_string(), uncommitted_delta);\n\n        // Execute with uncommitted update\n        let tx_result = test_execute(&mut circuit, uncommitted.clone(), pager.clone()).unwrap();\n\n        // Bob should now appear in the result (age 19 > 18)\n        // Consolidate to see the final state\n        let mut final_result = tx_result;\n        final_result.consolidate();\n\n        assert_eq!(final_result.changes.len(), 1, \"Bob should now be in view\");\n        assert_eq!(\n            final_result.changes[0].0.values[1],\n            Value::Text(\"Bob\".into())\n        );\n        assert_eq!(final_result.changes[0].0.values[2], Value::from_i64(19));\n\n        // Now actually commit the update\n        let mut commit_data = HashMap::default();\n        let mut commit_delta = Delta::new();\n        commit_delta.delete(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Bob\".into()),\n                Value::from_i64(17),\n            ],\n        );\n        commit_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Bob\".into()),\n                Value::from_i64(19),\n            ],\n        );\n        commit_data.insert(\"users\".to_string(), commit_delta);\n\n        // Commit the update\n        pager\n            .io\n            .block(|| circuit.commit(commit_data.clone(), pager.clone()))\n            .unwrap();\n\n        // After committing, Bob should be in the view's state\n        let state = get_current_state(pager, &circuit).unwrap();\n        let mut consolidated_state = state;\n        consolidated_state.consolidate();\n\n        // Should have both Alice and Bob now\n        assert_eq!(\n            consolidated_state.changes.len(),\n            2,\n            \"Should have Alice and Bob\"\n        );\n\n        let names: Vec<String> = consolidated_state\n            .changes\n            .iter()\n            .map(|(row, _)| {\n                if let Value::Text(name) = &row.values[1] {\n                    name.as_str().to_string()\n                } else {\n                    panic!(\"Expected text value\");\n                }\n            })\n            .collect();\n        assert!(names.contains(&\"Alice\".to_string()));\n        assert!(names.contains(&\"Bob\".to_string()));\n    }\n\n    #[test]\n    fn test_uncommitted_filtered_delete() {\n        // Test deleting a row that doesn't pass the filter\n        let (mut circuit, pager) = compile_sql!(\"SELECT * FROM users WHERE age > 18\");\n\n        // Initialize with mixed data\n        let mut init_data = HashMap::default();\n        let mut delta = Delta::new();\n        delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Alice\".into()),\n                Value::from_i64(25),\n            ],\n        );\n        delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Bob\".into()),\n                Value::from_i64(15),\n            ],\n        ); // Bob doesn't pass filter\n        init_data.insert(\"users\".to_string(), delta);\n\n        let _ = test_execute(&mut circuit, init_data.clone(), pager.clone()).unwrap();\n        pager\n            .io\n            .block(|| circuit.commit(init_data.clone(), pager.clone()))\n            .unwrap();\n\n        // Create uncommitted delete for Bob (who isn't in the view because age=15)\n        let mut uncommitted = HashMap::default();\n        let mut uncommitted_delta = Delta::new();\n        uncommitted_delta.delete(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Bob\".into()),\n                Value::from_i64(15),\n            ],\n        );\n        uncommitted.insert(\"users\".to_string(), uncommitted_delta);\n\n        // Execute with uncommitted delete - should produce no output changes\n        let tx_result = test_execute(&mut circuit, uncommitted, pager.clone()).unwrap();\n\n        // Bob wasn't in the view, so deleting him produces no output\n        assert_eq!(\n            tx_result.changes.len(),\n            0,\n            \"Deleting filtered row produces no changes\"\n        );\n\n        // The view state should still only have Alice\n        let state = get_current_state(pager, &circuit).unwrap();\n        assert_eq!(state.changes.len(), 1, \"View still has only Alice\");\n        assert_eq!(state.changes[0].0.values[1], Value::Text(\"Alice\".into()));\n    }\n\n    #[test]\n    fn test_uncommitted_mixed_operations() {\n        // Test multiple uncommitted operations together\n        let (mut circuit, pager) = compile_sql!(\"SELECT * FROM users WHERE age > 18\");\n\n        // Initialize with some data\n        let mut init_data = HashMap::default();\n        let mut delta = Delta::new();\n        delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Alice\".into()),\n                Value::from_i64(25),\n            ],\n        );\n        delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Bob\".into()),\n                Value::from_i64(30),\n            ],\n        );\n        init_data.insert(\"users\".to_string(), delta);\n\n        let _ = test_execute(&mut circuit, init_data.clone(), pager.clone()).unwrap();\n        pager\n            .io\n            .block(|| circuit.commit(init_data.clone(), pager.clone()))\n            .unwrap();\n\n        // Verify initial state\n        let state = get_current_state(pager.clone(), &circuit).unwrap();\n        assert_eq!(state.changes.len(), 2);\n\n        // Create uncommitted changes:\n        // - Delete Alice\n        // - Update Bob's age to 35\n        // - Insert Charlie (age 40)\n        // - Insert David (age 16, filtered out)\n        let mut uncommitted = HashMap::default();\n        let mut uncommitted_delta = Delta::new();\n        // Delete Alice\n        uncommitted_delta.delete(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Alice\".into()),\n                Value::from_i64(25),\n            ],\n        );\n        // Update Bob (delete + insert)\n        uncommitted_delta.delete(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Bob\".into()),\n                Value::from_i64(30),\n            ],\n        );\n        uncommitted_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Bob\".into()),\n                Value::from_i64(35),\n            ],\n        );\n        // Insert Charlie\n        uncommitted_delta.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::Text(\"Charlie\".into()),\n                Value::from_i64(40),\n            ],\n        );\n        // Insert David (will be filtered)\n        uncommitted_delta.insert(\n            4,\n            vec![\n                Value::from_i64(4),\n                Value::Text(\"David\".into()),\n                Value::from_i64(16),\n            ],\n        );\n        uncommitted.insert(\"users\".to_string(), uncommitted_delta);\n\n        // Execute with uncommitted changes\n        let tx_result = test_execute(&mut circuit, uncommitted.clone(), pager.clone()).unwrap();\n\n        // Result should show all changes: delete Alice, update Bob, insert Charlie and David\n        assert_eq!(\n            tx_result.changes.len(),\n            4,\n            \"Should see all uncommitted mixed operations\"\n        );\n\n        // Verify operator's internal state is unchanged\n        let state_after = get_current_state(pager.clone(), &circuit).unwrap();\n        assert_eq!(state_after.changes.len(), 2, \"Still has Alice and Bob\");\n\n        // Commit all changes\n        let mut commit_data = HashMap::default();\n        let mut commit_delta = Delta::new();\n        commit_delta.delete(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Alice\".into()),\n                Value::from_i64(25),\n            ],\n        );\n        commit_delta.delete(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Bob\".into()),\n                Value::from_i64(30),\n            ],\n        );\n        commit_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Bob\".into()),\n                Value::from_i64(35),\n            ],\n        );\n        commit_delta.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::Text(\"Charlie\".into()),\n                Value::from_i64(40),\n            ],\n        );\n        commit_delta.insert(\n            4,\n            vec![\n                Value::from_i64(4),\n                Value::Text(\"David\".into()),\n                Value::from_i64(16),\n            ],\n        );\n        commit_data.insert(\"users\".to_string(), commit_delta);\n\n        let commit_result = test_execute(&mut circuit, commit_data.clone(), pager.clone()).unwrap();\n\n        // Should see: Alice deleted, Bob deleted, Bob inserted, Charlie inserted\n        // (David filtered out)\n        assert_eq!(commit_result.changes.len(), 4, \"Should see 4 changes\");\n\n        // Actually commit the changes to update operator state\n        pager\n            .io\n            .block(|| circuit.commit(commit_data.clone(), pager.clone()))\n            .unwrap();\n\n        // After all commits, execute with no changes should return empty delta\n        let empty_result = test_execute(&mut circuit, HashMap::default(), pager).unwrap();\n        assert_eq!(empty_result.changes.len(), 0, \"No changes when no new data\");\n    }\n\n    #[test]\n    fn test_uncommitted_aggregation() {\n        // Test that aggregations work correctly with uncommitted changes\n        // This tests the specific scenario where a transaction adds new data\n        // and we need to see correct aggregation results within the transaction\n\n        // Create a sales table schema for testing\n        let _ = test_schema!();\n\n        let (mut circuit, pager) = compile_sql!(\"SELECT product_id, SUM(amount) as total, COUNT(*) as cnt FROM sales GROUP BY product_id\");\n\n        // Initialize with base data: (1, 100), (1, 200), (2, 150), (2, 250)\n        let mut init_data = HashMap::default();\n        let mut delta = Delta::new();\n        delta.insert(1, vec![Value::from_i64(1), Value::from_i64(100)]);\n        delta.insert(2, vec![Value::from_i64(1), Value::from_i64(200)]);\n        delta.insert(3, vec![Value::from_i64(2), Value::from_i64(150)]);\n        delta.insert(4, vec![Value::from_i64(2), Value::from_i64(250)]);\n        init_data.insert(\"sales\".to_string(), delta);\n\n        let _ = test_execute(&mut circuit, init_data.clone(), pager.clone()).unwrap();\n        pager\n            .io\n            .block(|| circuit.commit(init_data.clone(), pager.clone()))\n            .unwrap();\n\n        // Verify initial state: product 1 total=300, product 2 total=400\n        let state = get_current_state(pager.clone(), &circuit).unwrap();\n        assert_eq!(state.changes.len(), 2, \"Should have 2 product groups\");\n\n        // Build a map of product_id -> (total, count)\n        let initial_results: HashMap<i64, (i64, i64)> = state\n            .changes\n            .iter()\n            .map(|(row, _)| {\n                // SUM might return Integer or Float, COUNT returns Integer\n                let product_id = match &row.values[0] {\n                    Value::Numeric(Numeric::Integer(id)) => *id,\n                    _ => panic!(\"Product ID should be Integer, got {:?}\", row.values[0]),\n                };\n\n                let total = match &row.values[1] {\n                    Value::Numeric(Numeric::Integer(t)) => *t,\n                    Value::Numeric(Numeric::Float(t)) => f64::from(*t) as i64,\n                    _ => panic!(\"Total should be numeric, got {:?}\", row.values[1]),\n                };\n\n                let count = match &row.values[2] {\n                    Value::Numeric(Numeric::Integer(c)) => *c,\n                    _ => panic!(\"Count should be Integer, got {:?}\", row.values[2]),\n                };\n\n                (product_id, (total, count))\n            })\n            .collect();\n\n        assert_eq!(\n            initial_results.get(&1).unwrap(),\n            &(300, 2),\n            \"Product 1 should have total=300, count=2\"\n        );\n        assert_eq!(\n            initial_results.get(&2).unwrap(),\n            &(400, 2),\n            \"Product 2 should have total=400, count=2\"\n        );\n\n        // Create uncommitted changes: INSERT (1, 50), (3, 300)\n        let mut uncommitted = HashMap::default();\n        let mut uncommitted_delta = Delta::new();\n        uncommitted_delta.insert(5, vec![Value::from_i64(1), Value::from_i64(50)]); // Add to product 1\n        uncommitted_delta.insert(6, vec![Value::from_i64(3), Value::from_i64(300)]); // New product 3\n        uncommitted.insert(\"sales\".to_string(), uncommitted_delta);\n\n        // Execute with uncommitted data - simulating a read within transaction\n        let tx_result = test_execute(&mut circuit, uncommitted.clone(), pager.clone()).unwrap();\n\n        // Result should show the aggregate changes from uncommitted data\n        // Product 1: retraction of (300, 2) and insertion of (350, 3)\n        // Product 3: insertion of (300, 1) - new product\n        assert_eq!(\n            tx_result.changes.len(),\n            3,\n            \"Should see aggregate changes from uncommitted data\"\n        );\n\n        // IMPORTANT: Verify operator's internal state is unchanged\n        let state_after = get_current_state(pager.clone(), &circuit).unwrap();\n        assert_eq!(\n            state_after.changes.len(),\n            2,\n            \"Internal state should still have 2 groups\"\n        );\n\n        // Verify the internal state still has original values\n        let state_results: HashMap<i64, (i64, i64)> = state_after\n            .changes\n            .iter()\n            .map(|(row, _)| {\n                let product_id = match &row.values[0] {\n                    Value::Numeric(Numeric::Integer(id)) => *id,\n                    _ => panic!(\"Product ID should be Integer\"),\n                };\n\n                let total = match &row.values[1] {\n                    Value::Numeric(Numeric::Integer(t)) => *t,\n                    Value::Numeric(Numeric::Float(t)) => f64::from(*t) as i64,\n                    _ => panic!(\"Total should be numeric\"),\n                };\n\n                let count = match &row.values[2] {\n                    Value::Numeric(Numeric::Integer(c)) => *c,\n                    _ => panic!(\"Count should be Integer\"),\n                };\n\n                (product_id, (total, count))\n            })\n            .collect();\n\n        assert_eq!(\n            state_results.get(&1).unwrap(),\n            &(300, 2),\n            \"Product 1 unchanged\"\n        );\n        assert_eq!(\n            state_results.get(&2).unwrap(),\n            &(400, 2),\n            \"Product 2 unchanged\"\n        );\n        assert!(\n            !state_results.contains_key(&3),\n            \"Product 3 should not be in committed state\"\n        );\n\n        // Now actually commit the changes\n        let mut commit_data = HashMap::default();\n        let mut commit_delta = Delta::new();\n        commit_delta.insert(5, vec![Value::from_i64(1), Value::from_i64(50)]);\n        commit_delta.insert(6, vec![Value::from_i64(3), Value::from_i64(300)]);\n        commit_data.insert(\"sales\".to_string(), commit_delta);\n\n        let commit_result = test_execute(&mut circuit, commit_data.clone(), pager.clone()).unwrap();\n\n        // Should see changes for product 1 (updated) and product 3 (new)\n        assert_eq!(\n            commit_result.changes.len(),\n            3,\n            \"Should see 3 changes (delete old product 1, insert new product 1, insert product 3)\"\n        );\n\n        // Actually commit the changes to update operator state\n        pager\n            .io\n            .block(|| circuit.commit(commit_data.clone(), pager.clone()))\n            .unwrap();\n\n        // After commit, verify final state\n        let final_state = get_current_state(pager, &circuit).unwrap();\n        assert_eq!(\n            final_state.changes.len(),\n            3,\n            \"Should have 3 product groups after commit\"\n        );\n\n        let final_results: HashMap<i64, (i64, i64)> = final_state\n            .changes\n            .iter()\n            .map(|(row, _)| {\n                let product_id = match &row.values[0] {\n                    Value::Numeric(Numeric::Integer(id)) => *id,\n                    _ => panic!(\"Product ID should be Integer\"),\n                };\n\n                let total = match &row.values[1] {\n                    Value::Numeric(Numeric::Integer(t)) => *t,\n                    Value::Numeric(Numeric::Float(t)) => f64::from(*t) as i64,\n                    _ => panic!(\"Total should be numeric\"),\n                };\n\n                let count = match &row.values[2] {\n                    Value::Numeric(Numeric::Integer(c)) => *c,\n                    _ => panic!(\"Count should be Integer\"),\n                };\n\n                (product_id, (total, count))\n            })\n            .collect();\n\n        assert_eq!(\n            final_results.get(&1).unwrap(),\n            &(350, 3),\n            \"Product 1 should have total=350, count=3\"\n        );\n        assert_eq!(\n            final_results.get(&2).unwrap(),\n            &(400, 2),\n            \"Product 2 should have total=400, count=2\"\n        );\n        assert_eq!(\n            final_results.get(&3).unwrap(),\n            &(300, 1),\n            \"Product 3 should have total=300, count=1\"\n        );\n    }\n\n    #[test]\n    fn test_uncommitted_data_visible_in_transaction() {\n        // Test that uncommitted INSERTs are visible within the same transaction\n        // This simulates: BEGIN; INSERT ...; SELECT * FROM view; COMMIT;\n\n        let (mut circuit, pager) = compile_sql!(\"SELECT * FROM users WHERE age > 18\");\n\n        // Initialize with some data - need to match the schema (id, name, age)\n        let mut init_data = HashMap::default();\n        let mut delta = Delta::new();\n        delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Alice\".into()),\n                Value::from_i64(25),\n            ],\n        );\n        delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Bob\".into()),\n                Value::from_i64(30),\n            ],\n        );\n        init_data.insert(\"users\".to_string(), delta);\n\n        let _ = test_execute(&mut circuit, init_data.clone(), pager.clone()).unwrap();\n        pager\n            .io\n            .block(|| circuit.commit(init_data.clone(), pager.clone()))\n            .unwrap();\n\n        // Verify initial state\n        let state = get_current_state(pager.clone(), &circuit).unwrap();\n        assert_eq!(\n            state.len(),\n            2,\n            \"Should have 2 users initially (both pass age > 18 filter)\"\n        );\n\n        // Simulate a transaction: INSERT new users that pass the filter - match schema (id, name, age)\n        let mut uncommitted = HashMap::default();\n        let mut tx_delta = Delta::new();\n        tx_delta.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::Text(\"Charlie\".into()),\n                Value::from_i64(35),\n            ],\n        );\n        tx_delta.insert(\n            4,\n            vec![\n                Value::from_i64(4),\n                Value::Text(\"David\".into()),\n                Value::from_i64(20),\n            ],\n        );\n        uncommitted.insert(\"users\".to_string(), tx_delta);\n\n        // Execute with uncommitted data - this should return the uncommitted changes\n        // that passed through the filter (age > 18)\n        let tx_result = test_execute(&mut circuit, uncommitted.clone(), pager.clone()).unwrap();\n\n        // IMPORTANT: tx_result should contain the filtered uncommitted changes!\n        // Both Charlie (35) and David (20) should pass the age > 18 filter\n        assert_eq!(\n            tx_result.len(),\n            2,\n            \"Should see 2 uncommitted rows that pass filter\"\n        );\n\n        // Verify the uncommitted results contain the expected rows\n        let has_charlie = tx_result.changes.iter().any(|(row, _)| row.rowid == 3);\n        assert!(\n            has_charlie,\n            \"Should find Charlie (rowid=3) in uncommitted results\"\n        );\n\n        let has_david = tx_result.changes.iter().any(|(row, _)| row.rowid == 4);\n        assert!(\n            has_david,\n            \"Should find David (rowid=4) in uncommitted results\"\n        );\n\n        // CRITICAL: Verify the operator state wasn't modified by uncommitted execution\n        let state_after_uncommitted = get_current_state(pager, &circuit).unwrap();\n        assert_eq!(\n            state_after_uncommitted.len(),\n            2,\n            \"State should STILL be 2 after uncommitted execution - only Alice and Bob\"\n        );\n\n        // The state should not contain Charlie or David\n        let has_charlie_in_state = state_after_uncommitted\n            .changes\n            .iter()\n            .any(|(row, _)| row.rowid == 3);\n        let has_david_in_state = state_after_uncommitted\n            .changes\n            .iter()\n            .any(|(row, _)| row.rowid == 4);\n        assert!(\n            !has_charlie_in_state,\n            \"Charlie should NOT be in operator state (uncommitted)\"\n        );\n        assert!(\n            !has_david_in_state,\n            \"David should NOT be in operator state (uncommitted)\"\n        );\n    }\n\n    #[test]\n    fn test_uncommitted_aggregation_with_rollback() {\n        // Test that rollback properly discards uncommitted aggregation changes\n        // Similar to test_uncommitted_aggregation but explicitly tests rollback semantics\n\n        // Create a simple aggregation circuit\n        let (mut circuit, pager) =\n            compile_sql!(\"SELECT age, COUNT(*) as cnt FROM users GROUP BY age\");\n\n        // Initialize with some data\n        let mut init_data = HashMap::default();\n        let mut delta = Delta::new();\n        delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Alice\".into()),\n                Value::from_i64(25),\n            ],\n        );\n        delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Bob\".into()),\n                Value::from_i64(30),\n            ],\n        );\n        delta.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::Text(\"Charlie\".into()),\n                Value::from_i64(25),\n            ],\n        );\n        delta.insert(\n            4,\n            vec![\n                Value::from_i64(4),\n                Value::Text(\"David\".into()),\n                Value::from_i64(30),\n            ],\n        );\n        init_data.insert(\"users\".to_string(), delta);\n\n        let _ = test_execute(&mut circuit, init_data.clone(), pager.clone()).unwrap();\n        pager\n            .io\n            .block(|| circuit.commit(init_data.clone(), pager.clone()))\n            .unwrap();\n\n        // Verify initial state: age 25 count=2, age 30 count=2\n        let state = get_current_state(pager.clone(), &circuit).unwrap();\n        assert_eq!(state.changes.len(), 2);\n\n        let initial_counts: HashMap<i64, i64> = state\n            .changes\n            .iter()\n            .map(|(row, _)| {\n                if let (\n                    Value::Numeric(Numeric::Integer(age)),\n                    Value::Numeric(Numeric::Integer(count)),\n                ) = (&row.values[0], &row.values[1])\n                {\n                    (*age, *count)\n                } else {\n                    panic!(\"Unexpected value types\");\n                }\n            })\n            .collect();\n\n        assert_eq!(initial_counts.get(&25).unwrap(), &2);\n        assert_eq!(initial_counts.get(&30).unwrap(), &2);\n\n        // Create uncommitted changes that would affect aggregations\n        let mut uncommitted = HashMap::default();\n        let mut uncommitted_delta = Delta::new();\n        // Add more people aged 25\n        uncommitted_delta.insert(\n            5,\n            vec![\n                Value::from_i64(5),\n                Value::Text(\"Eve\".into()),\n                Value::from_i64(25),\n            ],\n        );\n        uncommitted_delta.insert(\n            6,\n            vec![\n                Value::from_i64(6),\n                Value::Text(\"Frank\".into()),\n                Value::from_i64(25),\n            ],\n        );\n        // Add person aged 35 (new group)\n        uncommitted_delta.insert(\n            7,\n            vec![\n                Value::from_i64(7),\n                Value::Text(\"Grace\".into()),\n                Value::from_i64(35),\n            ],\n        );\n        // Delete Bob (age 30)\n        uncommitted_delta.delete(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Bob\".into()),\n                Value::from_i64(30),\n            ],\n        );\n        uncommitted.insert(\"users\".to_string(), uncommitted_delta);\n\n        // Execute with uncommitted changes\n        let tx_result = test_execute(&mut circuit, uncommitted.clone(), pager.clone()).unwrap();\n\n        // Should see the aggregate changes from uncommitted data\n        // Age 25: retraction of count 1 and insertion of count 2\n        // Age 30: insertion of count 1 (Bob is new for age 30)\n        assert!(\n            !tx_result.changes.is_empty(),\n            \"Should see aggregate changes from uncommitted data\"\n        );\n\n        // Verify internal state is unchanged (simulating rollback by not committing)\n        let state_after_rollback = get_current_state(pager, &circuit).unwrap();\n        assert_eq!(\n            state_after_rollback.changes.len(),\n            2,\n            \"Should still have 2 age groups\"\n        );\n\n        let rollback_counts: HashMap<i64, i64> = state_after_rollback\n            .changes\n            .iter()\n            .map(|(row, _)| {\n                if let (\n                    Value::Numeric(Numeric::Integer(age)),\n                    Value::Numeric(Numeric::Integer(count)),\n                ) = (&row.values[0], &row.values[1])\n                {\n                    (*age, *count)\n                } else {\n                    panic!(\"Unexpected value types\");\n                }\n            })\n            .collect();\n\n        // Verify counts are unchanged after rollback\n        assert_eq!(\n            rollback_counts.get(&25).unwrap(),\n            &2,\n            \"Age 25 count unchanged\"\n        );\n        assert_eq!(\n            rollback_counts.get(&30).unwrap(),\n            &2,\n            \"Age 30 count unchanged\"\n        );\n        assert!(\n            !rollback_counts.contains_key(&35),\n            \"Age 35 should not exist\"\n        );\n    }\n\n    #[test]\n    fn test_circuit_rowid_update_consolidation() {\n        let (pager, p1, p2, p3) = setup_btree_for_circuit();\n\n        // Test that circuit properly consolidates state when rowid changes\n        let mut circuit = DbspCircuit::new(p1, p2, p3);\n\n        // Create a simple filter node\n        let schema = Arc::new(LogicalSchema::new(vec![\n            ColumnInfo {\n                name: \"id\".to_string(),\n                ty: Type::Integer,\n                database: None,\n                table: None,\n                table_alias: None,\n            },\n            ColumnInfo {\n                name: \"value\".to_string(),\n                ty: Type::Integer,\n                database: None,\n                table: None,\n                table_alias: None,\n            },\n        ]));\n\n        // First create an input node with InputOperator\n        let input_id = circuit.add_node(\n            DbspOperator::Input {\n                name: \"test\".to_string(),\n                schema: schema.clone(),\n            },\n            vec![],\n            Box::new(InputOperator::new(\"test\".to_string())),\n        );\n\n        let filter_op = FilterOperator::new(FilterPredicate::GreaterThan {\n            column_idx: 1, // \"value\" is at index 1\n            value: Value::from_i64(10),\n        });\n\n        // Create the filter predicate using DbspExpr\n        let predicate = DbspExpr::BinaryExpr {\n            left: Box::new(DbspExpr::Column(\"value\".to_string())),\n            op: ast::Operator::Greater,\n            right: Box::new(DbspExpr::Literal(Value::from_i64(10))),\n        };\n\n        let filter_id = circuit.add_node(\n            DbspOperator::Filter { predicate },\n            vec![input_id], // Filter takes input from the input node\n            Box::new(filter_op),\n        );\n\n        circuit.set_root(filter_id, schema);\n\n        // Initialize with a row\n        let mut init_data = HashMap::default();\n        let mut delta = Delta::new();\n        delta.insert(5, vec![Value::from_i64(5), Value::from_i64(20)]);\n        init_data.insert(\"test\".to_string(), delta);\n\n        let _ = test_execute(&mut circuit, init_data.clone(), pager.clone()).unwrap();\n        pager\n            .io\n            .block(|| circuit.commit(init_data.clone(), pager.clone()))\n            .unwrap();\n\n        // Verify initial state\n        let state = get_current_state(pager.clone(), &circuit).unwrap();\n        assert_eq!(state.changes.len(), 1);\n        assert_eq!(state.changes[0].0.rowid, 5);\n\n        // Now update the rowid from 5 to 3\n        let mut update_data = HashMap::default();\n        let mut update_delta = Delta::new();\n        update_delta.delete(5, vec![Value::from_i64(5), Value::from_i64(20)]);\n        update_delta.insert(3, vec![Value::from_i64(3), Value::from_i64(20)]);\n        update_data.insert(\"test\".to_string(), update_delta);\n\n        test_execute(&mut circuit, update_data.clone(), pager.clone()).unwrap();\n\n        // Commit the changes to update operator state\n        pager\n            .io\n            .block(|| circuit.commit(update_data.clone(), pager.clone()))\n            .unwrap();\n\n        // The circuit should consolidate the state properly\n        let final_state = get_current_state(pager, &circuit).unwrap();\n        assert_eq!(\n            final_state.changes.len(),\n            1,\n            \"Circuit should consolidate to single row\"\n        );\n        assert_eq!(final_state.changes[0].0.rowid, 3);\n        assert_eq!(\n            final_state.changes[0].0.values,\n            vec![Value::from_i64(3), Value::from_i64(20)]\n        );\n        assert_eq!(final_state.changes[0].1, 1);\n    }\n\n    #[test]\n    fn test_circuit_respects_multiplicities() {\n        let (mut circuit, pager) = compile_sql!(\"SELECT * from users\");\n\n        // Insert same row twice (multiplicity 2)\n        let mut delta = Delta::new();\n        delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Alice\".into()),\n                Value::from_i64(25),\n            ],\n        );\n        delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Alice\".into()),\n                Value::from_i64(25),\n            ],\n        );\n\n        let mut inputs = HashMap::default();\n        inputs.insert(\"users\".to_string(), delta);\n        test_execute(&mut circuit, inputs.clone(), pager.clone()).unwrap();\n        pager\n            .io\n            .block(|| circuit.commit(inputs.clone(), pager.clone()))\n            .unwrap();\n\n        // Delete once (should leave multiplicity 1)\n        let mut delete_one = Delta::new();\n        delete_one.delete(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Alice\".into()),\n                Value::from_i64(25),\n            ],\n        );\n\n        let mut inputs = HashMap::default();\n        inputs.insert(\"users\".to_string(), delete_one);\n        test_execute(&mut circuit, inputs.clone(), pager.clone()).unwrap();\n        pager\n            .io\n            .block(|| circuit.commit(inputs.clone(), pager.clone()))\n            .unwrap();\n\n        // With proper DBSP: row still exists (weight 2 - 1 = 1)\n        let state = get_current_state(pager, &circuit).unwrap();\n        let mut consolidated = state;\n        consolidated.consolidate();\n        assert_eq!(\n            consolidated.len(),\n            1,\n            \"Row should still exist with multiplicity 1\"\n        );\n    }\n\n    #[test]\n    fn test_join_with_aggregation() {\n        // Test join followed by aggregation - verifying actual output\n        let (mut circuit, pager) = compile_sql!(\n            \"SELECT u.name, SUM(o.quantity) as total_quantity\n             FROM users u\n             JOIN orders o ON u.id = o.user_id\n             GROUP BY u.name\"\n        );\n\n        // Create test data for users\n        let mut users_delta = Delta::new();\n        users_delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Alice\".into()),\n                Value::from_i64(30),\n            ],\n        );\n        users_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Bob\".into()),\n                Value::from_i64(25),\n            ],\n        );\n\n        // Create test data for orders (order_id, user_id, product_id, quantity)\n        let mut orders_delta = Delta::new();\n        orders_delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::from_i64(1),\n                Value::from_i64(101),\n                Value::from_i64(5),\n            ],\n        ); // Alice: 5\n        orders_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::from_i64(1),\n                Value::from_i64(102),\n                Value::from_i64(3),\n            ],\n        ); // Alice: 3\n        orders_delta.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::from_i64(2),\n                Value::from_i64(101),\n                Value::from_i64(7),\n            ],\n        ); // Bob: 7\n        orders_delta.insert(\n            4,\n            vec![\n                Value::from_i64(4),\n                Value::from_i64(1),\n                Value::from_i64(103),\n                Value::from_i64(2),\n            ],\n        ); // Alice: 2\n        let inputs = HashMap::from_iter([\n            (\"users\".to_string(), users_delta),\n            (\"orders\".to_string(), orders_delta),\n        ]);\n\n        let result = test_execute(&mut circuit, inputs, pager).unwrap();\n\n        // Should have 2 results: Alice with total 10, Bob with total 7\n        assert_eq!(\n            result.len(),\n            2,\n            \"Should have aggregated results for Alice and Bob\"\n        );\n\n        // Check the results\n        let mut results_map: HashMap<String, f64> = HashMap::default();\n        for (row, weight) in result.changes {\n            assert_eq!(weight, 1);\n            assert_eq!(row.values.len(), 2); // name and total_quantity\n\n            if let (Value::Text(name), Value::Numeric(Numeric::Float(total))) =\n                (&row.values[0], &row.values[1])\n            {\n                results_map.insert(name.to_string(), f64::from(*total));\n            } else {\n                panic!(\"Unexpected value types in result\");\n            }\n        }\n\n        assert_eq!(\n            results_map.get(\"Alice\"),\n            Some(&10.0),\n            \"Alice should have total quantity 10\"\n        );\n        assert_eq!(\n            results_map.get(\"Bob\"),\n            Some(&7.0),\n            \"Bob should have total quantity 7\"\n        );\n    }\n\n    #[test]\n    fn test_join_aggregate_with_filter() {\n        // Test complex query with join, filter, and aggregation - verifying output\n        let (mut circuit, pager) = compile_sql!(\n            \"SELECT u.name, SUM(o.quantity) as total\n             FROM users u\n             JOIN orders o ON u.id = o.user_id\n             WHERE u.age > 18\n             GROUP BY u.name\"\n        );\n\n        // Create test data for users\n        let mut users_delta = Delta::new();\n        users_delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Alice\".into()),\n                Value::from_i64(30),\n            ],\n        ); // age > 18\n        users_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Bob\".into()),\n                Value::from_i64(17),\n            ],\n        ); // age <= 18\n        users_delta.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::Text(\"Charlie\".into()),\n                Value::from_i64(25),\n            ],\n        ); // age > 18\n\n        // Create test data for orders (order_id, user_id, product_id, quantity)\n        let mut orders_delta = Delta::new();\n        orders_delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::from_i64(1),\n                Value::from_i64(101),\n                Value::from_i64(5),\n            ],\n        ); // Alice: 5\n        orders_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::from_i64(2),\n                Value::from_i64(102),\n                Value::from_i64(10),\n            ],\n        ); // Bob: 10 (should be filtered)\n        orders_delta.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::from_i64(3),\n                Value::from_i64(101),\n                Value::from_i64(7),\n            ],\n        ); // Charlie: 7\n        orders_delta.insert(\n            4,\n            vec![\n                Value::from_i64(4),\n                Value::from_i64(1),\n                Value::from_i64(103),\n                Value::from_i64(3),\n            ],\n        ); // Alice: 3\n\n        let inputs = HashMap::from_iter([\n            (\"users\".to_string(), users_delta),\n            (\"orders\".to_string(), orders_delta),\n        ]);\n\n        let result = test_execute(&mut circuit, inputs, pager).unwrap();\n\n        // Should only have results for Alice and Charlie (Bob filtered out due to age <= 18)\n        assert_eq!(\n            result.len(),\n            2,\n            \"Should only have results for users with age > 18\"\n        );\n\n        // Check the results\n        let mut results_map: HashMap<String, f64> = HashMap::default();\n        for (row, weight) in result.changes {\n            assert_eq!(weight, 1);\n            assert_eq!(row.values.len(), 2); // name and total\n\n            if let (Value::Text(name), Value::Numeric(Numeric::Float(total))) =\n                (&row.values[0], &row.values[1])\n            {\n                results_map.insert(name.to_string(), f64::from(*total));\n            }\n        }\n\n        assert_eq!(\n            results_map.get(\"Alice\"),\n            Some(&8.0),\n            \"Alice should have total 8\"\n        );\n        assert_eq!(\n            results_map.get(\"Charlie\"),\n            Some(&7.0),\n            \"Charlie should have total 7\"\n        );\n        assert_eq!(results_map.get(\"Bob\"), None, \"Bob should be filtered out\");\n    }\n\n    #[test]\n    fn test_three_way_join_execution() {\n        // Test executing a 3-way join with aggregation\n        let (mut circuit, pager) = compile_sql!(\n            \"SELECT u.name, p.product_name, SUM(o.quantity) as total\n             FROM users u\n             JOIN orders o ON u.id = o.user_id\n             JOIN products p ON o.product_id = p.product_id\n             GROUP BY u.name, p.product_name\"\n        );\n\n        // Create test data for users\n        let mut users_delta = Delta::new();\n        users_delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Alice\".into()),\n                Value::from_i64(25),\n            ],\n        );\n        users_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Bob\".into()),\n                Value::from_i64(30),\n            ],\n        );\n\n        // Create test data for products\n        let mut products_delta = Delta::new();\n        products_delta.insert(\n            100,\n            vec![\n                Value::from_i64(100),\n                Value::Text(\"Widget\".into()),\n                Value::from_i64(50),\n            ],\n        );\n        products_delta.insert(\n            101,\n            vec![\n                Value::from_i64(101),\n                Value::Text(\"Gadget\".into()),\n                Value::from_i64(75),\n            ],\n        );\n        products_delta.insert(\n            102,\n            vec![\n                Value::from_i64(102),\n                Value::Text(\"Doohickey\".into()),\n                Value::from_i64(25),\n            ],\n        );\n\n        // Create test data for orders joining users and products\n        let mut orders_delta = Delta::new();\n        // Alice orders 5 Widgets\n        orders_delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::from_i64(1),\n                Value::from_i64(100),\n                Value::from_i64(5),\n            ],\n        );\n        // Alice orders 3 Gadgets\n        orders_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::from_i64(1),\n                Value::from_i64(101),\n                Value::from_i64(3),\n            ],\n        );\n        // Bob orders 7 Widgets\n        orders_delta.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::from_i64(2),\n                Value::from_i64(100),\n                Value::from_i64(7),\n            ],\n        );\n        // Bob orders 2 Doohickeys\n        orders_delta.insert(\n            4,\n            vec![\n                Value::from_i64(4),\n                Value::from_i64(2),\n                Value::from_i64(102),\n                Value::from_i64(2),\n            ],\n        );\n        // Alice orders 4 more Widgets\n        orders_delta.insert(\n            5,\n            vec![\n                Value::from_i64(5),\n                Value::from_i64(1),\n                Value::from_i64(100),\n                Value::from_i64(4),\n            ],\n        );\n\n        let mut inputs = HashMap::default();\n        inputs.insert(\"users\".to_string(), users_delta);\n        inputs.insert(\"products\".to_string(), products_delta);\n        inputs.insert(\"orders\".to_string(), orders_delta);\n\n        // Execute the 3-way join with aggregation\n        let result = test_execute(&mut circuit, inputs.clone(), pager).unwrap();\n\n        // We should get aggregated results for each user-product combination\n        // Expected results:\n        // - Alice, Widget: 9 (5 + 4)\n        // - Alice, Gadget: 3\n        // - Bob, Widget: 7\n        // - Bob, Doohickey: 2\n        assert_eq!(result.len(), 4, \"Should have 4 aggregated results\");\n\n        // Verify aggregation results\n        let mut found_results = HashSet::default();\n        for (row, weight) in result.changes.iter() {\n            assert_eq!(*weight, 1);\n            // Row should have name, product_name, and sum columns\n            assert_eq!(row.values.len(), 3);\n\n            if let (\n                Value::Text(name),\n                Value::Text(product),\n                Value::Numeric(Numeric::Float(total)),\n            ) = (&row.values[0], &row.values[1], &row.values[2])\n            {\n                let key = format!(\"{}-{}\", name.as_ref(), product.as_ref());\n                found_results.insert(key.clone());\n\n                match key.as_str() {\n                    \"Alice-Widget\" => {\n                        assert_eq!(*total, 9.0, \"Alice should have ordered 9 Widgets total\")\n                    }\n                    \"Alice-Gadget\" => {\n                        assert_eq!(*total, 3.0, \"Alice should have ordered 3 Gadgets\")\n                    }\n                    \"Bob-Widget\" => assert_eq!(*total, 7.0, \"Bob should have ordered 7 Widgets\"),\n                    \"Bob-Doohickey\" => {\n                        assert_eq!(*total, 2.0, \"Bob should have ordered 2 Doohickeys\")\n                    }\n                    _ => panic!(\"Unexpected result: {key}\"),\n                }\n            } else {\n                panic!(\"Unexpected value types in result\");\n            }\n        }\n\n        // Ensure we found all expected combinations\n        assert!(found_results.contains(\"Alice-Widget\"));\n        assert!(found_results.contains(\"Alice-Gadget\"));\n        assert!(found_results.contains(\"Bob-Widget\"));\n        assert!(found_results.contains(\"Bob-Doohickey\"));\n    }\n\n    #[test]\n    fn test_join_execution() {\n        let (mut circuit, pager) = compile_sql!(\n            \"SELECT u.name, o.quantity FROM users u JOIN orders o ON u.id = o.user_id\"\n        );\n\n        // Create test data for users\n        let mut users_delta = Delta::new();\n        users_delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Alice\".into()),\n                Value::from_i64(25),\n            ],\n        );\n        users_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Bob\".into()),\n                Value::from_i64(30),\n            ],\n        );\n\n        // Create test data for orders\n        let mut orders_delta = Delta::new();\n        orders_delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::from_i64(1),\n                Value::from_i64(100),\n                Value::from_i64(5),\n            ],\n        );\n        orders_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::from_i64(1),\n                Value::from_i64(101),\n                Value::from_i64(3),\n            ],\n        );\n        orders_delta.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::from_i64(2),\n                Value::from_i64(102),\n                Value::from_i64(7),\n            ],\n        );\n\n        let mut inputs = HashMap::default();\n        inputs.insert(\"users\".to_string(), users_delta);\n        inputs.insert(\"orders\".to_string(), orders_delta);\n\n        // Execute the join\n        let result = test_execute(&mut circuit, inputs.clone(), pager).unwrap();\n\n        // We should get 3 results (2 orders for Alice, 1 for Bob)\n        assert_eq!(result.len(), 3, \"Should have 3 join results\");\n\n        // Verify the join results contain the correct data\n        let results: Vec<_> = result.changes.iter().collect();\n\n        // Check that we have the expected joined rows\n        for (row, weight) in results {\n            assert_eq!(*weight, 1); // All weights should be 1 for insertions\n                                    // Row should have name and quantity columns\n            assert_eq!(row.values.len(), 2);\n        }\n    }\n\n    #[test]\n    fn test_three_way_join_with_column_ambiguity() {\n        // Test three-way join with aggregation where multiple tables have columns with the same name\n        // Ensures that column references are correctly resolved to their respective tables\n        // Tables: customers(id, name), purchases(id, customer_id, vendor_id, quantity), vendors(id, name, price)\n        // Note: both customers and vendors have 'id' and 'name' columns which can cause ambiguity\n\n        let sql = \"SELECT c.name as customer_name, v.name as vendor_name,\n                          SUM(p.quantity) as total_quantity,\n                          SUM(p.quantity * v.price) as total_value\n                   FROM customers c\n                   JOIN purchases p ON c.id = p.customer_id\n                   JOIN vendors v ON p.vendor_id = v.id\n                   GROUP BY c.name, v.name\";\n\n        let (mut circuit, pager) = compile_sql!(sql);\n\n        // Create test data for customers (id, name)\n        let mut customers_delta = Delta::new();\n        customers_delta.insert(1, vec![Value::from_i64(1), Value::Text(\"Alice\".into())]);\n        customers_delta.insert(2, vec![Value::from_i64(2), Value::Text(\"Bob\".into())]);\n\n        // Create test data for vendors (id, name, price)\n        let mut vendors_delta = Delta::new();\n        vendors_delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Widget Co\".into()),\n                Value::from_i64(10),\n            ],\n        );\n        vendors_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Gadget Inc\".into()),\n                Value::from_i64(20),\n            ],\n        );\n\n        // Create test data for purchases (id, customer_id, vendor_id, quantity)\n        let mut purchases_delta = Delta::new();\n        // Alice purchases 5 units from Widget Co\n        purchases_delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::from_i64(1), // customer_id: Alice\n                Value::from_i64(1), // vendor_id: Widget Co\n                Value::from_i64(5),\n            ],\n        );\n        // Alice purchases 3 units from Gadget Inc\n        purchases_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::from_i64(1), // customer_id: Alice\n                Value::from_i64(2), // vendor_id: Gadget Inc\n                Value::from_i64(3),\n            ],\n        );\n        // Bob purchases 2 units from Widget Co\n        purchases_delta.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::from_i64(2), // customer_id: Bob\n                Value::from_i64(1), // vendor_id: Widget Co\n                Value::from_i64(2),\n            ],\n        );\n        // Alice purchases 4 more units from Widget Co\n        purchases_delta.insert(\n            4,\n            vec![\n                Value::from_i64(4),\n                Value::from_i64(1), // customer_id: Alice\n                Value::from_i64(1), // vendor_id: Widget Co\n                Value::from_i64(4),\n            ],\n        );\n\n        let inputs = HashMap::from_iter([\n            (\"customers\".to_string(), customers_delta),\n            (\"purchases\".to_string(), purchases_delta),\n            (\"vendors\".to_string(), vendors_delta),\n        ]);\n\n        let result = test_execute(&mut circuit, inputs, pager).unwrap();\n\n        // Expected results:\n        // Alice|Gadget Inc|3|60    (3 units * 20 price = 60)\n        // Alice|Widget Co|9|90     (9 units * 10 price = 90)\n        // Bob|Widget Co|2|20       (2 units * 10 price = 20)\n\n        assert_eq!(result.len(), 3, \"Should have 3 aggregated results\");\n\n        // Sort results for consistent testing\n        let mut results: Vec<_> = result.changes.into_iter().collect();\n        results.sort_by(|a, b| {\n            let a_cust = &a.0.values[0];\n            let a_vend = &a.0.values[1];\n            let b_cust = &b.0.values[0];\n            let b_vend = &b.0.values[1];\n            (a_cust, a_vend).cmp(&(b_cust, b_vend))\n        });\n\n        // Verify Alice's Gadget Inc purchases\n        assert_eq!(results[0].0.values[0], Value::Text(\"Alice\".into()));\n        assert_eq!(results[0].0.values[1], Value::Text(\"Gadget Inc\".into()));\n        assert_eq!(results[0].0.values[2], Value::from_i64(3)); // total_quantity\n        assert_eq!(results[0].0.values[3], Value::from_i64(60)); // total_value\n\n        // Verify Alice's Widget Co purchases\n        assert_eq!(results[1].0.values[0], Value::Text(\"Alice\".into()));\n        assert_eq!(results[1].0.values[1], Value::Text(\"Widget Co\".into()));\n        assert_eq!(results[1].0.values[2], Value::from_i64(9)); // total_quantity\n        assert_eq!(results[1].0.values[3], Value::from_i64(90)); // total_value\n\n        // Verify Bob's Widget Co purchases\n        assert_eq!(results[2].0.values[0], Value::Text(\"Bob\".into()));\n        assert_eq!(results[2].0.values[1], Value::Text(\"Widget Co\".into()));\n        assert_eq!(results[2].0.values[2], Value::from_i64(2)); // total_quantity\n        assert_eq!(results[2].0.values[3], Value::from_i64(20)); // total_value\n    }\n\n    #[test]\n    fn test_projection_with_function_and_ambiguous_columns() {\n        // Test projection with functions operating on potentially ambiguous columns\n        // Uses HEX() function on sum of columns from different tables with same names\n        // Tables: customers(id, name), vendors(id, name, price), purchases(id, customer_id, vendor_id, quantity)\n        // This test ensures column references are correctly resolved to their respective tables\n\n        let sql = \"SELECT HEX(c.id + v.id) as hex_sum,\n                          UPPER(c.name) as customer_upper,\n                          LOWER(v.name) as vendor_lower,\n                          c.id * v.price as product_value\n                   FROM customers c\n                   JOIN vendors v ON c.id = v.id\";\n\n        let (mut circuit, pager) = compile_sql!(sql);\n\n        // Create test data for customers (id, name)\n        let mut customers_delta = Delta::new();\n        customers_delta.insert(1, vec![Value::from_i64(1), Value::Text(\"Alice\".into())]);\n        customers_delta.insert(2, vec![Value::from_i64(2), Value::Text(\"Bob\".into())]);\n        customers_delta.insert(3, vec![Value::from_i64(3), Value::Text(\"Charlie\".into())]);\n\n        // Create test data for vendors (id, name, price)\n        let mut vendors_delta = Delta::new();\n        vendors_delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Widget Co\".into()),\n                Value::from_i64(10),\n            ],\n        );\n        vendors_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Gadget Inc\".into()),\n                Value::from_i64(20),\n            ],\n        );\n        vendors_delta.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::Text(\"Tool Corp\".into()),\n                Value::from_i64(30),\n            ],\n        );\n\n        let inputs = HashMap::from_iter([\n            (\"customers\".to_string(), customers_delta),\n            (\"vendors\".to_string(), vendors_delta),\n        ]);\n\n        let result = test_execute(&mut circuit, inputs, pager).unwrap();\n\n        // Expected results:\n        // For customer 1 (Alice) + vendor 1:\n        //   - HEX(1 + 1) = HEX(2) = \"32\"\n        //   - UPPER(\"Alice\") = \"ALICE\"\n        //   - LOWER(\"Widget Co\") = \"widget co\"\n        //   - 1 * 10 = 10\n        assert_eq!(result.len(), 3, \"Should have 3 join results\");\n\n        let mut results = result.changes;\n        results.sort_by_key(|(row, _)| {\n            // Sort by the product_value column for predictable ordering\n            match &row.values[3] {\n                Value::Numeric(Numeric::Integer(n)) => *n,\n                _ => 0,\n            }\n        });\n\n        // First result: Alice + Widget Co\n        assert_eq!(results[0].0.values[0], Value::Text(\"32\".into())); // HEX(2)\n        assert_eq!(results[0].0.values[1], Value::Text(\"ALICE\".into()));\n        assert_eq!(results[0].0.values[2], Value::Text(\"widget co\".into()));\n        assert_eq!(results[0].0.values[3], Value::from_i64(10)); // 1 * 10\n\n        // Second result: Bob + Gadget Inc\n        assert_eq!(results[1].0.values[0], Value::Text(\"34\".into())); // HEX(4)\n        assert_eq!(results[1].0.values[1], Value::Text(\"BOB\".into()));\n        assert_eq!(results[1].0.values[2], Value::Text(\"gadget inc\".into()));\n        assert_eq!(results[1].0.values[3], Value::from_i64(40)); // 2 * 20\n\n        // Third result: Charlie + Tool Corp\n        assert_eq!(results[2].0.values[0], Value::Text(\"36\".into())); // HEX(6)\n        assert_eq!(results[2].0.values[1], Value::Text(\"CHARLIE\".into()));\n        assert_eq!(results[2].0.values[2], Value::Text(\"tool corp\".into()));\n        assert_eq!(results[2].0.values[3], Value::from_i64(90)); // 3 * 30\n    }\n\n    #[test]\n    fn test_projection_column_selection_after_join() {\n        // Test selecting specific columns after a join, especially with overlapping column names\n        // This ensures the projection correctly picks columns by their qualified references\n\n        let sql = \"SELECT c.id as customer_id,\n                          c.name as customer_name,\n                          o.order_id,\n                          o.quantity,\n                          p.product_name\n                   FROM users c\n                   JOIN orders o ON c.id = o.user_id\n                   JOIN products p ON o.product_id = p.product_id\n                   WHERE o.quantity > 2\";\n\n        let (mut circuit, pager) = compile_sql!(sql);\n\n        // Create test data for users (id, name, age)\n        let mut users_delta = Delta::new();\n        users_delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Alice\".into()),\n                Value::from_i64(25),\n            ],\n        );\n        users_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Bob\".into()),\n                Value::from_i64(30),\n            ],\n        );\n\n        // Create test data for orders (order_id, user_id, product_id, quantity)\n        let mut orders_delta = Delta::new();\n        orders_delta.insert(\n            1,\n            vec![\n                Value::from_i64(101),\n                Value::from_i64(1),   // Alice\n                Value::from_i64(201), // Widget\n                Value::from_i64(5),   // quantity > 2\n            ],\n        );\n        orders_delta.insert(\n            2,\n            vec![\n                Value::from_i64(102),\n                Value::from_i64(2),   // Bob\n                Value::from_i64(202), // Gadget\n                Value::from_i64(1),   // quantity <= 2, filtered out\n            ],\n        );\n        orders_delta.insert(\n            3,\n            vec![\n                Value::from_i64(103),\n                Value::from_i64(1),   // Alice\n                Value::from_i64(202), // Gadget\n                Value::from_i64(3),   // quantity > 2\n            ],\n        );\n\n        // Create test data for products (product_id, product_name, price)\n        let mut products_delta = Delta::new();\n        products_delta.insert(\n            201,\n            vec![\n                Value::from_i64(201),\n                Value::Text(\"Widget\".into()),\n                Value::from_i64(10),\n            ],\n        );\n        products_delta.insert(\n            202,\n            vec![\n                Value::from_i64(202),\n                Value::Text(\"Gadget\".into()),\n                Value::from_i64(20),\n            ],\n        );\n\n        let inputs = HashMap::from_iter([\n            (\"users\".to_string(), users_delta),\n            (\"orders\".to_string(), orders_delta),\n            (\"products\".to_string(), products_delta),\n        ]);\n\n        let result = test_execute(&mut circuit, inputs, pager).unwrap();\n\n        // Should have 2 results (orders with quantity > 2)\n        assert_eq!(result.len(), 2, \"Should have 2 results after filtering\");\n\n        let mut results = result.changes;\n        results.sort_by_key(|(row, _)| {\n            match &row.values[2] {\n                // Sort by order_id\n                Value::Numeric(Numeric::Integer(n)) => *n,\n                _ => 0,\n            }\n        });\n\n        // First result: Alice's order 101 for Widget\n        assert_eq!(results[0].0.values[0], Value::from_i64(1)); // customer_id\n        assert_eq!(results[0].0.values[1], Value::Text(\"Alice\".into())); // customer_name\n        assert_eq!(results[0].0.values[2], Value::from_i64(101)); // order_id\n        assert_eq!(results[0].0.values[3], Value::from_i64(5)); // quantity\n        assert_eq!(results[0].0.values[4], Value::Text(\"Widget\".into())); // product_name\n\n        // Second result: Alice's order 103 for Gadget\n        assert_eq!(results[1].0.values[0], Value::from_i64(1)); // customer_id\n        assert_eq!(results[1].0.values[1], Value::Text(\"Alice\".into())); // customer_name\n        assert_eq!(results[1].0.values[2], Value::from_i64(103)); // order_id\n        assert_eq!(results[1].0.values[3], Value::from_i64(3)); // quantity\n        assert_eq!(results[1].0.values[4], Value::Text(\"Gadget\".into())); // product_name\n    }\n\n    #[test]\n    fn test_projection_column_reordering_and_duplication() {\n        // Test that projection can reorder columns and select the same column multiple times\n        // This is important for views that need specific column arrangements\n\n        let sql = \"SELECT o.quantity,\n                          u.name,\n                          u.id,\n                          o.quantity * 2 as double_quantity,\n                          u.id as user_id_again\n                   FROM users u\n                   JOIN orders o ON u.id = o.user_id\n                   WHERE u.id = 1\";\n\n        let (mut circuit, pager) = compile_sql!(sql);\n\n        // Create test data for users\n        let mut users_delta = Delta::new();\n        users_delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Alice\".into()),\n                Value::from_i64(25),\n            ],\n        );\n\n        // Create test data for orders\n        let mut orders_delta = Delta::new();\n        orders_delta.insert(\n            1,\n            vec![\n                Value::from_i64(101),\n                Value::from_i64(1),   // user_id\n                Value::from_i64(201), // product_id\n                Value::from_i64(5),   // quantity\n            ],\n        );\n        orders_delta.insert(\n            2,\n            vec![\n                Value::from_i64(102),\n                Value::from_i64(1),   // user_id\n                Value::from_i64(202), // product_id\n                Value::from_i64(3),   // quantity\n            ],\n        );\n\n        let inputs = HashMap::from_iter([\n            (\"users\".to_string(), users_delta),\n            (\"orders\".to_string(), orders_delta),\n        ]);\n\n        let result = test_execute(&mut circuit, inputs, pager).unwrap();\n\n        assert_eq!(result.len(), 2, \"Should have 2 results for user 1\");\n\n        // Check that columns are in the right order and values are correct\n        for (row, _) in &result.changes {\n            // Column 0: o.quantity (5 or 3)\n            assert!(matches!(\n                row.values[0],\n                Value::Numeric(Numeric::Integer(5)) | Value::Numeric(Numeric::Integer(3))\n            ));\n            // Column 1: u.name\n            assert_eq!(row.values[1], Value::Text(\"Alice\".into()));\n            // Column 2: u.id\n            assert_eq!(row.values[2], Value::from_i64(1));\n            // Column 3: o.quantity * 2 (10 or 6)\n            assert!(matches!(\n                row.values[3],\n                Value::Numeric(Numeric::Integer(10)) | Value::Numeric(Numeric::Integer(6))\n            ));\n            // Column 4: u.id again\n            assert_eq!(row.values[4], Value::from_i64(1));\n        }\n    }\n\n    #[test]\n    fn test_join_with_aggregate_execution() {\n        let (mut circuit, pager) = compile_sql!(\n            \"SELECT u.name, SUM(o.quantity) as total_quantity\n             FROM users u\n             JOIN orders o ON u.id = o.user_id\n             GROUP BY u.name\"\n        );\n\n        // Create test data for users\n        let mut users_delta = Delta::new();\n        users_delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Alice\".into()),\n                Value::from_i64(25),\n            ],\n        );\n        users_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Bob\".into()),\n                Value::from_i64(30),\n            ],\n        );\n\n        // Create test data for orders\n        let mut orders_delta = Delta::new();\n        orders_delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::from_i64(1),\n                Value::from_i64(100),\n                Value::from_i64(5),\n            ],\n        );\n        orders_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::from_i64(1),\n                Value::from_i64(101),\n                Value::from_i64(3),\n            ],\n        );\n        orders_delta.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::from_i64(2),\n                Value::from_i64(102),\n                Value::from_i64(7),\n            ],\n        );\n\n        let mut inputs = HashMap::default();\n        inputs.insert(\"users\".to_string(), users_delta);\n        inputs.insert(\"orders\".to_string(), orders_delta);\n\n        // Execute the join with aggregation\n        let result = test_execute(&mut circuit, inputs.clone(), pager).unwrap();\n\n        // We should get 2 aggregated results (one for Alice, one for Bob)\n        assert_eq!(result.len(), 2, \"Should have 2 aggregated results\");\n\n        // Verify aggregation results\n        for (row, weight) in result.changes.iter() {\n            assert_eq!(*weight, 1);\n            // Row should have name and sum columns\n            assert_eq!(row.values.len(), 2);\n\n            // Check the aggregated values\n            if let Value::Text(name) = &row.values[0] {\n                if name.as_ref() == \"Alice\" {\n                    // Alice should have total quantity of 8 (5 + 3)\n                    assert_eq!(row.values[1], Value::from_i64(8));\n                } else if name.as_ref() == \"Bob\" {\n                    // Bob should have total quantity of 7\n                    assert_eq!(row.values[1], Value::from_i64(7));\n                }\n            }\n        }\n    }\n\n    #[test]\n    fn test_filter_with_qualified_columns_in_join() {\n        // Test that filters correctly handle qualified column names in joins\n        // when multiple tables have columns with the SAME names.\n        // Both users and customers tables have 'id' and 'name' columns which can be ambiguous.\n\n        let (mut circuit, pager) = compile_sql!(\n            \"SELECT users.id, users.name, customers.id, customers.name\n             FROM users\n             JOIN customers ON users.id = customers.id\n             WHERE users.id > 1 AND customers.id < 100\"\n        );\n\n        // Create test data\n        let mut users_delta = Delta::new();\n        let mut customers_delta = Delta::new();\n\n        // Users data: (id, name, age)\n        users_delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Alice\".into()),\n                Value::from_i64(30),\n            ],\n        ); // id = 1\n        users_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Bob\".into()),\n                Value::from_i64(25),\n            ],\n        ); // id = 2\n        users_delta.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::Text(\"Charlie\".into()),\n                Value::from_i64(35),\n            ],\n        ); // id = 3\n\n        // Customers data: (id, name, email)\n        customers_delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Customer Alice\".into()),\n                Value::Text(\"alice@example.com\".into()),\n            ],\n        ); // id = 1\n        customers_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Customer Bob\".into()),\n                Value::Text(\"bob@example.com\".into()),\n            ],\n        ); // id = 2\n        customers_delta.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::Text(\"Customer Charlie\".into()),\n                Value::Text(\"charlie@example.com\".into()),\n            ],\n        ); // id = 3\n\n        let mut inputs = HashMap::default();\n        inputs.insert(\"users\".to_string(), users_delta);\n        inputs.insert(\"customers\".to_string(), customers_delta);\n\n        let result = test_execute(&mut circuit, inputs.clone(), pager).unwrap();\n\n        // Should get rows where users.id > 1 AND customers.id < 100\n        // - users.id=2 (> 1) AND customers.id=2 (< 100) ✓\n        // - users.id=3 (> 1) AND customers.id=3 (< 100) ✓\n        // Alice excluded: users.id=1 (NOT > 1)\n        assert_eq!(result.len(), 2, \"Should have 2 filtered results\");\n\n        let (row, weight) = &result.changes[0];\n        assert_eq!(*weight, 1);\n        assert_eq!(row.values.len(), 4, \"Should have 4 columns\");\n\n        // Verify the filter correctly used qualified columns for Bob\n        assert_eq!(row.values[0], Value::from_i64(2), \"users.id should be 2\");\n        assert_eq!(\n            row.values[1],\n            Value::Text(\"Bob\".into()),\n            \"users.name should be Bob\"\n        );\n        assert_eq!(\n            row.values[2],\n            Value::from_i64(2),\n            \"customers.id should be 2\"\n        );\n        assert_eq!(\n            row.values[3],\n            Value::Text(\"Customer Bob\".into()),\n            \"customers.name should be Customer Bob\"\n        );\n    }\n\n    #[test]\n    fn test_expression_in_where_clause() {\n        // Test expressions in WHERE clauses like (quantity * price) >= 400\n        let (mut circuit, pager) = compile_sql!(\"SELECT * FROM users WHERE (age * 2) > 30\");\n\n        // Create test data\n        let mut input_delta = Delta::new();\n        input_delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Alice\".into()),\n                Value::from_i64(20), // age * 2 = 40 > 30, should pass\n            ],\n        );\n        input_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Bob\".into()),\n                Value::from_i64(10), // age * 2 = 20 <= 30, should be filtered out\n            ],\n        );\n        input_delta.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::Text(\"Charlie\".into()),\n                Value::from_i64(16), // age * 2 = 32 > 30, should pass\n            ],\n        );\n\n        // Create input map\n        let mut inputs = HashMap::default();\n        inputs.insert(\"users\".to_string(), input_delta);\n\n        let result = test_execute(&mut circuit, inputs.clone(), pager).unwrap();\n\n        // Should only have Alice and Charlie (age * 2 > 30)\n        assert_eq!(\n            result.changes.len(),\n            2,\n            \"Should have 2 rows after filtering\"\n        );\n\n        // Check Alice\n        let alice = result\n            .changes\n            .iter()\n            .find(|(row, _)| row.values[0] == Value::from_i64(1))\n            .expect(\"Alice should be in result\");\n        assert_eq!(alice.0.values[1], Value::Text(\"Alice\".into()));\n        assert_eq!(alice.0.values[2], Value::from_i64(20));\n\n        // Check Charlie\n        let charlie = result\n            .changes\n            .iter()\n            .find(|(row, _)| row.values[0] == Value::from_i64(3))\n            .expect(\"Charlie should be in result\");\n        assert_eq!(charlie.0.values[1], Value::Text(\"Charlie\".into()));\n        assert_eq!(charlie.0.values[2], Value::from_i64(16));\n\n        // Bob should not be in result\n        let bob = result\n            .changes\n            .iter()\n            .find(|(row, _)| row.values[0] == Value::from_i64(2));\n        assert!(bob.is_none(), \"Bob should be filtered out\");\n    }\n\n    fn make_column_info(name: &str, ty: Type, table: &str) -> ColumnInfo {\n        ColumnInfo {\n            name: name.to_string(),\n            ty,\n            database: None,\n            table: Some(table.to_string()),\n            table_alias: None,\n        }\n    }\n\n    #[test]\n    fn test_resolve_join_columns_normal_order() {\n        // Normal case: left.id = right.id\n        let left_schema = LogicalSchema::new(vec![\n            ColumnInfo {\n                name: \"id\".to_string(),\n                ty: Type::Integer,\n                database: None,\n                table: Some(\"left\".to_string()),\n                table_alias: None,\n            },\n            ColumnInfo {\n                name: \"name\".to_string(),\n                ty: Type::Text,\n                database: None,\n                table: Some(\"left\".to_string()),\n                table_alias: None,\n            },\n        ]);\n        let right_schema = LogicalSchema::new(vec![\n            ColumnInfo {\n                name: \"id\".to_string(),\n                ty: Type::Integer,\n                database: None,\n                table: Some(\"right\".to_string()),\n                table_alias: None,\n            },\n            ColumnInfo {\n                name: \"value\".to_string(),\n                ty: Type::Integer,\n                database: None,\n                table: Some(\"right\".to_string()),\n                table_alias: None,\n            },\n        ]);\n\n        let left_col = Column {\n            name: \"id\".to_string(),\n            table: Some(\"left\".to_string()),\n        };\n        let right_col = Column {\n            name: \"id\".to_string(),\n            table: Some(\"right\".to_string()),\n        };\n\n        let result =\n            DbspCompiler::resolve_join_columns(&left_col, &right_col, &left_schema, &right_schema);\n        assert!(result.is_ok());\n        let (actual_left, left_idx, actual_right, right_idx) = result.unwrap();\n        assert_eq!(actual_left.name, \"id\");\n        assert_eq!(actual_left.table, Some(\"left\".to_string()));\n        assert_eq!(left_idx, 0);\n        assert_eq!(actual_right.name, \"id\");\n        assert_eq!(actual_right.table, Some(\"right\".to_string()));\n        assert_eq!(right_idx, 0);\n    }\n\n    #[test]\n    fn test_resolve_join_columns_swapped_order() {\n        // Swapped case: right.id = left.id\n        let left_schema = LogicalSchema::new(vec![\n            make_column_info(\"id\", Type::Integer, \"left\"),\n            make_column_info(\"name\", Type::Text, \"left\"),\n        ]);\n        let right_schema = LogicalSchema::new(vec![\n            make_column_info(\"id\", Type::Integer, \"right\"),\n            make_column_info(\"value\", Type::Integer, \"right\"),\n        ]);\n\n        let right_col = Column {\n            name: \"id\".to_string(),\n            table: Some(\"right\".to_string()),\n        };\n        let left_col = Column {\n            name: \"id\".to_string(),\n            table: Some(\"left\".to_string()),\n        };\n\n        let result =\n            DbspCompiler::resolve_join_columns(&right_col, &left_col, &left_schema, &right_schema);\n        assert!(result.is_ok());\n        let (actual_left, left_idx, actual_right, right_idx) = result.unwrap();\n        assert_eq!(actual_left.name, \"id\");\n        assert_eq!(actual_left.table, Some(\"left\".to_string()));\n        assert_eq!(left_idx, 0);\n        assert_eq!(actual_right.name, \"id\");\n        assert_eq!(actual_right.table, Some(\"right\".to_string()));\n        assert_eq!(right_idx, 0);\n    }\n\n    #[test]\n    fn test_resolve_join_columns_one_ambiguous_one_not() {\n        // Both tables have 'id', but only left has 'other_id'\n        let left_schema = LogicalSchema::new(vec![\n            make_column_info(\"id\", Type::Integer, \"left\"),\n            make_column_info(\"other_id\", Type::Integer, \"left\"),\n        ]);\n        let right_schema = LogicalSchema::new(vec![\n            make_column_info(\"id\", Type::Integer, \"right\"),\n            make_column_info(\"value\", Type::Integer, \"right\"),\n        ]);\n\n        // Unqualified 'id' with qualified 'left.other_id'\n        let id_col = Column {\n            name: \"id\".to_string(),\n            table: None,\n        };\n        let other_id_col = Column {\n            name: \"other_id\".to_string(),\n            table: Some(\"left\".to_string()),\n        };\n\n        // id from right, other_id from left\n        let result =\n            DbspCompiler::resolve_join_columns(&id_col, &other_id_col, &left_schema, &right_schema);\n        assert!(result.is_ok());\n        let (actual_left, left_idx, actual_right, right_idx) = result.unwrap();\n        assert_eq!(actual_left.name, \"other_id\");\n        assert_eq!(left_idx, 1);\n        assert_eq!(actual_right.name, \"id\");\n        assert_eq!(right_idx, 0);\n    }\n\n    #[test]\n    fn test_resolve_join_columns_mixed_qualified() {\n        // One qualified, one unqualified, column exists on both sides\n        let left_schema = LogicalSchema::new(vec![\n            make_column_info(\"id\", Type::Integer, \"left\"),\n            make_column_info(\"name\", Type::Text, \"left\"),\n        ]);\n        let right_schema = LogicalSchema::new(vec![\n            make_column_info(\"id\", Type::Integer, \"right\"),\n            make_column_info(\"name\", Type::Text, \"right\"),\n        ]);\n\n        // Qualified left.id with unqualified name\n        let left_id = Column {\n            name: \"id\".to_string(),\n            table: Some(\"left\".to_string()),\n        };\n        let name_unqualified = Column {\n            name: \"name\".to_string(),\n            table: None,\n        };\n\n        let result = DbspCompiler::resolve_join_columns(\n            &left_id,\n            &name_unqualified,\n            &left_schema,\n            &right_schema,\n        );\n        // left.id is explicitly from left, so unqualified 'name' must be resolved from right\n        assert!(result.is_ok());\n        let (actual_left, left_idx, actual_right, right_idx) = result.unwrap();\n        assert_eq!(actual_left.name, \"id\");\n        assert_eq!(left_idx, 0);\n        assert_eq!(actual_right.name, \"name\");\n        assert_eq!(right_idx, 1);\n    }\n\n    #[test]\n    fn test_resolve_join_columns_both_from_same_side() {\n        // Both columns from left table - should fail\n        let left_schema = LogicalSchema::new(vec![\n            make_column_info(\"id\", Type::Integer, \"left\"),\n            make_column_info(\"other_id\", Type::Integer, \"left\"),\n        ]);\n        let right_schema =\n            LogicalSchema::new(vec![make_column_info(\"value\", Type::Integer, \"right\")]);\n\n        let left_id = Column {\n            name: \"id\".to_string(),\n            table: Some(\"left\".to_string()),\n        };\n        let left_other_id = Column {\n            name: \"other_id\".to_string(),\n            table: Some(\"left\".to_string()),\n        };\n\n        let result = DbspCompiler::resolve_join_columns(\n            &left_id,\n            &left_other_id,\n            &left_schema,\n            &right_schema,\n        );\n        assert!(result.is_err());\n        assert!(result\n            .unwrap_err()\n            .to_string()\n            .contains(\"must come from different input tables\"));\n    }\n\n    #[test]\n    fn test_resolve_join_columns_nonexistent_column() {\n        // Column doesn't exist in either table\n        let left_schema = LogicalSchema::new(vec![make_column_info(\"id\", Type::Integer, \"left\")]);\n        let right_schema =\n            LogicalSchema::new(vec![make_column_info(\"value\", Type::Integer, \"right\")]);\n\n        let id_col = Column {\n            name: \"id\".to_string(),\n            table: None,\n        };\n        let nonexistent_col = Column {\n            name: \"does_not_exist\".to_string(),\n            table: None,\n        };\n\n        let result = DbspCompiler::resolve_join_columns(\n            &id_col,\n            &nonexistent_col,\n            &left_schema,\n            &right_schema,\n        );\n        assert!(result.is_err());\n    }\n\n    #[test]\n    fn test_resolve_join_columns_both_qualified() {\n        // Both columns qualified - should work normally\n        let left_schema = LogicalSchema::new(vec![\n            make_column_info(\"id\", Type::Integer, \"left\"),\n            make_column_info(\"name\", Type::Text, \"left\"),\n        ]);\n        let right_schema = LogicalSchema::new(vec![\n            make_column_info(\"id\", Type::Integer, \"right\"),\n            make_column_info(\"value\", Type::Integer, \"right\"),\n        ]);\n\n        let left_id = Column {\n            name: \"id\".to_string(),\n            table: Some(\"left\".to_string()),\n        };\n        let right_id = Column {\n            name: \"id\".to_string(),\n            table: Some(\"right\".to_string()),\n        };\n\n        let result =\n            DbspCompiler::resolve_join_columns(&left_id, &right_id, &left_schema, &right_schema);\n        assert!(result.is_ok());\n        let (actual_left, left_idx, actual_right, right_idx) = result.unwrap();\n        assert_eq!(actual_left.name, \"id\");\n        assert_eq!(left_idx, 0);\n        assert_eq!(actual_right.name, \"id\");\n        assert_eq!(right_idx, 0);\n    }\n\n    #[test]\n    fn test_resolve_join_columns_both_unqualified_same_name() {\n        // Both columns unqualified with same name existing in both tables - should succeed\n        // (first match wins based on order of checking)\n        let left_schema = LogicalSchema::new(vec![make_column_info(\"id\", Type::Integer, \"left\")]);\n        let right_schema = LogicalSchema::new(vec![make_column_info(\"id\", Type::Integer, \"right\")]);\n\n        let id_col1 = Column {\n            name: \"id\".to_string(),\n            table: None,\n        };\n        let id_col2 = Column {\n            name: \"id\".to_string(),\n            table: None,\n        };\n\n        let result =\n            DbspCompiler::resolve_join_columns(&id_col1, &id_col2, &left_schema, &right_schema);\n        // Should succeed - unqualified 'id' matches in both schemas\n        assert!(result.is_ok());\n    }\n\n    #[test]\n    fn test_resolve_join_columns_first_not_found() {\n        // First column doesn't exist anywhere\n        let left_schema = LogicalSchema::new(vec![make_column_info(\"id\", Type::Integer, \"left\")]);\n        let right_schema =\n            LogicalSchema::new(vec![make_column_info(\"value\", Type::Integer, \"right\")]);\n\n        let missing_col = Column {\n            name: \"missing\".to_string(),\n            table: None,\n        };\n        let value_col = Column {\n            name: \"value\".to_string(),\n            table: None,\n        };\n\n        let result = DbspCompiler::resolve_join_columns(\n            &missing_col,\n            &value_col,\n            &left_schema,\n            &right_schema,\n        );\n        assert!(result.is_err());\n        assert!(result\n            .unwrap_err()\n            .to_string()\n            .contains(\"not found in either input\"));\n    }\n\n    #[test]\n    fn test_resolve_join_columns_both_unqualified_different_names() {\n        // Both unqualified, each exists in only one table\n        let left_schema =\n            LogicalSchema::new(vec![make_column_info(\"left_id\", Type::Integer, \"left\")]);\n        let right_schema =\n            LogicalSchema::new(vec![make_column_info(\"right_id\", Type::Integer, \"right\")]);\n\n        let left_col = Column {\n            name: \"left_id\".to_string(),\n            table: None,\n        };\n        let right_col = Column {\n            name: \"right_id\".to_string(),\n            table: None,\n        };\n\n        let result =\n            DbspCompiler::resolve_join_columns(&left_col, &right_col, &left_schema, &right_schema);\n        assert!(result.is_ok());\n        let (actual_left, left_idx, actual_right, right_idx) = result.unwrap();\n        assert_eq!(actual_left.name, \"left_id\");\n        assert_eq!(left_idx, 0);\n        assert_eq!(actual_right.name, \"right_id\");\n        assert_eq!(right_idx, 0);\n    }\n}\n"
  },
  {
    "path": "core/incremental/cursor.rs",
    "content": "use crate::numeric::Numeric;\nuse crate::sync::Arc;\nuse crate::sync::Mutex;\nuse crate::{\n    incremental::{\n        compiler::{DeltaSet, ExecuteState},\n        dbsp::{Delta, HashableRow, RowKeyZSet},\n        view::{IncrementalView, ViewTransactionState},\n    },\n    return_if_io,\n    storage::btree::CursorTrait,\n    types::{IOResult, SeekKey, SeekOp, SeekResult, Value},\n    LimboError, Pager, Result,\n};\n\n/// State machine for seek operations\n#[derive(Debug)]\nenum SeekState {\n    /// Initial state before seeking\n    Init,\n\n    /// Actively seeking with btree and uncommitted iterators\n    Seek {\n        /// The row we are trying to find\n        target: i64,\n    },\n\n    /// Btree seek returned TryAdvance, now advancing with next()/prev()\n    Advancing {\n        /// The row we are trying to find\n        target: i64,\n        /// The seek operation (determines direction of advance)\n        op: SeekOp,\n    },\n\n    /// Seek completed successfully\n    Done,\n}\n\n/// Cursor for reading materialized views that combines:\n/// 1. Persistent btree data (committed state)\n/// 2. Transaction-specific DBSP deltas (uncommitted changes)\n///\n/// Works like a regular table cursor - reads from disk on-demand\n/// and overlays transaction changes as needed.\npub struct MaterializedViewCursor {\n    // Core components\n    btree_cursor: Box<dyn CursorTrait>,\n    view: Arc<Mutex<IncrementalView>>,\n    pager: Arc<Pager>,\n\n    // Current changes that are uncommitted\n    uncommitted: RowKeyZSet,\n\n    // Reference to shared transaction state for this specific view - shared with Connection\n    tx_state: Arc<ViewTransactionState>,\n\n    // The transaction state always grows. It never gets reduced. That is in the very nature of\n    // DBSP, because deletions are just appends with weight < 0. So we will use the length of the\n    // state to check if we have to recompute the transaction state\n    last_tx_state_len: usize,\n\n    // Current row cache - only cache the current row we're looking at\n    current_row: Option<(i64, Vec<Value>)>,\n\n    // Execution state for circuit processing\n    execute_state: ExecuteState,\n\n    // State machine for seek operations\n    seek_state: SeekState,\n}\n\nimpl MaterializedViewCursor {\n    pub fn new(\n        btree_cursor: Box<dyn CursorTrait>,\n        view: Arc<Mutex<IncrementalView>>,\n        pager: Arc<Pager>,\n        tx_state: Arc<ViewTransactionState>,\n    ) -> Result<Self> {\n        Ok(Self {\n            btree_cursor,\n            view,\n            pager,\n            uncommitted: RowKeyZSet::new(),\n            tx_state,\n            last_tx_state_len: 0,\n            current_row: None,\n            execute_state: ExecuteState::Uninitialized,\n            seek_state: SeekState::Init,\n        })\n    }\n\n    /// Compute transaction changes lazily on first access\n    fn ensure_tx_changes_computed(&mut self) -> Result<IOResult<()>> {\n        // Check if we've already processed the current state\n        let current_len = self.tx_state.len();\n        if current_len == self.last_tx_state_len {\n            return Ok(IOResult::Done(()));\n        }\n\n        // Get the view and the current transaction state\n        let mut view_guard = self.view.lock();\n        let table_deltas = self.tx_state.get_table_deltas();\n\n        // Process the deltas through the circuit to get materialized changes\n        let mut uncommitted = DeltaSet::new();\n        for (table_name, delta) in table_deltas {\n            uncommitted.insert(table_name, delta);\n        }\n\n        let processed_delta = return_if_io!(view_guard.execute_with_uncommitted(\n            uncommitted,\n            self.pager.clone(),\n            &mut self.execute_state\n        ));\n\n        self.uncommitted = RowKeyZSet::from_delta(&processed_delta);\n        self.last_tx_state_len = current_len;\n        Ok(IOResult::Done(()))\n    }\n\n    // Read the current btree entry as a vector (empty if no current position)\n    fn read_btree_delta_entry(&mut self) -> Result<IOResult<Vec<(HashableRow, isize)>>> {\n        let btree_rowid = return_if_io!(self.btree_cursor.rowid());\n        let rowid = match btree_rowid {\n            None => return Ok(IOResult::Done(Vec::new())),\n            Some(rowid) => rowid,\n        };\n\n        let btree_record = return_if_io!(self.btree_cursor.record()).ok_or_else(|| {\n            crate::LimboError::InternalError(\n                \"Invalid data in materialized view: found a rowid, but not the row!\".to_string(),\n            )\n        })?;\n        let mut btree_values = btree_record.get_values_owned()?;\n\n        // The last column should be the weight\n        let weight_value = btree_values.pop().ok_or_else(|| {\n            crate::LimboError::InternalError(\n                \"Invalid data in materialized view: no weight column found\".to_string(),\n            )\n        })?;\n\n        // Convert the Value to isize weight\n        let weight = match weight_value {\n            Value::Numeric(Numeric::Integer(w)) => w as isize,\n            _ => {\n                return Err(crate::LimboError::InternalError(format!(\n                    \"Invalid data in materialized view: expected integer weight, found {weight_value:?}\"\n                )))\n            }\n        };\n\n        if weight <= 0 {\n            return Err(crate::LimboError::InternalError(format!(\n                \"Invalid data in materialized view: expected a positive weight, found {weight}\"\n            )));\n        }\n\n        Ok(IOResult::Done(vec![(\n            HashableRow::new(rowid, btree_values),\n            weight,\n        )]))\n    }\n\n    /// Process btree changes: merge with uncommitted, build zset, and determine result.\n    /// Returns the next state action: either Done with a result, or updates seek_state for another iteration.\n    fn process_btree_changes(\n        &mut self,\n        target: i64,\n        target_rowid: i64,\n        op: SeekOp,\n        changes: Vec<(HashableRow, isize)>,\n    ) -> Result<IOResult<()>> {\n        let mut btree_entries = Delta { changes };\n        let changes = self.uncommitted.seek(target, op);\n\n        let uncommitted_entries = Delta { changes };\n        btree_entries.merge(&uncommitted_entries);\n\n        // if empty pre-zset, means nothing was found. Empty post-zset can mean that\n        // we just canceled weights.\n        if btree_entries.is_empty() {\n            self.seek_state = SeekState::Done;\n            return Ok(IOResult::Done(()));\n        }\n\n        let min_seen = btree_entries\n            .changes\n            .first()\n            .expect(\"cannot be empty, we just tested for it\")\n            .0\n            .rowid;\n        let max_seen = btree_entries\n            .changes\n            .last()\n            .expect(\"cannot be empty, we just tested for it\")\n            .0\n            .rowid;\n\n        let zset = RowKeyZSet::from_delta(&btree_entries);\n        let ret = zset.seek(target_rowid, op);\n\n        if !ret.is_empty() {\n            let (row, _) = &ret[0];\n            self.current_row = Some((row.rowid, row.values.clone()));\n            self.seek_state = SeekState::Done;\n            return Ok(IOResult::Done(()));\n        }\n\n        let new_target = match op {\n            SeekOp::GT => Some(max_seen),\n            SeekOp::GE { eq_only: false } => Some(max_seen + 1),\n            SeekOp::LT => Some(min_seen),\n            SeekOp::LE { eq_only: false } => Some(min_seen - 1),\n            SeekOp::LE { eq_only: true } | SeekOp::GE { eq_only: true } => None,\n        };\n\n        if let Some(target) = new_target {\n            self.seek_state = SeekState::Seek { target };\n        } else {\n            self.seek_state = SeekState::Done;\n        }\n        Ok(IOResult::Done(()))\n    }\n\n    /// Internal seek implementation that doesn't check preconditions\n    fn do_seek(&mut self, target_rowid: i64, op: SeekOp) -> Result<IOResult<SeekResult>> {\n        loop {\n            // Process state machine - need to handle mutable borrow carefully\n            match &mut self.seek_state {\n                SeekState::Init => {\n                    self.current_row = None;\n                    self.seek_state = SeekState::Seek {\n                        target: target_rowid,\n                    };\n                }\n                SeekState::Seek { target } => {\n                    let target = *target;\n                    let btree_result =\n                        return_if_io!(self.btree_cursor.seek(SeekKey::TableRowId(target), op));\n\n                    let changes = match btree_result {\n                        SeekResult::Found => return_if_io!(self.read_btree_delta_entry()),\n                        SeekResult::TryAdvance => {\n                            // Transition to Advancing state before calling next/prev.\n                            // This ensures that if next/prev returns IO, we resume in\n                            // Advancing state and don't redundantly call seek again.\n                            self.seek_state = SeekState::Advancing { target, op };\n                            continue;\n                        }\n                        SeekResult::NotFound => Vec::new(),\n                    };\n\n                    return_if_io!(self.process_btree_changes(target, target_rowid, op, changes));\n\n                    // Check if we're done or need to continue seeking\n                    if matches!(self.seek_state, SeekState::Done) {\n                        let result = if self.current_row.is_some() {\n                            SeekResult::Found\n                        } else {\n                            SeekResult::NotFound\n                        };\n                        return Ok(IOResult::Done(result));\n                    }\n                    // Otherwise state is Seek with new target, loop continues\n                }\n                SeekState::Advancing { target, op } => {\n                    let target = *target;\n                    let op = *op;\n\n                    // Cursor is positioned at the leaf but current entry doesn't match.\n                    // Advance in the appropriate direction to find the next matching entry.\n                    match op {\n                        SeekOp::GT | SeekOp::GE { .. } => {\n                            return_if_io!(self.btree_cursor.next())\n                        }\n                        SeekOp::LT | SeekOp::LE { .. } => {\n                            return_if_io!(self.btree_cursor.prev())\n                        }\n                    };\n                    // read_btree_delta_entry handles the case where cursor is at end\n                    let changes = return_if_io!(self.read_btree_delta_entry());\n\n                    return_if_io!(self.process_btree_changes(target, target_rowid, op, changes));\n\n                    // Check if we're done or need to continue seeking\n                    if matches!(self.seek_state, SeekState::Done) {\n                        let result = if self.current_row.is_some() {\n                            SeekResult::Found\n                        } else {\n                            SeekResult::NotFound\n                        };\n                        return Ok(IOResult::Done(result));\n                    }\n                    // Otherwise state is Seek with new target, loop continues\n                }\n                SeekState::Done => {\n                    // We always return before setting the state to done. Meaning if we got here,\n                    // this is a new seek.\n                    self.seek_state = SeekState::Init;\n                }\n            }\n        }\n    }\n\n    pub fn seek(&mut self, key: SeekKey, op: SeekOp) -> Result<IOResult<SeekResult>> {\n        // Ensure transaction changes are computed\n        return_if_io!(self.ensure_tx_changes_computed());\n\n        let target_rowid = match &key {\n            SeekKey::TableRowId(rowid) => *rowid,\n            SeekKey::IndexKey(_) => {\n                return Err(LimboError::ParseError(\n                    \"Cannot search a materialized view with an index key\".to_string(),\n                ));\n            }\n        };\n\n        self.do_seek(target_rowid, op)\n    }\n\n    pub fn next(&mut self) -> Result<IOResult<bool>> {\n        // If there's a pending seek operation (due to IO), complete it first.\n        // SeekState::Seek or SeekState::Advancing means IO was interrupted mid-seek and we need to resume.\n        // SeekState::Init means cursor was never positioned - don't resume, fall through to check current_row.\n        if matches!(\n            self.seek_state,\n            SeekState::Seek { .. } | SeekState::Advancing { .. }\n        ) {\n            // target is ignored when resuming\n            let result = return_if_io!(self.do_seek(0, SeekOp::GT));\n            return Ok(IOResult::Done(result == SeekResult::Found));\n        }\n\n        // If cursor is not positioned (no current_row), return false\n        // This matches BTreeCursor behavior when valid_state == Invalid\n        let Some((current_rowid, _)) = &self.current_row else {\n            return Ok(IOResult::Done(false));\n        };\n\n        // Use GT to find the next row after current position\n        let result = return_if_io!(self.do_seek(*current_rowid, SeekOp::GT));\n        Ok(IOResult::Done(result == SeekResult::Found))\n    }\n\n    pub fn column(&mut self, col: usize) -> Result<IOResult<Value>> {\n        if let Some((_, ref values)) = self.current_row {\n            Ok(IOResult::Done(\n                values.get(col).cloned().unwrap_or(Value::Null),\n            ))\n        } else {\n            Ok(IOResult::Done(Value::Null))\n        }\n    }\n\n    pub fn rowid(&self) -> Result<IOResult<Option<i64>>> {\n        Ok(IOResult::Done(self.current_row.as_ref().map(|(id, _)| *id)))\n    }\n\n    pub fn rewind(&mut self) -> Result<IOResult<()>> {\n        return_if_io!(self.ensure_tx_changes_computed());\n        // Seek GT from i64::MIN to find the first row using internal do_seek\n        let _result = return_if_io!(self.do_seek(i64::MIN, SeekOp::GT));\n        Ok(IOResult::Done(()))\n    }\n\n    pub fn is_valid(&self) -> Result<bool> {\n        Ok(self.current_row.is_some())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::storage::btree::BTreeCursor;\n    use crate::sync::Arc;\n    use crate::util::IOExt;\n    use crate::{Connection, Database, OpenFlags};\n\n    /// Helper to create a test connection with a table and materialized view\n    fn create_test_connection() -> Result<Arc<Connection>> {\n        // Create an in-memory database with experimental views enabled\n        let io = Arc::new(crate::io::MemoryIO::new());\n        let db = Database::open_file_with_flags(\n            io,\n            \":memory:\",\n            OpenFlags::default(),\n            crate::DatabaseOpts {\n                enable_views: true,\n                enable_custom_types: false,\n                enable_load_extension: false,\n                enable_encryption: false,\n                enable_index_method: false,\n                enable_autovacuum: false,\n                enable_attach: false,\n                unsafe_testing: false,\n            },\n            None,\n        )?;\n        let conn = db.connect()?;\n\n        // Create a test table\n        conn.execute(\"CREATE TABLE test_table (id INTEGER PRIMARY KEY, value INTEGER)\")?;\n\n        // Create materialized view\n        conn.execute(\"CREATE MATERIALIZED VIEW test_view AS SELECT id, value FROM test_table\")?;\n\n        Ok(conn)\n    }\n\n    /// Helper to create a test cursor for the materialized view\n    fn create_test_cursor(\n        conn: &Arc<Connection>,\n    ) -> Result<(\n        MaterializedViewCursor,\n        Arc<ViewTransactionState>,\n        Arc<Pager>,\n    )> {\n        // Get the schema and view\n        let view_mutex = conn\n            .schema\n            .read()\n            .get_materialized_view(\"test_view\")\n            .ok_or_else(|| crate::LimboError::InternalError(\"View not found\".to_string()))?;\n\n        // Get the view's root page\n        let view = view_mutex.lock();\n        let root_page = view.get_root_page();\n        if root_page == 0 {\n            return Err(crate::LimboError::InternalError(\n                \"View not materialized\".to_string(),\n            ));\n        }\n        let num_columns = view.column_schema.columns.len();\n        drop(view);\n\n        // Create a btree cursor\n        let pager = conn.get_pager();\n        let btree_cursor = Box::new(BTreeCursor::new(pager.clone(), root_page, num_columns));\n\n        // Get or create transaction state for this view\n        let tx_state = conn.view_transaction_states.get_or_create(\"test_view\");\n\n        // Create the materialized view cursor\n        let cursor = MaterializedViewCursor::new(\n            btree_cursor,\n            view_mutex.clone(),\n            pager.clone(),\n            tx_state.clone(),\n        )?;\n\n        Ok((cursor, tx_state, pager))\n    }\n\n    /// Helper to populate test table with data through SQL\n    fn populate_test_table(conn: &Arc<Connection>, rows: Vec<(i64, i64)>) -> Result<()> {\n        for (id, value) in rows {\n            let sql = format!(\"INSERT INTO test_table (id, value) VALUES ({id}, {value})\");\n            conn.execute(&sql)?;\n        }\n        Ok(())\n    }\n\n    /// Helper to apply changes through ViewTransactionState\n    fn apply_changes_to_tx_state(\n        tx_state: &ViewTransactionState,\n        changes: Vec<(i64, Vec<Value>, isize)>,\n    ) {\n        for (rowid, values, weight) in changes {\n            if weight > 0 {\n                tx_state.insert(\"test_table\", rowid, values);\n            } else if weight < 0 {\n                tx_state.delete(\"test_table\", rowid, values);\n            }\n        }\n    }\n\n    #[test]\n    fn test_seek_key_exists_in_btree() -> Result<()> {\n        let conn = create_test_connection()?;\n\n        // Populate table with test data: rows 1, 3, 5, 7\n        populate_test_table(&conn, vec![(1, 10), (3, 30), (5, 50), (7, 70)])?;\n\n        // Create cursor for testing\n        let (mut cursor, _tx_state, pager) = create_test_cursor(&conn)?;\n\n        // No uncommitted changes - tx_state is already empty\n\n        // Test 1: Seek exact match (row 3)\n        let result = pager\n            .io\n            .block(|| cursor.seek(SeekKey::TableRowId(3), SeekOp::GE { eq_only: true }))?;\n        assert_eq!(result, SeekResult::Found);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(3));\n\n        // Test 2: Seek GE (row 4 should find row 5)\n        let result = pager\n            .io\n            .block(|| cursor.seek(SeekKey::TableRowId(4), SeekOp::GE { eq_only: false }))?;\n        assert_eq!(result, SeekResult::Found);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(5));\n\n        // Test 3: Seek GT (row 3 should find row 5)\n        let result = pager\n            .io\n            .block(|| cursor.seek(SeekKey::TableRowId(3), SeekOp::GT))?;\n        assert_eq!(result, SeekResult::Found);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(5));\n\n        // Test 4: Seek LE (row 4 should find row 3)\n        let result = pager\n            .io\n            .block(|| cursor.seek(SeekKey::TableRowId(4), SeekOp::LE { eq_only: false }))?;\n        assert_eq!(result, SeekResult::Found);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(3));\n\n        // Test 5: Seek LT (row 5 should find row 3)\n        let result = pager\n            .io\n            .block(|| cursor.seek(SeekKey::TableRowId(5), SeekOp::LT))?;\n        assert_eq!(result, SeekResult::Found);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(3));\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_seek_key_exists_only_uncommitted() -> Result<()> {\n        let conn = create_test_connection()?;\n\n        // Populate table with rows 1, 5, 7\n        populate_test_table(&conn, vec![(1, 10), (5, 50), (7, 70)])?;\n\n        // Create cursor for testing\n        let (mut cursor, tx_state, pager) = create_test_cursor(&conn)?;\n\n        // Add uncommitted changes: insert rows 3 and 6\n        apply_changes_to_tx_state(\n            &tx_state,\n            vec![\n                (3, vec![Value::from_i64(3), Value::from_i64(30)], 1), // Insert row 3\n                (6, vec![Value::from_i64(6), Value::from_i64(60)], 1), // Insert row 6\n            ],\n        );\n\n        // Test 1: Seek exact match for uncommitted row 3\n        let result = pager\n            .io\n            .block(|| cursor.seek(SeekKey::TableRowId(3), SeekOp::GE { eq_only: true }))?;\n        assert_eq!(result, SeekResult::Found);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(3));\n        assert_eq!(pager.io.block(|| cursor.column(1))?, Value::from_i64(30));\n\n        // Test 2: Seek GE for row 2 should find uncommitted row 3\n        let result = pager\n            .io\n            .block(|| cursor.seek(SeekKey::TableRowId(2), SeekOp::GE { eq_only: false }))?;\n        assert_eq!(result, SeekResult::Found);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(3));\n\n        // Test 3: Seek GT for row 5 should find uncommitted row 6\n        let result = pager\n            .io\n            .block(|| cursor.seek(SeekKey::TableRowId(5), SeekOp::GT))?;\n        assert_eq!(result, SeekResult::Found);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(6));\n        assert_eq!(pager.io.block(|| cursor.column(1))?, Value::from_i64(60));\n\n        // Test 4: Seek LE for row 6 should find uncommitted row 6\n        let result = pager\n            .io\n            .block(|| cursor.seek(SeekKey::TableRowId(6), SeekOp::LE { eq_only: false }))?;\n        assert_eq!(result, SeekResult::Found);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(6));\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_seek_key_deleted_by_uncommitted() -> Result<()> {\n        let conn = create_test_connection()?;\n\n        // Populate table with rows 1, 3, 5, 7\n        populate_test_table(&conn, vec![(1, 10), (3, 30), (5, 50), (7, 70)])?;\n\n        // Create cursor for testing\n        let (mut cursor, tx_state, pager) = create_test_cursor(&conn)?;\n\n        // Delete row 3 and 5 in uncommitted changes\n        apply_changes_to_tx_state(\n            &tx_state,\n            vec![\n                (3, vec![Value::from_i64(3), Value::from_i64(30)], -1), // Delete row 3\n                (5, vec![Value::from_i64(5), Value::from_i64(50)], -1), // Delete row 5\n            ],\n        );\n\n        // Test 1: Seek exact match for deleted row 3 should not find it\n        let result = pager\n            .io\n            .block(|| cursor.seek(SeekKey::TableRowId(3), SeekOp::GE { eq_only: true }))?;\n        assert_eq!(result, SeekResult::NotFound);\n\n        // Test 2: Seek GE for row 2 should skip deleted row 3 and find row 7\n        let result = pager\n            .io\n            .block(|| cursor.seek(SeekKey::TableRowId(2), SeekOp::GE { eq_only: false }))?;\n        assert_eq!(result, SeekResult::Found);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(7));\n\n        // Test 3: Seek GT for row 1 should skip deleted rows and find row 7\n        let result = pager\n            .io\n            .block(|| cursor.seek(SeekKey::TableRowId(1), SeekOp::GT))?;\n        assert_eq!(result, SeekResult::Found);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(7));\n\n        // Test 4: Seek LE for row 5 should find row 1 (skipping deleted 3 and 5)\n        let result = pager\n            .io\n            .block(|| cursor.seek(SeekKey::TableRowId(5), SeekOp::LE { eq_only: false }))?;\n        assert_eq!(result, SeekResult::Found);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(1));\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_seek_with_updates() -> Result<()> {\n        let conn = create_test_connection()?;\n\n        // Populate table with rows 1, 3, 5\n        populate_test_table(&conn, vec![(1, 10), (3, 30), (5, 50)])?;\n\n        // Create cursor for testing\n        let (mut cursor, tx_state, pager) = create_test_cursor(&conn)?;\n\n        // Update row 3 (delete old + insert new)\n        apply_changes_to_tx_state(\n            &tx_state,\n            vec![\n                (3, vec![Value::from_i64(3), Value::from_i64(30)], -1), // Delete old row 3\n                (3, vec![Value::from_i64(3), Value::from_i64(35)], 1),  // Insert new row 3\n            ],\n        );\n\n        // Test: Seek for updated row 3 should find it\n        let result = pager\n            .io\n            .block(|| cursor.seek(SeekKey::TableRowId(3), SeekOp::GE { eq_only: true }))?;\n        assert_eq!(result, SeekResult::Found);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(3));\n        // The values should be from the uncommitted set (35 instead of 30)\n        assert_eq!(pager.io.block(|| cursor.column(1))?, Value::from_i64(35));\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_seek_boundary_conditions() -> Result<()> {\n        let conn = create_test_connection()?;\n\n        // Populate table with rows 5, 10\n        populate_test_table(&conn, vec![(5, 50), (10, 100)])?;\n\n        // Create cursor for testing\n        let (mut cursor, _tx_state, pager) = create_test_cursor(&conn)?;\n\n        // No uncommitted changes - tx_state is already empty\n\n        // Test 1: Seek LT for minimum value (should find nothing)\n        let result = pager\n            .io\n            .block(|| cursor.seek(SeekKey::TableRowId(1), SeekOp::LT))?;\n        assert_eq!(result, SeekResult::NotFound);\n\n        // Test 2: Seek GT for maximum value (should find nothing)\n        let result = pager\n            .io\n            .block(|| cursor.seek(SeekKey::TableRowId(15), SeekOp::GT))?;\n        assert_eq!(result, SeekResult::NotFound);\n\n        // Test 3: Seek exact for non-existent key\n        let result = pager\n            .io\n            .block(|| cursor.seek(SeekKey::TableRowId(7), SeekOp::GE { eq_only: true }))?;\n        assert_eq!(result, SeekResult::NotFound);\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_seek_complex_uncommitted_weights() -> Result<()> {\n        let conn = create_test_connection()?;\n\n        // Populate table with row 5\n        populate_test_table(&conn, vec![(5, 50)])?;\n\n        // Create cursor for testing\n        let (mut cursor, tx_state, pager) = create_test_cursor(&conn)?;\n\n        // Complex uncommitted changes with multiple operations on same row\n        apply_changes_to_tx_state(\n            &tx_state,\n            vec![\n                (5, vec![Value::from_i64(5), Value::from_i64(50)], -1), // Delete original\n                (5, vec![Value::from_i64(5), Value::from_i64(51)], 1),  // Insert update 1\n                (5, vec![Value::from_i64(5), Value::from_i64(51)], -1), // Delete update 1\n                (5, vec![Value::from_i64(5), Value::from_i64(52)], 1),  // Insert update 2\n                                                                        // Net effect: row 5 exists with value 52\n            ],\n        );\n\n        // Seek for row 5 should find it (net weight = 1 from btree + 0 from uncommitted = 1)\n        let result = pager\n            .io\n            .block(|| cursor.seek(SeekKey::TableRowId(5), SeekOp::GE { eq_only: true }))?;\n        assert_eq!(result, SeekResult::Found);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(5));\n        // The final value should be 52 from the last update\n        assert_eq!(pager.io.block(|| cursor.column(1))?, Value::from_i64(52));\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_seek_affected_by_transaction_state_changes() -> Result<()> {\n        let conn = create_test_connection()?;\n\n        // Populate table with rows 1 and 3\n        populate_test_table(&conn, vec![(1, 10), (3, 30)])?;\n\n        // Create cursor for testing\n        let (mut cursor, tx_state, pager) = create_test_cursor(&conn)?;\n\n        // Seek for row 2 - doesn't exist\n        let result = pager\n            .io\n            .block(|| cursor.seek(SeekKey::TableRowId(2), SeekOp::GE { eq_only: true }))?;\n        assert_eq!(result, SeekResult::NotFound);\n\n        // Add row 2 to uncommitted\n        tx_state.insert(\n            \"test_table\",\n            2,\n            vec![Value::from_i64(2), Value::from_i64(20)],\n        );\n\n        // Now seek for row 2 finds it\n        let result = pager\n            .io\n            .block(|| cursor.seek(SeekKey::TableRowId(2), SeekOp::GE { eq_only: true }))?;\n        assert_eq!(result, SeekResult::Found);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(2));\n        assert_eq!(pager.io.block(|| cursor.column(1))?, Value::from_i64(20));\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_rewind_btree_first_uncommitted_later() -> Result<()> {\n        let conn = create_test_connection()?;\n\n        // Populate table with rows 1, 3, 5\n        populate_test_table(&conn, vec![(1, 10), (3, 30), (5, 50)])?;\n\n        // Create cursor for testing\n        let (mut cursor, tx_state, pager) = create_test_cursor(&conn)?;\n\n        // Add uncommitted rows 8, 10 (all larger than btree rows)\n        apply_changes_to_tx_state(\n            &tx_state,\n            vec![\n                (8, vec![Value::from_i64(8), Value::from_i64(80)], 1),\n                (10, vec![Value::from_i64(10), Value::from_i64(100)], 1),\n            ],\n        );\n\n        // Initially cursor is not positioned\n        assert!(!cursor.is_valid()?);\n\n        // Rewind should position at first btree row (1) since uncommitted are all larger\n        pager.io.block(|| cursor.rewind())?;\n        assert!(cursor.is_valid()?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(1));\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_rewind_with_uncommitted_first() -> Result<()> {\n        let conn = create_test_connection()?;\n\n        // Populate table with rows 5, 7\n        populate_test_table(&conn, vec![(5, 50), (7, 70)])?;\n\n        // Create cursor for testing\n        let (mut cursor, tx_state, pager) = create_test_cursor(&conn)?;\n\n        // Add uncommitted row 2 (smaller than any btree row)\n        apply_changes_to_tx_state(\n            &tx_state,\n            vec![(2, vec![Value::from_i64(2), Value::from_i64(20)], 1)],\n        );\n\n        // Rewind should position at row 2 (uncommitted)\n        pager.io.block(|| cursor.rewind())?;\n        assert!(cursor.is_valid()?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(2));\n        assert_eq!(pager.io.block(|| cursor.column(1))?, Value::from_i64(20));\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_rewind_skip_deleted_first() -> Result<()> {\n        let conn = create_test_connection()?;\n\n        // Populate table with rows 1, 3, 5\n        populate_test_table(&conn, vec![(1, 10), (3, 30), (5, 50)])?;\n\n        // Create cursor for testing\n        let (mut cursor, tx_state, pager) = create_test_cursor(&conn)?;\n\n        // Delete row 1 in uncommitted\n        apply_changes_to_tx_state(\n            &tx_state,\n            vec![(1, vec![Value::from_i64(1), Value::from_i64(10)], -1)],\n        );\n\n        // Rewind should skip deleted row 1 and position at row 3\n        pager.io.block(|| cursor.rewind())?;\n        assert!(cursor.is_valid()?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(3));\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_rewind_empty_btree_with_uncommitted() -> Result<()> {\n        let conn = create_test_connection()?;\n\n        // Create cursor for testing\n        let (mut cursor, tx_state, pager) = create_test_cursor(&conn)?;\n\n        // Add uncommitted rows (no btree data)\n        apply_changes_to_tx_state(\n            &tx_state,\n            vec![\n                (3, vec![Value::from_i64(3), Value::from_i64(30)], 1),\n                (7, vec![Value::from_i64(7), Value::from_i64(70)], 1),\n            ],\n        );\n\n        // Rewind should find first uncommitted row\n        pager.io.block(|| cursor.rewind())?;\n        assert!(cursor.is_valid()?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(3));\n        assert_eq!(pager.io.block(|| cursor.column(1))?, Value::from_i64(30));\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_rewind_all_deleted() -> Result<()> {\n        let conn = create_test_connection()?;\n\n        // Populate table with rows 2, 4\n        populate_test_table(&conn, vec![(2, 20), (4, 40)])?;\n\n        // Create cursor for testing\n        let (mut cursor, tx_state, pager) = create_test_cursor(&conn)?;\n\n        // Delete all rows in uncommitted\n        apply_changes_to_tx_state(\n            &tx_state,\n            vec![\n                (2, vec![Value::from_i64(2), Value::from_i64(20)], -1),\n                (4, vec![Value::from_i64(4), Value::from_i64(40)], -1),\n            ],\n        );\n\n        // Rewind should find no valid rows\n        pager.io.block(|| cursor.rewind())?;\n        assert!(!cursor.is_valid()?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, None);\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_rewind_with_updates() -> Result<()> {\n        let conn = create_test_connection()?;\n\n        // Populate table with rows 1, 3\n        populate_test_table(&conn, vec![(1, 10), (3, 30)])?;\n\n        // Create cursor for testing\n        let (mut cursor, tx_state, pager) = create_test_cursor(&conn)?;\n\n        // Update row 1 (delete + insert with new value)\n        apply_changes_to_tx_state(\n            &tx_state,\n            vec![\n                (1, vec![Value::from_i64(1), Value::from_i64(10)], -1),\n                (1, vec![Value::from_i64(1), Value::from_i64(15)], 1),\n            ],\n        );\n\n        // Rewind should position at row 1 with updated value\n        pager.io.block(|| cursor.rewind())?;\n        assert!(cursor.is_valid()?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(1));\n        assert_eq!(pager.io.block(|| cursor.column(1))?, Value::from_i64(15));\n\n        Ok(())\n    }\n\n    // ===== NEXT() TEST SUITE =====\n\n    #[test]\n    fn test_next_btree_only_sequential() -> Result<()> {\n        let conn = create_test_connection()?;\n\n        // Populate table with rows 1, 3, 5, 7\n        populate_test_table(&conn, vec![(1, 10), (3, 30), (5, 50), (7, 70)])?;\n\n        // Create cursor for testing\n        let (mut cursor, _tx_state, pager) = create_test_cursor(&conn)?;\n\n        // Start with rewind to position at first row\n        pager.io.block(|| cursor.rewind())?;\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(1));\n\n        // Next should move to row 3\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(3));\n\n        // Next should move to row 5\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(5));\n\n        // Next should move to row 7\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(7));\n\n        // Next should reach end\n        assert!(!pager.io.block(|| cursor.next())?);\n        assert!(!cursor.is_valid()?);\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_next_uncommitted_only() -> Result<()> {\n        let conn = create_test_connection()?;\n\n        // Create cursor for testing (no btree data)\n        let (mut cursor, tx_state, pager) = create_test_cursor(&conn)?;\n\n        // Add uncommitted rows 2, 4, 6\n        apply_changes_to_tx_state(\n            &tx_state,\n            vec![\n                (2, vec![Value::from_i64(2), Value::from_i64(20)], 1),\n                (4, vec![Value::from_i64(4), Value::from_i64(40)], 1),\n                (6, vec![Value::from_i64(6), Value::from_i64(60)], 1),\n            ],\n        );\n\n        // Start with rewind to position at first row\n        pager.io.block(|| cursor.rewind())?;\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(2));\n\n        // Next should move to row 4\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(4));\n\n        // Next should move to row 6\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(6));\n\n        // Next should reach end\n        assert!(!pager.io.block(|| cursor.next())?);\n        assert!(!cursor.is_valid()?);\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_next_mixed_btree_uncommitted() -> Result<()> {\n        let conn = create_test_connection()?;\n\n        // Populate table with rows 1, 5, 9\n        populate_test_table(&conn, vec![(1, 10), (5, 50), (9, 90)])?;\n\n        // Create cursor for testing\n        let (mut cursor, tx_state, pager) = create_test_cursor(&conn)?;\n\n        // Add uncommitted rows 3, 7\n        apply_changes_to_tx_state(\n            &tx_state,\n            vec![\n                (3, vec![Value::from_i64(3), Value::from_i64(30)], 1),\n                (7, vec![Value::from_i64(7), Value::from_i64(70)], 1),\n            ],\n        );\n\n        // Should iterate in order: 1, 3, 5, 7, 9\n        pager.io.block(|| cursor.rewind())?;\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(1));\n\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(3));\n\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(5));\n\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(7));\n\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(9));\n\n        assert!(!pager.io.block(|| cursor.next())?);\n        assert!(!cursor.is_valid()?);\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_next_skip_deleted_rows() -> Result<()> {\n        let conn = create_test_connection()?;\n\n        // Populate table with rows 1, 2, 3, 4, 5\n        populate_test_table(&conn, vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50)])?;\n\n        // Create cursor for testing\n        let (mut cursor, tx_state, pager) = create_test_cursor(&conn)?;\n\n        // Delete rows 2 and 4 in uncommitted\n        apply_changes_to_tx_state(\n            &tx_state,\n            vec![\n                (2, vec![Value::from_i64(2), Value::from_i64(20)], -1),\n                (4, vec![Value::from_i64(4), Value::from_i64(40)], -1),\n            ],\n        );\n\n        // Should iterate: 1, 3, 5 (skipping deleted 2 and 4)\n        pager.io.block(|| cursor.rewind())?;\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(1));\n\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(3));\n\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(5));\n\n        assert!(!pager.io.block(|| cursor.next())?);\n        assert!(!cursor.is_valid()?);\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_next_with_updates() -> Result<()> {\n        let conn = create_test_connection()?;\n\n        // Populate table with rows 1, 3, 5\n        populate_test_table(&conn, vec![(1, 10), (3, 30), (5, 50)])?;\n\n        // Create cursor for testing\n        let (mut cursor, tx_state, pager) = create_test_cursor(&conn)?;\n\n        // Update row 3 (delete old + insert new)\n        apply_changes_to_tx_state(\n            &tx_state,\n            vec![\n                (3, vec![Value::from_i64(3), Value::from_i64(30)], -1),\n                (3, vec![Value::from_i64(3), Value::from_i64(35)], 1),\n            ],\n        );\n\n        // Should iterate all rows with updated values\n        pager.io.block(|| cursor.rewind())?;\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(1));\n\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(3));\n        assert_eq!(pager.io.block(|| cursor.column(1))?, Value::from_i64(35)); // Updated value\n\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(5));\n\n        assert!(!pager.io.block(|| cursor.next())?);\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_next_from_uninitialized() -> Result<()> {\n        let conn = create_test_connection()?;\n\n        // Populate table with rows 2, 4\n        populate_test_table(&conn, vec![(2, 20), (4, 40)])?;\n\n        // Create cursor for testing\n        let (mut cursor, _tx_state, pager) = create_test_cursor(&conn)?;\n\n        // Cursor not positioned initially\n        assert!(!cursor.is_valid()?);\n\n        // Next on uninitialized cursor should return false (matching BTreeCursor behavior)\n        assert!(!pager.io.block(|| cursor.next())?);\n        assert!(!cursor.is_valid()?);\n\n        // Position cursor with rewind first\n        pager.io.block(|| cursor.rewind())?;\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(2));\n\n        // Now next should work\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(4));\n\n        assert!(!pager.io.block(|| cursor.next())?);\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_next_empty_table() -> Result<()> {\n        let conn = create_test_connection()?;\n\n        // Create cursor for testing (empty table)\n        let (mut cursor, _tx_state, pager) = create_test_cursor(&conn)?;\n\n        // Next on empty table should return false\n        assert!(!pager.io.block(|| cursor.next())?);\n        assert!(!cursor.is_valid()?);\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_next_all_deleted() -> Result<()> {\n        let conn = create_test_connection()?;\n\n        // Populate table with rows 1, 2, 3\n        populate_test_table(&conn, vec![(1, 10), (2, 20), (3, 30)])?;\n\n        // Create cursor for testing\n        let (mut cursor, tx_state, pager) = create_test_cursor(&conn)?;\n\n        // Delete all rows\n        apply_changes_to_tx_state(\n            &tx_state,\n            vec![\n                (1, vec![Value::from_i64(1), Value::from_i64(10)], -1),\n                (2, vec![Value::from_i64(2), Value::from_i64(20)], -1),\n                (3, vec![Value::from_i64(3), Value::from_i64(30)], -1),\n            ],\n        );\n\n        // Next should find nothing\n        assert!(!pager.io.block(|| cursor.next())?);\n        assert!(!cursor.is_valid()?);\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_next_complex_interleaving() -> Result<()> {\n        let conn = create_test_connection()?;\n\n        // Populate table with rows 2, 4, 6, 8\n        populate_test_table(&conn, vec![(2, 20), (4, 40), (6, 60), (8, 80)])?;\n\n        // Create cursor for testing\n        let (mut cursor, tx_state, pager) = create_test_cursor(&conn)?;\n\n        // Complex changes:\n        // - Insert row 1\n        // - Delete row 2\n        // - Insert row 3\n        // - Update row 4\n        // - Insert row 5\n        // - Delete row 6\n        // - Insert row 7\n        // - Keep row 8 as-is\n        // - Insert row 9\n        apply_changes_to_tx_state(\n            &tx_state,\n            vec![\n                (1, vec![Value::from_i64(1), Value::from_i64(10)], 1), // Insert 1\n                (2, vec![Value::from_i64(2), Value::from_i64(20)], -1), // Delete 2\n                (3, vec![Value::from_i64(3), Value::from_i64(30)], 1), // Insert 3\n                (4, vec![Value::from_i64(4), Value::from_i64(40)], -1), // Delete old 4\n                (4, vec![Value::from_i64(4), Value::from_i64(45)], 1), // Insert new 4\n                (5, vec![Value::from_i64(5), Value::from_i64(50)], 1), // Insert 5\n                (6, vec![Value::from_i64(6), Value::from_i64(60)], -1), // Delete 6\n                (7, vec![Value::from_i64(7), Value::from_i64(70)], 1), // Insert 7\n                (9, vec![Value::from_i64(9), Value::from_i64(90)], 1), // Insert 9\n            ],\n        );\n\n        // Should iterate: 1, 3, 4(updated), 5, 7, 8, 9\n        pager.io.block(|| cursor.rewind())?;\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(1));\n\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(3));\n\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(4));\n        assert_eq!(pager.io.block(|| cursor.column(1))?, Value::from_i64(45)); // Updated value\n\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(5));\n\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(7));\n\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(8));\n\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(9));\n\n        assert!(!pager.io.block(|| cursor.next())?);\n        assert!(!cursor.is_valid()?);\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_next_after_seek() -> Result<()> {\n        let conn = create_test_connection()?;\n\n        // Populate table with rows 1, 3, 5, 7, 9\n        populate_test_table(&conn, vec![(1, 10), (3, 30), (5, 50), (7, 70), (9, 90)])?;\n\n        // Create cursor for testing\n        let (mut cursor, _tx_state, pager) = create_test_cursor(&conn)?;\n\n        // Seek to row 5\n        let result = pager\n            .io\n            .block(|| cursor.seek(SeekKey::TableRowId(5), SeekOp::GE { eq_only: true }))?;\n        assert_eq!(result, SeekResult::Found);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(5));\n\n        // Next should move to row 7\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(7));\n\n        // Next should move to row 9\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(9));\n\n        // Next should reach end\n        assert!(!pager.io.block(|| cursor.next())?);\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_next_multiple_weights_same_row() -> Result<()> {\n        let conn = create_test_connection()?;\n\n        // Populate table with row 1\n        populate_test_table(&conn, vec![(1, 10)])?;\n\n        // Create cursor for testing\n        let (mut cursor, tx_state, pager) = create_test_cursor(&conn)?;\n\n        // Multiple operations on same row:\n        apply_changes_to_tx_state(\n            &tx_state,\n            vec![\n                (1, vec![Value::from_i64(1), Value::from_i64(10)], -1), // Delete original\n                (1, vec![Value::from_i64(1), Value::from_i64(11)], 1),  // Insert v1\n                (1, vec![Value::from_i64(1), Value::from_i64(11)], -1), // Delete v1\n                (1, vec![Value::from_i64(1), Value::from_i64(12)], 1),  // Insert v2\n                (1, vec![Value::from_i64(1), Value::from_i64(12)], -1), // Delete v2\n                                                                        // Net weight: 1 (btree) - 1 + 1 - 1 + 1 - 1 = 0 (row deleted)\n            ],\n        );\n\n        // Row should be deleted\n        assert!(!pager.io.block(|| cursor.next())?);\n        assert!(!cursor.is_valid()?);\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_next_only_uncommitted_large_gaps() -> Result<()> {\n        let conn = create_test_connection()?;\n\n        // Create cursor for testing (no btree data)\n        let (mut cursor, tx_state, pager) = create_test_cursor(&conn)?;\n\n        // Add uncommitted rows with large gaps\n        apply_changes_to_tx_state(\n            &tx_state,\n            vec![\n                (100, vec![Value::from_i64(100), Value::from_i64(1000)], 1),\n                (500, vec![Value::from_i64(500), Value::from_i64(5000)], 1),\n                (999, vec![Value::from_i64(999), Value::from_i64(9990)], 1),\n            ],\n        );\n\n        // Should iterate through all with large gaps\n        pager.io.block(|| cursor.rewind())?;\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(100));\n\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(500));\n\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(999));\n\n        assert!(!pager.io.block(|| cursor.next())?);\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_multiple_updates_same_row_single_transaction() -> Result<()> {\n        let conn = create_test_connection()?;\n\n        // Populate table with rows 1, 2, 3\n        populate_test_table(&conn, vec![(1, 10), (2, 20), (3, 30)])?;\n\n        // Create cursor for testing\n        let (mut cursor, tx_state, pager) = create_test_cursor(&conn)?;\n\n        // Multiple successive updates to row 2 in the same transaction\n        // 20 -> 25 -> 28 -> 32 (final value should be 32)\n        apply_changes_to_tx_state(\n            &tx_state,\n            vec![\n                (2, vec![Value::from_i64(2), Value::from_i64(20)], -1), // Delete original\n                (2, vec![Value::from_i64(2), Value::from_i64(25)], 1),  // First update\n                (2, vec![Value::from_i64(2), Value::from_i64(25)], -1), // Delete first update\n                (2, vec![Value::from_i64(2), Value::from_i64(28)], 1),  // Second update\n                (2, vec![Value::from_i64(2), Value::from_i64(28)], -1), // Delete second update\n                (2, vec![Value::from_i64(2), Value::from_i64(32)], 1),  // Final update\n            ],\n        );\n\n        // Seek to row 2 should find the final value\n        let result = pager\n            .io\n            .block(|| cursor.seek(SeekKey::TableRowId(2), SeekOp::GE { eq_only: true }))?;\n        assert_eq!(result, SeekResult::Found);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(2));\n        assert_eq!(pager.io.block(|| cursor.column(1))?, Value::from_i64(32));\n\n        // Next through all rows to verify only final values are seen\n        pager.io.block(|| cursor.rewind())?;\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(1));\n        assert_eq!(pager.io.block(|| cursor.column(1))?, Value::from_i64(10));\n\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(2));\n        assert_eq!(pager.io.block(|| cursor.column(1))?, Value::from_i64(32)); // Final value\n\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(3));\n        assert_eq!(pager.io.block(|| cursor.column(1))?, Value::from_i64(30));\n\n        assert!(!pager.io.block(|| cursor.next())?);\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_empty_materialized_view_with_uncommitted() -> Result<()> {\n        let conn = create_test_connection()?;\n\n        // Don't populate any data - view is created but empty\n        // This tests a materialized view that was never populated\n\n        // Create cursor for testing\n        let (mut cursor, tx_state, pager) = create_test_cursor(&conn)?;\n\n        // Add uncommitted rows to empty materialized view\n        apply_changes_to_tx_state(\n            &tx_state,\n            vec![\n                (5, vec![Value::from_i64(5), Value::from_i64(50)], 1),\n                (10, vec![Value::from_i64(10), Value::from_i64(100)], 1),\n                (15, vec![Value::from_i64(15), Value::from_i64(150)], 1),\n            ],\n        );\n\n        // Test seek on empty materialized view with uncommitted data\n        let result = pager\n            .io\n            .block(|| cursor.seek(SeekKey::TableRowId(10), SeekOp::GE { eq_only: true }))?;\n        assert_eq!(result, SeekResult::Found);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(10));\n\n        // Test GT seek\n        let result = pager\n            .io\n            .block(|| cursor.seek(SeekKey::TableRowId(7), SeekOp::GT))?;\n        assert_eq!(result, SeekResult::Found);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(10));\n\n        // Test rewind and next\n        pager.io.block(|| cursor.rewind())?;\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(5));\n\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(10));\n\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(15));\n\n        assert!(!pager.io.block(|| cursor.next())?);\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_exact_match_btree_uncommitted_same_rowid_different_values() -> Result<()> {\n        let conn = create_test_connection()?;\n\n        // Populate table with rows 1, 3, 5\n        populate_test_table(&conn, vec![(1, 10), (3, 30), (5, 50)])?;\n\n        // Create cursor for testing\n        let (mut cursor, tx_state, pager) = create_test_cursor(&conn)?;\n\n        // Add uncommitted row 3 with different value (not a delete+insert, just insert)\n        // This simulates a case where uncommitted has a new version of row 3\n        apply_changes_to_tx_state(\n            &tx_state,\n            vec![\n                (3, vec![Value::from_i64(3), Value::from_i64(35)], 1), // New version with positive weight\n            ],\n        );\n\n        // Exact match seek for row 3 should find the uncommitted version (35)\n        // because when both exist with positive weight, uncommitted takes precedence\n        let result = pager\n            .io\n            .block(|| cursor.seek(SeekKey::TableRowId(3), SeekOp::GE { eq_only: true }))?;\n        assert_eq!(result, SeekResult::Found);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(3));\n\n        // This test verifies which value we get when both btree and uncommitted\n        // have the same rowid with positive weights\n        // The expected behavior needs to be defined - typically uncommitted wins\n        // or they get merged based on the DBSP semantics\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_boundary_value_seeks() -> Result<()> {\n        let conn = create_test_connection()?;\n\n        // Populate table with some normal values\n        populate_test_table(&conn, vec![(100, 1000), (200, 2000)])?;\n\n        // Create cursor for testing\n        let (mut cursor, tx_state, pager) = create_test_cursor(&conn)?;\n\n        // Add uncommitted rows at extreme positions\n        apply_changes_to_tx_state(\n            &tx_state,\n            vec![\n                (\n                    i64::MIN + 1,\n                    vec![Value::from_i64(i64::MIN + 1), Value::from_i64(-999)],\n                    1,\n                ),\n                (\n                    i64::MAX - 1,\n                    vec![Value::from_i64(i64::MAX - 1), Value::from_i64(999)],\n                    1,\n                ),\n            ],\n        );\n\n        // Test 1: Seek GT with i64::MAX should find nothing\n        let result = pager\n            .io\n            .block(|| cursor.seek(SeekKey::TableRowId(i64::MAX), SeekOp::GT))?;\n        assert_eq!(result, SeekResult::NotFound);\n\n        // Test 2: Seek LT with i64::MIN should find nothing\n        let result = pager\n            .io\n            .block(|| cursor.seek(SeekKey::TableRowId(i64::MIN), SeekOp::LT))?;\n        assert_eq!(result, SeekResult::NotFound);\n\n        // Test 3: Seek GE with i64::MAX - 1 should find our extreme row\n        let result = pager.io.block(|| {\n            cursor.seek(\n                SeekKey::TableRowId(i64::MAX - 1),\n                SeekOp::GE { eq_only: false },\n            )\n        })?;\n        assert_eq!(result, SeekResult::Found);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(i64::MAX - 1));\n\n        // Test 4: Seek LE with i64::MIN + 1 should find our extreme low row\n        let result = pager.io.block(|| {\n            cursor.seek(\n                SeekKey::TableRowId(i64::MIN + 1),\n                SeekOp::LE { eq_only: false },\n            )\n        })?;\n        assert_eq!(result, SeekResult::Found);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(i64::MIN + 1));\n\n        // Test 5: Seek GT from i64::MIN should find the smallest row\n        let result = pager\n            .io\n            .block(|| cursor.seek(SeekKey::TableRowId(i64::MIN), SeekOp::GT))?;\n        assert_eq!(result, SeekResult::Found);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(i64::MIN + 1));\n\n        // Test 6: Seek LT from i64::MAX should find the largest row\n        let result = pager\n            .io\n            .block(|| cursor.seek(SeekKey::TableRowId(i64::MAX), SeekOp::LT))?;\n        assert_eq!(result, SeekResult::Found);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(i64::MAX - 1));\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_next_concurrent_btree_uncommitted_advance() -> Result<()> {\n        let conn = create_test_connection()?;\n\n        // Populate table with rows 1, 2, 3, 4, 5\n        populate_test_table(&conn, vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50)])?;\n\n        // Create cursor for testing\n        let (mut cursor, tx_state, pager) = create_test_cursor(&conn)?;\n\n        // Delete some btree rows and add replacements in uncommitted\n        apply_changes_to_tx_state(\n            &tx_state,\n            vec![\n                (2, vec![Value::from_i64(2), Value::from_i64(20)], -1), // Delete btree row 2\n                (2, vec![Value::from_i64(2), Value::from_i64(25)], 1),  // Replace with new value\n                (4, vec![Value::from_i64(4), Value::from_i64(40)], -1), // Delete btree row 4\n            ],\n        );\n\n        // Should iterate: 1, 2(new), 3, 5\n        pager.io.block(|| cursor.rewind())?;\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(1));\n\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(2));\n        assert_eq!(pager.io.block(|| cursor.column(1))?, Value::from_i64(25)); // New value\n\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(3));\n\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(5));\n\n        assert!(!pager.io.block(|| cursor.next())?);\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_transaction_state_changes_mid_iteration() -> Result<()> {\n        let conn = create_test_connection()?;\n\n        // Populate table with rows 1, 3, 5\n        populate_test_table(&conn, vec![(1, 10), (3, 30), (5, 50)])?;\n\n        // Create cursor for testing\n        let (mut cursor, tx_state, pager) = create_test_cursor(&conn)?;\n\n        // Start iteration\n        pager.io.block(|| cursor.rewind())?;\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(1));\n\n        // Move to next row\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(3));\n\n        // Now add new uncommitted changes mid-iteration\n        apply_changes_to_tx_state(\n            &tx_state,\n            vec![\n                (2, vec![Value::from_i64(2), Value::from_i64(20)], 1), // Insert before current\n                (4, vec![Value::from_i64(4), Value::from_i64(40)], 1), // Insert after current\n                (6, vec![Value::from_i64(6), Value::from_i64(60)], 1), // Insert at end\n            ],\n        );\n\n        // Continue iteration - cursor continues from where it was, sees row 5 next\n        // (new changes are only visible after rewind/seek)\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(5));\n\n        // No more rows in original iteration\n        assert!(!pager.io.block(|| cursor.next())?);\n\n        // Rewind and verify we see all rows including the newly added ones\n        pager.io.block(|| cursor.rewind())?;\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(1));\n\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(2));\n\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(3));\n\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(4));\n\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(5));\n\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(6));\n\n        assert!(!pager.io.block(|| cursor.next())?);\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_rewind_after_failed_seek() -> Result<()> {\n        let conn = create_test_connection()?;\n\n        // Populate table with rows 1, 3, 5\n        populate_test_table(&conn, vec![(1, 10), (3, 30), (5, 50)])?;\n\n        // Create cursor for testing\n        let (mut cursor, tx_state, pager) = create_test_cursor(&conn)?;\n\n        // Add uncommitted row 2\n        apply_changes_to_tx_state(\n            &tx_state,\n            vec![(2, vec![Value::from_i64(2), Value::from_i64(20)], 1)],\n        );\n\n        // Seek to non-existent row 4 with exact match\n        assert_eq!(\n            pager\n                .io\n                .block(|| cursor.seek(SeekKey::TableRowId(4), SeekOp::GE { eq_only: true }))?,\n            SeekResult::NotFound\n        );\n        assert!(!cursor.is_valid()?);\n\n        // Rewind should work correctly after failed seek\n        pager.io.block(|| cursor.rewind())?;\n        assert!(cursor.is_valid()?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(1));\n\n        // Verify we can iterate through all rows\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(2));\n\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(3));\n\n        assert!(pager.io.block(|| cursor.next())?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(5));\n\n        assert!(!pager.io.block(|| cursor.next())?);\n\n        // Try another failed seek (GT on maximum value)\n        assert_eq!(\n            pager\n                .io\n                .block(|| cursor.seek(SeekKey::TableRowId(5), SeekOp::GT))?,\n            SeekResult::NotFound\n        );\n        assert!(!cursor.is_valid()?);\n\n        // Rewind again\n        pager.io.block(|| cursor.rewind())?;\n        assert!(cursor.is_valid()?);\n        assert_eq!(pager.io.block(|| cursor.rowid())?, Some(1));\n\n        Ok(())\n    }\n\n    // ===== IO RESUMPTION TEST SUITE =====\n    // These tests verify correct behavior when btree operations return IO (pending)\n\n    mod io_resumption_tests {\n        use super::*;\n        use crate::io::Completion;\n        use crate::storage::btree::{BTreeKey, CursorTrait};\n        use crate::types::{IOCompletions, ImmutableRecord, IndexInfo};\n        use crate::Register;\n        use std::sync::atomic::{AtomicUsize, Ordering};\n\n        /// Mock btree cursor that tracks calls and can simulate IO pending states.\n        /// Used to verify that seek operations aren't redundantly repeated after IO resumption.\n        struct MockBTreeCursor {\n            /// Number of times seek() was called\n            seek_count: AtomicUsize,\n            /// Number of times next() was called\n            next_count: AtomicUsize,\n            /// Number of times prev() was called\n            prev_count: AtomicUsize,\n            /// Current rowid to return\n            current_rowid: Option<i64>,\n            /// Record to return (needs to live for the cursor lifetime)\n            record: ImmutableRecord,\n            /// Index info\n            index_info: Arc<IndexInfo>,\n        }\n\n        impl MockBTreeCursor {\n            fn new() -> Self {\n                // Create a minimal record with rowid=1, value=10, weight=1\n                let record = Self::create_test_record(1, 10, 1);\n                Self {\n                    seek_count: AtomicUsize::new(0),\n                    next_count: AtomicUsize::new(0),\n                    prev_count: AtomicUsize::new(0),\n                    current_rowid: Some(1),\n                    record,\n                    index_info: Arc::new(IndexInfo::default()),\n                }\n            }\n\n            fn create_test_record(rowid: i64, value: i64, weight: i64) -> ImmutableRecord {\n                // Build a binary record with format: [header_size, type1, type2, type3, rowid, value, weight]\n                // For integers, type code is 1 for 1-byte int, 2 for 2-byte, etc.\n                // Using type 6 (8-byte integer) for all values\n                // Header: 4 bytes (header size byte + 3 type bytes)\n                let mut payload = vec![\n                    4u8, // header size\n                    6u8, // type for rowid (8-byte int)\n                    6u8, // type for value (8-byte int)\n                    6u8, // type for weight (8-byte int)\n                ];\n\n                // Data: 3 x 8-byte integers\n                payload.extend_from_slice(&rowid.to_be_bytes());\n                payload.extend_from_slice(&value.to_be_bytes());\n                payload.extend_from_slice(&weight.to_be_bytes());\n\n                ImmutableRecord::from_bin_record(payload)\n            }\n\n            fn get_seek_count(&self) -> usize {\n                self.seek_count.load(Ordering::SeqCst)\n            }\n\n            fn get_prev_count(&self) -> usize {\n                self.prev_count.load(Ordering::SeqCst)\n            }\n        }\n\n        impl CursorTrait for MockBTreeCursor {\n            fn seek(&mut self, _key: SeekKey<'_>, _op: SeekOp) -> Result<IOResult<SeekResult>> {\n                let count = self.seek_count.fetch_add(1, Ordering::SeqCst);\n                if count == 0 {\n                    // First seek returns TryAdvance\n                    Ok(IOResult::Done(SeekResult::TryAdvance))\n                } else {\n                    // Subsequent seeks return Found to avoid infinite loop\n                    // (The bug is that this second seek happens at all)\n                    Ok(IOResult::Done(SeekResult::Found))\n                }\n            }\n\n            fn seek_unpacked(\n                &mut self,\n                _registers: &[Register],\n                _op: SeekOp,\n            ) -> Result<IOResult<SeekResult>> {\n                // Not used in these tests\n                Ok(IOResult::Done(SeekResult::NotFound))\n            }\n\n            fn next(&mut self) -> Result<IOResult<()>> {\n                let count = self.next_count.fetch_add(1, Ordering::SeqCst);\n                if count == 0 {\n                    // First call returns IO (pending)\n                    let completion = Completion::new_yield();\n                    Ok(IOResult::IO(IOCompletions::Single(completion)))\n                } else {\n                    // Subsequent calls return Done\n                    Ok(IOResult::Done(()))\n                }\n            }\n\n            fn prev(&mut self) -> Result<IOResult<()>> {\n                let count = self.prev_count.fetch_add(1, Ordering::SeqCst);\n                if count == 0 {\n                    // First call returns IO (pending)\n                    let completion = Completion::new_yield();\n                    Ok(IOResult::IO(IOCompletions::Single(completion)))\n                } else {\n                    // Subsequent calls return Done\n                    Ok(IOResult::Done(()))\n                }\n            }\n\n            fn rowid(&mut self) -> Result<IOResult<Option<i64>>> {\n                Ok(IOResult::Done(self.current_rowid))\n            }\n\n            fn record(&mut self) -> Result<IOResult<Option<&ImmutableRecord>>> {\n                Ok(IOResult::Done(Some(&self.record)))\n            }\n\n            fn last(&mut self) -> Result<IOResult<()>> {\n                Ok(IOResult::Done(()))\n            }\n\n            fn insert(&mut self, _key: &BTreeKey) -> Result<IOResult<()>> {\n                Ok(IOResult::Done(()))\n            }\n\n            fn delete(&mut self) -> Result<IOResult<()>> {\n                Ok(IOResult::Done(()))\n            }\n\n            fn set_null_flag(&mut self, _flag: bool) {}\n\n            fn get_null_flag(&self) -> bool {\n                false\n            }\n\n            fn exists(&mut self, _key: &Value) -> Result<IOResult<bool>> {\n                Ok(IOResult::Done(false))\n            }\n\n            fn clear_btree(&mut self) -> Result<IOResult<Option<usize>>> {\n                Ok(IOResult::Done(None))\n            }\n\n            fn btree_destroy(&mut self) -> Result<IOResult<Option<usize>>> {\n                Ok(IOResult::Done(None))\n            }\n\n            fn count(&mut self) -> Result<IOResult<usize>> {\n                Ok(IOResult::Done(0))\n            }\n\n            fn is_empty(&self) -> bool {\n                false\n            }\n\n            fn root_page(&self) -> i64 {\n                1\n            }\n\n            fn rewind(&mut self) -> Result<IOResult<()>> {\n                Ok(IOResult::Done(()))\n            }\n\n            fn has_record(&self) -> bool {\n                true\n            }\n\n            fn set_has_record(&mut self, _has_record: bool) {}\n\n            fn get_index_info(&self) -> &Arc<IndexInfo> {\n                &self.index_info\n            }\n\n            fn seek_end(&mut self) -> Result<IOResult<()>> {\n                Ok(IOResult::Done(()))\n            }\n\n            fn seek_to_last(&mut self, _always_seek: bool) -> Result<IOResult<()>> {\n                Ok(IOResult::Done(()))\n            }\n\n            fn invalidate_record(&mut self) {}\n\n            fn has_rowid(&self) -> bool {\n                true\n            }\n\n            fn get_pager(&self) -> Arc<Pager> {\n                panic!(\"MockBTreeCursor::get_pager should not be called\")\n            }\n\n            fn get_skip_advance(&self) -> bool {\n                false\n            }\n        }\n\n        /// Test that verifies the bug: when btree.next() returns IO after TryAdvance,\n        /// resuming should NOT call btree.seek() again.\n        ///\n        /// Current behavior (BUG): seek is called twice\n        /// Expected behavior: seek should only be called once\n        #[test]\n        fn test_seek_not_repeated_after_io_during_try_advance() -> Result<()> {\n            let conn = create_test_connection()?;\n\n            // Get the view for creating a cursor\n            let view_mutex = conn\n                .schema\n                .read()\n                .get_materialized_view(\"test_view\")\n                .ok_or_else(|| crate::LimboError::InternalError(\"View not found\".to_string()))?;\n\n            let pager = conn.get_pager();\n            let tx_state = conn.view_transaction_states.get_or_create(\"test_view\");\n\n            // Create mock cursor that returns TryAdvance from seek and IO from next\n            let mock_cursor = MockBTreeCursor::new();\n            let mock_cursor_box: Box<dyn CursorTrait> = Box::new(mock_cursor);\n\n            // Get a reference to the mock to check counts later\n            // We need to use Any::downcast to access the mock's methods\n            let mock_ptr = mock_cursor_box.as_ref() as *const dyn CursorTrait;\n\n            let mut cursor =\n                MaterializedViewCursor::new(mock_cursor_box, view_mutex, pager, tx_state)?;\n\n            // Use LE so that rowid=1 satisfies the condition (1 <= 5)\n            let seek_op = SeekOp::LE { eq_only: false };\n\n            // First call to do_seek - should call btree.seek() which returns TryAdvance,\n            // then btree.prev() which returns IO\n            let result = cursor.do_seek(5, seek_op);\n\n            // Should return IO (pending)\n            assert!(\n                matches!(result, Ok(IOResult::IO(_))),\n                \"Expected IO result, got {result:?}\"\n            );\n\n            // Check seek was called once\n            let mock_ref: &MockBTreeCursor = unsafe { &*(mock_ptr as *const MockBTreeCursor) };\n            assert_eq!(\n                mock_ref.get_seek_count(),\n                1,\n                \"seek should be called exactly once before IO\"\n            );\n            // For LE, we call prev() not next()\n            assert_eq!(\n                mock_ref.get_prev_count(),\n                1,\n                \"prev should be called once (returned IO)\"\n            );\n\n            // Second call to do_seek (simulating resumption after IO completes)\n            // BUG: This will call btree.seek() again, which is wasteful\n            let result = cursor.do_seek(5, seek_op);\n\n            // The result might be Found or some other result\n            assert!(\n                matches!(result, Ok(IOResult::Done(_))),\n                \"Expected Done result on resume, got {result:?}\"\n            );\n\n            // Check seek count - seek should only be called once\n            // If this fails with seek_count=2, it means the bug exists:\n            // seek is being redundantly called again after IO resumption\n            let final_seek_count = mock_ref.get_seek_count();\n\n            assert_eq!(\n                final_seek_count, 1,\n                \"seek should only be called once, but was called {final_seek_count} times (redundant seek after IO during TryAdvance)\"\n            );\n\n            Ok(())\n        }\n    }\n}\n"
  },
  {
    "path": "core/incremental/dbsp.rs",
    "content": "// Simplified DBSP integration for incremental view maintenance\n// For now, we'll use a basic approach and can expand to full DBSP later\n\nuse crate::numeric::Numeric;\nuse crate::Value;\nuse std::collections::{BTreeMap, HashMap};\nuse std::hash::{Hash, Hasher};\n\n/// A 128-bit hash value implemented as a UUID\n/// We use UUID because it's a standard 128-bit type we already depend on\n#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]\npub struct Hash128 {\n    // Store as UUID internally for efficient 128-bit representation\n    uuid: uuid::Uuid,\n}\n\nimpl Hash128 {\n    /// Create a new 128-bit hash from high and low 64-bit parts\n    pub fn new(high: u64, low: u64) -> Self {\n        // Convert two u64 values to UUID bytes (big-endian)\n        let mut bytes = [0u8; 16];\n        bytes[0..8].copy_from_slice(&high.to_be_bytes());\n        bytes[8..16].copy_from_slice(&low.to_be_bytes());\n        Self {\n            uuid: uuid::Uuid::from_bytes(bytes),\n        }\n    }\n\n    /// Get the low 64 bits as i64 (for when we need a rowid)\n    pub fn as_i64(&self) -> i64 {\n        let bytes = self.uuid.as_bytes();\n        let low = u64::from_be_bytes([\n            bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15],\n        ]);\n        low as i64\n    }\n\n    /// Compute a 128-bit hash of the given values\n    /// We serialize values to a string representation and use UUID v5 (SHA-1 based)\n    /// to get a deterministic 128-bit hash\n    pub fn hash_values(values: &[Value]) -> Self {\n        // Build a string representation of all values\n        // Use a delimiter that won't appear in normal values\n        let mut s = String::new();\n        for (i, value) in values.iter().enumerate() {\n            if i > 0 {\n                s.push('\\x00'); // null byte as delimiter\n            }\n            // Add type prefix to distinguish between types\n            match value {\n                Value::Null => s.push_str(\"N:\"),\n                Value::Numeric(Numeric::Integer(n)) => {\n                    s.push_str(\"I:\");\n                    s.push_str(&n.to_string());\n                }\n                Value::Numeric(Numeric::Float(f)) => {\n                    s.push_str(\"F:\");\n                    // Use to_bits to ensure consistent representation\n                    s.push_str(&f64::from(*f).to_bits().to_string());\n                }\n                Value::Text(t) => {\n                    s.push_str(\"T:\");\n                    s.push_str(t.as_str());\n                }\n                Value::Blob(b) => {\n                    s.push_str(\"B:\");\n                    s.push_str(&hex::encode(b));\n                }\n            }\n        }\n\n        Self::hash_str(&s)\n    }\n\n    /// Hash a string value to 128 bits using UUID v5\n    pub fn hash_str(s: &str) -> Self {\n        // Use UUID v5 with a fixed namespace to get deterministic 128-bit hashes\n        // We use the DNS namespace as it's a standard choice\n        let uuid = uuid::Uuid::new_v5(&uuid::Uuid::NAMESPACE_DNS, s.as_bytes());\n        Self { uuid }\n    }\n\n    /// Convert to a big-endian byte array for storage\n    pub fn to_blob(self) -> Vec<u8> {\n        self.uuid.as_bytes().to_vec()\n    }\n\n    /// Create from a big-endian byte array\n    pub fn from_blob(bytes: &[u8]) -> Option<Self> {\n        if bytes.len() != 16 {\n            return None;\n        }\n\n        let mut uuid_bytes = [0u8; 16];\n        uuid_bytes.copy_from_slice(bytes);\n        Some(Self {\n            uuid: uuid::Uuid::from_bytes(uuid_bytes),\n        })\n    }\n\n    /// Convert to a Value::Blob for storage\n    pub fn to_value(self) -> Value {\n        Value::Blob(self.to_blob())\n    }\n\n    /// Try to extract a Hash128 from a Value\n    pub fn from_value(value: &Value) -> Option<Self> {\n        match value {\n            Value::Blob(b) => Self::from_blob(b),\n            _ => None,\n        }\n    }\n}\n\nimpl std::fmt::Display for Hash128 {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{}\", self.uuid)\n    }\n}\n\n// The DBSP paper uses as a key the whole record, with both the row key and the values.  This is a\n// bit confuses for us in databases, because when you say \"key\", it is easy to understand that as\n// being the row key.\n//\n// Empirically speaking, using row keys as the ZSet keys will waste a competent but not brilliant\n// engineer around 82 and 88 hours, depending on how you count. Hours that are never coming back.\n//\n// One of the situations in which using row keys completely breaks are table updates. If the \"key\"\n// is the row key, let's say \"5\", then an update is a delete + insert. Imagine a table that had k =\n// 5, v = 5, and a view that filters v > 2.\n//\n// Now we will do an update that changes v => 1. If the \"key\" is 5, then inside the Delta set, we\n// will have (5, weight = -1), (5, weight = +1), and the whole thing just disappears. The Delta\n// set, therefore, has to contain ((5, 5), weight = -1), ((5, 1), weight = +1).\n//\n// It is theoretically possible to use the rowkey in the ZSet and then use a hash of key ->\n// Vec(changes) in the Delta set. But deviating from the paper here is just asking for trouble, as\n// I am sure it would break somewhere else.\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct HashableRow {\n    pub rowid: i64,\n    pub values: Vec<Value>,\n    // Pre-computed hash: DBSP rows are immutable and frequently hashed during joins,\n    // making caching worthwhile despite the memory overhead\n    cached_hash: Hash128,\n}\n\nimpl HashableRow {\n    pub fn new(rowid: i64, values: Vec<Value>) -> Self {\n        let cached_hash = Self::compute_hash(rowid, &values);\n        Self {\n            rowid,\n            values,\n            cached_hash,\n        }\n    }\n\n    fn compute_hash(rowid: i64, values: &[Value]) -> Hash128 {\n        // Include rowid in the hash by prepending it to values\n        let mut all_values = Vec::with_capacity(values.len() + 1);\n        all_values.push(Value::from_i64(rowid));\n        all_values.extend_from_slice(values);\n        Hash128::hash_values(&all_values)\n    }\n\n    pub fn cached_hash(&self) -> Hash128 {\n        self.cached_hash\n    }\n}\n\nimpl Hash for HashableRow {\n    fn hash<H: Hasher>(&self, state: &mut H) {\n        // Hash the 128-bit value by hashing both parts\n        self.cached_hash.to_blob().hash(state);\n    }\n}\n\nimpl PartialOrd for HashableRow {\n    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {\n        Some(self.cmp(other))\n    }\n}\n\nimpl Ord for HashableRow {\n    fn cmp(&self, other: &Self) -> std::cmp::Ordering {\n        // First compare by rowid, then by values if rowids are equal\n        // This ensures Ord is consistent with Eq (which compares all fields)\n        match self.rowid.cmp(&other.rowid) {\n            std::cmp::Ordering::Equal => {\n                // If rowids are equal, compare values to maintain consistency with Eq\n                self.values.cmp(&other.values)\n            }\n            other => other,\n        }\n    }\n}\n\ntype DeltaEntry = (HashableRow, isize);\n/// A delta represents ordered changes to data\n#[derive(Debug, Clone, Default)]\npub struct Delta {\n    /// Ordered list of changes: (row, weight) where weight is +1 for insert, -1 for delete\n    /// It is crucial that this is ordered. Imagine the case of an update, which becomes a delete +\n    /// insert. If this is not ordered, it would be applied in arbitrary order and break the view.\n    pub changes: Vec<DeltaEntry>,\n}\n\nimpl Delta {\n    pub fn new() -> Self {\n        Self {\n            changes: Vec::new(),\n        }\n    }\n\n    pub fn insert(&mut self, row_key: i64, values: Vec<Value>) {\n        let row = HashableRow::new(row_key, values);\n        self.changes.push((row, 1));\n    }\n\n    pub fn delete(&mut self, row_key: i64, values: Vec<Value>) {\n        let row = HashableRow::new(row_key, values);\n        self.changes.push((row, -1));\n    }\n\n    pub fn is_empty(&self) -> bool {\n        self.changes.is_empty()\n    }\n\n    pub fn len(&self) -> usize {\n        self.changes.len()\n    }\n\n    /// Merge another delta into this one\n    /// This preserves the order of operations - no consolidation is done\n    /// to maintain the full history of changes\n    pub fn merge(&mut self, other: &Delta) {\n        // Simply append all changes from other, preserving order\n        self.changes.extend(other.changes.iter().cloned());\n    }\n\n    /// Consolidate changes by combining entries with the same HashableRow\n    pub fn consolidate(&mut self) {\n        if self.changes.is_empty() {\n            return;\n        }\n\n        // Use a HashMap to accumulate weights\n        let mut consolidated: HashMap<HashableRow, isize> = HashMap::default();\n\n        for (row, weight) in self.changes.drain(..) {\n            *consolidated.entry(row).or_insert(0) += weight;\n        }\n\n        // Convert back to vec, filtering out zero weights\n        self.changes = consolidated\n            .into_iter()\n            .filter(|(_, weight)| *weight != 0)\n            .collect();\n    }\n}\n\n/// A pair of deltas for operators that process two inputs\n#[derive(Debug, Clone, Default)]\npub struct DeltaPair {\n    pub left: Delta,\n    pub right: Delta,\n}\n\nimpl DeltaPair {\n    /// Create a new delta pair\n    pub fn new(left: Delta, right: Delta) -> Self {\n        Self { left, right }\n    }\n}\n\nimpl From<Delta> for DeltaPair {\n    /// Convert a single delta into a delta pair with empty right delta\n    fn from(delta: Delta) -> Self {\n        Self {\n            left: delta,\n            right: Delta::new(),\n        }\n    }\n}\n\nimpl From<&Delta> for DeltaPair {\n    /// Convert a delta reference into a delta pair with empty right delta\n    fn from(delta: &Delta) -> Self {\n        Self {\n            left: delta.clone(),\n            right: Delta::new(),\n        }\n    }\n}\n\n/// A simplified ZSet for incremental computation\n/// Each element has a weight: positive for additions, negative for deletions\n#[derive(Clone, Debug, Default)]\npub struct SimpleZSet<T> {\n    data: BTreeMap<T, isize>,\n}\n\n#[allow(dead_code)]\nimpl<T: std::hash::Hash + Eq + Ord + Clone> SimpleZSet<T> {\n    pub fn new() -> Self {\n        Self {\n            data: BTreeMap::new(),\n        }\n    }\n\n    pub fn insert(&mut self, item: T, weight: isize) {\n        let current = self.data.get(&item).copied().unwrap_or(0);\n        let new_weight = current + weight;\n        if new_weight == 0 {\n            self.data.remove(&item);\n        } else {\n            self.data.insert(item, new_weight);\n        }\n    }\n\n    pub fn iter(&self) -> impl Iterator<Item = (&T, isize)> {\n        self.data.iter().map(|(k, &v)| (k, v))\n    }\n\n    /// Get all items with positive weights\n    pub fn to_vec(&self) -> Vec<T> {\n        self.data\n            .iter()\n            .filter(|(_, &weight)| weight > 0)\n            .map(|(item, _)| item.clone())\n            .collect()\n    }\n\n    pub fn merge(&mut self, other: &SimpleZSet<T>) {\n        for (item, weight) in other.iter() {\n            self.insert(item.clone(), weight);\n        }\n    }\n\n    /// Get the weight for a specific item (0 if not present)\n    pub fn get(&self, item: &T) -> isize {\n        self.data.get(item).copied().unwrap_or(0)\n    }\n\n    /// Get the first element (smallest key) in the Z-set\n    pub fn first(&self) -> Option<(&T, isize)> {\n        self.data.iter().next().map(|(k, &v)| (k, v))\n    }\n\n    /// Get the last element (largest key) in the Z-set\n    pub fn last(&self) -> Option<(&T, isize)> {\n        self.data.iter().next_back().map(|(k, &v)| (k, v))\n    }\n\n    /// Get a range of elements\n    pub fn range<R>(&self, range: R) -> impl Iterator<Item = (&T, isize)> + '_\n    where\n        R: std::ops::RangeBounds<T>,\n    {\n        self.data.range(range).map(|(k, &v)| (k, v))\n    }\n\n    /// Check if empty\n    pub fn is_empty(&self) -> bool {\n        self.data.is_empty()\n    }\n\n    /// Get the number of elements\n    pub fn len(&self) -> usize {\n        self.data.len()\n    }\n}\n\n// Type aliases for convenience\npub type RowKey = HashableRow;\npub type RowKeyZSet = SimpleZSet<RowKey>;\n\nimpl RowKeyZSet {\n    /// Create a Z-set from a Delta by consolidating all changes\n    pub fn from_delta(delta: &Delta) -> Self {\n        let mut zset = Self::new();\n\n        // Add all changes from the delta, consolidating as we go\n        for (row, weight) in &delta.changes {\n            zset.insert(row.clone(), *weight);\n        }\n\n        zset\n    }\n\n    /// Seek to find ALL entries for the best matching rowid\n    /// For GT/GE: returns all entries for the smallest rowid that satisfies the condition\n    /// For LT/LE: returns all entries for the largest rowid that satisfies the condition\n    /// Returns empty vec if no match found\n    pub fn seek(&self, target: i64, op: crate::types::SeekOp) -> Vec<(HashableRow, isize)> {\n        use crate::types::SeekOp;\n\n        // First find the best matching rowid\n        let best_rowid = match op {\n            SeekOp::GT => {\n                // Find smallest rowid > target\n                self.data\n                    .iter()\n                    .filter(|(row, _)| row.rowid > target)\n                    .map(|(row, _)| row.rowid)\n                    .min()\n            }\n            SeekOp::GE { eq_only: false } => {\n                // Find smallest rowid >= target\n                self.data\n                    .iter()\n                    .filter(|(row, _)| row.rowid >= target)\n                    .map(|(row, _)| row.rowid)\n                    .min()\n            }\n            SeekOp::GE { eq_only: true } | SeekOp::LE { eq_only: true } => {\n                // Need exact match\n                if self.data.iter().any(|(row, _)| row.rowid == target) {\n                    Some(target)\n                } else {\n                    None\n                }\n            }\n            SeekOp::LT => {\n                // Find largest rowid < target\n                self.data\n                    .iter()\n                    .filter(|(row, _)| row.rowid < target)\n                    .map(|(row, _)| row.rowid)\n                    .max()\n            }\n            SeekOp::LE { eq_only: false } => {\n                // Find largest rowid <= target\n                self.data\n                    .iter()\n                    .filter(|(row, _)| row.rowid <= target)\n                    .map(|(row, _)| row.rowid)\n                    .max()\n            }\n        };\n\n        // Now get ALL entries with that rowid\n        match best_rowid {\n            Some(rowid) => self\n                .data\n                .iter()\n                .filter(|(row, _)| row.rowid == rowid)\n                .map(|(k, &v)| (k.clone(), v))\n                .collect(),\n            None => Vec::new(),\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_zset_merge_with_weights() {\n        let mut zset1 = SimpleZSet::new();\n        zset1.insert(1, 1); // Row 1 with weight +1\n        zset1.insert(2, 1); // Row 2 with weight +1\n\n        let mut zset2 = SimpleZSet::new();\n        zset2.insert(2, -1); // Row 2 with weight -1 (delete)\n        zset2.insert(3, 1); // Row 3 with weight +1 (insert)\n\n        zset1.merge(&zset2);\n\n        // Row 1: weight 1 (unchanged)\n        // Row 2: weight 0 (deleted)\n        // Row 3: weight 1 (inserted)\n        assert_eq!(zset1.iter().count(), 2); // Only rows 1 and 3\n        assert!(zset1.iter().any(|(k, _)| *k == 1));\n        assert!(zset1.iter().any(|(k, _)| *k == 3));\n        assert!(!zset1.iter().any(|(k, _)| *k == 2)); // Row 2 removed\n    }\n\n    #[test]\n    fn test_zset_represents_updates_as_delete_plus_insert() {\n        let mut zset = SimpleZSet::new();\n\n        // Initial state\n        zset.insert(1, 1);\n\n        // Update row 1: delete old + insert new\n        zset.insert(1, -1); // Delete old version\n        zset.insert(1, 1); // Insert new version\n\n        // Weight should be 1 (not 2)\n        let weight = zset.iter().find(|(k, _)| **k == 1).map(|(_, w)| w);\n        assert_eq!(weight, Some(1));\n    }\n\n    #[test]\n    fn test_hashable_row_delta_operations() {\n        let mut delta = Delta::new();\n\n        // Test INSERT\n        delta.insert(1, vec![Value::from_i64(1), Value::from_i64(100)]);\n        assert_eq!(delta.len(), 1);\n\n        // Test UPDATE (DELETE + INSERT) - order matters!\n        delta.delete(1, vec![Value::from_i64(1), Value::from_i64(100)]);\n        delta.insert(1, vec![Value::from_i64(1), Value::from_i64(200)]);\n        assert_eq!(delta.len(), 3); // Should have 3 operations before consolidation\n\n        // Verify order is preserved\n        let ops: Vec<_> = delta.changes.iter().collect();\n        assert_eq!(ops[0].1, 1); // First insert\n        assert_eq!(ops[1].1, -1); // Delete\n        assert_eq!(ops[2].1, 1); // Second insert\n\n        // Test consolidation\n        delta.consolidate();\n        // After consolidation, the first insert and delete should cancel out\n        // leaving only the second insert\n        assert_eq!(delta.len(), 1);\n\n        let final_row = &delta.changes[0];\n        assert_eq!(final_row.0.rowid, 1);\n        assert_eq!(\n            final_row.0.values,\n            vec![Value::from_i64(1), Value::from_i64(200)]\n        );\n        assert_eq!(final_row.1, 1);\n    }\n\n    #[test]\n    fn test_duplicate_row_consolidation() {\n        let mut delta = Delta::new();\n\n        // Insert same row twice\n        delta.insert(2, vec![Value::from_i64(2), Value::from_i64(300)]);\n        delta.insert(2, vec![Value::from_i64(2), Value::from_i64(300)]);\n\n        assert_eq!(delta.len(), 2);\n\n        delta.consolidate();\n        assert_eq!(delta.len(), 1);\n\n        // Weight should be 2 (sum of both inserts)\n        let final_row = &delta.changes[0];\n        assert_eq!(final_row.0.rowid, 2);\n        assert_eq!(final_row.1, 2);\n    }\n}\n"
  },
  {
    "path": "core/incremental/expr_compiler.rs",
    "content": "// Expression compilation for incremental operators\n// This module provides utilities to compile SQL expressions into VDBE subprograms\n// that can be executed efficiently in the incremental computation context.\n\nuse crate::numeric::Numeric;\nuse crate::schema::Schema;\nuse crate::storage::pager::Pager;\nuse crate::sync::Arc;\nuse crate::translate::emitter::Resolver;\nuse crate::translate::expr::translate_expr;\nuse crate::types::Text;\nuse crate::vdbe::builder::{ProgramBuilder, ProgramBuilderOpts};\nuse crate::vdbe::insn::Insn;\nuse crate::vdbe::{Program, ProgramState, Register};\nuse crate::{Connection, QueryMode, Result, Value};\nuse crate::{DatabaseCatalog, RwLock, SymbolTable};\nuse rustc_hash::FxHashMap as HashMap;\nuse turso_parser::ast::{Expr, Literal, Operator};\n\n// Transform an expression to replace column references with Register expressions Why do we want to\n// do this?\n//\n// Imagine you have a view like:\n//\n// create materialized view hex(count(*) + 2). translate_expr will usually try to find match names\n// to either literals or columns. But \"count(*)\" is not a column in any sqlite table.\n//\n// We *could* theoretically have a table-representation of every DBSP-step, but it is a lot simpler\n// to just pass registers as parameters to the VDBE expression, and teach translate_expr to\n// recognize those.\n//\n// But because the expression compiler will not generate those register inputs, we have to\n// transform the expression.\nfn transform_expr_for_dbsp(expr: &Expr, input_column_names: &[String]) -> Expr {\n    match expr {\n        // Transform column references (represented as Id) to Register expressions\n        Expr::Id(name) => {\n            // Check if this is a column name from our input\n            if let Some(idx) = input_column_names\n                .iter()\n                .position(|col| col == name.as_str())\n            {\n                // Replace with a Register expression\n                Expr::Register(idx)\n            } else {\n                // Not a column reference, keep as is\n                expr.clone()\n            }\n        }\n        // Recursively transform nested expressions\n        Expr::Binary(lhs, op, rhs) => Expr::Binary(\n            Box::new(transform_expr_for_dbsp(lhs, input_column_names)),\n            *op,\n            Box::new(transform_expr_for_dbsp(rhs, input_column_names)),\n        ),\n        Expr::Unary(op, operand) => Expr::Unary(\n            *op,\n            Box::new(transform_expr_for_dbsp(operand, input_column_names)),\n        ),\n        Expr::FunctionCall {\n            name,\n            distinctness,\n            args,\n            order_by,\n            filter_over,\n        } => Expr::FunctionCall {\n            name: name.clone(),\n            distinctness: *distinctness,\n            args: args\n                .iter()\n                .map(|arg| Box::new(transform_expr_for_dbsp(arg, input_column_names)))\n                .collect(),\n            order_by: order_by.clone(),\n            filter_over: filter_over.clone(),\n        },\n        Expr::Parenthesized(exprs) => Expr::Parenthesized(\n            exprs\n                .iter()\n                .map(|e| Box::new(transform_expr_for_dbsp(e, input_column_names)))\n                .collect(),\n        ),\n        // For other expression types, keep as is\n        _ => expr.clone(),\n    }\n}\n\n/// Enum to represent either a trivial or compiled expression\n#[derive(Clone)]\npub enum ExpressionExecutor {\n    /// Trivial expression that can be evaluated inline\n    Trivial(TrivialExpression),\n    /// Compiled VDBE program for complex expressions\n    Compiled(Arc<Program>),\n}\n\n/// Trivial expression that can be evaluated inline without VDBE\n/// Supports arithmetic operations with automatic type promotion (integer to float)\n#[derive(Clone, Debug)]\npub enum TrivialExpression {\n    /// Direct column reference\n    Column(usize),\n    /// Immediate value\n    Immediate(Value),\n    /// Binary operation on trivial expressions (supports type promotion)\n    Binary {\n        left: Box<TrivialExpression>,\n        op: Operator,\n        right: Box<TrivialExpression>,\n    },\n}\n\nimpl TrivialExpression {\n    /// Evaluate the trivial expression with the given input values\n    /// Automatically promotes integers to floats when mixing types in arithmetic\n    pub fn evaluate(&self, values: &[Value]) -> Value {\n        match self {\n            TrivialExpression::Column(idx) => values.get(*idx).cloned().unwrap_or(Value::Null),\n            TrivialExpression::Immediate(val) => val.clone(),\n            TrivialExpression::Binary { left, op, right } => {\n                let left_val = left.evaluate(values);\n                let right_val = right.evaluate(values);\n\n                // Use Value's exec_* methods which handle all type coercion\n                // (including Text → Numeric) consistently with SQLite semantics\n                match op {\n                    Operator::Add => left_val.exec_add(&right_val),\n                    Operator::Subtract => left_val.exec_subtract(&right_val),\n                    Operator::Multiply => left_val.exec_multiply(&right_val),\n                    Operator::Divide => left_val.exec_divide(&right_val),\n                    _ => panic!(\"Unsupported operator in trivial expression: {op:?}\"),\n                }\n            }\n        }\n    }\n}\n\n/// Compiled expression that can be executed on row values\n#[derive(Clone)]\npub struct CompiledExpression {\n    /// The expression executor (trivial or compiled)\n    pub executor: ExpressionExecutor,\n    /// Number of input values expected (columns from the row)\n    pub input_count: usize,\n}\n\nimpl std::fmt::Debug for CompiledExpression {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let mut s = f.debug_struct(\"CompiledExpression\");\n        s.field(\"input_count\", &self.input_count);\n        match &self.executor {\n            ExpressionExecutor::Trivial(t) => s.field(\"executor\", &format!(\"Trivial({t:?})\")),\n            ExpressionExecutor::Compiled(p) => {\n                s.field(\"executor\", &format!(\"Compiled({} insns)\", p.insns.len()))\n            }\n        };\n        s.finish()\n    }\n}\n\n#[derive(PartialEq)]\nenum TrivialType {\n    Integer,\n    Float,\n    Text,\n    Null,\n}\n\nimpl CompiledExpression {\n    /// Get the \"type\" of a trivial expression for type checking\n    /// Returns None if type can't be determined statically\n    fn get_trivial_type(expr: &TrivialExpression) -> Option<TrivialType> {\n        match expr {\n            TrivialExpression::Column(_) => None, // Can't know column type statically\n            TrivialExpression::Immediate(val) => match val {\n                Value::Numeric(Numeric::Integer(_)) => Some(TrivialType::Integer),\n                Value::Numeric(Numeric::Float(_)) => Some(TrivialType::Float),\n                Value::Text(_) => Some(TrivialType::Text),\n                Value::Null => Some(TrivialType::Null),\n                _ => None,\n            },\n            TrivialExpression::Binary { left, right, .. } => {\n                // For binary ops, both sides must have the same type\n                let left_type = Self::get_trivial_type(left)?;\n                let right_type = Self::get_trivial_type(right)?;\n                if left_type == right_type {\n                    Some(left_type)\n                } else {\n                    None // Type mismatch\n                }\n            }\n        }\n    }\n\n    // Validates if an expression is trivial (columns, immediates, and simple arithmetic)\n    // Only considers expressions trivial if they don't require type coercion\n    fn try_get_trivial_expr(\n        expr: &Expr,\n        input_column_names: &[String],\n    ) -> Option<TrivialExpression> {\n        match expr {\n            // Column reference or register\n            Expr::Id(name) => input_column_names\n                .iter()\n                .position(|col| col == name.as_str())\n                .map(TrivialExpression::Column),\n            Expr::Register(idx) => Some(TrivialExpression::Column(*idx)),\n\n            // Immediate values\n            Expr::Literal(lit) => {\n                let value = match lit {\n                    Literal::Numeric(n) => {\n                        if let Ok(i) = n.parse::<i64>() {\n                            Value::from_i64(i)\n                        } else if let Ok(f) = n.parse::<f64>() {\n                            Value::from_f64(f)\n                        } else {\n                            return None;\n                        }\n                    }\n                    Literal::String(s) => {\n                        let cleaned = s.trim_matches('\\'').trim_matches('\"').to_string();\n                        Value::Text(Text::new(cleaned))\n                    }\n                    Literal::Null => Value::Null,\n                    _ => return None,\n                };\n                Some(TrivialExpression::Immediate(value))\n            }\n\n            // Binary operations with simple operators\n            Expr::Binary(left, op, right) => {\n                // Only support simple arithmetic operators\n                match op {\n                    Operator::Add | Operator::Subtract | Operator::Multiply | Operator::Divide => {\n                        // Both operands must be trivial\n                        let left_trivial = Self::try_get_trivial_expr(left, input_column_names)?;\n                        let right_trivial = Self::try_get_trivial_expr(right, input_column_names)?;\n\n                        // Check if we can determine types statically\n                        // For arithmetic operations, we allow mixing integers and floats\n                        // since we promote integers to floats as needed\n                        if let (Some(left_type), Some(right_type)) = (\n                            Self::get_trivial_type(&left_trivial),\n                            Self::get_trivial_type(&right_trivial),\n                        ) {\n                            // Both types are known - check if they're numeric or null\n                            let numeric_types = matches!(\n                                left_type,\n                                TrivialType::Integer | TrivialType::Float | TrivialType::Null\n                            ) && matches!(\n                                right_type,\n                                TrivialType::Integer | TrivialType::Float | TrivialType::Null\n                            );\n\n                            if !numeric_types {\n                                return None; // Non-numeric types - not trivial\n                            }\n                        }\n                        // If we can't determine types (columns involved), we optimistically\n                        // assume they'll be compatible at runtime\n\n                        Some(TrivialExpression::Binary {\n                            left: Box::new(left_trivial),\n                            op: *op,\n                            right: Box::new(right_trivial),\n                        })\n                    }\n                    _ => None,\n                }\n            }\n\n            // Parenthesized expressions with single element\n            Expr::Parenthesized(exprs) if exprs.len() == 1 => {\n                Self::try_get_trivial_expr(&exprs[0], input_column_names)\n            }\n\n            _ => None,\n        }\n    }\n\n    /// Compile a SQL expression into either a trivial executor or VDBE program\n    ///\n    /// For trivial expressions (columns, immediates, simple same-type arithmetic), uses inline evaluation.\n    /// For complex expressions or those requiring type coercion, compiles to VDBE bytecode.\n    pub fn compile(\n        expr: &Expr,\n        input_column_names: &[String],\n        schema: &Schema,\n        syms: &SymbolTable,\n        connection: Arc<Connection>,\n    ) -> Result<Self> {\n        let input_count = input_column_names.len();\n\n        // First, check if this is a trivial expression\n        if let Some(trivial) = Self::try_get_trivial_expr(expr, input_column_names) {\n            return Ok(CompiledExpression {\n                executor: ExpressionExecutor::Trivial(trivial),\n                input_count,\n            });\n        }\n\n        // Fall back to VDBE compilation for complex expressions\n        // Create a minimal program builder for expression compilation\n        let mut builder = ProgramBuilder::new(\n            QueryMode::Normal,\n            None,\n            ProgramBuilderOpts {\n                num_cursors: 0,\n                approx_num_insns: 5,  // Most expressions are simple\n                approx_num_labels: 0, // Expressions don't need labels\n            },\n        );\n\n        // Allocate registers for input values\n        let input_count = input_column_names.len();\n\n        // Allocate input registers\n        for _ in 0..input_count {\n            builder.alloc_register();\n        }\n\n        // Allocate a temp register for computation\n        let temp_result_register = builder.alloc_register();\n\n        // Transform the expression to replace column references with Register expressions\n        let transformed_expr = transform_expr_for_dbsp(expr, input_column_names);\n\n        // Create a resolver for translate_expr\n        let database_schemas = RwLock::new(HashMap::default());\n        let attached_databases = RwLock::new(DatabaseCatalog::new());\n        let resolver = Resolver::new(schema, &database_schemas, &attached_databases, syms, true);\n\n        // Translate the transformed expression to bytecode\n        translate_expr(\n            &mut builder,\n            None, // No table references needed for pure expressions\n            &transformed_expr,\n            temp_result_register,\n            &resolver,\n        )?;\n\n        // Copy the result to register 0 for return\n        builder.emit_insn(Insn::Copy {\n            src_reg: temp_result_register,\n            dst_reg: 0,\n            extra_amount: 0,\n        });\n\n        // Add a Halt instruction to complete the subprogram\n        builder.emit_insn(Insn::Halt {\n            err_code: 0,\n            description: String::new(),\n            on_error: None,\n            description_reg: None,\n        });\n\n        // Build the program from the compiled expression bytecode\n        let program = Arc::new(builder.build(connection, false, \"\")?);\n\n        Ok(CompiledExpression {\n            executor: ExpressionExecutor::Compiled(program),\n            input_count,\n        })\n    }\n\n    /// Execute the compiled expression with the given input values\n    pub fn execute(&self, values: &[Value], pager: Arc<Pager>) -> Result<Value> {\n        match &self.executor {\n            ExpressionExecutor::Trivial(trivial) => {\n                // Fast path: evaluate trivial expression inline\n                Ok(trivial.evaluate(values))\n            }\n            ExpressionExecutor::Compiled(program) => {\n                // Slow path: execute VDBE program\n                // Create a state with the input values loaded into registers\n                let mut state = ProgramState::new(program.max_registers, 0);\n\n                // Load input values into registers\n                assert_eq!(\n                    values.len(),\n                    self.input_count,\n                    \"Mismatch in number of registers! Got {}, expected {}\",\n                    values.len(),\n                    self.input_count\n                );\n                for (idx, value) in values.iter().enumerate() {\n                    state.set_register(idx, Register::Value(value.clone()));\n                }\n\n                // Execute the program\n                let mut pc = 0usize;\n                while pc < program.insns.len() {\n                    let (insn, _) = &program.insns[pc];\n                    let insn_fn = insn.to_function();\n                    state.pc = pc as u32;\n\n                    // Execute the instruction\n                    match insn_fn(program, &mut state, insn, &pager)? {\n                        crate::vdbe::execute::InsnFunctionStepResult::IO(_) => {\n                            return Err(crate::LimboError::InternalError(\n                                \"Expression evaluation encountered unexpected I/O\".to_string(),\n                            ));\n                        }\n                        crate::vdbe::execute::InsnFunctionStepResult::Done => {\n                            break;\n                        }\n                        crate::vdbe::execute::InsnFunctionStepResult::Row => {\n                            return Err(crate::LimboError::InternalError(\n                                \"Expression evaluation produced unexpected row\".to_string(),\n                            ));\n                        }\n                        crate::vdbe::execute::InsnFunctionStepResult::Step => {\n                            pc = state.pc as usize;\n                        }\n                    }\n                }\n\n                // The compiled expression puts the result in register 0\n                match state.get_register(0) {\n                    Register::Value(v) => Ok(v.clone()),\n                    _ => Ok(Value::Null),\n                }\n            }\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_mixed_type_arithmetic() {\n        // Test integer - float\n        let expr = TrivialExpression::Binary {\n            left: Box::new(TrivialExpression::Immediate(Value::from_i64(1))),\n            op: Operator::Subtract,\n            right: Box::new(TrivialExpression::Immediate(Value::from_f64(0.5))),\n        };\n        let result = expr.evaluate(&[]);\n        assert_eq!(result, Value::from_f64(0.5));\n\n        // Test float - integer\n        let expr = TrivialExpression::Binary {\n            left: Box::new(TrivialExpression::Immediate(Value::from_f64(2.5))),\n            op: Operator::Subtract,\n            right: Box::new(TrivialExpression::Immediate(Value::from_i64(1))),\n        };\n        let result = expr.evaluate(&[]);\n        assert_eq!(result, Value::from_f64(1.5));\n\n        // Test integer * float\n        let expr = TrivialExpression::Binary {\n            left: Box::new(TrivialExpression::Immediate(Value::from_i64(10))),\n            op: Operator::Multiply,\n            right: Box::new(TrivialExpression::Immediate(Value::from_f64(0.1))),\n        };\n        let result = expr.evaluate(&[]);\n        assert_eq!(result, Value::from_f64(1.0));\n\n        // Test integer / float\n        let expr = TrivialExpression::Binary {\n            left: Box::new(TrivialExpression::Immediate(Value::from_i64(1))),\n            op: Operator::Divide,\n            right: Box::new(TrivialExpression::Immediate(Value::from_f64(2.0))),\n        };\n        let result = expr.evaluate(&[]);\n        assert_eq!(result, Value::from_f64(0.5));\n\n        // Test integer + float\n        let expr = TrivialExpression::Binary {\n            left: Box::new(TrivialExpression::Immediate(Value::from_i64(1))),\n            op: Operator::Add,\n            right: Box::new(TrivialExpression::Immediate(Value::from_f64(0.5))),\n        };\n        let result = expr.evaluate(&[]);\n        assert_eq!(result, Value::from_f64(1.5));\n    }\n\n    #[test]\n    fn test_nested_mixed_type_expressions() {\n        // Test nested expressions with mixed types: (1 - 0.04)\n        let one_minus_float = TrivialExpression::Binary {\n            left: Box::new(TrivialExpression::Immediate(Value::from_i64(1))),\n            op: Operator::Subtract,\n            right: Box::new(TrivialExpression::Immediate(Value::from_f64(0.04))),\n        };\n        let result = one_minus_float.evaluate(&[]);\n        assert_eq!(result, Value::from_f64(0.96));\n\n        // Test multiplication with nested mixed-type expression: 100.0 * (1 - 0.04)\n        let nested_expr = TrivialExpression::Binary {\n            left: Box::new(TrivialExpression::Immediate(Value::from_f64(100.0))),\n            op: Operator::Multiply,\n            right: Box::new(one_minus_float),\n        };\n        let result = nested_expr.evaluate(&[]);\n        assert_eq!(result, Value::from_f64(96.0));\n    }\n\n    #[test]\n    fn test_text_to_numeric_coercion_in_arithmetic() {\n        // Non-numeric text should coerce to 0 (SQLite behavior)\n        let values = vec![Value::Text(Text::new(\"hello\".to_string()))];\n\n        // text - 1 => 0 - 1 = -1\n        let expr = TrivialExpression::Binary {\n            left: Box::new(TrivialExpression::Column(0)),\n            op: Operator::Subtract,\n            right: Box::new(TrivialExpression::Immediate(Value::from_i64(1))),\n        };\n        assert_eq!(expr.evaluate(&values), Value::from_i64(-1));\n\n        // text + 1 => 0 + 1 = 1\n        let expr = TrivialExpression::Binary {\n            left: Box::new(TrivialExpression::Column(0)),\n            op: Operator::Add,\n            right: Box::new(TrivialExpression::Immediate(Value::from_i64(1))),\n        };\n        assert_eq!(expr.evaluate(&values), Value::from_i64(1));\n\n        // text * 2 => 0 * 2 = 0\n        let expr = TrivialExpression::Binary {\n            left: Box::new(TrivialExpression::Column(0)),\n            op: Operator::Multiply,\n            right: Box::new(TrivialExpression::Immediate(Value::from_i64(2))),\n        };\n        assert_eq!(expr.evaluate(&values), Value::from_i64(0));\n\n        // text / 2 => 0 / 2 = 0\n        let expr = TrivialExpression::Binary {\n            left: Box::new(TrivialExpression::Column(0)),\n            op: Operator::Divide,\n            right: Box::new(TrivialExpression::Immediate(Value::from_i64(2))),\n        };\n        assert_eq!(expr.evaluate(&values), Value::from_i64(0));\n\n        // Numeric text \"42\" - 1 => 41\n        let numeric_text_values = vec![Value::Text(Text::new(\"42\".to_string()))];\n        let expr = TrivialExpression::Binary {\n            left: Box::new(TrivialExpression::Column(0)),\n            op: Operator::Subtract,\n            right: Box::new(TrivialExpression::Immediate(Value::from_i64(1))),\n        };\n        assert_eq!(expr.evaluate(&numeric_text_values), Value::from_i64(41));\n    }\n}\n"
  },
  {
    "path": "core/incremental/filter_operator.rs",
    "content": "#![allow(dead_code)]\n// Filter operator for DBSP-style incremental computation\n// This operator filters rows based on predicates\n\nuse crate::incremental::dbsp::{Delta, DeltaPair};\nuse crate::incremental::operator::{\n    ComputationTracker, DbspStateCursors, EvalState, IncrementalOperator,\n};\nuse crate::sync::Arc;\nuse crate::sync::Mutex;\nuse crate::types::IOResult;\nuse crate::{Result, Value};\nuse std::cmp::Ordering;\n\n/// Filter predicate for filtering rows\n#[derive(Debug, Clone)]\npub enum FilterPredicate {\n    /// Column = value (using column index)\n    Equals { column_idx: usize, value: Value },\n    /// Column != value (using column index)\n    NotEquals { column_idx: usize, value: Value },\n    /// Column > value (using column index)\n    GreaterThan { column_idx: usize, value: Value },\n    /// Column >= value (using column index)\n    GreaterThanOrEqual { column_idx: usize, value: Value },\n    /// Column < value (using column index)\n    LessThan { column_idx: usize, value: Value },\n    /// Column <= value (using column index)\n    LessThanOrEqual { column_idx: usize, value: Value },\n\n    /// Column = Column comparisons\n    ColumnEquals { left_idx: usize, right_idx: usize },\n    /// Column != Column comparisons\n    ColumnNotEquals { left_idx: usize, right_idx: usize },\n    /// Column > Column comparisons\n    ColumnGreaterThan { left_idx: usize, right_idx: usize },\n    /// Column >= Column comparisons\n    ColumnGreaterThanOrEqual { left_idx: usize, right_idx: usize },\n    /// Column < Column comparisons\n    ColumnLessThan { left_idx: usize, right_idx: usize },\n    /// Column <= Column comparisons\n    ColumnLessThanOrEqual { left_idx: usize, right_idx: usize },\n\n    /// Column IS NULL check\n    IsNull { column_idx: usize },\n    /// Column IS NOT NULL check\n    IsNotNull { column_idx: usize },\n\n    /// Logical AND of two predicates\n    And(Box<FilterPredicate>, Box<FilterPredicate>),\n    /// Logical OR of two predicates\n    Or(Box<FilterPredicate>, Box<FilterPredicate>),\n    /// No predicate (accept all rows)\n    None,\n}\n\n/// Filter operator - filters rows based on predicate\n#[derive(Debug)]\npub struct FilterOperator {\n    predicate: FilterPredicate,\n    tracker: Option<Arc<Mutex<ComputationTracker>>>,\n}\n\nimpl FilterOperator {\n    pub fn new(predicate: FilterPredicate) -> Self {\n        Self {\n            predicate,\n            tracker: None,\n        }\n    }\n\n    /// Get the predicate for this filter\n    pub fn predicate(&self) -> &FilterPredicate {\n        &self.predicate\n    }\n\n    pub fn evaluate_predicate(&self, values: &[Value]) -> bool {\n        match &self.predicate {\n            FilterPredicate::None => true,\n            FilterPredicate::Equals { column_idx, value } => {\n                let v = &values[*column_idx];\n                v == value\n            }\n            FilterPredicate::NotEquals { column_idx, value } => {\n                let v = &values[*column_idx];\n                v != value\n            }\n            FilterPredicate::GreaterThan { column_idx, value } => {\n                let v = &values[*column_idx];\n                v.cmp(value) == Ordering::Greater\n            }\n            FilterPredicate::GreaterThanOrEqual { column_idx, value } => {\n                let v = &values[*column_idx];\n                v.cmp(value) != Ordering::Less\n            }\n            FilterPredicate::LessThan { column_idx, value } => {\n                let v = &values[*column_idx];\n                v.cmp(value) == Ordering::Less\n            }\n            FilterPredicate::LessThanOrEqual { column_idx, value } => {\n                let v = &values[*column_idx];\n                v.cmp(value) != Ordering::Greater\n            }\n            FilterPredicate::And(left, right) => {\n                // Temporarily create sub-filters to evaluate\n                let left_filter = FilterOperator::new((**left).clone());\n                let right_filter = FilterOperator::new((**right).clone());\n                left_filter.evaluate_predicate(values) && right_filter.evaluate_predicate(values)\n            }\n            FilterPredicate::Or(left, right) => {\n                let left_filter = FilterOperator::new((**left).clone());\n                let right_filter = FilterOperator::new((**right).clone());\n                left_filter.evaluate_predicate(values) || right_filter.evaluate_predicate(values)\n            }\n\n            FilterPredicate::ColumnEquals {\n                left_idx,\n                right_idx,\n            } => {\n                let left = &values[*left_idx];\n                let right = &values[*right_idx];\n                left == right\n            }\n            FilterPredicate::ColumnNotEquals {\n                left_idx,\n                right_idx,\n            } => {\n                let left = &values[*left_idx];\n                let right = &values[*right_idx];\n                left != right\n            }\n            FilterPredicate::ColumnGreaterThan {\n                left_idx,\n                right_idx,\n            } => {\n                let left = &values[*left_idx];\n                let right = &values[*right_idx];\n                left.cmp(right) == Ordering::Greater\n            }\n            FilterPredicate::ColumnGreaterThanOrEqual {\n                left_idx,\n                right_idx,\n            } => {\n                let left = &values[*left_idx];\n                let right = &values[*right_idx];\n                left.cmp(right) != Ordering::Less\n            }\n            FilterPredicate::ColumnLessThan {\n                left_idx,\n                right_idx,\n            } => {\n                let left = &values[*left_idx];\n                let right = &values[*right_idx];\n                left.cmp(right) == Ordering::Less\n            }\n            FilterPredicate::ColumnLessThanOrEqual {\n                left_idx,\n                right_idx,\n            } => {\n                let left = &values[*left_idx];\n                let right = &values[*right_idx];\n                left.cmp(right) != Ordering::Greater\n            }\n            FilterPredicate::IsNull { column_idx } => {\n                matches!(values[*column_idx], Value::Null)\n            }\n            FilterPredicate::IsNotNull { column_idx } => {\n                !matches!(values[*column_idx], Value::Null)\n            }\n        }\n    }\n}\n\nimpl IncrementalOperator for FilterOperator {\n    fn eval(\n        &mut self,\n        state: &mut EvalState,\n        _cursors: &mut DbspStateCursors,\n    ) -> Result<IOResult<Delta>> {\n        let delta = match state {\n            EvalState::Init { deltas } => {\n                // Filter operators only use left_delta, right_delta must be empty\n                assert!(\n                    deltas.right.is_empty(),\n                    \"FilterOperator expects right_delta to be empty\"\n                );\n                std::mem::take(&mut deltas.left)\n            }\n            _ => unreachable!(\n                \"FilterOperator doesn't execute the state machine. Should be in Init state\"\n            ),\n        };\n\n        let mut output_delta = Delta::new();\n\n        // Process the delta through the filter\n        for (row, weight) in delta.changes {\n            if let Some(tracker) = &self.tracker {\n                tracker.lock().record_filter();\n            }\n\n            // Only pass through rows that satisfy the filter predicate\n            // For deletes (weight < 0), we only pass them if the row values\n            // would have passed the filter (meaning it was in the view)\n            if self.evaluate_predicate(&row.values) {\n                output_delta.changes.push((row, weight));\n            }\n        }\n\n        *state = EvalState::Done;\n        Ok(IOResult::Done(output_delta))\n    }\n\n    fn commit(\n        &mut self,\n        deltas: DeltaPair,\n        _cursors: &mut DbspStateCursors,\n    ) -> Result<IOResult<Delta>> {\n        // Filter operator only uses left delta, right must be empty\n        assert!(\n            deltas.right.is_empty(),\n            \"FilterOperator expects right delta to be empty in commit\"\n        );\n\n        let mut output_delta = Delta::new();\n\n        // Commit the delta to our internal state\n        // Only pass through and track rows that satisfy the filter predicate\n        for (row, weight) in deltas.left.changes {\n            if let Some(tracker) = &self.tracker {\n                tracker.lock().record_filter();\n            }\n\n            // Only track and output rows that pass the filter\n            // For deletes, this means the row was in the view (its values pass the filter)\n            // For inserts, this means the row should be in the view\n            if self.evaluate_predicate(&row.values) {\n                output_delta.changes.push((row, weight));\n            }\n        }\n\n        Ok(IOResult::Done(output_delta))\n    }\n\n    fn set_tracker(&mut self, tracker: Arc<Mutex<ComputationTracker>>) {\n        self.tracker = Some(tracker);\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::types::Text;\n\n    #[test]\n    fn test_is_null_predicate() {\n        let predicate = FilterPredicate::IsNull { column_idx: 1 };\n        let filter = FilterOperator::new(predicate);\n\n        // Test with NULL value\n        let values_with_null = vec![\n            Value::from_i64(1),\n            Value::Null,\n            Value::Text(Text::from(\"test\")),\n        ];\n        assert!(filter.evaluate_predicate(&values_with_null));\n\n        // Test with non-NULL value\n        let values_without_null = vec![\n            Value::from_i64(1),\n            Value::from_i64(42),\n            Value::Text(Text::from(\"test\")),\n        ];\n        assert!(!filter.evaluate_predicate(&values_without_null));\n\n        // Test with different non-NULL types\n        let values_with_text = vec![\n            Value::from_i64(1),\n            Value::Text(Text::from(\"not null\")),\n            Value::Text(Text::from(\"test\")),\n        ];\n        assert!(!filter.evaluate_predicate(&values_with_text));\n\n        let values_with_blob = vec![\n            Value::from_i64(1),\n            Value::Blob(vec![1, 2, 3]),\n            Value::Text(Text::from(\"test\")),\n        ];\n        assert!(!filter.evaluate_predicate(&values_with_blob));\n    }\n\n    #[test]\n    fn test_is_not_null_predicate() {\n        let predicate = FilterPredicate::IsNotNull { column_idx: 1 };\n        let filter = FilterOperator::new(predicate);\n\n        // Test with NULL value\n        let values_with_null = vec![\n            Value::from_i64(1),\n            Value::Null,\n            Value::Text(Text::from(\"test\")),\n        ];\n        assert!(!filter.evaluate_predicate(&values_with_null));\n\n        // Test with non-NULL value (Integer)\n        let values_with_integer = vec![\n            Value::from_i64(1),\n            Value::from_i64(42),\n            Value::Text(Text::from(\"test\")),\n        ];\n        assert!(filter.evaluate_predicate(&values_with_integer));\n\n        // Test with non-NULL value (Text)\n        let values_with_text = vec![\n            Value::from_i64(1),\n            Value::Text(Text::from(\"not null\")),\n            Value::Text(Text::from(\"test\")),\n        ];\n        assert!(filter.evaluate_predicate(&values_with_text));\n\n        // Test with non-NULL value (Blob)\n        let values_with_blob = vec![\n            Value::from_i64(1),\n            Value::Blob(vec![1, 2, 3]),\n            Value::Text(Text::from(\"test\")),\n        ];\n        assert!(filter.evaluate_predicate(&values_with_blob));\n    }\n\n    #[test]\n    fn test_is_null_with_and() {\n        // Test: column_0 = 1 AND column_1 IS NULL\n        let predicate = FilterPredicate::And(\n            Box::new(FilterPredicate::Equals {\n                column_idx: 0,\n                value: Value::from_i64(1),\n            }),\n            Box::new(FilterPredicate::IsNull { column_idx: 1 }),\n        );\n        let filter = FilterOperator::new(predicate);\n\n        // Should match: column_0 = 1 AND column_1 IS NULL\n        let values_match = vec![\n            Value::from_i64(1),\n            Value::Null,\n            Value::Text(Text::from(\"test\")),\n        ];\n        assert!(filter.evaluate_predicate(&values_match));\n\n        // Should not match: column_0 = 2 AND column_1 IS NULL\n        let values_wrong_first = vec![\n            Value::from_i64(2),\n            Value::Null,\n            Value::Text(Text::from(\"test\")),\n        ];\n        assert!(!filter.evaluate_predicate(&values_wrong_first));\n\n        // Should not match: column_0 = 1 AND column_1 IS NOT NULL\n        let values_not_null = vec![\n            Value::from_i64(1),\n            Value::from_i64(42),\n            Value::Text(Text::from(\"test\")),\n        ];\n        assert!(!filter.evaluate_predicate(&values_not_null));\n    }\n\n    #[test]\n    fn test_is_not_null_with_or() {\n        // Test: column_0 = 1 OR column_1 IS NOT NULL\n        let predicate = FilterPredicate::Or(\n            Box::new(FilterPredicate::Equals {\n                column_idx: 0,\n                value: Value::from_i64(1),\n            }),\n            Box::new(FilterPredicate::IsNotNull { column_idx: 1 }),\n        );\n        let filter = FilterOperator::new(predicate);\n\n        // Should match: column_0 = 1 (regardless of column_1)\n        let values_first_matches = vec![\n            Value::from_i64(1),\n            Value::Null,\n            Value::Text(Text::from(\"test\")),\n        ];\n        assert!(filter.evaluate_predicate(&values_first_matches));\n\n        // Should match: column_1 IS NOT NULL (regardless of column_0)\n        let values_second_matches = vec![\n            Value::from_i64(2),\n            Value::from_i64(42),\n            Value::Text(Text::from(\"test\")),\n        ];\n        assert!(filter.evaluate_predicate(&values_second_matches));\n\n        // Should not match: column_0 != 1 AND column_1 IS NULL\n        let values_no_match = vec![\n            Value::from_i64(2),\n            Value::Null,\n            Value::Text(Text::from(\"test\")),\n        ];\n        assert!(!filter.evaluate_predicate(&values_no_match));\n    }\n\n    #[test]\n    fn test_complex_null_predicates() {\n        // Test: (column_0 IS NULL OR column_1 IS NOT NULL) AND column_2 = 'test'\n        let predicate = FilterPredicate::And(\n            Box::new(FilterPredicate::Or(\n                Box::new(FilterPredicate::IsNull { column_idx: 0 }),\n                Box::new(FilterPredicate::IsNotNull { column_idx: 1 }),\n            )),\n            Box::new(FilterPredicate::Equals {\n                column_idx: 2,\n                value: Value::Text(Text::from(\"test\")),\n            }),\n        );\n        let filter = FilterOperator::new(predicate);\n\n        // Should match: column_0 IS NULL, column_2 = 'test'\n        let values1 = vec![Value::Null, Value::Null, Value::Text(Text::from(\"test\"))];\n        assert!(filter.evaluate_predicate(&values1));\n\n        // Should match: column_1 IS NOT NULL, column_2 = 'test'\n        let values2 = vec![\n            Value::from_i64(1),\n            Value::from_i64(42),\n            Value::Text(Text::from(\"test\")),\n        ];\n        assert!(filter.evaluate_predicate(&values2));\n\n        // Should not match: column_2 != 'test'\n        let values3 = vec![\n            Value::Null,\n            Value::from_i64(42),\n            Value::Text(Text::from(\"other\")),\n        ];\n        assert!(!filter.evaluate_predicate(&values3));\n\n        // Should not match: column_0 IS NOT NULL AND column_1 IS NULL AND column_2 = 'test'\n        let values4 = vec![\n            Value::from_i64(1),\n            Value::Null,\n            Value::Text(Text::from(\"test\")),\n        ];\n        assert!(!filter.evaluate_predicate(&values4));\n    }\n\n    #[test]\n    fn test_cross_type_numeric_comparisons() {\n        // GreaterThan: Integer > Float\n        let predicate = FilterPredicate::GreaterThan {\n            column_idx: 0,\n            value: Value::from_f64(1.5),\n        };\n        let filter = FilterOperator::new(predicate);\n        assert!(filter.evaluate_predicate(&[Value::from_i64(2)])); // 2 > 1.5\n        assert!(!filter.evaluate_predicate(&[Value::from_i64(1)])); // 1 > 1.5\n\n        // GreaterThan: Float > Integer\n        let predicate = FilterPredicate::GreaterThan {\n            column_idx: 0,\n            value: Value::from_i64(2),\n        };\n        let filter = FilterOperator::new(predicate);\n        assert!(filter.evaluate_predicate(&[Value::from_f64(2.5)])); // 2.5 > 2\n        assert!(!filter.evaluate_predicate(&[Value::from_f64(1.5)])); // 1.5 > 2\n\n        // GreaterThanOrEqual: Integer >= Float\n        let predicate = FilterPredicate::GreaterThanOrEqual {\n            column_idx: 0,\n            value: Value::from_f64(2.0),\n        };\n        let filter = FilterOperator::new(predicate);\n        assert!(filter.evaluate_predicate(&[Value::from_i64(2)])); // 2 >= 2.0\n        assert!(filter.evaluate_predicate(&[Value::from_i64(3)])); // 3 >= 2.0\n        assert!(!filter.evaluate_predicate(&[Value::from_i64(1)])); // 1 >= 2.0\n\n        // GreaterThanOrEqual: Float >= Integer\n        let predicate = FilterPredicate::GreaterThanOrEqual {\n            column_idx: 0,\n            value: Value::from_i64(2),\n        };\n        let filter = FilterOperator::new(predicate);\n        assert!(filter.evaluate_predicate(&[Value::from_f64(2.0)])); // 2.0 >= 2\n        assert!(!filter.evaluate_predicate(&[Value::from_f64(1.9)])); // 1.9 >= 2\n\n        // LessThan: Integer < Float\n        let predicate = FilterPredicate::LessThan {\n            column_idx: 0,\n            value: Value::from_f64(1.5),\n        };\n        let filter = FilterOperator::new(predicate);\n        assert!(filter.evaluate_predicate(&[Value::from_i64(1)])); // 1 < 1.5\n        assert!(!filter.evaluate_predicate(&[Value::from_i64(2)])); // 2 < 1.5\n\n        // LessThan: Float < Integer\n        let predicate = FilterPredicate::LessThan {\n            column_idx: 0,\n            value: Value::from_i64(2),\n        };\n        let filter = FilterOperator::new(predicate);\n        assert!(filter.evaluate_predicate(&[Value::from_f64(1.5)])); // 1.5 < 2\n        assert!(!filter.evaluate_predicate(&[Value::from_f64(2.5)])); // 2.5 < 2\n\n        // LessThanOrEqual: Integer <= Float\n        let predicate = FilterPredicate::LessThanOrEqual {\n            column_idx: 0,\n            value: Value::from_f64(2.0),\n        };\n        let filter = FilterOperator::new(predicate);\n        assert!(filter.evaluate_predicate(&[Value::from_i64(2)])); // 2 <= 2.0\n        assert!(filter.evaluate_predicate(&[Value::from_i64(1)])); // 1 <= 2.0\n        assert!(!filter.evaluate_predicate(&[Value::from_i64(3)])); // 3 <= 2.0\n\n        // LessThanOrEqual: Float <= Integer\n        let predicate = FilterPredicate::LessThanOrEqual {\n            column_idx: 0,\n            value: Value::from_i64(2),\n        };\n        let filter = FilterOperator::new(predicate);\n        assert!(filter.evaluate_predicate(&[Value::from_f64(2.0)])); // 2.0 <= 2\n        assert!(!filter.evaluate_predicate(&[Value::from_f64(2.1)])); // 2.1 <= 2\n    }\n}\n"
  },
  {
    "path": "core/incremental/input_operator.rs",
    "content": "// Input operator for DBSP-style incremental computation\n// This operator serves as the entry point for data into the incremental computation pipeline\n\nuse crate::incremental::dbsp::{Delta, DeltaPair};\nuse crate::incremental::operator::{\n    ComputationTracker, DbspStateCursors, EvalState, IncrementalOperator,\n};\nuse crate::sync::Arc;\nuse crate::sync::Mutex;\nuse crate::types::IOResult;\nuse crate::Result;\n\n/// Input operator - source of data for the circuit\n/// Represents base relations/tables that receive external updates\n#[derive(Debug)]\npub struct InputOperator {\n    #[allow(dead_code)]\n    name: String,\n}\n\nimpl InputOperator {\n    pub fn new(name: String) -> Self {\n        Self { name }\n    }\n}\n\nimpl IncrementalOperator for InputOperator {\n    fn eval(\n        &mut self,\n        state: &mut EvalState,\n        _cursors: &mut DbspStateCursors,\n    ) -> Result<IOResult<Delta>> {\n        match state {\n            EvalState::Init { deltas } => {\n                // Input operators only use left_delta, right_delta must be empty\n                assert!(\n                    deltas.right.is_empty(),\n                    \"InputOperator expects right_delta to be empty\"\n                );\n                let output = std::mem::take(&mut deltas.left);\n                *state = EvalState::Done;\n                Ok(IOResult::Done(output))\n            }\n            _ => unreachable!(\n                \"InputOperator doesn't execute the state machine. Should be in Init state\"\n            ),\n        }\n    }\n\n    fn commit(\n        &mut self,\n        deltas: DeltaPair,\n        _cursors: &mut DbspStateCursors,\n    ) -> Result<IOResult<Delta>> {\n        // Input operator only uses left delta, right must be empty\n        assert!(\n            deltas.right.is_empty(),\n            \"InputOperator expects right delta to be empty in commit\"\n        );\n        // Input operator passes through the delta unchanged during commit\n        Ok(IOResult::Done(deltas.left))\n    }\n\n    fn set_tracker(&mut self, _tracker: Arc<Mutex<ComputationTracker>>) {\n        // Input operator doesn't need tracking\n    }\n}\n"
  },
  {
    "path": "core/incremental/join_operator.rs",
    "content": "#![allow(dead_code)]\n\nuse crate::incremental::dbsp::Hash128;\nuse crate::incremental::dbsp::{Delta, DeltaPair, HashableRow};\nuse crate::incremental::operator::{\n    generate_storage_id, ComputationTracker, DbspStateCursors, EvalState, IncrementalOperator,\n};\nuse crate::incremental::persistence::WriteRow;\nuse crate::numeric::Numeric;\nuse crate::storage::btree::CursorTrait;\nuse crate::sync::Arc;\nuse crate::sync::Mutex;\nuse crate::types::{IOResult, ImmutableRecord, SeekKey, SeekOp, SeekResult};\nuse crate::{return_and_restore_if_io, return_if_io, Result, Value};\n\n#[derive(Debug, Clone, PartialEq)]\npub enum JoinType {\n    Inner,\n    Left,\n    Right,\n    Full,\n    Cross,\n}\n\n// Helper function to read the next row from the BTree for joins\nfn read_next_join_row(\n    storage_id: i64,\n    join_key: &HashableRow,\n    last_element_hash: Option<Hash128>,\n    cursors: &mut DbspStateCursors,\n) -> Result<IOResult<Option<(Hash128, HashableRow, isize)>>> {\n    // Build the index key: (storage_id, zset_id, element_id)\n    // zset_id is the hash of the join key\n    let zset_hash = join_key.cached_hash();\n\n    // For iteration, use the last element hash if we have one, or NULL to start\n    let index_key_values = match last_element_hash {\n        Some(last_hash) => vec![\n            Value::from_i64(storage_id),\n            zset_hash.to_value(),\n            last_hash.to_value(),\n        ],\n        None => vec![\n            Value::from_i64(storage_id),\n            zset_hash.to_value(),\n            Value::Null, // Start iteration from beginning\n        ],\n    };\n\n    let index_record = ImmutableRecord::from_values(&index_key_values, index_key_values.len());\n\n    // Use GE (>=) for initial seek with NULL, GT (>) for continuation\n    let seek_op = if last_element_hash.is_none() {\n        SeekOp::GE { eq_only: false }\n    } else {\n        SeekOp::GT\n    };\n\n    let seek_result = return_if_io!(cursors\n        .index_cursor\n        .seek(SeekKey::IndexKey(&index_record), seek_op));\n\n    if !matches!(seek_result, SeekResult::Found) {\n        return Ok(IOResult::Done(None));\n    }\n\n    // Check if we're still in the same (storage_id, zset_id) range\n    let current_record = return_if_io!(cursors.index_cursor.record());\n\n    // Extract all needed values from the record before dropping it\n    let (found_storage_id, found_zset_hash, element_hash) = if let Some(rec) = current_record {\n        let values = rec.get_three_values(0, 1, 2);\n\n        // Index has 4 values: storage_id, zset_id, element_id, rowid (appended by WriteRow)\n        if let Ok((v0, v1, v2)) = values {\n            let found_storage_id = match &v0.to_owned() {\n                Value::Numeric(Numeric::Integer(id)) => *id,\n                _ => return Ok(IOResult::Done(None)),\n            };\n            let found_zset_hash = match &v1.to_owned() {\n                Value::Blob(blob) => Hash128::from_blob(blob).ok_or_else(|| {\n                    crate::LimboError::InternalError(\"Invalid zset_hash blob\".to_string())\n                })?,\n                _ => return Ok(IOResult::Done(None)),\n            };\n            let element_hash = match &v2.to_owned() {\n                Value::Blob(blob) => Hash128::from_blob(blob).ok_or_else(|| {\n                    crate::LimboError::InternalError(\"Invalid element_hash blob\".to_string())\n                })?,\n                _ => {\n                    return Ok(IOResult::Done(None));\n                }\n            };\n            (found_storage_id, found_zset_hash, element_hash)\n        } else {\n            return Ok(IOResult::Done(None));\n        }\n    } else {\n        return Ok(IOResult::Done(None));\n    };\n\n    // Now we can safely check if we're in the right range\n    // If we've moved to a different storage_id or zset_id, we're done\n    if found_storage_id != storage_id || found_zset_hash != zset_hash {\n        return Ok(IOResult::Done(None));\n    }\n\n    // Now get the actual row from the table using the rowid from the index\n    let rowid = return_if_io!(cursors.index_cursor.rowid());\n    if let Some(rowid) = rowid {\n        return_if_io!(cursors\n            .table_cursor\n            .seek(SeekKey::TableRowId(rowid), SeekOp::GE { eq_only: true }));\n\n        let table_record = return_if_io!(cursors.table_cursor.record());\n        if let Some(rec) = table_record {\n            let table_values = rec.get_two_values(3, 4);\n            // Table format: [storage_id, zset_id, element_id, value_blob, weight]\n            if let Ok((value_at_3, value_at_4)) = table_values {\n                // Deserialize the row from the blob\n                let value_at_3 = value_at_3.to_owned();\n                let blob = match value_at_3 {\n                    Value::Blob(ref b) => b,\n                    _ => return Ok(IOResult::Done(None)),\n                };\n\n                // The blob contains the serialized HashableRow\n                // For now, let's deserialize it simply\n                let row = deserialize_hashable_row(blob)?;\n\n                let weight = match &value_at_4.to_owned() {\n                    Value::Numeric(Numeric::Integer(w)) => *w as isize,\n                    _ => return Ok(IOResult::Done(None)),\n                };\n\n                return Ok(IOResult::Done(Some((element_hash, row, weight))));\n            }\n        }\n    }\n    Ok(IOResult::Done(None))\n}\n\n// Join-specific eval states\n#[derive(Debug)]\npub enum JoinEvalState {\n    ProcessDeltaJoin {\n        deltas: DeltaPair,\n        output: Delta,\n    },\n    ProcessLeftJoin {\n        deltas: DeltaPair,\n        output: Delta,\n        current_idx: usize,\n        last_row_scanned: Option<Hash128>,\n    },\n    ProcessRightJoin {\n        deltas: DeltaPair,\n        output: Delta,\n        current_idx: usize,\n        last_row_scanned: Option<Hash128>,\n    },\n    Done {\n        output: Delta,\n    },\n}\n\nimpl JoinEvalState {\n    fn combine_rows(\n        left_row: &HashableRow,\n        left_weight: i64,\n        right_row: &HashableRow,\n        right_weight: i64,\n        output: &mut Delta,\n    ) {\n        // Combine the rows\n        let mut combined_values = left_row.values.clone();\n        combined_values.extend(right_row.values.clone());\n        // Use hash of combined values as synthetic rowid\n        let temp_row = HashableRow::new(0, combined_values.clone());\n        let joined_rowid = temp_row.cached_hash().as_i64();\n        let joined_row = HashableRow::new(joined_rowid, combined_values);\n\n        // Add to output with combined weight\n        let combined_weight = left_weight * right_weight;\n        output.changes.push((joined_row, combined_weight as isize));\n    }\n\n    fn process_join_state(\n        &mut self,\n        cursors: &mut DbspStateCursors,\n        left_key_indices: &[usize],\n        right_key_indices: &[usize],\n        left_storage_id: i64,\n        right_storage_id: i64,\n    ) -> Result<IOResult<Delta>> {\n        loop {\n            match self {\n                JoinEvalState::ProcessDeltaJoin { deltas, output } => {\n                    // Move to ProcessLeftJoin\n                    *self = JoinEvalState::ProcessLeftJoin {\n                        deltas: std::mem::take(deltas),\n                        output: std::mem::take(output),\n                        current_idx: 0,\n                        last_row_scanned: None,\n                    };\n                }\n                JoinEvalState::ProcessLeftJoin {\n                    deltas,\n                    output,\n                    current_idx,\n                    last_row_scanned,\n                } => {\n                    if *current_idx >= deltas.left.changes.len() {\n                        *self = JoinEvalState::ProcessRightJoin {\n                            deltas: std::mem::take(deltas),\n                            output: std::mem::take(output),\n                            current_idx: 0,\n                            last_row_scanned: None,\n                        };\n                    } else {\n                        let (left_row, left_weight) = &deltas.left.changes[*current_idx];\n                        // Extract join key using provided indices\n                        let key_values: Vec<Value> = left_key_indices\n                            .iter()\n                            .map(|&idx| left_row.values.get(idx).cloned().unwrap_or(Value::Null))\n                            .collect();\n                        let left_key = HashableRow::new(0, key_values);\n\n                        let next_row = return_if_io!(read_next_join_row(\n                            right_storage_id,\n                            &left_key,\n                            *last_row_scanned,\n                            cursors\n                        ));\n                        match next_row {\n                            Some((element_hash, right_row, right_weight)) => {\n                                Self::combine_rows(\n                                    left_row,\n                                    (*left_weight) as i64,\n                                    &right_row,\n                                    right_weight as i64,\n                                    output,\n                                );\n                                // Continue scanning with this left row\n                                *self = JoinEvalState::ProcessLeftJoin {\n                                    deltas: std::mem::take(deltas),\n                                    output: std::mem::take(output),\n                                    current_idx: *current_idx,\n                                    last_row_scanned: Some(element_hash),\n                                };\n                            }\n                            None => {\n                                // No more matches for this left row, move to next\n                                *self = JoinEvalState::ProcessLeftJoin {\n                                    deltas: std::mem::take(deltas),\n                                    output: std::mem::take(output),\n                                    current_idx: *current_idx + 1,\n                                    last_row_scanned: None,\n                                };\n                            }\n                        }\n                    }\n                }\n                JoinEvalState::ProcessRightJoin {\n                    deltas,\n                    output,\n                    current_idx,\n                    last_row_scanned,\n                } => {\n                    if *current_idx >= deltas.right.changes.len() {\n                        *self = JoinEvalState::Done {\n                            output: std::mem::take(output),\n                        };\n                    } else {\n                        let (right_row, right_weight) = &deltas.right.changes[*current_idx];\n                        // Extract join key using provided indices\n                        let key_values: Vec<Value> = right_key_indices\n                            .iter()\n                            .map(|&idx| right_row.values.get(idx).cloned().unwrap_or(Value::Null))\n                            .collect();\n                        let right_key = HashableRow::new(0, key_values);\n\n                        let next_row = return_if_io!(read_next_join_row(\n                            left_storage_id,\n                            &right_key,\n                            *last_row_scanned,\n                            cursors\n                        ));\n                        match next_row {\n                            Some((element_hash, left_row, left_weight)) => {\n                                Self::combine_rows(\n                                    &left_row,\n                                    left_weight as i64,\n                                    right_row,\n                                    (*right_weight) as i64,\n                                    output,\n                                );\n                                // Continue scanning with this right row\n                                *self = JoinEvalState::ProcessRightJoin {\n                                    deltas: std::mem::take(deltas),\n                                    output: std::mem::take(output),\n                                    current_idx: *current_idx,\n                                    last_row_scanned: Some(element_hash),\n                                };\n                            }\n                            None => {\n                                // No more matches for this right row, move to next\n                                *self = JoinEvalState::ProcessRightJoin {\n                                    deltas: std::mem::take(deltas),\n                                    output: std::mem::take(output),\n                                    current_idx: *current_idx + 1,\n                                    last_row_scanned: None,\n                                };\n                            }\n                        }\n                    }\n                }\n                JoinEvalState::Done { output } => {\n                    return Ok(IOResult::Done(std::mem::take(output)));\n                }\n            }\n        }\n    }\n}\n\n#[derive(Debug)]\nenum JoinCommitState {\n    Idle,\n    Eval {\n        eval_state: EvalState,\n    },\n    CommitLeftDelta {\n        deltas: DeltaPair,\n        output: Delta,\n        current_idx: usize,\n        write_row: WriteRow,\n    },\n    CommitRightDelta {\n        deltas: DeltaPair,\n        output: Delta,\n        current_idx: usize,\n        write_row: WriteRow,\n    },\n    Invalid,\n}\n\n/// Join operator - performs incremental join between two relations\n/// Implements the DBSP formula: δ(R ⋈ S) = (δR ⋈ S) ∪ (R ⋈ δS) ∪ (δR ⋈ δS)\n#[derive(Debug)]\npub struct JoinOperator {\n    /// Unique operator ID for indexing in persistent storage\n    operator_id: i64,\n    /// Type of join to perform\n    join_type: JoinType,\n    /// Column indices for extracting join keys from left input\n    left_key_indices: Vec<usize>,\n    /// Column indices for extracting join keys from right input\n    right_key_indices: Vec<usize>,\n    /// Column names from left input\n    left_columns: Vec<String>,\n    /// Column names from right input\n    right_columns: Vec<String>,\n    /// Tracker for computation statistics\n    tracker: Option<Arc<Mutex<ComputationTracker>>>,\n\n    commit_state: JoinCommitState,\n}\n\nimpl JoinOperator {\n    pub fn new(\n        operator_id: i64,\n        join_type: JoinType,\n        left_key_indices: Vec<usize>,\n        right_key_indices: Vec<usize>,\n        left_columns: Vec<String>,\n        right_columns: Vec<String>,\n    ) -> Result<Self> {\n        // Check for unsupported join types\n        match join_type {\n            JoinType::Left => {\n                return Err(crate::LimboError::ParseError(\n                    \"LEFT OUTER JOIN is not yet supported in incremental views\".to_string(),\n                ))\n            }\n            JoinType::Right => {\n                return Err(crate::LimboError::ParseError(\n                    \"RIGHT OUTER JOIN is not yet supported in incremental views\".to_string(),\n                ))\n            }\n            JoinType::Full => {\n                return Err(crate::LimboError::ParseError(\n                    \"FULL OUTER JOIN is not yet supported in incremental views\".to_string(),\n                ))\n            }\n            JoinType::Cross => {\n                return Err(crate::LimboError::ParseError(\n                    \"CROSS JOIN is not yet supported in incremental views\".to_string(),\n                ))\n            }\n            JoinType::Inner => {} // Inner join is supported\n        }\n\n        let result = Self {\n            operator_id,\n            join_type,\n            left_key_indices,\n            right_key_indices,\n            left_columns,\n            right_columns,\n            tracker: None,\n            commit_state: JoinCommitState::Idle,\n        };\n        Ok(result)\n    }\n\n    /// Extract join key from row values using the specified indices\n    fn extract_join_key(&self, values: &[Value], indices: &[usize]) -> HashableRow {\n        let key_values: Vec<Value> = indices\n            .iter()\n            .map(|&idx| values.get(idx).cloned().unwrap_or(Value::Null))\n            .collect();\n        // Use 0 as a dummy rowid for join keys. They don't come from a table,\n        // so they don't need a rowid. Their key will be the hash of the row values.\n        HashableRow::new(0, key_values)\n    }\n\n    /// Generate storage ID for left table\n    fn left_storage_id(&self) -> i64 {\n        // Use column_index=0 for left side\n        generate_storage_id(self.operator_id, 0, 0)\n    }\n\n    /// Generate storage ID for right table\n    fn right_storage_id(&self) -> i64 {\n        // Use column_index=1 for right side\n        generate_storage_id(self.operator_id, 1, 0)\n    }\n\n    /// SQL-compliant comparison for join keys\n    /// Returns true if keys match according to SQL semantics (NULL != NULL)\n    fn sql_keys_equal(left_key: &HashableRow, right_key: &HashableRow) -> bool {\n        if left_key.values.len() != right_key.values.len() {\n            return false;\n        }\n\n        for (left_val, right_val) in left_key.values.iter().zip(right_key.values.iter()) {\n            // In SQL, NULL never equals NULL\n            if matches!(left_val, Value::Null) || matches!(right_val, Value::Null) {\n                return false;\n            }\n\n            // For non-NULL values, use regular comparison\n            if left_val != right_val {\n                return false;\n            }\n        }\n\n        true\n    }\n\n    fn process_join_state(\n        &mut self,\n        state: &mut EvalState,\n        cursors: &mut DbspStateCursors,\n    ) -> Result<IOResult<Delta>> {\n        // Get the join state out of the enum\n        match state {\n            EvalState::Join(js) => js.process_join_state(\n                cursors,\n                &self.left_key_indices,\n                &self.right_key_indices,\n                self.left_storage_id(),\n                self.right_storage_id(),\n            ),\n            _ => panic!(\"process_join_state called with non-join state\"),\n        }\n    }\n\n    fn eval_internal(\n        &mut self,\n        state: &mut EvalState,\n        cursors: &mut DbspStateCursors,\n    ) -> Result<IOResult<Delta>> {\n        loop {\n            let loop_state = std::mem::replace(state, EvalState::Uninitialized);\n            match loop_state {\n                EvalState::Uninitialized => {\n                    panic!(\"Cannot eval JoinOperator with Uninitialized state\");\n                }\n                EvalState::Init { deltas } => {\n                    let mut output = Delta::new();\n\n                    // Component 3: δR ⋈ δS (left delta join right delta)\n                    for (left_row, left_weight) in &deltas.left.changes {\n                        let left_key =\n                            self.extract_join_key(&left_row.values, &self.left_key_indices);\n\n                        for (right_row, right_weight) in &deltas.right.changes {\n                            let right_key =\n                                self.extract_join_key(&right_row.values, &self.right_key_indices);\n\n                            if Self::sql_keys_equal(&left_key, &right_key) {\n                                if let Some(tracker) = &self.tracker {\n                                    tracker.lock().record_join_lookup();\n                                }\n\n                                // Combine the rows\n                                let mut combined_values = left_row.values.clone();\n                                combined_values.extend(right_row.values.clone());\n\n                                // Create the joined row with a unique rowid\n                                // Use hash of the combined values to ensure uniqueness\n                                // Use hash of combined values as synthetic rowid\n                                let temp_row = HashableRow::new(0, combined_values.clone());\n                                let joined_rowid = temp_row.cached_hash().as_i64();\n                                let joined_row =\n                                    HashableRow::new(joined_rowid, combined_values.clone());\n\n                                // Add to output with combined weight\n                                let combined_weight = left_weight * right_weight;\n                                output.changes.push((joined_row, combined_weight));\n                            }\n                        }\n                    }\n\n                    *state = EvalState::Join(Box::new(JoinEvalState::ProcessDeltaJoin {\n                        deltas,\n                        output,\n                    }));\n                }\n                EvalState::Join(join_state) => {\n                    *state = EvalState::Join(join_state);\n                    let output = return_if_io!(self.process_join_state(state, cursors));\n                    return Ok(IOResult::Done(output));\n                }\n                EvalState::Done => {\n                    return Ok(IOResult::Done(Delta::new()));\n                }\n                EvalState::Aggregate(_) => {\n                    panic!(\"Aggregate state should not appear in join operator\");\n                }\n            }\n        }\n    }\n}\n\nfn deserialize_hashable_row(blob: &[u8]) -> Result<HashableRow> {\n    use crate::types::ImmutableRecord;\n\n    let record = ImmutableRecord::from_bin_record(blob.to_vec());\n    let all_values: Vec<Value> = record.get_values_owned()?;\n\n    if all_values.is_empty() {\n        return Err(crate::LimboError::InternalError(\n            \"HashableRow blob must contain at least rowid\".to_string(),\n        ));\n    }\n\n    // First value is the rowid\n    let rowid = match &all_values[0] {\n        Value::Numeric(Numeric::Integer(i)) => *i,\n        _ => {\n            return Err(crate::LimboError::InternalError(\n                \"First value must be rowid (integer)\".to_string(),\n            ))\n        }\n    };\n\n    // Rest are the row values\n    let values = all_values[1..].to_vec();\n\n    Ok(HashableRow::new(rowid, values))\n}\n\nfn serialize_hashable_row(row: &HashableRow) -> Vec<u8> {\n    use crate::types::ImmutableRecord;\n\n    let mut all_values = Vec::with_capacity(row.values.len() + 1);\n    all_values.push(Value::from_i64(row.rowid));\n    all_values.extend_from_slice(&row.values);\n\n    let record = ImmutableRecord::from_values(&all_values, all_values.len());\n    record.as_blob().clone()\n}\n\nimpl IncrementalOperator for JoinOperator {\n    fn eval(\n        &mut self,\n        state: &mut EvalState,\n        cursors: &mut DbspStateCursors,\n    ) -> Result<IOResult<Delta>> {\n        let delta = return_if_io!(self.eval_internal(state, cursors));\n        Ok(IOResult::Done(delta))\n    }\n\n    fn commit(\n        &mut self,\n        deltas: DeltaPair,\n        cursors: &mut DbspStateCursors,\n    ) -> Result<IOResult<Delta>> {\n        loop {\n            let mut state = std::mem::replace(&mut self.commit_state, JoinCommitState::Invalid);\n            match &mut state {\n                JoinCommitState::Idle => {\n                    self.commit_state = JoinCommitState::Eval {\n                        eval_state: deltas.clone().into(),\n                    }\n                }\n                JoinCommitState::Eval { ref mut eval_state } => {\n                    let output = return_and_restore_if_io!(\n                        &mut self.commit_state,\n                        state,\n                        self.eval(eval_state, cursors)\n                    );\n                    self.commit_state = JoinCommitState::CommitLeftDelta {\n                        deltas: deltas.clone(),\n                        output,\n                        current_idx: 0,\n                        write_row: WriteRow::new(),\n                    };\n                }\n                JoinCommitState::CommitLeftDelta {\n                    deltas,\n                    output,\n                    current_idx,\n                    ref mut write_row,\n                } => {\n                    if *current_idx >= deltas.left.changes.len() {\n                        self.commit_state = JoinCommitState::CommitRightDelta {\n                            deltas: std::mem::take(deltas),\n                            output: std::mem::take(output),\n                            current_idx: 0,\n                            write_row: WriteRow::new(),\n                        };\n                        continue;\n                    }\n\n                    let (row, weight) = &deltas.left.changes[*current_idx];\n                    // Extract join key from the left row\n                    let join_key = self.extract_join_key(&row.values, &self.left_key_indices);\n\n                    // The index key: (storage_id, zset_id, element_id)\n                    // zset_id is the hash of the join key, element_id is hash of the row\n                    let storage_id = self.left_storage_id();\n                    let zset_hash = join_key.cached_hash();\n                    let element_hash = row.cached_hash();\n                    let index_key = vec![\n                        Value::from_i64(storage_id),\n                        zset_hash.to_value(),\n                        element_hash.to_value(),\n                    ];\n\n                    // The record values: we'll store the serialized row as a blob\n                    let row_blob = serialize_hashable_row(row);\n                    let record_values = vec![\n                        Value::from_i64(self.left_storage_id()),\n                        zset_hash.to_value(),\n                        element_hash.to_value(),\n                        Value::Blob(row_blob),\n                    ];\n\n                    // Use return_and_restore_if_io to handle I/O properly\n                    return_and_restore_if_io!(\n                        &mut self.commit_state,\n                        state,\n                        write_row.write_row(cursors, index_key, record_values, *weight)\n                    );\n\n                    self.commit_state = JoinCommitState::CommitLeftDelta {\n                        deltas: deltas.clone(),\n                        output: output.clone(),\n                        current_idx: *current_idx + 1,\n                        write_row: WriteRow::new(),\n                    };\n                }\n                JoinCommitState::CommitRightDelta {\n                    deltas,\n                    output,\n                    current_idx,\n                    ref mut write_row,\n                } => {\n                    if *current_idx >= deltas.right.changes.len() {\n                        // Reset to Idle state for next commit\n                        self.commit_state = JoinCommitState::Idle;\n                        return Ok(IOResult::Done(output.clone()));\n                    }\n\n                    let (row, weight) = &deltas.right.changes[*current_idx];\n                    // Extract join key from the right row\n                    let join_key = self.extract_join_key(&row.values, &self.right_key_indices);\n\n                    // The index key: (storage_id, zset_id, element_id)\n                    let zset_hash = join_key.cached_hash();\n                    let element_hash = row.cached_hash();\n                    let index_key = vec![\n                        Value::from_i64(self.right_storage_id()),\n                        zset_hash.to_value(),\n                        element_hash.to_value(),\n                    ];\n\n                    // The record values: we'll store the serialized row as a blob\n                    let row_blob = serialize_hashable_row(row);\n                    let record_values = vec![\n                        Value::from_i64(self.right_storage_id()),\n                        zset_hash.to_value(),\n                        element_hash.to_value(),\n                        Value::Blob(row_blob),\n                    ];\n\n                    // Use return_and_restore_if_io to handle I/O properly\n                    return_and_restore_if_io!(\n                        &mut self.commit_state,\n                        state,\n                        write_row.write_row(cursors, index_key, record_values, *weight)\n                    );\n\n                    self.commit_state = JoinCommitState::CommitRightDelta {\n                        deltas: std::mem::take(deltas),\n                        output: std::mem::take(output),\n                        current_idx: *current_idx + 1,\n                        write_row: WriteRow::new(),\n                    };\n                }\n                JoinCommitState::Invalid => {\n                    panic!(\"Invalid join commit state\");\n                }\n            }\n        }\n    }\n\n    fn set_tracker(&mut self, tracker: Arc<Mutex<ComputationTracker>>) {\n        self.tracker = Some(tracker);\n    }\n}\n"
  },
  {
    "path": "core/incremental/merge_operator.rs",
    "content": "// Merge operator for DBSP - combines two delta streams\n// Used in recursive CTEs and UNION operations\n\nuse crate::incremental::dbsp::{Delta, DeltaPair, HashableRow};\nuse crate::incremental::operator::{\n    ComputationTracker, DbspStateCursors, EvalState, IncrementalOperator,\n};\nuse crate::sync::Arc;\nuse crate::sync::Mutex;\nuse crate::types::IOResult;\nuse crate::Result;\nuse std::collections::{hash_map::DefaultHasher, HashMap};\nuse std::fmt::{self, Display};\nuse std::hash::{Hash, Hasher};\n\n/// How the merge operator should handle rowids when combining deltas\n#[derive(Debug, Clone)]\npub enum UnionMode {\n    /// For UNION (distinct) - hash values only to merge duplicates\n    Distinct,\n    /// For UNION ALL - include source table name in hash to keep duplicates separate\n    All {\n        left_table: String,\n        right_table: String,\n    },\n}\n\n/// Merge operator that combines two input deltas into one output delta\n/// Handles both recursive CTEs and UNION/UNION ALL operations\n#[derive(Debug)]\npub struct MergeOperator {\n    operator_id: i64,\n    union_mode: UnionMode,\n    /// For UNION: tracks seen value hashes with their assigned rowids\n    /// For UNION ALL: tracks (source_id, original_rowid) -> assigned_rowid mappings\n    seen_rows: HashMap<u64, i64>, // hash -> assigned_rowid\n    /// Next rowid to assign for new rows\n    next_rowid: i64,\n}\n\nimpl MergeOperator {\n    /// Create a new merge operator with specified union mode\n    pub fn new(operator_id: i64, mode: UnionMode) -> Self {\n        Self {\n            operator_id,\n            union_mode: mode,\n            seen_rows: HashMap::default(),\n            next_rowid: 1,\n        }\n    }\n\n    /// Transform a delta's rowids based on the union mode with state tracking\n    fn transform_delta(&mut self, delta: Delta, is_left: bool) -> Delta {\n        match &self.union_mode {\n            UnionMode::Distinct => {\n                // For UNION distinct, track seen values and deduplicate\n                let mut output = Delta::new();\n                for (row, weight) in delta.changes {\n                    // Hash only the values (not rowid) for deduplication\n                    let temp_row = HashableRow::new(0, row.values.clone());\n                    let value_hash = temp_row.cached_hash().as_i64() as u64;\n\n                    // Check if we've seen this value before\n                    let assigned_rowid =\n                        if let Some(&existing_rowid) = self.seen_rows.get(&value_hash) {\n                            // Value already seen - use existing rowid\n                            existing_rowid\n                        } else {\n                            // New value - assign new rowid and remember it\n                            let new_rowid = self.next_rowid;\n                            self.next_rowid += 1;\n                            self.seen_rows.insert(value_hash, new_rowid);\n                            new_rowid\n                        };\n\n                    // Output the row with the assigned rowid\n                    let final_row = HashableRow::new(assigned_rowid, temp_row.values);\n                    output.changes.push((final_row, weight));\n                }\n                output\n            }\n            UnionMode::All {\n                left_table,\n                right_table,\n            } => {\n                // For UNION ALL, maintain consistent rowid mapping per source\n                let table = if is_left { left_table } else { right_table };\n                let mut source_hasher = DefaultHasher::new();\n                table.hash(&mut source_hasher);\n                let source_id = source_hasher.finish();\n\n                let mut output = Delta::new();\n                for (row, weight) in delta.changes {\n                    // Create a unique key for this (source, rowid) pair\n                    let mut key_hasher = DefaultHasher::new();\n                    source_id.hash(&mut key_hasher);\n                    row.rowid.hash(&mut key_hasher);\n                    let key_hash = key_hasher.finish();\n\n                    // Check if we've seen this (source, rowid) before\n                    let assigned_rowid =\n                        if let Some(&existing_rowid) = self.seen_rows.get(&key_hash) {\n                            // Use existing rowid for this (source, rowid) pair\n                            existing_rowid\n                        } else {\n                            // New row - assign new rowid\n                            let new_rowid = self.next_rowid;\n                            self.next_rowid += 1;\n                            self.seen_rows.insert(key_hash, new_rowid);\n                            new_rowid\n                        };\n\n                    // Create output row with consistent rowid\n                    let final_row = HashableRow::new(assigned_rowid, row.values.clone());\n                    output.changes.push((final_row, weight));\n                }\n                output\n            }\n        }\n    }\n}\n\nimpl Display for MergeOperator {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        match &self.union_mode {\n            UnionMode::Distinct => write!(f, \"MergeOperator({}, UNION)\", self.operator_id),\n            UnionMode::All { .. } => write!(f, \"MergeOperator({}, UNION ALL)\", self.operator_id),\n        }\n    }\n}\n\nimpl IncrementalOperator for MergeOperator {\n    fn eval(\n        &mut self,\n        input: &mut EvalState,\n        _cursors: &mut DbspStateCursors,\n    ) -> Result<IOResult<Delta>> {\n        match input {\n            EvalState::Init { deltas } => {\n                // Extract deltas from the evaluation state\n                let delta_pair = std::mem::take(deltas);\n\n                // Transform deltas based on union mode (with state tracking)\n                let left_transformed = self.transform_delta(delta_pair.left, true);\n                let right_transformed = self.transform_delta(delta_pair.right, false);\n\n                // Merge the transformed deltas\n                let mut output = Delta::new();\n                output.merge(&left_transformed);\n                output.merge(&right_transformed);\n\n                // Move to Done state\n                *input = EvalState::Done;\n\n                Ok(IOResult::Done(output))\n            }\n            EvalState::Aggregate(_) | EvalState::Join(_) | EvalState::Uninitialized => {\n                // Merge operator only handles Init state\n                unreachable!(\"MergeOperator only handles Init state\")\n            }\n            EvalState::Done => {\n                // Already evaluated\n                Ok(IOResult::Done(Delta::new()))\n            }\n        }\n    }\n\n    fn commit(\n        &mut self,\n        deltas: DeltaPair,\n        _cursors: &mut DbspStateCursors,\n    ) -> Result<IOResult<Delta>> {\n        // Transform deltas based on union mode\n        let left_transformed = self.transform_delta(deltas.left, true);\n        let right_transformed = self.transform_delta(deltas.right, false);\n\n        // Merge the transformed deltas\n        let mut output = Delta::new();\n        output.merge(&left_transformed);\n        output.merge(&right_transformed);\n\n        Ok(IOResult::Done(output))\n    }\n\n    fn set_tracker(&mut self, _tracker: Arc<Mutex<ComputationTracker>>) {\n        // Merge operator doesn't need tracking for now\n    }\n}\n"
  },
  {
    "path": "core/incremental/mod.rs",
    "content": "pub mod aggregate_operator;\npub mod compiler;\npub mod cursor;\npub mod dbsp;\npub mod expr_compiler;\npub mod filter_operator;\npub mod input_operator;\npub mod join_operator;\npub mod merge_operator;\npub mod operator;\npub mod persistence;\npub mod project_operator;\npub mod view;\n"
  },
  {
    "path": "core/incremental/operator.rs",
    "content": "#![allow(dead_code)]\n// Operator DAG for DBSP-style incremental computation\n// Based on Feldera DBSP design but adapted for Turso's architecture\n\npub use crate::incremental::aggregate_operator::{\n    AggregateEvalState, AggregateFunction, AggregateState,\n};\npub use crate::incremental::filter_operator::{FilterOperator, FilterPredicate};\npub use crate::incremental::input_operator::InputOperator;\npub use crate::incremental::join_operator::{JoinEvalState, JoinOperator, JoinType};\npub use crate::incremental::project_operator::{ProjectColumn, ProjectOperator};\n\nuse crate::incremental::dbsp::{Delta, DeltaPair};\n#[cfg(test)]\nuse crate::numeric::Numeric;\nuse crate::schema::{Index, IndexColumn};\nuse crate::storage::btree::BTreeCursor;\nuse crate::sync::Arc;\nuse crate::sync::Mutex;\nuse crate::types::IOResult;\nuse crate::Result;\nuse std::fmt::Debug;\n\n/// Struct to hold both table and index cursors for DBSP state operations\npub struct DbspStateCursors {\n    /// Cursor for the DBSP state table\n    pub table_cursor: BTreeCursor,\n    /// Cursor for the DBSP state table's primary key index\n    pub index_cursor: BTreeCursor,\n}\n\nimpl DbspStateCursors {\n    /// Create a new DbspStateCursors with both table and index cursors\n    pub fn new(table_cursor: BTreeCursor, index_cursor: BTreeCursor) -> Self {\n        Self {\n            table_cursor,\n            index_cursor,\n        }\n    }\n}\n\n/// Create an index definition for the DBSP state table\n/// This defines the primary key index on (operator_id, zset_id, element_id)\npub fn create_dbsp_state_index(root_page: i64) -> Index {\n    Index {\n        name: \"dbsp_state_pk\".to_string(),\n        table_name: \"dbsp_state\".to_string(),\n        root_page,\n        columns: vec![\n            IndexColumn {\n                name: \"operator_id\".to_string(),\n                order: turso_parser::ast::SortOrder::Asc,\n                collation: None,\n                pos_in_table: 0,\n                default: None,\n                expr: None,\n            },\n            IndexColumn {\n                name: \"zset_id\".to_string(),\n                order: turso_parser::ast::SortOrder::Asc,\n                collation: None,\n                pos_in_table: 1,\n                default: None,\n                expr: None,\n            },\n            IndexColumn {\n                name: \"element_id\".to_string(),\n                order: turso_parser::ast::SortOrder::Asc,\n                collation: None,\n                pos_in_table: 2,\n                default: None,\n                expr: None,\n            },\n        ],\n        unique: true,\n        ephemeral: false,\n        has_rowid: true,\n        where_clause: None,\n        index_method: None,\n        on_conflict: None,\n    }\n}\n\n/// Generate a storage ID with column index and operation type encoding\n/// Storage ID = (operator_id << 16) | (column_index << 2) | operation_type\n/// Bit layout (64-bit integer):\n/// - Bits 16-63 (48 bits): operator_id\n/// - Bits 2-15 (14 bits): column_index (supports up to 16,384 columns)\n/// - Bits 0-1 (2 bits): operation type (AGG_TYPE_REGULAR, AGG_TYPE_MINMAX, etc.)\npub fn generate_storage_id(operator_id: i64, column_index: usize, op_type: u8) -> i64 {\n    assert!(op_type <= 3, \"Invalid operation type\");\n    assert!(column_index < 16384, \"Column index too large\");\n\n    ((operator_id) << 16) | ((column_index as i64) << 2) | (op_type as i64)\n}\n\n// Generic eval state that delegates to operator-specific states\n#[derive(Debug)]\npub enum EvalState {\n    Uninitialized,\n    Init { deltas: DeltaPair },\n    Aggregate(Box<AggregateEvalState>),\n    Join(Box<JoinEvalState>),\n    Done,\n}\n\nimpl From<Delta> for EvalState {\n    fn from(delta: Delta) -> Self {\n        EvalState::Init {\n            deltas: delta.into(),\n        }\n    }\n}\n\nimpl From<DeltaPair> for EvalState {\n    fn from(deltas: DeltaPair) -> Self {\n        EvalState::Init { deltas }\n    }\n}\n\nimpl EvalState {\n    pub fn from_delta(delta: Delta) -> Self {\n        Self::Init {\n            deltas: delta.into(),\n        }\n    }\n\n    fn delta_ref(&self) -> &Delta {\n        match self {\n            EvalState::Init { deltas } => &deltas.left,\n            _ => panic!(\"delta_ref() can only be called when in Init state\",),\n        }\n    }\n    pub fn extract_delta(&mut self) -> Delta {\n        match self {\n            EvalState::Init { deltas } => {\n                let extracted = std::mem::take(&mut deltas.left);\n                *self = EvalState::Uninitialized;\n                extracted\n            }\n            _ => panic!(\"extract_delta() can only be called when in Init state\"),\n        }\n    }\n}\n\n/// Tracks computation counts to verify incremental behavior (for tests now), and in the future\n/// should be used to provide statistics.\n#[derive(Debug, Default, Clone)]\npub struct ComputationTracker {\n    pub filter_evaluations: usize,\n    pub project_operations: usize,\n    pub join_lookups: usize,\n    pub aggregation_updates: usize,\n    pub full_scans: usize,\n}\n\nimpl ComputationTracker {\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    pub fn record_filter(&mut self) {\n        self.filter_evaluations += 1;\n    }\n\n    pub fn record_project(&mut self) {\n        self.project_operations += 1;\n    }\n\n    pub fn record_join_lookup(&mut self) {\n        self.join_lookups += 1;\n    }\n\n    pub fn record_aggregation(&mut self) {\n        self.aggregation_updates += 1;\n    }\n\n    pub fn record_full_scan(&mut self) {\n        self.full_scans += 1;\n    }\n\n    pub fn total_computations(&self) -> usize {\n        self.filter_evaluations\n            + self.project_operations\n            + self.join_lookups\n            + self.aggregation_updates\n    }\n}\n\n/// Represents an operator in the dataflow graph\n#[derive(Debug, Clone)]\npub enum QueryOperator {\n    /// Table scan - source of data\n    TableScan {\n        table_name: String,\n        column_names: Vec<String>,\n    },\n\n    /// Filter rows based on predicate\n    Filter {\n        predicate: FilterPredicate,\n        input: usize, // Index of input operator\n    },\n\n    /// Project columns (select specific columns)\n    Project {\n        columns: Vec<ProjectColumn>,\n        input: usize,\n    },\n\n    /// Join two inputs\n    Join {\n        join_type: JoinType,\n        on_column: String,\n        left_input: usize,\n        right_input: usize,\n    },\n\n    /// Aggregate\n    Aggregate {\n        group_by: Vec<String>,\n        aggregates: Vec<AggregateFunction>,\n        input: usize,\n    },\n}\n\n/// Operator DAG (Directed Acyclic Graph)\n/// Base trait for incremental operators\n// SAFETY: This needs to be audited for thread safety.\n// See: https://github.com/tursodatabase/turso/issues/1552\npub trait IncrementalOperator: Debug + Send {\n    /// Evaluate the operator with a state, without modifying internal state\n    /// This is used during query execution to compute results\n    /// May need to read from storage to get current state (e.g., for aggregates)\n    ///\n    /// # Arguments\n    /// * `state` - The evaluation state (may be in progress from a previous I/O operation)\n    /// * `cursors` - Cursors for reading operator state from storage (table and optional index)\n    ///\n    /// # Returns\n    /// The output delta from the evaluation\n    fn eval(\n        &mut self,\n        state: &mut EvalState,\n        cursors: &mut DbspStateCursors,\n    ) -> Result<IOResult<Delta>>;\n\n    /// Commit deltas to the operator's internal state and return the output\n    /// This is called when a transaction commits, making changes permanent\n    /// Returns the output delta (what downstream operators should see)\n    /// The cursors parameter is for operators that need to persist state\n    fn commit(\n        &mut self,\n        deltas: DeltaPair,\n        cursors: &mut DbspStateCursors,\n    ) -> Result<IOResult<Delta>>;\n\n    /// Set computation tracker\n    fn set_tracker(&mut self, tracker: Arc<Mutex<ComputationTracker>>);\n}\n\n#[cfg(test)]\nmod tests {\n    use rustc_hash::FxHashSet as HashSet;\n\n    use super::*;\n    use crate::incremental::aggregate_operator::{AggregateOperator, AGG_TYPE_REGULAR};\n    use crate::incremental::dbsp::HashableRow;\n    use crate::storage::btree::CursorTrait;\n    use crate::storage::pager::CreateBTreeFlags;\n    use crate::sync::Arc;\n    use crate::sync::Mutex;\n    use crate::types::Text;\n    use crate::util::IOExt;\n    use crate::Value;\n    use crate::{Database, MemoryIO, IO};\n\n    /// Create a test pager for operator tests with both table and index\n    fn create_test_pager() -> (crate::sync::Arc<crate::Pager>, i64, i64) {\n        let io: Arc<dyn IO> = Arc::new(MemoryIO::new());\n        let db = Database::open_file(io.clone(), \":memory:\").unwrap();\n        let conn = db.connect().unwrap();\n\n        let pager = conn.pager.load().clone();\n\n        // Allocate page 1 first (database header)\n        let _ = pager.io.block(|| pager.allocate_page1());\n\n        // Create a BTree for the table\n        let table_root_page_id = pager\n            .io\n            .block(|| pager.btree_create(&CreateBTreeFlags::new_table()))\n            .expect(\"Failed to create BTree for aggregate state table\")\n            as i64;\n\n        // Create a BTree for the index\n        let index_root_page_id = pager\n            .io\n            .block(|| pager.btree_create(&CreateBTreeFlags::new_index()))\n            .expect(\"Failed to create BTree for aggregate state index\")\n            as i64;\n\n        (pager, table_root_page_id, index_root_page_id)\n    }\n\n    /// Read the current state from the BTree (for testing)\n    /// Returns a Delta with all the current aggregate values\n    fn get_current_state_from_btree(\n        agg: &AggregateOperator,\n        pager: &crate::sync::Arc<crate::Pager>,\n        cursors: &mut DbspStateCursors,\n    ) -> Delta {\n        let mut result = Delta::new();\n\n        // Rewind to start of table\n        pager.io.block(|| cursors.table_cursor.rewind()).unwrap();\n\n        loop {\n            // Check if cursor is empty (no more rows)\n            if cursors.table_cursor.is_empty() {\n                break;\n            }\n\n            // Get the record at this position\n            let record = loop {\n                match cursors.table_cursor.record().unwrap() {\n                    IOResult::Done(r) => break r,\n                    IOResult::IO(io) => io.wait(&*pager.io).unwrap(),\n                }\n            }\n            .unwrap()\n            .to_owned();\n\n            let values: Vec<Value> = record.get_values_owned().unwrap();\n\n            // Parse the 5-column structure: operator_id, zset_id, element_id, value, weight\n            if let Some(Value::Numeric(Numeric::Integer(op_id))) = values.first() {\n                // For regular aggregates, use column_index=0 and AGG_TYPE_REGULAR\n                let expected_op_id = generate_storage_id(agg.operator_id, 0, AGG_TYPE_REGULAR);\n\n                // Skip if not our operator\n                if *op_id != expected_op_id {\n                    pager.io.block(|| cursors.table_cursor.next()).unwrap();\n                    continue;\n                }\n\n                // Get the blob data from column 3 (value column)\n                if let Some(Value::Blob(blob)) = values.get(3) {\n                    // Deserialize the state\n                    match AggregateState::from_blob(blob) {\n                        Ok((state, group_key)) => {\n                            // Should not have made it this far.\n                            assert!(state.count != 0);\n                            // Build output row: group_by columns + aggregate values\n                            let mut output_values = group_key.clone();\n                            output_values.extend(state.to_values(&agg.aggregates));\n\n                            let group_key_str = AggregateOperator::group_key_to_string(&group_key);\n                            let rowid = agg.generate_group_rowid(&group_key_str);\n                            let output_row = HashableRow::new(rowid, output_values);\n                            result.changes.push((output_row, 1));\n                        }\n                        Err(e) => {\n                            // Log or handle the deserialization error\n                            // For now, we'll skip this entry\n                            eprintln!(\"Failed to deserialize aggregate state: {e}\");\n                        }\n                    }\n                }\n            }\n\n            pager.io.block(|| cursors.table_cursor.next()).unwrap();\n        }\n\n        result.consolidate();\n        result\n    }\n\n    /// Assert that we're doing incremental work, not full recomputation\n    fn assert_incremental(tracker: &ComputationTracker, expected_ops: usize, data_size: usize) {\n        assert!(\n            tracker.total_computations() <= expected_ops,\n            \"Expected <= {} operations for incremental update, got {}\",\n            expected_ops,\n            tracker.total_computations()\n        );\n        assert!(\n            tracker.total_computations() < data_size,\n            \"Computation count {} suggests full recomputation (data size: {})\",\n            tracker.total_computations(),\n            data_size\n        );\n        assert_eq!(\n            tracker.full_scans, 0,\n            \"Incremental computation should not perform full scans\"\n        );\n    }\n\n    // Aggregate tests\n    #[test]\n    fn test_aggregate_incremental_update_emits_retraction() {\n        // This test verifies that when an aggregate value changes,\n        // the operator emits both a retraction (-1) of the old value\n        // and an insertion (+1) of the new value.\n\n        // Create a persistent pager for the test\n        let (pager, table_root_page_id, index_root_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_root_page_id, 5);\n        // Create index cursor with proper index definition for DBSP state table\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        // Index has 4 columns: operator_id, zset_id, element_id, rowid\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        // Create an aggregate operator for SUM(age) with no GROUP BY\n        let mut agg = AggregateOperator::new(\n            1,                               // operator_id for testing\n            vec![],                          // No GROUP BY\n            vec![AggregateFunction::Sum(2)], // age is at index 2\n            vec![\"id\".to_string(), \"name\".to_string(), \"age\".to_string()],\n        )\n        .unwrap();\n\n        // Initial data: 3 users\n        let mut initial_delta = Delta::new();\n        initial_delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Alice\".to_string().into()),\n                Value::from_i64(25),\n            ],\n        );\n        initial_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Bob\".to_string().into()),\n                Value::from_i64(30),\n            ],\n        );\n        initial_delta.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::Text(\"Charlie\".to_string().into()),\n                Value::from_i64(35),\n            ],\n        );\n\n        // Initialize with initial data\n        pager\n            .io\n            .block(|| agg.commit((&initial_delta).into(), &mut cursors))\n            .unwrap();\n\n        // Verify initial state: SUM(age) = 25 + 30 + 35 = 90\n        let state = get_current_state_from_btree(&agg, &pager, &mut cursors);\n        assert_eq!(state.changes.len(), 1, \"Should have one aggregate row\");\n        let (row, weight) = &state.changes[0];\n        assert_eq!(*weight, 1, \"Aggregate row should have weight 1\");\n        assert_eq!(row.values[0], Value::from_f64(90.0), \"SUM should be 90\");\n\n        // Now add a new user (incremental update)\n        let mut update_delta = Delta::new();\n        update_delta.insert(\n            4,\n            vec![\n                Value::from_i64(4),\n                Value::Text(\"David\".to_string().into()),\n                Value::from_i64(40),\n            ],\n        );\n\n        // Process the incremental update\n        let output_delta = pager\n            .io\n            .block(|| agg.commit((&update_delta).into(), &mut cursors))\n            .unwrap();\n\n        // CRITICAL: The output delta should contain TWO changes:\n        // 1. Retraction of old aggregate value (90) with weight -1\n        // 2. Insertion of new aggregate value (130) with weight +1\n        assert_eq!(\n            output_delta.changes.len(),\n            2,\n            \"Expected 2 changes (retraction + insertion), got {}: {:?}\",\n            output_delta.changes.len(),\n            output_delta.changes\n        );\n\n        // Verify the retraction comes first\n        let (retraction_row, retraction_weight) = &output_delta.changes[0];\n        assert_eq!(\n            *retraction_weight, -1,\n            \"First change should be a retraction\"\n        );\n        assert_eq!(\n            retraction_row.values[0],\n            Value::from_f64(90.0),\n            \"Retracted value should be the old sum (90)\"\n        );\n\n        // Verify the insertion comes second\n        let (insertion_row, insertion_weight) = &output_delta.changes[1];\n        assert_eq!(*insertion_weight, 1, \"Second change should be an insertion\");\n        assert_eq!(\n            insertion_row.values[0],\n            Value::from_f64(130.0),\n            \"Inserted value should be the new sum (130)\"\n        );\n\n        // Both changes should have the same row ID (since it's the same aggregate group)\n        assert_eq!(\n            retraction_row.rowid, insertion_row.rowid,\n            \"Retraction and insertion should have the same row ID\"\n        );\n    }\n\n    #[test]\n    fn test_aggregate_with_group_by_emits_retractions() {\n        // This test verifies that when aggregate values change for grouped data,\n        // the operator emits both retractions and insertions correctly for each group.\n\n        // Create an aggregate operator for SUM(score) GROUP BY team\n        // Create a persistent pager for the test\n        let (pager, table_root_page_id, index_root_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_root_page_id, 5);\n        // Create index cursor with proper index definition for DBSP state table\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        // Index has 4 columns: operator_id, zset_id, element_id, rowid\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        let mut agg = AggregateOperator::new(\n            1,                               // operator_id for testing\n            vec![1],                         // GROUP BY team (index 1)\n            vec![AggregateFunction::Sum(3)], // score is at index 3\n            vec![\n                \"id\".to_string(),\n                \"team\".to_string(),\n                \"player\".to_string(),\n                \"score\".to_string(),\n            ],\n        )\n        .unwrap();\n\n        // Initial data: players on different teams\n        let mut initial_delta = Delta::new();\n        initial_delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"red\".to_string().into()),\n                Value::Text(\"Alice\".to_string().into()),\n                Value::from_i64(10),\n            ],\n        );\n        initial_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"blue\".to_string().into()),\n                Value::Text(\"Bob\".to_string().into()),\n                Value::from_i64(15),\n            ],\n        );\n        initial_delta.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::Text(\"red\".to_string().into()),\n                Value::Text(\"Charlie\".to_string().into()),\n                Value::from_i64(20),\n            ],\n        );\n\n        // Initialize with initial data\n        pager\n            .io\n            .block(|| agg.commit((&initial_delta).into(), &mut cursors))\n            .unwrap();\n\n        // Verify initial state: red team = 30, blue team = 15\n        let state = get_current_state_from_btree(&agg, &pager, &mut cursors);\n        assert_eq!(state.changes.len(), 2, \"Should have two groups\");\n\n        // Find the red and blue team aggregates\n        let mut red_sum = None;\n        let mut blue_sum = None;\n        for (row, weight) in &state.changes {\n            assert_eq!(*weight, 1);\n            if let Value::Text(team) = &row.values[0] {\n                if team.as_str() == \"red\" {\n                    red_sum = Some(&row.values[1]);\n                } else if team.as_str() == \"blue\" {\n                    blue_sum = Some(&row.values[1]);\n                }\n            }\n        }\n        assert_eq!(\n            red_sum,\n            Some(&Value::from_f64(30.0)),\n            \"Red team sum should be 30\"\n        );\n        assert_eq!(\n            blue_sum,\n            Some(&Value::from_f64(15.0)),\n            \"Blue team sum should be 15\"\n        );\n\n        // Now add a new player to the red team (incremental update)\n        let mut update_delta = Delta::new();\n        update_delta.insert(\n            4,\n            vec![\n                Value::from_i64(4),\n                Value::Text(\"red\".to_string().into()),\n                Value::Text(\"David\".to_string().into()),\n                Value::from_i64(25),\n            ],\n        );\n\n        // Process the incremental update\n        let output_delta = pager\n            .io\n            .block(|| agg.commit((&update_delta).into(), &mut cursors))\n            .unwrap();\n\n        // Should have 2 changes: retraction of old red team sum, insertion of new red team sum\n        // Blue team should NOT be affected\n        assert_eq!(\n            output_delta.changes.len(),\n            2,\n            \"Expected 2 changes for red team only, got {}: {:?}\",\n            output_delta.changes.len(),\n            output_delta.changes\n        );\n\n        // Both changes should be for the red team\n        let mut found_retraction = false;\n        let mut found_insertion = false;\n\n        for (row, weight) in &output_delta.changes {\n            if let Value::Text(team) = &row.values[0] {\n                assert_eq!(team.as_str(), \"red\", \"Only red team should have changes\");\n\n                if *weight == -1 {\n                    // Retraction of old value\n                    assert_eq!(\n                        row.values[1],\n                        Value::from_f64(30.0),\n                        \"Should retract old sum of 30\"\n                    );\n                    found_retraction = true;\n                } else if *weight == 1 {\n                    // Insertion of new value\n                    assert_eq!(\n                        row.values[1],\n                        Value::from_f64(55.0),\n                        \"Should insert new sum of 55\"\n                    );\n                    found_insertion = true;\n                }\n            }\n        }\n\n        assert!(found_retraction, \"Should have found retraction\");\n        assert!(found_insertion, \"Should have found insertion\");\n    }\n\n    // Aggregation tests\n    #[test]\n    fn test_count_increments_not_recounts() {\n        let tracker = Arc::new(Mutex::new(ComputationTracker::new()));\n\n        // Create a persistent pager for the test\n        let (pager, table_root_page_id, index_root_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_root_page_id, 5);\n        // Create index cursor with proper index definition for DBSP state table\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        // Index has 4 columns: operator_id, zset_id, element_id, rowid\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        // Create COUNT(*) GROUP BY category\n        let mut agg = AggregateOperator::new(\n            1,       // operator_id for testing\n            vec![1], // category is at index 1\n            vec![AggregateFunction::Count],\n            vec![\n                \"item_id\".to_string(),\n                \"category\".to_string(),\n                \"price\".to_string(),\n            ],\n        )\n        .unwrap();\n        agg.set_tracker(tracker.clone());\n\n        // Initial: 100 items in 10 categories (10 items each)\n        let mut initial = Delta::new();\n        for i in 0..100 {\n            let category = format!(\"cat_{}\", i / 10);\n            initial.insert(\n                i,\n                vec![\n                    Value::from_i64(i),\n                    Value::Text(Text::new(category)),\n                    Value::from_i64(i * 10),\n                ],\n            );\n        }\n        pager\n            .io\n            .block(|| agg.commit((&initial).into(), &mut cursors))\n            .unwrap();\n\n        // Reset tracker for delta processing\n        tracker.lock().aggregation_updates = 0;\n\n        // Add one item to category 'cat_0'\n        let mut delta = Delta::new();\n        delta.insert(\n            100,\n            vec![\n                Value::from_i64(100),\n                Value::Text(Text::new(\"cat_0\")),\n                Value::from_i64(1000),\n            ],\n        );\n\n        pager\n            .io\n            .block(|| agg.commit((&delta).into(), &mut cursors))\n            .unwrap();\n\n        assert_eq!(tracker.lock().aggregation_updates, 1);\n\n        // Check the final state - cat_0 should now have count 11\n        let final_state = get_current_state_from_btree(&agg, &pager, &mut cursors);\n        let cat_0 = final_state\n            .changes\n            .iter()\n            .find(|(row, _)| row.values[0] == Value::Text(Text::new(\"cat_0\")))\n            .unwrap();\n        assert_eq!(cat_0.0.values[1], Value::from_i64(11));\n\n        // Verify incremental behavior - we process the delta twice (eval + commit)\n        let t = tracker.lock();\n        assert_incremental(&t, 2, 101);\n    }\n\n    #[test]\n    fn test_sum_updates_incrementally() {\n        let tracker = Arc::new(Mutex::new(ComputationTracker::new()));\n\n        // Create SUM(amount) GROUP BY product\n        // Create a persistent pager for the test\n        let (pager, table_root_page_id, index_root_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_root_page_id, 5);\n        // Create index cursor with proper index definition for DBSP state table\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        // Index has 4 columns: operator_id, zset_id, element_id, rowid\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        let mut agg = AggregateOperator::new(\n            1,                               // operator_id for testing\n            vec![1],                         // product is at index 1\n            vec![AggregateFunction::Sum(2)], // amount is at index 2\n            vec![\n                \"sale_id\".to_string(),\n                \"product\".to_string(),\n                \"amount\".to_string(),\n            ],\n        )\n        .unwrap();\n        agg.set_tracker(tracker.clone());\n\n        // Initial sales\n        let mut initial = Delta::new();\n        initial.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(Text::new(\"Widget\")),\n                Value::from_i64(100),\n            ],\n        );\n        initial.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(Text::new(\"Gadget\")),\n                Value::from_i64(200),\n            ],\n        );\n        initial.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::Text(Text::new(\"Widget\")),\n                Value::from_i64(150),\n            ],\n        );\n        pager\n            .io\n            .block(|| agg.commit((&initial).into(), &mut cursors))\n            .unwrap();\n\n        // Check initial state: Widget=250, Gadget=200\n        let state = get_current_state_from_btree(&agg, &pager, &mut cursors);\n        let widget_sum = state\n            .changes\n            .iter()\n            .find(|(c, _)| c.values[0] == Value::Text(Text::new(\"Widget\")))\n            .map(|(c, _)| c)\n            .unwrap();\n        assert_eq!(widget_sum.values[1], Value::from_i64(250));\n\n        // Reset tracker\n        tracker.lock().aggregation_updates = 0;\n\n        // Add sale of 50 for Widget\n        let mut delta = Delta::new();\n        delta.insert(\n            4,\n            vec![\n                Value::from_i64(4),\n                Value::Text(Text::new(\"Widget\")),\n                Value::from_i64(50),\n            ],\n        );\n\n        pager\n            .io\n            .block(|| agg.commit((&delta).into(), &mut cursors))\n            .unwrap();\n\n        assert_eq!(tracker.lock().aggregation_updates, 1);\n\n        // Check final state - Widget should now be 300 (250 + 50)\n        let final_state = get_current_state_from_btree(&agg, &pager, &mut cursors);\n        let widget = final_state\n            .changes\n            .iter()\n            .find(|(row, _)| row.values[0] == Value::Text(Text::new(\"Widget\")))\n            .unwrap();\n        assert_eq!(widget.0.values[1], Value::from_i64(300));\n    }\n\n    #[test]\n    fn test_count_and_sum_together() {\n        // Test the example from DBSP_ROADMAP: COUNT(*) and SUM(amount) GROUP BY user_id\n        // Create a persistent pager for the test\n        let (pager, table_root_page_id, index_root_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_root_page_id, 5);\n        // Create index cursor with proper index definition for DBSP state table\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        // Index has 4 columns: operator_id, zset_id, element_id, rowid\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        let mut agg = AggregateOperator::new(\n            1,       // operator_id for testing\n            vec![1], // user_id is at index 1\n            vec![\n                AggregateFunction::Count,\n                AggregateFunction::Sum(2), // amount is at index 2\n            ],\n            vec![\n                \"order_id\".to_string(),\n                \"user_id\".to_string(),\n                \"amount\".to_string(),\n            ],\n        )\n        .unwrap();\n\n        // Initial orders\n        let mut initial = Delta::new();\n        initial.insert(\n            1,\n            vec![Value::from_i64(1), Value::from_i64(1), Value::from_i64(100)],\n        );\n        initial.insert(\n            2,\n            vec![Value::from_i64(2), Value::from_i64(1), Value::from_i64(200)],\n        );\n        initial.insert(\n            3,\n            vec![Value::from_i64(3), Value::from_i64(2), Value::from_i64(150)],\n        );\n        pager\n            .io\n            .block(|| agg.commit((&initial).into(), &mut cursors))\n            .unwrap();\n\n        // Check initial state\n        // User 1: count=2, sum=300\n        // User 2: count=1, sum=150\n        let state = get_current_state_from_btree(&agg, &pager, &mut cursors);\n        assert_eq!(state.changes.len(), 2);\n\n        let user1 = state\n            .changes\n            .iter()\n            .find(|(c, _)| c.values[0] == Value::from_i64(1))\n            .map(|(c, _)| c)\n            .unwrap();\n        assert_eq!(user1.values[1], Value::from_i64(2)); // count\n        assert_eq!(user1.values[2], Value::from_i64(300)); // sum\n\n        let user2 = state\n            .changes\n            .iter()\n            .find(|(c, _)| c.values[0] == Value::from_i64(2))\n            .map(|(c, _)| c)\n            .unwrap();\n        assert_eq!(user2.values[1], Value::from_i64(1)); // count\n        assert_eq!(user2.values[2], Value::from_i64(150)); // sum\n\n        // Add order for user 1\n        let mut delta = Delta::new();\n        delta.insert(\n            4,\n            vec![Value::from_i64(4), Value::from_i64(1), Value::from_i64(50)],\n        );\n        pager\n            .io\n            .block(|| agg.commit((&delta).into(), &mut cursors))\n            .unwrap();\n\n        // Check final state - user 1 should have updated count and sum\n        let final_state = get_current_state_from_btree(&agg, &pager, &mut cursors);\n        let user1 = final_state\n            .changes\n            .iter()\n            .find(|(row, _)| row.values[0] == Value::from_i64(1))\n            .unwrap();\n        assert_eq!(user1.0.values[1], Value::from_i64(3)); // count: 2 + 1\n        assert_eq!(user1.0.values[2], Value::from_i64(350)); // sum: 300 + 50\n    }\n\n    #[test]\n    fn test_avg_maintains_sum_and_count() {\n        // Test AVG aggregation\n        // Create a persistent pager for the test\n        let (pager, table_root_page_id, index_root_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_root_page_id, 5);\n        // Create index cursor with proper index definition for DBSP state table\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        // Index has 4 columns: operator_id, zset_id, element_id, rowid\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        let mut agg = AggregateOperator::new(\n            1,                               // operator_id for testing\n            vec![1],                         // category is at index 1\n            vec![AggregateFunction::Avg(2)], // value is at index 2\n            vec![\n                \"id\".to_string(),\n                \"category\".to_string(),\n                \"value\".to_string(),\n            ],\n        )\n        .unwrap();\n\n        // Initial data\n        let mut initial = Delta::new();\n        initial.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(Text::new(\"A\")),\n                Value::from_i64(10),\n            ],\n        );\n        initial.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(Text::new(\"A\")),\n                Value::from_i64(20),\n            ],\n        );\n        initial.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::Text(Text::new(\"B\")),\n                Value::from_i64(30),\n            ],\n        );\n        pager\n            .io\n            .block(|| agg.commit((&initial).into(), &mut cursors))\n            .unwrap();\n\n        // Check initial averages\n        // Category A: avg = (10 + 20) / 2 = 15\n        // Category B: avg = 30 / 1 = 30\n        let state = get_current_state_from_btree(&agg, &pager, &mut cursors);\n        let cat_a = state\n            .changes\n            .iter()\n            .find(|(c, _)| c.values[0] == Value::Text(Text::new(\"A\")))\n            .map(|(c, _)| c)\n            .unwrap();\n        assert_eq!(cat_a.values[1], Value::from_f64(15.0));\n\n        let cat_b = state\n            .changes\n            .iter()\n            .find(|(c, _)| c.values[0] == Value::Text(Text::new(\"B\")))\n            .map(|(c, _)| c)\n            .unwrap();\n        assert_eq!(cat_b.values[1], Value::from_f64(30.0));\n\n        // Add value to category A\n        let mut delta = Delta::new();\n        delta.insert(\n            4,\n            vec![\n                Value::from_i64(4),\n                Value::Text(Text::new(\"A\")),\n                Value::from_i64(30),\n            ],\n        );\n        pager\n            .io\n            .block(|| agg.commit((&delta).into(), &mut cursors))\n            .unwrap();\n\n        // Check final state - Category A avg should now be (10 + 20 + 30) / 3 = 20\n        let final_state = get_current_state_from_btree(&agg, &pager, &mut cursors);\n        let cat_a = final_state\n            .changes\n            .iter()\n            .find(|(row, _)| row.values[0] == Value::Text(Text::new(\"A\")))\n            .unwrap();\n        assert_eq!(cat_a.0.values[1], Value::from_f64(20.0));\n    }\n\n    #[test]\n    fn test_delete_updates_aggregates() {\n        // Test that deletes (negative weights) properly update aggregates\n        // Create a persistent pager for the test\n        let (pager, table_root_page_id, index_root_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_root_page_id, 5);\n        // Create index cursor with proper index definition for DBSP state table\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        // Index has 4 columns: operator_id, zset_id, element_id, rowid\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        let mut agg = AggregateOperator::new(\n            1,       // operator_id for testing\n            vec![1], // category is at index 1\n            vec![\n                AggregateFunction::Count,\n                AggregateFunction::Sum(2), // value is at index 2\n            ],\n            vec![\n                \"id\".to_string(),\n                \"category\".to_string(),\n                \"value\".to_string(),\n            ],\n        )\n        .unwrap();\n\n        // Initial data\n        let mut initial = Delta::new();\n        initial.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(Text::new(\"A\")),\n                Value::from_i64(100),\n            ],\n        );\n        initial.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(Text::new(\"A\")),\n                Value::from_i64(200),\n            ],\n        );\n        pager\n            .io\n            .block(|| agg.commit((&initial).into(), &mut cursors))\n            .unwrap();\n\n        // Check initial state: count=2, sum=300\n        let state = get_current_state_from_btree(&agg, &pager, &mut cursors);\n        assert!(!state.changes.is_empty());\n        let (row, _weight) = &state.changes[0];\n        assert_eq!(row.values[1], Value::from_i64(2)); // count\n        assert_eq!(row.values[2], Value::from_i64(300)); // sum\n\n        // Delete one row\n        let mut delta = Delta::new();\n        delta.delete(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(Text::new(\"A\")),\n                Value::from_i64(100),\n            ],\n        );\n\n        pager\n            .io\n            .block(|| agg.commit((&delta).into(), &mut cursors))\n            .unwrap();\n\n        // Check final state - should update to count=1, sum=200\n        let final_state = get_current_state_from_btree(&agg, &pager, &mut cursors);\n        let cat_a = final_state\n            .changes\n            .iter()\n            .find(|(row, _)| row.values[0] == Value::Text(Text::new(\"A\")))\n            .unwrap();\n        assert_eq!(cat_a.0.values[1], Value::from_i64(1)); // count: 2 - 1\n        assert_eq!(cat_a.0.values[2], Value::from_i64(200)); // sum: 300 - 100\n    }\n\n    #[test]\n    fn test_count_aggregation_with_deletions() {\n        let aggregates = vec![AggregateFunction::Count];\n        let group_by = vec![0]; // category is at index 0\n        let input_columns = vec![\"category\".to_string(), \"value\".to_string()];\n\n        // Create a persistent pager for the test\n        let (pager, table_root_page_id, index_root_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_root_page_id, 5);\n        // Create index cursor with proper index definition for DBSP state table\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        // Index has 4 columns: operator_id, zset_id, element_id, rowid\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        let mut agg = AggregateOperator::new(\n            1, // operator_id for testing\n            group_by,\n            aggregates,\n            input_columns,\n        )\n        .unwrap();\n\n        // Initialize with data\n        let mut init_data = Delta::new();\n        init_data.insert(1, vec![Value::Text(\"A\".into()), Value::from_i64(10)]);\n        init_data.insert(2, vec![Value::Text(\"A\".into()), Value::from_i64(20)]);\n        init_data.insert(3, vec![Value::Text(\"B\".into()), Value::from_i64(30)]);\n        pager\n            .io\n            .block(|| agg.commit((&init_data).into(), &mut cursors))\n            .unwrap();\n\n        // Check initial counts\n        let state = get_current_state_from_btree(&agg, &pager, &mut cursors);\n        assert_eq!(state.changes.len(), 2);\n\n        // Find group A and B\n        let group_a = state\n            .changes\n            .iter()\n            .find(|(row, _)| row.values[0] == Value::Text(\"A\".into()))\n            .unwrap();\n        let group_b = state\n            .changes\n            .iter()\n            .find(|(row, _)| row.values[0] == Value::Text(\"B\".into()))\n            .unwrap();\n\n        assert_eq!(group_a.0.values[1], Value::from_i64(2)); // COUNT = 2 for A\n        assert_eq!(group_b.0.values[1], Value::from_i64(1)); // COUNT = 1 for B\n\n        // Delete one row from group A\n        let mut delete_delta = Delta::new();\n        delete_delta.delete(1, vec![Value::Text(\"A\".into()), Value::from_i64(10)]);\n\n        let output = pager\n            .io\n            .block(|| agg.commit((&delete_delta).into(), &mut cursors))\n            .unwrap();\n\n        // Should emit retraction for old count and insertion for new count\n        assert_eq!(output.changes.len(), 2);\n\n        // Check final state\n        let final_state = get_current_state_from_btree(&agg, &pager, &mut cursors);\n        let group_a_final = final_state\n            .changes\n            .iter()\n            .find(|(row, _)| row.values[0] == Value::Text(\"A\".into()))\n            .unwrap();\n        assert_eq!(group_a_final.0.values[1], Value::from_i64(1)); // COUNT = 1 for A after deletion\n\n        // Delete all rows from group B\n        let mut delete_all_b = Delta::new();\n        delete_all_b.delete(3, vec![Value::Text(\"B\".into()), Value::from_i64(30)]);\n\n        let output_b = pager\n            .io\n            .block(|| agg.commit((&delete_all_b).into(), &mut cursors))\n            .unwrap();\n        assert_eq!(output_b.changes.len(), 1); // Only retraction, no new row\n        assert_eq!(output_b.changes[0].1, -1); // Retraction\n\n        // Final state should not have group B\n        let final_state2 = get_current_state_from_btree(&agg, &pager, &mut cursors);\n        assert_eq!(final_state2.changes.len(), 1); // Only group A remains\n        assert_eq!(final_state2.changes[0].0.values[0], Value::Text(\"A\".into()));\n    }\n\n    #[test]\n    fn test_sum_aggregation_with_deletions() {\n        let aggregates = vec![AggregateFunction::Sum(1)]; // value is at index 1\n        let group_by = vec![0]; // category is at index 0\n        let input_columns = vec![\"category\".to_string(), \"value\".to_string()];\n\n        // Create a persistent pager for the test\n        let (pager, table_root_page_id, index_root_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_root_page_id, 5);\n        // Create index cursor with proper index definition for DBSP state table\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        // Index has 4 columns: operator_id, zset_id, element_id, rowid\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        let mut agg = AggregateOperator::new(\n            1, // operator_id for testing\n            group_by,\n            aggregates,\n            input_columns,\n        )\n        .unwrap();\n\n        // Initialize with data\n        let mut init_data = Delta::new();\n        init_data.insert(1, vec![Value::Text(\"A\".into()), Value::from_i64(10)]);\n        init_data.insert(2, vec![Value::Text(\"A\".into()), Value::from_i64(20)]);\n        init_data.insert(3, vec![Value::Text(\"B\".into()), Value::from_i64(30)]);\n        init_data.insert(4, vec![Value::Text(\"B\".into()), Value::from_i64(15)]);\n        pager\n            .io\n            .block(|| agg.commit((&init_data).into(), &mut cursors))\n            .unwrap();\n\n        // Check initial sums\n        let state = get_current_state_from_btree(&agg, &pager, &mut cursors);\n        let group_a = state\n            .changes\n            .iter()\n            .find(|(row, _)| row.values[0] == Value::Text(\"A\".into()))\n            .unwrap();\n        let group_b = state\n            .changes\n            .iter()\n            .find(|(row, _)| row.values[0] == Value::Text(\"B\".into()))\n            .unwrap();\n\n        assert_eq!(group_a.0.values[1], Value::from_i64(30)); // SUM = 30 for A (10+20)\n        assert_eq!(group_b.0.values[1], Value::from_i64(45)); // SUM = 45 for B (30+15)\n\n        // Delete one row from group A\n        let mut delete_delta = Delta::new();\n        delete_delta.delete(2, vec![Value::Text(\"A\".into()), Value::from_i64(20)]);\n\n        pager\n            .io\n            .block(|| agg.commit((&delete_delta).into(), &mut cursors))\n            .unwrap();\n\n        // Check updated sum\n        let state = get_current_state_from_btree(&agg, &pager, &mut cursors);\n        let group_a = state\n            .changes\n            .iter()\n            .find(|(row, _)| row.values[0] == Value::Text(\"A\".into()))\n            .unwrap();\n        assert_eq!(group_a.0.values[1], Value::from_i64(10)); // SUM = 10 for A after deletion\n\n        // Delete all from group B\n        let mut delete_all_b = Delta::new();\n        delete_all_b.delete(3, vec![Value::Text(\"B\".into()), Value::from_i64(30)]);\n        delete_all_b.delete(4, vec![Value::Text(\"B\".into()), Value::from_i64(15)]);\n\n        pager\n            .io\n            .block(|| agg.commit((&delete_all_b).into(), &mut cursors))\n            .unwrap();\n\n        // Group B should be gone\n        let final_state = get_current_state_from_btree(&agg, &pager, &mut cursors);\n        assert_eq!(final_state.changes.len(), 1); // Only group A remains\n        assert_eq!(final_state.changes[0].0.values[0], Value::Text(\"A\".into()));\n    }\n\n    #[test]\n    fn test_avg_aggregation_with_deletions() {\n        let aggregates = vec![AggregateFunction::Avg(1)]; // value is at index 1\n        let group_by = vec![0]; // category is at index 0\n        let input_columns = vec![\"category\".to_string(), \"value\".to_string()];\n\n        // Create a persistent pager for the test\n        let (pager, table_root_page_id, index_root_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_root_page_id, 5);\n        // Create index cursor with proper index definition for DBSP state table\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        // Index has 4 columns: operator_id, zset_id, element_id, rowid\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        let mut agg = AggregateOperator::new(\n            1, // operator_id for testing\n            group_by,\n            aggregates,\n            input_columns,\n        )\n        .unwrap();\n\n        // Initialize with data\n        let mut init_data = Delta::new();\n        init_data.insert(1, vec![Value::Text(\"A\".into()), Value::from_i64(10)]);\n        init_data.insert(2, vec![Value::Text(\"A\".into()), Value::from_i64(20)]);\n        init_data.insert(3, vec![Value::Text(\"A\".into()), Value::from_i64(30)]);\n        pager\n            .io\n            .block(|| agg.commit((&init_data).into(), &mut cursors))\n            .unwrap();\n\n        // Check initial average\n        let state = get_current_state_from_btree(&agg, &pager, &mut cursors);\n        assert_eq!(state.changes.len(), 1);\n        assert_eq!(state.changes[0].0.values[1], Value::from_f64(20.0)); // AVG = (10+20+30)/3 = 20\n\n        // Delete the middle value\n        let mut delete_delta = Delta::new();\n        delete_delta.delete(2, vec![Value::Text(\"A\".into()), Value::from_i64(20)]);\n\n        pager\n            .io\n            .block(|| agg.commit((&delete_delta).into(), &mut cursors))\n            .unwrap();\n\n        // Check updated average\n        let state = get_current_state_from_btree(&agg, &pager, &mut cursors);\n        assert_eq!(state.changes[0].0.values[1], Value::from_f64(20.0)); // AVG = (10+30)/2 = 20 (same!)\n\n        // Delete another to change the average\n        let mut delete_another = Delta::new();\n        delete_another.delete(3, vec![Value::Text(\"A\".into()), Value::from_i64(30)]);\n\n        pager\n            .io\n            .block(|| agg.commit((&delete_another).into(), &mut cursors))\n            .unwrap();\n\n        let state = get_current_state_from_btree(&agg, &pager, &mut cursors);\n        assert_eq!(state.changes[0].0.values[1], Value::from_f64(10.0)); // AVG = 10/1 = 10\n    }\n\n    #[test]\n    fn test_multiple_aggregations_with_deletions() {\n        // Test COUNT, SUM, and AVG together\n        let aggregates = vec![\n            AggregateFunction::Count,\n            AggregateFunction::Sum(1), // value is at index 1\n            AggregateFunction::Avg(1), // value is at index 1\n        ];\n        let group_by = vec![0]; // category is at index 0\n        let input_columns = vec![\"category\".to_string(), \"value\".to_string()];\n\n        // Create a persistent pager for the test\n        let (pager, table_root_page_id, index_root_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_root_page_id, 5);\n        // Create index cursor with proper index definition for DBSP state table\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        // Index has 4 columns: operator_id, zset_id, element_id, rowid\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        let mut agg = AggregateOperator::new(\n            1, // operator_id for testing\n            group_by,\n            aggregates,\n            input_columns,\n        )\n        .unwrap();\n\n        // Initialize with data\n        let mut init_data = Delta::new();\n        init_data.insert(1, vec![Value::Text(\"A\".into()), Value::from_i64(100)]);\n        init_data.insert(2, vec![Value::Text(\"A\".into()), Value::from_i64(200)]);\n        init_data.insert(3, vec![Value::Text(\"B\".into()), Value::from_i64(50)]);\n        pager\n            .io\n            .block(|| agg.commit((&init_data).into(), &mut cursors))\n            .unwrap();\n\n        // Check initial state\n        let state = get_current_state_from_btree(&agg, &pager, &mut cursors);\n        let group_a = state\n            .changes\n            .iter()\n            .find(|(row, _)| row.values[0] == Value::Text(\"A\".into()))\n            .unwrap();\n\n        assert_eq!(group_a.0.values[1], Value::from_i64(2)); // COUNT = 2\n        assert_eq!(group_a.0.values[2], Value::from_i64(300)); // SUM = 300\n        assert_eq!(group_a.0.values[3], Value::from_f64(150.0)); // AVG = 150\n\n        // Delete one row from group A\n        let mut delete_delta = Delta::new();\n        delete_delta.delete(1, vec![Value::Text(\"A\".into()), Value::from_i64(100)]);\n\n        pager\n            .io\n            .block(|| agg.commit((&delete_delta).into(), &mut cursors))\n            .unwrap();\n\n        // Check all aggregates updated correctly\n        let state = get_current_state_from_btree(&agg, &pager, &mut cursors);\n        let group_a = state\n            .changes\n            .iter()\n            .find(|(row, _)| row.values[0] == Value::Text(\"A\".into()))\n            .unwrap();\n\n        assert_eq!(group_a.0.values[1], Value::from_i64(1)); // COUNT = 1\n        assert_eq!(group_a.0.values[2], Value::from_i64(200)); // SUM = 200\n        assert_eq!(group_a.0.values[3], Value::from_f64(200.0)); // AVG = 200\n\n        // Insert a new row with floating point value\n        let mut insert_delta = Delta::new();\n        insert_delta.insert(4, vec![Value::Text(\"A\".into()), Value::from_f64(50.5)]);\n\n        pager\n            .io\n            .block(|| agg.commit((&insert_delta).into(), &mut cursors))\n            .unwrap();\n\n        let state = get_current_state_from_btree(&agg, &pager, &mut cursors);\n        let group_a = state\n            .changes\n            .iter()\n            .find(|(row, _)| row.values[0] == Value::Text(\"A\".into()))\n            .unwrap();\n\n        assert_eq!(group_a.0.values[1], Value::from_i64(2)); // COUNT = 2\n        assert_eq!(group_a.0.values[2], Value::from_f64(250.5)); // SUM = 250.5\n        assert_eq!(group_a.0.values[3], Value::from_f64(125.25)); // AVG = 125.25\n    }\n\n    #[test]\n    fn test_filter_operator_rowid_update() {\n        // When a row's rowid changes (e.g., UPDATE t SET a=1 WHERE a=3 on INTEGER PRIMARY KEY),\n        // the operator should properly consolidate the state\n\n        // Create a persistent pager for the test\n        let (pager, table_root_page_id, index_root_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_root_page_id, 5);\n        // Create index cursor with proper index definition for DBSP state table\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        // Index has 4 columns: operator_id, zset_id, element_id, rowid\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        let mut filter = FilterOperator::new(FilterPredicate::GreaterThan {\n            column_idx: 1, // \"b\" is at index 1\n            value: Value::from_i64(2),\n        });\n\n        // Initialize with a row (rowid=3, values=[3, 3])\n        let mut init_data = Delta::new();\n        init_data.insert(3, vec![Value::from_i64(3), Value::from_i64(3)]);\n        let state = pager\n            .io\n            .block(|| filter.commit((&init_data).into(), &mut cursors))\n            .unwrap();\n\n        // Check initial state\n        assert_eq!(state.changes.len(), 1);\n        assert_eq!(state.changes[0].0.rowid, 3);\n        assert_eq!(\n            state.changes[0].0.values,\n            vec![Value::from_i64(3), Value::from_i64(3)]\n        );\n\n        // Simulate an UPDATE that changes rowid from 3 to 1\n        // This is sent as: delete(3) + insert(1)\n        let mut update_delta = Delta::new();\n        update_delta.delete(3, vec![Value::from_i64(3), Value::from_i64(3)]);\n        update_delta.insert(1, vec![Value::from_i64(1), Value::from_i64(3)]);\n\n        let output = pager\n            .io\n            .block(|| filter.commit((&update_delta).into(), &mut cursors))\n            .unwrap();\n\n        // The output delta should have both changes (both pass the filter b > 2)\n        assert_eq!(output.changes.len(), 2);\n        assert_eq!(output.changes[0].1, -1); // delete weight\n        assert_eq!(output.changes[1].1, 1); // insert weight\n    }\n\n    // ============================================================================\n    // EVAL/COMMIT PATTERN TESTS\n    // These tests verify that the eval/commit pattern works correctly:\n    // - eval() computes results without modifying state\n    // - eval() with uncommitted data returns correct results\n    // - commit() updates internal state\n    // - State remains unchanged when eval() is called with uncommitted data\n    // ============================================================================\n\n    #[test]\n    fn test_filter_eval_with_uncommitted() {\n        // Create a persistent pager for the test\n        let (pager, table_root_page_id, index_root_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_root_page_id, 5);\n        // Create index cursor with proper index definition for DBSP state table\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        // Index has 4 columns: operator_id, zset_id, element_id, rowid\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        let mut filter = FilterOperator::new(FilterPredicate::GreaterThan {\n            column_idx: 2, // \"age\" is at index 2\n            value: Value::from_i64(25),\n        });\n\n        // Initialize with some data\n        let mut init_data = Delta::new();\n        init_data.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Alice\".into()),\n                Value::from_i64(30),\n            ],\n        );\n        init_data.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Bob\".into()),\n                Value::from_i64(20),\n            ],\n        );\n        let state = pager\n            .io\n            .block(|| filter.commit((&init_data).into(), &mut cursors))\n            .unwrap();\n\n        // Verify initial state (only Alice passes filter)\n        assert_eq!(state.changes.len(), 1);\n        assert_eq!(state.changes[0].0.rowid, 1);\n\n        // Create uncommitted changes\n        let mut uncommitted = Delta::new();\n        uncommitted.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::Text(\"Charlie\".into()),\n                Value::from_i64(35),\n            ],\n        );\n        uncommitted.insert(\n            4,\n            vec![\n                Value::from_i64(4),\n                Value::Text(\"David\".into()),\n                Value::from_i64(15),\n            ],\n        );\n\n        // Eval with uncommitted - should return filtered uncommitted rows\n        let mut eval_state = uncommitted.clone().into();\n        let result = pager\n            .io\n            .block(|| filter.eval(&mut eval_state, &mut cursors))\n            .unwrap();\n        assert_eq!(\n            result.changes.len(),\n            1,\n            \"Only Charlie (35) should pass filter\"\n        );\n        assert_eq!(result.changes[0].0.rowid, 3);\n\n        // Now commit the changes\n        let state = pager\n            .io\n            .block(|| filter.commit((&uncommitted).into(), &mut cursors))\n            .unwrap();\n\n        // State should now include Charlie (who passes filter)\n        assert_eq!(\n            state.changes.len(),\n            1,\n            \"State should now have Alice and Charlie\"\n        );\n    }\n\n    #[test]\n    fn test_aggregate_eval_with_uncommitted_preserves_state() {\n        // This is the critical test - aggregations must not modify internal state during eval\n        // Create a persistent pager for the test\n        let (pager, table_root_page_id, index_root_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_root_page_id, 5);\n        // Create index cursor with proper index definition for DBSP state table\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        // Index has 4 columns: operator_id, zset_id, element_id, rowid\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        let mut agg = AggregateOperator::new(\n            1,       // operator_id for testing\n            vec![1], // category is at index 1\n            vec![\n                AggregateFunction::Count,\n                AggregateFunction::Sum(2), // amount is at index 2\n            ],\n            vec![\n                \"id\".to_string(),\n                \"category\".to_string(),\n                \"amount\".to_string(),\n            ],\n        )\n        .unwrap();\n\n        // Initialize with data\n        let mut init_data = Delta::new();\n        init_data.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"A\".into()),\n                Value::from_i64(100),\n            ],\n        );\n        init_data.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"A\".into()),\n                Value::from_i64(200),\n            ],\n        );\n        init_data.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::Text(\"B\".into()),\n                Value::from_i64(150),\n            ],\n        );\n        pager\n            .io\n            .block(|| agg.commit((&init_data).into(), &mut cursors))\n            .unwrap();\n\n        // Check initial state: A -> (count=2, sum=300), B -> (count=1, sum=150)\n        let initial_state = get_current_state_from_btree(&agg, &pager, &mut cursors);\n        assert_eq!(initial_state.changes.len(), 2);\n\n        // Store initial state for comparison\n        let initial_a = initial_state\n            .changes\n            .iter()\n            .find(|(row, _)| row.values[0] == Value::Text(\"A\".into()))\n            .unwrap();\n        assert_eq!(initial_a.0.values[1], Value::from_i64(2)); // count\n        assert_eq!(initial_a.0.values[2], Value::from_f64(300.0)); // sum\n\n        // Create uncommitted changes\n        let mut uncommitted = Delta::new();\n        uncommitted.insert(\n            4,\n            vec![\n                Value::from_i64(4),\n                Value::Text(\"A\".into()),\n                Value::from_i64(50),\n            ],\n        );\n        uncommitted.insert(\n            5,\n            vec![\n                Value::from_i64(5),\n                Value::Text(\"C\".into()),\n                Value::from_i64(75),\n            ],\n        );\n\n        // Eval with uncommitted should return the delta (changes to aggregates)\n        let mut eval_state = uncommitted.clone().into();\n        let result = pager\n            .io\n            .block(|| agg.eval(&mut eval_state, &mut cursors))\n            .unwrap();\n\n        // Result should contain updates for A and new group C\n        // For A: retraction of old (2, 300) and insertion of new (3, 350)\n        // For C: insertion of (1, 75)\n        assert!(!result.changes.is_empty(), \"Should have aggregate changes\");\n\n        // CRITICAL: Verify internal state hasn't changed\n        let state_after_eval = get_current_state_from_btree(&agg, &pager, &mut cursors);\n        assert_eq!(\n            state_after_eval.changes.len(),\n            2,\n            \"State should still have only A and B\"\n        );\n\n        let a_after_eval = state_after_eval\n            .changes\n            .iter()\n            .find(|(row, _)| row.values[0] == Value::Text(\"A\".into()))\n            .unwrap();\n        assert_eq!(\n            a_after_eval.0.values[1],\n            Value::from_i64(2),\n            \"A count should still be 2\"\n        );\n        assert_eq!(\n            a_after_eval.0.values[2],\n            Value::from_f64(300.0),\n            \"A sum should still be 300\"\n        );\n\n        // Now commit the changes\n        pager\n            .io\n            .block(|| agg.commit((&uncommitted).into(), &mut cursors))\n            .unwrap();\n\n        // State should now be updated\n        let final_state = get_current_state_from_btree(&agg, &pager, &mut cursors);\n        assert_eq!(final_state.changes.len(), 3, \"Should now have A, B, and C\");\n\n        let a_final = final_state\n            .changes\n            .iter()\n            .find(|(row, _)| row.values[0] == Value::Text(\"A\".into()))\n            .unwrap();\n        assert_eq!(\n            a_final.0.values[1],\n            Value::from_i64(3),\n            \"A count should now be 3\"\n        );\n        assert_eq!(\n            a_final.0.values[2],\n            Value::from_f64(350.0),\n            \"A sum should now be 350\"\n        );\n\n        let c_final = final_state\n            .changes\n            .iter()\n            .find(|(row, _)| row.values[0] == Value::Text(\"C\".into()))\n            .unwrap();\n        assert_eq!(\n            c_final.0.values[1],\n            Value::from_i64(1),\n            \"C count should be 1\"\n        );\n        assert_eq!(\n            c_final.0.values[2],\n            Value::from_f64(75.0),\n            \"C sum should be 75\"\n        );\n    }\n\n    #[test]\n    fn test_aggregate_eval_multiple_times_without_commit() {\n        // Test that calling eval multiple times with different uncommitted data\n        // doesn't pollute the internal state\n        // Create a persistent pager for the test\n        let (pager, table_root_page_id, index_root_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_root_page_id, 5);\n        // Create index cursor with proper index definition for DBSP state table\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        // Index has 4 columns: operator_id, zset_id, element_id, rowid\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        let mut agg = AggregateOperator::new(\n            1,      // operator_id for testing\n            vec![], // No GROUP BY\n            vec![\n                AggregateFunction::Count,\n                AggregateFunction::Sum(1), // value is at index 1\n            ],\n            vec![\"id\".to_string(), \"value\".to_string()],\n        )\n        .unwrap();\n\n        // Initialize\n        let mut init_data = Delta::new();\n        init_data.insert(1, vec![Value::from_i64(1), Value::from_i64(100)]);\n        init_data.insert(2, vec![Value::from_i64(2), Value::from_i64(200)]);\n        pager\n            .io\n            .block(|| agg.commit((&init_data).into(), &mut cursors))\n            .unwrap();\n\n        // Initial state: count=2, sum=300\n        let initial_state = get_current_state_from_btree(&agg, &pager, &mut cursors);\n        assert_eq!(initial_state.changes.len(), 1);\n        assert_eq!(initial_state.changes[0].0.values[0], Value::from_i64(2));\n        assert_eq!(initial_state.changes[0].0.values[1], Value::from_f64(300.0));\n\n        // First eval with uncommitted\n        let mut uncommitted1 = Delta::new();\n        uncommitted1.insert(3, vec![Value::from_i64(3), Value::from_i64(50)]);\n        let mut eval_state1 = uncommitted1.clone().into();\n        let _ = pager\n            .io\n            .block(|| agg.eval(&mut eval_state1, &mut cursors))\n            .unwrap();\n\n        // State should be unchanged\n        let state1 = get_current_state_from_btree(&agg, &pager, &mut cursors);\n        assert_eq!(state1.changes[0].0.values[0], Value::from_i64(2));\n        assert_eq!(state1.changes[0].0.values[1], Value::from_f64(300.0));\n\n        // Second eval with different uncommitted\n        let mut uncommitted2 = Delta::new();\n        uncommitted2.insert(4, vec![Value::from_i64(4), Value::from_i64(75)]);\n        uncommitted2.insert(5, vec![Value::from_i64(5), Value::from_i64(25)]);\n        let mut eval_state2 = uncommitted2.clone().into();\n        let _ = pager\n            .io\n            .block(|| agg.eval(&mut eval_state2, &mut cursors))\n            .unwrap();\n\n        // State should STILL be unchanged\n        let state2 = get_current_state_from_btree(&agg, &pager, &mut cursors);\n        assert_eq!(state2.changes[0].0.values[0], Value::from_i64(2));\n        assert_eq!(state2.changes[0].0.values[1], Value::from_f64(300.0));\n\n        // Third eval with deletion as uncommitted\n        let mut uncommitted3 = Delta::new();\n        uncommitted3.delete(1, vec![Value::from_i64(1), Value::from_i64(100)]);\n        let mut eval_state3 = uncommitted3.clone().into();\n        let _ = pager\n            .io\n            .block(|| agg.eval(&mut eval_state3, &mut cursors))\n            .unwrap();\n\n        // State should STILL be unchanged\n        let state3 = get_current_state_from_btree(&agg, &pager, &mut cursors);\n        assert_eq!(state3.changes[0].0.values[0], Value::from_i64(2));\n        assert_eq!(state3.changes[0].0.values[1], Value::from_f64(300.0));\n    }\n\n    #[test]\n    fn test_aggregate_eval_with_mixed_committed_and_uncommitted() {\n        // Test eval with both committed delta and uncommitted changes\n        // Create a persistent pager for the test\n        let (pager, table_root_page_id, index_root_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_root_page_id, 5);\n        // Create index cursor with proper index definition for DBSP state table\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        // Index has 4 columns: operator_id, zset_id, element_id, rowid\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        let mut agg = AggregateOperator::new(\n            1,       // operator_id for testing\n            vec![1], // type is at index 1\n            vec![AggregateFunction::Count],\n            vec![\"id\".to_string(), \"type\".to_string()],\n        )\n        .unwrap();\n\n        // Initialize\n        let mut init_data = Delta::new();\n        init_data.insert(1, vec![Value::from_i64(1), Value::Text(\"X\".into())]);\n        init_data.insert(2, vec![Value::from_i64(2), Value::Text(\"Y\".into())]);\n        pager\n            .io\n            .block(|| agg.commit((&init_data).into(), &mut cursors))\n            .unwrap();\n\n        // Create a committed delta (to be processed)\n        let mut committed_delta = Delta::new();\n        committed_delta.insert(3, vec![Value::from_i64(3), Value::Text(\"X\".into())]);\n\n        // Create uncommitted changes\n        let mut uncommitted = Delta::new();\n        uncommitted.insert(4, vec![Value::from_i64(4), Value::Text(\"Y\".into())]);\n        uncommitted.insert(5, vec![Value::from_i64(5), Value::Text(\"Z\".into())]);\n\n        // Eval with both - should process both but not commit\n        let mut combined = committed_delta.clone();\n        combined.merge(&uncommitted);\n        let mut eval_state = combined.clone().into();\n        let result = pager\n            .io\n            .block(|| agg.eval(&mut eval_state, &mut cursors))\n            .unwrap();\n\n        // Result should reflect changes from both\n        assert!(!result.changes.is_empty(), \"Result should not be empty\");\n\n        // Verify the DBSP pattern: retraction (-1) followed by insertion (1) for updates,\n        // and just insertion (1) for new groups\n\n        // We expect exactly 5 changes:\n        // - X: retraction + insertion (was 1, now 2)\n        // - Y: retraction + insertion (was 1, now 2)\n        // - Z: insertion only (new group with count 1)\n        assert_eq!(\n            result.changes.len(),\n            5,\n            \"Should have 5 changes (2 retractions + 3 insertions)\"\n        );\n\n        // Sort by group name then by weight to get predictable order\n        let mut sorted_changes: Vec<_> = result.changes.iter().collect();\n        sorted_changes.sort_by(|a, b| {\n            let a_group = &a.0.values[0];\n            let b_group = &b.0.values[0];\n            match a_group.partial_cmp(b_group).unwrap() {\n                std::cmp::Ordering::Equal => a.1.cmp(&b.1), // Sort by weight if same group\n                other => other,\n            }\n        });\n\n        // Check X group: should have retraction (-1) for count=1, then insertion (1) for count=2\n        assert_eq!(sorted_changes[0].0.values[0], Value::Text(\"X\".into()));\n        assert_eq!(sorted_changes[0].0.values[1], Value::from_i64(1)); // old count\n        assert_eq!(sorted_changes[0].1, -1); // retraction\n\n        assert_eq!(sorted_changes[1].0.values[0], Value::Text(\"X\".into()));\n        assert_eq!(sorted_changes[1].0.values[1], Value::from_i64(2)); // new count\n        assert_eq!(sorted_changes[1].1, 1); // insertion\n\n        // Check Y group: should have retraction (-1) for count=1, then insertion (1) for count=2\n        assert_eq!(sorted_changes[2].0.values[0], Value::Text(\"Y\".into()));\n        assert_eq!(sorted_changes[2].0.values[1], Value::from_i64(1)); // old count\n        assert_eq!(sorted_changes[2].1, -1); // retraction\n\n        assert_eq!(sorted_changes[3].0.values[0], Value::Text(\"Y\".into()));\n        assert_eq!(sorted_changes[3].0.values[1], Value::from_i64(2)); // new count\n        assert_eq!(sorted_changes[3].1, 1); // insertion\n\n        // Check Z group: should only have insertion (1) for count=1 (new group)\n        assert_eq!(sorted_changes[4].0.values[0], Value::Text(\"Z\".into()));\n        assert_eq!(sorted_changes[4].0.values[1], Value::from_i64(1)); // new count\n        assert_eq!(sorted_changes[4].1, 1); // insertion only (no retraction as it's new);\n\n        // But internal state should be unchanged\n        let state = get_current_state_from_btree(&agg, &pager, &mut cursors);\n        assert_eq!(state.changes.len(), 2, \"Should still have only X and Y\");\n\n        // Now commit only the committed_delta\n        pager\n            .io\n            .block(|| agg.commit((&committed_delta).into(), &mut cursors))\n            .unwrap();\n\n        // State should now have X count=2, Y count=1\n        let final_state = get_current_state_from_btree(&agg, &pager, &mut cursors);\n        let x = final_state\n            .changes\n            .iter()\n            .find(|(row, _)| row.values[0] == Value::Text(\"X\".into()))\n            .unwrap();\n        assert_eq!(x.0.values[1], Value::from_i64(2));\n    }\n\n    #[test]\n    fn test_min_max_basic() {\n        // Test basic MIN/MAX functionality\n        let (pager, table_root_page_id, index_root_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_root_page_id, 5);\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        let mut agg = AggregateOperator::new(\n            1,      // operator_id\n            vec![], // No GROUP BY\n            vec![\n                AggregateFunction::Min(2), // price is at index 2\n                AggregateFunction::Max(2), // price is at index 2\n            ],\n            vec![\"id\".to_string(), \"name\".to_string(), \"price\".to_string()],\n        )\n        .unwrap();\n\n        // Initial data\n        let mut initial_delta = Delta::new();\n        initial_delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Apple\".into()),\n                Value::from_f64(1.50),\n            ],\n        );\n        initial_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Banana\".into()),\n                Value::from_f64(0.75),\n            ],\n        );\n        initial_delta.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::Text(\"Orange\".into()),\n                Value::from_f64(2.00),\n            ],\n        );\n        initial_delta.insert(\n            4,\n            vec![\n                Value::from_i64(4),\n                Value::Text(\"Grape\".into()),\n                Value::from_f64(3.50),\n            ],\n        );\n\n        let result = pager\n            .io\n            .block(|| agg.commit((&initial_delta).into(), &mut cursors))\n            .unwrap();\n\n        // Verify MIN and MAX\n        assert_eq!(result.changes.len(), 1);\n        let (row, weight) = &result.changes[0];\n        assert_eq!(*weight, 1);\n        assert_eq!(row.values[0], Value::from_f64(0.75)); // MIN\n        assert_eq!(row.values[1], Value::from_f64(3.50)); // MAX\n    }\n\n    #[test]\n    fn test_min_max_deletion_updates_min() {\n        // Test that deleting the MIN value updates to the next lowest\n        let (pager, table_root_page_id, index_root_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_root_page_id, 5);\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        let mut agg = AggregateOperator::new(\n            1,      // operator_id\n            vec![], // No GROUP BY\n            vec![\n                AggregateFunction::Min(2), // price is at index 2\n                AggregateFunction::Max(2), // price is at index 2\n            ],\n            vec![\"id\".to_string(), \"name\".to_string(), \"price\".to_string()],\n        )\n        .unwrap();\n\n        // Initial data\n        let mut initial_delta = Delta::new();\n        initial_delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Apple\".into()),\n                Value::from_f64(1.50),\n            ],\n        );\n        initial_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Banana\".into()),\n                Value::from_f64(0.75),\n            ],\n        );\n        initial_delta.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::Text(\"Orange\".into()),\n                Value::from_f64(2.00),\n            ],\n        );\n        initial_delta.insert(\n            4,\n            vec![\n                Value::from_i64(4),\n                Value::Text(\"Grape\".into()),\n                Value::from_f64(3.50),\n            ],\n        );\n\n        pager\n            .io\n            .block(|| agg.commit((&initial_delta).into(), &mut cursors))\n            .unwrap();\n\n        // Delete the MIN value (Banana at 0.75)\n        let mut delete_delta = Delta::new();\n        delete_delta.delete(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Banana\".into()),\n                Value::from_f64(0.75),\n            ],\n        );\n\n        let result = pager\n            .io\n            .block(|| agg.commit((&delete_delta).into(), &mut cursors))\n            .unwrap();\n\n        // Should emit retraction of old values and new values\n        assert_eq!(result.changes.len(), 2);\n\n        // Find the retraction (weight = -1)\n        let retraction = result.changes.iter().find(|(_, w)| *w == -1).unwrap();\n        assert_eq!(retraction.0.values[0], Value::from_f64(0.75)); // Old MIN\n        assert_eq!(retraction.0.values[1], Value::from_f64(3.50)); // Old MAX\n\n        // Find the new values (weight = 1)\n        let new_values = result.changes.iter().find(|(_, w)| *w == 1).unwrap();\n        assert_eq!(new_values.0.values[0], Value::from_f64(1.50)); // New MIN (Apple)\n        assert_eq!(new_values.0.values[1], Value::from_f64(3.50)); // MAX unchanged\n    }\n\n    #[test]\n    fn test_min_max_deletion_updates_max() {\n        // Test that deleting the MAX value updates to the next highest\n        let (pager, table_root_page_id, index_root_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_root_page_id, 5);\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        let mut agg = AggregateOperator::new(\n            1,      // operator_id\n            vec![], // No GROUP BY\n            vec![\n                AggregateFunction::Min(2), // price is at index 2\n                AggregateFunction::Max(2), // price is at index 2\n            ],\n            vec![\"id\".to_string(), \"name\".to_string(), \"price\".to_string()],\n        )\n        .unwrap();\n\n        // Initial data\n        let mut initial_delta = Delta::new();\n        initial_delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Apple\".into()),\n                Value::from_f64(1.50),\n            ],\n        );\n        initial_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Banana\".into()),\n                Value::from_f64(0.75),\n            ],\n        );\n        initial_delta.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::Text(\"Orange\".into()),\n                Value::from_f64(2.00),\n            ],\n        );\n        initial_delta.insert(\n            4,\n            vec![\n                Value::from_i64(4),\n                Value::Text(\"Grape\".into()),\n                Value::from_f64(3.50),\n            ],\n        );\n\n        pager\n            .io\n            .block(|| agg.commit((&initial_delta).into(), &mut cursors))\n            .unwrap();\n\n        // Delete the MAX value (Grape at 3.50)\n        let mut delete_delta = Delta::new();\n        delete_delta.delete(\n            4,\n            vec![\n                Value::from_i64(4),\n                Value::Text(\"Grape\".into()),\n                Value::from_f64(3.50),\n            ],\n        );\n\n        let result = pager\n            .io\n            .block(|| agg.commit((&delete_delta).into(), &mut cursors))\n            .unwrap();\n\n        // Should emit retraction of old values and new values\n        assert_eq!(result.changes.len(), 2);\n\n        // Find the retraction (weight = -1)\n        let retraction = result.changes.iter().find(|(_, w)| *w == -1).unwrap();\n        assert_eq!(retraction.0.values[0], Value::from_f64(0.75)); // Old MIN\n        assert_eq!(retraction.0.values[1], Value::from_f64(3.50)); // Old MAX\n\n        // Find the new values (weight = 1)\n        let new_values = result.changes.iter().find(|(_, w)| *w == 1).unwrap();\n        assert_eq!(new_values.0.values[0], Value::from_f64(0.75)); // MIN unchanged\n        assert_eq!(new_values.0.values[1], Value::from_f64(2.00)); // New MAX (Orange)\n    }\n\n    #[test]\n    fn test_min_max_insertion_updates_min() {\n        // Test that inserting a new MIN value updates the aggregate\n        let (pager, table_root_page_id, index_root_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_root_page_id, 5);\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        let mut agg = AggregateOperator::new(\n            1,      // operator_id\n            vec![], // No GROUP BY\n            vec![\n                AggregateFunction::Min(2), // price is at index 2\n                AggregateFunction::Max(2), // price is at index 2\n            ],\n            vec![\"id\".to_string(), \"name\".to_string(), \"price\".to_string()],\n        )\n        .unwrap();\n\n        // Initial data\n        let mut initial_delta = Delta::new();\n        initial_delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Apple\".into()),\n                Value::from_f64(1.50),\n            ],\n        );\n        initial_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Orange\".into()),\n                Value::from_f64(2.00),\n            ],\n        );\n        initial_delta.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::Text(\"Grape\".into()),\n                Value::from_f64(3.50),\n            ],\n        );\n\n        pager\n            .io\n            .block(|| agg.commit((&initial_delta).into(), &mut cursors))\n            .unwrap();\n\n        // Insert a new MIN value\n        let mut insert_delta = Delta::new();\n        insert_delta.insert(\n            4,\n            vec![\n                Value::from_i64(4),\n                Value::Text(\"Lemon\".into()),\n                Value::from_f64(0.50),\n            ],\n        );\n\n        let result = pager\n            .io\n            .block(|| agg.commit((&insert_delta).into(), &mut cursors))\n            .unwrap();\n\n        // Should emit retraction of old values and new values\n        assert_eq!(result.changes.len(), 2);\n\n        // Find the retraction (weight = -1)\n        let retraction = result.changes.iter().find(|(_, w)| *w == -1).unwrap();\n        assert_eq!(retraction.0.values[0], Value::from_f64(1.50)); // Old MIN\n        assert_eq!(retraction.0.values[1], Value::from_f64(3.50)); // Old MAX\n\n        // Find the new values (weight = 1)\n        let new_values = result.changes.iter().find(|(_, w)| *w == 1).unwrap();\n        assert_eq!(new_values.0.values[0], Value::from_f64(0.50)); // New MIN (Lemon)\n        assert_eq!(new_values.0.values[1], Value::from_f64(3.50)); // MAX unchanged\n    }\n\n    #[test]\n    fn test_min_max_insertion_updates_max() {\n        // Test that inserting a new MAX value updates the aggregate\n        let (pager, table_root_page_id, index_root_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_root_page_id, 5);\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        let mut agg = AggregateOperator::new(\n            1,      // operator_id\n            vec![], // No GROUP BY\n            vec![\n                AggregateFunction::Min(2), // price is at index 2\n                AggregateFunction::Max(2), // price is at index 2\n            ],\n            vec![\"id\".to_string(), \"name\".to_string(), \"price\".to_string()],\n        )\n        .unwrap();\n\n        // Initial data\n        let mut initial_delta = Delta::new();\n        initial_delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Apple\".into()),\n                Value::from_f64(1.50),\n            ],\n        );\n        initial_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Orange\".into()),\n                Value::from_f64(2.00),\n            ],\n        );\n        initial_delta.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::Text(\"Grape\".into()),\n                Value::from_f64(3.50),\n            ],\n        );\n\n        pager\n            .io\n            .block(|| agg.commit((&initial_delta).into(), &mut cursors))\n            .unwrap();\n\n        // Insert a new MAX value\n        let mut insert_delta = Delta::new();\n        insert_delta.insert(\n            4,\n            vec![\n                Value::from_i64(4),\n                Value::Text(\"Melon\".into()),\n                Value::from_f64(5.00),\n            ],\n        );\n\n        let result = pager\n            .io\n            .block(|| agg.commit((&insert_delta).into(), &mut cursors))\n            .unwrap();\n\n        // Should emit retraction of old values and new values\n        assert_eq!(result.changes.len(), 2);\n\n        // Find the retraction (weight = -1)\n        let retraction = result.changes.iter().find(|(_, w)| *w == -1).unwrap();\n        assert_eq!(retraction.0.values[0], Value::from_f64(1.50)); // Old MIN\n        assert_eq!(retraction.0.values[1], Value::from_f64(3.50)); // Old MAX\n\n        // Find the new values (weight = 1)\n        let new_values = result.changes.iter().find(|(_, w)| *w == 1).unwrap();\n        assert_eq!(new_values.0.values[0], Value::from_f64(1.50)); // MIN unchanged\n        assert_eq!(new_values.0.values[1], Value::from_f64(5.00)); // New MAX (Melon)\n    }\n\n    #[test]\n    fn test_min_max_update_changes_min() {\n        // Test that updating a row to become the new MIN updates the aggregate\n        let (pager, table_root_page_id, index_root_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_root_page_id, 5);\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        let mut agg = AggregateOperator::new(\n            1,      // operator_id\n            vec![], // No GROUP BY\n            vec![\n                AggregateFunction::Min(2), // price is at index 2\n                AggregateFunction::Max(2), // price is at index 2\n            ],\n            vec![\"id\".to_string(), \"name\".to_string(), \"price\".to_string()],\n        )\n        .unwrap();\n\n        // Initial data\n        let mut initial_delta = Delta::new();\n        initial_delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Apple\".into()),\n                Value::from_f64(1.50),\n            ],\n        );\n        initial_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Orange\".into()),\n                Value::from_f64(2.00),\n            ],\n        );\n        initial_delta.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::Text(\"Grape\".into()),\n                Value::from_f64(3.50),\n            ],\n        );\n\n        pager\n            .io\n            .block(|| agg.commit((&initial_delta).into(), &mut cursors))\n            .unwrap();\n\n        // Update Orange price to be the new MIN (update = delete + insert)\n        let mut update_delta = Delta::new();\n        update_delta.delete(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Orange\".into()),\n                Value::from_f64(2.00),\n            ],\n        );\n        update_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Orange\".into()),\n                Value::from_f64(0.25),\n            ],\n        );\n\n        let result = pager\n            .io\n            .block(|| agg.commit((&update_delta).into(), &mut cursors))\n            .unwrap();\n\n        // Should emit retraction of old values and new values\n        assert_eq!(result.changes.len(), 2);\n\n        // Find the retraction (weight = -1)\n        let retraction = result.changes.iter().find(|(_, w)| *w == -1).unwrap();\n        assert_eq!(retraction.0.values[0], Value::from_f64(1.50)); // Old MIN\n        assert_eq!(retraction.0.values[1], Value::from_f64(3.50)); // Old MAX\n\n        // Find the new values (weight = 1)\n        let new_values = result.changes.iter().find(|(_, w)| *w == 1).unwrap();\n        assert_eq!(new_values.0.values[0], Value::from_f64(0.25)); // New MIN (updated Orange)\n        assert_eq!(new_values.0.values[1], Value::from_f64(3.50)); // MAX unchanged\n    }\n\n    #[test]\n    fn test_min_max_with_group_by() {\n        // Test MIN/MAX with GROUP BY\n        let (pager, table_root_page_id, index_root_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_root_page_id, 5);\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        let mut agg = AggregateOperator::new(\n            1,       // operator_id\n            vec![1], // GROUP BY category (index 1)\n            vec![\n                AggregateFunction::Min(3), // price is at index 3\n                AggregateFunction::Max(3), // price is at index 3\n            ],\n            vec![\n                \"id\".to_string(),\n                \"category\".to_string(),\n                \"name\".to_string(),\n                \"price\".to_string(),\n            ],\n        )\n        .unwrap();\n\n        // Initial data with two categories\n        let mut initial_delta = Delta::new();\n        initial_delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"fruit\".into()),\n                Value::Text(\"Apple\".into()),\n                Value::from_f64(1.50),\n            ],\n        );\n        initial_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"fruit\".into()),\n                Value::Text(\"Banana\".into()),\n                Value::from_f64(0.75),\n            ],\n        );\n        initial_delta.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::Text(\"fruit\".into()),\n                Value::Text(\"Orange\".into()),\n                Value::from_f64(2.00),\n            ],\n        );\n        initial_delta.insert(\n            4,\n            vec![\n                Value::from_i64(4),\n                Value::Text(\"veggie\".into()),\n                Value::Text(\"Carrot\".into()),\n                Value::from_f64(0.50),\n            ],\n        );\n        initial_delta.insert(\n            5,\n            vec![\n                Value::from_i64(5),\n                Value::Text(\"veggie\".into()),\n                Value::Text(\"Lettuce\".into()),\n                Value::from_f64(1.25),\n            ],\n        );\n\n        let result = pager\n            .io\n            .block(|| agg.commit((&initial_delta).into(), &mut cursors))\n            .unwrap();\n\n        // Should have two groups\n        assert_eq!(result.changes.len(), 2);\n\n        // Find fruit group\n        let fruit = result\n            .changes\n            .iter()\n            .find(|(row, _)| row.values[0] == Value::Text(\"fruit\".into()))\n            .unwrap();\n        assert_eq!(fruit.1, 1); // weight\n        assert_eq!(fruit.0.values[1], Value::from_f64(0.75)); // MIN (Banana)\n        assert_eq!(fruit.0.values[2], Value::from_f64(2.00)); // MAX (Orange)\n\n        // Find veggie group\n        let veggie = result\n            .changes\n            .iter()\n            .find(|(row, _)| row.values[0] == Value::Text(\"veggie\".into()))\n            .unwrap();\n        assert_eq!(veggie.1, 1); // weight\n        assert_eq!(veggie.0.values[1], Value::from_f64(0.50)); // MIN (Carrot)\n        assert_eq!(veggie.0.values[2], Value::from_f64(1.25)); // MAX (Lettuce)\n    }\n\n    #[test]\n    fn test_min_max_with_nulls() {\n        // Test that NULL values are ignored in MIN/MAX\n        let (pager, table_root_page_id, index_root_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_root_page_id, 5);\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        let mut agg = AggregateOperator::new(\n            1,      // operator_id\n            vec![], // No GROUP BY\n            vec![\n                AggregateFunction::Min(2), // price is at index 2\n                AggregateFunction::Max(2), // price is at index 2\n            ],\n            vec![\"id\".to_string(), \"name\".to_string(), \"price\".to_string()],\n        )\n        .unwrap();\n\n        // Initial data with NULL values\n        let mut initial_delta = Delta::new();\n        initial_delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Apple\".into()),\n                Value::from_f64(1.50),\n            ],\n        );\n        initial_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Unknown1\".into()),\n                Value::Null,\n            ],\n        );\n        initial_delta.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::Text(\"Orange\".into()),\n                Value::from_f64(2.00),\n            ],\n        );\n        initial_delta.insert(\n            4,\n            vec![\n                Value::from_i64(4),\n                Value::Text(\"Unknown2\".into()),\n                Value::Null,\n            ],\n        );\n        initial_delta.insert(\n            5,\n            vec![\n                Value::from_i64(5),\n                Value::Text(\"Grape\".into()),\n                Value::from_f64(3.50),\n            ],\n        );\n\n        let result = pager\n            .io\n            .block(|| agg.commit((&initial_delta).into(), &mut cursors))\n            .unwrap();\n\n        // Verify MIN and MAX ignore NULLs\n        assert_eq!(result.changes.len(), 1);\n        let (row, weight) = &result.changes[0];\n        assert_eq!(*weight, 1);\n        assert_eq!(row.values[0], Value::from_f64(1.50)); // MIN (Apple, ignoring NULLs)\n        assert_eq!(row.values[1], Value::from_f64(3.50)); // MAX (Grape, ignoring NULLs)\n    }\n\n    #[test]\n    fn test_min_max_integer_values() {\n        // Test MIN/MAX with integer values instead of floats\n        let (pager, table_root_page_id, index_root_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_root_page_id, 5);\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        let mut agg = AggregateOperator::new(\n            1,      // operator_id\n            vec![], // No GROUP BY\n            vec![\n                AggregateFunction::Min(2), // score is at index 2\n                AggregateFunction::Max(2), // score is at index 2\n            ],\n            vec![\"id\".to_string(), \"name\".to_string(), \"score\".to_string()],\n        )\n        .unwrap();\n\n        // Initial data with integer scores\n        let mut initial_delta = Delta::new();\n        initial_delta.insert(\n            1,\n            vec![\n                Value::from_i64(1),\n                Value::Text(\"Alice\".into()),\n                Value::from_i64(85),\n            ],\n        );\n        initial_delta.insert(\n            2,\n            vec![\n                Value::from_i64(2),\n                Value::Text(\"Bob\".into()),\n                Value::from_i64(92),\n            ],\n        );\n        initial_delta.insert(\n            3,\n            vec![\n                Value::from_i64(3),\n                Value::Text(\"Carol\".into()),\n                Value::from_i64(78),\n            ],\n        );\n        initial_delta.insert(\n            4,\n            vec![\n                Value::from_i64(4),\n                Value::Text(\"Dave\".into()),\n                Value::from_i64(95),\n            ],\n        );\n\n        let result = pager\n            .io\n            .block(|| agg.commit((&initial_delta).into(), &mut cursors))\n            .unwrap();\n\n        // Verify MIN and MAX with integers\n        assert_eq!(result.changes.len(), 1);\n        let (row, weight) = &result.changes[0];\n        assert_eq!(*weight, 1);\n        assert_eq!(row.values[0], Value::from_i64(78)); // MIN (Carol)\n        assert_eq!(row.values[1], Value::from_i64(95)); // MAX (Dave)\n    }\n\n    #[test]\n    fn test_min_max_text_values() {\n        // Test MIN/MAX with text values (alphabetical ordering)\n        let (pager, table_root_page_id, index_root_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_root_page_id, 5);\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        let mut agg = AggregateOperator::new(\n            1,      // operator_id\n            vec![], // No GROUP BY\n            vec![\n                AggregateFunction::Min(1), // name is at index 1\n                AggregateFunction::Max(1), // name is at index 1\n            ],\n            vec![\"id\".to_string(), \"name\".to_string()],\n        )\n        .unwrap();\n\n        // Initial data with text values\n        let mut initial_delta = Delta::new();\n        initial_delta.insert(1, vec![Value::from_i64(1), Value::Text(\"Charlie\".into())]);\n        initial_delta.insert(2, vec![Value::from_i64(2), Value::Text(\"Alice\".into())]);\n        initial_delta.insert(3, vec![Value::from_i64(3), Value::Text(\"Bob\".into())]);\n        initial_delta.insert(4, vec![Value::from_i64(4), Value::Text(\"David\".into())]);\n\n        let result = pager\n            .io\n            .block(|| agg.commit((&initial_delta).into(), &mut cursors))\n            .unwrap();\n\n        // Verify MIN and MAX with text (alphabetical)\n        assert_eq!(result.changes.len(), 1);\n        let (row, weight) = &result.changes[0];\n        assert_eq!(*weight, 1);\n        assert_eq!(row.values[0], Value::Text(\"Alice\".into())); // MIN alphabetically\n        assert_eq!(row.values[1], Value::Text(\"David\".into())); // MAX alphabetically\n    }\n\n    #[test]\n    fn test_min_max_with_other_aggregates() {\n        let (pager, table_root_page_id, index_root_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_root_page_id, 5);\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        let mut agg = AggregateOperator::new(\n            1,      // operator_id\n            vec![], // No GROUP BY\n            vec![\n                AggregateFunction::Count,\n                AggregateFunction::Sum(1), // value is at index 1\n                AggregateFunction::Min(1), // value is at index 1\n                AggregateFunction::Max(1), // value is at index 1\n                AggregateFunction::Avg(1), // value is at index 1\n            ],\n            vec![\"id\".to_string(), \"value\".to_string()],\n        )\n        .unwrap();\n\n        // Initial data\n        let mut delta = Delta::new();\n        delta.insert(1, vec![Value::from_i64(1), Value::from_i64(10)]);\n        delta.insert(2, vec![Value::from_i64(2), Value::from_i64(5)]);\n        delta.insert(3, vec![Value::from_i64(3), Value::from_i64(15)]);\n        delta.insert(4, vec![Value::from_i64(4), Value::from_i64(20)]);\n\n        let result = pager\n            .io\n            .block(|| agg.commit((&delta).into(), &mut cursors))\n            .unwrap();\n\n        assert_eq!(result.changes.len(), 1);\n        let (row, weight) = &result.changes[0];\n        assert_eq!(*weight, 1);\n        assert_eq!(row.values[0], Value::from_i64(4)); // COUNT\n        assert_eq!(row.values[1], Value::from_i64(50)); // SUM\n        assert_eq!(row.values[2], Value::from_i64(5)); // MIN\n        assert_eq!(row.values[3], Value::from_i64(20)); // MAX\n        assert_eq!(row.values[4], Value::from_f64(12.5)); // AVG (50/4)\n\n        // Delete the MIN value\n        let mut delta2 = Delta::new();\n        delta2.delete(2, vec![Value::from_i64(2), Value::from_i64(5)]);\n\n        let result2 = pager\n            .io\n            .block(|| agg.commit((&delta2).into(), &mut cursors))\n            .unwrap();\n\n        assert_eq!(result2.changes.len(), 2);\n        let (row_del, weight_del) = &result2.changes[0];\n        assert_eq!(*weight_del, -1);\n        assert_eq!(row_del.values[0], Value::from_i64(4)); // Old COUNT\n        assert_eq!(row_del.values[1], Value::from_i64(50)); // Old SUM\n        assert_eq!(row_del.values[2], Value::from_i64(5)); // Old MIN\n        assert_eq!(row_del.values[3], Value::from_i64(20)); // Old MAX\n        assert_eq!(row_del.values[4], Value::from_f64(12.5)); // Old AVG\n\n        let (row_ins, weight_ins) = &result2.changes[1];\n        assert_eq!(*weight_ins, 1);\n        assert_eq!(row_ins.values[0], Value::from_i64(3)); // New COUNT\n        assert_eq!(row_ins.values[1], Value::from_i64(45)); // New SUM\n        assert_eq!(row_ins.values[2], Value::from_i64(10)); // New MIN\n        assert_eq!(row_ins.values[3], Value::from_i64(20)); // MAX unchanged\n        assert_eq!(row_ins.values[4], Value::from_f64(15.0)); // New AVG (45/3)\n\n        // Now delete the MAX value\n        let mut delta3 = Delta::new();\n        delta3.delete(4, vec![Value::from_i64(4), Value::from_i64(20)]);\n\n        let result3 = pager\n            .io\n            .block(|| agg.commit((&delta3).into(), &mut cursors))\n            .unwrap();\n\n        assert_eq!(result3.changes.len(), 2);\n        let (row_del2, weight_del2) = &result3.changes[0];\n        assert_eq!(*weight_del2, -1);\n        assert_eq!(row_del2.values[3], Value::from_i64(20)); // Old MAX\n\n        let (row_ins2, weight_ins2) = &result3.changes[1];\n        assert_eq!(*weight_ins2, 1);\n        assert_eq!(row_ins2.values[0], Value::from_i64(2)); // COUNT\n        assert_eq!(row_ins2.values[1], Value::from_i64(25)); // SUM\n        assert_eq!(row_ins2.values[2], Value::from_i64(10)); // MIN unchanged\n        assert_eq!(row_ins2.values[3], Value::from_i64(15)); // New MAX\n        assert_eq!(row_ins2.values[4], Value::from_f64(12.5)); // AVG (25/2)\n    }\n\n    #[test]\n    fn test_min_max_multiple_columns() {\n        let (pager, table_root_page_id, index_root_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_root_page_id, 5);\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        let mut agg = AggregateOperator::new(\n            1,      // operator_id\n            vec![], // No GROUP BY\n            vec![\n                AggregateFunction::Min(0), // col1 is at index 0\n                AggregateFunction::Max(1), // col2 is at index 1\n                AggregateFunction::Min(2), // col3 is at index 2\n            ],\n            vec![\"col1\".to_string(), \"col2\".to_string(), \"col3\".to_string()],\n        )\n        .unwrap();\n\n        // Initial data\n        let mut delta = Delta::new();\n        delta.insert(\n            1,\n            vec![\n                Value::from_i64(10),\n                Value::from_i64(100),\n                Value::from_i64(1000),\n            ],\n        );\n        delta.insert(\n            2,\n            vec![\n                Value::from_i64(5),\n                Value::from_i64(200),\n                Value::from_i64(2000),\n            ],\n        );\n        delta.insert(\n            3,\n            vec![\n                Value::from_i64(15),\n                Value::from_i64(150),\n                Value::from_i64(500),\n            ],\n        );\n\n        let result = pager\n            .io\n            .block(|| agg.commit((&delta).into(), &mut cursors))\n            .unwrap();\n\n        assert_eq!(result.changes.len(), 1);\n        let (row, weight) = &result.changes[0];\n        assert_eq!(*weight, 1);\n        assert_eq!(row.values[0], Value::from_i64(5)); // MIN(col1)\n        assert_eq!(row.values[1], Value::from_i64(200)); // MAX(col2)\n        assert_eq!(row.values[2], Value::from_i64(500)); // MIN(col3)\n\n        // Delete the row with MIN(col1) and MAX(col2)\n        let mut delta2 = Delta::new();\n        delta2.delete(\n            2,\n            vec![\n                Value::from_i64(5),\n                Value::from_i64(200),\n                Value::from_i64(2000),\n            ],\n        );\n\n        let result2 = pager\n            .io\n            .block(|| agg.commit((&delta2).into(), &mut cursors))\n            .unwrap();\n\n        assert_eq!(result2.changes.len(), 2);\n        // Should emit delete of old state and insert of new state\n        let (row_del, weight_del) = &result2.changes[0];\n        assert_eq!(*weight_del, -1);\n        assert_eq!(row_del.values[0], Value::from_i64(5)); // Old MIN(col1)\n        assert_eq!(row_del.values[1], Value::from_i64(200)); // Old MAX(col2)\n        assert_eq!(row_del.values[2], Value::from_i64(500)); // Old MIN(col3)\n\n        let (row_ins, weight_ins) = &result2.changes[1];\n        assert_eq!(*weight_ins, 1);\n        assert_eq!(row_ins.values[0], Value::from_i64(10)); // New MIN(col1)\n        assert_eq!(row_ins.values[1], Value::from_i64(150)); // New MAX(col2)\n        assert_eq!(row_ins.values[2], Value::from_i64(500)); // MIN(col3) unchanged\n    }\n\n    #[test]\n    fn test_join_operator_inner() {\n        // Test INNER JOIN with incremental updates\n        let (pager, table_page_id, index_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_page_id, 10);\n        let index_def = create_dbsp_state_index(index_page_id);\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_page_id, &index_def, 10);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n        let mut join = JoinOperator::new(\n            1, // operator_id\n            JoinType::Inner,\n            vec![0], // Join on first column\n            vec![0],\n            vec![\"customer_id\".to_string(), \"amount\".to_string()],\n            vec![\"id\".to_string(), \"name\".to_string()],\n        )\n        .unwrap();\n\n        // FIRST COMMIT: Initialize with data\n        let mut left_delta = Delta::new();\n        left_delta.insert(1, vec![Value::from_i64(1), Value::from_f64(100.0)]);\n        left_delta.insert(2, vec![Value::from_i64(2), Value::from_f64(200.0)]);\n        left_delta.insert(3, vec![Value::from_i64(3), Value::from_f64(300.0)]); // No match initially\n        let mut right_delta = Delta::new();\n        right_delta.insert(1, vec![Value::from_i64(1), Value::Text(\"Alice\".into())]);\n        right_delta.insert(2, vec![Value::from_i64(2), Value::Text(\"Bob\".into())]);\n        right_delta.insert(4, vec![Value::from_i64(4), Value::Text(\"David\".into())]); // No match initially\n\n        let delta_pair = DeltaPair::new(left_delta, right_delta);\n        let result = pager\n            .io\n            .block(|| join.commit(delta_pair.clone(), &mut cursors))\n            .unwrap();\n\n        // Should have 2 matches (customer 1 and 2)\n        assert_eq!(\n            result.changes.len(),\n            2,\n            \"First commit should produce 2 matches\"\n        );\n\n        let mut results: Vec<_> = result.changes;\n        results.sort_by_key(|r| r.0.values[0].clone());\n\n        assert_eq!(results[0].0.values[0], Value::from_i64(1));\n        assert_eq!(results[0].0.values[3], Value::Text(\"Alice\".into()));\n        assert_eq!(results[1].0.values[0], Value::from_i64(2));\n        assert_eq!(results[1].0.values[3], Value::Text(\"Bob\".into()));\n\n        // SECOND COMMIT: Add incremental data that should join with persisted state\n        // Add a new left row that should match existing right row (customer 4)\n        let mut left_delta2 = Delta::new();\n        left_delta2.insert(5, vec![Value::from_i64(4), Value::from_f64(400.0)]); // Should match David from persisted state\n\n        // Add a new right row that should match existing left row (customer 3)\n        let mut right_delta2 = Delta::new();\n        right_delta2.insert(6, vec![Value::from_i64(3), Value::Text(\"Charlie\".into())]); // Should match customer 3 from persisted state\n\n        let delta_pair2 = DeltaPair::new(left_delta2, right_delta2);\n        let result2 = pager\n            .io\n            .block(|| join.commit(delta_pair2.clone(), &mut cursors))\n            .unwrap();\n\n        // The second commit should produce:\n        // 1. New left (customer_id=4) joins with persisted right (id=4, David)\n        // 2. Persisted left (customer_id=3) joins with new right (id=3, Charlie)\n\n        assert_eq!(\n            result2.changes.len(),\n            2,\n            \"Second commit should produce 2 new matches from incremental join. Got: {:?}\",\n            result2.changes\n        );\n\n        // Verify the incremental results\n        let mut results2: Vec<_> = result2.changes;\n        results2.sort_by_key(|r| r.0.values[0].clone());\n\n        // Check for customer 3 joined with Charlie (existing left + new right)\n        let charlie_match = results2\n            .iter()\n            .find(|(row, _)| row.values[0] == Value::from_i64(3))\n            .expect(\"Should find customer 3 joined with new Charlie\");\n        assert_eq!(charlie_match.0.values[2], Value::from_i64(3));\n        assert_eq!(charlie_match.0.values[3], Value::Text(\"Charlie\".into()));\n\n        // Check for customer 4 joined with David (new left + existing right)\n        let david_match = results2\n            .iter()\n            .find(|(row, _)| row.values[0] == Value::from_i64(4))\n            .expect(\"Should find new customer 4 joined with existing David\");\n        assert_eq!(david_match.0.values[0], Value::from_i64(4));\n        assert_eq!(david_match.0.values[3], Value::Text(\"David\".into()));\n    }\n\n    #[test]\n    fn test_join_operator_with_deletions() {\n        // Test INNER JOIN with deletions (negative weights)\n        let (pager, table_page_id, index_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_page_id, 10);\n        let index_def = create_dbsp_state_index(index_page_id);\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_page_id, &index_def, 10);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        let mut join = JoinOperator::new(\n            1, // operator_id\n            JoinType::Inner,\n            vec![0], // Join on first column\n            vec![0],\n            vec![\"customer_id\".to_string(), \"amount\".to_string()],\n            vec![\"id\".to_string(), \"name\".to_string()],\n        )\n        .unwrap();\n\n        // FIRST COMMIT: Add initial data\n        let mut left_delta = Delta::new();\n        left_delta.insert(1, vec![Value::from_i64(1), Value::from_f64(100.0)]);\n        left_delta.insert(2, vec![Value::from_i64(2), Value::from_f64(200.0)]);\n        left_delta.insert(3, vec![Value::from_i64(3), Value::from_f64(300.0)]);\n\n        let mut right_delta = Delta::new();\n        right_delta.insert(1, vec![Value::from_i64(1), Value::Text(\"Alice\".into())]);\n        right_delta.insert(2, vec![Value::from_i64(2), Value::Text(\"Bob\".into())]);\n        right_delta.insert(3, vec![Value::from_i64(3), Value::Text(\"Charlie\".into())]);\n\n        let delta_pair = DeltaPair::new(left_delta, right_delta);\n\n        let result = pager\n            .io\n            .block(|| join.commit(delta_pair.clone(), &mut cursors))\n            .unwrap();\n\n        assert_eq!(result.changes.len(), 3, \"Should have 3 initial joins\");\n\n        // SECOND COMMIT: Delete customer 2 from left side\n        let mut left_delta2 = Delta::new();\n        left_delta2.delete(2, vec![Value::from_i64(2), Value::from_f64(200.0)]);\n\n        let empty_right = Delta::new();\n        let delta_pair2 = DeltaPair::new(left_delta2, empty_right);\n\n        let result2 = pager\n            .io\n            .block(|| join.commit(delta_pair2.clone(), &mut cursors))\n            .unwrap();\n\n        // Should produce 1 deletion (retraction) of the join for customer 2\n        assert_eq!(\n            result2.changes.len(),\n            1,\n            \"Should produce 1 retraction for deleted customer 2\"\n        );\n        assert_eq!(\n            result2.changes[0].1, -1,\n            \"Should have weight -1 for deletion\"\n        );\n        assert_eq!(result2.changes[0].0.values[0], Value::from_i64(2));\n        assert_eq!(result2.changes[0].0.values[3], Value::Text(\"Bob\".into()));\n\n        // THIRD COMMIT: Delete customer 3 from right side\n        let empty_left = Delta::new();\n        let mut right_delta3 = Delta::new();\n        right_delta3.delete(3, vec![Value::from_i64(3), Value::Text(\"Charlie\".into())]);\n\n        let delta_pair3 = DeltaPair::new(empty_left, right_delta3);\n\n        let result3 = pager\n            .io\n            .block(|| join.commit(delta_pair3.clone(), &mut cursors))\n            .unwrap();\n\n        // Should produce 1 deletion (retraction) of the join for customer 3\n        assert_eq!(\n            result3.changes.len(),\n            1,\n            \"Should produce 1 retraction for deleted customer 3\"\n        );\n        assert_eq!(\n            result3.changes[0].1, -1,\n            \"Should have weight -1 for deletion\"\n        );\n        assert_eq!(result3.changes[0].0.values[0], Value::from_i64(3));\n        assert_eq!(result3.changes[0].0.values[2], Value::from_i64(3));\n    }\n\n    #[test]\n    fn test_join_operator_one_to_many() {\n        // Test one-to-many relationship: one customer with multiple orders\n        let (pager, table_page_id, index_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_page_id, 10);\n        let index_def = create_dbsp_state_index(index_page_id);\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_page_id, &index_def, 10);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        let mut join = JoinOperator::new(\n            1, // operator_id\n            JoinType::Inner,\n            vec![0], // Join on first column (customer_id for orders)\n            vec![0], // Join on first column (id for customers)\n            vec![\n                \"customer_id\".to_string(),\n                \"order_id\".to_string(),\n                \"amount\".to_string(),\n            ],\n            vec![\"id\".to_string(), \"name\".to_string()],\n        )\n        .unwrap();\n\n        // FIRST COMMIT: Add one customer\n        let left_delta = Delta::new(); // Empty orders initially\n        let mut right_delta = Delta::new();\n        right_delta.insert(1, vec![Value::from_i64(100), Value::Text(\"Alice\".into())]);\n\n        let delta_pair = DeltaPair::new(left_delta, right_delta);\n        let result = pager\n            .io\n            .block(|| join.commit(delta_pair.clone(), &mut cursors))\n            .unwrap();\n\n        // No joins yet (customer exists but no orders)\n        assert_eq!(\n            result.changes.len(),\n            0,\n            \"Should have no joins with customer but no orders\"\n        );\n\n        // SECOND COMMIT: Add multiple orders for the same customer\n        let mut left_delta2 = Delta::new();\n        left_delta2.insert(\n            1,\n            vec![\n                Value::from_i64(100),\n                Value::from_i64(1001),\n                Value::from_f64(50.0),\n            ],\n        ); // order 1001\n        left_delta2.insert(\n            2,\n            vec![\n                Value::from_i64(100),\n                Value::from_i64(1002),\n                Value::from_f64(75.0),\n            ],\n        ); // order 1002\n        left_delta2.insert(\n            3,\n            vec![\n                Value::from_i64(100),\n                Value::from_i64(1003),\n                Value::from_f64(100.0),\n            ],\n        ); // order 1003\n\n        let right_delta2 = Delta::new(); // No new customers\n\n        let delta_pair2 = DeltaPair::new(left_delta2, right_delta2);\n        let result2 = pager\n            .io\n            .block(|| join.commit(delta_pair2.clone(), &mut cursors))\n            .unwrap();\n\n        // Should produce 3 joins (3 orders × 1 customer)\n        assert_eq!(\n            result2.changes.len(),\n            3,\n            \"Should produce 3 joins for 3 orders with same customer. Got: {:?}\",\n            result2.changes\n        );\n\n        // Verify all three joins have the same customer but different orders\n        for (row, weight) in &result2.changes {\n            assert_eq!(*weight, 1, \"Weight should be 1 for insertion\");\n            assert_eq!(\n                row.values[0],\n                Value::from_i64(100),\n                \"Customer ID should be 100\"\n            );\n            assert_eq!(\n                row.values[4],\n                Value::Text(\"Alice\".into()),\n                \"Customer name should be Alice\"\n            );\n\n            // Check order IDs are different\n            let order_id = match &row.values[1] {\n                Value::Numeric(Numeric::Integer(id)) => *id,\n                _ => panic!(\"Expected integer order ID\"),\n            };\n            assert!(\n                (1001..=1003).contains(&order_id),\n                \"Order ID {order_id} should be between 1001 and 1003\"\n            );\n        }\n\n        // THIRD COMMIT: Delete one order\n        let mut left_delta3 = Delta::new();\n        left_delta3.delete(\n            2,\n            vec![\n                Value::from_i64(100),\n                Value::from_i64(1002),\n                Value::from_f64(75.0),\n            ],\n        );\n\n        let delta_pair3 = DeltaPair::new(left_delta3, Delta::new());\n        let result3 = pager\n            .io\n            .block(|| join.commit(delta_pair3.clone(), &mut cursors))\n            .unwrap();\n\n        // Should produce 1 retraction for the deleted order\n        assert_eq!(result3.changes.len(), 1, \"Should produce 1 retraction\");\n        assert_eq!(result3.changes[0].1, -1, \"Should be a deletion\");\n        assert_eq!(\n            result3.changes[0].0.values[1],\n            Value::from_i64(1002),\n            \"Should delete order 1002\"\n        );\n    }\n\n    #[test]\n    fn test_join_operator_many_to_many() {\n        // Test many-to-many: multiple rows with same key on both sides\n        let (pager, table_page_id, index_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_page_id, 10);\n        let index_def = create_dbsp_state_index(index_page_id);\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_page_id, &index_def, 10);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        let mut join = JoinOperator::new(\n            1, // operator_id\n            JoinType::Inner,\n            vec![0], // Join on category_id\n            vec![0], // Join on id\n            vec![\n                \"category_id\".to_string(),\n                \"product_name\".to_string(),\n                \"price\".to_string(),\n            ],\n            vec![\"id\".to_string(), \"category_name\".to_string()],\n        )\n        .unwrap();\n\n        // FIRST COMMIT: Add multiple products in same category\n        let mut left_delta = Delta::new();\n        left_delta.insert(\n            1,\n            vec![\n                Value::from_i64(10),\n                Value::Text(\"Laptop\".into()),\n                Value::from_f64(1000.0),\n            ],\n        );\n        left_delta.insert(\n            2,\n            vec![\n                Value::from_i64(10),\n                Value::Text(\"Mouse\".into()),\n                Value::from_f64(50.0),\n            ],\n        );\n        left_delta.insert(\n            3,\n            vec![\n                Value::from_i64(10),\n                Value::Text(\"Keyboard\".into()),\n                Value::from_f64(100.0),\n            ],\n        );\n\n        // Add multiple categories with same ID (simulating denormalized data or versioning)\n        let mut right_delta = Delta::new();\n        right_delta.insert(\n            1,\n            vec![Value::from_i64(10), Value::Text(\"Electronics\".into())],\n        );\n        right_delta.insert(\n            2,\n            vec![Value::from_i64(10), Value::Text(\"Computers\".into())],\n        ); // Same category ID, different name\n\n        let delta_pair = DeltaPair::new(left_delta, right_delta);\n        let result = pager\n            .io\n            .block(|| join.commit(delta_pair.clone(), &mut cursors))\n            .unwrap();\n\n        // Should produce 3 products × 2 categories = 6 joins\n        assert_eq!(\n            result.changes.len(),\n            6,\n            \"Should produce 6 joins (3 products × 2 category records). Got: {:?}\",\n            result.changes\n        );\n\n        // Verify we have all combinations\n        let mut found_combinations = HashSet::default();\n        for (row, weight) in &result.changes {\n            assert_eq!(*weight, 1);\n            let product = row.values[1].to_string();\n            let category = row.values[4].to_string();\n            found_combinations.insert((product, category));\n        }\n\n        assert_eq!(\n            found_combinations.len(),\n            6,\n            \"Should have 6 unique combinations\"\n        );\n\n        // SECOND COMMIT: Add one more product in the same category\n        let mut left_delta2 = Delta::new();\n        left_delta2.insert(\n            4,\n            vec![\n                Value::from_i64(10),\n                Value::Text(\"Monitor\".into()),\n                Value::from_f64(500.0),\n            ],\n        );\n\n        let delta_pair2 = DeltaPair::new(left_delta2, Delta::new());\n        let result2 = pager\n            .io\n            .block(|| join.commit(delta_pair2.clone(), &mut cursors))\n            .unwrap();\n\n        // New product should join with both existing category records\n        assert_eq!(\n            result2.changes.len(),\n            2,\n            \"New product should join with 2 existing category records\"\n        );\n\n        for (row, _) in &result2.changes {\n            assert_eq!(row.values[1], Value::Text(\"Monitor\".into()));\n        }\n    }\n\n    #[test]\n    fn test_join_operator_update_in_one_to_many() {\n        // Test updates in one-to-many scenarios\n        let (pager, table_page_id, index_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_page_id, 10);\n        let index_def = create_dbsp_state_index(index_page_id);\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_page_id, &index_def, 10);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        let mut join = JoinOperator::new(\n            1, // operator_id\n            JoinType::Inner,\n            vec![0], // Join on customer_id\n            vec![0], // Join on id\n            vec![\n                \"customer_id\".to_string(),\n                \"order_id\".to_string(),\n                \"amount\".to_string(),\n            ],\n            vec![\"id\".to_string(), \"name\".to_string()],\n        )\n        .unwrap();\n\n        // FIRST COMMIT: Setup one customer with multiple orders\n        let mut left_delta = Delta::new();\n        left_delta.insert(\n            1,\n            vec![\n                Value::from_i64(100),\n                Value::from_i64(1001),\n                Value::from_f64(50.0),\n            ],\n        );\n        left_delta.insert(\n            2,\n            vec![\n                Value::from_i64(100),\n                Value::from_i64(1002),\n                Value::from_f64(75.0),\n            ],\n        );\n        left_delta.insert(\n            3,\n            vec![\n                Value::from_i64(100),\n                Value::from_i64(1003),\n                Value::from_f64(100.0),\n            ],\n        );\n\n        let mut right_delta = Delta::new();\n        right_delta.insert(1, vec![Value::from_i64(100), Value::Text(\"Alice\".into())]);\n\n        let delta_pair = DeltaPair::new(left_delta, right_delta);\n        let result = pager\n            .io\n            .block(|| join.commit(delta_pair.clone(), &mut cursors))\n            .unwrap();\n\n        assert_eq!(result.changes.len(), 3, \"Should have 3 initial joins\");\n\n        // SECOND COMMIT: Update the customer name (affects all 3 joins)\n        let mut right_delta2 = Delta::new();\n        // Delete old customer record\n        right_delta2.delete(1, vec![Value::from_i64(100), Value::Text(\"Alice\".into())]);\n        // Insert updated customer record\n        right_delta2.insert(\n            1,\n            vec![Value::from_i64(100), Value::Text(\"Alice Smith\".into())],\n        );\n\n        let delta_pair2 = DeltaPair::new(Delta::new(), right_delta2);\n        let result2 = pager\n            .io\n            .block(|| join.commit(delta_pair2.clone(), &mut cursors))\n            .unwrap();\n\n        // Should produce 3 deletions and 3 insertions (one for each order)\n        assert_eq!(result2.changes.len(), 6,\n            \"Should produce 6 changes (3 deletions + 3 insertions) when updating customer with 3 orders\");\n\n        let deletions: Vec<_> = result2.changes.iter().filter(|(_, w)| *w == -1).collect();\n        let insertions: Vec<_> = result2.changes.iter().filter(|(_, w)| *w == 1).collect();\n\n        assert_eq!(deletions.len(), 3, \"Should have 3 deletions\");\n        assert_eq!(insertions.len(), 3, \"Should have 3 insertions\");\n\n        // Check all deletions have old name\n        for (row, _) in &deletions {\n            assert_eq!(\n                row.values[4],\n                Value::Text(\"Alice\".into()),\n                \"Deletions should have old name\"\n            );\n        }\n\n        // Check all insertions have new name\n        for (row, _) in &insertions {\n            assert_eq!(\n                row.values[4],\n                Value::Text(\"Alice Smith\".into()),\n                \"Insertions should have new name\"\n            );\n        }\n\n        // Verify we still have all three order IDs in the insertions\n        let mut order_ids = HashSet::default();\n        for (row, _) in &insertions {\n            if let Value::Numeric(Numeric::Integer(order_id)) = &row.values[1] {\n                order_ids.insert(*order_id);\n            }\n        }\n        assert_eq!(\n            order_ids.len(),\n            3,\n            \"Should still have all 3 order IDs after update\"\n        );\n        assert!(order_ids.contains(&1001));\n        assert!(order_ids.contains(&1002));\n        assert!(order_ids.contains(&1003));\n    }\n\n    #[test]\n    fn test_join_operator_weight_accumulation_complex() {\n        // Test complex weight accumulation with multiple identical rows\n        let (pager, table_page_id, index_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_page_id, 10);\n        let index_def = create_dbsp_state_index(index_page_id);\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_page_id, &index_def, 10);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        let mut join = JoinOperator::new(\n            1, // operator_id\n            JoinType::Inner,\n            vec![0], // Join on first column\n            vec![0],\n            vec![\"key\".to_string(), \"val_left\".to_string()],\n            vec![\"key\".to_string(), \"val_right\".to_string()],\n        )\n        .unwrap();\n\n        // FIRST COMMIT: Add identical rows multiple times (simulating duplicates)\n        let mut left_delta = Delta::new();\n        // Same key-value pair inserted 3 times with different rowids\n        left_delta.insert(1, vec![Value::from_i64(10), Value::Text(\"A\".into())]);\n        left_delta.insert(2, vec![Value::from_i64(10), Value::Text(\"A\".into())]);\n        left_delta.insert(3, vec![Value::from_i64(10), Value::Text(\"A\".into())]);\n\n        let mut right_delta = Delta::new();\n        // Same key-value pair inserted 2 times\n        right_delta.insert(4, vec![Value::from_i64(10), Value::Text(\"B\".into())]);\n        right_delta.insert(5, vec![Value::from_i64(10), Value::Text(\"B\".into())]);\n\n        let delta_pair = DeltaPair::new(left_delta, right_delta);\n        let result = pager\n            .io\n            .block(|| join.commit(delta_pair.clone(), &mut cursors))\n            .unwrap();\n\n        // Should produce 3 × 2 = 6 join results (cartesian product)\n        assert_eq!(\n            result.changes.len(),\n            6,\n            \"Should produce 6 joins (3 left rows × 2 right rows)\"\n        );\n\n        // All should have weight 1\n        for (_, weight) in &result.changes {\n            assert_eq!(*weight, 1);\n        }\n\n        // SECOND COMMIT: Delete one instance from left\n        let mut left_delta2 = Delta::new();\n        left_delta2.delete(2, vec![Value::from_i64(10), Value::Text(\"A\".into())]);\n\n        let delta_pair2 = DeltaPair::new(left_delta2, Delta::new());\n        let result2 = pager\n            .io\n            .block(|| join.commit(delta_pair2.clone(), &mut cursors))\n            .unwrap();\n\n        // Should produce 2 retractions (1 deleted left row × 2 right rows)\n        assert_eq!(\n            result2.changes.len(),\n            2,\n            \"Should produce 2 retractions when deleting 1 of 3 identical left rows\"\n        );\n\n        for (_, weight) in &result2.changes {\n            assert_eq!(*weight, -1, \"Should be retractions\");\n        }\n    }\n\n    #[test]\n    fn test_join_produces_all_expected_results() {\n        // Test that a join produces ALL expected output rows\n        // This reproduces the issue where only 1 of 3 expected rows appears in the final result\n\n        // Create a join operator similar to: SELECT u.name, o.quantity FROM users u JOIN orders o ON u.id = o.user_id\n        let mut join = JoinOperator::new(\n            0,\n            JoinType::Inner,\n            vec![0], // Join on first column (id)\n            vec![0], // Join on first column (user_id)\n            vec![\"id\".to_string(), \"name\".to_string()],\n            vec![\n                \"user_id\".to_string(),\n                \"product_id\".to_string(),\n                \"quantity\".to_string(),\n            ],\n        )\n        .unwrap();\n\n        // Create test data matching the example that fails:\n        // users: (1, 'Alice'), (2, 'Bob')\n        // orders: (1, 5), (1, 3), (2, 7)  -- user_id, quantity\n        let left_delta = Delta {\n            changes: vec![\n                (\n                    HashableRow::new(\n                        1,\n                        vec![Value::from_i64(1), Value::Text(Text::from(\"Alice\"))],\n                    ),\n                    1,\n                ),\n                (\n                    HashableRow::new(2, vec![Value::from_i64(2), Value::Text(Text::from(\"Bob\"))]),\n                    1,\n                ),\n            ],\n        };\n\n        // Orders: Alice has 2 orders, Bob has 1\n        let right_delta = Delta {\n            changes: vec![\n                (\n                    HashableRow::new(\n                        1,\n                        vec![Value::from_i64(1), Value::from_i64(100), Value::from_i64(5)],\n                    ),\n                    1,\n                ),\n                (\n                    HashableRow::new(\n                        2,\n                        vec![Value::from_i64(1), Value::from_i64(101), Value::from_i64(3)],\n                    ),\n                    1,\n                ),\n                (\n                    HashableRow::new(\n                        3,\n                        vec![Value::from_i64(2), Value::from_i64(100), Value::from_i64(7)],\n                    ),\n                    1,\n                ),\n            ],\n        };\n\n        // Evaluate the join\n        let delta_pair = DeltaPair::new(left_delta, right_delta);\n        let mut state = EvalState::Init { deltas: delta_pair };\n\n        let (pager, table_root, index_root) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_root, 5);\n        let index_def = create_dbsp_state_index(index_root);\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_root, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        let result = pager\n            .io\n            .block(|| join.eval(&mut state, &mut cursors))\n            .unwrap();\n\n        // Should produce 3 results: Alice with 2 orders, Bob with 1 order\n        assert_eq!(\n            result.changes.len(),\n            3,\n            \"Should produce 3 joined rows (Alice×2 + Bob×1)\"\n        );\n\n        // Verify the actual content of the results\n        let mut expected_results = HashSet::default();\n        // Expected: (Alice, 5), (Alice, 3), (Bob, 7)\n        expected_results.insert((\"Alice\".to_string(), 5));\n        expected_results.insert((\"Alice\".to_string(), 3));\n        expected_results.insert((\"Bob\".to_string(), 7));\n\n        let mut actual_results = HashSet::default();\n        for (row, weight) in &result.changes {\n            assert_eq!(*weight, 1, \"All results should have weight 1\");\n\n            // Extract name (column 1 from left) and quantity (column 3 from right)\n            let name = match &row.values[1] {\n                Value::Text(t) => t.as_str().to_string(),\n                _ => panic!(\"Expected text value for name\"),\n            };\n            let quantity = match &row.values[4] {\n                Value::Numeric(Numeric::Integer(q)) => *q,\n                _ => panic!(\"Expected integer value for quantity\"),\n            };\n\n            actual_results.insert((name, quantity));\n        }\n\n        assert_eq!(\n            expected_results, actual_results,\n            \"Join should produce all expected results. Expected: {expected_results:?}, Got: {actual_results:?}\",\n        );\n\n        // Also verify that rowids are unique (this is important for btree storage)\n        let mut seen_rowids = HashSet::default();\n        for (row, _) in &result.changes {\n            let was_new = seen_rowids.insert(row.rowid);\n            assert!(was_new, \"Duplicate rowid found: {}. This would cause rows to overwrite each other in btree storage!\", row.rowid);\n        }\n    }\n\n    // Merge operator tests\n    use crate::incremental::merge_operator::{MergeOperator, UnionMode};\n\n    #[test]\n    fn test_merge_operator_basic() {\n        let (_pager, table_root_page_id, index_root_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(_pager.clone(), table_root_page_id, 5);\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        let index_cursor = BTreeCursor::new_index(_pager, index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        let mut merge_op = MergeOperator::new(\n            1,\n            UnionMode::All {\n                left_table: \"table1\".to_string(),\n                right_table: \"table2\".to_string(),\n            },\n        );\n\n        // Create two deltas\n        let mut left_delta = Delta::new();\n        left_delta.insert(1, vec![Value::from_i64(1)]);\n        left_delta.insert(2, vec![Value::from_i64(2)]);\n\n        let mut right_delta = Delta::new();\n        right_delta.insert(3, vec![Value::from_i64(3)]);\n        right_delta.insert(4, vec![Value::from_i64(4)]);\n\n        let delta_pair = DeltaPair::new(left_delta, right_delta);\n\n        // Evaluate merge\n        let result = merge_op.commit(delta_pair, &mut cursors).unwrap();\n\n        if let IOResult::Done(merged) = result {\n            // Should have all 4 entries\n            assert_eq!(merged.len(), 4);\n\n            // Check that all values are present\n            let values: Vec<i64> = merged\n                .changes\n                .iter()\n                .filter_map(|(row, weight)| {\n                    if *weight > 0 && !row.values.is_empty() {\n                        if let Value::Numeric(Numeric::Integer(n)) = &row.values[0] {\n                            Some(*n)\n                        } else {\n                            None\n                        }\n                    } else {\n                        None\n                    }\n                })\n                .collect();\n\n            assert!(values.contains(&1));\n            assert!(values.contains(&2));\n            assert!(values.contains(&3));\n            assert!(values.contains(&4));\n        } else {\n            panic!(\"Expected Done result\");\n        }\n    }\n\n    #[test]\n    fn test_merge_operator_stateful_distinct() {\n        let (_pager, table_root_page_id, index_root_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(_pager.clone(), table_root_page_id, 5);\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        let index_cursor = BTreeCursor::new_index(_pager, index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        // Test that UNION (distinct) properly deduplicates across multiple operations\n        let mut merge_op = MergeOperator::new(7, UnionMode::Distinct);\n\n        // First operation: insert values 1, 2, 3 from left and 2, 3, 4 from right\n        let mut left_delta1 = Delta::new();\n        left_delta1.insert(1, vec![Value::from_i64(1)]);\n        left_delta1.insert(2, vec![Value::from_i64(2)]);\n        left_delta1.insert(3, vec![Value::from_i64(3)]);\n\n        let mut right_delta1 = Delta::new();\n        right_delta1.insert(4, vec![Value::from_i64(2)]); // Duplicate value 2\n        right_delta1.insert(5, vec![Value::from_i64(3)]); // Duplicate value 3\n        right_delta1.insert(6, vec![Value::from_i64(4)]);\n\n        let result1 = merge_op\n            .commit(DeltaPair::new(left_delta1, right_delta1), &mut cursors)\n            .unwrap();\n        if let IOResult::Done(merged1) = result1 {\n            // Should have 4 unique values (1, 2, 3, 4)\n            // But 6 total entries (3 from left + 3 from right)\n            assert_eq!(merged1.len(), 6);\n\n            // Collect unique rowids - should be 4\n            let unique_rowids: HashSet<i64> =\n                merged1.changes.iter().map(|(row, _)| row.rowid).collect();\n            assert_eq!(\n                unique_rowids.len(),\n                4,\n                \"Should have 4 unique rowids for 4 unique values\"\n            );\n        } else {\n            panic!(\"Expected Done result\");\n        }\n\n        // Second operation: insert value 2 again from left, and value 5 from right\n        let mut left_delta2 = Delta::new();\n        left_delta2.insert(7, vec![Value::from_i64(2)]); // Duplicate of existing value\n\n        let mut right_delta2 = Delta::new();\n        right_delta2.insert(8, vec![Value::from_i64(5)]); // New value\n\n        let result2 = merge_op\n            .commit(DeltaPair::new(left_delta2, right_delta2), &mut cursors)\n            .unwrap();\n        if let IOResult::Done(merged2) = result2 {\n            assert_eq!(merged2.len(), 2, \"Should have 2 entries in delta\");\n\n            // Check that value 2 got the same rowid as before\n            let has_existing_rowid = merged2\n                .changes\n                .iter()\n                .any(|(row, _)| row.values == vec![Value::from_i64(2)] && row.rowid <= 4);\n            assert!(has_existing_rowid, \"Value 2 should reuse existing rowid\");\n\n            // Check that value 5 got a new rowid\n            let has_new_rowid = merged2\n                .changes\n                .iter()\n                .any(|(row, _)| row.values == vec![Value::from_i64(5)] && row.rowid > 4);\n            assert!(has_new_rowid, \"Value 5 should get a new rowid\");\n        } else {\n            panic!(\"Expected Done result\");\n        }\n    }\n\n    #[test]\n    fn test_merge_operator_single_sided_inputs_union_all() {\n        let (_pager, table_root_page_id, index_root_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(_pager.clone(), table_root_page_id, 5);\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        let index_cursor = BTreeCursor::new_index(_pager, index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        // Test UNION ALL with inputs coming from only one side at a time\n        let mut merge_op = MergeOperator::new(\n            10,\n            UnionMode::All {\n                left_table: \"orders\".to_string(),\n                right_table: \"archived_orders\".to_string(),\n            },\n        );\n\n        // First: only left side (orders) has data\n        let mut left_delta1 = Delta::new();\n        left_delta1.insert(100, vec![Value::from_i64(1001)]);\n        left_delta1.insert(101, vec![Value::from_i64(1002)]);\n\n        let right_delta1 = Delta::new(); // Empty right side\n\n        let result1 = merge_op\n            .commit(DeltaPair::new(left_delta1, right_delta1), &mut cursors)\n            .unwrap();\n\n        let first_rowids = if let IOResult::Done(ref merged1) = result1 {\n            assert_eq!(merged1.len(), 2, \"Should have 2 entries from left only\");\n            merged1\n                .changes\n                .iter()\n                .map(|(row, _)| row.rowid)\n                .collect::<Vec<_>>()\n        } else {\n            panic!(\"Expected Done result\");\n        };\n\n        // Second: only right side (archived_orders) has data\n        let left_delta2 = Delta::new(); // Empty left side\n\n        let mut right_delta2 = Delta::new();\n        right_delta2.insert(100, vec![Value::from_i64(2001)]); // Same rowid as left, different table\n        right_delta2.insert(102, vec![Value::from_i64(2002)]);\n\n        let result2 = merge_op\n            .commit(DeltaPair::new(left_delta2, right_delta2), &mut cursors)\n            .unwrap();\n        let second_result_rowid_100 = if let IOResult::Done(ref merged2) = result2 {\n            assert_eq!(merged2.len(), 2, \"Should have 2 entries from right only\");\n\n            // Rowids should be different from the left side even though original rowid 100 is the same\n            let second_rowids: Vec<i64> =\n                merged2.changes.iter().map(|(row, _)| row.rowid).collect();\n            for rowid in &second_rowids {\n                assert!(\n                    !first_rowids.contains(rowid),\n                    \"Right side rowids should be different from left side rowids\"\n                );\n            }\n\n            // Save rowid for archived_orders.100\n            merged2\n                .changes\n                .iter()\n                .find(|(row, _)| row.values == vec![Value::from_i64(2001)])\n                .map(|(row, _)| row.rowid)\n                .unwrap()\n        } else {\n            panic!(\"Expected Done result\");\n        };\n\n        // Third: left side again with same rowids as before\n        let mut left_delta3 = Delta::new();\n        left_delta3.insert(100, vec![Value::from_i64(1003)]); // Same rowid 100 from orders\n        left_delta3.insert(101, vec![Value::from_i64(1004)]); // Same rowid 101 from orders\n\n        let right_delta3 = Delta::new(); // Empty right side\n\n        let result3 = merge_op\n            .commit(DeltaPair::new(left_delta3, right_delta3), &mut cursors)\n            .unwrap();\n        if let IOResult::Done(merged3) = result3 {\n            assert_eq!(merged3.len(), 2, \"Should have 2 entries from left\");\n\n            // Should get the same assigned rowids as the first operation\n            let third_rowids: Vec<i64> = merged3.changes.iter().map(|(row, _)| row.rowid).collect();\n            assert_eq!(\n                first_rowids, third_rowids,\n                \"Same (table, rowid) pairs should get same assigned rowids\"\n            );\n        } else {\n            panic!(\"Expected Done result\");\n        }\n\n        // Fourth: right side again with rowid 100\n        let left_delta4 = Delta::new(); // Empty left side\n\n        let mut right_delta4 = Delta::new();\n        right_delta4.insert(100, vec![Value::from_i64(2003)]); // Same rowid 100 from archived_orders\n\n        let result4 = merge_op\n            .commit(DeltaPair::new(left_delta4, right_delta4), &mut cursors)\n            .unwrap();\n        if let IOResult::Done(merged4) = result4 {\n            assert_eq!(merged4.len(), 1, \"Should have 1 entry from right\");\n\n            // Should get same assigned rowid as second operation for archived_orders.100\n            let fourth_rowid = merged4.changes[0].0.rowid;\n            assert_eq!(\n                fourth_rowid, second_result_rowid_100,\n                \"archived_orders rowid 100 should consistently map to same assigned rowid\"\n            );\n        } else {\n            panic!(\"Expected Done result\");\n        }\n    }\n\n    #[test]\n    fn test_merge_operator_both_sides_empty() {\n        let (_pager, table_root_page_id, index_root_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(_pager.clone(), table_root_page_id, 5);\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        let index_cursor = BTreeCursor::new_index(_pager, index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        // Test that both sides being empty works correctly\n        let mut merge_op = MergeOperator::new(\n            12,\n            UnionMode::All {\n                left_table: \"t1\".to_string(),\n                right_table: \"t2\".to_string(),\n            },\n        );\n\n        // First: insert some data to establish state\n        let mut left_delta1 = Delta::new();\n        left_delta1.insert(1, vec![Value::from_i64(100)]);\n        let mut right_delta1 = Delta::new();\n        right_delta1.insert(1, vec![Value::from_i64(200)]);\n\n        let result1 = merge_op\n            .commit(DeltaPair::new(left_delta1, right_delta1), &mut cursors)\n            .unwrap();\n        let original_t1_rowid = if let IOResult::Done(ref merged1) = result1 {\n            assert_eq!(merged1.len(), 2, \"Should have 2 entries initially\");\n            // Save the rowid for t1.rowid=1\n            merged1\n                .changes\n                .iter()\n                .find(|(row, _)| row.values == vec![Value::from_i64(100)])\n                .map(|(row, _)| row.rowid)\n                .unwrap()\n        } else {\n            panic!(\"Expected Done result\");\n        };\n\n        // Second: both sides empty - should produce empty output\n        let empty_left = Delta::new();\n        let empty_right = Delta::new();\n\n        let result2 = merge_op\n            .commit(DeltaPair::new(empty_left, empty_right), &mut cursors)\n            .unwrap();\n        if let IOResult::Done(merged2) = result2 {\n            assert_eq!(\n                merged2.len(),\n                0,\n                \"Both empty sides should produce empty output\"\n            );\n        } else {\n            panic!(\"Expected Done result\");\n        }\n\n        // Third: add more data to verify state is still intact\n        let mut left_delta3 = Delta::new();\n        left_delta3.insert(1, vec![Value::from_i64(101)]); // Same rowid as before\n        let right_delta3 = Delta::new();\n\n        let result3 = merge_op\n            .commit(DeltaPair::new(left_delta3, right_delta3), &mut cursors)\n            .unwrap();\n        if let IOResult::Done(merged3) = result3 {\n            assert_eq!(merged3.len(), 1, \"Should have 1 entry\");\n            // Should reuse the same assigned rowid for t1.rowid=1\n            let rowid = merged3.changes[0].0.rowid;\n            assert_eq!(\n                rowid, original_t1_rowid,\n                \"Should maintain consistent rowid mapping after empty operation\"\n            );\n        } else {\n            panic!(\"Expected Done result\");\n        }\n    }\n\n    #[test]\n    fn test_aggregate_serialization_with_different_column_indices() {\n        // Test that aggregate state serialization correctly preserves column indices\n        // when multiple aggregates operate on different columns\n        let (pager, table_root_page_id, index_root_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_root_page_id, 5);\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        // Create first operator with SUM(col1), MIN(col3) GROUP BY col0\n        let mut agg1 = AggregateOperator::new(\n            1,\n            vec![0],\n            vec![AggregateFunction::Sum(1), AggregateFunction::Min(3)],\n            vec![\n                \"group\".to_string(),\n                \"val1\".to_string(),\n                \"val2\".to_string(),\n                \"val3\".to_string(),\n            ],\n        )\n        .unwrap();\n\n        // Add initial data\n        let mut delta = Delta::new();\n        delta.insert(\n            1,\n            vec![\n                Value::Text(\"A\".into()),\n                Value::from_i64(10),\n                Value::from_i64(100),\n                Value::from_i64(5),\n            ],\n        );\n        delta.insert(\n            2,\n            vec![\n                Value::Text(\"A\".into()),\n                Value::from_i64(15),\n                Value::from_i64(200),\n                Value::from_i64(3),\n            ],\n        );\n\n        let result1 = pager\n            .io\n            .block(|| agg1.commit((&delta).into(), &mut cursors))\n            .unwrap();\n\n        assert_eq!(result1.changes.len(), 1);\n        let (row1, _) = &result1.changes[0];\n        assert_eq!(row1.values[0], Value::Text(\"A\".into()));\n        assert_eq!(row1.values[1], Value::from_i64(25)); // SUM(val1) = 10 + 15\n        assert_eq!(row1.values[2], Value::from_i64(3)); // MIN(val3) = min(5, 3)\n\n        // Create operator with same ID but different column mappings: SUM(col3), MIN(col1)\n        let mut agg2 = AggregateOperator::new(\n            1, // Same operator_id\n            vec![0],\n            vec![AggregateFunction::Sum(3), AggregateFunction::Min(1)],\n            vec![\n                \"group\".to_string(),\n                \"val1\".to_string(),\n                \"val2\".to_string(),\n                \"val3\".to_string(),\n            ],\n        )\n        .unwrap();\n\n        // Process new data\n        let mut delta2 = Delta::new();\n        delta2.insert(\n            3,\n            vec![\n                Value::Text(\"A\".into()),\n                Value::from_i64(20),\n                Value::from_i64(300),\n                Value::from_i64(4),\n            ],\n        );\n\n        let result2 = pager\n            .io\n            .block(|| agg2.commit((&delta2).into(), &mut cursors))\n            .unwrap();\n\n        // Find the positive weight row for group A (the updated aggregate)\n        let row2 = result2\n            .changes\n            .iter()\n            .find(|(row, weight)| row.values[0] == Value::Text(\"A\".into()) && *weight > 0)\n            .expect(\"Should have a positive weight row for group A\");\n        let (row2, _) = row2;\n\n        // Verify that column indices are preserved correctly in serialization\n        // When agg2 processes the data with different column mappings:\n        // - It reads the existing state which has SUM(col1)=25 and MIN(col3)=3\n        // - For SUM(col3), there's no existing state, so it starts fresh: 4\n        // - For MIN(col1), there's no existing state, so it starts fresh: 20\n        assert_eq!(\n            row2.values[1],\n            Value::from_i64(4),\n            \"SUM(col3) should be 4 (new data only)\"\n        );\n        assert_eq!(\n            row2.values[2],\n            Value::from_i64(20),\n            \"MIN(col1) should be 20 (new data only)\"\n        );\n    }\n\n    #[test]\n    fn test_distinct_removes_duplicates() {\n        let (pager, table_root_page_id, index_root_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_root_page_id, 5);\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        // Create a DISTINCT operator that groups by all columns\n        let mut operator = AggregateOperator::new(\n            0,       // operator_id\n            vec![0], // group by column 0 (value)\n            vec![],  // Empty aggregates for plain DISTINCT\n            vec![\"value\".to_string()],\n        )\n        .unwrap();\n\n        // Create input with duplicates\n        let mut input = Delta::new();\n        input.insert(1, vec![Value::from_i64(100)]); // First 100\n        input.insert(2, vec![Value::from_i64(200)]); // First 200\n        input.insert(3, vec![Value::from_i64(100)]); // Duplicate 100\n        input.insert(4, vec![Value::from_i64(300)]); // First 300\n        input.insert(5, vec![Value::from_i64(200)]); // Duplicate 200\n        input.insert(6, vec![Value::from_i64(100)]); // Another duplicate 100\n\n        // Execute commit (for materialized views) instead of eval\n        let result = pager\n            .io\n            .block(|| operator.commit((&input).into(), &mut cursors))\n            .unwrap();\n\n        // Should have exactly 3 distinct values (100, 200, 300)\n        let distinct_values: HashSet<i64> = result\n            .changes\n            .iter()\n            .map(|(row, _weight)| match &row.values[0] {\n                Value::Numeric(Numeric::Integer(i)) => *i,\n                _ => panic!(\"Expected integer value\"),\n            })\n            .collect();\n\n        assert_eq!(\n            distinct_values.len(),\n            3,\n            \"Should have exactly 3 distinct values\"\n        );\n        assert!(distinct_values.contains(&100));\n        assert!(distinct_values.contains(&200));\n        assert!(distinct_values.contains(&300));\n\n        // All weights should be 1 (distinct normalizes weights)\n        for (_row, weight) in &result.changes {\n            assert_eq!(*weight, 1, \"DISTINCT should output weight 1 for all groups\");\n        }\n    }\n\n    #[test]\n    fn test_distinct_incremental_updates() {\n        let (pager, table_root_page_id, index_root_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_root_page_id, 5);\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        let mut operator = AggregateOperator::new(\n            0,\n            vec![0, 1], // group by both columns\n            vec![],     // Empty aggregates for plain DISTINCT\n            vec![\"category\".to_string(), \"value\".to_string()],\n        )\n        .unwrap();\n\n        // First batch: insert some values\n        let mut delta1 = Delta::new();\n        delta1.insert(1, vec![Value::Text(\"A\".into()), Value::from_i64(100)]);\n        delta1.insert(2, vec![Value::Text(\"B\".into()), Value::from_i64(200)]);\n        delta1.insert(3, vec![Value::Text(\"A\".into()), Value::from_i64(100)]); // Duplicate\n\n        // Commit first batch\n        let result1 = pager\n            .io\n            .block(|| operator.commit((&delta1).into(), &mut cursors))\n            .unwrap();\n\n        // Should have 2 distinct groups: (A,100) and (B,200)\n        assert_eq!(\n            result1.changes.len(),\n            2,\n            \"First commit should output 2 distinct groups\"\n        );\n\n        // Verify each group appears with weight +1\n        for (_row, weight) in &result1.changes {\n            assert_eq!(*weight, 1, \"New groups should have weight +1\");\n        }\n\n        // Second batch: delete one instance of (A,100) and add new group\n        let mut delta2 = Delta::new();\n        delta2.delete(1, vec![Value::Text(\"A\".into()), Value::from_i64(100)]);\n        delta2.insert(4, vec![Value::Text(\"C\".into()), Value::from_i64(300)]);\n\n        let result2 = pager\n            .io\n            .block(|| operator.commit((&delta2).into(), &mut cursors))\n            .unwrap();\n\n        // Should only output the new group (C,300) with weight +1\n        // (A,100) still exists (weight went from 2 to 1), so no output for it\n        assert_eq!(\n            result2.changes.len(),\n            1,\n            \"Second commit should only output new group\"\n        );\n\n        let (row, weight) = &result2.changes[0];\n        assert_eq!(*weight, 1);\n        assert_eq!(row.values[0], Value::Text(\"C\".into()));\n        assert_eq!(row.values[1], Value::from_i64(300));\n\n        // Third batch: delete last instance of (A,100)\n        let mut delta3 = Delta::new();\n        delta3.delete(3, vec![Value::Text(\"A\".into()), Value::from_i64(100)]);\n\n        let result3 = pager\n            .io\n            .block(|| operator.commit((&delta3).into(), &mut cursors))\n            .unwrap();\n\n        // Should output (A,100) with weight -1 (group disappeared)\n        assert_eq!(\n            result3.changes.len(),\n            1,\n            \"Third commit should output disappeared group\"\n        );\n\n        let (row, weight) = &result3.changes[0];\n        assert_eq!(*weight, -1, \"Disappeared group should have weight -1\");\n        assert_eq!(row.values[0], Value::Text(\"A\".into()));\n        assert_eq!(row.values[1], Value::from_i64(100))\n    }\n\n    #[test]\n    fn test_distinct_state_transitions() {\n        let (pager, table_root_page_id, index_root_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_root_page_id, 5);\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        // Test that DISTINCT correctly tracks state transitions (0 ↔ positive)\n        let mut operator = AggregateOperator::new(\n            0,\n            vec![0],\n            vec![], // Empty aggregates for plain DISTINCT\n            vec![\"value\".to_string()],\n        )\n        .unwrap();\n\n        // Insert value with weight 3\n        let mut delta1 = Delta::new();\n        for i in 1..=3 {\n            delta1.insert(i, vec![Value::from_i64(100)]);\n        }\n\n        let result1 = pager\n            .io\n            .block(|| operator.commit((&delta1).into(), &mut cursors))\n            .unwrap();\n\n        assert_eq!(result1.changes.len(), 1);\n        assert_eq!(result1.changes[0].1, 1, \"First appearance should output +1\");\n\n        // Remove 2 instances (weight goes from 3 to 1, still positive)\n        let mut delta2 = Delta::new();\n        for i in 1..=2 {\n            delta2.delete(i, vec![Value::from_i64(100)]);\n        }\n\n        let result2 = pager\n            .io\n            .block(|| operator.commit((&delta2).into(), &mut cursors))\n            .unwrap();\n\n        assert_eq!(result2.changes.len(), 0, \"No transition, no output\");\n\n        // Remove last instance (weight goes from 1 to 0)\n        let mut delta3 = Delta::new();\n        delta3.delete(3, vec![Value::from_i64(100)]);\n\n        let result3 = pager\n            .io\n            .block(|| operator.commit((&delta3).into(), &mut cursors))\n            .unwrap();\n\n        assert_eq!(result3.changes.len(), 1);\n        assert_eq!(result3.changes[0].1, -1, \"Disappearance should output -1\");\n\n        // Re-add the value (weight goes from 0 to 1)\n        let mut delta4 = Delta::new();\n        delta4.insert(4, vec![Value::from_i64(100)]);\n\n        let result4 = pager\n            .io\n            .block(|| operator.commit((&delta4).into(), &mut cursors))\n            .unwrap();\n\n        assert_eq!(result4.changes.len(), 1);\n        assert_eq!(result4.changes[0].1, 1, \"Reappearance should output +1\")\n    }\n\n    #[test]\n    fn test_distinct_persistence() {\n        let (pager, table_root_page_id, index_root_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_root_page_id, 5);\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        // First operator instance\n        let mut operator1 = AggregateOperator::new(\n            0,\n            vec![0],\n            vec![], // Empty aggregates for plain DISTINCT\n            vec![\"value\".to_string()],\n        )\n        .unwrap();\n\n        // Insert some values\n        let mut delta1 = Delta::new();\n        delta1.insert(1, vec![Value::from_i64(100)]);\n        delta1.insert(2, vec![Value::from_i64(100)]); // Duplicate\n        delta1.insert(3, vec![Value::from_i64(200)]);\n\n        let result1 = pager\n            .io\n            .block(|| operator1.commit((&delta1).into(), &mut cursors))\n            .unwrap();\n\n        // Should have 2 distinct values\n        assert_eq!(result1.changes.len(), 2, \"Should output 2 distinct values\");\n\n        // Create new operator instance with same ID (simulates restart)\n        // Create new cursors for the second operator\n        let table_cursor2 = BTreeCursor::new_table(pager.clone(), table_root_page_id, 5);\n        let index_cursor2 =\n            BTreeCursor::new_index(pager.clone(), index_root_page_id, &index_def, 4);\n        let mut cursors2 = DbspStateCursors::new(table_cursor2, index_cursor2);\n\n        let mut operator2 = AggregateOperator::new(\n            0, // Same operator_id\n            vec![0],\n            vec![], // Empty aggregates for plain DISTINCT\n            vec![\"value\".to_string()],\n        )\n        .unwrap();\n\n        // Add new value and delete existing (100 has weight 2, so it stays)\n        let mut delta2 = Delta::new();\n        delta2.insert(4, vec![Value::from_i64(300)]);\n        delta2.delete(1, vec![Value::from_i64(100)]); // Remove one of the 100s\n\n        let result2 = pager\n            .io\n            .block(|| operator2.commit((&delta2).into(), &mut cursors2))\n            .unwrap();\n\n        // Should only output the new value (300)\n        // 100 still exists (went from weight 2 to 1)\n        assert_eq!(result2.changes.len(), 1, \"Should only output new value\");\n        assert_eq!(result2.changes[0].1, 1, \"Should be insertion\");\n        assert_eq!(result2.changes[0].0.values[0], Value::from_i64(300));\n\n        // Now delete the last instance of 100\n        let mut delta3 = Delta::new();\n        delta3.delete(2, vec![Value::from_i64(100)]);\n\n        let result3 = pager\n            .io\n            .block(|| operator2.commit((&delta3).into(), &mut cursors2))\n            .unwrap();\n\n        // Should output deletion of 100\n        assert_eq!(result3.changes.len(), 1, \"Should output deletion\");\n        assert_eq!(result3.changes[0].1, -1, \"Should be deletion\");\n        assert_eq!(result3.changes[0].0.values[0], Value::from_i64(100));\n    }\n\n    #[test]\n    fn test_distinct_batch_with_multiple_groups() {\n        let (pager, table_root_page_id, index_root_page_id) = create_test_pager();\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_root_page_id, 5);\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        let mut operator = AggregateOperator::new(\n            0,\n            vec![0, 1], // group by category and value\n            vec![],     // Empty aggregates for plain DISTINCT\n            vec![\"category\".to_string(), \"value\".to_string()],\n        )\n        .unwrap();\n\n        // Create a complex batch with multiple groups and duplicates within each group\n        let mut delta = Delta::new();\n\n        // Group (A, 100): 3 instances\n        delta.insert(1, vec![Value::Text(\"A\".into()), Value::from_i64(100)]);\n        delta.insert(2, vec![Value::Text(\"A\".into()), Value::from_i64(100)]);\n        delta.insert(3, vec![Value::Text(\"A\".into()), Value::from_i64(100)]);\n\n        // Group (B, 200): 2 instances\n        delta.insert(4, vec![Value::Text(\"B\".into()), Value::from_i64(200)]);\n        delta.insert(5, vec![Value::Text(\"B\".into()), Value::from_i64(200)]);\n\n        // Group (A, 200): 1 instance\n        delta.insert(6, vec![Value::Text(\"A\".into()), Value::from_i64(200)]);\n\n        // Group (C, 100): 2 instances\n        delta.insert(7, vec![Value::Text(\"C\".into()), Value::from_i64(100)]);\n        delta.insert(8, vec![Value::Text(\"C\".into()), Value::from_i64(100)]);\n\n        // More instances of Group (A, 100)\n        delta.insert(9, vec![Value::Text(\"A\".into()), Value::from_i64(100)]);\n        delta.insert(10, vec![Value::Text(\"A\".into()), Value::from_i64(100)]);\n\n        // Group (B, 100): 1 instance\n        delta.insert(11, vec![Value::Text(\"B\".into()), Value::from_i64(100)]);\n\n        let result = pager\n            .io\n            .block(|| operator.commit((&delta).into(), &mut cursors))\n            .unwrap();\n\n        // Should have exactly 5 distinct groups:\n        // (A, 100), (A, 200), (B, 100), (B, 200), (C, 100)\n        assert_eq!(\n            result.changes.len(),\n            5,\n            \"Should have exactly 5 distinct groups\"\n        );\n\n        // All should have weight +1 (new groups appearing)\n        for (_row, weight) in &result.changes {\n            assert_eq!(*weight, 1, \"All groups should have weight +1\");\n        }\n\n        // Verify the distinct groups\n        let groups: HashSet<(String, i64)> = result\n            .changes\n            .iter()\n            .map(|(row, _)| {\n                let category = match &row.values[0] {\n                    Value::Text(s) => s.value.clone().into_owned(),\n                    _ => panic!(\"Expected text for category\"),\n                };\n                let value = match &row.values[1] {\n                    Value::Numeric(Numeric::Integer(i)) => *i,\n                    _ => panic!(\"Expected integer for value\"),\n                };\n                (category, value)\n            })\n            .collect();\n\n        assert!(groups.contains(&(\"A\".to_string(), 100)));\n        assert!(groups.contains(&(\"A\".to_string(), 200)));\n        assert!(groups.contains(&(\"B\".to_string(), 100)));\n        assert!(groups.contains(&(\"B\".to_string(), 200)));\n        assert!(groups.contains(&(\"C\".to_string(), 100)));\n    }\n\n    #[test]\n    fn test_multiple_distinct_aggregates_same_column() {\n        // Test that multiple DISTINCT aggregates on the same column don't interfere\n        let (pager, table_root_page_id, index_root_page_id) = create_test_pager();\n\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_root_page_id, 5);\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_root_page_id, &index_def, 4);\n\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        // Create operator with COUNT(DISTINCT value), SUM(DISTINCT value), AVG(DISTINCT value)\n        // all on the same column\n        let mut operator = AggregateOperator::new(\n            0,\n            vec![], // No group by - single group\n            vec![\n                AggregateFunction::CountDistinct(0), // COUNT(DISTINCT value)\n                AggregateFunction::SumDistinct(0),   // SUM(DISTINCT value)\n                AggregateFunction::AvgDistinct(0),   // AVG(DISTINCT value)\n            ],\n            vec![\"value\".to_string()],\n        )\n        .unwrap();\n\n        // Insert distinct values: 10, 20, 30 (each appearing multiple times)\n        let mut input = Delta::new();\n        input.insert(1, vec![Value::from_i64(10)]);\n        input.insert(2, vec![Value::from_i64(10)]); // duplicate\n        input.insert(3, vec![Value::from_i64(20)]);\n        input.insert(4, vec![Value::from_i64(20)]); // duplicate\n        input.insert(5, vec![Value::from_i64(30)]);\n        input.insert(6, vec![Value::from_i64(10)]); // duplicate\n\n        let output = pager\n            .io\n            .block(|| operator.commit((&input).into(), &mut cursors))\n            .unwrap();\n\n        // Should have exactly one output row (no group by)\n        assert_eq!(output.changes.len(), 1);\n        let (row, weight) = &output.changes[0];\n        assert_eq!(*weight, 1);\n\n        // Extract the aggregate values\n        let values = &row.values;\n        assert_eq!(values.len(), 3); // 3 aggregate values\n\n        // COUNT(DISTINCT value) should be 3 (distinct values: 10, 20, 30)\n        assert_eq!(values[0], Value::from_i64(3));\n\n        // SUM(DISTINCT value) should be 60 (10 + 20 + 30)\n        assert_eq!(values[1], Value::from_i64(60));\n\n        // AVG(DISTINCT value) should be 20.0 (60 / 3)\n        assert_eq!(values[2], Value::from_f64(20.0));\n    }\n\n    #[test]\n    fn test_count_distinct_with_deletions() {\n        let (pager, table_root_page_id, index_root_page_id) = create_test_pager();\n\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_root_page_id, 5);\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        let mut operator = AggregateOperator::new(\n            1,\n            vec![], // No GROUP BY\n            vec![AggregateFunction::CountDistinct(1)],\n            vec![\"id\".to_string(), \"value\".to_string()],\n        )\n        .unwrap();\n\n        // Insert 3 distinct values\n        let mut delta1 = Delta::new();\n        delta1.insert(1, vec![Value::from_i64(1), Value::from_i64(100)]);\n        delta1.insert(2, vec![Value::from_i64(2), Value::from_i64(200)]);\n        delta1.insert(3, vec![Value::from_i64(3), Value::from_i64(300)]);\n\n        let result1 = pager\n            .io\n            .block(|| operator.commit((&delta1).into(), &mut cursors))\n            .unwrap();\n\n        assert_eq!(result1.changes.len(), 1);\n        assert_eq!(result1.changes[0].1, 1);\n        assert_eq!(result1.changes[0].0.values[0], Value::from_i64(3));\n\n        // Delete one value\n        let mut delta2 = Delta::new();\n        delta2.delete(2, vec![Value::from_i64(2), Value::from_i64(200)]);\n\n        let result2 = pager\n            .io\n            .block(|| operator.commit((&delta2).into(), &mut cursors))\n            .unwrap();\n\n        assert_eq!(result2.changes.len(), 2);\n        let new_row = result2.changes.iter().find(|(_, w)| *w == 1).unwrap();\n        assert_eq!(new_row.0.values[0], Value::from_i64(2));\n    }\n\n    #[test]\n    fn test_sum_distinct_with_deletions() {\n        let (pager, table_root_page_id, index_root_page_id) = create_test_pager();\n\n        let table_cursor = BTreeCursor::new_table(pager.clone(), table_root_page_id, 5);\n        let index_def = create_dbsp_state_index(index_root_page_id);\n        let index_cursor = BTreeCursor::new_index(pager.clone(), index_root_page_id, &index_def, 4);\n        let mut cursors = DbspStateCursors::new(table_cursor, index_cursor);\n\n        let mut operator = AggregateOperator::new(\n            1,\n            vec![],\n            vec![AggregateFunction::SumDistinct(1)],\n            vec![\"id\".to_string(), \"value\".to_string()],\n        )\n        .unwrap();\n\n        // Insert values including a duplicate\n        let mut delta1 = Delta::new();\n        delta1.insert(1, vec![Value::from_i64(1), Value::from_i64(100)]);\n        delta1.insert(2, vec![Value::from_i64(2), Value::from_i64(200)]);\n        delta1.insert(3, vec![Value::from_i64(3), Value::from_i64(100)]); // Duplicate\n        delta1.insert(4, vec![Value::from_i64(4), Value::from_i64(300)]);\n\n        let result1 = pager\n            .io\n            .block(|| operator.commit((&delta1).into(), &mut cursors))\n            .unwrap();\n\n        assert_eq!(result1.changes.len(), 1);\n        assert_eq!(result1.changes[0].1, 1);\n        assert_eq!(result1.changes[0].0.values[0], Value::from_f64(600.0)); // 100 + 200 + 300\n\n        // Delete value 200\n        let mut delta2 = Delta::new();\n        delta2.delete(2, vec![Value::from_i64(2), Value::from_i64(200)]);\n\n        let result2 = pager\n            .io\n            .block(|| operator.commit((&delta2).into(), &mut cursors))\n            .unwrap();\n\n        assert_eq!(result2.changes.len(), 2);\n        let new_row = result2.changes.iter().find(|(_, w)| *w == 1).unwrap();\n        assert_eq!(new_row.0.values[0], Value::from_f64(400.0)); // 100 + 300\n    }\n}\n"
  },
  {
    "path": "core/incremental/persistence.rs",
    "content": "use crate::incremental::operator::{AggregateState, DbspStateCursors};\nuse crate::numeric::Numeric;\nuse crate::storage::btree::{BTreeCursor, BTreeKey, CursorTrait};\nuse crate::types::{IOResult, ImmutableRecord, SeekKey, SeekOp, SeekResult};\nuse crate::{return_if_io, LimboError, Result, Value};\n\n#[derive(Debug, Default)]\npub enum ReadRecord {\n    #[default]\n    GetRecord,\n    Done {\n        state: Box<Option<AggregateState>>,\n    },\n}\n\nimpl ReadRecord {\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    pub fn read_record(\n        &mut self,\n        key: SeekKey,\n        cursor: &mut BTreeCursor,\n    ) -> Result<IOResult<Option<AggregateState>>> {\n        loop {\n            match self {\n                ReadRecord::GetRecord => {\n                    let res = return_if_io!(cursor.seek(key.clone(), SeekOp::GE { eq_only: true }));\n                    if !matches!(res, SeekResult::Found) {\n                        *self = ReadRecord::Done {\n                            state: Box::new(None),\n                        };\n                    } else {\n                        let record = return_if_io!(cursor.record());\n                        let r = record.ok_or_else(|| {\n                            LimboError::InternalError(format!(\n                                \"Found key {key:?} in aggregate storage but could not read record\"\n                            ))\n                        })?;\n                        // The blob is in column 3: operator_id, zset_id, element_id, value, weight\n                        let blob = r.get_value(3)?.to_owned();\n\n                        let (state, _group_key) = match blob {\n                            Value::Blob(blob) => AggregateState::from_blob(&blob),\n                            Value::Null => {\n                                // For plain DISTINCT, we store null value and just track weight\n                                // Return a minimal state indicating existence\n                                Ok((AggregateState::default(), vec![]))\n                            }\n                            _ => Err(LimboError::ParseError(\n                                \"Value in aggregator not blob or null\".to_string(),\n                            )),\n                        }?;\n                        *self = ReadRecord::Done {\n                            state: Box::new(Some(state)),\n                        }\n                    }\n                }\n                ReadRecord::Done { state } => return Ok(IOResult::Done((**state).clone())),\n            }\n        }\n    }\n}\n\n#[derive(Debug, Default)]\npub enum WriteRow {\n    #[default]\n    GetRecord,\n    Delete {\n        rowid: i64,\n    },\n    DeleteIndex,\n    ComputeNewRowId {\n        final_weight: isize,\n    },\n    InsertNew {\n        rowid: i64,\n        final_weight: isize,\n    },\n    InsertIndex {\n        rowid: i64,\n    },\n    UpdateExisting {\n        rowid: i64,\n        final_weight: isize,\n    },\n    Done,\n}\n\nimpl WriteRow {\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    /// Write a row with weight management using index for lookups.\n    ///\n    /// # Arguments\n    /// * `cursors` - DBSP state cursors (table and index)\n    /// * `index_key` - The key to seek in the index\n    /// * `record_values` - The record values (without weight) to insert\n    /// * `weight` - The weight delta to apply\n    pub fn write_row(\n        &mut self,\n        cursors: &mut DbspStateCursors,\n        index_key: Vec<Value>,\n        record_values: Vec<Value>,\n        weight: isize,\n    ) -> Result<IOResult<()>> {\n        loop {\n            match self {\n                WriteRow::GetRecord => {\n                    // First, seek in the index to find if the row exists\n                    let index_values = index_key.clone();\n                    let index_record =\n                        ImmutableRecord::from_values(&index_values, index_values.len());\n\n                    let res = return_if_io!(cursors.index_cursor.seek(\n                        SeekKey::IndexKey(&index_record),\n                        SeekOp::GE { eq_only: true }\n                    ));\n\n                    if !matches!(res, SeekResult::Found) {\n                        // Row doesn't exist, we'll insert a new one\n                        *self = WriteRow::ComputeNewRowId {\n                            final_weight: weight,\n                        };\n                    } else {\n                        // Found in index, get the rowid it points to\n                        let rowid = return_if_io!(cursors.index_cursor.rowid());\n                        let rowid = rowid.ok_or_else(|| {\n                            LimboError::InternalError(\n                                \"Index cursor does not have a valid rowid\".to_string(),\n                            )\n                        })?;\n\n                        // Now seek in the table using the rowid\n                        let table_res = return_if_io!(cursors\n                            .table_cursor\n                            .seek(SeekKey::TableRowId(rowid), SeekOp::GE { eq_only: true }));\n\n                        if !matches!(table_res, SeekResult::Found) {\n                            return Err(LimboError::InternalError(\n                                \"Index points to non-existent table row\".to_string(),\n                            ));\n                        }\n\n                        let existing_record = return_if_io!(cursors.table_cursor.record());\n                        let r = existing_record.ok_or_else(|| {\n                            LimboError::InternalError(\n                                \"Found rowid in table but could not read record\".to_string(),\n                            )\n                        })?;\n                        let weight_opt = r.get_value_opt(4);\n\n                        // Weight is always the last value (column 4 in our 5-column structure)\n                        let existing_weight = match weight_opt {\n                            Some(val) => match val.to_owned() {\n                                Value::Numeric(Numeric::Integer(w)) => w as isize,\n                                _ => {\n                                    return Err(LimboError::InternalError(\n                                        \"Invalid weight value in storage\".to_string(),\n                                    ))\n                                }\n                            },\n                            None => {\n                                return Err(LimboError::InternalError(\n                                    \"No weight value found in storage\".to_string(),\n                                ))\n                            }\n                        };\n\n                        let final_weight = existing_weight + weight;\n                        if final_weight <= 0 {\n                            // Store index_key for later deletion of index entry\n                            *self = WriteRow::Delete { rowid }\n                        } else {\n                            // Store the rowid for update\n                            *self = WriteRow::UpdateExisting {\n                                rowid,\n                                final_weight,\n                            }\n                        }\n                    }\n                }\n                WriteRow::Delete { rowid } => {\n                    // Seek to the row and delete it\n                    return_if_io!(cursors\n                        .table_cursor\n                        .seek(SeekKey::TableRowId(*rowid), SeekOp::GE { eq_only: true }));\n\n                    // Transition to DeleteIndex to also delete the index entry\n                    *self = WriteRow::DeleteIndex;\n                    return_if_io!(cursors.table_cursor.delete());\n                }\n                WriteRow::DeleteIndex => {\n                    // Mark as Done before delete to avoid retry on I/O\n                    *self = WriteRow::Done;\n                    return_if_io!(cursors.index_cursor.delete());\n                }\n                WriteRow::ComputeNewRowId { final_weight } => {\n                    // Find the last rowid to compute the next one\n                    return_if_io!(cursors.table_cursor.last());\n                    let rowid = if cursors.table_cursor.is_empty() {\n                        1\n                    } else {\n                        match return_if_io!(cursors.table_cursor.rowid()) {\n                            Some(id) => id + 1,\n                            None => {\n                                return Err(LimboError::InternalError(\n                                    \"Table cursor has rows but no valid rowid\".to_string(),\n                                ))\n                            }\n                        }\n                    };\n\n                    // Transition to InsertNew with the computed rowid\n                    *self = WriteRow::InsertNew {\n                        rowid,\n                        final_weight: *final_weight,\n                    };\n                }\n                WriteRow::InsertNew {\n                    rowid,\n                    final_weight,\n                } => {\n                    let rowid_val = *rowid;\n                    let final_weight_val = *final_weight;\n\n                    // Seek to where we want to insert\n                    // The insert will position the cursor correctly\n                    return_if_io!(cursors.table_cursor.seek(\n                        SeekKey::TableRowId(rowid_val),\n                        SeekOp::GE { eq_only: false }\n                    ));\n\n                    // Build the complete record with weight\n                    // Use the function parameter record_values directly\n                    let mut complete_record = record_values.clone();\n                    complete_record.push(Value::from_i64(final_weight_val as i64));\n\n                    // Create an ImmutableRecord from the values\n                    let immutable_record =\n                        ImmutableRecord::from_values(&complete_record, complete_record.len());\n                    let btree_key = BTreeKey::new_table_rowid(rowid_val, Some(&immutable_record));\n\n                    // Transition to InsertIndex state after table insertion\n                    *self = WriteRow::InsertIndex { rowid: rowid_val };\n                    return_if_io!(cursors.table_cursor.insert(&btree_key));\n                }\n                WriteRow::InsertIndex { rowid } => {\n                    // For has_rowid indexes, we need to append the rowid to the index key\n                    // Use the function parameter index_key directly\n                    let mut index_values = index_key.clone();\n                    index_values.push(Value::from_i64(*rowid));\n\n                    // Create the index record with the rowid appended\n                    let index_record =\n                        ImmutableRecord::from_values(&index_values, index_values.len());\n                    let index_btree_key = BTreeKey::new_index_key(&index_record);\n\n                    // Mark as Done before index insert to avoid retry on I/O\n                    *self = WriteRow::Done;\n                    return_if_io!(cursors.index_cursor.insert(&index_btree_key));\n                }\n                WriteRow::UpdateExisting {\n                    rowid,\n                    final_weight,\n                } => {\n                    // Build the complete record with weight\n                    let mut complete_record = record_values.clone();\n                    complete_record.push(Value::from_i64(*final_weight as i64));\n\n                    // Create an ImmutableRecord from the values\n                    let immutable_record =\n                        ImmutableRecord::from_values(&complete_record, complete_record.len());\n                    let btree_key = BTreeKey::new_table_rowid(*rowid, Some(&immutable_record));\n\n                    // Mark as Done before insert to avoid retry on I/O\n                    *self = WriteRow::Done;\n                    // BTree insert with existing key will replace the old value\n                    return_if_io!(cursors.table_cursor.insert(&btree_key));\n                }\n                WriteRow::Done => {\n                    return Ok(IOResult::Done(()));\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "core/incremental/project_operator.rs",
    "content": "// Project operator for DBSP-style incremental computation\n// This operator projects/transforms columns in a relational stream\n\nuse crate::incremental::dbsp::{Delta, DeltaPair, HashableRow};\nuse crate::incremental::expr_compiler::CompiledExpression;\nuse crate::incremental::operator::{\n    ComputationTracker, DbspStateCursors, EvalState, IncrementalOperator,\n};\nuse crate::sync::Mutex;\nuse crate::sync::{atomic::Ordering, Arc};\nuse crate::types::IOResult;\nuse crate::{Connection, Database, Result, Value};\n\n#[derive(Debug, Clone)]\npub struct ProjectColumn {\n    /// Compiled expression (handles both trivial columns and complex expressions)\n    pub compiled: CompiledExpression,\n}\n\n/// Project operator - selects/transforms columns\n#[derive(Clone)]\npub struct ProjectOperator {\n    columns: Vec<ProjectColumn>,\n    input_column_names: Vec<String>,\n    output_column_names: Vec<String>,\n    tracker: Option<Arc<Mutex<ComputationTracker>>>,\n    // Internal in-memory connection for expression evaluation\n    // Programs are very dependent on having a connection, so give it one.\n    //\n    // We could in theory pass the current connection, but there are a host of problems with that.\n    // For example: during a write transaction, where views are usually updated, we have autocommit\n    // on. When the program we are executing calls Halt, it will try to commit the current\n    // transaction, which is absolutely incorrect.\n    //\n    // There are other ways to solve this, but a read-only connection to an empty in-memory\n    // database gives us the closest environment we need to execute expressions.\n    internal_conn: Arc<Connection>,\n}\n\nimpl std::fmt::Debug for ProjectOperator {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"ProjectOperator\")\n            .field(\"columns\", &self.columns)\n            .field(\"input_column_names\", &self.input_column_names)\n            .field(\"output_column_names\", &self.output_column_names)\n            .finish()\n    }\n}\n\nimpl ProjectOperator {\n    /// Create a ProjectOperator from pre-compiled expressions\n    pub fn from_compiled(\n        compiled_exprs: Vec<CompiledExpression>,\n        aliases: Vec<Option<String>>,\n        input_column_names: Vec<String>,\n        output_column_names: Vec<String>,\n    ) -> crate::Result<Self> {\n        // Set up internal connection for expression evaluation\n        let io = Arc::new(crate::MemoryIO::new());\n        let db = Database::open_file(io, \":memory:\")?;\n        let internal_conn = db.connect()?;\n        // Set to read-only mode and disable auto-commit since we're only evaluating expressions\n        internal_conn.set_query_only(true);\n        internal_conn.auto_commit.store(false, Ordering::SeqCst);\n\n        // Create ProjectColumn structs from compiled expressions\n        let columns: Vec<ProjectColumn> = compiled_exprs\n            .into_iter()\n            .zip(aliases)\n            .map(|(compiled, _alias)| ProjectColumn { compiled })\n            .collect();\n\n        Ok(Self {\n            columns,\n            input_column_names,\n            output_column_names,\n            tracker: None,\n            internal_conn,\n        })\n    }\n\n    fn project_values(&self, values: &[Value]) -> Vec<Value> {\n        let mut output = Vec::new();\n\n        for col in &self.columns {\n            // Use the internal connection's pager for expression evaluation\n            let internal_pager = self.internal_conn.pager.load().clone();\n\n            // Execute the compiled expression (handles both columns and complex expressions)\n            let result = col\n                .compiled\n                .execute(values, internal_pager)\n                .expect(\"Failed to execute compiled expression for the Project operator\");\n            output.push(result);\n        }\n\n        output\n    }\n}\n\nimpl IncrementalOperator for ProjectOperator {\n    fn eval(\n        &mut self,\n        state: &mut EvalState,\n        _cursors: &mut DbspStateCursors,\n    ) -> Result<IOResult<Delta>> {\n        let delta = match state {\n            EvalState::Init { deltas } => {\n                // Project operators only use left_delta, right_delta must be empty\n                assert!(\n                    deltas.right.is_empty(),\n                    \"ProjectOperator expects right_delta to be empty\"\n                );\n                std::mem::take(&mut deltas.left)\n            }\n            _ => unreachable!(\n                \"ProjectOperator doesn't execute the state machine. Should be in Init state\"\n            ),\n        };\n\n        let mut output_delta = Delta::new();\n\n        for (row, weight) in delta.changes {\n            if let Some(tracker) = &self.tracker {\n                tracker.lock().record_project();\n            }\n\n            let projected = self.project_values(&row.values);\n            let projected_row = HashableRow::new(row.rowid, projected);\n            output_delta.changes.push((projected_row, weight));\n        }\n\n        *state = EvalState::Done;\n        Ok(IOResult::Done(output_delta))\n    }\n\n    fn commit(\n        &mut self,\n        deltas: DeltaPair,\n        _cursors: &mut DbspStateCursors,\n    ) -> Result<IOResult<Delta>> {\n        // Project operator only uses left delta, right must be empty\n        assert!(\n            deltas.right.is_empty(),\n            \"ProjectOperator expects right delta to be empty in commit\"\n        );\n\n        let mut output_delta = Delta::new();\n\n        // Commit the delta to our internal state and build output\n        for (row, weight) in &deltas.left.changes {\n            if let Some(tracker) = &self.tracker {\n                tracker.lock().record_project();\n            }\n            let projected = self.project_values(&row.values);\n            let projected_row = HashableRow::new(row.rowid, projected);\n            output_delta.changes.push((projected_row, *weight));\n        }\n\n        Ok(crate::types::IOResult::Done(output_delta))\n    }\n\n    fn set_tracker(&mut self, tracker: Arc<Mutex<ComputationTracker>>) {\n        self.tracker = Some(tracker);\n    }\n}\n"
  },
  {
    "path": "core/incremental/view.rs",
    "content": "use super::compiler::{DbspCircuit, DbspCompiler, DeltaSet};\nuse super::dbsp::Delta;\nuse super::operator::ComputationTracker;\nuse crate::numeric::Numeric;\nuse crate::schema::{BTreeTable, Schema};\nuse crate::storage::btree::CursorTrait;\nuse crate::sync::Arc;\nuse crate::sync::Mutex;\nuse crate::translate::logical::LogicalPlanBuilder;\nuse crate::types::{IOResult, Value};\nuse crate::util::{extract_view_columns, ViewColumnSchema};\nuse crate::{return_if_io, LimboError, Pager, Result, Statement};\nuse rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};\nuse std::cell::RefCell;\nuse std::fmt;\nuse std::rc::Rc;\nuse turso_parser::ast;\nuse turso_parser::{\n    ast::{Cmd, Stmt},\n    parser::Parser,\n};\n\n/// State machine for populating a view from its source table\npub enum PopulateState {\n    /// Initial state - need to prepare the query\n    Start,\n    /// All tables that need to be populated\n    ProcessingAllTables {\n        queries: Vec<String>,\n        current_idx: usize,\n    },\n    /// Actively processing rows from the query\n    ProcessingOneTable {\n        queries: Vec<String>,\n        current_idx: usize,\n        stmt: Box<Statement>,\n        rows_processed: usize,\n        /// If we're in the middle of processing a row (merge_delta returned I/O)\n        pending_row: Option<(i64, Vec<Value>)>, // (rowid, values)\n    },\n    /// Population complete\n    Done,\n}\n\n// SAFETY: This needs to be audited for thread safety.\n// See: https://github.com/tursodatabase/turso/issues/1552\nunsafe impl Send for PopulateState {}\nunsafe impl Sync for PopulateState {}\ncrate::assert::assert_send_sync!(PopulateState);\n\n/// State machine for merge_delta to handle I/O operations\nimpl fmt::Debug for PopulateState {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match self {\n            PopulateState::Start => write!(f, \"Start\"),\n            PopulateState::ProcessingAllTables {\n                current_idx,\n                queries,\n            } => f\n                .debug_struct(\"ProcessingAllTables\")\n                .field(\"current_idx\", current_idx)\n                .field(\"num_queries\", &queries.len())\n                .finish(),\n            PopulateState::ProcessingOneTable {\n                current_idx,\n                rows_processed,\n                pending_row,\n                queries,\n                ..\n            } => f\n                .debug_struct(\"ProcessingOneTable\")\n                .field(\"current_idx\", current_idx)\n                .field(\"rows_processed\", rows_processed)\n                .field(\"has_pending\", &pending_row.is_some())\n                .field(\"total_queries\", &queries.len())\n                .finish(),\n            PopulateState::Done => write!(f, \"Done\"),\n        }\n    }\n}\n\n/// Per-connection transaction state for incremental views\n#[derive(Debug, Clone, Default)]\npub struct ViewTransactionState {\n    // Per-table deltas for uncommitted changes\n    // Maps table_name -> Delta for that table\n    // Using RefCell for interior mutability\n    table_deltas: RefCell<HashMap<String, Delta>>,\n}\n\nimpl ViewTransactionState {\n    /// Create a new transaction state\n    pub fn new() -> Self {\n        Self {\n            table_deltas: RefCell::new(HashMap::default()),\n        }\n    }\n\n    /// Insert a row into the delta for a specific table\n    pub fn insert(&self, table_name: &str, key: i64, values: Vec<Value>) {\n        let mut deltas = self.table_deltas.borrow_mut();\n        let delta = deltas.entry(table_name.to_string()).or_default();\n        delta.insert(key, values);\n    }\n\n    /// Delete a row from the delta for a specific table\n    pub fn delete(&self, table_name: &str, key: i64, values: Vec<Value>) {\n        let mut deltas = self.table_deltas.borrow_mut();\n        let delta = deltas.entry(table_name.to_string()).or_default();\n        delta.delete(key, values);\n    }\n\n    /// Clear all changes in the delta\n    pub fn clear(&self) {\n        self.table_deltas.borrow_mut().clear();\n    }\n\n    /// Get deltas organized by table\n    pub fn get_table_deltas(&self) -> HashMap<String, Delta> {\n        self.table_deltas.borrow().clone()\n    }\n\n    /// Check if the delta is empty\n    pub fn is_empty(&self) -> bool {\n        self.table_deltas.borrow().values().all(|d| d.is_empty())\n    }\n\n    /// Returns how many elements exist in the delta.\n    pub fn len(&self) -> usize {\n        self.table_deltas.borrow().values().map(|d| d.len()).sum()\n    }\n}\n\n/// Container for all view transaction states within a connection\n/// Provides interior mutability for the map of view states\n#[derive(Debug, Clone, Default)]\npub struct AllViewsTxState {\n    states: Rc<RefCell<HashMap<String, Arc<ViewTransactionState>>>>,\n}\n\n// SAFETY: This needs to be audited for thread safety.\n// See: https://github.com/tursodatabase/turso/issues/1552\nunsafe impl Send for AllViewsTxState {}\nunsafe impl Sync for AllViewsTxState {}\ncrate::assert::assert_send_sync!(AllViewsTxState);\n\nimpl AllViewsTxState {\n    /// Create a new container for view transaction states\n    pub fn new() -> Self {\n        Self {\n            states: Rc::new(RefCell::new(HashMap::default())),\n        }\n    }\n\n    /// Get or create a transaction state for a view\n    #[allow(clippy::arc_with_non_send_sync)]\n    pub fn get_or_create(&self, view_name: &str) -> Arc<ViewTransactionState> {\n        let mut states = self.states.borrow_mut();\n        // ViewTransactionState uses RefCell (not Sync), but AllViewsTxState is\n        // single-threaded (Rc-based). Arc is used for shared ownership, not\n        // cross-thread sharing.\n        states\n            .entry(view_name.to_string())\n            .or_insert_with(|| Arc::new(ViewTransactionState::new()))\n            .clone()\n    }\n\n    /// Get a transaction state for a view if it exists\n    pub fn get(&self, view_name: &str) -> Option<Arc<ViewTransactionState>> {\n        self.states.borrow().get(view_name).cloned()\n    }\n\n    /// Clear all transaction states\n    pub fn clear(&self) {\n        self.states.borrow_mut().clear();\n    }\n\n    /// Check if there are no transaction states\n    pub fn is_empty(&self) -> bool {\n        self.states.borrow().is_empty()\n    }\n\n    /// Get all view names that have transaction states\n    pub fn get_view_names(&self) -> Vec<String> {\n        self.states.borrow().keys().cloned().collect()\n    }\n}\n\n/// Incremental view that maintains its state through a DBSP circuit\n///\n/// This version keeps everything in-memory. This is acceptable for small views, since DBSP\n/// doesn't have to track the history of changes. Still for very large views (think of the result\n/// of create view v as select * from tbl where x > 1; and that having 1B values.\n///\n/// We should have a version of this that materializes the results. Materializing will also be good\n/// for large aggregations, because then we don't have to re-compute when opening the database\n/// again.\n///\n/// Uses DBSP circuits for incremental computation.\n#[derive(Debug)]\npub struct IncrementalView {\n    name: String,\n    // The SELECT statement that defines how to transform input data\n    pub select_stmt: ast::Select,\n\n    // DBSP circuit that encapsulates the computation\n    circuit: DbspCircuit,\n\n    // All tables referenced by this view (from FROM clause and JOINs)\n    referenced_tables: Vec<Arc<BTreeTable>>,\n    // Mapping from table aliases to actual table names (e.g., \"c\" -> \"customers\")\n    table_aliases: HashMap<String, String>,\n    // Mapping from table name to fully qualified name (e.g., \"customers\" -> \"main.customers\")\n    // This preserves database qualification from the original query\n    qualified_table_names: HashMap<String, String>,\n    // WHERE conditions for each table (accumulated from all occurrences)\n    // Multiple conditions from UNION branches or duplicate references are stored as a vector\n    table_conditions: HashMap<String, Vec<Option<ast::Expr>>>,\n    // The view's column schema with table relationships\n    pub column_schema: ViewColumnSchema,\n    // State machine for population\n    populate_state: PopulateState,\n    // Computation tracker for statistics\n    // We will use this one day to export rows_read, but for now, will just test that we're doing the expected amount of compute\n    #[cfg_attr(not(test), allow(dead_code))]\n    pub tracker: Arc<Mutex<ComputationTracker>>,\n    // Root page of the btree storing the materialized state (0 for unmaterialized)\n    root_page: i64,\n}\n\n// SAFETY: This needs to be audited for thread safety.\n// See: https://github.com/tursodatabase/turso/issues/1552\nunsafe impl Send for IncrementalView {}\nunsafe impl Sync for IncrementalView {}\ncrate::assert::assert_send_sync!(IncrementalView);\n\nimpl IncrementalView {\n    /// Try to compile the SELECT statement into a DBSP circuit\n    fn try_compile_circuit(\n        select: &ast::Select,\n        schema: &Schema,\n        main_data_root: i64,\n        internal_state_root: i64,\n        internal_state_index_root: i64,\n    ) -> Result<DbspCircuit> {\n        // Build the logical plan from the SELECT statement\n        let mut builder = LogicalPlanBuilder::new(schema);\n        // Convert Select to a Stmt for the builder\n        let stmt = ast::Stmt::Select(select.clone());\n        let logical_plan = builder.build_statement(&stmt)?;\n\n        // Compile the logical plan to a DBSP circuit with the storage roots\n        let compiler = DbspCompiler::new(\n            main_data_root,\n            internal_state_root,\n            internal_state_index_root,\n        );\n        let circuit = compiler.compile(&logical_plan)?;\n\n        Ok(circuit)\n    }\n\n    /// Get an iterator over column names, using enumerated naming for unnamed columns\n    pub fn column_names(&self) -> impl Iterator<Item = String> + '_ {\n        self.column_schema\n            .columns\n            .iter()\n            .enumerate()\n            .map(|(i, vc)| {\n                vc.column\n                    .name\n                    .clone()\n                    .unwrap_or_else(|| format!(\"column{}\", i + 1))\n            })\n    }\n\n    /// Check if this view has the same SQL definition as the provided SQL string\n    pub fn has_same_sql(&self, sql: &str) -> bool {\n        // Parse the SQL to extract just the SELECT statement\n        if let Ok(Some(Cmd::Stmt(Stmt::CreateMaterializedView { select, .. }))) =\n            Parser::new(sql.as_bytes()).next_cmd()\n        {\n            // Compare the SELECT statements as SQL strings\n            return self.select_stmt == select;\n        }\n        false\n    }\n\n    /// Validate a SELECT statement and extract the columns it would produce\n    /// This is used during CREATE MATERIALIZED VIEW to validate the view before storing it\n    pub fn validate_and_extract_columns(\n        select: &ast::Select,\n        schema: &Schema,\n    ) -> Result<ViewColumnSchema> {\n        crate::util::validate_select_for_unsupported_features(select)?;\n        // Use the shared function to extract columns with full table context\n        extract_view_columns(select, schema)\n    }\n\n    pub fn from_sql(\n        sql: &str,\n        schema: &Schema,\n        main_data_root: i64,\n        internal_state_root: i64,\n        internal_state_index_root: i64,\n    ) -> Result<Self> {\n        let mut parser = Parser::new(sql.as_bytes());\n        let cmd = parser.next_cmd()?;\n        let cmd = cmd.expect(\"View is an empty statement\");\n        match cmd {\n            Cmd::Stmt(Stmt::CreateMaterializedView {\n                if_not_exists: _,\n                view_name,\n                columns: _,\n                select,\n            }) => IncrementalView::from_stmt(\n                view_name,\n                select,\n                schema,\n                main_data_root,\n                internal_state_root,\n                internal_state_index_root,\n            ),\n            _ => Err(LimboError::ParseError(format!(\n                \"View is not a CREATE MATERIALIZED VIEW statement: {sql}\"\n            ))),\n        }\n    }\n\n    pub fn from_stmt(\n        view_name: ast::QualifiedName,\n        select: ast::Select,\n        schema: &Schema,\n        main_data_root: i64,\n        internal_state_root: i64,\n        internal_state_index_root: i64,\n    ) -> Result<Self> {\n        let name = view_name.name.as_str().to_string();\n\n        // Extract output columns using the shared function\n        let column_schema = extract_view_columns(&select, schema)?;\n\n        let mut referenced_tables = Vec::new();\n        let mut table_aliases = HashMap::default();\n        let mut qualified_table_names = HashMap::default();\n        let mut table_conditions = HashMap::default();\n        Self::extract_all_tables(\n            &select,\n            schema,\n            &mut referenced_tables,\n            &mut table_aliases,\n            &mut qualified_table_names,\n            &mut table_conditions,\n        )?;\n\n        Self::new(\n            name,\n            select.clone(),\n            referenced_tables,\n            table_aliases,\n            qualified_table_names,\n            table_conditions,\n            column_schema,\n            schema,\n            main_data_root,\n            internal_state_root,\n            internal_state_index_root,\n        )\n    }\n\n    #[allow(clippy::too_many_arguments)]\n    pub fn new(\n        name: String,\n        select_stmt: ast::Select,\n        referenced_tables: Vec<Arc<BTreeTable>>,\n        table_aliases: HashMap<String, String>,\n        qualified_table_names: HashMap<String, String>,\n        table_conditions: HashMap<String, Vec<Option<ast::Expr>>>,\n        column_schema: ViewColumnSchema,\n        schema: &Schema,\n        main_data_root: i64,\n        internal_state_root: i64,\n        internal_state_index_root: i64,\n    ) -> Result<Self> {\n        // Create the tracker that will be shared by all operators\n        let tracker = Arc::new(Mutex::new(ComputationTracker::new()));\n\n        // Compile the SELECT statement into a DBSP circuit\n        let circuit = Self::try_compile_circuit(\n            &select_stmt,\n            schema,\n            main_data_root,\n            internal_state_root,\n            internal_state_index_root,\n        )?;\n\n        Ok(Self {\n            name,\n            select_stmt,\n            circuit,\n            referenced_tables,\n            table_aliases,\n            qualified_table_names,\n            table_conditions,\n            column_schema,\n            populate_state: PopulateState::Start,\n            tracker,\n            root_page: main_data_root,\n        })\n    }\n\n    pub fn name(&self) -> &str {\n        &self.name\n    }\n\n    /// Execute the circuit with uncommitted changes to get processed delta\n    pub fn execute_with_uncommitted(\n        &mut self,\n        uncommitted: DeltaSet,\n        pager: Arc<Pager>,\n        execute_state: &mut crate::incremental::compiler::ExecuteState,\n    ) -> crate::Result<crate::types::IOResult<Delta>> {\n        // Initialize execute_state with the input data\n        *execute_state = crate::incremental::compiler::ExecuteState::Init {\n            input_data: uncommitted,\n        };\n        self.circuit.execute(pager, execute_state)\n    }\n\n    /// Get the root page for this materialized view's btree\n    pub fn get_root_page(&self) -> i64 {\n        self.root_page\n    }\n\n    /// Get all table names referenced by this view\n    pub fn get_referenced_table_names(&self) -> Vec<String> {\n        self.referenced_tables\n            .iter()\n            .map(|t| t.name.clone())\n            .collect()\n    }\n\n    /// Get all tables referenced by this view\n    pub fn get_referenced_tables(&self) -> Vec<Arc<BTreeTable>> {\n        self.referenced_tables.clone()\n    }\n\n    /// Process a single table reference from a FROM or JOIN clause\n    fn process_table_reference(\n        name: &ast::QualifiedName,\n        alias: &Option<ast::As>,\n        schema: &Schema,\n        table_map: &mut HashMap<String, Arc<BTreeTable>>,\n        aliases: &mut HashMap<String, String>,\n        qualified_names: &mut HashMap<String, String>,\n        cte_names: &HashSet<String>,\n    ) -> Result<()> {\n        let table_name = name.name.as_str();\n\n        // Build the fully qualified name\n        let qualified_name = if let Some(ref db) = name.db_name {\n            format!(\"{db}.{table_name}\")\n        } else {\n            table_name.to_string()\n        };\n\n        // Skip CTEs - they're not real tables\n        if !cte_names.contains(table_name) {\n            if let Some(table) = schema.get_btree_table(table_name) {\n                table_map.insert(table_name.to_string(), table);\n                qualified_names.insert(table_name.to_string(), qualified_name);\n\n                // Store the alias mapping if there is an alias\n                if let Some(alias_enum) = alias {\n                    let alias_name = match alias_enum {\n                        ast::As::As(name) | ast::As::Elided(name) => name.as_str(),\n                    };\n                    aliases.insert(alias_name.to_string(), table_name.to_string());\n                }\n            } else {\n                return Err(LimboError::ParseError(format!(\n                    \"Table '{table_name}' not found in schema\"\n                )));\n            }\n        }\n        Ok(())\n    }\n\n    fn extract_one_statement(\n        select: &ast::OneSelect,\n        schema: &Schema,\n        table_map: &mut HashMap<String, Arc<BTreeTable>>,\n        aliases: &mut HashMap<String, String>,\n        qualified_names: &mut HashMap<String, String>,\n        table_conditions: &mut HashMap<String, Vec<Option<ast::Expr>>>,\n        cte_names: &HashSet<String>,\n    ) -> Result<()> {\n        if let ast::OneSelect::Select {\n            from: Some(ref from),\n            ..\n        } = select\n        {\n            // Get the main table from FROM clause\n            if let ast::SelectTable::Table(name, alias, _) = from.select.as_ref() {\n                Self::process_table_reference(\n                    name,\n                    alias,\n                    schema,\n                    table_map,\n                    aliases,\n                    qualified_names,\n                    cte_names,\n                )?;\n            }\n\n            // Get all tables from JOIN clauses\n            for join in &from.joins {\n                if let ast::SelectTable::Table(name, alias, _) = join.table.as_ref() {\n                    Self::process_table_reference(\n                        name,\n                        alias,\n                        schema,\n                        table_map,\n                        aliases,\n                        qualified_names,\n                        cte_names,\n                    )?;\n                }\n            }\n        }\n        // Extract WHERE conditions for this SELECT\n        let where_expr = if let ast::OneSelect::Select {\n            where_clause: Some(ref where_expr),\n            ..\n        } = select\n        {\n            Some(where_expr.as_ref().clone())\n        } else {\n            None\n        };\n\n        // Ensure all tables have an entry in table_conditions (even if empty)\n        for table_name in table_map.keys() {\n            table_conditions.entry(table_name.clone()).or_default();\n        }\n\n        // Extract and store table-specific conditions from the WHERE clause\n        if let Some(ref where_expr) = where_expr {\n            for table_name in table_map.keys() {\n                let all_tables: Vec<String> = table_map.keys().cloned().collect();\n                let table_specific_condition = Self::extract_conditions_for_table(\n                    where_expr,\n                    table_name,\n                    aliases,\n                    &all_tables,\n                    schema,\n                );\n                // Only add if there's actually a condition for this table\n                if let Some(condition) = table_specific_condition {\n                    let conditions = table_conditions.get_mut(table_name).ok_or_else(|| {\n                        LimboError::InternalError(\n                            \"table_conditions should have entry for table_name\".to_string(),\n                        )\n                    })?;\n                    conditions.push(Some(condition));\n                }\n            }\n        } else {\n            // No WHERE clause - push None for all tables in this SELECT. It is a way\n            // of signaling that we need all rows in the table. It is important we signal this\n            // explicitly, because the same table may appear in many conditions - some of which\n            // have filters that would otherwise be applied.\n            for table_name in table_map.keys() {\n                let conditions = table_conditions.get_mut(table_name).ok_or_else(|| {\n                    LimboError::InternalError(\n                        \"table_conditions should have entry for table_name\".to_string(),\n                    )\n                })?;\n                conditions.push(None);\n            }\n        }\n\n        Ok(())\n    }\n\n    /// Extract all tables and their aliases from the SELECT statement, handling CTEs\n    /// Deduplicates tables and accumulates WHERE conditions\n    fn extract_all_tables(\n        select: &ast::Select,\n        schema: &Schema,\n        tables: &mut Vec<Arc<BTreeTable>>,\n        aliases: &mut HashMap<String, String>,\n        qualified_names: &mut HashMap<String, String>,\n        table_conditions: &mut HashMap<String, Vec<Option<ast::Expr>>>,\n    ) -> Result<()> {\n        let mut table_map = HashMap::default();\n        Self::extract_all_tables_inner(\n            select,\n            schema,\n            &mut table_map,\n            aliases,\n            qualified_names,\n            table_conditions,\n            &HashSet::default(),\n        )?;\n\n        // Convert deduplicated table map to vector\n        for (_name, table) in table_map {\n            tables.push(table);\n        }\n\n        Ok(())\n    }\n\n    fn extract_all_tables_inner(\n        select: &ast::Select,\n        schema: &Schema,\n        table_map: &mut HashMap<String, Arc<BTreeTable>>,\n        aliases: &mut HashMap<String, String>,\n        qualified_names: &mut HashMap<String, String>,\n        table_conditions: &mut HashMap<String, Vec<Option<ast::Expr>>>,\n        parent_cte_names: &HashSet<String>,\n    ) -> Result<()> {\n        let mut cte_names = parent_cte_names.clone();\n\n        // First, collect CTE names and process any CTEs (WITH clauses)\n        if let Some(ref with) = select.with {\n            // First pass: collect all CTE names (needed for recursive CTEs)\n            for cte in &with.ctes {\n                cte_names.insert(cte.tbl_name.as_str().to_string());\n            }\n\n            // Second pass: extract tables from each CTE's SELECT statement\n            for cte in &with.ctes {\n                // Recursively extract tables from each CTE's SELECT statement\n                Self::extract_all_tables_inner(\n                    &cte.select,\n                    schema,\n                    table_map,\n                    aliases,\n                    qualified_names,\n                    table_conditions,\n                    &cte_names,\n                )?;\n            }\n        }\n\n        // Then process the main SELECT body\n        Self::extract_one_statement(\n            &select.body.select,\n            schema,\n            table_map,\n            aliases,\n            qualified_names,\n            table_conditions,\n            &cte_names,\n        )?;\n\n        // Process any compound selects (UNION, etc.)\n        for c in &select.body.compounds {\n            let ast::CompoundSelect { select, .. } = c;\n            Self::extract_one_statement(\n                select,\n                schema,\n                table_map,\n                aliases,\n                qualified_names,\n                table_conditions,\n                &cte_names,\n            )?;\n        }\n\n        Ok(())\n    }\n\n    /// Generate SQL queries for populating the view from each source table\n    /// Returns a vector of SQL statements, one for each referenced table\n    /// Each query includes the WHERE conditions accumulated from all occurrences\n    fn sql_for_populate(&self) -> crate::Result<Vec<String>> {\n        Self::generate_populate_queries(\n            &self.select_stmt,\n            &self.referenced_tables,\n            &self.table_aliases,\n            &self.qualified_table_names,\n            &self.table_conditions,\n        )\n    }\n\n    pub fn generate_populate_queries(\n        select_stmt: &ast::Select,\n        referenced_tables: &[Arc<BTreeTable>],\n        table_aliases: &HashMap<String, String>,\n        qualified_table_names: &HashMap<String, String>,\n        table_conditions: &HashMap<String, Vec<Option<ast::Expr>>>,\n    ) -> crate::Result<Vec<String>> {\n        if referenced_tables.is_empty() {\n            return Err(LimboError::ParseError(\n                \"No tables to populate from\".to_string(),\n            ));\n        }\n\n        let mut queries = Vec::new();\n\n        for table in referenced_tables {\n            // Check if the table has a rowid alias (INTEGER PRIMARY KEY column)\n            let has_rowid_alias = table.columns.iter().any(|col| col.is_rowid_alias());\n\n            // Select all columns. The circuit will handle filtering and projection\n            // If there's a rowid alias, we don't need to select rowid separately\n            let select_clause = if has_rowid_alias {\n                \"*\".to_string()\n            } else {\n                \"*, rowid\".to_string()\n            };\n\n            // Get accumulated WHERE conditions for this table\n            let where_clause = if let Some(conditions) = table_conditions.get(&table.name) {\n                // Combine multiple conditions with OR if there are multiple occurrences\n                Self::combine_conditions(\n                    select_stmt,\n                    conditions,\n                    &table.name,\n                    referenced_tables,\n                    table_aliases,\n                )?\n            } else {\n                String::new()\n            };\n\n            // Use the qualified table name if available, otherwise just the table name\n            let table_name = qualified_table_names\n                .get(&table.name)\n                .cloned()\n                .unwrap_or_else(|| table.name.clone());\n\n            // Construct the query for this table\n            let query = if where_clause.is_empty() {\n                format!(\"SELECT {select_clause} FROM {table_name}\")\n            } else {\n                format!(\"SELECT {select_clause} FROM {table_name} WHERE {where_clause}\")\n            };\n            tracing::debug!(\"populating materialized view with `{query}`\");\n            queries.push(query);\n        }\n\n        Ok(queries)\n    }\n\n    fn combine_conditions(\n        _select_stmt: &ast::Select,\n        conditions: &[Option<ast::Expr>],\n        table_name: &str,\n        _referenced_tables: &[Arc<BTreeTable>],\n        table_aliases: &HashMap<String, String>,\n    ) -> crate::Result<String> {\n        // Check if any conditions are None (SELECTs without WHERE)\n        let has_none = conditions.iter().any(|c| c.is_none());\n        let non_empty: Vec<_> = conditions.iter().filter_map(|c| c.as_ref()).collect();\n\n        // If we have both Some and None conditions, that means in some of the expressions where\n        // this table appear we want all rows. So we need to fetch all rows.\n        if has_none && !non_empty.is_empty() {\n            return Ok(String::new());\n        }\n\n        if non_empty.is_empty() {\n            return Ok(String::new());\n        }\n\n        if non_empty.len() == 1 {\n            // Unqualify the expression before converting to string\n            let unqualified = Self::unqualify_expression(non_empty[0], table_name, table_aliases);\n            return Ok(unqualified.to_string());\n        }\n\n        // Multiple conditions - combine with OR\n        // This happens in UNION ALL when the same table appears multiple times\n        let mut combined_parts = Vec::new();\n        for condition in non_empty {\n            let unqualified = Self::unqualify_expression(condition, table_name, table_aliases);\n            // Wrap each condition in parentheses to preserve precedence\n            combined_parts.push(format!(\"({unqualified})\"));\n        }\n\n        // Join all conditions with OR\n        Ok(combined_parts.join(\" OR \"))\n    }\n    /// Resolve a table alias to the actual table name\n    /// Check if an expression is a simple comparison that can be safely extracted\n    /// This excludes subqueries, CASE expressions, function calls, etc.\n    fn is_simple_comparison(expr: &ast::Expr) -> bool {\n        match expr {\n            // Simple column references and literals are OK\n            ast::Expr::Column { .. } | ast::Expr::Literal(_) => true,\n\n            // Simple binary operations between simple expressions are OK\n            ast::Expr::Binary(left, op, right) => {\n                match op {\n                    // Logical operators\n                    ast::Operator::And | ast::Operator::Or => {\n                        Self::is_simple_comparison(left) && Self::is_simple_comparison(right)\n                    }\n                    // Comparison operators\n                    ast::Operator::Equals\n                    | ast::Operator::NotEquals\n                    | ast::Operator::Less\n                    | ast::Operator::LessEquals\n                    | ast::Operator::Greater\n                    | ast::Operator::GreaterEquals\n                    | ast::Operator::Is\n                    | ast::Operator::IsNot => {\n                        Self::is_simple_comparison(left) && Self::is_simple_comparison(right)\n                    }\n                    // String concatenation and other operations are NOT simple\n                    ast::Operator::Concat => false,\n                    // Arithmetic might be OK if operands are simple\n                    ast::Operator::Add\n                    | ast::Operator::Subtract\n                    | ast::Operator::Multiply\n                    | ast::Operator::Divide\n                    | ast::Operator::Modulus => {\n                        Self::is_simple_comparison(left) && Self::is_simple_comparison(right)\n                    }\n                    _ => false,\n                }\n            }\n\n            // Unary operations might be OK\n            ast::Expr::Unary(\n                ast::UnaryOperator::Not\n                | ast::UnaryOperator::Negative\n                | ast::UnaryOperator::Positive,\n                inner,\n            ) => Self::is_simple_comparison(inner),\n            ast::Expr::Unary(_, _) => false,\n\n            // Complex expressions are NOT simple\n            ast::Expr::Case { .. } => false,\n            ast::Expr::Cast { .. } => false,\n            ast::Expr::Collate { .. } => false,\n            ast::Expr::Exists(_) => false,\n            ast::Expr::FunctionCall { .. } => false,\n            ast::Expr::InList { .. } => false,\n            ast::Expr::InSelect { .. } => false,\n            ast::Expr::Like { .. } => false,\n            ast::Expr::NotNull(_) => true, // IS NOT NULL is simple enough\n            ast::Expr::Parenthesized(exprs) => {\n                // Parenthesized expression can contain multiple expressions\n                // Only consider it simple if it has exactly one simple expression\n                exprs.len() == 1 && Self::is_simple_comparison(&exprs[0])\n            }\n            ast::Expr::Subquery(_) => false,\n\n            // BETWEEN might be OK if all operands are simple\n            ast::Expr::Between { .. } => {\n                // BETWEEN has a different structure, for safety just exclude it\n                false\n            }\n\n            // Qualified references are simple\n            ast::Expr::DoublyQualified(..) => true,\n            ast::Expr::Qualified(_, _) => true,\n\n            // These are simple\n            ast::Expr::Id(_) => true,\n            ast::Expr::Name(_) => true,\n\n            // Anything else is not simple\n            _ => false,\n        }\n    }\n\n    /// Extract conditions from a WHERE clause that apply to a specific table\n    fn extract_conditions_for_table(\n        expr: &ast::Expr,\n        table_name: &str,\n        aliases: &HashMap<String, String>,\n        all_tables: &[String],\n        schema: &Schema,\n    ) -> Option<ast::Expr> {\n        match expr {\n            ast::Expr::Binary(left, op, right) => {\n                match op {\n                    ast::Operator::And => {\n                        // For AND, we can extract conditions independently\n                        let left_cond = Self::extract_conditions_for_table(\n                            left, table_name, aliases, all_tables, schema,\n                        );\n                        let right_cond = Self::extract_conditions_for_table(\n                            right, table_name, aliases, all_tables, schema,\n                        );\n\n                        match (left_cond, right_cond) {\n                            (Some(l), Some(r)) => Some(ast::Expr::Binary(\n                                Box::new(l),\n                                ast::Operator::And,\n                                Box::new(r),\n                            )),\n                            (Some(l), None) => Some(l),\n                            (None, Some(r)) => Some(r),\n                            (None, None) => None,\n                        }\n                    }\n                    ast::Operator::Or => {\n                        // For OR, both sides must reference only our table\n                        let left_tables =\n                            Self::get_tables_in_expr(left, aliases, all_tables, schema);\n                        let right_tables =\n                            Self::get_tables_in_expr(right, aliases, all_tables, schema);\n\n                        if left_tables.len() == 1\n                            && left_tables.contains(&table_name.to_string())\n                            && right_tables.len() == 1\n                            && right_tables.contains(&table_name.to_string())\n                            && Self::is_simple_comparison(expr)\n                        {\n                            Some(expr.clone())\n                        } else {\n                            None\n                        }\n                    }\n                    _ => {\n                        // For comparison operators, check if this condition only references our table\n                        let referenced_tables =\n                            Self::get_tables_in_expr(expr, aliases, all_tables, schema);\n                        if referenced_tables.len() == 1\n                            && referenced_tables.contains(&table_name.to_string())\n                            && Self::is_simple_comparison(expr)\n                        {\n                            Some(expr.clone())\n                        } else {\n                            None\n                        }\n                    }\n                }\n            }\n            _ => {\n                // For other expressions, check if they only reference our table\n                let referenced_tables = Self::get_tables_in_expr(expr, aliases, all_tables, schema);\n                if referenced_tables.len() == 1\n                    && referenced_tables.contains(&table_name.to_string())\n                    && Self::is_simple_comparison(expr)\n                {\n                    Some(expr.clone())\n                } else {\n                    None\n                }\n            }\n        }\n    }\n\n    /// Unqualify column references in an expression\n    /// Removes table/alias prefixes from qualified column names\n    fn unqualify_expression(\n        expr: &ast::Expr,\n        table_name: &str,\n        aliases: &HashMap<String, String>,\n    ) -> ast::Expr {\n        match expr {\n            ast::Expr::Binary(left, op, right) => ast::Expr::Binary(\n                Box::new(Self::unqualify_expression(left, table_name, aliases)),\n                *op,\n                Box::new(Self::unqualify_expression(right, table_name, aliases)),\n            ),\n            ast::Expr::Qualified(table_or_alias, column) => {\n                // Check if this qualification refers to our table\n                let table_str = table_or_alias.as_str();\n                let actual_table = if let Some(actual) = aliases.get(table_str) {\n                    actual.clone()\n                } else if table_str.contains('.') {\n                    // Handle database.table format\n                    table_str\n                        .split('.')\n                        .next_back()\n                        .unwrap_or(table_str)\n                        .to_string()\n                } else {\n                    table_str.to_string()\n                };\n\n                if actual_table == table_name {\n                    // Remove the qualification\n                    ast::Expr::Id(column.clone())\n                } else {\n                    // Keep the qualification (shouldn't happen if extraction worked correctly)\n                    expr.clone()\n                }\n            }\n            ast::Expr::DoublyQualified(_database, table, column) => {\n                // Check if this refers to our table\n                if table.as_str() == table_name {\n                    // Remove the qualification, keep just the column\n                    ast::Expr::Id(column.clone())\n                } else {\n                    // Keep the qualification (shouldn't happen if extraction worked correctly)\n                    expr.clone()\n                }\n            }\n            ast::Expr::Unary(op, inner) => ast::Expr::Unary(\n                *op,\n                Box::new(Self::unqualify_expression(inner, table_name, aliases)),\n            ),\n            ast::Expr::FunctionCall {\n                name,\n                args,\n                distinctness,\n                filter_over,\n                order_by,\n            } => ast::Expr::FunctionCall {\n                name: name.clone(),\n                args: args\n                    .iter()\n                    .map(|arg| Box::new(Self::unqualify_expression(arg, table_name, aliases)))\n                    .collect(),\n                distinctness: *distinctness,\n                filter_over: filter_over.clone(),\n                order_by: order_by.clone(),\n            },\n            ast::Expr::InList { lhs, not, rhs } => ast::Expr::InList {\n                lhs: Box::new(Self::unqualify_expression(lhs, table_name, aliases)),\n                not: *not,\n                rhs: rhs\n                    .iter()\n                    .map(|item| Box::new(Self::unqualify_expression(item, table_name, aliases)))\n                    .collect(),\n            },\n            ast::Expr::Between {\n                lhs,\n                not,\n                start,\n                end,\n            } => ast::Expr::Between {\n                lhs: Box::new(Self::unqualify_expression(lhs, table_name, aliases)),\n                not: *not,\n                start: Box::new(Self::unqualify_expression(start, table_name, aliases)),\n                end: Box::new(Self::unqualify_expression(end, table_name, aliases)),\n            },\n            _ => expr.clone(),\n        }\n    }\n\n    /// Get all tables referenced in an expression\n    fn get_tables_in_expr(\n        expr: &ast::Expr,\n        aliases: &HashMap<String, String>,\n        all_tables: &[String],\n        schema: &Schema,\n    ) -> Vec<String> {\n        let mut tables = Vec::new();\n        Self::collect_tables_in_expr(expr, aliases, all_tables, schema, &mut tables);\n        tables.sort();\n        tables.dedup();\n        tables\n    }\n\n    /// Recursively collect table references from an expression\n    fn collect_tables_in_expr(\n        expr: &ast::Expr,\n        aliases: &HashMap<String, String>,\n        all_tables: &[String],\n        schema: &Schema,\n        tables: &mut Vec<String>,\n    ) {\n        match expr {\n            ast::Expr::Binary(left, _, right) => {\n                Self::collect_tables_in_expr(left, aliases, all_tables, schema, tables);\n                Self::collect_tables_in_expr(right, aliases, all_tables, schema, tables);\n            }\n            ast::Expr::Qualified(table_or_alias, _) => {\n                // Handle database.table or just table/alias\n                let table_str = table_or_alias.as_str();\n                let table_name = if let Some(actual_table) = aliases.get(table_str) {\n                    // It's an alias\n                    actual_table.clone()\n                } else if table_str.contains('.') {\n                    // It might be database.table format, extract just the table name\n                    table_str\n                        .split('.')\n                        .next_back()\n                        .unwrap_or(table_str)\n                        .to_string()\n                } else {\n                    // It's a direct table name\n                    table_str.to_string()\n                };\n                tables.push(table_name);\n            }\n            ast::Expr::DoublyQualified(_database, table, _column) => {\n                // For database.table.column, extract the table name\n                tables.push(table.to_string());\n            }\n            ast::Expr::Id(column) => {\n                // Unqualified column - try to find which table has this column\n                if all_tables.len() == 1 {\n                    tables.push(all_tables[0].clone());\n                } else {\n                    // Check which table has this column\n                    for table_name in all_tables {\n                        if let Some(table) = schema.get_btree_table(table_name) {\n                            if table\n                                .columns\n                                .iter()\n                                .any(|col| col.name.as_deref() == Some(column.as_str()))\n                            {\n                                tables.push(table_name.clone());\n                                break; // Found the table, stop looking\n                            }\n                        }\n                    }\n                }\n            }\n            ast::Expr::FunctionCall { args, .. } => {\n                for arg in args {\n                    Self::collect_tables_in_expr(arg, aliases, all_tables, schema, tables);\n                }\n            }\n            ast::Expr::InList { lhs, rhs, .. } => {\n                Self::collect_tables_in_expr(lhs, aliases, all_tables, schema, tables);\n                for item in rhs {\n                    Self::collect_tables_in_expr(item, aliases, all_tables, schema, tables);\n                }\n            }\n            ast::Expr::InSelect { lhs, .. } => {\n                Self::collect_tables_in_expr(lhs, aliases, all_tables, schema, tables);\n            }\n            ast::Expr::Between {\n                lhs, start, end, ..\n            } => {\n                Self::collect_tables_in_expr(lhs, aliases, all_tables, schema, tables);\n                Self::collect_tables_in_expr(start, aliases, all_tables, schema, tables);\n                Self::collect_tables_in_expr(end, aliases, all_tables, schema, tables);\n            }\n            ast::Expr::Unary(_, expr) => {\n                Self::collect_tables_in_expr(expr, aliases, all_tables, schema, tables);\n            }\n            _ => {\n                // Literals, etc. don't reference tables\n            }\n        }\n    }\n    /// Populate the view by scanning the source table using a state machine\n    /// This can be called multiple times and will resume from where it left off\n    /// This method is only for materialized views and will persist data to the btree\n    pub fn populate_from_table(\n        &mut self,\n        conn: &crate::sync::Arc<crate::Connection>,\n        pager: &crate::sync::Arc<crate::Pager>,\n        _btree_cursor: &mut dyn CursorTrait,\n    ) -> crate::Result<IOResult<()>> {\n        // Assert that this is a materialized view with a root page\n        assert!(\n            self.root_page != 0,\n            \"populate_from_table should only be called for materialized views with root_page\"\n        );\n\n        // Mark as nested for the duration of this call to prevent inner queries from\n        // committing the outer transaction's dirty pages. We increment on every entry\n        // and decrement on every exit (including IO yields and errors) so re-entrant\n        // calls keep the counter balanced.\n        conn.start_nested();\n        let result = self.populate_from_table_inner(conn, pager, _btree_cursor);\n        conn.end_nested();\n        result\n    }\n\n    fn populate_from_table_inner(\n        &mut self,\n        conn: &crate::sync::Arc<crate::Connection>,\n        pager: &crate::sync::Arc<crate::Pager>,\n        _btree_cursor: &mut dyn CursorTrait,\n    ) -> crate::Result<IOResult<()>> {\n        'outer: loop {\n            match std::mem::replace(&mut self.populate_state, PopulateState::Done) {\n                PopulateState::Start => {\n                    // Generate the SQL query for populating the view\n                    // It is best to use a standard query than a cursor for two reasons:\n                    // 1) Using a sql query will allow us to be much more efficient in cases where we only want\n                    //    some rows, in particular for indexed filters\n                    // 2) There are two types of cursors: index and table. In some situations (like for example\n                    //    if the table has an integer primary key), the key will be exclusively in the index\n                    //    btree and not in the table btree. Using cursors would force us to be aware of this\n                    //    distinction (and others), and ultimately lead to reimplementing the whole query\n                    //    machinery (next step is which index is best to use, etc)\n                    let queries = self.sql_for_populate()?;\n\n                    self.populate_state = PopulateState::ProcessingAllTables {\n                        queries,\n                        current_idx: 0,\n                    };\n                }\n\n                PopulateState::ProcessingAllTables {\n                    queries,\n                    current_idx,\n                } => {\n                    if current_idx >= queries.len() {\n                        self.populate_state = PopulateState::Done;\n                        return Ok(IOResult::Done(()));\n                    }\n\n                    let query = queries[current_idx].clone();\n                    // Use the parent connection directly for reading.\n                    // We need to use the same connection that has the uncommitted schema changes.\n                    // Creating a new connection would cause schema version mismatch issues because\n                    // the new connection's schema cookie check would fail (database file has old version).\n\n                    // Prepare the statement using the parent connection\n                    let stmt = conn.prepare(&query)?;\n\n                    self.populate_state = PopulateState::ProcessingOneTable {\n                        queries,\n                        current_idx,\n                        stmt: Box::new(stmt),\n                        rows_processed: 0,\n                        pending_row: None,\n                    };\n                }\n\n                PopulateState::ProcessingOneTable {\n                    queries,\n                    current_idx,\n                    mut stmt,\n                    mut rows_processed,\n                    pending_row,\n                } => {\n                    // If we have a pending row from a previous I/O interruption, process it first\n                    if let Some((rowid, values)) = pending_row {\n                        match self.process_one_row(\n                            rowid,\n                            values.clone(),\n                            current_idx,\n                            pager.clone(),\n                        )? {\n                            IOResult::Done(_) => {\n                                // Row processed successfully, continue to next row\n                                rows_processed += 1;\n                            }\n                            IOResult::IO(io) => {\n                                // Still not done, restore state with pending row and return\n                                self.populate_state = PopulateState::ProcessingOneTable {\n                                    queries,\n                                    current_idx,\n                                    stmt,\n                                    rows_processed,\n                                    pending_row: Some((rowid, values)),\n                                };\n                                return Ok(IOResult::IO(io));\n                            }\n                        }\n                    }\n\n                    // Process rows one at a time - no batching\n                    loop {\n                        // This step() call resumes from where the statement left off\n                        match stmt.step()? {\n                            crate::vdbe::StepResult::Row => {\n                                // Get the row\n                                let row = stmt.row().ok_or_else(|| {\n                                    LimboError::InternalError(\n                                        \"row should exist after StepResult::Row\".to_string(),\n                                    )\n                                })?;\n\n                                // Extract values from the row\n                                let all_values: Vec<crate::types::Value> =\n                                    row.get_values().cloned().collect();\n\n                                // Extract rowid and values using helper\n                                let (rowid, values) =\n                                    match self.extract_rowid_and_values(all_values, current_idx) {\n                                        Some(result) => result,\n                                        None => {\n                                            // Invalid rowid, skip this row\n                                            rows_processed += 1;\n                                            continue;\n                                        }\n                                    };\n\n                                // Process this row\n                                match self.process_one_row(\n                                    rowid,\n                                    values.clone(),\n                                    current_idx,\n                                    pager.clone(),\n                                )? {\n                                    IOResult::Done(_) => {\n                                        // Row processed successfully, continue to next row\n                                        rows_processed += 1;\n                                    }\n                                    IOResult::IO(io) => {\n                                        // Save state and return I/O\n                                        // We'll resume at the SAME row when called again (don't increment rows_processed)\n                                        // The circuit still has unfinished work for this row\n                                        self.populate_state = PopulateState::ProcessingOneTable {\n                                            queries,\n                                            current_idx,\n                                            stmt,\n                                            rows_processed, // Don't increment - row not done yet!\n                                            pending_row: Some((rowid, values)), // Save the row for resumption\n                                        };\n                                        return Ok(IOResult::IO(io));\n                                    }\n                                }\n                            }\n\n                            crate::vdbe::StepResult::Done => {\n                                // All rows processed from this table\n                                // Move to next table\n                                self.populate_state = PopulateState::ProcessingAllTables {\n                                    queries,\n                                    current_idx: current_idx + 1,\n                                };\n                                continue 'outer;\n                            }\n\n                            crate::vdbe::StepResult::Interrupt | crate::vdbe::StepResult::Busy => {\n                                // Save state before returning error\n                                self.populate_state = PopulateState::ProcessingOneTable {\n                                    queries,\n                                    current_idx,\n                                    stmt,\n                                    rows_processed,\n                                    pending_row: None, // No pending row when interrupted between rows\n                                };\n                                return Err(LimboError::Busy);\n                            }\n\n                            crate::vdbe::StepResult::IO => {\n                                // Statement needs I/O - save state and return\n                                self.populate_state = PopulateState::ProcessingOneTable {\n                                    queries,\n                                    current_idx,\n                                    stmt,\n                                    rows_processed,\n                                    pending_row: None, // No pending row when interrupted between rows\n                                };\n                                // TODO: Get the actual I/O completion from the statement\n                                let completion = crate::io::Completion::new_yield();\n                                return Ok(IOResult::IO(crate::types::IOCompletions::Single(\n                                    completion,\n                                )));\n                            }\n                        }\n                    }\n                }\n\n                PopulateState::Done => {\n                    return Ok(IOResult::Done(()));\n                }\n            }\n        }\n    }\n\n    /// Process a single row through the circuit\n    fn process_one_row(\n        &mut self,\n        rowid: i64,\n        values: Vec<Value>,\n        table_idx: usize,\n        pager: Arc<crate::Pager>,\n    ) -> crate::Result<IOResult<()>> {\n        // Create a single-row delta\n        let mut single_row_delta = Delta::new();\n        single_row_delta.insert(rowid, values);\n\n        // Create a DeltaSet with this delta for the current table\n        let mut delta_set = DeltaSet::new();\n        let table_name = self.referenced_tables[table_idx].name.clone();\n        delta_set.insert(table_name, single_row_delta);\n\n        // Process through merge_delta\n        self.merge_delta(delta_set, pager)\n    }\n\n    /// Extract rowid and values from a row\n    fn extract_rowid_and_values(\n        &self,\n        all_values: Vec<Value>,\n        table_idx: usize,\n    ) -> Option<(i64, Vec<Value>)> {\n        if let Some((idx, _)) = self.referenced_tables[table_idx].get_rowid_alias_column() {\n            // The rowid is the value at the rowid alias column index\n            let rowid = match all_values.get(idx) {\n                Some(Value::Numeric(Numeric::Integer(id))) => *id,\n                _ => return None, // Invalid rowid\n            };\n            // All values are table columns (no separate rowid was selected)\n            Some((rowid, all_values))\n        } else {\n            // The last value is the explicitly selected rowid\n            let rowid = match all_values.last() {\n                Some(Value::Numeric(Numeric::Integer(id))) => *id,\n                _ => return None, // Invalid rowid\n            };\n            // Get all values except the rowid\n            let values = all_values[..all_values.len() - 1].to_vec();\n            Some((rowid, values))\n        }\n    }\n\n    /// Merge a delta set of changes into the view's current state\n    pub fn merge_delta(\n        &mut self,\n        delta_set: DeltaSet,\n        pager: Arc<crate::Pager>,\n    ) -> crate::Result<IOResult<()>> {\n        // Early return if all deltas are empty\n        if delta_set.is_empty() {\n            return Ok(IOResult::Done(()));\n        }\n\n        // Use the circuit to process the deltas and write to btree\n        let input_data = delta_set.into_map();\n\n        // The circuit now handles all btree I/O internally with the provided pager\n        let _delta = return_if_io!(self.circuit.commit(input_data, pager));\n        Ok(IOResult::Done(()))\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::schema::{BTreeTable, ColDef, Column as SchemaColumn, Schema, Type};\n    use crate::sync::Arc;\n    use turso_parser::ast;\n    use turso_parser::parser::Parser;\n\n    // Helper function to create a test schema with multiple tables\n    fn create_test_schema() -> Schema {\n        let mut schema = Schema::new();\n\n        // Create customers table\n        let customers_table = BTreeTable {\n            name: \"customers\".to_string(),\n            root_page: 2,\n            primary_key_columns: vec![(\"id\".to_string(), ast::SortOrder::Asc)],\n            columns: vec![\n                SchemaColumn::new(\n                    Some(\"id\".to_string()),\n                    \"INTEGER\".to_string(),\n                    None,\n                    None,\n                    Type::Integer,\n                    None,\n                    ColDef {\n                        primary_key: true,\n                        rowid_alias: true,\n                        notnull: true,\n                        unique: false,\n                        hidden: false,\n                        notnull_conflict_clause: None,\n                    },\n                ),\n                SchemaColumn::new_default_text(Some(\"name\".to_string()), \"TEXT\".to_string(), None),\n            ],\n            has_rowid: true,\n            is_strict: false,\n            unique_sets: vec![],\n            foreign_keys: vec![],\n            check_constraints: vec![],\n            rowid_alias_conflict_clause: None,\n            has_autoincrement: false,\n        };\n\n        // Create orders table\n        let orders_table = BTreeTable {\n            name: \"orders\".to_string(),\n            root_page: 3,\n            primary_key_columns: vec![(\"id\".to_string(), ast::SortOrder::Asc)],\n            columns: vec![\n                SchemaColumn::new(\n                    Some(\"id\".to_string()),\n                    \"INTEGER\".to_string(),\n                    None,\n                    None,\n                    Type::Integer,\n                    None,\n                    ColDef {\n                        primary_key: true,\n                        rowid_alias: true,\n                        notnull: true,\n                        unique: false,\n                        hidden: false,\n                        notnull_conflict_clause: None,\n                    },\n                ),\n                SchemaColumn::new(\n                    Some(\"customer_id\".to_string()),\n                    \"INTEGER\".to_string(),\n                    None,\n                    None,\n                    Type::Integer,\n                    None,\n                    ColDef::default(),\n                ),\n                SchemaColumn::new_default_integer(\n                    Some(\"total\".to_string()),\n                    \"INTEGER\".to_string(),\n                    None,\n                ),\n            ],\n            has_rowid: true,\n            is_strict: false,\n            has_autoincrement: false,\n            foreign_keys: vec![],\n            check_constraints: vec![],\n            rowid_alias_conflict_clause: None,\n            unique_sets: vec![],\n        };\n\n        // Create products table\n        let products_table = BTreeTable {\n            name: \"products\".to_string(),\n            root_page: 4,\n            primary_key_columns: vec![(\"id\".to_string(), ast::SortOrder::Asc)],\n            columns: vec![\n                SchemaColumn::new(\n                    Some(\"id\".to_string()),\n                    \"INTEGER\".to_string(),\n                    None,\n                    None,\n                    Type::Integer,\n                    None,\n                    ColDef {\n                        primary_key: true,\n                        rowid_alias: true,\n                        notnull: true,\n                        unique: false,\n                        hidden: false,\n                        notnull_conflict_clause: None,\n                    },\n                ),\n                SchemaColumn::new_default_text(Some(\"name\".to_string()), \"TEXT\".to_string(), None),\n                SchemaColumn::new(\n                    Some(\"price\".to_string()),\n                    \"REAL\".to_string(),\n                    None,\n                    None,\n                    Type::Real,\n                    None,\n                    ColDef::default(),\n                ),\n            ],\n            has_rowid: true,\n            is_strict: false,\n            has_autoincrement: false,\n            foreign_keys: vec![],\n            check_constraints: vec![],\n            rowid_alias_conflict_clause: None,\n            unique_sets: vec![],\n        };\n\n        // Create logs table - without a rowid alias (no INTEGER PRIMARY KEY)\n        let logs_table = BTreeTable {\n            name: \"logs\".to_string(),\n            root_page: 5,\n            primary_key_columns: vec![], // No primary key, so no rowid alias\n            columns: vec![\n                SchemaColumn::new(\n                    Some(\"message\".to_string()),\n                    \"TEXT\".to_string(),\n                    None,\n                    None,\n                    Type::Text,\n                    None,\n                    ColDef::default(),\n                ),\n                SchemaColumn::new_default_integer(\n                    Some(\"level\".to_string()),\n                    \"INTEGER\".to_string(),\n                    None,\n                ),\n                SchemaColumn::new_default_integer(\n                    Some(\"timestamp\".to_string()),\n                    \"INTEGER\".to_string(),\n                    None,\n                ),\n            ],\n            has_rowid: true, // Has implicit rowid but no alias\n            is_strict: false,\n            has_autoincrement: false,\n            foreign_keys: vec![],\n            check_constraints: vec![],\n            rowid_alias_conflict_clause: None,\n            unique_sets: vec![],\n        };\n\n        schema\n            .add_btree_table(Arc::new(customers_table))\n            .expect(\"Test setup: failed to add customers table\");\n\n        schema\n            .add_btree_table(Arc::new(orders_table))\n            .expect(\"Test setup: failed to add orders table\");\n\n        schema\n            .add_btree_table(Arc::new(products_table))\n            .expect(\"Test setup: failed to add products table\");\n\n        schema\n            .add_btree_table(Arc::new(logs_table))\n            .expect(\"Test setup: failed to add logs table\");\n\n        schema\n    }\n\n    // Helper to parse SQL and extract the SELECT statement\n    fn parse_select(sql: &str) -> ast::Select {\n        let mut parser = Parser::new(sql.as_bytes());\n        let cmd = parser.next().unwrap().unwrap();\n        match cmd {\n            ast::Cmd::Stmt(ast::Stmt::Select(select)) => select,\n            _ => panic!(\"Expected SELECT statement\"),\n        }\n    }\n\n    // Type alias for the complex return type of extract_all_tables\n    type ExtractedTableInfo = (\n        Vec<Arc<BTreeTable>>,\n        HashMap<String, String>,\n        HashMap<String, String>,\n        HashMap<String, Vec<Option<ast::Expr>>>,\n    );\n\n    fn extract_all_tables(select: &ast::Select, schema: &Schema) -> Result<ExtractedTableInfo> {\n        let mut referenced_tables = Vec::new();\n        let mut table_aliases = HashMap::default();\n        let mut qualified_table_names = HashMap::default();\n        let mut table_conditions = HashMap::default();\n        IncrementalView::extract_all_tables(\n            select,\n            schema,\n            &mut referenced_tables,\n            &mut table_aliases,\n            &mut qualified_table_names,\n            &mut table_conditions,\n        )?;\n        Ok((\n            referenced_tables,\n            table_aliases,\n            qualified_table_names,\n            table_conditions,\n        ))\n    }\n\n    #[test]\n    fn test_extract_single_table() {\n        let schema = create_test_schema();\n        let select = parse_select(\"SELECT * FROM customers\");\n\n        let (tables, _, _, _table_conditions) = extract_all_tables(&select, &schema).unwrap();\n\n        assert_eq!(tables.len(), 1);\n        assert_eq!(tables[0].name, \"customers\");\n    }\n\n    #[test]\n    fn test_tables_from_union() {\n        let schema = create_test_schema();\n        let select = parse_select(\"SELECT name FROM customers union SELECT name from products\");\n\n        let (tables, _, _, table_conditions) = extract_all_tables(&select, &schema).unwrap();\n\n        assert_eq!(tables.len(), 2);\n        assert!(table_conditions.contains_key(\"customers\"));\n        assert!(table_conditions.contains_key(\"products\"));\n    }\n\n    #[test]\n    fn test_extract_tables_from_inner_join() {\n        let schema = create_test_schema();\n        let select = parse_select(\n            \"SELECT * FROM customers INNER JOIN orders ON customers.id = orders.customer_id\",\n        );\n\n        let (tables, _, _, table_conditions) = extract_all_tables(&select, &schema).unwrap();\n\n        assert_eq!(tables.len(), 2);\n        assert!(table_conditions.contains_key(\"customers\"));\n        assert!(table_conditions.contains_key(\"orders\"));\n    }\n\n    #[test]\n    fn test_extract_tables_from_multiple_joins() {\n        let schema = create_test_schema();\n        let select = parse_select(\n            \"SELECT * FROM customers\n             INNER JOIN orders ON customers.id = orders.customer_id\n             INNER JOIN products ON orders.id = products.id\",\n        );\n\n        let (tables, _, _, table_conditions) = extract_all_tables(&select, &schema).unwrap();\n\n        assert_eq!(tables.len(), 3);\n        assert!(table_conditions.contains_key(\"customers\"));\n        assert!(table_conditions.contains_key(\"orders\"));\n        assert!(table_conditions.contains_key(\"products\"));\n    }\n\n    #[test]\n    fn test_extract_tables_from_left_join() {\n        let schema = create_test_schema();\n        let select = parse_select(\n            \"SELECT * FROM customers LEFT JOIN orders ON customers.id = orders.customer_id\",\n        );\n\n        let (tables, _, _, table_conditions) = extract_all_tables(&select, &schema).unwrap();\n\n        assert_eq!(tables.len(), 2);\n        assert!(table_conditions.contains_key(\"customers\"));\n        assert!(table_conditions.contains_key(\"orders\"));\n    }\n\n    #[test]\n    fn test_extract_tables_from_cross_join() {\n        let schema = create_test_schema();\n        let select = parse_select(\"SELECT * FROM customers CROSS JOIN orders\");\n\n        let (tables, _, _, table_conditions) = extract_all_tables(&select, &schema).unwrap();\n\n        assert_eq!(tables.len(), 2);\n        assert!(table_conditions.contains_key(\"customers\"));\n        assert!(table_conditions.contains_key(\"orders\"));\n    }\n\n    #[test]\n    fn test_extract_tables_with_aliases() {\n        let schema = create_test_schema();\n        let select =\n            parse_select(\"SELECT * FROM customers c INNER JOIN orders o ON c.id = o.customer_id\");\n\n        let (tables, aliases, _, _table_conditions) = extract_all_tables(&select, &schema).unwrap();\n\n        // Should still extract the actual table names, not aliases\n        assert_eq!(tables.len(), 2);\n        let table_names: Vec<&str> = tables.iter().map(|t| t.name.as_str()).collect();\n        assert!(table_names.contains(&\"customers\"));\n        assert!(table_names.contains(&\"orders\"));\n\n        // Check that aliases are correctly mapped\n        assert_eq!(aliases.get(\"c\"), Some(&\"customers\".to_string()));\n        assert_eq!(aliases.get(\"o\"), Some(&\"orders\".to_string()));\n    }\n\n    #[test]\n    fn test_extract_tables_nonexistent_table_error() {\n        let schema = create_test_schema();\n        let select = parse_select(\"SELECT * FROM nonexistent\");\n\n        let result = extract_all_tables(&select, &schema).map(|(tables, _, _, _)| tables);\n\n        assert!(result.is_err());\n        assert!(result\n            .unwrap_err()\n            .to_string()\n            .contains(\"Table 'nonexistent' not found\"));\n    }\n\n    #[test]\n    fn test_extract_tables_nonexistent_join_table_error() {\n        let schema = create_test_schema();\n        let select = parse_select(\n            \"SELECT * FROM customers INNER JOIN nonexistent ON customers.id = nonexistent.id\",\n        );\n\n        let result = extract_all_tables(&select, &schema).map(|(tables, _, _, _)| tables);\n\n        assert!(result.is_err());\n        assert!(result\n            .unwrap_err()\n            .to_string()\n            .contains(\"Table 'nonexistent' not found\"));\n    }\n\n    #[test]\n    fn test_sql_for_populate_simple_query_no_where() {\n        // Test simple query with no WHERE clause\n        let schema = create_test_schema();\n        let select = parse_select(\"SELECT * FROM customers\");\n\n        let (tables, aliases, qualified_names, table_conditions) =\n            extract_all_tables(&select, &schema).unwrap();\n        let view = IncrementalView::new(\n            \"test_view\".to_string(),\n            select.clone(),\n            tables,\n            aliases,\n            qualified_names,\n            table_conditions,\n            extract_view_columns(&select, &schema).unwrap(),\n            &schema,\n            1, // main_data_root\n            2, // internal_state_root\n            3, // internal_state_index_root\n        )\n        .unwrap();\n\n        let queries = view.sql_for_populate().unwrap();\n\n        assert_eq!(queries.len(), 1);\n        // customers has id as rowid alias, so no need for explicit rowid\n        assert_eq!(queries[0], \"SELECT * FROM customers\");\n    }\n\n    #[test]\n    fn test_sql_for_populate_simple_query_with_where() {\n        // Test simple query with WHERE clause\n        let schema = create_test_schema();\n        let select = parse_select(\"SELECT * FROM customers WHERE id > 10\");\n\n        let (tables, aliases, qualified_names, table_conditions) =\n            extract_all_tables(&select, &schema).unwrap();\n        let view = IncrementalView::new(\n            \"test_view\".to_string(),\n            select.clone(),\n            tables,\n            aliases,\n            qualified_names,\n            table_conditions,\n            extract_view_columns(&select, &schema).unwrap(),\n            &schema,\n            1, // main_data_root\n            2, // internal_state_root\n            3, // internal_state_index_root\n        )\n        .unwrap();\n\n        let queries = view.sql_for_populate().unwrap();\n\n        assert_eq!(queries.len(), 1);\n        // For single-table queries, we should get the full WHERE clause\n        assert_eq!(queries[0], \"SELECT * FROM customers WHERE id > 10\");\n    }\n\n    #[test]\n    fn test_sql_for_populate_join_with_where_on_both_tables() {\n        // Test JOIN query with WHERE conditions on both tables\n        let schema = create_test_schema();\n        let select = parse_select(\n            \"SELECT * FROM customers c \\\n             JOIN orders o ON c.id = o.customer_id \\\n             WHERE c.id > 10 AND o.total > 100\",\n        );\n\n        let (tables, aliases, qualified_names, table_conditions) =\n            extract_all_tables(&select, &schema).unwrap();\n        let view = IncrementalView::new(\n            \"test_view\".to_string(),\n            select.clone(),\n            tables,\n            aliases,\n            qualified_names,\n            table_conditions,\n            extract_view_columns(&select, &schema).unwrap(),\n            &schema,\n            1, // main_data_root\n            2, // internal_state_root\n            3, // internal_state_index_root\n        )\n        .unwrap();\n\n        let queries = view.sql_for_populate().unwrap();\n\n        assert_eq!(queries.len(), 2);\n\n        // With per-table WHERE extraction:\n        // - customers table gets: c.id > 10\n        // - orders table gets: o.total > 100\n        assert!(queries\n            .iter()\n            .any(|q| q == \"SELECT * FROM customers WHERE id > 10\"));\n        assert!(queries\n            .iter()\n            .any(|q| q == \"SELECT * FROM orders WHERE total > 100\"));\n    }\n\n    #[test]\n    fn test_sql_for_populate_complex_join_with_mixed_conditions() {\n        // Test complex JOIN with WHERE conditions mixing both tables\n        let schema = create_test_schema();\n        let select = parse_select(\n            \"SELECT * FROM customers c \\\n             JOIN orders o ON c.id = o.customer_id \\\n             WHERE c.id > 10 AND o.total > 100 AND c.name = 'John' \\\n             AND o.customer_id = 5 AND (c.id = 15 OR o.total = 200)\",\n        );\n\n        let (tables, aliases, qualified_names, table_conditions) =\n            extract_all_tables(&select, &schema).unwrap();\n        let view = IncrementalView::new(\n            \"test_view\".to_string(),\n            select.clone(),\n            tables,\n            aliases,\n            qualified_names,\n            table_conditions,\n            extract_view_columns(&select, &schema).unwrap(),\n            &schema,\n            1, // main_data_root\n            2, // internal_state_root\n            3, // internal_state_index_root\n        )\n        .unwrap();\n\n        let queries = view.sql_for_populate().unwrap();\n\n        assert_eq!(queries.len(), 2);\n\n        // With per-table WHERE extraction:\n        // - customers gets: c.id > 10 AND c.name = 'John'\n        // - orders gets: o.total > 100 AND o.customer_id = 5\n        // Note: The OR condition (c.id = 15 OR o.total = 200) involves both tables,\n        // so it cannot be extracted to either table individually\n        // Check both queries exist (order doesn't matter)\n        assert!(queries\n            .contains(&\"SELECT * FROM customers WHERE id > 10 AND name = 'John'\".to_string()));\n        assert!(queries\n            .contains(&\"SELECT * FROM orders WHERE total > 100 AND customer_id = 5\".to_string()));\n    }\n\n    #[test]\n    fn test_sql_for_populate_table_without_rowid_alias() {\n        let schema = create_test_schema();\n        let select = parse_select(\"SELECT * FROM logs WHERE level > 2\");\n\n        let (tables, aliases, qualified_names, table_conditions) =\n            extract_all_tables(&select, &schema).unwrap();\n        let view = IncrementalView::new(\n            \"test_view\".to_string(),\n            select.clone(),\n            tables,\n            aliases,\n            qualified_names,\n            table_conditions,\n            extract_view_columns(&select, &schema).unwrap(),\n            &schema,\n            1, // main_data_root\n            2, // internal_state_root\n            3, // internal_state_index_root\n        )\n        .unwrap();\n\n        let queries = view.sql_for_populate().unwrap();\n\n        assert_eq!(queries.len(), 1);\n        // logs table has no rowid alias, so we need to explicitly select rowid\n        assert_eq!(queries[0], \"SELECT *, rowid FROM logs WHERE level > 2\");\n    }\n\n    #[test]\n    fn test_sql_for_populate_join_with_and_without_rowid_alias() {\n        // Test JOIN between a table with rowid alias and one without\n        let schema = create_test_schema();\n        let select = parse_select(\n            \"SELECT * FROM customers c \\\n             JOIN logs l ON c.id = l.level \\\n             WHERE c.id > 10 AND l.level > 2\",\n        );\n\n        let (tables, aliases, qualified_names, table_conditions) =\n            extract_all_tables(&select, &schema).unwrap();\n        let view = IncrementalView::new(\n            \"test_view\".to_string(),\n            select.clone(),\n            tables,\n            aliases,\n            qualified_names,\n            table_conditions,\n            extract_view_columns(&select, &schema).unwrap(),\n            &schema,\n            1, // main_data_root\n            2, // internal_state_root\n            3, // internal_state_index_root\n        )\n        .unwrap();\n\n        let queries = view.sql_for_populate().unwrap();\n\n        assert_eq!(queries.len(), 2);\n        // customers has rowid alias (id), logs doesn't\n        assert!(queries.contains(&\"SELECT * FROM customers WHERE id > 10\".to_string()));\n        assert!(queries.contains(&\"SELECT *, rowid FROM logs WHERE level > 2\".to_string()));\n    }\n\n    #[test]\n    fn test_sql_for_populate_with_database_qualified_names() {\n        // Test that database.table.column references are handled correctly\n        // The table name in FROM should keep the database prefix,\n        // but column names in WHERE should be unqualified\n        let schema = create_test_schema();\n\n        // Test with single table using database qualification\n        let select = parse_select(\"SELECT * FROM main.customers WHERE main.customers.id > 10\");\n\n        let (tables, aliases, qualified_names, table_conditions) =\n            extract_all_tables(&select, &schema).unwrap();\n        let view = IncrementalView::new(\n            \"test_view\".to_string(),\n            select.clone(),\n            tables,\n            aliases,\n            qualified_names,\n            table_conditions,\n            extract_view_columns(&select, &schema).unwrap(),\n            &schema,\n            1, // main_data_root\n            2, // internal_state_root\n            3, // internal_state_index_root\n        )\n        .unwrap();\n\n        let queries = view.sql_for_populate().unwrap();\n\n        assert_eq!(queries.len(), 1);\n        // The FROM clause should preserve the database qualification,\n        // but the WHERE clause should have unqualified column names\n        assert_eq!(queries[0], \"SELECT * FROM main.customers WHERE id > 10\");\n    }\n\n    #[test]\n    fn test_sql_for_populate_join_with_database_qualified_names() {\n        // Test JOIN with database-qualified table and column references\n        let schema = create_test_schema();\n\n        let select = parse_select(\n            \"SELECT * FROM main.customers c \\\n             JOIN main.orders o ON c.id = o.customer_id \\\n             WHERE main.customers.id > 10 AND main.orders.total > 100\",\n        );\n\n        let (tables, aliases, qualified_names, table_conditions) =\n            extract_all_tables(&select, &schema).unwrap();\n        let view = IncrementalView::new(\n            \"test_view\".to_string(),\n            select.clone(),\n            tables,\n            aliases,\n            qualified_names,\n            table_conditions,\n            extract_view_columns(&select, &schema).unwrap(),\n            &schema,\n            1, // main_data_root\n            2, // internal_state_root\n            3, // internal_state_index_root\n        )\n        .unwrap();\n\n        let queries = view.sql_for_populate().unwrap();\n\n        assert_eq!(queries.len(), 2);\n        // The FROM clauses should preserve database qualification,\n        // but WHERE clauses should have unqualified column names\n        assert!(queries.contains(&\"SELECT * FROM main.customers WHERE id > 10\".to_string()));\n        assert!(queries.contains(&\"SELECT * FROM main.orders WHERE total > 100\".to_string()));\n    }\n\n    #[test]\n    fn test_where_extraction_for_three_tables_with_aliases() {\n        // Test that WHERE clause extraction correctly separates conditions for 3+ tables\n        // This addresses the concern about conditions \"piling up\" as joins increase\n        let schema = create_test_schema();\n        let select = parse_select(\n            \"SELECT * FROM customers c\n             JOIN orders o ON c.id = o.customer_id\n             JOIN products p ON p.id = o.product_id\n             WHERE c.id > 10 AND o.total > 100 AND p.price > 50\",\n        );\n\n        let (tables, aliases, qualified_names, table_conditions) =\n            extract_all_tables(&select, &schema).unwrap();\n\n        // Verify we extracted all three tables\n        assert_eq!(tables.len(), 3);\n        let table_names: Vec<&str> = tables.iter().map(|t| t.name.as_str()).collect();\n        assert!(table_names.contains(&\"customers\"));\n        assert!(table_names.contains(&\"orders\"));\n        assert!(table_names.contains(&\"products\"));\n\n        // Verify aliases are correctly mapped\n        assert_eq!(aliases.get(\"c\"), Some(&\"customers\".to_string()));\n        assert_eq!(aliases.get(\"o\"), Some(&\"orders\".to_string()));\n        assert_eq!(aliases.get(\"p\"), Some(&\"products\".to_string()));\n\n        // Generate populate queries to verify each table gets its own conditions\n        let queries = IncrementalView::generate_populate_queries(\n            &select,\n            &tables,\n            &aliases,\n            &qualified_names,\n            &table_conditions,\n        )\n        .unwrap();\n\n        assert_eq!(queries.len(), 3);\n\n        // Verify the exact queries generated for each table\n        // The order might vary, so check all possibilities\n        let expected_queries = vec![\n            \"SELECT * FROM customers WHERE id > 10\",\n            \"SELECT * FROM orders WHERE total > 100\",\n            \"SELECT * FROM products WHERE price > 50\",\n        ];\n\n        for expected in &expected_queries {\n            assert!(\n                queries.contains(&expected.to_string()),\n                \"Missing expected query: {expected}. Got: {queries:?}\"\n            );\n        }\n    }\n\n    #[test]\n    fn test_sql_for_populate_complex_expressions_not_included() {\n        // Test that complex expressions (subqueries, CASE, string concat) are NOT included in populate queries\n        let schema = create_test_schema();\n        let select = parse_select(\n            \"SELECT * FROM customers\n             WHERE id > (SELECT MAX(customer_id) FROM orders)\n               AND name || ' Customer' = 'John Customer'\n               AND CASE WHEN id > 10 THEN 1 ELSE 0 END = 1\n               AND EXISTS (SELECT 1 FROM orders WHERE customer_id = customers.id)\",\n        );\n\n        let (tables, aliases, qualified_names, table_conditions) =\n            extract_all_tables(&select, &schema).unwrap();\n\n        let queries = IncrementalView::generate_populate_queries(\n            &select,\n            &tables,\n            &aliases,\n            &qualified_names,\n            &table_conditions,\n        )\n        .unwrap();\n\n        assert_eq!(queries.len(), 1);\n        // Since customers table has an INTEGER PRIMARY KEY (id), we should get SELECT *\n        // without rowid and without WHERE clause (all conditions are complex)\n        assert_eq!(queries[0], \"SELECT * FROM customers\");\n    }\n\n    #[test]\n    fn test_sql_for_populate_unambiguous_unqualified_column() {\n        // Test that unambiguous unqualified columns ARE extracted\n        let schema = create_test_schema();\n        let select = parse_select(\n            \"SELECT * FROM customers c \\\n             JOIN orders o ON c.id = o.customer_id \\\n             WHERE total > 100\", // 'total' only exists in orders table\n        );\n\n        let (tables, aliases, qualified_names, table_conditions) =\n            extract_all_tables(&select, &schema).unwrap();\n        let view = IncrementalView::new(\n            \"test_view\".to_string(),\n            select.clone(),\n            tables,\n            aliases,\n            qualified_names,\n            table_conditions,\n            extract_view_columns(&select, &schema).unwrap(),\n            &schema,\n            1, // main_data_root\n            2, // internal_state_root\n            3, // internal_state_index_root\n        )\n        .unwrap();\n\n        let queries = view.sql_for_populate().unwrap();\n\n        assert_eq!(queries.len(), 2);\n\n        // 'total' is unambiguous (only in orders), so it should be extracted\n        assert!(queries.contains(&\"SELECT * FROM customers\".to_string()));\n        assert!(queries.contains(&\"SELECT * FROM orders WHERE total > 100\".to_string()));\n    }\n\n    #[test]\n    fn test_database_qualified_table_names() {\n        let schema = create_test_schema();\n\n        // Test with database-qualified table names\n        let select = parse_select(\n            \"SELECT c.id, c.name, o.id, o.total\n             FROM main.customers c\n             JOIN main.orders o ON c.id = o.customer_id\n             WHERE c.id > 10\",\n        );\n\n        let (tables, aliases, qualified_names, table_conditions) =\n            extract_all_tables(&select, &schema).unwrap();\n\n        // Check that qualified names are preserved\n        assert!(qualified_names.contains_key(\"customers\"));\n        assert_eq!(qualified_names.get(\"customers\").unwrap(), \"main.customers\");\n        assert!(qualified_names.contains_key(\"orders\"));\n        assert_eq!(qualified_names.get(\"orders\").unwrap(), \"main.orders\");\n\n        let view = IncrementalView::new(\n            \"test_view\".to_string(),\n            select.clone(),\n            tables,\n            aliases,\n            qualified_names,\n            table_conditions,\n            extract_view_columns(&select, &schema).unwrap(),\n            &schema,\n            1, // main_data_root\n            2, // internal_state_root\n            3, // internal_state_index_root\n        )\n        .unwrap();\n\n        let queries = view.sql_for_populate().unwrap();\n\n        assert_eq!(queries.len(), 2);\n\n        // The FROM clause should contain the database-qualified name\n        // But the WHERE clause should use unqualified column names\n        assert!(queries.contains(&\"SELECT * FROM main.customers WHERE id > 10\".to_string()));\n        assert!(queries.contains(&\"SELECT * FROM main.orders\".to_string()));\n    }\n\n    #[test]\n    fn test_mixed_qualified_unqualified_tables() {\n        let schema = create_test_schema();\n\n        // Test with a mix of qualified and unqualified table names\n        let select = parse_select(\n            \"SELECT c.id, c.name, o.id, o.total\n             FROM main.customers c\n             JOIN orders o ON c.id = o.customer_id\n             WHERE c.id > 10 AND o.total < 1000\",\n        );\n\n        let (tables, aliases, qualified_names, table_conditions) =\n            extract_all_tables(&select, &schema).unwrap();\n\n        // Check that qualified names are preserved where specified\n        assert_eq!(qualified_names.get(\"customers\").unwrap(), \"main.customers\");\n        // Unqualified tables should not have an entry (or have the bare name)\n        assert!(\n            !qualified_names.contains_key(\"orders\")\n                || qualified_names.get(\"orders\").unwrap() == \"orders\"\n        );\n\n        let view = IncrementalView::new(\n            \"test_view\".to_string(),\n            select.clone(),\n            tables,\n            aliases,\n            qualified_names,\n            table_conditions,\n            extract_view_columns(&select, &schema).unwrap(),\n            &schema,\n            1, // main_data_root\n            2, // internal_state_root\n            3, // internal_state_index_root\n        )\n        .unwrap();\n\n        let queries = view.sql_for_populate().unwrap();\n\n        assert_eq!(queries.len(), 2);\n\n        // The FROM clause should preserve qualification where specified\n        assert!(queries.contains(&\"SELECT * FROM main.customers WHERE id > 10\".to_string()));\n        assert!(queries.contains(&\"SELECT * FROM orders WHERE total < 1000\".to_string()));\n    }\n\n    #[test]\n    fn test_extract_tables_with_simple_cte() {\n        let schema = create_test_schema();\n        let select = parse_select(\n            \"WITH customer_totals AS (\n                SELECT c.id, c.name, SUM(o.total) as total_spent\n                FROM customers c\n                JOIN orders o ON c.id = o.customer_id\n                GROUP BY c.id, c.name\n            )\n            SELECT * FROM customer_totals WHERE total_spent > 1000\",\n        );\n\n        let (tables, aliases, _qualified_names, _table_conditions) =\n            extract_all_tables(&select, &schema).unwrap();\n\n        // Check that we found both tables from the CTE\n        assert_eq!(tables.len(), 2);\n        let table_names: Vec<&str> = tables.iter().map(|t| t.name.as_str()).collect();\n        assert!(table_names.contains(&\"customers\"));\n        assert!(table_names.contains(&\"orders\"));\n\n        // Check aliases from the CTE\n        assert_eq!(aliases.get(\"c\"), Some(&\"customers\".to_string()));\n        assert_eq!(aliases.get(\"o\"), Some(&\"orders\".to_string()));\n    }\n\n    #[test]\n    fn test_extract_tables_with_multiple_ctes() {\n        let schema = create_test_schema();\n        let select = parse_select(\n            \"WITH\n            high_value_customers AS (\n                SELECT id, name\n                FROM customers\n                WHERE id IN (SELECT customer_id FROM orders WHERE total > 500)\n            ),\n            recent_orders AS (\n                SELECT id, customer_id, total\n                FROM orders\n                WHERE id > 100\n            )\n            SELECT hvc.name, ro.total\n            FROM high_value_customers hvc\n            JOIN recent_orders ro ON hvc.id = ro.customer_id\",\n        );\n\n        let (tables, _aliases, _qualified_names, _table_conditions) =\n            extract_all_tables(&select, &schema).unwrap();\n\n        // Check that we found both tables from both CTEs\n        assert_eq!(tables.len(), 2);\n        let table_names: Vec<&str> = tables.iter().map(|t| t.name.as_str()).collect();\n        assert!(table_names.contains(&\"customers\"));\n        assert!(table_names.contains(&\"orders\"));\n    }\n\n    #[test]\n    fn test_sql_for_populate_union_mixed_conditions() {\n        // Test UNION where same table appears with and without WHERE clause\n        // This should drop ALL conditions to ensure we get all rows\n        let schema = create_test_schema();\n\n        let select = parse_select(\n            \"SELECT * FROM customers WHERE id > 10\n             UNION ALL\n             SELECT * FROM customers\",\n        );\n\n        let (tables, aliases, qualified_names, table_conditions) =\n            extract_all_tables(&select, &schema).unwrap();\n\n        let view = IncrementalView::new(\n            \"union_view\".to_string(),\n            select.clone(),\n            tables,\n            aliases,\n            qualified_names,\n            table_conditions,\n            extract_view_columns(&select, &schema).unwrap(),\n            &schema,\n            1, // main_data_root\n            2, // internal_state_root\n            3, // internal_state_index_root\n        )\n        .unwrap();\n\n        let queries = view.sql_for_populate().unwrap();\n\n        assert_eq!(queries.len(), 1);\n        // When the same table appears with and without WHERE conditions in a UNION,\n        // we must fetch ALL rows (no WHERE clause) because the conditions are incompatible\n        assert_eq!(\n            queries[0], \"SELECT * FROM customers\",\n            \"UNION with mixed conditions (some with WHERE, some without) should fetch ALL rows\"\n        );\n    }\n\n    #[test]\n    fn test_extract_tables_with_nested_cte() {\n        let schema = create_test_schema();\n        let select = parse_select(\n            \"WITH RECURSIVE customer_hierarchy AS (\n                SELECT id, name, 0 as level\n                FROM customers\n                WHERE id = 1\n                UNION ALL\n                SELECT c.id, c.name, ch.level + 1\n                FROM customers c\n                JOIN orders o ON c.id = o.customer_id\n                JOIN customer_hierarchy ch ON o.customer_id = ch.id\n                WHERE ch.level < 3\n            )\n            SELECT * FROM customer_hierarchy\",\n        );\n\n        let (tables, _aliases, _qualified_names, _table_conditions) =\n            extract_all_tables(&select, &schema).unwrap();\n\n        // Check that we found the tables referenced in the recursive CTE\n        let table_names: Vec<&str> = tables.iter().map(|t| t.name.as_str()).collect();\n\n        // We're finding duplicates because \"customers\" appears twice in the recursive CTE\n        // Let's deduplicate\n        let unique_tables: HashSet<&str> = table_names.iter().cloned().collect();\n        assert_eq!(unique_tables.len(), 2);\n        assert!(unique_tables.contains(\"customers\"));\n        assert!(unique_tables.contains(\"orders\"));\n    }\n\n    #[test]\n    fn test_extract_tables_with_cte_and_main_query() {\n        let schema = create_test_schema();\n        let select = parse_select(\n            \"WITH customer_stats AS (\n                SELECT customer_id, COUNT(*) as order_count\n                FROM orders\n                GROUP BY customer_id\n            )\n            SELECT c.name, cs.order_count, p.name as product_name\n            FROM customers c\n            JOIN customer_stats cs ON c.id = cs.customer_id\n            JOIN products p ON p.id = 1\",\n        );\n\n        let (tables, aliases, _qualified_names, _table_conditions) =\n            extract_all_tables(&select, &schema).unwrap();\n\n        // Check that we found tables from both the CTE and the main query\n        assert_eq!(tables.len(), 3);\n        let table_names: Vec<&str> = tables.iter().map(|t| t.name.as_str()).collect();\n        assert!(table_names.contains(&\"customers\"));\n        assert!(table_names.contains(&\"orders\"));\n        assert!(table_names.contains(&\"products\"));\n\n        // Check aliases from main query\n        assert_eq!(aliases.get(\"c\"), Some(&\"customers\".to_string()));\n        assert_eq!(aliases.get(\"p\"), Some(&\"products\".to_string()));\n    }\n\n    #[test]\n    fn test_sql_for_populate_simple_union() {\n        let schema = create_test_schema();\n        let select = parse_select(\n            \"SELECT * FROM orders WHERE total > 1000\n             UNION ALL\n             SELECT * FROM orders WHERE total < 100\",\n        );\n\n        let (tables, aliases, qualified_names, table_conditions) =\n            extract_all_tables(&select, &schema).unwrap();\n\n        // Generate populate queries\n        let queries = IncrementalView::generate_populate_queries(\n            &select,\n            &tables,\n            &aliases,\n            &qualified_names,\n            &table_conditions,\n        )\n        .unwrap();\n\n        // We should have deduplicated to a single table\n        assert_eq!(tables.len(), 1, \"Should have one unique table\");\n        assert_eq!(tables[0].name, \"orders\"); // Single table, order doesn't matter\n\n        // Should have collected two conditions\n        assert_eq!(table_conditions.get(\"orders\").unwrap().len(), 2);\n\n        // Should combine multiple conditions with OR\n        assert_eq!(queries.len(), 1);\n        // Conditions are combined with OR\n        assert_eq!(\n            queries[0],\n            \"SELECT * FROM orders WHERE (total > 1000) OR (total < 100)\"\n        );\n    }\n\n    #[test]\n    fn test_sql_for_populate_with_union_and_filters() {\n        let schema = create_test_schema();\n\n        // Test UNION with different WHERE conditions on the same table\n        let select = parse_select(\n            \"SELECT * FROM orders WHERE total > 1000\n             UNION ALL\n             SELECT * FROM orders WHERE total < 100\",\n        );\n\n        let view = IncrementalView::from_stmt(\n            ast::QualifiedName {\n                db_name: None,\n                name: ast::Name::exact(\"test_view\".to_string()),\n                alias: None,\n            },\n            select,\n            &schema,\n            1,\n            2,\n            3,\n        )\n        .unwrap();\n\n        let queries = view.sql_for_populate().unwrap();\n\n        // We deduplicate tables, so we get 1 query for orders\n        assert_eq!(queries.len(), 1);\n\n        // Multiple conditions on the same table are combined with OR\n        assert_eq!(\n            queries[0],\n            \"SELECT * FROM orders WHERE (total > 1000) OR (total < 100)\"\n        );\n    }\n\n    #[test]\n    fn test_sql_for_populate_with_union_mixed_tables() {\n        let schema = create_test_schema();\n\n        // Test UNION with different tables\n        let select = parse_select(\n            \"SELECT id, name FROM customers WHERE id > 10\n             UNION ALL\n             SELECT customer_id as id, 'Order' as name FROM orders WHERE total > 500\",\n        );\n\n        let view = IncrementalView::from_stmt(\n            ast::QualifiedName {\n                db_name: None,\n                name: ast::Name::exact(\"test_view\".to_string()),\n                alias: None,\n            },\n            select,\n            &schema,\n            1,\n            2,\n            3,\n        )\n        .unwrap();\n\n        let queries = view.sql_for_populate().unwrap();\n\n        assert_eq!(queries.len(), 2, \"Should have one query per table\");\n\n        // Check that each table gets its appropriate WHERE clause\n        let customers_query = queries\n            .iter()\n            .find(|q| q.contains(\"FROM customers\"))\n            .unwrap();\n        let orders_query = queries.iter().find(|q| q.contains(\"FROM orders\")).unwrap();\n\n        assert!(customers_query.contains(\"WHERE id > 10\"));\n        assert!(orders_query.contains(\"WHERE total > 500\"));\n    }\n\n    #[test]\n    fn test_sql_for_populate_duplicate_tables_conflicting_filters() {\n        // This tests what happens when we have duplicate table references with different filters\n        // We need to manually construct a view to simulate what would happen with CTEs\n        let schema = create_test_schema();\n\n        // Get the orders table twice (simulating what would happen with CTEs)\n        let orders_table = schema.get_btree_table(\"orders\").unwrap();\n\n        let referenced_tables = vec![orders_table.clone(), orders_table];\n\n        // Create a SELECT that would have conflicting WHERE conditions\n        let select = parse_select(\n            \"SELECT * FROM orders WHERE total > 1000\", // This is just for the AST\n        );\n\n        let view = IncrementalView::new(\n            \"test_view\".to_string(),\n            select.clone(),\n            referenced_tables,\n            HashMap::default(),\n            HashMap::default(),\n            HashMap::default(),\n            extract_view_columns(&select, &schema).unwrap(),\n            &schema,\n            1,\n            2,\n            3,\n        )\n        .unwrap();\n\n        let queries = view.sql_for_populate().unwrap();\n\n        // With duplicates, we should get 2 identical queries\n        assert_eq!(queries.len(), 2);\n\n        // Both should be the same since they're from the same table reference\n        assert_eq!(queries[0], queries[1]);\n    }\n\n    #[test]\n    fn test_table_extraction_with_nested_ctes_complex_conditions() {\n        let schema = create_test_schema();\n        let select = parse_select(\n            \"WITH\n            customer_orders AS (\n                SELECT c.*, o.total\n                FROM customers c\n                JOIN orders o ON c.id = o.customer_id\n                WHERE c.name LIKE 'A%' AND o.total > 100\n            ),\n            top_customers AS (\n                SELECT * FROM customer_orders WHERE total > 500\n            )\n            SELECT * FROM top_customers\",\n        );\n\n        // Test table extraction directly without creating a view\n        let mut tables = Vec::new();\n        let mut aliases = HashMap::default();\n        let mut qualified_names = HashMap::default();\n        let mut table_conditions = HashMap::default();\n\n        IncrementalView::extract_all_tables(\n            &select,\n            &schema,\n            &mut tables,\n            &mut aliases,\n            &mut qualified_names,\n            &mut table_conditions,\n        )\n        .unwrap();\n\n        let table_names: Vec<&str> = tables.iter().map(|t| t.name.as_str()).collect();\n\n        // Should have one reference to each table\n        assert_eq!(table_names.len(), 2, \"Should have 2 table references\");\n        assert!(table_names.contains(&\"customers\"));\n        assert!(table_names.contains(&\"orders\"));\n\n        // Check aliases\n        assert_eq!(aliases.get(\"c\"), Some(&\"customers\".to_string()));\n        assert_eq!(aliases.get(\"o\"), Some(&\"orders\".to_string()));\n    }\n\n    #[test]\n    fn test_union_all_populate_queries() {\n        // Test that UNION ALL generates correct populate queries\n        let schema = create_test_schema();\n\n        // Create a UNION ALL query that references the same table twice with different WHERE conditions\n        let sql = \"\n            SELECT id, name FROM customers WHERE id < 5\n            UNION ALL\n            SELECT id, name FROM customers WHERE id > 10\n        \";\n\n        let mut parser = Parser::new(sql.as_bytes());\n        let cmd = parser.next_cmd().unwrap();\n        let select_stmt = match cmd.unwrap() {\n            turso_parser::ast::Cmd::Stmt(ast::Stmt::Select(select)) => select,\n            _ => panic!(\"Expected SELECT statement\"),\n        };\n\n        // Extract tables and conditions\n        let (tables, aliases, qualified_names, conditions) =\n            extract_all_tables(&select_stmt, &schema).unwrap();\n\n        // Generate populate queries\n        let queries = IncrementalView::generate_populate_queries(\n            &select_stmt,\n            &tables,\n            &aliases,\n            &qualified_names,\n            &conditions,\n        )\n        .unwrap();\n\n        // Expected query - assuming customers table has INTEGER PRIMARY KEY\n        // so we don't need to select rowid separately\n        let expected = \"SELECT * FROM customers WHERE (id < 5) OR (id > 10)\";\n\n        assert_eq!(\n            queries.len(),\n            1,\n            \"Should generate exactly 1 query for UNION ALL with same table\"\n        );\n        assert_eq!(queries[0], expected, \"Query should match expected format\");\n    }\n\n    #[test]\n    fn test_union_all_different_tables_populate_queries() {\n        // Test UNION ALL with different tables\n        let schema = create_test_schema();\n\n        let sql = \"\n            SELECT id, name FROM customers WHERE id < 5\n            UNION ALL\n            SELECT id, product_name FROM orders WHERE amount > 100\n        \";\n\n        let mut parser = Parser::new(sql.as_bytes());\n        let cmd = parser.next_cmd().unwrap();\n        let select_stmt = match cmd.unwrap() {\n            turso_parser::ast::Cmd::Stmt(ast::Stmt::Select(select)) => select,\n            _ => panic!(\"Expected SELECT statement\"),\n        };\n\n        // Extract tables and conditions\n        let (tables, aliases, qualified_names, conditions) =\n            extract_all_tables(&select_stmt, &schema).unwrap();\n\n        // Generate populate queries\n        let queries = IncrementalView::generate_populate_queries(\n            &select_stmt,\n            &tables,\n            &aliases,\n            &qualified_names,\n            &conditions,\n        )\n        .unwrap();\n\n        // Should generate separate queries for each table\n        assert_eq!(\n            queries.len(),\n            2,\n            \"Should generate 2 queries for different tables\"\n        );\n\n        // Check we have queries for both tables\n        let has_customers = queries.iter().any(|q| q.contains(\"customers\"));\n        let has_orders = queries.iter().any(|q| q.contains(\"orders\"));\n        assert!(has_customers, \"Should have a query for customers table\");\n        assert!(has_orders, \"Should have a query for orders table\");\n\n        // Verify the customers query has its WHERE clause\n        let customers_query = queries\n            .iter()\n            .find(|q| q.contains(\"customers\"))\n            .expect(\"Should have customers query\");\n        assert!(\n            customers_query.contains(\"WHERE\"),\n            \"Customers query should have WHERE clause\"\n        );\n    }\n}\n"
  },
  {
    "path": "core/index_method/backing_btree.rs",
    "content": "use crate::sync::Arc;\n\nuse crate::{\n    index_method::{\n        IndexMethod, IndexMethodAttachment, IndexMethodConfiguration, IndexMethodCursor,\n        IndexMethodDefinition, BACKING_BTREE_INDEX_METHOD_NAME,\n    },\n    LimboError, Result,\n};\n\n/// Special 'backing_btree' index method which can be used by other custom index methods\n///\n/// Under the hood, it's marked as 'treat_as_btree' which recognized by the tursodb core as a special index method\n/// which should be translated to ordinary btree but also do not explicitly managed by the core\n#[derive(Debug)]\npub struct BackingBtreeIndexMethod;\n\n#[derive(Debug)]\npub struct BackingBTreeIndexMethodAttachment(String);\n\nimpl IndexMethod for BackingBtreeIndexMethod {\n    fn attach(\n        &self,\n        configuration: &IndexMethodConfiguration,\n    ) -> Result<Arc<dyn IndexMethodAttachment>> {\n        Ok(Arc::new(BackingBTreeIndexMethodAttachment(\n            configuration.index_name.clone(),\n        )))\n    }\n}\n\nimpl IndexMethodAttachment for BackingBTreeIndexMethodAttachment {\n    fn definition<'a>(&'a self) -> IndexMethodDefinition<'a> {\n        IndexMethodDefinition {\n            method_name: BACKING_BTREE_INDEX_METHOD_NAME,\n            index_name: &self.0,\n            patterns: &[],\n            backing_btree: true,\n            results_materialized: false,\n        }\n    }\n\n    fn init(&self) -> Result<Box<dyn IndexMethodCursor>> {\n        Err(LimboError::InternalError(\n            \"init is not supported for backing_btree index method\".to_string(),\n        ))\n    }\n}\n"
  },
  {
    "path": "core/index_method/fts.rs",
    "content": "use crate::sync::Arc;\nuse crate::turso_assert;\nuse crate::turso_debug_assert;\nuse crate::{\n    index_method::{\n        parse_patterns, IndexMethod, IndexMethodAttachment, IndexMethodConfiguration,\n        IndexMethodCursor, IndexMethodDefinition,\n    },\n    return_if_io,\n    schema::IndexColumn,\n    storage::{\n        btree::{BTreeCursor, BTreeKey, CursorTrait},\n        pager::Pager,\n    },\n    translate::collate::CollationSeq,\n    types::{IOResult, ImmutableRecord, IndexInfo, KeyInfo, SeekKey, SeekOp, SeekResult, Text},\n    vdbe::Register,\n    Connection, LimboError, Result, Value,\n};\nuse parking_lot::RwLock;\nuse rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};\nuse std::io::{BufWriter, Write};\nuse std::ops::Range;\nuse std::path::{Path, PathBuf};\nuse std::{cell::RefCell, sync::atomic::Ordering};\nuse tantivy::{\n    directory::{\n        error::{DeleteError, OpenReadError, OpenWriteError},\n        Directory, FileHandle, OwnedBytes, TerminatingWrite, WatchCallback, WatchHandle,\n    },\n    merge_policy::NoMergePolicy,\n    schema::{Field, Schema},\n    tokenizer::{\n        NgramTokenizer, RawTokenizer, SimpleTokenizer, TextAnalyzer, TokenStream,\n        WhitespaceTokenizer,\n    },\n    DocAddress, HasLen, Index, IndexReader, IndexSettings, IndexWriter, Searcher, TantivyDocument,\n};\nuse turso_parser::ast::{self, Select, SortOrder};\n\n/// Name identifier for the FTS index method, used in `CREATE INDEX ... USING fts`.\npub const FTS_INDEX_METHOD_NAME: &str = \"fts\";\n\n/// Default memory budget (64MB) for Tantivy's IndexWriter.\n/// Controls how much memory Tantivy uses for in-memory indexing before flushing to disk.\npub const DEFAULT_MEMORY_BUDGET_BYTES: usize = 64 * 1024 * 1024;\n\n/// Default chunk size (152KB) for splitting large files when storing in BTree.\n/// Files larger than this are split into multiple chunks for efficient storage and retrieval.\npub const DEFAULT_CHUNK_SIZE: usize = 512 * 1024;\n\n/// Number of documents to batch before committing to Tantivy.\n/// Higher values improve throughput but increase memory usage and latency.\npub const BATCH_COMMIT_SIZE: usize = 1000;\n\n/// Default memory budget (64MB) for hot cache (metadata + term dictionaries).\n/// Hot files are frequently accessed and kept in an LRU cache.\npub const DEFAULT_HOT_CACHE_BYTES: usize = 64 * 1024 * 1024;\n\n/// Default memory budget (128MB) for chunk LRU cache.\n/// Caches segment data chunks loaded on-demand from the BTree.\npub const DEFAULT_CHUNK_CACHE_BYTES: usize = 128 * 1024 * 1024;\n\nconst ROWID_FIELD: &str = \"rowid\";\n\n// Thread-local tokenizer cache to avoid creating a new tokenizer for each call.\n// TextAnalyzer is not Send/Sync, so we use thread_local storage.\ncrate::thread::thread_local! {\n    static FTS_TOKENIZER: RefCell<TextAnalyzer> = RefCell::new(\n        TextAnalyzer::builder(SimpleTokenizer::default())\n            .filter(tantivy::tokenizer::LowerCaser)\n            .build()\n    );\n}\n\n/// Highlight matching terms in text by wrapping them with tags.\n///\n/// Standalone function that can be used without an FTS index.\n/// It tokenizes both the query and text using Tantivy's default tokenizer,\n/// finds matching terms, and wraps them with the specified tags.\npub fn fts_highlight(text: &str, query: &str, before_tag: &str, after_tag: &str) -> String {\n    if text.is_empty() || query.is_empty() {\n        return text.to_string();\n    }\n\n    FTS_TOKENIZER.with(|tokenizer| {\n        let mut tokenizer = tokenizer.borrow_mut();\n\n        // Extract query terms (lowercased)\n        let query_terms: HashSet<String> = {\n            let mut terms = HashSet::default();\n            let mut query_stream = tokenizer.token_stream(query);\n            while let Some(token) = query_stream.next() {\n                terms.insert(token.text.to_string());\n            }\n            terms\n        };\n        if query_terms.is_empty() {\n            return text.to_string();\n        }\n\n        // Tokenize the text and track positions of matching tokens\n        let match_ranges: Vec<(usize, usize)> = {\n            let mut ranges = Vec::new();\n            let mut text_stream = tokenizer.token_stream(text);\n            while let Some(token) = text_stream.next() {\n                if query_terms.contains(&token.text) {\n                    ranges.push((token.offset_from, token.offset_to));\n                }\n            }\n            ranges\n        };\n\n        if match_ranges.is_empty() {\n            return text.to_string();\n        }\n\n        // Optimized string building: pre-calculate size and build forward\n        let extra_len = match_ranges.len() * (before_tag.len() + after_tag.len());\n        let mut result = String::with_capacity(text.len() + extra_len);\n        let mut last_end = 0;\n\n        for (start, end) in &match_ranges {\n            // Validate UTF-8 boundaries\n            if *start > text.len()\n                || *end > text.len()\n                || !text.is_char_boundary(*start)\n                || !text.is_char_boundary(*end)\n            {\n                continue;\n            }\n\n            // Append text before this match\n            if *start > last_end {\n                result.push_str(&text[last_end..*start]);\n            }\n\n            // Append highlighted match\n            result.push_str(before_tag);\n            result.push_str(&text[*start..*end]);\n            result.push_str(after_tag);\n\n            last_end = *end;\n        }\n\n        // Append remaining text after last match\n        if last_end < text.len() {\n            result.push_str(&text[last_end..]);\n        }\n\n        result\n    })\n}\n\n/// Check if text matches a query by testing for any common terms.\n///\n/// Standalone function that can be used without an FTS index.\n/// It tokenizes both the query and text using Tantivy's default tokenizer,\n/// and returns true if any query terms appear in the text.\npub fn fts_match(text: &str, query: &str) -> bool {\n    if text.is_empty() || query.is_empty() {\n        return false;\n    }\n\n    FTS_TOKENIZER.with(|tokenizer| {\n        let mut tokenizer = tokenizer.borrow_mut();\n\n        // Extract query terms (lowercased)\n        let query_terms: HashSet<String> = {\n            let mut terms = HashSet::default();\n            let mut query_stream = tokenizer.token_stream(query);\n            while let Some(token) = query_stream.next() {\n                terms.insert(token.text.to_string());\n            }\n            terms\n        };\n        if query_terms.is_empty() {\n            return false;\n        }\n\n        // Tokenize the text and check if any query terms appear\n        let mut text_stream = tokenizer.token_stream(text);\n        while let Some(token) = text_stream.next() {\n            if query_terms.contains(&token.text) {\n                return true;\n            }\n        }\n        false\n    })\n}\n\n/// File classification for hybrid caching strategy.\n/// Determines which files are kept hot in memory vs lazy-loaded on demand.\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\nenum FileCategory {\n    /// Always in memory: meta.json, .managed.json, .lock (typically < 64KB)\n    Metadata,\n    /// Hot files: .term dictionaries - loaded on first access, kept in LRU\n    TermDictionary,\n    /// Fast fields and field norms - small, frequently accessed\n    FastFields,\n    /// Cold files: .idx, .pos, .store - lazy-loaded on demand\n    SegmentData,\n}\n\nimpl FileCategory {\n    const METADATA_FILES: [&'static str; 3] = [TANTIVY_META_FILE, \".managed.json\", \".lock\"];\n    /// Classify a file based on its path/extension.\n    /// https://fulmicoton.gitbooks.io/tantivy-doc/content/index-files.html\n    fn from_path(path: &Path) -> Self {\n        let name = path.file_name().and_then(|n| n.to_str()).unwrap_or(\"\");\n        let ext = path.extension().and_then(|e| e.to_str()).unwrap_or(\"\");\n\n        // Check for known Tantivy metadata files first\n        if Self::METADATA_FILES.contains(&name) {\n            return FileCategory::Metadata;\n        }\n\n        match ext {\n            // Term dictionary - hot for queries\n            \"term\" => FileCategory::TermDictionary,\n            // Fast fields and field norms - small, frequently accessed\n            \"fast\" | \"fieldnorm\" => FileCategory::FastFields,\n            // Segment data - large, lazy-loaded\n            \"idx\" | \"pos\" | \"store\" => FileCategory::SegmentData,\n            \"lock\" | \"info\" => FileCategory::Metadata,\n            // Default to segment data (lazy-loaded)\n            _ => FileCategory::SegmentData,\n        }\n    }\n\n    /// Returns true if files in this category should be preloaded at startup.\n    const fn should_preload(&self) -> bool {\n        matches!(self, FileCategory::Metadata)\n    }\n\n    /// Returns true if files in this category should be kept in the hot cache.\n    const fn is_hot(&self) -> bool {\n        matches!(\n            self,\n            FileCategory::Metadata | FileCategory::TermDictionary | FileCategory::FastFields\n        )\n    }\n}\n\n/// Metadata about a file stored in the FTS directory.\n/// Used for catalog-first loading where we build file metadata without loading content.\n#[derive(Debug, Clone)]\nstruct FileMetadata {\n    /// Total file size in bytes\n    size: usize,\n    /// Number of chunks this file is split into\n    num_chunks: usize,\n    /// File category for caching decisions\n    category: FileCategory,\n}\n\nimpl FileMetadata {\n    fn new(path: &Path, size: usize, num_chunks: usize) -> Self {\n        Self {\n            size,\n            num_chunks,\n            category: FileCategory::from_path(path),\n        }\n    }\n}\n\ntype ChunkKey = (PathBuf, i64);\n\n/// Eviction samples per put\nconst EVICTION_SAMPLES: usize = 8;\n\n/// Generic bounded LRU cache with sampling-based eviction.\npub struct LruCache<K> {\n    capacity: usize,\n    inner: RwLock<LruCacheInner<K>>,\n}\n\n#[derive(Debug)]\nstruct LruCacheInner<K> {\n    current_size: usize,\n    clock: u64,\n    entries: HashMap<K, LruCacheEntry>,\n}\n\n#[derive(Debug)]\nstruct LruCacheEntry {\n    data: Arc<[u8]>,\n    accessed: u64,\n}\n\nimpl<K: std::fmt::Debug> std::fmt::Debug for LruCache<K> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let inner = self.inner.read();\n        f.debug_struct(\"LruCache\")\n            .field(\"capacity\", &self.capacity)\n            .field(\"current_size\", &inner.current_size)\n            .field(\"entries\", &inner.entries.len())\n            .finish()\n    }\n}\n\nimpl<K: Eq + std::hash::Hash + Clone> LruCache<K> {\n    /// Creates a new empty cache with the specified capacity in bytes.\n    fn new(capacity: usize) -> Self {\n        Self {\n            capacity,\n            inner: RwLock::new(LruCacheInner {\n                current_size: 0,\n                clock: 0,\n                entries: HashMap::default(),\n            }),\n        }\n    }\n\n    /// Lookup entry, updating access timestamp. Returns Arc-cloned data.\n    fn get<Q>(&self, key: &Q) -> Option<Arc<[u8]>>\n    where\n        K: std::borrow::Borrow<Q>,\n        Q: Eq + std::hash::Hash + ?Sized,\n    {\n        let mut inner = self.inner.write();\n        inner.clock += 1;\n        let ts = inner.clock;\n        if let Some(entry) = inner.entries.get_mut(key) {\n            entry.accessed = ts;\n            Some(Arc::clone(&entry.data))\n        } else {\n            None\n        }\n    }\n\n    /// Insert entry, evicting stale entries if over capacity.\n    ///\n    /// Eviction uses sampling: examines K entries and evicts the one with\n    /// the oldest access timestamp. Repeat until under capacity.\n    fn put(&self, key: K, value: Vec<u8>) {\n        let arc_value: Arc<[u8]> = Arc::from(value);\n        let size = arc_value.len();\n        let mut inner = self.inner.write();\n\n        // Check for existing entry - get old size if present\n        let old_size = inner.entries.get(&key).map(|e| e.data.len());\n\n        if let Some(old) = old_size {\n            // Update existing entry\n            inner.clock += 1;\n            let ts = inner.clock;\n            let entry = inner.entries.get_mut(&key).expect(\"entry must exist\");\n            entry.data = arc_value;\n            entry.accessed = ts;\n            inner.current_size = inner.current_size - old + size;\n            return;\n        }\n\n        // Evict until under capacity\n        while inner.current_size + size > self.capacity && !inner.entries.is_empty() {\n            let victim = {\n                inner\n                    .entries\n                    .iter()\n                    .take(EVICTION_SAMPLES)\n                    .min_by_key(|(_, e)| e.accessed)\n                    .map(|(k, _)| k.clone())\n            };\n\n            match victim {\n                Some(k) => {\n                    if let Some(e) = inner.entries.remove(&k) {\n                        inner.current_size -= e.data.len();\n                    }\n                }\n                None => break,\n            }\n        }\n\n        inner.clock += 1;\n        let ts = inner.clock;\n        inner.entries.insert(\n            key,\n            LruCacheEntry {\n                data: arc_value,\n                accessed: ts,\n            },\n        );\n        inner.current_size += size;\n    }\n\n    /// Remove an entry from the cache.\n    fn remove<Q>(&self, key: &Q)\n    where\n        K: std::borrow::Borrow<Q>,\n        Q: Eq + std::hash::Hash + ?Sized,\n    {\n        let mut inner = self.inner.write();\n        if let Some(e) = inner.entries.remove(key) {\n            inner.current_size -= e.data.len();\n        }\n    }\n\n    /// Current memory usage in bytes.\n    fn size(&self) -> usize {\n        self.inner.read().current_size\n    }\n\n    /// Number of entries in the cache.\n    fn len(&self) -> usize {\n        self.inner.read().entries.len()\n    }\n\n    /// Check if key exists in cache.\n    fn contains<Q>(&self, key: &Q) -> bool\n    where\n        K: std::borrow::Borrow<Q>,\n        Q: Eq + std::hash::Hash + ?Sized,\n    {\n        self.inner.read().entries.contains_key(key)\n    }\n}\n\n/// Specialized methods for ChunkKey (PathBuf, i64) caches.\nimpl LruCache<ChunkKey> {\n    /// Invalidate all chunks for a file path.\n    /// Called when a file is deleted or overwritten.\n    fn invalidate(&self, path: &Path) {\n        let mut inner = self.inner.write();\n        let mut freed = 0usize;\n        inner.entries.retain(|(p, _), e| {\n            if p == path {\n                freed += e.data.len();\n                false\n            } else {\n                true\n            }\n        });\n        inner.current_size -= freed;\n    }\n}\n\n/// Specialized methods for PathBuf caches (hot files).\nimpl LruCache<PathBuf> {\n    /// Create from preloaded files (used during initialization).\n    fn with_preloaded(capacity: usize, files: HashMap<PathBuf, Vec<u8>>) -> Self {\n        let current_size: usize = files.values().map(|v| v.len()).sum();\n        let entries: HashMap<PathBuf, LruCacheEntry> = files\n            .into_iter()\n            .enumerate()\n            .map(|(i, (path, data))| {\n                (\n                    path,\n                    LruCacheEntry {\n                        data: Arc::from(data),\n                        accessed: i as u64,\n                    },\n                )\n            })\n            .collect();\n\n        Self {\n            capacity,\n            inner: RwLock::new(LruCacheInner {\n                current_size,\n                clock: entries.len() as u64,\n                entries,\n            }),\n        }\n    }\n}\n\n/// Type aliases to please the almighty clippy\ntype Catalog = HashMap<PathBuf, FileMetadata>;\ntype PendingWrites = HashMap<PathBuf, Vec<u8>>;\n\n/// Tantivy Directory implementation backed by Turso's BTree storage.\n///\n/// Tantivy stores its index as a collection of files (segments, metadata, term dictionaries, etc.).\n/// The `Directory` trait is Tantivy's storage abstraction for reading, writing, and managing\n/// these files. Tantivy's Directory methods are synchronous, so we must do blocking IO to back\n/// these operations and cache data in memory for performance.\n///\n/// FTS index files are stored in a BTree with the schema `(path TEXT, chunk_no INTEGER, bytes BLOB)`.\n/// Large files are split into chunks of `DEFAULT_CHUNK_SIZE` (1MB) to enable efficient\n/// partial reads and bounded memory usage during loading.\n///\n/// We use a two-tier caching strategy to optimize for Tantivy's access patterns:\n///\n/// 1. `hot_cache` (keyed by `PathBuf`): Caches entire files for small,\n///    frequently-accessed files that benefit from being fully resident in memory:\n///    - Metadata files (meta.json, .managed.json, .lock)\n///    - Term dictionaries (.term) - critical for query performance\n///    - Fast fields and field norms (.fast, .fieldnorm)\n///\n/// 2. `chunk_cache` (keyed by `(PathBuf, chunk_no)`): Caches individual 1MB chunks\n///    of large segment files. Large files like posting lists (.idx), positions (.pos),\n///    and document store (.store) are split into 1MB chunks when stored in the BTree.\n///    When Tantivy reads a byte range, we load only the chunks covering that range,\n///    allowing partial file access without loading entire multi-MB files into memory.\n///\n/// Writes are buffered in memory (`pending_writes`) and flushed to the BTree when:\n/// - A Tantivy commit occurs (via `commit_and_flush`)\n/// - The cursor is dropped with pending documents\n/// - The transaction is about to commit (via `pre_commit`)\n///\n/// During flush, writes are moved to `flushing_writes` so they remain readable while\n/// the async BTree write completes.\n#[derive(Clone)]\nstruct HybridBTreeDirectory {\n    /// File catalog: path -> metadata (always in memory, no content)\n    catalog: Arc<RwLock<Catalog>>,\n\n    /// Hot cache: LRU cache for frequently accessed files (metadata, term dictionaries)\n    /// Bounded to DEFAULT_HOT_CACHE_BYTES (64MB) to prevent unbounded memory growth\n    hot_cache: Arc<LruCache<PathBuf>>,\n\n    /// Chunk cache: LRU cache for lazy-loaded segment chunks\n    chunk_cache: Arc<LruCache<ChunkKey>>,\n\n    /// Pending writes to be flushed to BTree\n    pending_writes: Arc<RwLock<PendingWrites>>,\n\n    /// Writes currently being flushed to BTree (still readable during flush)\n    /// This preserves data for reads during async flush operations\n    flushing_writes: Arc<RwLock<HashMap<PathBuf, Vec<u8>>>>,\n\n    /// Pending deletes to be flushed to BTree\n    pending_deletes: Arc<RwLock<Vec<PathBuf>>>,\n\n    /// Reference to pager for IO\n    pager: Arc<Pager>,\n\n    /// BTree root page for the FTS directory index\n    btree_root_page: i64,\n}\n\nimpl std::fmt::Debug for HybridBTreeDirectory {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"HybridBTreeDirectory\")\n            .field(\"catalog_size\", &self.catalog.read().len())\n            .field(\"hot_cache_size\", &self.hot_cache.len())\n            .field(\"hot_cache_bytes\", &self.hot_cache.size())\n            .field(\"chunk_cache_size\", &self.chunk_cache.size())\n            .field(\"btree_root_page\", &self.btree_root_page)\n            .finish()\n    }\n}\n\nimpl HybridBTreeDirectory {\n    /// Create a clone with fresh (empty) pending state.\n    /// This is used when creating a new cursor from a cached directory to ensure\n    /// each cursor has its own isolated pending_writes/pending_deletes.\n    /// This prevents the bug where writes from one cursor affect the Drop behavior\n    /// of another cursor.\n    fn clone_with_fresh_pending(&self) -> Self {\n        Self {\n            catalog: Arc::clone(&self.catalog),\n            hot_cache: Arc::clone(&self.hot_cache),\n            chunk_cache: Arc::clone(&self.chunk_cache),\n            // Fresh pending state - not shared with cache\n            pending_writes: Arc::new(RwLock::new(HashMap::default())),\n            flushing_writes: Arc::new(RwLock::new(HashMap::default())),\n            pending_deletes: Arc::new(RwLock::new(Vec::new())),\n            pager: Arc::clone(&self.pager),\n            btree_root_page: self.btree_root_page,\n        }\n    }\n}\n\nfn io_not_found<M: Into<Box<dyn std::error::Error + Send + Sync>>>(msg: M) -> std::io::Error {\n    std::io::Error::new(std::io::ErrorKind::NotFound, msg)\n}\n\nimpl HybridBTreeDirectory {\n    /// Create from preloaded catalog and hot cache files.\n    fn with_preloaded(\n        pager: Arc<Pager>,\n        btree_root_page: i64,\n        catalog: HashMap<PathBuf, FileMetadata>,\n        hot_files: HashMap<PathBuf, Vec<u8>>,\n        hot_cache_capacity: usize,\n        chunk_cache_capacity: usize,\n    ) -> Self {\n        Self {\n            catalog: Arc::new(RwLock::new(catalog)),\n            hot_cache: Arc::new(LruCache::<PathBuf>::with_preloaded(\n                hot_cache_capacity,\n                hot_files,\n            )),\n            chunk_cache: Arc::new(LruCache::<ChunkKey>::new(chunk_cache_capacity)),\n            pending_writes: Arc::new(RwLock::new(HashMap::default())),\n            flushing_writes: Arc::new(RwLock::new(HashMap::default())),\n            pending_deletes: Arc::new(RwLock::new(Vec::new())),\n            pager,\n            btree_root_page,\n        }\n    }\n\n    /// Get pending writes for flushing.\n    /// With HashMap, writes are automatically deduplicated (only latest write per path is kept).\n    /// The writes are also copied to flushing_writes so they remain readable during async flush.\n    fn take_pending_writes(&self) -> Vec<(PathBuf, Vec<u8>)> {\n        let mut pending = self.pending_writes.write();\n        let writes_map = std::mem::take(&mut *pending);\n\n        // Convert HashMap to Vec for the state machine\n        let writes: Vec<(PathBuf, Vec<u8>)> = writes_map.into_iter().collect();\n\n        // Copy to flushing_writes so data remains readable during async flush\n        {\n            let mut flushing = self.flushing_writes.write();\n            for (path, data) in &writes {\n                flushing.insert(path.clone(), data.clone());\n            }\n        }\n\n        tracing::debug!(\"FTS take_pending_writes: {} entries\", writes.len());\n        writes\n    }\n\n    /// Clear flushing_writes after flush completes successfully.\n    /// Call this after all writes have been persisted to BTree.\n    fn complete_flush(&self) {\n        let mut flushing = self.flushing_writes.write();\n        tracing::debug!(\n            \"FTS complete_flush: clearing {} entries from flushing_writes\",\n            flushing.len()\n        );\n        flushing.clear();\n    }\n\n    /// Find file data in pending writes or flushing writes.\n    /// Checks pending_writes first (O(1) HashMap lookup), then flushing_writes.\n    fn find_in_pending_writes(&self, path: &Path) -> Option<Vec<u8>> {\n        // Check pending_writes first (most recent) - O(1) lookup\n        {\n            let pending = self.pending_writes.read();\n            if let Some(data) = pending.get(path) {\n                return Some(data.clone());\n            }\n        }\n        // Check flushing_writes (data being flushed but not yet in BTree)\n        {\n            let flushing = self.flushing_writes.read();\n            if let Some(data) = flushing.get(path) {\n                return Some(data.clone());\n            }\n        }\n\n        None\n    }\n\n    const CHUNK_LEN: usize = 3;\n\n    /// Blocking read of a range of chunks from BTree using a single cursor.\n    /// Efficient for both single and multiple chunk reads, as it only seeks once\n    /// and advances sequentially.\n    fn get_chunks_range_blocking(\n        &self,\n        path: &Path,\n        start_chunk: usize,\n        end_chunk: usize,\n    ) -> std::io::Result<Vec<Arc<[u8]>>> {\n        if start_chunk > end_chunk {\n            return Ok(Vec::new());\n        }\n\n        let mut chunks = Vec::with_capacity(end_chunk - start_chunk + 1);\n        let path_str = path.to_string_lossy().to_string();\n\n        // Check cache for all requested chunks first\n        let mut uncached_start = None;\n        for chunk_no in start_chunk..=end_chunk {\n            let cache_key = (path.to_path_buf(), chunk_no as i64);\n            if let Some(chunk) = self.chunk_cache.get(&cache_key) {\n                chunks.push(chunk);\n            } else {\n                // Found first uncached chunk\n                uncached_start = Some(chunk_no);\n                break;\n            }\n        }\n\n        // If all chunks were cached, return them\n        if uncached_start.is_none() {\n            return Ok(chunks);\n        }\n\n        let uncached_start = uncached_start.unwrap();\n\n        // Create cursor and seek to first uncached chunk\n        let mut cursor =\n            BTreeCursor::new(self.pager.clone(), self.btree_root_page, Self::CHUNK_LEN);\n        cursor.index_info = Some(Arc::new(IndexInfo {\n            has_rowid: false,\n            num_cols: Self::CHUNK_LEN,\n            key_info: vec![key_info(), key_info(), key_info()],\n            is_unique: false,\n        }));\n\n        let seek_key = ImmutableRecord::from_values(\n            &[\n                Value::Text(Text::new(path_str.clone())),\n                Value::from_i64(uncached_start as i64),\n                Value::Blob(vec![]),\n            ],\n            Self::CHUNK_LEN,\n        );\n\n        // Blocking seek to first chunk\n        loop {\n            match cursor.seek(SeekKey::IndexKey(&seek_key), SeekOp::GE { eq_only: false }) {\n                Ok(IOResult::Done(SeekResult::Found)) => break,\n                Ok(IOResult::Done(SeekResult::TryAdvance)) => {\n                    loop {\n                        match cursor.next() {\n                            Ok(IOResult::Done(_)) => {\n                                if !cursor.has_record() {\n                                    return Err(io_not_found(format!(\n                                        \"chunk {}:{} not found\",\n                                        path.display(),\n                                        uncached_start\n                                    )));\n                                }\n                                break;\n                            }\n                            Ok(IOResult::IO(_)) => {\n                                self.pager\n                                    .io\n                                    .step()\n                                    .map_err(|e| std::io::Error::other(e.to_string()))?;\n                            }\n                            Err(e) => return Err(std::io::Error::other(e.to_string())),\n                        }\n                    }\n                    break;\n                }\n                Ok(IOResult::Done(SeekResult::NotFound)) => {\n                    return Err(io_not_found(format!(\n                        \"chunk {}:{} not found\",\n                        path.display(),\n                        uncached_start\n                    )));\n                }\n                Ok(IOResult::IO(_)) => {\n                    self.pager\n                        .io\n                        .step()\n                        .map_err(|e| std::io::Error::other(e.to_string()))?;\n                }\n                Err(e) => return Err(std::io::Error::other(e.to_string())),\n            }\n        }\n\n        // Read remaining chunks sequentially\n        for expected_chunk_no in uncached_start..=end_chunk {\n            // Check if cursor has a record\n            if !cursor.has_record() {\n                return Err(io_not_found(format!(\n                    \"chunk {}:{} not found (cursor exhausted)\",\n                    path.display(),\n                    expected_chunk_no\n                )));\n            }\n\n            // Read current record\n            let record = loop {\n                match cursor.record() {\n                    Ok(IOResult::Done(r)) => break r,\n                    Ok(IOResult::IO(_)) => {\n                        self.pager\n                            .io\n                            .step()\n                            .map_err(|e| std::io::Error::other(e.to_string()))?;\n                    }\n                    Err(e) => return Err(std::io::Error::other(e.to_string())),\n                }\n            };\n\n            let record = record.ok_or_else(|| io_not_found(\"no record at cursor\"))?;\n\n            // Extract and validate\n            let found_path = record.get_value_opt(0).and_then(|v| match v {\n                crate::types::ValueRef::Text(t) => Some(t.value.to_string()),\n                _ => None,\n            });\n            let found_chunk = record.get_value_opt(1).and_then(|v| match v {\n                crate::types::ValueRef::Numeric(crate::numeric::Numeric::Integer(i)) => Some(i),\n                _ => None,\n            });\n            let bytes = record.get_value_opt(2).and_then(|v| match v {\n                crate::types::ValueRef::Blob(b) => Some(b.to_vec()),\n                _ => None,\n            });\n\n            let (found_path_str, found_chunk_no, bytes) = match (found_path, found_chunk, bytes) {\n                (Some(p), Some(c), Some(b)) => (p, c, b),\n                _ => {\n                    return Err(std::io::Error::new(\n                        std::io::ErrorKind::InvalidData,\n                        \"malformed chunk record\",\n                    ))\n                }\n            };\n\n            if found_path_str != path_str || found_chunk_no != expected_chunk_no as i64 {\n                return Err(io_not_found(format!(\n                    \"wrong chunk: expected {path_str}:{expected_chunk_no}, got {found_path_str}:{found_chunk_no}\",\n                )));\n            }\n\n            // Cache and collect the chunk\n            if can_cache_chunks(path) {\n                let cache_key = (path.to_path_buf(), expected_chunk_no as i64);\n                self.chunk_cache.put(cache_key, bytes.clone());\n            }\n            chunks.push(Arc::from(bytes));\n\n            // Advance cursor to next record (unless this is the last chunk we need)\n            if expected_chunk_no < end_chunk {\n                loop {\n                    match cursor.next() {\n                        Ok(IOResult::Done(_)) => break,\n                        Ok(IOResult::IO(_)) => {\n                            self.pager\n                                .io\n                                .step()\n                                .map_err(|e| std::io::Error::other(e.to_string()))?;\n                        }\n                        Err(e) => return Err(std::io::Error::other(e.to_string())),\n                    }\n                }\n            }\n        }\n\n        Ok(chunks)\n    }\n\n    /// Load an entire file by concatenating all its chunks (blocking).\n    /// Uses efficient bulk read with a single cursor seek.\n    fn load_file_blocking(&self, path: &Path) -> std::io::Result<Vec<u8>> {\n        let catalog = self.catalog.read();\n        let metadata = catalog\n            .get(path)\n            .ok_or_else(|| io_not_found(format!(\"file not in catalog: {}\", path.display())))?;\n\n        if metadata.num_chunks == 0 {\n            return Ok(Vec::new());\n        }\n\n        let chunks =\n            self.get_chunks_range_blocking(path, 0, metadata.num_chunks.saturating_sub(1))?;\n\n        let mut result = Vec::with_capacity(metadata.size);\n        for chunk in chunks {\n            result.extend_from_slice(&chunk);\n        }\n\n        Ok(result)\n    }\n\n    /// Add a file to the hot cache.\n    fn add_to_hot_cache(&self, path: PathBuf, data: Vec<u8>) {\n        self.hot_cache.put(path, data);\n    }\n\n    /// Update the catalog with file metadata.\n    fn update_catalog(&self, path: PathBuf, metadata: FileMetadata) {\n        let mut catalog = self.catalog.write();\n        catalog.insert(path, metadata);\n    }\n}\n\n/// Simple in-memory file handle for data already loaded (hot cache, pending writes).\n/// Use `Arc<[u8]>` for zero-copy reads when backed by the hot cache.\nstruct InMemoryFileHandle {\n    data: Arc<[u8]>,\n}\n\nimpl std::fmt::Debug for InMemoryFileHandle {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"InMemoryFileHandle\")\n            .field(\"len\", &self.data.len())\n            .finish()\n    }\n}\n\nimpl HasLen for InMemoryFileHandle {\n    fn len(&self) -> usize {\n        self.data.len()\n    }\n}\n\nimpl FileHandle for InMemoryFileHandle {\n    fn read_bytes(&self, range: Range<usize>) -> std::io::Result<OwnedBytes> {\n        if range.end > self.data.len() {\n            return Err(std::io::Error::new(\n                std::io::ErrorKind::UnexpectedEof,\n                \"range exceeds file length\",\n            ));\n        }\n        if range.start >= range.end {\n            return Ok(OwnedBytes::empty());\n        }\n        Ok(OwnedBytes::new(Arc::clone(&self.data)).slice(range))\n    }\n}\n\n/// Lazy file handle that fetches chunks on demand.\nstruct LazyFileHandle {\n    path: PathBuf,\n    size: usize,\n    directory: HybridBTreeDirectory,\n}\n\nimpl std::fmt::Debug for LazyFileHandle {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"LazyFileHandle\")\n            .field(\"path\", &self.path)\n            .field(\"size\", &self.size)\n            .finish()\n    }\n}\n\nimpl HasLen for LazyFileHandle {\n    fn len(&self) -> usize {\n        self.size\n    }\n}\n\nimpl FileHandle for LazyFileHandle {\n    fn read_bytes(&self, range: Range<usize>) -> std::io::Result<OwnedBytes> {\n        if range.end > self.size {\n            return Err(std::io::Error::new(\n                std::io::ErrorKind::UnexpectedEof,\n                format!(\n                    \"range {:?} exceeds file size {} for {}\",\n                    range,\n                    self.size,\n                    self.path.display()\n                ),\n            ));\n        }\n        if range.start >= range.end {\n            return Ok(OwnedBytes::new(Vec::new()));\n        }\n\n        // Check hot cache first\n        if let Some(data) = self.directory.hot_cache.get(&self.path) {\n            return Ok(OwnedBytes::new(data).slice(range));\n        }\n\n        // Check pending/flushing writes (data not yet persisted to BTree)\n        if let Some(data) = self.directory.find_in_pending_writes(&self.path) {\n            return Ok(OwnedBytes::new(data[range].to_vec()));\n        }\n\n        // Calculate required chunks\n        let chunk_size = DEFAULT_CHUNK_SIZE;\n        let start_chunk = range.start / chunk_size;\n        let end_chunk = range.end.saturating_sub(1) / chunk_size;\n\n        // Use efficient bulk read when multiple chunks are needed\n        let chunks =\n            self.directory\n                .get_chunks_range_blocking(&self.path, start_chunk, end_chunk)?;\n\n        // Collect result from chunks\n        let mut result = Vec::with_capacity(range.len());\n        for (i, chunk) in chunks.into_iter().enumerate() {\n            let chunk_no = start_chunk + i;\n            let chunk_start = chunk_no * chunk_size;\n\n            // Calculate slice within this chunk\n            let local_start = if chunk_no == start_chunk {\n                range.start - chunk_start\n            } else {\n                0\n            };\n            let local_end = if chunk_no == end_chunk {\n                range.end - chunk_start\n            } else {\n                chunk.len()\n            };\n\n            // Defensive bounds check - should not be needed if logic is correct\n            turso_debug_assert!(\n                local_start <= chunk.len() && local_end <= chunk.len(),\n                \"chunk slice out of bounds\",\n                { \"local_start\": local_start, \"local_end\": local_end, \"chunk_len\": chunk.len() }\n            );\n            let local_end = local_end.min(chunk.len());\n            let local_start = local_start.min(local_end);\n\n            result.extend_from_slice(&chunk[local_start..local_end]);\n        }\n\n        Ok(OwnedBytes::new(result))\n    }\n}\n\n/// In-memory writer for HybridBTreeDirectory.\nstruct HybridWriter {\n    path: PathBuf,\n    buffer: Vec<u8>,\n    directory: HybridBTreeDirectory,\n}\n\nimpl Write for HybridWriter {\n    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {\n        self.buffer.extend_from_slice(buf);\n        Ok(buf.len())\n    }\n\n    fn flush(&mut self) -> std::io::Result<()> {\n        Ok(())\n    }\n}\n\nimpl Drop for HybridWriter {\n    fn drop(&mut self) {\n        // Commit the write to the directory\n        let data = std::mem::take(&mut self.buffer);\n        if !data.is_empty() {\n            // Update catalog\n            let num_chunks = data.len().div_ceil(DEFAULT_CHUNK_SIZE);\n            let metadata = FileMetadata::new(&self.path, data.len(), num_chunks);\n            self.directory\n                .update_catalog(self.path.clone(), metadata.clone());\n\n            // If it's a hot file category, add to hot cache\n            if metadata.category.is_hot() {\n                self.directory\n                    .add_to_hot_cache(self.path.clone(), data.clone());\n            }\n\n            // Queue for BTree flush (HashMap auto-deduplicates by path)\n            let mut pending = self.directory.pending_writes.write();\n            pending.insert(self.path.clone(), data);\n        }\n    }\n}\n\nimpl TerminatingWrite for HybridWriter {\n    fn terminate_ref(&mut self, _: tantivy::directory::AntiCallToken) -> std::io::Result<()> {\n        let data = std::mem::take(&mut self.buffer);\n\n        // Calculate chunks (0 for empty files, consistent with Drop impl)\n        let num_chunks = data.len().div_ceil(DEFAULT_CHUNK_SIZE);\n\n        // Update catalog - even empty files should exist in the catalog\n        let metadata = FileMetadata::new(&self.path, data.len(), num_chunks);\n        self.directory\n            .update_catalog(self.path.clone(), metadata.clone());\n\n        // If it's a hot file category, add to hot cache (even if empty)\n        if metadata.category.is_hot() {\n            self.directory\n                .add_to_hot_cache(self.path.clone(), data.clone());\n        }\n\n        // Queue for BTree flush (HashMap auto-deduplicates by path)\n        // Empty files are still queued to ensure they can be read back from pending writes\n        let mut pending = self.directory.pending_writes.write();\n        pending.insert(self.path.clone(), data);\n        Ok(())\n    }\n}\n\nimpl Directory for HybridBTreeDirectory {\n    fn get_file_handle(\n        &self,\n        path: &Path,\n    ) -> std::result::Result<Arc<dyn FileHandle>, OpenReadError> {\n        if let Some(data) = self.hot_cache.get(path) {\n            return Ok(Arc::new(InMemoryFileHandle { data }));\n        }\n\n        // Check pending writes (files written but not yet flushed to BTree)\n        // This is critical for cold files that are immediately read back by Tantivy\n        if let Some(data) = self.find_in_pending_writes(path) {\n            return Ok(Arc::new(InMemoryFileHandle {\n                data: Arc::from(data),\n            }));\n        }\n\n        // Check catalog for file metadata\n        let catalog = self.catalog.read();\n        let metadata = catalog\n            .get(path)\n            .ok_or_else(|| OpenReadError::FileDoesNotExist(path.to_path_buf()))?;\n\n        Ok(Arc::new(LazyFileHandle {\n            path: path.to_path_buf(),\n            size: metadata.size,\n            directory: self.clone(),\n        }))\n    }\n\n    fn exists(&self, path: &Path) -> std::result::Result<bool, OpenReadError> {\n        // Check hot cache\n        if self.hot_cache.contains(path) {\n            return Ok(true);\n        }\n        // Check catalog\n        let catalog = self.catalog.read();\n        Ok(catalog.contains_key(path))\n    }\n\n    fn delete(&self, path: &Path) -> std::result::Result<(), DeleteError> {\n        // Remove from hot cache\n        self.hot_cache.remove(path);\n        // Remove from catalog\n        {\n            let mut catalog = self.catalog.write();\n            catalog.remove(path);\n        }\n        if can_cache_chunks(path) {\n            // Invalidate chunk cache\n            self.chunk_cache.invalidate(path);\n        }\n        // Queue for BTree deletion\n        {\n            let mut pending = self.pending_deletes.write();\n            pending.push(path.to_path_buf());\n        }\n        Ok(())\n    }\n\n    fn open_write(\n        &self,\n        path: &Path,\n    ) -> std::result::Result<BufWriter<Box<dyn TerminatingWrite>>, OpenWriteError> {\n        // Tantivy's Directory trait documentation states files \"may not previously exist\",\n        // and the standard MmapDirectory implementation uses OpenOptions::create_new(true)\n        // which fails with FileAlreadyExists if the file is present.\n        // However, Tantivy may call open_write on existing files during operations like\n        // segment merging or metadata updates. To handle this gracefully, we delete any\n        // existing file first. The error is ignored because:\n        // 1. If the file doesn't exist, delete() succeeds (no-op on missing files)\n        // 2. Our delete() implementation always returns Ok(()) - it only removes entries\n        //    from in-memory structures (hot_cache, catalog, chunk_cache) and queues the\n        //    BTree deletion, none of which can fail.\n        //\n        // Skip delete for the meta lock file: Tantivy calls open_write on it for every\n        // search query, but it's never cached (can_cache_chunks returns false), never in\n        // hot_cache, and doesn't need BTree deletion, so delete() is pure overhead.\n        if path != Path::new(TANTIVY_META_LOCK_FILE) {\n            let _ = self.delete(path);\n        }\n        let writer: Box<dyn TerminatingWrite> = Box::new(HybridWriter {\n            path: path.to_path_buf(),\n            buffer: Vec::new(),\n            directory: self.clone(),\n        });\n        Ok(BufWriter::new(writer))\n    }\n\n    fn atomic_read(&self, path: &Path) -> std::result::Result<Vec<u8>, OpenReadError> {\n        // Check hot cache first (includes recently written files)\n        if let Some(data) = self.hot_cache.get(path) {\n            return Ok(data.to_vec());\n        }\n\n        // Check pending writes (files written but not yet flushed to BTree)\n        if let Some(data) = self.find_in_pending_writes(path) {\n            return Ok(data);\n        }\n\n        // Check if file exists in catalog\n        {\n            let catalog = self.catalog.read();\n            if !catalog.contains_key(path) {\n                return Err(OpenReadError::FileDoesNotExist(path.to_path_buf()));\n            }\n        }\n\n        // Load file blocking from BTree\n        self.load_file_blocking(path)\n            .map_err(|e| OpenReadError::IoError {\n                io_error: Arc::new(e),\n                filepath: path.to_path_buf(),\n            })\n    }\n\n    fn atomic_write(&self, path: &Path, data: &[u8]) -> std::io::Result<()> {\n        // Update catalog\n        let num_chunks = data.len().div_ceil(DEFAULT_CHUNK_SIZE).max(1);\n        let metadata = FileMetadata::new(path, data.len(), num_chunks);\n        self.update_catalog(path.to_path_buf(), metadata.clone());\n\n        // If it's a hot file category, add to hot cache\n        if metadata.category.is_hot() {\n            self.add_to_hot_cache(path.to_path_buf(), data.to_vec());\n        }\n\n        // Queue for BTree flush (HashMap auto-deduplicates by path)\n        let mut pending = self.pending_writes.write();\n        pending.insert(path.to_path_buf(), data.to_vec());\n        Ok(())\n    }\n\n    fn sync_directory(&self) -> std::io::Result<()> {\n        Ok(())\n    }\n\n    fn watch(&self, _cb: WatchCallback) -> std::result::Result<WatchHandle, tantivy::TantivyError> {\n        Ok(WatchHandle::empty())\n    }\n}\n\n/// Creates default `KeyInfo` for BTree index columns.\nfn key_info() -> KeyInfo {\n    KeyInfo {\n        sort_order: SortOrder::Asc,\n        collation: CollationSeq::Binary,\n    }\n}\n\n/// Creates an AST `Name` node from a string.\nfn name(name: impl ToString) -> ast::Name {\n    ast::Name::exact(name.to_string())\n}\n\n/// Parse field weights from a string like \"body=2.0,title=1.0\"\n/// Returns a HashMap mapping column names to tantivy 'boost factors'\nfn parse_field_weights(weights_str: &str, columns: &[IndexColumn]) -> Result<HashMap<String, f32>> {\n    let mut weights = HashMap::default();\n\n    if weights_str.is_empty() {\n        return Ok(weights);\n    }\n\n    // Get valid column names for validation\n    let valid_columns: HashSet<&str> = columns.iter().map(|c| c.name.as_str()).collect();\n\n    // Parse format: \"col1=1.5,col2=2.0\"\n    for part in weights_str.split(',') {\n        let part = part.trim();\n        if part.is_empty() {\n            continue;\n        }\n\n        let (col_name, weight_str) = part.split_once('=').ok_or_else(|| {\n            LimboError::ParseError(format!(\n                \"invalid weight format '{part}'. Expected 'column=weight' (e.g., 'title=2.0')\",\n            ))\n        })?;\n\n        let col_name = col_name.trim();\n        let weight_str = weight_str.trim();\n\n        // Validate column exists in index\n        if !valid_columns.contains(col_name) {\n            return Err(LimboError::ParseError(format!(\n                \"unknown column '{}' in weights. Valid columns: {}\",\n                col_name,\n                columns\n                    .iter()\n                    .map(|c| c.name.as_str())\n                    .collect::<Vec<_>>()\n                    .join(\", \")\n            )));\n        }\n\n        let weight: f32 = weight_str.parse().map_err(|_| {\n            LimboError::ParseError(format!(\n                \"invalid weight value '{weight_str}' for column '{col_name}'. Expected a number (e.g., 2.0)\",\n            ))\n        })?;\n        if weight <= 0.0 {\n            return Err(LimboError::ParseError(format!(\n                \"weight for column '{col_name}' must be positive, got {weight}\",\n            )));\n        }\n\n        weights.insert(col_name.to_string(), weight);\n    }\n\n    Ok(weights)\n}\n\n/// Factory for creating FTS index attachments.\n///\n/// Implements the `IndexMethod` trait to integrate with turso's index method system.\n/// When a user creates an FTS index with `CREATE INDEX ... USING fts (...)`,\n/// this factory creates an `FtsIndexAttachment` with the specified configuration.\n#[derive(Debug)]\npub struct FtsIndexMethod;\n\nimpl IndexMethod for FtsIndexMethod {\n    fn attach(&self, cfg: &IndexMethodConfiguration) -> Result<Arc<dyn IndexMethodAttachment>> {\n        let attachment = FtsIndexAttachment::new(cfg.clone())?;\n        Ok(Arc::new(attachment))\n    }\n}\n\n/// Cached FTS directory shared across cursors to avoid expensive catalog reloads.\n///\n/// Contains a `HybridBTreeDirectory` with its catalog already loaded from the BTree.\n/// Only the directory is cached, not the Tantivy Index/Reader, because each cursor\n/// needs its own Index instance to handle writes correctly.\npub struct CachedFtsDirectory {\n    directory: HybridBTreeDirectory,\n}\n\nimpl std::fmt::Debug for CachedFtsDirectory {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"CachedFtsDirectory\")\n            .field(\"directory\", &\"HybridBTreeDirectory\")\n            .finish()\n    }\n}\n\n/// FTS index attachment that holds configuration and creates cursors for queries.\n///\n/// Created by `FtsIndexMethod::attach()` and implements `IndexMethodAttachment`.\n/// Stores the Tantivy schema, field mappings, query patterns, and a shared\n/// directory cache to optimize repeated queries.\n#[derive(Debug)]\npub struct FtsIndexAttachment {\n    /// Internal configuration\n    cfg: IndexMethodConfiguration,\n    /// Tantivy schema for the FTS index\n    schema: Schema,\n    /// Tantivy field for the rowid column\n    rowid_field: Field,\n    /// Schema fields for each indexed text column\n    text_fields: Vec<(IndexColumn, Field)>,\n    /// Parsed query patterns for FTS queries\n    patterns: Vec<Select>,\n    /// Weights for each field in FTS scoring.\n    /// Created from WITH clause parameters,\n    /// e.g. `WITH (tokenizer='default',weights='col1=1.0,col2=2.0')`.\n    field_weights: HashMap<String, f32>,\n    /// In-memory cached tantivy directory state\n    cached_directory_state: Arc<RwLock<Option<CachedFtsDirectory>>>,\n}\n\n/// Supported tokenizer names for FTS indexes\npub const SUPPORTED_TOKENIZERS: &[&str] = &[\n    \"default\",    // Tantivy default: lowercase + punctuation split + 40 char limit\n    \"raw\",        // No tokenization - exact match only\n    \"simple\",     // Basic whitespace/punctuation split\n    \"whitespace\", // Split on whitespace only\n    \"ngram\",      // N-gram tokenizer (2-3 chars by default)\n];\n\nimpl FtsIndexAttachment {\n    pub fn new(cfg: IndexMethodConfiguration) -> Result<Self> {\n        // Parse tokenizer from WITH clause parameters, default to \"default\"\n        // The parser may include surrounding quotes in the value, so we strip them\n        let tokenizer_name = cfg\n            .parameters\n            .get(\"tokenizer\")\n            .and_then(|v| match v {\n                Value::Text(t) => {\n                    let s = t.to_string();\n                    // Strip surrounding single or double quotes if present\n                    let trimmed = s.trim_matches(|c| c == '\\'' || c == '\"');\n                    Some(trimmed.to_string())\n                }\n                _ => None,\n            })\n            .unwrap_or_else(|| \"default\".to_string());\n\n        // Validate tokenizer name\n        if !SUPPORTED_TOKENIZERS.contains(&tokenizer_name.as_str()) {\n            return Err(LimboError::ParseError(format!(\n                \"unsupported FTS tokenizer '{}'. Supported tokenizers: {}\",\n                tokenizer_name,\n                SUPPORTED_TOKENIZERS.join(\", \")\n            )));\n        }\n\n        // Parse field weights from WITH clause: weights='body=2.0,title=1.0'\n        let field_weights = if let Some(weights_value) = cfg.parameters.get(\"weights\") {\n            let weights_str = match weights_value {\n                Value::Text(t) => {\n                    let s = t.to_string();\n                    s.trim_matches(|c| c == '\\'' || c == '\"').to_string()\n                }\n                _ => String::new(),\n            };\n            parse_field_weights(&weights_str, &cfg.columns)?\n        } else {\n            HashMap::default()\n        };\n\n        // Build Tantivy schema (no Directory or Index creation yet)\n        let mut schema_builder = Schema::builder();\n\n        // Use FAST field for rowid to enable efficient columnar access during query result retrieval.\n        // This avoids loading full documents from the .store file just to get the rowid.\n        let rowid_field = schema_builder.add_i64_field(\n            ROWID_FIELD,\n            tantivy::schema::INDEXED | tantivy::schema::FAST,\n        );\n\n        let mut text_fields = Vec::with_capacity(cfg.columns.len());\n        for col in &cfg.columns {\n            let opts = tantivy::schema::TextOptions::default()\n                .set_indexing_options(\n                    tantivy::schema::TextFieldIndexing::default()\n                        .set_tokenizer(&tokenizer_name)\n                        .set_index_option(\n                            tantivy::schema::IndexRecordOption::WithFreqsAndPositions,\n                        ),\n                )\n                .set_stored();\n            let field = schema_builder.add_text_field(&col.name, opts);\n            text_fields.push((col.clone(), field));\n        }\n\n        let schema = schema_builder.build();\n\n        // Build query patterns for FTS\n        // Order matters: more specific patterns should come first\n        // Pattern 0: SELECT fts_score(col1, col2, ..., 'query') as score FROM table ORDER BY score DESC LIMIT ?\n        // Pattern 1: SELECT fts_score(col1, col2, ..., 'query') as score FROM table WHERE fts_match(col1, col2, ..., 'query')\n        //            (combined: both score and match with same query - must come before pattern 2)\n        // Pattern 2: SELECT * FROM table WHERE fts_match(col1, col2, ..., 'query')\n        let cols = cfg\n            .columns\n            .iter()\n            .map(|c| c.name.as_str())\n            .collect::<Vec<_>>()\n            .join(\", \");\n        // Build all FTS patterns - more specific patterns first\n        // Use explicit ?1 for shared parameters between fts_score and fts_match\n\n        // Pattern 0: score with ORDER BY DESC LIMIT\n        let score_pattern = format!(\n            \"SELECT fts_score({}, ?) as score FROM {} ORDER BY score DESC LIMIT ?\",\n            cols, cfg.table_name\n        );\n        // Pattern 1: combined + ORDER BY DESC + LIMIT (most specific)\n        let combined_ordered_limit = format!(\n            \"SELECT fts_score({}, ?1) as score FROM {} WHERE fts_match({}, ?1) ORDER BY score DESC LIMIT ?\",\n            cols, cfg.table_name, cols\n        );\n        // Pattern 2: combined + ORDER BY DESC (no LIMIT)\n        let combined_ordered = format!(\n            \"SELECT fts_score({}, ?1) as score FROM {} WHERE fts_match({}, ?1) ORDER BY score DESC\",\n            cols, cfg.table_name, cols\n        );\n        // Pattern 3: combined + LIMIT (no ORDER BY)\n        let combined_limit = format!(\n            \"SELECT fts_score({}, ?1) as score FROM {} WHERE fts_match({}, ?1) LIMIT ?\",\n            cols, cfg.table_name, cols\n        );\n        // Pattern 4: combined (no ORDER BY, no LIMIT)\n        let combined = format!(\n            \"SELECT fts_score({}, ?1) as score FROM {} WHERE fts_match({}, ?1)\",\n            cols, cfg.table_name, cols\n        );\n        // Pattern 5: match + LIMIT\n        let match_limit = format!(\n            \"SELECT * FROM {} WHERE fts_match({}, ?) LIMIT ?\",\n            cfg.table_name, cols\n        );\n        // Pattern 6: match (no LIMIT)\n        let match_pattern = format!(\n            \"SELECT * FROM {} WHERE fts_match({}, ?)\",\n            cfg.table_name, cols\n        );\n        let patterns = parse_patterns(&[\n            &score_pattern,          // 0\n            &combined_ordered_limit, // 1\n            &combined_ordered,       // 2\n            &combined_limit,         // 3\n            &combined,               // 4\n            &match_limit,            // 5\n            &match_pattern,          // 6\n        ])?;\n        Ok(Self {\n            cfg,\n            schema,\n            rowid_field,\n            text_fields,\n            patterns,\n            field_weights,\n            cached_directory_state: Arc::new(RwLock::new(None)),\n        })\n    }\n}\n\nimpl IndexMethodAttachment for FtsIndexAttachment {\n    fn definition<'a>(&'a self) -> IndexMethodDefinition<'a> {\n        IndexMethodDefinition {\n            method_name: FTS_INDEX_METHOD_NAME,\n            index_name: &self.cfg.index_name,\n            patterns: &self.patterns,\n            backing_btree: false,\n            results_materialized: true,\n        }\n    }\n\n    fn init(&self) -> Result<Box<dyn IndexMethodCursor>> {\n        Ok(Box::new(FtsCursor::new(\n            &self.cfg,\n            self.schema.clone(),\n            self.rowid_field,\n            self.text_fields.clone(),\n            self.field_weights.clone(),\n            self.cached_directory_state.clone(),\n        )))\n    }\n}\n\nconst NOTNULL_CONSTRAINT: ast::NamedColumnConstraint = ast::NamedColumnConstraint {\n    name: None,\n    constraint: ast::ColumnConstraint::NotNull {\n        nullable: false,\n        conflict_clause: None,\n    },\n};\n\nfn initialize_btree_storage_table(conn: &Arc<Connection>, table_name: &str) -> Result<()> {\n    const PATH_COLUMN: &str = \"path\";\n    const CHUNK_NO_COLUMN: &str = \"chunk_no\";\n    const BYTES_COLUMN: &str = \"bytes\";\n    // inline ast to reduce parsing overhead\n    // CREATE TABLE table_name (path TEXT NOT NULL, chunk_no INTEGER NOT NULL, bytes BLOB NOT NULL);\n    let create_table_stmt = ast::Stmt::CreateTable {\n        body: ast::CreateTableBody::ColumnsAndConstraints {\n            columns: vec![\n                ast::ColumnDefinition {\n                    col_name: name(PATH_COLUMN),\n                    col_type: Some(ast::Type {\n                        name: \"TEXT\".to_string(),\n                        size: None,\n                        array_dimensions: 0,\n                    }),\n                    constraints: vec![NOTNULL_CONSTRAINT],\n                },\n                ast::ColumnDefinition {\n                    col_name: name(CHUNK_NO_COLUMN),\n                    col_type: Some(ast::Type {\n                        name: \"INTEGER\".to_string(),\n                        size: None,\n                        array_dimensions: 0,\n                    }),\n                    constraints: vec![NOTNULL_CONSTRAINT],\n                },\n                ast::ColumnDefinition {\n                    col_name: name(BYTES_COLUMN),\n                    col_type: Some(ast::Type {\n                        name: \"BLOB\".to_string(),\n                        size: None,\n                        array_dimensions: 0,\n                    }),\n                    constraints: vec![NOTNULL_CONSTRAINT],\n                },\n            ],\n            constraints: vec![],\n            options: ast::TableOptions::empty(),\n        },\n        temporary: false,\n        if_not_exists: true,\n        tbl_name: ast::QualifiedName::single(name(table_name)),\n    };\n    // \"CREATE INDEX IF NOT EXISTS idx_name ON table_name USING backing_btree (path, chunk_no, bytes);\"\n    // Use backing_btree to create a BTree that stores all columns without rowid indirection\n    // This allows direct cursor access with the exact key structure\n    let create_index_stmt = ast::Stmt::CreateIndex {\n        unique: false, // backing_btree doesn't use unique constraint\n        if_not_exists: true,\n        idx_name: ast::QualifiedName::single(name(format!(\"{table_name}_key\"))),\n        tbl_name: name(table_name),\n        using: Some(name(super::BACKING_BTREE_INDEX_METHOD_NAME)),\n        columns: vec![\n            ast::SortedColumn {\n                expr: Box::new(ast::Expr::Name(name(PATH_COLUMN))),\n                order: None,\n                nulls: None,\n            },\n            ast::SortedColumn {\n                expr: Box::new(ast::Expr::Name(name(CHUNK_NO_COLUMN))),\n                order: None,\n                nulls: None,\n            },\n            ast::SortedColumn {\n                expr: Box::new(ast::Expr::Name(name(BYTES_COLUMN))),\n                order: None,\n                nulls: None,\n            },\n        ],\n        where_clause: None,\n        with_clause: vec![],\n    };\n    // Execute nested statements without subtransactions to avoid DatabaseBusy\n    // (we're already inside a transaction from the parent CREATE INDEX statement)\n    {\n        conn.start_nested();\n        let mut stmt = conn.prepare_stmt(create_table_stmt)?;\n        stmt.program\n            .prepared\n            .needs_stmt_subtransactions\n            .store(false, Ordering::Relaxed);\n        let res = stmt.run_ignore_rows();\n        conn.end_nested();\n        res?;\n    }\n    {\n        conn.start_nested();\n        let mut stmt = conn.prepare_stmt(create_index_stmt)?;\n        stmt.program\n            .prepared\n            .needs_stmt_subtransactions\n            .store(false, Ordering::Relaxed);\n        let res = stmt.run_ignore_rows();\n        conn.end_nested();\n        res?;\n    }\n\n    Ok(())\n}\n\n/// Pattern indices for FTS queries\nconst FTS_PATTERN_SCORE: i64 = 0;\nconst FTS_PATTERN_COMBINED_ORDERED_LIMIT: i64 = 1;\nconst FTS_PATTERN_COMBINED_ORDERED: i64 = 2;\nconst FTS_PATTERN_COMBINED_LIMIT: i64 = 3;\nconst FTS_PATTERN_COMBINED: i64 = 4;\nconst FTS_PATTERN_MATCH_LIMIT: i64 = 5;\nconst FTS_PATTERN_MATCH: i64 = 6;\nconst TANTIVY_META_FILE: &str = \"meta.json\";\nconst TANTIVY_META_LOCK_FILE: &str = \".tantivy-meta.lock\";\n\n/// Check if a file's chunks should be cached.\n///\n/// The meta lock file is excluded because Tantivy calls `open_write` on it for every search query.\n/// Since `open_write` calls `delete` first, caching the lock file would trigger a full chunk cache\n/// scan on every query, causing significant overhead.\nfn can_cache_chunks(path: &Path) -> bool {\n    path.as_os_str().to_str() != Some(TANTIVY_META_LOCK_FILE)\n}\n\n/// Accumulated file metadata: path -> (chunk_no -> (blob_size, Option<blob_data>))\ntype CatalogBuilder = HashMap<i64, (usize, Option<Vec<u8>>)>;\n\n/// State machine for FTS cursor async operations\n#[derive(Debug)]\nenum FtsState {\n    /// Initial state\n    Init,\n    /// Rewinding cursor to start\n    Rewinding,\n    /// Loading file catalog from BTree (metadata only, not content)\n    /// This is the new catalog-first approach for HybridBTreeDirectory\n    LoadingCatalog {\n        /// Hot files capture blob data during scan to avoid a second pass.\n        catalog_builder: HashMap<PathBuf, CatalogBuilder>,\n        current_path: Option<PathBuf>,\n    },\n    /// Preloading essential files (meta.json and other hot files)\n    PreloadingEssentials {\n        /// Files that need to be preloaded\n        files_to_load: Vec<PathBuf>,\n        /// Files already loaded\n        loaded_files: HashMap<PathBuf, Vec<u8>>,\n        /// Current file being loaded\n        current_loading: Option<PathBuf>,\n        /// Current chunks being accumulated for the file being loaded\n        current_chunks: Vec<(i64, Vec<u8>)>,\n    },\n    /// Creating/opening Tantivy index\n    CreatingIndex,\n    /// Ready for operations\n    Ready,\n    /// Seeking to first chunk of a path before deleting old chunks\n    SeekingOldChunks {\n        writes: Vec<(PathBuf, Vec<u8>)>,\n        write_idx: usize,\n        path_str: String,\n    },\n    /// Advancing cursor after seek returned TryAdvance\n    AdvancingAfterSeek {\n        writes: Vec<(PathBuf, Vec<u8>)>,\n        write_idx: usize,\n        path_str: String,\n    },\n    /// Checking if current record's path matches (to determine if it should be deleted)\n    CheckingChunkPath {\n        writes: Vec<(PathBuf, Vec<u8>)>,\n        write_idx: usize,\n        path_str: String,\n    },\n    /// Performing the actual delete of a chunk\n    DeletingChunk {\n        writes: Vec<(PathBuf, Vec<u8>)>,\n        write_idx: usize,\n        path_str: String,\n    },\n    /// Advancing cursor after delete to check next record\n    AdvancingAfterDelete {\n        writes: Vec<(PathBuf, Vec<u8>)>,\n        write_idx: usize,\n        path_str: String,\n    },\n    /// Flushing pending writes to BTree - seeking phase\n    SeekingWrite {\n        writes: Vec<(PathBuf, Vec<u8>)>,\n        write_idx: usize,\n        /// Current chunk index to write. None means old chunks deleted, ready to start from 0.\n        chunk_idx: Option<usize>,\n    },\n    /// Flushing pending writes to BTree - insert phase (after seek completed)\n    InsertingWrite {\n        writes: Vec<(PathBuf, Vec<u8>)>,\n        write_idx: usize,\n        chunk_idx: usize,\n        record: ImmutableRecord,\n    },\n    /// Flushing pending writes to BTree - tracking state\n    FlushingWrites {\n        writes: Vec<(PathBuf, Vec<u8>)>,\n        write_idx: usize,\n        /// Current chunk index. None means old chunks need deletion first, then start from 0.\n        chunk_idx: Option<usize>,\n    },\n    /// Flushing pending deletes to BTree\n    FlushingDeletes {\n        deletes: Vec<PathBuf>,\n        delete_idx: usize,\n    },\n    /// Seeking for delete operation\n    SeekingDelete {\n        deletes: Vec<PathBuf>,\n        delete_idx: usize,\n    },\n    /// Deleting record at cursor position\n    DeletingRecord {\n        deletes: Vec<PathBuf>,\n        delete_idx: usize,\n    },\n}\n\n/// Cursor for executing FTS operations (queries, inserts, deletes).\n///\n/// Implements `IndexMethodCursor` to integrate with turso's VDBE execution.\n/// Uses a state machine pattern for async IO operations. Manages:\n/// - Tantivy index/reader/writer/searcher instances\n/// - BTree storage via `HybridBTreeDirectory`\n/// - Document batching for efficient bulk inserts\n/// - Query result iteration\npub struct FtsCursor {\n    schema: Schema,\n    rowid_field: Field,\n    text_fields: Vec<(IndexColumn, Field)>,\n    dir_table_name: String,\n    /// Pre-computed default fields for QueryParser (avoids rebuilding Vec per query)\n    default_fields: Vec<Field>,\n    /// Pre-computed (Field, boost) pairs for QueryParser (avoids re-iterating per query)\n    field_boosts: Vec<(Field, f32)>,\n    /// Cached QueryParser reused across queries (invalidated on commit)\n    cached_parser: Option<tantivy::query::QueryParser>,\n    shared_directory_cache: Arc<RwLock<Option<CachedFtsDirectory>>>,\n    connection: Option<Arc<Connection>>,\n    fts_dir_cursor: Option<BTreeCursor>,\n    btree_root_page: Option<i64>,\n    hybrid_directory: Option<HybridBTreeDirectory>,\n    index: Option<Index>,\n    reader: Option<IndexReader>,\n    writer: Option<IndexWriter>,\n    searcher: Option<Searcher>,\n    state: FtsState,\n    pending_docs_count: usize,\n    current_hits: Vec<(f32, DocAddress, i64)>,\n    hit_pos: usize,\n    current_pattern: i64,\n}\n\nimpl FtsCursor {\n    /// Maximum results when no LIMIT is specified (1 million).\n    /// TODO: configurable?\n    const MAX_NO_LIMIT_RESULT: usize = 1_000_000;\n\n    /// Creates a new FTS cursor with the given configuration.\n    pub fn new(\n        cfg: &IndexMethodConfiguration,\n        schema: Schema,\n        rowid_field: Field,\n        text_fields: Vec<(IndexColumn, Field)>,\n        field_weights: HashMap<String, f32>,\n        shared_directory_cache: Arc<RwLock<Option<CachedFtsDirectory>>>,\n    ) -> Self {\n        let dir_table_name = format!(\n            \"{}fts_dir_{}\",\n            crate::schema::TURSO_INTERNAL_PREFIX,\n            cfg.index_name\n        );\n        let default_fields: Vec<Field> = text_fields.iter().map(|(_, f)| *f).collect();\n        let field_boosts: Vec<(Field, f32)> = text_fields\n            .iter()\n            .filter_map(|(col, field)| field_weights.get(&col.name).map(|&boost| (*field, boost)))\n            .collect();\n        Self {\n            schema,\n            rowid_field,\n            text_fields,\n            dir_table_name,\n            default_fields,\n            field_boosts,\n            cached_parser: None,\n            shared_directory_cache,\n            connection: None,\n            fts_dir_cursor: None,\n            btree_root_page: None,\n            hybrid_directory: None,\n            index: None,\n            reader: None,\n            writer: None,\n            searcher: None,\n            state: FtsState::Init,\n            pending_docs_count: 0,\n            current_hits: Vec::new(),\n            hit_pos: 0,\n            current_pattern: FTS_PATTERN_SCORE,\n        }\n    }\n\n    /// Open the BTree cursor for FTS directory storage\n    fn open_cursor(&mut self, conn: &Arc<Connection>) -> Result<()> {\n        if self.fts_dir_cursor.is_some() {\n            return Ok(());\n        }\n        // Open cursor for the FTS directory index\n        // The index stores all 3 columns: (path, chunk_no, bytes) as the key\n        // This is similar to how toy_vector_sparse_ivf stores all data in the index\n        let index_name = format!(\"{}_key\", self.dir_table_name);\n\n        // Get root page for HybridBTreeDirectory\n        let pager = conn.pager.load().clone();\n        let schema = conn.schema.read();\n        let scratch = schema\n            .get_index(&self.dir_table_name, &index_name)\n            .ok_or_else(|| {\n                LimboError::InternalError(format!(\n                    \"index {} for table {} not found\",\n                    index_name, self.dir_table_name\n                ))\n            })?;\n        let root_page = scratch.root_page;\n        drop(schema);\n\n        self.btree_root_page = Some(root_page);\n\n        let mut cursor = BTreeCursor::new(pager, root_page, 3);\n        cursor.index_info = Some(Arc::new(IndexInfo {\n            has_rowid: false,\n            num_cols: 3,\n            key_info: vec![key_info(), key_info(), key_info()],\n            is_unique: false,\n        }));\n        self.fts_dir_cursor = Some(cursor);\n        Ok(())\n    }\n\n    /// Register custom tokenizers with Tantivy index\n    fn register_tokenizers(&self, index: &Index) {\n        let tokenizers = index.tokenizers();\n\n        // Register \"raw\" tokenizer - no tokenization, exact match only\n        tokenizers.register(\"raw\", RawTokenizer::default());\n\n        // Register \"simple\" tokenizer - basic whitespace/punctuation split\n        tokenizers.register(\"simple\", SimpleTokenizer::default());\n\n        // Register \"whitespace\" tokenizer - split on whitespace only\n        tokenizers.register(\"whitespace\", WhitespaceTokenizer::default());\n\n        // Register \"ngram\" tokenizer - 2-3 character n-grams for substring matching\n        // Using prefix=false for full n-gram (not just prefix)\n        if let Ok(ngram) = NgramTokenizer::new(2, 3, false) {\n            tokenizers.register(\"ngram\", ngram);\n        }\n    }\n\n    /// Create Tantivy index from directory (hybrid or cached)\n    fn create_index_from_directory(&mut self) -> Result<()> {\n        if let Some(ref hybrid_dir) = self.hybrid_directory {\n            let index_exists = hybrid_dir\n                .exists(Path::new(TANTIVY_META_FILE))\n                .unwrap_or(false);\n\n            let index = if index_exists {\n                Index::open(hybrid_dir.clone())\n                    .map_err(|e| LimboError::InternalError(e.to_string()))?\n            } else {\n                Index::create(\n                    hybrid_dir.clone(),\n                    self.schema.clone(),\n                    IndexSettings::default(),\n                )\n                .map_err(|e| LimboError::InternalError(e.to_string()))?\n            };\n\n            // Register custom tokenizers\n            self.register_tokenizers(&index);\n\n            self.index = Some(index);\n            return Ok(());\n        }\n\n        Err(LimboError::InternalError(\"no directory initialized\".into()))\n    }\n\n    /// Internal helper to continue flush_writes state machine\n    fn flush_writes_internal(&mut self) -> Result<IOResult<()>> {\n        loop {\n            match &mut self.state {\n                FtsState::FlushingWrites {\n                    writes,\n                    write_idx,\n                    chunk_idx,\n                } => {\n                    if *write_idx >= writes.len() {\n                        // Done with writes - clear flushing_writes since data is now in BTree\n                        if let Some(ref dir) = self.hybrid_directory {\n                            dir.complete_flush();\n                        }\n                        self.state = FtsState::Ready;\n                        return Ok(IOResult::Done(()));\n                    }\n\n                    // If starting a new file (chunk_idx is Some(0)), first delete old chunks\n                    if *chunk_idx == Some(0) {\n                        let path_str = writes[*write_idx].0.to_string_lossy().to_string();\n                        self.state = FtsState::SeekingOldChunks {\n                            writes: std::mem::take(writes),\n                            write_idx: *write_idx,\n                            path_str,\n                        };\n                        continue;\n                    }\n\n                    let (_, data) = &writes[*write_idx];\n                    let chunk_size = DEFAULT_CHUNK_SIZE;\n                    let total_chunks = data.len().div_ceil(chunk_size);\n\n                    // None means old chunks deleted, ready to start from 0\n                    let actual_chunk_idx = chunk_idx.unwrap_or(0);\n\n                    // Empty files (0 chunks) or all chunks written - move to next file\n                    if total_chunks == 0 || actual_chunk_idx >= total_chunks {\n                        *write_idx += 1;\n                        *chunk_idx = Some(0);\n                        continue;\n                    }\n\n                    // Transition to seeking state for writing this chunk\n                    self.state = FtsState::SeekingWrite {\n                        writes: std::mem::take(writes),\n                        write_idx: *write_idx,\n                        chunk_idx: Some(actual_chunk_idx),\n                    };\n                }\n                FtsState::SeekingOldChunks {\n                    writes,\n                    write_idx,\n                    path_str,\n                } => {\n                    let cursor = self.fts_dir_cursor.as_mut().ok_or_else(|| {\n                        LimboError::InternalError(\"cursor not initialized\".into())\n                    })?;\n                    tracing::debug!(\"FTS flush: deleting old chunks for path={}\", path_str);\n\n                    // Seek to first chunk of this path (with empty blob as minimum)\n                    let seek_key = ImmutableRecord::from_values(\n                        &[\n                            Value::Text(Text::new(path_str.clone())),\n                            Value::from_i64(0),\n                            Value::Blob(vec![]),\n                        ],\n                        3,\n                    );\n\n                    let seek_result =\n                        return_if_io!(cursor\n                            .seek(SeekKey::IndexKey(&seek_key), SeekOp::GE { eq_only: false }));\n\n                    match seek_result {\n                        SeekResult::NotFound => {\n                            // No matching records at all, start writing from chunk 0\n                            self.state = FtsState::FlushingWrites {\n                                writes: std::mem::take(writes),\n                                write_idx: *write_idx,\n                                chunk_idx: None, // None = ready to start from chunk 0\n                            };\n                        }\n                        SeekResult::TryAdvance => {\n                            // Cursor positioned at leaf but not on matching entry, need to advance\n                            self.state = FtsState::AdvancingAfterSeek {\n                                writes: std::mem::take(writes),\n                                write_idx: *write_idx,\n                                path_str: std::mem::take(path_str),\n                            };\n                        }\n                        SeekResult::Found => {\n                            // Found a record at or after our seek key, check it\n                            self.state = FtsState::CheckingChunkPath {\n                                writes: std::mem::take(writes),\n                                write_idx: *write_idx,\n                                path_str: std::mem::take(path_str),\n                            };\n                        }\n                    }\n                }\n                FtsState::AdvancingAfterSeek {\n                    writes,\n                    write_idx,\n                    path_str,\n                } => {\n                    let cursor = self.fts_dir_cursor.as_mut().ok_or_else(|| {\n                        LimboError::InternalError(\"cursor not initialized\".into())\n                    })?;\n\n                    return_if_io!(cursor.next());\n                    let has_next = cursor.has_record();\n\n                    if has_next {\n                        // Now positioned on a record, check if it matches our path\n                        self.state = FtsState::CheckingChunkPath {\n                            writes: std::mem::take(writes),\n                            write_idx: *write_idx,\n                            path_str: std::mem::take(path_str),\n                        };\n                    } else {\n                        // No more records, start writing\n                        self.state = FtsState::FlushingWrites {\n                            writes: std::mem::take(writes),\n                            write_idx: *write_idx,\n                            chunk_idx: None, // Ready to start from chunk 0\n                        };\n                    }\n                }\n                FtsState::CheckingChunkPath {\n                    writes,\n                    write_idx,\n                    path_str,\n                } => {\n                    let cursor = self.fts_dir_cursor.as_mut().ok_or_else(|| {\n                        LimboError::InternalError(\"cursor not initialized\".into())\n                    })?;\n\n                    if !cursor.has_record() {\n                        // No more records, start writing new chunks\n                        self.state = FtsState::FlushingWrites {\n                            writes: std::mem::take(writes),\n                            write_idx: *write_idx,\n                            chunk_idx: None, // Ready to start from chunk 0 // Special value to trigger first write\n                        };\n                        continue;\n                    }\n\n                    // Check if current record matches our path\n                    let record = return_if_io!(cursor.record());\n                    let current_path = record.as_ref().and_then(|r| {\n                        r.get_value_opt(0).and_then(|v| match v {\n                            crate::types::ValueRef::Text(t) => Some(t.value.to_string()),\n                            _ => None,\n                        })\n                    });\n\n                    if current_path.as_deref() == Some(path_str.as_str()) {\n                        // Transition to DeletingChunk to actually do the delete\n                        self.state = FtsState::DeletingChunk {\n                            writes: std::mem::take(writes),\n                            write_idx: *write_idx,\n                            path_str: std::mem::take(path_str),\n                        };\n                    } else {\n                        // No more chunks for this path, start writing new chunks\n                        // Use usize::MAX as special marker that old chunks have been deleted\n                        self.state = FtsState::FlushingWrites {\n                            writes: std::mem::take(writes),\n                            write_idx: *write_idx,\n                            chunk_idx: None, // Ready to start from chunk 0\n                        };\n                    }\n                }\n                FtsState::DeletingChunk {\n                    writes,\n                    write_idx,\n                    path_str,\n                } => {\n                    let cursor = self.fts_dir_cursor.as_mut().ok_or_else(|| {\n                        LimboError::InternalError(\"cursor not initialized\".into())\n                    })?;\n\n                    // Perform the delete - if IO is needed, we'll come back to this state\n                    return_if_io!(cursor.delete());\n\n                    // Delete completed, advance cursor to next record before checking again\n                    self.state = FtsState::AdvancingAfterDelete {\n                        writes: std::mem::take(writes),\n                        write_idx: *write_idx,\n                        path_str: std::mem::take(path_str),\n                    };\n                }\n                FtsState::AdvancingAfterDelete {\n                    writes,\n                    write_idx,\n                    path_str,\n                } => {\n                    let cursor = self.fts_dir_cursor.as_mut().ok_or_else(|| {\n                        LimboError::InternalError(\"cursor not initialized\".into())\n                    })?;\n\n                    // Advance cursor to next record after delete\n                    return_if_io!(cursor.next());\n                    let has_next = cursor.has_record();\n\n                    if has_next {\n                        // Check the next record in CheckingChunkPath state\n                        self.state = FtsState::CheckingChunkPath {\n                            writes: std::mem::take(writes),\n                            write_idx: *write_idx,\n                            path_str: std::mem::take(path_str),\n                        };\n                    } else {\n                        // No more records, start writing\n                        self.state = FtsState::FlushingWrites {\n                            writes: std::mem::take(writes),\n                            write_idx: *write_idx,\n                            chunk_idx: None, // Ready to start from chunk 0\n                        };\n                    }\n                }\n                FtsState::SeekingWrite {\n                    writes,\n                    write_idx,\n                    chunk_idx,\n                } => {\n                    let cursor = self.fts_dir_cursor.as_mut().ok_or_else(|| {\n                        LimboError::InternalError(\"cursor not initialized\".into())\n                    })?;\n\n                    let (path, data) = &writes[*write_idx];\n                    let path_str = path.to_string_lossy().to_string();\n                    let chunk_size = DEFAULT_CHUNK_SIZE;\n                    // None means ready to start from chunk 0\n                    let actual_chunk_idx = chunk_idx.unwrap_or(0);\n\n                    let start = actual_chunk_idx * chunk_size;\n                    let end = (start + chunk_size).min(data.len());\n                    let chunk_data = if start < data.len() {\n                        &data[start..end]\n                    } else {\n                        &[]\n                    };\n\n                    // Create record: [path, chunk_no, bytes]\n                    let record = ImmutableRecord::from_values(\n                        &[\n                            Value::Text(Text::new(path_str.clone())),\n                            Value::from_i64(actual_chunk_idx as i64),\n                            Value::Blob(chunk_data.to_vec()),\n                        ],\n                        3,\n                    );\n\n                    // Seek to find the correct position using GE (not eq_only)\n                    // This positions the cursor at or after where the record should be inserted\n                    let _result = return_if_io!(\n                        cursor.seek(SeekKey::IndexKey(&record), SeekOp::GE { eq_only: false })\n                    );\n\n                    // don't do insert in same state to avoid re-seeking on IO\n                    self.state = FtsState::InsertingWrite {\n                        writes: std::mem::take(writes),\n                        write_idx: *write_idx,\n                        chunk_idx: actual_chunk_idx,\n                        record,\n                    };\n                }\n                FtsState::InsertingWrite {\n                    writes,\n                    write_idx,\n                    chunk_idx,\n                    record,\n                } => {\n                    let cursor = self.fts_dir_cursor.as_mut().ok_or_else(|| {\n                        LimboError::InternalError(\"cursor not initialized\".into())\n                    })?;\n\n                    // the cursor should be positioned correctly after seek\n                    return_if_io!(cursor.insert(&BTreeKey::IndexKey(record)));\n\n                    // Move to next chunk\n                    self.state = FtsState::FlushingWrites {\n                        writes: std::mem::take(writes),\n                        write_idx: *write_idx,\n                        chunk_idx: Some(*chunk_idx + 1),\n                    };\n                }\n                FtsState::Ready => {\n                    return Ok(IOResult::Done(()));\n                }\n                _ => {\n                    return Err(LimboError::InternalError(\n                        \"unexpected state in flush_writes_internal\".into(),\n                    ));\n                }\n            }\n        }\n    }\n\n    /// Internal helper to continue flush_deletes state machine\n    fn flush_deletes_internal(&mut self) -> Result<IOResult<()>> {\n        loop {\n            match &mut self.state {\n                FtsState::FlushingDeletes {\n                    deletes,\n                    delete_idx,\n                } => {\n                    if *delete_idx >= deletes.len() {\n                        self.state = FtsState::Ready;\n                        return Ok(IOResult::Done(()));\n                    }\n\n                    self.state = FtsState::SeekingDelete {\n                        deletes: std::mem::take(deletes),\n                        delete_idx: *delete_idx,\n                    };\n                }\n                FtsState::SeekingDelete {\n                    deletes,\n                    delete_idx,\n                } => {\n                    let cursor = self.fts_dir_cursor.as_mut().ok_or_else(|| {\n                        LimboError::InternalError(\"cursor not initialized\".into())\n                    })?;\n\n                    let path = &deletes[*delete_idx];\n                    let path_str = path.to_string_lossy().to_string();\n\n                    // Seek to first chunk of this path with empty blob (minimum value for bytes)\n                    let seek_key = ImmutableRecord::from_values(\n                        &[\n                            Value::Text(Text::new(path_str)),\n                            Value::from_i64(0),\n                            Value::Blob(vec![]),\n                        ],\n                        3,\n                    );\n\n                    let _result =\n                        return_if_io!(cursor\n                            .seek(SeekKey::IndexKey(&seek_key), SeekOp::GE { eq_only: false }));\n\n                    self.state = FtsState::DeletingRecord {\n                        deletes: std::mem::take(deletes),\n                        delete_idx: *delete_idx,\n                    };\n                }\n                FtsState::DeletingRecord {\n                    deletes,\n                    delete_idx,\n                } => {\n                    let cursor = self.fts_dir_cursor.as_mut().ok_or_else(|| {\n                        LimboError::InternalError(\"cursor not initialized\".into())\n                    })?;\n\n                    let path = &deletes[*delete_idx];\n                    let path_str = path.to_string_lossy().to_string();\n\n                    if !cursor.has_record() {\n                        // No more records, move to next path\n                        *delete_idx += 1;\n                        if *delete_idx >= deletes.len() {\n                            self.state = FtsState::Ready;\n                            return Ok(IOResult::Done(()));\n                        }\n                        self.state = FtsState::FlushingDeletes {\n                            deletes: std::mem::take(deletes),\n                            delete_idx: *delete_idx,\n                        };\n                        continue;\n                    }\n\n                    // Check if current record matches our path\n                    let record = return_if_io!(cursor.record());\n                    let matches = if let Some(record) = record {\n                        match record.get_value_opt(0) {\n                            Some(crate::types::ValueRef::Text(t)) => t.value == path_str,\n                            _ => false,\n                        }\n                    } else {\n                        false\n                    };\n\n                    if matches {\n                        // Delete this record\n                        return_if_io!(cursor.delete());\n                        // Cursor automatically moves to next, stay in this state\n                    } else {\n                        // No more chunks for this path, move to next\n                        *delete_idx += 1;\n                        if *delete_idx >= deletes.len() {\n                            self.state = FtsState::Ready;\n                            return Ok(IOResult::Done(()));\n                        }\n                        self.state = FtsState::FlushingDeletes {\n                            deletes: std::mem::take(deletes),\n                            delete_idx: *delete_idx,\n                        };\n                    }\n                }\n                FtsState::Ready => {\n                    return Ok(IOResult::Done(()));\n                }\n                _ => {\n                    return Err(LimboError::InternalError(\n                        \"unexpected state in flush_deletes_internal\".into(),\n                    ));\n                }\n            }\n        }\n    }\n\n    /// Commit pending documents to Tantivy and flush to BTree.\n    /// If `force_flush` is true, flushes directory writes even when no pending docs.\n    fn commit_and_flush_inner(&mut self, force_flush: bool) -> Result<IOResult<()>> {\n        // Handle flush state machine if already in progress\n        match &self.state {\n            FtsState::FlushingWrites { .. }\n            | FtsState::SeekingOldChunks { .. }\n            | FtsState::AdvancingAfterSeek { .. }\n            | FtsState::CheckingChunkPath { .. }\n            | FtsState::DeletingChunk { .. }\n            | FtsState::AdvancingAfterDelete { .. }\n            | FtsState::SeekingWrite { .. }\n            | FtsState::InsertingWrite { .. } => {\n                return self.flush_writes_internal();\n            }\n            _ => {}\n        }\n\n        if self.pending_docs_count == 0 && !force_flush {\n            return Ok(IOResult::Done(()));\n        }\n\n        // Commit Tantivy to make documents visible\n        if let Some(ref mut writer) = self.writer {\n            tracing::debug!(\n                \"FTS commit_and_flush: committing {} documents\",\n                self.pending_docs_count\n            );\n            writer\n                .commit()\n                .map_err(|e| LimboError::InternalError(format!(\"FTS commit error: {e}\")))?;\n\n            // Invalidate shared directory cache since index has changed\n            // Next query will reload the updated catalog\n            {\n                let mut cache = self.shared_directory_cache.write();\n                if cache.is_some() {\n                    tracing::debug!(\"FTS commit_and_flush: invalidating cached directory\");\n                    *cache = None;\n                }\n            }\n        }\n        if let Some(ref reader) = self.reader {\n            reader\n                .reload()\n                .map_err(|e| LimboError::InternalError(format!(\"FTS reader reload error: {e}\")))?;\n            self.searcher = Some(reader.searcher());\n            // Invalidate cached parser since index changed (segments may differ)\n            self.cached_parser = None;\n        }\n\n        self.pending_docs_count = 0;\n\n        // Flush pending writes to BTree via async state machine\n        let writes = self\n            .hybrid_directory\n            .as_ref()\n            .map(|dir| dir.take_pending_writes())\n            .unwrap_or_default();\n\n        if !writes.is_empty() {\n            tracing::debug!(\n                \"FTS commit_and_flush: flushing {} files to BTree\",\n                writes.len()\n            );\n            self.state = FtsState::FlushingWrites {\n                writes,\n                write_idx: 0,\n                chunk_idx: Some(0),\n            };\n            return self.flush_writes_internal();\n        }\n\n        Ok(IOResult::Done(()))\n    }\n\n    /// Commit pending documents to Tantivy and flush to BTree.\n    pub fn commit_and_flush(&mut self) -> Result<IOResult<()>> {\n        self.commit_and_flush_inner(false)\n    }\n}\n\nimpl Drop for FtsCursor {\n    fn drop(&mut self) {\n        // Skip cleanup if we're already panicking\n        if crate::thread::panicking() {\n            return;\n        }\n\n        // Get connection reference for transaction check and pager access\n        let conn = match &self.connection {\n            Some(conn) => conn.clone(),\n            None => {\n                if self.pending_docs_count > 0 {\n                    tracing::warn!(\n                        \"FTS Drop: {} pending documents lost (no connection)\",\n                        self.pending_docs_count\n                    );\n                }\n                return;\n            }\n        };\n\n        let pager = conn.pager.load().clone();\n\n        // Check if we're already in a flushing state (from commit_and_flush)\n        // This can happen when commit_and_flush started a flush but yielded for IO\n        // and the cursor is being dropped before the flush completed\n        let is_flushing = matches!(\n            &self.state,\n            FtsState::FlushingWrites { .. }\n                | FtsState::SeekingOldChunks { .. }\n                | FtsState::AdvancingAfterSeek { .. }\n                | FtsState::CheckingChunkPath { .. }\n                | FtsState::DeletingChunk { .. }\n                | FtsState::AdvancingAfterDelete { .. }\n                | FtsState::SeekingWrite { .. }\n                | FtsState::InsertingWrite { .. }\n        );\n\n        if is_flushing {\n            turso_assert!(conn.is_in_write_tx(), \"FTS Drop: in-progress flush abandoned (transaction already committed). pre_commit should have completed the flush.\");\n\n            tracing::debug!(\"FTS Drop: completing in-progress flush\");\n            loop {\n                match self.flush_writes_internal() {\n                    Ok(IOResult::Done(())) => break,\n                    Ok(IOResult::IO(_)) => {\n                        if let Err(e) = pager.io.step() {\n                            tracing::error!(\"FTS Drop: IO error during flush: {}\", e);\n                            break;\n                        }\n                    }\n                    Err(e) => {\n                        tracing::error!(\"FTS Drop: error during flush: {}\", e);\n                        break;\n                    }\n                }\n            }\n            return;\n        }\n\n        // Only flush new pending documents if we have any\n        if self.pending_docs_count == 0 {\n            return;\n        }\n\n        // If the transaction has already committed (auto-commit), flushing to BTree\n        // would create dirty pages outside of any transaction, causing the\n        // \"dirty pages must be empty for read txn\" panic on the next read.\n        turso_assert!(\n            conn.is_in_write_tx(),\n            \"FTS Drop: transaction already committed, cannot flush\",\n            { \"pending_docs_count\": self.pending_docs_count }\n        );\n\n        // Commit any pending writes to Tantivy\n        if let Some(ref mut writer) = self.writer {\n            if let Err(e) = writer.commit() {\n                tracing::error!(\"FTS Drop: failed to commit writer: {}\", e);\n                return;\n            }\n\n            // Invalidate shared directory cache since index has changed\n            // This MUST happen after commit but before we check for pending writes\n            // to ensure the next cursor loads fresh data from BTree\n            {\n                let mut cache = self.shared_directory_cache.write();\n                if cache.is_some() {\n                    tracing::debug!(\"FTS Drop: invalidating cached directory\");\n                    *cache = None;\n                }\n            }\n        }\n\n        let Some(ref dir) = self.hybrid_directory else {\n            return;\n        };\n        let writes = dir.take_pending_writes();\n\n        if writes.is_empty() {\n            return;\n        }\n\n        tracing::debug!(\n            \"FTS Drop: blocking flush of {} files to BTree\",\n            writes.len()\n        );\n\n        // Set up flush state machine\n        self.state = FtsState::FlushingWrites {\n            writes,\n            write_idx: 0,\n            chunk_idx: Some(0),\n        };\n\n        // Run blocking flush\n        loop {\n            match self.flush_writes_internal() {\n                Ok(IOResult::Done(())) => break,\n                Ok(IOResult::IO(_)) => {\n                    if let Err(e) = pager.io.step() {\n                        tracing::error!(\"FTS Drop: IO error during flush: {}\", e);\n                        break;\n                    }\n                }\n                Err(e) => {\n                    tracing::error!(\"FTS Drop: error during flush: {}\", e);\n                    break;\n                }\n            }\n        }\n    }\n}\n\nimpl IndexMethodCursor for FtsCursor {\n    /// Creates the FTS index storage (internal BTree table for Tantivy files).\n    fn create(&mut self, conn: &Arc<Connection>) -> Result<IOResult<()>> {\n        initialize_btree_storage_table(conn, &self.dir_table_name)?;\n        Ok(IOResult::Done(()))\n    }\n\n    /// Destroys the FTS index, dropping all storage and clearing caches.\n    fn destroy(&mut self, conn: &Arc<Connection>) -> Result<IOResult<()>> {\n        tracing::debug!(\n            \"FTS destroy: dropping internal storage {}\",\n            self.dir_table_name\n        );\n\n        // Drop all in-memory components first\n        self.searcher = None;\n        self.reader = None;\n        self.writer = None;\n        self.index = None;\n        self.hybrid_directory = None;\n        self.fts_dir_cursor = None;\n\n        // Invalidate shared directory cache\n        {\n            let mut cache = self.shared_directory_cache.write();\n            *cache = None;\n        }\n\n        // Drop the internal storage table and index\n        // The backing_btree index will be dropped automatically when the table is dropped\n        // Use start_nested() before prepare() to bypass system table protection,\n        // then use prepare/run_ignore_rows pattern and disable subtransactions to avoid Busy error\n        let drop_table_ast = ast::Stmt::DropTable {\n            if_exists: true,\n            tbl_name: ast::QualifiedName::single(ast::Name::exact(self.dir_table_name.clone())),\n        };\n        conn.start_nested();\n        let mut stmt = conn.prepare_stmt(drop_table_ast)?;\n        // Disable subtransactions since we're already inside a transaction from the parent DROP INDEX\n        stmt.program\n            .prepared\n            .needs_stmt_subtransactions\n            .store(false, Ordering::Relaxed);\n        let result = stmt.run_ignore_rows();\n        conn.end_nested();\n        result?;\n\n        self.state = FtsState::Init;\n        Ok(IOResult::Done(()))\n    }\n\n    /// Opens the index for reading, loading the catalog and creating a searcher.\n    /// Uses async state machine for non-blocking IO during catalog/file loading.\n    fn open_read(&mut self, conn: &Arc<Connection>) -> Result<IOResult<()>> {\n        loop {\n            match &mut self.state {\n                FtsState::Init => {\n                    self.connection = Some(conn.clone());\n                    // Ensure storage table exists\n                    initialize_btree_storage_table(conn, &self.dir_table_name)?;\n                    // Open BTree cursor (needed for btree_root_page)\n                    self.open_cursor(conn)?;\n\n                    // Check for cached directory, avoid expensive catalog reload\n                    {\n                        let cache = self.shared_directory_cache.read();\n                        if let Some(ref cached) = *cache {\n                            tracing::debug!(\n                                \"FTS open_read: using cached directory (skipping catalog load)\"\n                            );\n                            // Clone with fresh pending state to ensure this cursor's writes\n                            // don't affect other cursors or cause Drop to flush after txn commits\n                            self.hybrid_directory =\n                                Some(cached.directory.clone_with_fresh_pending());\n                            // Skip to CreatingIndex to build Index/Reader from cached directory\n                            self.state = FtsState::CreatingIndex;\n                            continue;\n                        }\n                    }\n\n                    // No cache available, proceed with full catalog loading\n                    self.state = FtsState::Rewinding;\n                }\n                FtsState::Rewinding => {\n                    let cursor = self.fts_dir_cursor.as_mut().ok_or_else(|| {\n                        LimboError::InternalError(\"cursor not initialized\".into())\n                    })?;\n                    return_if_io!(cursor.rewind());\n                    // Use catalog-first loading for HybridBTreeDirectory\n                    self.state = FtsState::LoadingCatalog {\n                        catalog_builder: HashMap::default(),\n                        current_path: None,\n                    };\n                }\n                FtsState::LoadingCatalog {\n                    catalog_builder,\n                    current_path,\n                } => {\n                    let cursor = self.fts_dir_cursor.as_mut().ok_or_else(|| {\n                        LimboError::InternalError(\"cursor not initialized\".into())\n                    })?;\n\n                    if !cursor.has_record() {\n                        // Done scanning: build catalog and assemble hot files in single pass\n                        let mut catalog = HashMap::default();\n                        let mut hot_files: HashMap<PathBuf, Vec<u8>> = HashMap::default();\n                        let mut files_to_load = Vec::new();\n\n                        for (path, chunks) in catalog_builder.drain() {\n                            let max_chunk = chunks.keys().max().copied().unwrap_or(0);\n                            let total_size: usize = chunks.values().map(|(size, _)| size).sum();\n                            let num_chunks = (max_chunk + 1) as usize;\n                            let metadata = FileMetadata::new(&path, total_size, num_chunks);\n\n                            if metadata.category.is_hot() {\n                                // Try to assemble from captured blob data\n                                let mut all_captured = true;\n                                let mut assembled = Vec::with_capacity(total_size);\n                                for chunk_no in 0..=(max_chunk) {\n                                    if let Some((_, Some(data))) = chunks.get(&chunk_no) {\n                                        assembled.extend_from_slice(data);\n                                    } else {\n                                        all_captured = false;\n                                        break;\n                                    }\n                                }\n                                if all_captured {\n                                    hot_files.insert(path.clone(), assembled);\n                                } else {\n                                    files_to_load.push(path.clone());\n                                }\n                            } else if metadata.category.should_preload() {\n                                files_to_load.push(path.clone());\n                            }\n\n                            catalog.insert(path, metadata);\n                        }\n\n                        tracing::debug!(\n                            \"FTS LoadingCatalog: found {} files, {} hot assembled, {} to preload\",\n                            catalog.len(),\n                            hot_files.len(),\n                            files_to_load.len()\n                        );\n\n                        // Create HybridBTreeDirectory with catalog and pre-assembled hot files\n                        let pager = conn.pager.load().clone();\n                        let root_page = self.btree_root_page.ok_or_else(|| {\n                            LimboError::InternalError(\"btree_root_page not set\".into())\n                        })?;\n\n                        let hybrid_dir = HybridBTreeDirectory::with_preloaded(\n                            pager,\n                            root_page,\n                            catalog,\n                            hot_files,\n                            DEFAULT_HOT_CACHE_BYTES,\n                            DEFAULT_CHUNK_CACHE_BYTES,\n                        );\n                        self.hybrid_directory = Some(hybrid_dir);\n\n                        if files_to_load.is_empty() {\n                            // All hot files assembled in single pass, skip PreloadingEssentials\n                            self.state = FtsState::CreatingIndex;\n                        } else {\n                            self.state = FtsState::PreloadingEssentials {\n                                files_to_load,\n                                loaded_files: HashMap::default(),\n                                current_loading: None,\n                                current_chunks: Vec::new(),\n                            };\n                        }\n                        continue;\n                    }\n\n                    // Read record metadata and capture hot file blobs in single pass\n                    let record = return_if_io!(cursor.record());\n                    if let Some(record) = record {\n                        let path_str = record.get_value_opt(0).and_then(|v| match v {\n                            crate::types::ValueRef::Text(t) => Some(t.value.to_string()),\n                            _ => None,\n                        });\n                        let chunk_no = record.get_value_opt(1).and_then(|v| match v {\n                            crate::types::ValueRef::Numeric(crate::numeric::Numeric::Integer(\n                                i,\n                            )) => Some(i),\n                            _ => None,\n                        });\n\n                        if let (Some(path_str), Some(chunk_no)) = (path_str, chunk_no) {\n                            // Reuse PathBuf when path hasn't changed (records are BTree-ordered)\n                            let path_buf = if current_path.as_ref().map(|p| p.as_os_str().to_str())\n                                == Some(Some(&path_str))\n                            {\n                                current_path.clone().unwrap()\n                            } else {\n                                let p = PathBuf::from(&path_str);\n                                *current_path = Some(p.clone());\n                                p\n                            };\n\n                            // Classify file to decide whether to capture blob data\n                            let category = FileCategory::from_path(&path_buf);\n                            let (blob_size, blob_data) = record\n                                .get_value_opt(2)\n                                .map(|v| match v {\n                                    crate::types::ValueRef::Blob(b) => {\n                                        let size = b.len();\n                                        if category.is_hot() {\n                                            (size, Some(b.to_vec()))\n                                        } else {\n                                            (size, None)\n                                        }\n                                    }\n                                    _ => (0, None),\n                                })\n                                .unwrap_or((0, None));\n\n                            let chunks = catalog_builder.entry(path_buf).or_default();\n                            chunks.insert(chunk_no, (blob_size, blob_data));\n                        }\n                    }\n\n                    return_if_io!(cursor.next());\n                }\n                FtsState::PreloadingEssentials {\n                    files_to_load,\n                    loaded_files,\n                    current_loading,\n                    current_chunks,\n                } => {\n                    // Use blocking file load from HybridBTreeDirectory\n                    let hybrid_dir = self.hybrid_directory.as_ref().ok_or_else(|| {\n                        LimboError::InternalError(\"hybrid_directory not initialized\".into())\n                    })?;\n\n                    // If we're loading a file, continue with it\n                    if let Some(path) = current_loading.take() {\n                        // We loaded chunks, finalize the file\n                        if !current_chunks.is_empty() {\n                            current_chunks.sort_by_key(|(chunk_no, _)| *chunk_no);\n\n                            // Deduplicate\n                            let mut deduped: Vec<(i64, Vec<u8>)> = Vec::new();\n                            for (chunk_no, bytes) in current_chunks.drain(..) {\n                                if let Some(last) = deduped.last_mut() {\n                                    if last.0 == chunk_no {\n                                        *last = (chunk_no, bytes);\n                                    } else {\n                                        deduped.push((chunk_no, bytes));\n                                    }\n                                } else {\n                                    deduped.push((chunk_no, bytes));\n                                }\n                            }\n\n                            let data: Vec<u8> =\n                                deduped.iter().flat_map(|(_, b)| b.clone()).collect();\n                            loaded_files.insert(path.clone(), data.clone());\n\n                            // Add to hot cache\n                            hybrid_dir.add_to_hot_cache(path, data);\n                        }\n                    }\n\n                    // Check if we have more files to load\n                    if let Some(next_path) = files_to_load.pop() {\n                        // Load this file using blocking IO\n                        match hybrid_dir.load_file_blocking(&next_path) {\n                            Ok(data) => {\n                                loaded_files.insert(next_path.clone(), data.clone());\n                                hybrid_dir.add_to_hot_cache(next_path, data);\n                            }\n                            Err(e) => {\n                                let category = FileCategory::from_path(&next_path);\n                                // NotFound is expected for new empty indexes\n                                if e.kind() == std::io::ErrorKind::NotFound {\n                                    // Expected for new index, just log at debug level\n                                    tracing::debug!(\n                                        \"FTS: preload skipped (not found): {}\",\n                                        next_path.display()\n                                    );\n                                } else if category == FileCategory::Metadata {\n                                    // Metadata files are critical - propagate error\n                                    return Err(LimboError::InternalError(format!(\n                                        \"FTS: failed to preload metadata file {}: {}\",\n                                        next_path.display(),\n                                        e\n                                    )));\n                                } else {\n                                    // Non-critical file, warn but continue\n                                    tracing::warn!(\n                                        \"FTS: could not preload {} ({:?}): {}\",\n                                        next_path.display(),\n                                        category,\n                                        e\n                                    );\n                                }\n                            }\n                        }\n                        continue;\n                    }\n\n                    // All files loaded\n                    tracing::debug!(\n                        \"FTS PreloadingEssentials: loaded {} files into hot cache\",\n                        loaded_files.len()\n                    );\n                    self.state = FtsState::CreatingIndex;\n                    continue;\n                }\n                FtsState::CreatingIndex => {\n                    // Log loaded files for debugging\n                    if let Some(ref dir) = self.hybrid_directory {\n                        tracing::debug!(\"FTS CreatingIndex: {:?}\", dir);\n                    }\n\n                    // Create Tantivy index from directory\n                    self.create_index_from_directory()?;\n\n                    // Create reader and searcher\n                    if let Some(ref index) = self.index {\n                        self.reader = Some(\n                            index\n                                .reader()\n                                .map_err(|e| LimboError::InternalError(e.to_string()))?,\n                        );\n                        if let Some(ref reader) = self.reader {\n                            self.searcher = Some(reader.searcher());\n                        }\n                    }\n\n                    // Cache the directory for future queries (avoids catalog reload)\n                    if let Some(ref dir) = self.hybrid_directory {\n                        let mut cache = self.shared_directory_cache.write();\n                        *cache = Some(CachedFtsDirectory {\n                            directory: dir.clone(),\n                        });\n                        tracing::debug!(\"FTS CreatingIndex: cached directory for future queries\");\n                    }\n\n                    self.state = FtsState::Ready;\n                    return Ok(IOResult::Done(()));\n                }\n                FtsState::Ready => {\n                    return Ok(IOResult::Done(()));\n                }\n                _ => {\n                    return Err(LimboError::InternalError(\n                        \"unexpected state in open_read\".into(),\n                    ));\n                }\n            }\n        }\n    }\n\n    /// Opens the index for writing, creating the IndexWriter.\n    /// Calls `open_read` first if not already initialized.\n    fn open_write(&mut self, conn: &Arc<Connection>) -> Result<IOResult<()>> {\n        if self.connection.is_none() {\n            self.connection = Some(conn.clone());\n        }\n\n        // First do open_read to load existing index\n        match &self.state {\n            FtsState::Ready => {}\n            _ => {\n                let result = self.open_read(conn)?;\n                if let IOResult::IO(io) = result {\n                    return Ok(IOResult::IO(io));\n                }\n            }\n        }\n        // Should we assert no writer here? Tantivy enforces single writer\n        // it's just unsure if this can be called multiple times\n        if self.writer.is_some() {\n            return Ok(IOResult::Done(()));\n        }\n\n        // Now create writer\n        if let Some(ref index) = self.index {\n            // Use single-threaded mode to avoid concurrent access\n            let writer = index\n                .writer_with_num_threads(1, DEFAULT_MEMORY_BUDGET_BYTES)\n                .map_err(|e| LimboError::InternalError(e.to_string()))?;\n            // Disable background merges\n            writer.set_merge_policy(Box::new(NoMergePolicy));\n            self.writer = Some(writer);\n        }\n        Ok(IOResult::Done(()))\n    }\n\n    /// Inserts a document into the FTS index.\n    /// Values are text columns followed by rowid. Batches commits for efficiency.\n    fn insert(&mut self, values: &[Register]) -> Result<IOResult<()>> {\n        // Handle flush state machine if in progress\n        loop {\n            match &self.state {\n                FtsState::FlushingWrites { .. }\n                | FtsState::SeekingOldChunks { .. }\n                | FtsState::AdvancingAfterSeek { .. }\n                | FtsState::CheckingChunkPath { .. }\n                | FtsState::DeletingChunk { .. }\n                | FtsState::AdvancingAfterDelete { .. }\n                | FtsState::SeekingWrite { .. }\n                | FtsState::InsertingWrite { .. } => {\n                    let result = self.flush_writes_internal()?;\n                    match result {\n                        IOResult::IO(io) => return Ok(IOResult::IO(io)),\n                        IOResult::Done(()) => continue, // Flush done, check state again\n                    }\n                }\n                FtsState::FlushingDeletes { .. }\n                | FtsState::SeekingDelete { .. }\n                | FtsState::DeletingRecord { .. } => {\n                    let result = self.flush_deletes_internal()?;\n                    match result {\n                        IOResult::IO(io) => return Ok(IOResult::IO(io)),\n                        IOResult::Done(()) => continue, // Flush done, check state again\n                    }\n                }\n                _ => break, // Not flushing, proceed with insert\n            }\n        }\n\n        let Some(ref mut writer) = self.writer else {\n            return Err(LimboError::InternalError(\n                \"FTS writer not initialized - call open_write first\".into(),\n            ));\n        };\n\n        // Last register is rowid\n        let rowid_reg = values.last().ok_or_else(|| {\n            LimboError::InternalError(\"FTS insert requires at least rowid\".into())\n        })?;\n        let rowid = match rowid_reg {\n            Register::Value(Value::Numeric(crate::numeric::Numeric::Integer(i))) => *i,\n            _ => {\n                return Err(LimboError::InternalError(\n                    \"FTS rowid must be integer\".into(),\n                ))\n            }\n        };\n\n        let mut doc = TantivyDocument::default();\n        doc.add_i64(self.rowid_field, rowid);\n\n        for ((_col, field), reg) in self.text_fields.iter().zip(&values[..values.len() - 1]) {\n            match reg {\n                Register::Value(Value::Text(t)) => {\n                    doc.add_text(*field, t.as_str());\n                }\n                Register::Value(Value::Null) => continue,\n                _ => continue,\n            }\n        }\n\n        writer\n            .add_document(doc)\n            .map_err(|e| LimboError::InternalError(format!(\"FTS add_document error: {e}\")))?;\n\n        self.pending_docs_count += 1;\n\n        // Batch commits: only commit every BATCH_COMMIT_SIZE documents\n        // This dramatically improves bulk insert performance for CREATE INDEX\n        if self.pending_docs_count >= BATCH_COMMIT_SIZE {\n            return self.commit_and_flush();\n        }\n\n        Ok(IOResult::Done(()))\n    }\n\n    /// Deletes a document from the FTS index by rowid.\n    fn delete(&mut self, values: &[Register]) -> Result<IOResult<()>> {\n        let Some(ref mut writer) = self.writer else {\n            return Err(LimboError::InternalError(\n                \"FTS writer not initialized - call open_write first\".into(),\n            ));\n        };\n        // Last register is rowid\n        let rowid_reg = values.last().ok_or_else(|| {\n            LimboError::InternalError(\"FTS delete requires at least rowid\".into())\n        })?;\n        let rowid = match rowid_reg {\n            Register::Value(Value::Numeric(crate::numeric::Numeric::Integer(i))) => *i,\n            _ => {\n                return Err(LimboError::InternalError(\n                    \"FTS rowid must be integer\".into(),\n                ))\n            }\n        };\n\n        let term = tantivy::Term::from_field_i64(self.rowid_field, rowid);\n        writer.delete_term(term);\n\n        // Track delete as a pending operation so commit_and_flush() will run\n        // and invalidate the shared directory cache\n        self.pending_docs_count += 1;\n        if self.pending_docs_count >= BATCH_COMMIT_SIZE {\n            return self.commit_and_flush();\n        }\n\n        Ok(IOResult::Done(()))\n    }\n\n    /// Starts an FTS query. Parses the query string and executes the search.\n    /// Returns true if there are results, false otherwise.\n    fn query_start(&mut self, values: &[Register]) -> Result<IOResult<bool>> {\n        let Some(ref searcher) = self.searcher else {\n            return Err(LimboError::InternalError(\n                \"FTS searcher not initialized - call open_read first\".into(),\n            ));\n        };\n        if values.is_empty() {\n            return Err(LimboError::InternalError(\n                \"FTS query_start: missing pattern id\".into(),\n            ));\n        }\n\n        // values[0] = pattern index\n        let pattern_idx = match &values[0] {\n            Register::Value(Value::Numeric(crate::numeric::Numeric::Integer(i))) => *i,\n            _ => FTS_PATTERN_SCORE,\n        };\n        self.current_pattern = pattern_idx;\n\n        // values[1] = query string\n        let query_str = match &values[1] {\n            Register::Value(Value::Text(t)) => t.as_str().to_string(),\n            _ => return Err(LimboError::InternalError(\"FTS query must be text\".into())),\n        };\n\n        // Determine limit based on pattern:\n        // - Patterns WITHOUT LIMIT in pattern: fetch all matches (high limit)\n        // - Patterns WITH LIMIT: use the captured limit value from values[2]\n        let limit_raw = match pattern_idx {\n            // Patterns without LIMIT - fetch all matches\n            FTS_PATTERN_MATCH | FTS_PATTERN_COMBINED | FTS_PATTERN_COMBINED_ORDERED => {\n                Self::MAX_NO_LIMIT_RESULT as i64\n            }\n            // Patterns with LIMIT - use captured limit value\n            FTS_PATTERN_SCORE\n            | FTS_PATTERN_MATCH_LIMIT\n            | FTS_PATTERN_COMBINED_LIMIT\n            | FTS_PATTERN_COMBINED_ORDERED_LIMIT => {\n                if values.len() > 2 {\n                    match &values[2] {\n                        Register::Value(Value::Numeric(crate::numeric::Numeric::Integer(i))) => *i,\n                        _ => {\n                            tracing::debug!(\n                                \"FTS query_start: LIMIT value is not an integer, using default 10\"\n                            );\n                            10\n                        }\n                    }\n                } else {\n                    tracing::debug!(\n                        \"FTS query_start: LIMIT pattern but no limit value provided, using default 10\"\n                    );\n                    10\n                }\n            }\n            _ => {\n                tracing::debug!(\n                    \"FTS query_start: unknown pattern {}, using default limit 10\",\n                    pattern_idx\n                );\n                10\n            }\n        };\n\n        // Reuse cached QueryParser or build one on first query\n        if self.cached_parser.is_none() {\n            let index = self\n                .index\n                .as_ref()\n                .ok_or_else(|| LimboError::InternalError(\"FTS index not initialized\".into()))?;\n            let mut parser =\n                tantivy::query::QueryParser::for_index(index, self.default_fields.clone());\n            for &(field, boost) in &self.field_boosts {\n                parser.set_field_boost(field, boost);\n            }\n            self.cached_parser = Some(parser);\n        }\n        let parser = self.cached_parser.as_ref().unwrap();\n\n        let query = parser\n            .parse_query(&query_str)\n            .map_err(|e| LimboError::InternalError(format!(\"FTS parse error: {e}\")))?;\n\n        if limit_raw == 0 {\n            self.current_hits.clear();\n            self.hit_pos = 0;\n            return Ok(IOResult::Done(false));\n        }\n\n        let limit = if limit_raw < 0 {\n            Self::MAX_NO_LIMIT_RESULT\n        } else {\n            limit_raw as usize\n        };\n\n        let top_docs = searcher\n            .search(&query, &tantivy::collector::TopDocs::with_limit(limit))\n            .map_err(|e| LimboError::InternalError(format!(\"FTS search error: {e}\")))?;\n\n        self.current_hits.clear();\n        self.hit_pos = 0;\n\n        // Group results by segment for efficient fast field access.\n        // This avoids creating a new fast field reader for each document.\n        let mut by_segment: HashMap<u32, Vec<(f32, tantivy::DocAddress)>> = HashMap::default();\n        for (score, doc_addr) in top_docs {\n            by_segment\n                .entry(doc_addr.segment_ord)\n                .or_default()\n                .push((score, doc_addr));\n        }\n\n        // Process each segment's results with a single fast field reader.\n        // Fast fields provide columnar O(1) access to rowids without loading full documents.\n        for (segment_ord, hits) in by_segment {\n            let segment_reader = searcher.segment_reader(segment_ord);\n            let rowid_reader = segment_reader\n                .fast_fields()\n                .i64(ROWID_FIELD)\n                .map_err(|e| LimboError::InternalError(format!(\"FTS fast field error: {e}\")))?;\n\n            for (score, doc_addr) in hits {\n                let rowid = rowid_reader.first(doc_addr.doc_id).ok_or_else(|| {\n                    LimboError::InternalError(\"FTS: rowid fast field missing value\".into())\n                })?;\n                self.current_hits.push((score, doc_addr, rowid));\n            }\n        }\n\n        // Re-sort by score since we grouped by segment (preserves original ranking order)\n        self.current_hits\n            .sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));\n\n        Ok(IOResult::Done(!self.current_hits.is_empty()))\n    }\n\n    /// Advances to the next query result. Returns true if more results exist.\n    fn query_next(&mut self) -> Result<IOResult<bool>> {\n        if self.hit_pos >= self.current_hits.len() {\n            return Ok(IOResult::Done(false));\n        }\n        self.hit_pos += 1;\n        Ok(IOResult::Done(self.hit_pos < self.current_hits.len()))\n    }\n\n    /// Returns the column value for the current result (score or match indicator).\n    fn query_column(&mut self, idx: usize) -> Result<IOResult<Value>> {\n        // Column 0 = score for fts_score, or 1 (true) for fts_match\n        if idx != 0 {\n            return Err(LimboError::InternalError(\n                \"FTS: only column 0 supported\".into(),\n            ));\n        }\n\n        if self.hit_pos >= self.current_hits.len() {\n            return Err(LimboError::InternalError(\n                \"FTS: query_column out of bounds\".into(),\n            ));\n        }\n\n        match self.current_pattern {\n            FTS_PATTERN_MATCH | FTS_PATTERN_MATCH_LIMIT => {\n                // For fts_match patterns, return 1 (true) - indicates this row matches\n                Ok(IOResult::Done(Value::from_i64(1)))\n            }\n            FTS_PATTERN_SCORE\n            | FTS_PATTERN_COMBINED\n            | FTS_PATTERN_COMBINED_LIMIT\n            | FTS_PATTERN_COMBINED_ORDERED\n            | FTS_PATTERN_COMBINED_ORDERED_LIMIT => {\n                // For fts_score and combined patterns, return the actual score\n                let (score, _, _) = self.current_hits[self.hit_pos];\n                Ok(IOResult::Done(Value::from_f64(score as f64)))\n            }\n            _ => {\n                // Unknown pattern - return score as default\n                let (score, _, _) = self.current_hits[self.hit_pos];\n                Ok(IOResult::Done(Value::from_f64(score as f64)))\n            }\n        }\n    }\n\n    /// Returns the rowid for the current query result.\n    fn query_rowid(&mut self) -> Result<IOResult<Option<i64>>> {\n        if self.hit_pos >= self.current_hits.len() {\n            return Ok(IOResult::Done(None));\n        }\n        let (_, _, rowid) = self.current_hits[self.hit_pos];\n        Ok(IOResult::Done(Some(rowid)))\n    }\n\n    /// Flushes pending writes before transaction commit.\n    /// This ensures FTS writes are persisted as part of the transaction.\n    fn pre_commit(&mut self) -> Result<IOResult<()>> {\n        // First, check if we're in the middle of a flush operation that needs to continue\n        // This handles the case where commit_and_flush() returned IOResult::IO and we need\n        // to continue the flush after IO completes\n        match &self.state {\n            FtsState::FlushingWrites { .. }\n            | FtsState::SeekingOldChunks { .. }\n            | FtsState::AdvancingAfterSeek { .. }\n            | FtsState::CheckingChunkPath { .. }\n            | FtsState::DeletingChunk { .. }\n            | FtsState::AdvancingAfterDelete { .. }\n            | FtsState::SeekingWrite { .. }\n            | FtsState::InsertingWrite { .. } => {\n                return self.flush_writes_internal();\n            }\n            _ => {}\n        }\n\n        if self.pending_docs_count > 0 {\n            tracing::debug!(\n                \"FTS pre_commit: flushing {} pending documents\",\n                self.pending_docs_count\n            );\n            return self.commit_and_flush();\n        }\n        Ok(IOResult::Done(()))\n    }\n\n    /// Optimizes the FTS index by merging all segments into one.\n    /// Call via `OPTIMIZE INDEX idx_name` SQL command.\n    fn optimize(&mut self, connection: &Arc<Connection>) -> Result<IOResult<()>> {\n        // First ensure any pending documents are flushed\n        if self.pending_docs_count > 0 {\n            tracing::info!(\n                \"FTS optimize: flushing {} pending documents first\",\n                self.pending_docs_count\n            );\n            return_if_io!(self.commit_and_flush());\n        }\n\n        // If we're not open for writing, open it\n        if self.writer.is_none() {\n            return_if_io!(self.open_write(connection));\n        }\n\n        let index = self\n            .index\n            .as_ref()\n            .ok_or_else(|| LimboError::InternalError(\"FTS index not initialized\".to_string()))?;\n        let writer = self\n            .writer\n            .as_mut()\n            .ok_or_else(|| LimboError::InternalError(\"FTS writer not initialized\".to_string()))?;\n\n        // Get all searchable segment IDs\n        let segment_ids = index\n            .searchable_segment_ids()\n            .map_err(|e| LimboError::InternalError(format!(\"FTS optimize: {e}\")))?;\n\n        if segment_ids.len() <= 1 {\n            tracing::debug!(\n                \"FTS optimize: nothing to merge ({} segments)\",\n                segment_ids.len()\n            );\n            return Ok(IOResult::Done(()));\n        }\n\n        tracing::debug!(\n            \"FTS optimize: merging {} segments into one\",\n            segment_ids.len()\n        );\n        // Schedule the merge operation\n        let merge_future = writer.merge(&segment_ids);\n        // Wait for merge to complete (blocking)\n        match merge_future.wait() {\n            Ok(Some(segment_meta)) => {\n                tracing::debug!(\n                    \"FTS optimize: merge completed, new segment has {} docs\",\n                    segment_meta.num_docs()\n                );\n            }\n            Ok(None) => {\n                // Merge was cancelled or no merge was needed\n                tracing::debug!(\"FTS optimize: merge was cancelled or no merge needed\");\n            }\n            Err(e) => {\n                return Err(LimboError::InternalError(format!(\n                    \"FTS optimize merge failed: {e}\",\n                )));\n            }\n        }\n\n        // Commit merge and invalidate shared directory cache since we changed the structure\n        writer\n            .commit()\n            .map_err(|e| LimboError::InternalError(format!(\"FTS optimize commit failed: {e}\")))?;\n        {\n            let mut cache = self.shared_directory_cache.write();\n            *cache = None;\n        }\n\n        // Reload reader to see merged segments\n        if let Some(ref reader) = self.reader {\n            reader.reload().map_err(|e| {\n                LimboError::InternalError(format!(\"FTS optimize reader reload: {e}\"))\n            })?;\n            self.searcher = Some(reader.searcher());\n        }\n\n        // Force flush directory writes to BTree (even though pending_docs_count == 0)\n        self.commit_and_flush_inner(true)\n    }\n\n    /// Estimates the cost of executing a query with the given pattern.\n    ///\n    /// FTS queries are typically very selective (returning a small fraction of rows).\n    fn estimate_cost(\n        &self,\n        pattern_idx: usize,\n        base_table_rows: f64,\n    ) -> Option<super::IndexMethodCostEstimate> {\n        // FTS is typically very selective - assume ~1% of rows match\n        // This is a conservative estimate; real selectivity depends on query terms\n        let selectivity = 0.01;\n        let estimated_rows = (base_table_rows * selectivity).max(1.0) as u64;\n\n        // Cost model:\n        // - Base cost: logarithmic in vocabulary size (approximated by table size)\n        // - Result cost: linear in number of results\n        let base_cost = (base_table_rows.max(1.0)).ln() * 10.0;\n        let result_cost = estimated_rows as f64 * 0.1;\n\n        // Patterns with LIMIT are significantly cheaper because Tantivy's TopDocs\n        // collector can terminate early. Pattern indices:\n        // 0 = SCORE (ORDER BY + LIMIT)\n        // 1 = COMBINED_ORDERED_LIMIT (WHERE + ORDER BY + LIMIT)\n        // 3 = COMBINED_LIMIT (WHERE + LIMIT)\n        // 5 = MATCH_LIMIT (WHERE + LIMIT)\n        let limit_factor = match pattern_idx {\n            0 | 1 | 3 | 5 => 0.5, // Patterns with LIMIT\n            _ => 1.0,\n        };\n\n        Some(super::IndexMethodCostEstimate {\n            estimated_cost: (base_cost + result_cost) * limit_factor,\n            estimated_rows,\n        })\n    }\n}\n"
  },
  {
    "path": "core/index_method/mod.rs",
    "content": "use std::sync::Arc;\n\nuse rustc_hash::FxHashMap as HashMap;\nuse turso_parser::ast;\n\nuse crate::{\n    schema::IndexColumn,\n    storage::btree::BTreeCursor,\n    types::{IOResult, IndexInfo, KeyInfo},\n    vdbe::Register,\n    Connection, LimboError, Result, Value,\n};\n\npub mod backing_btree;\n#[cfg(all(feature = \"fts\", not(target_family = \"wasm\")))]\npub mod fts;\npub mod toy_vector_sparse_ivf;\n\npub const BACKING_BTREE_INDEX_METHOD_NAME: &str = \"backing_btree\";\npub const TOY_VECTOR_SPARSE_IVF_INDEX_METHOD_NAME: &str = \"toy_vector_sparse_ivf\";\n\n/// index method \"entry point\" which can create attachment of the method to the table with given configuration\n/// (this trait acts like a \"factory\")\npub trait IndexMethod: std::fmt::Debug + Send + Sync {\n    /// create attachment of the index method to the specific table with specific method configuration\n    fn attach(\n        &self,\n        configuration: &IndexMethodConfiguration,\n    ) -> Result<Arc<dyn IndexMethodAttachment>>;\n}\n\n#[derive(Debug, Clone)]\npub struct IndexMethodConfiguration {\n    /// table name for which index_method is defined\n    pub table_name: String,\n    /// index name\n    pub index_name: String,\n    /// columns c1, c2, c3, ... provided to the index method (e.g. create index t_idx on t using method (c1, c2, c3, ...))\n    pub columns: Vec<IndexColumn>,\n    /// optional parameters provided to the index method through WITH clause\n    pub parameters: HashMap<String, Value>,\n}\n\n/// index method attached to the table with specific configuration\n/// the attachment is capable of generating SELECT patterns where index can be used and also can create cursor for query execution\npub trait IndexMethodAttachment: std::fmt::Debug + Send + Sync {\n    fn definition<'a>(&'a self) -> IndexMethodDefinition<'a>;\n    fn init(&self) -> Result<Box<dyn IndexMethodCursor>>;\n}\n\n#[derive(Debug)]\npub struct IndexMethodDefinition<'a> {\n    /// index method name\n    pub method_name: &'a str,\n    /// index name\n    pub index_name: &'a str,\n    /// SELECT patterns where index method can be used\n    /// the patterns can contain positional placeholder which will make planner to capture parameters from the original query and provide them to the index method\n    /// (for example, pattern 'SELECT * FROM {table} LIMIT ?' will capture LIMIT parameter and provide its value from the query to the index method query_start(...) call)\n    pub patterns: &'a [ast::Select],\n    /// special marker which forces tursodb core to treat index method as backing btree - so it will allocate real btree on disk for that index method\n    pub backing_btree: bool,\n    /// Whether `query_start()` materializes all matching rowids up front (e.g. into a Vec/VecDeque).\n    /// When `true`, the cursor is safe to use during DML because it does not lazily stream from\n    /// a live data structure that writes could invalidate.\n    /// When `false`, the emitter will collect rowids into a RowSet/ephemeral table before writing.\n    pub results_materialized: bool,\n}\n\n/// Cost estimate returned by custom index methods for optimizer integration.\n/// This enables the optimizer to make cost-based decisions when choosing between\n/// custom index methods and traditional BTree indexes.\n#[derive(Debug, Clone, Copy)]\npub struct IndexMethodCostEstimate {\n    /// Estimated CPU/IO cost (lower is better, comparable to optimizer Cost values)\n    pub estimated_cost: f64,\n    /// Estimated number of rows returned by the query\n    pub estimated_rows: u64,\n}\n\n/// cursor opened for index method and capable of executing DML/DDL/DQL queries for the index method over fixed table\npub trait IndexMethodCursor {\n    /// create necessary components for index method (usually, this is a bunch of btree-s)\n    fn create(&mut self, connection: &Arc<Connection>) -> Result<IOResult<()>>;\n    /// destroy components created in the create(...) call for index method\n    fn destroy(&mut self, connection: &Arc<Connection>) -> Result<IOResult<()>>;\n\n    /// open necessary components for reading the index\n    fn open_read(&mut self, connection: &Arc<Connection>) -> Result<IOResult<()>>;\n    /// open necessary components for writing the index\n    fn open_write(&mut self, connection: &Arc<Connection>) -> Result<IOResult<()>>;\n\n    /// handle insert action\n    /// \"values\" argument contains registers with values for index columns followed by rowid Integer register\n    /// (e.g. for \"CREATE INDEX i ON t USING method (x, z)\" insert(...) call will have 3 registers in values: [x, z, rowid])\n    fn insert(&mut self, values: &[Register]) -> Result<IOResult<()>>;\n    /// handle delete action\n    /// \"values\" argument contains registers with values for index columns followed by rowid Integer register\n    /// (e.g. for \"CREATE INDEX i ON t USING method (x, z)\" insert(...) call will have 3 registers in values: [x, z, rowid])\n    fn delete(&mut self, values: &[Register]) -> Result<IOResult<()>>;\n\n    /// initialize query to the index method\n    /// first element of \"values\" slice is the Integer register which holds index of the chosen [IndexMethodDefinition::patterns] by query planner\n    /// next arguments of the \"values\" slice are values from the original query expression captured by pattern\n    ///\n    /// For example, for 2 patterns [\"SELECT * FROM {table} LIMIT ?\", \"SELECT * FROM {table} WHERE x = ?\"], query_start(...) call can have following arguments:\n    /// - [Integer(0), Integer(10)] - pattern \"SELECT * FROM {table} LIMIT ?\" was chosen with LIMIT parameter equals to 10\n    /// - [Integer(1), Text(\"turso\")] - pattern \"SELECT * FROM {table} WHERE x = ?\" was chosen with equality comparison equals to \"turso\"\n    ///\n    /// Returns false if query will produce no rows (similar to VFilter/Rewind op codes)\n    fn query_start(&mut self, values: &[Register]) -> Result<IOResult<bool>>;\n\n    /// Moves cursor to the next response row\n    /// Returns false if query exhausted all rows\n    fn query_next(&mut self) -> Result<IOResult<bool>>;\n\n    /// Return column with given idx (zero-based) from current row\n    fn query_column(&mut self, idx: usize) -> Result<IOResult<Value>>;\n\n    /// Return rowid of the original table row which corresponds to the current cursor row\n    ///\n    /// This method is used by tursodb core in order to \"enrich\" response from query pattern with additional fields from original table\n    /// For example, consider pattern like this:\n    ///\n    /// > SELECT vector_distance_jaccard(embedding, ?) as d FROM table ORDER BY d LIMIT 10\n    ///\n    /// It can be used in more complex query:\n    ///\n    /// > SELECT name, comment, rating, vector_distance_jaccard(embedding, ?) as d FROM table ORDER BY d LIMIT 10\n    ///\n    /// In this case query planner will execute index method query first, and then\n    /// enrich its result with name, comment, rating columns from original table accessing original row by its rowid\n    /// returned from query_rowid(...) method\n    fn query_rowid(&mut self) -> Result<IOResult<Option<i64>>>;\n\n    /// Called before transaction commit to flush any pending writes.\n    /// This ensures index method writes are persisted as part of the transaction.\n    fn pre_commit(&mut self) -> Result<IOResult<()>> {\n        Ok(IOResult::Done(()))\n    }\n\n    /// Optimize the index by merging segments or performing other maintenance.\n    fn optimize(&mut self, _connection: &Arc<Connection>) -> Result<IOResult<()>> {\n        Ok(IOResult::Done(()))\n    }\n\n    /// Estimate the cost of executing a query with the given pattern.\n    ///\n    /// This method enables the optimizer to make cost-based decisions when choosing\n    /// between custom index methods and traditional BTree indexes.\n    fn estimate_cost(\n        &self,\n        pattern_idx: usize,\n        base_table_rows: f64,\n    ) -> Option<IndexMethodCostEstimate> {\n        let _ = (pattern_idx, base_table_rows);\n        None\n    }\n}\n\n/// helper method to open table BTree cursor in the index method implementation\npub(crate) fn open_table_cursor(connection: &Connection, table: &str) -> Result<BTreeCursor> {\n    let pager = connection.pager.load().clone();\n    let schema = connection.schema.read();\n    let Some(table) = schema.get_table(table) else {\n        return Err(LimboError::InternalError(format!(\n            \"table {table} not found\",\n        )));\n    };\n    let cursor = BTreeCursor::new_table(pager, table.get_root_page()?, table.columns().len());\n    Ok(cursor)\n}\n\n/// helper method to open index BTree cursor in the index method implementation\npub(crate) fn open_index_cursor(\n    connection: &Connection,\n    table: &str,\n    index: &str,\n    keys: Vec<KeyInfo>,\n) -> Result<BTreeCursor> {\n    let pager = connection.pager.load().clone();\n    let schema = connection.schema.read();\n    let Some(scratch) = schema.get_index(table, index) else {\n        return Err(LimboError::InternalError(format!(\n            \"index {index} for table {table} not found\",\n        )));\n    };\n    let mut cursor = BTreeCursor::new(pager, scratch.root_page, keys.len());\n    cursor.index_info = Some(Arc::new(IndexInfo {\n        has_rowid: false,\n        num_cols: keys.len(),\n        key_info: keys,\n        is_unique: scratch.unique,\n    }));\n    Ok(cursor)\n}\n\n/// helper method to parse select patterns for [IndexMethodAttachment::definition] call\npub(crate) fn parse_patterns(patterns: &[&str]) -> Result<Vec<ast::Select>> {\n    let mut parsed = Vec::new();\n    for pattern in patterns {\n        let mut parser = turso_parser::parser::Parser::new(pattern.as_bytes());\n        let Some(ast) = parser.next() else {\n            return Err(LimboError::ParseError(format!(\n                \"unable to parse pattern statement: {pattern}\",\n            )));\n        };\n        let ast = ast?;\n        let ast::Cmd::Stmt(ast::Stmt::Select(select)) = ast else {\n            return Err(LimboError::ParseError(format!(\n                \"only select patterns are allowed: {pattern}\",\n            )));\n        };\n        parsed.push(select);\n    }\n    Ok(parsed)\n}\n"
  },
  {
    "path": "core/index_method/toy_vector_sparse_ivf.rs",
    "content": "use std::{\n    collections::{BTreeSet, HashSet, VecDeque},\n    sync::atomic::Ordering,\n};\n\nuse turso_parser::ast::{self, SortOrder};\n\nuse crate::numeric::Numeric;\nuse crate::{\n    index_method::{\n        open_index_cursor, open_table_cursor, parse_patterns, IndexMethod, IndexMethodAttachment,\n        IndexMethodConfiguration, IndexMethodCursor, IndexMethodDefinition,\n        BACKING_BTREE_INDEX_METHOD_NAME, TOY_VECTOR_SPARSE_IVF_INDEX_METHOD_NAME,\n    },\n    return_if_io,\n    storage::btree::{BTreeCursor, BTreeKey, CursorTrait},\n    sync::Arc,\n    translate::collate::CollationSeq,\n    types::{IOResult, ImmutableRecord, KeyInfo, SeekKey, SeekOp, SeekResult},\n    vdbe::Register,\n    vector::{\n        operations,\n        vector_types::{Vector, VectorType},\n    },\n    Connection, LimboError, Result, Value, ValueRef,\n};\n\n/// Simple inverted index for sparse vectors\n/// > CREATE INDEX t_idx ON t USING toy_vector_sparse_ivf (embedding)\n///\n/// It accept single column which must contain vector encoded in sparse format (e.g. vector32_sparse(...))\n/// It can handle jaccard similarity scoring queries like the following:\n/// > SELECT vector_distance_jaccard(embedding, ?) as d FROM t ORDER BY d LIMIT ?\n#[derive(Debug)]\npub struct VectorSparseInvertedIndexMethod;\n\n#[derive(Debug)]\npub struct VectorSparseInvertedIndexMethodAttachment {\n    configuration: IndexMethodConfiguration,\n    patterns: Vec<ast::Select>,\n}\n\n#[derive(Debug)]\npub enum VectorSparseInvertedIndexInsertState {\n    Init,\n    Prepare {\n        vector: Option<Vector<'static>>,\n        sum: f64,\n        rowid: i64,\n        idx: usize,\n    },\n    SeekInverted {\n        vector: Option<Vector<'static>>,\n        sum: f64,\n        key: Option<ImmutableRecord>,\n        rowid: i64,\n        idx: usize,\n    },\n    InsertInverted {\n        vector: Option<Vector<'static>>,\n        sum: f64,\n        key: Option<ImmutableRecord>,\n        rowid: i64,\n        idx: usize,\n    },\n    SeekStats {\n        vector: Option<Vector<'static>>,\n        sum: f64,\n        key: Option<ImmutableRecord>,\n        rowid: i64,\n        idx: usize,\n    },\n    ReadStats {\n        vector: Option<Vector<'static>>,\n        sum: f64,\n        rowid: i64,\n        idx: usize,\n    },\n    UpdateStats {\n        vector: Option<Vector<'static>>,\n        sum: f64,\n        key: Option<ImmutableRecord>,\n        rowid: i64,\n        idx: usize,\n    },\n}\n\n#[derive(Debug)]\npub enum VectorSparseInvertedIndexDeleteState {\n    Init,\n    Prepare {\n        vector: Option<Vector<'static>>,\n        sum: f64,\n        rowid: i64,\n        idx: usize,\n    },\n    SeekInverted {\n        vector: Option<Vector<'static>>,\n        sum: f64,\n        key: Option<ImmutableRecord>,\n        rowid: i64,\n        idx: usize,\n    },\n    NextInverted {\n        vector: Option<Vector<'static>>,\n        sum: f64,\n        rowid: i64,\n        idx: usize,\n    },\n    DeleteInverted {\n        vector: Option<Vector<'static>>,\n        sum: f64,\n        rowid: i64,\n        idx: usize,\n    },\n    SeekStats {\n        vector: Option<Vector<'static>>,\n        sum: f64,\n        key: Option<ImmutableRecord>,\n        rowid: i64,\n        idx: usize,\n    },\n    ReadStats {\n        vector: Option<Vector<'static>>,\n        sum: f64,\n        rowid: i64,\n        idx: usize,\n    },\n    UpdateStats {\n        vector: Option<Vector<'static>>,\n        sum: f64,\n        key: Option<ImmutableRecord>,\n        rowid: i64,\n        idx: usize,\n    },\n}\n\n#[derive(Debug, PartialEq)]\nstruct FloatOrd(f64);\n\nimpl Eq for FloatOrd {}\nimpl PartialOrd for FloatOrd {\n    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {\n        Some(self.cmp(other))\n    }\n}\nimpl Ord for FloatOrd {\n    fn cmp(&self, other: &Self) -> std::cmp::Ordering {\n        self.0.total_cmp(&other.0)\n    }\n}\n\n#[derive(Debug)]\nstruct ComponentStat {\n    position: u32,\n    cnt: i64,\n    min: f64,\n    max: f64,\n}\n\nfn parse_stat_row(record: Option<&ImmutableRecord>) -> Result<ComponentStat> {\n    let Some(record) = record else {\n        return Err(LimboError::Corrupt(\n            \"stats index corrupted: expected row\".to_string(),\n        ));\n    };\n    let ValueRef::Numeric(Numeric::Integer(position)) = record.get_value(0)? else {\n        return Err(LimboError::Corrupt(\n            \"stats index corrupted: expected integer\".to_string(),\n        ));\n    };\n    let ValueRef::Numeric(Numeric::Integer(cnt)) = record.get_value(1)? else {\n        return Err(LimboError::Corrupt(\n            \"stats index corrupted: expected integer\".to_string(),\n        ));\n    };\n    let ValueRef::Numeric(Numeric::Float(min)) = record.get_value(2)? else {\n        return Err(LimboError::Corrupt(\n            \"stats index corrupted: expected float\".to_string(),\n        ));\n    };\n    let ValueRef::Numeric(Numeric::Float(max)) = record.get_value(3)? else {\n        return Err(LimboError::Corrupt(\n            \"stats index corrupted: expected float\".to_string(),\n        ));\n    };\n    Ok(ComponentStat {\n        position: position as u32,\n        cnt,\n        min: f64::from(min),\n        max: f64::from(max),\n    })\n}\n#[derive(Debug)]\nstruct ComponentRow {\n    position: u32,\n    sum: f64,\n    rowid: i64,\n}\n\nfn parse_inverted_index_row(record: Option<&ImmutableRecord>) -> Result<ComponentRow> {\n    let Some(record) = record else {\n        return Err(LimboError::Corrupt(\n            \"inverted index corrupted: expected row\".to_string(),\n        ));\n    };\n    let ValueRef::Numeric(Numeric::Integer(position)) = record.get_value(0)? else {\n        return Err(LimboError::Corrupt(\n            \"inverted index corrupted: expected integer\".to_string(),\n        ));\n    };\n    let ValueRef::Numeric(Numeric::Float(sum)) = record.get_value(1)? else {\n        return Err(LimboError::Corrupt(\n            \"inverted index corrupted: expected float\".to_string(),\n        ));\n    };\n    let ValueRef::Numeric(Numeric::Integer(rowid)) = record.get_value(2)? else {\n        return Err(LimboError::Corrupt(\n            \"inverted index corrupted: expected integer\".to_string(),\n        ));\n    };\n    Ok(ComponentRow {\n        position: position as u32,\n        sum: f64::from(sum),\n        rowid,\n    })\n}\n\n#[derive(Debug)]\nenum VectorSparseInvertedIndexSearchState {\n    Init,\n    CollectComponentsSeek {\n        sum: f64,\n        vector: Option<Vector<'static>>,\n        idx: usize,\n        components: Option<Vec<(ComponentStat, f32)>>,\n        limit: i64,\n        key: Option<ImmutableRecord>,\n    },\n    CollectComponentsRead {\n        sum: f64,\n        vector: Option<Vector<'static>>,\n        idx: usize,\n        components: Option<Vec<(ComponentStat, f32)>>,\n        limit: i64,\n    },\n    Seek {\n        sum: f64,\n        components: Option<VecDeque<ComponentStat>>,\n        collected: Option<HashSet<i64>>,\n        distances: Option<BTreeSet<(FloatOrd, i64)>>,\n        limit: i64,\n        key: Option<ImmutableRecord>,\n        sum_threshold: Option<f64>,\n        component: Option<u32>,\n    },\n    Read {\n        sum: f64,\n        components: Option<VecDeque<ComponentStat>>,\n        collected: Option<HashSet<i64>>,\n        distances: Option<BTreeSet<(FloatOrd, i64)>>,\n        limit: i64,\n        sum_threshold: Option<f64>,\n        component: u32,\n        current: Option<Vec<i64>>,\n    },\n    Next {\n        sum: f64,\n        components: Option<VecDeque<ComponentStat>>,\n        collected: Option<HashSet<i64>>,\n        distances: Option<BTreeSet<(FloatOrd, i64)>>,\n        limit: i64,\n        sum_threshold: Option<f64>,\n        component: u32,\n        current: Option<Vec<i64>>,\n    },\n    EvaluateSeek {\n        sum: f64,\n        components: Option<VecDeque<ComponentStat>>,\n        collected: Option<HashSet<i64>>,\n        distances: Option<BTreeSet<(FloatOrd, i64)>>,\n        limit: i64,\n        current: Option<VecDeque<i64>>,\n        rowid: Option<i64>,\n    },\n    EvaluateRead {\n        sum: f64,\n        components: Option<VecDeque<ComponentStat>>,\n        collected: Option<HashSet<i64>>,\n        distances: Option<BTreeSet<(FloatOrd, i64)>>,\n        limit: i64,\n        current: Option<VecDeque<i64>>,\n        rowid: i64,\n    },\n}\n\n#[derive(Debug, PartialEq)]\npub enum ScanOrder {\n    DatasetFrequencyAsc,\n    QueryWeightDesc,\n}\n\npub struct VectorSparseInvertedIndexMethodCursor {\n    configuration: IndexMethodConfiguration,\n    delta: f64,\n    scan_portion: f64,\n    scan_order: ScanOrder,\n    inverted_index_btree: String,\n    inverted_index_cursor: Option<BTreeCursor>,\n    stats_btree: String,\n    stats_cursor: Option<BTreeCursor>,\n    main_btree: Option<BTreeCursor>,\n    insert_state: VectorSparseInvertedIndexInsertState,\n    delete_state: VectorSparseInvertedIndexDeleteState,\n    search_state: VectorSparseInvertedIndexSearchState,\n    search_result: VecDeque<(i64, f64)>,\n}\n\nimpl IndexMethod for VectorSparseInvertedIndexMethod {\n    fn attach(\n        &self,\n        configuration: &IndexMethodConfiguration,\n    ) -> Result<Arc<dyn IndexMethodAttachment>> {\n        let query_pattern1 = format!(\n            \"SELECT vector_distance_jaccard({}, ?) as distance FROM {} ORDER BY distance LIMIT ?\",\n            configuration.columns[0].name, configuration.table_name\n        );\n        let query_pattern2 = format!(\n            \"SELECT vector_distance_jaccard(?, {}) as distance FROM {} ORDER BY distance LIMIT ?\",\n            configuration.columns[0].name, configuration.table_name\n        );\n        Ok(Arc::new(VectorSparseInvertedIndexMethodAttachment {\n            configuration: configuration.clone(),\n            patterns: parse_patterns(&[&query_pattern1, &query_pattern2])?,\n        }))\n    }\n}\n\nimpl IndexMethodAttachment for VectorSparseInvertedIndexMethodAttachment {\n    fn definition<'a>(&'a self) -> IndexMethodDefinition<'a> {\n        IndexMethodDefinition {\n            method_name: TOY_VECTOR_SPARSE_IVF_INDEX_METHOD_NAME,\n            index_name: &self.configuration.index_name,\n            patterns: self.patterns.as_slice(),\n            backing_btree: false,\n            results_materialized: true,\n        }\n    }\n    fn init(&self) -> Result<Box<dyn IndexMethodCursor>> {\n        Ok(Box::new(VectorSparseInvertedIndexMethodCursor::new(\n            self.configuration.clone(),\n        )))\n    }\n}\n\nimpl VectorSparseInvertedIndexMethodCursor {\n    pub fn new(configuration: IndexMethodConfiguration) -> Self {\n        let inverted_index_btree = format!(\"{}_inverted_index\", configuration.index_name);\n        let stats_btree = format!(\"{}_stats\", configuration.index_name);\n        let delta = match configuration.parameters.get(\"delta\") {\n            Some(&Value::Numeric(Numeric::Float(delta))) => f64::from(delta),\n            _ => 0.0,\n        };\n        let scan_portion = match configuration.parameters.get(\"scan_portion\") {\n            Some(&Value::Numeric(Numeric::Float(scan_portion))) => f64::from(scan_portion),\n            _ => 1.0,\n        };\n        let scan_order = match configuration.parameters.get(\"scan_order\") {\n            Some(Value::Text(scan_order)) if scan_order.as_str() == \"dataset_frequency_asc\" => {\n                ScanOrder::DatasetFrequencyAsc\n            }\n            Some(Value::Text(scan_order)) if scan_order.as_str() == \"query_weight_desc\" => {\n                ScanOrder::QueryWeightDesc\n            }\n            _ => ScanOrder::QueryWeightDesc,\n        };\n        Self {\n            configuration,\n            delta,\n            scan_portion,\n            scan_order,\n            inverted_index_btree,\n            inverted_index_cursor: None,\n            stats_btree,\n            stats_cursor: None,\n            main_btree: None,\n            search_result: VecDeque::new(),\n            insert_state: VectorSparseInvertedIndexInsertState::Init,\n            delete_state: VectorSparseInvertedIndexDeleteState::Init,\n            search_state: VectorSparseInvertedIndexSearchState::Init,\n        }\n    }\n}\n\nfn key_info() -> KeyInfo {\n    KeyInfo {\n        collation: CollationSeq::Binary,\n        sort_order: SortOrder::Asc,\n    }\n}\n\nimpl IndexMethodCursor for VectorSparseInvertedIndexMethodCursor {\n    fn create(&mut self, connection: &Arc<Connection>) -> Result<IOResult<()>> {\n        // we need to properly track subprograms and propagate result to the root program to make this execution async\n\n        let columns = &self.configuration.columns;\n        let columns = columns.iter().map(|x| x.name.as_str()).collect::<Vec<_>>();\n        let inverted_index_create = format!(\n            \"CREATE INDEX {} ON {} USING {} ({})\",\n            self.inverted_index_btree,\n            self.configuration.table_name,\n            BACKING_BTREE_INDEX_METHOD_NAME,\n            columns.join(\", \")\n        );\n        let stats_index_create = format!(\n            \"CREATE INDEX {} ON {} USING {} ({})\",\n            self.stats_btree,\n            self.configuration.table_name,\n            BACKING_BTREE_INDEX_METHOD_NAME,\n            columns.join(\", \")\n        );\n        for sql in [inverted_index_create, stats_index_create] {\n            let mut stmt = connection.prepare(&sql)?;\n            // by default we set needs_stmt_subtransactions = true to all write transaction\n            // this will lead to Busy error here - because Transaction opcode will be unable to acquire ownership to the subjournal as it already owned by parent statement which is still active\n            //\n            // as we run nested statement - we actually don't need subjournal as it already started before in the parent statement\n            // so, this is hacky way to fix the situation for toy index for now, but we need to implement proper helpers in order to avoid similar errors in other code later\n            stmt.program\n                .prepared\n                .needs_stmt_subtransactions\n                .store(false, Ordering::Relaxed);\n            connection.start_nested();\n            let result = stmt.run_ignore_rows();\n            connection.end_nested();\n            result?;\n        }\n\n        Ok(IOResult::Done(()))\n    }\n\n    fn destroy(&mut self, connection: &Arc<Connection>) -> Result<IOResult<()>> {\n        let inverted_index_drop = format!(\"DROP INDEX {}\", self.inverted_index_btree);\n        let stats_index_drop = format!(\"DROP INDEX {}\", self.stats_btree);\n        for sql in [inverted_index_drop, stats_index_drop] {\n            let mut stmt = connection.prepare(&sql)?;\n            connection.start_nested();\n            let result = stmt.run_ignore_rows();\n            connection.end_nested();\n            result?;\n        }\n\n        Ok(IOResult::Done(()))\n    }\n\n    fn open_read(&mut self, connection: &Arc<Connection>) -> Result<IOResult<()>> {\n        self.inverted_index_cursor = Some(open_index_cursor(\n            connection,\n            &self.configuration.table_name,\n            &self.inverted_index_btree,\n            // component, length, rowid\n            vec![key_info(), key_info(), key_info()],\n        )?);\n        self.stats_cursor = Some(open_index_cursor(\n            connection,\n            &self.configuration.table_name,\n            &self.stats_btree,\n            // component\n            vec![key_info()],\n        )?);\n        self.main_btree = Some(open_table_cursor(\n            connection,\n            &self.configuration.table_name,\n        )?);\n        Ok(IOResult::Done(()))\n    }\n\n    fn open_write(&mut self, connection: &Arc<Connection>) -> Result<IOResult<()>> {\n        self.inverted_index_cursor = Some(open_index_cursor(\n            connection,\n            &self.configuration.table_name,\n            &self.inverted_index_btree,\n            // component, length, rowid\n            vec![key_info(), key_info(), key_info()],\n        )?);\n        self.stats_cursor = Some(open_index_cursor(\n            connection,\n            &self.configuration.table_name,\n            &self.stats_btree,\n            // component\n            vec![key_info()],\n        )?);\n        Ok(IOResult::Done(()))\n    }\n\n    fn insert(&mut self, values: &[Register]) -> Result<IOResult<()>> {\n        let Some(inverted_cursor) = &mut self.inverted_index_cursor else {\n            return Err(LimboError::InternalError(\n                \"inverted cursor must be opened\".to_string(),\n            ));\n        };\n        let Some(stats_cursor) = &mut self.stats_cursor else {\n            return Err(LimboError::InternalError(\n                \"stats cursor must be opened\".to_string(),\n            ));\n        };\n        loop {\n            tracing::debug!(\"insert_state: {:?}\", self.insert_state);\n            match &mut self.insert_state {\n                VectorSparseInvertedIndexInsertState::Init => {\n                    let Some(vector) = values[0].get_value().to_blob() else {\n                        return Err(LimboError::InternalError(\n                            \"first value must be sparse vector\".to_string(),\n                        ));\n                    };\n                    let vector = Vector::from_vec(vector.to_vec())?;\n                    if !matches!(vector.vector_type, VectorType::Float32Sparse) {\n                        return Err(LimboError::InternalError(\n                            \"first value must be sparse vector\".to_string(),\n                        ));\n                    }\n                    let Some(rowid) = values[1].get_value().as_int() else {\n                        return Err(LimboError::InternalError(\n                            \"second value must be i64 rowid\".to_string(),\n                        ));\n                    };\n                    let sum = vector.as_f32_sparse().values.iter().sum::<f32>() as f64;\n                    self.insert_state = VectorSparseInvertedIndexInsertState::Prepare {\n                        vector: Some(vector),\n                        sum,\n                        rowid,\n                        idx: 0,\n                    }\n                }\n                VectorSparseInvertedIndexInsertState::Prepare {\n                    vector,\n                    sum,\n                    rowid,\n                    idx,\n                } => {\n                    let Some(v) = vector.as_ref() else {\n                        return Err(LimboError::InternalError(\n                            \"vector must be present in Prepare state\".to_string(),\n                        ));\n                    };\n                    if *idx == v.as_f32_sparse().idx.len() {\n                        self.insert_state = VectorSparseInvertedIndexInsertState::Init;\n                        return Ok(IOResult::Done(()));\n                    }\n                    let position = v.as_f32_sparse().idx[*idx];\n                    let key = ImmutableRecord::from_values(\n                        &[\n                            Value::from_i64(position as i64),\n                            Value::from_f64(*sum),\n                            Value::from_i64(*rowid),\n                        ],\n                        3,\n                    );\n                    tracing::debug!(\n                        \"insert_state: seek: component={}, sum={}, rowid={}\",\n                        position,\n                        *sum,\n                        *rowid,\n                    );\n                    self.insert_state = VectorSparseInvertedIndexInsertState::SeekInverted {\n                        vector: vector.take(),\n                        sum: *sum,\n                        idx: *idx,\n                        rowid: *rowid,\n                        key: Some(key),\n                    };\n                }\n                VectorSparseInvertedIndexInsertState::SeekInverted {\n                    vector,\n                    sum,\n                    rowid,\n                    idx,\n                    key,\n                } => {\n                    let Some(k) = key.as_ref() else {\n                        return Err(LimboError::InternalError(\n                            \"key must be present in SeekInverted state\".to_string(),\n                        ));\n                    };\n                    let result =\n                        return_if_io!(inverted_cursor\n                            .seek(SeekKey::IndexKey(k), SeekOp::GE { eq_only: true }));\n                    tracing::debug!(\"insert_state: seek: result={:?}\", result);\n                    self.insert_state = VectorSparseInvertedIndexInsertState::InsertInverted {\n                        vector: vector.take(),\n                        sum: *sum,\n                        idx: *idx,\n                        rowid: *rowid,\n                        key: key.take(),\n                    };\n                }\n                VectorSparseInvertedIndexInsertState::InsertInverted {\n                    vector,\n                    sum,\n                    rowid,\n                    idx,\n                    key,\n                } => {\n                    let Some(k) = key.as_ref() else {\n                        return Err(LimboError::InternalError(\n                            \"key must be present in InsertInverted state\".to_string(),\n                        ));\n                    };\n                    return_if_io!(inverted_cursor.insert(&BTreeKey::IndexKey(k)));\n\n                    let Some(v) = vector.as_ref() else {\n                        return Err(LimboError::InternalError(\n                            \"vector must be present in InsertInverted state\".to_string(),\n                        ));\n                    };\n                    let position = v.as_f32_sparse().idx[*idx];\n                    let key = ImmutableRecord::from_values(&[Value::from_i64(position as i64)], 1);\n                    self.insert_state = VectorSparseInvertedIndexInsertState::SeekStats {\n                        vector: vector.take(),\n                        sum: *sum,\n                        idx: *idx,\n                        rowid: *rowid,\n                        key: Some(key),\n                    };\n                }\n                VectorSparseInvertedIndexInsertState::SeekStats {\n                    vector,\n                    sum,\n                    key,\n                    rowid,\n                    idx,\n                } => {\n                    let Some(k) = key.as_ref() else {\n                        return Err(LimboError::InternalError(\n                            \"key must be present in SeekStats state\".to_string(),\n                        ));\n                    };\n                    let result = return_if_io!(\n                        stats_cursor.seek(SeekKey::IndexKey(k), SeekOp::GE { eq_only: true })\n                    );\n                    match result {\n                        SeekResult::Found => {\n                            self.insert_state = VectorSparseInvertedIndexInsertState::ReadStats {\n                                vector: vector.take(),\n                                sum: *sum,\n                                idx: *idx,\n                                rowid: *rowid,\n                            };\n                        }\n                        SeekResult::NotFound | SeekResult::TryAdvance => {\n                            let Some(v) = vector.as_ref() else {\n                                return Err(LimboError::InternalError(\n                                    \"vector must be present in SeekStats state\".to_string(),\n                                ));\n                            };\n                            let position = v.as_f32_sparse().idx[*idx];\n                            let value = v.as_f32_sparse().values[*idx] as f64;\n                            tracing::debug!(\n                                \"update stats(insert): {} (cnt={}, min={}, max={})\",\n                                position,\n                                1,\n                                value,\n                                value,\n                            );\n                            let key = ImmutableRecord::from_values(\n                                &[\n                                    Value::from_i64(position as i64),\n                                    Value::from_i64(1),\n                                    Value::from_f64(value),\n                                    Value::from_f64(value),\n                                ],\n                                4,\n                            );\n                            self.insert_state = VectorSparseInvertedIndexInsertState::UpdateStats {\n                                vector: vector.take(),\n                                sum: *sum,\n                                idx: *idx,\n                                rowid: *rowid,\n                                key: Some(key),\n                            };\n                        }\n                    }\n                }\n                VectorSparseInvertedIndexInsertState::ReadStats {\n                    vector,\n                    sum,\n                    rowid,\n                    idx,\n                } => {\n                    let record = return_if_io!(stats_cursor.record());\n                    let component = parse_stat_row(record)?;\n                    let Some(v) = vector.as_ref() else {\n                        return Err(LimboError::InternalError(\n                            \"vector must be present in ReadStats state\".to_string(),\n                        ));\n                    };\n                    let position = v.as_f32_sparse().idx[*idx];\n                    let value = v.as_f32_sparse().values[*idx] as f64;\n                    tracing::debug!(\n                        \"update stats(insert): {} (cnt={}, min={}, max={})\",\n                        position,\n                        component.cnt + 1,\n                        value.min(component.min),\n                        value.max(component.max),\n                    );\n                    let key = ImmutableRecord::from_values(\n                        &[\n                            Value::from_i64(position as i64),\n                            Value::from_i64(component.cnt + 1),\n                            Value::from_f64(value.min(component.min)),\n                            Value::from_f64(value.max(component.max)),\n                        ],\n                        4,\n                    );\n                    self.insert_state = VectorSparseInvertedIndexInsertState::UpdateStats {\n                        vector: vector.take(),\n                        sum: *sum,\n                        idx: *idx,\n                        rowid: *rowid,\n                        key: Some(key),\n                    };\n                }\n                VectorSparseInvertedIndexInsertState::UpdateStats {\n                    vector,\n                    sum,\n                    key,\n                    rowid,\n                    idx,\n                } => {\n                    let Some(k) = key.as_ref() else {\n                        return Err(LimboError::InternalError(\n                            \"key must be present in UpdateStats state\".to_string(),\n                        ));\n                    };\n                    return_if_io!(stats_cursor.insert(&BTreeKey::IndexKey(k)));\n\n                    self.insert_state = VectorSparseInvertedIndexInsertState::Prepare {\n                        vector: vector.take(),\n                        sum: *sum,\n                        idx: *idx + 1,\n                        rowid: *rowid,\n                    };\n                }\n            }\n        }\n    }\n\n    fn delete(&mut self, values: &[Register]) -> Result<IOResult<()>> {\n        let Some(cursor) = &mut self.inverted_index_cursor else {\n            return Err(LimboError::InternalError(\n                \"cursor must be opened\".to_string(),\n            ));\n        };\n        let Some(stats_cursor) = &mut self.stats_cursor else {\n            return Err(LimboError::InternalError(\n                \"stats cursor must be opened\".to_string(),\n            ));\n        };\n        loop {\n            tracing::debug!(\"delete_state: {:?}\", self.delete_state);\n            match &mut self.delete_state {\n                VectorSparseInvertedIndexDeleteState::Init => {\n                    let Some(vector) = values[0].get_value().to_blob() else {\n                        return Err(LimboError::InternalError(\n                            \"first value must be sparse vector\".to_string(),\n                        ));\n                    };\n                    let vector = Vector::from_vec(vector.to_vec())?;\n                    if !matches!(vector.vector_type, VectorType::Float32Sparse) {\n                        return Err(LimboError::InternalError(\n                            \"first value must be sparse vector\".to_string(),\n                        ));\n                    }\n                    let Some(rowid) = values[1].get_value().as_int() else {\n                        return Err(LimboError::InternalError(\n                            \"second value must be i64 rowid\".to_string(),\n                        ));\n                    };\n                    let sum = vector.as_f32_sparse().values.iter().sum::<f32>() as f64;\n                    self.delete_state = VectorSparseInvertedIndexDeleteState::Prepare {\n                        vector: Some(vector),\n                        sum,\n                        rowid,\n                        idx: 0,\n                    }\n                }\n                VectorSparseInvertedIndexDeleteState::Prepare {\n                    vector,\n                    sum,\n                    rowid,\n                    idx,\n                } => {\n                    let Some(v) = vector.as_ref() else {\n                        return Err(LimboError::InternalError(\n                            \"vector must be present in Prepare state\".to_string(),\n                        ));\n                    };\n                    if *idx == v.as_f32_sparse().idx.len() {\n                        self.delete_state = VectorSparseInvertedIndexDeleteState::Init;\n                        return Ok(IOResult::Done(()));\n                    }\n                    let position = v.as_f32_sparse().idx[*idx];\n                    let key = ImmutableRecord::from_values(\n                        &[\n                            Value::from_i64(position as i64),\n                            Value::from_f64(*sum),\n                            Value::from_i64(*rowid),\n                        ],\n                        3,\n                    );\n                    self.delete_state = VectorSparseInvertedIndexDeleteState::SeekInverted {\n                        vector: vector.take(),\n                        idx: *idx,\n                        sum: *sum,\n                        rowid: *rowid,\n                        key: Some(key),\n                    };\n                }\n                VectorSparseInvertedIndexDeleteState::SeekInverted {\n                    vector,\n                    sum,\n                    rowid,\n                    idx,\n                    key,\n                } => {\n                    let component_idx = vector\n                        .as_ref()\n                        .and_then(|v| v.as_f32_sparse().idx.get(*idx).copied())\n                        .ok_or_else(|| {\n                            LimboError::InternalError(\n                                \"vector must be present in SeekInverted state\".to_string(),\n                            )\n                        })?;\n                    tracing::debug!(\n                        \"delete_state: seek: component={}, sum={}, rowid={}\",\n                        component_idx,\n                        *sum,\n                        *rowid,\n                    );\n                    let Some(k) = key.as_ref() else {\n                        return Err(LimboError::InternalError(\n                            \"key must be present in SeekInverted state\".to_string(),\n                        ));\n                    };\n                    let result = return_if_io!(\n                        cursor.seek(SeekKey::IndexKey(k), SeekOp::GE { eq_only: true })\n                    );\n                    match result {\n                        SeekResult::Found => {\n                            self.delete_state =\n                                VectorSparseInvertedIndexDeleteState::DeleteInverted {\n                                    vector: vector.take(),\n                                    sum: *sum,\n                                    idx: *idx,\n                                    rowid: *rowid,\n                                };\n                        }\n                        SeekResult::TryAdvance => {\n                            self.delete_state =\n                                VectorSparseInvertedIndexDeleteState::NextInverted {\n                                    vector: vector.take(),\n                                    sum: *sum,\n                                    idx: *idx,\n                                    rowid: *rowid,\n                                };\n                        }\n                        SeekResult::NotFound => {\n                            return Err(LimboError::Corrupt(\"inverted index corrupted\".to_string()))\n                        }\n                    }\n                }\n                VectorSparseInvertedIndexDeleteState::NextInverted {\n                    vector,\n                    sum,\n                    rowid,\n                    idx,\n                } => {\n                    return_if_io!(cursor.next());\n                    if !cursor.has_record() {\n                        return Err(LimboError::Corrupt(\"inverted index corrupted\".to_string()));\n                    }\n                    self.delete_state = VectorSparseInvertedIndexDeleteState::DeleteInverted {\n                        vector: vector.take(),\n                        sum: *sum,\n                        idx: *idx,\n                        rowid: *rowid,\n                    };\n                }\n                VectorSparseInvertedIndexDeleteState::DeleteInverted {\n                    vector,\n                    sum,\n                    rowid,\n                    idx,\n                } => {\n                    return_if_io!(cursor.delete());\n                    let Some(v) = vector.as_ref() else {\n                        return Err(LimboError::InternalError(\n                            \"vector must be present in DeleteInverted state\".to_string(),\n                        ));\n                    };\n                    let position = v.as_f32_sparse().idx[*idx];\n                    let key = ImmutableRecord::from_values(&[Value::from_i64(position as i64)], 1);\n                    self.delete_state = VectorSparseInvertedIndexDeleteState::SeekStats {\n                        vector: vector.take(),\n                        sum: *sum,\n                        idx: *idx,\n                        rowid: *rowid,\n                        key: Some(key),\n                    };\n                }\n                VectorSparseInvertedIndexDeleteState::SeekStats {\n                    vector,\n                    sum,\n                    key,\n                    rowid,\n                    idx,\n                } => {\n                    let Some(k) = key.as_ref() else {\n                        return Err(LimboError::InternalError(\n                            \"key must be present in SeekStats state\".to_string(),\n                        ));\n                    };\n                    let result = return_if_io!(\n                        stats_cursor.seek(SeekKey::IndexKey(k), SeekOp::GE { eq_only: true })\n                    );\n                    match result {\n                        SeekResult::Found => {\n                            self.delete_state = VectorSparseInvertedIndexDeleteState::ReadStats {\n                                vector: vector.take(),\n                                sum: *sum,\n                                idx: *idx,\n                                rowid: *rowid,\n                            };\n                        }\n                        SeekResult::NotFound | SeekResult::TryAdvance => {\n                            return Err(LimboError::Corrupt(\n                                \"stats index corrupted: can't find component row\".to_string(),\n                            ))\n                        }\n                    }\n                }\n                VectorSparseInvertedIndexDeleteState::ReadStats {\n                    vector,\n                    sum,\n                    rowid,\n                    idx,\n                } => {\n                    let record = return_if_io!(stats_cursor.record());\n                    let component = parse_stat_row(record)?;\n                    let Some(v) = vector.as_ref() else {\n                        return Err(LimboError::InternalError(\n                            \"vector must be present in ReadStats state\".to_string(),\n                        ));\n                    };\n                    let position = v.as_f32_sparse().idx[*idx];\n                    tracing::debug!(\n                        \"update stats(delete): {} (cnt={}, min={}, max={})\",\n                        position,\n                        component.cnt - 1,\n                        component.min,\n                        component.max,\n                    );\n                    let key = ImmutableRecord::from_values(\n                        &[\n                            Value::from_i64(position as i64),\n                            Value::from_i64(component.cnt - 1),\n                            Value::from_f64(component.min),\n                            Value::from_f64(component.max),\n                        ],\n                        4,\n                    );\n                    self.delete_state = VectorSparseInvertedIndexDeleteState::UpdateStats {\n                        vector: vector.take(),\n                        sum: *sum,\n                        idx: *idx,\n                        rowid: *rowid,\n                        key: Some(key),\n                    };\n                }\n                VectorSparseInvertedIndexDeleteState::UpdateStats {\n                    vector,\n                    sum,\n                    key,\n                    rowid,\n                    idx,\n                } => {\n                    let Some(k) = key.as_ref() else {\n                        return Err(LimboError::InternalError(\n                            \"key must be present in UpdateStats state\".to_string(),\n                        ));\n                    };\n                    return_if_io!(stats_cursor.insert(&BTreeKey::IndexKey(k)));\n\n                    self.delete_state = VectorSparseInvertedIndexDeleteState::Prepare {\n                        vector: vector.take(),\n                        sum: *sum,\n                        idx: *idx + 1,\n                        rowid: *rowid,\n                    };\n                }\n            }\n        }\n    }\n\n    fn query_start(&mut self, values: &[Register]) -> Result<IOResult<bool>> {\n        let Some(inverted) = &mut self.inverted_index_cursor else {\n            return Err(LimboError::InternalError(\n                \"cursor must be opened\".to_string(),\n            ));\n        };\n        let Some(stats) = &mut self.stats_cursor else {\n            return Err(LimboError::InternalError(\n                \"cursor must be opened\".to_string(),\n            ));\n        };\n        let Some(main) = &mut self.main_btree else {\n            return Err(LimboError::InternalError(\n                \"cursor must be opened\".to_string(),\n            ));\n        };\n        loop {\n            tracing::debug!(\"query_state: {:?}\", self.search_state);\n            match &mut self.search_state {\n                VectorSparseInvertedIndexSearchState::Init => {\n                    let Some(vector) = values[1].get_value().to_blob() else {\n                        return Err(LimboError::InternalError(\n                            \"first value must be sparse vector\".to_string(),\n                        ));\n                    };\n                    let Some(limit) = values[2].get_value().as_int() else {\n                        return Err(LimboError::InternalError(\n                            \"second value must be i64 limit parameter\".to_string(),\n                        ));\n                    };\n                    let vector = Vector::from_vec(vector.to_vec())?;\n                    if !matches!(vector.vector_type, VectorType::Float32Sparse) {\n                        return Err(LimboError::InternalError(\n                            \"first value must be sparse vector\".to_string(),\n                        ));\n                    }\n                    let sparse = vector.as_f32_sparse();\n                    let sum = sparse.values.iter().sum::<f32>() as f64;\n                    self.search_state =\n                        VectorSparseInvertedIndexSearchState::CollectComponentsSeek {\n                            sum,\n                            vector: Some(vector),\n                            idx: 0,\n                            components: Some(Vec::new()),\n                            key: None,\n                            limit,\n                        };\n                }\n                VectorSparseInvertedIndexSearchState::CollectComponentsSeek {\n                    sum,\n                    vector,\n                    idx,\n                    components,\n                    limit,\n                    key,\n                } => {\n                    let Some(v) = vector.as_ref() else {\n                        return Err(LimboError::InternalError(\n                            \"vector must be present in CollectComponentsSeek state\".to_string(),\n                        ));\n                    };\n                    let p = &v.as_f32_sparse().idx[*idx..];\n                    if p.is_empty() && key.is_none() {\n                        let Some(mut components) = components.take() else {\n                            return Err(LimboError::InternalError(\n                                \"components must be present in CollectComponentsSeek state\"\n                                    .to_string(),\n                            ));\n                        };\n                        match self.scan_order {\n                            ScanOrder::DatasetFrequencyAsc => {\n                                // order by cnt ASC in order to check low-cardinality components first\n                                components.sort_by_key(|(c, _)| c.cnt);\n                            }\n                            ScanOrder::QueryWeightDesc => {\n                                // order by weight DESC in order to check high-impact components first\n                                components\n                                    .sort_by_key(|(_, w)| std::cmp::Reverse(FloatOrd(*w as f64)));\n                            }\n                        }\n                        let take = (components.len() as f64 * self.scan_portion).ceil() as usize;\n                        let components = components\n                            .into_iter()\n                            .take(take)\n                            .map(|(c, _)| c)\n                            .collect::<Vec<_>>();\n\n                        tracing::debug!(\n                            \"query_start: components: {:?}, delta: {}, scan_portion: {}, scan_order: {:?}\",\n                            components,\n                            self.delta,\n                            self.scan_portion,\n                            self.scan_order,\n                        );\n                        self.search_state = VectorSparseInvertedIndexSearchState::Seek {\n                            sum: *sum,\n                            components: Some(components.into()),\n                            collected: Some(HashSet::default()),\n                            distances: Some(BTreeSet::new()),\n                            limit: *limit,\n                            key: None,\n                            component: None,\n                            sum_threshold: None,\n                        };\n                        continue;\n                    }\n                    if key.is_none() {\n                        let Some(v) = vector.as_ref() else {\n                            return Err(LimboError::InternalError(\n                                \"vector must be present in CollectComponentsSeek state\".to_string(),\n                            ));\n                        };\n                        let position = v.as_f32_sparse().idx[*idx];\n                        *key = Some(ImmutableRecord::from_values(\n                            &[Value::from_i64(position as i64)],\n                            1,\n                        ));\n                    }\n                    let Some(k) = key.as_ref() else {\n                        return Err(LimboError::InternalError(\n                            \"key must be present in CollectComponentsSeek state\".to_string(),\n                        ));\n                    };\n                    let result = return_if_io!(\n                        stats.seek(SeekKey::IndexKey(k), SeekOp::GE { eq_only: true })\n                    );\n                    match result {\n                        SeekResult::Found => {\n                            self.search_state =\n                                VectorSparseInvertedIndexSearchState::CollectComponentsRead {\n                                    sum: *sum,\n                                    vector: vector.take(),\n                                    idx: *idx,\n                                    components: components.take(),\n                                    limit: *limit,\n                                };\n                        }\n                        SeekResult::NotFound | SeekResult::TryAdvance => {\n                            self.search_state =\n                                VectorSparseInvertedIndexSearchState::CollectComponentsSeek {\n                                    sum: *sum,\n                                    components: components.take(),\n                                    vector: vector.take(),\n                                    idx: *idx + 1,\n                                    limit: *limit,\n                                    key: None,\n                                };\n                        }\n                    }\n                }\n                VectorSparseInvertedIndexSearchState::CollectComponentsRead {\n                    sum,\n                    vector,\n                    idx,\n                    components,\n                    limit,\n                } => {\n                    let record = return_if_io!(stats.record());\n                    let Some(v) = vector.as_ref() else {\n                        return Err(LimboError::InternalError(\n                            \"vector must be present in CollectComponentsRead state\".to_string(),\n                        ));\n                    };\n                    let value = v.as_f32_sparse().values[*idx];\n                    let component = parse_stat_row(record)?;\n                    let Some(comps) = components.as_mut() else {\n                        return Err(LimboError::InternalError(\n                            \"components must be present in CollectComponentsRead state\".to_string(),\n                        ));\n                    };\n                    comps.push((component, value));\n                    self.search_state =\n                        VectorSparseInvertedIndexSearchState::CollectComponentsSeek {\n                            sum: *sum,\n                            components: components.take(),\n                            vector: vector.take(),\n                            idx: *idx + 1,\n                            limit: *limit,\n                            key: None,\n                        };\n                }\n                VectorSparseInvertedIndexSearchState::Seek {\n                    sum,\n                    components,\n                    collected,\n                    distances,\n                    limit,\n                    key,\n                    component,\n                    sum_threshold,\n                } => {\n                    let Some(c) = components.as_ref() else {\n                        return Err(LimboError::InternalError(\n                            \"components must be present in Seek state\".to_string(),\n                        ));\n                    };\n                    if c.is_empty() && key.is_none() {\n                        let Some(distances) = distances.take() else {\n                            return Err(LimboError::InternalError(\n                                \"distances must be present in Seek state\".to_string(),\n                            ));\n                        };\n                        self.search_result = distances.iter().map(|(d, i)| (*i, d.0)).collect();\n                        return Ok(IOResult::Done(!self.search_result.is_empty()));\n                    }\n                    if key.is_none() {\n                        // we estimate jaccard distance with the following approach:\n                        // J = min(L, M1 + M2 + ... + Mr) / (Q + N - min(L, M1 + M2 + ... + Mr))\n                        // so we want J > best + delta; define M1 + M2 + ... + Mr = M\n                        // J = min(L, M) / (Q + L - min(L, M)) > best + delta\n                        // we need to consider two cases:\n                        // 1. L < M: J = L / (Q + L - L) > best + delta => L > (best + delta) * Q\n                        // 2. L > M: J = M / (Q + L - M) > best + delta => M > (best + delta) * (Q + L - M) => L < M / (best + delta) - (Q - M)\n                        // so we have two intervals: [(best + delta) * Q .. M] and [M .. M / (best + delta) - (Q - M)]\n                        // to simplify code for now we will pick upper bound from second range if it is not degenerate, otherwise check first range\n                        let m = c.iter().map(|c| c.max).sum::<f64>().min(*sum);\n                        let Some(dists) = distances.as_ref() else {\n                            return Err(LimboError::InternalError(\n                                \"distances must be present in Seek state\".to_string(),\n                            ));\n                        };\n                        if dists.len() >= *limit as usize {\n                            if let Some((max_threshold, _)) = dists.last() {\n                                let best = 1.0 - max_threshold.0;\n                                let delta = self.delta;\n                                let q = *sum;\n\n                                if best > 0.0 {\n                                    let first_range_l = (best + delta) * q;\n                                    let second_range_r = m / (best + delta) - (q - m);\n                                    if m <= second_range_r {\n                                        *sum_threshold = Some(second_range_r);\n                                    } else if first_range_l <= m {\n                                        *sum_threshold = Some(m);\n                                    } else {\n                                        *sum_threshold = Some(-1.0);\n                                    }\n                                    tracing::debug!(\n                                        \"sum_threshold={:?}, max_threshold={}, remained_sum={}, sum={}, components={:?}\",\n                                        sum_threshold,\n                                        best,\n                                        m,\n                                        sum,\n                                        c\n                                    );\n                                }\n                            }\n                        }\n                        let Some(comps) = components.as_mut() else {\n                            return Err(LimboError::InternalError(\n                                \"components must be present in Seek state\".to_string(),\n                            ));\n                        };\n                        let Some(c) = comps.pop_front() else {\n                            return Err(LimboError::InternalError(\n                                \"components queue must not be empty in Seek state\".to_string(),\n                            ));\n                        };\n                        *key = Some(ImmutableRecord::from_values(\n                            &[Value::from_i64(c.position as i64)],\n                            1,\n                        ));\n                        *component = Some(c.position);\n                    }\n                    let Some(k) = key.as_ref() else {\n                        return Err(LimboError::InternalError(\n                            \"key must be present in Seek state\".to_string(),\n                        ));\n                    };\n                    let result = return_if_io!(\n                        inverted.seek(SeekKey::IndexKey(k), SeekOp::GE { eq_only: false })\n                    );\n                    match result {\n                        SeekResult::Found => {\n                            let Some(comp) = component.take() else {\n                                return Err(LimboError::InternalError(\n                                    \"component must be present in Seek state\".to_string(),\n                                ));\n                            };\n                            self.search_state = VectorSparseInvertedIndexSearchState::Read {\n                                sum: *sum,\n                                components: components.take(),\n                                collected: collected.take(),\n                                distances: distances.take(),\n                                current: Some(Vec::new()),\n                                limit: *limit,\n                                sum_threshold: sum_threshold.take(),\n                                component: comp,\n                            };\n                        }\n                        SeekResult::TryAdvance | SeekResult::NotFound => {\n                            let Some(comp) = component.take() else {\n                                return Err(LimboError::InternalError(\n                                    \"component must be present in Seek state\".to_string(),\n                                ));\n                            };\n                            self.search_state = VectorSparseInvertedIndexSearchState::Next {\n                                sum: *sum,\n                                components: components.take(),\n                                collected: collected.take(),\n                                distances: distances.take(),\n                                current: Some(Vec::new()),\n                                limit: *limit,\n                                sum_threshold: sum_threshold.take(),\n                                component: comp,\n                            };\n                        }\n                    }\n                }\n                VectorSparseInvertedIndexSearchState::Read {\n                    sum,\n                    components,\n                    collected,\n                    distances,\n                    limit,\n                    sum_threshold,\n                    component,\n                    current,\n                } => {\n                    let record = return_if_io!(inverted.record());\n                    let row = parse_inverted_index_row(record)?;\n                    if row.position != *component\n                        || (sum_threshold.is_some()\n                            && row.sum\n                                > sum_threshold.ok_or_else(|| {\n                                    LimboError::InternalError(\n                                        \"sum_threshold must be present when checked\".to_string(),\n                                    )\n                                })?)\n                    {\n                        let Some(mut current) = current.take() else {\n                            return Err(LimboError::InternalError(\n                                \"current must be present in Read state\".to_string(),\n                            ));\n                        };\n                        current.sort_unstable();\n\n                        self.search_state = VectorSparseInvertedIndexSearchState::EvaluateSeek {\n                            sum: *sum,\n                            components: components.take(),\n                            collected: collected.take(),\n                            distances: distances.take(),\n                            limit: *limit,\n                            current: Some(current.into()),\n                            rowid: None,\n                        };\n                        continue;\n                    }\n                    let Some(coll) = collected.as_mut() else {\n                        return Err(LimboError::InternalError(\n                            \"collected must be present in Read state\".to_string(),\n                        ));\n                    };\n                    if coll.insert(row.rowid) {\n                        let Some(curr) = current.as_mut() else {\n                            return Err(LimboError::InternalError(\n                                \"current must be present in Read state\".to_string(),\n                            ));\n                        };\n                        curr.push(row.rowid);\n                    }\n\n                    self.search_state = VectorSparseInvertedIndexSearchState::Next {\n                        sum: *sum,\n                        components: components.take(),\n                        collected: collected.take(),\n                        distances: distances.take(),\n                        limit: *limit,\n                        sum_threshold: *sum_threshold,\n                        component: *component,\n                        current: current.take(),\n                    };\n                }\n                VectorSparseInvertedIndexSearchState::Next {\n                    sum,\n                    components,\n                    collected,\n                    distances,\n                    limit,\n                    sum_threshold,\n                    component,\n                    current,\n                } => {\n                    return_if_io!(inverted.next());\n                    if !inverted.has_record() {\n                        let Some(mut current) = current.take() else {\n                            return Err(LimboError::InternalError(\n                                \"current must be present in Next state\".to_string(),\n                            ));\n                        };\n                        current.sort_unstable();\n\n                        self.search_state = VectorSparseInvertedIndexSearchState::EvaluateSeek {\n                            sum: *sum,\n                            components: components.take(),\n                            collected: collected.take(),\n                            distances: distances.take(),\n                            limit: *limit,\n                            current: Some(current.into()),\n                            rowid: None,\n                        };\n                    } else {\n                        self.search_state = VectorSparseInvertedIndexSearchState::Read {\n                            sum: *sum,\n                            components: components.take(),\n                            collected: collected.take(),\n                            distances: distances.take(),\n                            limit: *limit,\n                            sum_threshold: *sum_threshold,\n                            component: *component,\n                            current: current.take(),\n                        };\n                    }\n                }\n                VectorSparseInvertedIndexSearchState::EvaluateSeek {\n                    sum,\n                    components,\n                    collected,\n                    distances,\n                    limit,\n                    current,\n                    rowid,\n                } => {\n                    let Some(c) = current.as_ref() else {\n                        return Err(LimboError::InternalError(\n                            \"current must be present in EvaluateSeek state\".to_string(),\n                        ));\n                    };\n                    if c.is_empty() && rowid.is_none() {\n                        self.search_state = VectorSparseInvertedIndexSearchState::Seek {\n                            sum: *sum,\n                            components: components.take(),\n                            collected: collected.take(),\n                            distances: distances.take(),\n                            limit: *limit,\n                            component: None,\n                            key: None,\n                            sum_threshold: None,\n                        };\n                        continue;\n                    }\n                    if rowid.is_none() {\n                        let Some(curr) = current.as_mut() else {\n                            return Err(LimboError::InternalError(\n                                \"current must be present in EvaluateSeek state\".to_string(),\n                            ));\n                        };\n                        *rowid = Some(curr.pop_front().ok_or_else(|| {\n                            LimboError::InternalError(\n                                \"current queue must not be empty in EvaluateSeek state\".to_string(),\n                            )\n                        })?);\n                    }\n\n                    let Some(rid) = rowid.as_ref() else {\n                        return Err(LimboError::InternalError(\n                            \"rowid must be present in EvaluateSeek state\".to_string(),\n                        ));\n                    };\n                    let rowid = *rid;\n                    let k = SeekKey::TableRowId(rowid);\n                    let result = return_if_io!(main.seek(k, SeekOp::GE { eq_only: true }));\n                    if !matches!(result, SeekResult::Found) {\n                        return Err(LimboError::Corrupt(\n                            \"vector_sparse_ivf corrupted: unable to find rowid in main table\"\n                                .to_string(),\n                        ));\n                    };\n                    self.search_state = VectorSparseInvertedIndexSearchState::EvaluateRead {\n                        sum: *sum,\n                        components: components.take(),\n                        collected: collected.take(),\n                        distances: distances.take(),\n                        limit: *limit,\n                        current: current.take(),\n                        rowid,\n                    };\n                }\n                VectorSparseInvertedIndexSearchState::EvaluateRead {\n                    sum,\n                    components,\n                    collected,\n                    distances,\n                    limit,\n                    current,\n                    rowid,\n                } => {\n                    let record = return_if_io!(main.record());\n                    if let Some(record) = record {\n                        let column_idx = self.configuration.columns[0].pos_in_table;\n                        let ValueRef::Blob(data) = record.get_value(column_idx)? else {\n                            return Err(LimboError::InternalError(\n                                \"table column value must be sparse vector\".to_string(),\n                            ));\n                        };\n                        let data = Vector::from_vec(data.to_vec())?;\n                        if !matches!(data.vector_type, VectorType::Float32Sparse) {\n                            return Err(LimboError::InternalError(\n                                \"table column value must be sparse vector\".to_string(),\n                            ));\n                        }\n                        let Some(arg) = values[1].get_value().to_blob() else {\n                            return Err(LimboError::InternalError(\n                                \"first value must be sparse vector\".to_string(),\n                            ));\n                        };\n                        let arg = Vector::from_vec(arg.to_vec())?;\n                        if !matches!(arg.vector_type, VectorType::Float32Sparse) {\n                            return Err(LimboError::InternalError(\n                                \"first value must be sparse vector\".to_string(),\n                            ));\n                        }\n                        tracing::debug!(\n                            \"vector: {:?}, query: {:?}\",\n                            data.as_f32_sparse(),\n                            arg.as_f32_sparse()\n                        );\n                        let distance = operations::jaccard::vector_distance_jaccard(&data, &arg)?;\n                        let Some(dists) = distances.as_mut() else {\n                            return Err(LimboError::InternalError(\n                                \"distances must be present in EvaluateRead state\".to_string(),\n                            ));\n                        };\n                        dists.insert((FloatOrd(distance), *rowid));\n                        if dists.len() > *limit as usize {\n                            let _ = dists.pop_last();\n                        }\n                    }\n\n                    self.search_state = VectorSparseInvertedIndexSearchState::EvaluateSeek {\n                        sum: *sum,\n                        components: components.take(),\n                        collected: collected.take(),\n                        distances: distances.take(),\n                        limit: *limit,\n                        current: current.take(),\n                        rowid: None,\n                    };\n                }\n            }\n        }\n    }\n\n    fn query_rowid(&mut self) -> Result<IOResult<Option<i64>>> {\n        let Some(result) = self.search_result.front() else {\n            return Err(LimboError::InternalError(\n                \"search_result must not be empty when query_rowid is called\".to_string(),\n            ));\n        };\n        Ok(IOResult::Done(Some(result.0)))\n    }\n\n    fn query_column(&mut self, _: usize) -> Result<IOResult<Value>> {\n        let Some(result) = self.search_result.front() else {\n            return Err(LimboError::InternalError(\n                \"search_result must not be empty when query_column is called\".to_string(),\n            ));\n        };\n        Ok(IOResult::Done(Value::from_f64(result.1)))\n    }\n\n    fn query_next(&mut self) -> Result<IOResult<bool>> {\n        let _ = self.search_result.pop_front();\n        Ok(IOResult::Done(!self.search_result.is_empty()))\n    }\n}\n"
  },
  {
    "path": "core/info.rs",
    "content": "pub mod build {\n    include!(concat!(env!(\"OUT_DIR\"), \"/built.rs\"));\n}\n"
  },
  {
    "path": "core/io/clock.rs",
    "content": "use std::time::{Duration, SystemTime, UNIX_EPOCH};\n\n/// A monotonic instant in time, backed by `std::time::Instant`.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]\npub struct MonotonicInstant(std::time::Instant);\n\nimpl MonotonicInstant {\n    pub fn now() -> Self {\n        MonotonicInstant(std::time::Instant::now())\n    }\n\n    pub fn elapsed(&self) -> Duration {\n        self.0.elapsed()\n    }\n\n    pub fn duration_since(&self, earlier: MonotonicInstant) -> Duration {\n        self.0.duration_since(earlier.0)\n    }\n\n    pub fn checked_add(&self, duration: Duration) -> Option<MonotonicInstant> {\n        self.0.checked_add(duration).map(MonotonicInstant)\n    }\n\n    pub fn checked_sub(&self, duration: Duration) -> Option<MonotonicInstant> {\n        self.0.checked_sub(duration).map(MonotonicInstant)\n    }\n}\n\nimpl std::ops::Add<Duration> for MonotonicInstant {\n    type Output = MonotonicInstant;\n\n    fn add(self, rhs: Duration) -> Self::Output {\n        MonotonicInstant(self.0 + rhs)\n    }\n}\n\nimpl std::ops::Sub<Duration> for MonotonicInstant {\n    type Output = MonotonicInstant;\n\n    fn sub(self, rhs: Duration) -> Self::Output {\n        MonotonicInstant(self.0 - rhs)\n    }\n}\n\n/// Wall-clock time as seconds and microseconds since Unix epoch.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]\npub struct WallClockInstant {\n    pub secs: i64,\n    pub micros: u32,\n}\n\nconst MICROS_PER_SEC: u32 = 1_000_000;\n\nimpl WallClockInstant {\n    pub fn now() -> Self {\n        let duration = SystemTime::now()\n            .duration_since(UNIX_EPOCH)\n            .expect(\"system time before Unix epoch\");\n        WallClockInstant {\n            secs: duration.as_secs() as i64,\n            micros: duration.subsec_micros(),\n        }\n    }\n\n    pub fn to_system_time(self) -> SystemTime {\n        if self.secs >= 0 {\n            UNIX_EPOCH + Duration::new(self.secs as u64, self.micros * 1000)\n        } else {\n            let positive_secs = (-self.secs) as u64;\n            if self.micros > 0 {\n                let nanos_to_subtract = (1_000_000 - self.micros) * 1000;\n                UNIX_EPOCH - Duration::new(positive_secs - 1, nanos_to_subtract)\n            } else {\n                UNIX_EPOCH - Duration::new(positive_secs, 0)\n            }\n        }\n    }\n\n    pub fn checked_add_duration(&self, other: &Duration) -> Option<WallClockInstant> {\n        let mut secs = self.secs.checked_add_unsigned(other.as_secs())?;\n        let mut micros = other.subsec_micros() + self.micros;\n        if micros >= MICROS_PER_SEC {\n            micros -= MICROS_PER_SEC;\n            secs = secs.checked_add(1)?;\n        }\n        Some(Self { secs, micros })\n    }\n\n    pub fn checked_sub_duration(&self, other: &Duration) -> Option<WallClockInstant> {\n        let mut secs = self.secs.checked_sub_unsigned(other.as_secs())?;\n        let mut micros = self.micros as i32 - other.subsec_micros() as i32;\n        if micros < 0 {\n            micros += MICROS_PER_SEC as i32;\n            secs = secs.checked_sub(1)?;\n        }\n        Some(Self {\n            secs,\n            micros: micros as u32,\n        })\n    }\n}\n\nimpl std::ops::Add<Duration> for WallClockInstant {\n    type Output = WallClockInstant;\n\n    fn add(self, rhs: Duration) -> Self::Output {\n        self.checked_add_duration(&rhs)\n            .expect(\"duration addition overflow\")\n    }\n}\n\nimpl std::ops::Sub<Duration> for WallClockInstant {\n    type Output = WallClockInstant;\n\n    fn sub(self, rhs: Duration) -> Self::Output {\n        self.checked_sub_duration(&rhs)\n            .expect(\"duration subtraction underflow\")\n    }\n}\n\nimpl<T: chrono::TimeZone> From<chrono::DateTime<T>> for WallClockInstant {\n    fn from(value: chrono::DateTime<T>) -> Self {\n        WallClockInstant {\n            secs: value.timestamp(),\n            micros: value.timestamp_subsec_micros(),\n        }\n    }\n}\n\npub trait Clock {\n    /// Monotonic time for timeout checking and elapsed time measurement.\n    /// Cheap on real systems (reads TSC), controllable in simulation.\n    fn current_time_monotonic(&self) -> MonotonicInstant;\n\n    /// Wall-clock time for timestamps (WAL, datetime functions).\n    /// Controllable in simulation for deterministic behavior.\n    fn current_time_wall_clock(&self) -> WallClockInstant;\n}\n\npub struct DefaultClock;\n\nimpl Clock for DefaultClock {\n    fn current_time_monotonic(&self) -> MonotonicInstant {\n        MonotonicInstant::now()\n    }\n\n    fn current_time_wall_clock(&self) -> WallClockInstant {\n        WallClockInstant::now()\n    }\n}\n"
  },
  {
    "path": "core/io/common.rs",
    "content": "pub const ENV_DISABLE_FILE_LOCK: &str = \"LIMBO_DISABLE_FILE_LOCK\";\n\n#[cfg(test)]\npub mod tests {\n    use crate::{Result, IO};\n    use std::process::{Command, Stdio};\n    use tempfile::NamedTempFile;\n\n    fn run_test_parent_process<T: IO>(create_io: fn() -> Result<T>) {\n        let temp_file: NamedTempFile = NamedTempFile::new().expect(\"Failed to create temp file\");\n        let path = temp_file.path().to_str().unwrap().to_string();\n\n        // Parent process opens the file\n        let io1 = create_io().expect(\"Failed to create IO\");\n        let _file1 = io1\n            .open_file(&path, crate::io::OpenFlags::None, false)\n            .expect(\"Failed to open file in parent process\");\n\n        let current_exe = std::env::current_exe().expect(\"Failed to get current executable path\");\n\n        // Spawn a child process and try to open the same file\n        let child = Command::new(current_exe)\n            .env(\"RUST_TEST_CHILD_PROCESS\", \"1\")\n            .env(\"RUST_TEST_FILE_PATH\", &path)\n            .stdout(Stdio::piped())\n            .stderr(Stdio::piped())\n            .spawn()\n            .expect(\"Failed to spawn child process\");\n\n        let output = child.wait_with_output().expect(\"Failed to wait on child\");\n        assert!(\n            !output.status.success(),\n            \"Child process should have failed to open the file\"\n        );\n    }\n\n    fn run_test_child_process<T: IO>(create_io: fn() -> Result<T>) -> Result<()> {\n        if std::env::var(\"RUST_TEST_CHILD_PROCESS\").is_ok() {\n            let path = std::env::var(\"RUST_TEST_FILE_PATH\")?;\n            let io = create_io()?;\n            match io.open_file(&path, crate::io::OpenFlags::None, false) {\n                Ok(_) => std::process::exit(0),\n                Err(_) => std::process::exit(1),\n            }\n        }\n        Ok(())\n    }\n\n    pub fn test_multiple_processes_cannot_open_file<T: IO>(create_io: fn() -> Result<T>) {\n        run_test_child_process(create_io).unwrap();\n        run_test_parent_process(create_io);\n    }\n}\n"
  },
  {
    "path": "core/io/completions.rs",
    "content": "use crate::turso_assert_eq;\nuse core::fmt::{self, Debug};\nuse std::{\n    future::Future,\n    sync::{\n        atomic::{AtomicUsize, Ordering},\n        Arc, OnceLock,\n    },\n    task::{Poll, Waker},\n};\n\nuse crate::sync::Mutex;\n\nuse crate::{Buffer, CompletionError};\n\n/// Callback for read completions. Returns `Some(error)` if the callback detects an error\n/// (e.g., short read), which will be stored in the completion and propagated to VDBE.\npub type ReadComplete =\n    dyn Fn(Result<(Arc<Buffer>, i32), CompletionError>) -> Option<CompletionError> + Send + Sync;\npub type WriteComplete = dyn Fn(Result<i32, CompletionError>) + Send + Sync;\npub type SyncComplete = dyn Fn(Result<i32, CompletionError>) + Send + Sync;\npub type TruncateComplete = dyn Fn(Result<i32, CompletionError>) + Send + Sync;\n\n#[must_use]\n#[derive(Debug, Clone)]\npub struct Completion {\n    /// Optional completion state. If None, it means we are Yield in order to not allocate anything\n    pub(super) inner: Option<Arc<CompletionInner>>,\n}\n\nimpl Future for Completion {\n    type Output = Result<(), crate::LimboError>;\n\n    fn poll(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {\n        self.set_waker(cx.waker());\n        if self.finished() {\n            self.wake();\n            let res = self\n                .get_error()\n                .map_or(Ok(()), |err| Err(crate::LimboError::CompletionError(err)));\n            return Poll::Ready(res);\n        }\n        Poll::Pending\n    }\n}\n\n#[derive(Debug, Default)]\nstruct ContextInner {\n    waker: Option<Waker>,\n    // TODO: add abort signal\n}\n\n#[derive(Debug, Clone)]\npub struct Context {\n    inner: Arc<Mutex<ContextInner>>,\n}\n\nimpl ContextInner {\n    pub fn new() -> Self {\n        Self { waker: None }\n    }\n\n    pub fn wake(&mut self) {\n        if let Some(waker) = self.waker.take() {\n            waker.wake();\n        }\n    }\n\n    pub fn set_waker(&mut self, waker: &Waker) {\n        if let Some(curr_waker) = self.waker.as_mut() {\n            // only call and change waker if it would awake a different task\n            if !curr_waker.will_wake(waker) {\n                let prev_waker = std::mem::replace(curr_waker, waker.clone());\n                prev_waker.wake();\n            }\n        } else {\n            self.waker = Some(waker.clone());\n        }\n    }\n}\n\nimpl Default for Context {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl Context {\n    pub fn new() -> Self {\n        Self {\n            inner: Arc::new(Mutex::new(ContextInner::new())),\n        }\n    }\n\n    pub fn wake(&self) {\n        self.inner.lock().wake();\n    }\n\n    pub fn set_waker(&self, waker: &Waker) {\n        self.inner.lock().set_waker(waker);\n    }\n}\n\npub(super) struct CompletionInner {\n    completion_type: CompletionType,\n    /// None means we completed successfully\n    // Thread safe with OnceLock\n    pub(super) result: crate::sync::OnceLock<Option<CompletionError>>,\n    context: Context,\n    /// Optional parent group this completion belongs to\n    parent: OnceLock<Arc<GroupCompletionInner>>,\n    /// Keeps the write buffer alive for async I/O backends (io_uring, VFS)\n    /// where pwrite returns before the kernel has consumed the buffer.\n    write_buffer: OnceLock<Arc<Buffer>>,\n}\n\nimpl fmt::Debug for CompletionInner {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"CompletionInner\")\n            .field(\"completion_type\", &self.completion_type)\n            .field(\"parent\", &self.parent.get().is_some())\n            .finish()\n    }\n}\n\npub struct CompletionGroup {\n    completions: Vec<Completion>,\n    callback: Box<dyn Fn(Result<i32, CompletionError>) + Send + Sync>,\n}\n\nimpl CompletionGroup {\n    pub fn new<F>(callback: F) -> Self\n    where\n        F: Fn(Result<i32, CompletionError>) + Send + Sync + 'static,\n    {\n        Self {\n            completions: Vec::new(),\n            callback: Box::new(callback),\n        }\n    }\n\n    pub fn add(&mut self, completion: &Completion) {\n        self.completions.push(completion.clone());\n    }\n\n    pub fn cancel(&self) {\n        for c in &self.completions {\n            c.abort();\n        }\n    }\n\n    pub fn build(self) -> Completion {\n        let total = self.completions.len();\n        if total == 0 {\n            (self.callback)(Ok(0));\n            return Completion::new_yield();\n        }\n        let group_completion = GroupCompletion::new(self.callback, total);\n        let group = Completion::new(CompletionType::Group(group_completion));\n\n        // Store the group completion reference for later callback\n        if let CompletionType::Group(ref g) = group.get_inner().completion_type {\n            let _ = g.inner.self_completion.set(group.clone());\n        }\n\n        for mut c in self.completions {\n            // If the completion has not completed, link it to the group.\n            if !c.finished() {\n                c.link_internal(&group);\n                continue;\n            }\n            let group_inner = match &group.get_inner().completion_type {\n                CompletionType::Group(g) => &g.inner,\n                _ => unreachable!(),\n            };\n            // Return early if there was an error.\n            if let Some(err) = c.get_error() {\n                let _ = group_inner.result.set(Some(err));\n                group_inner.outstanding.store(0, Ordering::SeqCst);\n                (group_inner.complete)(Err(err));\n                return group;\n            }\n            // Mark the successful completion as done.\n            group_inner.outstanding.fetch_sub(1, Ordering::SeqCst);\n        }\n\n        let group_inner = match &group.get_inner().completion_type {\n            CompletionType::Group(g) => &g.inner,\n            _ => unreachable!(),\n        };\n        if group_inner.outstanding.load(Ordering::SeqCst) == 0 {\n            // Set result to Some(None) on success so succeeded() returns true\n            let _ = group_inner.result.set(None);\n            (group_inner.complete)(Ok(0));\n        }\n        group\n    }\n}\n\npub struct GroupCompletion {\n    inner: Arc<GroupCompletionInner>,\n}\n\nimpl fmt::Debug for GroupCompletion {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"GroupCompletion\")\n            .field(\n                \"outstanding\",\n                &self.inner.outstanding.load(Ordering::SeqCst),\n            )\n            .finish()\n    }\n}\n\nstruct GroupCompletionInner {\n    /// Number of completions that need to finish\n    outstanding: AtomicUsize,\n    /// Callback to invoke when all completions finish\n    complete: Box<dyn Fn(Result<i32, CompletionError>) + Send + Sync>,\n    /// Cached result after all completions finish\n    result: OnceLock<Option<CompletionError>>,\n    /// Reference to the group's own Completion for notifying parents\n    self_completion: OnceLock<Completion>,\n}\n\nimpl GroupCompletion {\n    pub fn new<F>(complete: F, outstanding: usize) -> Self\n    where\n        F: Fn(Result<i32, CompletionError>) + Send + Sync + 'static,\n    {\n        Self {\n            inner: Arc::new(GroupCompletionInner {\n                outstanding: AtomicUsize::new(outstanding),\n                complete: Box::new(complete),\n                result: OnceLock::new(),\n                self_completion: OnceLock::new(),\n            }),\n        }\n    }\n\n    pub fn callback(&self, result: Result<i32, CompletionError>) {\n        turso_assert_eq!(\n            self.inner.outstanding.load(Ordering::SeqCst),\n            0,\n            \"callback called before all completions finished\"\n        );\n        (self.inner.complete)(result);\n    }\n}\n\nimpl Debug for CompletionType {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match self {\n            Self::Read(..) => f.debug_tuple(\"Read\").finish(),\n            Self::Write(..) => f.debug_tuple(\"Write\").finish(),\n            Self::Sync(..) => f.debug_tuple(\"Sync\").finish(),\n            Self::Truncate(..) => f.debug_tuple(\"Truncate\").finish(),\n            Self::Group(..) => f.debug_tuple(\"Group\").finish(),\n            Self::Yield => f.debug_tuple(\"Yield\").finish(),\n        }\n    }\n}\n\npub enum CompletionType {\n    Read(ReadCompletion),\n    Write(WriteCompletion),\n    Sync(SyncCompletion),\n    Truncate(TruncateCompletion),\n    Group(GroupCompletion),\n    Yield,\n}\n\nimpl CompletionInner {\n    fn new(completion_type: CompletionType) -> Self {\n        Self {\n            completion_type,\n            result: OnceLock::new(),\n            context: Context::new(),\n            parent: OnceLock::new(),\n            write_buffer: OnceLock::new(),\n        }\n    }\n}\n\nimpl Completion {\n    pub fn new(completion_type: CompletionType) -> Self {\n        Self {\n            inner: Some(Arc::new(CompletionInner::new(completion_type))),\n        }\n    }\n\n    pub(super) fn get_inner(&self) -> &Arc<CompletionInner> {\n        self.inner\n            .as_ref()\n            .expect(\"completion inner should be initialized\")\n    }\n\n    /// Stores a write buffer reference in the completion to keep it alive\n    /// until the I/O completes. Required for async backends (io_uring, VFS)\n    /// where pwrite returns before the kernel has consumed the buffer.\n    pub fn keep_write_buffer_alive(&self, buf: Arc<Buffer>) {\n        self.get_inner()\n            .write_buffer\n            .set(buf)\n            .expect(\"write buffer should only be set once\");\n    }\n\n    pub fn new_write<F>(complete: F) -> Self\n    where\n        F: Fn(Result<i32, CompletionError>) + Send + Sync + 'static,\n    {\n        Self::new(CompletionType::Write(WriteCompletion::new(Box::new(\n            complete,\n        ))))\n    }\n\n    pub fn new_read<F>(buf: Arc<Buffer>, complete: F) -> Self\n    where\n        F: Fn(Result<(Arc<Buffer>, i32), CompletionError>) -> Option<CompletionError>\n            + Send\n            + Sync\n            + 'static,\n    {\n        Self::new(CompletionType::Read(ReadCompletion::new(\n            buf,\n            Box::new(complete),\n        )))\n    }\n    pub fn new_sync<F>(complete: F) -> Self\n    where\n        F: Fn(Result<i32, CompletionError>) + Send + Sync + 'static,\n    {\n        Self::new(CompletionType::Sync(SyncCompletion::new(Box::new(\n            complete,\n        ))))\n    }\n\n    pub fn new_trunc<F>(complete: F) -> Self\n    where\n        F: Fn(Result<i32, CompletionError>) + Send + Sync + 'static,\n    {\n        Self::new(CompletionType::Truncate(TruncateCompletion::new(Box::new(\n            complete,\n        ))))\n    }\n\n    /// Create a yield completion. These are completed by default allowing to yield control without\n    /// allocating memory.\n    pub fn new_yield() -> Self {\n        Self { inner: None }\n    }\n\n    pub fn wake(&self) {\n        self.get_inner().context.wake();\n    }\n\n    pub fn set_waker(&self, waker: &Waker) {\n        if self.finished() || self.inner.is_none() {\n            waker.wake_by_ref();\n        } else {\n            self.get_inner().context.set_waker(waker);\n        }\n    }\n\n    pub fn succeeded(&self) -> bool {\n        match &self.inner {\n            Some(inner) => match &inner.completion_type {\n                CompletionType::Group(g) => {\n                    g.inner.outstanding.load(Ordering::SeqCst) == 0\n                        && g.inner.result.get().is_some_and(|e| e.is_none())\n                }\n                _ => inner.result.get().is_some_and(|e| e.is_none()),\n            },\n            None => true,\n        }\n    }\n\n    pub fn failed(&self) -> bool {\n        match &self.inner {\n            Some(inner) => inner.result.get().is_some_and(|val| val.is_some()),\n            None => false,\n        }\n    }\n\n    pub fn get_error(&self) -> Option<CompletionError> {\n        match &self.inner {\n            Some(inner) => {\n                match &inner.completion_type {\n                    CompletionType::Group(g) => {\n                        // For groups, check the group's cached result field\n                        // (set when the last completion finishes)\n                        g.inner.result.get().and_then(|res| *res)\n                    }\n                    _ => inner.result.get().and_then(|res| *res),\n                }\n            }\n            None => None,\n        }\n    }\n\n    /// Checks if the Completion completed or errored\n    pub fn finished(&self) -> bool {\n        match &self.inner {\n            Some(inner) => match &inner.completion_type {\n                CompletionType::Group(g) => g.inner.outstanding.load(Ordering::SeqCst) == 0,\n                _ => inner.result.get().is_some(),\n            },\n            None => true,\n        }\n    }\n\n    /// Returns true if this completion is an explicit yield — a signal to\n    /// return control to the cooperative scheduler so other connections can make\n    /// progress. Unlike real I/O completions that happen to be finished,\n    /// yield completions must not be treated as \"ready to continue immediately\"\n    /// because the yielding operation is waiting on external state (e.g. a lock\n    /// held by another fiber) that can only change when other fibers are stepped.\n    pub fn is_explicit_yield(&self) -> bool {\n        self.inner.is_none()\n    }\n\n    pub fn complete(&self, result: i32) {\n        let result = Ok(result);\n        self.callback(result);\n    }\n\n    pub fn error(&self, err: CompletionError) {\n        let result = Err(err);\n        self.callback(result);\n    }\n\n    pub fn abort(&self) {\n        self.error(CompletionError::Aborted);\n    }\n\n    fn callback(&self, result: Result<i32, CompletionError>) {\n        let inner = self.get_inner();\n        inner.result.get_or_init(|| {\n            // Run the type-specific callback. For ReadCompletion, this returns\n            // an optional error detected by the callback (e.g., short read).\n            let callback_error = match &inner.completion_type {\n                CompletionType::Read(r) => r.callback(result),\n                CompletionType::Write(w) => {\n                    w.callback(result);\n                    None\n                }\n                CompletionType::Sync(s) => {\n                    s.callback(result);\n                    None\n                }\n                CompletionType::Truncate(t) => {\n                    t.callback(result);\n                    None\n                }\n                CompletionType::Group(g) => {\n                    g.callback(result);\n                    None\n                }\n                CompletionType::Yield => None,\n            };\n\n            // Use callback error if present, otherwise use the original IO error\n            let final_error = callback_error.or_else(|| result.err());\n\n            if let Some(group) = inner.parent.get() {\n                // Capture first error in group\n                if let Some(err) = final_error {\n                    let _ = group.result.set(Some(err));\n                }\n                let prev = group.outstanding.fetch_sub(1, Ordering::SeqCst);\n                if prev > 1 {\n                    // progress wake so the waiter keeps driving io.step,\n                    // If prev > 1, there are still children outstanding after this one.\n                    if let Some(group_completion) = group.self_completion.get() {\n                        group_completion.wake();\n                    }\n                }\n                // If this was the last completion in the group, trigger the group's callback\n                // which will recursively call this same callback() method to notify parents\n                if prev == 1 {\n                    // Set result to Some(None) on success so succeeded() returns true\n                    let _ = group.result.set(None);\n                    if let Some(group_completion) = group.self_completion.get() {\n                        let group_result = group.result.get().and_then(|e| *e);\n                        group_completion.callback(group_result.map_or(Ok(0), Err));\n                    }\n                }\n            }\n\n            final_error\n        });\n        // call the waker regardless\n        inner.context.wake();\n    }\n\n    /// only call this method if you are sure that the completion is\n    /// a ReadCompletion, panics otherwise\n    pub fn as_read(&self) -> &ReadCompletion {\n        let inner = self.get_inner();\n        match inner.completion_type {\n            CompletionType::Read(ref r) => r,\n            _ => unreachable!(),\n        }\n    }\n\n    /// Link this completion to a group completion (internal use only)\n    fn link_internal(&mut self, group: &Completion) {\n        let group_inner = match &group.get_inner().completion_type {\n            CompletionType::Group(g) => &g.inner,\n            _ => panic!(\"link_internal() requires a group completion\"),\n        };\n\n        // Set the parent (can only be set once)\n        if self.get_inner().parent.set(group_inner.clone()).is_err() {\n            panic!(\"completion can only be linked once\");\n        }\n    }\n}\n\npub struct ReadCompletion {\n    pub buf: Arc<Buffer>,\n    pub complete: Box<ReadComplete>,\n}\n\nimpl ReadCompletion {\n    pub fn new(buf: Arc<Buffer>, complete: Box<ReadComplete>) -> Self {\n        Self { buf, complete }\n    }\n\n    pub fn buf(&self) -> &Buffer {\n        &self.buf\n    }\n\n    pub fn callback(&self, bytes_read: Result<i32, CompletionError>) -> Option<CompletionError> {\n        (self.complete)(bytes_read.map(|b| (self.buf.clone(), b)))\n    }\n\n    pub fn buf_arc(&self) -> Arc<Buffer> {\n        self.buf.clone()\n    }\n}\n\npub struct WriteCompletion {\n    pub complete: Box<WriteComplete>,\n}\n\nimpl WriteCompletion {\n    pub fn new(complete: Box<WriteComplete>) -> Self {\n        Self { complete }\n    }\n\n    pub fn callback(&self, bytes_written: Result<i32, CompletionError>) {\n        (self.complete)(bytes_written);\n    }\n}\n\npub struct SyncCompletion {\n    pub complete: Box<SyncComplete>,\n}\n\nimpl SyncCompletion {\n    pub fn new(complete: Box<SyncComplete>) -> Self {\n        Self { complete }\n    }\n\n    pub fn callback(&self, res: Result<i32, CompletionError>) {\n        (self.complete)(res);\n    }\n}\n\npub struct TruncateCompletion {\n    pub complete: Box<TruncateComplete>,\n}\n\nimpl TruncateCompletion {\n    pub fn new(complete: Box<TruncateComplete>) -> Self {\n        Self { complete }\n    }\n\n    pub fn callback(&self, res: Result<i32, CompletionError>) {\n        (self.complete)(res);\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::CompletionError;\n\n    use super::*;\n\n    #[test]\n    fn test_completion_group_empty() {\n        use crate::sync::atomic::{AtomicBool, Ordering};\n\n        let callback_called = Arc::new(AtomicBool::new(false));\n        let callback_called_clone = callback_called.clone();\n\n        let group = CompletionGroup::new(move |_| {\n            callback_called_clone.store(true, Ordering::SeqCst);\n        });\n        let group = group.build();\n        assert!(group.finished());\n        assert!(group.succeeded());\n        assert!(group.get_error().is_none());\n\n        // Verify the callback was actually called\n        assert!(\n            callback_called.load(Ordering::SeqCst),\n            \"callback should be called for empty group\"\n        );\n    }\n\n    #[test]\n    fn test_completion_group_single_completion() {\n        let mut group = CompletionGroup::new(|_| {});\n        let c = Completion::new_write(|_| {});\n        group.add(&c);\n        let group = group.build();\n\n        assert!(!group.finished());\n        assert!(!group.succeeded());\n\n        c.complete(0);\n\n        assert!(group.finished());\n        assert!(group.succeeded());\n        assert!(group.get_error().is_none());\n    }\n\n    #[test]\n    fn test_completion_group_multiple_completions() {\n        let mut group = CompletionGroup::new(|_| {});\n        let c1 = Completion::new_write(|_| {});\n        let c2 = Completion::new_write(|_| {});\n        let c3 = Completion::new_write(|_| {});\n        group.add(&c1);\n        group.add(&c2);\n        group.add(&c3);\n        let group = group.build();\n\n        assert!(!group.succeeded());\n        assert!(!group.finished());\n\n        c1.complete(0);\n        assert!(!group.succeeded());\n        assert!(!group.finished());\n\n        c2.complete(0);\n        assert!(!group.succeeded());\n        assert!(!group.finished());\n\n        c3.complete(0);\n        assert!(group.succeeded());\n        assert!(group.finished());\n    }\n\n    #[test]\n    fn test_completion_group_with_error() {\n        let mut group = CompletionGroup::new(|_| {});\n        let c1 = Completion::new_write(|_| {});\n        let c2 = Completion::new_write(|_| {});\n        group.add(&c1);\n        group.add(&c2);\n        let group = group.build();\n\n        c1.complete(0);\n        c2.error(CompletionError::Aborted);\n\n        assert!(group.finished());\n        assert!(!group.succeeded());\n        assert_eq!(group.get_error(), Some(CompletionError::Aborted));\n    }\n\n    #[test]\n    fn test_completion_group_callback() {\n        use crate::sync::atomic::{AtomicBool, Ordering};\n        let called = Arc::new(AtomicBool::new(false));\n        let called_clone = called.clone();\n\n        let mut group = CompletionGroup::new(move |_| {\n            called_clone.store(true, Ordering::SeqCst);\n        });\n\n        let c1 = Completion::new_write(|_| {});\n        let c2 = Completion::new_write(|_| {});\n        group.add(&c1);\n        group.add(&c2);\n        let group = group.build();\n\n        assert!(!called.load(Ordering::SeqCst));\n\n        c1.complete(0);\n        assert!(!called.load(Ordering::SeqCst));\n\n        c2.complete(0);\n        assert!(called.load(Ordering::SeqCst));\n        assert!(group.finished());\n        assert!(group.succeeded());\n    }\n\n    #[test]\n    fn test_completion_group_some_already_completed() {\n        // Test some completions added to group, then finish before build()\n        let mut group = CompletionGroup::new(|_| {});\n        let c1 = Completion::new_write(|_| {});\n        let c2 = Completion::new_write(|_| {});\n        let c3 = Completion::new_write(|_| {});\n\n        // Add all to group while pending\n        group.add(&c1);\n        group.add(&c2);\n        group.add(&c3);\n\n        // Complete c1 and c2 AFTER adding but BEFORE build()\n        c1.complete(0);\n        c2.complete(0);\n\n        let group = group.build();\n\n        // c1 and c2 finished before build(), so outstanding should account for them\n        // Only c3 should be pending\n        assert!(!group.finished());\n        assert!(!group.succeeded());\n\n        // Complete c3\n        c3.complete(0);\n\n        // Now the group should be finished\n        assert!(group.finished());\n        assert!(group.succeeded());\n        assert!(group.get_error().is_none());\n    }\n\n    #[test]\n    fn test_completion_group_all_already_completed() {\n        // Test when all completions are already finished before build()\n        let mut group = CompletionGroup::new(|_| {});\n        let c1 = Completion::new_write(|_| {});\n        let c2 = Completion::new_write(|_| {});\n\n        // Complete both before adding to group\n        c1.complete(0);\n        c2.complete(0);\n\n        group.add(&c1);\n        group.add(&c2);\n\n        let group = group.build();\n\n        // All completions were already complete, so group should be finished immediately\n        assert!(group.finished());\n        assert!(group.succeeded());\n        assert!(group.get_error().is_none());\n    }\n\n    #[test]\n    fn test_completion_group_mixed_finished_and_pending() {\n        use crate::sync::atomic::{AtomicBool, Ordering};\n        let called = Arc::new(AtomicBool::new(false));\n        let called_clone = called.clone();\n\n        let mut group = CompletionGroup::new(move |_| {\n            called_clone.store(true, Ordering::SeqCst);\n        });\n\n        let c1 = Completion::new_write(|_| {});\n        let c2 = Completion::new_write(|_| {});\n        let c3 = Completion::new_write(|_| {});\n        let c4 = Completion::new_write(|_| {});\n\n        // Complete c1 and c3 before adding to group\n        c1.complete(0);\n        c3.complete(0);\n\n        group.add(&c1);\n        group.add(&c2);\n        group.add(&c3);\n        group.add(&c4);\n\n        let group = group.build();\n\n        // Only c2 and c4 should be pending\n        assert!(!group.finished());\n        assert!(!called.load(Ordering::SeqCst));\n\n        c2.complete(0);\n        assert!(!group.finished());\n        assert!(!called.load(Ordering::SeqCst));\n\n        c4.complete(0);\n        assert!(group.finished());\n        assert!(group.succeeded());\n        assert!(called.load(Ordering::SeqCst));\n    }\n\n    #[test]\n    fn test_completion_group_already_completed_with_error() {\n        // Test when a completion finishes with error before build()\n        let mut group = CompletionGroup::new(|_| {});\n        let c1 = Completion::new_write(|_| {});\n        let c2 = Completion::new_write(|_| {});\n\n        // Complete c1 with error before adding to group\n        c1.error(CompletionError::Aborted);\n\n        group.add(&c1);\n        group.add(&c2);\n\n        let group = group.build();\n\n        // Group should immediately fail with the error\n        assert!(group.finished());\n        assert!(!group.succeeded());\n        assert_eq!(group.get_error(), Some(CompletionError::Aborted));\n    }\n\n    #[test]\n    fn test_completion_group_tracks_all_completions() {\n        // This test verifies the fix for the bug where CompletionGroup::add()\n        // would skip successfully-finished completions. This caused problems\n        // when code used drain() to move completions into a group, because\n        // finished completions would be removed from the source but not tracked\n        // by the group, effectively losing them.\n        use crate::sync::atomic::{AtomicUsize, Ordering};\n\n        let callback_count = Arc::new(AtomicUsize::new(0));\n        let callback_count_clone = callback_count.clone();\n\n        // Simulate the pattern: create multiple completions, complete some,\n        // then add ALL of them to a group (like drain() would do)\n        let mut completions = Vec::new();\n\n        // Create 4 completions\n        for _ in 0..4 {\n            completions.push(Completion::new_write(|_| {}));\n        }\n\n        // Complete 2 of them before adding to group (simulate async completion)\n        completions[0].complete(0);\n        completions[2].complete(0);\n\n        // Now create a group and add ALL completions (like drain() would do)\n        let mut group = CompletionGroup::new(move |_| {\n            callback_count_clone.fetch_add(1, Ordering::SeqCst);\n        });\n\n        // Add all completions to the group\n        for c in &completions {\n            group.add(c);\n        }\n\n        let group = group.build();\n\n        // The group should track all 4 completions:\n        // - c[0] and c[2] are already finished\n        // - c[1] and c[3] are still pending\n        // So the group should not be finished yet\n        assert!(!group.finished());\n        assert_eq!(callback_count.load(Ordering::SeqCst), 0);\n\n        // Complete the first pending completion\n        completions[1].complete(0);\n        assert!(!group.finished());\n        assert_eq!(callback_count.load(Ordering::SeqCst), 0);\n\n        // Complete the last pending completion - now group should finish\n        completions[3].complete(0);\n        assert!(group.finished());\n        assert!(group.succeeded());\n        assert_eq!(callback_count.load(Ordering::SeqCst), 1);\n\n        // Verify no errors\n        assert!(group.get_error().is_none());\n    }\n\n    #[test]\n    fn test_completion_group_with_all_finished_successfully() {\n        // Edge case: all completions are already successfully finished\n        // when added to the group. The group should complete immediately.\n        use crate::sync::atomic::{AtomicBool, Ordering};\n\n        let callback_called = Arc::new(AtomicBool::new(false));\n        let callback_called_clone = callback_called.clone();\n\n        let mut completions = Vec::new();\n\n        // Create and immediately complete 3 completions\n        for _ in 0..3 {\n            let c = Completion::new_write(|_| {});\n            c.complete(0);\n            completions.push(c);\n        }\n\n        // Add all already-completed completions to group\n        let mut group = CompletionGroup::new(move |_| {\n            callback_called_clone.store(true, Ordering::SeqCst);\n        });\n\n        for c in &completions {\n            group.add(c);\n        }\n\n        let group = group.build();\n\n        // Group should be immediately finished since all completions were done\n        assert!(group.finished());\n        assert!(group.succeeded());\n        assert!(callback_called.load(Ordering::SeqCst));\n        assert!(group.get_error().is_none());\n    }\n\n    #[test]\n    fn test_completion_group_nested() {\n        use crate::sync::atomic::{AtomicUsize, Ordering};\n\n        // Track callbacks at different levels\n        let parent_called = Arc::new(AtomicUsize::new(0));\n        let child1_called = Arc::new(AtomicUsize::new(0));\n        let child2_called = Arc::new(AtomicUsize::new(0));\n\n        // Create child group 1 with 2 completions\n        let child1_called_clone = child1_called.clone();\n        let mut child_group1 = CompletionGroup::new(move |_| {\n            child1_called_clone.fetch_add(1, Ordering::SeqCst);\n        });\n        let c1 = Completion::new_write(|_| {});\n        let c2 = Completion::new_write(|_| {});\n        child_group1.add(&c1);\n        child_group1.add(&c2);\n        let child_group1 = child_group1.build();\n\n        // Create child group 2 with 2 completions\n        let child2_called_clone = child2_called.clone();\n        let mut child_group2 = CompletionGroup::new(move |_| {\n            child2_called_clone.fetch_add(1, Ordering::SeqCst);\n        });\n        let c3 = Completion::new_write(|_| {});\n        let c4 = Completion::new_write(|_| {});\n        child_group2.add(&c3);\n        child_group2.add(&c4);\n        let child_group2 = child_group2.build();\n\n        // Create parent group containing both child groups\n        let parent_called_clone = parent_called.clone();\n        let mut parent_group = CompletionGroup::new(move |_| {\n            parent_called_clone.fetch_add(1, Ordering::SeqCst);\n        });\n        parent_group.add(&child_group1);\n        parent_group.add(&child_group2);\n        let parent_group = parent_group.build();\n\n        // Initially nothing should be finished\n        assert!(!parent_group.finished());\n        assert!(!child_group1.finished());\n        assert!(!child_group2.finished());\n        assert_eq!(parent_called.load(Ordering::SeqCst), 0);\n        assert_eq!(child1_called.load(Ordering::SeqCst), 0);\n        assert_eq!(child2_called.load(Ordering::SeqCst), 0);\n\n        // Complete first completion in child group 1\n        c1.complete(0);\n        assert!(!child_group1.finished());\n        assert!(!parent_group.finished());\n        assert_eq!(child1_called.load(Ordering::SeqCst), 0);\n        assert_eq!(parent_called.load(Ordering::SeqCst), 0);\n\n        // Complete second completion in child group 1 - should finish child group 1\n        c2.complete(0);\n        assert!(child_group1.finished());\n        assert!(child_group1.succeeded());\n        assert_eq!(child1_called.load(Ordering::SeqCst), 1);\n\n        // Parent should not be finished yet because child group 2 is still pending\n        assert!(!parent_group.finished());\n        assert_eq!(parent_called.load(Ordering::SeqCst), 0);\n\n        // Complete first completion in child group 2\n        c3.complete(0);\n        assert!(!child_group2.finished());\n        assert!(!parent_group.finished());\n        assert_eq!(child2_called.load(Ordering::SeqCst), 0);\n        assert_eq!(parent_called.load(Ordering::SeqCst), 0);\n\n        // Complete second completion in child group 2 - should finish everything\n        c4.complete(0);\n        assert!(child_group2.finished());\n        assert!(child_group2.succeeded());\n        assert_eq!(child2_called.load(Ordering::SeqCst), 1);\n\n        // Parent should now be finished\n        assert!(parent_group.finished());\n        assert!(parent_group.succeeded());\n        assert_eq!(parent_called.load(Ordering::SeqCst), 1);\n        assert!(parent_group.get_error().is_none());\n    }\n\n    #[test]\n    fn test_completion_group_nested_with_error() {\n        use crate::sync::atomic::{AtomicBool, Ordering};\n\n        let parent_called = Arc::new(AtomicBool::new(false));\n        let child_called = Arc::new(AtomicBool::new(false));\n\n        // Create child group with 2 completions\n        let child_called_clone = child_called.clone();\n        let mut child_group = CompletionGroup::new(move |_| {\n            child_called_clone.store(true, Ordering::SeqCst);\n        });\n        let c1 = Completion::new_write(|_| {});\n        let c2 = Completion::new_write(|_| {});\n        child_group.add(&c1);\n        child_group.add(&c2);\n        let child_group = child_group.build();\n\n        // Create parent group containing child group and another completion\n        let parent_called_clone = parent_called.clone();\n        let mut parent_group = CompletionGroup::new(move |_| {\n            parent_called_clone.store(true, Ordering::SeqCst);\n        });\n        let c3 = Completion::new_write(|_| {});\n        parent_group.add(&child_group);\n        parent_group.add(&c3);\n        let parent_group = parent_group.build();\n\n        // Complete child group with success\n        c1.complete(0);\n        c2.complete(0);\n        assert!(child_group.finished());\n        assert!(child_group.succeeded());\n        assert!(child_called.load(Ordering::SeqCst));\n\n        // Parent still pending\n        assert!(!parent_group.finished());\n        assert!(!parent_called.load(Ordering::SeqCst));\n\n        // Complete c3 with error\n        c3.error(CompletionError::Aborted);\n\n        // Parent should finish with error\n        assert!(parent_group.finished());\n        assert!(!parent_group.succeeded());\n        assert_eq!(parent_group.get_error(), Some(CompletionError::Aborted));\n        assert!(parent_called.load(Ordering::SeqCst));\n    }\n\n    // Tests for individual completion success/failure status\n\n    #[test]\n    fn test_write_completion_pending_status() {\n        let c = Completion::new_write(|_| {});\n\n        // Pending completion should not be finished, succeeded, or failed\n        assert!(!c.finished());\n        assert!(!c.succeeded());\n        assert!(!c.failed());\n        assert!(c.get_error().is_none());\n    }\n\n    #[test]\n    fn test_write_completion_success() {\n        let c = Completion::new_write(|_| {});\n\n        c.complete(42);\n\n        assert!(c.finished());\n        assert!(c.succeeded());\n        assert!(!c.failed());\n        assert!(c.get_error().is_none());\n    }\n\n    #[test]\n    fn test_write_completion_failure() {\n        let c = Completion::new_write(|_| {});\n\n        c.error(CompletionError::Aborted);\n\n        assert!(c.finished());\n        assert!(!c.succeeded());\n        assert!(c.failed());\n        assert_eq!(c.get_error(), Some(CompletionError::Aborted));\n    }\n\n    #[test]\n    fn test_read_completion_pending_status() {\n        let buf = Arc::new(crate::Buffer::new_temporary(4096));\n        let c = Completion::new_read(buf, |_| None);\n\n        assert!(!c.finished());\n        assert!(!c.succeeded());\n        assert!(!c.failed());\n        assert!(c.get_error().is_none());\n    }\n\n    #[test]\n    fn test_read_completion_success() {\n        let buf = Arc::new(crate::Buffer::new_temporary(4096));\n        let c = Completion::new_read(buf, |_| None);\n\n        c.complete(1024);\n\n        assert!(c.finished());\n        assert!(c.succeeded());\n        assert!(!c.failed());\n        assert!(c.get_error().is_none());\n    }\n\n    #[test]\n    fn test_read_completion_failure() {\n        let buf = Arc::new(crate::Buffer::new_temporary(4096));\n        let c = Completion::new_read(buf, |_| None);\n\n        c.error(CompletionError::Aborted);\n\n        assert!(c.finished());\n        assert!(!c.succeeded());\n        assert!(c.failed());\n        assert_eq!(c.get_error(), Some(CompletionError::Aborted));\n    }\n\n    #[test]\n    fn test_sync_completion_pending_status() {\n        let c = Completion::new_sync(|_| {});\n\n        assert!(!c.finished());\n        assert!(!c.succeeded());\n        assert!(!c.failed());\n        assert!(c.get_error().is_none());\n    }\n\n    #[test]\n    fn test_sync_completion_success() {\n        let c = Completion::new_sync(|_| {});\n\n        c.complete(0);\n\n        assert!(c.finished());\n        assert!(c.succeeded());\n        assert!(!c.failed());\n        assert!(c.get_error().is_none());\n    }\n\n    #[test]\n    fn test_sync_completion_failure() {\n        let c = Completion::new_sync(|_| {});\n\n        c.error(CompletionError::Aborted);\n\n        assert!(c.finished());\n        assert!(!c.succeeded());\n        assert!(c.failed());\n        assert_eq!(c.get_error(), Some(CompletionError::Aborted));\n    }\n\n    #[test]\n    fn test_truncate_completion_pending_status() {\n        let c = Completion::new_trunc(|_| {});\n\n        assert!(!c.finished());\n        assert!(!c.succeeded());\n        assert!(!c.failed());\n        assert!(c.get_error().is_none());\n    }\n\n    #[test]\n    fn test_truncate_completion_success() {\n        let c = Completion::new_trunc(|_| {});\n\n        c.complete(0);\n\n        assert!(c.finished());\n        assert!(c.succeeded());\n        assert!(!c.failed());\n        assert!(c.get_error().is_none());\n    }\n\n    #[test]\n    fn test_truncate_completion_failure() {\n        let c = Completion::new_trunc(|_| {});\n\n        c.error(CompletionError::Aborted);\n\n        assert!(c.finished());\n        assert!(!c.succeeded());\n        assert!(c.failed());\n        assert_eq!(c.get_error(), Some(CompletionError::Aborted));\n    }\n\n    #[test]\n    fn test_yield_completion_status() {\n        let c = Completion::new_yield();\n\n        // Yield completions are always considered finished and succeeded\n        assert!(c.finished());\n        assert!(c.succeeded());\n        assert!(!c.failed());\n        assert!(c.get_error().is_none());\n    }\n\n    #[test]\n    fn test_completion_abort() {\n        let c = Completion::new_write(|_| {});\n\n        c.abort();\n\n        assert!(c.finished());\n        assert!(!c.succeeded());\n        assert!(c.failed());\n        assert_eq!(c.get_error(), Some(CompletionError::Aborted));\n    }\n\n    #[test]\n    fn test_completion_callback_receives_success_result() {\n        use crate::sync::atomic::{AtomicI32, Ordering};\n\n        let result_value = Arc::new(AtomicI32::new(-1));\n        let result_value_clone = result_value.clone();\n\n        let c = Completion::new_write(move |res| {\n            if let Ok(val) = res {\n                result_value_clone.store(val, Ordering::SeqCst);\n            }\n        });\n\n        c.complete(42);\n\n        assert_eq!(result_value.load(Ordering::SeqCst), 42);\n        assert!(c.succeeded());\n    }\n\n    #[test]\n    fn test_completion_callback_receives_error_result() {\n        use crate::sync::atomic::{AtomicBool, Ordering};\n\n        let got_error = Arc::new(AtomicBool::new(false));\n        let got_error_clone = got_error.clone();\n\n        let c = Completion::new_write(move |res| {\n            if res.is_err() {\n                got_error_clone.store(true, Ordering::SeqCst);\n            }\n        });\n\n        c.error(CompletionError::Aborted);\n\n        assert!(got_error.load(Ordering::SeqCst));\n        assert!(c.failed());\n    }\n\n    #[test]\n    fn test_completion_idempotent_complete() {\n        // Completing a completion multiple times should only trigger the callback once\n        use crate::sync::atomic::{AtomicUsize, Ordering};\n\n        let call_count = Arc::new(AtomicUsize::new(0));\n        let call_count_clone = call_count.clone();\n\n        let c = Completion::new_write(move |_| {\n            call_count_clone.fetch_add(1, Ordering::SeqCst);\n        });\n\n        c.complete(1);\n        c.complete(2);\n        c.complete(3);\n\n        // Callback should only be called once\n        assert_eq!(call_count.load(Ordering::SeqCst), 1);\n        assert!(c.succeeded());\n    }\n\n    #[test]\n    fn test_completion_idempotent_error() {\n        // Erroring a completion multiple times should only trigger the callback once\n        use crate::sync::atomic::{AtomicUsize, Ordering};\n\n        let call_count = Arc::new(AtomicUsize::new(0));\n        let call_count_clone = call_count.clone();\n\n        let c = Completion::new_write(move |_| {\n            call_count_clone.fetch_add(1, Ordering::SeqCst);\n        });\n\n        c.error(CompletionError::Aborted);\n        c.error(CompletionError::Aborted);\n        c.complete(0); // Try completing after error\n\n        // Callback should only be called once\n        assert_eq!(call_count.load(Ordering::SeqCst), 1);\n        assert!(c.failed());\n    }\n}\n"
  },
  {
    "path": "core/io/generic.rs",
    "content": "use crate::error::io_error;\nuse crate::io::clock::{DefaultClock, MonotonicInstant, WallClockInstant};\nuse crate::{Clock, Completion, File, OpenFlags, Result, IO};\nuse crate::sync::RwLock;\nuse std::io::{Read, Seek, Write};\nuse crate::sync::Arc;\nuse tracing::{debug, instrument, trace, Level};\npub struct GenericIO {}\n\nimpl GenericIO {\n    pub fn new() -> Result<Self> {\n        debug!(\"Using IO backend 'syscall'\");\n        Ok(Self {})\n    }\n}\n\nimpl IO for GenericIO {\n    #[instrument(skip_all, level = Level::TRACE)]\n    fn open_file(&self, path: &str, flags: OpenFlags, direct: bool) -> Result<Arc<dyn File>> {\n        trace!(\"open_file(path = {})\", path);\n        let mut file = std::fs::File::options();\n        file.read(true);\n\n        if !flags.contains(OpenFlags::ReadOnly) {\n            file.write(true);\n            file.create(flags.contains(OpenFlags::Create));\n        }\n\n        let file = file.open(path).map_err(|e| io_error(e, \"open\"))?;\n        Ok(Arc::new(GenericFile {\n            file: RwLock::new(file),\n        }))\n    }\n\n    #[instrument(err, skip_all, level = Level::TRACE)]\n    fn remove_file(&self, path: &str) -> Result<()> {\n        trace!(\"remove_file(path = {})\", path);\n        std::fs::remove_file(path).map_err(|e| io_error(e, \"remove_file\"))?;\n        Ok(())\n    }\n\n    #[instrument(err, skip_all, level = Level::TRACE)]\n    fn step(&self) -> Result<()> {\n        Ok(())\n    }\n}\n\nimpl Clock for GenericIO {\n    fn current_time_monotonic(&self) -> MonotonicInstant {\n        DefaultClock.current_time_monotonic()\n    }\n\n    fn current_time_wall_clock(&self) -> WallClockInstant {\n        DefaultClock.current_time_wall_clock()\n    }\n}\n\npub struct GenericFile {\n    file: RwLock<std::fs::File>,\n}\n\nimpl File for GenericFile {\n    #[instrument(err, skip_all, level = Level::TRACE)]\n    fn lock_file(&self, exclusive: bool) -> Result<()> {\n        Ok(())\n    }\n\n    #[instrument(err, skip_all, level = Level::TRACE)]\n    fn unlock_file(&self) -> Result<()> {\n        Ok(())\n    }\n\n    #[instrument(skip(self, c), level = Level::TRACE)]\n    fn pread(&self, pos: u64, c: Completion) -> Result<Completion> {\n        let mut file = self.file.write();\n        file.seek(std::io::SeekFrom::Start(pos)).map_err(|e| io_error(e, \"pread\"))?;\n        let nr = {\n            let r = c.as_read();\n            let buf = r.buf();\n            let buf = buf.as_mut_slice();\n            file.read(buf).map_err(|e| io_error(e, \"pread\"))? as i32\n        };\n        c.complete(nr);\n        Ok(c)\n    }\n\n    #[instrument(skip(self, c, buffer), level = Level::TRACE)]\n    fn pwrite(&self, pos: u64, buffer: Arc<crate::Buffer>, c: Completion) -> Result<Completion> {\n        let mut file = self.file.write();\n        file.seek(std::io::SeekFrom::Start(pos)).map_err(|e| io_error(e, \"pwrite\"))?;\n        let buf = buffer.as_slice();\n        file.write_all(buf).map_err(|e| io_error(e, \"pwrite\"))?;\n        c.complete(buffer.len() as i32);\n        Ok(c)\n    }\n\n    #[instrument(err, skip_all, level = Level::TRACE)]\n    fn sync(&self, c: Completion, _sync_type: crate::io::FileSyncType) -> Result<Completion> {\n        let file = self.file.write();\n        file.sync_all().map_err(|e| io_error(e, \"sync\"))?;\n        c.complete(0);\n        Ok(c)\n    }\n\n    #[instrument(err, skip_all, level = Level::TRACE)]\n    fn truncate(&self, len: u64, c: Completion) -> Result<Completion> {\n        let file = self.file.write();\n        file.set_len(len).map_err(|e| io_error(e, \"truncate\"))?;\n        c.complete(0);\n        Ok(c)\n    }\n\n    fn size(&self) -> Result<u64> {\n        let file = self.file.read();\n        Ok(file.metadata().map_err(|e| io_error(e, \"metadata\"))?.len())\n    }\n}\n"
  },
  {
    "path": "core/io/io_uring.rs",
    "content": "#![allow(clippy::arc_with_non_send_sync)]\n\nuse super::{common, Completion, CompletionInner, File, OpenFlags, IO};\nuse crate::io::clock::{Clock, DefaultClock, MonotonicInstant, WallClockInstant};\nuse crate::storage::wal::CKPT_BATCH_PAGES;\nuse crate::sync::Mutex;\nuse crate::turso_assert;\nuse crate::error::io_error;\nuse crate::{CompletionError, LimboError, Result};\nuse rustix::fs::{self, FlockOperation, OFlags};\nuse std::ptr::NonNull;\nuse std::{\n    collections::{HashMap, VecDeque},\n    io::ErrorKind,\n    ops::Deref,\n    os::{fd::AsFd, unix::io::AsRawFd},\n    sync::Arc,\n};\nuse tracing::{debug, trace, warn};\n\n/// Size of the io_uring submission and completion queues\nconst ENTRIES: u32 = 512;\n\n/// Idle timeout for the sqpoll kernel thread before it needs\n/// to be woken back up by a call IORING_ENTER_SQ_WAKEUP flag.\n/// (handled by the io_uring crate in `submit_and_wait`)\nconst SQPOLL_IDLE: u32 = 1000;\n\n/// Number of file descriptors we preallocate for io_uring.\n/// NOTE: we may need to increase this when `attach` is fully implemented.\nconst FILES: u32 = 8;\n\n/// Number of Vec<Box<[iovec]>> we preallocate on initialization\nconst IOVEC_POOL_SIZE: usize = 64;\n\n/// Maximum number of iovec entries per writev operation.\n/// IOV_MAX is typically 1024\nconst MAX_IOVEC_ENTRIES: usize = CKPT_BATCH_PAGES;\n\n/// Maximum number of I/O operations to wait for in a single run,\n/// waiting for > 1 can reduce the amount of `io_uring_enter` syscalls we\n/// make, but can increase single operation latency.\nconst MAX_WAIT: usize = 4;\n\n/// One memory arena for DB pages and another for WAL frames\nconst ARENA_COUNT: usize = 2;\n\n/// user_data tag for cancellation operations\nconst CANCEL_TAG: u64 = 1;\n\n/// Probed io_uring opcode support. Opcodes that are not supported by the\n/// running kernel fall back to synchronous POSIX syscalls.\nstruct UringCapabilities {\n    ftruncate: bool,\n}\n\npub struct UringIO {\n    inner: Arc<Mutex<InnerUringIO>>,\n    caps: Arc<UringCapabilities>,\n}\n\nunsafe impl Send for UringIO {}\nunsafe impl Sync for UringIO {}\ncrate::assert::assert_send_sync!(UringIO);\n\nstruct WrappedIOUring {\n    ring: io_uring::IoUring,\n    pending_ops: usize,\n    writev_states: HashMap<u64, WritevState>,\n    overflow: VecDeque<io_uring::squeue::Entry>,\n    iov_pool: IovecPool,\n}\n\nstruct InnerUringIO {\n    ring: WrappedIOUring,\n    free_files: VecDeque<u32>,\n    free_arenas: [Option<(NonNull<u8>, usize)>; ARENA_COUNT],\n}\n\n/// preallocated vec of iovec arrays to avoid allocations during writev operations\nstruct IovecPool {\n    pool: Vec<Box<[libc::iovec; MAX_IOVEC_ENTRIES]>>,\n}\n\nimpl IovecPool {\n    fn new() -> Self {\n        let pool = (0..IOVEC_POOL_SIZE)\n            .map(|_| {\n                Box::new(\n                    [libc::iovec {\n                        iov_base: std::ptr::null_mut(),\n                        iov_len: 0,\n                    }; MAX_IOVEC_ENTRIES],\n                )\n            })\n            .collect();\n        Self { pool }\n    }\n\n    #[inline(always)]\n    fn acquire(&mut self) -> Option<Box<[libc::iovec; MAX_IOVEC_ENTRIES]>> {\n        self.pool.pop()\n    }\n\n    #[inline(always)]\n    fn release(&mut self, iovec: Box<[libc::iovec; MAX_IOVEC_ENTRIES]>) {\n        if self.pool.len() < IOVEC_POOL_SIZE {\n            self.pool.push(iovec);\n        }\n    }\n}\n\nimpl UringIO {\n    pub fn new() -> Result<Self> {\n        let ring = match io_uring::IoUring::builder()\n            .setup_sqpoll(SQPOLL_IDLE)\n            .build(ENTRIES)\n        {\n            Ok(ring) => ring,\n            Err(_) => io_uring::IoUring::new(ENTRIES).map_err(|e| io_error(e, \"io_uring_setup\"))?,\n        };\n        // we only ever have 2 files open at a time for the moment\n        ring.submitter().register_files_sparse(FILES).map_err(|e| io_error(e, \"register_files\"))?;\n        // RL_MEMLOCK cap is typically 8MB, the current design is to have one large arena\n        // registered at startup and therefore we can simply use the zero index, falling back\n        // to similar logic as the existing buffer pool for cases where it is over capacity.\n        ring.submitter()\n            .register_buffers_sparse(ARENA_COUNT as u32).map_err(|e| io_error(e, \"register_buffers\"))?;\n        // Probe supported opcodes so we can fall back to POSIX for unsupported ones.\n        let mut probe = io_uring::register::Probe::new();\n        let caps = if ring.submitter().register_probe(&mut probe).is_ok() {\n            UringCapabilities {\n                ftruncate: probe.is_supported(io_uring::opcode::Ftruncate::CODE),\n            }\n        } else {\n            UringCapabilities { ftruncate: false }\n        };\n        if !caps.ftruncate {\n            warn!(\"io_uring: IORING_OP_FTRUNCATE not supported by kernel, using POSIX fallback\");\n        }\n        let inner = InnerUringIO {\n            ring: WrappedIOUring {\n                ring,\n                overflow: VecDeque::new(),\n                pending_ops: 0,\n                writev_states: HashMap::default(),\n                iov_pool: IovecPool::new(),\n            },\n            free_files: (0..FILES).collect(),\n            free_arenas: [const { None }; ARENA_COUNT],\n        };\n        debug!(\"Using IO backend 'io-uring'\");\n        Ok(Self {\n            inner: Arc::new(Mutex::new(inner)),\n            caps: Arc::new(caps),\n        })\n    }\n}\n\n/// io_uring crate decides not to export their `UseFixed` trait, so we\n/// are forced to use a macro here to handle either fixed or raw file descriptors.\nmacro_rules! with_fd {\n    ($file:expr, |$fd:ident| $body:expr) => {\n        match $file.id() {\n            Some(id) => {\n                let $fd = io_uring::types::Fixed(id);\n                $body\n            }\n            None => {\n                let $fd = io_uring::types::Fd($file.as_raw_fd());\n                $body\n            }\n        }\n    };\n}\n\n/// wrapper type to represent a possibly registered file descriptor,\n/// only used in WritevState, and piggy-backs on the available methods from\n/// `UringFile`, so we don't have to store the file on `WritevState`.\n#[derive(Clone)]\nenum Fd {\n    Fixed(u32),\n    RawFd(i32),\n}\n\nimpl Fd {\n    /// to match the behavior of the File, we need to implement the same methods\n    fn id(&self) -> Option<u32> {\n        match self {\n            Fd::Fixed(id) => Some(*id),\n            Fd::RawFd(_) => None,\n        }\n    }\n    /// ONLY to be called by the macro, in the case where id() is None\n    fn as_raw_fd(&self) -> i32 {\n        match self {\n            Fd::RawFd(fd) => *fd,\n            _ => panic!(\"Cannot call as_raw_fd on a Fixed Fd\"),\n        }\n    }\n}\n\n/// State to track an ongoing writev operation in\n/// the case of a partial write.\nstruct WritevState {\n    /// File descriptor/id of the file we are writing to\n    file_id: Fd,\n    /// absolute file offset for next submit\n    file_pos: u64,\n    /// current buffer index in `bufs`\n    current_buffer_idx: usize,\n    /// intra-buffer offset\n    current_buffer_offset: usize,\n    /// total bytes written so far\n    total_written: usize,\n    /// cache the sum of all buffer lengths for the total expected write\n    total_len: usize,\n    /// buffers to write\n    bufs: Vec<Arc<crate::Buffer>>,\n    /// we keep the last iovec allocation alive until final CQE\n    last_iov_allocation: Option<Box<[libc::iovec; MAX_IOVEC_ENTRIES]>>,\n}\n\nimpl WritevState {\n    fn new(file: &UringFile, pos: u64, bufs: Vec<Arc<crate::Buffer>>) -> Self {\n        let file_id = file\n            .id()\n            .map(Fd::Fixed)\n            .unwrap_or_else(|| Fd::RawFd(file.as_raw_fd()));\n        let total_len = bufs.iter().map(|b| b.len()).sum();\n        Self {\n            file_id,\n            file_pos: pos,\n            current_buffer_idx: 0,\n            current_buffer_offset: 0,\n            total_written: 0,\n            bufs,\n            last_iov_allocation: None,\n            total_len,\n        }\n    }\n\n    #[inline(always)]\n    fn remaining(&self) -> usize {\n        self.total_len - self.total_written\n    }\n\n    /// Advance (idx, off, pos) after written bytes\n    #[inline(always)]\n    fn advance(&mut self, written: u64) {\n        let mut remaining = written;\n        while remaining > 0 {\n            let current_buf_len = self.bufs[self.current_buffer_idx].len();\n            let left = current_buf_len - self.current_buffer_offset;\n            if remaining < left as u64 {\n                self.current_buffer_offset += remaining as usize;\n                self.file_pos += remaining;\n                remaining = 0;\n            } else {\n                remaining -= left as u64;\n                self.file_pos += left as u64;\n                self.current_buffer_idx += 1;\n                self.current_buffer_offset = 0;\n            }\n        }\n        self.total_written += written as usize;\n    }\n\n    #[inline(always)]\n    /// Free the allocation that keeps the iovec array alive while writev is ongoing\n    fn free_last_iov(&mut self, pool: &mut IovecPool) {\n        if let Some(allocation) = self.last_iov_allocation.take() {\n            pool.release(allocation);\n        }\n    }\n}\n\nimpl InnerUringIO {\n    fn register_file(&mut self, fd: i32) -> Result<u32> {\n        if let Some(slot) = self.free_files.pop_front() {\n            self.ring\n                .ring\n                .submitter()\n                .register_files_update(slot, &[fd.as_raw_fd()]).map_err(|e| io_error(e, \"register_files_update\"))?;\n            return Ok(slot);\n        }\n        Err(crate::error::CompletionError::UringIOError(\n            \"unable to register file, no free slots available\",\n        )\n        .into())\n    }\n    fn unregister_file(&mut self, id: u32) -> Result<()> {\n        self.ring\n            .ring\n            .submitter()\n            .register_files_update(id, &[-1]).map_err(|e| io_error(e, \"register_files_update\"))?;\n        self.free_files.push_back(id);\n        Ok(())\n    }\n\n    #[cfg(debug_assertions)]\n    fn debug_check_fixed(&self, idx: u32, ptr: *const u8, len: usize) {\n        let (base, blen) = self.free_arenas[idx as usize].expect(\"slot not registered\");\n        let start = base.as_ptr() as usize;\n        let end = start + blen;\n        let p = ptr as usize;\n        turso_assert!(\n            p >= start && p + len <= end,\n            \"Fixed operation, pointer out of registered range\"\n        );\n    }\n}\n\nimpl WrappedIOUring {\n    fn submit_entry(&mut self, entry: &io_uring::squeue::Entry) {\n        trace!(\"submit_entry({:?})\", entry);\n        // we cannot push current entries before any overflow\n        if self.flush_overflow().is_ok() {\n            let pushed = unsafe {\n                let mut sub = self.ring.submission();\n                sub.push(entry).is_ok()\n            };\n            if pushed {\n                self.pending_ops += 1;\n                return;\n            }\n        }\n        // if we were unable to push, add to overflow\n        self.overflow.push_back(entry.clone());\n        self.ring.submit().expect(\"submitting when full\");\n    }\n\n    fn submit_cancel_urgent(&mut self, entry: &io_uring::squeue::Entry) -> Result<()> {\n        let pushed = unsafe { self.ring.submission().push(entry).is_ok() };\n        if pushed {\n            self.pending_ops += 1;\n            return Ok(());\n        }\n        // place cancel op at the front, if overflowed\n        self.overflow.push_front(entry.clone());\n        self.ring.submit().map_err(|e| io_error(e, \"io_uring_submit\"))?;\n        Ok(())\n    }\n\n    /// Flush overflow entries to submission queue when possible\n    fn flush_overflow(&mut self) -> Result<()> {\n        if self.overflow.is_empty() {\n            return Ok(());\n        }\n        // Best-effort: push as many overflow entries as the submission queue currently has space\n        // for. If the SQ is full, leave the remaining entries in `overflow` to preserve ordering\n        // and let the caller make progress (submit/wait and process CQEs) before retrying.\n        unsafe {\n            let mut sq = self.ring.submission();\n            while !self.overflow.is_empty() {\n                if sq.is_full() {\n                    break;\n                }\n                let entry = self.overflow.pop_front().expect(\"checked not empty\");\n                if sq.push(&entry).is_err() {\n                    // SQ state may have changed; keep the entry and retry later.\n                    self.overflow.push_front(entry);\n                    break;\n                }\n                self.pending_ops += 1;\n            }\n        }\n        Ok(())\n    }\n\n    fn submit_and_wait(&mut self) -> Result<()> {\n        if self.empty() {\n            return Ok(());\n        }\n        let wants = std::cmp::min(self.pending_ops, MAX_WAIT);\n        tracing::trace!(\"submit_and_wait for {wants} pending operations to complete\");\n        self.ring.submit_and_wait(wants).map_err(|e| io_error(e, \"io_uring_submit_and_wait\"))?;\n        Ok(())\n    }\n\n    fn empty(&self) -> bool {\n        self.pending_ops == 0 && self.overflow.is_empty()\n    }\n\n    /// Submit or resubmit a writev operation\n    fn submit_writev(&mut self, key: u64, mut st: WritevState) {\n        st.free_last_iov(&mut self.iov_pool);\n\n        let mut iov_allocation = self.iov_pool.acquire().unwrap_or_else(|| {\n            Box::new(\n                [libc::iovec {\n                    iov_base: std::ptr::null_mut(),\n                    iov_len: 0,\n                }; MAX_IOVEC_ENTRIES],\n            )\n        });\n\n        let mut iov_count = 0;\n        let mut last_end: Option<(*const u8, usize)> = None;\n\n        for (idx, buffer) in st.bufs.iter().enumerate().skip(st.current_buffer_idx) {\n            let mut ptr = buffer.as_ptr();\n            let mut len = buffer.len();\n            // advance intra-buffer offset if resubmitting\n            if idx == st.current_buffer_idx && st.current_buffer_offset != 0 {\n                turso_assert!(\n                    st.current_buffer_offset <= len,\n                    \"writev state offset out of bounds\"\n                );\n                ptr = unsafe { ptr.add(st.current_buffer_offset) };\n                len -= st.current_buffer_offset;\n            }\n            if let Some((last_ptr, last_len)) = last_end {\n                // Check if this buffer is adjacent to the last\n                if unsafe { last_ptr.add(last_len) } == ptr {\n                    iov_allocation[iov_count - 1].iov_len += len;\n                    last_end = Some((last_ptr, last_len + len));\n                    continue;\n                }\n            }\n            iov_allocation[iov_count] = libc::iovec {\n                iov_base: ptr as *mut _,\n                iov_len: len,\n            };\n            last_end = Some((ptr, len));\n            iov_count += 1;\n            if iov_count >= MAX_IOVEC_ENTRIES {\n                break;\n            }\n        }\n\n        let ptr = iov_allocation.as_ptr() as *mut libc::iovec;\n        st.last_iov_allocation = Some(iov_allocation);\n        let entry = with_fd!(st.file_id, |fd| {\n            io_uring::opcode::Writev::new(fd, ptr, iov_count as u32)\n                .offset(st.file_pos)\n                .build()\n                .user_data(key)\n        });\n        self.writev_states.insert(key, st);\n        self.submit_entry(&entry);\n    }\n\n    fn handle_writev_completion(&mut self, mut state: WritevState, user_data: u64, result: i32) {\n        if result < 0 {\n            let err = std::io::Error::from_raw_os_error(-result);\n            tracing::error!(\"writev failed (user_data: {}): {}\", user_data, err);\n            state.free_last_iov(&mut self.iov_pool);\n            completion_from_key(user_data).error(CompletionError::IOError(err.kind(), \"pwritev\"));\n            return;\n        }\n\n        let written = result;\n\n        // guard against no-progress loop\n        if written == 0 && state.remaining() > 0 {\n            state.free_last_iov(&mut self.iov_pool);\n            completion_from_key(user_data).error(CompletionError::ShortWrite);\n            return;\n        }\n        state.advance(written as u64);\n\n        match state.remaining() {\n            0 => {\n                tracing::debug!(\n                    \"writev operation completed: wrote {} bytes\",\n                    state.total_written\n                );\n                // write complete, return iovec to pool\n                state.free_last_iov(&mut self.iov_pool);\n                completion_from_key(user_data).complete(state.total_written as i32);\n            }\n            remaining => {\n                tracing::trace!(\n                    \"resubmitting writev operation for user_data {}: wrote {} bytes, remaining {}\",\n                    user_data,\n                    written,\n                    remaining\n                );\n                self.submit_writev(user_data, state);\n            }\n        }\n    }\n}\n\nimpl IO for UringIO {\n    fn open_file(&self, path: &str, flags: OpenFlags, direct: bool) -> Result<Arc<dyn File>> {\n        trace!(\"open_file(path = {})\", path);\n        let mut file = std::fs::File::options();\n        file.read(true);\n\n        if !flags.contains(OpenFlags::ReadOnly) {\n            file.write(true);\n            file.create(flags.contains(OpenFlags::Create));\n        }\n\n        let file = file.open(path).map_err(|e| io_error(e, \"open\"))?;\n        // Let's attempt to enable direct I/O. Not all filesystems support it\n        // so ignore any errors.\n        let fd = file.as_fd();\n        if direct {\n            match fs::fcntl_setfl(fd, OFlags::DIRECT) {\n                Ok(_) => {}\n                Err(error) => debug!(\"Error {error:?} returned when setting O_DIRECT flag to read file. The performance of the system may be affected\"),\n            }\n        }\n        let id = self.inner.lock().register_file(file.as_raw_fd()).ok();\n        let uring_file = Arc::new(UringFile {\n            io: self.inner.clone(),\n            caps: self.caps.clone(),\n            file,\n            id,\n        });\n        if std::env::var(common::ENV_DISABLE_FILE_LOCK).is_err()\n            || !flags.contains(OpenFlags::ReadOnly)\n        {\n            uring_file.lock_file(true)?;\n        }\n        Ok(uring_file)\n    }\n\n    fn remove_file(&self, path: &str) -> Result<()> {\n        std::fs::remove_file(path).map_err(|e| io_error(e, \"remove_file\"))?;\n        Ok(())\n    }\n\n    /// Drain calls `run_once` in a loop until the ring is empty.\n    /// To prevent mutex churn of checking if ring.empty() on each iteration, we violate DRY\n    fn drain(&self) -> Result<()> {\n        trace!(\"drain()\");\n        let mut inner = self.inner.lock();\n        let ring = &mut inner.ring;\n        loop {\n            ring.flush_overflow()?;\n            if ring.empty() {\n                return Ok(());\n            }\n            ring.submit_and_wait()?;\n            'inner: loop {\n                let mut cq = ring.ring.completion();\n                let Some(cqe) = cq.next() else {\n                    break 'inner;\n                };\n                ring.pending_ops -= 1;\n                let user_data = cqe.user_data();\n                if user_data == CANCEL_TAG {\n                    // ignore if this is a cancellation CQE,\n                    continue 'inner;\n                }\n                let result = cqe.result();\n                turso_assert!(\n                user_data != 0,\n                \"user_data must not be zero, we dont submit linked timeouts that would cause this\"\n            );\n                if let Some(state) = ring.writev_states.remove(&user_data) {\n                    // if we have ongoing writev state, handle it separately and don't call completion\n                    drop(cq);\n                    ring.handle_writev_completion(state, user_data, result);\n                    continue 'inner;\n                }\n                if result < 0 {\n                    let errno = -result;\n                    let err = std::io::Error::from_raw_os_error(errno);\n                    completion_from_key(user_data).error(CompletionError::IOError(err.kind(), \"io_uring_cqe\"));\n                } else {\n                    completion_from_key(user_data).complete(result)\n                }\n            }\n        }\n    }\n\n    fn cancel(&self, completions: &[Completion]) -> Result<()> {\n        let mut inner = self.inner.lock();\n        for c in completions {\n            c.abort();\n            // dont want to leak the refcount bump with `get_key`/into_raw here, so we use as_ptr\n            let e = io_uring::opcode::AsyncCancel::new(Arc::as_ptr(c.get_inner()) as u64)\n                .build()\n                .user_data(CANCEL_TAG);\n            inner.ring.submit_cancel_urgent(&e)?;\n        }\n        Ok(())\n    }\n\n    fn step(&self) -> Result<()> {\n        let mut inner = self.inner.lock();\n        let ring = &mut inner.ring;\n        ring.flush_overflow()?;\n        if ring.empty() {\n            return Ok(());\n        }\n        ring.submit_and_wait()?;\n        loop {\n            let mut cq = ring.ring.completion();\n            let Some(cqe) = cq.next() else {\n                return Ok(());\n            };\n            ring.pending_ops -= 1;\n            let user_data = cqe.user_data();\n            if user_data == CANCEL_TAG {\n                // ignore if this is a cancellation CQE\n                continue;\n            }\n            let result = cqe.result();\n            turso_assert!(\n                user_data != 0,\n                \"user_data must not be zero, we dont submit linked timeouts that would cause this\"\n            );\n            if let Some(state) = ring.writev_states.remove(&user_data) {\n                drop(cq);\n                // if we have ongoing writev state, handle it separately and don't call completion\n                ring.handle_writev_completion(state, user_data, result);\n                continue;\n            }\n            if result < 0 {\n                let errno = -result;\n                let err = std::io::Error::from_raw_os_error(errno);\n                completion_from_key(user_data).error(CompletionError::IOError(err.kind(), \"io_uring_cqe\"));\n            } else {\n                completion_from_key(user_data).complete(result)\n            }\n        }\n    }\n\n    fn register_fixed_buffer(&self, ptr: std::ptr::NonNull<u8>, len: usize) -> Result<u32> {\n        turso_assert!(\n            len % 512 == 0,\n            \"fixed buffer length must be logical block aligned\"\n        );\n        let mut inner = self.inner.lock();\n        let slot =\n            inner.free_arenas.iter().position(|e| e.is_none()).ok_or({\n                crate::error::CompletionError::UringIOError(\"no free fixed buffer slots\")\n            })?;\n        unsafe {\n            inner.ring.ring.submitter().register_buffers_update(\n                slot as u32,\n                &[libc::iovec {\n                    iov_base: ptr.as_ptr() as *mut libc::c_void,\n                    iov_len: len,\n                }],\n                None,\n            ).map_err(|e| io_error(e, \"register_buffers_update\"))?\n        };\n        inner.free_arenas[slot] = Some((ptr, len));\n        Ok(slot as u32)\n    }\n}\n\nimpl Clock for UringIO {\n    fn current_time_monotonic(&self) -> MonotonicInstant {\n        DefaultClock.current_time_monotonic()\n    }\n\n    fn current_time_wall_clock(&self) -> WallClockInstant {\n        DefaultClock.current_time_wall_clock()\n    }\n}\n\n#[inline(always)]\n/// use the callback pointer as the user_data for the operation as is\n/// common practice for io_uring to prevent more indirection\nfn get_key(c: Completion) -> u64 {\n    Arc::into_raw(c.get_inner().clone()) as u64\n}\n\n#[inline(always)]\n/// convert the user_data back to an Completion pointer\nfn completion_from_key(key: u64) -> Completion {\n    let c_inner = unsafe { Arc::from_raw(key as *const CompletionInner) };\n    Completion {\n        inner: Some(c_inner),\n    }\n}\n\npub struct UringFile {\n    io: Arc<Mutex<InnerUringIO>>,\n    caps: Arc<UringCapabilities>,\n    file: std::fs::File,\n    id: Option<u32>,\n}\n\nimpl Deref for UringFile {\n    type Target = std::fs::File;\n    fn deref(&self) -> &Self::Target {\n        &self.file\n    }\n}\n\nimpl UringFile {\n    fn id(&self) -> Option<u32> {\n        self.id\n    }\n}\nunsafe impl Send for UringFile {}\nunsafe impl Sync for UringFile {}\ncrate::assert::assert_send_sync!(UringFile);\n\nimpl File for UringFile {\n    fn lock_file(&self, exclusive: bool) -> Result<()> {\n        let fd = self.file.as_fd();\n        // F_SETLK is a non-blocking lock. The lock will be released when the file is closed\n        // or the process exits or after an explicit unlock.\n        fs::fcntl_lock(\n            fd,\n            if exclusive {\n                FlockOperation::NonBlockingLockExclusive\n            } else {\n                FlockOperation::NonBlockingLockShared\n            },\n        )\n        .map_err(|e| {\n            let io_error = std::io::Error::from(e);\n            let message = match io_error.kind() {\n                ErrorKind::WouldBlock => {\n                    \"Failed locking file. File is locked by another process\".to_string()\n                }\n                _ => format!(\"Failed locking file, {io_error}\"),\n            };\n            LimboError::LockingError(message)\n        })?;\n\n        Ok(())\n    }\n\n    fn unlock_file(&self) -> Result<()> {\n        let fd = self.file.as_fd();\n        fs::fcntl_lock(fd, FlockOperation::NonBlockingUnlock).map_err(|e| {\n            LimboError::LockingError(format!(\n                \"Failed to release file lock: {}\",\n                std::io::Error::from(e)\n            ))\n        })?;\n        Ok(())\n    }\n\n    fn pread(&self, pos: u64, c: Completion) -> Result<Completion> {\n        let r = c.as_read();\n        let read_e = {\n            let buf = r.buf();\n            let ptr = buf.as_mut_ptr();\n            let len = buf.len();\n            with_fd!(self, |fd| {\n                if let Some(idx) = buf.fixed_id() {\n                    trace!(\n                        \"pread_fixed(pos = {}, length = {}, idx = {})\",\n                        pos,\n                        len,\n                        idx\n                    );\n                    #[cfg(debug_assertions)]\n                    {\n                        self.io.lock().debug_check_fixed(idx, ptr, len);\n                    }\n                    io_uring::opcode::ReadFixed::new(fd, ptr, len as u32, idx as u16)\n                        .offset(pos)\n                        .build()\n                        .user_data(get_key(c.clone()))\n                } else {\n                    trace!(\"pread(pos = {}, length = {})\", pos, len);\n                    // Use Read opcode if fixed buffer is not available\n                    io_uring::opcode::Read::new(fd, buf.as_mut_ptr(), len as u32)\n                        .offset(pos)\n                        .build()\n                        .user_data(get_key(c.clone()))\n                }\n            })\n        };\n        self.io.lock().ring.submit_entry(&read_e);\n        Ok(c)\n    }\n\n    fn pwrite(&self, pos: u64, buffer: Arc<crate::Buffer>, c: Completion) -> Result<Completion> {\n        let mut io = self.io.lock();\n        let write = {\n            let ptr = buffer.as_ptr();\n            let len = buffer.len();\n            with_fd!(self, |fd| {\n                if let Some(idx) = buffer.fixed_id() {\n                    trace!(\n                        \"pwrite_fixed(pos = {}, length = {}, idx= {})\",\n                        pos,\n                        len,\n                        idx\n                    );\n                    #[cfg(debug_assertions)]\n                    {\n                        io.debug_check_fixed(idx, ptr, len);\n                    }\n                    io_uring::opcode::WriteFixed::new(fd, ptr, len as u32, idx as u16)\n                        .offset(pos)\n                        .build()\n                        .user_data(get_key(c.clone()))\n                } else {\n                    trace!(\"pwrite(pos = {}, length = {})\", pos, buffer.len());\n                    io_uring::opcode::Write::new(fd, ptr, len as u32)\n                        .offset(pos)\n                        .build()\n                        .user_data(get_key(c.clone()))\n                }\n            })\n        };\n\n        // Keep the buffer alive until the completion is processed. For non-fixed\n        // buffers the SQE holds a raw pointer; without this the Arc would drop\n        // here and the kernel could read freed memory.\n        c.keep_write_buffer_alive(buffer);\n        io.ring.submit_entry(&write);\n        Ok(c)\n    }\n\n    fn sync(&self, c: Completion, _sync_type: crate::io::FileSyncType) -> Result<Completion> {\n        trace!(\"sync()\");\n        let sync = with_fd!(self, |fd| {\n            io_uring::opcode::Fsync::new(fd)\n                .build()\n                .user_data(get_key(c.clone()))\n        });\n        self.io.lock().ring.submit_entry(&sync);\n        Ok(c)\n    }\n\n    fn pwritev(\n        &self,\n        pos: u64,\n        bufs: Vec<Arc<crate::Buffer>>,\n        c: Completion,\n    ) -> Result<Completion> {\n        tracing::trace!(\"pwritev(pos = {}, bufs.len() = {})\", pos, bufs.len());\n\n        let state = WritevState::new(self, pos, bufs);\n        let mut io = self.io.lock();\n        io.ring.submit_writev(get_key(c.clone()), state);\n        Ok(c)\n    }\n\n    fn size(&self) -> Result<u64> {\n        Ok(self.file.metadata().map_err(|e| io_error(e, \"metadata\"))?.len())\n    }\n\n    fn truncate(&self, len: u64, c: Completion) -> Result<Completion> {\n        if self.caps.ftruncate {\n            let truncate = with_fd!(self, |fd| {\n                io_uring::opcode::Ftruncate::new(fd, len)\n                    .build()\n                    .user_data(get_key(c.clone()))\n            });\n            self.io.lock().ring.submit_entry(&truncate);\n            Ok(c)\n        } else {\n            let result = self.file.set_len(len);\n            match result {\n                Ok(()) => {\n                    trace!(\"file truncated to len=({})\", len);\n                    c.complete(0);\n                    Ok(c)\n                }\n                Err(e) => Err(io_error(e, \"truncate\")),\n            }\n        }\n    }\n}\n\nimpl Drop for UringFile {\n    fn drop(&mut self) {\n        self.unlock_file().expect(\"Failed to unlock file\");\n        if let Some(id) = self.id {\n            self.io\n                .lock()\n                .unregister_file(id)\n                .inspect_err(|e| {\n                    debug!(\"Failed to unregister file: {e}\");\n                })\n                .ok();\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::io::common;\n\n    #[test]\n    fn test_multiple_processes_cannot_open_file() {\n        common::tests::test_multiple_processes_cannot_open_file(UringIO::new);\n    }\n}\n"
  },
  {
    "path": "core/io/memory.rs",
    "content": "use super::{Buffer, Clock, Completion, File, OpenFlags, IO};\nuse crate::io::clock::{DefaultClock, MonotonicInstant, WallClockInstant};\nuse crate::io::FileSyncType;\nuse crate::sync::Mutex;\nuse crate::turso_assert;\nuse crate::Result;\nuse std::{\n    cell::{Cell, UnsafeCell},\n    collections::{BTreeMap, HashMap},\n    sync::Arc,\n};\nuse tracing::debug;\n\npub struct MemoryIO {\n    files: Arc<Mutex<HashMap<String, Arc<MemoryFile>>>>,\n}\n\n// TODO: page size flag\nconst PAGE_SIZE: usize = 4096;\ntype MemPage = Box<[u8; PAGE_SIZE]>;\n\nimpl MemoryIO {\n    #[allow(clippy::arc_with_non_send_sync)]\n    pub fn new() -> Self {\n        debug!(\"Using IO backend 'memory'\");\n        Self {\n            files: Arc::new(Mutex::new(HashMap::default())),\n        }\n    }\n}\n\nimpl Default for MemoryIO {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl Clock for MemoryIO {\n    fn current_time_monotonic(&self) -> MonotonicInstant {\n        DefaultClock.current_time_monotonic()\n    }\n\n    fn current_time_wall_clock(&self) -> WallClockInstant {\n        DefaultClock.current_time_wall_clock()\n    }\n}\n\nimpl IO for MemoryIO {\n    fn open_file(&self, path: &str, flags: OpenFlags, _direct: bool) -> Result<Arc<dyn File>> {\n        let mut files = self.files.lock();\n        if !files.contains_key(path) && !flags.contains(OpenFlags::Create) {\n            return Err(crate::error::CompletionError::IOError(\n                std::io::ErrorKind::NotFound,\n                \"open\",\n            )\n            .into());\n        }\n        if !files.contains_key(path) {\n            files.insert(\n                path.to_string(),\n                Arc::new(MemoryFile {\n                    path: path.to_string(),\n                    pages: BTreeMap::new().into(),\n                    size: 0.into(),\n                }),\n            );\n        }\n        Ok(files\n            .get(path)\n            .ok_or_else(|| {\n                crate::LimboError::InternalError(\"file should exist after insert\".to_string())\n            })?\n            .clone())\n    }\n    fn remove_file(&self, path: &str) -> Result<()> {\n        let mut files = self.files.lock();\n        files.remove(path);\n        Ok(())\n    }\n}\n\npub struct MemoryFile {\n    path: String,\n    pages: UnsafeCell<BTreeMap<usize, MemPage>>,\n    size: Cell<u64>,\n}\n\nunsafe impl Sync for MemoryFile {}\ncrate::assert::assert_sync!(MemoryFile);\n\nimpl File for MemoryFile {\n    fn lock_file(&self, _exclusive: bool) -> Result<()> {\n        Ok(())\n    }\n    fn unlock_file(&self) -> Result<()> {\n        Ok(())\n    }\n\n    fn pread(&self, pos: u64, c: Completion) -> Result<Completion> {\n        tracing::debug!(\"pread(path={}): pos={}\", self.path, pos);\n        let r = c.as_read();\n        let buf_len = r.buf().len() as u64;\n        if buf_len == 0 {\n            c.complete(0);\n            return Ok(c);\n        }\n\n        let file_size = self.size.get();\n        if pos >= file_size {\n            c.complete(0);\n            return Ok(c);\n        }\n\n        let read_len = buf_len.min(file_size - pos);\n        {\n            let read_buf = r.buf();\n            let mut offset = pos as usize;\n            let mut remaining = read_len as usize;\n            let mut buf_offset = 0;\n\n            while remaining > 0 {\n                let page_no = offset / PAGE_SIZE;\n                let page_offset = offset % PAGE_SIZE;\n                let bytes_to_read = remaining.min(PAGE_SIZE - page_offset);\n                if let Some(page) = self.get_page(page_no) {\n                    read_buf.as_mut_slice()[buf_offset..buf_offset + bytes_to_read]\n                        .copy_from_slice(&page[page_offset..page_offset + bytes_to_read]);\n                } else {\n                    read_buf.as_mut_slice()[buf_offset..buf_offset + bytes_to_read].fill(0);\n                }\n\n                offset += bytes_to_read;\n                buf_offset += bytes_to_read;\n                remaining -= bytes_to_read;\n            }\n        }\n        c.complete(read_len as i32);\n        Ok(c)\n    }\n\n    fn pwrite(&self, pos: u64, buffer: Arc<Buffer>, c: Completion) -> Result<Completion> {\n        tracing::debug!(\n            \"pwrite(path={}): pos={}, size={}\",\n            self.path,\n            pos,\n            buffer.len()\n        );\n        let buf_len = buffer.len();\n        if buf_len == 0 {\n            c.complete(0);\n            return Ok(c);\n        }\n\n        let mut offset = pos as usize;\n        let mut remaining = buf_len;\n        let mut buf_offset = 0;\n        let data = &buffer.as_slice();\n\n        while remaining > 0 {\n            let page_no = offset / PAGE_SIZE;\n            let page_offset = offset % PAGE_SIZE;\n            let bytes_to_write = remaining.min(PAGE_SIZE - page_offset);\n\n            {\n                let page = self.get_or_allocate_page(page_no as u64);\n                page[page_offset..page_offset + bytes_to_write]\n                    .copy_from_slice(&data[buf_offset..buf_offset + bytes_to_write]);\n            }\n\n            offset += bytes_to_write;\n            buf_offset += bytes_to_write;\n            remaining -= bytes_to_write;\n        }\n\n        self.size\n            .set(core::cmp::max(pos + buf_len as u64, self.size.get()));\n\n        c.complete(buf_len as i32);\n        Ok(c)\n    }\n\n    fn sync(&self, c: Completion, _sync_type: FileSyncType) -> Result<Completion> {\n        tracing::debug!(\"sync(path={})\", self.path);\n        // no-op\n        c.complete(0);\n        Ok(c)\n    }\n\n    fn truncate(&self, len: u64, c: Completion) -> Result<Completion> {\n        tracing::debug!(\"truncate(path={}): len={}\", self.path, len);\n        if len < self.size.get() {\n            // Truncate pages\n            unsafe {\n                let pages = &mut *self.pages.get();\n                pages.retain(|&k, _| k * PAGE_SIZE < len as usize);\n            }\n        }\n        self.size.set(len);\n        c.complete(0);\n        Ok(c)\n    }\n\n    fn pwritev(&self, pos: u64, buffers: Vec<Arc<Buffer>>, c: Completion) -> Result<Completion> {\n        tracing::debug!(\n            \"pwritev(path={}): pos={}, buffers={:?}\",\n            self.path,\n            pos,\n            buffers.iter().map(|x| x.len()).collect::<Vec<_>>()\n        );\n        let mut offset = pos as usize;\n        let mut total_written = 0;\n\n        for buffer in buffers {\n            let buf_len = buffer.len();\n            if buf_len == 0 {\n                continue;\n            }\n\n            let mut remaining = buf_len;\n            let mut buf_offset = 0;\n            let data = &buffer.as_slice();\n\n            while remaining > 0 {\n                let page_no = offset / PAGE_SIZE;\n                let page_offset = offset % PAGE_SIZE;\n                let bytes_to_write = remaining.min(PAGE_SIZE - page_offset);\n\n                {\n                    let page = self.get_or_allocate_page(page_no as u64);\n                    page[page_offset..page_offset + bytes_to_write]\n                        .copy_from_slice(&data[buf_offset..buf_offset + bytes_to_write]);\n                }\n\n                offset += bytes_to_write;\n                buf_offset += bytes_to_write;\n                remaining -= bytes_to_write;\n            }\n            total_written += buf_len;\n        }\n        c.complete(total_written as i32);\n        self.size\n            .set(core::cmp::max(pos + total_written as u64, self.size.get()));\n        Ok(c)\n    }\n\n    fn size(&self) -> Result<u64> {\n        tracing::debug!(\"size(path={}): {}\", self.path, self.size.get());\n        Ok(self.size.get())\n    }\n\n    fn has_hole(&self, pos: usize, len: usize) -> Result<bool> {\n        let start_page = pos / PAGE_SIZE;\n        let end_page = ((pos + len.max(1)) - 1) / PAGE_SIZE;\n        for page_no in start_page..=end_page {\n            if self.get_page(page_no).is_some() {\n                return Ok(false);\n            }\n        }\n        Ok(true)\n    }\n\n    fn punch_hole(&self, pos: usize, len: usize) -> Result<()> {\n        turso_assert!(\n            pos % PAGE_SIZE == 0 && len % PAGE_SIZE == 0,\n            \"hole must be page aligned\"\n        );\n        let start_page = pos / PAGE_SIZE;\n        let end_page = ((pos + len.max(1)) - 1) / PAGE_SIZE;\n        for page_no in start_page..=end_page {\n            unsafe { (*self.pages.get()).remove(&page_no) };\n        }\n        Ok(())\n    }\n}\n\nimpl MemoryFile {\n    #[allow(clippy::mut_from_ref)]\n    fn get_or_allocate_page(&self, page_no: u64) -> &mut MemPage {\n        unsafe {\n            let pages = &mut *self.pages.get();\n            pages\n                .entry(page_no as usize)\n                .or_insert_with(|| Box::new([0; PAGE_SIZE]))\n        }\n    }\n\n    fn get_page(&self, page_no: usize) -> Option<&MemPage> {\n        unsafe { (*self.pages.get()).get(&page_no) }\n    }\n}\n"
  },
  {
    "path": "core/io/mod.rs",
    "content": "use crate::storage::buffer_pool::ArenaBuffer;\nuse crate::storage::sqlite3_ondisk::WAL_FRAME_HEADER_SIZE;\nuse crate::sync::Arc;\nuse crate::turso_assert;\nuse crate::{BufferPool, Result};\nuse bitflags::bitflags;\nuse cfg_block::cfg_block;\nuse rand::{Rng, RngCore};\nuse std::cell::RefCell;\nuse std::fmt;\nuse std::ptr::NonNull;\nuse std::{fmt::Debug, pin::Pin};\nuse turso_macros::AtomicEnum;\n\ncfg_block! {\n    #[cfg(all(target_os = \"linux\", feature = \"io_uring\", not(miri)))] {\n        mod io_uring;\n        #[cfg(feature = \"fs\")]\n        pub use io_uring::UringIO;\n    }\n\n    #[cfg(all(target_family = \"unix\", not(miri)))] {\n        mod unix;\n        #[cfg(feature = \"fs\")]\n        pub use unix::UnixIO;\n        pub use unix::UnixIO as PlatformIO;\n        pub use PlatformIO as SyscallIO;\n    }\n\n    #[cfg(all(target_os = \"windows\", feature = \"experimental_win_iocp\", not(miri)))] {\n        mod win_iocp;\n        #[cfg(feature = \"fs\")]\n        pub use win_iocp::WindowsIOCP;\n    }\n\n    #[cfg(any(not(any(target_family = \"unix\", target_os = \"android\", target_os = \"ios\")), miri))] {\n        mod generic;\n        pub use generic::GenericIO as PlatformIO;\n        pub use PlatformIO as SyscallIO;\n    }\n}\n\nmod memory;\n#[cfg(feature = \"fs\")]\nmod vfs;\npub use memory::MemoryIO;\npub mod clock;\nmod common;\nmod completions;\npub use clock::Clock;\npub use completions::*;\n\n/// Controls which sync mechanism to use for durability.\n/// `FullFsync` only has effect on Apple platforms (uses F_FULLFSYNC fcntl).\n/// On other platforms, both variants behave the same (regular fsync).\n#[derive(Debug, Clone, Copy, PartialEq, Eq, AtomicEnum)]\npub enum FileSyncType {\n    /// Regular fsync - flushes to disk but may not flush disk write cache on macOS.\n    Fsync,\n    /// Full fsync - on macOS uses F_FULLFSYNC to flush disk write cache.\n    /// On other platforms, behaves the same as Fsync.\n    FullFsync,\n}\n\npub trait File: Send + Sync {\n    fn lock_file(&self, exclusive: bool) -> Result<()>;\n    fn unlock_file(&self) -> Result<()>;\n    fn pread(&self, pos: u64, c: Completion) -> Result<Completion>;\n    fn pwrite(&self, pos: u64, buffer: Arc<Buffer>, c: Completion) -> Result<Completion>;\n    /// Sync file data&metadata to disk.\n    fn sync(&self, c: Completion, sync_type: FileSyncType) -> Result<Completion>;\n    fn pwritev(&self, pos: u64, buffers: Vec<Arc<Buffer>>, c: Completion) -> Result<Completion> {\n        use crate::sync::atomic::{AtomicUsize, Ordering};\n        if buffers.is_empty() {\n            c.complete(0);\n            return Ok(c);\n        }\n        if buffers.len() == 1 {\n            return self.pwrite(pos, buffers[0].clone(), c);\n        }\n        // naive default implementation can be overridden on backends where it makes sense to\n        let mut pos = pos;\n        let outstanding = Arc::new(AtomicUsize::new(buffers.len()));\n        let total_written = Arc::new(AtomicUsize::new(0));\n\n        for buf in buffers {\n            let len = buf.len();\n            let child_c = {\n                let c_main = c.clone();\n                let outstanding = outstanding.clone();\n                let total_written = total_written.clone();\n                Completion::new_write(move |n| {\n                    if let Ok(n) = n {\n                        // accumulate bytes actually reported by the backend\n                        total_written.fetch_add(n as usize, Ordering::SeqCst);\n                        if outstanding.fetch_sub(1, Ordering::AcqRel) == 1 {\n                            // last one finished\n                            c_main.complete(total_written.load(Ordering::Acquire) as i32);\n                        }\n                    }\n                })\n            };\n            if let Err(e) = self.pwrite(pos, buf.clone(), child_c) {\n                c.abort();\n                return Err(e);\n            }\n            pos += len as u64;\n        }\n        Ok(c)\n    }\n    fn size(&self) -> Result<u64>;\n    fn truncate(&self, len: u64, c: Completion) -> Result<Completion>;\n\n    /// Optional method implemented by the IO which supports \"partial\" files (e.g. file with \"holes\")\n    /// This method is used in sync engine only for now (in partial sync mode) and never used in the core database code\n    ///\n    /// The hole is the contiguous file region which is not allocated by the file-system\n    /// If there is a single byte which is allocated within a given range - method must return false in this case\n    // todo: need to add custom completion type?\n    fn has_hole(&self, _pos: usize, _len: usize) -> Result<bool> {\n        panic!(\"has_hole is not supported for the given IO implementation\")\n    }\n    /// Optional method implemented by the IO which supports \"partial\" files (e.g. file with \"holes\")\n    /// This method is used in sync engine only for now (in partial sync mode) and never used in the core database code\n    // todo: need to add custom completion type?\n    fn punch_hole(&self, _pos: usize, _len: usize) -> Result<()> {\n        panic!(\"punch_hole is not supported for the given IO implementation\")\n    }\n}\n\npub struct TempFile {\n    /// When temp_dir is dropped the folder is deleted\n    /// set to None if tempfile allocated in memory (for example, in case of WASM target)\n    _temp_dir: Option<tempfile::TempDir>,\n    pub(crate) file: Arc<dyn File>,\n}\n\nimpl TempFile {\n    pub fn new(io: &Arc<dyn IO>) -> Result<Self> {\n        #[cfg(not(target_family = \"wasm\"))]\n        {\n            let temp_dir = tempfile::tempdir().map_err(|e| crate::error::io_error(e, \"tempdir\"))?;\n            let chunk_file_path = temp_dir.as_ref().join(\"tursodb_temp_file\");\n            let chunk_file_path_str = chunk_file_path.to_str().ok_or_else(|| {\n                crate::LimboError::InternalError(\"temp file path is not valid UTF-8\".to_string())\n            })?;\n            let chunk_file = io.open_file(chunk_file_path_str, OpenFlags::Create, false)?;\n            Ok(TempFile {\n                _temp_dir: Some(temp_dir),\n                file: chunk_file.clone(),\n            })\n        }\n        // on WASM in browser we do not support temp files (as we pre-register db files in advance and can't easily create a new one)\n        // so, for now, we use in-memory IO for tempfiles in WASM\n        #[cfg(target_family = \"wasm\")]\n        {\n            use crate::MemoryIO;\n\n            let memory_io = Arc::new(MemoryIO::new());\n            let memory_file = memory_io.open_file(\"tursodb_temp_file\", OpenFlags::Create, false)?;\n            Ok(TempFile {\n                _temp_dir: None,\n                file: memory_file,\n            })\n        }\n    }\n\n    /// Creates a TempFile respecting the temp_store setting.\n    /// When temp_store is Memory, uses in-memory storage.\n    /// When temp_store is Default or File, uses file-based storage.\n    pub fn with_temp_store(io: &Arc<dyn IO>, temp_store: crate::TempStore) -> Result<Self> {\n        #[cfg(not(target_family = \"wasm\"))]\n        {\n            if matches!(temp_store, crate::TempStore::Memory) {\n                let memory_io = Arc::new(MemoryIO::new());\n                let memory_file =\n                    memory_io.open_file(\"tursodb_temp_file\", OpenFlags::Create, false)?;\n                return Ok(TempFile {\n                    _temp_dir: None,\n                    file: memory_file,\n                });\n            }\n            // Fall through to file-based for Default and File modes\n            Self::new(io)\n        }\n        #[cfg(target_family = \"wasm\")]\n        {\n            // WASM always uses memory, ignore temp_store setting\n            let _ = temp_store;\n            Self::new(io)\n        }\n    }\n}\n\nimpl core::ops::Deref for TempFile {\n    type Target = Arc<dyn File>;\n\n    fn deref(&self) -> &Self::Target {\n        &self.file\n    }\n}\n\n#[derive(Debug, Copy, Clone, PartialEq)]\npub struct OpenFlags(i32);\n\n// OpenFlags is a newtype over i32, which is inherently Send+Sync.\n// The assertion below verifies this at compile time.\ncrate::assert::assert_send_sync!(OpenFlags);\n\nbitflags! {\n    impl OpenFlags: i32 {\n        const None = 0b00000000;\n        const Create = 0b0000001;\n        const ReadOnly = 0b0000010;\n    }\n}\n\nimpl Default for OpenFlags {\n    fn default() -> Self {\n        Self::Create\n    }\n}\n\npub trait IO: Clock + Send + Sync {\n    fn open_file(&self, path: &str, flags: OpenFlags, direct: bool) -> Result<Arc<dyn File>>;\n\n    // remove_file is used in the sync-engine\n    fn remove_file(&self, path: &str) -> Result<()>;\n\n    fn step(&self) -> Result<()> {\n        Ok(())\n    }\n\n    fn cancel(&self, c: &[Completion]) -> Result<()> {\n        c.iter().for_each(|c| c.abort());\n        Ok(())\n    }\n\n    fn drain(&self) -> Result<()> {\n        Ok(())\n    }\n\n    fn wait_for_completion(&self, c: Completion) -> Result<()> {\n        while !c.finished() {\n            self.step()?\n        }\n        if let Some(inner) = &c.inner {\n            if let Some(Some(err)) = inner.result.get().copied() {\n                return Err(err.into());\n            }\n        }\n        Ok(())\n    }\n\n    fn generate_random_number(&self) -> i64 {\n        rand::rng().random()\n    }\n\n    /// Fill `dest` with random data.\n    fn fill_bytes(&self, dest: &mut [u8]) {\n        rand::rng().fill_bytes(dest);\n    }\n\n    fn get_memory_io(&self) -> Arc<MemoryIO> {\n        Arc::new(MemoryIO::new())\n    }\n\n    fn register_fixed_buffer(&self, _ptr: NonNull<u8>, _len: usize) -> Result<u32> {\n        Err(crate::LimboError::InternalError(\n            \"unsupported operation\".to_string(),\n        ))\n    }\n\n    /// Yield the current thread to the scheduler.\n    /// Used for backoff in contended lock acquisition.\n    fn yield_now(&self) {\n        crate::thread::yield_now();\n    }\n\n    /// Sleep for the specified duration.\n    /// Used for progressive backoff in contended lock acquisition.\n    fn sleep(&self, duration: std::time::Duration) {\n        crate::thread::sleep(duration);\n    }\n}\n\n/// Batches multiple vectored writes for submission.\npub struct WriteBatch<'a> {\n    file: Arc<dyn File>,\n    ops: Vec<WriteOp<'a>>,\n}\n\nstruct WriteOp<'a> {\n    pos: u64,\n    bufs: &'a [Arc<Buffer>],\n}\n\nimpl<'a> WriteBatch<'a> {\n    pub fn new(file: Arc<dyn File>) -> Self {\n        Self {\n            file,\n            ops: Vec::new(),\n        }\n    }\n\n    #[inline]\n    pub fn writev(&mut self, pos: u64, bufs: &'a [Arc<Buffer>]) {\n        if !bufs.is_empty() {\n            self.ops.push(WriteOp { pos, bufs });\n        }\n    }\n\n    /// Total bytes across all operations.\n    #[inline]\n    pub fn total_bytes(&self) -> usize {\n        self.ops\n            .iter()\n            .map(|op| op.bufs.iter().map(|b| b.len()).sum::<usize>())\n            .sum()\n    }\n\n    /// Submit all writes. Returns completions caller must wait on.\n    #[inline]\n    pub fn submit(self) -> Result<Vec<Completion>> {\n        let mut completions = Vec::with_capacity(self.ops.len());\n        for WriteOp { pos, bufs } in self.ops {\n            let total_len = bufs.iter().map(|b| b.len()).sum::<usize>() as i32;\n            let c = Completion::new_write(move |res| {\n                let Ok(bytes_written) = res else {\n                    return;\n                };\n                turso_assert!(\n                    bytes_written == total_len,\n                    \"pwritev wrote {bytes_written} bytes, expected {total_len}\"\n                );\n            });\n            completions.push(self.file.pwritev(pos, bufs.to_vec(), c)?);\n        }\n        Ok(completions)\n    }\n\n    /// Returns the file for fsync after writes complete.\n    #[inline]\n    pub const fn file(&self) -> &Arc<dyn File> {\n        &self.file\n    }\n}\n\npub type BufferData = Pin<Box<[u8]>>;\n\npub enum Buffer {\n    Heap(BufferData),\n    Pooled(ArenaBuffer),\n}\n\nimpl Debug for Buffer {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match self {\n            Self::Pooled(p) => write!(f, \"Pooled(len={})\", p.logical_len()),\n            Self::Heap(buf) => write!(f, \"{buf:?}: {}\", buf.len()),\n        }\n    }\n}\n\nimpl Drop for Buffer {\n    fn drop(&mut self) {\n        let len = self.len();\n        if let Self::Heap(buf) = self {\n            TEMP_BUFFER_CACHE.with(|cache| {\n                let mut cache = cache.borrow_mut();\n                // take ownership of the buffer by swapping it with a dummy\n                let buffer = std::mem::replace(buf, Pin::new(vec![].into_boxed_slice()));\n                cache.return_buffer(buffer, len);\n            });\n        }\n    }\n}\n\nimpl Buffer {\n    pub fn new(data: Vec<u8>) -> Self {\n        tracing::trace!(\"buffer::new({:?})\", data);\n        Self::Heap(Pin::new(data.into_boxed_slice()))\n    }\n\n    /// Returns the index of the underlying `Arena` if it was registered with\n    /// io_uring. Only for use with `UringIO` backend.\n    pub fn fixed_id(&self) -> Option<u32> {\n        match self {\n            Self::Heap { .. } => None,\n            Self::Pooled(buf) => buf.fixed_id(),\n        }\n    }\n\n    pub fn new_pooled(buf: ArenaBuffer) -> Self {\n        Self::Pooled(buf)\n    }\n\n    pub fn new_temporary(size: usize) -> Self {\n        TEMP_BUFFER_CACHE.with(|cache| {\n            if let Some(buffer) = cache.borrow_mut().get_buffer(size) {\n                Self::Heap(buffer)\n            } else {\n                Self::Heap(Pin::new(vec![0; size].into_boxed_slice()))\n            }\n        })\n    }\n\n    pub fn len(&self) -> usize {\n        match self {\n            Self::Heap(buf) => buf.len(),\n            Self::Pooled(buf) => buf.logical_len(),\n        }\n    }\n\n    pub fn is_empty(&self) -> bool {\n        self.len() == 0\n    }\n\n    pub fn as_slice(&self) -> &[u8] {\n        match self {\n            Self::Heap(buf) => {\n                // SAFETY: The buffer is guaranteed to be valid for the lifetime of the slice\n                unsafe { std::slice::from_raw_parts(buf.as_ptr(), buf.len()) }\n            }\n            Self::Pooled(buf) => buf,\n        }\n    }\n\n    #[allow(clippy::mut_from_ref)]\n    pub fn as_mut_slice(&self) -> &mut [u8] {\n        unsafe { std::slice::from_raw_parts_mut(self.as_mut_ptr(), self.len()) }\n    }\n    #[inline]\n    pub fn as_ptr(&self) -> *const u8 {\n        match self {\n            Self::Heap(buf) => buf.as_ptr(),\n            Self::Pooled(buf) => buf.as_ptr(),\n        }\n    }\n    #[inline]\n    pub fn as_mut_ptr(&self) -> *mut u8 {\n        match self {\n            Self::Heap(buf) => buf.as_ptr() as *mut u8,\n            Self::Pooled(buf) => buf.as_ptr() as *mut u8,\n        }\n    }\n\n    #[inline]\n    pub fn is_pooled(&self) -> bool {\n        matches!(self, Self::Pooled(..))\n    }\n\n    #[inline]\n    pub fn is_heap(&self) -> bool {\n        matches!(self, Self::Heap(..))\n    }\n}\n\ncrate::thread::thread_local! {\n    /// thread local cache to re-use temporary buffers to prevent churn when pool overflows\n    pub static TEMP_BUFFER_CACHE: RefCell<TempBufferCache> = RefCell::new(TempBufferCache::new());\n}\n\n/// A cache for temporary or any additional `Buffer` allocations beyond\n/// what the `BufferPool` has room for, or for use before the pool is\n/// fully initialized.\npub(crate) struct TempBufferCache {\n    /// The `[Database::page_size]` at the time the cache is initiated.\n    page_size: usize,\n    /// Cache of buffers of size `self.page_size`.\n    page_buffers: Vec<BufferData>,\n    /// Cache of buffers of size `self.page_size` + WAL_FRAME_HEADER_SIZE.\n    wal_frame_buffers: Vec<BufferData>,\n    /// Maximum number of buffers that will live in each cache.\n    max_cached: usize,\n}\n\nimpl TempBufferCache {\n    const DEFAULT_MAX_CACHE_SIZE: usize = 256;\n\n    fn new() -> Self {\n        Self {\n            page_size: BufferPool::DEFAULT_PAGE_SIZE,\n            page_buffers: Vec::with_capacity(8),\n            wal_frame_buffers: Vec::with_capacity(8),\n            max_cached: Self::DEFAULT_MAX_CACHE_SIZE,\n        }\n    }\n\n    /// If the `[Database::page_size]` is set, any temporary buffers that might\n    /// exist prior need to be cleared and new `page_size` needs to be saved.\n    pub fn reinit_cache(&mut self, page_size: usize) {\n        self.page_buffers.clear();\n        self.wal_frame_buffers.clear();\n        self.page_size = page_size;\n    }\n\n    fn get_buffer(&mut self, size: usize) -> Option<BufferData> {\n        match size {\n            sz if sz == self.page_size => self.page_buffers.pop(),\n            sz if sz == (self.page_size + WAL_FRAME_HEADER_SIZE) => self.wal_frame_buffers.pop(),\n            _ => None,\n        }\n    }\n\n    fn return_buffer(&mut self, buff: BufferData, len: usize) {\n        let sz = self.page_size;\n        let cache = match len {\n            n if n.eq(&sz) => &mut self.page_buffers,\n            n if n.eq(&(sz + WAL_FRAME_HEADER_SIZE)) => &mut self.wal_frame_buffers,\n            _ => return,\n        };\n        if self.max_cached > cache.len() {\n            cache.push(buff);\n        }\n    }\n}\n\n#[cfg(all(shuttle, test))]\nmod shuttle_tests {\n    use std::path::PathBuf;\n\n    use super::*;\n    use crate::io::{Buffer, Completion, OpenFlags, IO};\n    use crate::sync::atomic::{AtomicUsize, Ordering};\n    use crate::sync::Arc;\n    use crate::thread;\n\n    /// Factory trait for creating IO implementations in tests.\n    /// Allows the same test logic to run against different IO backends.\n    trait IOFactory: Send + Sync + 'static {\n        fn create(&self) -> Arc<dyn IO>;\n        /// Returns a unique temp directory path for this factory instance.\n        fn temp_dir(&self) -> PathBuf;\n    }\n\n    struct MemoryIOFactory {\n        id: u64,\n    }\n\n    impl MemoryIOFactory {\n        fn new() -> Self {\n            use crate::sync::atomic::AtomicU64;\n            static COUNTER: AtomicU64 = AtomicU64::new(0);\n            Self {\n                id: COUNTER.fetch_add(1, Ordering::SeqCst),\n            }\n        }\n    }\n\n    impl IOFactory for MemoryIOFactory {\n        fn create(&self) -> Arc<dyn IO> {\n            Arc::new(MemoryIO::new())\n        }\n        fn temp_dir(&self) -> PathBuf {\n            format!(\"mem_{}\", self.id).into()\n        }\n    }\n\n    #[cfg(all(target_family = \"unix\", feature = \"fs\", not(miri)))]\n    struct PlatformIOFactory {\n        temp_dir: tempfile::TempDir,\n    }\n\n    #[cfg(all(target_family = \"unix\", feature = \"fs\", not(miri)))]\n    impl PlatformIOFactory {\n        fn new() -> Self {\n            Self {\n                temp_dir: tempfile::tempdir().unwrap(),\n            }\n        }\n    }\n\n    #[cfg(all(target_family = \"unix\", feature = \"fs\", not(miri)))]\n    impl IOFactory for PlatformIOFactory {\n        fn create(&self) -> Arc<dyn IO> {\n            Arc::new(PlatformIO::new().unwrap())\n        }\n        fn temp_dir(&self) -> PathBuf {\n            self.temp_dir.path().to_path_buf()\n        }\n    }\n\n    #[cfg(all(target_os = \"linux\", feature = \"io_uring\", feature = \"fs\", not(miri)))]\n    struct UringIOFactory {\n        temp_dir: tempfile::TempDir,\n    }\n\n    #[cfg(all(target_os = \"linux\", feature = \"io_uring\", feature = \"fs\", not(miri)))]\n    impl UringIOFactory {\n        fn new() -> Self {\n            Self {\n                temp_dir: tempfile::tempdir().unwrap(),\n            }\n        }\n    }\n\n    #[cfg(all(target_os = \"linux\", feature = \"io_uring\", feature = \"fs\", not(miri)))]\n    impl IOFactory for UringIOFactory {\n        fn create(&self) -> Arc<dyn IO> {\n            Arc::new(UringIO::new().unwrap())\n        }\n        fn temp_dir(&self) -> PathBuf {\n            self.temp_dir.path().to_path_buf()\n        }\n    }\n\n    #[cfg(all(\n        target_os = \"windows\",\n        feature = \"experimental_win_iocp\",\n        feature = \"fs\",\n        not(miri)\n    ))]\n    struct WinIOCPFactory {\n        temp_dir: tempfile::TempDir,\n    }\n\n    #[cfg(all(\n        target_os = \"windows\",\n        feature = \"experimental_win_iocp\",\n        feature = \"fs\",\n        not(miri)\n    ))]\n    impl WinIOCPFactory {\n        fn new() -> Self {\n            Self {\n                temp_dir: tempfile::tempdir().unwrap(),\n            }\n        }\n    }\n\n    #[cfg(all(\n        target_os = \"windows\",\n        feature = \"experimental_win_iocp\",\n        feature = \"fs\",\n        not(miri)\n    ))]\n    impl IOFactory for WinIOCPFactory {\n        fn create(&self) -> Arc<dyn IO> {\n            Arc::new(WindowsIOCP::new().unwrap())\n        }\n        fn temp_dir(&self) -> PathBuf {\n            self.temp_dir.path().to_path_buf()\n        }\n    }\n\n    /// Macro to generate shuttle tests for all IO implementations.\n    /// Creates a test for MemoryIO, and conditionally for PlatformIO and UringIO.\n    macro_rules! shuttle_io_test {\n        ($test_name:ident, $test_impl:ident) => {\n            pastey::paste! {\n                #[test]\n                fn [<shuttle_ $test_name _memory>]() {\n                    shuttle::check_random(|| $test_impl(MemoryIOFactory::new()), 1000);\n                }\n\n                #[cfg(all(target_family = \"unix\", feature = \"fs\", not(miri)))]\n                #[test]\n                fn [<shuttle_ $test_name _platform>]() {\n                    shuttle::check_random(|| $test_impl(PlatformIOFactory::new()), 1000);\n                }\n\n                #[cfg(all(target_os = \"linux\", feature = \"io_uring\", feature = \"fs\", not(miri)))]\n                #[test]\n                fn [<shuttle_ $test_name _uring>]() {\n                    shuttle::check_random(|| $test_impl(UringIOFactory::new()), 1000);\n                }\n\n                #[cfg(all(target_os = \"windows\", feature = \"experimental_win_iocp\", feature = \"fs\", not(miri)))]\n                #[test]\n                fn [<shuttle_ $test_name _win_iocp>]() {\n                    shuttle::check_random(|| $test_impl(WinIOCPFactory::new()), 1000);\n                }\n\n            }\n        };\n    }\n\n    /// Helper to wait for a completion synchronously and assert it succeeded.\n    fn wait_completion_ok(io: &dyn IO, c: &Completion) {\n        io.wait_for_completion(c.clone()).unwrap();\n        assert!(c.succeeded(), \"completion failed: {:?}\", c.get_error());\n        assert!(!c.failed());\n        assert!(c.finished());\n        assert!(c.get_error().is_none());\n    }\n\n    /// Helper to wait for a completion synchronously without asserting success.\n    #[allow(dead_code)]\n    fn wait_completion(io: &dyn IO, c: &Completion) {\n        io.wait_for_completion(c.clone()).unwrap();\n        assert!(c.finished());\n    }\n\n    /// Test concurrent file creation from multiple threads.\n    fn test_concurrent_file_creation_impl<F: IOFactory>(factory: F) {\n        let io = factory.create();\n        let base = factory.temp_dir();\n        let mut handles = vec![];\n        const NUM_THREADS: usize = 3;\n\n        for i in 0..NUM_THREADS {\n            let io = io.clone();\n            let base = base.clone();\n            handles.push(thread::spawn(move || {\n                let path = base.join(format!(\"test_file_{}.db\", i));\n                let file = io\n                    .open_file(path.to_str().unwrap(), OpenFlags::Create, false)\n                    .unwrap();\n                assert!(file.size().unwrap() == 0);\n            }));\n        }\n\n        for h in handles {\n            h.join().unwrap();\n        }\n    }\n\n    shuttle_io_test!(concurrent_file_creation, test_concurrent_file_creation_impl);\n\n    /// Test concurrent writes to different offsets in the same file.\n    fn test_concurrent_writes_different_offsets_impl<F: IOFactory>(factory: F) {\n        let io = factory.create();\n        let path = factory.temp_dir().join(\"test.db\");\n        let file = io\n            .open_file(path.to_str().unwrap(), OpenFlags::Create, false)\n            .unwrap();\n\n        let mut handles = vec![];\n        const NUM_THREADS: usize = 3;\n\n        for i in 0..NUM_THREADS {\n            let file = file.clone();\n            let io = io.clone();\n            handles.push(thread::spawn(move || {\n                let data = vec![i as u8; 100];\n                let buf = Arc::new(Buffer::new(data));\n                let pos = (i * 100) as u64;\n\n                let c = Completion::new_write(|_| {});\n                let c = file.pwrite(pos, buf, c).unwrap();\n                wait_completion_ok(io.as_ref(), &c);\n            }));\n        }\n\n        for h in handles {\n            h.join().unwrap();\n        }\n\n        // Verify file size accounts for all writes\n        let expected_size = (NUM_THREADS * 100) as u64;\n        assert_eq!(file.size().unwrap(), expected_size);\n\n        // Read back and verify each segment contains correct data\n        for i in 0..NUM_THREADS {\n            let read_buf = Arc::new(Buffer::new_temporary(100));\n            let pos = (i * 100) as u64;\n            let c = Completion::new_read(read_buf.clone(), |_| None);\n            let c = file.pread(pos, c).unwrap();\n            wait_completion_ok(io.as_ref(), &c);\n\n            let expected = vec![i as u8; 100];\n            assert_eq!(\n                read_buf.as_slice(),\n                expected.as_slice(),\n                \"data mismatch at offset {}\",\n                pos\n            );\n        }\n    }\n\n    shuttle_io_test!(\n        concurrent_writes_different_offsets,\n        test_concurrent_writes_different_offsets_impl\n    );\n\n    /// Test concurrent reads and writes to the same file.\n    fn test_concurrent_read_write_impl<F: IOFactory>(factory: F) {\n        let io = factory.create();\n        let path = factory.temp_dir().join(\"test.db\");\n        let file = io\n            .open_file(path.to_str().unwrap(), OpenFlags::Create, false)\n            .unwrap();\n\n        // First write some initial data\n        let initial_data = vec![0xAA; 1000];\n        let buf = Arc::new(Buffer::new(initial_data));\n        let c = Completion::new_write(|_| {});\n        let c = file.pwrite(0, buf, c).unwrap();\n        wait_completion_ok(io.as_ref(), &c);\n\n        let mut handles = vec![];\n\n        // Spawn readers\n        for _ in 0..2 {\n            let file = file.clone();\n            let io = io.clone();\n            handles.push(thread::spawn(move || {\n                let read_buf = Arc::new(Buffer::new_temporary(100));\n                let c = Completion::new_read(read_buf.clone(), |_| None);\n                let c = file.pread(0, c).unwrap();\n                wait_completion_ok(io.as_ref(), &c);\n\n                // All bytes read should be 0xAA (initial data at offset 0)\n                assert!(\n                    read_buf.as_slice().iter().all(|&b| b == 0xAA),\n                    \"read buffer should contain initial data 0xAA\"\n                );\n            }));\n        }\n\n        // Spawn a writer\n        {\n            let file = file.clone();\n            let io = io.clone();\n            handles.push(thread::spawn(move || {\n                let data = vec![0xBB; 100];\n                let buf = Arc::new(Buffer::new(data));\n                let c = Completion::new_write(|_| {});\n                let c = file.pwrite(500, buf, c).unwrap();\n                wait_completion_ok(io.as_ref(), &c);\n            }));\n        }\n\n        for h in handles {\n            h.join().unwrap();\n        }\n\n        // Verify the write at offset 500 succeeded\n        let read_buf = Arc::new(Buffer::new_temporary(100));\n        let c = Completion::new_read(read_buf.clone(), |_| None);\n        let c = file.pread(500, c).unwrap();\n        wait_completion_ok(io.as_ref(), &c);\n        assert!(\n            read_buf.as_slice().iter().all(|&b| b == 0xBB),\n            \"data at offset 500 should be 0xBB\"\n        );\n    }\n\n    shuttle_io_test!(concurrent_read_write, test_concurrent_read_write_impl);\n\n    /// Test that completion callbacks are invoked correctly under concurrency.\n    fn test_completion_callbacks_concurrent_impl<F: IOFactory>(factory: F) {\n        let io = factory.create();\n        let path = factory.temp_dir().join(\"test.db\");\n        let file = io\n            .open_file(path.to_str().unwrap(), OpenFlags::Create, false)\n            .unwrap();\n\n        let callback_count = Arc::new(AtomicUsize::new(0));\n        let mut handles = vec![];\n        const NUM_WRITES: usize = 3;\n\n        for i in 0..NUM_WRITES {\n            let file = file.clone();\n            let io = io.clone();\n            let count = callback_count.clone();\n            handles.push(thread::spawn(move || {\n                let data = vec![i as u8; 50];\n                let buf = Arc::new(Buffer::new(data));\n                let count_clone = count.clone();\n                let c = Completion::new_write(move |res| {\n                    assert!(res.is_ok());\n                    count_clone.fetch_add(1, Ordering::SeqCst);\n                });\n                let c = file.pwrite((i * 50) as u64, buf, c).unwrap();\n                wait_completion_ok(io.as_ref(), &c);\n            }));\n        }\n\n        for h in handles {\n            h.join().unwrap();\n        }\n\n        assert_eq!(callback_count.load(Ordering::SeqCst), NUM_WRITES);\n    }\n\n    shuttle_io_test!(\n        completion_callbacks_concurrent,\n        test_completion_callbacks_concurrent_impl\n    );\n\n    /// Test concurrent truncate operations.\n    fn test_concurrent_truncate_impl<F: IOFactory>(factory: F) {\n        let io = factory.create();\n        let path = factory.temp_dir().join(\"test.db\");\n        let file = io\n            .open_file(path.to_str().unwrap(), OpenFlags::Create, false)\n            .unwrap();\n\n        // Write initial data\n        let initial = vec![0xFF; 5000];\n        let buf = Arc::new(Buffer::new(initial));\n        let c = Completion::new_write(|_| {});\n        let c = file.pwrite(0, buf, c).unwrap();\n        wait_completion_ok(io.as_ref(), &c);\n\n        let mut handles = vec![];\n\n        // Spawn threads that truncate to different sizes\n        for i in 0..3 {\n            let file = file.clone();\n            let io = io.clone();\n            handles.push(thread::spawn(move || {\n                let truncate_size = ((i + 1) * 1000) as u64;\n                let c = Completion::new_trunc(|_| {});\n                let c = file.truncate(truncate_size, c).unwrap();\n                wait_completion_ok(io.as_ref(), &c);\n            }));\n        }\n\n        for h in handles {\n            h.join().unwrap();\n        }\n\n        // Size should be one of the truncate values\n        let final_size = file.size().unwrap();\n        assert!(final_size == 1000 || final_size == 2000 || final_size == 3000);\n    }\n\n    shuttle_io_test!(concurrent_truncate, test_concurrent_truncate_impl);\n\n    /// Test pwritev with concurrent reads.\n    fn test_pwritev_with_concurrent_reads_impl<F: IOFactory>(factory: F) {\n        let io = factory.create();\n        let path = factory.temp_dir().join(\"test.db\");\n        let file = io\n            .open_file(path.to_str().unwrap(), OpenFlags::Create, false)\n            .unwrap();\n\n        // Write initial data so reads have something to return\n        let initial = vec![0x11; 2000];\n        let buf = Arc::new(Buffer::new(initial));\n        let c = Completion::new_write(|_| {});\n        let c = file.pwrite(0, buf, c).unwrap();\n        wait_completion_ok(io.as_ref(), &c);\n\n        let mut handles = vec![];\n\n        // Spawn a pwritev thread\n        {\n            let file = file.clone();\n            let io = io.clone();\n            handles.push(thread::spawn(move || {\n                let bufs = vec![\n                    Arc::new(Buffer::new(vec![0x22; 100])),\n                    Arc::new(Buffer::new(vec![0x33; 100])),\n                    Arc::new(Buffer::new(vec![0x44; 100])),\n                ];\n                let c = Completion::new_write(|_| {});\n                let c = file.pwritev(0, bufs, c).unwrap();\n                wait_completion_ok(io.as_ref(), &c);\n            }));\n        }\n\n        // Spawn reader threads\n        for _ in 0..2 {\n            let file = file.clone();\n            let io = io.clone();\n            handles.push(thread::spawn(move || {\n                let buf = Arc::new(Buffer::new_temporary(100));\n                let c = Completion::new_read(buf.clone(), |_| None);\n                let c = file.pread(0, c).unwrap();\n                wait_completion_ok(io.as_ref(), &c);\n\n                // Data should be either initial (0x11) or from pwritev (0x22)\n                // depending on race ordering\n                let first_byte = buf.as_slice()[0];\n                assert!(\n                    first_byte == 0x11 || first_byte == 0x22,\n                    \"first byte should be 0x11 or 0x22, got {:#x}\",\n                    first_byte\n                );\n                // All 100 bytes should be consistent\n                assert!(\n                    buf.as_slice().iter().all(|&b| b == first_byte),\n                    \"all bytes should be the same value\"\n                );\n            }));\n        }\n\n        for h in handles {\n            h.join().unwrap();\n        }\n\n        // After all threads complete, verify pwritev data is present\n        let read_buf = Arc::new(Buffer::new_temporary(300));\n        let c = Completion::new_read(read_buf.clone(), |_| None);\n        let c = file.pread(0, c).unwrap();\n        wait_completion_ok(io.as_ref(), &c);\n\n        // Should have 0x22 for first 100, 0x33 for next 100, 0x44 for last 100\n        assert!(\n            read_buf.as_slice()[..100].iter().all(|&b| b == 0x22),\n            \"bytes 0-99 should be 0x22\"\n        );\n        assert!(\n            read_buf.as_slice()[100..200].iter().all(|&b| b == 0x33),\n            \"bytes 100-199 should be 0x33\"\n        );\n        assert!(\n            read_buf.as_slice()[200..300].iter().all(|&b| b == 0x44),\n            \"bytes 200-299 should be 0x44\"\n        );\n    }\n\n    shuttle_io_test!(\n        pwritev_with_concurrent_reads,\n        test_pwritev_with_concurrent_reads_impl\n    );\n\n    /// Test concurrent access to multiple files.\n    fn test_concurrent_multifile_access_impl<F: IOFactory>(factory: F) {\n        let io = factory.create();\n        let base = factory.temp_dir();\n\n        let mut handles = vec![];\n        const NUM_FILES: usize = 3;\n\n        for i in 0..NUM_FILES {\n            let io = io.clone();\n            let base = base.clone();\n            handles.push(thread::spawn(move || {\n                let path = base.join(format!(\"file_{}.db\", i));\n                let file = io\n                    .open_file(path.to_str().unwrap(), OpenFlags::Create, false)\n                    .unwrap();\n\n                // Write to file\n                let data = vec![i as u8; 200];\n                let buf = Arc::new(Buffer::new(data.clone()));\n                let c = Completion::new_write(|_| {});\n                let c = file.pwrite(0, buf, c).unwrap();\n                wait_completion_ok(io.as_ref(), &c);\n\n                // Read back and verify\n                let read_buf = Arc::new(Buffer::new_temporary(200));\n                let c = Completion::new_read(read_buf.clone(), |_| None);\n                let c = file.pread(0, c).unwrap();\n                wait_completion_ok(io.as_ref(), &c);\n\n                assert_eq!(read_buf.as_slice(), data.as_slice());\n            }));\n        }\n\n        for h in handles {\n            h.join().unwrap();\n        }\n    }\n\n    shuttle_io_test!(\n        concurrent_multifile_access,\n        test_concurrent_multifile_access_impl\n    );\n\n    /// Test file locking under concurrent access.\n    fn test_file_locking_concurrent_impl<F: IOFactory>(factory: F) {\n        let io = factory.create();\n        let path = factory.temp_dir().join(\"test.db\");\n        let file = io\n            .open_file(path.to_str().unwrap(), OpenFlags::Create, false)\n            .unwrap();\n\n        let mut handles = vec![];\n\n        // Multiple threads try to lock/unlock\n        for _ in 0..3 {\n            let file = file.clone();\n            handles.push(thread::spawn(move || {\n                // Exclusive lock\n                file.lock_file(true).unwrap();\n                thread::yield_now();\n                file.unlock_file().unwrap();\n\n                // Shared lock\n                file.lock_file(false).unwrap();\n                thread::yield_now();\n                file.unlock_file().unwrap();\n            }));\n        }\n\n        for h in handles {\n            h.join().unwrap();\n        }\n    }\n\n    shuttle_io_test!(file_locking_concurrent, test_file_locking_concurrent_impl);\n\n    /// Test reading past end of file returns zero bytes.\n    fn test_read_past_eof_impl<F: IOFactory>(factory: F) {\n        let io = factory.create();\n        let path = factory.temp_dir().join(\"test.db\");\n        let file = io\n            .open_file(path.to_str().unwrap(), OpenFlags::Create, false)\n            .unwrap();\n\n        // Write 100 bytes\n        let data = vec![0xAA; 100];\n        let buf = Arc::new(Buffer::new(data));\n        let c = Completion::new_write(|_| {});\n        let c = file.pwrite(0, buf, c).unwrap();\n        wait_completion_ok(io.as_ref(), &c);\n\n        let mut handles = vec![];\n\n        // Multiple threads try to read past EOF\n        for _ in 0..3 {\n            let file = file.clone();\n            let io = io.clone();\n            handles.push(thread::spawn(move || {\n                let read_buf = Arc::new(Buffer::new_temporary(100));\n                let bytes_read = Arc::new(AtomicUsize::new(999));\n                let bytes_read_clone = bytes_read.clone();\n                let c = Completion::new_read(read_buf, move |res| {\n                    if let Ok((_, n)) = res {\n                        bytes_read_clone.store(n as usize, Ordering::SeqCst);\n                    }\n                    None\n                });\n                let c = file.pread(200, c).unwrap(); // Past EOF\n                                                     // Reading past EOF succeeds with 0 bytes read\n                wait_completion_ok(io.as_ref(), &c);\n                assert_eq!(bytes_read.load(Ordering::SeqCst), 0);\n            }));\n        }\n\n        for h in handles {\n            h.join().unwrap();\n        }\n    }\n\n    shuttle_io_test!(read_past_eof, test_read_past_eof_impl);\n\n    /// Test empty write operations.\n    fn test_empty_write_impl<F: IOFactory>(factory: F) {\n        let io = factory.create();\n        let path = factory.temp_dir().join(\"test.db\");\n        let file = io\n            .open_file(path.to_str().unwrap(), OpenFlags::Create, false)\n            .unwrap();\n\n        let mut handles = vec![];\n\n        for _ in 0..3 {\n            let file = file.clone();\n            let io = io.clone();\n            handles.push(thread::spawn(move || {\n                // Empty buffer write\n                let buf = Arc::new(Buffer::new(vec![]));\n                let c = Completion::new_write(|_| {});\n                let c = file.pwrite(0, buf, c).unwrap();\n                wait_completion_ok(io.as_ref(), &c);\n            }));\n        }\n\n        for h in handles {\n            h.join().unwrap();\n        }\n\n        assert_eq!(file.size().unwrap(), 0);\n    }\n\n    shuttle_io_test!(empty_write, test_empty_write_impl);\n\n    /// Test sync operations under concurrency.\n    fn test_concurrent_sync_impl<F: IOFactory>(factory: F) {\n        let io = factory.create();\n        let path = factory.temp_dir().join(\"test.db\");\n        let file = io\n            .open_file(path.to_str().unwrap(), OpenFlags::Create, false)\n            .unwrap();\n\n        // Write some data first\n        let data = vec![0xFF; 1000];\n        let buf = Arc::new(Buffer::new(data));\n        let c = Completion::new_write(|_| {});\n        let c = file.pwrite(0, buf, c).unwrap();\n        wait_completion_ok(io.as_ref(), &c);\n\n        let mut handles = vec![];\n\n        // Multiple sync calls concurrently\n        for _ in 0..3 {\n            let file = file.clone();\n            let io = io.clone();\n            handles.push(thread::spawn(move || {\n                let c = Completion::new_sync(|_| {});\n                let c = file.sync(c, FileSyncType::Fsync).unwrap();\n                wait_completion_ok(io.as_ref(), &c);\n            }));\n        }\n\n        for h in handles {\n            h.join().unwrap();\n        }\n    }\n\n    shuttle_io_test!(concurrent_sync, test_concurrent_sync_impl);\n\n    /// Test concurrent open of the same file returns same file instance.\n    fn test_concurrent_open_same_file_impl<F: IOFactory>(factory: F) {\n        let io = factory.create();\n        let path = factory.temp_dir().join(\"shared.db\");\n\n        // Create file first\n        let _ = io\n            .open_file(path.to_str().unwrap(), OpenFlags::Create, false)\n            .unwrap();\n\n        let mut handles = vec![];\n\n        for _ in 0..3 {\n            let io = io.clone();\n            let path = path.clone();\n            handles.push(thread::spawn(move || {\n                let file = io\n                    .open_file(path.to_str().unwrap(), OpenFlags::None, false)\n                    .unwrap();\n                thread::yield_now();\n                // Write a byte to prove we got a valid file\n                let buf = Arc::new(Buffer::new(vec![0xAA]));\n                let c = Completion::new_write(|_| {});\n                let c = file.pwrite(0, buf, c).unwrap();\n                wait_completion_ok(io.as_ref(), &c);\n            }));\n        }\n\n        for h in handles {\n            h.join().unwrap();\n        }\n    }\n\n    shuttle_io_test!(\n        concurrent_open_same_file,\n        test_concurrent_open_same_file_impl\n    );\n\n    /// Test file removal while concurrent access.\n    fn test_file_remove_concurrent_impl<F: IOFactory>(factory: F) {\n        let io = factory.create();\n        let base = factory.temp_dir();\n\n        // Create multiple files\n        for i in 0..3 {\n            let path = base.join(format!(\"remove_{}.db\", i));\n            let file = io\n                .open_file(path.to_str().unwrap(), OpenFlags::Create, false)\n                .unwrap();\n            let buf = Arc::new(Buffer::new(vec![0xFF; 100]));\n            let c = Completion::new_write(|_| {});\n            let c = file.pwrite(0, buf, c).unwrap();\n            wait_completion_ok(io.as_ref(), &c);\n        }\n\n        let mut handles = vec![];\n\n        // Remove files concurrently\n        for i in 0..3 {\n            let io = io.clone();\n            let base = base.clone();\n            handles.push(thread::spawn(move || {\n                let path = base.join(format!(\"remove_{}.db\", i));\n                io.remove_file(path.to_str().unwrap()).unwrap();\n            }));\n        }\n\n        for h in handles {\n            h.join().unwrap();\n        }\n    }\n\n    shuttle_io_test!(file_remove_concurrent, test_file_remove_concurrent_impl);\n\n    /// Test write spanning multiple internal pages.\n    fn test_large_write_concurrent_impl<F: IOFactory>(factory: F) {\n        let io = factory.create();\n        let path = factory.temp_dir().join(\"test.db\");\n        let file = io\n            .open_file(path.to_str().unwrap(), OpenFlags::Create, false)\n            .unwrap();\n\n        let mut handles = vec![];\n\n        // Multiple threads write large buffers that span multiple pages\n        for i in 0..2 {\n            let file = file.clone();\n            let io = io.clone();\n            handles.push(thread::spawn(move || {\n                // Write 10000 bytes (spans multiple 4096-byte pages)\n                let data = vec![(i + 1) as u8; 10000];\n                let buf = Arc::new(Buffer::new(data));\n                let c = Completion::new_write(|_| {});\n                let c = file.pwrite((i * 10000) as u64, buf, c).unwrap();\n                wait_completion_ok(io.as_ref(), &c);\n            }));\n        }\n\n        for h in handles {\n            h.join().unwrap();\n        }\n\n        assert_eq!(file.size().unwrap(), 20000);\n\n        // Read back and verify each segment contains correct data\n        for i in 0..2 {\n            let read_buf = Arc::new(Buffer::new_temporary(10000));\n            let pos = (i * 10000) as u64;\n            let c = Completion::new_read(read_buf.clone(), |_| None);\n            let c = file.pread(pos, c).unwrap();\n            wait_completion_ok(io.as_ref(), &c);\n\n            let expected_byte = (i + 1) as u8;\n            assert!(\n                read_buf.as_slice().iter().all(|&b| b == expected_byte),\n                \"all bytes at offset {} should be {:#x}\",\n                pos,\n                expected_byte\n            );\n        }\n    }\n\n    shuttle_io_test!(large_write_concurrent, test_large_write_concurrent_impl);\n\n    /// Test has_hole and punch_hole under concurrency.\n    /// Note: Only runs on MemoryIO as hole operations are not supported on all backends.\n    fn test_hole_operations_concurrent_impl<F: IOFactory>(factory: F) {\n        let io = factory.create();\n        let path = factory.temp_dir().join(\"test.db\");\n        let file = io\n            .open_file(path.to_str().unwrap(), OpenFlags::Create, false)\n            .unwrap();\n\n        // Write data spanning multiple pages (at least 3 pages = 12288 bytes)\n        let data = vec![0xFF; 16384];\n        let buf = Arc::new(Buffer::new(data));\n        let c = Completion::new_write(|_| {});\n        let c = file.pwrite(0, buf, c).unwrap();\n        wait_completion_ok(io.as_ref(), &c);\n\n        let mut handles = vec![];\n\n        // Thread 1: punch holes\n        {\n            let file = file.clone();\n            handles.push(thread::spawn(move || {\n                // Punch hole in middle page (page-aligned)\n                file.punch_hole(4096, 4096).unwrap();\n            }));\n        }\n\n        // Thread 2: check for holes\n        {\n            let file = file.clone();\n            handles.push(thread::spawn(move || {\n                // Check various regions\n                let has_hole = file.has_hole(0, 4096).unwrap();\n                assert!(!has_hole);\n                let _ = file.has_hole(4096, 4096).unwrap();\n                let has_hole = file.has_hole(8192, 4096).unwrap();\n                assert!(!has_hole);\n            }));\n        }\n\n        for h in handles {\n            h.join().unwrap();\n        }\n    }\n\n    // hole_operations only runs on MemoryIO since not all backends support holes\n    #[test]\n    fn shuttle_hole_operations_concurrent_memory() {\n        shuttle::check_random(\n            || test_hole_operations_concurrent_impl(MemoryIOFactory::new()),\n            1000,\n        );\n    }\n\n    /// Test that partial reads work correctly at EOF boundary.\n    fn test_partial_read_at_eof_impl<F: IOFactory>(factory: F) {\n        let io = factory.create();\n        let path = factory.temp_dir().join(\"test.db\");\n        let file = io\n            .open_file(path.to_str().unwrap(), OpenFlags::Create, false)\n            .unwrap();\n\n        // Write exactly 150 bytes\n        let data = vec![0xAB; 150];\n        let buf = Arc::new(Buffer::new(data));\n        let c = Completion::new_write(|_| {});\n        let c = file.pwrite(0, buf, c).unwrap();\n        wait_completion_ok(io.as_ref(), &c);\n\n        let mut handles = vec![];\n\n        // Multiple threads try to read 100 bytes starting at offset 100\n        // Should only get 50 bytes back\n        for _ in 0..3 {\n            let file = file.clone();\n            let io = io.clone();\n            handles.push(thread::spawn(move || {\n                let read_buf = Arc::new(Buffer::new_temporary(100));\n                let bytes_read = Arc::new(AtomicUsize::new(999));\n                let bytes_read_clone = bytes_read.clone();\n                let c = Completion::new_read(read_buf.clone(), move |res| {\n                    if let Ok((_, n)) = res {\n                        bytes_read_clone.store(n as usize, Ordering::SeqCst);\n                    }\n                    None\n                });\n                let c = file.pread(100, c).unwrap();\n                wait_completion_ok(io.as_ref(), &c);\n\n                // Should read exactly 50 bytes (150 - 100)\n                assert_eq!(bytes_read.load(Ordering::SeqCst), 50);\n                // Verify the bytes read are correct\n                assert_eq!(&read_buf.as_slice()[..50], &[0xAB; 50]);\n            }));\n        }\n\n        for h in handles {\n            h.join().unwrap();\n        }\n    }\n\n    shuttle_io_test!(partial_read_at_eof, test_partial_read_at_eof_impl);\n\n    /// Test empty pwritev.\n    fn test_empty_pwritev_impl<F: IOFactory>(factory: F) {\n        let io = factory.create();\n        let path = factory.temp_dir().join(\"test.db\");\n        let file = io\n            .open_file(path.to_str().unwrap(), OpenFlags::Create, false)\n            .unwrap();\n\n        let mut handles = vec![];\n\n        for _ in 0..3 {\n            let file = file.clone();\n            let io = io.clone();\n            handles.push(thread::spawn(move || {\n                let bufs: Vec<Arc<Buffer>> = vec![];\n                let c = Completion::new_write(|_| {});\n                let c = file.pwritev(0, bufs, c).unwrap();\n                wait_completion_ok(io.as_ref(), &c);\n            }));\n        }\n\n        for h in handles {\n            h.join().unwrap();\n        }\n    }\n\n    shuttle_io_test!(empty_pwritev, test_empty_pwritev_impl);\n\n    /// Test error case: opening non-existent file without Create flag.\n    fn test_open_nonexistent_without_create_impl<F: IOFactory>(factory: F) {\n        let io = factory.create();\n        let base = factory.temp_dir();\n\n        let mut handles = vec![];\n\n        for i in 0..3 {\n            let io = io.clone();\n            let base = base.clone();\n            handles.push(thread::spawn(move || {\n                let path = base.join(format!(\"nonexistent_{}.db\", i));\n                let result = io.open_file(path.to_str().unwrap(), OpenFlags::None, false);\n                assert!(result.is_err());\n            }));\n        }\n\n        for h in handles {\n            h.join().unwrap();\n        }\n    }\n\n    shuttle_io_test!(\n        open_nonexistent_without_create,\n        test_open_nonexistent_without_create_impl\n    );\n\n    /// Test concurrent writes to overlapping regions.\n    /// This tests that the final state is consistent (one of the writes wins).\n    fn test_concurrent_overlapping_writes_impl<F: IOFactory>(factory: F) {\n        let io = factory.create();\n        let path = factory.temp_dir().join(\"test.db\");\n        let file = io\n            .open_file(path.to_str().unwrap(), OpenFlags::Create, false)\n            .unwrap();\n\n        let write_complete = Arc::new(AtomicUsize::new(0));\n        let mut handles = vec![];\n\n        // Multiple threads write to the same offset\n        for i in 0..3 {\n            let file = file.clone();\n            let io = io.clone();\n            let write_complete = write_complete.clone();\n            handles.push(thread::spawn(move || {\n                let data = vec![(i + 1) as u8; 100];\n                let buf = Arc::new(Buffer::new(data));\n                let write_complete_clone = write_complete.clone();\n                let c = Completion::new_write(move |_| {\n                    write_complete_clone.fetch_add(1, Ordering::SeqCst);\n                });\n                let c = file.pwrite(0, buf, c).unwrap();\n                wait_completion_ok(io.as_ref(), &c);\n            }));\n        }\n\n        for h in handles {\n            h.join().unwrap();\n        }\n\n        // All writes should have completed\n        assert_eq!(write_complete.load(Ordering::SeqCst), 3);\n\n        // Read back and verify we got one of the written values\n        let read_buf = Arc::new(Buffer::new_temporary(100));\n        let c = Completion::new_read(read_buf.clone(), |_| None);\n        let c = file.pread(0, c).unwrap();\n        wait_completion_ok(io.as_ref(), &c);\n\n        let first_byte = read_buf.as_slice()[0];\n        assert!(first_byte == 1 || first_byte == 2 || first_byte == 3);\n\n        // All 100 bytes should be the same value\n        assert!(read_buf.as_slice().iter().all(|&b| b == first_byte));\n    }\n\n    shuttle_io_test!(\n        concurrent_overlapping_writes,\n        test_concurrent_overlapping_writes_impl\n    );\n}\n"
  },
  {
    "path": "core/io/unix.rs",
    "content": "use super::{Completion, File, OpenFlags, IO};\nuse crate::error::{io_error, CompletionError, LimboError};\nuse crate::io::clock::{Clock, DefaultClock, MonotonicInstant, WallClockInstant};\nuse crate::io::common;\nuse crate::io::FileSyncType;\nuse crate::Result;\nuse crate::sync::Mutex;\nuse rustix::{\n    fd::{AsFd, AsRawFd},\n    fs::{self, FlockOperation},\n};\nuse std::os::fd::RawFd;\n\nuse std::{io::ErrorKind, sync::Arc};\n#[cfg(feature = \"fs\")]\nuse tracing::debug;\nuse tracing::{instrument, trace, Level};\n\npub struct UnixIO {}\n\nimpl UnixIO {\n    #[cfg(feature = \"fs\")]\n    pub fn new() -> Result<Self> {\n        debug!(\"Using IO backend 'syscall'\");\n        Ok(Self {})\n    }\n}\n\nimpl Clock for UnixIO {\n    fn current_time_monotonic(&self) -> MonotonicInstant {\n        DefaultClock.current_time_monotonic()\n    }\n\n    fn current_time_wall_clock(&self) -> WallClockInstant {\n        DefaultClock.current_time_wall_clock()\n    }\n}\n\nfn try_pwritev_raw(\n    fd: RawFd,\n    off: u64,\n    bufs: &[Arc<crate::Buffer>],\n    start_idx: usize,\n    start_off: usize,\n) -> std::io::Result<usize> {\n    const MAX_IOV: usize = 1024;\n    let iov_len = std::cmp::min(bufs.len() - start_idx, MAX_IOV);\n    let mut iov: Vec<libc::iovec> = Vec::with_capacity(iov_len);\n\n    let mut last_end: Option<(*const u8, usize)> = None;\n    let mut iov_count = 0;\n    for (i, b) in bufs.iter().enumerate().skip(start_idx).take(iov_len) {\n        let s = b.as_slice();\n        let slice = if i == start_idx { &s[start_off..] } else { s };\n        let ptr = slice.as_ptr();\n        let len = slice.len();\n\n        if let Some((last_ptr, last_len)) = last_end {\n            // Check if this buffer is adjacent to the last\n            if unsafe { last_ptr.add(last_len) } == ptr {\n                // Extend the last iovec instead of adding new\n                iov[iov_count - 1].iov_len += len;\n                last_end = Some((last_ptr, last_len + len));\n                continue;\n            }\n        }\n        last_end = Some((ptr, len));\n        iov_count += 1;\n        iov.push(libc::iovec {\n            iov_base: ptr as *mut libc::c_void,\n            iov_len: len,\n        });\n    }\n    // On Android, off_t is i32. Cast to libc::off_t instead of hardcoding i64 for portability.\n    let n = if iov.len().eq(&1) {\n        unsafe {\n            libc::pwrite(\n                fd,\n                iov[0].iov_base as *const libc::c_void,\n                iov[0].iov_len,\n                off as libc::off_t,\n            )\n        }\n    } else {\n        unsafe { libc::pwritev(fd, iov.as_ptr(), iov.len() as i32, off as libc::off_t) }\n    };\n    if n < 0 {\n        Err(std::io::Error::last_os_error())\n    } else {\n        Ok(n as usize)\n    }\n}\n\nimpl IO for UnixIO {\n    fn open_file(&self, path: &str, flags: OpenFlags, _direct: bool) -> Result<Arc<dyn File>> {\n        trace!(\"open_file(path = {})\", path);\n        let mut file = std::fs::File::options();\n        file.read(true);\n\n        if !flags.contains(OpenFlags::ReadOnly) {\n            file.write(true);\n            file.create(flags.contains(OpenFlags::Create));\n        }\n\n        let file = file.open(path).map_err(|e| io_error(e, \"open\"))?;\n\n        #[allow(clippy::arc_with_non_send_sync)]\n        let unix_file = Arc::new(UnixFile {\n            file: Arc::new(Mutex::new(file)),\n        });\n        if std::env::var(common::ENV_DISABLE_FILE_LOCK).is_err()\n            && !flags.contains(OpenFlags::ReadOnly)\n        {\n            unix_file.lock_file(true)?;\n        }\n        Ok(unix_file)\n    }\n\n    fn remove_file(&self, path: &str) -> Result<()> {\n        std::fs::remove_file(path).map_err(|e| io_error(e, \"remove_file\"))?;\n        Ok(())\n    }\n\n    #[instrument(err, skip_all, level = Level::TRACE)]\n    fn step(&self) -> Result<()> {\n        Ok(())\n    }\n}\n\npub struct UnixFile {\n    file: Arc<Mutex<std::fs::File>>,\n}\n\nimpl File for UnixFile {\n    fn lock_file(&self, exclusive: bool) -> Result<()> {\n        let fd = self.file.lock();\n        let fd = fd.as_fd();\n        // F_SETLK is a non-blocking lock. The lock will be released when the file is closed\n        // or the process exits or after an explicit unlock.\n        fs::fcntl_lock(\n            fd,\n            if exclusive {\n                FlockOperation::NonBlockingLockExclusive\n            } else {\n                FlockOperation::NonBlockingLockShared\n            },\n        )\n        .map_err(|e| {\n            let io_error = std::io::Error::from(e);\n            let message = match io_error.kind() {\n                ErrorKind::WouldBlock => {\n                    \"Failed locking file. File is locked by another process\".to_string()\n                }\n                _ => format!(\"Failed locking file, {io_error}\"),\n            };\n            LimboError::LockingError(message)\n        })?;\n\n        Ok(())\n    }\n\n    fn unlock_file(&self) -> Result<()> {\n        let fd = self.file.lock();\n        let fd = fd.as_fd();\n        fs::fcntl_lock(fd, FlockOperation::NonBlockingUnlock).map_err(|e| {\n            LimboError::LockingError(format!(\n                \"Failed to release file lock: {}\",\n                std::io::Error::from(e)\n            ))\n        })?;\n        Ok(())\n    }\n\n    #[instrument(err, skip_all, level = Level::TRACE)]\n    fn pread(&self, pos: u64, c: Completion) -> Result<Completion> {\n        let file = self.file.lock();\n        let result = unsafe {\n            let r = c.as_read();\n            let buf = r.buf();\n            let slice = buf.as_mut_slice();\n            libc::pread(\n                file.as_raw_fd(),\n                slice.as_mut_ptr() as *mut libc::c_void,\n                slice.len(),\n                pos as libc::off_t,\n            )\n        };\n        if result == -1 {\n            let e = std::io::Error::last_os_error();\n            Err(io_error(e, \"pread\"))\n        } else {\n            trace!(\"pread n: {}\", result);\n            // Read succeeded immediately\n            c.complete(result as i32);\n            Ok(c)\n        }\n    }\n\n    #[instrument(err, skip_all, level = Level::TRACE)]\n    fn pwrite(&self, pos: u64, buffer: Arc<crate::Buffer>, c: Completion) -> Result<Completion> {\n        let file = self.file.lock();\n        let buf_slice = buffer.as_slice();\n        let total_size = buf_slice.len();\n\n        let mut total_written = 0usize;\n        let mut current_pos = pos;\n\n        while total_written < total_size {\n            let remaining_slice = &buf_slice[total_written..];\n            let result = unsafe {\n                libc::pwrite(\n                    file.as_raw_fd(),\n                    remaining_slice.as_ptr() as *const libc::c_void,\n                    remaining_slice.len(),\n                    current_pos as libc::off_t,\n                )\n            };\n            if result == -1 {\n                let e = std::io::Error::last_os_error();\n                if e.kind() == ErrorKind::Interrupted {\n                    // EINTR, retry without advancing\n                    continue;\n                }\n                return Err(io_error(e, \"pwrite\"));\n            }\n            let written = result as usize;\n            if written == 0 {\n                // Unexpected EOF for regular files\n                return Err(LimboError::CompletionError(CompletionError::IOError(\n                    ErrorKind::UnexpectedEof,\n                    \"pwrite\",\n                )));\n            }\n\n            total_written += written;\n            current_pos += written as u64;\n            trace!(\"pwrite iteration: wrote {written}, total {total_written}/{total_size}\");\n        }\n        trace!(\"pwrite complete: wrote {total_written} bytes\");\n        c.complete(total_written as i32);\n        Ok(c)\n    }\n\n    #[instrument(err, skip_all, level = Level::TRACE)]\n    fn pwritev(\n        &self,\n        pos: u64,\n        buffers: Vec<Arc<crate::Buffer>>,\n        c: Completion,\n    ) -> Result<Completion> {\n        if buffers.len().eq(&1) {\n            // use `pwrite` for single buffer\n            return self.pwrite(pos, buffers[0].clone(), c);\n        }\n\n        let file = self.file.lock();\n        let mut total_written = 0usize;\n        let mut current_pos = pos;\n        let mut buf_idx = 0;\n        let mut buf_offset = 0;\n\n        let total_size: usize = buffers.iter().map(|b| b.len()).sum();\n        while total_written < total_size {\n            match try_pwritev_raw(file.as_raw_fd(), current_pos, &buffers, buf_idx, buf_offset) {\n                Ok(written) => {\n                    if written == 0 {\n                        // Unexpected EOF\n                        return Err(LimboError::CompletionError(CompletionError::IOError(\n                            ErrorKind::UnexpectedEof,\n                            \"pwritev\",\n                        )));\n                    }\n                    total_written += written;\n                    current_pos += written as u64;\n\n                    let mut remaining = written;\n                    while remaining > 0 && buf_idx < buffers.len() {\n                        let buf_remaining = buffers[buf_idx].len() - buf_offset;\n\n                        if remaining >= buf_remaining {\n                            // Consumed rest of current buffer\n                            remaining -= buf_remaining;\n                            buf_idx += 1;\n                            buf_offset = 0;\n                        } else {\n                            // Partial write within current buffer\n                            buf_offset += remaining;\n                            remaining = 0;\n                        }\n                    }\n\n                    trace!(\n                        \"pwritev iteration: wrote {written}, total {total_written}/{total_size}\"\n                    );\n                }\n                Err(e) if e.kind() == ErrorKind::Interrupted => {\n                    // EINTR - retry without advancing\n                    continue;\n                }\n                Err(e) => {\n                    return Err(io_error(e, \"pwritev\"));\n                }\n            }\n        }\n        trace!(\"pwritev complete: wrote {total_written} bytes\");\n        c.complete(total_written as i32);\n        Ok(c)\n    }\n\n    #[instrument(err, skip_all, level = Level::TRACE)]\n    fn sync(&self, c: Completion, sync_type: FileSyncType) -> Result<Completion> {\n        let file = self.file.lock();\n\n        let result = unsafe {\n            #[cfg(target_vendor = \"apple\")]\n            {\n                match sync_type {\n                    FileSyncType::Fsync => libc::fsync(file.as_raw_fd()),\n                    FileSyncType::FullFsync => libc::fcntl(file.as_raw_fd(), libc::F_FULLFSYNC),\n                }\n            }\n            #[cfg(not(target_vendor = \"apple\"))]\n            {\n                // FullFsync has no effect on non-Apple platforms\n                let _ = sync_type;\n                libc::fsync(file.as_raw_fd())\n            }\n        };\n\n        if result == -1 {\n            let e = std::io::Error::last_os_error();\n            Err(io_error(e, \"sync\"))\n        } else {\n            #[cfg(target_vendor = \"apple\")]\n            match sync_type {\n                FileSyncType::FullFsync => trace!(\"fcntl(F_FULLFSYNC)\"),\n                FileSyncType::Fsync => trace!(\"fsync\"),\n            }\n            #[cfg(not(target_vendor = \"apple\"))]\n            trace!(\"fsync\");\n\n            c.complete(0);\n            Ok(c)\n        }\n    }\n\n    #[instrument(err, skip_all, level = Level::TRACE)]\n    fn size(&self) -> Result<u64> {\n        let file = self.file.lock();\n        Ok(file.metadata().map_err(|e| io_error(e, \"metadata\"))?.len())\n    }\n\n    #[instrument(err, skip_all, level = Level::INFO)]\n    fn truncate(&self, len: u64, c: Completion) -> Result<Completion> {\n        let file = self.file.lock();\n        let result = file.set_len(len);\n        match result {\n            Ok(()) => {\n                trace!(\"file truncated to len=({})\", len);\n                c.complete(0);\n                Ok(c)\n            }\n            Err(e) => Err(io_error(e, \"truncate\")),\n        }\n    }\n}\n\nimpl Drop for UnixFile {\n    fn drop(&mut self) {\n        self.unlock_file().expect(\"Failed to unlock file\");\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_multiple_processes_cannot_open_file() {\n        common::tests::test_multiple_processes_cannot_open_file(UnixIO::new);\n    }\n}\n"
  },
  {
    "path": "core/io/vfs.rs",
    "content": "use super::{Buffer, Completion, File, FileSyncType, OpenFlags, IO};\nuse crate::ext::VfsMod;\nuse crate::io::clock::{Clock, DefaultClock, MonotonicInstant, WallClockInstant};\nuse crate::io::CompletionInner;\nuse crate::sync::Arc;\nuse crate::{LimboError, Result};\nuse std::ffi::{c_void, CString};\nuse std::ptr::NonNull;\nuse turso_ext::{BufferRef, IOCallback, SendPtr, VfsFileImpl, VfsImpl};\n\nimpl Clock for VfsMod {\n    fn current_time_monotonic(&self) -> MonotonicInstant {\n        DefaultClock.current_time_monotonic()\n    }\n\n    fn current_time_wall_clock(&self) -> WallClockInstant {\n        DefaultClock.current_time_wall_clock()\n    }\n}\n\nimpl IO for VfsMod {\n    fn open_file(&self, path: &str, flags: OpenFlags, direct: bool) -> Result<Arc<dyn File>> {\n        let c_path = CString::new(path).map_err(|_| {\n            LimboError::ExtensionError(\"Failed to convert path to CString\".to_string())\n        })?;\n        let ctx = self.ctx as *mut c_void;\n        let vfs = unsafe { &*self.ctx };\n        let file = unsafe { (vfs.open)(ctx, c_path.as_ptr(), flags.0, direct) };\n        if file.is_null() {\n            return Err(LimboError::ExtensionError(\"File not found\".to_string()));\n        }\n        Ok(Arc::new(turso_ext::VfsFileImpl::new(file, self.ctx)?))\n    }\n\n    fn remove_file(&self, path: &str) -> Result<()> {\n        let c_path = CString::new(path).map_err(|_| {\n            LimboError::ExtensionError(\"Failed to convert path to CString\".to_string())\n        })?;\n        let ctx = self.ctx as *mut c_void;\n        let vfs = unsafe { &*self.ctx };\n        let result = unsafe { (vfs.remove)(ctx, c_path.as_ptr()) };\n        if !result.is_ok() {\n            return Err(LimboError::ExtensionError(result.to_string()));\n        }\n        Ok(())\n    }\n\n    fn step(&self) -> Result<()> {\n        if self.ctx.is_null() {\n            return Err(LimboError::ExtensionError(\"VFS is null\".to_string()));\n        }\n        let vfs = unsafe { &*self.ctx };\n        let result = unsafe { (vfs.run_once)(vfs.vfs) };\n        if !result.is_ok() {\n            return Err(LimboError::ExtensionError(result.to_string()));\n        }\n        Ok(())\n    }\n\n    fn generate_random_number(&self) -> i64 {\n        if self.ctx.is_null() {\n            return -1;\n        }\n        let vfs = unsafe { &*self.ctx };\n        unsafe { (vfs.gen_random_number)() }\n    }\n}\n\nimpl VfsMod {\n    #[allow(dead_code)] // used in FFI call\n    fn get_current_time(&self) -> String {\n        if self.ctx.is_null() {\n            return \"\".to_string();\n        }\n        unsafe {\n            let vfs = &*self.ctx;\n            let chars = (vfs.current_time)();\n            let cstr = CString::from_raw(chars as *mut _);\n            cstr.to_string_lossy().into_owned()\n        }\n    }\n}\n\n/// # Safety\n/// the callback wrapper in the extension library is FnOnce, so we know\n/// that the into_raw/from_raw contract will hold\nunsafe extern \"C\" fn callback_fn(result: i32, ctx: SendPtr) {\n    let completion = Completion {\n        inner: (Some(Arc::from_raw(ctx.inner().as_ptr() as *mut CompletionInner))),\n    };\n    completion.complete(result);\n}\n\nfn to_callback(c: Completion) -> IOCallback {\n    IOCallback::new(callback_fn, unsafe {\n        NonNull::new_unchecked(Arc::into_raw(c.get_inner().clone()) as *mut c_void)\n    })\n}\n\nimpl File for VfsFileImpl {\n    fn lock_file(&self, exclusive: bool) -> Result<()> {\n        let vfs = unsafe { &*self.vfs };\n        let result = unsafe { (vfs.lock)(self.file, exclusive) };\n        if result.is_ok() {\n            return Err(LimboError::ExtensionError(result.to_string()));\n        }\n        Ok(())\n    }\n\n    fn unlock_file(&self) -> Result<()> {\n        if self.vfs.is_null() {\n            return Err(LimboError::ExtensionError(\"VFS is null\".to_string()));\n        }\n        let vfs = unsafe { &*self.vfs };\n        let result = unsafe { (vfs.unlock)(self.file) };\n        if result.is_ok() {\n            return Err(LimboError::ExtensionError(result.to_string()));\n        }\n        Ok(())\n    }\n\n    fn pread(&self, pos: u64, c: Completion) -> Result<Completion> {\n        if self.vfs.is_null() {\n            c.complete(-1);\n            return Err(LimboError::ExtensionError(\"VFS is null\".to_string()));\n        }\n        let r = c.as_read();\n        let buf = r.buf();\n        let len = buf.len();\n        let cb = to_callback(c.clone());\n        let vfs = unsafe { &*self.vfs };\n        let res = unsafe {\n            (vfs.read)(\n                self.file,\n                BufferRef::new(buf.as_mut_ptr(), len),\n                pos as i64,\n                cb,\n            )\n        };\n        if res.is_error() {\n            return Err(LimboError::ExtensionError(\"pread failed\".to_string()));\n        }\n        Ok(c)\n    }\n\n    fn pwrite(&self, pos: u64, buffer: Arc<Buffer>, c: Completion) -> Result<Completion> {\n        if self.vfs.is_null() {\n            c.complete(-1);\n            return Err(LimboError::ExtensionError(\"VFS is null\".to_string()));\n        }\n        let vfs = unsafe { &*self.vfs };\n        let res = unsafe {\n            let len = buffer.len();\n            let cb = to_callback(c.clone());\n            (vfs.write)(\n                self.file,\n                BufferRef::new(buffer.as_ptr() as *mut u8, len),\n                pos as i64,\n                cb,\n            )\n        };\n        if res.is_error() {\n            return Err(LimboError::ExtensionError(\"pwrite failed\".to_string()));\n        }\n        // Keep the buffer alive until the VFS completion fires — the extension\n        // may process the write asynchronously after this function returns.\n        c.keep_write_buffer_alive(buffer);\n        Ok(c)\n    }\n\n    fn sync(&self, c: Completion, _sync_type: FileSyncType) -> Result<Completion> {\n        if self.vfs.is_null() {\n            c.complete(-1);\n            return Err(LimboError::ExtensionError(\"VFS is null\".to_string()));\n        }\n        let vfs = unsafe { &*self.vfs };\n        let cb = to_callback(c.clone());\n        let res = unsafe { (vfs.sync)(self.file, cb) };\n        if res.is_error() {\n            return Err(LimboError::ExtensionError(\"sync failed\".to_string()));\n        }\n        Ok(c)\n    }\n\n    fn size(&self) -> Result<u64> {\n        let vfs = unsafe { &*self.vfs };\n        let result = unsafe { (vfs.size)(self.file) };\n        if result < 0 {\n            Err(LimboError::ExtensionError(\"size failed\".to_string()))\n        } else {\n            Ok(result as u64)\n        }\n    }\n\n    fn truncate(&self, len: u64, c: Completion) -> Result<Completion> {\n        if self.vfs.is_null() {\n            c.complete(-1);\n            return Err(LimboError::ExtensionError(\"VFS is null\".to_string()));\n        }\n        let vfs = unsafe { &*self.vfs };\n        let cb = to_callback(c.clone());\n        let res = unsafe { (vfs.truncate)(self.file, len as i64, cb) };\n        if res.is_error() {\n            return Err(LimboError::ExtensionError(\"truncate failed\".to_string()));\n        }\n        Ok(c)\n    }\n}\n\nimpl Drop for VfsMod {\n    fn drop(&mut self) {\n        if self.ctx.is_null() {\n            return;\n        }\n        unsafe {\n            let _ = Box::from_raw(self.ctx as *mut VfsImpl);\n        }\n    }\n}\n"
  },
  {
    "path": "core/io/win_iocp.rs",
    "content": "// Windows IOCP Cycle\n// ===================\n//\n//                                   pread/pwrite\n//                                        |\n//                                        |      Get Packet\n//                  Completion -----> IO Packet <-----------|\n//                                     |  |                 |\n//                         |<-- Track -|  |                 |\n//                         |              |                 |\n//                    ==========      Issuing IO        ==========\n//                    [||||||||]        queue           ||||||||||\n//                    ==========          |             ==========\n//                     Tracked        ( Windows )     Free IO Packets\n//                     Packets            |                 |\n//                         |              |                 |\n//              Cancel     |   Untrack    |    -->(abort)   |\n//            ------------>|===========> Step ..............|\n//                         |              |                 |\n//                         |              |                 |\n//                         |           Io Completed         |\n//                         |              |                 |\n//                         |   Untrack    |      Reuse      |\n//                         |-----------> Step ------------->|\n//                                        |      Packet\n//                                        |\n//                                   To Completion\n//                                        -->(complete/error)\n//\n//\n// Assumption\n// ==========\n// - The IOPacket should have one reference just after withdrawing and before deposit\n//   back to object pools.\n// - The only place that should forget IO Packet should be in process queue step\n//   OR failure cases just after issueing IO.\n// - in Sync, IO Pakcet should not be touched, it should be handled in -and only in-\n//  `process_packet_from_iocp`\n\nuse crate::error::io_error;\nuse crate::io::clock::{DefaultClock, MonotonicInstant, WallClockInstant};\nuse crate::io::common;\nuse crate::sync::Arc;\nuse crate::sync::Mutex;\nuse crate::{Clock, Completion, CompletionError, File, LimboError, OpenFlags, Result, IO};\n\nuse smallvec::SmallVec;\nuse std::collections::{HashMap, VecDeque};\nuse std::error::Error;\nuse std::ffi::OsString;\nuse std::os::windows::ffi::OsStringExt;\nuse std::ptr::NonNull;\nuse windows_sys::core::BOOL;\nuse windows_sys::Win32::System::Diagnostics::Debug::{\n    FormatMessageW, FORMAT_MESSAGE_ALLOCATE_BUFFER, FORMAT_MESSAGE_FROM_SYSTEM,\n    FORMAT_MESSAGE_IGNORE_INSERTS,\n};\n\nuse std::{io, mem, ptr};\nuse tracing::{debug, instrument, trace, warn, Level};\n\nuse super::FileSyncType;\nuse crate::io::completions::CompletionInner;\nuse windows_sys::Win32::Foundation::{\n    CloseHandle, GetLastError, LocalFree, ERROR_HANDLE_EOF, ERROR_IO_PENDING,\n    ERROR_OPERATION_ABORTED, FALSE, GENERIC_READ, GENERIC_WRITE, HANDLE, INVALID_HANDLE_VALUE,\n    TRUE, WAIT_TIMEOUT,\n};\nuse windows_sys::Win32::Storage::FileSystem::{\n    CreateFileW, FileEndOfFileInfo, FlushFileBuffers, GetFileSizeEx, LockFileEx, ReadFile,\n    SetFileInformationByHandle, UnlockFileEx, WriteFile, FILE_END_OF_FILE_INFO,\n    FILE_FLAG_NO_BUFFERING, FILE_FLAG_OVERLAPPED, FILE_FLAG_WRITE_THROUGH, FILE_SHARE_DELETE,\n    FILE_SHARE_READ, FILE_SHARE_WRITE, LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY,\n    OPEN_ALWAYS, OPEN_EXISTING,\n};\nuse windows_sys::Win32::System::IO::{\n    CancelIoEx, CreateIoCompletionPort, GetOverlappedResult, GetQueuedCompletionStatus, OVERLAPPED,\n    OVERLAPPED_0, OVERLAPPED_0_0,\n};\n\n// Constants\n\nconst CACHING_CAPACITY: usize = 128;\n//TODO: enable this or remove when direct IO stabilized\nconst ENABLE_DIRECT_IO: bool = false;\n//TODO: enable this or remove when windows locking stabilized\nconst ENABLE_LOCK_ON_OPEN: bool = false;\n\n// Types\n\n#[derive(Clone)]\nstruct IoContext {\n    file_handle: HANDLE,\n    io_packet: IoPacket,\n}\n\nenum GetIOCPPacketError {\n    Empty,\n    SystemError(u32),\n    Aborted,\n    InvalidIO,\n}\n\n#[repr(C)]\n#[derive(Debug)]\nenum IoKind {\n    Write(Arc<crate::Buffer>),\n    Read,\n    Lock,\n    Unlock,\n    Unknown,\n}\n\n#[repr(C)]\nstruct IoOverlappedPacket {\n    overlapped: OVERLAPPED,\n    completion: Option<Completion>,\n    kind: IoKind,\n}\n\nunsafe impl Send for IoOverlappedPacket {}\nunsafe impl Sync for IoOverlappedPacket {}\n\nimpl std::fmt::Debug for IoOverlappedPacket {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        writeln!(f, \"IoOverlappedPacket {{\")?;\n        writeln!(f, \"-- completion: {:?} \", self.completion)?;\n        writeln!(f, \"-- kind: {:?} \", self.kind)?;\n        unsafe {\n            writeln!(\n                f,\n                \"-- offset: {:?} \",\n                self.overlapped.Anonymous.Anonymous.Offset\n            )?;\n            writeln!(\n                f,\n                \"-- offsetHigh: {:?} \",\n                self.overlapped.Anonymous.Anonymous.OffsetHigh\n            )?;\n        }\n        writeln!(f, \"}}\")?;\n\n        Ok(())\n    }\n}\n\ntype IoPacket = Arc<IoOverlappedPacket>;\ntype CompletionKey = *const CompletionInner;\n\n// Functions\n#[inline]\nfn get_unique_key_from_completion(c: &Completion) -> CompletionKey {\n    Arc::as_ptr(c.get_inner())\n}\n\n#[inline]\nfn get_generic_limboerror_from_last_os_err() -> LimboError {\n    get_generic_limboerror_from_os_err(unsafe { GetLastError() })\n}\n\n#[inline]\nfn get_generic_limboerror_from_os_err(err: u32) -> LimboError {\n    let mut buffer: *mut u16 = ptr::null_mut();\n    unsafe {\n        let size = FormatMessageW(\n            FORMAT_MESSAGE_ALLOCATE_BUFFER\n                | FORMAT_MESSAGE_FROM_SYSTEM\n                | FORMAT_MESSAGE_IGNORE_INSERTS,\n            ptr::null(),\n            err,\n            0,\n            (&raw mut buffer).cast(),\n            0,\n            ptr::null(),\n        );\n\n        if buffer.is_null() || size == 0 {\n            return LimboError::InternalError(format!(\"Windows Error: [{err}]\"));\n        }\n\n        let Ok(size) = size.try_into() else {\n            LocalFree(buffer.cast());\n            return LimboError::InternalError(format!(\"Windows Error: [{err}]\"));\n        };\n\n        let buffer_slice = std::slice::from_raw_parts(buffer, size);\n        let string = OsString::from_wide(buffer_slice);\n\n        LocalFree(buffer.cast());\n\n        let Ok(string) = string.into_string() else {\n            return LimboError::InternalError(format!(\"Windows Error: [{err}]\"));\n        };\n\n        LimboError::InternalError(format!(\"Windows Error: [{err}]{string}\"))\n    }\n}\n\n#[inline]\nfn get_limboerror_from_std_error(err: impl Error) -> LimboError {\n    LimboError::InternalError(err.to_string())\n}\n\n// Windows IOCP\n\npub struct WindowsIOCP {\n    instance: Arc<InnerWindowsIOCP>,\n}\n\nimpl WindowsIOCP {\n    pub fn new() -> Result<Self> {\n        debug!(\"Using IO backend 'win_iocp'\");\n\n        let iocp_queue_handle =\n            unsafe { CreateIoCompletionPort(INVALID_HANDLE_VALUE, ptr::null_mut(), 0, 0) };\n        if iocp_queue_handle == INVALID_HANDLE_VALUE {\n            return Err(LimboError::NullValue);\n        }\n        Ok(Self {\n            instance: InnerWindowsIOCP::new(iocp_queue_handle),\n        })\n    }\n}\n\nunsafe impl Send for WindowsIOCP {}\nunsafe impl Sync for WindowsIOCP {}\ncrate::assert::assert_send_sync!(WindowsIOCP);\n\nimpl IO for WindowsIOCP {\n    #[instrument(skip_all, level = Level::TRACE)]\n    fn open_file(\n        &self,\n        file_path: &str,\n        open_flags: OpenFlags,\n        direct_access: bool,\n    ) -> Result<Arc<dyn File>> {\n        debug!(\"open_file(path = {})\", file_path);\n\n        let path_unicode: SmallVec<[u16; 1024]> = SmallVec::new();\n\n        let unicode_path =\n            file_path\n                .encode_utf16()\n                .chain(std::iter::once(0))\n                .fold(path_unicode, |mut acc, v| {\n                    acc.push(v);\n                    acc\n                });\n\n        let mut desired_access = 0;\n        let mut creation_disposition = 0;\n\n        desired_access |= if open_flags.contains(OpenFlags::ReadOnly) {\n            GENERIC_READ\n        } else {\n            GENERIC_WRITE | GENERIC_READ\n        };\n\n        creation_disposition |= if open_flags.contains(OpenFlags::Create) {\n            OPEN_ALWAYS\n        } else {\n            OPEN_EXISTING\n        };\n\n        let flags_and_attributes = if ENABLE_DIRECT_IO && direct_access {\n            FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH\n        } else {\n            FILE_FLAG_OVERLAPPED\n        };\n\n        let shared_mode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;\n\n        unsafe {\n            let file_handle = CreateFileW(\n                unicode_path.as_ptr(),\n                desired_access,\n                shared_mode,\n                ptr::null(),\n                creation_disposition,\n                flags_and_attributes,\n                ptr::null_mut(),\n            );\n\n            if file_handle == INVALID_HANDLE_VALUE {\n                return Err(get_generic_limboerror_from_last_os_err());\n            };\n\n            let windows_file = Arc::new(WindowsFile {\n                file_handle,\n                parent_io: self.instance.clone(),\n            });\n\n            // Bind file to IOCP\n            let result = CreateIoCompletionPort(file_handle, self.instance.iocp_queue_handle, 0, 0);\n\n            if result.is_null() {\n                return Err(get_generic_limboerror_from_last_os_err());\n            };\n\n            if ENABLE_LOCK_ON_OPEN\n                && (std::env::var(common::ENV_DISABLE_FILE_LOCK).is_err()\n                    || !open_flags.contains(OpenFlags::ReadOnly))\n            {\n                windows_file.lock_file(true)?;\n            }\n\n            Ok(windows_file)\n        }\n    }\n\n    #[instrument(err, skip_all, level = Level::TRACE)]\n    fn remove_file(&self, file_path: &str) -> Result<()> {\n        trace!(\"remove_file(path = {})\", file_path);\n        std::fs::remove_file(file_path).map_err(|e| io_error(e, \"remove-file\"))\n    }\n\n    #[instrument(err, skip_all, level = Level::TRACE)]\n    fn cancel(&self, completions: &[Completion]) -> Result<()> {\n        for cmpl in completions {\n            trace!(\"cancelling {}\", get_unique_key_from_completion(cmpl).addr());\n            let mut succeeded = false;\n            if let Some(IoContext {\n                file_handle,\n                io_packet,\n            }) = self.instance.pop_io_context_from_completion(cmpl)\n            {\n                unsafe {\n                    if CancelIoEx(file_handle, &raw const io_packet.overlapped) == TRUE {\n                        // if succeeded the abort will be performed once cancel completed\n                        succeeded = true;\n                    } else {\n                        trace!(\"CancelIoEx failed:{}.. Ignored\", GetLastError());\n                    };\n                }\n            }\n\n            if !succeeded {\n                cmpl.abort();\n            }\n        }\n        Ok(())\n    }\n\n    #[instrument(err, skip_all, level = Level::TRACE)]\n    fn drain(&self) -> Result<()> {\n        trace!(\"I/O drainning..\");\n\n        self.instance.drain()\n    }\n\n    #[instrument(err, skip_all, level = Level::TRACE)]\n    fn step(&self) -> Result<()> {\n        trace!(\"I/O Step..\");\n\n        match self.instance.process_packet_from_iocp() {\n            Err(GetIOCPPacketError::SystemError(code)) => {\n                Err(get_generic_limboerror_from_os_err(code))\n            }\n            Err(GetIOCPPacketError::Aborted)\n            | Err(GetIOCPPacketError::Empty)\n            | Err(GetIOCPPacketError::InvalidIO)\n            | Ok(()) => Ok(()),\n        }\n    }\n}\n\nimpl Clock for WindowsIOCP {\n    fn current_time_monotonic(&self) -> MonotonicInstant {\n        DefaultClock.current_time_monotonic()\n    }\n\n    fn current_time_wall_clock(&self) -> WallClockInstant {\n        DefaultClock.current_time_wall_clock()\n    }\n}\n\n// Inner IOCP\n//\npub struct InnerWindowsIOCP {\n    iocp_queue_handle: HANDLE,\n    free_io_packets: Mutex<VecDeque<IoPacket>>,\n    tracked_io_packets: Mutex<HashMap<CompletionKey, IoContext>>,\n}\n\nunsafe impl Send for InnerWindowsIOCP {}\nunsafe impl Sync for InnerWindowsIOCP {}\ncrate::assert::assert_send_sync!(WindowsFile);\n\nimpl InnerWindowsIOCP {\n    fn new(iocp_handle: HANDLE) -> Arc<Self> {\n        let mut free_packets = VecDeque::with_capacity(CACHING_CAPACITY);\n\n        for _ in 0..CACHING_CAPACITY {\n            free_packets.push_back(Arc::new(IoOverlappedPacket {\n                overlapped: OVERLAPPED::default(),\n                completion: None,\n                kind: IoKind::Unknown,\n            }));\n        }\n\n        Arc::new(Self {\n            iocp_queue_handle: iocp_handle,\n            free_io_packets: Mutex::new(free_packets),\n            tracked_io_packets: Mutex::new(HashMap::with_capacity(CACHING_CAPACITY)),\n        })\n    }\n\n    fn recycle_or_create_io_packet(&self) -> IoPacket {\n        self.free_io_packets.lock().pop_front().unwrap_or_else(|| {\n            Arc::new(IoOverlappedPacket {\n                overlapped: OVERLAPPED::default(),\n                completion: None,\n                kind: IoKind::Unknown,\n            })\n        })\n    }\n\n    fn build_io_packet(\n        &self,\n        completion: Option<Completion>,\n        position: u64,\n        kind: IoKind,\n    ) -> IoPacket {\n        trace!(\"new salvaged overlapped packet. \");\n\n        let mut packet = self.recycle_or_create_io_packet();\n\n        assert!(\n            packet.completion.is_none(),\n            \"New packet should has no completion\"\n        );\n\n        let content =\n            Arc::get_mut(&mut packet).expect(\"This IO Packet should not have references elsewhere\");\n\n        let low_part = position as u32;\n        let high_part = (position >> 32) as u32;\n\n        *content = IoOverlappedPacket {\n            completion,\n            kind,\n            overlapped: OVERLAPPED {\n                Anonymous: OVERLAPPED_0 {\n                    Anonymous: OVERLAPPED_0_0 {\n                        Offset: low_part,\n                        OffsetHigh: high_part,\n                    },\n                },\n                ..Default::default()\n            },\n        };\n        packet\n    }\n\n    fn map_completion_to_io_packet(&self, file_handle: HANDLE, io_packet: IoPacket) -> bool {\n        let Some(completion) = io_packet.completion.as_ref().cloned() else {\n            return false;\n        };\n\n        let mut lock = self.tracked_io_packets.lock();\n\n        let completion_key = get_unique_key_from_completion(&completion);\n\n        if lock.contains_key(&completion_key) {\n            panic!(\"Completion should have one and only one io packet, this should not happen\");\n        }\n\n        let completion_key = get_unique_key_from_completion(&completion);\n        trace!(\"tracked completion for {}\", completion_key.addr());\n        lock.insert(\n            completion_key,\n            IoContext {\n                file_handle,\n                io_packet,\n            },\n        );\n        true\n    }\n\n    fn forget_io_packet(&self, mut io_packet: IoPacket) -> Option<(Option<Completion>, IoKind)> {\n        trace!(\"forget packet and completion\");\n\n        if let Some(completion) = io_packet.completion.as_ref() {\n            // this may be removed earlier in cancel\n            // so this operation is optional if the record exists\n            let _ = self.pop_io_context_from_completion(completion);\n        };\n\n        let internals = Arc::get_mut(&mut io_packet).unwrap();\n        let completion = internals.completion.take();\n        let kind = mem::replace(&mut internals.kind, IoKind::Unknown);\n\n        self.free_io_packets.lock().push_back(io_packet);\n        Some((completion, kind))\n    }\n\n    fn pop_io_context_from_completion(&self, completion: &Completion) -> Option<IoContext> {\n        let key = get_unique_key_from_completion(completion);\n        if let Some((key, context)) = self.tracked_io_packets.lock().remove_entry(&key) {\n            trace!(\"remove completion {} from mapped IO table\", key.addr());\n            return Some(context);\n        }\n        None\n    }\n\n    fn process_packet_from_iocp(&self) -> Result<(), GetIOCPPacketError> {\n        let mut overlapped_ptr = ptr::null_mut();\n        let mut bytes_received = 0;\n        let mut iocp_key = 0;\n\n        let result = unsafe {\n            GetQueuedCompletionStatus(\n                self.iocp_queue_handle,\n                &raw mut bytes_received,\n                &raw mut iocp_key,\n                &raw mut overlapped_ptr,\n                0,\n            )\n        };\n\n        let error = unsafe { GetLastError() };\n\n        let Some(overlapped_ptr) = NonNull::new(overlapped_ptr) else {\n            return Err(match (result, error) {\n                (FALSE, WAIT_TIMEOUT) => GetIOCPPacketError::Empty,\n                (FALSE, e) => GetIOCPPacketError::SystemError(e),\n                (TRUE, _) => GetIOCPPacketError::Aborted,\n                _ => unreachable!(),\n            });\n        };\n\n        let io_packet = unsafe { IoPacket::from_raw(overlapped_ptr.as_ptr().cast()) };\n\n        let data = self\n            .forget_io_packet(io_packet)\n            .ok_or(GetIOCPPacketError::InvalidIO)?;\n\n        if let IoKind::Write(buffer) = data.1 {\n            drop(buffer);\n        }\n\n        let completion = data.0.ok_or(GetIOCPPacketError::InvalidIO)?;\n        match (result, error) {\n            (TRUE, _) => {\n                trace!(\n                    \"completion {} completed\",\n                    get_unique_key_from_completion(&completion).addr()\n                );\n                completion.complete(\n                    bytes_received\n                        .try_into()\n                        .map_err(|_| GetIOCPPacketError::InvalidIO)?,\n                );\n            }\n            (FALSE, ERROR_OPERATION_ABORTED) => {\n                trace!(\n                    \"completion {} cancelled\",\n                    get_unique_key_from_completion(&completion).addr()\n                );\n                completion.abort();\n            }\n            (FALSE, error_code) => {\n                let error = match error_code {\n                    ERROR_HANDLE_EOF => {\n                        io::Error::new(io::ErrorKind::UnexpectedEof, \"Reading past the EOF point\")\n                    }\n                    code => io::Error::from_raw_os_error(\n                        code.try_into().map_err(|_| GetIOCPPacketError::InvalidIO)?,\n                    ),\n                };\n\n                trace!(\n                    \"completion {} errored {error}\",\n                    get_unique_key_from_completion(&completion).addr()\n                );\n\n                completion.error(CompletionError::IOError(\n                    error.kind(),\n                    \"io-error-completion\",\n                ));\n            }\n            (_, _) => unreachable!(),\n        }\n        Ok(())\n    }\n\n    fn drain(&self) -> Result<()> {\n        loop {\n            match self.process_packet_from_iocp() {\n                Err(GetIOCPPacketError::Empty | GetIOCPPacketError::Aborted) => {\n                    break;\n                }\n                Err(GetIOCPPacketError::SystemError(e)) => {\n                    let error = e.try_into().map_err(get_limboerror_from_std_error)?;\n                    let err = std::io::Error::from_raw_os_error(error);\n                    return Err(io_error(err, \"process-io-packet-sys-error\"));\n                }\n                Err(GetIOCPPacketError::InvalidIO) | Ok(()) => {}\n            }\n        }\n        Ok(())\n    }\n}\n\nimpl Drop for InnerWindowsIOCP {\n    fn drop(&mut self) {\n        trace!(\"Dropping Windows IOCP Queue..\");\n\n        self.tracked_io_packets\n            .lock()\n            .drain()\n            .for_each(|(_key, ctx)| {\n                unsafe { CancelIoEx(ctx.file_handle, &raw const ctx.io_packet.overlapped) };\n            });\n\n        let _ = self.drain();\n\n        unsafe {\n            CloseHandle(self.iocp_queue_handle);\n        }\n    }\n}\n\n// Windows File\n\npub struct WindowsFile {\n    file_handle: HANDLE,\n    parent_io: Arc<InnerWindowsIOCP>,\n}\n\nimpl WindowsFile {\n    fn sync_iocp_operation(\n        &self,\n        kind: IoKind,\n        io_function: impl Fn(*mut OVERLAPPED) -> BOOL,\n    ) -> Result<(), u32> {\n        let mut bytes = 0;\n        let packet_io = self.parent_io.build_io_packet(None, 0, kind);\n        let overlapped_ptr = Arc::into_raw(packet_io) as *mut OVERLAPPED;\n        unsafe {\n            let result = io_function(overlapped_ptr);\n            let error = GetLastError();\n            // the io function fails\n            if result == FALSE && error != ERROR_IO_PENDING {\n                let restored_io_packet = Arc::from_raw(overlapped_ptr as *mut IoOverlappedPacket);\n                let _ = self.parent_io.forget_io_packet(restored_io_packet);\n                return Err(GetLastError());\n            }\n\n            // if it is async wait for it\n            if result == FALSE\n                // && error == ERROR_IO_PENDING (just to remember)\n                && GetOverlappedResult(self.file_handle, overlapped_ptr, &raw mut bytes, TRUE)\n                    == FALSE\n            {\n                return Err(GetLastError());\n            }\n        }\n\n        Ok(())\n    }\n\n    fn async_iocp_operation(\n        &self,\n        position: u64,\n        completion: Completion,\n        kind: IoKind,\n\n        io_function: impl Fn(*mut OVERLAPPED) -> BOOL,\n    ) -> Result<Completion> {\n        let packet_io = self\n            .parent_io\n            .build_io_packet(Some(completion.clone()), position, kind);\n\n        let overlapped_ptr = Arc::into_raw(packet_io.clone()) as *mut OVERLAPPED;\n\n        if !self\n            .parent_io\n            .map_completion_to_io_packet(self.file_handle, packet_io)\n        {\n            return Err(LimboError::InternalError(\n                \"Cannot map the completion to I/O Packet\".into(),\n            ));\n        }\n\n        unsafe {\n            if io_function(overlapped_ptr) == FALSE && GetLastError() != ERROR_IO_PENDING {\n                let io_packet = Arc::from_raw(overlapped_ptr as *mut IoOverlappedPacket);\n                let _ = self.parent_io.forget_io_packet(io_packet);\n                return Err(get_generic_limboerror_from_last_os_err());\n            }\n        }\n        Ok(completion)\n    }\n}\n\nunsafe impl Send for WindowsFile {}\nunsafe impl Sync for WindowsFile {}\ncrate::assert::assert_send_sync!(WindowsFile);\n\nimpl File for WindowsFile {\n    #[instrument(err, skip_all, level = Level::TRACE)]\n    fn lock_file(&self, exclusive_access: bool) -> Result<()> {\n        trace!(\n            \"locking file {:08X} [ exclusive: {exclusive_access} ]..\",\n            self.file_handle.addr()\n        );\n\n        let locking_flags = if exclusive_access {\n            LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY\n        } else {\n            LOCKFILE_FAIL_IMMEDIATELY\n        };\n\n        self.sync_iocp_operation(IoKind::Lock, |overlapped| unsafe {\n            LockFileEx(\n                self.file_handle,\n                locking_flags,\n                0,\n                u32::MAX,\n                u32::MAX,\n                overlapped,\n            )\n        })\n        .map_err(|err| {\n            let error = io::Error::from_raw_os_error(err as i32);\n            LimboError::LockingError(error.to_string())\n        })\n    }\n\n    #[instrument(err, skip_all, level = Level::TRACE)]\n    fn unlock_file(&self) -> Result<()> {\n        trace!(\"Unlocking file {:08X}\", self.file_handle.addr());\n        self.sync_iocp_operation(IoKind::Unlock, |overlapped| unsafe {\n            UnlockFileEx(self.file_handle, 0, u32::MAX, u32::MAX, overlapped)\n        })\n        .map_err(|err| {\n            let error = io::Error::from_raw_os_error(err as i32);\n            LimboError::LockingError(error.to_string())\n        })\n    }\n\n    #[instrument(skip(self, completion), level = Level::TRACE)]\n    fn pread(&self, position: u64, completion: Completion) -> Result<Completion> {\n        trace!(\n            \"pread for handle {:08X} with completion {}\",\n            self.file_handle.addr(),\n            get_unique_key_from_completion(&completion).addr()\n        );\n\n        let read_completion = completion.as_read();\n        let read_buffer = read_completion.buf();\n        let read_buffer_ptr = read_buffer.as_mut_ptr();\n        let read_buffer_len = read_buffer\n            .len()\n            .try_into()\n            .map_err(get_limboerror_from_std_error)?;\n\n        self.async_iocp_operation(position, completion, IoKind::Read, |overlapped| unsafe {\n            ReadFile(\n                self.file_handle,\n                read_buffer_ptr,\n                read_buffer_len,\n                ptr::null_mut(),\n                overlapped,\n            )\n        })\n    }\n\n    #[instrument(skip(self, completion, buffer), level = Level::TRACE)]\n    fn pwrite(\n        &self,\n        position: u64,\n        buffer: Arc<crate::Buffer>,\n        completion: Completion,\n    ) -> Result<Completion> {\n        trace!(\n            \"pwrite for handle {:08X} with completion {}\",\n            self.file_handle.addr(),\n            get_unique_key_from_completion(&completion).addr()\n        );\n\n        let buffer_ptr = buffer.as_mut_ptr();\n        let buffer_len = buffer\n            .len()\n            .try_into()\n            .map_err(get_limboerror_from_std_error)?;\n\n        self.async_iocp_operation(\n            position,\n            completion,\n            IoKind::Write(buffer),\n            |overlapped| unsafe {\n                WriteFile(\n                    self.file_handle,\n                    buffer_ptr,\n                    buffer_len,\n                    ptr::null_mut(),\n                    overlapped,\n                )\n            },\n        )\n    }\n\n    #[instrument(err, skip_all, level = Level::TRACE)]\n    fn sync(&self, completion: Completion, _sync_type: FileSyncType) -> Result<Completion> {\n        trace!(\n            \"sync for handle {:08X} with completion {}\",\n            self.file_handle.addr(),\n            get_unique_key_from_completion(&completion).addr()\n        );\n\n        unsafe {\n            if FlushFileBuffers(self.file_handle) == FALSE {\n                return Err(get_generic_limboerror_from_last_os_err());\n            }\n        };\n        completion.complete(0);\n        Ok(completion)\n    }\n\n    #[instrument(err, skip_all, level = Level::TRACE)]\n    fn truncate(&self, length: u64, completion: Completion) -> Result<Completion> {\n        trace!(\n            \"truncate for handle {:08X} with completion {}\",\n            self.file_handle.addr(),\n            get_unique_key_from_completion(&completion).addr()\n        );\n\n        unsafe {\n            let file_info = FILE_END_OF_FILE_INFO {\n                EndOfFile: length.try_into().map_err(get_limboerror_from_std_error)?,\n            };\n\n            if SetFileInformationByHandle(\n                self.file_handle,\n                FileEndOfFileInfo,\n                (&raw const file_info).cast(),\n                size_of_val(&file_info)\n                    .try_into()\n                    .map_err(get_limboerror_from_std_error)?, // CONVERSION SAFETY:\n                                                              // the struct size will not exceed u32\n            ) == FALSE\n            {\n                return Err(get_generic_limboerror_from_last_os_err());\n            }\n        }\n        completion.complete(0);\n        Ok(completion)\n    }\n\n    fn size(&self) -> Result<u64> {\n        let mut filesize = 0;\n\n        unsafe {\n            if GetFileSizeEx(self.file_handle, &raw mut filesize) == FALSE {\n                return Err(get_generic_limboerror_from_last_os_err());\n            }\n        }\n\n        trace!(\"size for handle {:08X} {filesize}\", self.file_handle.addr());\n\n        filesize.try_into().map_err(get_limboerror_from_std_error)\n    }\n}\n\nimpl Drop for WindowsFile {\n    fn drop(&mut self) {\n        trace!(\"dropping handle {:08X}\", self.file_handle.addr());\n\n        if ENABLE_LOCK_ON_OPEN {\n            let _ = self.unlock_file();\n        }\n\n        unsafe {\n            CancelIoEx(self.file_handle, ptr::null());\n            CloseHandle(self.file_handle);\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::sync::Arc;\n\n    use crate::{\n        io::{win_iocp::get_generic_limboerror_from_os_err, TempFile},\n        Buffer, Completion, IO,\n    };\n\n    use super::WindowsIOCP;\n\n    #[test]\n    fn test_file_read_write() {\n        let iocp: Arc<dyn IO> = Arc::new(WindowsIOCP::new().unwrap());\n        let file = TempFile::new(&iocp).unwrap();\n\n        const WRITE: &[u8] = b\"ABCD\";\n\n        let mut vec = vec![];\n        for n in 0..150 {\n            let comp = Completion::new_write(|res| {\n                assert_eq!(res, Ok(4));\n            });\n            let buffer = Arc::new(Buffer::new_temporary(WRITE.len()));\n\n            buffer.as_mut_slice().copy_from_slice(WRITE);\n\n            let ret = file.pwrite(n * WRITE.len() as u64, buffer, comp).unwrap();\n            vec.push(ret);\n        }\n        vec.into_iter().for_each(|c| {\n            iocp.wait_for_completion(c.clone()).unwrap();\n            if c.failed() {\n                panic!();\n            }\n        });\n        let mut vec = vec![];\n\n        for n in 0..150 {\n            let buffer = Arc::new(Buffer::new_temporary(WRITE.len()));\n\n            let comp = Completion::new_read(buffer, |res| {\n                assert_eq!(res.clone().unwrap().1, 4);\n                res.err()\n            });\n\n            let ret = file.pread(n * WRITE.len() as u64, comp).unwrap();\n            vec.push(ret);\n        }\n        vec.iter().for_each(|c| {\n            iocp.wait_for_completion(c.clone()).unwrap();\n        });\n        vec.iter().any(|c| c.failed()).then(|| panic!());\n\n        assert_eq!(file.size().unwrap(), 150 * WRITE.iter().len() as u64);\n    }\n\n    #[test]\n    fn test_error_functions() {\n        assert_eq!(\n            get_generic_limboerror_from_os_err(5).to_string(),\n            String::from(\"Internal error: Windows Error: [5]Access is denied.\\r\\n\")\n        );\n    }\n\n    #[test]\n    fn test_proper_drop() {\n        let write = b\"Abcd\";\n        let iocp: Arc<dyn IO> = Arc::new(WindowsIOCP::new().unwrap());\n        let file = TempFile::new(&iocp).unwrap();\n        let comp = Completion::new_write(|_| {});\n        let buffer = Arc::new(Buffer::new_temporary(write.len()));\n\n        buffer.as_mut_slice().copy_from_slice(write);\n\n        let _ = file.pwrite(0, buffer, comp).unwrap();\n        drop(iocp);\n        drop(file);\n    }\n}\n"
  },
  {
    "path": "core/io/windows.rs",
    "content": "use crate::error::io_error;\nuse crate::io::clock::{DefaultClock, MonotonicInstant, WallClockInstant};\nuse crate::io::FileSyncType;\nuse crate::{Clock, Completion, File, OpenFlags, Result, IO};\nuse crate::sync::RwLock;\nuse std::io::{Read, Seek, Write};\nuse crate::sync::Arc;\nuse tracing::{debug, instrument, trace, Level};\npub struct WindowsIO {}\n\nimpl WindowsIO {\n    pub fn new() -> Result<Self> {\n        debug!(\"Using IO backend 'syscall'\");\n        Ok(Self {})\n    }\n}\n\nimpl IO for WindowsIO {\n    #[instrument(skip_all, level = Level::TRACE)]\n    fn open_file(&self, path: &str, flags: OpenFlags, direct: bool) -> Result<Arc<dyn File>> {\n        trace!(\"open_file(path = {})\", path);\n        let mut file = std::fs::File::options();\n        file.read(true);\n\n        if !flags.contains(OpenFlags::ReadOnly) {\n            file.write(true);\n            file.create(flags.contains(OpenFlags::Create));\n        }\n\n        let file = file.open(path).map_err(|e| io_error(e, \"open\"))?;\n        Ok(Arc::new(WindowsFile {\n            file: RwLock::new(file),\n        }))\n    }\n\n    #[instrument(err, skip_all, level = Level::TRACE)]\n    fn remove_file(&self, path: &str) -> Result<()> {\n        trace!(\"remove_file(path = {})\", path);\n        std::fs::remove_file(path).map_err(|e| io_error(e, \"remove_file\"))?;\n        Ok(())\n    }\n\n    #[instrument(err, skip_all, level = Level::TRACE)]\n    fn step(&self) -> Result<()> {\n        Ok(())\n    }\n}\n\nimpl Clock for WindowsIO {\n    fn current_time_monotonic(&self) -> MonotonicInstant {\n        DefaultClock.current_time_monotonic()\n    }\n\n    fn current_time_wall_clock(&self) -> WallClockInstant {\n        DefaultClock.current_time_wall_clock()\n    }\n}\n\npub struct WindowsFile {\n    file: RwLock<std::fs::File>,\n}\n\nimpl File for WindowsFile {\n    #[instrument(err, skip_all, level = Level::TRACE)]\n    fn lock_file(&self, exclusive: bool) -> Result<()> {\n        unimplemented!()\n    }\n\n    #[instrument(err, skip_all, level = Level::TRACE)]\n    fn unlock_file(&self) -> Result<()> {\n        unimplemented!()\n    }\n\n    #[instrument(skip(self, c), level = Level::TRACE)]\n    fn pread(&self, pos: u64, c: Completion) -> Result<Completion> {\n        let mut file = self.file.write();\n        file.seek(std::io::SeekFrom::Start(pos)).map_err(|e| io_error(e, \"pread\"))?;\n        let nr = {\n            let r = c.as_read();\n            let buf = r.buf();\n            let buf = buf.as_mut_slice();\n            file.read(buf).map_err(|e| io_error(e, \"pread\"))? as i32\n        };\n        c.complete(nr);\n        Ok(c)\n    }\n\n    #[instrument(skip(self, c, buffer), level = Level::TRACE)]\n    fn pwrite(&self, pos: u64, buffer: Arc<crate::Buffer>, c: Completion) -> Result<Completion> {\n        let mut file = self.file.write();\n        file.seek(std::io::SeekFrom::Start(pos)).map_err(|e| io_error(e, \"pwrite\"))?;\n        let buf = buffer.as_slice();\n        file.write_all(buf).map_err(|e| io_error(e, \"pwrite\"))?;\n        c.complete(buffer.len() as i32);\n        Ok(c)\n    }\n\n    #[instrument(err, skip_all, level = Level::TRACE)]\n    fn sync(&self, c: Completion, _sync_type: FileSyncType) -> Result<Completion> {\n        let file = self.file.write();\n        file.sync_all().map_err(|e| io_error(e, \"sync\"))?;\n        c.complete(0);\n        Ok(c)\n    }\n\n    #[instrument(err, skip_all, level = Level::TRACE)]\n    fn truncate(&self, len: u64, c: Completion) -> Result<Completion> {\n        let file = self.file.write();\n        file.set_len(len).map_err(|e| io_error(e, \"truncate\"))?;\n        c.complete(0);\n        Ok(c)\n    }\n\n    fn size(&self) -> Result<u64> {\n        let file = self.file.read();\n        Ok(file.metadata().map_err(|e| io_error(e, \"metadata\"))?.len())\n    }\n}\n"
  },
  {
    "path": "core/json/cache.rs",
    "content": "use std::cell::{Cell, UnsafeCell};\n\nuse crate::types::AsValueRef;\nuse crate::{Value, ValueRef};\n\nuse super::jsonb::Jsonb;\n\nconst JSON_CACHE_SIZE: usize = 4;\n\n#[derive(Debug)]\npub struct JsonCache {\n    entries: [Option<(Value, Jsonb)>; JSON_CACHE_SIZE],\n    age: [usize; JSON_CACHE_SIZE],\n    used: usize,\n    counter: usize,\n}\n\nimpl JsonCache {\n    pub fn new() -> Self {\n        Self {\n            entries: [None, None, None, None],\n            age: [0, 0, 0, 0],\n            used: 0,\n            counter: 0,\n        }\n    }\n\n    fn find_oldest_entry(&self) -> usize {\n        let mut oldest_idx = 0;\n        let mut oldest_age = self.age[0];\n\n        for i in 1..self.used {\n            if self.age[i] < oldest_age {\n                oldest_idx = i;\n                oldest_age = self.age[i];\n            }\n        }\n\n        oldest_idx\n    }\n\n    pub fn insert(&mut self, key: impl AsValueRef, value: &Jsonb) {\n        let key = key.as_value_ref();\n        if self.used < JSON_CACHE_SIZE {\n            self.entries[self.used] = Some((key.to_owned(), value.clone()));\n            self.age[self.used] = self.counter;\n            self.counter += 1;\n            self.used += 1\n        } else {\n            let id = self.find_oldest_entry();\n\n            self.entries[id] = Some((key.to_owned(), value.clone()));\n            self.age[id] = self.counter;\n            self.counter += 1;\n        }\n    }\n\n    pub fn lookup(&mut self, key: impl AsValueRef) -> Option<Jsonb> {\n        let key = key.as_value_ref();\n        for i in (0..self.used).rev() {\n            if let Some((stored_key, value)) = &self.entries[i] {\n                if key == *stored_key {\n                    self.age[i] = self.counter;\n                    self.counter += 1;\n                    let json = value.clone();\n\n                    return Some(json);\n                }\n            }\n        }\n        None\n    }\n\n    pub fn clear(&mut self) {\n        self.counter = 0;\n        self.used = 0;\n    }\n}\n\n#[derive(Debug)]\npub struct JsonCacheCell {\n    inner: UnsafeCell<Option<JsonCache>>,\n    accessed: Cell<bool>,\n}\n\nimpl Default for JsonCacheCell {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl JsonCacheCell {\n    pub fn new() -> Self {\n        Self {\n            inner: UnsafeCell::new(None),\n            accessed: Cell::new(false),\n        }\n    }\n\n    #[cfg(test)]\n    pub fn lookup(&self, key: impl AsValueRef) -> Option<Jsonb> {\n        assert!(!self.accessed.get());\n\n        self.accessed.set(true);\n\n        let result = unsafe {\n            let cache_ptr = self.inner.get();\n            if (*cache_ptr).is_none() {\n                *cache_ptr = Some(JsonCache::new());\n            }\n\n            if let Some(cache) = &mut (*cache_ptr) {\n                cache.lookup(key)\n            } else {\n                None\n            }\n        };\n\n        self.accessed.set(false);\n        result\n    }\n\n    pub fn get_or_insert_with(\n        &self,\n        key: impl AsValueRef,\n        value: impl FnOnce(ValueRef) -> crate::Result<Jsonb>,\n    ) -> crate::Result<Jsonb> {\n        assert!(!self.accessed.get());\n\n        let key = key.as_value_ref();\n        self.accessed.set(true);\n        let result = unsafe {\n            let cache_ptr = self.inner.get();\n            if (*cache_ptr).is_none() {\n                *cache_ptr = Some(JsonCache::new());\n            }\n\n            if let Some(cache) = &mut (*cache_ptr) {\n                if let Some(jsonb) = cache.lookup(key) {\n                    Ok(jsonb)\n                } else {\n                    let result = value(key);\n                    match result {\n                        Ok(json) => {\n                            cache.insert(key, &json);\n                            Ok(json)\n                        }\n                        Err(e) => Err(e),\n                    }\n                }\n            } else {\n                value(key)\n            }\n        };\n        self.accessed.set(false);\n\n        result\n    }\n\n    pub fn clear(&mut self) {\n        assert!(!self.accessed.get());\n        self.accessed.set(true);\n        unsafe {\n            let cache_ptr = self.inner.get();\n            if (*cache_ptr).is_none() {\n                self.accessed.set(false);\n                return;\n            }\n\n            if let Some(cache) = &mut (*cache_ptr) {\n                cache.clear()\n            }\n        }\n        self.accessed.set(false);\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use std::str::FromStr;\n\n    // Helper function to create test Value and Jsonb from JSON string\n    fn create_test_pair(json_str: &str) -> (Value, Jsonb) {\n        // Create Value as text representation of JSON\n        let key = Value::build_text(json_str.to_string());\n\n        // Create Jsonb from the same JSON string\n        let value = Jsonb::from_str(json_str).unwrap();\n\n        (key, value)\n    }\n\n    #[test]\n    fn test_json_cache_new() {\n        let cache = JsonCache::new();\n        assert_eq!(cache.used, 0);\n        assert_eq!(cache.counter, 0);\n        assert_eq!(cache.age, [0, 0, 0, 0]);\n        assert!(cache.entries.iter().all(|entry| entry.is_none()));\n    }\n\n    #[test]\n    fn test_json_cache_insert_and_lookup() {\n        let mut cache = JsonCache::new();\n        let json_str = \"{\\\"test\\\": \\\"value\\\"}\";\n        let (key, value) = create_test_pair(json_str);\n\n        // Insert a value\n        cache.insert(&key, &value);\n\n        // Verify it was inserted\n        assert_eq!(cache.used, 1);\n        assert_eq!(cache.counter, 1);\n\n        // Look it up\n        let result = cache.lookup(&key);\n        assert!(result.is_some());\n        assert_eq!(result.unwrap(), value);\n\n        // Counter should be incremented after lookup\n        assert_eq!(cache.counter, 2);\n    }\n\n    #[test]\n    fn test_json_cache_lookup_nonexistent() {\n        let mut cache = JsonCache::new();\n        let (key, _) = create_test_pair(\"{\\\"id\\\": 123}\");\n\n        // Look up a non-existent key\n        let result = cache.lookup(&key);\n        assert!(result.is_none());\n\n        // Counter should remain unchanged\n        assert_eq!(cache.counter, 0);\n    }\n\n    #[test]\n    fn test_json_cache_multiple_entries() {\n        let mut cache = JsonCache::new();\n\n        // Insert multiple entries\n        let (key1, value1) = create_test_pair(\"{\\\"id\\\": 1}\");\n        let (key2, value2) = create_test_pair(\"{\\\"id\\\": 2}\");\n        let (key3, value3) = create_test_pair(\"{\\\"id\\\": 3}\");\n\n        cache.insert(&key1, &value1);\n        cache.insert(&key2, &value2);\n        cache.insert(&key3, &value3);\n\n        // Verify they were all inserted\n        assert_eq!(cache.used, 3);\n        assert_eq!(cache.counter, 3);\n\n        // Look them up in reverse order\n        let result3 = cache.lookup(&key3);\n        let result2 = cache.lookup(&key2);\n        let result1 = cache.lookup(&key1);\n\n        assert_eq!(result3.unwrap(), value3);\n        assert_eq!(result2.unwrap(), value2);\n        assert_eq!(result1.unwrap(), value1);\n\n        // Counter should be incremented for each lookup\n        assert_eq!(cache.counter, 6);\n    }\n\n    #[test]\n    fn test_json_cache_eviction() {\n        let mut cache = JsonCache::new();\n\n        // Insert more than JSON_CACHE_SIZE entries\n        let (key1, value1) = create_test_pair(\"{\\\"id\\\": 1}\");\n        let (key2, value2) = create_test_pair(\"{\\\"id\\\": 2}\");\n        let (key3, value3) = create_test_pair(\"{\\\"id\\\": 3}\");\n        let (key4, value4) = create_test_pair(\"{\\\"id\\\": 4}\");\n        let (key5, value5) = create_test_pair(\"{\\\"id\\\": 5}\");\n\n        cache.insert(&key1, &value1);\n        cache.insert(&key2, &value2);\n        cache.insert(&key3, &value3);\n        cache.insert(&key4, &value4);\n\n        // Cache is now full\n        assert_eq!(cache.used, 4);\n\n        // Look up key1 to make it the most recently used\n        let _ = cache.lookup(&key1);\n\n        // Insert one more entry - should evict the oldest (key2)\n        cache.insert(&key5, &value5);\n\n        // Cache size should still be JSON_CACHE_SIZE\n        assert_eq!(cache.used, 4);\n\n        // key2 should have been evicted\n        let result2 = cache.lookup(&key2);\n        assert!(result2.is_none());\n\n        // Other entries should still be present\n        assert!(cache.lookup(&key1).is_some());\n        assert!(cache.lookup(&key3).is_some());\n        assert!(cache.lookup(&key4).is_some());\n        assert!(cache.lookup(&key5).is_some());\n    }\n\n    #[test]\n    fn test_json_cache_find_oldest_entry() {\n        let mut cache = JsonCache::new();\n\n        // Insert entries\n        let (key1, value1) = create_test_pair(\"{\\\"id\\\": 1}\");\n        let (key2, value2) = create_test_pair(\"{\\\"id\\\": 2}\");\n        let (key3, value3) = create_test_pair(\"{\\\"id\\\": 3}\");\n\n        cache.insert(&key1, &value1);\n        cache.insert(&key2, &value2);\n        cache.insert(&key3, &value3);\n\n        // key1 should be the oldest\n        assert_eq!(cache.find_oldest_entry(), 0);\n\n        // Access key1 to make it the newest\n        let _ = cache.lookup(&key1);\n\n        // Now key2 should be the oldest\n        assert_eq!(cache.find_oldest_entry(), 1);\n    }\n\n    // Tests for JsonCacheCell\n\n    #[test]\n    fn test_json_cache_cell_new() {\n        let cache_cell = JsonCacheCell::new();\n\n        // Access flag should be false initially\n        assert!(!cache_cell.accessed.get());\n\n        // Inner cache should be None initially\n        unsafe {\n            let inner = &*cache_cell.inner.get();\n            assert!(inner.is_none());\n        }\n    }\n\n    #[test]\n    fn test_json_cache_cell_lookup() {\n        let cache_cell = JsonCacheCell::new();\n        let (key, value) = create_test_pair(\"{\\\"test\\\": \\\"value\\\"}\");\n\n        // First lookup should return None since cache is empty\n        let result = cache_cell.lookup(&key);\n        assert!(result.is_none());\n\n        // Cache should be initialized after first lookup\n        unsafe {\n            let inner = &*cache_cell.inner.get();\n            assert!(inner.is_some());\n        }\n\n        // Access flag should be reset to false\n        assert!(!cache_cell.accessed.get());\n\n        // Insert the value using get_or_insert_with\n        let insert_result = cache_cell.get_or_insert_with(&key, |k| {\n            // Verify that k is the same as our key\n            assert_eq!(k, key);\n            Ok(value.clone())\n        });\n\n        assert!(insert_result.is_ok());\n        assert_eq!(insert_result.unwrap(), value);\n\n        // Access flag should be reset to false\n        assert!(!cache_cell.accessed.get());\n\n        // Lookup should now return the value\n        let lookup_result = cache_cell.lookup(&key);\n        assert!(lookup_result.is_some());\n        assert_eq!(lookup_result.unwrap(), value);\n    }\n\n    #[test]\n    fn test_json_cache_cell_get_or_insert_with_existing() {\n        let cache_cell = JsonCacheCell::new();\n        let (key, value) = create_test_pair(\"{\\\"test\\\": \\\"value\\\"}\");\n\n        // Insert a value\n        let _ = cache_cell.get_or_insert_with(&key, |_| Ok(value.clone()));\n\n        // Counter indicating if the closure was called\n        let closure_called = Cell::new(false);\n\n        // Try to insert again with the same key\n        let result = cache_cell.get_or_insert_with(&key, |_| {\n            closure_called.set(true);\n            Ok(Jsonb::from_str(\"{\\\"test\\\": \\\"value\\\"}\").unwrap())\n        });\n\n        // The closure should not have been called\n        assert!(!closure_called.get());\n\n        // Should return the original value\n        assert_eq!(result.unwrap(), value);\n    }\n\n    #[test]\n    #[should_panic]\n    fn test_json_cache_cell_double_access() {\n        let cache_cell = JsonCacheCell::new();\n        let (key, _) = create_test_pair(\"{\\\"test\\\": \\\"value\\\"}\");\n\n        // Access the cache\n\n        // Set accessed flag to true manually\n        cache_cell.accessed.set(true);\n\n        // This should panic due to double access\n\n        let _ = cache_cell.lookup(&key);\n    }\n\n    #[test]\n    fn test_json_cache_cell_get_or_insert_error_handling() {\n        let cache_cell = JsonCacheCell::new();\n        let (key, _) = create_test_pair(\"{\\\"test\\\": \\\"value\\\"}\");\n\n        // Test error handling\n        let error_result = cache_cell.get_or_insert_with(&key, |_| {\n            // Return an error\n            Err(crate::LimboError::Constraint(\"Test error\".to_string()))\n        });\n\n        // Should propagate the error\n        assert!(error_result.is_err());\n\n        // Access flag should be reset to false\n        assert!(!cache_cell.accessed.get());\n\n        // The entry should not be cached\n        let lookup_result = cache_cell.lookup(&key);\n        assert!(lookup_result.is_none());\n    }\n}\n"
  },
  {
    "path": "core/json/error.rs",
    "content": "use std::fmt::{self, Display};\n\n/// Alias for a `Result` with error type `json5::Error`\npub type Result<T> = std::result::Result<T, Error>;\n\n/// A bare bones error type which currently just collapses all the underlying errors in to a single\n/// string... This is fine for displaying to the user, but not very useful otherwise. Work to be\n/// done here.\n#[derive(Clone, Debug, PartialEq)]\npub enum Error {\n    /// Just shove everything in a single variant for now.\n    Message {\n        /// The error message.\n        msg: String,\n        /// The location of the error, if applicable.\n        location: Option<usize>,\n    },\n}\n\nimpl From<std::io::Error> for Error {\n    fn from(err: std::io::Error) -> Self {\n        Self::Message {\n            msg: err.to_string(),\n            location: None,\n        }\n    }\n}\n\nimpl From<std::str::Utf8Error> for Error {\n    fn from(err: std::str::Utf8Error) -> Self {\n        Self::Message {\n            msg: err.to_string(),\n            location: None,\n        }\n    }\n}\n\nimpl Display for Error {\n    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match self {\n            Self::Message { ref msg, .. } => write!(formatter, \"{msg}\"),\n        }\n    }\n}\n\nimpl std::error::Error for Error {}\n\nimpl From<Error> for crate::LimboError {\n    fn from(err: Error) -> Self {\n        match err {\n            Error::Message { msg, .. } => crate::LimboError::ParseError(msg),\n        }\n    }\n}\n"
  },
  {
    "path": "core/json/jsonb.rs",
    "content": "use crate::json::error::{Error as PError, Result as PResult};\nuse crate::json::Conv;\nuse crate::{bail_parse_error, LimboError, Result};\nuse std::{\n    borrow::Cow,\n    collections::{HashMap, VecDeque},\n    fmt::Write,\n    str::{from_utf8, from_utf8_unchecked},\n};\n\nuse super::path::{JsonPath, PathElement};\n\nconst SIZE_MARKER_8BIT: u8 = 12;\nconst SIZE_MARKER_16BIT: u8 = 13;\nconst SIZE_MARKER_32BIT: u8 = 14;\nconst MAX_JSON_DEPTH: usize = 1000;\nconst INFINITY_CHAR_COUNT: u8 = 8;\n\nconst fn make_whitespace_table() -> [u8; 256] {\n    let mut table = [0u8; 256];\n\n    // Mark whitespace characters\n    table[0x09] = 1; // Tab\n    table[0x0A] = 1; // Line feed\n    table[0x0D] = 1; // Carriage return\n    table[0x20] = 1; // Space\n\n    table\n}\n\nstatic WS_TABLE: [u8; 256] = make_whitespace_table();\n\nconst fn make_character_type_table() -> [u8; 256] {\n    let mut table = [0u8; 256];\n\n    // Mark whitespace characters\n    table[0x09] = 1; // Tab\n    table[0x0A] = 1; // Line feed\n    table[0x0D] = 1; // Carriage return\n    table[0x20] = 1; // Space\n\n    // Mark numeric digits\n    table[0x30] = 2; // 0\n    table[0x31] = 2; // 1\n    table[0x32] = 2; // 2\n    table[0x33] = 2; // 3\n    table[0x34] = 2; // 4\n    table[0x35] = 2; // 5\n    table[0x36] = 2; // 6\n    table[0x37] = 2; // 7\n    table[0x38] = 2; // 8\n    table[0x39] = 2; // 9\n\n    // Mark hex digits (a-f, A-F)\n    table[0x41] = 3; // A\n    table[0x42] = 3; // B\n    table[0x43] = 3; // C\n    table[0x44] = 3; // D\n    table[0x45] = 3; // E\n    table[0x46] = 3; // F\n    table[0x61] = 3; // a\n    table[0x62] = 3; // b\n    table[0x63] = 3; // c\n    table[0x64] = 3; // d\n    table[0x65] = 3; // e\n    table[0x66] = 3; // f\n\n    table\n}\n\nstatic CHARACTER_TYPE: [u8; 256] = make_character_type_table();\n\nconst fn make_character_type_ok_table() -> [u8; 256] {\n    let mut table = [0u8; 256];\n\n    table[0x20] |= 4; // Space\n    table[0x21] |= 4; // !\n                      // Skipping 0x22 (\") as it needs escaping\n    table[0x23] |= 4; // #\n    table[0x24] |= 4; // $\n    table[0x25] |= 4; // %\n    table[0x26] |= 4; // &\n    table[0x27] |= 4; // '\n    table[0x28] |= 4; // (\n    table[0x29] |= 4; // )\n    table[0x2A] |= 4; // *\n    table[0x2B] |= 4; // +\n    table[0x2C] |= 4; // ,\n    table[0x2D] |= 4; // -\n    table[0x2E] |= 4; // .\n    table[0x2F] |= 4; // /\n    table[0x30] |= 4; // 0\n    table[0x31] |= 4; // 1\n    table[0x32] |= 4; // 2\n    table[0x33] |= 4; // 3\n    table[0x34] |= 4; // 4\n    table[0x35] |= 4; // 5\n    table[0x36] |= 4; // 6\n    table[0x37] |= 4; // 7\n    table[0x38] |= 4; // 8\n    table[0x39] |= 4; // 9\n    table[0x3A] |= 4; // :\n    table[0x3B] |= 4; // ;\n    table[0x3C] |= 4; //\n    table[0x3D] |= 4; // =\n    table[0x3E] |= 4; // >\n    table[0x3F] |= 4; // ?\n    table[0x40] |= 4; // @\n    table[0x41] |= 4; // A\n    table[0x42] |= 4; // B\n    table[0x43] |= 4; // C\n    table[0x44] |= 4; // D\n    table[0x45] |= 4; // E\n    table[0x46] |= 4; // F\n    table[0x47] |= 4; // G\n    table[0x48] |= 4; // H\n    table[0x49] |= 4; // I\n    table[0x4A] |= 4; // J\n    table[0x4B] |= 4; // K\n    table[0x4C] |= 4; // L\n    table[0x4D] |= 4; // M\n    table[0x4E] |= 4; // N\n    table[0x4F] |= 4; // O\n    table[0x50] |= 4; // P\n    table[0x51] |= 4; // Q\n    table[0x52] |= 4; // R\n    table[0x53] |= 4; // S\n    table[0x54] |= 4; // T\n    table[0x55] |= 4; // U\n    table[0x56] |= 4; // V\n    table[0x57] |= 4; // W\n    table[0x58] |= 4; // X\n    table[0x59] |= 4; // Y\n    table[0x5A] |= 4; // Z\n    table[0x5B] |= 4; // [\n                      // Skipping 0x5C (\\) as it needs escaping\n    table[0x5D] |= 4; // ]\n    table[0x5E] |= 4; // ^\n    table[0x5F] |= 4; // _\n    table[0x60] |= 4; // `\n    table[0x61] |= 4; // a\n    table[0x62] |= 4; // b\n    table[0x63] |= 4; // c\n    table[0x64] |= 4; // d\n    table[0x65] |= 4; // e\n    table[0x66] |= 4; // f\n    table[0x67] |= 4; // g\n    table[0x68] |= 4; // h\n    table[0x69] |= 4; // i\n    table[0x6A] |= 4; // j\n    table[0x6B] |= 4; // k\n    table[0x6C] |= 4; // l\n    table[0x6D] |= 4; // m\n    table[0x6E] |= 4; // n\n    table[0x6F] |= 4; // o\n    table[0x70] |= 4; // p\n    table[0x71] |= 4; // q\n    table[0x72] |= 4; // r\n    table[0x73] |= 4; // s\n    table[0x74] |= 4; // t\n    table[0x75] |= 4; // u\n    table[0x76] |= 4; // v\n    table[0x77] |= 4; // w\n    table[0x78] |= 4; // x\n    table[0x79] |= 4; // y\n    table[0x7A] |= 4; // z\n    table[0x7B] |= 4; // {\n    table[0x7C] |= 4; // |\n    table[0x7D] |= 4; // }\n    table[0x7E] |= 4; // ~\n\n    table\n}\n\nstatic CHARACTER_TYPE_OK: [u8; 256] = make_character_type_ok_table();\n\n#[derive(Debug, Clone, PartialEq)]\npub struct Jsonb {\n    data: Vec<u8>,\n}\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq)]\n#[allow(clippy::enum_variant_names, clippy::upper_case_acronyms)]\npub enum ElementType {\n    NULL = 0,\n    TRUE = 1,\n    FALSE = 2,\n    INT = 3,\n    INT5 = 4,\n    FLOAT = 5,\n    FLOAT5 = 6,\n    TEXT = 7,\n    TEXTJ = 8,\n    TEXT5 = 9,\n    TEXTRAW = 10,\n    ARRAY = 11,\n    OBJECT = 12,\n    RESERVED1 = 13,\n    RESERVED2 = 14,\n    RESERVED3 = 15,\n}\n\npub enum IteratorState {\n    Array(ArrayIteratorState),\n    Object(ObjectIteratorState),\n    Primitive(Jsonb),\n}\n\npub enum JsonIndentation<'a> {\n    Indentation(Cow<'a, str>),\n    None,\n}\n\nimpl JsonIndentation<'_> {\n    pub fn is_pretty(&self) -> bool {\n        match self {\n            Self::Indentation(_) => true,\n            Self::None => false,\n        }\n    }\n}\n\nimpl ElementType {\n    pub fn is_valid_key(&self) -> bool {\n        matches!(self, Self::TEXT | Self::TEXT5 | Self::TEXTJ | Self::TEXTRAW)\n    }\n}\n\nimpl From<ElementType> for String {\n    fn from(element_type: ElementType) -> String {\n        match element_type {\n            ElementType::ARRAY => \"array\".to_string(),\n            ElementType::OBJECT => \"object\".to_string(),\n            ElementType::NULL => \"null\".to_string(),\n            ElementType::TRUE => \"true\".to_string(),\n            ElementType::FALSE => \"false\".to_string(),\n            ElementType::FLOAT | ElementType::FLOAT5 => \"real\".to_string(),\n            ElementType::INT | ElementType::INT5 => \"integer\".to_string(),\n            ElementType::TEXT | ElementType::TEXT5 | ElementType::TEXTJ | ElementType::TEXTRAW => {\n                \"text\".to_string()\n            }\n            _ => unreachable!(),\n        }\n    }\n}\n\nimpl TryFrom<u8> for ElementType {\n    type Error = LimboError;\n\n    fn try_from(value: u8) -> std::result::Result<Self, Self::Error> {\n        match value {\n            0 => Ok(Self::NULL),\n            1 => Ok(Self::TRUE),\n            2 => Ok(Self::FALSE),\n            3 => Ok(Self::INT),\n            4 => Ok(Self::INT5),\n            5 => Ok(Self::FLOAT),\n            6 => Ok(Self::FLOAT5),\n            7 => Ok(Self::TEXT),\n            8 => Ok(Self::TEXTJ),\n            9 => Ok(Self::TEXT5),\n            10 => Ok(Self::TEXTRAW),\n            11 => Ok(Self::ARRAY),\n            12 => Ok(Self::OBJECT),\n            13..=15 => bail_parse_error!(\"Invalid element type: {}\", value),\n            _ => bail_parse_error!(\"Failed to recognize jsonvalue type\"),\n        }\n    }\n}\n\ntype PayloadSize = usize;\n\n#[derive(Debug, Clone)]\npub enum ArrayPositionKind {\n    SpecificIndex(usize),\n}\n\n#[derive(Debug, Clone, PartialEq)]\npub enum JsonLocationKind {\n    ObjectProperty(usize),\n    DocumentRoot,\n    ArrayEntry,\n}\n\n#[derive(Debug, Clone)]\npub struct JsonTraversalResult {\n    field_key_index: JsonLocationKind,\n    pub field_value_index: usize,\n    delta: isize,\n    array_position_info: Option<ArrayPositionKind>,\n}\n\n#[derive(Debug, Clone)]\npub enum SegmentVariant<'a> {\n    Single(&'a PathElement<'a>),\n    KeyWithArrayIndex(&'a PathElement<'a>, &'a PathElement<'a>),\n}\n\npub trait PathOperation {\n    // Get the operation mode for this operation\n    fn operation_mode(&self) -> PathOperationMode;\n\n    // Execute the actual operation\n    fn execute(&mut self, json: &mut Jsonb, stack: Vec<JsonTraversalResult>) -> Result<()>;\n\n    // Name of the operation for logging/debugging\n}\n\npub struct SetOperation {\n    value: Jsonb,\n    mode: PathOperationMode,\n}\n\nimpl SetOperation {\n    pub fn new(value: Jsonb) -> Self {\n        Self {\n            value,\n            mode: PathOperationMode::Upsert,\n        }\n    }\n}\n\nimpl PathOperation for SetOperation {\n    fn operation_mode(&self) -> PathOperationMode {\n        self.mode\n    }\n\n    fn execute(&mut self, json: &mut Jsonb, mut stack: Vec<JsonTraversalResult>) -> Result<()> {\n        if stack.is_empty() {\n            bail_parse_error!(\"Nothing to operate on!\")\n        }\n        let value = &self.value.data;\n        let target = stack.pop().ok_or_else(|| {\n            LimboError::InternalError(\"stack should not be empty after check\".to_string())\n        })?;\n\n        // handle array\n        if target.has_specific_index() {\n            let array_value_idx = target.get_array_index().ok_or_else(|| {\n                LimboError::InternalError(\"target should have array index\".to_string())\n            })?;\n            let obj_value_idx = target.field_value_index;\n            let (JsonbHeader(_, obj_value_size), obj_value_header_size) =\n                json.read_header(obj_value_idx)?;\n            let (JsonbHeader(_, array_value_size), array_value_header_size) =\n                json.read_header(array_value_idx)?;\n\n            let delta =\n                value.len() as isize - (array_value_size + array_value_header_size) as isize;\n\n            let end_pos = array_value_idx + array_value_size + array_value_header_size;\n            json.data\n                .splice(array_value_idx..end_pos, value.iter().copied());\n\n            // update parent\n            let h_delta = if matches!(\n                target.field_key_index,\n                JsonLocationKind::ObjectProperty(_) | JsonLocationKind::DocumentRoot\n            ) {\n                let new_h_delta = json.write_element_header(\n                    obj_value_idx,\n                    ElementType::ARRAY,\n                    (obj_value_size as isize + delta) as usize,\n                    true,\n                )?;\n                (new_h_delta - obj_value_header_size) as isize\n            } else {\n                0\n            };\n\n            json.update_parent_references(stack, target.delta + delta + h_delta)?;\n        } else {\n            let old_value_idx = target.field_value_index;\n            let (JsonbHeader(_, old_value_size), old_value_header_size) =\n                json.read_header(old_value_idx)?;\n            let delta = value.len() as isize - (old_value_header_size + old_value_size) as isize;\n\n            let end_pos = old_value_idx + old_value_header_size + old_value_size;\n\n            json.data\n                .splice(old_value_idx..end_pos, value.iter().copied());\n\n            json.update_parent_references(stack, delta + target.delta)?;\n        }\n\n        Ok(())\n    }\n}\n\npub struct DeleteOperation {\n    mode: PathOperationMode,\n}\n\nimpl DeleteOperation {\n    pub fn new() -> Self {\n        Self {\n            mode: PathOperationMode::ReplaceExisting,\n        }\n    }\n}\n\nimpl PathOperation for DeleteOperation {\n    fn operation_mode(&self) -> PathOperationMode {\n        self.mode\n    }\n\n    fn execute(&mut self, json: &mut Jsonb, mut stack: Vec<JsonTraversalResult>) -> Result<()> {\n        if stack.is_empty() {\n            bail_parse_error!(\"Nothing to operate on!\")\n        }\n\n        let target = stack.pop().ok_or_else(|| {\n            LimboError::InternalError(\"stack should not be empty after check\".to_string())\n        })?;\n\n        // handle array\n        if target.has_specific_index() {\n            let array_value_idx = target.get_array_index().ok_or_else(|| {\n                LimboError::InternalError(\"target should have array index\".to_string())\n            })?;\n\n            let obj_value_idx = target.field_value_index;\n            let (JsonbHeader(_, obj_value_size), obj_value_header_size) =\n                json.read_header(obj_value_idx)?;\n            let (JsonbHeader(_, array_value_size), array_value_header_size) =\n                json.read_header(array_value_idx)?;\n            let delta = 0 - (array_value_size + array_value_header_size) as isize;\n\n            let end_pos = array_value_idx + array_value_size + array_value_header_size;\n            json.data.drain(array_value_idx..end_pos);\n\n            let h_delta = if matches!(\n                target.field_key_index,\n                JsonLocationKind::ObjectProperty(_) | JsonLocationKind::DocumentRoot\n            ) {\n                let new_h_delta = json.write_element_header(\n                    obj_value_idx,\n                    ElementType::ARRAY,\n                    (obj_value_size as isize + delta) as usize,\n                    true,\n                )?;\n                new_h_delta as isize - obj_value_header_size as isize\n            } else {\n                0\n            };\n            json.update_parent_references(stack, target.delta + delta + h_delta)?;\n        } else if let JsonLocationKind::ObjectProperty(key_idx) = target.field_key_index {\n            let value_idx = target.field_value_index;\n            let (JsonbHeader(_, value_size), value_header_size) = json.read_header(value_idx)?;\n            let (JsonbHeader(_, key_size), key_header_size) = json.read_header(key_idx)?;\n            let delta = 0 - (value_header_size + value_size + key_size + key_header_size) as isize;\n\n            let end_pos = key_idx + value_header_size + value_size + key_size + key_header_size;\n            json.data.drain(key_idx..end_pos);\n\n            json.update_parent_references(stack, delta + target.delta)?;\n        } else {\n            let nul = JsonbHeader::make_null().into_bytes();\n            let nul_bytes = nul.as_bytes();\n            json.data.clear();\n            json.data.extend_from_slice(nul_bytes);\n        }\n\n        Ok(())\n    }\n}\n\npub struct ReplaceOperation {\n    value: Jsonb,\n    mode: PathOperationMode,\n}\n\nimpl ReplaceOperation {\n    pub fn new(value: Jsonb) -> Self {\n        Self {\n            value,\n            mode: PathOperationMode::ReplaceExisting,\n        }\n    }\n}\n\nimpl PathOperation for ReplaceOperation {\n    fn operation_mode(&self) -> PathOperationMode {\n        self.mode\n    }\n\n    fn execute(&mut self, json: &mut Jsonb, mut stack: Vec<JsonTraversalResult>) -> Result<()> {\n        if stack.is_empty() {\n            bail_parse_error!(\"Nothing to operate on!\")\n        }\n        let value = &self.value.data;\n        let target = stack.pop().ok_or_else(|| {\n            LimboError::InternalError(\"stack should not be empty after check\".to_string())\n        })?;\n\n        // handle array\n        if target.has_specific_index() {\n            let array_value_idx = target.get_array_index().ok_or_else(|| {\n                LimboError::InternalError(\"target should have array index\".to_string())\n            })?;\n            let obj_value_idx = target.field_value_index;\n            let (JsonbHeader(_, obj_value_size), obj_value_header_size) =\n                json.read_header(obj_value_idx)?;\n            let (JsonbHeader(_, array_value_size), array_value_header_size) =\n                json.read_header(array_value_idx)?;\n\n            let delta =\n                value.len() as isize - (array_value_size + array_value_header_size) as isize;\n\n            let end_pos = array_value_idx + array_value_size + array_value_header_size;\n            json.data\n                .splice(array_value_idx..end_pos, value.iter().copied());\n\n            // update parent\n            let h_delta = if matches!(\n                target.field_key_index,\n                JsonLocationKind::ObjectProperty(_) | JsonLocationKind::DocumentRoot\n            ) {\n                let new_h_delta = json.write_element_header(\n                    obj_value_idx,\n                    ElementType::ARRAY,\n                    (obj_value_size as isize + delta) as usize,\n                    true,\n                )?;\n                (new_h_delta - obj_value_header_size) as isize\n            } else {\n                0\n            };\n\n            json.update_parent_references(stack, target.delta + delta + h_delta)?;\n        } else {\n            let old_value_idx = target.field_value_index;\n            let (JsonbHeader(_, old_value_size), old_value_header_size) =\n                json.read_header(old_value_idx)?;\n            let delta = value.len() as isize - (old_value_header_size + old_value_size) as isize;\n\n            let end_pos = old_value_idx + old_value_header_size + old_value_size;\n\n            json.data\n                .splice(old_value_idx..end_pos, value.iter().copied());\n\n            json.update_parent_references(stack, delta + target.delta)?;\n        }\n\n        Ok(())\n    }\n}\n\npub struct InsertOperation {\n    value: Jsonb,\n    mode: PathOperationMode,\n}\n\nimpl InsertOperation {\n    pub fn new(value: Jsonb) -> Self {\n        Self {\n            value,\n            mode: PathOperationMode::InsertNew,\n        }\n    }\n}\n\nimpl PathOperation for InsertOperation {\n    fn operation_mode(&self) -> PathOperationMode {\n        self.mode\n    }\n\n    fn execute(&mut self, json: &mut Jsonb, mut stack: Vec<JsonTraversalResult>) -> Result<()> {\n        if stack.is_empty() {\n            bail_parse_error!(\"Nothing to operate on!\")\n        }\n        let value = &self.value.data;\n        let target = stack.pop().ok_or_else(|| {\n            LimboError::InternalError(\"stack should not be empty after check\".to_string())\n        })?;\n\n        // handle array\n        if target.has_specific_index() {\n            let array_value_idx = target.get_array_index().ok_or_else(|| {\n                LimboError::InternalError(\"target should have array index\".to_string())\n            })?;\n            let obj_value_idx = target.field_value_index;\n            let (JsonbHeader(_, obj_value_size), obj_value_header_size) =\n                json.read_header(obj_value_idx)?;\n            let (JsonbHeader(_, array_value_size), array_value_header_size) =\n                json.read_header(array_value_idx)?;\n\n            let delta =\n                value.len() as isize - (array_value_size + array_value_header_size) as isize;\n\n            let end_pos = array_value_idx + array_value_size + array_value_header_size;\n            json.data\n                .splice(array_value_idx..end_pos, value.iter().copied());\n\n            // update parent\n            let h_delta = if matches!(\n                target.field_key_index,\n                JsonLocationKind::ObjectProperty(_) | JsonLocationKind::DocumentRoot\n            ) {\n                let new_h_delta = json.write_element_header(\n                    obj_value_idx,\n                    ElementType::ARRAY,\n                    (obj_value_size as isize + delta) as usize,\n                    true,\n                )?;\n                (new_h_delta - obj_value_header_size) as isize\n            } else {\n                0\n            };\n\n            json.update_parent_references(stack, target.delta + delta + h_delta)?;\n        } else {\n            let old_value_idx = target.field_value_index;\n            let (JsonbHeader(_, old_value_size), old_value_header_size) =\n                json.read_header(old_value_idx)?;\n            let delta = value.len() as isize - (old_value_header_size + old_value_size) as isize;\n\n            let end_pos = old_value_idx + old_value_header_size + old_value_size;\n\n            json.data\n                .splice(old_value_idx..end_pos, value.iter().copied());\n\n            json.update_parent_references(stack, delta + target.delta)?;\n        }\n\n        Ok(())\n    }\n}\n\npub struct SearchOperation {\n    value: Jsonb,\n    mode: PathOperationMode,\n}\n\nimpl SearchOperation {\n    pub fn new(capacity: usize) -> Self {\n        Self {\n            mode: PathOperationMode::ReplaceExisting,\n            value: Jsonb::new(capacity, None),\n        }\n    }\n\n    pub fn result(self) -> Jsonb {\n        self.value\n    }\n}\n\nimpl PathOperation for SearchOperation {\n    fn operation_mode(&self) -> PathOperationMode {\n        self.mode\n    }\n\n    fn execute(&mut self, json: &mut Jsonb, mut stack: Vec<JsonTraversalResult>) -> Result<()> {\n        let target = stack.pop().ok_or_else(|| {\n            LimboError::InternalError(\"stack should not be empty after check\".to_string())\n        })?;\n        let idx = if let Some(idx) = target.get_array_index() {\n            idx\n        } else {\n            target.field_value_index\n        };\n        let (JsonbHeader(_, size), header_size) = json.read_header(idx)?;\n        self.value\n            .data\n            .extend_from_slice(&json.data[idx..idx + header_size + size]);\n\n        Ok(())\n    }\n}\n\nimpl JsonTraversalResult {\n    pub fn new(field_value_index: usize, field_key_index: JsonLocationKind, delta: isize) -> Self {\n        Self {\n            field_value_index,\n            delta,\n            field_key_index,\n            array_position_info: None,\n        }\n    }\n\n    pub fn with_array_index(\n        field_value_index: usize,\n        field_key_index: JsonLocationKind,\n        delta: isize,\n        index: usize,\n    ) -> Self {\n        Self {\n            field_value_index,\n            field_key_index,\n            delta,\n            array_position_info: Some(ArrayPositionKind::SpecificIndex(index)),\n        }\n    }\n\n    pub fn has_specific_index(&self) -> bool {\n        matches!(\n            self.array_position_info,\n            Some(ArrayPositionKind::SpecificIndex(_))\n        )\n    }\n\n    pub fn get_array_index(&self) -> Option<usize> {\n        match self.array_position_info {\n            Some(ArrayPositionKind::SpecificIndex(idx)) => Some(idx),\n            _ => None,\n        }\n    }\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum PathOperationMode {\n    /// Only replace values if the complete path already exists\n    ReplaceExisting,\n\n    /// Only insert values if the path doesn't exist yet\n    InsertNew,\n\n    /// Either replace existing values or create new ones as needed\n    Upsert,\n}\n\nimpl PathOperationMode {\n    /// Returns true if this mode allows replacing existing values\n    pub fn allows_replace(&self) -> bool {\n        matches!(self, Self::ReplaceExisting | Self::Upsert)\n    }\n\n    /// Returns true if this mode allows creating new paths\n    pub fn allows_insert(&self) -> bool {\n        matches!(self, Self::InsertNew | Self::Upsert)\n    }\n}\n\n#[derive(Debug, Clone, Copy)]\npub struct JsonbHeader(ElementType, PayloadSize);\n\npub(crate) enum HeaderFormat {\n    Inline([u8; 1]),    // Small payloads embedded directly in the header\n    OneByte([u8; 2]),   // Medium payloads with 1-byte size field\n    TwoBytes([u8; 3]),  // Large payloads with 2-byte size field\n    FourBytes([u8; 5]), // Extra large payloads with 4-byte size field\n}\n\nimpl HeaderFormat {\n    pub fn as_bytes(&self) -> &[u8] {\n        match self {\n            Self::Inline(bytes) => bytes,\n            Self::OneByte(bytes) => bytes,\n            Self::TwoBytes(bytes) => bytes,\n            Self::FourBytes(bytes) => bytes,\n        }\n    }\n}\n\nimpl JsonbHeader {\n    fn new(element_type: ElementType, payload_size: PayloadSize) -> Self {\n        Self(element_type, payload_size)\n    }\n\n    pub fn make_null() -> Self {\n        Self(ElementType::NULL, 0)\n    }\n\n    pub fn make_obj() -> Self {\n        Self(ElementType::OBJECT, 0)\n    }\n\n    pub(super) fn element_type(&self) -> ElementType {\n        self.0\n    }\n\n    pub(super) fn payload_size(&self) -> PayloadSize {\n        self.1\n    }\n\n    pub(super) fn from_slice(cursor: usize, slice: &[u8]) -> Result<(Self, usize)> {\n        match slice.get(cursor) {\n            Some(header_byte) => {\n                // Extract first 4 bits (values 0-15)\n                let element_type = header_byte & 15;\n                if element_type > 12 {\n                    bail_parse_error!(\"Invalid element type: {}\", element_type);\n                }\n                // Get the last 4 bits for header_size\n                let header_size = header_byte >> 4;\n                let offset: usize;\n                let total_size = match header_size {\n                    size if size <= 11 => {\n                        offset = 1;\n                        size as usize\n                    }\n\n                    12 => match slice.get(cursor + 1) {\n                        Some(value) => {\n                            offset = 2;\n                            *value as usize\n                        }\n                        None => bail_parse_error!(\"Failed to read 1-byte size\"),\n                    },\n\n                    13 => match Self::get_size_bytes(slice, cursor + 1, 2) {\n                        Ok(bytes) => {\n                            offset = 3;\n                            u16::from_be_bytes([bytes[0], bytes[1]]) as usize\n                        }\n                        Err(e) => return Err(e),\n                    },\n\n                    14 => match Self::get_size_bytes(slice, cursor + 1, 4) {\n                        Ok(bytes) => {\n                            offset = 5;\n                            u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize\n                        }\n                        Err(e) => return Err(e),\n                    },\n\n                    // 15 = 8-byte payload size (for future expansion per SQLite spec)\n                    15 => match Self::get_size_bytes(slice, cursor + 1, 8) {\n                        Ok(bytes) => {\n                            offset = 9;\n                            u64::from_be_bytes([\n                                bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5],\n                                bytes[6], bytes[7],\n                            ]) as usize\n                        }\n                        Err(e) => return Err(e),\n                    },\n\n                    _ => unreachable!(),\n                };\n\n                Ok((Self(element_type.try_into()?, total_size), offset))\n            }\n            None => bail_parse_error!(\"Failed to read header byte\"),\n        }\n    }\n\n    pub fn into_bytes(self) -> HeaderFormat {\n        let (element_type, payload_size) = (self.0, self.1);\n\n        match payload_size {\n            // Small payload (fits in 4 bits)\n            size if size <= 11 => {\n                HeaderFormat::Inline([(element_type as u8) | ((size as u8) << 4)])\n            }\n\n            // Medium payload (fits in 1 byte)\n            size if size <= 0xFF => {\n                HeaderFormat::OneByte([(element_type as u8) | (SIZE_MARKER_8BIT << 4), size as u8])\n            }\n\n            // Large payload (fits in 2 bytes)\n            size if size <= 0xFFFF => {\n                let size_bytes = (size as u16).to_be_bytes();\n                HeaderFormat::TwoBytes([\n                    (element_type as u8) | (SIZE_MARKER_16BIT << 4),\n                    size_bytes[0],\n                    size_bytes[1],\n                ])\n            }\n\n            // Extra large payload (fits in 4 bytes)\n            size if size <= 0xFFFFFFFF => {\n                let size_bytes = (size as u32).to_be_bytes();\n                HeaderFormat::FourBytes([\n                    (element_type as u8) | (SIZE_MARKER_32BIT << 4),\n                    size_bytes[0],\n                    size_bytes[1],\n                    size_bytes[2],\n                    size_bytes[3],\n                ])\n            }\n\n            // Payload too large\n            _ => panic!(\"Payload size too large for encoding\"),\n        }\n    }\n\n    pub fn is_scalar(&self) -> bool {\n        !matches!(\n            self.element_type(),\n            ElementType::ARRAY | ElementType::OBJECT\n        )\n    }\n\n    fn get_size_bytes(slice: &[u8], start: usize, count: usize) -> Result<&[u8]> {\n        match slice.get(start..start + count) {\n            Some(bytes) => Ok(bytes),\n            None => bail_parse_error!(\"Failed to read header size\"),\n        }\n    }\n}\n\npub struct ArrayIteratorState {\n    cursor: usize,\n    end: usize,\n    index: usize,\n}\n\npub struct ObjectIteratorState {\n    cursor: usize,\n    end: usize,\n    index: usize,\n}\n\nimpl Jsonb {\n    pub fn new(capacity: usize, data: Option<&[u8]>) -> Self {\n        if let Some(data) = data {\n            return Self {\n                data: data.to_vec(),\n            };\n        }\n        Self {\n            data: Vec::with_capacity(capacity),\n        }\n    }\n\n    pub fn len(&self) -> usize {\n        self.data.len()\n    }\n\n    pub fn make_empty_array(size: usize) -> Self {\n        let mut jsonb = Self {\n            data: Vec::with_capacity(size),\n        };\n        jsonb\n            .write_element_header(0, ElementType::ARRAY, 0, false)\n            .expect(\"writing header to new vector should not fail\");\n        jsonb\n    }\n\n    pub fn make_empty_obj(size: usize) -> Self {\n        let mut jsonb = Self {\n            data: Vec::with_capacity(size),\n        };\n        jsonb\n            .write_element_header(0, ElementType::OBJECT, 0, false)\n            .expect(\"writing header to new vector should not fail\");\n        jsonb\n    }\n\n    pub fn append_to_array_unsafe(&mut self, data: &[u8]) {\n        self.data.extend_from_slice(data);\n    }\n\n    pub fn append_jsonb_to_end(&mut self, mut data: Vec<u8>) {\n        self.data.append(&mut data);\n    }\n\n    pub fn finalize_unsafe(&mut self, element_type: ElementType) -> Result<()> {\n        self.write_element_header(0, element_type, self.len() - 1, false)?;\n        Ok(())\n    }\n\n    fn read_header(&self, cursor: usize) -> Result<(JsonbHeader, usize)> {\n        let (header, offset) = JsonbHeader::from_slice(cursor, &self.data)?;\n\n        Ok((header, offset))\n    }\n\n    pub fn element_type(&self) -> Result<ElementType> {\n        match self.read_header(0) {\n            Ok((header, offset)) => {\n                if self.data.get(offset..offset + header.1).is_some() {\n                    Ok(header.0)\n                } else {\n                    bail_parse_error!(\"malformed JSON\")\n                }\n            }\n            Err(_) => bail_parse_error!(\"malformed JSON\"),\n        }\n    }\n\n    pub fn is_valid(&self) -> bool {\n        self.validate_element(0, self.data.len(), 0).is_ok()\n    }\n\n    fn validate_element(&self, start: usize, end: usize, depth: usize) -> Result<()> {\n        if depth > MAX_JSON_DEPTH {\n            bail_parse_error!(\"Too deep\");\n        }\n\n        if start >= end {\n            bail_parse_error!(\"Empty element\");\n        }\n\n        let (header, header_offset) = self.read_header(start)?;\n        let payload_start = start + header_offset;\n        let payload_size = header.payload_size();\n        let payload_end = payload_start + payload_size;\n\n        if payload_end != end {\n            bail_parse_error!(\"Size mismatch\");\n        }\n        if payload_end > self.data.len() {\n            bail_parse_error!(\"Payload extends beyond data\");\n        }\n\n        match header.element_type() {\n            ElementType::NULL | ElementType::TRUE | ElementType::FALSE => {\n                if payload_size == 0 {\n                    Ok(())\n                } else {\n                    bail_parse_error!(\"Invalid payload for primitive\")\n                }\n            }\n            ElementType::INT | ElementType::INT5 | ElementType::FLOAT | ElementType::FLOAT5 => {\n                if payload_size > 0 {\n                    Ok(())\n                } else {\n                    bail_parse_error!(\"Empty number payload\")\n                }\n            }\n            ElementType::TEXT | ElementType::TEXTJ | ElementType::TEXT5 | ElementType::TEXTRAW => {\n                let payload = &self.data[payload_start..payload_end];\n                std::str::from_utf8(payload).map_err(|_| {\n                    LimboError::ParseError(\"Invalid UTF-8 in text payload\".to_string())\n                })?;\n                Ok(())\n            }\n            ElementType::ARRAY => {\n                let mut pos = payload_start;\n                while pos < payload_end {\n                    if pos >= self.data.len() {\n                        bail_parse_error!(\"Array element out of bounds\");\n                    }\n                    let (elem_header, elem_header_size) = self.read_header(pos)?;\n                    let elem_end = pos + elem_header_size + elem_header.payload_size();\n                    if elem_end > payload_end {\n                        bail_parse_error!(\"Array element exceeds bounds\");\n                    }\n                    self.validate_element(pos, elem_end, depth + 1)?;\n                    pos = elem_end;\n                }\n                Ok(())\n            }\n            ElementType::OBJECT => {\n                let mut pos = payload_start;\n                let mut count = 0;\n                while pos < payload_end {\n                    if pos >= self.data.len() {\n                        bail_parse_error!(\"Object element out of bounds\");\n                    }\n                    let (elem_header, elem_header_size) = self.read_header(pos)?;\n                    if count % 2 == 0 && !elem_header.element_type().is_valid_key() {\n                        bail_parse_error!(\"Object key must be text\");\n                    }\n\n                    let elem_end = pos + elem_header_size + elem_header.payload_size();\n                    if elem_end > payload_end {\n                        bail_parse_error!(\"Object element exceeds bounds\");\n                    }\n                    self.validate_element(pos, elem_end, depth + 1)?;\n                    pos = elem_end;\n                    count += 1;\n                }\n\n                if count % 2 != 0 {\n                    bail_parse_error!(\"Object must have even number of elements\");\n                }\n                Ok(())\n            }\n            _ => bail_parse_error!(\"Invalid element type\"),\n        }\n    }\n\n    pub fn to_string(&self) -> Result<String> {\n        let mut result = String::with_capacity(self.data.len() * 2);\n        self.serialize_value(&mut result, 0, 0, &JsonIndentation::None)?;\n        Ok(result)\n    }\n\n    pub fn to_string_pretty(&self, indentation: Option<&str>) -> Result<String> {\n        let mut result = String::with_capacity(self.data.len() * 2);\n        let ind = if let Some(ind) = indentation {\n            JsonIndentation::Indentation(Cow::Borrowed(ind))\n        } else {\n            JsonIndentation::Indentation(Cow::Borrowed(\"    \"))\n        };\n        self.serialize_value(&mut result, 0, 0, &ind)?;\n        Ok(result)\n    }\n\n    fn serialize_value(\n        &self,\n        string: &mut String,\n        cursor: usize,\n        depth: usize,\n        delimiter: &JsonIndentation,\n    ) -> Result<usize> {\n        let (header, skip_header) = self.read_header(cursor)?;\n\n        let cursor = cursor + skip_header;\n        let current_cursor = match header {\n            JsonbHeader(ElementType::OBJECT, len) => {\n                self.serialize_object(string, cursor, len, depth, delimiter)?\n            }\n            JsonbHeader(ElementType::ARRAY, len) => {\n                self.serialize_array(string, cursor, len, depth, delimiter)?\n            }\n            JsonbHeader(ElementType::TEXT, len)\n            | JsonbHeader(ElementType::TEXTRAW, len)\n            | JsonbHeader(ElementType::TEXTJ, len)\n            | JsonbHeader(ElementType::TEXT5, len) => {\n                self.serialize_string(string, cursor, len, &header.0, true)?\n            }\n            JsonbHeader(ElementType::INT, len)\n            | JsonbHeader(ElementType::INT5, len)\n            | JsonbHeader(ElementType::FLOAT, len)\n            | JsonbHeader(ElementType::FLOAT5, len) => {\n                self.serialize_number(string, cursor, len, &header.0)?\n            }\n\n            JsonbHeader(ElementType::TRUE, _) => self.serialize_boolean(string, cursor, true),\n            JsonbHeader(ElementType::FALSE, _) => self.serialize_boolean(string, cursor, false),\n            JsonbHeader(ElementType::NULL, _) => self.serialize_null(string, cursor),\n            JsonbHeader(_, _) => {\n                unreachable!();\n            }\n        };\n        Ok(current_cursor)\n    }\n\n    fn serialize_object(\n        &self,\n        string: &mut String,\n        cursor: usize,\n        len: usize,\n        mut depth: usize,\n        indent: &JsonIndentation,\n    ) -> Result<usize> {\n        let end_cursor = cursor\n            .checked_add(len)\n            .ok_or_else(|| LimboError::ParseError(\"Invalid JSONB: payload size overflow\".into()))?;\n        let mut current_cursor = cursor;\n        depth += 1;\n        string.push('{');\n        if indent.is_pretty() {\n            string.push('\\n');\n        };\n        while current_cursor < end_cursor {\n            let (key_header, key_header_offset) = self.read_header(current_cursor)?;\n            current_cursor += key_header_offset;\n            let JsonbHeader(element_type, len) = key_header;\n            if let JsonIndentation::Indentation(value) = indent {\n                for _ in 0..depth {\n                    string.push_str(value);\n                }\n            };\n            match element_type {\n                ElementType::TEXT\n                | ElementType::TEXTRAW\n                | ElementType::TEXTJ\n                | ElementType::TEXT5 => {\n                    current_cursor =\n                        self.serialize_string(string, current_cursor, len, &element_type, true)?;\n                }\n                _ => bail_parse_error!(\"malformed JSON\"),\n            }\n\n            string.push(':');\n            if indent.is_pretty() {\n                string.push(' ');\n            }\n            current_cursor = self.serialize_value(string, current_cursor, depth, indent)?;\n            if current_cursor < end_cursor {\n                string.push(',');\n            }\n\n            if indent.is_pretty() {\n                string.push('\\n');\n            };\n        }\n        if let JsonIndentation::Indentation(value) = indent {\n            for _ in 0..depth - 1 {\n                string.push_str(value);\n            }\n        };\n        string.push('}');\n\n        Ok(current_cursor)\n    }\n\n    fn serialize_array(\n        &self,\n        string: &mut String,\n        cursor: usize,\n        len: usize,\n        mut depth: usize,\n        indent: &JsonIndentation,\n    ) -> Result<usize> {\n        let end_cursor = cursor\n            .checked_add(len)\n            .ok_or_else(|| LimboError::ParseError(\"Invalid JSONB: payload size overflow\".into()))?;\n        let mut current_cursor = cursor;\n        depth += 1;\n        string.push('[');\n        if indent.is_pretty() {\n            string.push('\\n');\n        };\n        while current_cursor < end_cursor {\n            if let JsonIndentation::Indentation(value) = indent {\n                for _ in 0..depth {\n                    string.push_str(value);\n                }\n            };\n            current_cursor = self.serialize_value(string, current_cursor, depth, indent)?;\n            if current_cursor < end_cursor {\n                string.push(',');\n            }\n            if indent.is_pretty() {\n                string.push('\\n');\n            };\n        }\n        if let JsonIndentation::Indentation(value) = indent {\n            for _ in 0..depth - 1 {\n                string.push_str(value);\n            }\n        };\n        string.push(']');\n\n        Ok(current_cursor)\n    }\n\n    fn serialize_string(\n        &self,\n        string: &mut String,\n        cursor: usize,\n        len: usize,\n        kind: &ElementType,\n        quote: bool,\n    ) -> Result<usize> {\n        let end_cursor = cursor\n            .checked_add(len)\n            .ok_or_else(|| LimboError::ParseError(\"Invalid JSONB: payload size overflow\".into()))?;\n        if end_cursor > self.data.len() {\n            bail_parse_error!(\"Invalid JSONB: string extends beyond data\");\n        }\n        let word_slice = &self.data[cursor..end_cursor];\n        if quote {\n            string.push('\"');\n        }\n\n        match kind {\n            ElementType::TEXT | ElementType::TEXTRAW | ElementType::TEXTJ => {\n                let word = from_utf8(word_slice).map_err(|_| {\n                    LimboError::ParseError(\"Failed to serialize string!\".to_string())\n                })?;\n\n                let mut last_end = 0;\n                let bytes = word.as_bytes();\n                for i in 0..bytes.len() {\n                    let b = bytes[i];\n                    let needs_escape = if *kind == ElementType::TEXTJ {\n                        b <= 0x1F\n                    } else {\n                        b == b'\"' || b == b'\\\\' || b <= 0x1F\n                    };\n\n                    if needs_escape {\n                        string.push_str(&word[last_end..i]);\n                        match b {\n                            b'\"' => string.push_str(\"\\\\\\\"\"),\n                            b'\\\\' => string.push_str(\"\\\\\\\\\"),\n                            0x08 => string.push_str(\"\\\\b\"),\n                            0x0C => string.push_str(\"\\\\f\"),\n                            b'\\n' => string.push_str(\"\\\\n\"),\n                            b'\\r' => string.push_str(\"\\\\r\"),\n                            b'\\t' => string.push_str(\"\\\\t\"),\n                            c => {\n                                let _ = write!(string, \"\\\\u{c:04x}\");\n                            }\n                        }\n                        last_end = i + 1;\n                    }\n                }\n                string.push_str(&word[last_end..]);\n            }\n\n            // We have to escape some JSON5 escape sequences\n            ElementType::TEXT5 => {\n                let mut i = 0;\n                while i < word_slice.len() {\n                    let ch = word_slice[i];\n\n                    // Handle normal characters that don't need escaping\n                    if is_json_ok(ch) || ch == b'\\'' {\n                        string.push(ch as char);\n                        i += 1;\n                        continue;\n                    }\n\n                    // Handle special cases\n                    match ch {\n                        // Double quotes need escaping\n                        b'\"' => {\n                            string.push_str(\"\\\\\\\"\");\n                            i += 1;\n                        }\n\n                        // Control characters (0x00-0x1F)\n                        ch if ch <= 0x1F => {\n                            match ch {\n                                // \\b\n                                0x08 => string.push_str(\"\\\\b\"),\n                                b'\\t' => string.push_str(\"\\\\t\"),\n                                b'\\n' => string.push_str(\"\\\\n\"),\n                                // \\f\n                                0x0C => string.push_str(\"\\\\f\"),\n                                b'\\r' => string.push_str(\"\\\\r\"),\n                                _ => {\n                                    // Format as \\u00XX\n                                    let hex = format!(\"\\\\u{ch:04x}\");\n                                    string.push_str(&hex);\n                                }\n                            }\n                            i += 1;\n                        }\n\n                        // Handle escape sequences\n                        b'\\\\' if i + 1 < word_slice.len() => {\n                            let next_ch = word_slice[i + 1];\n                            match next_ch {\n                                // Single quote\n                                b'\\'' => {\n                                    string.push('\\'');\n                                    i += 2;\n                                }\n\n                                // Vertical tab\n                                b'v' => {\n                                    string.push_str(\"\\\\u0009\");\n                                    i += 2;\n                                }\n\n                                // Hex escapes like \\x27\n                                b'x' if i + 3 < word_slice.len() => {\n                                    string.push_str(\"\\\\u00\");\n                                    string.push(word_slice[i + 2] as char);\n                                    string.push(word_slice[i + 3] as char);\n                                    i += 4;\n                                }\n\n                                // Null character\n                                b'0' => {\n                                    string.push_str(\"\\\\u0000\");\n                                    i += 2;\n                                }\n\n                                // CR line continuation\n                                b'\\r' => {\n                                    if i + 2 < word_slice.len() && word_slice[i + 2] == b'\\n' {\n                                        i += 3; // Skip CRLF\n                                    } else {\n                                        i += 2; // Skip CR\n                                    }\n                                }\n\n                                // LF line continuation\n                                b'\\n' => {\n                                    i += 2;\n                                }\n\n                                // Unicode line separators (U+2028 and U+2029)\n                                0xe2 if i + 3 < word_slice.len()\n                                    && word_slice[i + 2] == 0x80\n                                    && (word_slice[i + 3] == 0xa8 || word_slice[i + 3] == 0xa9) =>\n                                {\n                                    i += 4;\n                                }\n\n                                // All other escapes pass through\n                                _ => {\n                                    string.push('\\\\');\n                                    string.push(next_ch as char);\n                                    i += 2;\n                                }\n                            }\n                        }\n\n                        // Default case - just push the character\n                        _ => {\n                            string.push(ch as char);\n                            i += 1;\n                        }\n                    }\n                }\n            }\n\n            _ => {\n                unreachable!()\n            }\n        }\n        if quote {\n            string.push('\"');\n        }\n\n        Ok(end_cursor)\n    }\n\n    fn serialize_number(\n        &self,\n        string: &mut String,\n        cursor: usize,\n        len: usize,\n        kind: &ElementType,\n    ) -> Result<usize> {\n        let current_cursor = cursor\n            .checked_add(len)\n            .ok_or_else(|| LimboError::ParseError(\"Invalid JSONB: payload size overflow\".into()))?;\n        if current_cursor > self.data.len() {\n            bail_parse_error!(\"Invalid JSONB: number extends beyond data\");\n        }\n        let num_slice = from_utf8(&self.data[cursor..current_cursor])\n            .map_err(|_| LimboError::ParseError(\"Failed to parse integer\".to_string()))?;\n\n        match kind {\n            ElementType::INT | ElementType::FLOAT => {\n                string.push_str(num_slice);\n            }\n            ElementType::INT5 => {\n                self.serialize_int5(string, num_slice)?;\n            }\n            ElementType::FLOAT5 => {\n                self.serialize_float5(string, num_slice)?;\n            }\n            _ => unreachable!(),\n        }\n        Ok(current_cursor)\n    }\n\n    fn serialize_int5(&self, string: &mut String, hex_str: &str) -> Result<()> {\n        // Check if number is hex using byte-level operations to handle non-ASCII safely\n        let bytes = hex_str.as_bytes();\n        let starts_with_0x = bytes\n            .get(..2)\n            .is_some_and(|b| b.eq_ignore_ascii_case(b\"0x\"));\n        let has_sign_prefix = matches!(bytes.first(), Some(b'-' | b'+'));\n        let starts_with_sign_0x = has_sign_prefix\n            && bytes\n                .get(1..3)\n                .is_some_and(|b| b.eq_ignore_ascii_case(b\"0x\"));\n        let is_hex = bytes.len() > 2 && (starts_with_0x || starts_with_sign_0x);\n\n        if is_hex {\n            // The prefix is ASCII, so these byte indices are valid char boundaries\n            let (sign, hex_part) = if bytes.starts_with(b\"-0x\") || bytes.starts_with(b\"-0X\") {\n                (\"-\", &hex_str[3..])\n            } else if bytes.starts_with(b\"+0x\") || bytes.starts_with(b\"+0X\") {\n                (\"\", &hex_str[3..])\n            } else {\n                (\"\", &hex_str[2..])\n            };\n\n            // Add sign\n            string.push_str(sign);\n\n            let mut value = 0u64;\n\n            for ch in hex_part.chars() {\n                if !ch.is_ascii_hexdigit() {\n                    bail_parse_error!(\"Failed to parse hex digit: {}\", hex_part);\n                }\n\n                if (value >> 60) != 0 {\n                    string.push_str(\"9.0e999\");\n                    return Ok(());\n                }\n\n                value = value * 16 + ch.to_digit(16).unwrap_or(0) as u64;\n            }\n            write!(string, \"{value}\")\n                .map_err(|_| LimboError::ParseError(\"Error writing string to json!\".to_string()))?;\n        } else if !hex_str.is_empty() && hex_str.bytes().all(|b| b.is_ascii_digit()) {\n            // Plain unsigned integer - output as-is\n            string.push_str(hex_str);\n        } else if hex_str.len() > 1\n            && hex_str.starts_with(['+', '-'])\n            && hex_str.bytes().skip(1).all(|b| b.is_ascii_digit())\n        {\n            // Signed integer with at least one digit after sign - output as-is\n            string.push_str(hex_str);\n        } else {\n            bail_parse_error!(\"malformed JSON\");\n        }\n\n        Ok(())\n    }\n\n    fn serialize_float5(&self, string: &mut String, float_str: &str) -> Result<()> {\n        if float_str.len() < 2 {\n            bail_parse_error!(\"Integer is less then 2 chars: {}\", float_str);\n        }\n        match float_str {\n            \"9.0e+999\" | \"-9.0e+999\" => {\n                string.push_str(float_str);\n            }\n            val if val.starts_with(\"-.\") => {\n                string.push_str(\"-0.\");\n                string.push_str(&val[2..]);\n            }\n            val if val.starts_with(\"+.\") => {\n                string.push_str(\"0.\");\n                string.push_str(&val[2..]);\n            }\n            val if val.starts_with(\".\") => {\n                string.push_str(\"0.\");\n                string.push_str(&val[1..]);\n            }\n            val if val\n                .chars()\n                .next()\n                .is_some_and(|c| c.is_ascii_alphanumeric() || c == '+' || c == '-') =>\n            {\n                string.push_str(val);\n                string.push('0');\n            }\n            _ => bail_parse_error!(\"Unable to serialize float5: {}\", float_str),\n        }\n\n        Ok(())\n    }\n\n    fn serialize_boolean(&self, string: &mut String, cursor: usize, val: bool) -> usize {\n        if val {\n            string.push_str(\"true\");\n        } else {\n            string.push_str(\"false\");\n        }\n\n        cursor\n    }\n\n    fn serialize_null(&self, string: &mut String, cursor: usize) -> usize {\n        string.push_str(\"null\");\n        cursor\n    }\n\n    fn deserialize_value(&mut self, input: &[u8], mut pos: usize, depth: usize) -> PResult<usize> {\n        if depth > MAX_JSON_DEPTH {\n            return Err(PError::Message {\n                msg: \"Too deep\".to_string(),\n                location: Some(pos),\n            });\n        }\n\n        pos = skip_whitespace(input, pos);\n        if pos >= input.len() {\n            return Err(PError::Message {\n                msg: \"Unexpected end of input\".to_string(),\n                location: Some(pos),\n            });\n        }\n\n        match input[pos] {\n            b'{' => {\n                pos += 1; // consume '{'\n                pos = self.deserialize_obj(input, pos, depth + 1)?;\n            }\n            b'[' => {\n                pos += 1; // consume '['\n                pos = self.deserialize_array(input, pos, depth + 1)?;\n            }\n            b't' => {\n                pos = self.deserialize_true(input, pos)?;\n            }\n            b'f' => {\n                pos = self.deserialize_false(input, pos)?;\n            }\n            b'n' | b'N' => {\n                pos = self.deserialize_null_or_nan(input, pos)?;\n            }\n            b'\"' | b'\\'' => {\n                pos = self.deserialize_string(input, pos)?;\n            }\n            c if c.is_ascii_digit()\n                || c == b'-'\n                || c == b'+'\n                || c == b'.'\n                || c.eq_ignore_ascii_case(&b'i') =>\n            {\n                pos = self.deserialize_number(input, pos)?;\n            }\n            _ => {\n                return Err(PError::Message {\n                    msg: \"Unexpected character\".to_string(),\n                    location: Some(pos),\n                })\n            }\n        }\n\n        Ok(pos)\n    }\n\n    fn deserialize_obj(&mut self, input: &[u8], mut pos: usize, depth: usize) -> PResult<usize> {\n        if depth > MAX_JSON_DEPTH {\n            return Err(PError::Message {\n                msg: \"Too deep\".to_string(),\n                location: Some(pos),\n            });\n        }\n        if self.data.capacity() - self.data.len() < 50 {\n            self.data.reserve(self.data.capacity());\n        }\n        if pos >= input.len() {\n            return Err(PError::Message {\n                msg: \"Unexpected end of input\".to_string(),\n                location: Some(pos),\n            });\n        }\n\n        let header_pos = self.len();\n        self.write_element_header(header_pos, ElementType::OBJECT, 0, false)\n            .map_err(|_| PError::Message {\n                msg: \"Failed to write header\".to_string(),\n                location: Some(pos),\n            })?;\n        let obj_start = self.len();\n        let mut first = true;\n\n        loop {\n            pos = skip_whitespace(input, pos);\n            if pos >= input.len() {\n                return Err(PError::Message {\n                    msg: \"Unexpected end of input\".to_string(),\n                    location: Some(pos),\n                });\n            }\n\n            match input[pos] {\n                b'}' => {\n                    pos += 1; // consume '}'\n                    if first {\n                        return Ok(pos);\n                    } else {\n                        let obj_size = self.len() - obj_start;\n                        self.write_element_header(header_pos, ElementType::OBJECT, obj_size, false)\n                            .map_err(|_| PError::Message {\n                                msg: \"Failed to write header\".to_string(),\n                                location: Some(pos),\n                            })?;\n                        return Ok(pos);\n                    }\n                }\n                b',' if !first => {\n                    pos += 1; // consume ','\n                    pos = skip_whitespace(input, pos);\n                    if pos >= input.len() {\n                        return Err(PError::Message {\n                            msg: \"Unexpected end of input after comma in object\".to_string(),\n                            location: Some(pos),\n                        });\n                    }\n                    if input[pos] == b',' || input[pos] == b'{' {\n                        return Err(PError::Message {\n                            msg: \"Two commas in a row\".to_string(),\n                            location: Some(pos),\n                        });\n                    }\n                }\n                _ => {\n                    // Parse key (must be string)\n                    pos = self.deserialize_string(input, pos)?;\n\n                    pos = skip_whitespace(input, pos);\n                    if pos >= input.len() || input[pos] != b':' {\n                        return Err(PError::Message {\n                            msg: \"Expected : after object key\".to_string(),\n                            location: Some(pos),\n                        });\n                    }\n                    pos += 1; // consume ':'\n\n                    pos = skip_whitespace(input, pos);\n\n                    // Parse value - can be any JSON value including another object\n                    pos = self.deserialize_value(input, pos, depth + 1)?;\n                    pos = skip_whitespace(input, pos);\n                    if pos < input.len() && !matches!(input[pos], b',' | b'}') {\n                        return Err(PError::Message {\n                            msg: \"Should be , or }}\".to_string(),\n                            location: Some(pos),\n                        });\n                    }\n                    first = false;\n                }\n            }\n        }\n    }\n\n    fn deserialize_array(&mut self, input: &[u8], mut pos: usize, depth: usize) -> PResult<usize> {\n        if depth > MAX_JSON_DEPTH {\n            return Err(PError::Message {\n                msg: \"Too deep\".to_string(),\n                location: Some(pos),\n            });\n        }\n\n        let header_pos = self.len();\n        self.write_element_header(header_pos, ElementType::ARRAY, 0, false)\n            .map_err(|_| PError::Message {\n                msg: \"Failed to write header\".to_string(),\n                location: Some(pos),\n            })?;\n        let arr_start = self.len();\n        let mut first = true;\n\n        loop {\n            pos = skip_whitespace(input, pos);\n            if pos >= input.len() {\n                return Err(PError::Message {\n                    msg: \"Unexpected end of input\".to_string(),\n                    location: Some(pos),\n                });\n            }\n\n            match input[pos] {\n                b']' => {\n                    pos += 1; // consume ']'\n                    if first {\n                        return Ok(pos);\n                    } else {\n                        let arr_len = self.len() - arr_start;\n                        self.write_element_header(header_pos, ElementType::ARRAY, arr_len, false)\n                            .map_err(|_| PError::Message {\n                                msg: \"Failed to write header\".to_string(),\n                                location: Some(pos),\n                            })?;\n                        return Ok(pos);\n                    }\n                }\n                b',' if !first => {\n                    pos += 1; // consume ','\n                    pos = skip_whitespace(input, pos);\n                    if pos >= input.len() {\n                        return Err(PError::Message {\n                            msg: \"Unexpected end of input after comma\".to_string(),\n                            location: Some(pos),\n                        });\n                    }\n                    if input[pos] == b',' {\n                        return Err(PError::Message {\n                            msg: \"Two commas in a row\".to_string(),\n                            location: Some(pos),\n                        });\n                    }\n                }\n                _ => {\n                    pos = skip_whitespace(input, pos);\n\n                    // Parse array element\n                    pos = self.deserialize_value(input, pos, depth + 1)?;\n\n                    first = false;\n                }\n            }\n        }\n    }\n\n    fn deserialize_string(&mut self, input: &[u8], mut pos: usize) -> PResult<usize> {\n        if pos >= input.len() {\n            return Err(PError::Message {\n                msg: \"Unexpected end of input\".to_string(),\n                location: Some(pos),\n            });\n        }\n\n        let string_start = self.len();\n        let quote = input[pos];\n        pos += 1; // consume quote\n\n        let quoted = quote == b'\"' || quote == b'\\'';\n        let mut len = 0;\n\n        if quoted {\n            // Try to find the closing quote and check for simple string\n            let mut end_pos = pos;\n            let is_simple = true;\n\n            while end_pos < input.len() {\n                let c = input[end_pos];\n                if c == quote {\n                    // Found end of string - check if it's simple\n                    if is_simple {\n                        let len = end_pos - pos;\n                        let header_pos = self.data.len();\n\n                        // Write header and content\n                        if len <= 11 {\n                            self.data\n                                .push((ElementType::TEXT as u8) | ((len as u8) << 4));\n                        } else {\n                            self.write_element_header(header_pos, ElementType::TEXT, len, false)\n                                .map_err(|_| PError::Message {\n                                    msg: \"Failed to write header\".to_string(),\n                                    location: Some(pos),\n                                })?;\n                        }\n\n                        self.data.extend_from_slice(&input[pos..end_pos]);\n                        return Ok(end_pos + 1); // Skip past closing quote\n                    }\n                    break;\n                } else if c == b'\\\\' || c < 32 {\n                    // Not a simple string\n                    break;\n                }\n                end_pos += 1;\n            }\n        }\n\n        // Write placeholder header to be updated later\n        self.write_element_header(string_start, ElementType::TEXT, 0, false)\n            .map_err(|_| PError::Message {\n                msg: \"Failed to write header\".to_string(),\n                location: Some(pos),\n            })?;\n\n        if pos >= input.len() {\n            return Err(PError::Message {\n                msg: \"Unexpected end of input\".to_string(),\n                location: Some(pos),\n            });\n        }\n\n        let mut element_type = ElementType::TEXT;\n\n        // Special case for unquoted JSON5 keys (identifiers)\n        if !quoted {\n            self.data.push(quote);\n            len += 1;\n\n            if pos < input.len() && input[pos] == b':' {\n                self.write_element_header(string_start, element_type, len, false)\n                    .map_err(|_| PError::Message {\n                        msg: \"Failed to write header\".to_string(),\n                        location: Some(pos),\n                    })?;\n                return Ok(pos);\n            }\n        }\n\n        let mut escape_buffer = [0u8; 6]; // Buffer for escape sequences\n\n        while pos < input.len() {\n            let c = input[pos];\n            pos += 1;\n\n            if quoted && c == quote {\n                break; // End of string\n            } else if !quoted && (c == b'\"' || c == b'\\'') {\n                return Err(PError::Message {\n                    msg: \"Unexpected input\".to_string(),\n                    location: Some(pos),\n                });\n            } else if c == b'\\\\' {\n                // Handle escape sequences\n                if pos >= input.len() {\n                    return Err(PError::Message {\n                        msg: \"Unexpected end of input\".to_string(),\n                        location: Some(pos),\n                    });\n                }\n\n                let esc = input[pos];\n                pos += 1;\n\n                match esc {\n                    b'b' => {\n                        self.data.extend_from_slice(b\"\\\\b\");\n                        len += 2;\n                        element_type = ElementType::TEXTJ;\n                    }\n                    b'f' => {\n                        self.data.extend_from_slice(b\"\\\\f\");\n                        len += 2;\n                        element_type = ElementType::TEXTJ;\n                    }\n                    b'n' => {\n                        self.data.extend_from_slice(b\"\\\\n\");\n                        len += 2;\n                        element_type = ElementType::TEXTJ;\n                    }\n                    b'r' => {\n                        self.data.extend_from_slice(b\"\\\\r\");\n                        len += 2;\n                        element_type = ElementType::TEXTJ;\n                    }\n                    b't' => {\n                        self.data.extend_from_slice(b\"\\\\t\");\n                        len += 2;\n                        element_type = ElementType::TEXTJ;\n                    }\n                    b'\\\\' | b'\"' | b'/' => {\n                        self.data.push(b'\\\\');\n                        self.data.push(esc);\n                        len += 2;\n                        element_type = ElementType::TEXTJ;\n                    }\n                    b'u' => {\n                        // Unicode escape sequence\n                        if pos + 4 > input.len() {\n                            return Err(PError::Message {\n                                msg: \"Incomplete unicode escape sequence\".to_string(),\n                                location: Some(pos),\n                            });\n                        }\n\n                        escape_buffer[0] = b'\\\\';\n                        escape_buffer[1] = b'u';\n\n                        for i in 0..4 {\n                            let h = input[pos + i];\n                            if !is_hex_digit(h) {\n                                return Err(PError::Message {\n                                    msg: \"Invalid unicode escape sequence\".to_string(),\n                                    location: Some(pos),\n                                });\n                            }\n                            escape_buffer[2 + i] = h;\n                        }\n\n                        self.data.extend_from_slice(&escape_buffer[0..6]);\n                        len += 6;\n                        pos += 4;\n                        element_type = ElementType::TEXTJ;\n                    }\n                    // JSON5 extensions\n                    b'\\n' => {\n                        self.data.extend_from_slice(b\"\\\\\\n\");\n                        len += 2;\n                        element_type = ElementType::TEXT5;\n                    }\n                    b'\\'' => {\n                        self.data.extend_from_slice(b\"\\\\\\'\");\n                        len += 2;\n                        element_type = ElementType::TEXT5;\n                    }\n                    b'0' => {\n                        self.data.extend_from_slice(b\"\\\\0\");\n                        len += 2;\n                        element_type = ElementType::TEXT5;\n                    }\n                    b'v' => {\n                        self.data.extend_from_slice(b\"\\\\v\");\n                        len += 2;\n                        element_type = ElementType::TEXT5;\n                    }\n                    b'x' => {\n                        // Hex escape sequence (JSON5)\n                        if pos + 2 > input.len() {\n                            return Err(PError::Message {\n                                msg: \"Incopmlete hex escape sequence\".to_string(),\n                                location: Some(pos),\n                            });\n                        }\n\n                        escape_buffer[0] = b'\\\\';\n                        escape_buffer[1] = b'x';\n\n                        for i in 0..2 {\n                            let h = input[pos + i];\n                            if !is_hex_digit(h) {\n                                return Err(PError::Message {\n                                    msg: \"Invalid hex escape sequence\".to_string(),\n                                    location: Some(pos),\n                                });\n                            }\n                            escape_buffer[2 + i] = h;\n                        }\n\n                        self.data.extend_from_slice(&escape_buffer[0..4]);\n                        len += 4;\n                        pos += 2;\n                        element_type = ElementType::TEXT5;\n                    }\n\n                    _ => {\n                        return Err(PError::Message {\n                            msg: \"Invalid escape sequence\".to_string(),\n                            location: Some(pos),\n                        });\n                    }\n                }\n            } else if !quoted && (c == b':' || c.is_ascii_whitespace()) {\n                // End of unquoted identifier\n                pos -= 1; // Put back the terminating character\n                break;\n            } else if c <= 0x1F {\n                // Control character\n                element_type = ElementType::TEXT5;\n                self.data.push(c);\n                len += 1;\n            } else {\n                // Normal character\n                self.data.push(c);\n                len += 1;\n            }\n        }\n\n        // Write final header with correct type and size\n        self.write_element_header(string_start, element_type, len, false)\n            .map_err(|_| PError::Message {\n                msg: \"Failed to write header\".to_string(),\n                location: Some(pos),\n            })?;\n\n        Ok(pos)\n    }\n\n    fn deserialize_number(&mut self, input: &[u8], mut pos: usize) -> PResult<usize> {\n        let num_start = self.len();\n        let mut len = 0;\n        let mut is_float = false;\n        let mut is_json5 = false;\n        let mut has_digit = false;\n\n        // Write placeholder header\n        self.write_element_header(num_start, ElementType::INT, 0, false)\n            .map_err(|_| PError::Message {\n                msg: \"Failed to write header\".to_string(),\n                location: Some(pos),\n            })?;\n\n        // Handle sign\n        if pos < input.len() && (input[pos] == b'-' || input[pos] == b'+') {\n            if input[pos] == b'+' {\n                is_json5 = true;\n                pos += 1;\n            } else {\n                self.data.push(input[pos]);\n                pos += 1;\n                len += 1;\n            }\n        }\n\n        // Handle JSON5 float starting with dot\n        if pos < input.len() && input[pos] == b'.' {\n            is_json5 = true;\n            is_float = true;\n        }\n\n        // Check for hex (JSON5)\n        if pos < input.len() && input[pos] == b'0' && pos + 1 < input.len() {\n            self.data.push(input[pos]);\n            pos += 1;\n            len += 1;\n            has_digit = true;\n\n            if pos < input.len() && (input[pos] == b'x' || input[pos] == b'X') {\n                // Hexadecimal number\n                self.data.push(input[pos]);\n                pos += 1;\n                len += 1;\n\n                let mut has_digit = false;\n                while pos < input.len() && is_hex_digit(input[pos]) {\n                    self.data.push(input[pos]);\n                    pos += 1;\n                    len += 1;\n                    has_digit = true;\n                }\n\n                if !has_digit {\n                    return Err(PError::Message {\n                        msg: \"Invalid hex digit\".to_string(),\n                        location: Some(pos),\n                    });\n                }\n\n                self.write_element_header(num_start, ElementType::INT5, len, false)\n                    .map_err(|_| PError::Message {\n                        msg: \"Unexpected input after json\".to_string(),\n                        location: Some(pos),\n                    })?;\n                return Ok(pos);\n            } else if pos < input.len() && input[pos].is_ascii_digit() {\n                // Leading zero followed by digit is not allowed in standard JSON\n                return Err(PError::Message {\n                    msg: \"Leading zero is not allowed\".to_string(),\n                    location: Some(pos),\n                });\n            }\n        }\n\n        // Check for Infinity\n        if pos < input.len() && (input[pos] == b'I' || input[pos] == b'i') {\n            // Try to match \"Infinity\"\n            let infinity = b\"infinity\";\n            let mut i = 0;\n\n            while i < infinity.len() && pos + i < input.len() {\n                if input[pos + i].to_ascii_lowercase() != infinity[i] {\n                    return Err(PError::Message {\n                        msg: \"Invalid number\".to_string(),\n                        location: Some(pos),\n                    });\n                }\n                i += 1;\n            }\n\n            if i < infinity.len() {\n                return Err(PError::Message {\n                    msg: \"incomplete infinity\".to_string(),\n                    location: Some(pos),\n                });\n            }\n\n            pos += infinity.len();\n\n            // Write Infinity as 9.0e+999\n            self.data.extend_from_slice(b\"9.0e+999\");\n            self.write_element_header(\n                num_start,\n                ElementType::FLOAT5,\n                len + INFINITY_CHAR_COUNT as usize,\n                false,\n            )\n            .map_err(|_| PError::Message {\n                msg: \"Failed to write header\".to_string(),\n                location: Some(pos),\n            })?;\n\n            return Ok(pos);\n        }\n\n        // Regular number parsing\n        while pos < input.len() {\n            match input[pos] {\n                b'0'..=b'9' => {\n                    self.data.push(input[pos]);\n                    pos += 1;\n                    len += 1;\n                    has_digit = true;\n                }\n                b'.' => {\n                    is_float = true;\n                    self.data.push(input[pos]);\n                    pos += 1;\n                    len += 1;\n\n                    // Check for trailing dot\n                    if pos >= input.len() || !input[pos].is_ascii_digit() {\n                        is_json5 = true;\n                    }\n                }\n                b'e' | b'E' => {\n                    is_float = true;\n                    self.data.push(input[pos]);\n                    pos += 1;\n                    len += 1;\n\n                    // Optional sign after exponent\n                    if pos < input.len() && (input[pos] == b'+' || input[pos] == b'-') {\n                        self.data.push(input[pos]);\n                        pos += 1;\n                        len += 1;\n                    }\n\n                    // Exponent must have at least one digit\n                    if pos >= input.len() || !input[pos].is_ascii_digit() {\n                        return Err(PError::Message {\n                            msg: \"malformed JSON\".to_string(),\n                            location: Some(pos),\n                        });\n                    }\n                }\n                _ => break,\n            }\n        }\n\n        // Must have at least one digit\n        if !has_digit && (!is_json5 || !is_float) {\n            return Err(PError::Message {\n                msg: \"Not a digit\".to_string(),\n                location: Some(pos),\n            });\n        }\n\n        // Determine the appropriate element type\n        let element_type = if is_float {\n            if is_json5 {\n                ElementType::FLOAT5\n            } else {\n                ElementType::FLOAT\n            }\n        } else if is_json5 {\n            ElementType::INT5\n        } else {\n            ElementType::INT\n        };\n\n        self.write_element_header(num_start, element_type, len, false)\n            .map_err(|_| PError::Message {\n                msg: \"Unexpected input after json\".to_string(),\n                location: Some(pos),\n            })?;\n\n        Ok(pos)\n    }\n\n    fn deserialize_true(&mut self, input: &[u8], mut pos: usize) -> PResult<usize> {\n        let true_lit = b\"true\";\n        for i in 0..true_lit.len() {\n            if pos + i >= input.len() || input[pos + i] != true_lit[i] {\n                return Err(PError::Message {\n                    msg: \"Expected true\".to_string(),\n                    location: Some(pos),\n                });\n            }\n        }\n\n        pos += true_lit.len();\n        self.data.push(ElementType::TRUE as u8);\n\n        Ok(pos)\n    }\n\n    fn deserialize_false(&mut self, input: &[u8], mut pos: usize) -> PResult<usize> {\n        let false_lit = b\"false\";\n        for i in 0..false_lit.len() {\n            if pos + i >= input.len() || input[pos + i] != false_lit[i] {\n                return Err(PError::Message {\n                    msg: \"Expected false\".to_string(),\n                    location: Some(pos),\n                });\n            }\n        }\n\n        pos += false_lit.len();\n        self.data.push(ElementType::FALSE as u8);\n\n        Ok(pos)\n    }\n\n    pub fn deserialize_null_or_nan(&mut self, input: &[u8], mut pos: usize) -> PResult<usize> {\n        // First check if we have enough bytes remaining for \"nan\" (minimum 3 bytes)\n        if pos + 3 > input.len() {\n            return Err(PError::Message {\n                msg: \"Unexpected end of input\".to_string(),\n                location: Some(pos),\n            });\n        }\n\n        // Fast path for \"null\"\n        if pos + 4 <= input.len()\n            && input[pos] == b'n'\n            && input[pos + 1] == b'u'\n            && input[pos + 2] == b'l'\n            && input[pos + 3] == b'l'\n        {\n            pos += 4;\n            self.data.push(ElementType::NULL as u8);\n            return Ok(pos);\n        }\n\n        // Fast path for \"nan\"\n        if pos + 3 <= input.len()\n            && (input[pos] == b'n' || input[pos] == b'N')\n            && (input[pos + 1] == b'a' || input[pos + 1] == b'A')\n            && (input[pos + 2] == b'n' || input[pos + 2] == b'N')\n        {\n            pos += 3;\n            self.data.push(ElementType::NULL as u8);\n            return Ok(pos);\n        }\n\n        Err(PError::Message {\n            msg: \"Expected null or nan\".to_string(),\n            location: Some(pos),\n        })\n    }\n\n    fn write_element_header(\n        &mut self,\n        cursor: usize,\n        element_type: ElementType,\n        payload_size: usize,\n        size_might_change: bool,\n    ) -> Result<usize> {\n        if payload_size <= 11 && !size_might_change {\n            let header_byte = (element_type as u8) | ((payload_size as u8) << 4);\n            if cursor == self.len() {\n                self.data.push(header_byte);\n            } else {\n                self.data[cursor] = header_byte;\n            }\n            return Ok(1);\n        }\n\n        let header = JsonbHeader::new(element_type, payload_size).into_bytes();\n        let header_bytes = header.as_bytes();\n        let header_len = header_bytes.len();\n\n        if cursor == self.len() {\n            self.data.extend_from_slice(header_bytes);\n            return Ok(header_len);\n        }\n\n        let old_len = if size_might_change {\n            let (_, offset) = self.read_header(cursor)?;\n            offset\n        } else {\n            1\n        };\n\n        let new_len = header_bytes.len();\n\n        match new_len.cmp(&old_len) {\n            std::cmp::Ordering::Greater => {\n                self.data.splice(\n                    cursor + old_len..cursor + old_len,\n                    std::iter::repeat_n(0, new_len - old_len),\n                );\n            }\n            std::cmp::Ordering::Less => {\n                self.data.drain(cursor + new_len..cursor + old_len);\n            }\n            std::cmp::Ordering::Equal => {}\n        }\n\n        for (i, &byte) in header_bytes.iter().enumerate() {\n            self.data[cursor + i] = byte;\n        }\n\n        Ok(new_len)\n    }\n\n    fn from_str(input: &str) -> PResult<Self> {\n        let mut result = Self::new(input.len(), None);\n        let input = input.as_bytes();\n\n        if input.is_empty() {\n            return Err(PError::Message {\n                msg: \"Unexpected input after json\".to_string(),\n                location: None,\n            });\n        }\n\n        // Parse the first complete JSON value\n        let mut pos = 0;\n        pos = result.deserialize_value(input, pos, 0)?;\n\n        // Skip any trailing whitespace\n        pos = skip_whitespace(input, pos);\n\n        // Check for any non-whitespace characters after the JSON value\n        if pos < input.len() {\n            return Err(PError::Message {\n                msg: \"Unexpected input after json\".to_string(),\n                location: Some(pos),\n            });\n        }\n\n        Ok(result)\n    }\n\n    pub fn from_str_with_mode(input: &str, mode: Conv) -> PResult<Self> {\n        // Parse directly as JSON if it's already JSON subtype or strict mode is on\n        if matches!(mode, Conv::ToString) {\n            // Escape backslashes first, then double quotes\n            let mut str = input.replace('\\\\', \"\\\\\\\\\").replace('\"', \"\\\\\\\"\");\n            str.insert(0, '\"');\n            str.push('\"');\n            Jsonb::from_str(&str)\n        } else {\n            Jsonb::from_str(input)\n        }\n    }\n\n    pub fn from_raw_data(data: &[u8]) -> Self {\n        Self::new(data.len(), Some(data))\n    }\n\n    pub fn data(self) -> Vec<u8> {\n        self.data\n    }\n\n    pub fn element_type_at(&self, idx: usize) -> Result<ElementType> {\n        let (JsonbHeader(element_type, _), _) = self.read_header(idx)?;\n        Ok(element_type)\n    }\n\n    pub fn array_len(&self) -> Result<usize> {\n        let (header, header_skip) = self.read_header(0)?;\n        if header.0 != ElementType::ARRAY {\n            return Ok(0);\n        }\n\n        let end = header_skip\n            .checked_add(header.1)\n            .ok_or_else(|| LimboError::ParseError(\"malformed JSON\".to_string()))?;\n        let mut count = 0;\n        let mut pos = header_skip;\n        while pos < end {\n            pos = self.skip_element(pos)?;\n            count += 1;\n        }\n\n        Ok(count)\n    }\n\n    pub fn navigate_path(\n        &mut self,\n        path: &JsonPath,\n        mode: PathOperationMode,\n    ) -> Result<Vec<JsonTraversalResult>> {\n        let mut path_iter = path.elements.iter().peekable();\n        let mut pos = 0;\n        let mut stack: Vec<JsonTraversalResult> = Vec::with_capacity(path.elements.len());\n\n        while let Some(current) = path_iter.next() {\n            let next_is_array = matches!(path_iter.peek(), Some(PathElement::ArrayLocator(_)))\n                && !matches!(current, PathElement::ArrayLocator(_));\n\n            let is_intermediate_segment = if next_is_array {\n                let mut temp_iter = path_iter.clone();\n                temp_iter.next(); // skip the array locator\n                temp_iter.peek().is_some()\n            } else {\n                path_iter.peek().is_some()\n            };\n\n            let segment_mode = if is_intermediate_segment {\n                PathOperationMode::Upsert\n            } else {\n                mode\n            };\n\n            let result = if next_is_array {\n                let array_locator = path_iter.next().ok_or_else(|| {\n                    LimboError::InternalError(\n                        \"array locator should exist after peek check\".to_string(),\n                    )\n                })?;\n\n                self.navigate_to_segment(\n                    SegmentVariant::KeyWithArrayIndex(current, array_locator),\n                    pos,\n                    segment_mode,\n                )?\n            } else {\n                self.navigate_to_segment(SegmentVariant::Single(current), pos, segment_mode)?\n            };\n\n            pos = match &result.array_position_info {\n                Some(ArrayPositionKind::SpecificIndex(idx)) => *idx,\n                None => result.field_value_index,\n            };\n\n            stack.push(result);\n        }\n\n        Ok(stack)\n    }\n\n    pub fn operate_on_path<T>(&mut self, path: &JsonPath, operation: &mut T) -> Result<()>\n    where\n        T: PathOperation,\n    {\n        let mode = operation.operation_mode();\n\n        let stack = self.navigate_path(path, mode)?;\n\n        operation.execute(self, stack)?;\n\n        Ok(())\n    }\n\n    fn update_parent_references(\n        &mut self,\n        stack: Vec<JsonTraversalResult>,\n        delta: isize,\n    ) -> Result<()> {\n        let mut delta = delta;\n        for parent in stack.iter().rev() {\n            let (JsonbHeader(el_type, el_size), el_header_len) =\n                self.read_header(parent.field_value_index)?;\n\n            if el_type == ElementType::ARRAY {\n                let arr_element_idx = parent.get_array_index().ok_or_else(|| {\n                    LimboError::InternalError(\"array element should have index\".to_string())\n                })?;\n                let (JsonbHeader(arr_el_type, arr_el_size), arr_el_header_len) =\n                    self.read_header(arr_element_idx)?;\n\n                let new_arr_el_header_len = self.write_element_header(\n                    arr_element_idx,\n                    arr_el_type,\n                    (arr_el_size as isize + delta) as usize,\n                    true,\n                )?;\n\n                delta += (new_arr_el_header_len - arr_el_header_len) as isize;\n            }\n            let new_size = el_size as isize + delta;\n            let new_header_size = self.write_element_header(\n                parent.field_value_index,\n                el_type,\n                new_size as usize,\n                true,\n            )?;\n\n            let header_diff = new_header_size as isize - el_header_len as isize;\n\n            delta += parent.delta;\n            delta += header_diff;\n        }\n\n        Ok(())\n    }\n\n    fn navigate_to_segment(\n        &mut self,\n        segment: SegmentVariant,\n        mut pos: usize,\n        mode: PathOperationMode,\n    ) -> Result<JsonTraversalResult> {\n        let (JsonbHeader(element_type, element_size), header_size) = self.read_header(pos)?;\n\n        match segment {\n            SegmentVariant::Single(PathElement::Root()) => {\n                return Ok(JsonTraversalResult::new(\n                    pos,\n                    JsonLocationKind::DocumentRoot,\n                    0,\n                ));\n            }\n            SegmentVariant::Single(PathElement::ArrayLocator(idx)) => {\n                let (JsonbHeader(root_type, root_size), root_header_size) =\n                    self.read_header(pos)?;\n\n                if root_type == ElementType::ARRAY {\n                    let end_pos = pos + root_header_size + root_size;\n\n                    match idx {\n                        Some(idx) if *idx >= 0 => {\n                            let mut count = 0;\n                            let mut arr_pos = pos + root_header_size;\n\n                            while arr_pos < end_pos && count != *idx as usize {\n                                arr_pos = self.skip_element(arr_pos)?;\n                                count += 1;\n                            }\n\n                            if mode.allows_insert() && arr_pos == end_pos {\n                                let placeholder =\n                                    JsonbHeader::new(ElementType::OBJECT, 0).into_bytes();\n                                let placeholder_bytes = placeholder.as_bytes();\n\n                                self.data\n                                    .splice(arr_pos..arr_pos, placeholder_bytes.iter().copied());\n\n                                return Ok(JsonTraversalResult::with_array_index(\n                                    pos,\n                                    JsonLocationKind::ArrayEntry,\n                                    placeholder_bytes.len() as isize,\n                                    arr_pos,\n                                ));\n                            }\n\n                            if arr_pos != end_pos && mode.allows_replace() {\n                                return Ok(JsonTraversalResult::with_array_index(\n                                    pos,\n                                    JsonLocationKind::ArrayEntry,\n                                    0,\n                                    arr_pos,\n                                ));\n                            }\n\n                            bail_parse_error!(\"Not found!\");\n                        }\n                        Some(idx) if *idx < 0 => {\n                            let mut idx_map: HashMap<i32, usize> = HashMap::with_capacity(100);\n                            let mut element_idx = 0;\n                            let mut arr_pos = pos + root_header_size;\n\n                            while arr_pos < end_pos {\n                                idx_map.insert(element_idx, arr_pos);\n                                arr_pos = self.skip_element(arr_pos)?;\n                                element_idx += 1;\n                            }\n\n                            let real_idx = element_idx + idx;\n\n                            if let Some(index) = idx_map.get(&real_idx) {\n                                return Ok(JsonTraversalResult::with_array_index(\n                                    pos,\n                                    JsonLocationKind::ArrayEntry,\n                                    0,\n                                    *index,\n                                ));\n                            } else {\n                                bail_parse_error!(\"Element with negative index not found\")\n                            }\n                        }\n                        _ => unreachable!(),\n                    }\n                } else {\n                    if root_type == ElementType::OBJECT\n                        && root_size == 0\n                        && (*idx == Some(0) || idx.is_none())\n                        && mode.allows_insert()\n                    {\n                        let array = JsonbHeader::new(ElementType::ARRAY, 0).into_bytes();\n                        let array_bytes = array.as_bytes();\n                        let placeholder = JsonbHeader::new(ElementType::OBJECT, 0).into_bytes();\n                        let placeholder_bytes = placeholder.as_bytes();\n                        self.data.splice(\n                            pos..pos + root_header_size,\n                            array_bytes\n                                .iter()\n                                .copied()\n                                .chain(placeholder_bytes.iter().copied()),\n                        );\n\n                        return Ok(JsonTraversalResult::with_array_index(\n                            pos,\n                            JsonLocationKind::ArrayEntry,\n                            placeholder_bytes.len() as isize,\n                            pos + array_bytes.len(),\n                        ));\n                    };\n                    bail_parse_error!(\"Root is not an array\");\n                }\n            }\n            SegmentVariant::Single(PathElement::Key(path_key, is_raw)) => {\n                if element_type == ElementType::OBJECT {\n                    let end_pos = pos + element_size + header_size;\n\n                    pos += header_size;\n\n                    while pos < end_pos {\n                        let (JsonbHeader(key_type, key_len), key_header_len) =\n                            self.read_header(pos)?;\n\n                        if !key_type.is_valid_key() {\n                            bail_parse_error!(\"Key should be string\");\n                        }\n\n                        let key_start = pos + key_header_len;\n                        let json_key = unsafe {\n                            from_utf8_unchecked(&self.data[key_start..key_start + key_len])\n                        };\n\n                        if compare((json_key, key_type), (path_key, *is_raw)) {\n                            if mode.allows_replace() {\n                                let value_pos = pos + key_header_len + key_len;\n                                let key_pos = pos;\n\n                                return Ok(JsonTraversalResult::new(\n                                    value_pos,\n                                    JsonLocationKind::ObjectProperty(key_pos),\n                                    0,\n                                ));\n                            } else {\n                                bail_parse_error!(\"Cant replace\")\n                            }\n                        } else {\n                            pos += key_header_len + key_len;\n                            pos = self.skip_element(pos)?;\n                        }\n                    }\n\n                    if mode.allows_insert() {\n                        let key_type = if *is_raw {\n                            ElementType::TEXTRAW\n                        } else {\n                            ElementType::TEXT\n                        };\n\n                        let key_header = JsonbHeader::new(key_type, path_key.len()).into_bytes();\n                        let key_header_bytes = key_header.as_bytes();\n                        let key_bytes = path_key.as_bytes();\n                        let value_header = JsonbHeader::new(ElementType::OBJECT, 0).into_bytes();\n                        let value_header_bytes = value_header.as_bytes();\n\n                        self.data.splice(\n                            pos..pos,\n                            key_header_bytes\n                                .iter()\n                                .copied()\n                                .chain(key_bytes.iter().copied())\n                                .chain(value_header_bytes.iter().copied()),\n                        );\n\n                        let key_idx = pos;\n                        let value_idx = pos + key_header_bytes.len() + key_bytes.len();\n                        let delta =\n                            key_header_bytes.len() + key_bytes.len() + value_header_bytes.len();\n\n                        return Ok(JsonTraversalResult::new(\n                            value_idx,\n                            JsonLocationKind::ObjectProperty(key_idx),\n                            delta as isize,\n                        ));\n                    } else {\n                        bail_parse_error!(\"Mode does not allow insert cannot create new key!\")\n                    }\n                } else {\n                    bail_parse_error!(\"Looks like this is noop\");\n                }\n            }\n            SegmentVariant::KeyWithArrayIndex(\n                PathElement::Root(),\n                PathElement::ArrayLocator(idx),\n            ) => {\n                let (JsonbHeader(root_type, root_size), root_header_size) =\n                    self.read_header(pos)?;\n\n                if root_type == ElementType::ARRAY {\n                    let end_pos = pos + root_header_size + root_size;\n\n                    match idx {\n                        Some(idx) if *idx >= 0 => {\n                            let mut count = 0;\n                            let mut arr_pos = pos + root_header_size;\n\n                            while arr_pos < end_pos && count != *idx as usize {\n                                arr_pos = self.skip_element(arr_pos)?;\n                                count += 1;\n                            }\n\n                            if mode.allows_insert() && arr_pos == end_pos && count == *idx as usize\n                            {\n                                let placeholder =\n                                    JsonbHeader::new(ElementType::OBJECT, 0).into_bytes();\n                                let placeholder_bytes = placeholder.as_bytes();\n\n                                self.data\n                                    .splice(arr_pos..arr_pos, placeholder_bytes.iter().copied());\n\n                                return Ok(JsonTraversalResult::with_array_index(\n                                    pos,\n                                    JsonLocationKind::DocumentRoot,\n                                    placeholder_bytes.len() as isize,\n                                    arr_pos,\n                                ));\n                            }\n\n                            if arr_pos != end_pos && mode.allows_replace() {\n                                return Ok(JsonTraversalResult::with_array_index(\n                                    pos,\n                                    JsonLocationKind::DocumentRoot,\n                                    0,\n                                    arr_pos,\n                                ));\n                            }\n\n                            bail_parse_error!(\"Not found!\");\n                        }\n                        Some(idx) if *idx < 0 => {\n                            let mut idx_map: HashMap<i32, usize> = HashMap::with_capacity(100);\n                            let mut element_idx = 0;\n                            let mut arr_pos = pos + root_header_size;\n\n                            while arr_pos < end_pos {\n                                idx_map.insert(element_idx, arr_pos);\n                                arr_pos = self.skip_element(arr_pos)?;\n                                element_idx += 1;\n                            }\n\n                            let real_idx = element_idx + idx;\n\n                            if let Some(index) = idx_map.get(&real_idx) {\n                                return Ok(JsonTraversalResult::with_array_index(\n                                    pos,\n                                    JsonLocationKind::DocumentRoot,\n                                    0,\n                                    *index,\n                                ));\n                            } else {\n                                bail_parse_error!(\"Element with negative index not found\")\n                            }\n                        }\n                        _ => unreachable!(),\n                    }\n                } else {\n                    bail_parse_error!(\"Root is not an array\");\n                }\n            }\n            SegmentVariant::KeyWithArrayIndex(\n                PathElement::Key(path_key, is_raw),\n                PathElement::ArrayLocator(idx),\n            ) => {\n                if element_type != ElementType::OBJECT {\n                    bail_parse_error!(\"Not an object\");\n                }\n\n                let end_pos = pos + header_size + element_size;\n\n                let mut current_pos = pos + header_size;\n\n                while current_pos < end_pos {\n                    let (JsonbHeader(key_type, key_size), key_header_size) =\n                        self.read_header(current_pos)?;\n\n                    if !key_type.is_valid_key() {\n                        bail_parse_error!(\"Key should be string\")\n                    }\n\n                    let obj_key = unsafe {\n                        from_utf8_unchecked(\n                            &self.data[current_pos + key_header_size\n                                ..current_pos + key_header_size + key_size],\n                        )\n                    };\n\n                    if compare((obj_key, key_type), (path_key, *is_raw)) {\n                        break;\n                    } else {\n                        current_pos =\n                            self.skip_element(current_pos + key_size + key_header_size)?;\n                    }\n                }\n\n                if current_pos == end_pos && mode.allows_insert() {\n                    if let Some(idx_val) = idx {\n                        if *idx_val != 0 {\n                            bail_parse_error!(\"cant create new arr with idx\");\n                        }\n                    }\n\n                    let key_header_type = if *is_raw {\n                        ElementType::TEXTRAW\n                    } else {\n                        ElementType::TEXT\n                    };\n\n                    let key_header = JsonbHeader::new(key_header_type, path_key.len()).into_bytes();\n                    let key_header_bytes = key_header.as_bytes();\n                    let key_bytes = path_key.as_bytes();\n                    let array_header = JsonbHeader::new(ElementType::ARRAY, 1).into_bytes();\n                    let array_header_bytes = array_header.as_bytes();\n                    let array_value_header = JsonbHeader::new(ElementType::OBJECT, 0).into_bytes();\n                    let array_value_header_bytes = array_value_header.as_bytes();\n\n                    let delta = key_header_bytes.len()\n                        + key_bytes.len()\n                        + array_header_bytes.len()\n                        + array_value_header_bytes.len();\n\n                    self.data.splice(\n                        current_pos..current_pos,\n                        key_header_bytes\n                            .iter()\n                            .copied()\n                            .chain(key_bytes.iter().copied())\n                            .chain(array_header_bytes.iter().copied())\n                            .chain(array_value_header_bytes.iter().copied()),\n                    );\n\n                    let key_idx = current_pos;\n                    let value_idx = current_pos + key_header_bytes.len() + key_bytes.len();\n                    let array_idx = value_idx + array_header_bytes.len();\n\n                    return Ok(JsonTraversalResult::with_array_index(\n                        value_idx,\n                        JsonLocationKind::ObjectProperty(key_idx),\n                        delta as isize,\n                        array_idx,\n                    ));\n                }\n\n                if current_pos != end_pos && mode.allows_replace() {\n                    let key_idx = current_pos;\n\n                    current_pos = self.skip_element(current_pos)?;\n                    let value_idx = current_pos;\n\n                    let (JsonbHeader(value_type, value_size), value_header_size) =\n                        self.read_header(value_idx)?;\n\n                    if value_type != ElementType::ARRAY {\n                        bail_parse_error!(\"Should be array\")\n                    }\n\n                    let end_pos = current_pos + value_header_size + value_size;\n\n                    match idx {\n                        Some(idx) if *idx >= 0 => {\n                            let mut count = 0;\n                            let mut arr_pos = value_idx + value_header_size;\n\n                            while arr_pos < end_pos && count != *idx as usize {\n                                arr_pos = self.skip_element(arr_pos)?;\n                                count += 1;\n                            }\n\n                            if mode.allows_insert() && arr_pos == end_pos && count == *idx as usize\n                            {\n                                let placeholder =\n                                    JsonbHeader::new(ElementType::OBJECT, 0).into_bytes();\n                                let placeholder_bytes = placeholder.as_bytes();\n\n                                self.data\n                                    .splice(arr_pos..arr_pos, placeholder_bytes.iter().copied());\n                                self.write_element_header(\n                                    value_idx,\n                                    ElementType::ARRAY,\n                                    value_size + placeholder_bytes.len(),\n                                    true,\n                                )?;\n                                return Ok(JsonTraversalResult::with_array_index(\n                                    value_idx,\n                                    JsonLocationKind::ObjectProperty(key_idx),\n                                    placeholder_bytes.len() as isize,\n                                    arr_pos,\n                                ));\n                            }\n\n                            if arr_pos != end_pos && mode.allows_replace() {\n                                return Ok(JsonTraversalResult::with_array_index(\n                                    value_idx,\n                                    JsonLocationKind::ObjectProperty(key_idx),\n                                    0,\n                                    arr_pos,\n                                ));\n                            }\n\n                            bail_parse_error!(\"Not found!\");\n                        }\n                        Some(idx) if *idx < 0 => {\n                            let mut idx_map: HashMap<i32, usize> = HashMap::with_capacity(100);\n                            let mut element_idx = 0;\n                            let mut arr_pos = value_idx + value_header_size;\n\n                            while arr_pos < end_pos {\n                                idx_map.insert(element_idx, arr_pos);\n                                arr_pos = self.skip_element(arr_pos)?;\n                                element_idx += 1;\n                            }\n\n                            let real_idx = element_idx + idx;\n\n                            if let Some(index) = idx_map.get(&real_idx) {\n                                return Ok(JsonTraversalResult::with_array_index(\n                                    value_idx,\n                                    JsonLocationKind::ObjectProperty(key_idx),\n                                    0,\n                                    *index,\n                                ));\n                            } else {\n                                bail_parse_error!(\n                                    \"ERROR: Element at negative index {} not found\",\n                                    idx\n                                );\n                            }\n                        }\n                        Some(_) => unreachable!(),\n                        None => {\n                            if mode.allows_insert() {\n                                let placeholder =\n                                    JsonbHeader::new(ElementType::OBJECT, 0).into_bytes();\n                                let placeholder_bytes = placeholder.as_bytes();\n                                let insertion_point = value_idx + value_size + value_header_size;\n\n                                self.data.insert(insertion_point, placeholder_bytes[0]);\n                            } else {\n                                bail_parse_error!(\"Cant insert\")\n                            }\n                        }\n                    }\n                }\n            }\n            _ => {\n                unreachable!()\n            }\n        };\n\n        Err(LimboError::ParseError(\"Not found\".to_string()))\n    }\n\n    fn skip_element(&self, pos: usize) -> Result<usize> {\n        let (header, skip_header) = self.read_header(pos)?;\n        pos.checked_add(skip_header)\n            .and_then(|p| p.checked_add(header.1))\n            .ok_or_else(|| LimboError::ParseError(\"malformed JSON\".to_string()))\n    }\n\n    // TODO Primitive implementation could be optimized.\n    pub fn patch(&mut self, patch: &Jsonb) -> Result<()> {\n        let (patch_header, _) = patch.read_header(0)?;\n\n        if patch_header.0 != ElementType::OBJECT {\n            self.data.clear();\n            self.data.extend_from_slice(&patch.data);\n            return Ok(());\n        }\n\n        let result = self;\n\n        let mut work_stack = VecDeque::with_capacity(10);\n        work_stack.push_back((\n            JsonPath {\n                elements: vec![PathElement::Root()],\n            },\n            0,\n        ));\n\n        while let Some((path, patch_cursor)) = work_stack.pop_front() {\n            let (patch_obj_header, patch_obj_header_size) = patch.read_header(patch_cursor)?;\n\n            if patch_obj_header.0 != ElementType::OBJECT {\n                continue;\n            }\n\n            let patch_end = patch_cursor + patch_obj_header_size + patch_obj_header.1;\n            let mut patch_key_cursor = patch_cursor + patch_obj_header_size;\n\n            let mut key_values = Vec::new();\n\n            while patch_key_cursor < patch_end {\n                let (key_header, key_header_size) = patch.read_header(patch_key_cursor)?;\n                if !key_header.0.is_valid_key() {\n                    return Err(LimboError::ParseError(\"Invalid key type\".to_string()));\n                }\n\n                let key_start = patch_key_cursor + key_header_size;\n                let key_text = unsafe {\n                    from_utf8_unchecked(&patch.data[key_start..key_start + key_header.1])\n                };\n\n                // Read the value\n                let value_cursor = key_start + key_header.1;\n                let (value_header, value_header_size) = patch.read_header(value_cursor)?;\n                let key_text = if matches!(\n                    key_header.0,\n                    ElementType::TEXT5 | ElementType::TEXTJ | ElementType::TEXTRAW\n                ) {\n                    Cow::Owned(unescape_string(key_text))\n                } else {\n                    Cow::Borrowed(key_text)\n                };\n\n                key_values.push((\n                    key_text,\n                    value_header.0,\n                    value_cursor,\n                    value_header_size,\n                    value_header.1,\n                ));\n\n                patch_key_cursor = value_cursor + value_header_size + value_header.1;\n            }\n\n            for (key_text, value_type, value_cursor, value_header_size, value_size) in key_values {\n                // Create a path to this key\n                let mut key_path = path.clone();\n\n                key_path.elements.push(PathElement::Key(key_text, false));\n\n                match value_type {\n                    ElementType::NULL => {\n                        let mut op = DeleteOperation::new();\n\n                        let _ = result.operate_on_path(&key_path, &mut op);\n                    }\n                    ElementType::OBJECT => {\n                        let value_data = &patch.data\n                            [value_cursor..value_cursor + value_header_size + value_size];\n\n                        let target_path_result =\n                            result.navigate_path(&key_path, PathOperationMode::ReplaceExisting);\n\n                        if let Ok(target_stack) = target_path_result {\n                            let target_value_idx = target_stack\n                                .last()\n                                .ok_or_else(|| {\n                                    LimboError::InternalError(\n                                        \"target stack should not be empty\".to_string(),\n                                    )\n                                })?\n                                .field_value_index;\n                            let (target_header, _) = result.read_header(target_value_idx)?;\n\n                            if target_header.0 == ElementType::OBJECT {\n                                work_stack.push_back((key_path, value_cursor));\n                            } else {\n                                let patch_obj = Jsonb::new(value_data.len(), Some(value_data));\n                                let mut op = ReplaceOperation::new(patch_obj);\n                                result.operate_on_path(&key_path, &mut op)?;\n                                let _ = result.operate_on_path(&key_path, &mut op);\n\n                                work_stack.push_back((key_path, value_cursor));\n                            }\n                        } else {\n                            let empty_obj = Jsonb::new(\n                                1,\n                                Some(JsonbHeader::make_obj().into_bytes().as_bytes()),\n                            );\n                            let mut op = SetOperation::new(empty_obj);\n                            let _ = result.operate_on_path(&key_path, &mut op);\n\n                            work_stack.push_back((key_path, value_cursor));\n                        }\n                    }\n                    _ => {\n                        let value_data = &patch.data\n                            [value_cursor..value_cursor + value_header_size + value_size];\n                        let patch_value = Jsonb::new(value_data.len(), Some(value_data));\n\n                        let mut op = SetOperation::new(patch_value);\n\n                        let _ = result.operate_on_path(&key_path, &mut op);\n                    }\n                }\n            }\n        }\n\n        Ok(())\n    }\n\n    pub fn array_iterator(&self) -> Result<ArrayIteratorState> {\n        let (hdr, off) = self.read_header(0)?;\n        match hdr {\n            JsonbHeader(ElementType::ARRAY, len) => Ok(ArrayIteratorState {\n                cursor: off,\n                end: off + len,\n                index: 0,\n            }),\n            _ => bail_parse_error!(\"jsonb.array_iterator(): not an array\"),\n        }\n    }\n\n    pub fn array_iterator_next(\n        &self,\n        st: &ArrayIteratorState,\n    ) -> Option<((usize, Jsonb), ArrayIteratorState)> {\n        if st.cursor >= st.end {\n            return None;\n        }\n\n        let (JsonbHeader(_, payload_len), header_len) = self.read_header(st.cursor).ok()?;\n        let start = st.cursor;\n        let stop = start.checked_add(header_len + payload_len)?;\n\n        if stop > st.end || stop > self.data.len() {\n            return None;\n        }\n\n        let elem = Jsonb::new(stop - start, Some(&self.data[start..stop]));\n        let next = ArrayIteratorState {\n            cursor: stop,\n            end: st.end,\n            index: st.index + 1,\n        };\n\n        Some(((st.index, elem), next))\n    }\n\n    pub fn object_iterator(&self) -> Result<ObjectIteratorState> {\n        let (hdr, off) = self.read_header(0)?;\n        match hdr {\n            JsonbHeader(ElementType::OBJECT, len) => Ok(ObjectIteratorState {\n                cursor: off,\n                end: off + len,\n                index: 0,\n            }),\n            _ => bail_parse_error!(\"jsonb.object_iterator(): not an object\"),\n        }\n    }\n\n    pub fn object_iterator_next(\n        &self,\n        st: &ObjectIteratorState,\n    ) -> Option<((usize, Jsonb, Jsonb), ObjectIteratorState)> {\n        if st.cursor >= st.end {\n            return None;\n        }\n\n        // key\n        let (JsonbHeader(key_ty, key_len), key_hdr_len) = self.read_header(st.cursor).ok()?;\n        if !key_ty.is_valid_key() {\n            return None;\n        }\n        let key_start = st.cursor;\n        let key_stop = key_start.checked_add(key_hdr_len + key_len)?;\n        if key_stop > st.end || key_stop > self.data.len() {\n            return None;\n        }\n\n        // value\n        let (JsonbHeader(_, val_len), val_hdr_len) = self.read_header(key_stop).ok()?;\n        let val_start = key_stop;\n        let val_stop = val_start.checked_add(val_hdr_len + val_len)?;\n        if val_stop > st.end || val_stop > self.data.len() {\n            return None;\n        }\n\n        let key = Jsonb::new(key_stop - key_start, Some(&self.data[key_start..key_stop]));\n        let value = Jsonb::new(val_stop - val_start, Some(&self.data[val_start..val_stop]));\n        let next = ObjectIteratorState {\n            cursor: val_stop,\n            end: st.end,\n            index: st.index + 1,\n        };\n\n        Some(((st.index, key, value), next))\n    }\n\n    /// If the iterator points at a container value, return an iterator for that container.\n    /// For arrays, we inspect the next element; for objects, we inspect the next property's *value*.\n    pub fn container_property_iterator(&self, it: &IteratorState) -> Option<IteratorState> {\n        match it {\n            IteratorState::Array(st) => {\n                if st.cursor >= st.end {\n                    return None;\n                }\n                let (JsonbHeader(ty, len), hdr_len) = self.read_header(st.cursor).ok()?;\n                let payload_cursor = st.cursor.checked_add(hdr_len)?;\n                let payload_end = payload_cursor.checked_add(len)?;\n                if payload_end > st.end || payload_end > self.data.len() {\n                    return None;\n                }\n\n                match ty {\n                    ElementType::ARRAY => Some(IteratorState::Array(ArrayIteratorState {\n                        cursor: payload_cursor,\n                        end: payload_end,\n                        index: 0,\n                    })),\n                    ElementType::OBJECT => Some(IteratorState::Object(ObjectIteratorState {\n                        cursor: payload_cursor,\n                        end: payload_end,\n                        index: 0,\n                    })),\n                    _ => None,\n                }\n            }\n\n            IteratorState::Object(st) => {\n                if st.cursor >= st.end {\n                    return None;\n                }\n\n                // key -> value\n                let (JsonbHeader(key_ty, key_len), key_hdr_len) =\n                    self.read_header(st.cursor).ok()?;\n                if !key_ty.is_valid_key() {\n                    return None;\n                }\n                let key_stop = st.cursor.checked_add(key_hdr_len + key_len)?;\n                if key_stop > st.end || key_stop > self.data.len() {\n                    return None;\n                }\n\n                let (JsonbHeader(val_ty, val_len), val_hdr_len) =\n                    self.read_header(key_stop).ok()?;\n                let payload_cursor = key_stop.checked_add(val_hdr_len)?;\n                let payload_end = payload_cursor.checked_add(val_len)?;\n                if payload_end > st.end || payload_end > self.data.len() {\n                    return None;\n                }\n\n                match val_ty {\n                    ElementType::ARRAY => Some(IteratorState::Array(ArrayIteratorState {\n                        cursor: payload_cursor,\n                        end: payload_end,\n                        index: 0,\n                    })),\n                    ElementType::OBJECT => Some(IteratorState::Object(ObjectIteratorState {\n                        cursor: payload_cursor,\n                        end: payload_end,\n                        index: 0,\n                    })),\n                    _ => None,\n                }\n            }\n            IteratorState::Primitive(_) => None,\n        }\n    }\n}\n\nimpl std::str::FromStr for Jsonb {\n    type Err = PError;\n\n    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {\n        Self::from_str(s)\n    }\n}\n\n#[inline]\nfn compare(key: (&str, ElementType), path_key: (&str, bool)) -> bool {\n    let (key, element_type) = key;\n    let (path_key, is_raw) = path_key;\n    if !is_raw && element_type == ElementType::TEXT {\n        if key.len() == path_key.len() {\n            return key == path_key;\n        } else {\n            return false;\n        }\n    }\n    if !is_raw {\n        return unescape_string(key) == path_key;\n    }\n    match element_type {\n        ElementType::TEXTJ | ElementType::TEXT5 | ElementType::TEXTRAW | ElementType::TEXT => {\n            return unescape_string(key) == unescape_string(path_key);\n        }\n        _ => {}\n    };\n\n    false\n}\n\n#[inline]\npub fn unescape_string(input: &str) -> String {\n    let mut result = String::with_capacity(input.len());\n    let mut chars = input.chars().peekable();\n    let mut code_point = String::with_capacity(5);\n\n    while let Some(c) = chars.next() {\n        if c == '\\\\' {\n            match chars.next() {\n                Some('n') => result.push('\\n'),\n                Some('r') => result.push('\\r'),\n                Some('t') => result.push('\\t'),\n                Some('\\\\') => result.push('\\\\'),\n                Some('/') => result.push('/'),\n                Some('\"') => result.push('\"'),\n                Some('b') => result.push('\\u{0008}'),\n                Some('f') => result.push('\\u{000C}'),\n                Some('x') => {\n                    code_point.clear();\n                    for _ in 0..2 {\n                        if let Some(hex_char) = chars.next() {\n                            code_point.push(hex_char);\n                        } else {\n                            break;\n                        }\n                    }\n                    if let Ok(code) = u16::from_str_radix(&code_point, 16) {\n                        if let Some(ch) = char::from_u32(code as u32) {\n                            result.push(ch)\n                        }\n                    }\n                }\n                // Handle \\uXXXX format (JSON style)\n                Some('u') => {\n                    code_point.clear();\n                    for _ in 0..4 {\n                        if let Some(hex_char) = chars.next() {\n                            code_point.push(hex_char);\n                        } else {\n                            break;\n                        }\n                    }\n\n                    if let Ok(code) = u16::from_str_radix(&code_point, 16) {\n                        // Check if this is a high surrogate\n                        if matches!(code, 0xD800..=0xDBFF) {\n                            if chars.next() == Some('\\\\') && chars.next() == Some('u') {\n                                code_point.clear();\n                                for _ in 0..4 {\n                                    if let Some(hex_char) = chars.next() {\n                                        code_point.push(hex_char);\n                                    } else {\n                                        break;\n                                    }\n                                }\n\n                                if let Ok(low_code) = u16::from_str_radix(&code_point, 16) {\n                                    if (0xDC00..=0xDFFF).contains(&low_code) {\n                                        let high_ten_bits = (code - 0xD800) as u32;\n                                        let low_ten_bits = (low_code - 0xDC00) as u32;\n                                        let code_point = (high_ten_bits << 10) | low_ten_bits;\n                                        let unicode_value = code_point + 0x10000;\n\n                                        if let Some(unicode_char) = char::from_u32(unicode_value) {\n                                            result.push(unicode_char);\n                                        }\n                                    } else {\n                                        // If low surrogate is invalid, just push both as separate chars\n                                        if let Some(c1) = char::from_u32(code as u32) {\n                                            result.push(c1);\n                                        }\n                                        if let Some(c2) = char::from_u32(low_code as u32) {\n                                            result.push(c2);\n                                        }\n                                    }\n                                }\n                            } else {\n                                // No low surrogate, just push the high surrogate as is\n                                if let Some(unicode_char) = char::from_u32(code as u32) {\n                                    result.push(unicode_char);\n                                }\n                            }\n                        } else {\n                            // Not a surrogate pair, just a regular Unicode character\n                            if let Some(unicode_char) = char::from_u32(code as u32) {\n                                result.push(unicode_char);\n                            }\n                        }\n                    }\n                }\n\n                Some(c) => {\n                    // For any other escape sequence we don't recognize,\n                    // just output the backslash and the character\n                    result.push('\\\\');\n                    result.push(c);\n                }\n                None => {\n                    // Handle trailing backslash\n                    result.push('\\\\');\n                }\n            }\n        } else {\n            result.push(c);\n        }\n    }\n\n    result\n}\n\n#[inline]\npub fn skip_whitespace(input: &[u8], mut pos: usize) -> usize {\n    let len = input.len();\n    if pos >= len {\n        return pos;\n    }\n\n    // Fast path for non-whitespace, non-comment\n    if (WS_TABLE[input[pos] as usize] & 1) == 0 && input[pos] != b'/' {\n        return pos;\n    }\n\n    // Process whitespace and comments\n    while pos < len {\n        let ch = input[pos];\n        if (WS_TABLE[ch as usize] & 1) != 0 {\n            // Skip whitespace\n            pos += 1;\n        } else if ch == b'/' && pos + 1 < len {\n            // Handle JSON5 comments\n            match input[pos + 1] {\n                b'/' => {\n                    // Line comment - skip until newline\n                    pos += 2;\n                    while pos < len && input[pos] != b'\\n' {\n                        pos += 1;\n                    }\n                    if pos < len {\n                        pos += 1; // Skip the newline\n                    }\n                }\n                b'*' => {\n                    // Block comment - skip until \"*/\"\n                    pos += 2;\n                    while pos + 1 < len {\n                        if input[pos] == b'*' && input[pos + 1] == b'/' {\n                            pos += 2;\n                            break;\n                        }\n                        pos += 1;\n                    }\n                }\n                _ => {\n                    // Not a comment\n                    break;\n                }\n            }\n        } else {\n            // Not whitespace or comment\n            break;\n        }\n    }\n\n    pos\n}\n\n#[inline]\nfn is_hex_digit(ch: u8) -> bool {\n    (CHARACTER_TYPE[ch as usize] & 3) == 2 || (CHARACTER_TYPE[ch as usize] & 3) == 3\n}\n\n#[inline]\nfn is_json_ok(ch: u8) -> bool {\n    (CHARACTER_TYPE_OK[ch as usize] & 4) != 0\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_null_serialization() {\n        // Create JSONB with null value\n        let mut jsonb = Jsonb::new(10, None);\n        jsonb.data.push(ElementType::NULL as u8);\n\n        // Test serialization\n        let json_str = jsonb.to_string().unwrap();\n        assert_eq!(json_str, \"null\");\n\n        // Test round-trip\n        let reparsed = Jsonb::from_str(\"null\").unwrap();\n        assert_eq!(reparsed.data[0], ElementType::NULL as u8);\n    }\n\n    #[test]\n    fn test_boolean_serialization() {\n        // True\n        let mut jsonb_true = Jsonb::new(10, None);\n        jsonb_true.data.push(ElementType::TRUE as u8);\n        assert_eq!(jsonb_true.to_string().unwrap(), \"true\");\n\n        // False\n        let mut jsonb_false = Jsonb::new(10, None);\n        jsonb_false.data.push(ElementType::FALSE as u8);\n        assert_eq!(jsonb_false.to_string().unwrap(), \"false\");\n\n        // Round-trip\n        let true_parsed = Jsonb::from_str(\"true\").unwrap();\n        assert_eq!(true_parsed.data[0], ElementType::TRUE as u8);\n\n        let false_parsed = Jsonb::from_str(\"false\").unwrap();\n        assert_eq!(false_parsed.data[0], ElementType::FALSE as u8);\n    }\n\n    #[test]\n    fn test_integer_serialization() {\n        // Standard integer\n        let parsed = Jsonb::from_str(\"42\").unwrap();\n        assert_eq!(parsed.to_string().unwrap(), \"42\");\n\n        // Negative integer\n        let parsed = Jsonb::from_str(\"-123\").unwrap();\n        assert_eq!(parsed.to_string().unwrap(), \"-123\");\n\n        // Zero\n        let parsed = Jsonb::from_str(\"0\").unwrap();\n        assert_eq!(parsed.to_string().unwrap(), \"0\");\n\n        // Verify correct type\n        let header = JsonbHeader::from_slice(0, &parsed.data).unwrap().0;\n        assert!(matches!(header.0, ElementType::INT));\n    }\n\n    #[test]\n    fn test_json5_integer_serialization() {\n        // Hexadecimal notation\n        let parsed = Jsonb::from_str(\"0x1A\").unwrap();\n        assert_eq!(parsed.to_string().unwrap(), \"26\"); // Should convert to decimal\n\n        // Positive sign (JSON5)\n        let parsed = Jsonb::from_str(\"+42\").unwrap();\n        assert_eq!(parsed.to_string().unwrap(), \"42\");\n\n        // Negative hexadecimal\n        let parsed = Jsonb::from_str(\"-0xFF\").unwrap();\n        assert_eq!(parsed.to_string().unwrap(), \"-255\");\n\n        // Verify correct type\n        let header = JsonbHeader::from_slice(0, &parsed.data).unwrap().0;\n        assert!(matches!(header.0, ElementType::INT5));\n    }\n\n    #[test]\n    fn test_int5_with_multibyte_utf8_does_not_panic() {\n        // Regression test for fuzzer crash: serialize_int5 would panic on string\n        // slicing when the payload contained multi-byte UTF-8 characters.\n        // The fix uses byte-level checks for hex prefix detection.\n\n        // Construct malformed JSONB: INT5 header followed by UTF-8 with multi-byte chars\n        // Header: (6 << 4) | 4 = 0x64 (INT5 element, 6 bytes payload)\n        // Payload: \"T\" + U+F5D3 (3-byte UTF-8) + \"?]\"\n        let data: Vec<u8> = vec![\n            0x64, // INT5 header, 6 bytes payload\n            84,   // 'T'\n            239, 151, 147, // U+F5D3 (3-byte UTF-8 char)\n            63,  // '?'\n            93,  // ']'\n        ];\n\n        let jsonb = Jsonb::from_raw_data(&data);\n        // This should not panic - the fix uses byte-level checks that handle\n        // non-ASCII safely. The malformed data is rejected as invalid INT5,\n        // matching SQLite's \"malformed JSON\" behavior.\n        let err = jsonb.to_string().unwrap_err();\n        assert!(err.to_string().contains(\"malformed JSON\"));\n    }\n\n    #[test]\n    fn test_to_string_propagates_errors() {\n        // Valid JSONB should succeed\n        let valid = Jsonb::from_str(r#\"{\"key\": \"value\"}\"#).unwrap();\n        assert!(valid.to_string().is_ok());\n\n        // Malformed JSONB with invalid element type should error\n        let malformed = Jsonb::from_raw_data(&[0xFF]);\n        let err = malformed.to_string().unwrap_err();\n        assert!(err.to_string().contains(\"Invalid element type\"));\n\n        // Malformed INT5 with non-numeric content should error\n        let bad_int5 = Jsonb::from_raw_data(&[\n            0x34, // INT5 header, 3 bytes payload\n            b'a', b'b', b'c', // not a valid number\n        ]);\n        let err = bad_int5.to_string().unwrap_err();\n        assert!(err.to_string().contains(\"malformed JSON\"));\n\n        // Empty INT5 payload should error\n        let empty_int5 = Jsonb::from_raw_data(&[\n            0x04, // INT5 header, 0 bytes payload\n        ]);\n        let err = empty_int5.to_string().unwrap_err();\n        assert!(err.to_string().contains(\"malformed JSON\"));\n\n        // Sign-only INT5 payload should error\n        let sign_only_int5 = Jsonb::from_raw_data(&[\n            0x14, // INT5 header, 1 byte payload\n            b'+',\n        ]);\n        let err = sign_only_int5.to_string().unwrap_err();\n        assert!(err.to_string().contains(\"malformed JSON\"));\n\n        let minus_only_int5 = Jsonb::from_raw_data(&[\n            0x14, // INT5 header, 1 byte payload\n            b'-',\n        ]);\n        let err = minus_only_int5.to_string().unwrap_err();\n        assert!(err.to_string().contains(\"malformed JSON\"));\n    }\n\n    #[test]\n    fn test_reserved_element_types_rejected() {\n        for value in [13_u8, 14, 15] {\n            let err = ElementType::try_from(value).unwrap_err();\n            assert!(err.to_string().contains(\"Invalid element type\"));\n        }\n    }\n\n    #[test]\n    fn test_float_serialization() {\n        // Standard float\n        let parsed = Jsonb::from_str(\"3.14159\").unwrap();\n        assert_eq!(parsed.to_string().unwrap(), \"3.14159\");\n\n        // Negative float\n        let parsed = Jsonb::from_str(\"-2.718\").unwrap();\n        assert_eq!(parsed.to_string().unwrap(), \"-2.718\");\n\n        // Scientific notation\n        let parsed = Jsonb::from_str(\"6.022e23\").unwrap();\n        assert_eq!(parsed.to_string().unwrap(), \"6.022e23\");\n\n        // Verify correct type\n        let header = JsonbHeader::from_slice(0, &parsed.data).unwrap().0;\n        assert!(matches!(header.0, ElementType::FLOAT));\n    }\n\n    #[test]\n    fn test_json5_float_serialization() {\n        // Leading decimal point\n        let parsed = Jsonb::from_str(\".123\").unwrap();\n        assert_eq!(parsed.to_string().unwrap(), \"0.123\");\n\n        // Trailing decimal point\n        let parsed = Jsonb::from_str(\"42.\").unwrap();\n        assert_eq!(parsed.to_string().unwrap(), \"42.0\");\n\n        // Plus sign in exponent\n        let parsed = Jsonb::from_str(\"1.5e+10\").unwrap();\n        assert_eq!(parsed.to_string().unwrap(), \"1.5e+10\");\n\n        // Infinity\n        let parsed = Jsonb::from_str(\"Infinity\").unwrap();\n        assert_eq!(parsed.to_string().unwrap(), \"9.0e+999\");\n\n        // Negative Infinity\n        let parsed = Jsonb::from_str(\"-Infinity\").unwrap();\n        assert_eq!(parsed.to_string().unwrap(), \"-9.0e+999\");\n\n        // Verify correct type\n        let header = JsonbHeader::from_slice(0, &parsed.data).unwrap().0;\n        assert!(matches!(header.0, ElementType::FLOAT5));\n    }\n\n    #[test]\n    fn test_string_serialization() {\n        // Simple string\n        let parsed = Jsonb::from_str(r#\"\"hello world\"\"#).unwrap();\n        assert_eq!(parsed.to_string().unwrap(), r#\"\"hello world\"\"#);\n\n        // String with escaped characters\n        let parsed = Jsonb::from_str(r#\"\"hello\\nworld\"\"#).unwrap();\n        assert_eq!(parsed.to_string().unwrap(), r#\"\"hello\\nworld\"\"#);\n\n        // Unicode escape\n        let parsed = Jsonb::from_str(r#\"\"hello\\u0020world\"\"#).unwrap();\n        assert_eq!(parsed.to_string().unwrap(), r#\"\"hello\\u0020world\"\"#);\n\n        // Verify correct type\n        let header = JsonbHeader::from_slice(0, &parsed.data).unwrap().0;\n        assert!(matches!(header.0, ElementType::TEXTJ));\n    }\n\n    #[test]\n    fn test_json5_string_serialization() {\n        // Single quotes\n        let parsed = Jsonb::from_str(\"'hello world'\").unwrap();\n        assert_eq!(parsed.to_string().unwrap(), r#\"\"hello world\"\"#);\n\n        // Hex escape\n        let parsed = Jsonb::from_str(r#\"'\\x41\\x42\\x43'\"#).unwrap();\n        assert_eq!(parsed.to_string().unwrap(), r#\"\"\\u0041\\u0042\\u0043\"\"#);\n\n        // Multiline string with line continuation\n        let parsed = Jsonb::from_str(\n            r#\"\"hello \\\nworld\"\"#,\n        )\n        .unwrap();\n        assert_eq!(parsed.to_string().unwrap(), r#\"\"hello world\"\"#);\n\n        // Escaped single quote\n        let parsed = Jsonb::from_str(r#\"'Don\\'t worry'\"#).unwrap();\n        assert_eq!(parsed.to_string().unwrap(), r#\"\"Don't worry\"\"#);\n\n        // Verify correct type\n        let header = JsonbHeader::from_slice(0, &parsed.data).unwrap().0;\n        assert!(matches!(header.0, ElementType::TEXT5));\n    }\n\n    #[test]\n    fn test_array_serialization() {\n        // Empty array\n        let parsed = Jsonb::from_str(\"[]\").unwrap();\n        assert_eq!(parsed.to_string().unwrap(), \"[]\");\n\n        // Simple array\n        let parsed = Jsonb::from_str(\"[1,2,3]\").unwrap();\n        assert_eq!(parsed.to_string().unwrap(), \"[1,2,3]\");\n\n        // Nested array\n        let parsed = Jsonb::from_str(\"[[1,2],[3,4]]\").unwrap();\n        assert_eq!(parsed.to_string().unwrap(), \"[[1,2],[3,4]]\");\n\n        // Mixed types array\n        let parsed = Jsonb::from_str(r#\"[1,\"text\",true,null,{\"key\":\"value\"}]\"#).unwrap();\n        assert_eq!(\n            parsed.to_string().unwrap(),\n            r#\"[1,\"text\",true,null,{\"key\":\"value\"}]\"#\n        );\n\n        // Verify correct type\n        let header = JsonbHeader::from_slice(0, &parsed.data).unwrap().0;\n        assert!(matches!(header.0, ElementType::ARRAY));\n    }\n\n    #[test]\n    fn test_json5_array_serialization() {\n        // Trailing comma\n        let parsed = Jsonb::from_str(\"[1,2,3,]\").unwrap();\n        assert_eq!(parsed.to_string().unwrap(), \"[1,2,3]\");\n\n        // Comments in array\n        let parsed = Jsonb::from_str(\"[1,/* comment */2,3]\").unwrap();\n        assert_eq!(parsed.to_string().unwrap(), \"[1,2,3]\");\n\n        // Line comment in array\n        let parsed = Jsonb::from_str(\"[1,// line comment\\n2,3]\").unwrap();\n        assert_eq!(parsed.to_string().unwrap(), \"[1,2,3]\");\n    }\n\n    #[test]\n    fn test_object_serialization() {\n        // Empty object\n        let parsed = Jsonb::from_str(\"{}\").unwrap();\n        assert_eq!(parsed.to_string().unwrap(), \"{}\");\n\n        // Simple object\n        let parsed = Jsonb::from_str(r#\"{\"key\":\"value\"}\"#).unwrap();\n        assert_eq!(parsed.to_string().unwrap(), r#\"{\"key\":\"value\"}\"#);\n\n        // Multiple properties\n        let parsed = Jsonb::from_str(r#\"{\"a\":1,\"b\":2,\"c\":3}\"#).unwrap();\n        assert_eq!(parsed.to_string().unwrap(), r#\"{\"a\":1,\"b\":2,\"c\":3}\"#);\n\n        // Nested object\n        let parsed = Jsonb::from_str(r#\"{\"outer\":{\"inner\":\"value\"}}\"#).unwrap();\n        assert_eq!(\n            parsed.to_string().unwrap(),\n            r#\"{\"outer\":{\"inner\":\"value\"}}\"#\n        );\n\n        // Mixed values\n        let parsed =\n            Jsonb::from_str(r#\"{\"str\":\"text\",\"num\":42,\"bool\":true,\"null\":null,\"arr\":[1,2]}\"#)\n                .unwrap();\n        assert_eq!(\n            parsed.to_string().unwrap(),\n            r#\"{\"str\":\"text\",\"num\":42,\"bool\":true,\"null\":null,\"arr\":[1,2]}\"#\n        );\n\n        // Verify correct type\n        let header = JsonbHeader::from_slice(0, &parsed.data).unwrap().0;\n        assert!(matches!(header.0, ElementType::OBJECT));\n    }\n\n    #[test]\n    fn test_json5_object_serialization() {\n        // Unquoted keys\n        let parsed = Jsonb::from_str(\"{key:\\\"value\\\"}\").unwrap();\n        assert_eq!(parsed.to_string().unwrap(), r#\"{\"key\":\"value\"}\"#);\n\n        // Trailing comma\n        let parsed = Jsonb::from_str(r#\"{\"a\":1,\"b\":2,}\"#).unwrap();\n        assert_eq!(parsed.to_string().unwrap(), r#\"{\"a\":1,\"b\":2}\"#);\n\n        // Comments in object\n        let parsed = Jsonb::from_str(r#\"{\"a\":1,/*comment*/\"b\":2}\"#).unwrap();\n        assert_eq!(parsed.to_string().unwrap(), r#\"{\"a\":1,\"b\":2}\"#);\n\n        // Single quotes for keys and values\n        let parsed = Jsonb::from_str(\"{'a':'value'}\").unwrap();\n        assert_eq!(parsed.to_string().unwrap(), r#\"{\"a\":\"value\"}\"#);\n    }\n\n    #[test]\n    fn test_complex_json() {\n        let complex_json = r#\"{\n            \"string\": \"Hello, world!\",\n            \"number\": 42,\n            \"float\": 3.14159,\n            \"boolean\": true,\n            \"null\": null,\n            \"array\": [1, 2, 3, \"text\", {\"nested\": \"object\"}],\n            \"object\": {\n                \"key1\": \"value1\",\n                \"key2\": [4, 5, 6],\n                \"key3\": {\n                    \"nested\": true\n                }\n            }\n        }\"#;\n\n        let parsed = Jsonb::from_str(complex_json).unwrap();\n        // Round-trip test\n        let reparsed = Jsonb::from_str(&parsed.to_string().unwrap()).unwrap();\n        assert_eq!(parsed.to_string().unwrap(), reparsed.to_string().unwrap());\n    }\n\n    #[test]\n    fn test_error_handling() {\n        // Invalid JSON syntax\n        assert!(Jsonb::from_str(\"{\").is_err());\n        assert!(Jsonb::from_str(\"[\").is_err());\n        assert!(Jsonb::from_str(\"}\").is_err());\n        assert!(Jsonb::from_str(\"]\").is_err());\n\n        assert!(Jsonb::from_str(r#\"{\"a\":\"55,\"b\":72}\"#).is_err());\n\n        assert!(Jsonb::from_str(r#\"{\"a\":\"55\",,\"b\":72}\"#).is_err());\n\n        // Unclosed string\n        assert!(Jsonb::from_str(r#\"{\"key\":\"value\"#).is_err());\n\n        // Invalid number format\n        assert!(Jsonb::from_str(\"01234\").is_err()); // Leading zero not allowed in JSON\n\n        // Invalid escape sequence\n        assert!(Jsonb::from_str(r#\"\"\\z\"\"#).is_err());\n\n        // Missing colon in object\n        assert!(Jsonb::from_str(r#\"{\"key\" \"value\"}\"#).is_err());\n\n        // Trailing characters\n        assert!(Jsonb::from_str(r#\"{\"key\":\"value\"} extra\"#).is_err());\n    }\n\n    #[test]\n    fn test_depth_limit() {\n        // Create a JSON string that exceeds MAX_JSON_DEPTH\n        let mut deep_json = String::from(\"[\");\n        for _ in 0..MAX_JSON_DEPTH + 1 {\n            deep_json.push('[');\n        }\n        for _ in 0..MAX_JSON_DEPTH + 1 {\n            deep_json.push(']');\n        }\n        deep_json.push(']');\n\n        // Should fail due to exceeding depth limit\n        assert!(Jsonb::from_str(&deep_json).is_err());\n    }\n\n    #[test]\n    fn test_header_encoding() {\n        // Small payload (fits in 4 bits)\n        let header = JsonbHeader::new(ElementType::TEXT, 5);\n        let bytes = header.into_bytes().as_bytes().to_vec();\n        assert_eq!(bytes[0], (5 << 4) | (ElementType::TEXT as u8));\n\n        // Medium payload (8-bit)\n        let header = JsonbHeader::new(ElementType::TEXT, 200);\n        let bytes = header.into_bytes().as_bytes().to_vec();\n        assert_eq!(\n            bytes[0],\n            (SIZE_MARKER_8BIT << 4) | (ElementType::TEXT as u8)\n        );\n        assert_eq!(bytes[1], 200);\n\n        // Large payload (16-bit)\n        let header = JsonbHeader::new(ElementType::TEXT, 40000);\n        let bytes = header.into_bytes().as_bytes().to_vec();\n        assert_eq!(\n            bytes[0],\n            (SIZE_MARKER_16BIT << 4) | (ElementType::TEXT as u8)\n        );\n        assert_eq!(bytes[1], (40000 >> 8) as u8);\n        assert_eq!(bytes[2], (40000 & 0xFF) as u8);\n\n        // Extra large payload (32-bit)\n        let header = JsonbHeader::new(ElementType::TEXT, 70000);\n        let bytes = header.into_bytes().as_bytes().to_vec();\n        assert_eq!(\n            bytes[0],\n            (SIZE_MARKER_32BIT << 4) | (ElementType::TEXT as u8)\n        );\n        assert_eq!(bytes[1], (70000 >> 24) as u8);\n        assert_eq!(bytes[2], ((70000 >> 16) & 0xFF) as u8);\n        assert_eq!(bytes[3], ((70000 >> 8) & 0xFF) as u8);\n        assert_eq!(bytes[4], (70000 & 0xFF) as u8);\n    }\n\n    #[test]\n    fn test_header_decoding() {\n        // Create sample data with various headers\n        let data = vec![\n            (5 << 4) | (ElementType::TEXT as u8),\n            (SIZE_MARKER_8BIT << 4) | (ElementType::ARRAY as u8),\n            150,\n            (SIZE_MARKER_16BIT << 4) | (ElementType::OBJECT as u8),\n            0x98,\n            0x68,\n        ];\n\n        // Parse and verify each header\n        let (header1, offset1) = JsonbHeader::from_slice(0, &data).unwrap();\n        assert_eq!(offset1, 1);\n        assert_eq!(header1.0, ElementType::TEXT);\n        assert_eq!(header1.1, 5);\n\n        let (header2, offset2) = JsonbHeader::from_slice(1, &data).unwrap();\n        assert_eq!(offset2, 2);\n        assert_eq!(header2.0, ElementType::ARRAY);\n        assert_eq!(header2.1, 150);\n\n        let (header3, offset3) = JsonbHeader::from_slice(3, &data).unwrap();\n        assert_eq!(offset3, 3);\n        assert_eq!(header3.0, ElementType::OBJECT);\n        assert_eq!(header3.1, 0x9868); // 39000\n    }\n\n    #[test]\n    fn test_unicode_escapes() {\n        // Basic unicode escape\n        let parsed = Jsonb::from_str(r#\"\"\\u00A9\"\"#).unwrap(); // Copyright symbol\n        assert_eq!(parsed.to_string().unwrap(), r#\"\"\\u00A9\"\"#);\n\n        // Non-BMP character (surrogate pair)\n        let parsed = Jsonb::from_str(r#\"\"\\uD83D\\uDE00\"\"#).unwrap(); // Smiley emoji\n        assert_eq!(parsed.to_string().unwrap(), r#\"\"\\uD83D\\uDE00\"\"#);\n    }\n\n    #[test]\n    fn test_json5_comments() {\n        // Line comments\n        let parsed = Jsonb::from_str(\n            r#\"{\n            // This is a line comment\n            \"key\": \"value\"\n        }\"#,\n        )\n        .unwrap();\n        assert_eq!(parsed.to_string().unwrap(), r#\"{\"key\":\"value\"}\"#);\n\n        // Block comments\n        let parsed = Jsonb::from_str(\n            r#\"{\n            /* This is a\n               block comment */\n            \"key\": \"value\"\n        }\"#,\n        )\n        .unwrap();\n        assert_eq!(parsed.to_string().unwrap(), r#\"{\"key\":\"value\"}\"#);\n\n        // Comments inside array\n        let parsed = Jsonb::from_str(\n            r#\"[1, // Comment\n                                       2, /* Another comment */ 3]\"#,\n        )\n        .unwrap();\n        assert_eq!(parsed.to_string().unwrap(), \"[1,2,3]\");\n    }\n\n    #[test]\n    fn test_whitespace_handling() {\n        // Various whitespace patterns\n        let json_with_whitespace = r#\"\n        {\n            \"key1\"    :    \"value1\"   ,\n             \"key2\": [   1,    2,    3   ]  ,\n            \"key3\":   {\n                \"nested\"   :   true\n            }\n        }\n        \"#;\n\n        let parsed = Jsonb::from_str(json_with_whitespace).unwrap();\n        assert_eq!(\n            parsed.to_string().unwrap(),\n            r#\"{\"key1\":\"value1\",\"key2\":[1,2,3],\"key3\":{\"nested\":true}}\"#\n        );\n    }\n\n    #[test]\n    fn test_binary_roundtrip() {\n        // Test that binary data can be round-tripped through the JSONB format\n        let original = r#\"{\"test\":\"value\",\"array\":[1,2,3]}\"#;\n        let parsed = Jsonb::from_str(original).unwrap();\n        let binary_data = parsed.data;\n\n        // Create a new Jsonb from the binary data\n        let from_binary = Jsonb::new(0, Some(&binary_data));\n        assert_eq!(from_binary.to_string().unwrap(), original);\n    }\n\n    #[test]\n    fn test_large_json() {\n        // Generate a large JSON with many elements\n        let mut large_array = String::from(\"[\");\n        for i in 0..1000 {\n            large_array.push_str(&format!(\"{i}\"));\n            if i < 999 {\n                large_array.push(',');\n            }\n        }\n        large_array.push(']');\n\n        let parsed = Jsonb::from_str(&large_array).unwrap();\n        assert!(parsed.to_string().unwrap().starts_with(\"[0,1,2,\"));\n        assert!(parsed.to_string().unwrap().ends_with(\"998,999]\"));\n    }\n\n    #[test]\n    fn test_jsonb_is_valid() {\n        // Valid JSONB\n        let jsonb = Jsonb::from_str(r#\"{\"test\":\"value\"}\"#).unwrap();\n        assert!(jsonb.element_type().is_ok());\n\n        // Invalid JSONB (manually corrupted)\n        let mut invalid = jsonb.data;\n        if !invalid.is_empty() {\n            invalid[0] = 0xFF; // Invalid element type\n            let jsonb = Jsonb::new(0, Some(&invalid));\n            assert!(jsonb.element_type().is_err());\n        }\n    }\n\n    #[test]\n    fn test_special_characters_in_strings() {\n        // Test handling of various special characters\n        let json = r#\"{\n            \"escaped_quotes\": \"He said \\\"Hello\\\"\",\n            \"backslashes\": \"C:\\\\Windows\\\\System32\",\n            \"control_chars\": \"\\b\\f\\n\\r\\t\",\n            \"unicode\": \"\\u00A9 2023\"\n        }\"#;\n\n        let parsed = Jsonb::from_str(json).unwrap();\n        let result = parsed.to_string().unwrap();\n\n        assert!(result.contains(r#\"\"escaped_quotes\":\"He said \\\"Hello\\\"\"\"#));\n        assert!(result.contains(r#\"\"backslashes\":\"C:\\\\Windows\\\\System32\"\"#));\n        assert!(result.contains(r#\"\"control_chars\":\"\\b\\f\\n\\r\\t\"\"#));\n        assert!(result.contains(r#\"\"unicode\":\"\\u00A9 2023\"\"#));\n    }\n\n    #[test]\n    fn test_malformed_jsonb_payload_size_overflow() {\n        // Test that malformed JSONB data with extremely large payload sizes\n        // does not cause a panic due to integer overflow.\n        // This creates JSONB data with a header indicating a payload size\n        // that would overflow when added to the cursor position.\n        //\n        // Header format: lower 4 bits = element type, upper 4 bits = size marker\n        // When upper 4 bits = 0xF (15), the payload size follows as 8 bytes\n\n        let expected_error = \"Invalid JSONB: payload size overflow\";\n\n        // Test TEXT type (0x7) with overflow payload size\n        // Header: 0xF7 = TEXT type (0x7) with 8-byte size marker (0xF)\n        let malformed_text: Vec<u8> = vec![\n            0xF7, // TEXT with 8-byte size (header_size=15)\n            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // u64::MAX as payload size\n            b'a', b'b', b'c', // some actual data (doesn't matter, cursor+len will overflow)\n        ];\n        let jsonb = Jsonb::new(0, Some(&malformed_text));\n        let result = jsonb.to_string();\n        assert!(result.is_err());\n        assert!(\n            result.unwrap_err().to_string().contains(expected_error),\n            \"TEXT overflow should report payload size overflow\"\n        );\n\n        // Test ARRAY type (0xB = 11) with overflow payload size\n        // Header: 0xFB = ARRAY type (0xB) with 8-byte size marker (0xF)\n        let malformed_array: Vec<u8> = vec![\n            0xFB, // ARRAY with 8-byte size (header_size=15)\n            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // u64::MAX as payload size\n            0x01, // NULL element inside (doesn't matter, cursor+len will overflow)\n        ];\n        let jsonb = Jsonb::new(0, Some(&malformed_array));\n        let result = jsonb.to_string();\n        assert!(result.is_err());\n        assert!(\n            result.unwrap_err().to_string().contains(expected_error),\n            \"ARRAY overflow should report payload size overflow\"\n        );\n\n        // Test OBJECT type (0xC = 12) with overflow payload size\n        // Header: 0xFC = OBJECT type (0xC) with 8-byte size marker (0xF)\n        let malformed_object: Vec<u8> = vec![\n            0xFC, // OBJECT with 8-byte size (header_size=15)\n            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // u64::MAX as payload size\n            0x17, b'k', // TEXT key \"k\" (doesn't matter, cursor+len will overflow)\n        ];\n        let jsonb = Jsonb::new(0, Some(&malformed_object));\n        let result = jsonb.to_string();\n        assert!(result.is_err());\n        assert!(\n            result.unwrap_err().to_string().contains(expected_error),\n            \"OBJECT overflow should report payload size overflow\"\n        );\n    }\n}\n\n#[cfg(test)]\nmod path_operations_tests {\n    use super::*;\n    use crate::json::path::{JsonPath, PathElement};\n    use std::borrow::Cow;\n\n    // Helper function to create a simple JsonPath\n    fn create_path(elements: Vec<PathElement>) -> JsonPath {\n        JsonPath { elements }\n    }\n\n    #[test]\n    fn test_navigate_root_path() {\n        let json_str = r#\"{\"name\": \"John\", \"age\": 30}\"#;\n        let mut jsonb = Jsonb::from_str(json_str).unwrap();\n\n        // Create a path to the root\n        let path = create_path(vec![PathElement::Root()]);\n\n        // Navigate to the path\n        let result = jsonb.navigate_path(&path, PathOperationMode::ReplaceExisting);\n\n        // Verify navigation succeeds\n        assert!(result.is_ok());\n        let stack = result.unwrap();\n        assert_eq!(stack.len(), 1);\n        assert_eq!(stack[0].field_value_index, 0);\n        assert_eq!(stack[0].field_key_index, JsonLocationKind::DocumentRoot);\n    }\n\n    #[test]\n    fn test_navigate_object_property() {\n        let json_str = r#\"{\"name\": \"John\", \"age\": 30}\"#;\n        let mut jsonb = Jsonb::from_str(json_str).unwrap();\n\n        // Create a path to the \"name\" property\n        let path = create_path(vec![\n            PathElement::Root(),\n            PathElement::Key(Cow::Borrowed(\"name\"), false),\n        ]);\n\n        // Navigate to the path\n        let result = jsonb.navigate_path(&path, PathOperationMode::ReplaceExisting);\n\n        // Verify navigation succeeds and points to the correct value\n        assert!(result.is_ok());\n        let stack = result.unwrap();\n        assert_eq!(stack.len(), 2);\n\n        // Verify we can get the value at this position\n        let name_index = stack[1].field_value_index;\n        let (header, header_size) = jsonb.read_header(name_index).unwrap();\n        assert_eq!(header.0, ElementType::TEXT);\n\n        // Extract the actual string value to verify\n        let text_bytes = &jsonb.data[name_index + header_size..name_index + header_size + header.1];\n        let text = std::str::from_utf8(text_bytes).unwrap();\n        assert_eq!(text, \"John\");\n    }\n\n    #[test]\n    fn test_navigate_nested_object_property() {\n        let json_str = r#\"{\"person\": {\"name\": \"John\", \"age\": 30}}\"#;\n        let mut jsonb = Jsonb::from_str(json_str).unwrap();\n\n        // Create a path to the nested \"name\" property\n        let path = create_path(vec![\n            PathElement::Root(),\n            PathElement::Key(Cow::Borrowed(\"person\"), false),\n            PathElement::Key(Cow::Borrowed(\"name\"), false),\n        ]);\n\n        // Navigate to the path\n        let result = jsonb.navigate_path(&path, PathOperationMode::ReplaceExisting);\n\n        // Verify navigation succeeds\n        assert!(result.is_ok());\n        let stack = result.unwrap();\n        assert_eq!(stack.len(), 3);\n    }\n\n    #[test]\n    fn test_navigate_array_element() {\n        let json_str = r#\"{\"items\": [10, 20, 30]}\"#;\n        let mut jsonb = Jsonb::from_str(json_str).unwrap();\n\n        // Create a path to the second array element (index 1)\n        let path = create_path(vec![\n            PathElement::Root(),\n            PathElement::Key(Cow::Borrowed(\"items\"), false),\n            PathElement::ArrayLocator(Some(1)),\n        ]);\n\n        // Navigate to the path\n        let result = jsonb.navigate_path(&path, PathOperationMode::ReplaceExisting);\n\n        // Verify navigation succeeds\n        assert!(result.is_ok());\n        let stack = result.unwrap();\n        assert_eq!(stack.len(), 2);\n\n        // Verify we can get the value at the array position\n        assert!(stack[1].has_specific_index());\n        let array_element_index = stack[1].get_array_index().unwrap();\n        let (header, header_size) = jsonb.read_header(array_element_index).unwrap();\n        assert_eq!(header.0, ElementType::INT);\n\n        // Extract the actual integer value to verify\n        let int_bytes = &jsonb.data\n            [array_element_index + header_size..array_element_index + header_size + header.1];\n        let int_str = std::str::from_utf8(int_bytes).unwrap();\n        assert_eq!(int_str, \"20\");\n    }\n\n    #[test]\n    fn test_navigate_negative_array_index() {\n        let json_str = r#\"{\"items\": [10, 20, 30]}\"#;\n        let mut jsonb = Jsonb::from_str(json_str).unwrap();\n\n        // Create a path to the last array element (index -1)\n        let path = create_path(vec![\n            PathElement::Root(),\n            PathElement::Key(Cow::Borrowed(\"items\"), false),\n            PathElement::ArrayLocator(Some(-1)),\n        ]);\n\n        // Navigate to the path\n        let result = jsonb.navigate_path(&path, PathOperationMode::ReplaceExisting);\n\n        // Verify navigation succeeds\n        assert!(result.is_ok());\n        let stack = result.unwrap();\n        assert_eq!(stack.len(), 2);\n\n        // Verify we can get the value at the array position\n        assert!(stack[1].has_specific_index());\n    }\n\n    #[test]\n    fn test_set_operation() {\n        let json_str = r#\"{\"name\": \"John\", \"age\": 30}\"#;\n        let mut jsonb = Jsonb::from_str(json_str).unwrap();\n\n        // Create a new value to set\n        let new_value = Jsonb::from_str(\"\\\"Jane\\\"\").unwrap();\n        let mut operation = SetOperation::new(new_value);\n\n        // Create a path to the \"name\" property\n        let path = create_path(vec![\n            PathElement::Root(),\n            PathElement::Key(Cow::Borrowed(\"name\"), false),\n        ]);\n\n        // Execute the operation\n        let result = jsonb.operate_on_path(&path, &mut operation);\n        assert!(result.is_ok());\n\n        // Verify the value was updated\n        let updated_json = jsonb.to_string().unwrap();\n        assert_eq!(updated_json, r#\"{\"name\":\"Jane\",\"age\":30}\"#);\n    }\n\n    #[test]\n    fn test_insert_operation() {\n        let json_str = r#\"{\"name\": \"John\"}\"#;\n        let mut jsonb = Jsonb::from_str(json_str).unwrap();\n\n        // Create a new value to insert\n        let new_value = Jsonb::from_str(\"30\").unwrap();\n        let mut operation = InsertOperation::new(new_value);\n\n        // Create a path to a new \"age\" property\n        let path = create_path(vec![\n            PathElement::Root(),\n            PathElement::Key(Cow::Borrowed(\"age\"), false),\n        ]);\n\n        // Execute the operation\n        let result = jsonb.operate_on_path(&path, &mut operation);\n        assert!(result.is_ok());\n\n        // Verify the value was inserted\n        let updated_json = jsonb.to_string().unwrap();\n        assert_eq!(updated_json, r#\"{\"name\":\"John\",\"age\":30}\"#);\n    }\n\n    #[test]\n    fn test_delete_operation() {\n        let json_str = r#\"{\"name\": \"John\", \"age\": 30}\"#;\n        let mut jsonb = Jsonb::from_str(json_str).unwrap();\n\n        // Create a delete operation\n        let mut operation = DeleteOperation::new();\n\n        // Create a path to the \"age\" property\n        let path = create_path(vec![\n            PathElement::Root(),\n            PathElement::Key(Cow::Borrowed(\"age\"), false),\n        ]);\n\n        // Execute the operation\n        let result = jsonb.operate_on_path(&path, &mut operation);\n        assert!(result.is_ok());\n\n        // Verify the property was deleted\n        let updated_json = jsonb.to_string().unwrap();\n        assert_eq!(updated_json, r#\"{\"name\":\"John\"}\"#);\n    }\n\n    #[test]\n    fn test_replace_operation() {\n        let json_str = r#\"{\"items\": [10, 20, 30]}\"#;\n        let mut jsonb = Jsonb::from_str(json_str).unwrap();\n\n        // Create a new value to replace with\n        let new_value = Jsonb::from_str(\"50\").unwrap();\n        let mut operation = ReplaceOperation::new(new_value);\n\n        // Create a path to the second array element (index 1)\n        let path = create_path(vec![\n            PathElement::Root(),\n            PathElement::Key(Cow::Borrowed(\"items\"), false),\n            PathElement::ArrayLocator(Some(1)),\n        ]);\n\n        // Execute the operation\n        let result = jsonb.operate_on_path(&path, &mut operation);\n        assert!(result.is_ok());\n\n        // Verify the value was replaced\n        let updated_json = jsonb.to_string().unwrap();\n        assert_eq!(updated_json, r#\"{\"items\":[10,50,30]}\"#);\n    }\n\n    #[test]\n    fn test_search_operation() {\n        let json_str = r#\"{\"person\": {\"name\": \"John\", \"age\": 30}}\"#;\n        let mut jsonb = Jsonb::from_str(json_str).unwrap();\n\n        // Create a search operation\n        let mut operation = SearchOperation::new(100);\n\n        // Create a path to the \"person\" property\n        let path = create_path(vec![\n            PathElement::Root(),\n            PathElement::Key(Cow::Borrowed(\"person\"), false),\n        ]);\n\n        // Execute the operation\n        let result = jsonb.operate_on_path(&path, &mut operation);\n        assert!(result.is_ok());\n\n        // Get the search result\n        let search_result = operation.result();\n        let result_str = search_result.to_string().unwrap();\n\n        // Verify the search found the correct value\n        assert_eq!(result_str, r#\"{\"name\":\"John\",\"age\":30}\"#);\n    }\n\n    #[test]\n    fn test_error_for_nonexistent_path() {\n        let json_str = r#\"{\"name\": \"John\", \"age\": 30}\"#;\n        let mut jsonb = Jsonb::from_str(json_str).unwrap();\n\n        // Create a new value to set\n        let new_value = Jsonb::from_str(\"\\\"Doe\\\"\").unwrap();\n        let mut operation = ReplaceOperation::new(new_value);\n\n        // Create a path to a non-existent property with ReplaceExisting mode\n        let path = create_path(vec![\n            PathElement::Root(),\n            PathElement::Key(Cow::Borrowed(\"surname\"), false),\n        ]);\n\n        // Execute the operation - should fail because path doesn't exist\n        let result = jsonb.operate_on_path(&path, &mut operation);\n        assert!(result.is_err());\n    }\n\n    #[test]\n    fn test_deep_nested_path() {\n        let json_str = r#\"{\"level1\": {\"level2\": {\"level3\": {\"value\": 42}}}}\"#;\n        let mut jsonb = Jsonb::from_str(json_str).unwrap();\n\n        // Create a new value to set\n        let new_value = Jsonb::from_str(\"100\").unwrap();\n        let mut operation = SetOperation::new(new_value);\n\n        // Create a deeply nested path\n        let path = create_path(vec![\n            PathElement::Root(),\n            PathElement::Key(Cow::Borrowed(\"level1\"), false),\n            PathElement::Key(Cow::Borrowed(\"level2\"), false),\n            PathElement::Key(Cow::Borrowed(\"level3\"), false),\n            PathElement::Key(Cow::Borrowed(\"value\"), false),\n        ]);\n\n        // Execute the operation\n        let result = jsonb.operate_on_path(&path, &mut operation);\n        assert!(result.is_ok());\n\n        // Verify the deep value was updated\n        let updated_json = jsonb.to_string().unwrap();\n        assert_eq!(\n            updated_json,\n            r#\"{\"level1\":{\"level2\":{\"level3\":{\"value\":100}}}}\"#\n        );\n    }\n\n    #[test]\n    fn test_path_modes() {\n        // Test the different path operation modes\n\n        // 1. ReplaceExisting mode - should fail when path doesn't exist\n        let json_str = r#\"{\"name\": \"John\"}\"#;\n        let mut jsonb = Jsonb::from_str(json_str).unwrap();\n\n        let mut operation = SetOperation::new(Jsonb::from_str(\"30\").unwrap());\n        operation.mode = PathOperationMode::ReplaceExisting;\n\n        let path = create_path(vec![\n            PathElement::Root(),\n            PathElement::Key(Cow::Borrowed(\"age\"), false),\n        ]);\n\n        let result = jsonb.operate_on_path(&path, &mut operation);\n        assert!(result.is_err());\n\n        // 2. InsertNew mode - should succeed for new paths\n        let json_str = r#\"{\"name\": \"John\"}\"#;\n        let mut jsonb = Jsonb::from_str(json_str).unwrap();\n\n        let mut operation = InsertOperation::new(Jsonb::from_str(\"30\").unwrap());\n        operation.mode = PathOperationMode::InsertNew;\n\n        let path = create_path(vec![\n            PathElement::Root(),\n            PathElement::Key(Cow::Borrowed(\"age\"), false),\n        ]);\n\n        let result = jsonb.operate_on_path(&path, &mut operation);\n        assert!(result.is_ok());\n\n        let updated_json = jsonb.to_string().unwrap();\n        assert_eq!(updated_json, r#\"{\"name\":\"John\",\"age\":30}\"#);\n\n        // 3. InsertNew mode - should fail when path already exists\n        let mut operation = InsertOperation::new(Jsonb::from_str(\"31\").unwrap());\n        operation.mode = PathOperationMode::InsertNew;\n\n        let result = jsonb.operate_on_path(&path, &mut operation);\n        assert!(result.is_err());\n\n        // 4. Upsert mode - should work for both existing and new paths\n        let json_str = r#\"{\"name\": \"John\", \"age\": 30}\"#;\n        let mut jsonb = Jsonb::from_str(json_str).unwrap();\n\n        // Update existing value with Upsert\n        let mut operation = SetOperation::new(Jsonb::from_str(\"31\").unwrap());\n        operation.mode = PathOperationMode::Upsert;\n\n        let path = create_path(vec![\n            PathElement::Root(),\n            PathElement::Key(Cow::Borrowed(\"age\"), false),\n        ]);\n\n        let result = jsonb.operate_on_path(&path, &mut operation);\n        assert!(result.is_ok());\n\n        // Insert new value with Upsert\n        let mut operation = SetOperation::new(Jsonb::from_str(\"\\\"Doe\\\"\").unwrap());\n        operation.mode = PathOperationMode::Upsert;\n\n        let path = create_path(vec![\n            PathElement::Root(),\n            PathElement::Key(Cow::Borrowed(\"surname\"), false),\n        ]);\n\n        let result = jsonb.operate_on_path(&path, &mut operation);\n        assert!(result.is_ok());\n\n        let updated_json = jsonb.to_string().unwrap();\n        assert_eq!(updated_json, r#\"{\"name\":\"John\",\"age\":31,\"surname\":\"Doe\"}\"#);\n    }\n\n    #[test]\n    fn test_array_len_malformed_overflow() {\n        // Test that malformed JSONB with huge payload size doesn't panic.\n        // This blob has an 8-byte payload size header (header_size = 15) with\n        // a value that would cause overflow when added to the position.\n        // Header byte: 0xFB = element type ARRAY (11) + size marker 15 (8-byte size)\n        // Followed by 8 bytes of near-max u64 value.\n        let malformed: Vec<u8> = vec![\n            0xFB, // ARRAY type (11) with 8-byte payload size marker (15 << 4)\n            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, // huge payload size\n        ];\n        let jsonb = Jsonb { data: malformed };\n\n        // Should return an error instead of panicking with overflow\n        let result = jsonb.array_len();\n        assert!(result.is_err());\n    }\n}\n"
  },
  {
    "path": "core/json/mod.rs",
    "content": "mod cache;\nmod error;\npub(crate) mod jsonb;\nmod ops;\npub(crate) mod path;\npub(crate) mod vtab;\n\nuse crate::json::error::Error as JsonError;\npub use crate::json::ops::{\n    json_insert, json_patch, json_remove, json_replace, jsonb_insert, jsonb_patch, jsonb_remove,\n    jsonb_replace,\n};\nuse crate::json::path::{json_path, JsonPath, PathElement};\nuse crate::numeric::Numeric;\nuse crate::types::{AsValueRef, Text, TextSubtype, Value, ValueType};\nuse crate::{bail_constraint_error, bail_parse_error, LimboError, ValueRef};\npub use cache::JsonCacheCell;\nuse jsonb::{\n    unescape_string, ElementType, Jsonb, JsonbHeader, PathOperationMode, SearchOperation,\n    SetOperation,\n};\nuse std::borrow::Cow;\nuse std::str::FromStr;\n\n// Object/array headers with inline payload size <= 7 are ambiguous with 8-byte scalar blobs:\n// 1-byte header + 7-byte payload == 8 bytes (e.g. INT/FLOAT scalar bytes like `0x7C 12 34 56 78 9A BC DE`\n// It is not JSONB, but it is being recognized as JSONB.\nconst JSONB_AMBIGUOUS_PAYLOAD_MAX: usize = 7;\n\n#[derive(Debug, Clone, Copy)]\npub enum Conv {\n    Strict,\n    NotStrict,\n    ToString,\n}\n\n#[cfg(feature = \"json\")]\npub enum OutputVariant {\n    ElementType,\n    Binary,\n    String,\n}\n\npub fn get_json(json_value: &Value, indent: Option<&str>) -> crate::Result<Value> {\n    match json_value {\n        Value::Text(ref t) if t.subtype == TextSubtype::Json && indent.is_none() => {\n            // optimization: once we know the subtype is a valid JSON, we do not have\n            // to go through parsing JSON and serializing it back to string\n            Ok(json_value.to_owned())\n        }\n        Value::Null => Ok(Value::Null),\n        _ => {\n            let json_val = convert_dbtype_to_jsonb(json_value, Conv::Strict)?;\n            let mut json = match indent {\n                Some(indent) => json_val.to_string_pretty(Some(indent))?,\n                None => json_val.to_string()?,\n            };\n\n            // Simplify infinity format to match SQLite (#4196)\n            json = json.replace(\"9.0e+999\", \"9e999\");\n\n            Ok(Value::Text(Text::json(json)))\n        }\n    }\n}\n\n/// Converts a value to `Jsonb`, using the provided cache, and returns a `Value::Blob` containing\n/// the jsonb.\npub fn jsonb(json_value: &Value, cache: &JsonCacheCell) -> crate::Result<Value> {\n    let json_conv_fn = curry_convert_dbtype_to_jsonb(Conv::Strict);\n\n    let jsonbin = cache.get_or_insert_with(json_value, json_conv_fn);\n    match jsonbin {\n        Ok(jsonbin) => Ok(Value::Blob(jsonbin.data())),\n        Err(_) => {\n            bail_parse_error!(\"malformed JSON\")\n        }\n    }\n}\n\npub fn convert_dbtype_to_raw_jsonb(data: &Value) -> crate::Result<Vec<u8>> {\n    let json = convert_dbtype_to_jsonb(data, Conv::NotStrict)?;\n    Ok(json.data())\n}\n\npub fn json_from_raw_bytes_agg(data: &[u8], raw: bool) -> crate::Result<Value> {\n    let mut json = Jsonb::from_raw_data(data);\n    let el_type = json.element_type()?;\n    json.finalize_unsafe(el_type)?;\n    if raw {\n        json_string_to_db_type(json, el_type, OutputVariant::Binary)\n    } else {\n        json_string_to_db_type(json, el_type, OutputVariant::ElementType)\n    }\n}\n\npub fn convert_dbtype_to_jsonb(val: impl AsValueRef, strict: Conv) -> crate::Result<Jsonb> {\n    let val = val.as_value_ref();\n    convert_ref_dbtype_to_jsonb(val, strict)\n}\n\nfn parse_as_json_text(slice: &[u8], mode: Conv) -> crate::Result<Jsonb> {\n    let zero_pos = slice.iter().position(|&b| b == 0).unwrap_or(slice.len());\n    let truncated = &slice[..zero_pos];\n    let str = std::str::from_utf8(truncated)\n        .map_err(|_| LimboError::ParseError(\"malformed JSON\".to_string()))?;\n    Jsonb::from_str_with_mode(str, mode).map_err(Into::into)\n}\n\nfn is_jsonb_blob(slice: &[u8]) -> bool {\n    let Ok((header, header_offset)) = JsonbHeader::from_slice(0, slice) else {\n        return false;\n    };\n    let payload_size = header.payload_size();\n    let Some(total_expected) = header_offset.checked_add(payload_size) else {\n        return false;\n    };\n    if total_expected != slice.len() {\n        return false;\n    }\n\n    let jsonb = Jsonb::from_raw_data(slice);\n    if header.is_scalar() || payload_size <= JSONB_AMBIGUOUS_PAYLOAD_MAX {\n        jsonb.is_valid()\n    } else {\n        jsonb.element_type().is_ok()\n    }\n}\n\npub fn convert_ref_dbtype_to_jsonb(val: ValueRef<'_>, strict: Conv) -> crate::Result<Jsonb> {\n    match val {\n        ValueRef::Text(text) => {\n            let res = if text.subtype == TextSubtype::Json || matches!(strict, Conv::Strict) {\n                Jsonb::from_str_with_mode(&text, strict)\n            } else {\n                // Handle as a string literal otherwise\n                // Escape backslashes first, then double quotes\n                let mut str = text.replace('\\\\', \"\\\\\\\\\").replace('\"', \"\\\\\\\"\");\n                // Quote the string to make it a JSON string\n                str.insert(0, '\"');\n                str.push('\"');\n                Jsonb::from_str(&str)\n            };\n            res.map_err(|_| LimboError::ParseError(\"malformed JSON\".to_string()))\n        }\n        ValueRef::Blob(blob) => {\n            let bytes = blob;\n            // Valid JSON can start with these whitespace characters\n            let index = bytes\n                .iter()\n                .position(|&b| !matches!(b, b' ' | b'\\t' | b'\\n' | b'\\r'))\n                .unwrap_or(bytes.len());\n            let slice = &bytes[index..];\n            let json = match slice {\n                // branch with no overlapping initial byte\n                [b'\"', ..] | [b'-', ..] | [b'0'..=b'2', ..] => parse_as_json_text(slice, strict)?,\n                _ => match JsonbHeader::from_slice(0, slice) {\n                    Ok((header, header_offset)) => {\n                        let payload_size = header.payload_size();\n                        let total_expected = match header_offset.checked_add(payload_size) {\n                            Some(t) => t,\n                            None => {\n                                return Err(LimboError::ParseError(\"malformed JSON\".to_string()))\n                            }\n                        };\n\n                        if total_expected != slice.len() {\n                            parse_as_json_text(slice, strict)?\n                        } else {\n                            let jsonb = Jsonb::from_raw_data(slice);\n                            let is_valid_json = if payload_size <= 7 {\n                                jsonb.is_valid()\n                            } else {\n                                jsonb.element_type().is_ok()\n                            };\n                            if is_valid_json {\n                                jsonb\n                            } else {\n                                parse_as_json_text(slice, strict)?\n                            }\n                        }\n                    }\n                    Err(_) => parse_as_json_text(slice, strict)?,\n                },\n            };\n            json.element_type()?;\n            Ok(json)\n        }\n        ValueRef::Null => Ok(Jsonb::from_raw_data(\n            JsonbHeader::make_null().into_bytes().as_bytes(),\n        )),\n        ValueRef::Numeric(Numeric::Float(float)) => {\n            let float: f64 = float.into();\n            // Handle infinity for JSON compatibility with SQLite (#4196)\n            if float.is_infinite() {\n                let json_str = if float.is_sign_negative() {\n                    \"-9.0e+999\"\n                } else {\n                    \"9.0e+999\"\n                };\n                Jsonb::from_str(json_str)\n                    .map_err(|_| LimboError::ParseError(\"malformed JSON\".to_string()))\n            } else {\n                let mut buff = ryu::Buffer::new();\n                let s_ryu = buff.format(float);\n                let mut s = Cow::Borrowed(s_ryu);\n\n                if let Some(e_idx) = s_ryu.find('e') {\n                    // Scientific notation case\n                    s = Cow::Owned(String::with_capacity(s_ryu.len() + 4));\n                    let inner = s.to_mut();\n                    let mantissa = &s_ryu[..e_idx];\n                    let exponent = &s_ryu[e_idx + 1..];\n\n                    inner.push_str(mantissa);\n                    if !mantissa.contains('.') {\n                        inner.push_str(\".0\");\n                    }\n                    inner.push('e');\n                    if !exponent.starts_with('-') && !exponent.starts_with('+') {\n                        inner.push('+');\n                    }\n                    inner.push_str(exponent);\n                }\n\n                Jsonb::from_str(&s)\n                    .map_err(|_| LimboError::ParseError(\"malformed JSON\".to_string()))\n            }\n        }\n        ValueRef::Numeric(Numeric::Integer(int)) => Jsonb::from_str(&int.to_string())\n            .map_err(|_| LimboError::ParseError(\"malformed JSON\".to_string())),\n    }\n}\n\npub fn curry_convert_dbtype_to_jsonb(\n    strict: Conv,\n) -> impl FnOnce(ValueRef) -> crate::Result<Jsonb> {\n    move |val| convert_dbtype_to_jsonb(val, strict)\n}\n\npub fn json_array<I, E, V>(values: I) -> crate::Result<Value>\nwhere\n    V: AsValueRef,\n    E: ExactSizeIterator<Item = V>,\n    I: IntoIterator<IntoIter = E, Item = V>,\n{\n    let values = values.into_iter();\n    let mut json = Jsonb::make_empty_array(values.len());\n\n    for value in values {\n        let value = value.as_value_ref();\n        if matches!(value, ValueRef::Blob(_)) {\n            crate::bail_constraint_error!(\"JSON cannot hold BLOB values\")\n        }\n        let value = convert_dbtype_to_jsonb(value, Conv::NotStrict)?;\n        json.append_jsonb_to_end(value.data());\n    }\n    json.finalize_unsafe(ElementType::ARRAY)?;\n\n    json_string_to_db_type(json, ElementType::ARRAY, OutputVariant::ElementType)\n}\n\npub fn jsonb_array<I, E, V>(values: I) -> crate::Result<Value>\nwhere\n    V: AsValueRef,\n    E: ExactSizeIterator<Item = V>,\n    I: IntoIterator<IntoIter = E, Item = V>,\n{\n    let values = values.into_iter();\n    let mut json = Jsonb::make_empty_array(values.len());\n\n    for value in values {\n        let value = value.as_value_ref();\n        if matches!(value, ValueRef::Blob(_)) {\n            crate::bail_constraint_error!(\"JSON cannot hold BLOB values\")\n        }\n        let value = convert_dbtype_to_jsonb(value, Conv::NotStrict)?;\n        json.append_jsonb_to_end(value.data());\n    }\n    json.finalize_unsafe(ElementType::ARRAY)?;\n\n    json_string_to_db_type(json, ElementType::ARRAY, OutputVariant::Binary)\n}\n\npub fn json_array_length(\n    value: &Value,\n    path: Option<&Value>,\n    json_cache: &JsonCacheCell,\n) -> crate::Result<Value> {\n    if let Value::Null = value {\n        return Ok(Value::Null);\n    }\n\n    let make_jsonb_fn = curry_convert_dbtype_to_jsonb(Conv::Strict);\n    let mut json = json_cache.get_or_insert_with(value, make_jsonb_fn)?;\n\n    if path.is_none() {\n        let len = json.array_len()?;\n        return Ok(Value::from_i64(len as i64));\n    }\n\n    let path = json_path_from_db_value(path.expect(\"We already checked none\"), true)?;\n\n    if let Some(path) = path {\n        let mut op = SearchOperation::new(json.len() / 2);\n        let _ = json.operate_on_path(&path, &mut op);\n        if let Ok(len) = op.result().array_len() {\n            return Ok(Value::from_i64(len as i64));\n        }\n    }\n    Ok(Value::Null)\n}\n\npub fn json_set<I, E, V>(args: I, json_cache: &JsonCacheCell) -> crate::Result<Value>\nwhere\n    V: AsValueRef,\n    E: ExactSizeIterator<Item = V>,\n    I: IntoIterator<IntoIter = E, Item = V>,\n{\n    let mut args = args.into_iter();\n    if args.len() == 0 {\n        return Ok(Value::Null);\n    }\n    let make_jsonb_fn = curry_convert_dbtype_to_jsonb(Conv::Strict);\n    let first_arg = args.next().ok_or_else(|| {\n        crate::LimboError::InternalError(\"args should not be empty after length check\".to_string())\n    })?;\n    let mut json = json_cache.get_or_insert_with(first_arg, make_jsonb_fn)?;\n\n    // TODO: when `array_chunks` is stabilized we can chunk by 2 here\n    while args.len() > 1 {\n        let first = args.next().ok_or_else(|| {\n            crate::LimboError::InternalError(\n                \"args should have at least 2 elements in loop\".to_string(),\n            )\n        })?;\n\n        let second = args.next().ok_or_else(|| {\n            crate::LimboError::InternalError(\"args should have second element in loop\".to_string())\n        })?;\n\n        if second.as_value_ref().value_type() == ValueType::Blob {\n            return Err(crate::LimboError::Constraint(\n                \"JSON cannot hold BLOB values\".to_string(),\n            ));\n        }\n\n        let path = json_path_from_db_value(&first, true)?;\n\n        let value = convert_dbtype_to_jsonb(second, Conv::NotStrict)?;\n        let mut op = SetOperation::new(value);\n        if let Some(path) = path {\n            let _ = json.operate_on_path(&path, &mut op);\n        }\n    }\n\n    let el_type = json.element_type()?;\n\n    json_string_to_db_type(json, el_type, OutputVariant::String)\n}\n\npub fn jsonb_set<I, E, V>(args: I, json_cache: &JsonCacheCell) -> crate::Result<Value>\nwhere\n    V: AsValueRef,\n    E: ExactSizeIterator<Item = V>,\n    I: IntoIterator<IntoIter = E, Item = V>,\n{\n    let mut args = args.into_iter();\n    if args.len() == 0 {\n        return Ok(Value::Null);\n    }\n\n    let make_jsonb_fn = curry_convert_dbtype_to_jsonb(Conv::Strict);\n    let first_arg = args.next().ok_or_else(|| {\n        crate::LimboError::InternalError(\"args should not be empty after length check\".to_string())\n    })?;\n    let mut json = json_cache.get_or_insert_with(first_arg, make_jsonb_fn)?;\n\n    // TODO: when `array_chunks` is stabilized we can chunk by 2 here\n    while args.len() > 1 {\n        let first = args.next().ok_or_else(|| {\n            crate::LimboError::InternalError(\n                \"args should have at least 2 elements in loop\".to_string(),\n            )\n        })?;\n        let path = json_path_from_db_value(&first, true)?;\n\n        let second = args.next().ok_or_else(|| {\n            crate::LimboError::InternalError(\"args should have second element in loop\".to_string())\n        })?;\n        let value = convert_dbtype_to_jsonb(second, Conv::NotStrict)?;\n        let mut op = SetOperation::new(value);\n        if let Some(path) = path {\n            let _ = json.operate_on_path(&path, &mut op);\n        }\n    }\n\n    let el_type = json.element_type()?;\n\n    json_string_to_db_type(json, el_type, OutputVariant::Binary)\n}\n\n/// Implements the -> operator. Always returns a proper JSON value.\n/// https://sqlite.org/json1.html#the_and_operators\npub fn json_arrow_extract(\n    value: impl AsValueRef,\n    path: impl AsValueRef,\n    json_cache: &JsonCacheCell,\n) -> crate::Result<Value> {\n    let value = value.as_value_ref();\n    if let ValueRef::Null = value {\n        return Ok(Value::Null);\n    }\n\n    if let Some(path) = json_path_from_db_value(&path, false)? {\n        let make_jsonb_fn = curry_convert_dbtype_to_jsonb(Conv::Strict);\n        let mut json = json_cache.get_or_insert_with(value, make_jsonb_fn)?;\n        let mut op = SearchOperation::new(json.len());\n        let res = json.operate_on_path(&path, &mut op);\n        let extracted = op.result();\n        if res.is_ok() {\n            Ok(Value::Text(Text::json(extracted.to_string()?)))\n        } else {\n            Ok(Value::Null)\n        }\n    } else {\n        Ok(Value::Null)\n    }\n}\n\n/// Implements the ->> operator. Always returns a SQL representation of the JSON subcomponent.\n/// https://sqlite.org/json1.html#the_and_operators\npub fn json_arrow_shift_extract(\n    value: impl AsValueRef,\n    path: impl AsValueRef,\n    json_cache: &JsonCacheCell,\n) -> crate::Result<Value> {\n    let value = value.as_value_ref();\n    if let ValueRef::Null = value {\n        return Ok(Value::Null);\n    }\n    if let Some(path) = json_path_from_db_value(&path, false)? {\n        let make_jsonb_fn = curry_convert_dbtype_to_jsonb(Conv::Strict);\n        let mut json = json_cache.get_or_insert_with(value, make_jsonb_fn)?;\n        let mut op = SearchOperation::new(json.len());\n        let res = json.operate_on_path(&path, &mut op);\n        let extracted = op.result();\n        let element_type = match extracted.element_type() {\n            Err(_) => return Ok(Value::Null),\n            Ok(el) => el,\n        };\n\n        if res.is_ok() {\n            Ok(json_string_to_db_type(\n                extracted,\n                element_type,\n                OutputVariant::ElementType,\n            )?)\n        } else {\n            Ok(Value::Null)\n        }\n    } else {\n        Ok(Value::Null)\n    }\n}\n\n/// Extracts a JSON value from a JSON object or array.\n/// If there's only a single path, the return value might be either a TEXT or a database type.\n/// https://sqlite.org/json1.html#the_json_extract_function\npub fn json_extract<I, E, V>(\n    value: impl AsValueRef,\n    paths: I,\n    json_cache: &JsonCacheCell,\n) -> crate::Result<Value>\nwhere\n    V: AsValueRef,\n    E: ExactSizeIterator<Item = V>,\n    I: IntoIterator<IntoIter = E, Item = V>,\n{\n    let value = value.as_value_ref();\n    if let ValueRef::Null = value {\n        return Ok(Value::Null);\n    }\n\n    let paths = paths.into_iter();\n    if paths.len() == 0 {\n        return Ok(Value::Null);\n    }\n    let convert_to_jsonb = curry_convert_dbtype_to_jsonb(Conv::Strict);\n    let jsonb = json_cache.get_or_insert_with(value, convert_to_jsonb)?;\n    let (json, element_type) = jsonb_extract_internal(jsonb, paths)?;\n\n    let result = json_string_to_db_type(json, element_type, OutputVariant::ElementType)?;\n\n    Ok(result)\n}\n\npub fn jsonb_extract<I, E, V>(\n    value: &Value,\n    paths: I,\n    json_cache: &JsonCacheCell,\n) -> crate::Result<Value>\nwhere\n    V: AsValueRef,\n    E: ExactSizeIterator<Item = V>,\n    I: IntoIterator<IntoIter = E, Item = V>,\n{\n    if let Value::Null = value {\n        return Ok(Value::Null);\n    }\n\n    let paths = paths.into_iter();\n    if paths.len() == 0 {\n        return Ok(Value::Null);\n    }\n    let convert_to_jsonb = curry_convert_dbtype_to_jsonb(Conv::Strict);\n    let jsonb = json_cache.get_or_insert_with(value, convert_to_jsonb)?;\n\n    let (json, element_type) = jsonb_extract_internal(jsonb, paths)?;\n    let result = json_string_to_db_type(json, element_type, OutputVariant::ElementType)?;\n\n    Ok(result)\n}\n\nfn jsonb_extract_internal<E, V>(value: Jsonb, mut paths: E) -> crate::Result<(Jsonb, ElementType)>\nwhere\n    V: AsValueRef,\n    E: ExactSizeIterator<Item = V>,\n{\n    let null = Jsonb::from_raw_data(JsonbHeader::make_null().into_bytes().as_bytes());\n    if paths.len() == 1 {\n        let first_path = paths.next().ok_or_else(|| {\n            crate::LimboError::InternalError(\"paths should have one element\".to_string())\n        })?;\n        if let Some(path) = json_path_from_db_value(&first_path, true)? {\n            let mut json = value;\n\n            let mut op = SearchOperation::new(json.len());\n            let res = json.operate_on_path(&path, &mut op);\n            let extracted = op.result();\n            let element_type = match extracted.element_type() {\n                Err(_) => return Ok((null, ElementType::NULL)),\n                Ok(el) => el,\n            };\n            if res.is_ok() {\n                return Ok((extracted, element_type));\n            } else {\n                return Ok((null, ElementType::NULL));\n            }\n        } else {\n            return Ok((null, ElementType::NULL));\n        }\n    }\n\n    let mut json = value;\n    let mut result = Jsonb::make_empty_array(json.len());\n\n    // TODO: make an op to avoid creating new json for every path element\n    for path in paths {\n        let path = json_path_from_db_value(&path, true);\n        if let Some(path) = path? {\n            let mut op = SearchOperation::new(json.len());\n            let res = json.operate_on_path(&path, &mut op);\n            let extracted = op.result();\n            if res.is_ok() {\n                result.append_to_array_unsafe(&extracted.data());\n            } else {\n                result.append_to_array_unsafe(JsonbHeader::make_null().into_bytes().as_bytes());\n            }\n        } else {\n            return Ok((null, ElementType::NULL));\n        }\n    }\n    result.finalize_unsafe(ElementType::ARRAY)?;\n    Ok((result, ElementType::ARRAY))\n}\n\n/// converts a `Jsonb` value to a db Value\n///\n/// # Arguments\n///\n/// - `jsonb` – the value to convert\n/// - `element_type` – the element type of the jsonb\n/// - `flag` – how the result should be formatted (null values will stay null).\n///   - If the flag is `OutputVariant::Binary`, the result is a `Value::Blob`.\n///   - If it is `OutputVariant::ElementType` and the `element_type` is text, the result has a subtype of `TestSubtype::Text`, with the outer quotes removed.\n///   - If it is `OutputVariant::String` and the `element_type` is text, the result has a subtype of `TextSubtype::Text`.\n///   - If the `element_type` is not text, the flag is ignored.\npub fn json_string_to_db_type(\n    json: Jsonb,\n    element_type: ElementType,\n    flag: OutputVariant,\n) -> crate::Result<Value> {\n    if element_type == ElementType::NULL {\n        return Ok(Value::Null);\n    }\n    if matches!(flag, OutputVariant::Binary) {\n        return Ok(Value::Blob(json.data()));\n    }\n    let mut json_string = json.to_string()?;\n    if matches!(flag, OutputVariant::String) {\n        return Ok(Value::Text(Text::json(json_string)));\n    }\n    match element_type {\n        ElementType::ARRAY | ElementType::OBJECT => Ok(Value::Text(Text::json(json_string))),\n        ElementType::TEXT | ElementType::TEXT5 | ElementType::TEXTJ | ElementType::TEXTRAW => {\n            if matches!(flag, OutputVariant::ElementType) {\n                json_string.remove(json_string.len() - 1);\n                json_string.remove(0);\n                Ok(Value::Text(Text::new(unescape_string(&json_string))))\n            } else {\n                Ok(Value::Text(Text::new(json_string)))\n            }\n        }\n        ElementType::FLOAT5 | ElementType::FLOAT => {\n            match json_string.parse::<f64>() {\n                Ok(float_val)\n                    if float_val.is_infinite() && matches!(flag, OutputVariant::ElementType) =>\n                {\n                    // For json() function, SQLite returns bare infinity as \"9e999\" not \"9.0e+999\"\n                    let simplified = if float_val.is_sign_negative() {\n                        \"-9e999\"\n                    } else {\n                        \"9e999\"\n                    };\n                    Ok(Value::Text(Text::json(simplified.to_string())))\n                }\n                Ok(float_val) => Ok(Value::from_f64(float_val)),\n                Err(_) => Err(LimboError::Constraint(\"malformed JSON\".to_string())),\n            }\n        }\n        ElementType::INT | ElementType::INT5 => {\n            let result = i64::from_str(&json_string);\n            if let Ok(int) = result {\n                Ok(Value::from_i64(int))\n            } else {\n                let res = f64::from_str(&json_string);\n                match res {\n                    Ok(num) => Ok(Value::from_f64(num)),\n                    Err(_) => Err(LimboError::Constraint(\"malformed JSON\".to_string())),\n                }\n            }\n        }\n        ElementType::TRUE => Ok(Value::from_i64(1)),\n        ElementType::FALSE => Ok(Value::from_i64(0)),\n        _ => unreachable!(),\n    }\n}\n\npub fn json_type(value: impl AsValueRef, path: Option<impl AsValueRef>) -> crate::Result<Value> {\n    let value = value.as_value_ref();\n    if let ValueRef::Null = value {\n        return Ok(Value::Null);\n    }\n    if path.is_none() {\n        let json = convert_dbtype_to_jsonb(value, Conv::Strict)?;\n        let element_type = json.element_type()?;\n\n        return Ok(Value::Text(Text::json(element_type.into())));\n    }\n    let path_value = path.ok_or_else(|| {\n        crate::LimboError::InternalError(\"path should be Some after is_none check\".to_string())\n    })?;\n    if let Some(path) = json_path_from_db_value(&path_value, true)? {\n        let mut json = convert_dbtype_to_jsonb(value, Conv::Strict)?;\n\n        if let Ok(mut path) = json.navigate_path(&path, PathOperationMode::ReplaceExisting) {\n            let target = path.pop().expect(\"Should exist\");\n            let element_type = if let Some(el_index) = target.get_array_index() {\n                json.element_type_at(el_index)\n            } else {\n                json.element_type_at(target.field_value_index)\n            }?;\n            Ok(Value::Text(Text::json(element_type.into())))\n        } else {\n            Ok(Value::Null)\n        }\n    } else {\n        Ok(Value::Null)\n    }\n}\n\nfn json_path_from_db_value<'a>(\n    path: &'a (impl AsValueRef + 'a),\n    strict: bool,\n) -> crate::Result<Option<JsonPath<'a>>> {\n    let path = path.as_value_ref();\n    let json_path = if strict {\n        match path {\n            ValueRef::Text(t) => json_path(t.as_str())?,\n            ValueRef::Null => return Ok(None),\n            _ => crate::bail_constraint_error!(\"JSON path error near: {:?}\", path.to_string()),\n        }\n    } else {\n        match path {\n            ValueRef::Text(t) => {\n                if t.as_str().starts_with(\"$\") {\n                    json_path(t.as_str())?\n                } else {\n                    JsonPath {\n                        elements: vec![\n                            PathElement::Root(),\n                            PathElement::Key(Cow::Borrowed(t.as_str()), false),\n                        ],\n                    }\n                }\n            }\n            ValueRef::Null => return Ok(None),\n            ValueRef::Numeric(Numeric::Integer(i)) => JsonPath {\n                elements: vec![\n                    PathElement::Root(),\n                    PathElement::ArrayLocator(Some(i as i32)),\n                ],\n            },\n            ValueRef::Numeric(Numeric::Float(f)) => JsonPath {\n                elements: vec![\n                    PathElement::Root(),\n                    PathElement::Key(Cow::Owned(f64::from(f).to_string()), false),\n                ],\n            },\n            _ => crate::bail_constraint_error!(\"JSON path error near: {:?}\", path.to_string()),\n        }\n    };\n\n    Ok(Some(json_path))\n}\n\npub fn json_error_position(json: impl AsValueRef) -> crate::Result<Value> {\n    match json.as_value_ref() {\n        ValueRef::Text(t) => match Jsonb::from_str(t.as_str()) {\n            Ok(_) => Ok(Value::from_i64(0)),\n            Err(JsonError::Message { location, .. }) => {\n                if let Some(loc) = location {\n                    let one_indexed = loc + 1;\n                    Ok(Value::from_i64(one_indexed as i64))\n                } else {\n                    Err(crate::error::LimboError::InternalError(\n                        \"failed to determine json error position\".into(),\n                    ))\n                }\n            }\n        },\n        ValueRef::Blob(_) => {\n            bail_parse_error!(\"Unsupported\")\n        }\n        ValueRef::Null => Ok(Value::Null),\n        _ => Ok(Value::from_i64(0)),\n    }\n}\n\n/// Constructs a JSON object from a list of values that represent key-value pairs.\n/// The number of values must be even, and the first value of each pair (which represents the map key)\n/// must be a TEXT value. The second value of each pair can be any JSON value (which represents the map value)\npub fn json_object<I, E, V>(values: I) -> crate::Result<Value>\nwhere\n    V: AsValueRef,\n    E: ExactSizeIterator<Item = V>,\n    I: IntoIterator<IntoIter = E, Item = V>,\n{\n    let mut values = values.into_iter();\n    if values.len() % 2 != 0 {\n        bail_constraint_error!(\"json_object() requires an even number of arguments\")\n    }\n    let mut json = Jsonb::make_empty_obj(values.len() * 50);\n\n    // TODO: when `array_chunks` is stabilized we can chunk by 2 here\n    while values.len() > 1 {\n        let first = values.next().ok_or_else(|| {\n            crate::LimboError::InternalError(\n                \"values should have at least 2 elements in loop\".to_string(),\n            )\n        })?;\n        let first = first.as_value_ref();\n        if first.value_type() != ValueType::Text {\n            bail_constraint_error!(\"json_object() labels must be TEXT\")\n        }\n        let key = convert_dbtype_to_jsonb(first, Conv::ToString)?;\n        json.append_jsonb_to_end(key.data());\n\n        let second = values.next().ok_or_else(|| {\n            crate::LimboError::InternalError(\n                \"values should have second element in loop\".to_string(),\n            )\n        })?;\n        let value = convert_dbtype_to_jsonb(second, Conv::NotStrict)?;\n        json.append_jsonb_to_end(value.data());\n    }\n\n    json.finalize_unsafe(ElementType::OBJECT)?;\n\n    json_string_to_db_type(json, ElementType::OBJECT, OutputVariant::String)\n}\n\npub fn jsonb_object<I, E, V>(values: I) -> crate::Result<Value>\nwhere\n    V: AsValueRef,\n    E: ExactSizeIterator<Item = V>,\n    I: IntoIterator<IntoIter = E, Item = V>,\n{\n    let mut values = values.into_iter();\n    if values.len() % 2 != 0 {\n        bail_constraint_error!(\"json_object() requires an even number of arguments\")\n    }\n    let mut json = Jsonb::make_empty_obj(values.len() * 50);\n\n    // TODO: when `array_chunks` is stabilized we can chunk by 2 here\n    while values.len() > 1 {\n        let first = values.next().ok_or_else(|| {\n            crate::LimboError::InternalError(\n                \"values should have at least 2 elements in loop\".to_string(),\n            )\n        })?;\n        let first = first.as_value_ref();\n        if first.value_type() != ValueType::Text {\n            bail_constraint_error!(\"json_object() labels must be TEXT\")\n        }\n        let key = convert_dbtype_to_jsonb(first, Conv::ToString)?;\n        json.append_jsonb_to_end(key.data());\n\n        let second = values.next().ok_or_else(|| {\n            crate::LimboError::InternalError(\n                \"values should have second element in loop\".to_string(),\n            )\n        })?;\n        let value = convert_dbtype_to_jsonb(second, Conv::NotStrict)?;\n        json.append_jsonb_to_end(value.data());\n    }\n\n    json.finalize_unsafe(ElementType::OBJECT)?;\n\n    json_string_to_db_type(json, ElementType::OBJECT, OutputVariant::Binary)\n}\n\n/// Tries to convert the value to jsonb. Returns Value::from_i64(1) if the conversion\n/// succeeded, and Value::from_i64(0) if it didn't.\npub fn is_json_valid(json_value: impl AsValueRef) -> Value {\n    let json_value = json_value.as_value_ref();\n    match json_value {\n        ValueRef::Null => Value::Null,\n        ValueRef::Blob(blob) => {\n            let index = blob\n                .iter()\n                .position(|&b| !matches!(b, b' ' | b'\\t' | b'\\n' | b'\\r'))\n                .unwrap_or(blob.len());\n            let slice = &blob[index..];\n            if is_jsonb_blob(slice) {\n                Value::from_i64(0)\n            } else {\n                parse_as_json_text(slice, Conv::Strict)\n                    .map(|_| Value::from_i64(1))\n                    .unwrap_or_else(|_| Value::from_i64(0))\n            }\n        }\n        _ => convert_dbtype_to_jsonb(json_value, Conv::Strict)\n            .map(|_| Value::from_i64(1))\n            .unwrap_or_else(|_| Value::from_i64(0)),\n    }\n}\n\npub fn json_quote(value: impl AsValueRef) -> crate::Result<Value> {\n    let value = value.as_value_ref();\n    match value {\n        ValueRef::Text(ref t) => {\n            // If X is a JSON value returned by another JSON function,\n            // then this function is a no-op\n            if t.subtype == TextSubtype::Json {\n                // Should just return the json value with no quotes\n                return Ok(value.to_owned());\n            }\n\n            let mut escaped_value = String::with_capacity(t.value.len() + 4);\n            escaped_value.push('\"');\n\n            for c in t.as_str().chars() {\n                match c {\n                    '\"' | '\\\\' | '\\n' | '\\r' | '\\t' | '\\u{0008}' | '\\u{000c}' => {\n                        escaped_value.push('\\\\');\n                        escaped_value.push(c);\n                    }\n                    c => escaped_value.push(c),\n                }\n            }\n            escaped_value.push('\"');\n\n            Ok(Value::build_text(escaped_value))\n        }\n        // Numbers are unquoted in json, but must be returned as TEXT\n        ValueRef::Numeric(n) => match n {\n            crate::numeric::Numeric::Integer(i) => Ok(Value::build_text(i.to_string())),\n            crate::numeric::Numeric::Float(_) => {\n                let json = convert_ref_dbtype_to_jsonb(ValueRef::Numeric(n), Conv::Strict)?;\n                Ok(Value::build_text(json.to_string()?))\n            }\n        },\n        ValueRef::Blob(_) => crate::bail_constraint_error!(\"JSON cannot hold BLOB values\"),\n        ValueRef::Null => Ok(Value::build_text(\"null\")),\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::numeric::Numeric;\n    use crate::types::Value;\n\n    #[test]\n    fn test_get_json_valid_json5() {\n        let input = Value::build_text(\"{ key: 'value' }\");\n        let result = get_json(&input, None).unwrap();\n        if let Value::Text(result_str) = result {\n            assert!(result_str.as_str().contains(\"\\\"key\\\":\\\"value\\\"\"));\n            assert_eq!(result_str.subtype, TextSubtype::Json);\n        } else {\n            panic!(\"Expected Value::Text\");\n        }\n    }\n\n    #[test]\n    fn test_get_json_valid_json5_infinity() {\n        let input = Value::build_text(\"{ \\\"key\\\": Infinity }\");\n        let result = get_json(&input, None).unwrap();\n        if let Value::Text(result_str) = result {\n            assert!(result_str.as_str().contains(\"{\\\"key\\\":9e999}\"));\n            assert_eq!(result_str.subtype, TextSubtype::Json);\n        } else {\n            panic!(\"Expected Value::Text\");\n        }\n    }\n\n    #[test]\n    fn test_get_json_valid_json5_negative_infinity() {\n        let input = Value::build_text(\"{ \\\"key\\\": -Infinity }\");\n        let result = get_json(&input, None).unwrap();\n        if let Value::Text(result_str) = result {\n            assert!(result_str.as_str().contains(\"{\\\"key\\\":-9e999}\"));\n            assert_eq!(result_str.subtype, TextSubtype::Json);\n        } else {\n            panic!(\"Expected Value::Text\");\n        }\n    }\n\n    #[test]\n    fn test_get_json_valid_json5_nan() {\n        let input = Value::build_text(\"{ \\\"key\\\": NaN }\");\n        let result = get_json(&input, None).unwrap();\n        if let Value::Text(result_str) = result {\n            assert!(result_str.as_str().contains(\"{\\\"key\\\":null}\"));\n            assert_eq!(result_str.subtype, TextSubtype::Json);\n        } else {\n            panic!(\"Expected Value::Text\");\n        }\n    }\n\n    #[test]\n    fn test_get_json_invalid_json5() {\n        let input = Value::build_text(\"{ key: value }\");\n        let result = get_json(&input, None);\n        match result {\n            Ok(_) => panic!(\"Expected error for malformed JSON\"),\n            Err(e) => assert!(e.to_string().contains(\"malformed JSON\")),\n        }\n    }\n\n    #[test]\n    fn test_get_json_valid_jsonb() {\n        let input = Value::build_text(\"{\\\"key\\\":\\\"value\\\"}\");\n        let result = get_json(&input, None).unwrap();\n        if let Value::Text(result_str) = result {\n            assert!(result_str.as_str().contains(\"\\\"key\\\":\\\"value\\\"\"));\n            assert_eq!(result_str.subtype, TextSubtype::Json);\n        } else {\n            panic!(\"Expected Value::Text\");\n        }\n    }\n\n    #[test]\n    fn test_get_json_invalid_jsonb() {\n        let input = Value::build_text(\"{key:\\\"value\\\"\");\n        let result = get_json(&input, None);\n        match result {\n            Ok(_) => panic!(\"Expected error for malformed JSON\"),\n            Err(e) => assert!(e.to_string().contains(\"malformed JSON\")),\n        }\n    }\n\n    #[test]\n    fn test_get_json_blob_valid_jsonb() {\n        let binary_json = vec![124, 55, 104, 101, 121, 39, 121, 111];\n        let input = Value::Blob(binary_json);\n        let result = get_json(&input, None).unwrap();\n        if let Value::Text(result_str) = result {\n            assert!(result_str.as_str().contains(r#\"{\"hey\":\"yo\"}\"#));\n            assert_eq!(result_str.subtype, TextSubtype::Json);\n        } else {\n            panic!(\"Expected Value::Text\");\n        }\n    }\n\n    #[test]\n    fn test_get_json_blob_invalid_jsonb() {\n        let binary_json: Vec<u8> = vec![0xA2, 0x62, 0x6B, 0x31, 0x62, 0x76]; // Incomplete binary JSON\n        let input = Value::Blob(binary_json);\n        let result = get_json(&input, None);\n        println!(\"{result:?}\");\n        match result {\n            Ok(_) => panic!(\"Expected error for malformed JSON\"),\n            Err(e) => assert!(e.to_string().contains(\"malformed JSON\")),\n        }\n    }\n\n    #[test]\n    fn test_get_json_non_text() {\n        let input = Value::Null;\n        let result = get_json(&input, None).unwrap();\n        if let Value::Null = result {\n            // Test passed\n        } else {\n            panic!(\"Expected Value::Null\");\n        }\n    }\n\n    #[test]\n    fn test_json_array_simple() {\n        let text = Value::build_text(\"value1\");\n        let json = Value::Text(Text::json(\"\\\"value2\\\"\".to_string()));\n        let input = [text, json, Value::from_i64(1), Value::from_f64(1.1)];\n\n        let result = json_array(&input).unwrap();\n        if let Value::Text(res) = result {\n            assert_eq!(res.as_str(), \"[\\\"value1\\\",\\\"value2\\\",1,1.1]\");\n            assert_eq!(res.subtype, TextSubtype::Json);\n        } else {\n            panic!(\"Expected Value::Text\");\n        }\n    }\n\n    #[test]\n    fn test_json_array_with_infinity() {\n        let infinity = Value::from_f64(f64::INFINITY);\n        let neg_infinity = Value::from_f64(f64::NEG_INFINITY);\n        let input = [Value::from_i64(1), infinity, neg_infinity];\n\n        let result = json_array(&input).unwrap();\n        if let Value::Text(res) = result {\n            assert_eq!(res.as_str(), \"[1,9.0e+999,-9.0e+999]\");\n            assert_eq!(res.subtype, TextSubtype::Json);\n        } else {\n            panic!(\"Expected Value::Text\");\n        }\n    }\n\n    #[test]\n    fn test_json_object_with_infinity() {\n        let infinity = Value::from_f64(f64::INFINITY);\n        let key = Value::build_text(\"k\");\n        let input = [key, infinity];\n\n        let result = json_object(&input).unwrap();\n        if let Value::Text(res) = result {\n            assert_eq!(res.as_str(), r#\"{\"k\":9.0e+999}\"#);\n            assert_eq!(res.subtype, TextSubtype::Json);\n        } else {\n            panic!(\"Expected Value::Text\");\n        }\n    }\n\n    #[test]\n    fn test_json_object_with_negative_infinity() {\n        let neg_infinity = Value::from_f64(f64::NEG_INFINITY);\n        let key = Value::build_text(\"k\");\n        let input = [key, neg_infinity];\n\n        let result = json_object(&input).unwrap();\n        if let Value::Text(res) = result {\n            assert_eq!(res.as_str(), r#\"{\"k\":-9.0e+999}\"#);\n            assert_eq!(res.subtype, TextSubtype::Json);\n        } else {\n            panic!(\"Expected Value::Text\");\n        }\n    }\n\n    #[test]\n    fn test_json_with_infinity() {\n        let infinity = Value::from_f64(f64::INFINITY);\n        let result = get_json(&infinity, None).unwrap();\n        if let Value::Text(res) = result {\n            assert_eq!(res.as_str(), \"9e999\");\n            assert_eq!(res.subtype, TextSubtype::Json);\n        } else {\n            panic!(\"Expected Value::Text, got {result:?}\");\n        }\n    }\n\n    #[test]\n    fn test_json_with_negative_infinity() {\n        let neg_infinity = Value::from_f64(f64::NEG_INFINITY);\n        let result = get_json(&neg_infinity, None).unwrap();\n        if let Value::Text(res) = result {\n            assert_eq!(res.as_str(), \"-9e999\");\n            assert_eq!(res.subtype, TextSubtype::Json);\n        } else {\n            panic!(\"Expected Value::Text, got {result:?}\");\n        }\n    }\n\n    #[test]\n    fn test_json_array_empty() {\n        let input: [Value; 0] = [];\n\n        let result = json_array(input).unwrap();\n        if let Value::Text(res) = result {\n            assert_eq!(res.as_str(), \"[]\");\n            assert_eq!(res.subtype, TextSubtype::Json);\n        } else {\n            panic!(\"Expected Value::Text\");\n        }\n    }\n\n    #[test]\n    fn test_json_array_blob_invalid() {\n        let blob = Value::Blob(\"1\".as_bytes().to_vec());\n\n        let input = [blob];\n\n        let result = json_array(&input);\n\n        match result {\n            Ok(_) => panic!(\"Expected error for blob input\"),\n            Err(e) => assert!(e.to_string().contains(\"JSON cannot hold BLOB values\")),\n        }\n    }\n\n    #[test]\n    fn test_json_array_length() {\n        let input = Value::build_text(\"[1,2,3,4]\");\n        let json_cache = JsonCacheCell::new();\n        let result = json_array_length(&input, None, &json_cache).unwrap();\n        if let Value::Numeric(Numeric::Integer(res)) = result {\n            assert_eq!(res, 4);\n        } else {\n            panic!(\"Expected Value::Numeric(Numeric::Integer)\");\n        }\n    }\n\n    #[test]\n    fn test_json_array_length_null() {\n        let input = Value::Null;\n        let json_cache = JsonCacheCell::new();\n        let result = json_array_length(&input, None, &json_cache).unwrap();\n        assert_eq!(result, Value::Null);\n    }\n\n    #[test]\n    fn test_json_array_length_empty() {\n        let input = Value::build_text(\"[]\");\n        let json_cache = JsonCacheCell::new();\n        let result = json_array_length(&input, None, &json_cache).unwrap();\n        if let Value::Numeric(Numeric::Integer(res)) = result {\n            assert_eq!(res, 0);\n        } else {\n            panic!(\"Expected Value::Numeric(Numeric::Integer)\");\n        }\n    }\n\n    #[test]\n    fn test_json_array_length_root() {\n        let input = Value::build_text(\"[1,2,3,4]\");\n        let json_cache = JsonCacheCell::new();\n        let result = json_array_length(&input, Some(&Value::build_text(\"$\")), &json_cache).unwrap();\n        if let Value::Numeric(Numeric::Integer(res)) = result {\n            assert_eq!(res, 4);\n        } else {\n            panic!(\"Expected Value::Numeric(Numeric::Integer)\");\n        }\n    }\n\n    #[test]\n    fn test_json_array_length_not_array() {\n        let input = Value::build_text(\"{one: [1,2,3,4]}\");\n        let json_cache = JsonCacheCell::new();\n        let result = json_array_length(&input, None, &json_cache).unwrap();\n        if let Value::Numeric(Numeric::Integer(res)) = result {\n            assert_eq!(res, 0);\n        } else {\n            panic!(\"Expected Value::Numeric(Numeric::Integer)\");\n        }\n    }\n\n    #[test]\n    fn test_json_array_length_via_prop() {\n        let input = Value::build_text(\"{one: [1,2,3,4]}\");\n        let json_cache = JsonCacheCell::new();\n        let result =\n            json_array_length(&input, Some(&Value::build_text(\"$.one\")), &json_cache).unwrap();\n        if let Value::Numeric(Numeric::Integer(res)) = result {\n            assert_eq!(res, 4);\n        } else {\n            panic!(\"Expected Value::Numeric(Numeric::Integer)\");\n        }\n    }\n\n    #[test]\n    fn test_json_array_length_via_index() {\n        let input = Value::build_text(\"[[1,2,3,4]]\");\n        let json_cache = JsonCacheCell::new();\n        let result =\n            json_array_length(&input, Some(&Value::build_text(\"$[0]\")), &json_cache).unwrap();\n        if let Value::Numeric(Numeric::Integer(res)) = result {\n            assert_eq!(res, 4);\n        } else {\n            panic!(\"Expected Value::Numeric(Numeric::Integer)\");\n        }\n    }\n\n    #[test]\n    fn test_json_array_length_via_index_not_array() {\n        let input = Value::build_text(\"[1,2,3,4]\");\n        let json_cache = JsonCacheCell::new();\n        let result =\n            json_array_length(&input, Some(&Value::build_text(\"$[2]\")), &json_cache).unwrap();\n        if let Value::Numeric(Numeric::Integer(res)) = result {\n            assert_eq!(res, 0);\n        } else {\n            panic!(\"Expected Value::Numeric(Numeric::Integer)\");\n        }\n    }\n\n    #[test]\n    fn test_json_array_length_via_index_bad_prop() {\n        let input = Value::build_text(\"{one: [1,2,3,4]}\");\n        let json_cache = JsonCacheCell::new();\n        let result =\n            json_array_length(&input, Some(&Value::build_text(\"$.two\")), &json_cache).unwrap();\n        assert_eq!(Value::Null, result);\n    }\n\n    #[test]\n    fn test_json_array_length_simple_json_subtype() {\n        let input = Value::build_text(\"[1,2,3]\");\n        let json_cache = JsonCacheCell::new();\n        let wrapped = get_json(&input, None).unwrap();\n        let result = json_array_length(&wrapped, None, &json_cache).unwrap();\n\n        if let Value::Numeric(Numeric::Integer(res)) = result {\n            assert_eq!(res, 3);\n        } else {\n            panic!(\"Expected Value::Numeric(Numeric::Integer)\");\n        }\n    }\n\n    #[test]\n    fn test_json_extract_missing_path() {\n        let json_cache = JsonCacheCell::new();\n        let result = json_extract(\n            Value::build_text(\"{\\\"a\\\":2}\"),\n            &[Value::build_text(\"$.x\")],\n            &json_cache,\n        );\n\n        match result {\n            Ok(Value::Null) => (),\n            _ => panic!(\"Expected null result, got: {result:?}\"),\n        }\n    }\n    #[test]\n    fn test_json_extract_null_path() {\n        let json_cache = JsonCacheCell::new();\n        let result = json_extract(Value::build_text(\"{\\\"a\\\":2}\"), &[Value::Null], &json_cache);\n\n        match result {\n            Ok(Value::Null) => (),\n            _ => panic!(\"Expected null result, got: {result:?}\"),\n        }\n    }\n\n    #[test]\n    fn test_json_path_invalid() {\n        let json_cache = JsonCacheCell::new();\n        let result = json_extract(\n            Value::build_text(\"{\\\"a\\\":2}\"),\n            &[Value::from_f64(1.1)],\n            &json_cache,\n        );\n\n        match result {\n            Ok(_) => panic!(\"expected error\"),\n            Err(e) => assert!(e.to_string().contains(\"JSON path error\")),\n        }\n    }\n\n    #[test]\n    fn test_json_error_position_no_error() {\n        let input = Value::build_text(\"[1,2,3]\");\n        let result = json_error_position(&input).unwrap();\n        assert_eq!(result, Value::from_i64(0));\n    }\n\n    #[test]\n    fn test_json_error_position_no_error_more() {\n        let input = Value::build_text(r#\"{\"a\":55,\"b\":72 , }\"#);\n        let result = json_error_position(&input).unwrap();\n        assert_eq!(result, Value::from_i64(0));\n    }\n\n    #[test]\n    fn test_json_error_position_object() {\n        let input = Value::build_text(r#\"{\"a\":55,\"b\":72,,}\"#);\n        let result = json_error_position(&input).unwrap();\n        assert_eq!(result, Value::from_i64(16));\n    }\n\n    #[test]\n    fn test_json_error_position_array() {\n        let input = Value::build_text(r#\"[\"a\",55,\"b\",72,,]\"#);\n        let result = json_error_position(&input).unwrap();\n        assert_eq!(result, Value::from_i64(16));\n    }\n\n    #[test]\n    fn test_json_error_position_null() {\n        let input = Value::Null;\n        let result = json_error_position(&input).unwrap();\n        assert_eq!(result, Value::Null);\n    }\n\n    #[test]\n    fn test_json_error_position_integer() {\n        let input = Value::from_i64(5);\n        let result = json_error_position(&input).unwrap();\n        assert_eq!(result, Value::from_i64(0));\n    }\n\n    #[test]\n    fn test_json_error_position_float() {\n        let input = Value::from_f64(-5.5);\n        let result = json_error_position(&input).unwrap();\n        assert_eq!(result, Value::from_i64(0));\n    }\n\n    #[test]\n    fn test_json_object_simple() {\n        let key = Value::build_text(\"key\");\n        let value = Value::build_text(\"value\");\n        let input = [key, value];\n\n        let result = json_object(&input).unwrap();\n        let Value::Text(json_text) = result else {\n            panic!(\"Expected Value::Text\");\n        };\n        assert_eq!(json_text.as_str(), r#\"{\"key\":\"value\"}\"#);\n    }\n\n    #[test]\n    fn test_json_object_multiple_values() {\n        let text_key = Value::build_text(\"text_key\");\n        let text_value = Value::build_text(\"text_value\");\n        let json_key = Value::build_text(\"json_key\");\n        let json_value = Value::Text(Text::json(r#\"{\"json\":\"value\",\"number\":1}\"#.to_string()));\n        let integer_key = Value::build_text(\"integer_key\");\n        let integer_value = Value::from_i64(1);\n        let float_key = Value::build_text(\"float_key\");\n        let float_value = Value::from_f64(1.1);\n        let null_key = Value::build_text(\"null_key\");\n        let null_value = Value::Null;\n\n        let input = [\n            text_key,\n            text_value,\n            json_key,\n            json_value,\n            integer_key,\n            integer_value,\n            float_key,\n            float_value,\n            null_key,\n            null_value,\n        ];\n\n        let result = json_object(&input).unwrap();\n        let Value::Text(json_text) = result else {\n            panic!(\"Expected Value::Text\");\n        };\n        assert_eq!(\n            json_text.as_str(),\n            r#\"{\"text_key\":\"text_value\",\"json_key\":{\"json\":\"value\",\"number\":1},\"integer_key\":1,\"float_key\":1.1,\"null_key\":null}\"#\n        );\n    }\n\n    #[test]\n    fn test_json_object_json_value_is_rendered_as_json() {\n        let key = Value::build_text(\"key\");\n        let value = Value::Text(Text::json(r#\"{\"json\":\"value\"}\"#.to_string()));\n        let input = [key, value];\n\n        let result = json_object(&input).unwrap();\n        let Value::Text(json_text) = result else {\n            panic!(\"Expected Value::Text\");\n        };\n        assert_eq!(json_text.as_str(), r#\"{\"key\":{\"json\":\"value\"}}\"#);\n    }\n\n    #[test]\n    fn test_json_object_json_text_value_is_rendered_as_regular_text() {\n        let key = Value::build_text(\"key\");\n        let value = Value::Text(Text::new(r#\"{\"json\":\"value\"}\"#));\n        let input = [key, value];\n\n        let result = json_object(&input).unwrap();\n        let Value::Text(json_text) = result else {\n            panic!(\"Expected Value::Text\");\n        };\n        assert_eq!(json_text.as_str(), r#\"{\"key\":\"{\\\"json\\\":\\\"value\\\"}\"}\"#);\n    }\n\n    #[test]\n    fn test_json_object_nested() {\n        let key = Value::build_text(\"key\");\n        let value = Value::build_text(\"value\");\n        let input = [key, value];\n\n        let parent_key = Value::build_text(\"parent_key\");\n        let parent_value = json_object(&input).unwrap();\n        let parent_input = [parent_key, parent_value];\n\n        let result = json_object(&parent_input).unwrap();\n\n        let Value::Text(json_text) = result else {\n            panic!(\"Expected Value::Text\");\n        };\n        assert_eq!(json_text.as_str(), r#\"{\"parent_key\":{\"key\":\"value\"}}\"#);\n    }\n\n    #[test]\n    fn test_json_object_duplicated_keys() {\n        let key = Value::build_text(\"key\");\n        let value = Value::build_text(\"value\");\n        let input = [key.clone(), value.clone(), key, value];\n\n        let result = json_object(&input).unwrap();\n        let Value::Text(json_text) = result else {\n            panic!(\"Expected Value::Text\");\n        };\n        assert_eq!(json_text.as_str(), r#\"{\"key\":\"value\",\"key\":\"value\"}\"#);\n    }\n\n    #[test]\n    fn test_json_object_empty() {\n        let input: [Value; 0] = [];\n\n        let result = json_object(&input).unwrap();\n        let Value::Text(json_text) = result else {\n            panic!(\"Expected Value::Text\");\n        };\n        assert_eq!(json_text.as_str(), r#\"{}\"#);\n    }\n\n    #[test]\n    fn test_json_object_non_text_key() {\n        let key = Value::from_i64(1);\n        let value = Value::build_text(\"value\");\n        let input = [key, value];\n\n        match json_object(&input) {\n            Ok(_) => panic!(\"Expected error for non-TEXT key\"),\n            Err(e) => assert!(e.to_string().contains(\"labels must be TEXT\")),\n        }\n    }\n\n    #[test]\n    fn test_json_odd_number_of_values() {\n        let key = Value::build_text(\"key\");\n        let value = Value::build_text(\"value\");\n        let input = [key.clone(), value, key];\n\n        assert!(json_object(&input).is_err());\n    }\n\n    #[test]\n    fn test_json_object_escapes_special_characters() {\n        let cases = [\n            // (key, value, expected_json)\n            (\"key\", r\"Hello\\World\", r#\"{\"key\":\"Hello\\\\World\"}\"#),\n            (\n                r\"key\\with\\backslash\",\n                \"value\",\n                r#\"{\"key\\\\with\\\\backslash\":\"value\"}\"#,\n            ),\n            (\"key\", \"Hello\\nWorld\", r#\"{\"key\":\"Hello\\nWorld\"}\"#),\n            (\"key\", \"Hello\\tWorld\", r#\"{\"key\":\"Hello\\tWorld\"}\"#),\n            (\"key\", \"Hello\\rWorld\", r#\"{\"key\":\"Hello\\rWorld\"}\"#),\n            (\"key\", \"Hello\\x01World\", r#\"{\"key\":\"Hello\\u0001World\"}\"#),\n            (\"key\", \"Hello\\x08\\x0cWorld\", r#\"{\"key\":\"Hello\\b\\fWorld\"}\"#),\n        ];\n\n        for (key, value, expected) in cases {\n            let input = [Value::build_text(key), Value::build_text(value)];\n            let result = json_object(&input).unwrap();\n            let Value::Text(json_text) = result else {\n                panic!(\"Expected Value::Text\");\n            };\n            assert_eq!(\n                json_text.as_str(),\n                expected,\n                \"Failed for key={key:?}, value={value:?}\"\n            );\n        }\n    }\n\n    #[test]\n    fn test_json_path_from_db_value_root_strict() {\n        let path = Value::Text(Text::new(\"$\"));\n\n        let result = json_path_from_db_value(&path, true);\n        assert!(result.is_ok());\n\n        let result = result.unwrap();\n        assert!(result.is_some());\n\n        let result = result.unwrap();\n        match result.elements[..] {\n            [PathElement::Root()] => {}\n            _ => panic!(\"Expected root\"),\n        }\n    }\n\n    #[test]\n    fn test_json_path_from_db_value_root_non_strict() {\n        let path = Value::Text(Text::new(\"$\"));\n\n        let result = json_path_from_db_value(&path, false);\n        assert!(result.is_ok());\n\n        let result = result.unwrap();\n        assert!(result.is_some());\n\n        let result = result.unwrap();\n        match result.elements[..] {\n            [PathElement::Root()] => {}\n            _ => panic!(\"Expected root\"),\n        }\n    }\n\n    #[test]\n    fn test_json_path_from_db_value_named_strict() {\n        let path = Value::Text(Text::new(\"field\"));\n\n        assert!(json_path_from_db_value(&path, true).is_err());\n    }\n\n    #[test]\n    fn test_json_path_from_db_value_named_non_strict() {\n        let path = Value::Text(Text::new(\"field\"));\n\n        let result = json_path_from_db_value(&path, false);\n        assert!(result.is_ok());\n\n        let result = result.unwrap();\n        assert!(result.is_some());\n\n        let result = result.unwrap();\n        match &result.elements[..] {\n            [PathElement::Root(), PathElement::Key(field, false)] if *field == \"field\" => {}\n            _ => panic!(\"Expected root and field\"),\n        }\n    }\n\n    #[test]\n    fn test_json_path_from_db_value_integer_strict() {\n        let path = Value::from_i64(3);\n        assert!(json_path_from_db_value(&path, true).is_err());\n    }\n\n    #[test]\n    fn test_json_path_from_db_value_integer_non_strict() {\n        let path = Value::from_i64(3);\n\n        let result = json_path_from_db_value(&path, false);\n        assert!(result.is_ok());\n\n        let result = result.unwrap();\n        assert!(result.is_some());\n\n        let result = result.unwrap();\n        match &result.elements[..] {\n            [PathElement::Root(), PathElement::ArrayLocator(index)] if *index == Some(3) => {}\n            _ => panic!(\"Expected root and array locator\"),\n        }\n    }\n\n    #[test]\n    fn test_json_path_from_db_value_null_strict() {\n        let path = Value::Null;\n\n        let result = json_path_from_db_value(&path, true);\n        assert!(result.is_ok());\n\n        let result = result.unwrap();\n        assert!(result.is_none());\n    }\n\n    #[test]\n    fn test_json_path_from_db_value_null_non_strict() {\n        let path = Value::Null;\n\n        let result = json_path_from_db_value(&path, false);\n        assert!(result.is_ok());\n\n        let result = result.unwrap();\n        assert!(result.is_none());\n    }\n\n    #[test]\n    fn test_json_path_from_db_value_float_strict() {\n        let path = Value::from_f64(1.23);\n\n        assert!(json_path_from_db_value(&path, true).is_err());\n    }\n\n    #[test]\n    fn test_json_path_from_db_value_float_non_strict() {\n        let path = Value::from_f64(1.23);\n\n        let result = json_path_from_db_value(&path, false);\n        assert!(result.is_ok());\n\n        let result = result.unwrap();\n        assert!(result.is_some());\n\n        let result = result.unwrap();\n        match &result.elements[..] {\n            [PathElement::Root(), PathElement::Key(field, false)] if *field == \"1.23\" => {}\n            _ => panic!(\"Expected root and field\"),\n        }\n    }\n\n    #[test]\n    fn test_json_set_field_empty_object() {\n        let json_cache = JsonCacheCell::new();\n        let result = json_set(\n            &[\n                Value::build_text(\"{}\"),\n                Value::build_text(\"$.field\"),\n                Value::build_text(\"value\"),\n            ],\n            &json_cache,\n        );\n\n        assert!(result.is_ok());\n\n        assert_eq!(result.unwrap().to_text().unwrap(), r#\"{\"field\":\"value\"}\"#);\n    }\n\n    #[test]\n    fn test_json_set_replace_field() {\n        let json_cache = JsonCacheCell::new();\n        let result = json_set(\n            &[\n                Value::build_text(r#\"{\"field\":\"old_value\"}\"#),\n                Value::build_text(\"$.field\"),\n                Value::build_text(\"new_value\"),\n            ],\n            &json_cache,\n        );\n\n        assert!(result.is_ok());\n\n        assert_eq!(\n            result.unwrap().to_text().unwrap(),\n            r#\"{\"field\":\"new_value\"}\"#\n        );\n    }\n\n    #[test]\n    fn test_json_set_set_deeply_nested_key() {\n        let json_cache = JsonCacheCell::new();\n        let result = json_set(\n            &[\n                Value::build_text(\"{}\"),\n                Value::build_text(\"$.object.doesnt.exist\"),\n                Value::build_text(\"value\"),\n            ],\n            &json_cache,\n        );\n\n        assert!(result.is_ok());\n\n        assert_eq!(\n            result.unwrap().to_text().unwrap(),\n            r#\"{\"object\":{\"doesnt\":{\"exist\":\"value\"}}}\"#\n        );\n    }\n\n    #[test]\n    fn test_json_set_add_value_to_empty_array() {\n        let json_cache = JsonCacheCell::new();\n        let result = json_set(\n            &[\n                Value::build_text(\"[]\"),\n                Value::build_text(\"$[0]\"),\n                Value::build_text(\"value\"),\n            ],\n            &json_cache,\n        );\n\n        assert!(result.is_ok());\n\n        assert_eq!(result.unwrap().to_text().unwrap(), r#\"[\"value\"]\"#);\n    }\n\n    #[test]\n    fn test_json_set_add_value_to_nonexistent_array() {\n        let json_cache = JsonCacheCell::new();\n        let result = json_set(\n            &[\n                Value::build_text(\"{}\"),\n                Value::build_text(\"$.some_array[0]\"),\n                Value::from_i64(123),\n            ],\n            &json_cache,\n        );\n\n        assert!(result.is_ok());\n\n        assert_eq!(\n            result.unwrap().to_text().unwrap(),\n            r#\"{\"some_array\":[123]}\"#\n        );\n    }\n\n    #[test]\n    fn test_json_set_add_value_to_array() {\n        let json_cache = JsonCacheCell::new();\n        let result = json_set(\n            &[\n                Value::build_text(\"[123]\"),\n                Value::build_text(\"$[1]\"),\n                Value::from_i64(456),\n            ],\n            &json_cache,\n        );\n\n        assert!(result.is_ok());\n\n        assert_eq!(result.unwrap().to_text().unwrap(), \"[123,456]\");\n    }\n\n    #[test]\n    fn test_json_set_add_value_to_array_out_of_bounds() {\n        let json_cache = JsonCacheCell::new();\n        let result = json_set(\n            &[\n                Value::build_text(\"[123]\"),\n                Value::build_text(\"$[200]\"),\n                Value::from_i64(456),\n            ],\n            &json_cache,\n        );\n\n        assert!(result.is_ok());\n\n        assert_eq!(result.unwrap().to_text().unwrap(), \"[123]\");\n    }\n\n    #[test]\n    fn test_json_set_replace_value_in_array() {\n        let json_cache = JsonCacheCell::new();\n        let result = json_set(\n            &[\n                Value::build_text(\"[123]\"),\n                Value::build_text(\"$[0]\"),\n                Value::from_i64(456),\n            ],\n            &json_cache,\n        );\n\n        assert!(result.is_ok());\n\n        assert_eq!(result.unwrap().to_text().unwrap(), \"[456]\");\n    }\n\n    #[test]\n    fn test_json_set_null_path() {\n        let json_cache = JsonCacheCell::new();\n        let result = json_set(\n            &[Value::build_text(\"{}\"), Value::Null, Value::from_i64(456)],\n            &json_cache,\n        );\n\n        assert!(result.is_ok());\n\n        assert_eq!(result.unwrap().to_text().unwrap(), \"{}\");\n    }\n\n    #[test]\n    fn test_json_set_multiple_keys() {\n        let json_cache = JsonCacheCell::new();\n        let result = json_set(\n            &[\n                Value::build_text(\"[123]\"),\n                Value::build_text(\"$[0]\"),\n                Value::from_i64(456),\n                Value::build_text(\"$[1]\"),\n                Value::from_i64(789),\n            ],\n            &json_cache,\n        );\n\n        assert!(result.is_ok());\n\n        assert_eq!(result.unwrap().to_text().unwrap(), \"[456,789]\");\n    }\n\n    #[test]\n    fn test_json_set_add_array_in_nested_object() {\n        let json_cache = JsonCacheCell::new();\n        let result = json_set(\n            &[\n                Value::build_text(\"{}\"),\n                Value::build_text(\"$.object[0].field\"),\n                Value::from_i64(123),\n            ],\n            &json_cache,\n        );\n\n        assert!(result.is_ok());\n\n        assert_eq!(\n            result.unwrap().to_text().unwrap(),\n            r#\"{\"object\":[{\"field\":123}]}\"#\n        );\n    }\n\n    #[test]\n    fn test_json_set_add_array_in_array_in_nested_object() {\n        let json_cache = JsonCacheCell::new();\n        let result = json_set(\n            &[\n                Value::build_text(\"{}\"),\n                Value::build_text(\"$.object[0][0]\"),\n                Value::from_i64(123),\n            ],\n            &json_cache,\n        );\n\n        assert!(result.is_ok());\n\n        assert_eq!(result.unwrap().to_text().unwrap(), r#\"{\"object\":[[123]]}\"#);\n    }\n\n    #[test]\n    fn test_json_set_add_array_in_array_in_nested_object_out_of_bounds() {\n        let json_cache = JsonCacheCell::new();\n        let result = json_set(\n            &[\n                Value::build_text(\"{}\"),\n                Value::build_text(\"$.object[123].another\"),\n                Value::build_text(\"value\"),\n                Value::build_text(\"$.field\"),\n                Value::build_text(\"value\"),\n            ],\n            &json_cache,\n        );\n\n        assert!(result.is_ok());\n\n        assert_eq!(result.unwrap().to_text().unwrap(), r#\"{\"field\":\"value\"}\"#,);\n    }\n\n    #[test]\n    fn test_is_jsonb_blob_rejects_scalar_like_overlap_header() {\n        let overlapping_scalar = b\"|1234567\";\n        assert_eq!(overlapping_scalar.len(), JSONB_AMBIGUOUS_PAYLOAD_MAX + 1);\n        assert!(!is_jsonb_blob(overlapping_scalar));\n    }\n}\n"
  },
  {
    "path": "core/json/ops.rs",
    "content": "use super::{\n    convert_dbtype_to_jsonb, curry_convert_dbtype_to_jsonb, json_path_from_db_value,\n    json_string_to_db_type,\n    jsonb::{DeleteOperation, InsertOperation, ReplaceOperation},\n    Conv, JsonCacheCell, OutputVariant,\n};\nuse crate::{\n    types::{AsValueRef, Text, Value},\n    ValueRef,\n};\n\n/// The function follows RFC 7386 JSON Merge Patch semantics:\n/// * If the patch is null, the target is replaced with null\n/// * If the patch contains a scalar value, the target is replaced with that value\n/// * If both target and patch are objects, the patch is recursively applied\n/// * null values in the patch result in property removal from the target\npub fn json_patch(\n    target: impl AsValueRef,\n    patch: impl AsValueRef,\n    cache: &JsonCacheCell,\n) -> crate::Result<Value> {\n    let (target, patch) = (target.as_value_ref(), patch.as_value_ref());\n    match (target, patch) {\n        (ValueRef::Null, _) | (_, ValueRef::Null) => return Ok(Value::Null),\n        (ValueRef::Blob(_), _) | (_, ValueRef::Blob(_)) => {\n            crate::bail_constraint_error!(\"blob is not supported!\");\n        }\n        // Explicit handling for the case json_path('{}', 'null') case. If the patch value is\n        // the text null, the result will also be the text null. No extra parsing required.\n        (_, ValueRef::Text(t)) => {\n            if t.value == \"null\" {\n                return Ok(Value::Text(Text::new(\"null\")));\n            }\n        }\n        _ => (),\n    }\n    let make_jsonb = curry_convert_dbtype_to_jsonb(Conv::Strict);\n    let mut target = cache.get_or_insert_with(target, make_jsonb)?;\n    let make_jsonb = curry_convert_dbtype_to_jsonb(Conv::Strict);\n    let patch = cache.get_or_insert_with(patch, make_jsonb)?;\n\n    if patch.element_type()? != super::jsonb::ElementType::OBJECT {\n        target = patch;\n    } else {\n        if target.element_type()? != super::jsonb::ElementType::OBJECT {\n            target = super::jsonb::Jsonb::make_empty_obj(0);\n        }\n        target.patch(&patch)?;\n    }\n\n    let element_type = target.element_type()?;\n\n    json_string_to_db_type(target, element_type, OutputVariant::String)\n}\n\npub fn jsonb_patch(\n    target: impl AsValueRef,\n    patch: impl AsValueRef,\n    cache: &JsonCacheCell,\n) -> crate::Result<Value> {\n    let (target, patch) = (target.as_value_ref(), patch.as_value_ref());\n    match (target, patch) {\n        (ValueRef::Null, _) | (_, ValueRef::Null) => return Ok(Value::Null),\n        (ValueRef::Blob(_), _) | (_, ValueRef::Blob(_)) => {\n            crate::bail_constraint_error!(\"blob is not supported!\");\n        }\n        _ => (),\n    }\n    let make_jsonb = curry_convert_dbtype_to_jsonb(Conv::Strict);\n    let mut target = cache.get_or_insert_with(target, make_jsonb)?;\n    let make_jsonb = curry_convert_dbtype_to_jsonb(Conv::Strict);\n    let patch = cache.get_or_insert_with(patch, make_jsonb)?;\n\n    if patch.element_type()? != super::jsonb::ElementType::OBJECT {\n        target = patch;\n    } else {\n        if target.element_type()? != super::jsonb::ElementType::OBJECT {\n            target = super::jsonb::Jsonb::make_empty_obj(0);\n        }\n        target.patch(&patch)?;\n    }\n\n    let element_type = target.element_type()?;\n\n    json_string_to_db_type(target, element_type, OutputVariant::Binary)\n}\n\npub fn json_remove<I, E, V>(args: I, json_cache: &JsonCacheCell) -> crate::Result<Value>\nwhere\n    V: AsValueRef,\n    E: ExactSizeIterator<Item = V>,\n    I: IntoIterator<IntoIter = E, Item = V>,\n{\n    let mut args = args.into_iter();\n    if args.len() == 0 {\n        return Ok(Value::Null);\n    }\n\n    let make_jsonb_fn = curry_convert_dbtype_to_jsonb(Conv::Strict);\n    let first_arg = args.next().ok_or_else(|| {\n        crate::LimboError::InternalError(\"args should not be empty after length check\".to_string())\n    })?;\n    let mut json = json_cache.get_or_insert_with(first_arg, make_jsonb_fn)?;\n    for arg in args {\n        if let ValueRef::Text(s) = arg.as_value_ref() {\n            if s.as_str() == \"$\" {\n                return Ok(Value::Null);\n            }\n        }\n        if let Some(path) = json_path_from_db_value(&arg, true)? {\n            let mut op = DeleteOperation::new();\n            let _ = json.operate_on_path(&path, &mut op);\n        }\n    }\n\n    let el_type = json.element_type()?;\n\n    json_string_to_db_type(json, el_type, OutputVariant::String)\n}\n\npub fn jsonb_remove<I, E, V>(args: I, json_cache: &JsonCacheCell) -> crate::Result<Value>\nwhere\n    V: AsValueRef,\n    E: ExactSizeIterator<Item = V>,\n    I: IntoIterator<IntoIter = E, Item = V>,\n{\n    let mut args = args.into_iter();\n    if args.len() == 0 {\n        return Ok(Value::Null);\n    }\n\n    let make_jsonb_fn = curry_convert_dbtype_to_jsonb(Conv::Strict);\n    let first_arg = args.next().ok_or_else(|| {\n        crate::LimboError::InternalError(\"args should not be empty after length check\".to_string())\n    })?;\n    let mut json = json_cache.get_or_insert_with(first_arg, make_jsonb_fn)?;\n    for arg in args {\n        if let ValueRef::Text(s) = arg.as_value_ref() {\n            if s.as_str() == \"$\" {\n                return Ok(Value::Null);\n            }\n        }\n        if let Some(path) = json_path_from_db_value(&arg, true)? {\n            let mut op = DeleteOperation::new();\n            let _ = json.operate_on_path(&path, &mut op);\n        }\n    }\n\n    Ok(Value::Blob(json.data()))\n}\n\npub fn json_replace<I, E, V>(args: I, json_cache: &JsonCacheCell) -> crate::Result<Value>\nwhere\n    V: AsValueRef,\n    E: ExactSizeIterator<Item = V>,\n    I: IntoIterator<IntoIter = E, Item = V>,\n{\n    let mut args = args.into_iter();\n    if args.len() == 0 {\n        return Ok(Value::Null);\n    }\n\n    let make_jsonb_fn = curry_convert_dbtype_to_jsonb(Conv::Strict);\n    let first_arg = args.next().ok_or_else(|| {\n        crate::LimboError::InternalError(\"args should not be empty after length check\".to_string())\n    })?;\n    let mut json = json_cache.get_or_insert_with(first_arg, make_jsonb_fn)?;\n    // TODO: when `array_chunks` is stabilized we can chunk by 2 here\n    while args.len() > 1 {\n        let first = args.next().ok_or_else(|| {\n            crate::LimboError::InternalError(\n                \"args should have at least 2 elements in loop\".to_string(),\n            )\n        })?;\n        let path = json_path_from_db_value(&first, true)?;\n\n        let second = args.next().ok_or_else(|| {\n            crate::LimboError::InternalError(\"args should have second element in loop\".to_string())\n        })?;\n        let value = convert_dbtype_to_jsonb(&second, Conv::NotStrict)?;\n        if let Some(path) = path {\n            let mut op = ReplaceOperation::new(value);\n\n            let _ = json.operate_on_path(&path, &mut op);\n        }\n    }\n\n    let el_type = json.element_type()?;\n\n    json_string_to_db_type(json, el_type, super::OutputVariant::String)\n}\n\npub fn jsonb_replace<I, E, V>(args: I, json_cache: &JsonCacheCell) -> crate::Result<Value>\nwhere\n    V: AsValueRef,\n    E: ExactSizeIterator<Item = V>,\n    I: IntoIterator<IntoIter = E, Item = V>,\n{\n    let mut args = args.into_iter();\n    if args.len() == 0 {\n        return Ok(Value::Null);\n    }\n\n    let make_jsonb_fn = curry_convert_dbtype_to_jsonb(Conv::Strict);\n    let first_arg = args.next().ok_or_else(|| {\n        crate::LimboError::InternalError(\"args should not be empty after length check\".to_string())\n    })?;\n    let mut json = json_cache.get_or_insert_with(first_arg, make_jsonb_fn)?;\n    // TODO: when `array_chunks` is stabilized we can chunk by 2 here\n    while args.len() > 1 {\n        let first = args.next().ok_or_else(|| {\n            crate::LimboError::InternalError(\n                \"args should have at least 2 elements in loop\".to_string(),\n            )\n        })?;\n        let path = json_path_from_db_value(&first, true)?;\n\n        let second = args.next().ok_or_else(|| {\n            crate::LimboError::InternalError(\"args should have second element in loop\".to_string())\n        })?;\n        let value = convert_dbtype_to_jsonb(&second, Conv::NotStrict)?;\n        if let Some(path) = path {\n            let mut op = ReplaceOperation::new(value);\n\n            let _ = json.operate_on_path(&path, &mut op);\n        }\n    }\n\n    let el_type = json.element_type()?;\n\n    json_string_to_db_type(json, el_type, OutputVariant::Binary)\n}\n\npub fn json_insert<I, E, V>(args: I, json_cache: &JsonCacheCell) -> crate::Result<Value>\nwhere\n    V: AsValueRef,\n    E: ExactSizeIterator<Item = V>,\n    I: IntoIterator<IntoIter = E, Item = V>,\n{\n    let mut args = args.into_iter();\n    if args.len() == 0 {\n        return Ok(Value::Null);\n    }\n\n    let make_jsonb_fn = curry_convert_dbtype_to_jsonb(Conv::Strict);\n    let first_arg = args.next().ok_or_else(|| {\n        crate::LimboError::InternalError(\"args should not be empty after length check\".to_string())\n    })?;\n    let mut json = json_cache.get_or_insert_with(first_arg, make_jsonb_fn)?;\n\n    // TODO: when `array_chunks` is stabilized we can chunk by 2 here\n    while args.len() > 1 {\n        let first = args.next().ok_or_else(|| {\n            crate::LimboError::InternalError(\n                \"args should have at least 2 elements in loop\".to_string(),\n            )\n        })?;\n        let path = json_path_from_db_value(&first, true)?;\n\n        let second = args.next().ok_or_else(|| {\n            crate::LimboError::InternalError(\"args should have second element in loop\".to_string())\n        })?;\n        let value = convert_dbtype_to_jsonb(&second, Conv::NotStrict)?;\n        if let Some(path) = path {\n            let mut op = InsertOperation::new(value);\n\n            let _ = json.operate_on_path(&path, &mut op);\n        }\n    }\n\n    let el_type = json.element_type()?;\n\n    json_string_to_db_type(json, el_type, OutputVariant::String)\n}\n\npub fn jsonb_insert<I, E, V>(args: I, json_cache: &JsonCacheCell) -> crate::Result<Value>\nwhere\n    V: AsValueRef,\n    E: ExactSizeIterator<Item = V>,\n    I: IntoIterator<IntoIter = E, Item = V>,\n{\n    let mut args = args.into_iter();\n    if args.len() == 0 {\n        return Ok(Value::Null);\n    }\n\n    let make_jsonb_fn = curry_convert_dbtype_to_jsonb(Conv::Strict);\n    let first_arg = args.next().ok_or_else(|| {\n        crate::LimboError::InternalError(\"args should not be empty after length check\".to_string())\n    })?;\n    let mut json = json_cache.get_or_insert_with(first_arg, make_jsonb_fn)?;\n\n    // TODO: when `array_chunks` is stabilized we can chunk by 2 here\n    while args.len() > 1 {\n        let first = args.next().ok_or_else(|| {\n            crate::LimboError::InternalError(\n                \"args should have at least 2 elements in loop\".to_string(),\n            )\n        })?;\n        let path = json_path_from_db_value(&first, true)?;\n\n        let second = args.next().ok_or_else(|| {\n            crate::LimboError::InternalError(\"args should have second element in loop\".to_string())\n        })?;\n        let value = convert_dbtype_to_jsonb(&second, Conv::NotStrict)?;\n        if let Some(path) = path {\n            let mut op = InsertOperation::new(value);\n\n            let _ = json.operate_on_path(&path, &mut op);\n        }\n    }\n\n    let el_type = json.element_type()?;\n\n    json_string_to_db_type(json, el_type, OutputVariant::Binary)\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::types::Text;\n\n    use super::*;\n\n    fn create_text(s: &str) -> Value {\n        Value::Text(s.into())\n    }\n\n    fn create_json(s: &str) -> Value {\n        Value::Text(Text::json(s.to_string()))\n    }\n\n    #[test]\n    fn test_basic_text_replacement() {\n        let target = create_text(r#\"{\"name\":\"John\",\"age\":\"30\"}\"#);\n        let patch = create_text(r#\"{\"age\":\"31\"}\"#);\n        let cache = JsonCacheCell::new();\n\n        let result = json_patch(&target, &patch, &cache).unwrap();\n        assert_eq!(result, create_json(r#\"{\"name\":\"John\",\"age\":\"31\"}\"#));\n    }\n\n    #[test]\n    fn test_null_field_removal() {\n        let target = create_text(r#\"{\"name\":\"John\",\"email\":\"john@example.com\"}\"#);\n        let patch = create_text(r#\"{\"email\":null}\"#);\n        let cache = JsonCacheCell::new();\n\n        let result = json_patch(&target, &patch, &cache).unwrap();\n        assert_eq!(result, create_json(r#\"{\"name\":\"John\"}\"#));\n    }\n\n    #[test]\n    fn test_nested_object_merge() {\n        let target =\n            create_text(r#\"{\"user\":{\"name\":\"John\",\"details\":{\"age\":\"30\",\"score\":\"95.5\"}}}\"#);\n\n        let patch = create_text(r#\"{\"user\":{\"details\":{\"score\":\"97.5\"}}}\"#);\n        let cache = JsonCacheCell::new();\n\n        let result = json_patch(&target, &patch, &cache).unwrap();\n        assert_eq!(\n            result,\n            create_json(r#\"{\"user\":{\"name\":\"John\",\"details\":{\"age\":\"30\",\"score\":\"97.5\"}}}\"#)\n        );\n    }\n\n    #[test]\n    #[should_panic(expected = \"blob is not supported!\")]\n    fn test_blob_not_supported() {\n        let target = Value::Blob(vec![1, 2, 3]);\n        let patch = create_text(\"{}\");\n        let cache = JsonCacheCell::new();\n\n        json_patch(&target, &patch, &cache).unwrap();\n    }\n\n    #[test]\n    fn test_deep_null_replacement() {\n        let target = create_text(r#\"{\"level1\":{\"level2\":{\"keep\":\"value\",\"remove\":\"value\"}}}\"#);\n\n        let patch = create_text(r#\"{\"level1\":{\"level2\":{\"remove\":null}}}\"#);\n        let cache = JsonCacheCell::new();\n\n        let result = json_patch(&target, &patch, &cache).unwrap();\n        assert_eq!(\n            result,\n            create_json(r#\"{\"level1\":{\"level2\":{\"keep\":\"value\"}}}\"#)\n        );\n    }\n\n    #[test]\n    fn test_empty_patch() {\n        let target = create_json(r#\"{\"name\":\"John\",\"age\":\"30\"}\"#);\n        let patch = create_text(\"{}\");\n        let cache = JsonCacheCell::new();\n\n        let result = json_patch(&target, &patch, &cache).unwrap();\n        assert_eq!(result, target);\n    }\n\n    #[test]\n    fn test_add_new_field() {\n        let target = create_text(r#\"{\"existing\":\"value\"}\"#);\n        let patch = create_text(r#\"{\"new\":\"field\"}\"#);\n        let cache = JsonCacheCell::new();\n\n        let result = json_patch(&target, &patch, &cache).unwrap();\n        assert_eq!(result, create_json(r#\"{\"existing\":\"value\",\"new\":\"field\"}\"#));\n    }\n\n    #[test]\n    fn test_complete_object_replacement() {\n        let target = create_text(r#\"{\"old\":{\"nested\":\"value\"}}\"#);\n        let patch = create_text(r#\"{\"old\":\"new_value\"}\"#);\n        let cache = JsonCacheCell::new();\n\n        let result = json_patch(&target, &patch, &cache).unwrap();\n        assert_eq!(result, create_json(r#\"{\"old\":\"new_value\"}\"#));\n    }\n\n    #[test]\n    fn test_json_remove_empty_args() {\n        let args: [Value; 0] = [];\n        let json_cache = JsonCacheCell::new();\n        assert_eq!(json_remove(&args, &json_cache).unwrap(), Value::Null);\n    }\n\n    #[test]\n    fn test_json_remove_array_element() {\n        let args = [create_json(r#\"[1,2,3,4,5]\"#), create_text(\"$[2]\")];\n\n        let json_cache = JsonCacheCell::new();\n        let result = json_remove(&args, &json_cache).unwrap();\n        match result {\n            Value::Text(t) => assert_eq!(t.as_str(), \"[1,2,4,5]\"),\n            _ => panic!(\"Expected Text value\"),\n        }\n    }\n\n    #[test]\n    fn test_json_remove_multiple_paths() {\n        let args = [\n            create_json(r#\"{\"a\": 1, \"b\": 2, \"c\": 3}\"#),\n            create_text(\"$.a\"),\n            create_text(\"$.c\"),\n        ];\n\n        let json_cache = JsonCacheCell::new();\n        let result = json_remove(&args, &json_cache).unwrap();\n        match result {\n            Value::Text(t) => assert_eq!(t.as_str(), r#\"{\"b\":2}\"#),\n            _ => panic!(\"Expected Text value\"),\n        }\n    }\n\n    #[test]\n    fn test_json_remove_nested_paths() {\n        let args = [\n            create_json(r#\"{\"a\": {\"b\": {\"c\": 1, \"d\": 2}}}\"#),\n            create_text(\"$.a.b.c\"),\n        ];\n\n        let json_cache = JsonCacheCell::new();\n        let result = json_remove(&args, &json_cache).unwrap();\n        match result {\n            Value::Text(t) => assert_eq!(t.as_str(), r#\"{\"a\":{\"b\":{\"d\":2}}}\"#),\n            _ => panic!(\"Expected Text value\"),\n        }\n    }\n\n    #[test]\n    fn test_json_remove_duplicate_keys() {\n        let args = [\n            create_json(r#\"{\"a\": 1, \"a\": 2, \"a\": 3}\"#),\n            create_text(\"$.a\"),\n        ];\n\n        let json_cache = JsonCacheCell::new();\n        let result = json_remove(&args, &json_cache).unwrap();\n        match result {\n            Value::Text(t) => assert_eq!(t.as_str(), r#\"{\"a\":2,\"a\":3}\"#),\n            _ => panic!(\"Expected Text value\"),\n        }\n    }\n\n    #[test]\n    fn test_json_remove_invalid_path() {\n        let args = [\n            create_json(r#\"{\"a\": 1}\"#),\n            Value::from_i64(42), // Invalid path type\n        ];\n\n        let json_cache = JsonCacheCell::new();\n        assert!(json_remove(&args, &json_cache).is_err());\n    }\n\n    #[test]\n    fn test_json_remove_complex_case() {\n        let args = [\n            create_json(r#\"{\"a\":[1,2,3],\"b\":{\"x\":1,\"x\":2},\"c\":[{\"y\":1},{\"y\":2}]}\"#),\n            create_text(\"$.a[1]\"),\n            create_text(\"$.b.x\"),\n            create_text(\"$.c[0].y\"),\n        ];\n\n        let json_cache = JsonCacheCell::new();\n        let result = json_remove(&args, &json_cache).unwrap();\n        match result {\n            Value::Text(t) => {\n                let value = t.as_str();\n                assert!(value.contains(r#\"[1,3]\"#));\n                assert!(value.contains(r#\"{\"x\":2}\"#));\n            }\n            _ => panic!(\"Expected Text value\"),\n        }\n    }\n}\n"
  },
  {
    "path": "core/json/path.rs",
    "content": "use crate::bail_parse_error;\nuse std::borrow::Cow;\n\n#[derive(Clone, Debug, PartialEq)]\nenum PPState {\n    Start,\n    AfterRoot,\n    InKey,\n    InArrayIndex,\n    ExpectDotOrBracket,\n}\n\n#[derive(Clone, Debug, PartialEq)]\nenum ArrayIndexState {\n    Start,\n    AfterHash,\n    CollectingNumbers,\n    IsMax,\n}\n\n/// Describes a JSON path, which is a sequence of keys and/or array locators.\n#[derive(Clone, Debug)]\npub struct JsonPath<'a> {\n    pub elements: Vec<PathElement<'a>>,\n}\n\ntype RawString = bool;\n\n/// PathElement describes a single element of a JSON path.\n#[derive(Clone, Debug, PartialEq)]\npub enum PathElement<'a> {\n    /// Root element: '$'\n    Root(),\n    /// JSON key\n    Key(Cow<'a, str>, RawString),\n    /// Array locator, eg. [2], [#-5]\n    ArrayLocator(Option<i32>),\n}\n\ntype IsMaxNumber = bool;\n\nfn collect_num(current: i128, adding: i128, negative: bool) -> (i128, IsMaxNumber) {\n    let ten = 10i128;\n\n    let result = if negative {\n        current.saturating_mul(ten).saturating_sub(adding)\n    } else {\n        current.saturating_mul(ten).saturating_add(adding)\n    };\n\n    let is_max = result == i128::MAX || result == i128::MIN;\n    (result, is_max)\n}\n\nfn estimate_path_capacity(input: &str) -> usize {\n    // After $ we need either . or [ for each component\n    // So divide remaining length by 2 (minimum chars per component)\n    // Add 1 for the root component\n    1 + (input.len() - 1) / 2\n}\n\n/// Parses path into a Vec of Strings, where each string is a key or an array locator.\npub fn json_path(path: &str) -> crate::Result<JsonPath<'_>> {\n    if path.is_empty() {\n        bail_parse_error!(\"Bad json path: {}\", path)\n    }\n    let mut parser_state = PPState::Start;\n    let mut index_state = ArrayIndexState::Start;\n    let mut key_start = 0;\n    let mut index_buffer: i128 = 0;\n    let mut path_components = Vec::with_capacity(estimate_path_capacity(path));\n    let mut path_iter = path.char_indices();\n\n    while let Some(ch) = path_iter.next() {\n        match parser_state {\n            PPState::Start => {\n                handle_start(ch, &mut parser_state, &mut path_components, path)?;\n            }\n            PPState::AfterRoot => {\n                handle_after_root(\n                    ch,\n                    &mut parser_state,\n                    &mut index_state,\n                    &mut key_start,\n                    &mut index_buffer,\n                    path,\n                )?;\n            }\n            PPState::InKey => {\n                handle_in_key(\n                    ch,\n                    &mut parser_state,\n                    &mut index_state,\n                    &mut key_start,\n                    &mut index_buffer,\n                    &mut path_components,\n                    &mut path_iter,\n                    path,\n                )?;\n            }\n            PPState::InArrayIndex => {\n                handle_array_index(\n                    ch,\n                    &mut parser_state,\n                    &mut index_state,\n                    &mut index_buffer,\n                    &mut path_components,\n                    &mut path_iter,\n                    path,\n                )?;\n            }\n            PPState::ExpectDotOrBracket => {\n                handle_expect_dot_or_bracket(\n                    ch,\n                    &mut parser_state,\n                    &mut index_state,\n                    &mut key_start,\n                    &mut index_buffer,\n                    path,\n                )?;\n            }\n        }\n    }\n\n    finalize_path(parser_state, key_start, path, &mut path_components)?;\n    Ok(JsonPath {\n        elements: path_components,\n    })\n}\n\nfn handle_start(\n    ch: (usize, char),\n    parser_state: &mut PPState,\n    path_components: &mut Vec<PathElement>,\n    path: &str,\n) -> crate::Result<()> {\n    match ch {\n        (_, '$') => {\n            path_components.push(PathElement::Root());\n            *parser_state = PPState::AfterRoot;\n            Ok(())\n        }\n        (_, _) => bail_parse_error!(\"Bad json path: {}\", path),\n    }\n}\n\nfn handle_after_root(\n    ch: (usize, char),\n    parser_state: &mut PPState,\n    index_state: &mut ArrayIndexState,\n    key_start: &mut usize,\n    index_buffer: &mut i128,\n    path: &str,\n) -> crate::Result<()> {\n    match ch {\n        (idx, '.') => {\n            *parser_state = PPState::InKey;\n            *key_start = idx + ch.1.len_utf8();\n            Ok(())\n        }\n        (_, '[') => {\n            *index_state = ArrayIndexState::Start;\n            *parser_state = PPState::InArrayIndex;\n            *index_buffer = 0;\n            Ok(())\n        }\n        (_, _) => bail_parse_error!(\"Bad json path: {}\", path),\n    }\n}\n\n#[allow(clippy::too_many_arguments)]\nfn handle_in_key<'a>(\n    ch: (usize, char),\n    parser_state: &mut PPState,\n    index_state: &mut ArrayIndexState,\n    key_start: &mut usize,\n    index_buffer: &mut i128,\n    path_components: &mut Vec<PathElement<'a>>,\n    path_iter: &mut std::str::CharIndices,\n    path: &'a str,\n) -> crate::Result<()> {\n    match ch {\n        (idx, '.' | '[') => {\n            let key_end = idx;\n            if key_end > *key_start {\n                let key = &path[*key_start..key_end];\n                if ch.1 == '[' {\n                    *index_state = ArrayIndexState::Start;\n                    *parser_state = PPState::InArrayIndex;\n                    *index_buffer = 0;\n                } else {\n                    *key_start = idx + ch.1.len_utf8();\n                }\n                path_components.push(PathElement::Key(Cow::Borrowed(key), false));\n            } else {\n                bail_parse_error!(\"Bad json path: {}\", path)\n            }\n        }\n        (idx, '\"') => {\n            handle_quoted_key(parser_state, idx, path_components, path_iter, path)?;\n        }\n        (_, _) => (),\n    }\n    Ok(())\n}\n\nfn handle_quoted_key<'a>(\n    parser_state: &mut PPState,\n    quote_start: usize,\n    path_components: &mut Vec<PathElement<'a>>,\n    path_iter: &mut std::str::CharIndices,\n    path: &'a str,\n) -> crate::Result<()> {\n    // quote_start is the byte index of the opening '\"'\n    // The key content starts after the opening quote (1 byte for '\"')\n    let key_content_start = quote_start + 1;\n    while let Some((idx, ch)) = path_iter.next() {\n        match ch {\n            '\\\\' => {\n                path_iter.next();\n            }\n            '\"' => {\n                if key_content_start <= idx {\n                    let key = &path[key_content_start..idx];\n                    path_components.push(PathElement::Key(Cow::Borrowed(key), true));\n                    *parser_state = PPState::ExpectDotOrBracket;\n                    return Ok(());\n                }\n            }\n            _ => continue,\n        }\n    }\n    Ok(())\n}\n\nfn handle_array_index(\n    ch: (usize, char),\n    parser_state: &mut PPState,\n    index_state: &mut ArrayIndexState,\n    index_buffer: &mut i128,\n    path_components: &mut Vec<PathElement<'_>>,\n    path_iter: &mut std::str::CharIndices,\n    path: &str,\n) -> crate::Result<()> {\n    match (&index_state, ch.1) {\n        (ArrayIndexState::Start, '#') => {\n            *index_state = ArrayIndexState::AfterHash;\n        }\n        (ArrayIndexState::Start, '0'..='9') => {\n            *index_buffer = ch.1.to_digit(10).ok_or_else(|| {\n                crate::LimboError::ParseError(format!(\"failed to parse digit: {ch}\", ch = ch.1))\n            })? as i128;\n            *index_state = ArrayIndexState::CollectingNumbers;\n        }\n        (ArrayIndexState::AfterHash, '-') => {\n            handle_negative_index(index_state, index_buffer, path_iter, path)?;\n        }\n        (ArrayIndexState::AfterHash, ']') => {\n            *parser_state = PPState::ExpectDotOrBracket;\n            path_components.push(PathElement::ArrayLocator(None));\n        }\n        (ArrayIndexState::CollectingNumbers, '0'..='9') => {\n            let (new_num, is_max) = collect_num(\n                *index_buffer,\n                ch.1.to_digit(10).ok_or_else(|| {\n                    crate::LimboError::ParseError(format!(\"failed to parse digit: {ch}\", ch = ch.1))\n                })? as i128,\n                *index_buffer < 0,\n            );\n            if is_max {\n                *index_state = ArrayIndexState::IsMax;\n            }\n            *index_buffer = new_num;\n        }\n        (ArrayIndexState::IsMax, '0'..='9') => (),\n        (ArrayIndexState::CollectingNumbers | ArrayIndexState::IsMax, ']') => {\n            *parser_state = PPState::ExpectDotOrBracket;\n            path_components.push(PathElement::ArrayLocator(Some(*index_buffer as i32)));\n        }\n        (_, _) => bail_parse_error!(\"Bad json path: {}\", path),\n    }\n    Ok(())\n}\n\nfn handle_negative_index(\n    index_state: &mut ArrayIndexState,\n    index_buffer: &mut i128,\n    path_iter: &mut std::str::CharIndices,\n    path: &str,\n) -> crate::Result<()> {\n    if let Some((_, next_c)) = path_iter.next() {\n        if next_c.is_ascii_digit() {\n            *index_buffer = -(next_c.to_digit(10).ok_or_else(|| {\n                crate::LimboError::ParseError(format!(\"failed to parse digit: {next_c}\"))\n            })? as i128);\n            *index_state = ArrayIndexState::CollectingNumbers;\n            Ok(())\n        } else {\n            bail_parse_error!(\"Bad json path: {}\", path)\n        }\n    } else {\n        bail_parse_error!(\"Bad json path: {}\", path)\n    }\n}\n\nfn handle_expect_dot_or_bracket(\n    ch: (usize, char),\n    parser_state: &mut PPState,\n    index_state: &mut ArrayIndexState,\n    key_start: &mut usize,\n    index_buffer: &mut i128,\n    path: &str,\n) -> crate::Result<()> {\n    match ch {\n        (idx, '.') => {\n            *key_start = idx + ch.1.len_utf8();\n            *parser_state = PPState::InKey;\n            Ok(())\n        }\n        (_, '[') => {\n            *index_state = ArrayIndexState::Start;\n            *parser_state = PPState::InArrayIndex;\n            *index_buffer = 0;\n            Ok(())\n        }\n        (_, _) => bail_parse_error!(\"Bad json path: {}\", path),\n    }\n}\n\nfn finalize_path<'a>(\n    parser_state: PPState,\n    key_start: usize,\n    path: &'a str,\n    path_components: &mut Vec<PathElement<'a>>,\n) -> crate::Result<()> {\n    match parser_state {\n        PPState::InArrayIndex => bail_parse_error!(\"Bad json path: {}\", path),\n        PPState::InKey => {\n            if key_start < path.len() {\n                let key = &path[key_start..];\n                if key.starts_with('\"') && !key.ends_with('\"') && key.len() > 1\n                    || (key.starts_with('\"') && key.ends_with('\"') && key.len() == 1)\n                {\n                    bail_parse_error!(\"Bad json path: {}\", path)\n                }\n                path_components.push(PathElement::Key(Cow::Borrowed(key), false));\n            } else {\n                bail_parse_error!(\"Bad json path: {}\", path)\n            }\n        }\n        _ => (),\n    }\n    Ok(())\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_json_path_root() {\n        let path = json_path(\"$\").unwrap();\n        assert_eq!(path.elements.len(), 1);\n        assert_eq!(path.elements[0], PathElement::Root());\n    }\n\n    #[test]\n    fn test_json_path_single_locator() {\n        let path = json_path(\"$.x\").unwrap();\n        assert_eq!(path.elements.len(), 2);\n        assert_eq!(path.elements[0], PathElement::Root());\n        assert_eq!(\n            path.elements[1],\n            PathElement::Key(Cow::Borrowed(\"x\"), false)\n        );\n    }\n\n    #[test]\n    fn test_json_path_single_array_locator() {\n        let path = json_path(\"$[0]\").unwrap();\n        assert_eq!(path.elements.len(), 2);\n        assert_eq!(path.elements[0], PathElement::Root());\n        assert_eq!(path.elements[1], PathElement::ArrayLocator(Some(0)));\n    }\n\n    #[test]\n    fn test_json_path_single_negative_array_locator() {\n        let path = json_path(\"$[#-2]\").unwrap();\n        assert_eq!(path.elements.len(), 2);\n        assert_eq!(path.elements[0], PathElement::Root());\n        assert_eq!(path.elements[1], PathElement::ArrayLocator(Some(-2)));\n    }\n\n    #[test]\n    fn test_json_path_invalid() {\n        let invalid_values = vec![\n            \"\", \"$$$\", \"$.\", \"$ \", \"$[\", \"$]\", \"$[-1]\", \"x\", \"[]\", \"$[0\", \"$[0x]\", \"$\\\"\",\n        ];\n\n        for value in invalid_values {\n            let path = json_path(value);\n\n            match path {\n                Err(crate::error::LimboError::ParseError(_)) => {\n                    // happy path\n                }\n                _ => panic!(\"Expected error for: {value:?}, got: {path:?}\"),\n            }\n        }\n    }\n\n    #[test]\n    fn test_json_path() {\n        let path = json_path(\"$.store.book[0].title\").unwrap();\n        assert_eq!(path.elements.len(), 5);\n        assert_eq!(path.elements[0], PathElement::Root());\n        assert_eq!(\n            path.elements[1],\n            PathElement::Key(Cow::Borrowed(\"store\"), false)\n        );\n        assert_eq!(\n            path.elements[2],\n            PathElement::Key(Cow::Borrowed(\"book\"), false)\n        );\n        assert_eq!(path.elements[3], PathElement::ArrayLocator(Some(0)));\n        assert_eq!(\n            path.elements[4],\n            PathElement::Key(Cow::Borrowed(\"title\"), false)\n        );\n    }\n\n    #[test]\n    fn test_large_index_wrapping() {\n        let path = json_path(\"$[4294967296]\").unwrap();\n        assert_eq!(path.elements[1], PathElement::ArrayLocator(Some(0)));\n\n        let path = json_path(\"$[4294967297]\").unwrap();\n        assert_eq!(path.elements[1], PathElement::ArrayLocator(Some(1)));\n    }\n\n    #[test]\n    fn test_deeply_nested_path() {\n        let path = json_path(\"$[0][1][2].key[3].other\").unwrap();\n        assert_eq!(path.elements.len(), 7);\n        assert_eq!(path.elements[0], PathElement::Root());\n        assert_eq!(path.elements[1], PathElement::ArrayLocator(Some(0)));\n        assert_eq!(path.elements[2], PathElement::ArrayLocator(Some(1)));\n        assert_eq!(path.elements[3], PathElement::ArrayLocator(Some(2)));\n        assert_eq!(\n            path.elements[4],\n            PathElement::Key(Cow::Borrowed(\"key\"), false)\n        );\n        assert_eq!(path.elements[5], PathElement::ArrayLocator(Some(3)));\n    }\n\n    #[test]\n    fn test_edge_cases() {\n        // Empty key\n        assert!(json_path(\"$.\").is_err());\n\n        // Multiple dots\n        assert!(json_path(\"$..key\").is_err());\n\n        // Unclosed brackets\n        assert!(json_path(\"$[0\").is_err());\n        assert!(json_path(\"$[\").is_err());\n\n        // Invalid negative index format\n        assert!(json_path(\"$[-1]\").is_err()); // should be $[#-1]\n    }\n\n    #[test]\n    fn test_path_capacity() {\n        // Test that our capacity estimation is reasonable\n        let short_path = \"$[0]\";\n        assert!(estimate_path_capacity(short_path) >= 2);\n\n        let long_path = \"$.a.b.c.d.e.f.g[0][1][2]\";\n        assert!(estimate_path_capacity(long_path) >= 11);\n    }\n\n    #[test]\n    fn test_quoted_keys() {\n        let path = json_path(r#\"$.\"key\"\"#).unwrap();\n        assert_eq!(\n            path.elements[1],\n            PathElement::Key(Cow::Borrowed(\"key\"), true)\n        );\n\n        let path = json_path(r#\"$.\"key.with.dots\"\"#).unwrap();\n        assert_eq!(\n            path.elements[1],\n            PathElement::Key(Cow::Borrowed(\"key.with.dots\"), true)\n        );\n\n        let path = json_path(r#\"$.\"key[0]\"\"#).unwrap();\n        assert_eq!(\n            path.elements[1],\n            PathElement::Key(Cow::Borrowed(\"key[0]\"), true)\n        );\n    }\n\n    #[test]\n    fn test_empty_quoted_key() {\n        assert!(json_path(r#\"$.\"\"\"#).is_ok());\n    }\n\n    #[test]\n    fn test_quoted_key_after_multibyte_utf8_chars() {\n        // Regression test for issue #5028\n        // The path contains multi-byte UTF-8 chars before a quoted key.\n        // This should not panic with \"byte index is not a char boundary\".\n        // '՜' is a 2-byte UTF-8 character (bytes 2-3 in the path).\n        // The important thing is that it doesn't panic - the result\n        // (valid parse or parse error) is less important.\n        let _ = json_path(r#\"$.՜O'\"R\"RE\"#);\n\n        // Also test a simpler case where UTF-8 chars appear before a quoted key\n        // $.世界\"key\" - Chinese characters followed by a quoted key\n        let _ = json_path(r#\"$.世界\"key\"\"#);\n\n        // Test with a valid quoted key containing UTF-8 chars\n        let path = json_path(r#\"$.\"世界\"\"#).unwrap();\n        assert_eq!(path.elements.len(), 2);\n        assert_eq!(\n            path.elements[1],\n            PathElement::Key(Cow::Borrowed(\"世界\"), true)\n        );\n    }\n}\n"
  },
  {
    "path": "core/json/vtab.rs",
    "content": "use crate::sync::{Arc, RwLock};\nuse std::iter::successors;\nuse std::result::Result;\n\nuse turso_ext::{ConstraintOp, ConstraintUsage, ResultCode};\n\nuse crate::{\n    json::{\n        convert_dbtype_to_jsonb, json_path_from_db_value,\n        jsonb::{IteratorState, Jsonb, SearchOperation},\n        path::{json_path, JsonPath, PathElement},\n        vtab::columns::{Columns, Key},\n        Conv,\n    },\n    vtab::{InternalVirtualTable, InternalVirtualTableCursor},\n    Connection, LimboError, Value,\n};\n\nuse super::jsonb;\n\n#[derive(Clone)]\nenum JsonTraversalMode {\n    /// Walk top-level keys/indices, but don't recurse. Used in `json_each`.\n    Each,\n    /// Walk keys/indices recursively. Used in `json_tree`.\n    Tree,\n}\n\nimpl JsonTraversalMode {\n    fn function_name(&self) -> &'static str {\n        match self {\n            JsonTraversalMode::Each => \"json_each\",\n            JsonTraversalMode::Tree => \"json_tree\",\n        }\n    }\n}\n\npub struct JsonVirtualTable {\n    traversal_mode: JsonTraversalMode,\n}\n\nimpl JsonVirtualTable {\n    pub fn json_each() -> Self {\n        Self {\n            traversal_mode: JsonTraversalMode::Each,\n        }\n    }\n\n    pub fn json_tree() -> Self {\n        Self {\n            traversal_mode: JsonTraversalMode::Tree,\n        }\n    }\n}\n\nconst COL_KEY: usize = 0;\nconst COL_VALUE: usize = 1;\nconst COL_TYPE: usize = 2;\nconst COL_ATOM: usize = 3;\nconst COL_ID: usize = 4;\nconst COL_PARENT: usize = 5;\nconst COL_FULLKEY: usize = 6;\nconst COL_PATH: usize = 7;\nconst COL_JSON: usize = 8;\nconst COL_ROOT: usize = 9;\n\nimpl InternalVirtualTable for JsonVirtualTable {\n    fn name(&self) -> String {\n        self.traversal_mode.function_name().to_owned()\n    }\n\n    fn open(\n        &self,\n        _conn: Arc<Connection>,\n    ) -> crate::Result<Arc<RwLock<dyn InternalVirtualTableCursor + 'static>>> {\n        Ok(Arc::new(RwLock::new(JsonEachCursor::empty(\n            self.traversal_mode.clone(),\n        ))))\n    }\n\n    fn best_index(\n        &self,\n        constraints: &[turso_ext::ConstraintInfo],\n        _order_by: &[turso_ext::OrderByInfo],\n    ) -> Result<turso_ext::IndexInfo, ResultCode> {\n        let mut usages = vec![\n            ConstraintUsage {\n                argv_index: None,\n                omit: false\n            };\n            constraints.len()\n        ];\n\n        let mut json_idx: Option<usize> = None;\n        let mut path_idx: Option<usize> = None;\n        let mut has_json_eq_constraint = false;\n        let mut has_root_eq_constraint = false;\n        for (i, c) in constraints.iter().enumerate() {\n            if c.op != ConstraintOp::Eq {\n                continue;\n            }\n            match c.column_index as usize {\n                COL_JSON => {\n                    has_json_eq_constraint = true;\n                    if c.usable {\n                        json_idx = Some(i);\n                    }\n                }\n                COL_ROOT => {\n                    has_root_eq_constraint = true;\n                    if c.usable {\n                        path_idx = Some(i);\n                    }\n                }\n                _ => {}\n            }\n        }\n\n        // Hidden arguments supplied in SQL must be usable in the chosen loop.\n        // If they are present but unusable, reject this access shape so the\n        // optimizer can pick a join order where argument registers are available.\n        if has_json_eq_constraint && json_idx.is_none() {\n            return Err(ResultCode::ConstraintViolation);\n        }\n        if has_root_eq_constraint && path_idx.is_none() {\n            return Err(ResultCode::ConstraintViolation);\n        }\n\n        let argc = match (json_idx, path_idx) {\n            (Some(_), Some(_)) => 2,\n            (Some(_), None) => 1,\n            _ => 0,\n        };\n\n        if argc >= 1 {\n            let idx = json_idx.expect(\"json_idx should be Some when argc >= 1\");\n            usages[idx] = ConstraintUsage {\n                argv_index: Some(1),\n                omit: true,\n            };\n        }\n        if argc == 2 {\n            let idx = path_idx.expect(\"path_idx should be Some when argc == 2\");\n            usages[idx] = ConstraintUsage {\n                argv_index: Some(2),\n                omit: true,\n            };\n        }\n\n        let (cost, rows) = match argc {\n            1 => (1., 25),\n            2 => (1., 25),\n            _ => (f64::MAX, 25),\n        };\n\n        Ok(turso_ext::IndexInfo {\n            idx_num: -1,\n            idx_str: None,\n            order_by_consumed: false,\n            estimated_cost: cost,\n            estimated_rows: rows,\n            constraint_usages: usages,\n        })\n    }\n\n    fn sql(&self) -> String {\n        \"CREATE TABLE x(\n            key ANY,             -- key for current element relative to its parent\n            value ANY,           -- value for the current element\n            type TEXT,           -- 'object','array','string','integer', etc.\n            atom ANY,            -- value for primitive types, null for array & object\n            id INTEGER,          -- integer ID for this element\n            parent INTEGER,      -- integer ID for the parent of this element\n            fullkey TEXT,        -- full path describing the current element\n            path TEXT,           -- path to the container of the current row\n            json JSON HIDDEN,    -- 1st input parameter: the raw JSON\n            root TEXT HIDDEN     -- 2nd input parameter: the PATH at which to start\n        );\"\n        .to_owned()\n    }\n}\n\nimpl std::fmt::Debug for JsonVirtualTable {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"JsonEachVirtualTable\").finish()\n    }\n}\n\npub struct JsonEachCursor {\n    rowid: i64,\n    json: Jsonb,\n    path_to_current_value: InPlaceJsonPath,\n    traversal_states: Vec<TraversalState>,\n    columns: Columns,\n    traversal_mode: JsonTraversalMode,\n}\n\nstruct TraversalState {\n    iterator_state: IteratorState,\n    parent_id: Option<i64>,\n    innermost_container_id: Option<i64>,\n    innermost_container_cursor: InPlaceJsonPathCursor,\n}\n\nimpl JsonEachCursor {\n    fn empty(traversal_mode: JsonTraversalMode) -> Self {\n        Self {\n            rowid: 0,\n            json: Jsonb::new(0, None),\n            traversal_states: Vec::new(),\n            path_to_current_value: InPlaceJsonPath::new_root(),\n            columns: Columns::default(),\n            traversal_mode,\n        }\n    }\n\n    fn push_state(\n        &mut self,\n        iterator_state: IteratorState,\n        innermost_container_cursor: InPlaceJsonPathCursor,\n    ) {\n        let parent_id = self\n            .traversal_states\n            .last()\n            .and_then(|state| state.innermost_container_id)\n            .or(Some(0));\n\n        let innermost_container = match iterator_state {\n            IteratorState::Object(_) | IteratorState::Array(_) => Some(self.rowid),\n            _ => parent_id,\n        };\n\n        self.traversal_states.push(TraversalState {\n            iterator_state,\n            parent_id,\n            innermost_container_id: innermost_container,\n            innermost_container_cursor,\n        });\n    }\n\n    fn peek_state(&self) -> Option<&TraversalState> {\n        self.traversal_states.last()\n    }\n}\n\nimpl InternalVirtualTableCursor for JsonEachCursor {\n    fn filter(\n        &mut self,\n        args: &[Value],\n        _idx_str: Option<String>,\n        _idx_num: i32,\n    ) -> Result<bool, LimboError> {\n        self.traversal_states.clear();\n        self.rowid = 0;\n\n        if args.is_empty() {\n            return Ok(false);\n        }\n        if args.len() == 2 && matches!(self.traversal_mode, JsonTraversalMode::Tree) {\n            if let Value::Text(ref text) = args[1] {\n                if !text.value.is_empty()\n                    && text\n                        .value\n                        .as_bytes()\n                        .windows(3)\n                        .any(|chars| chars == b\"[#-\")\n                {\n                    return Err(LimboError::InvalidArgument(\n                        \"Json paths with negative indices in json_tree are not supported yet\"\n                            .to_owned(),\n                    ));\n                }\n            }\n        }\n\n        let mut jsonb = convert_dbtype_to_jsonb(&args[0], Conv::Strict)?;\n\n        let (path, root_json) = if args.len() == 1 {\n            let path = \"$\";\n            (path, jsonb)\n        } else {\n            let Value::Text(path) = &args[1] else {\n                return Err(LimboError::InvalidArgument(\n                    \"root path should be text\".to_owned(),\n                ));\n            };\n            let root_json = if let Some(json) = navigate_to_path(&mut jsonb, &args[1])? {\n                json\n            } else {\n                return Ok(false);\n            };\n            (path.as_str(), root_json)\n        };\n\n        self.json = root_json;\n        self.path_to_current_value =\n            InPlaceJsonPath::from_json_path(path.to_owned(), json_path(path)?);\n        let iterator_state = json_iterator_from(&self.json)?;\n        let innermost_container_path = if matches!(self.traversal_mode, JsonTraversalMode::Tree)\n            && matches!(iterator_state, IteratorState::Primitive(_))\n        {\n            self.path_to_current_value.cursor_before_last_element()\n        } else {\n            self.path_to_current_value.cursor()\n        };\n        self.push_state(iterator_state, innermost_container_path);\n\n        let key = self.path_to_current_value.key().to_owned();\n        match self.traversal_mode {\n            JsonTraversalMode::Each => self.next(),\n            JsonTraversalMode::Tree => {\n                let state = self.peek_state().ok_or_else(|| {\n                    crate::LimboError::InternalError(\"state stack should not be empty\".to_string())\n                })?;\n                if matches!(state.iterator_state, IteratorState::Primitive(_)) {\n                    self.next()\n                } else {\n                    self.columns = Columns::new(\n                        key,\n                        self.json.clone(),\n                        self.path_to_current_value.string.clone(),\n                        None,\n                        self.path_to_current_value\n                            .read(self.path_to_current_value.cursor_before_last_element())\n                            .to_owned(),\n                    );\n                    Ok(true)\n                }\n            }\n        }\n    }\n\n    fn next(&mut self) -> Result<bool, LimboError> {\n        self.rowid += 1;\n        if self.traversal_states.is_empty() {\n            return Ok(false);\n        }\n\n        let traversal_state = self\n            .traversal_states\n            .pop()\n            .expect(\"traversal state stack is empty\");\n\n        let parent_id = if matches!(self.traversal_mode, JsonTraversalMode::Tree) {\n            traversal_state.parent_id\n        } else {\n            None\n        };\n        match traversal_state.iterator_state {\n            IteratorState::Array(state) => {\n                let Some(((idx, value), new_state)) = self.json.array_iterator_next(&state) else {\n                    self.path_to_current_value.pop();\n                    return self.next();\n                };\n\n                let recursing_iterator = if matches!(self.traversal_mode, JsonTraversalMode::Tree) {\n                    self.json\n                        .container_property_iterator(&IteratorState::Array(state))\n                } else {\n                    None\n                };\n                self.push_state(\n                    IteratorState::Array(new_state),\n                    self.path_to_current_value.cursor(),\n                );\n                let recurses = recursing_iterator.is_some();\n                self.path_to_current_value.push_array_index(&idx);\n                if let Some(it) = recursing_iterator {\n                    self.push_state(it, self.path_to_current_value.cursor());\n                }\n\n                let key = self.path_to_current_value.key().to_owned();\n                self.columns = Columns::new(\n                    key,\n                    value,\n                    self.path_to_current_value.string.clone(),\n                    parent_id,\n                    self.path_to_current_value\n                        .read(traversal_state.innermost_container_cursor)\n                        .to_owned(),\n                );\n\n                if !recurses {\n                    self.path_to_current_value.pop();\n                }\n            }\n            IteratorState::Object(state) => {\n                let Some(((_idx, key, value), new_state)) = self.json.object_iterator_next(&state)\n                else {\n                    self.path_to_current_value.pop();\n                    return self.next();\n                };\n\n                self.push_state(\n                    IteratorState::Object(new_state),\n                    self.path_to_current_value.cursor(),\n                );\n                self.path_to_current_value\n                    .push_object_key(&key.to_string()?)?;\n                let recursing = matches!(self.traversal_mode, JsonTraversalMode::Tree)\n                    && self\n                        .json\n                        .container_property_iterator(&IteratorState::Object(state))\n                        .is_some_and(|it| {\n                            self.push_state(it, self.path_to_current_value.cursor());\n                            true\n                        });\n\n                self.columns = Columns::new(\n                    self.path_to_current_value.key().to_owned(),\n                    value,\n                    self.path_to_current_value.string.clone(),\n                    parent_id,\n                    self.path_to_current_value\n                        .read(traversal_state.innermost_container_cursor)\n                        .to_owned(),\n                );\n\n                if !recursing {\n                    self.path_to_current_value.pop();\n                }\n            }\n            IteratorState::Primitive(jsonb) => {\n                let key = match self.traversal_mode {\n                    JsonTraversalMode::Each => Key::None,\n                    JsonTraversalMode::Tree => self.path_to_current_value.key().to_owned(),\n                };\n                self.columns = Columns::new(\n                    key,\n                    jsonb,\n                    self.path_to_current_value.string.clone(),\n                    parent_id,\n                    self.path_to_current_value\n                        .read(traversal_state.innermost_container_cursor)\n                        .to_owned(),\n                );\n            }\n        };\n\n        Ok(true)\n    }\n\n    fn rowid(&self) -> i64 {\n        self.rowid\n    }\n\n    fn column(&self, idx: usize) -> Result<Value, LimboError> {\n        Ok(match idx {\n            COL_KEY => self.columns.key(),\n            COL_VALUE => self.columns.value()?,\n            COL_TYPE => self.columns.ttype(),\n            COL_ATOM => self.columns.atom()?,\n            COL_ID => Value::from_i64(self.rowid),\n            COL_PARENT => self.columns.parent(),\n            COL_FULLKEY => self.columns.fullkey(),\n            COL_PATH => self.columns.path(),\n            _ => Value::Null,\n        })\n    }\n}\n\nfn json_iterator_from(json: &Jsonb) -> crate::Result<IteratorState> {\n    let json_element_type = json.element_type()?;\n    match json_element_type {\n        jsonb::ElementType::ARRAY => {\n            let iter = json.array_iterator()?;\n            Ok(IteratorState::Array(iter))\n        }\n\n        jsonb::ElementType::OBJECT => {\n            let iter = json.object_iterator()?;\n            Ok(IteratorState::Object(iter))\n        }\n        jsonb::ElementType::NULL\n        | jsonb::ElementType::TRUE\n        | jsonb::ElementType::FALSE\n        | jsonb::ElementType::INT\n        | jsonb::ElementType::INT5\n        | jsonb::ElementType::FLOAT\n        | jsonb::ElementType::FLOAT5\n        | jsonb::ElementType::TEXT\n        | jsonb::ElementType::TEXT5\n        | jsonb::ElementType::TEXTJ\n        | jsonb::ElementType::TEXTRAW => Ok(IteratorState::Primitive(json.clone())),\n        jsonb::ElementType::RESERVED1\n        | jsonb::ElementType::RESERVED2\n        | jsonb::ElementType::RESERVED3 => {\n            unreachable!(\"element type not supported: {json_element_type:?}\");\n        }\n    }\n}\nfn navigate_to_path(jsonb: &mut Jsonb, path: &Value) -> Result<Option<Jsonb>, LimboError> {\n    let json_path = json_path_from_db_value(path, true)?.ok_or_else(|| {\n        LimboError::InvalidArgument(format!(\"path '{path}' is not a valid json path\"))\n    })?;\n    let mut search_operation = SearchOperation::new(jsonb.len() / 2);\n    if jsonb\n        .operate_on_path(&json_path, &mut search_operation)\n        .is_err()\n    {\n        return Ok(None);\n    }\n    Ok(Some(search_operation.result()))\n}\n\nmod columns {\n    use crate::{\n        json::{\n            json_string_to_db_type,\n            jsonb::{self, ElementType, Jsonb},\n            OutputVariant,\n        },\n        types::Text,\n        LimboError, Value,\n    };\n\n    #[derive(Debug, Clone)]\n    pub(super) enum Key {\n        Integer(i64),\n        String(String),\n        None,\n    }\n\n    impl Key {\n        fn empty() -> Self {\n            Self::None\n        }\n\n        fn key_representation(&self) -> Value {\n            match self {\n                Key::Integer(ref i) => Value::from_i64(*i),\n                Key::String(ref s) => Value::Text(Text::new(s.to_owned().replace(\"\\\\\\\"\", \"\\\"\"))),\n                Key::None => Value::Null,\n            }\n        }\n    }\n\n    pub(super) struct Columns {\n        key: Key,\n        value: Jsonb,\n        fullkey: String,\n        parent_id: Option<i64>,\n        innermost_container_path: String,\n    }\n\n    impl Default for Columns {\n        fn default() -> Columns {\n            Self {\n                key: Key::empty(),\n                value: Jsonb::new(0, None),\n                fullkey: \"\".to_owned(),\n                parent_id: None,\n                innermost_container_path: \"\".to_owned(),\n            }\n        }\n    }\n\n    impl Columns {\n        pub(super) fn new(\n            key: Key,\n            value: Jsonb,\n            fullkey: String,\n            parent_id: Option<i64>,\n            innermost_container_path: String,\n        ) -> Self {\n            Self {\n                key,\n                value,\n                parent_id,\n                fullkey,\n                innermost_container_path,\n            }\n        }\n\n        pub(super) fn atom(&self) -> Result<Value, LimboError> {\n            Self::atom_from_value(&self.value)\n        }\n\n        pub(super) fn value(&self) -> Result<Value, LimboError> {\n            let element_type = self.value.element_type()?;\n            Ok(match element_type {\n                ElementType::ARRAY | ElementType::OBJECT => {\n                    json_string_to_db_type(self.value.clone(), element_type, OutputVariant::String)?\n                }\n                _ => Self::atom_from_value(&self.value)?,\n            })\n        }\n\n        pub(super) fn key(&self) -> Value {\n            self.key.key_representation()\n        }\n\n        fn atom_from_value(value: &Jsonb) -> Result<Value, LimboError> {\n            let element_type = value.element_type().expect(\"invalid value\");\n            let string: Result<Value, LimboError> = match element_type {\n                jsonb::ElementType::NULL => Ok(Value::Null),\n                jsonb::ElementType::TRUE => Ok(Value::from_i64(1)),\n                jsonb::ElementType::FALSE => Ok(Value::from_i64(0)),\n                jsonb::ElementType::INT | jsonb::ElementType::INT5 => Self::jsonb_to_integer(value),\n                jsonb::ElementType::FLOAT | jsonb::ElementType::FLOAT5 => {\n                    Self::jsonb_to_float(value)\n                }\n                jsonb::ElementType::TEXT\n                | jsonb::ElementType::TEXTJ\n                | jsonb::ElementType::TEXT5\n                | jsonb::ElementType::TEXTRAW => {\n                    let s = value.to_string()?;\n                    // Text values must be properly quoted\n                    let unquoted = s\n                        .strip_prefix('\"')\n                        .and_then(|s| s.strip_suffix('\"'))\n                        .ok_or_else(|| LimboError::ParseError(\"malformed JSON\".to_string()))?;\n                    Ok(Value::Text(Text::new(unquoted.to_string())))\n                }\n                jsonb::ElementType::ARRAY => Ok(Value::Null),\n                jsonb::ElementType::OBJECT => Ok(Value::Null),\n                jsonb::ElementType::RESERVED1 => Ok(Value::Null),\n                jsonb::ElementType::RESERVED2 => Ok(Value::Null),\n                jsonb::ElementType::RESERVED3 => Ok(Value::Null),\n            };\n\n            string\n        }\n\n        fn jsonb_to_integer(value: &Jsonb) -> Result<Value, LimboError> {\n            let string = value.to_string()?;\n            let int = string.parse::<i64>()?;\n\n            Ok(Value::from_i64(int))\n        }\n\n        fn jsonb_to_float(value: &Jsonb) -> Result<Value, LimboError> {\n            let string = value.to_string()?;\n            let float = string.parse::<f64>()?;\n\n            Ok(Value::from_f64(float))\n        }\n\n        pub(super) fn fullkey(&self) -> Value {\n            Value::Text(Text::new(self.fullkey.clone()))\n        }\n\n        pub(super) fn path(&self) -> Value {\n            Value::Text(Text::new(self.innermost_container_path.clone()))\n        }\n\n        pub(super) fn parent(&self) -> Value {\n            match self.parent_id {\n                Some(id) => Value::from_i64(id),\n                None => Value::Null,\n            }\n        }\n\n        pub(super) fn ttype(&self) -> Value {\n            let element_type = self.value.element_type().expect(\"invalid value\");\n            let ttype = match element_type {\n                jsonb::ElementType::NULL => \"null\",\n                jsonb::ElementType::TRUE => \"true\",\n                jsonb::ElementType::FALSE => \"false\",\n                jsonb::ElementType::INT | jsonb::ElementType::INT5 => \"integer\",\n                jsonb::ElementType::FLOAT | jsonb::ElementType::FLOAT5 => \"real\",\n                jsonb::ElementType::TEXT\n                | jsonb::ElementType::TEXTJ\n                | jsonb::ElementType::TEXT5\n                | jsonb::ElementType::TEXTRAW => \"text\",\n                jsonb::ElementType::ARRAY => \"array\",\n                jsonb::ElementType::OBJECT => \"object\",\n                jsonb::ElementType::RESERVED1\n                | jsonb::ElementType::RESERVED2\n                | jsonb::ElementType::RESERVED3 => unreachable!(),\n            };\n\n            Value::Text(Text::new(ttype))\n        }\n    }\n}\n\nstruct InPlaceJsonPath {\n    string: String,\n    element_lengths: Vec<usize>,\n    last_element: Key,\n}\n\ntype InPlaceJsonPathCursor = usize;\n\nimpl InPlaceJsonPath {\n    fn new_root() -> Self {\n        Self {\n            string: \"$\".to_owned(),\n            element_lengths: vec![1],\n            last_element: Key::None,\n        }\n    }\n\n    fn pop(&mut self) {\n        if let Some(len) = self.element_lengths.pop() {\n            if len != 0 {\n                self.string.truncate(self.string.len() - len);\n            }\n        }\n    }\n\n    fn push_array_index(&mut self, idx: &usize) {\n        self.last_element = Key::Integer(*idx as i64);\n        self.push(format!(\"[{idx}]\"));\n    }\n\n    fn push_object_key(&mut self, key: &str) -> crate::Result<()> {\n        // This follows SQLite's current quoting scheme, but it is not part of the stable API.\n        // See https://sqlite.org/forum/forumpost?udc=1&name=be212a295ed8df4c\n        // Keys must be properly quoted strings\n        let inner = key\n            .strip_prefix('\"')\n            .and_then(|s| s.strip_suffix('\"'))\n            .ok_or_else(|| crate::LimboError::ParseError(\"malformed JSON\".to_string()))?;\n\n        let unquoted_if_necessary = if inner\n            .chars()\n            .any(|c| c == '.' || c == ' ' || c == '\"' || c == '_')\n        {\n            key\n        } else {\n            inner\n        };\n        self.last_element = Key::String(inner.to_owned());\n        self.push(format!(\".{unquoted_if_necessary}\"));\n        Ok(())\n    }\n\n    fn push(&mut self, element: String) {\n        self.element_lengths.push(element.len());\n        self.string.push_str(&element);\n    }\n\n    fn cursor(&self) -> InPlaceJsonPathCursor {\n        self.string.len()\n    }\n\n    fn read(&self, cursor: InPlaceJsonPathCursor) -> &str {\n        &self.string[0..cursor]\n    }\n\n    fn from_json_path(path: String, json_path: JsonPath<'_>) -> Self {\n        let (json_path, last_element) = if json_path.elements.is_empty() {\n            (\n                JsonPath {\n                    elements: vec![PathElement::Root()],\n                },\n                Key::None,\n            )\n        } else {\n            let last_element = json_path\n                .elements\n                .last()\n                .and_then(|path_element| match path_element {\n                    PathElement::Key(cow, _) => Some(Key::String(cow.to_string())),\n                    PathElement::ArrayLocator(Some(idx)) => Some(Key::Integer(*idx as i64)),\n                    _ => None,\n                })\n                .unwrap_or(Key::None);\n\n            (json_path, last_element)\n        };\n\n        let element_lengths = json_path\n            .elements\n            .iter()\n            .map(Self::element_length)\n            .collect();\n\n        Self {\n            string: path,\n            element_lengths,\n            last_element,\n        }\n    }\n\n    fn element_length(element: &PathElement) -> usize {\n        match element {\n            PathElement::Root() => 1,\n            PathElement::Key(key, _) => key.len() + 1,\n            PathElement::ArrayLocator(idx) => {\n                let digit_count = successors(*idx, |&n| (n >= 10).then_some(n / 10)).count();\n                let bracket_count = 2; // []\n\n                digit_count + bracket_count\n            }\n        }\n    }\n\n    fn cursor_before_last_element(&self) -> InPlaceJsonPathCursor {\n        if self.element_lengths.len() == 1 {\n            self.cursor()\n        } else {\n            self.cursor()\n                - self\n                    .element_lengths\n                    .last()\n                    .expect(\"element_lengths should not be empty in else branch\")\n        }\n    }\n\n    fn key(&self) -> &Key {\n        &self.last_element\n    }\n}\n"
  },
  {
    "path": "core/lib.rs",
    "content": "pub mod busy;\n#[cfg(feature = \"cli_only\")]\npub mod dbpage;\n#[cfg(any(feature = \"fuzz\", feature = \"bench\"))]\npub mod functions;\npub mod index_method;\npub mod io;\n#[cfg(all(feature = \"json\", any(feature = \"fuzz\", feature = \"bench\")))]\npub mod json;\npub mod mvcc;\n#[cfg(any(feature = \"fuzz\", feature = \"bench\"))]\npub mod numeric;\npub mod schema;\npub mod state_machine;\npub mod storage;\npub mod types;\n#[cfg(any(feature = \"fuzz\", feature = \"bench\"))]\npub mod vdbe;\npub mod vector;\n\n#[cfg(feature = \"cli_only\")]\npub(crate) mod btree_dump;\npub(crate) mod sync;\npub(crate) mod thread;\n\nmod assert;\nmod connection;\nmod error;\nmod ext;\nmod fast_lock;\nmod function;\n#[cfg(not(any(feature = \"fuzz\", feature = \"bench\")))]\nmod functions;\nmod incremental;\nmod info;\n#[cfg(all(feature = \"json\", not(any(feature = \"fuzz\", feature = \"bench\"))))]\nmod json;\n#[cfg(not(any(feature = \"fuzz\", feature = \"bench\")))]\nmod numeric;\nmod parameters;\nmod pragma;\nmod pseudo;\nmod regexp;\n#[cfg(feature = \"series\")]\nmod series;\nmod statement;\nmod stats;\n#[allow(dead_code)]\n#[cfg(feature = \"time\")]\nmod time;\nmod translate;\nmod util;\n#[cfg(feature = \"uuid\")]\nmod uuid;\n#[cfg(not(any(feature = \"fuzz\", feature = \"bench\")))]\nmod vdbe;\nmod vtab;\n\n#[cfg(any(feature = \"fuzz\", feature = \"bench\"))]\npub use function::MathFunc;\n\nuse crate::{\n    busy::{BusyHandler, BusyHandlerCallback},\n    incremental::view::AllViewsTxState,\n    index_method::IndexMethod,\n    schema::Trigger,\n    stats::refresh_analyze_stats,\n    storage::{\n        checksum::CHECKSUM_REQUIRED_RESERVED_BYTES,\n        encryption::{AtomicCipherMode, SQLITE_HEADER, TURSO_HEADER_PREFIX},\n        journal_mode,\n        pager::{self, AutoVacuumMode, HeaderRef, HeaderRefMut},\n        sqlite3_ondisk::{RawVersion, TextEncoding, Version},\n    },\n    sync::{\n        atomic::{\n            AtomicBool, AtomicI32, AtomicI64, AtomicIsize, AtomicU16, AtomicU64, AtomicUsize,\n            Ordering,\n        },\n        Arc, LazyLock, Mutex, RwLock, Weak,\n    },\n    translate::{emitter::TransactionMode, pragma::TURSO_CDC_DEFAULT_TABLE_NAME},\n    vdbe::metrics::ConnectionMetrics,\n    vtab::VirtualTable,\n};\nuse arc_swap::{ArcSwap, ArcSwapOption};\nuse core::str;\nuse rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};\nuse schema::Schema;\nuse std::{\n    fmt::{self},\n    ops::Deref,\n    time::Duration,\n};\n#[cfg(feature = \"fs\")]\nuse storage::database::DatabaseFile;\nuse storage::{page_cache::PageCache, sqlite3_ondisk::PageSize};\nuse tracing::{instrument, Level};\nuse turso_macros::{match_ignore_ascii_case, AtomicEnum};\nuse turso_parser::{ast, ast::Cmd, parser::Parser};\nuse util::parse_schema_rows;\n\npub use connection::{resolve_ext_path, Connection, Row, StepResult, SymbolTable};\npub(crate) use connection::{AtomicTransactionState, TransactionState};\npub use error::{io_error, CompletionError, LimboError};\n#[cfg(all(feature = \"fs\", target_family = \"unix\", not(miri)))]\npub use io::UnixIO;\n#[cfg(all(feature = \"fs\", target_os = \"linux\", feature = \"io_uring\", not(miri)))]\npub use io::UringIO;\n#[cfg(all(\n    feature = \"fs\",\n    target_os = \"windows\",\n    feature = \"experimental_win_iocp\",\n    not(miri)\n))]\npub use io::WindowsIOCP;\npub use io::{\n    clock::{Clock, MonotonicInstant, WallClockInstant},\n    Buffer, Completion, CompletionType, File, GroupCompletion, MemoryIO, OpenFlags, PlatformIO,\n    SyscallIO, WriteCompletion, IO,\n};\npub use numeric::{nonnan::NonNan, Numeric};\npub use statement::Statement;\npub use storage::{\n    buffer_pool::BufferPool,\n    database::{DatabaseStorage, IOContext},\n    encryption::{CipherMode, EncryptionContext, EncryptionKey},\n    pager::{Page, PageRef, Pager},\n    wal::{CheckpointMode, CheckpointResult, Wal, WalFile, WalFileShared},\n};\npub use turso_macros::{\n    turso_assert, turso_assert_all, turso_assert_eq, turso_assert_greater_than,\n    turso_assert_greater_than_or_equal, turso_assert_less_than, turso_assert_less_than_or_equal,\n    turso_assert_ne, turso_assert_reachable, turso_assert_some, turso_assert_sometimes,\n    turso_assert_sometimes_greater_than, turso_assert_sometimes_greater_than_or_equal,\n    turso_assert_sometimes_less_than, turso_assert_sometimes_less_than_or_equal,\n    turso_assert_unreachable, turso_debug_assert, turso_soft_unreachable,\n};\npub use types::{IOResult, Value, ValueRef};\npub use util::IOExt;\npub use vdbe::{\n    builder::QueryMode, explain::EXPLAIN_COLUMNS, explain::EXPLAIN_QUERY_PLAN_COLUMNS,\n    FromValueRow, PrepareContext, PreparedProgram, Program, Register,\n};\n\n/// Database index for the main database (always 0 in SQLite).\npub const MAIN_DB_ID: usize = 0;\n\nmod turso_types_vtab;\n\n/// Database index for the temp database (always 1 in SQLite).\npub const TEMP_DB_ID: usize = 1;\n\n/// First database index used for ATTACH-ed databases.\n/// SQLite reserves 0 for \"main\" and 1 for \"temp\", so attached databases\n/// start at index 2.\npub const FIRST_ATTACHED_DB_ID: usize = 2;\n\n/// Returns true if the database index refers to an attached database\n/// (i.e. not \"main\" and not \"temp\").\npub const fn is_attached_db(database_id: usize) -> bool {\n    database_id >= FIRST_ATTACHED_DB_ID\n}\n\n/// Configuration for database features\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]\npub struct DatabaseOpts {\n    pub enable_views: bool,\n    pub enable_custom_types: bool,\n    pub enable_encryption: bool,\n    pub enable_index_method: bool,\n    pub enable_autovacuum: bool,\n    pub enable_attach: bool,\n    pub unsafe_testing: bool,\n    enable_load_extension: bool,\n}\n\nimpl DatabaseOpts {\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    #[cfg(feature = \"cli_only\")]\n    pub fn turso_cli(mut self) -> Self {\n        self.enable_load_extension = true;\n        self\n    }\n\n    pub fn with_views(mut self, enable: bool) -> Self {\n        self.enable_views = enable;\n        self\n    }\n\n    pub fn with_custom_types(mut self, enable: bool) -> Self {\n        self.enable_custom_types = enable;\n        self\n    }\n\n    pub fn with_encryption(mut self, enable: bool) -> Self {\n        self.enable_encryption = enable;\n        self\n    }\n\n    pub fn with_index_method(mut self, enable: bool) -> Self {\n        self.enable_index_method = enable;\n        self\n    }\n\n    pub fn with_autovacuum(mut self, enable: bool) -> Self {\n        self.enable_autovacuum = enable;\n        self\n    }\n\n    pub fn with_attach(mut self, enable: bool) -> Self {\n        self.enable_attach = enable;\n        self\n    }\n\n    pub fn with_unsafe_testing(mut self, enable: bool) -> Self {\n        self.unsafe_testing = enable;\n        self\n    }\n}\n\n#[derive(Clone, Debug, Default)]\npub struct EncryptionOpts {\n    pub cipher: String,\n    pub hexkey: String,\n}\n\nimpl EncryptionOpts {\n    pub fn new() -> Self {\n        Self::default()\n    }\n}\n\npub type Result<T, E = LimboError> = std::result::Result<T, E>;\n\n#[derive(Debug, AtomicEnum, Clone, Copy, PartialEq, Eq)]\npub enum SyncMode {\n    Off = 0,\n    Normal = 1,\n    Full = 2,\n}\n\n/// Control where temporary tables and indices are stored.\n/// Matches SQLite's PRAGMA temp_store values:\n/// - 0 = DEFAULT (use compile-time default, which is FILE)\n/// - 1 = FILE (always use temp files on disk)\n/// - 2 = MEMORY (always use in-memory storage)\n#[derive(Debug, AtomicEnum, Clone, Copy, PartialEq, Eq, Default)]\npub enum TempStore {\n    #[default]\n    Default = 0,\n    File = 1,\n    Memory = 2,\n}\n\npub(crate) type MvStore = mvcc::MvStore<mvcc::MvccClock>;\n\npub(crate) type MvCursor = mvcc::cursor::MvccLazyCursor<mvcc::MvccClock>;\n\n/// Creates a read completion for database header reads that checks for short reads.\n/// The header is always on page 1, so this function hardcodes that page index.\nfn new_header_read_completion(buf: Arc<Buffer>) -> Completion {\n    let expected = buf.len();\n    Completion::new_read(buf, move |res| {\n        let Ok((_buf, bytes_read)) = res else {\n            return None; // IO error already captured in completion\n        };\n        if (bytes_read as usize) < expected {\n            tracing::error!(\n                \"short read on database header: expected {expected} bytes, got {bytes_read}\"\n            );\n            return Some(CompletionError::ShortRead {\n                page_idx: 1, // header is on page 1\n                expected,\n                actual: bytes_read as usize,\n            });\n        }\n        None\n    })\n}\n\n/// Phase tracking for async database opening\n#[derive(Default, Debug)]\npub enum OpenDbAsyncPhase {\n    #[default]\n    Init,\n    ReadingHeader,\n    LoadingSchema,\n    BootstrapMvStore,\n    Done,\n}\n\n/// State machine for async database opening\npub struct OpenDbAsyncState {\n    phase: OpenDbAsyncPhase,\n    db: Option<Arc<Database>>,\n    pager: Option<Arc<Pager>>,\n    conn: Option<Arc<Connection>>,\n    encryption_key: Option<EncryptionKey>,\n    make_from_btree_state: schema::MakeFromBtreeState,\n    /// Schema lock held during LoadingSchema phase to ensure atomicity across IO yields\n    schema_guard: Option<sync::ArcMutexGuard<Arc<Schema>>>,\n    /// Registry lock held during open_with_flags_async to prevent concurrent opens\n    registry_guard:\n        Option<parking_lot::ArcMutexGuard<parking_lot::RawMutex, HashMap<String, Weak<Database>>>>,\n    /// Canonical path for registry insertion (computed once at start)\n    canonical_path: Option<String>,\n}\n\nimpl Default for OpenDbAsyncState {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl OpenDbAsyncState {\n    pub fn new() -> Self {\n        Self {\n            phase: OpenDbAsyncPhase::Init,\n            db: None,\n            pager: None,\n            conn: None,\n            encryption_key: None,\n            make_from_btree_state: schema::MakeFromBtreeState::new(),\n            schema_guard: None,\n            registry_guard: None,\n            canonical_path: None,\n        }\n    }\n}\n\n/// The database manager ensures that there is a single, shared\n/// `Database` object per a database file. We need because it is not safe\n/// to have multiple independent WAL files open because coordination\n/// happens at process-level POSIX file advisory locks.\n///\n/// Uses parking_lot::Mutex instead of crate::sync::Mutex because this static\n/// must persist across shuttle test iterations. Shuttle resets its execution\n/// state between iterations, but static variables persist - using shuttle's\n/// Mutex here would cause panics when the second iteration tries to lock a\n/// mutex that belongs to a stale execution context.\n#[allow(clippy::type_complexity)]\nstatic DATABASE_MANAGER: LazyLock<Arc<parking_lot::Mutex<HashMap<String, Weak<Database>>>>> =\n    LazyLock::new(|| Arc::new(parking_lot::Mutex::new(HashMap::default())));\n\n/// The `Database` object contains per database file state that is shared\n/// between multiple connections.\n///\n/// Do that `Database` object is cached and can be long lived. DO NOT store anything sensitive like\n/// encryption key here.\npub struct Database {\n    mv_store: ArcSwapOption<MvStore>,\n    schema: Arc<Mutex<Arc<Schema>>>,\n    pub db_file: Arc<dyn DatabaseStorage>,\n    pub path: String,\n    wal_path: String,\n    pub io: Arc<dyn IO>,\n    buffer_pool: Arc<BufferPool>,\n    // Shared structures of a Database are the parts that are common to multiple threads that might\n    // create DB connections.\n    _shared_page_cache: Arc<RwLock<PageCache>>,\n\n    /// Optional per-database MVCC durable storage override.\n    ///\n    /// When set, MVCC will use this implementation for logical-log durability\n    /// (commit, sync, checkpoint thresholds, etc.) instead of the built-in storage.\n    durable_storage: Option<Arc<dyn crate::mvcc::persistent_storage::DurableStorage>>,\n    shared_wal: Arc<RwLock<WalFileShared>>,\n    init_lock: Arc<Mutex<()>>,\n    open_flags: OpenFlags,\n    // Use parking lot RwLock here and not `crate::sync::RwLock` because it relies on `data_ptr` and that is experimental\n    // in std.\n    builtin_syms: parking_lot::RwLock<SymbolTable>,\n    opts: DatabaseOpts,\n    n_connections: AtomicUsize,\n\n    /// In Memory Page 1 for Empty Dbs\n    init_page_1: Arc<ArcSwapOption<Page>>,\n\n    // Encryption\n    encryption_cipher_mode: AtomicCipherMode,\n}\n\n// SAFETY: This needs to be audited for thread safety.\n// See: https://github.com/tursodatabase/turso/issues/1552\ncrate::assert::assert_send_sync!(Database);\n\nimpl fmt::Debug for Database {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        let mut debug_struct = f.debug_struct(\"Database\");\n        debug_struct\n            .field(\"path\", &self.path)\n            .field(\"open_flags\", &self.open_flags);\n\n        // Database state information\n        let db_state_value = match &*self.init_page_1.load() {\n            // If init_page1 exists, this means the DB is empty\n            Some(_) => \"uninitialized\",\n            None => \"initialized\",\n        };\n        debug_struct.field(\"db_state\", &db_state_value);\n\n        let mv_store_status = if self.get_mv_store().is_some() {\n            \"present\"\n        } else {\n            \"none\"\n        };\n        debug_struct.field(\"mv_store\", &mv_store_status);\n\n        let init_lock_status = if self.init_lock.try_lock().is_some() {\n            \"unlocked\"\n        } else {\n            \"locked\"\n        };\n        debug_struct.field(\"init_lock\", &init_lock_status);\n\n        let wal_status = match self.shared_wal.try_read() {\n            Some(wal) if wal.enabled.load(Ordering::SeqCst) => \"enabled\",\n            Some(_) => \"disabled\",\n            None => \"locked_for_write\",\n        };\n        debug_struct.field(\"wal_state\", &wal_status);\n\n        // Page cache info (just basic stats, not full contents)\n        let cache_info = match self._shared_page_cache.try_read() {\n            Some(cache) => format!(\"( capacity {}, used: {} )\", cache.capacity(), cache.len()),\n            None => \"locked\".to_string(),\n        };\n        debug_struct.field(\"page_cache\", &cache_info);\n\n        debug_struct.field(\n            \"n_connections\",\n            &self\n                .n_connections\n                .load(crate::sync::atomic::Ordering::SeqCst),\n        );\n        debug_struct.finish()\n    }\n}\n\nimpl Database {\n    fn new(\n        opts: DatabaseOpts,\n        flags: OpenFlags,\n        path: impl Into<String>,\n        wal_path: impl Into<String>,\n        io: &Arc<dyn IO>,\n        db_file: Arc<dyn DatabaseStorage>,\n        encryption_opts: Option<EncryptionOpts>,\n    ) -> Result<Self> {\n        let shared_wal = WalFileShared::new_noop();\n        let mv_store = ArcSwapOption::empty();\n\n        let db_size = db_file.size()?;\n\n        let shared_page_cache = Arc::new(RwLock::new(PageCache::default()));\n        let syms = SymbolTable::new();\n        let arena_size = if std::env::var(\"TESTING\").is_ok_and(|v| v.eq_ignore_ascii_case(\"true\")) {\n            BufferPool::TEST_ARENA_SIZE\n        } else {\n            BufferPool::DEFAULT_ARENA_SIZE\n        };\n\n        let encryption_cipher_mode = if let Some(encryption_opts) = encryption_opts {\n            Some(CipherMode::try_from(encryption_opts.cipher.as_str())?)\n        } else {\n            None\n        };\n\n        let init_page_1 = if db_size == 0 {\n            let default_page_1 = pager::default_page1(encryption_cipher_mode.as_ref());\n\n            Some(default_page_1)\n        } else {\n            None\n        };\n\n        // opts is now passed as parameter\n        let db = Database {\n            mv_store,\n            path: path.into(),\n            wal_path: wal_path.into(),\n            schema: Arc::new(Mutex::new(Arc::new(Schema::with_options(\n                opts.enable_custom_types,\n            )))),\n            _shared_page_cache: shared_page_cache,\n            shared_wal,\n            db_file,\n            builtin_syms: parking_lot::RwLock::new(syms),\n            io: io.clone(),\n            open_flags: flags,\n            init_lock: Arc::new(Mutex::new(())),\n            opts,\n            buffer_pool: BufferPool::begin_init(io, arena_size),\n            n_connections: AtomicUsize::new(0),\n\n            init_page_1: Arc::new(ArcSwapOption::new(init_page_1)),\n\n            encryption_cipher_mode: AtomicCipherMode::new(\n                encryption_cipher_mode.unwrap_or(CipherMode::None),\n            ),\n\n            durable_storage: None,\n        };\n\n        db.register_global_builtin_extensions()\n            .expect(\"unable to register global extensions\");\n        Ok(db)\n    }\n\n    #[cfg(feature = \"fs\")]\n    pub fn open_file(io: Arc<dyn IO>, path: &str) -> Result<Arc<Database>> {\n        Self::open_file_with_flags(io, path, OpenFlags::default(), DatabaseOpts::new(), None)\n    }\n\n    /// Look up a database in the process-wide registry by path.\n    /// Returns the cached Database if found, with encryption validation.\n    /// This avoids opening a file (and acquiring a file lock) when the\n    /// database is already open in this process.\n    fn lookup_in_registry(\n        path: &str,\n        encryption_opts: &Option<EncryptionOpts>,\n    ) -> Result<Option<Arc<Database>>> {\n        if path.starts_with(\":memory:\") {\n            return Ok(None);\n        }\n        let canonical = match std::fs::canonicalize(path)\n            .ok()\n            .and_then(|p| p.to_str().map(|s| s.to_string()))\n        {\n            Some(c) => c,\n            None => return Ok(None),\n        };\n        let registry = DATABASE_MANAGER.lock_arc();\n        let db = match registry.get(&canonical).and_then(Weak::upgrade) {\n            Some(db) => db,\n            None => return Ok(None),\n        };\n\n        // Validate encryption compatibility (key is not stored for security,\n        // so we can only check cipher mode)\n        let db_is_encrypted = !matches!(db.encryption_cipher_mode.get(), CipherMode::None);\n        if db_is_encrypted && encryption_opts.is_none() {\n            return Err(LimboError::InvalidArgument(\n                \"Database is encrypted but no encryption options provided\".to_string(),\n            ));\n        }\n\n        Ok(Some(db))\n    }\n\n    #[cfg(feature = \"fs\")]\n    pub fn open_file_with_flags(\n        io: Arc<dyn IO>,\n        path: &str,\n        flags: OpenFlags,\n        opts: DatabaseOpts,\n        encryption_opts: Option<EncryptionOpts>,\n    ) -> Result<Arc<Database>> {\n        Self::open_file_with_flags_and_durable_storage(io, path, flags, opts, encryption_opts, None)\n    }\n\n    #[cfg(feature = \"fs\")]\n    pub fn open_file_with_flags_and_durable_storage(\n        io: Arc<dyn IO>,\n        path: &str,\n        flags: OpenFlags,\n        opts: DatabaseOpts,\n        encryption_opts: Option<EncryptionOpts>,\n        durable_storage: Option<Arc<dyn crate::mvcc::persistent_storage::DurableStorage>>,\n    ) -> Result<Arc<Database>> {\n        // Check the registry before opening the file to avoid acquiring a file\n        // lock that would conflict with an already-open Database in this process.\n        if let Some(db) = Self::lookup_in_registry(path, &encryption_opts)? {\n            if durable_storage.is_some() && db.durable_storage.is_none() {\n                return Err(LimboError::InvalidArgument(\n                    \"database already open without custom durable storage; \\\n                     close the existing instance before reopening with a custom DurableStorage\"\n                        .to_string(),\n                ));\n            }\n            return Ok(db);\n        }\n        let file = io.open_file(path, flags, true)?;\n        let db_file = Arc::new(DatabaseFile::new(file));\n        Self::open_with_flags(\n            io,\n            path,\n            db_file,\n            flags,\n            opts,\n            encryption_opts,\n            durable_storage,\n        )\n    }\n\n    pub fn open(\n        io: Arc<dyn IO>,\n        path: &str,\n        db_file: Arc<dyn DatabaseStorage>,\n    ) -> Result<Arc<Database>> {\n        Self::open_with_flags(\n            io,\n            path,\n            db_file,\n            OpenFlags::default(),\n            DatabaseOpts::new(),\n            None,\n            None,\n        )\n    }\n\n    #[allow(clippy::too_many_arguments)]\n    pub fn open_with_flags(\n        io: Arc<dyn IO>,\n        path: &str,\n        db_file: Arc<dyn DatabaseStorage>,\n        flags: OpenFlags,\n        opts: DatabaseOpts,\n        encryption_opts: Option<EncryptionOpts>,\n        durable_storage: Option<Arc<dyn crate::mvcc::persistent_storage::DurableStorage>>,\n    ) -> Result<Arc<Database>> {\n        let mut state = OpenDbAsyncState::new();\n        loop {\n            match Self::open_with_flags_async(\n                &mut state,\n                io.clone(),\n                path,\n                db_file.clone(),\n                flags,\n                opts,\n                encryption_opts.clone(),\n                durable_storage.clone(),\n            )? {\n                IOResult::Done(db) => return Ok(db),\n                IOResult::IO(io_completion) => {\n                    io_completion.wait(&*io)?;\n                }\n            }\n        }\n    }\n\n    /// async flow of opening the database\n    /// this is important to have open async, otherwise sync-engine will not work properly for cases when schema table span multiple pages\n    /// (so, potentially network IO is needed to load them)\n    ///\n    /// Uses the database registry to ensure single Database instance per file within a process.\n    /// Caller must drive the IO loop and pass state between calls.\n    /// The registry lock is held for the entire duration to prevent concurrent opens.\n    #[allow(clippy::too_many_arguments)]\n    pub fn open_with_flags_async(\n        state: &mut OpenDbAsyncState,\n        io: Arc<dyn IO>,\n        path: &str,\n        db_file: Arc<dyn DatabaseStorage>,\n        flags: OpenFlags,\n        opts: DatabaseOpts,\n        encryption_opts: Option<EncryptionOpts>,\n        durable_storage: Option<Arc<dyn crate::mvcc::persistent_storage::DurableStorage>>,\n    ) -> Result<IOResult<Arc<Database>>> {\n        let result = Self::open_with_flags_async_internal(\n            state,\n            io,\n            path,\n            db_file,\n            flags,\n            opts,\n            encryption_opts,\n            durable_storage,\n        );\n        if result.is_err() {\n            // registry_guard is set by the open_with_flags_async_internal - so we release it in case of error\n            let _ = state.registry_guard.take();\n        }\n        result\n    }\n\n    #[allow(clippy::too_many_arguments)]\n    fn open_with_flags_async_internal(\n        state: &mut OpenDbAsyncState,\n        io: Arc<dyn IO>,\n        path: &str,\n        db_file: Arc<dyn DatabaseStorage>,\n        flags: OpenFlags,\n        opts: DatabaseOpts,\n        encryption_opts: Option<EncryptionOpts>,\n        durable_storage: Option<Arc<dyn crate::mvcc::persistent_storage::DurableStorage>>,\n    ) -> Result<IOResult<Arc<Database>>> {\n        // turso-sync-engine creates 2 databases with different names in the same IO if MemoryIO is used\n        // in this case we need to bypass registry (as this is MemoryIO DB) but also preserve original distinction in names (e.g. :memory:-draft and :memory:-synced)\n        // so, we bypass registry for all db paths which starts with \":memory:\"\n\n        if matches!(state.phase, OpenDbAsyncPhase::Init) && !path.starts_with(\":memory:\") {\n            // lock the database manager for the whole duration of open_with_flags_async method\n            let registry = DATABASE_MANAGER.lock_arc();\n\n            let canonical_path = std::fs::canonicalize(path)\n                .ok()\n                .and_then(|p| p.to_str().map(|s| s.to_string()))\n                .unwrap_or_else(|| path.to_string());\n\n            // Check if already in registry\n            if let Some(db) = registry.get(&canonical_path).and_then(Weak::upgrade) {\n                tracing::debug!(\"took database {canonical_path:?} from the registry\");\n\n                // Check encryption compatibility using cipher mode (key is not stored in Database for security)\n                let db_is_encrypted = !matches!(db.encryption_cipher_mode.get(), CipherMode::None);\n\n                if db_is_encrypted && encryption_opts.is_none() {\n                    return Err(LimboError::InvalidArgument(\n                        \"Database is encrypted but no encryption options provided\".to_string(),\n                    ));\n                }\n\n                // Found in registry, no need to hold lock\n                return Ok(IOResult::Done(db));\n            }\n\n            // Not in registry, hold the lock and store canonical path for later insertion\n            state.registry_guard = Some(registry);\n            state.canonical_path = Some(canonical_path);\n        }\n\n        // Open the database asynchronously (registry lock is held in state for not `:memory:.*` pathes)\n        let result = Self::open_with_flags_bypass_registry_async(\n            state,\n            io,\n            path,\n            None,\n            db_file,\n            flags,\n            opts,\n            encryption_opts,\n            durable_storage,\n        )?;\n\n        if let IOResult::Done(ref db) = result {\n            // will be unset in case of `:memory:.*` path\n            if let (Some(mut registry), Some(canonical_path)) =\n                (state.registry_guard.take(), state.canonical_path.take())\n            {\n                registry.insert(canonical_path, Arc::downgrade(db));\n            }\n        }\n\n        Ok(result)\n    }\n\n    /// method for tests - for all other code we must use async alternative\n    #[cfg(all(feature = \"fs\", feature = \"conn_raw_api\"))]\n    pub fn open_with_flags_bypass_registry(\n        io: Arc<dyn IO>,\n        path: &str,\n        wal_path: &str,\n        db_file: Arc<dyn DatabaseStorage>,\n        flags: OpenFlags,\n        opts: DatabaseOpts,\n        encryption_opts: Option<EncryptionOpts>,\n    ) -> Result<Arc<Database>> {\n        let mut state = OpenDbAsyncState::new();\n        loop {\n            match Self::open_with_flags_bypass_registry_async(\n                &mut state,\n                io.clone(),\n                path,\n                Some(wal_path),\n                db_file.clone(),\n                flags,\n                opts,\n                encryption_opts.clone(),\n                None,\n            )? {\n                IOResult::Done(db) => return Ok(db),\n                IOResult::IO(io_completion) => {\n                    io_completion.wait(&*io)?;\n                }\n            }\n        }\n    }\n\n    /// Async version of database opening that returns IOResult.\n    /// Caller must drive the IO loop and pass state between calls.\n    /// This is useful for sync engine which needs to yield on IO.\n    #[allow(clippy::too_many_arguments)]\n    pub fn open_with_flags_bypass_registry_async(\n        state: &mut OpenDbAsyncState,\n        io: Arc<dyn IO>,\n        path: &str,\n        wal_path: Option<&str>,\n        db_file: Arc<dyn DatabaseStorage>,\n        flags: OpenFlags,\n        opts: DatabaseOpts,\n        encryption_opts: Option<EncryptionOpts>,\n        durable_storage: Option<Arc<dyn crate::mvcc::persistent_storage::DurableStorage>>,\n    ) -> Result<IOResult<Arc<Database>>> {\n        let result = Self::open_with_flags_bypass_registry_async_internal(\n            state,\n            io,\n            path,\n            wal_path,\n            db_file,\n            flags,\n            opts,\n            encryption_opts,\n            durable_storage,\n        );\n        if result.is_err() {\n            // schema_guard is set by the open_with_flags_bypass_registry_async_internal - so we release it in case of error\n            // registry_guard is not managed by this function - so we don't touch it here and reset in the appropriate place\n            let _ = state.schema_guard.take();\n        }\n        result\n    }\n\n    #[allow(clippy::too_many_arguments)]\n    fn open_with_flags_bypass_registry_async_internal(\n        state: &mut OpenDbAsyncState,\n        io: Arc<dyn IO>,\n        path: &str,\n        wal_path: Option<&str>,\n        db_file: Arc<dyn DatabaseStorage>,\n        flags: OpenFlags,\n        opts: DatabaseOpts,\n        encryption_opts: Option<EncryptionOpts>,\n        durable_storage: Option<Arc<dyn crate::mvcc::persistent_storage::DurableStorage>>,\n    ) -> Result<IOResult<Arc<Database>>> {\n        loop {\n            tracing::debug!(\n                \"open_with_flags_bypass_registry_async: state.phase={:?}\",\n                state.phase\n            );\n            match &state.phase {\n                OpenDbAsyncPhase::Init => {\n                    // Parse encryption key from encryption_opts if provided\n                    let encryption_key = if let Some(ref enc_opts) = encryption_opts {\n                        Some(EncryptionKey::from_hex_string(&enc_opts.hexkey)?)\n                    } else {\n                        None\n                    };\n\n                    let wal_path = if let Some(wal_path) = wal_path {\n                        wal_path\n                    } else {\n                        &format!(\"{path}-wal\")\n                    };\n                    let mut db = Self::new(\n                        opts,\n                        flags,\n                        path,\n                        wal_path,\n                        &io,\n                        db_file.clone(),\n                        encryption_opts.clone(),\n                    )?;\n                    db.durable_storage.clone_from(&durable_storage);\n\n                    let pager = db.header_validation(encryption_key.as_ref())?;\n\n                    #[cfg(debug_assertions)]\n                    {\n                        let wal_enabled = db.shared_wal.read().enabled.load(Ordering::SeqCst);\n                        let mv_store_enabled = db.get_mv_store().is_some();\n                        assert!(\n                            db.is_readonly() || wal_enabled || mv_store_enabled,\n                            \"Either WAL or MVStore must be enabled\"\n                        );\n                    }\n\n                    // Wrap db in Arc before connecting\n                    let db = Arc::new(db);\n\n                    // Check: https://github.com/tursodatabase/turso/pull/1761#discussion_r2154013123\n                    let conn = db._connect(false, Some(pager.clone()), encryption_key.clone())?;\n\n                    // Acquire schema lock and hold it through ReadingHeader and LoadingSchema phases\n                    // to ensure schema_version and make_from_btree are atomic\n                    let guard = db.schema.lock_arc();\n\n                    state.db = Some(db);\n                    state.pager = Some(pager);\n                    state.conn = Some(conn);\n                    state.encryption_key = encryption_key;\n                    state.schema_guard = Some(guard);\n\n                    state.phase = OpenDbAsyncPhase::ReadingHeader;\n                }\n\n                OpenDbAsyncPhase::ReadingHeader => {\n                    let pager = state\n                        .pager\n                        .as_ref()\n                        .expect(\"pager must be initialized in Init phase\");\n                    let header_schema_cookie =\n                        return_if_io!(pager.with_header(|header| header.schema_cookie.get()));\n                    let guard = state\n                        .schema_guard\n                        .as_mut()\n                        .expect(\"schema_guard must be acquired in Init phase\");\n                    // while we logically exclusively own schema as we hold DATABASE_MANAGER lock in the top level `open_with_flags_async_internal` function\n                    // at the moment we already created connection which cloned the schema internally\n                    // so, we can't use get_mut here for now\n                    //\n                    // it's not ideal but correctness is OK - before prepare connection call maybe_update_schema and in case of divergence update schema ref from the db + we always check connection cookie in the VDBE program itself\n                    let schema = Arc::make_mut(&mut **guard);\n                    schema.schema_version = header_schema_cookie;\n\n                    state.phase = OpenDbAsyncPhase::LoadingSchema;\n                }\n\n                OpenDbAsyncPhase::LoadingSchema => {\n                    let pager = state\n                        .pager\n                        .as_ref()\n                        .expect(\"pager must be initialized in Init phase\");\n                    let conn = state\n                        .conn\n                        .as_ref()\n                        .expect(\"conn must be initialized in Init phase\");\n                    let syms = conn.syms.read();\n\n                    let guard = state\n                        .schema_guard\n                        .as_mut()\n                        .expect(\"schema_guard must be acquired in Init phase\");\n                    // while we logically exclusively own schema as we hold DATABASE_MANAGER lock in the top level `open_with_flags_async_internal` function\n                    // at the moment we already created connection which cloned the schema internally\n                    // so, we can't use get_mut here for now\n                    //\n                    // it's not ideal but correctness is OK - before prepare connection call maybe_update_schema and in case of divergence update schema ref from the db + we always check connection cookie in the VDBE program itself\n                    let schema = Arc::make_mut(&mut **guard);\n\n                    let result = schema.make_from_btree(\n                        &mut state.make_from_btree_state,\n                        None,\n                        pager,\n                        &syms,\n                    );\n\n                    match result {\n                        Ok(IOResult::IO(io)) => return Ok(IOResult::IO(io)),\n                        Ok(IOResult::Done(())) => {\n                            // Release the schema lock\n                            state.schema_guard = None;\n                        }\n                        Err(LimboError::ExtensionError(e)) => {\n                            // this means that a vtab exists and we no longer have the module loaded.\n                            // we print a warning to the user to load the module\n                            state.schema_guard = None;\n                            tracing::warn!(\"open warning, failed to load extension: {e}\");\n                        }\n                        Err(e) => return Err(e),\n                    }\n\n                    // Load custom types from __turso_internal_types if the table\n                    // exists and custom types are enabled. The schema loaded by\n                    // make_from_btree includes the table definition but not its\n                    // contents. We need to read the stored type definitions so\n                    // that DECODE/ENCODE and affinity metadata are available to\n                    // all subsequent connections.\n                    if opts.enable_custom_types {\n                        let conn = state\n                            .conn\n                            .as_ref()\n                            .expect(\"conn must be initialized in Init phase\");\n                        // Sync the connection's schema from the database so it\n                        // can query __turso_internal_types.\n                        conn.maybe_update_schema();\n                        let load_result: Result<()> = (|| {\n                            let type_sqls = conn.query_stored_type_definitions()?;\n                            if !type_sqls.is_empty() {\n                                let db = state\n                                    .db\n                                    .as_ref()\n                                    .expect(\"db must be initialized in Init phase\");\n                                db.with_schema_mut(|schema| {\n                                    schema.load_type_definitions(&type_sqls)\n                                })?;\n                            }\n                            Ok(())\n                        })();\n                        if let Err(e) = load_result {\n                            tracing::warn!(\"Failed to load custom types during open: {}\", e);\n                        }\n                    }\n\n                    state.phase = OpenDbAsyncPhase::BootstrapMvStore;\n                }\n\n                OpenDbAsyncPhase::BootstrapMvStore => {\n                    let db = state\n                        .db\n                        .as_ref()\n                        .expect(\"db must be initialized in Init phase\");\n                    let pager = state\n                        .pager\n                        .as_ref()\n                        .expect(\"pager must be initialized in Init phase\");\n\n                    if let Some(mv_store) = db.get_mv_store().as_ref() {\n                        let mvcc_bootstrap_conn =\n                            db._connect(true, Some(pager.clone()), state.encryption_key.clone())?;\n                        mv_store.bootstrap(mvcc_bootstrap_conn)?;\n                    }\n\n                    state.phase = OpenDbAsyncPhase::Done;\n                    return Ok(IOResult::Done(\n                        state\n                            .db\n                            .take()\n                            .expect(\"db must be initialized in Init phase\"),\n                    ));\n                }\n\n                OpenDbAsyncPhase::Done => {\n                    panic!(\"open_with_flags_bypass_registry_async called after completion\");\n                }\n            }\n        }\n    }\n\n    /// Necessary Pager initialization, so that we are prepared to read from Page 1.\n    /// For encrypted databases, the encryption key must be provided to properly decrypt page 1.\n    pub(crate) fn _init(&self, encryption_key: Option<&EncryptionKey>) -> Result<Pager> {\n        let pager = self.init_pager(None)?;\n        pager.enable_encryption(self.opts.enable_encryption);\n\n        // Set up encryption context BEFORE reading the header page.\n        // For encrypted databases, page 1 has:\n        // - Bytes 0-15: Turso magic header (replaces SQLite magic)\n        // - Bytes 16-100: Unencrypted header metadata\n        // - Bytes 100+: Encrypted content\n        // The encryption context is needed to properly decrypt page 1 when reopening.\n        if let Some(key) = encryption_key {\n            let cipher_mode = self.encryption_cipher_mode.get();\n            pager.set_encryption_context(cipher_mode, key)?;\n        }\n\n        // Start read transaction before reading page 1 to acquire a read lock\n        // that prevents concurrent checkpoints from truncating the WAL\n        pager.begin_read_tx()?;\n\n        // Read header within the read transaction, ensuring cleanup on error\n        let result = (|| -> Result<AutoVacuumMode> {\n            let header_ref = pager.io.block(|| HeaderRef::from_pager(&pager))?;\n            let header = header_ref.borrow();\n\n            let mode = if header.vacuum_mode_largest_root_page.get() > 0 {\n                if header.incremental_vacuum_enabled.get() > 0 {\n                    AutoVacuumMode::Incremental\n                } else {\n                    AutoVacuumMode::Full\n                }\n            } else {\n                AutoVacuumMode::None\n            };\n\n            Ok(mode)\n        })();\n\n        // Always end read transaction, even on error\n        pager.end_read_tx();\n\n        let mode = result?;\n\n        pager.set_auto_vacuum_mode(mode);\n\n        Ok(pager)\n    }\n\n    /// Checks the Version numbers in the DatabaseHeader, and changes it according to the required options\n    ///\n    /// Will also open MVStore and WAL if needed\n    fn header_validation(&mut self, encryption_key: Option<&EncryptionKey>) -> Result<Arc<Pager>> {\n        let log_exists = journal_mode::logical_log_exists(std::path::Path::new(&self.path));\n        let is_readonly = self.open_flags.contains(OpenFlags::ReadOnly);\n\n        let mut pager = self._init(encryption_key)?;\n        turso_assert!(pager.wal.is_none(), \"Pager should have no WAL yet\");\n\n        let is_autovacuumed_db = self.io.block(|| {\n            pager.with_header(|header| {\n                header.vacuum_mode_largest_root_page.get() > 0\n                    || header.incremental_vacuum_enabled.get() > 0\n            })\n        })?;\n\n        if is_autovacuumed_db && !self.opts.enable_autovacuum {\n            tracing::warn!(\n                        \"Database has autovacuum enabled but --experimental-autovacuum flag is not set. Opening in readonly mode.\"\n                    );\n            self.open_flags |= OpenFlags::ReadOnly;\n        }\n\n        let header: HeaderRefMut = self.io.block(|| HeaderRefMut::from_pager(&pager))?;\n        let header_mut = header.borrow_mut();\n\n        if !header_mut.text_encoding.is_utf8() {\n            return Err(LimboError::UnsupportedEncoding(\n                header_mut.text_encoding.to_string(),\n            ));\n        }\n\n        let (read_version, write_version) = { (header_mut.read_version, header_mut.write_version) };\n\n        if encryption_key.is_none() && header_mut.magic != SQLITE_HEADER {\n            tracing::error!(\n                \"invalid value of database header magic bytes: {:?}\",\n                header_mut.magic\n            );\n            return Err(LimboError::NotADB);\n        }\n        // when we open fresh db with encryption params - header will be SQLite at this point\n        if encryption_key.is_some()\n            && (header_mut.magic != SQLITE_HEADER\n                && !header_mut.magic.starts_with(TURSO_HEADER_PREFIX))\n        {\n            tracing::error!(\n                \"invalid value of database header magic bytes: {:?}\",\n                header_mut.magic\n            );\n            return Err(LimboError::NotADB);\n        }\n\n        // TODO: right now we don't support READ ONLY and no READ or WRITE in the Version header\n        // https://www.sqlite.org/fileformat.html#file_format_version_numbers\n        if read_version != write_version {\n            return Err(LimboError::Corrupt(format!(\n                \"Read version `{read_version:?}` is not equal to Write version `{write_version:?} in database header`\"\n            )));\n        }\n\n        let (read_version, _write_version) = (\n            read_version\n                .to_version()\n                .map_err(|val| LimboError::Corrupt(format!(\"Invalid read_version: {val}\")))?,\n            write_version\n                .to_version()\n                .map_err(|val| LimboError::Corrupt(format!(\"Invalid write_version: {val}\")))?,\n        );\n\n        // Validate fixed header fields per SQLite spec\n        if header_mut.max_embed_frac != 64 {\n            return Err(LimboError::Corrupt(format!(\n                \"Invalid max_embed_frac: expected 64, got {}\",\n                header_mut.max_embed_frac\n            )));\n        }\n        if header_mut.min_embed_frac != 32 {\n            return Err(LimboError::Corrupt(format!(\n                \"Invalid min_embed_frac: expected 32, got {}\",\n                header_mut.min_embed_frac\n            )));\n        }\n        if header_mut.leaf_frac != 32 {\n            return Err(LimboError::Corrupt(format!(\n                \"Invalid leaf_frac: expected 32, got {}\",\n                header_mut.leaf_frac\n            )));\n        }\n        let schema_format = header_mut.schema_format.get();\n        // If the database is completely empty, if it has no schema, then the schema format number can be zero.\n        if !(0..=4).contains(&schema_format) {\n            return Err(LimboError::Corrupt(format!(\n                \"Invalid schema_format: expected 1-4, got {schema_format}\"\n            )));\n        }\n        if !matches!(\n            header_mut.text_encoding,\n            TextEncoding::Unset\n                | TextEncoding::Utf8\n                | TextEncoding::Utf16Le\n                | TextEncoding::Utf16Be\n        ) {\n            return Err(LimboError::Corrupt(format!(\n                \"Invalid text_encoding: {}\",\n                header_mut.text_encoding\n            )));\n        }\n        if !matches!(\n            header_mut.text_encoding,\n            TextEncoding::Unset | TextEncoding::Utf8\n        ) {\n            return Err(LimboError::Corrupt(format!(\n                \"Only utf8 text_encoding is supported by tursodb: got={}\",\n                header_mut.text_encoding\n            )));\n        }\n\n        // Determine if we should open in MVCC mode based on the database header version\n        // MVCC is controlled only by the database header (set via PRAGMA journal_mode)\n        let open_mv_store = matches!(read_version, Version::Mvcc);\n\n        // Now check the Header Version to see which mode the DB file really is on\n        // Track if header was modified so we can write it to disk\n        let header_modified = match read_version {\n            Version::Legacy => {\n                if is_readonly {\n                    tracing::warn!(\"Database {} is opened in readonly mode, cannot convert Legacy mode to WAL. Running in Legacy mode.\", self.path);\n                    false\n                } else {\n                    // Convert Legacy to WAL mode\n                    header_mut.read_version = RawVersion::from(Version::Wal);\n                    header_mut.write_version = RawVersion::from(Version::Wal);\n                    true\n                }\n            }\n            Version::Wal => false,\n            Version::Mvcc => false,\n        };\n\n        // In WAL mode, a logical log is always unexpected.\n        // In MVCC mode, WAL and logical-log coexistence can happen across interrupted checkpoint\n        // recovery and is reconciled in MvStore::bootstrap().\n        if !open_mv_store && log_exists {\n            return Err(LimboError::Corrupt(format!(\n                \"MVCC logical log file exists for database {}, but database header indicates WAL mode. The database may be corrupted.\",\n                self.path\n            )));\n        }\n\n        // If header was modified, write it directly to disk before we clear the cache\n        // This must happen before WAL is attached since we need to write directly to the DB file\n        if header_modified {\n            let completion =\n                storage::sqlite3_ondisk::begin_write_btree_page(&pager, header.page())?;\n            self.io.wait_for_completion(completion)?;\n        }\n\n        drop(header);\n\n        let flags = self.open_flags;\n\n        // Always Open shared wal and set it in the Database and Pager.\n        // MVCC currently requires a WAL open to function\n        let shared_wal = WalFileShared::open_shared_if_exists(&self.io, &self.wal_path, flags)?;\n\n        let last_checksum_and_max_frame = shared_wal.read().last_checksum_and_max_frame();\n        let wal = Arc::new(WalFile::new(\n            self.io.clone(),\n            Arc::clone(&shared_wal),\n            last_checksum_and_max_frame,\n            pager.buffer_pool.clone(),\n        ));\n\n        self.shared_wal = shared_wal;\n        pager.set_wal(wal);\n\n        // Clear page cache after attaching WAL since pages may have been cached\n        // from disk reads before WAL was attached. The WAL may contain newer\n        // versions of these pages (e.g., page 1 with updated schema_cookie).\n        pager.clear_page_cache(true);\n        pager.set_schema_cookie(None);\n\n        if open_mv_store {\n            // todo(v): pass required encryption ctx to enable encryption with mvcc\n            let mv_store = journal_mode::open_mv_store(\n                self.io.clone(),\n                &self.path,\n                self.open_flags,\n                self.durable_storage.clone(),\n                None,\n            )?;\n            self.mv_store.store(Some(mv_store));\n        }\n\n        Ok(Arc::new(pager))\n    }\n\n    #[instrument(skip_all, level = Level::INFO)]\n    pub fn connect(self: &Arc<Database>) -> Result<Arc<Connection>> {\n        self._connect(false, None, None)\n    }\n\n    /// Connect with an encryption key.\n    /// Use this when opening an encrypted database where the key is known at connect time.\n    #[instrument(skip_all, level = Level::INFO)]\n    pub fn connect_with_encryption(\n        self: &Arc<Database>,\n        encryption_key: Option<EncryptionKey>,\n    ) -> Result<Arc<Connection>> {\n        self._connect(false, None, encryption_key)\n    }\n\n    #[instrument(skip_all, level = Level::INFO)]\n    fn _connect(\n        self: &Arc<Database>,\n        is_mvcc_bootstrap_connection: bool,\n        pager: Option<Arc<Pager>>,\n        encryption_key: Option<EncryptionKey>,\n    ) -> Result<Arc<Connection>> {\n        let pager = if let Some(pager) = pager {\n            pager\n        } else {\n            // Pass encryption key to _init so it can set up encryption context\n            // before reading page 1. This is required for reopening encrypted databases.\n            Arc::new(self._init(encryption_key.as_ref())?)\n        };\n        let page_size = pager.get_page_size_unchecked();\n\n        let default_cache_size = pager\n            .io\n            .block(|| pager.with_header(|header| header.default_page_cache_size))\n            .unwrap_or_default()\n            .get();\n\n        let encryption_cipher = self.encryption_cipher_mode.get();\n\n        let conn = Arc::new(Connection {\n            db: self.clone(),\n            pager: ArcSwap::new(pager),\n            schema: RwLock::new(self.schema.lock().clone()),\n            database_schemas: RwLock::new(HashMap::default()),\n            auto_commit: AtomicBool::new(true),\n            transaction_state: AtomicTransactionState::new(TransactionState::None),\n            last_insert_rowid: AtomicI64::new(0),\n            last_change: AtomicI64::new(0),\n            total_changes: AtomicI64::new(0),\n            syms: parking_lot::RwLock::new(SymbolTable::new()),\n            _shared_cache: false,\n            cache_size: AtomicI32::new(default_cache_size),\n            page_size: AtomicU16::new(page_size.get_raw()),\n            wal_auto_checkpoint_disabled: AtomicBool::new(false),\n            capture_data_changes: RwLock::new(None),\n            cdc_transaction_id: AtomicI64::new(-1),\n            closed: AtomicBool::new(false),\n            attached_databases: RwLock::new(DatabaseCatalog::new()),\n            query_only: AtomicBool::new(false),\n            dml_require_where: AtomicBool::new(false),\n            mv_tx: RwLock::new(None),\n            attached_mv_txs: RwLock::new(HashMap::default()),\n            view_transaction_states: AllViewsTxState::new(),\n            metrics: RwLock::new(ConnectionMetrics::new()),\n            nestedness: AtomicI32::new(0),\n            compiling_triggers: RwLock::new(Vec::new()),\n            executing_triggers: RwLock::new(Vec::new()),\n            encryption_key: RwLock::new(encryption_key),\n            encryption_cipher_mode: AtomicCipherMode::new(encryption_cipher),\n            sync_mode: AtomicSyncMode::new(SyncMode::Full),\n            temp_store: AtomicTempStore::new(TempStore::Default),\n            data_sync_retry: AtomicBool::new(false),\n            busy_handler: RwLock::new(BusyHandler::None),\n            is_mvcc_bootstrap_connection: AtomicBool::new(is_mvcc_bootstrap_connection),\n            fk_pragma: AtomicBool::new(false),\n            fk_deferred_violations: AtomicIsize::new(0),\n            n_active_writes: AtomicI32::new(0),\n            check_constraints_pragma: AtomicBool::new(false),\n            vtab_txn_states: RwLock::new(HashSet::default()),\n            prepare_context_generation: AtomicU64::new(0),\n        });\n        self.n_connections\n            .fetch_add(1, crate::sync::atomic::Ordering::SeqCst);\n        let builtin_syms = self.builtin_syms.read();\n        // add built-in extensions symbols to the connection to prevent having to load each time\n        conn.syms.write().extend(&builtin_syms);\n        refresh_analyze_stats(&conn);\n        Ok(conn)\n    }\n\n    pub fn is_readonly(&self) -> bool {\n        self.open_flags.contains(OpenFlags::ReadOnly)\n    }\n\n    /// If we do not have a physical WAL file, but we know the database file is initialized on disk,\n    /// we need to read the page_size from the database header.\n    fn read_page_size_from_db_header(&self) -> Result<PageSize> {\n        turso_assert!(\n            self.initialized(),\n            \"read_reserved_space_bytes_from_db_header called on uninitialized database\"\n        );\n        turso_assert!(\n            PageSize::MIN % 512 == 0,\n            \"header read must be a multiple of 512 for O_DIRECT\"\n        );\n        let buf = Arc::new(Buffer::new_temporary(PageSize::MIN as usize));\n        let c = new_header_read_completion(buf.clone());\n        let c = self.db_file.read_header(c)?;\n        self.io.wait_for_completion(c)?;\n        let page_size = u16::from_be_bytes(buf.as_slice()[16..18].try_into().unwrap());\n        let page_size = PageSize::new_from_header_u16(page_size)?;\n        Ok(page_size)\n    }\n\n    fn read_reserved_space_bytes_from_db_header(&self) -> Result<u8> {\n        turso_assert!(\n            self.initialized(),\n            \"read_reserved_space_bytes_from_db_header called on uninitialized database\"\n        );\n        turso_assert!(\n            PageSize::MIN % 512 == 0,\n            \"header read must be a multiple of 512 for O_DIRECT\"\n        );\n        let buf = Arc::new(Buffer::new_temporary(PageSize::MIN as usize));\n        let c = new_header_read_completion(buf.clone());\n        let c = self.db_file.read_header(c)?;\n        self.io.wait_for_completion(c)?;\n        let reserved_bytes = u8::from_be_bytes(buf.as_slice()[20..21].try_into().unwrap());\n        Ok(reserved_bytes)\n    }\n\n    /// Read the page size in order of preference:\n    /// 1. From the WAL header if it exists and is initialized\n    /// 2. From the database header if the database is initialized\n    ///\n    /// Otherwise, fall back to, in order of preference:\n    /// 1. From the requested page size if it is provided\n    /// 2. PageSize::default(), i.e. 4096\n    fn determine_actual_page_size(\n        &self,\n        shared_wal: &WalFileShared,\n        requested_page_size: Option<usize>,\n    ) -> Result<PageSize> {\n        if shared_wal.enabled.load(Ordering::SeqCst) {\n            let size_in_wal = shared_wal.page_size();\n            if size_in_wal != 0 {\n                let Some(page_size) = PageSize::new(size_in_wal) else {\n                    bail_corrupt_error!(\"invalid page size in WAL: {size_in_wal}\");\n                };\n                return Ok(page_size);\n            }\n        }\n        if self.initialized() {\n            Ok(self.read_page_size_from_db_header()?)\n        } else {\n            let Some(size) = requested_page_size else {\n                return Ok(PageSize::default());\n            };\n            let Some(page_size) = PageSize::new(size as u32) else {\n                bail_corrupt_error!(\"invalid requested page size: {size}\");\n            };\n            Ok(page_size)\n        }\n    }\n\n    /// if the database is initialized i.e. it exists on disk, return the reserved space bytes from\n    /// the header or None\n    fn maybe_get_reserved_space_bytes(&self) -> Result<Option<u8>> {\n        if self.initialized() {\n            Ok(Some(self.read_reserved_space_bytes_from_db_header()?))\n        } else {\n            Ok(None)\n        }\n    }\n\n    fn init_pager(&self, requested_page_size: Option<usize>) -> Result<Pager> {\n        let cipher = self.encryption_cipher_mode.get();\n        let reserved_bytes = self.maybe_get_reserved_space_bytes()?.or_else(|| {\n            if !matches!(cipher, CipherMode::None) {\n                // For encryption, use the cipher's metadata size\n                Some(cipher.metadata_size() as u8)\n            } else {\n                // For non-encrypted databases, don't set reserved_bytes here.\n                // This allows checksums to be enabled by default (disable_checksums will be false).\n                None\n            }\n        });\n        let disable_checksums = if let Some(reserved_bytes) = reserved_bytes {\n            // if the required reserved bytes for checksums is not present, disable checksums\n            reserved_bytes != CHECKSUM_REQUIRED_RESERVED_BYTES\n        } else {\n            false\n        };\n        // Check if WAL is enabled\n        let shared_wal = self.shared_wal.read();\n\n        let page_size = self.determine_actual_page_size(&shared_wal, requested_page_size)?;\n\n        let buffer_pool = self.buffer_pool.clone();\n        if self.initialized() {\n            buffer_pool.finalize_with_page_size(page_size.get() as usize)?;\n        }\n\n        let pager_wal: Option<Arc<dyn Wal>> = if shared_wal.enabled.load(Ordering::SeqCst) {\n            Some(Arc::new(WalFile::new(\n                self.io.clone(),\n                self.shared_wal.clone(),\n                shared_wal.last_checksum_and_max_frame(),\n                buffer_pool.clone(),\n            )))\n        } else {\n            None\n        };\n\n        let pager = Pager::new(\n            self.db_file.clone(),\n            pager_wal,\n            self.io.clone(),\n            PageCache::default(),\n            buffer_pool,\n            self.init_lock.clone(),\n            self.init_page_1.clone(),\n        )?;\n        pager.set_page_size(page_size);\n        if let Some(reserved_bytes) = reserved_bytes {\n            pager.set_reserved_space_bytes(reserved_bytes);\n        }\n        if disable_checksums {\n            pager.reset_checksum_context();\n        }\n\n        Ok(pager)\n    }\n\n    #[cfg(feature = \"fs\")]\n    pub fn io_for_path(path: &str) -> Result<Arc<dyn IO>> {\n        use crate::util::MEMORY_PATH;\n        let io: Arc<dyn IO> = match path.trim() {\n            MEMORY_PATH => Arc::new(MemoryIO::new()),\n            _ => Arc::new(PlatformIO::new()?),\n        };\n        Ok(io)\n    }\n\n    #[cfg(feature = \"fs\")]\n    pub fn io_for_vfs<S: AsRef<str> + std::fmt::Display>(vfs: S) -> Result<Arc<dyn IO>> {\n        let vfsmods = ext::add_builtin_vfs_extensions(None)?;\n        let io: Arc<dyn IO> = match vfsmods\n            .iter()\n            .find(|v| v.0 == vfs.as_ref())\n            .map(|v| v.1.clone())\n        {\n            Some(vfs) => vfs,\n            None => match vfs.as_ref() {\n                \"memory\" => Arc::new(MemoryIO::new()),\n                \"syscall\" => Arc::new(SyscallIO::new()?),\n                #[cfg(all(target_os = \"linux\", feature = \"io_uring\", not(miri)))]\n                \"io_uring\" => Arc::new(UringIO::new()?),\n                #[cfg(all(target_os = \"windows\", feature = \"experimental_win_iocp\", not(miri)))]\n                \"experimental_win_iocp\" => Arc::new(WindowsIOCP::new()?),\n\n                other => {\n                    return Err(LimboError::InvalidArgument(format!(\"no such VFS: {other}\")));\n                }\n            },\n        };\n        Ok(io)\n    }\n\n    /// Open a new database file with optionally specifying a VFS without an existing database\n    /// connection and symbol table to register extensions.\n    #[cfg(feature = \"fs\")]\n    pub fn open_new<S>(\n        path: &str,\n        vfs: Option<S>,\n        flags: OpenFlags,\n        opts: DatabaseOpts,\n        encryption_opts: Option<EncryptionOpts>,\n    ) -> Result<(Arc<dyn IO>, Arc<Database>)>\n    where\n        S: AsRef<str> + std::fmt::Display,\n    {\n        let io = vfs\n            .map(|vfs| Self::io_for_vfs(vfs))\n            .or_else(|| Some(Self::io_for_path(path)))\n            .transpose()?\n            .unwrap();\n        let db = Self::open_file_with_flags(io.clone(), path, flags, opts, encryption_opts)?;\n        Ok((io, db))\n    }\n\n    #[inline]\n    pub(crate) fn initialized(&self) -> bool {\n        self.init_page_1.load().is_none()\n    }\n\n    pub(crate) fn can_load_extensions(&self) -> bool {\n        self.opts.enable_load_extension\n    }\n\n    #[inline]\n    pub(crate) fn with_schema_mut<T>(&self, f: impl FnOnce(&mut Schema) -> Result<T>) -> Result<T> {\n        let mut schema_ref = self.schema.lock();\n        let schema = Arc::make_mut(&mut *schema_ref);\n        f(schema)\n    }\n    pub(crate) fn clone_schema(&self) -> Arc<Schema> {\n        let schema = self.schema.lock();\n        schema.clone()\n    }\n\n    pub(crate) fn update_schema_if_newer(&self, another: Arc<Schema>) {\n        let mut schema = self.schema.lock();\n        if schema.schema_version < another.schema_version {\n            tracing::debug!(\n                \"DB schema is outdated: {} < {}\",\n                schema.schema_version,\n                another.schema_version\n            );\n            *schema = another;\n        } else {\n            tracing::debug!(\n                \"DB schema is up to date: {} >= {}\",\n                schema.schema_version,\n                another.schema_version\n            );\n        }\n    }\n\n    pub fn get_mv_store(&self) -> impl Deref<Target = Option<Arc<MvStore>>> {\n        self.mv_store.load()\n    }\n\n    pub fn experimental_views_enabled(&self) -> bool {\n        self.opts.enable_views\n    }\n\n    pub fn experimental_index_method_enabled(&self) -> bool {\n        self.opts.enable_index_method\n    }\n\n    pub fn experimental_custom_types_enabled(&self) -> bool {\n        self.opts.enable_custom_types\n    }\n\n    pub fn experimental_attach_enabled(&self) -> bool {\n        self.opts.enable_attach\n    }\n\n    /// check if database is currently in MVCC mode\n    pub fn mvcc_enabled(&self) -> bool {\n        self.mv_store.load().is_some()\n    }\n\n    #[cfg(feature = \"test_helper\")]\n    pub fn set_pending_byte(val: u32) {\n        Pager::set_pending_byte(val);\n    }\n\n    #[cfg(feature = \"test_helper\")]\n    pub fn get_pending_byte() -> u32 {\n        Pager::get_pending_byte()\n    }\n}\n\n#[derive(Debug, Clone, Eq, PartialEq)]\npub enum CaptureDataChangesMode {\n    Id,\n    Before,\n    After,\n    Full,\n}\n\n/// CDC schema version with integer ordering for feature checks.\n/// Higher versions are supersets of lower versions.\n#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]\n#[repr(u8)]\npub enum CdcVersion {\n    /// 8 columns: change_id, change_time, change_type, table_name, id, before, after, updates\n    V1 = 1,\n    /// 9 columns (adds change_txn_id + COMMIT records with change_type=2)\n    V2 = 2,\n}\n\npub const CDC_VERSION_CURRENT: CdcVersion = CdcVersion::V2;\n\nimpl CdcVersion {\n    /// Whether this version emits COMMIT records (change_type=2)\n    pub fn has_commit_record(self) -> bool {\n        self >= CdcVersion::V2\n    }\n}\n\nimpl std::fmt::Display for CdcVersion {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            CdcVersion::V1 => write!(f, \"v1\"),\n            CdcVersion::V2 => write!(f, \"v2\"),\n        }\n    }\n}\n\nimpl std::str::FromStr for CdcVersion {\n    type Err = LimboError;\n    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {\n        match s {\n            \"v1\" => Ok(CdcVersion::V1),\n            \"v2\" => Ok(CdcVersion::V2),\n            _ => Err(LimboError::InternalError(format!(\n                \"unexpected CDC version: {s}\"\n            ))),\n        }\n    }\n}\n\n#[derive(Debug, Clone, Eq, PartialEq)]\npub struct CaptureDataChangesInfo {\n    pub mode: CaptureDataChangesMode,\n    pub table: String,\n    pub version: Option<CdcVersion>,\n}\n\nimpl CaptureDataChangesInfo {\n    pub fn parse(\n        value: &str,\n        version: Option<CdcVersion>,\n    ) -> Result<Option<CaptureDataChangesInfo>> {\n        let (mode, table) = value\n            .split_once(\",\")\n            .unwrap_or((value, TURSO_CDC_DEFAULT_TABLE_NAME));\n        match mode {\n            \"off\" => Ok(None),\n            \"id\" => Ok(Some(CaptureDataChangesInfo { mode: CaptureDataChangesMode::Id, table: table.to_string(), version })),\n            \"before\" => Ok(Some(CaptureDataChangesInfo { mode: CaptureDataChangesMode::Before, table: table.to_string(), version })),\n            \"after\" => Ok(Some(CaptureDataChangesInfo { mode: CaptureDataChangesMode::After, table: table.to_string(), version })),\n            \"full\" => Ok(Some(CaptureDataChangesInfo { mode: CaptureDataChangesMode::Full, table: table.to_string(), version })),\n            _ => Err(LimboError::InvalidArgument(\n                \"unexpected pragma value: expected '<mode>' or '<mode>,<cdc-table-name>' parameter where mode is one of off|id|before|after|full\".to_string(),\n            ))\n        }\n    }\n    pub fn has_updates(&self) -> bool {\n        self.mode == CaptureDataChangesMode::Full\n    }\n    pub fn has_after(&self) -> bool {\n        matches!(\n            self.mode,\n            CaptureDataChangesMode::After | CaptureDataChangesMode::Full\n        )\n    }\n    pub fn has_before(&self) -> bool {\n        matches!(\n            self.mode,\n            CaptureDataChangesMode::Before | CaptureDataChangesMode::Full\n        )\n    }\n    pub fn mode_name(&self) -> &str {\n        match self.mode {\n            CaptureDataChangesMode::Id => \"id\",\n            CaptureDataChangesMode::Before => \"before\",\n            CaptureDataChangesMode::After => \"after\",\n            CaptureDataChangesMode::Full => \"full\",\n        }\n    }\n    pub fn cdc_version(&self) -> CdcVersion {\n        self.version.unwrap_or(CDC_VERSION_CURRENT)\n    }\n}\n\n/// Convenience methods for `Option<CaptureDataChangesInfo>` to keep call sites simple.\npub trait CaptureDataChangesExt {\n    fn has_updates(&self) -> bool;\n    fn has_after(&self) -> bool;\n    fn has_before(&self) -> bool;\n    fn table(&self) -> Option<&str>;\n}\n\nimpl CaptureDataChangesExt for Option<CaptureDataChangesInfo> {\n    fn has_updates(&self) -> bool {\n        self.as_ref().is_some_and(|i| i.has_updates())\n    }\n    fn has_after(&self) -> bool {\n        self.as_ref().is_some_and(|i| i.has_after())\n    }\n    fn has_before(&self) -> bool {\n        self.as_ref().is_some_and(|i| i.has_before())\n    }\n    fn table(&self) -> Option<&str> {\n        self.as_ref().map(|i| i.table.as_str())\n    }\n}\n\n// Optimized for fast get() operations and supports unlimited attached databases.\npub(crate) struct DatabaseCatalog {\n    name_to_index: HashMap<String, usize>,\n    allocated: Vec<u64>,\n    index_to_data: HashMap<usize, (Arc<Database>, Arc<Pager>)>,\n}\n\n#[allow(unused)]\nimpl DatabaseCatalog {\n    pub(crate) fn new() -> Self {\n        Self {\n            name_to_index: HashMap::default(),\n            index_to_data: HashMap::default(),\n            allocated: vec![3], // 0 | 1, as those are reserved for main and temp\n        }\n    }\n\n    fn get_database_by_index(&self, index: usize) -> Option<Arc<Database>> {\n        self.index_to_data\n            .get(&index)\n            .map(|(db, _pager)| db.clone())\n    }\n\n    fn get_name_by_index(&self, index: usize) -> Option<String> {\n        self.name_to_index\n            .iter()\n            .find(|(_, &idx)| idx == index)\n            .map(|(name, _)| name.clone())\n    }\n\n    fn get_database_by_name(&self, s: &str) -> Option<(usize, Arc<Database>)> {\n        match self.name_to_index.get(s) {\n            None => None,\n            Some(idx) => self\n                .index_to_data\n                .get(idx)\n                .map(|(db, _pager)| (*idx, db.clone())),\n        }\n    }\n\n    fn get_pager_by_index(&self, idx: &usize) -> Arc<Pager> {\n        let (_db, pager) = self\n            .index_to_data\n            .get(idx)\n            .expect(\"If we are looking up a database by index, it must exist.\");\n        pager.clone()\n    }\n\n    fn add(&mut self, s: &str) -> usize {\n        turso_assert!(\n            !self.name_to_index.contains_key(s),\n            \"lib: database name already exists in catalog\",\n            { \"name\": s }\n        );\n\n        let index = self.allocate_index();\n        self.name_to_index.insert(s.to_string(), index);\n        index\n    }\n\n    fn insert(&mut self, s: &str, data: (Arc<Database>, Arc<Pager>)) -> usize {\n        let idx = self.add(s);\n        self.index_to_data.insert(idx, data);\n        idx\n    }\n\n    fn remove(&mut self, s: &str) -> Option<usize> {\n        if let Some(index) = self.name_to_index.remove(s) {\n            // Should be impossible to remove main or temp.\n            turso_assert_greater_than_or_equal!(index, 2);\n            self.deallocate_index(index);\n            self.index_to_data.remove(&index);\n            Some(index)\n        } else {\n            None\n        }\n    }\n\n    #[inline(always)]\n    fn deallocate_index(&mut self, index: usize) {\n        let word_idx = index / 64;\n        let bit_idx = index % 64;\n\n        if word_idx < self.allocated.len() {\n            self.allocated[word_idx] &= !(1u64 << bit_idx);\n        }\n    }\n\n    fn allocate_index(&mut self) -> usize {\n        for word_idx in 0..self.allocated.len() {\n            let word = self.allocated[word_idx];\n\n            if word != u64::MAX {\n                let free_bit = Self::find_first_zero_bit(word);\n                let index = word_idx * 64 + free_bit;\n\n                self.allocated[word_idx] |= 1u64 << free_bit;\n\n                return index;\n            }\n        }\n\n        // Need to expand bitmap\n        let word_idx = self.allocated.len();\n        self.allocated.push(1u64); // Mark first bit as allocated\n        word_idx * 64\n    }\n\n    #[inline(always)]\n    fn find_first_zero_bit(word: u64) -> usize {\n        // Invert to find first zero as first one\n        let inverted = !word;\n\n        // Use trailing zeros count (compiles to single instruction on most CPUs)\n        inverted.trailing_zeros() as usize\n    }\n}\n\npub struct QueryRunner<'a> {\n    parser: Parser<'a>,\n    conn: &'a Arc<Connection>,\n    statements: &'a [u8],\n    last_offset: usize,\n}\n\nimpl<'a> QueryRunner<'a> {\n    pub(crate) fn new(conn: &'a Arc<Connection>, statements: &'a [u8]) -> Self {\n        Self {\n            parser: Parser::new(statements),\n            conn,\n            statements,\n            last_offset: 0,\n        }\n    }\n}\n\nimpl Iterator for QueryRunner<'_> {\n    type Item = Result<Option<Statement>>;\n\n    fn next(&mut self) -> Option<Self::Item> {\n        match self.parser.next_cmd() {\n            Ok(Some(cmd)) => {\n                let byte_offset_end = self.parser.offset();\n                let input = str::from_utf8(&self.statements[self.last_offset..byte_offset_end])\n                    .unwrap()\n                    .trim();\n                self.last_offset = byte_offset_end;\n                Some(self.conn.run_cmd(cmd, input))\n            }\n            Ok(None) => None,\n            Err(err) => Some(Result::Err(LimboError::from(err))),\n        }\n    }\n}\n"
  },
  {
    "path": "core/mvcc/clock.rs",
    "content": "use crate::sync::Mutex;\n\n/// No-op callback for use with [`LogicalClock::get_timestamp`] when no\n/// action needs to be taken atomically alongside timestamp generation\n/// (e.g. for begin timestamps).\npub fn no_op(_: u64) {}\n\n/// Logical clock.\npub trait LogicalClock: Send + Sync {\n    /// Generates the next timestamp, calls `f` with it, then returns it.\n    ///\n    /// Implementations that guard concurrent commit protocols (e.g.\n    /// [`MvccClock`]) hold their internal lock across the `f` call, so\n    /// that the timestamp is published (e.g. stored as `Preparing(ts)`)\n    /// before any other caller can observe a timestamp.\n    ///\n    /// Pass [`no_op`] when no atomic side-effect is needed (begin timestamps).\n    fn get_timestamp<F: FnOnce(u64)>(&self, f: F) -> u64;\n    fn reset(&self, ts: u64);\n}\n\n/// A mutex-guarded clock for concurrent MVCC use.\n///\n/// The lock is held across the `f` callback in [`get_timestamp`], ensuring\n/// that a commit timestamp is published (e.g. stored as `Preparing(ts)`)\n/// before any other transaction can generate a higher timestamp. This closes\n/// the TOCTOU window between timestamp generation and `Preparing` state\n/// publication in the commit protocol.\n///\n/// ## Speculative reads\n///\n/// We have speculative reads (and speculative ignores). That is, an active\n/// transaction can see changes of another transaction which is in the\n/// **preparing** phase. Assuming the other transaction successfully commits,\n/// the active transaction continues to make progress. If the other transaction\n/// gets aborted, then the active transaction needs to be aborted as well.\n///\n/// So, say `tx2` starts at `begin_ts(11)` and another transaction `tx1`,\n/// started earlier, is now in its preparing phase with `end_ts(10)`. Once the\n/// `end_ts` is assigned, that will be the final commit timestamp of that\n/// transaction. So `tx2` should see changes made by `tx1`, since `tx1` was\n/// committed (in logical time) before `tx2` started.\n///\n/// Whether `tx2` can see `tx1`'s changes depends on when `tx1` acquired the\n/// `end_ts` timestamp during the preparing phase.\n///\n/// > **Note:** We need speculative reads, otherwise it's difficult to make\n/// > the MVCC model work without blocking. I made an attempt in\n/// > [turso#5198](https://github.com/tursodatabase/turso/pull/5198) but this\n/// > introduced a subtle bug which violated snapshot isolation. So without speculative\n/// > reads in the previous example, `tx2` needs to wait till `tx1` is committed or\n/// > aborted.\n///\n/// ### Need for atomicity\n///\n/// We want to atomically generate `end_ts` and publish `Preparing(end_ts)`\n/// while the clock lock is held. This closes the TOCTOU window.\n///\n/// Consider the example:\n///\n/// ```text\n/// tx1 (Active):    generates end_ts = 10\n/// tx2 (Active):    gets begin_ts = 11\n/// tx2 (Active):    does queries but does not see changes by tx1 (tx1 is still Active)\n/// tx1 (Preparing): stores Preparing(end_ts=10)\n/// tx2 (Active):    queries again, now it can see changes by tx1 (tx1 is now Preparing)\n/// ```\n///\n/// **This is a snapshot isolation violation** — `tx2` observes different\n/// values for the same rows within the same transaction.\n///\n/// So we want the following two operations to be atomic:\n///\n/// ```text\n/// let ts = get_timestamp()\n/// store Preparing(ts)\n/// ```\n///\n/// `tx2` must get its begin timestamp either **before** or **after** these\n/// two operations. If it interleaves, the above bug happens.\n///\n/// ## Note on the Hekaton paper\n///\n/// The Hekaton paper doesn't mention this \"gotcha\". The paper says:\n///\n/// > \"When the transaction has completed its normal processing and requests\n/// > to commit, it acquires an end timestamp and switches to the Preparing\n/// > state.\"\n///\n/// But it doesn't go into more detail about atomicity here.\n#[derive(Debug, Default)]\npub struct MvccClock {\n    inner: Mutex<u64>,\n}\n\nimpl MvccClock {\n    pub fn new() -> Self {\n        Self {\n            inner: Mutex::new(0),\n        }\n    }\n\n    /// Generate a begin timestamp. No side-effect needed alongside generation.\n    pub fn get_begin_timestamp(&self) -> u64 {\n        self.get_timestamp(no_op)\n    }\n\n    /// Generate a commit timestamp and call `f` with it while the lock is\n    /// held, atomically publishing the timestamp before releasing.\n    pub fn get_commit_timestamp<F: FnOnce(u64)>(&self, f: F) -> u64 {\n        self.get_timestamp(f)\n    }\n}\n\nimpl LogicalClock for MvccClock {\n    fn get_timestamp<F: FnOnce(u64)>(&self, f: F) -> u64 {\n        let mut guard = self.inner.lock();\n        let ts = *guard;\n        *guard += 1;\n        f(ts);\n        ts\n    }\n\n    fn reset(&self, ts: u64) {\n        *self.inner.lock() = ts;\n    }\n}\n"
  },
  {
    "path": "core/mvcc/cursor.rs",
    "content": "use crate::sync::RwLock;\nuse crate::turso_assert;\nuse crossbeam_skiplist::map::Entry;\nuse crossbeam_skiplist::SkipMap;\n\nuse crate::mvcc::clock::LogicalClock;\nuse crate::mvcc::database::{\n    create_seek_range, MVTableId, MvStore, Row, RowID, RowKey, RowVersion, SortableIndexKey,\n};\nuse crate::storage::btree::{BTreeCursor, BTreeKey, CursorTrait};\nuse crate::sync::Arc;\nuse crate::translate::plan::IterationDirection;\nuse crate::types::{\n    compare_immutable, IOCompletions, IOResult, ImmutableRecord, IndexInfo, SeekKey, SeekOp,\n    SeekResult, Value,\n};\nuse crate::vdbe::make_record;\nuse crate::vdbe::Register;\nuse crate::{return_if_io, Completion, LimboError, Pager, Result};\nuse std::any::Any;\nuse std::fmt::Debug;\nuse std::ops::Bound;\n\n#[derive(Debug, Clone)]\nenum CursorPosition {\n    /// We haven't loaded any row yet.\n    BeforeFirst,\n    /// We have loaded a row. This position points to a rowid in either MVCC index or in BTree.\n    Loaded {\n        row_id: RowID,\n        /// Indicates whether the rowid is pointing BTreeCursor or MVCC index.\n        in_btree: bool,\n    },\n    /// We have reached the end of the table.\n    End,\n}\n\n#[derive(Debug, Clone, Copy)]\nenum ExistsState {\n    ExistsBtree,\n}\n\n#[derive(Debug, Clone, Copy)]\n/// State machine for advancing the btree cursor.\n/// Advancing means advancing the btree iterator that could be going either forwards or backwards.\nenum AdvanceBtreeState {\n    RewindCheckBtreeKey, // Check if first key found is valid\n    NextBtree,           // Advance to next key\n    NextCheckBtreeKey,   // Check if next key found is valid, if it isn't go back to NextBtree\n}\n\n#[derive(Debug, Clone, Copy)]\n/// Rewind state is used to track the state of the rewind **AND** last operation. Since both seem to do similiar\n/// operations we can use the same enum for both.\nenum RewindState {\n    Advance,\n}\n\n#[derive(Debug, Clone, Copy)]\nenum NextState {\n    AdvanceUnitialized,\n    CheckNeedsAdvance,\n    Advance,\n}\n#[derive(Debug, Clone, Copy)]\nenum PrevState {\n    AdvanceUnitialized,\n    CheckNeedsAdvance,\n    Advance,\n}\n\n#[derive(Debug, Clone, Copy)]\nenum SeekBtreeState {\n    /// Seeking in btree (MVCC seek already done)\n    SeekBtree,\n    /// Advance to next key in btree (if we got [SeekResult::TryAdvance], or the current row is shadowed by MVCC)\n    AdvanceBTree,\n    /// Check if current row is visible (not shadowed by MVCC)\n    CheckRow,\n}\n\n#[derive(Debug, Clone, Copy)]\nenum SeekState {\n    /// Seeking in btree (MVCC seek already done)\n    SeekBtree(SeekBtreeState),\n    /// Pick winner and finalize\n    PickWinner,\n}\n\n#[derive(Debug, Clone, Copy)]\nenum CountState {\n    Rewind,\n    NextBtree { count: usize },\n    CheckBtreeKey { count: usize },\n}\n#[derive(Debug, Clone)]\nenum MvccLazyCursorState {\n    Next(NextState),\n    Prev(PrevState),\n    Rewind(RewindState),\n    Exists(ExistsState),\n    Seek(SeekState, IterationDirection),\n}\n\n/// We read rows from MVCC index or BTree in a dual-cursor approach.\n/// This means we read rows from both cursors and then advance the cursor that was just consumed.\n/// With DualCursorPeek we track the \"peeked\" next value for each cursor in the dual-cursor iteration,\n/// so that we always return the correct 'next' value (e.g. if mvcc has 1 and 3 and btree has 2 and 4,\n/// we should return 1, 2, 3, 4 in order).\n#[derive(Debug, Clone, Default)]\nstruct DualCursorPeek {\n    /// Next row available from MVCC\n    mvcc_peek: CursorPeek,\n    /// Next row available from btree\n    btree_peek: CursorPeek,\n}\n\nimpl DualCursorPeek {\n    /// Returns the next row key and whether the row is from the BTree.\n    fn get_next(&self, dir: IterationDirection) -> Option<(RowKey, bool)> {\n        tracing::trace!(\n            \"get_next: mvcc_key: {:?}, btree_key: {:?}\",\n            self.mvcc_peek.get_row_key(),\n            self.btree_peek.get_row_key()\n        );\n        match (self.mvcc_peek.get_row_key(), self.btree_peek.get_row_key()) {\n            (Some(mvcc_key), Some(btree_key)) => {\n                if dir == IterationDirection::Forwards {\n                    // In forwards iteration we want the smaller of the two keys\n                    if mvcc_key <= btree_key {\n                        Some((mvcc_key.clone(), false))\n                    } else {\n                        Some((btree_key.clone(), true))\n                    }\n                // In backwards iteration we want the larger of the two keys\n                } else if mvcc_key >= btree_key {\n                    Some((mvcc_key.clone(), false))\n                } else {\n                    Some((btree_key.clone(), true))\n                }\n            }\n            (Some(mvcc_key), None) => Some((mvcc_key.clone(), false)),\n            (None, Some(btree_key)) => Some((btree_key.clone(), true)),\n            (None, None) => None,\n        }\n    }\n\n    /// Returns a new [CursorPosition] based on the next row key\n    pub fn cursor_position_from_next(\n        &self,\n        table_id: MVTableId,\n        dir: IterationDirection,\n    ) -> CursorPosition {\n        match self.get_next(dir) {\n            Some((row_key, in_btree)) => CursorPosition::Loaded {\n                row_id: RowID {\n                    table_id,\n                    row_id: row_key,\n                },\n                in_btree,\n            },\n            None => match dir {\n                IterationDirection::Forwards => CursorPosition::End,\n                IterationDirection::Backwards => CursorPosition::BeforeFirst,\n            },\n        }\n    }\n\n    pub fn both_uninitialized(&self) -> bool {\n        matches!(self.mvcc_peek, CursorPeek::Uninitialized)\n            && matches!(self.btree_peek, CursorPeek::Uninitialized)\n    }\n\n    pub fn btree_uninitialized(&self) -> bool {\n        matches!(self.btree_peek, CursorPeek::Uninitialized)\n    }\n\n    pub fn mvcc_exhausted(&self) -> bool {\n        matches!(self.mvcc_peek, CursorPeek::Exhausted)\n    }\n    pub fn btree_exhausted(&self) -> bool {\n        matches!(self.btree_peek, CursorPeek::Exhausted)\n    }\n}\n\n#[derive(Debug, Clone, Default)]\nenum CursorPeek {\n    #[default]\n    Uninitialized,\n    Row(RowKey),\n    Exhausted,\n}\n\nimpl CursorPeek {\n    pub fn get_row_key(&self) -> Option<&RowKey> {\n        match self {\n            CursorPeek::Row(k) => Some(k),\n            _ => None,\n        }\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\npub enum MvccCursorType {\n    Table,\n    Index(Arc<IndexInfo>),\n}\n\npub(crate) type MvccIterator<'l, T> =\n    Box<dyn Iterator<Item = Entry<'l, T, RwLock<Vec<RowVersion>>>> + Send + Sync>;\n\n/// Extends the lifetime of a SkipMap iterator to `'static`.\n///\n/// # Why a macro instead of a function?\n///\n/// Rust's `crossbeam_skiplist::map::Entry<'a, K, V>` is *invariant* over `K`, meaning\n/// the lifetime `'a` cannot be coerced through a function boundary. When we try to pass\n/// `Box<dyn Iterator<Item = Entry<'_, K, V>>>` to a function expecting a generic lifetime,\n/// the compiler cannot unify the lifetimes across the function call.\n///\n/// A macro expands inline at the call site, avoiding the function boundary entirely and\n/// allowing the explicit transmute with both source and destination types specified.\n///\n/// # Safety\n///\n/// The caller must ensure that the underlying `SkipMap` from which the iterator was created\n/// outlives the returned iterator. This is guaranteed when:\n/// - For table iterators: The `MvStore.rows` SkipMap is held in an `Arc<MvStore>` that\n///   outlives the cursor.\n/// - For index iterators: The `MvStore.index_rows` SkipMap is held in an `Arc<MvStore>`\n///   that outlives the cursor.\nmacro_rules! static_iterator_hack {\n    ($iter:expr, $key_type:ty) => {\n        // SAFETY: See macro documentation above.\n        unsafe {\n            std::mem::transmute::<\n                Box<\n                    dyn Iterator<Item = Entry<'_, $key_type, RwLock<Vec<RowVersion>>>>\n                        + Send\n                        + Sync,\n                >,\n                Box<\n                    dyn Iterator<Item = Entry<'static, $key_type, RwLock<Vec<RowVersion>>>>\n                        + Send\n                        + Sync,\n                >,\n            >($iter)\n        }\n    };\n}\n\npub(crate) use static_iterator_hack;\n\npub struct MvccLazyCursor<Clock: LogicalClock + 'static> {\n    pub db: Arc<MvStore<Clock>>,\n    current_pos: CursorPosition,\n    /// Stateful MVCC table iterator if this is a table cursor.\n    table_iterator: Option<MvccIterator<'static, RowID>>,\n    /// Stateful MVCC index iterator if this is an index cursor.\n    index_iterator: Option<MvccIterator<'static, Arc<SortableIndexKey>>>,\n    mv_cursor_type: MvccCursorType,\n    table_id: MVTableId,\n    tx_id: u64,\n    /// Reusable immutable record, used to allow better allocation strategy.\n    reusable_immutable_record: Option<ImmutableRecord>,\n    btree_cursor: Box<dyn CursorTrait>,\n    null_flag: bool,\n    creating_new_rowid: bool,\n    state: Option<MvccLazyCursorState>,\n    // we keep count_state separate to be able to call other public functions like rewind and next\n    count_state: Option<CountState>,\n    btree_advance_state: Option<AdvanceBtreeState>,\n    /// Dual-cursor peek state for proper iteration\n    dual_peek: DualCursorPeek,\n}\n\npub enum NextRowidResult {\n    /// We need to go to the last rowid and intialize allocator\n    Uninitialized,\n    /// It was initialized, so we get a new rowid\n    Next {\n        new_rowid: i64,\n        prev_rowid: Option<i64>,\n    },\n    /// We reached end of available rowids (i64::MAX), so we will have to try and find a random rowid.\n    FindRandom,\n}\n\nimpl<Clock: LogicalClock + 'static> MvccLazyCursor<Clock> {\n    pub fn new(\n        db: Arc<MvStore<Clock>>,\n        tx_id: u64,\n        root_page_or_table_id: i64,\n        mv_cursor_type: MvccCursorType,\n        btree_cursor: Box<dyn CursorTrait>,\n    ) -> Result<MvccLazyCursor<Clock>> {\n        turso_assert!(\n            (&*btree_cursor as &dyn Any).is::<BTreeCursor>(),\n            \"BTreeCursor expected for mvcc cursor\"\n        );\n        let table_id = db.get_table_id_from_root_page(root_page_or_table_id);\n        Ok(Self {\n            db,\n            tx_id,\n            table_iterator: None,\n            index_iterator: None,\n            mv_cursor_type,\n            current_pos: CursorPosition::BeforeFirst,\n            table_id,\n            reusable_immutable_record: None,\n            btree_cursor,\n            null_flag: false,\n            creating_new_rowid: false,\n            state: None,\n            count_state: None,\n            btree_advance_state: None,\n            dual_peek: DualCursorPeek::default(),\n        })\n    }\n\n    /// Returns the current row as an immutable record.\n    pub fn current_row(&mut self) -> Result<IOResult<Option<&crate::types::ImmutableRecord>>> {\n        if self.get_null_flag() {\n            return Ok(IOResult::Done(None));\n        }\n        let current_pos = &self.current_pos;\n        tracing::trace!(\"current_row({:?})\", current_pos);\n        match current_pos {\n            CursorPosition::Loaded {\n                row_id: _,\n                in_btree,\n            } => {\n                if *in_btree {\n                    self.btree_cursor.record()\n                } else {\n                    let Some(row) = self.read_mvcc_current_row()? else {\n                        return Ok(IOResult::Done(None));\n                    };\n                    {\n                        let mut record = self.get_immutable_record_or_create();\n                        let record = record.as_mut().ok_or_else(|| {\n                            LimboError::InternalError(\n                                \"immutable record not initialized\".to_string(),\n                            )\n                        })?;\n                        record.invalidate();\n                        record.start_serialization(row.payload());\n                    }\n\n                    let record_ref = self.reusable_immutable_record.as_ref().ok_or_else(|| {\n                        LimboError::InternalError(\"immutable record not initialized\".to_string())\n                    })?;\n                    Ok(IOResult::Done(Some(record_ref)))\n                }\n            }\n            CursorPosition::BeforeFirst => {\n                // Before first is not a valid position, so we return none.\n                Ok(IOResult::Done(None))\n            }\n            CursorPosition::End => Ok(IOResult::Done(None)),\n        }\n    }\n\n    pub fn read_mvcc_current_row(&self) -> Result<Option<Row>> {\n        let row_id = match &self.current_pos {\n            CursorPosition::Loaded { row_id, in_btree } if !in_btree => row_id,\n            _ => panic!(\"invalid position to read current mvcc row\"),\n        };\n        let maybe_index_id = match &self.mv_cursor_type {\n            MvccCursorType::Index(_) => Some(self.table_id),\n            MvccCursorType::Table => None,\n        };\n        self.db\n            .read_from_table_or_index(self.tx_id, row_id, maybe_index_id)\n    }\n\n    pub fn close(self) -> Result<()> {\n        Ok(())\n    }\n\n    pub fn start_new_rowid(&mut self) -> Result<IOResult<NextRowidResult>> {\n        tracing::trace!(\"start_new_rowid\");\n\n        let allocator = self.db.get_rowid_allocator(&self.table_id);\n        let locked = allocator.lock();\n        if !locked {\n            // Yield, some other cursor is generating new rowid\n            return Ok(IOResult::IO(IOCompletions::Single(Completion::new_yield())));\n        }\n\n        self.creating_new_rowid = true;\n        let res = if allocator.is_uninitialized() {\n            NextRowidResult::Uninitialized\n        } else if let Some((next_rowid, prev_max_rowid)) = allocator.get_next_rowid() {\n            NextRowidResult::Next {\n                new_rowid: next_rowid,\n                prev_rowid: prev_max_rowid,\n            }\n        } else {\n            NextRowidResult::FindRandom\n        };\n        Ok(IOResult::Done(res))\n    }\n\n    pub fn initialize_max_rowid(&mut self, max_rowid: Option<i64>) -> Result<()> {\n        let allocator = self.db.get_rowid_allocator(&self.table_id);\n        turso_assert!(\n            self.creating_new_rowid,\n            \"cursor didn't start creating new rowid\"\n        );\n        allocator.initialize(max_rowid);\n        Ok(())\n    }\n\n    /// Allocate the next rowid from the (already initialized) allocator.\n    /// Must be called while holding the allocator lock.\n    pub fn allocate_next_rowid(&self) -> Option<(i64, Option<i64>)> {\n        let allocator = self.db.get_rowid_allocator(&self.table_id);\n        allocator.get_next_rowid()\n    }\n\n    pub fn end_new_rowid(&mut self) {\n        tracing::trace!(\n            \"end_new_rowid creating_new_rowid={}\",\n            self.creating_new_rowid\n        );\n        // if we started creating a new rowid, we need to unlock the allocator\n        // this might be false if there was an error during `op_new_rowid` before calling `start_new_rowid` so we can call this function\n        // in any case\n        if self.creating_new_rowid {\n            let allocator = self.db.get_rowid_allocator(&self.table_id);\n            allocator.unlock();\n            self.creating_new_rowid = false;\n        }\n    }\n\n    fn get_immutable_record_or_create(&mut self) -> Option<&mut ImmutableRecord> {\n        let reusable_immutable_record = &mut self.reusable_immutable_record;\n        if reusable_immutable_record.is_none() {\n            let record = ImmutableRecord::new(1024);\n            reusable_immutable_record.replace(record);\n        }\n        reusable_immutable_record.as_mut()\n    }\n\n    fn get_current_pos(&self) -> CursorPosition {\n        self.current_pos.clone()\n    }\n\n    fn is_btree_allocated(&self) -> bool {\n        self.db.is_btree_allocated(&self.table_id)\n    }\n\n    fn query_btree_version_is_valid(&self, key: &RowKey) -> bool {\n        self.db\n            .query_btree_version_is_valid(self.table_id, key, self.tx_id)\n    }\n\n    /// Advance MVCC iterator and return next visible row key in the direction that the iterator was initialized in.\n    fn advance_mvcc_iterator(&mut self) {\n        let next = match &self.mv_cursor_type {\n            MvccCursorType::Table => self.db.advance_cursor_and_get_row_id_for_table(\n                self.table_id,\n                &mut self.table_iterator,\n                self.tx_id,\n            ),\n            MvccCursorType::Index(_) => self\n                .db\n                .advance_cursor_and_get_row_id_for_index(&mut self.index_iterator, self.tx_id),\n        };\n        let new_peek_state = match next {\n            Some(k) => CursorPeek::Row(k.row_id),\n            None => CursorPeek::Exhausted,\n        };\n        self.dual_peek.mvcc_peek = new_peek_state;\n    }\n\n    /// Advance btree cursor forward and set btree peek to the first valid row key (skipping rows shadowed by MVCC)\n    fn advance_btree_forward(&mut self) -> Result<IOResult<()>> {\n        self._advance_btree_forward(true)\n    }\n\n    /// Advance btree cursor forward from current position (cursor already positioned by seek)\n    fn advance_btree_forward_from_current(&mut self) -> Result<IOResult<()>> {\n        self._advance_btree_forward(false)\n    }\n\n    fn _advance_btree_forward(&mut self, initialize: bool) -> Result<IOResult<()>> {\n        loop {\n            let state = self.btree_advance_state;\n            match state {\n                None => {\n                    if !self.is_btree_allocated() {\n                        self.dual_peek.btree_peek = CursorPeek::Exhausted;\n                        self.btree_advance_state = None;\n                        return Ok(IOResult::Done(()));\n                    }\n                    // If the btree is uninitialized AND we should initialize, do the equivalent of rewind() to find the first valid row\n                    if initialize && self.dual_peek.btree_uninitialized() {\n                        return_if_io!(self.btree_cursor.rewind());\n                        self.btree_advance_state = Some(AdvanceBtreeState::RewindCheckBtreeKey);\n                    } else {\n                        self.btree_advance_state = Some(AdvanceBtreeState::NextBtree);\n                    }\n                }\n                Some(AdvanceBtreeState::RewindCheckBtreeKey) => {\n                    let key = self.get_btree_current_key()?;\n                    match key {\n                        Some(k) if self.query_btree_version_is_valid(&k) => {\n                            self.dual_peek.btree_peek = CursorPeek::Row(k);\n                            self.btree_advance_state = None;\n                            return Ok(IOResult::Done(()));\n                        }\n                        Some(_) => {\n                            // shadowed by MVCC, continue to next\n                            self.btree_advance_state = Some(AdvanceBtreeState::NextBtree);\n                        }\n                        None => {\n                            self.dual_peek.btree_peek = CursorPeek::Exhausted;\n                            self.btree_advance_state = None;\n                            return Ok(IOResult::Done(()));\n                        }\n                    }\n                }\n                Some(AdvanceBtreeState::NextBtree) => {\n                    let peek = &mut self.dual_peek;\n                    return_if_io!(self.btree_cursor.next());\n                    let found = self.btree_cursor.has_record();\n                    if !found {\n                        peek.btree_peek = CursorPeek::Exhausted;\n                        self.btree_advance_state = None;\n                        return Ok(IOResult::Done(()));\n                    }\n                    self.btree_advance_state = Some(AdvanceBtreeState::NextCheckBtreeKey);\n                }\n                Some(AdvanceBtreeState::NextCheckBtreeKey) => {\n                    let key = self.get_btree_current_key()?;\n                    if let Some(key) = key {\n                        if self.query_btree_version_is_valid(&key) {\n                            self.dual_peek.btree_peek = CursorPeek::Row(key);\n                            self.btree_advance_state = None;\n                            return Ok(IOResult::Done(()));\n                        }\n                        // Row is shadowed by MVCC, continue to next\n                        // FIXME: do we want to iterate over all shadowed rows? If every row is shadowed by MVCC, we will iterate the whole btree in a single `next` call\n                        self.btree_advance_state = Some(AdvanceBtreeState::NextBtree);\n                    } else {\n                        self.dual_peek.btree_peek = CursorPeek::Exhausted;\n                        self.btree_advance_state = None;\n                        return Ok(IOResult::Done(()));\n                    }\n                }\n            }\n        }\n    }\n\n    /// Advance btree cursor backward and set btree peek to the first valid row key (skipping rows shadowed by MVCC)\n    fn advance_btree_backward(&mut self) -> Result<IOResult<()>> {\n        self._advance_btree_backward(true)\n    }\n\n    /// Advance btree cursor backward from current position (cursor already positioned by seek)\n    fn advance_btree_backward_from_current(&mut self) -> Result<IOResult<()>> {\n        self._advance_btree_backward(false)\n    }\n\n    fn _advance_btree_backward(&mut self, initialize: bool) -> Result<IOResult<()>> {\n        loop {\n            let state = self.btree_advance_state;\n            match state {\n                None => {\n                    if !self.is_btree_allocated() {\n                        let peek = &mut self.dual_peek;\n                        peek.btree_peek = CursorPeek::Exhausted;\n                        self.btree_advance_state = None;\n                        return Ok(IOResult::Done(()));\n                    }\n                    // If the btree is uninitialized AND we should initialize, do the equivalent of last() to find the last valid row\n                    if initialize && self.dual_peek.btree_uninitialized() {\n                        return_if_io!(self.btree_cursor.last());\n                        self.btree_advance_state = Some(AdvanceBtreeState::RewindCheckBtreeKey);\n                    } else {\n                        self.btree_advance_state = Some(AdvanceBtreeState::NextBtree);\n                    }\n                }\n                Some(AdvanceBtreeState::RewindCheckBtreeKey) => {\n                    let key = self.get_btree_current_key()?;\n                    match key {\n                        Some(k) if self.query_btree_version_is_valid(&k) => {\n                            self.dual_peek.btree_peek = CursorPeek::Row(k);\n                            self.btree_advance_state = None;\n                            return Ok(IOResult::Done(()));\n                        }\n                        Some(_) => {\n                            // shadowed by MVCC, continue to prev\n                            self.btree_advance_state = Some(AdvanceBtreeState::NextBtree);\n                        }\n                        None => {\n                            self.dual_peek.btree_peek = CursorPeek::Exhausted;\n                            self.btree_advance_state = None;\n                            return Ok(IOResult::Done(()));\n                        }\n                    }\n                }\n                Some(AdvanceBtreeState::NextBtree) => {\n                    return_if_io!(self.btree_cursor.prev());\n                    let peek = &mut self.dual_peek;\n                    let found = self.btree_cursor.has_record();\n                    if !found {\n                        peek.btree_peek = CursorPeek::Exhausted;\n                        self.btree_advance_state = None;\n                        return Ok(IOResult::Done(()));\n                    }\n                    self.btree_advance_state = Some(AdvanceBtreeState::NextCheckBtreeKey);\n                }\n                Some(AdvanceBtreeState::NextCheckBtreeKey) => {\n                    let key = self.get_btree_current_key()?;\n                    match key {\n                        Some(k) if self.query_btree_version_is_valid(&k) => {\n                            self.dual_peek.btree_peek = CursorPeek::Row(k);\n                            self.btree_advance_state = None;\n                            return Ok(IOResult::Done(()));\n                        }\n                        Some(_) => {\n                            // shadowed by MVCC, continue to prev\n                            self.btree_advance_state = Some(AdvanceBtreeState::NextBtree);\n                        }\n                        None => {\n                            self.dual_peek.btree_peek = CursorPeek::Exhausted;\n                            self.btree_advance_state = None;\n                            return Ok(IOResult::Done(()));\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    /// Get the current key from btree cursor\n    fn get_btree_current_key(&mut self) -> Result<Option<RowKey>> {\n        match &self.mv_cursor_type {\n            MvccCursorType::Table => {\n                let maybe_rowid = loop {\n                    match self.btree_cursor.rowid()? {\n                        IOResult::Done(maybe_rowid) => {\n                            break maybe_rowid.map(RowKey::Int);\n                        }\n                        IOResult::IO(c) => {\n                            c.wait(self.btree_cursor.get_pager().io.as_ref())?; // FIXME: sync IO hack\n                        }\n                    }\n                };\n                Ok(maybe_rowid)\n            }\n            MvccCursorType::Index(index_info) => {\n                let maybe_record = loop {\n                    match self.btree_cursor.record()? {\n                        IOResult::Done(maybe_record) => {\n                            break maybe_record;\n                        }\n                        IOResult::IO(c) => {\n                            c.wait(self.btree_cursor.get_pager().io.as_ref())?; // FIXME: sync IO hack\n                        }\n                    }\n                };\n                Ok(maybe_record.map(|record| {\n                    RowKey::Record(SortableIndexKey {\n                        key: record.clone(),\n                        metadata: index_info.clone(),\n                    })\n                }))\n            }\n        }\n    }\n\n    /// Refresh the current position based on the peek values\n    fn refresh_current_position(&mut self, dir: IterationDirection) {\n        let new_position = self.dual_peek.cursor_position_from_next(self.table_id, dir);\n        self.current_pos = new_position;\n    }\n\n    /// Reset dual peek state (called on rewind/last/seek)\n    fn reset_dual_peek(&mut self) {\n        self.dual_peek = DualCursorPeek::default();\n    }\n\n    /// Seek btree cursor and set btree_peek to the result.\n    /// Skips rows that are shadowed by MVCC.\n    /// Returns IOResult indicating if we need to yield for IO or are done.\n    fn seek_btree_and_set_peek(\n        &mut self,\n        seek_key: SeekKey<'_>,\n        op: SeekOp,\n    ) -> Result<IOResult<()>> {\n        // Fast path: btree not allocated\n        if !self.is_btree_allocated() {\n            self.dual_peek.btree_peek = CursorPeek::Exhausted;\n            self.state = None;\n            return Ok(IOResult::Done(()));\n        }\n\n        loop {\n            let Some(MvccLazyCursorState::Seek(SeekState::SeekBtree(btree_seek_state), direction)) =\n                self.state.clone()\n            else {\n                panic!(\n                    \"Invalid btree seek state in seek_btree_and_set_peek: {:?}\",\n                    self.state\n                );\n            };\n            match btree_seek_state {\n                SeekBtreeState::SeekBtree => {\n                    let seek_result = return_if_io!(self.btree_cursor.seek(seek_key.clone(), op));\n\n                    match seek_result {\n                        SeekResult::NotFound => {\n                            self.dual_peek.btree_peek = CursorPeek::Exhausted;\n                            return Ok(IOResult::Done(()));\n                        }\n                        SeekResult::TryAdvance => {\n                            // Need to advance to find actual matching entry\n                            self.state.replace(MvccLazyCursorState::Seek(\n                                SeekState::SeekBtree(SeekBtreeState::AdvanceBTree),\n                                direction,\n                            ));\n                        }\n                        SeekResult::Found => {\n                            self.state.replace(MvccLazyCursorState::Seek(\n                                SeekState::SeekBtree(SeekBtreeState::CheckRow),\n                                direction,\n                            ));\n                        }\n                    }\n                }\n                SeekBtreeState::AdvanceBTree => {\n                    return_if_io!(match direction {\n                        IterationDirection::Forwards => {\n                            self.advance_btree_forward_from_current()\n                        }\n                        IterationDirection::Backwards => {\n                            self.advance_btree_backward_from_current()\n                        }\n                    });\n                    self.state.replace(MvccLazyCursorState::Seek(\n                        SeekState::SeekBtree(SeekBtreeState::CheckRow),\n                        direction,\n                    ));\n                }\n                SeekBtreeState::CheckRow => {\n                    let key = self.get_btree_current_key()?;\n                    match key {\n                        Some(k) if self.query_btree_version_is_valid(&k) => {\n                            self.dual_peek.btree_peek = CursorPeek::Row(k);\n                            return Ok(IOResult::Done(()));\n                        }\n                        Some(_) => {\n                            // shadowed by MVCC, continue to next\n                            self.state.replace(MvccLazyCursorState::Seek(\n                                SeekState::SeekBtree(SeekBtreeState::AdvanceBTree),\n                                direction,\n                            ));\n                        }\n                        None => {\n                            self.dual_peek.btree_peek = CursorPeek::Exhausted;\n                            return Ok(IOResult::Done(()));\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    /// Initialize MVCC iterator for forward iteration (used when next() is called without rewind())\n    fn init_mvcc_iterator_forward(&mut self) {\n        if self.table_iterator.is_some() || self.index_iterator.is_some() {\n            return; // Already initialized\n        }\n        match &self.mv_cursor_type {\n            MvccCursorType::Table => {\n                let start_rowid = RowID {\n                    table_id: self.table_id,\n                    row_id: RowKey::Int(i64::MIN),\n                };\n                let range =\n                    create_seek_range(Bound::Included(start_rowid), IterationDirection::Forwards);\n                let iter_box = Box::new(self.db.rows.range(range));\n                self.table_iterator = Some(static_iterator_hack!(iter_box, RowID));\n            }\n            MvccCursorType::Index(_) => {\n                let index_rows = self\n                    .db\n                    .index_rows\n                    .get_or_insert_with(self.table_id, SkipMap::new);\n                let index_rows = index_rows.value();\n                let iter_box = Box::new(index_rows.iter());\n                self.index_iterator = Some(static_iterator_hack!(iter_box, Arc<SortableIndexKey>));\n            }\n        }\n    }\n}\n\nimpl<Clock: LogicalClock + 'static> CursorTrait for MvccLazyCursor<Clock> {\n    fn last(&mut self) -> Result<IOResult<()>> {\n        // A cursor may be NullRow'd during outer-join unmatched emission.\n        // Repositioning to a real row must clear that synthetic NULL state.\n        self.set_null_flag(false);\n        let state = self.state.clone();\n        if state.is_none() {\n            let _ = self.table_iterator.take();\n            let _ = self.index_iterator.take();\n            self.reset_dual_peek();\n            self.state\n                .replace(MvccLazyCursorState::Rewind(RewindState::Advance));\n        }\n\n        turso_assert!(\n            matches!(\n                self.state\n                    .as_ref()\n                    .expect(\"rewind state is not initialized\"),\n                MvccLazyCursorState::Rewind(RewindState::Advance)\n            ),\n            \"invalid last state\",\n            { \"state\": format!(\"{:?}\", self.state) }\n        );\n\n        // Initialize btree cursor to last position\n        return_if_io!(self.advance_btree_backward());\n\n        self.invalidate_record();\n        self.current_pos = CursorPosition::End;\n\n        // Initialize MVCC iterator to last position\n        match &self.mv_cursor_type {\n            MvccCursorType::Table => match self.db.get_last_table_rowid(\n                self.table_id,\n                &mut self.table_iterator,\n                self.tx_id,\n            ) {\n                Some(k) => {\n                    tracing::trace!(\"last: mvcc_key: {:?}\", k);\n                    self.dual_peek.mvcc_peek = CursorPeek::Row(k);\n                }\n                None => {\n                    self.dual_peek.mvcc_peek = CursorPeek::Exhausted;\n                }\n            },\n            MvccCursorType::Index(_) => match self.db.get_last_index_rowid(\n                self.table_id,\n                self.tx_id,\n                &mut self.index_iterator,\n            ) {\n                Some(k) => {\n                    self.dual_peek.mvcc_peek = CursorPeek::Row(k);\n                }\n                None => {\n                    self.dual_peek.mvcc_peek = CursorPeek::Exhausted;\n                }\n            },\n        };\n\n        self.refresh_current_position(IterationDirection::Backwards);\n        self.invalidate_record();\n        self.state = None;\n\n        Ok(IOResult::Done(()))\n    }\n\n    /// Move the cursor to the next row. Returns true if the cursor moved to the next row, false if the cursor is at the end of the table.\n    ///\n    /// Uses dual-cursor approach: only advances the cursor that was just consumed.\n    fn next(&mut self) -> Result<IOResult<()>> {\n        if self.state.is_none() {\n            // If BeforeFirst and peek not initialized, initialize the iterators and peek values\n            let current_pos = self.get_current_pos();\n            if matches!(current_pos, CursorPosition::BeforeFirst) {\n                let uninitialized = self.dual_peek.both_uninitialized();\n                if uninitialized {\n                    // Initialize MVCC iterator and get first peek\n                    self.init_mvcc_iterator_forward();\n                    self.advance_mvcc_iterator();\n                    self.state\n                        .replace(MvccLazyCursorState::Next(NextState::AdvanceUnitialized));\n                } else {\n                    self.state\n                        .replace(MvccLazyCursorState::Next(NextState::CheckNeedsAdvance));\n                }\n            } else {\n                self.state\n                    .replace(MvccLazyCursorState::Next(NextState::CheckNeedsAdvance));\n            }\n        }\n        // If it was uninitialized, we need to advance the btree first\n        if matches!(\n            self.state.as_ref().expect(\"next state is not initialized\"),\n            MvccLazyCursorState::Next(NextState::AdvanceUnitialized)\n        ) {\n            return_if_io!(self.advance_btree_forward());\n            self.state\n                .replace(MvccLazyCursorState::Next(NextState::CheckNeedsAdvance));\n        }\n\n        if matches!(\n            self.state.as_ref().expect(\"next state is not initialized\"),\n            MvccLazyCursorState::Next(NextState::CheckNeedsAdvance)\n        ) {\n            // Determine which cursor(s) need to be advanced based on current position\n            let current_pos = self.get_current_pos();\n            let (need_advance_mvcc, need_advance_btree) = match &current_pos {\n                CursorPosition::BeforeFirst => {\n                    // First call after rewind - peek values should already be populated\n                    // Just need to pick the smaller one\n                    (false, false)\n                }\n                CursorPosition::Loaded { in_btree, .. } => {\n                    // Advance whichever cursor we just consumed\n                    if *in_btree {\n                        (false, true) // Last row was from btree, advance btree\n                    } else {\n                        (true, false) // Last row was from MVCC, advance MVCC\n                    }\n                }\n                CursorPosition::End => {\n                    self.state = None;\n                    return Ok(IOResult::Done(()));\n                }\n            };\n\n            // Advance cursors as needed and update peek state\n            if need_advance_mvcc && !self.dual_peek.mvcc_exhausted() {\n                self.advance_mvcc_iterator();\n            }\n            if need_advance_btree && !self.dual_peek.btree_exhausted() {\n                self.state\n                    .replace(MvccLazyCursorState::Next(NextState::Advance));\n            }\n        }\n\n        if matches!(\n            self.state.as_ref().expect(\"next state is not initialized\"),\n            MvccLazyCursorState::Next(NextState::Advance)\n        ) {\n            return_if_io!(self.advance_btree_forward());\n        }\n\n        self.refresh_current_position(IterationDirection::Forwards);\n        self.invalidate_record();\n        self.state = None;\n\n        Ok(IOResult::Done(()))\n    }\n\n    /// Move the cursor to the previous row. Returns true if the cursor moved, false if at the beginning.\n    ///\n    /// Uses dual-cursor approach: only advances the cursor that was just consumed.\n    fn prev(&mut self) -> Result<IOResult<()>> {\n        if self.state.is_none() {\n            // If End and peek not initialized, initialize via last()\n            let current_pos = self.get_current_pos();\n            if matches!(current_pos, CursorPosition::End) {\n                let uninitialized = self.dual_peek.both_uninitialized();\n                if uninitialized {\n                    self.state\n                        .replace(MvccLazyCursorState::Prev(PrevState::AdvanceUnitialized));\n                    return_if_io!(self.last());\n                } else {\n                    self.state\n                        .replace(MvccLazyCursorState::Prev(PrevState::CheckNeedsAdvance));\n                }\n            } else {\n                self.state\n                    .replace(MvccLazyCursorState::Prev(PrevState::CheckNeedsAdvance));\n            }\n        }\n\n        if matches!(\n            self.state.as_ref().expect(\"prev state is not initialized\"),\n            MvccLazyCursorState::Prev(PrevState::AdvanceUnitialized)\n        ) {\n            return_if_io!(self.last());\n            self.state\n                .replace(MvccLazyCursorState::Prev(PrevState::CheckNeedsAdvance));\n        }\n\n        if matches!(\n            self.state.as_ref().expect(\"prev state is not initialized\"),\n            MvccLazyCursorState::Prev(PrevState::CheckNeedsAdvance)\n        ) {\n            // Determine which cursor(s) need to be advanced based on current position\n            let current_pos = self.get_current_pos();\n            let (need_advance_mvcc, need_advance_btree) = match &current_pos {\n                CursorPosition::End => {\n                    // First call after last() - peek values should already be populated\n                    (false, false)\n                }\n                CursorPosition::Loaded { in_btree, .. } => {\n                    // Advance whichever cursor we just consumed\n                    if *in_btree {\n                        (false, true) // Last row was from btree, advance btree\n                    } else {\n                        (true, false) // Last row was from MVCC, advance MVCC\n                    }\n                }\n                CursorPosition::BeforeFirst => {\n                    self.state = None;\n                    return Ok(IOResult::Done(()));\n                }\n            };\n\n            // Advance cursors as needed and update peek state\n            if need_advance_mvcc && !self.dual_peek.mvcc_exhausted() {\n                self.advance_mvcc_iterator();\n            }\n            if need_advance_btree && !self.dual_peek.btree_exhausted() {\n                self.state\n                    .replace(MvccLazyCursorState::Prev(PrevState::Advance));\n            }\n        }\n\n        if matches!(\n            self.state.as_ref().expect(\"prev state is not initialized\"),\n            MvccLazyCursorState::Prev(PrevState::Advance)\n        ) {\n            return_if_io!(self.advance_btree_backward());\n        }\n        self.refresh_current_position(IterationDirection::Backwards);\n        self.invalidate_record();\n        self.state = None;\n\n        Ok(IOResult::Done(()))\n    }\n\n    fn rowid(&mut self) -> Result<IOResult<Option<i64>>> {\n        if self.get_null_flag() {\n            return Ok(IOResult::Done(None));\n        }\n        let rowid = match self.get_current_pos() {\n            CursorPosition::Loaded {\n                row_id,\n                in_btree: _,\n            } => match &row_id.row_id {\n                RowKey::Int(id) => Some(*id),\n                RowKey::Record(sortable_key) => {\n                    // For index cursors, the rowid is stored in the last column of the index record\n                    let MvccCursorType::Index(index_info) = &self.mv_cursor_type else {\n                        panic!(\"RowKey::Record requires Index cursor type\");\n                    };\n                    if index_info.has_rowid {\n                        match sortable_key.key.last_value() {\n                            Some(Ok(crate::types::ValueRef::Numeric(\n                                crate::numeric::Numeric::Integer(rowid),\n                            ))) => Some(rowid),\n                            _ => {\n                                crate::bail_parse_error!(\"Failed to parse rowid from index record\")\n                            }\n                        }\n                    } else {\n                        crate::bail_parse_error!(\"Indexes without rowid are not supported in MVCC\");\n                    }\n                }\n            },\n            CursorPosition::BeforeFirst => None,\n            CursorPosition::End => None,\n        };\n        Ok(IOResult::Done(rowid))\n    }\n\n    fn record(&mut self) -> Result<IOResult<Option<&crate::types::ImmutableRecord>>> {\n        self.current_row()\n    }\n\n    fn seek_unpacked(\n        &mut self,\n        registers: &[Register],\n        op: SeekOp,\n    ) -> Result<IOResult<SeekResult>> {\n        let record = make_record(registers, &0, &registers.len());\n        self.seek(SeekKey::IndexKey(&record), op)\n    }\n\n    fn seek(&mut self, seek_key: SeekKey<'_>, op: SeekOp) -> Result<IOResult<SeekResult>> {\n        // gt -> lower_bound bound excluded, we want first row after row_id\n        // ge -> lower_bound bound included, we want first row equal to row_id or first row after row_id\n        // lt -> upper_bound bound excluded, we want last row before row_id\n        // le -> upper_bound bound included, we want last row equal to row_id or first row before row_id\n\n        loop {\n            let state = self.state.clone();\n            match state {\n                None => {\n                    // Initial state: Reset and do MVCC seek\n                    let _ = self.table_iterator.take();\n                    let _ = self.index_iterator.take();\n                    self.reset_dual_peek();\n                    self.invalidate_record();\n                    // We need to clear the null flag for the table cursor before seeking,\n                    // because it might have been set to false by an unmatched left-join row during the previous iteration\n                    // on the outer loop.\n                    self.set_null_flag(false);\n\n                    let direction = op.iteration_direction();\n                    let inclusive = matches!(op, SeekOp::GE { .. } | SeekOp::LE { .. });\n\n                    match &seek_key {\n                        SeekKey::TableRowId(row_id) => {\n                            let rowid = RowID {\n                                table_id: self.table_id,\n                                row_id: RowKey::Int(*row_id),\n                            };\n\n                            // Seek in MVCC (synchronous)\n                            let mvcc_rowid = self.db.seek_rowid(\n                                rowid.clone(),\n                                inclusive,\n                                direction,\n                                self.tx_id,\n                                &mut self.table_iterator,\n                            );\n\n                            // Set MVCC peek\n                            {\n                                self.dual_peek.mvcc_peek = match &mvcc_rowid {\n                                    Some(rid) => CursorPeek::Row(rid.row_id.clone()),\n                                    None => CursorPeek::Exhausted,\n                                };\n                            }\n                        }\n                        SeekKey::IndexKey(index_key) => {\n                            let index_info = {\n                                let MvccCursorType::Index(index_info) = &self.mv_cursor_type else {\n                                    panic!(\"SeekKey::IndexKey requires Index cursor type\");\n                                };\n                                Arc::new(IndexInfo {\n                                    key_info: index_info.key_info.clone(),\n                                    has_rowid: index_info.has_rowid,\n                                    num_cols: index_key.column_count(),\n                                    is_unique: index_info.is_unique,\n                                })\n                            };\n                            let sortable_key =\n                                SortableIndexKey::new_from_record((*index_key).clone(), index_info);\n\n                            // Seek in MVCC (synchronous)\n                            let mvcc_rowid = self.db.seek_index(\n                                self.table_id,\n                                sortable_key.clone(),\n                                inclusive,\n                                direction,\n                                self.tx_id,\n                                &mut self.index_iterator,\n                            );\n\n                            // Set MVCC peek\n                            {\n                                self.dual_peek.mvcc_peek = match &mvcc_rowid {\n                                    Some(rid) => CursorPeek::Row(rid.row_id.clone()),\n                                    None => CursorPeek::Exhausted,\n                                };\n                            }\n                        }\n                    }\n\n                    // Move to btree seek state\n                    self.state.replace(MvccLazyCursorState::Seek(\n                        SeekState::SeekBtree(SeekBtreeState::SeekBtree),\n                        direction,\n                    ));\n                }\n                Some(MvccLazyCursorState::Seek(SeekState::SeekBtree(_), direction)) => {\n                    return_if_io!(self.seek_btree_and_set_peek(seek_key.clone(), op));\n                    self.state\n                        .replace(MvccLazyCursorState::Seek(SeekState::PickWinner, direction));\n                }\n                Some(MvccLazyCursorState::Seek(SeekState::PickWinner, direction)) => {\n                    // Pick winner and return result\n                    // Now pick the winner based on direction\n                    let winner = self.dual_peek.get_next(direction);\n\n                    // Clear seek state\n                    self.state = None;\n\n                    if let Some((winner_key, in_btree)) = winner {\n                        self.current_pos = CursorPosition::Loaded {\n                            row_id: RowID {\n                                table_id: self.table_id,\n                                row_id: winner_key.clone(),\n                            },\n                            in_btree,\n                        };\n\n                        if op.eq_only() {\n                            // Check if the winner matches the seek key\n                            let found = match &seek_key {\n                                SeekKey::TableRowId(row_id) => winner_key == RowKey::Int(*row_id),\n                                SeekKey::IndexKey(index_key) => {\n                                    let RowKey::Record(found_key) = &winner_key else {\n                                        panic!(\"Found rowid is not a record\");\n                                    };\n                                    let MvccCursorType::Index(index_info) = &self.mv_cursor_type\n                                    else {\n                                        panic!(\"Index cursor expected\");\n                                    };\n                                    let key_info: Vec<_> = index_info\n                                        .key_info\n                                        .iter()\n                                        .take(index_key.column_count())\n                                        .cloned()\n                                        .collect();\n                                    let cmp = compare_immutable(\n                                        index_key.get_values()?,\n                                        found_key.key.get_values()?,\n                                        &key_info,\n                                    );\n                                    cmp.is_eq()\n                                }\n                            };\n                            if found {\n                                return Ok(IOResult::Done(SeekResult::Found));\n                            } else {\n                                return Ok(IOResult::Done(SeekResult::NotFound));\n                            }\n                        } else {\n                            return Ok(IOResult::Done(SeekResult::Found));\n                        }\n                    } else {\n                        // Nothing found in either cursor\n                        let forwards = matches!(op, SeekOp::GE { .. } | SeekOp::GT);\n                        if forwards {\n                            self.current_pos = CursorPosition::End;\n                        } else {\n                            self.current_pos = CursorPosition::BeforeFirst;\n                        }\n                        return Ok(IOResult::Done(SeekResult::NotFound));\n                    }\n                }\n                _ => {\n                    panic!(\"Invalid state in seek: {:?}\", self.state);\n                }\n            }\n        }\n    }\n\n    /// Insert a row into the table or index.\n    /// Sets the cursor to the inserted row.\n    fn insert(&mut self, key: &BTreeKey) -> Result<IOResult<()>> {\n        let row_id = match key {\n            BTreeKey::TableRowId((rowid, _)) => RowID::new(self.table_id, RowKey::Int(*rowid)),\n            BTreeKey::IndexKey(record) => {\n                let MvccCursorType::Index(index_info) = &self.mv_cursor_type else {\n                    panic!(\"BTreeKey::IndexKey requires Index cursor type\");\n                };\n                let sortable_key =\n                    SortableIndexKey::new_from_record((*record).clone(), index_info.clone());\n                RowID::new(self.table_id, RowKey::Record(sortable_key))\n            }\n        };\n        let record_buf = key\n            .get_record()\n            .ok_or_else(|| LimboError::InternalError(\"BTreeKey should have a record\".to_string()))?\n            .get_payload()\n            .to_vec();\n        let num_columns = match key {\n            BTreeKey::IndexKey(record) => record.column_count(),\n            BTreeKey::TableRowId((_, record)) => record\n                .as_ref()\n                .ok_or_else(|| {\n                    LimboError::InternalError(\"TableRowId should have a record\".to_string())\n                })?\n                .column_count(),\n        };\n        let row = match &self.mv_cursor_type {\n            MvccCursorType::Table => Row::new_table_row(row_id, record_buf, num_columns),\n            MvccCursorType::Index(_) => Row::new_index_row(row_id, num_columns),\n        };\n\n        // Check if the cursor is currently positioned at a B-tree row that matches\n        // the row we're inserting. This indicates we're updating a B-tree-resident row\n        // that doesn't yet have an MVCC version.\n        let (in_btree, was_btree_resident) = match &self.current_pos {\n            CursorPosition::Loaded {\n                row_id: current_row_id,\n                in_btree,\n            } => (*in_btree, *in_btree && *current_row_id == row.id),\n            _ => (false, false),\n        };\n\n        self.current_pos = CursorPosition::Loaded {\n            row_id: row.id.clone(),\n            in_btree,\n        };\n        let maybe_index_id = match &self.mv_cursor_type {\n            MvccCursorType::Index(_) => Some(self.table_id),\n            MvccCursorType::Table => None,\n        };\n        // FIXME: set btree to somewhere close to this rowid?\n        if self\n            .db\n            .read_from_table_or_index(self.tx_id, &row.id, maybe_index_id)?\n            .is_some()\n        {\n            self.db\n                .update_to_table_or_index(self.tx_id, row, maybe_index_id)\n                .inspect_err(|_| {\n                    self.current_pos = CursorPosition::BeforeFirst;\n                })?;\n        } else if was_btree_resident {\n            // The row exists in B-tree but not in MvStore - mark it as B-tree resident\n            // so that checkpoint knows to write deletes to the B-tree file.\n            self.db\n                .insert_btree_resident_to_table_or_index(self.tx_id, row, maybe_index_id)\n                .inspect_err(|_| {\n                    self.current_pos = CursorPosition::BeforeFirst;\n                })?;\n        } else {\n            self.db\n                .insert_to_table_or_index(self.tx_id, row, maybe_index_id)\n                .inspect_err(|_| {\n                    self.current_pos = CursorPosition::BeforeFirst;\n                })?;\n        }\n        self.invalidate_record();\n        Ok(IOResult::Done(()))\n    }\n\n    fn delete(&mut self) -> Result<IOResult<()>> {\n        let rowid = match self.get_current_pos() {\n            CursorPosition::Loaded { row_id, .. } => row_id,\n            _ => panic!(\"Cannot delete: no current row\"),\n        };\n        let maybe_index_id = match &self.mv_cursor_type {\n            MvccCursorType::Index(_) => Some(self.table_id),\n            MvccCursorType::Table => None,\n        };\n        let was_deleted =\n            self.db\n                .delete_from_table_or_index(self.tx_id, rowid.clone(), maybe_index_id)?;\n        // If was_deleted is false, this can ONLY happen when we have a row that only exists\n        // in the btree but not the mv store. In this case, we create a tombstone for the row\n        // based on the btree row.\n        if !was_deleted {\n            // The btree cursor must be correctly positioned and cannot cause IO to happen\n            // because in order to get here, we must have read it already in the VDBE.\n            let IOResult::Done(Some(record)) = self.record()? else {\n                crate::bail_corrupt_error!(\n                    \"Btree cursor should have a record when deleting a row that only exists in the btree\"\n                );\n            };\n            // All operations below clone values so we can clone it here to circumvent the borrow checker\n            let record = record.clone();\n            let column_count = record.column_count();\n            let row = match &self.mv_cursor_type {\n                MvccCursorType::Table => {\n                    Row::new_table_row(rowid.clone(), record.into_payload(), column_count)\n                }\n                MvccCursorType::Index(_) => Row::new_index_row(rowid.clone(), column_count),\n            };\n            self.db\n                .insert_tombstone_to_table_or_index(self.tx_id, rowid, row, maybe_index_id)?;\n        }\n        self.invalidate_record();\n        Ok(IOResult::Done(()))\n    }\n\n    fn set_null_flag(&mut self, flag: bool) {\n        self.null_flag = flag;\n    }\n\n    fn get_null_flag(&self) -> bool {\n        self.null_flag\n    }\n\n    fn exists(&mut self, key: &Value) -> Result<IOResult<bool>> {\n        if self.state.is_none() {\n            self.invalidate_record();\n            let int_key = match key {\n                Value::Numeric(crate::numeric::Numeric::Integer(i)) => i,\n                _ => unreachable!(\"btree tables are indexed by integers!\"),\n            };\n            let inclusive = true;\n\n            // Check MVCC first\n            let rowid = self.db.seek_rowid(\n                RowID {\n                    table_id: self.table_id,\n                    row_id: RowKey::Int(*int_key),\n                },\n                inclusive,\n                IterationDirection::Forwards,\n                self.tx_id,\n                &mut self.table_iterator,\n            );\n\n            let mvcc_exists = if let Some(rowid) = &rowid {\n                let RowKey::Int(rowid) = rowid.row_id else {\n                    panic!(\"Rowid is not an integer in mvcc table cursor\");\n                };\n                rowid == *int_key\n            } else {\n                false\n            };\n\n            tracing::trace!(\n                \"MVCC exists check: mvcc_exists={mvcc_exists} find={int_key} got={rowid:?}\"\n            );\n\n            // If found in MVCC, update dual_peek and return true\n            if mvcc_exists {\n                self.dual_peek.mvcc_peek = CursorPeek::Row(RowKey::Int(*int_key));\n                self.current_pos = CursorPosition::Loaded {\n                    row_id: RowID {\n                        table_id: self.table_id,\n                        row_id: RowKey::Int(*int_key),\n                    },\n                    in_btree: false,\n                };\n                self.state = None;\n                return Ok(IOResult::Done(true));\n            }\n\n            // MVCC doesn't have it, but we need to check B-tree too\n            if self.is_btree_allocated() {\n                // Check if the B-tree version is valid (not shadowed/deleted by MVCC)\n                let btree_is_valid = self.query_btree_version_is_valid(&RowKey::Int(*int_key));\n\n                // If B-tree is invalid (row is deleted or shadowed), don't check B-tree\n                if !btree_is_valid {\n                    self.state = None;\n                    return Ok(IOResult::Done(false));\n                }\n                self.state\n                    .replace(MvccLazyCursorState::Exists(ExistsState::ExistsBtree));\n            } else {\n                // No B-tree allocated, row doesn't exist\n                self.state = None;\n                return Ok(IOResult::Done(false));\n            }\n        }\n\n        let Some(MvccLazyCursorState::Exists(ExistsState::ExistsBtree)) = self.state.clone() else {\n            panic!(\"Invalid state {:?}\", self.state);\n        };\n        turso_assert!(\n            self.is_btree_allocated(),\n            \"BTree should be allocated when we are in ExistsBtree state\"\n        );\n\n        // Check if row exists in B-tree\n        let found = return_if_io!(self.btree_cursor.exists(key));\n\n        if found {\n            // Found in B-tree, but need to verify it's not shadowed by MVCC tombstone\n            let int_key = match key {\n                Value::Numeric(crate::numeric::Numeric::Integer(i)) => *i,\n                _ => unreachable!(\"btree tables are indexed by integers!\"),\n            };\n            let row_key = RowKey::Int(int_key);\n\n            // Check if this B-tree row is shadowed (deleted/updated) in MVCC\n            let is_valid = self.query_btree_version_is_valid(&row_key);\n\n            if is_valid {\n                // B-tree row is visible (not shadowed), update dual_peek\n                self.dual_peek.btree_peek = CursorPeek::Row(row_key.clone());\n                self.current_pos = CursorPosition::Loaded {\n                    row_id: RowID {\n                        table_id: self.table_id,\n                        row_id: row_key,\n                    },\n                    in_btree: true,\n                };\n                self.state = None;\n                Ok(IOResult::Done(true))\n            } else {\n                // B-tree row is shadowed by MVCC (tombstone or update), so it doesn't exist\n                tracing::trace!(\"B-tree row {int_key} is shadowed by MVCC\");\n                self.state = None;\n                Ok(IOResult::Done(false))\n            }\n        } else {\n            // Not found in B-tree either\n            self.state = None;\n            Ok(IOResult::Done(false))\n        }\n    }\n\n    fn clear_btree(&mut self) -> Result<IOResult<Option<usize>>> {\n        todo!()\n    }\n\n    fn btree_destroy(&mut self) -> Result<IOResult<Option<usize>>> {\n        todo!()\n    }\n\n    fn count(&mut self) -> Result<IOResult<usize>> {\n        loop {\n            let state = self.count_state;\n            match state {\n                None => {\n                    self.count_state.replace(CountState::Rewind);\n                }\n                Some(CountState::Rewind) => {\n                    return_if_io!(self.rewind());\n                    self.count_state\n                        .replace(CountState::CheckBtreeKey { count: 0 });\n                }\n                Some(CountState::CheckBtreeKey { count }) => {\n                    if let CursorPosition::Loaded {\n                        row_id: _,\n                        in_btree: _,\n                    } = self.get_current_pos()\n                    {\n                        self.count_state\n                            .replace(CountState::NextBtree { count: count + 1 });\n                    } else {\n                        self.count_state = None;\n                        return Ok(IOResult::Done(count));\n                    }\n                }\n                Some(CountState::NextBtree { count }) => {\n                    // advance the btree cursor skips non valid keys\n                    return_if_io!(self.next());\n                    self.count_state\n                        .replace(CountState::CheckBtreeKey { count });\n                }\n            }\n        }\n    }\n\n    /// Returns true if the is not pointing to any row.\n    fn is_empty(&self) -> bool {\n        // If we reached the end of the table, it means we traversed the whole table therefore there must be something in the table.\n        // If we have loaded a row, it means there is something in the table.\n        match self.get_current_pos() {\n            CursorPosition::Loaded { .. } => false,\n            CursorPosition::BeforeFirst => true,\n            CursorPosition::End => true,\n        }\n    }\n\n    fn root_page(&self) -> i64 {\n        self.table_id.into()\n    }\n\n    fn rewind(&mut self) -> Result<IOResult<()>> {\n        // A cursor may be NullRow'd during outer-join unmatched emission.\n        // Repositioning to a real row must clear that synthetic NULL state.\n        self.set_null_flag(false);\n        let state = self.state.clone();\n        if state.is_none() {\n            let _ = self.table_iterator.take();\n            let _ = self.index_iterator.take();\n            self.reset_dual_peek();\n            self.state\n                .replace(MvccLazyCursorState::Rewind(RewindState::Advance));\n        }\n\n        turso_assert!(\n            matches!(\n                self.state\n                    .as_ref()\n                    .expect(\"rewind state is not initialized\"),\n                MvccLazyCursorState::Rewind(RewindState::Advance)\n            ),\n            \"invalid rewind state\",\n            { \"state\": format!(\"{:?}\", self.state) }\n        );\n        // First run btree_cursor rewind so that we don't need a explicit state machine.\n        return_if_io!(self.advance_btree_forward());\n\n        self.invalidate_record();\n        self.current_pos = CursorPosition::BeforeFirst;\n\n        // Initialize MVCC iterators for rewind operation; in practice there is only one of these\n        // depending on the cursor type, so we should at some point refactor the iterator thing to be\n        // generic over the type instead of having two on the struct.\n        match &self.mv_cursor_type {\n            MvccCursorType::Table => {\n                // For table cursors, initialize iterator from the correct table id + i64::MIN;\n                // this is because table rows from all tables are stored in the same map\n                let start_rowid = RowID {\n                    table_id: self.table_id,\n                    row_id: RowKey::Int(i64::MIN),\n                };\n                let range = (\n                    std::ops::Bound::Included(start_rowid),\n                    std::ops::Bound::Unbounded,\n                );\n                let iter_box = Box::new(self.db.rows.range(range));\n                self.table_iterator = Some(static_iterator_hack!(iter_box, RowID));\n            }\n            MvccCursorType::Index(_) => {\n                // For index cursors, initialize the iterator to the beginning\n                let index_rows = self\n                    .db\n                    .index_rows\n                    .get_or_insert_with(self.table_id, SkipMap::new);\n                let index_rows = index_rows.value();\n                let iter_box = Box::new(index_rows.iter());\n                self.index_iterator = Some(static_iterator_hack!(iter_box, Arc<SortableIndexKey>));\n            }\n        }\n\n        // Rewind mvcc iterator\n        self.advance_mvcc_iterator();\n\n        self.refresh_current_position(IterationDirection::Forwards);\n\n        self.invalidate_record();\n        self.state = None;\n        Ok(IOResult::Done(()))\n    }\n\n    fn has_record(&self) -> bool {\n        matches!(self.get_current_pos(), CursorPosition::Loaded { .. })\n    }\n\n    fn set_has_record(&mut self, _has_record: bool) {\n        todo!()\n    }\n\n    fn get_index_info(&self) -> &Arc<crate::types::IndexInfo> {\n        match &self.mv_cursor_type {\n            MvccCursorType::Index(index_info) => index_info,\n            MvccCursorType::Table => panic!(\"get_index_info called on table cursor\"),\n        }\n    }\n\n    fn seek_end(&mut self) -> Result<IOResult<()>> {\n        if self.is_btree_allocated() {\n            // Defer to btree cursor's seek_end implementation\n            self.btree_cursor.seek_end()\n        } else {\n            // SkipMap inserts don't require cursor positioning because\n            // SeekEnd instruction is only used for insertions.\n            Ok(IOResult::Done(()))\n        }\n    }\n\n    fn seek_to_last(&mut self, _always_seek: bool) -> Result<IOResult<()>> {\n        match self.seek(SeekKey::TableRowId(i64::MAX), SeekOp::LE { eq_only: false })? {\n            IOResult::Done(_) => Ok(IOResult::Done(())),\n            IOResult::IO(iocompletions) => Ok(IOResult::IO(iocompletions)),\n        }\n    }\n\n    fn invalidate_record(&mut self) {\n        self.get_immutable_record_or_create()\n            .as_mut()\n            .expect(\"immutable record should be initialized\")\n            .invalidate();\n    }\n\n    fn has_rowid(&self) -> bool {\n        match &self.mv_cursor_type {\n            MvccCursorType::Index(index_info) => index_info.has_rowid,\n            MvccCursorType::Table => true, // currently we don't support WITHOUT ROWID tables\n        }\n    }\n\n    fn get_pager(&self) -> Arc<Pager> {\n        self.btree_cursor.get_pager()\n    }\n\n    fn get_skip_advance(&self) -> bool {\n        todo!()\n    }\n\n    /// Returns true if this cursor operates in MVCC mode.\n    fn is_mvcc(&self) -> bool {\n        true\n    }\n}\n\nimpl<Clock: LogicalClock> Debug for MvccLazyCursor<Clock> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"MvccLazyCursor\")\n            .field(\"current_pos\", &self.current_pos)\n            .field(\"table_id\", &self.table_id)\n            .field(\"tx_id\", &self.tx_id)\n            .field(\"reusable_immutable_record\", &self.reusable_immutable_record)\n            .field(\"btree_cursor\", &())\n            .finish()\n    }\n}\n"
  },
  {
    "path": "core/mvcc/database/checkpoint_state_machine.rs",
    "content": "use crate::mvcc::clock::LogicalClock;\nuse crate::mvcc::database::{\n    DeleteRowStateMachine, MVTableId, MvStore, Row, RowID, RowKey, RowVersion, TxTimestampOrID,\n    WriteRowStateMachine, MVCC_META_KEY_PERSISTENT_TX_TS_MAX, MVCC_META_TABLE_NAME,\n    SQLITE_SCHEMA_MVCC_TABLE_ID,\n};\nuse crate::schema::Index;\nuse crate::state_machine::{StateMachine, StateTransition, TransitionResult};\nuse crate::storage::btree::{BTreeCursor, CursorTrait};\nuse crate::storage::pager::CreateBTreeFlags;\nuse crate::storage::sqlite3_ondisk::DatabaseHeader;\nuse crate::storage::wal::{CheckpointMode, TursoRwLock};\nuse crate::sync::atomic::Ordering;\nuse crate::sync::Arc;\nuse crate::sync::RwLock;\nuse crate::types::{IOCompletions, IOResult, ImmutableRecord};\nuse crate::{turso_assert, turso_assert_eq};\nuse crate::{\n    CheckpointResult, Completion, Connection, IOExt, LimboError, Numeric, Pager, Result, SyncMode,\n    TransactionState, Value, ValueRef,\n};\nuse rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};\nuse std::num::NonZeroU64;\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum CheckpointState {\n    AcquireLock,\n    BeginPagerTxn,\n    WriteRow {\n        write_set_index: usize,\n        requires_seek: bool,\n    },\n    WriteRowStateMachine {\n        write_set_index: usize,\n    },\n    DeleteRowStateMachine {\n        write_set_index: usize,\n    },\n    WriteIndexRow {\n        index_write_set_index: usize,\n        requires_seek: bool,\n    },\n    WriteIndexRowStateMachine {\n        index_write_set_index: usize,\n    },\n    DeleteIndexRowStateMachine {\n        index_write_set_index: usize,\n    },\n    CommitPagerTxn,\n    CheckpointWal,\n    /// Fsync the database file after checkpoint, before truncating WAL.\n    /// This ensures durability: if we crash after WAL truncation but before DB fsync,\n    /// the data would be lost.\n    SyncDbFile,\n    TruncateLogicalLog,\n    FsyncLogicalLog,\n    /// Truncate the WAL file after DB file and logical-log cleanup are safely durable.\n    TruncateWal,\n    Finalize,\n}\n\n/// The states of the locks held by the state machine - these are tracked for error handling so that they are\n/// released if the state machine fails.\npub struct LockStates {\n    blocking_checkpoint_lock_held: bool,\n    pager_read_tx: bool,\n    pager_write_tx: bool,\n}\n\n/// A state machine that performs a complete checkpoint operation on the MVCC store.\n///\n/// The checkpoint process:\n/// 1. Takes a blocking lock on the database so that no other transactions can run during the checkpoint.\n/// 2. Determines which row versions should be written to the B-tree.\n/// 3. Begins a pager transaction\n/// 4. Writes all the selected row versions to the B-tree.\n/// 5. Commits the pager transaction, effectively flushing to the WAL\n/// 6. Immediately does a TRUNCATE checkpoint from the WAL to the DB\n/// 7. Fsync the DB file\n/// 8. Truncate logical log to 0 (salt regenerated in memory), fsync, then truncate WAL\n/// 9. Releases the blocking_checkpoint_lock\npub struct CheckpointStateMachine<Clock: LogicalClock> {\n    /// The current state of the state machine\n    state: CheckpointState,\n    /// The states of the locks held by the state machine - these are tracked for error handling so that they are\n    /// released if the state machine fails.\n    lock_states: LockStates,\n    /// The highest transaction ID that has been made durable in the WAL in a previous checkpoint.\n    durable_txid_max_old: Option<NonZeroU64>,\n    /// The highest transaction ID that will be made durable in the WAL in the current checkpoint.\n    durable_txid_max_new: u64,\n    /// Pager used for writing to the B-tree\n    pager: Arc<Pager>,\n    /// MVCC store containing the row versions.\n    mvstore: Arc<MvStore<Clock>>,\n    /// Connection to the database\n    connection: Arc<Connection>,\n    /// Lock used to block other transactions from running during the checkpoint\n    checkpoint_lock: Arc<TursoRwLock>,\n    /// All committed versions to write to the B-tree.\n    /// In the case of CREATE TABLE / DROP TABLE ops, contains a [SpecialWrite] to create/destroy the B-tree.\n    write_set: Vec<(RowVersion, Option<SpecialWrite>)>,\n    /// State machine for writing rows to the B-tree\n    write_row_state_machine: Option<StateMachine<WriteRowStateMachine>>,\n    /// State machine for deleting rows from the B-tree\n    delete_row_state_machine: Option<StateMachine<DeleteRowStateMachine>>,\n    /// Cursors for the B-trees\n    cursors: HashMap<u64, Arc<RwLock<BTreeCursor>>>,\n    /// Tables or indexes that were created in this checkpoint\n    /// key is the rowid in the sqlite_schema table\n    created_btrees: HashMap<i64, (MVTableId, RowVersion)>,\n    /// Tables that were destroyed in this checkpoint\n    destroyed_tables: HashSet<MVTableId>,\n    /// Indexes that were destroyed in this checkpoint\n    destroyed_indexes: HashSet<MVTableId>,\n    /// Index row changes to write: (index_id, row_version, is_delete)\n    index_write_set: Vec<(MVTableId, RowVersion, bool)>,\n    /// Map from index_id to Index struct (for creating cursors)\n    /// This is populated when we process sqlite_schema rows for indexes\n    index_id_to_index: HashMap<MVTableId, Arc<Index>>,\n    /// Result of the checkpoint\n    checkpoint_result: Option<CheckpointResult>,\n    /// Update connection's transaction state on checkpoint. If checkpoint was called as automatic\n    /// process in a transaction we don't want to change the state as we assume we are already on a\n    /// write transaction and any failure will be cleared on vdbe error handling.\n    update_transaction_state: bool,\n    /// The synchronous mode for fsync operations. When set to Off, fsync is skipped.\n    sync_mode: SyncMode,\n    /// Internal metadata table info for persisting `persistent_tx_ts_max` atomically with pager commit.\n    mvcc_meta_table: Option<(MVTableId, usize)>,\n    /// File-backed databases must persist replay boundary durably.\n    durable_mvcc_metadata: bool,\n    /// Header staged into pager page 1 before commit; published to global_header on success.\n    staged_checkpoint_header: Option<DatabaseHeader>,\n    /// Guard to avoid restaging page 1 across CommitPagerTxn async retries.\n    header_staged_for_commit: bool,\n}\n\n#[derive(Debug, PartialEq, Eq, Clone, Copy)]\n/// Special writes for CREATE TABLE / DROP TABLE / CREATE INDEX / DROP INDEX ops.\n/// These are used to create/destroy B-trees during pager ops.\npub enum SpecialWrite {\n    BTreeCreate {\n        table_id: MVTableId,\n        sqlite_schema_rowid: i64,\n    },\n    BTreeDestroy {\n        table_id: MVTableId,\n        root_page: u64,\n        num_columns: usize,\n    },\n    BTreeCreateIndex {\n        index_id: MVTableId,\n        sqlite_schema_rowid: i64,\n    },\n    BTreeDestroyIndex {\n        index_id: MVTableId,\n        root_page: u64,\n        num_columns: usize,\n    },\n}\n\nimpl<Clock: LogicalClock> CheckpointStateMachine<Clock> {\n    pub fn new(\n        pager: Arc<Pager>,\n        mvstore: Arc<MvStore<Clock>>,\n        connection: Arc<Connection>,\n        update_transaction_state: bool,\n        sync_mode: SyncMode,\n    ) -> Self {\n        let checkpoint_lock = mvstore.blocking_checkpoint_lock.clone();\n        // Prevent stale per-connection schema during checkpoint by using the shared DB schema.\n        // Unlike in WAL mode we actually write stuff from mv store to pager in checkpoint\n        // so this is important.\n        let schema = connection.db.clone_schema();\n        let index_id_to_index = schema\n            .indexes\n            .values()\n            .flatten()\n            .map(|index| {\n                turso_assert!(index.root_page != 0, \"index root_page must be non-zero\");\n                (\n                    mvstore.get_table_id_from_root_page(index.root_page),\n                    index.clone(),\n                )\n            })\n            .collect();\n        let mvcc_meta_table = schema.get_btree_table(MVCC_META_TABLE_NAME).map(|table| {\n            turso_assert!(\n                table.root_page != 0,\n                \"mvcc meta table root_page must be non-zero\"\n            );\n            (\n                mvstore.get_table_id_from_root_page(table.root_page),\n                table.columns.len(),\n            )\n        });\n        let durable_mvcc_metadata =\n            !connection.db.path.starts_with(\":memory:\") && mvcc_meta_table.is_some();\n        let durable_tx_max = mvstore.durable_txid_max.load(Ordering::SeqCst);\n        let durable_txid_max_old = NonZeroU64::new(durable_tx_max);\n        Self {\n            state: CheckpointState::AcquireLock,\n            lock_states: LockStates {\n                blocking_checkpoint_lock_held: false,\n                pager_read_tx: false,\n                pager_write_tx: false,\n            },\n            pager,\n            durable_txid_max_old,\n            durable_txid_max_new: mvstore.durable_txid_max.load(Ordering::SeqCst),\n            mvstore,\n            connection,\n            checkpoint_lock,\n            write_set: Vec::new(),\n            write_row_state_machine: None,\n            delete_row_state_machine: None,\n            cursors: HashMap::default(),\n            created_btrees: HashMap::default(),\n            destroyed_tables: HashSet::default(),\n            destroyed_indexes: HashSet::default(),\n            index_write_set: Vec::new(),\n            index_id_to_index,\n            checkpoint_result: None,\n            update_transaction_state,\n            sync_mode,\n            mvcc_meta_table,\n            durable_mvcc_metadata,\n            staged_checkpoint_header: None,\n            header_staged_for_commit: false,\n        }\n    }\n\n    #[cfg(test)]\n    pub(crate) fn state_for_test(&self) -> CheckpointState {\n        self.state\n    }\n\n    #[cfg(test)]\n    pub(crate) fn checkpoint_bounds_for_test(&self) -> (Option<u64>, u64) {\n        (\n            self.durable_txid_max_old.map(u64::from),\n            self.durable_txid_max_new,\n        )\n    }\n\n    /// Cleanup path for I/O errors that happen while waiting on completions outside\n    /// of `step()`. This mirrors `step()` error handling and also resets pager/WAL\n    /// checkpoint bookkeeping.\n    pub fn cleanup_after_external_io_error(&mut self) {\n        if self.lock_states.pager_write_tx {\n            self.pager.rollback_tx(self.connection.as_ref());\n            if self.update_transaction_state {\n                self.connection.set_tx_state(TransactionState::None);\n            }\n            self.lock_states.pager_write_tx = false;\n            self.lock_states.pager_read_tx = false;\n        } else if self.lock_states.pager_read_tx {\n            self.pager.end_read_tx();\n            if self.update_transaction_state {\n                self.connection.set_tx_state(TransactionState::None);\n            }\n            self.lock_states.pager_read_tx = false;\n        }\n\n        // MVCC checkpointing drives WAL checkpoint directly; on errors we must\n        // explicitly reset both pager and WAL checkpoint states.\n        self.pager.clear_checkpoint_state();\n        if let Some(wal) = self.pager.wal.as_ref() {\n            wal.abort_checkpoint();\n        }\n\n        // Release the checkpoint lock only after checkpoint state has been reset.\n        if self.lock_states.blocking_checkpoint_lock_held {\n            self.checkpoint_lock.unlock();\n            self.lock_states.blocking_checkpoint_lock_held = false;\n        }\n    }\n\n    /// Determine whether the newest valid version of a row should be checkpointed.\n    /// Returns the version to checkpoint if it should be checkpointed, otherwise None.\n    fn maybe_get_checkpointable_version(\n        &self,\n        versions: &[RowVersion],\n        table_id: MVTableId,\n    ) -> Option<RowVersion> {\n        let mut version_to_checkpoint = None;\n        let mut exists_in_db_file = false;\n        // Iterate versions from oldest-to-newest to determine if the row exists in the database file and whether the newest version should be checkpointed.\n        for version in versions.iter() {\n            // Rows marked btree_resident existed in the DB file before MVCC tracked them.\n            // This also applies to synthetic tombstones that use begin=None.\n            if version.btree_resident {\n                exists_in_db_file = true;\n            }\n            // A row is in the database file if:\n            // There is a version whose begin timestamp is <= than the last checkpoint timestamp, AND\n            // There is NO version whose END timestamp is <= than the last checkpoint timestamp.\n            let mut begin_ts = None;\n            if let Some(TxTimestampOrID::Timestamp(b)) = version.begin {\n                begin_ts = Some(b);\n                // A row exists in the DB file if it was checkpointed in a previous checkpoint.\n                // For btree_resident rows we set exists_in_db_file above, regardless of begin encoding.\n                if self\n                    .durable_txid_max_old\n                    .is_some_and(|txid_max_old| b <= u64::from(txid_max_old))\n                {\n                    exists_in_db_file = true;\n                }\n            }\n            let mut end_ts = None;\n            if let Some(TxTimestampOrID::Timestamp(e)) = version.end {\n                end_ts = Some(e);\n                if self\n                    .durable_txid_max_old\n                    .is_some_and(|txid_max_old| e <= u64::from(txid_max_old))\n                {\n                    exists_in_db_file = false;\n                }\n            }\n            if begin_ts.is_none() && end_ts.is_none() {\n                continue;\n            }\n            // Should checkpoint the newest version if:\n            // - It is not a delete and it hasn't been checkpointed yet OR (begin_ts > max_old)\n            // We need the `self.durable_txid_max_old.is_none()` check because before\n            // the first checkpoint there is no persisted MVCC watermark.\n            let is_uncheckpointed_insert = end_ts.is_none()\n                && self.durable_txid_max_old.is_none_or(|txid_max_old| {\n                    begin_ts.is_some_and(|b| b > u64::from(txid_max_old))\n                });\n            // - It is a delete, AND some version of the row exists in the database file.\n            let is_delete_and_exists_in_db_file = end_ts.is_some() && exists_in_db_file;\n            // - It is a delete of a sqlite_schema row that hasn't been checkpointed yet. We need to\n            //   return these even if they don't exist in the DB file so we can track destroyed\n            //   tables/indexes and skip their data rows.\n            let is_schema_delete = table_id == SQLITE_SCHEMA_MVCC_TABLE_ID\n                && !exists_in_db_file\n                && self\n                    .durable_txid_max_old\n                    .is_none_or(|txid_max_old| end_ts.is_some_and(|e| e > u64::from(txid_max_old)));\n            let should_checkpoint =\n                is_uncheckpointed_insert || is_delete_and_exists_in_db_file || is_schema_delete;\n            if should_checkpoint {\n                version_to_checkpoint = Some(version.clone());\n            }\n        }\n        version_to_checkpoint\n    }\n\n    /// Collect all committed versions that need to be written to the B-tree.\n    /// We must only write to the B-tree if:\n    /// 1. The row has not already been checkpointed in a previous checkpoint.\n    ///    TODO: garbage collect row versions after checkpointing.\n    /// 2. Either:\n    ///    * The row is not a delete (we inserted or changed an existing row), OR\n    ///    * The row is a delete AND it exists in the database file already.\n    ///      If the row didn't exist in the database file and was deleted, we can simply not write it.\n    fn collect_committed_table_row_versions(&mut self) {\n        // Invariant: RowID ordering is (table_id, row_id) with table_id ascending.\n        // Since MV table IDs are negative and sqlite_schema is table_id=-1, iterating\n        // in reverse visits sqlite_schema first so CREATE/DROP metadata is applied\n        // before user-table rows in this checkpoint pass.\n        for entry in self.mvstore.rows.iter().rev() {\n            let key = entry.key();\n            tracing::trace!(\"collecting {key:?}\");\n            if self.destroyed_tables.contains(&key.table_id) {\n                // We won't checkpoint rows for tables that will be destroyed in this checkpoint.\n                // There's two forms of destroyed table:\n                // 1. A non-checkpointed table that was created in the logical log and then destroyed. We don't need to do anything about this table in the pager/btree layer.\n                // 2. A checkpointed table that was destroyed in the logical log. We need to destroy the btree in the pager/btree layer.\n                tracing::trace!(\"skipping {key:?}\");\n                continue;\n            }\n\n            let row_versions = entry.value().read();\n\n            if let Some(version) =\n                self.maybe_get_checkpointable_version(&row_versions, key.table_id)\n            {\n                let is_delete = version.end.is_some();\n\n                let mut special_write = None;\n                // Set to true for schema deletes of never-checkpointed tables/indexes.\n                // These don't need to be written to the B-tree, we just need to track them.\n                let mut skip_write = false;\n\n                if version.row.id.table_id == SQLITE_SCHEMA_MVCC_TABLE_ID {\n                    let row_data = ImmutableRecord::from_bin_record(version.row.payload().to_vec());\n\n                    let (col0, col3) = row_data.get_two_values(0, 3).expect(\n                        \"failed to get columns 0 and 3 (type, rootpage) from sqlite_schema\",\n                    );\n\n                    let ValueRef::Text(type_str) = col0 else {\n                        panic!(\"sqlite_schema.type column must be TEXT, got {col0:?}\");\n                    };\n\n                    if let ValueRef::Numeric(Numeric::Integer(root_page)) = col3 {\n                        if type_str.as_str() == \"index\" {\n                            // This is an index schema change\n                            if is_delete {\n                                // DROP INDEX\n                                if root_page < 0 {\n                                    // Index was never checkpointed - derive index_id directly from root_page.\n                                    // No BTreeDestroyIndex needed since there's no physical B-tree.\n                                    let index_id = MVTableId(root_page);\n                                    self.destroyed_indexes.insert(index_id);\n                                    skip_write = true;\n                                } else {\n                                    // DROP INDEX - index was checkpointed\n                                    let index_id = self\n                                        .mvstore\n                                        .table_id_to_rootpage\n                                        .iter()\n                                        .find(|entry| {\n                                            entry.value().is_some_and(|r| r == root_page as u64)\n                                        })\n                                        .map(|entry| *entry.key())\n                                        .expect(\n                                            \"index_id to rootpage mapping should exist for dropped index\",\n                                        );\n\n                                    self.destroyed_indexes.insert(index_id);\n\n                                    // DROP INDEX during checkpoint: schema may no longer contain the index definition.\n                                    // Fixes DROP INDEX during checkpoint when the schema cache no longer\n                                    // contains the index metadata; we only need a cursor to destroy pages so num_columns is not important.\n                                    let num_columns = self\n                                        .index_id_to_index\n                                        .get(&index_id)\n                                        .map(|index| index.columns.len())\n                                        .unwrap_or(0);\n\n                                    special_write = Some(SpecialWrite::BTreeDestroyIndex {\n                                        index_id,\n                                        root_page: root_page as u64,\n                                        num_columns,\n                                    });\n                                }\n                            } else if root_page < 0 {\n                                // CREATE INDEX (root page is negative so the index has not been checkpointed yet).\n                                let index_id = MVTableId::from(root_page);\n                                let sqlite_schema_rowid = version.row.id.row_id.to_int_or_panic();\n\n                                special_write = Some(SpecialWrite::BTreeCreateIndex {\n                                    index_id,\n                                    sqlite_schema_rowid,\n                                });\n                            } else {\n                                // Index schema row update (e.g. ALTER TABLE RENAME COLUMN propagates\n                                // to index SQL). No B-tree creation needed; the row itself is written\n                                // to sqlite_schema below. See: test_checkpoint_allows_index_schema_update_after_rename_column.\n                            }\n                        } else if type_str.as_str() == \"table\" {\n                            // This is a table schema change (existing logic)\n                            tracing::trace!(\"table schema change with root page {root_page}, is_delete={is_delete}\");\n                            if is_delete {\n                                if root_page < 0 {\n                                    // Table was never checkpointed - derive table_id directly from root_page.\n                                    // No BTreeDestroy needed since there's no physical B-tree.\n                                    let table_id = MVTableId::from(root_page);\n                                    self.destroyed_tables.insert(table_id);\n                                    skip_write = true;\n                                } else {\n                                    // Table was checkpointed - look up by physical root page\n                                    let table_id = self\n                                        .mvstore\n                                        .table_id_to_rootpage\n                                        .iter()\n                                        .find(|entry| {\n                                            entry.value().is_some_and(|r| r == root_page as u64)\n                                        })\n                                        .map(|entry| *entry.key())\n                                        .expect(\"table_id to rootpage mapping should exist\");\n                                    self.destroyed_tables.insert(table_id);\n\n                                    // Destroy the B-tree in the pager during checkpoint\n                                    special_write = Some(SpecialWrite::BTreeDestroy {\n                                        table_id,\n                                        root_page: root_page as u64,\n                                        num_columns: version.row.column_count,\n                                    });\n                                }\n                            } else if root_page < 0 {\n                                // CREATE TABLE (root page is negative so the table has not been checkpointed yet).\n                                let table_id = MVTableId::from(root_page);\n                                let sqlite_schema_rowid = version.row.id.row_id.to_int_or_panic();\n                                special_write = Some(SpecialWrite::BTreeCreate {\n                                    table_id,\n                                    sqlite_schema_rowid,\n                                });\n                            } else {\n                                // ALTER TABLE. No \"special write is needed\"; we'll just update the row in sqlite_schema.\n                            }\n                        }\n                    }\n                }\n                if !skip_write {\n                    tracing::trace!(\"adding to write_set {:?}\", (&version, &special_write));\n                    self.write_set.push((version, special_write));\n                }\n            }\n        }\n        // Writing in ascending order of rowid gives us a better chance of using balance-quick algorithm\n        // in case of an insert-heavy checkpoint.\n        self.write_set.sort_by_key(|version| {\n            (\n                // Sort by table_id descending (schema changes first)\n                std::cmp::Reverse(version.0.row.id.table_id),\n                // Then by row_id ascending\n                version.0.row.id.row_id.clone(),\n            )\n        });\n    }\n\n    /// Collect all committed index row versions that need to be written to the B-tree.\n    /// Index rows are stored separately from table rows and must be checkpointed independently.\n    /// We must only write to the B-tree if:\n    /// 1. The row has not already been checkpointed in a previous checkpoint.\n    /// 2. Either:\n    ///    * The row is not a delete (we inserted or changed an existing row), OR\n    ///    * The row is a delete AND it exists in the database file already.\n    fn collect_committed_index_row_versions(&mut self) {\n        for entry in self.mvstore.index_rows.iter() {\n            let index_id = *entry.key();\n\n            // Skip destroyed indexes - we won't checkpoint rows for indexes that will be destroyed\n            if self.destroyed_indexes.contains(&index_id) {\n                continue;\n            }\n\n            let index_rows_map = entry.value();\n            for entry in index_rows_map.iter() {\n                let versions = entry.value().read();\n\n                if let Some(version) = self.maybe_get_checkpointable_version(&versions, index_id) {\n                    let is_delete = version.end.is_some();\n\n                    // Only write the row to the B-tree if it is not a delete, or if it is a delete and it exists in\n                    // the database file.\n                    self.index_write_set.push((index_id, version, is_delete));\n                }\n            }\n        }\n    }\n\n    /// Get the current row version to write to the B-tree\n    fn get_current_row_version(\n        &self,\n        write_set_index: usize,\n    ) -> Option<&(RowVersion, Option<SpecialWrite>)> {\n        self.write_set.get(write_set_index)\n    }\n\n    /// Mutably get the current row version to write to the B-tree\n    fn get_current_row_version_mut(\n        &mut self,\n        write_set_index: usize,\n    ) -> Option<&mut (RowVersion, Option<SpecialWrite>)> {\n        self.write_set.get_mut(write_set_index)\n    }\n\n    /// Check if we have more rows to write\n    fn has_more_rows(&self, write_set_index: usize) -> bool {\n        write_set_index < self.write_set.len()\n    }\n\n    /// Fsync the logical log file\n    fn fsync_logical_log(&self) -> Result<Completion> {\n        self.mvstore.storage.sync(self.pager.get_sync_type())\n    }\n\n    /// Truncate the logical log file\n    fn truncate_logical_log(&self) -> Result<Completion> {\n        self.mvstore.storage.truncate()\n    }\n\n    /// Perform a TRUNCATE checkpoint on the WAL\n    fn checkpoint_wal(&self) -> Result<IOResult<CheckpointResult>> {\n        let Some(wal) = &self.pager.wal else {\n            panic!(\"No WAL to checkpoint\");\n        };\n        match wal.checkpoint(\n            &self.pager,\n            CheckpointMode::Truncate {\n                upper_bound_inclusive: None,\n            },\n        )? {\n            IOResult::Done(result) => Ok(IOResult::Done(result)),\n            IOResult::IO(io) => Ok(IOResult::IO(io)),\n        }\n    }\n\n    /// Garbage-collect row versions for rows that were just checkpointed.\n    /// Must be called AFTER durable_txid_max is updated and BEFORE the\n    /// checkpoint lock is released (no concurrent writers under blocking lock).\n    fn gc_checkpointed_versions(&self) {\n        // Safety: entry removal after dropping the version-chain write lock has a\n        // TOCTOU gap — a concurrent writer could insert between the two. This is\n        // only safe because the blocking checkpoint lock prevents concurrent writers.\n        // If we ever move to a non-blocking checkpoint, this must switch to lazy\n        // removal (like background GC) or hold the write lock across the remove().\n        assert!(\n            self.lock_states.blocking_checkpoint_lock_held,\n            \"gc_checkpointed_versions requires the blocking checkpoint lock\"\n        );\n        let lwm = self.mvstore.compute_lwm();\n        let ckpt_max = self.durable_txid_max_new;\n\n        for (row_version, _special_write) in &self.write_set {\n            let row_id = &row_version.row.id;\n            let Some(entry) = self.mvstore.rows.get(row_id) else {\n                // The MVCC metadata table row (persistent_tx_ts_max) is staged\n                // directly into the write set by maybe_stage_mvcc_metadata_write() and do not\n                // have a backing in-memory MVCC version chain. Skip GC for these.\n                assert!(\n                    self.mvcc_meta_table\n                        .is_some_and(|(tid, _)| tid == row_id.table_id),\n                    \"row {row_id:?} missing from MVCC store but is not an MVCC metadata table row\"\n                );\n                continue;\n            };\n            let is_now_empty = {\n                let mut versions = entry.value().write();\n                MvStore::<Clock>::gc_version_chain(&mut versions, lwm, ckpt_max);\n                versions.is_empty()\n            };\n            if is_now_empty {\n                self.mvstore.rows.remove(row_id);\n            }\n        }\n\n        for (index_id, row_version, _is_delete) in &self.index_write_set {\n            let RowKey::Record(sortable_key) = &row_version.row.id.row_id else {\n                unreachable!(\"index row versions always have Record keys\");\n            };\n            let outer_entry = self\n                .mvstore\n                .index_rows\n                .get(index_id)\n                .expect(\"index_id from write set must exist in index_rows\");\n            let inner_map = outer_entry.value();\n            let is_now_empty = {\n                let inner_entry = inner_map\n                    .get(sortable_key)\n                    .expect(\"index row from write set must exist in inner map\");\n                let mut versions = inner_entry.value().write();\n                MvStore::<Clock>::gc_version_chain(&mut versions, lwm, ckpt_max);\n                versions.is_empty()\n            };\n            if is_now_empty {\n                inner_map.remove(sortable_key);\n            }\n        }\n    }\n\n    /// Stages synthetic `persistent_tx_ts_max` row into the checkpoint write set\n    /// so it is committed atomically with all other data in the same pager transaction.\n    /// This is the mechanism that advances the durable replay boundary; on recovery, only\n    /// logical-log frames with `commit_ts > persistent_tx_ts_max` are replayed.\n    /// No-op when metadata hasn't advanced or when running in-memory (no durable metadata).\n    fn maybe_stage_mvcc_metadata_write(&mut self) -> Result<()> {\n        if !self.durable_mvcc_metadata {\n            return Ok(());\n        }\n        let old = self.durable_txid_max_old.map(u64::from).unwrap_or_default();\n        let new = self.durable_txid_max_new;\n        if new <= old {\n            return Ok(());\n        }\n\n        let (table_id, num_columns) = self.mvcc_meta_table.ok_or_else(|| {\n            LimboError::Corrupt(format!(\n                \"Missing required internal metadata table {MVCC_META_TABLE_NAME}\"\n            ))\n        })?;\n        let new_i64 = i64::try_from(new).map_err(|_| {\n            LimboError::Corrupt(format!(\"MVCC checkpoint timestamp does not fit i64: {new}\"))\n        })?;\n        let record = ImmutableRecord::from_values(\n            &[\n                Value::build_text(MVCC_META_KEY_PERSISTENT_TX_TS_MAX),\n                Value::from_i64(new_i64),\n            ],\n            2,\n        );\n        let row = Row::new_table_row(\n            RowID::new(table_id, RowKey::Int(1)),\n            record.get_payload().to_vec(),\n            num_columns,\n        );\n        self.write_set.push((\n            RowVersion {\n                id: 0,\n                begin: Some(TxTimestampOrID::Timestamp(new)),\n                end: None,\n                row,\n                btree_resident: true,\n            },\n            None,\n        ));\n        Ok(())\n    }\n\n    fn step_inner(&mut self, _context: &()) -> Result<TransitionResult<CheckpointResult>> {\n        match &self.state {\n            CheckpointState::AcquireLock => {\n                tracing::debug!(\"Acquiring blocking checkpoint lock\");\n                let locked = self.checkpoint_lock.write();\n                if !locked {\n                    return Err(crate::LimboError::Busy);\n                }\n                self.lock_states.blocking_checkpoint_lock_held = true;\n\n                self.collect_committed_table_row_versions();\n                tracing::debug!(\"Collected {} committed versions\", self.write_set.len());\n\n                self.collect_committed_index_row_versions();\n                tracing::debug!(\"Collected {} index row changes\", self.index_write_set.len());\n                // Checkpoint boundary is derived from a stable snapshot under the blocking lock:\n                // old durable boundary plus the latest committed tx watermark. This covers both\n                // row/index commits and header-only commits.\n                let durable_old = self.durable_txid_max_old.map(u64::from).unwrap_or_default();\n                let committed_max = self.mvstore.last_committed_tx_ts.load(Ordering::Acquire);\n                self.durable_txid_max_new = durable_old.max(committed_max);\n                self.maybe_stage_mvcc_metadata_write()?;\n\n                self.mvstore\n                    .storage\n                    .on_checkpoint_start(self.durable_txid_max_new);\n\n                if self.write_set.is_empty() && self.index_write_set.is_empty() {\n                    // Nothing to checkpoint, skip pager txn and go straight to WAL checkpoint.\n                    self.state = CheckpointState::CheckpointWal;\n                } else {\n                    self.state = CheckpointState::BeginPagerTxn;\n                }\n                Ok(TransitionResult::Continue)\n            }\n            CheckpointState::BeginPagerTxn => {\n                tracing::debug!(\"Beginning pager transaction\");\n                // Start a pager transaction to write committed versions to B-tree\n                let read_tx_active = self\n                    .pager\n                    .wal\n                    .as_ref()\n                    .is_some_and(|wal| wal.holds_read_lock());\n                if !read_tx_active {\n                    self.pager.begin_read_tx()?;\n                    self.lock_states.pager_read_tx = true;\n                }\n\n                self.pager.io.block(|| self.pager.begin_write_tx())?;\n                if self.update_transaction_state {\n                    self.connection.set_tx_state(TransactionState::Write {\n                        schema_did_change: false,\n                    }); // TODO: schema_did_change??\n                }\n                self.lock_states.pager_write_tx = true;\n                self.state = CheckpointState::WriteRow {\n                    write_set_index: 0,\n                    requires_seek: true,\n                };\n                Ok(TransitionResult::Continue)\n            }\n\n            CheckpointState::WriteRow {\n                write_set_index,\n                requires_seek,\n            } => {\n                let write_set_index = *write_set_index;\n                let requires_seek = *requires_seek;\n\n                if !self.has_more_rows(write_set_index) {\n                    // Done writing all table rows, now process index rows\n                    if self.index_write_set.is_empty() {\n                        // No index rows to write, skip to commit\n                        self.state = CheckpointState::CommitPagerTxn;\n                    } else {\n                        // Start writing index rows\n                        self.state = CheckpointState::WriteIndexRow {\n                            index_write_set_index: 0,\n                            requires_seek: true,\n                        };\n                    }\n                    return Ok(TransitionResult::Continue);\n                }\n\n                let (num_columns, table_id, special_write) = {\n                    let (row_version, special_write) = self\n                        .get_current_row_version(write_set_index)\n                        .ok_or_else(|| {\n                            LimboError::InternalError(\n                                \"row version not found in write set\".to_string(),\n                            )\n                        })?;\n                    tracing::trace!(\"checkpointing row {row_version:?} \");\n                    (\n                        row_version.row.column_count,\n                        row_version.row.id.table_id,\n                        *special_write,\n                    )\n                };\n\n                // Handle CREATE TABLE / DROP TABLE / CREATE INDEX / DROP INDEX ops\n                if let Some(special_write) = special_write {\n                    match special_write {\n                        SpecialWrite::BTreeCreate { table_id, .. } => {\n                            let created_root_page: u32 = self.pager.io.block(|| {\n                                self.pager.btree_create(&CreateBTreeFlags::new_table())\n                            })?;\n                            self.mvstore.insert_table_id_to_rootpage(\n                                table_id,\n                                Some(created_root_page as u64),\n                            );\n                        }\n                        SpecialWrite::BTreeDestroy {\n                            table_id,\n                            root_page,\n                            num_columns,\n                        } => {\n                            let known_root_page = self\n                                .mvstore\n                                .table_id_to_rootpage\n                                .get(&table_id)\n                                .expect(\"Table ID does not have a root page\");\n                            let known_root_page = known_root_page\n                                .value()\n                                .expect(\"Table ID does not have a root page\");\n                            turso_assert_eq!(\n                                known_root_page,\n                                root_page,\n                                \"checkpoint root page mismatch for BTreeDestroy\",\n                                { \"known_root_page\": known_root_page, \"schema_root_page\": root_page }\n                            );\n                            let cursor = if let Some(cursor) = self.cursors.get(&known_root_page) {\n                                cursor.clone()\n                            } else {\n                                let cursor = BTreeCursor::new_table(\n                                    self.pager.clone(),\n                                    known_root_page as i64,\n                                    num_columns,\n                                );\n                                let cursor = Arc::new(RwLock::new(cursor));\n                                self.cursors.insert(root_page, cursor.clone());\n                                cursor\n                            };\n                            self.pager.io.block(|| cursor.write().btree_destroy())?;\n                            self.destroyed_tables.insert(table_id);\n                        }\n                        SpecialWrite::BTreeCreateIndex { index_id, .. } => {\n                            let created_root_page: u32 = self.pager.io.block(|| {\n                                self.pager.btree_create(&CreateBTreeFlags::new_index())\n                            })?;\n                            self.mvstore.insert_table_id_to_rootpage(\n                                index_id,\n                                Some(created_root_page as u64),\n                            );\n                            // Index struct should already be stored in index_id_to_index from collect_committed_versions\n                            turso_assert!(\n                                self.index_id_to_index.contains_key(&index_id),\n                                \"checkpoint index struct missing before BTreeCreateIndex\",\n                                { \"index_id\": i64::from(index_id) }\n                            );\n                        }\n                        SpecialWrite::BTreeDestroyIndex {\n                            index_id,\n                            root_page,\n                            num_columns,\n                        } => {\n                            let known_root_page = self\n                                .mvstore\n                                .table_id_to_rootpage\n                                .get(&index_id)\n                                .expect(\"Index ID does not have a root page\");\n                            let known_root_page = known_root_page\n                                .value()\n                                .expect(\"Index ID does not have a root page\");\n                            turso_assert_eq!(\n                                known_root_page,\n                                root_page,\n                                \"checkpoint root page mismatch for BTreeDestroyIndex\",\n                                { \"known_root_page\": known_root_page, \"schema_root_page\": root_page }\n                            );\n\n                            let cursor = if let Some(cursor) = self.cursors.get(&known_root_page) {\n                                cursor.clone()\n                            } else if let Some(index) = self.index_id_to_index.get(&index_id) {\n                                let cursor = BTreeCursor::new_index(\n                                    self.pager.clone(),\n                                    known_root_page as i64,\n                                    index.as_ref(),\n                                    num_columns,\n                                );\n                                let cursor = Arc::new(RwLock::new(cursor));\n                                self.cursors.insert(root_page, cursor.clone());\n                                cursor\n                            } else {\n                                // DROP INDEX destroy path: schema may no longer contain the index definition.\n                                // We only need a cursor to destroy pages so num_columns is not important.\n                                Arc::new(RwLock::new(BTreeCursor::new_table(\n                                    self.pager.clone(),\n                                    known_root_page as i64,\n                                    num_columns,\n                                )))\n                            };\n                            self.pager.io.block(|| cursor.write().btree_destroy())?;\n                            self.destroyed_indexes.insert(index_id);\n                        }\n                    }\n                }\n\n                if self.destroyed_tables.contains(&table_id) {\n                    // Don't write rows for tables that will be destroyed in this checkpoint.\n                    self.state = CheckpointState::WriteRow {\n                        write_set_index: write_set_index + 1,\n                        requires_seek: true,\n                    };\n                    return Ok(TransitionResult::Continue);\n                }\n\n                let root_page = {\n                    let root_page = self\n                        .mvstore\n                        .table_id_to_rootpage\n                        .get(&table_id)\n                        .unwrap_or_else(|| {\n                            panic!(\n                                \"Table ID does not have a root page: {table_id}, row_version: {:?}\",\n                                self.get_current_row_version(write_set_index)\n                                    .expect(\"row version should exist\")\n                            )\n                        });\n                    root_page.value().unwrap_or_else(|| {\n                        panic!(\n                            \"Table ID does not have a root page: {table_id}, row_version: {:?}\",\n                            self.get_current_row_version(write_set_index)\n                                .expect(\"row version should exist\")\n                        )\n                    })\n                };\n\n                // If a table was created, it now has a real root page allocated for it, but the 'root_page' field in the sqlite_schema record is still the table id.\n                // So we need to rewrite the row version to use the real root page.\n                if let Some(SpecialWrite::BTreeCreate {\n                    table_id,\n                    sqlite_schema_rowid,\n                }) = special_write\n                {\n                    let root_page = {\n                        let root_page = self\n                            .mvstore\n                            .table_id_to_rootpage\n                            .get(&table_id)\n                            .expect(\"Table ID does not have a root page\");\n                        root_page\n                            .value()\n                            .expect(\"Table ID does not have a root page\")\n                    };\n                    let row_version = {\n                        let (row_version, _) = self\n                            .get_current_row_version_mut(write_set_index)\n                            .ok_or_else(|| {\n                                LimboError::InternalError(\n                                    \"row version not found in write set\".to_string(),\n                                )\n                            })?;\n                        let record =\n                            ImmutableRecord::from_bin_record(row_version.row.payload().to_vec());\n\n                        let mut values = record.get_values_owned()?;\n                        values[3] = Value::from_i64(root_page as i64);\n                        let record = ImmutableRecord::from_values(&values, values.len());\n                        row_version.row.data = Some(record.get_payload().to_owned());\n                        row_version.clone()\n                    };\n                    self.created_btrees\n                        .insert(sqlite_schema_rowid, (table_id, row_version));\n                } else if let Some(SpecialWrite::BTreeCreateIndex {\n                    index_id,\n                    sqlite_schema_rowid,\n                }) = special_write\n                {\n                    // Same for index btrees.\n                    let root_page = {\n                        let root_page = self\n                            .mvstore\n                            .table_id_to_rootpage\n                            .get(&index_id)\n                            .expect(\"Index ID does not have a root page\");\n                        root_page\n                            .value()\n                            .expect(\"Index ID does not have a root page\")\n                    };\n                    let row_version = {\n                        let (row_version, _) = self\n                            .get_current_row_version_mut(write_set_index)\n                            .ok_or_else(|| {\n                                LimboError::InternalError(\n                                    \"row version not found in write set\".to_string(),\n                                )\n                            })?;\n                        let record =\n                            ImmutableRecord::from_bin_record(row_version.row.payload().to_vec());\n                        let mut values = record.get_values_owned()?;\n                        values[3] = Value::from_i64(root_page as i64);\n                        let record = ImmutableRecord::from_values(&values, values.len());\n                        row_version.row.data = Some(record.get_payload().to_owned());\n                        row_version.clone()\n                    };\n\n                    self.created_btrees\n                        .insert(sqlite_schema_rowid, (index_id, row_version));\n                }\n\n                // Get or create cursor for this table\n                let cursor = if let Some(cursor) = self.cursors.get(&root_page) {\n                    cursor.clone()\n                } else {\n                    let cursor =\n                        BTreeCursor::new_table(self.pager.clone(), root_page as i64, num_columns);\n                    let cursor = Arc::new(RwLock::new(cursor));\n                    self.cursors.insert(root_page, cursor.clone());\n                    cursor\n                };\n\n                let (row_version, _) =\n                    self.get_current_row_version(write_set_index)\n                        .ok_or_else(|| {\n                            LimboError::InternalError(\n                                \"row version not found in write set\".to_string(),\n                            )\n                        })?;\n\n                // Check if this is an insert or delete\n                if row_version.end.is_some() {\n                    // This is a delete operation.\n                    // Don't write the deletion record to the b-tree if the b-tree was just created; we can no-op in this case,\n                    // since there is no existing row to delete.\n                    if self\n                        .created_btrees\n                        .values()\n                        .any(|(table_id, _)| *table_id == row_version.row.id.table_id)\n                    {\n                        self.state = CheckpointState::WriteRow {\n                            write_set_index: write_set_index + 1,\n                            requires_seek: true,\n                        };\n                        return Ok(TransitionResult::Continue);\n                    }\n                    let state_machine = self\n                        .mvstore\n                        .delete_row_from_pager(row_version.row.id.clone(), cursor)?;\n                    self.delete_row_state_machine = Some(state_machine);\n                    self.state = CheckpointState::DeleteRowStateMachine { write_set_index };\n                } else {\n                    // This is an insert/update operation\n                    let state_machine =\n                        self.mvstore\n                            .write_row_to_pager(&row_version.row, cursor, requires_seek)?;\n                    self.write_row_state_machine = Some(state_machine);\n                    self.state = CheckpointState::WriteRowStateMachine { write_set_index };\n                }\n\n                Ok(TransitionResult::Continue)\n            }\n\n            CheckpointState::WriteRowStateMachine { write_set_index } => {\n                let write_set_index = *write_set_index;\n                let write_row_state_machine =\n                    self.write_row_state_machine.as_mut().ok_or_else(|| {\n                        LimboError::InternalError(\n                            \"write_row_state_machine not initialized\".to_string(),\n                        )\n                    })?;\n\n                match write_row_state_machine.step(&())? {\n                    IOResult::IO(io) => Ok(TransitionResult::Io(io)),\n                    IOResult::Done(_) => {\n                        self.state = CheckpointState::WriteRow {\n                            write_set_index: write_set_index + 1,\n                            requires_seek: true,\n                        };\n                        Ok(TransitionResult::Continue)\n                    }\n                }\n            }\n\n            CheckpointState::DeleteRowStateMachine { write_set_index } => {\n                let write_set_index = *write_set_index;\n                let delete_row_state_machine =\n                    self.delete_row_state_machine.as_mut().ok_or_else(|| {\n                        LimboError::InternalError(\n                            \"delete_row_state_machine not initialized\".to_string(),\n                        )\n                    })?;\n\n                match delete_row_state_machine.step(&())? {\n                    IOResult::IO(io) => Ok(TransitionResult::Io(io)),\n                    IOResult::Done(_) => {\n                        self.state = CheckpointState::WriteRow {\n                            write_set_index: write_set_index + 1,\n                            requires_seek: true,\n                        };\n                        Ok(TransitionResult::Continue)\n                    }\n                }\n            }\n\n            CheckpointState::WriteIndexRow {\n                index_write_set_index,\n                requires_seek,\n            } => {\n                let index_write_set_index = *index_write_set_index;\n                let requires_seek = *requires_seek;\n\n                if index_write_set_index >= self.index_write_set.len() {\n                    // Done writing all index rows\n                    self.state = CheckpointState::CommitPagerTxn;\n                    return Ok(TransitionResult::Continue);\n                }\n\n                let (index_id, row_version, is_delete) =\n                    &self.index_write_set[index_write_set_index];\n\n                // Skip destroyed indexes\n                if self.destroyed_indexes.contains(index_id) {\n                    self.state = CheckpointState::WriteIndexRow {\n                        index_write_set_index: index_write_set_index + 1,\n                        requires_seek: true,\n                    };\n                    return Ok(TransitionResult::Continue);\n                }\n\n                // Get Index struct - it should exist for all indexes we're checkpointing\n                let index = self.index_id_to_index.get(index_id).unwrap_or_else(|| {\n                    panic!(\n                    \"Index struct for index_id {index_id} must exist when checkpointing index rows\",\n                )\n                });\n\n                // Get root page for this index\n                let root_page = self\n                    .mvstore\n                    .table_id_to_rootpage\n                    .get(index_id)\n                    .unwrap_or_else(|| panic!(\"Index ID {index_id} does not have a root page\"));\n                let root_page = root_page\n                    .value()\n                    .unwrap_or_else(|| panic!(\"Index ID {index_id} does not have a root page\"));\n\n                // Get or create cursor for this index\n                let cursor = if let Some(cursor) = self.cursors.get(&root_page) {\n                    cursor.clone()\n                } else {\n                    let cursor = BTreeCursor::new_index(\n                        self.pager.clone(),\n                        root_page as i64,\n                        index.as_ref(),\n                        index.columns.len(),\n                    );\n                    let cursor = Arc::new(RwLock::new(cursor));\n                    self.cursors.insert(root_page, cursor.clone());\n                    cursor\n                };\n\n                // Check if this is an insert or delete\n                if *is_delete {\n                    // This is a delete operation. Don't write the deletion record to the b-tree if the b-tree was just created; we can no-op in this case,\n                    // since there is no existing row to delete.\n                    if self\n                        .created_btrees\n                        .values()\n                        .any(|(table_id, _)| *table_id == row_version.row.id.table_id)\n                    {\n                        self.state = CheckpointState::WriteIndexRow {\n                            index_write_set_index: index_write_set_index + 1,\n                            requires_seek: true,\n                        };\n                        return Ok(TransitionResult::Continue);\n                    }\n                    let state_machine = self\n                        .mvstore\n                        .delete_row_from_pager(row_version.row.id.clone(), cursor)?;\n                    self.delete_row_state_machine = Some(state_machine);\n                    self.state = CheckpointState::DeleteIndexRowStateMachine {\n                        index_write_set_index,\n                    };\n                } else {\n                    // This is an insert/update operation\n                    let state_machine =\n                        self.mvstore\n                            .write_row_to_pager(&row_version.row, cursor, requires_seek)?;\n                    self.write_row_state_machine = Some(state_machine);\n                    self.state = CheckpointState::WriteIndexRowStateMachine {\n                        index_write_set_index,\n                    };\n                }\n\n                Ok(TransitionResult::Continue)\n            }\n\n            CheckpointState::WriteIndexRowStateMachine {\n                index_write_set_index,\n            } => {\n                let index_write_set_index = *index_write_set_index;\n                let write_row_state_machine =\n                    self.write_row_state_machine.as_mut().ok_or_else(|| {\n                        LimboError::InternalError(\n                            \"write_row_state_machine not initialized\".to_string(),\n                        )\n                    })?;\n\n                match write_row_state_machine.step(&())? {\n                    IOResult::IO(io) => Ok(TransitionResult::Io(io)),\n                    IOResult::Done(_) => {\n                        self.state = CheckpointState::WriteIndexRow {\n                            index_write_set_index: index_write_set_index + 1,\n                            requires_seek: true,\n                        };\n                        Ok(TransitionResult::Continue)\n                    }\n                }\n            }\n\n            CheckpointState::DeleteIndexRowStateMachine {\n                index_write_set_index,\n            } => {\n                let index_write_set_index = *index_write_set_index;\n                let delete_row_state_machine =\n                    self.delete_row_state_machine.as_mut().ok_or_else(|| {\n                        LimboError::InternalError(\n                            \"delete_row_state_machine not initialized\".to_string(),\n                        )\n                    })?;\n\n                match delete_row_state_machine.step(&())? {\n                    IOResult::IO(io) => Ok(TransitionResult::Io(io)),\n                    IOResult::Done(_) => {\n                        self.state = CheckpointState::WriteIndexRow {\n                            index_write_set_index: index_write_set_index + 1,\n                            requires_seek: true,\n                        };\n                        Ok(TransitionResult::Continue)\n                    }\n                }\n            }\n\n            CheckpointState::CommitPagerTxn => {\n                if !self.header_staged_for_commit {\n                    let mut checkpoint_header =\n                        *self.mvstore.global_header.read().as_ref().ok_or_else(|| {\n                            LimboError::InternalError(\n                                \"global_header not initialized during checkpoint\".to_string(),\n                            )\n                        })?;\n                    checkpoint_header.schema_cookie =\n                        self.connection.db.schema.lock().schema_version.into();\n                    let staged_header = self.pager.io.block(|| {\n                        self.pager.with_header_mut(|header| {\n                            // Keep pager-maintained fields (for example database_size/change_counter)\n                            // intact, and apply only MVCC header mutations that are authored via\n                            // SetCookie/PRAGMA paths.\n                            header.schema_cookie = checkpoint_header.schema_cookie;\n                            header.user_version = checkpoint_header.user_version;\n                            header.application_id = checkpoint_header.application_id;\n                            header.vacuum_mode_largest_root_page =\n                                checkpoint_header.vacuum_mode_largest_root_page;\n                            header.incremental_vacuum_enabled =\n                                checkpoint_header.incremental_vacuum_enabled;\n                            *header\n                        })\n                    })?;\n                    self.staged_checkpoint_header = Some(staged_header);\n                    self.header_staged_for_commit = true;\n                }\n                // If commit_tx fails, the `?` propagates to step() which rolls back\n                // the pager transaction. durable_txid_max is NOT advanced (only happens\n                // on success below), so a retry will re-stage from the previous boundary.\n                // The logical log is also unaffected — its offset is not advanced here.\n                tracing::debug!(\"Committing pager transaction\");\n                let result = self\n                    .pager\n                    .commit_tx(&self.connection, self.update_transaction_state)?;\n                match result {\n                    IOResult::Done(_) => {\n                        // Pager commit atomically staged data + metadata into WAL.\n                        // Advance the in-memory durable boundary immediately so that if\n                        // later checkpoint phases fail, a same-process retry starts from\n                        // this durable prefix instead of re-staging older versions.\n                        self.mvstore\n                            .durable_txid_max\n                            .store(self.durable_txid_max_new, Ordering::SeqCst);\n                        self.state = CheckpointState::CheckpointWal;\n                        self.lock_states.pager_read_tx = false;\n                        self.lock_states.pager_write_tx = false;\n                        // Publish the exact page-1 snapshot that was staged into the pager txn.\n                        let header = self.staged_checkpoint_header.take().ok_or_else(|| {\n                            LimboError::InternalError(\n                                \"checkpoint header was not staged before pager commit\".to_string(),\n                            )\n                        })?;\n                        self.mvstore.global_header.write().replace(header);\n                        Ok(TransitionResult::Continue)\n                    }\n                    IOResult::IO(io) => Ok(TransitionResult::Io(io)),\n                }\n            }\n\n            CheckpointState::TruncateLogicalLog => {\n                tracing::debug!(\"Truncating logical log file\");\n                let c = self.truncate_logical_log()?;\n                self.state = CheckpointState::FsyncLogicalLog;\n                // if Completion Completed without errors we can continue\n                if c.succeeded() {\n                    Ok(TransitionResult::Continue)\n                } else {\n                    Ok(TransitionResult::Io(IOCompletions::Single(c)))\n                }\n            }\n\n            CheckpointState::FsyncLogicalLog => {\n                // Skip fsync when synchronous mode is off\n                if self.sync_mode == SyncMode::Off {\n                    tracing::debug!(\"Skipping fsync of logical log file (synchronous=off)\");\n                    self.state = CheckpointState::TruncateWal;\n                    return Ok(TransitionResult::Continue);\n                }\n                tracing::debug!(\"Fsyncing logical log file\");\n                let c = self.fsync_logical_log()?;\n                self.state = CheckpointState::TruncateWal;\n                // if Completion Completed without errors we can continue\n                if c.succeeded() {\n                    Ok(TransitionResult::Continue)\n                } else {\n                    Ok(TransitionResult::Io(IOCompletions::Single(c)))\n                }\n            }\n\n            CheckpointState::CheckpointWal => {\n                tracing::debug!(\"Performing TRUNCATE checkpoint on WAL\");\n                match self.checkpoint_wal()? {\n                    IOResult::Done(result) => {\n                        self.checkpoint_result = Some(result);\n                        self.state = CheckpointState::SyncDbFile;\n                        Ok(TransitionResult::Continue)\n                    }\n                    IOResult::IO(io) => Ok(TransitionResult::Io(io)),\n                }\n            }\n\n            CheckpointState::SyncDbFile => {\n                // Fsync database file before truncating WAL.\n                // This ensures durability: if we crash after WAL truncation but before DB fsync,\n                // the checkpointed data would be lost.\n                if self.sync_mode == SyncMode::Off {\n                    tracing::debug!(\"Skipping fsync of database file (synchronous=off)\");\n                    self.state = CheckpointState::TruncateLogicalLog;\n                    return Ok(TransitionResult::Continue);\n                }\n\n                let checkpoint_result = self\n                    .checkpoint_result\n                    .as_mut()\n                    .expect(\"checkpoint_result should be set\");\n\n                // Only sync if we actually backfilled any frames\n                if checkpoint_result.wal_checkpoint_backfilled == 0 {\n                    self.state = CheckpointState::TruncateLogicalLog;\n                    return Ok(TransitionResult::Continue);\n                }\n\n                // Check if we already sent the sync\n                if checkpoint_result.db_sync_sent {\n                    self.state = CheckpointState::TruncateLogicalLog;\n                    return Ok(TransitionResult::Continue);\n                }\n\n                tracing::debug!(\"Fsyncing database file before WAL truncation\");\n                let c = self\n                    .pager\n                    .db_file\n                    .sync(Completion::new_sync(|_| {}), self.pager.get_sync_type())?;\n                checkpoint_result.db_sync_sent = true;\n                Ok(TransitionResult::Io(IOCompletions::Single(c)))\n            }\n\n            CheckpointState::TruncateWal => {\n                // Truncate WAL file after DB file is safely synced.\n                // This must be done explicitly because MVCC calls wal.checkpoint() directly,\n                // bypassing the pager's TruncateWalFile phase.\n                let Some(wal) = &self.pager.wal else {\n                    panic!(\"No WAL to truncate\");\n                };\n                let checkpoint_result = self\n                    .checkpoint_result\n                    .as_mut()\n                    .expect(\"checkpoint_result should be set\");\n                match wal.truncate_wal(checkpoint_result, self.pager.get_sync_type())? {\n                    IOResult::Done(()) => {\n                        self.state = CheckpointState::Finalize;\n                        Ok(TransitionResult::Continue)\n                    }\n                    IOResult::IO(io) => Ok(TransitionResult::Io(io)),\n                }\n            }\n\n            CheckpointState::Finalize => {\n                tracing::debug!(\"Releasing blocking checkpoint lock\");\n                // Patch sqlite_schema in MV Store to contain positive rootpages instead of negative ones\n                // for tables and indexes that were flushed to the physical database\n                for (sqlite_schema_rowid, (_, row_version)) in self.created_btrees.drain() {\n                    let key = RowID {\n                        table_id: SQLITE_SCHEMA_MVCC_TABLE_ID,\n                        row_id: RowKey::Int(sqlite_schema_rowid),\n                    };\n                    let sqlite_schema_row = self\n                        .mvstore\n                        .rows\n                        .get(&key)\n                        .expect(\"sqlite_schema row not found\");\n                    let mut row_versions = sqlite_schema_row.value().write();\n                    // row_version is a clone of the original with only the root\n                    // page column patched, so it shares the same version id. We\n                    // must replace the original in-place rather than append,\n                    // otherwise the version chain ends up with two entries that\n                    // have identical (id, begin, end). A later DELETE only marks\n                    // one of them as ended (it returns after the first match),\n                    // leaving the other as a phantom current version that causes\n                    // spurious write-write conflicts at commit time.\n                    let vid = row_version.id;\n                    if let Some(existing) = row_versions.iter_mut().find(|rv| rv.id == vid) {\n                        *existing = row_version;\n                    } else {\n                        self.mvstore\n                            .insert_version_raw(&mut row_versions, row_version);\n                    }\n                }\n\n                // Patch in-memory schema to do the same\n                self.connection.db.with_schema_mut(|schema| {\n                    for table in schema.tables.values_mut() {\n                        let table = Arc::get_mut(table).expect(\"this should be the only reference\");\n                        let Some(btree_table) = table.btree_mut() else {\n                            continue;\n                        };\n                        let btree_table = Arc::make_mut(btree_table);\n                        if btree_table.root_page < 0 {\n                            let table_id = MVTableId::from(btree_table.root_page);\n                            let entry = self.mvstore.table_id_to_rootpage.get(&table_id).expect(\n                                \"we should have checkpointed table with table_id {table_id:?}\",\n                            );\n                            let value = entry\n                                .value()\n                                .expect(\"table with id {table_id:?} should have a mapping\");\n                            btree_table.root_page = value as i64;\n                        }\n                    }\n                    for table_index_list in schema.indexes.values_mut() {\n                        for index in table_index_list.iter_mut() {\n                            if index.root_page < 0 {\n                                let table_id = MVTableId::from(index.root_page);\n                                let entry = self\n                                    .mvstore\n                                    .table_id_to_rootpage\n                                    .get(&table_id)\n                                    .expect(\n                                    \"we should have checkpointed index with table_id {table_id:?}\",\n                                );\n                                let value = entry\n                                    .value()\n                                    .expect(\"index with id {table_id:?} should have a mapping\");\n                                let index = Arc::make_mut(index);\n                                index.root_page = value as i64;\n                            }\n                        }\n                    }\n\n                    schema.schema_version += 1;\n                    // Clear dropped root pages now that the checkpoint has completed.\n                    // The btree pages for dropped tables have been freed, so integrity_check\n                    // no longer needs to track them.\n                    schema.dropped_root_pages.clear();\n                    let _ = self.pager.io.block(|| {\n                        self.pager.with_header_mut(|header| {\n                            header.schema_cookie = schema.schema_version.into();\n                            self.mvstore.global_header.write().replace(*header);\n                            IOResult::Done(())\n                        })\n                    })?;\n                    Ok(())\n                })?;\n\n                self.mvstore\n                    .durable_txid_max\n                    .store(self.durable_txid_max_new, Ordering::SeqCst);\n                self.gc_checkpointed_versions();\n                self.mvstore.drop_unused_row_versions();\n                self.mvstore\n                    .storage\n                    .on_checkpoint_end(self.durable_txid_max_new);\n                self.checkpoint_lock.unlock();\n                self.finalize(&())?;\n                Ok(TransitionResult::Done(\n                    self.checkpoint_result.take().ok_or_else(|| {\n                        LimboError::InternalError(\"checkpoint_result not set\".to_string())\n                    })?,\n                ))\n            }\n        }\n    }\n}\n\nimpl<Clock: LogicalClock> StateTransition for CheckpointStateMachine<Clock> {\n    type Context = ();\n    type SMResult = CheckpointResult;\n\n    fn step(&mut self, _context: &Self::Context) -> Result<TransitionResult<Self::SMResult>> {\n        let res = self.step_inner(&());\n        match res {\n            Err(err) => {\n                tracing::debug!(\"Error in checkpoint state machine: {err}\");\n                self.cleanup_after_external_io_error();\n                Err(err)\n            }\n            Ok(result) => Ok(result),\n        }\n    }\n\n    fn finalize(&mut self, _context: &Self::Context) -> Result<()> {\n        Ok(())\n    }\n\n    fn is_finalized(&self) -> bool {\n        matches!(self.state, CheckpointState::Finalize)\n    }\n}\n"
  },
  {
    "path": "core/mvcc/database/hermitage_tests.rs",
    "content": "use crate::mvcc::database::tests::MvccTestDbNoConn;\nuse crate::Connection;\nuse crate::LimboError;\nuse crate::Value;\nuse std::sync::Arc;\n\n//        /\\\n//       /  \\\n//      /    \\\n//     /______\\\n//     | .  . |    Hermitage\n//     |  __  |    Transaction Isolation Tests\n//     |_|  |_|\n//\n// These tests are adapted from https://github.com/ept/hermitage\n// Turso MVCC implements snapshot isolation with eager write-write conflict detection:\n//\n//   - Snapshot is taken at BEGIN (not at first read like FoundationDB)\n//   - Write-write conflicts are detected immediately at write time (WriteWriteConflict),\n//     NOT deferred to commit (like FoundationDB)\n//   - Transactions never see uncommitted changes from other active transactions (no dirty reads)\n//   - Isolation level: snapshot isolation (prevents G0, G1a, G1b, G1c, OTV, PMP, P4, G-single)\n//   - Does NOT prevent G2-item (write skew) or G2 (anti-dependency cycles) — those require serializable\n//\n// Comparison with hermitage reference databases:\n//   FoundationDB (serializable): writes succeed locally, conflict checked at commit time.\n//   Turso: fails eagerly at write time (WriteWriteConflict), no blocking.\n//   FoundationDB also prevents G2-item and G2 (serializable) → Turso does not (snapshot isolation).\n//   Postgres behavior varies by isolation level — see individual test comments.\n//\n\ntrait FromValue {\n    fn from_value(v: &Value) -> Self;\n}\n\nimpl FromValue for i64 {\n    fn from_value(v: &Value) -> Self {\n        v.as_int().unwrap()\n    }\n}\n\nimpl FromValue for String {\n    fn from_value(v: &Value) -> Self {\n        v.to_text().unwrap().to_string()\n    }\n}\n\nfn setup_hermitage_test() -> MvccTestDbNoConn {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n\n    // Setup: create table test (id int primary key, value int);\n    // insert into test (id, value) values (1, 10), (2, 20);\n    conn.execute(\"CREATE TABLE test (id INTEGER PRIMARY KEY, value INT)\")\n        .unwrap();\n    conn.execute(\"INSERT INTO test (id, value) VALUES (1, 10), (2, 20)\")\n        .unwrap();\n\n    db\n}\n\nfn get_rows<T: FromValue>(conn: &Arc<Connection>, query: &str) -> Vec<Vec<T>> {\n    let mut stmt = conn.prepare(query).unwrap();\n    let mut rows = Vec::new();\n    stmt.run_with_row_callback(|row| {\n        let values = row.get_values().map(|v| T::from_value(v)).collect();\n        rows.push(values);\n        Ok(())\n    })\n    .unwrap();\n    rows\n}\n\nfn read_value(conn: &Arc<Connection>, id: i64) -> i64 {\n    let rows: Vec<Vec<i64>> = get_rows(conn, &format!(\"SELECT value FROM test WHERE id = {id}\"));\n    assert!(!rows.is_empty(), \"No row found with id = {id}\");\n    rows[0][0]\n}\n\n/// Assert that SELECT * FROM test returns exactly the expected (id, value) pairs.\nfn assert_rows(conn: &Arc<Connection>, expected: &[(i64, i64)]) {\n    let rows: Vec<Vec<i64>> = get_rows(conn, \"SELECT id, value FROM test ORDER BY id\");\n    let actual: Vec<(i64, i64)> = rows.iter().map(|r| (r[0], r[1])).collect();\n    assert_eq!(actual, expected);\n}\n\n/// Verify the final committed state of rows in the database.\n/// Takes a list of (row_key, expected_value) pairs.\nfn verify_final_state(db: &MvccTestDbNoConn, expected: &[(i64, i64)]) {\n    let conn = db.connect();\n    for &(key, expected_value) in expected {\n        assert_eq!(read_value(&conn, key), expected_value);\n    }\n}\n\n/// G0: Write Cycles — https://jepsen.io/consistency/phenomena/g0\n/// If two transactions try to update the same row, one should fail with a write-write conflict,\n/// preventing a cycle of uncommitted updates that could lead to non-serializable behavior.\n///\n/// Turso: T2's write to the same row as T1 fails immediately with WriteWriteConflict.\n/// Postgres read committed: T2 BLOCKS until T1 commits, then T2 overwrites T1. Both succeed.\n/// FoundationDB: T2 writes locally, T2's commit is rejected.\n#[test]\nfn test_hermitage_g0_write_cycles_prevented() {\n    let db = setup_hermitage_test();\n\n    // T1 and T2 try to update the same row - second should fail\n    let conn1 = db.connect();\n    let conn2 = db.connect();\n\n    conn1.execute(\"BEGIN CONCURRENT\").unwrap();\n    conn2.execute(\"BEGIN CONCURRENT\").unwrap();\n\n    // T1: update test set value = 11 where id = 1\n    conn1\n        .execute(\"UPDATE test SET value = 11 WHERE id = 1\")\n        .unwrap();\n\n    // T2: update test set value = 12 where id = 1 -- should fail with write-write conflict\n    let result = conn2.execute(\"UPDATE test SET value = 12 WHERE id = 1\");\n    assert!(matches!(result, Err(LimboError::WriteWriteConflict)));\n    // T2: rollback (write-write conflict auto-rolls back the transaction)\n\n    // T1: update test set value = 21 where id = 2\n    conn1\n        .execute(\"UPDATE test SET value = 21 WHERE id = 2\")\n        .unwrap();\n\n    // T1: commit\n    conn1.execute(\"COMMIT\").unwrap();\n\n    {\n        // select * from test -- Shows 1 => 11, 2 => 21\n        let conn_read = db.connect();\n        assert_rows(&conn_read, &[(1, 11), (2, 21)]);\n    }\n}\n\n/// G1a: Aborted Reads — https://jepsen.io/consistency/phenomena/g1a\n/// If a transaction T1 updates a row but then aborts, another transaction T2 should never see\n/// T1's uncommitted changes, even if T2 reads the same row before T1 aborts.\n///\n/// Since we don't allow reading uncommited data ever, we prevent g1a and also dirty update phenomena\n/// (mentioned in the jepsen page)\n///\n/// Turso: T2 never sees T1's uncommitted write\n/// Postgres read committed: T2 never sees T1's uncommitted write.\n/// FoundationDB: T2 never sees T1's uncommitted write.\n#[test]\nfn test_hermitage_g1a_aborted_reads() {\n    let db = setup_hermitage_test();\n\n    let conn1 = db.connect();\n    let conn2 = db.connect();\n\n    conn1.execute(\"BEGIN CONCURRENT\").unwrap();\n    conn2.execute(\"BEGIN CONCURRENT\").unwrap();\n\n    // T1: update test set value = 101 where id = 1\n    conn1\n        .execute(\"UPDATE test SET value = 101 WHERE id = 1\")\n        .unwrap();\n\n    // T2: select * from test -- Should still show 1 => 10 (original value)\n    assert_rows(&conn2, &[(1, 10), (2, 20)]); // Should see original values, not 101\n\n    // T1: abort/rollback\n    conn1.execute(\"ROLLBACK\").unwrap();\n\n    // T2: select * from test -- Should still show 1 => 10 (original value)\n    assert_rows(&conn2, &[(1, 10), (2, 20)]); // Still should see original values\n\n    // T2: commit\n    conn2.execute(\"COMMIT\").unwrap();\n\n    // Verify final state - should still have original values since T1 aborted\n    verify_final_state(&db, &[(1, 10), (2, 20)]);\n}\n\n/// G1b: Intermediate Reads — https://jepsen.io/consistency/phenomena/g1b\n/// If a transaction T1 writes a value, then overwrites it with a second value, another committed\n/// transaction T2 should never see T1's first (intermediate) write — only the final committed value\n/// or the original value from before T1 (depending on when T2's snapshot is taken).\n/// Under snapshot isolation, T2 sees the value from its snapshot, not T1's intermediate or final write.\n///\n/// G1b: Intermediate Update - this is not present in hermitage but Jepsen has it:\n///\n/// \"If writes are arbitrary transformations of values (rather than blind writes),\n/// this definition of G1b is insufficient. Imagine an aborted transaction Ti writes an\n/// intermediate version xi, and a committed transaction Tj writes xj = f(xi). Finally, a\n/// committed transaction Tk reads xj. Clearly, intermediate state has leaked from Ti into Tk.\"\n///\n/// Turso never allows reading uncommitted data, so Tj would never be able to read xi from Ti, and\n/// thus Tk would never see xj = f(xi) if Ti aborted. So we prevent this form of G1b as well.\n///\n/// Turso: T2 sees its snapshot value (10), not T1's intermediate (101) or final (11) write.\n/// Postgres read committed: T2 sees T1's committed final value (11) after T1 commits.\n/// FoundationDB: T2 sees its snapshot value (10).\n#[test]\nfn test_hermitage_g1b_intermediate_reads() {\n    let db = setup_hermitage_test();\n\n    let conn1 = db.connect();\n    let conn2 = db.connect();\n\n    conn1.execute(\"BEGIN CONCURRENT\").unwrap();\n    conn2.execute(\"BEGIN CONCURRENT\").unwrap();\n\n    // T1: update test set value = 101 where id = 1 (intermediate value)\n    conn1\n        .execute(\"UPDATE test SET value = 101 WHERE id = 1\")\n        .unwrap();\n\n    // T2: select * from test -- Should still show 1 => 10 (original value)\n    assert_rows(&conn2, &[(1, 10), (2, 20)]); // Should NOT see 101\n\n    // T1: update test set value = 11 where id = 1 (final value)\n    conn1\n        .execute(\"UPDATE test SET value = 11 WHERE id = 1\")\n        .unwrap();\n\n    // T1: commit\n    conn1.execute(\"COMMIT\").unwrap();\n\n    // T2: select * from test\n    // Turso (snapshot isolation): T2 still sees 10 from its snapshot.\n    // FoundationDB: same — T2 still sees 10.\n    // Postgres read committed: T2 sees 11 (T1's committed final value).\n    assert_rows(&conn2, &[(1, 10), (2, 20)]);\n\n    // T2: commit\n    conn2.execute(\"COMMIT\").unwrap();\n\n    // Verify final state - should show final committed value of 11\n    verify_final_state(&db, &[(1, 11)]);\n}\n\n/// G1c: Circular Information Flow — https://jepsen.io/consistency/phenomena/g1c\n/// If two transactions T1 and T2 both update different rows but then read each other's updated\n/// rows before either commits, they should not see each other's uncommitted changes,\n/// preventing a cycle of intermediate reads.\n///\n/// Turso / Postgres (Read Committed) / FoundationDB: Neither transaction sees the other's\n/// uncommitted write\n#[test]\nfn test_hermitage_g1c_circular_information_flow() {\n    let db = setup_hermitage_test();\n\n    let conn1 = db.connect();\n    let conn2 = db.connect();\n\n    conn1.execute(\"BEGIN CONCURRENT\").unwrap();\n    conn2.execute(\"BEGIN CONCURRENT\").unwrap();\n\n    // T1: update test set value = 11 where id = 1\n    conn1\n        .execute(\"UPDATE test SET value = 11 WHERE id = 1\")\n        .unwrap();\n\n    // T2: update test set value = 22 where id = 2\n    conn2\n        .execute(\"UPDATE test SET value = 22 WHERE id = 2\")\n        .unwrap();\n\n    // T1: select * from test where id = 2\n    // Should still show 2 => 20 (original value), NOT T2's uncommitted 22\n    assert_eq!(read_value(&conn1, 2), 20); // Should NOT see T2's update\n\n    // T2: select * from test where id = 1\n    // Should still show 1 => 10 (original value), NOT T1's uncommitted 11\n    assert_eq!(read_value(&conn2, 1), 10); // Should NOT see T1's update\n\n    // T1: commit\n    conn1.execute(\"COMMIT\").unwrap();\n\n    // T2: commit\n    conn2.execute(\"COMMIT\").unwrap();\n\n    // Verify final state - both updates should be committed\n    verify_final_state(&db, &[(1, 11), (2, 22)]);\n}\n\n/// OTV: Observed Transaction Vanishes\n/// if a transaction T1 updates a row but has not yet committed, and another transaction T2 tries\n/// to update the same row and fails with a write-write conflict, then if a third transaction\n/// T3 tries to read that row, it should not see T1's uncommitted changes \"vanish\" due to T2's\n/// failed update. Instead, T3 should see the original value (since T1 is not committed yet)\n///\n/// IOW once a transaction's effects become visible to another transaction, they must not \"vanish\".\n///\n/// Turso: T2's write fails immediately with WriteWriteConflict. T3 sees original values\n/// Postgres read committed: T2 BLOCKS until T1 commits, then succeeds. T3 sees committed values change.\n/// FoundationDB: T2 writes locally, snapshot at first read, T2's commit is rejected.\n#[test]\nfn test_hermitage_otv_observed_transaction_vanishes() {\n    // T3 should not see T1's changes \"vanish\"\n    let db = setup_hermitage_test();\n\n    let conn1 = db.connect();\n    let conn2 = db.connect();\n    let conn3 = db.connect();\n\n    conn1.execute(\"BEGIN CONCURRENT\").unwrap();\n    conn2.execute(\"BEGIN CONCURRENT\").unwrap();\n    conn3.execute(\"BEGIN CONCURRENT\").unwrap();\n\n    // T1: update test set value = 11 where id = 1\n    conn1\n        .execute(\"UPDATE test SET value = 11 WHERE id = 1\")\n        .unwrap();\n\n    // T1: update test set value = 19 where id = 2\n    conn1\n        .execute(\"UPDATE test SET value = 19 WHERE id = 2\")\n        .unwrap();\n\n    // T2: update test set value = 12 where id = 1\n    // (following blocks in Postgres)\n    let result = conn2.execute(\"UPDATE test SET value = 12 WHERE id = 1\");\n    assert!(matches!(result, Err(LimboError::WriteWriteConflict)));\n    // T2: rollback (write-write conflict auto-rolls back the transaction)\n\n    // T1: commit\n    conn1.execute(\"COMMIT\").unwrap();\n\n    // T3: select * from test where id = 1\n    // With snapshot isolation, T3 sees the original value (10)\n    // (with read committed, like in postgres, T3 would see T1's committed value (11))\n    assert_eq!(read_value(&conn3, 1), 10); // Snapshot isolation\n\n    // Start a new T2 transaction after T1 commits\n    let conn2_new = db.connect();\n    conn2_new.execute(\"BEGIN CONCURRENT\").unwrap();\n\n    // T2_new: update test set value = 12 where id = 1 (now succeeds)\n    conn2_new\n        .execute(\"UPDATE test SET value = 12 WHERE id = 1\")\n        .unwrap();\n\n    // T2_new: update test set value = 18 where id = 2\n    conn2_new\n        .execute(\"UPDATE test SET value = 18 WHERE id = 2\")\n        .unwrap();\n\n    // T3: select * from test where id = 2\n    // Should still see original value with snapshot isolation\n    assert_eq!(read_value(&conn3, 2), 20); // Snapshot isolation\n\n    // T2_new: commit\n    conn2_new.execute(\"COMMIT\").unwrap();\n\n    // T3: select * from test where id = 2\n    // Still sees snapshot from T3's start\n    assert_eq!(read_value(&conn3, 2), 20); // Consistent snapshot\n\n    // T3: select * from test where id = 1\n    // Still sees snapshot from T3's start\n    assert_eq!(read_value(&conn3, 1), 10); // Consistent snapshot\n\n    // T3: commit\n    conn3.execute(\"COMMIT\").unwrap();\n\n    // Verify final state - T2_new's values are committed\n    verify_final_state(&db, &[(1, 12), (2, 18)]);\n}\n\n/// PMP: Predicate-Many-Preceders (read predicates) — https://jepsen.io/consistency/phenomena/p3\n/// If a transaction T1 reads rows matching a predicate, and another transaction T2 inserts a new\n/// row that matches T1's predicate and commits, then if T1 tries to read again with the same\n/// predicate, it should not see the new row (phantom prevention).\n///\n/// Turso: T1 does not see T2's inserted row (snapshot isolation prevents phantoms).\n/// Postgres repeatable read: T1 does not see T2's inserted row.\n/// FoundationDB: T1 does not see T2's inserted row.\n#[test]\nfn test_hermitage_pmp_predicate_many_preceders_read() {\n    // T1 should not see T2's inserted row that matches T1's predicate\n    let db = setup_hermitage_test();\n\n    let conn1 = db.connect();\n    let conn2 = db.connect();\n\n    conn1.execute(\"BEGIN CONCURRENT\").unwrap();\n    conn2.execute(\"BEGIN CONCURRENT\").unwrap();\n\n    // T1: select * from test where value = 30 -- Returns nothing\n    let rows: Vec<Vec<i64>> = get_rows(&conn1, \"SELECT * FROM test WHERE value = 30\");\n    assert!(rows.is_empty()); // Should find nothing\n\n    // T2: insert into test (id, value) values(3, 30)\n    conn2\n        .execute(\"INSERT INTO test (id, value) VALUES (3, 30)\")\n        .unwrap();\n\n    // T2: commit\n    conn2.execute(\"COMMIT\").unwrap();\n\n    // T1: select * from test where value % 3 = 0\n    // This would match both value=30 (newly inserted) and any existing divisible by 3\n    // With repeatable read/snapshot isolation, T1 should not see the new row\n    let rows: Vec<Vec<i64>> = get_rows(&conn1, \"SELECT * FROM test WHERE value % 3 = 0\");\n    assert!(rows.is_empty()); // Should still find nothing (phantom prevention)\n\n    // T1: commit\n    conn1.execute(\"COMMIT\").unwrap();\n\n    // Verify final state - the new row exists\n    verify_final_state(&db, &[(3, 30)]);\n}\n\n/// PMP: Predicate-Many-Preceders (write predicates) — https://jepsen.io/consistency/phenomena/p3\n/// If a transaction T1 updates rows matching a predicate, and another transaction T2 tries to\n/// delete rows matching the same predicate, then T2 should fail with a write-write conflict,\n/// even if T1 has not committed yet. This prevents lost updates and ensures that T2\n/// does not delete rows that T1 is in the process of updating.\n///\n/// Turso: T2's delete fails immediately with WriteWriteConflict (T1 has uncommitted write on row 2).\n/// Postgres repeatable read: T2's delete BLOCKS until T1 commits, then fails with serialization error.\n/// FoundationDB: T2's delete succeeds locally, T2's commit is rejected.\n#[test]\nfn test_hermitage_pmp_predicate_many_preceders_write() {\n    // T2's delete based on predicate conflicts with T1's update\n    let db = setup_hermitage_test();\n\n    let conn1 = db.connect();\n    let conn2 = db.connect();\n\n    conn1.execute(\"BEGIN CONCURRENT\").unwrap();\n    conn2.execute(\"BEGIN CONCURRENT\").unwrap();\n\n    // T1: update test set value = value + 10\n    // Updates: 1 => 20, 2 => 30\n    conn1.execute(\"UPDATE test SET value = value + 10\").unwrap();\n\n    // T2: select * from test where value = 20\n    // T2 should see the original values (snapshot isolation)\n    assert_eq!(read_value(&conn2, 2), 20); // Original value\n\n    // T2: delete from test where value = 20\n    // T2 tries to delete row 2 (which originally had value 20)\n    let delete_result = conn2.execute(\"DELETE FROM test WHERE value = 20\");\n    assert!(matches!(delete_result, Err(LimboError::WriteWriteConflict)));\n\n    // T1: commit\n    conn1.execute(\"COMMIT\").unwrap();\n\n    // T2: rollback (write-write conflict auto-rolls back the transaction)\n\n    // Verify final state - T1's updates are committed\n    verify_final_state(&db, &[(1, 20), (2, 30)]);\n}\n\n/// P4: Lost Update — https://jepsen.io/consistency/phenomena/p4\n/// If two transactions T1 and T2 both read the same row and then both try to update it, one of them\n/// should fail with a write-write conflict, preventing the \"lost update\" scenario where one\n/// transaction's update overwrites the other's without either being aware of the conflict.\n///\n/// Turso: T2's update fails immediately with WriteWriteConflict (T1 has uncommitted write).\n/// Postgres repeatable read: T2's update BLOCKS until T1 commits, then fails with serialization error.\n/// FoundationDB: T2's update succeeds locally, T2's commit is rejected.\n#[test]\nfn test_hermitage_p4_lost_update() {\n    // T2's update should not be lost when T1 also updates\n    let db = setup_hermitage_test();\n\n    let conn1 = db.connect();\n    let conn2 = db.connect();\n\n    conn1.execute(\"BEGIN CONCURRENT\").unwrap();\n    conn2.execute(\"BEGIN CONCURRENT\").unwrap();\n\n    // T1: select * from test where id = 1\n    assert_eq!(read_value(&conn1, 1), 10);\n\n    // T2: select * from test where id = 1\n    assert_eq!(read_value(&conn2, 1), 10);\n\n    // T1: update test set value = 11 where id = 1\n    conn1\n        .execute(\"UPDATE test SET value = 11 WHERE id = 1\")\n        .unwrap();\n\n    // T2: update test set value = 11 where id = 1\n    let result = conn2.execute(\"UPDATE test SET value = 11 WHERE id = 1\");\n    assert!(matches!(result, Err(LimboError::WriteWriteConflict)));\n\n    // T1: commit\n    conn1.execute(\"COMMIT\").unwrap();\n\n    // T2: abort (write-write conflict auto-rolls back the transaction)\n\n    // Verify final state - only T1's update is committed\n    verify_final_state(&db, &[(1, 11)]);\n}\n\n/// G-single: Read Skew — https://jepsen.io/consistency/phenomena/g-single\n/// If a transaction T1 reads a row and then another transaction T2 updates that row and commits,\n/// then if T1 tries to read the same row again, it should still see the original value,\n/// not an inconsistent state where T1 sees the updated value for some rows but not others.\n///\n/// Turso / Postgres (repeatable read) / FoundationDB: T1 sees original values throughout (snapshot isolation).\n#[test]\nfn test_hermitage_g_single_read_skew() {\n    // T1 should see a consistent snapshot\n    let db = setup_hermitage_test();\n\n    let conn1 = db.connect();\n    let conn2 = db.connect();\n\n    conn1.execute(\"BEGIN CONCURRENT\").unwrap();\n    conn2.execute(\"BEGIN CONCURRENT\").unwrap();\n\n    // T1: select * from test where id = 1 -- Shows 1 => 10\n    assert_eq!(read_value(&conn1, 1), 10);\n\n    // T2: select * from test\n    assert_rows(&conn2, &[(1, 10), (2, 20)]);\n\n    // T2: update test set value = 12 where id = 1\n    conn2\n        .execute(\"UPDATE test SET value = 12 WHERE id = 1\")\n        .unwrap();\n\n    // T2: update test set value = 18 where id = 2\n    conn2\n        .execute(\"UPDATE test SET value = 18 WHERE id = 2\")\n        .unwrap();\n\n    // T2: commit\n    conn2.execute(\"COMMIT\").unwrap();\n\n    // T1: select * from test\n    // With snapshot isolation, T1 should still see the original values\n    // This prevents read skew - T1 sees a consistent snapshot\n    assert_rows(&conn1, &[(1, 10), (2, 20)]); // Original values, not 12/18\n\n    // T1: commit\n    conn1.execute(\"COMMIT\").unwrap();\n\n    // Verify final state - T2's updates are committed\n    verify_final_state(&db, &[(1, 12), (2, 18)]);\n}\n\n/// G-single: Read Skew (predicate dependencies) — https://jepsen.io/consistency/phenomena/g-single\n/// If a transaction T1 reads rows matching a predicate, and another transaction T2 updates those rows\n/// and commits, then if T1 tries to read again with the same predicate, it should\n/// still see the original values that matched the predicate, not the updated values,\n/// preventing a skew where T1 sees some updated values but not others.\n///\n/// Turso / Postgres (repeatable read) / FoundationDB: T1 sees original values from its snapshot (snapshot isolation).\n#[test]\nfn test_hermitage_g_single_read_skew_predicate_dependencies() {\n    // T1's snapshot should not see T2's predicate-changing update\n    let db = setup_hermitage_test();\n\n    let conn1 = db.connect();\n    let conn2 = db.connect();\n\n    conn1.execute(\"BEGIN CONCURRENT\").unwrap();\n    conn2.execute(\"BEGIN CONCURRENT\").unwrap();\n\n    // T1: select * from test where value % 5 = 0\n    // Both 10 and 20 are divisible by 5, so returns both rows\n    let rows: Vec<Vec<i64>> = get_rows(\n        &conn1,\n        \"SELECT value FROM test WHERE value % 5 = 0 ORDER BY value\",\n    );\n    assert_eq!(rows.len(), 2);\n    assert_eq!(rows[0][0], 10);\n    assert_eq!(rows[1][0], 20);\n\n    // T2: update test set value = 12 where value = 10\n    conn2\n        .execute(\"UPDATE test SET value = 12 WHERE value = 10\")\n        .unwrap();\n\n    // T2: commit\n    conn2.execute(\"COMMIT\").unwrap();\n\n    // T1: select * from test where value % 3 = 0\n    // With snapshot isolation, T1 still sees (1, 10) and (2, 20) from its snapshot.\n    // Neither 10 nor 20 is divisible by 3, so this should return nothing.\n    // (The newly committed value 12 IS divisible by 3, but T1 must not see it.)\n    let rows: Vec<Vec<i64>> = get_rows(&conn1, \"SELECT * FROM test WHERE value % 3 = 0\");\n    assert!(rows.is_empty()); // Should find nothing (snapshot isolation)\n\n    // T1: commit\n    conn1.execute(\"COMMIT\").unwrap();\n\n    // Verify final state - T2's update is visible\n    verify_final_state(&db, &[(1, 12)]);\n}\n\n/// G-single: Read Skew (write predicate) — https://jepsen.io/consistency/phenomena/g-single\n/// If a transaction T1 reads rows matching a predicate, and another transaction T2 updates those rows\n/// and commits, then if T1 tries to update rows matching the same predicate, it should fail with\n/// a write-write conflict. This prevents T1 from updating rows based on a stale snapshot that has\n/// been changed by T2, ensuring that T1 does not overwrite T2's changes without being aware of the conflict.\n///\n/// Turso: T1's delete fails immediately with WriteWriteConflict (T2 already committed a write to row 2).\n/// Postgres repeatable read: T1's delete fails with serialization error at commit time (T1 blocks until T2 commits, then fails).\n/// FoundationDB: T1's delete succeeds locally, T1's commit is rejected.\n#[test]\nfn test_hermitage_g_single_read_skew_write_predicate() {\n    // T1's delete based on predicate should conflict\n    let db = setup_hermitage_test();\n\n    let conn1 = db.connect();\n    let conn2 = db.connect();\n\n    conn1.execute(\"BEGIN CONCURRENT\").unwrap();\n    conn2.execute(\"BEGIN CONCURRENT\").unwrap();\n\n    // T1: select * from test where id = 1 -- Shows 1 => 10\n    assert_eq!(read_value(&conn1, 1), 10);\n\n    // T2: select * from test\n    assert_rows(&conn2, &[(1, 10), (2, 20)]);\n\n    // T2: update test set value = 12 where id = 1\n    conn2\n        .execute(\"UPDATE test SET value = 12 WHERE id = 1\")\n        .unwrap();\n\n    // T2: update test set value = 18 where id = 2\n    conn2\n        .execute(\"UPDATE test SET value = 18 WHERE id = 2\")\n        .unwrap();\n\n    // T2: commit\n    conn2.execute(\"COMMIT\").unwrap();\n\n    // T1: delete from test where value = 20\n    // T1 sees row 2 with value 20 in its snapshot and tries to delete it\n    // But T2 has already updated this row, causing a write-write conflict\n    let delete_result = conn1.execute(\"DELETE FROM test WHERE value = 20\");\n    assert!(matches!(delete_result, Err(LimboError::WriteWriteConflict)));\n\n    // T1: abort (write-write conflict auto-rolls back the transaction)\n\n    // Verify final state - T2's updates are preserved\n    verify_final_state(&db, &[(1, 12), (2, 18)]);\n}\n\n/// G-single: Read Skew (write interleaved) — https://jepsen.io/consistency/phenomena/g-single\n/// Based on FoundationDB's g-single-write-2 test scenario. If a transaction T1 reads rows matching\n/// a predicate, and another transaction T2 updates those rows and commits, then if T1 tries to\n/// update rows matching the same predicate, it should fail with a write-write conflict.\n/// If T1 then rolls back, it should not affect T2's committed changes, and\n/// the final state should reflect T2's updates without any inconsistencies.\n///\n/// Turso: T2's update fails immediately with WriteWriteConflict (T1 has uncommitted delete).\n///        Both roll back. Final state: original values (10, 20).\n/// Postgres repeatable read: T2's update BLOCKS until T1 commits, then fails with serialization error.\n/// FoundationDB: T2's update succeeds locally, T1 rolls back, T2 commits.\n///        Final state: T2's values (12, 18) — conflicts are checked at commit time.\n#[test]\nfn test_hermitage_g_single_read_skew_write_interleaved() {\n    // Interleaved operations with rollback\n    let db = setup_hermitage_test();\n\n    let conn1 = db.connect();\n    let conn2 = db.connect();\n\n    conn1.execute(\"BEGIN CONCURRENT\").unwrap();\n    conn2.execute(\"BEGIN CONCURRENT\").unwrap();\n\n    // T1: select * from test where id = 1 -- Shows 1 => 10\n    assert_eq!(read_value(&conn1, 1), 10);\n\n    // T2: select * from test\n    assert_rows(&conn2, &[(1, 10), (2, 20)]);\n\n    // T2: update test set value = 12 where id = 1\n    conn2\n        .execute(\"UPDATE test SET value = 12 WHERE id = 1\")\n        .unwrap();\n\n    // T1: delete from test where value = 20 -- Tries to delete row 2\n    conn1.execute(\"DELETE FROM test WHERE value = 20\").unwrap(); // Should succeed since T2 hasn't updated row 2 yet\n\n    // T2: update test set value = 18 where id = 2\n    // This should fail with write-write conflict since T1 already deleted row 2\n    let update_result = conn2.execute(\"UPDATE test SET value = 18 WHERE id = 2\");\n    assert!(matches!(update_result, Err(LimboError::WriteWriteConflict)));\n\n    // T1: rollback\n    conn1.execute(\"ROLLBACK\").unwrap();\n\n    // T2: rollback (write-write conflict auto-rolls back the transaction)\n\n    // Verify final state - everything remains unchanged\n    verify_final_state(&db, &[(1, 10), (2, 20)]);\n}\n\n/// G2-item: Write Skew — https://jepsen.io/consistency/phenomena/g2-item\n/// If two transactions T1 and T2 both read the same rows and then both try to update different\n/// rows based on that same snapshot, both should be able to commit successfully,\n/// even though this can lead to an inconsistent state (write skew).\n/// This is ALLOWED under snapshot isolation but would not be allowed under serializable.\n///\n/// Turso / Postgres (repeatable read): Both commit successfully — no write-write conflict\n///     (different rows). Write skew ALLOWED.\n/// FoundationDB: T2's commit is REJECTED — serializable prevents write skew via read-conflict tracking.\n#[test]\nfn test_hermitage_g2_item_write_skew() {\n    // This test explicitly verifies that write skew DOES occur with snapshot isolation\n    // This is the expected behavior unless serializable isolation is implemented\n    let db = setup_hermitage_test();\n\n    let conn1 = db.connect();\n    let conn2 = db.connect();\n\n    conn1.execute(\"BEGIN CONCURRENT\").unwrap();\n    conn2.execute(\"BEGIN CONCURRENT\").unwrap();\n\n    // Both transactions read both rows\n    assert_rows(&conn1, &[(1, 10), (2, 20)]);\n    assert_rows(&conn2, &[(1, 10), (2, 20)]);\n\n    // Each updates a different row (no write-write conflict)\n    conn1\n        .execute(\"UPDATE test SET value = 11 WHERE id = 1\")\n        .unwrap();\n    conn2\n        .execute(\"UPDATE test SET value = 21 WHERE id = 2\")\n        .unwrap();\n\n    // Both should successfully commit with snapshot isolation\n    // (This demonstrates write skew is allowed)\n    conn1.execute(\"COMMIT\").unwrap();\n    conn2.execute(\"COMMIT\").unwrap();\n\n    // Verify both updates succeeded (write skew occurred)\n    verify_final_state(&db, &[(1, 11), (2, 21)]);\n}\n\n/// G2: Anti-Dependency Cycles (Fekete et al's two-edge example) — https://jepsen.io/consistency/phenomena/g2\n/// If three transactions T1, T2, and T3 all read the same rows, and then T2 updates one of those\n/// rows and commits, and then T3 reads (seeing T2's update) and commits, then if T1 tries to update\n/// a different row based on its original snapshot, it should succeed under snapshot isolation (since there\n/// are no write-write conflicts), even though this creates a dependency cycle\n/// (T1 --rw--> T2 --wr--> T3 --rw--> T1) that would not be allowed under serializable.\n///\n/// T1 reads both rows, T2 updates row 2 and commits, T3 reads (sees T2's update) and commits,\n/// then T1 updates row 1.\n/// Turso: T1's update succeeds and commits — no write-write conflict (disjoint rows). G2 ALLOWED.\n/// Postgres repeatable read: T1's update succeeds. G2 ALLOWED.\n/// Postgres serializable: T1's update fails.\n/// FoundationDB: T1's commit is REJECTED (serializable, detects read-write conflict on row 2).\n#[test]\nfn test_hermitage_g2_two_edges_fekete() {\n    // G2-two-edges: Fekete et al's example with two anti-dependency edges\n    let db = setup_hermitage_test();\n\n    let conn1 = db.connect();\n    let conn2 = db.connect();\n    let conn3 = db.connect();\n\n    // T1: begin (snapshot isolation)\n    conn1.execute(\"BEGIN CONCURRENT\").unwrap();\n\n    // T1: select * from test -- Shows 1 => 10, 2 => 20\n    // T1 reads row 2 (sees 20) — forms edge T1 --rw--> T2 (T2 will write row 2 later)\n    assert_rows(&conn1, &[(1, 10), (2, 20)]);\n\n    // T2: begin\n    conn2.execute(\"BEGIN CONCURRENT\").unwrap();\n\n    // T2: update test set value = value + 5 where id = 2\n    // T2 writes row 2 — completes edge T1 --rw--> T2\n    conn2\n        .execute(\"UPDATE test SET value = value + 5 WHERE id = 2\")\n        .unwrap();\n\n    // T2: commit\n    conn2.execute(\"COMMIT\").unwrap();\n\n    // T3: begin (after T2 committed)\n    conn3.execute(\"BEGIN CONCURRENT\").unwrap();\n\n    // T3: select * from test -- sees T2's committed changes\n    // T3 reads row 2 (sees 25, T2's write) — forms edge T2 --wr--> T3\n    // T3 reads row 1 (sees 10) — forms edge T3 --rw--> T1 (T1 will write row 1 later)\n    assert_rows(&conn3, &[(1, 10), (2, 25)]); // T2's update to row 2 visible\n\n    // T3: commit\n    conn3.execute(\"COMMIT\").unwrap();\n\n    // T1: update test set value = 0 where id = 1\n    // T1 writes row 1 — completes edge T3 --rw--> T1\n    // Cycle formed: T1 --rw--> T2 --wr--> T3 --rw--> T1\n    // Snapshot isolation: succeeds (no write-write conflict, T1 writes row 1, T2 wrote row 2)\n    // Serializable: would fail (cycle not allowed)\n    conn1\n        .execute(\"UPDATE test SET value = 0 WHERE id = 1\")\n        .unwrap();\n\n    // T1: commit — succeeds under snapshot isolation\n    conn1.execute(\"COMMIT\").unwrap();\n\n    // Verify final state - T1's update to row 1, T2's update to row 2\n    verify_final_state(&db, &[(1, 0), (2, 25)]);\n}\n\n/// G2: Anti-Dependency Cycles — https://jepsen.io/consistency/phenomena/g2\n/// If two transactions T1 and T2 both read using the same predicate and find no matching rows,\n/// and then each inserts a new row that would match the other's predicate,\n/// both should be able to commit successfully under snapshot isolation (no write-write conflict\n/// since they insert different rows), even though this creates a dependency cycle\n/// (T1 --rw--> T2 --rw--> T1) that would not be allowed under serializable isolation.\n///\n/// Both T1 and T2 read using a predicate (value % 3 = 0) and find no matching rows. Then each\n/// inserts a new row that WOULD match the other's predicate (T1 inserts 30, T2 inserts 42).\n/// Turso: Both commit successfully — inserts to different row IDs have no write-write conflict. G2 ALLOWED.\n/// Postgres repeatable read: Both commit successfully — same behavior (snapshot isolation allows G2).\n/// FoundationDB: T2's commit is REJECTED (serializable, detects predicate read-write conflict).\n#[test]\nfn test_hermitage_g2_anti_dependency_cycles() {\n    // This test verifies that G2 anomalies DO occur with snapshot isolation\n    let db = setup_hermitage_test();\n\n    let conn1 = db.connect();\n    let conn2 = db.connect();\n\n    conn1.execute(\"BEGIN CONCURRENT\").unwrap();\n    conn2.execute(\"BEGIN CONCURRENT\").unwrap();\n\n    // select * from test where value % 3 = 0; -- T1. Returns nothing\n    // select * from test where value % 3 = 0; -- T2. Returns nothing\n    // T1's read misses T2's future insert — forms edge T1 --rw--> T2\n    // T2's read misses T1's future insert — forms edge T2 --rw--> T1\n    let rows: Vec<Vec<i64>> = get_rows(&conn1, \"SELECT * FROM test WHERE value % 3 = 0\");\n    assert!(rows.is_empty());\n    let rows: Vec<Vec<i64>> = get_rows(&conn2, \"SELECT * FROM test WHERE value % 3 = 0\");\n    assert!(rows.is_empty());\n\n    // T1 inserts row 3 (value 30, divisible by 3) — completes edge T2 --rw--> T1\n    // T2 inserts row 4 (value 42, divisible by 3) — completes edge T1 --rw--> T2\n    // Cycle formed: T1 --rw--> T2 --rw--> T1\n    // Snapshot isolation: succeeds (different row IDs, no write-write conflict)\n    // Serializable: would fail (cycle not allowed)\n    conn1\n        .execute(\"INSERT INTO test (id, value) VALUES (3, 30)\")\n        .unwrap();\n    conn2\n        .execute(\"INSERT INTO test (id, value) VALUES (4, 42)\")\n        .unwrap();\n\n    conn1.execute(\"COMMIT\").unwrap();\n    conn2.execute(\"COMMIT\").unwrap();\n\n    // Verify both inserts succeeded — cycle occurred\n    verify_final_state(&db, &[(3, 30), (4, 42)]);\n}\n\n/// Aborted Transaction Not Visible (simplified G1a variant)\n/// A transaction that updates a row but then aborts does not make its changes visible\n/// to other transactions. Unlike the G1a test where T2 reads before T1 aborts,\n/// here T2 reads after T1 has already rolled back.\n#[test]\nfn test_hermitage_aborted_transaction_not_visible() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n    conn.execute(\"CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)\")\n        .unwrap();\n\n    // T1 inserts and commits\n    conn.execute(\"INSERT INTO test (id, value) VALUES (1, 'committed_data')\")\n        .unwrap();\n\n    // T2 updates but aborts\n    let conn2 = db.connect();\n    conn2.execute(\"BEGIN CONCURRENT\").unwrap();\n    conn2\n        .execute(\"UPDATE test SET value = 'aborted_data' WHERE id = 1\")\n        .unwrap();\n    conn2.execute(\"ROLLBACK\").unwrap();\n\n    // T3 should see original committed data, not aborted changes\n    let conn3 = db.connect();\n    let rows: Vec<Vec<String>> = get_rows(&conn3, \"SELECT value FROM test WHERE id = 1\");\n    // should see T1's data, not T2's\n    assert_eq!(rows[0][0], \"committed_data\");\n}\n\n/// Write-Write Conflict (simplified G0 variant)\n/// If two transactions try to update the same row, the second one should fail with a\n/// write-write conflict, even if the first transaction has not committed yet.\n#[test]\nfn test_hermitage_write_write_conflict() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n    conn.execute(\"CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)\")\n        .unwrap();\n\n    // setup initial data x=1\n    conn.execute(\"INSERT INTO test (id, value) VALUES (1, 'x=1')\")\n        .unwrap();\n\n    // conn1: start transaction and update x=2 (but don't commit yet)\n    let conn1 = db.connect();\n    conn1.execute(\"BEGIN CONCURRENT\").unwrap();\n    conn1\n        .execute(\"UPDATE test SET value = 'x=2' WHERE id = 1\")\n        .unwrap();\n\n    // conn2: start transaction and try to update x=3 - should fail with write-write conflict\n    let conn2 = db.connect();\n    conn2.execute(\"BEGIN CONCURRENT\").unwrap();\n    let update_result = conn2.execute(\"UPDATE test SET value = 'x=3' WHERE id = 1\");\n    assert!(matches!(update_result, Err(LimboError::WriteWriteConflict)));\n\n    // conn1 should be able to commit successfully since it was first\n    conn1.execute(\"COMMIT\").unwrap();\n\n    // verify final state - should see conn1's update (x=2)\n    let conn3 = db.connect();\n    let rows: Vec<Vec<String>> = get_rows(&conn3, \"SELECT value FROM test WHERE id = 1\");\n    assert_eq!(rows[0][0], \"x=2\");\n}\n"
  },
  {
    "path": "core/mvcc/database/mod.rs",
    "content": "use crate::mvcc::clock::LogicalClock;\nuse crate::mvcc::cursor::{static_iterator_hack, MvccIterator};\nuse crate::schema::{Schema, Table};\nuse crate::state_machine::StateMachine;\nuse crate::state_machine::StateTransition;\nuse crate::state_machine::TransitionResult;\nuse crate::storage::btree::BTreeCursor;\nuse crate::storage::btree::BTreeKey;\nuse crate::storage::btree::CursorTrait;\nuse crate::storage::btree::CursorValidState;\nuse crate::storage::pager::SavepointResult;\nuse crate::storage::sqlite3_ondisk::DatabaseHeader;\nuse crate::storage::wal::{CheckpointMode, CheckpointResult, TursoRwLock};\nuse crate::sync::atomic::{AtomicBool, AtomicI64};\nuse crate::sync::atomic::{AtomicU64, Ordering};\nuse crate::sync::Arc;\nuse crate::sync::{Mutex, RwLock};\nuse crate::translate::plan::IterationDirection;\nuse crate::types::compare_immutable;\nuse crate::types::IOCompletions;\nuse crate::types::IOResult;\nuse crate::types::ImmutableRecord;\nuse crate::types::IndexInfo;\nuse crate::types::SeekResult;\nuse crate::File;\nuse crate::IOExt;\nuse crate::LimboError;\nuse crate::Result;\nuse crate::ValueRef;\nuse crate::{\n    contains_ignore_ascii_case, eq_ignore_ascii_case, match_ignore_ascii_case, Completion,\n};\nuse crate::{\n    turso_assert, turso_assert_eq, turso_assert_less_than, turso_assert_reachable, Numeric,\n};\nuse crate::{Connection, Pager, SyncMode};\nuse crossbeam_skiplist::map::Entry;\nuse crossbeam_skiplist::{SkipMap, SkipSet};\nuse rustc_hash::FxHashMap as HashMap;\nuse rustc_hash::FxHashSet as HashSet;\nuse std::collections::BTreeSet;\nuse std::fmt::Debug;\nuse std::marker::PhantomData;\nuse std::ops::Bound;\nuse tracing::instrument;\nuse tracing::Level;\n\npub mod checkpoint_state_machine;\npub use checkpoint_state_machine::{CheckpointState, CheckpointStateMachine};\n\nuse super::persistent_storage::logical_log::{\n    HeaderReadResult, StreamingLogicalLogReader, StreamingResult, LOG_HDR_SIZE,\n};\n\n#[cfg(test)]\npub mod hermitage_tests;\n#[cfg(test)]\npub mod tests;\n\n/// Sentinel value for `MvStore::exclusive_tx` indicating no exclusive transaction is active.\nconst NO_EXCLUSIVE_TX: u64 = 0;\n\n/// A table ID for MVCC.\n/// MVCC table IDs are always negative. Their corresponding rootpage entry in sqlite_schema\n/// is the same negative value if the table has not been checkpointed yet. Otherwise, the root page\n/// will be positive and corresponds to the actual physical page.\n#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]\n#[repr(transparent)]\npub struct MVTableId(i64);\n\nimpl MVTableId {\n    pub fn new(value: i64) -> Self {\n        turso_assert_less_than!(value, 0, \"MVCC table IDs are always negative\");\n        Self(value)\n    }\n}\n\nimpl From<i64> for MVTableId {\n    fn from(value: i64) -> Self {\n        turso_assert_less_than!(value, 0, \"MVCC table IDs are always negative\");\n        Self(value)\n    }\n}\n\nimpl From<MVTableId> for i64 {\n    fn from(value: MVTableId) -> Self {\n        value.0\n    }\n}\n\nimpl std::fmt::Display for MVTableId {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"MVTableId({})\", self.0)\n    }\n}\n\n/// Wrapper for index keys that implements collation-aware, ASC/DESC-aware ordering.\n#[derive(Debug, Clone)]\npub struct SortableIndexKey {\n    /// The key as bytes.\n    pub key: ImmutableRecord,\n    /// Index metadata containing sort orders and collations\n    pub metadata: Arc<IndexInfo>,\n}\n\nimpl SortableIndexKey {\n    pub fn new_from_bytes(key_bytes: Vec<u8>, metadata: Arc<IndexInfo>) -> Self {\n        Self {\n            key: ImmutableRecord::from_bin_record(key_bytes),\n            metadata,\n        }\n    }\n\n    pub fn new_from_record(key: ImmutableRecord, metadata: Arc<IndexInfo>) -> Self {\n        Self { key, metadata }\n    }\n\n    pub fn new_from_values(values: Vec<ValueRef>, metadata: Arc<IndexInfo>) -> Self {\n        let len = values.len();\n        Self {\n            key: ImmutableRecord::from_values(values, len),\n            metadata,\n        }\n    }\n\n    fn compare(&self, other: &Self) -> Result<std::cmp::Ordering> {\n        // We sometimes need to compare a shorter key to a longer one,\n        // for example when seeking with an index key that is a prefix of the full key.\n        let num_cols = self.metadata.num_cols.min(other.metadata.num_cols);\n\n        let mut lhs = self.key.iter()?;\n        let mut rhs = other.key.iter()?;\n\n        for i in 0..num_cols {\n            let lhs_value = lhs.next().expect(\"we already checked length\")?;\n            let rhs_value = rhs.next().expect(\"we already checked length\")?;\n\n            let cmp = compare_immutable(\n                std::iter::once(&lhs_value),\n                std::iter::once(&rhs_value),\n                &self.metadata.key_info[i..i + 1],\n            );\n\n            if cmp != std::cmp::Ordering::Equal {\n                return Ok(cmp);\n            }\n        }\n\n        Ok(std::cmp::Ordering::Equal)\n    }\n\n    /// Check if the index key contains any NULL values (excluding the rowid column).\n    /// In SQLite, NULLs don't violate UNIQUE constraints, so we skip conflict checks for NULL keys.\n    pub fn contains_null(&self, num_indexed_cols: usize) -> Result<bool> {\n        let mut iter = self.key.iter()?;\n        // Only check the indexed columns, not the rowid at the end\n        for _ in 0..num_indexed_cols {\n            if let Some(value) = iter.next() {\n                if matches!(value?, crate::types::ValueRef::Null) {\n                    return Ok(true);\n                }\n            }\n        }\n        Ok(false)\n    }\n\n    /// Check if the first `num_cols` columns of this key match another key.\n    /// Used for UNIQUE index conflict detection where we need to compare only\n    /// the indexed columns, not the rowid suffix.\n    pub fn matches_prefix(&self, other: &Self, num_cols: usize) -> Result<bool> {\n        let mut lhs = self.key.iter()?;\n        let mut rhs = other.key.iter()?;\n\n        for i in 0..num_cols {\n            let lhs_value = match lhs.next() {\n                Some(v) => v?,\n                None => return Ok(false),\n            };\n            let rhs_value = match rhs.next() {\n                Some(v) => v?,\n                None => return Ok(false),\n            };\n\n            let cmp = compare_immutable(\n                std::iter::once(&lhs_value),\n                std::iter::once(&rhs_value),\n                &self.metadata.key_info[i..i + 1],\n            );\n\n            if cmp != std::cmp::Ordering::Equal {\n                return Ok(false);\n            }\n        }\n\n        Ok(true)\n    }\n}\n\nimpl PartialEq for SortableIndexKey {\n    fn eq(&self, other: &Self) -> bool {\n        if self.key == other.key {\n            return true;\n        }\n\n        self.compare(other)\n            .map(|ord| ord == std::cmp::Ordering::Equal)\n            .unwrap_or(false)\n    }\n}\n\nimpl Eq for SortableIndexKey {}\n\nimpl PartialOrd for SortableIndexKey {\n    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {\n        Some(self.cmp(other))\n    }\n}\n\nimpl Ord for SortableIndexKey {\n    fn cmp(&self, other: &Self) -> std::cmp::Ordering {\n        self.compare(other).expect(\"Failed to compare IndexKeys\")\n    }\n}\n\n#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]\npub enum RowKey {\n    Int(i64),\n    Record(SortableIndexKey),\n}\n\nimpl RowKey {\n    pub fn to_int_or_panic(&self) -> i64 {\n        match self {\n            RowKey::Int(row_id) => *row_id,\n            _ => panic!(\"RowKey is not an integer\"),\n        }\n    }\n\n    pub fn is_int_key(&self) -> bool {\n        matches!(self, RowKey::Int(_))\n    }\n}\n\nimpl std::fmt::Display for RowKey {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            RowKey::Int(row_id) => write!(f, \"{row_id}\"),\n            RowKey::Record(record) => write!(f, \"{record:?}\"),\n        }\n    }\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct RowID {\n    /// The table ID. Analogous to table's root page number.\n    pub table_id: MVTableId,\n    pub row_id: RowKey,\n}\n\nimpl RowID {\n    pub fn new(table_id: MVTableId, row_id: RowKey) -> Self {\n        Self { table_id, row_id }\n    }\n}\n\n#[derive(Clone, Debug, PartialEq, PartialOrd)]\n\npub struct Row {\n    pub id: RowID,\n    /// Data is None for index rows because the key holds all the data.\n    pub data: Option<Vec<u8>>,\n    pub column_count: usize,\n}\n\nimpl Row {\n    pub fn new_table_row(id: RowID, data: Vec<u8>, column_count: usize) -> Self {\n        Self {\n            id,\n            data: Some(data),\n            column_count,\n        }\n    }\n\n    pub fn new_index_row(id: RowID, column_count: usize) -> Self {\n        Self {\n            id,\n            data: None,\n            column_count,\n        }\n    }\n\n    pub fn is_index_row(&self) -> bool {\n        self.data.is_none()\n    }\n\n    pub fn payload(&self) -> &[u8] {\n        match self.id.row_id {\n            RowKey::Int(_) => self.data.as_ref().expect(\"table rows should have data\"),\n            RowKey::Record(ref sortable_key) => sortable_key.key.as_blob(),\n        }\n    }\n}\n\n/// A row version.\n/// TODO: we can optimize this by using bitpacking for the begin and end fields.\n#[derive(Clone, Debug, PartialEq)]\npub struct RowVersion {\n    /// Unique identifier for this version within the MvStore.\n    /// Used for savepoint tracking to identify specific versions to rollback.\n    pub id: u64,\n    pub begin: Option<TxTimestampOrID>,\n    pub end: Option<TxTimestampOrID>,\n    pub row: Row,\n    /// Indicates this version was created for a row that existed in B-tree before\n    /// MVCC was enabled (e.g., after switching from WAL to MVCC journal mode).\n    /// This flag helps the checkpoint logic determine if a delete should be\n    /// checkpointed to the B-tree file.\n    pub btree_resident: bool,\n}\n\n#[derive(Debug)]\npub enum RowVersionState {\n    LiveVersion,\n    NotFound,\n    Deleted,\n}\npub type TxID = u64;\n\n/// A log record contains all durable effects of a committed transaction.\n/// Besides row/index version deltas, this can include a header-only mutation.\n#[derive(Clone, Debug)]\npub struct LogRecord {\n    pub(crate) tx_timestamp: TxID,\n    pub row_versions: Vec<RowVersion>,\n    pub header: Option<DatabaseHeader>,\n}\n\nimpl LogRecord {\n    fn new(tx_timestamp: TxID) -> Self {\n        Self {\n            tx_timestamp,\n            row_versions: Vec::new(),\n            header: None,\n        }\n    }\n}\n\n/// A transaction timestamp or ID.\n///\n/// Versions either track a timestamp or a transaction ID, depending on the\n/// phase of the transaction. During the active phase, new versions track the\n/// transaction ID in the `begin` and `end` fields. After a transaction commits,\n/// versions switch to tracking timestamps.\n#[derive(Clone, Debug, PartialEq, PartialOrd)]\npub enum TxTimestampOrID {\n    /// A committed transaction's timestamp.\n    Timestamp(u64),\n    /// The ID of a non-committed transaction.\n    TxID(TxID),\n}\n\n/// Tracks versions created/modified during a savepoint for rollback.\n/// Used for statement-level savepoints in interactive transactions.\n#[derive(Debug, Default)]\nenum SavepointKind {\n    /// Internal savepoint used for statement-level rollback.\n    #[default]\n    Statement,\n    /// User-visible named savepoint.\n    Named {\n        name: String,\n        starts_transaction: bool,\n    },\n}\n\n/// Tracks row/index version deltas created inside a single savepoint scope.\n#[derive(Debug, Default)]\npub struct Savepoint {\n    kind: SavepointKind,\n    deferred_fk_violations: isize,\n    /// Versions CREATED during this savepoint (insert operations).\n    /// On rollback: these versions are removed from their chains.\n    created_table_versions: Vec<(RowID, u64)>,\n    created_index_versions: Vec<((MVTableId, Arc<SortableIndexKey>), u64)>,\n    /// Versions DELETED during this savepoint (end timestamp set).\n    /// On rollback: clear end timestamp to restore visibility.\n    deleted_table_versions: Vec<(RowID, u64)>,\n    deleted_index_versions: Vec<((MVTableId, Arc<SortableIndexKey>), u64)>,\n    /// RowIDs that were NEWLY added to write_set by this savepoint.\n    /// On rollback: only these should be removed from write_set.\n    newly_added_to_write_set: Vec<RowID>,\n}\n\nimpl Savepoint {\n    /// Creates an internal statement savepoint used for per-statement rollback.\n    fn statement() -> Self {\n        Self::default()\n    }\n\n    /// Creates a user-visible named savepoint snapshot.\n    fn named(name: String, starts_transaction: bool, deferred_fk_violations: isize) -> Self {\n        Self {\n            kind: SavepointKind::Named {\n                name,\n                starts_transaction,\n            },\n            deferred_fk_violations,\n            ..Default::default()\n        }\n    }\n\n    /// Merges child savepoint deltas into this savepoint.\n    ///\n    /// Called when releasing nested savepoints so outer rollback still has a full undo set.\n    fn merge_from(&mut self, mut other: Savepoint) {\n        self.created_table_versions\n            .append(&mut other.created_table_versions);\n        self.created_index_versions\n            .append(&mut other.created_index_versions);\n        self.deleted_table_versions\n            .append(&mut other.deleted_table_versions);\n        self.deleted_index_versions\n            .append(&mut other.deleted_index_versions);\n        self.newly_added_to_write_set\n            .append(&mut other.newly_added_to_write_set);\n    }\n}\n\nstruct SavepointRollbackResult {\n    /// Savepoints that were rolled back, in the order they were created (oldest to newest).\n    rolledback_savepoints: Vec<Savepoint>,\n    /// Deferred FK counter snapshot captured at the target named savepoint.\n    deferred_fk_violations: isize,\n}\n\n/// Transaction\n#[derive(Debug)]\npub struct Transaction {\n    /// The state of the transaction.\n    state: AtomicTransactionState,\n    /// The transaction ID.\n    tx_id: u64,\n    /// The transaction begin timestamp.\n    begin_ts: u64,\n    /// The transaction write set.\n    write_set: SkipSet<RowID>,\n    /// The transaction read set.\n    read_set: SkipSet<RowID>,\n    /// The transaction header.\n    header: RwLock<DatabaseHeader>,\n    /// True when the transaction mutated its local database header snapshot.\n    header_dirty: AtomicBool,\n    /// Stack of savepoints for statement-level rollback.\n    /// Each savepoint tracks versions created/deleted during that statement.\n    savepoint_stack: RwLock<Vec<Savepoint>>,\n    /// True when this transaction currently holds the serialized logical-log commit lock.\n    pager_commit_lock_held: AtomicBool,\n    /// Number of unresolved commit dependencies (must reach 0 before commit).\n    /// i.e the number of transactions this transaction is dependent on and waiting for\n    /// commit or abort.\n    /// Hekaton Section 2.7: \"A transaction cannot commit until this counter is zero.\"\n    commit_dep_counter: AtomicU64,\n    /// Flag: a depended-on transaction aborted; this transaction must abort too.\n    /// Hekaton Section 2.7: \"AbortNow that other transactions can set to tell T to abort.\"\n    abort_now: AtomicBool,\n    /// Transaction IDs that depend on this transaction (notified on commit/abort).\n    /// Hekaton Section 2.7: \"CommitDepSet, that stores transaction IDs of the\n    /// transactions that depend on T.\"\n    commit_dep_set: Mutex<HashSet<TxID>>,\n}\n\nimpl Transaction {\n    fn new(tx_id: u64, begin_ts: u64, header: DatabaseHeader) -> Transaction {\n        Transaction {\n            state: TransactionState::Active.into(),\n            tx_id,\n            begin_ts,\n            write_set: SkipSet::new(),\n            read_set: SkipSet::new(),\n            header: RwLock::new(header),\n            header_dirty: AtomicBool::new(false),\n            savepoint_stack: RwLock::new(Vec::new()),\n            pager_commit_lock_held: AtomicBool::new(false),\n            commit_dep_counter: AtomicU64::new(0),\n            abort_now: AtomicBool::new(false),\n            commit_dep_set: Mutex::new(HashSet::default()),\n        }\n    }\n\n    fn insert_to_read_set(&self, id: RowID) {\n        self.read_set.insert(id);\n    }\n\n    fn insert_to_write_set(&self, id: RowID) {\n        // Check if this is a new addition to write_set\n        let is_new = !self.write_set.contains(&id);\n        self.write_set.insert(id.clone());\n        // If new, record in the current savepoint so we can remove on rollback\n        if is_new {\n            if let Some(savepoint) = self.savepoint_stack.write().last_mut() {\n                savepoint.newly_added_to_write_set.push(id);\n            }\n        }\n    }\n\n    /// Begin a new savepoint for statement-level tracking.\n    fn begin_savepoint(&self) {\n        let depth = self.savepoint_stack.read().len();\n        tracing::debug!(\"begin_savepoint(tx_id={}, depth={})\", self.tx_id, depth);\n        self.savepoint_stack.write().push(Savepoint::statement());\n    }\n\n    /// Begin a new named savepoint. If `starts_transaction` is true, this savepoint represents the\n    /// beginning of an interactive transaction and will be used to track deferred FK violations\n    /// for that transaction.\n    fn begin_named_savepoint(\n        &self,\n        name: String,\n        starts_transaction: bool,\n        deferred_fk_violations: isize,\n    ) {\n        let depth = self.savepoint_stack.read().len();\n        tracing::debug!(\n            \"begin_named_savepoint(tx_id={}, depth={}, name={})\",\n            self.tx_id,\n            depth,\n            name\n        );\n        self.savepoint_stack.write().push(Savepoint::named(\n            name,\n            starts_transaction,\n            deferred_fk_violations,\n        ));\n    }\n\n    /// Release the newest savepoint (statement completed successfully).\n    fn release_savepoint(&self) {\n        let depth = self.savepoint_stack.read().len();\n        tracing::debug!(\"release_savepoint(tx_id={}, depth={})\", self.tx_id, depth);\n        let mut savepoints = self.savepoint_stack.write();\n        if !matches!(\n            savepoints.last().map(|savepoint| &savepoint.kind),\n            Some(SavepointKind::Statement)\n        ) {\n            return;\n        }\n        let savepoint = savepoints.pop().expect(\"savepoint must exist\");\n        if let Some(parent) = savepoints.last_mut() {\n            parent.merge_from(savepoint);\n        }\n    }\n\n    fn pop_statement_savepoint(&self) -> Option<Savepoint> {\n        let mut savepoints = self.savepoint_stack.write();\n        if !matches!(\n            savepoints.last().map(|savepoint| &savepoint.kind),\n            Some(SavepointKind::Statement)\n        ) {\n            return None;\n        }\n        savepoints.pop()\n    }\n\n    /// Release a named savepoint. If this savepoint starts a transaction, returns\n    /// [SavepointResult::Commit] to indicate the transaction should be committed.\n    fn release_named_savepoint(&self, name: &str) -> SavepointResult {\n        let mut savepoints = self.savepoint_stack.write();\n        let Some(target_idx) = savepoints.iter().rposition(|savepoint| {\n            matches!(\n                savepoint.kind,\n                SavepointKind::Named {\n                    name: ref savepoint_name,\n                    ..\n                } if savepoint_name == name\n            )\n        }) else {\n            return SavepointResult::NotFound;\n        };\n\n        let commits_transaction = if matches!(\n            savepoints[target_idx].kind,\n            SavepointKind::Named {\n                starts_transaction: true,\n                ..\n            }\n        ) && target_idx == 0\n        {\n            SavepointResult::Commit\n        } else {\n            SavepointResult::Release\n        };\n        if matches!(commits_transaction, SavepointResult::Commit) {\n            // Defer mutation until transaction commit succeeds. If commit fails\n            // (e.g. deferred FK violation), savepoints must remain intact.\n            return commits_transaction;\n        }\n\n        let drained: Vec<Savepoint> = savepoints.drain(target_idx..).collect();\n        if let Some(parent) = savepoints.last_mut() {\n            for savepoint in drained {\n                parent.merge_from(savepoint);\n            }\n        }\n        commits_transaction\n    }\n\n    /// Find the named savepoint to rollback to and pop all savepoints above it. Returns the rolled\n    /// back savepoints and net change in deferred FK violations for undoing changes to transaction\n    /// state.\n    fn rollback_to_named_savepoint(&self, name: &str) -> Option<SavepointRollbackResult> {\n        let mut savepoints = self.savepoint_stack.write();\n        let target_idx = savepoints.iter().rposition(|savepoint| {\n            matches!(\n                savepoint.kind,\n                SavepointKind::Named {\n                    name: ref savepoint_name,\n                    ..\n                } if savepoint_name == name\n            )\n        })?;\n\n        let target_name = match &savepoints[target_idx].kind {\n            SavepointKind::Named { name, .. } => name.clone(),\n            SavepointKind::Statement => unreachable!(\"target idx points to named savepoint\"),\n        };\n        let starts_transaction = matches!(\n            savepoints[target_idx].kind,\n            SavepointKind::Named {\n                starts_transaction: true,\n                ..\n            }\n        );\n        let deferred_fk_violations = savepoints[target_idx].deferred_fk_violations;\n\n        let drained: Vec<Savepoint> = savepoints.drain(target_idx..).collect();\n        savepoints.push(Savepoint::named(\n            target_name,\n            starts_transaction,\n            deferred_fk_violations,\n        ));\n        Some(SavepointRollbackResult {\n            rolledback_savepoints: drained,\n            deferred_fk_violations,\n        })\n    }\n\n    /// Record a version that was created during the current savepoint.\n    fn record_created_table_version(&self, rowid: RowID, version_id: u64) {\n        if let Some(savepoint) = self.savepoint_stack.write().last_mut() {\n            tracing::debug!(\n                \"record_created_table_version(tx_id={}, table_id={}, row_id={}, version_id={})\",\n                self.tx_id,\n                rowid.table_id,\n                rowid.row_id,\n                version_id\n            );\n            savepoint.created_table_versions.push((rowid, version_id));\n        }\n    }\n\n    /// Record an index version that was created during the current savepoint.\n    fn record_created_index_version(\n        &self,\n        key: (MVTableId, Arc<SortableIndexKey>),\n        version_id: u64,\n    ) {\n        if let Some(savepoint) = self.savepoint_stack.write().last_mut() {\n            tracing::debug!(\n                \"record_created_index_version(tx_id={}, table_id={}, version_id={})\",\n                self.tx_id,\n                key.0,\n                version_id\n            );\n            savepoint.created_index_versions.push((key, version_id));\n        }\n    }\n\n    /// Record a version that was deleted during the current savepoint.\n    fn record_deleted_table_version(&self, rowid: RowID, version_id: u64) {\n        if let Some(savepoint) = self.savepoint_stack.write().last_mut() {\n            tracing::debug!(\n                \"record_deleted_table_version(tx_id={}, table_id={}, row_id={}, version_id={})\",\n                self.tx_id,\n                rowid.table_id,\n                rowid.row_id,\n                version_id\n            );\n            savepoint.deleted_table_versions.push((rowid, version_id));\n        }\n    }\n\n    /// Record an index version that was deleted during the current savepoint.\n    fn record_deleted_index_version(\n        &self,\n        key: (MVTableId, Arc<SortableIndexKey>),\n        version_id: u64,\n    ) {\n        if let Some(savepoint) = self.savepoint_stack.write().last_mut() {\n            tracing::debug!(\n                \"record_deleted_index_version(tx_id={}, table_id={}, version_id={})\",\n                self.tx_id,\n                key.0,\n                version_id\n            );\n            savepoint.deleted_index_versions.push((key, version_id));\n        }\n    }\n}\n\nimpl std::fmt::Display for Transaction {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {\n        write!(\n            f,\n            \"{{ state: {}, id: {}, begin_ts: {}, write_set: [\",\n            self.state.load(),\n            self.tx_id,\n            self.begin_ts,\n        )?;\n\n        for (i, v) in self.write_set.iter().enumerate() {\n            if i > 0 {\n                write!(f, \", \")?\n            }\n            write!(f, \"{:?}\", *v.value())?;\n        }\n\n        write!(f, \"], read_set: [\")?;\n        for (i, v) in self.read_set.iter().enumerate() {\n            if i > 0 {\n                write!(f, \", \")?;\n            }\n            write!(f, \"{:?}\", *v.value())?;\n        }\n\n        write!(f, \"] }}\")\n    }\n}\n\n/// Transaction state.\n#[derive(Debug, Clone, PartialEq, Copy)]\nenum TransactionState {\n    Active,\n    /// Preparing state includes the end_ts so other transactions can compare\n    /// timestamps during validation to resolve races (first-committer-wins).\n    Preparing(u64),\n    Aborted,\n    Terminated,\n    Committed(u64),\n}\n\nimpl TransactionState {\n    // Bit patterns for encoding states with timestamps\n    const PREPARING_BIT: u64 = 0x4000_0000_0000_0000;\n    const COMMITTED_BIT: u64 = 0x8000_0000_0000_0000;\n    const TIMESTAMP_MASK: u64 = 0x3fff_ffff_ffff_ffff;\n\n    pub fn encode(&self) -> u64 {\n        match self {\n            TransactionState::Active => 0,\n            TransactionState::Preparing(ts) => {\n                // We only support 2^62 - 1 timestamps\n                assert!(ts & !Self::TIMESTAMP_MASK == 0);\n                Self::PREPARING_BIT | ts\n            }\n            TransactionState::Aborted => 1,\n            TransactionState::Terminated => 2,\n            TransactionState::Committed(ts) => {\n                // We only support 2^62 - 1 timestamps\n                turso_assert_eq!(ts & !Self::TIMESTAMP_MASK, 0);\n                Self::COMMITTED_BIT | ts\n            }\n        }\n    }\n\n    pub fn decode(v: u64) -> Self {\n        match v {\n            0 => TransactionState::Active,\n            1 => TransactionState::Aborted,\n            2 => TransactionState::Terminated,\n            v if v & Self::COMMITTED_BIT != 0 => {\n                TransactionState::Committed(v & Self::TIMESTAMP_MASK)\n            }\n            v if v & Self::PREPARING_BIT != 0 => {\n                TransactionState::Preparing(v & Self::TIMESTAMP_MASK)\n            }\n            _ => panic!(\"Invalid transaction state\"),\n        }\n    }\n}\n\n// Transaction state encoded into a single 64-bit atomic.\n#[derive(Debug)]\npub(crate) struct AtomicTransactionState {\n    pub(crate) state: AtomicU64,\n}\n\nimpl From<TransactionState> for AtomicTransactionState {\n    fn from(state: TransactionState) -> Self {\n        Self {\n            state: AtomicU64::new(state.encode()),\n        }\n    }\n}\n\nimpl From<AtomicTransactionState> for TransactionState {\n    fn from(state: AtomicTransactionState) -> Self {\n        let encoded = state.state.load(Ordering::Acquire);\n        TransactionState::decode(encoded)\n    }\n}\n\nimpl std::cmp::PartialEq<TransactionState> for AtomicTransactionState {\n    fn eq(&self, other: &TransactionState) -> bool {\n        &self.load() == other\n    }\n}\n\nimpl std::fmt::Display for TransactionState {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {\n        match self {\n            TransactionState::Active => write!(f, \"Active\"),\n            TransactionState::Preparing(ts) => write!(f, \"Preparing({ts})\"),\n            TransactionState::Committed(ts) => write!(f, \"Committed({ts})\"),\n            TransactionState::Aborted => write!(f, \"Aborted\"),\n            TransactionState::Terminated => write!(f, \"Terminated\"),\n        }\n    }\n}\n\nimpl AtomicTransactionState {\n    fn store(&self, state: TransactionState) {\n        self.state.store(state.encode(), Ordering::Release);\n    }\n\n    fn load(&self) -> TransactionState {\n        TransactionState::decode(self.state.load(Ordering::Acquire))\n    }\n}\n\n#[allow(clippy::large_enum_variant)]\npub enum CommitState<Clock: LogicalClock> {\n    Initial,\n    Commit {\n        end_ts: u64,\n    },\n    /// Wait for unresolved commit dependencies before building the durable\n    /// committed view for the logical log.\n    /// Hekaton Section 3.2: \"If T passes validation, it must wait for outstanding\n    /// commit dependencies to be resolved.\"\n    WaitForDependencies {\n        end_ts: u64,\n    },\n    BeginCommitLogicalLog {\n        end_ts: u64,\n        log_record: LogRecord,\n    },\n    EndCommitLogicalLog {\n        end_ts: u64,\n    },\n    SyncLogicalLog {\n        end_ts: u64,\n    },\n    Checkpoint {\n        // TODO: if and when we transform this code to async we won't be needing this explicit state machine nor\n        // the mutex\n        state_machine: Mutex<StateMachine<CheckpointStateMachine<Clock>>>,\n    },\n    CommitEnd {\n        end_ts: u64,\n    },\n}\n\n#[derive(Debug)]\npub enum WriteRowState {\n    Initial,\n    Seek,\n    Insert,\n    /// Move to the next record in order to leave the cursor in the next position, this is used for inserting multiple rows for optimizations.\n    Next,\n}\n\n#[derive(Debug)]\nstruct CommitCoordinator {\n    pager_commit_lock: Arc<TursoRwLock>,\n}\n\nimpl CommitCoordinator {\n    fn new() -> Self {\n        Self {\n            pager_commit_lock: Arc::new(TursoRwLock::new()),\n        }\n    }\n}\n\npub struct CommitStateMachine<Clock: LogicalClock> {\n    state: CommitState<Clock>,\n    is_finalized: bool,\n    did_commit_schema_change: bool,\n    tx_id: TxID,\n    connection: Arc<Connection>,\n    /// Write set sorted by table id and row id\n    write_set: Vec<RowID>,\n    commit_coordinator: Arc<CommitCoordinator>,\n    header: Arc<RwLock<Option<DatabaseHeader>>>,\n    pager: Arc<Pager>,\n    /// Bytes appended to the logical log for this commit; applied to writer offset only after durability and before lock release.\n    pending_log_append_bytes: Option<u64>,\n    /// The synchronous mode for fsync operations. When set to Off, fsync is skipped.\n    sync_mode: SyncMode,\n    _phantom: PhantomData<Clock>,\n}\n\nimpl<Clock: LogicalClock> Debug for CommitStateMachine<Clock> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"CommitStateMachine\")\n            .field(\"state\", &self.state)\n            .field(\"is_finalized\", &self.is_finalized)\n            .finish()\n    }\n}\n\npub struct WriteRowStateMachine {\n    state: WriteRowState,\n    is_finalized: bool,\n    row: Row,\n    record: Option<ImmutableRecord>,\n    cursor: Arc<RwLock<BTreeCursor>>,\n    requires_seek: bool,\n}\n\n#[derive(Debug)]\npub enum DeleteRowState {\n    Initial,\n    Seek,\n    /// After seek returns TryAdvance (key found in interior node, not leaf),\n    /// advance the cursor to position it on the interior cell.\n    Advance,\n    Delete,\n}\n\npub struct DeleteRowStateMachine {\n    state: DeleteRowState,\n    is_finalized: bool,\n    rowid: RowID,\n    cursor: Arc<RwLock<BTreeCursor>>,\n}\n\nimpl<Clock: LogicalClock> CommitStateMachine<Clock> {\n    fn new(\n        state: CommitState<Clock>,\n        tx_id: TxID,\n        connection: Arc<Connection>,\n        commit_coordinator: Arc<CommitCoordinator>,\n        header: Arc<RwLock<Option<DatabaseHeader>>>,\n        sync_mode: SyncMode,\n    ) -> Self {\n        let pager = connection.pager.load().clone();\n        Self {\n            state,\n            is_finalized: false,\n            did_commit_schema_change: false,\n            tx_id,\n            connection,\n            write_set: Vec::new(),\n            commit_coordinator,\n            pager,\n            header,\n            pending_log_append_bytes: None,\n            sync_mode,\n            _phantom: PhantomData,\n        }\n    }\n\n    /// Validates commit-time write-write conflicts for one table row key.\n    ///\n    /// Returns [LimboError::WriteWriteConflict] when another transaction committed or is\n    /// preparing a conflicting version according to first-committer-wins.\n    fn check_rowid_for_conflicts(\n        &self,\n        rowid: &RowID,\n        end_ts: u64,\n        tx: &Transaction,\n        mvcc_store: &Arc<MvStore<Clock>>,\n    ) -> Result<()> {\n        let row_versions = mvcc_store.rows.get(rowid);\n        if row_versions.is_none() {\n            return Ok(());\n        }\n\n        let row_versions = row_versions.unwrap();\n        let row_versions = row_versions.value();\n        let row_versions = row_versions.read();\n\n        self.check_version_conflicts(end_ts, tx, mvcc_store, &row_versions)?;\n        Ok(())\n    }\n\n    /// Validates commit-time write-write conflicts for one index key.\n    ///\n    /// Returns [LimboError::WriteWriteConflict] when another transaction committed or is\n    /// preparing a conflicting index version according to first-committer-wins.\n    fn check_index_for_conflicts(\n        &self,\n        rowid: &RowID,\n        end_ts: u64,\n        tx: &Transaction,\n        mvcc_store: &Arc<MvStore<Clock>>,\n    ) -> Result<()> {\n        let RowKey::Record(record) = &rowid.row_id else {\n            panic!(\"invalid index row_id type, should be Record\")\n        };\n        if !record.metadata.is_unique {\n            // Skip indexes which are not unique or not primary key\n            return Ok(());\n        }\n        // In SQLite, NULLs don't violate UNIQUE constraints - skip conflict check for keys containing NULL\n        let num_indexed_cols = record.metadata.num_cols.saturating_sub(1); // exclude rowid column\n        if record.contains_null(num_indexed_cols)? {\n            return Ok(());\n        }\n\n        // Create a prefix key with num_cols - 1 for range lookup.\n        // Due to SortableIndexKey's Ord using min(num_cols), this key compares Equal\n        // to all entries with the same indexed columns (regardless of rowid).\n        let prefix_key = {\n            let mut index_info = record.metadata.as_ref().clone();\n            turso_assert!(index_info.has_rowid, \"not supported yet without rowid\");\n            index_info.num_cols -= 1;\n            SortableIndexKey {\n                key: record.key.clone(),\n                metadata: Arc::new(index_info),\n            }\n        };\n\n        let table_id = rowid.table_id;\n        let index_rows = mvcc_store\n            .index_rows\n            .get(&table_id)\n            .unwrap_or_else(|| panic!(\"expected index {table_id:?}\"));\n        let index_rows = index_rows.value();\n\n        // Use range to efficiently find all entries that match the prefix.\n        // Since entries are ordered by Ord, all entries with the same indexed columns\n        // are contiguous. We start from the prefix_key and stop when prefix no longer matches.\n        for entry in index_rows.range::<SortableIndexKey, _>(&prefix_key..) {\n            let other_key = entry.key();\n            // Check if prefix still matches - if not, we've passed all matching entries\n            if !record.matches_prefix(other_key, num_indexed_cols)? {\n                break;\n            }\n            let row_versions = entry.value();\n            let row_versions = row_versions.read();\n            self.check_version_conflicts(end_ts, tx, mvcc_store, &row_versions)?;\n        }\n\n        Ok(())\n    }\n\n    /// Validates a single version chain against the current transaction's commit timestamp.\n    ///\n    /// This enforces snapshot-isolation conflict checks for both:\n    /// 1. versions ended by concurrent commits (`end > tx.begin_ts`), and\n    /// 2. live versions owned by concurrent transactions (state/ts tie-breaking).\n    fn check_version_conflicts(\n        &self,\n        end_ts: u64,\n        tx: &Transaction,\n        mvcc_store: &Arc<MvStore<Clock>>,\n        row_versions: &[RowVersion],\n    ) -> Result<()> {\n        // Check for conflicts - iterate in reverse for faster early termination\n        for version in row_versions.iter().rev() {\n            // A row that we are trying to commit was deleted/updated by another\n            // committed transaction after our begin timestamp. Even if that\n            // version is now \"ended\", this is still a write-write conflict.\n            if let Some(TxTimestampOrID::Timestamp(end_ts)) = version.end {\n                turso_assert!(\n                    end_ts != tx.begin_ts,\n                    \"committed end_ts and begin_ts cannot be equal: txn timestamps are strictly monotonic\"\n                );\n                if end_ts > tx.begin_ts {\n                    return Err(LimboError::WriteWriteConflict);\n                }\n            }\n\n            // B-tree tombstones (begin: None, end: TxID) act as write locks.\n            // When another transaction has created a tombstone to delete a\n            // B-tree-resident row, that tombstone is effectively a write lock\n            // on the row — same as Hekaton's End field. We must detect this\n            // as a write-write conflict using the same state-based logic used\n            // for begin: TxID checks below.\n            if version.begin.is_none() {\n                // Committed tombstones (end: Timestamp) are already handled by\n                // the check above at lines 1070-1074. Here we only need to check\n                // in-flight tombstones (end: TxID) from other transactions.\n                if let Some(TxTimestampOrID::TxID(other_tx_id)) = version.end {\n                    if other_tx_id != self.tx_id {\n                        let other_tx = mvcc_store.txs.get(&other_tx_id).expect(\n                            \"check_version_conflicts: tombstone end TxID not found in txn map\",\n                        );\n                        let other_tx = other_tx.value();\n                        match other_tx.state.load() {\n                            TransactionState::Committed(_) => {\n                                return Err(LimboError::WriteWriteConflict);\n                            }\n                            TransactionState::Preparing(other_end_ts) => {\n                                if other_end_ts < end_ts {\n                                    return Err(LimboError::WriteWriteConflict);\n                                }\n                            }\n                            TransactionState::Active => {}\n                            TransactionState::Aborted | TransactionState::Terminated => {}\n                        }\n                    }\n                }\n                // Tombstones have no meaningful begin field — skip begin checks\n                continue;\n            }\n\n            match version.end {\n                Some(TxTimestampOrID::Timestamp(end_ts)) => {\n                    // Committed deletion. If end_ts > our begin_ts, the conflict\n                    // would have been already caught earlier when we iterate through\n                    // the row versions in reverse. If end_ts < our\n                    // begin_ts, the deletion predates our snapshot — no conflict.\n                    turso_assert!(\n                        end_ts < tx.begin_ts,\n                        \"row version's end_ts cannot be greater than txns begin_ts\"\n                    );\n                    continue;\n                }\n                Some(TxTimestampOrID::TxID(end_tx_id)) => {\n                    // Deletion not yet finalized; the deleting transaction may still be in Preparing.\n                    if end_tx_id == self.tx_id {\n                        // We deleted this version ourselves, so it cannot conflict with our commit.\n                        continue;\n                    }\n\n                    match lookup_tx_state(\n                        &mvcc_store.txs,\n                        &mvcc_store.finalized_tx_states,\n                        end_tx_id,\n                    ) {\n                        Some(TransactionState::Committed(committed_end_ts)) => {\n                            turso_assert!(committed_end_ts != tx.begin_ts, \"committed end_ts and begin_ts cannot be equal: txn timestamps are strictly monotonic\");\n                            if committed_end_ts > tx.begin_ts {\n                                return Err(LimboError::WriteWriteConflict);\n                            }\n                            continue;\n                        }\n                        _ => {\n                            // Deleting tx is Active, Preparing, Aborted, or gone.\n                            // The deletion may not stick, so this version may still be live.\n                            // Fall through to check begin for conflicts.\n                        }\n                    }\n                }\n                None => {\n                    // No end — version is live. Fall through to check begin.\n                }\n            }\n\n            match version.begin {\n                Some(TxTimestampOrID::TxID(other_tx_id)) => {\n                    // Skip our own version\n                    if other_tx_id == self.tx_id {\n                        continue;\n                    }\n                    // Another transaction's uncommitted version - check their state\n                    match lookup_tx_state(\n                        &mvcc_store.txs,\n                        &mvcc_store.finalized_tx_states,\n                        other_tx_id,\n                    ) {\n                        // Other tx already committed = conflict\n                        Some(TransactionState::Committed(_)) => {\n                            return Err(LimboError::WriteWriteConflict);\n                        }\n                        // Both preparing - compare end_ts (lower wins)\n                        Some(TransactionState::Preparing(other_end_ts)) => {\n                            if other_end_ts < end_ts {\n                                // Other tx has lower end_ts, they win\n                                return Err(LimboError::WriteWriteConflict);\n                            }\n                            // We have lower end_ts, we win - they'll abort when they validate\n                        }\n                        // Other tx still active - we're already Preparing so we're ahead\n                        // They'll see us in Preparing/Committed when they try to commit\n                        Some(TransactionState::Active) => {}\n                        // Other tx aborted - no conflict\n                        Some(TransactionState::Aborted) | Some(TransactionState::Terminated) => {}\n                        None => {\n                            // TODO: an aborted txn should not affect another one.. properly handle\n                            // this case, but for now be conservative and treat as conflict to avoid\n                            // potential correctness issues\n                            tracing::debug!(\n                                \"check_version_conflicts: missing tx {} for row version {:?}; conservatively treating as conflict\",\n                                other_tx_id,\n                                version\n                            );\n                            return Err(LimboError::WriteWriteConflict);\n                        }\n                    }\n                }\n                Some(TxTimestampOrID::Timestamp(begin_ts)) => {\n                    // A live committed version with this rowid exists.\n                    // begin_ts >= tx.begin_ts: a concurrent transaction committed a row\n                    //   with this rowid after our snapshot — invisible to NotExists.\n                    // begin_ts < tx.begin_ts: the row predates our snapshot. NotExists\n                    //   should have seen it at INSERT time, so this is a defensive guard.\n                    let _ = begin_ts;\n                    return Err(LimboError::WriteWriteConflict);\n                }\n                None => {\n                    // Invalid version\n                }\n            }\n        }\n        Ok(())\n    }\n\n    /// Build the committed image for the logical log without mutating the\n    /// live MVCC version chains, which must stay TxID-backed until CommitEnd.\n    fn build_committed_log_record(\n        &mut self,\n        mvcc_store: &Arc<MvStore<Clock>>,\n        tx: &Transaction,\n        end_ts: u64,\n    ) -> LogRecord {\n        let mut log_record = LogRecord::new(end_ts);\n        if tx.header_dirty.load(Ordering::Acquire) {\n            // Persist the transaction-local header snapshot in the same logical-log frame.\n            log_record.header = Some(*tx.header.read());\n        }\n\n        for id in &self.write_set {\n            if let Some(row_versions) = mvcc_store.rows.get(id) {\n                let row_versions = row_versions.value().read();\n                for row_version in row_versions.iter() {\n                    let mut committed_version = row_version.clone();\n                    let mut changed = false;\n                    if let Some(TxTimestampOrID::TxID(id)) = committed_version.begin {\n                        if id == self.tx_id {\n                            // New version is valid STARTING FROM the committing\n                            // transaction's end timestamp. See Hekaton page 299.\n                            committed_version.begin = Some(TxTimestampOrID::Timestamp(end_ts));\n                            changed = true;\n                            if committed_version.row.id.table_id == SQLITE_SCHEMA_MVCC_TABLE_ID {\n                                self.did_commit_schema_change = true;\n                            }\n                        }\n                    }\n                    if let Some(TxTimestampOrID::TxID(id)) = committed_version.end {\n                        if id == self.tx_id {\n                            // Old version is valid UNTIL the committing\n                            // transaction's end timestamp. See Hekaton page 299.\n                            committed_version.end = Some(TxTimestampOrID::Timestamp(end_ts));\n                            changed = true;\n                            if committed_version.row.id.table_id == SQLITE_SCHEMA_MVCC_TABLE_ID {\n                                self.did_commit_schema_change = true;\n                            }\n                        }\n                    }\n                    if changed {\n                        mvcc_store\n                            .insert_version_raw(&mut log_record.row_versions, committed_version);\n                    }\n                }\n            }\n\n            if let Some(index) = mvcc_store.index_rows.get(&id.table_id) {\n                let index = index.value();\n                let RowKey::Record(ref index_key) = id.row_id else {\n                    panic!(\"Index writes must have a record key\");\n                };\n                if let Some(row_versions) = index.get(index_key) {\n                    let row_versions = row_versions.value().read();\n                    for row_version in row_versions.iter() {\n                        let mut committed_version = row_version.clone();\n                        let mut changed = false;\n                        if let Some(TxTimestampOrID::TxID(id)) = committed_version.begin {\n                            if id == self.tx_id {\n                                // New version is valid STARTING FROM the committing\n                                // transaction's end timestamp. See Hekaton page 299.\n                                committed_version.begin = Some(TxTimestampOrID::Timestamp(end_ts));\n                                changed = true;\n                            }\n                        }\n                        if let Some(TxTimestampOrID::TxID(id)) = committed_version.end {\n                            if id == self.tx_id {\n                                // Old version is valid UNTIL the committing\n                                // transaction's end timestamp. See Hekaton page 299.\n                                committed_version.end = Some(TxTimestampOrID::Timestamp(end_ts));\n                                changed = true;\n                            }\n                        }\n                        if changed {\n                            mvcc_store.insert_version_raw(\n                                &mut log_record.row_versions,\n                                committed_version,\n                            );\n                        }\n                    }\n                }\n            }\n        }\n\n        log_record\n    }\n\n    /// Publish committed timestamps into the live MVCC chains after the\n    /// transaction has been finalized as Committed(end_ts).\n    /// This must run as postprocessing step i.e. the txn is written to log and is durable\n    fn rewrite_live_versions_to_timestamps(&self, mvcc_store: &Arc<MvStore<Clock>>, end_ts: u64) {\n        let tx_state = mvcc_store\n            .txs\n            .get(&self.tx_id)\n            .map(|entry| entry.value().state.load());\n        turso_assert!(\n            matches!(tx_state, Some(TransactionState::Committed(ts)) if ts == end_ts),\n            \"rewrite_live_versions_to_timestamps requires a committed transaction state\"\n        );\n\n        for id in &self.write_set {\n            if let Some(row_versions) = mvcc_store.rows.get(id) {\n                let mut row_versions = row_versions.value().write();\n                for row_version in row_versions.iter_mut() {\n                    if let Some(TxTimestampOrID::TxID(id)) = row_version.begin {\n                        if id == self.tx_id {\n                            // Publish the committed begin timestamp into the live\n                            // version chain only after CommitEnd has decided the\n                            // transaction's fate.\n                            row_version.begin = Some(TxTimestampOrID::Timestamp(end_ts));\n                        }\n                    }\n                    if let Some(TxTimestampOrID::TxID(id)) = row_version.end {\n                        if id == self.tx_id {\n                            // Publish the committed end timestamp into the live\n                            // version chain only after CommitEnd has decided the\n                            // transaction's fate.\n                            row_version.end = Some(TxTimestampOrID::Timestamp(end_ts));\n                        }\n                    }\n                }\n            }\n\n            if let Some(index) = mvcc_store.index_rows.get(&id.table_id) {\n                let index = index.value();\n                let RowKey::Record(ref index_key) = id.row_id else {\n                    panic!(\"Index writes must have a record key\");\n                };\n                if let Some(row_versions) = index.get(index_key) {\n                    let mut row_versions = row_versions.value().write();\n                    for row_version in row_versions.iter_mut() {\n                        if let Some(TxTimestampOrID::TxID(id)) = row_version.begin {\n                            if id == self.tx_id {\n                                // Publish the committed begin timestamp into the live\n                                // version chain only after CommitEnd has decided the\n                                // transaction's fate.\n                                row_version.begin = Some(TxTimestampOrID::Timestamp(end_ts));\n                            }\n                        }\n                        if let Some(TxTimestampOrID::TxID(id)) = row_version.end {\n                            if id == self.tx_id {\n                                // Publish the committed end timestamp into the live\n                                // version chain only after CommitEnd has decided the\n                                // transaction's fate.\n                                row_version.end = Some(TxTimestampOrID::Timestamp(end_ts));\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n\nimpl WriteRowStateMachine {\n    fn new(row: Row, cursor: Arc<RwLock<BTreeCursor>>, requires_seek: bool) -> Self {\n        Self {\n            state: WriteRowState::Initial,\n            is_finalized: false,\n            row,\n            record: None,\n            cursor,\n            requires_seek,\n        }\n    }\n}\n\nimpl<Clock: LogicalClock> StateTransition for CommitStateMachine<Clock> {\n    type Context = Arc<MvStore<Clock>>;\n    type SMResult = ();\n\n    #[tracing::instrument(fields(state = ?self.state), skip(self, mvcc_store), level = Level::DEBUG)]\n    fn step(&mut self, mvcc_store: &Self::Context) -> Result<TransitionResult<Self::SMResult>> {\n        tracing::trace!(\"step(state={:?})\", self.state);\n        match &self.state {\n            CommitState::Initial => {\n                // NOTICE: the first shadowed tx keeps the entry alive in the map\n                // for the duration of this whole function, which is important for correctness!\n                let tx = mvcc_store\n                    .txs\n                    .get(&self.tx_id)\n                    .ok_or(LimboError::TxTerminated)?;\n                let tx = tx.value();\n                match tx.state.load() {\n                    TransactionState::Terminated => {\n                        return Err(LimboError::TxTerminated);\n                    }\n                    _ => {\n                        turso_assert_eq!(tx.state, TransactionState::Active);\n                    }\n                }\n\n                if mvcc_store\n                    .last_committed_schema_change_ts\n                    .load(Ordering::Acquire)\n                    > tx.begin_ts\n                {\n                    // Schema changes made after the transaction began always cause a [SchemaConflict] error and the tx must abort.\n                    return Err(LimboError::SchemaConflict);\n                }\n\n                // Atomically generate end_ts and publish Preparing(end_ts) while the\n                // clock lock is held. This closes the TOCTOU window\n                // Consider the example:\n                //\n                // tx1 (Active): get_ts for end - 10\n                // tx2 (Active): got begin_ts - 11\n                // tx2 (Active): does queries but does not see changes by tx1\n                // tx1 (Preparing): now stores `end_ts(10)`\n                // tx2 (Active): queries again, but now it can see changes by tx1\n                //\n                // hence we want to guard the timestamp generation by a mutex, only allow next\n                // ts to generate when the previous one is used / discarded\n                let end_ts = mvcc_store.get_commit_timestamp(|ts| {\n                    turso_assert!(\n                        ts > tx.begin_ts,\n                        \"end_ts must be strictly greater than begin_ts\"\n                    );\n                    tx.state.store(TransactionState::Preparing(ts));\n                });\n                tracing::trace!(\"prepare_tx(tx_id={}, end_ts={})\", self.tx_id, end_ts);\n                /* In order to implement serializability, we need the following steps:\n                **\n                ** 1. Validate if all read versions are still visible by inspecting the read_set\n                ** 2. Validate if there are no phantoms by walking the scans from scan_set (which we don't even have yet)\n                **    - a phantom is a version that became visible in the middle of our transaction,\n                **      but wasn't taken into account during one of the scans from the scan_set\n                ** 3. Wait for commit dependencies, which we don't even track yet...\n                **    Excerpt from what's a commit dependency and how it's tracked in the original paper:\n                **    \"\"\"\n                        A transaction T1 has a commit dependency on another transaction\n                        T2, if T1 is allowed to commit only if T2 commits. If T2 aborts,\n                        T1 must also abort, so cascading aborts are possible. T1 acquires a\n                        commit dependency either by speculatively reading or speculatively ignoring a version,\n                        instead of waiting for T2 to commit.\n                        We implement commit dependencies by a register-and-report\n                        approach: T1 registers its dependency with T2 and T2 informs T1\n                        when it has committed or aborted. Each transaction T contains a\n                        counter, CommitDepCounter, that counts how many unresolved\n                        commit dependencies it still has. A transaction cannot commit\n                        until this counter is zero. In addition, T has a Boolean variable\n                        AbortNow that other transactions can set to tell T to abort. Each\n                        transaction T also has a set, CommitDepSet, that stores transaction IDs\n                        of the transactions that depend on T.\n                        To take a commit dependency on a transaction T2, T1 increments\n                        its CommitDepCounter and adds its transaction ID to T2’s CommitDepSet.\n                        When T2 has committed, it locates each transaction in\n                        its CommitDepSet and decrements their CommitDepCounter. If\n                        T2 aborted, it tells the dependent transactions to also abort by\n                        setting their AbortNow flags. If a dependent transaction is not\n                        found, this means that it has already aborted.\n                        Note that a transaction with commit dependencies may not have to\n                        wait at all - the dependencies may have been resolved before it is\n                        ready to commit. Commit dependencies consolidate all waits into\n                        a single wait and postpone the wait to just before commit.\n                        Some transactions may have to wait before commit.\n                        Waiting raises a concern of deadlocks.\n                        However, deadlocks cannot occur because an older transaction never\n                        waits on a younger transaction. In\n                        a wait-for graph the direction of edges would always be from a\n                        younger transaction (higher end timestamp) to an older transaction\n                        (lower end timestamp) so cycles are impossible.\n                    \"\"\"\n                **  If you're wondering when a speculative read happens, here you go:\n                **  Case 1: speculative read of TB:\n                    \"\"\"If transaction TB is in the Preparing state, it has acquired an end\n                        timestamp TS which will be V’s begin timestamp if TB commits.\n                        A safe approach in this situation would be to have transaction T\n                        wait until transaction TB commits. However, we want to avoid all\n                        blocking during normal processing so instead we continue with\n                        the visibility test and, if the test returns true, allow T to\n                        speculatively read V. Transaction T acquires a commit dependency on\n                        TB, restricting the serialization order of the two transactions. That\n                        is, T is allowed to commit only if TB commits.\n                    \"\"\"\n                **  Case 2: speculative ignore of TE:\n                    \"\"\"\n                        If TE’s state is Preparing, it has an end timestamp TS that will become\n                        the end timestamp of V if TE does commit. If TS is greater than the read\n                        time RT, it is obvious that V will be visible if TE commits. If TE\n                        aborts, V will still be visible, because any transaction that updates\n                        V after TE has aborted will obtain an end timestamp greater than\n                        TS. If TS is less than RT, we have a more complicated situation:\n                        if TE commits, V will not be visible to T but if TE aborts, it will\n                        be visible. We could handle this by forcing T to wait until TE\n                        commits or aborts but we want to avoid all blocking during normal processing.\n                        Instead we allow T to speculatively ignore V and\n                        proceed with its processing. Transaction T acquires a commit\n                        dependency (see Section 2.7) on TE, that is, T is allowed to commit\n                        only if TE commits.\n                    \"\"\"\n                */\n                /* NOTE: Commit dependencies (Hekaton Section 2.7) are implemented via\n                 ** the register-and-report protocol:\n                 ** - Speculative reads/ignores call register_commit_dependency, which\n                 **   increments CommitDepCounter and adds to CommitDepSet.\n                 ** - WaitForDependencies checks AbortNow and waits for counter == 0.\n                 ** - CommitEnd / rollback_tx drain CommitDepSet, notifying dependents.\n                 **\n                 ** TODO: For full serializability (beyond snapshot isolation), we still need:\n                 ** 1. Validate if all read versions are still visible by inspecting the read_set\n                 ** 2. Validate if there are no phantoms by walking the scans from scan_set\n                 */\n                tracing::trace!(\"commit_tx(tx_id={})\", self.tx_id);\n                self.write_set\n                    .extend(tx.write_set.iter().map(|v| v.value().clone()));\n                self.write_set.sort_by(|a, b| {\n                    // table ids are negative, and sqlite_schema has id -1 so we want to sort in descending order of table id\n                    b.table_id.cmp(&a.table_id).then(a.row_id.cmp(&b.row_id))\n                });\n                // Header-only writes must not take this fast path; they need durable log records.\n                if self.write_set.is_empty() && !tx.header_dirty.load(Ordering::Acquire) {\n                    turso_assert!(\n                        tx.commit_dep_set.lock().is_empty(),\n                        \"MVCC read only transaction should not have commit dependencies on other txns\"\n                    );\n                    // Abort eagerly if requested\n                    if tx.abort_now.load(Ordering::Acquire) {\n                        return Err(LimboError::CommitDependencyAborted);\n                    }\n                    // Even read-only transactions must honour commit dependencies.\n                    // A SELECT during normal processing may have speculatively read\n                    // from a Preparing transaction (Hekaton §2.7), incrementing our\n                    // CommitDepCounter. We must wait for those to resolve.\n                    if tx.commit_dep_counter.load(Ordering::Acquire) > 0 {\n                        // Unresolved dependencies — skip validation (no writes)\n                        // and go straight to WaitForDependencies.\n                        self.state = CommitState::WaitForDependencies { end_ts };\n                        return Ok(TransitionResult::Continue);\n                    }\n                    // Check abort_now AFTER counter: rollback_tx stores abort_now\n                    // (Release) before fetch_sub (AcqRel). Once counter == 0, all\n                    // decrements have completed and the abort_now flag is visible.\n                    if tx.abort_now.load(Ordering::Acquire) {\n                        return Err(LimboError::CommitDependencyAborted);\n                    }\n                    tx.state.store(TransactionState::Committed(end_ts));\n                    if mvcc_store.is_exclusive_tx(&self.tx_id) {\n                        mvcc_store.release_exclusive_tx(&self.tx_id);\n                    }\n                    mvcc_store.unlock_commit_lock_if_held(tx);\n                    mvcc_store.remove_tx(self.tx_id);\n                    self.finalize(mvcc_store)?;\n                    return Ok(TransitionResult::Done(()));\n                }\n                self.state = CommitState::Commit { end_ts };\n                Ok(TransitionResult::Continue)\n            }\n            CommitState::Commit { end_ts } => {\n                if !mvcc_store.is_exclusive_tx(&self.tx_id) && mvcc_store.has_exclusive_tx() {\n                    // A non-CONCURRENT transaction is holding the exclusive lock, we must abort.\n                    turso_assert_reachable!(\"commit aborted due to exclusive tx conflict\");\n                    return Err(LimboError::WriteWriteConflict);\n                }\n                // Check for rowid conflicts before committing (pure optimistic, first-committer-wins)\n                // Ref: Hekaton paper Section 3.2 - validation uses end_ts comparison\n                let tx = mvcc_store\n                    .txs\n                    .get(&self.tx_id)\n                    .ok_or(LimboError::TxTerminated)?;\n                let tx = tx.value();\n\n                for id in &self.write_set {\n                    if id.row_id.is_int_key() {\n                        self.check_rowid_for_conflicts(id, *end_ts, tx, mvcc_store)?;\n                    } else {\n                        self.check_index_for_conflicts(id, *end_ts, tx, mvcc_store)?;\n                    }\n                }\n\n                // Validation passed. Wait for commit dependencies before building\n                // the durable commit record. The live row versions must stay on\n                // TxID references until CommitEnd so an abandoned commit can\n                // still be rolled back by matching on TxID(self.tx_id).\n                self.state = CommitState::WaitForDependencies { end_ts: *end_ts };\n                return Ok(TransitionResult::Continue);\n            }\n            CommitState::WaitForDependencies { end_ts } => {\n                let end_ts = *end_ts;\n                let tx = mvcc_store\n                    .txs\n                    .get(&self.tx_id)\n                    .ok_or(LimboError::TxTerminated)?;\n                let tx = tx.value();\n\n                // Eagarly check for abort_now\n                if tx.abort_now.load(Ordering::Acquire) {\n                    return Err(LimboError::CommitDependencyAborted);\n                }\n                // Hekaton Section 2.7: \"A transaction cannot commit until this\n                // counter is zero.\" Deadlock impossible: edges always go from higher\n                // end_ts to lower end_ts, so the wait graph is acyclic.\n                if tx.commit_dep_counter.load(Ordering::Acquire) > 0 {\n                    return Ok(TransitionResult::Io(IOCompletions::Single(\n                        Completion::new_yield(),\n                    )));\n                }\n\n                // Check abort_now AFTER counter reaches 0. Memory ordering:\n                // rollback_tx does abort_now.store(true, Release) BEFORE\n                // counter.fetch_sub(1, AcqRel). Our Acquire load of counter==0\n                // synchronizes-with that fetch_sub, making the abort_now store\n                // visible. Checking in the opposite order (abort_now first) has a\n                // TOCTOU race: an aborting dep can set abort_now and decrement\n                // between our two reads, letting us see (false, 0) and commit.\n                if tx.abort_now.load(Ordering::Acquire) {\n                    return Err(LimboError::CommitDependencyAborted);\n                }\n\n                // Read-only fast path: if write_set is empty and header was not mutated, commit without\n                // going through CommitEnd. CommitEnd updates last_committed_tx_ts\n                // which would make a read-only transaction look like a write,\n                // causing spurious Busy errors from acquire_exclusive_tx.\n                if self.write_set.is_empty() && !tx.header_dirty.load(Ordering::Acquire) {\n                    turso_assert!(\n                        tx.commit_dep_set.lock().is_empty(),\n                        \"MVCC read-only transaction should not have other transactions depending on it\"\n                    );\n                    tx.state.store(TransactionState::Committed(end_ts));\n                    if mvcc_store.is_exclusive_tx(&self.tx_id) {\n                        mvcc_store.release_exclusive_tx(&self.tx_id);\n                        self.commit_coordinator.pager_commit_lock.unlock();\n                    }\n                    mvcc_store.remove_tx(self.tx_id);\n                    self.finalize(mvcc_store)?;\n                    return Ok(TransitionResult::Done(()));\n                }\n\n                // All dependencies resolved. Build the committed image for the\n                // logical log, but keep live row versions on TxID references\n                // until CommitEnd so rollback of an abandoned commit can still\n                // match them.\n                let log_record = self.build_committed_log_record(mvcc_store, tx, end_ts);\n                tracing::trace!(\"prepared_log_record(tx_id={})\", self.tx_id);\n\n                if log_record.row_versions.is_empty() && log_record.header.is_none() {\n                    // Nothing to do, just end commit.\n                    if mvcc_store.is_exclusive_tx(&self.tx_id) {\n                        mvcc_store.unlock_commit_lock_if_held(tx);\n                    }\n                    self.state = CommitState::CommitEnd { end_ts };\n                } else {\n                    self.state = CommitState::BeginCommitLogicalLog { end_ts, log_record };\n                }\n                return Ok(TransitionResult::Continue);\n            }\n            CommitState::BeginCommitLogicalLog { end_ts, log_record } => {\n                if !mvcc_store.is_exclusive_tx(&self.tx_id) {\n                    // logical log needs to be serialized\n                    let locked = self.commit_coordinator.pager_commit_lock.write();\n                    if !locked {\n                        return Ok(TransitionResult::Io(IOCompletions::Single(\n                            Completion::new_yield(),\n                        )));\n                    }\n                    let tx = mvcc_store\n                        .txs\n                        .get(&self.tx_id)\n                        .ok_or_else(|| LimboError::NoSuchTransactionID(self.tx_id.to_string()))?;\n                    tx.value()\n                        .pager_commit_lock_held\n                        .store(true, Ordering::Release);\n                }\n                let (c, append_bytes) = mvcc_store.storage.log_tx(log_record, None)?;\n                self.pending_log_append_bytes = Some(append_bytes);\n                self.state = CommitState::SyncLogicalLog { end_ts: *end_ts };\n                // if Completion Completed without errors we can continue\n                if c.succeeded() {\n                    Ok(TransitionResult::Continue)\n                } else {\n                    Ok(TransitionResult::Io(IOCompletions::Single(c)))\n                }\n            }\n\n            CommitState::SyncLogicalLog { end_ts } => {\n                // Skip fsync when synchronous mode is not FULL.\n                // NORMAL mode skips fsync on commit (but still fsyncs on checkpoint).\n                if self.sync_mode != SyncMode::Full {\n                    tracing::debug!(\"Skipping fsync of logical log (synchronous!=full)\");\n                    self.state = CommitState::EndCommitLogicalLog { end_ts: *end_ts };\n                    return Ok(TransitionResult::Continue);\n                }\n                let c = mvcc_store.storage.sync(self.pager.get_sync_type())?;\n                self.state = CommitState::EndCommitLogicalLog { end_ts: *end_ts };\n                // if Completion Completed without errors we can continue\n                if c.succeeded() {\n                    Ok(TransitionResult::Continue)\n                } else {\n                    Ok(TransitionResult::Io(IOCompletions::Single(c)))\n                }\n            }\n            CommitState::EndCommitLogicalLog { end_ts } => {\n                let connection = self.connection.clone();\n                let schema_did_change = self.did_commit_schema_change;\n                if schema_did_change {\n                    let schema = connection.schema.read().clone();\n                    connection.db.update_schema_if_newer(schema);\n                }\n                let tx = mvcc_store\n                    .txs\n                    .get(&self.tx_id)\n                    .ok_or_else(|| LimboError::NoSuchTransactionID(self.tx_id.to_string()))?;\n                let tx_unlocked = tx.value();\n                self.header.write().replace(*tx_unlocked.header.read());\n                tracing::trace!(\"end_commit_logical_log(tx_id={})\", self.tx_id);\n                self.state = CommitState::CommitEnd { end_ts: *end_ts };\n                return Ok(TransitionResult::Continue);\n            }\n            CommitState::CommitEnd { end_ts } => {\n                // Order of operations matters here:\n                // 1. Advance logical log writer offset (makes the written bytes \"owned\")\n                // 2. Mark transaction Committed\n                // 3. Rewrite live row versions from TxID to Timestamp\n                // 4. Notify dependents\n                // 5. Release commit lock (allows next committer)\n                // 6. Update cached global header\n                //\n                // (1) must precede (5): the commit lock serializes log writes, and\n                // log_tx() writes at the current offset. If we released the lock before\n                // advancing, the next committer would overwrite our bytes.\n                //\n                // (2) must precede (3): rewriting before marking Committed would\n                // publish the transaction's effects to readers before its fate is\n                // decided, which breaks rollback of abandoned commits.\n                //\n                // (2) must also precede (5): the next committer's validation (CommitState::Commit)\n                // checks our transaction state. If it still sees Preparing instead of\n                // Committed, the tie-breaking logic (lower end_ts wins) applies instead\n                // of the definitive \"already committed = conflict\" path.\n                //\n                // pending_log_append_bytes is set in BeginCommitLogicalLog after log_tx\n                // writes to disk. If the commit fails before reaching here (e.g. during\n                // sync), the bytes are never consumed and the in-memory writer offset\n                // stays behind — the next write overwrites the uncommitted bytes.\n                let tx = mvcc_store\n                    .txs\n                    .get(&self.tx_id)\n                    .ok_or_else(|| LimboError::NoSuchTransactionID(self.tx_id.to_string()))?;\n                let tx_unlocked = tx.value();\n                if let Some(append_bytes) = self.pending_log_append_bytes.take() {\n                    mvcc_store\n                        .storage\n                        .advance_logical_log_offset_after_success(append_bytes);\n                }\n                tx_unlocked\n                    .state\n                    .store(TransactionState::Committed(*end_ts));\n\n                self.rewrite_live_versions_to_timestamps(mvcc_store, *end_ts);\n\n                // Hekaton Section 3.3: \"The transaction then processes all outgoing\n                // commit dependencies listed in its CommitDepSet. If it committed, it\n                // decrements the target transaction's CommitDepCounter.\"\n                // IOW since this txn committed, let's signal waiting transactions.\n                let dependents = std::mem::take(&mut *tx_unlocked.commit_dep_set.lock());\n                for dep_tx_id in dependents {\n                    if let Some(dep_tx_entry) = mvcc_store.txs.get(&dep_tx_id) {\n                        dep_tx_entry\n                            .value()\n                            .commit_dep_counter\n                            .fetch_sub(1, Ordering::AcqRel);\n                    }\n                }\n\n                mvcc_store.unlock_commit_lock_if_held(tx_unlocked);\n\n                mvcc_store\n                    .global_header\n                    .write()\n                    .replace(*tx_unlocked.header.read());\n\n                mvcc_store\n                    .last_committed_tx_ts\n                    .store(*end_ts, Ordering::Release);\n                if self.did_commit_schema_change {\n                    mvcc_store\n                        .last_committed_schema_change_ts\n                        .store(*end_ts, Ordering::Release);\n                }\n\n                // We have now updated all the versions with a reference to the\n                // transaction ID to a timestamp and can, therefore, remove the\n                // transaction.\n                mvcc_store.remove_tx(self.tx_id);\n\n                if mvcc_store.is_exclusive_tx(&self.tx_id) {\n                    mvcc_store.release_exclusive_tx(&self.tx_id);\n                }\n                if mvcc_store.storage.should_checkpoint() {\n                    let state_machine = StateMachine::new(CheckpointStateMachine::new(\n                        self.pager.clone(),\n                        mvcc_store.clone(),\n                        self.connection.clone(),\n                        false,\n                        self.connection.get_sync_mode(),\n                    ));\n                    let state_machine = Mutex::new(state_machine);\n                    self.state = CommitState::Checkpoint { state_machine };\n                    return Ok(TransitionResult::Continue);\n                }\n                tracing::trace!(\"logged(tx_id={}, end_ts={})\", self.tx_id, *end_ts);\n                self.finalize(mvcc_store)?;\n                Ok(TransitionResult::Done(()))\n            }\n            CommitState::Checkpoint { state_machine } => {\n                let step_result = {\n                    let mut sm = state_machine.lock();\n                    sm.step(&())\n                };\n                match step_result {\n                    Ok(IOResult::Done(_)) => {}\n                    Ok(IOResult::IO(iocompletions)) => {\n                        return Ok(TransitionResult::Io(iocompletions));\n                    }\n                    Err(err) => {\n                        // Auto-checkpoint errors should not surface to the committed statement.\n                        tracing::debug!(\"MVCC auto-checkpoint failed: {err}\");\n                        self.finalize(mvcc_store)?;\n                        return Ok(TransitionResult::Done(()));\n                    }\n                }\n                self.finalize(mvcc_store)?;\n                return Ok(TransitionResult::Done(()));\n            }\n        }\n    }\n\n    fn finalize(&mut self, _context: &Self::Context) -> Result<()> {\n        self.is_finalized = true;\n        Ok(())\n    }\n\n    fn is_finalized(&self) -> bool {\n        self.is_finalized\n    }\n}\n\nimpl StateTransition for WriteRowStateMachine {\n    type Context = ();\n    type SMResult = ();\n\n    #[tracing::instrument(fields(state = ?self.state), skip(self, _context), level = Level::DEBUG)]\n    fn step(&mut self, _context: &Self::Context) -> Result<TransitionResult<Self::SMResult>> {\n        use crate::types::{IOResult, SeekKey, SeekOp};\n\n        match self.state {\n            WriteRowState::Initial => {\n                // Create the record and key\n                self.record = if self.row.is_index_row() {\n                    None\n                } else {\n                    let row_data = self.row.data.as_ref().expect(\"table rows should have data\");\n                    let mut record = ImmutableRecord::new(row_data.len());\n                    record.start_serialization(row_data);\n                    Some(record)\n                };\n                if self.requires_seek {\n                    self.state = WriteRowState::Seek;\n                } else {\n                    self.state = WriteRowState::Insert;\n                }\n                Ok(TransitionResult::Continue)\n            }\n            WriteRowState::Seek => {\n                // Position the cursor by seeking to the row position\n                let seek_key = match &self.row.id.row_id {\n                    RowKey::Int(row_id) => SeekKey::TableRowId(*row_id),\n                    RowKey::Record(record) => SeekKey::IndexKey(&record.key),\n                };\n\n                match self\n                    .cursor\n                    .write()\n                    .seek(seek_key, SeekOp::GE { eq_only: true })?\n                {\n                    IOResult::Done(_) => {}\n                    IOResult::IO(io) => {\n                        return Ok(TransitionResult::Io(io));\n                    }\n                }\n                turso_assert_eq!(self.cursor.write().valid_state, CursorValidState::Valid);\n                self.state = WriteRowState::Insert;\n                Ok(TransitionResult::Continue)\n            }\n            WriteRowState::Insert => {\n                // Insert the record into the B-tree\n                let key = match &self.row.id.row_id {\n                    RowKey::Int(row_id) => BTreeKey::new_table_rowid(*row_id, self.record.as_ref()),\n                    RowKey::Record(record) => BTreeKey::new_index_key(&record.key),\n                };\n\n                match self\n                    .cursor\n                    .write()\n                    .insert(&key)\n                    .map_err(|e: LimboError| LimboError::InternalError(e.to_string()))?\n                {\n                    IOResult::Done(()) => {}\n                    IOResult::IO(io) => {\n                        return Ok(TransitionResult::Io(io));\n                    }\n                }\n                self.state = WriteRowState::Next;\n                Ok(TransitionResult::Continue)\n            }\n            WriteRowState::Next => {\n                match self\n                    .cursor\n                    .write()\n                    .next()\n                    .map_err(|e: LimboError| LimboError::InternalError(e.to_string()))?\n                {\n                    IOResult::Done(_) => {}\n                    IOResult::IO(io) => {\n                        return Ok(TransitionResult::Io(io));\n                    }\n                }\n                self.finalize(&())?;\n                Ok(TransitionResult::Done(()))\n            }\n        }\n    }\n\n    fn finalize(&mut self, _context: &Self::Context) -> Result<()> {\n        self.is_finalized = true;\n        Ok(())\n    }\n\n    fn is_finalized(&self) -> bool {\n        self.is_finalized\n    }\n}\n\nimpl StateTransition for DeleteRowStateMachine {\n    type Context = ();\n    type SMResult = ();\n\n    #[tracing::instrument(fields(state = ?self.state), skip(self, _context))]\n    fn step(&mut self, _context: &Self::Context) -> Result<TransitionResult<Self::SMResult>> {\n        use crate::types::{IOResult, SeekKey, SeekOp};\n\n        match self.state {\n            DeleteRowState::Initial => {\n                self.state = DeleteRowState::Seek;\n                Ok(TransitionResult::Continue)\n            }\n            DeleteRowState::Seek => {\n                let seek_key = match &self.rowid.row_id {\n                    RowKey::Int(row_id) => SeekKey::TableRowId(*row_id),\n                    RowKey::Record(record) => SeekKey::IndexKey(&record.key),\n                };\n\n                match self\n                    .cursor\n                    .write()\n                    .seek(seek_key, SeekOp::GE { eq_only: true })?\n                {\n                    IOResult::Done(seek_res) => {\n                        match seek_res {\n                            SeekResult::Found => {\n                                self.state = DeleteRowState::Delete;\n                            }\n                            SeekResult::TryAdvance => {\n                                // In index B-trees, the key can reside in an interior node\n                                // rather than a leaf. The seek descends to the leaf but\n                                // doesn't find it there, returning TryAdvance. Advancing\n                                // the cursor will move up to the interior cell.\n                                self.state = DeleteRowState::Advance;\n                            }\n                            SeekResult::NotFound => {\n                                crate::bail_corrupt_error!(\n                                    \"MVCC delete: rowid {} not found\",\n                                    self.rowid.row_id\n                                );\n                            }\n                        }\n                        Ok(TransitionResult::Continue)\n                    }\n                    IOResult::IO(io) => {\n                        return Ok(TransitionResult::Io(io));\n                    }\n                }\n            }\n            DeleteRowState::Advance => {\n                let next_result = self.cursor.write().next()?;\n                match next_result {\n                    IOResult::Done(()) => {\n                        if !self.cursor.read().has_record() {\n                            crate::bail_corrupt_error!(\n                                \"MVCC delete: rowid {} not found after advance\",\n                                self.rowid.row_id\n                            );\n                        }\n                        self.state = DeleteRowState::Delete;\n                        Ok(TransitionResult::Continue)\n                    }\n                    IOResult::IO(io) => {\n                        return Ok(TransitionResult::Io(io));\n                    }\n                }\n            }\n            DeleteRowState::Delete => {\n                // Insert the record into the B-tree\n\n                match self\n                    .cursor\n                    .write()\n                    .delete()\n                    .map_err(|e| LimboError::InternalError(e.to_string()))?\n                {\n                    IOResult::Done(()) => {}\n                    IOResult::IO(io) => {\n                        return Ok(TransitionResult::Io(io));\n                    }\n                }\n                tracing::trace!(\n                    \"delete_row_from_pager(table_id={}, row_id={})\",\n                    self.rowid.table_id,\n                    self.rowid.row_id\n                );\n                self.finalize(&())?;\n                Ok(TransitionResult::Done(()))\n            }\n        }\n    }\n\n    fn finalize(&mut self, _context: &Self::Context) -> Result<()> {\n        self.is_finalized = true;\n        Ok(())\n    }\n\n    fn is_finalized(&self) -> bool {\n        self.is_finalized\n    }\n}\n\nimpl DeleteRowStateMachine {\n    fn new(rowid: RowID, cursor: Arc<RwLock<BTreeCursor>>) -> Self {\n        Self {\n            state: DeleteRowState::Initial,\n            is_finalized: false,\n            rowid,\n            cursor,\n        }\n    }\n}\n\npub const SQLITE_SCHEMA_MVCC_TABLE_ID: MVTableId = MVTableId(-1);\npub(crate) const MVCC_META_TABLE_NAME: &str = \"__turso_internal_mvcc_meta\";\n/// Indicates the maximum transaction timestamp that has been made durable in the WAL.\n/// Used to determine the replay boundary for recovery; only records with a higher timestamp\n/// are replayed.\npub(crate) const MVCC_META_KEY_PERSISTENT_TX_TS_MAX: &str = \"persistent_tx_ts_max\";\n\n#[derive(Debug)]\npub struct RowidAllocator {\n    /// Exclusive lock serializing initialization (btree max read → store).\n    /// Only held during the first NewRowid for a table; after that, the\n    /// fast path is lock-free (atomic CAS on max_rowid).\n    lock: TursoRwLock,\n    /// Monotonically increasing counter. 0 = empty table (rowids start at 1).\n    /// Updated via atomic CAS — no RwLock needed on the fast path.\n    max_rowid: AtomicI64,\n    /// True after the first btree-max scan. Never reset to false.\n    initialized: AtomicBool,\n}\n\n/// A multi-version concurrency control database.\n#[derive(Debug)]\npub struct MvStore<Clock: LogicalClock> {\n    pub rows: SkipMap<RowID, RwLock<Vec<RowVersion>>>,\n    /// Table ID is an opaque identifier that is only meaningful to the MV store.\n    /// Each checkpointed MVCC table corresponds to a single B-tree on the pager,\n    /// which naturally has a root page.\n    /// We cannot use root page as the MVCC table ID directly because:\n    /// - We assign table IDs during MVCC commit, but\n    /// - we commit pages to the pager only during checkpoint\n    ///\n    /// which means the root page is not easily knowable ahead of time.\n    /// Hence, we store the mapping here.\n    /// The value is Option because tables created in an MVCC commit that have not\n    /// been checkpointed yet have no real root page assigned yet.\n    pub table_id_to_rootpage: SkipMap<MVTableId, Option<u64>>,\n    /// Unlike table rows which are stored in a single map, we have a separate map for every index\n    /// because operations like last() on an index are much easier when we don't have to take the\n    /// table identifier into account.\n    pub index_rows: SkipMap<MVTableId, SkipMap<Arc<SortableIndexKey>, RwLock<Vec<RowVersion>>>>,\n    txs: SkipMap<TxID, Transaction>,\n    /// Final state for removed transactions. Readers may still race with stale TxID\n    /// references in row versions after a transaction is removed from `txs`.\n    finalized_tx_states: SkipMap<TxID, TransactionState>,\n    tx_ids: AtomicU64,\n    version_id_counter: AtomicU64,\n    next_rowid: AtomicU64,\n    next_table_id: AtomicI64,\n    clock: Clock,\n\n    /// MVCC durable storage (logical log writes, checkpoint thresholding, recovery state).\n    ///\n    /// Stored behind a trait object so callers can inject their own implementation\n    /// per database (via `Database::durable_storage`) for testing or custom durability.\n    storage: Arc<dyn crate::mvcc::persistent_storage::DurableStorage>,\n\n    /// The transaction ID of a transaction that has acquired an exclusive write lock, if any.\n    ///\n    /// An exclusive MVCC transaction is one that has a write lock on the pager, which means\n    /// every other MVCC transaction must wait for it to commit before they can commit. We have\n    /// exclusive transactions to support single-writer semantics for compatibility with SQLite.\n    ///\n    /// If there is no exclusive transaction, the field is set to `NO_EXCLUSIVE_TX`.\n    exclusive_tx: AtomicU64,\n    commit_coordinator: Arc<CommitCoordinator>,\n    global_header: Arc<RwLock<Option<DatabaseHeader>>>,\n    /// MVCC checkpoints are always TRUNCATE, plus they block all other transactions.\n    /// This guarantees that never need to let transactions read from the SQLite WAL.\n    /// In MVCC, the checkpoint procedure is roughly as follows:\n    /// - Take the blocking_checkpoint_lock\n    /// - Write everything in the logical log to the pager, and from there commit to the SQLite WAL.\n    /// - Immediately TRUNCATE checkpoint the WAL into the database file.\n    /// - Release the blocking_checkpoint_lock.\n    blocking_checkpoint_lock: Arc<TursoRwLock>,\n    /// The highest transaction ID that has been made durable in the WAL.\n    /// Used to skip checkpointing transactions from mv store to WAL that have already been processed.\n    durable_txid_max: AtomicU64,\n    /// The timestamp of the last committed schema change.\n    /// Schema changes always cause a [SchemaUpdated] error.\n    last_committed_schema_change_ts: AtomicU64,\n    /// The timestamp of the last committed transaction.\n    /// If there are two concurrent BEGIN (non-CONCURRENT) transactions, and one tries to promote\n    /// to exclusive, it will abort if another transaction committed after its begin timestamp.\n    last_committed_tx_ts: AtomicU64,\n    table_id_to_last_rowid: RwLock<HashMap<MVTableId, Arc<RowidAllocator>>>,\n}\n\nimpl<Clock: LogicalClock> MvStore<Clock> {\n    fn uses_durable_mvcc_metadata(&self, connection: &Arc<Connection>) -> bool {\n        !connection.db.path.starts_with(\":memory:\")\n    }\n\n    /// Captures table-valued functions (e.g. generate_series) from the schema before\n    /// reparse_schema() drops them. Built-in TVFs are registered programmatically and\n    /// don't survive schema re-parsing from sqlite_schema; we save and re-inject them.\n    fn capture_table_valued_functions(schema: &Schema) -> Vec<Arc<crate::vtab::VirtualTable>> {\n        schema\n            .tables\n            .values()\n            .filter_map(|table| match table.as_ref() {\n                Table::Virtual(vtab)\n                    if matches!(vtab.kind, turso_ext::VTabKind::TableValuedFunction) =>\n                {\n                    Some(vtab.clone())\n                }\n                _ => None,\n            })\n            .collect()\n    }\n\n    fn rehydrate_table_valued_functions(\n        schema: &mut Schema,\n        table_valued_functions: &[Arc<crate::vtab::VirtualTable>],\n    ) {\n        for vtab in table_valued_functions {\n            let normalized_name = crate::util::normalize_ident(&vtab.name);\n            schema\n                .tables\n                .entry(normalized_name)\n                .or_insert_with(|| Arc::new(Table::Virtual(vtab.clone())));\n        }\n    }\n\n    fn rehydrate_connection_table_valued_functions(\n        &self,\n        connection: &Arc<Connection>,\n        table_valued_functions: &[Arc<crate::vtab::VirtualTable>],\n    ) {\n        connection.with_schema_mut(|schema| {\n            Self::rehydrate_table_valued_functions(schema, table_valued_functions);\n        });\n        *connection.db.schema.lock() = connection.schema.read().clone();\n    }\n\n    /// Creates a new database.\n    pub fn new(\n        clock: Clock,\n        storage: Arc<dyn crate::mvcc::persistent_storage::DurableStorage>,\n    ) -> Self {\n        Self {\n            rows: SkipMap::new(),\n            table_id_to_rootpage: SkipMap::from_iter(vec![(SQLITE_SCHEMA_MVCC_TABLE_ID, Some(1))]), // table id 1 / root page 1 is always sqlite_schema.\n            index_rows: SkipMap::new(),\n            txs: SkipMap::new(),\n            finalized_tx_states: SkipMap::new(),\n            tx_ids: AtomicU64::new(1), // let's reserve transaction 0 for special purposes\n            version_id_counter: AtomicU64::new(1), // Reserve 0 for special purposes\n            next_rowid: AtomicU64::new(0), // TODO: determine this from B-Tree\n            next_table_id: AtomicI64::new(-2), // table id -1 / root page 1 is always sqlite_schema.\n            clock,\n            storage,\n            exclusive_tx: AtomicU64::new(NO_EXCLUSIVE_TX),\n            commit_coordinator: Arc::new(CommitCoordinator::new()),\n            global_header: Arc::new(RwLock::new(None)),\n            blocking_checkpoint_lock: Arc::new(TursoRwLock::new()),\n            durable_txid_max: AtomicU64::new(0),\n            last_committed_schema_change_ts: AtomicU64::new(0),\n            last_committed_tx_ts: AtomicU64::new(0),\n            table_id_to_last_rowid: RwLock::new(HashMap::default()),\n        }\n    }\n\n    /// Get the table ID from the root page.\n    /// If the root page is negative, it is a non-checkpointed table and the table ID and root page are both the same negative value.\n    /// If the root page is positive, it is a checkpointed table and there should be a corresponding table ID.\n    pub fn get_table_id_from_root_page(&self, root_page: i64) -> MVTableId {\n        if root_page < 0 {\n            // Not checkpointed table - table ID and root_page are both the same negative value\n            root_page.into()\n        } else {\n            // Root page is positive: it is a checkpointed table and there should be a corresponding table ID\n            let root_page = root_page as u64;\n            let table_id = self\n                .table_id_to_rootpage\n                .iter()\n                .find(|entry| entry.value().is_some_and(|value| value == root_page))\n                .map(|entry| *entry.key())\n                .unwrap_or_else(|| {\n                    panic!(\"Positive root page is not mapped to a table id: {root_page}\")\n                });\n            table_id\n        }\n    }\n\n    /// Insert a table ID and root page mapping.\n    /// Root page must be positive here, because we only invoke this method with Some() for checkpointed tables.\n    pub fn insert_table_id_to_rootpage(&self, table_id: MVTableId, root_page: Option<u64>) {\n        self.table_id_to_rootpage.insert(table_id, root_page);\n        let minimum: i64 = if let Some(root_page) = root_page {\n            // On recovery, we assign table_id = -root_page. Let's make sure we don't get any clashes between checkpointed and non-checkpointed tables\n            // E.g. if we checkpoint a table that has physical root page 7, let's require the next table_id to be less than -7 (or if table_id is already smaller, then smaller than that.)\n            let root_page_as_table_id = MVTableId::from(-(root_page as i64));\n            table_id.min(root_page_as_table_id).into()\n        } else {\n            table_id.into()\n        };\n        if minimum <= self.next_table_id.load(Ordering::SeqCst) {\n            self.next_table_id.store(minimum - 1, Ordering::SeqCst);\n        }\n    }\n\n    /// Creates the `__turso_internal_mvcc_meta` table and seeds it with\n    /// `persistent_tx_ts_max` (initialized to 0). This table stores the durable replay\n    /// boundary: on recovery, only logical-log frames with `commit_ts > persistent_tx_ts_max`\n    /// are replayed. Called once during first MVCC bootstrap.\n    fn initialize_mvcc_metadata_table(&self, connection: &Arc<Connection>) -> Result<()> {\n        connection.execute(format!(\n            \"CREATE TABLE IF NOT EXISTS {MVCC_META_TABLE_NAME}(k TEXT, v INTEGER NOT NULL)\"\n        ))?;\n        connection.execute(format!(\n            \"INSERT OR IGNORE INTO {MVCC_META_TABLE_NAME}(rowid, k, v) VALUES (1, '{MVCC_META_KEY_PERSISTENT_TX_TS_MAX}', 0)\"\n        ))?;\n        Ok(())\n    }\n\n    /// Read the persistent transaction timestamp maximum from the MVCC metadata table.\n    fn try_read_persistent_tx_ts_max(&self, connection: &Arc<Connection>) -> Result<Option<u64>> {\n        let query_result = connection.query(format!(\n            \"SELECT v FROM {MVCC_META_TABLE_NAME}\n             WHERE k = '{MVCC_META_KEY_PERSISTENT_TX_TS_MAX}'\"\n        ));\n        let maybe_stmt = match query_result {\n            Ok(stmt) => stmt,\n            Err(LimboError::ParseError(msg)) if msg.contains(\"no such table\") => return Ok(None),\n            Err(err) => {\n                return Err(LimboError::Corrupt(format!(\n                    \"Failed to read MVCC metadata table: {err}\"\n                )))\n            }\n        };\n        let mut value: Option<i64> = None;\n        if let Some(mut stmt) = maybe_stmt {\n            stmt.run_with_row_callback(|row| {\n                value = Some(row.get::<i64>(0)?);\n                Ok(())\n            })?;\n        }\n\n        let value = value.ok_or_else(|| {\n            LimboError::Corrupt(format!(\n                \"Missing MVCC metadata row for key {MVCC_META_KEY_PERSISTENT_TX_TS_MAX}\"\n            ))\n        })?;\n\n        if value < 0 {\n            return Err(LimboError::Corrupt(format!(\n                \"Invalid MVCC metadata value for {MVCC_META_KEY_PERSISTENT_TX_TS_MAX}: {value}\"\n            )));\n        }\n        Ok(Some(value as u64))\n    }\n\n    /// Bootstrap the MV store from the SQLite schema table and logical log.\n    /// 1. Get all root pages from the already parsed schema object\n    /// 2. Assign table IDs to the root pages (table_id = -1 * root_page)\n    /// 3. Complete interrupted WAL/log checkpoint reconciliation, if needed\n    /// 4. Promote the bootstrap connection to a regular connection so that it reads from the MV store again\n    /// 5. Recover the logical log\n    /// 6. Make sure schema changes reflected from deserialized logical log are captured in the schema\n    pub fn bootstrap(&self, bootstrap_conn: Arc<Connection>) -> Result<()> {\n        let preserved_table_valued_functions =\n            Self::capture_table_valued_functions(&bootstrap_conn.schema.read());\n        self.maybe_complete_interrupted_checkpoint(&bootstrap_conn)?;\n        bootstrap_conn.reparse_schema()?;\n        self.rehydrate_connection_table_valued_functions(\n            &bootstrap_conn,\n            &preserved_table_valued_functions,\n        );\n\n        if self.uses_durable_mvcc_metadata(&bootstrap_conn) {\n            match self.try_read_persistent_tx_ts_max(&bootstrap_conn)? {\n                Some(_) => {}\n                None => {\n                    let log_size = self.get_logical_log_file().size()?;\n                    let pager = bootstrap_conn.pager.load().clone();\n                    if !pager.is_encryption_enabled() {\n                        if bootstrap_conn.db.is_readonly() {\n                            return Err(LimboError::Corrupt(\n                                \"Missing MVCC metadata table in read-only mode\".to_string(),\n                            ));\n                        }\n                        if log_size > LOG_HDR_SIZE as u64 {\n                            return Err(LimboError::Corrupt(\n                                \"Missing MVCC metadata table while logical log state exists\"\n                                    .to_string(),\n                            ));\n                        }\n                        // First-time MVCC bootstrap: ensure a durable logical-log header exists\n                        // before any metadata-table writes can commit into WAL.\n                        // If a previous crash left a torn header tail (0 < size < LOG_HDR_SIZE),\n                        // clear it before rewriting the header.\n                        if log_size > 0 && log_size < LOG_HDR_SIZE as u64 {\n                            let log_file = self.get_logical_log_file();\n                            let c = log_file.truncate(0, Completion::new_trunc(|_| {}))?;\n                            bootstrap_conn.db.io.wait_for_completion(c)?;\n                        }\n                        if log_size <= LOG_HDR_SIZE as u64 {\n                            let pager = bootstrap_conn.pager.load().clone();\n                            let c = self.storage.update_header()?;\n                            pager.io.wait_for_completion(c)?;\n                            if bootstrap_conn.get_sync_mode() != SyncMode::Off {\n                                let c = self.storage.sync(pager.get_sync_type())?;\n                                pager.io.wait_for_completion(c)?;\n                            }\n                        }\n                        self.initialize_mvcc_metadata_table(&bootstrap_conn)?;\n                        // Metadata bootstrap writes land in SQLite WAL first; reconcile immediately so\n                        // subsequent opens (including read-only opens) do not depend on WAL replay.\n                        self.maybe_complete_interrupted_checkpoint(&bootstrap_conn)?;\n                    }\n                }\n            }\n        }\n\n        {\n            let schema = bootstrap_conn.schema.read();\n            let sqlite_schema_root_pages = {\n                schema\n                    .tables\n                    .values()\n                    .filter_map(|t| {\n                        if let Table::BTree(btree) = t.as_ref() {\n                            Some(btree.root_page)\n                        } else {\n                            None\n                        }\n                    })\n                    .chain(\n                        schema\n                            .indexes\n                            .values()\n                            .flatten()\n                            .map(|index| index.root_page),\n                    )\n            };\n            // Map all existing checkpointed root pages to table ids so that if root_page=R, table_id=-R\n            for root_page in sqlite_schema_root_pages {\n                turso_assert!(root_page > 0, \"root_page={root_page} must be positive\");\n                let root_page_as_table_id = MVTableId::from(-(root_page));\n                self.insert_table_id_to_rootpage(root_page_as_table_id, Some(root_page as u64));\n            }\n        }\n\n        // Recover logical log while bootstrap connection still reads from pager-backed schema.\n        // This lets recovery merge checkpointed sqlite_schema rows with non-checkpointed rows from log replay.\n        // Return value indicates whether recovery replayed any frames; unused here because\n        // global_header initialization below is unconditional (guarded by is_none() instead).\n        // The return value is still used by tests to verify recovery behavior.\n        let _recovered = self.maybe_recover_logical_log(bootstrap_conn.clone())?;\n\n        // Recovery is done, switch back to regular MVCC reads.\n        bootstrap_conn.promote_to_regular_connection();\n        if self.global_header.read().is_none() {\n            let pager = bootstrap_conn.pager.load();\n            let header = pager\n                .io\n                .block(|| pager.with_header(|header| *header))\n                .expect(\"failed to read database header\");\n            self.global_header.write().replace(header);\n        }\n\n        Ok(())\n    }\n\n    /// MVCC does not use the pager/btree cursors to create pages until checkpoint.\n    /// This method is used to assign root page numbers when Insn::CreateBtree is used.\n    /// MVCC table ids are always negative. Their corresponding rootpage entry in sqlite_schema\n    /// is the same negative value if the table has not been checkpointed yet. Otherwise, the root page\n    /// will be positive and corresponds to the actual physical page.\n    pub fn get_next_table_id(&self) -> i64 {\n        self.next_table_id.fetch_sub(1, Ordering::SeqCst)\n    }\n\n    pub fn get_next_rowid(&self) -> i64 {\n        self.next_rowid.fetch_add(1, Ordering::SeqCst) as i64\n    }\n\n    /// Inserts a new row into a table in the database.\n    ///\n    /// This function inserts a new `row` into the database within the context\n    /// of the transaction `tx_id`.\n    ///\n    /// # Arguments\n    ///\n    /// * `tx_id` - the ID of the transaction in which to insert the new row.\n    /// * `row` - the row object containing the values to be inserted.\n    ///\n    pub fn insert(&self, tx_id: TxID, row: Row) -> Result<()> {\n        self.insert_to_table_or_index(tx_id, row, None)\n    }\n\n    /// Same as insert() but can insert to a table or an index, indicated by the `maybe_index_id` argument.\n    pub fn insert_to_table_or_index(\n        &self,\n        tx_id: TxID,\n        row: Row,\n        maybe_index_id: Option<MVTableId>,\n    ) -> Result<()> {\n        tracing::trace!(\"insert(tx_id={}, row.id={:?})\", tx_id, row.id);\n        let tx = self\n            .txs\n            .get(&tx_id)\n            .ok_or_else(|| LimboError::NoSuchTransactionID(tx_id.to_string()))?;\n        let tx = tx.value();\n        turso_assert_eq!(tx.state, TransactionState::Active);\n        let id = row.id.clone();\n        match maybe_index_id {\n            Some(index_id) => {\n                let version_id = self.get_version_id();\n                let row_version = RowVersion {\n                    id: version_id,\n                    begin: Some(TxTimestampOrID::TxID(tx.tx_id)),\n                    end: None,\n                    row: row.clone(),\n                    btree_resident: false,\n                };\n                let RowKey::Record(sortable_key) = row.id.row_id else {\n                    panic!(\"Index writes must be to a record\");\n                };\n                let sortable_key = self.get_or_create_index_key_arc(index_id, sortable_key);\n                tx.insert_to_write_set(id);\n                tx.record_created_index_version((index_id, sortable_key.clone()), version_id);\n                self.insert_index_version(index_id, sortable_key, row_version);\n            }\n            None => {\n                // NOTE: We do NOT check for conflicts at insert time (pure optimistic).\n                // Conflicts are detected at commit time using end_ts comparison.\n                // This allows multiple transactions to insert the same rowid,\n                // with first-committer-wins semantics.\n\n                let version_id = self.get_version_id();\n                let row_version = RowVersion {\n                    id: version_id,\n                    begin: Some(TxTimestampOrID::TxID(tx.tx_id)),\n                    end: None,\n                    row,\n                    btree_resident: false,\n                };\n                tx.insert_to_write_set(id.clone());\n                tx.record_created_table_version(id.clone(), version_id);\n                let allocator = self.get_rowid_allocator(&id.table_id);\n                allocator.insert_row_id_maybe_update(id.row_id.to_int_or_panic());\n                self.insert_version(id, row_version);\n            }\n        }\n        Ok(())\n    }\n\n    /// Inserts a deletion record for a row that does not currently have any versions in the MV store.\n    /// This is used in cases where the BTree contains that record, but it is logically deleted.\n    pub fn insert_tombstone_to_table_or_index(\n        &self,\n        tx_id: TxID,\n        id: RowID,\n        row: Row,\n        maybe_index_id: Option<MVTableId>,\n    ) -> Result<()> {\n        let version_id = self.get_version_id();\n        let row_version = RowVersion {\n            id: version_id,\n            // Tombstones over B-tree-resident rows have no MVCC creator begin.\n            // They invalidate B-tree visibility via end timestamp only.\n            begin: None,\n            end: Some(TxTimestampOrID::TxID(tx_id)),\n            row: row.clone(),\n            btree_resident: true,\n        };\n        let tx = self\n            .txs\n            .get(&tx_id)\n            .ok_or_else(|| LimboError::NoSuchTransactionID(tx_id.to_string()))?;\n        let tx = tx.value();\n        tx.insert_to_write_set(id.clone());\n        match maybe_index_id {\n            Some(index_id) => {\n                let RowKey::Record(sortable_key) = row.id.row_id else {\n                    panic!(\"Index writes must be to a record\");\n                };\n                let sortable_key = self.get_or_create_index_key_arc(index_id, sortable_key);\n                tx.record_created_index_version((index_id, sortable_key.clone()), version_id);\n                self.insert_index_version(index_id, sortable_key, row_version);\n            }\n            None => {\n                tx.record_created_table_version(id.clone(), version_id);\n                self.insert_version(id, row_version);\n            }\n        }\n        Ok(())\n    }\n\n    /// Inserts a row that was read from the B-tree (not in MvStore).\n    /// This is used when updating a row that exists in B-tree but hasn't been\n    /// modified in MVCC yet. The btree_resident flag helps the checkpoint logic\n    /// determine if subsequent deletes should be checkpointed to the B-tree file.\n    pub fn insert_btree_resident_to_table_or_index(\n        &self,\n        tx_id: TxID,\n        row: Row,\n        maybe_index_id: Option<MVTableId>,\n    ) -> Result<()> {\n        tracing::trace!(\n            \"insert_btree_resident(tx_id={}, row.id={:?})\",\n            tx_id,\n            row.id\n        );\n        let tx = self\n            .txs\n            .get(&tx_id)\n            .ok_or_else(|| LimboError::NoSuchTransactionID(tx_id.to_string()))?;\n        let tx = tx.value();\n        turso_assert_eq!(tx.state, TransactionState::Active);\n        let id = row.id.clone();\n        match maybe_index_id {\n            Some(index_id) => {\n                let version_id = self.get_version_id();\n                let row_version = RowVersion {\n                    id: version_id,\n                    begin: Some(TxTimestampOrID::TxID(tx.tx_id)),\n                    end: None,\n                    row: row.clone(),\n                    btree_resident: true,\n                };\n                let RowKey::Record(sortable_key) = row.id.row_id else {\n                    panic!(\"Index writes must be to a record\");\n                };\n                let sortable_key = self.get_or_create_index_key_arc(index_id, sortable_key);\n                tx.insert_to_write_set(id);\n                tx.record_created_index_version((index_id, sortable_key.clone()), version_id);\n                self.insert_index_version(index_id, sortable_key, row_version);\n            }\n            None => {\n                let version_id = self.get_version_id();\n                let row_version = RowVersion {\n                    id: version_id,\n                    begin: Some(TxTimestampOrID::TxID(tx.tx_id)),\n                    end: None,\n                    row,\n                    btree_resident: true,\n                };\n                tx.insert_to_write_set(id.clone());\n                tx.record_created_table_version(id.clone(), version_id);\n                self.insert_version(id, row_version);\n            }\n        }\n        Ok(())\n    }\n\n    /// Updates a row in a table in the database with new values.\n    ///\n    /// This function updates an existing row in the database within the\n    /// context of the transaction `tx_id`. The `row` argument identifies the\n    /// row to be updated as `id` and contains the new values to be inserted.\n    ///\n    /// If the row identified by the `id` does not exist, this function does\n    /// nothing and returns `false`. Otherwise, the function updates the row\n    /// with the new values and returns `true`.\n    ///\n    /// # Arguments\n    ///\n    /// * `tx_id` - the ID of the transaction in which to update the new row.\n    /// * `row` - the row object containing the values to be updated.\n    ///\n    /// # Returns\n    ///\n    /// Returns `true` if the row was successfully updated, and `false` otherwise.\n    pub fn update(&self, tx_id: TxID, row: Row) -> Result<bool> {\n        self.update_to_table_or_index(tx_id, row, None)\n    }\n\n    /// Same as update() but can update a table or an index, indicated by the `maybe_index_id` argument.\n    pub fn update_to_table_or_index(\n        &self,\n        tx_id: TxID,\n        row: Row,\n        maybe_index_id: Option<MVTableId>,\n    ) -> Result<bool> {\n        tracing::trace!(\"update(tx_id={}, row.id={:?})\", tx_id, row.id);\n        if !self.delete_from_table_or_index(tx_id, row.id.clone(), maybe_index_id)? {\n            return Ok(false);\n        }\n        self.insert_to_table_or_index(tx_id, row, maybe_index_id)?;\n        Ok(true)\n    }\n\n    /// Inserts a row into a table in the database with new values, previously deleting\n    /// any old data if it existed. Bails on a delete error, e.g. write-write conflict.\n    pub fn upsert(&self, tx_id: TxID, row: Row) -> Result<()> {\n        self.upsert_to_table_or_index(tx_id, row, None)\n    }\n\n    /// Same as upsert() but can upsert to a table or an index, indicated by the `maybe_index_id` argument.\n    pub fn upsert_to_table_or_index(\n        &self,\n        tx_id: TxID,\n        row: Row,\n        maybe_index_id: Option<MVTableId>,\n    ) -> Result<()> {\n        tracing::trace!(\"upsert(tx_id={}, row.id={:?})\", tx_id, row.id);\n        self.delete_from_table_or_index(tx_id, row.id.clone(), maybe_index_id)?;\n        self.insert_to_table_or_index(tx_id, row, maybe_index_id)?;\n        Ok(())\n    }\n\n    /// Deletes a row from the table with the given `id`.\n    ///\n    /// This function deletes an existing row `id` in the database within the\n    /// context of the transaction `tx_id`.\n    ///\n    /// # Arguments\n    ///\n    /// * `tx_id` - the ID of the transaction in which to delete the new row.\n    /// * `id` - the ID of the row to delete.\n    ///\n    /// # Returns\n    ///\n    /// Returns `true` if the row was successfully deleted, and `false` otherwise.\n    ///\n    pub fn delete(&self, tx_id: TxID, id: RowID) -> Result<bool> {\n        self.delete_from_table_or_index(tx_id, id, None)\n    }\n\n    /// Same as delete() but can delete from a table or an index, indicated by the `maybe_index_id` argument.\n    pub fn delete_from_table_or_index(\n        &self,\n        tx_id: TxID,\n        id: RowID,\n        maybe_index_id: Option<MVTableId>,\n    ) -> Result<bool> {\n        tracing::trace!(\"delete(tx_id={}, id={:?})\", tx_id, id);\n        match maybe_index_id {\n            Some(index_id) => {\n                let rows = self.index_rows.get_or_insert_with(index_id, SkipMap::new);\n                let rows = rows.value();\n                let RowKey::Record(sortable_key) = id.row_id.clone() else {\n                    panic!(\"Index deletes must have a record row_id\");\n                };\n                let row_versions_opt = rows.get(&sortable_key);\n                if let Some(ref row_versions_entry) = row_versions_opt {\n                    // Get the Arc key from the map entry for savepoint tracking\n                    let arc_key = row_versions_entry.key().clone();\n                    let mut row_versions = row_versions_entry.value().write();\n                    for rv in row_versions.iter_mut().rev() {\n                        let tx = self\n                            .txs\n                            .get(&tx_id)\n                            .ok_or_else(|| LimboError::NoSuchTransactionID(tx_id.to_string()))?;\n                        let tx = tx.value();\n                        turso_assert_eq!(tx.state, TransactionState::Active);\n                        // A transaction cannot delete a version that it cannot see,\n                        // nor can it conflict with it.\n                        if !rv.is_visible_to(tx, &self.txs, &self.finalized_tx_states) {\n                            continue;\n                        }\n                        if is_write_write_conflict(&self.txs, &self.finalized_tx_states, tx, rv) {\n                            turso_assert_reachable!(\"write-write conflict on delete\");\n                            drop(row_versions);\n                            drop(row_versions_opt);\n                            return Err(LimboError::WriteWriteConflict);\n                        }\n\n                        let version_id = rv.id;\n                        rv.end = Some(TxTimestampOrID::TxID(tx.tx_id));\n                        let tx = self\n                            .txs\n                            .get(&tx_id)\n                            .ok_or_else(|| LimboError::NoSuchTransactionID(tx_id.to_string()))?;\n                        let tx = tx.value();\n                        tx.insert_to_write_set(id);\n                        tx.record_deleted_index_version((index_id, arc_key), version_id);\n                        return Ok(true);\n                    }\n                }\n                Ok(false)\n            }\n            None => {\n                let row_versions_opt = self.rows.get(&id);\n                if let Some(ref row_versions) = row_versions_opt {\n                    let mut row_versions = row_versions.value().write();\n                    for rv in row_versions.iter_mut().rev() {\n                        let tx = self\n                            .txs\n                            .get(&tx_id)\n                            .ok_or_else(|| LimboError::NoSuchTransactionID(tx_id.to_string()))?;\n                        let tx = tx.value();\n                        turso_assert_eq!(tx.state, TransactionState::Active);\n                        // A transaction cannot delete a version that it cannot see,\n                        // nor can it conflict with it.\n                        if !rv.is_visible_to(tx, &self.txs, &self.finalized_tx_states) {\n                            continue;\n                        }\n                        if is_write_write_conflict(&self.txs, &self.finalized_tx_states, tx, rv) {\n                            turso_assert_reachable!(\"write-write conflict on delete\");\n                            drop(row_versions);\n                            drop(row_versions_opt);\n                            return Err(LimboError::WriteWriteConflict);\n                        }\n\n                        let version_id = rv.id;\n                        rv.end = Some(TxTimestampOrID::TxID(tx.tx_id));\n                        drop(row_versions);\n                        drop(row_versions_opt);\n                        let tx = self\n                            .txs\n                            .get(&tx_id)\n                            .ok_or_else(|| LimboError::NoSuchTransactionID(tx_id.to_string()))?;\n                        let tx = tx.value();\n                        tx.insert_to_write_set(id.clone());\n                        tx.record_deleted_table_version(id.clone(), version_id);\n                        return Ok(true);\n                    }\n                }\n                Ok(false)\n            }\n        }\n    }\n\n    /// Retrieves a row from the table with the given `id`.\n    ///\n    /// This operation is performed within the scope of the transaction identified\n    /// by `tx_id`.\n    ///\n    /// # Arguments\n    ///\n    /// * `tx_id` - The ID of the transaction to perform the read operation in.\n    /// * `id` - The ID of the row to retrieve.\n    ///\n    /// # Returns\n    ///\n    /// Returns `Some(row)` with the row data if the row with the given `id` exists,\n    /// and `None` otherwise.\n    pub fn read(&self, tx_id: TxID, id: &RowID) -> Result<Option<Row>> {\n        self.read_from_table_or_index(tx_id, id, None)\n    }\n\n    /// Same as read() but can read from a table or an index, indicated by the `maybe_index_id` argument.\n    pub fn read_from_table_or_index(\n        &self,\n        tx_id: TxID,\n        id: &RowID,\n        maybe_index_id: Option<MVTableId>,\n    ) -> Result<Option<Row>> {\n        tracing::trace!(\"read(tx_id={}, id={:?})\", tx_id, id);\n\n        let tx = self\n            .txs\n            .get(&tx_id)\n            .ok_or_else(|| LimboError::NoSuchTransactionID(tx_id.to_string()))?;\n        let tx = tx.value();\n        turso_assert_eq!(tx.state, TransactionState::Active);\n        match maybe_index_id {\n            Some(index_id) => {\n                let rows = self.index_rows.get_or_insert_with(index_id, SkipMap::new);\n                let rows = rows.value();\n                let RowKey::Record(sortable_key) = &id.row_id else {\n                    panic!(\"Index reads must have a record row_id\");\n                };\n                let row_versions_opt = rows.get(sortable_key);\n                if let Some(ref row_versions) = row_versions_opt {\n                    let row_versions = row_versions.value().read();\n                    if let Some(rv) = row_versions\n                        .iter()\n                        .rev()\n                        .find(|rv| rv.is_visible_to(tx, &self.txs, &self.finalized_tx_states))\n                    {\n                        return Ok(Some(rv.row.clone()));\n                    }\n                }\n                Ok(None)\n            }\n            None => {\n                if let Some(row_versions) = self.rows.get(id) {\n                    let row_versions = row_versions.value().read();\n                    if let Some(rv) = row_versions\n                        .iter()\n                        .rev()\n                        .find(|rv| rv.is_visible_to(tx, &self.txs, &self.finalized_tx_states))\n                    {\n                        tx.insert_to_read_set(id.clone());\n                        return Ok(Some(rv.row.clone()));\n                    }\n                }\n                Ok(None)\n            }\n        }\n    }\n\n    /// Gets all row ids in the database.\n    pub fn scan_row_ids(&self) -> Result<Vec<RowID>> {\n        tracing::trace!(\"scan_row_ids\");\n        let keys = self.rows.iter().map(|entry| entry.key().clone());\n        Ok(keys.collect())\n    }\n\n    pub fn get_row_id_range(\n        &self,\n        table_id: MVTableId,\n        start: i64,\n        bucket: &mut Vec<RowID>,\n        max_items: u64,\n    ) -> Result<()> {\n        tracing::trace!(\n            \"get_row_id_in_range(table_id={}, range_start={})\",\n            table_id,\n            start,\n        );\n        let start_id = RowID {\n            table_id,\n            row_id: RowKey::Int(start),\n        };\n\n        let end_id = RowID {\n            table_id,\n            row_id: RowKey::Int(i64::MAX),\n        };\n\n        self.rows\n            .range(start_id..end_id)\n            .take(max_items as usize)\n            .for_each(|entry| bucket.push(entry.key().clone()));\n\n        Ok(())\n    }\n\n    pub(crate) fn advance_cursor_and_get_row_id_for_table(\n        &self,\n        table_id: MVTableId,\n        mv_store_iterator: &mut Option<MvccIterator<'static, RowID>>,\n        tx_id: TxID,\n    ) -> Option<RowID> {\n        let mv_store_iterator = mv_store_iterator.as_mut().expect(\n            \"mv_store_iterator must be initialized when calling get_row_id_for_table_in_direction\",\n        );\n\n        let tx = self\n            .txs\n            .get(&tx_id)\n            .expect(\"transaction should exist in txs map\");\n        let tx = tx.value();\n        loop {\n            // We are moving forward, so if a row was deleted we just need to skip it. Therefore, we need\n            // to loop either until we find a row that is not deleted or until we reach the end of the table.\n            let next_row = mv_store_iterator.next();\n            let row = next_row?;\n            if row.key().table_id != table_id {\n                // In case of table rows, we store the rows of all tables in a single map,\n                // so we must stop iteration if we reach a row that is on a different table.\n                // In the case of indexes we have a separate map per table so this is not\n                // relevant.\n                return None;\n            }\n\n            // We found a row, let's check if it's visible to the transaction.\n            if let Some(visible_row) = self.find_last_visible_version(tx, &row) {\n                return Some(visible_row);\n            }\n            // If this row is not visible, continue to the next row\n        }\n    }\n\n    pub(crate) fn advance_cursor_and_get_row_id_for_index(\n        &self,\n        mv_store_iterator: &mut Option<MvccIterator<'static, Arc<SortableIndexKey>>>,\n        tx_id: TxID,\n    ) -> Option<RowID> {\n        let mv_store_iterator = mv_store_iterator.as_mut().expect(\n            \"mv_store_iterator must be initialized when calling get_row_id_for_index_in_direction\",\n        );\n\n        let tx = self\n            .txs\n            .get(&tx_id)\n            .expect(\"transaction should exist in txs map\");\n        let tx = tx.value();\n\n        self.find_next_visible_index_row(tx, mv_store_iterator)\n    }\n\n    /// Check if the B-tree version of a row should be shown to the given transaction.\n    ///\n    /// Returns true if the B-tree version is valid (should be shown).\n    /// Returns false if the B-tree version is shadowed or deleted by MVCC.\n    pub fn query_btree_version_is_valid(\n        &self,\n        table_id: MVTableId,\n        row_id: &RowKey,\n        tx_id: TxID,\n    ) -> bool {\n        let tx = self\n            .txs\n            .get(&tx_id)\n            .expect(\"transaction should exist in txs map\");\n        let tx = tx.value();\n\n        match row_id {\n            RowKey::Int(_) => {\n                let row_id_full = RowID {\n                    table_id,\n                    row_id: row_id.clone(),\n                };\n                let Some(versions) = self.rows.get(&row_id_full) else {\n                    // No MVCC version -> B-tree is valid\n                    return true;\n                };\n                let versions = versions.value().read();\n\n                // Check if any version invalidates the B-tree row\n                let btree_is_invalid = versions.iter().rev().any(|version| {\n                    version.is_btree_invalidating_version(tx, &self.txs, &self.finalized_tx_states)\n                });\n\n                !btree_is_invalid\n            }\n            RowKey::Record(record) => {\n                let index_rows = self.index_rows.get_or_insert_with(table_id, SkipMap::new);\n                let index_rows = index_rows.value();\n                let Some(versions) = index_rows.get(record) else {\n                    // No MVCC version -> B-tree is valid\n                    return true;\n                };\n                let versions = versions.value().read();\n\n                // Check if any version invalidates the B-tree row\n                let btree_is_invalid = versions.iter().rev().any(|version| {\n                    version.is_btree_invalidating_version(tx, &self.txs, &self.finalized_tx_states)\n                });\n\n                !btree_is_invalid\n            }\n        }\n    }\n\n    fn find_last_visible_version(\n        &self,\n        tx: &Transaction,\n        row: &crossbeam_skiplist::map::Entry<'_, RowID, RwLock<Vec<RowVersion>>>,\n    ) -> Option<RowID> {\n        row.value()\n            .read()\n            .iter()\n            .rev()\n            .find(|version| version.is_visible_to(tx, &self.txs, &self.finalized_tx_states))\n            .map(|_| row.key().clone())\n    }\n\n    fn find_last_visible_index_version(\n        &self,\n        tx: &Transaction,\n        row: crossbeam_skiplist::map::Entry<'_, Arc<SortableIndexKey>, RwLock<Vec<RowVersion>>>,\n    ) -> Option<RowID> {\n        row.value()\n            .read()\n            .iter()\n            .rev()\n            .find(|version| version.is_visible_to(tx, &self.txs, &self.finalized_tx_states))\n            .map(|version| version.row.id.clone())\n    }\n\n    fn find_next_visible_index_row<'a, I>(&self, tx: &Transaction, mut rows: I) -> Option<RowID>\n    where\n        I: Iterator<\n            Item = crossbeam_skiplist::map::Entry<\n                'a,\n                Arc<SortableIndexKey>,\n                RwLock<Vec<RowVersion>>,\n            >,\n        >,\n    {\n        loop {\n            let row = rows.next()?;\n            if let Some(visible_row) = self.find_last_visible_index_version(tx, row) {\n                return Some(visible_row);\n            }\n        }\n    }\n\n    fn find_next_visible_table_row<'a, I>(\n        &self,\n        tx: &Transaction,\n        mut rows: I,\n        table_id: MVTableId,\n    ) -> Option<RowID>\n    where\n        I: Iterator<Item = crossbeam_skiplist::map::Entry<'a, RowID, RwLock<Vec<RowVersion>>>>,\n    {\n        loop {\n            let row = rows.next()?;\n            if row.key().table_id != table_id {\n                return None;\n            }\n            if let Some(visible_row) = self.find_last_visible_version(tx, &row) {\n                return Some(visible_row);\n            }\n        }\n    }\n\n    pub fn seek_rowid(\n        &self,\n        start: RowID,\n        inclusive: bool,\n        direction: IterationDirection,\n        tx_id: TxID,\n        table_iterator: &mut Option<MvccIterator<'static, RowID>>,\n    ) -> Option<RowID> {\n        let table_id = start.table_id;\n        let iter_box = {\n            let start = if inclusive {\n                Bound::Included(start)\n            } else {\n                Bound::Excluded(start)\n            };\n            let range = create_seek_range(start, direction);\n            match direction {\n                IterationDirection::Forwards => Box::new(self.rows.range(range))\n                    as Box<\n                        dyn Iterator<Item = Entry<'_, RowID, RwLock<Vec<RowVersion>>>>\n                            + Send\n                            + Sync,\n                    >,\n                IterationDirection::Backwards => Box::new(self.rows.range(range).rev())\n                    as Box<\n                        dyn Iterator<Item = Entry<'_, RowID, RwLock<Vec<RowVersion>>>>\n                            + Send\n                            + Sync,\n                    >,\n            }\n        };\n        *table_iterator = Some(static_iterator_hack!(iter_box, RowID));\n\n        let mv_store_iterator = table_iterator\n            .as_mut()\n            .expect(\"table_iterator was assigned above if it was None\");\n\n        let tx = self\n            .txs\n            .get(&tx_id)\n            .expect(\"transaction should exist in txs map\");\n        let tx = tx.value();\n\n        self.find_next_visible_table_row(tx, mv_store_iterator, table_id)\n    }\n\n    pub fn seek_index(\n        &self,\n        index_id: MVTableId,\n        start: SortableIndexKey,\n        inclusive: bool,\n        direction: IterationDirection,\n        tx_id: TxID,\n        index_iterator: &mut Option<MvccIterator<'static, Arc<SortableIndexKey>>>,\n    ) -> Option<RowID> {\n        let index_rows = self.index_rows.get_or_insert_with(index_id, SkipMap::new);\n        let index_rows = index_rows.value();\n        let start = if inclusive {\n            Bound::Included(start)\n        } else {\n            Bound::Excluded(start)\n        };\n        let range = create_seek_range(start, direction);\n        let iter_box = match direction {\n            IterationDirection::Forwards => Box::new(index_rows.range(range))\n                as Box<\n                    dyn Iterator<Item = Entry<'_, Arc<SortableIndexKey>, RwLock<Vec<RowVersion>>>>\n                        + Send\n                        + Sync,\n                >,\n            IterationDirection::Backwards => Box::new(index_rows.range(range).rev())\n                as Box<\n                    dyn Iterator<Item = Entry<'_, Arc<SortableIndexKey>, RwLock<Vec<RowVersion>>>>\n                        + Send\n                        + Sync,\n                >,\n        };\n        *index_iterator = Some(static_iterator_hack!(iter_box, Arc<SortableIndexKey>));\n        let mv_store_iterator = index_iterator\n            .as_mut()\n            .expect(\"index_iterator was assigned above if it was None\");\n\n        let tx = self\n            .txs\n            .get(&tx_id)\n            .expect(\"transaction should exist in txs map\");\n        let tx = tx.value();\n        self.find_next_visible_index_row(tx, mv_store_iterator)\n    }\n\n    /// Begins an exclusive write transaction that prevents concurrent writes.\n    ///\n    /// This is used for IMMEDIATE and EXCLUSIVE transaction types where we need\n    /// to ensure exclusive write access as per SQLite semantics.\n    #[instrument(skip_all, level = Level::DEBUG)]\n    pub fn begin_exclusive_tx(\n        &self,\n        pager: Arc<Pager>,\n        maybe_existing_tx_id: Option<TxID>,\n    ) -> Result<TxID> {\n        // Existing transactions already hold one blocking-checkpoint read guard\n        // from begin_tx(). When upgrading read->write, do not acquire another one.\n        let acquires_checkpoint_guard = maybe_existing_tx_id.is_none();\n        if acquires_checkpoint_guard && !self.blocking_checkpoint_lock.read() {\n            // If there is a stop-the-world checkpoint in progress, we cannot begin any transaction at all.\n            return Err(LimboError::Busy);\n        }\n        let unlock_checkpoint_guard = || {\n            if acquires_checkpoint_guard {\n                self.blocking_checkpoint_lock.unlock();\n            }\n        };\n        let tx_id = maybe_existing_tx_id.unwrap_or_else(|| self.get_tx_id());\n        let begin_ts = if let Some(tx_id) = maybe_existing_tx_id {\n            self.txs\n                .get(&tx_id)\n                .ok_or_else(|| LimboError::NoSuchTransactionID(tx_id.to_string()))?\n                .value()\n                .begin_ts\n        } else {\n            self.get_begin_timestamp()\n        };\n\n        let already_exclusive = self.is_exclusive_tx(&tx_id);\n        if !already_exclusive {\n            self.acquire_exclusive_tx(&tx_id)\n                .inspect_err(|_| unlock_checkpoint_guard())?;\n        }\n\n        let already_holds_commit_lock = maybe_existing_tx_id\n            .and_then(|existing_tx_id| self.txs.get(&existing_tx_id))\n            .is_some_and(|tx| tx.value().pager_commit_lock_held.load(Ordering::Acquire));\n\n        if !already_holds_commit_lock {\n            let locked = self.commit_coordinator.pager_commit_lock.write();\n            if !locked {\n                tracing::debug!(\n                    \"begin_exclusive_tx: tx_id={} failed with Busy on pager_commit_lock\",\n                    tx_id\n                );\n                if !already_exclusive {\n                    self.release_exclusive_tx(&tx_id);\n                }\n                unlock_checkpoint_guard();\n                return Err(LimboError::Busy);\n            }\n        }\n\n        let header = self.get_new_transaction_database_header(&pager);\n\n        if let Some(existing_tx_id) = maybe_existing_tx_id {\n            let tx = self\n                .txs\n                .get(&existing_tx_id)\n                .ok_or_else(|| LimboError::NoSuchTransactionID(existing_tx_id.to_string()))?;\n            tx.value()\n                .pager_commit_lock_held\n                .store(true, Ordering::Release);\n            *tx.value().header.write() = header;\n            tracing::trace!(\n                \"begin_exclusive_tx(tx_id={}, begin_ts={}) - upgraded existing transaction\",\n                tx_id,\n                begin_ts\n            );\n            tracing::debug!(\"begin_exclusive_tx: tx_id={} succeeded\", tx_id);\n            return Ok(tx_id);\n        }\n\n        let tx = Transaction::new(tx_id, begin_ts, header);\n        tx.pager_commit_lock_held.store(true, Ordering::Release);\n        tracing::trace!(\n            \"begin_exclusive_tx(tx_id={}, begin_ts={}) - exclusive write logical log transaction\",\n            tx_id,\n            begin_ts\n        );\n        tracing::debug!(\"begin_exclusive_tx: tx_id={} succeeded\", tx_id);\n        self.txs.insert(tx_id, tx);\n        Ok(tx_id)\n    }\n\n    /// Begins a new transaction in the database.\n    ///\n    /// This function starts a new transaction in the database and returns a `TxID` value\n    /// that you can use to perform operations within the transaction. All changes made within the\n    /// transaction are isolated from other transactions until you commit the transaction.\n    pub fn begin_tx(&self, pager: Arc<Pager>) -> Result<TxID> {\n        if !self.blocking_checkpoint_lock.read() {\n            // If there is a stop-the-world checkpoint in progress, we cannot begin any transaction at all.\n            return Err(LimboError::Busy);\n        }\n        let tx_id = self.get_tx_id();\n        let begin_ts = self.get_begin_timestamp();\n\n        // Set txn's header to the global header\n        let header = self.get_new_transaction_database_header(&pager);\n        let tx = Transaction::new(tx_id, begin_ts, header);\n        tracing::trace!(\"begin_tx(tx_id={}, begin_ts={})\", tx_id, begin_ts);\n        self.txs.insert(tx_id, tx);\n\n        Ok(tx_id)\n    }\n\n    pub fn remove_tx(&self, tx_id: TxID) {\n        if let Some(entry) = self.txs.get(&tx_id) {\n            let tx = entry.value();\n            if let TransactionState::Committed(commit_ts) = tx.state.load() {\n                // Read-only transactions cannot leave row versions with stale TxID\n                // references, so they do not need finalized-state caching.\n                if !tx.write_set.is_empty() {\n                    self.finalized_tx_states\n                        .insert(tx_id, TransactionState::Committed(commit_ts));\n                }\n            }\n            let dep_set = std::mem::take(&mut *tx.commit_dep_set.lock());\n            // Invariant: commit_dep_set must be drained before removing the transaction.\n            // CommitEnd and rollback_tx both drain the commit_dep_set to notify dependencies.\n            // If we remove a transaction with non-empty commit_dep_set, those dependencies will wait\n            // forever (deadlock).\n            turso_assert!(\n                dep_set.is_empty(),\n                \"remove_tx({tx_id}): commit_dep_set is not empty\"\n            );\n        }\n        self.txs.remove(&tx_id);\n        self.blocking_checkpoint_lock.unlock();\n    }\n\n    fn get_new_transaction_database_header(&self, pager: &Arc<Pager>) -> DatabaseHeader {\n        if self.global_header.read().is_none() {\n            pager\n                .io\n                .block(|| pager.maybe_allocate_page1())\n                .expect(\"failed to allocate page1\");\n            let header = pager\n                .io\n                .block(|| pager.with_header(|header| *header))\n                .expect(\"failed to read database header\");\n            // TODO: We initialize header here, maybe this needs more careful handling\n            self.global_header.write().replace(header);\n            tracing::debug!(\n                \"get_transaction_database_header create: header={:?}\",\n                header\n            );\n            header\n        } else {\n            let header = self\n                .global_header\n                .read()\n                .expect(\"global_header should be initialized\");\n            // The header could be stored, but not persisted yet\n            pager\n                .io\n                .block(|| pager.maybe_allocate_page1())\n                .expect(\"failed to allocate page1\");\n            tracing::debug!(\"get_transaction_database_header read: header={:?}\", header);\n            header\n        }\n    }\n\n    pub fn get_transaction_database_header(&self, tx_id: &TxID) -> DatabaseHeader {\n        let tx = self\n            .txs\n            .get(tx_id)\n            .expect(\"transaction not found when trying to get header\");\n        let header = tx.value();\n        let header = header.header.read();\n        tracing::debug!(\"get_transaction_database_header read: header={:?}\", header);\n        *header\n    }\n\n    pub fn with_header<T, F>(&self, f: F, tx_id: Option<&TxID>) -> Result<T>\n    where\n        F: Fn(&DatabaseHeader) -> T,\n    {\n        if let Some(tx_id) = tx_id {\n            let tx = self\n                .txs\n                .get(tx_id)\n                .ok_or_else(|| LimboError::NoSuchTransactionID(tx_id.to_string()))?;\n            let header = tx.value();\n            let header = header.header.read();\n            tracing::debug!(\"with_header read: header={:?}\", header);\n            Ok(f(&header))\n        } else {\n            let header = self.global_header.read();\n            tracing::debug!(\"with_header read: header={:?}\", header);\n            Ok(f(header.as_ref().ok_or_else(|| {\n                LimboError::InternalError(\"global_header not initialized\".to_string())\n            })?))\n        }\n    }\n\n    pub fn with_header_mut<T, F>(&self, f: F, tx_id: Option<&TxID>) -> Result<T>\n    where\n        F: Fn(&mut DatabaseHeader) -> T,\n    {\n        if let Some(tx_id) = tx_id {\n            let tx = self\n                .txs\n                .get(tx_id)\n                .ok_or_else(|| LimboError::NoSuchTransactionID(tx_id.to_string()))?;\n            let header = tx.value();\n            let mut header = header.header.write();\n            tracing::debug!(\"with_header_mut read: header={:?}\", header);\n            let out = f(&mut header);\n            // Commit path consults this flag to decide whether a header-only logical-log record\n            // is required even when write_set stays empty.\n            tx.value().header_dirty.store(true, Ordering::Release);\n            Ok(out)\n        } else {\n            let mut header = self.global_header.write();\n            let header = header.as_mut().ok_or_else(|| {\n                LimboError::InternalError(\"global_header not initialized\".to_string())\n            })?;\n            tracing::debug!(\"with_header_mut write: header={:?}\", header);\n            Ok(f(header))\n        }\n    }\n\n    /// Commits a transaction with the specified transaction ID.\n    ///\n    /// This function commits the changes made within the specified transaction and finalizes the\n    /// transaction. Once a transaction has been committed, all changes made within the transaction\n    /// are visible to other transactions that access the same data.\n    ///\n    /// # Arguments\n    ///\n    /// * `tx_id` - The ID of the transaction to commit.\n    pub fn commit_tx(\n        &self,\n        tx_id: TxID,\n        connection: &Arc<Connection>,\n    ) -> Result<StateMachine<CommitStateMachine<Clock>>> {\n        let state_machine: StateMachine<CommitStateMachine<Clock>> =\n            StateMachine::<CommitStateMachine<Clock>>::new(CommitStateMachine::new(\n                CommitState::Initial,\n                tx_id,\n                connection.clone(),\n                self.commit_coordinator.clone(),\n                self.global_header.clone(),\n                connection.get_sync_mode(),\n            ));\n        Ok(state_machine)\n    }\n\n    /// Returns true if the transaction can be rolled back (Active or Preparing).\n    pub fn is_tx_rollbackable(&self, tx_id: TxID) -> bool {\n        self.txs.get(&tx_id).is_some_and(|tx| {\n            matches!(\n                tx.value().state.load(),\n                TransactionState::Active | TransactionState::Preparing(_)\n            )\n        })\n    }\n\n    /// Rolls back a transaction with the specified ID.\n    ///\n    /// This function rolls back a transaction with the specified `tx_id` by\n    /// discarding any changes made by the transaction.\n    ///\n    /// # Arguments\n    ///\n    /// * `tx_id` - The ID of the transaction to abort.\n    /// * `db` - The database index this transaction belongs to.\n    pub fn rollback_tx(&self, tx_id: TxID, _pager: Arc<Pager>, connection: &Connection, db: usize) {\n        let tx_unlocked = self\n            .txs\n            .get(&tx_id)\n            .expect(\"transaction should exist in txs map\");\n        let tx = tx_unlocked.value();\n        connection.set_mv_tx_for_db(db, None);\n        turso_assert!(matches!(\n            tx.state.load(),\n            TransactionState::Active | TransactionState::Preparing(_)\n        ));\n        tx.state.store(TransactionState::Aborted);\n        tracing::trace!(\"abort(tx_id={})\", tx_id);\n        self.unlock_commit_lock_if_held(tx);\n\n        // Hekaton Section 3.3: \"If it aborted, it forces the dependent transactions\n        // to also abort by setting their AbortNow flags.\"\n        let dependents = std::mem::take(&mut *tx.commit_dep_set.lock());\n        // a txn cannot depend on itself\n        turso_assert!(\n            !dependents.contains(&tx_id),\n            \"rollback_tx: transaction has itself in its own commit_dep_set\"\n        );\n        for dep_tx_id in dependents {\n            if let Some(dep_tx_entry) = self.txs.get(&dep_tx_id) {\n                let dep_tx = dep_tx_entry.value();\n                dep_tx.abort_now.store(true, Ordering::Release);\n                dep_tx.commit_dep_counter.fetch_sub(1, Ordering::AcqRel);\n            }\n        }\n\n        if self.is_exclusive_tx(&tx_id) {\n            self.release_exclusive_tx(&tx_id);\n        }\n\n        for rowid in &tx.write_set {\n            let rowid = rowid.value();\n            self.rollback_rowid(tx_id, rowid);\n        }\n\n        if connection.schema.read().schema_version > connection.db.schema.lock().schema_version {\n            // Connection made schema changes during tx and rolled back -> revert connection-local schema.\n            *connection.schema.write() = connection.db.clone_schema();\n        }\n\n        let tx = tx_unlocked.value();\n        tx.state.store(TransactionState::Terminated);\n        tracing::trace!(\"terminate(tx_id={})\", tx_id);\n        // Safe to remove here: rollback_rowid (above) acquired the write lock on\n        // every row version chain in the write set, clearing all TxID references.\n        // Any concurrent reader that held a read lock on one of those chains has\n        // already completed its register_commit_dependency call (it runs under the\n        // read lock), so no future txs.get() for this tx_id can come from a\n        // speculative read path.\n        self.remove_tx(tx_id);\n    }\n\n    fn rollback_rowid(&self, tx_id: u64, rowid: &RowID) {\n        if rowid.row_id.is_int_key() {\n            self.rollback_table_rowid(tx_id, rowid);\n        } else {\n            self.rollback_index_rowid(tx_id, rowid);\n        }\n    }\n\n    fn unlock_commit_lock_if_held(&self, tx: &Transaction) {\n        if tx.pager_commit_lock_held.swap(false, Ordering::AcqRel) {\n            self.commit_coordinator.pager_commit_lock.unlock();\n        }\n    }\n\n    fn rollback_index_rowid(&self, tx_id: u64, rowid: &RowID) {\n        if let Some(index) = self.index_rows.get(&rowid.table_id) {\n            let index = index.value();\n            let RowKey::Record(ref index_key) = rowid.row_id else {\n                panic!(\"Index writes must have a record key\");\n            };\n            if let Some(row_versions) = index.get(index_key) {\n                let mut row_versions = row_versions.value().write();\n                for rv in row_versions.iter_mut() {\n                    rollback_row_version(tx_id, rv);\n                }\n            }\n        }\n    }\n\n    fn rollback_table_rowid(&self, tx_id: u64, rowid: &RowID) {\n        if let Some(row_versions) = self.rows.get(rowid) {\n            let mut row_versions = row_versions.value().write();\n            for rv in row_versions.iter_mut() {\n                rollback_row_version(tx_id, rv);\n            }\n        }\n    }\n\n    /// Begin a savepoint for the transaction.\n    /// This should be called at the start of a statement in an interactive transaction.\n    pub fn begin_savepoint(&self, tx_id: TxID) {\n        let tx = self\n            .txs\n            .get(&tx_id)\n            .unwrap_or_else(|| panic!(\"Transaction {tx_id} not found while beginning savepoint\"));\n        tx.value().begin_savepoint();\n    }\n\n    /// Begin a user-visible named savepoint inside an existing transaction.\n    ///\n    /// `starts_transaction` is true when the savepoint was opened in autocommit mode and therefore\n    /// releasing the root savepoint should commit the transaction.\n    pub fn begin_named_savepoint(\n        &self,\n        tx_id: TxID,\n        name: String,\n        starts_transaction: bool,\n        deferred_fk_violations: isize,\n    ) {\n        let tx = self.txs.get(&tx_id).unwrap_or_else(|| {\n            panic!(\"Transaction {tx_id} not found while beginning named savepoint\")\n        });\n        tx.value()\n            .begin_named_savepoint(name, starts_transaction, deferred_fk_violations);\n    }\n\n    /// Release the newest savepoint for the transaction.\n    /// This should be called when a statement completes successfully.\n    /// Silently returns if the transaction doesn't exist (e.g., already committed).\n    pub fn release_savepoint(&self, tx_id: TxID) {\n        if let Some(tx) = self.txs.get(&tx_id) {\n            tx.value().release_savepoint();\n        }\n        // If transaction doesn't exist, it was already committed - nothing to release\n    }\n\n    /// Releases a named savepoint and nested savepoints above it.\n    ///\n    /// Returns [SavepointResult::Commit] when releasing the root savepoint should commit the\n    /// transaction.\n    pub fn release_named_savepoint(&self, tx_id: TxID, name: &str) -> Result<SavepointResult> {\n        let tx = self\n            .txs\n            .get(&tx_id)\n            .unwrap_or_else(|| panic!(\"Transaction {tx_id} not found while releasing savepoint\"));\n        Ok(tx.value().release_named_savepoint(name))\n    }\n\n    /// Rolls back a savepoint within a transaction.\n    /// Returns true if a savepoint was rolled back, false if no savepoint was active.\n    pub fn rollback_first_savepoint(&self, tx_id: u64) -> Result<bool> {\n        let tx = self.txs.get(&tx_id).unwrap_or_else(|| {\n            panic!(\"Transaction {tx_id} not found while rolling back savepoint\")\n        });\n\n        let tx = tx.value();\n        let savepoint = tx.pop_statement_savepoint();\n\n        if let Some(savepoint) = savepoint {\n            self.rollback_savepoint_changes(tx_id, savepoint);\n            Ok(true)\n        } else {\n            tracing::debug!(\n                \"rollback_savepoint(tx_id={}): no savepoint was active\",\n                tx_id\n            );\n            Ok(false)\n        }\n    }\n\n    /// Rolls back to the newest matching named savepoint while keeping that savepoint active.\n    ///\n    /// Returns the deferred FK snapshot stored on the named savepoint, or `None` if no matching\n    /// savepoint exists.\n    pub fn rollback_to_named_savepoint(&self, tx_id: TxID, name: &str) -> Result<Option<isize>> {\n        let tx = self.txs.get(&tx_id).unwrap_or_else(|| {\n            panic!(\"Transaction {tx_id} not found while rolling back named savepoint\")\n        });\n        let Some(SavepointRollbackResult {\n            rolledback_savepoints,\n            deferred_fk_violations,\n        }) = tx.value().rollback_to_named_savepoint(name)\n        else {\n            return Ok(None);\n        };\n\n        for savepoint in rolledback_savepoints.into_iter().rev() {\n            self.rollback_savepoint_changes(tx_id, savepoint);\n        }\n\n        Ok(Some(deferred_fk_violations))\n    }\n\n    fn rollback_savepoint_changes(&self, tx_id: TxID, savepoint: Savepoint) {\n        let Savepoint {\n            created_table_versions,\n            created_index_versions,\n            deleted_table_versions,\n            deleted_index_versions,\n            newly_added_to_write_set,\n            ..\n        } = savepoint;\n\n        tracing::debug!(\n            \"rollback_savepoint(tx_id={}, created_table={}, created_index={}, deleted_table={}, deleted_index={})\",\n            tx_id,\n            created_table_versions.len(),\n            created_index_versions.len(),\n            deleted_table_versions.len(),\n            deleted_index_versions.len()\n        );\n\n        let mut touched_rowids = BTreeSet::new();\n\n        for (rowid, version_id) in created_table_versions {\n            touched_rowids.insert(rowid.clone());\n            if let Some(entry) = self.rows.get(&rowid) {\n                let mut versions = entry.value().write();\n                versions.retain(|rv| rv.id != version_id);\n                tracing::debug!(\n                    \"rollback_savepoint: removed table version(table_id={}, row_id={}, version_id={})\",\n                    rowid.table_id,\n                    rowid.row_id,\n                    version_id\n                );\n            }\n        }\n\n        for ((table_id, key), version_id) in created_index_versions {\n            if let Some(index) = self.index_rows.get(&table_id) {\n                if let Some(entry) = index.value().get(&key) {\n                    let mut versions = entry.value().write();\n                    versions.retain(|rv| rv.id != version_id);\n                    tracing::debug!(\n                        \"rollback_savepoint: removed index version(table_id={}, version_id={})\",\n                        table_id,\n                        version_id\n                    );\n                }\n            }\n        }\n\n        for (rowid, version_id) in deleted_table_versions {\n            touched_rowids.insert(rowid.clone());\n            if let Some(entry) = self.rows.get(&rowid) {\n                let mut versions = entry.value().write();\n                for rv in versions.iter_mut() {\n                    if rv.id == version_id {\n                        rv.end = None;\n                        tracing::debug!(\n                            \"rollback_savepoint: restored table version(table_id={}, row_id={}, version_id={})\",\n                            rowid.table_id,\n                            rowid.row_id,\n                            version_id\n                        );\n                        break;\n                    }\n                }\n            }\n        }\n\n        for ((table_id, key), version_id) in deleted_index_versions {\n            if let Some(index) = self.index_rows.get(&table_id) {\n                if let Some(entry) = index.value().get(&key) {\n                    let mut versions = entry.value().write();\n                    for rv in versions.iter_mut() {\n                        if rv.id == version_id {\n                            rv.end = None;\n                            tracing::debug!(\n                                \"rollback_savepoint: restored index version(table_id={}, version_id={})\",\n                                table_id,\n                                version_id\n                            );\n                            break;\n                        }\n                    }\n                }\n            }\n        }\n\n        touched_rowids.extend(newly_added_to_write_set);\n        self.remove_rolled_back_rows_from_write_set(tx_id, touched_rowids.clone());\n    }\n\n    fn row_has_uncommitted_version_for_tx(&self, rowid: &RowID, tx_id: TxID) -> bool {\n        if rowid.row_id.is_int_key() {\n            let Some(entry) = self.rows.get(rowid) else {\n                return false;\n            };\n            let versions = entry.value().read();\n            return versions.iter().any(|rv| {\n                rv.begin == Some(TxTimestampOrID::TxID(tx_id))\n                    || rv.end == Some(TxTimestampOrID::TxID(tx_id))\n            });\n        }\n\n        let RowKey::Record(ref record) = rowid.row_id else {\n            return false;\n        };\n        let Some(index) = self.index_rows.get(&rowid.table_id) else {\n            return false;\n        };\n        let Some(entry) = index.value().get(record) else {\n            return false;\n        };\n        let versions = entry.value().read();\n        versions.iter().any(|rv| {\n            rv.begin == Some(TxTimestampOrID::TxID(tx_id))\n                || rv.end == Some(TxTimestampOrID::TxID(tx_id))\n        })\n    }\n\n    fn remove_rolled_back_rows_from_write_set(&self, tx_id: TxID, rowids: BTreeSet<RowID>) {\n        if rowids.is_empty() {\n            return;\n        }\n        let Some(tx) = self.txs.get(&tx_id) else {\n            return;\n        };\n        let tx = tx.value();\n        for rowid in rowids {\n            if self.row_has_uncommitted_version_for_tx(&rowid, tx_id) {\n                continue;\n            }\n            tx.write_set.remove(&rowid);\n        }\n    }\n\n    /// Returns true if the given transaction is the exclusive transaction.\n    #[inline]\n    pub fn is_exclusive_tx(&self, tx_id: &TxID) -> bool {\n        self.exclusive_tx.load(Ordering::Acquire) == *tx_id\n    }\n\n    /// Returns true if there is an exclusive transaction ongoing.\n    #[inline]\n    fn has_exclusive_tx(&self) -> bool {\n        self.exclusive_tx.load(Ordering::Acquire) != NO_EXCLUSIVE_TX\n    }\n\n    /// Acquires the exclusive transaction lock to the given transaction ID.\n    fn acquire_exclusive_tx(&self, tx_id: &TxID) -> Result<()> {\n        if self.exclusive_tx.load(Ordering::Acquire) == *tx_id {\n            // Re-entrant upgrade attempt for the same transaction.\n            return Ok(());\n        }\n        if let Some(tx) = self.txs.get(tx_id) {\n            let tx = tx.value();\n            if tx.begin_ts < self.last_committed_tx_ts.load(Ordering::Acquire) {\n                // Another transaction committed after this transaction's begin timestamp, do not allow exclusive lock.\n                // This mimics regular (non-CONCURRENT) sqlite transaction behavior.\n                return Err(LimboError::Busy);\n            }\n        }\n        match self.exclusive_tx.compare_exchange(\n            NO_EXCLUSIVE_TX,\n            *tx_id,\n            Ordering::AcqRel,\n            Ordering::Acquire,\n        ) {\n            Ok(_) => Ok(()),\n            Err(_) => {\n                // Another transaction already holds the exclusive lock\n                Err(LimboError::Busy)\n            }\n        }\n    }\n\n    /// Release the exclusive transaction lock if held by the this transaction.\n    fn release_exclusive_tx(&self, tx_id: &TxID) {\n        tracing::trace!(\"release_exclusive_tx(tx_id={})\", tx_id);\n        let prev = self.exclusive_tx.swap(NO_EXCLUSIVE_TX, Ordering::Release);\n        turso_assert_eq!(prev, *tx_id, \"exclusive lock released by wrong tx\", { \"expected_tx_id\": *tx_id, \"actual_tx_id\": prev });\n    }\n\n    /// Generates next unique transaction id\n    pub fn get_tx_id(&self) -> u64 {\n        self.tx_ids.fetch_add(1, Ordering::SeqCst)\n    }\n\n    /// Generates next unique version ID for RowVersion tracking.\n    pub fn get_version_id(&self) -> u64 {\n        self.version_id_counter.fetch_add(1, Ordering::SeqCst)\n    }\n\n    /// Generate a begin timestamp. No side-effect needed alongside generation.\n    pub fn get_begin_timestamp(&self) -> u64 {\n        self.clock.get_timestamp(crate::mvcc::clock::no_op)\n    }\n\n    /// Generate a commit timestamp and call `f` with it while the clock\n    /// lock is held, atomically publishing the timestamp before release.\n    /// See [`MvccClock`] for the full explanation.\n    pub fn get_commit_timestamp<F: FnOnce(u64)>(&self, f: F) -> u64 {\n        self.clock.get_timestamp(f)\n    }\n\n    /// Compute the low-water mark: the minimum begin_ts of all active or\n    /// preparing transactions. Returns u64::MAX if no transactions are active.\n    /// Used by GC to determine which row versions are safe to reclaim.\n    pub fn compute_lwm(&self) -> u64 {\n        self.txs\n            .iter()\n            .filter_map(|entry| {\n                let tx = entry.value();\n                match tx.state.load() {\n                    TransactionState::Active | TransactionState::Preparing(_) => Some(tx.begin_ts),\n                    _ => None,\n                }\n            })\n            .min()\n            .unwrap_or(u64::MAX)\n    }\n\n    /// Garbage-collects row versions that are invisible to all active transactions.\n    /// Uses the low-water mark (LWM) to determine reclaimability in O(1) per version.\n    /// Covers both table rows (`self.rows`) and index rows (`self.index_rows`).\n    /// Returns the number of removed versions.\n    pub fn drop_unused_row_versions(&self) -> usize {\n        let lwm = self.compute_lwm();\n        let ckpt_max = self.durable_txid_max.load(Ordering::SeqCst);\n        let mut referenced_tx_ids = HashSet::default();\n\n        let dropped = self.gc_table_row_versions(lwm, ckpt_max, &mut referenced_tx_ids)\n            + self.gc_index_row_versions(lwm, ckpt_max, &mut referenced_tx_ids);\n        let pruned_finalized = self.prune_finalized_tx_states(&referenced_tx_ids);\n\n        tracing::trace!(\n            \"drop_unused_row_versions() -> dropped {dropped}, pruned_finalized={pruned_finalized}, txs: {}, finalized_tx_states: {}, rows: {}\",\n            self.txs.len(),\n            self.finalized_tx_states.len(),\n            self.rows.len()\n        );\n        dropped\n    }\n\n    fn gc_table_row_versions(\n        &self,\n        lwm: u64,\n        ckpt_max: u64,\n        referenced_tx_ids: &mut HashSet<TxID>,\n    ) -> usize {\n        let mut dropped = 0;\n\n        for entry in self.rows.iter() {\n            let mut versions = entry.value().write();\n            dropped += Self::gc_version_chain(&mut versions, lwm, ckpt_max);\n            Self::collect_referenced_txids(&versions, referenced_tx_ids);\n            // Empty entries are left in the SkipMap (lazy removal). This avoids\n            // a TOCTOU race where a concurrent writer inserts a version between\n            // the emptiness check and SkipMap::remove(). Empty entries are reused\n            // by get_or_insert_with on subsequent inserts and cleaned up by\n            // checkpoint-time GC which runs under the blocking lock.\n        }\n        dropped\n    }\n\n    fn gc_index_row_versions(\n        &self,\n        lwm: u64,\n        ckpt_max: u64,\n        referenced_tx_ids: &mut HashSet<TxID>,\n    ) -> usize {\n        let mut dropped = 0;\n\n        for outer_entry in self.index_rows.iter() {\n            let inner_map = outer_entry.value();\n\n            for inner_entry in inner_map.iter() {\n                let mut versions = inner_entry.value().write();\n                dropped += Self::gc_version_chain(&mut versions, lwm, ckpt_max);\n                Self::collect_referenced_txids(&versions, referenced_tx_ids);\n            }\n            // Empty entries left in place — same TOCTOU rationale as table rows.\n        }\n        dropped\n    }\n\n    fn collect_referenced_txids(versions: &[RowVersion], referenced_tx_ids: &mut HashSet<TxID>) {\n        for version in versions {\n            if let Some(TxTimestampOrID::TxID(tx_id)) = version.begin {\n                referenced_tx_ids.insert(tx_id);\n            }\n            if let Some(TxTimestampOrID::TxID(tx_id)) = version.end {\n                referenced_tx_ids.insert(tx_id);\n            }\n        }\n    }\n\n    fn prune_finalized_tx_states(&self, referenced_tx_ids: &HashSet<TxID>) -> usize {\n        if self.finalized_tx_states.is_empty() {\n            return 0;\n        }\n\n        let to_remove: Vec<TxID> = self\n            .finalized_tx_states\n            .iter()\n            .filter_map(|entry| {\n                let tx_id = *entry.key();\n                (!referenced_tx_ids.contains(&tx_id)).then_some(tx_id)\n            })\n            .collect();\n\n        for tx_id in &to_remove {\n            self.finalized_tx_states.remove(tx_id);\n        }\n\n        to_remove.len()\n    }\n\n    /// Apply GC rules to a single version chain. Returns number of versions removed.\n    ///\n    /// Rule 1: Aborted garbage (begin=None, end=None) — always remove.\n    /// Rule 2: Superseded (end=Timestamp(e), e <= lwm) — remove unless it's a\n    ///         tombstone (no committed current version) whose deletion hasn't\n    ///         been checkpointed (e > ckpt_max).\n    /// Rule 3: Current checkpointed sole-survivor (end=None, b <= ckpt_max,\n    ///         b < lwm, no other versions remain) — remove.\n    ///\n    fn gc_version_chain(versions: &mut Vec<RowVersion>, lwm: u64, ckpt_max: u64) -> usize {\n        let before = versions.len();\n\n        // Rule 1: aborted garbage\n        versions.retain(|rv| !matches!((&rv.begin, &rv.end), (None, None)));\n\n        // Rule 2: superseded versions below LWM, with tombstone guard.\n        // A superseded version with e <= lwm is invisible to all readers and\n        // removable — UNLESS it's a tombstone (sole version, no committed\n        // current version) whose deletion hasn't been checkpointed to B-tree\n        // yet. In that case removing it would let the dual cursor fall through\n        // to a stale B-tree row.\n        //\n        // has_current only counts committed current versions (begin=Timestamp).\n        // Pending inserts (begin=TxID) don't count — they might roll back,\n        // which would resurrect the B-tree row if the tombstone was removed.\n        let has_current = versions\n            .iter()\n            .any(|rv| rv.end.is_none() && matches!(&rv.begin, Some(TxTimestampOrID::Timestamp(_))));\n        versions.retain(|rv| match &rv.end {\n            Some(TxTimestampOrID::Timestamp(e)) if *e <= lwm => {\n                // Retain only if this is a tombstone AND not yet checkpointed.\n                !has_current && *e > ckpt_max\n            }\n            _ => true,\n        });\n\n        // Rule 3: checkpointed sole-survivor current version.\n        // Safe to remove only when the B-tree has the data (b <= ckpt_max),\n        // no reader needs the MVCC copy (b < lwm), and no superseded versions\n        // remain that would poison is_btree_invalidating_version.\n        if versions.len() == 1 {\n            if let (Some(TxTimestampOrID::Timestamp(b)), None) =\n                (&versions[0].begin, &versions[0].end)\n            {\n                if *b <= ckpt_max && *b < lwm {\n                    versions.clear();\n                }\n            }\n        }\n\n        before - versions.len()\n    }\n\n    // Extracts the begin timestamp from a transaction\n    #[inline]\n    fn resolve_begin_timestamp(&self, ts_or_id: &Option<TxTimestampOrID>) -> u64 {\n        match ts_or_id {\n            Some(TxTimestampOrID::Timestamp(ts)) => *ts,\n            Some(TxTimestampOrID::TxID(tx_id)) => {\n                self.txs\n                    .get(tx_id)\n                    .expect(\"transaction should exist in txs map\")\n                    .value()\n                    .begin_ts\n            }\n            // This function is intended to be used in the ordering of row versions within the row version chain in `insert_version_raw`.\n            //\n            // The row version chain should be append-only (aside from garbage collection),\n            // so the specific ordering handled by this function may not be critical. We might\n            // be able to append directly to the row version chain in the future.\n            //\n            // The value 0 is used here to represent an infinite timestamp value. This is a deliberate\n            // choice for a planned future bitpacking optimization, reserving 0 for this purpose,\n            // while actual timestamps will start from 1.\n            None => 0,\n        }\n    }\n\n    /// Inserts a new row version into the database, while making sure that\n    /// the row version is inserted in the correct order.\n    fn insert_version(&self, id: RowID, row_version: RowVersion) {\n        let versions = self.rows.get_or_insert_with(id, || RwLock::new(Vec::new()));\n        let mut versions = versions.value().write();\n        self.insert_version_raw(&mut versions, row_version)\n    }\n\n    /// Gets an existing Arc<SortableIndexKey> from the index if the key exists,\n    /// otherwise creates a new Arc. This ensures we reuse Arc instances for the same key.\n    fn get_or_create_index_key_arc(\n        &self,\n        index_id: MVTableId,\n        key: SortableIndexKey,\n    ) -> Arc<SortableIndexKey> {\n        let index = self.index_rows.get_or_insert_with(index_id, SkipMap::new);\n        let index = index.value();\n        // Check if key exists and get the Arc if so\n        let existing = index.get(&key).map(|entry| entry.key().clone());\n        existing.unwrap_or_else(|| Arc::new(key))\n    }\n\n    pub fn insert_index_version(\n        &self,\n        index_id: MVTableId,\n        key: Arc<SortableIndexKey>,\n        row_version: RowVersion,\n    ) {\n        let index = self.index_rows.get_or_insert_with(index_id, SkipMap::new);\n        let index = index.value();\n        let versions = index.get_or_insert_with(key, || RwLock::new(Vec::new()));\n        let mut versions = versions.value().write();\n        self.insert_version_raw(&mut versions, row_version);\n    }\n\n    /// Inserts a new row version into the internal data structure for versions,\n    /// while making sure that the row version is inserted in the correct order.\n    pub fn insert_version_raw(&self, versions: &mut Vec<RowVersion>, row_version: RowVersion) {\n        // NOTICE: this is an insert a'la insertion sort, with pessimistic linear complexity.\n        // However, we expect the number of versions to be nearly sorted, so we deem it worthy\n        // to search linearly for the insertion point instead of paying the price of using\n        // another data structure, e.g. a BTreeSet. If it proves to be too quadratic empirically,\n        // we can either switch to a tree-like structure, or at least use partition_point()\n        // which performs a binary search for the insertion point.\n        let mut position = 0_usize;\n        for (i, v) in versions.iter().enumerate().rev() {\n            let existing_begin = self.resolve_begin_timestamp(&v.begin);\n            let new_begin = self.resolve_begin_timestamp(&row_version.begin);\n            if existing_begin <= new_begin {\n                // Recovery can replay multiple operations for the same row from one transaction\n                // (e.g. insert then delete), which share the same begin timestamp.\n                // Keep only the latest version for that begin timestamp so visibility checks don't\n                // surface a stale intermediate version.\n                // Only collapse duplicate \"begin\" values when both are concrete begins.\n                // `begin=None` is used for committed tombstones over B-tree-resident rows and\n                // must never be conflated with a later statement's transient tombstone.\n                if versions[i].row.id == row_version.row.id\n                    && matches!(\n                        (&versions[i].begin, &row_version.begin),\n                        (\n                            Some(TxTimestampOrID::Timestamp(existing)),\n                            Some(TxTimestampOrID::Timestamp(new))\n                        ) if existing == new\n                    )\n                {\n                    versions[i] = row_version;\n                    return;\n                }\n                position = i + 1;\n                break;\n            }\n        }\n        versions.insert(position, row_version);\n    }\n\n    pub fn write_row_to_pager(\n        &self,\n        row: &Row,\n        cursor: Arc<RwLock<BTreeCursor>>,\n        requires_seek: bool,\n    ) -> Result<StateMachine<WriteRowStateMachine>> {\n        let state_machine: StateMachine<WriteRowStateMachine> =\n            StateMachine::<WriteRowStateMachine>::new(WriteRowStateMachine::new(\n                row.clone(),\n                cursor,\n                requires_seek,\n            ));\n\n        Ok(state_machine)\n    }\n\n    pub fn delete_row_from_pager(\n        &self,\n        rowid: RowID,\n        cursor: Arc<RwLock<BTreeCursor>>,\n    ) -> Result<StateMachine<DeleteRowStateMachine>> {\n        let state_machine: StateMachine<DeleteRowStateMachine> =\n            StateMachine::<DeleteRowStateMachine>::new(DeleteRowStateMachine::new(rowid, cursor));\n\n        Ok(state_machine)\n    }\n\n    pub fn get_last_table_rowid(\n        &self,\n        table_id: MVTableId,\n        table_iterator: &mut Option<MvccIterator<'static, RowID>>,\n        tx_id: TxID,\n    ) -> Option<RowKey> {\n        let tx = self\n            .txs\n            .get(&tx_id)\n            .expect(\"transaction should exist in txs map\");\n        let tx = tx.value();\n        let max_rowid = RowID {\n            table_id,\n            row_id: RowKey::Int(i64::MAX),\n        };\n        let range = create_seek_range(Bound::Included(max_rowid), IterationDirection::Backwards);\n        let iter_box = Box::new(self.rows.range(range).rev());\n        *table_iterator = Some(static_iterator_hack!(iter_box, RowID));\n        let iter = table_iterator\n            .as_mut()\n            .expect(\"table_iterator was assigned above\");\n        loop {\n            let entry = iter.next()?;\n            // Rowid is not part of the table, therefore we already reached the end of the table.\n            // NOTE: Shouldn't range already prevent this?\n            tracing::trace!(\n                \"get_last_table_rowid: entry.key().table_id={}, table_id={}, row_id={}\",\n                entry.key().table_id,\n                table_id,\n                entry.key().row_id\n            );\n            if entry.key().table_id != table_id {\n                tracing::trace!(\"get_last_table_rowid: reached end of table\");\n                return None;\n            }\n            if let Some(_visible_row) = self.find_last_visible_version(tx, &entry) {\n                tracing::trace!(\n                    \"get_last_table_rowid: found visible row: {:?}\",\n                    _visible_row\n                );\n                // There is a visible version for this rowid, so we return it\n                return Some(RowKey::Int(match &entry.key().row_id {\n                    RowKey::Int(i) => *i,\n                    _ => panic!(\"Expected RowKey::Int for table rowid\"),\n                }));\n            }\n        }\n    }\n\n    pub fn get_last_table_rowid_without_visibility_check(\n        &self,\n        table_id: MVTableId,\n    ) -> Option<RowKey> {\n        let max_rowid = RowID {\n            table_id,\n            row_id: RowKey::Int(i64::MAX),\n        };\n        let range = create_seek_range(Bound::Included(max_rowid), IterationDirection::Backwards);\n        let mut range = self.rows.range(range).rev();\n        let entry = range.next()?;\n        if entry.key().table_id != table_id {\n            return None;\n        }\n        Some(entry.key().row_id.clone())\n    }\n\n    pub fn get_last_index_rowid(\n        &self,\n        index_id: MVTableId,\n        tx_id: TxID,\n        index_iterator: &mut Option<MvccIterator<'static, Arc<SortableIndexKey>>>,\n    ) -> Option<RowKey> {\n        let index = self.index_rows.get_or_insert_with(index_id, SkipMap::new);\n        let index = index.value();\n        let iter_box = Box::new(index.iter().rev());\n        *index_iterator = Some(static_iterator_hack!(iter_box, Arc<SortableIndexKey>));\n        let iter = index_iterator\n            .as_mut()\n            .expect(\"index_iterator was assigned above\");\n        let tx = self\n            .txs\n            .get(&tx_id)\n            .expect(\"transaction should exist in txs map\");\n        let tx = tx.value();\n        self.find_next_visible_index_row(tx, iter)\n            .map(|row| row.row_id)\n    }\n\n    pub fn get_logical_log_file(&self) -> Arc<dyn File> {\n        self.storage.get_logical_log_file()\n    }\n\n    fn logical_log_header_crc_valid(&self, pager: &Arc<Pager>) -> Result<bool> {\n        let file = self.get_logical_log_file();\n        let mut reader = StreamingLogicalLogReader::new(file, None);\n        match reader.try_read_header(&pager.io)? {\n            HeaderReadResult::Valid(_) => Ok(true),\n            HeaderReadResult::NoLog | HeaderReadResult::Invalid => Ok(false),\n        }\n    }\n\n    /// Runs during bootstrap to reconcile WAL state left by a prior crash or incomplete\n    /// checkpoint. Classifies startup state by WAL frame count and logical-log header\n    /// validity, then either completes the interrupted checkpoint (backfill WAL → DB,\n    /// sync, truncate) or fails closed on corrupt/inconsistent artifacts.\n    /// See RECOVERY_SEMANTICS.md \"Startup Case Classification\" for the full case table.\n    fn maybe_complete_interrupted_checkpoint(&self, connection: &Arc<Connection>) -> Result<()> {\n        let pager = connection.pager.load().clone();\n        let Some(wal) = &pager.wal else {\n            return Ok(());\n        };\n        // The bootstrap connection may have acquired a WAL read lock during earlier\n        // bootstrap steps (e.g. schema parsing). Drop it so the TRUNCATE checkpoint\n        // below isn't blocked by our own read lock.\n        if wal.holds_read_lock() {\n            wal.end_read_tx();\n        }\n\n        let wal_max_frame = wal.get_max_frame_in_wal();\n        let file = self.get_logical_log_file();\n        let mut reader = StreamingLogicalLogReader::new(file, None);\n        let header_result = reader.try_read_header(&pager.io)?;\n\n        let is_readonly = connection.db.is_readonly();\n        if wal_max_frame == 0 {\n            if !is_readonly {\n                let mut checkpoint_result = CheckpointResult::new(0, 0, 0);\n                pager\n                    .io\n                    .block(|| wal.truncate_wal(&mut checkpoint_result, pager.get_sync_type()))?;\n                if let HeaderReadResult::Valid(header) = &header_result {\n                    self.storage.set_header(header.clone());\n                }\n            }\n            return Ok(());\n        }\n\n        if is_readonly {\n            return Err(LimboError::Corrupt(\n                \"Cannot reconcile interrupted MVCC checkpoint in read-only mode\".to_string(),\n            ));\n        }\n\n        let header = match header_result {\n            HeaderReadResult::Valid(header) => header,\n            HeaderReadResult::NoLog => {\n                return Err(LimboError::Corrupt(\n                    \"WAL has committed frames but logical log header is missing\".to_string(),\n                ))\n            }\n            HeaderReadResult::Invalid => {\n                return Err(LimboError::Corrupt(\n                    \"WAL has committed frames but logical log header is invalid\".to_string(),\n                ))\n            }\n        };\n        self.storage.set_header(header);\n\n        // NOTE: this uses `CheckpointMode::Truncate` to drive WAL backfill only; we still\n        // truncate the WAL explicitly below to preserve WAL-last ordering in recovery.\n        let mut checkpoint_result = pager.io.block(|| {\n            wal.checkpoint(\n                &pager,\n                CheckpointMode::Truncate {\n                    upper_bound_inclusive: None,\n                },\n            )\n        })?;\n        if !checkpoint_result.everything_backfilled() {\n            return Err(LimboError::Corrupt(\n                \"Unable to fully backfill committed WAL frames during MVCC recovery\".to_string(),\n            ));\n        }\n\n        if connection.get_sync_mode() != SyncMode::Off\n            && checkpoint_result.wal_checkpoint_backfilled > 0\n        {\n            let c = pager\n                .db_file\n                .sync(Completion::new_sync(|_| {}), pager.get_sync_type())?;\n            pager.io.wait_for_completion(c)?;\n        }\n\n        // Write a fresh log header (distinct from the checkpoint state machine which no\n        // longer rewrites the header). This is bootstrap-only: we need a valid header on\n        // disk before truncating WAL. CRC verify + retry guards against torn header writes.\n        let mut retried_crc = false;\n        loop {\n            let c = self.storage.update_header()?;\n            pager.io.wait_for_completion(c)?;\n\n            if connection.get_sync_mode() != SyncMode::Off {\n                let c = self.storage.sync(pager.get_sync_type())?;\n                pager.io.wait_for_completion(c)?;\n            }\n\n            if self.logical_log_header_crc_valid(&pager)? {\n                break;\n            }\n\n            if retried_crc {\n                return Err(LimboError::Corrupt(\n                    \"Logical log header CRC mismatch after retry\".to_string(),\n                ));\n            }\n            retried_crc = true;\n        }\n\n        pager\n            .io\n            .block(|| wal.truncate_wal(&mut checkpoint_result, pager.get_sync_type()))?;\n        Ok(())\n    }\n\n    /// Replays committed logical-log frames into the in-memory MVCC store.\n    /// Only frames with `commit_ts > persistent_tx_ts_max` (the durable replay boundary\n    /// from the metadata table) are applied; earlier frames were already checkpointed.\n    /// On success, reseeds the MVCC clock to `max(persistent_tx_ts_max, max_replayed_commit_ts) + 1`\n    /// and sets the log writer offset to `last_valid_offset` so torn-tail bytes are overwritten.\n    /// Returns true if any frames were replayed, false otherwise.\n    pub fn maybe_recover_logical_log(&self, connection: Arc<Connection>) -> Result<bool> {\n        let pager = connection.pager.load().clone();\n        let file = self.get_logical_log_file();\n        let mut reader = StreamingLogicalLogReader::new(file.clone(), None);\n        let preserved_table_valued_functions =\n            Self::capture_table_valued_functions(&connection.schema.read());\n\n        let header = match reader.try_read_header(&pager.io)? {\n            HeaderReadResult::Valid(header) => Some(header),\n            HeaderReadResult::NoLog => None,\n            HeaderReadResult::Invalid => {\n                return Err(LimboError::Corrupt(\n                    \"Logical log header corrupt and no WAL recovery available\".to_string(),\n                ))\n            }\n        };\n\n        if let Some(header) = &header {\n            self.storage.set_header(header.clone());\n        }\n        let persistent_tx_ts_max = if self.uses_durable_mvcc_metadata(&connection) {\n            match self.try_read_persistent_tx_ts_max(&connection)? {\n                Some(ts) => ts,\n                None if pager.is_encryption_enabled() => 0,\n                None if header.is_none() => 0,\n                None => {\n                    return Err(LimboError::Corrupt(\n                        \"Missing MVCC metadata table\".to_string(),\n                    ))\n                }\n            }\n        } else {\n            0\n        };\n        self.durable_txid_max\n            .store(persistent_tx_ts_max, Ordering::SeqCst);\n        self.clock.reset(persistent_tx_ts_max + 1);\n\n        if header.is_none() || file.size()? <= LOG_HDR_SIZE as u64 {\n            return Ok(false);\n        }\n\n        let mut max_commit_ts_seen = persistent_tx_ts_max;\n        let replay_cutoff_ts = persistent_tx_ts_max;\n        let mut schema_rows: HashMap<i64, ImmutableRecord> = HashMap::default();\n        let mut dropped_root_pages: HashSet<i64> = HashSet::default();\n        if let Some(mut stmt) = connection\n            .query(\"SELECT rowid, type, name, tbl_name, rootpage, sql FROM sqlite_schema\")?\n        {\n            stmt.run_with_row_callback(|row| {\n                let rowid = row.get::<i64>(0)?;\n                let values = (1..=5)\n                    .map(|i| row.get_value(i).clone())\n                    .collect::<Vec<_>>();\n                schema_rows.insert(rowid, ImmutableRecord::from_values(&values, values.len()));\n                Ok(())\n            })?;\n        }\n        let mut index_infos: HashMap<MVTableId, Arc<IndexInfo>> = HashMap::default();\n\n        // Track whether we have pending schema changes that need a rebuild.\n        // We defer rebuild_schema() until all consecutive schema rows have been\n        // inserted, because an intermediate rebuild (e.g. after inserting the\n        // renamed table row but before inserting the renamed index row) can see\n        // an inconsistent state and panic in populate_indices().\n        // Cell is used so the get_index_info closure can flush the pending rebuild.\n        let needs_schema_rebuild = std::cell::Cell::new(false);\n\n        let rebuild_schema =\n            |connection: &Arc<Connection>, schema_rows: &HashMap<i64, ImmutableRecord>| {\n                let pager = connection.pager.load().clone();\n                let cookie = self\n                    .global_header\n                    .read()\n                    .as_ref()\n                    .map(|header| header.schema_cookie.get())\n                    .unwrap_or(\n                        pager\n                            .io\n                            .block(|| pager.with_header(|header| header.schema_cookie))?\n                            .get(),\n                    );\n                let mut fresh = Schema::new();\n                fresh.schema_version = cookie;\n                let mut from_sql_indexes = Vec::with_capacity(10);\n                let mut automatic_indices: HashMap<String, Vec<(String, i64)>> = HashMap::default();\n                let mut dbsp_state_roots: HashMap<String, i64> = HashMap::default();\n                let mut dbsp_state_index_roots: HashMap<String, i64> = HashMap::default();\n                let mut materialized_view_info: HashMap<String, (String, i64)> = HashMap::default();\n                let syms = connection.syms.read();\n                let mv_store = connection.db.get_mv_store().clone();\n\n                for record in schema_rows.values() {\n                    let ty = match record.get_value_opt(0) {\n                        Some(ValueRef::Text(v)) => v.as_str(),\n                        _ => {\n                            return Err(LimboError::Corrupt(\n                                \"sqlite_schema type must be text\".to_string(),\n                            ))\n                        }\n                    };\n                    let name = match record.get_value_opt(1) {\n                        Some(ValueRef::Text(v)) => v.as_str(),\n                        _ => {\n                            return Err(LimboError::Corrupt(\n                                \"sqlite_schema name must be text\".to_string(),\n                            ))\n                        }\n                    };\n                    let table_name = match record.get_value_opt(2) {\n                        Some(ValueRef::Text(v)) => v.as_str(),\n                        _ => {\n                            return Err(LimboError::Corrupt(\n                                \"sqlite_schema tbl_name must be text\".to_string(),\n                            ))\n                        }\n                    };\n                    let root_page = match record.get_value_opt(3) {\n                        Some(ValueRef::Numeric(Numeric::Integer(v))) => v,\n                        _ => {\n                            return Err(LimboError::Corrupt(\n                                \"sqlite_schema root_page must be integer\".to_string(),\n                            ))\n                        }\n                    };\n                    let sql = match record.get_value_opt(4) {\n                        Some(ValueRef::Text(v)) => Some(v.as_str()),\n                        _ => None,\n                    };\n                    fresh.handle_schema_row(\n                        ty,\n                        name,\n                        table_name,\n                        root_page,\n                        sql,\n                        &syms,\n                        &mut from_sql_indexes,\n                        &mut automatic_indices,\n                        &mut dbsp_state_roots,\n                        &mut dbsp_state_index_roots,\n                        &mut materialized_view_info,\n                    )?;\n                }\n                fresh.populate_indices(\n                    &syms,\n                    from_sql_indexes,\n                    automatic_indices,\n                    mv_store.is_some(),\n                )?;\n                fresh.populate_materialized_views(\n                    materialized_view_info,\n                    dbsp_state_roots,\n                    dbsp_state_index_roots,\n                )?;\n                Self::rehydrate_table_valued_functions(\n                    &mut fresh,\n                    &preserved_table_valued_functions,\n                );\n\n                let fresh = Arc::new(fresh);\n                *connection.schema.write() = fresh.clone();\n                *connection.db.schema.lock() = fresh;\n                Ok(())\n            };\n\n        loop {\n            let mut get_index_info = |index_id: MVTableId| -> Result<Arc<IndexInfo>> {\n                if let Some(index_info) = index_infos.get(&index_id) {\n                    Ok(index_info.clone())\n                } else {\n                    // Flush any pending schema rebuild so we can see newly created indexes.\n                    if needs_schema_rebuild.get() {\n                        rebuild_schema(&connection, &schema_rows)?;\n                        index_infos.clear();\n                        needs_schema_rebuild.set(false);\n                    }\n                    let schema = connection.schema.read();\n                    let root_page = self\n                        .table_id_to_rootpage\n                        .get(&index_id)\n                        .and_then(|entry| *entry.value())\n                        .map(|value| value as i64)\n                        .unwrap_or_else(|| i64::from(index_id)); // this can be negative for non-checkpointed indexes\n\n                    let index = schema\n                        .indexes\n                        .values()\n                        .flatten()\n                        .find(|idx| idx.root_page == root_page)\n                        .ok_or_else(|| {\n                            LimboError::InternalError(format!(\n                                \"Index with root page {root_page} not found in schema\",\n                            ))\n                        })?;\n                    let index_info = Arc::new(IndexInfo::new_from_index(index));\n                    index_infos.insert(index_id, index_info.clone());\n                    Ok(index_info)\n                }\n            };\n            let next_rec = reader.next_record(&pager.io, &mut get_index_info)?;\n\n            tracing::trace!(\"next_rec {next_rec:?}\");\n\n            match next_rec {\n                StreamingResult::UpsertTableRow {\n                    row,\n                    rowid,\n                    commit_ts,\n                    btree_resident,\n                } => {\n                    max_commit_ts_seen = max_commit_ts_seen.max(commit_ts);\n                    if commit_ts <= replay_cutoff_ts {\n                        continue;\n                    }\n                    let is_schema_row = rowid.table_id == SQLITE_SCHEMA_MVCC_TABLE_ID;\n                    if is_schema_row {\n                        let row_data = row.payload().to_vec();\n                        let record = ImmutableRecord::from_bin_record(row_data);\n                        if record.column_count() < 5 {\n                            return Err(LimboError::Corrupt(format!(\n                                \"sqlite_schema row must have at least 5 columns, got {}\",\n                                record.column_count()\n                            )));\n                        }\n                        let Some(ValueRef::Text(row_type)) = record.get_value_opt(0) else {\n                            return Err(LimboError::Corrupt(\n                                \"sqlite_schema type must be text\".to_string(),\n                            ));\n                        };\n                        let row_type = row_type.as_str();\n                        let val = match record.get_value_opt(3) {\n                            Some(v) => v,\n                            None => {\n                                return Err(LimboError::InternalError(\n                                    \"Expected at least 5 columns in sqlite_schema\".to_string(),\n                                ));\n                            }\n                        };\n                        let ValueRef::Numeric(crate::numeric::Numeric::Integer(root_page)) = val\n                        else {\n                            panic!(\"Expected integer value for root page, got {val:?}\");\n                        };\n                        let sql = match record.get_value_opt(4) {\n                            Some(ValueRef::Text(v)) => Some(v.as_str()),\n                            _ => None,\n                        };\n                        let is_virtual_table = row_type == \"table\"\n                            && sql.is_some_and(|sql| {\n                                contains_ignore_ascii_case!(sql.as_bytes(), b\"create virtual\")\n                            });\n                        let has_btree = match row_type {\n                            \"index\" => true,\n                            \"table\" => !is_virtual_table,\n                            _ => false,\n                        };\n                        if has_btree {\n                            if root_page == 0 {\n                                return Err(LimboError::Corrupt(format!(\n                                    \"sqlite_schema root_page=0 for btree {row_type}\"\n                                )));\n                            }\n                            if root_page < 0 {\n                                let table_id = self.get_table_id_from_root_page(root_page);\n                                if let Some(entry) = self.table_id_to_rootpage.get(&table_id) {\n                                    if let Some(value) = *entry.value() {\n                                        panic!(\"Logical log contains an insertion of a sqlite_schema record that has both a negative root page and a positive root page: {root_page} & {value}\");\n                                    }\n                                }\n                                self.insert_table_id_to_rootpage(table_id, None);\n                            } else {\n                                let table_id = self.get_table_id_from_root_page(root_page);\n                                let Some(entry) = self.table_id_to_rootpage.get(&table_id) else {\n                                    panic!(\"Logical log contains root page reference {root_page} that does not exist in the table_id_to_rootpage map\");\n                                };\n                                let Some(value) = *entry.value() else {\n                                    panic!(\"Logical log contains root page reference {root_page} that does not have a root page in the table_id_to_rootpage map\");\n                                };\n                                turso_assert_eq!(value, root_page as u64, \"logical log root page does not match table_id_to_rootpage map\", { \"root_page\": root_page, \"map_value\": value });\n                            }\n                        } else if root_page != 0 {\n                            return Err(LimboError::Corrupt(format!(\n                                \"sqlite_schema root_page must be 0 for {row_type}, got {root_page}\"\n                            )));\n                        }\n                        let rowid_int = rowid.row_id.to_int_or_panic();\n                        schema_rows.insert(rowid_int, record);\n                        needs_schema_rebuild.set(true);\n                    } else {\n                        turso_assert!(self.table_id_to_rootpage.get(&rowid.table_id).is_some(),\n                        \"Logical log contains a row version insert with a table id that does not exist in the table_id_to_rootpage map\",\n                        {\"table_id\": rowid.table_id,\n                            \"table_id_to_rootpage_map\": format!(\"{:?}\", self.table_id_to_rootpage.iter().collect::<Vec<_>>())\n                        });\n                    }\n\n                    let version_id = self.get_version_id();\n                    let row_version = RowVersion {\n                        id: version_id,\n                        begin: Some(TxTimestampOrID::Timestamp(commit_ts)),\n                        end: None,\n                        row: row.clone(),\n                        btree_resident,\n                    };\n                    {\n                        let versions = self\n                            .rows\n                            .get_or_insert_with(rowid.clone(), || RwLock::new(Vec::new()));\n                        let mut versions = versions.value().write();\n                        self.insert_version_raw(&mut versions, row_version);\n                    }\n                    let allocator = self.get_rowid_allocator(&rowid.table_id);\n                    allocator.insert_row_id_maybe_update(rowid.row_id.to_int_or_panic());\n                }\n                StreamingResult::DeleteTableRow {\n                    rowid,\n                    commit_ts,\n                    btree_resident,\n                } => {\n                    max_commit_ts_seen = max_commit_ts_seen.max(commit_ts);\n                    if commit_ts <= replay_cutoff_ts {\n                        continue;\n                    }\n                    turso_assert!(self.table_id_to_rootpage.get(&rowid.table_id).is_some(),\n                        \"Logical log contains a row version delete with a table id that does not exist in the table_id_to_rootpage map\",\n                        {\"rootpage_map\": rowid.table_id});\n                    if let Some(versions) = self.rows.get(&rowid) {\n                        // Row exists in memory — try to find the current (non-ended) version\n                        // that was committed before this delete, and mark it as ended. If no\n                        // such version exists (e.g. it was already GC'd or this is a B-tree\n                        // resident row not yet in memory), insert a tombstone instead.\n                        let mut versions = versions.value().write();\n                        if let Some(existing) = versions.iter_mut().rev().find(|rv| {\n                            rv.end.is_none()\n                                && matches!(rv.begin, Some(TxTimestampOrID::Timestamp(b)) if b < commit_ts)\n                        }) {\n                            existing.end = Some(TxTimestampOrID::Timestamp(commit_ts));\n                        } else {\n                            let version_id = self.get_version_id();\n                            let row = Row::new_table_row(rowid.clone(), Vec::new(), 0);\n                            let row_version = RowVersion {\n                                id: version_id,\n                                begin: None,\n                                end: Some(TxTimestampOrID::Timestamp(commit_ts)),\n                                row,\n                                btree_resident,\n                            };\n                            self.insert_version_raw(&mut versions, row_version);\n                        }\n                    } else {\n                        let version_id = self.get_version_id();\n                        let row = Row::new_table_row(rowid.clone(), Vec::new(), 0);\n                        let row_version = RowVersion {\n                            id: version_id,\n                            begin: None,\n                            end: Some(TxTimestampOrID::Timestamp(commit_ts)),\n                            row,\n                            btree_resident,\n                        };\n                        let versions = self\n                            .rows\n                            .get_or_insert_with(rowid.clone(), || RwLock::new(Vec::new()));\n                        let mut versions = versions.value().write();\n                        self.insert_version_raw(&mut versions, row_version);\n                    }\n                    if rowid.table_id == SQLITE_SCHEMA_MVCC_TABLE_ID {\n                        let rowid_int = rowid.row_id.to_int_or_panic();\n                        let record = schema_rows.get(&rowid_int).ok_or_else(|| {\n                            LimboError::Corrupt(format!(\n                                \"Logical log deletes sqlite_schema rowid {rowid_int} that does not exist in merged schema state\"\n                            ))\n                        })?;\n                        if record.column_count() < 5 {\n                            return Err(LimboError::Corrupt(format!(\n                                \"sqlite_schema row must have at least 5 columns, got {}\",\n                                record.column_count()\n                            )));\n                        }\n                        let Some(ValueRef::Text(row_type)) = record.get_value_opt(0) else {\n                            return Err(LimboError::Corrupt(\n                                \"sqlite_schema type must be text\".to_string(),\n                            ));\n                        };\n                        let row_type = row_type.as_str();\n                        let Some(ValueRef::Numeric(Numeric::Integer(root_page))) =\n                            record.get_value_opt(3)\n                        else {\n                            return Err(LimboError::Corrupt(\n                                \"sqlite_schema root_page must be integer\".to_string(),\n                            ));\n                        };\n                        if (row_type == \"table\" || row_type == \"index\") && root_page > 0 {\n                            dropped_root_pages.insert(root_page);\n                        }\n                        schema_rows.remove(&rowid_int);\n                        needs_schema_rebuild.set(true);\n                    }\n                }\n                StreamingResult::UpsertIndexRow {\n                    row,\n                    rowid,\n                    commit_ts,\n                    btree_resident,\n                } => {\n                    max_commit_ts_seen = max_commit_ts_seen.max(commit_ts);\n                    if commit_ts <= replay_cutoff_ts {\n                        continue;\n                    }\n                    let version_id = self.get_version_id();\n                    let row_version = RowVersion {\n                        id: version_id,\n                        begin: Some(TxTimestampOrID::Timestamp(commit_ts)),\n                        end: None,\n                        row: row.clone(),\n                        btree_resident,\n                    };\n                    let RowKey::Record(sortable_key) = rowid.row_id.clone() else {\n                        panic!(\"Index writes must be to a record\");\n                    };\n                    let sortable_key =\n                        self.get_or_create_index_key_arc(rowid.table_id, sortable_key);\n                    self.insert_index_version(rowid.table_id, sortable_key, row_version);\n                }\n                StreamingResult::DeleteIndexRow {\n                    row,\n                    rowid,\n                    commit_ts,\n                    btree_resident,\n                } => {\n                    max_commit_ts_seen = max_commit_ts_seen.max(commit_ts);\n                    if commit_ts <= replay_cutoff_ts {\n                        continue;\n                    }\n                    let RowKey::Record(sortable_key) = rowid.row_id.clone() else {\n                        panic!(\"Index writes must be to a record\");\n                    };\n                    let sortable_key =\n                        self.get_or_create_index_key_arc(rowid.table_id, sortable_key);\n                    if let Some(index_map) = self.index_rows.get(&rowid.table_id) {\n                        if let Some(versions) = index_map.value().get(&sortable_key) {\n                            let mut versions = versions.value().write();\n                            if let Some(existing) = versions.iter_mut().rev().find(|rv| {\n                                rv.end.is_none()\n                                    && matches!(rv.begin, Some(TxTimestampOrID::Timestamp(b)) if b < commit_ts)\n                            }) {\n                                existing.end = Some(TxTimestampOrID::Timestamp(commit_ts));\n                                continue;\n                            }\n                        }\n                    }\n                    let version_id = self.get_version_id();\n                    let row_version = RowVersion {\n                        id: version_id,\n                        begin: None,\n                        end: Some(TxTimestampOrID::Timestamp(commit_ts)),\n                        row: row.clone(),\n                        btree_resident,\n                    };\n                    self.insert_index_version(rowid.table_id, sortable_key, row_version);\n                }\n                StreamingResult::UpdateHeader { header, commit_ts } => {\n                    max_commit_ts_seen = max_commit_ts_seen.max(commit_ts);\n                    if commit_ts <= replay_cutoff_ts {\n                        continue;\n                    }\n                    // Recovery applies only post-boundary header ops; the same value is later\n                    // staged to pager page-1 during checkpoint.\n                    self.global_header.write().replace(header);\n                }\n                StreamingResult::Eof => {\n                    let recovered_offset = reader.last_valid_offset() as u64;\n                    let recovered_running_crc = reader.running_crc();\n                    self.storage.restore_logical_log_state_after_recovery(\n                        recovered_offset,\n                        recovered_running_crc,\n                    );\n                    break;\n                }\n            }\n        }\n        // Flush any remaining pending schema rebuild after processing all log records.\n        if needs_schema_rebuild.get() {\n            rebuild_schema(&connection, &schema_rows)?;\n        }\n\n        assert!(\n            max_commit_ts_seen >= persistent_tx_ts_max,\n            \"replay clock would rewind below metadata boundary: max_commit_ts_seen={max_commit_ts_seen} persistent_tx_ts_max={persistent_tx_ts_max}\"\n        );\n        connection.with_schema_mut(|schema| {\n            schema.dropped_root_pages = dropped_root_pages;\n        });\n        if let Some(header) = self.global_header.read().as_ref() {\n            // Replay may rebuild schema rows before seeing a later UpdateHeader op in the same\n            // logical-log tail. Normalize to the final recovered header cookie so VDBE schema\n            // cookie checks do not observe a stale in-memory schema version after restart.\n            connection.with_schema_mut(|schema| {\n                schema.schema_version = header.schema_cookie.get();\n            });\n        }\n        *connection.db.schema.lock() = connection.schema.read().clone();\n        self.clock.reset(max_commit_ts_seen + 1);\n        self.last_committed_tx_ts\n            .store(max_commit_ts_seen, Ordering::SeqCst);\n        Ok(true)\n    }\n\n    pub fn set_checkpoint_threshold(&self, threshold: i64) {\n        self.storage.set_checkpoint_threshold(threshold)\n    }\n\n    pub fn checkpoint_threshold(&self) -> i64 {\n        self.storage.checkpoint_threshold()\n    }\n\n    pub fn get_real_table_id(&self, table_id: i64) -> i64 {\n        let entry = self.table_id_to_rootpage.get(&MVTableId::from(table_id));\n        if let Some(entry) = entry {\n            entry.value().map_or(table_id, |value| value as i64)\n        } else {\n            table_id\n        }\n    }\n\n    pub fn get_rowid_allocator(&self, table_id: &MVTableId) -> Arc<RowidAllocator> {\n        let mut map = self.table_id_to_last_rowid.write();\n        if map.contains_key(table_id) {\n            map.get(table_id).unwrap().clone()\n        } else {\n            let allocator = Arc::new(RowidAllocator {\n                lock: TursoRwLock::new(),\n                max_rowid: AtomicI64::new(0),\n                initialized: AtomicBool::new(false),\n            });\n            map.insert(*table_id, allocator.clone());\n            allocator\n        }\n    }\n\n    pub fn is_btree_allocated(&self, table_id: &MVTableId) -> bool {\n        let maybe_root_page = self.table_id_to_rootpage.get(table_id);\n        maybe_root_page.is_some_and(|entry| entry.value().is_some())\n    }\n}\n\nfn rollback_row_version(tx_id: u64, rv: &mut RowVersion) {\n    if rv.begin == Some(TxTimestampOrID::TxID(tx_id)) {\n        // If the transaction has aborted,\n        // it marks all its new versions as garbage and sets their Begin\n        // and End timestamps to infinity to make them invisible\n        // See section 2.4: https://www.cs.cmu.edu/~15721-f24/papers/Hekaton.pdf\n        rv.begin = None;\n        rv.end = None;\n    } else if rv.end == Some(TxTimestampOrID::TxID(tx_id)) {\n        // undo deletions by this transaction\n        rv.end = None;\n    }\n}\n\nimpl RowidAllocator {\n    /// Lock-free rowid allocation via atomic CAS.\n    /// Returns None only when at i64::MAX (triggers random fallback).\n    /// Returns Some((new_rowid, prev_rowid)) where prev_rowid is None if table was empty.\n    pub fn get_next_rowid(&self) -> Option<(i64, Option<i64>)> {\n        loop {\n            let cur = self.max_rowid.load(Ordering::SeqCst);\n            if cur == i64::MAX {\n                tracing::trace!(\"get_next_rowid(max)\");\n                return None;\n            }\n            let next = cur + 1;\n            if self\n                .max_rowid\n                .compare_exchange(cur, next, Ordering::SeqCst, Ordering::SeqCst)\n                .is_ok()\n            {\n                let prev = if cur == 0 { None } else { Some(cur) };\n                tracing::trace!(\"get_next_rowid({next})\");\n                return Some((next, prev));\n            }\n        }\n    }\n\n    /// Bump the counter to at least `rowid`. Used for user-specified rowids\n    /// (e.g. INSERT INTO t(rowid,...) VALUES(1000,...)).\n    pub fn insert_row_id_maybe_update(&self, rowid: i64) {\n        loop {\n            let cur = self.max_rowid.load(Ordering::SeqCst);\n            if rowid <= cur {\n                return;\n            }\n            if self\n                .max_rowid\n                .compare_exchange(cur, rowid, Ordering::SeqCst, Ordering::SeqCst)\n                .is_ok()\n            {\n                return;\n            }\n        }\n    }\n\n    pub fn is_uninitialized(&self) -> bool {\n        !self.initialized.load(Ordering::SeqCst)\n    }\n\n    /// Initialize from btree max. Called once per table, under lock.\n    pub fn initialize(&self, rowid: Option<i64>) {\n        tracing::trace!(\"initialize({rowid:?})\");\n        self.max_rowid.store(rowid.unwrap_or(0), Ordering::SeqCst);\n        self.initialized.store(true, Ordering::SeqCst);\n    }\n\n    pub fn lock(&self) -> bool {\n        self.lock.write()\n    }\n\n    pub fn unlock(&self) {\n        self.lock.unlock()\n    }\n}\n\npub fn create_seek_range<K: Ord>(\n    limit_boundary: Bound<K>,\n    direction: IterationDirection,\n) -> (Bound<K>, Bound<K>) {\n    if direction == IterationDirection::Forwards {\n        (limit_boundary, Bound::Unbounded)\n    } else {\n        (Bound::Unbounded, limit_boundary)\n    }\n}\n\n/// A write-write conflict happens when transaction T_current attempts to update a\n/// row version that is:\n/// a) currently being updated by an active transaction T_previous, or\n/// b) was updated by an ended transaction T_previous that committed AFTER T_current started\n/// but BEFORE T_previous commits.\n///\n/// \"Suppose transaction T wants to update a version V. V is updatable\n/// only if it is the latest version, that is, it has an end timestamp equal\n/// to infinity or its End field contains the ID of a transaction TE and\n/// TE’s state is Aborted\"\n/// Ref: https://www.cs.cmu.edu/~15721-f24/papers/Hekaton.pdf , page 301,\n/// 2.6. Updating a Version.\nfn is_write_write_conflict(\n    txs: &SkipMap<TxID, Transaction>,\n    finalized_tx_states: &SkipMap<TxID, TransactionState>,\n    tx: &Transaction,\n    rv: &RowVersion,\n) -> bool {\n    match rv.end {\n        Some(TxTimestampOrID::TxID(rv_end)) => {\n            if rv_end == tx.tx_id {\n                return false;\n            }\n            match lookup_tx_state(txs, finalized_tx_states, rv_end) {\n                Some(TransactionState::Aborted) | Some(TransactionState::Terminated) => false,\n                Some(TransactionState::Active)\n                | Some(TransactionState::Preparing(_))\n                | Some(TransactionState::Committed(_)) => true,\n                None => {\n                    tracing::debug!(\n                        \"is_write_write_conflict: missing tx {} for row version {:?}; treating as conflict\",\n                        rv_end,\n                        rv\n                    );\n                    true\n                }\n            }\n        }\n        // A non-\"infinity\" end timestamp (here modeled by Some(ts)) functions as a write lock\n        // on the row, so it can never be updated by another transaction.\n        // Ref: https://www.cs.cmu.edu/~15721-f24/papers/Hekaton.pdf , page 301,\n        // 2.6. Updating a Version.\n        Some(TxTimestampOrID::Timestamp(_)) => true,\n        None => false,\n    }\n}\n\nimpl RowVersion {\n    /// A row is visible to a transaction if:\n    /// * Begin is visible to the transaction\n    /// * End timestamp is not applicable yet, meaning deletion of row is not visible to this transaction\n    fn is_visible_to(\n        &self,\n        tx: &Transaction,\n        txs: &SkipMap<TxID, Transaction>,\n        finalized_tx_states: &SkipMap<TxID, TransactionState>,\n    ) -> bool {\n        is_begin_visible(txs, finalized_tx_states, tx, self)\n            && is_end_visible(txs, finalized_tx_states, tx, self)\n    }\n\n    /// Check if this version indicates the B-tree row has been modified (updated or deleted).\n    ///\n    /// A version is \"relevant\" to a transaction if:\n    /// 1. The version is fully visible (begin visible AND end visible), OR\n    /// 2. The version has an end timestamp that indicates the row was deleted before/at the transaction's begin, OR\n    /// 3. The current transaction itself has deleted/updated this row (end = current tx_id)\n    ///\n    /// This is used by dual-cursor to determine if a B-tree row should be shown or hidden.\n    fn is_btree_invalidating_version(\n        &self,\n        tx: &Transaction,\n        txs: &SkipMap<TxID, Transaction>,\n        finalized_tx_states: &SkipMap<TxID, TransactionState>,\n    ) -> bool {\n        // If the version is fully visible, it invalidates the B-tree\n        if self.is_visible_to(tx, txs, finalized_tx_states) {\n            return true;\n        }\n\n        // Check if this version represents a deletion/update that affects us\n        match self.end {\n            Some(TxTimestampOrID::Timestamp(end_ts)) => {\n                // Row was deleted at end_ts. If we started after end_ts, we shouldn't see it\n                turso_assert!(\n                    tx.begin_ts != end_ts,\n                    \"begin_ts and committed end_ts cannot be equal: txn timestamps are strictly monotonic\"\n                );\n                tx.begin_ts > end_ts\n            }\n            Some(TxTimestampOrID::TxID(end_tx_id)) => {\n                // Row is being deleted/updated by another transaction\n                // If it's OUR transaction, the B-tree row is invalid (we deleted/updated it)\n                end_tx_id == tx.tx_id\n            }\n            None => false,\n        }\n    }\n}\n\n/// Hekaton Section 2.7 — register-and-report protocol:\n/// \"To take a commit dependency on a transaction T2, T1 increments its\n/// CommitDepCounter and adds its transaction ID to T2's CommitDepSet.\"\n///\n/// The lock on `commit_dep_set` serializes with the drain in commit/abort\n/// resolution, preventing the race where we push an entry after the drain.\nfn register_commit_dependency(\n    txs: &SkipMap<TxID, Transaction>,\n    dependent_tx: &Transaction,\n    depended_on_tx_id: TxID,\n) {\n    turso_assert!(\n        dependent_tx.tx_id != depended_on_tx_id,\n        \"transaction cannot depend on itself\"\n    );\n    let Some(depended_on) = txs.get(&depended_on_tx_id) else {\n        // Transaction was already committed and removed from the map\n        // (CommitEnd calls remove_tx after setting Committed and draining\n        // CommitDepSet). Dependency is trivially resolved.\n        return;\n    };\n    let depended_on = depended_on.value();\n\n    // Hold lock while checking state to serialize with the drain in\n    // commit/abort postprocessing.\n    let mut dep_set = depended_on.commit_dep_set.lock();\n    match depended_on.state.load() {\n        TransactionState::Preparing(_) => {\n            // Increment counter BEFORE inserting into dep_set and BEFORE dropping\n            // the lock. This prevents underflow: if we inserted first and\n            // released the lock, the depended-on tx could drain the dep_set\n            // and call fetch_sub before we increment, wrapping the counter\n            // from 0 to u64::MAX. Only increment on first insertion (dedup).\n            if dep_set.insert(dependent_tx.tx_id) {\n                dependent_tx\n                    .commit_dep_counter\n                    .fetch_add(1, Ordering::AcqRel);\n            }\n            drop(dep_set);\n            tracing::trace!(\n                \"register_commit_dependency: tx {} depends on tx {}\",\n                dependent_tx.tx_id,\n                depended_on_tx_id\n            );\n        }\n        TransactionState::Active => {\n            turso_assert!(false, \"a txn found dependent on active txn\");\n        }\n        TransactionState::Committed(_) => {\n            // Already committed — dependency trivially resolved.\n        }\n        TransactionState::Aborted | TransactionState::Terminated => {\n            // Already aborted — cascade abort to dependent.\n            drop(dep_set);\n            dependent_tx.abort_now.store(true, Ordering::Release);\n            tracing::trace!(\n                \"register_commit_dependency: tx {} must abort (dep tx {} aborted)\",\n                dependent_tx.tx_id,\n                depended_on_tx_id\n            );\n        }\n    }\n}\n\nfn lookup_tx_state(\n    txs: &SkipMap<TxID, Transaction>,\n    finalized_tx_states: &SkipMap<TxID, TransactionState>,\n    tx_id: TxID,\n) -> Option<TransactionState> {\n    txs.get(&tx_id)\n        .map(|entry| entry.value().state.load())\n        .or_else(|| finalized_tx_states.get(&tx_id).map(|entry| *entry.value()))\n}\n\nfn lookup_finalized_tx_state(\n    finalized_tx_states: &SkipMap<TxID, TransactionState>,\n    tx_id: TxID,\n) -> Option<TransactionState> {\n    finalized_tx_states.get(&tx_id).map(|entry| {\n        let state = *entry.value();\n        turso_assert!(\n            !matches!(\n                state,\n                TransactionState::Active | TransactionState::Preparing(_)\n            ),\n            \"finalized_tx_states contains non-final state for tx {tx_id}: {state:?}\"\n        );\n        state\n    })\n}\n\nfn is_begin_visible(\n    txs: &SkipMap<TxID, Transaction>,\n    finalized_tx_states: &SkipMap<TxID, TransactionState>,\n    tx: &Transaction,\n    rv: &RowVersion,\n) -> bool {\n    match rv.begin {\n        Some(TxTimestampOrID::Timestamp(rv_begin_ts)) => {\n            turso_assert!(\n                tx.begin_ts != rv_begin_ts,\n                \"begin_ts and committed rv_begin_ts cannot be equal: txn timestamps are strictly monotonic\"\n            );\n            tx.begin_ts > rv_begin_ts\n        }\n        Some(TxTimestampOrID::TxID(rv_begin)) => {\n            let visible = match txs.get(&rv_begin) {\n                Some(tb_entry) => {\n                    let tb = tb_entry.value();\n                    let visible = match tb.state.load() {\n                        TransactionState::Active => tx.tx_id == tb.tx_id && rv.end.is_none(),\n                        TransactionState::Preparing(end_ts) => {\n                            // Hekaton Table 1 / Section 2.5: speculative read of TB.\n                            // If begin_ts > end_ts, the version would be visible once TB\n                            // commits. Speculatively return true and register a dependency.\n                            // Fixes partial commit visibility (Bug #8).\n                            turso_assert!(\n                                tx.tx_id != tb.tx_id,\n                                \"a txn cannot read its own row versions during prepare\"\n                            );\n                            turso_assert!(\n                                tx.begin_ts != end_ts,\n                                \"begin_ts and preparing end_ts cannot be equal: txn timestamps are strictly monotonic\"\n                            );\n                            if tx.begin_ts > end_ts {\n                                register_commit_dependency(txs, tx, rv_begin);\n                                true\n                            } else {\n                                false\n                            }\n                        }\n                        TransactionState::Committed(committed_ts) => {\n                            turso_assert!(\n                                tx.begin_ts != committed_ts,\n                                \"begin_ts and committed_ts cannot be equal: txn timestamps are strictly monotonic\"\n                            );\n                            tx.begin_ts > committed_ts\n                        }\n                        TransactionState::Aborted => false,\n                        TransactionState::Terminated => {\n                            tracing::debug!(\n                                \"TODO: should reread rv's end field - it should have updated the timestamp in the row version by now\"\n                            );\n                            false\n                        }\n                    };\n                    tracing::trace!(\n                        \"is_begin_visible: tx={tx}, tb={tb} rv = {:?}-{:?} visible = {visible}\",\n                        rv.begin,\n                        rv.end\n                    );\n                    visible\n                }\n                None => match lookup_finalized_tx_state(finalized_tx_states, rv_begin) {\n                    Some(TransactionState::Committed(committed_ts)) => {\n                        turso_assert!(\n                            tx.begin_ts != committed_ts,\n                            \"begin_ts and committed_ts cannot be equal: txn timestamps are strictly monotonic\"\n                        );\n                        tx.begin_ts > committed_ts\n                    }\n                    Some(TransactionState::Aborted) | Some(TransactionState::Terminated) => false,\n                    Some(TransactionState::Active) | Some(TransactionState::Preparing(_)) => {\n                        unreachable!(\n                            \"is_begin_visible: live tx {} missing from txs but present in finalized cache\",\n                            rv_begin\n                        );\n                    }\n                    None => {\n                        // Transaction was removed from the map after converting its TxID refs\n                        // to Timestamps. The begin field should have been updated but we still\n                        // see the stale TxID. Conservative fallback.\n                        false\n                    }\n                },\n            };\n            visible\n        }\n        None => false,\n    }\n}\n\nfn is_end_visible(\n    txs: &SkipMap<TxID, Transaction>,\n    finalized_tx_states: &SkipMap<TxID, TransactionState>,\n    current_tx: &Transaction,\n    row_version: &RowVersion,\n) -> bool {\n    match row_version.end {\n        Some(TxTimestampOrID::Timestamp(rv_end_ts)) => current_tx.begin_ts < rv_end_ts,\n        Some(TxTimestampOrID::TxID(rv_end)) => {\n            let visible = match txs.get(&rv_end) {\n                Some(other_tx_entry) => {\n                    let other_tx = other_tx_entry.value();\n                    let visible = match other_tx.state.load() {\n                        // V's sharp mind discovered an issue with the hekaton paper which basically states that a\n                        // transaction can see a row version if the end is a TXId only if it isn't the same transaction.\n                        // Source: https://avi.im/blag/2023/hekaton-paper-typo/\n                        TransactionState::Active => current_tx.tx_id != other_tx.tx_id,\n                        // Hekaton Table 2: speculative ignore of TE. If end_ts < begin_ts,\n                        // we speculatively ignore V (treat deletion as committed). Register a\n                        // dependency in case TE aborts (then V should have been visible).\n                        TransactionState::Preparing(end_ts) => {\n                            turso_assert!(\n                                current_tx.tx_id != other_tx.tx_id,\n                                \"a txn is reading itself while preparing\"\n                            );\n                            let visible = current_tx.begin_ts < end_ts;\n                            if !visible {\n                                register_commit_dependency(txs, current_tx, rv_end);\n                            }\n                            visible\n                        }\n                        TransactionState::Committed(committed_ts) => {\n                            current_tx.begin_ts < committed_ts\n                        }\n                        TransactionState::Aborted => true,\n                        // Table 2 (Hekaton): Reread V's End field. In this codebase Terminated is only\n                        // reachable from Aborted, and abort rollback resets end to None → visible.\n                        TransactionState::Terminated => true,\n                    };\n                    tracing::trace!(\n                        \"is_end_visible: tx={current_tx}, te={other_tx} rv = {:?}-{:?}  visible = {visible}\",\n                        row_version.begin,\n                        row_version.end\n                    );\n                    visible\n                }\n                None => match lookup_finalized_tx_state(finalized_tx_states, rv_end) {\n                    Some(TransactionState::Committed(committed_ts)) => {\n                        current_tx.begin_ts < committed_ts\n                    }\n                    Some(TransactionState::Aborted) | Some(TransactionState::Terminated) => true,\n                    Some(TransactionState::Active) | Some(TransactionState::Preparing(_)) => {\n                        unreachable!(\n                            \"is_end_visible: live tx {rv_end} missing from txs but present in finalized cache\"\n                        );\n                    }\n                    None => {\n                        // Transaction was removed after converting its TxID refs to Timestamps.\n                        // The end field should have been updated. Conservative fallback.\n                        true\n                    }\n                },\n            };\n            visible\n        }\n        None => true,\n    }\n}\n\nimpl<Clock: LogicalClock> Debug for CommitState<Clock> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Self::Initial => write!(f, \"Initial\"),\n            Self::Commit { end_ts } => f.debug_struct(\"Commit\").field(\"end_ts\", end_ts).finish(),\n            Self::WaitForDependencies { end_ts } => f\n                .debug_struct(\"WaitForDependencies\")\n                .field(\"end_ts\", end_ts)\n                .finish(),\n            Self::BeginCommitLogicalLog { end_ts, log_record } => f\n                .debug_struct(\"BeginCommitLogicalLog\")\n                .field(\"end_ts\", end_ts)\n                .field(\"log_record\", log_record)\n                .finish(),\n            Self::EndCommitLogicalLog { end_ts } => f\n                .debug_struct(\"EndCommitLogicalLog\")\n                .field(\"end_ts\", end_ts)\n                .finish(),\n            Self::SyncLogicalLog { end_ts } => f\n                .debug_struct(\"SyncLogicalLog\")\n                .field(\"end_ts\", end_ts)\n                .finish(),\n            Self::Checkpoint { state_machine: _ } => f.debug_struct(\"Checkpoint\").finish(),\n            Self::CommitEnd { end_ts } => {\n                f.debug_struct(\"CommitEnd\").field(\"end_ts\", end_ts).finish()\n            }\n        }\n    }\n}\n\nimpl PartialOrd for RowID {\n    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {\n        Some(self.cmp(other))\n    }\n}\n\nimpl Ord for RowID {\n    fn cmp(&self, other: &Self) -> std::cmp::Ordering {\n        // Make sure table id is first comparison so that we sort first by table_id and then by\n        // rowid. Due to order of the struct, table_id is first which is fine but if we were to\n        // change it we would bring chaos.\n        match self.table_id.cmp(&other.table_id) {\n            std::cmp::Ordering::Equal => self.row_id.cmp(&other.row_id),\n            ord => ord,\n        }\n    }\n}\n"
  },
  {
    "path": "core/mvcc/database/tests.rs",
    "content": "use rustc_hash::FxHashSet as HashSet;\n\nuse super::*;\nuse crate::io::PlatformIO;\nuse crate::mvcc::clock::MvccClock;\nuse crate::mvcc::cursor::MvccCursorType;\nuse crate::mvcc::persistent_storage::logical_log::LOG_HDR_SIZE;\nuse crate::state_machine::{StateTransition, TransitionResult};\nuse crate::storage::sqlite3_ondisk::{\n    checksum_wal, read_varint, write_varint, DatabaseHeader, WalHeader, WAL_FRAME_HEADER_SIZE,\n    WAL_HEADER_SIZE,\n};\nuse crate::sync::atomic::{AtomicBool, Ordering};\nuse crate::sync::RwLock;\nuse crate::{Buffer, Completion, DatabaseOpts, OpenFlags};\nuse quickcheck::{Arbitrary, Gen};\nuse quickcheck_macros::quickcheck;\nuse rand::{Rng, SeedableRng};\nuse rand_chacha::ChaCha8Rng;\n\npub(crate) struct MvccTestDbNoConn {\n    pub(crate) db: Option<Arc<Database>>,\n    path: Option<String>,\n    opts: DatabaseOpts,\n    // Stored mainly to not drop the temp dir before the test is done.\n    _temp_dir: Option<tempfile::TempDir>,\n}\npub(crate) struct MvccTestDb {\n    pub(crate) mvcc_store: Arc<MvStore<MvccClock>>,\n    pub(crate) db: Arc<Database>,\n    pub(crate) conn: Arc<Connection>,\n}\n\nimpl MvccTestDb {\n    pub fn new() -> Self {\n        let io = Arc::new(MemoryIO::new());\n        let db = Database::open_file(io, \":memory:\").unwrap();\n        let conn = db.connect().unwrap();\n        // Enable MVCC via PRAGMA\n        conn.execute(\"PRAGMA journal_mode = 'mvcc'\").unwrap();\n        let mvcc_store = db.get_mv_store().clone().unwrap();\n        Self {\n            mvcc_store,\n            db,\n            conn,\n        }\n    }\n}\n\nimpl MvccTestDbNoConn {\n    pub fn new() -> Self {\n        let io = Arc::new(MemoryIO::new());\n        let opts = DatabaseOpts::new();\n        let db = Database::open_file_with_flags(io, \":memory:\", OpenFlags::default(), opts, None)\n            .unwrap();\n        // Enable MVCC via PRAGMA\n        let conn = db.connect().unwrap();\n        conn.execute(\"PRAGMA journal_mode = 'mvcc'\").unwrap();\n        conn.close().unwrap();\n        Self {\n            db: Some(db),\n            path: None,\n            opts,\n            _temp_dir: None,\n        }\n    }\n\n    /// Opens a database with a file\n    pub fn new_with_random_db() -> Self {\n        Self::new_with_random_db_with_opts(DatabaseOpts::new())\n    }\n\n    /// Opens a database with a file and the requested options.\n    pub fn new_with_random_db_with_opts(opts: DatabaseOpts) -> Self {\n        let temp_dir = tempfile::TempDir::new().unwrap();\n        let path = temp_dir\n            .path()\n            .join(format!(\"test_{}\", rand::random::<u64>()));\n        std::fs::create_dir_all(path.parent().unwrap()).unwrap();\n        let io = Arc::new(PlatformIO::new().unwrap());\n        println!(\"path: {}\", path.as_os_str().to_str().unwrap());\n        let db = Database::open_file_with_flags(\n            io,\n            path.as_os_str().to_str().unwrap(),\n            OpenFlags::default(),\n            opts,\n            None,\n        )\n        .unwrap();\n        // Enable MVCC via PRAGMA\n        let conn = db.connect().unwrap();\n        conn.execute(\"PRAGMA journal_mode = 'mvcc'\").unwrap();\n        conn.close().unwrap();\n        Self {\n            db: Some(db),\n            path: Some(path.to_str().unwrap().to_string()),\n            opts,\n            _temp_dir: Some(temp_dir),\n        }\n    }\n\n    /// Restarts the database, make sure there is no connection to the database open before calling this!\n    pub fn restart(&mut self) {\n        // First let's clear any entries in database manager in order to force restart.\n        // If not, we will load the same database instance again.\n        {\n            let mut manager = DATABASE_MANAGER.lock();\n            manager.clear();\n        }\n\n        // Now open again.\n        let io = Arc::new(PlatformIO::new().unwrap());\n        let path = self.path.as_ref().unwrap();\n        let db = Database::open_file_with_flags(io, path, OpenFlags::default(), self.opts, None)\n            .unwrap();\n        self.db.replace(db);\n    }\n\n    /// Asumes there is a database open\n    pub fn get_db(&self) -> Arc<Database> {\n        self.db.as_ref().unwrap().clone()\n    }\n\n    pub fn connect(&self) -> Arc<Connection> {\n        self.get_db().connect().unwrap()\n    }\n\n    pub fn get_mvcc_store(&self) -> Arc<MvStore<MvccClock>> {\n        self.get_db().get_mv_store().clone().unwrap()\n    }\n}\n\npub(crate) fn generate_simple_string_row(table_id: MVTableId, id: i64, data: &str) -> Row {\n    let record = ImmutableRecord::from_values(&[Value::Text(Text::new(data.to_string()))], 1);\n    Row::new_table_row(\n        RowID::new(table_id, RowKey::Int(id)),\n        record.as_blob().to_vec(),\n        1,\n    )\n}\n\npub(crate) fn generate_simple_string_record(data: &str) -> ImmutableRecord {\n    ImmutableRecord::from_values(&[Value::Text(Text::new(data.to_string()))], 1)\n}\n\nfn advance_checkpoint_until_wal_has_commit_frame(\n    mvcc_store: Arc<MvStore<MvccClock>>,\n    conn: &Arc<Connection>,\n) {\n    let pager = conn.pager.load().clone();\n    let initial_wal_max_frame = pager\n        .wal\n        .as_ref()\n        .expect(\"mvcc mode requires wal\")\n        .get_max_frame_in_wal();\n    let mut checkpoint_sm = CheckpointStateMachine::new(\n        pager.clone(),\n        mvcc_store,\n        conn.clone(),\n        true,\n        conn.get_sync_mode(),\n    );\n\n    for _ in 0..10_000 {\n        if pager\n            .wal\n            .as_ref()\n            .expect(\"mvcc mode requires wal\")\n            .get_max_frame_in_wal()\n            > initial_wal_max_frame\n        {\n            return;\n        }\n\n        match checkpoint_sm.step(&()).unwrap() {\n            TransitionResult::Io(io) => io.wait(pager.io.as_ref()).unwrap(),\n            TransitionResult::Continue => {}\n            TransitionResult::Done(_) => {\n                panic!(\"checkpoint finalized before WAL had committed frames\")\n            }\n        }\n    }\n\n    panic!(\"checkpoint did not produce committed WAL frame in bounded steps\");\n}\n\nfn overwrite_log_header_byte(path: &str, offset: u64, value: u8) {\n    let log_path = std::path::Path::new(path).with_extension(\"db-log\");\n    let mut file = std::fs::OpenOptions::new()\n        .read(true)\n        .write(true)\n        .open(log_path)\n        .unwrap();\n    use std::io::{Seek, SeekFrom, Write};\n    file.seek(SeekFrom::Start(offset)).unwrap();\n    file.write_all(&[value]).unwrap();\n    file.sync_all().unwrap();\n}\n\nfn overwrite_file_with_junk(path: &std::path::Path, size: usize, byte: u8) {\n    let mut file = std::fs::OpenOptions::new()\n        .create(true)\n        .write(true)\n        .truncate(true)\n        .open(path)\n        .unwrap();\n    let payload = vec![byte; size];\n    use std::io::Write;\n    file.write_all(&payload).unwrap();\n    file.sync_all().unwrap();\n}\n\nfn wal_path_for_db(path: &str) -> std::path::PathBuf {\n    std::path::PathBuf::from(format!(\"{path}-wal\"))\n}\n\nfn force_close_for_artifact_tamper(db: &mut MvccTestDbNoConn) {\n    db.db.take();\n    let mut manager = DATABASE_MANAGER.lock();\n    manager.clear();\n}\n\nfn read_db_page_size(path: &str) -> usize {\n    use std::io::{Read, Seek, SeekFrom};\n    let mut file = std::fs::OpenOptions::new().read(true).open(path).unwrap();\n    let mut header = [0u8; 100];\n    file.seek(SeekFrom::Start(0)).unwrap();\n    file.read_exact(&mut header).unwrap();\n    let raw = u16::from_be_bytes([header[16], header[17]]);\n    if raw == 1 {\n        65536\n    } else {\n        raw as usize\n    }\n}\n\nfn page_file_offset(page_no: u32, page_size: usize) -> u64 {\n    (page_no as u64 - 1) * page_size as u64\n}\n\nfn page_header_offset(page_no: u32) -> usize {\n    if page_no == 1 {\n        100\n    } else {\n        0\n    }\n}\n\nfn read_db_page(path: &str, page_no: u32, page_size: usize) -> Vec<u8> {\n    use std::io::{Read, Seek, SeekFrom};\n    let mut file = std::fs::OpenOptions::new().read(true).open(path).unwrap();\n    let mut page = vec![0u8; page_size];\n    file.seek(SeekFrom::Start(page_file_offset(page_no, page_size)))\n        .unwrap();\n    file.read_exact(&mut page).unwrap();\n    page\n}\n\nfn write_db_page(path: &str, page_no: u32, page_size: usize, page: &[u8]) {\n    use std::io::{Seek, SeekFrom, Write};\n    let mut file = std::fs::OpenOptions::new()\n        .read(true)\n        .write(true)\n        .open(path)\n        .unwrap();\n    file.seek(SeekFrom::Start(page_file_offset(page_no, page_size)))\n        .unwrap();\n    file.write_all(page).unwrap();\n    file.sync_all().unwrap();\n}\n\n#[derive(Debug, Clone, Copy)]\nstruct TableLeafCellLoc {\n    cell_offset: usize,\n    payload_varint_len: usize,\n    payload_len: usize,\n    payload_offset: usize,\n}\n\nfn table_leaf_cell_locs(page: &[u8], page_no: u32) -> Vec<TableLeafCellLoc> {\n    let hdr_off = page_header_offset(page_no);\n    assert_eq!(page[hdr_off], 0x0D, \"expected table-leaf page type\");\n    let cell_count = u16::from_be_bytes([page[hdr_off + 3], page[hdr_off + 4]]) as usize;\n    let ptr_base = hdr_off + 8;\n    let mut locs = Vec::with_capacity(cell_count);\n    for i in 0..cell_count {\n        let ptr_off = ptr_base + i * 2;\n        let cell_ptr = u16::from_be_bytes([page[ptr_off], page[ptr_off + 1]]) as usize;\n        let (payload_len_u64, payload_varint_len) = read_varint(&page[cell_ptr..]).unwrap();\n        let payload_len = payload_len_u64 as usize;\n        let (_, rowid_varint_len) = read_varint(&page[cell_ptr + payload_varint_len..]).unwrap();\n        let payload_offset = cell_ptr + payload_varint_len + rowid_varint_len;\n        locs.push(TableLeafCellLoc {\n            cell_offset: cell_ptr,\n            payload_varint_len,\n            payload_len,\n            payload_offset,\n        });\n    }\n    locs\n}\n\nfn table_leaf_first_cell_loc(page: &[u8], page_no: u32) -> TableLeafCellLoc {\n    let locs = table_leaf_cell_locs(page, page_no);\n    let cell_count = locs.len();\n    assert!(\n        cell_count > 0,\n        \"expected at least one cell in metadata page\"\n    );\n    locs[0]\n}\n\nfn rewrite_table_leaf_cell_payload(page: &mut [u8], loc: TableLeafCellLoc, new_payload: &[u8]) {\n    assert!(\n        new_payload.len() <= loc.payload_len,\n        \"new payload {} exceeds existing payload {}\",\n        new_payload.len(),\n        loc.payload_len\n    );\n    let mut varint_buf = [0u8; 9];\n    let n = write_varint(&mut varint_buf, new_payload.len() as u64);\n    assert_eq!(\n        n, loc.payload_varint_len,\n        \"payload varint length changed; in-place rewrite is unsafe\"\n    );\n    page[loc.cell_offset..loc.cell_offset + n].copy_from_slice(&varint_buf[..n]);\n    page[loc.payload_offset..loc.payload_offset + new_payload.len()].copy_from_slice(new_payload);\n    if new_payload.len() < loc.payload_len {\n        page[loc.payload_offset + new_payload.len()..loc.payload_offset + loc.payload_len].fill(0);\n    }\n}\n\nfn tamper_table_leaf_value_serial_type(page: &mut [u8], page_no: u32, new_serial_type: u8) -> bool {\n    let loc = table_leaf_first_cell_loc(page, page_no);\n    let payload = &mut page[loc.payload_offset..loc.payload_offset + loc.payload_len];\n\n    let (header_size, hs_len) = read_varint(payload).unwrap();\n    let header_size = header_size as usize;\n    if header_size < hs_len + 2 || header_size > payload.len() {\n        return false;\n    }\n\n    let mut idx = hs_len;\n    let (_serial_type0, n0) = read_varint(&payload[idx..header_size]).unwrap();\n    idx += n0;\n    if idx >= header_size {\n        return false;\n    }\n    payload[idx] = new_serial_type;\n    true\n}\n\nfn wipe_table_leaf_cells(page: &mut [u8], page_no: u32) -> bool {\n    let hdr_off = page_header_offset(page_no);\n    if page.len() <= hdr_off + 8 || page[hdr_off] != 0x0D {\n        return false;\n    }\n    let page_size = page.len();\n    page[hdr_off + 3..hdr_off + 5].copy_from_slice(&0u16.to_be_bytes()); // number of cells\n    page[hdr_off + 5..hdr_off + 7].copy_from_slice(&(page_size as u16).to_be_bytes()); // cell content area start\n    page[hdr_off + 7] = 0; // fragmented free bytes\n    true\n}\n\nfn metadata_root_page(conn: &Arc<Connection>) -> u32 {\n    let rows = get_rows(\n        conn,\n        \"SELECT rootpage FROM sqlite_schema\n         WHERE type = 'table' AND name = '__turso_internal_mvcc_meta'\",\n    );\n    assert_eq!(rows.len(), 1, \"expected exactly one metadata table row\");\n    rows[0][0].as_int().unwrap() as u32\n}\n\nfn tamper_db_metadata_row_value(db_path: &str, metadata_root_page: u32, new_value: i64) {\n    let page_size = read_db_page_size(db_path);\n    let mut page = read_db_page(db_path, metadata_root_page, page_size);\n    let loc = table_leaf_first_cell_loc(&page, metadata_root_page);\n    let payload = &page[loc.payload_offset..loc.payload_offset + loc.payload_len];\n    let record = ImmutableRecord::from_bin_record(payload.to_vec());\n    let key = record\n        .get_value_opt(0)\n        .expect(\"metadata key column missing\");\n    let ValueRef::Text(key) = key else {\n        panic!(\"metadata key must be text\");\n    };\n    let new_record = ImmutableRecord::from_values(\n        &[\n            Value::Text(Text::new(key.as_str().to_string())),\n            Value::from_i64(new_value),\n        ],\n        2,\n    );\n    rewrite_table_leaf_cell_payload(&mut page, loc, new_record.as_blob());\n    write_db_page(db_path, metadata_root_page, page_size, &page);\n}\n\nfn tamper_db_metadata_row_value_by_key(\n    db_path: &str,\n    metadata_root_page: u32,\n    target_key: &str,\n    new_value: i64,\n) {\n    let page_size = read_db_page_size(db_path);\n    let mut page = read_db_page(db_path, metadata_root_page, page_size);\n    let mut updated = false;\n    for loc in table_leaf_cell_locs(&page, metadata_root_page) {\n        let payload = &page[loc.payload_offset..loc.payload_offset + loc.payload_len];\n        let record = ImmutableRecord::from_bin_record(payload.to_vec());\n        let key = record\n            .get_value_opt(0)\n            .expect(\"metadata key column missing\");\n        let ValueRef::Text(key) = key else {\n            panic!(\"metadata key must be text\");\n        };\n        if key.as_str() != target_key {\n            continue;\n        }\n        let new_record = ImmutableRecord::from_values(\n            &[\n                Value::Text(Text::new(target_key.to_string())),\n                Value::from_i64(new_value),\n            ],\n            2,\n        );\n        rewrite_table_leaf_cell_payload(&mut page, loc, new_record.as_blob());\n        updated = true;\n    }\n    assert!(updated, \"expected metadata key {target_key} to exist\");\n    write_db_page(db_path, metadata_root_page, page_size, &page);\n}\n\nfn tamper_db_metadata_value_serial_type(\n    db_path: &str,\n    metadata_root_page: u32,\n    new_serial_type: u8,\n) {\n    let page_size = read_db_page_size(db_path);\n    let mut page = read_db_page(db_path, metadata_root_page, page_size);\n    assert!(\n        tamper_table_leaf_value_serial_type(&mut page, metadata_root_page, new_serial_type),\n        \"expected metadata serial-type tamper to succeed\"\n    );\n    write_db_page(db_path, metadata_root_page, page_size, &page);\n}\n\nfn tamper_db_metadata_row_key(db_path: &str, metadata_root_page: u32, new_key: &str) {\n    let page_size = read_db_page_size(db_path);\n    let mut page = read_db_page(db_path, metadata_root_page, page_size);\n    let loc = table_leaf_first_cell_loc(&page, metadata_root_page);\n    let payload = &page[loc.payload_offset..loc.payload_offset + loc.payload_len];\n    let record = ImmutableRecord::from_bin_record(payload.to_vec());\n    let value = record\n        .get_value_opt(1)\n        .expect(\"metadata value column missing\");\n    let ValueRef::Numeric(Numeric::Integer(value)) = value else {\n        panic!(\"metadata value must be integer\");\n    };\n    let new_record = ImmutableRecord::from_values(\n        &[\n            Value::Text(Text::new(new_key.to_string())),\n            Value::from_i64(value),\n        ],\n        2,\n    );\n    rewrite_table_leaf_cell_payload(&mut page, loc, new_record.as_blob());\n    write_db_page(db_path, metadata_root_page, page_size, &page);\n}\n\nfn tamper_wal_metadata_value_serial_type(\n    wal_path: &std::path::Path,\n    metadata_root_page: u32,\n    new_serial_type: u8,\n) -> bool {\n    use std::io::{Read, Seek, SeekFrom, Write};\n\n    let mut file = std::fs::OpenOptions::new()\n        .read(true)\n        .write(true)\n        .open(wal_path)\n        .unwrap();\n\n    let mut bytes = Vec::new();\n    file.read_to_end(&mut bytes).unwrap();\n    if bytes.len() < WAL_HEADER_SIZE {\n        return false;\n    }\n\n    let header = WalHeader {\n        magic: u32::from_be_bytes(bytes[0..4].try_into().unwrap()),\n        file_format: u32::from_be_bytes(bytes[4..8].try_into().unwrap()),\n        page_size: u32::from_be_bytes(bytes[8..12].try_into().unwrap()),\n        checkpoint_seq: u32::from_be_bytes(bytes[12..16].try_into().unwrap()),\n        salt_1: u32::from_be_bytes(bytes[16..20].try_into().unwrap()),\n        salt_2: u32::from_be_bytes(bytes[20..24].try_into().unwrap()),\n        checksum_1: u32::from_be_bytes(bytes[24..28].try_into().unwrap()),\n        checksum_2: u32::from_be_bytes(bytes[28..32].try_into().unwrap()),\n    };\n    let use_native_endian = cfg!(target_endian = \"big\") == ((header.magic & 1) != 0);\n    let frame_size = WAL_FRAME_HEADER_SIZE + header.page_size as usize;\n    let mut frame_offset = WAL_HEADER_SIZE;\n    let mut prev_checksums = (header.checksum_1, header.checksum_2);\n    let mut mutated = false;\n\n    while frame_offset + frame_size <= bytes.len() {\n        let frame = &mut bytes[frame_offset..frame_offset + frame_size];\n        let page_no = u32::from_be_bytes(frame[0..4].try_into().unwrap());\n        if page_no == metadata_root_page {\n            let page_image = &mut frame\n                [WAL_FRAME_HEADER_SIZE..WAL_FRAME_HEADER_SIZE + header.page_size as usize];\n            let hdr_off = page_header_offset(metadata_root_page);\n            if page_image.len() > hdr_off + 5 && page_image[hdr_off] == 0x0D {\n                let cell_count =\n                    u16::from_be_bytes([page_image[hdr_off + 3], page_image[hdr_off + 4]]);\n                if cell_count > 0\n                    && tamper_table_leaf_value_serial_type(\n                        page_image,\n                        metadata_root_page,\n                        new_serial_type,\n                    )\n                {\n                    mutated = true;\n                }\n            }\n        }\n\n        let header_checksum =\n            checksum_wal(&frame[0..8], &header, prev_checksums, use_native_endian);\n        let final_checksum = checksum_wal(\n            &frame[WAL_FRAME_HEADER_SIZE..WAL_FRAME_HEADER_SIZE + header.page_size as usize],\n            &header,\n            header_checksum,\n            use_native_endian,\n        );\n        frame[16..20].copy_from_slice(&final_checksum.0.to_be_bytes());\n        frame[20..24].copy_from_slice(&final_checksum.1.to_be_bytes());\n        prev_checksums = final_checksum;\n        frame_offset += frame_size;\n    }\n\n    file.seek(SeekFrom::Start(0)).unwrap();\n    file.write_all(&bytes).unwrap();\n    file.sync_all().unwrap();\n    mutated\n}\n\nfn tamper_wal_metadata_page_empty(wal_path: &std::path::Path, metadata_root_page: u32) -> bool {\n    use std::io::{Read, Seek, SeekFrom, Write};\n\n    let mut file = std::fs::OpenOptions::new()\n        .read(true)\n        .write(true)\n        .open(wal_path)\n        .unwrap();\n\n    let mut bytes = Vec::new();\n    file.read_to_end(&mut bytes).unwrap();\n    if bytes.len() < WAL_HEADER_SIZE {\n        return false;\n    }\n\n    let header = WalHeader {\n        magic: u32::from_be_bytes(bytes[0..4].try_into().unwrap()),\n        file_format: u32::from_be_bytes(bytes[4..8].try_into().unwrap()),\n        page_size: u32::from_be_bytes(bytes[8..12].try_into().unwrap()),\n        checkpoint_seq: u32::from_be_bytes(bytes[12..16].try_into().unwrap()),\n        salt_1: u32::from_be_bytes(bytes[16..20].try_into().unwrap()),\n        salt_2: u32::from_be_bytes(bytes[20..24].try_into().unwrap()),\n        checksum_1: u32::from_be_bytes(bytes[24..28].try_into().unwrap()),\n        checksum_2: u32::from_be_bytes(bytes[28..32].try_into().unwrap()),\n    };\n    let use_native_endian = cfg!(target_endian = \"big\") == ((header.magic & 1) != 0);\n    let frame_size = WAL_FRAME_HEADER_SIZE + header.page_size as usize;\n    let mut frame_offset = WAL_HEADER_SIZE;\n    let mut prev_checksums = (header.checksum_1, header.checksum_2);\n    let mut mutated = false;\n\n    while frame_offset + frame_size <= bytes.len() {\n        let frame = &mut bytes[frame_offset..frame_offset + frame_size];\n        let page_no = u32::from_be_bytes(frame[0..4].try_into().unwrap());\n        if page_no == metadata_root_page {\n            let page_image = &mut frame\n                [WAL_FRAME_HEADER_SIZE..WAL_FRAME_HEADER_SIZE + header.page_size as usize];\n            if wipe_table_leaf_cells(page_image, metadata_root_page) {\n                mutated = true;\n            }\n        }\n\n        let header_checksum =\n            checksum_wal(&frame[0..8], &header, prev_checksums, use_native_endian);\n        let final_checksum = checksum_wal(\n            &frame[WAL_FRAME_HEADER_SIZE..WAL_FRAME_HEADER_SIZE + header.page_size as usize],\n            &header,\n            header_checksum,\n            use_native_endian,\n        );\n        frame[16..20].copy_from_slice(&final_checksum.0.to_be_bytes());\n        frame[20..24].copy_from_slice(&final_checksum.1.to_be_bytes());\n        prev_checksums = final_checksum;\n        frame_offset += frame_size;\n    }\n\n    file.seek(SeekFrom::Start(0)).unwrap();\n    file.write_all(&bytes).unwrap();\n    file.sync_all().unwrap();\n    mutated\n}\n\nfn rewrite_wal_frames_as_non_commit(path: &std::path::Path) {\n    use std::io::{Read, Seek, SeekFrom, Write};\n\n    let mut file = std::fs::OpenOptions::new()\n        .read(true)\n        .write(true)\n        .open(path)\n        .unwrap();\n\n    let mut bytes = Vec::new();\n    file.read_to_end(&mut bytes).unwrap();\n    assert!(bytes.len() >= WAL_HEADER_SIZE);\n\n    let header = WalHeader {\n        magic: u32::from_be_bytes(bytes[0..4].try_into().unwrap()),\n        file_format: u32::from_be_bytes(bytes[4..8].try_into().unwrap()),\n        page_size: u32::from_be_bytes(bytes[8..12].try_into().unwrap()),\n        checkpoint_seq: u32::from_be_bytes(bytes[12..16].try_into().unwrap()),\n        salt_1: u32::from_be_bytes(bytes[16..20].try_into().unwrap()),\n        salt_2: u32::from_be_bytes(bytes[20..24].try_into().unwrap()),\n        checksum_1: u32::from_be_bytes(bytes[24..28].try_into().unwrap()),\n        checksum_2: u32::from_be_bytes(bytes[28..32].try_into().unwrap()),\n    };\n    let use_native_endian = cfg!(target_endian = \"big\") == ((header.magic & 1) != 0);\n    let frame_size = WAL_FRAME_HEADER_SIZE + header.page_size as usize;\n    let mut frame_offset = WAL_HEADER_SIZE;\n    let mut prev_checksums = (header.checksum_1, header.checksum_2);\n\n    while frame_offset + frame_size <= bytes.len() {\n        let frame = &mut bytes[frame_offset..frame_offset + frame_size];\n        frame[4..8].copy_from_slice(&0u32.to_be_bytes());\n        let header_checksum =\n            checksum_wal(&frame[0..8], &header, prev_checksums, use_native_endian);\n        let final_checksum = checksum_wal(\n            &frame[WAL_FRAME_HEADER_SIZE..WAL_FRAME_HEADER_SIZE + header.page_size as usize],\n            &header,\n            header_checksum,\n            use_native_endian,\n        );\n        frame[16..20].copy_from_slice(&final_checksum.0.to_be_bytes());\n        frame[20..24].copy_from_slice(&final_checksum.1.to_be_bytes());\n        prev_checksums = final_checksum;\n        frame_offset += frame_size;\n    }\n\n    file.seek(SeekFrom::Start(0)).unwrap();\n    file.write_all(&bytes).unwrap();\n    file.sync_all().unwrap();\n}\n\n/// What this test checks: Startup recovery reconciles WAL/log artifacts into one consistent MVCC state and replay boundary.\n/// Why this matters: This path runs automatically after crashes; errors here can duplicate effects or drop durable data.\n#[test]\nfn test_recovery_clock_monotonicity() {\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n    let max_commit_ts = {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE test(id INTEGER PRIMARY KEY, data TEXT)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO test(id, data) VALUES (1, 'foo')\")\n            .unwrap();\n        let mvcc_store = db.get_mvcc_store();\n        mvcc_store.last_committed_tx_ts.load(Ordering::SeqCst)\n    };\n\n    db.restart();\n    let conn = db.connect();\n    let pager = conn.pager.load().clone();\n    let mvcc_store = db.get_mvcc_store();\n    let tx_id = mvcc_store.begin_tx(pager).unwrap();\n    let tx_entry = mvcc_store\n        .txs\n        .get(&tx_id)\n        .expect(\"transaction should exist\");\n    let tx = tx_entry.value();\n    assert!(\n        tx.begin_ts > max_commit_ts,\n        \"expected begin_ts {} to be > max_commit_ts {}\",\n        tx.begin_ts,\n        max_commit_ts\n    );\n}\n\n/// What this test checks: Recovery stops cleanly at a torn/incomplete tail and keeps all previously validated frames.\n/// Why this matters: Crashes can leave partial writes at EOF; we need durable-prefix recovery, not all-or-nothing failure.\n#[test]\nfn test_recover_logical_log_short_file_ignored() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n    let mvcc_store = db.get_mvcc_store();\n    let file = mvcc_store.get_logical_log_file();\n\n    let c = file.truncate(1, Completion::new_write(|_| {})).unwrap();\n    conn.db.io.wait_for_completion(c).unwrap();\n\n    let c = file\n        .pwrite(\n            0,\n            Arc::new(Buffer::new(vec![0xAB])),\n            Completion::new_write(|_| {}),\n        )\n        .unwrap();\n    conn.db.io.wait_for_completion(c).unwrap();\n    assert_eq!(file.size().unwrap(), 1);\n\n    let recovered = mvcc_store.maybe_recover_logical_log(conn).unwrap();\n    assert!(!recovered);\n}\n\n/// What this test checks: Checkpoint transitions preserve DB/WAL/log ordering and watermark updates for the tested edge case.\n/// Why this matters: Incorrect ordering breaks crash safety, replay boundaries, or durability guarantees.\n#[test]\nfn test_journal_mode_switch_from_mvcc_to_wal_without_log_frames() {\n    let db = MvccTestDb::new();\n    let rows = get_rows(&db.conn, \"PRAGMA journal_mode = 'wal'\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(rows[0][0].to_string().to_lowercase(), \"wal\");\n}\n\n/// What this test checks: Startup recovery reconciles WAL/log artifacts into one consistent MVCC state and replay boundary.\n/// Why this matters: This path runs automatically after crashes; errors here can duplicate effects or drop durable data.\n#[test]\nfn test_recovery_checkpoint_then_more_writes() {\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n    {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY, v TEXT)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO t VALUES (1, 'a')\").unwrap();\n        conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n        conn.execute(\"INSERT INTO t VALUES (2, 'b')\").unwrap();\n        conn.execute(\"INSERT INTO t VALUES (3, 'c')\").unwrap();\n    }\n\n    db.restart();\n    let conn = db.connect();\n    let rows = get_rows(&conn, \"SELECT id, v FROM t ORDER BY id\");\n    assert_eq!(rows.len(), 3);\n    assert_eq!(rows[0][0].as_int().unwrap(), 1);\n    assert_eq!(rows[0][1].to_string(), \"a\");\n    assert_eq!(rows[1][0].as_int().unwrap(), 2);\n    assert_eq!(rows[1][1].to_string(), \"b\");\n    assert_eq!(rows[2][0].as_int().unwrap(), 3);\n    assert_eq!(rows[2][1].to_string(), \"c\");\n}\n\n/// What this test checks: MVCC restart handles sqlite_schema rows with rootpage=0 (triggers).\n/// Why this matters: Trigger definitions are stored without btrees and should not break recovery.\n#[test]\nfn test_restart_with_trigger_rootpage_zero() {\n    let mut db = MvccTestDbNoConn::new_with_random_db_with_opts(DatabaseOpts::new());\n    {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE t1(id INTEGER PRIMARY KEY, a TEXT)\")\n            .unwrap();\n        conn.execute(\"CREATE TABLE audit(id INTEGER PRIMARY KEY, action TEXT)\")\n            .unwrap();\n        conn.execute(\n            \"CREATE TRIGGER trg_del AFTER DELETE ON t1 \\\n             BEGIN INSERT INTO audit VALUES (NULL, 'deleted'); END;\",\n        )\n        .unwrap();\n        conn.execute(\"INSERT INTO t1 VALUES (1, 'x')\").unwrap();\n        conn.close().unwrap();\n    }\n\n    db.restart();\n\n    {\n        let conn = db.connect();\n        conn.execute(\"DELETE FROM t1 WHERE id = 1\").unwrap();\n        let rows = get_rows(&conn, \"SELECT action FROM audit ORDER BY id\");\n        assert_eq!(rows.len(), 1);\n        assert_eq!(rows[0][0].to_string(), \"deleted\");\n    }\n}\n\n/// What this test checks: Startup recovery reconciles WAL/log artifacts into one consistent MVCC state and replay boundary.\n/// Why this matters: This path runs automatically after crashes; errors here can duplicate effects or drop durable data.\n#[test]\nfn test_btree_resident_recovery_then_checkpoint_delete_stays_deleted() {\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n    {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY, v TEXT)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO t VALUES (1, 'keep')\").unwrap();\n        conn.execute(\"INSERT INTO t VALUES (2, 'gone')\").unwrap();\n        conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n    }\n\n    // Delete a B-tree resident row and crash/restart before checkpoint.\n    {\n        let conn = db.connect();\n        conn.execute(\"DELETE FROM t WHERE id = 2\").unwrap();\n    }\n\n    db.restart();\n    {\n        let conn = db.connect();\n        // Recovery tombstone must hide stale B-tree row before checkpoint.\n        let rows = get_rows(&conn, \"SELECT id FROM t ORDER BY id\");\n        assert_eq!(rows.len(), 1);\n        assert_eq!(rows[0][0].as_int().unwrap(), 1);\n\n        // After checkpoint + GC, row must stay deleted (B-tree delete persisted).\n        conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n        let rows = get_rows(&conn, \"SELECT id FROM t ORDER BY id\");\n        assert_eq!(rows.len(), 1);\n        assert_eq!(rows[0][0].as_int().unwrap(), 1);\n\n        let rows = get_rows(&conn, \"PRAGMA integrity_check\");\n        assert_eq!(rows.len(), 1);\n        assert_eq!(&rows[0][0].to_string(), \"ok\");\n    }\n}\n\n/// What this test checks: Startup recovery reconciles WAL/log artifacts into one consistent MVCC state and replay boundary.\n/// Why this matters: This path runs automatically after crashes; errors here can duplicate effects or drop durable data.\n#[test]\nfn test_recovery_overwrites_torn_tail_on_next_append() {\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n    {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY, v TEXT)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO t VALUES (1, 'a')\").unwrap();\n        conn.execute(\"INSERT INTO t VALUES (2, 'b')\").unwrap();\n    }\n\n    // Corrupt only the tail of the latest frame.\n    {\n        let conn = db.connect();\n        let mvcc_store = db.get_mvcc_store();\n        let file = mvcc_store.get_logical_log_file();\n        let size = file.size().unwrap();\n        assert!(size > 1);\n        let c = file\n            .truncate(size - 1, Completion::new_trunc(|_| {}))\n            .unwrap();\n        conn.db.io.wait_for_completion(c).unwrap();\n    }\n\n    // First restart: recovery should stop at torn tail and reset log write offset.\n    db.restart();\n    {\n        let conn = db.connect();\n        let rows = get_rows(&conn, \"SELECT id FROM t ORDER BY id\");\n        assert_eq!(rows.len(), 1);\n        assert_eq!(rows[0][0].as_int().unwrap(), 1);\n        conn.execute(\"INSERT INTO t VALUES (3, 'c')\").unwrap();\n    }\n\n    // Second restart: row 3 must be recoverable, proving it was appended at last_valid_offset.\n    db.restart();\n    {\n        let conn = db.connect();\n        let rows = get_rows(&conn, \"SELECT id, v FROM t ORDER BY id\");\n        assert_eq!(rows.len(), 2);\n        assert_eq!(rows[0][0].as_int().unwrap(), 1);\n        assert_eq!(rows[0][1].to_string(), \"a\");\n        assert_eq!(rows[1][0].as_int().unwrap(), 3);\n        assert_eq!(rows[1][1].to_string(), \"c\");\n    }\n}\n\n/// What this test checks: First-time MVCC bootstrap repairs a torn short `.db-log` header before metadata writes commit.\n/// Why this matters: Otherwise a crash after metadata WAL commit can leave an unrecoverable startup state.\n#[test]\n#[ignore = \"Needs a dedicated bootstrap harness that can create header=MVCC + missing metadata + torn short log atomically\"]\nfn test_bootstrap_repairs_torn_short_log_before_metadata_init() {\n    let temp_dir = tempfile::TempDir::new().unwrap();\n    let db_path = temp_dir\n        .path()\n        .join(format!(\"bootstrap_torn_{}\", rand::random::<u64>()));\n    let db_path_str = db_path.to_str().unwrap().to_string();\n\n    {\n        let io = Arc::new(PlatformIO::new().unwrap());\n        let db = Database::open_file(io, &db_path_str).unwrap();\n        let conn = db.connect().unwrap();\n        conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY, v TEXT)\")\n            .unwrap();\n        conn.close().unwrap();\n    }\n\n    let log_path = std::path::Path::new(&db_path_str).with_extension(\"db-log\");\n    overwrite_file_with_junk(&log_path, LOG_HDR_SIZE / 2, 0xAB);\n\n    {\n        let mut manager = DATABASE_MANAGER.lock();\n        manager.clear();\n    }\n    {\n        let io = Arc::new(PlatformIO::new().unwrap());\n        let db = Database::open_file(io, &db_path_str).unwrap();\n        let conn = db.connect().unwrap();\n        conn.execute(\"PRAGMA journal_mode = 'mvcc'\").unwrap();\n        conn.close().unwrap();\n    }\n\n    {\n        let mut manager = DATABASE_MANAGER.lock();\n        manager.clear();\n    }\n    let io = Arc::new(PlatformIO::new().unwrap());\n    let db = Database::open_file(io, &db_path_str).unwrap();\n    let conn = db.connect().unwrap();\n    let meta = get_rows(\n        &conn,\n        \"SELECT v FROM __turso_internal_mvcc_meta WHERE k = 'persistent_tx_ts_max'\",\n    );\n    assert_eq!(meta.len(), 1);\n    assert_eq!(meta[0][0].as_int().unwrap(), 0);\n\n    let log_len = std::fs::metadata(&log_path).map(|m| m.len()).unwrap_or(0);\n    assert!(\n        log_len >= LOG_HDR_SIZE as u64,\n        \"expected bootstrap to rewrite durable logical-log header\"\n    );\n}\n\n/// What this test checks: Startup recovery reconciles WAL/log artifacts into one consistent MVCC state and replay boundary.\n/// Why this matters: This path runs automatically after crashes; errors here can duplicate effects or drop durable data.\n#[test]\nfn test_bootstrap_completes_interrupted_checkpoint_with_committed_wal() {\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n    let db_path = db.path.as_ref().unwrap().clone();\n    {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY, v TEXT)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO t VALUES (1, 'a')\").unwrap();\n        conn.execute(\"INSERT INTO t VALUES (2, 'b')\").unwrap();\n        let mvcc_store = db.get_mvcc_store();\n        advance_checkpoint_until_wal_has_commit_frame(mvcc_store, &conn);\n\n        let pager = conn.pager.load().clone();\n        assert!(\n            pager\n                .wal\n                .as_ref()\n                .expect(\"wal must exist\")\n                .get_max_frame_in_wal()\n                > 0\n        );\n        let log_file = db.get_mvcc_store().get_logical_log_file();\n        assert!(log_file.size().unwrap() > LOG_HDR_SIZE as u64);\n    }\n\n    db.restart();\n\n    let conn = db.connect();\n    let rows = get_rows(&conn, \"SELECT id, v FROM t ORDER BY id\");\n    assert_eq!(rows.len(), 2);\n    assert_eq!(rows[0][0].as_int().unwrap(), 1);\n    assert_eq!(rows[0][1].to_string(), \"a\");\n    assert_eq!(rows[1][0].as_int().unwrap(), 2);\n    assert_eq!(rows[1][1].to_string(), \"b\");\n\n    let log_size = db.get_mvcc_store().get_logical_log_file().size().unwrap();\n    assert!(\n        log_size >= LOG_HDR_SIZE as u64,\n        \"logical log must be at least {LOG_HDR_SIZE} bytes after interrupted-checkpoint reconciliation\"\n    );\n    let wal_path = wal_path_for_db(&db_path);\n    let wal_len = wal_path.metadata().map(|m| m.len()).unwrap_or(0);\n    assert_eq!(wal_len, 0);\n}\n\n/// What this test checks: Checkpoint transitions preserve DB/WAL/log ordering and watermark updates for the tested edge case.\n/// Why this matters: Incorrect ordering breaks crash safety, replay boundaries, or durability guarantees.\n#[test]\nfn test_checkpoint_truncates_wal_last() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let db_path = db.path.as_ref().unwrap().clone();\n    let wal_path = wal_path_for_db(&db_path);\n    let conn = db.connect();\n    conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY, v TEXT)\")\n        .unwrap();\n    conn.execute(\"INSERT INTO t VALUES (1, 'a')\").unwrap();\n    conn.execute(\"INSERT INTO t VALUES (2, 'b')\").unwrap();\n\n    let mvcc_store = db.get_mvcc_store();\n    let pager = conn.pager.load().clone();\n    let mut checkpoint_sm = CheckpointStateMachine::new(\n        pager.clone(),\n        mvcc_store.clone(),\n        conn.clone(),\n        true,\n        conn.get_sync_mode(),\n    );\n\n    let mut saw_truncate_log_state_with_wal = false;\n    let mut finished = false;\n    for _ in 0..50_000 {\n        let state = checkpoint_sm.state_for_test();\n\n        if state == CheckpointState::TruncateLogicalLog {\n            let wal_len = wal_path.metadata().map(|m| m.len()).unwrap_or(0);\n            assert!(wal_len > 0, \"WAL must still exist before log truncation\");\n            saw_truncate_log_state_with_wal = true;\n        }\n\n        if state == CheckpointState::TruncateWal {\n            assert!(\n                saw_truncate_log_state_with_wal,\n                \"must truncate logical log before truncating WAL\"\n            );\n            assert_eq!(\n                mvcc_store.get_logical_log_file().size().unwrap(),\n                0,\n                \"logical log should be truncated to 0\"\n            );\n        }\n\n        match checkpoint_sm.step(&()).unwrap() {\n            TransitionResult::Io(io) => io.wait(pager.io.as_ref()).unwrap(),\n            TransitionResult::Continue => {}\n            TransitionResult::Done(_) => {\n                finished = true;\n                break;\n            }\n        }\n    }\n\n    assert!(finished, \"checkpoint state machine did not finish\");\n    assert!(saw_truncate_log_state_with_wal);\n\n    let final_wal_len = wal_path.metadata().map(|m| m.len()).unwrap_or(0);\n    assert_eq!(final_wal_len, 0);\n    assert_eq!(\n        mvcc_store.get_logical_log_file().size().unwrap(),\n        0,\n        \"logical log should be truncated to 0 after checkpoint\"\n    );\n}\n\n/// What this test checks: Checkpoint accepts sqlite_schema index-row updates for already-checkpointed indexes\n/// (e.g. column rename), without requiring create/destroy special writes.\n/// Why this matters: RENAME COLUMN on indexed tables rewrites sqlite_schema index SQL text while preserving rootpage.\n/// Treating that as an impossible state crashes checkpoint.\n#[test]\nfn test_checkpoint_allows_index_schema_update_after_rename_column() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n\n    conn.execute(\"CREATE TABLE t(a INTEGER, b INTEGER)\")\n        .unwrap();\n    conn.execute(\"CREATE INDEX idx_t_a ON t(a)\").unwrap();\n    conn.execute(\"INSERT INTO t VALUES (1, 2)\").unwrap();\n    conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n\n    // Rewrites sqlite_schema entry for the existing index while keeping positive rootpage.\n    conn.execute(\"ALTER TABLE t RENAME COLUMN a TO c\").unwrap();\n    conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n\n    let rows = get_rows(&conn, \"SELECT c, b FROM t\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(rows[0][0].as_int().unwrap(), 1);\n    assert_eq!(rows[0][1].as_int().unwrap(), 2);\n}\n\n/// What this test checks: Startup recovery reconciles WAL/log artifacts into one consistent MVCC state and replay boundary.\n/// Why this matters: This path runs automatically after crashes; errors here can duplicate effects or drop durable data.\n#[test]\nfn test_bootstrap_rejects_committed_wal_without_log_file() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let db_path = db.path.as_ref().unwrap().clone();\n    {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY, v TEXT)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO t VALUES (1, 'x')\").unwrap();\n        let mvcc_store = db.get_mvcc_store();\n        advance_checkpoint_until_wal_has_commit_frame(mvcc_store, &conn);\n    }\n\n    {\n        let mut manager = DATABASE_MANAGER.lock();\n        manager.clear();\n    }\n\n    let log_path = std::path::Path::new(&db_path).with_extension(\"db-log\");\n    std::fs::remove_file(&log_path).unwrap();\n\n    let io = Arc::new(PlatformIO::new().unwrap());\n    match Database::open_file(io, &db_path) {\n        Ok(db) => match db.connect() {\n            Ok(_) => panic!(\"expected connect to fail with Corrupt\"),\n            Err(err) => assert!(matches!(err, LimboError::Corrupt(_))),\n        },\n        Err(err) => assert!(matches!(err, LimboError::Corrupt(_))),\n    }\n}\n\n/// What this test checks: Startup recovery reconciles WAL/log artifacts into one consistent MVCC state and replay boundary.\n/// Why this matters: This path runs automatically after crashes; errors here can duplicate effects or drop durable data.\n#[test]\nfn test_bootstrap_rejects_torn_log_header_with_committed_wal() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let db_path = db.path.as_ref().unwrap().clone();\n    let wal_path = wal_path_for_db(&db_path);\n    {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY, v TEXT)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO t VALUES (1, 'x')\").unwrap();\n        conn.execute(\"INSERT INTO t VALUES (2, 'y')\").unwrap();\n        let mvcc_store = db.get_mvcc_store();\n        advance_checkpoint_until_wal_has_commit_frame(mvcc_store, &conn);\n    }\n\n    overwrite_log_header_byte(&db_path, 0, 0x00);\n\n    {\n        let mut manager = DATABASE_MANAGER.lock();\n        manager.clear();\n    }\n\n    let io = Arc::new(PlatformIO::new().unwrap());\n    match Database::open_file(io, &db_path) {\n        Ok(db) => match db.connect() {\n            Ok(_) => panic!(\"expected connect to fail with Corrupt\"),\n            Err(err) => assert!(matches!(err, LimboError::Corrupt(_))),\n        },\n        Err(err) => assert!(matches!(err, LimboError::Corrupt(_))),\n    }\n    let wal_len = wal_path.metadata().map(|m| m.len()).unwrap_or(0);\n    assert!(\n        wal_len > 0,\n        \"failed bootstrap must not truncate WAL before header validation\"\n    );\n}\n\n/// What this test checks: Startup recovery reconciles WAL/log artifacts into one consistent MVCC state and replay boundary.\n/// Why this matters: This path runs automatically after crashes; errors here can duplicate effects or drop durable data.\n#[test]\nfn test_bootstrap_rejects_corrupt_log_header_without_wal() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let db_path = db.path.as_ref().unwrap().clone();\n    {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY, v TEXT)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO t VALUES (1, 'x')\").unwrap();\n    }\n\n    overwrite_log_header_byte(&db_path, 0, 0x00);\n\n    {\n        let wal_path = wal_path_for_db(&db_path);\n        let _ = std::fs::remove_file(&wal_path);\n        overwrite_file_with_junk(&wal_path, 0, 0x00);\n    }\n\n    {\n        let mut manager = DATABASE_MANAGER.lock();\n        manager.clear();\n    }\n\n    let io = Arc::new(PlatformIO::new().unwrap());\n    match Database::open_file(io, &db_path) {\n        Ok(db) => match db.connect() {\n            Ok(_) => panic!(\"expected connect to fail with Corrupt\"),\n            Err(err) => assert!(matches!(err, LimboError::Corrupt(_))),\n        },\n        Err(err) => assert!(matches!(err, LimboError::Corrupt(_))),\n    }\n}\n\n/// What this test checks: Startup recovery reconciles WAL/log artifacts into one consistent MVCC state and replay boundary.\n/// Why this matters: This path runs automatically after crashes; errors here can duplicate effects or drop durable data.\n#[test]\nfn test_bootstrap_handles_committed_wal_when_log_truncated() {\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n    let db_path = db.path.as_ref().unwrap().clone();\n    {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY, v TEXT)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO t VALUES (1, 'a')\").unwrap();\n        conn.execute(\"INSERT INTO t VALUES (2, 'b')\").unwrap();\n        let mvcc_store = db.get_mvcc_store();\n        advance_checkpoint_until_wal_has_commit_frame(mvcc_store.clone(), &conn);\n\n        let log_file = mvcc_store.get_logical_log_file();\n        let c = log_file\n            .truncate(LOG_HDR_SIZE as u64, Completion::new_trunc(|_| {}))\n            .unwrap();\n        conn.db.io.wait_for_completion(c).unwrap();\n    }\n\n    db.restart();\n\n    let conn = db.connect();\n    let rows = get_rows(&conn, \"SELECT id, v FROM t ORDER BY id\");\n    assert_eq!(rows.len(), 2);\n    assert_eq!(rows[0][0].as_int().unwrap(), 1);\n    assert_eq!(rows[0][1].to_string(), \"a\");\n    assert_eq!(rows[1][0].as_int().unwrap(), 2);\n    assert_eq!(rows[1][1].to_string(), \"b\");\n\n    let log_size = db.get_mvcc_store().get_logical_log_file().size().unwrap();\n    assert_eq!(log_size, LOG_HDR_SIZE as u64);\n    let wal_path = wal_path_for_db(&db_path);\n    let wal_len = wal_path.metadata().map(|m| m.len()).unwrap_or(0);\n    assert_eq!(wal_len, 0);\n}\n\n/// What this test checks: WAL frames without a commit marker are treated as non-committed tail and ignored.\n/// Why this matters: Recovery must preserve availability by discarding invalid WAL tail bytes instead of failing startup.\n#[test]\nfn test_bootstrap_ignores_wal_frames_without_commit_marker() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let db_path = db.path.as_ref().unwrap().clone();\n    let wal_path = wal_path_for_db(&db_path);\n    {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY, v TEXT)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO t VALUES (1, 'x')\").unwrap();\n        let mvcc_store = db.get_mvcc_store();\n        advance_checkpoint_until_wal_has_commit_frame(mvcc_store, &conn);\n    }\n\n    rewrite_wal_frames_as_non_commit(&wal_path);\n    {\n        let mut manager = DATABASE_MANAGER.lock();\n        manager.clear();\n    }\n    let io = Arc::new(PlatformIO::new().unwrap());\n    let db2 = Database::open_file(io, &db_path).expect(\"open should succeed\");\n    let conn2 = db2.connect().expect(\"connect should succeed\");\n    let rows = get_rows(&conn2, \"SELECT id, v FROM t ORDER BY id\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(rows[0][0].as_int().unwrap(), 1);\n    assert_eq!(rows[0][1].to_string(), \"x\");\n}\n\n/// What this test checks: Recovery after checkpoint (empty log) seeds new tx timestamps above durable metadata boundary.\n/// Why this matters: Timestamp rewind below checkpointed boundary would break MVCC ordering.\n#[test]\nfn test_empty_log_recovery_loads_checkpoint_watermark() {\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n    let persistent_tx_ts_max = {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY, v TEXT)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO t VALUES (1, 'a')\").unwrap();\n        conn.execute(\"INSERT INTO t VALUES (2, 'b')\").unwrap();\n        let mvcc_store = db.get_mvcc_store();\n        conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n        assert_eq!(\n            mvcc_store.get_logical_log_file().size().unwrap(),\n            0,\n            \"logical log should be truncated to 0 after checkpoint\"\n        );\n        let meta = get_rows(\n            &conn,\n            \"SELECT v FROM __turso_internal_mvcc_meta WHERE k = 'persistent_tx_ts_max'\",\n        );\n        assert_eq!(meta.len(), 1);\n        meta[0][0].as_int().unwrap() as u64\n    };\n\n    db.restart();\n    let conn = db.connect();\n    let pager = conn.pager.load().clone();\n    let mvcc_store = db.get_mvcc_store();\n    let tx_id = mvcc_store.begin_tx(pager).unwrap();\n    let tx_entry = mvcc_store\n        .txs\n        .get(&tx_id)\n        .expect(\"transaction should exist\");\n    assert!(\n        tx_entry.value().begin_ts > persistent_tx_ts_max,\n        \"expected begin_ts {} > persistent_tx_ts_max {}\",\n        tx_entry.value().begin_ts,\n        persistent_tx_ts_max\n    );\n}\n\n/// TDD recovery/checkpoint matrix for metadata-table source of truth.\n///\n/// Proposed semantics under test:\n/// - Source of truth for replay boundary is internal SQLite table\n///   `turso_internal_mvcc_meta` with `persistent_tx_ts_max`.\n/// - Logical-log header carries no replay timestamps.\n/// - On startup with committed WAL frames, recovery must reconcile WAL first, then read metadata.\n///\n/// Enumerated cases:\n/// 1. No committed WAL + no logical-log frames + metadata row present.\n/// 2. No committed WAL + logical-log frames + metadata row present -> replay `ts > persistent_tx_ts_max`.\n/// 3. No committed WAL + logical-log frames + metadata row missing/corrupt -> fail closed.\n/// 4. Committed WAL + metadata row present -> reconcile WAL first, then replay above metadata boundary.\n/// 5. Committed WAL + metadata row missing -> fail closed.\n/// 6. Committed WAL + metadata row malformed/corrupt -> fail closed.\n/// 7. Metadata table exists but has duplicate rows/invalid key shape -> fail closed.\n/// 8. User tampered metadata row downward -> detect and fail closed.\n/// 9. User deleted metadata row -> detect and fail closed.\n/// 10. Checkpoint pager commit atomically upserts metadata row in same WAL txn.\n/// 11. Auto-checkpoint failure after pager commit keeps COMMIT result stable and recoverable.\n/// 12. Replay gate correctness: never apply `commit_ts <= persistent_tx_ts_max`.\n#[test]\nfn test_meta_recovery_case_1_no_wal_no_log_metadata_present_clean_boot() {\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n    let db_path = db.path.as_ref().unwrap().clone();\n    let wal_path = wal_path_for_db(&db_path);\n    let log_path = std::path::Path::new(&db_path).with_extension(\"db-log\");\n\n    {\n        let conn = db.connect();\n        let rows = get_rows(\n            &conn,\n            \"SELECT k, v FROM __turso_internal_mvcc_meta ORDER BY rowid\",\n        );\n        assert_eq!(rows.len(), 1);\n        assert_eq!(rows[0][0].to_string(), \"persistent_tx_ts_max\");\n        assert_eq!(rows[0][1].as_int().unwrap(), 0);\n    }\n\n    db.restart();\n    let conn = db.connect();\n    let rows = get_rows(\n        &conn,\n        \"SELECT k, v FROM __turso_internal_mvcc_meta ORDER BY rowid\",\n    );\n    assert_eq!(rows.len(), 1);\n    assert_eq!(rows[0][0].to_string(), \"persistent_tx_ts_max\");\n    assert_eq!(rows[0][1].as_int().unwrap(), 0);\n\n    let wal_len = wal_path.metadata().map(|m| m.len()).unwrap_or(0);\n    assert_eq!(\n        wal_len, 0,\n        \"expected no committed WAL tail after clean boot\"\n    );\n    let log_len = std::fs::metadata(&log_path).map(|m| m.len()).unwrap_or(0);\n    assert_eq!(\n        log_len, LOG_HDR_SIZE as u64,\n        \"expected logical log to be {LOG_HDR_SIZE} bytes (bootstrap header) on clean boot\"\n    );\n}\n\n/// What this test checks: With no committed WAL and metadata present, replay includes only frames above `persistent_tx_ts_max`.\n/// Why this matters: This is the core idempotency contract for logical-log replay.\n#[test]\nfn test_meta_recovery_case_2_no_wal_replay_above_metadata_boundary() {\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n    {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY, v TEXT)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO t VALUES (1, 'a')\").unwrap();\n        conn.execute(\"INSERT INTO t VALUES (2, 'b')\").unwrap();\n        conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n\n        let meta = get_rows(\n            &conn,\n            \"SELECT v FROM __turso_internal_mvcc_meta WHERE k = 'persistent_tx_ts_max'\",\n        );\n        assert_eq!(meta.len(), 1);\n        let boundary = meta[0][0].as_int().unwrap();\n        assert!(\n            boundary >= 2,\n            \"expected metadata boundary >= 2 after checkpoint, got {boundary}\"\n        );\n\n        conn.execute(\"INSERT INTO t VALUES (3, 'c')\").unwrap();\n    }\n\n    db.restart();\n    let conn = db.connect();\n    let rows = get_rows(&conn, \"SELECT id, v FROM t ORDER BY id\");\n    assert_eq!(rows.len(), 3);\n    assert_eq!(rows[0][0].as_int().unwrap(), 1);\n    assert_eq!(rows[0][1].to_string(), \"a\");\n    assert_eq!(rows[1][0].as_int().unwrap(), 2);\n    assert_eq!(rows[1][1].to_string(), \"b\");\n    assert_eq!(rows[2][0].as_int().unwrap(), 3);\n    assert_eq!(rows[2][1].to_string(), \"c\");\n}\n\n/// What this test checks: Header-only commits are durably replayed from the logical log and\n/// then persisted into the database header by checkpoint.\n/// Why this matters: PRAGMA header mutations (for example user_version) must survive restart\n/// both before and after log truncation, including implicit autocommit statement transactions.\n#[test]\nfn test_header_only_mutation_is_replayed_and_checkpointed() {\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n\n    {\n        let conn = db.connect();\n        conn.execute(\"PRAGMA user_version = 42\").unwrap();\n        let rows = get_rows(&conn, \"PRAGMA user_version\");\n        assert_eq!(rows.len(), 1);\n        assert_eq!(rows[0][0].as_int().unwrap(), 42);\n    }\n\n    db.restart();\n    {\n        let conn = db.connect();\n        let rows = get_rows(&conn, \"PRAGMA user_version\");\n        assert_eq!(rows.len(), 1);\n        assert_eq!(\n            rows[0][0].as_int().unwrap(),\n            42,\n            \"header mutation should recover from logical log before checkpoint\",\n        );\n        conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n    }\n\n    db.restart();\n    {\n        let conn = db.connect();\n        let rows = get_rows(&conn, \"PRAGMA user_version\");\n        assert_eq!(rows.len(), 1);\n        assert_eq!(\n            rows[0][0].as_int().unwrap(),\n            42,\n            \"header mutation should persist in DB header after checkpoint truncates logical log\",\n        );\n    }\n}\n\n/// What this test checks: Header PRAGMAs in MVCC require an exclusive transaction and reject\n/// BEGIN CONCURRENT writes.\n/// Why this matters: Header updates have no row-level conflict keys, so they must not run under\n/// optimistic concurrent write mode.\n#[test]\nfn test_mvcc_header_updates_require_exclusive_transaction() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n\n    conn.execute(\"BEGIN CONCURRENT\").unwrap();\n    let err = conn.execute(\"PRAGMA user_version = 42\").unwrap_err();\n    assert!(\n        err.to_string().contains(\"exclusive transaction\"),\n        \"expected exclusive-transaction error, got: {err:?}\"\n    );\n    conn.execute(\"ROLLBACK\").unwrap();\n\n    conn.execute(\"BEGIN\").unwrap();\n    conn.execute(\"PRAGMA user_version = 7\").unwrap();\n    conn.execute(\"COMMIT\").unwrap();\n\n    let rows = get_rows(&conn, \"PRAGMA user_version\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(rows[0][0].as_int().unwrap(), 7);\n}\n\n/// What this test checks: Header PRAGMAs in MVCC succeed in autocommit mode, where the VM\n/// opens an implicit single-statement write transaction.\n/// Why this matters: The exclusive-transaction gate must block BEGIN CONCURRENT, but not reject\n/// valid autocommit writes that are internally upgraded to exclusive write mode.\n#[test]\nfn test_mvcc_header_updates_allow_autocommit_statement_tx() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n\n    conn.execute(\"PRAGMA user_version = 19\").unwrap();\n\n    let rows = get_rows(&conn, \"PRAGMA user_version\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(rows[0][0].as_int().unwrap(), 19);\n}\n\n/// What this test checks: Missing/corrupt metadata with logical-log frames and no WAL causes fail-closed startup.\n/// Why this matters: Without metadata boundary recovery cannot choose replay/discard safely.\n#[test]\n#[cfg_attr(\n    feature = \"checksum\",\n    ignore = \"byte-level tamper caught by checksum layer\"\n)]\nfn test_meta_recovery_case_3_no_wal_log_frames_without_valid_metadata_fails_closed() {\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n    let db_path = db.path.as_ref().unwrap().clone();\n    let metadata_root_page = {\n        let conn = db.connect();\n        conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n        conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY, v TEXT)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO t VALUES (1, 'a')\").unwrap();\n        conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n        conn.execute(\"INSERT INTO t VALUES (2, 'b')\").unwrap();\n        metadata_root_page(&conn)\n    };\n    force_close_for_artifact_tamper(&mut db);\n    tamper_db_metadata_row_value(&db_path, metadata_root_page, -1);\n    let wal_path = wal_path_for_db(&db_path);\n    let _ = std::fs::remove_file(&wal_path);\n    overwrite_file_with_junk(&wal_path, 0, 0);\n\n    {\n        // Ensure cold open after artifact tamper.\n        let mut manager = DATABASE_MANAGER.lock();\n        manager.clear();\n    }\n    let io = Arc::new(PlatformIO::new().unwrap());\n    match Database::open_file(io, &db_path) {\n        Ok(db2) => match db2.connect() {\n            Ok(_) => panic!(\"expected connect to fail with Corrupt\"),\n            Err(err) => assert!(\n                matches!(err, LimboError::Corrupt(_)),\n                \"unexpected connect error: {err:?}\"\n            ),\n        },\n        Err(err) => assert!(\n            matches!(err, LimboError::Corrupt(_)),\n            \"unexpected open error: {err:?}\"\n        ),\n    }\n}\n\n/// What this test checks: Recovery reconciles committed WAL first, then applies logical-log replay boundary from metadata.\n/// Why this matters: Ordering prevents double-apply and loss when WAL and logical log both exist.\n#[test]\nfn test_meta_recovery_case_4_committed_wal_reconcile_before_metadata_boundary_replay() {\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n    let db_path = db.path.as_ref().unwrap().clone();\n    let wal_path = wal_path_for_db(&db_path);\n    {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY, v TEXT)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO t VALUES (1, 'a')\").unwrap();\n        conn.execute(\"INSERT INTO t VALUES (2, 'b')\").unwrap();\n        let mvcc_store = db.get_mvcc_store();\n        advance_checkpoint_until_wal_has_commit_frame(mvcc_store, &conn);\n    }\n\n    db.restart();\n    let conn = db.connect();\n    let rows = get_rows(&conn, \"SELECT id, v FROM t ORDER BY id\");\n    assert_eq!(rows.len(), 2);\n    assert_eq!(rows[0][0].as_int().unwrap(), 1);\n    assert_eq!(rows[1][0].as_int().unwrap(), 2);\n\n    let meta = get_rows(\n        &conn,\n        \"SELECT v FROM __turso_internal_mvcc_meta WHERE k = 'persistent_tx_ts_max'\",\n    );\n    assert_eq!(meta.len(), 1);\n    assert!(\n        meta[0][0].as_int().unwrap() >= 2,\n        \"expected replay boundary to advance after committed-WAL reconciliation\",\n    );\n\n    let wal_len = wal_path.metadata().map(|m| m.len()).unwrap_or(0);\n    assert_eq!(wal_len, 0, \"reconciliation must truncate WAL at the end\");\n}\n\n/// What this test checks: Committed WAL with missing metadata row fails closed.\n/// Why this matters: Recovery cannot infer authoritative replay boundary from WAL bytes alone.\n#[test]\n#[cfg_attr(\n    feature = \"checksum\",\n    ignore = \"byte-level tamper caught by checksum layer\"\n)]\nfn test_meta_recovery_case_5_committed_wal_missing_metadata_fails_closed() {\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n    let db_path = db.path.as_ref().unwrap().clone();\n    let wal_path = wal_path_for_db(&db_path);\n    let metadata_root_page = {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY, v TEXT)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO t VALUES (1, 'a')\").unwrap();\n        let mvcc_store = db.get_mvcc_store();\n        let root_page = metadata_root_page(&conn);\n        advance_checkpoint_until_wal_has_commit_frame(mvcc_store, &conn);\n        root_page\n    };\n    force_close_for_artifact_tamper(&mut db);\n    let mutated = tamper_wal_metadata_page_empty(&wal_path, metadata_root_page);\n    assert!(\n        mutated,\n        \"expected metadata WAL frame to be mutated into missing-row shape\"\n    );\n\n    {\n        let mut manager = DATABASE_MANAGER.lock();\n        manager.clear();\n    }\n    let io = Arc::new(PlatformIO::new().unwrap());\n    match Database::open_file(io, &db_path) {\n        Ok(db2) => match db2.connect() {\n            Ok(_) => panic!(\"expected connect to fail closed\"),\n            Err(err) => assert!(matches!(err, LimboError::Corrupt(_))),\n        },\n        Err(err) => assert!(matches!(err, LimboError::Corrupt(_))),\n    }\n}\n\n/// What this test checks: Committed WAL with malformed metadata row fails closed.\n/// Why this matters: Corrupt internal metadata must never be interpreted best-effort.\n#[test]\nfn test_meta_recovery_case_6_committed_wal_corrupt_metadata_fails_closed() {\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n    let db_path = db.path.as_ref().unwrap().clone();\n    let wal_path = wal_path_for_db(&db_path);\n    let metadata_root_page = {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY, v TEXT)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO t VALUES (1, 'a')\").unwrap();\n        let mvcc_store = db.get_mvcc_store();\n        let root_page = metadata_root_page(&conn);\n        advance_checkpoint_until_wal_has_commit_frame(mvcc_store, &conn);\n        root_page\n    };\n    force_close_for_artifact_tamper(&mut db);\n    let mutated = tamper_wal_metadata_value_serial_type(&wal_path, metadata_root_page, 0);\n    assert!(\n        mutated,\n        \"expected at least one metadata WAL frame to be mutated\"\n    );\n\n    {\n        // Ensure cold open after artifact tamper.\n        let mut manager = DATABASE_MANAGER.lock();\n        manager.clear();\n    }\n    let io = Arc::new(PlatformIO::new().unwrap());\n    if Database::open_file(io, &db_path).is_ok_and(|db2| db2.connect().is_ok()) {\n        panic!(\"expected connect to fail closed\")\n    }\n}\n\n/// What this test checks: Invalid metadata-table shape (duplicates or bad key domain) fails closed.\n/// Why this matters: Schema/shape corruption in internal state must not be silently tolerated.\n#[test]\nfn test_meta_recovery_case_7_metadata_table_shape_violation_fails_closed() {\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n    let db_path = db.path.as_ref().unwrap().clone();\n    let metadata_root_page = {\n        let conn = db.connect();\n        conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n        conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY, v TEXT)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO t VALUES (1, 'a')\").unwrap();\n        metadata_root_page(&conn)\n    };\n    force_close_for_artifact_tamper(&mut db);\n    tamper_db_metadata_value_serial_type(&db_path, metadata_root_page, 0);\n    let wal_path = wal_path_for_db(&db_path);\n    let _ = std::fs::remove_file(&wal_path);\n    overwrite_file_with_junk(&wal_path, 0, 0);\n\n    {\n        // Ensure cold open after artifact tamper.\n        let mut manager = DATABASE_MANAGER.lock();\n        manager.clear();\n    }\n    let io = Arc::new(PlatformIO::new().unwrap());\n    if Database::open_file(io, &db_path).is_ok_and(|db2| db2.connect().is_ok()) {\n        panic!(\"expected connect to fail closed\")\n    }\n}\n\n/// What this test checks: Deletion of metadata row is detected and rejected.\n/// Why this matters: Missing boundary metadata makes replay decision ambiguous.\n#[test]\n#[cfg_attr(\n    feature = \"checksum\",\n    ignore = \"byte-level tamper caught by checksum layer\"\n)]\nfn test_meta_recovery_case_9_metadata_row_deleted_fails_closed() {\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n    let db_path = db.path.as_ref().unwrap().clone();\n    let metadata_root_page = {\n        let conn = db.connect();\n        conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n        conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY, v TEXT)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO t VALUES (1, 'a')\").unwrap();\n        conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n        conn.execute(\"INSERT INTO t VALUES (2, 'b')\").unwrap();\n        metadata_root_page(&conn)\n    };\n    force_close_for_artifact_tamper(&mut db);\n    tamper_db_metadata_row_key(&db_path, metadata_root_page, \"persistent_tx_ts_may\");\n    let wal_path = wal_path_for_db(&db_path);\n    let _ = std::fs::remove_file(&wal_path);\n    overwrite_file_with_junk(&wal_path, 0, 0);\n\n    {\n        let mut manager = DATABASE_MANAGER.lock();\n        manager.clear();\n    }\n    let io = Arc::new(PlatformIO::new().unwrap());\n    match Database::open_file(io, &db_path) {\n        Ok(db2) => match db2.connect() {\n            Ok(_) => panic!(\"expected connect to fail closed\"),\n            Err(err) => assert!(matches!(err, LimboError::Corrupt(_))),\n        },\n        Err(err) => assert!(matches!(err, LimboError::Corrupt(_))),\n    }\n}\n\n/// What this test checks: Checkpoint pager commit writes data pages and metadata row in the same WAL transaction.\n/// Why this matters: This is the atomicity mechanism replacing logical-log header checkpoint timestamps.\n#[test]\nfn test_meta_checkpoint_case_10_metadata_upsert_is_atomic_with_pager_commit() {\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n    {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY, v TEXT)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO t VALUES (1, 'a')\").unwrap();\n        let mvcc_store = db.get_mvcc_store();\n        let committed_ts = mvcc_store.last_committed_tx_ts.load(Ordering::SeqCst);\n        assert!(committed_ts > 0);\n\n        let pager = conn.pager.load().clone();\n        let mut checkpoint_sm = CheckpointStateMachine::new(\n            pager.clone(),\n            mvcc_store,\n            conn.clone(),\n            true,\n            conn.get_sync_mode(),\n        );\n\n        for _ in 0..50_000 {\n            if checkpoint_sm.state_for_test() == CheckpointState::CheckpointWal {\n                break;\n            }\n            match checkpoint_sm.step(&()).unwrap() {\n                TransitionResult::Io(io) => io.wait(pager.io.as_ref()).unwrap(),\n                TransitionResult::Continue => {}\n                TransitionResult::Done(_) => {\n                    panic!(\"checkpoint finished before expected stop window\")\n                }\n            }\n        }\n    }\n\n    db.restart();\n    let conn = db.connect();\n    let rows = get_rows(&conn, \"SELECT id, v FROM t ORDER BY id\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(rows[0][0].as_int().unwrap(), 1);\n    assert_eq!(rows[0][1].to_string(), \"a\");\n\n    let meta = get_rows(\n        &conn,\n        \"SELECT v FROM __turso_internal_mvcc_meta WHERE k = 'persistent_tx_ts_max'\",\n    );\n    assert_eq!(meta.len(), 1);\n    assert!(\n        meta[0][0].as_int().unwrap() >= 1,\n        \"expected metadata boundary to persist with pager commit\"\n    );\n}\n\n/// What this test checks: Auto-checkpoint post-commit failure does not invalidate committed transaction visibility on restart.\n/// Why this matters: Commit contract must remain stable even when checkpoint cleanup fails mid-flight.\n#[test]\nfn test_meta_checkpoint_case_11_auto_checkpoint_failure_after_commit_remains_recoverable() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n    conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY, v TEXT)\")\n        .unwrap();\n    conn.execute(\"INSERT INTO t VALUES (1, 'a')\").unwrap();\n\n    let mvcc_store = db.get_mvcc_store();\n    let ts1 = mvcc_store.last_committed_tx_ts.load(Ordering::SeqCst);\n    assert!(ts1 > 0, \"expected committed timestamp for first insert\");\n\n    let pager = conn.pager.load().clone();\n    let mut checkpoint_sm = CheckpointStateMachine::new(\n        pager.clone(),\n        mvcc_store.clone(),\n        conn.clone(),\n        true,\n        conn.get_sync_mode(),\n    );\n    let mut reached_truncate = false;\n    for _ in 0..50_000 {\n        if checkpoint_sm.state_for_test() == CheckpointState::TruncateLogicalLog {\n            reached_truncate = true;\n            break; // Simulate checkpoint aborting before log truncation\n        }\n        match checkpoint_sm.step(&()).unwrap() {\n            TransitionResult::Io(io) => io.wait(pager.io.as_ref()).unwrap(),\n            TransitionResult::Continue => {}\n            TransitionResult::Done(_) => {\n                panic!(\"checkpoint finished before reaching truncate state\")\n            }\n        }\n    }\n    assert!(\n        reached_truncate,\n        \"expected to reach TruncateLogicalLog state\"\n    );\n\n    // Pager commit already succeeded before log truncation.\n    // Same-process retries must advance from this durable boundary.\n    let durable_boundary = mvcc_store.durable_txid_max.load(Ordering::SeqCst);\n    assert!(\n        durable_boundary >= ts1,\n        \"expected in-memory durable checkpoint boundary to advance after pager commit: boundary={durable_boundary} ts1={ts1}\"\n    );\n\n    let sync_mode = conn.get_sync_mode();\n    let checkpoint_sm2 = CheckpointStateMachine::new(pager, mvcc_store, conn, true, sync_mode);\n    let (old_boundary, _) = checkpoint_sm2.checkpoint_bounds_for_test();\n    assert!(\n        old_boundary.unwrap_or_default() >= ts1,\n        \"expected retry checkpoint to start from durable boundary: old={old_boundary:?} ts1={ts1}\"\n    );\n}\n\n/// What this test checks: Replay gate uses metadata boundary and never applies frames at or below it.\n/// Why this matters: This enforces exactly-once effects at the DB-file apply boundary.\n#[test]\n#[cfg_attr(\n    feature = \"checksum\",\n    ignore = \"byte-level tamper caught by checksum layer\"\n)]\nfn test_meta_recovery_case_12_replay_gate_skips_at_or_below_metadata_boundary() {\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n    let db_path = db.path.as_ref().unwrap().clone();\n    let boundary = {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY, v TEXT)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO t VALUES (1, 'a')\").unwrap();\n        conn.execute(\"INSERT INTO t VALUES (2, 'b')\").unwrap();\n        conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n        let root_page = metadata_root_page(&conn);\n        conn.execute(\"INSERT INTO t VALUES (3, 'c')\").unwrap();\n        let ts3 = db\n            .get_mvcc_store()\n            .last_committed_tx_ts\n            .load(Ordering::SeqCst);\n        drop(conn);\n        force_close_for_artifact_tamper(&mut db);\n        tamper_db_metadata_row_value_by_key(\n            &db_path,\n            root_page,\n            MVCC_META_KEY_PERSISTENT_TX_TS_MAX,\n            ts3 as i64,\n        );\n        ts3\n    };\n\n    db.restart();\n    let conn = db.connect();\n    let rows = get_rows(&conn, \"SELECT id, v FROM t ORDER BY id\");\n    assert_eq!(rows.len(), 2);\n    assert_eq!(rows[0][0].as_int().unwrap(), 1);\n    assert_eq!(rows[1][0].as_int().unwrap(), 2);\n\n    let meta = get_rows(\n        &conn,\n        \"SELECT v FROM __turso_internal_mvcc_meta WHERE k = 'persistent_tx_ts_max'\",\n    );\n    assert_eq!(meta.len(), 1);\n    assert_eq!(meta[0][0].as_int().unwrap() as u64, boundary);\n}\n\n/// What this test checks: Core MVCC read/write semantics hold for this operation sequence.\n/// Why this matters: These are foundational invariants; regressions here invalidate higher-level SQL behavior.\n#[test]\nfn test_mvcc_memory_keeps_builtin_table_valued_functions() {\n    let db = MvccTestDb::new();\n    let rows = get_rows(&db.conn, \"SELECT value FROM generate_series(1,3)\");\n    assert_eq!(rows.len(), 3);\n    assert_eq!(rows[0][0].as_int().unwrap(), 1);\n    assert_eq!(rows[1][0].as_int().unwrap(), 2);\n    assert_eq!(rows[2][0].as_int().unwrap(), 3);\n}\n\n/// What this test checks: Core MVCC read/write semantics hold for this operation sequence.\n/// Why this matters: These are foundational invariants; regressions here invalidate higher-level SQL behavior.\n#[test]\nfn test_insert_read() {\n    let db = MvccTestDb::new();\n\n    let tx1 = db\n        .mvcc_store\n        .begin_tx(db.conn.pager.load().clone())\n        .unwrap();\n    let tx1_row = generate_simple_string_row((-2).into(), 1, \"Hello\");\n    db.mvcc_store.insert(tx1, tx1_row.clone()).unwrap();\n    let row = db\n        .mvcc_store\n        .read(\n            tx1,\n            &RowID {\n                table_id: (-2).into(),\n                row_id: RowKey::Int(1),\n            },\n        )\n        .unwrap()\n        .unwrap();\n    assert_eq!(tx1_row, row);\n    commit_tx(db.mvcc_store.clone(), &db.conn, tx1).unwrap();\n\n    let tx2 = db\n        .mvcc_store\n        .begin_tx(db.conn.pager.load().clone())\n        .unwrap();\n    let row = db\n        .mvcc_store\n        .read(\n            tx2,\n            &RowID {\n                table_id: (-2).into(),\n                row_id: RowKey::Int(1),\n            },\n        )\n        .unwrap()\n        .unwrap();\n    assert_eq!(tx1_row, row);\n}\n\n/// What this test checks: Core MVCC read/write semantics hold for this operation sequence.\n/// Why this matters: These are foundational invariants; regressions here invalidate higher-level SQL behavior.\n#[test]\nfn test_read_nonexistent() {\n    let db = MvccTestDb::new();\n    let tx = db\n        .mvcc_store\n        .begin_tx(db.conn.pager.load().clone())\n        .unwrap();\n    let row = db.mvcc_store.read(\n        tx,\n        &RowID {\n            table_id: (-2).into(),\n            row_id: RowKey::Int(1),\n        },\n    );\n    assert!(row.unwrap().is_none());\n}\n\n/// What this test checks: Core MVCC read/write semantics hold for this operation sequence.\n/// Why this matters: These are foundational invariants; regressions here invalidate higher-level SQL behavior.\n#[test]\nfn test_delete() {\n    let db = MvccTestDb::new();\n\n    let tx1 = db\n        .mvcc_store\n        .begin_tx(db.conn.pager.load().clone())\n        .unwrap();\n    let tx1_row = generate_simple_string_row((-2).into(), 1, \"Hello\");\n    db.mvcc_store.insert(tx1, tx1_row.clone()).unwrap();\n    let row = db\n        .mvcc_store\n        .read(\n            tx1,\n            &RowID {\n                table_id: (-2).into(),\n                row_id: RowKey::Int(1),\n            },\n        )\n        .unwrap()\n        .unwrap();\n    assert_eq!(tx1_row, row);\n    db.mvcc_store\n        .delete(\n            tx1,\n            RowID {\n                table_id: (-2).into(),\n                row_id: RowKey::Int(1),\n            },\n        )\n        .unwrap();\n    let row = db\n        .mvcc_store\n        .read(\n            tx1,\n            &RowID {\n                table_id: (-2).into(),\n                row_id: RowKey::Int(1),\n            },\n        )\n        .unwrap();\n    assert!(row.is_none());\n    commit_tx(db.mvcc_store.clone(), &db.conn, tx1).unwrap();\n\n    let tx2 = db\n        .mvcc_store\n        .begin_tx(db.conn.pager.load().clone())\n        .unwrap();\n    let row = db\n        .mvcc_store\n        .read(\n            tx2,\n            &RowID {\n                table_id: (-2).into(),\n                row_id: RowKey::Int(1),\n            },\n        )\n        .unwrap();\n    assert!(row.is_none());\n}\n\n/// What this test checks: Core MVCC read/write semantics hold for this operation sequence.\n/// Why this matters: These are foundational invariants; regressions here invalidate higher-level SQL behavior.\n#[test]\nfn test_delete_nonexistent() {\n    let db = MvccTestDb::new();\n    let tx = db\n        .mvcc_store\n        .begin_tx(db.conn.pager.load().clone())\n        .unwrap();\n    assert!(!db\n        .mvcc_store\n        .delete(\n            tx,\n            RowID {\n                table_id: (-2).into(),\n                row_id: RowKey::Int(1)\n            },\n        )\n        .unwrap());\n}\n\n/// What this test checks: Core MVCC read/write semantics hold for this operation sequence.\n/// Why this matters: These are foundational invariants; regressions here invalidate higher-level SQL behavior.\n#[test]\nfn test_commit() {\n    let db = MvccTestDb::new();\n    let tx1 = db\n        .mvcc_store\n        .begin_tx(db.conn.pager.load().clone())\n        .unwrap();\n    let tx1_row = generate_simple_string_row((-2).into(), 1, \"Hello\");\n    db.mvcc_store.insert(tx1, tx1_row.clone()).unwrap();\n    let row = db\n        .mvcc_store\n        .read(\n            tx1,\n            &RowID {\n                table_id: (-2).into(),\n                row_id: RowKey::Int(1),\n            },\n        )\n        .unwrap()\n        .unwrap();\n    assert_eq!(tx1_row, row);\n    let tx1_updated_row = generate_simple_string_row((-2).into(), 1, \"World\");\n    db.mvcc_store.update(tx1, tx1_updated_row.clone()).unwrap();\n    let row = db\n        .mvcc_store\n        .read(\n            tx1,\n            &RowID {\n                table_id: (-2).into(),\n                row_id: RowKey::Int(1),\n            },\n        )\n        .unwrap()\n        .unwrap();\n    assert_eq!(tx1_updated_row, row);\n    commit_tx(db.mvcc_store.clone(), &db.conn, tx1).unwrap();\n\n    let tx2 = db\n        .mvcc_store\n        .begin_tx(db.conn.pager.load().clone())\n        .unwrap();\n    let row = db\n        .mvcc_store\n        .read(\n            tx2,\n            &RowID {\n                table_id: (-2).into(),\n                row_id: RowKey::Int(1),\n            },\n        )\n        .unwrap()\n        .unwrap();\n    commit_tx(db.mvcc_store.clone(), &db.conn, tx2).unwrap();\n    assert_eq!(tx1_updated_row, row);\n    db.mvcc_store.drop_unused_row_versions();\n}\n\n/// What this test checks: Rollback/savepoint behavior restores exactly the intended state when statements or transactions fail.\n/// Why this matters: Partial rollback mistakes leave data in impossible intermediate states.\n#[test]\nfn test_rollback() {\n    let db = MvccTestDb::new();\n    let tx1 = db\n        .mvcc_store\n        .begin_tx(db.conn.pager.load().clone())\n        .unwrap();\n    let row1 = generate_simple_string_row((-2).into(), 1, \"Hello\");\n    db.mvcc_store.insert(tx1, row1.clone()).unwrap();\n    let row2 = db\n        .mvcc_store\n        .read(\n            tx1,\n            &RowID {\n                table_id: (-2).into(),\n                row_id: RowKey::Int(1),\n            },\n        )\n        .unwrap()\n        .unwrap();\n    assert_eq!(row1, row2);\n    let row3 = generate_simple_string_row((-2).into(), 1, \"World\");\n    db.mvcc_store.update(tx1, row3.clone()).unwrap();\n    let row4 = db\n        .mvcc_store\n        .read(\n            tx1,\n            &RowID {\n                table_id: (-2).into(),\n                row_id: RowKey::Int(1),\n            },\n        )\n        .unwrap()\n        .unwrap();\n    assert_eq!(row3, row4);\n    db.mvcc_store.rollback_tx(\n        tx1,\n        db.conn.pager.load().clone(),\n        &db.conn,\n        crate::MAIN_DB_ID,\n    );\n    let tx2 = db\n        .mvcc_store\n        .begin_tx(db.conn.pager.load().clone())\n        .unwrap();\n    let row5 = db\n        .mvcc_store\n        .read(\n            tx2,\n            &RowID {\n                table_id: (-2).into(),\n                row_id: RowKey::Int(1),\n            },\n        )\n        .unwrap();\n    assert_eq!(row5, None);\n}\n\n/// What this test checks: MVCC transaction visibility and conflict handling follow the intended isolation behavior.\n/// Why this matters: Concurrency bugs are correctness bugs: they create anomalies users can observe as wrong query results.\n#[test]\nfn test_dirty_write() {\n    let db = MvccTestDb::new();\n\n    // T1 inserts a row with ID 1, but does not commit.\n    let tx1 = db\n        .mvcc_store\n        .begin_tx(db.conn.pager.load().clone())\n        .unwrap();\n    let tx1_row = generate_simple_string_row((-2).into(), 1, \"Hello\");\n    db.mvcc_store.insert(tx1, tx1_row.clone()).unwrap();\n    let row = db\n        .mvcc_store\n        .read(\n            tx1,\n            &RowID {\n                table_id: (-2).into(),\n                row_id: RowKey::Int(1),\n            },\n        )\n        .unwrap()\n        .unwrap();\n    assert_eq!(tx1_row, row);\n\n    let conn2 = db.db.connect().unwrap();\n    // T2 attempts to delete row with ID 1, but fails because T1 has not committed.\n    let tx2 = db.mvcc_store.begin_tx(conn2.pager.load().clone()).unwrap();\n    let tx2_row = generate_simple_string_row((-2).into(), 1, \"World\");\n    assert!(!db.mvcc_store.update(tx2, tx2_row).unwrap());\n\n    let row = db\n        .mvcc_store\n        .read(\n            tx1,\n            &RowID {\n                table_id: (-2).into(),\n                row_id: RowKey::Int(1),\n            },\n        )\n        .unwrap()\n        .unwrap();\n    assert_eq!(tx1_row, row);\n}\n\n/// What this test checks: MVCC transaction visibility and conflict handling follow the intended isolation behavior.\n/// Why this matters: Concurrency bugs are correctness bugs: they create anomalies users can observe as wrong query results.\n#[test]\nfn test_dirty_read() {\n    let db = MvccTestDb::new();\n\n    // T1 inserts a row with ID 1, but does not commit.\n    let tx1 = db\n        .mvcc_store\n        .begin_tx(db.conn.pager.load().clone())\n        .unwrap();\n    let row1 = generate_simple_string_row((-2).into(), 1, \"Hello\");\n    db.mvcc_store.insert(tx1, row1).unwrap();\n\n    // T2 attempts to read row with ID 1, but doesn't see one because T1 has not committed.\n    let conn2 = db.db.connect().unwrap();\n    let tx2 = db.mvcc_store.begin_tx(conn2.pager.load().clone()).unwrap();\n    let row2 = db\n        .mvcc_store\n        .read(\n            tx2,\n            &RowID {\n                table_id: (-2).into(),\n                row_id: RowKey::Int(1),\n            },\n        )\n        .unwrap();\n    assert_eq!(row2, None);\n}\n\n/// What this test checks: MVCC transaction visibility and conflict handling follow the intended isolation behavior.\n/// Why this matters: Concurrency bugs are correctness bugs: they create anomalies users can observe as wrong query results.\n#[test]\nfn test_dirty_read_deleted() {\n    let db = MvccTestDb::new();\n\n    // T1 inserts a row with ID 1 and commits.\n    let tx1 = db\n        .mvcc_store\n        .begin_tx(db.conn.pager.load().clone())\n        .unwrap();\n    let tx1_row = generate_simple_string_row((-2).into(), 1, \"Hello\");\n    db.mvcc_store.insert(tx1, tx1_row.clone()).unwrap();\n    commit_tx(db.mvcc_store.clone(), &db.conn, tx1).unwrap();\n\n    // T2 deletes row with ID 1, but does not commit.\n    let conn2 = db.db.connect().unwrap();\n    let tx2 = db.mvcc_store.begin_tx(conn2.pager.load().clone()).unwrap();\n    assert!(db\n        .mvcc_store\n        .delete(\n            tx2,\n            RowID {\n                table_id: (-2).into(),\n                row_id: RowKey::Int(1)\n            },\n        )\n        .unwrap());\n\n    // T3 reads row with ID 1, but doesn't see the delete because T2 hasn't committed.\n    let conn3 = db.db.connect().unwrap();\n    let tx3 = db.mvcc_store.begin_tx(conn3.pager.load().clone()).unwrap();\n    let row = db\n        .mvcc_store\n        .read(\n            tx3,\n            &RowID {\n                table_id: (-2).into(),\n                row_id: RowKey::Int(1),\n            },\n        )\n        .unwrap()\n        .unwrap();\n    assert_eq!(tx1_row, row);\n}\n\n/// What this test checks: Core MVCC read/write semantics hold for this operation sequence.\n/// Why this matters: These are foundational invariants; regressions here invalidate higher-level SQL behavior.\n#[test]\nfn test_fuzzy_read() {\n    let db = MvccTestDb::new();\n\n    // T1 inserts a row with ID 1 and commits.\n    let tx1 = db\n        .mvcc_store\n        .begin_tx(db.conn.pager.load().clone())\n        .unwrap();\n    let tx1_row = generate_simple_string_row((-2).into(), 1, \"First\");\n    db.mvcc_store.insert(tx1, tx1_row.clone()).unwrap();\n    let row = db\n        .mvcc_store\n        .read(\n            tx1,\n            &RowID {\n                table_id: (-2).into(),\n                row_id: RowKey::Int(1),\n            },\n        )\n        .unwrap()\n        .unwrap();\n    assert_eq!(tx1_row, row);\n    commit_tx(db.mvcc_store.clone(), &db.conn, tx1).unwrap();\n\n    // T2 reads the row with ID 1 within an active transaction.\n    let conn2 = db.db.connect().unwrap();\n    let tx2 = db.mvcc_store.begin_tx(conn2.pager.load().clone()).unwrap();\n    let row = db\n        .mvcc_store\n        .read(\n            tx2,\n            &RowID {\n                table_id: (-2).into(),\n                row_id: RowKey::Int(1),\n            },\n        )\n        .unwrap()\n        .unwrap();\n    assert_eq!(tx1_row, row);\n\n    // T3 updates the row and commits.\n    let conn3 = db.db.connect().unwrap();\n    let tx3 = db.mvcc_store.begin_tx(conn3.pager.load().clone()).unwrap();\n    let tx3_row = generate_simple_string_row((-2).into(), 1, \"Second\");\n    db.mvcc_store.update(tx3, tx3_row).unwrap();\n    commit_tx(db.mvcc_store.clone(), &conn3, tx3).unwrap();\n\n    // T2 still reads the same version of the row as before.\n    let row = db\n        .mvcc_store\n        .read(\n            tx2,\n            &RowID {\n                table_id: (-2).into(),\n                row_id: RowKey::Int(1),\n            },\n        )\n        .unwrap()\n        .unwrap();\n    assert_eq!(tx1_row, row);\n\n    // T2 tries to update the row, but fails because T3 has already committed an update to the row,\n    // so T2 trying to write would violate snapshot isolation if it succeeded.\n    let tx2_newrow = generate_simple_string_row((-2).into(), 1, \"Third\");\n    let update_result = db.mvcc_store.update(tx2, tx2_newrow);\n    assert!(matches!(update_result, Err(LimboError::WriteWriteConflict)));\n}\n\n/// What this test checks: MVCC transaction visibility and conflict handling follow the intended isolation behavior.\n/// Why this matters: Concurrency bugs are correctness bugs: they create anomalies users can observe as wrong query results.\n#[test]\nfn test_lost_update() {\n    let db = MvccTestDb::new();\n\n    // T1 inserts a row with ID 1 and commits.\n    let tx1 = db\n        .mvcc_store\n        .begin_tx(db.conn.pager.load().clone())\n        .unwrap();\n    let tx1_row = generate_simple_string_row((-2).into(), 1, \"Hello\");\n    db.mvcc_store.insert(tx1, tx1_row.clone()).unwrap();\n    let row = db\n        .mvcc_store\n        .read(\n            tx1,\n            &RowID {\n                table_id: (-2).into(),\n                row_id: RowKey::Int(1),\n            },\n        )\n        .unwrap()\n        .unwrap();\n    assert_eq!(tx1_row, row);\n    commit_tx(db.mvcc_store.clone(), &db.conn, tx1).unwrap();\n\n    // T2 attempts to update row ID 1 within an active transaction.\n    let conn2 = db.db.connect().unwrap();\n    let tx2 = db.mvcc_store.begin_tx(conn2.pager.load().clone()).unwrap();\n    let tx2_row = generate_simple_string_row((-2).into(), 1, \"World\");\n    assert!(db.mvcc_store.update(tx2, tx2_row.clone()).unwrap());\n\n    // T3 also attempts to update row ID 1 within an active transaction.\n    let conn3 = db.db.connect().unwrap();\n    let tx3 = db.mvcc_store.begin_tx(conn3.pager.load().clone()).unwrap();\n    let tx3_row = generate_simple_string_row((-2).into(), 1, \"Hello, world!\");\n    assert!(matches!(\n        db.mvcc_store.update(tx3, tx3_row),\n        Err(LimboError::WriteWriteConflict)\n    ));\n    // hack: in the actual tursodb database we rollback the mvcc tx ourselves, so manually roll it back here\n    db.mvcc_store\n        .rollback_tx(tx3, conn3.pager.load().clone(), &conn3, crate::MAIN_DB_ID);\n\n    commit_tx(db.mvcc_store.clone(), &conn2, tx2).unwrap();\n    assert!(matches!(\n        commit_tx(db.mvcc_store.clone(), &conn3, tx3),\n        Err(LimboError::TxTerminated)\n    ));\n\n    let conn4 = db.db.connect().unwrap();\n    let tx4 = db.mvcc_store.begin_tx(conn4.pager.load().clone()).unwrap();\n    let row = db\n        .mvcc_store\n        .read(\n            tx4,\n            &RowID {\n                table_id: (-2).into(),\n                row_id: RowKey::Int(1),\n            },\n        )\n        .unwrap()\n        .unwrap();\n    assert_eq!(tx2_row, row);\n}\n\n// Test for the visibility to check if a new transaction can see old committed values.\n// This test checks for the typo present in the paper, explained in https://github.com/penberg/mvcc-rs/issues/15\n#[test]\nfn test_committed_visibility() {\n    let db = MvccTestDb::new();\n\n    // let's add $10 to my account since I like money\n    let tx1 = db\n        .mvcc_store\n        .begin_tx(db.conn.pager.load().clone())\n        .unwrap();\n    let tx1_row = generate_simple_string_row((-2).into(), 1, \"10\");\n    db.mvcc_store.insert(tx1, tx1_row.clone()).unwrap();\n    commit_tx(db.mvcc_store.clone(), &db.conn, tx1).unwrap();\n\n    // but I like more money, so let me try adding $10 more\n    let conn2 = db.db.connect().unwrap();\n    let tx2 = db.mvcc_store.begin_tx(conn2.pager.load().clone()).unwrap();\n    let tx2_row = generate_simple_string_row((-2).into(), 1, \"20\");\n    assert!(db.mvcc_store.update(tx2, tx2_row.clone()).unwrap());\n    let row = db\n        .mvcc_store\n        .read(\n            tx2,\n            &RowID {\n                table_id: (-2).into(),\n                row_id: RowKey::Int(1),\n            },\n        )\n        .unwrap()\n        .unwrap();\n    assert_eq!(row, tx2_row);\n\n    // can I check how much money I have?\n    let conn3 = db.db.connect().unwrap();\n    let tx3 = db.mvcc_store.begin_tx(conn3.pager.load().clone()).unwrap();\n    let row = db\n        .mvcc_store\n        .read(\n            tx3,\n            &RowID {\n                table_id: (-2).into(),\n                row_id: RowKey::Int(1),\n            },\n        )\n        .unwrap()\n        .unwrap();\n    assert_eq!(tx1_row, row);\n}\n\n// Test to check if a older transaction can see (un)committed future rows\n#[test]\nfn test_future_row() {\n    let db = MvccTestDb::new();\n\n    let tx1 = db\n        .mvcc_store\n        .begin_tx(db.conn.pager.load().clone())\n        .unwrap();\n\n    let conn2 = db.db.connect().unwrap();\n    let tx2 = db.mvcc_store.begin_tx(conn2.pager.load().clone()).unwrap();\n    let tx2_row = generate_simple_string_row((-2).into(), 1, \"Hello\");\n    db.mvcc_store.insert(tx2, tx2_row).unwrap();\n\n    // transaction in progress, so tx1 shouldn't be able to see the value\n    let row = db\n        .mvcc_store\n        .read(\n            tx1,\n            &RowID {\n                table_id: (-2).into(),\n                row_id: RowKey::Int(1),\n            },\n        )\n        .unwrap();\n    assert_eq!(row, None);\n\n    // lets commit the transaction and check if tx1 can see it\n    commit_tx(db.mvcc_store.clone(), &conn2, tx2).unwrap();\n    let row = db\n        .mvcc_store\n        .read(\n            tx1,\n            &RowID {\n                table_id: (-2).into(),\n                row_id: RowKey::Int(1),\n            },\n        )\n        .unwrap();\n    assert_eq!(row, None);\n}\n\nuse crate::mvcc::cursor::MvccLazyCursor;\nuse crate::mvcc::database::{MvStore, Row, RowID};\nuse crate::types::Text;\nuse crate::Value;\nuse crate::{Database, StepResult};\nuse crate::{MemoryIO, Statement};\nuse crate::{ValueRef, DATABASE_MANAGER};\n\n// Simple atomic clock implementation for testing\n\nfn setup_test_db() -> (MvccTestDb, u64, MVTableId, i64) {\n    let db = MvccTestDb::new();\n    db.conn\n        .execute(\"CREATE TABLE mvcc_lazy_gap_test(x INTEGER PRIMARY KEY, v TEXT)\")\n        .unwrap();\n    let root_page = get_rows(\n        &db.conn,\n        \"SELECT rootpage FROM sqlite_schema WHERE type = 'table' AND name = 'mvcc_lazy_gap_test'\",\n    )[0][0]\n        .as_int()\n        .unwrap();\n    let table_id = db.mvcc_store.get_table_id_from_root_page(root_page);\n    let btree_root_page = root_page.abs();\n\n    let tx_id = db\n        .mvcc_store\n        .begin_tx(db.conn.pager.load().clone())\n        .unwrap();\n\n    let test_rows = [\n        (5, \"row5\"),\n        (10, \"row10\"),\n        (15, \"row15\"),\n        (20, \"row20\"),\n        (30, \"row30\"),\n    ];\n\n    for (row_id, data) in test_rows.iter() {\n        let id = RowID::new(table_id, RowKey::Int(*row_id));\n        let record = ImmutableRecord::from_values(&[Value::Text(Text::new(data.to_string()))], 1);\n        let row = Row::new_table_row(id, record.as_blob().to_vec(), 1);\n        db.mvcc_store.insert(tx_id, row).unwrap();\n    }\n\n    commit_tx(db.mvcc_store.clone(), &db.conn, tx_id).unwrap();\n\n    let tx_id = db\n        .mvcc_store\n        .begin_tx(db.conn.pager.load().clone())\n        .unwrap();\n    (db, tx_id, table_id, btree_root_page)\n}\n\nfn setup_lazy_db(initial_keys: &[i64]) -> (MvccTestDb, u64, MVTableId, i64) {\n    let db = MvccTestDb::new();\n    db.conn\n        .execute(\"CREATE TABLE mvcc_lazy_basic_test(x INTEGER PRIMARY KEY, v TEXT)\")\n        .unwrap();\n    let root_page = get_rows(\n        &db.conn,\n        \"SELECT rootpage FROM sqlite_schema WHERE type = 'table' AND name = 'mvcc_lazy_basic_test'\",\n    )[0][0]\n        .as_int()\n        .unwrap();\n    let table_id = db.mvcc_store.get_table_id_from_root_page(root_page);\n    let btree_root_page = root_page.abs();\n\n    let tx_id = db\n        .mvcc_store\n        .begin_tx(db.conn.pager.load().clone())\n        .unwrap();\n\n    for i in initial_keys {\n        let id = RowID::new(table_id, RowKey::Int(*i));\n        let data = format!(\"row{i}\");\n        let record = ImmutableRecord::from_values(&[Value::Text(Text::new(data))], 1);\n        let row = Row::new_table_row(id, record.as_blob().to_vec(), 1);\n        db.mvcc_store.insert(tx_id, row).unwrap();\n    }\n\n    commit_tx(db.mvcc_store.clone(), &db.conn, tx_id).unwrap();\n\n    let tx_id = db\n        .mvcc_store\n        .begin_tx(db.conn.pager.load().clone())\n        .unwrap();\n    (db, tx_id, table_id, btree_root_page)\n}\n\npub(crate) fn commit_tx(\n    mv_store: Arc<MvStore<MvccClock>>,\n    conn: &Arc<Connection>,\n    tx_id: u64,\n) -> Result<()> {\n    let mut sm = mv_store.commit_tx(tx_id, conn).unwrap();\n    // TODO: sync IO hack\n    loop {\n        let res = sm.step(&mv_store)?;\n        match res {\n            IOResult::IO(io) => {\n                io.wait(conn.db.io.as_ref())?;\n            }\n            IOResult::Done(_) => break,\n        }\n    }\n    assert!(sm.is_finalized());\n    Ok(())\n}\n\npub(crate) fn commit_tx_no_conn(\n    db: &MvccTestDbNoConn,\n    tx_id: u64,\n    conn: &Arc<Connection>,\n) -> Result<(), LimboError> {\n    let mv_store = db.get_mvcc_store();\n    let mut sm = mv_store.commit_tx(tx_id, conn).unwrap();\n    // TODO: sync IO hack\n    loop {\n        let res = sm.step(&mv_store)?;\n        match res {\n            IOResult::IO(io) => {\n                io.wait(conn.db.io.as_ref())?;\n            }\n            IOResult::Done(_) => break,\n        }\n    }\n    assert!(sm.is_finalized());\n    Ok(())\n}\n\n/// What this test checks: Cursor traversal and seek operations honor MVCC visibility and key ordering under updates/deletes.\n/// Why this matters: Read-path correctness is critical: wrong cursor semantics directly surface as wrong query answers.\n#[test]\nfn test_lazy_scan_cursor_basic() {\n    let (db, tx_id, table_id, btree_root_page) = setup_lazy_db(&[1, 2, 3, 4, 5]);\n\n    let mut cursor = MvccLazyCursor::new(\n        db.mvcc_store.clone(),\n        tx_id,\n        i64::from(table_id),\n        MvccCursorType::Table,\n        Box::new(BTreeCursor::new(\n            db.conn.pager.load().clone(),\n            btree_root_page,\n            1,\n        )),\n    )\n    .unwrap();\n\n    // Check first row\n    let res = cursor.next().unwrap();\n    assert!(matches!(res, IOResult::Done(())));\n    assert!(cursor.has_record());\n    assert!(!cursor.is_empty());\n    let row = cursor.read_mvcc_current_row().unwrap().unwrap();\n    assert_eq!(row.id.row_id.to_int_or_panic(), 1);\n\n    // Iterate through all rows\n    let mut count = 1;\n    loop {\n        let res = cursor.next().unwrap();\n        let IOResult::Done(()) = res else {\n            panic!(\"unexpected next result {res:?}\");\n        };\n        if !cursor.has_record() {\n            break;\n        }\n        count += 1;\n        let row = cursor.read_mvcc_current_row().unwrap().unwrap();\n        assert_eq!(row.id.row_id.to_int_or_panic(), count);\n    }\n\n    // Should have found 5 rows\n    assert_eq!(count, 5);\n\n    // After the last row, is_empty should return true\n    let res = cursor.next().unwrap();\n    assert!(matches!(res, IOResult::Done(())));\n    assert!(!cursor.has_record());\n    assert!(cursor.is_empty());\n}\n\n/// What this test checks: Cursor traversal and seek operations honor MVCC visibility and key ordering under updates/deletes.\n/// Why this matters: Read-path correctness is critical: wrong cursor semantics directly surface as wrong query answers.\n#[test]\nfn test_lazy_scan_cursor_with_gaps() {\n    let (db, tx_id, table_id, btree_root_page) = setup_test_db();\n\n    let mut cursor = MvccLazyCursor::new(\n        db.mvcc_store.clone(),\n        tx_id,\n        i64::from(table_id),\n        MvccCursorType::Table,\n        Box::new(BTreeCursor::new(\n            db.conn.pager.load().clone(),\n            btree_root_page,\n            1,\n        )),\n    )\n    .unwrap();\n\n    // Check first row\n    let res = cursor.next().unwrap();\n    assert!(matches!(res, IOResult::Done(())));\n    assert!(cursor.has_record());\n    assert!(!cursor.is_empty());\n    let row = cursor.read_mvcc_current_row().unwrap().unwrap();\n    assert_eq!(row.id.row_id.to_int_or_panic(), 5);\n\n    // Test moving forward and checking IDs\n    let expected_ids = [5, 10, 15, 20, 30];\n    let mut index = 0;\n\n    let IOResult::Done(rowid) = cursor.rowid().unwrap() else {\n        unreachable!();\n    };\n    let rowid = rowid.unwrap();\n    assert_eq!(rowid, expected_ids[index]);\n\n    loop {\n        let res = cursor.next().unwrap();\n        let IOResult::Done(()) = res else {\n            panic!(\"unexpected next result {res:?}\");\n        };\n        if !cursor.has_record() {\n            break;\n        }\n        index += 1;\n        if index < expected_ids.len() {\n            let IOResult::Done(rowid) = cursor.rowid().unwrap() else {\n                unreachable!();\n            };\n            let rowid = rowid.unwrap();\n            assert_eq!(rowid, expected_ids[index]);\n        }\n    }\n\n    // Should have found all 5 rows\n    assert_eq!(index, expected_ids.len() - 1);\n}\n\n/// What this test checks: Cursor traversal and seek operations honor MVCC visibility and key ordering under updates/deletes.\n/// Why this matters: Read-path correctness is critical: wrong cursor semantics directly surface as wrong query answers.\n#[test]\nfn test_cursor_basic() {\n    let (db, tx_id, table_id, btree_root_page) = setup_lazy_db(&[1, 2, 3, 4, 5]);\n\n    let mut cursor = MvccLazyCursor::new(\n        db.mvcc_store.clone(),\n        tx_id,\n        i64::from(table_id),\n        MvccCursorType::Table,\n        Box::new(BTreeCursor::new(\n            db.conn.pager.load().clone(),\n            btree_root_page,\n            1,\n        )),\n    )\n    .unwrap();\n\n    let _ = cursor.next().unwrap();\n\n    // Check first row\n    assert!(!cursor.is_empty());\n    let row = cursor.read_mvcc_current_row().unwrap().unwrap();\n    assert_eq!(row.id.row_id.to_int_or_panic(), 1);\n\n    // Iterate through all rows\n    let mut count = 1;\n    loop {\n        let res = cursor.next().unwrap();\n        let IOResult::Done(()) = res else {\n            panic!(\"unexpected next result {res:?}\");\n        };\n        if !cursor.has_record() {\n            break;\n        }\n        count += 1;\n        let row = cursor.read_mvcc_current_row().unwrap().unwrap();\n        assert_eq!(row.id.row_id.to_int_or_panic(), count);\n    }\n\n    // Should have found 5 rows\n    assert_eq!(count, 5);\n\n    // After the last row, is_empty should return true\n    let res = cursor.next().unwrap();\n    assert!(matches!(res, IOResult::Done(())));\n    assert!(!cursor.has_record());\n    assert!(cursor.is_empty());\n}\n\n/// What this test checks: Cursor traversal and seek operations honor MVCC visibility and key ordering under updates/deletes.\n/// Why this matters: Read-path correctness is critical: wrong cursor semantics directly surface as wrong query answers.\n#[test]\nfn test_cursor_with_empty_table() {\n    let db = MvccTestDb::new();\n    {\n        // FIXME: force page 1 initialization\n        let pager = db.conn.pager.load().clone();\n        let tx_id = db.mvcc_store.begin_tx(pager).unwrap();\n        commit_tx(db.mvcc_store.clone(), &db.conn, tx_id).unwrap();\n    }\n    let tx_id = db\n        .mvcc_store\n        .begin_tx(db.conn.pager.load().clone())\n        .unwrap();\n    let table_id = -1; // Empty table\n\n    // Test LazyScanCursor with empty table\n    let mut cursor = MvccLazyCursor::new(\n        db.mvcc_store.clone(),\n        tx_id,\n        table_id,\n        MvccCursorType::Table,\n        Box::new(BTreeCursor::new(db.conn.pager.load().clone(), -table_id, 1)),\n    )\n    .unwrap();\n    assert!(cursor.is_empty());\n    let rowid = cursor.rowid().unwrap();\n    assert!(matches!(rowid, IOResult::Done(None)));\n}\n\n/// What this test checks: Cursor traversal and seek operations honor MVCC visibility and key ordering under updates/deletes.\n/// Why this matters: Read-path correctness is critical: wrong cursor semantics directly surface as wrong query answers.\n#[test]\nfn test_cursor_modification_during_scan() {\n    let _ = tracing_subscriber::fmt::try_init();\n    let (db, tx_id, table_id, btree_root_page) = setup_lazy_db(&[1, 2, 4, 5]);\n\n    let mut cursor = MvccLazyCursor::new(\n        db.mvcc_store.clone(),\n        tx_id,\n        i64::from(table_id),\n        MvccCursorType::Table,\n        Box::new(BTreeCursor::new(\n            db.conn.pager.load().clone(),\n            btree_root_page,\n            1,\n        )),\n    )\n    .unwrap();\n\n    // Read first row\n    let res = cursor.next().unwrap();\n    assert!(matches!(res, IOResult::Done(())));\n    assert!(cursor.has_record());\n    let first_row = cursor.read_mvcc_current_row().unwrap().unwrap();\n    assert_eq!(first_row.id.row_id.to_int_or_panic(), 1);\n\n    // Insert a new row with ID between existing rows\n    let new_row_id = RowID::new(table_id, RowKey::Int(3));\n    let new_row = generate_simple_string_record(\"new_row\");\n\n    let _ = cursor\n        .insert(&BTreeKey::TableRowId((\n            new_row_id.row_id.to_int_or_panic(),\n            Some(&new_row),\n        )))\n        .unwrap();\n\n    let mut read_rowids = vec![];\n    loop {\n        let res = cursor.next().unwrap();\n        let IOResult::Done(()) = res else {\n            panic!(\"unexpected next result {res:?}\");\n        };\n        if !cursor.has_record() {\n            break;\n        }\n        read_rowids.push(\n            cursor\n                .read_mvcc_current_row()\n                .unwrap()\n                .unwrap()\n                .id\n                .row_id\n                .to_int_or_panic(),\n        );\n    }\n    assert_eq!(read_rowids, vec![2, 3, 4, 5]);\n    let res = cursor.next().unwrap();\n    assert!(matches!(res, IOResult::Done(())));\n    assert!(!cursor.has_record());\n    assert!(cursor.is_empty());\n}\n\n/* States described in the Hekaton paper *for serializability*:\n\nTable 1: Case analysis of action to take when version V’s\nBegin field contains the ID of transaction TB\n------------------------------------------------------------------------------------------------------\nTB’s state   | TB’s end timestamp | Action to take when transaction T checks visibility of version V.\n------------------------------------------------------------------------------------------------------\nActive       | Not set            | V is visible only if TB=T and V’s end timestamp equals infinity.\n------------------------------------------------------------------------------------------------------\nPreparing    | TS                 | V’s begin timestamp will be TS ut V is not yet committed. Use TS\n                                  | as V’s begin time when testing visibility. If the test is true,\n                                  | allow T to speculatively read V. Committed TS V’s begin timestamp\n                                  | will be TS and V is committed. Use TS as V’s begin time to test\n                                  | visibility.\n------------------------------------------------------------------------------------------------------\nCommitted    | TS                 | V’s begin timestamp will be TS and V is committed. Use TS as V’s\n                                  | begin time to test visibility.\n------------------------------------------------------------------------------------------------------\nAborted      | Irrelevant         | Ignore V; it’s a garbage version.\n------------------------------------------------------------------------------------------------------\nTerminated   | Irrelevant         | Reread V’s Begin field. TB has terminated so it must have finalized\nor not found |                    | the timestamp.\n------------------------------------------------------------------------------------------------------\n\nTable 2: Case analysis of action to take when V's End field\ncontains a transaction ID TE.\n------------------------------------------------------------------------------------------------------\nTE’s state   | TE’s end timestamp | Action to take when transaction T checks visibility of a version V\n             |                    | as of read time RT.\n------------------------------------------------------------------------------------------------------\nActive       | Not set            | V is visible only if TE is not T.\n------------------------------------------------------------------------------------------------------\nPreparing    | TS                 | V’s end timestamp will be TS provided that TE commits. If TS > RT,\n                                  | V is visible to T. If TS < RT, T speculatively ignores V.\n------------------------------------------------------------------------------------------------------\nCommitted    | TS                 | V’s end timestamp will be TS and V is committed. Use TS as V’s end\n                                  | timestamp when testing visibility.\n------------------------------------------------------------------------------------------------------\nAborted      | Irrelevant         | V is visible.\n------------------------------------------------------------------------------------------------------\nTerminated   | Irrelevant         | Reread V’s End field. TE has terminated so it must have finalized\nor not found |                    | the timestamp.\n*/\n\nfn new_tx(tx_id: TxID, begin_ts: u64, state: TransactionState) -> Transaction {\n    let state = state.into();\n    Transaction {\n        state,\n        tx_id,\n        begin_ts,\n        write_set: SkipSet::new(),\n        read_set: SkipSet::new(),\n        header: RwLock::new(DatabaseHeader::default()),\n        header_dirty: AtomicBool::new(false),\n        savepoint_stack: RwLock::new(Vec::new()),\n        pager_commit_lock_held: AtomicBool::new(false),\n        commit_dep_counter: AtomicU64::new(0),\n        abort_now: AtomicBool::new(false),\n        commit_dep_set: Mutex::new(HashSet::default()),\n    }\n}\n\n/// What this test checks: MVCC transaction visibility and conflict handling follow the intended isolation behavior.\n/// Why this matters: Concurrency bugs are correctness bugs: they create anomalies users can observe as wrong query results.\n#[test]\nfn test_snapshot_isolation_tx_visible1() {\n    let txs: SkipMap<TxID, Transaction> = SkipMap::from_iter([\n        (1, new_tx(1, 1, TransactionState::Committed(2))),\n        (2, new_tx(2, 2, TransactionState::Committed(5))),\n        (3, new_tx(3, 3, TransactionState::Aborted)),\n        (5, new_tx(5, 5, TransactionState::Preparing(8))),\n        (6, new_tx(6, 6, TransactionState::Committed(10))),\n        (7, new_tx(7, 7, TransactionState::Active)),\n        // tx 8 with Preparing(3): current_tx (begin_ts=4) can speculatively read\n        (8, new_tx(8, 1, TransactionState::Preparing(3))),\n    ]);\n    let finalized_tx_states: SkipMap<TxID, TransactionState> = SkipMap::new();\n\n    let current_tx = new_tx(4, 4, TransactionState::Preparing(7));\n\n    let rv_visible = |begin: Option<TxTimestampOrID>, end: Option<TxTimestampOrID>| {\n        let row_version = RowVersion {\n            id: 0, // Dummy ID for visibility tests\n            begin,\n            end,\n            row: generate_simple_string_row((-2).into(), 1, \"testme\"),\n            btree_resident: false,\n        };\n        tracing::debug!(\"Testing visibility of {row_version:?}\");\n        row_version.is_visible_to(&current_tx, &txs, &finalized_tx_states)\n    };\n\n    // begin visible:   transaction committed with ts < current_tx.begin_ts\n    // end visible:     inf\n    assert!(rv_visible(Some(TxTimestampOrID::TxID(1)), None));\n\n    // begin invisible: transaction committed with ts > current_tx.begin_ts\n    assert!(!rv_visible(Some(TxTimestampOrID::TxID(2)), None));\n\n    // begin invisible: transaction aborted\n    assert!(!rv_visible(Some(TxTimestampOrID::TxID(3)), None));\n\n    // begin visible:   timestamp < current_tx.begin_ts\n    // end invisible:   transaction committed with ts > current_tx.begin_ts\n    assert!(!rv_visible(\n        Some(TxTimestampOrID::Timestamp(0)),\n        Some(TxTimestampOrID::TxID(1))\n    ));\n\n    // begin visible:   timestamp < current_tx.begin_ts\n    // end visible:     transaction committed with ts < current_tx.begin_ts\n    assert!(rv_visible(\n        Some(TxTimestampOrID::Timestamp(0)),\n        Some(TxTimestampOrID::TxID(2))\n    ));\n\n    // begin visible:   timestamp < current_tx.begin_ts\n    // end visible:     transaction aborted, delete never happened (Table 2)\n    assert!(rv_visible(\n        Some(TxTimestampOrID::Timestamp(0)),\n        Some(TxTimestampOrID::TxID(3))\n    ));\n\n    // begin invisible: transaction preparing with end_ts(8) > begin_ts(4)\n    // Speculative read condition (begin_ts >= end_ts) is false: 4 >= 8 is false\n    assert!(!rv_visible(Some(TxTimestampOrID::TxID(5)), None));\n\n    // begin VISIBLE via speculative read: tx 8 is Preparing(3), begin_ts(4) >= end_ts(3)\n    // Hekaton Table 1: speculatively read and register commit dependency\n    assert!(rv_visible(Some(TxTimestampOrID::TxID(8)), None));\n    // Verify dependency was registered via register-and-report protocol\n    assert_eq!(\n        current_tx.commit_dep_counter.load(Ordering::Acquire),\n        1,\n        \"speculative read should register a commit dependency\"\n    );\n\n    // begin invisible: transaction committed with ts > current_tx.begin_ts\n    assert!(!rv_visible(Some(TxTimestampOrID::TxID(6)), None));\n\n    // begin invisible: transaction active\n    assert!(!rv_visible(Some(TxTimestampOrID::TxID(7)), None));\n\n    // begin invisible: transaction committed with ts > current_tx.begin_ts\n    assert!(!rv_visible(Some(TxTimestampOrID::TxID(6)), None));\n\n    // begin invisible:   transaction active\n    assert!(!rv_visible(Some(TxTimestampOrID::TxID(7)), None));\n\n    // begin visible:   timestamp < current_tx.begin_ts\n    // end visible:     transaction preparing with TS(8) > RT(4) (Table 2)\n    assert!(rv_visible(\n        Some(TxTimestampOrID::Timestamp(0)),\n        Some(TxTimestampOrID::TxID(5))\n    ));\n\n    // begin invisible: timestamp > current_tx.begin_ts\n    assert!(!rv_visible(\n        Some(TxTimestampOrID::Timestamp(6)),\n        Some(TxTimestampOrID::TxID(6))\n    ));\n\n    // begin visible:   timestamp < current_tx.begin_ts\n    // end visible:     some active transaction will eventually overwrite this version,\n    //                  but that hasn't happened\n    //                  (this is the https://avi.im/blag/2023/hekaton-paper-typo/ case, I believe!)\n    assert!(rv_visible(\n        Some(TxTimestampOrID::Timestamp(0)),\n        Some(TxTimestampOrID::TxID(7))\n    ));\n\n    assert!(!rv_visible(None, None));\n}\n\n#[test]\nfn test_visibility_uses_finalized_state_for_removed_committed_tx() {\n    let txs: SkipMap<TxID, Transaction> = SkipMap::new();\n    let finalized_tx_states: SkipMap<TxID, TransactionState> =\n        SkipMap::from_iter([(42, TransactionState::Committed(5))]);\n    let reader = new_tx(7, 10, TransactionState::Active);\n\n    let inserted_row = RowVersion {\n        id: 1,\n        begin: Some(TxTimestampOrID::TxID(42)),\n        end: None,\n        row: generate_simple_string_row((-2).into(), 1, \"x\"),\n        btree_resident: false,\n    };\n    assert!(\n        inserted_row.is_visible_to(&reader, &txs, &finalized_tx_states),\n        \"stale begin=TxID should resolve via finalized committed state\"\n    );\n\n    let deleted_row = RowVersion {\n        id: 2,\n        begin: Some(TxTimestampOrID::Timestamp(1)),\n        end: Some(TxTimestampOrID::TxID(42)),\n        row: generate_simple_string_row((-2).into(), 2, \"y\"),\n        btree_resident: false,\n    };\n    assert!(\n        !deleted_row.is_visible_to(&reader, &txs, &finalized_tx_states),\n        \"stale end=TxID should resolve via finalized committed state\"\n    );\n}\n\n#[test]\nfn test_read_only_commit_does_not_cache_finalized_state() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n    let mvcc_store = db.get_mvcc_store();\n    conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY, v INTEGER)\")\n        .unwrap();\n\n    // Establish a clean baseline after schema/setup writes.\n    mvcc_store.drop_unused_row_versions();\n    let baseline = mvcc_store.finalized_tx_states.len();\n\n    conn.execute(\"BEGIN CONCURRENT\").unwrap();\n    let _ = get_rows(&conn, \"SELECT 1\");\n    conn.execute(\"COMMIT\").unwrap();\n\n    assert_eq!(\n        mvcc_store.finalized_tx_states.len(),\n        baseline,\n        \"read-only commit should not add finalized tx cache entries\"\n    );\n}\n\n#[test]\nfn test_drop_unused_row_versions_prunes_unreferenced_finalized_tx_states() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n    let mvcc_store = db.get_mvcc_store();\n    conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY, v INTEGER)\")\n        .unwrap();\n\n    // Establish a clean baseline after schema/setup writes.\n    mvcc_store.drop_unused_row_versions();\n    let baseline = mvcc_store.finalized_tx_states.len();\n\n    conn.execute(\"INSERT INTO t VALUES (1, 1)\").unwrap();\n    let after_write = mvcc_store.finalized_tx_states.len();\n    assert!(\n        after_write > baseline,\n        \"write commit should add at least one finalized tx cache entry\"\n    );\n\n    mvcc_store.drop_unused_row_versions();\n\n    assert_eq!(\n        mvcc_store.finalized_tx_states.len(),\n        baseline,\n        \"GC scan should prune finalized tx cache entries with no remaining TxID references\"\n    );\n}\n\n/// Test Hekaton register-and-report: speculative read increments CommitDepCounter\n/// and adds to CommitDepSet.\n#[test]\nfn test_commit_dependency_speculative_read() {\n    let txs: SkipMap<TxID, Transaction> =\n        SkipMap::from_iter([(1, new_tx(1, 1, TransactionState::Preparing(5)))]);\n    let finalized_tx_states: SkipMap<TxID, TransactionState> = SkipMap::new();\n\n    // Reader with begin_ts=10 > end_ts=5 → speculative read → dependency\n    let reader = new_tx(2, 10, TransactionState::Active);\n\n    let rv = RowVersion {\n        id: 0,\n        begin: Some(TxTimestampOrID::TxID(1)),\n        end: None,\n        row: generate_simple_string_row((-2).into(), 1, \"test\"),\n        btree_resident: false,\n    };\n\n    assert_eq!(reader.commit_dep_counter.load(Ordering::Acquire), 0);\n\n    // Speculative read: begin_ts(10) >= end_ts(5) → visible, dependency registered\n    assert!(rv.is_visible_to(&reader, &txs, &finalized_tx_states));\n    assert_eq!(reader.commit_dep_counter.load(Ordering::Acquire), 1);\n\n    // Verify tx 1's CommitDepSet contains reader's tx_id\n    let dep_set = txs.get(&1).unwrap();\n    assert_eq!(\n        *dep_set.value().commit_dep_set.lock(),\n        HashSet::from_iter([2])\n    );\n}\n\n/// Test cascade abort: when depended-on tx aborts, it sets AbortNow on dependents\n/// and decrements their CommitDepCounter.\n#[test]\nfn test_commit_dependency_cascade_abort() {\n    let txs: SkipMap<TxID, Transaction> =\n        SkipMap::from_iter([(1, new_tx(1, 1, TransactionState::Preparing(5)))]);\n    let finalized_tx_states: SkipMap<TxID, TransactionState> = SkipMap::new();\n\n    let reader = new_tx(2, 10, TransactionState::Active);\n\n    let rv = RowVersion {\n        id: 0,\n        begin: Some(TxTimestampOrID::TxID(1)),\n        end: None,\n        row: generate_simple_string_row((-2).into(), 1, \"test\"),\n        btree_resident: false,\n    };\n\n    // Speculative read registers dependency\n    assert!(rv.is_visible_to(&reader, &txs, &finalized_tx_states));\n    assert_eq!(reader.commit_dep_counter.load(Ordering::Acquire), 1);\n    assert!(!reader.abort_now.load(Ordering::Acquire));\n\n    // Simulate tx 1 aborting and cascading to dependents\n    let tx1 = txs.get(&1).unwrap();\n    let tx1 = tx1.value();\n    tx1.state.store(TransactionState::Aborted);\n\n    // Add reader to txs so cascade can find it\n    txs.insert(2, reader);\n\n    for dep_tx_id in tx1.commit_dep_set.lock().drain() {\n        if let Some(dep_tx_entry) = txs.get(&dep_tx_id) {\n            let dep_tx = dep_tx_entry.value();\n            dep_tx.abort_now.store(true, Ordering::Release);\n            dep_tx.commit_dep_counter.fetch_sub(1, Ordering::AcqRel);\n        }\n    }\n\n    let reader = txs.get(&2).unwrap();\n    let reader = reader.value();\n    assert!(reader.abort_now.load(Ordering::Acquire));\n    assert_eq!(reader.commit_dep_counter.load(Ordering::Acquire), 0);\n}\n\n/// Test that registering a dependency on an already-committed tx is a no-op.\n#[test]\nfn test_commit_dependency_already_committed() {\n    let txs: SkipMap<TxID, Transaction> =\n        SkipMap::from_iter([(1, new_tx(1, 1, TransactionState::Committed(5)))]);\n\n    let reader = new_tx(2, 10, TransactionState::Active);\n\n    register_commit_dependency(&txs, &reader, 1);\n\n    assert_eq!(reader.commit_dep_counter.load(Ordering::Acquire), 0);\n    assert!(!reader.abort_now.load(Ordering::Acquire));\n}\n\n/// Test that registering a dependency on an already-aborted tx sets AbortNow.\n#[test]\nfn test_commit_dependency_already_aborted() {\n    let txs: SkipMap<TxID, Transaction> =\n        SkipMap::from_iter([(1, new_tx(1, 1, TransactionState::Aborted))]);\n\n    let reader = new_tx(2, 10, TransactionState::Active);\n\n    register_commit_dependency(&txs, &reader, 1);\n\n    assert_eq!(reader.commit_dep_counter.load(Ordering::Acquire), 0);\n    assert!(reader.abort_now.load(Ordering::Acquire));\n}\n\n/// Test speculative ignore in is_end_visible registers dependency.\n#[test]\nfn test_commit_dependency_speculative_ignore() {\n    let txs: SkipMap<TxID, Transaction> = SkipMap::from_iter([\n        (1, new_tx(1, 1, TransactionState::Committed(2))),\n        (3, new_tx(3, 3, TransactionState::Preparing(5))),\n    ]);\n    let finalized_tx_states: SkipMap<TxID, TransactionState> = SkipMap::new();\n\n    // Reader with begin_ts=10 > end_ts=5: will speculatively ignore (treat as deleted)\n    let reader = new_tx(4, 10, TransactionState::Active);\n\n    let rv = RowVersion {\n        id: 0,\n        begin: Some(TxTimestampOrID::Timestamp(2)),\n        end: Some(TxTimestampOrID::TxID(3)),\n        row: generate_simple_string_row((-2).into(), 1, \"test\"),\n        btree_resident: false,\n    };\n\n    // is_end_visible: Preparing(5), begin_ts(10) < 5 = false → deletion visible\n    // is_begin_visible: Timestamp(2), 10 >= 2 = true\n    // Combined: true && false = false (row not visible because it was deleted)\n    assert!(!rv.is_visible_to(&reader, &txs, &finalized_tx_states));\n    assert_eq!(\n        reader.commit_dep_counter.load(Ordering::Acquire),\n        1,\n        \"speculative ignore should register a commit dependency\"\n    );\n}\n\n/// Test that multiple speculative reads from the same preparing tx only\n/// register one commit dependency (dedup).\n#[test]\nfn test_commit_dependency_multiple_reads_dedup() {\n    let txs: SkipMap<TxID, Transaction> =\n        SkipMap::from_iter([(1, new_tx(1, 1, TransactionState::Preparing(5)))]);\n    let finalized_tx_states: SkipMap<TxID, TransactionState> = SkipMap::new();\n\n    let reader = new_tx(2, 10, TransactionState::Active);\n\n    let make_rv = |row_id: i64| RowVersion {\n        id: row_id as u64,\n        begin: Some(TxTimestampOrID::TxID(1)),\n        end: None,\n        row: generate_simple_string_row((-2).into(), row_id, \"test\"),\n        btree_resident: false,\n    };\n\n    // Read 3 rows from the same preparing tx — dependency is deduplicated\n    assert!(make_rv(1).is_visible_to(&reader, &txs, &finalized_tx_states));\n    assert!(make_rv(2).is_visible_to(&reader, &txs, &finalized_tx_states));\n    assert!(make_rv(3).is_visible_to(&reader, &txs, &finalized_tx_states));\n\n    assert_eq!(reader.commit_dep_counter.load(Ordering::Acquire), 1);\n\n    // tx 1's CommitDepSet has 1 entry for reader (deduplicated)\n    let dep_set = txs.get(&1).unwrap();\n    assert_eq!(dep_set.value().commit_dep_set.lock().len(), 1);\n}\n\n/// Hekaton §2.7 cascade abort with real connections and threads.\n///\n/// A Preparing writer is speculatively read by a reader on another thread.\n/// When the writer aborts, the reader's COMMIT must fail with\n/// CommitDependencyAborted (cascade abort via AbortNow).\n///\n/// Sequence:\n///   1. Writer: BEGIN CONCURRENT → UPDATE (real SQL)\n///   2. Writer state manually set to Preparing(end_ts)\n///   3. Reader thread: BEGIN → SELECT (speculative read → dependency) → INSERT\n///   4. Main thread: rollback writer → cascade abort via AbortNow\n///   5. Reader COMMIT → CommitDependencyAborted\n#[test]\nfn test_commit_dep_threaded_abort_cascades() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE t (id INTEGER PRIMARY KEY, value TEXT)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO t VALUES (1, 'initial')\").unwrap();\n        conn.close().unwrap();\n    }\n\n    let mvcc_store = db.get_mvcc_store();\n\n    // Writer: real SQL operations\n    let writer_conn = db.connect();\n    writer_conn.execute(\"BEGIN CONCURRENT\").unwrap();\n    writer_conn\n        .execute(\"UPDATE t SET value = 'modified' WHERE id = 1\")\n        .unwrap();\n    let writer_tx_id = writer_conn.get_mv_tx_id().unwrap();\n\n    // Simulate mid-commit: transition writer to Preparing.\n    // end_ts comes from the global clock so the reader's begin_ts will be >=\n    // end_ts, satisfying the speculative read condition (Hekaton Table 1).\n    let _end_ts = mvcc_store.get_commit_timestamp(|ts| {\n        mvcc_store\n            .txs\n            .get(&writer_tx_id)\n            .unwrap()\n            .value()\n            .state\n            .store(TransactionState::Preparing(ts));\n    });\n\n    // Reader signals after speculative read so main thread can abort writer.\n    let (signal_tx, signal_rx) = std::sync::mpsc::channel();\n\n    let db_arc = db.get_db();\n    let reader_handle = std::thread::spawn(move || {\n        let reader_conn = db_arc.connect().unwrap();\n        reader_conn.execute(\"BEGIN CONCURRENT\").unwrap();\n\n        // SELECT triggers speculative read: reader.begin_ts >= writer.end_ts\n        // → Hekaton Table 1: visible, register commit dependency\n        let mut stmt = reader_conn\n            .prepare(\"SELECT value FROM t WHERE id = 1\")\n            .unwrap();\n        let rows = stmt.run_collect_rows().unwrap();\n\n        // Write so COMMIT exercises the full commit state machine path.\n        reader_conn\n            .execute(\"INSERT INTO t VALUES (2, 'reader_data')\")\n            .unwrap();\n\n        // Signal: speculative read done, dependency registered\n        signal_tx.send(()).unwrap();\n\n        // COMMIT blocks in WaitForDependencies until the writer resolves.\n        // Writer will abort → AbortNow set → CommitDependencyAborted.\n        let commit_result = reader_conn.execute(\"COMMIT\");\n        let _ = reader_conn.close(); // cleanup (rolls back if still active)\n        (rows, commit_result)\n    });\n\n    // Wait for reader to complete speculative read\n    signal_rx.recv().unwrap();\n\n    // Abort writer → cascade: sets AbortNow on reader, decrements counter\n    mvcc_store.rollback_tx(\n        writer_tx_id,\n        writer_conn.pager.load().clone(),\n        &writer_conn,\n        crate::MAIN_DB_ID,\n    );\n\n    let (rows, commit_result) = reader_handle.join().unwrap();\n\n    // Reader saw the writer's modified value via speculative read\n    assert_eq!(rows.len(), 1);\n    assert_eq!(\n        rows[0][0].to_text().unwrap(),\n        \"modified\",\n        \"reader should have speculatively read the Preparing writer's value\"\n    );\n\n    // Reader's COMMIT must fail: depended-on writer aborted\n    assert!(\n        matches!(commit_result, Err(LimboError::CommitDependencyAborted)),\n        \"expected CommitDependencyAborted, got: {commit_result:?}\",\n    );\n\n    // Verify database consistency\n    {\n        let conn = db.connect();\n\n        // Only the initial value should remain\n        let mut stmt = conn.prepare(\"SELECT value FROM t WHERE id = 1\").unwrap();\n        let rows = stmt.run_collect_rows().unwrap();\n        assert_eq!(rows.len(), 1);\n        assert_eq!(rows[0][0].to_text().unwrap(), \"initial\");\n\n        // Reader's INSERT must not be visible (cascade-aborted)\n        let mut stmt = conn.prepare(\"SELECT * FROM t WHERE id = 2\").unwrap();\n        let rows = stmt.run_collect_rows().unwrap();\n        assert!(\n            rows.is_empty(),\n            \"reader's write should not be visible after cascade abort\"\n        );\n    }\n}\n\n/// Hekaton §2.7: multiple readers depending on the same Preparing writer\n/// all cascade-abort when the writer aborts.\n#[test]\nfn test_commit_dep_threaded_multiple_dependents_abort() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE t (id INTEGER PRIMARY KEY, value TEXT)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO t VALUES (1, 'initial')\").unwrap();\n        conn.close().unwrap();\n    }\n\n    let mvcc_store = db.get_mvcc_store();\n\n    // Writer\n    let writer_conn = db.connect();\n    writer_conn.execute(\"BEGIN CONCURRENT\").unwrap();\n    writer_conn\n        .execute(\"UPDATE t SET value = 'modified' WHERE id = 1\")\n        .unwrap();\n    let writer_tx_id = writer_conn.get_mv_tx_id().unwrap();\n\n    let _end_ts = mvcc_store.get_commit_timestamp(|ts| {\n        mvcc_store\n            .txs\n            .get(&writer_tx_id)\n            .unwrap()\n            .value()\n            .state\n            .store(TransactionState::Preparing(ts));\n    });\n\n    let num_readers = 4;\n    // Barrier: all readers + main thread synchronize after speculative reads\n    let barrier = std::sync::Arc::new(std::sync::Barrier::new(num_readers + 1));\n\n    let mut handles = Vec::new();\n    for i in 0..num_readers {\n        let db_arc = db.get_db();\n        let barrier_clone = barrier.clone();\n        handles.push(std::thread::spawn(move || {\n            let conn = db_arc.connect().unwrap();\n            conn.execute(\"BEGIN CONCURRENT\").unwrap();\n\n            // Speculative read from Preparing writer\n            let mut stmt = conn.prepare(\"SELECT value FROM t WHERE id = 1\").unwrap();\n            let rows = stmt.run_collect_rows().unwrap();\n\n            // Each reader writes to a unique row (no conflicts)\n            conn.execute(format!(\"INSERT INTO t VALUES ({}, 'reader_{i}')\", i + 10,))\n                .unwrap();\n\n            // Signal: all readers done with speculative reads\n            barrier_clone.wait();\n\n            let commit_result = conn.execute(\"COMMIT\");\n            let _ = conn.close();\n            (rows, commit_result)\n        }));\n    }\n\n    // Wait for all readers to complete speculative reads\n    barrier.wait();\n\n    // Abort writer → cascade to ALL readers\n    mvcc_store.rollback_tx(\n        writer_tx_id,\n        writer_conn.pager.load().clone(),\n        &writer_conn,\n        crate::MAIN_DB_ID,\n    );\n\n    for handle in handles {\n        let (rows, commit_result) = handle.join().unwrap();\n        assert_eq!(rows.len(), 1);\n        assert_eq!(rows[0][0].to_text().unwrap(), \"modified\");\n        assert!(\n            matches!(commit_result, Err(LimboError::CommitDependencyAborted)),\n            \"expected CommitDependencyAborted, got: {commit_result:?}\",\n        );\n    }\n\n    // All reader writes should be invisible — only the initial row remains\n    {\n        let conn = db.connect();\n        let mut stmt = conn.prepare(\"SELECT count(*) FROM t\").unwrap();\n        let rows = stmt.run_collect_rows().unwrap();\n        assert_eq!(rows[0][0].as_int().unwrap(), 1);\n    }\n}\n\n/// Hekaton §2.7 happy path: when a Preparing writer commits, the dependent\n/// reader's CommitDepCounter is decremented and the reader can proceed.\n#[test]\nfn test_commit_dep_threaded_commit_resolves() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE t (id INTEGER PRIMARY KEY, value TEXT)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO t VALUES (1, 'initial')\").unwrap();\n        conn.close().unwrap();\n    }\n\n    let mvcc_store = db.get_mvcc_store();\n\n    // Writer: UPDATE via real connection, then set to Preparing\n    let writer_conn = db.connect();\n    writer_conn.execute(\"BEGIN CONCURRENT\").unwrap();\n    writer_conn\n        .execute(\"UPDATE t SET value = 'committed' WHERE id = 1\")\n        .unwrap();\n    let writer_tx_id = writer_conn.get_mv_tx_id().unwrap();\n\n    let end_ts = mvcc_store.get_commit_timestamp(|ts| {\n        mvcc_store\n            .txs\n            .get(&writer_tx_id)\n            .unwrap()\n            .value()\n            .state\n            .store(TransactionState::Preparing(ts));\n    });\n\n    let (signal_tx, signal_rx) = std::sync::mpsc::channel();\n\n    let db_arc = db.get_db();\n    let reader_handle = std::thread::spawn(move || {\n        let reader_conn = db_arc.connect().unwrap();\n        reader_conn.execute(\"BEGIN CONCURRENT\").unwrap();\n\n        let mut stmt = reader_conn\n            .prepare(\"SELECT value FROM t WHERE id = 1\")\n            .unwrap();\n        let rows = stmt.run_collect_rows().unwrap();\n\n        reader_conn\n            .execute(\"INSERT INTO t VALUES (2, 'reader_data')\")\n            .unwrap();\n\n        signal_tx.send(()).unwrap();\n\n        // COMMIT blocks in WaitForDependencies. Writer will commit →\n        // counter decremented → reader proceeds.\n        let commit_result = reader_conn.execute(\"COMMIT\");\n        let _ = reader_conn.close();\n        (rows, commit_result)\n    });\n\n    signal_rx.recv().unwrap();\n\n    // Complete the writer's commit manually (postprocessing):\n    // 1. Convert TxID → Timestamp in row versions\n    // 2. Set state to Committed\n    // 3. Drain CommitDepSet, decrement dependents' counters\n    {\n        let writer_tx = mvcc_store.txs.get(&writer_tx_id).unwrap();\n        let writer_tx = writer_tx.value();\n\n        // Convert TxID→Timestamp in row versions (Hekaton §3.3 postprocessing)\n        for entry in mvcc_store.rows.iter() {\n            let mut rvs = entry.value().write();\n            for rv in rvs.iter_mut() {\n                if rv.begin == Some(TxTimestampOrID::TxID(writer_tx_id)) {\n                    rv.begin = Some(TxTimestampOrID::Timestamp(end_ts));\n                }\n                if rv.end == Some(TxTimestampOrID::TxID(writer_tx_id)) {\n                    rv.end = Some(TxTimestampOrID::Timestamp(end_ts));\n                }\n            }\n        }\n\n        // Committed state + notify dependents\n        writer_tx.state.store(TransactionState::Committed(end_ts));\n        for dep_tx_id in writer_tx.commit_dep_set.lock().drain() {\n            if let Some(dep_tx_entry) = mvcc_store.txs.get(&dep_tx_id) {\n                dep_tx_entry\n                    .value()\n                    .commit_dep_counter\n                    .fetch_sub(1, Ordering::AcqRel);\n            }\n        }\n    }\n\n    let (rows, commit_result) = reader_handle.join().unwrap();\n\n    // Reader speculatively read the writer's value\n    assert_eq!(rows.len(), 1);\n    assert_eq!(rows[0][0].to_text().unwrap(), \"committed\");\n\n    // Reader's COMMIT succeeds: dependency resolved by writer's commit\n    assert!(\n        commit_result.is_ok(),\n        \"expected reader COMMIT to succeed, got: {commit_result:?}\",\n    );\n\n    // Both writes are visible\n    {\n        let conn = db.connect();\n        let mut stmt = conn.prepare(\"SELECT value FROM t ORDER BY id\").unwrap();\n        let rows = stmt.run_collect_rows().unwrap();\n        assert_eq!(rows.len(), 2);\n        assert_eq!(rows[0][0].to_text().unwrap(), \"committed\");\n        assert_eq!(rows[1][0].to_text().unwrap(), \"reader_data\");\n    }\n}\n\n/// Regression: the write_set.is_empty() fast path used to commit read-only\n/// transactions without checking commit dependencies. A read-only tx that\n/// speculatively read from a Preparing writer must still honour AbortNow.\n#[test]\nfn test_commit_dep_threaded_readonly_abort_cascades() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE t (id INTEGER PRIMARY KEY, value TEXT)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO t VALUES (1, 'initial')\").unwrap();\n        conn.close().unwrap();\n    }\n\n    let mvcc_store = db.get_mvcc_store();\n\n    // Writer\n    let writer_conn = db.connect();\n    writer_conn.execute(\"BEGIN CONCURRENT\").unwrap();\n    writer_conn\n        .execute(\"UPDATE t SET value = 'modified' WHERE id = 1\")\n        .unwrap();\n    let writer_tx_id = writer_conn.get_mv_tx_id().unwrap();\n\n    let _end_ts = mvcc_store.get_commit_timestamp(|ts| {\n        mvcc_store\n            .txs\n            .get(&writer_tx_id)\n            .unwrap()\n            .value()\n            .state\n            .store(TransactionState::Preparing(ts));\n    });\n\n    let (signal_tx, signal_rx) = std::sync::mpsc::channel();\n\n    let db_arc = db.get_db();\n    let reader_handle = std::thread::spawn(move || {\n        let reader_conn = db_arc.connect().unwrap();\n        reader_conn.execute(\"BEGIN CONCURRENT\").unwrap();\n\n        // Read-only: no writes, only SELECT → triggers speculative read\n        let mut stmt = reader_conn\n            .prepare(\"SELECT value FROM t WHERE id = 1\")\n            .unwrap();\n        let rows = stmt.run_collect_rows().unwrap();\n\n        signal_tx.send(()).unwrap();\n\n        // COMMIT on a read-only tx hits the write_set.is_empty() fast path.\n        // It must still check commit dependencies.\n        let commit_result = reader_conn.execute(\"COMMIT\");\n        let _ = reader_conn.close();\n        (rows, commit_result)\n    });\n\n    signal_rx.recv().unwrap();\n\n    // Abort writer → cascade to read-only reader\n    mvcc_store.rollback_tx(\n        writer_tx_id,\n        writer_conn.pager.load().clone(),\n        &writer_conn,\n        crate::MAIN_DB_ID,\n    );\n\n    let (rows, commit_result) = reader_handle.join().unwrap();\n\n    assert_eq!(rows.len(), 1);\n    assert_eq!(rows[0][0].to_text().unwrap(), \"modified\");\n\n    // Read-only tx must still fail when its dependency aborts\n    assert!(\n        matches!(commit_result, Err(LimboError::CommitDependencyAborted)),\n        \"read-only tx should fail with CommitDependencyAborted, got: {commit_result:?}\",\n    );\n}\n\n/// Test that register_commit_dependency increments counter before pushing to\n/// dep_set, preventing underflow. If counter is incremented after push+unlock,\n/// a concurrent drain could fetch_sub(1) on a zero counter, wrapping to MAX.\n#[test]\nfn test_commit_dependency_counter_no_underflow() {\n    let txs: SkipMap<TxID, Transaction> =\n        SkipMap::from_iter([(1, new_tx(1, 1, TransactionState::Preparing(5)))]);\n    let reader = new_tx(2, 10, TransactionState::Active);\n\n    // Register dependency: counter should go 0 → 1\n    register_commit_dependency(&txs, &reader, 1);\n    assert_eq!(reader.commit_dep_counter.load(Ordering::Acquire), 1);\n\n    // Simulate drain (as in CommitEnd): fetch_sub should go 1 → 0, not wrap\n    reader.commit_dep_counter.fetch_sub(1, Ordering::AcqRel);\n    assert_eq!(\n        reader.commit_dep_counter.load(Ordering::Acquire),\n        0,\n        \"counter should be exactly 0, not u64::MAX (underflow)\"\n    );\n}\n\n/// Test that registering a dependency on a Terminated (aborted+removed from map)\n/// transaction correctly sets AbortNow. Before the fix, rollback_tx removed the\n/// tx from txs, so register_commit_dependency saw None and assumed \"committed.\"\n#[test]\nfn test_commit_dependency_terminated_tx_sets_abort() {\n    let txs: SkipMap<TxID, Transaction> =\n        SkipMap::from_iter([(1, new_tx(1, 1, TransactionState::Terminated))]);\n\n    let reader = new_tx(2, 10, TransactionState::Active);\n    register_commit_dependency(&txs, &reader, 1);\n\n    // Terminated means the tx aborted — must set abort_now\n    assert!(\n        reader.abort_now.load(Ordering::Acquire),\n        \"dependency on Terminated tx should set abort_now\"\n    );\n    assert_eq!(\n        reader.commit_dep_counter.load(Ordering::Acquire),\n        0,\n        \"no counter increment for aborted/terminated dependency\"\n    );\n}\n\n/// Test that when tx is NOT in the map (removed), register_commit_dependency\n/// treats it as committed (no abort_now, no counter increment). This is correct\n/// only for committed transactions. Aborted transactions should NOT be removed\n/// from the map (Issue #3 fix ensures this).\n#[test]\nfn test_commit_dependency_missing_tx_assumes_committed() {\n    let txs: SkipMap<TxID, Transaction> = SkipMap::new();\n\n    let reader = new_tx(2, 10, TransactionState::Active);\n    register_commit_dependency(&txs, &reader, 99);\n\n    assert!(\n        !reader.abort_now.load(Ordering::Acquire),\n        \"missing tx (committed+removed) should not set abort_now\"\n    );\n    assert_eq!(reader.commit_dep_counter.load(Ordering::Acquire), 0);\n}\n\n/// Test that read-only transactions with resolved dependencies do NOT advance\n/// last_committed_tx_ts. A read-only tx going through WaitForDependencies →\n/// CommitEnd would update last_committed_tx_ts, causing spurious Busy errors\n/// from acquire_exclusive_tx.\n#[test]\nfn test_commit_dep_readonly_does_not_advance_timestamp() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE t (id INTEGER PRIMARY KEY, value TEXT)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO t VALUES (1, 'initial')\").unwrap();\n        conn.close().unwrap();\n    }\n\n    let mvcc_store = db.get_mvcc_store();\n    let ts_before = mvcc_store.last_committed_tx_ts.load(Ordering::Acquire);\n\n    // Writer: UPDATE then set to Preparing\n    let writer_conn = db.connect();\n    writer_conn.execute(\"BEGIN CONCURRENT\").unwrap();\n    writer_conn\n        .execute(\"UPDATE t SET value = 'modified' WHERE id = 1\")\n        .unwrap();\n    let writer_tx_id = writer_conn.get_mv_tx_id().unwrap();\n\n    let end_ts = mvcc_store.get_commit_timestamp(|ts| {\n        mvcc_store\n            .txs\n            .get(&writer_tx_id)\n            .unwrap()\n            .value()\n            .state\n            .store(TransactionState::Preparing(ts));\n    });\n\n    let (signal_tx, signal_rx) = std::sync::mpsc::channel();\n\n    let db_arc = db.get_db();\n    let mvcc_clone = mvcc_store.clone();\n    let reader_handle = std::thread::spawn(move || {\n        let reader_conn = db_arc.connect().unwrap();\n        reader_conn.execute(\"BEGIN CONCURRENT\").unwrap();\n\n        // Read-only: SELECT only → speculative read registers dependency\n        let mut stmt = reader_conn\n            .prepare(\"SELECT value FROM t WHERE id = 1\")\n            .unwrap();\n        let _rows = stmt.run_collect_rows().unwrap();\n\n        signal_tx.send(()).unwrap();\n\n        // COMMIT: read-only with dependency → WaitForDependencies\n        let commit_result = reader_conn.execute(\"COMMIT\");\n        let _ = reader_conn.close();\n        commit_result\n    });\n\n    signal_rx.recv().unwrap();\n\n    // Complete writer's commit manually (resolve dependency)\n    {\n        let writer_tx = mvcc_store.txs.get(&writer_tx_id).unwrap();\n        let writer_tx = writer_tx.value();\n        for entry in mvcc_store.rows.iter() {\n            let mut rvs = entry.value().write();\n            for rv in rvs.iter_mut() {\n                if rv.begin == Some(TxTimestampOrID::TxID(writer_tx_id)) {\n                    rv.begin = Some(TxTimestampOrID::Timestamp(end_ts));\n                }\n                if rv.end == Some(TxTimestampOrID::TxID(writer_tx_id)) {\n                    rv.end = Some(TxTimestampOrID::Timestamp(end_ts));\n                }\n            }\n        }\n        writer_tx.state.store(TransactionState::Committed(end_ts));\n        for dep_tx_id in writer_tx.commit_dep_set.lock().drain() {\n            if let Some(dep_tx_entry) = mvcc_store.txs.get(&dep_tx_id) {\n                dep_tx_entry\n                    .value()\n                    .commit_dep_counter\n                    .fetch_sub(1, Ordering::AcqRel);\n            }\n        }\n    }\n\n    let commit_result = reader_handle.join().unwrap();\n    assert!(\n        commit_result.is_ok(),\n        \"read-only tx with resolved dependency should commit: {commit_result:?}\",\n    );\n\n    let ts_after = mvcc_clone.last_committed_tx_ts.load(Ordering::Acquire);\n    assert_eq!(\n        ts_before, ts_after,\n        \"read-only tx should NOT advance last_committed_tx_ts (was {ts_before}, now {ts_after})\"\n    );\n}\n\n/// Test that a new transaction can still acquire the exclusive lock after a\n/// read-only dependent tx commits. Before the fix, the read-only tx would\n/// advance last_committed_tx_ts via CommitEnd, making acquire_exclusive_tx\n/// return Busy for transactions that started before the read.\n#[test]\nfn test_commit_dep_readonly_does_not_cause_spurious_busy() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE t (id INTEGER PRIMARY KEY, value TEXT)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO t VALUES (1, 'initial')\").unwrap();\n        conn.close().unwrap();\n    }\n\n    let mvcc_store = db.get_mvcc_store();\n\n    // Writer: UPDATE then set to Preparing\n    let writer_conn = db.connect();\n    writer_conn.execute(\"BEGIN CONCURRENT\").unwrap();\n    writer_conn\n        .execute(\"UPDATE t SET value = 'modified' WHERE id = 1\")\n        .unwrap();\n    let writer_tx_id = writer_conn.get_mv_tx_id().unwrap();\n\n    let end_ts = mvcc_store.get_commit_timestamp(|ts| {\n        mvcc_store\n            .txs\n            .get(&writer_tx_id)\n            .unwrap()\n            .value()\n            .state\n            .store(TransactionState::Preparing(ts));\n    });\n\n    // Start a non-CONCURRENT tx that will try to get exclusive lock later.\n    // Its begin_ts is assigned now, before the read-only tx commits.\n    let exclusive_conn = db.connect();\n    exclusive_conn.execute(\"BEGIN CONCURRENT\").unwrap();\n    let exclusive_tx_id = exclusive_conn.get_mv_tx_id().unwrap();\n\n    let (signal_tx, signal_rx) = std::sync::mpsc::channel();\n\n    let db_arc = db.get_db();\n    let reader_handle = std::thread::spawn(move || {\n        let reader_conn = db_arc.connect().unwrap();\n        reader_conn.execute(\"BEGIN CONCURRENT\").unwrap();\n\n        let mut stmt = reader_conn\n            .prepare(\"SELECT value FROM t WHERE id = 1\")\n            .unwrap();\n        let _rows = stmt.run_collect_rows().unwrap();\n\n        signal_tx.send(()).unwrap();\n\n        let commit_result = reader_conn.execute(\"COMMIT\");\n        let _ = reader_conn.close();\n        commit_result\n    });\n\n    signal_rx.recv().unwrap();\n\n    // Resolve the writer's commit (unblocks reader's WaitForDependencies)\n    {\n        let writer_tx = mvcc_store.txs.get(&writer_tx_id).unwrap();\n        let writer_tx = writer_tx.value();\n        for entry in mvcc_store.rows.iter() {\n            let mut rvs = entry.value().write();\n            for rv in rvs.iter_mut() {\n                if rv.begin == Some(TxTimestampOrID::TxID(writer_tx_id)) {\n                    rv.begin = Some(TxTimestampOrID::Timestamp(end_ts));\n                }\n                if rv.end == Some(TxTimestampOrID::TxID(writer_tx_id)) {\n                    rv.end = Some(TxTimestampOrID::Timestamp(end_ts));\n                }\n            }\n        }\n        writer_tx.state.store(TransactionState::Committed(end_ts));\n        for dep_tx_id in writer_tx.commit_dep_set.lock().drain() {\n            if let Some(dep_tx_entry) = mvcc_store.txs.get(&dep_tx_id) {\n                dep_tx_entry\n                    .value()\n                    .commit_dep_counter\n                    .fetch_sub(1, Ordering::AcqRel);\n            }\n        }\n    }\n\n    let commit_result = reader_handle.join().unwrap();\n    assert!(commit_result.is_ok());\n\n    // Now try to acquire exclusive lock for the tx that started before the\n    // read-only dependent committed. Should succeed because the read-only tx\n    // did not advance last_committed_tx_ts.\n    let acquire_result = mvcc_store.acquire_exclusive_tx(&exclusive_tx_id);\n    assert!(\n        acquire_result.is_ok(),\n        \"acquire_exclusive_tx should not return Busy after a read-only dependent committed: {acquire_result:?}\",\n    );\n    mvcc_store.release_exclusive_tx(&exclusive_tx_id);\n}\n\n/// What this test checks: Startup recovery reconciles WAL/log artifacts into one consistent MVCC state and replay boundary.\n/// Why this matters: This path runs automatically after crashes; errors here can duplicate effects or drop durable data.\n#[test]\nfn test_restart() {\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n    {\n        let conn = db.connect();\n        let mvcc_store = db.get_mvcc_store();\n        let max_root_page = get_rows(\n            &conn,\n            \"SELECT COALESCE(MAX(rootpage), 0) FROM sqlite_schema WHERE rootpage > 0\",\n        )[0][0]\n            .as_int()\n            .unwrap();\n        let next_schema_rowid = get_rows(\n            &conn,\n            \"SELECT COALESCE(MAX(rowid), 0) + 1 FROM sqlite_schema\",\n        )[0][0]\n            .as_int()\n            .unwrap();\n        let synthetic_root = -(max_root_page + 100);\n        let synthetic_table_id = MVTableId::new(synthetic_root);\n        let tx_id = mvcc_store.begin_tx(conn.pager.load().clone()).unwrap();\n        // Insert synthetic table metadata into sqlite_schema (table_id -1).\n        let data = ImmutableRecord::from_values(\n            &[\n                Value::Text(Text::new(\"table\")), // type\n                Value::Text(Text::new(\"test\")),  // name\n                Value::Text(Text::new(\"test\")),  // tbl_name\n                Value::from_i64(synthetic_root), // rootpage\n                Value::Text(Text::new(\n                    \"CREATE TABLE test(id INTEGER PRIMARY KEY, data TEXT)\",\n                )), // sql\n            ],\n            5,\n        );\n        mvcc_store\n            .insert(\n                tx_id,\n                Row::new_table_row(\n                    RowID::new((-1).into(), RowKey::Int(next_schema_rowid)),\n                    data.as_blob().to_vec(),\n                    5,\n                ),\n            )\n            .unwrap();\n        // Now insert a row into the synthetic table.\n        let row = generate_simple_string_row(synthetic_table_id, 1, \"foo\");\n        mvcc_store.insert(tx_id, row).unwrap();\n        commit_tx(mvcc_store, &conn, tx_id).unwrap();\n        conn.close().unwrap();\n    }\n    db.restart();\n\n    {\n        let conn = db.connect();\n        let mvcc_store = db.get_mvcc_store();\n        let max_root_page = get_rows(\n            &conn,\n            \"SELECT COALESCE(MAX(rootpage), 0) FROM sqlite_schema WHERE rootpage > 0\",\n        )[0][0]\n            .as_int()\n            .unwrap();\n        let synthetic_table_id = MVTableId::new(-(max_root_page + 100));\n        let tx_id = mvcc_store.begin_tx(conn.pager.load().clone()).unwrap();\n        let row = generate_simple_string_row(synthetic_table_id, 2, \"bar\");\n\n        mvcc_store.insert(tx_id, row).unwrap();\n        commit_tx(mvcc_store.clone(), &conn, tx_id).unwrap();\n\n        let tx_id = mvcc_store.begin_tx(conn.pager.load().clone()).unwrap();\n        let row = mvcc_store\n            .read(tx_id, &RowID::new(synthetic_table_id, RowKey::Int(2)))\n            .unwrap()\n            .unwrap();\n        let record = get_record_value(&row);\n        match record.get_value(0).unwrap() {\n            ValueRef::Text(text) => {\n                assert_eq!(text.as_str(), \"bar\");\n            }\n            _ => panic!(\"Expected Text value\"),\n        }\n        conn.close().unwrap();\n    }\n}\n\n/// What this test checks: The implementation maintains the intended invariant for this scenario.\n/// Why this matters: The invariant protects correctness across commit, replay, and query execution paths.\n#[test]\nfn test_connection_sees_other_connection_changes() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn0 = db.connect();\n    conn0\n        .execute(\"CREATE TABLE IF NOT EXISTS test_table (id INTEGER PRIMARY KEY, text TEXT)\")\n        .unwrap();\n    let conn1 = db.connect();\n    conn1\n        .execute(\"CREATE TABLE IF NOT EXISTS test_table (id INTEGER PRIMARY KEY, text TEXT)\")\n        .unwrap();\n    conn0\n        .execute(\"INSERT INTO test_table (id, text) VALUES (965, 'text_877')\")\n        .unwrap();\n    let mut stmt = conn1.query(\"SELECT * FROM test_table\").unwrap().unwrap();\n    stmt.run_with_row_callback(|row| {\n        let text = row.get_value(1).to_text().unwrap();\n        assert_eq!(text, \"text_877\");\n        Ok(())\n    })\n    .unwrap();\n}\n\n/// What this test checks: Core MVCC read/write semantics hold for this operation sequence.\n/// Why this matters: These are foundational invariants; regressions here invalidate higher-level SQL behavior.\n#[test]\nfn test_delete_with_conn() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn0 = db.connect();\n    conn0.execute(\"CREATE TABLE test(t)\").unwrap();\n\n    let mut inserts = vec![1, 2, 3, 4, 5, 6, 7];\n\n    for t in &inserts {\n        conn0\n            .execute(format!(\"INSERT INTO test(t) VALUES ({t})\"))\n            .unwrap();\n    }\n\n    conn0.execute(\"DELETE FROM test WHERE t = 5\").unwrap();\n    inserts.remove(4);\n\n    let mut stmt = conn0.prepare(\"SELECT * FROM test\").unwrap();\n    let mut pos = 0;\n    stmt.run_with_row_callback(|row| {\n        let t = row.get_value(0).as_int().unwrap();\n        assert_eq!(t, inserts[pos]);\n        pos += 1;\n        Ok(())\n    })\n    .unwrap();\n}\n\nfn get_record_value(row: &Row) -> ImmutableRecord {\n    let mut record = ImmutableRecord::new(1024);\n    record.start_serialization(row.payload());\n    record\n}\n\n/// What this test checks: The implementation maintains the intended invariant for this scenario.\n/// Why this matters: The invariant protects correctness across commit, replay, and query execution paths.\n#[test]\nfn test_interactive_transaction() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n\n    // do some transaction\n    conn.execute(\"BEGIN\").unwrap();\n    conn.execute(\"CREATE TABLE test (x)\").unwrap();\n    conn.execute(\"INSERT INTO test (x) VALUES (1)\").unwrap();\n    conn.execute(\"INSERT INTO test (x) VALUES (2)\").unwrap();\n    conn.execute(\"COMMIT\").unwrap();\n\n    // expect other transaction to see the changes\n    let rows = get_rows(&conn, \"SELECT * FROM test\");\n    assert_eq!(\n        rows,\n        vec![vec![Value::from_i64(1)], vec![Value::from_i64(2)]]\n    );\n}\n\n/// What this test checks: Core MVCC read/write semantics hold for this operation sequence.\n/// Why this matters: These are foundational invariants; regressions here invalidate higher-level SQL behavior.\n#[test]\nfn test_commit_without_tx() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n    // do not start interactive transaction\n    conn.execute(\"CREATE TABLE test (x)\").unwrap();\n    conn.execute(\"INSERT INTO test (x) VALUES (1)\").unwrap();\n\n    // expect error on trying to commit a non-existent interactive transaction\n    let err = conn.execute(\"COMMIT\").unwrap_err();\n    if let LimboError::TxError(e) = err {\n        assert_eq!(e, \"cannot commit - no transaction is active\");\n    } else {\n        panic!(\"Expected TxError\");\n    }\n}\n\nfn get_rows(conn: &Arc<Connection>, query: &str) -> Vec<Vec<Value>> {\n    let mut stmt = conn.prepare(query).unwrap();\n    let mut rows = Vec::new();\n    stmt.run_with_row_callback(|row| {\n        let values = row.get_values().cloned().collect::<Vec<_>>();\n        rows.push(values);\n        Ok(())\n    })\n    .unwrap();\n    rows\n}\n\n/// What this test checks: MVCC transaction visibility and conflict handling follow the intended isolation behavior.\n/// Why this matters: Concurrency bugs are correctness bugs: they create anomalies users can observe as wrong query results.\n#[test]\n#[ignore]\nfn test_concurrent_writes() {\n    struct ConnectionState {\n        conn: Arc<Connection>,\n        inserts: Vec<i64>,\n        current_statement: Option<Statement>,\n    }\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let mut connections = Vec::new();\n    {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE test (x)\").unwrap();\n        conn.close().unwrap();\n    }\n    let num_connections = 20;\n    let num_inserts_per_connection = 10000;\n    for i in 0..num_connections {\n        let conn = db.connect();\n        let mut inserts = ((num_inserts_per_connection * i)\n            ..(num_inserts_per_connection * (i + 1)))\n            .collect::<Vec<i64>>();\n        inserts.reverse();\n        connections.push(ConnectionState {\n            conn,\n            inserts,\n            current_statement: None,\n        });\n    }\n\n    loop {\n        let mut all_finished = true;\n        for conn in &mut connections {\n            if !conn.inserts.is_empty() || conn.current_statement.is_some() {\n                all_finished = false;\n                break;\n            }\n        }\n        for (conn_id, conn) in connections.iter_mut().enumerate() {\n            // println!(\"connection {conn_id} inserts: {:?}\", conn.inserts);\n            if conn.current_statement.is_none() && !conn.inserts.is_empty() {\n                let write = conn.inserts.pop().unwrap();\n                println!(\"inserting row {write} from connection {conn_id}\");\n                conn.current_statement = Some(\n                    conn.conn\n                        .prepare(format!(\"INSERT INTO test (x) VALUES ({write})\"))\n                        .unwrap(),\n                );\n            }\n            if conn.current_statement.is_none() {\n                continue;\n            }\n            println!(\"connection step {conn_id}\");\n            let stmt = conn.current_statement.as_mut().unwrap();\n            match stmt.step().unwrap() {\n                // These you be only possible cases in write concurrency.\n                // No rows because insert doesn't return\n                // No interrupt because insert doesn't interrupt\n                // No busy because insert in mvcc should be multi concurrent write\n                StepResult::Done => {\n                    println!(\"connection {conn_id} done\");\n                    conn.current_statement = None;\n                }\n                StepResult::IO => {\n                    // let's skip doing I/O here, we want to perform io only after all the statements are stepped\n                }\n                StepResult::Busy => {\n                    println!(\"connection {conn_id} busy\");\n                    // stmt.reprepare().unwrap();\n                    unreachable!();\n                }\n                _ => {\n                    unreachable!()\n                }\n            }\n        }\n        db.get_db().io.step().unwrap();\n\n        if all_finished {\n            println!(\"all finished\");\n            break;\n        }\n    }\n\n    // Now let's find out if we wrote everything we intended to write.\n    let conn = db.connect();\n    let rows = get_rows(&conn, \"SELECT * FROM test ORDER BY x ASC\");\n    assert_eq!(\n        rows.len() as i64,\n        num_connections * num_inserts_per_connection\n    );\n    for (row_id, row) in rows.iter().enumerate() {\n        assert_eq!(row[0].as_int().unwrap(), row_id as i64);\n    }\n    conn.close().unwrap();\n}\n\n/// What this test checks: The implementation maintains the intended invariant for this scenario.\n/// Why this matters: The invariant protects correctness across commit, replay, and query execution paths.\n#[test]\nfn transaction_display() {\n    let state = AtomicTransactionState::from(TransactionState::Preparing(20250915));\n    let tx_id = 42;\n    let begin_ts = 20250914;\n\n    let write_set = SkipSet::new();\n    write_set.insert(RowID::new((-2).into(), RowKey::Int(11)));\n    write_set.insert(RowID::new((-2).into(), RowKey::Int(13)));\n\n    let read_set = SkipSet::new();\n    read_set.insert(RowID::new((-2).into(), RowKey::Int(17)));\n    read_set.insert(RowID::new((-2).into(), RowKey::Int(19)));\n\n    let tx = Transaction {\n        state,\n        tx_id,\n        begin_ts,\n        write_set,\n        read_set,\n        header: RwLock::new(DatabaseHeader::default()),\n        header_dirty: AtomicBool::new(false),\n        savepoint_stack: RwLock::new(Vec::new()),\n        pager_commit_lock_held: AtomicBool::new(false),\n        commit_dep_counter: AtomicU64::new(0),\n        abort_now: AtomicBool::new(false),\n        commit_dep_set: Mutex::new(HashSet::default()),\n    };\n\n    let expected = \"{ state: Preparing(20250915), id: 42, begin_ts: 20250914, write_set: [RowID { table_id: MVTableId(-2), row_id: Int(11) }, RowID { table_id: MVTableId(-2), row_id: Int(13) }], read_set: [RowID { table_id: MVTableId(-2), row_id: Int(17) }, RowID { table_id: MVTableId(-2), row_id: Int(19) }] }\";\n    let output = format!(\"{tx}\");\n    assert_eq!(output, expected);\n}\n\n/// What this test checks: Checkpoint transitions preserve DB/WAL/log ordering and watermark updates for the tested edge case.\n/// Why this matters: Incorrect ordering breaks crash safety, replay boundaries, or durability guarantees.\n#[test]\nfn test_should_checkpoint() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let mv_store = db.get_mvcc_store();\n    assert!(!mv_store.storage.should_checkpoint());\n    mv_store.set_checkpoint_threshold(0);\n    assert!(mv_store.storage.should_checkpoint());\n}\n\n/// What this test checks: After restart recovery, checkpoint-threshold checks use the recovered log offset.\n/// Why this matters: Shadow-offset drift can suppress auto-checkpoint despite a large recovered log tail.\n#[test]\nfn test_should_checkpoint_after_recovery_uses_recovered_offset() {\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n    {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE t(x)\").unwrap();\n        conn.execute(\"INSERT INTO t VALUES (1)\").unwrap();\n    }\n\n    db.restart();\n    let _conn = db.connect();\n    let mv_store = db.get_mvcc_store();\n\n    // We used to assert on the concrete logical-log offset here, but MVCC durable storage\n    // is now abstracted behind a trait object (to allow injecting custom implementations).\n    // Validate behavior instead: after recovery, the recovered offset should be reflected\n    // in should_checkpoint() when the threshold is set very low.\n    mv_store.set_checkpoint_threshold(1);\n    assert!(\n        mv_store.storage.should_checkpoint(),\n        \"expected should_checkpoint() to reflect the recovered logical-log offset\"\n    );\n}\n\n/// What this test checks: Checkpoint transitions preserve DB/WAL/log ordering and watermark updates for the tested edge case.\n/// Why this matters: Incorrect ordering breaks crash safety, replay boundaries, or durability guarantees.\n#[test]\nfn test_insert_with_checkpoint() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let mv_store = db.get_mvcc_store();\n    // force checkpoint on every transaction\n    mv_store.set_checkpoint_threshold(0);\n    let conn = db.connect();\n    conn.execute(\"CREATE TABLE t(x)\").unwrap();\n    conn.execute(\"INSERT INTO t VALUES (1)\").unwrap();\n    let rows = get_rows(&conn, \"SELECT * FROM t\");\n    assert_eq!(rows.len(), 1);\n    let row = rows.first().unwrap();\n    assert_eq!(row.len(), 1);\n    let value = row.first().unwrap();\n    match value {\n        Value::Numeric(crate::numeric::Numeric::Integer(i)) => assert_eq!(*i, 1),\n        _ => unreachable!(),\n    }\n}\n\n/// What this test checks: Checkpoint transitions preserve DB/WAL/log ordering and watermark updates for the tested edge case.\n/// Why this matters: Incorrect ordering breaks crash safety, replay boundaries, or durability guarantees.\n#[test]\nfn test_auto_checkpoint_busy_is_ignored() {\n    let db = MvccTestDb::new();\n    db.mvcc_store.set_checkpoint_threshold(0);\n\n    // Keep a second transaction open to hold the checkpoint read lock.\n    let tx1 = db\n        .mvcc_store\n        .begin_tx(db.conn.pager.load().clone())\n        .unwrap();\n    let tx2 = db\n        .mvcc_store\n        .begin_tx(db.conn.pager.load().clone())\n        .unwrap();\n\n    let row = generate_simple_string_row((-2).into(), 1, \"Hello\");\n    db.mvcc_store.insert(tx1, row).unwrap();\n\n    // Regression: auto-checkpoint returning Busy used to bubble up and cause\n    // statement abort/rollback after the tx was removed.\n    // Commit should succeed even if the auto-checkpoint is busy.\n    commit_tx(db.mvcc_store.clone(), &db.conn, tx1).unwrap();\n\n    // Cleanup: release the read lock held by tx2.\n    db.mvcc_store.rollback_tx(\n        tx2,\n        db.conn.pager.load().clone(),\n        &db.conn,\n        crate::MAIN_DB_ID,\n    );\n}\n\n/// What this test checks: Core MVCC read/write semantics hold for this operation sequence.\n/// Why this matters: These are foundational invariants; regressions here invalidate higher-level SQL behavior.\n#[test]\nfn test_mvcc_read_tx_lifecycle() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n\n    conn.execute(\"CREATE TABLE t(x)\").unwrap();\n    conn.execute(\"BEGIN\").unwrap();\n    conn.execute(\"SELECT * FROM t\").unwrap();\n\n    let pager = conn.pager.load();\n    let wal = pager.wal.as_ref().expect(\"wal should be enabled\");\n    assert!(wal.holds_read_lock());\n\n    conn.execute(\"COMMIT\").unwrap();\n    assert!(!wal.holds_read_lock());\n}\n\n/// What this test checks: Core MVCC read/write semantics hold for this operation sequence.\n/// Why this matters: These are foundational invariants; regressions here invalidate higher-level SQL behavior.\n#[test]\nfn test_mvcc_conn_drop_releases_read_tx() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n\n    conn.execute(\"CREATE TABLE t(x)\").unwrap();\n\n    let pager = conn.pager.load();\n    pager.begin_read_tx().unwrap();\n    let wal = pager.wal.as_ref().expect(\"wal should be enabled\").clone();\n    assert!(wal.holds_read_lock());\n\n    drop(conn);\n    assert!(!wal.holds_read_lock());\n}\n\n/// What this test checks: The implementation maintains the intended invariant for this scenario.\n/// Why this matters: The invariant protects correctness across commit, replay, and query execution paths.\n#[test]\nfn test_select_empty_table() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let mv_store = db.get_mvcc_store();\n    // force checkpoint on every transaction\n    mv_store.set_checkpoint_threshold(0);\n    let conn = db.connect();\n    conn.execute(\"CREATE TABLE t(x integer primary key)\")\n        .unwrap();\n    let rows = get_rows(&conn, \"SELECT * FROM t where x > 100\");\n    assert!(rows.is_empty());\n}\n\n/// What this test checks: Cursor traversal and seek operations honor MVCC visibility and key ordering under updates/deletes.\n/// Why this matters: Read-path correctness is critical: wrong cursor semantics directly surface as wrong query answers.\n#[test]\nfn test_cursor_with_btree_and_mvcc() {\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n    // First write some rows and checkpoint so data is flushed to BTree file (.db)\n    {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE t(x integer primary key)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO t VALUES (1)\").unwrap();\n        conn.execute(\"INSERT INTO t VALUES (2)\").unwrap();\n        conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n    }\n    // Now restart so new connection will have to read data from BTree instead of MVCC.\n    db.restart();\n    let conn = db.connect();\n    println!(\"getting rows\");\n    let rows = get_rows(&conn, \"SELECT * FROM t\");\n    assert_eq!(rows.len(), 2);\n    assert_eq!(rows[0], vec![Value::from_i64(1)]);\n    assert_eq!(rows[1], vec![Value::from_i64(2)]);\n}\n\n/// What this test checks: Cursor traversal and seek operations honor MVCC visibility and key ordering under updates/deletes.\n/// Why this matters: Read-path correctness is critical: wrong cursor semantics directly surface as wrong query answers.\n#[test]\nfn test_cursor_with_btree_and_mvcc_2() {\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n    // First write some rows and checkpoint so data is flushed to BTree file (.db)\n    {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE t(x integer primary key)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO t VALUES (1)\").unwrap();\n        conn.execute(\"INSERT INTO t VALUES (3)\").unwrap();\n        conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n    }\n    // Now restart so new connection will have to read data from BTree instead of MVCC.\n    db.restart();\n    let conn = db.connect();\n    // Insert a new row so that we have a gap in the BTree.\n    conn.execute(\"INSERT INTO t VALUES (2)\").unwrap();\n    println!(\"getting rows\");\n    let rows = get_rows(&conn, \"SELECT * FROM t\");\n    dbg!(&rows);\n    assert_eq!(rows.len(), 3);\n    assert_eq!(rows[0], vec![Value::from_i64(1)]);\n    assert_eq!(rows[1], vec![Value::from_i64(2)]);\n    assert_eq!(rows[2], vec![Value::from_i64(3)]);\n}\n\n/// What this test checks: Cursor traversal and seek operations honor MVCC visibility and key ordering under updates/deletes.\n/// Why this matters: Read-path correctness is critical: wrong cursor semantics directly surface as wrong query answers.\n#[test]\nfn test_cursor_with_btree_and_mvcc_with_backward_cursor() {\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n    // First write some rows and checkpoint so data is flushed to BTree file (.db)\n    {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE t(x integer primary key)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO t VALUES (1)\").unwrap();\n        conn.execute(\"INSERT INTO t VALUES (3)\").unwrap();\n        conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n    }\n    // Now restart so new connection will have to read data from BTree instead of MVCC.\n    db.restart();\n    let conn = db.connect();\n    // Insert a new row so that we have a gap in the BTree.\n    conn.execute(\"INSERT INTO t VALUES (2)\").unwrap();\n    let rows = get_rows(&conn, \"SELECT * FROM t order by x desc\");\n    dbg!(&rows);\n    assert_eq!(rows.len(), 3);\n    assert_eq!(rows[0], vec![Value::from_i64(3)]);\n    assert_eq!(rows[1], vec![Value::from_i64(2)]);\n    assert_eq!(rows[2], vec![Value::from_i64(1)]);\n}\n\n/// What this test checks: Cursor traversal and seek operations honor MVCC visibility and key ordering under updates/deletes.\n/// Why this matters: Read-path correctness is critical: wrong cursor semantics directly surface as wrong query answers.\n#[test]\nfn test_cursor_with_btree_and_mvcc_with_backward_cursor_with_delete() {\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n    // First write some rows and checkpoint so data is flushed to BTree file (.db)\n    {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE t(x integer primary key)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO t VALUES (1)\").unwrap();\n        conn.execute(\"INSERT INTO t VALUES (2)\").unwrap();\n        conn.execute(\"INSERT INTO t VALUES (4)\").unwrap();\n        conn.execute(\"INSERT INTO t VALUES (5)\").unwrap();\n        conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n    }\n    // Now restart so new connection will have to read data from BTree instead of MVCC.\n    db.restart();\n    let conn = db.connect();\n    // Insert a new row so that we have a gap in the BTree.\n    conn.execute(\"INSERT INTO t VALUES (3)\").unwrap();\n    conn.execute(\"DELETE FROM t WHERE x = 2\").unwrap();\n    println!(\"getting rows\");\n    let rows = get_rows(&conn, \"SELECT * FROM t order by x desc\");\n    dbg!(&rows);\n    assert_eq!(rows.len(), 4);\n    assert_eq!(rows[0], vec![Value::from_i64(5)]);\n    assert_eq!(rows[1], vec![Value::from_i64(4)]);\n    assert_eq!(rows[2], vec![Value::from_i64(3)]);\n    assert_eq!(rows[3], vec![Value::from_i64(1)]);\n}\n\n/// What this test checks: Cursor traversal and seek operations honor MVCC visibility and key ordering under updates/deletes.\n/// Why this matters: Read-path correctness is critical: wrong cursor semantics directly surface as wrong query answers.\n#[test]\n#[ignore] // FIXME: This fails constantly on main and is really annoying, disabling for now :]\nfn test_cursor_with_btree_and_mvcc_fuzz() {\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n    let mut rows_in_db = sorted_vec::SortedVec::new();\n    let mut seen = HashSet::default();\n    let (mut rng, _seed) = rng_from_time_or_env();\n    println!(\"seed: {_seed}\");\n\n    let mut maybe_conn = Some(db.connect());\n    {\n        maybe_conn\n            .as_mut()\n            .unwrap()\n            .execute(\"CREATE TABLE t(x integer primary key)\")\n            .unwrap();\n    }\n\n    #[repr(u8)]\n    #[derive(Debug)]\n    enum Op {\n        Insert = 0,\n        Delete = 1,\n        SelectForward = 2,\n        SelectBackward = 3,\n        SeekForward = 4,\n        SeekBackward = 5,\n        Checkpoint = 6,\n    }\n\n    impl From<u8> for Op {\n        fn from(value: u8) -> Self {\n            match value {\n                0 => Op::Insert,\n                1 => Op::Delete,\n                2 => Op::SelectForward,\n                3 => Op::SelectBackward,\n                4 => Op::SeekForward,\n                5 => Op::SeekBackward,\n                6 => Op::Checkpoint,\n                _ => unreachable!(),\n            }\n        }\n    }\n\n    for i in 0..10000 {\n        let conn = maybe_conn.as_mut().unwrap();\n        let op = rng.random_range(0..=Op::Checkpoint as usize);\n        let op = Op::from(op as u8);\n        println!(\"tick: {i} op: {op:?} \");\n        match op {\n            Op::Insert => {\n                let value = loop {\n                    let value = rng.random_range(0..10000);\n                    if !seen.contains(&value) {\n                        seen.insert(value);\n                        break value;\n                    }\n                };\n                let query = format!(\"INSERT INTO t VALUES ({value})\");\n                println!(\"inserting: {query}\");\n                conn.execute(query.as_str()).unwrap();\n                rows_in_db.push(value);\n            }\n            Op::Delete => {\n                if rows_in_db.is_empty() {\n                    continue;\n                }\n                let index = rng.random_range(0..rows_in_db.len());\n                let value = rows_in_db[index];\n                let query = format!(\"DELETE FROM t WHERE x = {value}\");\n                println!(\"deleting: {query}\");\n                conn.execute(query.as_str()).unwrap();\n                rows_in_db.remove_index(index);\n                seen.remove(&value);\n            }\n            Op::SelectForward => {\n                let rows = get_rows(conn, \"SELECT * FROM t order by x asc\");\n                assert_eq!(\n                    rows.len(),\n                    rows_in_db.len(),\n                    \"expected {} rows, got {}\",\n                    rows_in_db.len(),\n                    rows.len()\n                );\n                for (row, expected_rowid) in rows.iter().zip(rows_in_db.iter()) {\n                    assert_eq!(\n                        row[0].as_int().unwrap(),\n                        *expected_rowid,\n                        \"expected row id {}  got {}\",\n                        *expected_rowid,\n                        row[0].as_int().unwrap()\n                    );\n                }\n            }\n            Op::SelectBackward => {\n                let rows = get_rows(conn, \"SELECT * FROM t order by x desc\");\n                assert_eq!(\n                    rows.len(),\n                    rows_in_db.len(),\n                    \"expected {} rows, got {}\",\n                    rows_in_db.len(),\n                    rows.len()\n                );\n                for (row, expected_rowid) in rows.iter().zip(rows_in_db.iter().rev()) {\n                    assert_eq!(\n                        row[0].as_int().unwrap(),\n                        *expected_rowid,\n                        \"expected row id {}  got {}\",\n                        *expected_rowid,\n                        row[0].as_int().unwrap()\n                    );\n                }\n            }\n            Op::SeekForward => {\n                let value = rng.random_range(0..10000);\n                let rows = get_rows(\n                    conn,\n                    format!(\"SELECT * FROM t where x > {value} order by x asc\").as_str(),\n                );\n                let filtered_rows_in_db = rows_in_db\n                    .iter()\n                    .filter(|&id| *id > value)\n                    .cloned()\n                    .collect::<Vec<i64>>();\n\n                assert_eq!(\n                    rows.len(),\n                    filtered_rows_in_db.len(),\n                    \"expected {} rows, got {}\",\n                    filtered_rows_in_db.len(),\n                    rows.len()\n                );\n                for (row, expected_rowid) in rows.iter().zip(filtered_rows_in_db.iter()) {\n                    assert_eq!(\n                        row[0].as_int().unwrap(),\n                        *expected_rowid,\n                        \"expected row id {}  got {}\",\n                        *expected_rowid,\n                        row[0].as_int().unwrap()\n                    );\n                }\n            }\n            Op::SeekBackward => {\n                let value = rng.random_range(0..10000);\n                let rows = get_rows(\n                    conn,\n                    format!(\"SELECT * FROM t where x > {value} order by x desc\").as_str(),\n                );\n                let filtered_rows_in_db = rows_in_db\n                    .iter()\n                    .filter(|&id| *id > value)\n                    .cloned()\n                    .collect::<Vec<i64>>();\n\n                assert_eq!(\n                    rows.len(),\n                    filtered_rows_in_db.len(),\n                    \"expected {} rows, got {}\",\n                    filtered_rows_in_db.len(),\n                    rows.len()\n                );\n                for (row, expected_rowid) in rows.iter().zip(filtered_rows_in_db.iter().rev()) {\n                    assert_eq!(\n                        row[0].as_int().unwrap(),\n                        *expected_rowid,\n                        \"expected row id {}  got {}\",\n                        *expected_rowid,\n                        row[0].as_int().unwrap()\n                    );\n                }\n            }\n            Op::Checkpoint => {\n                // This forces things to move to the BTree file (.db)\n                conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n                // This forces MVCC to be cleared\n                db.restart();\n                maybe_conn = Some(db.connect());\n            }\n        }\n    }\n}\n\npub fn rng_from_time_or_env() -> (ChaCha8Rng, u64) {\n    let seed = std::env::var(\"SEED\").map_or(\n        std::time::SystemTime::now()\n            .duration_since(std::time::UNIX_EPOCH)\n            .unwrap()\n            .as_millis(),\n        |v| {\n            v.parse()\n                .expect(\"Failed to parse SEED environment variable as u64\")\n        },\n    );\n    let rng = ChaCha8Rng::seed_from_u64(seed as u64);\n    (rng, seed as u64)\n}\n\n/// What this test checks: Checkpoint transitions preserve DB/WAL/log ordering and watermark updates for the tested edge case.\n/// Why this matters: Incorrect ordering breaks crash safety, replay boundaries, or durability guarantees.\n#[test]\nfn test_cursor_with_btree_and_mvcc_insert_after_checkpoint_repeated_key() {\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n    // First write some rows and checkpoint so data is flushed to BTree file (.db)\n    {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE t(x integer primary key)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO t VALUES (1)\").unwrap();\n        conn.execute(\"INSERT INTO t VALUES (2)\").unwrap();\n        conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n    }\n    // Now restart so new connection will have to read data from BTree instead of MVCC.\n    db.restart();\n    let conn = db.connect();\n    // Insert a new row so that we have a gap in the BTree.\n    let res = conn.execute(\"INSERT INTO t VALUES (2)\");\n    assert!(res.is_err(), \"Expected error because key 2 already exists\");\n}\n\n/// What this test checks: Checkpoint transitions preserve DB/WAL/log ordering and watermark updates for the tested edge case.\n/// Why this matters: Incorrect ordering breaks crash safety, replay boundaries, or durability guarantees.\n#[test]\nfn test_cursor_with_btree_and_mvcc_seek_after_checkpoint() {\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n    // First write some rows and checkpoint so data is flushed to BTree file (.db)\n    {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE t(x integer primary key)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO t VALUES (1)\").unwrap();\n        conn.execute(\"INSERT INTO t VALUES (2)\").unwrap();\n        conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n    }\n    // Now restart so new connection will have to read data from BTree instead of MVCC.\n    db.restart();\n    let conn = db.connect();\n    // Seek to the second row.\n    let res = get_rows(&conn, \"SELECT * FROM t WHERE x = 2\");\n    assert_eq!(res.len(), 1);\n    assert_eq!(res[0][0].as_int().unwrap(), 2);\n}\n\n/// What this test checks: Checkpoint transitions preserve DB/WAL/log ordering and watermark updates for the tested edge case.\n/// Why this matters: Incorrect ordering breaks crash safety, replay boundaries, or durability guarantees.\n#[test]\nfn test_cursor_with_btree_and_mvcc_delete_after_checkpoint() {\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n    // First write some rows and checkpoint so data is flushed to BTree file (.db)\n    {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE t(x integer primary key)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO t VALUES (1)\").unwrap();\n        conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n    }\n    // Now restart so new connection will have to read data from BTree instead of MVCC.\n    db.restart();\n    let conn = db.connect();\n    conn.execute(\"DELETE FROM t WHERE x = 1\").unwrap();\n    let rows = get_rows(&conn, \"SELECT * FROM t order by x desc\");\n    assert_eq!(rows.len(), 0);\n}\n\n/// Core MVCC read/write semantics for AUTOINCREMENT with rowid update.\n#[test]\n#[ignore = \"AUTOINCREMENT not yet supported in MVCC mode\"]\nfn test_skips_updated_rowid() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n\n    conn.execute(\"CREATE TABLE t(a INTEGER PRIMARY KEY AUTOINCREMENT)\")\n        .unwrap();\n\n    // we insert with default values\n    conn.execute(\"INSERT INTO t DEFAULT VALUES\").unwrap();\n    let rows = get_rows(&conn, \"SELECT * FROM sqlite_sequence\");\n    dbg!(&rows);\n    assert_eq!(rows.len(), 1);\n    assert_eq!(rows[0][1].as_int().unwrap(), 1);\n\n    // we update the rowid to +1\n    conn.execute(\"UPDATE t SET a = a + 1\").unwrap();\n    let rows = get_rows(&conn, \"SELECT * FROM sqlite_sequence\");\n    dbg!(&rows);\n    assert_eq!(rows.len(), 1);\n    assert_eq!(rows[0][1].as_int().unwrap(), 1);\n\n    // we insert with default values again\n    conn.execute(\"INSERT INTO t DEFAULT VALUES\").unwrap();\n    let rows = get_rows(&conn, \"SELECT * FROM sqlite_sequence\");\n    dbg!(&rows);\n    assert_eq!(rows.len(), 1);\n    assert_eq!(rows[0][1].as_int().unwrap(), 3);\n}\n\n/// What this test checks: The implementation maintains the intended invariant for this scenario.\n/// Why this matters: The invariant protects correctness across commit, replay, and query execution paths.\n#[test]\nfn test_mvcc_integrity_check() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n\n    conn.execute(\"CREATE TABLE t(a INTEGER PRIMARY KEY)\")\n        .unwrap();\n\n    // we insert with default values\n    conn.execute(\"INSERT INTO t values(1)\").unwrap();\n\n    let ensure_integrity = || {\n        let rows = get_rows(&conn, \"PRAGMA integrity_check\");\n        assert_eq!(rows.len(), 1);\n        assert_eq!(&rows[0][0].cast_text().unwrap(), \"ok\");\n    };\n\n    ensure_integrity();\n\n    conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n\n    ensure_integrity();\n}\n\n/// Test that integrity_check passes after DROP TABLE but before checkpoint.\n/// Issue #4975: After checkpointing a table and then dropping it, integrity_check\n/// would fail because the dropped table's btree pages still exist but aren't\n/// tracked by the schema. The fix is to track dropped root pages until checkpoint.\n#[test]\nfn test_integrity_check_after_drop_table_before_checkpoint() {\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n\n    conn.execute(\"CREATE TABLE t (id INTEGER PRIMARY KEY, data TEXT)\")\n        .unwrap();\n    conn.execute(\"CREATE INDEX idx_t_data ON t(data)\").unwrap();\n\n    // Insert data to force page allocation\n    for i in 0..10 {\n        let data = format!(\"data_{i}\");\n        conn.execute(format!(\"INSERT INTO t VALUES ({i}, '{data}')\"))\n            .unwrap();\n    }\n    conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n    drop(conn);\n\n    db.restart();\n\n    let conn = db.connect();\n\n    // Now drop table. Before the fix, this would make integrity_check fail because\n    // we dropped the table before checkpointing, meaning integrity_check would find\n    // pages not being used since we didn't provide root page of table t for checks.\n    conn.execute(\"DROP TABLE t\").unwrap();\n    let rows = get_rows(&conn, \"PRAGMA integrity_check\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(&rows[0][0].to_string(), \"ok\");\n}\n\n/// Test that integrity_check passes after DROP INDEX but before checkpoint.\n/// Issue #4975: After checkpointing an index and then dropping it, integrity_check\n/// would fail because the dropped index's btree pages still exist but aren't\n/// tracked by the schema. The fix is to track dropped root pages until checkpoint.\n#[test]\nfn test_integrity_check_after_drop_index_before_checkpoint() {\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n\n    conn.execute(\"CREATE TABLE t (id INTEGER PRIMARY KEY, data TEXT)\")\n        .unwrap();\n    conn.execute(\"CREATE INDEX idx_t_data ON t(data)\").unwrap();\n\n    // Insert data to force page allocation\n    for i in 0..10 {\n        let data = format!(\"data_{i}\");\n        conn.execute(format!(\"INSERT INTO t VALUES ({i}, '{data}')\"))\n            .unwrap();\n    }\n    conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n    drop(conn);\n\n    db.restart();\n\n    let conn = db.connect();\n\n    // Now drop index. Before the fix, this would make integrity_check fail because\n    // we dropped the index before checkpointing, meaning integrity_check would find\n    // pages not being used since we didn't provide root page of index idx_t_data for checks.\n    conn.execute(\"DROP INDEX idx_t_data\").unwrap();\n    let rows = get_rows(&conn, \"PRAGMA integrity_check\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(&rows[0][0].to_string(), \"ok\");\n}\n\n/// What this test checks: Rollback/savepoint behavior restores exactly the intended state when statements or transactions fail.\n/// Why this matters: Partial rollback mistakes leave data in impossible intermediate states.\n#[test]\nfn test_rollback_with_index() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n\n    conn.execute(\"CREATE TABLE t(a INTEGER PRIMARY KEY, b INTEGER UNIQUE)\")\n        .unwrap();\n\n    // we insert with default values\n    conn.execute(\"BEGIN CONCURRENT\").unwrap();\n    conn.execute(\"INSERT INTO t values (1, 1)\").unwrap();\n    conn.execute(\"ROLLBACK\").unwrap();\n\n    // This query will try to use index to find the row, if we rollback correctly it shouldn't panic\n    let rows = get_rows(&conn, \"SELECT * FROM t where b = 1\");\n    assert_eq!(rows.len(), 0);\n\n    let rows = get_rows(&conn, \"PRAGMA integrity_check\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(&rows[0][0].to_string(), \"ok\");\n}\n/// 1. BEGIN CONCURRENT (start interactive transaction)\n/// 2. UPDATE modifies col_a's index, then fails constraint check on col_b\n/// 3. The partial index changes are NOT rolled back (this is the bug!)\n/// 4. COMMIT succeeds, persisting the inconsistent state\n/// 5. Later UPDATE on same row fails: \"IdxDelete: no matching index entry found\"\n///    because table row has old value but index has new value\n#[test]\nfn test_update_multiple_unique_columns_partial_rollback() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n\n    // Create table with multiple unique columns (like blue_sun_77 in the bug)\n    conn.execute(\n        \"CREATE TABLE t(\n            id INTEGER PRIMARY KEY,\n            col_a TEXT UNIQUE,\n            col_b REAL UNIQUE\n        )\",\n    )\n    .unwrap();\n\n    // Insert two rows - one to update, one to cause conflict\n    conn.execute(\"INSERT INTO t VALUES (1, 'original_a', 1.0)\")\n        .unwrap();\n    conn.execute(\"INSERT INTO t VALUES (2, 'other_a', 2.0)\")\n        .unwrap();\n\n    // Start an INTERACTIVE transaction - this is KEY to reproducing the bug!\n    // In auto-commit mode, the entire transaction is rolled back on error.\n    // In interactive mode, only the statement should be rolled back.\n    conn.execute(\"BEGIN CONCURRENT\").unwrap();\n\n    // Try to UPDATE row 1 with:\n    // - col_a = 'new_a' (index modification happens first)\n    // - col_b = 2.0 (should FAIL - conflicts with row 2)\n    //\n    // The UPDATE bytecode does:\n    // 1. Delete old index entry for col_a ('original_a', 1)\n    // 2. Insert new index entry for col_a ('new_a', 1)\n    // 3. Delete old index entry for col_b (1.0, 1)\n    // 4. Check constraint for col_b (2.0) - FAIL with Halt err_code=1555!\n    //\n    // BUG: Without proper statement rollback, steps 1-3 are committed!\n    let result = conn.execute(\"UPDATE t SET col_a = 'new_a', col_b = 2.0 WHERE id = 1\");\n    assert!(\n        result.is_err(),\n        \"Expected unique constraint violation on col_b\"\n    );\n\n    // COMMIT the transaction - this is what the stress test does after the error!\n    // In the buggy case, this commits the partial index changes from the failed UPDATE.\n    conn.execute(\"COMMIT\").unwrap();\n\n    // Now in a NEW transaction, try to UPDATE the same row.\n    // If the previous statement's partial changes were committed:\n    // - Table row still has col_a = 'original_a' (UPDATE didn't complete)\n    // - But index for col_a now has 'new_a' instead of 'original_a'!\n    // - This UPDATE reads 'original_a' from table, tries to delete that index entry\n    // - CRASH: \"IdxDelete: no matching index entry found for key ['original_a', 1]\"\n    conn.execute(\"UPDATE t SET col_a = 'updated_a', col_b = 3.0 WHERE id = 1\")\n        .unwrap();\n\n    // Verify the update worked\n    let rows = get_rows(&conn, \"SELECT * FROM t WHERE id = 1\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(rows[0][1].cast_text().unwrap(), \"updated_a\");\n\n    // Integrity check\n    let rows = get_rows(&conn, \"PRAGMA integrity_check\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(&rows[0][0].to_string(), \"ok\");\n}\n\n// ─── GC helpers ───────────────────────────────────────────────────────────\n\nfn make_rv(begin: Option<TxTimestampOrID>, end: Option<TxTimestampOrID>) -> RowVersion {\n    RowVersion {\n        id: 0,\n        begin,\n        end,\n        row: generate_simple_string_row((-2).into(), 1, \"gc_test\"),\n        btree_resident: false,\n    }\n}\n\nfn ts(v: u64) -> Option<TxTimestampOrID> {\n    Some(TxTimestampOrID::Timestamp(v))\n}\n\nfn txid(v: u64) -> Option<TxTimestampOrID> {\n    Some(TxTimestampOrID::TxID(v))\n}\n\n// ─── GC unit tests ───────────────────────────────────────────────────────\n\n#[test]\n/// Rolled-back transactions leave versions with begin=None, end=None. These are\n/// invisible to every transaction and must be removed unconditionally by Rule 1.\nfn test_gc_rule1_aborted_garbage_removed() {\n    let mut versions = vec![make_rv(None, None)];\n    let dropped = MvStore::<MvccClock>::gc_version_chain(&mut versions, u64::MAX, 0);\n    assert_eq!(dropped, 1);\n    assert!(versions.is_empty());\n}\n\n/// What this test checks: Garbage collection removes only versions that are provably unreachable and keeps versions still required for visibility and safety.\n/// Why this matters: GC mistakes can either lose data (over-collection) or retain stale history forever (under-collection).\n#[test]\n/// Rule 1 removes only aborted garbage, leaving live and superseded versions intact.\nfn test_gc_rule1_aborted_among_live_versions() {\n    let mut versions = vec![\n        make_rv(ts(5), None),  // current\n        make_rv(None, None),   // aborted\n        make_rv(ts(3), ts(5)), // superseded\n    ];\n    let dropped = MvStore::<MvccClock>::gc_version_chain(&mut versions, 2, 0);\n    // Only aborted removed; superseded has e=5 > lwm=2 so retained\n    assert_eq!(dropped, 1);\n    assert_eq!(versions.len(), 2);\n    assert!(versions\n        .iter()\n        .all(|rv| rv.begin.is_some() || rv.end.is_some()));\n}\n\n/// What this test checks: Garbage collection removes only versions that are provably unreachable and keeps versions still required for visibility and safety.\n/// Why this matters: GC mistakes can either lose data (over-collection) or retain stale history forever (under-collection).\n#[test]\n/// A superseded version whose end timestamp is at or below the low-water mark is\n/// invisible to all active readers. When a committed current version exists to\n/// take over B-tree invalidation, the superseded version is safely removable.\nfn test_gc_rule2_superseded_below_lwm_with_current() {\n    // Superseded version (end=Timestamp(3)) below LWM=10, and there's a current version.\n    let mut versions = vec![\n        make_rv(ts(3), ts(5)), // superseded, e=5 <= lwm=10\n        make_rv(ts(5), None),  // current\n    ];\n    let dropped = MvStore::<MvccClock>::gc_version_chain(&mut versions, 10, 0);\n    assert_eq!(dropped, 1);\n    assert_eq!(versions.len(), 1);\n    assert!(versions[0].end.is_none()); // only current remains\n}\n\n/// What this test checks: Garbage collection removes only versions that are provably unreachable and keeps versions still required for visibility and safety.\n/// Why this matters: GC mistakes can either lose data (over-collection) or retain stale history forever (under-collection).\n#[test]\n/// A superseded version whose end timestamp exceeds the LWM may still be visible\n/// to an active reader. It must be retained regardless of other conditions.\nfn test_gc_rule2_superseded_above_lwm_retained() {\n    // Superseded version (end=Timestamp(15)) above LWM=10 — must be retained.\n    let mut versions = vec![make_rv(ts(3), ts(15)), make_rv(ts(15), None)];\n    let dropped = MvStore::<MvccClock>::gc_version_chain(&mut versions, 10, 0);\n    assert_eq!(dropped, 0);\n    assert_eq!(versions.len(), 2);\n}\n\n/// What this test checks: Garbage collection removes only versions that are provably unreachable and keeps versions still required for visibility and safety.\n/// Why this matters: GC mistakes can either lose data (over-collection) or retain stale history forever (under-collection).\n#[test]\n/// When a row was deleted but the deletion hasn't been checkpointed to the B-tree\n/// yet (e > ckpt_max), the tombstone is the only thing hiding the stale B-tree\n/// row. Removing it would resurrect a deleted row. Must be retained.\nfn test_gc_rule2_tombstone_guard_uncheckpointed() {\n    // Tombstone: end is set, no current version, and e > ckpt_max.\n    // Must be retained to prevent row resurrection via dual cursor.\n    let mut versions = vec![\n        make_rv(ts(3), ts(5)), // tombstone (sole version, no current)\n    ];\n    let dropped = MvStore::<MvccClock>::gc_version_chain(&mut versions, 10, 2);\n    // e=5 > ckpt_max=2, no current → tombstone guard retains it\n    assert_eq!(dropped, 0);\n    assert_eq!(versions.len(), 1);\n}\n\n/// What this test checks: Garbage collection removes only versions that are provably unreachable and keeps versions still required for visibility and safety.\n/// Why this matters: GC mistakes can either lose data (over-collection) or retain stale history forever (under-collection).\n#[test]\n/// Once the deletion has been checkpointed (e <= ckpt_max), the B-tree no longer\n/// contains the row, so the tombstone is safe to remove.\nfn test_gc_rule2_tombstone_guard_checkpointed() {\n    // Tombstone with e <= ckpt_max — deletion is checkpointed, safe to remove.\n    let mut versions = vec![make_rv(ts(3), ts(5))];\n    let dropped = MvStore::<MvccClock>::gc_version_chain(&mut versions, 10, 5);\n    // e=5 <= ckpt_max=5, e=5 <= lwm=10 → removable\n    assert_eq!(dropped, 1);\n    assert!(versions.is_empty());\n}\n\n/// What this test checks: Garbage collection removes only versions that are provably unreachable and keeps versions still required for visibility and safety.\n/// Why this matters: GC mistakes can either lose data (over-collection) or retain stale history forever (under-collection).\n#[test]\n/// A current version that's been checkpointed to B-tree, with no other versions in\n/// the chain and no active reader needing it, is redundant. The dual cursor will\n/// fall through to the B-tree which has identical data. Safe to remove.\nfn test_gc_rule3_checkpointed_sole_survivor_removed() {\n    // Single current version with b <= ckpt_max and b < lwm.\n    let mut versions = vec![make_rv(ts(5), None)];\n    let dropped = MvStore::<MvccClock>::gc_version_chain(&mut versions, 10, 5);\n    assert_eq!(dropped, 1);\n    assert!(versions.is_empty());\n}\n\n/// What this test checks: Garbage collection removes only versions that are provably unreachable and keeps versions still required for visibility and safety.\n/// Why this matters: GC mistakes can either lose data (over-collection) or retain stale history forever (under-collection).\n#[test]\n/// A current version not yet checkpointed (b > ckpt_max) cannot be removed —\n/// the B-tree doesn't have the data, so fallthrough would return stale results.\nfn test_gc_rule3_not_checkpointed_retained() {\n    // Single current version with b > ckpt_max — B-tree doesn't have it yet.\n    let mut versions = vec![make_rv(ts(5), None)];\n    let dropped = MvStore::<MvccClock>::gc_version_chain(&mut versions, 10, 3);\n    assert_eq!(dropped, 0);\n    assert_eq!(versions.len(), 1);\n}\n\n/// What this test checks: Garbage collection removes only versions that are provably unreachable and keeps versions still required for visibility and safety.\n/// Why this matters: GC mistakes can either lose data (over-collection) or retain stale history forever (under-collection).\n#[test]\n/// A current version whose begin timestamp equals the LWM might still be needed\n/// by the oldest active reader. Rule 3 requires strict b < lwm, so it's retained.\nfn test_gc_rule3_visible_to_active_tx_retained() {\n    // Single current version with b >= lwm — some active tx might need it.\n    let mut versions = vec![make_rv(ts(5), None)];\n    let dropped = MvStore::<MvccClock>::gc_version_chain(&mut versions, 5, 10);\n    // b=5 is NOT < lwm=5 (strict <), so retained\n    assert_eq!(dropped, 0);\n    assert_eq!(versions.len(), 1);\n}\n\n/// What this test checks: Garbage collection removes only versions that are provably unreachable and keeps versions still required for visibility and safety.\n/// Why this matters: GC mistakes can either lose data (over-collection) or retain stale history forever (under-collection).\n#[test]\n/// A current version cannot be removed before checkpoint has persisted it.\nfn test_gc_rule3_current_retained_before_first_checkpoint() {\n    let mut versions = vec![make_rv(ts(1), None)];\n    let dropped = MvStore::<MvccClock>::gc_version_chain(&mut versions, 10, 0);\n    assert_eq!(dropped, 0);\n    assert_eq!(versions.len(), 1);\n}\n\n/// What this test checks: Garbage collection removes only versions that are provably unreachable and keeps versions still required for visibility and safety.\n/// Why this matters: GC mistakes can either lose data (over-collection) or retain stale history forever (under-collection).\n#[test]\n/// Once checkpoint has persisted a sole current version, it becomes GC-eligible.\nfn test_gc_rule3_current_collected_after_checkpoint() {\n    let mut versions = vec![make_rv(ts(1), None)];\n    let dropped = MvStore::<MvccClock>::gc_version_chain(&mut versions, 10, 5);\n    assert_eq!(dropped, 1);\n    assert_eq!(versions.len(), 0);\n}\n\n/// What this test checks: Garbage collection removes only versions that are provably unreachable and keeps versions still required for visibility and safety.\n/// Why this matters: GC mistakes can either lose data (over-collection) or retain stale history forever (under-collection).\n#[test]\n/// Rule 3 requires the current version to be the sole remaining version in the\n/// chain. When a superseded version is removed first by Rule 2, Rule 3 can then\n/// fire on the remaining sole survivor — both rules compose correctly.\nfn test_gc_rule3_not_sole_survivor() {\n    // Rule 3 only fires when exactly one version remains after rules 1 & 2.\n    let mut versions = vec![make_rv(ts(3), ts(5)), make_rv(ts(5), None)];\n    // Both b <= ckpt_max and b < lwm, but there are 2 versions.\n    // Rule 2 removes the superseded one (has_current=true), then rule 3 fires\n    // on the remaining sole survivor.\n    let dropped = MvStore::<MvccClock>::gc_version_chain(&mut versions, 10, 5);\n    assert_eq!(dropped, 2);\n    assert!(versions.is_empty());\n}\n\n/// What this test checks: Garbage collection removes only versions that are provably unreachable and keeps versions still required for visibility and safety.\n/// Why this matters: GC mistakes can either lose data (over-collection) or retain stale history forever (under-collection).\n#[test]\n/// Versions referencing an active transaction (begin=TxID) represent uncommitted\n/// inserts. They don't match any removal rule and must always be retained.\nfn test_gc_txid_refs_retained() {\n    // Versions with TxID (uncommitted) references are never collected.\n    let mut versions = vec![make_rv(txid(99), None)];\n    let dropped = MvStore::<MvccClock>::gc_version_chain(&mut versions, u64::MAX, u64::MAX);\n    assert_eq!(dropped, 0);\n    assert_eq!(versions.len(), 1);\n}\n\n/// What this test checks: Garbage collection removes only versions that are provably unreachable and keeps versions still required for visibility and safety.\n/// Why this matters: GC mistakes can either lose data (over-collection) or retain stale history forever (under-collection).\n#[test]\n/// Versions with end=TxID represent an uncommitted deletion. Rule 2 only matches\n/// end=Timestamp, so these are never collected until the deleting tx resolves.\nfn test_gc_txid_end_retained() {\n    // end=TxID means the deletion is uncommitted; rule 2 only matches Timestamp.\n    let mut versions = vec![make_rv(ts(3), txid(50))];\n    let dropped = MvStore::<MvccClock>::gc_version_chain(&mut versions, u64::MAX, u64::MAX);\n    assert_eq!(dropped, 0);\n    assert_eq!(versions.len(), 1);\n}\n\n/// What this test checks: Garbage collection removes only versions that are provably unreachable and keeps versions still required for visibility and safety.\n/// Why this matters: GC mistakes can either lose data (over-collection) or retain stale history forever (under-collection).\n#[test]\n/// A pending insert (begin=TxID) must NOT count as a \"committed current version\"\n/// for the tombstone guard. If it rolled back, the tombstone would be the only\n/// thing hiding the stale B-tree row, and removing it would resurrect deleted data.\nfn test_gc_rule2_pending_insert_does_not_disable_tombstone_guard() {\n    // A pending insert (begin=TxID, end=None) coexists with a tombstone.\n    // has_current must NOT count the pending insert — if it rolls back,\n    // the tombstone is the only thing hiding the B-tree row.\n    let mut versions = vec![\n        make_rv(ts(3), ts(5)), // tombstone: deletion at e=5, not checkpointed (ckpt_max=2)\n        make_rv(txid(99), None), // pending insert (uncommitted)\n    ];\n    let dropped = MvStore::<MvccClock>::gc_version_chain(&mut versions, 10, 2);\n    // Tombstone must be retained: e=5 > ckpt_max=2, and pending insert doesn't count.\n    // Only nothing changes (pending insert is not aborted garbage either).\n    assert_eq!(dropped, 0);\n    assert_eq!(versions.len(), 2);\n}\n\n/// What this test checks: Garbage collection removes only versions that are provably unreachable and keeps versions still required for visibility and safety.\n/// Why this matters: GC mistakes can either lose data (over-collection) or retain stale history forever (under-collection).\n#[test]\n/// When a committed current version exists (begin=Timestamp, end=None), it takes\n/// over B-tree invalidation from the superseded version. The tombstone guard is\n/// no longer needed, so the superseded version can be safely removed.\nfn test_gc_rule2_committed_current_disables_tombstone_guard() {\n    // A committed current version (begin=Timestamp, end=None) means the row\n    // has a live successor — the tombstone can safely be removed.\n    let mut versions = vec![\n        make_rv(ts(3), ts(5)), // superseded, e=5 <= lwm=10\n        make_rv(ts(5), None),  // committed current\n    ];\n    let dropped = MvStore::<MvccClock>::gc_version_chain(&mut versions, 10, 2);\n    // Superseded removed (has_current=true for committed version), current remains.\n    assert_eq!(dropped, 1);\n    assert_eq!(versions.len(), 1);\n    assert!(versions[0].end.is_none());\n}\n\n/// What this test checks: Garbage collection removes only versions that are provably unreachable and keeps versions still required for visibility and safety.\n/// Why this matters: GC mistakes can either lose data (over-collection) or retain stale history forever (under-collection).\n#[test]\n/// B-tree tombstones (begin=None, end=e) represent rows that existed in the B-tree\n/// before MVCC was enabled and were then deleted. Before checkpoint writes the\n/// deletion, the tombstone hides the stale B-tree row. After checkpoint, it's safe\n/// to remove. Tests the full lifecycle: retained → checkpointed → collected.\nfn test_gc_rule2_btree_tombstone_lifecycle() {\n    // B-tree tombstone: begin=None, end=Timestamp(e) where e > 0.\n    // Represents a row deleted in MVCC that existed in B-tree before MVCC.\n    // Before checkpoint (ckpt_max < e): tombstone must be retained.\n    let mut versions = vec![make_rv(None, ts(5))];\n    let dropped = MvStore::<MvccClock>::gc_version_chain(&mut versions, u64::MAX, 3);\n    assert_eq!(dropped, 0, \"tombstone retained: e=5 > ckpt_max=3\");\n    assert_eq!(versions.len(), 1);\n\n    // After checkpoint (ckpt_max >= e): tombstone is collected.\n    let dropped = MvStore::<MvccClock>::gc_version_chain(&mut versions, u64::MAX, 5);\n    assert_eq!(dropped, 1, \"tombstone collected: e=5 <= ckpt_max=5\");\n    assert_eq!(versions.len(), 0);\n}\n\n/// What this test checks: Garbage collection removes only versions that are provably unreachable and keeps versions still required for visibility and safety.\n/// Why this matters: GC mistakes can either lose data (over-collection) or retain stale history forever (under-collection).\n#[test]\n/// Rule 3 must never fire when superseded versions remain in the chain — removing\n/// the current version would leave orphaned superseded versions that \"poison\" the\n/// dual cursor, making it hide the B-tree row without providing a replacement.\nfn test_gc_rule3_not_firing_with_unremovable_superseded() {\n    // Two versions: superseded with e > lwm (can't remove), and current.\n    // Rule 2 can't remove the superseded one, so 2 versions remain.\n    // Rule 3 requires sole-survivor, so it must NOT fire.\n    let mut versions = vec![\n        make_rv(ts(3), ts(15)), // e=15 > lwm=10 — retained\n        make_rv(ts(15), None),  // current\n    ];\n    let dropped = MvStore::<MvccClock>::gc_version_chain(&mut versions, 10, 20);\n    assert_eq!(dropped, 0);\n    assert_eq!(versions.len(), 2);\n}\n\n/// What this test checks: Garbage collection removes only versions that are provably unreachable and keeps versions still required for visibility and safety.\n/// Why this matters: GC mistakes can either lose data (over-collection) or retain stale history forever (under-collection).\n#[test]\n/// GC on an empty version chain is a no-op. Verifies no panics or off-by-one errors.\nfn test_gc_noop_on_empty() {\n    let mut versions: Vec<RowVersion> = vec![];\n    let dropped = MvStore::<MvccClock>::gc_version_chain(&mut versions, 10, 5);\n    assert_eq!(dropped, 0);\n}\n\n/// What this test checks: Garbage collection removes only versions that are provably unreachable and keeps versions still required for visibility and safety.\n/// Why this matters: GC mistakes can either lose data (over-collection) or retain stale history forever (under-collection).\n#[test]\n/// All three rules fire together: aborted garbage (Rule 1), two superseded versions\n/// below LWM with a committed current (Rule 2), and the sole surviving current\n/// version below LWM and checkpointed (Rule 3). The chain is fully reclaimed.\nfn test_gc_combined_rules() {\n    // Mix of all cases: aborted, superseded below LWM, current checkpointed,\n    // and one above LWM that must be retained.\n    let mut versions = vec![\n        make_rv(None, None),   // aborted → rule 1\n        make_rv(ts(1), ts(3)), // superseded, e=3 <= lwm=10 → rule 2 (has_current=true)\n        make_rv(ts(3), ts(5)), // superseded, e=5 <= lwm=10 → rule 2\n        make_rv(ts(5), None),  // current, b=5 <= ckpt_max=5, b < lwm=10 → rule 3\n    ];\n    let dropped = MvStore::<MvccClock>::gc_version_chain(&mut versions, 10, 5);\n    assert_eq!(dropped, 4);\n    assert!(versions.is_empty());\n}\n\n/// What this test checks: Garbage collection removes only versions that are provably unreachable and keeps versions still required for visibility and safety.\n/// Why this matters: GC mistakes can either lose data (over-collection) or retain stale history forever (under-collection).\n#[test]\n/// End-to-end at the MvStore level: insert a row, commit, and run GC. Without a\n/// checkpoint the version is not yet in the B-tree, so Rule 3 doesn't fire and\n/// the version survives. Verifies the full insert→commit→GC pipeline.\nfn test_gc_integration_insert_commit_gc() {\n    let db = MvccTestDb::new();\n\n    // Insert and commit a row.\n    let tx1 = db\n        .mvcc_store\n        .begin_tx(db.conn.pager.load().clone())\n        .unwrap();\n    let row = generate_simple_string_row((-2).into(), 1, \"gc_test\");\n    db.mvcc_store.insert(tx1, row).unwrap();\n    commit_tx(db.mvcc_store.clone(), &db.conn, tx1).unwrap();\n\n    // Row should be in the MvStore.\n    assert!(!db.mvcc_store.rows.is_empty());\n\n    // No active transactions → LWM = u64::MAX.\n    // ckpt_max = 0 (no checkpoint yet), so rule 3 won't fire (b > ckpt_max).\n    let dropped = db.mvcc_store.drop_unused_row_versions();\n    assert_eq!(dropped, 0);\n    assert!(!db.mvcc_store.rows.is_empty());\n}\n\n/// What this test checks: Garbage collection removes only versions that are provably unreachable and keeps versions still required for visibility and safety.\n/// Why this matters: GC mistakes can either lose data (over-collection) or retain stale history forever (under-collection).\n#[test]\n/// Rolling back a transaction leaves aborted garbage (begin=None, end=None).\n/// GC reclaims the versions. The SkipMap entry stays (lazy removal to avoid\n/// TOCTOU with concurrent writers) but the version vec is empty.\nfn test_gc_integration_rollback_creates_aborted_garbage() {\n    let db = MvccTestDb::new();\n\n    let tx1 = db\n        .mvcc_store\n        .begin_tx(db.conn.pager.load().clone())\n        .unwrap();\n    let row = generate_simple_string_row((-2).into(), 1, \"will_rollback\");\n    db.mvcc_store.insert(tx1, row).unwrap();\n    db.mvcc_store.rollback_tx(\n        tx1,\n        db.conn.pager.load().clone(),\n        &db.conn,\n        crate::MAIN_DB_ID,\n    );\n\n    // Rollback should leave aborted garbage (begin=None, end=None).\n    let entry = db\n        .mvcc_store\n        .rows\n        .get(&RowID::new((-2).into(), RowKey::Int(1)));\n    assert!(entry.is_some());\n    {\n        let versions = entry.as_ref().unwrap().value().read();\n        assert_eq!(versions.len(), 1);\n        assert!(versions[0].begin.is_none());\n        assert!(versions[0].end.is_none());\n    }\n\n    // GC should clean up the version. The SkipMap entry stays (lazy removal\n    // in background GC avoids TOCTOU), but the version vec should be empty.\n    let dropped = db.mvcc_store.drop_unused_row_versions();\n    assert_eq!(dropped, 1);\n    let entry = db\n        .mvcc_store\n        .rows\n        .get(&RowID::new((-2).into(), RowKey::Int(1)));\n    assert!(entry.is_some(), \"SkipMap entry stays (lazy removal)\");\n    assert!(\n        entry.unwrap().value().read().is_empty(),\n        \"but versions should be empty\"\n    );\n}\n\n/// The low-water mark (LWM) is the minimum begin_ts of all active readers. GC\n/// must not remove any version that an active reader might still need. This test\n/// opens a reader, writes a new version that supersedes the reader's snapshot,\n/// and runs GC — the old version must survive. After the reader closes, GC runs\n/// again and reclaims it. This is the core safety property of LWM-based GC.\n#[test]\nfn test_gc_active_reader_pins_lwm() {\n    let db = MvccTestDb::new();\n    let table_id: MVTableId = (-2).into();\n\n    // T1 inserts a row and commits.\n    let tx1 = db\n        .mvcc_store\n        .begin_tx(db.conn.pager.load().clone())\n        .unwrap();\n    let row_v1 = generate_simple_string_row(table_id, 1, \"version_1\");\n    db.mvcc_store.insert(tx1, row_v1.clone()).unwrap();\n    commit_tx(db.mvcc_store.clone(), &db.conn, tx1).unwrap();\n\n    // T2 begins a read transaction — pins LWM at T2's begin_ts.\n    let conn2 = db.db.connect().unwrap();\n    let tx2 = db.mvcc_store.begin_tx(conn2.pager.load().clone()).unwrap();\n    let tx2_begin_ts = db.mvcc_store.txs.get(&tx2).unwrap().value().begin_ts;\n\n    // T3 updates the row and commits, creating a superseded version.\n    let conn3 = db.db.connect().unwrap();\n    let tx3 = db.mvcc_store.begin_tx(conn3.pager.load().clone()).unwrap();\n    let row_v2 = generate_simple_string_row(table_id, 1, \"version_2\");\n    db.mvcc_store.update(tx3, row_v2).unwrap();\n    commit_tx(db.mvcc_store.clone(), &conn3, tx3).unwrap();\n\n    // LWM should be T2's begin_ts (the active reader).\n    let lwm = db.mvcc_store.compute_lwm();\n    assert_eq!(\n        lwm, tx2_begin_ts,\n        \"LWM should equal the active reader's begin_ts\"\n    );\n\n    // GC should NOT remove the superseded version (its end_ts > lwm).\n    let row_id = RowID::new(table_id, RowKey::Int(1));\n    let dropped = db.mvcc_store.drop_unused_row_versions();\n    assert_eq!(\n        dropped, 0,\n        \"GC should not remove versions visible to active reader\"\n    );\n    {\n        let entry = db.mvcc_store.rows.get(&row_id).unwrap();\n        let versions = entry.value().read();\n        assert_eq!(versions.len(), 2, \"both versions should be retained\");\n    }\n\n    // T2 still sees the old version.\n    let read_row = db.mvcc_store.read(tx2, &row_id).unwrap().unwrap();\n    assert_eq!(\n        read_row, row_v1,\n        \"active reader should still see the old version\"\n    );\n\n    // Close the reader transaction.\n    db.mvcc_store.remove_tx(tx2);\n\n    // LWM should now be u64::MAX.\n    assert_eq!(db.mvcc_store.compute_lwm(), u64::MAX);\n\n    // GC should now remove the superseded version.\n    let dropped = db.mvcc_store.drop_unused_row_versions();\n    assert_eq!(\n        dropped, 1,\n        \"superseded version should be reclaimed after reader closes\"\n    );\n    {\n        let entry = db.mvcc_store.rows.get(&row_id).unwrap();\n        let versions = entry.value().read();\n        assert_eq!(versions.len(), 1, \"only current version should remain\");\n    }\n}\n\n/// Index rows live in a separate SkipMap from table rows and go through their own\n/// GC path (gc_index_row_versions). This SQL-level test creates an indexed table,\n/// checkpoints, updates the row (creating superseded index versions), checkpoints\n/// again, and verifies the index still returns correct results. Catches regressions\n/// where index GC removes versions that the dual cursor still needs.\n#[test]\nfn test_gc_e2e_index_rows_collected_after_checkpoint() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n    conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY, val TEXT)\")\n        .unwrap();\n    conn.execute(\"CREATE INDEX idx_val ON t(val)\").unwrap();\n    conn.execute(\"INSERT INTO t VALUES (1, 'alpha')\").unwrap();\n    conn.execute(\"INSERT INTO t VALUES (2, 'beta')\").unwrap();\n\n    // Checkpoint flushes to B-tree and triggers GC on both table and index rows.\n    conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n\n    // After GC, reads should still work via B-tree fallthrough.\n    let rows = get_rows(&conn, \"SELECT val FROM t ORDER BY val\");\n    assert_eq!(rows.len(), 2);\n    assert_eq!(rows[0][0].to_string(), \"alpha\");\n    assert_eq!(rows[1][0].to_string(), \"beta\");\n\n    // Index scan should also work.\n    let rows = get_rows(&conn, \"SELECT id FROM t WHERE val = 'alpha'\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(rows[0][0].as_int().unwrap(), 1);\n\n    // Update a row — creates new index versions.\n    conn.execute(\"UPDATE t SET val = 'gamma' WHERE id = 1\")\n        .unwrap();\n    conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n\n    // Old index entry ('alpha') should be gone, new entry ('gamma') visible.\n    let rows = get_rows(&conn, \"SELECT id FROM t WHERE val = 'alpha'\");\n    assert_eq!(rows.len(), 0);\n    let rows = get_rows(&conn, \"SELECT id FROM t WHERE val = 'gamma'\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(rows[0][0].as_int().unwrap(), 1);\n\n    let rows = get_rows(&conn, \"PRAGMA integrity_check\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(&rows[0][0].to_string(), \"ok\");\n}\n\n// ─── GC quickcheck property tests ────────────────────────────────────────\n\n/// Represents a version chain entry for quickcheck.\n#[derive(Debug, Clone)]\nstruct ArbitraryVersionChain {\n    versions: Vec<RowVersion>,\n    lwm: u64,\n    ckpt_max: u64,\n}\n\n/// Generates RowVersions matching realistic MVCC states.\n/// Only produces valid (begin, end) combinations that can actually occur.\nfn arbitrary_row_version(g: &mut Gen) -> RowVersion {\n    // Weight toward realistic states:\n    // 32% current (Timestamp, None), 24% superseded (Timestamp, Timestamp),\n    // 8% aborted (None, None), 8% pending insert (TxID, None),\n    // 8% pending delete (Timestamp, TxID), 20% B-tree tombstone (None, Timestamp)\n    let kind = u8::arbitrary(g) % 25;\n    let (begin, end) = match kind {\n        0..=7 => {\n            // Current committed version\n            let b = u64::arbitrary(g) % 20 + 1;\n            (Some(TxTimestampOrID::Timestamp(b)), None)\n        }\n        8..=13 => {\n            // Superseded version\n            let b = u64::arbitrary(g) % 15 + 1;\n            let e = b + u64::arbitrary(g) % 10 + 1;\n            (\n                Some(TxTimestampOrID::Timestamp(b)),\n                Some(TxTimestampOrID::Timestamp(e)),\n            )\n        }\n        14..=15 => {\n            // Aborted garbage\n            (None, None)\n        }\n        16..=17 => {\n            // Pending insert\n            let t = u64::arbitrary(g) % 20 + 1;\n            (Some(TxTimestampOrID::TxID(t)), None)\n        }\n        18..=19 => {\n            // Pending delete\n            let b = u64::arbitrary(g) % 15 + 1;\n            let t = u64::arbitrary(g) % 20 + 1;\n            (\n                Some(TxTimestampOrID::Timestamp(b)),\n                Some(TxTimestampOrID::TxID(t)),\n            )\n        }\n        20..=24 => {\n            // B-tree tombstone (begin=None, end=e) — row existed before MVCC, then deleted\n            let e = u64::arbitrary(g) % 20 + 1;\n            (None, Some(TxTimestampOrID::Timestamp(e)))\n        }\n        _ => unreachable!(),\n    };\n\n    RowVersion {\n        id: 0,\n        begin,\n        end,\n        row: generate_simple_string_row((-2).into(), 1, \"qc\"),\n        btree_resident: bool::arbitrary(g),\n    }\n}\n\nimpl Arbitrary for ArbitraryVersionChain {\n    fn arbitrary(g: &mut Gen) -> Self {\n        // 1..8 versions (no empty chains — they trivially pass all properties)\n        let len = usize::arbitrary(g) % 8 + 1;\n        let versions: Vec<RowVersion> = (0..len).map(|_| arbitrary_row_version(g)).collect();\n        // Include boundary values with ~20% probability each.\n        let lwm = match u8::arbitrary(g) % 5 {\n            0 => 0,\n            1 => u64::MAX, // blocking checkpoint case\n            _ => u64::arbitrary(g) % 30,\n        };\n        let ckpt_max = match u8::arbitrary(g) % 5 {\n            0 => 0,        // no checkpoint has run\n            1 => u64::MAX, // everything checkpointed\n            _ => u64::arbitrary(g) % 30,\n        };\n        Self {\n            versions,\n            lwm,\n            ckpt_max,\n        }\n    }\n}\n\n/// GC only removes versions — it never synthesizes new ones. For any input chain,\n/// the output must be a subset (same length or shorter).\n#[quickcheck]\nfn prop_gc_never_increases_version_count(chain: ArbitraryVersionChain) -> bool {\n    let before = chain.versions.len();\n    let mut versions = chain.versions;\n    MvStore::<MvccClock>::gc_version_chain(&mut versions, chain.lwm, chain.ckpt_max);\n    versions.len() <= before\n}\n\n/// Running GC twice with the same LWM and ckpt_max must produce the same result\n/// as running it once. A non-idempotent GC would indicate that GC output triggers\n/// further removals on re-evaluation, which means the first pass missed something.\n/// Compares actual version content (begin/end), not just chain length.\n#[quickcheck]\nfn prop_gc_is_idempotent(chain: ArbitraryVersionChain) -> bool {\n    let mut v1 = chain.versions.clone();\n    MvStore::<MvccClock>::gc_version_chain(&mut v1, chain.lwm, chain.ckpt_max);\n    let snapshot = v1.clone();\n    MvStore::<MvccClock>::gc_version_chain(&mut v1, chain.lwm, chain.ckpt_max);\n    // Compare content, not just length — a swap bug would pass a length-only check.\n    v1.len() == snapshot.len()\n        && v1\n            .iter()\n            .zip(snapshot.iter())\n            .all(|(a, b)| a.begin == b.begin && a.end == b.end)\n}\n\n/// Aborted garbage (begin=None, end=None) is invisible to every transaction and\n/// has no B-tree implications. GC must remove all of it unconditionally (Rule 1).\n/// No aborted garbage should survive a GC pass, regardless of LWM or ckpt_max.\n#[quickcheck]\nfn prop_gc_removes_all_aborted_garbage(chain: ArbitraryVersionChain) -> bool {\n    let mut versions = chain.versions;\n    MvStore::<MvccClock>::gc_version_chain(&mut versions, chain.lwm, chain.ckpt_max);\n    versions\n        .iter()\n        .all(|rv| !matches!((&rv.begin, &rv.end), (None, None)))\n}\n\n/// Uncommitted inserts (begin=TxID, end=None) belong to an in-flight transaction.\n/// GC cannot know whether it will commit or abort, so it must never touch them.\n/// Verifies all such versions survive GC regardless of other chain contents.\n#[quickcheck]\nfn prop_gc_retains_txid_begins(chain: ArbitraryVersionChain) -> bool {\n    let txid_begins_before: usize = chain\n        .versions\n        .iter()\n        .filter(|rv| matches!(&rv.begin, Some(TxTimestampOrID::TxID(_))) && rv.end.is_none())\n        .count();\n    let mut versions = chain.versions;\n    MvStore::<MvccClock>::gc_version_chain(&mut versions, chain.lwm, chain.ckpt_max);\n    let txid_begins_after: usize = versions\n        .iter()\n        .filter(|rv| matches!(&rv.begin, Some(TxTimestampOrID::TxID(_))) && rv.end.is_none())\n        .count();\n    // Active uncommitted versions (begin=TxID, end=None) are never aborted garbage\n    // and don't match rule 2 or 3, so they should be retained.\n    txid_begins_after == txid_begins_before\n}\n\n/// Uncommitted deletions (end=TxID) represent a pending delete by an in-flight\n/// transaction. Rule 2 only matches end=Timestamp, so these must be retained.\n/// Verifies GC never removes versions with TxID end markers.\n#[quickcheck]\nfn prop_gc_retains_txid_ends(chain: ArbitraryVersionChain) -> bool {\n    // Versions with end=TxID and non-None begin are not matched by any removal\n    // rule (rule 1 requires (None,None), rule 2 requires end=Timestamp).\n    let filter =\n        |rv: &&RowVersion| matches!(&rv.end, Some(TxTimestampOrID::TxID(_))) && rv.begin.is_some();\n    let txid_ends_before: usize = chain.versions.iter().filter(filter).count();\n    let mut versions = chain.versions;\n    MvStore::<MvccClock>::gc_version_chain(&mut versions, chain.lwm, chain.ckpt_max);\n    let txid_ends_after: usize = versions.iter().filter(filter).count();\n    txid_ends_after == txid_ends_before\n}\n\n/// Current versions (begin=Timestamp(b), end=None) are not removable before they\n/// are checkpointed. Forces ckpt_max=0 and verifies all committed current versions\n/// survive.\n#[quickcheck]\nfn prop_gc_current_versions_protected_before_checkpoint(chain: ArbitraryVersionChain) -> bool {\n    let current_before: usize = chain\n        .versions\n        .iter()\n        .filter(|rv| {\n            matches!(\n                (&rv.begin, &rv.end),\n                (Some(TxTimestampOrID::Timestamp(_)), None)\n            )\n        })\n        .count();\n    let mut versions = chain.versions;\n    MvStore::<MvccClock>::gc_version_chain(&mut versions, chain.lwm, 0);\n    let current_after: usize = versions\n        .iter()\n        .filter(|rv| {\n            matches!(\n                (&rv.begin, &rv.end),\n                (Some(TxTimestampOrID::Timestamp(_)), None)\n            )\n        })\n        .count();\n    current_after == current_before\n}\n\n/// When a row has been deleted but the deletion isn't checkpointed yet, the\n/// tombstone (superseded version with end > ckpt_max) is the only thing preventing\n/// the dual cursor from reading a stale B-tree row. If GC empties such a chain,\n/// the deleted row reappears. Verifies GC never empties a chain that has\n/// uncheckpointed tombstones and no committed current version to take over.\n#[quickcheck]\nfn prop_gc_tombstone_guard_preserves_btree_safety(chain: ArbitraryVersionChain) -> bool {\n    // If a chain has only superseded versions (no committed current) and at\n    // least one has e > ckpt_max, GC must not empty the chain — removing all\n    // versions would let the dual cursor fall through to a stale B-tree row.\n    let mut versions = chain.versions.clone();\n    MvStore::<MvccClock>::gc_version_chain(&mut versions, chain.lwm, chain.ckpt_max);\n\n    // Check: if pre-GC chain had no committed current version AND had a\n    // superseded version with e > ckpt_max, post-GC chain must not be empty.\n    let had_committed_current = chain\n        .versions\n        .iter()\n        .any(|rv| rv.end.is_none() && matches!(&rv.begin, Some(TxTimestampOrID::Timestamp(_))));\n    let had_uncheckpointed_tombstone = chain\n        .versions\n        .iter()\n        .any(|rv| matches!(&rv.end, Some(TxTimestampOrID::Timestamp(e)) if *e > chain.ckpt_max));\n    // Only non-garbage versions matter (aborted garbage is always removed first)\n    let had_non_garbage = chain\n        .versions\n        .iter()\n        .any(|rv| !matches!((&rv.begin, &rv.end), (None, None)));\n\n    if !had_committed_current && had_uncheckpointed_tombstone && had_non_garbage {\n        !versions.is_empty()\n    } else {\n        true // no constraint in this case\n    }\n}\n\n/// Superseded versions without a committed current version are dangerous — their\n/// presence tells the dual cursor \"this row was modified\" but there's no current\n/// version to serve reads. GC must only leave such orphans when they're justifiably\n/// retained: still visible to a reader (e > lwm), guarding an uncheckpointed\n/// deletion (e > ckpt_max).\n#[quickcheck]\nfn prop_gc_no_orphaned_superseded_versions(chain: ArbitraryVersionChain) -> bool {\n    // After GC, if a chain has superseded versions without a committed current\n    // version, each superseded version must be justifiably retained:\n    // - e > lwm (Rule 2 didn't fire — still visible to some reader)\n    // - e > ckpt_max (tombstone guard — deletion not yet in B-tree)\n    let mut versions = chain.versions;\n    MvStore::<MvccClock>::gc_version_chain(&mut versions, chain.lwm, chain.ckpt_max);\n\n    let has_committed_current = versions\n        .iter()\n        .any(|rv| rv.end.is_none() && matches!(&rv.begin, Some(TxTimestampOrID::Timestamp(_))));\n    let has_superseded = versions.iter().any(|rv| {\n        matches!(\n            (&rv.begin, &rv.end),\n            (\n                Some(TxTimestampOrID::Timestamp(_)),\n                Some(TxTimestampOrID::Timestamp(_))\n            )\n        )\n    });\n\n    if has_superseded && !has_committed_current {\n        versions\n            .iter()\n            .filter(|rv| {\n                matches!(\n                    (&rv.begin, &rv.end),\n                    (\n                        Some(TxTimestampOrID::Timestamp(_)),\n                        Some(TxTimestampOrID::Timestamp(_))\n                    )\n                )\n            })\n            .all(|rv| {\n                if let Some(TxTimestampOrID::Timestamp(e)) = &rv.end {\n                    *e > chain.lwm || *e > chain.ckpt_max\n                } else {\n                    false\n                }\n            })\n    } else {\n        true\n    }\n}\n\n/// Test that a transaction cannot see uncommitted changes from another transaction.\n/// This verifies snapshot isolation.\n#[test]\nfn test_mvcc_snapshot_isolation() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n\n    let conn1 = db.connect();\n    conn1\n        .execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY, value INTEGER)\")\n        .unwrap();\n    conn1\n        .execute(\"INSERT INTO t VALUES (1, 100), (2, 200), (3, 300)\")\n        .unwrap();\n\n    // Start tx1 and read initial values\n    conn1.execute(\"BEGIN CONCURRENT\").unwrap();\n    let rows1 = get_rows(&conn1, \"SELECT value FROM t WHERE id = 2\");\n    assert_eq!(rows1[0][0].to_string(), \"200\");\n\n    // Start tx2 and modify the same row\n    let conn2 = db.connect();\n    conn2.execute(\"BEGIN CONCURRENT\").unwrap();\n    conn2\n        .execute(\"UPDATE t SET value = 999 WHERE id = 2\")\n        .unwrap();\n    conn2.execute(\"COMMIT\").unwrap();\n\n    // Tx1 should still see the old value (snapshot isolation)\n    let rows1_again = get_rows(&conn1, \"SELECT value FROM t WHERE id = 2\");\n    assert_eq!(\n        rows1_again[0][0].to_string(),\n        \"200\",\n        \"Tx1 should not see tx2's committed changes\"\n    );\n\n    conn1.execute(\"COMMIT\").unwrap();\n\n    // After tx1 commits, new reads should see tx2's changes\n    let rows_after = get_rows(&conn1, \"SELECT value FROM t WHERE id = 2\");\n    assert_eq!(rows_after[0][0].to_string(), \"999\");\n}\n/// Similar test but with the constraint error happening on the third unique column.\n/// This tests that ALL previous index modifications are rolled back.\n/// Uses interactive transaction (BEGIN CONCURRENT) to reproduce the bug.\n#[test]\nfn test_update_three_unique_columns_partial_rollback() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n\n    // Create table with three unique columns\n    conn.execute(\n        \"CREATE TABLE t(\n            id INTEGER PRIMARY KEY,\n            col_a TEXT UNIQUE,\n            col_b REAL UNIQUE,\n            col_c INTEGER UNIQUE\n        )\",\n    )\n    .unwrap();\n\n    // Insert two rows\n    conn.execute(\"INSERT INTO t VALUES (1, 'a1', 1.0, 100)\")\n        .unwrap();\n    conn.execute(\"INSERT INTO t VALUES (2, 'a2', 2.0, 200)\")\n        .unwrap();\n\n    // Start interactive transaction\n    conn.execute(\"BEGIN CONCURRENT\").unwrap();\n\n    // Try to UPDATE row 1 with:\n    // - col_a = 'new_a' (index modified)\n    // - col_b = 3.0 (index modified)\n    // - col_c = 200 (FAIL - conflicts with row 2)\n    // BUG: col_a and col_b index changes are NOT rolled back!\n    let result =\n        conn.execute(\"UPDATE t SET col_a = 'new_a', col_b = 3.0, col_c = 200 WHERE id = 1\");\n    assert!(\n        result.is_err(),\n        \"Expected unique constraint violation on col_c\"\n    );\n\n    // COMMIT - in buggy case, this commits partial index changes\n    conn.execute(\"COMMIT\").unwrap();\n\n    // Now try to UPDATE the same row - this should work but may crash\n    // if col_a or col_b index entries are inconsistent\n    conn.execute(\"UPDATE t SET col_a = 'updated_a', col_b = 5.0, col_c = 500 WHERE id = 1\")\n        .unwrap();\n\n    // Verify index lookups work\n    let rows = get_rows(&conn, \"SELECT * FROM t WHERE col_a = 'updated_a'\");\n    assert_eq!(rows.len(), 1);\n\n    let rows = get_rows(&conn, \"SELECT * FROM t WHERE col_b = 5.0\");\n    assert_eq!(rows.len(), 1);\n\n    let rows = get_rows(&conn, \"SELECT * FROM t WHERE col_c = 500\");\n    assert_eq!(rows.len(), 1);\n\n    // Integrity check\n    let rows = get_rows(&conn, \"PRAGMA integrity_check\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(&rows[0][0].to_string(), \"ok\");\n}\n\n/// Test that simulates the exact sequence from the stress test bug:\n/// Multiple interactive transactions updating the same row, with constraint errors.\n///\n/// From the log:\n/// - tx 248: UPDATE row with pk=1.37, sets unique_col='sweet_wind_280' -> COMMIT\n/// - tx 1149: BEGIN, UPDATE same row (modifies unique_col index, fails on other_unique), COMMIT\n///   BUG: partial index changes from failed UPDATE are committed!\n/// - tx 1324: UPDATE same row -> CRASH \"IdxDelete: no matching index entry found\"\n#[test]\nfn test_sequential_updates_with_constraint_errors() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n\n    conn.execute(\n        \"CREATE TABLE t(\n            pk REAL PRIMARY KEY,\n            unique_col TEXT UNIQUE,\n            other_unique REAL UNIQUE\n        )\",\n    )\n    .unwrap();\n\n    // Insert initial rows (simulating the stress test setup)\n    conn.execute(\"INSERT INTO t VALUES (1.37, 'sweet_wind_280', 9.05)\")\n        .unwrap();\n    conn.execute(\"INSERT INTO t VALUES (2.13, 'other_value', 2.13)\")\n        .unwrap();\n\n    // First successful update (like tx 248 in the bug)\n    conn.execute(\"UPDATE t SET unique_col = 'cold_grass_813', other_unique = 3.90 WHERE pk = 1.37\")\n        .unwrap();\n\n    // Verify the update\n    let rows = get_rows(&conn, \"SELECT unique_col FROM t WHERE pk = 1.37\");\n    assert_eq!(rows[0][0].cast_text().unwrap(), \"cold_grass_813\");\n\n    // Like tx 1149: Start interactive transaction\n    conn.execute(\"BEGIN CONCURRENT\").unwrap();\n\n    // Try an update that will fail on other_unique (conflicts with row 2)\n    // The UPDATE will:\n    // 1. Delete old index entry for unique_col ('cold_grass_813')\n    // 2. Insert new index entry for unique_col ('new_value')\n    // 3. Delete old index entry for other_unique (3.90)\n    // 4. Check constraint for other_unique (2.13) -> FAIL!\n    // BUG: Steps 1-3 are NOT rolled back!\n    let result =\n        conn.execute(\"UPDATE t SET unique_col = 'new_value', other_unique = 2.13 WHERE pk = 1.37\");\n    assert!(result.is_err(), \"Expected unique constraint violation\");\n\n    // COMMIT the transaction (like the stress test does after the error)\n    // BUG: This commits the partial index changes!\n    conn.execute(\"COMMIT\").unwrap();\n\n    // Like tx 1324: Try another update on the same row\n    // If partial changes were committed:\n    // - Table row has unique_col = 'cold_grass_813'\n    // - But unique_col index has 'new_value' (not 'cold_grass_813')!\n    // - This UPDATE reads 'cold_grass_813' from table, tries to delete that index entry\n    // - CRASH: \"IdxDelete: no matching index entry found\"\n    conn.execute(\"UPDATE t SET unique_col = 'fresh_sun_348', other_unique = 5.0 WHERE pk = 1.37\")\n        .unwrap();\n\n    // Verify final state\n    let rows = get_rows(\n        &conn,\n        \"SELECT unique_col, other_unique FROM t WHERE pk = 1.37\",\n    );\n    assert_eq!(rows[0][0].cast_text().unwrap(), \"fresh_sun_348\");\n\n    // Verify index lookups work\n    let rows = get_rows(&conn, \"SELECT * FROM t WHERE unique_col = 'fresh_sun_348'\");\n    assert_eq!(rows.len(), 1);\n\n    // Integrity check\n    let rows = get_rows(&conn, \"PRAGMA integrity_check\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(&rows[0][0].to_string(), \"ok\");\n}\n\n/// Test that multiple successful statements in an interactive transaction\n/// have their changes preserved when a subsequent statement fails.\n/// This tests the statement-level savepoint functionality.\n#[test]\nfn test_savepoint_multiple_statements_last_fails() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n\n    conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY)\")\n        .unwrap();\n\n    // Start interactive transaction\n    conn.execute(\"BEGIN CONCURRENT\").unwrap();\n\n    // Statement 1: Insert row 1 - success\n    conn.execute(\"INSERT INTO t VALUES (1)\").unwrap();\n\n    // Statement 2: Insert row 2 - success\n    conn.execute(\"INSERT INTO t VALUES (2)\").unwrap();\n\n    // Statement 3: Insert row 1 again - fails with PK violation\n    let result = conn.execute(\"INSERT INTO t VALUES (1)\");\n    assert!(result.is_err(), \"Expected primary key violation\");\n\n    // COMMIT - should preserve statements 1 and 2\n    conn.execute(\"COMMIT\").unwrap();\n\n    // Verify rows 1 and 2 exist\n    let rows = get_rows(&conn, \"SELECT * FROM t ORDER BY id\");\n    assert_eq!(rows.len(), 2);\n    assert_eq!(rows[0][0].as_int().unwrap(), 1);\n    assert_eq!(rows[1][0].as_int().unwrap(), 2);\n\n    // Integrity check\n    let rows = get_rows(&conn, \"PRAGMA integrity_check\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(&rows[0][0].to_string(), \"ok\");\n}\n\n/// Test that when the same row is modified by multiple statements,\n/// and the second modification fails, the first modification is preserved.\n#[test]\nfn test_savepoint_same_row_multiple_statements() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n\n    conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY, v INTEGER, other_unique INTEGER UNIQUE)\")\n        .unwrap();\n\n    // Insert initial row and a row to cause conflict\n    conn.execute(\"INSERT INTO t VALUES (1, 100, 1)\").unwrap();\n    conn.execute(\"INSERT INTO t VALUES (2, 200, 2)\").unwrap();\n\n    // Start interactive transaction\n    conn.execute(\"BEGIN CONCURRENT\").unwrap();\n\n    // Statement 1: Update row 1's value to 150 - success\n    conn.execute(\"UPDATE t SET v = 150 WHERE id = 1\").unwrap();\n\n    // Statement 2: Try to update row 1 with conflicting other_unique - fails\n    let result = conn.execute(\"UPDATE t SET v = 175, other_unique = 2 WHERE id = 1\");\n    assert!(result.is_err(), \"Expected unique constraint violation\");\n\n    // COMMIT - should preserve statement 1's change (v = 150)\n    conn.execute(\"COMMIT\").unwrap();\n\n    // Verify row 1 has v = 150 (from statement 1), not 175 (from failed statement 2)\n    let rows = get_rows(&conn, \"SELECT v, other_unique FROM t WHERE id = 1\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(rows[0][0].as_int().unwrap(), 150);\n    assert_eq!(rows[0][1].as_int().unwrap(), 1); // other_unique unchanged\n\n    // Integrity check\n    let rows = get_rows(&conn, \"PRAGMA integrity_check\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(&rows[0][0].to_string(), \"ok\");\n}\n\n/// Test that index operations are properly tracked per-statement.\n/// When a statement fails after partially modifying indexes,\n/// only that statement's index changes are rolled back.\n#[test]\nfn test_savepoint_index_multiple_statements() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n\n    conn.execute(\n        \"CREATE TABLE t(\n            id INTEGER PRIMARY KEY,\n            name TEXT UNIQUE,\n            value INTEGER UNIQUE\n        )\",\n    )\n    .unwrap();\n\n    // Insert rows\n    conn.execute(\"INSERT INTO t VALUES (1, 'a', 10)\").unwrap();\n    conn.execute(\"INSERT INTO t VALUES (2, 'b', 20)\").unwrap();\n\n    // Start interactive transaction\n    conn.execute(\"BEGIN CONCURRENT\").unwrap();\n\n    // Statement 1: Successfully change name for row 1\n    conn.execute(\"UPDATE t SET name = 'c' WHERE id = 1\")\n        .unwrap();\n\n    // Statement 2: Try to change name to 'b' (conflict with row 2) - fails\n    let result = conn.execute(\"UPDATE t SET name = 'b' WHERE id = 1\");\n    assert!(\n        result.is_err(),\n        \"Expected unique constraint violation on name\"\n    );\n\n    // COMMIT\n    conn.execute(\"COMMIT\").unwrap();\n\n    // Verify row 1 has name 'c' from statement 1\n    let rows = get_rows(&conn, \"SELECT name FROM t WHERE id = 1\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(rows[0][0].cast_text().unwrap(), \"c\");\n\n    // Verify index lookups work correctly\n    let rows = get_rows(&conn, \"SELECT id FROM t WHERE name = 'c'\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(rows[0][0].as_int().unwrap(), 1);\n\n    // 'a' should no longer be in the index\n    let rows = get_rows(&conn, \"SELECT id FROM t WHERE name = 'a'\");\n    assert_eq!(rows.len(), 0);\n\n    // 'b' should still point to row 2\n    let rows = get_rows(&conn, \"SELECT id FROM t WHERE name = 'b'\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(rows[0][0].as_int().unwrap(), 2);\n\n    // Integrity check\n    let rows = get_rows(&conn, \"PRAGMA integrity_check\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(&rows[0][0].to_string(), \"ok\");\n}\n\n/// Test INSERT followed by DELETE of same row, then another statement fails.\n/// The insert+delete should be preserved (row shouldn't exist).\n#[test]\nfn test_savepoint_insert_delete_then_fail() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n\n    conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY, v INTEGER UNIQUE)\")\n        .unwrap();\n    conn.execute(\"INSERT INTO t VALUES (2, 200)\").unwrap();\n\n    // Start interactive transaction\n    conn.execute(\"BEGIN CONCURRENT\").unwrap();\n\n    // Statement 1: Insert row 1\n    conn.execute(\"INSERT INTO t VALUES (1, 100)\").unwrap();\n\n    // Statement 2: Delete row 1\n    conn.execute(\"DELETE FROM t WHERE id = 1\").unwrap();\n\n    // Statement 3: Try to insert with conflicting unique value - fails\n    let result = conn.execute(\"INSERT INTO t VALUES (3, 200)\");\n    assert!(result.is_err(), \"Expected unique constraint violation\");\n\n    // COMMIT\n    conn.execute(\"COMMIT\").unwrap();\n\n    // Verify row 1 does not exist (was deleted in statement 2)\n    let rows = get_rows(&conn, \"SELECT * FROM t WHERE id = 1\");\n    assert_eq!(rows.len(), 0);\n\n    // Row 2 should still exist\n    let rows = get_rows(&conn, \"SELECT * FROM t WHERE id = 2\");\n    assert_eq!(rows.len(), 1);\n\n    // Integrity check\n    let rows = get_rows(&conn, \"PRAGMA integrity_check\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(&rows[0][0].to_string(), \"ok\");\n}\n\n#[test]\nfn test_delete_row_is_hidden_from_desc_unique_index_scan() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n\n    conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY, val INTEGER UNIQUE)\")\n        .unwrap();\n    conn.execute(\"INSERT INTO t VALUES (42, 46)\").unwrap();\n    conn.execute(\"DELETE FROM t WHERE id = 42\").unwrap();\n\n    let rows = get_rows(&conn, \"SELECT id, val FROM t ORDER BY val DESC\");\n    assert_eq!(rows, Vec::<Vec<Value>>::new());\n\n    let rows = get_rows(&conn, \"PRAGMA integrity_check\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(&rows[0][0].to_string(), \"ok\");\n}\n\n#[test]\nfn test_delete_row_is_skipped_by_desc_explicit_index_scan() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n\n    conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY, val INTEGER)\")\n        .unwrap();\n    conn.execute(\"CREATE INDEX idx_t_val ON t(val)\").unwrap();\n    conn.execute(\"INSERT INTO t VALUES (1, 10)\").unwrap();\n    conn.execute(\"INSERT INTO t VALUES (2, 20)\").unwrap();\n    conn.execute(\"DELETE FROM t WHERE id = 2\").unwrap();\n\n    let rows = get_rows(&conn, \"SELECT id, val FROM t ORDER BY val DESC\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(rows[0][0].as_int().unwrap(), 1);\n    assert_eq!(rows[0][1].as_int().unwrap(), 10);\n\n    let rows = get_rows(&conn, \"PRAGMA integrity_check\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(&rows[0][0].to_string(), \"ok\");\n}\n\n#[test]\nfn test_delete_btree_resident_row_is_skipped_by_desc_unique_index_scan() {\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n\n    {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY, val INTEGER UNIQUE)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO t VALUES (1, 10)\").unwrap();\n        conn.execute(\"INSERT INTO t VALUES (2, 20)\").unwrap();\n        conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n    }\n\n    db.restart();\n\n    let conn = db.connect();\n    conn.execute(\"DELETE FROM t WHERE id = 2\").unwrap();\n\n    let rows = get_rows(&conn, \"SELECT id, val FROM t ORDER BY val DESC\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(rows[0][0].as_int().unwrap(), 1);\n    assert_eq!(rows[0][1].as_int().unwrap(), 10);\n\n    let rows = get_rows(&conn, \"PRAGMA integrity_check\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(&rows[0][0].to_string(), \"ok\");\n}\n\n/// Test DELETE all B-tree rows and re-insert with same IDs in MVCC.\n/// Verifies tombstones correctly shadow B-tree and new rows are visible.\n///\n/// This test was initially failing with \"UNIQUE constraint failed: t.id\"\n/// Fixed by implementing dual-peek in the exists() method to check MVCC tombstones.\n#[test]\nfn test_mvcc_dual_cursor_delete_all_btree_reinsert() {\n    let _ = tracing_subscriber::fmt::try_init();\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n\n    {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY, val TEXT)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO t VALUES (1, 'old1')\").unwrap();\n        conn.execute(\"INSERT INTO t VALUES (2, 'old2')\").unwrap();\n        conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n    }\n\n    db.restart();\n\n    let conn = db.connect();\n    // Delete all B-tree rows\n    conn.execute(\"DELETE FROM t WHERE id IN (1, 2)\").unwrap();\n    // Re-insert with new values\n    conn.execute(\"INSERT INTO t VALUES (1, 'new1')\").unwrap();\n    conn.execute(\"INSERT INTO t VALUES (2, 'new2')\").unwrap();\n\n    // Should see new values, not old B-tree values\n    let rows = get_rows(&conn, \"SELECT id, val FROM t ORDER BY id\");\n    assert_eq!(rows.len(), 2);\n    assert_eq!(rows[0][1].to_string(), \"new1\");\n    assert_eq!(rows[1][1].to_string(), \"new2\");\n}\n\n/// What this test checks: Checkpoint transitions preserve DB/WAL/log ordering and watermark updates for the tested edge case.\n/// Why this matters: Incorrect ordering breaks crash safety, replay boundaries, or durability guarantees.\n#[test]\nfn test_checkpoint_root_page_mismatch_with_index() {\n    // Strategy:\n    // 1. Create table1 with index, insert many rows to allocate many pages (e.g., pages 2-30)\n    // 2. Create table2 with index (will get negative IDs like -35, -36)\n    // 3. Insert into table2\n    // 4. Checkpoint - table2 will be allocated to pages 32, 33 (after table1's pages)\n    // 5. But schema update will do abs(-35) = 35, abs(-36) = 36 (WRONG!)\n    // 6. Query table2 using index - will look for page 36 but data is in page 33\n\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n\n    conn.execute(\"PRAGMA journal_mode = 'mvcc'\").unwrap();\n\n    // Create MULTIPLE tables to consume enough page numbers\n    // so that test_table's allocated pages diverge from abs(negative_id)\n    for table_num in 1..=30 {\n        conn.execute(format!(\n            \"CREATE TABLE tbl{table_num} (id INTEGER PRIMARY KEY, data TEXT)\",\n        ))\n        .unwrap();\n        conn.execute(format!(\n            \"CREATE INDEX idx{table_num} ON tbl{table_num}(data)\",\n        ))\n        .unwrap();\n\n        // Insert data to force page allocation\n        for i in 0..10 {\n            let data = format!(\"data_{table_num}_{i}\");\n            conn.execute(format!(\"INSERT INTO tbl{table_num} VALUES ({i}, '{data}')\",))\n                .unwrap();\n        }\n    }\n\n    println!(\"Created 30 tables with indexes and data\");\n\n    // Create test_table with UNIQUE index (auto-created for the key)\n    conn.execute(\"CREATE TABLE test_table (key TEXT PRIMARY KEY, value TEXT)\")\n        .unwrap();\n\n    // Check test_table's root pages (should be negative)\n    let rows = get_rows(\n        &conn,\n        \"SELECT name, rootpage FROM sqlite_schema WHERE tbl_name = 'test_table' ORDER BY name\",\n    );\n    let table_root: i64 = rows\n        .iter()\n        .find(|r| r[0].to_string() == \"test_table\")\n        .unwrap()[1]\n        .to_string()\n        .parse()\n        .unwrap();\n    let index_root: i64 = rows\n        .iter()\n        .find(|r| r[0].to_string().contains(\"autoindex\"))\n        .unwrap()[1]\n        .to_string()\n        .parse()\n        .unwrap();\n    assert!(\n        table_root < 0,\n        \"test_table should have negative root before checkpoint\"\n    );\n    assert!(\n        index_root < 0,\n        \"test_table index should have negative root before checkpoint\"\n    );\n\n    // Insert a row into test_table\n    conn.execute(\"INSERT INTO test_table (key, value) VALUES ('test_key', 'test_value')\")\n        .unwrap();\n\n    // Verify row exists before checkpoint\n    let rows = get_rows(&conn, \"SELECT value FROM test_table WHERE key = 'test_key'\");\n    assert_eq!(rows.len(), 1, \"Row should exist before checkpoint\");\n    assert_eq!(rows[0][0].to_string(), \"test_value\");\n\n    println!(\"Inserted row into test_table, verified it exists\");\n\n    // Run checkpoint\n    conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n\n    println!(\"Checkpoint complete\");\n\n    // Now try to query using the index - this is where the bug manifests\n    // The query will use root_page from schema (e.g., abs(index_root) if bug exists)\n    // But data is actually in the correct allocated page\n    let rows = get_rows(&conn, \"SELECT value FROM test_table WHERE key = 'test_key'\");\n\n    assert_eq!(rows.len(), 1);\n    assert_eq!(rows[0][0].to_string(), \"test_value\", \"Value should match\");\n\n    println!(\"Test passed - row found correctly after checkpoint\");\n}\n\n/// What this test checks: Checkpoint transitions preserve DB/WAL/log ordering and watermark updates for the tested edge case.\n/// Why this matters: Incorrect ordering breaks crash safety, replay boundaries, or durability guarantees.\n#[test]\nfn test_checkpoint_drop_table() {\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n\n    conn.execute(\"CREATE TABLE t (id INTEGER PRIMARY KEY, data TEXT)\")\n        .unwrap();\n    conn.execute(\"CREATE INDEX idx_t_data ON t(data)\").unwrap();\n\n    // Insert data to force page allocation\n    for i in 0..10 {\n        let data = format!(\"data_{i}\");\n        conn.execute(format!(\"INSERT INTO t VALUES ({i}, '{data}')\",))\n            .unwrap();\n    }\n    conn.execute(\"DROP TABLE t\").unwrap();\n    conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n    drop(conn);\n\n    db.restart();\n\n    let conn = db.connect();\n    let rows = get_rows(&conn, \"PRAGMA integrity_check\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(&rows[0][0].to_string(), \"ok\");\n}\n\n/// Test that inserting a duplicate primary key fails when the existing row\n/// was committed before this transaction started (and thus is visible).\n#[test]\nfn test_mvcc_same_primary_key() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n\n    conn.execute(\"CREATE TABLE t (id INTEGER PRIMARY KEY)\")\n        .unwrap();\n    let conn2 = db.connect();\n\n    conn.execute(\"BEGIN CONCURRENT\").unwrap();\n    conn.execute(\"INSERT INTO t VALUES (666)\").unwrap();\n    conn.execute(\"COMMIT\").unwrap();\n\n    // conn2 starts AFTER conn1 committed, so conn2 can see the committed row.\n    // INSERT should fail with UNIQUE constraint because the row is visible.\n    conn2.execute(\"BEGIN CONCURRENT\").unwrap();\n    conn2\n        .execute(\"INSERT INTO t VALUES (666)\")\n        .expect_err(\"duplicate key - visible committed row\");\n}\n\n/// What this test checks: MVCC transaction visibility and conflict handling follow the intended isolation behavior.\n/// Why this matters: Concurrency bugs are correctness bugs: they create anomalies users can observe as wrong query results.\n#[test]\nfn test_mvcc_same_primary_key_concurrent() {\n    // Pure optimistic concurrency: both transactions can INSERT the same rowid,\n    // but only one can commit (first-committer-wins based on end_ts comparison).\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n\n    conn.execute(\"CREATE TABLE t (id INTEGER PRIMARY KEY)\")\n        .unwrap();\n    let conn2 = db.connect();\n\n    conn.execute(\"BEGIN CONCURRENT\").unwrap();\n    conn.execute(\"INSERT INTO t VALUES (666)\").unwrap();\n\n    conn2.execute(\"BEGIN CONCURRENT\").unwrap();\n    // With pure optimistic CC, INSERT succeeds - conflict detected at commit time\n    conn2.execute(\"INSERT INTO t VALUES (666)\").unwrap();\n\n    // First transaction commits successfully (gets lower end_ts)\n    conn.execute(\"COMMIT\").unwrap();\n\n    // Second transaction fails at commit time (first-committer-wins)\n    conn2\n        .execute(\"COMMIT\")\n        .expect_err(\"duplicate key - first committer wins\");\n}\n\n// ─── End-to-end GC + dual cursor tests ───────────────────────────────────\n\n/// After checkpoint + GC, checkpointed current versions are removed from\n/// the SkipMap. Readers must still see the data via B-tree fallthrough.\n#[test]\nfn test_gc_e2e_checkpointed_row_readable_after_gc() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n    conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY, val TEXT)\")\n        .unwrap();\n    conn.execute(\"INSERT INTO t VALUES (1, 'hello')\").unwrap();\n    conn.execute(\"INSERT INTO t VALUES (2, 'world')\").unwrap();\n\n    // Checkpoint flushes to B-tree and triggers GC.\n    conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n\n    // After GC, the SkipMap entries should be cleared (sole-survivor rule 3),\n    // and reads fall through to B-tree.\n    let rows = get_rows(&conn, \"SELECT id, val FROM t ORDER BY id\");\n    assert_eq!(rows.len(), 2);\n    assert_eq!(rows[0][0].as_int().unwrap(), 1);\n    assert_eq!(rows[0][1].to_string(), \"hello\");\n    assert_eq!(rows[1][0].as_int().unwrap(), 2);\n    assert_eq!(rows[1][1].to_string(), \"world\");\n\n    let rows = get_rows(&conn, \"PRAGMA integrity_check\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(&rows[0][0].to_string(), \"ok\");\n}\n\n/// After deleting a B-tree row and checkpointing, the tombstone is removed\n/// by GC. The deleted row must stay invisible (B-tree no longer has it).\n#[test]\nfn test_gc_e2e_deleted_row_stays_hidden_after_gc() {\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n    {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY, val TEXT)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO t VALUES (1, 'keep')\").unwrap();\n        conn.execute(\"INSERT INTO t VALUES (2, 'delete_me')\")\n            .unwrap();\n        conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n    }\n\n    // Restart so rows are only in B-tree.\n    db.restart();\n    let conn = db.connect();\n\n    // Delete row 2 in MVCC (creates tombstone over B-tree row).\n    conn.execute(\"DELETE FROM t WHERE id = 2\").unwrap();\n\n    // Checkpoint writes the deletion to B-tree and GC removes the tombstone.\n    conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n\n    // Row 2 must remain invisible.\n    let rows = get_rows(&conn, \"SELECT id, val FROM t ORDER BY id\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(rows[0][0].as_int().unwrap(), 1);\n    assert_eq!(rows[0][1].to_string(), \"keep\");\n\n    let rows = get_rows(&conn, \"PRAGMA integrity_check\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(&rows[0][0].to_string(), \"ok\");\n}\n\n/// After updating a B-tree row and checkpointing, GC removes old versions.\n/// The updated value must be visible (from B-tree after GC).\n#[test]\nfn test_gc_e2e_updated_row_correct_after_gc() {\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n    {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY, val TEXT)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO t VALUES (1, 'original')\")\n            .unwrap();\n        conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n    }\n\n    db.restart();\n    let conn = db.connect();\n\n    // Update in MVCC.\n    conn.execute(\"UPDATE t SET val = 'updated' WHERE id = 1\")\n        .unwrap();\n\n    // Checkpoint + GC.\n    conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n\n    // Must see updated value.\n    let rows = get_rows(&conn, \"SELECT val FROM t WHERE id = 1\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(rows[0][0].to_string(), \"updated\");\n\n    let rows = get_rows(&conn, \"PRAGMA integrity_check\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(&rows[0][0].to_string(), \"ok\");\n}\n\n/// Multiple checkpoints with interleaved writes. Each checkpoint triggers GC.\n/// Verifies cumulative correctness across GC cycles.\n#[test]\nfn test_gc_e2e_multiple_checkpoint_gc_cycles() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n    conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY, val INTEGER)\")\n        .unwrap();\n\n    for i in 1..=5 {\n        conn.execute(format!(\"INSERT INTO t VALUES ({i}, {i})\"))\n            .unwrap();\n    }\n    conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n\n    // Delete rows 2, 4 and update row 3.\n    conn.execute(\"DELETE FROM t WHERE id IN (2, 4)\").unwrap();\n    conn.execute(\"UPDATE t SET val = 30 WHERE id = 3\").unwrap();\n    conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n\n    // Insert row 6, delete row 1.\n    conn.execute(\"INSERT INTO t VALUES (6, 6)\").unwrap();\n    conn.execute(\"DELETE FROM t WHERE id = 1\").unwrap();\n    conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n\n    let rows = get_rows(&conn, \"SELECT id, val FROM t ORDER BY id\");\n    assert_eq!(rows.len(), 3);\n    assert_eq!(rows[0][0].as_int().unwrap(), 3);\n    assert_eq!(rows[0][1].as_int().unwrap(), 30);\n    assert_eq!(rows[1][0].as_int().unwrap(), 5);\n    assert_eq!(rows[1][1].as_int().unwrap(), 5);\n    assert_eq!(rows[2][0].as_int().unwrap(), 6);\n    assert_eq!(rows[2][1].as_int().unwrap(), 6);\n\n    let rows = get_rows(&conn, \"PRAGMA integrity_check\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(&rows[0][0].to_string(), \"ok\");\n}\n\n#[test]\nfn test_mvcc_unique_constraint() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n\n    conn.execute(\"CREATE TABLE t (id UNIQUE)\").unwrap();\n    let conn2 = db.connect();\n\n    conn.execute(\"BEGIN CONCURRENT\").unwrap();\n    conn.execute(\"INSERT INTO t VALUES (666)\").unwrap();\n\n    conn2.execute(\"BEGIN CONCURRENT\").unwrap();\n    conn2.execute(\"INSERT INTO t VALUES (666)\").unwrap();\n\n    conn.execute(\"COMMIT\").unwrap();\n    // conn2 should see conflict with conn1's row where first conneciton changed `begin` to a Timestamp that is < than conn2's end_ts\n    conn2\n        .execute(\"COMMIT\")\n        .expect_err(\"duplicate unique - first committer wins\");\n}\n\n/// Regression test for MVCC concurrent commit yield-spin deadlock.\n///\n/// When the VDBE encounters a yield completion (pager_commit_lock contention),\n/// it must return StepResult::IO to yield control. Previously, it checked\n/// `finished()` which is always true for yield completions, causing an infinite\n/// spin inside a single step() call — deadlocking cooperative schedulers.\n///\n/// We simulate lock contention by pre-acquiring pager_commit_lock before\n/// calling COMMIT, then verify step() returns IO instead of hanging.\n#[test]\nfn test_concurrent_commit_yield_spin() {\n    let db = MvccTestDbNoConn::new();\n    let conn = db.connect();\n\n    conn.execute(\"CREATE TABLE t (id INTEGER PRIMARY KEY, v TEXT)\")\n        .unwrap();\n\n    conn.execute(\"BEGIN CONCURRENT\").unwrap();\n    conn.execute(\"INSERT INTO t VALUES (1, 'a')\").unwrap();\n\n    // Pre-acquire the pager_commit_lock to simulate another connection\n    // holding it mid-commit.\n    let mv_store = db.get_mvcc_store();\n    let lock = &mv_store.commit_coordinator.pager_commit_lock;\n    assert!(lock.write(), \"should acquire lock\");\n\n    // Prepare COMMIT — step() should yield (return IO), not spin forever.\n    let mut stmt = conn.prepare(\"COMMIT\").unwrap();\n    let mut returned_io = false;\n    for _ in 0..100 {\n        match stmt.step().unwrap() {\n            crate::StepResult::IO => {\n                returned_io = true;\n                break;\n            }\n            crate::StepResult::Done => break,\n            _ => {}\n        }\n    }\n    assert!(\n        returned_io,\n        \"step() should return IO when pager_commit_lock is contended\"\n    );\n\n    // Release the lock and let the commit finish\n    lock.unlock();\n    loop {\n        match stmt.step().unwrap() {\n            crate::StepResult::Done => break,\n            crate::StepResult::IO => {}\n            _ => {}\n        }\n    }\n\n    // Verify the insert is visible\n    let rows = get_rows(&conn, \"SELECT COUNT(*) FROM t\");\n    assert_eq!(rows[0][0].as_int().unwrap(), 1);\n}\n\nfn abandon_commit_after_first_io(conn: &Arc<Connection>, mv_store: &Arc<MvStore<MvccClock>>) {\n    let lock = &mv_store.commit_coordinator.pager_commit_lock;\n    assert!(lock.write(), \"should acquire commit lock\");\n\n    let mut stmt = conn.prepare(\"COMMIT\").unwrap();\n    assert!(\n        matches!(stmt.step().unwrap(), crate::StepResult::IO),\n        \"COMMIT should yield while the commit lock is held\",\n    );\n\n    drop(stmt);\n    lock.unlock();\n    conn.close().unwrap();\n}\n\n/// if a txn made some inserts, then aborted (or abandoned due to some IO issue), then those\n/// inserted rows should not be visible\n#[test]\nfn test_abandoned_commit_rolls_back_insert() {\n    let db = MvccTestDbNoConn::new();\n    let conn = db.connect();\n\n    conn.execute(\"CREATE TABLE t (id INTEGER PRIMARY KEY, v TEXT)\")\n        .unwrap();\n    conn.execute(\"BEGIN CONCURRENT\").unwrap();\n    conn.execute(\"INSERT INTO t VALUES (1, 'new')\").unwrap();\n\n    let mv_store = db.get_mvcc_store();\n    abandon_commit_after_first_io(&conn, &mv_store);\n\n    let observer = db.connect();\n    let rows = get_rows(&observer, \"SELECT id FROM t WHERE id = 1\");\n    assert!(\n        rows.is_empty(),\n        \"row from abandoned INSERT commit remained visible: {rows:?}\",\n    );\n    observer.close().unwrap();\n}\n\n/// if a txn deleted some existing rows, but then aborted (or abandoned due to some IO issue), then\n/// those rows should not become deleted\n#[test]\nfn test_abandoned_commit_rolls_back_delete() {\n    let db = MvccTestDbNoConn::new();\n    let conn = db.connect();\n\n    conn.execute(\"CREATE TABLE t (id INTEGER PRIMARY KEY, v TEXT)\")\n        .unwrap();\n    conn.execute(\"INSERT INTO t VALUES (1, 'seed')\").unwrap();\n    conn.execute(\"BEGIN CONCURRENT\").unwrap();\n    conn.execute(\"DELETE FROM t WHERE id = 1\").unwrap();\n\n    let mv_store = db.get_mvcc_store();\n    abandon_commit_after_first_io(&conn, &mv_store);\n\n    let observer = db.connect();\n    let rows = get_rows(&observer, \"SELECT id, v FROM t WHERE id = 1\");\n    assert_eq!(\n        rows,\n        vec![vec![\n            Value::Numeric(Numeric::Integer(1)),\n            Value::Text(Text::new(\"seed\".to_string())),\n        ]],\n        \"row disappeared after abandoned DELETE commit: {rows:?}\",\n    );\n    observer.close().unwrap();\n}\n\n/// ALTER TABLE RENAME TO on a table with a CREATE INDEX panics on the next\n/// session open. Reproduces the issue with 3 separate sessions (DB restarts).\n#[test]\nfn test_alter_table_rename_with_index_panics_on_restart() {\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n    // Session 1: Create indexed table + checkpoint\n    {\n        let conn = db.connect();\n        conn.execute(\"PRAGMA mvcc_checkpoint_threshold = 1\")\n            .unwrap();\n        conn.execute(\"CREATE TABLE old_name(id INTEGER PRIMARY KEY, val TEXT)\")\n            .unwrap();\n        conn.execute(\"CREATE INDEX idx_val ON old_name(val)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO old_name VALUES (1, 'a')\")\n            .unwrap();\n        conn.close().unwrap();\n    }\n    // Session 2: Rename table\n    db.restart();\n    {\n        let conn = db.connect();\n        conn.execute(\"PRAGMA journal_mode = 'mvcc'\").unwrap();\n        conn.execute(\"ALTER TABLE old_name RENAME TO new_name\")\n            .unwrap();\n        conn.close().unwrap();\n    }\n    // Session 3: PANIC\n    db.restart();\n    {\n        let conn = db.connect();\n        conn.execute(\"PRAGMA journal_mode = 'mvcc'\").unwrap();\n        let rows = get_rows(&conn, \"SELECT * FROM new_name\");\n        assert_eq!(rows.len(), 1);\n    }\n}\n\n/// Same as above but with a UNIQUE constraint (autoindex).\n#[test]\nfn test_alter_table_rename_with_unique_constraint_panics_on_restart() {\n    let _ = tracing_subscriber::fmt::try_init();\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n    // Session 1\n    {\n        let conn = db.connect();\n        conn.execute(\"PRAGMA mvcc_checkpoint_threshold = 1\")\n            .unwrap();\n        conn.execute(\"CREATE TABLE old_name(id INTEGER PRIMARY KEY, val TEXT UNIQUE)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO old_name VALUES (1, 'a')\")\n            .unwrap();\n        conn.close().unwrap();\n    }\n    // Session 2\n    db.restart();\n    {\n        let conn = db.connect();\n        conn.execute(\"PRAGMA journal_mode = 'mvcc'\").unwrap();\n        conn.execute(\"ALTER TABLE old_name RENAME TO new_name\")\n            .unwrap();\n        conn.close().unwrap();\n    }\n    // Session 3: PANIC\n    db.restart();\n    {\n        let conn = db.connect();\n        conn.execute(\"PRAGMA journal_mode = 'mvcc'\").unwrap();\n        let rows = get_rows(&conn, \"SELECT * FROM new_name\");\n        assert_eq!(rows.len(), 1);\n    }\n}\n\n/// Reproducer: DROP TABLE ghost data after restart without explicit checkpoint.\n/// Session 1: create + insert + checkpoint. Session 2: drop. Session 3: reopen.\n#[test]\nfn test_close_persists_drop_table() {\n    // Session 1: create table, insert data, checkpoint to DB file\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n    conn.execute(\"CREATE TABLE todrop(id INTEGER PRIMARY KEY, val TEXT)\")\n        .unwrap();\n    conn.execute(\"INSERT INTO todrop VALUES (1, 'data')\")\n        .unwrap();\n    conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n    conn.close().unwrap();\n\n    // Session 2: drop table (no explicit checkpoint, rely on close)\n    let conn = db.connect();\n    conn.execute(\"DROP TABLE todrop\").unwrap();\n    conn.close().unwrap();\n\n    // Session 3: reopen — table must be gone\n    db.restart();\n    let conn = db.connect();\n\n    // The table must not exist — CREATE should succeed\n    let create_result = conn.execute(\"CREATE TABLE todrop(id INTEGER PRIMARY KEY, newval TEXT)\");\n    assert!(\n        create_result.is_ok(),\n        \"CREATE TABLE should succeed after DROP, but got: {:?}\",\n        create_result.unwrap_err()\n    );\n\n    // No ghost data from old table\n    let rows = get_rows(&conn, \"SELECT * FROM todrop\");\n    assert!(rows.is_empty(), \"New table should be empty, got {rows:?}\");\n\n    let rows = get_rows(&conn, \"PRAGMA integrity_check\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(&rows[0][0].to_string(), \"ok\");\n}\n\n/// Reproducer: DROP INDEX ghost pages after restart without explicit checkpoint.\n/// Session 1: create table + index + insert + checkpoint. Session 2: drop index. Session 3: reopen.\n#[test]\nfn test_close_persists_drop_index() {\n    // Session 1: create table/index, insert data, checkpoint to DB file\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n    conn.execute(\"CREATE TABLE tdropidx(id INTEGER PRIMARY KEY, val TEXT)\")\n        .unwrap();\n    conn.execute(\"CREATE INDEX idx_tdropidx_val ON tdropidx(val)\")\n        .unwrap();\n    conn.execute(\"INSERT INTO tdropidx VALUES (1, 'data')\")\n        .unwrap();\n    conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n    conn.close().unwrap();\n\n    // Session 2: drop index (no explicit checkpoint, rely on close)\n    let conn = db.connect();\n    conn.execute(\"DROP INDEX idx_tdropidx_val\").unwrap();\n    conn.close().unwrap();\n\n    // Session 3: reopen - index must be gone and integrity check must pass\n    db.restart();\n    let conn = db.connect();\n\n    let recreate_index = conn.execute(\"CREATE INDEX idx_tdropidx_val ON tdropidx(val)\");\n    assert!(\n        recreate_index.is_ok(),\n        \"CREATE INDEX should succeed after DROP INDEX, but got: {:?}\",\n        recreate_index.unwrap_err()\n    );\n\n    let rows = get_rows(&conn, \"PRAGMA integrity_check\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(&rows[0][0].to_string(), \"ok\");\n}\n\n#[test]\nfn test_partial_commit_visibility_bug() {\n    use crate::sync::atomic::{AtomicBool, AtomicU64, Ordering};\n    use crate::sync::Arc;\n    use std::collections::HashMap;\n    use std::thread;\n    use std::time::Duration;\n    for _ in 0..10 {\n        // Setup: Create a table with batch_id and row_num columns\n        let db = Arc::new(MvccTestDbNoConn::new_with_random_db());\n        {\n            let conn = db.connect();\n            conn.execute(\"CREATE TABLE consistency_test (batch_id INTEGER, row_num INTEGER)\")\n                .unwrap();\n        }\n\n        const ROWS_PER_BATCH: i64 = 50; // Large enough to increase race window\n        const NUM_BATCHES: u64 = 100;\n        const NUM_READER_THREADS: usize = 4;\n\n        let writer_done = Arc::new(AtomicBool::new(false));\n        let violation_detected = Arc::new(AtomicBool::new(false));\n        let current_batch = Arc::new(AtomicU64::new(0));\n\n        // Writer thread: Insert batches of rows\n        let writer_handle = {\n            let db = db.clone();\n            let writer_done = writer_done.clone();\n            let current_batch = current_batch.clone();\n            thread::spawn(move || {\n                let conn = db.connect();\n\n                for batch_id in 0..NUM_BATCHES {\n                    // Start a transaction\n                    conn.execute(\"BEGIN CONCURRENT\").unwrap();\n\n                    // Insert ROWS_PER_BATCH rows with the same batch_id\n                    // This simulates a multi-row operation like a bank transfer\n                    for row_num in 0..ROWS_PER_BATCH {\n                        conn.execute(format!(\n                            \"INSERT INTO consistency_test VALUES ({batch_id}, {row_num})\",\n                        ))\n                        .unwrap();\n                    }\n\n                    // Update current batch before committing to allow readers to check\n                    current_batch.store(batch_id, Ordering::Release);\n\n                    // Commit the transaction\n                    // BUG LOCATION: During commit, the loop at mod.rs:912-984 updates\n                    // row timestamps one-by-one while state remains Preparing.\n                    // Concurrent readers can see partial updates.\n                    conn.execute(\"COMMIT\").unwrap();\n\n                    // Small delay to allow readers to observe the race window\n                    thread::sleep(Duration::from_micros(100));\n                }\n\n                writer_done.store(true, Ordering::Release);\n            })\n        };\n\n        // Reader threads: Continuously read and verify batch consistency\n        let mut reader_handles = Vec::new();\n        for reader_id in 0..NUM_READER_THREADS {\n            let db = db.clone();\n            let writer_done = writer_done.clone();\n            let violation_detected = violation_detected.clone();\n            let current_batch = current_batch.clone();\n\n            let handle = thread::spawn(move || {\n                let conn = db.connect();\n                let mut iteration = 0u64;\n\n                loop {\n                    iteration += 1;\n\n                    // Start a new transaction to get a fresh snapshot\n                    // Snapshot isolation: This snapshot should see a consistent state\n                    conn.execute(\"BEGIN CONCURRENT\").unwrap();\n\n                    // Read all rows grouped by batch_id\n                    let rows = get_rows(\n                        &conn,\n                        \"SELECT batch_id, row_num FROM consistency_test ORDER BY batch_id, row_num\",\n                    );\n\n                    // Group rows by batch_id\n                    let mut batches: HashMap<i64, Vec<i64>> = HashMap::new();\n                    for row in rows {\n                        let batch_id = row[0].as_int().unwrap();\n                        let row_num = row[1].as_int().unwrap();\n                        batches.entry(batch_id).or_default().push(row_num);\n                    }\n\n                    // Check consistency: Each batch must have EITHER all rows OR no rows\n                    for (batch_id, row_nums) in &batches {\n                        let count = row_nums.len() as i64;\n\n                        // CRITICAL ASSERTION: Snapshot isolation guarantees atomic visibility\n                        // A batch is either fully committed (all 50 rows) or not yet committed (0 rows)\n                        //\n                        // If we see a partial batch (e.g., 23 rows), it means:\n                        // 1. The commit loop updated timestamps for rows 0-22 (visible)\n                        // 2. Transaction still in Preparing state\n                        // 3. Rows 23-49 still have TxID (invisible to us)\n                        // 4. We started our snapshot DURING the commit loop\n                        //\n                        // This is a SNAPSHOT ISOLATION VIOLATION.\n                        if count != 0 && count != ROWS_PER_BATCH {\n                            eprintln!(\n                                \"[Reader {reader_id}] VIOLATION DETECTED at iteration {iteration}!\",\n                            );\n                            eprintln!(\n                                \"  Batch {batch_id} has {count} rows (expected {ROWS_PER_BATCH} or 0)\",\n                            );\n                            eprintln!(\"  Visible row_nums: {row_nums:?}\");\n                            eprintln!();\n                            eprintln!(\"  EXPLANATION:\");\n                            eprintln!(\n                                \"  - This reader started a snapshot during batch {batch_id}'s commit\",\n                            );\n                            eprintln!(\n                                \"  - The commit loop (mod.rs:912-984) was updating timestamps\"\n                            );\n                            eprintln!(\"  - Transaction state was still Preparing(ts)\");\n                            eprintln!(\"  - Rows with updated Timestamps became visible\");\n                            eprintln!(\"  - Rows with TxID timestamps remained invisible\");\n                            eprintln!(\"  - Result: Partial batch visibility (atomicity violation)\");\n                            eprintln!();\n                            eprintln!(\"  RACE TIMELINE:\");\n                            eprintln!(\"  1. Writer: state = Preparing(end_ts)\");\n                            eprintln!(\"  2. Writer: Update row 0's timestamp\");\n                            eprintln!(\"  3. Writer: Update row 1's timestamp\");\n                            eprintln!(\"  ...\");\n                            eprintln!(\"  N. Reader: BEGIN (snapshot)\");\n                            eprintln!(\n                                \"  N+1. Reader: Read rows 0-{} (visible via Timestamp)\",\n                                count - 1\n                            );\n                            eprintln!(\n                                \"  N+2. Reader: Read rows {}-{} (invisible, still TxID)\",\n                                count,\n                                ROWS_PER_BATCH - 1\n                            );\n                            eprintln!(\"  N+3. Writer: Continue updating remaining timestamps...\");\n\n                            violation_detected.store(true, Ordering::Release);\n\n                            // Continue to accumulate more evidence\n                        }\n                    }\n\n                    conn.execute(\"COMMIT\").unwrap();\n\n                    // Exit if writer is done and we've checked a few more times\n                    if writer_done.load(Ordering::Acquire) {\n                        let final_batch = current_batch.load(Ordering::Acquire);\n                        if iteration > final_batch + 10 {\n                            break;\n                        }\n                    }\n\n                    // Small delay to vary timing\n                    thread::sleep(Duration::from_micros(50));\n                }\n\n                eprintln!(\"[Reader {reader_id}] Completed {iteration} iterations\");\n            });\n\n            reader_handles.push(handle);\n        }\n\n        // Wait for writer to complete\n        writer_handle.join().unwrap();\n\n        // Wait for readers to complete\n        for handle in reader_handles {\n            handle.join().unwrap();\n        }\n\n        // ASSERTION: No violations should be detected\n        // With the current bug, this will FAIL because readers observe partial commits\n        assert!(\n            !violation_detected.load(Ordering::Acquire),\n            \"Partial commit visibility detected! Transaction atomicity violated.\\n\\\n         \\n\\\n         ROOT CAUSE: Commit loop (mod.rs:912-984) updates row timestamps non-atomically\\n\\\n         while transaction state remains Preparing. Concurrent readers see inconsistent\\n\\\n         snapshots with partial transaction visibility.\\n\\\n         \\n\\\n         FIX REQUIRED: Make timestamp updates atomic, or change visibility logic to\\n\\\n         always dereference transaction state instead of reading row timestamps directly.\"\n        );\n    }\n}\n\n/// Two concurrent transactions delete the same B-tree-resident row that has a\n/// UNIQUE index. Both DELETEs succeed at execute time because tombstones\n/// (begin: None) are invisible to is_visible_to(), so both transactions\n/// create independent tombstones. However, commit-time validation in\n/// check_version_conflicts detects the other transaction's tombstone as a\n/// write lock (via its end: TxID field) and rejects the second committer\n/// with WriteWriteConflict.\n#[test]\nfn test_double_delete_btree_resident_row_with_unique_index() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n\n    conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY, val INTEGER, uniq TEXT UNIQUE)\")\n        .unwrap();\n    conn.execute(\"INSERT INTO t VALUES(1, 10, 'a')\").unwrap();\n    conn.execute(\"INSERT INTO t VALUES(2, 20, 'b')\").unwrap();\n\n    // Checkpoint so rows are only in B-tree, not in MVCC store\n    conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n    drop(conn);\n\n    // Two transactions both try to delete the same B-tree-resident row\n    let conn1 = db.connect();\n    let conn2 = db.connect();\n\n    conn1.execute(\"BEGIN CONCURRENT\").unwrap();\n    conn2.execute(\"BEGIN CONCURRENT\").unwrap();\n\n    // T1 deletes row 1 — creates tombstone (begin: None, end: TxID(T1))\n    conn1.execute(\"DELETE FROM t WHERE id = 1\").unwrap();\n\n    // T2 deletes the same row — creates a second tombstone at execute time\n    // (is_visible_to still returns false for tombstones, so operation-time\n    // conflict detection is bypassed — that's a separate issue)\n    conn2.execute(\"DELETE FROM t WHERE id = 1\").unwrap();\n\n    // T1 commits first — stamps its tombstones with Timestamp\n    conn1.execute(\"COMMIT\").unwrap();\n\n    // T2's commit should fail: check_version_conflicts now detects T1's\n    // committed tombstone (end: Timestamp >= T2.begin_ts)\n    assert!(\n        conn2.execute(\"COMMIT\").is_err(),\n        \"T2's COMMIT should fail with WriteWriteConflict when T1 already \\\n         committed a tombstone for the same row\"\n    );\n    drop(conn1);\n    drop(conn2);\n\n    // Checkpoint: only T1's delete should have gone through\n    let conn = db.connect();\n    conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n\n    let rows = get_rows(&conn, \"PRAGMA integrity_check\");\n    assert_eq!(rows.len(), 1);\n    assert_eq!(\n        &rows[0][0].to_string(),\n        \"ok\",\n        \"Index corruption after concurrent double-delete of B-tree-resident row\"\n    );\n}\n\n/// AUTOINCREMENT is not supported in MVCC mode due to sqlite_sequence\n/// corruption with concurrent transactions. Verify that CREATE TABLE\n/// with AUTOINCREMENT and INSERT into AUTOINCREMENT tables are blocked.\n#[test]\nfn test_autoincrement_blocked_in_mvcc() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n\n    // CREATE TABLE with AUTOINCREMENT should fail in MVCC mode\n    let result = conn.execute(\"CREATE TABLE t(a INTEGER PRIMARY KEY AUTOINCREMENT, b TEXT)\");\n    assert!(\n        result.is_err(),\n        \"CREATE TABLE with AUTOINCREMENT should fail in MVCC mode\"\n    );\n    let err = result.unwrap_err().to_string();\n    assert!(\n        err.contains(\"AUTOINCREMENT is not supported in MVCC mode\"),\n        \"unexpected error: {err}\"\n    );\n\n    // Regular tables without AUTOINCREMENT should still work\n    conn.execute(\"CREATE TABLE t(a INTEGER PRIMARY KEY, b TEXT)\")\n        .unwrap();\n    conn.execute(\"INSERT INTO t VALUES (1, 'hello')\").unwrap();\n    let rows = get_rows(&conn, \"SELECT * FROM t\");\n    assert_eq!(rows.len(), 1);\n}\n\n/// If a table with AUTOINCREMENT was created before MVCC was enabled,\n/// INSERT into that table should still be blocked in MVCC mode.\n#[test]\nfn test_autoincrement_insert_blocked_for_preexisting_table() {\n    let temp_dir = tempfile::TempDir::new().unwrap();\n    let path = temp_dir\n        .path()\n        .join(format!(\"test_{}\", rand::random::<u64>()));\n    let path_str = path.to_str().unwrap();\n    let io = Arc::new(PlatformIO::new().unwrap());\n\n    // Phase 1: Open in WAL mode, create AUTOINCREMENT table\n    {\n        let db = crate::Database::open_file_with_flags(\n            io.clone(),\n            path_str,\n            OpenFlags::default(),\n            DatabaseOpts::new(),\n            None,\n        )\n        .unwrap();\n        let conn = db.connect().unwrap();\n        conn.execute(\"CREATE TABLE t(a INTEGER PRIMARY KEY AUTOINCREMENT, b TEXT)\")\n            .unwrap();\n        conn.execute(\"INSERT INTO t(b) VALUES ('before_mvcc')\")\n            .unwrap();\n        conn.close().unwrap();\n    }\n\n    // Clear the database manager to force a fresh open\n    {\n        let mut manager = crate::DATABASE_MANAGER.lock();\n        manager.clear();\n    }\n\n    // Phase 2: Reopen in MVCC mode — INSERT should be blocked\n    {\n        let db = crate::Database::open_file_with_flags(\n            io,\n            path_str,\n            OpenFlags::default(),\n            DatabaseOpts::new(),\n            None,\n        )\n        .unwrap();\n        let conn = db.connect().unwrap();\n        conn.execute(\"PRAGMA journal_mode = 'experimental_mvcc'\")\n            .unwrap();\n\n        let result = conn.execute(\"INSERT INTO t(b) VALUES ('in_mvcc')\");\n        assert!(\n            result.is_err(),\n            \"INSERT into AUTOINCREMENT table should fail in MVCC mode\"\n        );\n        let err = result.unwrap_err().to_string();\n        assert!(\n            err.contains(\"AUTOINCREMENT is not supported in MVCC mode\"),\n            \"unexpected error: {err}\"\n        );\n    }\n}\n\n/// Two concurrent MVCC transactions inserting into an AUTOINCREMENT table must\n/// both succeed. Before the fix, the second transaction would fail with a\n/// WriteWriteConflict on the sqlite_sequence metadata table.\n#[test]\n#[ignore = \"AUTOINCREMENT not yet supported in MVCC mode\"]\nfn test_concurrent_autoincrement_inserts() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn1 = db.connect();\n\n    conn1\n        .execute(\"CREATE TABLE t(a INTEGER PRIMARY KEY AUTOINCREMENT, b TEXT)\")\n        .unwrap();\n\n    // Tx1: begin and insert\n    conn1.execute(\"BEGIN CONCURRENT\").unwrap();\n    conn1\n        .execute(\"INSERT INTO t(b) VALUES ('from_tx1')\")\n        .unwrap();\n\n    // Tx2: begin and insert (while tx1 is still open)\n    let conn2 = db.connect();\n    conn2.execute(\"BEGIN CONCURRENT\").unwrap();\n    conn2\n        .execute(\"INSERT INTO t(b) VALUES ('from_tx2')\")\n        .unwrap();\n\n    // Both commits must succeed\n    conn1.execute(\"COMMIT\").unwrap();\n    conn2.execute(\"COMMIT\").unwrap();\n\n    // Verify both rows are present with distinct, increasing rowids\n    let rows = get_rows(&conn1, \"SELECT a, b FROM t ORDER BY a\");\n    assert_eq!(rows.len(), 2, \"both inserts should be visible\");\n    let rowid1 = rows[0][0].as_int().unwrap();\n    let rowid2 = rows[1][0].as_int().unwrap();\n    assert!(rowid1 < rowid2, \"rowids must be strictly increasing\");\n    assert_eq!(rows[0][1].to_string(), \"from_tx1\");\n    assert_eq!(rows[1][1].to_string(), \"from_tx2\");\n}\n\n/// After concurrent autoincrement inserts and a checkpoint, sqlite_sequence\n/// must reflect the true maximum rowid.\n#[test]\n#[ignore = \"AUTOINCREMENT not yet supported in MVCC mode\"]\nfn test_autoincrement_sqlite_sequence_after_checkpoint() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn1 = db.connect();\n\n    conn1\n        .execute(\"CREATE TABLE t(a INTEGER PRIMARY KEY AUTOINCREMENT, b TEXT)\")\n        .unwrap();\n\n    // Insert several rows from separate transactions\n    conn1.execute(\"BEGIN CONCURRENT\").unwrap();\n    conn1.execute(\"INSERT INTO t(b) VALUES ('row1')\").unwrap();\n    conn1.execute(\"COMMIT\").unwrap();\n\n    let conn2 = db.connect();\n    conn2.execute(\"BEGIN CONCURRENT\").unwrap();\n    conn2.execute(\"INSERT INTO t(b) VALUES ('row2')\").unwrap();\n    conn2.execute(\"COMMIT\").unwrap();\n\n    // Force a checkpoint to flush autoincrement entries\n    conn1.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n\n    // After checkpoint, sqlite_sequence should have the correct max (2)\n    let rows = get_rows(&conn1, \"SELECT seq FROM sqlite_sequence WHERE name = 't'\");\n    assert_eq!(rows.len(), 1, \"sqlite_sequence should have entry for 't'\");\n    let seq = rows[0][0].as_int().unwrap();\n    assert_eq!(seq, 2, \"sqlite_sequence should reflect the max rowid\");\n\n    // A subsequent insert should get rowid 3\n    conn1.execute(\"INSERT INTO t(b) VALUES ('row3')\").unwrap();\n    let rows = get_rows(&conn1, \"SELECT MAX(a) FROM t\");\n    assert_eq!(rows[0][0].as_int().unwrap(), 3);\n}\n\n/// Three concurrent transactions all inserting into the same AUTOINCREMENT table\n/// must all succeed and produce unique, increasing rowids.\n#[test]\n#[ignore = \"AUTOINCREMENT not yet supported in MVCC mode\"]\nfn test_three_concurrent_autoincrement_inserts() {\n    let db = MvccTestDbNoConn::new_with_random_db();\n    let conn = db.connect();\n\n    conn.execute(\"CREATE TABLE t(a INTEGER PRIMARY KEY AUTOINCREMENT, b TEXT)\")\n        .unwrap();\n\n    let conn1 = db.connect();\n    let conn2 = db.connect();\n    let conn3 = db.connect();\n\n    conn1.execute(\"BEGIN CONCURRENT\").unwrap();\n    conn1.execute(\"INSERT INTO t(b) VALUES ('tx1')\").unwrap();\n\n    conn2.execute(\"BEGIN CONCURRENT\").unwrap();\n    conn2.execute(\"INSERT INTO t(b) VALUES ('tx2')\").unwrap();\n\n    conn3.execute(\"BEGIN CONCURRENT\").unwrap();\n    conn3.execute(\"INSERT INTO t(b) VALUES ('tx3')\").unwrap();\n\n    conn1.execute(\"COMMIT\").unwrap();\n    conn2.execute(\"COMMIT\").unwrap();\n    conn3.execute(\"COMMIT\").unwrap();\n\n    let rows = get_rows(&conn, \"SELECT a FROM t ORDER BY a\");\n    assert_eq!(rows.len(), 3, \"all three inserts should be visible\");\n    let ids: Vec<i64> = rows.iter().map(|r| r[0].as_int().unwrap()).collect();\n    assert!(\n        ids[0] < ids[1] && ids[1] < ids[2],\n        \"rowids must be strictly increasing: {ids:?}\"\n    );\n}\n\n/// Deterministic reproduction of the sqlite_sequence pollution bug.\n///\n/// Two concurrent transactions insert into an AUTOINCREMENT table.\n/// Tx1 gets data rowid 1, tx2 gets data rowid 2. Tx1 commits first,\n/// so its sqlite_sequence row (name='t', seq=1) gets sqlite_sequence\n/// rowid 1. Tx2 commits second, so its sqlite_sequence row (name='t',\n/// seq=2) gets sqlite_sequence rowid 2.\n///\n/// After DELETE + checkpoint + restart, the table is empty (btree max = 0).\n/// init_autoincrement scans sqlite_sequence by rowid order, finds the FIRST\n/// match at sqlite_sequence rowid 1 with seq=1, and uses that.\n/// New rowid = max(1, 0) + 1 = 2, which REUSES the previously-used rowid 2.\n///\n/// This violates AUTOINCREMENT's contract that rowids must never decrease.\n#[test]\n#[ignore = \"AUTOINCREMENT not yet supported in MVCC mode\"]\nfn test_autoincrement_no_reuse_after_delete_and_restart() {\n    let _ = tracing_subscriber::fmt().try_init();\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n    let conn1 = db.connect();\n\n    conn1\n        .execute(\"CREATE TABLE t(a INTEGER PRIMARY KEY AUTOINCREMENT, b TEXT)\")\n        .unwrap();\n\n    // Two concurrent transactions: tx1 commits first, tx2 commits second.\n    conn1.execute(\"BEGIN CONCURRENT\").unwrap();\n    conn1\n        .execute(\"INSERT INTO t(b) VALUES ('from_tx1')\")\n        .unwrap();\n\n    let conn2 = db.connect();\n    conn2.execute(\"BEGIN CONCURRENT\").unwrap();\n    conn2\n        .execute(\"INSERT INTO t(b) VALUES ('from_tx2')\")\n        .unwrap();\n\n    // Commit tx1 first: its sqlite_sequence row gets the lower rowid\n    conn1.execute(\"COMMIT\").unwrap();\n    conn2.execute(\"COMMIT\").unwrap();\n\n    // Verify: data rowids are 1 and 2\n    let rows = get_rows(&conn1, \"SELECT a FROM t ORDER BY a\");\n    assert_eq!(rows.len(), 2);\n    let max_data_rowid = rows[1][0].as_int().unwrap();\n    assert_eq!(max_data_rowid, 2);\n\n    // sqlite_sequence should have duplicate rows (the bug):\n    // rowid=1: name=t, seq=1  (from tx1, committed first)\n    // rowid=2: name=t, seq=2  (from tx2, committed second)\n    let seq_rows = get_rows(\n        &conn1,\n        \"SELECT rowid, seq FROM sqlite_sequence WHERE name = 't' ORDER BY rowid\",\n    );\n    // If there's only 1 row with the correct max, the fix is applied.\n    // If there are 2 rows, the bug is present and init_autoincrement will\n    // pick the wrong one after restart.\n    let seq_count = seq_rows.len();\n\n    // Delete all data rows so btree max becomes 0 after restart\n    conn1.execute(\"DELETE FROM t\").unwrap();\n\n    // Checkpoint to flush everything to disk\n    conn1.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n\n    // Drop connections and restart\n    drop(conn1);\n    drop(conn2);\n    db.restart();\n\n    let conn = db.connect();\n\n    // Verify table is empty\n    let rows = get_rows(&conn, \"SELECT COUNT(*) FROM t\");\n    assert_eq!(rows[0][0].as_int().unwrap(), 0);\n\n    // Insert after restart. The new rowid MUST be > max_data_rowid (2).\n    conn.execute(\"INSERT INTO t(b) VALUES ('after_restart')\")\n        .unwrap();\n    let rows = get_rows(&conn, \"SELECT a FROM t\");\n    let new_rowid = rows[0][0].as_int().unwrap();\n\n    if seq_count > 1 {\n        // Bug present: sqlite_sequence has duplicate rows.\n        // init_autoincrement picked the first match (seq=1), so new rowid = 2,\n        // which reuses a previously-issued rowid.\n        eprintln!(\n            \"sqlite_sequence had {seq_count} rows for 't'. \\\n             After restart, new rowid = {new_rowid} (previous max was {max_data_rowid})\"\n        );\n    }\n\n    assert!(\n        new_rowid > max_data_rowid,\n        \"AUTOINCREMENT rowid reuse! Previous max was {max_data_rowid}, \\\n         but new rowid after delete+restart is {new_rowid}. \\\n         sqlite_sequence had {seq_count} duplicate rows; \\\n         init_autoincrement picked the stale one (seq=1 instead of seq=2).\"\n    );\n}\n\n/// Same bug as `test_elle_lost_update_exclusive_concurrent` but with simplified SQLs.\n/// For this bug to happen, we need deferred conflict detection done at\n/// `check_version_conflicts`.\n/// Requires UPSERT (INSERT ... ON CONFLICT DO UPDATE) — to hit `insert_btree_resident`\n/// and then `check_version_conflicts`.\n/// (Note: plain UPDATE eagerly detects conflicts via `delete_from_table_or_index`)\n///\n/// We need three txns for this bug to happen:\n/// Initial state: some row btree resident\n/// tx2: starts\n/// tx1: upserts the row, commits\n/// tx3: upserts the same row, which sets end=TxID(T3) on tx1's version (speculative delete)\n/// tx2: upserts the same row, should get WriteWriteConflict at commit time because tx1 committed previously\n#[test]\nfn test_speculative_delete_hides_committed_version_sql() {\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n    {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE t (key TEXT PRIMARY KEY, val TEXT)\")\n            .unwrap();\n        conn.execute(\"PRAGMA mvcc_checkpoint_threshold = 1\")\n            .unwrap();\n        conn.execute(\"INSERT INTO t VALUES ('k1', 'a')\").unwrap();\n        conn.close().unwrap();\n    }\n    // lets do this so that row becomes b tree resident\n    db.restart();\n    {\n        let conn = db.connect();\n        conn.execute(\"PRAGMA journal_mode = 'mvcc'\").unwrap();\n        conn.close().unwrap();\n    }\n\n    let upsert = |val: &str| {\n        format!(\n            \"INSERT INTO t VALUES ('k1', '{val}') \\\n             ON CONFLICT(key) DO UPDATE SET val = excluded.val\"\n        )\n    };\n\n    // T2: begin early so T1's future commit is invisible under SI.\n    let conn2 = db.connect();\n    conn2.execute(\"BEGIN CONCURRENT\").unwrap();\n\n    // T1: auto-commit UPSERT → insert_btree_resident, commits.\n    let conn1 = db.connect();\n    conn1.execute(upsert(\"b\")).unwrap();\n    conn1.close().unwrap();\n\n    // T3: UPSERT → sets end=TxID(T3) on T1's version.\n    let conn3 = db.connect();\n    conn3.execute(\"BEGIN CONCURRENT\").unwrap();\n    conn3.execute(upsert(\"d\")).unwrap();\n\n    // T2: UPSERT → insert_btree_resident (T1 invisible, T3 invisible).\n    conn2.execute(upsert(\"c\")).unwrap();\n\n    // T2: COMMIT → must detect conflict with T1.\n    let result = conn2.execute(\"COMMIT\");\n    assert!(\n        matches!(&result, Err(LimboError::WriteWriteConflict)),\n        \"Expected WriteWriteConflict, got: {result:?}.\"\n    );\n}\n\n/// Regression test for Elle bug (https://github.com/tursodatabase/turso/actions/runs/22855976911/job/66296309873?pr=5819#logs)\n/// Previously, `check_version_conflicts` skipped any version with `end.is_some()`,\n/// so a speculative delete by T3 (setting end=TxID(T3)) hid T1's committed version\n/// from T2's conflict check, allowing a lost update.\n///\n/// The SQL statements resemble the ones in Elle. For simplified variant, check\n/// `test_speculative_delete_hides_committed_version_sql` test\n///\n/// 1. Row for key \"k8\" exists in B-tree (B-tree-resident, no MVCC version)\n/// 2. T2 starts via BEGIN CONCURRENT\n/// 3. T1 does auto-commit UPSERT on \"k8\" → insert_btree_resident, commits\n/// 4. T3 starts via BEGIN CONCURRENT, does UPSERT on \"k8\" → update path sets\n///    end=TxID(T3) on T1's committed version\n/// 5. T2 does UPSERT on \"k8\" → insert_btree_resident (T1's version invisible, T3's invisible)\n/// 6. T2 COMMIT → check_version_conflicts SKIPS T1's version because end.is_some()\n///    → no WriteWriteConflict detected → lost update!\n/// 7. T3 eventually aborts, restoring T1's version, but T2 already committed.\n#[test]\nfn test_elle_lost_update_exclusive_concurrent() {\n    let mut db = MvccTestDbNoConn::new_with_random_db();\n\n    // Setup: create the elle-style table and seed initial data into the B-tree\n    {\n        let conn = db.connect();\n        conn.execute(\"CREATE TABLE elle_lists (key TEXT PRIMARY KEY, vals TEXT DEFAULT '')\")\n            .unwrap();\n        conn.execute(\"PRAGMA mvcc_checkpoint_threshold = 1\")\n            .unwrap();\n        conn.execute(\"INSERT INTO elle_lists (key, vals) VALUES ('k8', '100')\")\n            .unwrap();\n        conn.close().unwrap();\n    }\n    // Restart: data is only in B-tree, MVCC store is empty.\n    db.restart();\n    {\n        let conn = db.connect();\n        conn.execute(\"PRAGMA journal_mode = 'mvcc'\").unwrap();\n        conn.close().unwrap();\n    }\n\n    // T2: start concurrent transaction (begin_ts established early)\n    let conn2 = db.connect();\n    conn2.execute(\"BEGIN CONCURRENT\").unwrap();\n\n    // T1: auto-commit UPSERT on k8 → insert_btree_resident, commits immediately\n    let conn1 = db.connect();\n    conn1\n        .execute(\n            \"INSERT INTO elle_lists (key, vals) VALUES ('k8', '200') \\\n             ON CONFLICT(key) DO UPDATE SET vals = CASE WHEN vals = '' THEN '200' ELSE vals || ',' || '200' END\",\n        )\n        .unwrap();\n    conn1.close().unwrap();\n\n    // T3: start concurrent transaction (begin_ts > T1.end_ts, so T1's version IS visible to T3)\n    let conn3 = db.connect();\n    conn3.execute(\"BEGIN CONCURRENT\").unwrap();\n\n    // T3: UPSERT on k8 → update path: deletes T1's version (sets end=TxID(T3))\n    // and creates T3's own version. This speculatively hides T1's version.\n    conn3\n        .execute(\n            \"INSERT INTO elle_lists (key, vals) VALUES ('k8', '400') \\\n             ON CONFLICT(key) DO UPDATE SET vals = CASE WHEN vals = '' THEN '400' ELSE vals || ',' || '400' END\",\n        )\n        .unwrap();\n\n    // T2: UPSERT on k8 → insert_btree_resident (T1's version invisible under SI,\n    // T3's version invisible as Active)\n    conn2\n        .execute(\n            \"INSERT INTO elle_lists (key, vals) VALUES ('k8', '300') \\\n             ON CONFLICT(key) DO UPDATE SET vals = CASE WHEN vals = '' THEN '300' ELSE vals || ',' || '300' END\",\n        )\n        .unwrap();\n\n    // T2: COMMIT → should detect write-write conflict with T1's committed version.\n    // BUG: T1's version has end=TxID(T3), and the old code skips versions with\n    // end.is_some() → conflict missed → lost update.\n    let commit_result = conn2.execute(\"COMMIT\");\n    assert!(\n        matches!(&commit_result, Err(LimboError::WriteWriteConflict)),\n        \"Expected WriteWriteConflict, got: {commit_result:?}. \\\n         T1's committed version was hidden by T3's speculative delete (end=TxID), \\\n         causing check_version_conflicts to skip it.\"\n    );\n}\n\n/// Regression test: speculative delete by an active transaction must not hide a\n/// committed version from commit-time conflict checks.\n///\n/// Previously, `check_version_conflicts` skipped any version with `end.is_some()`,\n/// including versions where `end` was `TxID` of an active (uncommitted) transaction.\n/// This allowed T2 to commit without detecting the write-write conflict with T1.\n///\n/// Minimal reproduction using the MvStore API directly (no SQL, no restart, no UPSERT).\n/// Note: T2 begins after T1 commits, so T2 *can* see T1 under SI. We call\n/// `insert_btree_resident` directly to simulate the UPSERT code path where the\n/// cursor doesn't go through normal read visibility.\n///\n/// Timeline:\n///   T1: insert row 1, commit\n///   T2: begin (will write later)\n///   T3: begin, update row 1 → sets end=TxID(T3) on T1's version\n///   T2: insert_btree_resident row 1 (via API, bypassing read visibility)\n///   T2: commit → must detect conflict with T1\n#[test]\nfn test_speculative_delete_hides_committed_version() {\n    let db = MvccTestDb::new();\n    let table_id: MVTableId = (-2).into();\n\n    // T1: insert row 1 and commit.\n    let tx1 = db\n        .mvcc_store\n        .begin_tx(db.conn.pager.load().clone())\n        .unwrap();\n    let row_v1 = generate_simple_string_row(table_id, 1, \"v1\");\n    db.mvcc_store.insert(tx1, row_v1).unwrap();\n    commit_tx(db.mvcc_store.clone(), &db.conn, tx1).unwrap();\n\n    // T2: begin (will write later).\n    let conn2 = db.db.connect().unwrap();\n    let tx2 = db.mvcc_store.begin_tx(conn2.pager.load().clone()).unwrap();\n\n    // T3: begin, update row 1 → delete sets end=TxID(T3) on T1's version.\n    let conn3 = db.db.connect().unwrap();\n    let tx3 = db.mvcc_store.begin_tx(conn3.pager.load().clone()).unwrap();\n    let row_v3 = generate_simple_string_row(table_id, 1, \"v3\");\n    assert!(db.mvcc_store.update(tx3, row_v3).unwrap());\n\n    // T2: insert_btree_resident for the same row (called directly via API to\n    // simulate the UPSERT code path that bypasses eager conflict detection).\n    let row_v2 = generate_simple_string_row(table_id, 1, \"v2\");\n    db.mvcc_store\n        .insert_btree_resident_to_table_or_index(tx2, row_v2, None)\n        .unwrap();\n\n    // T2: commit → must fail with WriteWriteConflict.\n    let result = commit_tx(db.mvcc_store, &conn2, tx2);\n    assert!(\n        matches!(&result, Err(LimboError::WriteWriteConflict)),\n        \"Expected WriteWriteConflict, got: {result:?}. \\\n         T3's speculative delete (end=TxID) on T1's version must not hide it from conflict checks.\"\n    );\n}\n\n/// Verify that a committed pure delete (tombstone) is detected as a conflict.\n///\n/// Scenario: Td deletes a row and commits. Between Td's Commit and CommitEnd\n/// (when TxID→Timestamp conversion happens), the tombstone still has\n/// end=TxID(Td). T2 does insert_btree_resident for the same row and tries to\n/// commit. The tombstone's begin=None, end=TxID(Td) should be caught by the\n/// B-tree tombstone check in check_version_conflicts.\n///\n/// Timeline:\n///   T1: insert row 1, commit\n///   T2: begin (will write later)\n///   Td: delete row 1, commit\n///   T2: insert_btree_resident row 1\n///   T2: commit → must detect conflict with Td's tombstone\n#[test]\nfn test_committed_delete_tombstone_conflict() {\n    let db = MvccTestDb::new();\n    let table_id: MVTableId = (-2).into();\n\n    // T1: insert row 1 and commit.\n    let tx1 = db\n        .mvcc_store\n        .begin_tx(db.conn.pager.load().clone())\n        .unwrap();\n    let row_v1 = generate_simple_string_row(table_id, 1, \"v1\");\n    db.mvcc_store.insert(tx1, row_v1).unwrap();\n    commit_tx(db.mvcc_store.clone(), &db.conn, tx1).unwrap();\n\n    // T2: begin (will write later).\n    let conn2 = db.db.connect().unwrap();\n    let tx2 = db.mvcc_store.begin_tx(conn2.pager.load().clone()).unwrap();\n\n    // Td: delete row 1 and commit.\n    let conn_d = db.db.connect().unwrap();\n    let tx_d = db.mvcc_store.begin_tx(conn_d.pager.load().clone()).unwrap();\n    assert!(db\n        .mvcc_store\n        .delete(tx_d, RowID::new(table_id, RowKey::Int(1)))\n        .unwrap());\n    commit_tx(db.mvcc_store.clone(), &conn_d, tx_d).unwrap();\n\n    // T2: insert_btree_resident for the same row.\n    let row_v2 = generate_simple_string_row(table_id, 1, \"v2\");\n    db.mvcc_store\n        .insert_btree_resident_to_table_or_index(tx2, row_v2, None)\n        .unwrap();\n\n    // T2: commit → must detect conflict with Td.\n    let result = commit_tx(db.mvcc_store, &conn2, tx2);\n    assert!(\n        matches!(&result, Err(LimboError::WriteWriteConflict)),\n        \"Expected WriteWriteConflict, got: {result:?}. \\\n         Td's committed delete (tombstone) must be detected as a conflict.\"\n    );\n}\n\n/// Verify that when a transaction (Td) updates a row and commits, another\n/// transaction (T2) that also writes to the same row detects the conflict —\n/// even though T1's version has end=TxID(Td) with Td committed.\n///\n/// This tests the `Committed(_) => continue` branch in `check_version_conflicts`:\n/// skipping T1's version is safe because Td's NEW version (begin=TxID(Td)) catches\n/// the conflict.\n///\n/// Timeline:\n///   T1: insert row 1, commit\n///   T2: begin (will write later)\n///   Td: update row 1 (sets end=TxID(Td) on T1's version, creates new version), commit\n///   T2: insert_btree_resident row 1\n///   T2: commit → must detect conflict with Td's new version\n#[test]\nfn test_committed_update_version_conflict() {\n    let db = MvccTestDb::new();\n    let table_id: MVTableId = (-2).into();\n\n    // T1: insert row 1 and commit.\n    let tx1 = db\n        .mvcc_store\n        .begin_tx(db.conn.pager.load().clone())\n        .unwrap();\n    let row_v1 = generate_simple_string_row(table_id, 1, \"v1\");\n    db.mvcc_store.insert(tx1, row_v1).unwrap();\n    commit_tx(db.mvcc_store.clone(), &db.conn, tx1).unwrap();\n\n    // T2: begin (will write later).\n    let conn2 = db.db.connect().unwrap();\n    let tx2 = db.mvcc_store.begin_tx(conn2.pager.load().clone()).unwrap();\n\n    // Td: update row 1 and commit.\n    let conn_d = db.db.connect().unwrap();\n    let tx_d = db.mvcc_store.begin_tx(conn_d.pager.load().clone()).unwrap();\n    let row_vd = generate_simple_string_row(table_id, 1, \"vd\");\n    assert!(db.mvcc_store.update(tx_d, row_vd).unwrap());\n    commit_tx(db.mvcc_store.clone(), &conn_d, tx_d).unwrap();\n\n    // T2: insert_btree_resident for the same row.\n    let row_v2 = generate_simple_string_row(table_id, 1, \"v2\");\n    db.mvcc_store\n        .insert_btree_resident_to_table_or_index(tx2, row_v2, None)\n        .unwrap();\n\n    // T2: commit → must detect conflict with Td.\n    let result = commit_tx(db.mvcc_store, &conn2, tx2);\n    assert!(\n        matches!(&result, Err(LimboError::WriteWriteConflict)),\n        \"Expected WriteWriteConflict, got: {result:?}. \\\n         Td's committed update must be detected via Td's new version.\"\n    );\n}\n"
  },
  {
    "path": "core/mvcc/mod.rs",
    "content": "//! Multiversion concurrency control (MVCC) for Rust.\n//!\n//! This module implements the main memory MVCC method outlined in the paper\n//! \"High-Performance Concurrency Control Mechanisms for Main-Memory Databases\"\n//! by Per-Åke Larson et al (VLDB, 2011).\n//!\n//! ## Data anomalies\n//!\n//! * A *dirty write* occurs when transaction T_m updates a value that is written by\n//!   transaction T_n but not yet committed. The MVCC algorithm prevents dirty\n//!   writes by validating that a row version is visible to transaction T_m before\n//!   allowing update to it.\n//!\n//! * A *dirty read* occurs when transaction T_m reads a value that was written by\n//!   transaction T_n but not yet committed. The MVCC algorithm prevents dirty\n//!   reads by validating that a row version is visible to transaction T_m.\n//!\n//! * A *fuzzy read* (non-repeatable read) occurs when transaction T_m reads a\n//!   different value in the course of the transaction because another\n//!   transaction T_n has updated the value.\n//!\n//! * A *lost update* occurs when transactions T_m and T_n both attempt to update\n//!   the same value, resulting in one of the updates being lost. The MVCC algorithm\n//!   prevents lost updates by detecting the write-write conflict and letting the\n//!   first-writer win by aborting the later transaction.\n//!\n//! TODO: phantom reads, cursor lost updates, read skew, write skew.\n//!\n//! ## TODO\n//!\n//! * Optimistic reads and writes\n//! * Garbage collection\n\npub mod clock;\npub mod cursor;\npub mod database;\npub mod persistent_storage;\n\npub use clock::MvccClock;\npub use database::MvStore;\n\n#[cfg(test)]\nmod tests {\n    use crate::mvcc::database::tests::{\n        commit_tx_no_conn, generate_simple_string_row, MvccTestDbNoConn,\n    };\n    use crate::mvcc::database::{RowID, RowKey};\n    use crate::sync::atomic::AtomicI64;\n    use crate::sync::atomic::Ordering;\n    use crate::sync::Arc;\n    use crate::LimboError;\n\n    static IDS: AtomicI64 = AtomicI64::new(1);\n\n    /// What this test checks: MVCC transaction visibility and conflict handling follow the intended isolation behavior.\n    /// Why this matters: Concurrency bugs are correctness bugs: they create anomalies users can observe as wrong query results.\n    #[test]\n    #[ignore = \"FIXME: This test fails because there is write busy lock yet to be fixed\"]\n    fn test_non_overlapping_concurrent_inserts() {\n        // Two threads insert to the database concurrently using non-overlapping\n        // row IDs.\n        let db = Arc::new(MvccTestDbNoConn::new());\n        {\n            let conn = db.connect();\n            conn.execute(\"CREATE TABLE t(v TEXT)\").unwrap();\n        }\n        let iterations = 10_000;\n\n        let th1 = {\n            let db = db.clone();\n            std::thread::spawn(move || {\n                let conn = db.get_db().connect().unwrap();\n                let mvcc_store = db.get_db().get_mv_store().clone().unwrap();\n                for _ in 0..iterations {\n                    let tx = loop {\n                        match mvcc_store.begin_tx(conn.pager.load().clone()) {\n                            Ok(tx) => break tx,\n                            Err(LimboError::Busy) => {\n                                std::thread::yield_now();\n                            }\n                            Err(e) => panic!(\"unexpected begin_tx error: {e:?}\"),\n                        }\n                    };\n                    let id = IDS.fetch_add(1, Ordering::SeqCst);\n                    let id = RowID {\n                        table_id: (-2).into(),\n                        row_id: RowKey::Int(id),\n                    };\n                    let row = generate_simple_string_row(\n                        (-2).into(),\n                        id.row_id.to_int_or_panic(),\n                        \"Hello\",\n                    );\n                    mvcc_store.insert(tx, row.clone()).unwrap();\n                    loop {\n                        match commit_tx_no_conn(&db, tx, &conn) {\n                            Ok(()) => break,\n                            Err(LimboError::Busy) => {\n                                mvcc_store.rollback_tx(\n                                    tx,\n                                    conn.pager.load().clone(),\n                                    &conn,\n                                    crate::MAIN_DB_ID,\n                                );\n                                std::thread::yield_now();\n                                continue;\n                            }\n                            Err(e) => panic!(\"unexpected commit error: {e:?}\"),\n                        }\n                    }\n                    let tx = loop {\n                        match mvcc_store.begin_tx(conn.pager.load().clone()) {\n                            Ok(tx) => break tx,\n                            Err(LimboError::Busy) => {\n                                std::thread::yield_now();\n                            }\n                            Err(e) => panic!(\"unexpected begin_tx error: {e:?}\"),\n                        }\n                    };\n                    let committed_row = mvcc_store.read(tx, &id).unwrap();\n                    loop {\n                        match commit_tx_no_conn(&db, tx, &conn) {\n                            Ok(()) => break,\n                            Err(LimboError::Busy) => {\n                                mvcc_store.rollback_tx(\n                                    tx,\n                                    conn.pager.load().clone(),\n                                    &conn,\n                                    crate::MAIN_DB_ID,\n                                );\n                                std::thread::yield_now();\n                                continue;\n                            }\n                            Err(e) => panic!(\"unexpected commit error: {e:?}\"),\n                        }\n                    }\n                    assert_eq!(committed_row, Some(row));\n                }\n            })\n        };\n        let th2 = {\n            std::thread::spawn(move || {\n                let conn = db.get_db().connect().unwrap();\n                let mvcc_store = db.get_db().get_mv_store().clone().unwrap();\n                for _ in 0..iterations {\n                    let tx = loop {\n                        match mvcc_store.begin_tx(conn.pager.load().clone()) {\n                            Ok(tx) => break tx,\n                            Err(LimboError::Busy) => {\n                                std::thread::yield_now();\n                            }\n                            Err(e) => panic!(\"unexpected begin_tx error: {e:?}\"),\n                        }\n                    };\n                    let id = IDS.fetch_add(1, Ordering::SeqCst);\n                    let id = RowID {\n                        table_id: (-2).into(),\n                        row_id: RowKey::Int(id),\n                    };\n                    let row = generate_simple_string_row(\n                        (-2).into(),\n                        id.row_id.to_int_or_panic(),\n                        \"World\",\n                    );\n                    mvcc_store.insert(tx, row.clone()).unwrap();\n                    loop {\n                        match commit_tx_no_conn(&db, tx, &conn) {\n                            Ok(()) => break,\n                            Err(LimboError::Busy) => {\n                                mvcc_store.rollback_tx(\n                                    tx,\n                                    conn.pager.load().clone(),\n                                    &conn,\n                                    crate::MAIN_DB_ID,\n                                );\n                                std::thread::yield_now();\n                                continue;\n                            }\n                            Err(e) => panic!(\"unexpected commit error: {e:?}\"),\n                        }\n                    }\n                    let tx = loop {\n                        match mvcc_store.begin_tx(conn.pager.load().clone()) {\n                            Ok(tx) => break tx,\n                            Err(LimboError::Busy) => {\n                                std::thread::yield_now();\n                            }\n                            Err(e) => panic!(\"unexpected begin_tx error: {e:?}\"),\n                        }\n                    };\n                    let committed_row = mvcc_store.read(tx, &id).unwrap();\n                    loop {\n                        match commit_tx_no_conn(&db, tx, &conn) {\n                            Ok(()) => break,\n                            Err(LimboError::Busy) => {\n                                mvcc_store.rollback_tx(\n                                    tx,\n                                    conn.pager.load().clone(),\n                                    &conn,\n                                    crate::MAIN_DB_ID,\n                                );\n                                std::thread::yield_now();\n                                continue;\n                            }\n                            Err(e) => panic!(\"unexpected commit error: {e:?}\"),\n                        }\n                    }\n                    assert_eq!(committed_row, Some(row));\n                }\n            })\n        };\n        th1.join().unwrap();\n        th2.join().unwrap();\n    }\n\n    // FIXME: This test fails sporadically.\n    #[test]\n    #[ignore]\n    fn test_overlapping_concurrent_inserts_read_your_writes() {\n        let db = Arc::new(MvccTestDbNoConn::new());\n        {\n            let conn = db.connect();\n            conn.execute(\"CREATE TABLE t(v TEXT)\").unwrap();\n        }\n        let iterations = 20_000;\n\n        let work = |prefix: &'static str| {\n            let db = db.clone();\n            std::thread::spawn(move || {\n                let conn = db.get_db().connect().unwrap();\n                let mvcc_store = db.get_db().get_mv_store().clone().unwrap();\n                let mut failed_upserts = 0;\n                let mut failed_commits = 0;\n                let mut busy_retries = 0;\n                for i in 0..iterations {\n                    if i % 1000 == 0 {\n                        tracing::debug!(\"{prefix}: {i}\");\n                    }\n                    if i % 10000 == 0 {\n                        let dropped = mvcc_store.drop_unused_row_versions();\n                        tracing::debug!(\"garbage collected {dropped} versions\");\n                    }\n                    let tx = loop {\n                        match mvcc_store.begin_tx(conn.pager.load().clone()) {\n                            Ok(tx) => break tx,\n                            Err(LimboError::Busy) => {\n                                busy_retries += 1;\n                                std::thread::yield_now();\n                            }\n                            Err(e) => panic!(\"unexpected begin_tx error: {e:?}\"),\n                        }\n                    };\n                    let id = i % 16;\n                    let id = RowID {\n                        table_id: (-2).into(),\n                        row_id: RowKey::Int(id),\n                    };\n                    let row = generate_simple_string_row(\n                        (-2).into(),\n                        id.row_id.to_int_or_panic(),\n                        &format!(\"{prefix} @{tx}\"),\n                    );\n                    if let Err(e) = mvcc_store.upsert(tx, row.clone()) {\n                        tracing::trace!(\"upsert failed: {e}\");\n                        failed_upserts += 1;\n                        mvcc_store.rollback_tx(\n                            tx,\n                            conn.pager.load().clone(),\n                            &conn,\n                            crate::MAIN_DB_ID,\n                        );\n                        continue;\n                    }\n                    let committed_row = mvcc_store.read(tx, &id).unwrap();\n                    match commit_tx_no_conn(&db, tx, &conn) {\n                        Ok(()) => {}\n                        Err(LimboError::Busy | LimboError::WriteWriteConflict) => {\n                            failed_commits += 1;\n                            mvcc_store.rollback_tx(\n                                tx,\n                                conn.pager.load().clone(),\n                                &conn,\n                                crate::MAIN_DB_ID,\n                            );\n                            continue;\n                        }\n                        Err(e) => panic!(\"unexpected commit error: {e:?}\"),\n                    }\n                    assert_eq!(committed_row, Some(row));\n                }\n                tracing::info!(\n                    \"{prefix}: failed_upserts={failed_upserts}, failed_commits={failed_commits}, busy_retries={busy_retries} of {iterations}\",\n                );\n            })\n        };\n\n        let threads = vec![work(\"A\"), work(\"B\"), work(\"C\"), work(\"D\")];\n        for th in threads {\n            th.join().unwrap();\n        }\n    }\n\n    /// What this test checks: MVCC transaction visibility and conflict handling follow the intended isolation behavior.\n    /// Why this matters: Concurrency bugs are correctness bugs: they create anomalies users can observe as wrong query results.\n    #[test]\n    fn test_mvcc_dual_cursor_transaction_isolation() {\n        let res = tracing_subscriber::fmt::try_init();\n        drop(res);\n        let mut db = MvccTestDbNoConn::new_with_random_db();\n\n        {\n            let conn = db.connect();\n            conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY, val INTEGER)\")\n                .unwrap();\n            conn.execute(\"INSERT INTO t VALUES (1, 100)\").unwrap();\n            conn.execute(\"INSERT INTO t VALUES (2, 200)\").unwrap();\n            conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n        }\n\n        db.restart();\n\n        // Tx1: Update B-tree row\n        let conn1 = db.connect();\n        conn1.execute(\"BEGIN CONCURRENT\").unwrap();\n        conn1\n            .execute(\"UPDATE t SET val = 999 WHERE id = 1\")\n            .unwrap();\n\n        // Tx2: Should see old B-tree value, not Tx1's MVCC change\n        let conn2 = db.connect();\n        conn2.execute(\"BEGIN CONCURRENT\").unwrap();\n        let rows = get_rows(&conn2, \"SELECT val FROM t WHERE id = 1\");\n        assert_eq!(rows[0][0].as_int().unwrap(), 100); // Original B-tree value\n\n        conn1.execute(\"COMMIT\").unwrap();\n        conn2.execute(\"COMMIT\").unwrap();\n    }\n    fn get_rows(conn: &Arc<crate::Connection>, sql: &str) -> Vec<Vec<crate::Value>> {\n        let mut stmt = conn.prepare(sql).unwrap();\n        let mut rows = Vec::new();\n        stmt.run_with_row_callback(|row| {\n            rows.push(row.get_values().cloned().collect());\n            Ok(())\n        })\n        .unwrap();\n        rows\n    }\n}\n"
  },
  {
    "path": "core/mvcc/persistent_storage/logical_log.rs",
    "content": "//! MVCC logical log: file format, recovery rules, and durability contract.\n//!\n//! ## What this file is for\n//!\n//! The logical log stores committed MVCC operations that are not checkpointed into the main\n//! SQLite database file yet. On restart, recovery replays those operations.\n//!\n//! In normal operation:\n//! - commits append transaction frames to `.db-log`;\n//! - checkpoint copies data into the DB file, then truncates `.db-log` to 0.\n//!\n//! ## File layout\n//!\n//! A logical log file has:\n//! - one fixed-size header (`LOG_HDR_SIZE = 56` bytes), then\n//! - zero or more transaction frames.\n//!\n//! ### Header fields (56 bytes, little-endian)\n//! - `magic: u32` (`LOG_MAGIC`)\n//! - `version: u8` (`LOG_VERSION`)\n//! - `flags: u8` (bits 1..7 must be zero; bit 0 is currently reserved/ignored)\n//! - `hdr_len: u16` (`>= 56`)\n//! - `salt: u64` (random salt, regenerated on each log truncation)\n//! - `reserved: [u8; 36]` (must be zero for current format)\n//! - `hdr_crc32c: u32` (CRC32C of the header with this field zeroed)\n//!\n//! ### Transaction frame (LOG_VERSION = 2)\n//!\n//! Header (`TX_HEADER_SIZE = 24`):\n//! - `frame_magic: u32` (`FRAME_MAGIC`)\n//! - `payload_size: u64` (total bytes of all op entries)\n//! - `op_count: u32`\n//! - `commit_ts: u64`\n//!\n//! Payload:\n//! - `op_count` operation entries:\n//!   - `tag: u8` (`OP_*`)\n//!   - `flags: u8` (`OP_FLAG_BTREE_RESIDENT` currently defined)\n//!   - `table_id: i32` (must be negative)\n//!   - `payload_len: sqlite varint`\n//!   - `payload: [u8; payload_len]`\n//!\n//! Trailer (`TX_TRAILER_SIZE = 8`):\n//! - `crc32c: u32` (chained CRC32C: `crc32c_append(prev_frame_crc, tx_header || payload)`;\n//!   the first frame uses `crc32c(salt.to_le_bytes())` as its seed)\n//! - `end_magic: u32` (`END_MAGIC`)\n//!\n//! ## Operation encoding\n//!\n//! - `OP_UPSERT_TABLE`: `rowid_varint || table_record_bytes`\n//! - `OP_DELETE_TABLE`: `rowid_varint`\n//! - `OP_UPSERT_INDEX`: serialized index key record\n//! - `OP_DELETE_INDEX`: serialized index key record\n//!\n//! `OP_FLAG_BTREE_RESIDENT` means the row existed in the B-tree before MVCC started tracking it.\n//! Recovery preserves this bit because checkpoint/GC logic depends on it.\n//!\n//! ## Validation behavior\n//!\n//! The read path (`parse_next_transaction`) performs strict structural validation (header/trailer\n//! fields, reserved bits, table-id sign, op payload shape) plus chained CRC verification.\n//!\n//! Validation is availability-focused, mirroring SQLite WAL prefix semantics:\n//! - torn/incomplete tail at end-of-file is accepted as EOF (previous validated frames remain);\n//! - first invalid frame encountered during forward scan is treated as an invalid tail and ignored;\n//! - only header corruption fails closed.\n//!\n//! ## Recovery behavior\n//!\n//! Recovery (reader + MVCC replay) does this:\n//! - validates header first (empty/0-byte file treated as no log);\n//! - accepts a valid header with no frames (size `<= LOG_HDR_SIZE`);\n//! - reads `persistent_tx_ts_max` from `__turso_internal_mvcc_meta` (the durable replay boundary);\n//! - streams frames in commit order until first torn tail;\n//! - applies only validated frames whose `commit_ts > persistent_tx_ts_max`;\n//! - sets clock to `max(persistent_tx_ts_max, max_replayed_commit_ts) + 1`;\n//! - restores writer offset to `last_valid_offset` so torn-tail bytes are overwritten.\n//!\n//! ## Durability and checkpoint ordering\n//!\n//! Commit durability:\n//! - Append completion must succeed.\n//! - Fsync behavior depends on sync mode (`Full` fsyncs per commit; lower modes may defer).\n//!\n//! Checkpoint ordering (enforced by checkpoint state machine):\n//! 1. write committed MVCC versions into pager (WAL);\n//! 2. commit pager transaction (data + metadata row in same WAL txn);\n//! 3. checkpoint WAL pages into DB file;\n//! 4. fsync DB file (unless `SyncMode::Off`);\n//! 5. truncate logical log to 0 (regenerates salt in memory; header written with next frame);\n//! 6. fsync logical log (unless `SyncMode::Off`);\n//! 7. truncate WAL last.\n//!\n//! WAL-last is intentional: if crash happens mid-checkpoint, WAL remains a safety net until\n//! logical-log cleanup is complete.\n//!\n//! ## Non-goal\n//!\n//! Frame-level atomicity only: torn tails are discarded; partially written frames are not salvaged.\n#![allow(dead_code)]\n\nuse crate::io::FileSyncType;\nuse crate::sync::Arc;\nuse crate::sync::RwLock;\nuse crate::turso_assert;\nuse crate::{\n    io::ReadComplete,\n    mvcc::database::{LogRecord, MVTableId, Row, RowID, RowKey, RowVersion, SortableIndexKey},\n    storage::sqlite3_ondisk::{\n        read_varint, read_varint_partial, varint_len, write_varint_to_vec, DatabaseHeader,\n    },\n    types::IndexInfo,\n    Buffer, Completion, CompletionError, LimboError, Result,\n};\n\nuse crate::storage::encryption::EncryptionContext;\nuse crate::File;\n\n/// Logical log size in bytes at which a committing transaction will trigger a checkpoint.\n/// Default to the size of 1000 SQLite WAL frames; disable by setting a negative value.\npub const DEFAULT_LOG_CHECKPOINT_THRESHOLD: i64 = 4120 * 1000;\n\n/// Optional callback invoked after serialization with a zero-copy reference to\n/// the serialized frame bytes and the running CRC, before the disk write.\npub type OnSerializationComplete<'a> = Option<&'a dyn Fn(&[u8], u32)>;\n\nconst LOG_MAGIC: u32 = 0x4C4D4C32; // \"LML2\" in LE\nconst LOG_VERSION: u8 = 2;\npub const LOG_HDR_SIZE: usize = 56;\nconst LOG_HDR_SALT_START: usize = 8;\nconst LOG_HDR_SALT_SIZE: usize = 8;\nconst LOG_HDR_RESERVED_START: usize = LOG_HDR_SALT_START + LOG_HDR_SALT_SIZE; // 16\nconst LOG_HDR_CRC_START: usize = 52;\nconst LOG_HDR_RESERVED_SIZE: usize = LOG_HDR_CRC_START - LOG_HDR_RESERVED_START; // 36\nconst FRAME_MAGIC: u32 = 0x5854564D; // \"MVTX\" in LE\nconst END_MAGIC: u32 = 0x4554564D; // \"MVTE\" in LE\n\nconst OP_UPSERT_TABLE: u8 = 0;\nconst OP_DELETE_TABLE: u8 = 1;\nconst OP_UPSERT_INDEX: u8 = 2;\nconst OP_DELETE_INDEX: u8 = 3;\n/// Frame-local database-header mutation (payload = serialized `DatabaseHeader`).\nconst OP_UPDATE_HEADER: u8 = 4;\n\nconst OP_FLAG_BTREE_RESIDENT: u8 = 1 << 0;\n\nconst TX_HEADER_SIZE: usize = 24; // FRAME_MAGIC(4) + payload_size(8) + op_count(4) + commit_ts(8)\nconst TX_TRAILER_SIZE: usize = 8; // crc32c(4) + END_MAGIC(4)\nconst TX_MIN_FRAME_SIZE: usize = TX_HEADER_SIZE + TX_TRAILER_SIZE; // 32\n\n/// Log's Header, the first 56 bytes of any logical log file.\n#[derive(Clone, Debug)]\npub struct LogHeader {\n    version: u8,\n    flags: u8,\n    hdr_len: u16,\n    pub(crate) salt: u64,\n    hdr_crc32c: u32,\n    reserved: [u8; LOG_HDR_RESERVED_SIZE],\n}\n\nimpl LogHeader {\n    pub(crate) fn new(io: &Arc<dyn crate::IO>) -> Self {\n        Self {\n            version: LOG_VERSION,\n            flags: 0,\n            hdr_len: LOG_HDR_SIZE as u16,\n            salt: io.generate_random_number() as u64,\n            hdr_crc32c: 0,\n            reserved: [0; LOG_HDR_RESERVED_SIZE],\n        }\n    }\n\n    fn encode(&self) -> [u8; LOG_HDR_SIZE] {\n        let mut buf = [0u8; LOG_HDR_SIZE];\n        buf[0..4].copy_from_slice(&LOG_MAGIC.to_le_bytes());\n        buf[4] = self.version;\n        buf[5] = self.flags;\n        buf[6..8].copy_from_slice(&self.hdr_len.to_le_bytes());\n        buf[LOG_HDR_SALT_START..LOG_HDR_SALT_START + LOG_HDR_SALT_SIZE]\n            .copy_from_slice(&self.salt.to_le_bytes());\n        buf[LOG_HDR_RESERVED_START..LOG_HDR_CRC_START].copy_from_slice(&self.reserved);\n\n        let crc = crc32c::crc32c(&buf);\n        buf[LOG_HDR_CRC_START..LOG_HDR_SIZE].copy_from_slice(&crc.to_le_bytes());\n        buf\n    }\n\n    fn decode(buf: &[u8]) -> Result<Self> {\n        if buf.len() < LOG_HDR_SIZE {\n            return Err(LimboError::Corrupt(\n                \"Logical log header too small\".to_string(),\n            ));\n        }\n        let magic = u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]);\n        if magic != LOG_MAGIC {\n            return Err(LimboError::Corrupt(\"Invalid logical log magic\".to_string()));\n        }\n        let version = buf[4];\n        if version != LOG_VERSION {\n            return Err(LimboError::Corrupt(format!(\n                \"Unsupported logical log version {version}\"\n            )));\n        }\n        let flags = buf[5];\n        if flags & 0b1111_1110 != 0 {\n            return Err(LimboError::Corrupt(\n                \"Invalid logical log header flags\".to_string(),\n            ));\n        }\n        let hdr_len = u16::from_le_bytes([buf[6], buf[7]]);\n        if hdr_len as usize != LOG_HDR_SIZE {\n            return Err(LimboError::Corrupt(format!(\n                \"Invalid logical log header length {hdr_len}\"\n            )));\n        }\n        if buf.len() < hdr_len as usize {\n            return Err(LimboError::Corrupt(\n                \"Logical log header shorter than hdr_len\".to_string(),\n            ));\n        }\n        let hdr_crc32c = u32::from_le_bytes([\n            buf[LOG_HDR_CRC_START],\n            buf[LOG_HDR_CRC_START + 1],\n            buf[LOG_HDR_CRC_START + 2],\n            buf[LOG_HDR_CRC_START + 3],\n        ]);\n        let mut crc_buf = [0u8; LOG_HDR_SIZE];\n        crc_buf.copy_from_slice(&buf[..LOG_HDR_SIZE]);\n        crc_buf[LOG_HDR_CRC_START..LOG_HDR_SIZE].fill(0);\n        let expected_crc = crc32c::crc32c(&crc_buf);\n        if expected_crc != hdr_crc32c {\n            return Err(LimboError::Corrupt(\n                \"Logical log header checksum mismatch\".to_string(),\n            ));\n        }\n\n        let salt = u64::from_le_bytes([\n            buf[LOG_HDR_SALT_START],\n            buf[LOG_HDR_SALT_START + 1],\n            buf[LOG_HDR_SALT_START + 2],\n            buf[LOG_HDR_SALT_START + 3],\n            buf[LOG_HDR_SALT_START + 4],\n            buf[LOG_HDR_SALT_START + 5],\n            buf[LOG_HDR_SALT_START + 6],\n            buf[LOG_HDR_SALT_START + 7],\n        ]);\n\n        let mut reserved = [0u8; LOG_HDR_RESERVED_SIZE];\n        reserved.copy_from_slice(&buf[LOG_HDR_RESERVED_START..LOG_HDR_CRC_START]);\n        if reserved.iter().any(|b| *b != 0) {\n            return Err(LimboError::Corrupt(\n                \"Logical log header reserved bytes must be zero\".to_string(),\n            ));\n        }\n\n        Ok(Self {\n            version,\n            flags,\n            hdr_len,\n            salt,\n            hdr_crc32c,\n            reserved,\n        })\n    }\n}\n\n/// Derives the initial CRC seed from the header salt.\n/// The salt is mixed into a 32-bit CRC state that seeds the first frame's checksum.\nfn derive_initial_crc(salt: u64) -> u32 {\n    crc32c::crc32c(&salt.to_le_bytes())\n}\n\npub struct LogicalLog {\n    pub file: Arc<dyn File>,\n    io: Arc<dyn crate::IO>,\n    pub offset: u64,\n    write_buf: Vec<u8>,\n    header: Option<LogHeader>,\n    /// Running CRC state for chained checksums. Seeded from the header salt;\n    /// updated after each committed frame. The next frame's CRC is computed as\n    /// `crc32c_append(running_crc, frame_bytes)`.\n    pub running_crc: u32,\n    /// Pending CRC from a deferred-offset write. Applied by\n    /// `advance_offset_after_success` so that an abandoned write\n    /// doesn't corrupt the chain.\n    pending_running_crc: Option<u32>,\n    encryption_ctx: Option<EncryptionContext>,\n    /// Reusable scratch buffer for ops serialization on the encrypted write path.\n    encryption_scratch_buffer: Vec<u8>,\n}\n\nimpl LogicalLog {\n    pub fn new(\n        file: Arc<dyn File>,\n        io: Arc<dyn crate::IO>,\n        encryption_ctx: Option<EncryptionContext>,\n    ) -> Self {\n        Self {\n            file,\n            io,\n            offset: 0,\n            write_buf: Vec::new(),\n            header: None,\n            running_crc: 0,\n            pending_running_crc: None,\n            encryption_ctx,\n            encryption_scratch_buffer: Vec::new(),\n        }\n    }\n\n    pub(crate) fn set_header(&mut self, header: LogHeader) {\n        self.running_crc = derive_initial_crc(header.salt);\n        self.header = Some(header);\n    }\n\n    pub(crate) fn header(&self) -> Option<&LogHeader> {\n        self.header.as_ref()\n    }\n\n    /// Serializes a transaction into `write_buf`, optionally calls\n    /// `on_serialization_complete` with a zero-copy reference to the frame bytes,\n    /// then writes to disk. `write_buf` retains its allocation across calls.\n    ///\n    /// `advance_offset_immediately`: when true, the writer offset advances right\n    /// after the pwrite (checkpoint path). When false, the offset stays behind\n    /// until `advance_offset_after_success` is called (MVCC commit path).\n    fn serialize_and_pwrite_tx(\n        &mut self,\n        tx: &LogRecord,\n        advance_offset_immediately: bool,\n        on_serialization_complete: OnSerializationComplete<'_>,\n    ) -> Result<(Completion, u64)> {\n        self.write_buf.clear();\n\n        // 1. Serialize log header if it's first write\n        let is_first_write = self.offset == 0;\n        if is_first_write {\n            if self.header.is_none() {\n                let header = LogHeader::new(&self.io);\n                self.running_crc = derive_initial_crc(header.salt);\n                self.header = Some(header);\n            }\n            let header_bytes = self.header.as_ref().unwrap().encode();\n            self.write_buf.extend_from_slice(&header_bytes);\n        }\n\n        // 2. Serialize Transaction header.\n        // A header-only transaction is encoded as a single OP_UPDATE_HEADER op.\n        // payload_size is only known after serializing all ops. We reserve TX_HEADER_SIZE bytes\n        // as a placeholder and backfill all header fields in step 4.\n        let op_count = u32::try_from(tx.row_versions.len() + usize::from(tx.header.is_some()))\n            .map_err(|_| {\n                LimboError::InternalError(\"Logical log op_count exceeds u32\".to_string())\n            })?;\n        let commit_ts = tx.tx_timestamp;\n        let tx_header_start = self.write_buf.len();\n        self.write_buf.resize(tx_header_start + TX_HEADER_SIZE, 0);\n\n        // 3. Serialize ops into write_buf (encrypted or plaintext).\n        let payload_size = self.serialize_ops_into_write_buf(tx, op_count, commit_ts)?;\n        let payload_end = self.write_buf.len();\n\n        // 4. Backfill TX HEADER: FRAME_MAGIC(4) | payload_size(8) | op_count(4) | commit_ts(8)\n        self.write_buf[tx_header_start..tx_header_start + 4]\n            .copy_from_slice(&FRAME_MAGIC.to_le_bytes());\n        self.write_buf[tx_header_start + 4..tx_header_start + 12]\n            .copy_from_slice(&payload_size.to_le_bytes());\n        self.write_buf[tx_header_start + 12..tx_header_start + 16]\n            .copy_from_slice(&op_count.to_le_bytes());\n        self.write_buf[tx_header_start + 16..tx_header_start + 24]\n            .copy_from_slice(&commit_ts.to_le_bytes());\n\n        // 5. TX TRAILER layout (8 bytes): crc32c(4, le u32) | END_MAGIC(4)\n        // CRC is chained: seeded from running_crc (salt-derived, or previous frame's CRC),\n        // covers TX_HEADER (24 B) + payload (encrypted or plaintext).\n        let crc = crc32c::crc32c_append(\n            self.running_crc,\n            &self.write_buf[tx_header_start..payload_end],\n        );\n        self.write_buf.extend_from_slice(&crc.to_le_bytes());\n        self.write_buf.extend_from_slice(&END_MAGIC.to_le_bytes());\n\n        // 6. Call observer before writing — zero-copy reference into write_buf.\n        if let Some(cb) = on_serialization_complete {\n            cb(&self.write_buf, crc);\n        }\n\n        // 7. Copy write_buf into an I/O buffer and pwrite. write_buf keeps its allocation.\n        let buffer = Arc::new(Buffer::new(self.write_buf.to_vec()));\n        let c = Completion::new_write({\n            let buffer_len = buffer.len();\n            move |res: Result<i32, CompletionError>| {\n                let Ok(bytes_written) = res else {\n                    return;\n                };\n                turso_assert!(\n                    bytes_written == buffer_len as i32,\n                    \"wrote({bytes_written}) != expected({buffer_len})\"\n                );\n            }\n        });\n\n        let buffer_len = buffer.len();\n        let c = self.file.pwrite(self.offset, buffer, c)?;\n        if advance_offset_immediately {\n            self.offset += buffer_len as u64;\n            self.running_crc = crc;\n        } else {\n            self.pending_running_crc = Some(crc);\n        }\n        Ok((c, buffer_len as u64))\n    }\n\n    /// Serializes ops into `write_buf`, encrypting if an encryption context is set.\n    /// Returns the plaintext payload size (used in the TX header's `payload_size` field).\n    ///\n    /// Encrypted on-disk blob layout: ciphertext(plaintext_len + tag_size) | nonce(nonce_size)\n    fn serialize_ops_into_write_buf(\n        &mut self,\n        tx: &LogRecord,\n        op_count: u32,\n        commit_ts: u64,\n    ) -> Result<u64> {\n        if let Some(enc_ctx) = &self.encryption_ctx {\n            self.encryption_scratch_buffer.clear();\n            for row_version in &tx.row_versions {\n                serialize_op_entry(&mut self.encryption_scratch_buffer, row_version)?;\n            }\n            if let Some(hdr) = tx.header {\n                serialize_header_entry(&mut self.encryption_scratch_buffer, &hdr);\n            }\n            let payload_size = self.encryption_scratch_buffer.len() as u64;\n\n            // AAD = salt(8 LE) || payload_size(8 LE) || op_count(4 LE) || commit_ts(8 LE)\n            // Binds ciphertext to this specific frame so payloads cannot be swapped\n            // between transactions.\n            let salt = self\n                .header\n                .as_ref()\n                .expect(\"log header must be set before writing\")\n                .salt;\n            let mut aad = [0u8; 28];\n            aad[..8].copy_from_slice(&salt.to_le_bytes());\n            aad[8..16].copy_from_slice(&payload_size.to_le_bytes());\n            aad[16..20].copy_from_slice(&op_count.to_le_bytes());\n            aad[20..28].copy_from_slice(&commit_ts.to_le_bytes());\n\n            let (ciphertext, nonce) =\n                enc_ctx.encrypt_chunk(&self.encryption_scratch_buffer, &aad)?;\n            // encrypt_chunk returns ciphertext with the auth tag appended, so its\n            // length must be exactly plaintext_len + tag_size.  The read path\n            // relies on this to split the on-disk blob back into (ciphertext+tag, nonce).\n            debug_assert_eq!(\n                ciphertext.len(),\n                self.encryption_scratch_buffer.len() + enc_ctx.tag_size(),\n                \"encrypt_chunk output size mismatch: expected plaintext({}) + tag({}), got {}\",\n                self.encryption_scratch_buffer.len(),\n                enc_ctx.tag_size(),\n                ciphertext.len(),\n            );\n            self.write_buf.extend_from_slice(&ciphertext);\n            self.write_buf.extend_from_slice(&nonce);\n            Ok(payload_size)\n        } else {\n            let payload_start = self.write_buf.len();\n            for row_version in &tx.row_versions {\n                serialize_op_entry(&mut self.write_buf, row_version)?;\n            }\n            if let Some(header) = tx.header {\n                serialize_header_entry(&mut self.write_buf, &header);\n            }\n            Ok((self.write_buf.len() - payload_start) as u64)\n        }\n    }\n\n    /// Writes a transaction to the log and immediately advances the writer offset.\n    /// Used for checkpoint-initiated writes where no two-phase commit is needed.\n    pub fn log_tx(&mut self, tx: &LogRecord) -> Result<Completion> {\n        let (c, _) = self.serialize_and_pwrite_tx(tx, true, None)?;\n        Ok(c)\n    }\n\n    /// Writes a transaction to the log but does NOT advance the writer offset.\n    /// Returns `(completion, bytes_written)`. The caller must call\n    /// `advance_offset_after_success(bytes)` after confirming the commit succeeded.\n    ///\n    /// If `on_serialization_complete` is provided, it is called with a zero-copy\n    /// reference to the serialized frame bytes and the running CRC after\n    /// serialization but before the disk write.\n    pub fn log_tx_deferred_offset(\n        &mut self,\n        tx: &LogRecord,\n        on_serialization_complete: OnSerializationComplete<'_>,\n    ) -> Result<(Completion, u64)> {\n        self.serialize_and_pwrite_tx(tx, false, on_serialization_complete)\n    }\n\n    pub fn advance_offset_after_success(&mut self, bytes: u64) {\n        self.offset = self\n            .offset\n            .checked_add(bytes)\n            .expect(\"logical log offset overflow\");\n        self.running_crc = self\n            .pending_running_crc\n            .take()\n            .expect(\"advance_offset_after_success called without pending deferred write\");\n    }\n\n    pub fn sync(&mut self, sync_type: FileSyncType) -> Result<Completion> {\n        let completion = Completion::new_sync(move |_| {\n            tracing::debug!(\"logical_log_sync finish\");\n        });\n        let c = self.file.sync(completion, sync_type)?;\n        Ok(c)\n    }\n\n    fn current_or_new_header(&self) -> Result<LogHeader> {\n        if let Some(header) = self.header.clone() {\n            return Ok(header);\n        }\n        if self.offset == 0 {\n            // Valid path: checkpoint can run before the first logical-log append.\n            return Ok(LogHeader::new(&self.io));\n        }\n        Err(LimboError::InternalError(\n            \"Logical log header not initialized\".to_string(),\n        ))\n    }\n\n    fn write_header(&mut self, mut header: LogHeader) -> Result<Completion> {\n        let header_bytes = header.encode();\n        header.hdr_crc32c = u32::from_le_bytes([\n            header_bytes[LOG_HDR_CRC_START],\n            header_bytes[LOG_HDR_CRC_START + 1],\n            header_bytes[LOG_HDR_CRC_START + 2],\n            header_bytes[LOG_HDR_CRC_START + 3],\n        ]);\n        self.header = Some(header);\n\n        let buffer = Arc::new(Buffer::new(header_bytes.to_vec()));\n        let c = Completion::new_write({\n            let buffer_len = buffer.len();\n            move |res: Result<i32, CompletionError>| {\n                let Ok(bytes_written) = res else {\n                    return;\n                };\n                turso_assert!(\n                    bytes_written == buffer_len as i32,\n                    \"wrote({bytes_written}) != expected({buffer_len})\"\n                );\n            }\n        });\n        self.file.pwrite(0, buffer, c)\n    }\n\n    pub fn update_header(&mut self) -> Result<Completion> {\n        let header = self.current_or_new_header()?;\n        self.write_header(header)\n    }\n\n    pub fn truncate(&mut self) -> Result<Completion> {\n        // Regenerate salt so stale frames (from before truncation) cannot validate\n        // against the new CRC chain.\n        let mut header = self.current_or_new_header()?;\n        header.salt = self.io.generate_random_number() as u64;\n        self.running_crc = derive_initial_crc(header.salt);\n        self.pending_running_crc = None;\n        self.header = Some(header);\n\n        let completion = Completion::new_trunc(move |result| {\n            if let Err(err) = result {\n                tracing::error!(\"logical_log_truncate failed: {}\", err);\n            }\n        });\n        let c = self.file.truncate(0, completion)?;\n        self.offset = 0;\n        Ok(c)\n    }\n}\n\n/// Serialize one op into `buffer`.\n/// Op layout: tag(1) | flags(1) | table_id(4, le i32) | payload_len(varint) | payload(variable)\nfn serialize_op_entry(buffer: &mut Vec<u8>, row_version: &RowVersion) -> Result<()> {\n    let is_delete = row_version.end.is_some();\n    let tag = match (&row_version.row.id.row_id, is_delete) {\n        (RowKey::Int(_), false) => OP_UPSERT_TABLE,\n        (RowKey::Int(_), true) => OP_DELETE_TABLE,\n        (RowKey::Record(_), false) => OP_UPSERT_INDEX,\n        (RowKey::Record(_), true) => OP_DELETE_INDEX,\n    };\n\n    let mut flags = 0u8;\n    if row_version.btree_resident {\n        flags |= OP_FLAG_BTREE_RESIDENT;\n    }\n\n    let table_id_i64: i64 = row_version.row.id.table_id.into();\n    turso_assert!(\n        table_id_i64 < 0,\n        \"table_id_i64 should be negative, but got {table_id_i64}\"\n    );\n    turso_assert!(\n        (i32::MIN as i64..=i32::MAX as i64).contains(&table_id_i64),\n        \"table_id_i64 out of i32 range: {table_id_i64}\"\n    );\n    let table_id_i32 = table_id_i64 as i32;\n\n    buffer.push(tag);\n    buffer.push(flags);\n    buffer.extend_from_slice(&table_id_i32.to_le_bytes());\n\n    match tag {\n        OP_UPSERT_TABLE => {\n            let RowKey::Int(rowid) = row_version.row.id.row_id else {\n                unreachable!(\"table ops must have RowKey::Int\")\n            };\n            let record_bytes = row_version.row.payload();\n            let rowid_u64 = rowid as u64;\n            let rowid_len = varint_len(rowid_u64);\n            let payload_len = rowid_len + record_bytes.len();\n            write_varint_to_vec(payload_len as u64, buffer);\n            write_varint_to_vec(rowid_u64, buffer);\n            buffer.extend_from_slice(record_bytes);\n        }\n        OP_DELETE_TABLE => {\n            let RowKey::Int(rowid) = row_version.row.id.row_id else {\n                unreachable!(\"table ops must have RowKey::Int\")\n            };\n            let rowid_u64 = rowid as u64;\n            let rowid_len = varint_len(rowid_u64);\n            write_varint_to_vec(rowid_len as u64, buffer);\n            write_varint_to_vec(rowid_u64, buffer);\n        }\n        OP_UPSERT_INDEX | OP_DELETE_INDEX => {\n            let key_bytes = row_version.row.payload();\n            write_varint_to_vec(key_bytes.len() as u64, buffer);\n            buffer.extend_from_slice(key_bytes);\n        }\n        _ => {\n            return Err(LimboError::InternalError(format!(\n                \"invalid logical log op tag: {tag}\"\n            )));\n        }\n    }\n\n    Ok(())\n}\n\nfn serialize_header_entry(buffer: &mut Vec<u8>, header: &DatabaseHeader) {\n    // Header op uses tag-only addressing (table_id=0, flags=0) and fixed payload length.\n    buffer.push(OP_UPDATE_HEADER);\n    buffer.push(0);\n    buffer.extend_from_slice(&0i32.to_le_bytes());\n    write_varint_to_vec(DatabaseHeader::SIZE as u64, buffer);\n    buffer.extend_from_slice(bytemuck::bytes_of(header));\n}\n\n/// Parse all ops from a decrypted plaintext buffer.\n/// Validates that `plaintext.len() == payload_size` and that every byte is consumed.\nfn parse_ops_from_plaintext(\n    plaintext: &[u8],\n    payload_size: usize,\n    op_count: u32,\n    commit_ts: u64,\n) -> Result<Vec<ParsedOp>> {\n    if plaintext.len() != payload_size {\n        return Err(LimboError::Corrupt(format!(\n            \"decrypted size ({}) != payload_size ({payload_size})\",\n            plaintext.len()\n        )));\n    }\n    let mut ops = Vec::with_capacity((op_count as usize).min(1024));\n    let mut cursor = 0usize;\n    for _ in 0..op_count {\n        match try_parse_one_op_from_buf(&plaintext[cursor..], commit_ts)? {\n            Some((op, consumed)) => {\n                cursor += consumed;\n                ops.push(op);\n            }\n            None => {\n                return Err(LimboError::Corrupt(\n                    \"incomplete op in decrypted payload\".into(),\n                ));\n            }\n        }\n    }\n    if cursor != plaintext.len() {\n        return Err(LimboError::Corrupt(format!(\n            \"trailing bytes after ops: consumed {cursor}, total {}\",\n            plaintext.len()\n        )));\n    }\n    Ok(ops)\n}\n\n/// Parse one op entry from a contiguous byte slice (no IO).\n/// Returns `Ok(Some((parsed_op, bytes_consumed)))` on success,\n/// `Ok(None)` when not enough bytes, or `Err` on structural corruption.\n///\n/// Op layout: tag(1) | flags(1) | table_id(4, le i32) | payload_len(varint) | payload(variable)\nfn try_parse_one_op_from_buf(buf: &[u8], commit_ts: u64) -> Result<Option<(ParsedOp, usize)>> {\n    if buf.len() < 6 {\n        return Ok(None);\n    }\n\n    let tag = buf[0];\n    let flags = buf[1];\n    let table_id_i32 = i32::from_le_bytes([buf[2], buf[3], buf[4], buf[5]]);\n\n    let table_id: Option<MVTableId> = match tag {\n        OP_UPSERT_TABLE | OP_DELETE_TABLE | OP_UPSERT_INDEX | OP_DELETE_INDEX => {\n            if flags & !OP_FLAG_BTREE_RESIDENT != 0 || table_id_i32 >= 0 {\n                return Err(LimboError::Corrupt(\n                    \"Invalid op flags or non-negative table_id\".into(),\n                ));\n            }\n            Some(MVTableId::from(table_id_i32 as i64))\n        }\n        OP_UPDATE_HEADER => {\n            if flags != 0 || table_id_i32 != 0 {\n                return Err(LimboError::Corrupt(\n                    \"Invalid UPDATE_HEADER flags/table_id\".into(),\n                ));\n            }\n            None\n        }\n        _ => return Err(LimboError::Corrupt(format!(\"Unknown op tag: {tag}\"))),\n    };\n    let btree_resident = (flags & OP_FLAG_BTREE_RESIDENT) != 0;\n\n    let Some((payload_len_u64, varint_bytes)) = read_varint_partial(&buf[6..])? else {\n        return Ok(None);\n    };\n    let payload_len = match usize::try_from(payload_len_u64) {\n        Ok(v) => v,\n        Err(_) => return Err(LimboError::Corrupt(\"payload_len overflows usize\".into())),\n    };\n\n    let fixed = 6 + varint_bytes;\n    let total = fixed + payload_len;\n    if buf.len() < total {\n        return Ok(None);\n    }\n\n    let payload = buf[fixed..total].to_vec();\n\n    let parsed_op = match tag {\n        OP_UPSERT_TABLE => {\n            let table_id = table_id.expect(\"table op must have table_id\");\n            let (rowid_u64, rowid_len) = read_varint(&payload)\n                .map_err(|_| LimboError::Corrupt(\"Bad rowid varint in UPSERT_TABLE\".into()))?;\n            if rowid_len > payload.len() {\n                return Err(LimboError::Corrupt(\"rowid_len > payload\".into()));\n            }\n            let mut p = payload;\n            let record_bytes = p.split_off(rowid_len);\n            let rowid = RowID::new(table_id, RowKey::Int(rowid_u64 as i64));\n            ParsedOp::UpsertTable {\n                table_id,\n                rowid,\n                record_bytes,\n                commit_ts,\n                btree_resident,\n            }\n        }\n        OP_DELETE_TABLE => {\n            let table_id = table_id.expect(\"table op must have table_id\");\n            let (rowid_u64, rowid_len) = read_varint(&payload)\n                .map_err(|_| LimboError::Corrupt(\"Bad rowid varint in DELETE_TABLE\".into()))?;\n            if rowid_len != payload.len() {\n                return Err(LimboError::Corrupt(\n                    \"DELETE_TABLE payload size mismatch\".into(),\n                ));\n            }\n            let rowid = RowID::new(table_id, RowKey::Int(rowid_u64 as i64));\n            ParsedOp::DeleteTable {\n                rowid,\n                commit_ts,\n                btree_resident,\n            }\n        }\n        OP_UPSERT_INDEX => ParsedOp::UpsertIndex {\n            table_id: table_id.expect(\"index op must have table_id\"),\n            payload,\n            commit_ts,\n            btree_resident,\n        },\n        OP_DELETE_INDEX => ParsedOp::DeleteIndex {\n            table_id: table_id.expect(\"index op must have table_id\"),\n            payload,\n            commit_ts,\n            btree_resident,\n        },\n        OP_UPDATE_HEADER => {\n            if payload.len() != DatabaseHeader::SIZE {\n                return Err(LimboError::Corrupt(\n                    \"UPDATE_HEADER wrong payload size\".into(),\n                ));\n            }\n            let mut bytes = [0u8; DatabaseHeader::SIZE];\n            bytes.copy_from_slice(&payload);\n            let header = *bytemuck::from_bytes::<DatabaseHeader>(&bytes);\n            if header.magic != *b\"SQLite format 3\\0\" {\n                return Err(LimboError::Corrupt(\"UPDATE_HEADER bad SQLite magic\".into()));\n            }\n            ParsedOp::UpdateHeader { header, commit_ts }\n        }\n        _ => unreachable!(\"tag validated above\"),\n    };\n\n    Ok(Some((parsed_op, total)))\n}\n\n#[derive(Debug)]\npub enum StreamingResult {\n    UpsertTableRow {\n        row: Row,\n        rowid: RowID,\n        commit_ts: u64,\n        btree_resident: bool,\n    },\n    DeleteTableRow {\n        rowid: RowID,\n        commit_ts: u64,\n        btree_resident: bool,\n    },\n    UpsertIndexRow {\n        row: Row,\n        rowid: RowID,\n        commit_ts: u64,\n        btree_resident: bool,\n    },\n    DeleteIndexRow {\n        row: Row,\n        rowid: RowID,\n        commit_ts: u64,\n        btree_resident: bool,\n    },\n    UpdateHeader {\n        header: DatabaseHeader,\n        commit_ts: u64,\n    },\n    Eof,\n}\n\n#[derive(Clone, Copy, Debug)]\nenum StreamingState {\n    NeedTransactionStart,\n}\n\n/// Result of attempting to read and validate the logical log file header.\n#[derive(Debug, Clone)]\npub(crate) enum HeaderReadResult {\n    /// Header is well-formed: magic, version, flags, reserved, and CRC all valid.\n    Valid(LogHeader),\n    /// File is smaller than `LOG_HDR_SIZE` — no log exists (first run or truncated to zero).\n    NoLog,\n    /// Header exists but is corrupt (bad magic, version, flags, CRC, non-zero reserved, or truncated).\n    Invalid,\n}\n\npub struct StreamingLogicalLogReader {\n    file: Arc<dyn File>,\n    /// Offset to read from file\n    pub offset: usize,\n    /// Log Header\n    header: Option<LogHeader>,\n    /// Cached buffer after io read\n    buffer: Arc<RwLock<Vec<u8>>>,\n    /// Position to read from loaded buffer\n    buffer_offset: usize,\n    file_size: usize,\n    state: StreamingState,\n    /// Buffer of parsed ops from the current transaction frame. `parse_next_transaction`\n    /// fills this; `next_record` drains one op at a time. Empty between transactions.\n    pending_ops: std::collections::VecDeque<ParsedOp>,\n    /// Byte offset of the end of the last fully validated transaction frame. Used during\n    /// recovery to set the writer offset so that torn-tail bytes are overwritten on next append.\n    last_valid_offset: usize,\n    /// Running CRC state for chained checksum validation. Seeded from the header salt;\n    /// updated after each successfully validated frame.\n    running_crc: u32,\n    encryption_ctx: Option<EncryptionContext>,\n}\n\nimpl StreamingLogicalLogReader {\n    pub fn new(file: Arc<dyn File>, encryption_ctx: Option<EncryptionContext>) -> Self {\n        let file_size = file.size().expect(\"failed to get file size\") as usize;\n        Self {\n            file,\n            offset: 0,\n            header: None,\n            buffer: Arc::new(RwLock::new(Vec::with_capacity(4096))),\n            buffer_offset: 0,\n            file_size,\n            state: StreamingState::NeedTransactionStart,\n            pending_ops: std::collections::VecDeque::new(),\n            last_valid_offset: 0,\n            running_crc: 0,\n            encryption_ctx,\n        }\n    }\n\n    pub(crate) fn header(&self) -> Option<&LogHeader> {\n        self.header.as_ref()\n    }\n\n    /// Returns the byte offset just past the last fully validated transaction frame.\n    /// After recovery, the log writer should resume from this offset so any torn-tail\n    /// bytes beyond it are overwritten by the next append.\n    pub fn last_valid_offset(&self) -> usize {\n        self.last_valid_offset\n    }\n\n    /// Returns the running CRC state after all validated frames. Used during recovery\n    /// to hand off the chain state to the writer so it can continue appending.\n    pub fn running_crc(&self) -> u32 {\n        self.running_crc\n    }\n\n    pub fn read_header(&mut self, io: &Arc<dyn crate::IO>) -> Result<()> {\n        match self.try_read_header(io)? {\n            HeaderReadResult::Valid(_) => Ok(()),\n            HeaderReadResult::NoLog => Err(LimboError::Corrupt(\n                \"Logical log header incomplete\".to_string(),\n            )),\n            HeaderReadResult::Invalid => Err(LimboError::Corrupt(\n                \"Logical log header corrupt\".to_string(),\n            )),\n        }\n    }\n\n    pub(crate) fn try_read_header(&mut self, io: &Arc<dyn crate::IO>) -> Result<HeaderReadResult> {\n        self.file_size = self.file.size()? as usize;\n        if self.file_size < LOG_HDR_SIZE {\n            return Ok(HeaderReadResult::NoLog);\n        }\n\n        let header_bytes = self.read_exact_at(io, 0, LOG_HDR_SIZE)?;\n        let hdr_len = u16::from_le_bytes([header_bytes[6], header_bytes[7]]) as usize;\n        if hdr_len != LOG_HDR_SIZE {\n            self.set_invalid_header_state();\n            return Ok(HeaderReadResult::Invalid);\n        }\n\n        match LogHeader::decode(&header_bytes) {\n            Ok(header) => {\n                self.running_crc = derive_initial_crc(header.salt);\n                self.header = Some(header.clone());\n                self.offset = hdr_len;\n                self.buffer.write().clear();\n                self.buffer_offset = 0;\n                self.last_valid_offset = hdr_len;\n                Ok(HeaderReadResult::Valid(header))\n            }\n            Err(LimboError::Corrupt(_)) => {\n                self.set_invalid_header_state();\n                Ok(HeaderReadResult::Invalid)\n            }\n            Err(err) => Err(err),\n        }\n    }\n\n    fn set_invalid_header_state(&mut self) {\n        self.header = None;\n        self.offset = LOG_HDR_SIZE;\n        self.buffer.write().clear();\n        self.buffer_offset = 0;\n        self.last_valid_offset = LOG_HDR_SIZE;\n    }\n\n    /// Reads next record in log.\n    pub fn next_record(\n        &mut self,\n        io: &Arc<dyn crate::IO>,\n        mut get_index_info: impl FnMut(MVTableId) -> Result<Arc<IndexInfo>>,\n    ) -> Result<StreamingResult> {\n        if let Some(op) = self.pending_ops.pop_front() {\n            return self.parsed_op_to_streaming(op, &mut get_index_info);\n        }\n\n        loop {\n            match self.state {\n                StreamingState::NeedTransactionStart => {\n                    if self.remaining_bytes() < TX_MIN_FRAME_SIZE {\n                        return Ok(StreamingResult::Eof);\n                    }\n\n                    let ops = match self.parse_next_transaction(io)? {\n                        ParseResult::Ops(ops) => ops,\n                        ParseResult::Eof | ParseResult::InvalidFrame => {\n                            return Ok(StreamingResult::Eof);\n                        }\n                    };\n\n                    if ops.is_empty() {\n                        continue;\n                    }\n                    self.pending_ops = ops.into();\n                    let op = self\n                        .pending_ops\n                        .pop_front()\n                        .expect(\"ops queue should not be empty\");\n                    return self.parsed_op_to_streaming(op, &mut get_index_info);\n                }\n            }\n        }\n    }\n\n    pub fn is_eof(&self) -> bool {\n        self.remaining_bytes() == 0\n    }\n\n    /// Parse an encrypted payload: read the single AEAD blob, decrypt, then parse ops from plaintext.\n    /// Encrypted on-disk blob layout: ciphertext(plaintext_len + tag_size) | nonce(nonce_size)\n    fn parse_encrypted_payload(\n        &mut self,\n        io: &Arc<dyn crate::IO>,\n        op_count: u32,\n        payload_size: usize,\n        commit_ts: u64,\n        running_crc: u32,\n    ) -> Result<PayloadParseResult> {\n        let enc = self\n            .encryption_ctx\n            .as_ref()\n            .expect(\"encryption_ctx must be set for encrypted payload\");\n        let nonce_size = enc.nonce_size();\n        let tag_size = enc.tag_size();\n\n        let on_disk_size = match payload_size\n            .checked_add(tag_size)\n            .and_then(|s| s.checked_add(nonce_size))\n        {\n            Some(s) => s,\n            None => return Ok(PayloadParseResult::InvalidFrame),\n        };\n\n        let blob = match self.try_consume_bytes(io, on_disk_size)? {\n            Some(b) => b,\n            None => return Ok(PayloadParseResult::Eof),\n        };\n        let running_crc = crc32c::crc32c_append(running_crc, &blob);\n\n        // AAD = salt(8 LE) || payload_size(8 LE) || op_count(4 LE) || commit_ts(8 LE)\n        // Must match the write path exactly.\n        let salt = self\n            .header\n            .as_ref()\n            .expect(\"log header must be read before parsing\")\n            .salt;\n        let mut aad = [0u8; 28];\n        aad[..8].copy_from_slice(&salt.to_le_bytes());\n        aad[8..16].copy_from_slice(&(payload_size as u64).to_le_bytes());\n        aad[16..20].copy_from_slice(&op_count.to_le_bytes());\n        aad[20..28].copy_from_slice(&commit_ts.to_le_bytes());\n\n        let ciphertext = &blob[..payload_size + tag_size];\n        let nonce = &blob[payload_size + tag_size..];\n\n        let enc_ctx = self\n            .encryption_ctx\n            .as_ref()\n            .expect(\"encryption_ctx must be set for encrypted payload\");\n        let plaintext = match enc_ctx.decrypt_chunk(ciphertext, nonce, &aad) {\n            Ok(p) => p,\n            Err(e) => {\n                tracing::warn!(\"decrypt_chunk failed: {e}\");\n                return Ok(PayloadParseResult::InvalidFrame);\n            }\n        };\n\n        match parse_ops_from_plaintext(&plaintext, payload_size, op_count, commit_ts) {\n            Ok(ops) => Ok(PayloadParseResult::Ok(ops, running_crc)),\n            Err(e) => {\n                tracing::warn!(\"encrypted frame parse error: {e}\");\n                Ok(PayloadParseResult::InvalidFrame)\n            }\n        }\n    }\n\n    /// Parse an unencrypted payload via field-by-field streaming IO reads.\n    fn parse_streaming_payload(\n        &mut self,\n        io: &Arc<dyn crate::IO>,\n        op_count: u32,\n        payload_size: usize,\n        commit_ts: u64,\n        mut running_crc: u32,\n    ) -> Result<PayloadParseResult> {\n        let mut parsed_ops = Vec::with_capacity((op_count as usize).min(1024));\n        let mut payload_bytes_read: u64 = 0;\n\n        for _ in 0..op_count {\n            // Op header (6 bytes): tag(1) | flags(1) | table_id(4, little-endian i32)\n            let op_bytes = match self.try_consume_fixed::<6>(io)? {\n                Some(bytes) => bytes,\n                None => return Ok(PayloadParseResult::Eof),\n            };\n            running_crc = crc32c::crc32c_append(running_crc, &op_bytes);\n            let tag = op_bytes[0];\n            let flags = op_bytes[1];\n            let table_id_i32 =\n                i32::from_le_bytes([op_bytes[2], op_bytes[3], op_bytes[4], op_bytes[5]]);\n            let table_id = match tag {\n                OP_UPSERT_TABLE | OP_DELETE_TABLE | OP_UPSERT_INDEX | OP_DELETE_INDEX => {\n                    if flags & !OP_FLAG_BTREE_RESIDENT != 0 || table_id_i32 >= 0 {\n                        return Ok(PayloadParseResult::InvalidFrame);\n                    }\n                    Some(MVTableId::from(table_id_i32 as i64))\n                }\n                OP_UPDATE_HEADER => {\n                    if flags != 0 || table_id_i32 != 0 {\n                        return Ok(PayloadParseResult::InvalidFrame);\n                    }\n                    None\n                }\n                _ => return Ok(PayloadParseResult::InvalidFrame),\n            };\n            let btree_resident = (flags & OP_FLAG_BTREE_RESIDENT) != 0;\n\n            let (payload_len, payload_len_bytes, payload_len_bytes_len) =\n                match self.consume_varint_bytes(io) {\n                    Ok(Some((value, bytes, len))) => (value, bytes, len),\n                    Ok(None) => return Ok(PayloadParseResult::Eof),\n                    Err(LimboError::Corrupt(_)) => return Ok(PayloadParseResult::InvalidFrame),\n                    Err(err) => return Err(err),\n                };\n            running_crc =\n                crc32c::crc32c_append(running_crc, &payload_len_bytes[..payload_len_bytes_len]);\n            let payload_len = match usize::try_from(payload_len) {\n                Ok(v) => v,\n                Err(e) => {\n                    tracing::warn!(\"payload_len overflows usize: {e}\");\n                    return Ok(PayloadParseResult::InvalidFrame);\n                }\n            };\n\n            let payload = match self.try_consume_bytes(io, payload_len)? {\n                Some(bytes) => bytes,\n                None => return Ok(PayloadParseResult::Eof),\n            };\n            running_crc = crc32c::crc32c_append(running_crc, &payload);\n\n            let op_total_bytes = 6 + payload_len_bytes_len + payload_len;\n            payload_bytes_read = match u64::try_from(op_total_bytes)\n                .ok()\n                .and_then(|op_size| payload_bytes_read.checked_add(op_size))\n            {\n                Some(v) => v,\n                None => return Ok(PayloadParseResult::InvalidFrame),\n            };\n\n            let parsed_op = match tag {\n                OP_UPSERT_TABLE => {\n                    let table_id = table_id.expect(\"table op must carry table id\");\n                    let (rowid_u64, rowid_len) = match read_varint(&payload) {\n                        Ok(v) => v,\n                        Err(e) => {\n                            tracing::warn!(\"failed to read rowid varint in upsert op: {e}\");\n                            return Ok(PayloadParseResult::InvalidFrame);\n                        }\n                    };\n                    let rowid_i64 = rowid_u64 as i64;\n                    if rowid_len > payload.len() {\n                        return Ok(PayloadParseResult::InvalidFrame);\n                    }\n                    let mut payload = payload;\n                    let record_bytes = payload.split_off(rowid_len);\n                    let rowid = RowID::new(table_id, RowKey::Int(rowid_i64));\n                    ParsedOp::UpsertTable {\n                        table_id,\n                        rowid,\n                        record_bytes,\n                        commit_ts,\n                        btree_resident,\n                    }\n                }\n                OP_DELETE_TABLE => {\n                    let table_id = table_id.expect(\"table op must carry table id\");\n                    let (rowid_u64, rowid_len) = match read_varint(&payload) {\n                        Ok(v) => v,\n                        Err(e) => {\n                            tracing::warn!(\"failed to read rowid varint in delete op: {e}\");\n                            return Ok(PayloadParseResult::InvalidFrame);\n                        }\n                    };\n                    if rowid_len != payload.len() {\n                        return Ok(PayloadParseResult::InvalidFrame);\n                    }\n                    let rowid_i64 = rowid_u64 as i64;\n                    let rowid = RowID::new(table_id, RowKey::Int(rowid_i64));\n                    ParsedOp::DeleteTable {\n                        rowid,\n                        commit_ts,\n                        btree_resident,\n                    }\n                }\n                OP_UPSERT_INDEX => {\n                    let table_id = table_id.expect(\"index op must carry table id\");\n                    ParsedOp::UpsertIndex {\n                        table_id,\n                        payload,\n                        commit_ts,\n                        btree_resident,\n                    }\n                }\n                OP_DELETE_INDEX => {\n                    let table_id = table_id.expect(\"index op must carry table id\");\n                    ParsedOp::DeleteIndex {\n                        table_id,\n                        payload,\n                        commit_ts,\n                        btree_resident,\n                    }\n                }\n                OP_UPDATE_HEADER => {\n                    if payload.len() != DatabaseHeader::SIZE {\n                        return Ok(PayloadParseResult::InvalidFrame);\n                    }\n                    let mut bytes = [0u8; DatabaseHeader::SIZE];\n                    bytes.copy_from_slice(&payload);\n                    let header = *bytemuck::from_bytes::<DatabaseHeader>(&bytes);\n                    if header.magic != *b\"SQLite format 3\\0\" {\n                        return Ok(PayloadParseResult::InvalidFrame);\n                    }\n                    ParsedOp::UpdateHeader { header, commit_ts }\n                }\n                _ => return Ok(PayloadParseResult::InvalidFrame),\n            };\n\n            parsed_ops.push(parsed_op);\n        }\n\n        if payload_size as u64 != payload_bytes_read {\n            return Ok(PayloadParseResult::InvalidFrame);\n        }\n\n        Ok(PayloadParseResult::Ok(parsed_ops, running_crc))\n    }\n\n    fn parse_next_transaction(&mut self, io: &Arc<dyn crate::IO>) -> Result<ParseResult> {\n        if self.remaining_bytes() < TX_MIN_FRAME_SIZE {\n            return Ok(ParseResult::Eof);\n        }\n        let frame_start = self.offset.saturating_sub(self.bytes_can_read());\n\n        let header_bytes = match self.try_consume_fixed::<TX_HEADER_SIZE>(io)? {\n            Some(bytes) => bytes,\n            None => return Ok(ParseResult::Eof),\n        };\n\n        // TX HEADER layout (24 bytes): FRAME_MAGIC(4) | payload_size(8) | op_count(4) | commit_ts(8)\n        let frame_magic = u32::from_le_bytes([\n            header_bytes[0],\n            header_bytes[1],\n            header_bytes[2],\n            header_bytes[3],\n        ]);\n        if frame_magic != FRAME_MAGIC {\n            self.last_valid_offset = frame_start;\n            return Ok(ParseResult::InvalidFrame);\n        }\n        let payload_size_u64 = u64::from_le_bytes([\n            header_bytes[4],\n            header_bytes[5],\n            header_bytes[6],\n            header_bytes[7],\n            header_bytes[8],\n            header_bytes[9],\n            header_bytes[10],\n            header_bytes[11],\n        ]);\n        let op_count = u32::from_le_bytes([\n            header_bytes[12],\n            header_bytes[13],\n            header_bytes[14],\n            header_bytes[15],\n        ]);\n        let commit_ts = u64::from_le_bytes([\n            header_bytes[16],\n            header_bytes[17],\n            header_bytes[18],\n            header_bytes[19],\n            header_bytes[20],\n            header_bytes[21],\n            header_bytes[22],\n            header_bytes[23],\n        ]);\n\n        let payload_size = match usize::try_from(payload_size_u64) {\n            Ok(v) => v,\n            Err(e) => {\n                tracing::warn!(\"payload_size overflows usize: {e}\");\n                self.last_valid_offset = frame_start;\n                return Ok(ParseResult::InvalidFrame);\n            }\n        };\n\n        // Chained CRC: seed from running_crc (derived from salt, or previous frame's CRC)\n        let running_crc = crc32c::crc32c_append(self.running_crc, &header_bytes);\n\n        // 2. Parse payload — branches for encrypted vs unencrypted.\n        let payload_result = if self.encryption_ctx.is_some() {\n            self.parse_encrypted_payload(io, op_count, payload_size, commit_ts, running_crc)?\n        } else {\n            self.parse_streaming_payload(io, op_count, payload_size, commit_ts, running_crc)?\n        };\n        let (parsed_ops, running_crc) = match payload_result {\n            PayloadParseResult::Ok(ops, crc) => (ops, crc),\n            PayloadParseResult::Eof => return Ok(ParseResult::Eof),\n            PayloadParseResult::InvalidFrame => {\n                self.last_valid_offset = frame_start;\n                return Ok(ParseResult::InvalidFrame);\n            }\n        };\n\n        // 3. TX TRAILER layout (8 bytes): crc32c(4, le u32) | END_MAGIC(4)\n        let trailer_bytes = match self.try_consume_fixed::<TX_TRAILER_SIZE>(io)? {\n            Some(bytes) => bytes,\n            None => return Ok(ParseResult::Eof),\n        };\n\n        let crc32c_expected = u32::from_le_bytes([\n            trailer_bytes[0],\n            trailer_bytes[1],\n            trailer_bytes[2],\n            trailer_bytes[3],\n        ]);\n        let end_magic = u32::from_le_bytes([\n            trailer_bytes[4],\n            trailer_bytes[5],\n            trailer_bytes[6],\n            trailer_bytes[7],\n        ]);\n\n        if crc32c_expected != running_crc {\n            self.last_valid_offset = frame_start;\n            return Ok(ParseResult::InvalidFrame);\n        }\n        if end_magic != END_MAGIC {\n            self.last_valid_offset = frame_start;\n            return Ok(ParseResult::InvalidFrame);\n        }\n\n        self.last_valid_offset = self.offset.saturating_sub(self.bytes_can_read());\n        // Advance the chain: this frame's CRC becomes the seed for the next frame.\n        self.running_crc = running_crc;\n        Ok(ParseResult::Ops(parsed_ops))\n    }\n\n    fn parsed_op_to_streaming(\n        &self,\n        parsed_op: ParsedOp,\n        get_index_info: &mut impl FnMut(MVTableId) -> Result<Arc<IndexInfo>>,\n    ) -> Result<StreamingResult> {\n        match parsed_op {\n            ParsedOp::UpsertTable {\n                table_id,\n                rowid,\n                record_bytes,\n                commit_ts,\n                btree_resident,\n            } => {\n                // Compute column_count from the serialized record so recovered rows keep\n                // the same shape metadata as non-recovered rows.\n                let column_count =\n                    crate::types::ImmutableRecord::from_bin_record(record_bytes.clone())\n                        .column_count();\n                let row = Row::new_table_row(\n                    RowID::new(table_id, rowid.row_id.clone()),\n                    record_bytes,\n                    column_count,\n                );\n                Ok(StreamingResult::UpsertTableRow {\n                    row,\n                    rowid,\n                    commit_ts,\n                    btree_resident,\n                })\n            }\n            ParsedOp::DeleteTable {\n                rowid,\n                commit_ts,\n                btree_resident,\n            } => Ok(StreamingResult::DeleteTableRow {\n                rowid,\n                commit_ts,\n                btree_resident,\n            }),\n            ParsedOp::UpsertIndex {\n                table_id,\n                payload,\n                commit_ts,\n                btree_resident,\n            } => {\n                let key_record = crate::types::ImmutableRecord::from_bin_record(payload);\n                let column_count = key_record.column_count();\n                let index_info = get_index_info(table_id)?;\n                let key = SortableIndexKey::new_from_record(key_record, index_info);\n                let rowid = RowID::new(table_id, RowKey::Record(key));\n                let row = Row::new_index_row(rowid.clone(), column_count);\n                Ok(StreamingResult::UpsertIndexRow {\n                    row,\n                    rowid,\n                    commit_ts,\n                    btree_resident,\n                })\n            }\n            ParsedOp::DeleteIndex {\n                table_id,\n                payload,\n                commit_ts,\n                btree_resident,\n            } => {\n                let key_record = crate::types::ImmutableRecord::from_bin_record(payload);\n                let column_count = key_record.column_count();\n                let index_info = get_index_info(table_id)?;\n                let key = SortableIndexKey::new_from_record(key_record, index_info);\n                let rowid = RowID::new(table_id, RowKey::Record(key));\n                let row = Row::new_index_row(rowid.clone(), column_count);\n                Ok(StreamingResult::DeleteIndexRow {\n                    row,\n                    rowid,\n                    commit_ts,\n                    btree_resident,\n                })\n            }\n            ParsedOp::UpdateHeader { header, commit_ts } => {\n                Ok(StreamingResult::UpdateHeader { header, commit_ts })\n            }\n        }\n    }\n\n    fn remaining_bytes(&self) -> usize {\n        let bytes_in_buffer = self.bytes_can_read();\n        let bytes_in_file = self.file_size.saturating_sub(self.offset);\n        bytes_in_buffer + bytes_in_file\n    }\n\n    fn try_consume_bytes(\n        &mut self,\n        io: &Arc<dyn crate::IO>,\n        amount: usize,\n    ) -> Result<Option<Vec<u8>>> {\n        if self.remaining_bytes() < amount {\n            return Ok(None);\n        }\n        self.read_more_data(io, amount)?;\n        let buffer = self.buffer.read();\n        let start = self.buffer_offset;\n        let end = start + amount;\n        let bytes = buffer[start..end].to_vec();\n        self.buffer_offset = end;\n        Ok(Some(bytes))\n    }\n\n    fn try_consume_fixed<const N: usize>(\n        &mut self,\n        io: &Arc<dyn crate::IO>,\n    ) -> Result<Option<[u8; N]>> {\n        if self.remaining_bytes() < N {\n            return Ok(None);\n        }\n        self.read_more_data(io, N)?;\n        let buffer = self.buffer.read();\n        let start = self.buffer_offset;\n        let end = start + N;\n        let mut out = [0u8; N];\n        out.copy_from_slice(&buffer[start..end]);\n        self.buffer_offset = end;\n        Ok(Some(out))\n    }\n\n    fn try_consume_u8(&mut self, io: &Arc<dyn crate::IO>) -> Result<Option<u8>> {\n        if self.remaining_bytes() == 0 {\n            return Ok(None);\n        }\n        self.read_more_data(io, 1)?;\n        let r = self.buffer.read()[self.buffer_offset];\n        self.buffer_offset += 1;\n        Ok(Some(r))\n    }\n\n    /// Reads a SQLite-format varint one byte at a time from the streaming reader.\n    /// Returns `(decoded_value, raw_bytes, byte_count)`. The raw bytes are returned\n    /// so callers can feed them into the CRC computation without re-encoding.\n    /// Unlike `read_varint` from sqlite3_ondisk (which requires a contiguous buffer),\n    /// this reads byte-by-byte via `try_consume_u8` to handle streaming I/O where\n    /// the varint may span a buffer boundary. Returns `None` on EOF (short read).\n    fn consume_varint_bytes(\n        &mut self,\n        io: &Arc<dyn crate::IO>,\n    ) -> Result<Option<(u64, [u8; 9], usize)>> {\n        let mut v: u64 = 0;\n        let mut bytes = [0u8; 9];\n        let mut len = 0usize;\n        for _ in 0..8 {\n            let Some(c) = self.try_consume_u8(io)? else {\n                return Ok(None);\n            };\n            bytes[len] = c;\n            len += 1;\n            v = (v << 7) + (c & 0x7f) as u64;\n            if (c & 0x80) == 0 {\n                return Ok(Some((v, bytes, len)));\n            }\n        }\n        let Some(c) = self.try_consume_u8(io)? else {\n            return Ok(None);\n        };\n        bytes[len] = c;\n        len += 1;\n        if (v >> 48) == 0 {\n            return Err(LimboError::Corrupt(\"Invalid varint\".to_string()));\n        }\n        v = (v << 8) + c as u64;\n        Ok(Some((v, bytes, len)))\n    }\n\n    fn read_exact_at(&self, io: &Arc<dyn crate::IO>, pos: u64, len: usize) -> Result<Vec<u8>> {\n        let header_buf = Arc::new(Buffer::new_temporary(len));\n        let out = Arc::new(RwLock::new(Vec::with_capacity(len)));\n        let out_clone = out.clone();\n        let completion: Box<ReadComplete> = Box::new(move |res| {\n            let out = out_clone.clone();\n            let mut out = out.write();\n            let Ok((buf, bytes_read)) = res else {\n                tracing::error!(\"couldn't read logical log header err={:?}\", res);\n                return None;\n            };\n            if bytes_read > 0 {\n                out.extend_from_slice(&buf.as_slice()[..bytes_read as usize]);\n            }\n            None\n        });\n        let c = Completion::new_read(header_buf, completion);\n        let c = self.file.pread(pos, c)?;\n        io.wait_for_completion(c)?;\n        let out = out.read().clone();\n        if out.len() != len {\n            return Err(LimboError::Corrupt(format!(\n                \"Logical log short read: expected {len}, got {}\",\n                out.len()\n            )));\n        }\n        Ok(out)\n    }\n\n    fn get_buffer(&self) -> crate::sync::RwLockReadGuard<'_, Vec<u8>> {\n        self.buffer.read()\n    }\n\n    /// Read at least `need` bytes from the logical log, issuing multiple reads if necessary.\n    /// If at any point 0 bytes are read, that indicates corruption.\n    pub fn read_more_data(&mut self, io: &Arc<dyn crate::IO>, need: usize) -> Result<()> {\n        let bytes_can_read = self.bytes_can_read();\n        if bytes_can_read >= need {\n            return Ok(());\n        }\n\n        let initial_buffer_offset = self.buffer_offset;\n\n        loop {\n            let buffer_size_before_read = self.buffer.read().len();\n            turso_assert!(\n                buffer_size_before_read >= self.buffer_offset,\n                \"buffer_size_before_read < buffer_offset\",\n                { \"buffer_size_before_read\": buffer_size_before_read, \"buffer_offset\": self.buffer_offset }\n            );\n            let bytes_available_in_buffer = buffer_size_before_read - self.buffer_offset;\n            let still_need = need.saturating_sub(bytes_available_in_buffer);\n\n            if still_need == 0 {\n                break;\n            }\n\n            turso_assert!(\n                self.file_size >= self.offset,\n                \"file_size < offset\",\n                { \"file_size\": self.file_size, \"offset\": self.offset }\n            );\n            let to_read = 4096.max(still_need).min(self.file_size - self.offset);\n\n            if to_read == 0 {\n                // No more data available in file even though we need more -> corrupt\n                return Err(LimboError::Corrupt(format!(\n                    \"Expected to read {still_need} bytes more but reached end of file at offset {}\",\n                    self.offset\n                )));\n            }\n\n            let header_buf = Arc::new(Buffer::new_temporary(to_read));\n            let buffer = self.buffer.clone();\n            let completion: Box<ReadComplete> = Box::new(move |res| match res {\n                Ok((buf, bytes_read)) => {\n                    let mut buffer = buffer.write();\n                    let buf = buf.as_slice();\n                    if bytes_read > 0 {\n                        buffer.extend_from_slice(&buf[..bytes_read as usize]);\n                    }\n                    None\n                }\n                Err(err) => Some(err),\n            });\n            let c = Completion::new_read(header_buf, completion);\n            let c = self.file.pread(self.offset as u64, c)?;\n            io.wait_for_completion(c)?;\n\n            let buffer_size_after_read = self.buffer.read().len();\n            let bytes_read = buffer_size_after_read - buffer_size_before_read;\n\n            if bytes_read == 0 {\n                return Err(LimboError::Corrupt(format!(\n                    \"Expected to read {still_need} bytes more but read 0 bytes at offset {}\",\n                    self.offset\n                )));\n            }\n\n            self.offset += bytes_read;\n        }\n\n        // Cleanup consumed bytes. If everything was consumed, clear avoids memmove.\n        let mut buffer = self.buffer.write();\n        if initial_buffer_offset >= buffer.len() {\n            buffer.clear();\n        } else if initial_buffer_offset > 0 {\n            let _ = buffer.drain(0..initial_buffer_offset);\n        }\n        self.buffer_offset = 0;\n        Ok(())\n    }\n\n    fn bytes_can_read(&self) -> usize {\n        self.buffer.read().len().saturating_sub(self.buffer_offset)\n    }\n}\n\n/// Result of parsing just the payload portion of a transaction frame.\n/// Used by `parse_encrypted_payload` and `parse_streaming_payload` to communicate\n/// back to `parse_next_transaction` without duplicating control flow.\nenum PayloadParseResult {\n    /// Successfully parsed ops and updated running CRC.\n    Ok(Vec<ParsedOp>, u32),\n    /// Not enough bytes to complete the payload.\n    Eof,\n    /// Payload data is structurally invalid.\n    InvalidFrame,\n}\n\n#[cfg_attr(test, derive(Debug))]\nenum ParseResult {\n    /// A fully validated transaction frame was parsed.\n    Ops(Vec<ParsedOp>),\n    /// True end-of-file: not enough bytes remain to form a complete frame.\n    Eof,\n    /// An invalid frame was encountered (bad magic, CRC mismatch, structural error).\n    /// Handled the same as EOF (stop scanning, keep previously validated frames),\n    /// but semantically distinct: the data exists but is not a valid frame.\n    /// `last_valid_offset` is set to the start of the invalid frame before returning this.\n    InvalidFrame,\n}\n\n#[cfg_attr(test, derive(Debug))]\nenum ParsedOp {\n    UpsertTable {\n        table_id: MVTableId,\n        rowid: RowID,\n        record_bytes: Vec<u8>,\n        commit_ts: u64,\n        btree_resident: bool,\n    },\n    DeleteTable {\n        rowid: RowID,\n        commit_ts: u64,\n        btree_resident: bool,\n    },\n    UpsertIndex {\n        table_id: MVTableId,\n        payload: Vec<u8>,\n        commit_ts: u64,\n        btree_resident: bool,\n    },\n    DeleteIndex {\n        table_id: MVTableId,\n        payload: Vec<u8>,\n        commit_ts: u64,\n        btree_resident: bool,\n    },\n    UpdateHeader {\n        header: DatabaseHeader,\n        commit_ts: u64,\n    },\n}\n\n#[cfg(test)]\nmod tests {\n    use std::collections::BTreeSet;\n    use std::sync::Once;\n\n    use quickcheck_macros::quickcheck;\n    use rand::{rng, Rng};\n    use rand_chacha::{\n        rand_core::{RngCore, SeedableRng},\n        ChaCha8Rng,\n    };\n\n    use crate::io::MemoryIO;\n    use crate::sync::Arc;\n    use crate::{\n        mvcc::database::{\n            tests::{commit_tx, generate_simple_string_row, MvccTestDbNoConn},\n            MVTableId, Row, RowID, RowKey, SortableIndexKey,\n        },\n        schema::Table,\n        storage::sqlite3_ondisk::{read_varint, varint_len, write_varint, DatabaseHeader},\n        types::{ImmutableRecord, IndexInfo, Text},\n        Buffer, Completion, LimboError, Value, ValueRef,\n    };\n\n    use super::{\n        HeaderReadResult, LogHeader, LogicalLog, FRAME_MAGIC, LOG_HDR_CRC_START,\n        LOG_HDR_RESERVED_START, LOG_HDR_SIZE, TX_HEADER_SIZE, TX_TRAILER_SIZE,\n    };\n    use super::{ParseResult, StreamingLogicalLogReader, StreamingResult};\n    use crate::OpenFlags;\n    use tracing_subscriber::EnvFilter;\n\n    fn init_tracing() {\n        static INIT: Once = Once::new();\n        INIT.call_once(|| {\n            let _ = tracing_subscriber::fmt()\n                .with_env_filter(EnvFilter::from_default_env())\n                .try_init();\n        });\n    }\n\n    fn write_single_table_tx(\n        io: &Arc<dyn crate::IO>,\n        file_name: &str,\n        commit_ts: u64,\n    ) -> (Arc<dyn crate::File>, usize) {\n        let file = io.open_file(file_name, OpenFlags::Create, false).unwrap();\n        let mut log = LogicalLog::new(file.clone(), io.clone(), None);\n\n        let mut tx = crate::mvcc::database::LogRecord {\n            tx_timestamp: commit_ts,\n            row_versions: Vec::new(),\n            header: None,\n        };\n        let row = generate_simple_string_row((-2).into(), 1, \"foo\");\n        let version = crate::mvcc::database::RowVersion {\n            id: 1,\n            begin: Some(crate::mvcc::database::TxTimestampOrID::Timestamp(commit_ts)),\n            end: None,\n            row: row.clone(),\n            btree_resident: false,\n        };\n        tx.row_versions.push(version);\n        let c = log.log_tx(&tx).unwrap();\n        io.wait_for_completion(c).unwrap();\n\n        let rowid_len = varint_len(1);\n        let payload_len = rowid_len + row.payload().len();\n        let payload_len_len = varint_len(payload_len as u64);\n        let op_size = 6 + payload_len_len + payload_len;\n        (file, op_size)\n    }\n\n    #[derive(Debug, Clone, PartialEq, Eq)]\n    enum ExpectedTableOp {\n        Upsert {\n            rowid: i64,\n            payload: Vec<u8>,\n            commit_ts: u64,\n            btree_resident: bool,\n        },\n        Delete {\n            rowid: i64,\n            commit_ts: u64,\n            btree_resident: bool,\n        },\n    }\n\n    fn read_table_ops(file: Arc<dyn crate::File>, io: &Arc<dyn crate::IO>) -> Vec<ExpectedTableOp> {\n        let mut reader = StreamingLogicalLogReader::new(file, None);\n        reader.read_header(io).unwrap();\n        let mut ops = Vec::new();\n        loop {\n            match reader\n                .next_record(io, |_id| {\n                    Err(LimboError::InternalError(\"no index\".to_string()))\n                })\n                .unwrap()\n            {\n                StreamingResult::UpsertTableRow {\n                    row,\n                    rowid,\n                    commit_ts,\n                    btree_resident,\n                } => {\n                    ops.push(ExpectedTableOp::Upsert {\n                        rowid: rowid.row_id.to_int_or_panic(),\n                        payload: row.payload().to_vec(),\n                        commit_ts,\n                        btree_resident,\n                    });\n                }\n                StreamingResult::DeleteTableRow {\n                    rowid,\n                    commit_ts,\n                    btree_resident,\n                } => {\n                    ops.push(ExpectedTableOp::Delete {\n                        rowid: rowid.row_id.to_int_or_panic(),\n                        commit_ts,\n                        btree_resident,\n                    });\n                }\n                StreamingResult::Eof => break,\n                other => panic!(\"unexpected record: {other:?}\"),\n            }\n        }\n        ops\n    }\n\n    #[allow(clippy::too_many_arguments)]\n    fn append_single_table_op_tx(\n        log: &mut LogicalLog,\n        io: &Arc<dyn crate::IO>,\n        table_id: crate::mvcc::database::MVTableId,\n        rowid: i64,\n        commit_ts: u64,\n        is_delete: bool,\n        btree_resident: bool,\n        payload_text: &str,\n    ) {\n        let row = generate_simple_string_row(table_id, rowid, payload_text);\n        let row_version = crate::mvcc::database::RowVersion {\n            id: commit_ts,\n            begin: Some(crate::mvcc::database::TxTimestampOrID::Timestamp(commit_ts)),\n            end: if is_delete {\n                Some(crate::mvcc::database::TxTimestampOrID::Timestamp(commit_ts))\n            } else {\n                None\n            },\n            row,\n            btree_resident,\n        };\n        let tx = crate::mvcc::database::LogRecord {\n            tx_timestamp: commit_ts,\n            row_versions: vec![row_version],\n            header: None,\n        };\n        let c = log.log_tx(&tx).unwrap();\n        io.wait_for_completion(c).unwrap();\n    }\n\n    fn decode_streaming_varint(bytes: &[u8]) -> crate::Result<Option<(u64, [u8; 9], usize)>> {\n        let io: Arc<dyn crate::IO> = Arc::new(MemoryIO::new());\n        let file = io\n            .open_file(\"logical_log_varint_decode_tmp\", OpenFlags::Create, false)\n            .unwrap();\n        let mut reader = StreamingLogicalLogReader::new(file, None);\n        reader.buffer.write().extend_from_slice(bytes);\n        reader.consume_varint_bytes(&io)\n    }\n\n    /// What this test checks: A committed transaction written to the logical log is replayed correctly after restart.\n    /// Why this matters: This is the baseline durability/recovery guarantee for MVCC commits.\n    #[test]\n    fn test_logical_log_read() {\n        init_tracing();\n        // Load a transaction\n        // let's not drop db as we don't want files to be removed\n        let mut db = MvccTestDbNoConn::new_with_random_db();\n        {\n            let conn = db.connect();\n            let pager = conn.pager.load().clone();\n            let mvcc_store = db.get_mvcc_store();\n            let table_id: MVTableId = (-100).into();\n            let tx_id = mvcc_store.begin_tx(pager).unwrap();\n            // insert table id -2 into sqlite_schema table (table_id -1)\n            let data = ImmutableRecord::from_values(\n                &[\n                    Value::Text(Text::new(\"table\")),  // type\n                    Value::Text(Text::new(\"test\")),   // name\n                    Value::Text(Text::new(\"test\")),   // tbl_name\n                    Value::from_i64(table_id.into()), // rootpage\n                    Value::Text(Text::new(\n                        \"CREATE TABLE test(id INTEGER PRIMARY KEY, data TEXT)\",\n                    )), // sql\n                ],\n                5,\n            );\n            mvcc_store\n                .insert(\n                    tx_id,\n                    Row::new_table_row(\n                        RowID::new((-1).into(), RowKey::Int(1000)),\n                        data.as_blob().to_vec(),\n                        5,\n                    ),\n                )\n                .unwrap();\n            // now insert a row into table -2\n            let row = generate_simple_string_row(table_id, 1, \"foo\");\n            mvcc_store.insert(tx_id, row).unwrap();\n            commit_tx(mvcc_store, &conn, tx_id).unwrap();\n        }\n\n        // Restart the database to trigger recovery\n        db.restart();\n\n        // Now try to read it back - recovery happens automatically during bootstrap\n        let conn = db.connect();\n        let pager = conn.pager.load().clone();\n        let mvcc_store = db.get_mvcc_store();\n        let tx = mvcc_store.begin_tx(pager).unwrap();\n        let row = mvcc_store\n            .read(tx, &RowID::new((-100).into(), RowKey::Int(1)))\n            .unwrap()\n            .unwrap();\n        let record = ImmutableRecord::from_bin_record(row.payload().to_vec());\n        let foo = record.iter().unwrap().next().unwrap().unwrap();\n        let ValueRef::Text(foo) = foo else {\n            unreachable!()\n        };\n        assert_eq!(foo.as_str(), \"foo\");\n    }\n\n    /// What this test checks: A long sequence of committed frames is replayed in order without dropping or reordering transactions.\n    /// Why this matters: Recovery must preserve commit order to maintain MVCC visibility semantics.\n    #[test]\n    fn test_logical_log_read_multiple_transactions() {\n        init_tracing();\n        let table_id: MVTableId = (-100).into();\n        let values = (0..100)\n            .map(|i| {\n                (\n                    RowID::new(table_id, RowKey::Int(i as i64)),\n                    format!(\"foo_{i}\"),\n                )\n            })\n            .collect::<Vec<(RowID, String)>>();\n        // let's not drop db as we don't want files to be removed\n        let mut db = MvccTestDbNoConn::new_with_random_db();\n        {\n            let conn = db.connect();\n            let pager = conn.pager.load().clone();\n            let mvcc_store = db.get_mvcc_store();\n\n            let tx_id = mvcc_store.begin_tx(pager.clone()).unwrap();\n            // insert table id -2 into sqlite_schema table (table_id -1)\n            let data = ImmutableRecord::from_values(\n                &[\n                    Value::Text(Text::new(\"table\")),  // type\n                    Value::Text(Text::new(\"test\")),   // name\n                    Value::Text(Text::new(\"test\")),   // tbl_name\n                    Value::from_i64(table_id.into()), // rootpage\n                    Value::Text(Text::new(\n                        \"CREATE TABLE test(id INTEGER PRIMARY KEY, data TEXT)\",\n                    )), // sql\n                ],\n                5,\n            );\n            mvcc_store\n                .insert(\n                    tx_id,\n                    Row::new_table_row(\n                        RowID::new((-1).into(), RowKey::Int(1000)),\n                        data.as_blob().to_vec(),\n                        5,\n                    ),\n                )\n                .unwrap();\n            commit_tx(mvcc_store.clone(), &conn, tx_id).unwrap();\n            // now insert a row into table -2\n            // generate insert per transaction\n            for (rowid, value) in &values {\n                let tx_id = mvcc_store.begin_tx(pager.clone()).unwrap();\n                let row = generate_simple_string_row(\n                    rowid.table_id,\n                    rowid.row_id.to_int_or_panic(),\n                    value,\n                );\n                mvcc_store.insert(tx_id, row).unwrap();\n                commit_tx(mvcc_store.clone(), &conn, tx_id).unwrap();\n            }\n        }\n\n        // Restart the database to trigger recovery\n        db.restart();\n\n        // Now try to read it back - recovery happens automatically during bootstrap\n        let conn = db.connect();\n        let pager = conn.pager.load().clone();\n        let mvcc_store = db.get_mvcc_store();\n        for (rowid, value) in &values {\n            let tx = mvcc_store.begin_tx(pager.clone()).unwrap();\n            let row = mvcc_store.read(tx, rowid).unwrap().unwrap();\n            let record = ImmutableRecord::from_bin_record(row.payload().to_vec());\n            let foo = record.iter().unwrap().next().unwrap().unwrap();\n            let ValueRef::Text(foo) = foo else {\n                unreachable!()\n            };\n            assert_eq!(foo.as_str(), value.as_str());\n        }\n    }\n\n    /// What this test checks: Randomized insert/delete workloads round-trip through write + restart replay with matching final contents.\n    /// Why this matters: Fuzz-style coverage catches edge combinations that hand-written examples miss.\n    #[test]\n    fn test_logical_log_read_fuzz() {\n        init_tracing();\n        let table_id: MVTableId = (-100).into();\n        let seed = rng().random();\n        let mut rng = ChaCha8Rng::seed_from_u64(seed);\n        let num_transactions = rng.next_u64() % 128;\n        let mut txns = vec![];\n        let mut present_rowids = BTreeSet::new();\n        let mut non_present_rowids = BTreeSet::new();\n        for _ in 0..num_transactions {\n            let num_operations = rng.next_u64() % 8;\n            let mut ops = vec![];\n            for _ in 0..num_operations {\n                let op_type = rng.next_u64() % 2;\n                match op_type {\n                    0 => {\n                        // Generate a positive rowid that fits in i64\n                        let row_id = (rng.next_u64() % (i64::MAX as u64)) as i64;\n                        let rowid = RowID::new(table_id, RowKey::Int(row_id));\n                        let row = generate_simple_string_row(\n                            rowid.table_id,\n                            rowid.row_id.to_int_or_panic(),\n                            &format!(\"row_{row_id}\"),\n                        );\n                        ops.push((true, Some(row), rowid.clone()));\n                        present_rowids.insert(rowid.clone());\n                        non_present_rowids.remove(&rowid);\n                        tracing::debug!(\"insert {rowid:?}\");\n                    }\n                    1 => {\n                        if present_rowids.is_empty() {\n                            continue;\n                        }\n                        let row_id_pos = rng.next_u64() as usize % present_rowids.len();\n                        let row_id = present_rowids.iter().nth(row_id_pos).unwrap().clone();\n                        ops.push((false, None, row_id.clone()));\n                        present_rowids.remove(&row_id);\n                        non_present_rowids.insert(row_id.clone());\n                        tracing::debug!(\"removed {row_id:?}\");\n                    }\n                    _ => unreachable!(),\n                }\n            }\n            txns.push(ops);\n        }\n        // let's not drop db as we don't want files to be removed\n        let mut db = MvccTestDbNoConn::new_with_random_db();\n        let pager = {\n            let conn = db.connect();\n            let pager = conn.pager.load().clone();\n            let mvcc_store = db.get_mvcc_store();\n\n            // insert table id -2 into sqlite_schema table (table_id -1)\n            let tx_id = mvcc_store.begin_tx(pager.clone()).unwrap();\n            let data = ImmutableRecord::from_values(\n                &[\n                    Value::Text(Text::new(\"table\")),  // type\n                    Value::Text(Text::new(\"test\")),   // name\n                    Value::Text(Text::new(\"test\")),   // tbl_name\n                    Value::from_i64(table_id.into()), // rootpage\n                    Value::Text(Text::new(\n                        \"CREATE TABLE test(id INTEGER PRIMARY KEY, data TEXT)\",\n                    )), // sql\n                ],\n                5,\n            );\n            mvcc_store\n                .insert(\n                    tx_id,\n                    Row::new_table_row(\n                        RowID::new((-1).into(), RowKey::Int(1000)),\n                        data.as_blob().to_vec(),\n                        5,\n                    ),\n                )\n                .unwrap();\n            commit_tx(mvcc_store.clone(), &conn, tx_id).unwrap();\n\n            // insert rows\n            for ops in &txns {\n                let tx_id = mvcc_store.begin_tx(pager.clone()).unwrap();\n                for (is_insert, maybe_row, rowid) in ops {\n                    if *is_insert {\n                        mvcc_store\n                            .insert(tx_id, maybe_row.as_ref().unwrap().clone())\n                            .unwrap();\n                    } else {\n                        mvcc_store.delete(tx_id, rowid.clone()).unwrap();\n                    }\n                }\n                commit_tx(mvcc_store.clone(), &conn, tx_id).unwrap();\n            }\n\n            conn.close().unwrap();\n            pager\n        };\n\n        db.restart();\n\n        // connect after restart should recover log.\n        let _conn = db.connect();\n        let mvcc_store = db.get_mvcc_store();\n\n        // Check rowids that weren't deleted\n        let tx = mvcc_store.begin_tx(pager.clone()).unwrap();\n        for present_rowid in present_rowids {\n            let row = mvcc_store.read(tx, &present_rowid).unwrap().unwrap();\n            let record = ImmutableRecord::from_bin_record(row.payload().to_vec());\n            let foo = record.iter().unwrap().next().unwrap().unwrap();\n            let ValueRef::Text(foo) = foo else {\n                unreachable!()\n            };\n\n            assert_eq!(\n                foo.as_str(),\n                format!(\"row_{}\", present_rowid.row_id.to_int_or_panic())\n            );\n        }\n\n        // Check rowids that were deleted\n        let tx = mvcc_store.begin_tx(pager).unwrap();\n        for present_rowid in non_present_rowids {\n            let row = mvcc_store.read(tx, &present_rowid).unwrap();\n            assert!(\n                row.is_none(),\n                \"row {present_rowid:?} should have been removed\"\n            );\n        }\n    }\n\n    /// What this test checks: Recovery rebuilds both table rows and index rows from logical-log operations.\n    /// Why this matters: Table/index divergence after restart would break query correctness.\n    #[test]\n    fn test_logical_log_read_table_and_index_rows() {\n        init_tracing();\n        // Test that both table rows and index rows can be read back after recovery\n        let mut db = MvccTestDbNoConn::new_with_random_db();\n        {\n            let conn = db.connect();\n\n            // Create a table with an index\n            conn.execute(\"CREATE TABLE test(id INTEGER PRIMARY KEY, data TEXT)\")\n                .unwrap();\n            conn.execute(\"CREATE INDEX idx_data ON test(data)\").unwrap();\n\n            // Checkpoint to ensure the index has a root_page mapping\n            conn.execute(\"PRAGMA wal_checkpoint(TRUNCATE)\").unwrap();\n\n            // Insert some data - this will create both table rows and index rows in the logical log\n            // Don't checkpoint after inserts so they remain in the logical log for recovery testing\n            conn.execute(\"INSERT INTO test(id, data) VALUES (1, 'foo')\")\n                .unwrap();\n            conn.execute(\"INSERT INTO test(id, data) VALUES (2, 'bar')\")\n                .unwrap();\n            conn.execute(\"INSERT INTO test(id, data) VALUES (3, 'baz')\")\n                .unwrap();\n        }\n\n        // Restart the database to trigger recovery\n        db.restart();\n\n        // Now verify that both table rows and index rows can be read back\n        let conn = db.connect();\n        let pager = conn.pager.load().clone();\n        let mvcc_store = db.get_mvcc_store();\n        let schema = conn.schema.read();\n        let table = schema.get_table(\"test\").expect(\"table test should exist\");\n        let Table::BTree(table) = table.as_ref() else {\n            panic!(\"table test should be btree\");\n        };\n        let table_id = mvcc_store.get_table_id_from_root_page(table.root_page);\n\n        // Get the index from schema\n        let index = schema\n            .get_index(\"test\", \"idx_data\")\n            .expect(\"Index should exist\");\n        // Use get_table_id_from_root_page to get the correct index_id (handles both checkpointed and non-checkpointed)\n        let index_id = mvcc_store.get_table_id_from_root_page(index.root_page);\n        let index_info = Arc::new(IndexInfo::new_from_index(index));\n\n        // Verify table rows can be read\n        let tx = mvcc_store.begin_tx(pager).unwrap();\n        for (row_id, expected_data) in [(1, \"foo\"), (2, \"bar\"), (3, \"baz\")] {\n            let row = mvcc_store\n                .read(tx, &RowID::new(table_id, RowKey::Int(row_id)))\n                .unwrap()\n                .expect(\"Table row should exist\");\n            let record = ImmutableRecord::from_bin_record(row.payload().to_vec());\n            let values = record.get_values().unwrap();\n            let data_value = values.get(1).expect(\"Should have data column\");\n            let ValueRef::Text(data_text) = data_value else {\n                panic!(\"Data column should be text\");\n            };\n            assert_eq!(data_text.as_str(), expected_data);\n        }\n\n        // Verify index rows can be read\n        // Note: Index rows are written to the logical log, but we need to construct the correct key format\n        // The index key format is (indexed_column_value, table_rowid)\n        for (row_id, data_value) in [(1, \"foo\"), (2, \"bar\"), (3, \"baz\")] {\n            // Create the index key: (data_value, rowid)\n            // The index on data column stores (data_value, table_rowid) as the key\n            let key_record = ImmutableRecord::from_values(\n                &[\n                    Value::Text(Text::new(data_value.to_string())),\n                    Value::from_i64(row_id),\n                ],\n                2,\n            );\n            let sortable_key = SortableIndexKey::new_from_record(key_record, index_info.clone());\n            let index_rowid = RowID::new(index_id, RowKey::Record(sortable_key));\n\n            // Use read_from_table_or_index to read the index row\n            // This verifies that index rows were properly serialized and deserialized from the logical log\n            let index_row_opt = mvcc_store\n                .read_from_table_or_index(tx, &index_rowid, Some(index_id))\n                .unwrap_or_else(|e| {\n                    panic!(\"Failed to read index row for ({}, {}): {:?}. Index ID: {:?}, root_page: {}\", \n                           data_value, row_id, e, index_id, index.root_page)\n                });\n\n            let Some(index_row) = index_row_opt else {\n                panic!(\"Index row for ({data_value}, {row_id}) not found after recovery. Index rows should be in the logical log.\");\n            };\n            // Verify the index row contains the correct data\n            let RowKey::Record(sortable_key) = index_row.id.row_id else {\n                panic!(\"Index row should have a record row_id\");\n            };\n            let record = sortable_key.key.clone();\n            let values = record.get_values().unwrap();\n            assert_eq!(\n                values.len(),\n                2,\n                \"Index row should have 2 columns (data, rowid)\"\n            );\n            let ValueRef::Text(index_data) = values[0] else {\n                panic!(\"First index column should be text\");\n            };\n            assert_eq!(index_data.as_str(), data_value, \"Index data should match\");\n            let ValueRef::Numeric(crate::numeric::Numeric::Integer(index_rowid_val)) = values[1]\n            else {\n                panic!(\"Second index column should be integer (rowid)\");\n            };\n            assert_eq!(index_rowid_val, row_id, \"Index rowid should match\");\n        }\n    }\n\n    /// What this test checks: If the last frame is torn, recovery keeps the valid prefix and ignores only the incomplete tail.\n    /// Why this matters: Crashes commonly leave partial EOF writes; we need safe prefix recovery instead of full failure.\n    #[test]\n    fn test_logical_log_torn_tail_stops_cleanly() {\n        init_tracing();\n        let io: Arc<dyn crate::IO> = Arc::new(MemoryIO::new());\n        let file = io\n            .open_file(\"test.db-log\", crate::OpenFlags::Create, false)\n            .unwrap();\n        let mut log = LogicalLog::new(file.clone(), io.clone(), None);\n\n        let row = generate_simple_string_row((-2).into(), 1, \"foo\");\n        let rowid_len = varint_len(1);\n        let payload_len = rowid_len + row.payload().len();\n        let payload_len_len = varint_len(payload_len as u64);\n        let op_size = 6 + payload_len_len + payload_len;\n        let frame_size = TX_HEADER_SIZE + op_size + TX_TRAILER_SIZE;\n\n        let mut tx1 = crate::mvcc::database::LogRecord {\n            tx_timestamp: 10,\n            row_versions: Vec::new(),\n            header: None,\n        };\n        tx1.row_versions.push(crate::mvcc::database::RowVersion {\n            id: 1,\n            begin: Some(crate::mvcc::database::TxTimestampOrID::Timestamp(10)),\n            end: None,\n            row: row.clone(),\n            btree_resident: false,\n        });\n        let c = log.log_tx(&tx1).unwrap();\n        io.wait_for_completion(c).unwrap();\n\n        let mut tx2 = crate::mvcc::database::LogRecord {\n            tx_timestamp: 20,\n            row_versions: Vec::new(),\n            header: None,\n        };\n        tx2.row_versions.push(crate::mvcc::database::RowVersion {\n            id: 2,\n            begin: Some(crate::mvcc::database::TxTimestampOrID::Timestamp(20)),\n            end: None,\n            row,\n            btree_resident: false,\n        });\n        let c = log.log_tx(&tx2).unwrap();\n        io.wait_for_completion(c).unwrap();\n\n        let file_size = file.size().unwrap() as usize;\n        let last_frame_start = LOG_HDR_SIZE + frame_size;\n\n        // Truncate the file at every offset within the last frame.\n        for cut in (last_frame_start..file_size).rev() {\n            let c = file\n                .truncate(cut as u64, Completion::new_trunc(|_| {}))\n                .unwrap();\n            io.wait_for_completion(c).unwrap();\n\n            let mut reader = StreamingLogicalLogReader::new(file.clone(), None);\n            reader.read_header(&io).unwrap();\n            let mut seen = 0;\n            loop {\n                match reader.next_record(&io, |_id| {\n                    Err(LimboError::InternalError(\"no index\".to_string()))\n                }) {\n                    Ok(StreamingResult::UpsertTableRow { .. }) => seen += 1,\n                    Ok(StreamingResult::Eof) => break,\n                    Ok(other) => panic!(\"unexpected record: {other:?}\"),\n                    Err(err) => panic!(\"unexpected error: {err:?}\"),\n                }\n            }\n            assert_eq!(seen, 1, \"should apply only the first transaction\");\n        }\n    }\n\n    /// What this test checks: With many frames, a torn tail still preserves all earlier complete frames.\n    /// Why this matters: Durable commits before the crash boundary must survive regardless of tail damage.\n    #[test]\n    fn test_logical_log_torn_tail_multiple_frames_stops_cleanly() {\n        init_tracing();\n        let io: Arc<dyn crate::IO> = Arc::new(MemoryIO::new());\n        let file = io\n            .open_file(\n                \"logical_log_torn_tail_multi_frame\",\n                OpenFlags::Create,\n                false,\n            )\n            .unwrap();\n        let mut log = LogicalLog::new(file.clone(), io.clone(), None);\n\n        append_single_table_op_tx(&mut log, &io, (-2).into(), 1, 1, false, false, \"a\");\n        append_single_table_op_tx(&mut log, &io, (-2).into(), 2, 2, false, false, \"b\");\n        let after_tx2 = log.offset as usize;\n        append_single_table_op_tx(&mut log, &io, (-2).into(), 3, 3, false, false, \"c\");\n        let after_tx3 = log.offset as usize;\n\n        let partial_tail_len = (after_tx3 - after_tx2) / 2;\n        let trunc_offset = (after_tx2 + partial_tail_len) as u64;\n        let c = file\n            .truncate(trunc_offset, Completion::new_trunc(|_| {}))\n            .unwrap();\n        io.wait_for_completion(c).unwrap();\n\n        let read_back = read_table_ops(file.clone(), &io);\n        assert_eq!(read_back.len(), 2);\n        assert_eq!(\n            read_back[0],\n            ExpectedTableOp::Upsert {\n                rowid: 1,\n                payload: generate_simple_string_row((-2).into(), 1, \"a\")\n                    .payload()\n                    .to_vec(),\n                commit_ts: 1,\n                btree_resident: false,\n            }\n        );\n        assert_eq!(\n            read_back[1],\n            ExpectedTableOp::Upsert {\n                rowid: 2,\n                payload: generate_simple_string_row((-2).into(), 2, \"b\")\n                    .payload()\n                    .to_vec(),\n                commit_ts: 2,\n                btree_resident: false,\n            }\n        );\n    }\n\n    /// What this test checks: The parser accepts the full valid negative table-id range, including i32::MIN.\n    /// Why this matters: Edge ID handling must be stable to avoid replay panics/corruption on valid inputs.\n    #[test]\n    fn test_logical_log_read_i32_min_table_id() {\n        init_tracing();\n        let io: Arc<dyn crate::IO> = Arc::new(MemoryIO::new());\n        let file = io\n            .open_file(\"logical_log_i32_min_table_id\", OpenFlags::Create, false)\n            .unwrap();\n        let mut log = LogicalLog::new(file.clone(), io.clone(), None);\n        let table_id = crate::mvcc::database::MVTableId::from(i32::MIN as i64);\n\n        append_single_table_op_tx(&mut log, &io, table_id, 7, 11, false, false, \"min\");\n\n        let mut reader = StreamingLogicalLogReader::new(file, None);\n        reader.read_header(&io).unwrap();\n        match reader\n            .next_record(&io, |_id| {\n                Err(LimboError::InternalError(\"no index\".to_string()))\n            })\n            .unwrap()\n        {\n            StreamingResult::UpsertTableRow { rowid, .. } => {\n                assert_eq!(rowid.table_id, table_id);\n                assert_eq!(rowid.row_id.to_int_or_panic(), 7);\n            }\n            other => panic!(\"unexpected record: {other:?}\"),\n        }\n    }\n\n    /// What this test checks: Rowid varint encoding/decoding is consistent for negative i64-style\n    /// values, and the deferred-offset write path (log_tx_deferred_offset) does not advance the\n    /// writer offset until advance_offset_after_success is called, after which all frames are\n    /// readable with a valid CRC chain.\n    /// Why this matters: Rowid decoding mismatches would replay to the wrong keys.\n    ///   The MVCC commit path uses deferred writes so an aborted commit can be silently overwritten;\n    ///   the offset must not advance before confirmation.\n    #[test]\n    fn test_logical_log_rowid_negative_varint_roundtrip() {\n        init_tracing();\n        let io: Arc<dyn crate::IO> = Arc::new(MemoryIO::new());\n        let file = io\n            .open_file(\n                \"logical_log_negative_rowid_roundtrip\",\n                OpenFlags::Create,\n                false,\n            )\n            .unwrap();\n        let mut log = LogicalLog::new(file.clone(), io.clone(), None);\n\n        append_single_table_op_tx(&mut log, &io, (-2).into(), -1, 1, false, false, \"neg\");\n        append_single_table_op_tx(&mut log, &io, (-2).into(), -1, 2, true, false, \"neg\");\n        let offset_after_frame2 = log.offset;\n\n        // Frame 3: deferred path — offset must not advance until confirmed.\n        let row3 = generate_simple_string_row((-2).into(), 3, \"deferred\");\n        let tx3 = crate::mvcc::database::LogRecord {\n            tx_timestamp: 3,\n            row_versions: vec![crate::mvcc::database::RowVersion {\n                id: 3,\n                begin: Some(crate::mvcc::database::TxTimestampOrID::Timestamp(3)),\n                end: None,\n                row: row3,\n                btree_resident: false,\n            }],\n            header: None,\n        };\n        let (c, bytes_written) = log.log_tx_deferred_offset(&tx3, None).unwrap();\n        io.wait_for_completion(c).unwrap();\n\n        assert_eq!(\n            log.offset, offset_after_frame2,\n            \"deferred write must not advance offset before advance_offset_after_success\"\n        );\n        log.advance_offset_after_success(bytes_written);\n        assert_eq!(\n            log.offset,\n            offset_after_frame2 + bytes_written,\n            \"offset must advance by exactly bytes_written after confirmation\"\n        );\n\n        let read_back = read_table_ops(file, &io);\n        assert_eq!(read_back.len(), 3);\n        match &read_back[0] {\n            ExpectedTableOp::Upsert { rowid, .. } => assert_eq!(*rowid, -1),\n            other => panic!(\"unexpected op: {other:?}\"),\n        }\n        match &read_back[1] {\n            ExpectedTableOp::Delete { rowid, .. } => assert_eq!(*rowid, -1),\n            other => panic!(\"unexpected op: {other:?}\"),\n        }\n        match &read_back[2] {\n            ExpectedTableOp::Upsert { rowid, .. } => assert_eq!(*rowid, 3),\n            other => panic!(\"unexpected op: {other:?}\"),\n        }\n    }\n\n    /// What this test checks: A payload bit flip in a fully present tail frame is ignored as invalid tail.\n    /// Why this matters: Availability-focused recovery keeps the valid prefix even when newest tail bytes are bad.\n    #[test]\n    fn test_logical_log_corruption_detected() {\n        init_tracing();\n        let io: Arc<dyn crate::IO> = Arc::new(MemoryIO::new());\n        let file = io\n            .open_file(\"corrupt.db-log\", crate::OpenFlags::Create, false)\n            .unwrap();\n        let mut log = LogicalLog::new(file.clone(), io.clone(), None);\n\n        let mut tx = crate::mvcc::database::LogRecord {\n            tx_timestamp: 123,\n            row_versions: Vec::new(),\n            header: None,\n        };\n        let row = generate_simple_string_row((-2).into(), 1, \"foo\");\n        let version = crate::mvcc::database::RowVersion {\n            id: 1,\n            begin: Some(crate::mvcc::database::TxTimestampOrID::Timestamp(123)),\n            end: None,\n            row,\n            btree_resident: false,\n        };\n        tx.row_versions.push(version);\n        let c = log.log_tx(&tx).unwrap();\n        io.wait_for_completion(c).unwrap();\n\n        // Flip one byte in the op data (varint payload_len).\n        let mut reader = StreamingLogicalLogReader::new(file.clone(), None);\n        reader.read_header(&io).unwrap();\n        // After read_header, reader.offset = LOG_HDR_SIZE.\n        // Skip frame header (TX_HEADER_SIZE) + fixed op prefix (tag+flags+table_id = 6 bytes).\n        let offset = reader.offset + TX_HEADER_SIZE + 6; // first byte of varint payload_len\n        let buf = Arc::new(Buffer::new(vec![0xFF]));\n        let c = file\n            .pwrite(offset as u64, buf, Completion::new_write(|_| {}))\n            .unwrap();\n        io.wait_for_completion(c).unwrap();\n\n        let mut reader = StreamingLogicalLogReader::new(file.clone(), None);\n        reader.read_header(&io).unwrap();\n        let res = reader.next_record(&io, |_id| {\n            Err(LimboError::InternalError(\"no index\".to_string()))\n        });\n        assert!(matches!(res.unwrap(), StreamingResult::Eof));\n    }\n\n    /// What this test checks: Malformed payload-length varint in newest frame is treated as invalid tail.\n    /// Why this matters: Recovery must preserve already-validated commits instead of failing hard.\n    #[test]\n    fn test_logical_log_payload_len_varint_corrupt_tail_keeps_prefix() {\n        init_tracing();\n        let io: Arc<dyn crate::IO> = Arc::new(MemoryIO::new());\n        let file = io\n            .open_file(\n                \"payload-len-varint-corrupt.db-log\",\n                OpenFlags::Create,\n                false,\n            )\n            .unwrap();\n        let mut log = LogicalLog::new(file.clone(), io.clone(), None);\n\n        append_single_table_op_tx(&mut log, &io, (-2).into(), 1, 1, false, false, \"first\");\n        let frame2_start = log.offset;\n        append_single_table_op_tx(&mut log, &io, (-2).into(), 2, 2, false, false, \"second\");\n\n        // Corrupt frame-2 payload_len varint into an invalid 9-byte varint sequence.\n        let payload_len_offset = frame2_start + (TX_HEADER_SIZE + 6) as u64;\n        let mut bad_varint = vec![0x80; 8];\n        bad_varint.push(0x00);\n        let c = file\n            .pwrite(\n                payload_len_offset,\n                Arc::new(Buffer::new(bad_varint)),\n                Completion::new_write(|_| {}),\n            )\n            .unwrap();\n        io.wait_for_completion(c).unwrap();\n\n        let read_back = read_table_ops(file, &io);\n        assert_eq!(read_back.len(), 1);\n        assert_eq!(\n            read_back[0],\n            ExpectedTableOp::Upsert {\n                rowid: 1,\n                payload: generate_simple_string_row((-2).into(), 1, \"first\")\n                    .payload()\n                    .to_vec(),\n                commit_ts: 1,\n                btree_resident: false,\n            }\n        );\n    }\n\n    /// What this test checks: Frames with invalid trailer end-magic are treated as invalid tail.\n    /// Why this matters: End-magic damage in newest bytes should not fail startup.\n    #[test]\n    fn test_logical_log_end_magic_corruption() {\n        init_tracing();\n        let io: Arc<dyn crate::IO> = Arc::new(MemoryIO::new());\n        let (file, op_size) = write_single_table_tx(&io, \"end-magic.db-log\", 100);\n        let trailer_offset = LOG_HDR_SIZE + TX_HEADER_SIZE + op_size;\n        // TX trailer layout: [crc32c(4)][END_MAGIC(4)]; END_MAGIC is at offset +4.\n        let bad = Arc::new(Buffer::new(0u32.to_le_bytes().to_vec()));\n        let c = file\n            .pwrite(\n                (trailer_offset + 4) as u64,\n                bad,\n                Completion::new_write(|_| {}),\n            )\n            .unwrap();\n        io.wait_for_completion(c).unwrap();\n\n        let mut reader = StreamingLogicalLogReader::new(file.clone(), None);\n        reader.read_header(&io).unwrap();\n        let res = reader.next_record(&io, |_id| {\n            Err(LimboError::InternalError(\"no index\".to_string()))\n        });\n        assert!(matches!(res.unwrap(), StreamingResult::Eof));\n    }\n\n    /// What this test checks: Header payload-size mismatch in the newest frame is treated as invalid tail.\n    /// Why this matters: Prefix-preserving recovery should not hard-fail on newest damaged frame.\n    #[test]\n    fn test_logical_log_payload_size_corruption() {\n        init_tracing();\n        let io: Arc<dyn crate::IO> = Arc::new(MemoryIO::new());\n        let (file, op_size) = write_single_table_tx(&io, \"payload-size.db-log\", 101);\n        // TX header layout: [FRAME_MAGIC(4)][payload_size(8)][op_count(4)][commit_ts(8)]\n        // payload_size is at byte 4 of the frame (right after FRAME_MAGIC).\n        let bad_payload_size = (op_size as u64 + 1).to_le_bytes().to_vec();\n        let bad = Arc::new(Buffer::new(bad_payload_size));\n        let c = file\n            .pwrite(\n                (LOG_HDR_SIZE + 4) as u64,\n                bad,\n                Completion::new_write(|_| {}),\n            )\n            .unwrap();\n        io.wait_for_completion(c).unwrap();\n\n        let mut reader = StreamingLogicalLogReader::new(file.clone(), None);\n        reader.read_header(&io).unwrap();\n        let res = reader.next_record(&io, |_id| {\n            Err(LimboError::InternalError(\"no index\".to_string()))\n        });\n        assert!(matches!(res.unwrap(), StreamingResult::Eof));\n    }\n\n    /// What this test checks: Invalid frame-magic at newest frame boundary is treated as invalid tail.\n    /// Why this matters: Recovery should stop at last valid frame instead of failing startup.\n    #[test]\n    fn test_logical_log_frame_magic_corruption() {\n        init_tracing();\n        let io: Arc<dyn crate::IO> = Arc::new(MemoryIO::new());\n        let (file, _) = write_single_table_tx(&io, \"frame-magic.db-log\", 103);\n\n        // TX header layout: [FRAME_MAGIC(4)][payload_size(8)][op_count(4)][commit_ts(8)]\n        // FRAME_MAGIC is at offset +0 from frame start.\n        let bad = Arc::new(Buffer::new(0u32.to_le_bytes().to_vec()));\n        let c = file\n            .pwrite(LOG_HDR_SIZE as u64, bad, Completion::new_write(|_| {}))\n            .unwrap();\n        io.wait_for_completion(c).unwrap();\n\n        let mut reader = StreamingLogicalLogReader::new(file.clone(), None);\n        reader.read_header(&io).unwrap();\n        let res = reader.next_record(&io, |_id| {\n            Err(LimboError::InternalError(\"no index\".to_string()))\n        });\n        assert!(matches!(res.unwrap(), StreamingResult::Eof));\n    }\n\n    /// What this test checks: Corrupting only the stored CRC field turns newest frame into invalid tail.\n    /// Why this matters: Prefix must remain replayable under tail checksum damage.\n    #[test]\n    fn test_logical_log_crc_field_corruption() {\n        init_tracing();\n        let io: Arc<dyn crate::IO> = Arc::new(MemoryIO::new());\n        let (file, op_size) = write_single_table_tx(&io, \"crc-field.db-log\", 104);\n        let trailer_offset = LOG_HDR_SIZE + TX_HEADER_SIZE + op_size;\n        // TX trailer layout: [crc32c(4)][END_MAGIC(4)]; crc32c is at offset +0.\n        let bad = Arc::new(Buffer::new(0u32.to_le_bytes().to_vec()));\n        let c = file\n            .pwrite(trailer_offset as u64, bad, Completion::new_write(|_| {}))\n            .unwrap();\n        io.wait_for_completion(c).unwrap();\n\n        let mut reader = StreamingLogicalLogReader::new(file.clone(), None);\n        reader.read_header(&io).unwrap();\n        let res = reader.next_record(&io, |_id| {\n            Err(LimboError::InternalError(\"no index\".to_string()))\n        });\n        assert!(matches!(res.unwrap(), StreamingResult::Eof));\n    }\n\n    /// What this test checks: A corrupted newest frame is dropped while older valid frames still replay.\n    /// Why this matters: Prefix-preserving behavior is required for SQLite-style availability recovery.\n    #[test]\n    fn test_logical_log_corrupt_tail_keeps_valid_prefix() {\n        init_tracing();\n        let io: Arc<dyn crate::IO> = Arc::new(MemoryIO::new());\n        let file = io\n            .open_file(\n                \"corrupt-tail-prefix.db-log\",\n                crate::OpenFlags::Create,\n                false,\n            )\n            .unwrap();\n        let mut log = LogicalLog::new(file.clone(), io.clone(), None);\n\n        append_single_table_op_tx(&mut log, &io, (-2).into(), 1, 10, false, false, \"a\");\n        let after_first = log.offset as usize;\n        append_single_table_op_tx(&mut log, &io, (-2).into(), 2, 20, false, false, \"b\");\n        let after_second = log.offset as usize;\n        let second_frame_len = after_second - after_first;\n\n        // TX trailer layout: [crc32c(4)][END_MAGIC(4)]; crc32c is at trailer offset +0.\n        let second_trailer_crc_offset = after_first + second_frame_len - TX_TRAILER_SIZE;\n        let c = file\n            .pwrite(\n                second_trailer_crc_offset as u64,\n                Arc::new(Buffer::new(vec![0xDE, 0xAD, 0xBE, 0xEF])),\n                Completion::new_write(|_| {}),\n            )\n            .unwrap();\n        io.wait_for_completion(c).unwrap();\n\n        let ops = read_table_ops(file, &io);\n        assert_eq!(\n            ops,\n            vec![ExpectedTableOp::Upsert {\n                rowid: 1,\n                payload: generate_simple_string_row((-2).into(), 1, \"a\")\n                    .payload()\n                    .to_vec(),\n                commit_ts: 10,\n                btree_resident: false,\n            }]\n        );\n    }\n\n    /// What this test checks: Corrupted file-header bytes are detected before replay starts.\n    /// Why this matters: Header trust is foundational for offsets and version checks.\n    #[test]\n    fn test_logical_log_header_corruption_detected() {\n        init_tracing();\n        let io: Arc<dyn crate::IO> = Arc::new(MemoryIO::new());\n        let file = io\n            .open_file(\"header-corrupt.db-log\", crate::OpenFlags::Create, false)\n            .unwrap();\n        let mut log = LogicalLog::new(file.clone(), io.clone(), None);\n        let tx = crate::mvcc::database::LogRecord {\n            tx_timestamp: 77,\n            row_versions: vec![],\n            header: None,\n        };\n        let c = log.log_tx(&tx).unwrap();\n        io.wait_for_completion(c).unwrap();\n\n        // Corrupt magic bytes in the file header.\n        let bad = Arc::new(Buffer::new(0u32.to_le_bytes().to_vec()));\n        let c = file.pwrite(0, bad, Completion::new_write(|_| {})).unwrap();\n        io.wait_for_completion(c).unwrap();\n\n        let mut reader = StreamingLogicalLogReader::new(file.clone(), None);\n        let res = reader.read_header(&io);\n        assert!(res.is_err());\n    }\n\n    /// What this test checks: Unknown/invalid header flag bits are rejected.\n    /// Why this matters: Fail-closed flag handling prevents old readers from misinterpreting new format states.\n    #[test]\n    fn test_logical_log_header_flags_rejected() {\n        init_tracing();\n        let io: Arc<dyn crate::IO> = Arc::new(MemoryIO::new());\n        let (file, _) = write_single_table_tx(&io, \"header-flags.db-log\", 105);\n\n        // Header flags byte at offset 5 must not have reserved bits set.\n        let c = file\n            .pwrite(\n                5,\n                Arc::new(Buffer::new(vec![0b0000_0010])),\n                Completion::new_write(|_| {}),\n            )\n            .unwrap();\n        io.wait_for_completion(c).unwrap();\n\n        let mut reader = StreamingLogicalLogReader::new(file.clone(), None);\n        let res = reader.read_header(&io);\n        assert!(res.is_err());\n    }\n\n    /// What this test checks: v2 headers must use the fixed 56-byte length and a known version byte.\n    /// Why this matters: Accepting larger lengths can misalign frame parsing and drop valid commits.\n    ///   Unknown versions must not be silently misread.\n    #[test]\n    fn test_logical_log_header_non_default_len_rejected() {\n        init_tracing();\n        let io: Arc<dyn crate::IO> = Arc::new(MemoryIO::new());\n        let (file, _) = write_single_table_tx(&io, \"header-len.db-log\", 106);\n\n        let header_buf = Arc::new(Buffer::new_temporary(LOG_HDR_SIZE));\n        let c = file\n            .pread(0, Completion::new_read(header_buf.clone(), |_| None))\n            .unwrap();\n        io.wait_for_completion(c).unwrap();\n        let original_header_bytes = header_buf.as_slice()[..LOG_HDR_SIZE].to_vec();\n\n        // Test 1: non-default header length (LOG_HDR_SIZE + 1) with valid CRC is rejected.\n        let mut header_bytes = original_header_bytes.clone();\n        header_bytes[6..8].copy_from_slice(&(LOG_HDR_SIZE as u16 + 1).to_le_bytes());\n        header_bytes[LOG_HDR_CRC_START..LOG_HDR_SIZE].fill(0);\n        let new_crc = crc32c::crc32c(&header_bytes);\n        header_bytes[LOG_HDR_CRC_START..LOG_HDR_SIZE].copy_from_slice(&new_crc.to_le_bytes());\n\n        let c = file\n            .pwrite(\n                0,\n                Arc::new(Buffer::new(header_bytes)),\n                Completion::new_write(|_| {}),\n            )\n            .unwrap();\n        io.wait_for_completion(c).unwrap();\n\n        let mut reader = StreamingLogicalLogReader::new(file.clone(), None);\n        let res = reader.read_header(&io);\n        assert!(res.is_err());\n\n        // Test 2: unknown version byte (99) with valid CRC is rejected as Invalid.\n        let mut header_bytes = original_header_bytes;\n        header_bytes[4] = 99; // unknown version\n        header_bytes[LOG_HDR_CRC_START..LOG_HDR_SIZE].fill(0);\n        let new_crc = crc32c::crc32c(&header_bytes);\n        header_bytes[LOG_HDR_CRC_START..LOG_HDR_SIZE].copy_from_slice(&new_crc.to_le_bytes());\n\n        let c = file\n            .pwrite(\n                0,\n                Arc::new(Buffer::new(header_bytes)),\n                Completion::new_write(|_| {}),\n            )\n            .unwrap();\n        io.wait_for_completion(c).unwrap();\n\n        let mut reader = StreamingLogicalLogReader::new(file, None);\n        let result = reader.try_read_header(&io).unwrap();\n        assert!(\n            matches!(result, HeaderReadResult::Invalid),\n            \"unknown version header must be rejected as Invalid, got {result:?}\"\n        );\n    }\n\n    /// What this test checks: Non-zero reserved bytes in the file header are rejected for this format version.\n    /// Why this matters: Reserved-region discipline preserves forward-compatibility and corruption detection.\n    #[test]\n    fn test_logical_log_header_reserved_bytes_rejected() {\n        init_tracing();\n        let io: Arc<dyn crate::IO> = Arc::new(MemoryIO::new());\n        let (file, _) = write_single_table_tx(&io, \"header-reserved.db-log\", 106);\n\n        // Read existing header bytes so we can corrupt reserved and recompute CRC.\n        let header_buf = Arc::new(Buffer::new_temporary(LOG_HDR_SIZE));\n        let c = file\n            .pread(0, Completion::new_read(header_buf.clone(), |_| None))\n            .unwrap();\n        io.wait_for_completion(c).unwrap();\n        let mut header_bytes = header_buf.as_slice()[..LOG_HDR_SIZE].to_vec();\n\n        // Corrupt reserved region (bytes 16-51). Reserved region starts at offset 16 (after salt at 8-15).\n        header_bytes[LOG_HDR_RESERVED_START] = 1;\n\n        // Recompute CRC with CRC field zeroed, then fill in the new CRC.\n        header_bytes[LOG_HDR_CRC_START..LOG_HDR_SIZE].fill(0);\n        let new_crc = crc32c::crc32c(&header_bytes);\n        header_bytes[LOG_HDR_CRC_START..LOG_HDR_SIZE].copy_from_slice(&new_crc.to_le_bytes());\n\n        // Write the corrupted header back.\n        let c = file\n            .pwrite(\n                0,\n                Arc::new(Buffer::new(header_bytes)),\n                Completion::new_write(|_| {}),\n            )\n            .unwrap();\n        io.wait_for_completion(c).unwrap();\n\n        let mut reader = StreamingLogicalLogReader::new(file.clone(), None);\n        let res = reader.read_header(&io);\n        assert!(res.is_err());\n    }\n\n    /// What this test checks: Unknown op reserved-flag bits in newest frame are treated as invalid tail.\n    /// Why this matters: Prefix frames must remain usable after tail damage.\n    #[test]\n    fn test_logical_log_op_reserved_flags_rejected() {\n        init_tracing();\n        let io: Arc<dyn crate::IO> = Arc::new(MemoryIO::new());\n        let (file, _) = write_single_table_tx(&io, \"op-flags.db-log\", 108);\n\n        // First op flags byte at frame offset: TX header + tag byte.\n        let c = file\n            .pwrite(\n                (LOG_HDR_SIZE + TX_HEADER_SIZE + 1) as u64,\n                Arc::new(Buffer::new(vec![0b0000_0010])),\n                Completion::new_write(|_| {}),\n            )\n            .unwrap();\n        io.wait_for_completion(c).unwrap();\n\n        let mut reader = StreamingLogicalLogReader::new(file.clone(), None);\n        reader.read_header(&io).unwrap();\n        let res = reader.next_record(&io, |_id| {\n            Err(LimboError::InternalError(\"no index\".to_string()))\n        });\n        assert!(matches!(res.unwrap(), StreamingResult::Eof));\n    }\n\n    /// What this test checks: Non-negative table_id in newest frame is treated as invalid tail.\n    /// Why this matters: Bad tail metadata should not make the entire log unreadable.\n    #[test]\n    fn test_logical_log_non_negative_table_id_rejected() {\n        init_tracing();\n        let io: Arc<dyn crate::IO> = Arc::new(MemoryIO::new());\n        let (file, _) = write_single_table_tx(&io, \"table-id-sign.db-log\", 109);\n\n        // First op table_id starts after tag+flags.\n        let c = file\n            .pwrite(\n                (LOG_HDR_SIZE + TX_HEADER_SIZE + 2) as u64,\n                Arc::new(Buffer::new(1i32.to_le_bytes().to_vec())),\n                Completion::new_write(|_| {}),\n            )\n            .unwrap();\n        io.wait_for_completion(c).unwrap();\n\n        let mut reader = StreamingLogicalLogReader::new(file.clone(), None);\n        reader.read_header(&io).unwrap();\n        let res = reader.next_record(&io, |_id| {\n            Err(LimboError::InternalError(\"no index\".to_string()))\n        });\n        assert!(matches!(res.unwrap(), StreamingResult::Eof));\n    }\n\n    /// What this test checks: Zero-operation frames are silently skipped by the reader, and a\n    /// LogRecord carrying a DatabaseHeader round-trips as UpdateHeader with all fields intact.\n    /// Why this matters: Edge-case frame shapes must remain parseable to keep format handling robust.\n    ///   UPDATE_HEADER is a distinct op type with its own fixed-size payload, zero-flags constraint,\n    ///   zero-table_id constraint, and magic validation — none of which the table/index op tests cover.\n    #[test]\n    fn test_logical_log_empty_transaction_frame() {\n        init_tracing();\n        let io: Arc<dyn crate::IO> = Arc::new(MemoryIO::new());\n        let file = io\n            .open_file(\"empty-tx.db-log\", crate::OpenFlags::Create, false)\n            .unwrap();\n        let mut log = LogicalLog::new(file.clone(), io.clone(), None);\n\n        // Frame 1: empty tx (no ops). The reader must skip it silently (ops.is_empty() → continue).\n        let tx = crate::mvcc::database::LogRecord {\n            tx_timestamp: 200,\n            row_versions: vec![],\n            header: None,\n        };\n        let c = log.log_tx(&tx).unwrap();\n        io.wait_for_completion(c).unwrap();\n\n        // Frame 2: header-only tx. DatabaseHeader::default() has the SQLite magic that passes\n        // the reader's magic validation check.\n        let commit_ts = 201u64;\n        let db_header = DatabaseHeader::default();\n        let header_tx = crate::mvcc::database::LogRecord {\n            tx_timestamp: commit_ts,\n            row_versions: vec![],\n            header: Some(db_header),\n        };\n        let c = log.log_tx(&header_tx).unwrap();\n        io.wait_for_completion(c).unwrap();\n\n        let mut reader = StreamingLogicalLogReader::new(file.clone(), None);\n        reader.read_header(&io).unwrap();\n\n        // The reader skips the empty frame and returns the UpdateHeader from frame 2.\n        let rec = reader\n            .next_record(&io, |_id| {\n                Err(LimboError::InternalError(\"no index\".to_string()))\n            })\n            .unwrap();\n        match rec {\n            StreamingResult::UpdateHeader {\n                header: recovered,\n                commit_ts: recovered_ts,\n            } => {\n                assert_eq!(recovered_ts, commit_ts);\n                assert_eq!(recovered.magic, db_header.magic);\n            }\n            other => panic!(\"expected UpdateHeader, got {other:?}\"),\n        }\n\n        // Nothing left after frame 2.\n        let eof = reader\n            .next_record(&io, |_id| {\n                Err(LimboError::InternalError(\"no index\".to_string()))\n            })\n            .unwrap();\n        assert!(matches!(eof, StreamingResult::Eof));\n    }\n\n    /// What this test checks: Every single-bit flip in a full frame is either detected or safely rejected.\n    /// Why this matters: This gives strong confidence that integrity checks catch realistic media faults.\n    #[test]\n    fn test_logical_log_bitflip_integrity_exhaustive_single_frame() {\n        init_tracing();\n        let io: Arc<dyn crate::IO> = Arc::new(MemoryIO::new());\n        let file = io\n            .open_file(\"bitflip.db-log\", crate::OpenFlags::Create, false)\n            .unwrap();\n        let mut log = LogicalLog::new(file.clone(), io.clone(), None);\n        let mut tx = crate::mvcc::database::LogRecord {\n            tx_timestamp: 300,\n            row_versions: Vec::new(),\n            header: None,\n        };\n        tx.row_versions.push(crate::mvcc::database::RowVersion {\n            id: 1,\n            begin: Some(crate::mvcc::database::TxTimestampOrID::Timestamp(300)),\n            end: None,\n            row: generate_simple_string_row((-2).into(), 42, \"flip\"),\n            btree_resident: false,\n        });\n        let c = log.log_tx(&tx).unwrap();\n        io.wait_for_completion(c).unwrap();\n\n        let size = file.size().unwrap() as usize;\n        let mut original = vec![0u8; size];\n        let read_buf = Arc::new(Buffer::new_temporary(size));\n        let c = file\n            .pread(0, Completion::new_read(read_buf.clone(), |_| None))\n            .unwrap();\n        io.wait_for_completion(c).unwrap();\n        original.copy_from_slice(&read_buf.as_slice()[..size]);\n\n        for (i, original_byte) in original.iter().enumerate().take(size).skip(LOG_HDR_SIZE) {\n            for bit in 0..8u8 {\n                let mutated = original_byte ^ (1 << bit);\n                let c = file\n                    .pwrite(\n                        i as u64,\n                        Arc::new(Buffer::new(vec![mutated])),\n                        Completion::new_write(|_| {}),\n                    )\n                    .unwrap();\n                io.wait_for_completion(c).unwrap();\n\n                let mut reader = StreamingLogicalLogReader::new(file.clone(), None);\n                reader.read_header(&io).unwrap();\n                let res = reader.next_record(&io, |_id| {\n                    Err(LimboError::InternalError(\"no index\".to_string()))\n                });\n                match res {\n                    Err(_) | Ok(StreamingResult::Eof) => {}\n                    Ok(other) => {\n                        panic!(\"bit flip at offset={i}, bit={bit} produced valid record: {other:?}\")\n                    }\n                }\n\n                let c = file\n                    .pwrite(\n                        i as u64,\n                        Arc::new(Buffer::new(vec![*original_byte])),\n                        Completion::new_write(|_| {}),\n                    )\n                    .unwrap();\n                io.wait_for_completion(c).unwrap();\n            }\n        }\n    }\n\n    /// What this test checks: Random table upsert/delete sequences round-trip through serialize + parse.\n    /// Why this matters: Randomized coverage validates invariants across many payload/order combinations.\n    #[test]\n    fn test_logical_log_roundtrip_random_table_ops() {\n        init_tracing();\n        let seed = 0xA11CE55u64;\n        let mut rng = ChaCha8Rng::seed_from_u64(seed);\n        let io: Arc<dyn crate::IO> = Arc::new(MemoryIO::new());\n        let file = io\n            .open_file(\"roundtrip-rand.db-log\", crate::OpenFlags::Create, false)\n            .unwrap();\n        let mut log = LogicalLog::new(file.clone(), io.clone(), None);\n\n        let mut expected = Vec::new();\n        for tx_i in 0..128u64 {\n            let mut tx = crate::mvcc::database::LogRecord {\n                tx_timestamp: 1_000 + tx_i,\n                row_versions: Vec::new(),\n                header: None,\n            };\n            let op_count = (rng.next_u64() % 4) as usize;\n            for _ in 0..op_count {\n                let rowid = (rng.next_u64() % 64) as i64 + 1;\n                let btree_resident = (rng.next_u32() & 1) == 1;\n                let is_delete = (rng.next_u32() & 1) == 1;\n                if is_delete {\n                    tx.row_versions.push(crate::mvcc::database::RowVersion {\n                        id: 0,\n                        begin: None,\n                        end: Some(crate::mvcc::database::TxTimestampOrID::Timestamp(\n                            tx.tx_timestamp,\n                        )),\n                        row: Row::new_table_row(\n                            RowID::new((-2).into(), RowKey::Int(rowid)),\n                            Vec::new(),\n                            0,\n                        ),\n                        btree_resident,\n                    });\n                    expected.push(ExpectedTableOp::Delete {\n                        rowid,\n                        commit_ts: tx.tx_timestamp,\n                        btree_resident,\n                    });\n                } else {\n                    let payload = format!(\"r-{tx_i}-{rowid}\");\n                    let row = generate_simple_string_row((-2).into(), rowid, &payload);\n                    tx.row_versions.push(crate::mvcc::database::RowVersion {\n                        id: 0,\n                        begin: Some(crate::mvcc::database::TxTimestampOrID::Timestamp(\n                            tx.tx_timestamp,\n                        )),\n                        end: None,\n                        row: row.clone(),\n                        btree_resident,\n                    });\n                    expected.push(ExpectedTableOp::Upsert {\n                        rowid,\n                        payload: row.payload().to_vec(),\n                        commit_ts: tx.tx_timestamp,\n                        btree_resident,\n                    });\n                }\n            }\n            let c = log.log_tx(&tx).unwrap();\n            io.wait_for_completion(c).unwrap();\n        }\n\n        // Large-payload frame: 30 rows × 200 bytes ≈ 6 KB — well above the 4096-byte internal\n        // read-chunk boundary. This verifies the reader stitches together multiple pread results\n        // correctly when a single frame spans chunk boundaries.\n        let large_commit_ts = 1_000 + 128u64;\n        let large_text: String = \"x\".repeat(200);\n        let mut large_tx = crate::mvcc::database::LogRecord {\n            tx_timestamp: large_commit_ts,\n            row_versions: Vec::new(),\n            header: None,\n        };\n        for rowid in 1..=30i64 {\n            let row = generate_simple_string_row((-3).into(), rowid, &large_text);\n            expected.push(ExpectedTableOp::Upsert {\n                rowid,\n                payload: row.payload().to_vec(),\n                commit_ts: large_commit_ts,\n                btree_resident: false,\n            });\n            large_tx\n                .row_versions\n                .push(crate::mvcc::database::RowVersion {\n                    id: rowid as u64,\n                    begin: Some(crate::mvcc::database::TxTimestampOrID::Timestamp(\n                        large_commit_ts,\n                    )),\n                    end: None,\n                    row,\n                    btree_resident: false,\n                });\n        }\n        let c = log.log_tx(&large_tx).unwrap();\n        io.wait_for_completion(c).unwrap();\n\n        let got = read_table_ops(file.clone(), &io);\n        assert_eq!(got, expected);\n    }\n\n    /// What this property checks: For arbitrary event sequences, write/read round-trip preserves operation intent.\n    /// Why this matters: Property checks broaden coverage beyond hand-crafted examples.\n    #[quickcheck]\n    fn prop_logical_log_roundtrip_sequence(events: Vec<(bool, i64, bool)>) -> bool {\n        let io: Arc<dyn crate::IO> = Arc::new(MemoryIO::new());\n        let file = match io.open_file(\n            \"logical_log_prop_roundtrip_sequence\",\n            OpenFlags::Create,\n            false,\n        ) {\n            Ok(f) => f,\n            Err(_) => return false,\n        };\n        let mut log = LogicalLog::new(file.clone(), io.clone(), None);\n        let mut expected = Vec::new();\n\n        for (idx, (is_delete, rowid, btree_resident)) in events.into_iter().take(64).enumerate() {\n            let commit_ts = (idx + 1) as u64;\n            let payload_text = format!(\"v{idx}\");\n            let row = generate_simple_string_row((-2).into(), rowid, &payload_text);\n            let row_version = crate::mvcc::database::RowVersion {\n                id: commit_ts,\n                begin: Some(crate::mvcc::database::TxTimestampOrID::Timestamp(commit_ts)),\n                end: if is_delete {\n                    Some(crate::mvcc::database::TxTimestampOrID::Timestamp(commit_ts))\n                } else {\n                    None\n                },\n                row: row.clone(),\n                btree_resident,\n            };\n            expected.push(if is_delete {\n                ExpectedTableOp::Delete {\n                    rowid,\n                    commit_ts,\n                    btree_resident,\n                }\n            } else {\n                ExpectedTableOp::Upsert {\n                    rowid,\n                    payload: row.payload().to_vec(),\n                    commit_ts,\n                    btree_resident,\n                }\n            });\n            let tx = crate::mvcc::database::LogRecord {\n                tx_timestamp: commit_ts,\n                row_versions: vec![row_version],\n                header: None,\n            };\n            let Ok(c) = log.log_tx(&tx) else {\n                return false;\n            };\n            if io.wait_for_completion(c).is_err() {\n                return false;\n            }\n        }\n\n        if expected.is_empty() {\n            return file.size().expect(\"file.size() failed\") == 0;\n        }\n\n        read_table_ops(file, &io) == expected\n    }\n\n    /// What this property checks: Streaming varint decode returns the original value for encoded inputs.\n    /// Why this matters: Varint correctness is required for rowid and payload-length decoding.\n    #[quickcheck]\n    fn prop_streaming_varint_roundtrip(value: u64) -> bool {\n        let mut encoded = [0u8; 9];\n        let len = write_varint(&mut encoded, value);\n        if len == 0 || len > 9 {\n            return false;\n        }\n        let encoded = &encoded[..len];\n\n        let parsed_streaming = match decode_streaming_varint(encoded) {\n            Ok(Some(v)) => v,\n            _ => return false,\n        };\n        let parsed_read = match read_varint(encoded) {\n            Ok(v) => v,\n            Err(_) => return false,\n        };\n\n        parsed_streaming.0 == value\n            && parsed_streaming.2 == len\n            && parsed_streaming.1[..len] == encoded[..]\n            && parsed_read.0 == value\n            && parsed_read.1 == len\n    }\n\n    /// What this property checks: The streaming varint decoder agrees with the reference decoder on the same bytes.\n    /// Why this matters: Decoder agreement reduces risk of split-brain parsing behavior.\n    #[quickcheck]\n    fn prop_streaming_varint_matches_read_varint(bytes: Vec<u8>) -> bool {\n        let bytes = if bytes.len() > 16 {\n            &bytes[..16]\n        } else {\n            bytes.as_slice()\n        };\n        let streaming = decode_streaming_varint(bytes);\n        let plain = read_varint(bytes);\n\n        match (streaming, plain) {\n            (Ok(Some((v1, b1, l1))), Ok((v2, l2))) => {\n                v1 == v2 && l1 == l2 && b1[..l1] == bytes[..l1]\n            }\n            (Ok(None), Err(_)) => true, // truncated varint in streaming path\n            (Err(_), Err(_)) => true,   // malformed varint in both paths\n            _ => false,\n        }\n    }\n\n    /// What this test checks: The btree_resident flag survives write/read round-trip unchanged,\n    /// and the on-disk frame header has the correct binary layout (FRAME_MAGIC at [0..4],\n    /// payload_size as u64 at [4..12]).\n    /// Why this matters: This flag affects tombstone and checkpoint behavior after recovery.\n    ///   The frame layout check is baseline confirmation that the serialized format is self-consistent.\n    #[test]\n    fn test_logical_log_btree_resident_roundtrip() {\n        init_tracing();\n        let io: Arc<dyn crate::IO> = Arc::new(MemoryIO::new());\n        let file = io\n            .open_file(\"btree.db-log\", crate::OpenFlags::Create, false)\n            .unwrap();\n        let mut log = LogicalLog::new(file.clone(), io.clone(), None);\n\n        let mut tx = crate::mvcc::database::LogRecord {\n            tx_timestamp: 55,\n            row_versions: Vec::new(),\n            header: None,\n        };\n        let mut row = generate_simple_string_row((-2).into(), 1, \"foo\");\n        row.id.table_id = (-2).into();\n        let version = crate::mvcc::database::RowVersion {\n            id: 1,\n            begin: Some(crate::mvcc::database::TxTimestampOrID::Timestamp(55)),\n            end: None,\n            row,\n            btree_resident: true,\n        };\n        tx.row_versions.push(version);\n        let c = log.log_tx(&tx).unwrap();\n        io.wait_for_completion(c).unwrap();\n\n        // Verify the on-disk frame header binary layout.\n        let frame_hdr_buf = Arc::new(Buffer::new_temporary(TX_HEADER_SIZE));\n        let c = file\n            .pread(\n                LOG_HDR_SIZE as u64,\n                Completion::new_read(frame_hdr_buf.clone(), |_| None),\n            )\n            .unwrap();\n        io.wait_for_completion(c).unwrap();\n        let frame_hdr = frame_hdr_buf.as_slice()[..TX_HEADER_SIZE].to_vec();\n        assert_eq!(\n            u32::from_le_bytes(frame_hdr[0..4].try_into().unwrap()),\n            FRAME_MAGIC,\n            \"FRAME_MAGIC at bytes [0..4]\"\n        );\n        assert!(\n            u64::from_le_bytes(frame_hdr[4..12].try_into().unwrap()) > 0,\n            \"payload_size at bytes [4..12] must be non-zero for a non-empty op\"\n        );\n\n        let mut reader = StreamingLogicalLogReader::new(file.clone(), None);\n        reader.read_header(&io).unwrap();\n        let rec = reader\n            .next_record(&io, |_id| {\n                Err(LimboError::InternalError(\"no index\".to_string()))\n            })\n            .unwrap();\n        match rec {\n            StreamingResult::UpsertTableRow { btree_resident, .. } => {\n                assert!(btree_resident);\n            }\n            _ => panic!(\"unexpected record\"),\n        }\n    }\n\n    /// What this test checks: Header rewrites remain durable and parseable across truncate/reopen cycles.\n    /// Why this matters: Recovery depends on header validity even when the log body is empty.\n    #[test]\n    fn test_logical_log_header_persistence() {\n        init_tracing();\n        let io: Arc<dyn crate::IO> = Arc::new(MemoryIO::new());\n        let file = io\n            .open_file(\"header.db-log\", crate::OpenFlags::Create, false)\n            .unwrap();\n        let mut log = LogicalLog::new(file.clone(), io.clone(), None);\n\n        let mut tx = crate::mvcc::database::LogRecord {\n            tx_timestamp: 10,\n            row_versions: Vec::new(),\n            header: None,\n        };\n        let row = generate_simple_string_row((-2).into(), 1, \"foo\");\n        let version = crate::mvcc::database::RowVersion {\n            id: 1,\n            begin: Some(crate::mvcc::database::TxTimestampOrID::Timestamp(10)),\n            end: None,\n            row,\n            btree_resident: false,\n        };\n        tx.row_versions.push(version);\n        let c = log.log_tx(&tx).unwrap();\n        io.wait_for_completion(c).unwrap();\n\n        let c = file\n            .truncate(LOG_HDR_SIZE as u64, Completion::new_trunc(|_| {}))\n            .unwrap();\n        io.wait_for_completion(c).unwrap();\n\n        let mut reader = StreamingLogicalLogReader::new(file.clone(), None);\n        reader.read_header(&io).unwrap();\n        let header = reader.header().unwrap();\n        // Verify the on-disk CRC matches a fresh computation over the header bytes\n        let encoded = header.encode();\n        let mut check_buf = [0u8; LOG_HDR_SIZE];\n        check_buf.copy_from_slice(&encoded);\n        check_buf[LOG_HDR_CRC_START..LOG_HDR_SIZE].copy_from_slice(&[0; 4]);\n        let expected_crc = crc32c::crc32c(&check_buf);\n        assert_eq!(header.hdr_crc32c, expected_crc);\n    }\n\n    /// What this test checks: Header encode/decode with CRC validation round-trips cleanly, including salt.\n    /// Why this matters: Header integrity verification must be deterministic across writes/restarts.\n    #[test]\n    fn test_logical_log_header_crc_roundtrip() {\n        init_tracing();\n        let io: Arc<dyn crate::IO> = Arc::new(MemoryIO::new());\n        let header = LogHeader::new(&io);\n        assert_ne!(header.salt, 0, \"salt should be non-zero from IO RNG\");\n        let bytes = header.encode();\n        // Verify CRC: zero out the CRC field and recompute\n        let mut check_buf = bytes;\n        check_buf[LOG_HDR_CRC_START..LOG_HDR_SIZE].copy_from_slice(&[0; 4]);\n        let expected_crc = crc32c::crc32c(&check_buf);\n        let decoded = LogHeader::decode(&bytes).unwrap();\n        assert_eq!(decoded.version, header.version);\n        assert_eq!(decoded.salt, header.salt);\n        assert_eq!(decoded.hdr_crc32c, expected_crc);\n    }\n\n    /// What this test checks: try_read_header classifies malformed headers as Invalid (recoverable path) instead of hard-failing immediately.\n    /// Why this matters: Bootstrap logic needs this distinction to decide between body-scan fallback and fatal errors.\n    #[test]\n    fn test_try_read_header_reports_invalid_not_corrupt() {\n        init_tracing();\n        let io: Arc<dyn crate::IO> = Arc::new(MemoryIO::new());\n        let file = io\n            .open_file(\n                \"try-read-header-invalid.db-log\",\n                crate::OpenFlags::Create,\n                false,\n            )\n            .unwrap();\n        let mut log = LogicalLog::new(file.clone(), io.clone(), None);\n\n        append_single_table_op_tx(&mut log, &io, (-2).into(), 1, 11, false, false, \"foo\");\n        let c = file\n            .pwrite(\n                0,\n                Arc::new(Buffer::new(vec![0])),\n                Completion::new_write(|_| {}),\n            )\n            .unwrap();\n        io.wait_for_completion(c).unwrap();\n\n        let mut reader = StreamingLogicalLogReader::new(file, None);\n        let result = reader.try_read_header(&io).unwrap();\n        assert!(matches!(result, HeaderReadResult::Invalid));\n    }\n\n    /// What this test checks: Truncation regenerates the salt and old frames can't validate with the new salt.\n    /// Why this matters: Salt rotation on truncation ensures stale data from a previous log epoch\n    /// cannot accidentally validate against the new CRC chain.\n    #[test]\n    fn test_truncation_regenerates_salt() {\n        init_tracing();\n        let io: Arc<dyn crate::IO> = Arc::new(MemoryIO::new());\n        let file = io\n            .open_file(\"salt-regen.db-log\", crate::OpenFlags::Create, false)\n            .unwrap();\n        let mut log = LogicalLog::new(file.clone(), io.clone(), None);\n\n        // Write a frame and capture the salt\n        append_single_table_op_tx(&mut log, &io, (-2).into(), 1, 10, false, false, \"a\");\n        let salt_before = log.header.as_ref().unwrap().salt;\n\n        // Truncate to 0 (simulates checkpoint truncation); header with new salt\n        // will be written together with the next frame.\n        let c = log.truncate().unwrap();\n        io.wait_for_completion(c).unwrap();\n\n        let salt_after = log.header.as_ref().unwrap().salt;\n        assert_ne!(salt_before, salt_after, \"salt must change on truncation\");\n        assert_eq!(log.offset, 0, \"offset must be 0 after truncation\");\n\n        // Write a new frame — this also writes the header with the new salt\n        append_single_table_op_tx(&mut log, &io, (-2).into(), 2, 20, false, false, \"b\");\n\n        // Reader should see only the new frame (old data was truncated)\n        let mut reader = StreamingLogicalLogReader::new(file, None);\n        assert!(matches!(\n            reader.try_read_header(&io).unwrap(),\n            HeaderReadResult::Valid(_)\n        ));\n        let header = reader.header().unwrap();\n        assert_eq!(header.salt, salt_after);\n\n        match reader.parse_next_transaction(&io) {\n            Ok(ParseResult::Ops(ops)) => {\n                assert!(!ops.is_empty(), \"expected at least one op\");\n            }\n            Ok(ParseResult::Eof) => panic!(\"expected ops, got EOF\"),\n            Ok(ParseResult::InvalidFrame) => panic!(\"expected ops, got InvalidFrame\"),\n            Err(e) => panic!(\"expected ops, got error: {e:?}\"),\n        }\n        assert!(matches!(\n            reader.parse_next_transaction(&io),\n            Ok(ParseResult::Eof)\n        ));\n    }\n\n    /// What this test checks: Corrupting frame 1 in a multi-frame log invalidates frame 2 even\n    /// though frame 2's bytes are intact, because the CRC chain is broken.\n    /// Why this matters: Chained CRC guarantees prefix integrity — any corruption stops the entire\n    /// suffix from validating, not just the corrupted frame.\n    #[test]\n    fn test_crc_chain_invalidates_suffix_on_corruption() {\n        init_tracing();\n        let io: Arc<dyn crate::IO> = Arc::new(MemoryIO::new());\n        let file = io\n            .open_file(\"crc-chain.db-log\", crate::OpenFlags::Create, false)\n            .unwrap();\n        let mut log = LogicalLog::new(file.clone(), io.clone(), None);\n\n        // Write 3 frames\n        append_single_table_op_tx(&mut log, &io, (-2).into(), 1, 10, false, false, \"aaa\");\n        let after_first = log.offset as usize;\n        append_single_table_op_tx(&mut log, &io, (-2).into(), 2, 20, false, false, \"bbb\");\n        append_single_table_op_tx(&mut log, &io, (-2).into(), 3, 30, false, false, \"ccc\");\n\n        // Without corruption, all 3 frames should read back\n        let mut reader = StreamingLogicalLogReader::new(file.clone(), None);\n        assert!(matches!(\n            reader.try_read_header(&io).unwrap(),\n            HeaderReadResult::Valid(_)\n        ));\n        let mut count = 0;\n        while let Ok(ParseResult::Ops(_)) = reader.parse_next_transaction(&io) {\n            count += 1;\n        }\n        assert_eq!(count, 3);\n\n        // Corrupt one byte in frame 1's payload (not the CRC field itself)\n        let corrupt_offset = LOG_HDR_SIZE + TX_HEADER_SIZE + 1; // inside frame 1 payload\n        let c = file\n            .pwrite(\n                corrupt_offset as u64,\n                Arc::new(Buffer::new(vec![0xFF])),\n                Completion::new_write(|_| {}),\n            )\n            .unwrap();\n        io.wait_for_completion(c).unwrap();\n\n        // Now frame 1 should fail CRC, and frames 2+3 should NOT be returned\n        // (chained CRC means the reader stops at the first invalid frame)\n        let mut reader = StreamingLogicalLogReader::new(file, None);\n        assert!(matches!(\n            reader.try_read_header(&io).unwrap(),\n            HeaderReadResult::Valid(_)\n        ));\n        // Frame 1 is corrupted — CRC mismatch on structurally complete frame\n        match reader.parse_next_transaction(&io) {\n            Ok(ParseResult::InvalidFrame) => {}\n            other => panic!(\"expected InvalidFrame after corrupted frame 1, got {other:?}\"),\n        }\n        // Verify we didn't somehow get frame 2 or 3\n        let valid_offset = reader.last_valid_offset();\n        assert!(\n            valid_offset <= after_first,\n            \"valid offset {valid_offset} should be <= first frame end {after_first}\",\n        );\n    }\n\n    /// What this test checks: A structurally valid tx frame from one log cannot be spliced\n    /// into another log and pass CRC validation, because the two logs have different salts\n    /// and therefore different CRC chains.\n    /// Why this matters: Salt-seeded chained CRC prevents cross-log frame replay attacks —\n    /// an adversary cannot copy frames between logs to forge commit history.\n    #[test]\n    fn test_splice_frame_from_different_log_rejected() {\n        init_tracing();\n        let io: Arc<dyn crate::IO> = Arc::new(MemoryIO::new());\n\n        // --- Log A: write one frame ---\n        let file_a = io\n            .open_file(\"splice-a.db-log\", crate::OpenFlags::Create, false)\n            .unwrap();\n        let mut log_a = LogicalLog::new(file_a.clone(), io.clone(), None);\n        append_single_table_op_tx(&mut log_a, &io, (-2).into(), 1, 10, false, false, \"aaa\");\n        let log_a_end = log_a.offset as usize;\n\n        // --- Log B: write one frame (different salt → different CRC chain) ---\n        let file_b = io\n            .open_file(\"splice-b.db-log\", crate::OpenFlags::Create, false)\n            .unwrap();\n        let mut log_b = LogicalLog::new(file_b.clone(), io.clone(), None);\n        append_single_table_op_tx(&mut log_b, &io, (-2).into(), 2, 20, false, false, \"bbb\");\n        let log_b_end = log_b.offset as usize;\n\n        // Verify the two logs have different salts\n        let salt_a = log_a.header.as_ref().unwrap().salt;\n        let salt_b = log_b.header.as_ref().unwrap().salt;\n        assert_ne!(\n            salt_a, salt_b,\n            \"two independent logs should have different salts\"\n        );\n\n        // Read raw frame bytes from log B (everything after the header)\n        let frame_b_len = log_b_end - LOG_HDR_SIZE;\n        let read_buf = Arc::new(Buffer::new_temporary(frame_b_len));\n        let c = file_b\n            .pread(\n                LOG_HDR_SIZE as u64,\n                Completion::new_read(read_buf.clone(), |_| None),\n            )\n            .unwrap();\n        io.wait_for_completion(c).unwrap();\n        let frame_b_bytes: Vec<u8> = read_buf.as_slice()[..frame_b_len].to_vec();\n\n        // Splice log B's frame onto the end of log A\n        let c = file_a\n            .pwrite(\n                log_a_end as u64,\n                Arc::new(Buffer::new(frame_b_bytes)),\n                Completion::new_write(|_| {}),\n            )\n            .unwrap();\n        io.wait_for_completion(c).unwrap();\n\n        // Read log A — should get 1 valid frame (A's own), then reject the spliced frame\n        let mut reader = StreamingLogicalLogReader::new(file_a, None);\n        assert!(matches!(\n            reader.try_read_header(&io).unwrap(),\n            HeaderReadResult::Valid(_)\n        ));\n\n        // Frame 1 from log A should validate fine\n        match reader.parse_next_transaction(&io) {\n            Ok(ParseResult::Ops(ops)) => assert!(!ops.is_empty()),\n            other => panic!(\"expected log A's frame to parse, got {other:?}\"),\n        }\n\n        // The spliced frame from log B should fail CRC validation\n        match reader.parse_next_transaction(&io) {\n            Ok(ParseResult::InvalidFrame) => {}\n            other => {\n                panic!(\"spliced frame from a different log should NOT validate, got {other:?}\")\n            }\n        }\n    }\n\n    fn test_enc_ctx() -> crate::storage::encryption::EncryptionContext {\n        use crate::storage::encryption::{CipherMode, EncryptionKey};\n        let key = EncryptionKey::Key128([0x42u8; 16]);\n        crate::storage::encryption::EncryptionContext::new(CipherMode::Aes128Gcm, &key, 4096)\n            .unwrap()\n    }\n\n    fn wrong_key_enc_ctx() -> crate::storage::encryption::EncryptionContext {\n        use crate::storage::encryption::{CipherMode, EncryptionKey};\n        let key = EncryptionKey::Key128([0xFFu8; 16]);\n        crate::storage::encryption::EncryptionContext::new(CipherMode::Aes128Gcm, &key, 4096)\n            .unwrap()\n    }\n\n    fn make_test_row_version(\n        table_id: MVTableId,\n        rowid: i64,\n        value: &str,\n        commit_ts: u64,\n    ) -> crate::mvcc::database::RowVersion {\n        let row = generate_simple_string_row(table_id, rowid, value);\n        crate::mvcc::database::RowVersion {\n            id: rowid as u64,\n            begin: Some(crate::mvcc::database::TxTimestampOrID::Timestamp(commit_ts)),\n            end: None,\n            row,\n            btree_resident: false,\n        }\n    }\n\n    /// Write an encrypted frame, verify the on-disk layout invariant\n    /// (blob = plaintext + tag_size + nonce_size), then read back and\n    /// verify roundtrip correctness with multiple ops.\n    #[test]\n    fn test_encrypted_log_roundtrip_and_layout() {\n        init_tracing();\n        let io: Arc<dyn crate::IO> = Arc::new(MemoryIO::new());\n        let file = io\n            .open_file(\"enc-roundtrip.db-log\", OpenFlags::Create, false)\n            .unwrap();\n        let table_id: MVTableId = (-2).into();\n        let enc_ctx = test_enc_ctx();\n        let tag_size = enc_ctx.tag_size();\n        let nonce_size = enc_ctx.nonce_size();\n\n        // Write one encrypted frame with 2 ops.\n        let mut log = LogicalLog::new(file.clone(), io.clone(), Some(enc_ctx.clone()));\n        let tx = crate::mvcc::database::LogRecord {\n            tx_timestamp: 100,\n            row_versions: vec![\n                make_test_row_version(table_id, 1, \"hello\", 100),\n                make_test_row_version(table_id, 2, \"world\", 100),\n            ],\n            header: None,\n        };\n        let c = log.log_tx(&tx).unwrap();\n        io.wait_for_completion(c).unwrap();\n\n        // ── Layout invariant check ──\n        // Read the raw TX header to extract payload_size.\n        let frame_hdr_buf = Arc::new(Buffer::new_temporary(TX_HEADER_SIZE));\n        let frame_hdr_out = Arc::new(crate::sync::RwLock::new(Vec::new()));\n        let out = frame_hdr_out.clone();\n        let c = Completion::new_read(\n            frame_hdr_buf,\n            Box::new(\n                move |res: std::result::Result<(Arc<Buffer>, i32), crate::CompletionError>| {\n                    let Ok((buf, n)) = res else { return None };\n                    out.write().extend_from_slice(&buf.as_slice()[..n as usize]);\n                    None\n                },\n            ),\n        );\n        let c = file.pread(LOG_HDR_SIZE as u64, c).unwrap();\n        io.wait_for_completion(c).unwrap();\n\n        let frame_hdr = frame_hdr_out.read();\n        assert_eq!(frame_hdr.len(), TX_HEADER_SIZE);\n        let payload_size = u64::from_le_bytes(frame_hdr[4..12].try_into().unwrap()) as usize;\n\n        let file_size = file.size().unwrap() as usize;\n        let encrypted_blob_size = file_size - LOG_HDR_SIZE - TX_HEADER_SIZE - TX_TRAILER_SIZE;\n        assert_eq!(\n            encrypted_blob_size,\n            payload_size + tag_size + nonce_size,\n            \"on-disk blob size ({encrypted_blob_size}) != \\\n             payload_size({payload_size}) + tag_size({tag_size}) + nonce_size({nonce_size})\"\n        );\n\n        // ── Roundtrip read ──\n        let mut reader = StreamingLogicalLogReader::new(file, Some(enc_ctx));\n        reader.read_header(&io).unwrap();\n\n        let ops = match reader.parse_next_transaction(&io).unwrap() {\n            ParseResult::Ops(ops) => ops,\n            other => panic!(\"expected Ops, got {other:?}\"),\n        };\n        assert_eq!(ops.len(), 2);\n\n        match &ops[0] {\n            super::ParsedOp::UpsertTable { rowid, .. } => {\n                assert_eq!(rowid.row_id, RowKey::Int(1));\n            }\n            other => panic!(\"expected UpsertTable, got {other:?}\"),\n        }\n        match &ops[1] {\n            super::ParsedOp::UpsertTable { rowid, .. } => {\n                assert_eq!(rowid.row_id, RowKey::Int(2));\n            }\n            other => panic!(\"expected UpsertTable, got {other:?}\"),\n        }\n\n        assert!(matches!(\n            reader.parse_next_transaction(&io).unwrap(),\n            ParseResult::Eof\n        ));\n    }\n\n    #[test]\n    fn test_encrypted_log_multiple_frames_crc_chain() {\n        init_tracing();\n        let io: Arc<dyn crate::IO> = Arc::new(MemoryIO::new());\n        let file = io\n            .open_file(\"enc-multi.db-log\", OpenFlags::Create, false)\n            .unwrap();\n        let table_id: MVTableId = (-2).into();\n        let enc_ctx = test_enc_ctx();\n\n        let mut log = LogicalLog::new(file.clone(), io.clone(), Some(enc_ctx.clone()));\n        for i in 0..5u64 {\n            let tx = crate::mvcc::database::LogRecord {\n                tx_timestamp: 100 + i,\n                row_versions: vec![make_test_row_version(\n                    table_id,\n                    i as i64,\n                    &format!(\"val_{i}\"),\n                    100 + i,\n                )],\n                header: None,\n            };\n            let c = log.log_tx(&tx).unwrap();\n            io.wait_for_completion(c).unwrap();\n        }\n\n        let mut reader = StreamingLogicalLogReader::new(file, Some(enc_ctx));\n        reader.read_header(&io).unwrap();\n\n        for i in 0..5u64 {\n            let ops = match reader.parse_next_transaction(&io).unwrap() {\n                ParseResult::Ops(ops) => ops,\n                other => panic!(\"frame {i}: expected Ops, got {other:?}\"),\n            };\n            assert_eq!(ops.len(), 1, \"frame {i}\");\n            match &ops[0] {\n                super::ParsedOp::UpsertTable {\n                    rowid, commit_ts, ..\n                } => {\n                    assert_eq!(rowid.row_id, RowKey::Int(i as i64));\n                    assert_eq!(*commit_ts, 100 + i);\n                }\n                other => panic!(\"frame {i}: expected UpsertTable, got {other:?}\"),\n            }\n        }\n\n        assert!(matches!(\n            reader.parse_next_transaction(&io).unwrap(),\n            ParseResult::Eof\n        ));\n    }\n\n    /// AEAD integrity: wrong key and tampered ciphertext must both be rejected.\n    #[test]\n    fn test_encrypted_log_integrity_rejection() {\n        init_tracing();\n        let table_id: MVTableId = (-2).into();\n        let enc_ctx = test_enc_ctx();\n\n        // ── Wrong key ──\n        {\n            let io: Arc<dyn crate::IO> = Arc::new(MemoryIO::new());\n            let file = io\n                .open_file(\"enc-wrongkey.db-log\", OpenFlags::Create, false)\n                .unwrap();\n\n            let mut log = LogicalLog::new(file.clone(), io.clone(), Some(enc_ctx.clone()));\n            let tx = crate::mvcc::database::LogRecord {\n                tx_timestamp: 100,\n                row_versions: vec![make_test_row_version(table_id, 1, \"secret\", 100)],\n                header: None,\n            };\n            let c = log.log_tx(&tx).unwrap();\n            io.wait_for_completion(c).unwrap();\n\n            let mut reader = StreamingLogicalLogReader::new(file, Some(wrong_key_enc_ctx()));\n            reader.read_header(&io).unwrap();\n\n            match reader.parse_next_transaction(&io).unwrap() {\n                ParseResult::InvalidFrame => {}\n                other => panic!(\"expected InvalidFrame with wrong key, got {other:?}\"),\n            }\n        }\n\n        // ── Tampered TX header (commit_ts) ──\n        // commit_ts is part of the AAD, so flipping a byte in it causes AEAD\n        // decryption to fail even though the ciphertext itself is untouched.\n        {\n            let io: Arc<dyn crate::IO> = Arc::new(MemoryIO::new());\n            let file = io\n                .open_file(\"enc-hdr-tamper.db-log\", OpenFlags::Create, false)\n                .unwrap();\n\n            let mut log = LogicalLog::new(file.clone(), io.clone(), Some(enc_ctx.clone()));\n            let tx = crate::mvcc::database::LogRecord {\n                tx_timestamp: 100,\n                row_versions: vec![make_test_row_version(table_id, 1, \"hdr_tamper\", 100)],\n                header: None,\n            };\n            let c = log.log_tx(&tx).unwrap();\n            io.wait_for_completion(c).unwrap();\n\n            // Flip a byte in the commit_ts field (TX header offset 16..24, file offset = LOG_HDR + 16).\n            let corrupt_offset = (LOG_HDR_SIZE + 16) as u64;\n            let byte_buf = Arc::new(Buffer::new(vec![0xFF]));\n            let c = Completion::new_write(move |_| {});\n            io.wait_for_completion(file.pwrite(corrupt_offset, byte_buf, c).unwrap())\n                .unwrap();\n\n            let mut reader = StreamingLogicalLogReader::new(file, Some(enc_ctx.clone()));\n            reader.read_header(&io).unwrap();\n\n            match reader.parse_next_transaction(&io).unwrap() {\n                ParseResult::InvalidFrame => {}\n                other => panic!(\"expected InvalidFrame after TX header tamper, got {other:?}\"),\n            }\n        }\n\n        // ── Tampered ciphertext ──\n        {\n            let io: Arc<dyn crate::IO> = Arc::new(MemoryIO::new());\n            let file = io\n                .open_file(\"enc-tamper.db-log\", OpenFlags::Create, false)\n                .unwrap();\n\n            let mut log = LogicalLog::new(file.clone(), io.clone(), Some(enc_ctx.clone()));\n            let tx = crate::mvcc::database::LogRecord {\n                tx_timestamp: 100,\n                row_versions: vec![make_test_row_version(table_id, 1, \"tamper_me\", 100)],\n                header: None,\n            };\n            let c = log.log_tx(&tx).unwrap();\n            io.wait_for_completion(c).unwrap();\n\n            // Flip a byte in the ciphertext (after log header + TX header).\n            let corrupt_offset = (LOG_HDR_SIZE + TX_HEADER_SIZE + 1) as u64;\n            let byte_buf = Arc::new(Buffer::new(vec![0xFF]));\n            let c = Completion::new_write(move |_| {});\n            io.wait_for_completion(file.pwrite(corrupt_offset, byte_buf, c).unwrap())\n                .unwrap();\n\n            let mut reader = StreamingLogicalLogReader::new(file, Some(enc_ctx));\n            reader.read_header(&io).unwrap();\n\n            match reader.parse_next_transaction(&io).unwrap() {\n                ParseResult::InvalidFrame => {}\n                other => panic!(\"expected InvalidFrame after ciphertext tamper, got {other:?}\"),\n            }\n        }\n    }\n\n    #[test]\n    fn test_encrypted_log_torn_tail_rejected() {\n        init_tracing();\n        let io: Arc<dyn crate::IO> = Arc::new(MemoryIO::new());\n        let file = io\n            .open_file(\"enc-torn.db-log\", OpenFlags::Create, false)\n            .unwrap();\n        let table_id: MVTableId = (-2).into();\n        let enc_ctx = test_enc_ctx();\n\n        // Write 2 frames.\n        let mut log = LogicalLog::new(file.clone(), io.clone(), Some(enc_ctx.clone()));\n        for i in 0..2u64 {\n            let tx = crate::mvcc::database::LogRecord {\n                tx_timestamp: 100 + i,\n                row_versions: vec![make_test_row_version(table_id, i as i64, \"data\", 100 + i)],\n                header: None,\n            };\n            let c = log.log_tx(&tx).unwrap();\n            io.wait_for_completion(c).unwrap();\n        }\n\n        // Truncate mid-way through the second frame.\n        let file_size = file.size().unwrap();\n        let truncate_at = file_size - 5; // remove last 5 bytes\n        let c = Completion::new_trunc(|_| {});\n        io.wait_for_completion(file.truncate(truncate_at, c).unwrap())\n            .unwrap();\n\n        let mut reader = StreamingLogicalLogReader::new(file, Some(enc_ctx));\n        reader.read_header(&io).unwrap();\n\n        // First frame should parse fine.\n        match reader.parse_next_transaction(&io).unwrap() {\n            ParseResult::Ops(ops) => assert_eq!(ops.len(), 1),\n            other => panic!(\"expected Ops for frame 1, got {other:?}\"),\n        }\n\n        // Second frame is torn — should be EOF.\n        match reader.parse_next_transaction(&io).unwrap() {\n            ParseResult::Eof => {}\n            other => panic!(\"expected Eof for torn frame 2, got {other:?}\"),\n        }\n    }\n}\n"
  },
  {
    "path": "core/mvcc/persistent_storage/mod.rs",
    "content": "use crate::io::FileSyncType;\nuse crate::storage::encryption::EncryptionContext;\nuse crate::sync::atomic::{AtomicI64, AtomicU64, Ordering};\nuse crate::sync::Arc;\nuse crate::sync::RwLock;\nuse std::fmt::Debug;\n\npub mod logical_log;\nuse crate::mvcc::database::LogRecord;\nuse crate::mvcc::persistent_storage::logical_log::{\n    LogicalLog, OnSerializationComplete, DEFAULT_LOG_CHECKPOINT_THRESHOLD,\n};\nuse crate::{Completion, File, Result};\n\npub trait DurableStorage: Send + Sync + Debug {\n    /// Write a transaction to the logical log without advancing the writer offset.\n    ///\n    /// If `on_serialization_complete` is provided, it is called with a zero-copy\n    /// reference to the serialized frame bytes and the running CRC after\n    /// serialization but before the disk write. The callback runs while the\n    /// internal write lock is held, so it should be fast (e.g. memcpy to a side\n    /// buffer).\n    fn log_tx(\n        &self,\n        m: &LogRecord,\n        on_serialization_complete: OnSerializationComplete<'_>,\n    ) -> Result<(Completion, u64)>;\n\n    fn sync(&self, sync_type: FileSyncType) -> Result<Completion>;\n\n    /// Persist the current logical-log header to durable storage.\n    ///\n    /// This is used by MVCC recovery/checkpoint flows. Keeping this in the trait avoids\n    /// reaching into concrete storage internals.\n    fn update_header(&self) -> Result<Completion>;\n\n    fn truncate(&self) -> Result<Completion>;\n    fn get_logical_log_file(&self) -> Arc<dyn File>;\n    fn should_checkpoint(&self) -> bool;\n    /// Set the checkpoint threshold in bytes of logical-log data written.\n    /// A negative value disables automatic checkpointing.\n    fn set_checkpoint_threshold(&self, threshold: i64);\n    fn checkpoint_threshold(&self) -> i64;\n    fn advance_logical_log_offset_after_success(&self, bytes: u64);\n    fn restore_logical_log_state_after_recovery(&self, offset: u64, running_crc: u32);\n\n    /// Set the in-memory log header from a previously-read on-disk header.\n    ///\n    /// Called during recovery to seed the CRC state from the header's salt.\n    fn set_header(&self, header: logical_log::LogHeader);\n\n    /// Called when a checkpoint begins, before any rows are written to the B-tree.\n    /// `durable_txid_max` is the transaction watermark that will be durably persisted\n    /// once the checkpoint completes.\n    fn on_checkpoint_start(&self, _durable_txid_max: u64) {}\n\n    /// Called after the checkpoint has fully completed: rows are flushed, WAL is\n    /// truncated, and the logical log is reset.\n    fn on_checkpoint_end(&self, _durable_txid_max: u64) {}\n}\n\npub struct Storage {\n    pub logical_log: RwLock<LogicalLog>,\n    /// Shadowed from LogicalLog::offset for lock-free should_checkpoint() reads.\n    log_offset: AtomicU64,\n    checkpoint_threshold: AtomicI64,\n}\n\nimpl Storage {\n    pub fn new(\n        file: Arc<dyn File>,\n        io: Arc<dyn crate::IO>,\n        encryption_ctx: Option<EncryptionContext>,\n    ) -> Self {\n        Self {\n            logical_log: RwLock::new(LogicalLog::new(file, io, encryption_ctx)),\n            log_offset: AtomicU64::new(0),\n            checkpoint_threshold: AtomicI64::new(DEFAULT_LOG_CHECKPOINT_THRESHOLD),\n        }\n    }\n\n    /// Update the shadow offset to stay in sync with LogicalLog::offset.\n    /// Called after any operation that mutates the canonical offset under the write lock.\n    #[inline(always)]\n    fn shadow_offset_store(&self, value: u64) {\n        self.log_offset.store(value, Ordering::Relaxed);\n    }\n\n    #[inline(always)]\n    fn shadow_offset_advance(&self, bytes: u64) {\n        self.log_offset.fetch_add(bytes, Ordering::Relaxed);\n    }\n}\n\nimpl DurableStorage for Storage {\n    fn log_tx(\n        &self,\n        m: &LogRecord,\n        on_serialization_complete: OnSerializationComplete<'_>,\n    ) -> Result<(Completion, u64)> {\n        self.logical_log\n            .write()\n            .log_tx_deferred_offset(m, on_serialization_complete)\n    }\n\n    fn sync(&self, sync_type: FileSyncType) -> Result<Completion> {\n        self.logical_log.write().sync(sync_type)\n    }\n\n    fn update_header(&self) -> Result<Completion> {\n        self.logical_log.write().update_header()\n    }\n\n    fn truncate(&self) -> Result<Completion> {\n        let c = self.logical_log.write().truncate()?;\n        self.shadow_offset_store(0);\n        Ok(c)\n    }\n\n    fn get_logical_log_file(&self) -> Arc<dyn File> {\n        self.logical_log.write().file.clone()\n    }\n\n    /// Lock-free: reads shadowed atomics only.\n    fn should_checkpoint(&self) -> bool {\n        let threshold = self.checkpoint_threshold.load(Ordering::Relaxed);\n        if threshold < 0 {\n            return false;\n        }\n        self.log_offset.load(Ordering::Relaxed) >= threshold as u64\n    }\n\n    fn set_checkpoint_threshold(&self, threshold: i64) {\n        self.checkpoint_threshold\n            .store(threshold, Ordering::Relaxed);\n    }\n\n    fn checkpoint_threshold(&self) -> i64 {\n        self.checkpoint_threshold.load(Ordering::Relaxed)\n    }\n\n    fn advance_logical_log_offset_after_success(&self, bytes: u64) {\n        self.logical_log.write().advance_offset_after_success(bytes);\n        self.shadow_offset_advance(bytes);\n    }\n\n    fn restore_logical_log_state_after_recovery(&self, offset: u64, running_crc: u32) {\n        let mut log = self.logical_log.write();\n        log.offset = offset;\n        log.running_crc = running_crc;\n        self.shadow_offset_store(offset);\n    }\n\n    fn set_header(&self, header: logical_log::LogHeader) {\n        self.logical_log.write().set_header(header);\n    }\n}\n\nimpl Debug for Storage {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"LogicalLog {{ logical_log }}\")\n    }\n}\n"
  },
  {
    "path": "core/numeric/decimal.rs",
    "content": "use bigdecimal::BigDecimal;\nuse num_bigint::{BigInt, Sign};\n\nuse crate::LimboError;\n\nconst NUMERIC_BLOB_VERSION: u8 = 0x01;\nconst FLAG_NEGATIVE: u8 = 0x01;\nconst MAX_SCALE_MAGNITUDE: i64 = 1_000_000;\n\n/// Serialize a BigDecimal to our portable blob format.\n///\n/// Format (all multi-byte values little-endian):\n/// ```text\n/// [version: 1 byte (0x01)]\n/// [flags:   1 byte (bit 0 = sign, 0=positive/zero, 1=negative)]\n/// [scale:   8 bytes, i64 LE]\n/// [num_limbs: 4 bytes, u32 LE]\n/// [limb0: 4 bytes u32 LE] [limb1: 4 bytes u32 LE] ... [limbN: 4 bytes u32 LE]\n/// ```\npub fn bigdecimal_to_blob(val: &BigDecimal) -> Vec<u8> {\n    let (bigint, scale) = val.as_bigint_and_exponent();\n    let (sign, magnitude) = bigint.into_parts();\n\n    // magnitude.to_u32_digits() returns limbs in little-endian order\n    let limbs = magnitude.to_u32_digits();\n    debug_assert!(\n        limbs.len() <= u32::MAX as usize,\n        \"limb count {} exceeds u32::MAX\",\n        limbs.len()\n    );\n    let num_limbs = limbs.len() as u32;\n\n    let mut buf = Vec::with_capacity(14 + limbs.len() * 4);\n\n    // version\n    buf.push(NUMERIC_BLOB_VERSION);\n\n    // flags\n    let flags = if sign == Sign::Minus {\n        FLAG_NEGATIVE\n    } else {\n        0\n    };\n    buf.push(flags);\n\n    // scale (i64 LE)\n    buf.extend_from_slice(&scale.to_le_bytes());\n\n    // num_limbs (u32 LE)\n    buf.extend_from_slice(&num_limbs.to_le_bytes());\n\n    // limbs (u32 LE each)\n    for limb in &limbs {\n        buf.extend_from_slice(&limb.to_le_bytes());\n    }\n\n    buf\n}\n\n/// Deserialize a BigDecimal from our portable blob format.\npub fn blob_to_bigdecimal(blob: &[u8]) -> crate::Result<BigDecimal> {\n    // Minimum size: version(1) + flags(1) + scale(8) + num_limbs(4) = 14\n    if blob.len() < 14 {\n        return Err(LimboError::Constraint(\n            \"invalid numeric blob: too short\".to_string(),\n        ));\n    }\n\n    let version = blob[0];\n    if version != NUMERIC_BLOB_VERSION {\n        return Err(LimboError::Constraint(format!(\n            \"unsupported numeric blob version: {version}\"\n        )));\n    }\n\n    let flags = blob[1];\n    let sign = if flags & FLAG_NEGATIVE != 0 {\n        Sign::Minus\n    } else {\n        Sign::Plus\n    };\n\n    // SAFETY: blob[2..10] is exactly 8 bytes (checked by min-length guard above)\n    let scale = i64::from_le_bytes(blob[2..10].try_into().unwrap());\n\n    // Reject absurd scales from corrupted blobs.\n    if !(-MAX_SCALE_MAGNITUDE..=MAX_SCALE_MAGNITUDE).contains(&scale) {\n        return Err(LimboError::Constraint(format!(\n            \"invalid numeric blob: scale {scale} out of range\"\n        )));\n    }\n\n    // SAFETY: blob[10..14] is exactly 4 bytes (checked by min-length guard above)\n    let num_limbs = u32::from_le_bytes(blob[10..14].try_into().unwrap()) as usize;\n\n    // Cap limb count to prevent OOM from crafted blobs. 65536 limbs = 256KB,\n    // which supports numbers with ~600,000 decimal digits — far beyond any\n    // reasonable precision.\n    const MAX_LIMBS: usize = 65536;\n    if num_limbs > MAX_LIMBS {\n        return Err(LimboError::Constraint(format!(\n            \"invalid numeric blob: limb count {num_limbs} exceeds maximum {MAX_LIMBS}\"\n        )));\n    }\n\n    let expected_len = num_limbs\n        .checked_mul(4)\n        .and_then(|n| n.checked_add(14))\n        .ok_or_else(|| {\n            LimboError::Constraint(\"invalid numeric blob: limb count overflow\".to_string())\n        })?;\n    if blob.len() != expected_len {\n        return Err(LimboError::Constraint(format!(\n            \"invalid numeric blob: expected {expected_len} bytes, got {}\",\n            blob.len()\n        )));\n    }\n\n    let mut limbs = Vec::with_capacity(num_limbs);\n    for i in 0..num_limbs {\n        let offset = 14 + i * 4;\n        let limb = u32::from_le_bytes(blob[offset..offset + 4].try_into().unwrap());\n        limbs.push(limb);\n    }\n\n    // Handle zero case: BigInt::new with empty limbs and Plus sign = 0\n    let sign = if limbs.is_empty() { Sign::NoSign } else { sign };\n    let bigint = BigInt::new(sign, limbs);\n    Ok(BigDecimal::new(bigint, scale))\n}\n\n/// Format a BigDecimal as a string, preserving trailing zeros for the scale.\n/// BigDecimal::to_string() may drop trailing zeros, but we want \"1.10\" to\n/// remain \"1.10\" and \"0.00\" to remain \"0.00\" for a value stored with scale=2.\npub fn format_numeric(bd: &BigDecimal) -> String {\n    let (bigint, scale) = bd.as_bigint_and_exponent();\n    if scale <= 0 {\n        // No decimal places: just print the integer (possibly scaled up)\n        if scale == 0 {\n            return bigint.to_string();\n        }\n        // Negative scale means multiply by 10^(-scale).\n        // Cap to MAX_SCALE_MAGNITUDE (validated on read) to avoid runaway allocation.\n        let neg_scale = scale.unsigned_abs().min(MAX_SCALE_MAGNITUDE as u64) as u32;\n        let factor = num_bigint::BigInt::from(10).pow(neg_scale);\n        return (bigint * factor).to_string();\n    }\n    let scale = scale as usize;\n    let is_negative = bigint.sign() == Sign::Minus;\n    let digits = bigint.magnitude().to_string();\n    let digits_len = digits.len();\n\n    let (integer_part, frac_part) = if digits_len > scale {\n        let split = digits_len - scale;\n        (&digits[..split], digits[split..].to_string())\n    } else {\n        let zeros = \"0\".repeat(scale - digits_len);\n        (\"0\", format!(\"{zeros}{digits}\"))\n    };\n\n    if is_negative {\n        format!(\"-{integer_part}.{frac_part}\")\n    } else {\n        format!(\"{integer_part}.{frac_part}\")\n    }\n}\n\n/// Validate that a BigDecimal fits within the given precision and scale.\n/// Precision = total number of significant digits.\n/// Scale = number of digits after the decimal point.\npub fn validate_precision_scale(\n    val: &BigDecimal,\n    precision: i64,\n    scale: i64,\n) -> crate::Result<BigDecimal> {\n    use bigdecimal::Zero;\n\n    if precision <= 0 {\n        return Err(LimboError::Constraint(format!(\n            \"numeric precision must be positive, got {precision}\"\n        )));\n    }\n    if scale < 0 {\n        return Err(LimboError::Constraint(format!(\n            \"numeric scale must be non-negative, got {scale}\"\n        )));\n    }\n    if scale > precision {\n        return Err(LimboError::Constraint(format!(\n            \"numeric scale ({scale}) must not exceed precision ({precision})\"\n        )));\n    }\n\n    if val.is_zero() {\n        return Ok(BigDecimal::new(BigInt::from(0), scale));\n    }\n\n    // Round to the requested scale\n    let rounded = val.with_scale(scale);\n\n    // Count total significant digits (excluding leading zeros)\n    let (bigint, _) = rounded.as_bigint_and_exponent();\n    let abs_str = bigint.magnitude().to_string();\n    let total_digits = abs_str.len() as i64;\n\n    if total_digits > precision {\n        return Err(LimboError::Constraint(format!(\n            \"numeric value out of range: precision {precision}, scale {scale}\"\n        )));\n    }\n\n    Ok(rounded)\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use bigdecimal::Zero;\n    use std::str::FromStr;\n\n    #[test]\n    fn test_roundtrip_positive() {\n        let val = BigDecimal::from_str(\"123.45\").unwrap();\n        let blob = bigdecimal_to_blob(&val);\n        let decoded = blob_to_bigdecimal(&blob).unwrap();\n        assert_eq!(val, decoded);\n    }\n\n    #[test]\n    fn test_roundtrip_negative() {\n        let val = BigDecimal::from_str(\"-987.654\").unwrap();\n        let blob = bigdecimal_to_blob(&val);\n        let decoded = blob_to_bigdecimal(&blob).unwrap();\n        assert_eq!(val, decoded);\n    }\n\n    #[test]\n    fn test_roundtrip_zero() {\n        let val = BigDecimal::from_str(\"0\").unwrap();\n        let blob = bigdecimal_to_blob(&val);\n        let decoded = blob_to_bigdecimal(&blob).unwrap();\n        assert!(decoded.is_zero());\n    }\n\n    #[test]\n    fn test_roundtrip_large() {\n        // 100+ digit number\n        let s = \"12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890.99\";\n        let val = BigDecimal::from_str(s).unwrap();\n        let blob = bigdecimal_to_blob(&val);\n        let decoded = blob_to_bigdecimal(&blob).unwrap();\n        assert_eq!(val, decoded);\n    }\n\n    #[test]\n    fn test_validate_precision_scale_ok() {\n        let val = BigDecimal::from_str(\"123.45\").unwrap();\n        let result = validate_precision_scale(&val, 5, 2);\n        assert!(result.is_ok());\n    }\n\n    #[test]\n    fn test_validate_precision_scale_overflow() {\n        let val = BigDecimal::from_str(\"12345.67\").unwrap();\n        let result = validate_precision_scale(&val, 5, 2);\n        assert!(result.is_err());\n    }\n\n    // ================================================================\n    // Property-based fuzz tests (quickcheck)\n    // ================================================================\n\n    use quickcheck::{Arbitrary, Gen};\n    use quickcheck_macros::quickcheck;\n\n    /// Newtype for generating arbitrary BigDecimal values via quickcheck.\n    #[derive(Debug, Clone)]\n    struct ArbDecimal(BigDecimal);\n\n    impl Arbitrary for ArbDecimal {\n        fn arbitrary(g: &mut Gen) -> Self {\n            let mantissa = i64::arbitrary(g);\n            // Scale in [-100, 100] — covers negative scale (large integers),\n            // zero scale (plain integers), and positive scale (decimals).\n            let scale = (i64::arbitrary(g) % 101).abs();\n            let sign_flip = bool::arbitrary(g);\n            let s = if sign_flip { -scale } else { scale };\n            ArbDecimal(BigDecimal::new(BigInt::from(mantissa), s))\n        }\n    }\n\n    /// Newtype for generating valid (precision, scale) pairs.\n    #[derive(Debug, Clone)]\n    struct ArbPrecisionScale {\n        precision: i64,\n        scale: i64,\n    }\n\n    impl Arbitrary for ArbPrecisionScale {\n        fn arbitrary(g: &mut Gen) -> Self {\n            let precision = (u8::arbitrary(g) % 38) as i64 + 1; // 1..=38\n            let scale = (u8::arbitrary(g) as i64) % (precision + 1); // 0..=precision\n            ArbPrecisionScale { precision, scale }\n        }\n    }\n\n    // P1: Roundtrip encode/decode — blob_to_bigdecimal(bigdecimal_to_blob(x)) == x\n    #[quickcheck]\n    fn prop_blob_roundtrip(arb: ArbDecimal) -> bool {\n        let blob = bigdecimal_to_blob(&arb.0);\n        let decoded = blob_to_bigdecimal(&blob).unwrap();\n        decoded == arb.0\n    }\n\n    // P2: format_numeric parse roundtrip — for non-negative scale,\n    // parsing the formatted string should produce a numerically equal value.\n    #[quickcheck]\n    fn prop_format_parse_roundtrip(arb: ArbDecimal) -> bool {\n        let (_, scale) = arb.0.as_bigint_and_exponent();\n        if scale < 0 {\n            return true; // skip negative scale — format_numeric expands it\n        }\n        let formatted = format_numeric(&arb.0);\n        let parsed = BigDecimal::from_str(&formatted).unwrap();\n        // Numerically equal (may differ in internal scale representation)\n        parsed == arb.0\n    }\n\n    // P3: validate_precision_scale idempotence — applying it twice gives same result.\n    #[quickcheck]\n    fn prop_validate_idempotent(arb: ArbDecimal, ps: ArbPrecisionScale) -> bool {\n        let first = validate_precision_scale(&arb.0, ps.precision, ps.scale);\n        match first {\n            Ok(y) => {\n                let second = validate_precision_scale(&y, ps.precision, ps.scale).unwrap();\n                second == y\n            }\n            Err(_) => true, // rejection is fine\n        }\n    }\n\n    // P4: validate_precision_scale bounds — result fits within declared precision/scale.\n    #[quickcheck]\n    fn prop_validate_respects_bounds(arb: ArbDecimal, ps: ArbPrecisionScale) -> bool {\n        match validate_precision_scale(&arb.0, ps.precision, ps.scale) {\n            Ok(y) => {\n                let (bigint, result_scale) = y.as_bigint_and_exponent();\n                // Scale must match requested scale\n                if result_scale != ps.scale {\n                    return false;\n                }\n                // Digit count must be <= precision\n                let digits = bigint.magnitude().to_string();\n                let digit_count = if digits == \"0\" {\n                    0\n                } else {\n                    digits.len() as i64\n                };\n                digit_count <= ps.precision\n            }\n            Err(_) => true,\n        }\n    }\n\n    // P5: blob_to_bigdecimal never panics on arbitrary bytes.\n    #[quickcheck]\n    fn prop_blob_decode_no_panic(data: Vec<u8>) -> bool {\n        let _ = blob_to_bigdecimal(&data);\n        true // just checking it doesn't panic\n    }\n\n    // P6: format_numeric never panics.\n    #[quickcheck]\n    fn prop_format_no_panic(arb: ArbDecimal) -> bool {\n        let _ = format_numeric(&arb.0);\n        true\n    }\n\n    // P7: Ordering is preserved through encode/decode roundtrip.\n    #[quickcheck]\n    fn prop_ordering_preserved(a: ArbDecimal, b: ArbDecimal) -> bool {\n        let blob_a = bigdecimal_to_blob(&a.0);\n        let blob_b = bigdecimal_to_blob(&b.0);\n        let decoded_a = blob_to_bigdecimal(&blob_a).unwrap();\n        let decoded_b = blob_to_bigdecimal(&blob_b).unwrap();\n        // The ordering of decoded values must match the ordering of originals\n        a.0.cmp(&b.0) == decoded_a.cmp(&decoded_b)\n    }\n}\n"
  },
  {
    "path": "core/numeric/mod.rs",
    "content": "use crate::{types::AsValueRef, Value, ValueRef};\n\npub mod decimal;\npub mod nonnan;\n\nuse nonnan::NonNan;\n\n// TODO: Remove when https://github.com/rust-lang/libs-team/issues/230 is available\ntrait SaturatingShl {\n    fn saturating_shl(self, rhs: u32) -> Self;\n}\n\nimpl SaturatingShl for i64 {\n    fn saturating_shl(self, rhs: u32) -> Self {\n        if rhs >= Self::BITS {\n            0\n        } else {\n            self << rhs\n        }\n    }\n}\n\n// TODO: Remove when https://github.com/rust-lang/libs-team/issues/230 is available\ntrait SaturatingShr {\n    fn saturating_shr(self, rhs: u32) -> Self;\n}\n\nimpl SaturatingShr for i64 {\n    fn saturating_shr(self, rhs: u32) -> Self {\n        if rhs >= Self::BITS {\n            if self >= 0 {\n                0\n            } else {\n                -1\n            }\n        } else {\n            self >> rhs\n        }\n    }\n}\n\n#[derive(Debug, Clone, Copy)]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\npub enum Numeric {\n    Integer(i64),\n    Float(NonNan),\n}\n\nimpl Numeric {\n    pub fn from_value<T: AsValueRef>(value: T) -> Option<Self> {\n        let value = value.as_value_ref();\n\n        match value {\n            ValueRef::Null => None,\n            ValueRef::Numeric(v) => Some(v),\n            ValueRef::Text(text) => Some(Numeric::from(text.as_str())),\n            ValueRef::Blob(blob) => {\n                let text = String::from_utf8_lossy(blob);\n                Some(Numeric::from(&text))\n            }\n        }\n    }\n\n    #[inline]\n    pub fn from_value_strict(value: &Value) -> Option<Self> {\n        match value {\n            Value::Null | Value::Blob(_) => None,\n            Value::Numeric(n) => Some(*n),\n            Value::Text(text) => {\n                let s = text.as_str();\n\n                match str_to_f64(s) {\n                    None\n                    | Some(StrToF64::FractionalPrefix(_))\n                    | Some(StrToF64::DecimalPrefix(_)) => None,\n                    Some(StrToF64::Fractional(value)) => Some(Self::Float(value)),\n                    Some(StrToF64::Decimal(real)) => {\n                        let integer = str_to_i64(s).unwrap_or(0);\n\n                        Some(if real == integer as f64 {\n                            Self::Integer(integer)\n                        } else {\n                            Self::Float(real)\n                        })\n                    }\n                }\n            }\n        }\n    }\n\n    #[inline]\n    pub fn to_f64(&self) -> f64 {\n        match self {\n            Numeric::Integer(v) => *v as _,\n            Numeric::Float(v) => (*v).into(),\n        }\n    }\n\n    #[inline]\n    pub fn to_bool(&self) -> bool {\n        match self {\n            Numeric::Integer(0) => false,\n            Numeric::Float(non_nan) if *non_nan == 0.0 => false,\n            _ => true,\n        }\n    }\n\n    #[inline]\n    pub fn checked_add(self, rhs: Self) -> Option<Self> {\n        match (self, rhs) {\n            (Numeric::Integer(lhs), Numeric::Integer(rhs)) => match lhs.checked_add(rhs) {\n                None => Numeric::Float(lhs.into()).checked_add(Numeric::Float(rhs.into())),\n                Some(i) => Some(Numeric::Integer(i)),\n            },\n            (Numeric::Float(lhs), Numeric::Float(rhs)) => (lhs + rhs).map(Numeric::Float),\n            (f @ Numeric::Float(_), Numeric::Integer(i))\n            | (Numeric::Integer(i), f @ Numeric::Float(_)) => {\n                f.checked_add(Numeric::Float(i.into()))\n            }\n        }\n    }\n\n    #[inline]\n    pub fn checked_sub(self, rhs: Self) -> Option<Self> {\n        match (self, rhs) {\n            (Numeric::Float(lhs), Numeric::Float(rhs)) => (lhs - rhs).map(Numeric::Float),\n            (Numeric::Integer(lhs), Numeric::Integer(rhs)) => match lhs.checked_sub(rhs) {\n                None => Numeric::Float(lhs.into()).checked_sub(Numeric::Float(rhs.into())),\n                Some(i) => Some(Numeric::Integer(i)),\n            },\n            (f @ Numeric::Float(_), Numeric::Integer(i)) => f.checked_sub(Numeric::Float(i.into())),\n            (Numeric::Integer(i), f @ Numeric::Float(_)) => Numeric::Float(i.into()).checked_sub(f),\n        }\n    }\n\n    #[inline]\n    pub fn checked_mul(self, rhs: Self) -> Option<Self> {\n        match (self, rhs) {\n            (Numeric::Float(lhs), Numeric::Float(rhs)) => (lhs * rhs).map(Numeric::Float),\n            (Numeric::Integer(lhs), Numeric::Integer(rhs)) => match lhs.checked_mul(rhs) {\n                None => Numeric::Float(lhs.into()).checked_mul(Numeric::Float(rhs.into())),\n                Some(i) => Some(Numeric::Integer(i)),\n            },\n            (f @ Numeric::Float(_), Numeric::Integer(i))\n            | (Numeric::Integer(i), f @ Numeric::Float(_)) => {\n                f.checked_mul(Numeric::Float(i.into()))\n            }\n        }\n    }\n\n    #[inline]\n    pub fn checked_div(self, rhs: Self) -> Option<Self> {\n        match (self, rhs) {\n            (Numeric::Float(lhs), Numeric::Float(rhs)) => match lhs / rhs {\n                Some(v) if rhs != 0.0 => Some(Numeric::Float(v)),\n                _ => None,\n            },\n            (Numeric::Integer(lhs), Numeric::Integer(rhs)) => match lhs.checked_div(rhs) {\n                None => Numeric::Float(lhs.into()).checked_div(Numeric::Float(rhs.into())),\n                Some(v) => Some(Numeric::Integer(v)),\n            },\n            (f @ Numeric::Float(_), Numeric::Integer(i)) => f.checked_div(Numeric::Float(i.into())),\n            (Numeric::Integer(i), f @ Numeric::Float(_)) => Numeric::Float(i.into()).checked_div(f),\n        }\n    }\n}\n\nimpl From<Numeric> for NullableInteger {\n    #[inline]\n    fn from(value: Numeric) -> Self {\n        match value {\n            Numeric::Integer(v) => NullableInteger::Integer(v),\n            Numeric::Float(v) => NullableInteger::Integer(f64::from(v) as i64),\n        }\n    }\n}\n\nimpl From<Numeric> for Value {\n    #[inline]\n    fn from(value: Numeric) -> Self {\n        Value::Numeric(value)\n    }\n}\n\nimpl From<Option<Numeric>> for Value {\n    fn from(value: Option<Numeric>) -> Self {\n        value.map_or_else(|| Value::Null, Value::from)\n    }\n}\n\nimpl<T: AsRef<str>> From<T> for Numeric {\n    fn from(value: T) -> Self {\n        let text = value.as_ref();\n\n        match str_to_f64(text) {\n            None => Self::Integer(0),\n            Some(StrToF64::Fractional(value) | StrToF64::FractionalPrefix(value)) => {\n                Self::Float(value)\n            }\n            Some(StrToF64::Decimal(real) | StrToF64::DecimalPrefix(real)) => {\n                let integer = str_to_i64(text).unwrap_or(0);\n\n                if real == integer as f64 {\n                    Self::Integer(integer)\n                } else {\n                    Self::Float(real)\n                }\n            }\n        }\n    }\n}\n\nimpl From<Value> for Option<Numeric> {\n    fn from(value: Value) -> Self {\n        Self::from(&value)\n    }\n}\nimpl From<&Value> for Option<Numeric> {\n    fn from(value: &Value) -> Self {\n        match value {\n            Value::Null => None,\n            Value::Numeric(n) => Some(*n),\n            Value::Text(text) => Some(Numeric::from(text.as_str())),\n            Value::Blob(blob) => {\n                let text = String::from_utf8_lossy(blob.as_slice());\n                Some(Numeric::from(&text))\n            }\n        }\n    }\n}\n\nimpl std::ops::Neg for Numeric {\n    type Output = Self;\n\n    fn neg(self) -> Self::Output {\n        match self {\n            Numeric::Integer(v) => match v.checked_neg() {\n                None => -Numeric::Float(v.into()),\n                Some(i) => Numeric::Integer(i),\n            },\n            Numeric::Float(v) => Numeric::Float(-v),\n        }\n    }\n}\n\nimpl PartialEq for Numeric {\n    fn eq(&self, other: &Self) -> bool {\n        self.cmp(other).is_eq()\n    }\n}\n\nimpl Eq for Numeric {}\n\nimpl PartialOrd for Numeric {\n    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {\n        Some(self.cmp(other))\n    }\n}\n\nimpl Ord for Numeric {\n    fn cmp(&self, other: &Self) -> std::cmp::Ordering {\n        match (self, other) {\n            (Numeric::Integer(a), Numeric::Integer(b)) => a.cmp(b),\n            (Numeric::Float(a), Numeric::Float(b)) => {\n                let fa: f64 = (*a).into();\n                let fb: f64 = (*b).into();\n                // NonNan guarantees no NaN, so partial_cmp always returns Some.\n                // SQLite's float-vs-float uses raw IEEE 754 < and > operators,\n                // which both return false for NaN, resulting in \"equal\".\n                fa.partial_cmp(&fb).unwrap_or(std::cmp::Ordering::Equal)\n            }\n            (Numeric::Integer(int), Numeric::Float(float)) => {\n                sqlite_int_float_cmp(*int, f64::from(*float))\n            }\n            (Numeric::Float(float), Numeric::Integer(int)) => {\n                sqlite_int_float_cmp(*int, f64::from(*float)).reverse()\n            }\n        }\n    }\n}\n\n/// Compare an integer and a float following SQLite semantics.\n///\n/// SQLite treats NaN as NULL, so int > NaN. In practice, NonNan prevents NaN\n/// from appearing in Numeric::Float, but we check defensively.\n///\n/// See sqlite3IntFloatCompare in src/vdbeaux.c.\nfn sqlite_int_float_cmp(int_val: i64, float_val: f64) -> std::cmp::Ordering {\n    if float_val.is_nan() {\n        // NaN is treated as NULL; all integers are greater than NULL\n        return std::cmp::Ordering::Greater;\n    }\n\n    if float_val < -9_223_372_036_854_775_808.0 {\n        return std::cmp::Ordering::Greater;\n    }\n    if float_val >= 9_223_372_036_854_775_808.0 {\n        return std::cmp::Ordering::Less;\n    }\n\n    let float_as_int = float_val as i64;\n    match int_val.cmp(&float_as_int) {\n        std::cmp::Ordering::Equal => {\n            let int_as_float = int_val as f64;\n            int_as_float\n                .partial_cmp(&float_val)\n                .unwrap_or(std::cmp::Ordering::Equal)\n        }\n        other => other,\n    }\n}\n\n#[derive(Debug)]\npub enum NullableInteger {\n    Null,\n    Integer(i64),\n}\n\nimpl From<NullableInteger> for Value {\n    fn from(value: NullableInteger) -> Self {\n        match value {\n            NullableInteger::Null => Value::Null,\n            NullableInteger::Integer(v) => Value::from_i64(v),\n        }\n    }\n}\n\nimpl<T: AsRef<str>> From<T> for NullableInteger {\n    fn from(value: T) -> Self {\n        Self::Integer(str_to_i64(value.as_ref()).unwrap_or(0))\n    }\n}\n\nimpl From<Value> for NullableInteger {\n    fn from(value: Value) -> Self {\n        Self::from(&value)\n    }\n}\n\nimpl From<&Value> for NullableInteger {\n    fn from(value: &Value) -> Self {\n        match value {\n            Value::Null => Self::Null,\n            Value::Numeric(Numeric::Integer(v)) => Self::Integer(*v),\n            Value::Numeric(Numeric::Float(v)) => Self::Integer(f64::from(*v) as i64),\n            Value::Text(text) => Self::from(text.as_str()),\n            Value::Blob(blob) => {\n                let text = String::from_utf8_lossy(blob.as_slice());\n                Self::from(text)\n            }\n        }\n    }\n}\n\nimpl std::ops::Not for NullableInteger {\n    type Output = Self;\n\n    fn not(self) -> Self::Output {\n        match self {\n            NullableInteger::Null => NullableInteger::Null,\n            NullableInteger::Integer(lhs) => NullableInteger::Integer(!lhs),\n        }\n    }\n}\n\nimpl std::ops::BitAnd for NullableInteger {\n    type Output = Self;\n\n    fn bitand(self, rhs: Self) -> Self::Output {\n        match (self, rhs) {\n            (NullableInteger::Null, _) | (_, NullableInteger::Null) => NullableInteger::Null,\n            (NullableInteger::Integer(lhs), NullableInteger::Integer(rhs)) => {\n                NullableInteger::Integer(lhs & rhs)\n            }\n        }\n    }\n}\n\nimpl std::ops::BitOr for NullableInteger {\n    type Output = Self;\n\n    fn bitor(self, rhs: Self) -> Self::Output {\n        match (self, rhs) {\n            (NullableInteger::Null, _) | (_, NullableInteger::Null) => NullableInteger::Null,\n            (NullableInteger::Integer(lhs), NullableInteger::Integer(rhs)) => {\n                NullableInteger::Integer(lhs | rhs)\n            }\n        }\n    }\n}\n\nimpl std::ops::Shl for NullableInteger {\n    type Output = Self;\n\n    fn shl(self, rhs: Self) -> Self::Output {\n        match (self, rhs) {\n            (NullableInteger::Null, _) | (_, NullableInteger::Null) => NullableInteger::Null,\n            (NullableInteger::Integer(lhs), NullableInteger::Integer(rhs)) => {\n                NullableInteger::Integer(if rhs.is_positive() {\n                    lhs.saturating_shl(rhs.try_into().unwrap_or(u32::MAX))\n                } else {\n                    lhs.saturating_shr(rhs.saturating_abs().try_into().unwrap_or(u32::MAX))\n                })\n            }\n        }\n    }\n}\n\nimpl std::ops::Shr for NullableInteger {\n    type Output = Self;\n\n    fn shr(self, rhs: Self) -> Self::Output {\n        match (self, rhs) {\n            (NullableInteger::Null, _) | (_, NullableInteger::Null) => NullableInteger::Null,\n            (NullableInteger::Integer(lhs), NullableInteger::Integer(rhs)) => {\n                NullableInteger::Integer(if rhs.is_positive() {\n                    lhs.saturating_shr(rhs.try_into().unwrap_or(u32::MAX))\n                } else {\n                    lhs.saturating_shl(rhs.saturating_abs().try_into().unwrap_or(u32::MAX))\n                })\n            }\n        }\n    }\n}\n\nimpl std::ops::Rem for NullableInteger {\n    type Output = Self;\n\n    fn rem(self, rhs: Self) -> Self::Output {\n        match (self, rhs) {\n            (NullableInteger::Null, _) | (_, NullableInteger::Null) => NullableInteger::Null,\n            (_, NullableInteger::Integer(0)) => NullableInteger::Null,\n            (lhs, NullableInteger::Integer(-1)) => lhs % NullableInteger::Integer(1),\n            (NullableInteger::Integer(lhs), NullableInteger::Integer(rhs)) => {\n                NullableInteger::Integer(lhs % rhs)\n            }\n        }\n    }\n}\n\n// Maximum u64 that can survive a f64 round trip\nconst MAX_EXACT: u64 = u64::MAX << 11;\n\nconst VERTICAL_TAB: char = '\\u{b}';\n\n/// Encapsulates Dekker's arithmetic for higher precision. This is spiritually the same as using a\n/// f128 for arithmetic, but cross platform and compatible with sqlite.\n#[derive(Debug, Clone, Copy)]\npub struct DoubleDouble(pub f64, pub f64);\n\nimpl DoubleDouble {\n    pub const E100: Self = DoubleDouble(1.0e+100, -1.590_289_110_975_991_8e83);\n    pub const E10: Self = DoubleDouble(1.0e+10, 0.0);\n    pub const E1: Self = DoubleDouble(1.0e+01, 0.0);\n\n    pub const NEG_E100: Self = DoubleDouble(1.0e-100, -1.999_189_980_260_288_3e-117);\n    pub const NEG_E10: Self = DoubleDouble(1.0e-10, -3.643_219_731_549_774e-27);\n    pub const NEG_E1: Self = DoubleDouble(1.0e-01, -5.551_115_123_125_783e-18);\n}\n\nimpl From<u64> for DoubleDouble {\n    fn from(value: u64) -> Self {\n        let r = value as f64;\n\n        // If the value is smaller than MAX_EXACT, the error isn't significant\n        let rr = if r <= MAX_EXACT as f64 {\n            let round_tripped = value as f64 as u64;\n            let sign = if value >= round_tripped { 1.0 } else { -1.0 };\n\n            // Error term is the signed distance of the round tripped value and itself\n            sign * value.abs_diff(round_tripped) as f64\n        } else {\n            0.0\n        };\n\n        DoubleDouble(r, rr)\n    }\n}\n\nimpl From<DoubleDouble> for u64 {\n    fn from(value: DoubleDouble) -> Self {\n        if value.1 < 0.0 {\n            value.0 as u64 - value.1.abs() as u64\n        } else {\n            value.0 as u64 + value.1 as u64\n        }\n    }\n}\n\nimpl From<DoubleDouble> for f64 {\n    fn from(DoubleDouble(a, aa): DoubleDouble) -> Self {\n        a + aa\n    }\n}\n\nimpl std::ops::Mul for DoubleDouble {\n    type Output = Self;\n\n    /// Double-Double multiplication.  (self.0, self.1) *= (rhs.0, rhs.1)\n    ///\n    /// Reference:\n    ///   T. J. Dekker, \"A Floating-Point Technique for Extending the Available Precision\".\n    ///   1971-07-26.\n    ///\n    fn mul(self, rhs: Self) -> Self::Output {\n        // TODO: Better variable naming\n\n        let mask = u64::MAX << 26;\n\n        let hx = f64::from_bits(self.0.to_bits() & mask);\n        let tx = self.0 - hx;\n\n        let hy = f64::from_bits(rhs.0.to_bits() & mask);\n        let ty = rhs.0 - hy;\n\n        let p = hx * hy;\n        let q = hx * ty + tx * hy;\n\n        let c = p + q;\n        let cc = p - c + q + tx * ty;\n        let cc = self.0 * rhs.1 + self.1 * rhs.0 + cc;\n\n        let r = c + cc;\n        let rr = (c - r) + cc;\n\n        DoubleDouble(r, rr)\n    }\n}\n\nimpl std::ops::MulAssign for DoubleDouble {\n    fn mul_assign(&mut self, rhs: Self) {\n        *self = *self * rhs;\n    }\n}\n\npub fn str_to_i64(input: impl AsRef<str>) -> Option<i64> {\n    let input = input\n        .as_ref()\n        .trim_matches(|ch: char| ch.is_ascii_whitespace() || ch == VERTICAL_TAB);\n\n    let mut iter = input.chars().enumerate().peekable();\n\n    iter.next_if(|(_, ch)| matches!(ch, '+' | '-'));\n    let Some((end, _)) = iter.take_while(|(_, ch)| ch.is_ascii_digit()).last() else {\n        return Some(0);\n    };\n\n    input[0..=end].parse::<i64>().map_or_else(\n        |err| match err.kind() {\n            std::num::IntErrorKind::PosOverflow => Some(i64::MAX),\n            std::num::IntErrorKind::NegOverflow => Some(i64::MIN),\n            std::num::IntErrorKind::Empty => unreachable!(),\n            _ => Some(0),\n        },\n        Some,\n    )\n}\n\n#[derive(Debug, Clone, Copy)]\npub enum StrToF64 {\n    Fractional(NonNan),\n    Decimal(NonNan),\n    FractionalPrefix(NonNan),\n    DecimalPrefix(NonNan),\n}\n\nimpl From<StrToF64> for f64 {\n    fn from(value: StrToF64) -> Self {\n        match value {\n            StrToF64::Fractional(non_nan) => non_nan.into(),\n            StrToF64::Decimal(non_nan) => non_nan.into(),\n            StrToF64::FractionalPrefix(non_nan) => non_nan.into(),\n            StrToF64::DecimalPrefix(non_nan) => non_nan.into(),\n        }\n    }\n}\n\npub fn str_to_f64(input: impl AsRef<str>) -> Option<StrToF64> {\n    let mut input = input\n        .as_ref()\n        .trim_matches(|ch: char| ch.is_ascii_whitespace() || ch == VERTICAL_TAB)\n        .chars()\n        .peekable();\n\n    let sign = match input.next_if(|ch| matches!(ch, '-' | '+')) {\n        Some('-') => -1.0,\n        _ => 1.0,\n    };\n\n    let mut had_digits = false;\n    let mut is_fractional = false;\n\n    let mut significant: u64 = 0;\n\n    // Copy as many significant digits as we can\n    while let Some(digit) = input.peek().and_then(|ch| ch.to_digit(10)) {\n        had_digits = true;\n\n        match significant\n            .checked_mul(10)\n            .and_then(|v| v.checked_add(digit as u64))\n        {\n            Some(new) => significant = new,\n            None => break,\n        }\n\n        input.next();\n    }\n\n    let mut exponent = 0;\n\n    // Increment the exponent for every non significant digit we skipped\n    while input.next_if(char::is_ascii_digit).is_some() {\n        exponent += 1\n    }\n\n    if input.next_if(|ch| matches!(ch, '.')).is_some() {\n        if had_digits {\n            is_fractional = true;\n        }\n\n        if input.peek().is_some_and(char::is_ascii_digit) {\n            is_fractional = true;\n        }\n\n        while let Some(digit) = input.peek().and_then(|ch| ch.to_digit(10)) {\n            if significant < (u64::MAX - 9) / 10 {\n                significant = significant * 10 + digit as u64;\n                exponent -= 1;\n            }\n\n            input.next();\n        }\n    };\n\n    let mut valid_exponent = true;\n\n    if (had_digits || is_fractional) && input.next_if(|ch| matches!(ch, 'e' | 'E')).is_some() {\n        let sign = match input.next_if(|ch| matches!(ch, '-' | '+')) {\n            Some('-') => -1,\n            _ => 1,\n        };\n\n        if input.peek().is_some_and(char::is_ascii_digit) {\n            is_fractional = true;\n            let mut e = 0;\n\n            while let Some(ch) = input.next_if(char::is_ascii_digit) {\n                e = (e * 10 + ch.to_digit(10).unwrap() as i32).min(1000);\n            }\n\n            exponent += sign * e;\n        } else {\n            valid_exponent = false;\n        }\n    };\n\n    if !(had_digits || is_fractional) {\n        return None;\n    }\n\n    while exponent.is_positive() && significant < MAX_EXACT / 10 {\n        significant *= 10;\n        exponent -= 1;\n    }\n\n    while exponent.is_negative() && significant % 10 == 0 {\n        significant /= 10;\n        exponent += 1;\n    }\n\n    let mut result = DoubleDouble::from(significant);\n\n    if exponent > 0 {\n        while exponent >= 100 {\n            exponent -= 100;\n            result *= DoubleDouble::E100;\n        }\n        while exponent >= 10 {\n            exponent -= 10;\n            result *= DoubleDouble::E10;\n        }\n        while exponent >= 1 {\n            exponent -= 1;\n            result *= DoubleDouble::E1;\n        }\n    } else {\n        while exponent <= -100 {\n            exponent += 100;\n            result *= DoubleDouble::NEG_E100;\n        }\n        while exponent <= -10 {\n            exponent += 10;\n            result *= DoubleDouble::NEG_E10;\n        }\n        while exponent <= -1 {\n            exponent += 1;\n            result *= DoubleDouble::NEG_E1;\n        }\n    }\n\n    let result = NonNan::new(f64::from(result) * sign)\n        .unwrap_or_else(|| NonNan::new(sign * f64::INFINITY).unwrap());\n\n    if !valid_exponent || input.count() > 0 {\n        if is_fractional {\n            return Some(StrToF64::FractionalPrefix(result));\n        } else {\n            return Some(StrToF64::DecimalPrefix(result));\n        }\n    }\n\n    Some(if is_fractional {\n        StrToF64::Fractional(result)\n    } else {\n        StrToF64::Decimal(result)\n    })\n}\n\nenum FloatParts {\n    Special(String),\n    Normal {\n        negative: bool,\n        digits: Vec<u8>,\n        exp: i32,\n    },\n}\n\nfn decompose_float(v: f64, precision: usize) -> FloatParts {\n    if v.is_nan() {\n        return FloatParts::Special(\"\".to_string());\n    }\n\n    if v.is_infinite() {\n        return FloatParts::Special(if v.is_sign_negative() { \"-Inf\" } else { \"Inf\" }.to_string());\n    }\n\n    if v == 0.0 {\n        return FloatParts::Special(\"0.0\".to_string());\n    }\n\n    let negative = v < 0.0;\n    let mut d = DoubleDouble(v.abs(), 0.0);\n    let mut exp = 0;\n\n    if d.0 > 9.223_372_036_854_775e18 {\n        while d.0 > 9.223_372_036_854_774e118 {\n            exp += 100;\n            d *= DoubleDouble::NEG_E100;\n        }\n        while d.0 > 9.223_372_036_854_774e28 {\n            exp += 10;\n            d *= DoubleDouble::NEG_E10;\n        }\n        while d.0 > 9.223_372_036_854_775e18 {\n            exp += 1;\n            d *= DoubleDouble::NEG_E1;\n        }\n    } else {\n        while d.0 < 9.223_372_036_854_775e-83 {\n            exp -= 100;\n            d *= DoubleDouble::E100;\n        }\n        while d.0 < 9.223_372_036_854_775e7 {\n            exp -= 10;\n            d *= DoubleDouble::E10;\n        }\n        while d.0 < 9.223_372_036_854_775e17 {\n            exp -= 1;\n            d *= DoubleDouble::E1;\n        }\n    }\n\n    let mut digits = u64::from(d).to_string().into_bytes();\n    let mut decimal_pos = digits.len() as i32 + exp;\n\n    'out: {\n        if digits.len() > precision {\n            let round_up = digits[precision] >= b'5';\n            digits.truncate(precision);\n\n            if round_up {\n                for i in (0..precision).rev() {\n                    if digits[i] < b'9' {\n                        digits[i] += 1;\n                        break 'out;\n                    }\n                    digits[i] = b'0';\n                }\n\n                digits.insert(0, b'1');\n                decimal_pos += 1;\n            }\n        }\n    }\n\n    while digits.len() > 1 && digits[digits.len() - 1] == b'0' {\n        digits.pop();\n    }\n\n    FloatParts::Normal {\n        negative,\n        digits,\n        exp: decimal_pos - 1,\n    }\n}\n\nfn format_float_scientific(v: f64, precision: usize) -> String {\n    match decompose_float(v, precision) {\n        FloatParts::Special(s) => s,\n        FloatParts::Normal {\n            negative,\n            digits,\n            exp,\n        } => {\n            let first = digits.first().cloned().unwrap_or(b'0') as char;\n            let rest = digits\n                .get(1..)\n                .filter(|v| !v.is_empty())\n                .map(|v| unsafe { str::from_utf8_unchecked(v) })\n                .unwrap_or(\"0\");\n            format!(\n                \"{}{}.{}e{}{:0width$}\",\n                if negative { \"-\" } else { \"\" },\n                first,\n                rest,\n                if exp.is_positive() { \"+\" } else { \"-\" },\n                exp.abs(),\n                width = if exp.abs() > 99 { 3 } else { 2 }\n            )\n        }\n    }\n}\n\npub fn format_float(v: f64) -> String {\n    match decompose_float(v, 15) {\n        FloatParts::Special(s) => s,\n        FloatParts::Normal {\n            negative,\n            digits,\n            exp,\n        } => {\n            let decimal_pos = exp + 1;\n            if (-4..=14).contains(&exp) {\n                format!(\n                    \"{}{}.{}{}\",\n                    if negative { \"-\" } else { Default::default() },\n                    if decimal_pos > 0 {\n                        let zeroes = (decimal_pos - digits.len() as i32).max(0) as usize;\n                        let digits = digits\n                            .get(0..(decimal_pos.min(digits.len() as i32) as usize))\n                            .unwrap();\n                        (unsafe { str::from_utf8_unchecked(digits) }).to_owned()\n                            + &\"0\".repeat(zeroes)\n                    } else {\n                        \"0\".to_string()\n                    },\n                    \"0\".repeat(decimal_pos.min(0).unsigned_abs() as usize),\n                    digits\n                        .get((decimal_pos.max(0) as usize)..)\n                        .filter(|v| !v.is_empty())\n                        .map(|v| unsafe { str::from_utf8_unchecked(v) })\n                        .unwrap_or(\"0\")\n                )\n            } else {\n                format_float_scientific(v, 15)\n            }\n        }\n    }\n}\n\npub fn format_float_for_quote(v: f64) -> String {\n    let default = format_float(v);\n    if str_to_f64(&default).map(f64::from) == Some(v) {\n        return default;\n    }\n    format_float_scientific(v, 19)\n}\n\n#[test]\nfn test_decode_float() {\n    assert_eq!(format_float(9.93e-322), \"9.93071948140905e-322\");\n    assert_eq!(format_float(9.93), \"9.93\");\n    assert_eq!(format_float(0.093), \"0.093\");\n    assert_eq!(format_float(-0.093), \"-0.093\");\n    assert_eq!(format_float(0.0), \"0.0\");\n    assert_eq!(format_float(4.94e-322), \"4.94065645841247e-322\");\n    assert_eq!(format_float(-20228007.0), \"-20228007.0\");\n}\n"
  },
  {
    "path": "core/numeric/nonnan.rs",
    "content": "#[repr(transparent)]\n#[derive(Debug, Clone, Copy, PartialEq)]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\npub struct NonNan(f64);\n\nimpl NonNan {\n    pub fn new(value: f64) -> Option<Self> {\n        if value.is_nan() {\n            return None;\n        }\n\n        Some(NonNan(value))\n    }\n}\n\nimpl PartialEq<NonNan> for f64 {\n    fn eq(&self, other: &NonNan) -> bool {\n        *self == other.0\n    }\n}\n\nimpl PartialEq<f64> for NonNan {\n    fn eq(&self, other: &f64) -> bool {\n        self.0 == *other\n    }\n}\n\nimpl PartialOrd<f64> for NonNan {\n    fn partial_cmp(&self, other: &f64) -> Option<std::cmp::Ordering> {\n        self.0.partial_cmp(other)\n    }\n}\n\nimpl PartialOrd<NonNan> for f64 {\n    fn partial_cmp(&self, other: &NonNan) -> Option<std::cmp::Ordering> {\n        self.partial_cmp(&other.0)\n    }\n}\n\nimpl From<i64> for NonNan {\n    fn from(value: i64) -> Self {\n        NonNan(value as f64)\n    }\n}\n\nimpl From<NonNan> for f64 {\n    fn from(value: NonNan) -> Self {\n        value.0\n    }\n}\n\nimpl std::ops::Deref for NonNan {\n    type Target = f64;\n\n    fn deref(&self) -> &Self::Target {\n        &self.0\n    }\n}\n\nimpl std::ops::Add for NonNan {\n    type Output = Option<NonNan>;\n\n    fn add(self, rhs: Self) -> Self::Output {\n        Self::new(self.0 + rhs.0)\n    }\n}\n\nimpl std::ops::Sub for NonNan {\n    type Output = Option<NonNan>;\n\n    fn sub(self, rhs: Self) -> Self::Output {\n        Self::new(self.0 - rhs.0)\n    }\n}\n\nimpl std::ops::Mul for NonNan {\n    type Output = Option<NonNan>;\n\n    fn mul(self, rhs: Self) -> Self::Output {\n        Self::new(self.0 * rhs.0)\n    }\n}\n\nimpl std::ops::Div for NonNan {\n    type Output = Option<NonNan>;\n\n    fn div(self, rhs: Self) -> Self::Output {\n        Self::new(self.0 / rhs.0)\n    }\n}\n\nimpl std::ops::Rem for NonNan {\n    type Output = Option<NonNan>;\n\n    fn rem(self, rhs: Self) -> Self::Output {\n        Self::new(self.0 % rhs.0)\n    }\n}\n\nimpl std::fmt::Display for NonNan {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        self.0.fmt(f)\n    }\n}\n\nimpl std::ops::Neg for NonNan {\n    type Output = Self;\n\n    fn neg(self) -> Self::Output {\n        Self(-self.0)\n    }\n}\n"
  },
  {
    "path": "core/parameters.rs",
    "content": "use std::num::NonZero;\n\n#[derive(Clone, Debug)]\npub enum Parameter {\n    Indexed(NonZero<usize>),\n    Named(String, NonZero<usize>),\n}\n\nimpl PartialEq for Parameter {\n    fn eq(&self, other: &Self) -> bool {\n        self.index() == other.index()\n    }\n}\n\nimpl Parameter {\n    pub fn index(&self) -> NonZero<usize> {\n        match self {\n            Parameter::Indexed(index) => *index,\n            Parameter::Named(_, index) => *index,\n        }\n    }\n}\n\n#[derive(Debug, Clone)]\npub struct Parameters {\n    next_index: NonZero<usize>,\n    pub list: Vec<Parameter>,\n}\n\nimpl Default for Parameters {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl Parameters {\n    pub fn new() -> Self {\n        Self {\n            next_index: 1.try_into().unwrap(),\n            list: vec![],\n        }\n    }\n\n    pub fn count(&self) -> usize {\n        self.next_index.get() - 1\n    }\n\n    pub fn has_slot(&self, index: NonZero<usize>) -> bool {\n        index < self.next_index\n    }\n\n    pub fn has_index(&self, index: NonZero<usize>) -> bool {\n        self.list.iter().any(|p| p.index() == index)\n    }\n\n    pub fn is_indexed(&self, index: NonZero<usize>) -> bool {\n        self.list\n            .iter()\n            .any(|p| matches!(p, Parameter::Indexed(i) if *i == index))\n    }\n\n    pub fn name(&self, index: NonZero<usize>) -> Option<String> {\n        self.list.iter().find_map(|p| match p {\n            Parameter::Indexed(i) if *i == index => Some(format!(\"?{i}\")),\n            Parameter::Named(name, i) if *i == index => Some(name.to_owned()),\n            _ => None,\n        })\n    }\n\n    pub fn index(&self, name: impl AsRef<str>) -> Option<NonZero<usize>> {\n        self.list\n            .iter()\n            .find_map(|p| match p {\n                Parameter::Named(n, index) if n == name.as_ref() => Some(index),\n                _ => None,\n            })\n            .copied()\n    }\n\n    pub fn next_index(&self) -> NonZero<usize> {\n        self.next_index\n    }\n\n    fn allocate_new_index(&mut self) -> NonZero<usize> {\n        let index = self.next_index;\n        self.next_index = self.next_index.checked_add(1).unwrap();\n        index\n    }\n\n    pub fn push_index(&mut self, index: NonZero<usize>) -> NonZero<usize> {\n        if index >= self.next_index {\n            self.next_index = index.checked_add(1).unwrap();\n        }\n        if !self.has_index(index) {\n            self.list.push(Parameter::Indexed(index));\n        }\n        tracing::trace!(\"indexed parameter at {index}\");\n        index\n    }\n\n    pub fn push_named_at(\n        &mut self,\n        name: impl Into<String>,\n        index: NonZero<usize>,\n    ) -> NonZero<usize> {\n        let name = name.into();\n        if index >= self.next_index {\n            self.next_index = index.checked_add(1).unwrap();\n        }\n\n        if let Some(Parameter::Named(existing_name, _)) = self\n            .list\n            .iter_mut()\n            .find(|parameter| parameter.index() == index)\n        {\n            if existing_name != &name {\n                tracing::trace!(\n                    \"named parameter alias at {index} as {name}; keeping existing name {existing_name}\"\n                );\n            }\n            return index;\n        }\n\n        if self.has_index(index) {\n            self.list.retain(|parameter| parameter.index() != index);\n        }\n\n        tracing::trace!(\"named parameter at {index} as {name}\");\n        self.list.push(Parameter::Named(name, index));\n        index\n    }\n\n    pub fn push(&mut self, name: impl AsRef<str>) -> NonZero<usize> {\n        match name.as_ref() {\n            name if name.starts_with(['$', ':', '@', '#']) => {\n                match self\n                    .list\n                    .iter()\n                    .find(|p| matches!(p, Parameter::Named(n, _) if name == n))\n                {\n                    Some(t) => {\n                        let index = t.index();\n                        tracing::trace!(\"named parameter at {index} as {name}\");\n                        index\n                    }\n                    None => {\n                        let index = self.allocate_new_index();\n                        self.push_named_at(name, index)\n                    }\n                }\n            }\n            index => {\n                // SAFETY: Guaranteed from parser that the index is bigger than 0.\n                let index: NonZero<usize> = index.parse().unwrap();\n                self.push_index(index)\n            }\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::Parameters;\n\n    #[test]\n    fn count_and_name_are_stable_with_reused_parameters() {\n        let mut parameters = Parameters::new();\n\n        parameters.push(\":a\");\n        parameters.push(\":a\");\n        parameters.push(\"1\");\n        parameters.push(\"1\");\n        parameters.push(\":b\");\n        parameters.push(\"2\");\n\n        assert_eq!(parameters.count(), 2);\n\n        let idx1 = 1usize.try_into().unwrap();\n        let idx2 = 2usize.try_into().unwrap();\n        let idx3 = 3usize.try_into().unwrap();\n\n        assert!(parameters.has_index(idx1));\n        assert!(parameters.has_index(idx2));\n        assert!(!parameters.has_index(idx3));\n        assert!(!parameters.is_indexed(idx1));\n        assert!(!parameters.is_indexed(idx2));\n\n        assert_eq!(parameters.name(idx1).as_deref(), Some(\":a\"));\n        assert_eq!(parameters.name(idx2).as_deref(), Some(\":b\"));\n    }\n\n    #[test]\n    fn is_indexed_is_true_only_for_indexed_parameters() {\n        let mut parameters = Parameters::new();\n        parameters.push(\"1\");\n        parameters.push(\":b\");\n\n        let idx1 = 1usize.try_into().unwrap();\n        let idx2 = 2usize.try_into().unwrap();\n\n        assert!(parameters.is_indexed(idx1));\n        assert!(!parameters.is_indexed(idx2));\n    }\n\n    #[test]\n    fn count_tracks_highest_index_for_sparse_parameters() {\n        let mut parameters = Parameters::new();\n        parameters.push(\"3\");\n\n        let idx1 = 1usize.try_into().unwrap();\n        let idx2 = 2usize.try_into().unwrap();\n        let idx3 = 3usize.try_into().unwrap();\n        let idx4 = 4usize.try_into().unwrap();\n\n        assert_eq!(parameters.count(), 3);\n        assert!(parameters.has_slot(idx1));\n        assert!(parameters.has_slot(idx2));\n        assert!(parameters.has_slot(idx3));\n        assert!(!parameters.has_slot(idx4));\n\n        assert!(!parameters.has_index(idx1));\n        assert!(!parameters.has_index(idx2));\n        assert!(parameters.has_index(idx3));\n    }\n}\n"
  },
  {
    "path": "core/pragma.rs",
    "content": "use crate::sync::Arc;\nuse crate::{Connection, LimboError, Statement, StepResult, Value};\nuse bitflags::bitflags;\nuse strum::IntoEnumIterator;\nuse turso_ext::{ConstraintInfo, ConstraintOp, ConstraintUsage, IndexInfo, ResultCode};\nuse turso_parser::ast::PragmaName;\n\nbitflags! {\n    // Flag names match those used in SQLite:\n    // https://github.com/sqlite/sqlite/blob/b3c1884b65400da85636458298bd77cbbfdfb401/tool/mkpragmatab.tcl#L22-L29\n    pub struct PragmaFlags: u8 {\n        const NeedSchema = 0x01; /* Force schema load before running */\n        const NoColumns  = 0x02; /* OP_ResultRow called with zero columns */\n        const NoColumns1 = 0x04; /* zero columns if RHS argument is present */\n        const ReadOnly   = 0x08; /* Read-only HEADER_VALUE */\n        const Result0    = 0x10; /* Acts as query when no argument */\n        const Result1    = 0x20; /* Acts as query when has one argument */\n        const SchemaOpt  = 0x40; /* Schema restricts name search if present */\n        const SchemaReq  = 0x80; /* Schema required - \"main\" is default */\n    }\n}\n\npub struct Pragma {\n    pub flags: PragmaFlags,\n    pub columns: &'static [&'static str],\n}\n\nimpl Pragma {\n    const fn new(flags: PragmaFlags, columns: &'static [&'static str]) -> Self {\n        Self { flags, columns }\n    }\n}\n\npub fn pragma_for(pragma: &PragmaName) -> Pragma {\n    use PragmaName::*;\n\n    match pragma {\n        ApplicationId => Pragma::new(\n            PragmaFlags::NoColumns1 | PragmaFlags::Result0,\n            &[\"application_id\"],\n        ),\n        CacheSize => Pragma::new(\n            PragmaFlags::NeedSchema\n                | PragmaFlags::Result0\n                | PragmaFlags::SchemaReq\n                | PragmaFlags::NoColumns1,\n            &[\"cache_size\"],\n        ),\n        DataSyncRetry => Pragma::new(\n            PragmaFlags::Result0 | PragmaFlags::NoColumns1,\n            &[\"data_sync_retry\"],\n        ),\n        DatabaseList => Pragma::new(PragmaFlags::Result0, &[\"seq\", \"name\", \"file\"]),\n        Encoding => Pragma::new(\n            PragmaFlags::Result0 | PragmaFlags::NoColumns1,\n            &[\"encoding\"],\n        ),\n        JournalMode => Pragma::new(\n            PragmaFlags::NeedSchema | PragmaFlags::Result0 | PragmaFlags::SchemaReq,\n            &[\"journal_mode\"],\n        ),\n        LegacyFileFormat => {\n            unreachable!(\"pragma_for() called with LegacyFileFormat, which is unsupported\")\n        }\n        ModuleList => Pragma::new(\n            PragmaFlags::NeedSchema | PragmaFlags::Result0 | PragmaFlags::SchemaReq,\n            &[\"module_list\"],\n        ),\n        PageCount => Pragma::new(\n            PragmaFlags::NeedSchema | PragmaFlags::Result0 | PragmaFlags::SchemaReq,\n            &[\"page_count\"],\n        ),\n        PageSize => Pragma::new(\n            PragmaFlags::Result0 | PragmaFlags::SchemaReq | PragmaFlags::NoColumns1,\n            &[\"page_size\"],\n        ),\n        MaxPageCount => Pragma::new(\n            PragmaFlags::NeedSchema\n                | PragmaFlags::Result0\n                | PragmaFlags::SchemaReq\n                | PragmaFlags::NoColumns1,\n            &[\"max_page_count\"],\n        ),\n        SchemaVersion => Pragma::new(\n            PragmaFlags::NoColumns1 | PragmaFlags::Result0,\n            &[\"schema_version\"],\n        ),\n        Synchronous => Pragma::new(\n            PragmaFlags::NoColumns1 | PragmaFlags::Result0,\n            &[\"synchronous\"],\n        ),\n        TempStore => Pragma::new(\n            PragmaFlags::NoColumns1 | PragmaFlags::Result0,\n            &[\"temp_store\"],\n        ),\n        IndexInfo => Pragma::new(\n            PragmaFlags::NeedSchema | PragmaFlags::Result1 | PragmaFlags::SchemaOpt,\n            &[\"seqno\", \"cid\", \"name\"],\n        ),\n        IndexXinfo => Pragma::new(\n            PragmaFlags::NeedSchema | PragmaFlags::Result1 | PragmaFlags::SchemaOpt,\n            &[\"seqno\", \"cid\", \"name\", \"desc\", \"coll\", \"key\"],\n        ),\n        IndexList => Pragma::new(\n            PragmaFlags::NeedSchema | PragmaFlags::Result1 | PragmaFlags::SchemaOpt,\n            &[\"seq\", \"name\", \"unique\", \"origin\", \"partial\"],\n        ),\n        TableList => Pragma::new(\n            PragmaFlags::NeedSchema | PragmaFlags::Result0 | PragmaFlags::Result1,\n            &[\"schema\", \"name\", \"type\", \"ncol\", \"wr\", \"strict\"],\n        ),\n        TableInfo => Pragma::new(\n            PragmaFlags::NeedSchema | PragmaFlags::Result1 | PragmaFlags::SchemaOpt,\n            &[\"cid\", \"name\", \"type\", \"notnull\", \"dflt_value\", \"pk\"],\n        ),\n        TableXinfo => Pragma::new(\n            PragmaFlags::NeedSchema | PragmaFlags::Result1 | PragmaFlags::SchemaOpt,\n            &[\n                \"cid\",\n                \"name\",\n                \"type\",\n                \"notnull\",\n                \"dflt_value\",\n                \"pk\",\n                \"hidden\",\n            ],\n        ),\n        UserVersion => Pragma::new(\n            PragmaFlags::NoColumns1 | PragmaFlags::Result0,\n            &[\"user_version\"],\n        ),\n        WalCheckpoint => Pragma::new(PragmaFlags::NeedSchema, &[\"busy\", \"log\", \"checkpointed\"]),\n        AutoVacuum => Pragma::new(\n            PragmaFlags::NoColumns1 | PragmaFlags::Result0,\n            &[\"auto_vacuum\"],\n        ),\n        BusyTimeout => Pragma::new(\n            PragmaFlags::NoColumns1 | PragmaFlags::Result0,\n            &[\"busy_timeout\"],\n        ),\n        IntegrityCheck => Pragma::new(\n            PragmaFlags::NeedSchema | PragmaFlags::ReadOnly | PragmaFlags::Result0,\n            &[\"message\"],\n        ),\n        QuickCheck => Pragma::new(\n            PragmaFlags::NeedSchema | PragmaFlags::ReadOnly | PragmaFlags::Result0,\n            &[\"message\"],\n        ),\n        CaptureDataChangesConn | UnstableCaptureDataChangesConn => Pragma::new(\n            PragmaFlags::NeedSchema | PragmaFlags::Result0 | PragmaFlags::SchemaReq,\n            &[\"mode\", \"table\", \"version\"],\n        ),\n        QueryOnly => Pragma::new(\n            PragmaFlags::Result0 | PragmaFlags::NoColumns1,\n            &[\"query_only\"],\n        ),\n        IAmADummy | RequireWhere => Pragma::new(\n            PragmaFlags::Result0 | PragmaFlags::NoColumns1,\n            &[\"require_where\"],\n        ),\n        FreelistCount => Pragma::new(PragmaFlags::Result0, &[\"freelist_count\"]),\n        EncryptionKey => Pragma::new(\n            PragmaFlags::Result0 | PragmaFlags::SchemaReq | PragmaFlags::NoColumns1,\n            &[\"hexkey\"],\n        ),\n        EncryptionCipher => Pragma::new(\n            PragmaFlags::Result0 | PragmaFlags::SchemaReq | PragmaFlags::NoColumns1,\n            &[\"cipher\"],\n        ),\n        PragmaName::MvccCheckpointThreshold => Pragma::new(\n            PragmaFlags::NoColumns1 | PragmaFlags::Result0,\n            &[\"mvcc_checkpoint_threshold\"],\n        ),\n        ForeignKeys => Pragma::new(\n            PragmaFlags::NoColumns1 | PragmaFlags::Result0,\n            &[\"foreign_keys\"],\n        ),\n        FunctionList => Pragma::new(\n            PragmaFlags::Result0,\n            &[\"name\", \"builtin\", \"type\", \"enc\", \"narg\", \"flags\"],\n        ),\n        PragmaName::CacheSpill => Pragma::new(\n            PragmaFlags::NoColumns1 | PragmaFlags::Result0,\n            &[\"cache_spill\"],\n        ),\n        #[cfg(target_vendor = \"apple\")]\n        PragmaName::Fullfsync => Pragma::new(\n            PragmaFlags::NoColumns1 | PragmaFlags::Result0,\n            &[\"fullfsync\"],\n        ),\n        IgnoreCheckConstraints => Pragma::new(\n            PragmaFlags::NoColumns1 | PragmaFlags::Result0,\n            &[\"ignore_check_constraints\"],\n        ),\n        ListTypes => Pragma::new(\n            PragmaFlags::Result0,\n            &[\"type\", \"parent\", \"encode\", \"decode\", \"default\", \"operators\"],\n        ),\n    }\n}\n\n#[derive(Debug, Clone)]\npub(crate) struct PragmaVirtualTable {\n    pub(crate) pragma_name: String,\n    visible_column_count: usize,\n    max_arg_count: usize,\n    has_pragma_arg: bool,\n}\n\nimpl PragmaVirtualTable {\n    pub(crate) fn functions() -> Vec<(PragmaVirtualTable, String)> {\n        PragmaName::iter()\n            .filter(|name| *name != PragmaName::LegacyFileFormat)\n            .filter_map(|name| {\n                let pragma = pragma_for(&name);\n                if pragma\n                    .flags\n                    .intersects(PragmaFlags::Result0 | PragmaFlags::Result1)\n                {\n                    Some(Self::create(name.to_string(), pragma))\n                } else {\n                    None\n                }\n            })\n            .collect()\n    }\n\n    fn create(pragma_name: String, pragma: Pragma) -> (Self, String) {\n        let mut max_arg_count = 0;\n        let mut has_pragma_arg = false;\n\n        let mut sql = String::from(\"CREATE TABLE x (\");\n        let col_defs = pragma\n            .columns\n            .iter()\n            .map(|col| format!(\"\\\"{col}\\\"\"))\n            .collect::<Vec<_>>()\n            .join(\", \");\n        sql.push_str(&col_defs);\n        if pragma.flags.contains(PragmaFlags::Result1) {\n            sql.push_str(\", arg HIDDEN\");\n            max_arg_count += 1;\n            has_pragma_arg = true;\n        }\n        if pragma\n            .flags\n            .intersects(PragmaFlags::SchemaOpt | PragmaFlags::SchemaReq)\n        {\n            sql.push_str(\", schema HIDDEN\");\n            max_arg_count += 1;\n        }\n        sql.push(')');\n\n        (\n            PragmaVirtualTable {\n                pragma_name,\n                visible_column_count: pragma.columns.len(),\n                max_arg_count,\n                has_pragma_arg,\n            },\n            sql,\n        )\n    }\n\n    pub(crate) fn open(&self, conn: Arc<Connection>) -> crate::Result<PragmaVirtualTableCursor> {\n        Ok(PragmaVirtualTableCursor {\n            pragma_name: self.pragma_name.clone(),\n            pos: 0,\n            conn,\n            stmt: None,\n            arg: None,\n            visible_column_count: self.visible_column_count,\n            max_arg_count: self.max_arg_count,\n            has_pragma_arg: self.has_pragma_arg,\n        })\n    }\n\n    pub(crate) fn best_index(\n        &self,\n        constraints: &[ConstraintInfo],\n    ) -> Result<IndexInfo, ResultCode> {\n        let mut arg0_idx = None;\n        let mut arg1_idx = None;\n\n        for (i, c) in constraints.iter().enumerate() {\n            if c.op != ConstraintOp::Eq {\n                continue;\n            }\n            let visible_count = self.visible_column_count as u32;\n            if c.column_index < visible_count {\n                continue;\n            }\n            if !c.usable {\n                return Err(ResultCode::ConstraintViolation);\n            }\n            let hidden_idx = c.column_index - visible_count;\n            match hidden_idx {\n                0 => arg0_idx = Some(i),\n                1 => arg1_idx = Some(i),\n                _ => unreachable!(\"Unexpected hidden column index: {}\", hidden_idx),\n            }\n        }\n\n        let argv_arg0_idx = arg0_idx.map_or(0, |_| 1);\n        let argv_arg1_idx = arg1_idx.map_or(argv_arg0_idx, |_| argv_arg0_idx + 1);\n\n        let constraint_usages = constraints\n            .iter()\n            .enumerate()\n            .map(|(i, _)| {\n                let argv_index = if Some(i) == arg0_idx {\n                    Some(argv_arg0_idx)\n                } else if Some(i) == arg1_idx {\n                    Some(argv_arg1_idx)\n                } else {\n                    None\n                };\n                ConstraintUsage {\n                    argv_index,\n                    omit: argv_index.is_some(),\n                }\n            })\n            .collect();\n\n        Ok(IndexInfo {\n            constraint_usages,\n            ..Default::default()\n        })\n    }\n}\n\npub struct PragmaVirtualTableCursor {\n    pragma_name: String,\n    pos: usize,\n    conn: Arc<Connection>,\n    stmt: Option<Statement>,\n    arg: Option<String>,\n    visible_column_count: usize,\n    max_arg_count: usize,\n    has_pragma_arg: bool,\n}\n\nimpl PragmaVirtualTableCursor {\n    pub(crate) fn rowid(&self) -> i64 {\n        self.pos as i64\n    }\n\n    pub(crate) fn next(&mut self) -> crate::Result<bool> {\n        let stmt = self\n            .stmt\n            .as_mut()\n            .ok_or_else(|| LimboError::InternalError(\"Statement is missing\".into()))?;\n        let result = stmt.step()?;\n        match result {\n            StepResult::Done => Ok(false),\n            _ => {\n                self.pos += 1;\n                Ok(true)\n            }\n        }\n    }\n\n    pub(crate) fn column(&self, idx: usize) -> crate::Result<Value> {\n        if idx < self.visible_column_count {\n            let value = self\n                .stmt\n                .as_ref()\n                .ok_or_else(|| LimboError::InternalError(\"Statement is missing\".into()))?\n                .row()\n                .ok_or_else(|| LimboError::InternalError(\"No row available\".into()))?\n                .get_value(idx)\n                .clone();\n            return Ok(value);\n        }\n\n        let value = match idx - self.visible_column_count {\n            0 => self\n                .arg\n                .as_ref()\n                .map_or(Value::Null, |arg| Value::from_text(arg.to_string())),\n            _ => Value::Null,\n        };\n        Ok(value)\n    }\n\n    pub(crate) fn filter(&mut self, args: Vec<Value>) -> crate::Result<bool> {\n        if args.len() > self.max_arg_count {\n            return Err(LimboError::ParseError(format!(\n                \"Too many arguments for pragma {}: expected at most {}, got {}\",\n                self.pragma_name,\n                self.max_arg_count,\n                args.len()\n            )));\n        }\n\n        let to_text = |v: &Value| v.to_text().map(str::to_owned);\n        let (arg, schema) = match args.as_slice() {\n            [arg0] if self.has_pragma_arg => (to_text(arg0), None),\n            [arg0] => (None, to_text(arg0)),\n            [arg0, arg1] => (to_text(arg0), to_text(arg1)),\n            _ => (None, None),\n        };\n\n        self.arg = arg;\n\n        if let Some(schema) = schema {\n            // Schema-qualified PRAGMA statements are not supported yet\n            return Err(LimboError::ParseError(format!(\n                \"Schema argument is not supported yet (got schema: '{schema}')\"\n            )));\n        }\n\n        let mut sql = format!(\"PRAGMA {}\", self.pragma_name);\n        if let Some(arg) = &self.arg {\n            sql.push_str(&format!(\"=\\\"{arg}\\\"\"));\n        }\n\n        self.stmt = Some(self.conn.prepare(sql)?);\n\n        self.next()\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_best_index_argv_order_both_hidden_constraints() {\n        // Test when both hidden constraints are present (arg and schema)\n        let pragma_vtab = PragmaVirtualTable {\n            pragma_name: \"table_info\".to_string(),\n            visible_column_count: 6,\n            max_arg_count: 2,\n            has_pragma_arg: true,\n        };\n\n        let constraints = vec![\n            usable_constraint(6), // arg (first hidden column)\n            usable_constraint(7), // schema (second hidden column)\n        ];\n\n        let index_info = pragma_vtab.best_index(&constraints).unwrap();\n\n        // Verify arg gets argv_index 1, schema gets argv_index 2\n        assert_eq!(index_info.constraint_usages[0].argv_index, Some(1)); // arg\n        assert_eq!(index_info.constraint_usages[1].argv_index, Some(2)); // schema\n        assert!(index_info.constraint_usages[0].omit);\n        assert!(index_info.constraint_usages[1].omit);\n    }\n\n    #[test]\n    fn test_best_index_argv_order_only_arg() {\n        let pragma_vtab = PragmaVirtualTable {\n            pragma_name: \"table_info\".to_string(),\n            visible_column_count: 6,\n            max_arg_count: 2,\n            has_pragma_arg: true,\n        };\n\n        let constraints = vec![\n            usable_constraint(6), // arg (first hidden column)\n        ];\n\n        let index_info = pragma_vtab.best_index(&constraints).unwrap();\n\n        // Verify arg gets argv_index 1\n        assert_eq!(index_info.constraint_usages[0].argv_index, Some(1)); // arg\n        assert!(index_info.constraint_usages[0].omit);\n    }\n\n    #[test]\n    fn test_best_index_argv_order_only_schema() {\n        let pragma_vtab = PragmaVirtualTable {\n            pragma_name: \"table_info\".to_string(),\n            visible_column_count: 1,\n            max_arg_count: 1,\n            has_pragma_arg: false,\n        };\n\n        let constraints = vec![\n            usable_constraint(1), // schema (first hidden column after visible columns)\n        ];\n\n        let index_info = pragma_vtab.best_index(&constraints).unwrap();\n\n        // Verify schema gets argv_index 1\n        assert_eq!(index_info.constraint_usages[0].argv_index, Some(1)); // schema\n        assert!(index_info.constraint_usages[0].omit);\n    }\n\n    #[test]\n    fn test_best_index_argv_order_reverse_constraint_order() {\n        // Test when constraints are provided in reverse order (schema first, then arg)\n        let pragma_vtab = PragmaVirtualTable {\n            pragma_name: \"table_info\".to_string(),\n            visible_column_count: 6,\n            max_arg_count: 2,\n            has_pragma_arg: true,\n        };\n\n        let constraints = vec![\n            usable_constraint(7), // schema (second hidden column)\n            usable_constraint(6), // arg (first hidden column)\n        ];\n\n        let index_info = pragma_vtab.best_index(&constraints).unwrap();\n\n        // Verify arg still gets argv_index 1, schema gets argv_index 2 regardless of constraint order\n        assert_eq!(index_info.constraint_usages[0].argv_index, Some(2)); // schema\n        assert_eq!(index_info.constraint_usages[1].argv_index, Some(1)); // arg\n        assert!(index_info.constraint_usages[0].omit);\n        assert!(index_info.constraint_usages[1].omit);\n    }\n\n    #[test]\n    fn test_best_index_visible_columns_ignored() {\n        let pragma_vtab = PragmaVirtualTable {\n            pragma_name: \"table_info\".to_string(),\n            visible_column_count: 6,\n            max_arg_count: 2,\n            has_pragma_arg: true,\n        };\n\n        let constraints = vec![\n            usable_constraint(0), // visible column (cid)\n            usable_constraint(6), // arg (hidden)\n        ];\n\n        let index_info = pragma_vtab.best_index(&constraints).unwrap();\n\n        // Verify visible column constraint is ignored, arg gets argv_index 1\n        assert_eq!(index_info.constraint_usages[0].argv_index, None); // visible column\n        assert_eq!(index_info.constraint_usages[1].argv_index, Some(1)); // arg\n        assert!(!index_info.constraint_usages[0].omit); // visible column not omitted\n        assert!(index_info.constraint_usages[1].omit); // arg omitted\n    }\n\n    #[test]\n    fn test_best_index_no_usable_constraints() {\n        let pragma_vtab = PragmaVirtualTable {\n            pragma_name: \"table_info\".to_string(),\n            visible_column_count: 6,\n            max_arg_count: 2,\n            has_pragma_arg: true,\n        };\n\n        let constraints = vec![ConstraintInfo {\n            column_index: 6,\n            op: ConstraintOp::Eq,\n            usable: false,\n            index: 0,\n        }];\n\n        let result = pragma_vtab.best_index(&constraints);\n\n        assert!(matches!(result, Err(ResultCode::ConstraintViolation)));\n    }\n\n    fn usable_constraint(column_index: u32) -> ConstraintInfo {\n        ConstraintInfo {\n            column_index,\n            op: ConstraintOp::Eq,\n            usable: true,\n            index: 0,\n        }\n    }\n}\n"
  },
  {
    "path": "core/pseudo.rs",
    "content": "use crate::{types::ImmutableRecord, Result, Value};\n\npub struct PseudoCursor {\n    current: Option<ImmutableRecord>,\n}\n\nimpl Default for PseudoCursor {\n    #[inline]\n    fn default() -> Self {\n        Self { current: None }\n    }\n}\n\nimpl PseudoCursor {\n    #[inline]\n    pub fn record(&self) -> Option<&ImmutableRecord> {\n        self.current.as_ref()\n    }\n\n    #[inline]\n    pub fn insert(&mut self, record: ImmutableRecord) {\n        self.current = Some(record);\n    }\n\n    #[inline]\n    pub fn get_value(&self, column: usize) -> Result<Value> {\n        if let Some(record) = self.current.as_ref() {\n            Ok(record.get_value(column)?.to_owned())\n        } else {\n            Ok(Value::Null)\n        }\n    }\n}\n"
  },
  {
    "path": "core/regexp.rs",
    "content": "use crate::ext::register_scalar_function;\nuse turso_ext::{scalar, ExtensionApi, Value};\n\npub fn register_extension(ext_api: &mut ExtensionApi) {\n    unsafe {\n        register_scalar_function(ext_api.ctx, c\"regexp\".as_ptr(), regexp);\n    }\n}\n\n#[scalar(name = \"regexp\")]\nfn regexp(args: &[Value]) -> Value {\n    if !(1..=2).contains(&args.len()) {\n        return Value::error_with_message(\"wrong number of arguments to function regexp()\".into());\n    }\n    let Some(pattern) = args[0].to_text_coerced() else {\n        return Value::null();\n    };\n    let Some(haystack) = args[1].to_text_coerced() else {\n        return Value::null();\n    };\n\n    let re = match regex::Regex::new(&pattern) {\n        Ok(re) => re,\n        Err(_) => return Value::null(),\n    };\n\n    Value::from_integer(re.is_match(&haystack) as i64)\n}\n"
  },
  {
    "path": "core/schema.rs",
    "content": "use crate::function::{Deterministic, Func};\nuse crate::incremental::view::IncrementalView;\nuse crate::index_method::{IndexMethodAttachment, IndexMethodConfiguration};\nuse crate::return_if_io;\nuse crate::stats::AnalyzeStats;\nuse crate::sync::RwLock;\nuse crate::translate::emitter::Resolver;\nuse crate::translate::expr::{bind_and_rewrite_expr, walk_expr, BindingBehavior, WalkControl};\nuse crate::translate::index::{resolve_index_method_parameters, resolve_sorted_columns};\nuse crate::translate::planner::ROWID_STRS;\nuse crate::types::IOResult;\nuse crate::util::{exprs_are_equivalent, normalize_ident};\nuse crate::vdbe::affinity::Affinity;\nuse crate::vdbe::CursorID;\nuse crate::{turso_assert, turso_debug_assert};\nuse turso_macros::AtomicEnum;\n\n#[derive(Debug, Clone, AtomicEnum)]\npub enum ViewState {\n    Ready,\n    InProgress,\n}\n\n/// Simple view structure for non-materialized views\n#[derive(Debug)]\npub struct View {\n    pub name: String,\n    pub sql: String,\n    pub select_stmt: ast::Select,\n    pub columns: Vec<Column>,\n    pub state: AtomicViewState,\n}\n\nimpl View {\n    fn new(name: String, sql: String, select_stmt: ast::Select, columns: Vec<Column>) -> Self {\n        Self {\n            name,\n            sql,\n            select_stmt,\n            columns,\n            state: AtomicViewState::new(ViewState::Ready),\n        }\n    }\n\n    pub fn process(&self) -> Result<()> {\n        let state = self.state.get();\n        match state {\n            ViewState::InProgress => {\n                bail_parse_error!(\"view {} is circularly defined\", self.name)\n            }\n            ViewState::Ready => {\n                self.state.set(ViewState::InProgress);\n                Ok(())\n            }\n        }\n    }\n\n    pub fn done(&self) {\n        let state = self.state.get();\n        match state {\n            ViewState::InProgress => {\n                self.state.set(ViewState::Ready);\n            }\n            ViewState::Ready => {}\n        }\n    }\n}\n\nimpl Clone for View {\n    fn clone(&self) -> Self {\n        Self {\n            name: self.name.clone(),\n            sql: self.sql.clone(),\n            select_stmt: self.select_stmt.clone(),\n            columns: self.columns.clone(),\n            state: AtomicViewState::new(ViewState::Ready),\n        }\n    }\n}\n\n/// Type alias for regular views collection\npub type ViewsMap = HashMap<String, Arc<View>>;\n\n/// Trigger structure\n#[derive(Debug, Clone)]\npub struct Trigger {\n    pub name: String,\n    pub sql: String,\n    pub table_name: String,\n    pub time: turso_parser::ast::TriggerTime,\n    pub event: turso_parser::ast::TriggerEvent,\n    pub for_each_row: bool,\n    pub when_clause: Option<turso_parser::ast::Expr>,\n    pub commands: Vec<turso_parser::ast::TriggerCmd>,\n    pub temporary: bool,\n}\n\nimpl Trigger {\n    #[allow(clippy::too_many_arguments)]\n    pub fn new(\n        name: String,\n        sql: String,\n        table_name: String,\n        time: Option<turso_parser::ast::TriggerTime>,\n        event: turso_parser::ast::TriggerEvent,\n        for_each_row: bool,\n        when_clause: Option<turso_parser::ast::Expr>,\n        commands: Vec<turso_parser::ast::TriggerCmd>,\n        temporary: bool,\n    ) -> Self {\n        Self {\n            name,\n            sql,\n            table_name,\n            time: time.unwrap_or(turso_parser::ast::TriggerTime::Before),\n            event,\n            for_each_row,\n            when_clause,\n            commands,\n            temporary,\n        }\n    }\n}\n\nuse crate::storage::btree::{BTreeCursor, CursorTrait};\nuse crate::sync::Arc;\nuse crate::sync::Mutex;\nuse crate::translate::collate::CollationSeq;\nuse crate::translate::plan::{Plan, TableReferences};\nuse crate::util::{\n    module_args_from_sql, module_name_from_sql, type_from_name, UnparsedFromSqlIndex,\n};\nuse crate::Result;\nuse crate::{\n    bail_parse_error, contains_ignore_ascii_case, eq_ignore_ascii_case, match_ignore_ascii_case,\n    LimboError, MvCursor, Pager, SymbolTable, ValueRef, VirtualTable,\n};\nuse core::fmt;\nuse rustc_hash::{FxBuildHasher, FxHashMap as HashMap, FxHashSet as HashSet};\nuse std::collections::VecDeque;\nuse std::ops::Deref;\nuse tracing::trace;\nuse turso_parser::ast::{\n    self, ColumnDefinition, Expr, InitDeferredPred, Literal, Name, RefAct, ResolveType, SortOrder,\n    TypeOperator,\n};\nuse turso_parser::{\n    ast::{Cmd, CreateTableBody, ResultColumn, Stmt},\n    parser::Parser,\n};\n\nconst SCHEMA_TABLE_NAME: &str = \"sqlite_schema\";\nconst SCHEMA_TABLE_NAME_ALT: &str = \"sqlite_master\";\npub const SQLITE_SEQUENCE_TABLE_NAME: &str = \"sqlite_sequence\";\npub const TURSO_TYPES_TABLE_NAME: &str = \"__turso_internal_types\";\npub const DBSP_TABLE_PREFIX: &str = \"__turso_internal_dbsp_state_v\";\npub const TURSO_INTERNAL_PREFIX: &str = \"__turso_internal_\";\n\n/// Quote a SQL identifier with double quotes if it needs quoting.\n/// Quotes when the name contains non-alphanumeric characters (except underscore),\n/// starts with a digit, or is empty. Simple names like \"test_uint\" are left unquoted.\nfn quote_ident(name: &str) -> String {\n    let needs_quoting = name.is_empty()\n        || name.as_bytes()[0].is_ascii_digit()\n        || !name.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'_')\n        || turso_parser::lexer::is_keyword(name.as_bytes());\n    if needs_quoting {\n        let escaped = name.replace('\"', \"\\\"\\\"\");\n        format!(\"\\\"{escaped}\\\"\")\n    } else {\n        name.to_string()\n    }\n}\n\n/// Escape a string literal for SQL single-quote context.\n/// The value goes inside the surrounding quotes already present in the format string.\nfn quote_string_literal(s: &str) -> String {\n    s.replace('\\'', \"''\")\n}\n\n/// Custom type definition, loaded from sqlite_turso_types\n#[derive(Debug, Clone)]\npub struct TypeDef {\n    pub name: String,\n    pub params: Vec<ast::TypeParam>,\n    pub base: String,\n    pub encode: Option<Box<ast::Expr>>,\n    pub decode: Option<Box<ast::Expr>>,\n    pub operators: Vec<TypeOperator>,\n    pub default: Option<Box<ast::Expr>>,\n    pub is_builtin: bool,\n}\n\nimpl TypeDef {\n    /// Construct a TypeDef from a parsed CREATE TYPE statement.\n    pub fn from_create_type(type_name: &str, body: &ast::CreateTypeBody, is_builtin: bool) -> Self {\n        Self {\n            name: type_name.to_string(),\n            params: body.params.clone(),\n            base: body.base.clone(),\n            encode: body.encode.clone(),\n            decode: body.decode.clone(),\n            operators: body.operators.clone(),\n            default: body.default.clone(),\n            is_builtin,\n        }\n    }\n\n    /// The expected input type for `value` in this custom type.\n    /// Looks for a `value` parameter with a type annotation.\n    /// Falls back to `self.base` if `value` is not declared.\n    pub fn value_input_type(&self) -> &str {\n        for p in &self.params {\n            if p.name.eq_ignore_ascii_case(\"value\") {\n                return p.ty.as_deref().unwrap_or(&self.base);\n            }\n        }\n        &self.base\n    }\n\n    /// The non-value params (user-provided at column declaration time).\n    pub fn user_params(&self) -> impl Iterator<Item = &turso_parser::ast::TypeParam> {\n        self.params\n            .iter()\n            .filter(|p| !p.name.eq_ignore_ascii_case(\"value\"))\n    }\n\n    /// Reconstruct the CREATE TYPE SQL string from this definition.\n    pub fn to_sql(&self) -> String {\n        let mut sql = if self.params.is_empty() {\n            format!(\n                \"CREATE TYPE {} BASE {}\",\n                quote_ident(&self.name),\n                quote_ident(&self.base)\n            )\n        } else {\n            let params: Vec<String> = self\n                .params\n                .iter()\n                .map(|p| match &p.ty {\n                    Some(ty) => format!(\"{} {}\", quote_ident(&p.name), ty),\n                    None => quote_ident(&p.name),\n                })\n                .collect();\n            format!(\n                \"CREATE TYPE {}({}) BASE {}\",\n                quote_ident(&self.name),\n                params.join(\", \"),\n                quote_ident(&self.base)\n            )\n        };\n        if let Some(ref encode) = self.encode {\n            sql.push_str(&format!(\" ENCODE {encode}\"));\n        }\n        if let Some(ref decode) = self.decode {\n            sql.push_str(&format!(\" DECODE {decode}\"));\n        }\n        if let Some(ref default) = self.default {\n            sql.push_str(&format!(\" DEFAULT {default}\"));\n        }\n        for op in &self.operators {\n            match &op.func_name {\n                Some(func_name) => sql.push_str(&format!(\n                    \" OPERATOR '{}' {}\",\n                    quote_string_literal(&op.op),\n                    quote_ident(func_name)\n                )),\n                None => sql.push_str(&format!(\" OPERATOR '{}'\", quote_string_literal(&op.op),)),\n            }\n        }\n        sql\n    }\n}\n\n/// Accumulators for schema loading - kept separate to avoid moving through state variants\nstruct MakeFromBtreeAccumulators {\n    from_sql_indexes: Vec<UnparsedFromSqlIndex>,\n    automatic_indices: HashMap<String, Vec<(String, i64)>>,\n    /// Store DBSP state table root pages: view_name -> dbsp_state_root_page\n    dbsp_state_roots: HashMap<String, i64>,\n    /// Store DBSP state table index root pages: view_name -> dbsp_state_index_root_page\n    dbsp_state_index_roots: HashMap<String, i64>,\n    /// Store materialized view info (SQL and root page) for later creation\n    materialized_view_info: HashMap<String, (String, i64)>,\n}\n\n/// Phase tracking for async schema loading\n#[derive(Default, Debug)]\npub enum MakeFromBtreePhase {\n    #[default]\n    Init,\n    Rewinding,\n    FetchingRecord,\n    Advancing,\n    Done,\n}\n\n/// State machine for async schema loading - passed by caller, not stored on Schema\npub struct MakeFromBtreeState {\n    phase: MakeFromBtreePhase,\n    cursor: Option<BTreeCursor>,\n    accumulators: Option<MakeFromBtreeAccumulators>,\n    read_tx_active: bool,\n}\n\nimpl Default for MakeFromBtreeState {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl MakeFromBtreeState {\n    pub fn new() -> Self {\n        Self {\n            phase: MakeFromBtreePhase::Init,\n            cursor: None,\n            accumulators: None,\n            read_tx_active: false,\n        }\n    }\n\n    /// Cleanup on error - ensures end_read_tx is called\n    pub fn cleanup(&mut self, pager: &Pager) {\n        if self.read_tx_active {\n            pager.end_read_tx();\n            self.read_tx_active = false;\n        }\n        self.cursor = None;\n        self.accumulators = None;\n    }\n}\n\n/// Used to refer to the implicit rowid column in tables without an alias during UPDATE\npub const ROWID_SENTINEL: usize = usize::MAX;\n\n/// The Position in Table for indexes which are arbitrary expressions (index.expr.is_some())\npub const EXPR_INDEX_SENTINEL: usize = usize::MAX;\n\n/// Internal table prefixes that should be protected from CREATE/DROP\npub const RESERVED_TABLE_PREFIXES: [&str; 2] = [\"sqlite_\", \"__turso_internal_\"];\n\n/// Check if a table name refers to a system table that should be protected from direct writes\npub fn is_system_table(table_name: &str) -> bool {\n    RESERVED_TABLE_PREFIXES\n        .iter()\n        .any(|prefix| table_name.to_lowercase().starts_with(prefix))\n}\n\npub fn can_write_to_table(table_name: &str) -> bool {\n    let normalized = table_name.to_lowercase();\n    !(normalized == SCHEMA_TABLE_NAME\n        || normalized == SCHEMA_TABLE_NAME_ALT\n        || normalized.starts_with(TURSO_INTERNAL_PREFIX))\n}\n\n/// Type of schema object for conflict checking\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum SchemaObjectType {\n    Table,\n    View,\n    Index,\n}\n\n#[derive(Debug)]\npub struct Schema {\n    pub tables: HashMap<String, Arc<Table>>,\n\n    /// Track which tables are actually materialized views\n    pub materialized_view_names: HashSet<String>,\n    /// Store original SQL for materialized views (for .schema command)\n    pub materialized_view_sql: HashMap<String, String>,\n    /// The incremental view objects (DBSP circuits)\n    pub incremental_views: HashMap<String, Arc<Mutex<IncrementalView>>>,\n\n    pub views: ViewsMap,\n\n    /// table_name to list of triggers\n    pub triggers: HashMap<String, VecDeque<Arc<Trigger>>>,\n\n    /// table_name to list of indexes for the table\n    pub indexes: HashMap<String, VecDeque<Arc<Index>>>,\n    pub has_indexes: HashSet<String>,\n    pub schema_version: u32,\n    /// Statistics collected via ANALYZE for regular B-tree tables and indexes.\n    pub analyze_stats: AnalyzeStats,\n\n    /// Mapping from table names to the materialized views that depend on them\n    pub table_to_materialized_views: HashMap<String, Vec<String>>,\n\n    /// Track views that exist but have incompatible versions\n    pub incompatible_views: HashSet<String>,\n\n    /// Root pages of tables/indexes that have been dropped but not yet checkpointed.\n    /// In MVCC mode, when a table is dropped, the btree pages are not freed until checkpoint.\n    /// integrity_check needs to know about these pages to avoid false positives about \"page never used\".\n    pub dropped_root_pages: HashSet<i64>,\n\n    /// Custom type registry, loaded from sqlite_turso_types\n    pub type_registry: HashMap<String, Arc<TypeDef>>,\n}\n\nimpl Default for Schema {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nfn bootstrap_builtin_types(registry: &mut HashMap<String, Arc<TypeDef>>) {\n    use turso_parser::ast::{Cmd, Stmt};\n    use turso_parser::parser::Parser;\n\n    let type_sqls: &[&str] = &[\n        #[cfg(feature = \"uuid\")]\n        \"CREATE TYPE uuid(value text) BASE blob ENCODE uuid_blob(value) DECODE uuid_str(value) DEFAULT uuid4_str() OPERATOR '<'\",\n        \"CREATE TYPE boolean(value any) BASE integer ENCODE boolean_to_int(value) DECODE CASE WHEN value THEN 1 ELSE 0 END OPERATOR '<'\",\n        #[cfg(feature = \"json\")]\n        \"CREATE TYPE json(value text) BASE text ENCODE json(value) DECODE value\",\n        #[cfg(feature = \"json\")]\n        \"CREATE TYPE jsonb(value text) BASE blob ENCODE jsonb(value) DECODE json(value)\",\n        \"CREATE TYPE varchar(value text, maxlen integer) BASE text ENCODE CASE WHEN length(value) <= maxlen THEN value ELSE RAISE(ABORT, 'value too long for varchar') END DECODE value OPERATOR '<'\",\n        \"CREATE TYPE date(value text) BASE text ENCODE CASE WHEN value IS NULL THEN NULL WHEN date(value) IS NULL THEN RAISE(ABORT, 'invalid date value') ELSE date(value) END DECODE value OPERATOR '<'\",\n        \"CREATE TYPE time(value text) BASE text ENCODE CASE WHEN value IS NULL THEN NULL WHEN time(value) IS NULL THEN RAISE(ABORT, 'invalid time value') ELSE time(value) END DECODE value OPERATOR '<'\",\n        \"CREATE TYPE timestamp(value text) BASE text ENCODE CASE WHEN value IS NULL THEN NULL WHEN datetime(value) IS NULL THEN RAISE(ABORT, 'invalid timestamp value') ELSE datetime(value) END DECODE value OPERATOR '<'\",\n        \"CREATE TYPE smallint(value integer) BASE integer ENCODE CASE WHEN value BETWEEN -32768 AND 32767 THEN value ELSE RAISE(ABORT, 'integer out of range for smallint') END DECODE value OPERATOR '<'\",\n        \"CREATE TYPE bigint(value integer) BASE integer\",\n        \"CREATE TYPE inet(value text) BASE text ENCODE validate_ipaddr(value) DECODE value\",\n        \"CREATE TYPE bytea(value blob) BASE blob OPERATOR '<'\",\n        \"CREATE TYPE numeric(value any, precision integer, scale integer) BASE blob ENCODE numeric_encode(value, precision, scale) DECODE numeric_decode(value) OPERATOR '+' numeric_add OPERATOR '-' numeric_sub OPERATOR '*' numeric_mul OPERATOR '/' numeric_div OPERATOR '<' numeric_lt OPERATOR '=' numeric_eq\",\n    ];\n\n    for sql in type_sqls {\n        let mut parser = Parser::new(sql.as_bytes());\n        let Ok(Some(Cmd::Stmt(Stmt::CreateType {\n            type_name, body, ..\n        }))) = parser.next_cmd()\n        else {\n            panic!(\"Failed to parse built-in type SQL: {sql}\");\n        };\n\n        let type_def = TypeDef::from_create_type(&type_name, &body, true);\n        registry.insert(type_name.to_lowercase(), Arc::new(type_def));\n    }\n\n    // Register aliases\n    let aliases: &[(&str, &str)] = &[\n        (\"bool\", \"boolean\"),\n        (\"int2\", \"smallint\"),\n        (\"int8\", \"bigint\"),\n    ];\n    for (alias, target) in aliases {\n        if let Some(type_def) = registry.get(*target).cloned() {\n            registry.insert(alias.to_string(), type_def);\n        }\n    }\n}\n\nimpl Schema {\n    pub fn new() -> Self {\n        Self::with_options(true)\n    }\n\n    pub fn with_options(enable_custom_types: bool) -> Self {\n        let mut tables: HashMap<String, Arc<Table>> = HashMap::default();\n        let has_indexes = HashSet::default();\n        let indexes: HashMap<String, VecDeque<Arc<Index>>> = HashMap::default();\n        #[allow(clippy::arc_with_non_send_sync)]\n        tables.insert(\n            SCHEMA_TABLE_NAME.to_string(),\n            Arc::new(Table::BTree(sqlite_schema_table().into())),\n        );\n        for function in VirtualTable::builtin_functions(enable_custom_types) {\n            tables.insert(\n                function.name.to_owned(),\n                Arc::new(Table::Virtual(Arc::new((*function).clone()))),\n            );\n        }\n        let materialized_view_names = HashSet::default();\n        let materialized_view_sql = HashMap::default();\n        let incremental_views = HashMap::default();\n        let views: ViewsMap = HashMap::default();\n        let triggers = HashMap::default();\n        let table_to_materialized_views: HashMap<String, Vec<String>> = HashMap::default();\n        let incompatible_views = HashSet::default();\n        Self {\n            tables,\n            materialized_view_names,\n            materialized_view_sql,\n            incremental_views,\n            views,\n            triggers,\n            indexes,\n            has_indexes,\n            schema_version: 0,\n            analyze_stats: AnalyzeStats::default(),\n            table_to_materialized_views,\n            incompatible_views,\n            dropped_root_pages: HashSet::default(),\n            type_registry: {\n                let mut registry = HashMap::default();\n                if enable_custom_types {\n                    bootstrap_builtin_types(&mut registry);\n                }\n                registry\n            },\n        }\n    }\n\n    /// Look up a custom type definition by name.\n    /// Custom types are only valid on STRICT tables; pass `is_strict` from the\n    /// owning table so that non-STRICT tables never resolve a custom type.\n    pub fn get_type_def(&self, type_name: &str, is_strict: bool) -> Option<&Arc<TypeDef>> {\n        if !is_strict {\n            return None;\n        }\n        self.type_registry.get(&type_name.to_lowercase())\n    }\n\n    /// Look up a custom type definition by name without a strictness check.\n    /// Only use this for operations that aren't column-scoped (e.g. DROP TYPE,\n    /// CREATE TABLE validation, CAST).\n    pub fn get_type_def_unchecked(&self, type_name: &str) -> Option<&Arc<TypeDef>> {\n        self.type_registry.get(&type_name.to_lowercase())\n    }\n\n    pub fn remove_type(&mut self, type_name: &str) {\n        self.type_registry.remove(&type_name.to_lowercase());\n    }\n\n    /// Parse a CREATE TYPE SQL string and add the type to the in-memory registry.\n    pub fn add_type_from_sql(&mut self, sql: &str) -> crate::Result<()> {\n        use turso_parser::ast::{Cmd, Stmt};\n        use turso_parser::parser::Parser;\n\n        let mut parser = Parser::new(sql.as_bytes());\n        let Ok(Some(Cmd::Stmt(Stmt::CreateType {\n            type_name, body, ..\n        }))) = parser.next_cmd()\n        else {\n            return Err(crate::LimboError::ParseError(format!(\n                \"invalid type sql: {sql}\"\n            )));\n        };\n\n        let type_def = TypeDef::from_create_type(&type_name, &body, false);\n        self.type_registry\n            .insert(type_name.to_lowercase(), Arc::new(type_def));\n        Ok(())\n    }\n\n    /// Load type definitions from CREATE TYPE SQL strings and resolve custom\n    /// type affinities on all STRICT tables. This is the shared entry point\n    /// used by both initial database open and schema reparse.\n    pub fn load_type_definitions(&mut self, type_sqls: &[String]) -> crate::Result<()> {\n        for sql in type_sqls {\n            self.add_type_from_sql(sql)?;\n        }\n        self.resolve_all_custom_type_affinities();\n        Ok(())\n    }\n\n    /// Resolve custom type affinities for all STRICT tables in the schema.\n    /// Call this after loading user-defined types from __turso_internal_types\n    /// so that columns declared with custom types use the BASE type's affinity.\n    pub fn resolve_all_custom_type_affinities(&mut self) {\n        let table_names: Vec<String> = self\n            .tables\n            .iter()\n            .filter_map(|(name, t)| {\n                if let Table::BTree(bt) = t.as_ref() {\n                    if bt.is_strict {\n                        return Some(name.clone());\n                    }\n                }\n                None\n            })\n            .collect();\n        for name in table_names {\n            if let Some(table_arc) = self.tables.get(&name) {\n                if let Table::BTree(bt) = table_arc.as_ref() {\n                    let needs_fixup = bt\n                        .columns\n                        .iter()\n                        .any(|c| self.get_type_def_unchecked(&c.ty_str).is_some());\n                    if needs_fixup {\n                        let mut modified = (**bt).clone();\n                        modified.resolve_custom_type_affinities(self);\n                        self.tables\n                            .insert(name, Arc::new(Table::BTree(Arc::new(modified))));\n                    }\n                }\n            }\n        }\n    }\n\n    pub fn is_unique_idx_name(&self, name: &str) -> bool {\n        !self\n            .indexes\n            .iter()\n            .any(|idx| idx.1.iter().any(|i| i.name == name))\n    }\n    pub fn add_materialized_view(&mut self, view: IncrementalView, table: Arc<Table>, sql: String) {\n        let name = normalize_ident(view.name());\n\n        // Add to tables (so it appears as a regular table)\n        self.tables.insert(name.clone(), table);\n\n        // Track that this is a materialized view\n        self.materialized_view_names.insert(name.clone());\n        self.materialized_view_sql.insert(name.clone(), sql);\n\n        // Store the incremental view (DBSP circuit)\n        self.incremental_views\n            .insert(name, Arc::new(Mutex::new(view)));\n    }\n\n    pub fn get_materialized_view(&self, name: &str) -> Option<Arc<Mutex<IncrementalView>>> {\n        let name = normalize_ident(name);\n        self.incremental_views.get(&name).cloned()\n    }\n\n    /// Check if DBSP state table exists with the current version\n    pub fn has_compatible_dbsp_state_table(&self, view_name: &str) -> bool {\n        use crate::incremental::compiler::DBSP_CIRCUIT_VERSION;\n        let view_name = normalize_ident(view_name);\n        let expected_table_name = format!(\"{DBSP_TABLE_PREFIX}{DBSP_CIRCUIT_VERSION}_{view_name}\");\n\n        // Check if a table with the expected versioned name exists\n        self.tables.contains_key(&expected_table_name)\n    }\n\n    pub fn is_materialized_view(&self, name: &str) -> bool {\n        let name = normalize_ident(name);\n        self.materialized_view_names.contains(&name)\n    }\n\n    /// Check if a table has any incompatible dependent materialized views\n    pub fn has_incompatible_dependent_views(&self, table_name: &str) -> Vec<String> {\n        let table_name = normalize_ident(table_name);\n\n        // Get all materialized views that depend on this table\n        let dependent_views = self\n            .table_to_materialized_views\n            .get(&table_name)\n            .cloned()\n            .unwrap_or_default();\n\n        // Filter to only incompatible views\n        dependent_views\n            .into_iter()\n            .filter(|view_name| self.incompatible_views.contains(view_name))\n            .collect()\n    }\n\n    pub fn remove_view(&mut self, name: &str) -> Result<()> {\n        let name = normalize_ident(name);\n\n        if self.views.contains_key(&name) {\n            self.views.remove(&name);\n            Ok(())\n        } else if self.materialized_view_names.contains(&name) {\n            // Remove from tables\n            self.tables.remove(&name);\n\n            // Remove DBSP state table and its indexes from in-memory schema\n            use crate::incremental::compiler::DBSP_CIRCUIT_VERSION;\n            let dbsp_table_name = format!(\"{DBSP_TABLE_PREFIX}{DBSP_CIRCUIT_VERSION}_{name}\");\n            self.tables.remove(&dbsp_table_name);\n            self.remove_indices_for_table(&dbsp_table_name);\n\n            // Remove from materialized view tracking\n            self.materialized_view_names.remove(&name);\n            self.materialized_view_sql.remove(&name);\n            self.incremental_views.remove(&name);\n\n            // Remove from table_to_materialized_views dependencies\n            for views in self.table_to_materialized_views.values_mut() {\n                views.retain(|v| v != &name);\n            }\n\n            Ok(())\n        } else {\n            Err(crate::LimboError::ParseError(format!(\n                \"no such view: {name}\"\n            )))\n        }\n    }\n\n    /// Register that a materialized view depends on a table\n    pub fn add_materialized_view_dependency(&mut self, table_name: &str, view_name: &str) {\n        let table_name = normalize_ident(table_name);\n        let view_name = normalize_ident(view_name);\n\n        self.table_to_materialized_views\n            .entry(table_name)\n            .or_default()\n            .push(view_name);\n    }\n\n    /// Get all materialized views that depend on a given table\n    pub fn get_dependent_materialized_views(&self, table_name: &str) -> Vec<String> {\n        if self.table_to_materialized_views.is_empty() {\n            return Vec::new();\n        }\n        let table_name = normalize_ident(table_name);\n        self.table_to_materialized_views\n            .get(&table_name)\n            .cloned()\n            .unwrap_or_default()\n    }\n\n    /// Add a regular (non-materialized) view\n    pub fn add_view(&mut self, view: View) -> Result<()> {\n        self.check_object_name_conflict(&view.name)?;\n        let name = normalize_ident(&view.name);\n        self.views.insert(name, Arc::new(view));\n        Ok(())\n    }\n\n    /// Get a regular view by name\n    pub fn get_view(&self, name: &str) -> Option<Arc<View>> {\n        let name = normalize_ident(name);\n        self.views.get(&name).cloned()\n    }\n\n    pub fn add_trigger(&mut self, trigger: Trigger, table_name: &str) -> Result<()> {\n        self.check_object_name_conflict(&trigger.name)?;\n        let table_name = normalize_ident(table_name);\n\n        // See [Schema::add_index] for why we push to the front of the deque.\n        self.triggers\n            .entry(table_name)\n            .or_default()\n            .push_front(Arc::new(trigger));\n\n        Ok(())\n    }\n\n    pub fn remove_trigger(&mut self, name: &str) -> Result<()> {\n        let name = normalize_ident(name);\n\n        let mut removed = false;\n        for triggers_list in self.triggers.values_mut() {\n            for i in 0..triggers_list.len() {\n                let trigger = &triggers_list[i];\n                if normalize_ident(&trigger.name) == name {\n                    removed = true;\n                    triggers_list.remove(i);\n                    break;\n                }\n            }\n            if removed {\n                break;\n            }\n        }\n        if !removed {\n            return Err(crate::LimboError::ParseError(format!(\n                \"no such trigger: {name}\"\n            )));\n        }\n        Ok(())\n    }\n    pub fn remove_triggers_for_table(&mut self, table_name: &str) {\n        let table_name = normalize_ident(table_name);\n        self.triggers.remove(&table_name);\n    }\n\n    pub fn get_trigger_for_table(&self, table_name: &str, name: &str) -> Option<Arc<Trigger>> {\n        let table_name = normalize_ident(table_name);\n        let name = normalize_ident(name);\n        self.triggers\n            .get(&table_name)\n            .and_then(|triggers| triggers.iter().find(|t| t.name == name).cloned())\n    }\n\n    pub fn get_triggers_for_table(\n        &self,\n        table_name: &str,\n    ) -> impl Iterator<Item = &Arc<Trigger>> + Clone {\n        let table_name = normalize_ident(table_name);\n        self.triggers\n            .get(&table_name)\n            .map(|triggers| triggers.iter())\n            .unwrap_or_default()\n    }\n\n    pub fn get_trigger(&self, name: &str) -> Option<Arc<Trigger>> {\n        let name = normalize_ident(name);\n        self.triggers\n            .values()\n            .flatten()\n            .find(|t| t.name == name)\n            .cloned()\n    }\n\n    pub fn add_btree_table(&mut self, table: Arc<BTreeTable>) -> Result<()> {\n        self.check_object_name_conflict(&table.name)?;\n        let name = normalize_ident(&table.name);\n        self.tables.insert(name, Table::BTree(table).into());\n        Ok(())\n    }\n\n    pub fn add_virtual_table(&mut self, table: Arc<VirtualTable>) -> Result<()> {\n        self.check_object_name_conflict(&table.name)?;\n        let name = normalize_ident(&table.name);\n        self.tables.insert(name, Table::Virtual(table).into());\n        Ok(())\n    }\n\n    pub fn get_table(&self, name: &str) -> Option<Arc<Table>> {\n        let name = normalize_ident(name);\n        let name = if name.eq_ignore_ascii_case(SCHEMA_TABLE_NAME_ALT) {\n            SCHEMA_TABLE_NAME\n        } else {\n            &name\n        };\n        self.tables.get(name).cloned()\n    }\n\n    pub fn remove_table(&mut self, table_name: &str) {\n        let name = normalize_ident(table_name);\n        self.tables.remove(&name);\n        self.analyze_stats.remove_table(&name);\n\n        // If this was a materialized view, also clean up the metadata\n        if self.materialized_view_names.remove(&name) {\n            self.incremental_views.remove(&name);\n            self.materialized_view_sql.remove(&name);\n        }\n    }\n\n    pub fn get_btree_table(&self, name: &str) -> Option<Arc<BTreeTable>> {\n        let name = normalize_ident(name);\n        if let Some(table) = self.tables.get(&name) {\n            table.btree()\n        } else {\n            None\n        }\n    }\n\n    pub fn add_index(&mut self, index: Arc<Index>) -> Result<()> {\n        self.check_object_name_conflict(&index.name)?;\n        let table_name = normalize_ident(&index.table_name);\n        // We must add the new index to the front of the deque, because SQLite stores index definitions as a linked list\n        // where the newest parsed index entry is at the head of list. If we would add it to the back of a regular Vec for example,\n        // then we would evaluate ON CONFLICT DO UPDATE clauses in the wrong index iteration order and UPDATE the wrong row.\n        // Additionally, REPLACE indexes must go after all the non-REPLACE indexes so that\n        // non-mutating conflict resolutions all happen before mutating ones, ensuring that\n        // no half-committed state is left behind.\n        let is_replace = index.on_conflict == Some(ResolveType::Replace);\n        let indexes_for_table = self.indexes.entry(table_name).or_default();\n        if is_replace {\n            // REPLACE indexes sort newest-first among themselves.\n            let first_replace = indexes_for_table\n                .iter()\n                .position(|idx| idx.on_conflict == Some(ResolveType::Replace));\n            let pos = first_replace.unwrap_or(indexes_for_table.len());\n            indexes_for_table.insert(pos, index);\n        } else {\n            // Non-REPLACE indexes go at the front, newest first.\n            indexes_for_table.push_front(index);\n        }\n        turso_debug_assert!(\n            indexes_for_table\n                .iter()\n                .position(|idx| idx.on_conflict == Some(ResolveType::Replace))\n                .is_none_or(|first_replace| {\n                    indexes_for_table\n                        .iter()\n                        .skip(first_replace)\n                        .all(|idx| idx.on_conflict == Some(ResolveType::Replace))\n                }),\n            \"REPLACE indexes must form a contiguous suffix\"\n        );\n        Ok(())\n    }\n\n    pub fn get_indices(&self, table_name: &str) -> impl Iterator<Item = &Arc<Index>> {\n        let name = normalize_ident(table_name);\n        self.indexes\n            .get(&name)\n            .map(|v| v.iter())\n            .unwrap_or_default()\n            .filter(|i| !i.is_backing_btree_index())\n    }\n\n    #[cfg(all(feature = \"fts\", not(target_family = \"wasm\")))]\n    pub fn has_fts_index(&self, table_name: &str) -> bool {\n        use crate::index_method::fts::FTS_INDEX_METHOD_NAME;\n        self.get_indices(table_name).any(|idx| {\n            idx.index_method\n                .as_ref()\n                .is_some_and(|m| m.definition().method_name == FTS_INDEX_METHOD_NAME)\n        })\n    }\n\n    pub fn get_index(&self, table_name: &str, index_name: &str) -> Option<&Arc<Index>> {\n        let name = normalize_ident(table_name);\n        self.indexes\n            .get(&name)?\n            .iter()\n            .find(|index| index.name == index_name)\n    }\n\n    pub fn remove_indices_for_table(&mut self, table_name: &str) {\n        let name = normalize_ident(table_name);\n        self.indexes.remove(&name);\n        self.analyze_stats.remove_table(&name);\n    }\n\n    pub fn remove_index(&mut self, idx: &Index) {\n        let name = normalize_ident(&idx.table_name);\n        self.indexes\n            .get_mut(&name)\n            .expect(\"Must have the index\")\n            .retain_mut(|other_idx| other_idx.name != idx.name);\n        self.analyze_stats.remove_index(&name, &idx.name);\n    }\n\n    pub fn table_has_indexes(&self, table_name: &str) -> bool {\n        let name = normalize_ident(table_name);\n        self.has_indexes.contains(&name)\n    }\n\n    pub fn table_set_has_index(&mut self, table_name: &str) {\n        self.has_indexes.insert(table_name.to_string());\n    }\n\n    /// Update [Schema] by scanning the first root page (sqlite_schema)\n    /// Returns Result<IOResult<()>> to allow async operation with external IO loop\n    pub fn make_from_btree(\n        &mut self,\n        state: &mut MakeFromBtreeState,\n        mv_cursor: Option<Arc<RwLock<MvCursor>>>,\n        pager: &Arc<Pager>,\n        syms: &SymbolTable,\n    ) -> Result<IOResult<()>> {\n        let result = self.make_from_btree_internal(state, mv_cursor, pager, syms);\n        if result.is_err() {\n            state.cleanup(pager);\n        } else if let Ok(IOResult::Done(..)) = result {\n            turso_assert!(\n                !state.read_tx_active,\n                \"make_from_btree must properly cleanup internal state in case of success\"\n            );\n        }\n        result\n    }\n\n    fn make_from_btree_internal(\n        &mut self,\n        state: &mut MakeFromBtreeState,\n        mv_cursor: Option<Arc<RwLock<MvCursor>>>,\n        pager: &Arc<Pager>,\n        syms: &SymbolTable,\n    ) -> Result<IOResult<()>> {\n        loop {\n            tracing::debug!(\"make_from_btree: state.phase={:?}\", state.phase);\n            match &state.phase {\n                MakeFromBtreePhase::Init => {\n                    if mv_cursor.is_some() {\n                        return Err(crate::LimboError::ParseError(\n                            \"MVCC is not supported for make_from_btree schema recovery\".to_string(),\n                        ));\n                    }\n\n                    state.cursor = Some(BTreeCursor::new_table(Arc::clone(pager), 1, 10));\n                    pager.begin_read_tx()?;\n                    state.read_tx_active = true;\n\n                    state.accumulators = Some(MakeFromBtreeAccumulators {\n                        from_sql_indexes: Vec::with_capacity(10),\n                        automatic_indices: HashMap::with_capacity_and_hasher(10, FxBuildHasher),\n                        dbsp_state_roots: HashMap::default(),\n                        dbsp_state_index_roots: HashMap::default(),\n                        materialized_view_info: HashMap::default(),\n                    });\n\n                    state.phase = MakeFromBtreePhase::Rewinding;\n                }\n\n                MakeFromBtreePhase::Rewinding => {\n                    let cursor = state\n                        .cursor\n                        .as_mut()\n                        .expect(\"cursor must be initialized in Init phase\");\n                    return_if_io!(cursor.rewind());\n                    state.phase = MakeFromBtreePhase::FetchingRecord;\n                }\n\n                MakeFromBtreePhase::FetchingRecord => {\n                    let cursor = state\n                        .cursor\n                        .as_mut()\n                        .expect(\"cursor must be initialized in Init phase\");\n                    let row = return_if_io!(cursor.record());\n\n                    let Some(row) = row else {\n                        // EOF - finalize\n                        pager.end_read_tx();\n                        state.read_tx_active = false;\n\n                        let acc = state\n                            .accumulators\n                            .take()\n                            .expect(\"accumulators must be initialized in Init phase\");\n                        self.populate_indices(\n                            syms,\n                            acc.from_sql_indexes,\n                            acc.automatic_indices,\n                            mv_cursor.is_some(),\n                        )?;\n                        self.populate_materialized_views(\n                            acc.materialized_view_info,\n                            acc.dbsp_state_roots,\n                            acc.dbsp_state_index_roots,\n                        )?;\n\n                        state.cursor = None;\n                        state.phase = MakeFromBtreePhase::Done;\n                        return Ok(IOResult::Done(()));\n                    };\n\n                    // Process the row (no IO - CPU only)\n                    // sqlite schema table has 5 columns: type, name, tbl_name, rootpage, sql\n                    let ty_value = row.get_value(0)?;\n                    let ValueRef::Text(ty) = ty_value else {\n                        return Err(LimboError::ConversionError(\"Expected text value\".into()));\n                    };\n                    let ValueRef::Text(name) = row.get_value(1)? else {\n                        return Err(LimboError::ConversionError(\"Expected text value\".into()));\n                    };\n                    let table_name_value = row.get_value(2)?;\n                    let ValueRef::Text(table_name) = table_name_value else {\n                        return Err(LimboError::ConversionError(\"Expected text value\".into()));\n                    };\n                    let root_page_value = row.get_value(3)?;\n                    let ValueRef::Numeric(crate::numeric::Numeric::Integer(root_page)) =\n                        root_page_value\n                    else {\n                        return Err(LimboError::ConversionError(\"Expected integer value\".into()));\n                    };\n                    let sql_value = row.get_value(4)?;\n                    let sql_textref = match sql_value {\n                        ValueRef::Text(sql) => Some(sql),\n                        _ => None,\n                    };\n                    let sql = sql_textref.map(|s| s.as_str());\n\n                    let acc = state\n                        .accumulators\n                        .as_mut()\n                        .expect(\"accumulators must be initialized in Init phase\");\n                    self.handle_schema_row(\n                        &ty,\n                        &name,\n                        &table_name,\n                        root_page,\n                        sql,\n                        syms,\n                        &mut acc.from_sql_indexes,\n                        &mut acc.automatic_indices,\n                        &mut acc.dbsp_state_roots,\n                        &mut acc.dbsp_state_index_roots,\n                        &mut acc.materialized_view_info,\n                    )?;\n\n                    state.phase = MakeFromBtreePhase::Advancing;\n                }\n\n                MakeFromBtreePhase::Advancing => {\n                    let cursor = state\n                        .cursor\n                        .as_mut()\n                        .expect(\"cursor must be initialized in Init phase\");\n                    return_if_io!(cursor.next());\n                    state.phase = MakeFromBtreePhase::FetchingRecord;\n                }\n\n                MakeFromBtreePhase::Done => {\n                    return Ok(IOResult::Done(()));\n                }\n            }\n        }\n    }\n\n    /// Populate indices parsed from the schema.\n    /// from_sql_indexes: indices explicitly created with CREATE INDEX\n    /// automatic_indices: indices created automatically for primary key and unique constraints\n    pub fn populate_indices(\n        &mut self,\n        syms: &SymbolTable,\n        from_sql_indexes: Vec<UnparsedFromSqlIndex>,\n        automatic_indices: HashMap<String, Vec<(String, i64)>>,\n        mvcc_enabled: bool,\n    ) -> Result<()> {\n        for unparsed_sql_from_index in from_sql_indexes {\n            let table = self\n                .get_btree_table(&unparsed_sql_from_index.table_name)\n                .unwrap();\n            let index = Index::from_sql(\n                syms,\n                &unparsed_sql_from_index.sql,\n                unparsed_sql_from_index.root_page,\n                table.as_ref(),\n            )?;\n            if mvcc_enabled && index.index_method.is_some() {\n                crate::bail_parse_error!(\"Custom index modules are not supported with MVCC\");\n            }\n            self.add_index(Arc::new(index))?;\n        }\n\n        for automatic_index in automatic_indices {\n            // Autoindexes must be parsed in definition order.\n            // The SQL statement parser enforces that the column definitions come first, and compounds are defined after that,\n            // e.g. CREATE TABLE t (a, b, UNIQUE(a, b)), and you can't do something like CREATE TABLE t (a, b, UNIQUE(a, b), c);\n            // Hence, we can process the singles first (unique_set.columns.len() == 1), and then the compounds (unique_set.columns.len() > 1).\n            let table = self.get_btree_table(&automatic_index.0).unwrap();\n            let mut automatic_indexes = automatic_index.1;\n            automatic_indexes.reverse(); // reverse so we can pop() without shifting array elements, while still processing in left-to-right order\n\n            // we must process unique_sets in this exact order in order to emit automatic indices schema entries in the same order\n            let mut pk_index_added = false;\n            for unique_set in &table.unique_sets {\n                if unique_set.is_primary_key {\n                    assert!(table.primary_key_columns.len() == unique_set.columns.len(), \"trying to add a {}-column primary key index for table {}, but the table has {} primary key columns\", unique_set.columns.len(), table.name, table.primary_key_columns.len());\n                    // Add composite primary key index\n                    assert!(\n                        !pk_index_added,\n                        \"trying to add a second primary key index for table {}\",\n                        table.name\n                    );\n                    pk_index_added = true;\n\n                    if unique_set.columns.len() == 1 {\n                        let col_name = &unique_set.columns.first().unwrap().0;\n                        let Some((_, column)) = table.get_column(col_name) else {\n                            return Err(LimboError::ParseError(format!(\n                                \"Column {col_name} not found in table {}\",\n                                table.name\n                            )));\n                        };\n                        if column.is_rowid_alias() {\n                            // rowid alias, no index needed\n                            continue;\n                        }\n                    }\n\n                    if let Some(index_entry) = automatic_indexes.pop() {\n                        self.add_index(Arc::new(Index::automatic_from_primary_key(\n                            table.as_ref(),\n                            index_entry,\n                            unique_set.columns.len(),\n                            unique_set.conflict_clause,\n                        )?))?;\n                    } else if mvcc_enabled {\n                        // In MVCC mode, automatic indices might not be fully populated yet during recovery\n                        // Skip creating this index - it will be added later when its schema row is processed\n                        continue;\n                    } else {\n                        return Err(LimboError::InternalError(format!(\n                            \"Missing automatic index entry for primary key on table {}\",\n                            table.name\n                        )));\n                    }\n                } else {\n                    // Add composite unique index\n                    let mut column_indices_and_sort_orders =\n                        Vec::with_capacity(unique_set.columns.len());\n                    for (col_name, sort_order) in unique_set.columns.iter() {\n                        let Some((pos_in_table, _)) = table.get_column(col_name) else {\n                            return Err(crate::LimboError::ParseError(format!(\n                                \"Column {} not found in table {}\",\n                                col_name, table.name\n                            )));\n                        };\n                        column_indices_and_sort_orders.push((pos_in_table, *sort_order));\n                    }\n                    if let Some(index_entry) = automatic_indexes.pop() {\n                        self.add_index(Arc::new(Index::automatic_from_unique(\n                            table.as_ref(),\n                            index_entry,\n                            column_indices_and_sort_orders,\n                            unique_set.conflict_clause,\n                        )?))?;\n                    } else if mvcc_enabled {\n                        // In MVCC mode, automatic indices might not be fully populated yet during recovery\n                        // Skip creating this index - it will be added later when its schema row is processed\n                        continue;\n                    } else {\n                        return Err(LimboError::InternalError(format!(\n                            \"Missing automatic index entry for UNIQUE constraint on table {}\",\n                            table.name\n                        )));\n                    }\n                }\n            }\n\n            // In MVCC mode during recovery, not all automatic index schema rows might be visible yet\n            // during incremental schema reparsing, so we may have extra entries\n            if !mvcc_enabled {\n                assert!(automatic_indexes.is_empty(), \"all automatic indexes parsed from sqlite_schema should have been consumed, but {} remain\", automatic_indexes.len());\n            }\n        }\n        Ok(())\n    }\n\n    /// Populate materialized views parsed from the schema.\n    pub fn populate_materialized_views(\n        &mut self,\n        materialized_view_info: HashMap<String, (String, i64)>,\n        dbsp_state_roots: HashMap<String, i64>,\n        dbsp_state_index_roots: HashMap<String, i64>,\n    ) -> Result<()> {\n        for (view_name, (sql, main_root)) in materialized_view_info {\n            // Look up the DBSP state root for this view\n            // If missing, it means version mismatch - skip this view\n            // Check if we have a compatible DBSP state root\n            let dbsp_state_root = if let Some(&root) = dbsp_state_roots.get(&view_name) {\n                root\n            } else {\n                tracing::warn!(\n                    \"Materialized view '{}' has incompatible version or missing DBSP state table\",\n                    view_name\n                );\n                // Track this as an incompatible view\n                self.incompatible_views.insert(view_name.clone());\n                // Use a dummy root page - the view won't be usable anyway\n                0\n            };\n\n            // Look up the DBSP state index root (may not exist for older schemas)\n            let dbsp_state_index_root =\n                dbsp_state_index_roots.get(&view_name).copied().unwrap_or(0);\n\n            // Register the DBSP state index so integrity check can account for its pages.\n            if dbsp_state_index_root > 0 && dbsp_state_root > 0 {\n                use crate::incremental::compiler::DBSP_CIRCUIT_VERSION;\n                use crate::incremental::operator::create_dbsp_state_index;\n                let mut index = create_dbsp_state_index(dbsp_state_index_root);\n                let dbsp_table_name =\n                    format!(\"{DBSP_TABLE_PREFIX}{DBSP_CIRCUIT_VERSION}_{view_name}\");\n                index.name = format!(\"sqlite_autoindex_{dbsp_table_name}_1\");\n                index.table_name = dbsp_table_name;\n                if let Err(e) = self.add_index(std::sync::Arc::new(index)) {\n                    if !e.to_string().contains(\"already exists\") {\n                        return Err(e);\n                    }\n                }\n            }\n\n            // Create the IncrementalView with all root pages\n            let incremental_view = IncrementalView::from_sql(\n                &sql,\n                self,\n                main_root,\n                dbsp_state_root,\n                dbsp_state_index_root,\n            )?;\n            let referenced_tables = incremental_view.get_referenced_table_names();\n\n            // Create a BTreeTable for the materialized view\n            let table = Arc::new(Table::BTree(Arc::new(BTreeTable {\n                name: view_name.clone(),\n                root_page: main_root,\n                columns: incremental_view.column_schema.flat_columns(),\n                primary_key_columns: Vec::new(),\n                has_rowid: true,\n                is_strict: false,\n                has_autoincrement: false,\n                foreign_keys: vec![],\n                check_constraints: vec![],\n                rowid_alias_conflict_clause: None,\n                unique_sets: vec![],\n            })));\n\n            // Only add to schema if compatible\n            if !self.incompatible_views.contains(&view_name) {\n                self.add_materialized_view(incremental_view, table, sql);\n            }\n\n            // Register dependencies regardless of compatibility\n            for table_name in referenced_tables {\n                self.add_materialized_view_dependency(&table_name, &view_name);\n            }\n        }\n        Ok(())\n    }\n\n    #[allow(clippy::too_many_arguments)]\n    pub fn handle_schema_row(\n        &mut self,\n        ty: &str,\n        name: &str,\n        table_name: &str,\n        root_page: i64,\n        maybe_sql: Option<&str>,\n        syms: &SymbolTable,\n        from_sql_indexes: &mut Vec<UnparsedFromSqlIndex>,\n        automatic_indices: &mut HashMap<String, Vec<(String, i64)>>,\n        dbsp_state_roots: &mut HashMap<String, i64>,\n        dbsp_state_index_roots: &mut HashMap<String, i64>,\n        materialized_view_info: &mut HashMap<String, (String, i64)>,\n    ) -> Result<()> {\n        match ty {\n            \"table\" => {\n                let sql = maybe_sql.expect(\"sql should be present for table\");\n                let sql_bytes = sql.as_bytes();\n                if root_page == 0 && contains_ignore_ascii_case!(sql_bytes, b\"create virtual\") {\n                    // a virtual table is found in the sqlite_schema, but it's no\n                    // longer in the in-memory schema. We need to recreate it if\n                    // the module is loaded in the symbol table.\n                    let vtab = if let Some(vtab) = syms.vtabs.get(name) {\n                        vtab.clone()\n                    } else {\n                        let mod_name = module_name_from_sql(sql)?;\n                        crate::VirtualTable::table(\n                            Some(name),\n                            mod_name,\n                            module_args_from_sql(sql)?,\n                            syms,\n                        )?\n                    };\n                    self.add_virtual_table(vtab)?;\n                } else {\n                    let table = BTreeTable::from_sql(sql, root_page)?;\n\n                    // Check if this is a DBSP state table\n                    if table.name.starts_with(DBSP_TABLE_PREFIX) {\n                        // Extract version and view name from __turso_internal_dbsp_state_v<version>_<viewname>\n                        let suffix = table.name.strip_prefix(DBSP_TABLE_PREFIX).unwrap();\n\n                        // Parse version and view name (format: \"<version>_<viewname>\")\n                        if let Some(underscore_pos) = suffix.find('_') {\n                            let version_str = &suffix[..underscore_pos];\n                            let view_name = &suffix[underscore_pos + 1..];\n\n                            // Check version compatibility\n                            if let Ok(stored_version) = version_str.parse::<u32>() {\n                                use crate::incremental::compiler::DBSP_CIRCUIT_VERSION;\n                                if stored_version == DBSP_CIRCUIT_VERSION {\n                                    // Version matches, store the root page\n                                    dbsp_state_roots.insert(view_name.to_string(), root_page);\n                                } else {\n                                    // Version mismatch - DO NOT insert into dbsp_state_roots\n                                    // This will cause populate_materialized_views to skip this view\n                                    tracing::warn!(\n                                        \"Skipping materialized view '{}' - has version {} but current version is {}. DROP and recreate the view to use it.\",\n                                        view_name, stored_version, DBSP_CIRCUIT_VERSION\n                                    );\n                                    // We can't track incompatible views here since we're in handle_schema_row\n                                    // which doesn't have mutable access to self\n                                }\n                            }\n                        }\n                    }\n\n                    let mut table = table;\n                    table.resolve_custom_type_affinities(self);\n                    self.add_btree_table(Arc::new(table))?;\n                }\n            }\n            \"index\" => {\n                match maybe_sql {\n                    Some(sql) => {\n                        from_sql_indexes.push(UnparsedFromSqlIndex {\n                            table_name: table_name.to_string(),\n                            root_page,\n                            sql: sql.to_string(),\n                        });\n                    }\n                    None => {\n                        // Automatic index on primary key and/or unique constraint, e.g.\n                        // table|foo|foo|2|CREATE TABLE foo (a text PRIMARY KEY, b)\n                        // index|sqlite_autoindex_foo_1|foo|3|\n                        let index_name = name.to_string();\n                        let table_name = table_name.to_string();\n\n                        // Check if this is an index for a DBSP state table\n                        if table_name.starts_with(DBSP_TABLE_PREFIX) {\n                            // Extract version and view name from __turso_internal_dbsp_state_v<version>_<viewname>\n                            let suffix = table_name.strip_prefix(DBSP_TABLE_PREFIX).unwrap();\n\n                            // Parse version and view name (format: \"<version>_<viewname>\")\n                            if let Some(underscore_pos) = suffix.find('_') {\n                                let version_str = &suffix[..underscore_pos];\n                                let view_name = &suffix[underscore_pos + 1..];\n\n                                // Only store index root if version matches\n                                if let Ok(stored_version) = version_str.parse::<u32>() {\n                                    use crate::incremental::compiler::DBSP_CIRCUIT_VERSION;\n                                    if stored_version == DBSP_CIRCUIT_VERSION {\n                                        dbsp_state_index_roots\n                                            .insert(view_name.to_string(), root_page);\n                                    }\n                                }\n                            }\n                        } else {\n                            match automatic_indices.entry(table_name) {\n                                std::collections::hash_map::Entry::Vacant(e) => {\n                                    e.insert(vec![(index_name, root_page)]);\n                                }\n                                std::collections::hash_map::Entry::Occupied(mut e) => {\n                                    e.get_mut().push((index_name, root_page));\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n            \"view\" => {\n                use crate::schema::View;\n                use turso_parser::ast::{Cmd, Stmt};\n                use turso_parser::parser::Parser;\n\n                let sql = maybe_sql.expect(\"sql should be present for view\");\n                let view_name = name.to_string();\n\n                // Parse the SQL to determine if it's a regular or materialized view\n                let mut parser = Parser::new(sql.as_bytes());\n                if let Ok(Some(Cmd::Stmt(stmt))) = parser.next_cmd() {\n                    match stmt {\n                        Stmt::CreateMaterializedView { .. } => {\n                            // Store materialized view info for later creation\n                            // We'll handle reuse logic and create the actual IncrementalView\n                            // in a later pass when we have both the main root page and DBSP state root\n                            materialized_view_info\n                                .insert(view_name.clone(), (sql.to_string(), root_page));\n\n                            // Mark the existing view for potential reuse\n                            if self.incremental_views.contains_key(&view_name) {\n                                // We'll check for reuse in the third pass\n                            }\n                        }\n                        Stmt::CreateView {\n                            view_name: _,\n                            columns: column_names,\n                            select,\n                            ..\n                        } => {\n                            crate::util::validate_select_for_unsupported_features(&select)?;\n\n                            // Extract actual columns from the SELECT statement\n                            let view_column_schema =\n                                crate::util::extract_view_columns(&select, self)?;\n\n                            // If column names were provided in CREATE VIEW (col1, col2, ...),\n                            // use them to rename the columns\n                            let mut final_columns = view_column_schema.flat_columns();\n                            for (i, indexed_col) in column_names.iter().enumerate() {\n                                if let Some(col) = final_columns.get_mut(i) {\n                                    col.name = Some(indexed_col.col_name.to_string());\n                                }\n                            }\n\n                            // Create regular view\n                            let view =\n                                View::new(name.to_string(), sql.to_string(), select, final_columns);\n                            self.add_view(view)?;\n                        }\n                        _ => {}\n                    }\n                }\n            }\n            \"trigger\" => {\n                use turso_parser::ast::{Cmd, Stmt};\n                use turso_parser::parser::Parser;\n\n                let sql = maybe_sql.expect(\"sql should be present for trigger\");\n                let trigger_name = name.to_string();\n\n                let mut parser = Parser::new(sql.as_bytes());\n                let Ok(Some(Cmd::Stmt(Stmt::CreateTrigger {\n                    temporary,\n                    if_not_exists: _,\n                    trigger_name: _,\n                    time,\n                    event,\n                    tbl_name,\n                    for_each_row,\n                    when_clause,\n                    commands,\n                }))) = parser.next_cmd()\n                else {\n                    return Err(crate::LimboError::ParseError(format!(\n                        \"invalid trigger sql: {sql}\"\n                    )));\n                };\n                self.add_trigger(\n                    Trigger::new(\n                        trigger_name,\n                        sql.to_string(),\n                        tbl_name.name.to_string(),\n                        time,\n                        event,\n                        for_each_row,\n                        when_clause.map(|e| *e),\n                        commands,\n                        temporary,\n                    ),\n                    tbl_name.name.as_str(),\n                )?;\n            }\n            // Types are stored in sqlite_turso_types, not sqlite_schema\n            _ => {}\n        };\n\n        Ok(())\n    }\n\n    /// Compute all resolved FKs *referencing* `table_name` (arg: `table_name` is the parent).\n    /// Each item contains the child table, normalized columns/positions, and the parent lookup\n    /// strategy (rowid vs. UNIQUE index or PK).\n    pub fn resolved_fks_referencing(&self, table_name: &str) -> Result<Vec<ResolvedFkRef>> {\n        let fk_mismatch_err = |child: &str, parent: &str| -> crate::LimboError {\n            crate::LimboError::ForeignKeyConstraint(format!(\n                \"foreign key mismatch - \\\"{child}\\\" referencing \\\"{parent}\\\"\"\n            ))\n        };\n        let target = normalize_ident(table_name);\n        let mut out = Vec::with_capacity(4); // arbitrary estimate\n        let parent_tbl = self\n            .get_btree_table(&target)\n            .ok_or_else(|| fk_mismatch_err(\"<unknown>\", &target))?;\n\n        // Precompute helper to find parent unique index, if it's not the rowid\n        let find_parent_unique = |cols: &Vec<String>| -> Option<Arc<Index>> {\n            self.get_indices(&parent_tbl.name)\n                .find(|idx| {\n                    idx.unique\n                        && idx.columns.len() == cols.len()\n                        && idx\n                            .columns\n                            .iter()\n                            .zip(cols.iter())\n                            .all(|(ic, pc)| ic.name.eq_ignore_ascii_case(pc))\n                })\n                .cloned()\n        };\n\n        for t in self.tables.values() {\n            let Some(child) = t.btree() else {\n                continue;\n            };\n            for fk in &child.foreign_keys {\n                if !fk.parent_table.eq_ignore_ascii_case(&target) {\n                    continue;\n                }\n                if fk.child_columns.is_empty() {\n                    // SQLite requires an explicit child column list unless the table has a single-column PK that\n                    return Err(fk_mismatch_err(&child.name, &parent_tbl.name));\n                }\n                let child_cols: Vec<String> = fk.child_columns.clone();\n                let mut child_pos = Vec::with_capacity(child_cols.len());\n\n                for cname in &child_cols {\n                    let (i, _) = child\n                        .get_column(cname)\n                        .ok_or_else(|| fk_mismatch_err(&child.name, &parent_tbl.name))?;\n                    child_pos.push(i);\n                }\n                let parent_cols: Vec<String> = if fk.parent_columns.is_empty() {\n                    if !parent_tbl.primary_key_columns.is_empty() {\n                        parent_tbl\n                            .primary_key_columns\n                            .iter()\n                            .map(|(col, _)| col)\n                            .cloned()\n                            .collect()\n                    } else {\n                        return Err(fk_mismatch_err(&child.name, &parent_tbl.name));\n                    }\n                } else {\n                    fk.parent_columns.clone()\n                };\n\n                // Same length required\n                if parent_cols.len() != child_cols.len() {\n                    return Err(fk_mismatch_err(&child.name, &parent_tbl.name));\n                }\n\n                let mut parent_pos = Vec::with_capacity(parent_cols.len());\n                for pc in &parent_cols {\n                    let pos = parent_tbl.get_column(pc).map(|(i, _)| i).or_else(|| {\n                        ROWID_STRS\n                            .iter()\n                            .any(|s| pc.eq_ignore_ascii_case(s))\n                            .then_some(0)\n                    });\n                    let Some(p) = pos else {\n                        return Err(fk_mismatch_err(&child.name, &parent_tbl.name));\n                    };\n                    parent_pos.push(p);\n                }\n\n                // Determine if the FK's parent key is the ROWID or a rowid alias.\n                let parent_uses_rowid = if parent_cols.len() == 1 {\n                    let pc = &parent_cols[0];\n                    ROWID_STRS.iter().any(|&r| r.eq_ignore_ascii_case(pc))\n                        || parent_tbl.columns.iter().any(|c| {\n                            c.is_rowid_alias()\n                                && c.name\n                                    .as_deref()\n                                    .is_some_and(|n| n.eq_ignore_ascii_case(pc))\n                        })\n                } else {\n                    false\n                };\n\n                // If not rowid, there must be a non-partial UNIQUE exactly on parent_cols\n                let parent_unique_index = if parent_uses_rowid {\n                    None\n                } else {\n                    find_parent_unique(&parent_cols)\n                };\n                fk.validate()?;\n                out.push(ResolvedFkRef {\n                    child_table: Arc::clone(&child),\n                    fk: Arc::clone(fk),\n                    child_cols,\n                    child_pos,\n                    parent_pos,\n                    parent_uses_rowid,\n                    parent_unique_index,\n                });\n            }\n        }\n        Ok(out)\n    }\n\n    /// Compute all resolved FKs *declared by* `child_table`\n    pub fn resolved_fks_for_child(&self, child_table: &str) -> crate::Result<Vec<ResolvedFkRef>> {\n        let fk_mismatch_err = |child: &str, parent: &str| -> crate::LimboError {\n            crate::LimboError::ForeignKeyConstraint(format!(\n                \"foreign key mismatch - \\\"{child}\\\" referencing \\\"{parent}\\\"\"\n            ))\n        };\n        let child_name = normalize_ident(child_table);\n        let child = self\n            .get_btree_table(&child_name)\n            .ok_or_else(|| fk_mismatch_err(&child_name, \"<unknown>\"))?;\n\n        let mut out = Vec::with_capacity(child.foreign_keys.len());\n\n        for fk in &child.foreign_keys {\n            let parent_name = normalize_ident(&fk.parent_table);\n            let parent_tbl = self\n                .get_btree_table(&parent_name)\n                .ok_or_else(|| fk_mismatch_err(&child.name, &parent_name))?;\n\n            let child_cols: Vec<String> = fk.child_columns.clone();\n            if child_cols.is_empty() {\n                return Err(fk_mismatch_err(&child.name, &parent_tbl.name));\n            }\n\n            // Child positions exist\n            let mut child_pos = Vec::with_capacity(child_cols.len());\n            for cname in &child_cols {\n                let (i, _) = child\n                    .get_column(cname)\n                    .ok_or_else(|| fk_mismatch_err(&child.name, &parent_tbl.name))?;\n                child_pos.push(i);\n            }\n\n            let parent_cols: Vec<String> = if fk.parent_columns.is_empty() {\n                if !parent_tbl.primary_key_columns.is_empty() {\n                    parent_tbl\n                        .primary_key_columns\n                        .iter()\n                        .map(|(col, _)| col)\n                        .cloned()\n                        .collect()\n                } else {\n                    return Err(fk_mismatch_err(&child.name, &parent_tbl.name));\n                }\n            } else {\n                fk.parent_columns.clone()\n            };\n\n            if parent_cols.len() != child_cols.len() {\n                return Err(fk_mismatch_err(&child.name, &parent_tbl.name));\n            }\n\n            // Parent positions exist, or rowid sentinel\n            let mut parent_pos = Vec::with_capacity(parent_cols.len());\n            for pc in &parent_cols {\n                let pos = parent_tbl.get_column(pc).map(|(i, _)| i).or_else(|| {\n                    ROWID_STRS\n                        .iter()\n                        .any(|&r| r.eq_ignore_ascii_case(pc))\n                        .then_some(0)\n                });\n                let Some(p) = pos else {\n                    return Err(fk_mismatch_err(&child.name, &parent_tbl.name));\n                };\n                parent_pos.push(p);\n            }\n\n            let parent_uses_rowid = parent_cols.len().eq(&1) && {\n                let c = parent_cols[0].as_str();\n                ROWID_STRS.iter().any(|&r| r.eq_ignore_ascii_case(c))\n                    || parent_tbl.columns.iter().any(|col| {\n                        col.is_rowid_alias()\n                            && col\n                                .name\n                                .as_deref()\n                                .is_some_and(|n| n.eq_ignore_ascii_case(c))\n                    })\n            };\n\n            // Must be PK or a non-partial UNIQUE on exactly those columns.\n            let parent_unique_index = if parent_uses_rowid {\n                None\n            } else {\n                self.get_indices(&parent_tbl.name)\n                    .find(|idx| {\n                        idx.unique\n                            && idx.where_clause.is_none()\n                            && idx.columns.len() == parent_cols.len()\n                            && idx\n                                .columns\n                                .iter()\n                                .zip(parent_cols.iter())\n                                .all(|(ic, pc)| ic.name.eq_ignore_ascii_case(pc))\n                    })\n                    .cloned()\n                    .ok_or_else(|| fk_mismatch_err(&child.name, &parent_tbl.name))?\n                    .into()\n            };\n\n            fk.validate()?;\n            out.push(ResolvedFkRef {\n                child_table: Arc::clone(&child),\n                fk: Arc::clone(fk),\n                child_cols,\n                child_pos,\n                parent_pos,\n                parent_uses_rowid,\n                parent_unique_index,\n            });\n        }\n\n        Ok(out)\n    }\n\n    /// Returns if any table declares a FOREIGN KEY whose parent is `table_name`.\n    pub fn any_resolved_fks_referencing(&self, table_name: &str) -> bool {\n        self.tables.values().any(|t| {\n            let Some(bt) = t.btree() else {\n                return false;\n            };\n            bt.foreign_keys\n                .iter()\n                .any(|fk| fk.parent_table == table_name)\n        })\n    }\n\n    /// Returns true if `table_name` declares any FOREIGN KEYs\n    pub fn has_child_fks(&self, table_name: &str) -> bool {\n        self.get_table(table_name)\n            .and_then(|t| t.btree())\n            .is_some_and(|t| !t.foreign_keys.is_empty())\n    }\n\n    fn check_object_name_conflict(&self, name: &str) -> Result<()> {\n        if let Some(object_type) = self.get_object_type(name) {\n            let type_str = match object_type {\n                SchemaObjectType::Table => \"table\",\n                SchemaObjectType::View => \"view\",\n                SchemaObjectType::Index => \"index\",\n            };\n            return Err(crate::LimboError::ParseError(format!(\n                \"{type_str} \\\"{name}\\\" already exists\"\n            )));\n        }\n        Ok(())\n    }\n\n    /// Returns the type of schema object with the given name, if one exists.\n    /// Checks tables, views, and indexes.\n    pub fn get_object_type(&self, name: &str) -> Option<SchemaObjectType> {\n        let normalized_name = normalize_ident(name);\n\n        if self.tables.contains_key(&normalized_name) {\n            return Some(SchemaObjectType::Table);\n        }\n\n        if self.views.contains_key(&normalized_name) {\n            return Some(SchemaObjectType::View);\n        }\n\n        for index_list in self.indexes.values() {\n            if index_list.iter().any(|i| i.name.eq_ignore_ascii_case(name)) {\n                return Some(SchemaObjectType::Index);\n            }\n        }\n\n        None\n    }\n}\n\nimpl Clone for Schema {\n    /// Cloning a `Schema` requires deep cloning of all internal tables and indexes, even though they are wrapped in `Arc`.\n    /// Simply copying the `Arc` pointers would result in multiple `Schema` instances sharing the same underlying tables and indexes,\n    /// which could lead to panics or data races if any instance attempts to modify them.\n    /// To ensure each `Schema` is independent and safe to modify, we clone the underlying data for all tables and indexes.\n    fn clone(&self) -> Self {\n        let tables = self\n            .tables\n            .iter()\n            .map(|(name, table)| match table.deref() {\n                Table::BTree(table) => {\n                    let table = Arc::deref(table);\n                    (\n                        name.clone(),\n                        Arc::new(Table::BTree(Arc::new(table.clone()))),\n                    )\n                }\n                Table::Virtual(table) => {\n                    let table = Arc::deref(table);\n                    (\n                        name.clone(),\n                        Arc::new(Table::Virtual(Arc::new(table.clone()))),\n                    )\n                }\n                Table::FromClauseSubquery(from_clause_subquery) => (\n                    name.clone(),\n                    Arc::new(Table::FromClauseSubquery(Arc::new(\n                        (**from_clause_subquery).clone(),\n                    ))),\n                ),\n            })\n            .collect();\n        let indexes = self\n            .indexes\n            .iter()\n            .map(|(name, indexes)| {\n                let indexes = indexes\n                    .iter()\n                    .map(|index| Arc::new((**index).clone()))\n                    .collect();\n                (name.clone(), indexes)\n            })\n            .collect();\n        let materialized_view_names = self.materialized_view_names.clone();\n        let materialized_view_sql = self.materialized_view_sql.clone();\n        let incremental_views = self\n            .incremental_views\n            .iter()\n            .map(|(name, view)| (name.clone(), view.clone()))\n            .collect();\n        let views = self\n            .views\n            .iter()\n            .map(|(name, view)| (name.clone(), Arc::new((**view).clone())))\n            .collect();\n        let triggers = self\n            .triggers\n            .iter()\n            .map(|(table_name, triggers)| {\n                (\n                    table_name.clone(),\n                    triggers.iter().map(|t| Arc::new((**t).clone())).collect(),\n                )\n            })\n            .collect();\n        let incompatible_views = self.incompatible_views.clone();\n        Self {\n            tables,\n            materialized_view_names,\n            materialized_view_sql,\n            incremental_views,\n            views,\n            triggers,\n            indexes,\n            has_indexes: self.has_indexes.clone(),\n            schema_version: self.schema_version,\n            analyze_stats: self.analyze_stats.clone(),\n            table_to_materialized_views: self.table_to_materialized_views.clone(),\n            incompatible_views,\n            dropped_root_pages: self.dropped_root_pages.clone(),\n            type_registry: self.type_registry.clone(),\n        }\n    }\n}\n\n#[derive(Clone, Debug)]\npub enum Table {\n    BTree(Arc<BTreeTable>),\n    Virtual(Arc<VirtualTable>),\n    FromClauseSubquery(Arc<FromClauseSubquery>),\n}\n\nimpl Table {\n    pub fn get_root_page(&self) -> crate::Result<i64> {\n        match self {\n            Table::BTree(table) => Ok(table.root_page),\n            Table::Virtual(_) => Err(crate::LimboError::InternalError(\n                \"Virtual tables do not have a root page\".to_string(),\n            )),\n            Table::FromClauseSubquery(_) => Err(crate::LimboError::InternalError(\n                \"FROM clause subqueries do not have a root page\".to_string(),\n            )),\n        }\n    }\n\n    pub fn get_name(&self) -> &str {\n        match self {\n            Self::BTree(table) => &table.name,\n            Self::Virtual(table) => &table.name,\n            Self::FromClauseSubquery(from_clause_subquery) => &from_clause_subquery.name,\n        }\n    }\n\n    pub fn get_column_at(&self, index: usize) -> Option<&Column> {\n        match self {\n            Self::BTree(table) => table.columns.get(index),\n            Self::Virtual(table) => table.columns.get(index),\n            Self::FromClauseSubquery(from_clause_subquery) => {\n                from_clause_subquery.columns.get(index)\n            }\n        }\n    }\n\n    /// Returns the column position and column for a given column name.\n    pub fn get_column_by_name(&self, name: &str) -> Option<(usize, &Column)> {\n        match self {\n            Self::BTree(table) => table.get_column(name),\n            Self::Virtual(table) => table.columns.iter().enumerate().find(|(_, col)| {\n                col.name\n                    .as_ref()\n                    .is_some_and(|n| n.eq_ignore_ascii_case(name))\n            }),\n            Self::FromClauseSubquery(from_clause_subquery) => from_clause_subquery\n                .columns\n                .iter()\n                .enumerate()\n                .find(|(_, col)| {\n                    col.name\n                        .as_ref()\n                        .is_some_and(|n| n.eq_ignore_ascii_case(name))\n                }),\n        }\n    }\n\n    pub fn columns(&self) -> &Vec<Column> {\n        match self {\n            Self::BTree(table) => &table.columns,\n            Self::Virtual(table) => &table.columns,\n            Self::FromClauseSubquery(from_clause_subquery) => &from_clause_subquery.columns,\n        }\n    }\n\n    pub fn is_strict(&self) -> bool {\n        match self {\n            Self::BTree(table) => table.is_strict,\n            Self::Virtual(_) => false,\n            Self::FromClauseSubquery(_) => false,\n        }\n    }\n\n    pub fn btree(&self) -> Option<Arc<BTreeTable>> {\n        match self {\n            Self::BTree(table) => Some(table.clone()),\n            Self::Virtual(_) => None,\n            Self::FromClauseSubquery(_) => None,\n        }\n    }\n\n    pub fn btree_mut(&mut self) -> Option<&mut Arc<BTreeTable>> {\n        match self {\n            Self::BTree(table) => Some(table),\n            Self::Virtual(_) => None,\n            Self::FromClauseSubquery(_) => None,\n        }\n    }\n\n    pub fn virtual_table(&self) -> Option<Arc<VirtualTable>> {\n        match self {\n            Self::Virtual(table) => Some(table.clone()),\n            _ => None,\n        }\n    }\n}\n\nimpl PartialEq for Table {\n    fn eq(&self, other: &Self) -> bool {\n        match (self, other) {\n            (Self::BTree(a), Self::BTree(b)) => Arc::ptr_eq(a, b),\n            (Self::Virtual(a), Self::Virtual(b)) => Arc::ptr_eq(a, b),\n            _ => false,\n        }\n    }\n}\n\n#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]\npub struct UniqueSet {\n    pub columns: Vec<(String, SortOrder)>,\n    pub is_primary_key: bool,\n    pub conflict_clause: Option<ResolveType>,\n}\n\n#[derive(Clone, Debug)]\npub struct CheckConstraint {\n    /// Optional constraint name\n    pub name: Option<String>,\n    /// CHECK expression\n    pub expr: ast::Expr,\n    /// Column name if this is a column-level CHECK constraint (defined inline with the column).\n    /// None if this is a table-level CHECK constraint.\n    pub column: Option<String>,\n}\n\nimpl CheckConstraint {\n    pub fn new(name: Option<&ast::Name>, expr: &ast::Expr, column: Option<&str>) -> Self {\n        Self {\n            name: name.map(|n| n.as_str().to_string()),\n            expr: expr.clone(),\n            column: column.map(|s| s.to_string()),\n        }\n    }\n\n    /// Returns the SQL representation of this CHECK constraint (e.g. `CHECK(x > 0)`).\n    pub fn sql(&self) -> String {\n        format!(\"CHECK({})\", self.expr)\n    }\n}\n\n#[derive(Clone, Debug)]\npub struct BTreeTable {\n    pub root_page: i64,\n    pub name: String,\n    pub primary_key_columns: Vec<(String, SortOrder)>,\n    pub columns: Vec<Column>,\n    pub has_rowid: bool,\n    pub is_strict: bool,\n    pub has_autoincrement: bool,\n    pub unique_sets: Vec<UniqueSet>,\n    pub foreign_keys: Vec<Arc<ForeignKey>>,\n    pub check_constraints: Vec<CheckConstraint>,\n    /// ON CONFLICT clause for the INTEGER PRIMARY KEY constraint.\n    /// Stored here because rowid-alias PKs have their UniqueSet removed.\n    pub rowid_alias_conflict_clause: Option<ResolveType>,\n}\n\nimpl BTreeTable {\n    /// Create a table reference for TypeCheck where custom type columns have\n    /// their `ty_str` replaced with the base type name. This ensures TypeCheck\n    /// validates the encoded value against the correct base type (e.g., BLOB)\n    /// rather than accepting any STRICT type via the wildcard arm.\n    pub fn type_check_table_ref(table: &Arc<BTreeTable>, schema: &Schema) -> Arc<BTreeTable> {\n        let has_custom = table\n            .columns\n            .iter()\n            .any(|c| c.is_array() || schema.get_type_def(&c.ty_str, table.is_strict).is_some());\n        if !has_custom {\n            return Arc::clone(table);\n        }\n        let mut modified = (**table).clone();\n        for col in &mut modified.columns {\n            if col.is_array() {\n                // Arrays are stored as record-format blobs.\n                col.ty_str = \"BLOB\".to_string();\n            } else if let Some(type_def) = schema.get_type_def(&col.ty_str, table.is_strict) {\n                col.ty_str = type_def.base.to_uppercase();\n            }\n        }\n        Arc::new(modified)\n    }\n\n    /// Create a table ref for pre-encode TypeCheck that validates user input\n    /// against the type's declared `value` input type (or base if not declared).\n    /// For UPDATE, `only_columns` limits which columns are checked — non-SET\n    /// columns hold encoded values and must be skipped (set to ANY).\n    pub fn input_type_check_table_ref(\n        table: &Arc<BTreeTable>,\n        schema: &Schema,\n        only_columns: Option<&std::collections::HashSet<usize>>,\n    ) -> Arc<BTreeTable> {\n        let has_custom = table\n            .columns\n            .iter()\n            .any(|c| c.is_array() || schema.get_type_def(&c.ty_str, table.is_strict).is_some());\n        if !has_custom {\n            return Arc::clone(table);\n        }\n        let mut modified = (**table).clone();\n        for (i, col) in modified.columns.iter_mut().enumerate() {\n            if let Some(only) = only_columns {\n                if !only.contains(&i) {\n                    // Non-SET column in UPDATE: holds encoded value, skip check\n                    col.ty_str = \"ANY\".to_string();\n                    continue;\n                }\n            }\n            if col.is_array() {\n                // Pre-encode: user input can be text ('[1,2]') or blob (ARRAY[]),\n                // so accept ANY here; the encoder handles conversion.\n                col.ty_str = \"ANY\".to_string();\n            } else if let Some(type_def) = schema.get_type_def(&col.ty_str, table.is_strict) {\n                col.ty_str = type_def.value_input_type().to_uppercase();\n            }\n        }\n        Arc::new(modified)\n    }\n\n    /// Override column type metadata for custom type columns so that\n    /// SQLite's name-based type/affinity rules use the BASE type\n    /// instead of the custom type name (e.g. \"doubled\" contains \"DOUB\"\n    /// which would incorrectly map to REAL instead of INTEGER).\n    pub fn resolve_custom_type_affinities(&mut self, schema: &Schema) {\n        if !self.is_strict {\n            return;\n        }\n        for col in &mut self.columns {\n            if col.is_array() {\n                // Arrays are stored as record-format blobs regardless of element type.\n                col.set_ty(Type::Blob);\n                col.set_base_affinity(Affinity::Blob);\n                continue;\n            }\n            if let Some(type_def) = schema.get_type_def_unchecked(&col.ty_str) {\n                let (base_ty, _) = type_from_name(&type_def.base);\n                col.set_ty(base_ty);\n                col.set_base_affinity(Affinity::affinity(&type_def.base));\n            }\n        }\n    }\n\n    pub fn get_rowid_alias_column(&self) -> Option<(usize, &Column)> {\n        self.columns\n            .iter()\n            .enumerate()\n            .find(|(_, column)| column.is_rowid_alias())\n    }\n\n    /// Returns the column position and column for a given column name.\n    /// Returns None if the column name is not found.\n    /// E.g. if table is CREATE TABLE t (a, b, c)\n    /// then get_column(\"b\") returns (1, &Column { .. })\n    pub fn get_column(&self, name: &str) -> Option<(usize, &Column)> {\n        self.columns.iter().enumerate().find(|(_, column)| {\n            column\n                .name\n                .as_ref()\n                .is_some_and(|n| n.eq_ignore_ascii_case(name))\n        })\n    }\n\n    pub fn from_sql(sql: &str, root_page: i64) -> Result<BTreeTable> {\n        let mut parser = Parser::new(sql.as_bytes());\n        let cmd = parser.next_cmd()?;\n        match cmd {\n            Some(Cmd::Stmt(Stmt::CreateTable { tbl_name, body, .. })) => {\n                create_table(tbl_name.name.as_str(), &body, root_page)\n            }\n            _ => unreachable!(\"Expected CREATE TABLE statement\"),\n        }\n    }\n\n    /// Reconstruct the SQL for the table.\n    /// FIXME: this makes us incompatible with SQLite since sqlite stores the user-provided SQL as is in\n    /// `sqlite_schema.sql`\n    /// For example, if a user creates a table like: `CREATE TABLE t              (x)`, we store it as\n    /// `CREATE TABLE t (x)`, whereas sqlite stores it with the original extra whitespace.\n    pub fn to_sql(&self) -> String {\n        let mut sql = format!(\"CREATE TABLE {} (\", quote_ident(&self.name));\n        let needs_pk_inline = self.primary_key_columns.len() == 1;\n        // Add columns\n        for (i, column) in self.columns.iter().enumerate() {\n            if i > 0 {\n                sql.push_str(\", \");\n            }\n\n            // we need to wrap the column name in square brackets if it contains special characters\n            let column_name = column.name.as_ref().expect(\"column name is None\");\n            if identifier_contains_special_chars(column_name) {\n                sql.push('[');\n                sql.push_str(column_name);\n                sql.push(']');\n            } else {\n                sql.push_str(column_name);\n            }\n\n            if !column.ty_str.is_empty() {\n                sql.push(' ');\n                sql.push_str(&column.ty_str);\n                if column.is_array() {\n                    sql.push_str(\"[]\");\n                }\n            }\n            if column.notnull() {\n                sql.push_str(\" NOT NULL\");\n            }\n\n            if column.unique() {\n                sql.push_str(\" UNIQUE\");\n            }\n            if needs_pk_inline && column.primary_key() {\n                sql.push_str(\" PRIMARY KEY\");\n            }\n\n            if let Some(default) = &column.default {\n                sql.push_str(\" DEFAULT \");\n                sql.push_str(&default.to_string());\n            }\n\n            if let Some(generated) = &column.generated {\n                sql.push_str(\" AS (\");\n                sql.push_str(&generated.to_string());\n                sql.push(')');\n            }\n\n            // Add column-level CHECK constraints inline\n            for check_constraint in &self.check_constraints {\n                if check_constraint.column.as_deref() == Some(column_name) {\n                    sql.push(' ');\n                    if let Some(name) = &check_constraint.name {\n                        sql.push_str(\"CONSTRAINT \");\n                        sql.push_str(&Name::exact(name.clone()).as_ident());\n                        sql.push(' ');\n                    }\n                    sql.push_str(&check_constraint.sql());\n                }\n            }\n        }\n\n        let has_table_pk = !self.primary_key_columns.is_empty();\n        // Add table-level PRIMARY KEY constraint if exists\n        if !needs_pk_inline && has_table_pk {\n            sql.push_str(\", PRIMARY KEY (\");\n            for (i, col) in self.primary_key_columns.iter().enumerate() {\n                if i > 0 {\n                    sql.push_str(\", \");\n                }\n                sql.push_str(&col.0);\n            }\n            sql.push(')');\n        }\n\n        for fk in &self.foreign_keys {\n            sql.push_str(\", FOREIGN KEY (\");\n            for (i, col) in fk.child_columns.iter().enumerate() {\n                if i > 0 {\n                    sql.push_str(\", \");\n                }\n                sql.push_str(col);\n            }\n            sql.push_str(\") REFERENCES \");\n            sql.push_str(&fk.parent_table);\n            sql.push('(');\n            for (i, col) in fk.parent_columns.iter().enumerate() {\n                if i > 0 {\n                    sql.push_str(\", \");\n                }\n                sql.push_str(col);\n            }\n            sql.push(')');\n\n            // Add ON DELETE/UPDATE actions, NoAction is default so just make empty in that case\n            if fk.on_delete != RefAct::NoAction {\n                sql.push_str(\" ON DELETE \");\n                sql.push_str(match fk.on_delete {\n                    RefAct::SetNull => \"SET NULL\",\n                    RefAct::SetDefault => \"SET DEFAULT\",\n                    RefAct::Cascade => \"CASCADE\",\n                    RefAct::Restrict => \"RESTRICT\",\n                    _ => \"\",\n                });\n            }\n            if fk.on_update != RefAct::NoAction {\n                sql.push_str(\" ON UPDATE \");\n                sql.push_str(match fk.on_update {\n                    RefAct::SetNull => \"SET NULL\",\n                    RefAct::SetDefault => \"SET DEFAULT\",\n                    RefAct::Cascade => \"CASCADE\",\n                    RefAct::Restrict => \"RESTRICT\",\n                    _ => \"\",\n                });\n            }\n            if fk.deferred {\n                sql.push_str(\" DEFERRABLE INITIALLY DEFERRED\");\n            }\n        }\n\n        // Add table-level CHECK constraints (column-level ones were emitted inline above)\n        for check_constraint in &self.check_constraints {\n            if check_constraint.column.is_some() {\n                continue;\n            }\n            sql.push_str(\", \");\n            if let Some(name) = &check_constraint.name {\n                sql.push_str(\"CONSTRAINT \");\n                sql.push_str(&Name::exact(name.clone()).as_ident());\n                sql.push(' ');\n            }\n            sql.push_str(&check_constraint.sql());\n        }\n\n        // Add table-level UNIQUE constraints\n        for unique_set in &self.unique_sets {\n            // Skip primary key (handled above)\n            if unique_set.is_primary_key {\n                continue;\n            }\n            // Skip single-column unique constraints that were already emitted inline\n            if unique_set.columns.len() == 1 {\n                let col_name = &unique_set.columns[0].0;\n                if let Some((_, col)) = self.get_column(col_name) {\n                    if col.unique() {\n                        continue;\n                    }\n                }\n            }\n            sql.push_str(\", UNIQUE (\");\n            for (i, (col_name, _)) in unique_set.columns.iter().enumerate() {\n                if i > 0 {\n                    sql.push_str(\", \");\n                }\n                if identifier_contains_special_chars(col_name) {\n                    sql.push('[');\n                    sql.push_str(col_name);\n                    sql.push(']');\n                } else {\n                    sql.push_str(col_name);\n                }\n            }\n            sql.push(')');\n        }\n\n        sql.push(')');\n\n        // Add STRICT keyword if this is a STRICT table\n        if self.is_strict {\n            sql.push_str(\" STRICT\");\n        }\n\n        sql\n    }\n\n    pub fn column_collations(&self) -> Vec<CollationSeq> {\n        self.columns\n            .iter()\n            .map(|column| column.collation())\n            .collect()\n    }\n}\n\nfn identifier_contains_special_chars(name: &str) -> bool {\n    name.chars().any(|c| !c.is_ascii_alphanumeric() && c != '_')\n}\n\n#[derive(Debug, Default, Clone, Copy)]\npub struct PseudoCursorType {\n    pub column_count: usize,\n}\n\nimpl PseudoCursorType {\n    pub fn new() -> Self {\n        Self { column_count: 0 }\n    }\n\n    pub fn new_with_columns(columns: impl AsRef<[Column]>) -> Self {\n        Self {\n            column_count: columns.as_ref().len(),\n        }\n    }\n}\n\n/// A derived table from a FROM clause subquery.\n#[derive(Debug, Clone)]\npub struct FromClauseSubquery {\n    /// The name of the derived table; uses the alias if available.\n    pub name: String,\n    /// The query plan for the derived table. Can be either a simple SelectPlan\n    /// or a compound select (UNION/INTERSECT/EXCEPT).\n    pub plan: Box<Plan>,\n    /// The columns of the derived table.\n    pub columns: Vec<Column>,\n    /// The start register for the result columns of the derived table;\n    /// must be set before data is read from it.\n    pub result_columns_start_reg: Option<usize>,\n    /// The table cursor backing a materialized EphemeralTable representation of\n    /// this subquery, if one was emitted.\n    pub materialized_cursor_id: Option<CursorID>,\n    /// CTE-specific materialization metadata, when this FROM-subquery is a CTE\n    /// reference rather than an inline derived table.\n    pub cte: Option<FromClauseSubqueryCteMetadata>,\n}\n\n#[derive(Debug, Clone, Copy)]\npub struct FromClauseSubqueryCteMetadata {\n    /// Identity shared by all references to the same CTE definition.\n    pub id: usize,\n    /// True when this CTE is referenced more than once inside the enclosing\n    /// query tree and therefore must be materialized once and shared.\n    pub shared_materialization: bool,\n    /// True for explicit WITH ... AS MATERIALIZED.\n    pub materialize_hint: bool,\n}\n\nimpl FromClauseSubquery {\n    pub fn cte_id(&self) -> Option<usize> {\n        self.cte.map(|cte| cte.id)\n    }\n\n    pub fn materialize_hint(&self) -> bool {\n        self.cte.is_some_and(|cte| cte.materialize_hint)\n    }\n\n    pub fn shared_materialization(&self) -> bool {\n        self.cte.is_some_and(|cte| cte.shared_materialization)\n    }\n\n    pub fn set_shared_materialization(&mut self, shared: bool) {\n        if let Some(cte) = &mut self.cte {\n            cte.shared_materialization = shared;\n        }\n    }\n\n    /// Shared CTE references and explicit MATERIALIZED hints both force a\n    /// table-backed materialization that can be scanned or probed later.\n    pub fn requires_table_materialization(&self) -> bool {\n        self.shared_materialization() || self.materialize_hint()\n    }\n\n    /// Only simple single-reference SELECT subqueries can safely use their\n    /// synthesized seek index as the storage target directly. Compound\n    /// subqueries still need table-backed storage so their set-operation\n    /// semantics are preserved before any later SEARCH shape is chosen.\n    pub fn supports_direct_index_materialization(&self) -> bool {\n        matches!(self.plan.as_ref(), Plan::Select(_)) && !self.requires_table_materialization()\n    }\n}\n\npub fn create_table(tbl_name: &str, body: &CreateTableBody, root_page: i64) -> Result<BTreeTable> {\n    let table_name = normalize_ident(tbl_name);\n    trace!(\"Creating table {}\", table_name);\n    let mut has_rowid = true;\n    let mut has_autoincrement = false;\n    let mut primary_key_columns = vec![];\n    let mut foreign_keys = vec![];\n    let mut check_constraints = vec![];\n    let mut cols = vec![];\n    let is_strict: bool;\n    let mut unique_sets_columns: Vec<UniqueSet> = vec![];\n    let mut unique_sets_constraints: Vec<UniqueSet> = vec![];\n    match body {\n        CreateTableBody::ColumnsAndConstraints {\n            columns,\n            constraints,\n            options,\n        } => {\n            is_strict = options.contains_strict();\n\n            // we need to preserve order of unique sets definition\n            // but also, we analyze constraints first in order to check PRIMARY KEY constraint and recognize rowid alias properly\n            // that's why we maintain 2 unique_set sequences and merge them together in the end\n\n            for c in constraints {\n                if let ast::TableConstraint::PrimaryKey {\n                    columns,\n                    auto_increment,\n                    conflict_clause,\n                } = &c.constraint\n                {\n                    if !primary_key_columns.is_empty() {\n                        crate::bail_parse_error!(\n                            \"table \\\"{}\\\" has more than one primary key\",\n                            tbl_name\n                        );\n                    }\n                    if *auto_increment {\n                        has_autoincrement = true;\n                    }\n\n                    for column in columns {\n                        let col_name = match column.expr.as_ref() {\n                            Expr::Id(id) => normalize_ident(id.as_str()),\n                            Expr::Literal(Literal::String(value)) => {\n                                value.trim_matches('\\'').to_owned()\n                            }\n                            expr => {\n                                bail_parse_error!(\"unsupported primary key expression: {}\", expr)\n                            }\n                        };\n                        primary_key_columns\n                            .push((col_name, column.order.unwrap_or(SortOrder::Asc)));\n                    }\n                    unique_sets_constraints.push(UniqueSet {\n                        columns: primary_key_columns.clone(),\n                        is_primary_key: true,\n                        conflict_clause: *conflict_clause,\n                    });\n                } else if let ast::TableConstraint::Unique {\n                    columns,\n                    conflict_clause,\n                } = &c.constraint\n                {\n                    let mut unique_columns = Vec::with_capacity(columns.len());\n                    for column in columns {\n                        match column.expr.as_ref() {\n                            Expr::Id(id) => unique_columns.push((\n                                id.as_str().to_string(),\n                                column.order.unwrap_or(SortOrder::Asc),\n                            )),\n                            Expr::Literal(Literal::String(value)) => unique_columns.push((\n                                value.trim_matches('\\'').to_owned(),\n                                column.order.unwrap_or(SortOrder::Asc),\n                            )),\n                            expr => {\n                                bail_parse_error!(\"unsupported unique key expression: {}\", expr)\n                            }\n                        }\n                    }\n                    let unique_set = UniqueSet {\n                        columns: unique_columns,\n                        is_primary_key: false,\n                        conflict_clause: *conflict_clause,\n                    };\n                    unique_sets_constraints.push(unique_set);\n                } else if let ast::TableConstraint::ForeignKey {\n                    columns,\n                    clause,\n                    defer_clause,\n                } = &c.constraint\n                {\n                    let child_columns: Vec<String> = columns\n                        .iter()\n                        .map(|ic| normalize_ident(ic.col_name.as_str()))\n                        .collect();\n                    // derive parent columns: explicit or default to parent PK\n                    let parent_table = normalize_ident(clause.tbl_name.as_str());\n                    let parent_columns: Vec<String> = clause\n                        .columns\n                        .iter()\n                        .map(|ic| normalize_ident(ic.col_name.as_str()))\n                        .collect();\n\n                    // Only check arity if parent columns were explicitly listed\n                    if !parent_columns.is_empty() && child_columns.len() != parent_columns.len() {\n                        crate::bail_parse_error!(\n                            \"foreign key on \\\"{}\\\" has {} child column(s) but {} parent column(s)\",\n                            tbl_name,\n                            child_columns.len(),\n                            parent_columns.len()\n                        );\n                    }\n                    // deferrable semantics\n                    let deferred = match defer_clause {\n                        Some(d) => {\n                            d.deferrable\n                                && matches!(\n                                    d.init_deferred,\n                                    Some(InitDeferredPred::InitiallyDeferred)\n                                )\n                        }\n                        None => false, // NOT DEFERRABLE INITIALLY IMMEDIATE by default\n                    };\n                    let fk = ForeignKey {\n                        parent_table,\n                        parent_columns,\n                        child_columns,\n                        on_delete: clause\n                            .args\n                            .iter()\n                            .find_map(|a| {\n                                if let ast::RefArg::OnDelete(x) = a {\n                                    Some(*x)\n                                } else {\n                                    None\n                                }\n                            })\n                            .unwrap_or(RefAct::NoAction),\n                        on_update: clause\n                            .args\n                            .iter()\n                            .find_map(|a| {\n                                if let ast::RefArg::OnUpdate(x) = a {\n                                    Some(*x)\n                                } else {\n                                    None\n                                }\n                            })\n                            .unwrap_or(RefAct::NoAction),\n                        deferred,\n                    };\n                    foreign_keys.push(Arc::new(fk));\n                } else if let ast::TableConstraint::Check(expr) = &c.constraint {\n                    check_constraints.push(CheckConstraint::new(c.name.as_ref(), expr, None));\n                }\n            }\n\n            // Due to a bug in SQLite, this check is needed to maintain backwards compatibility with rowid alias\n            // SQLite docs: https://sqlite.org/lang_createtable.html#rowids_and_the_integer_primary_key\n            // Issue: https://github.com/tursodatabase/turso/issues/3665\n            let mut primary_key_desc_columns_constraint = false;\n\n            for ast::ColumnDefinition {\n                col_name,\n                col_type,\n                constraints,\n            } in columns\n            {\n                let name = col_name.as_str().to_string();\n                // Regular sqlite tables have an integer rowid that uniquely identifies a row.\n                // Even if you create a table with a column e.g. 'id INT PRIMARY KEY', there will still\n                // be a separate hidden rowid, and the 'id' column will have a separate index built for it.\n                //\n                // However:\n                // A column defined as exactly INTEGER PRIMARY KEY is a rowid alias, meaning that the rowid\n                // and the value of this column are the same.\n                // https://www.sqlite.org/lang_createtable.html#rowids_and_the_integer_primary_key\n                let ty_str = col_type\n                    .as_ref()\n                    .cloned()\n                    .map(|ast::Type { name, .. }| name)\n                    .unwrap_or_default();\n\n                let ty_params: Vec<Box<Expr>> = match col_type {\n                    Some(ast::Type {\n                        size: Some(ast::TypeSize::MaxSize(ref expr)),\n                        ..\n                    }) => vec![expr.clone()],\n                    Some(ast::Type {\n                        size: Some(ast::TypeSize::TypeSize(ref e1, ref e2)),\n                        ..\n                    }) => vec![e1.clone(), e2.clone()],\n                    _ => Vec::new(),\n                };\n\n                let mut typename_exactly_integer = false;\n                let ty = match col_type {\n                    Some(data_type) => {\n                        let (ty, ei) = type_from_name(&data_type.name);\n                        typename_exactly_integer = ei;\n                        ty\n                    }\n                    None => Type::Null,\n                };\n\n                let mut default = None;\n                let mut primary_key = false;\n                let mut notnull = false;\n                let mut notnull_conflict_clause = None;\n                let mut order = SortOrder::Asc;\n                let mut unique = false;\n                let mut collation = None;\n                for c_def in constraints {\n                    match &c_def.constraint {\n                        ast::ColumnConstraint::Check(expr) => {\n                            check_constraints.push(CheckConstraint::new(\n                                c_def.name.as_ref(),\n                                expr,\n                                Some(&name),\n                            ));\n                        }\n                        ast::ColumnConstraint::Generated { .. } => {\n                            // todo(sivukhin): table_xinfo must be updated when generated columns will be supported in order to properly emit \"hidden\" column value\n                            crate::bail_parse_error!(\"GENERATED columns are not yet supported\");\n                        }\n                        ast::ColumnConstraint::PrimaryKey {\n                            order: o,\n                            auto_increment,\n                            conflict_clause,\n                            ..\n                        } => {\n                            if !primary_key_columns.is_empty() {\n                                crate::bail_parse_error!(\n                                    \"table \\\"{}\\\" has more than one primary key\",\n                                    tbl_name\n                                );\n                            }\n                            primary_key = true;\n                            if *auto_increment {\n                                has_autoincrement = true;\n                            }\n                            if let Some(o) = o {\n                                order = *o;\n                            }\n                            unique_sets_columns.push(UniqueSet {\n                                columns: vec![(name.clone(), order)],\n                                is_primary_key: true,\n                                conflict_clause: *conflict_clause,\n                            });\n                        }\n                        ast::ColumnConstraint::NotNull {\n                            nullable,\n                            conflict_clause,\n                            ..\n                        } => {\n                            notnull = !nullable;\n                            notnull_conflict_clause = *conflict_clause;\n                        }\n                        ast::ColumnConstraint::Default(ref expr) => {\n                            default = Some(\n                                translate_ident_to_string_literal(expr)\n                                    .unwrap_or_else(|| expr.clone()),\n                            );\n                        }\n                        ast::ColumnConstraint::Unique(conflict) => {\n                            unique = true;\n                            unique_sets_columns.push(UniqueSet {\n                                columns: vec![(name.clone(), order)],\n                                is_primary_key: false,\n                                conflict_clause: *conflict,\n                            });\n                        }\n                        ast::ColumnConstraint::Collate { ref collation_name } => {\n                            collation = Some(CollationSeq::new(collation_name.as_str())?);\n                        }\n                        ast::ColumnConstraint::ForeignKey {\n                            clause,\n                            defer_clause,\n                        } => {\n                            if clause.columns.len() > 1 {\n                                crate::bail_parse_error!(\n                                    \"foreign key on {} should reference only one column of table {}\",\n                                    name,\n                                    clause.tbl_name.as_str()\n                                );\n                            }\n                            let fk = ForeignKey {\n                                parent_table: normalize_ident(clause.tbl_name.as_str()),\n                                parent_columns: clause\n                                    .columns\n                                    .iter()\n                                    .map(|c| normalize_ident(c.col_name.as_str()))\n                                    .collect(),\n                                on_delete: clause\n                                    .args\n                                    .iter()\n                                    .find_map(|arg| {\n                                        if let ast::RefArg::OnDelete(act) = arg {\n                                            Some(*act)\n                                        } else {\n                                            None\n                                        }\n                                    })\n                                    .unwrap_or(RefAct::NoAction),\n                                on_update: clause\n                                    .args\n                                    .iter()\n                                    .find_map(|arg| {\n                                        if let ast::RefArg::OnUpdate(act) = arg {\n                                            Some(*act)\n                                        } else {\n                                            None\n                                        }\n                                    })\n                                    .unwrap_or(RefAct::NoAction),\n                                child_columns: vec![name.clone()],\n                                deferred: match defer_clause {\n                                    Some(d) => {\n                                        d.deferrable\n                                            && matches!(\n                                                d.init_deferred,\n                                                Some(InitDeferredPred::InitiallyDeferred)\n                                            )\n                                    }\n                                    None => false,\n                                },\n                            };\n                            foreign_keys.push(Arc::new(fk));\n                        }\n                    }\n                }\n\n                if primary_key {\n                    primary_key_columns.push((name.clone(), order));\n                    if order == SortOrder::Desc {\n                        primary_key_desc_columns_constraint = true;\n                    }\n                } else if primary_key_columns\n                    .iter()\n                    .any(|(col_name, _)| col_name.eq_ignore_ascii_case(&name))\n                {\n                    primary_key = true;\n                }\n\n                let mut col = Column::new(\n                    Some(name),\n                    ty_str,\n                    default,\n                    None,\n                    ty,\n                    collation,\n                    ColDef {\n                        primary_key,\n                        rowid_alias: typename_exactly_integer\n                            && primary_key\n                            && !primary_key_desc_columns_constraint,\n                        notnull,\n                        unique,\n                        hidden: false,\n                        notnull_conflict_clause,\n                    },\n                );\n                col.ty_params = ty_params;\n                if let Some(t) = col_type.as_ref() {\n                    if t.is_array() {\n                        col.set_array_dimensions(t.array_dimensions);\n                    }\n                }\n                cols.push(col);\n            }\n\n            if options.contains_without_rowid() {\n                has_rowid = false;\n            }\n        }\n        CreateTableBody::AsSelect(_) => {\n            crate::bail_parse_error!(\"CREATE TABLE AS SELECT is not supported\")\n        }\n    };\n\n    // flip is_rowid_alias back to false if the table has multiple primary key columns\n    // or if the table has no rowid\n    if !has_rowid || primary_key_columns.len() > 1 {\n        for col in cols.iter_mut() {\n            col.set_rowid_alias(false);\n        }\n    }\n\n    if has_autoincrement {\n        // only allow integers\n        if primary_key_columns.len() != 1 {\n            crate::bail_parse_error!(\"AUTOINCREMENT is only allowed on an INTEGER PRIMARY KEY\");\n        }\n\n        let pk_col_name = &primary_key_columns[0].0;\n        let pk_col = cols.iter().find(|c| {\n            c.name\n                .as_deref()\n                .is_some_and(|n| n.eq_ignore_ascii_case(pk_col_name))\n        });\n\n        if let Some(col) = pk_col {\n            if col.ty() != Type::Integer {\n                crate::bail_parse_error!(\"AUTOINCREMENT is only allowed on an INTEGER PRIMARY KEY\");\n            }\n        }\n    }\n\n    // concat unqiue_sets collected from column definitions and constraints in correct order\n    let mut unique_sets = unique_sets_columns\n        .into_iter()\n        .chain(unique_sets_constraints)\n        .collect::<Vec<_>>();\n    // Capture PK conflict clause before the rowid-alias UniqueSet is removed.\n    let rowid_alias_conflict_clause = unique_sets\n        .iter()\n        .find(|us| us.is_primary_key)\n        .and_then(|us| us.conflict_clause);\n    for col in cols.iter() {\n        if col.is_rowid_alias() {\n            // Unique sets are used for creating automatic indexes. An index is not created for a rowid alias PRIMARY KEY.\n            // However, an index IS created for a rowid alias UNIQUE, e.g. CREATE TABLE t(x INTEGER PRIMARY KEY, UNIQUE(x))\n            let unique_set_w_only_rowid_alias = unique_sets.iter().position(|us| {\n                us.is_primary_key\n                    && us.columns.len() == 1\n                    && us\n                        .columns\n                        .first()\n                        .unwrap()\n                        .0\n                        .eq_ignore_ascii_case(col.name.as_ref().unwrap())\n            });\n            if let Some(u) = unique_set_w_only_rowid_alias {\n                unique_sets.remove(u);\n            }\n        }\n    }\n\n    Ok(BTreeTable {\n        root_page,\n        name: table_name,\n        has_rowid,\n        primary_key_columns,\n        has_autoincrement,\n        columns: cols,\n        is_strict,\n        foreign_keys,\n        unique_sets: {\n            // If there are any unique sets that have identical column names in the same order (even if they are PRIMARY KEY and UNIQUE and have different sort orders), remove the duplicates.\n            // Examples:\n            // PRIMARY KEY (a, b) and UNIQUE (a desc, b) are the same\n            // PRIMARY KEY (a, b) and UNIQUE (b, a) are not the same\n            // Using a n^2 monkey algorithm here because n is small, CPUs are fast, life is short, and most importantly:\n            // we want to preserve the order of the sets -- automatic index names in sqlite_schema must be in definition order.\n            let mut i = 0;\n            while i < unique_sets.len() {\n                let mut j = i + 1;\n                while j < unique_sets.len() {\n                    let lengths_equal =\n                        unique_sets[i].columns.len() == unique_sets[j].columns.len();\n                    if lengths_equal\n                        && unique_sets[i]\n                            .columns\n                            .iter()\n                            .zip(unique_sets[j].columns.iter())\n                            .all(|((a_name, _), (b_name, _))| a_name.eq_ignore_ascii_case(b_name))\n                    {\n                        // SQLite rejects duplicate constraints on the same columns when both\n                        // specify ON CONFLICT with different resolve types.\n                        if let (Some(a), Some(b)) = (\n                            unique_sets[i].conflict_clause,\n                            unique_sets[j].conflict_clause,\n                        ) {\n                            if a != b {\n                                crate::bail_parse_error!(\n                                    \"conflicting ON CONFLICT clauses specified\"\n                                );\n                            }\n                        }\n                        unique_sets.remove(j);\n                    } else {\n                        j += 1;\n                    }\n                }\n                i += 1;\n            }\n            unique_sets\n        },\n        check_constraints,\n        rowid_alias_conflict_clause,\n    })\n}\n\n/// SQLite treats bare identifiers in DEFAULT clauses as string literals.\n/// E.g., `DEFAULT hello` becomes the string \"hello\", not a column reference.\npub fn translate_ident_to_string_literal(expr: &Expr) -> Option<Box<Expr>> {\n    match expr {\n        Expr::Name(name) | Expr::Id(name) => {\n            Some(Box::new(Expr::Literal(Literal::String(name.as_literal()))))\n        }\n        _ => None,\n    }\n}\n\npub fn _build_pseudo_table(columns: &[ResultColumn]) -> PseudoCursorType {\n    let table = PseudoCursorType::new();\n    for column in columns {\n        match column {\n            ResultColumn::Expr(expr, _as_name) => {\n                todo!(\"unsupported expression {:?}\", expr);\n            }\n            ResultColumn::Star => {\n                todo!();\n            }\n            ResultColumn::TableStar(_) => {\n                todo!();\n            }\n        }\n    }\n    table\n}\n\n#[derive(Debug, Clone)]\npub struct ForeignKey {\n    /// Columns in this table (child side)\n    pub child_columns: Vec<String>,\n    /// Referenced (parent) table\n    pub parent_table: String,\n    /// Parent-side referenced columns\n    pub parent_columns: Vec<String>,\n    pub on_delete: RefAct,\n    pub on_update: RefAct,\n    /// DEFERRABLE INITIALLY DEFERRED\n    pub deferred: bool,\n}\nimpl ForeignKey {\n    fn validate(&self) -> Result<()> {\n        if self\n            .parent_columns\n            .iter()\n            .any(|c| ROWID_STRS.iter().any(|&r| r.eq_ignore_ascii_case(c)))\n        {\n            return Err(crate::LimboError::ForeignKeyConstraint(format!(\n                \"foreign key mismatch referencing \\\"{}\\\"\",\n                self.parent_table\n            )));\n        }\n        Ok(())\n    }\n}\n\n/// A single resolved foreign key where `parent_table == target`.\n#[derive(Clone, Debug)]\npub struct ResolvedFkRef {\n    /// Child table that owns the FK.\n    pub child_table: Arc<BTreeTable>,\n    /// The FK as declared on the child table.\n    pub fk: Arc<ForeignKey>,\n\n    /// Resolved, normalized column names.\n    pub child_cols: Vec<String>,\n    /// Column positions in the child/parent tables (pos_in_table)\n    pub child_pos: Vec<usize>,\n    pub parent_pos: Vec<usize>,\n\n    /// If the parent key is rowid or a rowid-alias (single-column only)\n    pub parent_uses_rowid: bool,\n    /// For non-rowid parents: the UNIQUE index that enforces the parent key.\n    /// (None when `parent_uses_rowid == true`.)\n    pub parent_unique_index: Option<Arc<Index>>,\n}\n\nimpl ResolvedFkRef {\n    /// Returns if any referenced parent column can change when these column positions are updated.\n    pub fn parent_key_may_change(\n        &self,\n        updated_parent_positions: &HashSet<usize>,\n        parent_tbl: &BTreeTable,\n    ) -> bool {\n        if self.parent_uses_rowid {\n            // parent rowid changes if the parent's rowid or alias is updated\n            if let Some((idx, _)) = parent_tbl\n                .columns\n                .iter()\n                .enumerate()\n                .find(|(_, c)| c.is_rowid_alias())\n            {\n                return updated_parent_positions.contains(&idx);\n            }\n            // Without a rowid alias, a direct rowid update is represented separately with ROWID_SENTINEL\n            return true;\n        }\n        self.parent_pos\n            .iter()\n            .any(|p| updated_parent_positions.contains(p))\n    }\n\n    /// Returns if any child column of this FK is in `updated_child_positions`\n    pub fn child_key_changed(\n        &self,\n        updated_child_positions: &HashSet<usize>,\n        child_tbl: &BTreeTable,\n    ) -> bool {\n        if self\n            .child_pos\n            .iter()\n            .any(|p| updated_child_positions.contains(p))\n        {\n            return true;\n        }\n        // special case: if FK uses a rowid alias on child, and rowid changed\n        if self.child_cols.len() == 1 {\n            let (i, col) = child_tbl.get_column(&self.child_cols[0]).unwrap();\n            if col.is_rowid_alias() && updated_child_positions.contains(&i) {\n                return true;\n            }\n        }\n        false\n    }\n}\n\n#[derive(Debug, Clone)]\npub struct Column {\n    pub name: Option<String>,\n    pub ty_str: String,\n    pub ty_params: Vec<Box<Expr>>,\n    pub default: Option<Box<Expr>>,\n    pub generated: Option<Box<Expr>>,\n    raw: u16,\n    /// ON CONFLICT clause for NOT NULL constraint on this column.\n    pub notnull_conflict_clause: Option<ResolveType>,\n}\n\n#[derive(Default)]\npub struct ColDef {\n    pub primary_key: bool,\n    pub rowid_alias: bool,\n    pub notnull: bool,\n    pub unique: bool,\n    pub hidden: bool,\n    pub notnull_conflict_clause: Option<ResolveType>,\n}\n\n// flags\nconst F_PRIMARY_KEY: u16 = 1;\nconst F_ROWID_ALIAS: u16 = 2;\nconst F_NOTNULL: u16 = 4;\nconst F_UNIQUE: u16 = 8;\nconst F_HIDDEN: u16 = 16;\n\n// pack Type and Collation in the remaining bits\nconst TYPE_SHIFT: u16 = 5;\nconst TYPE_MASK: u16 = 0b111 << TYPE_SHIFT;\nconst COLL_SHIFT: u16 = TYPE_SHIFT + 3;\nconst COLL_MASK: u16 = 0b11 << COLL_SHIFT;\n\n// Bits 10-12: base type affinity override for custom type columns.\n// 0 = not set (use ty_str-based affinity), 1-5 = Affinity value + 1\nconst BASE_AFF_SHIFT: u16 = COLL_SHIFT + 2;\nconst BASE_AFF_MASK: u16 = 0b111 << BASE_AFF_SHIFT;\n\n// Bits 13-15: array dimensions (0 = scalar, 1-7 = number of [] dimensions)\nconst ARRAY_DIM_SHIFT: u16 = 13;\nconst ARRAY_DIM_MASK: u16 = 0b111 << ARRAY_DIM_SHIFT;\n\nimpl Column {\n    pub fn affinity(&self) -> Affinity {\n        let v = ((self.raw & BASE_AFF_MASK) >> BASE_AFF_SHIFT) as u8;\n        if v > 0 {\n            // Custom type column: use the base type's affinity\n            match v {\n                1 => Affinity::Integer,\n                2 => Affinity::Text,\n                3 => Affinity::Blob,\n                4 => Affinity::Real,\n                _ => Affinity::Numeric,\n            }\n        } else {\n            Affinity::affinity(&self.ty_str)\n        }\n    }\n\n    /// Set the base type affinity override for a custom type column.\n    /// This ensures affinity rules use the custom type's BASE type\n    /// rather than applying SQLite name-based rules to the type name.\n    pub fn set_base_affinity(&mut self, affinity: Affinity) {\n        let v: u16 = match affinity {\n            Affinity::Integer => 1,\n            Affinity::Text => 2,\n            Affinity::Blob => 3,\n            Affinity::Real => 4,\n            Affinity::Numeric => 5,\n        };\n        self.raw = (self.raw & !BASE_AFF_MASK) | ((v << BASE_AFF_SHIFT) & BASE_AFF_MASK);\n    }\n    pub fn affinity_with_strict(&self, is_strict: bool) -> Affinity {\n        if is_strict && self.ty_str.eq_ignore_ascii_case(\"ANY\") {\n            Affinity::Blob\n        } else {\n            self.affinity()\n        }\n    }\n    pub fn new_default_text(\n        name: Option<String>,\n        ty_str: String,\n        default: Option<Box<Expr>>,\n    ) -> Self {\n        Self::new(\n            name,\n            ty_str,\n            default,\n            None,\n            Type::Text,\n            None,\n            ColDef::default(),\n        )\n    }\n    pub fn new_default_integer(\n        name: Option<String>,\n        ty_str: String,\n        default: Option<Box<Expr>>,\n    ) -> Self {\n        Self::new(\n            name,\n            ty_str,\n            default,\n            None,\n            Type::Integer,\n            None,\n            ColDef::default(),\n        )\n    }\n    #[inline]\n    pub fn new(\n        name: Option<String>,\n        ty_str: String,\n        default: Option<Box<Expr>>,\n        generated: Option<Box<Expr>>,\n        ty: Type,\n        col: Option<CollationSeq>,\n        coldef: ColDef,\n    ) -> Self {\n        let mut raw = 0u16;\n        raw |= (ty as u16) << TYPE_SHIFT;\n        if let Some(c) = col {\n            raw |= (c as u16) << COLL_SHIFT;\n        }\n        if coldef.primary_key {\n            raw |= F_PRIMARY_KEY\n        }\n        if coldef.rowid_alias {\n            raw |= F_ROWID_ALIAS\n        }\n        if coldef.notnull {\n            raw |= F_NOTNULL\n        }\n        if coldef.unique {\n            raw |= F_UNIQUE\n        }\n        if coldef.hidden {\n            raw |= F_HIDDEN\n        }\n        Self {\n            name,\n            ty_str,\n            ty_params: Vec::new(),\n            default,\n            generated,\n            raw,\n            notnull_conflict_clause: coldef.notnull_conflict_clause,\n        }\n    }\n    #[inline]\n    pub const fn ty(&self) -> Type {\n        let v = ((self.raw & TYPE_MASK) >> TYPE_SHIFT) as u8;\n        Type::from_bits(v)\n    }\n\n    #[inline]\n    pub const fn set_ty(&mut self, ty: Type) {\n        self.raw = (self.raw & !TYPE_MASK) | (((ty as u16) << TYPE_SHIFT) & TYPE_MASK);\n    }\n\n    #[inline]\n    pub const fn collation_opt(&self) -> Option<CollationSeq> {\n        if self.has_explicit_collation() {\n            Some(self.collation())\n        } else {\n            None\n        }\n    }\n\n    #[inline]\n    pub const fn collation(&self) -> CollationSeq {\n        let v = ((self.raw & COLL_MASK) >> COLL_SHIFT) as u8;\n        CollationSeq::from_bits(v)\n    }\n\n    #[inline]\n    pub const fn has_explicit_collation(&self) -> bool {\n        let v = ((self.raw & COLL_MASK) >> COLL_SHIFT) as u8;\n        v != CollationSeq::Unset as u8\n    }\n\n    #[inline]\n    pub const fn set_collation(&mut self, c: Option<CollationSeq>) {\n        if let Some(c) = c {\n            self.raw = (self.raw & !COLL_MASK) | (((c as u16) << COLL_SHIFT) & COLL_MASK);\n        }\n    }\n\n    #[inline]\n    pub fn primary_key(&self) -> bool {\n        self.raw & F_PRIMARY_KEY != 0\n    }\n    #[inline]\n    pub const fn is_rowid_alias(&self) -> bool {\n        self.raw & F_ROWID_ALIAS != 0\n    }\n    #[inline]\n    pub const fn notnull(&self) -> bool {\n        self.raw & F_NOTNULL != 0\n    }\n    #[inline]\n    pub const fn unique(&self) -> bool {\n        self.raw & F_UNIQUE != 0\n    }\n    #[inline]\n    pub const fn hidden(&self) -> bool {\n        self.raw & F_HIDDEN != 0\n    }\n\n    #[inline]\n    pub const fn set_primary_key(&mut self, v: bool) {\n        self.set_flag(F_PRIMARY_KEY, v);\n    }\n    #[inline]\n    pub const fn set_rowid_alias(&mut self, v: bool) {\n        self.set_flag(F_ROWID_ALIAS, v);\n    }\n    #[inline]\n    pub const fn set_notnull(&mut self, v: bool) {\n        self.set_flag(F_NOTNULL, v);\n    }\n    #[inline]\n    pub const fn set_unique(&mut self, v: bool) {\n        self.set_flag(F_UNIQUE, v);\n    }\n    #[inline]\n    pub const fn set_hidden(&mut self, v: bool) {\n        self.set_flag(F_HIDDEN, v);\n    }\n\n    #[inline]\n    pub const fn is_array(&self) -> bool {\n        (self.raw & ARRAY_DIM_MASK) != 0\n    }\n\n    /// Number of array dimensions (0 = scalar, 1 = `[]`, 2 = `[][]`, etc.)\n    #[inline]\n    pub const fn array_dimensions(&self) -> u32 {\n        ((self.raw & ARRAY_DIM_MASK) >> ARRAY_DIM_SHIFT) as u32\n    }\n\n    #[inline]\n    pub fn set_array_dimensions(&mut self, dims: u32) {\n        assert!(dims <= 7, \"array dimensions must be <= 7\");\n        self.raw = (self.raw & !ARRAY_DIM_MASK) | ((dims as u16) << ARRAY_DIM_SHIFT);\n    }\n\n    #[inline]\n    const fn set_flag(&mut self, mask: u16, val: bool) {\n        if val {\n            self.raw |= mask\n        } else {\n            self.raw &= !mask\n        }\n    }\n}\n\n// TODO: This might replace some of util::columns_from_create_table_body\nimpl TryFrom<&ColumnDefinition> for Column {\n    type Error = crate::LimboError;\n\n    fn try_from(value: &ColumnDefinition) -> crate::Result<Self> {\n        let name = value.col_name.as_str();\n\n        let mut default = None;\n        let mut generated = None;\n        let mut notnull = false;\n        let mut notnull_conflict_clause = None;\n        let mut primary_key = false;\n        let mut unique = false;\n        let mut collation = None;\n\n        for ast::NamedColumnConstraint { constraint, .. } in &value.constraints {\n            match constraint {\n                ast::ColumnConstraint::PrimaryKey { .. } => primary_key = true,\n                ast::ColumnConstraint::NotNull {\n                    conflict_clause, ..\n                } => {\n                    notnull = true;\n                    notnull_conflict_clause = *conflict_clause;\n                }\n                ast::ColumnConstraint::Unique(..) => unique = true,\n                ast::ColumnConstraint::Default(expr) => {\n                    default.replace(\n                        translate_ident_to_string_literal(expr).unwrap_or_else(|| expr.clone()),\n                    );\n                }\n                ast::ColumnConstraint::Collate { collation_name } => {\n                    collation.replace(CollationSeq::new(collation_name.as_str())?);\n                }\n                ast::ColumnConstraint::Generated { expr, .. } => {\n                    generated = Some(expr.clone());\n                }\n                _ => {}\n            };\n        }\n\n        let ty = match value.col_type {\n            Some(ref data_type) => type_from_name(&data_type.name).0,\n            None => Type::Null,\n        };\n\n        let ty_str = value\n            .col_type\n            .as_ref()\n            .map(|t| t.name.to_string())\n            .unwrap_or_default();\n\n        let ty_params: Vec<Box<turso_parser::ast::Expr>> = match &value.col_type {\n            Some(ast::Type {\n                size: Some(ast::TypeSize::MaxSize(ref expr)),\n                ..\n            }) => vec![expr.clone()],\n            Some(ast::Type {\n                size: Some(ast::TypeSize::TypeSize(ref e1, ref e2)),\n                ..\n            }) => vec![e1.clone(), e2.clone()],\n            _ => Vec::new(),\n        };\n\n        let hidden = ty_str.contains(\"HIDDEN\");\n\n        let mut col = Column::new(\n            Some(name.to_string()),\n            ty_str,\n            default,\n            generated,\n            ty,\n            collation,\n            ColDef {\n                primary_key,\n                rowid_alias: primary_key && matches!(ty, Type::Integer),\n                notnull,\n                unique,\n                hidden,\n                notnull_conflict_clause,\n            },\n        );\n        col.ty_params = ty_params;\n        if let Some(t) = value.col_type.as_ref() {\n            if t.is_array() {\n                col.set_array_dimensions(t.array_dimensions);\n            }\n        }\n        Ok(col)\n    }\n}\n\n#[repr(u8)]\n#[derive(Debug, Clone, Copy, PartialEq)]\npub enum Type {\n    Null = 0,\n    Text = 1,\n    Numeric = 2,\n    Integer = 3,\n    Real = 4,\n    Blob = 5,\n}\n\nimpl Type {\n    #[inline]\n    const fn from_bits(bits: u8) -> Self {\n        match bits {\n            0 => Type::Null,\n            1 => Type::Text,\n            2 => Type::Numeric,\n            3 => Type::Integer,\n            4 => Type::Real,\n            5 => Type::Blob,\n            _ => Type::Null,\n        }\n    }\n}\n\nimpl fmt::Display for Type {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        let s = match self {\n            Self::Null => \"\",\n            Self::Text => \"TEXT\",\n            Self::Numeric => \"NUMERIC\",\n            Self::Integer => \"INTEGER\",\n            Self::Real => \"REAL\",\n            Self::Blob => \"BLOB\",\n        };\n        write!(f, \"{s}\")\n    }\n}\n\npub fn sqlite_schema_table() -> BTreeTable {\n    BTreeTable {\n        root_page: 1,\n        name: \"sqlite_schema\".to_string(),\n        has_rowid: true,\n        is_strict: false,\n        has_autoincrement: false,\n        primary_key_columns: vec![],\n        columns: vec![\n            Column::new_default_text(Some(\"type\".to_string()), \"TEXT\".to_string(), None),\n            Column::new_default_text(Some(\"name\".to_string()), \"TEXT\".to_string(), None),\n            Column::new_default_text(Some(\"tbl_name\".to_string()), \"TEXT\".to_string(), None),\n            Column::new_default_integer(Some(\"rootpage\".to_string()), \"INT\".to_string(), None),\n            Column::new_default_text(Some(\"sql\".to_string()), \"TEXT\".to_string(), None),\n        ],\n        foreign_keys: vec![],\n        check_constraints: vec![],\n        rowid_alias_conflict_clause: None,\n        unique_sets: vec![],\n    }\n}\n\n#[allow(dead_code)]\n#[derive(Debug, Clone)]\npub struct Index {\n    pub name: String,\n    pub table_name: String,\n    pub root_page: i64,\n    pub columns: Vec<IndexColumn>,\n    pub unique: bool,\n    pub ephemeral: bool,\n    /// Does the index have a rowid as the last column?\n    /// This is the case for btree indexes (persistent or ephemeral) that\n    /// have been created based on a table with a rowid.\n    /// For example, WITHOUT ROWID tables (not supported in Limbo yet),\n    /// and  SELECT DISTINCT ephemeral indexes will not have a rowid.\n    pub has_rowid: bool,\n    pub where_clause: Option<Box<Expr>>,\n    pub index_method: Option<Arc<dyn IndexMethodAttachment>>,\n    /// ON CONFLICT clause from the constraint definition (PRIMARY KEY or UNIQUE).\n    pub on_conflict: Option<ResolveType>,\n}\n\n#[allow(dead_code)]\n#[derive(Debug, Clone)]\npub struct IndexColumn {\n    pub name: String,\n    pub order: SortOrder,\n    /// the position of the column in the source table.\n    /// for example:\n    /// CREATE TABLE t (a,b,c)\n    /// CREATE INDEX idx ON t(b)\n    /// b.pos_in_table == 1\n    pub pos_in_table: usize,\n    pub collation: Option<CollationSeq>,\n    pub default: Option<Box<Expr>>,\n    /// Expression for expression indexes. None for simple column indexes.\n    pub expr: Option<Box<Expr>>,\n}\n\nimpl Index {\n    pub fn from_sql(\n        syms: &SymbolTable,\n        sql: &str,\n        root_page: i64,\n        table: &BTreeTable,\n    ) -> Result<Index> {\n        let mut parser = Parser::new(sql.as_bytes());\n        let cmd = parser.next_cmd()?;\n        match cmd {\n            Some(Cmd::Stmt(Stmt::CreateIndex {\n                idx_name,\n                tbl_name,\n                columns,\n                unique,\n                where_clause,\n                using,\n                with_clause,\n                ..\n            })) => {\n                let index_name = normalize_ident(idx_name.name.as_str());\n                let index_columns = resolve_sorted_columns(table, &columns)?;\n                if let Some(using) = using {\n                    if where_clause.is_some() {\n                        bail_parse_error!(\"custom index module do not support partial indices\");\n                    }\n                    if unique {\n                        bail_parse_error!(\"custom index module do not support UNIQUE indices\");\n                    }\n                    let parameters = resolve_index_method_parameters(with_clause)?;\n                    let Some(module) = syms.index_methods.get(using.as_str()) else {\n                        bail_parse_error!(\"unknown module name: '{}'\", using);\n                    };\n                    let configuration = IndexMethodConfiguration {\n                        table_name: table.name.clone(),\n                        index_name: index_name.clone(),\n                        columns: index_columns.clone(),\n                        parameters,\n                    };\n                    let descriptor = module.attach(&configuration)?;\n                    Ok(Index {\n                        name: index_name,\n                        table_name: normalize_ident(tbl_name.as_str()),\n                        root_page,\n                        columns: index_columns,\n                        unique: false,\n                        ephemeral: false,\n                        has_rowid: table.has_rowid,\n                        where_clause: None,\n                        index_method: Some(descriptor),\n                        on_conflict: None,\n                    })\n                } else {\n                    Ok(Index {\n                        name: index_name,\n                        table_name: normalize_ident(tbl_name.as_str()),\n                        root_page,\n                        columns: index_columns,\n                        unique,\n                        ephemeral: false,\n                        has_rowid: table.has_rowid,\n                        where_clause,\n                        index_method: None,\n                        on_conflict: None,\n                    })\n                }\n            }\n            _ => todo!(\"Expected create index statement\"),\n        }\n    }\n\n    /// Check if this is an expression index.\n    pub fn is_expression_index(&self) -> bool {\n        self.columns.iter().any(|c| c.expr.is_some())\n    }\n\n    /// check if this is special backing_btree index created and managed by custom index_method\n    pub fn is_backing_btree_index(&self) -> bool {\n        self.index_method\n            .as_ref()\n            .is_some_and(|x| x.definition().backing_btree)\n    }\n\n    pub fn automatic_from_primary_key(\n        table: &BTreeTable,\n        auto_index: (String, i64), // name, root_page\n        column_count: usize,\n        conflict_clause: Option<ResolveType>,\n    ) -> Result<Index> {\n        let has_primary_key_index =\n            table.get_rowid_alias_column().is_none() && !table.primary_key_columns.is_empty();\n        assert!(has_primary_key_index);\n        let (index_name, root_page) = auto_index;\n\n        let mut primary_keys = Vec::with_capacity(column_count);\n        for (col_name, order) in table.primary_key_columns.iter() {\n            let Some((pos_in_table, _)) = table.get_column(col_name) else {\n                return Err(crate::LimboError::ParseError(format!(\n                    \"Column {} not found in table {}\",\n                    col_name, table.name\n                )));\n            };\n            let (_, column) = table.get_column(col_name).unwrap();\n            primary_keys.push(IndexColumn {\n                name: normalize_ident(col_name),\n                order: *order,\n                pos_in_table,\n                collation: column.collation_opt(),\n                default: column.default.clone(),\n                expr: None,\n            });\n        }\n\n        assert!(primary_keys.len() == column_count);\n\n        Ok(Index {\n            name: normalize_ident(index_name.as_str()),\n            table_name: table.name.clone(),\n            root_page,\n            columns: primary_keys,\n            unique: true,\n            ephemeral: false,\n            has_rowid: table.has_rowid,\n            where_clause: None,\n            index_method: None,\n            on_conflict: conflict_clause,\n        })\n    }\n\n    pub fn automatic_from_unique(\n        table: &BTreeTable,\n        auto_index: (String, i64), // name, root_page\n        column_indices_and_sort_orders: Vec<(usize, SortOrder)>,\n        conflict_clause: Option<ResolveType>,\n    ) -> Result<Index> {\n        let (index_name, root_page) = auto_index;\n\n        let mut unique_cols = Vec::with_capacity(column_indices_and_sort_orders.len());\n        for (pos, sort_order) in &column_indices_and_sort_orders {\n            let Some((pos_in_table, col)) = table\n                .columns\n                .iter()\n                .enumerate()\n                .find(|(pos_in_table, _)| pos == pos_in_table)\n            else {\n                return Err(crate::LimboError::ParseError(format!(\n                    \"Unique constraint column not found in table {}\",\n                    table.name\n                )));\n            };\n            unique_cols.push(IndexColumn {\n                name: normalize_ident(col.name.as_ref().unwrap()),\n                order: *sort_order,\n                pos_in_table,\n                collation: col.collation_opt(),\n                default: col.default.clone(),\n                expr: None,\n            });\n        }\n\n        Ok(Index {\n            name: normalize_ident(index_name.as_str()),\n            table_name: table.name.clone(),\n            root_page,\n            columns: unique_cols,\n            unique: true,\n            ephemeral: false,\n            has_rowid: table.has_rowid,\n            where_clause: None,\n            index_method: None,\n            on_conflict: conflict_clause,\n        })\n    }\n\n    /// Given a column position in the table, return the position in the index.\n    /// Returns None if the column is not found in the index.\n    /// For example, given:\n    /// CREATE TABLE t (a, b, c)\n    /// CREATE INDEX idx ON t(b)\n    /// then column_table_pos_to_index_pos(1) returns Some(0)\n    pub fn column_table_pos_to_index_pos(&self, table_pos: usize) -> Option<usize> {\n        self.columns\n            .iter()\n            .position(|c| c.pos_in_table == table_pos)\n    }\n\n    /// Given an expression, return the position in the index if it matches an expression index column.\n    /// Expression index matching is textual (after binding), so the caller should normalize the query\n    /// expression to resemble the stored index expression (e.g. unqualified column names).\n    pub fn expression_to_index_pos(&self, expr: &Expr) -> Option<usize> {\n        self.columns.iter().position(|c| {\n            c.expr\n                .as_ref()\n                .is_some_and(|e| exprs_are_equivalent(e, expr))\n        })\n    }\n\n    /// Walk the where_clause Expr of a partial index and validate that it doesn't reference any other\n    /// tables or use any disallowed constructs.\n    pub fn validate_where_expr(&self, table: &Table, _resolver: &Resolver) -> bool {\n        let Some(where_clause) = &self.where_clause else {\n            return true;\n        };\n\n        let tbl_norm = self.table_name.as_str();\n        let has_col = |name: &str| {\n            table.columns().iter().any(|c| {\n                c.name\n                    .as_ref()\n                    .is_some_and(|cn| cn.eq_ignore_ascii_case(name))\n            })\n        };\n        let is_tbl = |ns: &str| normalize_ident(ns) == tbl_norm;\n        let is_deterministic_fn = |name: &str, argc: usize| {\n            let n = normalize_ident(name);\n            Func::resolve_function(&n, argc).is_ok_and(|f| f.is_deterministic())\n        };\n\n        let mut ok = true;\n        let _ = walk_expr(where_clause.as_ref(), &mut |e: &Expr| -> crate::Result<\n            WalkControl,\n        > {\n            if !ok {\n                return Ok(WalkControl::SkipChildren);\n            }\n            match e {\n                Expr::Literal(_) | Expr::RowId { .. } => {}\n                // Unqualified identifier: must be a column of the target table or ROWID\n                Expr::Id(n) => {\n                    let n = n.as_str();\n                    if !ROWID_STRS.iter().any(|s| s.eq_ignore_ascii_case(n)) && !has_col(n) {\n                        ok = false;\n                    }\n                }\n                // Qualified: qualifier must match this index's table; column must exist\n                Expr::Qualified(ns, col) | Expr::DoublyQualified(_, ns, col) => {\n                    if !is_tbl(ns.as_str()) || !has_col(col.as_str()) {\n                        ok = false;\n                    }\n                }\n                Expr::FunctionCall {\n                    name, filter_over, ..\n                }\n                | Expr::FunctionCallStar {\n                    name, filter_over, ..\n                } => {\n                    // reject windowed\n                    if filter_over.over_clause.is_some() {\n                        ok = false;\n                    } else {\n                        let argc = match e {\n                            Expr::FunctionCall { args, .. } => args.len(),\n                            Expr::FunctionCallStar { .. } => 0,\n                            _ => unreachable!(),\n                        };\n                        // Reject non-deterministic functions. Function arguments can reference\n                        // columns of the indexed table (e.g., LENGTH(t0.c0)), which will be\n                        // validated by the Expr::Id and Expr::Qualified cases during the walk.\n                        if !is_deterministic_fn(name.as_str(), argc) {\n                            ok = false;\n                        }\n                    }\n                }\n                // Explicitly disallowed constructs\n                Expr::Exists(_)\n                | Expr::InSelect { .. }\n                | Expr::Subquery(_)\n                | Expr::Raise { .. }\n                | Expr::Variable(_) => {\n                    ok = false;\n                }\n                _ => {}\n            }\n            Ok(if ok {\n                WalkControl::Continue\n            } else {\n                WalkControl::SkipChildren\n            })\n        });\n        ok\n    }\n\n    pub fn bind_where_expr(\n        &self,\n        table_refs: Option<&mut TableReferences>,\n        resolver: &Resolver,\n    ) -> Option<ast::Expr> {\n        let Some(where_clause) = &self.where_clause else {\n            return None;\n        };\n        let mut expr = where_clause.clone();\n        bind_and_rewrite_expr(\n            &mut expr,\n            table_refs,\n            None,\n            resolver,\n            BindingBehavior::ResultColumnsNotAllowed,\n        )\n        .ok()?;\n        Some(*expr)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    pub fn test_has_rowid_true() -> Result<()> {\n        let sql = r#\"CREATE TABLE t1 (a INTEGER PRIMARY KEY, b TEXT);\"#;\n        let table = BTreeTable::from_sql(sql, 0)?;\n        assert!(table.has_rowid, \"has_rowid should be set to true\");\n        Ok(())\n    }\n\n    #[test]\n    pub fn test_has_rowid_false() -> Result<()> {\n        let sql = r#\"CREATE TABLE t1 (a INTEGER PRIMARY KEY, b TEXT) WITHOUT ROWID;\"#;\n        let table = BTreeTable::from_sql(sql, 0)?;\n        assert!(!table.has_rowid, \"has_rowid should be set to false\");\n        Ok(())\n    }\n\n    #[test]\n    pub fn test_column_is_rowid_alias_single_text() -> Result<()> {\n        let sql = r#\"CREATE TABLE t1 (a TEXT PRIMARY KEY, b TEXT);\"#;\n        let table = BTreeTable::from_sql(sql, 0)?;\n        let column = table.get_column(\"a\").unwrap().1;\n        assert!(\n            !column.is_rowid_alias(),\n            \"column 'a´ has type different than INTEGER so can't be a rowid alias\"\n        );\n        Ok(())\n    }\n\n    #[test]\n    pub fn test_column_is_rowid_alias_single_integer() -> Result<()> {\n        let sql = r#\"CREATE TABLE t1 (a INTEGER PRIMARY KEY, b TEXT);\"#;\n        let table = BTreeTable::from_sql(sql, 0)?;\n        let column = table.get_column(\"a\").unwrap().1;\n        assert!(\n            column.is_rowid_alias(),\n            \"column 'a´ should be a rowid alias\"\n        );\n        Ok(())\n    }\n\n    #[test]\n    pub fn test_column_is_rowid_alias_single_integer_separate_primary_key_definition() -> Result<()>\n    {\n        let sql = r#\"CREATE TABLE t1 (a INTEGER, b TEXT, PRIMARY KEY(a));\"#;\n        let table = BTreeTable::from_sql(sql, 0)?;\n        let column = table.get_column(\"a\").unwrap().1;\n        assert!(\n            column.is_rowid_alias(),\n            \"column 'a´ should be a rowid alias\"\n        );\n        Ok(())\n    }\n\n    #[test]\n    pub fn test_column_is_rowid_alias_single_integer_separate_primary_key_definition_without_rowid(\n    ) -> Result<()> {\n        let sql = r#\"CREATE TABLE t1 (a INTEGER, b TEXT, PRIMARY KEY(a)) WITHOUT ROWID;\"#;\n        let table = BTreeTable::from_sql(sql, 0)?;\n        let column = table.get_column(\"a\").unwrap().1;\n        assert!(\n            !column.is_rowid_alias(),\n            \"column 'a´ shouldn't be a rowid alias because table has no rowid\"\n        );\n        Ok(())\n    }\n\n    #[test]\n    pub fn test_column_is_rowid_alias_single_integer_without_rowid() -> Result<()> {\n        let sql = r#\"CREATE TABLE t1 (a INTEGER PRIMARY KEY, b TEXT) WITHOUT ROWID;\"#;\n        let table = BTreeTable::from_sql(sql, 0)?;\n        let column = table.get_column(\"a\").unwrap().1;\n        assert!(\n            !column.is_rowid_alias(),\n            \"column 'a´ shouldn't be a rowid alias because table has no rowid\"\n        );\n        Ok(())\n    }\n\n    #[test]\n    pub fn test_multiple_pk_forbidden() -> Result<()> {\n        let sql = r#\"CREATE TABLE t1 (a INTEGER PRIMARY KEY, b TEXT PRIMARY KEY);\"#;\n        let table = BTreeTable::from_sql(sql, 0);\n        let error = table.unwrap_err();\n        assert!(\n            matches!(error, LimboError::ParseError(e) if e.contains(\"table \\\"t1\\\" has more than one primary key\"))\n        );\n        Ok(())\n    }\n\n    #[test]\n    pub fn test_column_is_rowid_alias_separate_composite_primary_key_definition() -> Result<()> {\n        let sql = r#\"CREATE TABLE t1 (a INTEGER, b TEXT, PRIMARY KEY(a, b));\"#;\n        let table = BTreeTable::from_sql(sql, 0)?;\n        let column = table.get_column(\"a\").unwrap().1;\n        assert!(\n            !column.is_rowid_alias(),\n            \"column 'a´ shouldn't be a rowid alias because table has composite primary key\"\n        );\n        Ok(())\n    }\n\n    #[test]\n    pub fn test_primary_key_inline_single() -> Result<()> {\n        let sql = r#\"CREATE TABLE t1 (a INTEGER PRIMARY KEY, b TEXT, c REAL);\"#;\n        let table = BTreeTable::from_sql(sql, 0)?;\n        let column = table.get_column(\"a\").unwrap().1;\n        assert!(column.primary_key(), \"column 'a' should be a primary key\");\n        let column = table.get_column(\"b\").unwrap().1;\n        assert!(\n            !column.primary_key(),\n            \"column 'b' shouldn't be a primary key\"\n        );\n        let column = table.get_column(\"c\").unwrap().1;\n        assert!(\n            !column.primary_key(),\n            \"column 'c' shouldn't be a primary key\"\n        );\n        assert_eq!(\n            vec![(\"a\".to_string(), SortOrder::Asc)],\n            table.primary_key_columns,\n            \"primary key column names should be ['a']\"\n        );\n        Ok(())\n    }\n\n    #[test]\n    pub fn test_primary_key_inline_multiple_forbidden() -> Result<()> {\n        let sql = r#\"CREATE TABLE t1 (a INTEGER PRIMARY KEY, b TEXT PRIMARY KEY, c REAL);\"#;\n        let table = BTreeTable::from_sql(sql, 0);\n        let error = table.unwrap_err();\n        assert!(\n            matches!(error, LimboError::ParseError(e) if e.contains(\"table \\\"t1\\\" has more than one primary key\"))\n        );\n        Ok(())\n    }\n\n    #[test]\n    pub fn test_conflicting_on_conflict_unique_rejected() -> Result<()> {\n        let sql =\n            r#\"CREATE TABLE t1 (a UNIQUE ON CONFLICT FAIL, b, UNIQUE(a) ON CONFLICT IGNORE);\"#;\n        let table = BTreeTable::from_sql(sql, 0);\n        let error = table.unwrap_err();\n        assert!(\n            matches!(error, LimboError::ParseError(e) if e.contains(\"conflicting ON CONFLICT clauses\"))\n        );\n        Ok(())\n    }\n\n    #[test]\n    pub fn test_conflicting_on_conflict_composite_unique_rejected() -> Result<()> {\n        let sql = r#\"CREATE TABLE t1 (a, b, UNIQUE(a, b) ON CONFLICT FAIL, UNIQUE(a, b) ON CONFLICT IGNORE);\"#;\n        let table = BTreeTable::from_sql(sql, 0);\n        let error = table.unwrap_err();\n        assert!(\n            matches!(error, LimboError::ParseError(e) if e.contains(\"conflicting ON CONFLICT clauses\"))\n        );\n        Ok(())\n    }\n\n    #[test]\n    pub fn test_same_on_conflict_unique_allowed() -> Result<()> {\n        let sql = r#\"CREATE TABLE t1 (a UNIQUE ON CONFLICT FAIL, b, UNIQUE(a) ON CONFLICT FAIL);\"#;\n        assert!(BTreeTable::from_sql(sql, 0).is_ok());\n        Ok(())\n    }\n\n    #[test]\n    pub fn test_one_on_conflict_unique_allowed() -> Result<()> {\n        let sql = r#\"CREATE TABLE t1 (a UNIQUE ON CONFLICT FAIL, b, UNIQUE(a));\"#;\n        assert!(BTreeTable::from_sql(sql, 0).is_ok());\n        Ok(())\n    }\n\n    #[test]\n    pub fn test_primary_key_separate_single() -> Result<()> {\n        let sql = r#\"CREATE TABLE t1 (a INTEGER, b TEXT, c REAL, PRIMARY KEY(a desc));\"#;\n        let table = BTreeTable::from_sql(sql, 0)?;\n        let column = table.get_column(\"a\").unwrap().1;\n        assert!(column.primary_key(), \"column 'a' should be a primary key\");\n        let column = table.get_column(\"b\").unwrap().1;\n        assert!(\n            !column.primary_key(),\n            \"column 'b' shouldn't be a primary key\"\n        );\n        let column = table.get_column(\"c\").unwrap().1;\n        assert!(\n            !column.primary_key(),\n            \"column 'c' shouldn't be a primary key\"\n        );\n        assert_eq!(\n            vec![(\"a\".to_string(), SortOrder::Desc)],\n            table.primary_key_columns,\n            \"primary key column names should be ['a']\"\n        );\n        Ok(())\n    }\n\n    #[test]\n    pub fn test_primary_key_separate_multiple() -> Result<()> {\n        let sql = r#\"CREATE TABLE t1 (a INTEGER, b TEXT, c REAL, PRIMARY KEY(a, b desc));\"#;\n        let table = BTreeTable::from_sql(sql, 0)?;\n        let column = table.get_column(\"a\").unwrap().1;\n        assert!(column.primary_key(), \"column 'a' should be a primary key\");\n        let column = table.get_column(\"b\").unwrap().1;\n        assert!(column.primary_key(), \"column 'b' shouldn be a primary key\");\n        let column = table.get_column(\"c\").unwrap().1;\n        assert!(\n            !column.primary_key(),\n            \"column 'c' shouldn't be a primary key\"\n        );\n        assert_eq!(\n            vec![\n                (\"a\".to_string(), SortOrder::Asc),\n                (\"b\".to_string(), SortOrder::Desc)\n            ],\n            table.primary_key_columns,\n            \"primary key column names should be ['a', 'b']\"\n        );\n        Ok(())\n    }\n\n    #[test]\n    pub fn test_primary_key_separate_single_quoted() -> Result<()> {\n        let sql = r#\"CREATE TABLE t1 (a INTEGER, b TEXT, c REAL, PRIMARY KEY('a'));\"#;\n        let table = BTreeTable::from_sql(sql, 0)?;\n        let column = table.get_column(\"a\").unwrap().1;\n        assert!(column.primary_key(), \"column 'a' should be a primary key\");\n        let column = table.get_column(\"b\").unwrap().1;\n        assert!(\n            !column.primary_key(),\n            \"column 'b' shouldn't be a primary key\"\n        );\n        let column = table.get_column(\"c\").unwrap().1;\n        assert!(\n            !column.primary_key(),\n            \"column 'c' shouldn't be a primary key\"\n        );\n        assert_eq!(\n            vec![(\"a\".to_string(), SortOrder::Asc)],\n            table.primary_key_columns,\n            \"primary key column names should be ['a']\"\n        );\n        Ok(())\n    }\n    #[test]\n    pub fn test_primary_key_separate_single_doubly_quoted() -> Result<()> {\n        let sql = r#\"CREATE TABLE t1 (a INTEGER, b TEXT, c REAL, PRIMARY KEY(\"a\"));\"#;\n        let table = BTreeTable::from_sql(sql, 0)?;\n        let column = table.get_column(\"a\").unwrap().1;\n        assert!(column.primary_key(), \"column 'a' should be a primary key\");\n        let column = table.get_column(\"b\").unwrap().1;\n        assert!(\n            !column.primary_key(),\n            \"column 'b' shouldn't be a primary key\"\n        );\n        let column = table.get_column(\"c\").unwrap().1;\n        assert!(\n            !column.primary_key(),\n            \"column 'c' shouldn't be a primary key\"\n        );\n        assert_eq!(\n            vec![(\"a\".to_string(), SortOrder::Asc)],\n            table.primary_key_columns,\n            \"primary key column names should be ['a']\"\n        );\n        Ok(())\n    }\n\n    #[test]\n    pub fn test_default_value() -> Result<()> {\n        let sql = r#\"CREATE TABLE t1 (a INTEGER DEFAULT 23);\"#;\n        let table = BTreeTable::from_sql(sql, 0)?;\n        let column = table.get_column(\"a\").unwrap().1;\n        let default = column.default.clone().unwrap();\n        assert_eq!(default.to_string(), \"23\");\n        Ok(())\n    }\n\n    #[test]\n    pub fn test_col_notnull() -> Result<()> {\n        let sql = r#\"CREATE TABLE t1 (a INTEGER NOT NULL);\"#;\n        let table = BTreeTable::from_sql(sql, 0)?;\n        let column = table.get_column(\"a\").unwrap().1;\n        assert!(column.notnull());\n        Ok(())\n    }\n\n    #[test]\n    pub fn test_col_notnull_negative() -> Result<()> {\n        let sql = r#\"CREATE TABLE t1 (a INTEGER);\"#;\n        let table = BTreeTable::from_sql(sql, 0)?;\n        let column = table.get_column(\"a\").unwrap().1;\n        assert!(!column.notnull());\n        Ok(())\n    }\n\n    #[test]\n    pub fn test_col_type_string_integer() -> Result<()> {\n        let sql = r#\"CREATE TABLE t1 (a InTeGeR);\"#;\n        let table = BTreeTable::from_sql(sql, 0)?;\n        let column = table.get_column(\"a\").unwrap().1;\n        assert_eq!(column.ty_str, \"InTeGeR\");\n        Ok(())\n    }\n\n    #[test]\n    pub fn test_sqlite_schema() {\n        let expected = r#\"CREATE TABLE sqlite_schema (type TEXT, name TEXT, tbl_name TEXT, rootpage INT, sql TEXT)\"#;\n        let actual = sqlite_schema_table().to_sql();\n        assert_eq!(expected, actual);\n    }\n\n    #[test]\n    pub fn test_special_column_names() -> Result<()> {\n        let tests = [\n            (\"foobar\", \"CREATE TABLE t (foobar TEXT)\"),\n            (\"_table_name3\", \"CREATE TABLE t (_table_name3 TEXT)\"),\n            (\"special name\", \"CREATE TABLE t ([special name] TEXT)\"),\n            (\"foo&bar\", \"CREATE TABLE t ([foo&bar] TEXT)\"),\n            (\" name\", \"CREATE TABLE t ([ name] TEXT)\"),\n        ];\n\n        for (input_column_name, expected_sql) in tests {\n            let sql = format!(\"CREATE TABLE t ([{input_column_name}] TEXT)\");\n            let actual = BTreeTable::from_sql(&sql, 0)?.to_sql();\n            assert_eq!(expected_sql, actual);\n        }\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_special_table_names_are_quoted_in_to_sql() -> Result<()> {\n        let tests = [\n            (\n                r#\"CREATE TABLE \"t t\" (x TEXT)\"#,\n                r#\"CREATE TABLE \"t t\" (x TEXT)\"#,\n            ),\n            (\n                r#\"CREATE TABLE \"123table\" (x TEXT)\"#,\n                r#\"CREATE TABLE \"123table\" (x TEXT)\"#,\n            ),\n            (\n                r#\"CREATE TABLE \"t\"\"t\" (x TEXT)\"#,\n                r#\"CREATE TABLE \"t\"\"t\" (x TEXT)\"#,\n            ),\n        ];\n\n        for (input_sql, expected_sql) in tests {\n            let actual = BTreeTable::from_sql(input_sql, 0)?.to_sql();\n            assert_eq!(actual, expected_sql);\n        }\n\n        Ok(())\n    }\n\n    #[test]\n    #[should_panic]\n    fn test_automatic_index_single_column() {\n        // Without composite primary keys, we should not have an automatic index on a primary key that is a rowid alias\n        let sql = r#\"CREATE TABLE t1 (a INTEGER PRIMARY KEY, b TEXT);\"#;\n        let table = BTreeTable::from_sql(sql, 0).unwrap();\n        let _index = Index::automatic_from_primary_key(\n            &table,\n            (\"sqlite_autoindex_t1_1\".to_string(), 2),\n            1,\n            None,\n        )\n        .unwrap();\n    }\n\n    #[test]\n    fn test_automatic_index_composite_key() -> Result<()> {\n        let sql = r#\"CREATE TABLE t1 (a INTEGER, b TEXT, PRIMARY KEY(a, b));\"#;\n        let table = BTreeTable::from_sql(sql, 0)?;\n        let index = Index::automatic_from_primary_key(\n            &table,\n            (\"sqlite_autoindex_t1_1\".to_string(), 2),\n            2,\n            None,\n        )?;\n\n        assert_eq!(index.name, \"sqlite_autoindex_t1_1\");\n        assert_eq!(index.table_name, \"t1\");\n        assert_eq!(index.root_page, 2);\n        assert!(index.unique);\n        assert_eq!(index.columns.len(), 2);\n        assert_eq!(index.columns[0].name, \"a\");\n        assert_eq!(index.columns[1].name, \"b\");\n        assert!(matches!(index.columns[0].order, SortOrder::Asc));\n        assert!(matches!(index.columns[1].order, SortOrder::Asc));\n        Ok(())\n    }\n\n    #[test]\n    #[should_panic]\n    fn test_automatic_index_no_primary_key() {\n        let sql = r#\"CREATE TABLE t1 (a INTEGER, b TEXT);\"#;\n        let table = BTreeTable::from_sql(sql, 0).unwrap();\n        Index::automatic_from_primary_key(\n            &table,\n            (\"sqlite_autoindex_t1_1\".to_string(), 2),\n            1,\n            None,\n        )\n        .unwrap();\n    }\n\n    #[test]\n    fn test_automatic_index_nonexistent_column() {\n        // Create a table with a primary key column that doesn't exist in the table\n        let table = BTreeTable {\n            root_page: 0,\n            name: \"t1\".to_string(),\n            has_rowid: true,\n            is_strict: false,\n            has_autoincrement: false,\n            primary_key_columns: vec![(\"nonexistent\".to_string(), SortOrder::Asc)],\n            columns: vec![Column::new_default_integer(\n                Some(\"a\".to_string()),\n                \"INT\".to_string(),\n                None,\n            )],\n            unique_sets: vec![],\n            foreign_keys: vec![],\n            check_constraints: vec![],\n            rowid_alias_conflict_clause: None,\n        };\n\n        let result = Index::automatic_from_primary_key(\n            &table,\n            (\"sqlite_autoindex_t1_1\".to_string(), 2),\n            1,\n            None,\n        );\n        assert!(result.is_err());\n    }\n\n    #[test]\n    fn test_automatic_index_unique_column() -> Result<()> {\n        let sql = r#\"CREATE table t1 (x INTEGER, y INTEGER UNIQUE);\"#;\n        let table = BTreeTable::from_sql(sql, 0)?;\n        let index = Index::automatic_from_unique(\n            &table,\n            (\"sqlite_autoindex_t1_1\".to_string(), 2),\n            vec![(1, SortOrder::Asc)],\n            None,\n        )?;\n\n        assert_eq!(index.name, \"sqlite_autoindex_t1_1\");\n        assert_eq!(index.table_name, \"t1\");\n        assert_eq!(index.root_page, 2);\n        assert!(index.unique);\n        assert_eq!(index.columns.len(), 1);\n        assert_eq!(index.columns[0].name, \"y\");\n        assert!(matches!(index.columns[0].order, SortOrder::Asc));\n        Ok(())\n    }\n\n    #[test]\n    fn test_automatic_index_pkey_unique_column() -> Result<()> {\n        let sql = r#\"CREATE TABLE t1 (x PRIMARY KEY, y UNIQUE);\"#;\n        let table = BTreeTable::from_sql(sql, 0)?;\n        let indices = [\n            Index::automatic_from_primary_key(\n                &table,\n                (\"sqlite_autoindex_t1_1\".to_string(), 2),\n                1,\n                None,\n            )?,\n            Index::automatic_from_unique(\n                &table,\n                (\"sqlite_autoindex_t1_2\".to_string(), 3),\n                vec![(1, SortOrder::Asc)],\n                None,\n            )?,\n        ];\n\n        assert_eq!(indices[0].name, \"sqlite_autoindex_t1_1\");\n        assert_eq!(indices[0].table_name, \"t1\");\n        assert_eq!(indices[0].root_page, 2);\n        assert!(indices[0].unique);\n        assert_eq!(indices[0].columns.len(), 1);\n        assert_eq!(indices[0].columns[0].name, \"x\");\n        assert!(matches!(indices[0].columns[0].order, SortOrder::Asc));\n\n        assert_eq!(indices[1].name, \"sqlite_autoindex_t1_2\");\n        assert_eq!(indices[1].table_name, \"t1\");\n        assert_eq!(indices[1].root_page, 3);\n        assert!(indices[1].unique);\n        assert_eq!(indices[1].columns.len(), 1);\n        assert_eq!(indices[1].columns[0].name, \"y\");\n        assert!(matches!(indices[1].columns[0].order, SortOrder::Asc));\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_automatic_index_pkey_many_unique_columns() -> Result<()> {\n        let sql = r#\"CREATE TABLE t1 (a PRIMARY KEY, b UNIQUE, c, d, UNIQUE(c, d));\"#;\n        let table = BTreeTable::from_sql(sql, 0)?;\n        let auto_indices = [\n            (\"sqlite_autoindex_t1_1\".to_string(), 2),\n            (\"sqlite_autoindex_t1_2\".to_string(), 3),\n            (\"sqlite_autoindex_t1_3\".to_string(), 4),\n        ];\n        let indices = vec![\n            Index::automatic_from_primary_key(\n                &table,\n                (\"sqlite_autoindex_t1_1\".to_string(), 2),\n                1,\n                None,\n            )?,\n            Index::automatic_from_unique(\n                &table,\n                (\"sqlite_autoindex_t1_2\".to_string(), 3),\n                vec![(1, SortOrder::Asc)],\n                None,\n            )?,\n            Index::automatic_from_unique(\n                &table,\n                (\"sqlite_autoindex_t1_3\".to_string(), 4),\n                vec![(2, SortOrder::Asc), (3, SortOrder::Asc)],\n                None,\n            )?,\n        ];\n\n        assert!(indices.len() == auto_indices.len());\n\n        for (pos, index) in indices.iter().enumerate() {\n            let (index_name, root_page) = &auto_indices[pos];\n            assert_eq!(index.name, *index_name);\n            assert_eq!(index.table_name, \"t1\");\n            assert_eq!(index.root_page, *root_page);\n            assert!(index.unique);\n\n            if pos == 0 {\n                assert_eq!(index.columns.len(), 1);\n                assert_eq!(index.columns[0].name, \"a\");\n            } else if pos == 1 {\n                assert_eq!(index.columns.len(), 1);\n                assert_eq!(index.columns[0].name, \"b\");\n            } else if pos == 2 {\n                assert_eq!(index.columns.len(), 2);\n                assert_eq!(index.columns[0].name, \"c\");\n                assert_eq!(index.columns[1].name, \"d\");\n            }\n\n            assert!(matches!(index.columns[0].order, SortOrder::Asc));\n        }\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_automatic_index_unique_set_dedup() -> Result<()> {\n        let sql = r#\"CREATE TABLE t1 (a, b, UNIQUE(a, b), UNIQUE(a, b));\"#;\n        let table = BTreeTable::from_sql(sql, 0)?;\n        let index = Index::automatic_from_unique(\n            &table,\n            (\"sqlite_autoindex_t1_1\".to_string(), 2),\n            vec![(0, SortOrder::Asc), (1, SortOrder::Asc)],\n            None,\n        )?;\n\n        assert_eq!(index.name, \"sqlite_autoindex_t1_1\");\n        assert_eq!(index.table_name, \"t1\");\n        assert_eq!(index.root_page, 2);\n        assert!(index.unique);\n        assert_eq!(index.columns.len(), 2);\n        assert_eq!(index.columns[0].name, \"a\");\n        assert!(matches!(index.columns[0].order, SortOrder::Asc));\n        assert_eq!(index.columns[1].name, \"b\");\n        assert!(matches!(index.columns[1].order, SortOrder::Asc));\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_automatic_index_primary_key_is_unique() -> Result<()> {\n        let sql = r#\"CREATE TABLE t1 (a primary key unique);\"#;\n        let table = BTreeTable::from_sql(sql, 0)?;\n        let index = Index::automatic_from_primary_key(\n            &table,\n            (\"sqlite_autoindex_t1_1\".to_string(), 2),\n            1,\n            None,\n        )?;\n\n        assert_eq!(index.name, \"sqlite_autoindex_t1_1\");\n        assert_eq!(index.table_name, \"t1\");\n        assert_eq!(index.root_page, 2);\n        assert!(index.unique);\n        assert_eq!(index.columns.len(), 1);\n        assert_eq!(index.columns[0].name, \"a\");\n        assert!(matches!(index.columns[0].order, SortOrder::Asc));\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_automatic_index_primary_key_is_unique_and_composite() -> Result<()> {\n        let sql = r#\"CREATE TABLE t1 (a, b, PRIMARY KEY(a, b), UNIQUE(a, b));\"#;\n        let table = BTreeTable::from_sql(sql, 0)?;\n        let index = Index::automatic_from_primary_key(\n            &table,\n            (\"sqlite_autoindex_t1_1\".to_string(), 2),\n            2,\n            None,\n        )?;\n\n        assert_eq!(index.name, \"sqlite_autoindex_t1_1\");\n        assert_eq!(index.table_name, \"t1\");\n        assert_eq!(index.root_page, 2);\n        assert!(index.unique);\n        assert_eq!(index.columns.len(), 2);\n        assert_eq!(index.columns[0].name, \"a\");\n        assert_eq!(index.columns[1].name, \"b\");\n        assert!(matches!(index.columns[0].order, SortOrder::Asc));\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_strict_table_to_sql() -> Result<()> {\n        let sql = r#\"CREATE TABLE test_strict (id INTEGER, name TEXT) STRICT\"#;\n        let table = BTreeTable::from_sql(sql, 0)?;\n\n        // Verify the table is marked as strict\n        assert!(table.is_strict);\n\n        // Verify that to_sql() includes the STRICT keyword\n        let reconstructed_sql = table.to_sql();\n        assert!(\n            reconstructed_sql.contains(\"STRICT\"),\n            \"Reconstructed SQL should contain STRICT keyword: {reconstructed_sql}\"\n        );\n        assert_eq!(\n            reconstructed_sql,\n            \"CREATE TABLE test_strict (id INTEGER, name TEXT) STRICT\"\n        );\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_non_strict_table_to_sql() -> Result<()> {\n        let sql = r#\"CREATE TABLE test_normal (id INTEGER, name TEXT)\"#;\n        let table = BTreeTable::from_sql(sql, 0)?;\n\n        // Verify the table is NOT marked as strict\n        assert!(!table.is_strict);\n\n        // Verify that to_sql() does NOT include the STRICT keyword\n        let reconstructed_sql = table.to_sql();\n        assert!(\n            !reconstructed_sql.contains(\"STRICT\"),\n            \"Non-strict table SQL should not contain STRICT keyword: {reconstructed_sql}\"\n        );\n        assert_eq!(\n            reconstructed_sql,\n            \"CREATE TABLE test_normal (id INTEGER, name TEXT)\"\n        );\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_automatic_index_unique_and_a_pk() -> Result<()> {\n        let sql = r#\"CREATE TABLE t1 (a NUMERIC UNIQUE UNIQUE,  b TEXT PRIMARY KEY)\"#;\n        let table = BTreeTable::from_sql(sql, 0)?;\n        let mut indexes = vec![\n            Index::automatic_from_unique(\n                &table,\n                (\"sqlite_autoindex_t1_1\".to_string(), 2),\n                vec![(0, SortOrder::Asc)],\n                None,\n            )?,\n            Index::automatic_from_primary_key(\n                &table,\n                (\"sqlite_autoindex_t1_2\".to_string(), 3),\n                1,\n                None,\n            )?,\n        ];\n\n        assert!(indexes.len() == 2);\n        let index = indexes.pop().unwrap();\n        assert_eq!(index.name, \"sqlite_autoindex_t1_2\");\n        assert_eq!(index.table_name, \"t1\");\n        assert_eq!(index.root_page, 3);\n        assert!(index.unique);\n        assert_eq!(index.columns.len(), 1);\n        assert_eq!(index.columns[0].name, \"b\");\n        assert!(matches!(index.columns[0].order, SortOrder::Asc));\n\n        let index = indexes.pop().unwrap();\n        assert_eq!(index.name, \"sqlite_autoindex_t1_1\");\n        assert_eq!(index.table_name, \"t1\");\n        assert_eq!(index.root_page, 2);\n        assert!(index.unique);\n        assert_eq!(index.columns.len(), 1);\n        assert_eq!(index.columns[0].name, \"a\");\n        assert!(matches!(index.columns[0].order, SortOrder::Asc));\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "core/series.rs",
    "content": "use crate::sync::Arc;\n\nuse turso_ext::{\n    Connection, ConstraintInfo, ConstraintOp, ConstraintUsage, ExtensionApi, IndexInfo,\n    OrderByInfo, ResultCode, VTabCursor, VTabKind, VTabModule, VTabModuleDerive, VTable, Value,\n};\n\npub fn register_extension(ext_api: &mut ExtensionApi) {\n    // FIXME: Add macro magic to register functions automatically.\n    unsafe {\n        GenerateSeriesVTabModule::register_GenerateSeriesVTabModule(ext_api);\n    }\n}\n\nmacro_rules! extract_arg_integer {\n    ($args:expr, $idx:expr) => {\n        $args.get($idx).and_then(|v| v.to_integer())\n    };\n}\n\n/// A virtual table that generates a sequence of integers\n#[derive(Debug, VTabModuleDerive, Default)]\nstruct GenerateSeriesVTabModule;\n\nimpl VTabModule for GenerateSeriesVTabModule {\n    type Table = GenerateSeriesTable;\n    const NAME: &'static str = \"generate_series\";\n    const VTAB_KIND: VTabKind = VTabKind::TableValuedFunction;\n\n    fn create(_args: &[Value]) -> Result<(String, Self::Table), ResultCode> {\n        let schema = \"CREATE TABLE generate_series (\n            value INTEGER,\n            start INTEGER HIDDEN,\n            stop INTEGER HIDDEN,\n            step INTEGER HIDDEN\n        )\"\n        .into();\n        Ok((schema, GenerateSeriesTable {}))\n    }\n}\n\nstruct GenerateSeriesTable {}\n\nimpl VTable for GenerateSeriesTable {\n    type Cursor = GenerateSeriesCursor;\n    type Error = ResultCode;\n\n    fn open(&self, _conn: Option<Arc<Connection>>) -> Result<Self::Cursor, Self::Error> {\n        Ok(GenerateSeriesCursor {\n            start: 0,\n            stop: 0,\n            step: 0,\n            current: 0,\n        })\n    }\n\n    fn best_index(\n        constraints: &[ConstraintInfo],\n        _order_by: &[OrderByInfo],\n    ) -> Result<IndexInfo, ResultCode> {\n        const START_COLUMN_INDEX: u32 = 1;\n        const STEP_COLUMN_INDEX: u32 = 3;\n\n        // The bits of `idx_num` are used to indicate which arguments are available to the filter method:\n        // - Bit 0 set -> 'start' is available\n        // - Bit 1 set -> 'stop' is available\n        // - Bit 2 set -> 'step' is available\n        let mut idx_num = 0;\n        let mut positions = [None; 4]; // maps column index to constraint position\n        let mut start_exists = false;\n        let mut usable = true;\n\n        for (i, c) in constraints.iter().enumerate() {\n            if c.column_index == START_COLUMN_INDEX && c.op == ConstraintOp::Eq {\n                start_exists = true;\n            }\n            if c.column_index >= START_COLUMN_INDEX && c.column_index <= STEP_COLUMN_INDEX {\n                if !c.usable {\n                    usable = false;\n                } else if c.op == ConstraintOp::Eq {\n                    let bit = 1 << (c.column_index - 1);\n                    idx_num |= bit;\n                    positions[c.column_index as usize] = Some(i);\n                }\n            }\n        }\n\n        if !start_exists {\n            return Err(ResultCode::InvalidArgs);\n        }\n        if !usable {\n            return Err(ResultCode::ConstraintViolation);\n        }\n\n        // Assign argv indexes contiguously\n        let mut argv_idx = 1;\n        let mut argv_indexes = [None; 4];\n\n        for (i, pos) in positions.iter().enumerate() {\n            if pos.is_some() {\n                argv_indexes[i] = Some(argv_idx);\n                argv_idx += 1;\n            }\n        }\n\n        let constraint_usages = constraints\n            .iter()\n            .enumerate()\n            .map(|(idx, c)| {\n                let argv_index = positions.get(c.column_index as usize).and_then(|&pos| {\n                    pos.filter(|&i| i == idx)\n                        .and_then(|_| argv_indexes[c.column_index as usize])\n                });\n\n                ConstraintUsage {\n                    argv_index,\n                    omit: argv_index.is_some(),\n                }\n            })\n            .collect();\n\n        Ok(IndexInfo {\n            idx_num,\n            idx_str: Some(idx_num.to_string()),\n            constraint_usages,\n            ..Default::default()\n        })\n    }\n}\n\n/// The cursor for iterating over the generated sequence\n#[derive(Debug)]\nstruct GenerateSeriesCursor {\n    start: i64,\n    stop: i64,\n    step: i64,\n    current: i64,\n}\n\nimpl GenerateSeriesCursor {\n    /// Returns true if this is an ascending series (positive step) but start > stop\n    fn is_invalid_ascending_series(&self) -> bool {\n        self.step > 0 && self.start > self.stop\n    }\n\n    /// Returns true if this is a descending series (negative step) but start < stop\n    fn is_invalid_descending_series(&self) -> bool {\n        self.step < 0 && self.start < self.stop\n    }\n\n    /// Returns true if this is an invalid range that should produce an empty series\n    fn is_invalid_range(&self) -> bool {\n        self.is_invalid_ascending_series() || self.is_invalid_descending_series()\n    }\n\n    /// Returns true if we would exceed the stop value in the current direction\n    fn would_exceed(&self) -> bool {\n        (self.step > 0 && self.current.saturating_add(self.step) > self.stop)\n            || (self.step < 0 && self.current.saturating_add(self.step) < self.stop)\n    }\n}\n\nimpl VTabCursor for GenerateSeriesCursor {\n    type Error = ResultCode;\n\n    fn filter(&mut self, args: &[Value], idx_info: Option<(&str, i32)>) -> ResultCode {\n        let mut start: Option<i64> = None;\n        let mut stop: Option<i64> = None;\n        let mut step = 1;\n        // SQLite default for stop when it is omitted\n        const DEFAULT_STOP_OMITTED: Option<i64> = Some(u32::MAX as i64);\n\n        if let Some((_, idx_num)) = idx_info {\n            let mut arg_idx = 0;\n            // For the semantics of `idx_num`, see the comment in the `best_index` method.\n            if idx_num & 1 != 0 {\n                start = extract_arg_integer!(args, arg_idx);\n                arg_idx += 1;\n            }\n            if idx_num & 2 != 0 {\n                stop = extract_arg_integer!(args, arg_idx);\n                arg_idx += 1;\n            } else {\n                stop = DEFAULT_STOP_OMITTED;\n            }\n            if idx_num & 4 != 0 {\n                step = args\n                    .get(arg_idx)\n                    .map(|v| v.to_integer().unwrap_or(1))\n                    .unwrap_or(1);\n            }\n        }\n\n        if start.is_none() {\n            return ResultCode::InvalidArgs;\n        }\n        if stop.is_none() {\n            return ResultCode::EOF; // Sqlite returns an empty series for wacky args\n        }\n\n        // Convert zero step to 1, matching SQLite behavior\n        if step == 0 {\n            step = 1;\n        }\n\n        self.start = start.unwrap();\n        self.step = step;\n        self.stop = stop.unwrap();\n\n        // Set initial value based on range validity\n        // For invalid input SQLite returns an empty series\n        self.current = if self.is_invalid_range() {\n            return ResultCode::EOF;\n        } else {\n            self.start\n        };\n\n        ResultCode::OK\n    }\n\n    fn next(&mut self) -> ResultCode {\n        if self.eof() {\n            return ResultCode::EOF;\n        }\n\n        self.current = match self.current.checked_add(self.step) {\n            Some(val) => val,\n            None => {\n                return ResultCode::EOF;\n            }\n        };\n\n        ResultCode::OK\n    }\n\n    fn eof(&self) -> bool {\n        // Check for invalid ranges (empty series) first\n        if self.is_invalid_range() {\n            return true;\n        }\n\n        // Check if we would exceed the stop value in the current direction\n        if self.would_exceed() {\n            return true;\n        }\n\n        if self.current == i64::MAX && self.step > 0 {\n            return true;\n        }\n\n        if self.current == i64::MIN && self.step < 0 {\n            return true;\n        }\n\n        false\n    }\n\n    fn column(&self, idx: u32) -> Result<Value, Self::Error> {\n        Ok(match idx {\n            0 => Value::from_integer(self.current),\n            1 => Value::from_integer(self.start),\n            2 => Value::from_integer(self.stop),\n            3 => Value::from_integer(self.step),\n            _ => Value::null(),\n        })\n    }\n\n    fn rowid(&self) -> i64 {\n        let sub = self.current.saturating_sub(self.start);\n\n        // Handle overflow in rowid calculation by capping at MAX/MIN\n        match sub.checked_div(self.step) {\n            Some(val) => val.saturating_add(1),\n            None => {\n                if self.step > 0 {\n                    i64::MAX\n                } else {\n                    i64::MIN\n                }\n            }\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use quickcheck::{Arbitrary, Gen};\n    use quickcheck_macros::quickcheck;\n\n    #[derive(Debug, Clone)]\n    struct Series {\n        start: i64,\n        stop: i64,\n        step: i64,\n    }\n\n    impl Arbitrary for Series {\n        fn arbitrary(g: &mut Gen) -> Self {\n            let mut start = i64::arbitrary(g);\n            let mut stop = i64::arbitrary(g);\n            let mut iters = 0;\n            while stop.checked_sub(start).is_none() {\n                start = i64::arbitrary(g);\n                stop = i64::arbitrary(g);\n                iters += 1;\n                if iters > 1000 {\n                    panic!(\"Failed to generate valid range after 1000 attempts\");\n                }\n            }\n            // step should be a reasonable value proportional to the range\n            let mut divisor = i8::arbitrary(g);\n            if divisor == 0 {\n                divisor = 1;\n            }\n            let step = (stop - start).saturating_abs() / divisor as i64;\n            Series { start, stop, step }\n        }\n    }\n    // Helper function to collect all values from a cursor, returns Result with error code\n    fn collect_series(series: Series) -> Result<Vec<i64>, ResultCode> {\n        let tbl = GenerateSeriesTable {};\n        let mut cursor = tbl.open(None)?;\n\n        // Create args array for filter\n        let args = vec![\n            Value::from_integer(series.start),\n            Value::from_integer(series.stop),\n            Value::from_integer(series.step),\n        ];\n\n        // Initialize cursor through filter\n        match cursor.filter(&args, Some((\"idx\", 1 | 2 | 4))) {\n            ResultCode::OK => (),\n            ResultCode::EOF => return Ok(vec![]),\n            err => return Err(err),\n        }\n\n        let mut values = Vec::new();\n        loop {\n            values.push(cursor.column(0)?.to_integer().unwrap());\n            if values.len() > 1000 {\n                panic!(\n                    \"Generated more than 1000 values, expected this many: {:?}\",\n                    (series.stop - series.start) / series.step + 1\n                );\n            }\n            match cursor.next() {\n                ResultCode::OK => (),\n                ResultCode::EOF => break,\n                err => return Err(err),\n            }\n        }\n        Ok(values)\n    }\n\n    #[quickcheck]\n    /// Test that the series length is correct\n    /// Example:\n    /// start = 1, stop = 10, step = 1\n    /// expected length = 10\n    fn prop_series_length(series: Series) {\n        let start = series.start;\n        let stop = series.stop;\n        let step = series.step;\n        let values = collect_series(series.clone()).unwrap_or_else(|e| {\n            panic!(\"Failed to generate series for start={start}, stop={stop}, step={step}: {e:?}\")\n        });\n\n        if series_is_invalid_or_empty(&series) {\n            assert!(\n                values.is_empty(),\n                \"Series should be empty for invalid range: start={start}, stop={stop}, step={step}, got {values:?}\"\n            );\n        } else {\n            let expected_len = series_expected_length(&series);\n            assert_eq!(\n                values.len(),\n                expected_len,\n                \"Series length mismatch for start={}, stop={}, step={}: expected {}, got {}, values: {:?}\",\n                start,\n                stop,\n                step,\n                expected_len,\n                values.len(),\n                values\n            );\n        }\n    }\n\n    #[quickcheck]\n    /// Test that the series is monotonically increasing\n    /// Example:\n    /// start = 1, stop = 10, step = 1\n    /// expected series = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n    fn prop_series_monotonic_increasing_or_decreasing(series: Series) {\n        let start = series.start;\n        let stop = series.stop;\n        let step = series.step;\n\n        let values = collect_series(series.clone()).unwrap_or_else(|e| {\n            panic!(\"Failed to generate series for start={start}, stop={stop}, step={step}: {e:?}\")\n        });\n\n        if series_is_invalid_or_empty(&series) {\n            assert!(\n                values.is_empty(),\n                \"Series should be empty for invalid range: start={start}, stop={stop}, step={step}\"\n            );\n        } else {\n            assert!(\n                values\n                    .windows(2)\n                    .all(|w| if step > 0 { w[0] < w[1] } else { w[0] > w[1] }),\n                \"Series not monotonically {}: {:?} (start={}, stop={}, step={})\",\n                if step > 0 { \"increasing\" } else { \"decreasing\" },\n                values,\n                start,\n                stop,\n                step\n            );\n        }\n    }\n\n    #[quickcheck]\n    /// Test that the series step size is consistent\n    /// Example:\n    /// start = 1, stop = 10, step = 1\n    /// expected step size = 1\n    fn prop_series_step_size(series: Series) {\n        let start = series.start;\n        let stop = series.stop;\n        let step = series.step;\n\n        let values = collect_series(series.clone()).unwrap_or_else(|e| {\n            panic!(\"Failed to generate series for start={start}, stop={stop}, step={step}: {e:?}\")\n        });\n\n        if series_is_invalid_or_empty(&series) {\n            assert!(\n                values.is_empty(),\n                \"Series should be empty for invalid range: start={start}, stop={stop}, step={step}\"\n            );\n        } else if !values.is_empty() {\n            assert!(\n                values\n                    .windows(2)\n                    .all(|w| (w[1].saturating_sub(w[0])).abs() == step.abs()),\n                \"Step size not consistent: {:?} (expected step size: {})\",\n                values\n                    .windows(2)\n                    .map(|w| w[1].saturating_sub(w[0]))\n                    .collect::<Vec<_>>(),\n                step.abs()\n            );\n        }\n    }\n\n    #[quickcheck]\n    /// Test that the series bounds are correct\n    /// Example:\n    /// start = 1, stop = 10, step = 1\n    /// expected bounds = [1, 10]\n    fn prop_series_bounds(series: Series) {\n        let start = series.start;\n        let stop = series.stop;\n        let step = series.step;\n\n        let values = collect_series(series.clone()).unwrap_or_else(|e| {\n            panic!(\"Failed to generate series for start={start}, stop={stop}, step={step}: {e:?}\")\n        });\n\n        if series_is_invalid_or_empty(&series) {\n            assert!(\n                values.is_empty(),\n                \"Series should be empty for invalid range: start={start}, stop={stop}, step={step}\"\n            );\n        } else if !values.is_empty() {\n            assert_eq!(\n                values.first(),\n                Some(&start),\n                \"Series doesn't start with start value: {values:?} (expected start: {start})\"\n            );\n            assert!(\n                values.last().is_none_or(|&last| if step > 0 {\n                    last <= stop\n                } else {\n                    last >= stop\n                }),\n                \"Series exceeds stop value: {values:?} (stop: {stop})\"\n            );\n        }\n    }\n\n    #[test]\n\n    fn test_series_empty_positive_step() {\n        let values = collect_series(Series {\n            start: 10,\n            stop: 5,\n            step: 1,\n        })\n        .expect(\"Failed to generate series\");\n        assert!(\n            values.is_empty(),\n            \"Series should be empty when start > stop with positive step\"\n        );\n    }\n\n    #[test]\n    fn test_series_empty_negative_step() {\n        let values = collect_series(Series {\n            start: 5,\n            stop: 10,\n            step: -1,\n        })\n        .expect(\"Failed to generate series\");\n        assert!(\n            values.is_empty(),\n            \"Series should be empty when start < stop with negative step\"\n        );\n    }\n\n    #[test]\n    fn test_series_single_element() {\n        let values = collect_series(Series {\n            start: 5,\n            stop: 5,\n            step: 1,\n        })\n        .expect(\"Failed to generate single element series\");\n        assert_eq!(\n            values,\n            vec![5],\n            \"Single element series should contain only the start value\"\n        );\n    }\n\n    #[test]\n    fn test_zero_step_is_interpreted_as_1() {\n        let values = collect_series(Series {\n            start: 1,\n            stop: 10,\n            step: 0,\n        })\n        .expect(\"Failed to generate series\");\n        assert_eq!(\n            values,\n            vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10],\n            \"Zero step should be interpreted as 1\"\n        );\n    }\n\n    #[test]\n    fn test_invalid_inputs() {\n        // Test that invalid ranges return empty series instead of errors\n        let values = collect_series(Series {\n            start: 10,\n            stop: 1,\n            step: 1,\n        })\n        .expect(\"Failed to generate series\");\n        assert!(\n            values.is_empty(),\n            \"Invalid positive range should return empty series, got {values:?}\"\n        );\n\n        let values = collect_series(Series {\n            start: 1,\n            stop: 10,\n            step: -1,\n        })\n        .expect(\"Failed to generate series\");\n        assert!(\n            values.is_empty(),\n            \"Invalid negative range should return empty series\"\n        );\n\n        // Test that extreme ranges return empty series\n        let values = collect_series(Series {\n            start: i64::MAX,\n            stop: i64::MIN,\n            step: 1,\n        })\n        .expect(\"Failed to generate series\");\n        assert!(\n            values.is_empty(),\n            \"Extreme range (MAX to MIN) should return empty series\"\n        );\n\n        let values = collect_series(Series {\n            start: i64::MIN,\n            stop: i64::MAX,\n            step: -1,\n        })\n        .expect(\"Failed to generate series\");\n        assert!(\n            values.is_empty(),\n            \"Extreme range (MIN to MAX) should return empty series\"\n        );\n    }\n\n    #[quickcheck]\n    /// Test that rowid is always monotonically increasing regardless of step direction\n    fn prop_series_rowid_monotonic(series: Series) {\n        let start = series.start;\n        let stop = series.stop;\n        let step = series.step;\n        let tbl = GenerateSeriesTable {};\n        let mut cursor = tbl.open(None).unwrap();\n\n        let args = vec![\n            Value::from_integer(start),\n            Value::from_integer(stop),\n            Value::from_integer(step),\n        ];\n\n        // Initialize cursor through filter\n        cursor.filter(&args, Some((\"idx\", 1 | 2 | 4)));\n\n        let mut rowids = vec![];\n        while !cursor.eof() {\n            let cur_rowid = cursor.rowid();\n            match cursor.next() {\n                ResultCode::OK => rowids.push(cur_rowid),\n                ResultCode::EOF => break,\n                err => {\n                    panic!(\"Unexpected error {err:?} for start={start}, stop={stop}, step={step}\")\n                }\n            }\n        }\n\n        assert!(\n            rowids.windows(2).all(|w| w[1] == w[0] + 1),\n            \"Rowids not monotonically increasing: {rowids:?} (start={start}, stop={stop}, step={step})\"\n        );\n    }\n\n    #[quickcheck]\n    /// Test that empty series are handled consistently\n    fn prop_series_empty(series: Series) {\n        let start = series.start;\n        let stop = series.stop;\n        let step = series.step;\n\n        let values = collect_series(series.clone()).unwrap_or_else(|e| {\n            panic!(\"Failed to generate series for start={start}, stop={stop}, step={step}: {e:?}\")\n        });\n\n        if series_is_invalid_or_empty(&series) {\n            assert!(\n                values.is_empty(),\n                \"Series should be empty for invalid range: start={start}, stop={stop}, step={step}\"\n            );\n        } else if start == stop {\n            assert_eq!(\n                values,\n                vec![start],\n                \"Series with start==stop should contain exactly one element\"\n            );\n        }\n    }\n\n    fn series_is_invalid_or_empty(series: &Series) -> bool {\n        let start = series.start;\n        let stop = series.stop;\n        let step = series.step;\n        (start > stop && step > 0) || (start < stop && step < 0) || (step == 0 && start != stop)\n    }\n\n    fn series_expected_length(series: &Series) -> usize {\n        let start = series.start;\n        let stop = series.stop;\n        let step = series.step;\n        if step == 0 {\n            if start == stop {\n                1\n            } else {\n                0\n            }\n        } else {\n            ((stop.saturating_sub(start)).saturating_div(step)).saturating_add(1) as usize\n        }\n    }\n\n    #[test]\n    fn test_best_index_argv_order_all_constraints() {\n        // Test when start, stop, and step constraints are present\n        let constraints = vec![\n            usable_constraint(1), // start\n            usable_constraint(2), // stop\n            usable_constraint(3), // step\n        ];\n\n        let index_info = GenerateSeriesTable::best_index(&constraints, &[]).unwrap();\n\n        // Verify start gets argv_index 1, stop gets 2, step gets 3\n        assert_eq!(index_info.constraint_usages[0].argv_index, Some(1)); // start\n        assert_eq!(index_info.constraint_usages[1].argv_index, Some(2)); // stop\n        assert_eq!(index_info.constraint_usages[2].argv_index, Some(3)); // step\n        assert_eq!(index_info.idx_num, 7); // All bits set (1 | 2 | 4)\n    }\n\n    #[test]\n    fn test_best_index_argv_order_start_stop_only() {\n        let constraints = vec![\n            usable_constraint(1), // start\n            usable_constraint(2), // stop\n        ];\n\n        let index_info = GenerateSeriesTable::best_index(&constraints, &[]).unwrap();\n\n        // Verify start gets argv_index 1, stop gets 2\n        assert_eq!(index_info.constraint_usages[0].argv_index, Some(1)); // start\n        assert_eq!(index_info.constraint_usages[1].argv_index, Some(2)); // stop\n        assert_eq!(index_info.idx_num, 3); // Bits 0 and 1 set (1 | 2)\n    }\n\n    #[test]\n    fn test_best_index_argv_order_only_start() {\n        let constraints = vec![\n            usable_constraint(1), // start\n        ];\n\n        let index_info = GenerateSeriesTable::best_index(&constraints, &[]).unwrap();\n\n        // Verify start gets argv_index 1\n        assert_eq!(index_info.constraint_usages[0].argv_index, Some(1)); // start\n        assert_eq!(index_info.idx_num, 1); // Only bit 0 set\n    }\n\n    #[test]\n    fn test_best_index_argv_order_reverse_constraint_order() {\n        // Test when constraints are provided in reverse order (step, stop, start)\n        let constraints = vec![\n            usable_constraint(3), // step\n            usable_constraint(2), // stop\n            usable_constraint(1), // start\n        ];\n\n        let index_info = GenerateSeriesTable::best_index(&constraints, &[]).unwrap();\n\n        // Verify start still gets argv_index 1, stop gets 2, step gets 3 regardless of constraint order\n        assert_eq!(index_info.constraint_usages[0].argv_index, Some(3)); // step\n        assert_eq!(index_info.constraint_usages[1].argv_index, Some(2)); // stop\n        assert_eq!(index_info.constraint_usages[2].argv_index, Some(1)); // start\n        assert_eq!(index_info.idx_num, 7); // All bits set (1 | 2 | 4)\n    }\n\n    #[test]\n    fn test_best_index_argv_order_missing_start() {\n        // Test when start constraint is missing but stop and step are present\n        let constraints = vec![\n            usable_constraint(2), // stop\n            usable_constraint(3), // step\n        ];\n\n        let result = GenerateSeriesTable::best_index(&constraints, &[]);\n\n        assert!(matches!(result, Err(ResultCode::InvalidArgs)));\n    }\n\n    #[test]\n    fn test_best_index_no_usable_constraints() {\n        let constraints = vec![ConstraintInfo {\n            column_index: 1,\n            op: ConstraintOp::Eq,\n            usable: false,\n            index: 0,\n        }];\n\n        let result = GenerateSeriesTable::best_index(&constraints, &[]);\n\n        assert!(matches!(result, Err(ResultCode::ConstraintViolation)));\n    }\n\n    fn usable_constraint(column_index: u32) -> ConstraintInfo {\n        ConstraintInfo {\n            column_index,\n            op: ConstraintOp::Eq,\n            usable: true,\n            index: 0,\n        }\n    }\n}\n"
  },
  {
    "path": "core/state_machine.rs",
    "content": "use crate::{\n    types::{IOCompletions, IOResult},\n    Result,\n};\n\npub enum TransitionResult<Result> {\n    Io(IOCompletions),\n    Continue,\n    Done(Result),\n}\n\n/// A generic trait for state machines.\npub trait StateTransition {\n    type Context;\n    type SMResult;\n\n    /// Transition the state machine to the next state.\n    ///\n    /// Returns `TransitionResult::Io` if the state machine needs to perform an IO operation.\n    /// Returns `TransitionResult::Continue` if the state machine needs to continue.\n    /// Returns `TransitionResult::Done` if the state machine is done.\n    fn step(&mut self, context: &Self::Context) -> Result<TransitionResult<Self::SMResult>>;\n\n    /// Finalize the state machine.\n    ///\n    /// This is called when the state machine is done.\n    fn finalize(&mut self, context: &Self::Context) -> Result<()>;\n\n    /// Check if the state machine is finalized.\n    fn is_finalized(&self) -> bool;\n}\n\n#[derive(Debug)]\npub struct StateMachine<State: StateTransition> {\n    state: State,\n    is_finalized: bool,\n}\n\n/// A generic state machine that loops calling `transition` until it returns `TransitionResult::Done` or `TransitionResult::Io`.\nimpl<State: StateTransition> StateMachine<State> {\n    pub fn new(state: State) -> Self {\n        Self {\n            state,\n            is_finalized: false,\n        }\n    }\n\n    pub fn step(&mut self, context: &State::Context) -> Result<IOResult<State::SMResult>> {\n        loop {\n            if self.is_finalized {\n                unreachable!(\"StateMachine::transition: state machine is finalized\");\n            }\n            match self.state.step(context)? {\n                TransitionResult::Io(io) => {\n                    return Ok(IOResult::IO(io));\n                }\n                TransitionResult::Continue => {\n                    continue;\n                }\n                TransitionResult::Done(result) => {\n                    assert!(self.state.is_finalized());\n                    self.is_finalized = true;\n                    return Ok(IOResult::Done(result));\n                }\n            }\n        }\n    }\n\n    pub fn finalize(&mut self, context: &State::Context) -> Result<()> {\n        self.state.finalize(context)?;\n        self.is_finalized = true;\n        Ok(())\n    }\n\n    pub fn is_finalized(&self) -> bool {\n        self.is_finalized\n    }\n}\n"
  },
  {
    "path": "core/statement.rs",
    "content": "use crate::turso_assert_eq;\nuse std::{\n    borrow::Cow,\n    num::NonZero,\n    ops::Deref,\n    sync::{atomic::Ordering, Arc},\n    task::Waker,\n};\n\nuse tracing::{instrument, Level};\nuse turso_parser::{\n    ast::{fmt::ToTokens, Cmd},\n    parser::Parser,\n};\n\nuse crate::{\n    busy::BusyHandlerState,\n    parameters,\n    schema::Trigger,\n    stats::refresh_analyze_stats,\n    translate::{self, display::PlanContext, emitter::TransactionMode},\n    vdbe::{\n        self,\n        explain::{EXPLAIN_COLUMNS_TYPE, EXPLAIN_QUERY_PLAN_COLUMNS_TYPE},\n    },\n    LimboError, MvStore, Pager, QueryMode, Result, Value, EXPLAIN_COLUMNS,\n    EXPLAIN_QUERY_PLAN_COLUMNS,\n};\n\ntype ProgramExecutionState = vdbe::ProgramExecutionState;\ntype Row = vdbe::Row;\ntype StepResult = vdbe::StepResult;\n\npub struct Statement {\n    pub(crate) program: vdbe::Program,\n    state: vdbe::ProgramState,\n    pager: Arc<Pager>,\n    /// indicates if the statement is a NORMAL/EXPLAIN/EXPLAIN QUERY PLAN\n    query_mode: QueryMode,\n    /// Flag to show if the statement was busy\n    busy: bool,\n    /// Busy handler state for tracking invocations and timeouts\n    busy_handler_state: Option<BusyHandlerState>,\n    /// True once step() has returned Row for a write statement (INSERT/UPDATE/DELETE\n    /// with RETURNING). With ephemeral-buffered RETURNING, the first Row proves all\n    /// DML completed — only the scan-back remains. Used by reset_internal to decide\n    /// commit vs rollback when a statement is abandoned.\n    has_returned_row: bool,\n    /// Byte offset in the original SQL string where this statement ends.\n    /// Used by sqlite3_prepare_v2 to set the *pzTail output parameter.\n    tail_offset: usize,\n}\n\ncrate::assert::assert_send_sync!(Statement);\n\nimpl std::fmt::Debug for Statement {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"Statement\").finish()\n    }\n}\n\nimpl Drop for Statement {\n    fn drop(&mut self) {\n        self.reset_best_effort();\n    }\n}\n\nimpl Statement {\n    pub fn new(\n        program: vdbe::Program,\n        pager: Arc<Pager>,\n        query_mode: QueryMode,\n        tail_offset: usize,\n    ) -> Self {\n        let (max_registers, cursor_count) = match query_mode {\n            QueryMode::Normal => (program.max_registers, program.cursor_ref.len()),\n            QueryMode::Explain => (EXPLAIN_COLUMNS.len(), 0),\n            QueryMode::ExplainQueryPlan => (EXPLAIN_QUERY_PLAN_COLUMNS.len(), 0),\n        };\n        let state = vdbe::ProgramState::new(max_registers, cursor_count);\n        Self {\n            program,\n            state,\n            pager,\n            query_mode,\n            busy: false,\n            busy_handler_state: None,\n            has_returned_row: false,\n            tail_offset,\n        }\n    }\n\n    pub fn tail_offset(&self) -> usize {\n        self.tail_offset\n    }\n\n    pub fn get_trigger(&self) -> Option<Arc<Trigger>> {\n        self.program.trigger.clone()\n    }\n\n    pub fn get_query_mode(&self) -> QueryMode {\n        self.query_mode\n    }\n\n    pub fn get_program(&self) -> &vdbe::Program {\n        &self.program\n    }\n\n    pub fn get_pager(&self) -> &Arc<Pager> {\n        &self.pager\n    }\n\n    pub fn n_change(&self) -> i64 {\n        self.state\n            .n_change\n            .load(crate::sync::atomic::Ordering::SeqCst)\n    }\n\n    pub fn set_mv_tx(&mut self, mv_tx: Option<(u64, TransactionMode)>) {\n        self.program.connection.set_mv_tx(mv_tx);\n    }\n\n    pub fn interrupt(&mut self) {\n        self.state.interrupt();\n    }\n\n    pub fn execution_state(&self) -> ProgramExecutionState {\n        self.state.execution_state\n    }\n\n    /// Current statement-level metrics (rows read, VM steps, etc.).\n    pub fn metrics(&self) -> &vdbe::metrics::StatementMetrics {\n        &self.state.metrics\n    }\n\n    pub fn mv_store(&self) -> impl Deref<Target = Option<Arc<MvStore>>> {\n        self.program.connection.mv_store()\n    }\n\n    /// Take the pending IO completions from this statement.\n    /// Returns None if no IO is pending.\n    /// This is used by async state machines that need to yield the completions.\n    pub fn take_io_completions(&mut self) -> Option<crate::types::IOCompletions> {\n        self.state.io_completions.take()\n    }\n\n    fn _step(&mut self, waker: Option<&Waker>) -> Result<StepResult> {\n        if matches!(self.state.execution_state, ProgramExecutionState::Init)\n            && !self\n                .program\n                .prepare_context\n                .matches_connection(&self.program.connection)\n        {\n            self.reprepare()?;\n        }\n        // If we're waiting for a busy handler timeout, check if we can proceed\n        if let Some(busy_state) = self.busy_handler_state.as_ref() {\n            if self.pager.io.current_time_monotonic() < busy_state.timeout() {\n                // Yield the query as the timeout has not been reached yet\n                if let Some(waker) = waker {\n                    waker.wake_by_ref();\n                }\n                return Ok(StepResult::IO);\n            }\n        }\n\n        const MAX_SCHEMA_RETRY: usize = 50;\n        let mut res = self\n            .program\n            .step(&mut self.state, &self.pager, self.query_mode, waker);\n        for attempt in 0..MAX_SCHEMA_RETRY {\n            // Only reprepare if we still need to update schema\n            if !matches!(res, Err(LimboError::SchemaUpdated)) {\n                break;\n            }\n            tracing::debug!(\"reprepare: attempt={}\", attempt);\n            self.reprepare()?;\n            res = self\n                .program\n                .step(&mut self.state, &self.pager, self.query_mode, waker);\n        }\n\n        // Aggregate metrics when statement completes\n        if matches!(res, Ok(StepResult::Done)) {\n            self.program\n                .connection\n                .metrics\n                .write()\n                .record_statement(&self.state.metrics);\n            self.busy = false;\n            self.busy_handler_state = None; // Reset busy state on completion\n\n            // After ANALYZE completes, refresh in-memory stats so planners can use them.\n            let sql = self.program.sql.trim_start().as_bytes();\n            if sql.len() >= 7 && sql[..7].eq_ignore_ascii_case(b\"ANALYZE\") {\n                refresh_analyze_stats(&self.program.connection);\n            }\n        } else {\n            self.busy = true;\n        }\n\n        // Handle busy result by invoking the busy handler\n        if matches!(res, Ok(StepResult::Busy)) {\n            let now = self.pager.io.current_time_monotonic();\n            let handler = self.program.connection.get_busy_handler();\n\n            // Initialize or get existing busy handler state\n            let busy_state = self\n                .busy_handler_state\n                .get_or_insert_with(|| BusyHandlerState::new(now));\n\n            // Invoke the busy handler to determine if we should retry\n            if busy_state.invoke(&handler, now) {\n                // Handler says retry, yield with IO to wait for timeout\n                if let Some(waker) = waker {\n                    waker.wake_by_ref();\n                }\n                res = Ok(StepResult::IO);\n                #[cfg(shuttle)]\n                crate::thread::spin_loop();\n            }\n            // else: Handler says stop, res stays as Busy\n        }\n\n        // Track when a write statement yields its first Row. With ephemeral-buffered\n        // RETURNING, this proves all DML completed — only the scan-back remains.\n        if matches!(res, Ok(StepResult::Row))\n            && self.query_mode == QueryMode::Normal\n            && self.program.change_cnt_on\n            && !self.program.result_columns.is_empty()\n        {\n            self.has_returned_row = true;\n        }\n\n        res\n    }\n\n    #[inline]\n    pub fn step(&mut self) -> Result<StepResult> {\n        self._step(None)\n    }\n\n    #[inline]\n    pub fn step_with_waker(&mut self, waker: &Waker) -> Result<StepResult> {\n        self._step(Some(waker))\n    }\n\n    pub fn run_ignore_rows(&mut self) -> Result<()> {\n        loop {\n            match self.step()? {\n                vdbe::StepResult::Done => return Ok(()),\n                vdbe::StepResult::IO => self.pager.io.step()?,\n                vdbe::StepResult::Row => continue,\n                vdbe::StepResult::Interrupt | vdbe::StepResult::Busy => {\n                    return Err(LimboError::Busy)\n                }\n            }\n        }\n    }\n\n    pub fn run_collect_rows(&mut self) -> Result<Vec<Vec<Value>>> {\n        let mut values = Vec::new();\n        loop {\n            match self.step()? {\n                vdbe::StepResult::Done => return Ok(values),\n                vdbe::StepResult::IO => self.pager.io.step()?,\n                vdbe::StepResult::Row => {\n                    values.push(self.row().unwrap().get_values().cloned().collect());\n                    continue;\n                }\n                vdbe::StepResult::Interrupt | vdbe::StepResult::Busy => {\n                    return Err(LimboError::Busy)\n                }\n            }\n        }\n    }\n\n    /// Blocks execution, advances IO, and runs to completion of the statement\n    pub fn run_with_row_callback(\n        &mut self,\n        mut func: impl FnMut(&Row) -> Result<()>,\n    ) -> Result<()> {\n        loop {\n            match self.step()? {\n                vdbe::StepResult::Done => break,\n                vdbe::StepResult::IO => self.pager.io.step()?,\n                vdbe::StepResult::Row => {\n                    func(self.row().expect(\"row should be present\"))?;\n                }\n                vdbe::StepResult::Interrupt => return Err(LimboError::Interrupt),\n                vdbe::StepResult::Busy => return Err(LimboError::Busy),\n            }\n        }\n        Ok(())\n    }\n\n    /// Blocks execution, advances IO, and stops at any StepResult except IO\n    /// You can optionally pass a handler to run after IO is advanced\n    pub fn run_one_step_blocking(\n        &mut self,\n        mut pre_io_func: impl FnMut() -> Result<()>,\n        mut post_io_func: impl FnMut() -> Result<()>,\n    ) -> Result<Option<&Row>> {\n        let result = loop {\n            match self.step()? {\n                vdbe::StepResult::Done => break None,\n                vdbe::StepResult::IO => {\n                    pre_io_func()?;\n                    self.pager.io.step()?;\n                    post_io_func()?;\n                }\n                vdbe::StepResult::Row => break Some(self.row().expect(\"row should be present\")),\n                vdbe::StepResult::Interrupt => return Err(LimboError::Interrupt),\n                vdbe::StepResult::Busy => return Err(LimboError::Busy),\n            }\n        };\n        Ok(result)\n    }\n\n    #[instrument(skip_all, level = Level::DEBUG)]\n    fn reprepare(&mut self) -> Result<()> {\n        tracing::trace!(\"repreparing statement\");\n        let conn = self.program.connection.clone();\n\n        // End transactions on attached database pagers so they get a fresh view\n        // of the database. Without this, the pager would still see the old page 1\n        // with the stale schema cookie, causing an infinite SchemaUpdated loop.\n        // SchemaUpdated can occur at different points in the Transaction opcode,\n        // so the attached pager may or may not hold locks at this point.\n        let attached_db_ids: Vec<usize> = self\n            .program\n            .prepared\n            .write_databases\n            .iter()\n            .chain(self.program.prepared.read_databases.iter())\n            .filter(|&&id| crate::is_attached_db(id))\n            .copied()\n            .collect();\n        for db_id in attached_db_ids {\n            let pager = conn.get_pager_from_database_index(&db_id);\n            // Discard any connection-local schema changes for this attached DB\n            // so the re-translate reads the committed schema.\n            conn.database_schemas().write().remove(&db_id);\n            if pager.holds_read_lock() {\n                pager.rollback_attached();\n            }\n        }\n\n        *conn.schema.write() = conn.db.clone_schema();\n        self.program = {\n            let mut parser = Parser::new(self.program.sql.as_bytes());\n            let cmd = parser.next_cmd()?;\n            let cmd = cmd.expect(\"Same SQL string should be able to be parsed\");\n\n            let syms = conn.syms.read();\n            let mode = self.query_mode;\n            #[cfg(debug_assertions)]\n            turso_assert_eq!(QueryMode::new(&cmd), mode);\n            let (Cmd::Stmt(stmt) | Cmd::Explain(stmt) | Cmd::ExplainQueryPlan(stmt)) = cmd;\n            let schema = conn.schema.read().clone();\n            translate::translate(\n                &schema,\n                stmt,\n                self.pager.clone(),\n                conn.clone(),\n                &syms,\n                mode,\n                &self.program.sql,\n            )?\n        };\n\n        // Save parameters before they are reset\n        let parameters = std::mem::take(&mut self.state.parameters);\n        let (max_registers, cursor_count) = match self.query_mode {\n            QueryMode::Normal => (self.program.max_registers, self.program.cursor_ref.len()),\n            QueryMode::Explain => (EXPLAIN_COLUMNS.len(), 0),\n            QueryMode::ExplainQueryPlan => (EXPLAIN_QUERY_PLAN_COLUMNS.len(), 0),\n        };\n        self.reset_internal(Some(max_registers), Some(cursor_count))?;\n        // Load the parameters back into the state\n        self.state.parameters = parameters;\n        Ok(())\n    }\n\n    pub fn num_columns(&self) -> usize {\n        match self.query_mode {\n            QueryMode::Normal => self.program.result_columns.len(),\n            QueryMode::Explain => EXPLAIN_COLUMNS.len(),\n            QueryMode::ExplainQueryPlan => EXPLAIN_QUERY_PLAN_COLUMNS.len(),\n        }\n    }\n\n    pub fn get_column_name(&self, idx: usize) -> Cow<'_, str> {\n        if self.query_mode == QueryMode::Explain {\n            return Cow::Owned(EXPLAIN_COLUMNS.get(idx).expect(\"No column\").to_string());\n        }\n        if self.query_mode == QueryMode::ExplainQueryPlan {\n            return Cow::Owned(\n                EXPLAIN_QUERY_PLAN_COLUMNS\n                    .get(idx)\n                    .expect(\"No column\")\n                    .to_string(),\n            );\n        }\n        match self.query_mode {\n            QueryMode::Normal => {\n                let column = &self.program.result_columns.get(idx).expect(\"No column\");\n                match column.name(&self.program.table_references) {\n                    Some(name) => Cow::Borrowed(name),\n                    None => {\n                        let tables = [&self.program.table_references];\n                        let ctx = PlanContext(&tables);\n                        Cow::Owned(column.expr.displayer(&ctx).to_string())\n                    }\n                }\n            }\n            QueryMode::Explain => Cow::Borrowed(EXPLAIN_COLUMNS[idx]),\n            QueryMode::ExplainQueryPlan => Cow::Borrowed(EXPLAIN_QUERY_PLAN_COLUMNS[idx]),\n        }\n    }\n\n    pub fn get_column_table_name(&self, idx: usize) -> Option<Cow<'_, str>> {\n        if self.query_mode == QueryMode::Explain || self.query_mode == QueryMode::ExplainQueryPlan {\n            return None;\n        }\n        let column = &self.program.result_columns.get(idx).expect(\"No column\");\n        match &column.expr {\n            turso_parser::ast::Expr::Column { table, .. } => self\n                .program\n                .table_references\n                .find_table_by_internal_id(*table)\n                .map(|(_, table_ref)| Cow::Borrowed(table_ref.get_name())),\n            _ => None,\n        }\n    }\n\n    /// Returns the declared type of a result column.\n    ///\n    /// This behaves similarly to SQLite's `sqlite3_column_decltype()`:\n    /// If the Nth column of the returned result set of a SELECT is a table column\n    /// (not an expression or subquery) then the declared type of the table column\n    /// is returned. If the Nth column of the result set is an expression or subquery,\n    /// then None is returned. The returned string is always UTF-8 encoded.\n    ///\n    /// See: <https://sqlite.org/c3ref/column_decltype.html>\n    pub fn get_column_decltype(&self, idx: usize) -> Option<String> {\n        if self.query_mode == QueryMode::Explain {\n            return Some(\n                EXPLAIN_COLUMNS_TYPE\n                    .get(idx)\n                    .expect(\"No column\")\n                    .to_string(),\n            );\n        }\n        if self.query_mode == QueryMode::ExplainQueryPlan {\n            return Some(\n                EXPLAIN_QUERY_PLAN_COLUMNS_TYPE\n                    .get(idx)\n                    .expect(\"No column\")\n                    .to_string(),\n            );\n        }\n        let column = &self.program.result_columns.get(idx).expect(\"No column\");\n        match &column.expr {\n            turso_parser::ast::Expr::Column {\n                table,\n                column: column_idx,\n                ..\n            } => {\n                let (_, table_ref) = self\n                    .program\n                    .table_references\n                    .find_table_by_internal_id(*table)?;\n                let table_column = table_ref.get_column_at(*column_idx)?;\n                let ty_str = &table_column.ty_str;\n                if ty_str.is_empty() {\n                    None\n                } else {\n                    Some(ty_str.clone())\n                }\n            }\n            _ => None,\n        }\n    }\n\n    /// Returns the type affinity name of a result column (e.g., \"INTEGER\", \"TEXT\", \"REAL\", \"BLOB\", \"NUMERIC\").\n    ///\n    /// Unlike `get_column_decltype` which returns the original declared type string,\n    /// this method returns the normalized SQLite type affinity name.\n    pub fn get_column_type_name(&self, idx: usize) -> Option<String> {\n        if self.query_mode == QueryMode::Explain {\n            return Some(\n                EXPLAIN_COLUMNS_TYPE\n                    .get(idx)\n                    .expect(\"No column\")\n                    .to_string(),\n            );\n        }\n        if self.query_mode == QueryMode::ExplainQueryPlan {\n            return Some(\n                EXPLAIN_QUERY_PLAN_COLUMNS_TYPE\n                    .get(idx)\n                    .expect(\"No column\")\n                    .to_string(),\n            );\n        }\n        let column = &self.program.result_columns.get(idx).expect(\"No column\");\n        match &column.expr {\n            turso_parser::ast::Expr::Column {\n                table,\n                column: column_idx,\n                ..\n            } => {\n                let (_, table_ref) = self\n                    .program\n                    .table_references\n                    .find_table_by_internal_id(*table)?;\n                let table_column = table_ref.get_column_at(*column_idx)?;\n                match &table_column.ty() {\n                    crate::schema::Type::Integer => Some(\"INTEGER\".to_string()),\n                    crate::schema::Type::Real => Some(\"REAL\".to_string()),\n                    crate::schema::Type::Text => Some(\"TEXT\".to_string()),\n                    crate::schema::Type::Blob => Some(\"BLOB\".to_string()),\n                    crate::schema::Type::Numeric => Some(\"NUMERIC\".to_string()),\n                    crate::schema::Type::Null => None,\n                }\n            }\n            _ => None,\n        }\n    }\n\n    pub fn parameters(&self) -> &parameters::Parameters {\n        &self.program.parameters\n    }\n\n    pub fn parameters_count(&self) -> usize {\n        self.program.parameters.count()\n    }\n\n    pub fn parameter_index(&self, name: &str) -> Option<NonZero<usize>> {\n        self.program.parameters.index(name)\n    }\n\n    pub fn bind_at(&mut self, index: NonZero<usize>, value: Value) {\n        self.state.bind_at(index, value);\n    }\n\n    pub fn clear_bindings(&mut self) {\n        self.state.clear_bindings();\n    }\n\n    pub fn reset(&mut self) -> Result<()> {\n        self.reset_internal(None, None)\n    }\n\n    pub fn reset_best_effort(&mut self) {\n        match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| self.reset())) {\n            Ok(Ok(())) => {}\n            Ok(Err(err)) => {\n                tracing::error!(\"Statement reset failed during best-effort cleanup: {err}\");\n            }\n            Err(_) => {\n                tracing::error!(\"Statement reset panicked during best-effort cleanup\");\n            }\n        }\n    }\n\n    fn reset_internal(\n        &mut self,\n        max_registers: Option<usize>,\n        max_cursors: Option<usize>,\n    ) -> Result<()> {\n        fn capture_reset_error(\n            reset_error: &mut Option<LimboError>,\n            err: LimboError,\n            context: &str,\n        ) {\n            tracing::error!(\"{context}: {err}\");\n            if reset_error.is_none() {\n                *reset_error = Some(err);\n            }\n        }\n\n        let mut reset_error: Option<LimboError> = None;\n\n        if let Some(io) = self.state.io_completions.take() {\n            if let Err(err) = io.wait(self.pager.io.as_ref()) {\n                capture_reset_error(\n                    &mut reset_error,\n                    err,\n                    \"Error while draining pending IO during statement reset\",\n                );\n            }\n        }\n\n        if self.state.execution_state.is_running() {\n            if self.query_mode == QueryMode::Normal\n                && self.program.change_cnt_on\n                && self.has_returned_row\n            {\n                // Write statement with RETURNING, user got at least one Row.\n                // With ephemeral-buffered RETURNING, ALL DML completed before any\n                // rows were yielded. The remaining work is just the scan-back\n                // (in-memory) + Halt. Commit the transaction via halt().\n                let mut halt_completed = false;\n                loop {\n                    match vdbe::execute::halt(\n                        &self.program,\n                        &mut self.state,\n                        &self.pager,\n                        0,\n                        \"\",\n                        None,\n                    ) {\n                        Ok(vdbe::execute::InsnFunctionStepResult::Done) => {\n                            halt_completed = true;\n                            break;\n                        }\n                        Ok(vdbe::execute::InsnFunctionStepResult::IO(_)) => {\n                            if let Err(e) = self.pager.io.step() {\n                                capture_reset_error(\n                                    &mut reset_error,\n                                    e,\n                                    \"Error committing during statement reset\",\n                                );\n                                break;\n                            }\n                        }\n                        Err(e) => {\n                            capture_reset_error(\n                                &mut reset_error,\n                                e,\n                                \"Error halting statement during reset\",\n                            );\n                            break;\n                        }\n                        Ok(vdbe::execute::InsnFunctionStepResult::Row)\n                        | Ok(vdbe::execute::InsnFunctionStepResult::Step) => {\n                            capture_reset_error(\n                                &mut reset_error,\n                                LimboError::InternalError(\n                                    \"Unexpected halt result during reset\".to_string(),\n                                ),\n                                \"Statement reset encountered unexpected halt result\",\n                            );\n                            break;\n                        }\n                    }\n                }\n\n                if !halt_completed {\n                    if let Err(abort_err) =\n                        self.program\n                            .abort(&self.pager, reset_error.as_ref(), &mut self.state)\n                    {\n                        capture_reset_error(\n                            &mut reset_error,\n                            abort_err,\n                            \"Abort failed during statement reset\",\n                        );\n                    }\n                }\n            } else {\n                // Either a read-only statement, a write statement that never\n                // yielded a Row (DML still in progress or hit Busy/error), or a\n                // write statement without RETURNING. Rollback to avoid committing\n                // partial DML or silently retrying after transient errors (Busy).\n                if let Err(abort_err) = self.program.abort(&self.pager, None, &mut self.state) {\n                    capture_reset_error(\n                        &mut reset_error,\n                        abort_err,\n                        \"Abort failed during statement reset\",\n                    );\n                }\n            }\n        } else {\n            // Statement not running (Done/Failed/Init) — cleanup only.\n            if let Err(abort_err) = self.program.abort(&self.pager, None, &mut self.state) {\n                capture_reset_error(\n                    &mut reset_error,\n                    abort_err,\n                    \"Abort failed during statement reset\",\n                );\n            }\n        }\n        // Safety net: if end_statement wasn't reached (e.g. statement dropped\n        // mid-execution), ensure n_active_writes is decremented before reset\n        // clears the flag.\n        if self.state.is_active_write {\n            self.program\n                .connection\n                .n_active_writes\n                .fetch_sub(1, Ordering::SeqCst);\n            self.state.is_active_write = false;\n        }\n        self.state.reset(max_registers, max_cursors);\n        self.state.n_change.store(0, Ordering::SeqCst);\n        self.busy = false;\n        self.busy_handler_state = None;\n        self.has_returned_row = false;\n\n        if let Some(err) = reset_error {\n            return Err(err);\n        }\n        Ok(())\n    }\n\n    pub fn row(&self) -> Option<&Row> {\n        self.state.result_row.as_ref()\n    }\n\n    pub fn get_sql(&self) -> &str {\n        &self.program.sql\n    }\n\n    pub fn is_busy(&self) -> bool {\n        self.busy\n    }\n\n    /// Internal method to get IO from a statement.\n    /// Used by select internal crate\n    ///\n    /// Avoid using this method for advancing IO while iteration over `step`.\n    /// Prefer to use helper methods instead such as [Self::run_with_row_callback]\n    pub fn _io(&self) -> &dyn crate::IO {\n        self.pager.io.as_ref()\n    }\n}\n"
  },
  {
    "path": "core/stats.rs",
    "content": "use crate::sync::Arc;\nuse rustc_hash::FxHashMap as HashMap;\n\nuse crate::schema::Schema;\nuse crate::translate::emitter::TransactionMode;\nuse crate::util::normalize_ident;\nuse crate::{Connection, Result, Statement, TransactionState, Value};\npub const STATS_TABLE: &str = \"sqlite_stat1\";\nconst STATS_QUERY: &str = \"SELECT tbl, idx, stat FROM sqlite_stat1\";\n\n/// Statistics produced by ANALYZE for a single index.\n#[derive(Clone, Debug, Default)]\npub struct IndexStat {\n    /// Estimated total number of rows in the table/index when the stat was collected.\n    pub total_rows: Option<u64>,\n    /// Average number of rows per distinct key prefix, for each leftmost prefix\n    /// of the index columns.\n    ///\n    /// These values come directly from sqlite_stat1's stat column (after the\n    /// first number which is total_rows). For a stat string \"1000 100 10 1\":\n    /// - total_rows = 1000\n    /// - avg_rows_per_distinct_prefix = [100, 10, 1]\n    ///\n    /// Entry at position `i` means: on average, this many rows share the same\n    /// values in the first `i + 1` columns of the index. Lower values indicate\n    /// higher selectivity (more distinct prefixes).\n    ///\n    /// To compute number of distinct values (NDV) for prefix i:\n    ///   ndv = total_rows / avg_rows_per_distinct_prefix[i]\n    pub avg_rows_per_distinct_prefix: Vec<u64>,\n}\n\n/// Statistics produced by ANALYZE for a single BTree table.\n#[derive(Clone, Debug, Default)]\npub struct TableStat {\n    /// Estimated row count for the table (sqlite_stat1 entry with a NULL index name).\n    pub row_count: Option<u64>,\n    /// Per-index statistics keyed by normalized index name.\n    pub index_stats: HashMap<String, IndexStat>,\n}\n\nimpl TableStat {\n    /// Get or create the per-index statistics bucket for the given index name.\n    pub fn index_stats_mut(&mut self, index_name: &str) -> &mut IndexStat {\n        let index_name = normalize_ident(index_name);\n        self.index_stats.entry(index_name).or_default()\n    }\n}\n\n/// Container for ANALYZE statistics across the schema.\n#[derive(Clone, Debug, Default)]\npub struct AnalyzeStats {\n    /// Per-table statistics keyed by normalized table name.\n    pub tables: HashMap<String, TableStat>,\n}\n\nimpl AnalyzeStats {\n    pub fn needs_refresh(&self) -> bool {\n        self.tables.is_empty()\n    }\n    /// Get the statistics for a table, if present.\n    pub fn table_stats(&self, table_name: &str) -> Option<&TableStat> {\n        let table_name = normalize_ident(table_name);\n        self.tables.get(&table_name)\n    }\n\n    /// Get or create the statistics bucket for a table.\n    pub fn table_stats_mut(&mut self, table_name: &str) -> &mut TableStat {\n        let table_name = normalize_ident(table_name);\n        self.tables.entry(table_name).or_default()\n    }\n\n    /// Remove all statistics for a table.\n    pub fn remove_table(&mut self, table_name: &str) {\n        let table_name = normalize_ident(table_name);\n        self.tables.remove(&table_name);\n    }\n\n    /// Remove statistics for a specific index on a table.\n    pub fn remove_index(&mut self, table_name: &str, index_name: &str) {\n        let table_name = normalize_ident(table_name);\n        let index_name = normalize_ident(index_name);\n        if let Some(table_stats) = self.tables.get_mut(&table_name) {\n            table_stats.index_stats.remove(&index_name);\n        }\n    }\n}\n\n/// Read sqlite_stat1 contents into an AnalyzeStats map without mutating schema.\n///\n/// Only regular B-tree tables and indexes are considered. Virtual and ephemeral\n/// tables are ignored.\npub fn gather_sqlite_stat1(\n    conn: &Arc<Connection>,\n    schema: &Schema,\n    mv_tx: Option<(u64, TransactionMode)>,\n) -> Result<AnalyzeStats> {\n    let mut stats = AnalyzeStats::default();\n    let mut stmt = conn.prepare(STATS_QUERY)?;\n    stmt.set_mv_tx(mv_tx);\n    load_sqlite_stat1_from_stmt(stmt, schema, &mut stats)?;\n    Ok(stats)\n}\n\n/// Best-effort refresh analyze_stats on the connection's schema.\npub fn refresh_analyze_stats(conn: &Arc<Connection>) {\n    if !conn.is_db_initialized() || conn.is_nested_stmt() {\n        return;\n    }\n    if matches!(conn.get_tx_state(), TransactionState::Write { .. }) {\n        return;\n    }\n\n    // Need a snapshot of the current schema to validate tables/indexes.\n    let schema_snapshot = { conn.schema.read().clone() };\n    if schema_snapshot.get_btree_table(STATS_TABLE).is_none() {\n        return;\n    }\n\n    let mv_tx = conn.get_mv_tx();\n    if let Ok(stats) = gather_sqlite_stat1(conn, &schema_snapshot, mv_tx) {\n        conn.with_schema_mut(|schema| {\n            schema.analyze_stats = stats;\n        });\n    }\n}\n\nfn load_sqlite_stat1_from_stmt(\n    mut stmt: Statement,\n    schema: &Schema,\n    stats: &mut AnalyzeStats,\n) -> Result<()> {\n    stmt.run_with_row_callback(|row| {\n        let table_name = row.get::<&str>(0)?;\n        let idx_value = row.get::<&Value>(1)?;\n        let stat_value = row.get::<&Value>(2)?;\n\n        let idx_name = match idx_value {\n            Value::Null => None,\n            Value::Text(s) => Some(s.as_str()),\n            _ => None,\n        };\n        let stat = match stat_value {\n            Value::Text(s) => s.as_str(),\n            _ => return Ok(()),\n        };\n\n        // Skip if table is not a regular B-tree.\n        if schema.get_btree_table(table_name).is_none() {\n            return Ok(());\n        }\n        let Some(numbers) = parse_stat_numbers(stat) else {\n            return Ok(());\n        };\n        if numbers.is_empty() {\n            return Ok(());\n        }\n        if idx_name.is_none() {\n            if let Some(total_rows) = numbers.first().copied() {\n                stats.table_stats_mut(table_name).row_count = Some(total_rows);\n            }\n            return Ok(());\n        }\n\n        // Index-level entry: only keep if the index exists on this table.\n        let idx_name = normalize_ident(idx_name.unwrap());\n        if schema.get_index(table_name, &idx_name).is_none() {\n            return Ok(());\n        }\n\n        let total_rows = numbers.first().copied();\n        {\n            let idx_stats = stats.table_stats_mut(table_name).index_stats_mut(&idx_name);\n            idx_stats.total_rows = total_rows;\n            idx_stats.avg_rows_per_distinct_prefix = numbers.iter().skip(1).copied().collect();\n        }\n\n        // If we didn't see a table-level row yet, seed row_count from index stats.\n        if let Some(total_rows) = total_rows {\n            let table_stats = stats.table_stats_mut(table_name);\n            if table_stats.row_count.is_none() {\n                table_stats.row_count = Some(total_rows);\n            }\n        }\n        Ok(())\n    })?;\n\n    Ok(())\n}\n\nfn parse_stat_numbers(stat: &str) -> Option<Vec<u64>> {\n    stat.split_whitespace()\n        .map(|s| s.parse::<u64>().ok())\n        .collect()\n}\n\n/// Statistics accumulator for ANALYZE.\n#[derive(Debug, Clone)]\npub struct StatAccum {\n    /// Number of columns in the index (not including rowid)\n    pub n_col: usize,\n    /// Total number of rows seen\n    pub n_row: u64,\n    /// Distinct counts for each column prefix.\n    /// distinct[i] = number of distinct values for columns 0..=i\n    pub distinct: Vec<u64>,\n}\n\nimpl StatAccum {\n    pub fn new(n_col: usize) -> Self {\n        Self {\n            n_col,\n            n_row: 0,\n            distinct: vec![0; n_col],\n        }\n    }\n\n    /// Push a row, indicating which column (0-indexed) is the first to differ\n    /// from the previous row. If this is the first row, pass 0.\n    ///\n    /// i_chng is the index of the leftmost column that changed:\n    /// - 0 means column 0 changed (or first row)\n    /// - 1 means columns 0 was same, column 1 changed\n    /// - n_col means all columns were the same (duplicate row)\n    pub fn push(&mut self, i_chng: usize) {\n        self.n_row += 1;\n        // Increment distinct counts for columns i_chng and onwards\n        // because if column i changed, then prefixes (0..=i), (0..=i+1), etc. all have a new distinct value\n        for i in i_chng..self.n_col {\n            self.distinct[i] += 1;\n        }\n    }\n\n    /// Get the stat1 string: \"total avg1 avg2 ...\"\n    /// where avgN = ceil(total / distinctN)\n    pub fn get_stat1(&self) -> String {\n        if self.n_row == 0 {\n            return String::new();\n        }\n\n        let mut parts = vec![self.n_row.to_string()];\n        for &d in &self.distinct {\n            let avg = if d > 0 {\n                self.n_row.div_ceil(d)\n            } else {\n                self.n_row\n            };\n            parts.push(avg.to_string());\n        }\n        parts.join(\" \")\n    }\n\n    /// Serialize to bytes for storage in a blob register.\n    pub fn to_bytes(&self) -> Vec<u8> {\n        let mut bytes = Vec::with_capacity(8 + 8 + 8 * self.n_col);\n        bytes.extend_from_slice(&(self.n_col as u64).to_le_bytes());\n        bytes.extend_from_slice(&self.n_row.to_le_bytes());\n        for &d in &self.distinct {\n            bytes.extend_from_slice(&d.to_le_bytes());\n        }\n        bytes\n    }\n\n    /// Deserialize from bytes.\n    pub fn from_bytes(bytes: &[u8]) -> Option<Self> {\n        if bytes.len() < 16 {\n            return None;\n        }\n        let n_col = u64::from_le_bytes(bytes[0..8].try_into().ok()?) as usize;\n        let n_row = u64::from_le_bytes(bytes[8..16].try_into().ok()?);\n\n        if bytes.len() < 16 + 8 * n_col {\n            return None;\n        }\n        let mut distinct = Vec::with_capacity(n_col);\n        for i in 0..n_col {\n            let start = 16 + i * 8;\n            let d = u64::from_le_bytes(bytes[start..start + 8].try_into().ok()?);\n            distinct.push(d);\n        }\n        Some(Self {\n            n_col,\n            n_row,\n            distinct,\n        })\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::parse_stat_numbers;\n\n    #[test]\n    fn parse_stat_numbers_basic() {\n        assert_eq!(parse_stat_numbers(\"10 5 3 1\").unwrap(), vec![10, 5, 3, 1]);\n        assert_eq!(parse_stat_numbers(\"  42\\t7 \").unwrap(), vec![42, 7]);\n        assert!(parse_stat_numbers(\"abc 1\").is_none());\n    }\n}\n"
  },
  {
    "path": "core/storage/btree.rs",
    "content": "use branches::{mark_unlikely, unlikely};\nuse rustc_hash::FxHashMap as HashMap;\n#[cfg(debug_assertions)]\nuse rustc_hash::FxHashSet as HashSet;\nuse smallvec::SmallVec;\nuse tracing::{instrument, Level};\n\nuse super::{\n    pager::PageRef,\n    sqlite3_ondisk::{\n        write_varint_to_vec, IndexInteriorCell, IndexLeafCell, OverflowCell, MINIMUM_CELL_SIZE,\n    },\n};\nuse crate::{\n    io::CompletionGroup,\n    io_yield_one,\n    schema::Index,\n    storage::{\n        pager::{BtreePageAllocMode, Pager},\n        sqlite3_ondisk::{\n            payload_overflows, read_u32, read_varint, write_varint, BTreeCell, DatabaseHeader,\n            PageContent, PageSize, PageType, TableInteriorCell, TableLeafCell, CELL_PTR_SIZE_BYTES,\n            FREELIST_LEAF_PTR_SIZE, FREELIST_TRUNK_HEADER_SIZE,\n            FREELIST_TRUNK_OFFSET_FIRST_LEAF_PTR, FREELIST_TRUNK_OFFSET_LEAF_COUNT,\n            FREELIST_TRUNK_OFFSET_NEXT_TRUNK_PTR, INTERIOR_PAGE_HEADER_SIZE_BYTES,\n            LEAF_PAGE_HEADER_SIZE_BYTES, LEFT_CHILD_PTR_SIZE_BYTES,\n        },\n        state_machines::{\n            AdvanceState, CountState, EmptyTableState, MoveToRightState, MoveToState, RewindState,\n            SeekEndState, SeekToLastState,\n        },\n    },\n    translate::plan::IterationDirection,\n    turso_assert,\n    types::{\n        find_compare, get_tie_breaker_from_seek_op, IOCompletions, IndexInfo, RecordCompare,\n        SeekResult,\n    },\n    util::IOExt,\n    vdbe::Register,\n    Completion, MvStore,\n};\nuse crate::{\n    numeric::Numeric,\n    return_corrupt, return_if_io,\n    types::{\n        compare_immutable_iter, AsValueRef, IOResult, ImmutableRecord, SeekKey, SeekOp, Value,\n        ValueRef,\n    },\n    LimboError, Result,\n};\nuse crate::{\n    turso_assert_eq, turso_assert_greater_than, turso_assert_greater_than_or_equal,\n    turso_assert_less_than, turso_assert_less_than_or_equal,\n};\nuse std::{\n    any::Any,\n    cmp::{Ordering, Reverse},\n    collections::BinaryHeap,\n    fmt::Debug,\n    ops::ControlFlow,\n    pin::Pin,\n    sync::Arc,\n};\n\n/// Maximum number of key values to store on the stack when converting registers to ValueRefs\n/// during seeking. Since we use a SmallVec it'll gracefully fall back to heap allocating beyond\n/// this threshold.\nconst STACK_ALLOC_KEY_VALS_MAX: usize = 16;\n\n/// The B-Tree page header is 12 bytes for interior pages and 8 bytes for leaf pages.\n///\n/// +--------+-----------------+-----------------+-----------------+--------+----- ..... ----+\n/// | Page   | First Freeblock | Cell Count      | Cell Content    | Frag.  | Right-most     |\n/// | Type   | Offset          |                 | Area Start      | Bytes  | pointer        |\n/// +--------+-----------------+-----------------+-----------------+--------+----- ..... ----+\n///     0        1        2        3        4        5        6        7        8       11\n///\npub mod offset {\n    /// Type of the B-Tree page (u8).\n    pub const BTREE_PAGE_TYPE: usize = 0;\n\n    /// A pointer to the first freeblock (u16).\n    ///\n    /// This field of the B-Tree page header is an offset to the first freeblock, or zero if\n    /// there are no freeblocks on the page.  A freeblock is a structure used to identify\n    /// unallocated space within a B-Tree page, organized as a chain.\n    ///\n    /// Please note that freeblocks do not mean the regular unallocated free space to the left\n    /// of the cell content area pointer, but instead blocks of at least 4\n    /// bytes WITHIN the cell content area that are not in use due to e.g.\n    /// deletions.\n    pub const BTREE_FIRST_FREEBLOCK: usize = 1;\n\n    /// The number of cells in the page (u16).\n    pub const BTREE_CELL_COUNT: usize = 3;\n\n    /// A pointer to the first byte of cell allocated content from top (u16).\n    ///\n    /// A zero value for this integer is interpreted as 65,536.\n    /// If a page contains no cells (which is only possible for a root page of a table that\n    /// contains no rows) then the offset to the cell content area will equal the page size minus\n    /// the bytes of reserved space. If the database uses a 65536-byte page size and the\n    /// reserved space is zero (the usual value for reserved space) then the cell content offset of\n    /// an empty page wants to be 6,5536\n    ///\n    /// SQLite strives to place cells as far toward the end of the b-tree page as it can, in\n    /// order to leave space for future growth of the cell pointer array. This means that the\n    /// cell content area pointer moves leftward as cells are added to the page.\n    pub const BTREE_CELL_CONTENT_AREA: usize = 5;\n\n    /// The number of fragmented bytes (u8).\n    ///\n    /// Fragments are isolated groups of 1, 2, or 3 unused bytes within the cell content area.\n    pub const BTREE_FRAGMENTED_BYTES_COUNT: usize = 7;\n\n    /// The right-most pointer (saved separately from cells) (u32)\n    pub const BTREE_RIGHTMOST_PTR: usize = 8;\n}\n\n/// Maximum depth of an SQLite B-Tree structure. Any B-Tree deeper than\n/// this will be declared corrupt. This value is calculated based on a\n/// maximum database size of 2^31 pages a minimum fanout of 2 for a\n/// root-node and 3 for all other internal nodes.\n///\n/// If a tree that appears to be taller than this is encountered, it is\n/// assumed that the database is corrupt.\npub const BTCURSOR_MAX_DEPTH: usize = 20;\n\n/// Maximum number of sibling pages that balancing is performed on.\npub const MAX_SIBLING_PAGES_TO_BALANCE: usize = 3;\n\n/// We only need maximum 5 pages to balance 3 pages, because we can guarantee that cells from 3 pages will fit in 5 pages.\npub const MAX_NEW_SIBLING_PAGES_AFTER_BALANCE: usize = 5;\n\n/// Validate cells in a page are in a valid state. Only in debug mode.\nmacro_rules! debug_validate_cells {\n    ($page_contents:expr, $usable_space:expr) => {\n        #[cfg(debug_assertions)]\n        {\n            debug_validate_cells_core($page_contents, $usable_space);\n        }\n    };\n}\n\n/// State machine of destroy operations\n/// Keep track of traversal so that it can be resumed when IO is encountered\n#[derive(Debug, Clone)]\nenum DestroyState {\n    Start,\n    LoadPage,\n    ProcessPage,\n    ClearOverflowPages { cell: BTreeCell },\n    FreePage,\n}\n\nstruct DestroyInfo {\n    state: DestroyState,\n}\n\n#[derive(Debug)]\nenum DeleteState {\n    Start,\n    DeterminePostBalancingSeekKey,\n    LoadPage {\n        post_balancing_seek_key: Option<CursorContext>,\n    },\n    FindCell {\n        post_balancing_seek_key: Option<CursorContext>,\n    },\n    ClearOverflowPages {\n        cell_idx: usize,\n        cell: BTreeCell,\n        original_child_pointer: Option<u32>,\n        post_balancing_seek_key: Option<CursorContext>,\n    },\n    InteriorNodeReplacement {\n        page: PageRef,\n        /// the btree level of the page where the cell replacement happened.\n        /// if the replacement causes the page to overflow/underflow, we need to remember it and balance it\n        /// after the deletion process is otherwise complete.\n        btree_depth: usize,\n        cell_idx: usize,\n        original_child_pointer: Option<u32>,\n        post_balancing_seek_key: Option<CursorContext>,\n    },\n    CheckNeedsBalancing {\n        /// same as `InteriorNodeReplacement::btree_depth`\n        btree_depth: usize,\n        post_balancing_seek_key: Option<CursorContext>,\n        interior_node_was_replaced: bool,\n    },\n    /// If an interior node was replaced, we need to move back up from the subtree to the interior cell\n    /// that now has the replaced content, so that the next invocation of BTreeCursor::next() does not\n    /// stop at that cell.\n    /// The reason it is important to land here is that the replaced cell was smaller (LT) than the deleted cell,\n    /// so we must ensure we skip over it. I.e., when BTreeCursor::next() is called, it will move past the cell\n    /// that holds the replaced content.\n    /// See: https://github.com/tursodatabase/turso/issues/3045\n    PostInteriorNodeReplacement,\n    Balancing {\n        /// If provided, will also balance an ancestor page at depth `balance_ancestor_at_depth`.\n        /// If not provided, balancing will stop as soon as a level is encountered where no balancing is required.\n        balance_ancestor_at_depth: Option<usize>,\n    },\n    RestoreContextAfterBalancing,\n}\n\n#[derive(Debug)]\npub enum OverwriteCellState {\n    /// Allocate a new payload for the cell.\n    AllocatePayload,\n    /// Fill the cell payload with the new payload.\n    FillPayload {\n        new_payload: Vec<u8>,\n        rowid: Option<i64>,\n        fill_cell_payload_state: FillCellPayloadState,\n    },\n    /// Clear the old cell's overflow pages and add them to the freelist.\n    /// Overwrite the cell with the new payload.\n    ClearOverflowPagesAndOverwrite {\n        new_payload: Vec<u8>,\n        old_offset: usize,\n        old_local_size: usize,\n    },\n}\n\nstruct BalanceContext {\n    pages_to_balance_new: [Option<PinGuard>; MAX_NEW_SIBLING_PAGES_AFTER_BALANCE],\n    sibling_count_new: usize,\n    cell_array: CellArray,\n    old_cell_count_per_page_cumulative: [u16; MAX_NEW_SIBLING_PAGES_AFTER_BALANCE],\n    #[cfg(debug_assertions)]\n    cells_debug: Vec<Vec<u8>>,\n}\n\nimpl std::fmt::Debug for BalanceContext {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"BalanceContext\")\n            .field(\"pages_to_balance_new\", &self.pages_to_balance_new)\n            .field(\"sibling_count_new\", &self.sibling_count_new)\n            .field(\"cell_array\", &self.cell_array)\n            .field(\n                \"old_cell_count_per_page_cumulative\",\n                &self.old_cell_count_per_page_cumulative,\n            )\n            .finish()\n    }\n}\n\n#[derive(Debug, Default)]\n/// State machine of a btree rebalancing operation.\nenum BalanceSubState {\n    #[default]\n    Start,\n    BalanceRoot,\n    Decide,\n    Quick,\n    /// Choose which sibling pages to balance (max 3).\n    /// Generally, the siblings involved will be the page that triggered the balancing and its left and right siblings.\n    /// The exceptions are:\n    /// 1. If the leftmost page triggered balancing, up to 3 leftmost pages will be balanced.\n    /// 2. If the rightmost page triggered balancing, up to 3 rightmost pages will be balanced.\n    NonRootPickSiblings,\n    /// Perform the actual balancing. This will result in 1-5 pages depending on the number of total cells to be distributed\n    /// from the source pages.\n    NonRootDoBalancing,\n    NonRootDoBalancingAllocate {\n        i: usize,\n        context: Option<BalanceContext>,\n    },\n    NonRootDoBalancingFinish {\n        context: BalanceContext,\n    },\n    /// Free pages that are not used anymore after balancing.\n    FreePages {\n        curr_page: usize,\n        sibling_count_new: usize,\n    },\n}\n\n#[derive(Debug, Default)]\nstruct BalanceState {\n    sub_state: BalanceSubState,\n    balance_info: Option<BalanceInfo>,\n    /// Reusable buffers for divider cell payloads.\n    /// These persist across balance operations to avoid repeated allocations.\n    /// We use Vec<u8> with clear/resize instead of allocating new each time.\n    reusable_divider_buffers: [Vec<u8>; MAX_SIBLING_PAGES_TO_BALANCE - 1],\n    /// Reusable Vec for CellArray cell_payloads to avoid per-balance allocation.\n    /// Cleared before each use; grows as needed and retains capacity across operations.\n    reusable_cell_payloads: Vec<&'static mut [u8]>,\n}\n\n/// State machine of a write operation.\n/// May involve balancing due to overflow.\n#[derive(Debug)]\nenum WriteState {\n    Start,\n    /// Overwrite an existing cell.\n    /// In addition to deleting the old cell and writing a new one,\n    /// we may also need to clear the old cell's overflow pages\n    /// and add them to the freelist.\n    Overwrite {\n        page: PageRef,\n        cell_idx: usize,\n        // This is an Option although it's not optional; we `take` it as owned for [BTreeCursor::overwrite_cell]\n        // to work around the borrow checker, and then insert it back if overwriting returns IO.\n        state: Option<OverwriteCellState>,\n    },\n    /// Insert a new cell. This path is taken when inserting a new row.\n    Insert {\n        page: PageRef,\n        cell_idx: usize,\n        new_payload: Vec<u8>,\n        fill_cell_payload_state: FillCellPayloadState,\n    },\n    Balancing,\n    Finish,\n}\n\nstruct ReadPayloadOverflow {\n    payload: Vec<u8>,\n    next_page: u32,\n    remaining_to_read: usize,\n    page: PageRef,\n}\n\n#[derive(Debug)]\npub struct PinGuard(PageRef);\nimpl PinGuard {\n    pub fn new(p: PageRef) -> Self {\n        p.pin();\n        Self(p)\n    }\n}\n\n// Since every Drop will unpin, every clone\n// needs to add to the pin count\nimpl Clone for PinGuard {\n    fn clone(&self) -> Self {\n        self.0.pin();\n        Self(self.0.clone())\n    }\n}\n\nimpl PinGuard {\n    pub fn to_page(&self) -> PageRef {\n        self.0.clone()\n    }\n}\n\nimpl Drop for PinGuard {\n    fn drop(&mut self) {\n        self.0.try_unpin();\n    }\n}\n\nimpl std::ops::Deref for PinGuard {\n    type Target = PageRef;\n    fn deref(&self) -> &Self::Target {\n        &self.0\n    }\n}\n\n#[derive(Clone, Debug)]\npub enum BTreeKey<'a> {\n    TableRowId((i64, Option<&'a ImmutableRecord>)),\n    IndexKey(&'a ImmutableRecord),\n}\n\nimpl BTreeKey<'_> {\n    /// Create a new table rowid key from a rowid and an optional immutable record.\n    /// The record is optional because it may not be available when the key is created.\n    pub fn new_table_rowid(rowid: i64, record: Option<&ImmutableRecord>) -> BTreeKey<'_> {\n        BTreeKey::TableRowId((rowid, record))\n    }\n\n    /// Create a new index key from an immutable record.\n    pub fn new_index_key(record: &ImmutableRecord) -> BTreeKey<'_> {\n        BTreeKey::IndexKey(record)\n    }\n\n    /// Get the record, if present. Index will always be present,\n    pub fn get_record(&self) -> Option<&'_ ImmutableRecord> {\n        match self {\n            BTreeKey::TableRowId((_, record)) => *record,\n            BTreeKey::IndexKey(record) => Some(record),\n        }\n    }\n\n    /// Get the rowid, if present. Index will never be present.\n    pub fn maybe_rowid(&self) -> Option<i64> {\n        match self {\n            BTreeKey::TableRowId((rowid, _)) => Some(*rowid),\n            BTreeKey::IndexKey(_) => None,\n        }\n    }\n\n    /// Assert that the key is an integer rowid and return it.\n    fn to_rowid(&self) -> i64 {\n        match self {\n            BTreeKey::TableRowId((rowid, _)) => *rowid,\n            BTreeKey::IndexKey(_) => panic!(\"BTreeKey::to_rowid called on IndexKey\"),\n        }\n    }\n}\n\n#[derive(Debug, Clone)]\nstruct BalanceInfo {\n    /// Old pages being balanced. We can have maximum 3 pages being balanced at the same time.\n    pages_to_balance: [Option<PinGuard>; MAX_SIBLING_PAGES_TO_BALANCE],\n    /// Bookkeeping of the rightmost pointer so the offset::BTREE_RIGHTMOST_PTR can be updated.\n    rightmost_pointer: *mut u8,\n    /// Number of siblings being used to balance\n    sibling_count: usize,\n    /// First divider cell to remove that marks the first sibling\n    first_divider_cell: usize,\n    /// Reusable buffer for constructing new divider cells during balance.\n    /// Avoids allocating a new Vec for each sibling during balance_non_root.\n    reusable_divider_cell: Vec<u8>,\n}\n\n// SAFETY: Need to guarantee during balancing that we do not modify the rightmost pointer on the pointee `PageContent`\n// safe as long as the Balance Algorithm does not modify the pointer\nunsafe impl Send for BalanceInfo {}\nunsafe impl Sync for BalanceInfo {}\n\n/// Holds the state machine for the operation that was in flight when the cursor\n/// was suspended due to IO.\nenum CursorState {\n    None,\n    /// The cursor is in a write operation.\n    Write(WriteState),\n    Destroy(DestroyInfo),\n    Delete(DeleteState),\n}\n\nimpl CursorState {\n    fn destroy_info(&self) -> Option<&DestroyInfo> {\n        match self {\n            CursorState::Destroy(x) => Some(x),\n            _ => None,\n        }\n    }\n    fn mut_destroy_info(&mut self) -> Option<&mut DestroyInfo> {\n        match self {\n            CursorState::Destroy(x) => Some(x),\n            _ => None,\n        }\n    }\n}\n\nimpl Debug for CursorState {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Self::Delete(..) => write!(f, \"Delete\"),\n            Self::Destroy(..) => write!(f, \"Destroy\"),\n            Self::None => write!(f, \"None\"),\n            Self::Write(..) => write!(f, \"Write\"),\n        }\n    }\n}\n\n#[derive(Debug, Clone)]\nenum OverflowState {\n    Start,\n    ProcessPage { next_page: PageRef },\n    Done,\n}\n\n/// Holds a Record or RowId, so that these can be transformed into a SeekKey to restore\n/// cursor position to its previous location.\n#[derive(Debug)]\npub enum CursorContextKey {\n    TableRowId(i64),\n\n    /// If we are in an index tree we can then reuse this field to save\n    /// our cursor information\n    IndexKeyRowId(ImmutableRecord),\n}\n\n#[derive(Debug)]\npub struct CursorContext {\n    pub key: CursorContextKey,\n    pub seek_op: SeekOp,\n}\n\nimpl CursorContext {\n    fn seek_eq_only(key: &BTreeKey<'_>) -> Self {\n        Self {\n            key: key.into(),\n            seek_op: SeekOp::GE { eq_only: true },\n        }\n    }\n}\n\nimpl From<&BTreeKey<'_>> for CursorContextKey {\n    fn from(key: &BTreeKey<'_>) -> Self {\n        match key {\n            BTreeKey::TableRowId((rowid, _)) => CursorContextKey::TableRowId(*rowid),\n            BTreeKey::IndexKey(record) => CursorContextKey::IndexKeyRowId((*record).clone()),\n        }\n    }\n}\n\n/// In the future, we may expand these general validity states\n#[derive(Debug, PartialEq, Eq)]\npub enum CursorValidState {\n    /// Cursor does not point to a valid entry, and Btree will never yield a record.\n    Invalid,\n    /// Cursor is pointing a to an existing location/cell in the Btree\n    Valid,\n    /// Cursor may be pointing to a non-existent location/cell. This can happen after balancing operations\n    RequireSeek,\n    /// Cursor requires an advance after a seek\n    RequireAdvance(IterationDirection),\n}\n\n#[derive(Debug, Clone, Copy)]\npub struct InteriorPageBinarySearchState {\n    min_cell_idx: isize,\n    max_cell_idx: isize,\n    nearest_matching_cell: Option<usize>,\n    eq_seen: bool,\n}\n\n#[derive(Debug, Clone, Copy)]\npub struct LeafPageBinarySearchState {\n    min_cell_idx: isize,\n    max_cell_idx: isize,\n    nearest_matching_cell: Option<usize>,\n    /// Indicates if we have seen an exact match during the downwards traversal of the btree.\n    /// This is only needed in index seeks, in cases where we need to determine whether we call\n    /// an additional next()/prev() to fetch a matching record from an interior node. We will not\n    /// do that if both are true:\n    /// 1. We have not seen an EQ during the traversal\n    /// 2. We are looking for an exact match ([SeekOp::GE] or [SeekOp::LE] with eq_only: true)\n    eq_seen: bool,\n    /// In multiple places, we do a seek that checks for an exact match (SeekOp::EQ) in the tree.\n    /// In those cases, we need to know where to land if we don't find an exact match in the leaf page.\n    /// For non-eq-only conditions (GT, LT, GE, LE), this is pretty simple:\n    /// - If we are looking for GT/GE and don't find a match, we should end up beyond the end of the page (idx=cell count).\n    /// - If we are looking for LT/LE and don't find a match, we should end up before the beginning of the page (idx=-1).\n    ///\n    /// For eq-only conditions (GE { eq_only: true } or LE { eq_only: true }), we need to know where to land if we don't find an exact match.\n    /// For GE, we want to land at the first cell that is greater than the seek key.\n    /// For LE, we want to land at the last cell that is less than the seek key.\n    /// This is because e.g. when we attempt to insert rowid 666, we first check if it exists.\n    /// If it doesn't, we want to land in the place where rowid 666 WOULD be inserted.\n    target_cell_when_not_found: i32,\n}\n\n#[derive(Debug)]\n/// State used for seeking\npub enum CursorSeekState {\n    Start,\n    MovingBetweenPages {\n        eq_seen: bool,\n    },\n    InteriorPageBinarySearch {\n        state: InteriorPageBinarySearchState,\n    },\n    FoundLeaf {\n        eq_seen: bool,\n    },\n    LeafPageBinarySearch {\n        state: LeafPageBinarySearchState,\n    },\n}\n\npub trait CursorTrait: Any + Send + Sync {\n    /// Move cursor to last entry.\n    fn last(&mut self) -> Result<IOResult<()>>;\n    /// Move cursor to next entry.\n    fn next(&mut self) -> Result<IOResult<()>>;\n    /// Move cursor to previous entry.\n    fn prev(&mut self) -> Result<IOResult<()>>;\n    /// Get the rowid of the entry the cursor is poiting to if any\n    fn rowid(&mut self) -> Result<IOResult<Option<i64>>>;\n    /// Get the record of the entry the cursor is poiting to if any\n    fn record(&mut self) -> Result<IOResult<Option<&ImmutableRecord>>>;\n    /// Move the cursor based on the key and the type of operation (op).\n    fn seek(&mut self, key: SeekKey<'_>, op: SeekOp) -> Result<IOResult<SeekResult>>;\n    /// Seek using registers directly without serializing them into an ImmutableRecord first.\n    /// This avoids heap allocation and serialization overhead in hot paths like index lookups.\n    fn seek_unpacked(&mut self, registers: &[Register], op: SeekOp)\n        -> Result<IOResult<SeekResult>>;\n    /// Insert a record in the position the cursor is at.\n    fn insert(&mut self, key: &BTreeKey) -> Result<IOResult<()>>;\n    /// Delete a record in the position the cursor is at.\n    fn delete(&mut self) -> Result<IOResult<()>>;\n    fn set_null_flag(&mut self, flag: bool);\n    fn get_null_flag(&self) -> bool;\n    /// Check if a key exists.\n    fn exists(&mut self, key: &Value) -> Result<IOResult<bool>>;\n    fn clear_btree(&mut self) -> Result<IOResult<Option<usize>>>;\n    fn btree_destroy(&mut self) -> Result<IOResult<Option<usize>>>;\n    /// Count the number of entries in the b-tree\n    ///\n    /// Only supposed to be used in the context of a simple Count Select Statement\n    fn count(&mut self) -> Result<IOResult<usize>>;\n    fn is_empty(&self) -> bool;\n    fn root_page(&self) -> i64;\n    /// Move cursor at the start.\n    fn rewind(&mut self) -> Result<IOResult<()>>;\n    /// Check if cursor is poiting at a valid entry with a record.\n    fn has_record(&self) -> bool;\n    fn set_has_record(&mut self, has_record: bool);\n    fn get_index_info(&self) -> &Arc<IndexInfo>;\n\n    fn seek_end(&mut self) -> Result<IOResult<()>>;\n    fn seek_to_last(&mut self, always_seek: bool) -> Result<IOResult<()>>;\n\n    /// Returns true if this cursor operates in MVCC mode.\n    fn is_mvcc(&self) -> bool {\n        false\n    }\n\n    // --- start: BTreeCursor specific functions ----\n    fn invalidate_record(&mut self);\n    fn has_rowid(&self) -> bool;\n    fn get_pager(&self) -> Arc<Pager>;\n    fn get_skip_advance(&self) -> bool;\n    /// Invalidate cached navigation state. Must be called on cursors that\n    /// share a btree (e.g. OpenDup cursors) when the btree structure is\n    /// modified by another cursor (e.g. clear_btree via ResetSorter).\n    fn invalidate_btree_cache(&mut self) {}\n    // --- end: BTreeCursor specific functions ----\n}\n\npub struct BTreeCursor {\n    /// The pager that is used to read and write to the database file.\n    pub pager: Arc<Pager>,\n    /// Cached value of the usable space of a BTree page, since it is very expensive to call in a hot loop via pager.usable_space().\n    /// This is OK to cache because both 'PRAGMA page_size' and '.filectrl reserve_bytes' only have an effect on:\n    /// 1. an uninitialized database,\n    /// 2. an initialized database when the command is immediately followed by VACUUM.\n    usable_space_cached: usize,\n    /// Page id of the root page used to go back up fast.\n    root_page: i64,\n    /// Rowid and record are stored before being consumed.\n    pub has_record: bool,\n    null_flag: bool,\n    /// Index internal pages are consumed on the way up, so we store going upwards flag in case\n    /// we just moved to a parent page and the parent page is an internal index page which requires\n    /// to be consumed.\n    going_upwards: bool,\n    /// Information maintained across execution attempts when an operation yields due to I/O.\n    state: CursorState,\n    /// State machine for balancing.\n    balance_state: BalanceState,\n    /// Information maintained while freeing overflow pages. Maintained separately from cursor state since\n    /// any method could require freeing overflow pages\n    overflow_state: OverflowState,\n    /// Page stack used to traverse the btree.\n    /// Each cursor has a stack because each cursor traverses the btree independently.\n    stack: PageStack,\n    /// Reusable immutable record, used to allow better allocation strategy.\n    reusable_immutable_record: Option<ImmutableRecord>,\n    /// Information about the index key structure (sort order, collation, etc)\n    pub index_info: Option<Arc<IndexInfo>>,\n    /// Maintain count of the number of records in the btree. Used for the `Count` opcode\n    count: usize,\n    /// Stores the cursor context before rebalancing so that a seek can be done later\n    context: Option<CursorContext>,\n    /// Store whether the Cursor is in a valid state. Meaning if it is pointing to a valid cell index or not\n    pub valid_state: CursorValidState,\n    seek_state: CursorSeekState,\n    /// Separate state to read a record with overflow pages. This separation from `state` is necessary as\n    /// we can be in a function that relies on `state`, but also needs to process overflow pages\n    read_overflow_state: Option<ReadPayloadOverflow>,\n    /// State machine for [BTreeCursor::is_empty_table]\n    is_empty_table_state: EmptyTableState,\n    /// State machine for [BTreeCursor::move_to_rightmost] and, optionally, the id of the rightmost page in the btree.\n    /// If we know the rightmost page id and are already on that page, we can skip a seek.\n    move_to_right_state: (MoveToRightState, Option<usize>),\n    /// State machine for [BTreeCursor::seek_to_last]\n    seek_to_last_state: SeekToLastState,\n    /// State machine for [BTreeCursor::rewind]\n    rewind_state: RewindState,\n    /// State machine for [BTreeCursor::next] and [BTreeCursor::prev]\n    advance_state: AdvanceState,\n    /// State machine for [BTreeCursor::count]\n    count_state: CountState,\n    /// State machine for [BTreeCursor::seek_end]\n    seek_end_state: SeekEndState,\n    /// State machine for [BTreeCursor::move_to]\n    move_to_state: MoveToState,\n    /// Whether the next call to [BTreeCursor::next()] should be a no-op.\n    /// This is currently only used after a delete operation causes a rebalancing.\n    /// Advancing is only skipped if the cursor is currently pointing to a valid record\n    /// when next() is called.\n    pub skip_advance: bool,\n    /// Reusable buffer for cell payloads during insert/update operations.\n    /// This avoids allocating a new Vec for each write operation.\n    reusable_cell_payload: Vec<u8>,\n}\n\ncrate::assert::assert_send!(BTreeCursor);\ncrate::assert::assert_sync!(BTreeCursor);\n\n/// We store the cell index and cell count for each page in the stack.\n/// The reason we store the cell count is because we need to know when we are at the end of the page,\n/// without having to perform IO to get the ancestor pages.\n#[derive(Debug, Clone, Copy, Default)]\nstruct BTreeNodeState {\n    cell_idx: i32,\n    cell_count: Option<i32>,\n}\n\nimpl BTreeNodeState {\n    /// Check if the current cell index is at the end of the page.\n    /// This information is used to determine whether a child page should move up to its parent.\n    /// If the child page is the rightmost leaf page and it has reached the end, this means all of its ancestors have\n    /// already reached the end, so it should not go up because there are no more records to traverse.\n    fn is_at_end(&self) -> bool {\n        let cell_count = self.cell_count.expect(\"cell_count is not set\");\n        // cell_idx == cell_count means: we will traverse to the rightmost pointer next.\n        // cell_idx == cell_count + 1 means: we have already gone down to the rightmost pointer.\n        self.cell_idx == cell_count + 1\n    }\n}\n\nimpl BTreeCursor {\n    pub fn new(pager: Arc<Pager>, root_page: i64, _num_columns: usize) -> Self {\n        let valid_state = if root_page == 1 && !pager.db_initialized() {\n            CursorValidState::Invalid\n        } else {\n            CursorValidState::Valid\n        };\n        let usable_space = pager.usable_space();\n        Self {\n            pager,\n            root_page,\n            usable_space_cached: usable_space,\n            has_record: false,\n            null_flag: false,\n            going_upwards: false,\n            state: CursorState::None,\n            balance_state: BalanceState::default(),\n            overflow_state: OverflowState::Start,\n            stack: PageStack {\n                current_page: -1,\n                node_states: [BTreeNodeState::default(); BTCURSOR_MAX_DEPTH + 1],\n                stack: [const { None }; BTCURSOR_MAX_DEPTH + 1],\n            },\n            reusable_immutable_record: None,\n            index_info: None,\n            count: 0,\n            context: None,\n            valid_state,\n            seek_state: CursorSeekState::Start,\n            read_overflow_state: None,\n            is_empty_table_state: EmptyTableState::Start,\n            move_to_right_state: (MoveToRightState::Start, None),\n            seek_to_last_state: SeekToLastState::Start,\n            rewind_state: RewindState::Start,\n            advance_state: AdvanceState::Start,\n            count_state: CountState::Start,\n            seek_end_state: SeekEndState::Start,\n            move_to_state: MoveToState::Start,\n            skip_advance: false,\n            reusable_cell_payload: Vec::new(),\n        }\n    }\n\n    pub fn new_table(pager: Arc<Pager>, root_page: i64, num_columns: usize) -> Self {\n        Self::new(pager, root_page, num_columns)\n    }\n\n    pub fn new_index(pager: Arc<Pager>, root_page: i64, index: &Index, num_columns: usize) -> Self {\n        let mut cursor = Self::new(pager, root_page, num_columns);\n        cursor.index_info = Some(Arc::new(IndexInfo::new_from_index(index)));\n        cursor\n    }\n\n    /// Resets the cached count state so the next `count()` call re-traverses the\n    /// btree. Must be called after any mutation (insert, delete, clear) that may\n    /// change the number of rows in the tree.\n    fn invalidate_count_cache(&mut self) {\n        self.count_state = CountState::Start;\n        self.count = 0;\n    }\n\n    pub fn get_index_rowid_from_record(&self) -> Option<i64> {\n        if !self.has_rowid() {\n            return None;\n        }\n        let rowid = match self.get_immutable_record().as_ref().unwrap().last_value() {\n            Some(Ok(ValueRef::Numeric(Numeric::Integer(rowid)))) => rowid,\n            _ => unreachable!(\n                \"index where has_rowid() is true should have an integer rowid as the last value\"\n            ),\n        };\n        Some(rowid)\n    }\n\n    /// Check if the table is empty.\n    /// This is done by checking if the root page has no cells.\n    #[cfg_attr(debug_assertions, instrument(skip_all, level = Level::DEBUG))]\n    fn is_empty_table(&mut self) -> Result<IOResult<bool>> {\n        loop {\n            let state = self.is_empty_table_state.clone();\n            match state {\n                EmptyTableState::Start => {\n                    let (page, c) = self.pager.read_page(self.root_page)?;\n                    self.is_empty_table_state = EmptyTableState::ReadPage { page };\n                    if let Some(c) = c {\n                        io_yield_one!(c);\n                    }\n                }\n                EmptyTableState::ReadPage { page } => {\n                    turso_assert!(page.is_loaded(), \"page should be loaded\");\n                    let cell_count = page.get_contents().cell_count();\n                    break Ok(IOResult::Done(cell_count == 0));\n                }\n            }\n        }\n    }\n\n    /// Move the cursor to the previous record and return it.\n    /// Used in backwards iteration.\n    #[cfg_attr(debug_assertions, instrument(skip(self), level = Level::DEBUG, name = \"prev\"))]\n    pub fn get_prev_record(&mut self) -> Result<IOResult<()>> {\n        let mut inner = || {\n            loop {\n                let (old_top_idx, page_type, is_index, is_leaf, cell_count) = {\n                    let page = self.stack.top_ref();\n                    let contents = page.get_contents();\n                    (\n                        self.stack.current(),\n                        contents.page_type()?,\n                        page.is_index()?,\n                        contents.is_leaf(),\n                        contents.cell_count(),\n                    )\n                };\n\n                let cell_idx = self.stack.current_cell_index();\n\n                // If we are at the end of the page and we haven't just come back from the right child,\n                // we now need to move to the rightmost child.\n                if cell_idx == i32::MAX && !self.going_upwards {\n                    let rightmost_pointer =\n                        self.stack.top_ref().get_contents().rightmost_pointer()?;\n                    if let Some(rightmost_pointer) = rightmost_pointer {\n                        let past_rightmost_pointer = cell_count as i32 + 1;\n                        self.stack.set_cell_index(past_rightmost_pointer);\n                        let (page, c) = self.read_page(rightmost_pointer as i64)?;\n                        self.descend_backwards(page);\n                        if let Some(c) = c {\n                            io_yield_one!(c);\n                        }\n                        continue;\n                    }\n                }\n\n                if cell_idx >= cell_count as i32 {\n                    self.stack.set_cell_index(cell_count as i32 - 1);\n                } else if !self.stack.current_cell_index_less_than_min() {\n                    // skip retreat in case we still haven't visited this cell in index\n                    let should_visit_internal_node = is_index && self.going_upwards; // we are going upwards, this means we still need to visit divider cell in an index\n                    if should_visit_internal_node {\n                        self.going_upwards = false;\n                        return Ok(IOResult::Done(true));\n                    } else if matches!(\n                        page_type,\n                        PageType::IndexLeaf | PageType::TableLeaf | PageType::TableInterior\n                    ) {\n                        self.stack.retreat();\n                    }\n                }\n                // moved to beginning of current page\n                // todo: find a better way to flag moved to end or begin of page\n                if self.stack.current_cell_index_less_than_min() {\n                    loop {\n                        if self.stack.current_cell_index() >= 0 {\n                            break;\n                        }\n                        if self.stack.has_parent() {\n                            self.pop_upwards();\n                        } else {\n                            // moved to begin of btree\n                            return Ok(IOResult::Done(false));\n                        }\n                    }\n                    // continue to next loop to get record from the new page\n                    continue;\n                }\n                if is_leaf {\n                    return Ok(IOResult::Done(true));\n                }\n\n                if is_index && self.going_upwards {\n                    // If we are going upwards, we need to visit the divider cell before going back to another child page.\n                    // This is because index interior cells have payloads, so unless we do this we will be skipping an entry when traversing the tree.\n                    self.going_upwards = false;\n                    return Ok(IOResult::Done(true));\n                }\n\n                let cell_idx = self.stack.current_cell_index() as usize;\n                let left_child_page = self\n                    .stack\n                    .get_page_contents_at_level(old_top_idx)\n                    .unwrap()\n                    .cell_interior_read_left_child_page(cell_idx)?;\n\n                if page_type == PageType::IndexInterior {\n                    // In backwards iteration, if we haven't just moved to this interior node from the\n                    // right child, but instead are about to move to the left child, we need to retreat\n                    // so that we don't come back to this node again.\n                    // For example:\n                    // this parent: key 666\n                    // left child has: key 663, key 664, key 665\n                    // we need to move to the previous parent (with e.g. key 662) when iterating backwards.\n                    self.stack.retreat();\n                }\n\n                let (mem_page, c) = self.read_page(left_child_page as i64)?;\n                self.descend_backwards(mem_page);\n                if let Some(c) = c {\n                    io_yield_one!(c);\n                }\n            }\n        };\n\n        let has_record = return_if_io!(inner());\n        self.invalidate_record();\n        self.set_has_record(has_record);\n        Ok(IOResult::Done(()))\n    }\n\n    /// Reads the record of a cell that has overflow pages. This is a state machine that requires to be called until completion so everything\n    /// that calls this function should be reentrant.\n    #[cfg_attr(debug_assertions, instrument(skip_all, level = Level::DEBUG))]\n    fn process_overflow_read(\n        &mut self,\n        payload: &'static [u8],\n        start_next_page: u32,\n        payload_size: u64,\n    ) -> Result<IOResult<()>> {\n        loop {\n            if self.read_overflow_state.is_none() {\n                let (page, c) = self.read_page(start_next_page as i64)?;\n                self.read_overflow_state.replace(ReadPayloadOverflow {\n                    payload: payload.to_vec(),\n                    next_page: start_next_page,\n                    remaining_to_read: payload_size as usize - payload.len(),\n                    page,\n                });\n                if let Some(c) = c {\n                    io_yield_one!(c);\n                }\n                continue;\n            }\n            let ReadPayloadOverflow {\n                payload,\n                remaining_to_read,\n                next_page,\n                page,\n                ..\n            } = self.read_overflow_state.as_mut().unwrap();\n\n            turso_assert!(page.is_loaded(), \"page should be loaded\");\n            tracing::debug!(next_page, remaining_to_read, \"reading overflow page\");\n            let contents = page.get_contents();\n            // The first four bytes of each overflow page are a big-endian integer which is the page number of the next page in the chain, or zero for the final page in the chain.\n            let next = contents.read_u32_no_offset(0);\n            let buf = contents.as_ptr();\n            let usable_space = self.pager.usable_space();\n            let to_read = (*remaining_to_read).min(usable_space - 4);\n            payload.extend_from_slice(&buf[4..4 + to_read]);\n            *remaining_to_read -= to_read;\n\n            if *remaining_to_read != 0 && next != 0 {\n                let (new_page, c) = self.read_page(next as i64)?;\n                let ReadPayloadOverflow {\n                    next_page, page, ..\n                } = self.read_overflow_state.as_mut().unwrap();\n                *page = new_page;\n                *next_page = next;\n                if let Some(c) = c {\n                    io_yield_one!(c);\n                }\n                continue;\n            }\n            turso_assert!(\n                *remaining_to_read == 0 && next == 0,\n                \"we can't have more pages to read while also have read everything\"\n            );\n            let payload_swap = std::mem::take(payload);\n\n            let mut reuse_immutable = self.get_immutable_record_or_create();\n            reuse_immutable.as_mut().unwrap().invalidate();\n\n            reuse_immutable\n                .as_mut()\n                .unwrap()\n                .start_serialization(&payload_swap);\n\n            let _ = self.read_overflow_state.take();\n            break Ok(IOResult::Done(()));\n        }\n    }\n\n    /// Check if any ancestor pages still have cells to iterate.\n    /// If not, traversing back up to parent is of no use because we are at the end of the tree.\n    fn ancestor_pages_have_more_children(&self) -> bool {\n        let node_states = self.stack.node_states;\n        (0..self.stack.current())\n            .rev()\n            .any(|idx| !node_states[idx].is_at_end())\n    }\n\n    /// Move the cursor to the next record and return it.\n    /// Used in forwards iteration, which is the default.\n    #[cfg_attr(debug_assertions, instrument(skip(self), level = Level::DEBUG, name = \"next\"))]\n    pub fn get_next_record(&mut self) -> Result<IOResult<()>> {\n        let mut inner = || {\n            if self.stack.current_page == -1 {\n                // This can happen in nested left joins. See:\n                // https://github.com/tursodatabase/turso/issues/2924\n                return Ok(IOResult::Done(false));\n            }\n            loop {\n                let mem_page = self.stack.top_ref();\n                let contents = mem_page.get_contents();\n                let cell_idx = self.stack.current_cell_index();\n                let cell_count = contents.cell_count();\n                let is_leaf = contents.is_leaf();\n                if cell_idx != -1 && is_leaf && cell_idx as usize + 1 < cell_count {\n                    self.stack.advance();\n                    return Ok(IOResult::Done(true));\n                }\n\n                let mem_page = mem_page.clone();\n                let contents = mem_page.get_contents();\n                tracing::debug!(\n                    id = mem_page.get().id,\n                    cell = self.stack.current_cell_index(),\n                    cell_count,\n                    \"current_before_advance\",\n                );\n\n                let is_index = mem_page.is_index()?;\n                let should_skip_advance = is_index\n                && self.going_upwards // we are going upwards, this means we still need to visit divider cell in an index\n                && self.stack.current_cell_index() >= 0 && self.stack.current_cell_index() < cell_count as i32; // if we weren't on a\n                                                                                                                // valid cell then it means we will have to move upwards again or move to right page,\n                                                                                                                // anyways, we won't visit this invalid cell index\n                if should_skip_advance {\n                    tracing::debug!(\n                        going_upwards = self.going_upwards,\n                        page = mem_page.get().id,\n                        cell_idx = self.stack.current_cell_index(),\n                        \"skipping advance\",\n                    );\n                    self.going_upwards = false;\n                    return Ok(IOResult::Done(true));\n                }\n\n                // Important to advance only after loading the page in order to not advance > 1 times\n                self.stack.advance();\n                let cell_idx = self.stack.current_cell_index() as usize;\n                tracing::debug!(id = mem_page.get().id, cell = cell_idx, \"current\");\n\n                if cell_idx >= cell_count {\n                    let rightmost_already_traversed = cell_idx > cell_count;\n                    match (contents.rightmost_pointer()?, rightmost_already_traversed) {\n                        (Some(right_most_pointer), false) => {\n                            // do rightmost\n                            self.stack.advance();\n                            let (mem_page, c) = self.read_page(right_most_pointer as i64)?;\n                            self.descend(mem_page);\n                            if let Some(c) = c {\n                                io_yield_one!(c);\n                            }\n                            continue;\n                        }\n                        _ => {\n                            if self.ancestor_pages_have_more_children() {\n                                tracing::trace!(\"moving simple upwards\");\n                                self.pop_upwards();\n                                continue;\n                            } else {\n                                // If none of the ancestor pages have more children to iterate, that means we are at the end of the btree and should stop iterating.\n                                return Ok(IOResult::Done(false));\n                            }\n                        }\n                    }\n                }\n\n                turso_assert!(\n                    cell_idx < cell_count,\n                    \"cell index out of bounds\",\n                    { \"cell_idx\": cell_idx, \"cell_count\": cell_count, \"page_type\": contents.page_type().ok(), \"page_id\": mem_page.get().id }\n                );\n\n                if is_leaf {\n                    return Ok(IOResult::Done(true));\n                }\n                if is_index && self.going_upwards {\n                    // This means we just came up from a child, so now we need to visit the divider cell before going back to another child page.\n                    // This is because index interior cells have payloads, so unless we do this we will be skipping an entry when traversing the tree.\n                    self.going_upwards = false;\n                    return Ok(IOResult::Done(true));\n                }\n\n                let left_child_page = contents.cell_interior_read_left_child_page(cell_idx)?;\n                let (mem_page, c) = self.read_page(left_child_page as i64)?;\n                self.descend(mem_page);\n                if let Some(c) = c {\n                    io_yield_one!(c);\n                }\n            }\n        };\n        let has_record = return_if_io!(inner());\n        self.invalidate_record();\n        self.set_has_record(has_record);\n        Ok(IOResult::Done(()))\n    }\n\n    /// Move the cursor to the record that matches the seek key and seek operation.\n    /// This may be used to seek to a specific record in a point query (e.g. SELECT * FROM table WHERE col = 10)\n    /// or e.g. find the first record greater than the seek key in a range query (e.g. SELECT * FROM table WHERE col > 10).\n    /// We don't include the rowid in the comparison and that's why the last value from the record is not included.\n    fn do_seek(&mut self, key: SeekKey<'_>, op: SeekOp) -> Result<IOResult<SeekResult>> {\n        let ret = return_if_io!(match key {\n            SeekKey::TableRowId(rowid) => {\n                self.tablebtree_seek(rowid, op)\n            }\n            SeekKey::IndexKey(index_key) => {\n                self.indexbtree_seek(index_key, op)\n            }\n        });\n        self.valid_state = CursorValidState::Valid;\n        Ok(IOResult::Done(ret))\n    }\n\n    fn do_seek_unpacked(\n        &mut self,\n        registers: &[Register],\n        op: SeekOp,\n    ) -> Result<IOResult<SeekResult>> {\n        let ret = return_if_io!(self.indexbtree_seek_unpacked(registers, op));\n        self.valid_state = CursorValidState::Valid;\n        Ok(IOResult::Done(ret))\n    }\n\n    /// Pop the stack and mark that we are going upwards in the B-tree.\n    /// This is the only place where `going_upwards` should be set to `true`.\n    fn pop_upwards(&mut self) {\n        self.going_upwards = true;\n        self.stack.pop();\n    }\n\n    /// Descend into a child page during forward iteration.\n    /// Clears the `going_upwards` flag — once we descend, we are no longer going upwards.\n    fn descend(&mut self, page: PageRef) {\n        self.going_upwards = false;\n        self.stack.push(page);\n    }\n\n    /// Descend into a child page during backward iteration.\n    /// Clears the `going_upwards` flag — once we descend, we are no longer going upwards.\n    fn descend_backwards(&mut self, page: PageRef) {\n        self.going_upwards = false;\n        self.stack.push_backwards(page);\n    }\n\n    /// Move the cursor to the root page of the btree.\n    #[cfg_attr(debug_assertions, instrument(skip_all, level = Level::DEBUG))]\n    fn move_to_root(&mut self) -> Result<Option<Completion>> {\n        self.seek_state = CursorSeekState::Start;\n        self.going_upwards = false;\n        tracing::trace!(root_page = self.root_page);\n        let (mem_page, c) = self.read_page(self.root_page)?;\n        self.stack.clear();\n        self.stack.push(mem_page);\n        Ok(c)\n    }\n\n    /// Move the cursor to the rightmost record in the btree.\n    #[cfg_attr(debug_assertions, instrument(skip(self), level = Level::DEBUG))]\n    fn move_to_rightmost(&mut self, always_seek: bool) -> Result<IOResult<bool>> {\n        loop {\n            let (move_to_right_state, rightmost_page_id) = &self.move_to_right_state;\n            match *move_to_right_state {\n                MoveToRightState::Start => {\n                    if !always_seek {\n                        if let Some(rightmost_page_id) = rightmost_page_id {\n                            // If we know the rightmost page and are already on it, we can skip a seek.\n                            // This optimization is never performed if always_seek = true. always_seek is used\n                            // in cases where we cannot be sure that the btree wasn't modified from under us\n                            // e.g. by a trigger subprogram.\n                            let current_page = self.stack.top_ref();\n                            if current_page.get().id == *rightmost_page_id {\n                                let contents = current_page.get_contents();\n                                let cell_count = contents.cell_count();\n                                self.stack.set_cell_index(cell_count as i32 - 1);\n                                return Ok(IOResult::Done(cell_count > 0));\n                            }\n                        }\n                    }\n                    let rightmost_page_id = *rightmost_page_id;\n                    let c = self.move_to_root()?;\n                    self.move_to_right_state = (MoveToRightState::ProcessPage, rightmost_page_id);\n                    if let Some(c) = c {\n                        io_yield_one!(c);\n                    }\n                }\n                MoveToRightState::ProcessPage => {\n                    let mem_page = self.stack.top_ref();\n                    let page_idx = mem_page.get().id;\n                    let contents = mem_page.get_contents();\n                    if contents.is_leaf() {\n                        self.move_to_right_state = (MoveToRightState::Start, Some(page_idx));\n                        if contents.cell_count() > 0 {\n                            self.stack.set_cell_index(contents.cell_count() as i32 - 1);\n                            return Ok(IOResult::Done(true));\n                        }\n                        return Ok(IOResult::Done(false));\n                    }\n\n                    match contents.rightmost_pointer()? {\n                        Some(right_most_pointer) => {\n                            self.stack.set_cell_index(contents.cell_count() as i32 + 1);\n                            let (mem_page, c) = self.read_page(right_most_pointer as i64)?;\n                            self.stack.push(mem_page);\n                            if let Some(c) = c {\n                                io_yield_one!(c);\n                            }\n                        }\n                        None => {\n                            unreachable!(\"interior page should have a rightmost pointer\");\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    /// Specialized version of move_to() for table btrees.\n    #[cfg_attr(debug_assertions, instrument(skip(self), level = Level::DEBUG))]\n    fn tablebtree_move_to(&mut self, rowid: i64, seek_op: SeekOp) -> Result<IOResult<()>> {\n        loop {\n            let (old_top_idx, is_leaf, cell_count) = {\n                let page = self.stack.top_ref();\n                let contents = page.get_contents();\n                (\n                    self.stack.current(),\n                    contents.is_leaf(),\n                    contents.cell_count(),\n                )\n            };\n\n            if is_leaf {\n                self.seek_state = CursorSeekState::FoundLeaf { eq_seen: false };\n                return Ok(IOResult::Done(()));\n            }\n\n            if matches!(\n                self.seek_state,\n                CursorSeekState::Start | CursorSeekState::MovingBetweenPages { .. }\n            ) {\n                let eq_seen = match &self.seek_state {\n                    CursorSeekState::MovingBetweenPages { eq_seen } => *eq_seen,\n                    _ => false,\n                };\n                let min_cell_idx = 0;\n                let max_cell_idx = cell_count as isize - 1;\n                let nearest_matching_cell = None;\n\n                self.seek_state = CursorSeekState::InteriorPageBinarySearch {\n                    state: InteriorPageBinarySearchState {\n                        min_cell_idx,\n                        max_cell_idx,\n                        nearest_matching_cell,\n                        eq_seen,\n                    },\n                };\n            }\n\n            let CursorSeekState::InteriorPageBinarySearch { state } = &self.seek_state else {\n                unreachable!(\"we must be in an interior binary search state\");\n            };\n\n            let mut state = *state;\n\n            let control =\n                self.tablebtree_move_inner(rowid, seek_op, old_top_idx, cell_count, &mut state)?;\n            // Persist state if inner function didn't change seek_state to something else (e.g., MovingBetweenPages)\n            if matches!(\n                self.seek_state,\n                CursorSeekState::InteriorPageBinarySearch { .. }\n            ) {\n                self.seek_state = CursorSeekState::InteriorPageBinarySearch { state };\n            }\n            match control {\n                ControlFlow::Continue(_) => {}\n                ControlFlow::Break(result) => {\n                    return Ok(result);\n                }\n            }\n        }\n    }\n\n    fn tablebtree_move_inner(\n        &mut self,\n        rowid: i64,\n        seek_op: SeekOp,\n        old_top_idx: usize,\n        cell_count: usize,\n        state: &mut InteriorPageBinarySearchState,\n    ) -> Result<ControlFlow<IOResult<()>>> {\n        let min = state.min_cell_idx;\n        let max = state.max_cell_idx;\n        if min > max {\n            if let Some(nearest_matching_cell) = state.nearest_matching_cell {\n                let left_child_page = self\n                    .stack\n                    .get_page_contents_at_level(old_top_idx)\n                    .unwrap()\n                    .cell_interior_read_left_child_page(nearest_matching_cell)?;\n                self.stack.set_cell_index(nearest_matching_cell as i32);\n                let (mem_page, c) = self.read_page(left_child_page as i64)?;\n                self.stack.push(mem_page);\n                self.seek_state = CursorSeekState::MovingBetweenPages {\n                    eq_seen: state.eq_seen,\n                };\n                if let Some(c) = c {\n                    return Ok(ControlFlow::Break(IOResult::IO(IOCompletions::Single(c))));\n                }\n                return Ok(ControlFlow::Continue(()));\n            }\n            self.stack.set_cell_index(cell_count as i32 + 1);\n            match self\n                .stack\n                .get_page_contents_at_level(old_top_idx)\n                .unwrap()\n                .rightmost_pointer()?\n            {\n                Some(right_most_pointer) => {\n                    let (mem_page, c) = self.read_page(right_most_pointer as i64)?;\n                    self.stack.push(mem_page);\n                    self.seek_state = CursorSeekState::MovingBetweenPages {\n                        eq_seen: state.eq_seen,\n                    };\n                    if let Some(c) = c {\n                        return Ok(ControlFlow::Break(IOResult::IO(IOCompletions::Single(c))));\n                    }\n                    return Ok(ControlFlow::Continue(()));\n                }\n                None => {\n                    unreachable!(\"we shall not go back up! The only way is down the slope\");\n                }\n            }\n        }\n        let cur_cell_idx = (min + max) >> 1; // rustc generates extra insns for (min+max)/2 due to them being isize. we know min&max are >=0 here.\n        let cell_rowid = self\n            .stack\n            .get_page_contents_at_level(old_top_idx)\n            .unwrap()\n            .cell_table_interior_read_rowid(cur_cell_idx as usize)?;\n        // in sqlite btrees left child pages have <= keys.\n        // table btrees can have a duplicate rowid in the interior cell, so for example if we are looking for rowid=10,\n        // and we find an interior cell with rowid=10, we need to move to the left page since (due to the <= rule of sqlite btrees)\n        // the left page may have a rowid=10.\n        // Logic table for determining if target leaf page is in left subtree\n        //\n        // Forwards iteration (looking for first match in tree):\n        // OP  | Current Cell vs Seek Key   | Action?  | Explanation\n        // GT  | >                          | go left  | First > key is in left subtree\n        // GT  | = or <                     | go right | First > key is in right subtree\n        // GE  | > or =                     | go left  | First >= key is in left subtree\n        // GE  | <                          | go right | First >= key is in right subtree\n        //\n        // Backwards iteration (looking for last match in tree):\n        // OP  | Current Cell vs Seek Key   | Action?  | Explanation\n        // LE  | > or =                     | go left  | Last <= key is in left subtree\n        // LE  | <                          | go right | Last <= key is in right subtree\n        // LT  | > or =                     | go left  | Last < key is in left subtree\n        // LT  | <                          | go right?| Last < key is in right subtree, except if cell rowid is exactly 1 less\n        //\n        // No iteration (point query):\n        // EQ  | > or =                     | go left  | Last = key is in left subtree\n        // EQ  | <                          | go right | Last = key is in right subtree\n        let is_on_left = match seek_op {\n            SeekOp::GT => cell_rowid > rowid,\n            SeekOp::GE { .. } => cell_rowid >= rowid,\n            SeekOp::LE { .. } => cell_rowid >= rowid,\n            SeekOp::LT => cell_rowid + 1 >= rowid,\n        };\n        if is_on_left {\n            state.nearest_matching_cell.replace(cur_cell_idx as usize);\n            state.max_cell_idx = cur_cell_idx - 1;\n        } else {\n            state.min_cell_idx = cur_cell_idx + 1;\n        }\n        Ok(ControlFlow::Continue(()))\n    }\n\n    /// Specialized version of move_to() for index btrees.\n    #[cfg_attr(debug_assertions, instrument(skip(self, index_key), level = Level::DEBUG))]\n    fn indexbtree_move_to(\n        &mut self,\n        index_key: &ImmutableRecord,\n        cmp: SeekOp,\n    ) -> Result<IOResult<()>> {\n        let key_values = index_key.get_values()?;\n        let record_comparer = {\n            let index_info = self\n                .index_info\n                .as_ref()\n                .expect(\"indexbtree_move_to: index_info required\");\n            find_compare(key_values.iter().peekable(), index_info)\n        };\n        self.indexbtree_move_to_internal(cmp, record_comparer, &key_values)\n    }\n\n    /// Move cursor to position using registers directly, avoiding record serialization.\n    /// See `seek_unpacked` for rationale.\n    #[instrument(skip(self, registers), level = Level::DEBUG)]\n    fn indexbtree_move_to_unpacked(\n        &mut self,\n        registers: &[Register],\n        cmp: SeekOp,\n    ) -> Result<IOResult<()>> {\n        if matches!(\n            self.seek_state,\n            CursorSeekState::LeafPageBinarySearch { .. } | CursorSeekState::FoundLeaf { .. }\n        ) {\n            self.seek_state = CursorSeekState::Start;\n        }\n\n        if matches!(self.seek_state, CursorSeekState::Start) {\n            if let Some(c) = self.move_to_root()? {\n                return Ok(IOResult::IO(IOCompletions::Single(c)));\n            }\n        }\n\n        let index_info = self\n            .index_info\n            .as_ref()\n            .expect(\"indexbtree_move_to_unpacked: index_info required\");\n\n        let key_values: SmallVec<[ValueRef<'_>; STACK_ALLOC_KEY_VALS_MAX]> = registers\n            .iter()\n            .map(|r| r.get_value().as_value_ref())\n            .collect();\n        let record_comparer = find_compare(key_values.iter().peekable(), index_info);\n        self.indexbtree_move_to_internal(cmp, record_comparer, &key_values)\n    }\n\n    fn indexbtree_move_to_internal(\n        &mut self,\n        cmp: SeekOp,\n        record_comparer: RecordCompare,\n        key_values: &[ValueRef<'_>],\n    ) -> Result<IOResult<()>> {\n        tracing::debug!(\"Using record comparison strategy: {:?}\", record_comparer);\n        let tie_breaker = get_tie_breaker_from_seek_op(cmp);\n\n        loop {\n            let (old_top_idx, is_leaf, cell_count) = {\n                let page = self.stack.top_ref();\n                let contents = page.get_contents();\n                (\n                    self.stack.current(),\n                    contents.is_leaf(),\n                    contents.cell_count(),\n                )\n            };\n\n            if is_leaf {\n                let eq_seen = match &self.seek_state {\n                    CursorSeekState::MovingBetweenPages { eq_seen } => *eq_seen,\n                    _ => false,\n                };\n                self.seek_state = CursorSeekState::FoundLeaf { eq_seen };\n                return Ok(IOResult::Done(()));\n            }\n\n            if matches!(\n                self.seek_state,\n                CursorSeekState::Start | CursorSeekState::MovingBetweenPages { .. }\n            ) {\n                let eq_seen = match &self.seek_state {\n                    CursorSeekState::MovingBetweenPages { eq_seen } => *eq_seen,\n                    _ => false,\n                };\n                let min_cell_idx = 0;\n                let max_cell_idx = cell_count as isize - 1;\n                let nearest_matching_cell = None;\n\n                self.seek_state = CursorSeekState::InteriorPageBinarySearch {\n                    state: InteriorPageBinarySearchState {\n                        min_cell_idx,\n                        max_cell_idx,\n                        nearest_matching_cell,\n                        eq_seen,\n                    },\n                };\n            }\n\n            let CursorSeekState::InteriorPageBinarySearch { state } = &self.seek_state else {\n                unreachable!(\n                    \"we must be in an interior binary search state, got {:?}\",\n                    self.seek_state\n                );\n            };\n\n            let mut state = *state;\n\n            let control = self.indexbtree_move_to_inner(\n                cmp,\n                old_top_idx,\n                cell_count,\n                record_comparer,\n                key_values,\n                tie_breaker,\n                &mut state,\n            )?;\n            // Persist state if inner function didn't change seek_state to something else (e.g., MovingBetweenPages)\n            if matches!(\n                self.seek_state,\n                CursorSeekState::InteriorPageBinarySearch { .. }\n            ) {\n                self.seek_state = CursorSeekState::InteriorPageBinarySearch { state };\n            }\n            match control {\n                ControlFlow::Continue(_) => {}\n                ControlFlow::Break(result) => {\n                    return Ok(result);\n                }\n            }\n        }\n    }\n\n    #[expect(clippy::too_many_arguments)]\n    fn indexbtree_move_to_inner(\n        &mut self,\n        cmp: SeekOp,\n        old_top_idx: usize,\n        cell_count: usize,\n        record_comparer: RecordCompare,\n        key_values: &[ValueRef<'_>],\n        tie_breaker: Ordering,\n        state: &mut InteriorPageBinarySearchState,\n    ) -> Result<ControlFlow<IOResult<()>>> {\n        let iter_dir = cmp.iteration_direction();\n        let min = state.min_cell_idx;\n        let max = state.max_cell_idx;\n        if min > max {\n            let Some(leftmost_matching_cell) = state.nearest_matching_cell else {\n                self.stack.set_cell_index(cell_count as i32 + 1);\n                match self\n                    .stack\n                    .get_page_contents_at_level(old_top_idx)\n                    .unwrap()\n                    .rightmost_pointer()?\n                {\n                    Some(right_most_pointer) => {\n                        let (mem_page, c) = self.read_page(right_most_pointer as i64)?;\n                        self.stack.push(mem_page);\n                        self.seek_state = CursorSeekState::MovingBetweenPages {\n                            eq_seen: state.eq_seen,\n                        };\n                        if let Some(c) = c {\n                            return Ok(ControlFlow::Break(IOResult::IO(IOCompletions::Single(c))));\n                        }\n                        return Ok(ControlFlow::Continue(()));\n                    }\n                    None => {\n                        unreachable!(\"we shall not go back up! The only way is down the slope\");\n                    }\n                }\n            };\n            let matching_cell = self\n                .stack\n                .get_page_contents_at_level(old_top_idx)\n                .unwrap()\n                .cell_get(leftmost_matching_cell, self.usable_space())?;\n            self.stack.set_cell_index(leftmost_matching_cell as i32);\n            // we don't advance in case of forward iteration and index tree internal nodes because we will visit this node going up.\n            // in backwards iteration, we must retreat because otherwise we would unnecessarily visit this node again.\n            // Example:\n            // this parent: key 666, and we found the target key in the left child.\n            // left child has: key 663, key 664, key 665\n            // we need to move to the previous parent (with e.g. key 662) when iterating backwards so that we don't end up back here again.\n            if iter_dir == IterationDirection::Backwards {\n                self.stack.retreat();\n            }\n            let BTreeCell::IndexInteriorCell(IndexInteriorCell {\n                left_child_page, ..\n            }) = &matching_cell\n            else {\n                unreachable!(\"unexpected cell type: {:?}\", matching_cell);\n            };\n\n            {\n                let page = self.stack.get_page_at_level(old_top_idx).unwrap();\n                turso_assert!(\n                    page.get().id != *left_child_page as usize,\n                    \"corrupt: current page and left child page are the same\",\n                    { \"cell\": leftmost_matching_cell, \"page_id\": page.get().id }\n                );\n            }\n\n            let (mem_page, c) = self.read_page(*left_child_page as i64)?;\n            self.stack.push(mem_page);\n            self.seek_state = CursorSeekState::MovingBetweenPages {\n                eq_seen: state.eq_seen,\n            };\n            if let Some(c) = c {\n                return Ok(ControlFlow::Break(IOResult::IO(IOCompletions::Single(c))));\n            }\n            return Ok(ControlFlow::Continue(()));\n        }\n\n        let cur_cell_idx = (min + max) >> 1; // rustc generates extra insns for (min+max)/2 due to them being isize. we know min&max are >=0 here.\n        self.stack.set_cell_index(cur_cell_idx as i32);\n\n        let (payload, payload_size, first_overflow_page) = self\n            .stack\n            .get_page_contents_at_level(old_top_idx)\n            .unwrap()\n            .cell_index_read_payload_ptr(cur_cell_idx as usize, self.usable_space())?;\n\n        if let Some(next_page) = first_overflow_page {\n            let res = self.process_overflow_read(payload, next_page, payload_size)?;\n            if res.is_io() {\n                return Ok(ControlFlow::Break(res));\n            }\n        } else {\n            self.get_immutable_record_or_create()\n                .as_mut()\n                .unwrap()\n                .invalidate();\n            self.get_immutable_record_or_create()\n                .as_mut()\n                .unwrap()\n                .start_serialization(payload);\n        };\n\n        let (target_leaf_page_is_in_left_subtree, is_eq) = {\n            let record = self.get_immutable_record();\n            let record = record.as_ref().unwrap();\n\n            let interior_cell_vs_index_key = record_comparer\n                .compare(\n                    record,\n                    key_values,\n                    self.index_info\n                        .as_ref()\n                        .expect(\"indexbtree_move_to: index_info required\"),\n                    0,\n                    tie_breaker,\n                )\n                .unwrap();\n\n            // in sqlite btrees left child pages have <= keys.\n            // in general, in forwards iteration we want to find the first key that matches the seek condition.\n            // in backwards iteration we want to find the last key that matches the seek condition.\n            //\n            // Logic table for determining if target leaf page is in left subtree.\n            // For index b-trees this is a bit more complicated since the interior cells contain payloads (the key is the payload).\n            // and for non-unique indexes there might be several cells with the same key.\n            //\n            // Forwards iteration (looking for first match in tree):\n            // OP  | Current Cell vs Seek Key  | Action?  | Explanation\n            // GT  | >                         | go left  | First > key could be exactly this one, or in left subtree\n            // GT  | = or <                    | go right | First > key must be in right subtree\n            // GE  | >                         | go left  | First >= key could be exactly this one, or in left subtree\n            // GE  | =                         | go left  | First >= key could be exactly this one, or in left subtree\n            // GE  | <                         | go right | First >= key must be in right subtree\n            //\n            // Backwards iteration (looking for last match in tree):\n            // OP  | Current Cell vs Seek Key  | Action?  | Explanation\n            // LE  | >                         | go left  | Last <= key must be in left subtree\n            // LE  | =                         | go right | Last <= key is either this one, or somewhere to the right of this one. So we need to go right to make sure\n            // LE  | <                         | go right | Last <= key must be in right subtree\n            // LT  | >                         | go left  | Last < key must be in left subtree\n            // LT  | =                         | go left  | Last < key must be in left subtree since we want strictly less than\n            // LT  | <                         | go right | Last < key could be exactly this one, or in right subtree\n            //\n            // No iteration (point query):\n            // EQ  | >                         | go left  | First = key must be in left subtree\n            // EQ  | =                         | go left  | First = key could be exactly this one, or in left subtree\n            // EQ  | <                         | go right | First = key must be in right subtree\n\n            (\n                match cmp {\n                    SeekOp::GT => interior_cell_vs_index_key.is_gt(),\n                    SeekOp::GE { .. } => interior_cell_vs_index_key.is_ge(),\n                    SeekOp::LE { .. } => interior_cell_vs_index_key.is_gt(),\n                    SeekOp::LT => interior_cell_vs_index_key.is_ge(),\n                },\n                interior_cell_vs_index_key.is_eq(),\n            )\n        };\n\n        if is_eq {\n            state.eq_seen = true;\n        }\n\n        if target_leaf_page_is_in_left_subtree {\n            state.nearest_matching_cell = Some(cur_cell_idx as usize);\n            state.max_cell_idx = cur_cell_idx - 1;\n        } else {\n            state.min_cell_idx = cur_cell_idx + 1;\n        }\n        Ok(ControlFlow::Continue(()))\n    }\n\n    /// Specialized version of do_seek() for table btrees that uses binary search instead\n    /// of iterating cells in order.\n    #[cfg_attr(debug_assertions, instrument(skip_all, level = Level::DEBUG))]\n    fn tablebtree_seek(&mut self, rowid: i64, seek_op: SeekOp) -> Result<IOResult<SeekResult>> {\n        if matches!(\n            self.seek_state,\n            CursorSeekState::Start\n                | CursorSeekState::MovingBetweenPages { .. }\n                | CursorSeekState::InteriorPageBinarySearch { .. }\n        ) {\n            // No need for another move_to_root. Move_to already moves to root\n            return_if_io!(self.move_to(SeekKey::TableRowId(rowid), seek_op));\n            let page = self.stack.top_ref();\n            let contents = page.get_contents();\n            turso_assert!(\n                contents.is_leaf(),\n                \"tablebtree_seek() called on non-leaf page\"\n            );\n\n            let cell_count = contents.cell_count();\n            if cell_count == 0 {\n                self.stack.set_cell_index(0);\n                return Ok(IOResult::Done(SeekResult::NotFound));\n            }\n            let min_cell_idx = 0;\n            let max_cell_idx = cell_count as isize - 1;\n\n            // If iter dir is forwards, we want the first cell that matches;\n            // If iter dir is backwards, we want the last cell that matches.\n            let nearest_matching_cell = None;\n\n            self.seek_state = CursorSeekState::LeafPageBinarySearch {\n                state: LeafPageBinarySearchState {\n                    min_cell_idx,\n                    max_cell_idx,\n                    nearest_matching_cell,\n                    eq_seen: false, // not relevant for table btrees\n                    target_cell_when_not_found: match seek_op.iteration_direction() {\n                        IterationDirection::Forwards => cell_count as i32,\n                        IterationDirection::Backwards => -1,\n                    },\n                },\n            };\n        }\n\n        let CursorSeekState::LeafPageBinarySearch { state } = &self.seek_state else {\n            unreachable!(\"we must be in a leaf binary search state\");\n        };\n\n        let page = self.stack.top_ref().clone();\n        let contents = page.get_contents();\n        let mut state = *state;\n\n        loop {\n            let control = self.tablebtree_seek_inner(rowid, seek_op, contents, &mut state)?;\n            // Persist state after each iteration since inner function modifies it\n            if matches!(\n                self.seek_state,\n                CursorSeekState::LeafPageBinarySearch { .. }\n            ) {\n                self.seek_state = CursorSeekState::LeafPageBinarySearch { state };\n            }\n            match control {\n                ControlFlow::Continue(_) => {}\n                ControlFlow::Break(res) => {\n                    return Ok(res);\n                }\n            }\n        }\n    }\n\n    fn tablebtree_seek_inner(\n        &mut self,\n        rowid: i64,\n        seek_op: SeekOp,\n        contents: &mut PageContent,\n        state: &mut LeafPageBinarySearchState,\n    ) -> Result<ControlFlow<IOResult<SeekResult>>> {\n        let iter_dir = seek_op.iteration_direction();\n        let min = state.min_cell_idx;\n        let max = state.max_cell_idx;\n        let target_cell_when_not_found = state.target_cell_when_not_found;\n        if min > max {\n            if let Some(nearest_matching_cell) = state.nearest_matching_cell {\n                self.stack.set_cell_index(nearest_matching_cell as i32);\n                self.set_has_record(true);\n                return Ok(ControlFlow::Break(IOResult::Done(SeekResult::Found)));\n            } else {\n                // if !eq_only - matching entry can exist in neighbour leaf page\n                // this can happen if key in the interiour page was deleted - but divider kept untouched\n                // in such case BTree can navigate to the leaf which no longer has matching key for seek_op\n                // in this case, caller must advance cursor if necessary\n                return Ok(ControlFlow::Break(IOResult::Done(if seek_op.eq_only() {\n                    let has_record = target_cell_when_not_found >= 0\n                        && target_cell_when_not_found < contents.cell_count() as i32;\n                    self.has_record = has_record;\n                    self.stack.set_cell_index(target_cell_when_not_found);\n                    SeekResult::NotFound\n                } else {\n                    // set cursor to the position where which would hold the op-boundary if it were present\n                    self.stack.set_cell_index(target_cell_when_not_found);\n                    SeekResult::TryAdvance\n                })));\n            };\n        }\n\n        let cur_cell_idx = (min + max) >> 1; // rustc generates extra insns for (min+max)/2 due to them being isize. we know min&max are >=0 here.\n        let cell_rowid = contents.cell_table_leaf_read_rowid(cur_cell_idx as usize)?;\n\n        let cmp = cell_rowid.cmp(&rowid);\n\n        let found = match seek_op {\n            SeekOp::GT => cmp.is_gt(),\n            SeekOp::GE { eq_only: true } => cmp.is_eq(),\n            SeekOp::GE { eq_only: false } => cmp.is_ge(),\n            SeekOp::LE { eq_only: true } => cmp.is_eq(),\n            SeekOp::LE { eq_only: false } => cmp.is_le(),\n            SeekOp::LT => cmp.is_lt(),\n        };\n\n        // rowids are unique, so we can return the rowid immediately\n        if found && seek_op.eq_only() {\n            self.stack.set_cell_index(cur_cell_idx as i32);\n            self.set_has_record(true);\n            return Ok(ControlFlow::Break(IOResult::Done(SeekResult::Found)));\n        }\n\n        if found {\n            state.nearest_matching_cell = Some(cur_cell_idx as usize);\n            match iter_dir {\n                IterationDirection::Forwards => {\n                    state.max_cell_idx = cur_cell_idx - 1;\n                }\n                IterationDirection::Backwards => {\n                    state.min_cell_idx = cur_cell_idx + 1;\n                }\n            }\n        } else if cmp.is_gt() {\n            if matches!(seek_op, SeekOp::GE { eq_only: true }) {\n                state.target_cell_when_not_found =\n                    target_cell_when_not_found.min(cur_cell_idx as i32);\n            }\n            state.max_cell_idx = cur_cell_idx - 1;\n        } else if cmp.is_lt() {\n            if matches!(seek_op, SeekOp::LE { eq_only: true }) {\n                state.target_cell_when_not_found =\n                    target_cell_when_not_found.max(cur_cell_idx as i32);\n            }\n            state.min_cell_idx = cur_cell_idx + 1;\n        } else {\n            match iter_dir {\n                IterationDirection::Forwards => {\n                    state.min_cell_idx = cur_cell_idx + 1;\n                }\n                IterationDirection::Backwards => {\n                    state.max_cell_idx = cur_cell_idx - 1;\n                }\n            }\n        }\n        Ok(ControlFlow::Continue(()))\n    }\n\n    #[cfg_attr(debug_assertions, instrument(skip_all, level = Level::DEBUG))]\n    fn indexbtree_seek(\n        &mut self,\n        key: &ImmutableRecord,\n        seek_op: SeekOp,\n    ) -> Result<IOResult<SeekResult>> {\n        let key_values = key.get_values()?;\n        let record_comparer = {\n            let index_info = self\n                .index_info\n                .as_ref()\n                .expect(\"indexbtree_seek: index_info required\");\n            find_compare(key_values.iter().peekable(), index_info)\n        };\n\n        tracing::debug!(\n            \"Using record comparison strategy for seek: {:?}\",\n            record_comparer\n        );\n\n        self.indexbtree_seek_internal(seek_op, record_comparer, &key_values)\n    }\n\n    /// Seek using registers directly, avoiding record serialization overhead.\n    /// See `seek_unpacked` trait method for rationale.\n    #[instrument(skip_all, level = Level::DEBUG)]\n    fn indexbtree_seek_unpacked(\n        &mut self,\n        registers: &[Register],\n        seek_op: SeekOp,\n    ) -> Result<IOResult<SeekResult>> {\n        let index_info = self\n            .index_info\n            .as_ref()\n            .expect(\"indexbtree_seek_unpacked: index_info required\");\n\n        // SmallVec stores up to MAX_STACK_KEY_VALUES on the stack, spilling to heap only if exceeded\n        let key_values: SmallVec<[ValueRef<'_>; STACK_ALLOC_KEY_VALS_MAX]> = registers\n            .iter()\n            .map(|r| r.get_value().as_value_ref())\n            .collect();\n        let record_comparer = find_compare(key_values.iter().peekable(), index_info);\n        tracing::debug!(\n            \"Using record comparison strategy for seek: {:?}\",\n            record_comparer\n        );\n        self.indexbtree_seek_internal(seek_op, record_comparer, &key_values)\n    }\n\n    fn indexbtree_seek_internal(\n        &mut self,\n        seek_op: SeekOp,\n        record_comparer: RecordCompare,\n        key_values: &[ValueRef<'_>],\n    ) -> Result<IOResult<SeekResult>> {\n        if matches!(\n            self.seek_state,\n            CursorSeekState::Start\n                | CursorSeekState::MovingBetweenPages { .. }\n                | CursorSeekState::InteriorPageBinarySearch { .. }\n        ) {\n            if matches!(self.seek_state, CursorSeekState::Start) {\n                if let Some(c) = self.move_to_root()? {\n                    return Ok(IOResult::IO(IOCompletions::Single(c)));\n                }\n            }\n            return_if_io!(self.indexbtree_move_to_internal(seek_op, record_comparer, key_values));\n            let CursorSeekState::FoundLeaf { eq_seen } = &self.seek_state else {\n                unreachable!(\n                    \"We must still be in FoundLeaf state after indexbtree_move_to_internal, got: {:?}\",\n                    self.seek_state\n                );\n            };\n            let eq_seen = *eq_seen;\n            let page = self.stack.top_ref();\n\n            let contents = page.get_contents();\n            let cell_count = contents.cell_count();\n            if cell_count == 0 {\n                return Ok(IOResult::Done(SeekResult::NotFound));\n            }\n\n            let min = 0;\n            let max = cell_count as isize - 1;\n\n            // If iter dir is forwards, we want the first cell that matches;\n            // If iter dir is backwards, we want the last cell that matches.\n            let nearest_matching_cell = None;\n\n            self.seek_state = CursorSeekState::LeafPageBinarySearch {\n                state: LeafPageBinarySearchState {\n                    min_cell_idx: min,\n                    max_cell_idx: max,\n                    nearest_matching_cell,\n                    eq_seen,\n                    target_cell_when_not_found: match seek_op.iteration_direction() {\n                        IterationDirection::Forwards => cell_count as i32,\n                        IterationDirection::Backwards => -1,\n                    },\n                },\n            };\n        }\n\n        let CursorSeekState::LeafPageBinarySearch { state } = &self.seek_state else {\n            unreachable!(\n                \"we must be in a leaf binary search state, got: {:?}\",\n                self.seek_state\n            );\n        };\n\n        let old_top_idx = self.stack.current();\n\n        let mut state = *state;\n\n        loop {\n            let control = self.indexbtree_seek_inner(\n                seek_op,\n                old_top_idx,\n                key_values,\n                record_comparer,\n                &mut state,\n            )?;\n            // Persist state after each iteration since inner function modifies it\n            if matches!(\n                self.seek_state,\n                CursorSeekState::LeafPageBinarySearch { .. }\n            ) {\n                self.seek_state = CursorSeekState::LeafPageBinarySearch { state };\n            }\n            match control {\n                ControlFlow::Continue(_) => {}\n                ControlFlow::Break(res) => {\n                    return Ok(res);\n                }\n            }\n        }\n    }\n\n    fn indexbtree_seek_inner(\n        &mut self,\n        seek_op: SeekOp,\n        old_top_idx: usize,\n        key_values: &[ValueRef<'_>],\n        record_comparer: RecordCompare,\n        state: &mut LeafPageBinarySearchState,\n    ) -> Result<ControlFlow<IOResult<SeekResult>>> {\n        let iter_dir = seek_op.iteration_direction();\n        let min = state.min_cell_idx;\n        let max = state.max_cell_idx;\n        let eq_seen = state.eq_seen;\n        if min > max {\n            if let Some(nearest_matching_cell) = state.nearest_matching_cell {\n                self.stack.set_cell_index(nearest_matching_cell as i32);\n                self.set_has_record(true);\n\n                return Ok(ControlFlow::Break(IOResult::Done(SeekResult::Found)));\n            } else {\n                // set cursor to the position where which would hold the op-boundary if it were present\n                let target_cell = state.target_cell_when_not_found;\n                self.stack.set_cell_index(target_cell);\n                let has_record = target_cell >= 0\n                    && target_cell\n                        < self\n                            .stack\n                            .get_page_contents_at_level(old_top_idx)\n                            .unwrap()\n                            .cell_count() as i32;\n                self.has_record = has_record;\n\n                // Similar logic as in tablebtree_seek(), but for indexes.\n                // The difference is that since index keys are not necessarily unique, we need to TryAdvance\n                // even when eq_only=true and we have seen an EQ match up in the tree in an interior node.\n                if seek_op.eq_only() && !eq_seen {\n                    return Ok(ControlFlow::Break(IOResult::Done(SeekResult::NotFound)));\n                }\n                return Ok(ControlFlow::Break(IOResult::Done(SeekResult::TryAdvance)));\n            };\n        }\n\n        let cur_cell_idx = (min + max) >> 1; // rustc generates extra insns for (min+max)/2 due to them being isize. we know min&max are >=0 here.\n        self.stack.set_cell_index(cur_cell_idx as i32);\n\n        let (payload, payload_size, first_overflow_page) = self\n            .stack\n            .get_page_contents_at_level(old_top_idx)\n            .unwrap()\n            .cell_index_read_payload_ptr(cur_cell_idx as usize, self.usable_space())?;\n\n        if let Some(next_page) = first_overflow_page {\n            let res = self.process_overflow_read(payload, next_page, payload_size)?;\n            if let IOResult::IO(io) = res {\n                return Ok(ControlFlow::Break(IOResult::IO(io)));\n            }\n        } else {\n            self.get_immutable_record_or_create()\n                .as_mut()\n                .unwrap()\n                .invalidate();\n            self.get_immutable_record_or_create()\n                .as_mut()\n                .unwrap()\n                .start_serialization(payload);\n        };\n\n        let (cmp, found) = self.compare_with_current_record(\n            key_values,\n            seek_op,\n            &record_comparer,\n            self.index_info\n                .as_ref()\n                .expect(\"indexbtree_seek: index_info required\"),\n        );\n        if found {\n            state.nearest_matching_cell.replace(cur_cell_idx as usize);\n            match iter_dir {\n                IterationDirection::Forwards => {\n                    state.max_cell_idx = cur_cell_idx - 1;\n                }\n                IterationDirection::Backwards => {\n                    state.min_cell_idx = cur_cell_idx + 1;\n                }\n            }\n        } else if cmp.is_gt() {\n            if matches!(seek_op, SeekOp::GE { eq_only: true }) {\n                state.target_cell_when_not_found =\n                    state.target_cell_when_not_found.min(cur_cell_idx as i32);\n            }\n            state.max_cell_idx = cur_cell_idx - 1;\n        } else if cmp.is_lt() {\n            if matches!(seek_op, SeekOp::LE { eq_only: true }) {\n                state.target_cell_when_not_found =\n                    state.target_cell_when_not_found.max(cur_cell_idx as i32);\n            }\n            state.min_cell_idx = cur_cell_idx + 1;\n        } else {\n            match iter_dir {\n                IterationDirection::Forwards => {\n                    state.min_cell_idx = cur_cell_idx + 1;\n                }\n                IterationDirection::Backwards => {\n                    state.max_cell_idx = cur_cell_idx - 1;\n                }\n            }\n        }\n        Ok(ControlFlow::Continue(()))\n    }\n\n    fn compare_with_current_record(\n        &self,\n        key_values: &[ValueRef],\n        seek_op: SeekOp,\n        record_comparer: &RecordCompare,\n        index_info: &IndexInfo,\n    ) -> (Ordering, bool) {\n        let record = self.get_immutable_record();\n        let record = record.as_ref().unwrap();\n\n        let tie_breaker = get_tie_breaker_from_seek_op(seek_op);\n        let cmp = record_comparer\n            .compare(record, key_values, index_info, 0, tie_breaker)\n            .unwrap();\n\n        let found = match seek_op {\n            SeekOp::GT => cmp.is_gt(),\n            SeekOp::GE { eq_only: true } => cmp.is_eq(),\n            SeekOp::GE { eq_only: false } => cmp.is_ge(),\n            SeekOp::LE { eq_only: true } => cmp.is_eq(),\n            SeekOp::LE { eq_only: false } => cmp.is_le(),\n            SeekOp::LT => cmp.is_lt(),\n        };\n        (cmp, found)\n    }\n\n    #[cfg_attr(debug_assertions, instrument(skip_all, level = Level::DEBUG))]\n    pub fn move_to(&mut self, key: SeekKey<'_>, cmp: SeekOp) -> Result<IOResult<()>> {\n        tracing::trace!(?key, ?cmp);\n        // For a table with N rows, we can find any row by row id in O(log(N)) time by starting at the root page and following the B-tree pointers.\n        // B-trees consist of interior pages and leaf pages. Interior pages contain pointers to other pages, while leaf pages contain the actual row data.\n        //\n        // Conceptually, each Interior Cell in a interior page has a rowid and a left child node, and the page itself has a right-most child node.\n        // Example: consider an interior page that contains cells C1(rowid=10), C2(rowid=20), C3(rowid=30).\n        // - All rows with rowids <= 10 are in the left child node of C1.\n        // - All rows with rowids > 10 and <= 20 are in the left child node of C2.\n        // - All rows with rowids > 20 and <= 30 are in the left child node of C3.\n        // - All rows with rowids > 30 are in the right-most child node of the page.\n        //\n        // There will generally be multiple levels of interior pages before we reach a leaf page,\n        // so we need to follow the interior page pointers until we reach the leaf page that contains the row we are looking for (if it exists).\n        //\n        // Here's a high-level overview of the algorithm:\n        // 1. Since we start at the root page, its cells are all interior cells.\n        // 2. We scan the interior cells until we find a cell whose rowid is greater than or equal to the rowid we are looking for.\n        // 3. Follow the left child pointer of the cell we found in step 2.\n        //    a. In case none of the cells in the page have a rowid greater than or equal to the rowid we are looking for,\n        //       we follow the right-most child pointer of the page instead (since all rows with rowids greater than the rowid we are looking for are in the right-most child node).\n        // 4. We are now at a new page. If it's another interior page, we repeat the process from step 2. If it's a leaf page, we continue to step 5.\n        // 5. We scan the leaf cells in the leaf page until we find the cell whose rowid is equal to the rowid we are looking for.\n        //    This cell contains the actual data we are looking for.\n        // 6. If we find the cell, we return the record. Otherwise, we return an empty result.\n\n        // If we are at the beginning/end of seek state, start a new move from the root.\n        if matches!(\n            self.seek_state,\n            // these are stages that happen at the leaf page, so we can consider that the previous seek finished and we can start a new one.\n            CursorSeekState::LeafPageBinarySearch { .. } | CursorSeekState::FoundLeaf { .. }\n        ) {\n            self.seek_state = CursorSeekState::Start;\n        }\n        loop {\n            match self.move_to_state {\n                MoveToState::Start => {\n                    self.move_to_state = MoveToState::MoveToPage;\n                    if matches!(self.seek_state, CursorSeekState::Start) {\n                        let c = self.move_to_root()?;\n                        if let Some(c) = c {\n                            io_yield_one!(c);\n                        }\n                    }\n                }\n                MoveToState::MoveToPage => {\n                    let ret = match key {\n                        SeekKey::TableRowId(rowid_key) => self.tablebtree_move_to(rowid_key, cmp),\n                        SeekKey::IndexKey(index_key) => self.indexbtree_move_to(index_key, cmp),\n                    };\n                    return_if_io!(ret);\n                    self.move_to_state = MoveToState::Start;\n                    return Ok(IOResult::Done(()));\n                }\n            }\n        }\n    }\n\n    /// Insert a record into the btree.\n    /// If the insert operation overflows the page, it will be split and the btree will be balanced.\n    #[cfg_attr(debug_assertions, instrument(skip_all, level = Level::DEBUG))]\n    fn insert_into_page(&mut self, bkey: &BTreeKey) -> Result<IOResult<()>> {\n        let record = bkey\n            .get_record()\n            .expect(\"expected record present on insert\");\n        if let CursorState::None = &self.state {\n            self.state = CursorState::Write(WriteState::Start);\n        }\n        let usable_space = self.usable_space();\n        let ret = loop {\n            let CursorState::Write(write_state) = &mut self.state else {\n                panic!(\"expected write state\");\n            };\n            match write_state {\n                WriteState::Start => {\n                    let page = self.stack.top();\n\n                    // get page and find cell\n                    let cell_idx = {\n                        self.pager.add_dirty(&page)?;\n                        self.stack.current_cell_index()\n                    };\n                    if cell_idx == -1 {\n                        // This might be a brand new table and the cursor hasn't moved yet. Let's advance it to the first slot.\n                        self.stack.set_cell_index(0);\n                    }\n                    let cell_idx = self.stack.current_cell_index() as usize;\n                    tracing::debug!(cell_idx);\n\n                    // if the cell index is less than the total cells, check: if its an existing\n                    // rowid, we are going to update / overwrite the cell\n                    if cell_idx < page.get_contents().cell_count() {\n                        let cell = page.get_contents().cell_get(cell_idx, usable_space)?;\n                        match cell {\n                            BTreeCell::TableLeafCell(tbl_leaf) => {\n                                if tbl_leaf.rowid == bkey.to_rowid() {\n                                    tracing::debug!(\"TableLeafCell: found exact match with cell_idx={cell_idx}, overwriting\");\n                                    self.has_record = true;\n                                    *write_state = WriteState::Overwrite {\n                                        page,\n                                        cell_idx,\n                                        state: Some(OverwriteCellState::AllocatePayload),\n                                    };\n                                    continue;\n                                }\n                            }\n                            BTreeCell::IndexLeafCell(..) | BTreeCell::IndexInteriorCell(..) => {\n                                return_if_io!(self.record());\n                                let cmp = compare_immutable_iter(\n                                    record.iter()?,\n                                    self.get_immutable_record()\n                                        .as_ref()\n                                        .unwrap()\n                                        .iter()?,\n                                        &self.index_info.as_ref().unwrap().key_info,\n                                )?;\n                                if cmp == Ordering::Equal {\n                                    tracing::debug!(\"IndexLeafCell: found exact match with cell_idx={cell_idx}, overwriting\");\n                                    self.set_has_record(true);\n                                    let CursorState::Write(write_state) = &mut self.state else {\n                                        panic!(\"expected write state\");\n                                    };\n                                    *write_state = WriteState::Overwrite {\n                                        page,\n                                        cell_idx,\n                                        state: Some(OverwriteCellState::AllocatePayload),\n                                    };\n                                    continue;\n                                } else {\n                                    turso_assert!(\n                                        !matches!(cell, BTreeCell::IndexInteriorCell(..)),\n                                         \"we should not be inserting a new index interior cell. the only valid operation on an index interior cell is an overwrite!\"\n                                    );\n                                }\n                            }\n                            other => panic!(\"unexpected cell type, expected TableLeaf or IndexLeaf, found: {other:?}\"),\n                        }\n                    }\n\n                    let CursorState::Write(write_state) = &mut self.state else {\n                        panic!(\"expected write state\");\n                    };\n                    // Reuse the cell payload buffer to avoid allocations\n                    let mut payload = std::mem::take(&mut self.reusable_cell_payload);\n                    payload.clear();\n                    // Reserve capacity if needed (typical cell is small)\n                    // child pointer (4) + payload size varint (up to 9) + rowid varint (up to 9)\n                    const MAX_CELL_HEADER: usize = 22;\n                    let needed_capacity = record.get_payload().len() + MAX_CELL_HEADER;\n                    if payload.capacity() < needed_capacity {\n                        payload.reserve(needed_capacity - payload.capacity());\n                    }\n                    *write_state = WriteState::Insert {\n                        page,\n                        cell_idx,\n                        new_payload: payload,\n                        fill_cell_payload_state: FillCellPayloadState::Start,\n                    };\n                    continue;\n                }\n                WriteState::Insert {\n                    page,\n                    cell_idx,\n                    new_payload,\n                    ref mut fill_cell_payload_state,\n                } => {\n                    return_if_io!(fill_cell_payload(\n                        &PinGuard::new(page.clone()),\n                        bkey.maybe_rowid(),\n                        new_payload,\n                        *cell_idx,\n                        record,\n                        usable_space,\n                        self.pager.clone(),\n                        fill_cell_payload_state,\n                    ));\n\n                    {\n                        let contents = page.get_contents();\n                        tracing::debug!(name: \"overflow\", cell_count = contents.cell_count());\n\n                        insert_into_cell(\n                            contents,\n                            new_payload.as_slice(),\n                            *cell_idx,\n                            usable_space,\n                        )?;\n                    };\n                    self.stack.set_cell_index(*cell_idx as i32);\n                    let overflows = !page.get_contents().overflow_cells.is_empty();\n\n                    // Recover the reusable buffer before transitioning state\n                    let recovered_payload = std::mem::take(new_payload);\n                    self.reusable_cell_payload = recovered_payload;\n\n                    if overflows {\n                        *write_state = WriteState::Balancing;\n                        turso_assert!(matches!(self.balance_state.sub_state, BalanceSubState::Start), \"no balancing operation should be in progress during insert\", { \"state\": self.state, \"sub_state\": self.balance_state.sub_state });\n                        // If we balance, we must save the cursor position and seek to it later.\n                        self.save_context(CursorContext::seek_eq_only(bkey));\n                    } else {\n                        *write_state = WriteState::Finish;\n                    }\n                    continue;\n                }\n                WriteState::Overwrite {\n                    page,\n                    cell_idx,\n                    ref mut state,\n                } => {\n                    turso_assert!(page.is_loaded(), \"page is not loaded\", { \"page_id\": page.get().id });\n                    let page = page.clone();\n\n                    // Currently it's necessary to .take() here to prevent double-borrow of `self` in `overwrite_cell`.\n                    // We insert the state back if overwriting returns IO.\n                    let mut state = state.take().expect(\"state should be present\");\n                    let cell_idx = *cell_idx;\n                    if let IOResult::IO(io) =\n                        self.overwrite_cell(&page, cell_idx, record, &mut state)?\n                    {\n                        let CursorState::Write(write_state) = &mut self.state else {\n                            panic!(\"expected write state\");\n                        };\n                        *write_state = WriteState::Overwrite {\n                            page,\n                            cell_idx,\n                            state: Some(state),\n                        };\n                        return Ok(IOResult::IO(io));\n                    }\n                    let overflows = !page.get_contents().overflow_cells.is_empty();\n                    let underflows = !overflows && {\n                        let free_space = compute_free_space(page.get_contents(), usable_space)?;\n                        free_space * 3 > usable_space * 2\n                    };\n                    let CursorState::Write(write_state) = &mut self.state else {\n                        panic!(\"expected write state\");\n                    };\n                    if overflows || underflows {\n                        *write_state = WriteState::Balancing;\n                        turso_assert!(matches!(self.balance_state.sub_state, BalanceSubState::Start), \"no balancing operation should be in progress during overwrite\", { \"state\": self.state, \"sub_state\": self.balance_state.sub_state });\n                        // If we balance, we must save the cursor position and seek to it later.\n                        self.save_context(CursorContext::seek_eq_only(bkey));\n                    } else {\n                        *write_state = WriteState::Finish;\n                    }\n                    continue;\n                }\n                WriteState::Balancing => {\n                    return_if_io!(self.balance(None));\n                    let CursorState::Write(write_state) = &mut self.state else {\n                        panic!(\"expected write state\");\n                    };\n                    *write_state = WriteState::Finish;\n                }\n                WriteState::Finish => {\n                    break Ok(IOResult::Done(()));\n                }\n            };\n        };\n        if matches!(self.state, CursorState::Write(WriteState::Finish)) {\n            // if there was a balance triggered, the cursor position is invalid.\n            // it's probably not the greatest idea in the world to do this eagerly here,\n            // but at least it works.\n            return_if_io!(self.restore_context());\n        }\n        self.state = CursorState::None;\n        ret\n    }\n\n    /// Balance a leaf page.\n    /// Balancing is done when a page overflows.\n    /// see e.g. https://en.wikipedia.org/wiki/B-tree\n    ///\n    /// This is a naive algorithm that doesn't try to distribute cells evenly by content.\n    /// It will try to split the page in half by keys not by content.\n    /// Sqlite tries to have a page at least 40% full.\n    ///\n    /// `balance_ancestor_at_depth` specifies whether to balance an ancestor page at a specific depth.\n    /// If `None`, balancing stops when a level is encountered that doesn't need balancing.\n    /// If `Some(depth)`, the page on the stack at depth `depth` will be rebalanced after balancing the current page.\n    #[cfg_attr(debug_assertions, instrument(skip(self), level = Level::DEBUG))]\n    fn balance(&mut self, balance_ancestor_at_depth: Option<usize>) -> Result<IOResult<()>> {\n        loop {\n            let usable_space = self.usable_space();\n            let BalanceState {\n                sub_state,\n                balance_info,\n                ..\n            } = &mut self.balance_state;\n            match sub_state {\n                BalanceSubState::Start => {\n                    turso_assert!(\n                        balance_info.is_none(),\n                        \"BalanceInfo should be empty on start\"\n                    );\n                    let current_page = self.stack.top_ref();\n                    let next_balance_depth =\n                        balance_ancestor_at_depth.unwrap_or_else(|| self.stack.current());\n                    {\n                        // check if we don't need to balance\n                        // don't continue if:\n                        // - current page is not overfull root\n                        // OR\n                        // - current page is not overfull and the amount of free space on the page\n                        // is less than 2/3rds of the total usable space on the page\n                        //\n                        // https://github.com/sqlite/sqlite/blob/0aa95099f5003dc99f599ab77ac0004950b281ef/src/btree.c#L9064-L9071\n                        let page = current_page.get_contents();\n                        let free_space = compute_free_space(page, usable_space)?;\n                        let this_level_is_already_balanced = page.overflow_cells.is_empty()\n                            && (!self.stack.has_parent() || free_space * 3 <= usable_space * 2);\n                        if this_level_is_already_balanced {\n                            if self.stack.current() > next_balance_depth {\n                                while self.stack.current() > next_balance_depth {\n                                    // Even though this level is already balanced, we know there's an upper level that needs balancing.\n                                    // So we pop the stack and continue.\n                                    self.stack.pop();\n                                }\n                                continue;\n                            }\n                            // Otherwise, we're done.\n                            *sub_state = BalanceSubState::Start;\n                            return Ok(IOResult::Done(()));\n                        }\n                    }\n                    if !self.stack.has_parent() {\n                        *sub_state = BalanceSubState::BalanceRoot;\n                    } else {\n                        *sub_state = BalanceSubState::Decide;\n                    }\n                }\n                BalanceSubState::BalanceRoot => {\n                    return_if_io!(self.balance_root());\n\n                    let BalanceState { sub_state, .. } = &mut self.balance_state;\n                    *sub_state = BalanceSubState::Decide;\n                }\n                BalanceSubState::Decide => {\n                    let cur_page = self.stack.top_ref();\n                    let cur_page_contents = cur_page.get_contents();\n\n                    // Check if we can use the balance_quick() fast path.\n                    let mut do_quick = false;\n                    if cur_page_contents.page_type()? == PageType::TableLeaf\n                        && cur_page_contents.overflow_cells.len() == 1\n                    {\n                        let overflow_cell_is_last =\n                            cur_page_contents.overflow_cells.first().unwrap().index\n                                == cur_page_contents.cell_count();\n                        if overflow_cell_is_last {\n                            let parent = self\n                                .stack\n                                .get_page_at_level(self.stack.current() - 1)\n                                .expect(\"parent page should be on the stack\");\n                            let parent_contents = parent.get_contents();\n                            let parent_rightmost =\n                                parent_contents.rightmost_pointer()?.ok_or_else(|| {\n                                    mark_unlikely();\n                                    LimboError::Corrupt(format!(\n                                        \"parent page {} is a leaf page, expected interior page\",\n                                        parent.get().id\n                                    ))\n                                })?;\n                            if parent.get().id != 1 && parent_rightmost == cur_page.get().id as u32\n                            {\n                                // If all of the following are true, we can use the balance_quick() fast path:\n                                // - The page is a table leaf page\n                                // - The overflow cell would be the last cell on the leaf page\n                                // - The parent page is not page 1\n                                // - The leaf page is the rightmost page in the subtree\n                                do_quick = true;\n                            }\n                        }\n                    }\n\n                    let BalanceState { sub_state, .. } = &mut self.balance_state;\n                    if do_quick {\n                        *sub_state = BalanceSubState::Quick;\n                    } else {\n                        *sub_state = BalanceSubState::NonRootPickSiblings;\n                        self.stack.pop();\n                    }\n                }\n                BalanceSubState::Quick => {\n                    return_if_io!(self.balance_quick());\n                }\n                BalanceSubState::NonRootPickSiblings\n                | BalanceSubState::NonRootDoBalancing\n                | BalanceSubState::NonRootDoBalancingAllocate { .. }\n                | BalanceSubState::NonRootDoBalancingFinish { .. }\n                | BalanceSubState::FreePages { .. } => {\n                    return_if_io!(self.balance_non_root());\n                }\n            }\n        }\n    }\n\n    /// Fast balancing routine for the common special case where the rightmost leaf page of a given subtree overflows (= an append).\n    /// In this case we just add a new leaf page as the right sibling of that page, and insert a new divider cell into the parent.\n    /// The high level steps are:\n    /// 1. Allocate a new leaf page and insert the overflow cell payload in it.\n    /// 2. Create a new divider cell in the parent - it contains the page number of the old rightmost leaf, plus the largest rowid on that page.\n    /// 3. Update the rightmost pointer of the parent to point to the new leaf page.\n    /// 4. Continue balance from the parent page (inserting the new divider cell may have overflowed the parent)\n    #[cfg_attr(debug_assertions, instrument(skip(self), level = Level::DEBUG))]\n    fn balance_quick(&mut self) -> Result<IOResult<()>> {\n        // Since we are going to change the btree structure, let's forget our cached knowledge of the rightmost page.\n        let _ = self.move_to_right_state.1.take();\n\n        // Allocate a new leaf page and insert the overflow cell payload in it.\n        let new_rightmost_leaf = return_if_io!(self.pager.do_allocate_page(\n            PageType::TableLeaf,\n            0,\n            BtreePageAllocMode::Any\n        ));\n        self.pager.add_dirty(&new_rightmost_leaf)?;\n\n        let usable_space = self.usable_space();\n        let old_rightmost_leaf = self.stack.top_ref();\n        let old_rightmost_leaf_contents = old_rightmost_leaf.get_contents();\n        turso_assert!(\n            old_rightmost_leaf_contents.overflow_cells.len() == 1,\n            \"expected 1 overflow cell\",\n            { \"overflow_cell_count\": old_rightmost_leaf_contents.overflow_cells.len() }\n        );\n\n        let parent = self\n            .stack\n            .get_page_at_level(self.stack.current() - 1)\n            .expect(\"parent page should be on the stack\");\n        self.pager.add_dirty(parent)?;\n        let parent_contents = parent.get_contents();\n        let rightmost_pointer = parent_contents\n            .rightmost_pointer()?\n            .expect(\"parent should have a rightmost pointer\");\n        turso_assert!(\n            rightmost_pointer == old_rightmost_leaf.get().id as u32,\n            \"leaf should be the rightmost page in the subtree\"\n        );\n\n        let overflow_cell = old_rightmost_leaf_contents\n            .overflow_cells\n            .pop()\n            .expect(\"overflow cell should be present\");\n        turso_assert!(\n            overflow_cell.index == old_rightmost_leaf_contents.cell_count(),\n            \"overflow cell must be the last cell in the leaf\"\n        );\n\n        let new_rightmost_leaf_contents = new_rightmost_leaf.get_contents();\n        insert_into_cell(\n            new_rightmost_leaf_contents,\n            &overflow_cell.payload.as_ref(),\n            0,\n            usable_space,\n        )?;\n\n        // Create a new divider cell in the parent - it contains the page number of the old rightmost leaf, plus the largest rowid on that page.\n        let mut new_divider: [u8; 13] = [0; 13]; // 4 bytes for page number, max 9 bytes for rowid (varint)\n        new_divider[0..4].copy_from_slice(&(old_rightmost_leaf.get().id as u32).to_be_bytes());\n        let largest_rowid = old_rightmost_leaf_contents\n            .cell_table_leaf_read_rowid(old_rightmost_leaf_contents.cell_count() - 1)?;\n        let n = write_varint(&mut new_divider[4..], largest_rowid as u64);\n        let divider_length = 4 + n;\n\n        // Insert the new divider cell into the parent.\n        insert_into_cell(\n            parent_contents,\n            &new_divider[..divider_length],\n            parent_contents.cell_count(),\n            usable_space,\n        )?;\n        parent_contents.write_rightmost_ptr(new_rightmost_leaf.get().id as u32);\n        // Continue balance from the parent page (inserting the new divider cell may have overflowed the parent)\n        self.stack.pop();\n\n        let BalanceState { sub_state, .. } = &mut self.balance_state;\n        *sub_state = BalanceSubState::Start;\n        Ok(IOResult::Done(()))\n    }\n\n    /// Balance a non root page by trying to balance cells between a maximum of 3 siblings that should be neighboring the page that overflowed/underflowed.\n    #[cfg_attr(debug_assertions, instrument(skip(self), level = Level::DEBUG))]\n    fn balance_non_root(&mut self) -> Result<IOResult<()>> {\n        loop {\n            let usable_space = self.usable_space();\n            let BalanceState {\n                sub_state,\n                balance_info,\n                reusable_divider_buffers,\n                reusable_cell_payloads,\n            } = &mut self.balance_state;\n            tracing::debug!(?sub_state);\n\n            match sub_state {\n                BalanceSubState::Start\n                | BalanceSubState::BalanceRoot\n                | BalanceSubState::Decide\n                | BalanceSubState::Quick => {\n                    panic!(\"balance_non_root: unexpected state {sub_state:?}\")\n                }\n                BalanceSubState::NonRootPickSiblings => {\n                    // Since we are going to change the btree structure, let's forget our cached knowledge of the rightmost page.\n                    let _ = self.move_to_right_state.1.take();\n\n                    let (parent_page_idx, page_type, cell_count, over_cell_count) = {\n                        let parent_page = self.stack.top_ref();\n                        let parent_contents = parent_page.get_contents();\n                        (\n                            self.stack.current(),\n                            parent_contents.page_type()?,\n                            parent_contents.cell_count(),\n                            parent_contents.overflow_cells.len(),\n                        )\n                    };\n\n                    turso_assert!(\n                        matches!(page_type, PageType::IndexInterior | PageType::TableInterior),\n                        \"expected index or table interior page\"\n                    );\n                    let number_of_cells_in_parent = cell_count + over_cell_count;\n\n                    // If `seek` moved to rightmost page, cell index will be out of bounds. Meaning cell_count+1.\n                    // In any other case, `seek` will stay in the correct index.\n                    let past_rightmost_pointer =\n                        self.stack.current_cell_index() as usize == number_of_cells_in_parent + 1;\n                    if past_rightmost_pointer {\n                        self.stack.retreat();\n                    }\n\n                    let parent_page = self.stack.get_page_at_level(parent_page_idx).unwrap();\n                    let parent_contents = parent_page.get_contents();\n                    if !past_rightmost_pointer && over_cell_count > 0 {\n                        // The ONLY way we can have an overflow cell in the parent is if we replaced an interior cell from a cell in the child, and that replacement did not fit.\n                        // This can only happen on index btrees.\n                        if matches!(page_type, PageType::IndexInterior) {\n                            turso_assert!(parent_contents.overflow_cells.len() == 1, \"index interior page must have no more than 1 overflow cell, as a result of InteriorNodeReplacement\");\n                        } else {\n                            turso_assert!(false, \"page type must have no overflow cells\", { \"page_type\": page_type });\n                        }\n                        let overflow_cell = parent_contents.overflow_cells.first().unwrap();\n                        let parent_page_cell_idx = self.stack.current_cell_index() as usize;\n                        // Parent page must be positioned at the divider cell that overflowed due to the replacement.\n                        turso_assert!(\n                            overflow_cell.index == parent_page_cell_idx,\n                            \"overflow cell index must be the result of InteriorNodeReplacement that leaves both child and parent unbalanced, and hence parent page's position must equal overflow_cell.index\",\n                            { \"parent_page_id\": parent_page.get().id, \"parent_page_cell_idx\": parent_page_cell_idx, \"overflow_cell_index\": overflow_cell.index }\n                        );\n                    }\n                    self.pager.add_dirty(parent_page)?;\n                    let parent_contents = parent_page.get_contents();\n                    let page_to_balance_idx = self.stack.current_cell_index() as usize;\n\n                    tracing::debug!(\n                        \"balance_non_root(parent_id={} page_to_balance_idx={})\",\n                        parent_page.get().id,\n                        page_to_balance_idx\n                    );\n                    // Part 1: Find the sibling pages to balance\n                    let mut pages_to_balance: [Option<PinGuard>; MAX_SIBLING_PAGES_TO_BALANCE] =\n                        [const { None }; MAX_SIBLING_PAGES_TO_BALANCE];\n                    turso_assert!(\n                        page_to_balance_idx <= parent_contents.cell_count(),\n                        \"page_to_balance_idx={page_to_balance_idx} is out of bounds for parent cell count {number_of_cells_in_parent}\"\n                    );\n                    // As there will be at maximum 3 pages used to balance:\n                    // sibling_pointer is the index represeneting one of those 3 pages, and we initialize it to the last possible page.\n                    // next_divider is the first divider that contains the first page of the 3 pages.\n                    let (sibling_pointer, first_cell_divider) = match number_of_cells_in_parent {\n                        n if n < 2 => (number_of_cells_in_parent, 0),\n                        2 => (2, 0),\n                        // Here we will have at lest 2 cells and one right pointer, therefore we can get 3 siblings.\n                        // In case of 2 we will have all pages to balance.\n                        _ => {\n                            // In case of > 3 we have to check which ones to get\n                            let next_divider = if page_to_balance_idx == 0 {\n                                // first cell, take first 3\n                                0\n                            } else if page_to_balance_idx == number_of_cells_in_parent {\n                                // Page corresponds to right pointer, so take last 3\n                                number_of_cells_in_parent - 2\n                            } else {\n                                // Some cell in the middle, so we want to take sibling on left and right.\n                                page_to_balance_idx - 1\n                            };\n                            (2, next_divider)\n                        }\n                    };\n                    let sibling_count = sibling_pointer + 1;\n\n                    let last_sibling_is_right_pointer = sibling_pointer + first_cell_divider\n                        - parent_contents.overflow_cells.len()\n                        == parent_contents.cell_count();\n                    // Get the right page pointer that we will need to update later\n                    let right_pointer = if last_sibling_is_right_pointer {\n                        parent_contents.rightmost_pointer_raw()?.unwrap()\n                    } else {\n                        let max_overflow_cells = if matches!(page_type, PageType::IndexInterior) {\n                            1\n                        } else {\n                            0\n                        };\n                        turso_assert!(\n                            parent_contents.overflow_cells.len() <= max_overflow_cells,\n                            \"must have at most {max_overflow_cells} overflow cell in the parent\"\n                        );\n                        // OVERFLOW CELL ADJUSTMENT:\n                        // Let there be parent with cells [0,1,2,3,4].\n                        // Let's imagine the cell at idx 2 gets replaced with a new payload that causes it to overflow.\n                        // See handling of InteriorNodeReplacement in btree.rs.\n                        //\n                        // In this case the rightmost divider is going to be 3 (2 is the middle one and we pick neighbors 1-3).\n                        // drop_cell(): [0,1,2,3,4] -> [0,1,3,4]   <-- cells on right side get shifted left!\n                        // insert_into_cell(): [0,1,3,4] -> [0,1,3,4] + overflow cell (2)  <-- crucially, no physical shifting happens, overflow cell is stored separately\n                        //\n                        // This means '3' is actually physically located at index '2'.\n                        // So IF the parent has an overflow cell, we need to subtract 1 to get the actual rightmost divider cell idx to physically read from.\n                        // The formula for the actual cell idx is:\n                        // first_cell_divider + sibling_pointer - parent_contents.overflow_cells.len()\n                        // so in the above case:\n                        // actual_cell_idx = 1 + 2 - 1 = 2\n                        //\n                        // In the case where the last divider cell is the overflow cell, there would be no left-shifting of cells in drop_cell(),\n                        // because they are still positioned correctly (imagine .pop() from a vector).\n                        // However, note that we are always looking for the _rightmost_ child page pointer between the (max 2) dividers, and for any case where the last divider cell is the overflow cell,\n                        // the 'last_sibling_is_right_pointer' condition will also be true (since the overflow cell's left child will be the middle page), so we won't enter this code branch.\n                        //\n                        // Hence: when we enter this branch with overflow_cells.len() == 1, we know that left-shifting has happened and we need to subtract 1.\n                        let actual_cell_idx = first_cell_divider + sibling_pointer\n                            - parent_contents.overflow_cells.len();\n                        let start_of_cell =\n                            parent_contents.cell_get_raw_start_offset(actual_cell_idx);\n                        let buf = parent_contents.as_ptr().as_mut_ptr();\n                        unsafe { buf.add(start_of_cell) }\n                    };\n\n                    // load sibling pages\n                    // start loading right page first\n                    let mut pgno: u32 =\n                        unsafe { right_pointer.cast::<u32>().read_unaligned().swap_bytes() };\n                    let current_sibling = sibling_pointer;\n                    let mut group = CompletionGroup::new(|_| {});\n                    for i in (0..=current_sibling).rev() {\n                        match btree_read_page(&self.pager, pgno as i64) {\n                            Err(e) => {\n                                mark_unlikely();\n                                tracing::error!(\"error reading page {}: {}\", pgno, e);\n                                group.cancel();\n                                self.pager.io.drain()?;\n                                return Err(e);\n                            }\n                            Ok((page, c)) => {\n                                pages_to_balance[i].replace(PinGuard::new(page));\n                                if let Some(c) = c {\n                                    group.add(&c);\n                                }\n                            }\n                        }\n                        if i == 0 {\n                            break;\n                        }\n                        let next_cell_divider = i + first_cell_divider - 1;\n                        let divider_is_overflow_cell = parent_contents\n                            .overflow_cells\n                            .first()\n                            .is_some_and(|overflow_cell| overflow_cell.index == next_cell_divider);\n                        if divider_is_overflow_cell {\n                            turso_assert!(\n                                matches!(\n                                    parent_contents.page_type().ok(),\n                                    Some(PageType::IndexInterior)\n                                ),\n                                \"expected index interior page\",\n                                { \"page_type\": parent_contents.page_type().ok() }\n                            );\n                            turso_assert!(\n                                parent_contents.overflow_cells.len() == 1,\n                                \"must have a single overflow cell in the parent, as a result of InteriorNodeReplacement\"\n                            );\n                            let overflow_cell = parent_contents.overflow_cells.first().unwrap();\n                            pgno =\n                                u32::from_be_bytes(overflow_cell.payload[0..4].try_into().unwrap());\n                        } else {\n                            // grep for 'OVERFLOW CELL ADJUSTMENT' for explanation.\n                            // here we only subtract 1 if the divider cell has been shifted left, i.e. the overflow cell was placed to the left\n                            // this cell.\n                            let actual_cell_idx = if let Some(overflow_cell) =\n                                parent_contents.overflow_cells.first()\n                            {\n                                if next_cell_divider < overflow_cell.index {\n                                    next_cell_divider\n                                } else {\n                                    next_cell_divider - 1\n                                }\n                            } else {\n                                next_cell_divider\n                            };\n                            pgno = match parent_contents.cell_get(actual_cell_idx, usable_space)? {\n                                BTreeCell::TableInteriorCell(TableInteriorCell {\n                                    left_child_page,\n                                    ..\n                                })\n                                | BTreeCell::IndexInteriorCell(IndexInteriorCell {\n                                    left_child_page,\n                                    ..\n                                }) => left_child_page,\n                                other => {\n                                    mark_unlikely();\n                                    crate::bail_corrupt_error!(\n                                        \"expected interior cell, got {:?}\",\n                                        other\n                                    )\n                                }\n                            };\n                        }\n                    }\n\n                    balance_info.replace(BalanceInfo {\n                        pages_to_balance,\n                        rightmost_pointer: right_pointer,\n                        sibling_count,\n                        first_divider_cell: first_cell_divider,\n                        reusable_divider_cell: Vec::new(),\n                    });\n                    *sub_state = BalanceSubState::NonRootDoBalancing;\n                    let completion = group.build();\n                    if !completion.finished() {\n                        io_yield_one!(completion);\n                    }\n                }\n                BalanceSubState::NonRootDoBalancing => {\n                    // Ensure all involved pages are in memory.\n                    let balance_info = balance_info.as_mut().unwrap();\n                    for page in balance_info\n                        .pages_to_balance\n                        .iter()\n                        .take(balance_info.sibling_count)\n                    {\n                        let page = page.as_ref().unwrap();\n                        self.pager.add_dirty(page)?;\n\n                        #[cfg(debug_assertions)]\n                        let page_type_of_siblings = balance_info.pages_to_balance[0]\n                            .as_ref()\n                            .unwrap()\n                            .get_contents()\n                            .page_type()\n                            .ok();\n\n                        #[cfg(debug_assertions)]\n                        {\n                            let contents = page.get_contents();\n                            debug_validate_cells!(&contents, usable_space);\n                            turso_assert_eq!(contents.page_type().ok(), page_type_of_siblings);\n                        }\n                    }\n                    // Start balancing.\n                    let parent_page = PinGuard::new(self.stack.top_ref().clone());\n                    let parent_contents = parent_page.get_contents();\n\n                    // Pre-compute parent page parameters for faster cell region lookups.\n                    // Note: cell_count cannot be pre-computed as it changes during the loop via drop_cell.\n                    let parent_page_type = parent_contents.page_type()?;\n                    let parent_max_local =\n                        payload_overflow_threshold_max(parent_page_type, usable_space);\n                    let parent_min_local =\n                        payload_overflow_threshold_min(parent_page_type, usable_space);\n\n                    // 1. Collect cell data from divider cells, and count the total number of cells to be distributed.\n                    // The count includes: all cells and overflow cells from the sibling pages, and divider cells from the parent page,\n                    // excluding the rightmost divider, which will not be dropped from the parent; instead it will be updated at the end.\n                    let mut total_cells_to_redistribute = 0;\n                    let pages_to_balance_new: [Option<PinGuard>;\n                        MAX_NEW_SIBLING_PAGES_AFTER_BALANCE] =\n                        [const { None }; MAX_NEW_SIBLING_PAGES_AFTER_BALANCE];\n                    for i in (0..balance_info.sibling_count).rev() {\n                        let sibling_page = balance_info.pages_to_balance[i].as_ref().unwrap();\n                        turso_assert!(sibling_page.is_loaded(), \"sibling page is not loaded\");\n                        let sibling_contents = sibling_page.get_contents();\n                        total_cells_to_redistribute += sibling_contents.cell_count();\n                        total_cells_to_redistribute += sibling_contents.overflow_cells.len();\n\n                        // Right pointer is not dropped, we simply update it at the end. This could be a divider cell that points\n                        // to the last page in the list of pages to balance or this could be the rightmost pointer that points to a page.\n                        let is_last_sibling = i == balance_info.sibling_count - 1;\n                        if is_last_sibling {\n                            continue;\n                        }\n                        // Since we know we have a left sibling, take the divider that points to left sibling of this page\n                        let cell_idx = balance_info.first_divider_cell + i;\n                        let divider_is_overflow_cell = parent_contents\n                            .overflow_cells\n                            .first()\n                            .is_some_and(|overflow_cell| overflow_cell.index == cell_idx);\n                        let cell_buf = if divider_is_overflow_cell {\n                            turso_assert!(\n                                matches!(\n                                    parent_contents.page_type().ok(),\n                                    Some(PageType::IndexInterior)\n                                ),\n                                \"expected index interior page\",\n                                { \"page_type\": parent_contents.page_type().ok() }\n                            );\n                            turso_assert!(\n                                parent_contents.overflow_cells.len() == 1,\n                                \"must have a single overflow cell in the parent, as a result of InteriorNodeReplacement\"\n                            );\n                            let overflow_cell = parent_contents.overflow_cells.first().unwrap();\n                            &overflow_cell.payload\n                        } else {\n                            // grep for 'OVERFLOW CELL ADJUSTMENT' for explanation.\n                            // here we can subtract overflow_cells.len() every time, because we are iterating right-to-left,\n                            // so if we are to the left of the overflow cell, it has already been cleared from the parent and overflow_cells.len() is 0.\n                            let actual_cell_idx = cell_idx - parent_contents.overflow_cells.len();\n                            // Use pre-computed page parameters for faster lookup.\n                            // Note: cell_count must be fresh as it changes during the loop.\n                            let (cell_start, cell_len) = parent_contents\n                                ._cell_get_raw_region_faster(\n                                    actual_cell_idx,\n                                    usable_space,\n                                    parent_contents.cell_count(),\n                                    parent_max_local,\n                                    parent_min_local,\n                                    parent_page_type,\n                                )?;\n                            let buf = parent_contents.as_ptr();\n                            &buf[cell_start..cell_start + cell_len]\n                        };\n\n                        // Count the divider cell itself (which will be dropped from the parent)\n                        total_cells_to_redistribute += 1;\n\n                        tracing::debug!(\n                            \"balance_non_root(drop_divider_cell, first_divider_cell={}, divider_cell={}, left_pointer={})\",\n                            balance_info.first_divider_cell,\n                            i,\n                            read_u32(cell_buf, 0)\n                        );\n\n                        // Reuse the divider buffer to avoid allocation per balance operation.\n                        // The buffer is cleared and filled with the new cell data.\n                        reusable_divider_buffers[i].clear();\n                        reusable_divider_buffers[i].extend_from_slice(cell_buf);\n                        if divider_is_overflow_cell {\n                            tracing::debug!(\n                                \"clearing overflow cells from parent cell_idx={}\",\n                                cell_idx\n                            );\n                            parent_contents.overflow_cells.clear();\n                        } else {\n                            // grep for 'OVERFLOW CELL ADJUSTMENT' for explanation.\n                            // here we can subtract overflow_cells.len() every time, because we are iterating right-to-left,\n                            // so if we are to the left of the overflow cell, it has already been cleared from the parent and overflow_cells.len() is 0.\n                            let actual_cell_idx = cell_idx - parent_contents.overflow_cells.len();\n                            tracing::trace!(\n                                \"dropping divider cell from parent cell_idx={} count={}\",\n                                actual_cell_idx,\n                                parent_contents.cell_count()\n                            );\n                            drop_cell(parent_contents, actual_cell_idx, usable_space)?;\n                        }\n                    }\n\n                    /* 2. Initialize CellArray with all the cells used for distribution, this includes divider cells if !leaf. */\n                    // Reuse the cell_payloads Vec from previous balance operations to avoid allocation.\n                    let mut cell_payloads_vec = std::mem::take(reusable_cell_payloads);\n                    cell_payloads_vec.clear();\n                    // Ensure we have at least total_cells_to_redistribute capacity.\n                    // Since len=0 after clear, reserve(n) ensures capacity >= n.\n                    cell_payloads_vec.reserve(total_cells_to_redistribute);\n                    let mut cell_array = CellArray {\n                        cell_payloads: cell_payloads_vec,\n                        cell_count_per_page_cumulative: [0; MAX_NEW_SIBLING_PAGES_AFTER_BALANCE],\n                    };\n                    let cells_capacity_start = cell_array.cell_payloads.capacity();\n\n                    let mut total_cells_inserted = 0;\n                    // This is otherwise identical to CellArray.cell_count_per_page_cumulative,\n                    // but we exclusively track what the prefix sums were _before_ we started redistributing cells.\n                    let mut old_cell_count_per_page_cumulative: [u16;\n                        MAX_NEW_SIBLING_PAGES_AFTER_BALANCE] =\n                        [0; MAX_NEW_SIBLING_PAGES_AFTER_BALANCE];\n\n                    let page_type = balance_info.pages_to_balance[0]\n                        .as_ref()\n                        .unwrap()\n                        .get_contents()\n                        .page_type()?;\n                    tracing::debug!(\"balance_non_root(page_type={:?})\", page_type);\n                    let is_table_leaf = matches!(page_type, PageType::TableLeaf);\n                    let is_leaf = matches!(page_type, PageType::TableLeaf | PageType::IndexLeaf);\n                    for (i, old_page) in balance_info\n                        .pages_to_balance\n                        .iter()\n                        .take(balance_info.sibling_count)\n                        .enumerate()\n                    {\n                        let old_page = old_page.as_ref().unwrap();\n                        let old_page_contents = old_page.get_contents();\n                        let page_type = old_page_contents.page_type()?;\n                        let max_local = payload_overflow_threshold_max(page_type, usable_space);\n                        let min_local = payload_overflow_threshold_min(page_type, usable_space);\n                        let cell_count = old_page_contents.cell_count();\n                        debug_validate_cells!(&old_page_contents, usable_space);\n                        for cell_idx in 0..cell_count {\n                            let (cell_start, cell_len) = old_page_contents\n                                ._cell_get_raw_region_faster(\n                                    cell_idx,\n                                    usable_space,\n                                    cell_count,\n                                    max_local,\n                                    min_local,\n                                    page_type,\n                                )?;\n                            let buf = old_page_contents.as_ptr();\n                            let cell_buf = &mut buf[cell_start..cell_start + cell_len];\n                            // TODO(pere): make this reference and not copy\n                            cell_array.cell_payloads.push(to_static_buf(cell_buf));\n                        }\n                        // Insert overflow cells into correct place\n                        let offset = total_cells_inserted;\n                        for overflow_cell in old_page_contents.overflow_cells.iter_mut() {\n                            cell_array.cell_payloads.insert(\n                                offset + overflow_cell.index,\n                                to_static_buf(&mut Pin::as_mut(&mut overflow_cell.payload)),\n                            );\n                        }\n\n                        old_cell_count_per_page_cumulative[i] =\n                            cell_array.cell_payloads.len() as u16;\n\n                        let mut cells_inserted =\n                            old_page_contents.cell_count() + old_page_contents.overflow_cells.len();\n\n                        let is_last_sibling = i == balance_info.sibling_count - 1;\n                        if !is_last_sibling && !is_table_leaf {\n                            // If we are a index page or a interior table page we need to take the divider cell too.\n                            // But we don't need the last divider as it will remain the same.\n                            let mut divider_cell = reusable_divider_buffers[i].as_mut_slice();\n                            // TODO(pere): in case of old pages are leaf pages, so index leaf page, we need to strip page pointers\n                            // from divider cells in index interior pages (parent) because those should not be included.\n                            cells_inserted += 1;\n                            if !is_leaf {\n                                // This divider cell needs to be updated with new left pointer,\n                                let right_pointer = old_page_contents.rightmost_pointer()?.unwrap();\n                                divider_cell[..LEFT_CHILD_PTR_SIZE_BYTES]\n                                    .copy_from_slice(&right_pointer.to_be_bytes());\n                            } else {\n                                // index leaf\n                                turso_assert!(\n                                    divider_cell.len() >= LEFT_CHILD_PTR_SIZE_BYTES,\n                                    \"divider cell is too short\"\n                                );\n                                // let's strip the page pointer\n                                divider_cell = &mut divider_cell[LEFT_CHILD_PTR_SIZE_BYTES..];\n                            }\n                            cell_array.cell_payloads.push(to_static_buf(divider_cell));\n                        }\n                        total_cells_inserted += cells_inserted;\n                    }\n                    turso_assert!(\n                        cell_array.cell_payloads.capacity() == cells_capacity_start,\n                        \"calculation of max cells was wrong\"\n                    );\n\n                    // Verify that all cells were collected correctly.\n                    // Note: For table leaf pages, dividers are counted in total_cells_to_redistribute\n                    // but are NOT included in cell_array (they stay in parent as bookkeeping).\n                    // For index/interior pages, dividers ARE included in cell_array.\n                    let dividers_in_parent_only = if is_table_leaf {\n                        // Table leaf: dividers are NOT added to cell_array\n                        balance_info.sibling_count.saturating_sub(1)\n                    } else {\n                        // Index/interior: dividers ARE added to cell_array\n                        0\n                    };\n                    let expected_cells_in_array =\n                        total_cells_to_redistribute - dividers_in_parent_only;\n                    turso_assert!(\n                        cell_array.cell_payloads.len() == expected_cells_in_array,\n                        \"cell count mismatch after collection\",\n                        { \"collected\": cell_array.cell_payloads.len(), \"expected\": expected_cells_in_array, \"total_cells_to_redistribute\": total_cells_to_redistribute, \"dividers_in_parent_only\": dividers_in_parent_only, \"is_table_leaf\": is_table_leaf }\n                    );\n                    turso_assert!(\n                        total_cells_inserted == expected_cells_in_array,\n                        \"cell count mismatch between total cells inserted and expected\",\n                        { \"total_cells_inserted\": total_cells_inserted, \"expected_cells_in_array\": expected_cells_in_array, \"total_cells_to_redistribute\": total_cells_to_redistribute, \"dividers_in_parent_only\": dividers_in_parent_only }\n                    );\n\n                    // Let's copy all cells for later checks\n                    #[cfg(debug_assertions)]\n                    let mut cells_debug = Vec::new();\n                    #[cfg(debug_assertions)]\n                    {\n                        for cell in &cell_array.cell_payloads {\n                            cells_debug.push(cell.to_vec());\n                            if is_leaf {\n                                crate::turso_assert_ne!(cell[0], 0);\n                            }\n                        }\n                    }\n\n                    #[cfg(debug_assertions)]\n                    validate_cells_after_insertion(&cell_array, is_table_leaf);\n\n                    /* 3. Initiliaze current size of every page including overflow cells and divider cells that might be included. */\n                    let mut new_page_sizes: [i64; MAX_NEW_SIBLING_PAGES_AFTER_BALANCE] =\n                        [0; MAX_NEW_SIBLING_PAGES_AFTER_BALANCE];\n                    let header_size = if is_leaf {\n                        LEAF_PAGE_HEADER_SIZE_BYTES\n                    } else {\n                        INTERIOR_PAGE_HEADER_SIZE_BYTES\n                    };\n                    // number of bytes beyond header, different from global usableSapce which includes\n                    // header\n                    let usable_space_without_header = usable_space - header_size;\n                    for i in 0..balance_info.sibling_count {\n                        cell_array.cell_count_per_page_cumulative[i] =\n                            old_cell_count_per_page_cumulative[i];\n                        let page = &balance_info.pages_to_balance[i].as_ref().unwrap();\n                        let page_contents = page.get_contents();\n                        let free_space = compute_free_space(page_contents, usable_space)?;\n\n                        new_page_sizes[i] = usable_space_without_header as i64 - free_space as i64;\n                        for overflow in &page_contents.overflow_cells {\n                            // 2 to account of pointer\n                            new_page_sizes[i] += 2 + overflow.payload.len() as i64;\n                        }\n                        let is_last_sibling = i == balance_info.sibling_count - 1;\n                        if !is_leaf && !is_last_sibling {\n                            // Account for divider cell which is included in this page.\n                            new_page_sizes[i] += cell_array.cell_payloads\n                                [cell_array.cell_count_up_to_page(i)]\n                            .len() as i64;\n                        }\n                    }\n\n                    /* 4. Now let's try to move cells to the left trying to stack them without exceeding the maximum size of a page.\n                         There are two cases:\n                           * If current page has too many cells, it will move them to the next page.\n                           * If it still has space, and it can take a cell from the right it will take them.\n                             Here there is a caveat. Taking a cell from the right might take cells from page i+1, i+2, i+3, so not necessarily\n                             adjacent. But we decrease the size of the adjacent page if we move from the right. This might cause a intermitent state\n                             where page can have size <0.\n                        This will also calculate how many pages are required to balance the cells and store in sibling_count_new.\n                    */\n                    // Try to pack as many cells to the left\n                    let mut sibling_count_new = balance_info.sibling_count;\n                    let mut i = 0;\n                    while i < sibling_count_new {\n                        // First try to move cells to the right if they do not fit\n                        while new_page_sizes[i] > usable_space_without_header as i64 {\n                            let needs_new_page = i + 1 >= sibling_count_new;\n                            if needs_new_page {\n                                sibling_count_new = i + 2;\n                                turso_assert!(\n                                    sibling_count_new <= 5,\n                                    \"it is corrupt to require more than 5 pages to balance 3 siblings\"\n                                );\n\n                                new_page_sizes[sibling_count_new - 1] = 0;\n                                cell_array.cell_count_per_page_cumulative[sibling_count_new - 1] =\n                                    cell_array.cell_payloads.len() as u16;\n                            }\n                            let size_of_cell_to_remove_from_left = 2 + cell_array.cell_payloads\n                                [cell_array.cell_count_up_to_page(i) - 1]\n                                .len()\n                                as i64;\n                            new_page_sizes[i] -= size_of_cell_to_remove_from_left;\n                            let size_of_cell_to_move_right = if !is_table_leaf {\n                                if cell_array.cell_count_per_page_cumulative[i]\n                                    < cell_array.cell_payloads.len() as u16\n                                {\n                                    // This means we move to the right page the divider cell and we\n                                    // promote left cell to divider\n                                    CELL_PTR_SIZE_BYTES as i64\n                                        + cell_array.cell_payloads\n                                            [cell_array.cell_count_up_to_page(i)]\n                                        .len() as i64\n                                } else {\n                                    0\n                                }\n                            } else {\n                                size_of_cell_to_remove_from_left\n                            };\n                            new_page_sizes[i + 1] += size_of_cell_to_move_right;\n                            cell_array.cell_count_per_page_cumulative[i] -= 1;\n                        }\n\n                        // Now try to take from the right if we didn't have enough\n                        while cell_array.cell_count_per_page_cumulative[i]\n                            < cell_array.cell_payloads.len() as u16\n                        {\n                            let size_of_cell_to_remove_from_right = CELL_PTR_SIZE_BYTES as i64\n                                + cell_array.cell_payloads[cell_array.cell_count_up_to_page(i)]\n                                    .len() as i64;\n                            let can_take = new_page_sizes[i] + size_of_cell_to_remove_from_right\n                                > usable_space_without_header as i64;\n                            if can_take {\n                                break;\n                            }\n                            new_page_sizes[i] += size_of_cell_to_remove_from_right;\n                            cell_array.cell_count_per_page_cumulative[i] += 1;\n\n                            let size_of_cell_to_remove_from_right = if !is_table_leaf {\n                                if cell_array.cell_count_per_page_cumulative[i]\n                                    < cell_array.cell_payloads.len() as u16\n                                {\n                                    CELL_PTR_SIZE_BYTES as i64\n                                        + cell_array.cell_payloads\n                                            [cell_array.cell_count_up_to_page(i)]\n                                        .len() as i64\n                                } else {\n                                    0\n                                }\n                            } else {\n                                size_of_cell_to_remove_from_right\n                            };\n\n                            new_page_sizes[i + 1] -= size_of_cell_to_remove_from_right;\n                        }\n\n                        // Check if this page contains up to the last cell. If this happens it means we really just need up to this page.\n                        // Let's update the number of new pages to be up to this page (i+1)\n                        let page_completes_all_cells = cell_array.cell_count_per_page_cumulative[i]\n                            >= cell_array.cell_payloads.len() as u16;\n                        if page_completes_all_cells {\n                            sibling_count_new = i + 1;\n                            break;\n                        }\n                        i += 1;\n                        if i >= sibling_count_new {\n                            break;\n                        }\n                    }\n\n                    tracing::debug!(\n                        \"balance_non_root(sibling_count={}, sibling_count_new={}, cells={})\",\n                        balance_info.sibling_count,\n                        sibling_count_new,\n                        cell_array.cell_payloads.len()\n                    );\n\n                    /* 5. Balance pages starting from a left stacked cell state and move them to right trying to maintain a balanced state\n                    where we only move from left to right if it will not unbalance both pages, meaning moving left to right won't make\n                    right page bigger than left page.\n                    */\n                    // Comment borrowed from SQLite src/btree.c\n                    // The packing computed by the previous block is biased toward the siblings\n                    // on the left side (siblings with smaller keys). The left siblings are\n                    // always nearly full, while the right-most sibling might be nearly empty.\n                    // The next block of code attempts to adjust the packing of siblings to\n                    // get a better balance.\n                    //\n                    // This adjustment is more than an optimization.  The packing above might\n                    // be so out of balance as to be illegal.  For example, the right-most\n                    // sibling might be completely empty.  This adjustment is not optional.\n                    for i in (1..sibling_count_new).rev() {\n                        let mut size_right_page = new_page_sizes[i];\n                        let mut size_left_page = new_page_sizes[i - 1];\n                        let mut cell_left = cell_array.cell_count_per_page_cumulative[i - 1] - 1;\n                        // When table leaves are being balanced, divider cells are not part of the balancing,\n                        // because table dividers don't have payloads unlike index dividers.\n                        // Hence:\n                        // - For table leaves: the same cell that is removed from left is added to right.\n                        // - For all other page types: the divider cell is added to right, and the last non-divider cell is removed from left;\n                        //   the cell removed from the left will later become a new divider cell in the parent page.\n                        // TABLE LEAVES BALANCING:\n                        // =======================\n                        // Before balancing:\n                        // LEFT                          RIGHT\n                        // +-----+-----+-----+-----+    +-----+-----+\n                        // | C1  | C2  | C3  | C4  |    | C5  | C6  |\n                        // +-----+-----+-----+-----+    +-----+-----+\n                        //         ^                           ^\n                        //    (too full)                  (has space)\n                        // After balancing:\n                        // LEFT                     RIGHT\n                        // +-----+-----+-----+      +-----+-----+-----+\n                        // | C1  | C2  | C3  |      | C4  | C5  | C6  |\n                        // +-----+-----+-----+      +-----+-----+-----+\n                        //                               ^\n                        //                          (C4 moved directly)\n                        //\n                        // (C3's rowid also becomes the divider cell's rowid in the parent page\n                        //\n                        // OTHER PAGE TYPES BALANCING:\n                        // ===========================\n                        // Before balancing:\n                        // PARENT: [...|D1|...]\n                        //            |\n                        // LEFT                          RIGHT\n                        // +-----+-----+-----+-----+    +-----+-----+\n                        // | K1  | K2  | K3  | K4  |    | K5  | K6  |\n                        // +-----+-----+-----+-----+    +-----+-----+\n                        //         ^                           ^\n                        //    (too full)                  (has space)\n                        // After balancing:\n                        // PARENT: [...|K4|...]  <-- K4 becomes new divider\n                        //            |\n                        // LEFT                     RIGHT\n                        // +-----+-----+-----+      +-----+-----+-----+\n                        // | K1  | K2  | K3  |      | D1  | K5  | K6  |\n                        // +-----+-----+-----+      +-----+-----+-----+\n                        //                               ^\n                        //                     (old divider D1 added to right)\n                        // Legend:\n                        // - C# = Cell (table leaf)\n                        // - K# = Key cell (index/internal node)\n                        // - D# = Divider cell\n                        let mut cell_right = if is_table_leaf {\n                            cell_left\n                        } else {\n                            cell_left + 1\n                        };\n                        loop {\n                            let cell_left_size =\n                                cell_array.cell_size_bytes(cell_left as usize) as i64;\n                            let cell_right_size =\n                                cell_array.cell_size_bytes(cell_right as usize) as i64;\n                            // TODO: add assert nMaxCells\n\n                            let is_last_sibling = i == sibling_count_new - 1;\n                            let pointer_size = if is_last_sibling {\n                                0\n                            } else {\n                                CELL_PTR_SIZE_BYTES as i64\n                            };\n                            // As mentioned, this step rebalances the siblings so that cells are moved from left to right, since the previous step just\n                            // packed as much as possible to the left. However, if the right-hand-side page would become larger than the left-hand-side page,\n                            // we stop.\n                            let would_not_improve_balance =\n                                size_right_page + cell_right_size + (CELL_PTR_SIZE_BYTES as i64)\n                                    > size_left_page - (cell_left_size + pointer_size);\n                            if size_right_page != 0 && would_not_improve_balance {\n                                break;\n                            }\n\n                            size_left_page -= cell_left_size + (CELL_PTR_SIZE_BYTES as i64);\n                            size_right_page += cell_right_size + (CELL_PTR_SIZE_BYTES as i64);\n                            cell_array.cell_count_per_page_cumulative[i - 1] = cell_left;\n\n                            if cell_left == 0 {\n                                break;\n                            }\n                            cell_left -= 1;\n                            cell_right -= 1;\n                        }\n\n                        new_page_sizes[i] = size_right_page;\n                        new_page_sizes[i - 1] = size_left_page;\n                        turso_assert_greater_than!(\n                            cell_array.cell_count_per_page_cumulative[i - 1],\n                            if i > 1 {\n                                cell_array.cell_count_per_page_cumulative[i - 2]\n                            } else {\n                                0\n                            }\n                        );\n                    }\n\n                    *sub_state = BalanceSubState::NonRootDoBalancingAllocate {\n                        i: 0,\n                        context: Some(BalanceContext {\n                            pages_to_balance_new,\n                            sibling_count_new,\n                            cell_array,\n                            old_cell_count_per_page_cumulative,\n                            #[cfg(debug_assertions)]\n                            cells_debug,\n                        }),\n                    };\n                }\n                BalanceSubState::NonRootDoBalancingAllocate { i, context } => {\n                    let BalanceContext {\n                        pages_to_balance_new,\n                        old_cell_count_per_page_cumulative,\n                        cell_array,\n                        sibling_count_new,\n                        ..\n                    } = context.as_mut().unwrap();\n                    let pager = self.pager.clone();\n                    let balance_info = balance_info.as_mut().unwrap();\n                    let page_type = balance_info.pages_to_balance[0]\n                        .as_ref()\n                        .unwrap()\n                        .get_contents()\n                        .page_type()?;\n                    // Allocate pages or set dirty if not needed\n                    if *i < balance_info.sibling_count {\n                        let page = balance_info.pages_to_balance[*i].as_ref().unwrap();\n                        turso_assert!(page.is_dirty(), \"sibling page must be already marked dirty\");\n                        pages_to_balance_new[*i].replace(page.clone());\n                    } else {\n                        let page = return_if_io!(pager.do_allocate_page(\n                            page_type,\n                            0,\n                            BtreePageAllocMode::Any\n                        ));\n                        pages_to_balance_new[*i].replace(PinGuard::new(page));\n                        // Since this page didn't exist before, we can set it to cells length as it\n                        // marks them as empty since it is a prefix sum of cells.\n                        old_cell_count_per_page_cumulative[*i] =\n                            cell_array.cell_payloads.len() as u16;\n                    }\n                    if *i + 1 < *sibling_count_new {\n                        *i += 1;\n                        continue;\n                    } else {\n                        *sub_state = BalanceSubState::NonRootDoBalancingFinish {\n                            context: context.take().unwrap(),\n                        };\n                    }\n                }\n                BalanceSubState::NonRootDoBalancingFinish {\n                    context:\n                        BalanceContext {\n                            pages_to_balance_new,\n                            sibling_count_new,\n                            cell_array,\n                            old_cell_count_per_page_cumulative,\n                            #[cfg(debug_assertions)]\n                            cells_debug,\n                        },\n                } => {\n                    let balance_info = balance_info.as_mut().unwrap();\n                    let page_type = balance_info.pages_to_balance[0]\n                        .as_ref()\n                        .unwrap()\n                        .get_contents()\n                        .page_type()?;\n                    let parent_is_root = !self.stack.has_parent();\n                    let parent_page = PinGuard::new(self.stack.top_ref().clone());\n                    let parent_contents = parent_page.get_contents();\n                    let mut sibling_count_new = *sibling_count_new;\n                    let is_table_leaf = matches!(page_type, PageType::TableLeaf);\n                    // Reassign page numbers in increasing order\n                    {\n                        let mut page_numbers: [usize; MAX_NEW_SIBLING_PAGES_AFTER_BALANCE] =\n                            [0; MAX_NEW_SIBLING_PAGES_AFTER_BALANCE];\n                        for (i, page) in pages_to_balance_new\n                            .iter()\n                            .take(sibling_count_new)\n                            .enumerate()\n                        {\n                            page_numbers[i] = page.as_ref().unwrap().get().id;\n                        }\n                        page_numbers.sort_unstable();\n                        for (page, new_id) in pages_to_balance_new\n                            .iter()\n                            .take(sibling_count_new)\n                            .rev()\n                            .zip(page_numbers.iter().rev().take(sibling_count_new))\n                        {\n                            let page = page.as_ref().unwrap();\n                            if *new_id != page.get().id {\n                                page.get().id = *new_id;\n                                self.pager\n                                    .upsert_page_in_cache(*new_id, page.0.clone(), true)?;\n                            }\n                        }\n\n                        #[cfg(debug_assertions)]\n                        {\n                            tracing::debug!(\n                                \"balance_non_root(parent page_id={})\",\n                                parent_page.get().id\n                            );\n                            for page in pages_to_balance_new.iter().take(sibling_count_new) {\n                                tracing::debug!(\n                                    \"balance_non_root(new_sibling page_id={})\",\n                                    page.as_ref().unwrap().get().id\n                                );\n                            }\n                        }\n                    }\n\n                    // pages_pointed_to helps us debug we did in fact create divider cells to all the new pages and the rightmost pointer,\n                    // also points to the last page.\n                    #[cfg(debug_assertions)]\n                    let mut pages_pointed_to = HashSet::default();\n\n                    // Write right pointer in parent page to point to new rightmost page. keep in mind\n                    // we update rightmost pointer first because inserting cells could defragment parent page,\n                    // therfore invalidating the pointer.\n                    let right_page_id = pages_to_balance_new[sibling_count_new - 1]\n                        .as_ref()\n                        .unwrap()\n                        .get()\n                        .id as u32;\n                    let rightmost_pointer = balance_info.rightmost_pointer;\n                    let rightmost_pointer =\n                        unsafe { std::slice::from_raw_parts_mut(rightmost_pointer, 4) };\n                    rightmost_pointer[0..4].copy_from_slice(&right_page_id.to_be_bytes());\n\n                    #[cfg(debug_assertions)]\n                    pages_pointed_to.insert(right_page_id);\n                    tracing::debug!(\n                        \"balance_non_root(rightmost_pointer_update, rightmost_pointer={})\",\n                        right_page_id\n                    );\n\n                    /* 6. Update parent pointers. Update right pointer and insert divider cells with newly created distribution of cells */\n                    // Ensure right-child pointer of the right-most new sibling pge points to the page\n                    // that was originally on that place.\n                    let is_leaf_page =\n                        matches!(page_type, PageType::TableLeaf | PageType::IndexLeaf);\n                    if !is_leaf_page {\n                        let last_sibling_idx = balance_info.sibling_count - 1;\n                        let last_page = balance_info.pages_to_balance[last_sibling_idx]\n                            .as_ref()\n                            .unwrap();\n                        let right_pointer = last_page.get_contents().rightmost_pointer()?.unwrap();\n                        let new_last_page = pages_to_balance_new[sibling_count_new - 1]\n                            .as_ref()\n                            .unwrap();\n                        new_last_page\n                            .get_contents()\n                            .write_rightmost_ptr(right_pointer);\n                    }\n                    turso_assert!(\n                        parent_contents.overflow_cells.is_empty(),\n                        \"parent page overflow cells should be empty before divider cell reinsertion\"\n                    );\n                    // TODO: pointer map update (vacuum support)\n                    // Update divider cells in parent\n                    // Cache first_divider_cell to allow mutable access to reusable_divider_cell\n                    let first_divider_cell_cached = balance_info.first_divider_cell;\n                    for (sibling_page_idx, page) in pages_to_balance_new\n                        .iter()\n                        .enumerate()\n                        .take(sibling_count_new - 1)\n                    /* do not take last page */\n                    {\n                        let page = page.as_ref().unwrap();\n                        // e.g. if we have 3 pages and the leftmost child page has 3 cells,\n                        // then the divider cell idx is 3 in the flat cell array.\n                        let divider_cell_idx = cell_array.cell_count_up_to_page(sibling_page_idx);\n                        let mut divider_cell = &mut cell_array.cell_payloads[divider_cell_idx];\n                        // Reuse the buffer for constructing new divider cell to avoid allocation per iteration\n                        balance_info.reusable_divider_cell.clear();\n                        if !is_leaf_page {\n                            // Interior\n                            // Make this page's rightmost pointer point to pointer of divider cell before modification\n                            let previous_pointer_divider = read_u32(divider_cell, 0);\n                            page.get_contents()\n                                .write_rightmost_ptr(previous_pointer_divider);\n                            // divider cell now points to this page\n                            balance_info\n                                .reusable_divider_cell\n                                .extend_from_slice(&(page.get().id as u32).to_be_bytes());\n                            // now copy the rest of the divider cell:\n                            // Table Interior page:\n                            //   * varint rowid\n                            // Index Interior page:\n                            //   * varint payload size\n                            //   * payload\n                            //   * first overflow page (u32 optional)\n                            balance_info\n                                .reusable_divider_cell\n                                .extend_from_slice(&divider_cell[4..]);\n                        } else if is_table_leaf {\n                            // For table leaves, divider_cell_idx effectively points to the last cell of the old left page.\n                            // The new divider cell's rowid becomes the second-to-last cell's rowid.\n                            // i.e. in the diagram above, the new divider cell's rowid becomes the rowid of C3.\n                            // FIXME: not needed conversion\n                            // FIXME: need to update cell size in order to free correctly?\n                            // insert into cell with correct range should be enough\n                            divider_cell = &mut cell_array.cell_payloads[divider_cell_idx - 1];\n                            let (_, n_bytes_payload) = read_varint(divider_cell)?;\n                            let (rowid, _) = read_varint(&divider_cell[n_bytes_payload..])?;\n                            balance_info\n                                .reusable_divider_cell\n                                .extend_from_slice(&(page.get().id as u32).to_be_bytes());\n                            write_varint_to_vec(rowid, &mut balance_info.reusable_divider_cell);\n                        } else {\n                            // Leaf index\n                            balance_info\n                                .reusable_divider_cell\n                                .extend_from_slice(&(page.get().id as u32).to_be_bytes());\n                            balance_info\n                                .reusable_divider_cell\n                                .extend_from_slice(divider_cell);\n                        }\n\n                        let left_pointer = read_u32(\n                            &balance_info.reusable_divider_cell[..LEFT_CHILD_PTR_SIZE_BYTES],\n                            0,\n                        );\n                        turso_assert!(\n                            left_pointer != parent_page.get().id as u32,\n                            \"left pointer is the same as parent page id\"\n                        );\n                        #[cfg(debug_assertions)]\n                        {\n                            pages_pointed_to.insert(left_pointer);\n                            tracing::debug!(\n                                \"balance_non_root(insert_divider_cell, first_divider_cell={}, divider_cell={}, left_pointer={})\",\n                                first_divider_cell_cached,\n                                sibling_page_idx,\n                                left_pointer\n                            );\n                        }\n                        turso_assert!(\n                            left_pointer == page.get().id as u32,\n                            \"left pointer is not the same as page id\"\n                        );\n                        // FIXME: remove this lock\n                        let database_size = self\n                            .pager\n                            .io\n                            .block(|| self.pager.with_header(|header| header.database_size))?\n                            .get();\n                        turso_assert!(\n                            left_pointer <= database_size,\n                            \"invalid page number divider left pointer exceeds database number of pages\",\n                            { \"left_pointer\": left_pointer, \"database_size\": database_size }\n                        );\n                        let divider_cell_insert_idx_in_parent =\n                            first_divider_cell_cached + sibling_page_idx;\n                        #[cfg(debug_assertions)]\n                        let overflow_cell_count_before = parent_contents.overflow_cells.len();\n                        insert_into_cell(\n                            parent_contents,\n                            &balance_info.reusable_divider_cell,\n                            divider_cell_insert_idx_in_parent,\n                            usable_space,\n                        )?;\n                        #[cfg(debug_assertions)]\n                        {\n                            let overflow_cell_count_after = parent_contents.overflow_cells.len();\n                            let divider_cell_is_overflow_cell =\n                                overflow_cell_count_after > overflow_cell_count_before;\n\n                            BTreeCursor::validate_balance_non_root_divider_cell_insertion(\n                                balance_info,\n                                parent_contents,\n                                divider_cell_insert_idx_in_parent,\n                                divider_cell_is_overflow_cell,\n                                page,\n                                usable_space,\n                            );\n                        }\n                    }\n                    tracing::debug!(\n                        \"balance_non_root(parent_overflow={})\",\n                        parent_contents.overflow_cells.len()\n                    );\n\n                    #[cfg(debug_assertions)]\n                    {\n                        // Let's ensure every page is pointed to by the divider cell or the rightmost pointer.\n                        for page in pages_to_balance_new.iter().take(sibling_count_new) {\n                            let page = page.as_ref().unwrap();\n                            turso_assert!(\n                                pages_pointed_to.contains(&(page.get().id as u32)),\n                                \"page not pointed to by divider cell or rightmost pointer\",\n                                { \"page_id\": page.get().id }\n                            );\n                        }\n                    }\n                    /* 7. Start real movement of cells. Next comment is borrowed from SQLite: */\n                    /* Now update the actual sibling pages. The order in which they are updated\n                     ** is important, as this code needs to avoid disrupting any page from which\n                     ** cells may still to be read. In practice, this means:\n                     **\n                     **  (1) If cells are moving left (from apNew[iPg] to apNew[iPg-1])\n                     **      then it is not safe to update page apNew[iPg] until after\n                     **      the left-hand sibling apNew[iPg-1] has been updated.\n                     **\n                     **  (2) If cells are moving right (from apNew[iPg] to apNew[iPg+1])\n                     **      then it is not safe to update page apNew[iPg] until after\n                     **      the right-hand sibling apNew[iPg+1] has been updated.\n                     **\n                     ** If neither of the above apply, the page is safe to update.\n                     **\n                     ** The iPg value in the following loop starts at nNew-1 goes down\n                     ** to 0, then back up to nNew-1 again, thus making two passes over\n                     ** the pages.  On the initial downward pass, only condition (1) above\n                     ** needs to be tested because (2) will always be true from the previous\n                     ** step.  On the upward pass, both conditions are always true, so the\n                     ** upwards pass simply processes pages that were missed on the downward\n                     ** pass.\n                     */\n                    let mut done = [false; MAX_NEW_SIBLING_PAGES_AFTER_BALANCE];\n                    let rightmost_page_negative_idx = 1 - sibling_count_new as i64;\n                    let rightmost_page_positive_idx = sibling_count_new as i64 - 1;\n                    for i in rightmost_page_negative_idx..=rightmost_page_positive_idx {\n                        // As mentioned above, we do two passes over the pages:\n                        // 1. Downward pass: Process pages in decreasing order\n                        // 2. Upward pass: Process pages in increasing order\n                        // Hence if we have 3 siblings:\n                        // the order of 'i' will be: -2, -1, 0, 1, 2.\n                        // and the page processing order is: 2, 1, 0, 1, 2.\n                        let page_idx = i.unsigned_abs() as usize;\n                        if done[page_idx] {\n                            continue;\n                        }\n                        // As outlined above, this condition ensures we process pages in the correct order to avoid disrupting cells that still need to be read.\n                        // 1. i >= 0 handles the upward pass where we process any pages not processed in the downward pass.\n                        //    - condition (1) is not violated: if cells are moving right-to-left, righthand sibling has not been updated yet.\n                        //    - condition (2) is not violated: if cells are moving left-to-right, righthand sibling has already been updated in the downward pass.\n                        // 2. The second condition checks if it's safe to process a page during the downward pass.\n                        //    - condition (1) is not violated: if cells are moving right-to-left, we do nothing.\n                        //    - condition (2) is not violated: if cells are moving left-to-right, we are allowed to update.\n                        if i >= 0\n                            || old_cell_count_per_page_cumulative[page_idx - 1]\n                                >= cell_array.cell_count_per_page_cumulative[page_idx - 1]\n                        {\n                            let (start_old_cells, start_new_cells, number_new_cells) = if page_idx\n                                == 0\n                            {\n                                (0, 0, cell_array.cell_count_up_to_page(0))\n                            } else {\n                                let this_was_old_page = page_idx < balance_info.sibling_count;\n                                // We add !is_table_leaf because we want to skip 1 in case of divider cell which is encountared between pages assigned\n                                let start_old_cells = if this_was_old_page {\n                                    old_cell_count_per_page_cumulative[page_idx - 1] as usize\n                                        + (!is_table_leaf) as usize\n                                } else {\n                                    cell_array.cell_payloads.len()\n                                };\n                                let start_new_cells = cell_array\n                                    .cell_count_up_to_page(page_idx - 1)\n                                    + (!is_table_leaf) as usize;\n                                (\n                                    start_old_cells,\n                                    start_new_cells,\n                                    cell_array.cell_count_up_to_page(page_idx) - start_new_cells,\n                                )\n                            };\n                            let page = pages_to_balance_new[page_idx].as_ref().unwrap();\n                            tracing::debug!(\"pre_edit_page(page={})\", page.get().id);\n                            let page_contents = page.get_contents();\n                            edit_page(\n                                page_contents,\n                                start_old_cells,\n                                start_new_cells,\n                                number_new_cells,\n                                cell_array,\n                                usable_space,\n                            )?;\n                            debug_validate_cells!(page_contents, usable_space);\n                            tracing::trace!(\n                                \"edit_page page={} cells={}\",\n                                page.get().id,\n                                page_contents.cell_count()\n                            );\n                            page_contents.overflow_cells.clear();\n\n                            done[page_idx] = true;\n                        }\n                    }\n\n                    // TODO: vacuum support\n                    let first_child_page = pages_to_balance_new[0].as_ref().unwrap();\n                    let first_child_contents = first_child_page.get_contents();\n                    if parent_is_root\n                        && parent_contents.cell_count() == 0\n                        // this check to make sure we are not having negative free space\n                        && parent_contents.offset()\n                            <= compute_free_space(first_child_contents, usable_space)?\n                    {\n                        // From SQLite:\n                        // The root page of the b-tree now contains no cells. The only sibling\n                        // page is the right-child of the parent. Copy the contents of the\n                        // child page into the parent, decreasing the overall height of the\n                        // b-tree structure by one. This is described as the \"balance-shallower\"\n                        // sub-algorithm in some documentation.\n                        turso_assert_eq!(sibling_count_new, 1);\n                        let parent_offset = if parent_page.get().id == 1 {\n                            DatabaseHeader::SIZE\n                        } else {\n                            0\n                        };\n                        #[cfg(debug_assertions)]\n                        turso_assert_eq!(parent_offset, parent_contents.offset());\n\n                        // From SQLite:\n                        // It is critical that the child page be defragmented before being\n                        // copied into the parent, because if the parent is page 1 then it will\n                        // by smaller than the child due to the database header, and so\n                        // all the free space needs to be up front.\n                        defragment_page_full(first_child_contents, usable_space)?;\n\n                        let child_top = first_child_contents.cell_content_area() as usize;\n                        let parent_buf = parent_contents.as_ptr();\n                        let child_buf = first_child_contents.as_ptr();\n                        let content_size = usable_space - child_top;\n\n                        // Copy cell contents\n                        parent_buf[child_top..child_top + content_size]\n                            .copy_from_slice(&child_buf[child_top..child_top + content_size]);\n\n                        // Copy header and pointer\n                        // NOTE: don't use .cell_pointer_array_offset_and_size() because of different\n                        // header size\n                        let header_and_pointer_size = first_child_contents.header_size()\n                            + first_child_contents.cell_pointer_array_size();\n                        let first_child_offset = first_child_contents.offset();\n                        parent_buf[parent_offset..parent_offset + header_and_pointer_size]\n                            .copy_from_slice(\n                                &child_buf[first_child_offset\n                                    ..first_child_offset + header_and_pointer_size],\n                            );\n\n                        sibling_count_new -= 1; // decrease sibling count for debugging and free at the end\n                        turso_assert_less_than!(sibling_count_new, balance_info.sibling_count);\n                    }\n\n                    #[cfg(debug_assertions)]\n                    BTreeCursor::post_balance_non_root_validation(\n                        &parent_page,\n                        balance_info,\n                        parent_contents,\n                        pages_to_balance_new,\n                        page_type,\n                        is_table_leaf,\n                        cells_debug,\n                        sibling_count_new,\n                        right_page_id,\n                        usable_space,\n                    );\n\n                    // Balance-shallower case\n                    if sibling_count_new == 0 {\n                        self.stack.set_cell_index(0); // reset cell index, top is already parent\n                    }\n\n                    // Restore the cell_payloads Vec to BalanceState for reuse in future operations.\n                    // This avoids allocation on subsequent balance operations.\n                    let mut recovered_vec = std::mem::take(&mut cell_array.cell_payloads);\n                    recovered_vec.clear();\n                    *reusable_cell_payloads = recovered_vec;\n\n                    *sub_state = BalanceSubState::FreePages {\n                        curr_page: sibling_count_new,\n                        sibling_count_new,\n                    };\n                }\n                BalanceSubState::FreePages {\n                    curr_page,\n                    sibling_count_new,\n                } => {\n                    let sibling_count = {\n                        balance_info\n                            .as_ref()\n                            .expect(\"must be balancing\")\n                            .sibling_count\n                    };\n                    // We have to free pages that are not used anymore\n                    if !((*sibling_count_new..sibling_count).contains(curr_page)) {\n                        *sub_state = BalanceSubState::Start;\n                        let _ = balance_info.take();\n                        return Ok(IOResult::Done(()));\n                    } else {\n                        let balance_info = balance_info.as_ref().expect(\"must be balancing\");\n                        let page = balance_info.pages_to_balance[*curr_page].as_ref().unwrap();\n                        return_if_io!(self.pager.free_page(Some(page.0.clone()), page.get().id));\n                        *sub_state = BalanceSubState::FreePages {\n                            curr_page: *curr_page + 1,\n                            sibling_count_new: *sibling_count_new,\n                        };\n                    }\n                }\n            }\n        }\n    }\n\n    /// Validates that a divider cell was correctly inserted into the parent page\n    /// during B-tree balancing and that it points to the correct child page.\n    #[cfg(debug_assertions)]\n    fn validate_balance_non_root_divider_cell_insertion(\n        balance_info: &BalanceInfo,\n        parent_contents: &mut PageContent,\n        divider_cell_insert_idx_in_parent: usize,\n        divider_cell_is_overflow_cell: bool,\n        child_page: &PageRef,\n        usable_space: usize,\n    ) {\n        let left_pointer = if divider_cell_is_overflow_cell {\n            parent_contents.overflow_cells\n                .iter()\n                .find(|cell| cell.index == divider_cell_insert_idx_in_parent)\n                .map(|cell| read_u32(&cell.payload, 0))\n                .unwrap_or_else(|| {\n                    panic!(\n                        \"overflow cell with divider cell was not found (divider_cell_idx={}, balance_info.first_divider_cell={}, overflow_cells.len={})\",\n                        divider_cell_insert_idx_in_parent,\n                        balance_info.first_divider_cell,\n                        parent_contents.overflow_cells.len(),\n                    )\n                })\n        } else if divider_cell_insert_idx_in_parent < parent_contents.cell_count() {\n            let (cell_start, cell_len) = parent_contents\n                .cell_get_raw_region(divider_cell_insert_idx_in_parent, usable_space)\n                .unwrap();\n            read_u32(\n                &parent_contents.as_ptr()[cell_start..cell_start + cell_len],\n                0,\n            )\n        } else {\n            panic!(\n                \"divider cell is not in the parent page (divider_cell_idx={}, balance_info.first_divider_cell={}, overflow_cells.len={})\",\n                divider_cell_insert_idx_in_parent,\n                balance_info.first_divider_cell,\n                parent_contents.overflow_cells.len(),\n            )\n        };\n\n        // Verify the left pointer points to the correct page\n        turso_assert_eq!(\n            left_pointer,\n            child_page.get().id as u32,\n            \"inserted cell doesn't point to correct page\",\n            { \"left_pointer\": left_pointer, \"child_page_id\": child_page.get().id }\n        );\n    }\n\n    #[cfg(debug_assertions)]\n    #[allow(clippy::too_many_arguments)]\n    fn post_balance_non_root_validation(\n        parent_page: &PageRef,\n        balance_info: &BalanceInfo,\n        parent_contents: &mut PageContent,\n        pages_to_balance_new: &[Option<PinGuard>; MAX_NEW_SIBLING_PAGES_AFTER_BALANCE],\n        page_type: PageType,\n        is_table_leaf: bool,\n        cells_debug: &mut [Vec<u8>],\n        sibling_count_new: usize,\n        right_page_id: u32,\n        usable_space: usize,\n    ) {\n        let mut valid = true;\n        let mut current_index_cell = 0;\n        for cell_idx in 0..parent_contents.cell_count() {\n            let cell = parent_contents.cell_get(cell_idx, usable_space).unwrap();\n            match cell {\n                BTreeCell::TableInteriorCell(table_interior_cell) => {\n                    let left_child_page = table_interior_cell.left_child_page;\n                    if left_child_page == parent_page.get().id as u32 {\n                        tracing::error!(\"balance_non_root(parent_divider_points_to_same_page, page_id={}, cell_left_child_page={})\",\n                                parent_page.get().id,\n                                left_child_page,\n                            );\n                        valid = false;\n                    }\n                }\n                BTreeCell::IndexInteriorCell(index_interior_cell) => {\n                    let left_child_page = index_interior_cell.left_child_page;\n                    if left_child_page == parent_page.get().id as u32 {\n                        tracing::error!(\"balance_non_root(parent_divider_points_to_same_page, page_id={}, cell_left_child_page={})\",\n                                parent_page.get().id,\n                                left_child_page,\n                            );\n                        valid = false;\n                    }\n                }\n                _ => {}\n            }\n        }\n        // Let's now make a in depth check that we in fact added all possible cells somewhere and they are not lost\n        for (page_idx, page) in pages_to_balance_new\n            .iter()\n            .take(sibling_count_new)\n            .enumerate()\n        {\n            let page = page.as_ref().unwrap();\n            let contents = page.get_contents();\n            debug_validate_cells!(contents, usable_space);\n            // Cells are distributed in order\n            for cell_idx in 0..contents.cell_count() {\n                let (cell_start, cell_len) = contents\n                    .cell_get_raw_region(cell_idx, usable_space)\n                    .unwrap();\n                let buf = contents.as_ptr();\n                let cell_buf = to_static_buf(&mut buf[cell_start..cell_start + cell_len]);\n                let cell_buf_in_array = &cells_debug[current_index_cell];\n                if cell_buf != cell_buf_in_array {\n                    tracing::error!(\"balance_non_root(cell_not_found_debug, page_id={}, cell_in_cell_array_idx={})\",\n                        page.get().id,\n                        current_index_cell,\n                    );\n                    valid = false;\n                }\n\n                let cell = crate::storage::sqlite3_ondisk::read_btree_cell(\n                    cell_buf,\n                    contents,\n                    0,\n                    usable_space,\n                )\n                .unwrap();\n                match &cell {\n                    BTreeCell::TableInteriorCell(table_interior_cell) => {\n                        let left_child_page = table_interior_cell.left_child_page;\n                        if left_child_page == page.get().id as u32 {\n                            tracing::error!(\"balance_non_root(child_page_points_same_page, page_id={}, cell_left_child_page={}, page_idx={})\",\n                                page.get().id,\n                                left_child_page,\n                                page_idx\n                            );\n                            valid = false;\n                        }\n                        if left_child_page == parent_page.get().id as u32 {\n                            tracing::error!(\"balance_non_root(child_page_points_parent_of_child, page_id={}, cell_left_child_page={}, page_idx={})\",\n                                page.get().id,\n                                left_child_page,\n                                page_idx\n                            );\n                            valid = false;\n                        }\n                    }\n                    BTreeCell::IndexInteriorCell(index_interior_cell) => {\n                        let left_child_page = index_interior_cell.left_child_page;\n                        if left_child_page == page.get().id as u32 {\n                            tracing::error!(\"balance_non_root(child_page_points_same_page, page_id={}, cell_left_child_page={}, page_idx={})\",\n                                page.get().id,\n                                left_child_page,\n                                page_idx\n                            );\n                            valid = false;\n                        }\n                        if left_child_page == parent_page.get().id as u32 {\n                            tracing::error!(\"balance_non_root(child_page_points_parent_of_child, page_id={}, cell_left_child_page={}, page_idx={})\",\n                                page.get().id,\n                                left_child_page,\n                                page_idx\n                            );\n                            valid = false;\n                        }\n                    }\n                    _ => {}\n                }\n                current_index_cell += 1;\n            }\n            // Now check divider cells and their pointers.\n            let parent_buf = parent_contents.as_ptr();\n            let cell_divider_idx = balance_info.first_divider_cell + page_idx;\n            if sibling_count_new == 0 {\n                // Balance-shallower case\n                // We need to check data in parent page\n                debug_validate_cells!(parent_contents, usable_space);\n\n                if pages_to_balance_new[0].is_none() {\n                    tracing::error!(\n                        \"balance_non_root(balance_shallower_incorrect_page, page_idx={})\",\n                        0\n                    );\n                    valid = false;\n                }\n\n                for (i, value) in pages_to_balance_new\n                    .iter()\n                    .enumerate()\n                    .take(sibling_count_new)\n                    .skip(1)\n                {\n                    if value.is_some() {\n                        tracing::error!(\n                            \"balance_non_root(balance_shallower_incorrect_page, page_idx={})\",\n                            i\n                        );\n                        valid = false;\n                    }\n                }\n\n                if current_index_cell != cells_debug.len()\n                    || cells_debug.len() != contents.cell_count()\n                    || contents.cell_count() != parent_contents.cell_count()\n                {\n                    tracing::error!(\"balance_non_root(balance_shallower_incorrect_cell_count, current_index_cell={}, cells_debug={}, cell_count={}, parent_cell_count={})\",\n                        current_index_cell,\n                        cells_debug.len(),\n                        contents.cell_count(),\n                        parent_contents.cell_count()\n                    );\n                    valid = false;\n                }\n\n                if right_page_id == page.get().id as u32\n                    || right_page_id == parent_page.get().id as u32\n                {\n                    tracing::error!(\"balance_non_root(balance_shallower_rightmost_pointer, page_id={}, parent_page_id={}, rightmost={})\",\n                        page.get().id,\n                        parent_page.get().id,\n                        right_page_id,\n                    );\n                    valid = false;\n                }\n\n                if let Some(rm) = contents.rightmost_pointer().ok().flatten() {\n                    if rm != right_page_id {\n                        tracing::error!(\"balance_non_root(balance_shallower_rightmost_pointer, page_rightmost={}, rightmost={})\",\n                            rm,\n                            right_page_id,\n                        );\n                        valid = false;\n                    }\n                }\n\n                if let Some(rm) = parent_contents.rightmost_pointer().ok().flatten() {\n                    if rm != right_page_id {\n                        tracing::error!(\"balance_non_root(balance_shallower_rightmost_pointer, parent_rightmost={}, rightmost={})\",\n                            rm,\n                            right_page_id,\n                        );\n                        valid = false;\n                    }\n                }\n\n                if parent_contents.page_type().ok() != Some(page_type) {\n                    tracing::error!(\"balance_non_root(balance_shallower_parent_page_type, page_type={:?}, parent_page_type={:?})\",\n                        page_type,\n                        parent_contents.page_type().ok()\n                    );\n                    valid = false\n                }\n\n                for (parent_cell_idx, cell_buf_in_array) in\n                    cells_debug.iter().enumerate().take(contents.cell_count())\n                {\n                    let (parent_cell_start, parent_cell_len) = parent_contents\n                        .cell_get_raw_region(parent_cell_idx, usable_space)\n                        .unwrap();\n\n                    let (cell_start, cell_len) = contents\n                        .cell_get_raw_region(parent_cell_idx, usable_space)\n                        .unwrap();\n\n                    let buf = contents.as_ptr();\n                    let cell_buf = to_static_buf(&mut buf[cell_start..cell_start + cell_len]);\n                    let parent_cell_buf = to_static_buf(\n                        &mut parent_buf[parent_cell_start..parent_cell_start + parent_cell_len],\n                    );\n\n                    if cell_buf != cell_buf_in_array || cell_buf != parent_cell_buf {\n                        tracing::error!(\"balance_non_root(balance_shallower_cell_not_found_debug, page_id={}, cell_in_cell_array_idx={})\",\n                            page.get().id,\n                            parent_cell_idx,\n                        );\n                        valid = false;\n                    }\n                }\n            } else if page_idx == sibling_count_new - 1 {\n                // We will only validate rightmost pointer of parent page, we will not validate rightmost if it's a cell and not the last pointer because,\n                // insert cell could've defragmented the page and invalidated the pointer.\n                // right pointer, we just check right pointer points to this page.\n                if cell_divider_idx == parent_contents.cell_count()\n                    && right_page_id != page.get().id as u32\n                {\n                    tracing::error!(\"balance_non_root(cell_divider_right_pointer, should point to {}, but points to {})\",\n                        page.get().id,\n                        right_page_id\n                    );\n                    valid = false;\n                }\n            } else {\n                // divider cell might be an overflow cell\n                let mut was_overflow = false;\n                for overflow_cell in &parent_contents.overflow_cells {\n                    if overflow_cell.index == cell_divider_idx {\n                        let left_pointer = read_u32(&overflow_cell.payload, 0);\n                        if left_pointer != page.get().id as u32 {\n                            tracing::error!(\"balance_non_root(cell_divider_left_pointer_overflow, should point to page_id={}, but points to {}, divider_cell={}, overflow_cells_parent={})\",\n                        page.get().id,\n                        left_pointer,\n                        page_idx,\n                        parent_contents.overflow_cells.len()\n                    );\n                            valid = false;\n                        }\n                        was_overflow = true;\n                        break;\n                    }\n                }\n                if was_overflow {\n                    if !is_table_leaf {\n                        // remember to increase cell if this cell was moved to parent\n                        current_index_cell += 1;\n                    }\n                    continue;\n                }\n                // check if overflow\n                // check if right pointer, this is the last page. Do we update rightmost pointer and defragment moves it?\n                let (cell_start, cell_len) = parent_contents\n                    .cell_get_raw_region(cell_divider_idx, usable_space)\n                    .unwrap();\n                let cell_left_pointer = read_u32(&parent_buf[cell_start..cell_start + cell_len], 0);\n                if cell_left_pointer != page.get().id as u32 {\n                    tracing::error!(\"balance_non_root(cell_divider_left_pointer, should point to page_id={}, but points to {}, divider_cell={}, overflow_cells_parent={})\",\n                        page.get().id,\n                        cell_left_pointer,\n                        page_idx,\n                        parent_contents.overflow_cells.len()\n                    );\n                    valid = false;\n                }\n                if is_table_leaf {\n                    // If we are in a table leaf page, we just need to check that this cell that should be a divider cell is in the parent\n                    // This means we already check cell in leaf pages but not on parent so we don't advance current_index_cell\n                    let last_sibling_idx = balance_info.sibling_count - 1;\n                    if page_idx >= last_sibling_idx {\n                        // This means we are in the last page and we don't need to check anything\n                        continue;\n                    }\n                    let cell_buf: &'static mut [u8] =\n                        to_static_buf(&mut cells_debug[current_index_cell - 1]);\n                    let cell = crate::storage::sqlite3_ondisk::read_btree_cell(\n                        cell_buf,\n                        contents,\n                        0,\n                        usable_space,\n                    )\n                    .unwrap();\n                    let parent_cell = parent_contents\n                        .cell_get(cell_divider_idx, usable_space)\n                        .unwrap();\n                    let rowid = match cell {\n                        BTreeCell::TableLeafCell(table_leaf_cell) => table_leaf_cell.rowid,\n                        _ => unreachable!(),\n                    };\n                    let rowid_parent = match parent_cell {\n                        BTreeCell::TableInteriorCell(table_interior_cell) => {\n                            table_interior_cell.rowid\n                        }\n                        _ => unreachable!(),\n                    };\n                    if rowid_parent != rowid {\n                        tracing::error!(\"balance_non_root(cell_divider_rowid, page_id={}, cell_divider_idx={}, rowid_parent={}, rowid={})\",\n                            page.get().id,\n                            cell_divider_idx,\n                            rowid_parent,\n                            rowid\n                        );\n                        valid = false;\n                    }\n                } else {\n                    // In any other case, we need to check that this cell was moved to parent as divider cell\n                    let mut was_overflow = false;\n                    for overflow_cell in &parent_contents.overflow_cells {\n                        if overflow_cell.index == cell_divider_idx {\n                            let left_pointer = read_u32(&overflow_cell.payload, 0);\n                            if left_pointer != page.get().id as u32 {\n                                tracing::error!(\"balance_non_root(cell_divider_divider_cell_overflow should point to page_id={}, but points to {}, divider_cell={}, overflow_cells_parent={})\",\n                                    page.get().id,\n                                    left_pointer,\n                                    page_idx,\n                                    parent_contents.overflow_cells.len()\n                                );\n                                valid = false;\n                            }\n                            was_overflow = true;\n                            break;\n                        }\n                    }\n                    if was_overflow {\n                        if !is_table_leaf {\n                            // remember to increase cell if this cell was moved to parent\n                            current_index_cell += 1;\n                        }\n                        continue;\n                    }\n                    let (parent_cell_start, parent_cell_len) = parent_contents\n                        .cell_get_raw_region(cell_divider_idx, usable_space)\n                        .unwrap();\n                    let cell_buf_in_array = &cells_debug[current_index_cell];\n                    let left_pointer = read_u32(\n                        &parent_buf[parent_cell_start..parent_cell_start + parent_cell_len],\n                        0,\n                    );\n                    if left_pointer != page.get().id as u32 {\n                        tracing::error!(\"balance_non_root(divider_cell_left_pointer_interior should point to page_id={}, but points to {}, divider_cell={}, overflow_cells_parent={})\",\n                                    page.get().id,\n                                    left_pointer,\n                                    page_idx,\n                                    parent_contents.overflow_cells.len()\n                                );\n                        valid = false;\n                    }\n                    match page_type {\n                        PageType::TableInterior | PageType::IndexInterior => {\n                            let parent_cell_buf =\n                                &parent_buf[parent_cell_start..parent_cell_start + parent_cell_len];\n                            if parent_cell_buf[4..] != cell_buf_in_array[4..] {\n                                tracing::error!(\"balance_non_root(cell_divider_cell, page_id={}, cell_divider_idx={})\",\n                                    page.get().id,\n                                    cell_divider_idx,\n                                );\n                                valid = false;\n                            }\n                        }\n                        PageType::IndexLeaf => {\n                            let parent_cell_buf =\n                                &parent_buf[parent_cell_start..parent_cell_start + parent_cell_len];\n                            if parent_cell_buf[4..] != cell_buf_in_array[..] {\n                                tracing::error!(\"balance_non_root(cell_divider_cell_index_leaf, page_id={}, cell_divider_idx={})\",\n                                    page.get().id,\n                                    cell_divider_idx,\n                                );\n                                valid = false;\n                            }\n                        }\n                        _ => {\n                            unreachable!()\n                        }\n                    }\n                    current_index_cell += 1;\n                }\n            }\n        }\n\n        // Verify all cells were accounted for (non-shallower case)\n        if sibling_count_new > 0 && current_index_cell != cells_debug.len() {\n            tracing::error!(\n                \"balance_non_root(cell_count_mismatch, current_index_cell={}, cells_debug_len={}, sibling_count_new={})\",\n                current_index_cell,\n                cells_debug.len(),\n                sibling_count_new\n            );\n            valid = false;\n        }\n\n        turso_assert!(\n            valid,\n            \"corrupted database, cells were not balanced properly\"\n        );\n    }\n\n    /// Balance the root page.\n    /// This is done when the root page overflows, and we need to create a new root page.\n    /// See e.g. https://en.wikipedia.org/wiki/B-tree\n    fn balance_root(&mut self) -> Result<IOResult<()>> {\n        /* todo: balance deeper, create child and copy contents of root there. Then split root */\n        /* if we are in root page then we just need to create a new root and push key there */\n\n        // Since we are going to change the btree structure, let's forget our cached knowledge of the rightmost page.\n        let _ = self.move_to_right_state.1.take();\n\n        let root = self.stack.top();\n        let root_contents = root.get_contents();\n        let child = return_if_io!(self.pager.do_allocate_page(\n            root_contents.page_type()?,\n            0,\n            BtreePageAllocMode::Any\n        ));\n\n        let is_page_1 = root.get().id == 1;\n        let offset = if is_page_1 { DatabaseHeader::SIZE } else { 0 };\n        #[cfg(debug_assertions)]\n        turso_assert_eq!(offset, root_contents.offset());\n\n        tracing::debug!(\n            \"balance_root(root={}, rightmost={}, page_type={:?})\",\n            root.get().id,\n            child.get().id,\n            root_contents.page_type().ok()\n        );\n\n        turso_assert!(root.is_dirty(), \"root must be marked dirty\");\n        turso_assert!(\n            child.is_dirty(),\n            \"child must be marked dirty as freshly allocated page\"\n        );\n\n        let root_buf = root_contents.as_ptr();\n        let child_contents = child.get_contents();\n        let child_buf = child_contents.as_ptr();\n        let (root_pointer_start, root_pointer_len) =\n            root_contents.cell_pointer_array_offset_and_size();\n        let (child_pointer_start, _) = child.get_contents().cell_pointer_array_offset_and_size();\n\n        let top = root_contents.cell_content_area() as usize;\n\n        // 1. Modify child\n        // Copy pointers\n        child_buf[child_pointer_start..child_pointer_start + root_pointer_len]\n            .copy_from_slice(&root_buf[root_pointer_start..root_pointer_start + root_pointer_len]);\n        // Copy cell contents\n        child_buf[top..].copy_from_slice(&root_buf[top..]);\n        // Copy header\n        child_buf[0..root_contents.header_size()]\n            .copy_from_slice(&root_buf[offset..offset + root_contents.header_size()]);\n        // Copy overflow cells\n        std::mem::swap(\n            &mut child_contents.overflow_cells,\n            &mut root_contents.overflow_cells,\n        );\n        root_contents.overflow_cells.clear();\n\n        // 2. Modify root\n        let new_root_page_type = match root_contents.page_type()? {\n            PageType::IndexLeaf => PageType::IndexInterior,\n            PageType::TableLeaf => PageType::TableInterior,\n            other => other,\n        } as u8;\n        // set new page type\n        root_contents.write_page_type(new_root_page_type);\n        root_contents.write_rightmost_ptr(child.get().id as u32);\n        root_contents.write_cell_content_area(self.usable_space());\n        root_contents.write_cell_count(0);\n        root_contents.write_first_freeblock(0);\n\n        root_contents.write_fragmented_bytes_count(0);\n        root_contents.overflow_cells.clear();\n        self.root_page = root.get().id as i64;\n        self.stack.clear();\n        self.stack.push(root);\n        self.stack.set_cell_index(0); // leave parent pointing at the rightmost pointer (in this case 0, as there are no cells), since we will be balancing the rightmost child page.\n        self.stack.push(child);\n        Ok(IOResult::Done(()))\n    }\n\n    #[inline(always)]\n    /// Returns the usable space of the current page (which is computed as: page_size - reserved_bytes).\n    /// This is cached to avoid calling `pager.usable_space()` in a hot loop.\n    fn usable_space(&self) -> usize {\n        self.usable_space_cached\n    }\n\n    /// Clear the overflow pages linked to a specific page provided by the leaf cell\n    /// Uses a state machine to keep track of it's operations so that traversal can be\n    /// resumed from last point after IO interruption\n    #[cfg_attr(debug_assertions, instrument(skip_all, level = Level::DEBUG))]\n    fn clear_overflow_pages(&mut self, cell: &BTreeCell) -> Result<IOResult<()>> {\n        loop {\n            match self.overflow_state.clone() {\n                OverflowState::Start => {\n                    let first_overflow_page = match cell {\n                        BTreeCell::TableLeafCell(leaf_cell) => leaf_cell.first_overflow_page,\n                        BTreeCell::IndexLeafCell(leaf_cell) => leaf_cell.first_overflow_page,\n                        BTreeCell::IndexInteriorCell(interior_cell) => {\n                            interior_cell.first_overflow_page\n                        }\n                        BTreeCell::TableInteriorCell(_) => return Ok(IOResult::Done(())), // No overflow pages\n                    };\n\n                    if let Some(next_page) = first_overflow_page {\n                        if unlikely(\n                            next_page < 2\n                                || next_page\n                                    > self\n                                        .pager\n                                        .io\n                                        .block(|| {\n                                            self.pager.with_header(|header| header.database_size)\n                                        })?\n                                        .get(),\n                        ) {\n                            self.overflow_state = OverflowState::Start;\n                            return Err(LimboError::Corrupt(\"Invalid overflow page number\".into()));\n                        }\n                        let (page, c) = self.read_page(next_page as i64)?;\n                        self.overflow_state = OverflowState::ProcessPage { next_page: page };\n                        if let Some(c) = c {\n                            io_yield_one!(c);\n                        }\n                    } else {\n                        self.overflow_state = OverflowState::Done;\n                    }\n                }\n                OverflowState::ProcessPage { next_page: page } => {\n                    turso_assert!(page.is_loaded(), \"page should be loaded\");\n\n                    let contents = page.get_contents();\n                    let next = contents.read_u32_no_offset(0);\n                    let next_page_id = page.get().id;\n\n                    return_if_io!(self.pager.free_page(Some(page), next_page_id));\n\n                    if next != 0 {\n                        if unlikely(\n                            next < 2\n                                || next\n                                    > self\n                                        .pager\n                                        .io\n                                        .block(|| {\n                                            self.pager.with_header(|header| header.database_size)\n                                        })?\n                                        .get(),\n                        ) {\n                            self.overflow_state = OverflowState::Start;\n                            return Err(LimboError::Corrupt(\"Invalid overflow page number\".into()));\n                        }\n                        let (page, c) = self.read_page(next as i64)?;\n                        self.overflow_state = OverflowState::ProcessPage { next_page: page };\n                        if let Some(c) = c {\n                            io_yield_one!(c);\n                        }\n                    } else {\n                        self.overflow_state = OverflowState::Done;\n                    }\n                }\n                OverflowState::Done => {\n                    self.overflow_state = OverflowState::Start;\n                    return Ok(IOResult::Done(()));\n                }\n            };\n        }\n    }\n\n    /// Deletes all contents of the B-tree by freeing all its pages in an iterative depth-first order.\n    /// This ensures child pages are freed before their parents\n    /// Uses a state machine to keep track of the operation to ensure IO doesn't cause repeated traversals\n    ///\n    /// Depending on the caller, the root page may either be freed as well or left allocated but emptied.\n    ///\n    /// # Example\n    /// For a B-tree with this structure (where 4' is an overflow page):\n    /// ```text\n    ///            1 (root)\n    ///           /        \\\n    ///          2          3\n    ///        /   \\      /   \\\n    /// 4' <- 4     5    6     7\n    /// ```\n    ///\n    /// The destruction order would be: [4',4,5,2,6,7,3,1]\n    fn destroy_btree_contents(&mut self, keep_root: bool) -> Result<IOResult<Option<usize>>> {\n        if let CursorState::None = &self.state {\n            let c = self.move_to_root()?;\n            self.state = CursorState::Destroy(DestroyInfo {\n                state: DestroyState::Start,\n            });\n            if let Some(c) = c {\n                io_yield_one!(c);\n            }\n        }\n\n        loop {\n            let destroy_state = {\n                let destroy_info = self\n                    .state\n                    .destroy_info()\n                    .expect(\"unable to get a mut reference to destroy state in cursor\");\n                destroy_info.state.clone()\n            };\n\n            match destroy_state {\n                DestroyState::Start => {\n                    let destroy_info = self\n                        .state\n                        .mut_destroy_info()\n                        .expect(\"unable to get a mut reference to destroy state in cursor\");\n                    destroy_info.state = DestroyState::LoadPage;\n                }\n                DestroyState::LoadPage => {\n                    let _page = self.stack.top_ref();\n\n                    let destroy_info = self\n                        .state\n                        .mut_destroy_info()\n                        .expect(\"unable to get a mut reference to destroy state in cursor\");\n                    destroy_info.state = DestroyState::ProcessPage;\n                }\n                DestroyState::ProcessPage => {\n                    self.stack.advance();\n                    let page = self.stack.top_ref();\n                    let contents = page.get_contents();\n                    let cell_idx = self.stack.current_cell_index();\n\n                    //  If we've processed all cells in this page, figure out what to do with this page\n                    if cell_idx >= contents.cell_count() as i32 {\n                        match (contents.is_leaf(), cell_idx) {\n                            //  Leaf pages with all cells processed\n                            (true, n) if n >= contents.cell_count() as i32 => {\n                                let destroy_info = self.state.mut_destroy_info().expect(\n                                    \"unable to get a mut reference to destroy state in cursor\",\n                                );\n                                destroy_info.state = DestroyState::FreePage;\n                                continue;\n                            }\n                            //  Non-leaf page which has processed all children but not it's potential right child\n                            (false, n) if n == contents.cell_count() as i32 => {\n                                if let Some(rightmost) = contents.rightmost_pointer()? {\n                                    let (rightmost_page, c) = self.read_page(rightmost as i64)?;\n                                    self.stack.push(rightmost_page);\n                                    let destroy_info = self.state.mut_destroy_info().expect(\n                                        \"unable to get a mut reference to destroy state in cursor\",\n                                    );\n                                    destroy_info.state = DestroyState::LoadPage;\n                                    if let Some(c) = c {\n                                        io_yield_one!(c);\n                                    }\n                                } else {\n                                    let destroy_info = self.state.mut_destroy_info().expect(\n                                        \"unable to get a mut reference to destroy state in cursor\",\n                                    );\n                                    destroy_info.state = DestroyState::FreePage;\n                                }\n                                continue;\n                            }\n                            //  Non-leaf page which has processed all children and it's right child\n                            (false, n) if n > contents.cell_count() as i32 => {\n                                let destroy_info = self.state.mut_destroy_info().expect(\n                                    \"unable to get a mut reference to destroy state in cursor\",\n                                );\n                                destroy_info.state = DestroyState::FreePage;\n                                continue;\n                            }\n                            _ => unreachable!(\"Invalid cell idx state\"),\n                        }\n                    }\n\n                    //  We have not yet processed all cells in this page\n                    //  Get the current cell\n                    let cell = contents.cell_get(cell_idx as usize, self.usable_space())?;\n\n                    match contents.is_leaf() {\n                        //  For a leaf cell, clear the overflow pages associated with this cell\n                        true => {\n                            let destroy_info = self\n                                .state\n                                .mut_destroy_info()\n                                .expect(\"unable to get a mut reference to destroy state in cursor\");\n                            destroy_info.state = DestroyState::ClearOverflowPages { cell };\n                            continue;\n                        }\n                        //  For interior cells, check the type of cell to determine what to do\n                        false => match &cell {\n                            //  For index interior cells, remove the overflow pages\n                            BTreeCell::IndexInteriorCell(_) => {\n                                let destroy_info = self.state.mut_destroy_info().expect(\n                                    \"unable to get a mut reference to destroy state in cursor\",\n                                );\n                                destroy_info.state = DestroyState::ClearOverflowPages { cell };\n                                continue;\n                            }\n                            //  For all other interior cells, load the left child page\n                            _ => {\n                                let child_page_id = match &cell {\n                                    BTreeCell::TableInteriorCell(cell) => cell.left_child_page,\n                                    BTreeCell::IndexInteriorCell(cell) => cell.left_child_page,\n                                    _ => panic!(\"expected interior cell\"),\n                                };\n                                let (child_page, c) = self.read_page(child_page_id as i64)?;\n                                self.stack.push(child_page);\n                                let destroy_info = self.state.mut_destroy_info().expect(\n                                    \"unable to get a mut reference to destroy state in cursor\",\n                                );\n                                destroy_info.state = DestroyState::LoadPage;\n                                if let Some(c) = c {\n                                    io_yield_one!(c);\n                                }\n                            }\n                        },\n                    }\n                }\n                DestroyState::ClearOverflowPages { cell } => {\n                    return_if_io!(self.clear_overflow_pages(&cell));\n                    match cell {\n                        //  For an index interior cell, clear the left child page now that overflow pages have been cleared\n                        BTreeCell::IndexInteriorCell(index_int_cell) => {\n                            let (child_page, c) =\n                                self.read_page(index_int_cell.left_child_page as i64)?;\n                            self.stack.push(child_page);\n                            let destroy_info = self\n                                .state\n                                .mut_destroy_info()\n                                .expect(\"unable to get a mut reference to destroy state in cursor\");\n                            destroy_info.state = DestroyState::LoadPage;\n                            if let Some(c) = c {\n                                io_yield_one!(c);\n                            }\n                        }\n                        //  For any leaf cell, advance the index now that overflow pages have been cleared\n                        BTreeCell::TableLeafCell(_) | BTreeCell::IndexLeafCell(_) => {\n                            let destroy_info = self\n                                .state\n                                .mut_destroy_info()\n                                .expect(\"unable to get a mut reference to destroy state in cursor\");\n                            destroy_info.state = DestroyState::LoadPage;\n                        }\n                        _ => panic!(\"unexpected cell type\"),\n                    }\n                }\n                DestroyState::FreePage => {\n                    let page = self.stack.top();\n                    let page_id = page.get().id;\n\n                    if self.stack.has_parent() {\n                        return_if_io!(self.pager.free_page(Some(page), page_id));\n\n                        self.stack.pop();\n                        let destroy_info = self\n                            .state\n                            .mut_destroy_info()\n                            .expect(\"unable to get a mut reference to destroy state in cursor\");\n                        destroy_info.state = DestroyState::ProcessPage;\n                    } else {\n                        if keep_root {\n                            self.clear_root(&page)?;\n                        } else {\n                            return_if_io!(self.pager.free_page(Some(page), page_id));\n                        }\n\n                        self.state = CursorState::None;\n                        //  TODO: For now, no-op the result return None always. This will change once [AUTO_VACUUM](https://www.sqlite.org/lang_vacuum.html) is introduced\n                        //  At that point, the last root page(call this x) will be moved into the position of the root page of this table and the value returned will be x\n                        return Ok(IOResult::Done(None));\n                    }\n                }\n            }\n        }\n    }\n\n    fn clear_root(&mut self, root_page: &PageRef) -> Result<()> {\n        let contents = root_page.get_contents();\n\n        let page_type = match contents.page_type()? {\n            PageType::TableLeaf | PageType::TableInterior => PageType::TableLeaf,\n            PageType::IndexLeaf | PageType::IndexInterior => PageType::IndexLeaf,\n        };\n\n        self.pager.add_dirty(root_page)?;\n        btree_init_page(root_page, page_type, 0, self.pager.usable_space());\n        Ok(())\n    }\n\n    pub fn overwrite_cell(\n        &mut self,\n        page: &PageRef,\n        cell_idx: usize,\n        record: &ImmutableRecord,\n        state: &mut OverwriteCellState,\n    ) -> Result<IOResult<()>> {\n        loop {\n            turso_assert!(page.is_loaded(), \"page is not loaded\", { \"page_id\": page.get().id });\n            match state {\n                OverwriteCellState::AllocatePayload => {\n                    let serial_types_len = record.column_count();\n                    // Reuse the cell payload buffer to avoid allocations\n                    let mut new_payload = std::mem::take(&mut self.reusable_cell_payload);\n                    new_payload.clear();\n                    if new_payload.capacity() < serial_types_len {\n                        new_payload.reserve(serial_types_len - new_payload.capacity());\n                    }\n                    let rowid = return_if_io!(self.rowid());\n                    *state = OverwriteCellState::FillPayload {\n                        new_payload,\n                        rowid,\n                        fill_cell_payload_state: FillCellPayloadState::Start,\n                    };\n                    continue;\n                }\n                OverwriteCellState::FillPayload {\n                    new_payload,\n                    rowid,\n                    fill_cell_payload_state,\n                } => {\n                    {\n                        return_if_io!(fill_cell_payload(\n                            &PinGuard::new(page.clone()),\n                            *rowid,\n                            new_payload,\n                            cell_idx,\n                            record,\n                            self.usable_space(),\n                            self.pager.clone(),\n                            fill_cell_payload_state,\n                        ));\n                    }\n                    // figure out old cell offset & size\n                    let (old_offset, old_local_size) = {\n                        let contents = page.get_contents();\n                        contents.cell_get_raw_region(cell_idx, self.usable_space())?\n                    };\n\n                    *state = OverwriteCellState::ClearOverflowPagesAndOverwrite {\n                        new_payload: std::mem::take(new_payload),\n                        old_offset,\n                        old_local_size,\n                    };\n                    continue;\n                }\n                OverwriteCellState::ClearOverflowPagesAndOverwrite {\n                    new_payload,\n                    old_offset,\n                    old_local_size,\n                } => {\n                    let contents = page.get_contents();\n                    let cell = contents.cell_get(cell_idx, self.usable_space())?;\n                    return_if_io!(self.clear_overflow_pages(&cell));\n\n                    // if it all fits in local space and old_local_size is enough, do an in-place overwrite\n                    if new_payload.len() == *old_local_size {\n                        Self::overwrite_content(page, *old_offset, new_payload)?;\n                        // Recover the reusable buffer\n                        self.reusable_cell_payload = std::mem::take(new_payload);\n                        return Ok(IOResult::Done(()));\n                    }\n\n                    drop_cell(contents, cell_idx, self.usable_space())?;\n                    insert_into_cell(contents, new_payload, cell_idx, self.usable_space())?;\n                    // Recover the reusable buffer\n                    self.reusable_cell_payload = std::mem::take(new_payload);\n                    return Ok(IOResult::Done(()));\n                }\n            }\n        }\n    }\n\n    pub fn overwrite_content(page: &PageRef, dest_offset: usize, new_payload: &[u8]) -> Result<()> {\n        turso_assert!(page.is_loaded(), \"page should be loaded\");\n        let buf = page.get_contents().as_ptr();\n        buf[dest_offset..dest_offset + new_payload.len()].copy_from_slice(new_payload);\n        Ok(())\n    }\n\n    fn get_immutable_record_or_create(&mut self) -> Option<&mut ImmutableRecord> {\n        let reusable_immutable_record = &mut self.reusable_immutable_record;\n        if reusable_immutable_record.is_none() {\n            let page_size = self.pager.get_page_size_unchecked().get();\n            let record = ImmutableRecord::new(page_size as usize);\n            reusable_immutable_record.replace(record);\n        }\n        reusable_immutable_record.as_mut()\n    }\n\n    fn get_immutable_record(&self) -> Option<&ImmutableRecord> {\n        self.reusable_immutable_record.as_ref()\n    }\n\n    pub fn is_write_in_progress(&self) -> bool {\n        matches!(self.state, CursorState::Write(_))\n    }\n\n    // Save cursor context, to be restored later\n    pub fn save_context(&mut self, cursor_context: CursorContext) {\n        self.valid_state = CursorValidState::RequireSeek;\n        self.context = Some(cursor_context);\n    }\n\n    /// If context is defined, restore it and set it None on success\n    #[cfg_attr(debug_assertions, instrument(skip_all, level = Level::DEBUG))]\n    fn restore_context(&mut self) -> Result<IOResult<()>> {\n        if self.context.is_none() || matches!(self.valid_state, CursorValidState::Valid) {\n            return Ok(IOResult::Done(()));\n        }\n        if let CursorValidState::RequireAdvance(direction) = self.valid_state {\n            return_if_io!(match direction {\n                // Avoid calling next()/prev() directly because they immediately call restore_context()\n                IterationDirection::Forwards => self.get_next_record(),\n                IterationDirection::Backwards => self.get_prev_record(),\n            });\n            self.context = None;\n            self.valid_state = CursorValidState::Valid;\n            return Ok(IOResult::Done(()));\n        }\n        let ctx = self.context.take().unwrap();\n        let seek_key = match ctx.key {\n            CursorContextKey::TableRowId(rowid) => SeekKey::TableRowId(rowid),\n            CursorContextKey::IndexKeyRowId(ref record) => SeekKey::IndexKey(record),\n        };\n        let res = self.seek(seek_key, ctx.seek_op)?;\n        match res {\n            IOResult::Done(res) => {\n                if let SeekResult::TryAdvance = res {\n                    self.valid_state =\n                        CursorValidState::RequireAdvance(ctx.seek_op.iteration_direction());\n                    self.context = Some(ctx);\n                    io_yield_one!(Completion::new_yield());\n                }\n                self.valid_state = CursorValidState::Valid;\n                Ok(IOResult::Done(()))\n            }\n            IOResult::IO(io) => {\n                self.context = Some(ctx);\n                Ok(IOResult::IO(io))\n            }\n        }\n    }\n\n    pub fn read_page(&self, page_idx: i64) -> Result<(PageRef, Option<Completion>)> {\n        btree_read_page(&self.pager, page_idx)\n    }\n\n    pub fn allocate_page(&self, page_type: PageType, offset: usize) -> Result<IOResult<PageRef>> {\n        self.pager\n            .do_allocate_page(page_type, offset, BtreePageAllocMode::Any)\n    }\n}\n\nimpl CursorTrait for BTreeCursor {\n    #[cfg_attr(debug_assertions, instrument(skip_all, level = Level::DEBUG))]\n    fn next(&mut self) -> Result<IOResult<()>> {\n        if self.valid_state == CursorValidState::Invalid {\n            return Ok(IOResult::Done(()));\n        }\n        if self.skip_advance {\n            // See DeleteState::RestoreContextAfterBalancing\n            self.skip_advance = false;\n            let mem_page = self.stack.top_ref();\n            let contents = mem_page.get_contents();\n            let cell_idx = self.stack.current_cell_index();\n            let cell_count = contents.cell_count();\n            let has_record = cell_idx >= 0 && cell_idx < cell_count as i32;\n            if has_record {\n                self.set_has_record(has_record);\n                // If we are positioned at a record, we stop here without advancing.\n                self.read_overflow_state = None;\n                return Ok(IOResult::Done(()));\n            }\n            // But: if we aren't currently positioned at a record (for example, we are at the end of a page),\n            // we need to advance despite the skip_advance flag\n            // because the intent is to find the next record immediately after the one we just deleted.\n        }\n        loop {\n            match self.advance_state {\n                AdvanceState::Start => {\n                    return_if_io!(self.restore_context());\n                    self.advance_state = AdvanceState::Advance;\n                }\n                AdvanceState::Advance => {\n                    return_if_io!(self.get_next_record());\n                    self.advance_state = AdvanceState::Start;\n                    self.read_overflow_state = None;\n                    return Ok(IOResult::Done(()));\n                }\n            }\n        }\n    }\n\n    #[cfg_attr(debug_assertions, instrument(skip_all, level = Level::DEBUG))]\n    fn last(&mut self) -> Result<IOResult<()>> {\n        self.set_null_flag(false);\n        let always_seek = false;\n        let cursor_has_record = return_if_io!(self.move_to_rightmost(always_seek));\n        self.set_has_record(cursor_has_record);\n        self.invalidate_record();\n        self.read_overflow_state = None;\n        Ok(IOResult::Done(()))\n    }\n\n    #[cfg_attr(debug_assertions, instrument(skip_all, level = Level::DEBUG))]\n    fn prev(&mut self) -> Result<IOResult<()>> {\n        loop {\n            match self.advance_state {\n                AdvanceState::Start => {\n                    return_if_io!(self.restore_context());\n                    self.advance_state = AdvanceState::Advance;\n                }\n                AdvanceState::Advance => {\n                    return_if_io!(self.get_prev_record());\n                    self.advance_state = AdvanceState::Start;\n                    self.read_overflow_state = None;\n                    return Ok(IOResult::Done(()));\n                }\n            }\n        }\n    }\n\n    #[cfg_attr(debug_assertions, instrument(skip(self), level = Level::DEBUG))]\n    fn rowid(&mut self) -> Result<IOResult<Option<i64>>> {\n        if self.get_null_flag() {\n            return Ok(IOResult::Done(None));\n        }\n        if self.has_record() {\n            let page = self.stack.top_ref();\n            let contents = page.get_contents();\n            let page_type = contents.page_type()?;\n            if page_type.is_table() {\n                let cell_idx = self.stack.current_cell_index();\n                let rowid = contents.cell_table_leaf_read_rowid(cell_idx as usize)?;\n                Ok(IOResult::Done(Some(rowid)))\n            } else {\n                let _ = return_if_io!(self.record());\n                Ok(IOResult::Done(self.get_index_rowid_from_record()))\n            }\n        } else {\n            Ok(IOResult::Done(None))\n        }\n    }\n\n    #[cfg_attr(debug_assertions, instrument(skip(self, key), level = Level::DEBUG))]\n    fn seek(&mut self, key: SeekKey<'_>, op: SeekOp) -> Result<IOResult<SeekResult>> {\n        self.skip_advance = false;\n        // Empty trace to capture the span information\n        tracing::trace!(\"\");\n        // We need to clear the null flag for the table cursor before seeking,\n        // because it might have been set to false by an unmatched left-join row during the previous iteration\n        // on the outer loop.\n        self.set_null_flag(false);\n        let seek_result = return_if_io!(self.do_seek(key, op));\n        self.invalidate_record();\n        // Reset seek state\n        self.seek_state = CursorSeekState::Start;\n        self.valid_state = CursorValidState::Valid;\n        self.read_overflow_state = None;\n        Ok(IOResult::Done(seek_result))\n    }\n\n    #[cfg_attr(debug_assertions, instrument(skip(self, registers), level = Level::DEBUG))]\n    fn seek_unpacked(\n        &mut self,\n        registers: &[Register],\n        op: SeekOp,\n    ) -> Result<IOResult<SeekResult>> {\n        self.skip_advance = false;\n        // Empty trace to capture the span information\n        tracing::trace!(\"\");\n        // We need to clear the null flag for the table cursor before seeking,\n        // because it might have been set to false by an unmatched left-join row during the previous iteration\n        // on the outer loop.\n        self.set_null_flag(false);\n        let seek_result = return_if_io!(self.do_seek_unpacked(registers, op));\n        self.invalidate_record();\n        // Reset seek state\n        self.seek_state = CursorSeekState::Start;\n        self.valid_state = CursorValidState::Valid;\n        Ok(IOResult::Done(seek_result))\n    }\n\n    #[cfg_attr(debug_assertions, instrument(skip(self), level = Level::DEBUG))]\n    fn record(&mut self) -> Result<IOResult<Option<&ImmutableRecord>>> {\n        if !self.has_record() {\n            return Ok(IOResult::Done(None));\n        }\n        let invalidated = self\n            .reusable_immutable_record\n            .as_ref()\n            .is_none_or(|record| record.is_invalidated());\n        if !invalidated {\n            return Ok(IOResult::Done(self.reusable_immutable_record.as_ref()));\n        }\n\n        let page = self.stack.top_ref();\n        let contents = page.get_contents();\n        let cell_idx = self.stack.current_cell_index();\n        let cell = contents.cell_get(cell_idx as usize, self.usable_space())?;\n        let (payload, payload_size, first_overflow_page) = match cell {\n            BTreeCell::TableLeafCell(TableLeafCell {\n                payload,\n                payload_size,\n                first_overflow_page,\n                ..\n            }) => (payload, payload_size, first_overflow_page),\n            BTreeCell::IndexInteriorCell(IndexInteriorCell {\n                payload,\n                payload_size,\n                first_overflow_page,\n                ..\n            }) => (payload, payload_size, first_overflow_page),\n            BTreeCell::IndexLeafCell(IndexLeafCell {\n                payload,\n                first_overflow_page,\n                payload_size,\n            }) => (payload, payload_size, first_overflow_page),\n            _ => unreachable!(\"unexpected page_type\"),\n        };\n        if let Some(next_page) = first_overflow_page {\n            return_if_io!(self.process_overflow_read(payload, next_page, payload_size))\n        } else {\n            self.get_immutable_record_or_create()\n                .as_mut()\n                .unwrap()\n                .invalidate();\n            self.get_immutable_record_or_create()\n                .as_mut()\n                .unwrap()\n                .start_serialization(payload);\n        };\n\n        Ok(IOResult::Done(self.reusable_immutable_record.as_ref()))\n    }\n\n    #[cfg_attr(debug_assertions, instrument(skip_all, level = Level::DEBUG))]\n    fn insert(&mut self, key: &BTreeKey) -> Result<IOResult<()>> {\n        tracing::debug!(valid_state = ?self.valid_state, cursor_state = ?self.state, is_write_in_progress = self.is_write_in_progress());\n        return_if_io!(self.insert_into_page(key));\n        self.invalidate_count_cache();\n        if key.maybe_rowid().is_some() {\n            self.set_has_record(true);\n        }\n        Ok(IOResult::Done(()))\n    }\n\n    #[cfg_attr(debug_assertions, instrument(skip(self), level = Level::DEBUG))]\n    /// Delete state machine flow:\n    /// 1. Start -> check if the rowid to be delete is present in the page or not. If not we early return\n    /// 2. DeterminePostBalancingSeekKey -> determine the key to seek to after balancing.\n    /// 3. LoadPage -> load the page.\n    /// 4. FindCell -> find the cell to be deleted in the page.\n    /// 5. ClearOverflowPages -> Clear the overflow pages if there are any before dropping the cell, then if we are in a leaf page we just drop the cell in place.\n    /// if we are in interior page, we need to rotate keys in order to replace current cell (InteriorNodeReplacement).\n    /// 6. InteriorNodeReplacement -> we copy the left subtree leaf node into the deleted interior node's place.\n    /// 7. Balancing -> perform balancing\n    /// 8. PostInteriorNodeReplacement -> if an interior node was replaced, we need to advance the cursor once.\n    /// 9. SeekAfterBalancing -> adjust the cursor to a node that is closer to the deleted value. go to Finish\n    /// 10. Finish -> Delete operation is done. Return CursorResult(Ok())\n    fn delete(&mut self) -> Result<IOResult<()>> {\n        if let CursorState::None = &self.state {\n            self.invalidate_count_cache();\n            self.state = CursorState::Delete(DeleteState::Start);\n        }\n\n        loop {\n            let usable_space = self.usable_space();\n            let delete_state = match &mut self.state {\n                CursorState::Delete(x) => x,\n                _ => unreachable!(\"expected delete state\"),\n            };\n            tracing::debug!(?delete_state);\n\n            match delete_state {\n                DeleteState::Start => {\n                    let page = self.stack.top_ref();\n                    self.pager.add_dirty(page)?;\n                    if matches!(\n                        page.get_contents().page_type()?,\n                        PageType::TableLeaf | PageType::TableInterior\n                    ) {\n                        if return_if_io!(self.rowid()).is_none() {\n                            self.state = CursorState::None;\n                            return Ok(IOResult::Done(()));\n                        }\n                    } else if !self.has_record() {\n                        self.state = CursorState::None;\n                        return Ok(IOResult::Done(()));\n                    }\n\n                    self.state = CursorState::Delete(DeleteState::DeterminePostBalancingSeekKey);\n                }\n\n                DeleteState::DeterminePostBalancingSeekKey => {\n                    // FIXME: skip this work if we determine deletion wont result in balancing\n                    // Right now we calculate the key every time for simplicity/debugging\n                    // since it won't affect correctness which is more important\n                    let page = self.stack.top_ref();\n                    let target_key = if page.is_index()? {\n                        let record = match return_if_io!(self.record()) {\n                            Some(record) => record.clone(),\n                            None => unreachable!(\"there should've been a record\"),\n                        };\n                        CursorContext {\n                            key: CursorContextKey::IndexKeyRowId(record),\n                            seek_op: SeekOp::GE { eq_only: true },\n                        }\n                    } else {\n                        let Some(rowid) = return_if_io!(self.rowid()) else {\n                            panic!(\"cursor should be pointing to a record with a rowid\");\n                        };\n                        CursorContext {\n                            key: CursorContextKey::TableRowId(rowid),\n                            seek_op: SeekOp::GE { eq_only: true },\n                        }\n                    };\n\n                    self.state = CursorState::Delete(DeleteState::LoadPage {\n                        post_balancing_seek_key: Some(target_key),\n                    });\n                }\n\n                DeleteState::LoadPage {\n                    post_balancing_seek_key,\n                } => {\n                    self.state = CursorState::Delete(DeleteState::FindCell {\n                        post_balancing_seek_key: post_balancing_seek_key.take(),\n                    });\n                }\n\n                DeleteState::FindCell {\n                    post_balancing_seek_key,\n                } => {\n                    let page = self.stack.top_ref();\n                    let cell_idx = self.stack.current_cell_index() as usize;\n                    let contents = page.get_contents();\n                    if unlikely(cell_idx >= contents.cell_count()) {\n                        return_corrupt!(\n                            \"Corrupted page: cell index {} is out of bounds for page with {} cells\",\n                            cell_idx,\n                            contents.cell_count()\n                        );\n                    }\n\n                    tracing::debug!(\n                        \"DeleteState::FindCell: page_id: {}, cell_idx: {}\",\n                        page.get().id,\n                        cell_idx\n                    );\n\n                    let cell = contents.cell_get(cell_idx, usable_space)?;\n\n                    let original_child_pointer = match &cell {\n                        BTreeCell::TableInteriorCell(interior) => Some(interior.left_child_page),\n                        BTreeCell::IndexInteriorCell(interior) => Some(interior.left_child_page),\n                        _ => None,\n                    };\n\n                    self.state = CursorState::Delete(DeleteState::ClearOverflowPages {\n                        cell_idx,\n                        cell,\n                        original_child_pointer,\n                        post_balancing_seek_key: post_balancing_seek_key.take(),\n                    });\n                }\n\n                DeleteState::ClearOverflowPages { cell, .. } => {\n                    let cell = cell.clone();\n                    return_if_io!(self.clear_overflow_pages(&cell));\n\n                    let CursorState::Delete(DeleteState::ClearOverflowPages {\n                        cell_idx,\n                        original_child_pointer,\n                        ref mut post_balancing_seek_key,\n                        ..\n                    }) = self.state\n                    else {\n                        unreachable!(\"expected clear overflow pages state\");\n                    };\n\n                    let page = self.stack.top_ref();\n                    let contents = page.get_contents();\n\n                    if !contents.is_leaf() {\n                        self.state = CursorState::Delete(DeleteState::InteriorNodeReplacement {\n                            page: page.clone(),\n                            btree_depth: self.stack.current(),\n                            cell_idx,\n                            original_child_pointer,\n                            post_balancing_seek_key: post_balancing_seek_key.take(),\n                        });\n                    } else {\n                        drop_cell(contents, cell_idx, usable_space)?;\n\n                        self.state = CursorState::Delete(DeleteState::CheckNeedsBalancing {\n                            btree_depth: self.stack.current(),\n                            post_balancing_seek_key: post_balancing_seek_key.take(),\n                            interior_node_was_replaced: false,\n                        });\n                    }\n                }\n\n                DeleteState::InteriorNodeReplacement { .. } => {\n                    // This is an interior node, we need to handle deletion differently.\n                    // 1. Move cursor to the largest key in the left subtree.\n                    // 2. Replace the cell in the interior (parent) node with that key.\n                    // 3. Delete that key from the child page.\n\n                    // Step 1: Move cursor to the largest key in the left subtree.\n                    // The largest key is always in a leaf, and so this traversal may involvegoing multiple pages downwards,\n                    // so we store the page we are currently on.\n\n                    // avoid calling prev() because it internally calls restore_context() which may cause unintended behavior.\n                    return_if_io!(self.get_prev_record());\n\n                    let CursorState::Delete(DeleteState::InteriorNodeReplacement {\n                        ref page,\n                        btree_depth,\n                        cell_idx,\n                        original_child_pointer,\n                        ref mut post_balancing_seek_key,\n                        ..\n                    }) = self.state\n                    else {\n                        unreachable!(\"expected interior node replacement state\");\n                    };\n\n                    // Ensure we keep the parent page at the same position as before the replacement.\n                    self.stack\n                        .node_states\n                        .get_mut(btree_depth)\n                        .expect(\"parent page should be on the stack\")\n                        .cell_idx = cell_idx as i32;\n                    let (cell_payload, leaf_cell_idx) = {\n                        let leaf_page = self.stack.top_ref();\n                        let leaf_contents = leaf_page.get_contents();\n                        turso_assert!(leaf_contents.is_leaf());\n                        turso_assert_greater_than!(leaf_contents.cell_count(), 0);\n                        let leaf_cell_idx = leaf_contents.cell_count() - 1;\n                        let last_cell_on_child_page =\n                            leaf_contents.cell_get(leaf_cell_idx, usable_space)?;\n\n                        let mut cell_payload: Vec<u8> = Vec::new();\n                        let child_pointer =\n                            original_child_pointer.expect(\"there should be a pointer\");\n                        // Rewrite the old leaf cell as an interior cell depending on type.\n                        match last_cell_on_child_page {\n                            BTreeCell::TableLeafCell(leaf_cell) => {\n                                // Table interior cells contain the left child pointer and the rowid as varint.\n                                cell_payload.extend_from_slice(&child_pointer.to_be_bytes());\n                                write_varint_to_vec(leaf_cell.rowid as u64, &mut cell_payload);\n                            }\n                            BTreeCell::IndexLeafCell(leaf_cell) => {\n                                // Index interior cells contain:\n                                // 1. The left child pointer\n                                // 2. The payload size as varint\n                                // 3. The payload\n                                // 4. The first overflow page as varint, omitted if no overflow.\n                                cell_payload.extend_from_slice(&child_pointer.to_be_bytes());\n                                write_varint_to_vec(leaf_cell.payload_size, &mut cell_payload);\n                                cell_payload.extend_from_slice(leaf_cell.payload);\n                                if let Some(first_overflow_page) = leaf_cell.first_overflow_page {\n                                    cell_payload\n                                        .extend_from_slice(&first_overflow_page.to_be_bytes());\n                                }\n                            }\n                            _ => unreachable!(\"Expected table leaf cell\"),\n                        }\n                        (cell_payload, leaf_cell_idx)\n                    };\n\n                    let leaf_page = self.stack.top_ref();\n\n                    self.pager.add_dirty(page)?;\n                    self.pager.add_dirty(leaf_page)?;\n\n                    // Step 2: Replace the cell in the parent (interior) page.\n                    {\n                        let parent_contents = page.get_contents();\n                        let parent_page_id = page.get().id;\n                        let left_child_page = u32::from_be_bytes(\n                            cell_payload[..4].try_into().expect(\"invalid cell payload\"),\n                        );\n                        turso_assert!(\n                            left_child_page as usize != parent_page_id,\n                            \"corrupt: current page and left child page are the same\",\n                            { \"left_child_page\": left_child_page, \"parent_page_id\": parent_page_id }\n                        );\n\n                        // First, drop the old cell that is being replaced.\n                        drop_cell(parent_contents, cell_idx, usable_space)?;\n                        // Then, insert the new cell (the predecessor) in its place.\n                        insert_into_cell(parent_contents, &cell_payload, cell_idx, usable_space)?;\n                    }\n\n                    // Step 3: Delete the predecessor cell from the leaf page.\n                    {\n                        let leaf_contents = leaf_page.get_contents();\n                        drop_cell(leaf_contents, leaf_cell_idx, usable_space)?;\n                    }\n\n                    self.state = CursorState::Delete(DeleteState::CheckNeedsBalancing {\n                        btree_depth,\n                        post_balancing_seek_key: post_balancing_seek_key.take(),\n                        interior_node_was_replaced: true,\n                    });\n                }\n\n                DeleteState::CheckNeedsBalancing { btree_depth, .. } => {\n                    let page = self.stack.top_ref();\n                    // Check if either the leaf page we took the replacement cell from underflows, or if the interior page we inserted it into overflows OR underflows.\n                    // If the latter is true, we must always balance that level regardless of whether the leaf page (or any ancestor pages in between) need balancing.\n\n                    let leaf_underflows = {\n                        let leaf_contents = page.get_contents();\n                        let free_space = compute_free_space(leaf_contents, usable_space)?;\n                        free_space * 3 > usable_space * 2\n                    };\n\n                    let interior_overflows_or_underflows = {\n                        // Invariant: ancestor pages on the stack are pinned to the page cache,\n                        // so we don't need return_if_locked_maybe_load! any ancestor,\n                        // and we already loaded the current page above.\n                        let interior_page = self\n                            .stack\n                            .get_page_at_level(*btree_depth)\n                            .expect(\"ancestor page should be on the stack\");\n                        let interior_contents = interior_page.get_contents();\n                        let overflows = !interior_contents.overflow_cells.is_empty();\n                        if overflows {\n                            true\n                        } else {\n                            let free_space = compute_free_space(interior_contents, usable_space)?;\n                            free_space * 3 > usable_space * 2\n                        }\n                    };\n\n                    let needs_balancing = leaf_underflows || interior_overflows_or_underflows;\n\n                    let CursorState::Delete(DeleteState::CheckNeedsBalancing {\n                        btree_depth,\n                        ref mut post_balancing_seek_key,\n                        interior_node_was_replaced,\n                        ..\n                    }) = self.state\n                    else {\n                        unreachable!(\"expected check needs balancing state\");\n                    };\n\n                    if needs_balancing {\n                        let balance_only_ancestor =\n                            !leaf_underflows && interior_overflows_or_underflows;\n                        if balance_only_ancestor {\n                            // Only need to balance the ancestor page; move there immediately.\n                            while self.stack.current() > btree_depth {\n                                self.stack.pop();\n                            }\n                        }\n                        let balance_both = leaf_underflows && interior_overflows_or_underflows;\n                        turso_assert!(matches!(self.balance_state.sub_state, BalanceSubState::Start), \"no balancing operation should be in progress during delete\", { \"sub_state\": self.balance_state.sub_state });\n                        let post_balancing_seek_key = post_balancing_seek_key\n                            .take()\n                            .expect(\"post_balancing_seek_key should be Some\");\n                        self.save_context(post_balancing_seek_key);\n                        self.state = CursorState::Delete(DeleteState::Balancing {\n                            balance_ancestor_at_depth: if balance_both {\n                                Some(btree_depth)\n                            } else {\n                                None\n                            },\n                        });\n                    } else {\n                        // No balancing needed.\n                        if interior_node_was_replaced {\n                            // If we did replace an interior node, we need to advance the cursor once to\n                            // get back at the interior node that now has the replaced content.\n                            // The reason it is important to land here is that the replaced cell was smaller (LT) than the deleted cell,\n                            // so we must ensure we skip over it. I.e., when BTreeCursor::next() is called, it will move past the cell\n                            // that holds the replaced content.\n                            self.state =\n                                CursorState::Delete(DeleteState::PostInteriorNodeReplacement);\n                        } else {\n                            // If we didn't replace an interior node, we are done,\n                            // except we need to retreat, so that the next call to BTreeCursor::next() lands at the next record (because we deleted the current one)\n                            self.stack.retreat();\n                            self.state = CursorState::None;\n                            return Ok(IOResult::Done(()));\n                        }\n                    }\n                }\n                DeleteState::PostInteriorNodeReplacement => {\n                    return_if_io!(self.get_next_record());\n                    self.state = CursorState::None;\n                    return Ok(IOResult::Done(()));\n                }\n\n                DeleteState::Balancing {\n                    balance_ancestor_at_depth,\n                } => {\n                    let balance_ancestor_at_depth = *balance_ancestor_at_depth;\n                    return_if_io!(self.balance(balance_ancestor_at_depth));\n                    self.state = CursorState::Delete(DeleteState::RestoreContextAfterBalancing);\n                }\n                DeleteState::RestoreContextAfterBalancing => {\n                    return_if_io!(self.restore_context());\n\n                    // We deleted key K, and performed a seek to: GE { eq_only: true } K.\n                    // This means that the cursor is now pointing to the next key after K.\n                    // We need to make the next call to BTreeCursor::next() a no-op so that we don't skip over\n                    // a row when deleting rows in a loop.\n                    self.skip_advance = true;\n                    self.state = CursorState::None;\n                    return Ok(IOResult::Done(()));\n                }\n            }\n        }\n    }\n\n    #[inline(always)]\n    /// In outer joins, whenever the right-side table has no matching row, the query must still return a row\n    /// for each left-side row. In order to achieve this, we set the null flag on the right-side table cursor\n    /// so that it returns NULL for all columns until cleared.\n    fn set_null_flag(&mut self, flag: bool) {\n        self.null_flag = flag;\n    }\n\n    #[inline(always)]\n    fn get_null_flag(&self) -> bool {\n        self.null_flag\n    }\n\n    #[cfg_attr(debug_assertions, instrument(skip_all, level = Level::DEBUG))]\n    fn exists(&mut self, key: &Value) -> Result<IOResult<bool>> {\n        let int_key = match key {\n            Value::Numeric(Numeric::Integer(i)) => i,\n            _ => unreachable!(\"btree tables are indexed by integers!\"),\n        };\n        let seek_result =\n            return_if_io!(self.seek(SeekKey::TableRowId(*int_key), SeekOp::GE { eq_only: true }));\n        let exists = matches!(seek_result, SeekResult::Found);\n        self.invalidate_record();\n        Ok(IOResult::Done(exists))\n    }\n\n    /// Deletes all content from the B-Tree but preserves the root page.\n    ///\n    /// Unlike [`btree_destroy`], which frees all pages including the root,\n    /// this method only clears the tree’s contents. The root page remains\n    /// allocated and is reset to an empty leaf page.\n    fn clear_btree(&mut self) -> Result<IOResult<Option<usize>>> {\n        self.invalidate_count_cache();\n        self.destroy_btree_contents(true)\n    }\n\n    /// Destroys the entire B-Tree, including the root page.\n    ///\n    /// All pages belonging to the tree are freed, leaving no trace of the B-Tree.\n    /// Use this when the structure itself is no longer needed.\n    ///\n    /// For cases where the B-Tree should remain allocated but emptied, see [`btree_clear`].\n    #[cfg_attr(debug_assertions, instrument(skip(self), level = Level::DEBUG))]\n    fn btree_destroy(&mut self) -> Result<IOResult<Option<usize>>> {\n        self.destroy_btree_contents(false)\n    }\n\n    #[cfg_attr(debug_assertions, instrument(skip(self), level = Level::DEBUG))]\n    /// Count the number of entries in the b-tree\n    ///\n    /// Only supposed to be used in the context of a simple Count Select Statement\n    fn count(&mut self) -> Result<IOResult<usize>> {\n        let mut mem_page;\n        let mut contents;\n\n        'outer: loop {\n            let state = self.count_state;\n            match state {\n                CountState::Start => {\n                    let c = self.move_to_root()?;\n                    self.count_state = CountState::Loop;\n                    if let Some(c) = c {\n                        io_yield_one!(c);\n                    }\n                }\n                CountState::Loop => {\n                    self.stack.advance();\n                    mem_page = self.stack.top_ref();\n                    contents = mem_page.get_contents();\n\n                    /* If this is a leaf page or the tree is not an int-key tree, then\n                     ** this page contains countable entries. Increment the entry counter\n                     ** accordingly.\n                     */\n                    if !matches!(contents.page_type()?, PageType::TableInterior) {\n                        self.count += contents.cell_count();\n                    }\n\n                    let cell_idx = self.stack.current_cell_index() as usize;\n\n                    // Second condition is necessary in case we return if the page is locked in the loop below\n                    if contents.is_leaf() || cell_idx > contents.cell_count() {\n                        loop {\n                            if !self.stack.has_parent() {\n                                // All pages of the b-tree have been visited. Return successfully\n                                let c = self.move_to_root()?;\n                                self.count_state = CountState::Finish;\n                                if let Some(c) = c {\n                                    io_yield_one!(c);\n                                }\n                                continue 'outer;\n                            }\n\n                            // Move to parent\n                            self.stack.pop();\n\n                            mem_page = self.stack.top_ref();\n                            turso_assert!(mem_page.is_loaded(), \"page should be loaded\");\n                            contents = mem_page.get_contents();\n\n                            let cell_idx = self.stack.current_cell_index() as usize;\n\n                            if cell_idx <= contents.cell_count() {\n                                break;\n                            }\n                        }\n                    }\n\n                    let cell_idx = self.stack.current_cell_index() as usize;\n\n                    turso_assert_less_than_or_equal!(cell_idx, contents.cell_count());\n                    turso_assert!(!contents.is_leaf());\n\n                    if cell_idx == contents.cell_count() {\n                        // Move to right child\n                        // should be safe as contents is not a leaf page\n                        let right_most_pointer = contents.rightmost_pointer()?.unwrap();\n                        self.stack.advance();\n                        let (child, c) = self.read_page(right_most_pointer as i64)?;\n                        self.stack.push(child);\n                        if let Some(c) = c {\n                            io_yield_one!(c);\n                        }\n                    } else {\n                        // Move to child left page\n                        let cell = contents.cell_get(cell_idx, self.usable_space())?;\n\n                        match cell {\n                            BTreeCell::TableInteriorCell(TableInteriorCell {\n                                left_child_page,\n                                ..\n                            })\n                            | BTreeCell::IndexInteriorCell(IndexInteriorCell {\n                                left_child_page,\n                                ..\n                            }) => {\n                                self.stack.advance();\n                                let (child, c) = self.read_page(left_child_page as i64)?;\n                                self.stack.push(child);\n                                if let Some(c) = c {\n                                    io_yield_one!(c);\n                                }\n                            }\n                            _ => unreachable!(),\n                        }\n                    }\n                }\n                CountState::Finish => {\n                    return Ok(IOResult::Done(self.count));\n                }\n            }\n        }\n    }\n\n    #[inline]\n    fn is_empty(&self) -> bool {\n        !self.has_record\n    }\n\n    #[inline]\n    fn root_page(&self) -> i64 {\n        self.root_page\n    }\n\n    #[cfg_attr(debug_assertions, instrument(skip_all, level = Level::DEBUG))]\n    fn rewind(&mut self) -> Result<IOResult<()>> {\n        self.set_null_flag(false);\n        if self.valid_state == CursorValidState::Invalid {\n            return Ok(IOResult::Done(()));\n        }\n        self.skip_advance = false;\n        loop {\n            match self.rewind_state {\n                RewindState::Start => {\n                    self.rewind_state = RewindState::NextRecord;\n                    let c = self.move_to_root()?;\n                    if let Some(c) = c {\n                        io_yield_one!(c);\n                    }\n                }\n                RewindState::NextRecord => {\n                    return_if_io!(self.get_next_record());\n                    self.rewind_state = RewindState::Start;\n                    self.read_overflow_state = None;\n                    return Ok(IOResult::Done(()));\n                }\n            }\n        }\n    }\n\n    #[inline]\n    fn has_rowid(&self) -> bool {\n        match &self.index_info {\n            Some(index_key_info) => index_key_info.has_rowid,\n            None => true, // currently we don't support WITHOUT ROWID tables\n        }\n    }\n\n    #[inline]\n    fn invalidate_record(&mut self) {\n        self.get_immutable_record_or_create()\n            .as_mut()\n            .unwrap()\n            .invalidate();\n    }\n\n    #[inline]\n    fn get_pager(&self) -> Arc<Pager> {\n        self.pager.clone()\n    }\n\n    #[inline]\n    fn get_skip_advance(&self) -> bool {\n        self.skip_advance\n    }\n\n    fn invalidate_btree_cache(&mut self) {\n        self.move_to_right_state.1 = None;\n        self.invalidate_count_cache();\n    }\n\n    #[inline]\n    fn has_record(&self) -> bool {\n        self.has_record\n    }\n\n    #[inline]\n    fn set_has_record(&mut self, has_record: bool) {\n        self.has_record = has_record\n    }\n\n    #[inline]\n    fn get_index_info(&self) -> &Arc<IndexInfo> {\n        self.index_info.as_ref().unwrap()\n    }\n\n    fn seek_end(&mut self) -> Result<IOResult<()>> {\n        loop {\n            match self.seek_end_state {\n                SeekEndState::Start => {\n                    let c = self.move_to_root()?;\n                    self.seek_end_state = SeekEndState::ProcessPage;\n                    if let Some(c) = c {\n                        io_yield_one!(c);\n                    }\n                }\n                SeekEndState::ProcessPage => {\n                    let mem_page = self.stack.top_ref();\n                    let contents = mem_page.get_contents();\n                    if contents.is_leaf() {\n                        // set cursor just past the last cell to append\n                        self.stack.set_cell_index(contents.cell_count() as i32);\n                        self.seek_end_state = SeekEndState::Start;\n                        return Ok(IOResult::Done(()));\n                    }\n\n                    match contents.rightmost_pointer()? {\n                        Some(right_most_pointer) => {\n                            self.stack.set_cell_index(contents.cell_count() as i32 + 1); // invalid on interior\n                            let (child, c) = self.read_page(right_most_pointer as i64)?;\n                            self.stack.push(child);\n                            if let Some(c) = c {\n                                io_yield_one!(c);\n                            }\n                        }\n                        None => unreachable!(\"interior page must have rightmost pointer\"),\n                    }\n                }\n            }\n        }\n    }\n\n    #[cfg_attr(debug_assertions, instrument(skip_all, level = Level::DEBUG))]\n    fn seek_to_last(&mut self, always_seek: bool) -> Result<IOResult<()>> {\n        loop {\n            match self.seek_to_last_state {\n                SeekToLastState::Start => {\n                    let has_record = return_if_io!(self.move_to_rightmost(always_seek));\n                    self.invalidate_record();\n                    self.set_has_record(has_record);\n                    if !has_record {\n                        self.seek_to_last_state = SeekToLastState::IsEmpty;\n                        continue;\n                    }\n                    return Ok(IOResult::Done(()));\n                }\n                SeekToLastState::IsEmpty => {\n                    let is_empty = return_if_io!(self.is_empty_table());\n                    turso_assert!(is_empty);\n                    self.seek_to_last_state = SeekToLastState::Start;\n                    return Ok(IOResult::Done(()));\n                }\n            }\n        }\n    }\n}\n\n#[derive(Debug, thiserror::Error)]\npub enum IntegrityCheckError {\n    #[error(\"Cell {cell_idx} in page {page_id} is out of range. cell_range={cell_start}..{cell_end}, content_area={content_area}, usable_space={usable_space}\")]\n    CellOutOfRange {\n        cell_idx: usize,\n        page_id: i64,\n        cell_start: usize,\n        cell_end: usize,\n        content_area: usize,\n        usable_space: usize,\n    },\n    #[error(\"Cell {cell_idx} in page {page_id} extends out of page. cell_range={cell_start}..{cell_end}, content_area={content_area}, usable_space={usable_space}\")]\n    CellOverflowsPage {\n        cell_idx: usize,\n        page_id: i64,\n        cell_start: usize,\n        cell_end: usize,\n        content_area: usize,\n        usable_space: usize,\n    },\n    #[error(\"Page {page_id} ({page_category:?}) cell {cell_idx} has rowid={rowid} in wrong order. Parent cell has parent_rowid={max_intkey} and next_rowid={next_rowid}\")]\n    CellRowidOutOfRange {\n        page_id: i64,\n        page_category: PageCategory,\n        cell_idx: usize,\n        rowid: i64,\n        max_intkey: i64,\n        next_rowid: i64,\n    },\n    #[error(\"Page {page_id} is at different depth from another leaf page this_page_depth={this_page_depth}, other_page_depth={other_page_depth} \")]\n    LeafDepthMismatch {\n        page_id: i64,\n        this_page_depth: usize,\n        other_page_depth: usize,\n    },\n    #[error(\"Page {page_id} detected freeblock that extends page start={start} end={end}\")]\n    FreeBlockOutOfRange {\n        page_id: i64,\n        start: usize,\n        end: usize,\n    },\n    #[error(\"Page {page_id} cell overlap detected at position={start} with previous_end={prev_end}. content_area={content_area}, is_free_block={is_free_block}\")]\n    CellOverlap {\n        page_id: i64,\n        start: usize,\n        prev_end: usize,\n        content_area: usize,\n        is_free_block: bool,\n    },\n    #[error(\"Page {page_id} unexpected fragmentation got={got}, expected={expected}\")]\n    UnexpectedFragmentation {\n        page_id: i64,\n        got: usize,\n        expected: usize,\n    },\n    #[error(\"Page {page_id} referenced multiple times (references={references:?}, page_category={page_category:?})\")]\n    PageReferencedMultipleTimes {\n        page_id: i64,\n        references: Vec<i64>,\n        page_category: PageCategory,\n    },\n    #[error(\"Freelist: size is {actual_count} but should be {expected_count}\")]\n    FreelistCountMismatch {\n        actual_count: usize,\n        expected_count: usize,\n    },\n    #[error(\"Page {page_id}: never used\")]\n    PageNeverUsed { page_id: i64 },\n    #[error(\"Pending byte page {page_id} is being used\")]\n    PendingBytePageUsed { page_id: i64 },\n    #[error(\"Freelist: freelist leaf count too big on page {page_id}\")]\n    FreelistTrunkCorrupt {\n        page_id: i64,\n        page_pointers: u32,\n        max_pointers: usize,\n    },\n    #[error(\"Freelist: invalid page number {pointer}\")]\n    FreelistPointerOutOfRange { page_id: i64, pointer: i64 },\n    #[error(\"overflow list length is {got} but should be {expected}\")]\n    OverflowListLengthMismatch { got: usize, expected: usize },\n}\n\n#[derive(Debug, Clone, Copy, PartialEq)]\npub enum PageCategory {\n    Normal,\n    Overflow,\n    FreeListTrunk,\n    FreePage,\n}\n\n#[derive(Clone)]\npub struct CheckFreelist {\n    pub expected_count: usize,\n    pub actual_count: usize,\n}\n\n#[derive(Clone)]\nstruct IntegrityCheckPageEntry {\n    page_idx: i64,\n    level: usize,\n    max_intkey: i64,\n    page_category: PageCategory,\n    overflow_pages_expected: Option<usize>,\n    overflow_pages_seen: usize,\n}\npub struct IntegrityCheckState {\n    page_stack: Vec<IntegrityCheckPageEntry>,\n    pub db_size: usize,\n    first_leaf_level: Option<usize>,\n    pub page_reference: HashMap<i64, i64>,\n    page: Option<PageRef>,\n    pub freelist_count: CheckFreelist,\n}\n\nimpl IntegrityCheckState {\n    pub fn new(db_size: usize) -> Self {\n        Self {\n            page_stack: Vec::new(),\n            db_size,\n            page_reference: HashMap::default(),\n            first_leaf_level: None,\n            page: None,\n            freelist_count: CheckFreelist {\n                expected_count: 0,\n                actual_count: 0,\n            },\n        }\n    }\n\n    pub fn set_expected_freelist_count(&mut self, count: usize) {\n        self.freelist_count.expected_count = count;\n    }\n\n    pub fn start(\n        &mut self,\n        page_idx: i64,\n        page_category: PageCategory,\n        errors: &mut Vec<IntegrityCheckError>,\n    ) {\n        turso_assert!(\n            self.page_stack.is_empty(),\n            \"stack should be empty before integrity check for new root\"\n        );\n        self.first_leaf_level = None;\n        let _ = self.page.take();\n        // root can't be referenced from anywhere - so we insert \"zero entry\" for it\n        self.push_page(\n            IntegrityCheckPageEntry {\n                page_idx,\n                level: 0,\n                max_intkey: i64::MAX,\n                page_category,\n                overflow_pages_expected: None,\n                overflow_pages_seen: 0,\n            },\n            0,\n            errors,\n        );\n    }\n\n    fn push_page(\n        &mut self,\n        entry: IntegrityCheckPageEntry,\n        referenced_by: i64,\n        errors: &mut Vec<IntegrityCheckError>,\n    ) {\n        let page_id = entry.page_idx;\n        let Some(previous) = self.page_reference.insert(page_id, referenced_by) else {\n            self.page_stack.push(entry);\n            return;\n        };\n        errors.push(IntegrityCheckError::PageReferencedMultipleTimes {\n            page_id,\n            page_category: entry.page_category,\n            references: vec![previous, referenced_by],\n        });\n    }\n}\nimpl std::fmt::Debug for IntegrityCheckState {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"IntegrityCheckState\")\n            .field(\"first_leaf_level\", &self.first_leaf_level)\n            .finish()\n    }\n}\n\nfn overflow_pages_expected_for_cell(\n    payload_size: u64,\n    local_payload_size: usize,\n    usable_space: usize,\n) -> usize {\n    let payload_size = usize::try_from(payload_size).unwrap_or(usize::MAX);\n    let remaining_payload = payload_size.saturating_sub(local_payload_size);\n    if remaining_payload == 0 {\n        return 0;\n    }\n    let overflow_page_payload = usable_space.saturating_sub(4).max(1);\n    remaining_payload.div_ceil(overflow_page_payload)\n}\n\n/// Perform integrity check on a whole table/index. We check for:\n/// 1. Correct order of keys in case of rowids.\n/// 2. There are no overlap between cells.\n/// 3. Cells do not scape outside expected range.\n/// 4. Depth of leaf pages are equal.\n/// 5. Overflow pages are correct (TODO)\n///\n/// In order to keep this reentrant, we keep a stack of pages we need to check. Ideally, like in\n/// SQLlite, we would have implemented a recursive solution which would make it easier to check the\n/// depth.\npub fn integrity_check(\n    state: &mut IntegrityCheckState,\n    errors: &mut Vec<IntegrityCheckError>,\n    pager: &Arc<Pager>,\n    mv_store: Option<&Arc<MvStore>>,\n) -> Result<IOResult<()>> {\n    if let Some(mv_store) = mv_store {\n        let Some(IntegrityCheckPageEntry {\n            page_idx: root_page,\n            ..\n        }) = state.page_stack.last().cloned()\n        else {\n            panic!(\"Page stack is empty on integrity_check start\");\n        };\n        if root_page < 0 {\n            let table_id = mv_store.get_table_id_from_root_page(root_page);\n            turso_assert!(\n                !mv_store.is_btree_allocated(&table_id),\n                \"we got a negative page index that is reported as allocated\"\n            );\n            state.page_stack.pop();\n            return Ok(IOResult::Done(()));\n        }\n    }\n    if state.db_size == 0 {\n        state.page_stack.pop();\n        return Ok(IOResult::Done(()));\n    }\n    loop {\n        let Some(IntegrityCheckPageEntry {\n            page_idx,\n            page_category,\n            level,\n            max_intkey,\n            overflow_pages_expected,\n            overflow_pages_seen,\n        }) = state.page_stack.last().cloned()\n        else {\n            return Ok(IOResult::Done(()));\n        };\n        turso_assert!(\n            page_idx >= 0,\n            \"pages should be positive during integrity check\"\n        );\n        let page = match state.page.take() {\n            Some(page) => page,\n            None => {\n                let (page, c) = btree_read_page(pager, page_idx)?;\n                state.page = Some(page);\n                if let Some(c) = c {\n                    io_yield_one!(c);\n                }\n                state.page.take().expect(\"page should be present\")\n            }\n        };\n        turso_assert!(page.is_loaded(), \"page should be loaded\");\n        state.page_stack.pop();\n\n        let contents = page.get_contents();\n        if page_category == PageCategory::FreeListTrunk {\n            state.freelist_count.actual_count += 1;\n            let next_freelist_trunk_page =\n                contents.read_u32_no_offset(FREELIST_TRUNK_OFFSET_NEXT_TRUNK_PTR);\n            if next_freelist_trunk_page != 0 {\n                if next_freelist_trunk_page as usize > state.db_size {\n                    tracing::error!(\n                        \"integrity_check: freelist trunk page {} has invalid next pointer {}. header_bytes={:02x?}\",\n                        page.get().id,\n                        next_freelist_trunk_page,\n                        &contents.as_ptr()[0..16]\n                    );\n                    errors.push(IntegrityCheckError::FreelistPointerOutOfRange {\n                        page_id: page.get().id as i64,\n                        pointer: next_freelist_trunk_page as i64,\n                    });\n                    continue;\n                }\n                state.push_page(\n                    IntegrityCheckPageEntry {\n                        page_idx: next_freelist_trunk_page as i64,\n                        level,\n                        max_intkey,\n                        page_category: PageCategory::FreeListTrunk,\n                        overflow_pages_expected: None,\n                        overflow_pages_seen: 0,\n                    },\n                    page.get().id as i64,\n                    errors,\n                );\n            }\n            let page_pointers = contents.read_u32_no_offset(FREELIST_TRUNK_OFFSET_LEAF_COUNT);\n            let page_size = contents.as_ptr().len();\n            let max_pointers =\n                page_size.saturating_sub(FREELIST_TRUNK_HEADER_SIZE) / FREELIST_LEAF_PTR_SIZE;\n            if unlikely(page_pointers as usize > max_pointers) {\n                tracing::error!(\n                    \"integrity_check: freelist trunk page {} has invalid leaf count {} (max {}). header_bytes={:02x?}\",\n                    page.get().id,\n                    page_pointers,\n                    max_pointers,\n                    &contents.as_ptr()[0..16]\n                );\n                errors.push(IntegrityCheckError::FreelistTrunkCorrupt {\n                    page_id: page.get().id as i64,\n                    page_pointers,\n                    max_pointers,\n                });\n                continue;\n            }\n            for i in 0..page_pointers {\n                let offset =\n                    FREELIST_TRUNK_OFFSET_FIRST_LEAF_PTR + FREELIST_LEAF_PTR_SIZE * i as usize;\n                if unlikely(offset + FREELIST_LEAF_PTR_SIZE > page_size) {\n                    tracing::error!(\n                        \"integrity_check: freelist trunk page {} has invalid leaf offset {}. header_bytes={:02x?}\",\n                        page.get().id,\n                        offset,\n                        &contents.as_ptr()[0..16]\n                    );\n                    errors.push(IntegrityCheckError::FreelistTrunkCorrupt {\n                        page_id: page.get().id as i64,\n                        page_pointers,\n                        max_pointers,\n                    });\n                    break;\n                }\n                let page_pointer = contents.read_u32_no_offset(offset);\n                if page_pointer as usize > state.db_size {\n                    tracing::error!(\n                        \"integrity_check: freelist trunk page {} has invalid leaf pointer {}. header_bytes={:02x?}\",\n                        page.get().id,\n                        page_pointer,\n                        &contents.as_ptr()[0..16]\n                    );\n                    errors.push(IntegrityCheckError::FreelistPointerOutOfRange {\n                        page_id: page.get().id as i64,\n                        pointer: page_pointer as i64,\n                    });\n                    continue;\n                }\n                state.push_page(\n                    IntegrityCheckPageEntry {\n                        page_idx: page_pointer as i64,\n                        level,\n                        max_intkey,\n                        page_category: PageCategory::FreePage,\n                        overflow_pages_expected: None,\n                        overflow_pages_seen: 0,\n                    },\n                    page.get().id as i64,\n                    errors,\n                );\n            }\n            continue;\n        }\n        if page_category == PageCategory::FreePage {\n            state.freelist_count.actual_count += 1;\n            continue;\n        }\n        if page_category == PageCategory::Overflow {\n            let overflow_pages_seen = overflow_pages_seen.saturating_add(1);\n            let next_overflow_page = contents.read_u32_no_offset(0);\n            if next_overflow_page != 0 {\n                state.push_page(\n                    IntegrityCheckPageEntry {\n                        page_idx: next_overflow_page as i64,\n                        level,\n                        max_intkey,\n                        page_category: PageCategory::Overflow,\n                        overflow_pages_expected,\n                        overflow_pages_seen,\n                    },\n                    page.get().id as i64,\n                    errors,\n                );\n            } else if let Some(expected) = overflow_pages_expected {\n                if overflow_pages_seen != expected {\n                    errors.push(IntegrityCheckError::OverflowListLengthMismatch {\n                        got: overflow_pages_seen,\n                        expected,\n                    });\n                }\n            }\n            continue;\n        }\n\n        let usable_space = pager.usable_space();\n        let mut coverage_checker = CoverageChecker::new(page.get().id as i64);\n\n        // Now we check every cell for few things:\n        // 1. Check cell is in correct range. Not exceeds page and not starts before we have marked\n        //    (cell content area).\n        // 2. We add the cell to coverage checker in order to check if cells do not overlap.\n        // 3. We check order of rowids in case of table pages. We iterate backwards in order to check\n        //    if current cell's rowid is less than the next cell. We also check rowid is less than the\n        //    parent's divider cell. In case of this page being root page max rowid will be i64::MAX.\n        // 4. We append pages to the stack to check later.\n        // 5. In case of leaf page, check if the current level(depth) is equal to other leaf pages we\n        //    have seen.\n        let mut next_rowid = max_intkey;\n        for cell_idx in (0..contents.cell_count()).rev() {\n            let (cell_start, cell_length) = contents.cell_get_raw_region(cell_idx, usable_space)?;\n            if cell_start < contents.cell_content_area() as usize || cell_start > usable_space - 4 {\n                errors.push(IntegrityCheckError::CellOutOfRange {\n                    cell_idx,\n                    page_id: page.get().id as i64,\n                    cell_start,\n                    cell_end: cell_start + cell_length,\n                    content_area: contents.cell_content_area() as usize,\n                    usable_space,\n                });\n            }\n            if cell_start + cell_length > usable_space {\n                errors.push(IntegrityCheckError::CellOverflowsPage {\n                    cell_idx,\n                    page_id: page.get().id as i64,\n                    cell_start,\n                    cell_end: cell_start + cell_length,\n                    content_area: contents.cell_content_area() as usize,\n                    usable_space,\n                });\n            }\n            coverage_checker.add_cell(cell_start, cell_start + cell_length);\n            let cell = contents.cell_get(cell_idx, usable_space)?;\n            match cell {\n                BTreeCell::TableInteriorCell(table_interior_cell) => {\n                    state.push_page(\n                        IntegrityCheckPageEntry {\n                            page_idx: table_interior_cell.left_child_page as i64,\n                            level: level + 1,\n                            max_intkey: table_interior_cell.rowid,\n                            page_category: PageCategory::Normal,\n                            overflow_pages_expected: None,\n                            overflow_pages_seen: 0,\n                        },\n                        page.get().id as i64,\n                        errors,\n                    );\n                    let rowid = table_interior_cell.rowid;\n                    if rowid > max_intkey || rowid > next_rowid {\n                        errors.push(IntegrityCheckError::CellRowidOutOfRange {\n                            page_id: page.get().id as i64,\n                            page_category,\n                            cell_idx,\n                            rowid,\n                            max_intkey,\n                            next_rowid,\n                        });\n                    }\n                    next_rowid = rowid;\n                }\n                BTreeCell::TableLeafCell(table_leaf_cell) => {\n                    // check depth of leaf pages are equal\n                    if let Some(expected_leaf_level) = state.first_leaf_level {\n                        if expected_leaf_level != level {\n                            errors.push(IntegrityCheckError::LeafDepthMismatch {\n                                page_id: page.get().id as i64,\n                                this_page_depth: level,\n                                other_page_depth: expected_leaf_level,\n                            });\n                        }\n                    } else {\n                        state.first_leaf_level = Some(level);\n                    }\n                    let rowid = table_leaf_cell.rowid;\n                    if rowid > max_intkey || rowid > next_rowid {\n                        errors.push(IntegrityCheckError::CellRowidOutOfRange {\n                            page_id: page.get().id as i64,\n                            page_category,\n                            cell_idx,\n                            rowid,\n                            max_intkey,\n                            next_rowid,\n                        });\n                    }\n                    next_rowid = rowid;\n                    if let Some(first_overflow_page) = table_leaf_cell.first_overflow_page {\n                        let expected_pages = overflow_pages_expected_for_cell(\n                            table_leaf_cell.payload_size,\n                            table_leaf_cell.payload.len(),\n                            usable_space,\n                        );\n                        state.push_page(\n                            IntegrityCheckPageEntry {\n                                page_idx: first_overflow_page as i64,\n                                level,\n                                max_intkey,\n                                page_category: PageCategory::Overflow,\n                                overflow_pages_expected: Some(expected_pages),\n                                overflow_pages_seen: 0,\n                            },\n                            page.get().id as i64,\n                            errors,\n                        );\n                    }\n                }\n                BTreeCell::IndexInteriorCell(index_interior_cell) => {\n                    state.push_page(\n                        IntegrityCheckPageEntry {\n                            page_idx: index_interior_cell.left_child_page as i64,\n                            level: level + 1,\n                            max_intkey, // we don't care about intkey in non-table pages\n                            page_category: PageCategory::Normal,\n                            overflow_pages_expected: None,\n                            overflow_pages_seen: 0,\n                        },\n                        page.get().id as i64,\n                        errors,\n                    );\n                    if let Some(first_overflow_page) = index_interior_cell.first_overflow_page {\n                        let expected_pages = overflow_pages_expected_for_cell(\n                            index_interior_cell.payload_size,\n                            index_interior_cell.payload.len(),\n                            usable_space,\n                        );\n                        state.push_page(\n                            IntegrityCheckPageEntry {\n                                page_idx: first_overflow_page as i64,\n                                level,\n                                max_intkey,\n                                page_category: PageCategory::Overflow,\n                                overflow_pages_expected: Some(expected_pages),\n                                overflow_pages_seen: 0,\n                            },\n                            page.get().id as i64,\n                            errors,\n                        );\n                    }\n                }\n                BTreeCell::IndexLeafCell(index_leaf_cell) => {\n                    // check depth of leaf pages are equal\n                    if let Some(expected_leaf_level) = state.first_leaf_level {\n                        if expected_leaf_level != level {\n                            errors.push(IntegrityCheckError::LeafDepthMismatch {\n                                page_id: page.get().id as i64,\n                                this_page_depth: level,\n                                other_page_depth: expected_leaf_level,\n                            });\n                        }\n                    } else {\n                        state.first_leaf_level = Some(level);\n                    }\n                    if let Some(first_overflow_page) = index_leaf_cell.first_overflow_page {\n                        let expected_pages = overflow_pages_expected_for_cell(\n                            index_leaf_cell.payload_size,\n                            index_leaf_cell.payload.len(),\n                            usable_space,\n                        );\n                        state.push_page(\n                            IntegrityCheckPageEntry {\n                                page_idx: first_overflow_page as i64,\n                                level,\n                                max_intkey,\n                                page_category: PageCategory::Overflow,\n                                overflow_pages_expected: Some(expected_pages),\n                                overflow_pages_seen: 0,\n                            },\n                            page.get().id as i64,\n                            errors,\n                        );\n                    }\n                }\n            }\n        }\n\n        if let Some(rightmost) = contents.rightmost_pointer()? {\n            state.push_page(\n                IntegrityCheckPageEntry {\n                    page_idx: rightmost as i64,\n                    level: level + 1,\n                    max_intkey,\n                    page_category: PageCategory::Normal,\n                    overflow_pages_expected: None,\n                    overflow_pages_seen: 0,\n                },\n                page.get().id as i64,\n                errors,\n            );\n        }\n\n        // Now we add free blocks to the coverage checker\n        let first_freeblock = contents.first_freeblock() as usize;\n        if first_freeblock > 0 {\n            let mut pc = first_freeblock;\n            while pc > 0 {\n                let next = contents.read_u16_no_offset(pc as usize) as usize;\n                let size = contents.read_u16_no_offset(pc as usize + 2) as usize;\n                // check it doesn't go out of range\n                if pc > usable_space - 4 {\n                    errors.push(IntegrityCheckError::FreeBlockOutOfRange {\n                        page_id: page.get().id as i64,\n                        start: pc,\n                        end: pc + size,\n                    });\n                    break;\n                }\n                coverage_checker.add_free_block(pc, pc + size);\n                pc = next;\n            }\n        }\n\n        // Let's check the overlap of freeblocks and cells now that we have collected them all.\n        coverage_checker.analyze(\n            usable_space,\n            contents.cell_content_area() as usize,\n            errors,\n            contents.num_frag_free_bytes() as usize,\n        );\n    }\n}\n\npub fn btree_read_page(pager: &Arc<Pager>, page_idx: i64) -> Result<(PageRef, Option<Completion>)> {\n    pager.read_page(page_idx)\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\nstruct IntegrityCheckCellRange {\n    start: usize,\n    end: usize,\n    is_free_block: bool,\n}\n\n// Implement ordering for min-heap (smallest start address first)\nimpl Ord for IntegrityCheckCellRange {\n    fn cmp(&self, other: &Self) -> std::cmp::Ordering {\n        self.start.cmp(&other.start)\n    }\n}\n\nimpl PartialOrd for IntegrityCheckCellRange {\n    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {\n        Some(self.cmp(other))\n    }\n}\n\n#[cfg(debug_assertions)]\nfn validate_cells_after_insertion(cell_array: &CellArray, leaf_data: bool) {\n    for cell in &cell_array.cell_payloads {\n        turso_assert_greater_than_or_equal!(cell.len(), 4);\n\n        if leaf_data {\n            turso_assert!(cell[0] != 0);\n        }\n    }\n}\n\npub struct CoverageChecker {\n    /// Min-heap ordered by cell start\n    heap: BinaryHeap<Reverse<IntegrityCheckCellRange>>,\n    page_idx: i64,\n}\n\nimpl CoverageChecker {\n    pub fn new(page_idx: i64) -> Self {\n        Self {\n            heap: BinaryHeap::new(),\n            page_idx,\n        }\n    }\n\n    fn add_range(&mut self, cell_start: usize, cell_end: usize, is_free_block: bool) {\n        self.heap.push(Reverse(IntegrityCheckCellRange {\n            start: cell_start,\n            end: cell_end,\n            is_free_block,\n        }));\n    }\n\n    pub fn add_cell(&mut self, cell_start: usize, cell_end: usize) {\n        self.add_range(cell_start, cell_end, false);\n    }\n\n    pub fn add_free_block(&mut self, cell_start: usize, cell_end: usize) {\n        self.add_range(cell_start, cell_end, true);\n    }\n\n    pub fn analyze(\n        &mut self,\n        usable_space: usize,\n        content_area: usize,\n        errors: &mut Vec<IntegrityCheckError>,\n        expected_fragmentation: usize,\n    ) {\n        let mut fragmentation = 0;\n        let mut prev_end = content_area;\n        while let Some(cell) = self.heap.pop() {\n            let start = cell.0.start;\n            if prev_end > start {\n                errors.push(IntegrityCheckError::CellOverlap {\n                    page_id: self.page_idx,\n                    start,\n                    prev_end,\n                    content_area,\n                    is_free_block: cell.0.is_free_block,\n                });\n                break;\n            } else {\n                fragmentation += start - prev_end;\n                prev_end = cell.0.end;\n            }\n        }\n        fragmentation += usable_space - prev_end;\n        if fragmentation != expected_fragmentation {\n            errors.push(IntegrityCheckError::UnexpectedFragmentation {\n                page_id: self.page_idx,\n                got: fragmentation,\n                expected: expected_fragmentation,\n            });\n        }\n    }\n}\n\n/// Stack of pages representing the tree traversal order.\n/// current_page represents the current page being used in the tree and current_page - 1 would be\n/// the parent. Using current_page + 1 or higher is undefined behaviour.\nstruct PageStack {\n    /// Pointer to the current page being consumed\n    current_page: i32,\n    /// List of pages in the stack. Root page will be in index 0\n    pub stack: [Option<PageRef>; BTCURSOR_MAX_DEPTH + 1],\n    /// List of cell indices in the stack.\n    /// node_states[current_page] is the current cell index being consumed. Similarly\n    /// node_states[current_page-1] is the cell index of the parent of the current page\n    /// that we save in case of going back up.\n    /// There are two points that need special attention:\n    ///  If node_states[current_page] = -1, it indicates that the current iteration has reached the start of the current_page\n    ///  If node_states[current_page] = `cell_count`, it means that the current iteration has reached the end of the current_page\n    node_states: [BTreeNodeState; BTCURSOR_MAX_DEPTH + 1],\n}\n\nimpl PageStack {\n    /// Push a new page onto the stack.\n    /// This effectively means traversing to a child page.\n    #[cfg_attr(debug_assertions, instrument(skip_all, level = Level::DEBUG, name = \"pagestack::push\"))]\n    fn _push(&mut self, page: PageRef, starting_cell_idx: i32) {\n        tracing::trace!(current = self.current_page, new_page_id = page.get().id,);\n        'validate: {\n            let current = self.current_page;\n            if current == -1 {\n                break 'validate;\n            }\n            let current_top = self.stack[current as usize].as_ref();\n            if let Some(current_top) = current_top {\n                turso_assert!(\n                    current_top.get().id != page.get().id,\n                    \"about to push page twice\",\n                    { \"page_id\": page.get().id }\n                );\n            }\n        }\n        self.populate_parent_cell_count();\n        self.current_page += 1;\n        turso_assert_greater_than_or_equal!(self.current_page, 0);\n        let current = self.current_page as usize;\n        turso_assert_less_than!(\n            current,\n            BTCURSOR_MAX_DEPTH,\n            \"corrupted database, stack is bigger than expected\"\n        );\n\n        // Pin the page to prevent it from being evicted while on the stack\n        page.pin();\n\n        self.stack[current] = Some(page);\n        self.node_states[current] = BTreeNodeState {\n            cell_idx: starting_cell_idx,\n            cell_count: None, // we don't know the cell count yet, so we set it to None. any code pushing a child page onto the stack MUST set the parent page's cell_count.\n        };\n    }\n\n    /// Populate the parent page's cell count.\n    /// This is needed so that we can, from a child page, check of ancestor pages' position relative to its cell index\n    /// without having to perform IO to get the ancestor page contents.\n    ///\n    /// This rests on the assumption that the parent page is already in memory whenever a child is pushed onto the stack.\n    /// We currently ensure this by pinning all the pages on [PageStack] to the page cache so that they cannot be evicted.\n    fn populate_parent_cell_count(&mut self) {\n        let stack_empty = self.current_page == -1;\n        if stack_empty {\n            return;\n        }\n        let current = self.current();\n        let page = self.stack[current].as_ref().unwrap();\n        turso_assert!(\n            page.is_pinned(),\n            \"parent page is not pinned\",\n            { \"page_id\": page.get().id }\n        );\n        turso_assert!(\n            page.is_loaded(),\n            \"parent page is not loaded\",\n            { \"page_id\": page.get().id }\n        );\n        let contents = page.get_contents();\n        let cell_count = contents.cell_count() as i32;\n        self.node_states[current].cell_count = Some(cell_count);\n    }\n\n    fn push(&mut self, page: PageRef) {\n        self._push(page, -1);\n    }\n\n    fn push_backwards(&mut self, page: PageRef) {\n        self._push(page, i32::MAX);\n    }\n\n    /// Pop a page off the stack.\n    /// This effectively means traversing back up to a parent page.\n    #[cfg_attr(debug_assertions, instrument(skip_all, level = Level::DEBUG, name = \"pagestack::pop\"))]\n    fn pop(&mut self) {\n        let current = self.current_page;\n        turso_assert_greater_than_or_equal!(current, 0);\n        tracing::trace!(current);\n        let current = current as usize;\n\n        // Unpin the page before removing it from the stack\n        if let Some(page) = &self.stack[current] {\n            page.unpin();\n        }\n\n        turso_assert_greater_than!(current, 0);\n        self.node_states[current] = BTreeNodeState::default();\n        self.stack[current] = None;\n        self.current_page -= 1;\n    }\n\n    /// Get the top page on the stack.\n    /// This is the page that is currently being traversed.\n    fn top(&self) -> PageRef {\n        let current = self.current();\n        let page = self.stack[current].clone().unwrap();\n        turso_assert!(page.is_loaded(), \"page should be loaded\");\n        page\n    }\n\n    fn top_ref(&self) -> &PageRef {\n        let current = self.current();\n        let page = self.stack[current].as_ref().unwrap();\n        turso_assert!(page.is_loaded(), \"page should be loaded\");\n        page\n    }\n\n    /// Current page pointer being used\n    #[inline(always)]\n    fn current(&self) -> usize {\n        turso_assert_greater_than_or_equal!(self.current_page, 0);\n        self.current_page as usize\n    }\n\n    /// Cell index of the current page\n    fn current_cell_index(&self) -> i32 {\n        let current = self.current();\n        self.node_states[current].cell_idx\n    }\n\n    /// Check if the current cell index is less than 0.\n    /// This means we have been iterating backwards and have reached the start of the page.\n    fn current_cell_index_less_than_min(&self) -> bool {\n        let cell_idx = self.current_cell_index();\n        cell_idx < 0\n    }\n\n    /// Advance the current cell index of the current page to the next cell.\n    /// We usually advance after going traversing a new page\n    #[inline(always)]\n    fn advance(&mut self) {\n        let current = self.current();\n        self.node_states[current].cell_idx += 1;\n    }\n\n    #[cfg_attr(debug_assertions, instrument(skip(self), level = Level::DEBUG, name = \"pagestack::retreat\"))]\n    fn retreat(&mut self) {\n        let current = self.current();\n        #[cfg(debug_assertions)]\n        {\n            tracing::trace!(\n                curr_cell_index = self.node_states[current].cell_idx,\n                node_states = ?self.node_states.iter().map(|state| state.cell_idx).collect::<Vec<_>>(),\n            );\n        }\n        self.node_states[current].cell_idx -= 1;\n    }\n\n    fn set_cell_index(&mut self, idx: i32) {\n        let current = self.current();\n        self.node_states[current].cell_idx = idx;\n    }\n\n    fn has_parent(&self) -> bool {\n        self.current_page > 0\n    }\n\n    /// Get a page at a specific level in the stack (0 = root, 1 = first child, etc.)\n    fn get_page_at_level(&self, level: usize) -> Option<&PageRef> {\n        if level < self.stack.len() {\n            self.stack[level].as_ref()\n        } else {\n            None\n        }\n    }\n\n    fn get_page_contents_at_level(&self, level: usize) -> Option<&mut PageContent> {\n        self.get_page_at_level(level)\n            .map(|page| page.get_contents())\n    }\n\n    fn unpin_all_if_pinned(&mut self) {\n        self.stack.iter_mut().flatten().for_each(|page| {\n            let _ = page.try_unpin();\n        });\n    }\n\n    fn clear(&mut self) {\n        self.unpin_all_if_pinned();\n\n        self.current_page = -1;\n    }\n}\n\nimpl Drop for PageStack {\n    fn drop(&mut self) {\n        self.unpin_all_if_pinned();\n    }\n}\n\n/// Used for redistributing cells during a balance operation.\nstruct CellArray {\n    /// The actual cell data.\n    /// For all other page types except table leaves, this will also contain the associated divider cell from the parent page.\n    cell_payloads: Vec<&'static mut [u8]>,\n\n    /// Prefix sum of cells in each page.\n    /// For example, if three pages have 1, 2, and 3 cells, respectively,\n    /// then cell_count_per_page_cumulative will be [1, 3, 6].\n    cell_count_per_page_cumulative: [u16; MAX_NEW_SIBLING_PAGES_AFTER_BALANCE],\n}\n\nimpl std::fmt::Debug for CellArray {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"CellArray\").finish()\n    }\n}\n\nimpl CellArray {\n    pub fn cell_size_bytes(&self, cell_idx: usize) -> u16 {\n        self.cell_payloads[cell_idx].len() as u16\n    }\n\n    /// Returns the number of cells up to and including the given page.\n    pub fn cell_count_up_to_page(&self, page_idx: usize) -> usize {\n        self.cell_count_per_page_cumulative[page_idx] as usize\n    }\n}\n\n/// Try to find a freeblock inside the cell content area that is large enough to fit the given amount of bytes.\n/// Used to check if a cell can be inserted into a freeblock to reduce fragmentation.\n/// Returns the absolute byte offset of the freeblock if found.\nfn find_free_slot(\n    page_ref: &PageContent,\n    usable_space: usize,\n    amount: usize,\n) -> Result<Option<usize>> {\n    const CELL_SIZE_MIN: usize = 4;\n    // NOTE: freelist is in ascending order of keys and pc\n    // unuse_space is reserved bytes at the end of page, therefore we must substract from maxpc\n    let mut prev_block = None;\n    let mut cur_block = match page_ref.first_freeblock() {\n        0 => None,\n        first_block => Some(first_block as usize),\n    };\n\n    let max_start_offset = usable_space - amount;\n\n    while let Some(cur) = cur_block {\n        if unlikely(cur + CELL_SIZE_MIN > usable_space) {\n            return_corrupt!(\"Free block header extends beyond page\");\n        }\n\n        let (next, size) = {\n            let cur_u16: u16 = cur\n                .try_into()\n                .unwrap_or_else(|_| panic!(\"cur={cur} is too large to fit in a u16\"));\n            let (next, size) = page_ref.read_freeblock(cur_u16);\n            (next as usize, size as usize)\n        };\n\n        // Doesn't fit in this freeblock, try the next one.\n        if amount > size {\n            if next == 0 {\n                // No next -> can't fit.\n                return Ok(None);\n            }\n\n            prev_block = cur_block;\n            if unlikely(next <= cur) {\n                return_corrupt!(\"Free list not in ascending order\");\n            }\n            cur_block = Some(next);\n            continue;\n        }\n\n        let new_size = size - amount;\n        // If the freeblock's new size is < CELL_SIZE_MIN, the freeblock is deleted and the remaining bytes\n        // become fragmented free bytes.\n        if new_size < CELL_SIZE_MIN {\n            if page_ref.num_frag_free_bytes() > 57 {\n                // SQLite has a fragmentation limit of 60 bytes.\n                // check sqlite docs https://www.sqlite.org/fileformat.html#:~:text=A%20freeblock%20requires,not%20exceed%2060\n                return Ok(None);\n            }\n            // Delete the slot from freelist and update the page's fragment count.\n            match prev_block {\n                Some(prev) => {\n                    let prev_u16: u16 = prev\n                        .try_into()\n                        .unwrap_or_else(|_| panic!(\"prev={prev} is too large to fit in a u16\"));\n                    let next_u16: u16 = next\n                        .try_into()\n                        .unwrap_or_else(|_| panic!(\"next={next} is too large to fit in a u16\"));\n                    page_ref.write_freeblock_next_ptr(prev_u16, next_u16);\n                }\n                None => {\n                    let next_u16: u16 = next\n                        .try_into()\n                        .unwrap_or_else(|_| panic!(\"next={next} is too large to fit in a u16\"));\n                    page_ref.write_first_freeblock(next_u16);\n                }\n            }\n            let new_size_u8: u8 = new_size\n                .try_into()\n                .unwrap_or_else(|_| panic!(\"new_size={new_size} is too large to fit in a u8\"));\n            let frag = page_ref.num_frag_free_bytes() + new_size_u8;\n            page_ref.write_fragmented_bytes_count(frag);\n            return Ok(cur_block);\n        } else if unlikely(new_size + cur > max_start_offset) {\n            return_corrupt!(\"Free block extends beyond page end\");\n        } else {\n            // Requested amount fits inside the current free slot so we reduce its size\n            // to account for newly allocated space.\n            let cur_u16: u16 = cur\n                .try_into()\n                .unwrap_or_else(|_| panic!(\"cur={cur} is too large to fit in a u16\"));\n            let new_size_u16: u16 = new_size\n                .try_into()\n                .unwrap_or_else(|_| panic!(\"new_size={new_size} is too large to fit in a u16\"));\n            page_ref.write_freeblock_size(cur_u16, new_size_u16);\n            // Return the offset immediately after the shrunk freeblock.\n            return Ok(Some(cur + new_size));\n        }\n    }\n\n    Ok(None)\n}\n\npub fn btree_init_page(page: &PageRef, page_type: PageType, offset: usize, usable_space: usize) {\n    // setup btree page\n    let contents = page.get_contents();\n    tracing::debug!(\n        \"btree_init_page(id={}, offset={}, usable_space={})\",\n        page.get().id,\n        offset,\n        usable_space\n    );\n    #[cfg(debug_assertions)]\n    //TODO restore format args (as the \"details\" last arg)\n    turso_assert_eq!(\n        offset,\n        contents.offset(),\n        \"offset doesn't match computed offset for page\"\n    );\n    let id = page_type as u8;\n    contents.write_page_type(id);\n    contents.write_first_freeblock(0);\n    contents.write_cell_count(0);\n\n    contents.write_cell_content_area(usable_space);\n\n    contents.write_fragmented_bytes_count(0);\n    contents.write_rightmost_ptr(0);\n\n    #[cfg(debug_assertions)]\n    {\n        // we might get already used page from the pool. generally this is not a problem because\n        // b tree access is very controlled. However, for encrypted pages (and also checksums) we want\n        // to ensure that there are no reserved bytes that contain old data.\n        let buf = contents.as_ptr();\n        let buffer_len = buf.len();\n        turso_assert!(\n            usable_space <= buffer_len,\n            \"usable_space must be <= buffer_len\"\n        );\n        // this is no op if usable_space == buffer_len\n        buf[usable_space..buffer_len].fill(0);\n    }\n}\n\nfn to_static_buf(buf: &mut [u8]) -> &'static mut [u8] {\n    unsafe { std::mem::transmute::<&mut [u8], &'static mut [u8]>(buf) }\n}\n\nfn edit_page(\n    page: &mut PageContent,\n    start_old_cells: usize,\n    start_new_cells: usize,\n    number_new_cells: usize,\n    cell_array: &CellArray,\n    usable_space: usize,\n) -> Result<()> {\n    tracing::debug!(\n        \"edit_page start_old_cells={} start_new_cells={} number_new_cells={} cell_array={}\",\n        start_old_cells,\n        start_new_cells,\n        number_new_cells,\n        cell_array.cell_payloads.len()\n    );\n    let end_old_cells = start_old_cells + page.cell_count() + page.overflow_cells.len();\n    let end_new_cells = start_new_cells + number_new_cells;\n    let mut count_cells = page.cell_count();\n    if start_old_cells < start_new_cells {\n        debug_validate_cells!(page, usable_space);\n        let number_to_shift = page_free_array(\n            page,\n            start_old_cells,\n            start_new_cells - start_old_cells,\n            cell_array,\n            usable_space,\n        )?;\n        // shift pointers left\n        shift_cells_left(page, count_cells, number_to_shift);\n        count_cells -= number_to_shift;\n        debug_validate_cells!(page, usable_space);\n    }\n    if end_new_cells < end_old_cells {\n        debug_validate_cells!(page, usable_space);\n        let number_tail_removed = page_free_array(\n            page,\n            end_new_cells,\n            end_old_cells - end_new_cells,\n            cell_array,\n            usable_space,\n        )?;\n        turso_assert_greater_than_or_equal!(count_cells, number_tail_removed);\n        count_cells -= number_tail_removed;\n        debug_validate_cells!(page, usable_space);\n    }\n    // TODO: make page_free_array defragment, for now I'm lazy so this will work for now.\n    let mut defragmented_page = defragment_page_for_insert(page, usable_space, 0)?;\n    // TODO: add to start\n    if start_new_cells < start_old_cells {\n        let count = number_new_cells.min(start_old_cells - start_new_cells);\n        page_insert_array(\n            &mut defragmented_page,\n            start_new_cells,\n            count,\n            cell_array,\n            0,\n            usable_space,\n        )?;\n        count_cells += count;\n    }\n    // TODO: overflow cells\n    debug_validate_cells!(defragmented_page.0, usable_space);\n    for i in 0..defragmented_page.0.overflow_cells.len() {\n        let overflow_cell = &defragmented_page.0.overflow_cells[i];\n        // cell index in context of new list of cells that should be in the page\n        if start_old_cells + overflow_cell.index >= start_new_cells {\n            let cell_idx = start_old_cells + overflow_cell.index - start_new_cells;\n            if cell_idx < number_new_cells {\n                count_cells += 1;\n                page_insert_array(\n                    &mut defragmented_page,\n                    start_new_cells + cell_idx,\n                    1,\n                    cell_array,\n                    cell_idx,\n                    usable_space,\n                )?;\n            }\n        }\n    }\n    debug_validate_cells!(defragmented_page.0, usable_space);\n    // TODO: append cells to end\n    page_insert_array(\n        &mut defragmented_page,\n        start_new_cells + count_cells,\n        number_new_cells - count_cells,\n        cell_array,\n        count_cells,\n        usable_space,\n    )?;\n    debug_validate_cells!(defragmented_page.0, usable_space);\n    // TODO: noverflow\n    page.write_cell_count(number_new_cells as u16);\n    Ok(())\n}\n\n/// Shifts the cell pointers in the B-tree page to the left by a specified number of positions.\n///\n/// # Parameters\n/// - `page`: A mutable reference to the `PageContent` representing the B-tree page.\n/// - `count_cells`: The total number of cells currently in the page.\n/// - `number_to_shift`: The number of cell pointers to shift to the left.\n///\n/// # Behavior\n/// This function modifies the cell pointer array within the page by copying memory regions.\n/// It shifts the pointers starting from `number_to_shift` to the beginning of the array,\n/// effectively removing the first `number_to_shift` pointers.\nfn shift_cells_left(page: &mut PageContent, count_cells: usize, number_to_shift: usize) {\n    let buf = page.as_ptr();\n    let (start, _) = page.cell_pointer_array_offset_and_size();\n    buf.copy_within(\n        start + (number_to_shift * 2)..start + (count_cells * 2),\n        start,\n    );\n}\n\nfn page_free_array(\n    page: &mut PageContent,\n    first: usize,\n    count: usize,\n    cell_array: &CellArray,\n    usable_space: usize,\n) -> Result<usize> {\n    tracing::debug!(\"page_free_array {}..{}\", first, first + count);\n    let buf = &mut page.as_ptr()[page.offset()..usable_space];\n    let buf_range = buf.as_ptr_range();\n    let mut number_of_cells_removed = 0;\n    let mut number_of_cells_buffered = 0;\n    let mut buffered_cells_offsets: [usize; 10] = [0; 10];\n    let mut buffered_cells_ends: [usize; 10] = [0; 10];\n    for i in first..first + count {\n        let cell = &cell_array.cell_payloads[i];\n        let cell_pointer = cell.as_ptr_range();\n        // check if not overflow cell\n        if cell_pointer.start >= buf_range.start && cell_pointer.start < buf_range.end {\n            turso_assert!(\n                cell_pointer.end >= buf_range.start && cell_pointer.end <= buf_range.end,\n                \"whole cell should be inside the page\"\n            );\n            // TODO: remove pointer too\n            let offset = cell_pointer.start as usize - buf_range.start as usize;\n            let len = cell_pointer.end as usize - cell_pointer.start as usize;\n            turso_assert_greater_than!(len, 0, \"cell size should be greater than 0\");\n            let end = offset + len;\n\n            /* Try to merge the current cell with a contiguous buffered cell to reduce the number of\n             * `free_cell_range()` operations. Break on the first merge to avoid consuming too much time,\n             * `free_cell_range()` will try to merge contiguous cells anyway. */\n            let mut j = 0;\n            while j < number_of_cells_buffered {\n                // If the buffered cell is immediately after the current cell\n                if buffered_cells_offsets[j] == end {\n                    // Merge them by updating the buffered cell's offset to the current cell's offset\n                    buffered_cells_offsets[j] = offset;\n                    break;\n                // If the buffered cell is immediately before the current cell\n                } else if buffered_cells_ends[j] == offset {\n                    // Merge them by updating the buffered cell's end offset to the current cell's end offset\n                    buffered_cells_ends[j] = end;\n                    break;\n                }\n                j += 1;\n            }\n            // If no cells were merged\n            if j >= number_of_cells_buffered {\n                // If the buffered cells array is full, flush the buffered cells using `free_cell_range()` to empty the array\n                if number_of_cells_buffered >= buffered_cells_offsets.len() {\n                    for j in 0..number_of_cells_buffered {\n                        free_cell_range(\n                            page,\n                            buffered_cells_offsets[j],\n                            buffered_cells_ends[j] - buffered_cells_offsets[j],\n                            usable_space,\n                        )?;\n                    }\n                    number_of_cells_buffered = 0; // Reset array counter\n                }\n                // Buffer the current cell\n                buffered_cells_offsets[number_of_cells_buffered] = offset;\n                buffered_cells_ends[number_of_cells_buffered] = end;\n                number_of_cells_buffered += 1;\n            }\n            number_of_cells_removed += 1;\n        }\n    }\n    for j in 0..number_of_cells_buffered {\n        free_cell_range(\n            page,\n            buffered_cells_offsets[j],\n            buffered_cells_ends[j] - buffered_cells_offsets[j],\n            usable_space,\n        )?;\n    }\n    page.write_cell_count(page.cell_count() as u16 - number_of_cells_removed as u16);\n    Ok(number_of_cells_removed)\n}\n\n/// A proof type that guarantees a page has been defragmented.\n///\n/// This type can only be constructed by calling [`defragment_page_for_insert`],\n/// which ensures the page has been defragmented before any insert operations.\n/// Functions like [`page_insert_array`] require this type to enforce at compile-time\n/// that defragmentation has occurred.\npub struct DefragmentedPage<'a>(&'a mut PageContent);\n\nimpl std::ops::Deref for DefragmentedPage<'_> {\n    type Target = PageContent;\n\n    #[inline]\n    fn deref(&self) -> &Self::Target {\n        self.0\n    }\n}\n\nimpl std::ops::DerefMut for DefragmentedPage<'_> {\n    #[inline]\n    fn deref_mut(&mut self) -> &mut Self::Target {\n        self.0\n    }\n}\n\n/// Insert multiple cells into a page in a single batch operation.\n///\n/// This is an optimized version that avoids O(N²) complexity by:\n/// 1. Computing total space needed upfront\n/// 2. Allocating all space at once\n/// 3. Copying all cell payloads sequentially\n/// 4. Shifting existing cell pointers once\n/// 5. Writing all new cell pointers in one pass\n/// 6. Updating cell count once\nfn page_insert_array(\n    page: &mut DefragmentedPage,\n    first: usize,\n    count: usize,\n    cell_array: &CellArray,\n    start_insert: usize,\n    _usable_space: usize,\n) -> Result<()> {\n    if count == 0 {\n        return Ok(());\n    }\n\n    tracing::debug!(\n        \"page_insert_array(first={}, count={}, start_insert={}, cell_count={}, page_type={:?})\",\n        first,\n        count,\n        start_insert,\n        page.cell_count(),\n        page.page_type().ok()\n    );\n\n    turso_assert!(first <= cell_array.cell_payloads.len(), \"first OOB\");\n    turso_assert!(\n        count <= cell_array.cell_payloads.len().saturating_sub(first),\n        \"first+count OOB\"\n    );\n    // Calculate total space needed for all cell payloads\n    // We read from cell_array at indices [first, first+count)\n    let mut total_payload_size: usize = 0;\n    for i in 0..count {\n        let payload = &cell_array.cell_payloads[first + i];\n        let cell_size = payload.len().max(MINIMUM_CELL_SIZE);\n        total_payload_size += cell_size;\n    }\n\n    // Total space needed includes cell pointers\n    let total_ptr_space = count.checked_mul(CELL_PTR_SIZE_BYTES).ok_or_else(|| {\n        mark_unlikely();\n        LimboError::Corrupt(\"page_insert_array: ptr space overflow\".into())\n    })?;\n\n    // After defragmentation, all free space is in the unallocated region\n    // between the cell pointer array and the cell content area.\n    let current_cell_count = page.cell_count();\n    let mut cell_content_area = page.cell_content_area() as usize;\n    let unallocated_start = page.unallocated_region_start();\n    turso_assert!(\n        start_insert <= current_cell_count,\n        \"start_insert beyond cell_count\"\n    );\n    turso_assert!(\n        // we cast to u16 later so assert no overflow\n        current_cell_count + count <= u16::MAX as usize,\n        \"cell_count overflow\"\n    );\n    // Verify we have enough space\n    // The new cell pointers will extend the cell pointer array by `total_ptr_space`\n    // The new cell content will reduce cell_content_area by `total_payload_size`\n    let new_unallocated_start =\n        unallocated_start\n            .checked_add(total_ptr_space)\n            .ok_or_else(|| {\n                mark_unlikely();\n                LimboError::Corrupt(\"page_insert_array: unalloc start overflow\".into())\n            })?;\n    let new_cell_content_area = cell_content_area\n        .checked_sub(total_payload_size)\n        .ok_or_else(|| {\n            mark_unlikely();\n            LimboError::Corrupt(\"page_insert_array: payload underflow\".to_string())\n        })?;\n\n    turso_assert!(\n        new_unallocated_start <= new_cell_content_area,\n        \"page_insert_array: not enough space for pointers and payloads in unallocated region\",\n        { \"total_ptr_space\": total_ptr_space, \"total_payload_size\": total_payload_size, \"unallocated_start\": unallocated_start, \"cell_content_area\": cell_content_area, \"unallocated_region_size\": cell_content_area - unallocated_start }\n    );\n\n    let buf = page.as_ptr();\n    let (cell_pointer_array_start, _) = page.cell_pointer_array_offset_and_size();\n\n    // Shift existing cell pointers to make room for new ones\n    // We're inserting `count` cells at position `start_insert`, so we need to shift\n    // all cell pointers from position `start_insert` onwards to the right by `count * 2` bytes\n    if start_insert < current_cell_count {\n        let cells_to_shift = current_cell_count - start_insert;\n        let shift_src_start = cell_pointer_array_start + (start_insert * CELL_PTR_SIZE_BYTES);\n        let shift_dst_start = shift_src_start + total_ptr_space;\n        let shift_size = cells_to_shift * CELL_PTR_SIZE_BYTES;\n        buf.copy_within(\n            shift_src_start..shift_src_start + shift_size,\n            shift_dst_start,\n        );\n    }\n\n    // Allocate space for all cells and write payloads + pointers\n    // We allocate space from the content area (which grows downward)\n    // Read from cell_array[first..first+count], insert at page positions [start_insert..start_insert+count]\n    for i in 0..count {\n        let payload = &cell_array.cell_payloads[first + i];\n        let cell_size = payload.len().max(MINIMUM_CELL_SIZE);\n\n        // Allocate space for this cell (grow content area downward)\n        cell_content_area = cell_content_area.checked_sub(cell_size).ok_or_else(|| {\n            mark_unlikely();\n            LimboError::Corrupt(\"page_insert_array: cell allocation underflow\".to_string())\n        })?;\n\n        // Copy cell payload\n        buf[cell_content_area..cell_content_area + payload.len()].copy_from_slice(payload);\n\n        // Write cell pointer at position (start_insert + i)\n        let ptr_offset = cell_pointer_array_start + ((start_insert + i) * CELL_PTR_SIZE_BYTES);\n        page.write_u16_no_offset(ptr_offset, cell_content_area as u16);\n    }\n\n    // Update page header\n    page.write_cell_content_area(cell_content_area);\n    page.write_cell_count((current_cell_count + count) as u16);\n\n    debug_validate_cells!(page, _usable_space);\n    Ok(())\n}\n\n/// Free the range of bytes that a cell occupies.\n/// This function also updates the freeblock list in the page.\n/// Freeblocks are used to keep track of free space in the page,\n/// and are organized as a linked list.\n///\n/// This function may merge the freed cell range into either the next freeblock,\n/// previous freeblock, or both.\nfn free_cell_range(\n    page: &mut PageContent,\n    mut offset: usize,\n    len: usize,\n    usable_space: usize,\n) -> Result<()> {\n    const CELL_SIZE_MIN: usize = 4;\n    if unlikely(len < CELL_SIZE_MIN) {\n        return_corrupt!(\"free_cell_range: minimum cell size is {CELL_SIZE_MIN}\");\n    }\n    if unlikely(offset > usable_space.saturating_sub(CELL_SIZE_MIN)) {\n        return_corrupt!(\"free_cell_range: start offset beyond usable space: offset={offset} usable_space={usable_space}\");\n    }\n\n    let mut size = len;\n    let mut end = offset + len;\n    if unlikely(end > usable_space) {\n        return_corrupt!(\"free_cell_range: freed range extends beyond usable space: offset={offset} len={len} end={end} usable_space={usable_space}\");\n    }\n    let cur_content_area = page.cell_content_area() as usize;\n    let first_block = page.first_freeblock() as usize;\n    if first_block == 0 {\n        if unlikely(offset < cur_content_area) {\n            return_corrupt!(\"free_cell_range: free block before content area: offset={offset} cell_content_area={cur_content_area}\");\n        }\n        if offset == cur_content_area {\n            // if the freeblock list is empty and the freed range is exactly at the beginning of the content area,\n            // we are not creating a freeblock; instead we are just extending the unallocated region.\n            page.write_cell_content_area(end);\n        } else {\n            // otherwise we set it as the first freeblock in the page header.\n            let offset_u16: u16 = offset\n                .try_into()\n                .unwrap_or_else(|_| panic!(\"offset={offset} is too large to fit in a u16\"));\n            page.write_first_freeblock(offset_u16);\n            let size_u16: u16 = size\n                .try_into()\n                .unwrap_or_else(|_| panic!(\"size={size} is too large to fit in a u16\"));\n            page.write_freeblock(offset_u16, size_u16, None);\n        }\n        return Ok(());\n    }\n\n    // if the freeblock list is not empty, we need to find the correct position to insert the new freeblock\n    // resulting from the freeing of this cell range; we may be also able to merge the freed range into existing freeblocks.\n    let mut prev_block = None;\n    let mut next_block = Some(first_block);\n\n    while let Some(next) = next_block {\n        if unlikely(prev_block.is_some_and(|prev| next <= prev)) {\n            return_corrupt!(\"free_cell_range: freeblocks not in ascending order: next_block={next} prev_block={prev_block:?}\");\n        }\n        if next >= offset {\n            break;\n        }\n        prev_block = Some(next);\n        next_block = match page.read_u16_no_offset(next) {\n            // Freed range extends beyond the last freeblock, so we are creating a new freeblock.\n            0 => None,\n            next => Some(next as usize),\n        };\n    }\n\n    if let Some(next) = next_block {\n        if unlikely(next + CELL_SIZE_MIN > usable_space) {\n            return_corrupt!(\"free_cell_range: free block beyond usable space: next_block={next} usable_space={usable_space}\");\n        }\n    }\n    let mut removed_fragmentation = 0;\n    const SINGLE_FRAGMENT_SIZE_MAX: usize = CELL_SIZE_MIN - 1;\n\n    // If the freed range extends into the next freeblock, we will merge the freed range into it.\n    // If there is a 1-3 byte gap between the freed range and the next freeblock, we are effectively\n    // clearing that amount of fragmented bytes, since a 1-3 byte range cannot be a valid cell.\n    if let Some(next) = next_block {\n        if end + SINGLE_FRAGMENT_SIZE_MAX >= next {\n            removed_fragmentation = (next - end) as u8;\n            let next_size = page.read_u16_no_offset(next + 2) as usize;\n            end = next + next_size;\n            if unlikely(end > usable_space) {\n                return_corrupt!(\"free_cell_range: coalesced block extends beyond page: offset={offset} len={len} end={end} usable_space={usable_space}\");\n            }\n            size = end - offset;\n            // Since we merged the two freeblocks, we need to update the next_block to the next freeblock in the list.\n            next_block = match page.read_u16_no_offset(next) {\n                0 => None,\n                next => Some(next as usize),\n            };\n        }\n    }\n\n    // If the freed range extends into the previous freeblock, we will merge them similarly as above.\n    if let Some(prev) = prev_block {\n        let prev_size = page.read_u16_no_offset(prev + 2) as usize;\n        let prev_end = prev + prev_size;\n        if unlikely(prev_end > offset) {\n            return_corrupt!(\n                \"free_cell_range: previous block overlap: prev_end={prev_end} offset={offset}\"\n            );\n        }\n        // If the previous freeblock extends into the freed range, we will merge the freed range into the\n        // previous freeblock and clear any 1-3 byte fragmentation in between, similarly as above\n        if prev_end + SINGLE_FRAGMENT_SIZE_MAX >= offset {\n            removed_fragmentation += (offset - prev_end) as u8;\n            size = end - prev;\n            offset = prev;\n        }\n    }\n\n    let cur_frag_free_bytes = page.num_frag_free_bytes();\n    if unlikely(removed_fragmentation > cur_frag_free_bytes) {\n        return_corrupt!(\"free_cell_range: invalid fragmentation count: removed_fragmentation={removed_fragmentation} num_frag_free_bytes={cur_frag_free_bytes}\");\n    }\n    let frag = cur_frag_free_bytes - removed_fragmentation;\n    page.write_fragmented_bytes_count(frag);\n\n    if unlikely(offset < cur_content_area) {\n        return_corrupt!(\"free_cell_range: free block before content area: offset={offset} cell_content_area={cur_content_area}\");\n    }\n\n    // As above, if the freed range is exactly at the beginning of the content area, we are not creating a freeblock;\n    // instead we are just extending the unallocated region.\n    if offset == cur_content_area {\n        if unlikely(prev_block.is_some_and(|prev| prev != first_block)) {\n            return_corrupt!(\"free_cell_range: invalid content area merge - freed range should have been merged with previous freeblock: prev={prev_block:?} first_block={first_block}\");\n        }\n        // If we get here, we are freeing data from the left end of the content area,\n        // so we are extending the unallocated region instead of creating a freeblock.\n        // We update the first freeblock to be the next one, and shrink the content area to start from the end\n        // of the freed range.\n        match next_block {\n            Some(next) => {\n                if unlikely(next <= end) {\n                    return_corrupt!(\"free_cell_range: invalid content area merge - first freeblock should either be 0 or greater than the content area start: next_block={next} end={end}\");\n                }\n                let next_u16: u16 = next\n                    .try_into()\n                    .unwrap_or_else(|_| panic!(\"next={next} is too large to fit in a u16\"));\n                page.write_first_freeblock(next_u16);\n            }\n            None => {\n                page.write_first_freeblock(0);\n            }\n        }\n        page.write_cell_content_area(end);\n    } else {\n        // If we are creating a new freeblock:\n        // a) if it's the first one, we update the header to indicate so,\n        // b) if it's not the first one, we update the previous freeblock to point to the new one,\n        //    and the new one to point to the next one.\n        let offset_u16: u16 = offset\n            .try_into()\n            .unwrap_or_else(|_| panic!(\"offset={offset} is too large to fit in a u16\"));\n        if let Some(prev) = prev_block {\n            page.write_u16_no_offset(prev, offset_u16);\n        } else {\n            page.write_first_freeblock(offset_u16);\n        }\n        let size_u16: u16 = size\n            .try_into()\n            .unwrap_or_else(|_| panic!(\"size={size} is too large to fit in a u16\"));\n        let next_block_u16 = next_block.map(|b| {\n            b.try_into()\n                .unwrap_or_else(|_| panic!(\"next_block={b} is too large to fit in a u16\"))\n        });\n        page.write_freeblock(offset_u16, size_u16, next_block_u16);\n    }\n\n    Ok(())\n}\n\n/// This function handles pages with two or fewer freeblocks and max_frag_bytes (parameter to defragment_page())\n/// or fewer fragmented bytes. In this case it is faster to move the two (or one)\n/// blocks of cells using memmove() and add the required offsets to each pointer\n/// in the cell-pointer array than it is to reconstruct the entire page.\n/// Note that this function will leave max_frag_bytes as is, it will not try to reduce it.\nfn defragment_page_fast(\n    page: &PageContent,\n    usable_space: usize,\n    freeblock_1st: usize,\n    freeblock_2nd: usize,\n) -> Result<()> {\n    if unlikely(freeblock_1st == 0) {\n        return_corrupt!(\"defragment_page_fast: expected at least one freeblock\");\n    }\n    if unlikely(freeblock_2nd > 0 && freeblock_1st >= freeblock_2nd) {\n        return_corrupt!(\n            \"defragment_page_fast: first freeblock must be before second freeblock: freeblock_1st={freeblock_1st} freeblock_2nd={freeblock_2nd}\"\n        );\n    }\n    const FREEBLOCK_SIZE_MIN: usize = 4;\n    if unlikely(freeblock_1st > usable_space - FREEBLOCK_SIZE_MIN) {\n        return_corrupt!(\n            \"defragment_page_fast: first freeblock beyond usable space: freeblock_1st={freeblock_1st} usable_space={usable_space}\"\n        );\n    }\n    if unlikely(freeblock_2nd > usable_space - FREEBLOCK_SIZE_MIN) {\n        return_corrupt!(\n            \"defragment_page_fast: second freeblock beyond usable space: freeblock_2nd={freeblock_2nd} usable_space={usable_space}\"\n        );\n    }\n\n    let freeblock_1st_size = page.read_u16_no_offset(freeblock_1st + 2) as usize;\n    let freeblock_2nd_size = if freeblock_2nd > 0 {\n        page.read_u16_no_offset(freeblock_2nd + 2) as usize\n    } else {\n        0\n    };\n    let freeblocks_total_size = freeblock_1st_size + freeblock_2nd_size;\n\n    let cell_content_area = page.cell_content_area() as usize;\n\n    if freeblock_2nd > 0 {\n        // If there's 2 freeblocks, merge them into one first.\n        if unlikely(freeblock_1st + freeblock_1st_size > freeblock_2nd) {\n            return_corrupt!(\n                \"defragment_page_fast: overlapping freeblocks: freeblock_1st={freeblock_1st} freeblock_1st_size={freeblock_1st_size} freeblock_2nd={freeblock_2nd}\"\n            );\n        }\n        if unlikely(freeblock_2nd + freeblock_2nd_size > usable_space) {\n            return_corrupt!(\n                \"defragment_page_fast: second freeblock extends beyond usable space: freeblock_2nd={freeblock_2nd} freeblock_2nd_size={freeblock_2nd_size} usable_space={usable_space}\"\n            );\n        }\n        let buf = page.as_ptr();\n        // Effectively moves everything in between the two freeblocks rightwards by the length of the 2nd freeblock,\n        // so that the first freeblock size becomes `freeblocks_total_size` (merging the two freeblocks)\n        // and the second freeblock gets overwritten by non-free cell data.\n        // Illustrative doodle:\n        // | content area start |--cell content A--| 1st free |--cell content B--| 2nd free |--cell content C--|\n        // ->\n        // | content area start |--cell content A--|      merged free    |--cell content B--|--cell content C--|\n        let after_first_freeblock = freeblock_1st + freeblock_1st_size;\n        let copy_amount = freeblock_2nd - after_first_freeblock;\n        buf.copy_within(\n            after_first_freeblock..after_first_freeblock + copy_amount,\n            freeblock_1st + freeblocks_total_size,\n        );\n    } else if unlikely(freeblock_1st + freeblock_1st_size > usable_space) {\n        return_corrupt!(\n            \"defragment_page_fast: first freeblock extends beyond usable space: freeblock_1st={freeblock_1st} freeblock_1st_size={freeblock_1st_size} usable_space={usable_space}\"\n        );\n    }\n\n    // Now we have one freeblock somewhere in the middle of the content area, e.g.:\n    // content area start |-----------| merged freeblock |-----------|\n    // By moving the cells from the left of the merged free block to where the merged freeblock was, we effectively move the freeblock to the very left end of the content area,\n    // meaning, it's no longer a freeblock, it's just plain old free space.\n    // content area start | free space | ----------- cells ----------|\n    let new_cell_content_area = cell_content_area + freeblocks_total_size;\n    if unlikely(new_cell_content_area + (freeblock_1st - cell_content_area) > usable_space) {\n        return_corrupt!(\n            \"defragment_page_fast: new cell content area extends beyond usable space: new_cell_content_area={new_cell_content_area} freeblock_1st={freeblock_1st} cell_content_area={cell_content_area} usable_space={usable_space}\"\n        );\n    }\n\n    let copy_amount = freeblock_1st - cell_content_area; // cells to the left of the first freeblock\n    let buf = page.as_ptr();\n    buf.copy_within(\n        cell_content_area..cell_content_area + copy_amount,\n        new_cell_content_area,\n    );\n\n    // Freeblocks are now erased since the free space is at the beginning, but we must update the cell pointer array to point to the right locations.\n    let cell_count = page.cell_count();\n    let cell_pointer_array_offset = page.cell_pointer_array_offset_and_size().0;\n    for i in 0..cell_count {\n        let ptr_offset = cell_pointer_array_offset + (i * CELL_PTR_SIZE_BYTES);\n        let cell_ptr = page.read_u16_no_offset(ptr_offset) as usize;\n        if cell_ptr < freeblock_1st {\n            // If the cell pointer was located before the first freeblock, we need to shift it right by the size of the merged freeblock\n            // since the space occupied by both the 1st and 2nd freeblocks was now moved to its left.\n            let new_offset = cell_ptr + freeblocks_total_size;\n            if unlikely(new_offset > usable_space) {\n                return_corrupt!(\n                    \"defragment_page_fast: shifted cell pointer beyond usable space: new_offset={new_offset} usable_space={usable_space}\"\n                );\n            }\n            page.write_u16_no_offset(ptr_offset, (cell_ptr + freeblocks_total_size) as u16);\n        } else if freeblock_2nd > 0 && cell_ptr < freeblock_2nd {\n            // If the cell pointer was located between the first and second freeblock, we need to shift it right by the size of only the second freeblock,\n            // since the first one was already on its left.\n            let new_offset = cell_ptr + freeblock_2nd_size;\n            if unlikely(new_offset > usable_space) {\n                return_corrupt!(\n                    \"defragment_page_fast: shifted cell pointer beyond usable space: new_offset={new_offset} usable_space={usable_space}\"\n                );\n            }\n            page.write_u16_no_offset(ptr_offset, (cell_ptr + freeblock_2nd_size) as u16);\n        }\n    }\n\n    // Update page header\n    page.write_cell_content_area(new_cell_content_area);\n    page.write_first_freeblock(0);\n\n    debug_validate_cells!(page, usable_space);\n\n    Ok(())\n}\n\n/// Defragment a page, and never use the fast-path algorithm.\nfn defragment_page_full(page: &PageContent, usable_space: usize) -> Result<()> {\n    defragment_page(page, usable_space, -1)\n}\n\n/// Defragment a page and return a proof that can be used with [`page_insert_array`].\n///\n/// This is the entry point for defragmentation when you need to perform insert\n/// operations afterward. The returned [`DefragmentedPage`] proves at compile-time\n/// that defragmentation has occurred.\n///\n/// For defragmentation without the type-state proof (e.g., in `allocate_cell_space`),\n/// use [`defragment_page`] directly.\n#[inline]\nfn defragment_page_for_insert(\n    page: &mut PageContent,\n    usable_space: usize,\n    max_frag_bytes: isize,\n) -> Result<DefragmentedPage<'_>> {\n    defragment_page(page, usable_space, max_frag_bytes)?;\n    Ok(DefragmentedPage(page))\n}\n\n/// Defragment a page. This means packing all the cells to the end of the page.\nfn defragment_page(page: &PageContent, usable_space: usize, max_frag_bytes: isize) -> Result<()> {\n    debug_validate_cells!(page, usable_space);\n    tracing::debug!(\"defragment_page (optimized in-place)\");\n\n    let cell_count = page.cell_count();\n    if cell_count == 0 {\n        page.write_cell_content_area(usable_space);\n        page.write_first_freeblock(0);\n        page.write_fragmented_bytes_count(0);\n        debug_validate_cells!(page, usable_space);\n        return Ok(());\n    }\n\n    // Use fast algorithm if there are at most 2 freeblocks and the total fragmented free space is less than max_frag_bytes.\n    if page.num_frag_free_bytes() as isize <= max_frag_bytes {\n        let freeblock_1st = page.first_freeblock() as usize;\n        if freeblock_1st == 0 {\n            // No freeblocks and very little if any fragmented free bytes -> no need to defragment.\n            return Ok(());\n        }\n        let freeblock_2nd = page.read_u16_no_offset(freeblock_1st) as usize;\n        if freeblock_2nd == 0 {\n            return defragment_page_fast(page, usable_space, freeblock_1st, 0);\n        }\n        let freeblock_3rd = page.read_u16_no_offset(freeblock_2nd) as usize;\n        if freeblock_3rd == 0 {\n            return defragment_page_fast(page, usable_space, freeblock_1st, freeblock_2nd);\n        }\n    }\n\n    // A small struct to hold cell metadata for sorting.\n    // Size: 2 + 2 + 8 = 12 bytes, with alignment likely 16 bytes.\n    #[derive(Clone, Copy)]\n    struct CellInfo {\n        old_offset: u16,\n        size: u16,\n        pointer_index: usize,\n    }\n\n    // Use stack allocation for the common case (most pages have <256 cells).\n    // This avoids heap allocation in the hot path.\n    // MAX_STACK_CELLS * 16 bytes = 4KB of stack space.\n    const MAX_STACK_CELLS: usize = 256;\n\n    // Helper function to process cells and defragment the page.\n    // This is generic over the slice type to work with both stack and heap storage.\n    #[inline]\n    fn process_cells(\n        page: &PageContent,\n        usable_space: usize,\n        cells: &mut [CellInfo],\n        is_physically_sorted: bool,\n    ) -> Result<()> {\n        if !is_physically_sorted {\n            // Sort cells by old physical offset in descending order.\n            // Using unstable sort is fine as the original order doesn't matter.\n            cells.sort_unstable_by(|a, b| b.old_offset.cmp(&a.old_offset));\n        }\n\n        // Get direct mutable access to the page buffer.\n        let buffer = page.as_ptr();\n        let cell_pointer_area_offset = page.cell_pointer_array_offset();\n        let first_cell_content_byte = page.unallocated_region_start();\n\n        // Move data and update pointers.\n        let mut cbrk = usable_space;\n        for cell in cells.iter() {\n            cbrk -= cell.size as usize;\n            let new_offset = cbrk;\n            let old_offset = cell.old_offset as usize;\n\n            // Basic corruption check\n            turso_assert!(\n                new_offset >= first_cell_content_byte && old_offset + cell.size as usize <= usable_space,\n                \"corrupt page detected during defragmentation\",\n                { \"new_offset\": new_offset, \"first_cell_content_byte\": first_cell_content_byte, \"old_offset\": old_offset, \"cell_size\": cell.size, \"usable_space\": usable_space }\n            );\n\n            // Move the cell data. `copy_within` is the idiomatic and safe\n            // way to perform a `memmove` operation on a slice.\n            if new_offset != old_offset {\n                let src_range = old_offset..(old_offset + cell.size as usize);\n                buffer.copy_within(src_range, new_offset);\n            }\n\n            // Update the pointer in the cell pointer array to the new offset.\n            let pointer_location = cell_pointer_area_offset + (cell.pointer_index * 2);\n            turso_assert!(\n                new_offset < PageSize::MAX as usize,\n                \"new_offset exceeds PageSize::MAX\",\n                { \"new_offset\": new_offset, \"page_size_max\": PageSize::MAX }\n            );\n            page.write_u16_no_offset(pointer_location, new_offset as u16);\n        }\n\n        page.write_cell_content_area(cbrk);\n        page.write_first_freeblock(0);\n        page.write_fragmented_bytes_count(0);\n        Ok(())\n    }\n\n    // Gather cell metadata.\n    let cell_offset = page.cell_pointer_array_offset();\n    let mut is_physically_sorted = true;\n    let mut last_offset = u16::MAX;\n\n    // Pre-compute page-level constants for cell_get_raw_region_faster.\n    // These are the same for all cells on the page, so computing them once\n    // avoids redundant work in the loop.\n    let page_type = page.page_type()?;\n    let max_local = payload_overflow_threshold_max(page_type, usable_space);\n    let min_local = payload_overflow_threshold_min(page_type, usable_space);\n\n    let mut cells = SmallVec::<[CellInfo; MAX_STACK_CELLS]>::with_capacity(cell_count);\n    for i in 0..cell_count {\n        let pc = page.read_u16_no_offset(cell_offset + (i * 2));\n        let (_, size) = page._cell_get_raw_region_faster(\n            i,\n            usable_space,\n            cell_count,\n            max_local,\n            min_local,\n            page_type,\n        )?;\n\n        if pc > last_offset {\n            is_physically_sorted = false;\n        }\n        last_offset = pc;\n\n        cells.push(CellInfo {\n            old_offset: pc,\n            size: size as u16,\n            pointer_index: i,\n        });\n    }\n\n    process_cells(page, usable_space, &mut cells, is_physically_sorted)?;\n    debug_validate_cells!(page, usable_space);\n    Ok(())\n}\n\n#[cfg(debug_assertions)]\n/// Only enabled in debug mode, where we ensure that all cells are valid.\nfn debug_validate_cells_core(page: &PageContent, usable_space: usize) {\n    for i in 0..page.cell_count() {\n        let (offset, size) = page.cell_get_raw_region(i, usable_space).unwrap();\n        let _buf = &page.as_ptr()[offset..offset + size];\n        // E.g. the following table btree cell may just have two bytes:\n        // Payload size 0 (stored as SerialTypeKind::ConstInt0)\n        // Rowid 1 (stored as SerialTypeKind::ConstInt1)\n        turso_assert_greater_than_or_equal!(\n            size, 2,\n            \"cell size should be at least 2 bytes\",\n            { \"idx\": i, \"offset\": offset, \"buf\": _buf }\n        );\n        if page.is_leaf() {\n            turso_assert!(page.as_ptr()[offset] != 0);\n        }\n        turso_assert_less_than_or_equal!(\n            offset + size,\n            usable_space,\n            \"cell spans out of usable space\"\n        );\n    }\n}\n\n/// Insert a record into a cell.\n/// If the cell overflows, an overflow cell is created.\n/// insert_into_cell() is called from insert_into_page(),\n/// and the overflow cell count is used to determine if the page overflows,\n/// i.e. whether we need to balance the btree after the insert.\nfn _insert_into_cell(\n    page: &mut PageContent,\n    payload: &[u8],\n    cell_idx: usize,\n    usable_space: usize,\n    allow_regular_insert_despite_overflow: bool, // used during balancing to allow regular insert despite overflow cells\n) -> Result<()> {\n    turso_assert_less_than_or_equal!(\n        cell_idx, page.cell_count() + page.overflow_cells.len(),\n        \"attempting to add cell to incorrect place\",\n        { \"cell_idx\": cell_idx, \"cell_count\": page.cell_count(), \"overflow_count\": page.overflow_cells.len(), \"page_type\": format!(\"{:?}\", page.page_type()) }\n    );\n    let already_has_overflow = !page.overflow_cells.is_empty();\n    let free = compute_free_space(page, usable_space)?;\n    let enough_space = if already_has_overflow && !allow_regular_insert_despite_overflow {\n        false\n    } else {\n        // otherwise, we need to check if we have enough space\n        payload.len() + CELL_PTR_SIZE_BYTES <= free\n    };\n    if !enough_space {\n        #[cfg(debug_assertions)]\n        {\n            if let Some(overflow_cell) = page.overflow_cells.last() {\n                turso_assert!(overflow_cell.index + 1 == cell_idx, \"multiple overflow cells can only occur when a parent overflows during balancing as divider cells are inserted into it. those cells should always be in-order and sequential\");\n            }\n        }\n        page.overflow_cells.push(OverflowCell {\n            index: cell_idx,\n            payload: Pin::new(Vec::from(payload)),\n        });\n        return Ok(());\n    }\n    turso_assert_less_than_or_equal!(\n        cell_idx,\n        page.cell_count(),\n        \"cell_idx > cell_count without overflow cells\"\n    );\n\n    let new_cell_data_pointer = allocate_cell_space(page, payload.len(), usable_space, free)?;\n    tracing::debug!(\n        \"insert_into_cell(idx={}, pc={}, size={})\",\n        cell_idx,\n        new_cell_data_pointer,\n        payload.len()\n    );\n    turso_assert_less_than_or_equal!(new_cell_data_pointer as usize + payload.len(), usable_space);\n    let buf = page.as_ptr();\n\n    // copy data\n    buf[new_cell_data_pointer as usize..new_cell_data_pointer as usize + payload.len()]\n        .copy_from_slice(payload);\n    //  memmove(pIns+2, pIns, 2*(pPage->nCell - i));\n    let (cell_pointer_array_start, _) = page.cell_pointer_array_offset_and_size();\n    let cell_pointer_cur_idx = cell_pointer_array_start + (CELL_PTR_SIZE_BYTES * cell_idx);\n\n    // move existing pointers forward by CELL_PTR_SIZE_BYTES...\n    let n_cells_forward = page.cell_count() - cell_idx;\n    let n_bytes_forward = CELL_PTR_SIZE_BYTES * n_cells_forward;\n    if n_bytes_forward > 0 {\n        buf.copy_within(\n            cell_pointer_cur_idx..cell_pointer_cur_idx + n_bytes_forward,\n            cell_pointer_cur_idx + CELL_PTR_SIZE_BYTES,\n        );\n    }\n    // ...and insert new cell pointer at the current index\n    page.write_u16_no_offset(cell_pointer_cur_idx, new_cell_data_pointer);\n\n    // update cell count\n    let new_n_cells = (page.cell_count() + 1) as u16;\n    page.write_cell_count(new_n_cells);\n    debug_validate_cells!(page, usable_space);\n    Ok(())\n}\n\nfn insert_into_cell(\n    page: &mut PageContent,\n    payload: &[u8],\n    cell_idx: usize,\n    usable_space: usize,\n) -> Result<()> {\n    _insert_into_cell(page, payload, cell_idx, usable_space, false)\n}\n\n/// Normally in [insert_into_cell()], if a page already has overflow cells, all\n/// new insertions are also added to the overflow cells vector.\n/// The amount of free space is the sum of:\n///  #1. The size of the unallocated region\n///  #2. Fragments (isolated 1-3 byte chunks of free space within the cell content area)\n///  #3. freeblocks (linked list of blocks of at least 4 bytes within the cell content area that\n///      are not in use due to e.g. deletions)\n/// Free blocks can be zero, meaning the \"real free space\" that can be used to allocate is expected\n/// to be between first cell byte and end of cell pointer area.\n#[allow(unused_assignments)]\n#[inline]\nfn compute_free_space(page: &PageContent, usable_space: usize) -> Result<usize> {\n    // TODO(pere): maybe free space is not calculated correctly with offset\n\n    // Usable space, not the same as free space, simply means:\n    // space that is not reserved for extensions by sqlite. Usually reserved_space is 0.\n\n    let first_cell = page.offset() + page.header_size() + (2 * page.cell_count());\n    if unlikely(first_cell > usable_space) {\n        return_corrupt!(\n            \"compute_free_space: first_cell beyond usable space: first_cell={first_cell} usable_space={usable_space}\"\n        );\n    }\n\n    let cell_content_area_start = page.cell_content_area() as usize;\n    if unlikely(cell_content_area_start > usable_space) {\n        return_corrupt!(\n            \"compute_free_space: cell content area beyond usable space: cell_content_area_start={cell_content_area_start} usable_space={usable_space}\"\n        );\n    }\n\n    let mut free_space_bytes = cell_content_area_start + page.num_frag_free_bytes() as usize;\n\n    // #3 is computed by iterating over the freeblocks linked list\n    let mut cur_freeblock_ptr = page.first_freeblock() as usize;\n    if cur_freeblock_ptr > 0 {\n        if unlikely(cur_freeblock_ptr < cell_content_area_start) {\n            return_corrupt!(\n                \"compute_free_space: first freeblock before content area: first_freeblock={cur_freeblock_ptr} cell_content_area_start={cell_content_area_start}\"\n            );\n        }\n\n        let mut next = 0usize;\n        let mut size = 0usize;\n        loop {\n            if unlikely(cur_freeblock_ptr + 4 > usable_space) {\n                return_corrupt!(\n                    \"compute_free_space: freeblock header out of bounds: cur_freeblock_ptr={cur_freeblock_ptr} usable_space={usable_space}\"\n                );\n            }\n            next = page.read_u16_no_offset(cur_freeblock_ptr) as usize; // first 2 bytes in freeblock = next freeblock pointer\n            size = page.read_u16_no_offset(cur_freeblock_ptr + 2) as usize; // next 2 bytes in freeblock = size of current freeblock\n            if unlikely(size < 4) {\n                return_corrupt!(\n                    \"compute_free_space: freeblock too small: cur_freeblock_ptr={cur_freeblock_ptr} size={size}\"\n                );\n            }\n            if unlikely(cur_freeblock_ptr + size > usable_space) {\n                return_corrupt!(\n                    \"compute_free_space: freeblock extends beyond page: cur_freeblock_ptr={cur_freeblock_ptr} size={size} usable_space={usable_space}\"\n                );\n            }\n            free_space_bytes += size;\n\n            if next == 0 {\n                break;\n            }\n            // Freeblocks are in order from left to right on the page.\n            if unlikely(next <= cur_freeblock_ptr + size + 3) {\n                return_corrupt!(\n                    \"compute_free_space: freeblocks list not in ascending order: cur_freeblock_ptr={cur_freeblock_ptr} size={size} next={next}\"\n                );\n            }\n            cur_freeblock_ptr = next;\n        }\n    }\n\n    if unlikely(free_space_bytes > usable_space) {\n        return_corrupt!(\n            \"compute_free_space: free space greater than usable space: free_space_bytes={free_space_bytes} usable_space={usable_space}\"\n        );\n    }\n    if unlikely(free_space_bytes < first_cell) {\n        return_corrupt!(\n            \"compute_free_space: free space underflow: free_space_bytes={free_space_bytes} first_cell={first_cell} usable_space={usable_space}\"\n        );\n    }\n\n    Ok(free_space_bytes - first_cell)\n}\n\n/// Allocate space for a cell on a page.\n#[inline]\nfn allocate_cell_space(\n    page_ref: &PageContent,\n    mut amount: usize,\n    usable_space: usize,\n    free_space: usize,\n) -> Result<u16> {\n    if amount < MINIMUM_CELL_SIZE {\n        amount = MINIMUM_CELL_SIZE;\n    }\n\n    let unallocated_region_start = page_ref.unallocated_region_start();\n    let mut cell_content_area_start = page_ref.cell_content_area() as usize;\n\n    // there are free blocks and enough space to fit a new 2-byte cell pointer\n    if page_ref.first_freeblock() != 0\n        && unallocated_region_start + CELL_PTR_SIZE_BYTES <= cell_content_area_start\n    {\n        // find slot\n        if let Some(pc) = find_free_slot(page_ref, usable_space, amount)? {\n            // we can fit the cell in a freeblock.\n            return Ok(pc as u16);\n        }\n        /* fall through, we might need to defragment */\n    }\n\n    // We know at this point that we have no freeblocks in the middle of the cell content area\n    // that can fit the cell, but we do know we have enough space to _somehow_ fit it.\n    // The check below sees whether we can just put the cell in the unallocated region.\n    if unallocated_region_start + CELL_PTR_SIZE_BYTES + amount > cell_content_area_start {\n        // There's no room in the unallocated region, so we need to defragment.\n        // max_frag_bytes is a parameter to defragment_page() that controls whether we are able to use\n        // the fast-path defragmentation. The calculation here is done to see whether we can merge 1-2 freeblocks\n        // and move them to the unallocated region and fit the cell that way.\n        // Basically: if we have exactly enough space for the cell and the cell pointer on the page,\n        // we cannot have any fragmented space because then the freeblocks would not fit the cell.\n        let max_frag_bytes = 4.min(free_space as isize - (CELL_PTR_SIZE_BYTES + amount) as isize);\n        defragment_page(page_ref, usable_space, max_frag_bytes)?;\n        cell_content_area_start = page_ref.cell_content_area() as usize;\n    }\n\n    // insert the cell -> content area start moves left by that amount.\n    cell_content_area_start -= amount;\n    page_ref.write_cell_content_area(cell_content_area_start);\n\n    turso_assert_less_than_or_equal!(cell_content_area_start + amount, usable_space);\n    // we can just return the start of the cell content area, since the cell is inserted to the very left of the cell content area.\n    Ok(cell_content_area_start as u16)\n}\n\n#[derive(Debug, Clone)]\npub enum FillCellPayloadState {\n    /// Determine whether we can fit the record on the current page.\n    /// If yes, return immediately after copying the data.\n    /// Otherwise move to [CopyData] state.\n    Start,\n    /// Copy the next chunk of data from the record buffer to the cell payload.\n    /// If we can't fit all of the remaining data on the current page,\n    /// move the internal state to [CopyDataState::AllocateOverflowPage]\n    CopyData {\n        /// Internal state of the copy data operation.\n        /// We can either be copying data or allocating an overflow page.\n        state: CopyDataState,\n        /// Track how much space we have left on the current page we are copying data into.\n        /// This is reset whenever a new overflow page is allocated.\n        space_left_on_cur_page: usize,\n        /// Offset into the record buffer to copy from.\n        src_data_offset: usize,\n        /// Offset into the destination buffer we are copying data into.\n        /// This is either:\n        /// - an offset in the btree page where the cell is, or\n        /// - an offset in an overflow page\n        dst_data_offset: usize,\n        /// If this is Some, we will copy data into this overflow page.\n        /// If this is None, we will copy data into the cell payload on the btree page.\n        /// Also: to safely form a chain of overflow pages, the current page must be pinned to the page cache\n        /// so that e.g. a spilling operation does not evict it to disk.\n        current_overflow_page: Option<PinGuard>,\n    },\n}\n\n#[derive(Debug, Clone, PartialEq, Eq, Copy)]\npub enum CopyDataState {\n    /// Copy the next chunk of data from the record buffer to the cell payload.\n    Copy,\n    /// Allocate a new overflow page if we couldn't fit all data to the current page.\n    AllocateOverflowPage,\n}\n\n/// Fill in the cell payload with the record.\n/// If the record is too large to fit in the cell, it will spill onto overflow pages.\n/// This function needs a separate [FillCellPayloadState] because allocating overflow pages\n/// may require I/O.\n#[allow(clippy::too_many_arguments)]\nfn fill_cell_payload(\n    page: &PinGuard,\n    int_key: Option<i64>,\n    cell_payload: &mut Vec<u8>,\n    cell_idx: usize,\n    record: &ImmutableRecord,\n    usable_space: usize,\n    pager: Arc<Pager>,\n    fill_cell_payload_state: &mut FillCellPayloadState,\n) -> Result<IOResult<()>> {\n    let overflow_page_pointer_size = 4;\n    let overflow_page_data_size = usable_space - overflow_page_pointer_size;\n    let result = loop {\n        let record_buf = record.get_payload();\n        match fill_cell_payload_state {\n            FillCellPayloadState::Start => {\n                let page_contents = page.get_contents();\n\n                let page_type = page_contents.page_type()?;\n                // fill in header\n                if matches!(page_type, PageType::IndexInterior) {\n                    // if a write happened on an index interior page, it is always an overwrite.\n                    // we must copy the left child pointer of the replaced cell to the new cell.\n                    let left_child_page =\n                        page_contents.cell_interior_read_left_child_page(cell_idx)?;\n                    cell_payload.extend_from_slice(&left_child_page.to_be_bytes());\n                }\n                if matches!(page_type, PageType::TableLeaf) {\n                    let int_key = int_key.unwrap();\n                    write_varint_to_vec(record_buf.len() as u64, cell_payload);\n                    write_varint_to_vec(int_key as u64, cell_payload);\n                } else {\n                    write_varint_to_vec(record_buf.len() as u64, cell_payload);\n                }\n\n                let max_local = payload_overflow_threshold_max(page_type, usable_space);\n                let min_local = payload_overflow_threshold_min(page_type, usable_space);\n\n                let (overflows, local_size_if_overflow) =\n                    payload_overflows(record_buf.len(), max_local, min_local, usable_space);\n                if !overflows {\n                    // enough allowed space to fit inside a btree page\n                    cell_payload.extend_from_slice(record_buf.as_ref());\n                    break Ok(IOResult::Done(()));\n                }\n\n                // so far we've written any of: left child page, rowid, payload size (depending on page type)\n                let cell_non_payload_elems_size = cell_payload.len();\n                let new_total_local_size = cell_non_payload_elems_size + local_size_if_overflow;\n                cell_payload.resize(new_total_local_size, 0);\n\n                *fill_cell_payload_state = FillCellPayloadState::CopyData {\n                    state: CopyDataState::Copy,\n                    space_left_on_cur_page: local_size_if_overflow - overflow_page_pointer_size, // local_size_if_overflow includes the overflow page pointer, but we don't want to write payload data there.\n                    src_data_offset: 0,\n                    dst_data_offset: cell_non_payload_elems_size,\n                    current_overflow_page: None,\n                };\n                continue;\n            }\n            FillCellPayloadState::CopyData {\n                state,\n                src_data_offset,\n                space_left_on_cur_page,\n                dst_data_offset,\n                current_overflow_page,\n            } => {\n                match state {\n                    CopyDataState::Copy => {\n                        turso_assert!(*src_data_offset < record_buf.len(), \"trying to read past end of record buffer\", { \"src_data_offset\": src_data_offset, \"record_buf_len\": record_buf.len() });\n                        let record_offset_slice = &record_buf[*src_data_offset..];\n                        let amount_to_copy =\n                            (*space_left_on_cur_page).min(record_offset_slice.len());\n                        let record_offset_slice_to_copy = &record_offset_slice[..amount_to_copy];\n                        if let Some(cur_page) = current_overflow_page {\n                            // Copy data into the current overflow page.\n                            turso_assert!(\n                                cur_page.is_loaded(),\n                                \"current overflow page is not loaded\"\n                            );\n                            turso_assert!(*dst_data_offset == overflow_page_pointer_size, \"data must be copied to overflow page pointer offset on overflow pages\", { \"dst_data_offset\": dst_data_offset, \"overflow_page_pointer_size\": overflow_page_pointer_size });\n                            let contents = cur_page.get_contents();\n                            let buf = &mut contents.as_ptr()\n                                [*dst_data_offset..*dst_data_offset + amount_to_copy];\n                            buf.copy_from_slice(record_offset_slice_to_copy);\n                        } else {\n                            // Copy data into the cell payload on the btree page.\n                            let buf = &mut cell_payload\n                                [*dst_data_offset..*dst_data_offset + amount_to_copy];\n                            buf.copy_from_slice(record_offset_slice_to_copy);\n                        }\n\n                        if record_offset_slice.len() - amount_to_copy == 0 {\n                            break Ok(IOResult::Done(()));\n                        }\n                        *state = CopyDataState::AllocateOverflowPage;\n                        *src_data_offset += amount_to_copy;\n                    }\n                    CopyDataState::AllocateOverflowPage => {\n                        let new_overflow_page = match pager.allocate_overflow_page() {\n                            Ok(IOResult::Done(new_overflow_page)) => {\n                                PinGuard::new(new_overflow_page)\n                            }\n                            Ok(IOResult::IO(io_result)) => return Ok(IOResult::IO(io_result)),\n                            Err(e) => {\n                                mark_unlikely();\n                                break Err(e);\n                            }\n                        };\n                        turso_assert!(\n                            new_overflow_page.is_loaded(),\n                            \"new overflow page is not loaded\"\n                        );\n                        let new_overflow_page_id = new_overflow_page.get().id as u32;\n\n                        if let Some(prev_page) = current_overflow_page {\n                            // Update the previous overflow page's \"next overflow page\" pointer to point to the new overflow page.\n                            turso_assert!(\n                                prev_page.is_loaded(),\n                                \"previous overflow page is not loaded\"\n                            );\n                            let contents = prev_page.get_contents();\n                            let buf = &mut contents.as_ptr()[..overflow_page_pointer_size];\n                            buf.copy_from_slice(&new_overflow_page_id.to_be_bytes());\n                        } else {\n                            // Update the cell payload's \"next overflow page\" pointer to point to the new overflow page.\n                            let first_overflow_page_ptr_offset =\n                                cell_payload.len() - overflow_page_pointer_size;\n                            let buf = &mut cell_payload[first_overflow_page_ptr_offset\n                                ..first_overflow_page_ptr_offset + overflow_page_pointer_size];\n                            buf.copy_from_slice(&new_overflow_page_id.to_be_bytes());\n                        }\n\n                        *dst_data_offset = overflow_page_pointer_size;\n                        *space_left_on_cur_page = overflow_page_data_size;\n                        *current_overflow_page = Some(new_overflow_page.clone());\n                        *state = CopyDataState::Copy;\n                    }\n                }\n            }\n        }\n    };\n    result\n}\n/// Returns the maximum payload size (X) that can be stored directly on a b-tree page without spilling to overflow pages.\n///\n/// For table leaf pages: X = usable_size - 35\n/// For index pages: X = ((usable_size - 12) * 64/255) - 23\n///\n/// The usable size is the total page size less the reserved space at the end of each page.\n/// These thresholds are designed to:\n/// - Give a minimum fanout of 4 for index b-trees\n/// - Ensure enough payload is on the b-tree page that the record header can usually be accessed\n///   without consulting an overflow page\n#[inline]\npub fn payload_overflow_threshold_max(page_type: PageType, usable_space: usize) -> usize {\n    match page_type {\n        PageType::IndexInterior | PageType::IndexLeaf => {\n            ((usable_space - 12) * 64 / 255) - 23 // Index page formula\n        }\n        PageType::TableInterior | PageType::TableLeaf => {\n            usable_space - 35 // Table leaf page formula\n        }\n    }\n}\n\n/// Returns the minimum payload size (M) that must be stored on the b-tree page before spilling to overflow pages is allowed.\n///\n/// For all page types: M = ((usable_size - 12) * 32/255) - 23\n///\n/// When payload size P exceeds max_local():\n/// - If K = M + ((P-M) % (usable_size-4)) <= max_local(): store K bytes on page\n/// - Otherwise: store M bytes on page\n///\n/// The remaining bytes are stored on overflow pages in both cases.\n#[inline]\npub fn payload_overflow_threshold_min(_page_type: PageType, usable_space: usize) -> usize {\n    // Same formula for all page types\n    ((usable_space - 12) * 32 / 255) - 23\n}\n\n/// Drop a cell from a page.\n/// This is done by freeing the range of bytes that the cell occupies.\n#[inline]\nfn drop_cell(page: &mut PageContent, cell_idx: usize, usable_space: usize) -> Result<()> {\n    let (cell_start, cell_len) = page.cell_get_raw_region(cell_idx, usable_space)?;\n    free_cell_range(page, cell_start, cell_len, usable_space)?;\n    if page.cell_count() > 1 {\n        shift_pointers_left(page, cell_idx);\n    } else {\n        page.write_cell_content_area(usable_space);\n        page.write_first_freeblock(0);\n        page.write_fragmented_bytes_count(0);\n    }\n    page.write_cell_count(page.cell_count() as u16 - 1);\n    debug_validate_cells!(page, usable_space);\n    Ok(())\n}\n\n/// Shift pointers to the left once starting from a cell position\n/// This is useful when we remove a cell and we want to move left the cells from the right to fill\n/// the empty space that's not needed\n#[inline]\nfn shift_pointers_left(page: &mut PageContent, cell_idx: usize) {\n    turso_assert_greater_than!(page.cell_count(), 0);\n    let buf = page.as_ptr();\n    let (start, _) = page.cell_pointer_array_offset_and_size();\n    let start = start + (cell_idx * 2) + 2;\n    let right_cells = page.cell_count() - cell_idx - 1;\n    let amount_to_shift = right_cells * 2;\n    buf.copy_within(start..start + amount_to_shift, start - 2);\n}\n\n#[cfg(test)]\nmod tests {\n    use rand::{rng, Rng};\n    use rand_chacha::{\n        rand_core::{RngCore, SeedableRng},\n        ChaCha8Rng,\n    };\n    use sorted_vec::SortedVec;\n    use test_log::test;\n    use turso_parser::ast::SortOrder;\n\n    use super::*;\n    use crate::{\n        io::{Buffer, MemoryIO, OpenFlags, IO},\n        schema::IndexColumn,\n        storage::{\n            database::DatabaseFile, page_cache::PageCache, pager::default_page1,\n            sqlite3_ondisk::PageSize,\n        },\n        types::Text,\n        vdbe::Register,\n        BufferPool, Completion, Connection, IOContext, StepResult, Wal, WalFile, WalFileShared,\n    };\n    use arc_swap::ArcSwapOption;\n    use std::{mem::transmute, ops::Deref, sync::Arc};\n\n    use tempfile::TempDir;\n\n    use crate::{\n        storage::{\n            btree::{compute_free_space, fill_cell_payload, payload_overflow_threshold_max},\n            sqlite3_ondisk::{BTreeCell, PageContent, PageType},\n        },\n        types::Value,\n        Database, Page, Pager, PlatformIO,\n    };\n\n    use super::{btree_init_page, defragment_page, drop_cell, insert_into_cell};\n\n    #[allow(clippy::arc_with_non_send_sync)]\n    fn get_page(id: usize) -> PageRef {\n        let page = Arc::new(Page::new(id as i64));\n\n        {\n            let inner = page.get();\n            inner.buffer = Some(Arc::new(Buffer::new_temporary(4096)));\n        }\n        page.set_loaded();\n\n        btree_init_page(&page, PageType::TableLeaf, 0, 4096);\n        page\n    }\n\n    #[allow(clippy::arc_with_non_send_sync)]\n    fn get_database() -> Arc<Database> {\n        let mut path = TempDir::new().unwrap().keep();\n        path.push(\"test.db\");\n        {\n            let connection = rusqlite::Connection::open(&path).unwrap();\n            connection\n                .pragma_update(None, \"journal_mode\", \"wal\")\n                .unwrap();\n        }\n        let io: Arc<dyn IO> = Arc::new(PlatformIO::new().unwrap());\n        let db = Database::open_file(io.clone(), path.to_str().unwrap()).unwrap();\n\n        db\n    }\n\n    fn ensure_cell(page: &mut PageContent, cell_idx: usize, payload: &Vec<u8>) {\n        let cell = page.cell_get_raw_region(cell_idx, 4096).unwrap();\n        tracing::trace!(\"cell idx={} start={} len={}\", cell_idx, cell.0, cell.1);\n        let buf = &page.as_ptr()[cell.0..cell.0 + cell.1];\n        assert_eq!(buf.len(), payload.len());\n        assert_eq!(buf, payload);\n    }\n\n    fn add_record(\n        id: usize,\n        pos: usize,\n        page: PageRef,\n        record: ImmutableRecord,\n        conn: &Arc<Connection>,\n    ) -> Vec<u8> {\n        let mut payload: Vec<u8> = Vec::new();\n        let mut fill_cell_payload_state = FillCellPayloadState::Start;\n        run_until_done(\n            || {\n                fill_cell_payload(\n                    &PinGuard::new(page.clone()),\n                    Some(id as i64),\n                    &mut payload,\n                    pos,\n                    &record,\n                    4096,\n                    conn.pager.load().clone(),\n                    &mut fill_cell_payload_state,\n                )\n            },\n            &conn.pager.load().clone(),\n        )\n        .unwrap();\n        insert_into_cell(page.get_contents(), &payload, pos, 4096).unwrap();\n        payload\n    }\n\n    fn insert_record(\n        cursor: &mut BTreeCursor,\n        pager: &Arc<Pager>,\n        rowid: i64,\n        val: Value,\n    ) -> Result<(), LimboError> {\n        let regs = &[Register::Value(val)];\n        let record = ImmutableRecord::from_registers(regs, regs.len());\n\n        run_until_done(\n            || {\n                let key = SeekKey::TableRowId(rowid);\n                cursor.seek(key, SeekOp::GE { eq_only: true })\n            },\n            pager.deref(),\n        )?;\n        run_until_done(\n            || cursor.insert(&BTreeKey::new_table_rowid(rowid, Some(&record))),\n            pager.deref(),\n        )?;\n        Ok(())\n    }\n\n    fn assert_btree_empty(cursor: &mut BTreeCursor, pager: &Pager) -> Result<()> {\n        let _c = cursor.move_to_root()?;\n        run_until_done(|| cursor.next(), pager)?;\n        let empty = !cursor.has_record;\n        assert!(empty, \"expected B-tree to be empty\");\n        Ok(())\n    }\n\n    #[test]\n    fn test_insert_cell() {\n        let db = get_database();\n        let conn = db.connect().unwrap();\n        let page = get_page(2);\n\n        let header_size = 8;\n        let regs = &[Register::Value(Value::from_i64(1))];\n        let record = ImmutableRecord::from_registers(regs, regs.len());\n        let payload = add_record(1, 0, page.clone(), record, &conn);\n        let page_contents = page.get_contents();\n        assert_eq!(page_contents.cell_count(), 1);\n        let free = compute_free_space(page_contents, 4096).unwrap();\n        assert_eq!(free, 4096 - payload.len() - 2 - header_size);\n\n        let cell_idx = 0;\n        ensure_cell(page_contents, cell_idx, &payload);\n    }\n\n    struct Cell {\n        pos: usize,\n        payload: Vec<u8>,\n    }\n\n    #[test]\n    fn test_drop_1() {\n        let db = get_database();\n        let conn = db.connect().unwrap();\n\n        let page = get_page(2);\n\n        let page_contents = page.get_contents();\n        let header_size = 8;\n\n        let mut total_size = 0;\n        let mut cells = Vec::new();\n        let usable_space = 4096;\n        for i in 0..3 {\n            let regs = &[Register::Value(Value::from_i64(i as i64))];\n            let record = ImmutableRecord::from_registers(regs, regs.len());\n            let payload = add_record(i, i, page.clone(), record, &conn);\n            assert_eq!(page_contents.cell_count(), i + 1);\n            let free = compute_free_space(page_contents, usable_space).unwrap();\n            total_size += payload.len() + 2;\n            assert_eq!(free, 4096 - total_size - header_size);\n            cells.push(Cell { pos: i, payload });\n        }\n\n        for (i, cell) in cells.iter().enumerate() {\n            ensure_cell(page_contents, i, &cell.payload);\n        }\n        cells.remove(1);\n        drop_cell(page_contents, 1, usable_space).unwrap();\n\n        for (i, cell) in cells.iter().enumerate() {\n            ensure_cell(page_contents, i, &cell.payload);\n        }\n    }\n\n    fn validate_btree(pager: Arc<Pager>, page_idx: i64) -> (usize, bool) {\n        let num_columns = 5;\n        let cursor = BTreeCursor::new_table(pager.clone(), page_idx, num_columns);\n        let (page, _c) = cursor.read_page(page_idx).unwrap();\n        while page.is_locked() {\n            pager.io.step().unwrap();\n        }\n\n        // Pin page in order to not drop it in between\n        page.set_dirty();\n        let contents = page.get_contents();\n        let mut previous_key = None;\n        let mut valid = true;\n        let mut depth = None;\n        debug_validate_cells!(contents, pager.usable_space());\n        let mut child_pages = Vec::new();\n        for cell_idx in 0..contents.cell_count() {\n            let cell = contents.cell_get(cell_idx, cursor.usable_space()).unwrap();\n            let current_depth = match cell {\n                BTreeCell::TableLeafCell(..) => 1,\n                BTreeCell::TableInteriorCell(TableInteriorCell {\n                    left_child_page, ..\n                }) => {\n                    let (child_page, _c) = cursor.read_page(left_child_page as i64).unwrap();\n                    while child_page.is_locked() {\n                        pager.io.step().unwrap();\n                    }\n                    child_pages.push(child_page);\n                    if left_child_page == page.get().id as u32 {\n                        valid = false;\n                        tracing::error!(\n                            \"left child page is the same as parent {}\",\n                            left_child_page\n                        );\n                        continue;\n                    }\n                    let (child_depth, child_valid) =\n                        validate_btree(pager.clone(), left_child_page as i64);\n                    valid &= child_valid;\n                    child_depth\n                }\n                _ => panic!(\"unsupported btree cell: {cell:?}\"),\n            };\n            if current_depth >= 100 {\n                tracing::error!(\"depth is too big\");\n                page.clear_dirty();\n                return (100, false);\n            }\n            depth = Some(depth.unwrap_or(current_depth + 1));\n            if depth != Some(current_depth + 1) {\n                tracing::error!(\"depth is different for child of page {}\", page_idx);\n                valid = false;\n            }\n            match cell {\n                BTreeCell::TableInteriorCell(TableInteriorCell { rowid, .. })\n                | BTreeCell::TableLeafCell(TableLeafCell { rowid, .. }) => {\n                    if previous_key.is_some() && previous_key.unwrap() >= rowid {\n                        tracing::error!(\n                            \"keys are in bad order: prev={:?}, current={}\",\n                            previous_key,\n                            rowid\n                        );\n                        valid = false;\n                    }\n                    previous_key = Some(rowid);\n                }\n                _ => panic!(\"unsupported btree cell: {cell:?}\"),\n            }\n        }\n        if let Some(right) = contents.rightmost_pointer().ok().flatten() {\n            let (right_depth, right_valid) = validate_btree(pager.clone(), right as i64);\n            valid &= right_valid;\n            depth = Some(depth.unwrap_or(right_depth + 1));\n            if depth != Some(right_depth + 1) {\n                tracing::error!(\"depth is different for child of page {}\", page_idx);\n                valid = false;\n            }\n        }\n        let first_page_type = child_pages.first_mut().map(|p| {\n            if !p.is_loaded() {\n                let (new_page, _c) = pager.read_page(p.get().id as i64).unwrap();\n                *p = new_page;\n            }\n            while p.is_locked() {\n                pager.io.step().unwrap();\n            }\n            p.get_contents().page_type().ok()\n        });\n        if let Some(child_type) = first_page_type {\n            for page in child_pages.iter_mut().skip(1) {\n                if !page.is_loaded() {\n                    let (new_page, _c) = pager.read_page(page.get().id as i64).unwrap();\n                    *page = new_page;\n                }\n                while page.is_locked() {\n                    pager.io.step().unwrap();\n                }\n                if page.get_contents().page_type().ok() != child_type {\n                    tracing::error!(\"child pages have different types\");\n                    valid = false;\n                }\n            }\n        }\n        if contents.rightmost_pointer().ok().flatten().is_none() && contents.cell_count() == 0 {\n            valid = false;\n        }\n        page.clear_dirty();\n        (depth.unwrap(), valid)\n    }\n\n    fn format_btree(pager: Arc<Pager>, page_idx: i64, depth: usize) -> String {\n        let num_columns = 5;\n\n        let cursor = BTreeCursor::new_table(pager.clone(), page_idx, num_columns);\n        let (page, _c) = cursor.read_page(page_idx).unwrap();\n        while page.is_locked() {\n            pager.io.step().unwrap();\n        }\n\n        // Pin page in order to not drop it in between loading of different pages. If not contents will be a dangling reference.\n        page.set_dirty();\n        let contents = page.get_contents();\n        let mut current = Vec::new();\n        let mut child = Vec::new();\n        for cell_idx in 0..contents.cell_count() {\n            let cell = contents.cell_get(cell_idx, cursor.usable_space()).unwrap();\n            match cell {\n                BTreeCell::TableInteriorCell(cell) => {\n                    current.push(format!(\n                        \"node[rowid:{}, ptr(<=):{}]\",\n                        cell.rowid, cell.left_child_page\n                    ));\n                    child.push(format_btree(\n                        pager.clone(),\n                        cell.left_child_page as i64,\n                        depth + 2,\n                    ));\n                }\n                BTreeCell::TableLeafCell(cell) => {\n                    current.push(format!(\n                        \"leaf[rowid:{}, len(payload):{}, overflow:{}]\",\n                        cell.rowid,\n                        cell.payload.len(),\n                        cell.first_overflow_page.is_some()\n                    ));\n                }\n                _ => panic!(\"unsupported btree cell: {cell:?}\"),\n            }\n        }\n        if let Some(rightmost) = contents.rightmost_pointer().ok().flatten() {\n            child.push(format_btree(pager, rightmost as i64, depth + 2));\n        }\n        let current = format!(\n            \"{}-page:{}, ptr(right):{:?}\\n{}+cells:{}\",\n            \" \".repeat(depth),\n            page_idx,\n            contents.rightmost_pointer().ok().flatten(),\n            \" \".repeat(depth),\n            current.join(\", \")\n        );\n        page.clear_dirty();\n        if child.is_empty() {\n            current\n        } else {\n            current + \"\\n\" + &child.join(\"\\n\")\n        }\n    }\n\n    fn empty_btree() -> (Arc<Pager>, i64, Arc<Database>, Arc<Connection>) {\n        #[allow(clippy::arc_with_non_send_sync)]\n        let io: Arc<dyn IO> = Arc::new(MemoryIO::new());\n        let db = Database::open_file(io.clone(), \":memory:\").unwrap();\n        let conn = db.connect().unwrap();\n        let pager = conn.pager.load().clone();\n\n        // FIXME: handle page cache is full\n\n        // force allocate page1 with a transaction\n        pager.begin_read_tx().unwrap();\n        run_until_done(|| pager.begin_write_tx(), &pager).unwrap();\n        run_until_done(|| pager.commit_tx(&conn, true), &pager).unwrap();\n\n        let page2 = run_until_done(|| pager.allocate_page(), &pager).unwrap();\n        btree_init_page(&page2, PageType::TableLeaf, 0, pager.usable_space());\n        (pager, page2.get().id as i64, db, conn)\n    }\n\n    #[test]\n    fn btree_with_virtual_page_1() -> Result<()> {\n        #[allow(clippy::arc_with_non_send_sync)]\n        let io: Arc<dyn IO> = Arc::new(MemoryIO::new());\n        let db = Database::open_file(io.clone(), \":memory:\").unwrap();\n        let conn = db.connect().unwrap();\n        let pager = conn.pager.load().clone();\n\n        let mut cursor = BTreeCursor::new(pager, 1, 5);\n        let result = cursor.rewind()?;\n        assert!(matches!(result, IOResult::Done(_)));\n        let result = cursor.next()?;\n        assert!(matches!(result, IOResult::Done(_)));\n        assert!(!cursor.has_record);\n        let result = cursor.record()?;\n        assert!(matches!(result, IOResult::Done(record) if record.is_none()));\n        Ok(())\n    }\n\n    #[test]\n    pub fn btree_test_overflow_pages_are_cleared_on_overwrite() {\n        // Create a database with a table\n        let (pager, root_page, _, _) = empty_btree();\n        let num_columns = 5;\n\n        // Get the maximum local payload size for table leaf pages\n        let max_local = payload_overflow_threshold_max(PageType::TableLeaf, 4096);\n        let usable_size = 4096;\n\n        // Create a payload that is definitely larger than the maximum local size\n        // This will force the creation of overflow pages\n        let large_payload_size = max_local + usable_size * 2;\n        let large_payload = vec![b'X'; large_payload_size];\n\n        // Create a record with the large payload\n        let regs = &[Register::Value(Value::Blob(large_payload))];\n        let large_record = ImmutableRecord::from_registers(regs, regs.len());\n\n        // Create cursor for the table\n        let mut cursor = BTreeCursor::new_table(pager.clone(), root_page, num_columns);\n        let cursor = &mut cursor;\n\n        let initial_pagecount = pager\n            .io\n            .block(|| pager.with_header(|header| header.database_size.get()))\n            .unwrap();\n        assert_eq!(\n            initial_pagecount, 2,\n            \"Page count should be 2 after initial insert, was {initial_pagecount}\"\n        );\n\n        // Insert the large record with rowid 1\n        run_until_done(\n            || {\n                let key = SeekKey::TableRowId(1);\n                cursor.seek(key, SeekOp::GE { eq_only: true })\n            },\n            pager.deref(),\n        )\n        .unwrap();\n        let key = BTreeKey::new_table_rowid(1, Some(&large_record));\n        run_until_done(|| cursor.insert(&key), pager.deref()).unwrap();\n\n        // Verify that overflow pages were created by checking freelist count\n        // The freelist count should be 0 initially, and after inserting a large record,\n        // some pages should be allocated for overflow, but they won't be in freelist yet\n        let freelist_after_insert = pager\n            .io\n            .block(|| pager.with_header(|header| header.freelist_pages.get()))\n            .unwrap();\n        assert_eq!(\n            freelist_after_insert, 0,\n            \"Freelist count should be 0 after insert, was {freelist_after_insert}\"\n        );\n        let pagecount_after_insert = pager\n            .io\n            .block(|| pager.with_header(|header| header.database_size.get()))\n            .unwrap();\n        const EXPECTED_OVERFLOW_PAGES: u32 = 3;\n        assert_eq!(\n            pagecount_after_insert,\n            initial_pagecount + EXPECTED_OVERFLOW_PAGES,\n            \"Page count should be {} after insert, was {pagecount_after_insert}\",\n            initial_pagecount + EXPECTED_OVERFLOW_PAGES\n        );\n\n        // Create a smaller record to overwrite with\n        let small_payload = vec![b'Y'; 100]; // Much smaller payload\n        let regs = &[Register::Value(Value::Blob(small_payload.clone()))];\n        let small_record = ImmutableRecord::from_registers(regs, regs.len());\n\n        // Seek to the existing record\n        run_until_done(\n            || {\n                let key = SeekKey::TableRowId(1);\n                cursor.seek(key, SeekOp::GE { eq_only: true })\n            },\n            pager.deref(),\n        )\n        .unwrap();\n\n        // Overwrite the record with the same rowid\n        let key = BTreeKey::new_table_rowid(1, Some(&small_record));\n        run_until_done(|| cursor.insert(&key), pager.deref()).unwrap();\n\n        // Check that the freelist count has increased, indicating overflow pages were cleared\n        let freelist_after_overwrite = pager\n            .io\n            .block(|| pager.with_header(|header| header.freelist_pages.get()))\n            .unwrap();\n        assert_eq!(freelist_after_overwrite, EXPECTED_OVERFLOW_PAGES, \"Freelist count should be {EXPECTED_OVERFLOW_PAGES} after overwrite, was {freelist_after_overwrite}\");\n\n        // Verify the record was actually overwritten by reading it back\n        run_until_done(\n            || {\n                let key = SeekKey::TableRowId(1);\n                cursor.seek(key, SeekOp::GE { eq_only: true })\n            },\n            pager.deref(),\n        )\n        .unwrap();\n\n        let record = loop {\n            match cursor.record().unwrap() {\n                IOResult::Done(r) => break r,\n                IOResult::IO(io) => io.wait(&*pager.io).unwrap(),\n            }\n        };\n        let record = record.unwrap();\n\n        // The record should now contain the smaller payload\n        let record_payload = record.get_payload();\n        const RECORD_HEADER_SIZE: usize = 1;\n        const ROWID_VARINT_SIZE: usize = 1;\n        const ROWID_PAYLOAD_SIZE: usize = 0; // const int 1 doesn't take any space\n        const BLOB_PAYLOAD_SIZE: usize = 1; // the size '100 bytes' can be expressed as 1 byte\n        assert_eq!(\n            record_payload.len(),\n            RECORD_HEADER_SIZE\n                + ROWID_VARINT_SIZE\n                + ROWID_PAYLOAD_SIZE\n                + BLOB_PAYLOAD_SIZE\n                + small_payload.len(),\n            \"Record should now contain smaller payload after overwrite\"\n        );\n    }\n\n    #[test]\n    #[ignore]\n    pub fn btree_insert_fuzz_ex() {\n        for sequence in [\n            &[\n                (777548915, 3364),\n                (639157228, 3796),\n                (709175417, 1214),\n                (390824637, 210),\n                (906124785, 1481),\n                (197677875, 1305),\n                (457946262, 3734),\n                (956825466, 592),\n                (835875722, 1334),\n                (649214013, 1250),\n                (531143011, 1788),\n                (765057993, 2351),\n                (510007766, 1349),\n                (884516059, 822),\n                (81604840, 2545),\n            ]\n            .as_slice(),\n            &[\n                (293471650, 2452),\n                (163608869, 627),\n                (544576229, 464),\n                (705823748, 3441),\n            ]\n            .as_slice(),\n            &[\n                (987283511, 2924),\n                (261851260, 1766),\n                (343847101, 1657),\n                (315844794, 572),\n            ]\n            .as_slice(),\n            &[\n                (987283511, 2924),\n                (261851260, 1766),\n                (343847101, 1657),\n                (315844794, 572),\n                (649272840, 1632),\n                (723398505, 3140),\n                (334416967, 3874),\n            ]\n            .as_slice(),\n        ] {\n            let (pager, root_page, _, _) = empty_btree();\n            let num_columns = 5;\n\n            let mut cursor = BTreeCursor::new_table(pager.clone(), root_page, num_columns);\n            for (key, size) in sequence.iter() {\n                run_until_done(\n                    || {\n                        let key = SeekKey::TableRowId(*key);\n                        cursor.seek(key, SeekOp::GE { eq_only: true })\n                    },\n                    pager.deref(),\n                )\n                .unwrap();\n                let regs = &[Register::Value(Value::Blob(vec![0; *size]))];\n                let value = ImmutableRecord::from_registers(regs, regs.len());\n                tracing::info!(\"insert key:{}\", key);\n                run_until_done(\n                    || cursor.insert(&BTreeKey::new_table_rowid(*key, Some(&value))),\n                    pager.deref(),\n                )\n                .unwrap();\n                tracing::info!(\n                    \"=========== btree ===========\\n{}\\n\\n\",\n                    format_btree(pager.clone(), root_page, 0)\n                );\n            }\n            for (key, _) in sequence.iter() {\n                let seek_key = SeekKey::TableRowId(*key);\n                assert!(\n                    matches!(\n                        cursor.seek(seek_key, SeekOp::GE { eq_only: true }).unwrap(),\n                        IOResult::Done(SeekResult::Found)\n                    ),\n                    \"key {key} is not found\"\n                );\n            }\n        }\n    }\n\n    fn rng_from_time_or_env() -> (ChaCha8Rng, u64) {\n        let seed = std::env::var(\"SEED\").map_or(\n            std::time::SystemTime::now()\n                .duration_since(std::time::UNIX_EPOCH)\n                .unwrap()\n                .as_millis(),\n            |v| {\n                v.parse()\n                    .expect(\"Failed to parse SEED environment variable as u64\")\n            },\n        );\n        let rng = ChaCha8Rng::seed_from_u64(seed as u64);\n        (rng, seed as u64)\n    }\n\n    fn btree_insert_fuzz_run(\n        attempts: usize,\n        inserts: usize,\n        size: impl Fn(&mut ChaCha8Rng) -> usize,\n    ) {\n        const VALIDATE_INTERVAL: usize = 1000;\n        let do_validate_btree = std::env::var(\"VALIDATE_BTREE\")\n            .is_ok_and(|v| v.parse().expect(\"validate should be bool\"));\n        let (mut rng, seed) = rng_from_time_or_env();\n        let mut seen = crate::HashSet::default();\n        tracing::info!(\"super seed: {}\", seed);\n        let num_columns = 5;\n\n        for _ in 0..attempts {\n            let (pager, root_page, _db, conn) = empty_btree();\n            let mut cursor = BTreeCursor::new_table(pager.clone(), root_page, num_columns);\n            let mut keys = SortedVec::new();\n            tracing::info!(\"seed: {seed}\");\n            for insert_id in 0..inserts {\n                let do_validate = do_validate_btree || (insert_id % VALIDATE_INTERVAL == 0);\n                pager.begin_read_tx().unwrap();\n                run_until_done(|| pager.begin_write_tx(), &pager).unwrap();\n                let size = size(&mut rng);\n                let key = {\n                    let result;\n                    loop {\n                        let key = (rng.next_u64() % (1 << 30)) as i64;\n                        if seen.contains(&key) {\n                            continue;\n                        } else {\n                            seen.insert(key);\n                        }\n                        result = key;\n                        break;\n                    }\n                    result\n                };\n                keys.push(key);\n                tracing::info!(\n                    \"INSERT INTO t VALUES ({}, randomblob({})); -- {}\",\n                    key,\n                    size,\n                    insert_id\n                );\n                run_until_done(\n                    || {\n                        let key = SeekKey::TableRowId(key);\n                        cursor.seek(key, SeekOp::GE { eq_only: true })\n                    },\n                    pager.deref(),\n                )\n                .unwrap();\n                let regs = &[Register::Value(Value::Blob(vec![0; size]))];\n                let value = ImmutableRecord::from_registers(regs, regs.len());\n                let btree_before = if do_validate {\n                    format_btree(pager.clone(), root_page, 0)\n                } else {\n                    \"\".to_string()\n                };\n                run_until_done(\n                    || cursor.insert(&BTreeKey::new_table_rowid(key, Some(&value))),\n                    pager.deref(),\n                )\n                .unwrap();\n                pager.io.block(|| pager.commit_tx(&conn, true)).unwrap();\n                pager.begin_read_tx().unwrap();\n                // FIXME: add sorted vector instead, should be okay for small amounts of keys for now :P, too lazy to fix right now\n                let _c = cursor.move_to_root().unwrap();\n                let mut valid = true;\n                if do_validate {\n                    let _c = cursor.move_to_root().unwrap();\n                    for key in keys.iter() {\n                        tracing::trace!(\"seeking key: {}\", key);\n                        run_until_done(|| cursor.next(), pager.deref()).unwrap();\n                        let cursor_rowid = run_until_done(|| cursor.rowid(), pager.deref())\n                            .unwrap()\n                            .unwrap();\n                        if *key != cursor_rowid {\n                            valid = false;\n                            println!(\"key {key} is not found, got {cursor_rowid}\");\n                            break;\n                        }\n                    }\n                }\n                // let's validate btree too so that we undertsand where the btree failed\n                if do_validate\n                    && (!valid || matches!(validate_btree(pager.clone(), root_page), (_, false)))\n                {\n                    let btree_after = format_btree(pager, root_page, 0);\n                    println!(\"btree before:\\n{btree_before}\");\n                    println!(\"btree after:\\n{btree_after}\");\n                    panic!(\"invalid btree\");\n                }\n                pager.end_read_tx();\n            }\n            pager.begin_read_tx().unwrap();\n            tracing::info!(\n                \"=========== btree ===========\\n{}\\n\\n\",\n                format_btree(pager.clone(), root_page, 0)\n            );\n            if matches!(validate_btree(pager.clone(), root_page), (_, false)) {\n                panic!(\"invalid btree\");\n            }\n            let _c = cursor.move_to_root().unwrap();\n            for key in keys.iter() {\n                tracing::trace!(\"seeking key: {}\", key);\n                run_until_done(|| cursor.next(), pager.deref()).unwrap();\n                let cursor_rowid = run_until_done(|| cursor.rowid(), pager.deref())\n                    .unwrap()\n                    .unwrap();\n                assert_eq!(\n                    *key, cursor_rowid,\n                    \"key {key} is not found, got {cursor_rowid}\"\n                );\n            }\n            pager.end_read_tx();\n        }\n    }\n\n    fn btree_index_insert_fuzz_run(attempts: usize, inserts: usize) {\n        use crate::storage::pager::CreateBTreeFlags;\n\n        let (mut rng, seed) = if std::env::var(\"SEED\").is_ok() {\n            let seed = std::env::var(\"SEED\").unwrap();\n            let seed = seed.parse::<u64>().unwrap();\n            let rng = ChaCha8Rng::seed_from_u64(seed);\n            (rng, seed)\n        } else {\n            rng_from_time_or_env()\n        };\n        let mut seen = crate::HashSet::default();\n        tracing::info!(\"super seed: {}\", seed);\n        for _ in 0..attempts {\n            let (pager, _, _db, conn) = empty_btree();\n            let index_root_page = pager\n                .io\n                .block(|| pager.btree_create(&CreateBTreeFlags::new_index()))\n                .unwrap() as i64;\n            let index_def = Index {\n                name: \"testindex\".to_string(),\n                where_clause: None,\n                columns: (0..10)\n                    .map(|i| IndexColumn {\n                        name: format!(\"test{i}\"),\n                        order: SortOrder::Asc,\n                        collation: None,\n                        pos_in_table: i,\n                        default: None,\n                        expr: None,\n                    })\n                    .collect(),\n                table_name: \"test\".to_string(),\n                root_page: index_root_page,\n                unique: false,\n                ephemeral: false,\n                has_rowid: false,\n                index_method: None,\n                on_conflict: None,\n            };\n            let num_columns = index_def.columns.len();\n            let mut cursor =\n                BTreeCursor::new_index(pager.clone(), index_root_page, &index_def, num_columns);\n            let mut keys = SortedVec::new();\n            tracing::info!(\"seed: {seed}\");\n            for i in 0..inserts {\n                pager.begin_read_tx().unwrap();\n                pager.io.block(|| pager.begin_write_tx()).unwrap();\n                let key = {\n                    let result;\n                    loop {\n                        let cols = (0..num_columns)\n                            .map(|_| (rng.next_u64() % (1 << 30)) as i64)\n                            .collect::<Vec<_>>();\n                        if seen.contains(&cols) {\n                            continue;\n                        } else {\n                            seen.insert(cols.clone());\n                        }\n                        result = cols;\n                        break;\n                    }\n                    result\n                };\n                tracing::info!(\"insert {}/{}: {:?}\", i + 1, inserts, key);\n                keys.push(key.clone());\n                let regs = key\n                    .iter()\n                    .map(|col| Register::Value(Value::from_i64(*col)))\n                    .collect::<Vec<_>>();\n                let value = ImmutableRecord::from_registers(&regs, regs.len());\n                run_until_done(\n                    || {\n                        let record = ImmutableRecord::from_registers(&regs, regs.len());\n                        let key = SeekKey::IndexKey(&record);\n                        cursor.seek(key, SeekOp::GE { eq_only: true })\n                    },\n                    pager.deref(),\n                )\n                .unwrap();\n                run_until_done(\n                    || cursor.insert(&BTreeKey::new_index_key(&value)),\n                    pager.deref(),\n                )\n                .unwrap();\n                let c = cursor.move_to_root().unwrap();\n                if let Some(c) = c {\n                    pager.io.wait_for_completion(c).unwrap();\n                }\n                pager.io.block(|| pager.commit_tx(&conn, true)).unwrap();\n            }\n\n            // Check that all keys can be found by seeking\n            pager.begin_read_tx().unwrap();\n            let _c = cursor.move_to_root().unwrap();\n            for (i, key) in keys.iter().enumerate() {\n                tracing::info!(\"seeking key {}/{}: {:?}\", i + 1, keys.len(), key);\n                let exists = run_until_done(\n                    || {\n                        let regs = key\n                            .iter()\n                            .map(|col| Register::Value(Value::from_i64(*col)))\n                            .collect::<Vec<_>>();\n                        cursor.seek(\n                            SeekKey::IndexKey(&ImmutableRecord::from_registers(&regs, regs.len())),\n                            SeekOp::GE { eq_only: true },\n                        )\n                    },\n                    pager.deref(),\n                )\n                .unwrap();\n                let mut found = matches!(exists, SeekResult::Found);\n                if matches!(exists, SeekResult::TryAdvance) {\n                    run_until_done(|| cursor.next(), pager.deref()).unwrap();\n                    found = cursor.has_record();\n                }\n                assert!(found, \"key {key:?} is not found\");\n            }\n            // Check that key count is right\n            let _c = cursor.move_to_root().unwrap();\n            let mut count = 0;\n            while {\n                run_until_done(|| cursor.next(), pager.deref()).unwrap();\n                cursor.has_record\n            } {\n                count += 1;\n            }\n            assert_eq!(\n                count,\n                keys.len(),\n                \"key count is not right, got {}, expected {}\",\n                count,\n                keys.len()\n            );\n            // Check that all keys can be found in-order, by iterating the btree\n            let _c = cursor.move_to_root().unwrap();\n            let mut prev = None;\n            for (i, key) in keys.iter().enumerate() {\n                tracing::info!(\"iterating key {}/{}: {:?}\", i + 1, keys.len(), key);\n                run_until_done(|| cursor.next(), pager.deref()).unwrap();\n                let record = loop {\n                    match cursor.record().unwrap() {\n                        IOResult::Done(r) => break r,\n                        IOResult::IO(io) => io.wait(&*pager.io).unwrap(),\n                    }\n                };\n                let record = record.as_ref().unwrap();\n                let cur = record\n                    .get_values()\n                    .unwrap()\n                    .iter()\n                    .map(ValueRef::to_owned)\n                    .collect::<Vec<_>>();\n                if let Some(prev) = prev {\n                    if prev >= cur {\n                        println!(\"Seed: {seed}\");\n                    }\n                    assert!(\n                        prev < cur,\n                        \"keys are not in ascending order: {prev:?} < {cur:?}\",\n                    );\n                }\n                prev = Some(cur);\n            }\n            pager.end_read_tx();\n        }\n    }\n\n    fn btree_index_insert_delete_fuzz_run(\n        attempts: usize,\n        operations: usize,\n        size: impl Fn(&mut ChaCha8Rng) -> usize,\n        insert_chance: f64,\n    ) {\n        use crate::storage::pager::CreateBTreeFlags;\n\n        let (mut rng, seed) = if std::env::var(\"SEED\").is_ok() {\n            let seed = std::env::var(\"SEED\").unwrap();\n            let seed = seed.parse::<u64>().unwrap();\n            let rng = ChaCha8Rng::seed_from_u64(seed);\n            (rng, seed)\n        } else {\n            rng_from_time_or_env()\n        };\n        let mut seen = crate::HashSet::default();\n        tracing::info!(\"super seed: {}\", seed);\n\n        for _ in 0..attempts {\n            let (pager, _, _db, conn) = empty_btree();\n            let index_root_page = pager\n                .io\n                .block(|| pager.btree_create(&CreateBTreeFlags::new_index()))\n                .unwrap() as i64;\n            let index_def = Index {\n                name: \"testindex\".to_string(),\n                where_clause: None,\n                columns: vec![IndexColumn {\n                    name: \"testcol\".to_string(),\n                    order: SortOrder::Asc,\n                    collation: None,\n                    pos_in_table: 0,\n                    default: None,\n                    expr: None,\n                }],\n                table_name: \"test\".to_string(),\n                root_page: index_root_page,\n                unique: false,\n                ephemeral: false,\n                has_rowid: false,\n                index_method: None,\n                on_conflict: None,\n            };\n            let mut cursor = BTreeCursor::new_index(pager.clone(), index_root_page, &index_def, 1);\n\n            // Track expected keys that should be present in the tree\n            let mut expected_keys = Vec::new();\n\n            tracing::info!(\"seed: {seed}\");\n            for i in 0..operations {\n                let print_progress = i % 100 == 0;\n                pager.begin_read_tx().unwrap();\n\n                pager.io.block(|| pager.begin_write_tx()).unwrap();\n\n                // Decide whether to insert or delete (80% chance of insert)\n                let is_insert = rng.next_u64() % 100 < (insert_chance * 100.0) as u64;\n\n                if is_insert {\n                    // Generate a unique key for insertion\n                    let key = {\n                        let result;\n                        loop {\n                            let sizeof_blob = size(&mut rng);\n                            let blob = (0..sizeof_blob)\n                                .map(|_| (rng.next_u64() % 256) as u8)\n                                .collect::<Vec<_>>();\n                            if seen.contains(&blob) {\n                                continue;\n                            } else {\n                                seen.insert(blob.clone());\n                            }\n                            result = blob;\n                            break;\n                        }\n                        result\n                    };\n\n                    if print_progress {\n                        tracing::info!(\"insert {}/{}, seed: {seed}\", i + 1, operations);\n                    }\n                    expected_keys.push(key.clone());\n\n                    let regs = vec![Register::Value(Value::Blob(key))];\n                    let value = ImmutableRecord::from_registers(&regs, regs.len());\n\n                    let seek_result = run_until_done(\n                        || {\n                            let record = ImmutableRecord::from_registers(&regs, regs.len());\n                            let key = SeekKey::IndexKey(&record);\n                            cursor.seek(key, SeekOp::GE { eq_only: true })\n                        },\n                        pager.deref(),\n                    )\n                    .unwrap();\n                    if let SeekResult::TryAdvance = seek_result {\n                        run_until_done(|| cursor.next(), pager.deref()).unwrap();\n                    }\n                    run_until_done(\n                        || cursor.insert(&BTreeKey::new_index_key(&value)),\n                        pager.deref(),\n                    )\n                    .unwrap();\n                } else {\n                    // Delete a random existing key\n                    if !expected_keys.is_empty() {\n                        let delete_idx = rng.next_u64() as usize % expected_keys.len();\n                        let key_to_delete = expected_keys[delete_idx].clone();\n\n                        if print_progress {\n                            tracing::info!(\"delete {}/{}, seed: {seed}\", i + 1, operations);\n                        }\n\n                        let regs = vec![Register::Value(Value::Blob(key_to_delete.clone()))];\n                        let record = ImmutableRecord::from_registers(&regs, regs.len());\n\n                        // Seek to the key to delete\n                        let seek_result = run_until_done(\n                            || {\n                                cursor\n                                    .seek(SeekKey::IndexKey(&record), SeekOp::GE { eq_only: true })\n                            },\n                            pager.deref(),\n                        )\n                        .unwrap();\n                        let mut found = matches!(seek_result, SeekResult::Found);\n                        if matches!(seek_result, SeekResult::TryAdvance) {\n                            run_until_done(|| cursor.next(), pager.deref()).unwrap();\n                            found = cursor.has_record()\n                        }\n                        assert!(found, \"expected key {key_to_delete:?} is not found\");\n\n                        // Delete the key\n                        run_until_done(|| cursor.delete(), pager.deref()).unwrap();\n\n                        // Remove from expected keys\n                        expected_keys.remove(delete_idx);\n                    }\n                }\n\n                let c = cursor.move_to_root().unwrap();\n                if let Some(c) = c {\n                    pager.io.wait_for_completion(c).unwrap();\n                }\n                pager.io.block(|| pager.commit_tx(&conn, true)).unwrap();\n            }\n\n            // Final validation\n            let mut sorted_keys = expected_keys.clone();\n            sorted_keys.sort();\n            validate_expected_keys(&pager, &mut cursor, &sorted_keys, seed);\n\n            pager.end_read_tx();\n        }\n    }\n\n    fn validate_expected_keys(\n        pager: &Arc<Pager>,\n        cursor: &mut BTreeCursor,\n        expected_keys: &[Vec<u8>],\n        seed: u64,\n    ) {\n        // Check that all expected keys can be found by seeking\n        pager.begin_read_tx().unwrap();\n        let _c = cursor.move_to_root().unwrap();\n        for (i, key) in expected_keys.iter().enumerate() {\n            tracing::info!(\n                \"validating key {}/{}, seed: {seed}\",\n                i + 1,\n                expected_keys.len()\n            );\n            let exists = run_until_done(\n                || {\n                    let regs = vec![Register::Value(Value::Blob(key.clone()))];\n                    cursor.seek(\n                        SeekKey::IndexKey(&ImmutableRecord::from_registers(&regs, regs.len())),\n                        SeekOp::GE { eq_only: true },\n                    )\n                },\n                pager.deref(),\n            )\n            .unwrap();\n            let mut found = matches!(exists, SeekResult::Found);\n            if matches!(exists, SeekResult::TryAdvance) {\n                run_until_done(|| cursor.next(), pager.deref()).unwrap();\n                found = cursor.has_record();\n            }\n            assert!(found, \"expected key {key:?} is not found\");\n        }\n\n        // Check key count\n        let _c = cursor.move_to_root().unwrap();\n        run_until_done(|| cursor.rewind(), pager.deref()).unwrap();\n        if !cursor.has_record() {\n            panic!(\"no keys in tree\");\n        }\n        let mut count = 1;\n        loop {\n            run_until_done(|| cursor.next(), pager.deref()).unwrap();\n            if !cursor.has_record() {\n                break;\n            }\n            count += 1;\n        }\n        assert_eq!(\n            count,\n            expected_keys.len(),\n            \"key count is not right, got {}, expected {}, seed: {seed}\",\n            count,\n            expected_keys.len()\n        );\n\n        // Check that all keys can be found in-order, by iterating the btree\n        let _c = cursor.move_to_root().unwrap();\n        for (i, key) in expected_keys.iter().enumerate() {\n            run_until_done(|| cursor.next(), pager.deref()).unwrap();\n            tracing::info!(\n                \"iterating key {}/{}, cursor stack cur idx: {:?}, cursor stack depth: {:?}, seed: {seed}\",\n                i + 1,\n                expected_keys.len(),\n                cursor.stack.current_cell_index(),\n                cursor.stack.current()\n            );\n            let record = loop {\n                match cursor.record().unwrap() {\n                    IOResult::Done(r) => break r,\n                    IOResult::IO(io) => io.wait(&*pager.io).unwrap(),\n                }\n            };\n            let record = record.as_ref().unwrap();\n            let cur = record.get_value(0).expect(\"expected at least one column\");\n            let ValueRef::Blob(ref cur) = cur else {\n                panic!(\"expected blob, got {cur:?}\");\n            };\n            assert_eq!(cur, key, \"key {key:?} is not found, seed: {seed}\");\n        }\n        pager.end_read_tx();\n    }\n\n    #[test]\n    pub fn test_drop_odd() {\n        let db = get_database();\n        let conn = db.connect().unwrap();\n\n        let page = get_page(2);\n\n        let page_contents = page.get_contents();\n        let header_size = 8;\n\n        let mut total_size = 0;\n        let mut cells = Vec::new();\n        let usable_space = 4096;\n        let total_cells = 10;\n        for i in 0..total_cells {\n            let regs = &[Register::Value(Value::from_i64(i as i64))];\n            let record = ImmutableRecord::from_registers(regs, regs.len());\n            let payload = add_record(i, i, page.clone(), record, &conn);\n            assert_eq!(page_contents.cell_count(), i + 1);\n            let free = compute_free_space(page_contents, usable_space).unwrap();\n            total_size += payload.len() + 2;\n            assert_eq!(free, 4096 - total_size - header_size);\n            cells.push(Cell { pos: i, payload });\n        }\n\n        let mut removed = 0;\n        let mut new_cells = Vec::new();\n        for cell in cells {\n            if cell.pos % 2 == 1 {\n                drop_cell(page_contents, cell.pos - removed, usable_space).unwrap();\n                removed += 1;\n            } else {\n                new_cells.push(cell);\n            }\n        }\n        let cells = new_cells;\n        for (i, cell) in cells.iter().enumerate() {\n            ensure_cell(page_contents, i, &cell.payload);\n        }\n\n        for (i, cell) in cells.iter().enumerate() {\n            ensure_cell(page_contents, i, &cell.payload);\n        }\n    }\n\n    #[test]\n    pub fn btree_insert_fuzz_run_equal_size() {\n        for size in 1..8 {\n            tracing::info!(\"======= size:{} =======\", size);\n            btree_insert_fuzz_run(2, 1024, |_| size);\n        }\n    }\n\n    #[test]\n    pub fn btree_index_insert_fuzz_run_equal_size() {\n        btree_index_insert_fuzz_run(2, 1024);\n    }\n\n    #[test]\n    pub fn btree_index_insert_delete_fuzz_run_test() {\n        btree_index_insert_delete_fuzz_run(\n            2,\n            2000,\n            |rng| {\n                let min: u32 = 4;\n                let size = min + rng.next_u32() % (1024 - min);\n                size as usize\n            },\n            0.65,\n        );\n    }\n\n    #[test]\n    pub fn btree_insert_fuzz_run_random() {\n        btree_insert_fuzz_run(128, 16, |rng| (rng.next_u32() % 4096) as usize);\n    }\n\n    #[test]\n    pub fn btree_insert_fuzz_run_small() {\n        btree_insert_fuzz_run(1, 100, |rng| (rng.next_u32() % 128) as usize);\n    }\n\n    #[test]\n    pub fn btree_insert_fuzz_run_big() {\n        btree_insert_fuzz_run(64, 32, |rng| 3 * 1024 + (rng.next_u32() % 1024) as usize);\n    }\n\n    #[test]\n    pub fn btree_insert_fuzz_run_overflow() {\n        btree_insert_fuzz_run(64, 32, |rng| (rng.next_u32() % 32 * 1024) as usize);\n    }\n\n    #[test]\n    #[ignore]\n    pub fn fuzz_long_btree_insert_fuzz_run_equal_size() {\n        for size in 1..8 {\n            tracing::info!(\"======= size:{} =======\", size);\n            btree_insert_fuzz_run(2, 10_000, |_| size);\n        }\n    }\n\n    #[test]\n    #[ignore]\n    pub fn fuzz_long_btree_index_insert_fuzz_run_equal_size() {\n        btree_index_insert_fuzz_run(2, 10_000);\n    }\n\n    #[test]\n    #[ignore]\n    pub fn fuzz_long_btree_index_insert_delete_fuzz_run() {\n        btree_index_insert_delete_fuzz_run(\n            2,\n            10000,\n            |rng| {\n                let min: u32 = 4;\n                let size = min + rng.next_u32() % (1024 - min);\n                size as usize\n            },\n            0.65,\n        );\n    }\n\n    #[test]\n    #[ignore]\n    pub fn fuzz_long_btree_insert_fuzz_run_random() {\n        btree_insert_fuzz_run(2, 10_000, |rng| (rng.next_u32() % 4096) as usize);\n    }\n\n    #[test]\n    #[ignore]\n    pub fn fuzz_long_btree_insert_fuzz_run_small() {\n        btree_insert_fuzz_run(2, 10_000, |rng| (rng.next_u32() % 128) as usize);\n    }\n\n    #[test]\n    #[ignore]\n    pub fn fuzz_long_btree_insert_fuzz_run_big() {\n        btree_insert_fuzz_run(2, 10_000, |rng| 3 * 1024 + (rng.next_u32() % 1024) as usize);\n    }\n\n    #[test]\n    #[ignore]\n    pub fn fuzz_long_btree_insert_fuzz_run_overflow() {\n        btree_insert_fuzz_run(2, 5_000, |rng| (rng.next_u32() % 32 * 1024) as usize);\n    }\n\n    #[allow(clippy::arc_with_non_send_sync)]\n    fn setup_test_env(database_size: u32) -> Arc<Pager> {\n        let page_size = 512;\n\n        let io: Arc<dyn IO> = Arc::new(MemoryIO::new());\n        let buffer_pool = BufferPool::begin_init(&io, page_size * 128);\n\n        let db_file = Arc::new(DatabaseFile::new(\n            io.open_file(\":memory:\", OpenFlags::Create, false).unwrap(),\n        ));\n\n        let wal_file = io.open_file(\"test.wal\", OpenFlags::Create, false).unwrap();\n        let wal_shared = WalFileShared::new_shared(wal_file).unwrap();\n        let last_checksum_and_max_frame = wal_shared.read().last_checksum_and_max_frame();\n        let wal: Arc<dyn Wal> = Arc::new(WalFile::new(\n            io.clone(),\n            wal_shared,\n            last_checksum_and_max_frame,\n            buffer_pool.clone(),\n        ));\n\n        // For new empty databases, init_page_1 must be Some(page) so allocate_page1() can be called\n        let init_page_1 = Arc::new(ArcSwapOption::new(Some(default_page1(None))));\n        let pager = Arc::new(\n            Pager::new(\n                db_file,\n                Some(wal),\n                io,\n                PageCache::new(10),\n                buffer_pool,\n                Arc::new(crate::sync::Mutex::new(())),\n                init_page_1,\n            )\n            .unwrap(),\n        );\n\n        pager.io.step().unwrap();\n\n        let _ = run_until_done(|| pager.allocate_page1(), &pager);\n        for _ in 0..(database_size - 1) {\n            let _res = pager.allocate_page().unwrap();\n        }\n\n        pager\n            .io\n            .block(|| {\n                pager.with_header_mut(|header| {\n                    header.page_size = PageSize::new(page_size as u32).unwrap()\n                })\n            })\n            .unwrap();\n\n        pager\n    }\n\n    #[test]\n    pub fn test_clear_overflow_pages() -> Result<()> {\n        let pager = setup_test_env(5);\n        let num_columns = 5;\n\n        let mut cursor = BTreeCursor::new_table(pager.clone(), 1, num_columns);\n\n        let max_local = payload_overflow_threshold_max(PageType::TableLeaf, 4096);\n        let usable_size = cursor.usable_space();\n\n        // Create a large payload that will definitely trigger overflow\n        let large_payload = vec![b'A'; max_local + usable_size];\n\n        // Setup overflow pages (2, 3, 4) with linking\n        let mut current_page = 2_usize;\n        while current_page <= 4 {\n            #[allow(clippy::arc_with_non_send_sync)]\n            let buf = Arc::new(Buffer::new_temporary(\n                pager\n                    .io\n                    .block(|| pager.with_header(|header| header.page_size))?\n                    .get() as usize,\n            ));\n            let _buf = buf.clone();\n            let c = Completion::new_write(move |_| {\n                let _ = _buf.clone();\n            });\n            let _c =\n                pager\n                    .db_file\n                    .write_page(current_page, buf.clone(), &IOContext::default(), c)?;\n            pager.io.step()?;\n\n            let (page, _c) = cursor.read_page(current_page as i64)?;\n            while page.is_locked() {\n                cursor.pager.io.step()?;\n            }\n\n            {\n                let contents = page.get_contents();\n\n                let next_page = if current_page < 4 {\n                    current_page + 1\n                } else {\n                    0\n                };\n                contents.write_u32_no_offset(0, next_page as u32); // Write pointer to next overflow page\n\n                let buf = contents.as_ptr();\n                buf[4..].fill(b'A');\n            }\n\n            current_page += 1;\n        }\n        pager.io.step()?;\n\n        // Create leaf cell pointing to start of overflow chain\n        let leaf_cell = BTreeCell::TableLeafCell(TableLeafCell {\n            rowid: 1,\n            payload: unsafe { transmute::<&[u8], &'static [u8]>(large_payload.as_slice()) },\n            first_overflow_page: Some(2), // Point to first overflow page\n            payload_size: large_payload.len() as u64,\n        });\n\n        let initial_freelist_pages = pager\n            .io\n            .block(|| pager.with_header(|header| header.freelist_pages))?\n            .get();\n        // Clear overflow pages\n        pager.io.block(|| cursor.clear_overflow_pages(&leaf_cell))?;\n        let (freelist_pages, freelist_trunk_page) = pager\n            .io\n            .block(|| {\n                pager.with_header(|header| {\n                    (\n                        header.freelist_pages.get(),\n                        header.freelist_trunk_page.get(),\n                    )\n                })\n            })\n            .unwrap();\n\n        // Verify proper number of pages were added to freelist\n        assert_eq!(\n            freelist_pages,\n            initial_freelist_pages + 3,\n            \"Expected 3 pages to be added to freelist\"\n        );\n\n        // If this is first trunk page\n        let trunk_page_id = freelist_trunk_page;\n        if trunk_page_id > 0 {\n            // Verify trunk page structure\n            let (trunk_page, _c) = cursor.read_page(trunk_page_id as i64)?;\n            let contents = trunk_page.get_contents();\n            // Read number of leaf pages in trunk\n            let n_leaf = contents.read_u32_no_offset(4);\n            assert!(n_leaf > 0, \"Trunk page should have leaf entries\");\n\n            for i in 0..n_leaf {\n                let leaf_page_id = contents.read_u32_no_offset(8 + (i as usize * 4));\n                assert!(\n                    (2..=4).contains(&leaf_page_id),\n                    \"Leaf page ID {leaf_page_id} should be in range 2-4\"\n                );\n            }\n        }\n\n        Ok(())\n    }\n\n    #[test]\n    pub fn test_clear_overflow_pages_no_overflow() -> Result<()> {\n        let pager = setup_test_env(5);\n        let num_columns = 5;\n\n        let mut cursor = BTreeCursor::new_table(pager.clone(), 1, num_columns);\n\n        let small_payload = vec![b'A'; 10];\n\n        // Create leaf cell with no overflow pages\n        let leaf_cell = BTreeCell::TableLeafCell(TableLeafCell {\n            rowid: 1,\n            payload: unsafe { transmute::<&[u8], &'static [u8]>(small_payload.as_slice()) },\n            first_overflow_page: None,\n            payload_size: small_payload.len() as u64,\n        });\n\n        let initial_freelist_pages = pager\n            .io\n            .block(|| pager.with_header(|header| header.freelist_pages))?\n            .get() as usize;\n\n        // Try to clear non-existent overflow pages\n        pager.io.block(|| cursor.clear_overflow_pages(&leaf_cell))?;\n        let (freelist_pages, freelist_trunk_page) = pager.io.block(|| {\n            pager.with_header(|header| {\n                (\n                    header.freelist_pages.get(),\n                    header.freelist_trunk_page.get(),\n                )\n            })\n        })?;\n\n        // Verify freelist was not modified\n        assert_eq!(\n            freelist_pages as usize, initial_freelist_pages,\n            \"Freelist should not change when no overflow pages exist\"\n        );\n\n        // Verify trunk page wasn't created\n        assert_eq!(\n            freelist_trunk_page, 0,\n            \"No trunk page should be created when no overflow pages exist\"\n        );\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_btree_destroy() -> Result<()> {\n        let initial_size = 1;\n        let pager = setup_test_env(initial_size);\n        let num_columns = 5;\n\n        let mut cursor = BTreeCursor::new_table(pager.clone(), 2, num_columns);\n\n        // Initialize page 2 as a root page (interior)\n        let root_page = run_until_done(\n            || cursor.allocate_page(PageType::TableInterior, 0),\n            &cursor.pager,\n        )?;\n\n        // Allocate two leaf pages\n        let page3 = run_until_done(\n            || cursor.allocate_page(PageType::TableLeaf, 0),\n            &cursor.pager,\n        )?;\n        let page4 = run_until_done(\n            || cursor.allocate_page(PageType::TableLeaf, 0),\n            &cursor.pager,\n        )?;\n\n        // Configure the root page to point to the two leaf pages\n        {\n            let contents = root_page.get_contents();\n\n            // Set rightmost pointer to page4\n            contents.write_rightmost_ptr(page4.get().id as u32);\n\n            // Create a cell with pointer to page3\n            let cell_content = vec![\n                // First 4 bytes: left child pointer (page3)\n                (page3.get().id >> 24) as u8,\n                (page3.get().id >> 16) as u8,\n                (page3.get().id >> 8) as u8,\n                page3.get().id as u8,\n                // Next byte: rowid as varint (simple value 100)\n                100,\n            ];\n\n            // Insert the cell\n            insert_into_cell(contents, &cell_content, 0, 512)?;\n        }\n\n        // Add a simple record to each leaf page\n        for page in [&page3, &page4] {\n            let contents = page.get_contents();\n\n            // Simple record with just a rowid and payload\n            let record_bytes = vec![\n                5,                   // Payload length (varint)\n                page.get().id as u8, // Rowid (varint)\n                b'h',\n                b'e',\n                b'l',\n                b'l',\n                b'o', // Payload\n            ];\n\n            insert_into_cell(contents, &record_bytes, 0, 512)?;\n        }\n\n        // Verify structure before destruction\n        assert_eq!(\n            pager\n                .io\n                .block(|| pager.with_header(|header| header.database_size))?\n                .get(),\n            4, // We should have pages 1-4\n            \"Database should have 4 pages total\"\n        );\n\n        // Track freelist state before destruction\n        let initial_free_pages = pager\n            .io\n            .block(|| pager.with_header(|header| header.freelist_pages))?\n            .get();\n        assert_eq!(initial_free_pages, 0, \"should start with no free pages\");\n\n        run_until_done(|| cursor.btree_destroy(), pager.deref())?;\n\n        let pages_freed = pager\n            .io\n            .block(|| pager.with_header(|header| header.freelist_pages))?\n            .get()\n            - initial_free_pages;\n        assert_eq!(pages_freed, 3, \"should free 3 pages (root + 2 leaves)\");\n\n        Ok(())\n    }\n\n    #[test]\n    pub fn test_clear_btree_with_single_page() -> Result<()> {\n        let (pager, root_page, _, _) = empty_btree();\n        let num_columns = 5;\n        let record_count = 10;\n\n        let mut cursor = BTreeCursor::new_table(pager.clone(), root_page, num_columns);\n\n        for rowid in 1..=record_count {\n            insert_record(&mut cursor, &pager, rowid, Value::from_i64(rowid))?;\n        }\n\n        let page_count = pager\n            .io\n            .block(|| pager.with_header(|header| header.database_size.get()))?;\n        assert_eq!(\n            page_count, 2,\n            \"expected two pages (header + root), got {page_count}\"\n        );\n\n        run_until_done(|| cursor.clear_btree(), &pager)?;\n\n        assert_btree_empty(&mut cursor, pager.deref())\n    }\n\n    #[test]\n    pub fn test_clear_btree_with_multiple_pages() -> Result<()> {\n        let (pager, root_page, _, _) = empty_btree();\n        let num_columns = 5;\n        let record_count = 1000;\n\n        let mut cursor = BTreeCursor::new_table(pager.clone(), root_page, num_columns);\n\n        for rowid in 1..=record_count {\n            insert_record(&mut cursor, &pager, rowid, Value::from_i64(rowid))?;\n        }\n\n        // Ensure enough records were created so the tree spans multiple pages.\n        let page_count = pager\n            .io\n            .block(|| pager.with_header(|header| header.database_size.get()))?;\n        assert!(\n            page_count > 2,\n            \"expected more pages than just header + root, got {page_count}\"\n        );\n\n        run_until_done(|| cursor.clear_btree(), &pager)?;\n\n        assert_btree_empty(&mut cursor, pager.deref())\n    }\n\n    #[test]\n    pub fn test_clear_btree_reinsertion() -> Result<()> {\n        let (pager, root_page, _, _) = empty_btree();\n        let num_columns = 5;\n        let record_count = 1000;\n\n        let mut cursor = BTreeCursor::new_table(pager.clone(), root_page, num_columns);\n\n        for rowid in 1..=record_count {\n            insert_record(&mut cursor, &pager, rowid, Value::from_i64(rowid))?;\n        }\n\n        run_until_done(|| cursor.clear_btree(), &pager)?;\n\n        // Reinsert into cleared B-tree to ensure it’s still functional\n        for rowid in 1..=record_count {\n            insert_record(&mut cursor, &pager, rowid, Value::from_i64(rowid))?;\n        }\n\n        if let (_, false) = validate_btree(pager.clone(), root_page) {\n            panic!(\"Invalid B-tree after reinsertion\");\n        }\n\n        let _c = cursor.move_to_root()?;\n        for i in 1..=record_count {\n            run_until_done(|| cursor.next(), &pager)?;\n            let exists = cursor.has_record();\n            assert!(exists, \"Record {i} not found\");\n\n            let record = loop {\n                match cursor.record()? {\n                    IOResult::Done(r) => break r,\n                    IOResult::IO(io) => io.wait(&*pager.io)?,\n                }\n            }\n            .unwrap();\n            let value = record.get_value(0)?;\n            assert_eq!(\n                value,\n                ValueRef::Numeric(Numeric::Integer(i)),\n                \"Unexpected value for record {i}\",\n            );\n        }\n\n        Ok(())\n    }\n\n    #[test]\n    pub fn test_clear_btree_multiple_cursors() -> Result<()> {\n        let (pager, root_page, _, _) = empty_btree();\n        let num_columns = 5;\n        let record_count = 1000;\n\n        let mut cursor1 = BTreeCursor::new_table(pager.clone(), root_page, num_columns);\n        let mut cursor2 = BTreeCursor::new_table(pager.clone(), root_page, num_columns);\n\n        // Use cursor1 to insert records\n        for rowid in 1..=record_count {\n            insert_record(&mut cursor1, &pager, rowid, Value::from_i64(rowid))?;\n        }\n\n        // Use cursor1 to clear the btree\n        run_until_done(|| cursor1.clear_btree(), &pager)?;\n\n        // Verify that cursor2 works correctly\n        assert_btree_empty(&mut cursor2, pager.deref())?;\n\n        // Insert using cursor2\n        insert_record(&mut cursor1, &pager, 1, Value::from_i64(123))?;\n\n        if let (_, false) = validate_btree(pager.clone(), root_page) {\n            panic!(\"Invalid B-tree after insertion\");\n        }\n\n        let key = Value::from_i64(1);\n        let exists = run_until_done(|| cursor2.exists(&key), pager.deref())?;\n        assert!(exists, \"key not found {key}\");\n\n        Ok(())\n    }\n\n    /// Regression test: after clear_btree() on one cursor and invalidate_btree_cache()\n    /// on a sibling cursor sharing the same btree (e.g. OpenDup), the count cache must\n    /// be reset. Otherwise count() returns the stale value from before the clear.\n    ///\n    /// This is the mechanism behind stale partition counts in window functions:\n    /// ResetSorter calls clear_btree on the main cursor and invalidate_btree_cache on\n    /// OpenDup cursors. If count_state/count are not reset, the Count instruction on the\n    /// dup cursor returns the previous partition's row count.\n    #[test]\n    pub fn test_clear_btree_resets_count_cache() -> Result<()> {\n        let (pager, root_page, _, _) = empty_btree();\n        let num_columns = 1;\n\n        let mut cursor_main = BTreeCursor::new_table(pager.clone(), root_page, num_columns);\n        let mut cursor_dup = BTreeCursor::new_table(pager.clone(), root_page, num_columns);\n\n        // Insert 5 records (simulating partition 'a' with 5 rows)\n        for rowid in 1..=5 {\n            insert_record(&mut cursor_main, &pager, rowid, Value::from_i64(rowid))?;\n        }\n\n        // Count via the dup cursor -- should be 5 and caches the result\n        let count1 = run_until_done(|| cursor_dup.count(), pager.deref())?;\n        assert_eq!(count1, 5, \"first count should be 5\");\n\n        // Simulate ResetSorter: clear the btree via the main cursor\n        run_until_done(|| cursor_main.clear_btree(), &pager)?;\n        // Invalidate sibling cursor's cache (as op_reset_sorter does)\n        cursor_dup.invalidate_btree_cache();\n\n        // Insert only 2 records (simulating partition 'b' with 2 rows)\n        for rowid in 1..=2 {\n            insert_record(&mut cursor_main, &pager, rowid, Value::from_i64(rowid + 10))?;\n        }\n\n        // Count via the dup cursor again -- must be 2, not the stale 5\n        let count2 = run_until_done(|| cursor_dup.count(), pager.deref())?;\n        assert_eq!(\n            count2, 2,\n            \"count after clear + re-insert should be 2, got stale count if cache was not reset\"\n        );\n\n        Ok(())\n    }\n\n    /// Verify that clear_btree() resets its own count cache, not just sibling cursors.\n    #[test]\n    pub fn test_clear_btree_resets_own_count_cache() -> Result<()> {\n        let (pager, root_page, _, _) = empty_btree();\n        let num_columns = 1;\n\n        let mut cursor = BTreeCursor::new_table(pager.clone(), root_page, num_columns);\n\n        // Insert 5 records and count\n        for rowid in 1..=5 {\n            insert_record(&mut cursor, &pager, rowid, Value::from_i64(rowid))?;\n        }\n        let count1 = run_until_done(|| cursor.count(), pager.deref())?;\n        assert_eq!(count1, 5);\n\n        // Clear and re-insert 3 records\n        run_until_done(|| cursor.clear_btree(), &pager)?;\n        for rowid in 1..=3 {\n            insert_record(&mut cursor, &pager, rowid, Value::from_i64(rowid + 10))?;\n        }\n\n        // Count should reflect the new 3 records, not the stale 5\n        let count2 = run_until_done(|| cursor.count(), pager.deref())?;\n        assert_eq!(\n            count2, 3,\n            \"count after clear_btree + re-insert should be 3, not stale 5\"\n        );\n\n        Ok(())\n    }\n\n    /// Verify that insert() invalidates the count cache so a subsequent count()\n    /// re-traverses the btree instead of returning the stale cached value.\n    #[test]\n    pub fn test_insert_invalidates_count_cache() -> Result<()> {\n        let (pager, root_page, _, _) = empty_btree();\n        let num_columns = 1;\n\n        let mut cursor = BTreeCursor::new_table(pager.clone(), root_page, num_columns);\n\n        // Insert 3 records and count\n        for rowid in 1..=3 {\n            insert_record(&mut cursor, &pager, rowid, Value::from_i64(rowid))?;\n        }\n        let count1 = run_until_done(|| cursor.count(), pager.deref())?;\n        assert_eq!(count1, 3, \"initial count should be 3\");\n\n        // Insert 2 more records\n        for rowid in 4..=5 {\n            insert_record(&mut cursor, &pager, rowid, Value::from_i64(rowid))?;\n        }\n\n        // Count should reflect all 5 records, not the stale 3\n        let count2 = run_until_done(|| cursor.count(), pager.deref())?;\n        assert_eq!(\n            count2, 5,\n            \"count after additional inserts should be 5, not stale 3\"\n        );\n\n        Ok(())\n    }\n\n    #[test]\n    pub fn test_clear_btree_with_overflow_pages() -> Result<()> {\n        let (pager, root_page, _, _) = empty_btree();\n        let num_columns = 5;\n        let record_count = 100;\n\n        let mut cursor = BTreeCursor::new_table(pager.clone(), root_page, num_columns);\n\n        let initial_page_count = pager\n            .io\n            .block(|| pager.with_header(|header| header.database_size.get()))?;\n\n        for rowid in 1..=record_count {\n            let large_blob = vec![b'A'; 8192];\n            insert_record(&mut cursor, &pager, rowid, Value::Blob(large_blob))?;\n        }\n\n        let page_count_after_inserts = pager\n            .io\n            .block(|| pager.with_header(|header| header.database_size.get()))?;\n        let created_pages = page_count_after_inserts - initial_page_count;\n        assert!(\n            created_pages > record_count as u32,\n            \"expected more pages to be created than records, got {created_pages}\"\n        );\n\n        run_until_done(|| cursor.clear_btree(), &pager)?;\n\n        assert_btree_empty(&mut cursor, pager.deref())\n    }\n\n    #[test]\n    pub fn test_defragment() {\n        let db = get_database();\n        let conn = db.connect().unwrap();\n\n        let page = get_page(2);\n\n        let page_contents = page.get_contents();\n        let header_size = 8;\n\n        let mut total_size = 0;\n        let mut cells = Vec::new();\n        let usable_space = 4096;\n        for i in 0..3 {\n            let regs = &[Register::Value(Value::from_i64(i as i64))];\n            let record = ImmutableRecord::from_registers(regs, regs.len());\n            let payload = add_record(i, i, page.clone(), record, &conn);\n            assert_eq!(page_contents.cell_count(), i + 1);\n            let free = compute_free_space(page_contents, usable_space).unwrap();\n            total_size += payload.len() + 2;\n            assert_eq!(free, 4096 - total_size - header_size);\n            cells.push(Cell { pos: i, payload });\n        }\n\n        for (i, cell) in cells.iter().enumerate() {\n            ensure_cell(page_contents, i, &cell.payload);\n        }\n        cells.remove(1);\n        drop_cell(page_contents, 1, usable_space).unwrap();\n\n        for (i, cell) in cells.iter().enumerate() {\n            ensure_cell(page_contents, i, &cell.payload);\n        }\n\n        defragment_page(page_contents, usable_space, 4).unwrap();\n\n        for (i, cell) in cells.iter().enumerate() {\n            ensure_cell(page_contents, i, &cell.payload);\n        }\n    }\n\n    #[test]\n    pub fn test_drop_odd_with_defragment() {\n        let db = get_database();\n        let conn = db.connect().unwrap();\n\n        let page = get_page(2);\n\n        let page_contents = page.get_contents();\n        let header_size = 8;\n\n        let mut total_size = 0;\n        let mut cells = Vec::new();\n        let usable_space = 4096;\n        let total_cells = 10;\n        for i in 0..total_cells {\n            let regs = &[Register::Value(Value::from_i64(i as i64))];\n            let record = ImmutableRecord::from_registers(regs, regs.len());\n            let payload = add_record(i, i, page.clone(), record, &conn);\n            assert_eq!(page_contents.cell_count(), i + 1);\n            let free = compute_free_space(page_contents, usable_space).unwrap();\n            total_size += payload.len() + 2;\n            assert_eq!(free, 4096 - total_size - header_size);\n            cells.push(Cell { pos: i, payload });\n        }\n\n        let mut removed = 0;\n        let mut new_cells = Vec::new();\n        for cell in cells {\n            if cell.pos % 2 == 1 {\n                drop_cell(page_contents, cell.pos - removed, usable_space).unwrap();\n                removed += 1;\n            } else {\n                new_cells.push(cell);\n            }\n        }\n        let cells = new_cells;\n        for (i, cell) in cells.iter().enumerate() {\n            ensure_cell(page_contents, i, &cell.payload);\n        }\n\n        defragment_page(page_contents, usable_space, 4).unwrap();\n\n        for (i, cell) in cells.iter().enumerate() {\n            ensure_cell(page_contents, i, &cell.payload);\n        }\n    }\n\n    #[test]\n    pub fn test_fuzz_drop_defragment_insert() {\n        let db = get_database();\n        let conn = db.connect().unwrap();\n\n        let page = get_page(2);\n\n        let page_contents = page.get_contents();\n        let header_size = 8;\n\n        let mut total_size = 0;\n        let mut cells = Vec::new();\n        let usable_space = 4096;\n        let mut i = 100000;\n        let seed = rng().random();\n        tracing::info!(\"seed {}\", seed);\n        let mut rng = ChaCha8Rng::seed_from_u64(seed);\n        while i > 0 {\n            i -= 1;\n            match rng.next_u64() % 4 {\n                0 => {\n                    // allow appends with extra place to insert\n                    let cell_idx = rng.next_u64() as usize % (page_contents.cell_count() + 1);\n                    let free = compute_free_space(page_contents, usable_space).unwrap();\n                    let regs = &[Register::Value(Value::from_i64(i as i64))];\n                    let record = ImmutableRecord::from_registers(regs, regs.len());\n                    let mut payload: Vec<u8> = Vec::new();\n                    let mut fill_cell_payload_state = FillCellPayloadState::Start;\n                    run_until_done(\n                        || {\n                            fill_cell_payload(\n                                &PinGuard::new(page.clone()),\n                                Some(i as i64),\n                                &mut payload,\n                                cell_idx,\n                                &record,\n                                4096,\n                                conn.pager.load().clone(),\n                                &mut fill_cell_payload_state,\n                            )\n                        },\n                        &conn.pager.load().clone(),\n                    )\n                    .unwrap();\n                    if (free as usize) < payload.len() + 2 {\n                        // do not try to insert overflow pages because they require balancing\n                        continue;\n                    }\n                    insert_into_cell(page_contents, &payload, cell_idx, 4096).unwrap();\n                    assert!(page_contents.overflow_cells.is_empty());\n                    total_size += payload.len() + 2;\n                    cells.insert(cell_idx, Cell { pos: i, payload });\n                }\n                1 => {\n                    if page_contents.cell_count() == 0 {\n                        continue;\n                    }\n                    let cell_idx = rng.next_u64() as usize % page_contents.cell_count();\n                    let (_, len) = page_contents\n                        .cell_get_raw_region(cell_idx, usable_space)\n                        .unwrap();\n                    drop_cell(page_contents, cell_idx, usable_space).unwrap();\n                    total_size -= len + 2;\n                    cells.remove(cell_idx);\n                }\n                2 => {\n                    defragment_page(page_contents, usable_space, 4).unwrap();\n                }\n                3 => {\n                    // check cells\n                    for (i, cell) in cells.iter().enumerate() {\n                        ensure_cell(page_contents, i, &cell.payload);\n                    }\n                    assert_eq!(page_contents.cell_count(), cells.len());\n                }\n                _ => unreachable!(),\n            }\n            let free = compute_free_space(page_contents, usable_space).unwrap();\n            assert_eq!(free, 4096 - total_size - header_size);\n        }\n    }\n\n    #[test]\n    pub fn test_fuzz_drop_defragment_insert_issue_1085() {\n        // This test is used to demonstrate that issue at https://github.com/tursodatabase/turso/issues/1085\n        // is FIXED.\n        let db = get_database();\n        let conn = db.connect().unwrap();\n\n        let page = get_page(2);\n\n        let page_contents = page.get_contents();\n        let header_size = 8;\n\n        let mut total_size = 0;\n        let usable_space = 4096;\n        let mut i = 1000;\n        for seed in [15292777653676891381, 9261043168681395159] {\n            tracing::info!(\"seed {}\", seed);\n            let mut rng = ChaCha8Rng::seed_from_u64(seed);\n            while i > 0 {\n                i -= 1;\n                match rng.next_u64() % 3 {\n                    0 => {\n                        // allow appends with extra place to insert\n                        let cell_idx = rng.next_u64() as usize % (page_contents.cell_count() + 1);\n                        let free = compute_free_space(page_contents, usable_space).unwrap();\n                        let regs = &[Register::Value(Value::from_i64(i))];\n                        let record = ImmutableRecord::from_registers(regs, regs.len());\n                        let mut payload: Vec<u8> = Vec::new();\n                        let mut fill_cell_payload_state = FillCellPayloadState::Start;\n                        run_until_done(\n                            || {\n                                fill_cell_payload(\n                                    &PinGuard::new(page.clone()),\n                                    Some(i),\n                                    &mut payload,\n                                    cell_idx,\n                                    &record,\n                                    4096,\n                                    conn.pager.load().clone(),\n                                    &mut fill_cell_payload_state,\n                                )\n                            },\n                            &conn.pager.load().clone(),\n                        )\n                        .unwrap();\n                        if (free as usize) < payload.len() - 2 {\n                            // do not try to insert overflow pages because they require balancing\n                            continue;\n                        }\n                        insert_into_cell(page_contents, &payload, cell_idx, 4096).unwrap();\n                        assert!(page_contents.overflow_cells.is_empty());\n                        total_size += payload.len() + 2;\n                    }\n                    1 => {\n                        if page_contents.cell_count() == 0 {\n                            continue;\n                        }\n                        let cell_idx = rng.next_u64() as usize % page_contents.cell_count();\n                        let (_, len) = page_contents\n                            .cell_get_raw_region(cell_idx, usable_space)\n                            .unwrap();\n                        drop_cell(page_contents, cell_idx, usable_space).unwrap();\n                        total_size -= len + 2;\n                    }\n                    2 => {\n                        defragment_page(page_contents, usable_space, 4).unwrap();\n                    }\n                    _ => unreachable!(),\n                }\n                let free = compute_free_space(page_contents, usable_space).unwrap();\n                assert_eq!(free, 4096 - total_size - header_size);\n            }\n        }\n    }\n\n    // this test will create a tree like this:\n    // -page:2, ptr(right):4\n    // +cells:node[rowid:14, ptr(<=):3]\n    //   -page:3, ptr(right):0\n    //   +cells:leaf[rowid:11, len(payload):137, overflow:false]\n    //   -page:4, ptr(right):0\n    //   +cells:\n    #[test]\n    pub fn test_drop_page_in_balancing_issue_1203() {\n        let db = get_database();\n        let conn = db.connect().unwrap();\n\n        let queries = vec![\n\"CREATE TABLE lustrous_petit (awesome_nomous TEXT,ambitious_amargi TEXT,fantastic_daniels BLOB,stupendous_highleyman TEXT,relaxed_crane TEXT,elegant_bromma INTEGER,proficient_castro BLOB,ambitious_liman TEXT,responsible_lusbert BLOB);\",\n\"INSERT INTO lustrous_petit VALUES ('funny_sarambi', 'hardworking_naoumov', X'666561726C6573735F68696C6C', 'elegant_iafd', 'rousing_flag', 681399778772406122, X'706572736F6E61626C655F676F6477696E6772696D6D', 'insightful_anonymous', X'706F77657266756C5F726F636861'), ('personable_holmes', 'diligent_pera', X'686F6E6573745F64696D656E73696F6E', 'energetic_raskin', 'gleaming_federasyon', -2778469859573362611, X'656666696369656E745F6769617A', 'sensible_skirda', X'66616E7461737469635F6B656174696E67'), ('inquisitive_baedan', 'brave_sphinx', X'67656E65726F75735F6D6F6E7473656E79', 'inquisitive_syndicate', 'amiable_room', 6954857961525890638, X'7374756E6E696E675F6E6965747A73636865', 'glowing_coordinator', X'64617A7A6C696E675F7365766572696E65'), ('upbeat_foxtale', 'engaging_aktimon', X'63726561746976655F6875746368696E6773', 'ample_locura', 'creative_barrett', 6413352509911171593, X'6772697070696E675F6D696E7969', 'competitive_parissi', X'72656D61726B61626C655F77696E7374616E6C6579');\",\n\"INSERT INTO lustrous_petit VALUES ('ambitious_berry', 'devoted_marshall', X'696E7175697369746976655F6C6172657661', 'flexible_pramen', 'outstanding_stauch', 6936508362673228293, X'6C6F76696E675F6261756572', 'charming_anonymous', X'68617264776F726B696E675F616E6E6973'), ('enchanting_cohen', 'engaging_rubel', X'686F6E6573745F70726F766F63617A696F6E65', 'humorous_robin', 'imaginative_shuzo', 4762266264295288131, X'726F7573696E675F6261796572', 'vivid_bolling', X'6F7267616E697A65645F7275696E73'), ('affectionate_resistance', 'gripping_rustamova', X'6B696E645F6C61726B696E', 'bright_boulanger', 'upbeat_ashirov', -1726815435854320541, X'61646570745F66646361', 'dazzling_tashjian', X'68617264776F726B696E675F6D6F72656C'), ('zestful_ewald', 'favorable_lewis', X'73747570656E646F75735F7368616C6966', 'bright_combustion', 'blithesome_harding', 8408539013935554176, X'62726176655F737079726F706F756C6F75', 'hilarious_finnegan', X'676976696E675F6F7267616E697A696E67'), ('blithesome_picqueray', 'sincere_william', X'636F75726167656F75735F6D69746368656C6C', 'rousing_atan', 'mirthful_katie', -429232313453215091, X'6C6F76656C795F776174616E616265', 'stupendous_mcmillan', X'666F63757365645F6B61666568'), ('incredible_kid', 'friendly_yvetot', X'706572666563745F617A697A', 'helpful_manhattan', 'shining_horrox', -4318061095860308846, X'616D626974696F75735F726F7765', 'twinkling_anarkiya', X'696D6167696E61746976655F73756D6E6572');\",\n\"INSERT INTO lustrous_petit VALUES ('sleek_graeber', 'approachable_ghazzawi', X'62726176655F6865776974747768697465', 'adaptable_zimmer', 'polite_cohn', -5464225138957223865, X'68756D6F726F75735F736E72', 'adaptable_igualada', X'6C6F76656C795F7A686F75'), ('imaginative_rautiainen', 'magnificent_ellul', X'73706C656E6469645F726F6361', 'responsible_brown', 'upbeat_uruguaya', -1185340834321792223, X'616D706C655F6D6470', 'philosophical_kelly', X'676976696E675F6461676865726D6172676F7369616E'), ('blithesome_darkness', 'creative_newell', X'6C757374726F75735F61706174726973', 'engaging_kids', 'charming_wark', -1752453819873942466, X'76697669645F6162657273', 'independent_barricadas', X'676C697374656E696E675F64686F6E6474'), ('productive_chardronnet', 'optimistic_karnage', X'64696C6967656E745F666F72657374', 'engaging_beggar', 'sensible_wolke', 784341549042407442, X'656E676167696E675F6265726B6F7769637A', 'blithesome_zuzenko', X'6E6963655F70726F766F63617A696F6E65');\",\n\"INSERT INTO lustrous_petit VALUES ('shining_sagris', 'considerate_mother', X'6F70656E5F6D696E6465645F72696F74', 'polite_laufer', 'patient_mink', 2240393952789100851, X'636F75726167656F75735F6D636D696C6C616E', 'glowing_robertson', X'68656C7066756C5F73796D6F6E6473'), ('dazzling_glug', 'stupendous_poznan', X'706572736F6E61626C655F6672616E6B73', 'open_minded_ruins', 'qualified_manes', 2937238916206423261, X'696E736967687466756C5F68616B69656C', 'passionate_borl', X'616D6961626C655F6B7570656E647561'), ('wondrous_parry', 'knowledgeable_giovanni', X'6D6F76696E675F77696E6E', 'shimmering_aberlin', 'affectionate_calhoun', 702116954493913499, X'7265736F7572636566756C5F62726F6D6D61', 'propitious_mezzagarcia', X'746563686E6F6C6F676963616C5F6E6973686974616E69');\",\n\"INSERT INTO lustrous_petit VALUES ('kind_room', 'hilarious_crow', X'6F70656E5F6D696E6465645F6B6F74616E7969', 'hardworking_petit', 'adaptable_zarrow', 2491343172109894986, X'70726F647563746976655F646563616C6F677565', 'willing_sindikalis', X'62726561746874616B696E675F6A6F7264616E');\",\n\"INSERT INTO lustrous_petit VALUES ('confident_etrebilal', 'agreeable_shifu', X'726F6D616E7469635F7363687765697A6572', 'loving_debs', 'gripping_spooner', -3136910055229112693, X'677265676172696F75735F736B726F7A6974736B79', 'ample_ontiveros', X'7175616C69666965645F726F6D616E69656E6B6F'), ('competitive_call', 'technological_egoumenides', X'6469706C6F6D617469635F6D6F6E616768616E', 'willing_stew', 'frank_neal', -5973720171570031332, X'6C6F76696E675F6465737461', 'dazzling_gambone', X'70726F647563746976655F6D656E64656C676C6565736F6E'), ('favorable_delesalle', 'sensible_atterbury', X'666169746866756C5F64617861', 'bountiful_aldred', 'marvelous_malgraith', 5330463874397264493, X'706572666563745F7765726265', 'lustrous_anti', X'6C6F79616C5F626F6F6B6368696E'), ('stellar_corlu', 'loyal_espana', X'6D6F76696E675F7A6167', 'efficient_nelson', 'qualified_shepard', 1015518116803600464, X'737061726B6C696E675F76616E6469766572', 'loving_scoffer', X'686F6E6573745F756C72696368'), ('adaptable_taylor', 'shining_yasushi', X'696D6167696E61746976655F776974746967', 'alluring_blackmore', 'zestful_coeurderoy', -7094136731216188999, X'696D6167696E61746976655F757A63617465677569', 'gleaming_hernandez', X'6672616E6B5F646F6D696E69636B'), ('competitive_luis', 'stellar_fredericks', X'616772656561626C655F6D696368656C', 'optimistic_navarro', 'funny_hamilton', 4003895682491323194, X'6F70656E5F6D696E6465645F62656C6D6173', 'incredible_thorndycraft', X'656C6567616E745F746F6C6B69656E'), ('remarkable_parsons', 'sparkling_ulrich', X'737061726B6C696E675F6D6172696E636561', 'technological_leighlais', 'warmhearted_konok', -5789111414354869563, X'676976696E675F68657272696E67', 'adept_dabtara', X'667269656E646C795F72617070');\",\n\"INSERT INTO lustrous_petit VALUES ('hardworking_norberg', 'approachable_winter', X'62726176655F68617474696E6768', 'imaginative_james', 'open_minded_capital', -5950508516718821688, X'6C757374726F75735F72616E7473', 'warmhearted_limanov', X'696E736967687466756C5F646F637472696E65'), ('generous_shatz', 'generous_finley', X'726176697368696E675F6B757A6E6574736F76', 'stunning_arrigoni', 'favorable_volcano', -8442328990977069526, X'6D6972746866756C5F616C7467656C64', 'thoughtful_zurbrugg', X'6D6972746866756C5F6D6F6E726F65'), ('frank_kerr', 'splendid_swain', X'70617373696F6E6174655F6D6470', 'flexible_dubey', 'sensible_tj', 6352949260574274181, X'656666696369656E745F6B656D736B79', 'vibrant_ege', X'736C65656B5F6272696768746F6E'), ('organized_neal', 'glistening_sugar', X'656E676167696E675F6A6F72616D', 'romantic_krieger', 'qualified_corr', -4774868512022958085, X'706572666563745F6B6F7A6172656B', 'bountiful_zaikowska', X'74686F7567687466756C5F6C6F6767616E73'), ('excellent_lydiettcarrion', 'diligent_denslow', X'666162756C6F75735F6D616E68617474616E', 'confident_tomar', 'glistening_ligt', -1134906665439009896, X'7175616C69666965645F6F6E6B656E', 'remarkable_anarkiya', X'6C6F79616C5F696E64616261'), ('passionate_melis', 'loyal_xsilent', X'68617264776F726B696E675F73637564', 'lustrous_barnes', 'nice_sugako', -4097897163377829983, X'726F6D616E7469635F6461686572', 'bright_imrie', X'73656E7369626C655F6D61726B'), ('giving_mlb', 'breathtaking_fourier', X'736C65656B5F616E61726368697374', 'glittering_malet', 'brilliant_crew', 8791228049111405793, X'626F756E746966756C5F626576656E736565', 'lovely_swords', X'70726F706974696F75735F696E656469746173'), ('honest_wright', 'qualified_rabble', X'736C65656B5F6D6172656368616C', 'shimmering_marius', 'blithesome_mckelvie', -1330737263592370654, X'6F70656E5F6D696E6465645F736D616C6C', 'energetic_gorman', X'70726F706974696F75735F6B6F74616E7969');\",\n\"DELETE FROM lustrous_petit WHERE (ambitious_liman > 'adept_dabtaqu');\",\n\"INSERT INTO lustrous_petit VALUES ('technological_dewey', 'fabulous_st', X'6F7074696D69737469635F73687562', 'considerate_levy', 'adaptable_kernis', 4195134012457716562, X'61646570745F736F6C6964617269646164', 'vibrant_crump', X'6C6F79616C5F72796E6572'), ('super_marjan', 'awesome_gethin', X'736C65656B5F6F737465727765696C', 'diplomatic_loidl', 'qualified_bokani', -2822676417968234733, X'6272696768745F64756E6C6170', 'creative_en', X'6D6972746866756C5F656C6F6666'), ('philosophical_malet', 'unique_garcia', X'76697669645F6E6F7262657267', 'spellbinding_fire', 'faithful_barringtonbush', -7293711848773657758, X'6272696C6C69616E745F6F6B65656665', 'gripping_guillon', X'706572736F6E61626C655F6D61726C696E7370696B65'), ('thoughtful_morefus', 'lustrous_rodriguez', X'636F6E666964656E745F67726F73736D616E726F73686368696E', 'devoted_jackson', 'propitious_karnage', -7802999054396485709, X'63617061626C655F64', 'enchanting_orwell', X'7477696E6B6C696E675F64616C616B6F676C6F75'), ('alluring_guillon', 'brilliant_pinotnoir', X'706572736F6E61626C655F6A6165636B6C65', 'open_minded_azeez', 'courageous_romania', 2126962403055072268, X'746563686E6F6C6F676963616C5F6962616E657A', 'open_minded_rosa', X'6C757374726F75735F6575726F7065'), ('courageous_kolokotronis', 'inquisitive_gahman', X'677265676172696F75735F626172726574', 'ambitious_shakur', 'fantastic_apatris', -1232732971861520864, X'737061726B6C696E675F7761746368', 'captivating_clover', X'636F6E666964656E745F736574686E65737363617374726F'), ('charming_sullivan', 'focused_congress', X'7368696D6D6572696E675F636C7562', 'wondrous_skrbina', 'giving_mendanlioglu', -6837337053772308333, X'636861726D696E675F73616C696E6173', 'rousing_hedva', X'6469706C6F6D617469635F7061796E');\",\n        ];\n\n        for query in queries {\n            let mut stmt = conn.query(query).unwrap().unwrap();\n            loop {\n                let row = stmt.step().expect(\"step\");\n                match row {\n                    StepResult::Done => {\n                        break;\n                    }\n                    _ => {\n                        tracing::debug!(\"row {:?}\", row);\n                    }\n                }\n            }\n        }\n    }\n\n    // this test will create a tree like this:\n    // -page:2, ptr(right):3\n    // +cells:\n    //   -page:3, ptr(right):0\n    //   +cells:\n    #[test]\n    pub fn test_drop_page_in_balancing_issue_1203_2() {\n        let db = get_database();\n        let conn = db.connect().unwrap();\n\n        let queries = vec![\n\"CREATE TABLE super_becky (engrossing_berger BLOB,plucky_chai BLOB,mirthful_asbo REAL,bountiful_jon REAL,competitive_petit REAL,engrossing_rexroth REAL);\",\n\"INSERT INTO super_becky VALUES (X'636861726D696E675F6261796572', X'70726F647563746976655F70617269737369', 6847793643.408741, 7330361375.924953, -6586051582.891455, -6921021872.711397), (X'657863656C6C656E745F6F7267616E697A696E67', X'6C757374726F75735F73696E64696B616C6973', 9905774996.48619, 570325205.2246342, 5852346465.53047, 728566012.1968269), (X'7570626561745F73656174746C65', X'62726176655F6661756E', -2202725836.424899, 5424554426.388281, 2625872085.917082, -6657362503.808359), (X'676C6F77696E675F6D617877656C6C', X'7761726D686561727465645F726F77616E', -9610936969.793116, 4886606277.093559, -3414536174.7928505, 6898267795.317778), (X'64796E616D69635F616D616E', X'7374656C6C61725F7374657073', 3918935692.153696, 151068445.947237, 4582065669.356403, -3312668220.4789667), (X'64696C6967656E745F64757272757469', X'7175616C69666965645F6D726163686E696B', 5527271629.262201, 6068855126.044355, 289904657.13490677, 2975774820.0877323), (X'6469706C6F6D617469635F726F76657363696F', X'616C6C7572696E675F626F7474696369', 9844748192.66119, -6180276383.305578, -4137330511.025565, -478754566.79494476), (X'776F6E64726F75735F6173686572', X'6465766F7465645F6176657273696F6E', 2310211470.114773, -6129166761.628184, -2865371645.3145514, 7542428654.8645935), (X'617070726F61636861626C655F6B686F6C61', X'6C757374726F75735F6C696E6E656C6C', -4993113161.458349, 7356727284.362968, -3228937035.568404, -1779334005.5067253);\",\n\"INSERT INTO super_becky VALUES (X'74686F7567687466756C5F726576696577', X'617765736F6D655F63726F73736579', 9401977997.012783, 8428201961.643898, 2822821303.052643, 4555601220.718847), (X'73706563746163756C61725F6B686179617469', X'616772656561626C655F61646F6E696465', 7414547022.041355, 365016845.73330307, 50682963.055828094, -9258802584.962656), (X'6C6F79616C5F656D6572736F6E', X'676C6F77696E675F626174616C6F', -5522070106.765736, 2712536599.6384163, 6631385631.869345, 1242757880.7583427), (X'68617264776F726B696E675F6F6B656C6C79', X'666162756C6F75735F66696C697373', 6682622809.9778805, 4233900041.917185, 9017477903.795563, -756846353.6034946), (X'68617264776F726B696E675F626C61756D616368656E', X'616666656374696F6E6174655F6B6F736D616E', -1146438175.3174362, -7545123696.438596, -6799494012.403366, 5646913977.971333), (X'66616E7461737469635F726F77616E', X'74686F7567687466756C5F7465727269746F72696573', -4414529784.916277, -6209371635.279242, 4491104121.288605, 2590223842.117277);\",\n\"INSERT INTO super_becky VALUES (X'676C697374656E696E675F706F72746572', X'696E7175697369746976655F656D', 2986144164.3676434, 3495899172.5935287, -849280584.9386635, 6869709150.2699375), (X'696D6167696E61746976655F6D65726C696E6F', X'676C6F77696E675F616B74696D6F6E', 8733490615.829357, 6782649864.719433, 6926744218.74107, 1532081022.4379768), (X'6E6963655F726F73736574', X'626C69746865736F6D655F66696C697373', -839304300.0706863, 6155504968.705227, -2951592321.950267, -6254186334.572437), (X'636F6E666964656E745F6C69626574', X'676C696D6D6572696E675F6B6F74616E7969', -5344675223.37533, -8703794729.211002, 3987472096.020382, -7678989974.961197), (X'696D6167696E61746976655F6B61726162756C7574', X'64796E616D69635F6D6367697272', 2028227065.6995697, -7435689525.030833, 7011220815.569796, 5526665697.213846), (X'696E7175697369746976655F636C61726B', X'616666656374696F6E6174655F636C6561766572', 3016598350.546356, -3686782925.383732, 9671422351.958004, 9099319829.078941), (X'63617061626C655F746174616E6B61', X'696E6372656469626C655F6F746F6E6F6D61', 6339989259.432795, -8888997534.102034, 6855868409.475763, -2565348887.290493), (X'676F7267656F75735F6265726E657269', X'65647563617465645F6F6D6F77616C69', 6992467657.527826, -3538089391.748543, -7103111660.146708, 4019283237.3740463), (X'616772656561626C655F63756C74757265', X'73706563746163756C61725F657370616E61', 189387871.06959534, 6211851191.361202, 1786455196.9768047, 7966404387.318119);\",\n\"INSERT INTO super_becky VALUES (X'7068696C6F736F70686963616C5F6C656967686C616973', X'666162756C6F75735F73656D696E61746F7265', 8688321500.141502, -7855144036.024546, -5234949709.573349, -9937638367.366447), (X'617070726F61636861626C655F726F677565', X'676C65616D696E675F6D7574696E79', -5351540099.744092, -3614025150.9013805, -2327775310.276925, 2223379997.077526), (X'676C696D6D6572696E675F63617263686961', X'696D6167696E61746976655F61737379616E6E', 4104832554.8371887, -5531434716.627781, 1652773397.4099865, 3884980522.1830273);\",\n\"DELETE FROM super_becky WHERE (plucky_chai != X'7761726D686561727465645F6877616E67' AND mirthful_asbo != 9537234687.183533 AND bountiful_jon = -3538089391.748543);\",\n\"INSERT INTO super_becky VALUES (X'706C75636B795F6D617263616E74656C', X'696D6167696E61746976655F73696D73', 9535651632.375484, 92270815.0720501, 1299048084.6248207, 6460855331.572151), (X'726F6D616E7469635F706F746C61746368', X'68756D6F726F75735F63686165686F', 9345375719.265533, 7825332230.247925, -7133157299.39028, -6939677879.6597), (X'656666696369656E745F6261676E696E69', X'63726561746976655F67726168616D', -2615470560.1954746, 6790849074.977201, -8081732985.448849, -8133707792.312794), (X'677265676172696F75735F73637564', X'7368696E696E675F67726F7570', -7996394978.2610035, -9734939565.228964, 1108439333.8481388, -5420483517.169478), (X'6C696B61626C655F6B616E6176616C6368796B', X'636F75726167656F75735F7761726669656C64', -1959869609.656724, 4176668769.239971, -8423220404.063669, 9987687878.685959), (X'657863656C6C656E745F68696C6473646F74746572', X'676C6974746572696E675F7472616D7564616E61', -5220160777.908238, 3892402687.8826714, 9803857762.617172, -1065043714.0265541), (X'6D61676E69666963656E745F717565657273', X'73757065725F717565657273', -700932053.2006226, -4706306995.253335, -5286045811.046467, 1954345265.5250092), (X'676976696E675F6275636B65726D616E6E', X'667269656E646C795F70697A7A6F6C61746F', -2186859620.9089565, -6098492099.446075, -7456845586.405931, 8796967674.444252);\",\n\"DELETE FROM super_becky WHERE TRUE;\",\n\"INSERT INTO super_becky VALUES (X'6F7074696D69737469635F6368616E69616C', X'656E657267657469635F6E65677261', 1683345860.4208698, 4163199322.9289455, -4192968616.7868404, -7253371206.571701), (X'616C6C7572696E675F686176656C', X'7477696E6B6C696E675F626965627579636B', -9947019174.287437, 5975899640.893995, 3844707723.8570194, -9699970750.513876), (X'6F7074696D69737469635F7A686F75', X'616D626974696F75735F636F6E6772657373', 4143738484.1081524, -2138255286.170598, 9960750454.03466, 5840575852.80299), (X'73706563746163756C61725F6A6F6E67', X'73656E7369626C655F616269646F72', -1767611042.9716015, -7684260477.580351, 4570634429.188147, -9222640121.140202), (X'706F6C6974655F6B657272', X'696E736967687466756C5F63686F646F726B6F6666', -635016769.5123329, -4359901288.494518, -7531565119.905825, -1180410948.6572971), (X'666C657869626C655F636F6D756E69656C6C6F', X'6E6963655F6172636F73', 8708423014.802425, -6276712625.559328, -771680766.2485523, 8639486874.113342);\",\n\"DELETE FROM super_becky WHERE (mirthful_asbo < 9730384310.536528 AND plucky_chai < X'6E6963655F61726370B2');\",\n\"DELETE FROM super_becky WHERE (mirthful_asbo > 6248699554.426553 AND bountiful_jon > 4124481472.333034);\",\n\"INSERT INTO super_becky VALUES (X'676C696D6D6572696E675F77656C7368', X'64696C6967656E745F636F7262696E', 8217054003.369003, 8745594518.77864, 1928172803.2261295, -8375115534.050233), (X'616772656561626C655F6463', X'6C6F76696E675F666F72656D616E', -5483889804.871533, -8264576639.127487, 4770567289.404846, -3409172927.2573576), (X'6D617276656C6F75735F6173696D616B6F706F756C6F73', X'746563686E6F6C6F676963616C5F6A61637175696572', 2694858779.206814, -1703227425.3442516, -4504989231.263319, -3097265869.5230227), (X'73747570656E646F75735F64757075697364657269', X'68696C6172696F75735F6D75697268656164', 568174708.66469, -4878260547.265669, -9579691520.956625, 73507727.8100338), (X'626C69746865736F6D655F626C6F6B', X'61646570745F6C65696572', 7772117077.916897, 4590608571.321514, -881713470.657032, -9158405774.647465);\",\n\"INSERT INTO super_becky VALUES (X'6772697070696E675F6573736578', X'67656E65726F75735F636875726368696C6C', -4180431825.598956, 7277443000.677654, 2499796052.7878246, -2858339306.235305), (X'756E697175655F6D6172656368616C', X'62726561746874616B696E675F636875726368696C6C', 1401354536.7625294, -611427440.2796707, -4621650430.463729, 1531473111.7482872), (X'657863656C6C656E745F66696E6C6579', X'666169746866756C5F62726F636B', -4020697828.0073624, -2833530733.19637, -7766170050.654022, 8661820959.434689);\",\n\"INSERT INTO super_becky VALUES (X'756E697175655F6C617061797265', X'6C6F76696E675F7374617465', 7063237787.258968, -5425712581.365798, -7750509440.0141945, -7570954710.892544), (X'62726561746874616B696E675F6E65616C', X'636F75726167656F75735F61727269676F6E69', 289862394.2028198, 9690362375.014446, -4712463267.033899, 2474917855.0973473), (X'7477696E6B6C696E675F7368616B7572', X'636F75726167656F75735F636F6D6D6974746565', 5449035403.229155, -2159678989.597906, 3625606019.1150894, -3752010405.4475393);\",\n\"INSERT INTO super_becky VALUES (X'70617373696F6E6174655F73686970776179', X'686F6E6573745F7363687765697A6572', 4193384746.165228, -2232151704.896323, 8615245520.962444, -9789090953.995636);\",\n\"INSERT INTO super_becky VALUES (X'6C696B61626C655F69', X'6661766F7261626C655F6D626168', 6581403690.769894, 3260059398.9544716, -407118859.046051, -3155853965.2700634), (X'73696E636572655F6F72', X'616772656561626C655F617070656C6261756D', 9402938544.308651, -7595112171.758331, -7005316716.211025, -8368210960.419411);\",\n\"INSERT INTO super_becky VALUES (X'6D617276656C6F75735F6B61736864616E', X'6E6963655F636F7272', -5976459640.85817, -3177550476.2092276, 2073318650.736992, -1363247319.9978447);\",\n\"INSERT INTO super_becky VALUES (X'73706C656E6469645F6C616D656E646F6C61', X'677265676172696F75735F766F6E6E65677574', 6898259773.050102, 8973519699.707073, -25070632.280548096, -1845922497.9676847), (X'617765736F6D655F7365766572', X'656E657267657469635F706F746C61746368', -8750678407.717808, 5130907533.668898, -6778425327.111566, 3718982135.202587);\",\n\"INSERT INTO super_becky VALUES (X'70726F706974696F75735F6D616C617465737461', X'657863656C6C656E745F65766572657474', -8846855772.62094, -6168969732.697067, -8796372709.125793, 9983557891.544613), (X'73696E636572655F6C6177', X'696E7175697369746976655F73616E647374726F6D', -6366985697.975358, 3838628702.6652164, 3680621713.3371124, -786796486.8049564), (X'706F6C6974655F676C6561736F6E', X'706C75636B795F677579616E61', -3987946379.104308, -2119148244.413993, -1448660343.6888638, -1264195510.1611118), (X'676C6974746572696E675F6C6975', X'70657273697374656E745F6F6C6976696572', 6741779968.943846, -3239809989.227495, -1026074003.5506897, 4654600514.871752);\",\n\"DELETE FROM super_becky WHERE (engrossing_berger < X'6566651A3C70278D4E200657551D8071A1' AND competitive_petit > 1236742147.9451914);\",\n\"INSERT INTO super_becky VALUES (X'6661766F7261626C655F726569746D616E', X'64657465726D696E65645F726974746572', -7412553243.829927, -7572665195.290464, 7879603411.222157, 3706943306.5691853), (X'70657273697374656E745F6E6F6C616E', X'676C6974746572696E675F73686570617264', 7028261282.277422, -2064164782.3494844, -5244048504.507779, -2399526243.005843), (X'6B6E6F776C6564676561626C655F70617474656E', X'70726F66696369656E745F726F7365627261756768', 3713056763.583538, 3919834206.566164, -6306779387.430006, -9939464323.995546), (X'616461707461626C655F7172757A', X'696E7175697369746976655F68617261776179', 6519349690.299835, -9977624623.820414, 7500579325.440605, -8118341251.362242);\",\n\"INSERT INTO super_becky VALUES (X'636F6E73696465726174655F756E696F6E', X'6E6963655F6573736578', -1497385534.8720198, 9957688503.242973, 9191804202.566128, -179015615.7117195), (X'666169746866756C5F626F776C656773', X'6361707469766174696E675F6D6367697272', 893707300.1576138, 3381656294.246702, 6884723724.381908, 6248331214.701559), (X'6B6E6F776C6564676561626C655F70656E6E61', X'6B696E645F616A697468', -3335162603.6574974, 1812878172.8505402, 5115606679.658335, -5690100280.808182), (X'617765736F6D655F77696E7374616E6C6579', X'70726F706974696F75735F6361726173736F', -7395576292.503981, 4956546102.029215, -1468521769.7486448, -2968223925.60355), (X'636F75726167656F75735F77617266617265', X'74686F7567687466756C5F7361707068697265', 7052982930.566017, -9806098174.104418, -6910398936.377775, -4041963031.766964), (X'657863656C6C656E745F6B62', X'626C69746865736F6D655F666F75747A6F706F756C6F73', 6142173202.994768, 5193126957.544125, -7522202722.983735, -1659088056.594862), (X'7374756E6E696E675F6E6576616461', X'626F756E746966756C5F627572746F6E', -3822097036.7628613, -3458840259.240303, 2544472236.86788, 6928890176.466003);\",\n\"INSERT INTO super_becky VALUES (X'706572736F6E61626C655F646D69747269', X'776F6E64726F75735F6133796F', 2651932559.0077076, 811299402.3174248, -8271909238.671928, 6761098864.189909);\",\n\"INSERT INTO super_becky VALUES (X'726F7573696E675F6B6C6166657461', X'64617A7A6C696E675F6B6E617070', 9370628891.439335, -5923332007.253168, -2763161830.5880013, -9156194881.875952), (X'656666696369656E745F6C6576656C6C6572', X'616C6C7572696E675F706561636F7474', 3102641409.8314342, 2838360181.628153, 2466271662.169607, 1015942181.844162), (X'6469706C6F6D617469635F7065726B696E73', X'726F7573696E675F6172616269', -1551071129.022499, -8079487600.186886, 7832984580.070087, -6785993247.895652), (X'626F756E746966756C5F6D656D62657273', X'706F77657266756C5F70617269737369', 9226031830.72445, 7012021503.536997, -2297349030.108919, -2738320055.4710903), (X'676F7267656F75735F616E6172636F7469636F', X'68656C7066756C5F7765696C616E64', -8394163480.676959, -2978605095.699134, -6439355448.021704, 9137308022.281273), (X'616666656374696F6E6174655F70726F6C65696E666F', X'706C75636B795F73616E7A', 3546758708.3524914, -1870964264.9353771, 338752565.3643894, -3908023657.299715), (X'66756E6E795F706F70756C61697265', X'6F75747374616E64696E675F626576696E67746F6E', -1533858145.408224, 6164225076.710373, 8419445987.622173, 584555253.6852646), (X'76697669645F6D7474', X'7368696D6D6572696E675F70616F6E65737361', 5512251366.193035, -8680583180.123213, -4445968638.153208, -3274009935.4229546);\",\n\"INSERT INTO super_becky VALUES (X'7068696C6F736F70686963616C5F686F7264', X'657863656C6C656E745F67757373656C7370726F757473', -816909447.0240917, -3614686681.8786583, 7701617524.26067, -4541962047.183721), (X'616D6961626C655F69676E6174696576', X'6D61676E69666963656E745F70726F76696E6369616C69', -1318532883.847702, -4918966075.976474, -7601723171.33518, -3515747704.3847466), (X'70726F66696369656E745F32303137', X'66756E6E795F6E77', -1264540201.518032, 8227396547.578808, 6245093925.183641, -8368355328.110817);\",\n\"INSERT INTO super_becky VALUES (X'77696C6C696E675F6E6F6B6B65', X'726F6D616E7469635F677579616E61', 6618610796.3707695, -3814565359.1524105, 1663106272.4565296, -4175107840.768817), (X'72656C617865645F7061766C6F76', X'64657465726D696E65645F63686F646F726B6F6666', -3350029338.034504, -3520837855.4619064, 3375167499.631817, -8866806483.714607), (X'616D706C655F67696464696E6773', X'667269656E646C795F6A6F686E', 1458864959.9942684, 1344208968.0486107, 9335156635.91314, -6180643697.918882), (X'72656C617865645F6C65726F79', X'636F75726167656F75735F6E6F72646772656E', -5164986537.499656, 8820065797.720875, 6146530425.891005, 6949241471.958189), (X'666F63757365645F656D6D61', X'696D6167696E61746976655F6C6F6E67', -9587619060.80035, 6128068142.184402, 6765196076.956905, 800226302.7983418);\",\n\"INSERT INTO super_becky VALUES (X'616D626974696F75735F736F6E67', X'706572666563745F6761686D616E', 4989979180.706432, -9374266591.537058, 314459621.2820797, -3200029490.9553604), (X'666561726C6573735F626C6174', X'676C697374656E696E675F616374696F6E', -8512203612.903147, -7625581186.013805, -9711122307.234787, -301590929.32751083), (X'617765736F6D655F6669646573', X'666169746866756C5F63756E6E696E6768616D', -1428228887.9205084, 7669883854.400173, 5604446195.905277, -1509311057.9653416), (X'68756D6F726F75735F77697468647261776E', X'62726561746874616B696E675F7472617562656C', -7292778713.676636, -6728132503.529593, 2805341768.7252483, 330416975.2300949);\",\n\"INSERT INTO super_becky VALUES (X'677265676172696F75735F696873616E', X'7374656C6C61725F686172746D616E', 8819210651.1988, 5298459883.813452, 7293544377.958424, 460475869.72971725), (X'696E736967687466756C5F62657765726E69747A', X'676C65616D696E675F64656E736C6F77', -6911957282.193239, 1754196756.2193146, -6316860403.693853, -3094020672.236368), (X'6D6972746866756C5F616D6265727261656B656C6C79', X'68756D6F726F75735F6772617665', 1785574023.0269203, -372056983.82761574, 4133719439.9538956, 9374053482.066044), (X'76697669645F736169747461', X'7761726D686561727465645F696E656469746173', 2787071361.6099434, 9663839418.553448, -5934098589.901047, -9774745509.608858), (X'61646570745F6F6375727279', X'6C696B61626C655F726569746D616E', -3098540915.1310825, 5460848322.672174, -6012867197.519758, 6769770087.661135), (X'696E646570656E64656E745F6F', X'656C6567616E745F726F6F726461', 1462542860.3143978, 3360904654.2464733, 5458876201.665213, -5522844849.529962), (X'72656D61726B61626C655F626F6B616E69', X'6F70656E5F6D696E6465645F686F72726F78', 7589481760.867031, 7970075121.546291, 7513467575.5213585, 9663061478.289227), (X'636F6E666964656E745F6C616479', X'70617373696F6E6174655F736B726F7A6974736B79', 8266917234.53915, -7172933478.625412, 309854059.94031143, -8309837814.497616);\",\n\"DELETE FROM super_becky WHERE (competitive_petit != 8725256604.165474 OR engrossing_rexroth > -3607424615.7839313 OR plucky_chai < X'726F7573696E675F6216E20375');\",\n\"INSERT INTO super_becky VALUES (X'7368696E696E675F736F6C69646169726573', X'666561726C6573735F63617264616E', -170727879.20838165, 2744601113.384678, 5676912434.941502, 6757573601.657997), (X'636F75726167656F75735F706C616E636865', X'696E646570656E64656E745F636172736F6E', -6271723086.761938, -180566679.7470188, -1285774632.134449, 1359665735.7842407), (X'677265676172696F75735F7374616D61746F76', X'7374756E6E696E675F77696C64726F6F7473', -6210238866.953484, 2492683045.8287067, -9688894361.68205, 5420275482.048567), (X'696E646570656E64656E745F6F7267616E697A6572', X'676C6974746572696E675F736F72656C', 9291163783.3073, -6843003475.769236, -1320245894.772686, -5023483808.044955), (X'676C6F77696E675F6E65736963', X'676C65616D696E675F746F726D6579', 829526382.8027191, 9365690945.1316, 4761505764.826195, -4149154965.0024815), (X'616C6C7572696E675F646F637472696E65', X'6E6963655F636C6561766572', 3896644979.981762, -288600448.8016701, 9462856570.130062, -909633752.5993862);\",\n        ];\n\n        for query in queries {\n            let mut stmt = conn.query(query).unwrap().unwrap();\n            loop {\n                let row = stmt.step().expect(\"step\");\n                match row {\n                    StepResult::Done => {\n                        break;\n                    }\n                    _ => {\n                        tracing::debug!(\"row {:?}\", row);\n                    }\n                }\n            }\n        }\n    }\n\n    #[test]\n    pub fn test_free_space() {\n        let db = get_database();\n        let conn = db.connect().unwrap();\n        let page = get_page(2);\n\n        let page_contents = page.get_contents();\n        let header_size = 8;\n        let usable_space = 4096;\n\n        let regs = &[Register::Value(Value::from_i64(0))];\n        let record = ImmutableRecord::from_registers(regs, regs.len());\n        let payload = add_record(0, 0, page.clone(), record, &conn);\n        let free = compute_free_space(page_contents, usable_space).unwrap();\n        assert_eq!(free, 4096 - payload.len() - 2 - header_size);\n    }\n\n    #[test]\n    pub fn test_defragment_1() {\n        let db = get_database();\n        let conn = db.connect().unwrap();\n\n        let page = get_page(2);\n\n        let page_contents = page.get_contents();\n        let usable_space = 4096;\n\n        let regs = &[Register::Value(Value::from_i64(0))];\n        let record = ImmutableRecord::from_registers(regs, regs.len());\n        let payload = add_record(0, 0, page.clone(), record, &conn);\n\n        assert_eq!(page_contents.cell_count(), 1);\n        defragment_page(page_contents, usable_space, 4).unwrap();\n        assert_eq!(page_contents.cell_count(), 1);\n        let (start, len) = page_contents.cell_get_raw_region(0, usable_space).unwrap();\n        let buf = page_contents.as_ptr();\n        assert_eq!(&payload, &buf[start..start + len]);\n    }\n\n    #[test]\n    pub fn test_insert_drop_insert() {\n        let db = get_database();\n        let conn = db.connect().unwrap();\n\n        let page = get_page(2);\n\n        let page_contents = page.get_contents();\n        let usable_space = 4096;\n\n        let regs = &[\n            Register::Value(Value::from_i64(0)),\n            Register::Value(Value::Text(Text::new(\"aaaaaaaa\"))),\n        ];\n        let record = ImmutableRecord::from_registers(regs, regs.len());\n        let _ = add_record(0, 0, page.clone(), record, &conn);\n\n        assert_eq!(page_contents.cell_count(), 1);\n        drop_cell(page_contents, 0, usable_space).unwrap();\n        assert_eq!(page_contents.cell_count(), 0);\n\n        let regs = &[Register::Value(Value::from_i64(0))];\n        let record = ImmutableRecord::from_registers(regs, regs.len());\n        let payload = add_record(0, 0, page.clone(), record, &conn);\n        assert_eq!(page_contents.cell_count(), 1);\n\n        let (start, len) = page_contents.cell_get_raw_region(0, usable_space).unwrap();\n        let buf = page_contents.as_ptr();\n        assert_eq!(&payload, &buf[start..start + len]);\n    }\n\n    #[test]\n    pub fn test_insert_drop_insert_multiple() {\n        let db = get_database();\n        let conn = db.connect().unwrap();\n\n        let page = get_page(2);\n\n        let page_contents = page.get_contents();\n        let usable_space = 4096;\n\n        let regs = &[\n            Register::Value(Value::from_i64(0)),\n            Register::Value(Value::Text(Text::new(\"aaaaaaaa\"))),\n        ];\n        let record = ImmutableRecord::from_registers(regs, regs.len());\n        let _ = add_record(0, 0, page.clone(), record, &conn);\n\n        for _ in 0..100 {\n            assert_eq!(page_contents.cell_count(), 1);\n            drop_cell(page_contents, 0, usable_space).unwrap();\n            assert_eq!(page_contents.cell_count(), 0);\n\n            let regs = &[Register::Value(Value::from_i64(0))];\n            let record = ImmutableRecord::from_registers(regs, regs.len());\n            let payload = add_record(0, 0, page.clone(), record, &conn);\n            assert_eq!(page_contents.cell_count(), 1);\n\n            let (start, len) = page_contents.cell_get_raw_region(0, usable_space).unwrap();\n            let buf = page_contents.as_ptr();\n            assert_eq!(&payload, &buf[start..start + len]);\n        }\n    }\n\n    #[test]\n    pub fn test_drop_a_few_insert() {\n        let db = get_database();\n        let conn = db.connect().unwrap();\n\n        let page = get_page(2);\n\n        let page_contents = page.get_contents();\n        let usable_space = 4096;\n\n        let regs = &[Register::Value(Value::from_i64(0))];\n        let record = ImmutableRecord::from_registers(regs, regs.len());\n        let payload = add_record(0, 0, page.clone(), record, &conn);\n        let regs = &[Register::Value(Value::from_i64(1))];\n        let record = ImmutableRecord::from_registers(regs, regs.len());\n        let _ = add_record(1, 1, page.clone(), record, &conn);\n        let regs = &[Register::Value(Value::from_i64(2))];\n        let record = ImmutableRecord::from_registers(regs, regs.len());\n        let _ = add_record(2, 2, page.clone(), record, &conn);\n\n        drop_cell(page_contents, 1, usable_space).unwrap();\n        drop_cell(page_contents, 1, usable_space).unwrap();\n\n        ensure_cell(page_contents, 0, &payload);\n    }\n\n    #[test]\n    pub fn test_fuzz_victim_1() {\n        let db = get_database();\n        let conn = db.connect().unwrap();\n\n        let page = get_page(2);\n\n        let page_contents = page.get_contents();\n        let usable_space = 4096;\n\n        let regs = &[Register::Value(Value::from_i64(0))];\n        let record = ImmutableRecord::from_registers(regs, regs.len());\n        let _ = add_record(0, 0, page.clone(), record, &conn);\n\n        let regs = &[Register::Value(Value::from_i64(0))];\n        let record = ImmutableRecord::from_registers(regs, regs.len());\n        let _ = add_record(0, 0, page.clone(), record, &conn);\n        drop_cell(page_contents, 0, usable_space).unwrap();\n\n        defragment_page(page_contents, usable_space, 4).unwrap();\n\n        let regs = &[Register::Value(Value::from_i64(0))];\n        let record = ImmutableRecord::from_registers(regs, regs.len());\n        let _ = add_record(0, 1, page.clone(), record, &conn);\n\n        drop_cell(page_contents, 0, usable_space).unwrap();\n\n        let regs = &[Register::Value(Value::from_i64(0))];\n        let record = ImmutableRecord::from_registers(regs, regs.len());\n        let _ = add_record(0, 1, page.clone(), record, &conn);\n    }\n\n    #[test]\n    pub fn test_fuzz_victim_2() {\n        let db = get_database();\n        let conn = db.connect().unwrap();\n\n        let page = get_page(2);\n        let usable_space = 4096;\n        let insert = |pos, page| {\n            let regs = &[Register::Value(Value::from_i64(0))];\n            let record = ImmutableRecord::from_registers(regs, regs.len());\n            let _ = add_record(0, pos, page, record, &conn);\n        };\n        let drop = |pos, page| {\n            drop_cell(page, pos, usable_space).unwrap();\n        };\n        let defragment = |page| {\n            defragment_page(page, usable_space, 4).unwrap();\n        };\n\n        defragment(page.get_contents());\n        defragment(page.get_contents());\n        insert(0, page.clone());\n        drop(0, page.get_contents());\n        insert(0, page.clone());\n        drop(0, page.get_contents());\n        insert(0, page.clone());\n        defragment(page.get_contents());\n        defragment(page.get_contents());\n        drop(0, page.get_contents());\n        defragment(page.get_contents());\n        insert(0, page.clone());\n        drop(0, page.get_contents());\n        insert(0, page.clone());\n        insert(1, page.clone());\n        insert(1, page.clone());\n        insert(0, page.clone());\n        drop(3, page.get_contents());\n        drop(2, page.get_contents());\n        compute_free_space(page.get_contents(), usable_space).unwrap();\n    }\n\n    #[test]\n    pub fn test_fuzz_victim_3() {\n        let db = get_database();\n        let conn = db.connect().unwrap();\n\n        let page = get_page(2);\n        let usable_space = 4096;\n        let insert = |pos, page| {\n            let regs = &[Register::Value(Value::from_i64(0))];\n            let record = ImmutableRecord::from_registers(regs, regs.len());\n            let _ = add_record(0, pos, page, record, &conn);\n        };\n        let drop = |pos, page| {\n            drop_cell(page, pos, usable_space).unwrap();\n        };\n        let defragment = |page| {\n            defragment_page(page, usable_space, 4).unwrap();\n        };\n        let regs = &[Register::Value(Value::from_i64(0))];\n        let record = ImmutableRecord::from_registers(regs, regs.len());\n        let mut payload: Vec<u8> = Vec::new();\n        let mut fill_cell_payload_state = FillCellPayloadState::Start;\n        run_until_done(\n            || {\n                fill_cell_payload(\n                    &PinGuard::new(page.clone()),\n                    Some(0),\n                    &mut payload,\n                    0,\n                    &record,\n                    4096,\n                    conn.pager.load().clone(),\n                    &mut fill_cell_payload_state,\n                )\n            },\n            &conn.pager.load().clone(),\n        )\n        .unwrap();\n\n        insert(0, page.clone());\n        defragment(page.get_contents());\n        insert(0, page.clone());\n        defragment(page.get_contents());\n        insert(0, page.clone());\n        drop(2, page.get_contents());\n        drop(0, page.get_contents());\n        let free = compute_free_space(page.get_contents(), usable_space).unwrap();\n        let total_size = payload.len() + 2;\n        assert_eq!(\n            free,\n            usable_space - page.get_contents().header_size() - total_size\n        );\n        dbg!(free);\n    }\n\n    #[test]\n    pub fn btree_insert_sequential() {\n        let (pager, root_page, _, _) = empty_btree();\n        let mut keys = Vec::new();\n        let num_columns = 5;\n\n        for i in 0..10000 {\n            let mut cursor = BTreeCursor::new_table(pager.clone(), root_page, num_columns);\n            tracing::info!(\"INSERT INTO t VALUES ({});\", i,);\n            let regs = &[Register::Value(Value::from_i64(i))];\n            let value = ImmutableRecord::from_registers(regs, regs.len());\n            tracing::trace!(\"before insert {}\", i);\n            run_until_done(\n                || {\n                    let key = SeekKey::TableRowId(i);\n                    cursor.seek(key, SeekOp::GE { eq_only: true })\n                },\n                pager.deref(),\n            )\n            .unwrap();\n            run_until_done(\n                || cursor.insert(&BTreeKey::new_table_rowid(i, Some(&value))),\n                pager.deref(),\n            )\n            .unwrap();\n            keys.push(i);\n        }\n        if matches!(validate_btree(pager.clone(), root_page), (_, false)) {\n            panic!(\"invalid btree\");\n        }\n        tracing::trace!(\n            \"=========== btree ===========\\n{}\\n\\n\",\n            format_btree(pager.clone(), root_page, 0)\n        );\n        for key in keys.iter() {\n            let mut cursor = BTreeCursor::new_table(pager.clone(), root_page, num_columns);\n            let key = Value::from_i64(*key);\n            let exists = run_until_done(|| cursor.exists(&key), pager.deref()).unwrap();\n            assert!(exists, \"key not found {key}\");\n        }\n    }\n\n    #[test]\n    pub fn test_big_payload_compute_free() {\n        let db = get_database();\n        let conn = db.connect().unwrap();\n\n        let page = get_page(2);\n        let usable_space = 4096;\n        let regs = &[Register::Value(Value::Blob(vec![0; 3600]))];\n        let record = ImmutableRecord::from_registers(regs, regs.len());\n        let mut payload: Vec<u8> = Vec::new();\n        let mut fill_cell_payload_state = FillCellPayloadState::Start;\n        run_until_done(\n            || {\n                fill_cell_payload(\n                    &PinGuard::new(page.clone()),\n                    Some(0),\n                    &mut payload,\n                    0,\n                    &record,\n                    4096,\n                    conn.pager.load().clone(),\n                    &mut fill_cell_payload_state,\n                )\n            },\n            &conn.pager.load().clone(),\n        )\n        .unwrap();\n        insert_into_cell(page.get_contents(), &payload, 0, 4096).unwrap();\n        let free = compute_free_space(page.get_contents(), usable_space).unwrap();\n        let total_size = payload.len() + 2;\n        assert_eq!(\n            free,\n            usable_space - page.get_contents().header_size() - total_size\n        );\n        dbg!(free);\n    }\n\n    #[test]\n    pub fn test_delete_balancing() {\n        // What does this test do:\n        // 1. Insert 10,000 rows of ~15 byte payload each. This creates\n        //    nearly 40 pages (10,000 * 15 / 4096) and 240 rows per page.\n        // 2. Delete enough rows to create empty/ nearly empty pages to trigger balancing\n        //    (verified this in SQLite).\n        // 3. Verify validity/integrity of btree after deleting and also verify that these\n        //    values are actually deleted.\n\n        let (pager, root_page, _, _) = empty_btree();\n        let num_columns = 5;\n\n        // Insert 10,000 records in to the BTree.\n        for i in 1..=10000 {\n            let mut cursor = BTreeCursor::new_table(pager.clone(), root_page, num_columns);\n            let regs = &[Register::Value(Value::Text(Text::new(\"hello world\")))];\n            let value = ImmutableRecord::from_registers(regs, regs.len());\n\n            run_until_done(\n                || {\n                    let key = SeekKey::TableRowId(i);\n                    cursor.seek(key, SeekOp::GE { eq_only: true })\n                },\n                pager.deref(),\n            )\n            .unwrap();\n\n            run_until_done(\n                || cursor.insert(&BTreeKey::new_table_rowid(i, Some(&value))),\n                pager.deref(),\n            )\n            .unwrap();\n        }\n\n        if let (_, false) = validate_btree(pager.clone(), root_page) {\n            panic!(\"Invalid B-tree after insertion\");\n        }\n        let num_columns = 5;\n\n        // Delete records with 500 <= key <= 3500\n        for i in 500..=3500 {\n            let mut cursor = BTreeCursor::new_table(pager.clone(), root_page, num_columns);\n            let seek_key = SeekKey::TableRowId(i);\n\n            let seek_result = run_until_done(\n                || cursor.seek(seek_key.clone(), SeekOp::GE { eq_only: true }),\n                pager.deref(),\n            )\n            .unwrap();\n\n            if matches!(seek_result, SeekResult::Found) {\n                run_until_done(|| cursor.delete(), pager.deref()).unwrap();\n            }\n        }\n\n        // Verify that records with key < 500 and key > 3500 still exist in the BTree.\n        for i in 1..=10000 {\n            if (500..=3500).contains(&i) {\n                continue;\n            }\n\n            let mut cursor = BTreeCursor::new_table(pager.clone(), root_page, num_columns);\n            let key = Value::from_i64(i);\n            let exists = run_until_done(|| cursor.exists(&key), pager.deref()).unwrap();\n            assert!(exists, \"Key {i} should exist but doesn't\");\n        }\n\n        // Verify the deleted records don't exist.\n        for i in 500..=3500 {\n            let mut cursor = BTreeCursor::new_table(pager.clone(), root_page, num_columns);\n            let key = Value::from_i64(i);\n            let exists = run_until_done(|| cursor.exists(&key), pager.deref()).unwrap();\n            assert!(!exists, \"Deleted key {i} still exists\");\n        }\n    }\n\n    #[test]\n    pub fn test_overflow_cells() {\n        let iterations = 10_usize;\n        let mut huge_texts = Vec::new();\n        for i in 0..iterations {\n            let mut huge_text = String::new();\n            for _j in 0..8192 {\n                huge_text.push((b'A' + i as u8) as char);\n            }\n            huge_texts.push(huge_text);\n        }\n\n        let (pager, root_page, _, _) = empty_btree();\n        let num_columns = 5;\n\n        for (i, huge_text) in huge_texts.iter().enumerate().take(iterations) {\n            let mut cursor = BTreeCursor::new_table(pager.clone(), root_page, num_columns);\n            tracing::info!(\"INSERT INTO t VALUES ({});\", i,);\n            let regs = &[Register::Value(Value::Text(Text::new(huge_text.clone())))];\n            let value = ImmutableRecord::from_registers(regs, regs.len());\n            tracing::trace!(\"before insert {}\", i);\n            tracing::debug!(\n                \"=========== btree before ===========\\n{}\\n\\n\",\n                format_btree(pager.clone(), root_page, 0)\n            );\n            run_until_done(\n                || {\n                    let key = SeekKey::TableRowId(i as i64);\n                    cursor.seek(key, SeekOp::GE { eq_only: true })\n                },\n                pager.deref(),\n            )\n            .unwrap();\n            run_until_done(\n                || cursor.insert(&BTreeKey::new_table_rowid(i as i64, Some(&value))),\n                pager.deref(),\n            )\n            .unwrap();\n            tracing::debug!(\n                \"=========== btree after ===========\\n{}\\n\\n\",\n                format_btree(pager.clone(), root_page, 0)\n            );\n        }\n        let mut cursor = BTreeCursor::new_table(pager.clone(), root_page, num_columns);\n        let _c = cursor.move_to_root().unwrap();\n        for i in 0..iterations {\n            run_until_done(|| cursor.next(), pager.deref()).unwrap();\n            let has_next = cursor.has_record();\n            if !has_next {\n                panic!(\"expected Some(rowid) but got {:?}\", cursor.has_record());\n            };\n            let rowid = run_until_done(|| cursor.rowid(), pager.deref())\n                .unwrap()\n                .unwrap();\n            assert_eq!(rowid, i as i64, \"got!=expected\");\n        }\n    }\n\n    fn run_until_done<T>(action: impl FnMut() -> Result<IOResult<T>>, pager: &Pager) -> Result<T> {\n        pager.io.block(action)\n    }\n\n    #[test]\n    fn test_free_array() {\n        let (mut rng, seed) = rng_from_time_or_env();\n        tracing::info!(\"seed={}\", seed);\n\n        const ITERATIONS: usize = 10000;\n        for _ in 0..ITERATIONS {\n            let mut cell_array = CellArray {\n                cell_payloads: Vec::new(),\n                cell_count_per_page_cumulative: [0; MAX_NEW_SIBLING_PAGES_AFTER_BALANCE],\n            };\n            let mut cells_cloned = Vec::new();\n            let (pager, _, _, _) = empty_btree();\n            let page_type = PageType::TableLeaf;\n            let page = run_until_done(|| pager.allocate_page(), &pager).unwrap();\n            btree_init_page(&page, page_type, 0, pager.usable_space());\n\n            let mut size = (rng.next_u64() % 100) as u16;\n            let mut i = 0;\n            // add a bunch of cells\n            while compute_free_space(page.get_contents(), pager.usable_space()).unwrap()\n                >= size as usize + 10\n            {\n                insert_cell(i, size, page.clone(), pager.clone());\n                i += 1;\n                size = (rng.next_u64() % 1024) as u16;\n            }\n\n            // Create cell array with references to cells inserted\n            let contents = page.get_contents();\n            for cell_idx in 0..contents.cell_count() {\n                let buf = contents.as_ptr();\n                let (start, len) = contents\n                    .cell_get_raw_region(cell_idx, pager.usable_space())\n                    .unwrap();\n                cell_array\n                    .cell_payloads\n                    .push(to_static_buf(&mut buf[start..start + len]));\n                cells_cloned.push(buf[start..start + len].to_vec());\n            }\n\n            debug_validate_cells!(contents, pager.usable_space());\n\n            // now free a prefix or suffix of cells added\n            let cells_before_free = contents.cell_count();\n            let size = rng.next_u64() as usize % cells_before_free;\n            let prefix = rng.next_u64() % 2 == 0;\n            let start = if prefix {\n                0\n            } else {\n                contents.cell_count() - size\n            };\n            let removed =\n                page_free_array(contents, start, size, &cell_array, pager.usable_space()).unwrap();\n            // shift if needed\n            if prefix {\n                shift_cells_left(contents, cells_before_free, removed);\n            }\n\n            assert_eq!(removed, size);\n            assert_eq!(contents.cell_count(), cells_before_free - size);\n            #[cfg(debug_assertions)]\n            debug_validate_cells_core(contents, pager.usable_space());\n            // check cells are correct\n            let mut cell_idx_cloned = if prefix { size } else { 0 };\n            for cell_idx in 0..contents.cell_count() {\n                let buf = contents.as_ptr();\n                let (start, len) = contents\n                    .cell_get_raw_region(cell_idx, pager.usable_space())\n                    .unwrap();\n                let cell_in_page = &buf[start..start + len];\n                let cell_in_array = &cells_cloned[cell_idx_cloned];\n                assert_eq!(cell_in_page, cell_in_array);\n                cell_idx_cloned += 1;\n            }\n        }\n    }\n\n    fn insert_cell(cell_idx: u64, size: u16, page: PageRef, pager: Arc<Pager>) {\n        let mut payload = Vec::new();\n        let regs = &[Register::Value(Value::Blob(vec![0; size as usize]))];\n        let record = ImmutableRecord::from_registers(regs, regs.len());\n        let mut fill_cell_payload_state = FillCellPayloadState::Start;\n        let contents = page.get_contents();\n        run_until_done(\n            || {\n                fill_cell_payload(\n                    &PinGuard::new(page.clone()),\n                    Some(cell_idx as i64),\n                    &mut payload,\n                    cell_idx as usize,\n                    &record,\n                    pager.usable_space(),\n                    pager.clone(),\n                    &mut fill_cell_payload_state,\n                )\n            },\n            &pager,\n        )\n        .unwrap();\n        insert_into_cell(contents, &payload, cell_idx as usize, pager.usable_space()).unwrap();\n    }\n\n    /// Strict property tests for page-level btree mutations.\n    ///\n    /// These tests model expected cell bytes and check that every mutation\n    /// preserves both byte-level payload contents and page-layout invariants.\n    mod property_tests {\n        use std::collections::HashSet;\n\n        use quickcheck::{quickcheck, TestResult};\n\n        use crate::storage::btree::{\n            compute_free_space, defragment_page, drop_cell, insert_into_cell,\n        };\n        use crate::storage::sqlite3_ondisk::{write_varint, PageContent, CELL_PTR_SIZE_BYTES};\n        use crate::PageRef;\n\n        use super::get_page;\n\n        const PAGE_SIZE: usize = 4096;\n        const MIN_INSERTED_CELLS: usize = 6;\n\n        struct FillOutcome {\n            expected: Vec<Vec<u8>>,\n            had_middle_insert: bool,\n        }\n\n        /// Convert arbitrary fuzz bytes into bounded payload sizes that are small enough\n        /// to produce many cells and varied freeblock behavior in a single page.\n        fn normalize_sizes(raw: &[u8]) -> Vec<usize> {\n            raw.iter()\n                .take(300)\n                .map(|v| ((*v as usize) % 220) + 1)\n                .collect()\n        }\n\n        /// Validate strict page invariants after each mutation.\n        ///\n        /// Checks:\n        /// - pointer array/header consistency:\n        ///   `unallocated_region_start` must equal\n        ///   `cell_pointer_array_offset + (cell_count * 2)`, so the header and pointer-array\n        ///   metadata agree on where unallocated space begins.\n        /// - structural bounds:\n        ///   every cell and freeblock must lie fully within `[cell_content_area, usable_space)`.\n        /// - pointer uniqueness:\n        ///   no two cell-pointer entries may reference the same cell start offset.\n        /// - interval non-overlap:\n        ///   cell byte ranges and freeblock ranges must be disjoint; overlap means corruption.\n        /// - freeblock chain validity:\n        ///   the linked list must be strictly ascending by offset and must not contain cycles.\n        /// - accounting equality:\n        ///   independently computed free space from layout pieces must exactly equal\n        ///   `compute_free_space(page, usable_space)`.\n        /// - logical data preservation (optional):\n        ///   when an expected model is provided, each on-page cell must match expected bytes\n        ///   at the same logical index.\n        fn strict_validate_page(\n            page: &PageContent,\n            usable_space: usize,\n            expected_cells: Option<&[Vec<u8>]>,\n        ) {\n            let cell_count = page.cell_count();\n            let cell_content_area = page.cell_content_area() as usize;\n            let unallocated_start = page.unallocated_region_start();\n            let ptr_start = page.cell_pointer_array_offset();\n            let expected_unallocated_start = ptr_start + (cell_count * CELL_PTR_SIZE_BYTES);\n\n            assert_eq!(\n                unallocated_start, expected_unallocated_start,\n                \"unallocated region start inconsistent with cell pointer array\"\n            );\n            assert!(\n                unallocated_start <= cell_content_area,\n                \"cell pointer array overlaps cell content area\"\n            );\n            assert!(\n                cell_content_area <= usable_space,\n                \"cell content area beyond usable space\"\n            );\n            assert!(\n                page.num_frag_free_bytes() <= 60,\n                \"fragmented free bytes exceed SQLite limit\"\n            );\n\n            let mut intervals = Vec::<(usize, usize, &'static str)>::new();\n            let mut ptrs = HashSet::new();\n            for i in 0..cell_count {\n                let ptr_offset = ptr_start + (i * CELL_PTR_SIZE_BYTES);\n                let raw_ptr = page.read_u16_no_offset(ptr_offset) as usize;\n                let (start, len) = page.cell_get_raw_region(i, usable_space).unwrap();\n                assert_eq!(\n                    raw_ptr, start,\n                    \"cell pointer does not match parsed cell start\"\n                );\n                assert!(len >= 2, \"cell too small\");\n                assert!(\n                    start >= cell_content_area,\n                    \"cell starts before cell content area\"\n                );\n                assert!(\n                    start + len <= usable_space,\n                    \"cell extends beyond usable space\"\n                );\n                assert!(ptrs.insert(raw_ptr), \"duplicate cell pointer\");\n                intervals.push((start, start + len, \"cell\"));\n            }\n\n            let mut freeblock_total = 0usize;\n            let mut seen_freeblocks = HashSet::new();\n            let mut cur = page.first_freeblock() as usize;\n            let mut prev = 0usize;\n            while cur != 0 {\n                assert!(\n                    seen_freeblocks.insert(cur),\n                    \"freeblock cycle detected at offset {cur}\"\n                );\n                assert!(\n                    cur >= cell_content_area,\n                    \"freeblock before cell content area\"\n                );\n                assert!(cur + 4 <= usable_space, \"freeblock header out of bounds\");\n                let (next, size_u16) = page.read_freeblock(cur as u16);\n                let size = size_u16 as usize;\n                assert!(size >= 4, \"freeblock size too small\");\n                assert!(\n                    cur + size <= usable_space,\n                    \"freeblock extends beyond usable space\"\n                );\n                if prev != 0 {\n                    assert!(cur > prev, \"freeblocks must be strictly ascending\");\n                }\n                let next_usize = next as usize;\n                if next_usize != 0 {\n                    assert!(next_usize > cur, \"freeblock next pointer not ascending\");\n                }\n                intervals.push((cur, cur + size, \"freeblock\"));\n                freeblock_total += size;\n                prev = cur;\n                cur = next_usize;\n            }\n\n            intervals.sort_by_key(|(start, _, _)| *start);\n            for pair in intervals.windows(2) {\n                let (a_start, a_end, a_kind) = pair[0];\n                let (b_start, _b_end, b_kind) = pair[1];\n                assert!(\n                    a_end <= b_start,\n                    \"interval overlap: {a_kind}@{a_start}..{a_end} overlaps {b_kind}@{b_start}\"\n                );\n            }\n\n            let computed = compute_free_space(page, usable_space).unwrap();\n            let expected_free = (cell_content_area - unallocated_start)\n                + page.num_frag_free_bytes() as usize\n                + freeblock_total;\n            assert_eq!(\n                computed, expected_free,\n                \"compute_free_space mismatch: computed={computed}, expected={expected_free}\"\n            );\n\n            if let Some(expected_cells) = expected_cells {\n                assert_eq!(\n                    cell_count,\n                    expected_cells.len(),\n                    \"cell count mismatch against expected model\"\n                );\n                for (i, expected) in expected_cells.iter().enumerate() {\n                    let (start, len) = page.cell_get_raw_region(i, usable_space).unwrap();\n                    let actual = &page.as_ptr()[start..start + len];\n                    assert_eq!(\n                        actual,\n                        expected.as_slice(),\n                        \"cell bytes mismatch at idx {i}\"\n                    );\n                }\n            }\n        }\n\n        /// Build a valid table-leaf cell:\n        /// [payload_size varint][rowid varint][record(header + blob data)].\n        ///\n        /// The body is synthetic but stable, so byte-level equality checks are deterministic.\n        fn make_table_leaf_cell(rowid: u64, data_size: usize) -> Vec<u8> {\n            let mut cell = Vec::new();\n            let serial_type = (data_size as u64) * 2 + 12;\n\n            let mut header_buf = [0u8; 9];\n            let mut serial_buf = [0u8; 9];\n            let serial_len = write_varint(&mut serial_buf, serial_type);\n            let header_size = 1 + serial_len;\n            let header_size_len = write_varint(&mut header_buf, header_size as u64);\n\n            let mut record = Vec::new();\n            record.extend_from_slice(&header_buf[..header_size_len]);\n            record.extend_from_slice(&serial_buf[..serial_len]);\n            record.extend(vec![0xAB; data_size]);\n\n            let payload_size = record.len() as u64;\n            let mut payload_size_buf = [0u8; 9];\n            let payload_size_len = write_varint(&mut payload_size_buf, payload_size);\n            cell.extend_from_slice(&payload_size_buf[..payload_size_len]);\n\n            let mut rowid_buf = [0u8; 9];\n            let rowid_len = write_varint(&mut rowid_buf, rowid);\n            cell.extend_from_slice(&rowid_buf[..rowid_len]);\n            cell.extend_from_slice(&record);\n            cell\n        }\n\n        /// Execute a modeled insertion workload against one page.\n        ///\n        /// For each insert that fits, mutate both:\n        /// - the real page (via `insert_into_cell`), and\n        /// - the expected model vector at the same index.\n        ///\n        /// `had_middle_insert` ensures we exercised pointer-shift paths, not only appends.\n        fn fill_page_with_model(\n            page: &PageRef,\n            cell_sizes: &[usize],\n            insert_hints: &[u8],\n        ) -> FillOutcome {\n            let mut expected = Vec::new();\n            let mut had_middle_insert = false;\n            let contents = page.get_contents();\n\n            for (i, size) in cell_sizes.iter().copied().enumerate() {\n                let cell = make_table_leaf_cell(i as u64, size);\n                let free = compute_free_space(contents, PAGE_SIZE).unwrap();\n                if cell.len() + CELL_PTR_SIZE_BYTES > free {\n                    continue;\n                }\n\n                let idx = if expected.is_empty() {\n                    0\n                } else {\n                    insert_hints.get(i).copied().unwrap_or(i as u8) as usize % (expected.len() + 1)\n                };\n                if idx < expected.len() {\n                    had_middle_insert = true;\n                }\n\n                insert_into_cell(contents, &cell, idx, PAGE_SIZE).unwrap();\n                expected.insert(idx, cell);\n                strict_validate_page(contents, PAGE_SIZE, Some(&expected));\n            }\n\n            FillOutcome {\n                expected,\n                had_middle_insert,\n            }\n        }\n\n        quickcheck! {\n            // Invariant: arbitrary insert sequences (including middle inserts) preserve exact cell bytes\n            // and keep page layout/accounting valid after every insertion.\n            fn prop_insertions_preserve_exact_cell_bytes(\n                raw_sizes: Vec<u8>,\n                insert_hints: Vec<u8>\n            ) -> TestResult {\n                // Build many small payload sizes from random bytes so one page gets many edits.\n                let cell_sizes = normalize_sizes(&raw_sizes);\n                if cell_sizes.len() < MIN_INSERTED_CELLS {\n                    return TestResult::discard();\n                }\n\n                let page = get_page(2);\n                // Mutate both the real page and the expected-model vector in lock-step.\n                let outcome = fill_page_with_model(&page, &cell_sizes, &insert_hints);\n                // Require enough inserts and at least one middle insert (not append-only).\n                if outcome.expected.len() < MIN_INSERTED_CELLS || !outcome.had_middle_insert {\n                    return TestResult::discard();\n                }\n\n                // Final strict check: metadata + free-space accounting + exact cell bytes.\n                strict_validate_page(page.get_contents(), PAGE_SIZE, Some(&outcome.expected));\n                TestResult::passed()\n            }\n        }\n\n        quickcheck! {\n            // Invariant: every drop operation removes exactly one modeled cell, never mutates surviving\n            // cell bytes, and always preserves freeblock/pointer/free-space structural validity.\n            fn prop_drop_sequence_preserves_model_and_layout(\n                raw_sizes: Vec<u8>,\n                insert_hints: Vec<u8>,\n                drop_ops: Vec<u8>\n            ) -> TestResult {\n                if drop_ops.is_empty() {\n                    return TestResult::discard();\n                }\n\n                // Start from a non-trivial page state built with randomized inserts.\n                let page = get_page(2);\n                let cell_sizes = normalize_sizes(&raw_sizes);\n                let mut outcome = fill_page_with_model(&page, &cell_sizes, &insert_hints);\n                if outcome.expected.len() < MIN_INSERTED_CELLS || !outcome.had_middle_insert {\n                    return TestResult::discard();\n                }\n\n                let contents = page.get_contents();\n                let mut drops_executed = 0usize;\n                for op in drop_ops.iter().take(200) {\n                    // Stop once the model is empty; there is nothing left to drop.\n                    if outcome.expected.is_empty() {\n                        break;\n                    }\n                    // Drop same logical index in model and real page.\n                    let idx = (*op as usize) % outcome.expected.len();\n                    outcome.expected.remove(idx);\n                    drop_cell(contents, idx, PAGE_SIZE).unwrap();\n                    // After each mutation, validate structure and surviving bytes immediately.\n                    strict_validate_page(contents, PAGE_SIZE, Some(&outcome.expected));\n                    drops_executed += 1;\n                }\n\n                if drops_executed == 0 {\n                    // Require at least one real mutation, otherwise this run is not informative.\n                    return TestResult::discard();\n                }\n                TestResult::passed()\n            }\n        }\n\n        quickcheck! {\n            // Invariant: after creating holes via drops, inserting new cells back into the page\n            // preserves all existing bytes and keeps freeblock reuse/allocation safe.\n            fn prop_insert_drop_insert_reuses_space_safely(\n                raw_sizes: Vec<u8>,\n                insert_hints: Vec<u8>,\n                drop_ops: Vec<u8>,\n                new_sizes: Vec<u8>,\n                new_insert_hints: Vec<u8>\n            ) -> TestResult {\n                if drop_ops.is_empty() || new_sizes.is_empty() {\n                    return TestResult::discard();\n                }\n\n                let page = get_page(2);\n                let cell_sizes = normalize_sizes(&raw_sizes);\n                let mut outcome = fill_page_with_model(&page, &cell_sizes, &insert_hints);\n                if outcome.expected.len() < MIN_INSERTED_CELLS {\n                    return TestResult::discard();\n                }\n\n                let contents = page.get_contents();\n                let mut drops_executed = 0usize;\n                // Phase 1: create holes and freeblocks by dropping cells in random positions.\n                for op in drop_ops.iter().take(16) {\n                    if outcome.expected.len() <= 2 {\n                        break;\n                    }\n                    let idx = (*op as usize) % outcome.expected.len();\n                    outcome.expected.remove(idx);\n                    drop_cell(contents, idx, PAGE_SIZE).unwrap();\n                    strict_validate_page(contents, PAGE_SIZE, Some(&outcome.expected));\n                    drops_executed += 1;\n                }\n                if drops_executed == 0 {\n                    return TestResult::discard();\n                }\n\n                let base_rowid = 1_000_000u64 + outcome.expected.len() as u64;\n                let mut inserted = 0usize;\n                // Phase 2: insert new cells back, forcing allocator/freeblock reuse paths.\n                for (i, raw) in new_sizes.iter().take(32).enumerate() {\n                    let size = ((*raw as usize) % 220) + 1;\n                    let cell = make_table_leaf_cell(base_rowid + i as u64, size);\n                    let free = compute_free_space(contents, PAGE_SIZE).unwrap();\n                    if cell.len() + CELL_PTR_SIZE_BYTES > free {\n                        continue;\n                    }\n                    let idx = if outcome.expected.is_empty() {\n                        0\n                    } else {\n                        new_insert_hints\n                            .get(i)\n                            .copied()\n                            .unwrap_or(i as u8) as usize\n                            % (outcome.expected.len() + 1)\n                    };\n                    insert_into_cell(contents, &cell, idx, PAGE_SIZE).unwrap();\n                    outcome.expected.insert(idx, cell);\n                    strict_validate_page(contents, PAGE_SIZE, Some(&outcome.expected));\n                    inserted += 1;\n                }\n\n                if inserted == 0 {\n                    // Require at least one successful re-insert to exercise the target path.\n                    return TestResult::discard();\n                }\n                TestResult::passed()\n            }\n        }\n\n        quickcheck! {\n            // Invariant: full defragmentation is lossless (all live cell bytes unchanged), reaches canonical\n            // no-freeblock/no-fragment state, and is idempotent when applied repeatedly.\n            fn prop_defragment_is_lossless_and_idempotent(\n                raw_sizes: Vec<u8>,\n                insert_hints: Vec<u8>,\n                drop_ops: Vec<u8>\n            ) -> TestResult {\n                if drop_ops.is_empty() {\n                    return TestResult::discard();\n                }\n\n                let page = get_page(2);\n                let cell_sizes = normalize_sizes(&raw_sizes);\n                let mut outcome = fill_page_with_model(&page, &cell_sizes, &insert_hints);\n                if outcome.expected.len() < MIN_INSERTED_CELLS {\n                    // Need enough cells so one drop still leaves a meaningful page state.\n                    return TestResult::discard();\n                }\n\n                let contents = page.get_contents();\n                let mut drops_executed = 0usize;\n                for op in drop_ops.iter().take(40) {\n                    if outcome.expected.len() <= 1 {\n                        break;\n                    }\n                    // Create realistic holes/freeblocks before defragmenting.\n                    let idx = (*op as usize) % outcome.expected.len();\n                    outcome.expected.remove(idx);\n                    drop_cell(contents, idx, PAGE_SIZE).unwrap();\n                    strict_validate_page(contents, PAGE_SIZE, Some(&outcome.expected));\n                    drops_executed += 1;\n                }\n\n                if drops_executed == 0 || outcome.expected.is_empty() {\n                    return TestResult::discard();\n                }\n\n                // First defrag: must preserve live cells and clean freeblock/fragment metadata.\n                defragment_page(contents, PAGE_SIZE, -1).unwrap();\n                strict_validate_page(contents, PAGE_SIZE, Some(&outcome.expected));\n                assert_eq!(contents.first_freeblock(), 0, \"freeblocks remain after defrag\");\n                assert_eq!(contents.num_frag_free_bytes(), 0, \"fragments remain after defrag\");\n\n                // Second defrag should be a no-op on bytes (idempotence).\n                let snapshot_after_first = contents.as_ptr().to_vec();\n                defragment_page(contents, PAGE_SIZE, -1).unwrap();\n                strict_validate_page(contents, PAGE_SIZE, Some(&outcome.expected));\n                assert_eq!(\n                    contents.as_ptr().to_vec(),\n                    snapshot_after_first,\n                    \"defragmentation is not idempotent\"\n                );\n                TestResult::passed()\n            }\n        }\n\n        quickcheck! {\n            // Invariant: for simple freeblock layouts where fast-path is applicable, fast defrag and\n            // full defrag produce the same logical page state and identical serialized cell bytes.\n            fn prop_defragment_fast_matches_full(\n                raw_sizes: Vec<u8>,\n                insert_hints: Vec<u8>,\n                drop_op: u8\n            ) -> TestResult {\n                let cell_sizes = normalize_sizes(&raw_sizes);\n                if cell_sizes.len() < MIN_INSERTED_CELLS {\n                    return TestResult::discard();\n                }\n\n                let page_fast = get_page(2);\n                let mut outcome = fill_page_with_model(&page_fast, &cell_sizes, &insert_hints);\n                if outcome.expected.len() < MIN_INSERTED_CELLS {\n                    return TestResult::discard();\n                }\n\n                // Clone logical state to second page so both start identical.\n                let page_full = get_page(3);\n                let full_contents = page_full.get_contents();\n                for (i, cell) in outcome.expected.iter().enumerate() {\n                    insert_into_cell(full_contents, cell, i, PAGE_SIZE).unwrap();\n                }\n                strict_validate_page(full_contents, PAGE_SIZE, Some(&outcome.expected));\n\n                // Create a single hole => one freeblock, making fast-path eligibility likely.\n                let idx = drop_op as usize % outcome.expected.len();\n                let fast_contents = page_fast.get_contents();\n                outcome.expected.remove(idx);\n                drop_cell(fast_contents, idx, PAGE_SIZE).unwrap();\n                drop_cell(full_contents, idx, PAGE_SIZE).unwrap();\n                strict_validate_page(fast_contents, PAGE_SIZE, Some(&outcome.expected));\n                strict_validate_page(full_contents, PAGE_SIZE, Some(&outcome.expected));\n\n                // Try fast-path on one page and force full-path on the other.\n                defragment_page(fast_contents, PAGE_SIZE, 4).unwrap();\n                defragment_page(full_contents, PAGE_SIZE, -1).unwrap();\n\n                // Both algorithms must preserve the exact same logical model.\n                strict_validate_page(fast_contents, PAGE_SIZE, Some(&outcome.expected));\n                strict_validate_page(full_contents, PAGE_SIZE, Some(&outcome.expected));\n                assert_eq!(fast_contents.cell_count(), full_contents.cell_count());\n                assert_eq!(\n                    compute_free_space(fast_contents, PAGE_SIZE).unwrap(),\n                    compute_free_space(full_contents, PAGE_SIZE).unwrap()\n                );\n                assert_eq!(fast_contents.first_freeblock(), full_contents.first_freeblock());\n                assert_eq!(\n                    fast_contents.num_frag_free_bytes(),\n                    full_contents.num_frag_free_bytes()\n                );\n\n                for i in 0..fast_contents.cell_count() {\n                    let (s1, l1) = fast_contents.cell_get_raw_region(i, PAGE_SIZE).unwrap();\n                    let (s2, l2) = full_contents.cell_get_raw_region(i, PAGE_SIZE).unwrap();\n                    assert_eq!(l1, l2, \"cell {i} length mismatch after defragmentation\");\n                    assert_eq!(\n                        &fast_contents.as_ptr()[s1..s1 + l1],\n                        &full_contents.as_ptr()[s2..s2 + l2],\n                        \"cell {i} bytes mismatch between fast and full defrag\"\n                    );\n                }\n                TestResult::passed()\n            }\n        }\n\n        quickcheck! {\n            // Invariant: dropping all cells and defragmenting returns the page to a canonical empty state\n            // (zero cells, no fragments/freeblocks, content area at end, exact free-space accounting).\n            fn prop_drop_all_then_defrag_returns_canonical_empty_page(\n                raw_sizes: Vec<u8>,\n                insert_hints: Vec<u8>\n            ) -> TestResult {\n                let page = get_page(2);\n                let cell_sizes = normalize_sizes(&raw_sizes);\n                let mut outcome = fill_page_with_model(&page, &cell_sizes, &insert_hints);\n                if outcome.expected.len() < MIN_INSERTED_CELLS {\n                    return TestResult::discard();\n                }\n\n                let contents = page.get_contents();\n                while !outcome.expected.is_empty() {\n                    // Repeatedly drop from the logical front so model and page stay aligned.\n                    outcome.expected.remove(0);\n                    drop_cell(contents, 0, PAGE_SIZE).unwrap();\n                    strict_validate_page(contents, PAGE_SIZE, Some(&outcome.expected));\n                }\n\n                // After all cells are gone, defrag should normalize page to canonical empty form.\n                defragment_page(contents, PAGE_SIZE, -1).unwrap();\n                strict_validate_page(contents, PAGE_SIZE, Some(&[]));\n                assert_eq!(contents.cell_count(), 0);\n                assert_eq!(contents.first_freeblock(), 0);\n                assert_eq!(contents.num_frag_free_bytes(), 0);\n                assert_eq!(contents.cell_content_area() as usize, PAGE_SIZE);\n                assert_eq!(\n                    compute_free_space(contents, PAGE_SIZE).unwrap(),\n                    PAGE_SIZE - contents.header_size(),\n                    \"empty page must expose full free space minus header\"\n                );\n                TestResult::passed()\n            }\n        }\n    }\n\n    /// Corruption-handling properties.\n    ///\n    /// These tests verify that malformed on-page metadata is rejected with\n    /// corruption errors, instead of silently succeeding or panicking.\n    mod corruption_properties {\n        use quickcheck::quickcheck;\n\n        use crate::storage::btree::{compute_free_space, defragment_page, insert_into_cell};\n        use crate::storage::sqlite3_ondisk::write_varint;\n\n        use super::get_page;\n\n        const PAGE_SIZE: usize = 4096;\n\n        fn make_table_leaf_cell(rowid: u64, data_size: usize) -> Vec<u8> {\n            let mut cell = Vec::new();\n            let serial_type = (data_size as u64) * 2 + 12;\n\n            let mut header_buf = [0u8; 9];\n            let mut serial_buf = [0u8; 9];\n            let serial_len = write_varint(&mut serial_buf, serial_type);\n            let header_size = 1 + serial_len;\n            let header_size_len = write_varint(&mut header_buf, header_size as u64);\n\n            let mut record = Vec::new();\n            record.extend_from_slice(&header_buf[..header_size_len]);\n            record.extend_from_slice(&serial_buf[..serial_len]);\n            record.extend(vec![0xCC; data_size]);\n\n            let payload_size = record.len() as u64;\n            let mut payload_size_buf = [0u8; 9];\n            let payload_size_len = write_varint(&mut payload_size_buf, payload_size);\n            cell.extend_from_slice(&payload_size_buf[..payload_size_len]);\n\n            let mut rowid_buf = [0u8; 9];\n            let rowid_len = write_varint(&mut rowid_buf, rowid);\n            cell.extend_from_slice(&rowid_buf[..rowid_len]);\n            cell.extend_from_slice(&record);\n            cell\n        }\n\n        quickcheck! {\n            // Desired invariant: malformed freeblock pointer values should return Corrupt errors.\n            fn prop_compute_free_space_returns_err_when_first_freeblock_is_invalid(seed: u16) -> bool {\n                let page = get_page(2);\n                let contents = page.get_contents();\n                let bad_ptr = ((seed as usize % (PAGE_SIZE - 1)) + 1) as u16; // 1..=4095, always < initial cell_content_area (4096)\n                contents.write_first_freeblock(bad_ptr);\n                compute_free_space(contents, PAGE_SIZE).is_err()\n            }\n        }\n\n        quickcheck! {\n            // Desired invariant: malformed freeblock chain ordering should return Corrupt errors.\n            fn prop_compute_free_space_returns_err_on_malformed_freeblock_chain(seed: u16) -> bool {\n                let page = get_page(2);\n                let contents = page.get_contents();\n\n                // Move content area left so freeblocks can exist \"inside content area\".\n                contents.write_cell_content_area(64);\n\n                // Create one freeblock whose \"next\" pointer violates ordering assumptions.\n                let base = 128 + (seed as usize % (PAGE_SIZE - 256));\n                let cur = base as u16;\n                let next = (base + 1) as u16; // intentionally invalid relative to size constraints\n                contents.write_first_freeblock(cur);\n                contents.write_freeblock(cur, 8, Some(next));\n                compute_free_space(contents, PAGE_SIZE).is_err()\n            }\n        }\n\n        quickcheck! {\n            // Desired invariant: malformed freeblock metadata should return Corrupt errors.\n            fn prop_defragment_returns_err_on_malformed_freeblock_chain(seed: u8) -> bool {\n                let page = get_page(2);\n                let contents = page.get_contents();\n\n                // Ensure page is non-empty so defragmentation doesn't early-return.\n                let cell = make_table_leaf_cell(1, (seed as usize % 24) + 1);\n                if insert_into_cell(contents, &cell, 0, PAGE_SIZE).is_err() {\n                    return true;\n                }\n\n                // Construct malformed chain: first freeblock points \"backwards\".\n                contents.write_first_freeblock(100);\n                contents.write_freeblock(100, 8, Some(90));\n                defragment_page(contents, PAGE_SIZE, 4).is_err()\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "core/storage/buffer_pool.rs",
    "content": "use branches::unlikely;\n\nuse super::{slot_bitmap::AtomicSlotBitmap, sqlite3_ondisk::WAL_FRAME_HEADER_SIZE};\nuse crate::io::TEMP_BUFFER_CACHE;\nuse crate::sync::atomic::{AtomicUsize, Ordering};\nuse crate::sync::Arc;\nuse crate::turso_assert;\nuse crate::{Buffer, LimboError, IO};\n\nuse std::cell::UnsafeCell;\nuse std::ptr::NonNull;\nuse std::sync::OnceLock;\n\n#[derive(Debug)]\n/// A buffer allocated from an arena from `[BufferPool]`\npub struct ArenaBuffer {\n    /// The `Arena` the buffer came from\n    arena: Arc<Arena>,\n    /// Pointer to the start of the buffer\n    ptr: NonNull<u8>,\n    /// Identifier for the `[Arena]` the buffer came from\n    arena_id: u32,\n    /// The index of the first slot making up the buffer\n    slot_idx: u32,\n    /// The requested length of the allocation.\n    /// For pooled buffers, `len` is always `<= Arena::slot_size` and occupies exactly one slot.\n    len: usize,\n}\n\n// Unsound: write and read from different threads can be dangerous with current ArenaBuffer implementation without some additional explicit synchronization\nunsafe impl Sync for ArenaBuffer {}\nunsafe impl Send for ArenaBuffer {}\ncrate::assert::assert_send_sync!(ArenaBuffer);\n\nimpl ArenaBuffer {\n    fn new(arena: Arc<Arena>, ptr: NonNull<u8>, len: usize, arena_id: u32, slot_idx: u32) -> Self {\n        ArenaBuffer {\n            arena,\n            ptr,\n            arena_id,\n            slot_idx,\n            len,\n        }\n    }\n\n    #[inline(always)]\n    /// Returns the `id` of the underlying arena, only if it was registered with `io_uring`\n    pub const fn fixed_id(&self) -> Option<u32> {\n        // Arenas which are not registered will have `id`s <= UNREGISTERED_START\n        if self.arena_id < UNREGISTERED_START {\n            Some(self.arena_id)\n        } else {\n            None\n        }\n    }\n\n    /// The requested size of the allocation, the actual size of the underlying buffer is rounded up to\n    /// the arena's slot_size (and in practice is always `<= slot_size` for pooled buffers).\n    pub const fn logical_len(&self) -> usize {\n        self.len\n    }\n    pub fn as_slice(&self) -> &[u8] {\n        unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.logical_len()) }\n    }\n    pub fn as_mut_slice(&mut self) -> &mut [u8] {\n        unsafe { std::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.logical_len()) }\n    }\n}\n\nimpl Drop for ArenaBuffer {\n    fn drop(&mut self) {\n        self.arena.free(self.slot_idx, self.logical_len());\n    }\n}\n\nimpl std::ops::Deref for ArenaBuffer {\n    type Target = [u8];\n    fn deref(&self) -> &Self::Target {\n        self.as_slice()\n    }\n}\n\nimpl std::ops::DerefMut for ArenaBuffer {\n    fn deref_mut(&mut self) -> &mut Self::Target {\n        self.as_mut_slice()\n    }\n}\n\n/// Static Buffer pool managing multiple memory arenas\n/// of which `[ArenaBuffer]`s are returned for requested allocations\npub struct BufferPool {\n    inner: UnsafeCell<PoolInner>,\n}\n\nunsafe impl Sync for BufferPool {}\nunsafe impl Send for BufferPool {}\ncrate::assert::assert_send_sync!(BufferPool);\n\nstruct PoolInner {\n    /// An instance of the program's IO, used for registering\n    /// Arena's with io_uring.\n    io: Option<Arc<dyn IO>>,\n    /// An Arena which returns `ArenaBuffer`s of size `db_page_size`.\n    page_arena: Option<Arc<Arena>>,\n    /// An Arena which returns `ArenaBuffer`s of size `db_page_size`\n    /// plus 24 byte `WAL_FRAME_HEADER_SIZE`, preventing the fragmentation\n    /// or complex book-keeping needed to use the same arena for both sizes.\n    wal_frame_arena: Option<Arc<Arena>>,\n    /// The size of each `Arena`, in bytes.\n    arena_size: usize,\n    /// The `[Database::page_size]`, which the `page_arena` will use to\n    /// return buffers from `Self::get_page`.\n    db_page_size: OnceLock<usize>,\n}\n\nunsafe impl Sync for PoolInner {}\nunsafe impl Send for PoolInner {}\ncrate::assert::assert_send_sync!(PoolInner);\n\nimpl Default for BufferPool {\n    fn default() -> Self {\n        Self::new(Self::DEFAULT_ARENA_SIZE)\n    }\n}\n\nimpl BufferPool {\n    /// 3MB Default size for each `Arena`. Any higher and\n    /// it will fail to register the second arena with io_uring due\n    /// to `RL_MEMLOCK` limit for un-privileged processes being 8MB total.\n    pub const DEFAULT_ARENA_SIZE: usize = 3 * 1024 * 1024;\n    /// 1MB size For testing/CI\n    pub const TEST_ARENA_SIZE: usize = 1024 * 1024;\n    /// 4KB default page_size\n    pub const DEFAULT_PAGE_SIZE: usize = 4096;\n    /// Maximum size for each Arena (64MB total)\n    const MAX_ARENA_SIZE: usize = 32 * 1024 * 1024;\n    /// 64kb Minimum arena size\n    const MIN_ARENA_SIZE: usize = 1024 * 64;\n    fn new(arena_size: usize) -> Self {\n        turso_assert!(\n            (Self::MIN_ARENA_SIZE..Self::MAX_ARENA_SIZE).contains(&arena_size),\n            \"Arena size out of valid range\",\n            { \"arena_size\": arena_size, \"min\": Self::MIN_ARENA_SIZE, \"max\": Self::MAX_ARENA_SIZE }\n        );\n        Self {\n            inner: UnsafeCell::new(PoolInner {\n                page_arena: None,\n                wal_frame_arena: None,\n                arena_size,\n                db_page_size: OnceLock::new(),\n                io: None,\n            }),\n        }\n    }\n\n    /// Request a `Buffer` of size `len`\n    #[inline]\n    pub fn allocate(&self, len: usize) -> Buffer {\n        self.inner().allocate(len)\n    }\n\n    /// Request a `Buffer` the size of the `db_page_size` the `BufferPool` was initialized with.\n    #[inline]\n    pub fn get_page(&self) -> Buffer {\n        let inner = self.inner_mut();\n        inner.get_db_page_buffer()\n    }\n\n    /// Request a `Buffer` for use with a WAL frame,\n    /// `[Database::page_size] + `WAL_FRAME_HEADER_SIZE`\n    #[inline]\n    pub fn get_wal_frame(&self) -> Buffer {\n        let inner = self.inner_mut();\n        inner.get_wal_frame_buffer()\n    }\n\n    #[inline]\n    fn inner(&self) -> &PoolInner {\n        unsafe { &*self.inner.get() }\n    }\n\n    #[inline]\n    #[allow(clippy::mut_from_ref)]\n    fn inner_mut(&self) -> &mut PoolInner {\n        unsafe { &mut *self.inner.get() }\n    }\n\n    /// Create a static `BufferPool` initialize the pool to the default page size, **without**\n    /// populating the Arenas. Arenas will not be created until `[BufferPool::finalize_page_size]`,\n    /// and the pool will temporarily return temporary buffers to prevent reallocation of the\n    /// arena if the page size is set to something other than the default value.\n    pub fn begin_init(io: &Arc<dyn IO>, arena_size: usize) -> Arc<Self> {\n        let pool = Arc::new(BufferPool::new(arena_size));\n        let inner = pool.inner_mut();\n        // Just store the IO handle, don't create arena yet\n        if inner.io.is_none() {\n            inner.io = Some(Arc::clone(io));\n        }\n        pool\n    }\n\n    /// Call when `[Database::db_state]` is initialized, providing the `page_size` to allocate\n    /// an arena for the pool. Before this call, the pool will use temporary buffers which are\n    /// cached in thread local storage.\n    pub fn finalize_with_page_size(&self, page_size: usize) -> crate::Result<()> {\n        let inner = self.inner_mut();\n        tracing::trace!(\"finalize page size called with size {page_size}\");\n        if page_size != BufferPool::DEFAULT_PAGE_SIZE {\n            // so far we have handed out some temporary buffers, since the page size is not\n            // default, we need to clear the cache so they aren't reused for other operations.\n            TEMP_BUFFER_CACHE.with(|cache| {\n                cache.borrow_mut().reinit_cache(page_size);\n            });\n        }\n        if inner.page_arena.is_some() {\n            tracing::trace!(\"Buffer pool already initialized, skipping finalize\");\n            return Ok(());\n        }\n\n        // Tries to atomically (guarenteed by the OnceLock) initialize the page size for the inner pool.\n        // If it succeeds, we now have to initialize the arenas.\n        // If the initialization fails, this means the arenas have already been initialized by a previous thread\n        // This avoids a potential TOCTOU race, where 2 threads could try to initalize the arena at the same time\n        // after checking the `db_page_size`\n        if inner.db_page_size.set(page_size).is_ok() {\n            inner.init_arenas()?;\n        };\n        Ok(())\n    }\n}\n\nimpl PoolInner {\n    #[inline]\n    pub fn get_db_page_size(&self) -> usize {\n        *(self\n            .db_page_size\n            .get()\n            .unwrap_or(&BufferPool::DEFAULT_PAGE_SIZE))\n    }\n\n    /// Allocate a buffer of the given length from the pool, falling back to\n    /// temporary thread local buffers if the pool is not initialized or is full.\n    pub fn allocate(&self, len: usize) -> Buffer {\n        turso_assert!(len > 0, \"Cannot allocate zero-length buffer\");\n\n        let db_page_size = self.get_db_page_size();\n        let wal_frame_size = db_page_size + WAL_FRAME_HEADER_SIZE;\n\n        // Check if this is exactly a WAL frame size allocation\n        if len == wal_frame_size {\n            return self\n                .wal_frame_arena\n                .as_ref()\n                .and_then(|wal_arena| Arena::try_alloc(wal_arena, len))\n                .unwrap_or_else(|| Buffer::new_temporary(len));\n        }\n        // For all other sizes, use regular arena\n        self.page_arena\n            .as_ref()\n            .and_then(|arena| Arena::try_alloc(arena, len))\n            .unwrap_or_else(|| Buffer::new_temporary(len))\n    }\n\n    fn get_db_page_buffer(&mut self) -> Buffer {\n        let db_page_size = self.get_db_page_size();\n        self.page_arena\n            .as_ref()\n            .and_then(|arena| Arena::try_alloc(arena, db_page_size))\n            .unwrap_or_else(|| Buffer::new_temporary(db_page_size))\n    }\n\n    fn get_wal_frame_buffer(&mut self) -> Buffer {\n        let len = self.get_db_page_size() + WAL_FRAME_HEADER_SIZE;\n        self.wal_frame_arena\n            .as_ref()\n            .and_then(|wal_arena| Arena::try_alloc(wal_arena, len))\n            .unwrap_or_else(|| Buffer::new_temporary(len))\n    }\n\n    /// Allocate a new arena for the pool to use\n    fn init_arenas(&mut self) -> crate::Result<()> {\n        let db_page_size = self.get_db_page_size();\n        let arena_size = self.arena_size;\n\n        let io = self.io.as_ref().expect(\"Pool not initialized\").clone();\n\n        // Create regular page arena\n        match Arena::new(db_page_size, arena_size, &io) {\n            Ok(arena) => {\n                tracing::trace!(\n                    \"added arena {} with size {} MB and slot size {}\",\n                    arena.id,\n                    arena_size / (1024 * 1024),\n                    db_page_size\n                );\n                self.page_arena = Some(Arc::new(arena));\n            }\n            Err(e) => {\n                tracing::error!(\"Failed to create arena: {:?}\", e);\n                return Err(LimboError::InternalError(format!(\n                    \"Failed to create arena: {e}\",\n                )));\n            }\n        }\n\n        // Create WAL frame arena\n        let wal_frame_size = db_page_size + WAL_FRAME_HEADER_SIZE;\n        match Arena::new(wal_frame_size, arena_size, &io) {\n            Ok(arena) => {\n                tracing::trace!(\n                    \"added WAL frame arena {} with size {} MB and slot size {}\",\n                    arena.id,\n                    arena_size / (1024 * 1024),\n                    wal_frame_size\n                );\n                self.wal_frame_arena = Some(Arc::new(arena));\n            }\n            Err(e) => {\n                tracing::error!(\"Failed to create WAL frame arena: {:?}\", e);\n                return Err(LimboError::InternalError(format!(\n                    \"Failed to create WAL frame arena: {e}\",\n                )));\n            }\n        }\n\n        Ok(())\n    }\n}\n\n/// Preallocated block of memory used by the pool to distribute `ArenaBuffer`s\n#[derive(Debug)]\nstruct Arena {\n    /// Identifier to tie allocations back to the arena. If the arena is registerd\n    /// with `io_uring`, then the ID represents the index of the arena into the ring's\n    /// sparse registered buffer array created on the ring's initialization.\n    id: u32,\n    /// Base pointer to the arena returned by `mmap`\n    base: NonNull<u8>,\n    /// Total number of slots currently allocated/in use.\n    allocated_slots: AtomicUsize,\n    /// Currently free slots (lock-free atomic bitmap).\n    free_slots: AtomicSlotBitmap,\n    /// Total size of the arena in bytes\n    arena_size: usize,\n    /// Slot size the total arena is divided into.\n    slot_size: usize,\n}\n\n// SAFETY: Arena's base pointer comes from mmap and is never aliased. All mutable\n// state is behind atomics (AtomicUsize, AtomicSlotBitmap), so concurrent access is safe.\nunsafe impl Send for Arena {}\nunsafe impl Sync for Arena {}\n\nimpl Drop for Arena {\n    fn drop(&mut self) {\n        unsafe { arena::dealloc(self.base.as_ptr(), self.arena_size) };\n    }\n}\n\n/// Slots 0 and 1 will be reserved for Arenas which are registered buffers\n/// with io_uring.\nconst UNREGISTERED_START: u32 = 2;\n\n/// ID's for an Arena which is not registered with `io_uring`\n/// registered arena will always have id = 0..=1\n/// we purposely use std::sync::AtomicU32 instead of core::sync::AtomicU32 because\n/// this is a global static variable and can mess with shuttle tests\nstatic NEXT_ID: std::sync::atomic::AtomicU32 =\n    std::sync::atomic::AtomicU32::new(UNREGISTERED_START);\n\nimpl Arena {\n    /// Create a new arena with the given size and page size.\n    /// NOTE: Minimum arena size is slot_size * 64\n    fn new(slot_size: usize, arena_size: usize, io: &Arc<dyn IO>) -> Result<Self, String> {\n        let min_slots = arena_size.div_ceil(slot_size);\n        let rounded_slots = (min_slots.max(64) + 63) & !63;\n        let rounded_bytes = rounded_slots * slot_size;\n        // Guard against the global cap\n        if unlikely(rounded_bytes > BufferPool::MAX_ARENA_SIZE) {\n            return Err(format!(\n                \"arena size {} B exceeds hard limit of {} B\",\n                rounded_bytes,\n                BufferPool::MAX_ARENA_SIZE\n            ));\n        }\n        let ptr = unsafe { arena::alloc(rounded_bytes) };\n        let base = NonNull::new(ptr).ok_or(\"Failed to allocate arena\")?;\n        let id = io\n            .register_fixed_buffer(base, rounded_bytes)\n            .unwrap_or_else(|_| {\n                // Register with io_uring if possible, otherwise use next available ID\n                let next_id = NEXT_ID.fetch_add(1, Ordering::AcqRel);\n                tracing::trace!(\"Allocating arena with id {}\", next_id);\n                next_id\n            });\n        let map = AtomicSlotBitmap::new(rounded_slots as u32);\n        Ok(Self {\n            id,\n            base,\n            free_slots: map,\n            allocated_slots: AtomicUsize::new(0),\n            slot_size,\n            arena_size: rounded_bytes,\n        })\n    }\n\n    /// Allocate a `Buffer` large enough for logical length `size`.\n    pub fn try_alloc(arena: &Arc<Arena>, size: usize) -> Option<Buffer> {\n        if size > arena.slot_size {\n            // The buffer pool only supports single-slot allocations. Larger requests fall back to\n            // temporary heap buffers via the caller.\n            return None;\n        }\n        let first_idx = arena.free_slots.alloc_one()?;\n        arena.allocated_slots.fetch_add(1, Ordering::AcqRel);\n        let offset = first_idx as usize * arena.slot_size;\n        let ptr = unsafe { NonNull::new_unchecked(arena.base.as_ptr().add(offset)) };\n        Some(Buffer::new_pooled(ArenaBuffer::new(\n            Arc::clone(arena),\n            ptr,\n            size,\n            arena.id,\n            first_idx,\n        )))\n    }\n\n    /// Mark all relevant slots that include `size` starting at `slot_idx` as free.\n    pub fn free(&self, slot_idx: u32, size: usize) {\n        turso_assert!(\n            size <= self.slot_size,\n            \"pooled buffers must not exceed one slot\"\n        );\n        turso_assert!(\n            !self.free_slots.is_free(slot_idx),\n            \"must not already be marked free\"\n        );\n        self.free_slots.free_one(slot_idx);\n        self.allocated_slots.fetch_sub(1, Ordering::AcqRel);\n    }\n}\n\n#[cfg(all(unix, not(miri)))]\nmod arena {\n    use libc::MAP_ANONYMOUS;\n    use libc::{mmap, munmap, MAP_PRIVATE, PROT_READ, PROT_WRITE};\n    use std::ffi::c_void;\n\n    pub unsafe fn alloc(len: usize) -> *mut u8 {\n        let ptr = mmap(\n            std::ptr::null_mut(),\n            len,\n            PROT_READ | PROT_WRITE,\n            MAP_PRIVATE | MAP_ANONYMOUS,\n            -1,\n            0,\n        );\n        if ptr == libc::MAP_FAILED {\n            panic!(\"mmap failed: {}\", std::io::Error::last_os_error());\n        }\n        #[cfg(target_os = \"linux\")]\n        {\n            libc::madvise(ptr, len, libc::MADV_HUGEPAGE);\n        }\n        ptr as *mut u8\n    }\n\n    pub unsafe fn dealloc(ptr: *mut u8, len: usize) {\n        let result = munmap(ptr as *mut c_void, len);\n        if result != 0 {\n            panic!(\"munmap failed: {}\", std::io::Error::last_os_error());\n        }\n    }\n}\n\n#[cfg(any(not(unix), miri))]\nmod arena {\n    pub fn alloc(len: usize) -> *mut u8 {\n        let layout = std::alloc::Layout::from_size_align(len, std::mem::size_of::<u8>()).unwrap();\n        unsafe { std::alloc::alloc_zeroed(layout) }\n    }\n    pub fn dealloc(ptr: *mut u8, len: usize) {\n        let layout = std::alloc::Layout::from_size_align(len, std::mem::size_of::<u8>()).unwrap();\n        unsafe { std::alloc::dealloc(ptr, layout) };\n    }\n}\n\n/// Shuttle tests for concurrent buffer pool operations.\n///\n/// These tests target the `unsafe impl Sync/Send` on:\n/// - `ArenaBuffer`: Raw pointer access across threads\n/// - `BufferPool`: UnsafeCell-based interior mutability\n/// - `PoolInner`: Shared mutable state\n///\n#[cfg(all(shuttle, test))]\nmod shuttle_tests {\n    use super::*;\n    use crate::io::MemoryIO;\n    use crate::sync::*;\n    use crate::thread;\n    use rustc_hash::FxHashSet as HashSet;\n\n    fn create_test_pool() -> Arc<BufferPool> {\n        let io: Arc<dyn IO> = Arc::new(MemoryIO::new());\n        let pool = BufferPool::begin_init(&io, BufferPool::TEST_ARENA_SIZE);\n        pool.finalize_with_page_size(4096).unwrap();\n        pool\n    }\n\n    /// Test concurrent allocations from BufferPool.\n    /// Verifies that multiple threads can safely call get_page() simultaneously.\n    #[test]\n    fn shuttle_concurrent_page_allocation() {\n        shuttle::check_random(\n            || {\n                let pool = create_test_pool();\n                let mut handles = vec![];\n\n                for _ in 0..3 {\n                    let pool = Arc::clone(&pool);\n                    let h = thread::spawn(move || {\n                        let buf = pool.get_page();\n                        assert_eq!(buf.len(), 4096);\n                        buf\n                    });\n                    handles.push(h);\n                }\n\n                let buffers: Vec<_> = handles.into_iter().map(|h| h.join().unwrap()).collect();\n                // Verify all buffers are valid and distinct (no double allocation)\n                assert_eq!(buffers.len(), 3);\n            },\n            1000,\n        );\n    }\n\n    /// Test concurrent allocation and deallocation.\n    /// Buffers are dropped in different threads than they were allocated.\n    #[test]\n    fn shuttle_concurrent_alloc_and_drop() {\n        shuttle::check_random(\n            || {\n                let pool = create_test_pool();\n                let pool2 = Arc::clone(&pool);\n\n                // Thread 1: allocate and send buffer to be dropped elsewhere\n                let h1 = thread::spawn(move || {\n                    let buf = pool.get_page();\n                    buf.len() // return length, buffer dropped here\n                });\n\n                // Thread 2: allocate concurrently\n                let h2 = thread::spawn(move || {\n                    let buf = pool2.get_page();\n                    buf.len()\n                });\n\n                assert_eq!(h1.join().unwrap(), 4096);\n                assert_eq!(h2.join().unwrap(), 4096);\n            },\n            1000,\n        );\n    }\n\n    /// Test that ArenaBuffer can be safely sent between threads and written to.\n    /// This tests the `unsafe impl Send + Sync for ArenaBuffer`.\n    #[test]\n    fn shuttle_arena_buffer_send_and_write() {\n        shuttle::check_random(\n            || {\n                let pool = create_test_pool();\n                let buf = pool.get_page();\n\n                // Write some data\n                buf.as_mut_slice()[0] = 42;\n\n                // Send to another thread for reading\n                let h = thread::spawn(move || {\n                    assert_eq!(buf.as_slice()[0], 42);\n                    buf.as_slice()[0]\n                });\n\n                assert_eq!(h.join().unwrap(), 42);\n            },\n            1000,\n        );\n    }\n\n    /// Test concurrent WAL frame and page allocations.\n    /// Both arena types are exercised simultaneously.\n    #[test]\n    fn shuttle_concurrent_mixed_allocations() {\n        shuttle::check_random(\n            || {\n                let pool = create_test_pool();\n                let pool2 = Arc::clone(&pool);\n                let pool3 = Arc::clone(&pool);\n\n                let h1 = thread::spawn(move || {\n                    let buf = pool.get_page();\n                    assert_eq!(buf.len(), 4096);\n                });\n\n                let h2 = thread::spawn(move || {\n                    let buf = pool2.get_wal_frame();\n                    // WAL frame = page_size + WAL_FRAME_HEADER_SIZE (24)\n                    assert_eq!(buf.len(), 4096 + WAL_FRAME_HEADER_SIZE);\n                });\n\n                let h3 = thread::spawn(move || {\n                    let buf = pool3.allocate(1024);\n                    assert_eq!(buf.len(), 1024);\n                });\n\n                h1.join().unwrap();\n                h2.join().unwrap();\n                h3.join().unwrap();\n            },\n            1000,\n        );\n    }\n\n    /// Stress test: many threads allocating and dropping buffers rapidly.\n    /// This helps find race conditions in the slot bitmap and arena management.\n    #[test]\n    fn shuttle_stress_concurrent_alloc_drop() {\n        shuttle::check_random(\n            || {\n                let pool = create_test_pool();\n                let mut handles = vec![];\n\n                for i in 0..4 {\n                    let pool = Arc::clone(&pool);\n                    let h = thread::spawn(move || {\n                        // Each thread does multiple alloc/drop cycles\n                        for _ in 0..2 {\n                            let buf = pool.get_page();\n                            // Write thread-specific data\n                            buf.as_mut_slice()[0] = i as u8;\n                            assert_eq!(buf.as_slice()[0], i as u8);\n                            // buf dropped here, returning slot to arena\n                        }\n                    });\n                    handles.push(h);\n                }\n\n                for h in handles {\n                    h.join().unwrap();\n                }\n            },\n            1000,\n        );\n    }\n\n    /// Test that buffers allocated by one thread can be safely read by another.\n    /// Uses a channel-like pattern with Arc to share buffers.\n    #[test]\n    fn shuttle_buffer_shared_read() {\n        shuttle::check_random(\n            || {\n                let pool = create_test_pool();\n\n                // Allocate and write in main thread\n                let buf = pool.get_page();\n                for (i, byte) in buf.as_mut_slice().iter_mut().enumerate().take(100) {\n                    *byte = (i % 256) as u8;\n                }\n\n                // Wrap in Arc for shared access (Buffer itself doesn't impl Clone)\n                let buf = Arc::new(buf);\n                let buf2 = Arc::clone(&buf);\n\n                // Reader thread\n                let h = thread::spawn(move || {\n                    for i in 0..100 {\n                        assert_eq!(buf2.as_slice()[i], (i % 256) as u8);\n                    }\n                });\n\n                // Main thread also reads\n                for i in 0..100 {\n                    assert_eq!(buf.as_slice()[i], (i % 256) as u8);\n                }\n\n                h.join().unwrap();\n            },\n            1000,\n        );\n    }\n\n    /// Test pool initialization race (though guarded by init_lock).\n    /// Multiple threads trying to finalize should be safe.\n    #[test]\n    fn shuttle_concurrent_finalize() {\n        let test = || {\n            let io: Arc<dyn IO> = Arc::new(MemoryIO::new());\n            let pool = BufferPool::begin_init(&io, BufferPool::TEST_ARENA_SIZE);\n            let pool2 = Arc::clone(&pool);\n            let pool3 = Arc::clone(&pool);\n\n            let h1 = thread::spawn(move || {\n                let _ = pool.finalize_with_page_size(4096).unwrap();\n            });\n\n            let h2 = thread::spawn(move || {\n                let _ = pool2.finalize_with_page_size(4096).unwrap();\n            });\n\n            // Also try to allocate while finalizing\n            let h3 = thread::spawn(move || {\n                // This may get a temporary buffer if arena isn't ready\n                let buf = pool3.allocate(4096);\n                assert_eq!(buf.len(), 4096);\n            });\n\n            h1.join().unwrap();\n            h2.join().unwrap();\n            h3.join().unwrap();\n        };\n        shuttle::check_random(test, 1000);\n    }\n\n    /// Test concurrent writes to the same ArenaBuffer at the SAME offsets.\n    /// This exercises the `unsafe impl Sync for ArenaBuffer` which is documented as potentially unsound.\n    /// Each thread writes a distinct pattern to all 4096 bytes, and we verify no torn writes occurred\n    /// (all bytes must have the same pattern value, not a mix from different threads).\n    #[test]\n    fn shuttle_concurrent_write_same_buffer_same_offset() {\n        shuttle::check_random(\n            || {\n                let pool = create_test_pool();\n                let buf = pool.get_page();\n\n                // Three distinct byte patterns that threads will race to write\n                const PATTERN_A: u8 = 0xAA;\n                const PATTERN_B: u8 = 0xBB;\n                const PATTERN_C: u8 = 0xCC;\n\n                // Use scoped threads so buf can be borrowed by multiple threads\n                thread::scope(|scope| {\n                    // Thread A writes PATTERN_A to all 4096 bytes\n                    scope.spawn(|| {\n                        buf.as_mut_slice().fill(PATTERN_A);\n                    });\n\n                    // Thread B writes PATTERN_B to all 4096 bytes\n                    scope.spawn(|| {\n                        buf.as_mut_slice().fill(PATTERN_B);\n                    });\n\n                    // Thread C writes PATTERN_C to all 4096 bytes\n                    scope.spawn(|| {\n                        buf.as_mut_slice().fill(PATTERN_C);\n                    });\n                });\n\n                // After all writes complete, verify no torn writes across the entire buffer\n                // All 4096 bytes should have the same pattern (whichever thread won)\n                let slice = buf.as_slice();\n                let first_byte = slice[0];\n\n                // First byte must be one of our patterns\n                assert!(\n                    first_byte == PATTERN_A || first_byte == PATTERN_B || first_byte == PATTERN_C,\n                    \"Invalid pattern in buffer: 0x{:02X}\",\n                    first_byte\n                );\n\n                // All bytes must match the first byte (no partial/torn writes)\n                for (i, &byte) in slice.iter().enumerate() {\n                    assert!(\n                        byte == first_byte,\n                        \"Torn write at offset {}: got 0x{:02X}, expected 0xAA, 0xBB, or 0xCC\",\n                        i,\n                        byte\n                    );\n                }\n            },\n            1000,\n        );\n    }\n\n    /// Test concurrent writes to different offsets of the same buffer (non-overlapping).\n    /// This tests that writes to different parts of the buffer don't interfere.\n    #[test]\n    fn shuttle_concurrent_write_different_offsets() {\n        shuttle::check_random(\n            || {\n                let pool = create_test_pool();\n                let buf = pool.get_page();\n\n                let ptr = buf.as_ptr() as usize;\n                let len = buf.len();\n                let _buf = buf;\n\n                let h1 = thread::spawn(move || {\n                    let slice = unsafe { std::slice::from_raw_parts_mut(ptr as *mut u8, len) };\n                    for i in 0..100 {\n                        slice[i] = 0xAA;\n                    }\n                });\n\n                let h2 = thread::spawn(move || {\n                    let slice = unsafe { std::slice::from_raw_parts_mut(ptr as *mut u8, len) };\n                    for i in 100..200 {\n                        slice[i] = 0xBB;\n                    }\n                });\n\n                let h3 = thread::spawn(move || {\n                    let slice = unsafe { std::slice::from_raw_parts_mut(ptr as *mut u8, len) };\n                    for i in 200..300 {\n                        slice[i] = 0xCC;\n                    }\n                });\n\n                h1.join().unwrap();\n                h2.join().unwrap();\n                h3.join().unwrap();\n\n                // Verify each section has the correct pattern\n                let final_slice = unsafe { std::slice::from_raw_parts(ptr as *const u8, len) };\n                for i in 0..100 {\n                    assert_eq!(\n                        final_slice[i], 0xAA,\n                        \"Section 1 corrupted at offset {}: expected 0xAA, got 0x{:02X}\",\n                        i, final_slice[i]\n                    );\n                }\n                for i in 100..200 {\n                    assert_eq!(\n                        final_slice[i], 0xBB,\n                        \"Section 2 corrupted at offset {}: expected 0xBB, got 0x{:02X}\",\n                        i, final_slice[i]\n                    );\n                }\n                for i in 200..300 {\n                    assert_eq!(\n                        final_slice[i], 0xCC,\n                        \"Section 3 corrupted at offset {}: expected 0xCC, got 0x{:02X}\",\n                        i, final_slice[i]\n                    );\n                }\n            },\n            1000,\n        );\n    }\n\n    /// Test allocation racing with deallocation (slot recycling).\n    /// Verifies that when a buffer is dropped and its slot is freed,\n    /// concurrent allocations correctly handle the recycled slot.\n    #[test]\n    fn shuttle_alloc_during_drop_slot_recycling() {\n        shuttle::check_random(\n            || {\n                let pool = create_test_pool();\n\n                // Pre-allocate some buffers and write identifying data\n                let mut initial_bufs: Vec<_> = (0..5).map(|_| pool.get_page()).collect();\n                let initial_ptrs: Vec<usize> =\n                    initial_bufs.iter().map(|b| b.as_ptr() as usize).collect();\n\n                // Write unique patterns to initial buffers\n                for (i, buf) in initial_bufs.iter_mut().enumerate() {\n                    buf.as_mut_slice()[0] = 0xDE;\n                    buf.as_mut_slice()[1] = i as u8;\n                }\n\n                let pool2 = Arc::clone(&pool);\n                let pool3 = Arc::clone(&pool);\n\n                // Thread 1: drops buffers, freeing slots\n                let h1 = thread::spawn(move || {\n                    drop(initial_bufs);\n                });\n\n                // Thread 2: allocates while slots are being freed\n                let h2 = thread::spawn(move || {\n                    let mut bufs = Vec::new();\n                    for i in 0..3 {\n                        let buf = pool2.get_page();\n                        assert_eq!(buf.len(), 4096, \"Buffer {} has wrong length\", i);\n                        // Write identifying pattern\n                        buf.as_mut_slice()[0] = 0xAA;\n                        buf.as_mut_slice()[1] = i as u8;\n                        bufs.push(buf);\n                    }\n                    bufs\n                });\n\n                // Thread 3: also allocates concurrently\n                let h3 = thread::spawn(move || {\n                    let mut bufs = Vec::new();\n                    for i in 0..3 {\n                        let buf = pool3.get_page();\n                        assert_eq!(buf.len(), 4096, \"Buffer {} has wrong length\", i);\n                        // Write different identifying pattern\n                        buf.as_mut_slice()[0] = 0xBB;\n                        buf.as_mut_slice()[1] = i as u8;\n                        bufs.push(buf);\n                    }\n                    bufs\n                });\n\n                h1.join().unwrap();\n                let bufs2 = h2.join().unwrap();\n                let bufs3 = h3.join().unwrap();\n\n                // Verify buffers within each thread don't overlap\n                let ptrs2: HashSet<_> = bufs2.iter().map(|b| b.as_ptr() as usize).collect();\n                assert_eq!(\n                    ptrs2.len(),\n                    bufs2.len(),\n                    \"Thread 2 got duplicate buffer pointers\"\n                );\n\n                let ptrs3: HashSet<_> = bufs3.iter().map(|b| b.as_ptr() as usize).collect();\n                assert_eq!(\n                    ptrs3.len(),\n                    bufs3.len(),\n                    \"Thread 3 got duplicate buffer pointers\"\n                );\n\n                // Verify no overlap between buffers from different threads\n                for ptr in &ptrs2 {\n                    assert!(\n                        !ptrs3.contains(ptr),\n                        \"Slot double-allocation: same memory 0x{:X} returned to both threads\",\n                        ptr\n                    );\n                }\n\n                // Verify each buffer has correct identifying data (not corrupted)\n                for (i, buf) in bufs2.iter().enumerate() {\n                    assert_eq!(\n                        buf.as_slice()[0],\n                        0xAA,\n                        \"Thread 2 buffer {} header corrupted\",\n                        i\n                    );\n                    assert_eq!(\n                        buf.as_slice()[1],\n                        i as u8,\n                        \"Thread 2 buffer {} index corrupted\",\n                        i\n                    );\n                }\n                for (i, buf) in bufs3.iter().enumerate() {\n                    assert_eq!(\n                        buf.as_slice()[0],\n                        0xBB,\n                        \"Thread 3 buffer {} header corrupted\",\n                        i\n                    );\n                    assert_eq!(\n                        buf.as_slice()[1],\n                        i as u8,\n                        \"Thread 3 buffer {} index corrupted\",\n                        i\n                    );\n                }\n\n                // Verify we can still allocate after all this\n                let final_buf = pool.get_page();\n                assert_eq!(final_buf.len(), 4096, \"Final allocation failed\");\n\n                // Keep initial_ptrs to suppress unused warning\n                let _ = initial_ptrs;\n            },\n            1000,\n        );\n    }\n\n    /// Test arena exhaustion and recovery.\n    /// Allocates until the arena is full (falls back to temporary buffers),\n    /// then frees and verifies slots are correctly recycled.\n    #[test]\n    fn shuttle_arena_exhaustion_and_recovery() {\n        shuttle::check_random(\n            || {\n                let pool = create_test_pool();\n\n                // Allocate many buffers to exhaust the arena\n                // TEST_ARENA_SIZE = 1MB, page_size = 4KB, so ~256 slots max\n                let mut buffers: Vec<Buffer> = Vec::new();\n                let mut pooled_count = 0;\n                let mut temp_count = 0;\n\n                for i in 0..300 {\n                    let buf = pool.get_page();\n                    assert_eq!(buf.len(), 4096, \"Buffer {} has wrong length\", i);\n\n                    // Write identifying data\n                    buf.as_mut_slice()[0] = (i & 0xFF) as u8;\n                    buf.as_mut_slice()[1] = ((i >> 8) & 0xFF) as u8;\n\n                    if buf.is_pooled() {\n                        pooled_count += 1;\n                    } else {\n                        temp_count += 1;\n                    }\n                    buffers.push(buf);\n                }\n\n                assert!(temp_count > 0);\n                // We should have some pooled and some temporary buffers\n                assert!(pooled_count > 0, \"Expected some pooled buffers, got none\");\n                // With 1MB arena and 4KB pages, we have ~256 slots\n                // So with 300 allocations, we should have some temporary\n                assert!(\n                    pooled_count <= 256,\n                    \"Got {} pooled buffers, but arena should only have ~256 slots\",\n                    pooled_count\n                );\n                assert!(pooled_count + temp_count >= 256);\n\n                // Verify all buffers have correct identifying data\n                for (i, buf) in buffers.iter().enumerate() {\n                    assert_eq!(\n                        buf.as_slice()[0],\n                        (i & 0xFF) as u8,\n                        \"Buffer {} low byte corrupted\",\n                        i\n                    );\n                    assert_eq!(\n                        buf.as_slice()[1],\n                        ((i >> 8) & 0xFF) as u8,\n                        \"Buffer {} high byte corrupted\",\n                        i\n                    );\n                }\n\n                // Drop half the buffers to free slots\n                let dropped_count = buffers.len() - 150;\n                buffers.truncate(150);\n\n                // Allocate again - should get recycled slots\n                let pool2 = Arc::clone(&pool);\n                let h = thread::spawn(move || {\n                    let mut new_bufs = Vec::new();\n                    for i in 0..50 {\n                        let buf = pool2.get_page();\n                        assert_eq!(buf.len(), 4096, \"New buffer {} has wrong length\", i);\n                        // Write new pattern\n                        buf.as_mut_slice()[0] = 0xFF;\n                        buf.as_mut_slice()[1] = i as u8;\n                        new_bufs.push(buf);\n                    }\n                    new_bufs\n                });\n\n                let new_bufs = h.join().unwrap();\n\n                // Verify new buffers\n                for (i, buf) in new_bufs.iter().enumerate() {\n                    assert_eq!(buf.len(), 4096, \"New buffer {} length check failed\", i);\n                    assert_eq!(buf.as_slice()[0], 0xFF, \"New buffer {} header corrupted\", i);\n                    assert_eq!(\n                        buf.as_slice()[1],\n                        i as u8,\n                        \"New buffer {} index corrupted\",\n                        i\n                    );\n                }\n\n                // Verify remaining original buffers still have correct data\n                for (i, buf) in buffers.iter().enumerate() {\n                    assert_eq!(\n                        buf.as_slice()[0],\n                        (i & 0xFF) as u8,\n                        \"Original buffer {} corrupted after recycling\",\n                        i\n                    );\n                }\n\n                let _ = (temp_count, dropped_count); // suppress warnings\n            },\n            1000,\n        );\n    }\n\n    /// Test that allocated buffers never overlap (slot double-allocation detection).\n    /// Multiple threads allocate concurrently and we verify all pointers are unique.\n    #[test]\n    fn shuttle_slot_overlap_verification() {\n        shuttle::check_random(\n            || {\n                let pool = create_test_pool();\n                let mut handles = vec![];\n\n                for thread_id in 0..4u8 {\n                    let pool = Arc::clone(&pool);\n                    let h = thread::spawn(move || {\n                        let mut bufs = Vec::new();\n                        for buf_id in 0..10u8 {\n                            let buf = pool.get_page();\n                            assert_eq!(buf.len(), 4096, \"Buffer has wrong length\");\n\n                            // Write thread and buffer identifying data\n                            buf.as_mut_slice()[0] = thread_id;\n                            buf.as_mut_slice()[1] = buf_id;\n                            // Write a checksum pattern\n                            buf.as_mut_slice()[2] = thread_id ^ buf_id;\n\n                            bufs.push(buf);\n                        }\n                        bufs\n                    });\n                    handles.push(h);\n                }\n\n                let all_bufs: Vec<Vec<Buffer>> =\n                    handles.into_iter().map(|h| h.join().unwrap()).collect();\n\n                // Verify each thread got the expected number of buffers\n                for (thread_id, thread_bufs) in all_bufs.iter().enumerate() {\n                    assert_eq!(\n                        thread_bufs.len(),\n                        10,\n                        \"Thread {} got {} buffers instead of 10\",\n                        thread_id,\n                        thread_bufs.len()\n                    );\n                }\n\n                // Collect all pointers and verify uniqueness\n                let mut all_ptrs: Vec<usize> = Vec::new();\n                for thread_bufs in &all_bufs {\n                    for buf in thread_bufs {\n                        all_ptrs.push(buf.as_ptr() as usize);\n                    }\n                }\n\n                let unique_ptrs: HashSet<_> = all_ptrs.iter().copied().collect();\n                assert_eq!(\n                    all_ptrs.len(),\n                    unique_ptrs.len(),\n                    \"Slot double-allocation detected: {} total buffers but only {} unique pointers\",\n                    all_ptrs.len(),\n                    unique_ptrs.len()\n                );\n\n                // Verify each buffer still has correct identifying data (no cross-thread corruption)\n                for (thread_id, thread_bufs) in all_bufs.iter().enumerate() {\n                    for (buf_id, buf) in thread_bufs.iter().enumerate() {\n                        assert_eq!(\n                            buf.as_slice()[0],\n                            thread_id as u8,\n                            \"Buffer [{},{}] thread_id corrupted: expected {}, got {}\",\n                            thread_id,\n                            buf_id,\n                            thread_id,\n                            buf.as_slice()[0]\n                        );\n                        assert_eq!(\n                            buf.as_slice()[1],\n                            buf_id as u8,\n                            \"Buffer [{},{}] buf_id corrupted: expected {}, got {}\",\n                            thread_id,\n                            buf_id,\n                            buf_id,\n                            buf.as_slice()[1]\n                        );\n                        let expected_checksum = (thread_id as u8) ^ (buf_id as u8);\n                        assert_eq!(\n                            buf.as_slice()[2],\n                            expected_checksum,\n                            \"Buffer [{},{}] checksum corrupted: expected {}, got {}\",\n                            thread_id,\n                            buf_id,\n                            expected_checksum,\n                            buf.as_slice()[2]\n                        );\n                    }\n                }\n\n                // Verify buffers don't overlap by checking memory ranges\n                let page_size = 4096usize;\n                for (i, ptr_i) in all_ptrs.iter().enumerate() {\n                    for (j, ptr_j) in all_ptrs.iter().enumerate() {\n                        if i != j {\n                            let range_i = *ptr_i..(*ptr_i + page_size);\n                            // Check if ptr_j falls within range_i\n                            assert!(\n                                !range_i.contains(ptr_j),\n                                \"Buffer {} (0x{:X}) overlaps with buffer {} (0x{:X})\",\n                                i,\n                                ptr_i,\n                                j,\n                                ptr_j\n                            );\n                        }\n                    }\n                }\n            },\n            1000,\n        );\n    }\n\n    /// Test buffer content integrity under concurrent operations.\n    /// Each thread writes a unique pattern and verifies it's not corrupted\n    /// by other threads' operations.\n    #[test]\n    fn shuttle_buffer_content_integrity() {\n        shuttle::check_random(\n            || {\n                let pool = create_test_pool();\n                let mut handles = vec![];\n\n                for thread_id in 0u8..4 {\n                    let pool = Arc::clone(&pool);\n                    let h = thread::spawn(move || {\n                        let buf = pool.get_page();\n                        // Write thread-specific pattern\n                        let pattern = thread_id.wrapping_mul(37);\n                        for byte in buf.as_mut_slice().iter_mut() {\n                            *byte = pattern;\n                        }\n                        // Yield to allow other threads to run\n                        thread::yield_now();\n                        // Verify pattern is intact\n                        for (i, byte) in buf.as_slice().iter().enumerate() {\n                            assert_eq!(\n                                *byte, pattern,\n                                \"Buffer corruption at offset {}: expected {}, got {}\",\n                                i, pattern, *byte\n                            );\n                        }\n                        buf\n                    });\n                    handles.push(h);\n                }\n\n                // All threads should complete without corruption\n                for h in handles {\n                    h.join().unwrap();\n                }\n            },\n            1000,\n        );\n    }\n\n    /// Test the race between ArenaBuffer::drop upgrading Weak<Arena> while\n    /// the Arena might be getting dropped. This exercises the weak reference\n    /// pattern used for buffer deallocation.\n    #[test]\n    fn shuttle_weak_reference_upgrade_during_drop() {\n        shuttle::check_random(\n            || {\n                let pool = create_test_pool();\n\n                // Allocate multiple buffers and write identifying data\n                let buf1 = pool.get_page();\n                let buf2 = pool.get_page();\n                let buf3 = pool.get_page();\n\n                buf1.as_mut_slice()[0] = 0x11;\n                buf2.as_mut_slice()[0] = 0x22;\n                buf3.as_mut_slice()[0] = 0x33;\n\n                let buf1_ptr = buf1.as_ptr() as usize;\n                let buf2_ptr = buf2.as_ptr() as usize;\n\n                // Clone pool references\n                let pool2 = Arc::clone(&pool);\n                let pool3 = Arc::clone(&pool);\n\n                // Thread 1: drop buffer1, freeing its slot\n                let h1 = thread::spawn(move || {\n                    // Buffer drop will try to upgrade Weak<Arena> and call free()\n                    drop(buf1);\n                });\n\n                // Thread 2: drop pool reference and buffer2\n                let h2 = thread::spawn(move || {\n                    drop(buf2);\n                    drop(pool2);\n                });\n\n                // Thread 3: allocate while others are dropping\n                let h3 = thread::spawn(move || {\n                    let new_buf = pool3.get_page();\n                    assert_eq!(new_buf.len(), 4096, \"New buffer has wrong length\");\n                    new_buf.as_mut_slice()[0] = 0x44;\n                    new_buf\n                });\n\n                h1.join().unwrap();\n                h2.join().unwrap();\n                let new_buf = h3.join().unwrap();\n\n                // Verify buf3 is still intact\n                assert_eq!(buf3.as_slice()[0], 0x33, \"buf3 was corrupted\");\n\n                // Verify new_buf has correct data\n                assert_eq!(new_buf.as_slice()[0], 0x44, \"new_buf was corrupted\");\n\n                // Original pool reference keeps arena alive\n                // Allocate more to verify arena is still functional\n                let final_buf = pool.get_page();\n                assert_eq!(final_buf.len(), 4096, \"Final allocation failed\");\n                final_buf.as_mut_slice()[0] = 0x55;\n                assert_eq!(final_buf.as_slice()[0], 0x55, \"Final buffer write failed\");\n\n                // The recycled slot might be one of the dropped buffers\n                let final_ptr = final_buf.as_ptr() as usize;\n                // This is valid - we might get a recycled slot\n                let _ = (buf1_ptr, buf2_ptr, final_ptr);\n            },\n            1000,\n        );\n    }\n\n    /// Test bitmap consistency: after many concurrent operations,\n    /// verify that allocated_slots matches actual allocations.\n    #[test]\n    fn shuttle_bitmap_consistency() {\n        shuttle::check_random(\n            || {\n                let pool = create_test_pool();\n                let mut handles = vec![];\n\n                // Many threads doing alloc/free cycles\n                for thread_id in 0..4u8 {\n                    let pool = Arc::clone(&pool);\n                    let h = thread::spawn(move || {\n                        let mut bufs = Vec::new();\n                        // Allocate 5 buffers\n                        for i in 0..5u8 {\n                            let buf = pool.get_page();\n                            assert_eq!(\n                                buf.len(),\n                                4096,\n                                \"Thread {} buf {} wrong length\",\n                                thread_id,\n                                i\n                            );\n                            // Mark with identifying data\n                            buf.as_mut_slice()[0] = thread_id;\n                            buf.as_mut_slice()[1] = i;\n                            buf.as_mut_slice()[2] = 0xAA; // Initial marker\n                            bufs.push(buf);\n                        }\n\n                        // Verify all 5 before truncation\n                        for (i, buf) in bufs.iter().enumerate() {\n                            assert_eq!(\n                                buf.as_slice()[0],\n                                thread_id,\n                                \"Pre-truncate thread_id mismatch\"\n                            );\n                            assert_eq!(buf.as_slice()[1], i as u8, \"Pre-truncate index mismatch\");\n                        }\n\n                        // Free 3 buffers (keep first 2)\n                        bufs.truncate(2);\n\n                        // Allocate 3 more\n                        for i in 0..3u8 {\n                            let buf = pool.get_page();\n                            assert_eq!(\n                                buf.len(),\n                                4096,\n                                \"Thread {} new buf {} wrong length\",\n                                thread_id,\n                                i\n                            );\n                            buf.as_mut_slice()[0] = thread_id;\n                            buf.as_mut_slice()[1] = 10 + i; // Different index range\n                            buf.as_mut_slice()[2] = 0xBB; // New marker\n                            bufs.push(buf);\n                        }\n\n                        // Should have 5 buffers now (2 original + 3 new)\n                        assert_eq!(bufs.len(), 5, \"Thread {} should have 5 buffers\", thread_id);\n                        bufs\n                    });\n                    handles.push(h);\n                }\n\n                let all_bufs: Vec<_> = handles.into_iter().map(|h| h.join().unwrap()).collect();\n\n                // Verify each thread returned 5 buffers\n                for (thread_id, thread_bufs) in all_bufs.iter().enumerate() {\n                    assert_eq!(\n                        thread_bufs.len(),\n                        5,\n                        \"Thread {} returned {} buffers instead of 5\",\n                        thread_id,\n                        thread_bufs.len()\n                    );\n                }\n\n                // Count pooled vs temporary buffers\n                let mut pooled_count = 0;\n                let mut temp_count = 0;\n                for thread_bufs in &all_bufs {\n                    for buf in thread_bufs {\n                        if buf.fixed_id().is_some() {\n                            pooled_count += 1;\n                        } else {\n                            temp_count += 1;\n                        }\n                    }\n                }\n\n                // Total should be 20 (4 threads * 5 buffers)\n                assert_eq!(\n                    pooled_count + temp_count,\n                    20,\n                    \"Total buffer count mismatch: {} pooled + {} temp != 20\",\n                    pooled_count,\n                    temp_count\n                );\n\n                // Verify all buffers have valid identifying data\n                for (thread_id, thread_bufs) in all_bufs.iter().enumerate() {\n                    for (i, buf) in thread_bufs.iter().enumerate() {\n                        assert_eq!(\n                            buf.as_slice()[0],\n                            thread_id as u8,\n                            \"Buffer [{},{}] thread_id corrupted\",\n                            thread_id,\n                            i\n                        );\n                        let marker = buf.as_slice()[2];\n                        assert!(\n                            marker == 0xAA || marker == 0xBB,\n                            \"Buffer [{},{}] has invalid marker 0x{:02X}\",\n                            thread_id,\n                            i,\n                            marker\n                        );\n                    }\n                }\n\n                // Collect all pointers to verify no duplicates\n                let all_ptrs: HashSet<_> = all_bufs\n                    .iter()\n                    .flat_map(|bufs| bufs.iter().map(|b| b.as_ptr() as usize))\n                    .collect();\n                assert_eq!(\n                    all_ptrs.len(),\n                    20,\n                    \"Found duplicate pointers: {} unique out of 20\",\n                    all_ptrs.len()\n                );\n\n                // Try allocating more to verify arena is consistent\n                let mut final_bufs = Vec::new();\n                for i in 0..10 {\n                    let buf = pool.get_page();\n                    assert_eq!(buf.len(), 4096, \"Final buf {} wrong length\", i);\n                    buf.as_mut_slice()[0] = 0xFF;\n                    buf.as_mut_slice()[1] = i as u8;\n                    final_bufs.push(buf);\n                }\n\n                // Verify final buffers don't overlap with existing ones\n                for buf in &final_bufs {\n                    let ptr = buf.as_ptr() as usize;\n                    assert!(\n                        !all_ptrs.contains(&ptr),\n                        \"Final buffer overlaps with existing at 0x{:X}\",\n                        ptr\n                    );\n                }\n\n                // Keep buffers alive until end\n                drop(all_bufs);\n                drop(final_bufs);\n            },\n            1000,\n        );\n    }\n\n    /// Test concurrent access through inner_mut().\n    /// Multiple threads calling get_page() and get_wal_frame() simultaneously\n    /// access different PoolInner fields through the unsynchronized inner_mut().\n    #[test]\n    fn shuttle_concurrent_inner_mut_access() {\n        shuttle::check_random(\n            || {\n                let pool = create_test_pool();\n                let mut handles = vec![];\n\n                // Threads calling get_page (accesses page_arena through inner_mut)\n                for page_thread_id in 0..2u8 {\n                    let pool = Arc::clone(&pool);\n                    let h = thread::spawn(move || {\n                        let mut bufs = Vec::new();\n                        for i in 0..5u8 {\n                            let buf = pool.get_page();\n                            assert_eq!(buf.len(), 4096, \"Page buffer has wrong length\");\n                            // Mark as page buffer with identifying data\n                            buf.as_mut_slice()[0] = 0xAA; // Page marker\n                            buf.as_mut_slice()[1] = page_thread_id;\n                            buf.as_mut_slice()[2] = i;\n                            bufs.push(buf);\n                        }\n                        bufs\n                    });\n                    handles.push(h);\n                }\n\n                // Threads calling get_wal_frame (accesses wal_frame_arena through inner_mut)\n                for wal_thread_id in 0..2u8 {\n                    let pool = Arc::clone(&pool);\n                    let h = thread::spawn(move || {\n                        let mut bufs = Vec::new();\n                        for i in 0..5u8 {\n                            let buf = pool.get_wal_frame();\n                            assert_eq!(\n                                buf.len(),\n                                4096 + WAL_FRAME_HEADER_SIZE,\n                                \"WAL frame buffer has wrong length\"\n                            );\n                            // Mark as WAL buffer with identifying data\n                            buf.as_mut_slice()[0] = 0xBB; // WAL marker\n                            buf.as_mut_slice()[1] = wal_thread_id;\n                            buf.as_mut_slice()[2] = i;\n                            bufs.push(buf);\n                        }\n                        bufs\n                    });\n                    handles.push(h);\n                }\n\n                // Thread calling allocate with various sizes\n                {\n                    let pool = Arc::clone(&pool);\n                    let h = thread::spawn(move || {\n                        let mut bufs = Vec::new();\n                        for i in 0..5u8 {\n                            let buf = pool.allocate(2048);\n                            assert_eq!(buf.len(), 2048, \"Allocated buffer has wrong length\");\n                            // Mark as generic allocation\n                            buf.as_mut_slice()[0] = 0xCC; // Allocate marker\n                            buf.as_mut_slice()[1] = i;\n                            bufs.push(buf);\n                        }\n                        bufs\n                    });\n                    handles.push(h);\n                }\n\n                let results: Vec<Vec<Buffer>> =\n                    handles.into_iter().map(|h| h.join().unwrap()).collect();\n\n                // Verify we got expected number of buffers from each type\n                // 2 page threads * 5 + 2 wal threads * 5 + 1 allocate thread * 5 = 25 total\n                let total_bufs: usize = results.iter().map(|v| v.len()).sum();\n                assert_eq!(\n                    total_bufs, 25,\n                    \"Expected 25 total buffers, got {}\",\n                    total_bufs\n                );\n\n                // Verify each buffer has correct marker and data\n                for (thread_idx, thread_bufs) in results.iter().enumerate() {\n                    for (buf_idx, buf) in thread_bufs.iter().enumerate() {\n                        let marker = buf.as_slice()[0];\n                        assert!(\n                            marker == 0xAA || marker == 0xBB || marker == 0xCC,\n                            \"Buffer [{},{}] has invalid marker 0x{:02X}\",\n                            thread_idx,\n                            buf_idx,\n                            marker\n                        );\n\n                        // Verify length matches marker type\n                        match marker {\n                            0xAA => assert_eq!(buf.len(), 4096, \"Page buffer wrong length\"),\n                            0xBB => assert_eq!(\n                                buf.len(),\n                                4096 + WAL_FRAME_HEADER_SIZE,\n                                \"WAL buffer wrong length\"\n                            ),\n                            0xCC => assert_eq!(buf.len(), 2048, \"Allocate buffer wrong length\"),\n                            _ => unreachable!(),\n                        }\n                    }\n                }\n\n                // Collect all pointers and verify no duplicates\n                let all_ptrs: HashSet<_> = results\n                    .iter()\n                    .flat_map(|bufs| bufs.iter().map(|b| b.as_ptr() as usize))\n                    .collect();\n                assert_eq!(\n                    all_ptrs.len(),\n                    total_bufs,\n                    \"Found duplicate pointers: {} unique out of {}\",\n                    all_ptrs.len(),\n                    total_bufs\n                );\n            },\n            1000,\n        );\n    }\n\n    /// Stress test with higher iteration count and more threads.\n    /// This provides better coverage for subtle race conditions.\n    #[test]\n    fn shuttle_high_contention_stress() {\n        shuttle::check_random(\n            || {\n                let pool = create_test_pool();\n                let mut handles = vec![];\n\n                for thread_id in 0..6u8 {\n                    let pool = Arc::clone(&pool);\n                    let h = thread::spawn(move || {\n                        let mut bufs = Vec::new();\n                        let mut dropped_count = 0u8;\n\n                        for iter in 0..4u8 {\n                            // Alternate between page and WAL frame allocations\n                            let is_page = (thread_id + iter) % 2 == 0;\n                            let buf = if is_page {\n                                pool.get_page()\n                            } else {\n                                pool.get_wal_frame()\n                            };\n\n                            // Verify length matches allocation type\n                            let expected_len = if is_page {\n                                4096\n                            } else {\n                                4096 + WAL_FRAME_HEADER_SIZE\n                            };\n                            assert_eq!(\n                                buf.len(),\n                                expected_len,\n                                \"Thread {} iter {} got wrong buffer length\",\n                                thread_id,\n                                iter\n                            );\n\n                            // Write identifying data with checksum\n                            buf.as_mut_slice()[0] = thread_id;\n                            buf.as_mut_slice()[1] = iter;\n                            buf.as_mut_slice()[2] = if is_page { 0xAA } else { 0xBB };\n                            buf.as_mut_slice()[3] = thread_id ^ iter; // Checksum\n\n                            bufs.push(buf);\n\n                            // Occasionally drop a buffer to test recycling\n                            if bufs.len() > 2 && iter % 2 == 1 {\n                                let dropped = bufs.pop().unwrap();\n                                // Verify the dropped buffer still had valid data\n                                assert_eq!(\n                                    dropped.as_slice()[0],\n                                    thread_id,\n                                    \"Dropped buffer thread_id corrupted\"\n                                );\n                                dropped_count += 1;\n                            }\n                        }\n\n                        // Verify all remaining buffers before returning\n                        for (i, buf) in bufs.iter().enumerate() {\n                            assert_eq!(\n                                buf.as_slice()[0],\n                                thread_id,\n                                \"Pre-return buffer {} thread_id corrupted\",\n                                i\n                            );\n                            let checksum = buf.as_slice()[0] ^ buf.as_slice()[1];\n                            assert_eq!(\n                                buf.as_slice()[3],\n                                checksum,\n                                \"Pre-return buffer {} checksum invalid\",\n                                i\n                            );\n                        }\n\n                        (bufs, dropped_count)\n                    });\n                    handles.push(h);\n                }\n\n                let results: Vec<_> = handles.into_iter().map(|h| h.join().unwrap()).collect();\n\n                // Verify results from all threads\n                let mut total_remaining = 0;\n                let mut total_dropped = 0u8;\n\n                for (thread_id, (thread_bufs, dropped)) in results.iter().enumerate() {\n                    total_remaining += thread_bufs.len();\n                    total_dropped = total_dropped.saturating_add(*dropped);\n\n                    // Verify each buffer has correct identifying data\n                    for (buf_idx, buf) in thread_bufs.iter().enumerate() {\n                        assert_eq!(\n                            buf.as_slice()[0],\n                            thread_id as u8,\n                            \"Thread {} buffer {} thread_id corrupted: expected {}, got {}\",\n                            thread_id,\n                            buf_idx,\n                            thread_id,\n                            buf.as_slice()[0]\n                        );\n\n                        let marker = buf.as_slice()[2];\n                        assert!(\n                            marker == 0xAA || marker == 0xBB,\n                            \"Thread {} buffer {} has invalid marker 0x{:02X}\",\n                            thread_id,\n                            buf_idx,\n                            marker\n                        );\n\n                        // Verify checksum\n                        let expected_checksum = buf.as_slice()[0] ^ buf.as_slice()[1];\n                        assert_eq!(\n                            buf.as_slice()[3],\n                            expected_checksum,\n                            \"Thread {} buffer {} checksum mismatch\",\n                            thread_id,\n                            buf_idx\n                        );\n\n                        // Verify length matches marker\n                        let expected_len = if marker == 0xAA {\n                            4096\n                        } else {\n                            4096 + WAL_FRAME_HEADER_SIZE\n                        };\n                        assert_eq!(\n                            buf.len(),\n                            expected_len,\n                            \"Thread {} buffer {} length mismatch for marker\",\n                            thread_id,\n                            buf_idx\n                        );\n                    }\n                }\n\n                // Sanity check: we should have some buffers remaining\n                assert!(\n                    total_remaining > 0,\n                    \"No buffers remaining after stress test\"\n                );\n\n                // Collect all pointers and verify no duplicates among remaining buffers\n                let all_ptrs: HashSet<_> = results\n                    .iter()\n                    .flat_map(|(bufs, _)| bufs.iter().map(|b| b.as_ptr() as usize))\n                    .collect();\n                assert_eq!(\n                    all_ptrs.len(),\n                    total_remaining,\n                    \"Found duplicate pointers: {} unique out of {} remaining\",\n                    all_ptrs.len(),\n                    total_remaining\n                );\n\n                // Final allocation to verify pool is still healthy\n                let final_buf = pool.get_page();\n                assert_eq!(final_buf.len(), 4096, \"Final allocation failed\");\n                final_buf.as_mut_slice()[0] = 0xFF;\n                assert_eq!(final_buf.as_slice()[0], 0xFF, \"Final buffer write failed\");\n\n                let _ = total_dropped; // suppress warning\n            },\n            1000,\n        );\n    }\n}\n"
  },
  {
    "path": "core/storage/checksum.rs",
    "content": "#![allow(unused_variables, dead_code)]\nuse crate::{CompletionError, Result};\n\nconst CHECKSUM_PAGE_SIZE: usize = 4096;\nconst CHECKSUM_SIZE: usize = 8;\npub(crate) const CHECKSUM_REQUIRED_RESERVED_BYTES: u8 = CHECKSUM_SIZE as u8;\n\n#[derive(Debug, Clone)]\npub struct ChecksumContext {}\n\nimpl ChecksumContext {\n    pub fn new() -> Self {\n        ChecksumContext {}\n    }\n\n    #[cfg(not(feature = \"checksum\"))]\n    pub fn add_checksum_to_page(&self, _page: &mut [u8], _page_id: usize) -> Result<()> {\n        use crate::LimboError;\n        Err(LimboError::InternalError(\n            \"tursodb must be recompiled with checksum feature in order to use checksums\"\n                .to_string(),\n        ))\n    }\n\n    #[cfg(not(feature = \"checksum\"))]\n    pub fn verify_checksum(\n        &self,\n        _page: &mut [u8],\n        _page_id: usize,\n    ) -> std::result::Result<(), CompletionError> {\n        Err(CompletionError::ChecksumNotEnabled)\n    }\n\n    #[cfg(feature = \"checksum\")]\n    pub fn add_checksum_to_page(&self, page: &mut [u8], _page_id: usize) -> Result<()> {\n        if page.len() != CHECKSUM_PAGE_SIZE {\n            return Ok(());\n        }\n\n        // compute checksum on the actual page data (excluding the reserved checksum area)\n        let actual_page = &page[..CHECKSUM_PAGE_SIZE - CHECKSUM_SIZE];\n        let checksum = self.compute_checksum(actual_page);\n\n        let checksum_bytes = checksum.to_le_bytes();\n        assert_eq!(checksum_bytes.len(), CHECKSUM_SIZE);\n        page[CHECKSUM_PAGE_SIZE - CHECKSUM_SIZE..].copy_from_slice(&checksum_bytes);\n        Ok(())\n    }\n\n    #[cfg(feature = \"checksum\")]\n    pub fn verify_checksum(\n        &self,\n        page: &mut [u8],\n        page_id: usize,\n    ) -> std::result::Result<(), CompletionError> {\n        if page.len() != CHECKSUM_PAGE_SIZE {\n            return Ok(());\n        }\n\n        let actual_page = &page[..CHECKSUM_PAGE_SIZE - CHECKSUM_SIZE];\n        let stored_checksum_bytes = &page[CHECKSUM_PAGE_SIZE - CHECKSUM_SIZE..];\n        let stored_checksum = u64::from_le_bytes(stored_checksum_bytes.try_into().unwrap());\n\n        let computed_checksum = self.compute_checksum(actual_page);\n        if stored_checksum != computed_checksum {\n            tracing::error!(\n                \"Checksum mismatch on page {}: expected {:x}, got {:x}\",\n                page_id,\n                stored_checksum,\n                computed_checksum\n            );\n            return Err(CompletionError::ChecksumMismatch {\n                page_id,\n                expected: stored_checksum,\n                actual: computed_checksum,\n            });\n        }\n        Ok(())\n    }\n\n    fn compute_checksum(&self, data: &[u8]) -> u64 {\n        twox_hash::XxHash3_64::oneshot(data)\n    }\n\n    pub fn required_reserved_bytes(&self) -> u8 {\n        CHECKSUM_REQUIRED_RESERVED_BYTES\n    }\n}\n\nimpl Default for ChecksumContext {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\n#[cfg(test)]\n#[cfg(feature = \"checksum\")]\nmod tests {\n    use super::*;\n\n    fn get_random_page() -> [u8; CHECKSUM_PAGE_SIZE] {\n        let mut page = [0u8; CHECKSUM_PAGE_SIZE];\n        for (i, byte) in page\n            .iter_mut()\n            .enumerate()\n            .take(CHECKSUM_PAGE_SIZE - CHECKSUM_SIZE)\n        {\n            *byte = (i % 256) as u8;\n        }\n        page\n    }\n\n    #[test]\n    fn test_add_checksum_to_page() {\n        let ctx = ChecksumContext::new();\n        let mut page = get_random_page();\n\n        let result = ctx.add_checksum_to_page(&mut page, 2);\n        assert!(result.is_ok());\n\n        let checksum_bytes = &page[CHECKSUM_PAGE_SIZE - CHECKSUM_SIZE..];\n        let stored_checksum = u64::from_le_bytes(checksum_bytes.try_into().unwrap());\n\n        let actual_page = &page[..CHECKSUM_PAGE_SIZE - CHECKSUM_SIZE];\n        let expected_checksum = ctx.compute_checksum(actual_page);\n\n        assert_eq!(stored_checksum, expected_checksum);\n    }\n\n    #[test]\n    fn test_verify_checksum_valid() {\n        let ctx = ChecksumContext::new();\n        let mut page = get_random_page();\n\n        ctx.add_checksum_to_page(&mut page, 2).unwrap();\n\n        let result = ctx.verify_checksum(&mut page, 2);\n        assert!(result.is_ok());\n    }\n\n    #[test]\n    fn test_verify_checksum_mismatch() {\n        let ctx = ChecksumContext::new();\n        let mut page = get_random_page();\n\n        ctx.add_checksum_to_page(&mut page, 2).unwrap();\n\n        // corrupt the data to cause checksum mismatch\n        page[0] = 255;\n\n        let result = ctx.verify_checksum(&mut page, 2);\n        assert!(result.is_err());\n        match result.unwrap_err() {\n            CompletionError::ChecksumMismatch {\n                page_id,\n                expected,\n                actual,\n            } => {\n                assert_eq!(page_id, 2);\n                assert_ne!(expected, actual);\n            }\n            _ => panic!(\"Expected ChecksumMismatch error\"),\n        }\n    }\n\n    #[test]\n    fn test_verify_checksum_corrupted_checksum() {\n        let ctx = ChecksumContext::new();\n        let mut page = get_random_page();\n\n        ctx.add_checksum_to_page(&mut page, 2).unwrap();\n\n        // corrupt the checksum itself\n        page[CHECKSUM_PAGE_SIZE - 1] = 255;\n\n        let result = ctx.verify_checksum(&mut page, 2);\n        assert!(result.is_err());\n\n        match result.unwrap_err() {\n            CompletionError::ChecksumMismatch {\n                page_id,\n                expected,\n                actual,\n            } => {\n                assert_eq!(page_id, 2);\n                assert_ne!(expected, actual);\n            }\n            _ => panic!(\"Expected ChecksumMismatch error\"),\n        }\n    }\n}\n"
  },
  {
    "path": "core/storage/database.rs",
    "content": "use crate::io::FileSyncType;\nuse crate::storage::checksum::ChecksumContext;\nuse crate::storage::encryption::EncryptionContext;\nuse crate::sync::Arc;\nuse crate::{io::Completion, Buffer, CompletionError, LimboError, Result};\nuse crate::{\n    turso_assert, turso_assert_eq, turso_assert_greater_than, turso_assert_greater_than_or_equal,\n    turso_assert_less_than_or_equal,\n};\nuse tracing::{instrument, Level};\n\n#[derive(Debug, Clone)]\npub enum EncryptionOrChecksum {\n    Encryption(EncryptionContext),\n    Checksum(ChecksumContext),\n    None,\n}\n\n#[derive(Debug, Clone)]\npub struct IOContext {\n    encryption_or_checksum: EncryptionOrChecksum,\n}\n\nimpl IOContext {\n    pub fn encryption_context(&self) -> Option<&EncryptionContext> {\n        match &self.encryption_or_checksum {\n            EncryptionOrChecksum::Encryption(ctx) => Some(ctx),\n            _ => None,\n        }\n    }\n\n    pub fn get_reserved_space_bytes(&self) -> u8 {\n        match &self.encryption_or_checksum {\n            EncryptionOrChecksum::Encryption(ctx) => ctx.required_reserved_bytes(),\n            EncryptionOrChecksum::Checksum(ctx) => ctx.required_reserved_bytes(),\n            EncryptionOrChecksum::None => Default::default(),\n        }\n    }\n\n    pub fn set_encryption(&mut self, encryption_ctx: EncryptionContext) {\n        self.encryption_or_checksum = EncryptionOrChecksum::Encryption(encryption_ctx);\n    }\n\n    pub fn encryption_or_checksum(&self) -> &EncryptionOrChecksum {\n        &self.encryption_or_checksum\n    }\n\n    pub fn reset_checksum(&mut self) {\n        self.encryption_or_checksum = EncryptionOrChecksum::None;\n    }\n}\n\nimpl Default for IOContext {\n    fn default() -> Self {\n        #[cfg(feature = \"checksum\")]\n        let encryption_or_checksum = EncryptionOrChecksum::Checksum(ChecksumContext::default());\n        #[cfg(not(feature = \"checksum\"))]\n        let encryption_or_checksum = EncryptionOrChecksum::None;\n        Self {\n            encryption_or_checksum,\n        }\n    }\n}\n\n/// DatabaseStorage is an interface a database file that consists of pages.\n///\n/// The purpose of this trait is to abstract the upper layers of Limbo from\n/// the storage medium. A database can either be a file on disk, like in SQLite,\n/// or something like a remote page server service.\npub trait DatabaseStorage: Send + Sync {\n    fn read_header(&self, c: Completion) -> Result<Completion>;\n\n    fn read_page(&self, page_idx: usize, io_ctx: &IOContext, c: Completion) -> Result<Completion>;\n    fn write_page(\n        &self,\n        page_idx: usize,\n        buffer: Arc<Buffer>,\n        io_ctx: &IOContext,\n        c: Completion,\n    ) -> Result<Completion>;\n    fn write_pages(\n        &self,\n        first_page_idx: usize,\n        page_size: usize,\n        buffers: Vec<Arc<Buffer>>,\n        io_ctx: &IOContext,\n        c: Completion,\n    ) -> Result<Completion>;\n    fn sync(&self, c: Completion, sync_type: FileSyncType) -> Result<Completion>;\n    fn size(&self) -> Result<u64>;\n    fn truncate(&self, len: usize, c: Completion) -> Result<Completion>;\n}\n\n#[derive(Clone)]\npub struct DatabaseFile {\n    file: Arc<dyn crate::io::File>,\n}\n\nimpl DatabaseStorage for DatabaseFile {\n    #[instrument(skip_all, level = Level::DEBUG)]\n    fn read_header(&self, c: Completion) -> Result<Completion> {\n        self.file.pread(0, c)\n    }\n\n    #[instrument(skip_all, level = Level::DEBUG)]\n    fn read_page(&self, page_idx: usize, io_ctx: &IOContext, c: Completion) -> Result<Completion> {\n        // casting to i64 to check some weird casting that could've happened before. This should be\n        // okay since page numbers should be u32\n        turso_assert_greater_than_or_equal!(page_idx as i64, 0);\n        let r = c.as_read();\n        let size = r.buf().len();\n        turso_assert_greater_than!(page_idx, 0);\n        if !(512..=65536).contains(&size) || size & (size - 1) != 0 {\n            return Err(LimboError::NotADB);\n        }\n        let Some(pos) = (page_idx as u64 - 1).checked_mul(size as u64) else {\n            return Err(LimboError::IntegerOverflow);\n        };\n\n        match &io_ctx.encryption_or_checksum {\n            EncryptionOrChecksum::Encryption(ctx) => {\n                let encryption_ctx = ctx.clone();\n                let read_buffer = r.buf_arc();\n                let original_c = c.clone();\n                let decrypt_complete =\n                    Box::new(move |res: Result<(Arc<Buffer>, i32), CompletionError>| {\n                        let (buf, bytes_read) = match res {\n                            Ok((buf, bytes_read)) => (buf, bytes_read),\n                            Err(err) => {\n                                tracing::error!(err = ?err);\n                                original_c.error(err);\n                                return original_c.get_error();\n                            }\n                        };\n                        turso_assert_greater_than!(\n                            bytes_read, 0,\n                            \"database: expected to read data on success for encrypted page\",\n                            { \"page_idx\": page_idx }\n                        );\n                        match encryption_ctx.decrypt_page(buf.as_slice(), page_idx) {\n                            Ok(decrypted_data) => {\n                                let original_buf = original_c.as_read().buf();\n                                original_buf.as_mut_slice().copy_from_slice(&decrypted_data);\n                                original_c.complete(bytes_read);\n                                original_c.get_error()\n                            }\n                            Err(e) => {\n                                tracing::error!(\n                                    \"Failed to decrypt page data for page_id={page_idx}: {e}\"\n                                );\n                                turso_assert!(\n                                    !original_c.failed(),\n                                    \"Original completion already has an error\"\n                                );\n                                original_c.error(CompletionError::DecryptionError { page_idx });\n                                original_c.get_error()\n                            }\n                        }\n                    });\n                let wrapped_completion = Completion::new_read(read_buffer, decrypt_complete);\n                self.file.pread(pos, wrapped_completion)\n            }\n            EncryptionOrChecksum::Checksum(ctx) => {\n                let checksum_ctx = ctx.clone();\n                let read_buffer = r.buf_arc();\n                let original_c = c.clone();\n\n                let verify_complete =\n                    Box::new(move |res: Result<(Arc<Buffer>, i32), CompletionError>| {\n                        let (buf, bytes_read) = match res {\n                            Ok((buf, bytes_read)) => (buf, bytes_read),\n                            Err(err) => {\n                                original_c.error(err);\n                                return original_c.get_error();\n                            }\n                        };\n                        if bytes_read <= 0 {\n                            tracing::trace!(\"Read page {page_idx} with {} bytes\", bytes_read);\n                            original_c.complete(bytes_read);\n                            return original_c.get_error();\n                        }\n                        match checksum_ctx.verify_checksum(buf.as_mut_slice(), page_idx) {\n                            Ok(_) => {\n                                original_c.complete(bytes_read);\n                                original_c.get_error()\n                            }\n                            Err(e) => {\n                                tracing::error!(\n                                    \"Failed to verify checksum for page_id={page_idx}: {e}\"\n                                );\n                                turso_assert!(\n                                    !original_c.failed(),\n                                    \"Original completion already has an error\"\n                                );\n                                original_c.error(e);\n                                original_c.get_error()\n                            }\n                        }\n                    });\n\n                let wrapped_completion = Completion::new_read(read_buffer, verify_complete);\n                self.file.pread(pos, wrapped_completion)\n            }\n            EncryptionOrChecksum::None => self.file.pread(pos, c),\n        }\n    }\n\n    #[instrument(skip_all, level = Level::DEBUG)]\n    fn write_page(\n        &self,\n        page_idx: usize,\n        buffer: Arc<Buffer>,\n        io_ctx: &IOContext,\n        c: Completion,\n    ) -> Result<Completion> {\n        let buffer_size = buffer.len();\n        turso_assert_greater_than!(page_idx, 0);\n        turso_assert_greater_than_or_equal!(buffer_size, 512);\n        turso_assert_less_than_or_equal!(buffer_size, 65536);\n        turso_assert_eq!(buffer_size & (buffer_size - 1), 0);\n        let Some(pos) = (page_idx as u64 - 1).checked_mul(buffer_size as u64) else {\n            return Err(LimboError::IntegerOverflow);\n        };\n        let buffer = match &io_ctx.encryption_or_checksum {\n            EncryptionOrChecksum::Encryption(ctx) => encrypt_buffer(page_idx, buffer, ctx),\n            EncryptionOrChecksum::Checksum(ctx) => checksum_buffer(page_idx, buffer, ctx),\n            EncryptionOrChecksum::None => buffer,\n        };\n        self.file.pwrite(pos, buffer, c)\n    }\n\n    fn write_pages(\n        &self,\n        first_page_idx: usize,\n        page_size: usize,\n        buffers: Vec<Arc<Buffer>>,\n        io_ctx: &IOContext,\n        c: Completion,\n    ) -> Result<Completion> {\n        turso_assert_greater_than!(first_page_idx, 0);\n        turso_assert_greater_than_or_equal!(page_size, 512);\n        turso_assert_less_than_or_equal!(page_size, 65536);\n        turso_assert_eq!(page_size & (page_size - 1), 0);\n\n        let Some(pos) = (first_page_idx as u64 - 1).checked_mul(page_size as u64) else {\n            return Err(LimboError::IntegerOverflow);\n        };\n        let buffers = match &io_ctx.encryption_or_checksum() {\n            EncryptionOrChecksum::Encryption(ctx) => buffers\n                .into_iter()\n                .enumerate()\n                .map(|(i, buffer)| encrypt_buffer(first_page_idx + i, buffer, ctx))\n                .collect::<Vec<_>>(),\n            EncryptionOrChecksum::Checksum(ctx) => buffers\n                .into_iter()\n                .enumerate()\n                .map(|(i, buffer)| checksum_buffer(first_page_idx + i, buffer, ctx))\n                .collect::<Vec<_>>(),\n            EncryptionOrChecksum::None => buffers,\n        };\n        let c = self.file.pwritev(pos, buffers, c)?;\n        Ok(c)\n    }\n\n    #[instrument(skip_all, level = Level::DEBUG)]\n    fn sync(&self, c: Completion, sync_type: FileSyncType) -> Result<Completion> {\n        self.file.sync(c, sync_type)\n    }\n\n    #[instrument(skip_all, level = Level::DEBUG)]\n    fn size(&self) -> Result<u64> {\n        self.file.size()\n    }\n\n    #[instrument(skip_all, level = Level::INFO)]\n    fn truncate(&self, len: usize, c: Completion) -> Result<Completion> {\n        let c = self.file.truncate(len as u64, c)?;\n        Ok(c)\n    }\n}\n\n#[cfg(feature = \"fs\")]\nimpl DatabaseFile {\n    pub fn new(file: Arc<dyn crate::io::File>) -> Self {\n        Self { file }\n    }\n}\n\nfn encrypt_buffer(page_idx: usize, buffer: Arc<Buffer>, ctx: &EncryptionContext) -> Arc<Buffer> {\n    let encrypted_data = ctx.encrypt_page(buffer.as_slice(), page_idx).unwrap();\n    Arc::new(Buffer::new(encrypted_data.to_vec()))\n}\n\nfn checksum_buffer(page_idx: usize, buffer: Arc<Buffer>, ctx: &ChecksumContext) -> Arc<Buffer> {\n    ctx.add_checksum_to_page(buffer.as_mut_slice(), page_idx)\n        .unwrap();\n    buffer\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::{File, MemoryIO, IO};\n\n    struct MockFile {\n        read_result: std::result::Result<i32, CompletionError>,\n    }\n\n    impl File for MockFile {\n        fn lock_file(&self, _exclusive: bool) -> Result<()> {\n            Ok(())\n        }\n\n        fn unlock_file(&self) -> Result<()> {\n            Ok(())\n        }\n\n        fn pread(&self, _pos: u64, c: Completion) -> Result<Completion> {\n            match self.read_result {\n                Ok(bytes_read) => c.complete(bytes_read),\n                Err(err) => c.error(err),\n            }\n            Ok(c)\n        }\n\n        fn pwrite(&self, _pos: u64, _buffer: Arc<Buffer>, c: Completion) -> Result<Completion> {\n            c.complete(0);\n            Ok(c)\n        }\n\n        fn sync(&self, c: Completion, _sync_type: FileSyncType) -> Result<Completion> {\n            c.complete(0);\n            Ok(c)\n        }\n\n        fn size(&self) -> Result<u64> {\n            Ok(0)\n        }\n\n        fn truncate(&self, _len: u64, c: Completion) -> Result<Completion> {\n            c.complete(0);\n            Ok(c)\n        }\n    }\n\n    #[cfg(feature = \"checksum\")]\n    #[test]\n    fn checksum_read_wrapper_propagates_callback_errors() {\n        let db_file = DatabaseFile {\n            file: Arc::new(MockFile { read_result: Ok(0) }),\n        };\n        let io_ctx = IOContext::default();\n        let page_idx = 1usize;\n        let expected = 4096usize;\n        let buf = Arc::new(Buffer::new_temporary(expected));\n        let original = Completion::new_read(buf, move |res| {\n            let (_, bytes_read) = res.expect(\"mock read should complete\");\n            if bytes_read == 0 {\n                Some(CompletionError::ShortRead {\n                    page_idx,\n                    expected,\n                    actual: 0,\n                })\n            } else {\n                None\n            }\n        });\n\n        let wrapped = db_file\n            .read_page(page_idx, &io_ctx, original.clone())\n            .unwrap();\n        let io = MemoryIO::new();\n        let err = io\n            .wait_for_completion(wrapped)\n            .expect_err(\"wrapped completion must fail\");\n        assert!(matches!(\n            err,\n            LimboError::CompletionError(CompletionError::ShortRead { .. })\n        ));\n        assert!(matches!(\n            original.get_error(),\n            Some(CompletionError::ShortRead { .. })\n        ));\n    }\n\n    #[cfg(feature = \"checksum\")]\n    #[test]\n    fn checksum_read_wrapper_propagates_transport_errors_to_original_completion() {\n        let db_file = DatabaseFile {\n            file: Arc::new(MockFile {\n                read_result: Err(CompletionError::Aborted),\n            }),\n        };\n        let io_ctx = IOContext::default();\n        let page_idx = 1usize;\n        let buf = Arc::new(Buffer::new_temporary(4096));\n        let original = Completion::new_read(buf, |_res| None);\n\n        let wrapped = db_file\n            .read_page(page_idx, &io_ctx, original.clone())\n            .unwrap();\n        let io = MemoryIO::new();\n        let err = io\n            .wait_for_completion(wrapped)\n            .expect_err(\"wrapped completion must fail\");\n        assert!(matches!(\n            err,\n            LimboError::CompletionError(CompletionError::Aborted)\n        ));\n        assert_eq!(original.get_error(), Some(CompletionError::Aborted));\n    }\n}\n"
  },
  {
    "path": "core/storage/encryption.rs",
    "content": "#![allow(unused_variables, dead_code)]\nuse crate::turso_assert;\nuse crate::{LimboError, Result};\nuse aegis::aegis128l::Aegis128L;\nuse aegis::aegis128x2::Aegis128X2;\nuse aegis::aegis128x4::Aegis128X4;\nuse aegis::aegis256::Aegis256;\nuse aegis::aegis256x2::Aegis256X2;\nuse aegis::aegis256x4::Aegis256X4;\nuse aes_gcm::{\n    aead::{Aead, AeadCore, KeyInit, OsRng},\n    Aes128Gcm, Aes256Gcm, Key, Nonce,\n};\nuse turso_macros::{match_ignore_ascii_case, AtomicEnum};\n\n/// Encryption Scheme\n/// We support two major algorithms: AEGIS, AES GCM. These algorithms picked so that they also do\n/// verification of the ciphertext, so we don't need to implement. That is if the page is corrupted\n/// (or tampered), then we will know if we got garbage bytes post decryption.\n///\n/// We perform encryption at the page level, i.e., each page is encrypted and decrypted individually.\n/// We store the nonce and tag (or the verification bits) in the page itself.  We also generate a\n/// random nonce every time we encrypt a page.\n///\n/// Example: Assume the page size is 4096 bytes and we use AEGIS 256. So we reserve the last 48 bytes\n/// for the nonce (32 bytes) and tag (16 bytes).\n///\n/// ```ignore\n///             Unencrypted Page              Encrypted Page\n///             ┌───────────────┐            ┌───────────────┐\n///             │               │            │               │\n///             │ Page Content  │            │   Encrypted   │\n///             │ (4048 bytes)  │  ────────► │    Content    │\n///             │               │            │ (4048 bytes)  │\n///             ├───────────────┤            ├───────────────┤\n///             │   Reserved    │            │    Tag (16)   │\n///             │  (48 bytes)   │            ├───────────────┤\n///             │   [empty]     │            │   Nonce (32)  │\n///             └───────────────┘            └───────────────┘\n///                4096 bytes                   4096 bytes\n/// ```\n///\n/// The above applies to all the pages except Page 1. The page 1 contains the SQLite header (the\n/// first 100 bytes). Specifically, the bytes 16 to 24 contain metadata which is required to\n/// initialise the connection, which happens before we can setup the encryption context. So, we\n/// don't encrypt the header but instead use the header data as additional data (AD) for the\n/// encryption of the rest of the page. This provides us protection against tampering and\n/// corruption for the unencrypted portion.\n///\n/// On disk, the encrypted page 1 contains special bytes replacing the SQLite's magic bytes (the\n/// first 16 bytes):\n///\n/// ```ignore\n///                    Turso Header (16 bytes)\n///        ┌─────────┬───────┬────────┬──────────────────┐\n///        │         │       │        │                  │\n///        │  Turso  │Version│ Cipher │     Unused       │\n///        │  (5)    │ (1)   │  (1)   │    (9 bytes)     │\n///        │         │       │        │                  │\n///        └─────────┴───────┴────────┴──────────────────┘\n///         0-4      5       6        7-15\n///\n///        Standard SQLite Header: \"SQLite format 3\\0\" (16 bytes)\n///                            ↓\n///        Turso Encrypted Header: \"Turso\" + Version + Cipher ID + Unused\n/// ```\n///\n/// constants used for the Turso page header in the encrypted dbs.\npub const TURSO_HEADER_PREFIX: &[u8] = b\"Turso\";\npub const SQLITE_HEADER: &[u8] = b\"SQLite format 3\\0\";\nconst TURSO_VERSION: u8 = 0x00;\nconst VERSION_OFFSET: usize = 5;\nconst CIPHER_OFFSET: usize = 6;\nconst TURSO_HEADER_SIZE: usize = 16;\n\n#[derive(Clone)]\npub enum EncryptionKey {\n    Key128([u8; 16]),\n    Key256([u8; 32]),\n}\n\nimpl EncryptionKey {\n    pub fn new_256(key: [u8; 32]) -> Self {\n        Self::Key256(key)\n    }\n\n    pub fn new_128(key: [u8; 16]) -> Self {\n        Self::Key128(key)\n    }\n\n    pub fn from_hex_string(s: &str) -> Result<Self> {\n        let hex_str = s.trim();\n        let bytes = hex::decode(hex_str)\n            .map_err(|e| LimboError::InvalidArgument(format!(\"Invalid hex string: {e}\")))?;\n\n        match bytes.len() {\n            16 => {\n                let key: [u8; 16] = bytes.try_into().unwrap();\n                Ok(Self::Key128(key))\n            }\n            32 => {\n                let key: [u8; 32] = bytes.try_into().unwrap();\n                Ok(Self::Key256(key))\n            }\n            _ => Err(LimboError::InvalidArgument(format!(\n                \"Hex string must decode to exactly 16 or 32 bytes, got {}\",\n                bytes.len()\n            ))),\n        }\n    }\n\n    pub fn as_slice(&self) -> &[u8] {\n        match self {\n            Self::Key128(key) => key,\n            Self::Key256(key) => key,\n        }\n    }\n\n    #[allow(clippy::len_without_is_empty)]\n    pub fn len(&self) -> usize {\n        match self {\n            Self::Key128(_) => 16,\n            Self::Key256(_) => 32,\n        }\n    }\n\n    pub fn as_128(&self) -> Option<&[u8; 16]> {\n        match self {\n            Self::Key128(key) => Some(key),\n            _ => None,\n        }\n    }\n\n    pub fn as_256(&self) -> Option<&[u8; 32]> {\n        match self {\n            Self::Key256(key) => Some(key),\n            _ => None,\n        }\n    }\n}\n\nimpl std::fmt::Debug for EncryptionKey {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"EncryptionKey\")\n            .field(\"key\", &\"<encryption key redacted>\")\n            .finish()\n    }\n}\n\nimpl Drop for EncryptionKey {\n    fn drop(&mut self) {\n        // securely zero out the key bytes before dropping\n        match self {\n            Self::Key128(key) => {\n                for byte in key.iter_mut() {\n                    unsafe {\n                        std::ptr::write_volatile(byte, 0);\n                    }\n                }\n            }\n            Self::Key256(key) => {\n                for byte in key.iter_mut() {\n                    unsafe {\n                        std::ptr::write_volatile(byte, 0);\n                    }\n                }\n            }\n        }\n    }\n}\n\nmacro_rules! define_aegis_cipher {\n    ($struct_name:ident, $cipher_type:ty, key128, $nonce_size:literal, $name:literal) => {\n        define_aegis_cipher!(@impl $struct_name, $cipher_type, $nonce_size, $name, 16, as_128);\n    };\n    ($struct_name:ident, $cipher_type:ty, key256, $nonce_size:literal, $name:literal) => {\n        define_aegis_cipher!(@impl $struct_name, $cipher_type, $nonce_size, $name, 32, as_256);\n    };\n    (@impl $struct_name:ident, $cipher_type:ty, $nonce_size:literal, $name:literal, $key_size:literal, $key_method:ident) => {\n        #[derive(Clone)]\n        pub struct $struct_name {\n            key: EncryptionKey,\n        }\n\n        impl $struct_name {\n            const TAG_SIZE: usize = 16;\n\n            fn new(key: &EncryptionKey) -> Self {\n                Self { key: key.clone() }\n            }\n\n            fn encrypt(&self, plaintext: &[u8], ad: &[u8]) -> Result<(Vec<u8>, [u8; $nonce_size])> {\n                let nonce = generate_secure_nonce::<$nonce_size>();\n                let key_bytes = self.key.$key_method()\n                    .ok_or_else(|| -> LimboError { CipherError::InvalidKeySize { cipher: $name, expected: $key_size }.into() })?;\n                let (ciphertext, tag) = <$cipher_type>::new(key_bytes, &nonce).encrypt(plaintext, ad);\n                let mut result = ciphertext;\n                result.extend_from_slice(&tag);\n                Ok((result, nonce))\n            }\n\n            fn decrypt(&self, ciphertext: &[u8], nonce: &[u8; $nonce_size], ad: &[u8]) -> Result<Vec<u8>> {\n                if ciphertext.len() < Self::TAG_SIZE {\n                    return Err(LimboError::from(CipherError::CiphertextTooShort { cipher: $name }));\n                }\n                let (ct, tag) = ciphertext.split_at(ciphertext.len() - Self::TAG_SIZE);\n                let tag_array: [u8; 16] = tag.try_into().map_err(|_| -> LimboError { CipherError::InvalidTagSize { cipher: $name }.into() })?;\n\n                let key_bytes = self.key.$key_method()\n                    .ok_or_else(|| -> LimboError { CipherError::InvalidKeySize { cipher: $name, expected: $key_size }.into() })?;\n                <$cipher_type>::new(key_bytes, nonce)\n                    .decrypt(ct, &tag_array, ad)\n                    .map_err(|_| -> LimboError { CipherError::DecryptionFailed { cipher: $name }.into() })\n            }\n        }\n\n        impl std::fmt::Debug for $struct_name {\n            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n                f.debug_struct(stringify!($struct_name))\n                    .field(\"key\", &\"<redacted>\")\n                    .finish()\n            }\n        }\n    };\n}\n\nmacro_rules! define_aes_gcm_cipher {\n    ($struct_name:ident, $cipher_type:ty, key128, $name:literal) => {\n        define_aes_gcm_cipher!(@impl $struct_name, $cipher_type, $name, 16, as_128);\n    };\n    ($struct_name:ident, $cipher_type:ty, key256, $name:literal) => {\n        define_aes_gcm_cipher!(@impl $struct_name, $cipher_type, $name, 32, as_256);\n    };\n    (@impl $struct_name:ident, $cipher_type:ty, $name:literal, $key_size:literal, $key_method:ident) => {\n        #[derive(Clone)]\n        pub struct $struct_name {\n            cipher: $cipher_type,\n        }\n\n        impl $struct_name {\n            const TAG_SIZE: usize = 16;\n            const NONCE_SIZE: usize = 12;\n\n            fn new(key: &EncryptionKey) -> Result<Self> {\n                let key_bytes = key.$key_method()\n                    .ok_or_else(|| -> LimboError { CipherError::InvalidKeySize { cipher: $name, expected: $key_size }.into() })?;\n                let cipher_key: &Key<$cipher_type> = key_bytes.into();\n                Ok(Self {\n                    cipher: <$cipher_type>::new(cipher_key),\n                })\n            }\n\n            fn encrypt(&self, plaintext: &[u8], ad: &[u8]) -> Result<(Vec<u8>, [u8; 12])> {\n                let nonce = <$cipher_type>::generate_nonce(&mut OsRng);\n                let ciphertext = self.cipher.encrypt(&nonce, aes_gcm::aead::Payload {\n                    msg: plaintext,\n                    aad: ad,\n                }).map_err(|e| {\n                    LimboError::InternalError(format!(\"{} encryption failed: {e:?}\", $name))\n                })?;\n                let mut nonce_array = [0u8; 12];\n                nonce_array.copy_from_slice(&nonce);\n                Ok((ciphertext, nonce_array))\n            }\n\n            fn decrypt(&self, ciphertext: &[u8], nonce: &[u8; 12], ad: &[u8]) -> Result<Vec<u8>> {\n                let nonce = Nonce::from_slice(nonce);\n                self.cipher\n                    .decrypt(nonce, aes_gcm::aead::Payload {\n                        msg: ciphertext,\n                        aad: ad,\n                    })\n                    .map_err(|_| -> LimboError { CipherError::DecryptionFailed { cipher: $name }.into() })\n            }\n        }\n\n        impl std::fmt::Debug for $struct_name {\n            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n                f.debug_struct(stringify!($struct_name))\n                    .field(\"key\", &\"<redacted>\")\n                    .finish()\n            }\n        }\n    };\n}\n\n// AES-GCM ciphers\ndefine_aes_gcm_cipher!(Aes128GcmCipher, Aes128Gcm, key128, \"AES-128-GCM\");\ndefine_aes_gcm_cipher!(Aes256GcmCipher, Aes256Gcm, key256, \"AES-256-GCM\");\n\n// AEGIS ciphers\ndefine_aegis_cipher!(Aegis256Cipher, Aegis256::<16>, key256, 32, \"AEGIS-256\");\ndefine_aegis_cipher!(\n    Aegis256X2Cipher,\n    Aegis256X2::<16>,\n    key256,\n    32,\n    \"AEGIS-256X2\"\n);\ndefine_aegis_cipher!(\n    Aegis256X4Cipher,\n    Aegis256X4::<16>,\n    key256,\n    32,\n    \"AEGIS-256X4\"\n);\ndefine_aegis_cipher!(\n    Aegis128X2Cipher,\n    Aegis128X2::<16>,\n    key128,\n    16,\n    \"AEGIS-128X2\"\n);\ndefine_aegis_cipher!(Aegis128LCipher, Aegis128L::<16>, key128, 16, \"AEGIS-128L\");\ndefine_aegis_cipher!(\n    Aegis128X4Cipher,\n    Aegis128X4::<16>,\n    key128,\n    16,\n    \"AEGIS-128X4\"\n);\n\n#[derive(Debug, AtomicEnum, Clone, Copy, PartialEq, Eq)]\npub enum CipherMode {\n    None,\n    Aes128Gcm,\n    Aes256Gcm,\n    Aegis256,\n    Aegis128L,\n    Aegis128X2,\n    Aegis128X4,\n    Aegis256X2,\n    Aegis256X4,\n}\n\nimpl TryFrom<&str> for CipherMode {\n    type Error = LimboError;\n\n    fn try_from(s: &str) -> Result<Self, Self::Error> {\n        let s_bytes = s.as_bytes();\n        match_ignore_ascii_case!(match s_bytes {\n            b\"aes128gcm\" | b\"aes-128-gcm\" | b\"aes_128_gcm\" => Ok(CipherMode::Aes128Gcm),\n            b\"aes256gcm\" | b\"aes-256-gcm\" | b\"aes_256_gcm\" => Ok(CipherMode::Aes256Gcm),\n            b\"aegis256\" | b\"aegis-256\" | b\"aegis_256\" => Ok(CipherMode::Aegis256),\n            b\"aegis128l\" | b\"aegis-128l\" | b\"aegis_128l\" => Ok(CipherMode::Aegis128L),\n            b\"aegis128x2\" | b\"aegis-128x2\" | b\"aegis_128x2\" => Ok(CipherMode::Aegis128X2),\n            b\"aegis128x4\" | b\"aegis-128x4\" | b\"aegis_128x4\" => Ok(CipherMode::Aegis128X4),\n            b\"aegis256x2\" | b\"aegis-256x2\" | b\"aegis_256x2\" => Ok(CipherMode::Aegis256X2),\n            b\"aegis256x4\" | b\"aegis-256x4\" | b\"aegis_256x4\" => Ok(CipherMode::Aegis256X4),\n            _ => Err(LimboError::InvalidArgument(format!(\n                \"Unknown cipher name: {s}\"\n            ))),\n        })\n    }\n}\n\nimpl std::fmt::Display for CipherMode {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            CipherMode::Aes128Gcm => write!(f, \"aes128gcm\"),\n            CipherMode::Aes256Gcm => write!(f, \"aes256gcm\"),\n            CipherMode::Aegis256 => write!(f, \"aegis256\"),\n            CipherMode::Aegis128L => write!(f, \"aegis128l\"),\n            CipherMode::Aegis128X2 => write!(f, \"aegis128x2\"),\n            CipherMode::Aegis128X4 => write!(f, \"aegis128x4\"),\n            CipherMode::Aegis256X2 => write!(f, \"aegis256x2\"),\n            CipherMode::Aegis256X4 => write!(f, \"aegis256x4\"),\n            CipherMode::None => write!(f, \"None\"),\n        }\n    }\n}\n\nimpl CipherMode {\n    /// Every cipher requires a specific key size. For 256-bit algorithms, this is 32 bytes.\n    /// For 128-bit algorithms, it would be 16 bytes, etc.\n    pub fn required_key_size(&self) -> usize {\n        match self {\n            CipherMode::Aes128Gcm => 16,\n            CipherMode::Aes256Gcm => 32,\n            CipherMode::Aegis256 => 32,\n            CipherMode::Aegis256X2 => 32,\n            CipherMode::Aegis256X4 => 32,\n            CipherMode::Aegis128L => 16,\n            CipherMode::Aegis128X2 => 16,\n            CipherMode::Aegis128X4 => 16,\n            CipherMode::None => 0,\n        }\n    }\n\n    /// Returns the nonce size for this cipher mode.\n    pub fn nonce_size(&self) -> usize {\n        match self {\n            CipherMode::Aes128Gcm => 12,\n            CipherMode::Aes256Gcm => 12,\n            CipherMode::Aegis256 => 32,\n            CipherMode::Aegis256X2 => 32,\n            CipherMode::Aegis256X4 => 32,\n            CipherMode::Aegis128L => 16,\n            CipherMode::Aegis128X2 => 16,\n            CipherMode::Aegis128X4 => 16,\n            CipherMode::None => 0,\n        }\n    }\n\n    /// Returns the authentication tag size for this cipher mode.\n    pub fn tag_size(&self) -> usize {\n        match self {\n            CipherMode::Aes128Gcm => 16,\n            CipherMode::Aes256Gcm => 16,\n            CipherMode::Aegis256 => 16,\n            CipherMode::Aegis256X2 => 16,\n            CipherMode::Aegis256X4 => 16,\n            CipherMode::Aegis128L => 16,\n            CipherMode::Aegis128X2 => 16,\n            CipherMode::Aegis128X4 => 16,\n            CipherMode::None => 0,\n        }\n    }\n\n    /// Returns the total metadata size (nonce + tag) for this cipher mode.\n    pub fn metadata_size(&self) -> usize {\n        self.nonce_size() + self.tag_size()\n    }\n\n    /// Returns the cipher identifier byte for Turso header\n    pub fn cipher_id(&self) -> u8 {\n        match self {\n            CipherMode::Aes128Gcm => 1,\n            CipherMode::Aes256Gcm => 2,\n            CipherMode::Aegis256 => 3,\n            CipherMode::Aegis256X2 => 4,\n            CipherMode::Aegis256X4 => 5,\n            CipherMode::Aegis128L => 6,\n            CipherMode::Aegis128X2 => 7,\n            CipherMode::Aegis128X4 => 8,\n            CipherMode::None => 0,\n        }\n    }\n\n    /// Creates a CipherMode from cipher identifier byte. This is used when read from Turso header.\n    pub fn from_cipher_id(id: u8) -> Result<Self> {\n        match id {\n            1 => Ok(CipherMode::Aes128Gcm),\n            2 => Ok(CipherMode::Aes256Gcm),\n            3 => Ok(CipherMode::Aegis256),\n            4 => Ok(CipherMode::Aegis256X2),\n            5 => Ok(CipherMode::Aegis256X4),\n            6 => Ok(CipherMode::Aegis128L),\n            7 => Ok(CipherMode::Aegis128X2),\n            8 => Ok(CipherMode::Aegis128X4),\n            _ => Err(LimboError::InvalidArgument(format!(\n                \"Unknown cipher ID: {id}\"\n            ))),\n        }\n    }\n}\n\n#[derive(Clone)]\npub enum Cipher {\n    Aes128Gcm(Box<Aes128GcmCipher>),\n    Aes256Gcm(Box<Aes256GcmCipher>),\n    Aegis256(Box<Aegis256Cipher>),\n    Aegis256X2(Box<Aegis256X2Cipher>),\n    Aegis256X4(Box<Aegis256X4Cipher>),\n    Aegis128L(Box<Aegis128LCipher>),\n    Aegis128X2(Box<Aegis128X2Cipher>),\n    Aegis128X4(Box<Aegis128X4Cipher>),\n}\n\nimpl std::fmt::Debug for Cipher {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Cipher::Aes128Gcm(_) => write!(f, \"Cipher::Aes128Gcm\"),\n            Cipher::Aes256Gcm(_) => write!(f, \"Cipher::Aes256Gcm\"),\n            Cipher::Aegis256(_) => write!(f, \"Cipher::Aegis256\"),\n            Cipher::Aegis256X2(_) => write!(f, \"Cipher::Aegis256X2\"),\n            Cipher::Aegis256X4(_) => write!(f, \"Cipher::Aegis256X4\"),\n            Cipher::Aegis128L(_) => write!(f, \"Cipher::Aegis128L\"),\n            Cipher::Aegis128X2(_) => write!(f, \"Cipher::Aegis128X2\"),\n            Cipher::Aegis128X4(_) => write!(f, \"Cipher::Aegis128X4\"),\n        }\n    }\n}\n\n#[derive(Debug, Clone)]\npub struct EncryptionContext {\n    cipher_mode: CipherMode,\n    cipher: Cipher,\n    page_size: usize,\n}\n\nimpl EncryptionContext {\n    pub fn new(cipher_mode: CipherMode, key: &EncryptionKey, page_size: usize) -> Result<Self> {\n        let required_size = cipher_mode.required_key_size();\n        if key.len() != required_size {\n            return Err(crate::LimboError::InvalidArgument(format!(\n                \"Invalid key size for {:?}: expected {} bytes, got {}\",\n                cipher_mode,\n                required_size,\n                key.len()\n            )));\n        }\n\n        let cipher = match cipher_mode {\n            CipherMode::Aes128Gcm => Cipher::Aes128Gcm(Box::new(Aes128GcmCipher::new(key)?)),\n            CipherMode::Aes256Gcm => Cipher::Aes256Gcm(Box::new(Aes256GcmCipher::new(key)?)),\n            CipherMode::Aegis256 => Cipher::Aegis256(Box::new(Aegis256Cipher::new(key))),\n            CipherMode::Aegis256X2 => Cipher::Aegis256X2(Box::new(Aegis256X2Cipher::new(key))),\n            CipherMode::Aegis256X4 => Cipher::Aegis256X4(Box::new(Aegis256X4Cipher::new(key))),\n            CipherMode::Aegis128L => Cipher::Aegis128L(Box::new(Aegis128LCipher::new(key))),\n            CipherMode::Aegis128X2 => Cipher::Aegis128X2(Box::new(Aegis128X2Cipher::new(key))),\n            CipherMode::Aegis128X4 => Cipher::Aegis128X4(Box::new(Aegis128X4Cipher::new(key))),\n            CipherMode::None => {\n                return Err(LimboError::InvalidArgument(\n                    \"must select valid CipherMode\".into(),\n                ))\n            }\n        };\n        Ok(Self {\n            cipher_mode,\n            cipher,\n            page_size,\n        })\n    }\n\n    pub fn cipher_mode(&self) -> CipherMode {\n        self.cipher_mode\n    }\n\n    /// Returns the number of reserved bytes required at the end of each page for encryption metadata.\n    pub fn required_reserved_bytes(&self) -> u8 {\n        self.cipher_mode.metadata_size() as u8\n    }\n\n    pub fn encrypt_chunk(&self, plaintext: &[u8], aad: &[u8]) -> Result<(Vec<u8>, Vec<u8>)> {\n        self.encrypt_raw_with_ad(plaintext, aad)\n    }\n\n    pub fn decrypt_chunk(&self, ciphertext: &[u8], nonce: &[u8], aad: &[u8]) -> Result<Vec<u8>> {\n        self.decrypt_raw_with_ad(ciphertext, nonce, aad)\n    }\n\n    pub fn nonce_size(&self) -> usize {\n        self.cipher_mode.nonce_size()\n    }\n\n    pub fn tag_size(&self) -> usize {\n        self.cipher_mode.tag_size()\n    }\n\n    /// Creates Turso header for encrypted page 1\n    fn create_turso_header(&self) -> [u8; TURSO_HEADER_SIZE] {\n        let mut header = [0u8; TURSO_HEADER_SIZE];\n\n        // \"Turso\" prefix (5 bytes)\n        header[..TURSO_HEADER_PREFIX.len()].copy_from_slice(TURSO_HEADER_PREFIX);\n\n        // version byte (1 byte)\n        header[VERSION_OFFSET] = TURSO_VERSION;\n\n        // cipher identifier (1 byte)\n        header[CIPHER_OFFSET] = self.cipher_mode.cipher_id();\n\n        // remaining unused 9 bytes\n        header\n    }\n\n    /// Validates and extracts cipher mode from Turso header\n    fn validate_turso_header(&self, header: &[u8]) -> Result<()> {\n        if header.len() < TURSO_HEADER_SIZE {\n            return Err(LimboError::InternalError(\n                \"Header too short for encrypted Turso db\".into(),\n            ));\n        }\n\n        if &header[..TURSO_HEADER_PREFIX.len()] != TURSO_HEADER_PREFIX {\n            return Err(LimboError::InternalError(\n                \"Invalid Turso header: prefix mismatch\".into(),\n            ));\n        }\n\n        let version = header[VERSION_OFFSET];\n        if version != TURSO_VERSION {\n            return Err(LimboError::InternalError(format!(\n                \"Unsupported Turso header version: expected {TURSO_VERSION}, got {version}\"\n            )));\n        }\n\n        let cipher_id = header[CIPHER_OFFSET];\n        let header_cipher = CipherMode::from_cipher_id(cipher_id)?;\n        if header_cipher != self.cipher_mode {\n            return Err(LimboError::InternalError(format!(\n                \"Cipher mode mismatch: expected {:?} (ID {}), got {:?} (ID {})\",\n                self.cipher_mode,\n                self.cipher_mode.cipher_id(),\n                header_cipher,\n                cipher_id\n            )));\n        }\n\n        if header[CIPHER_OFFSET + 1..TURSO_HEADER_SIZE]\n            .iter()\n            .any(|&b| b != 0)\n        {\n            return Err(LimboError::InternalError(\n                \"Invalid Turso header: unused bytes must be zero\".into(),\n            ));\n        }\n        Ok(())\n    }\n\n    #[cfg(feature = \"encryption\")]\n    pub fn encrypt_page(&self, page: &[u8], page_id: usize) -> Result<Vec<u8>> {\n        use crate::storage::sqlite3_ondisk::DatabaseHeader;\n        if page_id == DatabaseHeader::PAGE_ID {\n            return self.encrypt_page_1(page);\n        }\n        tracing::debug!(\"encrypting page {}\", page_id);\n        assert_eq!(\n            page.len(),\n            self.page_size,\n            \"Page data must be exactly {} bytes\",\n            self.page_size\n        );\n\n        let metadata_size = self.cipher_mode.metadata_size();\n        let reserved_bytes = &page[self.page_size - metadata_size..];\n\n        #[cfg(debug_assertions)]\n        {\n            let reserved_bytes_zeroed = reserved_bytes.iter().all(|&b| b == 0);\n            turso_assert!(\n                reserved_bytes_zeroed,\n                \"last reserved bytes must be empty/zero, but found non-zero bytes\"\n            );\n        }\n\n        let payload = &page[..self.page_size - metadata_size];\n        let (encrypted, nonce) = self.encrypt_raw(payload)?;\n\n        let nonce_size = self.cipher_mode.nonce_size();\n        assert_eq!(\n            encrypted.len(),\n            self.page_size - nonce_size,\n            \"Encrypted page must be exactly {} bytes\",\n            self.page_size - nonce_size\n        );\n\n        let mut result = Vec::with_capacity(self.page_size);\n        result.extend_from_slice(&encrypted);\n        result.extend_from_slice(&nonce);\n        assert_eq!(\n            result.len(),\n            self.page_size,\n            \"Encrypted page must be exactly {} bytes\",\n            self.page_size\n        );\n        Ok(result)\n    }\n\n    #[cfg(feature = \"encryption\")]\n    pub fn decrypt_page(&self, encrypted_page: &[u8], page_id: usize) -> Result<Vec<u8>> {\n        use crate::storage::sqlite3_ondisk::DatabaseHeader;\n        if page_id == DatabaseHeader::PAGE_ID {\n            return self.decrypt_page_1(encrypted_page);\n        }\n        tracing::debug!(\"decrypting page {}\", page_id);\n        assert_eq!(\n            encrypted_page.len(),\n            self.page_size,\n            \"Encrypted page data must be exactly {} bytes\",\n            self.page_size\n        );\n\n        let nonce_size = self.cipher_mode.nonce_size();\n        let nonce_offset = encrypted_page.len() - nonce_size;\n        let payload = &encrypted_page[..nonce_offset];\n        let nonce = &encrypted_page[nonce_offset..];\n\n        let decrypted_data = self.decrypt_raw(payload, nonce)?;\n        let metadata_size = self.cipher_mode.metadata_size();\n        assert_eq!(\n            decrypted_data.len(),\n            self.page_size - metadata_size,\n            \"Decrypted page data must be exactly {} bytes\",\n            self.page_size - metadata_size\n        );\n\n        let mut result = Vec::with_capacity(self.page_size);\n        result.extend_from_slice(&decrypted_data);\n        result.resize(self.page_size, 0);\n\n        assert_eq!(\n            result.len(),\n            self.page_size,\n            \"Decrypted page data must be exactly {} bytes\",\n            self.page_size\n        );\n        Ok(result)\n    }\n\n    #[cfg(feature = \"encryption\")]\n    fn encrypt_page_1(&self, page: &[u8]) -> Result<Vec<u8>> {\n        use crate::storage::sqlite3_ondisk::DatabaseHeader;\n\n        tracing::debug!(\"encrypting page 1\");\n        assert_eq!(\n            page.len(),\n            self.page_size,\n            \"Page data must be exactly {} bytes\",\n            self.page_size\n        );\n\n        // since this is page 1, this must have header\n        turso_assert!(\n            page.starts_with(SQLITE_HEADER),\n            \"Page 1 must start with SQLite header\"\n        );\n\n        let metadata_size = self.cipher_mode.metadata_size();\n        let reserved_bytes = &page[self.page_size - metadata_size..];\n\n        #[cfg(debug_assertions)]\n        {\n            // In debug builds, ensure that the reserved bytes are zeroed out. So even when we are\n            // reusing a page from buffer pool, we zero out in debug build so that we can be\n            // sure that b tree layer is not writing any data into the reserved space.\n            // We avoid calling `memset` in release builds for performance reasons.\n            let reserved_bytes_zeroed = reserved_bytes.iter().all(|&b| b == 0);\n            turso_assert!(\n                reserved_bytes_zeroed,\n                \"last reserved bytes must be empty/zero, but found non-zero bytes\"\n            );\n        }\n\n        // page 1 encryption:\n        // 1. First 16 bytes are replaced with Turso magic bytes\n        // 2. Next 84 bytes (16-100) are kept as-is (not encrypted)\n        // 3. Remaining bytes (100-end) are encrypted\n        // 4. The header (the first 100 bytes) as associated data\n        let turso_header = self.create_turso_header();\n        let mut new_header = Vec::with_capacity(DatabaseHeader::SIZE);\n        new_header.extend_from_slice(&turso_header);\n        new_header.extend_from_slice(&page[TURSO_HEADER_SIZE..DatabaseHeader::SIZE]);\n\n        let payload = &page[DatabaseHeader::SIZE..self.page_size - metadata_size];\n        let (encrypted, nonce) = self.encrypt_raw_with_ad(payload, &new_header)?;\n\n        let nonce_size = self.cipher_mode.nonce_size();\n        assert_eq!(\n            encrypted.len(),\n            self.page_size - nonce_size - DatabaseHeader::SIZE,\n            \"Encrypted page must be exactly {} bytes\",\n            self.page_size - nonce_size - DatabaseHeader::SIZE\n        );\n\n        let mut result = Vec::with_capacity(self.page_size);\n\n        // 1. copy the header\n        result.append(&mut new_header);\n        // 2. copy the encrypted payload\n        result.extend_from_slice(&encrypted);\n        // 3. now add the nonce\n        result.extend_from_slice(&nonce);\n\n        assert_eq!(\n            result.len(),\n            self.page_size,\n            \"Encrypted page must be exactly {} bytes\",\n            self.page_size\n        );\n        Ok(result)\n    }\n\n    #[cfg(feature = \"encryption\")]\n    fn decrypt_page_1(&self, encrypted_page: &[u8]) -> Result<Vec<u8>> {\n        use crate::storage::sqlite3_ondisk::DatabaseHeader;\n\n        tracing::debug!(\"decrypting page 1\");\n        assert_eq!(\n            encrypted_page.len(),\n            self.page_size,\n            \"Encrypted page data must be exactly {} bytes\",\n            self.page_size\n        );\n\n        self.validate_turso_header(&encrypted_page[..TURSO_HEADER_SIZE])?;\n\n        let nonce_size = self.cipher_mode.nonce_size();\n        let nonce_offset = encrypted_page.len() - nonce_size;\n        let payload = &encrypted_page[DatabaseHeader::SIZE..nonce_offset];\n        let nonce = &encrypted_page[nonce_offset..];\n\n        // it's important to use the header on disk (with Turso magic bytes) as associated data\n        // for protection against tampering the header\n        let header = &encrypted_page[..DatabaseHeader::SIZE];\n        let decrypted_data = self.decrypt_raw_with_ad(payload, nonce, header)?;\n\n        let metadata_size = self.cipher_mode.metadata_size();\n        assert_eq!(\n            decrypted_data.len(),\n            self.page_size - metadata_size - DatabaseHeader::SIZE,\n            \"Decrypted page data must be exactly {} bytes\",\n            self.page_size - metadata_size - DatabaseHeader::SIZE\n        );\n\n        // reconstruct the page with the appropriate SQLite header\n        let mut result = Vec::with_capacity(self.page_size);\n        result.extend_from_slice(SQLITE_HEADER);\n        result.extend_from_slice(&encrypted_page[TURSO_HEADER_SIZE..DatabaseHeader::SIZE]);\n        result.extend_from_slice(&decrypted_data);\n        result.resize(self.page_size, 0);\n\n        assert_eq!(\n            result.len(),\n            self.page_size,\n            \"Decrypted page data must be exactly {} bytes\",\n            self.page_size\n        );\n        Ok(result)\n    }\n\n    /// encrypts raw data using the configured cipher, returns ciphertext and nonce\n    fn encrypt_raw(&self, plaintext: &[u8]) -> Result<(Vec<u8>, Vec<u8>)> {\n        const AD: &[u8] = b\"\";\n        self.encrypt_raw_with_ad(plaintext, AD)\n    }\n\n    /// encrypts raw data with associated data using the configured cipher\n    fn encrypt_raw_with_ad(&self, plaintext: &[u8], ad: &[u8]) -> Result<(Vec<u8>, Vec<u8>)> {\n        macro_rules! encrypt_cipher {\n            ($cipher:expr) => {{\n                let (ciphertext, nonce) = $cipher.encrypt(plaintext, ad)?;\n                Ok((ciphertext, nonce.to_vec()))\n            }};\n        }\n\n        match &self.cipher {\n            Cipher::Aes128Gcm(cipher) => encrypt_cipher!(cipher),\n            Cipher::Aes256Gcm(cipher) => encrypt_cipher!(cipher),\n            Cipher::Aegis256(cipher) => encrypt_cipher!(cipher),\n            Cipher::Aegis256X2(cipher) => encrypt_cipher!(cipher),\n            Cipher::Aegis256X4(cipher) => encrypt_cipher!(cipher),\n            Cipher::Aegis128L(cipher) => encrypt_cipher!(cipher),\n            Cipher::Aegis128X2(cipher) => encrypt_cipher!(cipher),\n            Cipher::Aegis128X4(cipher) => encrypt_cipher!(cipher),\n        }\n    }\n\n    fn decrypt_raw(&self, ciphertext: &[u8], nonce: &[u8]) -> Result<Vec<u8>> {\n        const AD: &[u8] = b\"\";\n        self.decrypt_raw_with_ad(ciphertext, nonce, AD)\n    }\n\n    fn decrypt_raw_with_ad(&self, ciphertext: &[u8], nonce: &[u8], ad: &[u8]) -> Result<Vec<u8>> {\n        macro_rules! decrypt_with_nonce {\n            ($cipher:expr, $nonce_size:literal, $name:literal) => {{\n                let nonce_array: [u8; $nonce_size] = nonce.try_into().map_err(|_| {\n                    LimboError::InternalError(format!(\n                        \"Invalid nonce size for {}: expected {}, got {}\",\n                        $name,\n                        $nonce_size,\n                        nonce.len()\n                    ))\n                })?;\n                $cipher.decrypt(ciphertext, &nonce_array, ad)\n            }};\n        }\n\n        match &self.cipher {\n            Cipher::Aes128Gcm(cipher) => decrypt_with_nonce!(cipher, 12, \"AES-128-GCM\"),\n            Cipher::Aes256Gcm(cipher) => decrypt_with_nonce!(cipher, 12, \"AES-256-GCM\"),\n            Cipher::Aegis256(cipher) => decrypt_with_nonce!(cipher, 32, \"AEGIS-256\"),\n            Cipher::Aegis256X2(cipher) => decrypt_with_nonce!(cipher, 32, \"AEGIS-256X2\"),\n            Cipher::Aegis256X4(cipher) => decrypt_with_nonce!(cipher, 32, \"AEGIS-256X4\"),\n            Cipher::Aegis128L(cipher) => decrypt_with_nonce!(cipher, 16, \"AEGIS-128L\"),\n            Cipher::Aegis128X2(cipher) => decrypt_with_nonce!(cipher, 16, \"AEGIS-128X2\"),\n            Cipher::Aegis128X4(cipher) => decrypt_with_nonce!(cipher, 16, \"AEGIS-128X4\"),\n        }\n    }\n\n    #[cfg(not(feature = \"encryption\"))]\n    pub fn encrypt_page(&self, _page: &[u8], _page_id: usize) -> Result<Vec<u8>> {\n        Err(LimboError::InvalidArgument(\n            \"encryption is not enabled, cannot encrypt page. enable via passing `--features encryption`\".into(),\n        ))\n    }\n\n    #[cfg(not(feature = \"encryption\"))]\n    pub fn decrypt_page(&self, _encrypted_page: &[u8], _page_id: usize) -> Result<Vec<u8>> {\n        Err(LimboError::InvalidArgument(\n            \"encryption is not enabled, cannot decrypt page. enable via passing `--features encryption`\".into(),\n        ))\n    }\n}\n\nfn generate_secure_nonce<const N: usize>() -> [u8; N] {\n    // use OsRng directly to fill bytes, generic over nonce size\n    use aes_gcm::aead::rand_core::RngCore;\n    let mut nonce = [0u8; N];\n    OsRng.fill_bytes(&mut nonce);\n    nonce\n}\n\n// Helper functions for consistent error messages\nenum CipherError {\n    InvalidKeySize {\n        cipher: &'static str,\n        expected: usize,\n    },\n    InvalidTagSize {\n        cipher: &'static str,\n    },\n    DecryptionFailed {\n        cipher: &'static str,\n    },\n    CiphertextTooShort {\n        cipher: &'static str,\n    },\n}\n\nimpl From<CipherError> for LimboError {\n    fn from(err: CipherError) -> Self {\n        let msg = match err {\n            CipherError::InvalidKeySize { cipher, expected } => {\n                format!(\"{cipher} requires {expected}-byte key\")\n            }\n            CipherError::InvalidTagSize { cipher } => format!(\"Invalid tag size for {cipher}\"),\n            CipherError::DecryptionFailed { cipher } => {\n                format!(\"{cipher} decryption failed: invalid tag\")\n            }\n            CipherError::CiphertextTooShort { cipher } => {\n                format!(\"Ciphertext too short for {cipher}\")\n            }\n        };\n        LimboError::InternalError(msg)\n    }\n}\n\n#[cfg(test)]\n#[cfg(feature = \"encryption\")]\nmod tests {\n    use crate::storage::sqlite3_ondisk::DatabaseHeader;\n\n    use super::*;\n    use rand::Rng;\n    const DEFAULT_ENCRYPTED_PAGE_SIZE: usize = 4096;\n\n    macro_rules! test_cipher_wrapper {\n        ($test_name:ident, $cipher_type:ty, $key_gen:expr, $nonce_size:literal, $message:literal) => {\n            #[test]\n            fn $test_name() {\n                let key = EncryptionKey::from_hex_string(&$key_gen()).unwrap();\n                let cipher = <$cipher_type>::new(&key);\n\n                let plaintext = $message.as_bytes();\n                let ad = b\"additional data\";\n\n                let (ciphertext, nonce) = cipher.encrypt(plaintext, ad).unwrap();\n                assert_eq!(nonce.len(), $nonce_size);\n                assert_ne!(ciphertext[..plaintext.len()], plaintext[..]);\n\n                let decrypted = cipher.decrypt(&ciphertext, &nonce, ad).unwrap();\n                assert_eq!(decrypted, plaintext);\n            }\n        };\n    }\n\n    macro_rules! test_aes_cipher_wrapper {\n        ($test_name:ident, $cipher_type:ty, $key_gen:expr, $nonce_size:literal, $message:literal) => {\n            #[test]\n            fn $test_name() {\n                let key = EncryptionKey::from_hex_string(&$key_gen()).unwrap();\n                let cipher = <$cipher_type>::new(&key).unwrap();\n\n                let plaintext = $message.as_bytes();\n                let ad = b\"additional data\";\n\n                let (ciphertext, nonce) = cipher.encrypt(plaintext, ad).unwrap();\n                assert_eq!(nonce.len(), $nonce_size);\n                assert_ne!(ciphertext[..plaintext.len()], plaintext[..]);\n\n                let decrypted = cipher.decrypt(&ciphertext, &nonce, ad).unwrap();\n                assert_eq!(decrypted, plaintext);\n            }\n        };\n    }\n\n    macro_rules! test_raw_encryption {\n        ($test_name:ident, $cipher_mode:expr, $key_gen:expr, $nonce_size:literal, $message:literal) => {\n            #[test]\n            fn $test_name() {\n                let key = EncryptionKey::from_hex_string(&$key_gen()).unwrap();\n                let ctx = EncryptionContext::new($cipher_mode, &key, DEFAULT_ENCRYPTED_PAGE_SIZE)\n                    .unwrap();\n\n                let plaintext = $message.as_bytes();\n                let (ciphertext, nonce) = ctx.encrypt_raw(plaintext).unwrap();\n\n                assert_eq!(nonce.len(), $nonce_size);\n                assert_ne!(ciphertext[..plaintext.len()], plaintext[..]);\n\n                let decrypted = ctx.decrypt_raw(&ciphertext, &nonce).unwrap();\n                assert_eq!(decrypted, plaintext);\n            }\n        };\n    }\n\n    fn generate_random_hex_key() -> String {\n        let mut rng = rand::rng();\n        let mut bytes = [0u8; 32];\n        rng.fill(&mut bytes);\n        hex::encode(bytes)\n    }\n\n    fn generate_random_hex_key_128() -> String {\n        let mut rng = rand::rng();\n        let mut bytes = [0u8; 16];\n        rng.fill(&mut bytes);\n        hex::encode(bytes)\n    }\n\n    fn create_test_page_1() -> Vec<u8> {\n        let mut page = vec![0u8; DEFAULT_ENCRYPTED_PAGE_SIZE];\n        page[..SQLITE_HEADER.len()].copy_from_slice(SQLITE_HEADER);\n        let mut rng = rand::rng();\n        // 48 is the max reserved bytes we might need for metadata with any cipher\n        rng.fill(&mut page[SQLITE_HEADER.len()..DEFAULT_ENCRYPTED_PAGE_SIZE - 48]);\n        page\n    }\n\n    test_aes_cipher_wrapper!(\n        test_aes128gcm_cipher_wrapper,\n        Aes128GcmCipher,\n        generate_random_hex_key_128,\n        12,\n        \"Hello, AES-128-GCM!\"\n    );\n\n    test_raw_encryption!(\n        test_aes128gcm_raw_encryption,\n        CipherMode::Aes128Gcm,\n        generate_random_hex_key_128,\n        12,\n        \"Hello, AES-128-GCM!\"\n    );\n\n    #[test]\n    fn test_page_1_encrypt_decrypt_round_trip_with_ad() {\n        let key = EncryptionKey::from_hex_string(&generate_random_hex_key()).unwrap();\n        let ctx = EncryptionContext::new(CipherMode::Aegis256, &key, DEFAULT_ENCRYPTED_PAGE_SIZE)\n            .unwrap();\n\n        let page_data = create_test_page_1();\n        let encrypted = ctx.encrypt_page(&page_data, 1).unwrap();\n        assert_ne!(\n            &page_data[0..DatabaseHeader::SIZE],\n            &encrypted[0..DatabaseHeader::SIZE],\n            \"Encrypted data should be different from the page data\"\n        );\n        assert_eq!(encrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE);\n\n        // check that header is readable directly from disk (not encrypted)\n        assert_eq!(&encrypted[..5], b\"Turso\");\n        assert_eq!(encrypted[5], TURSO_VERSION);\n        assert_eq!(encrypted[6], CipherMode::Aegis256.cipher_id());\n\n        // header should be unencrypted, but data after DatabaseHeader::SIZE should be different\n        assert_eq!(&encrypted[16..100], &page_data[16..100]); // header portion\n        assert_ne!(&encrypted[100..200], &page_data[100..200]); // some encrypted portion\n\n        // decrypt page 1\n        let decrypted = ctx.decrypt_page(&encrypted, 1).unwrap();\n        assert_eq!(decrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE);\n\n        // check that SQLite header was restored\n        assert_eq!(&decrypted[..SQLITE_HEADER.len()], SQLITE_HEADER);\n        assert_eq!(decrypted, page_data);\n    }\n\n    #[test]\n    fn test_turso_header_validation() {\n        let key = EncryptionKey::from_hex_string(&generate_random_hex_key()).unwrap();\n        let ctx = EncryptionContext::new(CipherMode::Aegis256, &key, DEFAULT_ENCRYPTED_PAGE_SIZE)\n            .unwrap();\n\n        // test cipher_id conversion\n        assert_eq!(CipherMode::Aes128Gcm.cipher_id(), 1);\n        assert_eq!(CipherMode::Aes256Gcm.cipher_id(), 2);\n        assert_eq!(CipherMode::Aegis256.cipher_id(), 3);\n        assert_eq!(CipherMode::Aegis128L.cipher_id(), 6);\n\n        // test from_cipher_id conversion\n        assert_eq!(\n            CipherMode::from_cipher_id(1).unwrap(),\n            CipherMode::Aes128Gcm\n        );\n        assert_eq!(CipherMode::from_cipher_id(3).unwrap(), CipherMode::Aegis256);\n        assert!(CipherMode::from_cipher_id(99).is_err());\n\n        // test header creation\n        let header = ctx.create_turso_header();\n        assert_eq!(&header[..5], b\"Turso\");\n        assert_eq!(header[5], TURSO_VERSION);\n        assert_eq!(header[6], 3); // AEGIS-256\n        assert_eq!(&header[7..], &[0u8; 9]); // unused bytes are zero\n    }\n\n    #[test]\n    fn test_invalid_turso_header_fails_decrypt() {\n        let key = EncryptionKey::from_hex_string(&generate_random_hex_key()).unwrap();\n        let ctx = EncryptionContext::new(CipherMode::Aegis256, &key, DEFAULT_ENCRYPTED_PAGE_SIZE)\n            .unwrap();\n\n        let page_data = create_test_page_1();\n        let encrypted = ctx.encrypt_page(&page_data, 1).unwrap();\n\n        // corrupt the header prefix\n        let mut corrupted = encrypted.clone();\n        corrupted[0] = b'V'; // make `Turso` to `Vurso`\n        assert!(ctx.decrypt_page(&corrupted, 1).is_err());\n\n        // test with wrong cipher ID\n        let mut wrong_cipher = encrypted;\n        wrong_cipher[6] = 99; // invalid cipher ID\n        assert!(ctx.decrypt_page(&wrong_cipher, 1).is_err());\n    }\n\n    #[test]\n    fn test_associated_data_validation() {\n        let key = EncryptionKey::from_hex_string(&generate_random_hex_key()).unwrap();\n        let ctx = EncryptionContext::new(CipherMode::Aegis256, &key, DEFAULT_ENCRYPTED_PAGE_SIZE)\n            .unwrap();\n\n        let page_data = create_test_page_1();\n        let encrypted = ctx.encrypt_page(&page_data, 1).unwrap();\n\n        // modify a byte in the preserved header portion (bytes 16-100)\n        let mut corrupted_ad = encrypted;\n        corrupted_ad[50] ^= 1; // flip one bit in the associated data portion\n\n        // this should fail decryption because associated data doesn't match\n        let decrypt_result = ctx.decrypt_page(&corrupted_ad, 1);\n        assert!(\n            decrypt_result.is_err(),\n            \"Decryption should fail with corrupted associated data\"\n        );\n    }\n\n    #[test]\n    fn test_turso_header_corruption_detection() {\n        let key = EncryptionKey::from_hex_string(&generate_random_hex_key()).unwrap();\n        let ctx = EncryptionContext::new(CipherMode::Aegis256, &key, DEFAULT_ENCRYPTED_PAGE_SIZE)\n            .unwrap();\n\n        let page_data = create_test_page_1();\n        let encrypted = ctx.encrypt_page(&page_data, 1).unwrap();\n\n        let mut corrupted_turso_header = encrypted;\n        corrupted_turso_header[7] ^= 1;\n\n        let decrypt_result = ctx.decrypt_page(&corrupted_turso_header, 1);\n        assert!(\n            decrypt_result.is_err(),\n            \"Decryption should fail with corrupted Turso header\"\n        );\n    }\n\n    #[test]\n    fn test_aes128gcm_encrypt_decrypt_round_trip() {\n        let mut rng = rand::rng();\n        let cipher_mode = CipherMode::Aes128Gcm;\n        let metadata_size = cipher_mode.metadata_size();\n        let data_size = DEFAULT_ENCRYPTED_PAGE_SIZE - metadata_size;\n\n        let page_data = {\n            let mut page = vec![0u8; DEFAULT_ENCRYPTED_PAGE_SIZE];\n            page.iter_mut()\n                .take(data_size)\n                .for_each(|byte| *byte = rng.random());\n            page\n        };\n\n        let key = EncryptionKey::from_hex_string(&generate_random_hex_key_128()).unwrap();\n        let ctx = EncryptionContext::new(CipherMode::Aes128Gcm, &key, DEFAULT_ENCRYPTED_PAGE_SIZE)\n            .unwrap();\n\n        let page_id = 42;\n        let encrypted = ctx.encrypt_page(&page_data, page_id).unwrap();\n        assert_eq!(encrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE);\n        assert_ne!(&encrypted[..data_size], &page_data[..data_size]);\n        assert_ne!(&encrypted[..], &page_data[..]);\n\n        let decrypted = ctx.decrypt_page(&encrypted, page_id).unwrap();\n        assert_eq!(decrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE);\n        assert_eq!(decrypted, page_data);\n    }\n\n    #[test]\n    fn test_aes_encrypt_decrypt_round_trip() {\n        let mut rng = rand::rng();\n        let cipher_mode = CipherMode::Aes256Gcm;\n        let metadata_size = cipher_mode.metadata_size();\n        let data_size = DEFAULT_ENCRYPTED_PAGE_SIZE - metadata_size;\n\n        let page_data = {\n            let mut page = vec![0u8; DEFAULT_ENCRYPTED_PAGE_SIZE];\n            page.iter_mut()\n                .take(data_size)\n                .for_each(|byte| *byte = rng.random());\n            page\n        };\n\n        let key = EncryptionKey::from_hex_string(&generate_random_hex_key()).unwrap();\n        let ctx = EncryptionContext::new(CipherMode::Aes256Gcm, &key, DEFAULT_ENCRYPTED_PAGE_SIZE)\n            .unwrap();\n\n        let page_id = 42;\n        let encrypted = ctx.encrypt_page(&page_data, page_id).unwrap();\n        assert_eq!(encrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE);\n        assert_ne!(&encrypted[..data_size], &page_data[..data_size]);\n        assert_ne!(&encrypted[..], &page_data[..]);\n\n        let decrypted = ctx.decrypt_page(&encrypted, page_id).unwrap();\n        assert_eq!(decrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE);\n        assert_eq!(decrypted, page_data);\n    }\n\n    test_cipher_wrapper!(\n        test_aegis256_cipher_wrapper,\n        Aegis256Cipher,\n        generate_random_hex_key,\n        32,\n        \"Hello, AEGIS-256!\"\n    );\n\n    test_raw_encryption!(\n        test_aegis256_raw_encryption,\n        CipherMode::Aegis256,\n        generate_random_hex_key,\n        32,\n        \"Hello, AEGIS-256!\"\n    );\n\n    #[test]\n    fn test_aegis256_encrypt_decrypt_round_trip() {\n        let mut rng = rand::rng();\n        let cipher_mode = CipherMode::Aegis256;\n        let metadata_size = cipher_mode.metadata_size();\n        let data_size = DEFAULT_ENCRYPTED_PAGE_SIZE - metadata_size;\n\n        let page_data = {\n            let mut page = vec![0u8; DEFAULT_ENCRYPTED_PAGE_SIZE];\n            page.iter_mut()\n                .take(data_size)\n                .for_each(|byte| *byte = rng.random());\n            page\n        };\n\n        let key = EncryptionKey::from_hex_string(&generate_random_hex_key()).unwrap();\n        let ctx = EncryptionContext::new(CipherMode::Aegis256, &key, DEFAULT_ENCRYPTED_PAGE_SIZE)\n            .unwrap();\n\n        let page_id = 42;\n        let encrypted = ctx.encrypt_page(&page_data, page_id).unwrap();\n        assert_eq!(encrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE);\n        assert_ne!(&encrypted[..data_size], &page_data[..data_size]);\n\n        let decrypted = ctx.decrypt_page(&encrypted, page_id).unwrap();\n        assert_eq!(decrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE);\n        assert_eq!(decrypted, page_data);\n    }\n\n    test_cipher_wrapper!(\n        test_aegis128x2_cipher_wrapper,\n        Aegis128X2Cipher,\n        generate_random_hex_key_128,\n        16,\n        \"Hello, AEGIS-128X2!\"\n    );\n\n    test_raw_encryption!(\n        test_aegis128x2_raw_encryption,\n        CipherMode::Aegis128X2,\n        generate_random_hex_key_128,\n        16,\n        \"Hello, AEGIS-128X2!\"\n    );\n\n    #[test]\n    fn test_aegis128x2_encrypt_decrypt_round_trip() {\n        let mut rng = rand::rng();\n        let cipher_mode = CipherMode::Aegis128X2;\n        let metadata_size = cipher_mode.metadata_size();\n        let data_size = DEFAULT_ENCRYPTED_PAGE_SIZE - metadata_size;\n\n        let page_data = {\n            let mut page = vec![0u8; DEFAULT_ENCRYPTED_PAGE_SIZE];\n            page.iter_mut()\n                .take(data_size)\n                .for_each(|byte| *byte = rng.random());\n            page\n        };\n\n        let key = EncryptionKey::from_hex_string(&generate_random_hex_key_128()).unwrap();\n        let ctx = EncryptionContext::new(CipherMode::Aegis128X2, &key, DEFAULT_ENCRYPTED_PAGE_SIZE)\n            .unwrap();\n\n        let page_id = 42;\n        let encrypted = ctx.encrypt_page(&page_data, page_id).unwrap();\n        assert_eq!(encrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE);\n        assert_ne!(&encrypted[..data_size], &page_data[..data_size]);\n\n        let decrypted = ctx.decrypt_page(&encrypted, page_id).unwrap();\n        assert_eq!(decrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE);\n        assert_eq!(decrypted, page_data);\n    }\n\n    test_cipher_wrapper!(\n        test_aegis128l_cipher_wrapper,\n        Aegis128LCipher,\n        generate_random_hex_key_128,\n        16,\n        \"Hello, AEGIS-128L!\"\n    );\n\n    test_raw_encryption!(\n        test_aegis128l_raw_encryption,\n        CipherMode::Aegis128L,\n        generate_random_hex_key_128,\n        16,\n        \"Hello, AEGIS-128L!\"\n    );\n\n    #[test]\n    fn test_aegis128l_encrypt_decrypt_round_trip() {\n        let mut rng = rand::rng();\n        let cipher_mode = CipherMode::Aegis128L;\n        let metadata_size = cipher_mode.metadata_size();\n        let data_size = DEFAULT_ENCRYPTED_PAGE_SIZE - metadata_size;\n\n        let page_data = {\n            let mut page = vec![0u8; DEFAULT_ENCRYPTED_PAGE_SIZE];\n            page.iter_mut()\n                .take(data_size)\n                .for_each(|byte| *byte = rng.random());\n            page\n        };\n\n        let key = EncryptionKey::from_hex_string(&generate_random_hex_key_128()).unwrap();\n        let ctx = EncryptionContext::new(CipherMode::Aegis128L, &key, DEFAULT_ENCRYPTED_PAGE_SIZE)\n            .unwrap();\n\n        let page_id = 42;\n        let encrypted = ctx.encrypt_page(&page_data, page_id).unwrap();\n        assert_eq!(encrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE);\n        assert_ne!(&encrypted[..data_size], &page_data[..data_size]);\n\n        let decrypted = ctx.decrypt_page(&encrypted, page_id).unwrap();\n        assert_eq!(decrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE);\n        assert_eq!(decrypted, page_data);\n    }\n\n    test_cipher_wrapper!(\n        test_aegis128x4_cipher_wrapper,\n        Aegis128X4Cipher,\n        generate_random_hex_key_128,\n        16,\n        \"Hello, AEGIS-128X4!\"\n    );\n\n    test_raw_encryption!(\n        test_aegis128x4_raw_encryption,\n        CipherMode::Aegis128X4,\n        generate_random_hex_key_128,\n        16,\n        \"Hello, AEGIS-128X4!\"\n    );\n\n    #[test]\n    fn test_aegis128x4_encrypt_decrypt_round_trip() {\n        let mut rng = rand::rng();\n        let cipher_mode = CipherMode::Aegis128X4;\n        let metadata_size = cipher_mode.metadata_size();\n        let data_size = DEFAULT_ENCRYPTED_PAGE_SIZE - metadata_size;\n\n        let page_data = {\n            let mut page = vec![0u8; DEFAULT_ENCRYPTED_PAGE_SIZE];\n            page.iter_mut()\n                .take(data_size)\n                .for_each(|byte| *byte = rng.random());\n            page\n        };\n\n        let key = EncryptionKey::from_hex_string(&generate_random_hex_key_128()).unwrap();\n        let ctx = EncryptionContext::new(CipherMode::Aegis128X4, &key, DEFAULT_ENCRYPTED_PAGE_SIZE)\n            .unwrap();\n\n        let page_id = 42;\n        let encrypted = ctx.encrypt_page(&page_data, page_id).unwrap();\n        assert_eq!(encrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE);\n        assert_ne!(&encrypted[..data_size], &page_data[..data_size]);\n\n        let decrypted = ctx.decrypt_page(&encrypted, page_id).unwrap();\n        assert_eq!(decrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE);\n        assert_eq!(decrypted, page_data);\n    }\n\n    test_cipher_wrapper!(\n        test_aegis256x2_cipher_wrapper,\n        Aegis256X2Cipher,\n        generate_random_hex_key,\n        32,\n        \"Hello, AEGIS-256X2!\"\n    );\n\n    test_raw_encryption!(\n        test_aegis256x2_raw_encryption,\n        CipherMode::Aegis256X2,\n        generate_random_hex_key,\n        32,\n        \"Hello, AEGIS-256X2!\"\n    );\n\n    #[test]\n    fn test_aegis256x2_encrypt_decrypt_round_trip() {\n        let mut rng = rand::rng();\n        let cipher_mode = CipherMode::Aegis256X2;\n        let metadata_size = cipher_mode.metadata_size();\n        let data_size = DEFAULT_ENCRYPTED_PAGE_SIZE - metadata_size;\n\n        let page_data = {\n            let mut page = vec![0u8; DEFAULT_ENCRYPTED_PAGE_SIZE];\n            page.iter_mut()\n                .take(data_size)\n                .for_each(|byte| *byte = rng.random());\n            page\n        };\n\n        let key = EncryptionKey::from_hex_string(&generate_random_hex_key()).unwrap();\n        let ctx = EncryptionContext::new(CipherMode::Aegis256X2, &key, DEFAULT_ENCRYPTED_PAGE_SIZE)\n            .unwrap();\n\n        let page_id = 42;\n        let encrypted = ctx.encrypt_page(&page_data, page_id).unwrap();\n        assert_eq!(encrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE);\n        assert_ne!(&encrypted[..data_size], &page_data[..data_size]);\n\n        let decrypted = ctx.decrypt_page(&encrypted, page_id).unwrap();\n        assert_eq!(decrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE);\n        assert_eq!(decrypted, page_data);\n    }\n\n    test_cipher_wrapper!(\n        test_aegis256x4_cipher_wrapper,\n        Aegis256X4Cipher,\n        generate_random_hex_key,\n        32,\n        \"Hello, AEGIS-256X4!\"\n    );\n\n    test_raw_encryption!(\n        test_aegis256x4_raw_encryption,\n        CipherMode::Aegis256X4,\n        generate_random_hex_key,\n        32,\n        \"Hello, AEGIS-256X4!\"\n    );\n\n    #[test]\n    fn test_aegis256x4_encrypt_decrypt_round_trip() {\n        let mut rng = rand::rng();\n        let cipher_mode = CipherMode::Aegis256X4;\n        let metadata_size = cipher_mode.metadata_size();\n        let data_size = DEFAULT_ENCRYPTED_PAGE_SIZE - metadata_size;\n\n        let page_data = {\n            let mut page = vec![0u8; DEFAULT_ENCRYPTED_PAGE_SIZE];\n            page.iter_mut()\n                .take(data_size)\n                .for_each(|byte| *byte = rng.random());\n            page\n        };\n\n        let key = EncryptionKey::from_hex_string(&generate_random_hex_key()).unwrap();\n        let ctx = EncryptionContext::new(CipherMode::Aegis256X4, &key, DEFAULT_ENCRYPTED_PAGE_SIZE)\n            .unwrap();\n\n        let page_id = 42;\n        let encrypted = ctx.encrypt_page(&page_data, page_id).unwrap();\n        assert_eq!(encrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE);\n        assert_ne!(&encrypted[..data_size], &page_data[..data_size]);\n\n        let decrypted = ctx.decrypt_page(&encrypted, page_id).unwrap();\n        assert_eq!(decrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE);\n        assert_eq!(decrypted, page_data);\n    }\n\n    #[test]\n    fn test_cipher_mode_string_parsing() {\n        // Test AES-128-GCM\n        let mode = CipherMode::try_from(\"aes128gcm\").unwrap();\n        assert_eq!(mode, CipherMode::Aes128Gcm);\n        assert_eq!(mode.to_string(), \"aes128gcm\");\n        assert_eq!(mode.required_key_size(), 16);\n        assert_eq!(mode.nonce_size(), 12);\n        assert_eq!(mode.tag_size(), 16);\n\n        let mode = CipherMode::try_from(\"aes-128-gcm\").unwrap();\n        assert_eq!(mode, CipherMode::Aes128Gcm);\n\n        let mode = CipherMode::try_from(\"aes_128_gcm\").unwrap();\n        assert_eq!(mode, CipherMode::Aes128Gcm);\n\n        // Test AES-256-GCM\n        let mode = CipherMode::try_from(\"aes256gcm\").unwrap();\n        assert_eq!(mode, CipherMode::Aes256Gcm);\n        assert_eq!(mode.to_string(), \"aes256gcm\");\n        assert_eq!(mode.required_key_size(), 32);\n        assert_eq!(mode.nonce_size(), 12);\n\n        // Test that all AEGIS variants can be parsed from strings\n        let mode = CipherMode::try_from(\"aegis128x2\").unwrap();\n        assert_eq!(mode, CipherMode::Aegis128X2);\n        assert_eq!(mode.to_string(), \"aegis128x2\");\n        assert_eq!(mode.required_key_size(), 16);\n        assert_eq!(mode.nonce_size(), 16);\n        assert_eq!(mode.tag_size(), 16);\n\n        let mode = CipherMode::try_from(\"aegis-128x2\").unwrap();\n        assert_eq!(mode, CipherMode::Aegis128X2);\n\n        let mode = CipherMode::try_from(\"aegis_128x2\").unwrap();\n        assert_eq!(mode, CipherMode::Aegis128X2);\n\n        // Test AEGIS-128L\n        let mode = CipherMode::try_from(\"aegis128l\").unwrap();\n        assert_eq!(mode, CipherMode::Aegis128L);\n        assert_eq!(mode.to_string(), \"aegis128l\");\n        assert_eq!(mode.required_key_size(), 16);\n        assert_eq!(mode.nonce_size(), 16);\n\n        // Test AEGIS-128X4\n        let mode = CipherMode::try_from(\"aegis128x4\").unwrap();\n        assert_eq!(mode, CipherMode::Aegis128X4);\n        assert_eq!(mode.to_string(), \"aegis128x4\");\n        assert_eq!(mode.required_key_size(), 16);\n        assert_eq!(mode.nonce_size(), 16);\n\n        // Test AEGIS-256X2\n        let mode = CipherMode::try_from(\"aegis256x2\").unwrap();\n        assert_eq!(mode, CipherMode::Aegis256X2);\n        assert_eq!(mode.to_string(), \"aegis256x2\");\n        assert_eq!(mode.required_key_size(), 32);\n        assert_eq!(mode.nonce_size(), 32);\n\n        // Test AEGIS-256X4\n        let mode = CipherMode::try_from(\"aegis256x4\").unwrap();\n        assert_eq!(mode, CipherMode::Aegis256X4);\n        assert_eq!(mode.to_string(), \"aegis256x4\");\n        assert_eq!(mode.required_key_size(), 32);\n        assert_eq!(mode.nonce_size(), 32);\n    }\n}\n"
  },
  {
    "path": "core/storage/journal_mode.rs",
    "content": "use crate::sync::Arc;\n\nuse crate::storage::sqlite3_ondisk::Version;\nuse crate::{mvcc, MvStore, OpenFlags, Result, IO};\n\n#[derive(\n    Debug,\n    Clone,\n    Copy,\n    PartialEq,\n    Eq,\n    PartialOrd,\n    Ord,\n    Hash,\n    strum_macros::EnumString,\n    strum_macros::Display,\n    strum_macros::IntoStaticStr,\n)]\n#[strum(ascii_case_insensitive, serialize_all = \"snake_case\")]\npub enum JournalMode {\n    Delete,\n    Truncate,\n    Persist,\n    Memory,\n    Wal,\n    #[strum(to_string = \"mvcc\", serialize = \"experimental_mvcc\")]\n    Mvcc,\n    Off,\n}\n\nimpl JournalMode {\n    /// Modes that are supported\n    #[inline]\n    pub fn supported(&self) -> bool {\n        matches!(self, JournalMode::Wal | JournalMode::Mvcc)\n    }\n\n    /// As the header file version\n    #[inline]\n    pub fn as_version(&self) -> Option<Version> {\n        match self {\n            JournalMode::Wal => Some(Version::Wal),\n            JournalMode::Mvcc => Some(Version::Mvcc),\n            _ => None,\n        }\n    }\n}\n\nimpl From<Version> for JournalMode {\n    fn from(value: Version) -> Self {\n        match value {\n            Version::Legacy => Self::Delete,\n            Version::Wal => Self::Wal,\n            Version::Mvcc => Self::Mvcc,\n        }\n    }\n}\n\npub fn logical_log_exists(db_path: impl AsRef<std::path::Path>) -> bool {\n    let db_path = db_path.as_ref();\n    let log_path = db_path.with_extension(\"db-log\");\n    std::path::Path::exists(log_path.as_path()) && log_path.as_path().metadata().unwrap().len() > 0\n}\n\npub fn open_mv_store(\n    io: Arc<dyn IO>,\n    db_path: impl AsRef<std::path::Path>,\n    flags: OpenFlags,\n    durable_storage: Option<Arc<dyn mvcc::persistent_storage::DurableStorage>>,\n    encryption_ctx: Option<crate::storage::encryption::EncryptionContext>,\n) -> Result<Arc<MvStore>> {\n    let storage: Arc<dyn mvcc::persistent_storage::DurableStorage> =\n        if let Some(storage) = durable_storage {\n            storage\n        } else {\n            let db_path = db_path.as_ref();\n            let log_path = db_path.with_extension(\"db-log\");\n            let string_path = log_path\n                .as_os_str()\n                .to_str()\n                .expect(\"path should be valid string\");\n            let file = io.open_file(string_path, flags, false)?;\n            Arc::new(mvcc::persistent_storage::Storage::new(\n                file,\n                io,\n                encryption_ctx,\n            ))\n        };\n\n    let mv_store = MvStore::new(mvcc::MvccClock::new(), storage);\n    let mv_store = Arc::new(mv_store);\n    Ok(mv_store)\n}\n"
  },
  {
    "path": "core/storage/mod.rs",
    "content": "//! The storage layer.\n//!\n//! This module contains the storage layer for Limbo. The storage layer is\n//! responsible for managing access to the database and its pages. The main\n//! interface to the storage layer is the `Pager` struct, which is\n//! responsible for managing the database file and the pages it contains.\n//!\n//! Pages in a database are stored in one of the following to data structures:\n//! `DatabaseStorage` or `Wal`. The `DatabaseStorage` trait is responsible\n//! for reading and writing pages to the database file, either local or\n//! remote. The `Wal` struct is responsible for managing the write-ahead log\n//! for the database, also either local or remote.\npub(crate) mod btree;\npub(crate) mod buffer_pool;\npub(crate) mod checksum;\npub mod database;\npub(crate) mod encryption;\npub(crate) mod journal_mode;\npub(crate) mod page_cache;\n#[allow(clippy::arc_with_non_send_sync)]\npub(crate) mod pager;\n#[allow(dead_code)]\npub(super) mod slot_bitmap;\npub mod sqlite3_ondisk;\nmod state_machines;\npub(crate) mod subjournal;\n#[allow(clippy::arc_with_non_send_sync)]\npub(crate) mod wal;\n\n#[macro_export]\nmacro_rules! return_corrupt {\n    ($($arg:tt)*) => {\n        return Err(LimboError::Corrupt(format!($($arg)*)));\n    };\n}\n"
  },
  {
    "path": "core/storage/page_cache.rs",
    "content": "use crate::sync::{atomic::Ordering, Arc};\nuse intrusive_collections::{intrusive_adapter, LinkedList, LinkedListLink};\nuse rustc_hash::FxHashMap as HashMap;\nuse tracing::trace;\n\nuse crate::{\n    storage::{btree::PinGuard, sqlite3_ondisk::DatabaseHeader},\n    turso_assert,\n};\n\nuse super::pager::PageRef;\n\n#[cfg(not(target_family = \"wasm\"))]\nconst DEFAULT_PAGE_CACHE_SIZE_IN_PAGES: usize = 2000;\n#[cfg(target_family = \"wasm\")]\nconst DEFAULT_PAGE_CACHE_SIZE_IN_PAGES: usize = 100000;\n\n/// Minimum safe cache size in pages.\n/// This accounts for:\n/// - Btree cursor stack (up to BTCURSOR_MAX_DEPTH = 20 pages)\n/// - Balance operations (MAX_SIBLING_PAGES_TO_BALANCE = 5 new pages)\n/// - State machine pages (freelist operations, header refs, etc.)\n/// - Some buffer for concurrent operations\npub const MINIMUM_PAGE_CACHE_SIZE_IN_PAGES: usize = 200;\n\n/// The spill threshold as a fraction of capacity.\nconst DEFAULT_SPILL_THRESHOLD_PERCENT: usize = 90;\n\n#[derive(Debug, Copy, Eq, Hash, PartialEq, Clone)]\n#[repr(transparent)]\npub struct PageCacheKey(usize);\n\nconst CLEAR: u8 = 0;\nconst REF_MAX: u8 = 3;\n\n/// An entry in the page cache.\n///\n/// The entry is stored in the intrusive linked list in PageCache::list`.\nstruct PageCacheEntry {\n    /// Key identifying this page\n    key: PageCacheKey,\n    /// The cached page\n    page: PageRef,\n    /// Reference counter (SIEVE/GClock): starts at zero, bumped on access,\n    /// decremented during eviction, only pages at 0 are evicted.\n    ref_bit: u8,\n    /// Intrusive link for SIEVE queue\n    link: LinkedListLink,\n}\n\nintrusive_adapter!(EntryAdapter = Box<PageCacheEntry>: PageCacheEntry { link: LinkedListLink });\n\nimpl PageCacheEntry {\n    fn new(key: PageCacheKey, page: PageRef) -> Box<Self> {\n        Box::new(Self {\n            key,\n            page,\n            ref_bit: CLEAR,\n            link: LinkedListLink::new(),\n        })\n    }\n\n    #[inline]\n    fn bump_ref(&mut self) {\n        self.ref_bit = std::cmp::min(self.ref_bit + 1, REF_MAX);\n    }\n\n    #[inline]\n    /// Returns the old value\n    fn decrement_ref(&mut self) -> u8 {\n        let old = self.ref_bit;\n        self.ref_bit = old.saturating_sub(1);\n        old\n    }\n}\n\n/// Result returned when attempting to spill dirty pages from the cache.\n#[derive(Debug)]\npub enum SpillResult {\n    /// No spilling was needed (cache is below threshold)\n    NotNeeded,\n    /// Spilling is needed but disabled\n    Disabled,\n    /// Successfully collected dirty pages to spill\n    PagesToSpill(Vec<PinGuard>),\n    /// Cache is at capacity with only unevictable pages\n    CacheFull,\n}\n\n/// PageCache implements a variation of the SIEVE algorithm that maintains an intrusive linked list queue of\n/// pages which keep a 'reference_bit' to determine how recently/frequently the page has been accessed.\n/// The bit is set to `Clear` on initial insertion and then bumped on each access and decremented\n/// during eviction scans.\n///\n/// The ring is circular. `clock_hand` points at the tail (LRU).\n/// Sweep order follows next: tail (LRU) -> head (MRU) -> .. -> tail\n/// New pages are inserted after the clock hand in the `next` direction,\n/// which places them at head (MRU) (i.e. `tail.next` is the head).\npub struct PageCache {\n    /// Capacity in pages\n    capacity: usize,\n    /// Map of Key -> pointer to entry in the queue\n    map: HashMap<PageCacheKey, *mut PageCacheEntry>,\n    /// The eviction queue (intrusive doubly-linked list)\n    queue: LinkedList<EntryAdapter>,\n    /// Clock hand cursor for SIEVE eviction (pointer to an entry in the queue, or null)\n    clock_hand: *mut PageCacheEntry,\n    /// Threshold number of pages at which we start spilling dirty pages.\n    spill_threshold: usize,\n    spill_enabled: bool,\n    /// Conservative estimation of pages that are evictable based on dirty/spilled state.\n    evictable_count: usize,\n}\n\nunsafe impl Send for PageCache {}\nunsafe impl Sync for PageCache {}\ncrate::assert::assert_send_sync!(PageCache);\n\n#[derive(Debug, Clone, PartialEq, thiserror::Error)]\npub enum CacheError {\n    #[error(\"{0}\")]\n    InternalError(String),\n    #[error(\"page {pgno} is locked\")]\n    Locked { pgno: usize },\n    #[error(\"page {pgno} is dirty\")]\n    Dirty { pgno: usize },\n    #[error(\"page {pgno} is pinned\")]\n    Pinned { pgno: usize },\n    #[error(\"cache active refs\")]\n    ActiveRefs,\n    #[error(\"Page cache is full\")]\n    Full,\n    #[error(\"key already exists\")]\n    KeyExists,\n}\n\n#[derive(Debug, PartialEq)]\npub enum CacheResizeResult {\n    Done,\n    PendingEvictions,\n}\n\nimpl PageCacheKey {\n    pub fn new(pgno: usize) -> Self {\n        Self(pgno)\n    }\n}\n\nimpl PageCache {\n    #[cfg(not(target_family = \"wasm\"))]\n    pub fn new(capacity: usize) -> Self {\n        Self::new_with_spill(capacity, true)\n    }\n    #[cfg(target_family = \"wasm\")]\n    pub fn new(capacity: usize) -> Self {\n        Self::new_with_spill(capacity, false)\n    }\n\n    /// Create a new PageCache with explicit spill control.\n    pub fn new_with_spill(capacity: usize, spill_enabled: bool) -> Self {\n        let spill_threshold = (capacity * DEFAULT_SPILL_THRESHOLD_PERCENT) / 100;\n        Self {\n            capacity,\n            map: HashMap::default(),\n            queue: LinkedList::new(EntryAdapter::new()),\n            clock_hand: std::ptr::null_mut(),\n            spill_threshold: spill_threshold.max(1),\n            spill_enabled,\n            evictable_count: 0,\n        }\n    }\n\n    /// Advances the clock hand to the next entry in the circular queue.\n    /// Follows the \"next\" direction: from tail/LRU through the list back to tail.\n    /// With our insertion-after-hand strategy, this moves through entries in age order.\n    fn advance_clock_hand(&mut self) {\n        if self.clock_hand.is_null() {\n            return;\n        }\n\n        unsafe {\n            let mut cursor = self.queue.cursor_mut_from_ptr(self.clock_hand);\n            cursor.move_next();\n\n            if cursor.get().is_some() {\n                self.clock_hand =\n                    cursor.as_cursor().get().unwrap() as *const _ as *mut PageCacheEntry;\n            } else {\n                // Reached end, wrap to front\n                let front_cursor = self.queue.front_mut();\n                if front_cursor.get().is_some() {\n                    self.clock_hand =\n                        front_cursor.as_cursor().get().unwrap() as *const _ as *mut PageCacheEntry;\n                } else {\n                    self.clock_hand = std::ptr::null_mut();\n                }\n            }\n        }\n    }\n\n    pub fn contains_key(&self, key: &PageCacheKey) -> bool {\n        self.map.contains_key(key)\n    }\n\n    #[inline]\n    pub fn insert(&mut self, key: PageCacheKey, value: PageRef) -> Result<(), CacheError> {\n        self._insert(key, value, false)\n    }\n\n    #[inline]\n    pub fn upsert_page(&mut self, key: PageCacheKey, value: PageRef) -> Result<(), CacheError> {\n        self._insert(key, value, true)\n    }\n\n    pub fn _insert(\n        &mut self,\n        key: PageCacheKey,\n        value: PageRef,\n        update_in_place: bool,\n    ) -> Result<(), CacheError> {\n        trace!(\"insert(key={:?})\", key);\n\n        if let Some(&entry_ptr) = self.map.get(&key) {\n            let entry = unsafe { &mut *entry_ptr };\n            let p = &entry.page;\n\n            if !p.is_loaded() && !p.is_locked() {\n                // evict, then continue with fresh insert\n                self._delete(key, true)?;\n                // Proceed to insert new entry\n            } else {\n                entry.bump_ref();\n                if update_in_place {\n                    // Track evictable count change if page state differs\n                    let old_evictable = Self::counted_as_evictable(&entry.page);\n                    let new_evictable = Self::counted_as_evictable(&value);\n                    if old_evictable && !new_evictable {\n                        self.evictable_count = self.evictable_count.saturating_sub(1);\n                    } else if !old_evictable && new_evictable {\n                        self.evictable_count += 1;\n                    }\n                    entry.page = value;\n                    return Ok(());\n                } else {\n                    turso_assert!(\n                        Arc::ptr_eq(&entry.page, &value),\n                        \"Attempted to insert different page with same key: {key:?}\"\n                    );\n                    return Err(CacheError::KeyExists);\n                }\n            }\n        }\n\n        // Key doesn't exist, proceed with new entry\n        self.make_room_for(1)?;\n\n        // Track evictable count for the new page\n        let is_evictable = Self::counted_as_evictable(&value);\n\n        let entry = PageCacheEntry::new(key, value);\n\n        if self.clock_hand.is_null() {\n            // First entry - just push it\n            self.queue.push_back(entry);\n            let entry_ptr = self.queue.back().get().unwrap() as *const _ as *mut PageCacheEntry;\n            self.map.insert(key, entry_ptr);\n            self.clock_hand = entry_ptr;\n        } else {\n            // Insert after clock hand (in circular list semantics, this makes it the new head/MRU)\n            unsafe {\n                let mut cursor = self.queue.cursor_mut_from_ptr(self.clock_hand);\n                cursor.insert_after(entry);\n                // The inserted entry is now at the next position after clock hand\n                cursor.move_next();\n                let entry_ptr = cursor.get().ok_or_else(|| {\n                    CacheError::InternalError(\"Failed to get inserted entry pointer\".into())\n                })? as *const PageCacheEntry as *mut PageCacheEntry;\n                self.map.insert(key, entry_ptr);\n            }\n        }\n\n        // Update evictable count after successful insertion\n        if is_evictable {\n            self.evictable_count += 1;\n        }\n\n        Ok(())\n    }\n\n    fn _delete(&mut self, key: PageCacheKey, clean_page: bool) -> Result<(), CacheError> {\n        let Some(&entry_ptr) = self.map.get(&key) else {\n            return Ok(());\n        };\n\n        let entry = unsafe { &mut *entry_ptr };\n        let page = &entry.page;\n\n        if page.is_locked() {\n            return Err(CacheError::Locked {\n                pgno: page.get().id,\n            });\n        }\n        if page.is_dirty() {\n            return Err(CacheError::Dirty {\n                pgno: page.get().id,\n            });\n        }\n        if page.is_pinned() {\n            return Err(CacheError::Pinned {\n                pgno: page.get().id,\n            });\n        }\n\n        // Track evictable count before removing\n        let was_evictable = Self::counted_as_evictable(page);\n\n        if clean_page {\n            page.clear_loaded();\n            let _ = page.get().buffer.take();\n        }\n\n        // Remove from map first\n        self.map.remove(&key);\n\n        // If clock hand points to this entry, advance it before removing\n        if self.clock_hand == entry_ptr {\n            self.advance_clock_hand();\n            // If hand is still pointing to the same entry after advance, we're removing the last entry\n            if self.clock_hand == entry_ptr {\n                self.clock_hand = std::ptr::null_mut();\n            }\n        }\n\n        // Remove the entry from the queue\n        unsafe {\n            let mut cursor = self.queue.cursor_mut_from_ptr(entry_ptr);\n            cursor.remove();\n        }\n\n        // Update evictable count after successful removal\n        if was_evictable {\n            self.evictable_count = self.evictable_count.saturating_sub(1);\n        }\n\n        Ok(())\n    }\n\n    #[inline]\n    /// Deletes a page from the cache\n    pub fn delete(&mut self, key: PageCacheKey) -> Result<(), CacheError> {\n        trace!(\"cache_delete(key={:?})\", key);\n        self._delete(key, true)\n    }\n\n    #[inline]\n    pub fn get(&mut self, key: &PageCacheKey) -> crate::Result<Option<PageRef>> {\n        let Some(&entry_ptr) = self.map.get(key) else {\n            return Ok(None);\n        };\n\n        let entry = unsafe { &mut *entry_ptr };\n        let page = entry.page.clone();\n\n        // Because we can abort a read_page completion, this means a page can be in the cache but be unloaded and unlocked.\n        // However, if we do not evict that page from the page cache, we will return an unloaded page later which will trigger\n        // assertions later on. This is worsened by the fact that page cache is not per `Statement`, so you can abort a completion\n        // in one Statement, and trigger some error in the next one if we don't evict the page here.\n        if !page.is_loaded() && !page.is_locked() {\n            self.delete(*key)?;\n            return Ok(None);\n        }\n\n        entry.bump_ref();\n        Ok(Some(page))\n    }\n\n    #[inline]\n    pub fn peek(&mut self, key: &PageCacheKey, touch: bool) -> Option<PageRef> {\n        let &entry_ptr = self.map.get(key)?;\n        let entry = unsafe { &mut *entry_ptr };\n        let page = entry.page.clone();\n        if touch {\n            entry.bump_ref();\n        }\n        Some(page)\n    }\n\n    /// Resizes the cache to a new capacity.\n    /// If shrinking, attempts to evict pages. if growing, increases capacity.\n    pub fn resize(&mut self, new_cap: usize) -> CacheResizeResult {\n        if new_cap == self.capacity {\n            return CacheResizeResult::Done;\n        }\n        // Evict entries one by one until we're at new capacity\n        while new_cap < self.len() {\n            if self.evict_one().is_err() {\n                return CacheResizeResult::PendingEvictions;\n            }\n        }\n        self.capacity = new_cap;\n        self.spill_threshold = ((new_cap * DEFAULT_SPILL_THRESHOLD_PERCENT) / 100).max(1);\n        CacheResizeResult::Done\n    }\n\n    /// Returns true if the cache is at or above the spill threshold and spilling is enabled.\n    /// This indicates that dirty pages should be flushed to make room for new pages.\n    #[inline]\n    pub fn needs_spill(&self) -> bool {\n        let len = self.len();\n\n        if len < self.spill_threshold || !self.spill_enabled {\n            return false;\n        }\n\n        let needed_evictable = len.saturating_sub(self.spill_threshold);\n\n        // Fast path: use tracked evictable_count to avoid O(n) scan:\n        // evictable_count is a conservative upper bound on evictable pages,\n        // Empty slots also count as available room since make_room_for uses them first.\n        // Calculate if we have enough room (evictable + empty slots) and won't need to spill.\n        let empty_slots = self.capacity.saturating_sub(len);\n        let available_room = self.evictable_count.saturating_add(empty_slots);\n        if available_room >= needed_evictable {\n            return false;\n        }\n\n        // Slow path: do the full count since our estimate suggests we might need to spill.\n        // The actual count may be lower than evictable_count due to locked/pinned pages.\n        self.count_evictable_pages() < needed_evictable\n    }\n\n    #[inline]\n    /// Count pages that can be evicted without spilling.\n    fn count_evictable_pages(&self) -> usize {\n        self.map\n            .values()\n            .filter(|&&entry_ptr| {\n                let entry = unsafe { &*entry_ptr };\n                Self::evictable(&entry.page)\n            })\n            .count()\n    }\n\n    /// Check if spilling is enabled for this cache.\n    #[inline]\n    pub fn is_spill_enabled(&self) -> bool {\n        self.spill_enabled\n    }\n\n    /// Enable or disable spilling for this cache.\n    pub fn set_spill_enabled(&mut self, enabled: bool) {\n        self.spill_enabled = enabled;\n    }\n\n    /// Get the current spill threshold (number of pages).\n    #[inline]\n    pub fn spill_threshold(&self) -> usize {\n        self.spill_threshold\n    }\n\n    /// Set a custom spill threshold (number of pages).\n    /// The threshold will be clamped to be at least 1 and at most capacity.\n    pub fn set_spill_threshold(&mut self, threshold: usize) {\n        self.spill_threshold = threshold.clamp(1, self.capacity);\n    }\n\n    #[inline]\n    fn spillable(page: &PageRef) -> bool {\n        page.is_dirty()\n            && !page.is_spilled()\n            && !page.is_locked()\n            && !page.is_pinned()\n            && Arc::strong_count(page) == 1\n            && page.get().id.ne(&DatabaseHeader::PAGE_ID)\n            && page.get().overflow_cells.is_empty()\n    }\n\n    #[inline]\n    /// Check if a page should be counted as evictable for tracking purposes.\n    /// This is a conservative check that ignores locked/pinned/strong_count state\n    /// since those are typically short-lived. We track based on dirty/spilled state.\n    fn counted_as_evictable(page: &PageRef) -> bool {\n        // Page 1 is never evictable\n        if page.get().id == DatabaseHeader::PAGE_ID {\n            return false;\n        }\n        // A page is evictable if it's clean OR spilled\n        !page.is_dirty() || page.is_spilled()\n    }\n\n    /// Notify the cache that a page has become dirty.\n    /// This should be called when a page transitions from clean/spilled to dirty.\n    /// The page must already be in the cache.\n    pub fn notify_page_dirty(&mut self, key: PageCacheKey) {\n        if let Some(&entry_ptr) = self.map.get(&key) {\n            let entry = unsafe { &*entry_ptr };\n            let page = &entry.page;\n            // Page was evictable (clean or spilled) before becoming dirty,\n            // now it's dirty && !spilled, so not evictable\n            if page.get().id != DatabaseHeader::PAGE_ID {\n                // Only decrement if we were counting it as evictable\n                // (it was clean or spilled before this call)\n                self.evictable_count = self.evictable_count.saturating_sub(1);\n            }\n        }\n    }\n\n    /// Notify the cache that a page has been spilled.\n    /// This should be called when a page transitions from dirty to spilled.\n    /// The page must already be in the cache.\n    pub fn notify_page_spilled(&mut self, key: PageCacheKey) {\n        if let Some(&entry_ptr) = self.map.get(&key) {\n            let entry = unsafe { &*entry_ptr };\n            let page = &entry.page;\n            // Page was dirty && !spilled (not evictable), now it's spilled (evictable)\n            if page.get().id != DatabaseHeader::PAGE_ID {\n                self.evictable_count += 1;\n            }\n        }\n    }\n\n    /// Get the current evictable page count (for diagnostics/testing).\n    #[cfg(test)]\n    pub fn evictable_count(&self) -> usize {\n        self.evictable_count\n    }\n\n    /// Collect dirty pages that can be spilled to make room in the cache.\n    /// Pages that are locked or pinned are skipped.\n    pub fn collect_spillable_pages(&self, max_pages: usize) -> Vec<PinGuard> {\n        if !self.spill_enabled || max_pages == 0 {\n            return Vec::new();\n        }\n        const EST_SPILL: usize = 128;\n        let mut spillable: Vec<PinGuard> = Vec::with_capacity(EST_SPILL);\n\n        for (_, &entry_ptr) in self.map.iter() {\n            let entry = unsafe { &*entry_ptr };\n            let page = &entry.page;\n            if Self::spillable(page) {\n                spillable.push(PinGuard::new(page.clone()));\n            }\n            if spillable.len() >= max_pages {\n                break;\n            }\n        }\n        spillable.sort_by_key(|pg| pg.get().id);\n        spillable\n    }\n\n    /// Returns the number of dirty pages currently in the cache.\n    pub fn dirty_count(&self) -> usize {\n        self.map\n            .values()\n            .filter(|&&entry_ptr| {\n                let entry = unsafe { &*entry_ptr };\n                entry.page.is_dirty()\n            })\n            .count()\n    }\n\n    /// Check if the cache needs spilling and return appropriate result.\n    /// This is the main entry point for the spilling check during insertion.\n    pub fn check_spill(&self, max_pages: usize) -> SpillResult {\n        if !self.needs_spill() {\n            return SpillResult::NotNeeded;\n        }\n\n        let pages = self.collect_spillable_pages(max_pages);\n        if pages.is_empty() {\n            SpillResult::CacheFull\n        } else {\n            SpillResult::PagesToSpill(pages)\n        }\n    }\n\n    /// Ensures at least `n` free slots are available\n    ///\n    /// Uses the SIEVE algorithm to evict pages if necessary:\n    /// Start at clock hand position\n    /// If page ref_bit > 0, decrement and continue\n    /// If page ref_bit == 0 and evictable, evict it\n    /// If page is unevictable (dirty/locked/pinned), continue sweep\n    /// On sweep, pages with ref_bit > 0 are given a second chance by decrementing\n    /// their ref_bit and leaving them in place; only pages with ref_bit == 0 are evicted.\n    ///\n    /// Returns `CacheError::Full` if not enough pages can be evicted\n    pub fn make_room_for(&mut self, n: usize) -> Result<(), CacheError> {\n        if n > self.capacity {\n            return Err(CacheError::Full);\n        }\n        let available = self.capacity - self.len();\n        if n <= available {\n            return Ok(());\n        }\n\n        let need = n - available;\n        for _ in 0..need {\n            self.evict_one()?;\n        }\n        Ok(())\n    }\n\n    #[inline]\n    fn evictable(page: &PageRef) -> bool {\n        (!page.is_dirty() || page.is_spilled())\n            && !page.is_locked()\n            && !page.is_pinned()\n            && page.get().id.ne(&DatabaseHeader::PAGE_ID)\n            && Arc::strong_count(page) == 1\n    }\n\n    /// Evicts a single page using the SIEVE algorithm\n    fn evict_one(&mut self) -> Result<(), CacheError> {\n        if self.len() == 0 {\n            return Err(CacheError::InternalError(\n                \"Cannot evict from empty cache\".into(),\n            ));\n        }\n\n        let mut examined = 0usize;\n        let max_examinations = self.len().saturating_mul(REF_MAX as usize + 1);\n\n        while examined < max_examinations {\n            // Clock hand should never be null here since we checked len() > 0\n            turso_assert!(\n                !self.clock_hand.is_null(),\n                \"page_cache: clock hand is null during eviction\",\n                { \"entries\": self.len() }\n            );\n\n            let entry_ptr = self.clock_hand;\n            let entry = unsafe { &mut *entry_ptr };\n            let key = entry.key;\n            let page = &entry.page;\n\n            let evictable = Self::evictable(page);\n\n            if evictable && entry.ref_bit == CLEAR {\n                turso_assert!(\n                    Self::counted_as_evictable(page),\n                    \"mismatched evictable count state\"\n                );\n\n                // Evict this entry\n                self.advance_clock_hand();\n                // Check if clock hand wrapped back to the same entry (meaning this is the only/last entry)\n                if self.clock_hand == entry_ptr {\n                    self.clock_hand = std::ptr::null_mut();\n                }\n\n                self.map.remove(&key);\n                // Clean the page\n                page.clear_loaded();\n                let _ = page.get().buffer.take();\n\n                // Remove from queue\n                unsafe {\n                    let mut cursor = self.queue.cursor_mut_from_ptr(entry_ptr);\n                    cursor.remove();\n                }\n\n                // Update evictable count after successful eviction\n                self.evictable_count = self.evictable_count.saturating_sub(1);\n\n                return Ok(());\n            } else if evictable {\n                // Decrement ref bit and continue\n                entry.decrement_ref();\n                self.advance_clock_hand();\n                examined += 1;\n            } else {\n                // Skip unevictable page\n                self.advance_clock_hand();\n                examined += 1;\n            }\n        }\n\n        Err(CacheError::Full)\n    }\n\n    pub fn clear(&mut self, clear_dirty: bool) -> Result<(), CacheError> {\n        // Check all pages are clean\n        for &entry_ptr in self.map.values() {\n            let entry = unsafe { &*entry_ptr };\n            if entry.page.is_dirty() && !clear_dirty {\n                return Err(CacheError::Dirty {\n                    pgno: entry.page.get().id,\n                });\n            }\n        }\n\n        // Clean all pages\n        for &entry_ptr in self.map.values() {\n            let entry = unsafe { &*entry_ptr };\n            entry.page.clear_loaded();\n            let _ = entry.page.get().buffer.take();\n        }\n\n        self.map.clear();\n        self.queue.clear();\n        self.clock_hand = std::ptr::null_mut();\n        self.evictable_count = 0;\n        Ok(())\n    }\n\n    /// Removes all pages from the cache with pgno greater than max_page_num\n    pub fn truncate(&mut self, max_page_num: usize) -> Result<(), CacheError> {\n        for key in self\n            .map\n            .keys()\n            .filter(|k| k.0 > max_page_num)\n            .copied()\n            .collect::<Vec<_>>()\n        {\n            self.delete(key)?;\n        }\n        Ok(())\n    }\n\n    pub fn print(&self) {\n        tracing::debug!(\"page_cache_len={}\", self.map.len());\n\n        let mut cursor = self.queue.front();\n        let mut i = 0;\n        while let Some(entry) = cursor.get() {\n            let page = &entry.page;\n            tracing::debug!(\n                \"slot={}, page={:?}, flags={}, pin_count={}, ref_bit={:?}\",\n                i,\n                entry.key,\n                page.get().flags.load(Ordering::SeqCst),\n                page.get().pin_count.load(Ordering::SeqCst),\n                entry.ref_bit,\n            );\n            cursor.move_next();\n            i += 1;\n        }\n    }\n\n    #[cfg(test)]\n    pub fn keys(&mut self) -> Vec<PageCacheKey> {\n        self.map.keys().copied().collect()\n    }\n\n    pub fn len(&self) -> usize {\n        self.map.len()\n    }\n\n    pub fn capacity(&self) -> usize {\n        self.capacity\n    }\n\n    #[cfg(test)]\n    fn verify_cache_integrity(&self) {\n        use rustc_hash::FxHashSet as HashSet;\n\n        let map_len = self.map.len();\n\n        // Count entries in queue\n        let mut queue_len = 0;\n        let mut cursor = self.queue.front();\n        let mut seen_keys = HashSet::default();\n\n        while let Some(entry) = cursor.get() {\n            queue_len += 1;\n            seen_keys.insert(entry.key);\n            cursor.move_next();\n        }\n\n        assert_eq!(map_len, queue_len, \"map and queue length mismatch\");\n        assert_eq!(map_len, seen_keys.len(), \"duplicate keys in queue\");\n\n        // Verify all map entries are in queue\n        for &key in self.map.keys() {\n            assert!(seen_keys.contains(&key), \"map key not in queue\");\n        }\n\n        // Verify clock hand\n        if !self.clock_hand.is_null() {\n            assert!(map_len > 0, \"clock hand set but map is empty\");\n            let hand_key = unsafe { (*self.clock_hand).key };\n            assert!(\n                self.map.contains_key(&hand_key),\n                \"clock hand points to non-existent entry\"\n            );\n        } else {\n            assert_eq!(map_len, 0, \"clock hand null but map not empty\");\n        }\n    }\n\n    #[cfg(test)]\n    fn ref_of(&self, key: &PageCacheKey) -> Option<u8> {\n        self.map.get(key).map(|&ptr| unsafe { (*ptr).ref_bit })\n    }\n}\n\nimpl Default for PageCache {\n    fn default() -> Self {\n        PageCache::new(DEFAULT_PAGE_CACHE_SIZE_IN_PAGES)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::storage::page_cache::CacheError;\n    use crate::storage::pager::{Page, PageRef};\n    use crate::sync::Arc;\n    use rand_chacha::{\n        rand_core::{RngCore, SeedableRng},\n        ChaCha8Rng,\n    };\n\n    fn create_key(id: usize) -> PageCacheKey {\n        PageCacheKey::new(id)\n    }\n\n    pub fn page_with_content(page_id: usize) -> PageRef {\n        let page = Arc::new(Page::new(page_id as i64));\n        {\n            let inner = page.get();\n            inner.buffer = Some(Arc::new(crate::Buffer::new_temporary(4096)));\n        }\n        page.set_loaded();\n        page\n    }\n\n    fn insert_page(cache: &mut PageCache, id: usize) -> PageCacheKey {\n        let key = create_key(id);\n        let page = page_with_content(id);\n        cache\n            .insert(key, page)\n            .unwrap_or_else(|e| panic!(\"Failed to insert page {id}: {e:?}\"));\n        key\n    }\n\n    #[test]\n    fn test_delete_only_element() {\n        let mut cache = PageCache::default();\n        let key1 = insert_page(&mut cache, 1);\n        cache.verify_cache_integrity();\n        assert_eq!(cache.len(), 1);\n\n        assert!(cache.delete(key1).is_ok());\n\n        assert_eq!(\n            cache.len(),\n            0,\n            \"Length should be 0 after deleting only element\"\n        );\n        assert!(\n            !cache.contains_key(&key1),\n            \"Cache should not contain key after delete\"\n        );\n        cache.verify_cache_integrity();\n    }\n\n    #[test]\n    fn test_detach_tail() {\n        let mut cache = PageCache::default();\n        let key1 = insert_page(&mut cache, 1); // tail\n        let _key2 = insert_page(&mut cache, 2); // middle\n        let _key3 = insert_page(&mut cache, 3); // head\n        cache.verify_cache_integrity();\n        assert_eq!(cache.len(), 3);\n\n        // Delete tail\n        assert!(cache.delete(key1).is_ok());\n        assert_eq!(cache.len(), 2, \"Length should be 2 after deleting tail\");\n        assert!(\n            !cache.contains_key(&key1),\n            \"Cache should not contain deleted tail key\"\n        );\n        cache.verify_cache_integrity();\n    }\n\n    #[test]\n    fn test_insert_existing_key_updates_in_place() {\n        let mut cache = PageCache::default();\n        let key1 = create_key(1);\n        let page1_v1 = page_with_content(1);\n        let page1_v2 = page1_v1.clone(); // Same Arc instance\n\n        assert!(cache.insert(key1, page1_v1).is_ok());\n        assert_eq!(cache.len(), 1);\n\n        // Inserting same page instance should return KeyExists error\n        let result = cache.insert(key1, page1_v2);\n        assert_eq!(result, Err(CacheError::KeyExists));\n        assert_eq!(cache.len(), 1);\n\n        // Verify the page is still accessible\n        assert!(cache.get(&key1).unwrap().is_some());\n        cache.verify_cache_integrity();\n    }\n\n    #[test]\n    #[should_panic(expected = \"Attempted to insert different page with same key\")]\n    fn test_insert_different_page_same_key_panics() {\n        let mut cache = PageCache::default();\n        let key1 = create_key(1);\n        let page1_v1 = page_with_content(1);\n        let page1_v2 = page_with_content(1); // Different Arc instance\n\n        assert!(cache.insert(key1, page1_v1).is_ok());\n        assert_eq!(cache.len(), 1);\n        cache.verify_cache_integrity();\n\n        // This should panic because it's a different page instance\n        let _ = cache.insert(key1, page1_v2);\n    }\n\n    #[test]\n    fn test_delete_nonexistent_key() {\n        let mut cache = PageCache::default();\n        let key_nonexist = create_key(99);\n\n        // Deleting non-existent key should be a no-op (returns Ok)\n        assert!(cache.delete(key_nonexist).is_ok());\n        assert_eq!(cache.len(), 0);\n        cache.verify_cache_integrity();\n    }\n\n    #[test]\n    fn test_page_cache_evict() {\n        // Note: page 1 is DatabaseHeader and is never evictable, so use page ids >= 2\n        let mut cache = PageCache::new_with_spill(1, true);\n        let key2 = insert_page(&mut cache, 2);\n        let key3 = insert_page(&mut cache, 3);\n\n        // With capacity=1, inserting key3 should evict key2\n        assert_eq!(cache.get(&key3).unwrap().unwrap().get().id, 3);\n        assert!(\n            cache.get(&key2).unwrap().is_none(),\n            \"key2 should be evicted\"\n        );\n\n        // key3 should still be accessible\n        assert_eq!(cache.get(&key3).unwrap().unwrap().get().id, 3);\n        assert!(\n            cache.get(&key2).unwrap().is_none(),\n            \"capacity=1 should have evicted the older page\"\n        );\n        cache.verify_cache_integrity();\n    }\n\n    #[test]\n    fn test_sieve_touch_non_tail_does_not_affect_immediate_eviction() {\n        // SIEVE algorithm: touching a non-tail page marks it but doesn't move it.\n        // The tail (if unmarked) will still be the first eviction candidate.\n        // Note: page 1 is DatabaseHeader and is never evictable, so use page ids >= 2\n\n        // Insert 2,3,4 -> order [4,3,2] with tail=2\n        let mut cache = PageCache::new_with_spill(3, true);\n        let key2 = insert_page(&mut cache, 2);\n        let key3 = insert_page(&mut cache, 3);\n        let key4 = insert_page(&mut cache, 4);\n\n        // Touch key3 (middle) to mark it with reference bit\n        assert!(cache.get(&key3).unwrap().is_some());\n\n        // Insert 5: SIEVE examines tail (key2, unmarked) -> evict key2\n        let key5 = insert_page(&mut cache, 5);\n\n        assert!(\n            cache.get(&key3).unwrap().is_some(),\n            \"marked non-tail (key3) should remain\"\n        );\n        assert!(cache.get(&key4).unwrap().is_some(), \"key4 should remain\");\n        assert!(\n            cache.get(&key5).unwrap().is_some(),\n            \"key5 was just inserted\"\n        );\n        assert!(\n            cache.get(&key2).unwrap().is_none(),\n            \"unmarked tail (key2) should be evicted first\"\n        );\n        cache.verify_cache_integrity();\n    }\n\n    #[test]\n    fn clock_second_chance_decrements_tail_then_evicts_next() {\n        let mut cache = PageCache::new_with_spill(3, true);\n        let key1 = insert_page(&mut cache, 1);\n        let key2 = insert_page(&mut cache, 2);\n        let key3 = insert_page(&mut cache, 3);\n        assert_eq!(cache.len(), 3);\n        assert!(cache.get(&key1).unwrap().is_some());\n        let key4 = insert_page(&mut cache, 4);\n        assert!(cache.get(&key1).unwrap().is_some(), \"key1 should survive\");\n        assert!(cache.get(&key2).unwrap().is_some(), \"key2 remains\");\n        assert!(cache.get(&key4).unwrap().is_some(), \"key4 inserted\");\n        assert!(\n            cache.get(&key3).unwrap().is_none(),\n            \"key3 (next after tail) evicted\"\n        );\n        assert_eq!(cache.len(), 3);\n        cache.verify_cache_integrity();\n    }\n\n    #[test]\n    fn test_delete_locked_page() {\n        let mut cache = PageCache::default();\n        let key = insert_page(&mut cache, 1);\n        let page = cache.get(&key).unwrap().unwrap();\n        page.set_locked();\n\n        assert_eq!(cache.delete(key), Err(CacheError::Locked { pgno: 1 }));\n        assert_eq!(cache.len(), 1, \"Locked page should not be deleted\");\n        cache.verify_cache_integrity();\n    }\n\n    #[test]\n    fn test_delete_dirty_page() {\n        let mut cache = PageCache::default();\n        let key = insert_page(&mut cache, 1);\n        let page = cache.get(&key).unwrap().unwrap();\n        page.set_dirty();\n\n        assert_eq!(cache.delete(key), Err(CacheError::Dirty { pgno: 1 }));\n        assert_eq!(cache.len(), 1, \"Dirty page should not be deleted\");\n        cache.verify_cache_integrity();\n    }\n\n    #[test]\n    fn test_delete_pinned_page() {\n        let mut cache = PageCache::default();\n        let key = insert_page(&mut cache, 1);\n        let page = cache.get(&key).unwrap().unwrap();\n        page.pin();\n\n        assert_eq!(cache.delete(key), Err(CacheError::Pinned { pgno: 1 }));\n        assert_eq!(cache.len(), 1, \"Pinned page should not be deleted\");\n        cache.verify_cache_integrity();\n    }\n\n    #[test]\n    fn test_make_room_for_with_dirty_pages() {\n        let mut cache = PageCache::new_with_spill(2, true);\n        let key1 = insert_page(&mut cache, 1);\n        let key2 = insert_page(&mut cache, 2);\n\n        // Make both pages dirty (unevictable)\n        cache.get(&key1).unwrap().unwrap().set_dirty();\n        cache.get(&key2).unwrap().unwrap().set_dirty();\n\n        // Try to insert a third page, should fail because can't evict dirty pages\n        let key3 = create_key(3);\n        let page3 = page_with_content(3);\n        let result = cache.insert(key3, page3);\n\n        assert_eq!(result, Err(CacheError::Full));\n        assert_eq!(cache.len(), 2);\n        cache.verify_cache_integrity();\n    }\n\n    #[test]\n    fn test_page_cache_insert_and_get() {\n        let mut cache = PageCache::default();\n        let key1 = insert_page(&mut cache, 1);\n        let key2 = insert_page(&mut cache, 2);\n\n        assert_eq!(cache.get(&key1).unwrap().unwrap().get().id, 1);\n        assert_eq!(cache.get(&key2).unwrap().unwrap().get().id, 2);\n        cache.verify_cache_integrity();\n    }\n\n    #[test]\n    fn test_page_cache_over_capacity() {\n        // Test SIEVE eviction when exceeding capacity\n        // Note: page 1 is DatabaseHeader and is never evictable, so use page ids >= 2\n        let mut cache = PageCache::new_with_spill(2, true);\n        let key2 = insert_page(&mut cache, 2);\n        let key3 = insert_page(&mut cache, 3);\n\n        // Insert 4: tail (key2, unmarked) should be evicted\n        let key4 = insert_page(&mut cache, 4);\n\n        assert_eq!(cache.len(), 2);\n        assert!(cache.get(&key3).unwrap().is_some(), \"key3 should remain\");\n        assert!(cache.get(&key4).unwrap().is_some(), \"key4 just inserted\");\n        assert!(\n            cache.get(&key2).unwrap().is_none(),\n            \"key2 (oldest, unmarked) should be evicted\"\n        );\n        cache.verify_cache_integrity();\n    }\n\n    #[test]\n    fn test_page_cache_delete() {\n        let mut cache = PageCache::default();\n        let key1 = insert_page(&mut cache, 1);\n\n        assert!(cache.delete(key1).is_ok());\n        assert!(cache.get(&key1).unwrap().is_none());\n        assert_eq!(cache.len(), 0);\n        cache.verify_cache_integrity();\n    }\n\n    #[test]\n    fn test_page_cache_clear() {\n        let mut cache = PageCache::default();\n        let key1 = insert_page(&mut cache, 1);\n        let key2 = insert_page(&mut cache, 2);\n\n        assert!(cache.clear(false).is_ok());\n        assert!(cache.get(&key1).unwrap().is_none());\n        assert!(cache.get(&key2).unwrap().is_none());\n        assert_eq!(cache.len(), 0);\n        cache.verify_cache_integrity();\n    }\n\n    #[test]\n    fn test_resize_smaller_success() {\n        let mut cache = PageCache::default();\n        for i in 1..=5 {\n            let _ = insert_page(&mut cache, i);\n        }\n        assert_eq!(cache.len(), 5);\n\n        let result = cache.resize(3);\n        assert_eq!(result, CacheResizeResult::Done);\n        assert_eq!(cache.len(), 3);\n        assert_eq!(cache.capacity(), 3);\n\n        // Should still be able to insert after resize\n        assert!(cache.insert(create_key(6), page_with_content(6)).is_ok());\n        assert_eq!(cache.len(), 3); // One was evicted to make room\n        cache.verify_cache_integrity();\n    }\n\n    #[test]\n    fn test_detach_with_multiple_pages() {\n        let mut cache = PageCache::default();\n        let _key1 = insert_page(&mut cache, 1);\n        let key2 = insert_page(&mut cache, 2);\n        let _key3 = insert_page(&mut cache, 3);\n\n        // Delete middle element (key2)\n        assert!(cache.delete(key2).is_ok());\n\n        // Verify structure after deletion\n        assert_eq!(cache.len(), 2);\n        assert!(!cache.contains_key(&key2));\n\n        cache.verify_cache_integrity();\n    }\n\n    #[test]\n    fn test_delete_multiple_elements() {\n        let mut cache = PageCache::default();\n        let key1 = insert_page(&mut cache, 1);\n        let key2 = insert_page(&mut cache, 2);\n        let key3 = insert_page(&mut cache, 3);\n        cache.verify_cache_integrity();\n        assert_eq!(cache.len(), 3);\n\n        // Delete head (key3)\n        assert!(cache.delete(key3).is_ok());\n        assert_eq!(cache.len(), 2, \"Length should be 2 after deleting head\");\n        assert!(\n            !cache.contains_key(&key3),\n            \"Cache should not contain deleted head key\"\n        );\n        cache.verify_cache_integrity();\n\n        // Delete tail (key1)\n        assert!(cache.delete(key1).is_ok());\n        assert_eq!(cache.len(), 1, \"Length should be 1 after deleting two\");\n        cache.verify_cache_integrity();\n\n        // Delete last element (key2)\n        assert!(cache.delete(key2).is_ok());\n        assert_eq!(cache.len(), 0, \"Length should be 0 after deleting all\");\n        cache.verify_cache_integrity();\n    }\n\n    #[test]\n    fn test_resize_larger() {\n        let mut cache = PageCache::new_with_spill(2, true);\n        let key1 = insert_page(&mut cache, 1);\n        let key2 = insert_page(&mut cache, 2);\n        assert_eq!(cache.len(), 2);\n\n        let result = cache.resize(5);\n        assert_eq!(result, CacheResizeResult::Done);\n        assert_eq!(cache.len(), 2);\n        assert_eq!(cache.capacity(), 5);\n\n        // Existing pages should still be accessible\n        assert!(cache.get(&key1).is_ok_and(|p| p.is_some()));\n        assert!(cache.get(&key2).is_ok_and(|p| p.is_some()));\n\n        // Now we should be able to add 3 more without eviction\n        for i in 3..=5 {\n            let _ = insert_page(&mut cache, i);\n        }\n        assert_eq!(cache.len(), 5);\n        cache.verify_cache_integrity();\n    }\n\n    #[test]\n    fn test_resize_same_capacity() {\n        let mut cache = PageCache::new_with_spill(3, true);\n        for i in 1..=3 {\n            let _ = insert_page(&mut cache, i);\n        }\n\n        let result = cache.resize(3);\n        assert_eq!(result, CacheResizeResult::Done);\n        assert_eq!(cache.len(), 3);\n        assert_eq!(cache.capacity(), 3);\n        cache.verify_cache_integrity();\n    }\n\n    #[test]\n    fn test_truncate_page_cache() {\n        let mut cache = PageCache::new_with_spill(10, true);\n        let _ = insert_page(&mut cache, 1);\n        let _ = insert_page(&mut cache, 4);\n        let _ = insert_page(&mut cache, 8);\n        let _ = insert_page(&mut cache, 10);\n\n        // Truncate to keep only pages <= 4\n        cache.truncate(4).unwrap();\n\n        assert!(cache.contains_key(&PageCacheKey(1)));\n        assert!(cache.contains_key(&PageCacheKey(4)));\n        assert!(!cache.contains_key(&PageCacheKey(8)));\n        assert!(!cache.contains_key(&PageCacheKey(10)));\n        assert_eq!(cache.len(), 2);\n        assert_eq!(cache.capacity(), 10);\n        cache.verify_cache_integrity();\n    }\n\n    #[test]\n    fn test_truncate_page_cache_remove_all() {\n        let mut cache = PageCache::new_with_spill(10, true);\n        let _ = insert_page(&mut cache, 8);\n        let _ = insert_page(&mut cache, 10);\n\n        // Truncate to 4 (removes all pages since they're > 4)\n        cache.truncate(4).unwrap();\n\n        assert!(!cache.contains_key(&PageCacheKey(8)));\n        assert!(!cache.contains_key(&PageCacheKey(10)));\n        assert_eq!(cache.len(), 0);\n        assert_eq!(cache.capacity(), 10);\n        cache.verify_cache_integrity();\n    }\n\n    #[test]\n    fn test_page_cache_fuzz() {\n        let seed = std::time::SystemTime::now()\n            .duration_since(std::time::UNIX_EPOCH)\n            .unwrap()\n            .as_secs();\n        let mut rng = ChaCha8Rng::seed_from_u64(seed);\n        tracing::info!(\"fuzz test seed: {}\", seed);\n\n        let max_pages = 10;\n        let mut cache = PageCache::new_with_spill(10, true);\n        let mut reference_map = HashMap::default();\n\n        for _ in 0..10000 {\n            cache.print();\n\n            match rng.next_u64() % 2 {\n                0 => {\n                    // Insert operation\n                    let id_page = rng.next_u64() % max_pages;\n                    let key = PageCacheKey::new(id_page as usize);\n                    #[allow(clippy::arc_with_non_send_sync)]\n                    let page = Arc::new(Page::new(id_page as i64));\n\n                    if cache.peek(&key, false).is_some() {\n                        continue; // Skip duplicate page ids\n                    }\n\n                    tracing::debug!(\"inserting page {:?}\", key);\n                    match cache.insert(key, page.clone()) {\n                        Err(CacheError::Full | CacheError::ActiveRefs) => {} // Expected, ignore\n                        Err(err) => {\n                            panic!(\"Cache insertion failed unexpectedly: {err:?}\");\n                        }\n                        Ok(_) => {\n                            reference_map.insert(key, page);\n                            // Clean up reference_map if cache evicted something\n                            if cache.len() < reference_map.len() {\n                                reference_map.retain(|k, _| cache.contains_key(k));\n                            }\n                        }\n                    }\n                    assert!(cache.len() <= 10, \"Cache size exceeded capacity\");\n                }\n                1 => {\n                    // Delete operation\n                    let random = rng.next_u64() % 2 == 0;\n                    let key = if random || reference_map.is_empty() {\n                        let id_page: u64 = rng.next_u64() % max_pages;\n                        PageCacheKey::new(id_page as usize)\n                    } else {\n                        let i = rng.next_u64() as usize % reference_map.len();\n                        *reference_map.keys().nth(i).unwrap()\n                    };\n\n                    tracing::debug!(\"removing page {:?}\", key);\n                    reference_map.remove(&key);\n                    assert!(cache.delete(key).is_ok());\n                }\n                _ => unreachable!(),\n            }\n\n            cache.verify_cache_integrity();\n\n            // Verify all pages in reference_map are in cache\n            for (key, page) in &reference_map {\n                let cached_page = cache.peek(key, false).expect(\"Page should be in cache\");\n                assert_eq!(cached_page.get().id, key.0);\n                assert_eq!(page.get().id, key.0);\n            }\n        }\n    }\n\n    #[test]\n    fn test_peek_without_touch() {\n        // Test that peek with touch=false doesn't mark pages\n        // Note: page 1 is DatabaseHeader and is never evictable, so use page ids >= 2\n        let mut cache = PageCache::new_with_spill(2, true);\n        let key2 = insert_page(&mut cache, 2);\n        let key3 = insert_page(&mut cache, 3);\n\n        // Peek key2 without touching (no ref bit set)\n        assert!(cache.peek(&key2, false).is_some());\n\n        // Insert 4: should evict unmarked tail (key2)\n        let key4 = insert_page(&mut cache, 4);\n\n        assert!(cache.get(&key3).unwrap().is_some(), \"key3 should remain\");\n        assert!(\n            cache.get(&key4).unwrap().is_some(),\n            \"key4 was just inserted\"\n        );\n        assert!(\n            cache.get(&key2).unwrap().is_none(),\n            \"key2 should be evicted since peek(false) didn't mark it\"\n        );\n        assert_eq!(cache.len(), 2);\n        cache.verify_cache_integrity();\n    }\n\n    #[test]\n    fn test_peek_with_touch() {\n        // Test that peek with touch=true marks pages for SIEVE\n        let mut cache = PageCache::new_with_spill(2, true);\n        let key1 = insert_page(&mut cache, 1);\n        let key2 = insert_page(&mut cache, 2);\n\n        // Peek key1 WITH touching (sets ref bit)\n        assert!(cache.peek(&key1, true).is_some());\n\n        // Insert 3: key1 is marked, so it gets second chance\n        // key2 becomes new tail and gets evicted\n        let key3 = insert_page(&mut cache, 3);\n\n        assert!(\n            cache.get(&key1).unwrap().is_some(),\n            \"key1 should survive (was marked)\"\n        );\n        assert!(\n            cache.get(&key3).unwrap().is_some(),\n            \"key3 was just inserted\"\n        );\n        assert!(\n            cache.get(&key2).unwrap().is_none(),\n            \"key2 should be evicted after key1's second chance\"\n        );\n        assert_eq!(cache.len(), 2);\n        cache.verify_cache_integrity();\n    }\n\n    #[test]\n    #[ignore = \"long running test, remove ignore to verify memory stability\"]\n    fn test_clear_memory_stability() {\n        let initial_memory = memory_stats::memory_stats().unwrap().physical_mem;\n\n        for _ in 0..100000 {\n            let mut cache = PageCache::new(1000);\n\n            for i in 0..1000 {\n                let key = create_key(i);\n                let page = page_with_content(i);\n                cache.insert(key, page).unwrap();\n            }\n\n            cache.clear(false).unwrap();\n            drop(cache);\n        }\n\n        let final_memory = memory_stats::memory_stats().unwrap().physical_mem;\n        let growth = final_memory.saturating_sub(initial_memory);\n\n        println!(\"Memory growth: {growth} bytes\");\n        assert!(\n            growth < 10_000_000,\n            \"Memory grew by {growth} bytes over test cycles (limit: 10MB)\",\n        );\n    }\n\n    #[test]\n    fn clock_drains_hot_page_within_single_sweep_when_others_are_unevictable() {\n        // Note: page 1 is DatabaseHeader and is never evictable, so use page ids >= 2\n        // capacity 3: [4(head), 3, 2(tail)]\n        let mut c = PageCache::new_with_spill(3, true);\n        let k2 = insert_page(&mut c, 2);\n        let k3 = insert_page(&mut c, 3);\n        let k4 = insert_page(&mut c, 4);\n\n        // Make k2 hot: bump to Max\n        for _ in 0..3 {\n            assert!(c.get(&k2).unwrap().is_some());\n        }\n        assert!(matches!(c.ref_of(&k2), Some(REF_MAX)));\n\n        // Make other pages unevictable; clock must keep revisiting k2.\n        c.get(&k3).unwrap().unwrap().set_dirty();\n        c.get(&k4).unwrap().unwrap().set_dirty();\n\n        // Insert 5 -> sweep rotates as needed, draining k2 and evicting it.\n        let k5 = insert_page(&mut c, 5);\n\n        assert!(\n            c.get(&k2).unwrap().is_none(),\n            \"k2 should be evicted after its credit drains\"\n        );\n        assert!(c.get(&k3).unwrap().is_some(), \"k3 is dirty (unevictable)\");\n        assert!(c.get(&k4).unwrap().is_some(), \"k4 is dirty (unevictable)\");\n        assert!(c.get(&k5).unwrap().is_some(), \"k5 just inserted\");\n        c.verify_cache_integrity();\n    }\n\n    #[test]\n    fn gclock_hot_survives_scan_pages() {\n        let mut c = PageCache::new_with_spill(4, true);\n        let _k1 = insert_page(&mut c, 1);\n        let k2 = insert_page(&mut c, 2);\n        let _k3 = insert_page(&mut c, 3);\n        let _k4 = insert_page(&mut c, 4);\n\n        // Make k2 truly hot: three real touches\n        for _ in 0..3 {\n            assert!(c.get(&k2).unwrap().is_some());\n        }\n        assert!(matches!(c.ref_of(&k2), Some(REF_MAX)));\n\n        // Now simulate a scan inserting new pages 5..10 (one-hit wonders).\n        for id in 5..=10 {\n            let _ = insert_page(&mut c, id);\n        }\n\n        // Hot k2 should still be present; most single-hit scan pages should churn.\n        assert!(\n            c.get(&k2).unwrap().is_some(),\n            \"hot page should survive scan\"\n        );\n        // The earliest single-hit page should be gone.\n        assert!(c.get(&create_key(5)).unwrap().is_none());\n        c.verify_cache_integrity();\n    }\n\n    #[test]\n    fn hand_stays_valid_after_deleting_only_element() {\n        let mut c = PageCache::new_with_spill(2, true);\n        let k = insert_page(&mut c, 1);\n        assert!(c.delete(k).is_ok());\n        // Inserting again should not panic and should succeed\n        let _ = insert_page(&mut c, 2);\n        c.verify_cache_integrity();\n    }\n\n    #[test]\n    fn hand_is_reset_after_clear_and_resize() {\n        let mut c = PageCache::new_with_spill(3, true);\n        for i in 1..=3 {\n            let _ = insert_page(&mut c, i);\n        }\n        c.clear(false).unwrap();\n        // No elements; insert should not rely on stale hand\n        let _ = insert_page(&mut c, 10);\n\n        // Resize from 1 -> 4 and back should not OOB the hand\n        assert_eq!(c.resize(4), CacheResizeResult::Done);\n        assert_eq!(c.resize(1), CacheResizeResult::Done);\n        let _ = insert_page(&mut c, 11);\n        c.verify_cache_integrity();\n    }\n\n    #[test]\n    fn resize_preserves_ref_and_recency() {\n        let mut c = PageCache::new_with_spill(4, true);\n        let _k1 = insert_page(&mut c, 1);\n        let k2 = insert_page(&mut c, 2);\n        let _k3 = insert_page(&mut c, 3);\n        let _k4 = insert_page(&mut c, 4);\n        // Make k2 hot.\n        for _ in 0..3 {\n            assert!(c.get(&k2).unwrap().is_some());\n        }\n        let _r_before = c.ref_of(&k2);\n\n        // Shrink to 3 (one page will be evicted during repack/next insert)\n        assert_eq!(c.resize(3), CacheResizeResult::Done);\n        assert!(matches!(c.ref_of(&k2), _r_before));\n\n        // Force an eviction; hot k2 should survive more passes.\n        let _ = insert_page(&mut c, 5);\n        assert!(c.get(&k2).unwrap().is_some());\n        c.verify_cache_integrity();\n    }\n\n    #[test]\n    fn test_sieve_second_chance_preserves_marked_page() {\n        let mut cache = PageCache::new_with_spill(3, true);\n        let key1 = insert_page(&mut cache, 1);\n        let key2 = insert_page(&mut cache, 2);\n        let key3 = insert_page(&mut cache, 3);\n\n        // Mark key1 for second chance\n        assert!(cache.get(&key1).unwrap().is_some());\n\n        let key4 = insert_page(&mut cache, 4);\n        // CLOCK sweep from hand:\n        // - key1 marked -> decrement, continue\n        // - key3 (MRU) unmarked -> evict\n        assert!(\n            cache.get(&key1).unwrap().is_some(),\n            \"key1 had ref bit set, got second chance\"\n        );\n        assert!(\n            cache.get(&key3).unwrap().is_none(),\n            \"key3 (MRU) should be evicted\"\n        );\n        assert!(cache.get(&key4).unwrap().is_some(), \"key4 just inserted\");\n        assert!(\n            cache.get(&key2).unwrap().is_some(),\n            \"key2 (middle) should remain\"\n        );\n        cache.verify_cache_integrity();\n    }\n\n    #[test]\n    fn test_clock_sweep_wraps_around() {\n        // Test that clock hand properly wraps around the circular list\n        let mut cache = PageCache::new_with_spill(3, true);\n        let key1 = insert_page(&mut cache, 1);\n        let key2 = insert_page(&mut cache, 2);\n        let key3 = insert_page(&mut cache, 3);\n\n        // Mark all pages\n        assert!(cache.get(&key1).unwrap().is_some());\n        assert!(cache.get(&key2).unwrap().is_some());\n        assert!(cache.get(&key3).unwrap().is_some());\n\n        // Insert 4: hand will sweep full circle, decrementing all refs\n        // then sweep again and evict first unmarked page\n        let key4 = insert_page(&mut cache, 4);\n\n        // One page was evicted after full sweep\n        assert_eq!(cache.len(), 3);\n        assert!(cache.get(&key4).unwrap().is_some());\n\n        // Verify exactly one of the original pages was evicted\n        let survivors = [key1, key2, key3]\n            .iter()\n            .filter(|k| cache.get(k).unwrap().is_some())\n            .count();\n        assert_eq!(survivors, 2, \"Should have 2 survivors from original 3\");\n        cache.verify_cache_integrity();\n    }\n\n    #[test]\n    fn test_circular_list_single_element() {\n        let mut cache = PageCache::new_with_spill(3, true);\n        let key1 = insert_page(&mut cache, 1);\n\n        // Single element exists\n        assert_eq!(cache.len(), 1);\n        assert!(cache.contains_key(&key1));\n\n        // Delete single element\n        assert!(cache.delete(key1).is_ok());\n        assert!(cache.clock_hand.is_null());\n\n        // Insert after empty should work\n        let key2 = insert_page(&mut cache, 2);\n        assert_eq!(cache.len(), 1);\n        assert!(cache.contains_key(&key2));\n        cache.verify_cache_integrity();\n    }\n\n    #[test]\n    fn test_hand_advances_on_eviction() {\n        // Note: page 1 is DatabaseHeader and is never evictable, so use page ids >= 2\n        let mut cache = PageCache::new_with_spill(2, true);\n        let _key2 = insert_page(&mut cache, 2);\n        let _key3 = insert_page(&mut cache, 3);\n\n        // Note initial hand position\n        let initial_hand = cache.clock_hand;\n\n        // Force eviction\n        let _key4 = insert_page(&mut cache, 4);\n\n        // Hand should exist (not null)\n        let new_hand = cache.clock_hand;\n        assert!(!new_hand.is_null());\n        // Hand moved during sweep (exact position depends on eviction)\n        assert!(initial_hand.is_null() || new_hand != initial_hand || cache.len() < 2);\n        cache.verify_cache_integrity();\n    }\n\n    #[test]\n    fn test_multi_level_ref_counting() {\n        let mut cache = PageCache::new_with_spill(2, true);\n        let key1 = insert_page(&mut cache, 1);\n        let _key2 = insert_page(&mut cache, 2);\n\n        // Bump key1 to MAX (3 accesses)\n        for _ in 0..3 {\n            assert!(cache.get(&key1).unwrap().is_some());\n        }\n        assert_eq!(cache.ref_of(&key1), Some(REF_MAX));\n\n        // Insert multiple new pages - key1 should survive longer\n        for i in 3..6 {\n            let _ = insert_page(&mut cache, i);\n        }\n\n        // key1 might still be there due to high ref count\n        // (depends on exact sweep pattern, but it got multiple chances)\n        cache.verify_cache_integrity();\n    }\n\n    #[test]\n    fn test_resize_maintains_circular_structure() {\n        let mut cache = PageCache::new_with_spill(5, true);\n        for i in 1..=4 {\n            let _ = insert_page(&mut cache, i);\n        }\n\n        // Resize smaller\n        assert_eq!(cache.resize(2), CacheResizeResult::Done);\n        assert_eq!(cache.len(), 2);\n\n        // Verify structure via integrity check\n        cache.verify_cache_integrity();\n    }\n\n    #[test]\n    fn test_link_after_correctness() {\n        let mut cache = PageCache::new_with_spill(4, true);\n        let key1 = insert_page(&mut cache, 1);\n        let key2 = insert_page(&mut cache, 2);\n        let key3 = insert_page(&mut cache, 3);\n\n        // Verify all keys are in cache\n        assert!(cache.contains_key(&key1));\n        assert!(cache.contains_key(&key2));\n        assert!(cache.contains_key(&key3));\n        assert_eq!(cache.len(), 3);\n\n        cache.verify_cache_integrity();\n    }\n\n    #[test]\n    fn test_evictable_count_tracking() {\n        // Test that evictable_count is tracked correctly for fast-path spill check\n        // Note: page 1 is DatabaseHeader and is never evictable\n        let mut cache = PageCache::new_with_spill(10, true);\n\n        // Insert clean pages (all evictable except page 1)\n        let key1 = insert_page(&mut cache, 1); // page 1 is never evictable\n        let key2 = insert_page(&mut cache, 2);\n        let key3 = insert_page(&mut cache, 3);\n\n        // Page 1 is not counted, pages 2 and 3 are evictable\n        assert_eq!(cache.evictable_count(), 2);\n\n        // Make page 2 dirty - it becomes non-evictable\n        cache.notify_page_dirty(key2);\n        assert_eq!(cache.evictable_count(), 1);\n\n        // Make page 2 spilled - it becomes evictable again\n        cache.notify_page_spilled(key2);\n        assert_eq!(cache.evictable_count(), 2);\n\n        // Delete page 3 - evictable count decreases\n        assert!(cache.delete(key3).is_ok());\n        assert_eq!(cache.evictable_count(), 1);\n\n        // Delete page 1 (which wasn't counted) - no change\n        assert!(cache.delete(key1).is_ok());\n        assert_eq!(cache.evictable_count(), 1);\n\n        // Clear cache\n        assert!(cache.clear(true).is_ok());\n        assert_eq!(cache.evictable_count(), 0);\n\n        cache.verify_cache_integrity();\n    }\n\n    #[test]\n    fn test_needs_spill_fast_path() {\n        // Test that needs_spill uses the fast path when we have enough evictable pages\n        // Capacity 10, threshold 90% = 9, so when len > 9 we need some evictable pages\n        let mut cache = PageCache::new_with_spill(10, true);\n\n        // Insert 10 clean pages (all evictable except page 1)\n        for i in 1..=10 {\n            let _ = insert_page(&mut cache, i);\n        }\n\n        // len=10, threshold=9, needed_evictable = 10-9 = 1\n        // evictable_count = 9 (pages 2-10, page 1 not counted)\n        // Fast path: 9 >= 1, so no spill needed\n        assert!(!cache.needs_spill());\n        assert_eq!(cache.evictable_count(), 9);\n\n        // Make all pages dirty\n        for i in 2..=10 {\n            let key = create_key(i);\n            cache.notify_page_dirty(key);\n            cache.peek(&key, false).unwrap().set_dirty();\n        }\n\n        // Now evictable_count = 0 and pages are actually dirty\n        // needed_evictable = 1, but we have 0 evictable pages\n        assert_eq!(cache.evictable_count(), 0);\n        // needs_spill should return true because we need 1 evictable page but have 0\n        assert!(cache.needs_spill());\n\n        // Mark pages as spilled\n        for i in 2..=10 {\n            let key = create_key(i);\n            cache.notify_page_spilled(key);\n            cache.peek(&key, false).unwrap().set_spilled();\n        }\n\n        // Now evictable_count = 9 and pages are spilled (evictable)\n        assert_eq!(cache.evictable_count(), 9);\n        // Fast path: 9 >= 1, so no spill needed\n        assert!(!cache.needs_spill());\n\n        cache.verify_cache_integrity();\n    }\n}\n"
  },
  {
    "path": "core/storage/pager.rs",
    "content": "use crate::assert::assert_send_sync;\n#[cfg(target_vendor = \"apple\")]\nuse crate::io::AtomicFileSyncType;\nuse crate::io::FileSyncType;\nuse crate::io::WriteBatch;\nuse crate::storage::btree::PinGuard;\nuse crate::storage::subjournal::Subjournal;\nuse crate::storage::wal::PreparedFrames;\nuse crate::storage::{\n    buffer_pool::BufferPool,\n    database::DatabaseStorage,\n    sqlite3_ondisk::{\n        self, parse_wal_frame_header, DatabaseHeader, OverflowCell, PageSize, PageType,\n        CELL_PTR_SIZE_BYTES, INTERIOR_PAGE_HEADER_SIZE_BYTES, LEAF_PAGE_HEADER_SIZE_BYTES,\n        MINIMUM_CELL_SIZE,\n    },\n    wal::{CheckpointResult, RollbackTo, Wal, IOV_MAX},\n};\nuse crate::sync::atomic::{\n    AtomicBool, AtomicIsize, AtomicU16, AtomicU32, AtomicU64, AtomicU8, AtomicUsize, Ordering,\n};\nuse crate::sync::Arc;\nuse crate::sync::{Mutex, RwLock};\nuse crate::types::{IOCompletions, WalState};\nuse crate::util::IOExt as _;\nuse crate::{\n    io::CompletionGroup, return_if_io, types::WalFrameInfo, Completion, Connection, IOResult,\n    LimboError, Result, TransactionState,\n};\nuse crate::{io_yield_one, Buffer, CompletionError, IOContext, OpenFlags, SyncMode, IO};\n#[allow(unused_imports)]\nuse crate::{\n    turso_assert, turso_assert_eq, turso_assert_greater_than, turso_assert_greater_than_or_equal,\n    turso_assert_less_than, turso_assert_ne, turso_debug_assert, turso_soft_unreachable,\n};\nuse arc_swap::ArcSwapOption;\nuse roaring::RoaringBitmap;\nuse std::cell::UnsafeCell;\nuse tracing::{instrument, trace, Level};\n\nuse super::btree::offset::{\n    BTREE_CELL_CONTENT_AREA, BTREE_CELL_COUNT, BTREE_FIRST_FREEBLOCK, BTREE_FRAGMENTED_BYTES_COUNT,\n    BTREE_PAGE_TYPE, BTREE_RIGHTMOST_PTR,\n};\nuse super::btree::{\n    btree_init_page, payload_overflow_threshold_max, payload_overflow_threshold_min,\n};\nuse super::page_cache::{CacheError, CacheResizeResult, PageCache, PageCacheKey, SpillResult};\nuse super::sqlite3_ondisk::read_varint;\nuse super::sqlite3_ondisk::{\n    begin_write_btree_page, read_btree_cell, read_u32, BTreeCell, FREELIST_LEAF_PTR_SIZE,\n    FREELIST_TRUNK_OFFSET_FIRST_LEAF_PTR, FREELIST_TRUNK_OFFSET_LEAF_COUNT,\n    FREELIST_TRUNK_OFFSET_NEXT_TRUNK_PTR,\n};\nuse super::wal::CheckpointMode;\nuse crate::storage::encryption::{CipherMode, EncryptionContext, EncryptionKey};\n\n/// SQLite's default maximum page count\nconst DEFAULT_MAX_PAGE_COUNT: u32 = 0xfffffffe;\nconst RESERVED_SPACE_NOT_SET: u16 = u16::MAX;\n\n#[cfg(feature = \"test_helper\")]\n/// Used for testing purposes to change the position of the PENDING BYTE\nstatic PENDING_BYTE: AtomicU32 = AtomicU32::new(0x40000000);\n\n#[cfg(not(feature = \"test_helper\"))]\n/// Byte offset that signifies the start of the ignored page - 1 GB mark\nconst PENDING_BYTE: u32 = 0x40000000;\n\n#[cfg(not(feature = \"omit_autovacuum\"))]\nuse ptrmap::*;\n\n#[derive(Debug, Clone)]\npub struct HeaderRef(PageRef);\n\nimpl HeaderRef {\n    pub fn from_pager(pager: &Pager) -> Result<IOResult<Self>> {\n        let page = return_if_io!(pager.read_header_page());\n        Ok(IOResult::Done(Self(page)))\n    }\n\n    pub fn borrow(&self) -> &DatabaseHeader {\n        // TODO: Instead of erasing mutability, implement `get_mut_contents` and return a shared reference.\n        let content = self.0.get_contents();\n        bytemuck::from_bytes::<DatabaseHeader>(&content.as_ptr()[0..DatabaseHeader::SIZE])\n    }\n}\n\n#[derive(Debug, Clone)]\npub struct HeaderRefMut(PageRef);\n\nimpl HeaderRefMut {\n    pub fn from_pager(pager: &Pager) -> Result<IOResult<Self>> {\n        let page = return_if_io!(pager.read_header_page());\n        pager.add_dirty(&page)?;\n        Ok(IOResult::Done(Self(page)))\n    }\n\n    pub fn borrow_mut(&self) -> &mut DatabaseHeader {\n        let content = self.0.get_contents();\n        bytemuck::from_bytes_mut::<DatabaseHeader>(&mut content.as_ptr()[0..DatabaseHeader::SIZE])\n    }\n\n    /// Get a reference to the underlying page\n    pub fn page(&self) -> &PageRef {\n        &self.0\n    }\n}\n\npub struct PageInner {\n    pub flags: AtomicUsize,\n    pub id: usize,\n    /// If >0, the page is pinned and not eligible for eviction from the page cache.\n    /// The reason this is a counter is that multiple nested code paths may signal that\n    /// a page must not be evicted from the page cache, so even if an inner code path\n    /// requests unpinning via [Page::unpin], the pin count will still be >0 if the outer\n    /// code path has not yet requested to unpin the page as well.\n    ///\n    /// Note that [PageCache::clear] evicts the pages even if pinned, so as long as\n    /// we clear the page cache on errors, pins will not 'leak'.\n    pub pin_count: AtomicUsize,\n    /// The WAL frame number this page was loaded from (0 if loaded from main DB file)\n    /// This tracks which version of the page we have in memory\n    pub wal_tag: AtomicU64,\n    /// The actual page data buffer. None if not loaded.\n    pub buffer: Option<Arc<Buffer>>,\n    /// Overflow cells during btree operations\n    pub overflow_cells: Vec<OverflowCell>,\n}\n\n// Methods moved from PageContent - these provide btree page access\nimpl PageInner {\n    /// Creates a new PageInner from an Arc<Buffer>.\n    pub fn new(buffer: Arc<Buffer>) -> Self {\n        Self {\n            flags: AtomicUsize::new(0),\n            id: 0,\n            pin_count: AtomicUsize::new(0),\n            wal_tag: AtomicU64::new(TAG_UNSET),\n            buffer: Some(buffer),\n            overflow_cells: Vec::new(),\n        }\n    }\n\n    /// Creates a new PageInner with an owned buffer.\n    pub fn from_buffer(buffer: Buffer) -> Self {\n        Self {\n            flags: AtomicUsize::new(0),\n            id: 0,\n            pin_count: AtomicUsize::new(0),\n            wal_tag: AtomicU64::new(TAG_UNSET),\n            buffer: Some(Arc::new(buffer)),\n            overflow_cells: Vec::new(),\n        }\n    }\n    /// Get the page buffer as a mutable slice. Panics if buffer not loaded.\n    #[inline]\n    #[allow(clippy::mut_from_ref)]\n    pub fn as_ptr(&self) -> &mut [u8] {\n        self.buffer\n            .as_ref()\n            .expect(\"buffer not loaded\")\n            .as_mut_slice()\n    }\n\n    /// The position where page content starts. It's 100 for page 1 (database file header is 100 bytes),\n    /// 0 for all other pages.\n    #[inline]\n    pub fn offset(&self) -> usize {\n        if self.id == 1 {\n            DatabaseHeader::SIZE\n        } else {\n            0\n        }\n    }\n\n    /// Read a u8 from the page content at the given offset, taking account the possible db header on page 1.\n    #[inline]\n    fn read_u8(&self, pos: usize) -> u8 {\n        let buf = self.as_ptr();\n        buf[self.offset() + pos]\n    }\n\n    /// Read a u16 from the page content at the given offset, taking account the possible db header on page 1.\n    #[inline]\n    fn read_u16(&self, pos: usize) -> u16 {\n        let buf = self.as_ptr();\n        let offset = self.offset();\n        u16::from_be_bytes([buf[offset + pos], buf[offset + pos + 1]])\n    }\n\n    /// Read a u32 from the page content at the given offset, taking account the possible db header on page 1.\n    #[inline]\n    fn read_u32(&self, pos: usize) -> u32 {\n        let buf = self.as_ptr();\n        read_u32(buf, self.offset() + pos)\n    }\n\n    /// Write a u8 to the page content at the given offset, taking account the possible db header on page 1.\n    #[inline]\n    fn write_u8(&self, pos: usize, value: u8) {\n        tracing::trace!(\"write_u8(pos={}, value={})\", pos, value);\n        let buf = self.as_ptr();\n        buf[self.offset() + pos] = value;\n    }\n\n    /// Write a u16 to the page content at the given offset, taking account the possible db header on page 1.\n    #[inline]\n    fn write_u16(&self, pos: usize, value: u16) {\n        tracing::trace!(\"write_u16(pos={}, value={})\", pos, value);\n        let buf = self.as_ptr();\n        let offset = self.offset();\n        buf[offset + pos..offset + pos + 2].copy_from_slice(&value.to_be_bytes());\n    }\n\n    /// Write a u32 to the page content at the given offset, taking account the possible db header on page 1.\n    #[inline]\n    fn write_u32(&self, pos: usize, value: u32) {\n        tracing::trace!(\"write_u32(pos={}, value={})\", pos, value);\n        let buf = self.as_ptr();\n        let offset = self.offset();\n        buf[offset + pos..offset + pos + 4].copy_from_slice(&value.to_be_bytes());\n    }\n\n    #[inline]\n    pub fn page_type(&self) -> crate::Result<PageType> {\n        self.read_u8(BTREE_PAGE_TYPE).try_into()\n    }\n\n    /// Read a u16 from the page content at the given absolute offset (no db header offset).\n    #[inline]\n    pub fn read_u16_no_offset(&self, pos: usize) -> u16 {\n        let buf = self.as_ptr();\n        u16::from_be_bytes([buf[pos], buf[pos + 1]])\n    }\n\n    /// Read a u32 from the page content at the given absolute offset (no db header offset).\n    #[inline]\n    pub fn read_u32_no_offset(&self, pos: usize) -> u32 {\n        let buf = self.as_ptr();\n        u32::from_be_bytes([buf[pos], buf[pos + 1], buf[pos + 2], buf[pos + 3]])\n    }\n\n    /// Write a u16 at the given absolute offset (no db header offset).\n    pub fn write_u16_no_offset(&self, pos: usize, value: u16) {\n        tracing::trace!(\"write_u16_no_offset(pos={}, value={})\", pos, value);\n        let buf = self.as_ptr();\n        buf[pos..pos + 2].copy_from_slice(&value.to_be_bytes());\n    }\n\n    /// Write a u32 at the given absolute offset (no db header offset).\n    pub fn write_u32_no_offset(&self, pos: usize, value: u32) {\n        tracing::trace!(\"write_u32_no_offset(pos={}, value={})\", pos, value);\n        let buf = self.as_ptr();\n        buf[pos..pos + 4].copy_from_slice(&value.to_be_bytes());\n    }\n\n    pub fn write_page_type(&self, value: u8) {\n        self.write_u8(BTREE_PAGE_TYPE, value);\n    }\n\n    pub fn write_rightmost_ptr(&self, value: u32) {\n        self.write_u32(BTREE_RIGHTMOST_PTR, value);\n    }\n\n    pub fn write_first_freeblock(&self, value: u16) {\n        self.write_u16(BTREE_FIRST_FREEBLOCK, value);\n    }\n\n    pub fn write_freeblock(&self, offset: u16, size: u16, next_block: Option<u16>) {\n        self.write_freeblock_next_ptr(offset, next_block.unwrap_or(0));\n        self.write_freeblock_size(offset, size);\n    }\n\n    pub fn write_freeblock_size(&self, offset: u16, size: u16) {\n        self.write_u16_no_offset(offset as usize + 2, size);\n    }\n\n    pub fn write_freeblock_next_ptr(&self, offset: u16, next_block: u16) {\n        self.write_u16_no_offset(offset as usize, next_block);\n    }\n\n    pub fn read_freeblock(&self, offset: u16) -> (u16, u16) {\n        (\n            self.read_u16_no_offset(offset as usize),\n            self.read_u16_no_offset(offset as usize + 2),\n        )\n    }\n\n    pub fn write_cell_count(&self, value: u16) {\n        self.write_u16(BTREE_CELL_COUNT, value);\n    }\n\n    pub fn write_cell_content_area(&self, value: usize) {\n        turso_debug_assert!(value <= PageSize::MAX as usize);\n        let value = value as u16;\n        self.write_u16(BTREE_CELL_CONTENT_AREA, value);\n    }\n\n    pub fn write_fragmented_bytes_count(&self, value: u8) {\n        self.write_u8(BTREE_FRAGMENTED_BYTES_COUNT, value);\n    }\n\n    #[inline]\n    pub fn first_freeblock(&self) -> u16 {\n        self.read_u16(BTREE_FIRST_FREEBLOCK)\n    }\n\n    #[inline]\n    pub fn cell_count(&self) -> usize {\n        self.read_u16(BTREE_CELL_COUNT) as usize\n    }\n\n    #[inline]\n    pub fn cell_pointer_array_size(&self) -> usize {\n        self.cell_count() * CELL_PTR_SIZE_BYTES\n    }\n\n    #[inline]\n    pub fn unallocated_region_start(&self) -> usize {\n        let (cell_ptr_array_start, cell_ptr_array_size) = self.cell_pointer_array_offset_and_size();\n        cell_ptr_array_start + cell_ptr_array_size\n    }\n\n    #[inline]\n    pub fn unallocated_region_size(&self) -> usize {\n        self.cell_content_area() as usize - self.unallocated_region_start()\n    }\n\n    #[inline]\n    pub fn cell_content_area(&self) -> u32 {\n        let offset = self.read_u16(BTREE_CELL_CONTENT_AREA);\n        if offset == 0 {\n            PageSize::MAX\n        } else {\n            offset as u32\n        }\n    }\n\n    #[inline]\n    pub fn header_size(&self) -> usize {\n        let is_interior = self.read_u8(BTREE_PAGE_TYPE) <= PageType::TableInterior as u8;\n        (!is_interior as usize) * LEAF_PAGE_HEADER_SIZE_BYTES\n            + (is_interior as usize) * INTERIOR_PAGE_HEADER_SIZE_BYTES\n    }\n\n    #[inline]\n    pub fn num_frag_free_bytes(&self) -> u8 {\n        self.read_u8(BTREE_FRAGMENTED_BYTES_COUNT)\n    }\n\n    #[inline]\n    pub fn rightmost_pointer(&self) -> crate::Result<Option<u32>> {\n        match self.page_type()? {\n            PageType::IndexInterior | PageType::TableInterior => {\n                Ok(Some(self.read_u32(BTREE_RIGHTMOST_PTR)))\n            }\n            PageType::IndexLeaf | PageType::TableLeaf => Ok(None),\n        }\n    }\n\n    #[inline]\n    pub fn rightmost_pointer_raw(&self) -> crate::Result<Option<*mut u8>> {\n        match self.page_type()? {\n            PageType::IndexInterior | PageType::TableInterior => Ok(Some(unsafe {\n                self.as_ptr()\n                    .as_mut_ptr()\n                    .add(self.offset() + BTREE_RIGHTMOST_PTR)\n            })),\n            PageType::IndexLeaf | PageType::TableLeaf => Ok(None),\n        }\n    }\n\n    #[inline]\n    pub fn cell_get(&self, idx: usize, usable_size: usize) -> crate::Result<BTreeCell> {\n        tracing::trace!(\"cell_get(idx={})\", idx);\n        let buf = self.as_ptr();\n\n        let ncells = self.cell_count();\n        turso_assert_less_than!(idx, ncells,\n            \"cell_get: idx out of bounds\",\n            {\"idx\": idx, \"ncells\": ncells}\n        );\n        let cell_pointer_array_start = self.header_size();\n        let cell_pointer = cell_pointer_array_start + (idx * CELL_PTR_SIZE_BYTES);\n        let cell_pointer = self.read_u16(cell_pointer) as usize;\n\n        let static_buf: &'static [u8] = unsafe { std::mem::transmute::<&[u8], &'static [u8]>(buf) };\n        read_btree_cell(static_buf, self, cell_pointer, usable_size)\n    }\n\n    #[inline(always)]\n    pub fn cell_table_interior_read_rowid(&self, idx: usize) -> crate::Result<i64> {\n        turso_debug_assert!(matches!(self.page_type(), Ok(PageType::TableInterior)));\n        let buf = self.as_ptr();\n        let cell_pointer_array_start = self.header_size();\n        let cell_pointer = cell_pointer_array_start + (idx * CELL_PTR_SIZE_BYTES);\n        let cell_pointer = self.read_u16(cell_pointer) as usize;\n        const LEFT_CHILD_PAGE_SIZE_BYTES: usize = 4;\n        let (rowid, _) = read_varint(crate::slice_in_bounds_or_corrupt!(\n            buf,\n            cell_pointer + LEFT_CHILD_PAGE_SIZE_BYTES..\n        ))?;\n        Ok(rowid as i64)\n    }\n\n    #[inline(always)]\n    pub fn cell_interior_read_left_child_page(&self, idx: usize) -> crate::Result<u32> {\n        turso_debug_assert!(matches!(\n            self.page_type(),\n            Ok(PageType::TableInterior) | Ok(PageType::IndexInterior)\n        ));\n        let buf = self.as_ptr();\n        let cell_pointer_array_start = self.header_size();\n        let cell_pointer = cell_pointer_array_start + (idx * CELL_PTR_SIZE_BYTES);\n        let cell_pointer = self.read_u16(cell_pointer) as usize;\n        crate::assert_or_bail_corrupt!(\n            cell_pointer + 4 <= buf.len(),\n            \"cell pointer {} out of bounds for page size {}\",\n            cell_pointer,\n            buf.len()\n        );\n        Ok(u32::from_be_bytes([\n            buf[cell_pointer],\n            buf[cell_pointer + 1],\n            buf[cell_pointer + 2],\n            buf[cell_pointer + 3],\n        ]))\n    }\n\n    #[inline(always)]\n    pub fn cell_table_leaf_read_rowid(&self, idx: usize) -> crate::Result<i64> {\n        turso_debug_assert!(matches!(self.page_type(), Ok(PageType::TableLeaf)));\n        let buf = self.as_ptr();\n        let cell_pointer_array_start = self.header_size();\n        let cell_pointer = cell_pointer_array_start + (idx * CELL_PTR_SIZE_BYTES);\n        let cell_pointer = self.read_u16(cell_pointer) as usize;\n        let mut pos = cell_pointer;\n        let (_, nr) = read_varint(crate::slice_in_bounds_or_corrupt!(buf, pos..))?;\n        pos += nr;\n        let (rowid, _) = read_varint(crate::slice_in_bounds_or_corrupt!(buf, pos..))?;\n        Ok(rowid as i64)\n    }\n\n    /// Fast path for index cells: returns payload slice and overflow info without constructing BTreeCell.\n    ///\n    /// This bypasses the full `cell_get()` to `read_btree_cell()` path for binary search hot loops.\n    /// The returned slice is valid as long as the page is alive.\n    ///\n    /// Returns: (payload_slice, payload_size, first_overflow_page)\n    #[inline(always)]\n    pub fn cell_index_read_payload_ptr(\n        &self,\n        idx: usize,\n        usable_size: usize,\n    ) -> crate::Result<(&'static [u8], u64, Option<u32>)> {\n        let buf = self.as_ptr();\n        let cell_pointer_array_start = self.header_size();\n        let cell_pointer = cell_pointer_array_start + (idx * CELL_PTR_SIZE_BYTES);\n        let cell_offset = self.read_u16(cell_pointer) as usize;\n\n        let page_type = self.page_type()?;\n        let (payload_size, varint_len, header_skip) = match page_type {\n            PageType::IndexInterior => {\n                let (size, len) =\n                    read_varint(crate::slice_in_bounds_or_corrupt!(buf, cell_offset + 4..))?;\n                (size, len, 4usize)\n            }\n            PageType::IndexLeaf => {\n                let (size, len) =\n                    read_varint(crate::slice_in_bounds_or_corrupt!(buf, cell_offset..))?;\n                (size, len, 0usize)\n            }\n            _ => unreachable!(\"cell_index_read_payload_ptr called on non-index page\"),\n        };\n\n        let payload_start = cell_offset + header_skip + varint_len;\n\n        let max_local = payload_overflow_threshold_max(page_type, usable_size);\n        let min_local = payload_overflow_threshold_min(page_type, usable_size);\n        let (overflows, local_size) = sqlite3_ondisk::payload_overflows(\n            payload_size as usize,\n            max_local,\n            min_local,\n            usable_size,\n        );\n\n        let (payload_slice, first_overflow) = if overflows {\n            let overflow_ptr_offset = payload_start + local_size - 4;\n            crate::assert_or_bail_corrupt!(\n                overflow_ptr_offset + 4 <= buf.len(),\n                \"overflow pointer offset {} out of bounds for page size {}\",\n                overflow_ptr_offset,\n                buf.len()\n            );\n            let first_overflow_page = u32::from_be_bytes([\n                buf[overflow_ptr_offset],\n                buf[overflow_ptr_offset + 1],\n                buf[overflow_ptr_offset + 2],\n                buf[overflow_ptr_offset + 3],\n            ]);\n            let payload_end = payload_start + local_size - 4;\n            crate::assert_or_bail_corrupt!(\n                payload_start < payload_end && payload_end <= buf.len(),\n                \"payload range {}..{} out of bounds for page size {}\",\n                payload_start,\n                payload_end,\n                buf.len()\n            );\n            // SAFETY: valid as long as page is alive\n            let slice = unsafe {\n                std::mem::transmute::<&[u8], &'static [u8]>(&buf[payload_start..payload_end])\n            };\n            (slice, Some(first_overflow_page))\n        } else {\n            let payload_end = payload_start + payload_size as usize;\n            crate::assert_or_bail_corrupt!(\n                payload_end <= buf.len(),\n                \"payload range {}..{} out of bounds for page size {}\",\n                payload_start,\n                payload_end,\n                buf.len()\n            );\n            // SAFETY: valid as long as page is alive\n            let slice = unsafe {\n                std::mem::transmute::<&[u8], &'static [u8]>(&buf[payload_start..payload_end])\n            };\n            (slice, None)\n        };\n\n        Ok((payload_slice, payload_size, first_overflow))\n    }\n\n    #[inline]\n    pub fn cell_pointer_array_offset_and_size(&self) -> (usize, usize) {\n        (\n            self.cell_pointer_array_offset(),\n            self.cell_pointer_array_size(),\n        )\n    }\n\n    #[inline]\n    pub fn cell_pointer_array_offset(&self) -> usize {\n        self.offset() + self.header_size()\n    }\n\n    #[inline]\n    pub fn cell_get_raw_start_offset(&self, idx: usize) -> usize {\n        let cell_pointer_array_start = self.cell_pointer_array_offset();\n        let cell_pointer = cell_pointer_array_start + (idx * CELL_PTR_SIZE_BYTES);\n        self.read_u16_no_offset(cell_pointer) as usize\n    }\n\n    #[inline]\n    pub fn cell_get_raw_region(\n        &self,\n        idx: usize,\n        usable_size: usize,\n    ) -> crate::Result<(usize, usize)> {\n        let page_type = self.page_type()?;\n        let max_local = payload_overflow_threshold_max(page_type, usable_size);\n        let min_local = payload_overflow_threshold_min(page_type, usable_size);\n        let cell_count = self.cell_count();\n        self._cell_get_raw_region_faster(\n            idx,\n            usable_size,\n            cell_count,\n            max_local,\n            min_local,\n            page_type,\n        )\n    }\n\n    #[inline]\n    pub fn _cell_get_raw_region_faster(\n        &self,\n        idx: usize,\n        usable_size: usize,\n        cell_count: usize,\n        max_local: usize,\n        min_local: usize,\n        page_type: PageType,\n    ) -> crate::Result<(usize, usize)> {\n        let buf = self.as_ptr();\n        turso_assert_less_than!(idx, cell_count);\n        let start = self.cell_get_raw_start_offset(idx);\n        let len = match page_type {\n            PageType::IndexInterior => {\n                let (len_payload, n_payload) =\n                    read_varint(crate::slice_in_bounds_or_corrupt!(buf, start + 4..))?;\n                let (overflows, to_read) = sqlite3_ondisk::payload_overflows(\n                    len_payload as usize,\n                    max_local,\n                    min_local,\n                    usable_size,\n                );\n                if overflows {\n                    4 + to_read + n_payload\n                } else {\n                    4 + len_payload as usize + n_payload\n                }\n            }\n            PageType::TableInterior => {\n                let (_, n_rowid) =\n                    read_varint(crate::slice_in_bounds_or_corrupt!(buf, start + 4..))?;\n                4 + n_rowid\n            }\n            PageType::IndexLeaf => {\n                let (len_payload, n_payload) =\n                    read_varint(crate::slice_in_bounds_or_corrupt!(buf, start..))?;\n                let (overflows, to_read) = sqlite3_ondisk::payload_overflows(\n                    len_payload as usize,\n                    max_local,\n                    min_local,\n                    usable_size,\n                );\n                if overflows {\n                    to_read + n_payload\n                } else {\n                    let mut size = len_payload as usize + n_payload;\n                    if size < MINIMUM_CELL_SIZE {\n                        size = MINIMUM_CELL_SIZE;\n                    }\n                    size\n                }\n            }\n            PageType::TableLeaf => {\n                let (len_payload, n_payload) =\n                    read_varint(crate::slice_in_bounds_or_corrupt!(buf, start..))?;\n                let (_, n_rowid) =\n                    read_varint(crate::slice_in_bounds_or_corrupt!(buf, start + n_payload..))?;\n                let (overflows, to_read) = sqlite3_ondisk::payload_overflows(\n                    len_payload as usize,\n                    max_local,\n                    min_local,\n                    usable_size,\n                );\n                if overflows {\n                    to_read + n_payload + n_rowid\n                } else {\n                    let mut size = len_payload as usize + n_payload + n_rowid;\n                    if size < MINIMUM_CELL_SIZE {\n                        size = MINIMUM_CELL_SIZE;\n                    }\n                    size\n                }\n            }\n        };\n        crate::assert_or_bail_corrupt!(\n            start + len <= buf.len(),\n            \"cell region {}..{} out of bounds for page size {}\",\n            start,\n            start + len,\n            buf.len()\n        );\n        Ok((start, len))\n    }\n\n    pub fn is_leaf(&self) -> bool {\n        self.read_u8(BTREE_PAGE_TYPE) > PageType::TableInterior as u8\n    }\n\n    pub fn write_database_header(&self, header: &DatabaseHeader) {\n        let buf = self.as_ptr();\n        buf[0..DatabaseHeader::SIZE].copy_from_slice(bytemuck::bytes_of(header));\n    }\n\n    pub fn debug_print_freelist(&self, usable_space: usize) {\n        let mut pc = self.first_freeblock() as usize;\n        let mut block_num = 0;\n        println!(\"---- Free List Blocks ----\");\n        println!(\"first freeblock pointer: {pc}\");\n        println!(\"cell content area: {}\", self.cell_content_area());\n        println!(\"fragmented bytes: {}\", self.num_frag_free_bytes());\n\n        while pc != 0 && pc <= usable_space {\n            let next = self.read_u16_no_offset(pc);\n            let size = self.read_u16_no_offset(pc + 2);\n\n            println!(\"block {block_num}: position={pc}, size={size}, next={next}\");\n            pc = next as usize;\n            block_num += 1;\n        }\n        println!(\"--------------\");\n    }\n}\n\n/// Type alias for backward compatibility - PageContent is now PageInner\npub type PageContent = PageInner;\n\n/// WAL tag not set\npub const TAG_UNSET: u64 = u64::MAX;\n/// WAL write in progress, sentinel value set before starting a WAL write\n/// so we can detect if page was modified during the write\npub const TAG_WRITE_PENDING: u64 = u64::MAX - 1;\n\n/// Bit layout:\n/// epoch: 20\n/// frame: 44\nconst EPOCH_BITS: u32 = 20;\nconst FRAME_BITS: u32 = 64 - EPOCH_BITS;\nconst EPOCH_SHIFT: u32 = FRAME_BITS;\nconst EPOCH_MAX: u32 = (1u32 << EPOCH_BITS) - 1;\nconst FRAME_MAX: u64 = (1u64 << FRAME_BITS) - 1;\n\n#[inline]\npub fn pack_tag_pair(frame: u64, seq: u32) -> u64 {\n    ((seq as u64) << EPOCH_SHIFT) | (frame & FRAME_MAX)\n}\n\n#[inline]\npub fn unpack_tag_pair(tag: u64) -> (u64, u32) {\n    let epoch = ((tag >> EPOCH_SHIFT) & (EPOCH_MAX as u64)) as u32;\n    let frame = tag & FRAME_MAX;\n    (frame, epoch)\n}\n\n#[derive(Debug)]\npub struct Page {\n    pub inner: UnsafeCell<PageInner>,\n}\n\n// SAFETY: Page is thread-safe because we use atomic page flags to serialize\n// concurrent modifications.\nunsafe impl Send for Page {}\nunsafe impl Sync for Page {}\ncrate::assert::assert_send_sync!(Page);\n\n// Concurrency control of pages will be handled by the pager, we won't wrap Page with RwLock\n// because that is bad bad.\npub type PageRef = Arc<Page>;\n\n/// Page is locked for I/O to prevent concurrent access.\nconst PAGE_LOCKED: usize = 0b010;\n/// Page is dirty. Flush needed.\nconst PAGE_DIRTY: usize = 0b1000;\n/// Page's contents are loaded in memory.\nconst PAGE_LOADED: usize = 0b10000;\n/// Page has been spilled to WAL (can be evicted even though dirty).\nconst PAGE_SPILLED: usize = 0b100000;\n\nimpl Page {\n    pub fn new(id: i64) -> Self {\n        turso_assert_greater_than_or_equal!(id, 0);\n        Self {\n            inner: UnsafeCell::new(PageInner {\n                flags: AtomicUsize::new(0),\n                id: id as usize,\n                pin_count: AtomicUsize::new(0),\n                wal_tag: AtomicU64::new(TAG_UNSET),\n                buffer: None,\n                overflow_cells: Vec::new(),\n            }),\n        }\n    }\n\n    #[allow(clippy::mut_from_ref)]\n    pub fn get(&self) -> &mut PageInner {\n        unsafe { &mut *self.inner.get() }\n    }\n\n    /// Returns a mutable reference to PageInner for accessing page contents.\n    /// Panics if the page buffer is not loaded.\n    pub fn get_contents(&self) -> &mut PageInner {\n        let inner = self.get();\n        turso_debug_assert!(\n            inner.buffer.is_some(),\n            \"page buffer not loaded\",\n            { \"page_id\": inner.id }\n        );\n        inner\n    }\n\n    #[inline]\n    pub fn is_locked(&self) -> bool {\n        self.get().flags.load(Ordering::Acquire) & PAGE_LOCKED != 0\n    }\n\n    #[inline]\n    pub fn set_locked(&self) {\n        self.get().flags.fetch_or(PAGE_LOCKED, Ordering::Acquire);\n    }\n\n    #[inline]\n    pub fn clear_locked(&self) {\n        self.get().flags.fetch_and(!PAGE_LOCKED, Ordering::Release);\n    }\n\n    #[inline]\n    pub fn is_dirty(&self) -> bool {\n        self.get().flags.load(Ordering::Acquire) & PAGE_DIRTY != 0\n    }\n\n    #[inline]\n    /// almost never should be called explicitly - instead [Pager::add_dirty] method must be used\n    pub fn set_dirty(&self) {\n        tracing::debug!(\"set_dirty(page={})\", self.get().id);\n        self.clear_wal_tag();\n        // Clear spilled flag since page is being modified again\n        self.get().flags.fetch_and(!PAGE_SPILLED, Ordering::Release);\n        self.get().flags.fetch_or(PAGE_DIRTY, Ordering::Release);\n    }\n\n    #[inline]\n    /// caller must ensure that [Pager::dirty_pages] will be updated accordingly\n    pub fn clear_dirty(&self) {\n        tracing::debug!(\"clear_dirty(page={})\", self.get().id);\n        self.get().flags.fetch_and(!PAGE_DIRTY, Ordering::Release);\n        self.clear_wal_tag();\n    }\n\n    /// Clear the dirty flag without touching wal_tag.\n    /// Used when a WAL frame has been durably written and the tag already encodes it.\n    #[inline]\n    pub fn clear_dirty_keep_wal_tag(&self) {\n        tracing::debug!(\"clear_dirty_keep_wal_tag(page={})\", self.get().id);\n        self.get().flags.fetch_and(!PAGE_DIRTY, Ordering::Release);\n    }\n\n    /// Returns true if the page has been spilled to WAL and is safe to evict even while dirty.\n    #[inline]\n    pub fn is_spilled(&self) -> bool {\n        self.get().flags.load(Ordering::Acquire) & PAGE_SPILLED != 0\n    }\n\n    /// Mark the page as spilled to WAL. Spilled pages remain dirty but may be evicted from cache.\n    #[inline]\n    pub fn set_spilled(&self) {\n        tracing::debug!(\"set_spilled(page={})\", self.get().id);\n        self.get().flags.fetch_or(PAGE_SPILLED, Ordering::Release);\n    }\n\n    /// Clear the spilled flag. This is also done implicitly on set_dirty().\n    #[inline]\n    pub fn clear_spilled(&self) {\n        self.get().flags.fetch_and(!PAGE_SPILLED, Ordering::Release);\n    }\n\n    #[inline]\n    pub fn is_loaded(&self) -> bool {\n        self.get().flags.load(Ordering::Acquire) & PAGE_LOADED != 0\n    }\n\n    #[inline]\n    pub fn set_loaded(&self) {\n        self.get().flags.fetch_or(PAGE_LOADED, Ordering::Release);\n    }\n\n    #[inline]\n    pub fn clear_loaded(&self) {\n        tracing::debug!(\"clear loaded {}\", self.get().id);\n        self.get().flags.fetch_and(!PAGE_LOADED, Ordering::Release);\n    }\n\n    #[inline]\n    pub fn is_index(&self) -> crate::Result<bool> {\n        Ok(match self.get_contents().page_type()? {\n            PageType::IndexLeaf | PageType::IndexInterior => true,\n            PageType::TableLeaf | PageType::TableInterior => false,\n        })\n    }\n\n    /// Increment the pin count by 1. A pin count >0 means the page is pinned and not eligible for eviction from the page cache.\n    #[inline]\n    pub fn pin(&self) {\n        self.get().pin_count.fetch_add(1, Ordering::SeqCst);\n    }\n\n    /// Decrement the pin count by 1. If the count reaches 0, the page is no longer\n    /// pinned and is eligible for eviction from the page cache.\n    #[inline]\n    pub fn unpin(&self) {\n        let was_pinned = self.try_unpin();\n\n        turso_assert!(\n            was_pinned,\n            \"Attempted to unpin page that was not pinned\",\n            { \"page_id\": self.get().id }\n        );\n    }\n\n    /// Try to decrement the pin count by 1, but do nothing if it was already 0.\n    /// Returns true if the pin count was decremented.\n    #[inline]\n    pub fn try_unpin(&self) -> bool {\n        self.get()\n            .pin_count\n            .fetch_update(Ordering::Release, Ordering::SeqCst, |current| {\n                if current == 0 {\n                    None\n                } else {\n                    Some(current - 1)\n                }\n            })\n            .is_ok()\n    }\n\n    /// Returns true if the page is pinned and thus not eligible for eviction from the page cache.\n    #[inline]\n    pub fn is_pinned(&self) -> bool {\n        self.get().pin_count.load(Ordering::Acquire) > 0\n    }\n\n    #[inline]\n    /// Set the WAL tag from a (frame, epoch) pair.\n    /// If inputs are invalid, stores TAG_UNSET, which will prevent\n    /// the cached page from being used during checkpoint.\n    pub fn set_wal_tag(&self, frame: u64, epoch: u32) {\n        // use only first 20 bits for seq (max: 1048576)\n        let e = epoch & EPOCH_MAX;\n        self.get()\n            .wal_tag\n            .store(pack_tag_pair(frame, e), Ordering::Release);\n    }\n\n    #[inline]\n    /// Load the (frame, seq) pair from the packed tag.\n    pub fn wal_tag_pair(&self) -> (u64, u32) {\n        unpack_tag_pair(self.get().wal_tag.load(Ordering::Acquire))\n    }\n\n    #[inline]\n    pub fn clear_wal_tag(&self) {\n        self.get().wal_tag.store(TAG_UNSET, Ordering::Release)\n    }\n\n    #[inline]\n    /// Returns true if the page has a valid WAL tag (i.e., was written to WAL and not modified since).\n    /// Returns false if the wal_tag is TAG_UNSET (page was modified since last WAL write).\n    pub fn has_wal_tag(&self) -> bool {\n        let tag = self.get().wal_tag.load(Ordering::Acquire);\n        let result = tag != TAG_UNSET && tag != TAG_WRITE_PENDING;\n        tracing::debug!(\n            \"has_wal_tag(page={}) = {} (tag={:x})\",\n            self.get().id,\n            result,\n            tag\n        );\n        result\n    }\n\n    #[inline]\n    /// Mark page as having a WAL write in progress.\n    /// This is set before starting a spill/cacheflush so we can detect\n    /// if the page was modified during the write.\n    pub fn set_write_pending(&self) {\n        tracing::debug!(\"set_write_pending(page={})\", self.get().id);\n        self.get()\n            .wal_tag\n            .store(TAG_WRITE_PENDING, Ordering::Release);\n    }\n\n    #[inline]\n    /// Try to set the WAL tag, but only if the page wasn't modified during the write.\n    /// Returns true if the tag was set, false if the page was modified (wal_tag became TAG_UNSET).\n    pub fn try_set_wal_tag(&self, frame: u64, epoch: u32) -> bool {\n        let new_tag = pack_tag_pair(frame, epoch);\n        let page_id = self.get().id;\n        let current = self.get().wal_tag.load(Ordering::Acquire);\n        // Only set if current tag is not TAG_UNSET (meaning page wasn't modified during write)\n        // TAG_WRITE_PENDING is fine, it means the write was in progress and page wasn't modified\n        if current == TAG_UNSET {\n            tracing::debug!(\n                \"try_set_wal_tag(page={}, frame={}) SKIPPED: wal_tag is TAG_UNSET (page was modified)\",\n                page_id, frame\n            );\n            return false;\n        }\n        tracing::debug!(\n            \"try_set_wal_tag(page={}, frame={}) SUCCESS: current={:x}\",\n            page_id,\n            frame,\n            current\n        );\n        self.get().wal_tag.store(new_tag, Ordering::Release);\n        true\n    }\n\n    #[inline]\n    pub fn is_valid_for_checkpoint(&self, target_frame: u64, epoch: u32) -> bool {\n        let (f, s) = self.wal_tag_pair();\n        f == target_frame && s == epoch && !self.is_dirty() && self.is_loaded() && !self.is_locked()\n    }\n}\n\n#[derive(Clone, Copy, Debug, PartialEq)]\n/// The state of the current pager cache commit.\nenum CommitState {\n    /// Prepare WAL header for commit if needed\n    PrepareWal,\n    /// Sync WAL header after prepare\n    PrepareWalSync,\n    /// Get DB size (mostly from page cache - but in rare cases we can read it from disk)\n    GetDbSize,\n    /// Scan all dirty pages and issue concurrent reads for evicted (spilled) pages.\n    ScanAndIssueReads { db_size: u32 },\n    /// Wait for all batched reads of evicted pages to complete.\n    WaitBatchedReads { db_size: u32 },\n    /// Collect pages (now all available) and prepare WAL frames.\n    PrepareFrames { db_size: u32 },\n    /// All frames prepared, writes are in flight\n    WaitWrites,\n    /// Writes are complete, wait for WAL sync to complete\n    WaitSync,\n    /// Wait for WAL sync to complete and finalize the WAL commit.\n    /// After this state, the write transaction is durable.\n    /// If autocheckpoint is enabled and the autocheckpoint threshold is reached, checkpoint will be attempted.\n    WalCommitDone,\n    /// Checkpoint the WAL to the database file (if needed).\n    /// This is decoupled from commit - checkpoint failure does not affect commit durability.\n    AutoCheckpoint,\n}\n\n#[derive(Debug, Default)]\nstruct CheckpointState {\n    phase: CheckpointPhase,\n    /// The checkpoint result, set after WAL checkpoint completes\n    result: Option<CheckpointResult>,\n    /// The checkpoint mode, used to determine if WAL truncation is needed\n    mode: Option<CheckpointMode>,\n}\n\n#[derive(Clone, Debug, Default, PartialEq)]\nenum CheckpointPhase {\n    #[default]\n    NotCheckpointing,\n    Checkpoint {\n        mode: CheckpointMode,\n        sync_mode: crate::SyncMode,\n        clear_page_cache: bool,\n    },\n    /// Truncate the database file if everything was backfilled and file is larger than expected.\n    TruncateDbFile {\n        sync_mode: crate::SyncMode,\n        clear_page_cache: bool,\n        /// Whether we've invalidated page 1 from cache (needed because checkpoint may write\n        /// pages directly from WALto DB file, so cached page 1 of the checkpointer connection may have stale database_size)\n        page1_invalidated: bool,\n    },\n    /// Sync the database file after checkpoint (if sync_mode != Off and we backfilled any frames from the WAL).\n    SyncDbFile { clear_page_cache: bool },\n    /// Truncate the WAL file after DB file is safely synced (only for TRUNCATE checkpoint mode).\n    /// This must happen AFTER SyncDbFile to ensure data durability.\n    TruncateWalFile { clear_page_cache: bool },\n    /// Finalize: release guard and optionally clear page cache.\n    Finalize { clear_page_cache: bool },\n}\n\n/// The mode of allocating a btree page.\n/// SQLite defines the following:\n/// #define BTALLOC_ANY   0           /* Allocate any page */\n/// #define BTALLOC_EXACT 1           /* Allocate exact page if possible */\n/// #define BTALLOC_LE    2           /* Allocate any page <= the parameter */\npub enum BtreePageAllocMode {\n    /// Allocate any btree page\n    Any,\n    /// Allocate a specific page number, typically used for root page allocation\n    Exact(u32),\n    /// Allocate a page number less than or equal to the parameter\n    Le(u32),\n}\n\n/// This will keep track of the state of current cache commit in order to not repeat work\nstruct CommitInfo {\n    completions: Vec<Completion>,\n    completion_group: Option<Completion>,\n    state: CommitState,\n    collected_pages: Vec<PageRef>,\n    page_sources: Vec<PageSource>,\n    page_source_cursor: usize,\n    prepared_frames: Vec<PreparedFrames>,\n}\n\n/// Represents a dirty page that will be committed to the log.\nenum PageSource {\n    /// Cache resident page\n    Cached(usize),\n    /// A page read from disk because it was spilled/evicted from cache\n    Evicted(PageRef),\n}\n\nimpl CommitInfo {\n    fn reset(&mut self) {\n        self.completions.clear();\n        self.completion_group = None;\n        self.state = CommitState::PrepareWal;\n        self.collected_pages.clear();\n        self.page_sources.clear();\n        self.prepared_frames.clear();\n        self.page_source_cursor = 0;\n    }\n\n    /// Clear and reserve space for n pages in each vector.\n    fn initialize(&mut self, n: usize) {\n        self.page_sources.clear();\n        self.page_sources.reserve(n.min(IOV_MAX));\n        self.completions.clear();\n        self.completions.reserve(n / 4);\n        self.completion_group = None;\n        self.collected_pages.reserve(n.min(IOV_MAX));\n    }\n}\n\n/// Track the state of the auto-vacuum mode.\n#[derive(Clone, Copy, Debug, PartialEq)]\npub enum AutoVacuumMode {\n    None,\n    Full,\n    Incremental,\n}\n\nimpl From<AutoVacuumMode> for u8 {\n    fn from(mode: AutoVacuumMode) -> u8 {\n        match mode {\n            AutoVacuumMode::None => 0,\n            AutoVacuumMode::Full => 1,\n            AutoVacuumMode::Incremental => 2,\n        }\n    }\n}\n\nimpl From<u8> for AutoVacuumMode {\n    fn from(value: u8) -> AutoVacuumMode {\n        match value {\n            0 => AutoVacuumMode::None,\n            1 => AutoVacuumMode::Full,\n            2 => AutoVacuumMode::Incremental,\n            _ => unreachable!(\"Invalid AutoVacuumMode value: {}\", value),\n        }\n    }\n}\n\n#[derive(Debug, Clone)]\n#[cfg(not(feature = \"omit_autovacuum\"))]\nenum PtrMapGetState {\n    Start,\n    Deserialize {\n        ptrmap_page: PageRef,\n        offset_in_ptrmap_page: usize,\n    },\n}\n\n#[derive(Debug, Clone)]\n#[cfg(not(feature = \"omit_autovacuum\"))]\nenum PtrMapPutState {\n    Start,\n    Deserialize {\n        ptrmap_page: PageRef,\n        offset_in_ptrmap_page: usize,\n    },\n}\n\n#[derive(Debug, Clone)]\nenum HeaderRefState {\n    Start,\n    CreateHeader {\n        page: PageRef,\n        completion: Option<Completion>,\n    },\n}\n\n#[cfg(not(feature = \"omit_autovacuum\"))]\n#[derive(Debug, Clone, Copy)]\nenum BtreeCreateVacuumFullState {\n    Start,\n    AllocatePage { root_page_num: u32 },\n    PtrMapPut { allocated_page_id: u32 },\n}\n\n#[derive(Debug, Clone)]\nenum SavepointKind {\n    Statement,\n    Named {\n        name: String,\n        starts_transaction: bool,\n    },\n}\n\n#[derive(Clone, Copy, Debug)]\npub enum SavepointResult {\n    /// Releasing the named savepoint should commit the surrounding transaction.\n    Commit,\n    /// The named savepoint was released without committing the transaction.\n    Release,\n    /// No matching named savepoint exists.\n    NotFound,\n}\n\n#[derive(Debug, Clone)]\nstruct SavepointSnapshot {\n    kind: SavepointKind,\n    start_offset: u64,\n    db_size: u32,\n    wal_max_frame: u64,\n    wal_checksum: (u32, u32),\n    deferred_fk_violations: isize,\n}\n\nstruct Savepoint {\n    kind: SavepointKind,\n    /// Start offset of this savepoint in the subjournal.\n    start_offset: AtomicU64,\n    /// Current write offset in the subjournal.\n    write_offset: AtomicU64,\n    /// Bitmap of page numbers that are dirty in the savepoint.\n    page_bitmap: RwLock<RoaringBitmap>,\n    /// Database size at the start of the savepoint.\n    /// If the database grows during the savepoint and a rollback to the savepoint is performed,\n    /// the pages exceeding the database size at the start of the savepoint will be ignored.\n    db_size: AtomicU32,\n    /// We might want to rollback.\n    /// WAL max frame at the start of the savepoint.\n    wal_max_frame: AtomicU64,\n    /// WAL checksum at the start of the savepoint.\n    wal_checksum: RwLock<(u32, u32)>,\n    /// Deferred FK counter value at the start of this savepoint.\n    deferred_fk_violations: AtomicIsize,\n}\n\nimpl Savepoint {\n    fn new(\n        kind: SavepointKind,\n        subjournal_offset: u64,\n        db_size: u32,\n        wal_max_frame: u64,\n        wal_checksum: (u32, u32),\n        deferred_fk_violations: isize,\n    ) -> Self {\n        Self {\n            kind,\n            start_offset: AtomicU64::new(subjournal_offset),\n            write_offset: AtomicU64::new(subjournal_offset),\n            page_bitmap: RwLock::new(RoaringBitmap::new()),\n            db_size: AtomicU32::new(db_size),\n            wal_max_frame: AtomicU64::new(wal_max_frame),\n            wal_checksum: RwLock::new(wal_checksum),\n            deferred_fk_violations: AtomicIsize::new(deferred_fk_violations),\n        }\n    }\n\n    pub fn add_dirty_page(&self, page_num: u32) {\n        self.page_bitmap.write().insert(page_num);\n    }\n\n    pub fn has_dirty_page(&self, page_num: u32) -> bool {\n        self.page_bitmap.read().contains(page_num)\n    }\n\n    fn start_offset(&self) -> u64 {\n        self.start_offset.load(Ordering::Acquire)\n    }\n\n    fn write_offset(&self) -> u64 {\n        self.write_offset.load(Ordering::Acquire)\n    }\n\n    fn set_write_offset(&self, offset: u64) {\n        self.write_offset.store(offset, Ordering::Release);\n    }\n\n    fn snapshot(&self) -> SavepointSnapshot {\n        SavepointSnapshot {\n            kind: self.kind.clone(),\n            start_offset: self.start_offset(),\n            db_size: self.db_size.load(Ordering::Acquire),\n            wal_max_frame: self.wal_max_frame.load(Ordering::Acquire),\n            wal_checksum: *self.wal_checksum.read(),\n            deferred_fk_violations: self.deferred_fk_violations.load(Ordering::Acquire),\n        }\n    }\n\n    fn from_snapshot(snapshot: SavepointSnapshot) -> Self {\n        Self {\n            kind: snapshot.kind,\n            start_offset: AtomicU64::new(snapshot.start_offset),\n            write_offset: AtomicU64::new(snapshot.start_offset),\n            page_bitmap: RwLock::new(RoaringBitmap::new()),\n            db_size: AtomicU32::new(snapshot.db_size),\n            wal_max_frame: AtomicU64::new(snapshot.wal_max_frame),\n            wal_checksum: RwLock::new(snapshot.wal_checksum),\n            deferred_fk_violations: AtomicIsize::new(snapshot.deferred_fk_violations),\n        }\n    }\n}\n\n/// The pager interface implements the persistence layer by providing access\n/// to pages of the database file, including caching, concurrency control, and\n/// transaction management.\npub struct Pager {\n    /// Source of the database pages.\n    pub db_file: Arc<dyn DatabaseStorage>,\n    /// The write-ahead log (WAL) for the database.\n    /// in-memory databases, ephemeral tables and ephemeral indexes do not have a WAL.\n    pub(crate) wal: Option<Arc<dyn Wal>>,\n    /// A page cache for the database.\n    page_cache: Arc<RwLock<PageCache>>,\n    /// Buffer pool for temporary data storage.\n    pub buffer_pool: Arc<BufferPool>,\n    /// I/O interface for input/output operations.\n    pub io: Arc<dyn crate::io::IO>,\n    /// Dirty pages as a bitmap, naturally sorted by page number.\n    dirty_pages: Arc<RwLock<RoaringBitmap>>,\n    subjournal: RwLock<Option<Subjournal>>,\n    savepoints: Arc<RwLock<Vec<Savepoint>>>,\n    commit_info: RwLock<CommitInfo>,\n    checkpoint_state: RwLock<CheckpointState>,\n    syncing: Arc<AtomicBool>,\n    auto_vacuum_mode: AtomicU8,\n    /// Mutex for synchronizing database initialization to prevent race conditions\n    init_lock: Arc<Mutex<()>>,\n    /// The state of the current allocate page operation.\n    allocate_page_state: RwLock<AllocatePageState>,\n    /// The state of the current allocate page1 operation.\n    allocate_page1_state: RwLock<AllocatePage1State>,\n    /// Cache page_size and reserved_space at Pager init and reuse for subsequent\n    /// `usable_space` calls. TODO: Invalidate reserved_space when we add the functionality\n    /// to change it.\n    pub(crate) page_size: AtomicU32,\n    reserved_space: AtomicU16,\n    /// Schema cookie cache.\n    ///\n    /// Note that schema cookie is 32-bits, but we use 64-bit field so we can\n    /// represent case where value is not set.\n    schema_cookie: AtomicU64,\n    free_page_state: RwLock<FreePageState>,\n    /// State machine for async cache spilling.\n    spill_state: RwLock<SpillState>,\n    /// State machine for async cacheflush operation.\n    cacheflush_state: RwLock<CacheFlushState>,\n    /// Maximum number of pages allowed in the database. Default is 1073741823 (SQLite default).\n    max_page_count: AtomicU32,\n    header_ref_state: RwLock<HeaderRefState>,\n    #[cfg(not(feature = \"omit_autovacuum\"))]\n    vacuum_state: RwLock<VacuumState>,\n    pub(crate) io_ctx: RwLock<IOContext>,\n    /// encryption is an opt-in feature. we will enable it only if the flag is passed\n    enable_encryption: AtomicBool,\n    /// In Memory Page 1 for Empty Dbs\n    init_page_1: Arc<ArcSwapOption<Page>>,\n    /// Sync type for durability. FullFsync uses F_FULLFSYNC on macOS (PRAGMA fullfsync).\n    /// Only stored on Apple platforms; on others, always returns Fsync.\n    #[cfg(target_vendor = \"apple\")]\n    sync_type: AtomicFileSyncType,\n}\n\nassert_send_sync!(Pager);\n\n#[cfg(not(feature = \"omit_autovacuum\"))]\npub struct VacuumState {\n    /// State machine for [Pager::ptrmap_get]\n    ptrmap_get_state: PtrMapGetState,\n    /// State machine for [Pager::ptrmap_put]\n    ptrmap_put_state: PtrMapPutState,\n    btree_create_vacuum_full_state: BtreeCreateVacuumFullState,\n}\n\n#[derive(Debug, Clone)]\nenum AllocatePageState {\n    Start,\n    /// Search the trunk page for an available free list leaf.\n    /// If none are found, there are two options:\n    /// - If there are no more trunk pages, the freelist is empty, so allocate a new page.\n    /// - If there are more trunk pages, use the current first trunk page as the new allocation,\n    ///   and set the next trunk page as the database's \"first freelist trunk page\".\n    SearchAvailableFreeListLeaf {\n        trunk_page: PageRef,\n    },\n    /// If a freelist leaf is found, reuse it for the page allocation and remove it from the trunk page.\n    ReuseFreelistLeaf {\n        trunk_page: PageRef,\n        leaf_page: PageRef,\n        number_of_freelist_leaves: u32,\n    },\n    /// If a suitable freelist leaf is not found, allocate an entirely new page.\n    AllocateNewPage {\n        current_db_size: u32,\n    },\n}\n\n#[derive(Clone)]\nenum AllocatePage1State {\n    Start,\n    Writing { page: PageRef },\n    Done,\n}\n\n#[derive(Debug, Clone)]\nenum FreePageState {\n    Start,\n    AddToTrunk { page: Arc<Page> },\n    NewTrunk { page: Arc<Page> },\n}\n\n/// State machine for async cache spilling.\n/// Tracks progress of writing dirty pages to WAL or disk.\n#[derive(Debug, Default, Clone)]\nenum SpillState {\n    #[default]\n    /// No spill operation in progress\n    Idle,\n    /// WAL spill in progress, waiting for write completions\n    WritingToWal {\n        /// Pinned pages being spilled\n        pages: Vec<PinGuard>,\n        /// Completions to wait for\n        completions: Vec<Completion>,\n    },\n    /// Writing ephemeral tables pages directly to disk\n    WritingToDisk {\n        /// Pages being spilled\n        pages: Vec<PinGuard>,\n        /// Completions to wait for\n        completions: Vec<Completion>,\n    },\n}\nenum CacheFlushStep {\n    /// Yield to caller with pending I/O, resume with given phase\n    Yield(CacheFlushState, IOCompletions),\n    /// Continue immediately to next phase (no I/O wait)\n    Continue(CacheFlushState),\n    /// Flush complete, return accumulated completions\n    Done(Vec<Completion>),\n}\n\n#[derive(Default)]\npub enum CacheFlushState {\n    #[default]\n    Init,\n    WalPrepareStart {\n        dirty_ids: Vec<usize>,\n        completion: Completion,\n    },\n    WalPrepareFinish {\n        dirty_ids: Vec<usize>,\n        completion: Completion,\n    },\n    Collecting(CollectingState),\n    WaitingForRead {\n        state: CollectingState,\n        page_id: usize,\n        page: PageRef,\n        completion: Completion,\n    },\n}\n\n#[derive(Default)]\npub struct CollectingState {\n    pub dirty_ids: Vec<usize>,\n    pub current_idx: usize,\n    pub collected_pages: Vec<PageRef>,\n    pub completions: Vec<Completion>,\n}\n\nimpl Pager {\n    pub fn new(\n        db_file: Arc<dyn DatabaseStorage>,\n        wal: Option<Arc<dyn Wal>>,\n        io: Arc<dyn crate::io::IO>,\n        page_cache: PageCache,\n        buffer_pool: Arc<BufferPool>,\n        init_lock: Arc<Mutex<()>>,\n        init_page_1: Arc<ArcSwapOption<Page>>,\n    ) -> Result<Self> {\n        let allocate_page1_state = if init_page_1.load().is_some() {\n            RwLock::new(AllocatePage1State::Start)\n        } else {\n            RwLock::new(AllocatePage1State::Done)\n        };\n        Ok(Self {\n            db_file,\n            wal,\n            page_cache: Arc::new(RwLock::new(page_cache)),\n            io,\n            dirty_pages: Arc::new(RwLock::new(RoaringBitmap::new())),\n            subjournal: RwLock::new(None),\n            savepoints: Arc::new(RwLock::new(Vec::new())),\n            commit_info: RwLock::new(CommitInfo {\n                completions: Vec::new(),\n                completion_group: None,\n                state: CommitState::PrepareWal,\n                collected_pages: Vec::new(),\n                prepared_frames: Vec::new(),\n                page_sources: Vec::new(),\n                page_source_cursor: 0,\n            }),\n            syncing: Arc::new(AtomicBool::new(false)),\n            checkpoint_state: RwLock::new(CheckpointState::default()),\n            buffer_pool,\n            auto_vacuum_mode: AtomicU8::new(AutoVacuumMode::None.into()),\n            init_lock,\n            allocate_page1_state,\n            page_size: AtomicU32::new(0), // 0 means not set\n            reserved_space: AtomicU16::new(RESERVED_SPACE_NOT_SET),\n            schema_cookie: AtomicU64::new(Self::SCHEMA_COOKIE_NOT_SET),\n            free_page_state: RwLock::new(FreePageState::Start),\n            spill_state: RwLock::new(SpillState::Idle),\n            cacheflush_state: RwLock::new(CacheFlushState::default()),\n            allocate_page_state: RwLock::new(AllocatePageState::Start),\n            max_page_count: AtomicU32::new(DEFAULT_MAX_PAGE_COUNT),\n            header_ref_state: RwLock::new(HeaderRefState::Start),\n            #[cfg(not(feature = \"omit_autovacuum\"))]\n            vacuum_state: RwLock::new(VacuumState {\n                ptrmap_get_state: PtrMapGetState::Start,\n                ptrmap_put_state: PtrMapPutState::Start,\n                btree_create_vacuum_full_state: BtreeCreateVacuumFullState::Start,\n            }),\n            io_ctx: RwLock::new(IOContext::default()),\n            enable_encryption: AtomicBool::new(false),\n            init_page_1,\n            #[cfg(target_vendor = \"apple\")]\n            sync_type: AtomicFileSyncType::new(FileSyncType::Fsync),\n        })\n    }\n\n    /// Get the sync type setting.\n    /// On non-Apple platforms, always returns Fsync (compile-time constant).\n    #[cfg(target_vendor = \"apple\")]\n    #[inline]\n    pub fn get_sync_type(&self) -> FileSyncType {\n        self.sync_type.get()\n    }\n\n    /// Get the sync type setting.\n    /// On non-Apple platforms, always returns Fsync (compile-time constant).\n    #[cfg(not(target_vendor = \"apple\"))]\n    #[inline]\n    pub fn get_sync_type(&self) -> FileSyncType {\n        FileSyncType::Fsync\n    }\n\n    /// Set the sync type (for PRAGMA fullfsync). Only effective on Apple platforms.\n    #[cfg(target_vendor = \"apple\")]\n    pub fn set_sync_type(&self, value: FileSyncType) {\n        self.sync_type.set(value);\n    }\n\n    /// Set the sync type. No-op on non-Apple platforms.\n    #[cfg(not(target_vendor = \"apple\"))]\n    pub fn set_sync_type(&self, _value: FileSyncType) {\n        // No-op: FullFsync only has effect on Apple platforms\n    }\n\n    pub fn init_page_1(&self) -> Arc<ArcSwapOption<Page>> {\n        self.init_page_1.clone()\n    }\n\n    /// Read page 1 (the database header page) using the header_ref_state state machine.\n    /// Used by HeaderRef and HeaderRefMut to avoid duplicating the page-loading logic.\n    fn read_header_page(&self) -> Result<IOResult<PageRef>> {\n        loop {\n            let state = self.header_ref_state.read().clone();\n            tracing::trace!(\"read_header_page - {:?}\", state);\n            match state {\n                HeaderRefState::Start => {\n                    // If db is not initialized, return the in-memory page\n                    if let Some(page1) = self.init_page_1.load_full() {\n                        return Ok(IOResult::Done(page1));\n                    }\n\n                    let (page, c) = self.read_page(DatabaseHeader::PAGE_ID as i64)?;\n                    *self.header_ref_state.write() = HeaderRefState::CreateHeader {\n                        page,\n                        completion: c.clone(),\n                    };\n                    if let Some(c) = c {\n                        io_yield_one!(c);\n                    }\n                }\n                HeaderRefState::CreateHeader { page, completion } => {\n                    // Check if the read failed (e.g., due to checksum/decryption error)\n                    if let Some(ref c) = completion {\n                        if let Some(err) = c.get_error() {\n                            *self.header_ref_state.write() = HeaderRefState::Start;\n                            return Err(err.into());\n                        }\n                    }\n                    turso_assert!(page.is_loaded(), \"page should be loaded\");\n                    turso_assert!(\n                        page.get().id == DatabaseHeader::PAGE_ID,\n                        \"incorrect header page id\"\n                    );\n                    *self.header_ref_state.write() = HeaderRefState::Start;\n                    return Ok(IOResult::Done(page));\n                }\n            }\n        }\n    }\n\n    /// Set whether cache spilling is enabled.\n    pub fn set_spill_enabled(&self, enabled: bool) {\n        self.page_cache.write().set_spill_enabled(enabled);\n    }\n    /// Get whether cache spilling is enabled.\n    pub fn get_spill_enabled(&self) -> bool {\n        self.page_cache.read().is_spill_enabled()\n    }\n\n    /// Open the subjournal if not yet open.\n    /// The subjournal is a file that is used to store the \"before images\" of pages for the\n    /// current savepoint. If the savepoint is rolled back, the pages can be restored from the subjournal.\n    ///\n    /// Currently uses MemoryIO, but should eventually be backed by temporary on-disk files.\n    pub fn open_subjournal(&self) -> Result<()> {\n        if self.subjournal.read().is_some() {\n            return Ok(());\n        }\n        use crate::MemoryIO;\n\n        let db_file_io = Arc::new(MemoryIO::new());\n        let file = db_file_io.open_file(\"subjournal\", OpenFlags::Create, false)?;\n        let db_file = Subjournal::new(file);\n        *self.subjournal.write() = Some(db_file);\n        Ok(())\n    }\n\n    /// Write page to subjournal if the current savepoint does not currently\n    /// contain an an entry for it. In case of a statement-level rollback,\n    /// the page image can be restored from the subjournal.\n    ///\n    /// A buffer of length page_size + 4 bytes is allocated and the page id\n    /// is written to the beginning of the buffer. The rest of the buffer is filled with the page contents.\n    pub fn subjournal_page_if_required(&self, page: &Page) -> Result<()> {\n        if self.subjournal.read().is_none() {\n            return Ok(());\n        }\n        let write_offset = {\n            let savepoints = self.savepoints.read();\n            let Some(cur_savepoint) = savepoints.last() else {\n                return Ok(());\n            };\n            // Skip subjournaling for pages that didn't exist when the savepoint was opened.\n            // New pages (allocated during this statement) can be \"rolled back\" by simply\n            // truncating back to the original db_size. This matches SQLite's subjRequiresPage()\n            // which checks: p->nOrig >= pgno.\n            let page_id_u32 = page.get().id as u32;\n            if page_id_u32 > cur_savepoint.db_size.load(Ordering::Acquire) {\n                return Ok(());\n            }\n            if cur_savepoint.has_dirty_page(page_id_u32) {\n                return Ok(());\n            }\n            cur_savepoint.write_offset.load(Ordering::SeqCst)\n        };\n        let page_id = page.get().id;\n        let page_size = self.page_size.load(Ordering::SeqCst) as usize;\n        let buffer = {\n            let page_id = page.get().id as u32;\n            let contents = page.get_contents();\n            let buffer = self.buffer_pool.allocate(page_size + 4);\n            let contents_buffer = contents.as_ptr();\n            turso_assert!(\n                contents_buffer.len() == page_size,\n                \"contents buffer length should be equal to page size\"\n            );\n\n            buffer.as_mut_slice()[0..4].copy_from_slice(&page_id.to_be_bytes());\n            buffer.as_mut_slice()[4..4 + page_size].copy_from_slice(contents_buffer);\n\n            Arc::new(buffer)\n        };\n\n        let savepoints = self.savepoints.clone();\n\n        let write_complete = {\n            let buf_copy = buffer.clone();\n            Box::new(move |res: Result<i32, CompletionError>| {\n                let Ok(bytes_written) = res else {\n                    return;\n                };\n                let buf_copy = buf_copy.clone();\n                let buf_len = buf_copy.len();\n\n                turso_assert!(\n                    bytes_written == buf_len as i32,\n                    \"wrote({bytes_written}) != expected({buf_len})\"\n                );\n\n                let savepoints = savepoints.read();\n                let cur_savepoint = savepoints.last().unwrap();\n                cur_savepoint.add_dirty_page(page_id as u32);\n                cur_savepoint\n                    .write_offset\n                    .fetch_add(page_size as u64 + 4, Ordering::SeqCst);\n            })\n        };\n        let c = Completion::new_write(write_complete);\n\n        let subjournal = self.subjournal.read();\n        let subjournal = subjournal.as_ref().unwrap();\n\n        let c = subjournal.write_page(write_offset, page_size, buffer, c)?;\n        turso_assert!(c.succeeded(), \"memory IO should complete immediately\");\n        Ok(())\n    }\n\n    /// try to \"acquire\" ownership on the subjournal of the connection-scoped pager\n    /// if another statement owns the subjournal - return Busy error and let the caller retry attempt later\n    pub fn try_use_subjournal(&self) -> Result<()> {\n        let subjournal = self.subjournal.read();\n        let subjournal = subjournal.as_ref().expect(\"subjournal must be opened\");\n        subjournal.try_use()\n    }\n\n    /// release ownership of the subjournal\n    /// caller must guarantee that [Self::stop_use_subjournal] is called only after successful call to the [Self::try_use_subjournal]\n    pub fn stop_use_subjournal(&self) {\n        let subjournal = self.subjournal.read();\n        let subjournal = subjournal.as_ref().expect(\"subjournal must be opened\");\n        subjournal.stop_use()\n    }\n\n    /// check if subjournal is in use for some statement\n    pub fn subjournal_in_use(&self) -> bool {\n        let subjournal = self.subjournal.read();\n        let Some(subjournal) = subjournal.as_ref() else {\n            return false;\n        };\n        subjournal.in_use()\n    }\n\n    pub fn open_savepoint(&self, db_size: u32) -> Result<()> {\n        self.open_savepoint_with_kind(SavepointKind::Statement, db_size, 0)\n    }\n\n    /// Release i.e. commit the current savepoint. This basically just means removing it.\n    pub fn release_savepoint(&self) -> Result<()> {\n        let mut savepoints = self.savepoints.write();\n        if !matches!(\n            savepoints.last().map(|savepoint| &savepoint.kind),\n            Some(SavepointKind::Statement)\n        ) {\n            return Ok(());\n        };\n        let savepoint = savepoints.pop().expect(\"savepoint must exist\");\n        if let Some(parent) = savepoints.last() {\n            parent.set_write_offset(savepoint.write_offset());\n        } else {\n            let subjournal = self.subjournal.read();\n            let Some(subjournal) = subjournal.as_ref() else {\n                return Ok(());\n            };\n            let c = subjournal.truncate(0)?;\n            turso_assert!(c.succeeded(), \"memory IO should complete immediately\");\n        }\n        Ok(())\n    }\n\n    /// Opens a named savepoint and captures rollback metadata for the current transaction state.\n    ///\n    /// If `starts_transaction` is true, releasing this savepoint at the root depth commits the\n    /// transaction.\n    pub fn open_named_savepoint(\n        &self,\n        name: String,\n        db_size: u32,\n        starts_transaction: bool,\n        deferred_fk_violations: isize,\n    ) -> Result<()> {\n        self.open_savepoint_with_kind(\n            SavepointKind::Named {\n                name,\n                starts_transaction,\n            },\n            db_size,\n            deferred_fk_violations,\n        )\n    }\n\n    /// Releases the newest matching named savepoint and all nested savepoints opened after it.\n    pub fn release_named_savepoint(&self, name: &str) -> Result<SavepointResult> {\n        let mut savepoints = self.savepoints.write();\n        let Some(target_idx) = savepoints.iter().rposition(|savepoint| {\n            matches!(\n                savepoint.kind,\n                SavepointKind::Named {\n                    name: ref savepoint_name,\n                    ..\n                } if savepoint_name == name\n            )\n        }) else {\n            return Ok(SavepointResult::NotFound);\n        };\n\n        let result = if matches!(\n            savepoints[target_idx].kind,\n            SavepointKind::Named {\n                starts_transaction: true,\n                ..\n            }\n        ) && target_idx == 0\n        {\n            SavepointResult::Commit\n        } else {\n            SavepointResult::Release\n        };\n        if matches!(result, SavepointResult::Commit) {\n            // Defer mutation until transaction commit succeeds. If commit fails\n            // (e.g. deferred FK violation), savepoints must remain intact.\n            return Ok(result);\n        }\n        let journal_end_offset = savepoints\n            .last()\n            .map(|savepoint| savepoint.write_offset())\n            .unwrap_or(0);\n\n        savepoints.truncate(target_idx);\n\n        if let Some(parent) = savepoints.last() {\n            parent.set_write_offset(journal_end_offset);\n        } else {\n            let subjournal = self.subjournal.read();\n            let Some(subjournal) = subjournal.as_ref() else {\n                return Ok(result);\n            };\n            let c = subjournal.truncate(0)?;\n            assert!(c.succeeded(), \"memory IO should complete immediately\");\n        }\n\n        Ok(result)\n    }\n\n    pub fn clear_savepoints(&self) -> Result<()> {\n        *self.savepoints.write() = Vec::new();\n        let subjournal = self.subjournal.read();\n        let Some(subjournal) = subjournal.as_ref() else {\n            return Ok(());\n        };\n        let c = subjournal.truncate(0)?;\n        turso_assert!(c.succeeded(), \"memory IO should complete immediately\");\n        Ok(())\n    }\n\n    /// Rollback to the newest savepoint. This basically just means reading the subjournal from the start offset\n    /// of the savepoint to the end of the subjournal and restoring the page images to the page cache.\n    pub fn rollback_to_newest_savepoint(&self) -> Result<bool> {\n        let mut savepoints = self.savepoints.write();\n        if !matches!(\n            savepoints.last().map(|savepoint| &savepoint.kind),\n            Some(SavepointKind::Statement)\n        ) {\n            return Ok(false);\n        }\n        let savepoint = savepoints.pop().expect(\"savepoint must exist\");\n        let journal_end_offset = savepoint.write_offset();\n        let savepoint = savepoint.snapshot();\n\n        self.rollback_to_snapshot(&savepoint, journal_end_offset)?;\n\n        if let Some(parent) = savepoints.last() {\n            parent.set_write_offset(savepoint.start_offset);\n        }\n\n        Ok(true)\n    }\n\n    /// Rollback to the newest matching named savepoint while keeping the named savepoint active.\n    ///\n    /// Returns deferred FK counter snapshot for the rolled-back savepoint.\n    pub fn rollback_to_named_savepoint(&self, name: &str) -> Result<Option<isize>> {\n        let target = {\n            let savepoints = self.savepoints.read();\n            let Some(target_idx) = savepoints.iter().rposition(|savepoint| {\n                matches!(\n                    savepoint.kind,\n                    SavepointKind::Named {\n                        name: ref savepoint_name,\n                        ..\n                    } if savepoint_name == name\n                )\n            }) else {\n                return Ok(None);\n            };\n            let journal_end_offset = savepoints\n                .last()\n                .map(|savepoint| savepoint.write_offset())\n                .unwrap_or_else(|| savepoints[target_idx].write_offset());\n            (\n                target_idx,\n                savepoints[target_idx].snapshot(),\n                journal_end_offset,\n            )\n        };\n\n        self.rollback_to_snapshot(&target.1, target.2)?;\n\n        let mut savepoints = self.savepoints.write();\n        let deferred_fk_violations = target.1.deferred_fk_violations;\n        savepoints.truncate(target.0);\n        if let Some(parent) = savepoints.last() {\n            parent.set_write_offset(target.1.start_offset);\n        }\n        savepoints.push(Savepoint::from_snapshot(target.1));\n\n        Ok(Some(deferred_fk_violations))\n    }\n\n    fn open_savepoint_with_kind(\n        &self,\n        kind: SavepointKind,\n        db_size: u32,\n        deferred_fk_violations: isize,\n    ) -> Result<()> {\n        let subjournal_offset = self\n            .savepoints\n            .read()\n            .last()\n            .map(|savepoint| savepoint.write_offset())\n            .unwrap_or(0);\n        let (wal_max_frame, wal_checksum) = if let Some(wal) = &self.wal {\n            (wal.get_max_frame(), wal.get_last_checksum())\n        } else {\n            (0, (0, 0))\n        };\n        let savepoint = Savepoint::new(\n            kind,\n            subjournal_offset,\n            db_size,\n            wal_max_frame,\n            wal_checksum,\n            deferred_fk_violations,\n        );\n        self.savepoints.write().push(savepoint);\n        Ok(())\n    }\n\n    fn rollback_to_snapshot(\n        &self,\n        savepoint: &SavepointSnapshot,\n        journal_end_offset: u64,\n    ) -> Result<()> {\n        let subjournal = self.subjournal.read();\n        let Some(subjournal) = subjournal.as_ref() else {\n            return Ok(());\n        };\n\n        let journal_start_offset = savepoint.start_offset;\n        let db_size = savepoint.db_size;\n\n        let mut rollback_bitset = RoaringBitmap::new();\n        let mut current_offset = journal_start_offset;\n        let page_size = self.page_size.load(Ordering::SeqCst) as u64;\n        let mut dirty_pages = self.dirty_pages.write();\n\n        while current_offset < journal_end_offset {\n            let page_id_buffer = Arc::new(self.buffer_pool.allocate(4));\n            let c = subjournal.read_page_number(current_offset, page_id_buffer.clone())?;\n            turso_assert!(c.succeeded(), \"memory IO should complete immediately\");\n            let page_id = u32::from_be_bytes(page_id_buffer.as_slice()[0..4].try_into().unwrap());\n            current_offset += 4;\n\n            if rollback_bitset.contains(page_id) {\n                current_offset += page_size;\n                continue;\n            }\n            if page_id > db_size {\n                dirty_pages.remove(page_id);\n                if let Some(page) = self\n                    .page_cache\n                    .write()\n                    .get(&PageCacheKey::new(page_id as usize))?\n                {\n                    page.clear_dirty();\n                    page.try_unpin();\n                }\n                current_offset += page_size;\n                rollback_bitset.insert(page_id);\n                continue;\n            }\n\n            let page_buffer = Arc::new(self.buffer_pool.allocate(page_size as usize));\n            let page = Arc::new(Page::new(page_id as i64));\n            let c = subjournal.read_page(\n                current_offset,\n                page_buffer,\n                page.clone(),\n                page_size as usize,\n            )?;\n            turso_assert!(c.succeeded(), \"memory IO should complete immediately\");\n            current_offset += page_size;\n            rollback_bitset.insert(page_id);\n            self.upsert_page_in_cache(page_id as usize, page, false)?;\n        }\n\n        let truncate_completion = subjournal.truncate(journal_start_offset)?;\n        turso_assert!(\n            truncate_completion.succeeded(),\n            \"memory IO should complete immediately\"\n        );\n\n        self.page_cache.write().truncate(db_size as usize)?;\n\n        if let Some(wal) = &self.wal {\n            wal.rollback(Some(RollbackTo {\n                frame: savepoint.wal_max_frame,\n                checksum: savepoint.wal_checksum,\n            }));\n        }\n\n        Ok(())\n    }\n\n    #[cfg(feature = \"test_helper\")]\n    pub fn get_pending_byte() -> u32 {\n        PENDING_BYTE.load(Ordering::Relaxed)\n    }\n\n    #[cfg(feature = \"test_helper\")]\n    /// Used in testing to allow for pending byte pages in smaller dbs\n    pub fn set_pending_byte(val: u32) {\n        PENDING_BYTE.store(val, Ordering::Relaxed);\n    }\n\n    #[cfg(not(feature = \"test_helper\"))]\n    pub const fn get_pending_byte() -> u32 {\n        PENDING_BYTE\n    }\n\n    /// From SQLITE: https://github.com/sqlite/sqlite/blob/7e38287da43ea3b661da3d8c1f431aa907d648c9/src/btreeInt.h#L608 \\\n    /// The database page the [PENDING_BYTE] occupies. This page is never used.\n    pub fn pending_byte_page_id(&self) -> Option<u32> {\n        // PENDING_BYTE_PAGE(pBt)  ((Pgno)((PENDING_BYTE/((pBt)->pageSize))+1))\n        let page_size = self.page_size.load(Ordering::SeqCst);\n        Self::get_pending_byte()\n            .checked_div(page_size)\n            .map(|val| val + 1)\n    }\n\n    /// Get the maximum page count for this database\n    pub fn get_max_page_count(&self) -> u32 {\n        self.max_page_count.load(Ordering::SeqCst)\n    }\n\n    /// Set the maximum page count for this database\n    /// Returns the new maximum page count (may be clamped to current database size)\n    pub fn set_max_page_count(&self, new_max: u32) -> crate::Result<IOResult<u32>> {\n        // Get current database size\n        let current_page_count =\n            return_if_io!(self.with_header(|header| header.database_size.get()));\n\n        // Clamp new_max to be at least the current database size\n        let clamped_max = std::cmp::max(new_max, current_page_count);\n        self.max_page_count.store(clamped_max, Ordering::SeqCst);\n        Ok(IOResult::Done(clamped_max))\n    }\n\n    pub fn set_wal(&mut self, wal: Arc<dyn Wal>) {\n        wal.set_io_context(self.io_ctx.read().clone());\n        self.wal = Some(wal);\n    }\n\n    pub fn get_auto_vacuum_mode(&self) -> AutoVacuumMode {\n        self.auto_vacuum_mode.load(Ordering::SeqCst).into()\n    }\n\n    pub fn set_auto_vacuum_mode(&self, mode: AutoVacuumMode) {\n        self.auto_vacuum_mode.store(mode.into(), Ordering::SeqCst);\n    }\n\n    /// Retrieves the pointer map entry for a given database page.\n    /// `target_page_num` (1-indexed) is the page whose entry is sought.\n    /// Returns `Ok(None)` if the page is not supposed to have a ptrmap entry (e.g. header, or a ptrmap page itself).\n    #[cfg(not(feature = \"omit_autovacuum\"))]\n    pub fn ptrmap_get(&self, target_page_num: u32) -> Result<IOResult<Option<PtrmapEntry>>> {\n        loop {\n            let ptrmap_get_state = {\n                let vacuum_state = self.vacuum_state.read();\n                vacuum_state.ptrmap_get_state.clone()\n            };\n            match ptrmap_get_state {\n                PtrMapGetState::Start => {\n                    tracing::trace!(\"ptrmap_get(page_idx = {})\", target_page_num);\n                    let configured_page_size =\n                        return_if_io!(self.with_header(|header| header.page_size)).get() as usize;\n\n                    if target_page_num < FIRST_PTRMAP_PAGE_NO\n                        || is_ptrmap_page(target_page_num, configured_page_size)\n                    {\n                        return Ok(IOResult::Done(None));\n                    }\n\n                    let ptrmap_pg_no =\n                        get_ptrmap_page_no_for_db_page(target_page_num, configured_page_size);\n                    let offset_in_ptrmap_page = get_ptrmap_offset_in_page(\n                        target_page_num,\n                        ptrmap_pg_no,\n                        configured_page_size,\n                    )?;\n                    tracing::trace!(\n                        \"ptrmap_get(page_idx = {}) = ptrmap_pg_no = {}\",\n                        target_page_num,\n                        ptrmap_pg_no\n                    );\n\n                    let (ptrmap_page, c) = self.read_page(ptrmap_pg_no as i64)?;\n                    self.vacuum_state.write().ptrmap_get_state = PtrMapGetState::Deserialize {\n                        ptrmap_page,\n                        offset_in_ptrmap_page,\n                    };\n                    if let Some(c) = c {\n                        io_yield_one!(c);\n                    }\n                }\n                PtrMapGetState::Deserialize {\n                    ptrmap_page,\n                    offset_in_ptrmap_page,\n                } => {\n                    turso_assert!(ptrmap_page.is_loaded(), \"ptrmap_page should be loaded\");\n                    let page_content = ptrmap_page.get_contents();\n                    let ptrmap_pg_no = page_content.id;\n\n                    let full_buffer_slice: &[u8] = page_content.as_ptr();\n\n                    // Ptrmap pages are not page 1, so their internal offset within their buffer should be 0.\n                    // The actual page data starts at page_content.offset() within the full_buffer_slice.\n                    if ptrmap_pg_no != 1 && page_content.offset() != 0 {\n                        return Err(LimboError::Corrupt(format!(\n                            \"Ptrmap page {} has unexpected internal offset {}\",\n                            ptrmap_pg_no,\n                            page_content.offset()\n                        )));\n                    }\n                    let ptrmap_page_data_slice: &[u8] = &full_buffer_slice[page_content.offset()..];\n                    let actual_data_length = ptrmap_page_data_slice.len();\n\n                    // Check if the calculated offset for the entry is within the bounds of the actual page data length.\n                    if offset_in_ptrmap_page + PTRMAP_ENTRY_SIZE > actual_data_length {\n                        return Err(LimboError::InternalError(format!(\n                        \"Ptrmap offset {offset_in_ptrmap_page} + entry size {PTRMAP_ENTRY_SIZE} out of bounds for page {ptrmap_pg_no} (actual data len {actual_data_length})\"\n                    )));\n                    }\n\n                    let entry_slice = &ptrmap_page_data_slice\n                        [offset_in_ptrmap_page..offset_in_ptrmap_page + PTRMAP_ENTRY_SIZE];\n                    self.vacuum_state.write().ptrmap_get_state = PtrMapGetState::Start;\n                    break match PtrmapEntry::deserialize(entry_slice) {\n                        Some(entry) => Ok(IOResult::Done(Some(entry))),\n                        None => Err(LimboError::Corrupt(format!(\n                            \"Failed to deserialize ptrmap entry for page {target_page_num} from ptrmap page {ptrmap_pg_no}\"\n                        ))),\n                    };\n                }\n            }\n        }\n    }\n\n    /// Writes or updates the pointer map entry for a given database page.\n    /// `db_page_no_to_update` (1-indexed) is the page whose entry is to be set.\n    /// `entry_type` and `parent_page_no` define the new entry.\n    #[cfg(not(feature = \"omit_autovacuum\"))]\n    pub fn ptrmap_put(\n        &self,\n        db_page_no_to_update: u32,\n        entry_type: PtrmapType,\n        parent_page_no: u32,\n    ) -> Result<IOResult<()>> {\n        tracing::trace!(\n            \"ptrmap_put(page_idx = {}, entry_type = {:?}, parent_page_no = {})\",\n            db_page_no_to_update,\n            entry_type,\n            parent_page_no\n        );\n        loop {\n            let ptrmap_put_state = {\n                let vacuum_state = self.vacuum_state.read();\n                vacuum_state.ptrmap_put_state.clone()\n            };\n            match ptrmap_put_state {\n                PtrMapPutState::Start => {\n                    let page_size =\n                        return_if_io!(self.with_header(|header| header.page_size)).get() as usize;\n\n                    if db_page_no_to_update < FIRST_PTRMAP_PAGE_NO\n                        || is_ptrmap_page(db_page_no_to_update, page_size)\n                    {\n                        turso_soft_unreachable!(\"Cannot set ptrmap entry for header/ptrmap page or invalid page\", { \"page\": db_page_no_to_update });\n                        return Err(LimboError::InternalError(format!(\n                        \"Cannot set ptrmap entry for page {db_page_no_to_update}: it's a header/ptrmap page or invalid.\"\n                    )));\n                    }\n\n                    let ptrmap_pg_no =\n                        get_ptrmap_page_no_for_db_page(db_page_no_to_update, page_size);\n                    let offset_in_ptrmap_page =\n                        get_ptrmap_offset_in_page(db_page_no_to_update, ptrmap_pg_no, page_size)?;\n                    tracing::trace!(\n                        \"ptrmap_put(page_idx = {}, entry_type = {:?}, parent_page_no = {}) = ptrmap_pg_no = {}, offset_in_ptrmap_page = {}\",\n                        db_page_no_to_update,\n                        entry_type,\n                        parent_page_no,\n                        ptrmap_pg_no,\n                        offset_in_ptrmap_page\n                    );\n\n                    let (ptrmap_page, c) = self.read_page(ptrmap_pg_no as i64)?;\n                    self.vacuum_state.write().ptrmap_put_state = PtrMapPutState::Deserialize {\n                        ptrmap_page,\n                        offset_in_ptrmap_page,\n                    };\n                    if let Some(c) = c {\n                        io_yield_one!(c);\n                    }\n                }\n                PtrMapPutState::Deserialize {\n                    ptrmap_page,\n                    offset_in_ptrmap_page,\n                } => {\n                    turso_assert!(ptrmap_page.is_loaded(), \"page should be loaded\");\n                    self.add_dirty(&ptrmap_page)?;\n                    let page_content = ptrmap_page.get_contents();\n                    let ptrmap_pg_no = page_content.id;\n\n                    let full_buffer_slice = page_content.as_ptr();\n\n                    if offset_in_ptrmap_page + PTRMAP_ENTRY_SIZE > full_buffer_slice.len() {\n                        return Err(LimboError::InternalError(format!(\n                        \"Ptrmap offset {} + entry size {} out of bounds for page {} (actual data len {})\",\n                        offset_in_ptrmap_page,\n                        PTRMAP_ENTRY_SIZE,\n                        ptrmap_pg_no,\n                        full_buffer_slice.len()\n                    )));\n                    }\n\n                    let entry = PtrmapEntry {\n                        entry_type,\n                        parent_page_no,\n                    };\n                    entry.serialize(\n                        &mut full_buffer_slice\n                            [offset_in_ptrmap_page..offset_in_ptrmap_page + PTRMAP_ENTRY_SIZE],\n                    )?;\n\n                    turso_assert!(\n                        ptrmap_page.get().id == ptrmap_pg_no,\n                        \"ptrmap page has unexpected number\"\n                    );\n                    self.vacuum_state.write().ptrmap_put_state = PtrMapPutState::Start;\n                    break Ok(IOResult::Done(()));\n                }\n            }\n        }\n    }\n\n    /// This method is used to allocate a new root page for a btree, both for tables and indexes\n    /// FIXME: handle no room in page cache\n    #[instrument(skip_all, level = Level::DEBUG)]\n    pub fn btree_create(&self, flags: &CreateBTreeFlags) -> Result<IOResult<u32>> {\n        let page_type = match flags {\n            _ if flags.is_table() => PageType::TableLeaf,\n            _ if flags.is_index() => PageType::IndexLeaf,\n            _ => unreachable!(\"Invalid flags state\"),\n        };\n        #[cfg(feature = \"omit_autovacuum\")]\n        {\n            let page = return_if_io!(self.do_allocate_page(page_type, 0, BtreePageAllocMode::Any));\n            Ok(IOResult::Done(page.get().id as u32))\n        }\n\n        //  If autovacuum is enabled, we need to allocate a new page number that is greater than the largest root page number\n        #[cfg(not(feature = \"omit_autovacuum\"))]\n        {\n            let auto_vacuum_mode =\n                AutoVacuumMode::from(self.auto_vacuum_mode.load(Ordering::SeqCst));\n            match auto_vacuum_mode {\n                AutoVacuumMode::None => {\n                    let page =\n                        return_if_io!(self.do_allocate_page(page_type, 0, BtreePageAllocMode::Any));\n                    Ok(IOResult::Done(page.get().id as u32))\n                }\n                AutoVacuumMode::Full => {\n                    loop {\n                        let btree_create_vacuum_full_state = {\n                            let vacuum_state = self.vacuum_state.read();\n                            vacuum_state.btree_create_vacuum_full_state\n                        };\n                        match btree_create_vacuum_full_state {\n                            BtreeCreateVacuumFullState::Start => {\n                                let (mut root_page_num, page_size) = return_if_io!(self\n                                    .with_header(|header| {\n                                        (\n                                            header.vacuum_mode_largest_root_page.get(),\n                                            header.page_size.get(),\n                                        )\n                                    }));\n\n                                turso_assert_greater_than!(root_page_num, 0, \"Largest root page number cannot be 0 because that is set to 1 when creating the database with autovacuum enabled\");\n                                root_page_num += 1;\n                                turso_assert_greater_than_or_equal!(\n                                    root_page_num,\n                                    FIRST_PTRMAP_PAGE_NO,\n                                    \"can never be less than 2 because we have already incremented\"\n                                );\n\n                                while is_ptrmap_page(root_page_num, page_size as usize) {\n                                    root_page_num += 1;\n                                }\n                                turso_assert_greater_than_or_equal!(\n                                    root_page_num,\n                                    3,\n                                    \"root page must be >= 3 (number of the first root page)\"\n                                );\n                                self.vacuum_state.write().btree_create_vacuum_full_state =\n                                    BtreeCreateVacuumFullState::AllocatePage { root_page_num };\n                            }\n                            BtreeCreateVacuumFullState::AllocatePage { root_page_num } => {\n                                //  root_page_num here is the desired root page\n                                let page = return_if_io!(self.do_allocate_page(\n                                    page_type,\n                                    0,\n                                    BtreePageAllocMode::Exact(root_page_num),\n                                ));\n                                let allocated_page_id = page.get().id as u32;\n\n                                return_if_io!(self.with_header_mut(|header| {\n                                    if allocated_page_id\n                                        > header.vacuum_mode_largest_root_page.get()\n                                    {\n                                        tracing::debug!(\n                                            \"Updating largest root page in header from {} to {}\",\n                                            header.vacuum_mode_largest_root_page.get(),\n                                            allocated_page_id\n                                        );\n                                        header.vacuum_mode_largest_root_page =\n                                            allocated_page_id.into();\n                                    }\n                                }));\n\n                                if allocated_page_id != root_page_num {\n                                    //  TODO(Zaid): Handle swapping the allocated page with the desired root page\n                                }\n\n                                //  TODO(Zaid): Update the header metadata to reflect the new root page number\n                                self.vacuum_state.write().btree_create_vacuum_full_state =\n                                    BtreeCreateVacuumFullState::PtrMapPut { allocated_page_id };\n                            }\n                            BtreeCreateVacuumFullState::PtrMapPut { allocated_page_id } => {\n                                //  For now map allocated_page_id since we are not swapping it with root_page_num\n                                return_if_io!(self.ptrmap_put(\n                                    allocated_page_id,\n                                    PtrmapType::RootPage,\n                                    0,\n                                ));\n                                self.vacuum_state.write().btree_create_vacuum_full_state =\n                                    BtreeCreateVacuumFullState::Start;\n                                return Ok(IOResult::Done(allocated_page_id));\n                            }\n                        }\n                    }\n                }\n                AutoVacuumMode::Incremental => {\n                    return Err(LimboError::InternalError(\n                        \"Incremental auto-vacuum is not supported\".to_string(),\n                    ));\n                }\n            }\n        }\n    }\n\n    /// Allocate a new overflow page.\n    /// This is done when a cell overflows and new space is needed.\n    // FIXME: handle no room in page cache\n    pub fn allocate_overflow_page(&self) -> Result<IOResult<PageRef>> {\n        let page = return_if_io!(self.allocate_page());\n        tracing::debug!(\"Pager::allocate_overflow_page(id={})\", page.get().id);\n\n        // setup overflow page\n        let contents = page.get_contents();\n        let buf = contents.as_ptr();\n        buf.fill(0);\n\n        Ok(IOResult::Done(page))\n    }\n\n    /// Allocate a new page to the btree via the pager.\n    /// This marks the page as dirty and writes the page header.\n    // FIXME: handle no room in page cache\n    pub fn do_allocate_page(\n        &self,\n        page_type: PageType,\n        offset: usize,\n        _alloc_mode: BtreePageAllocMode,\n    ) -> Result<IOResult<PageRef>> {\n        let page = return_if_io!(self.allocate_page());\n        #[cfg(debug_assertions)]\n        turso_assert_eq!(\n            offset,\n            page.get_contents().offset(),\n            \"offset doesn't match computed offset for page\"\n        );\n        btree_init_page(&page, page_type, offset, self.usable_space());\n        tracing::debug!(\n            \"do_allocate_page(id={}, page_type={:?})\",\n            page.get().id,\n            page.get_contents().page_type().ok()\n        );\n        Ok(IOResult::Done(page))\n    }\n\n    /// The \"usable size\" of a database page is the page size specified by the 2-byte integer at offset 16\n    /// in the header, minus the \"reserved\" space size recorded in the 1-byte integer at offset 20 in the header.\n    /// The usable size of a page might be an odd number. However, the usable size is not allowed to be less than 480.\n    /// In other words, if the page size is 512, then the reserved space size cannot exceed 32.\n    pub fn usable_space(&self) -> usize {\n        let page_size = self.get_page_size().unwrap_or_else(|| {\n            let size = self\n                .io\n                .block(|| self.with_header(|header| header.page_size))\n                .unwrap_or_default();\n            self.page_size.store(size.get(), Ordering::SeqCst);\n            size\n        });\n\n        let reserved_space = self.get_reserved_space().unwrap_or_else(|| {\n            let space = if self.db_initialized() {\n                self.io\n                    .block(|| self.with_header(|header| header.reserved_space))\n                    .unwrap_or_default()\n            } else {\n                // Before page 1 is allocated, the in-memory bootstrap header may still carry\n                // reserved_space=0. Use IOContext so checksum/encryption-required tail bytes are\n                // respected when computing usable space for first writes.\n                self.io_ctx.read().get_reserved_space_bytes()\n            };\n            self.set_reserved_space(space);\n            space\n        });\n\n        (page_size.get() as usize) - (reserved_space as usize)\n    }\n\n    pub fn db_initialized(&self) -> bool {\n        self.init_page_1.load().is_none()\n    }\n\n    /// Set the initial page size for the database. Should only be called before the database is initialized\n    pub fn set_initial_page_size(&self, size: PageSize) -> Result<()> {\n        turso_assert!(!self.db_initialized());\n        let IOResult::Done(_) = self.with_header_mut(|header| {\n            header.page_size = size;\n        })?\n        else {\n            panic!(\"DB should not be initialized and should not do any IO\");\n        };\n        self.page_size.store(size.get(), Ordering::SeqCst);\n        // Clear dirty pages since this is pre-initialization setup, not a real write transaction.\n        // with_header_mut marks page 1 dirty as a side effect, but no transaction is active.\n        self.dirty_pages.write().clear();\n        Ok(())\n    }\n\n    /// Get the current page size. Returns None if not set yet.\n    pub fn get_page_size(&self) -> Option<PageSize> {\n        let value = self.page_size.load(Ordering::SeqCst);\n        if value == 0 {\n            None\n        } else {\n            PageSize::new(value)\n        }\n    }\n\n    /// Get the current page size, panicking if not set.\n    pub fn get_page_size_unchecked(&self) -> PageSize {\n        let value = self.page_size.load(Ordering::SeqCst);\n        turso_assert_ne!(value, 0);\n        PageSize::new(value).expect(\"invalid page size stored\")\n    }\n\n    /// Set the page size. Used internally when page size is determined.\n    pub fn set_page_size(&self, size: PageSize) {\n        self.page_size.store(size.get(), Ordering::SeqCst);\n    }\n\n    /// Get the current reserved space. Returns None if not set yet.\n    pub fn get_reserved_space(&self) -> Option<u8> {\n        let value = self.reserved_space.load(Ordering::SeqCst);\n        if value == RESERVED_SPACE_NOT_SET {\n            None\n        } else {\n            Some(value as u8)\n        }\n    }\n\n    /// Set the reserved space. Must fit in u8.\n    pub fn set_reserved_space(&self, space: u8) {\n        self.reserved_space.store(space as u16, Ordering::SeqCst);\n    }\n\n    /// Schema cookie sentinel value that represents value not set.\n    const SCHEMA_COOKIE_NOT_SET: u64 = u64::MAX;\n\n    /// Get the cached schema cookie. Returns None if not set yet.\n    pub fn get_schema_cookie_cached(&self) -> Option<u32> {\n        let value = self.schema_cookie.load(Ordering::SeqCst);\n        if value == Self::SCHEMA_COOKIE_NOT_SET {\n            None\n        } else {\n            Some(value as u32)\n        }\n    }\n\n    /// Set the schema cookie cache.\n    pub fn set_schema_cookie(&self, cookie: Option<u32>) {\n        let value = cookie.map_or(Self::SCHEMA_COOKIE_NOT_SET, |v| v as u64);\n        self.schema_cookie.store(value, Ordering::SeqCst);\n    }\n\n    /// Get the schema cookie, using the cached value if available to avoid reading page 1.\n    pub fn get_schema_cookie(&self) -> Result<IOResult<u32>> {\n        // Try to use cached value first\n        if let Some(cookie) = self.get_schema_cookie_cached() {\n            return Ok(IOResult::Done(cookie));\n        }\n        // If not cached, read from header and cache it\n        self.with_header(|header| header.schema_cookie.get())\n    }\n\n    #[inline(always)]\n    #[instrument(skip_all, level = Level::DEBUG)]\n    pub fn begin_read_tx(&self) -> Result<()> {\n        let Some(wal) = self.wal.as_ref() else {\n            return Ok(());\n        };\n        let changed = wal.begin_read_tx()?;\n        if changed {\n            // Someone else changed the database -> assume our page cache is invalid (this is default SQLite behavior, we can probably do better with more granular invalidation)\n            self.clear_page_cache(false);\n            // Invalidate cached schema cookie to force re-read on next access\n            self.set_schema_cookie(None);\n        }\n        Ok(())\n    }\n\n    /// MVCC-only: refresh connection-private WAL change counters without starting a read tx and invalidate cache if needed.\n    pub fn mvcc_refresh_if_db_changed(&self) {\n        let Some(wal) = self.wal.as_ref() else {\n            return;\n        };\n        if wal.mvcc_refresh_if_db_changed() {\n            // Prevents stale page cache reads after MVCC checkpoints update the DB file.\n            self.clear_page_cache(false);\n            self.set_schema_cookie(None);\n        }\n    }\n\n    #[instrument(skip_all, level = Level::DEBUG)]\n    pub fn maybe_allocate_page1(&self) -> Result<IOResult<()>> {\n        if !self.db_initialized() {\n            if let Some(_lock) = self.init_lock.try_lock() {\n                return Ok(self.allocate_page1()?.map(|_| ()));\n            }\n            // Give a chance for the allocation to happen elsewhere\n            io_yield_one!(Completion::new_yield());\n        }\n        Ok(IOResult::Done(()))\n    }\n\n    #[inline(always)]\n    #[instrument(skip_all, level = Level::DEBUG)]\n    pub fn begin_write_tx(&self) -> Result<IOResult<()>> {\n        // TODO(Diego): The only possibly allocate page1 here is because OpenEphemeral needs a write transaction\n        // we should have a unique API to begin transactions, something like sqlite3BtreeBeginTrans\n        return_if_io!(self.maybe_allocate_page1());\n        let Some(wal) = self.wal.as_ref() else {\n            return Ok(IOResult::Done(()));\n        };\n        Ok(IOResult::Done(wal.begin_write_tx()?))\n    }\n\n    /// commit dirty pages from current transaction in WAL mode if this is not nested statement (for nested statements, parent will do the commit)\n    /// if update_transaction_state set to false, then [Connection::transaction_state] left unchanged\n    /// if update_transaction_state set to true, then [Connection::transaction_state] reset to [TransactionState::None] in case when method completes without error\n    #[instrument(skip_all, level = Level::DEBUG)]\n    pub fn commit_tx(\n        &self,\n        connection: &Connection,\n        update_transaction_state: bool,\n    ) -> Result<IOResult<()>> {\n        if connection.is_nested_stmt() {\n            // Parent statement will handle the transaction commit.\n            return Ok(IOResult::Done(()));\n        }\n        let Some(wal) = self.wal.as_ref() else {\n            // TODO: Unsure what the semantics of \"end_tx\" is for in-memory databases, ephemeral tables and ephemeral indexes.\n            self.clear_savepoints()?;\n            return Ok(IOResult::Done(()));\n        };\n\n        let complete_commit = || {\n            if update_transaction_state {\n                connection.set_tx_state(TransactionState::None);\n            }\n            self.commit_dirty_pages_end();\n        };\n\n        loop {\n            let commit_state = self.commit_info.read().state;\n            tracing::debug!(\"commit_state: {:?}\", commit_state);\n            // we separate auto-checkpoint from the commit in order for checkpoint to be able to backfill WAL till the end\n            // (including new frames from current transaction)\n            // otherwise, we will be unable to do WAL restart\n            match commit_state {\n                CommitState::AutoCheckpoint => {\n                    let checkpoint_result = self.checkpoint(\n                        CheckpointMode::Passive {\n                            upper_bound_inclusive: None,\n                        },\n                        connection.get_sync_mode(),\n                        false,\n                    );\n                    match checkpoint_result {\n                        Ok(IOResult::IO(io)) => return Ok(IOResult::IO(io)),\n                        Ok(IOResult::Done(_)) => complete_commit(),\n                        Err(err) => {\n                            tracing::info!(\"auto-checkpoint failed: {err}\");\n                            complete_commit();\n                            self.cleanup_after_auto_checkpoint_failure();\n                        }\n                    }\n                    self.clear_savepoints()?;\n                    return Ok(IOResult::Done(()));\n                }\n                _ => {\n                    return_if_io!(self.commit_dirty_pages(\n                        connection.is_wal_auto_checkpoint_disabled(),\n                        connection.get_sync_mode(),\n                        connection.get_data_sync_retry(),\n                    ));\n\n                    let schema_did_change = match connection.get_tx_state() {\n                        TransactionState::Write { schema_did_change } => schema_did_change,\n                        _ => false,\n                    };\n\n                    wal.end_write_tx();\n                    wal.end_read_tx();\n                    // we do not set TransactionState::None here - because caller can decide that nothing should be done for this connection\n                    // and skip next calls of the commit_tx methods after IO\n\n                    tracing::debug!(\"commit_tx: schema_did_change={schema_did_change}\");\n                    if schema_did_change {\n                        let schema = connection.schema.read().clone();\n                        connection.db.update_schema_if_newer(schema);\n                    }\n\n                    if self.commit_info.read().state != CommitState::AutoCheckpoint {\n                        complete_commit();\n                        self.clear_savepoints()?;\n                        return Ok(IOResult::Done(()));\n                    }\n                }\n            }\n        }\n    }\n\n    #[instrument(skip_all, level = Level::DEBUG)]\n    pub fn rollback_tx(&self, connection: &Connection) {\n        if connection.is_nested_stmt() {\n            // Parent statement will handle the transaction rollback.\n            return;\n        }\n        let Some(wal) = self.wal.as_ref() else {\n            // TODO: Unsure what the semantics of \"end_tx\" is for in-memory databases, ephemeral tables and ephemeral indexes.\n            return;\n        };\n        let (is_write, schema_did_change) = match connection.get_tx_state() {\n            TransactionState::Write { schema_did_change } => (true, schema_did_change),\n            _ => (false, false),\n        };\n        tracing::trace!(\"rollback_tx(schema_did_change={})\", schema_did_change);\n        if is_write {\n            self.clear_savepoints()\n                .expect(\"in practice, clear_savepoints() should never fail as it uses memory IO\");\n            // IMPORTANT: rollback() must be called BEFORE end_write_tx() releases the write_lock.\n            // Otherwise, another thread could commit new frames to frame_cache between\n            // end_write_tx() and rollback(), and rollback() would incorrectly remove them.\n            self.rollback(schema_did_change, connection, is_write);\n            wal.end_write_tx();\n        } else {\n            self.rollback(schema_did_change, connection, is_write);\n        }\n        wal.end_read_tx();\n    }\n\n    #[instrument(skip_all, level = Level::DEBUG)]\n    pub fn end_read_tx(&self) {\n        let Some(wal) = self.wal.as_ref() else {\n            return;\n        };\n        wal.end_read_tx();\n    }\n\n    /// End just the write transaction on the WAL, without affecting the read lock.\n    pub fn end_write_tx(&self) {\n        let Some(wal) = self.wal.as_ref() else {\n            return;\n        };\n        wal.end_write_tx();\n    }\n\n    /// Returns true if this pager's WAL currently holds a read lock.\n    pub fn holds_read_lock(&self) -> bool {\n        let Some(wal) = self.wal.as_ref() else {\n            return false;\n        };\n        wal.holds_read_lock()\n    }\n\n    pub fn holds_write_lock(&self) -> bool {\n        let Some(wal) = self.wal.as_ref() else {\n            return false;\n        };\n        wal.holds_write_lock()\n    }\n\n    /// Rollback and clean up an attached database pager's transaction.\n    /// Unlike rollback_tx, this doesn't modify connection-level state.\n    pub fn rollback_attached(&self) {\n        let Some(wal) = self.wal.as_ref() else {\n            return;\n        };\n        let is_write = wal.holds_write_lock();\n        if is_write {\n            self.clear_savepoints()\n                .expect(\"clear_savepoints should not fail for attached DB\");\n            // Clear dirty pages and page cache before releasing the write lock\n            self.clear_page_cache(true);\n            self.dirty_pages.write().clear();\n            self.reset_internal_states();\n            self.set_schema_cookie(None);\n            wal.rollback(None);\n            wal.end_write_tx();\n        } else {\n            // For read-only transactions, pager state machines (e.g. header_ref_state)\n            // can be left in intermediate states if an IO completion was aborted.\n            // Reset them so the next query on this attached DB starts clean.\n            self.reset_internal_states();\n        }\n        if wal.holds_read_lock() {\n            wal.end_read_tx();\n        }\n    }\n\n    /// Reads a page from disk (either WAL or DB file) bypassing page-cache\n    #[tracing::instrument(skip_all, level = Level::DEBUG)]\n    pub fn read_page_no_cache(\n        &self,\n        page_idx: i64,\n        frame_watermark: Option<u64>,\n        allow_empty_read: bool,\n    ) -> Result<(PageRef, Completion)> {\n        turso_assert_greater_than_or_equal!(page_idx, 0);\n        tracing::debug!(\"read_page_no_cache(page_idx = {})\", page_idx);\n        let page = Arc::new(Page::new(page_idx));\n        let io_ctx = self.io_ctx.read();\n        let Some(wal) = self.wal.as_ref() else {\n            turso_assert!(\n                matches!(frame_watermark, Some(0) | None),\n                \"frame_watermark must be either None or Some(0) because DB has no WAL and read with other watermark is invalid\"\n            );\n\n            page.set_locked();\n            let c = self.begin_read_disk_page(\n                page_idx as usize,\n                page.clone(),\n                allow_empty_read,\n                &io_ctx,\n            )?;\n            return Ok((page, c));\n        };\n\n        if let Some(frame_id) = wal.find_frame(page_idx as u64, frame_watermark)? {\n            let c = wal.read_frame(frame_id, page.clone(), self.buffer_pool.clone())?;\n            // TODO(pere) should probably first insert to page cache, and if successful,\n            // read frame or page\n            return Ok((page, c));\n        }\n\n        let c =\n            self.begin_read_disk_page(page_idx as usize, page.clone(), allow_empty_read, &io_ctx)?;\n        Ok((page, c))\n    }\n\n    /// Reads a page from the database.\n    #[tracing::instrument(skip_all, level = Level::TRACE)]\n    pub fn read_page(&self, page_idx: i64) -> Result<(PageRef, Option<Completion>)> {\n        turso_assert_greater_than_or_equal!(page_idx, 0, \"pages in pager should be positive, negative might indicate unallocated pages from mvcc or any other nasty bug\");\n        tracing::debug!(\"read_page(page_idx = {})\", page_idx);\n\n        // First check if page is in cache\n        {\n            let mut page_cache = self.page_cache.write();\n            let page_key = PageCacheKey::new(page_idx as usize);\n            if let Some(page) = page_cache.get(&page_key)? {\n                turso_assert!(\n                    page_idx as usize == page.get().id,\n                    \"attempted to read page but got different page\",\n                    { \"expected_page\": page_idx, \"actual_page\": page.get().id }\n                );\n                return Ok((page, None));\n            }\n        }\n\n        tracing::debug!(\"read_page(page_idx = {page_idx}) = reading page from disk\");\n        // Page not in cache, read from disk\n        let (page, c) = self.read_page_no_cache(page_idx, None, false)?;\n        loop {\n            match self.cache_insert(page_idx as usize, page.clone())? {\n                IOResult::Done(()) => {\n                    return Ok((page, Some(c)));\n                }\n                IOResult::IO(IOCompletions::Single(spill_c)) => {\n                    // NOTE: Because `cache_insert` can return completions as *multiple* different states, we cannot\n                    // simply create a new CompletionGroup and return it here without inserting the\n                    // page into the cache. In order to do this, we would need to make read_page\n                    // re-entrant so it continues to call cache_insert and have every caller\n                    // propogate the IOResult. For now, we will wait syncronously for spilling IO\n                    // on cache insertion on read_page.\n                    self.io.wait_for_completion(spill_c)?;\n                }\n            }\n        }\n    }\n\n    fn begin_read_disk_page(\n        &self,\n        page_idx: usize,\n        page: PageRef,\n        allow_empty_read: bool,\n        io_ctx: &IOContext,\n    ) -> Result<Completion> {\n        sqlite3_ondisk::begin_read_page(\n            self.db_file.as_ref(),\n            self.buffer_pool.clone(),\n            page,\n            page_idx,\n            allow_empty_read,\n            io_ctx,\n        )\n    }\n\n    /// Insert a page into the cache, with spilling support.\n    /// This handles cache full conditions by spilling dirty pages and retrying.\n    fn cache_insert(&self, page_idx: usize, page: PageRef) -> Result<IOResult<()>> {\n        {\n            let mut page_cache = self.page_cache.write();\n            let page_key = PageCacheKey::new(page_idx);\n            match page_cache.insert(page_key, page.clone()) {\n                Ok(_) => return Ok(IOResult::Done(())),\n                Err(CacheError::KeyExists) => {\n                    unreachable!(\"Page should not exist in cache after get() miss\");\n                }\n                Err(CacheError::Full) => {\n                    // Fall through to spilling\n                }\n                Err(e) => return Err(e.into()),\n            }\n        }\n\n        match self.try_spill_dirty_pages()? {\n            IOResult::Done(true) => {\n                let mut page_cache = self.page_cache.write();\n                let page_key = PageCacheKey::new(page_idx);\n                match page_cache.insert(page_key, page) {\n                    Ok(_) => Ok(IOResult::Done(())),\n                    Err(CacheError::KeyExists) => Ok(IOResult::Done(())),\n                    Err(e) => Err(e.into()),\n                }\n            }\n            IOResult::Done(false) => Err(LimboError::Busy),\n            IOResult::IO(c) => Ok(IOResult::IO(c)),\n        }\n    }\n\n    // Get a page from the cache, if it exists.\n    pub fn cache_get(&self, page_idx: usize) -> Result<Option<PageRef>> {\n        tracing::trace!(\"read_page(page_idx = {})\", page_idx);\n        let mut page_cache = self.page_cache.write();\n        let page_key = PageCacheKey::new(page_idx);\n        page_cache.get(&page_key)\n    }\n\n    /// Get a page from cache only if it matches the target frame\n    pub fn cache_get_for_checkpoint(\n        &self,\n        page_idx: usize,\n        target_frame: u64,\n        seq: u32,\n    ) -> Result<Option<PageRef>> {\n        let mut page_cache = self.page_cache.write();\n        let page_key = PageCacheKey::new(page_idx);\n        let page = page_cache.get(&page_key)?.and_then(|page| {\n            if page.is_valid_for_checkpoint(target_frame, seq) {\n                tracing::debug!(\n                    \"cache_get_for_checkpoint: page {page_idx} frame {target_frame} is valid\",\n                );\n                Some(page)\n            } else {\n                tracing::trace!(\n                    \"cache_get_for_checkpoint: page {} has frame/tag {:?}: (dirty={}), need frame {} and seq {seq}\",\n                    page_idx,\n                    page.wal_tag_pair(),\n                    page.is_dirty(),\n                    target_frame\n                );\n                None\n            }\n        });\n        Ok(page)\n    }\n\n    /// Changes the size of the page cache.\n    pub fn change_page_cache_size(&self, capacity: usize) -> Result<CacheResizeResult> {\n        let mut page_cache = self.page_cache.write();\n        Ok(page_cache.resize(capacity))\n    }\n\n    pub fn add_dirty(&self, page: &Page) -> Result<()> {\n        turso_assert!(\n            page.is_loaded(),\n            \"page must be loaded in add_dirty() so its contents can be subjournaled\",\n            { \"page_id\": page.get().id }\n        );\n        self.subjournal_page_if_required(page)?;\n        let mut dirty_pages = self.dirty_pages.write();\n        dirty_pages.insert(page.get().id as u32);\n        // Notify cache before marking dirty (page was evictable, now it won't be)\n        // Only notify if page wasn't already dirty\n        if !page.is_dirty() {\n            let key = PageCacheKey::new(page.get().id);\n            self.page_cache.write().notify_page_dirty(key);\n        }\n        page.set_dirty();\n        Ok(())\n    }\n\n    pub fn wal_state(&self) -> Result<WalState> {\n        let Some(wal) = self.wal.as_ref() else {\n            turso_soft_unreachable!(\"wal_state() called on database without WAL\");\n            return Err(LimboError::InternalError(\n                \"wal_state() called on database without WAL\".to_string(),\n            ));\n        };\n        Ok(WalState {\n            checkpoint_seq_no: wal.get_checkpoint_seq(),\n            max_frame: wal.get_max_frame(),\n        })\n    }\n\n    /// Flush all dirty pages to disk (async/re-entrant).\n    /// Unlike commit_dirty_pages, this function does not commit, checkpoint nor sync the WAL/Database.\n    #[instrument(skip_all, level = Level::INFO)]\n    pub fn cacheflush(&self) -> Result<IOResult<Vec<Completion>>> {\n        let wal = self\n            .wal\n            .as_ref()\n            .ok_or_else(|| LimboError::InternalError(\"cacheflush() called without WAL\".into()))?;\n        let page_sz = self.get_page_size().unwrap_or_default();\n\n        loop {\n            let phase = std::mem::take(&mut *self.cacheflush_state.write());\n\n            match self.cacheflush_step(wal, page_sz, phase)? {\n                CacheFlushStep::Yield(next_phase, io) => {\n                    *self.cacheflush_state.write() = next_phase;\n                    return Ok(IOResult::IO(io));\n                }\n                CacheFlushStep::Continue(next_phase) => {\n                    *self.cacheflush_state.write() = next_phase;\n                }\n                CacheFlushStep::Done(completions) => {\n                    *self.cacheflush_state.write() = CacheFlushState::Init;\n                    return Ok(IOResult::Done(completions));\n                }\n            }\n        }\n    }\n\n    /// Executes one step of the cache flush state machine.\n    #[inline]\n    fn cacheflush_step(\n        &self,\n        wal: &Arc<dyn Wal>,\n        page_sz: PageSize,\n        phase: CacheFlushState,\n    ) -> Result<CacheFlushStep> {\n        match phase {\n            CacheFlushState::Init => self.cacheflush_init(wal, page_sz),\n            CacheFlushState::WalPrepareStart {\n                dirty_ids,\n                completion,\n            } => self.cacheflush_wal_prepare_start(wal, dirty_ids, completion),\n            CacheFlushState::WalPrepareFinish {\n                dirty_ids,\n                completion,\n            } => self.cacheflush_wal_prepare_finish(dirty_ids, completion),\n            CacheFlushState::Collecting(state) => self.cacheflush_collect(wal, page_sz, state),\n            CacheFlushState::WaitingForRead {\n                state,\n                page_id,\n                page,\n                completion,\n            } => self.cacheflush_handle_read(wal, page_sz, state, page_id, page, completion),\n        }\n    }\n\n    /// Init phase: gather dirty page IDs and begin WAL preparation.\n    fn cacheflush_init(&self, wal: &Arc<dyn Wal>, page_sz: PageSize) -> Result<CacheFlushStep> {\n        let dirty_ids: Vec<usize> = self.dirty_pages.read().iter().map(|x| x as usize).collect();\n\n        if dirty_ids.is_empty() {\n            return Ok(CacheFlushStep::Done(Vec::new()));\n        }\n\n        // Start WAL preparation\n        match wal.prepare_wal_start(page_sz)? {\n            Some(completion) => Ok(CacheFlushStep::Yield(\n                CacheFlushState::WalPrepareStart {\n                    dirty_ids,\n                    completion: completion.clone(),\n                },\n                IOCompletions::Single(completion),\n            )),\n            None => {\n                // No async prep needed, go straight to finish\n                let completion = wal.prepare_wal_finish(self.get_sync_type())?;\n                Ok(CacheFlushStep::Yield(\n                    CacheFlushState::WalPrepareFinish {\n                        dirty_ids,\n                        completion: completion.clone(),\n                    },\n                    IOCompletions::Single(completion),\n                ))\n            }\n        }\n    }\n\n    #[inline]\n    /// Wait for WAL prepare_start, then call prepare_finish.\n    fn cacheflush_wal_prepare_start(\n        &self,\n        wal: &Arc<dyn Wal>,\n        dirty_ids: Vec<usize>,\n        completion: Completion,\n    ) -> Result<CacheFlushStep> {\n        if !completion.succeeded() {\n            return Ok(CacheFlushStep::Yield(\n                CacheFlushState::WalPrepareStart {\n                    dirty_ids,\n                    completion: completion.clone(),\n                },\n                IOCompletions::Single(completion),\n            ));\n        }\n\n        let finish_completion = wal.prepare_wal_finish(self.get_sync_type())?;\n        Ok(CacheFlushStep::Yield(\n            CacheFlushState::WalPrepareFinish {\n                dirty_ids,\n                completion: finish_completion.clone(),\n            },\n            IOCompletions::Single(finish_completion),\n        ))\n    }\n\n    #[inline]\n    /// Wait for WAL prepare_finish, then start collecting pages.\n    fn cacheflush_wal_prepare_finish(\n        &self,\n        dirty_ids: Vec<usize>,\n        completion: Completion,\n    ) -> Result<CacheFlushStep> {\n        if !completion.succeeded() {\n            return Ok(CacheFlushStep::Yield(\n                CacheFlushState::WalPrepareFinish {\n                    dirty_ids,\n                    completion: completion.clone(),\n                },\n                IOCompletions::Single(completion),\n            ));\n        }\n\n        Ok(CacheFlushStep::Continue(CacheFlushState::Collecting(\n            CollectingState {\n                dirty_ids,\n                current_idx: 0,\n                collected_pages: Vec::new(),\n                completions: Vec::new(),\n            },\n        )))\n    }\n\n    #[inline]\n    /// Main collection loop: fetch pages from cache, handle evictions, write batches.\n    fn cacheflush_collect(\n        &self,\n        wal: &Arc<dyn Wal>,\n        page_sz: PageSize,\n        mut state: CollectingState,\n    ) -> Result<CacheFlushStep> {\n        while state.current_idx < state.dirty_ids.len() {\n            let page_id = state.dirty_ids[state.current_idx];\n            let cache_result = self.page_cache.write().get(&PageCacheKey::new(page_id))?;\n\n            match cache_result {\n                Some(page) => {\n                    trace!(\n                        \"cacheflush(page={}, page_type={:?})\",\n                        page_id,\n                        page.get_contents().page_type().ok()\n                    );\n                    state.collected_pages.push(page);\n                    state.current_idx += 1;\n                }\n                None => {\n                    // Page evicted, need async read from WAL\n                    trace!(\"cacheflush: page {} evicted, reading from WAL\", page_id);\n                    let (page, completion) =\n                        self.read_page_no_cache(page_id as i64, None, false)?;\n\n                    if !completion.succeeded() {\n                        return Ok(CacheFlushStep::Yield(\n                            CacheFlushState::WaitingForRead {\n                                state,\n                                page_id,\n                                page,\n                                completion: completion.clone(),\n                            },\n                            IOCompletions::Single(completion),\n                        ));\n                    }\n\n                    // Sync read completed immediately\n                    trace!(\n                        \"cacheflush(page={}, page_type={:?}) [re-read sync]\",\n                        page_id,\n                        page.get_contents().page_type().ok()\n                    );\n                    state.collected_pages.push(page);\n                    state.current_idx += 1;\n                }\n            }\n            if Self::should_flush_batch(&state) {\n                self.flush_page_batch(wal, page_sz, &mut state)?;\n            }\n        }\n        // All pages collected and written\n        Ok(CacheFlushStep::Done(state.completions))\n    }\n\n    /// Handle completion of async page read for evicted page.\n    fn cacheflush_handle_read(\n        &self,\n        wal: &Arc<dyn Wal>,\n        page_sz: PageSize,\n        mut state: CollectingState,\n        page_id: usize,\n        page: PageRef,\n        completion: Completion,\n    ) -> Result<CacheFlushStep> {\n        if !completion.succeeded() {\n            return Ok(CacheFlushStep::Yield(\n                CacheFlushState::WaitingForRead {\n                    state,\n                    page_id,\n                    page,\n                    completion: completion.clone(),\n                },\n                IOCompletions::Single(completion),\n            ));\n        }\n        trace!(\n            \"cacheflush(page={}, page_type={:?}) [re-read complete]\",\n            page_id,\n            page.get_contents().page_type().ok()\n        );\n        state.collected_pages.push(page);\n        state.current_idx += 1;\n        if Self::should_flush_batch(&state) {\n            self.flush_page_batch(wal, page_sz, &mut state)?;\n        }\n\n        Ok(CacheFlushStep::Continue(CacheFlushState::Collecting(state)))\n    }\n\n    #[inline]\n    fn should_flush_batch(state: &CollectingState) -> bool {\n        let at_capacity = state.collected_pages.len() == IOV_MAX;\n        let at_end = state.current_idx >= state.dirty_ids.len();\n        !state.collected_pages.is_empty() && (at_capacity || at_end)\n    }\n\n    /// Writes accumulated pages to WAL as a single vectored append.\n    #[inline]\n    fn flush_page_batch(\n        &self,\n        wal: &Arc<dyn Wal>,\n        page_sz: PageSize,\n        state: &mut CollectingState,\n    ) -> Result<()> {\n        let pages = std::mem::take(&mut state.collected_pages);\n        // Mark pages as write-pending to detect concurrent modifications\n        for page in &pages {\n            page.set_write_pending();\n        }\n        match wal.append_frames_vectored(pages, page_sz) {\n            Ok(completion) => {\n                state.completions.push(completion);\n                Ok(())\n            }\n            Err(e) => {\n                self.io.cancel(&state.completions)?;\n                self.io.drain()?;\n                Err(e)\n            }\n        }\n    }\n\n    /// Attempt to spill dirty pages from the cache to make room for new pages.\n    /// This is called when the cache reaches its spill threshold.\n    ///\n    /// For databases with a WAL: write only spillable dirty pages to WAL,\n    /// then mark them as spilled so they can be evicted even while dirty.\n    /// For ephemeral tables: writes pages directly to the temp database file.\n    #[instrument(skip_all, level = Level::DEBUG)]\n    pub fn try_spill_dirty_pages(&self) -> Result<IOResult<bool>> {\n        let state = self.spill_state.read().clone();\n        match state {\n            SpillState::Idle => {\n                // Check if spilling is needed\n                let spill_result = {\n                    let cache = self.page_cache.read();\n                    cache.check_spill(IOV_MAX)\n                };\n                match spill_result {\n                    SpillResult::NotNeeded | SpillResult::Disabled => {\n                        return Ok(IOResult::Done(false));\n                    }\n                    SpillResult::CacheFull => {\n                        tracing::debug!(\"try_spill_dirty_pages: cache full, no spillable pages\");\n                        return Ok(IOResult::Done(false));\n                    }\n                    SpillResult::PagesToSpill(pages) => {\n                        if pages.is_empty() {\n                            return Ok(IOResult::Done(false));\n                        }\n                        let page_count = pages.len();\n                        tracing::debug!(\"try_spill_dirty_pages: spilling {} pages\", page_count);\n                        if let Some(wal) = self.wal.as_ref() {\n                            let page_sz = self.get_page_size().unwrap_or_default();\n\n                            // Ensure WAL is initialized. Most of the time this is a no-op.\n                            let prepare = wal.prepare_wal_start(page_sz)?;\n                            if let Some(c) = prepare {\n                                self.io.wait_for_completion(c)?;\n                                let c = wal.prepare_wal_finish(self.get_sync_type())?;\n                                self.io.wait_for_completion(c)?;\n                            }\n\n                            let wal_pages: Vec<PageRef> = pages\n                                .iter()\n                                .map(|p| {\n                                    // Set write_pending on all pages before WAL write so callback can\n                                    // detect mid-write modifications.\n                                    p.set_write_pending();\n                                    p.to_page()\n                                })\n                                .collect();\n                            let c = wal.append_frames_vectored(wal_pages, page_sz)?;\n\n                            if c.succeeded() {\n                                // Synchronous completion, WAL tags already set by callback.\n                                {\n                                    let mut cache = self.page_cache.write();\n                                    for page in &pages {\n                                        if page.has_wal_tag() {\n                                            let key = PageCacheKey::new(page.get().id);\n                                            cache.notify_page_spilled(key);\n                                            page.set_spilled();\n                                        }\n                                    }\n                                }\n                                *self.spill_state.write() = SpillState::Idle;\n                                return Ok(IOResult::Done(true));\n                            }\n                            *self.spill_state.write() = SpillState::WritingToWal {\n                                pages,\n                                completions: vec![c.clone()],\n                            };\n                            io_yield_one!(c);\n                        } else {\n                            let mut group = CompletionGroup::new(|_| {});\n                            // Ephemeral table case: write directly to temp file\n                            for page in &pages {\n                                page.set_write_pending();\n                            }\n                            let completions = self.spill_pages_to_disk(&pages)?;\n                            if completions.is_empty() {\n                                self.finish_ephemeral_spill(&pages);\n                                return Ok(IOResult::Done(true));\n                            }\n                            for completion in &completions {\n                                group.add(completion);\n                            }\n                            *self.spill_state.write() = SpillState::WritingToDisk {\n                                pages,\n                                completions: completions.clone(),\n                            };\n                            io_yield_one!(group.build());\n                        }\n                    }\n                }\n            }\n            SpillState::WritingToWal { pages, completions } => {\n                for c in &completions {\n                    if !c.succeeded() {\n                        io_yield_one!(c.clone());\n                    }\n                }\n                // All I/O complete, pages are now in WAL.\n                // Mark spilled pages so they can be evicted while dirty.\n                // Only do so if page wasn't modified since write started (each page has valid wal_tag).\n                let mut spilled_count = 0;\n                {\n                    let mut cache = self.page_cache.write();\n                    for page in &pages {\n                        if page.has_wal_tag() {\n                            let key = PageCacheKey::new(page.get().id);\n                            cache.notify_page_spilled(key);\n                            page.set_spilled();\n                            spilled_count += 1;\n                        } else {\n                            // Page was modified during write, it will need to be re-spilled\n                            tracing::debug!(\n                                \"try_spill_dirty_pages: page {} modified during write, not marking as spilled\",\n                                page.get().id\n                            );\n                        }\n                    }\n                }\n                if spilled_count == 0 && !pages.is_empty() {\n                    tracing::warn!(\n                        \"try_spill_dirty_pages: no pages marked as spilled out of {}, all were modified during write\",\n                        pages.len()\n                    );\n                }\n                *self.spill_state.write() = SpillState::Idle;\n                trace!(\n                    \"try_spill_dirty_pages: successfully spilled {} / {} pages to WAL\",\n                    spilled_count,\n                    pages.len(),\n                );\n                return Ok(IOResult::Done(true));\n            }\n            SpillState::WritingToDisk { pages, completions } => {\n                let all_done = completions.iter().all(|c| c.succeeded());\n                if !all_done {\n                    for c in &completions {\n                        if !c.succeeded() {\n                            io_yield_one!(c.clone());\n                        }\n                    }\n                }\n                // All I/O complete, finish ephemeral spill\n                self.finish_ephemeral_spill(&pages);\n                *self.spill_state.write() = SpillState::Idle;\n                trace!(\n                    \"try_spill_dirty_pages: successfully spilled {} pages to disk\",\n                    pages.len()\n                );\n                return Ok(IOResult::Done(true));\n            }\n        }\n    }\n\n    /// Wait for any in-flight spill writes to finish.\n    /// This prevents publishing WAL metadata that references frames that are not yet durable.\n    fn wait_for_spill_completions(&self) -> Result<IOResult<()>> {\n        loop {\n            let state = self.spill_state.read().clone();\n            if matches!(state, SpillState::Idle) {\n                return Ok(IOResult::Done(()));\n            }\n            match self.try_spill_dirty_pages()? {\n                IOResult::Done(_) => continue,\n                IOResult::IO(c) => return Ok(IOResult::IO(c)),\n            }\n        }\n    }\n\n    /// Finish a spill operation for ephemeral tables\n    fn finish_ephemeral_spill(&self, pages: &[PinGuard]) {\n        for page in pages {\n            let tag = page.get().wal_tag.load(Ordering::Acquire);\n            // wal tag is set to TAG_UNSET when adding to dirty_pages, meaning that this\n            // page was dirtied after the spill started, so we don't clear the dirty flag in that case\n            if tag != TAG_UNSET {\n                page.clear_dirty();\n            }\n        }\n    }\n    /// Write a set of pages directly to the database file (for ephemeral tables without WAL).\n    /// This is used by try_spill_dirty_pages for ephemeral tables/indexes.\n    fn spill_pages_to_disk(&self, pages: &[PinGuard]) -> Result<Vec<Completion>> {\n        let mut completions: Vec<Completion> = Vec::with_capacity(pages.len());\n        for page in pages {\n            match begin_write_btree_page(self, &page.to_page()) {\n                Ok(c) => completions.push(c),\n                Err(e) => {\n                    self.io.cancel(&completions)?;\n                    self.io.drain()?;\n                    return Err(e);\n                }\n            }\n        }\n\n        Ok(completions)\n    }\n\n    /// Check if the cache needs spilling and attempt to spill if necessary.\n    /// This should be called before inserting new pages into the cache.\n    pub fn ensure_cache_space(&self) -> Result<IOResult<()>> {\n        let needs_spill = {\n            let cache = self.page_cache.read();\n            cache.needs_spill()\n        };\n\n        if needs_spill {\n            match self.try_spill_dirty_pages()? {\n                IOResult::Done(spilled) => {\n                    if spilled {\n                        // After spilling, try to evict clean pages to make room in the cache\n                        let mut cache = self.page_cache.write();\n                        if let Err(e) = cache.make_room_for(1) {\n                            // Cache is completely full with unevictable pages\n                            tracing::error!(\n                                \"ensure_cache_space: {e} cache full, could not make room\"\n                            );\n                            return Err(LimboError::CacheError(CacheError::Full));\n                        }\n                    }\n                }\n                IOResult::IO(completion) => {\n                    return Ok(IOResult::IO(completion));\n                }\n            }\n        }\n        Ok(IOResult::Done(()))\n    }\n\n    /// Flush all dirty pages to disk.\n    /// In the base case, it will write the dirty pages to the WAL and then fsync the WAL.\n    /// If the WAL size is over the checkpoint threshold, it will checkpoint the WAL to\n    /// the database file and then fsync the database file.\n    #[instrument(skip_all, level = Level::DEBUG)]\n    pub fn commit_dirty_pages(\n        &self,\n        wal_auto_checkpoint_disabled: bool,\n        sync_mode: SyncMode,\n        data_sync_retry: bool,\n    ) -> Result<IOResult<()>> {\n        {\n            let mut commit_info = self.commit_info.write();\n            if commit_info.state == CommitState::PrepareWal {\n                commit_info.reset();\n            }\n        }\n\n        // Wait for spill writes before publishing frames\n        if let IOResult::IO(c) = self.wait_for_spill_completions()? {\n            return Ok(IOResult::IO(c));\n        }\n\n        let result =\n            self.commit_dirty_pages_inner(wal_auto_checkpoint_disabled, sync_mode, data_sync_retry);\n        if result.is_err() {\n            self.commit_info.write().reset();\n        }\n        result\n    }\n\n    pub fn commit_dirty_pages_end(&self) {\n        self.commit_info.write().reset();\n    }\n\n    #[instrument(skip_all, level = Level::DEBUG)]\n    fn commit_dirty_pages_inner(\n        &self,\n        wal_auto_checkpoint_disabled: bool,\n        sync_mode: SyncMode,\n        data_sync_retry: bool,\n    ) -> Result<IOResult<()>> {\n        let Some(wal) = self.wal.as_ref() else {\n            turso_soft_unreachable!(\"commit_dirty_pages() called without WAL\");\n            return Err(LimboError::InternalError(\n                \"commit_dirty_pages() called without WAL\".into(),\n            ));\n        };\n\n        loop {\n            let state = self.commit_info.read().state;\n            trace!(?state);\n\n            match state {\n                CommitState::PrepareWal => {\n                    let page_sz = self.get_page_size_unchecked();\n                    let c = wal.prepare_wal_start(page_sz)?;\n                    let Some(c) = c else {\n                        self.commit_info.write().state = CommitState::GetDbSize;\n                        continue;\n                    };\n                    self.commit_info.write().state = CommitState::PrepareWalSync;\n                    if !c.succeeded() {\n                        io_yield_one!(c);\n                    }\n                }\n                CommitState::PrepareWalSync => {\n                    let c = wal.prepare_wal_finish(self.get_sync_type())?;\n                    self.commit_info.write().state = CommitState::GetDbSize;\n                    if !c.succeeded() {\n                        io_yield_one!(c);\n                    }\n                }\n                CommitState::GetDbSize => {\n                    let db_size = return_if_io!(self.with_header(|h| h.database_size));\n                    self.commit_info.write().state = CommitState::ScanAndIssueReads {\n                        db_size: db_size.get(),\n                    };\n                }\n                CommitState::ScanAndIssueReads { db_size } => {\n                    let mut commit_info = self.commit_info.write();\n                    let dirty_pages = self.dirty_pages.read();\n\n                    if dirty_pages.is_empty() {\n                        return Ok(IOResult::Done(()));\n                    }\n                    commit_info.initialize(dirty_pages.len() as usize);\n                    let mut cache = self.page_cache.write();\n\n                    for page_id in dirty_pages.iter() {\n                        let page_id = page_id as usize;\n                        let page_key = PageCacheKey::new(page_id);\n                        if cache.peek(&page_key, false).is_some() {\n                            commit_info.page_sources.push(PageSource::Cached(page_id));\n                        } else {\n                            let (page, completion) =\n                                self.read_page_no_cache(page_id as i64, None, false)?;\n                            commit_info.page_sources.push(PageSource::Evicted(page));\n                            if !completion.finished() {\n                                commit_info.completions.push(completion);\n                            }\n                        }\n                    }\n                    drop(cache);\n                    drop(dirty_pages);\n                    if !commit_info.completions.is_empty() {\n                        commit_info.state = CommitState::WaitBatchedReads { db_size };\n                        drop(commit_info);\n                        io_yield_one!(self.commit_completion());\n                    }\n                    commit_info.state = CommitState::PrepareFrames { db_size };\n                }\n                CommitState::WaitBatchedReads { db_size } => {\n                    let all_done = self\n                        .commit_info\n                        .read()\n                        .completions\n                        .iter()\n                        .all(|c| c.finished());\n                    if !all_done {\n                        io_yield_one!(self.commit_completion());\n                    }\n                    // Check for any read errors\n                    let mut commit_info = self.commit_info.write();\n                    let failed = commit_info\n                        .completions\n                        .iter()\n                        .find(|c| !c.succeeded())\n                        .cloned();\n                    if let Some(_failed) = failed {\n                        return Err(LimboError::CompletionError(CompletionError::IOError(\n                            std::io::ErrorKind::Other,\n                            \"read\",\n                        )));\n                    }\n                    // All reads complete and successful, proceed to frame preparation\n                    commit_info.completions.clear();\n                    commit_info.completion_group = None;\n                    commit_info.state = CommitState::PrepareFrames { db_size };\n                }\n                CommitState::PrepareFrames { db_size } => {\n                    let page_sz = self.get_page_size_unchecked();\n                    let mut commit_info = self.commit_info.write();\n                    let mut cache = self.page_cache.write();\n\n                    'inner: loop {\n                        let cursor = commit_info.page_source_cursor;\n                        if cursor >= commit_info.page_sources.len() {\n                            break 'inner;\n                        }\n\n                        let total = commit_info.page_sources.len();\n                        let is_last = cursor + 1 >= total;\n                        // Linear consumption, no lookup required\n                        let page = match &commit_info.page_sources[cursor] {\n                            PageSource::Cached(page_id) => {\n                                let page_key = PageCacheKey::new(*page_id);\n                                cache\n                                    .get(&page_key)?\n                                    .expect(\"page evicted between scan and prepare\")\n                            }\n                            PageSource::Evicted(page) => page.clone(),\n                        };\n                        commit_info.page_source_cursor += 1;\n                        commit_info.collected_pages.push(page);\n\n                        if commit_info.collected_pages.len() == IOV_MAX || is_last {\n                            self.prepare_collected_frames(\n                                &mut commit_info,\n                                wal,\n                                page_sz,\n                                db_size,\n                                is_last,\n                            )?;\n                        }\n                    }\n                    drop(cache);\n                    if commit_info.prepared_frames.is_empty() {\n                        turso_assert!(\n                            self.dirty_pages.read().is_empty(),\n                            \"dirty pages must be empty if no frames prepared\"\n                        );\n                        return Ok(IOResult::Done(()));\n                    }\n                    // Submit all WAL writes\n                    let wal_file = wal.wal_file()?;\n                    let mut batch = WriteBatch::new(wal_file);\n                    for prepared in &commit_info.prepared_frames {\n                        batch.writev(prepared.offset, &prepared.bufs);\n                    }\n                    commit_info.completions = batch.submit()?;\n                    commit_info.completion_group = None;\n                    commit_info.state = CommitState::WaitWrites;\n                }\n                CommitState::WaitWrites => {\n                    if !self\n                        .commit_info\n                        .read()\n                        .completions\n                        .iter()\n                        .all(|c| c.finished())\n                    {\n                        io_yield_one!(self.commit_completion());\n                    }\n                    // Check for any write errors\n                    let failed = self\n                        .commit_info\n                        .read()\n                        .completions\n                        .iter()\n                        .find(|c| !c.succeeded())\n                        .cloned();\n\n                    let mut commit_info = self.commit_info.write();\n                    if let Some(_failed) = failed {\n                        commit_info.completions.clear();\n                        commit_info.completion_group = None;\n                        commit_info.prepared_frames.clear();\n                        return Err(LimboError::CompletionError(CompletionError::IOError(\n                            std::io::ErrorKind::Other,\n                            \"write\",\n                        )));\n                    }\n                    commit_info.completions.clear();\n                    commit_info.completion_group = None;\n                    // Writes done, submit fsync if needed.\n                    // NORMAL mode skips fsync on WAL commit (but still fsyncs on checkpoint and wal restart).\n                    if sync_mode == SyncMode::Full {\n                        let sync_c = wal.sync(self.get_sync_type())?;\n                        // Reuse the existing Vec instead of allocating a new one\n                        commit_info.completions.push(sync_c);\n                        commit_info.state = CommitState::WaitSync;\n                    } else {\n                        commit_info.state = CommitState::WalCommitDone;\n                    }\n                }\n                // To protect against partial writes, we MUST ensure that all write Completions\n                // finish before submitting the fsync. It is possible that a partial write will\n                // cause an IO backend to resubmit the write (particularly with io_uring) and we\n                // cannot have the fsync submitted before all writes are fully done, even if\n                // they are IO_LINK'd together or we submit the fsync with IO_DRAIN, the only way\n                // to ensure durability in the case of partial writes is to ensure the pwritev\n                // completes before the fsync is submitted.\n                CommitState::WaitSync => {\n                    let sync_c = self.commit_info.read().completions[0].clone();\n                    // Wait for fsync to complete\n                    if !sync_c.finished() {\n                        io_yield_one!(sync_c);\n                    }\n                    // Check for fsync error as we might need to panic on data_sync_retry=off\n                    let mut commit_info = self.commit_info.write();\n                    if !sync_c.succeeded() {\n                        commit_info.completions.clear();\n                        commit_info.prepared_frames.clear();\n\n                        if !data_sync_retry {\n                            panic!(\n                                \"fsync error (data_sync_retry=off): {:?}\",\n                                sync_c.get_error()\n                            );\n                        }\n                        return Err(LimboError::CompletionError(CompletionError::IOError(\n                            std::io::ErrorKind::Other,\n                            \"sync\",\n                        )));\n                    }\n                    commit_info.completions.clear();\n                    commit_info.state = CommitState::WalCommitDone;\n                }\n                CommitState::WalCommitDone => {\n                    // all I/O complete, NOW it's safe to advance WAL state\n                    let mut commit_info = self.commit_info.write();\n                    wal.commit_prepared_frames(&commit_info.prepared_frames);\n                    wal.finalize_committed_pages(&commit_info.prepared_frames);\n                    wal.finish_append_frames_commit()?;\n                    self.dirty_pages.write().clear();\n                    commit_info.prepared_frames.clear();\n\n                    let need_checkpoint = !wal_auto_checkpoint_disabled && wal.should_checkpoint();\n                    if need_checkpoint {\n                        commit_info.state = CommitState::AutoCheckpoint;\n                    }\n                    return Ok(IOResult::Done(()));\n                }\n                CommitState::AutoCheckpoint => panic!(\"checkpoint must be handled externally\"),\n            }\n        }\n    }\n\n    /// Prepare collected pages as WAL frames without submitting I/O.\n    fn prepare_collected_frames(\n        &self,\n        commit_info: &mut CommitInfo,\n        wal: &Arc<dyn Wal>,\n        page_sz: PageSize,\n        db_size: u32,\n        is_commit_frame: bool,\n    ) -> Result<()> {\n        let pages = std::mem::take(&mut commit_info.collected_pages);\n        if pages.is_empty() {\n            return Ok(());\n        }\n        let commit_flag = if is_commit_frame { Some(db_size) } else { None };\n        for page in &pages {\n            page.set_write_pending();\n        }\n        // Chain from previous batch if any\n        let prev = commit_info.prepared_frames.last();\n        let prepared = wal.prepare_frames(&pages, page_sz, commit_flag, prev)?;\n        tracing::debug!(\"prepare_collected_frames: offset={}\", prepared.offset);\n        commit_info.prepared_frames.push(prepared);\n        Ok(())\n    }\n\n    fn commit_completion(&self) -> Completion {\n        let mut commit_info = self.commit_info.write();\n        if let Some(group) = &commit_info.completion_group {\n            return group.clone();\n        }\n        let mut group = CompletionGroup::new(|_| {});\n        for c in commit_info.completions.iter() {\n            group.add(c);\n        }\n        let result = group.build();\n        commit_info.completion_group = Some(result.clone());\n        result\n    }\n\n    #[instrument(skip_all, level = Level::DEBUG)]\n    pub fn wal_changed_pages_after(&self, frame_watermark: u64) -> Result<Vec<u32>> {\n        let wal = self.wal.as_ref().unwrap();\n        wal.changed_pages_after(frame_watermark)\n    }\n\n    #[instrument(skip_all, level = Level::DEBUG)]\n    pub fn wal_get_frame(&self, frame_no: u64, frame: &mut [u8]) -> Result<Completion> {\n        let Some(wal) = self.wal.as_ref() else {\n            turso_soft_unreachable!(\"wal_get_frame() called on database without WAL\");\n            return Err(LimboError::InternalError(\n                \"wal_get_frame() called on database without WAL\".to_string(),\n            ));\n        };\n        wal.read_frame_raw(frame_no, frame)\n    }\n\n    #[instrument(skip_all, level = Level::DEBUG)]\n    pub fn wal_insert_frame(&self, frame_no: u64, frame: &[u8]) -> Result<WalFrameInfo> {\n        let Some(wal) = self.wal.as_ref() else {\n            turso_soft_unreachable!(\"wal_insert_frame() called on database without WAL\");\n            return Err(LimboError::InternalError(\n                \"wal_insert_frame() called on database without WAL\".to_string(),\n            ));\n        };\n        let (header, raw_page) = parse_wal_frame_header(frame);\n\n        wal.write_frame_raw(\n            self.buffer_pool.clone(),\n            frame_no,\n            header.page_number as u64,\n            header.db_size as u64,\n            raw_page,\n            self.get_sync_type(),\n        )?;\n        if let Some(page) = self.cache_get(header.page_number as usize)? {\n            let content = page.get_contents();\n            content.as_ptr().copy_from_slice(raw_page);\n            turso_assert!(\n                page.get().id == header.page_number as usize,\n                \"page has unexpected id\"\n            );\n        }\n        if header.page_number == 1 {\n            let db_size = self\n                .io\n                .block(|| self.with_header(|header| header.database_size))?;\n            tracing::debug!(\"truncate page_cache as first page was written: {}\", db_size);\n            let mut page_cache = self.page_cache.write();\n            page_cache.truncate(db_size.get() as usize).map_err(|e| {\n                LimboError::InternalError(format!(\"Failed to truncate page cache: {e:?}\"))\n            })?;\n        }\n        if header.is_commit_frame() {\n            let mut dirty_pages = self.dirty_pages.write();\n            tracing::debug!(\n                \"wal_callback: commit frame, clearing {} dirty pages\",\n                dirty_pages.len()\n            );\n            let mut cache = self.page_cache.write();\n            for page_id in dirty_pages.iter() {\n                let page_key = PageCacheKey::new(page_id as usize);\n                // Page may have been evicted from cache after spilling to WAL\n                if let Some(page) = cache.get(&page_key)? {\n                    page.clear_dirty();\n                }\n            }\n            dirty_pages.clear();\n        }\n        Ok(WalFrameInfo {\n            page_no: header.page_number,\n            db_size: header.db_size,\n        })\n    }\n\n    pub fn is_checkpointing(&self) -> bool {\n        self.checkpoint_state.read().phase != CheckpointPhase::NotCheckpointing\n    }\n\n    fn reset_checkpoint_state(&self) {\n        self.clear_checkpoint_state();\n        self.commit_info.write().state = CommitState::PrepareWal;\n    }\n\n    /// Reset checkpoint state machine to initial state.\n    /// Use this to clean up after a failed explicit checkpoint (PRAGMA wal_checkpoint).\n    pub fn clear_checkpoint_state(&self) {\n        let mut state = self.checkpoint_state.write();\n        state.phase = CheckpointPhase::NotCheckpointing;\n        state.result = None;\n        state.mode = None;\n    }\n\n    /// Clean up after a auto-checkpoint failure.\n    /// Auto-checkpoint executed outside of the main transaction - so WAL transaction was already finalized\n    pub fn cleanup_after_auto_checkpoint_failure(&self) {\n        self.reset_checkpoint_state();\n        if let Some(wal) = self.wal.as_ref() {\n            wal.abort_checkpoint();\n        }\n    }\n\n    #[instrument(skip_all, level = Level::DEBUG, name = \"pager_checkpoint\",)]\n    /// Checkpoint the WAL to the database file (if needed).\n    /// Args:\n    /// - mode: The checkpoint mode to use (PASSIVE, FULL, RESTART, TRUNCATE)\n    /// - sync_mode: The fsync mode to use (OFF, NORMAL, FULL)\n    /// - clear_page_cache: Whether to clear the page cache after checkpointing\n    pub fn checkpoint(\n        &self,\n        mode: CheckpointMode,\n        sync_mode: crate::SyncMode,\n        clear_page_cache: bool,\n    ) -> Result<IOResult<CheckpointResult>> {\n        let Some(wal) = self.wal.as_ref() else {\n            turso_soft_unreachable!(\"checkpoint() called on database without WAL\");\n            return Err(LimboError::InternalError(\n                \"checkpoint() called on database without WAL\".to_string(),\n            ));\n        };\n        loop {\n            // Clone the phase to check what state we're in, but keep result in place\n            // This is important because we need to be careful not to e.g. clone and drop the checkpoint result which\n            // causes a drop of CheckpointLocks prematurely and results in a panic.\n            let phase = self.checkpoint_state.read().phase.clone();\n            match phase {\n                CheckpointPhase::NotCheckpointing => {\n                    let mut state = self.checkpoint_state.write();\n                    state.phase = CheckpointPhase::Checkpoint {\n                        mode,\n                        sync_mode,\n                        clear_page_cache,\n                    };\n                    state.mode = Some(mode);\n                }\n                CheckpointPhase::Checkpoint {\n                    mode,\n                    sync_mode,\n                    clear_page_cache,\n                } => {\n                    let res = return_if_io!(wal.checkpoint(self, mode));\n                    let mut state = self.checkpoint_state.write();\n                    if matches!(mode, CheckpointMode::Truncate { .. })\n                        // `should_truncate` will be true for successful truncate checkpoint\n                        && res.should_truncate()\n                    {\n                        state.phase = CheckpointPhase::TruncateDbFile {\n                            sync_mode,\n                            clear_page_cache,\n                            page1_invalidated: false,\n                        };\n                    } else if res.wal_checkpoint_backfilled == 0\n                        || sync_mode == crate::SyncMode::Off\n                    {\n                        state.phase = CheckpointPhase::Finalize { clear_page_cache };\n                    } else {\n                        state.phase = CheckpointPhase::SyncDbFile { clear_page_cache };\n                    }\n                    state.result = Some(res);\n                }\n                CheckpointPhase::TruncateDbFile {\n                    sync_mode,\n                    clear_page_cache,\n                    page1_invalidated,\n                } => {\n                    let should_skip_truncate_db_file = {\n                        let state = self.checkpoint_state.read();\n                        turso_assert!(\n                            matches!(state.mode, Some(CheckpointMode::Truncate { .. })),\n                            \"mode should be truncate in CheckpointPhase::TruncateDbFile\"\n                        );\n                        let result = state.result.as_ref().expect(\"result should be set\");\n                        // Skip if we already sent truncate\n                        result.db_truncate_sent\n                    };\n\n                    if should_skip_truncate_db_file {\n                        let mut state = self.checkpoint_state.write();\n                        if sync_mode == crate::SyncMode::Off {\n                            // Skip DB sync, proceed to WAL truncation\n                            state.phase = CheckpointPhase::TruncateWalFile { clear_page_cache };\n                        } else {\n                            // Sync DB first, then SyncDbFile will transition to TruncateWalFile\n                            state.phase = CheckpointPhase::SyncDbFile { clear_page_cache };\n                        }\n                        continue;\n                    }\n                    // Invalidate page 1 (header) in cache before reading - checkpoint potentially wrote pages\n                    // directly to DB file from the WAL, so the checkpointer connections' page 1 may have stale database_size.\n                    if !page1_invalidated {\n                        let page1_key = PageCacheKey::new(DatabaseHeader::PAGE_ID);\n                        self.page_cache.write().delete(page1_key)?;\n                        let mut state = self.checkpoint_state.write();\n                        state.phase = CheckpointPhase::TruncateDbFile {\n                            sync_mode,\n                            clear_page_cache,\n                            page1_invalidated: true,\n                        };\n                    }\n\n                    // Truncate the database file unless already at correct size\n                    let db_size =\n                        return_if_io!(self.with_header(|header| header.database_size)).get();\n                    let page_size = self.get_page_size().unwrap_or_default();\n                    let expected = db_size as u64 * page_size.get() as u64;\n                    let should_skip_db_truncate = match self.db_file.size() {\n                        Ok(current_size) => expected >= current_size,\n                        Err(err) => {\n                            // e.g. file.size() is not supported in web worker environment, so we should\n                            // skip the truncate if we can't check the size.\n                            tracing::debug!(\n                                \"checkpoint(TRUNCATE): db_file.size unavailable, skipping db truncate pre-check: {err}\"\n                            );\n                            true\n                        }\n                    };\n                    if should_skip_db_truncate {\n                        // No DB truncation needed (or unsupported size pre-check), move to next phase.\n                        let mut state = self.checkpoint_state.write();\n                        if sync_mode == crate::SyncMode::Off {\n                            // Skip DB sync, proceed to WAL truncation\n                            state.phase = CheckpointPhase::TruncateWalFile { clear_page_cache };\n                        } else {\n                            // Sync DB first, then SyncDbFile will transition to TruncateWalFile\n                            state.phase = CheckpointPhase::SyncDbFile { clear_page_cache };\n                        }\n                        continue;\n                    }\n                    let c = self.db_file.truncate(\n                        expected as usize,\n                        Completion::new_trunc(move |_| {\n                            tracing::trace!(\n                                \"Database file truncated to expected size: {} bytes\",\n                                expected\n                            );\n                        }),\n                    )?;\n                    self.checkpoint_state\n                        .write()\n                        .result\n                        .as_mut()\n                        .expect(\"result should be set\")\n                        .db_truncate_sent = true;\n                    io_yield_one!(c);\n                }\n                CheckpointPhase::SyncDbFile { clear_page_cache } => {\n                    let need_sync_db_file = {\n                        let state = self.checkpoint_state.read();\n                        let result = state.result.as_ref().expect(\"result should be set\");\n                        !result.db_sync_sent\n                    };\n\n                    if !need_sync_db_file {\n                        turso_assert!(\n                            !self.syncing.load(Ordering::SeqCst),\n                            \"syncing should be done\"\n                        );\n                        // After DB is synced, truncate WAL if in TRUNCATE mode\n                        let is_truncate_mode = {\n                            let state = self.checkpoint_state.read();\n                            matches!(state.mode, Some(CheckpointMode::Truncate { .. }))\n                        };\n                        if is_truncate_mode {\n                            self.checkpoint_state.write().phase =\n                                CheckpointPhase::TruncateWalFile { clear_page_cache };\n                        } else {\n                            self.checkpoint_state.write().phase =\n                                CheckpointPhase::Finalize { clear_page_cache };\n                        }\n                        continue;\n                    }\n\n                    let c = sqlite3_ondisk::begin_sync(\n                        self.db_file.as_ref(),\n                        self.syncing.clone(),\n                        self.get_sync_type(),\n                    )?;\n                    self.checkpoint_state\n                        .write()\n                        .result\n                        .as_mut()\n                        .expect(\"result should be set\")\n                        .db_sync_sent = true;\n                    io_yield_one!(c);\n                }\n                CheckpointPhase::TruncateWalFile { clear_page_cache } => {\n                    // Truncate WAL file after DB is safely synced - this ensures data durability.\n                    // If crash occurred after WAL truncate but before DB sync, data would be lost.\n                    let need_wal_truncate = {\n                        let state = self.checkpoint_state.read();\n                        turso_assert!(\n                            matches!(state.mode, Some(CheckpointMode::Truncate { .. })),\n                            \"mode should be truncate in CheckpointPhase::TruncateWalFile\"\n                        );\n                        let result = state.result.as_ref().expect(\"result should be set\");\n                        !result.wal_truncate_sent || !result.wal_sync_sent\n                    };\n\n                    if !need_wal_truncate {\n                        self.checkpoint_state.write().phase =\n                            CheckpointPhase::Finalize { clear_page_cache };\n                        continue;\n                    }\n\n                    // Call WAL truncate\n                    return_if_io!(wal.truncate_wal(\n                        self.checkpoint_state\n                            .write()\n                            .result\n                            .as_mut()\n                            .expect(\"result should be set\"),\n                        self.get_sync_type(),\n                    ));\n                }\n                CheckpointPhase::Finalize { clear_page_cache } => {\n                    let mut state = self.checkpoint_state.write();\n                    let mut res = state.result.take().expect(\"result should be set\");\n                    state.phase = CheckpointPhase::NotCheckpointing;\n                    state.mode = None;\n\n                    // Clear page cache only if requested (explicit checkpoints do this, auto-checkpoint does not)\n                    if clear_page_cache {\n                        self.page_cache.write().clear(false).map_err(|e| {\n                            res.release_guard();\n                            LimboError::InternalError(format!(\"Failed to clear page cache: {e:?}\"))\n                        })?;\n                    }\n\n                    // Release checkpoint guard\n                    res.release_guard();\n\n                    return Ok(IOResult::Done(res));\n                }\n            }\n        }\n    }\n\n    /// Invalidates entire page cache by removing all dirty and clean pages. Usually used in case\n    /// of a rollback or in case we want to invalidate page cache after starting a read transaction\n    /// right after new writes happened which would invalidate current page cache.\n    pub fn clear_page_cache(&self, clear_dirty: bool) {\n        let dirty_pages = self.dirty_pages.write();\n        let mut cache = self.page_cache.write();\n        for page_id in dirty_pages.iter() {\n            let page_key = PageCacheKey::new(page_id as usize);\n            if let Some(page) = cache.get(&page_key).unwrap_or(None) {\n                page.clear_dirty();\n            }\n        }\n        cache\n            .clear(clear_dirty)\n            .expect(\"Failed to clear page cache\");\n        if clear_dirty {\n            drop(dirty_pages);\n            self.dirty_pages.write().clear();\n        }\n    }\n\n    /// Checkpoint in Truncate mode and delete the WAL file. This method is _only_ to be called\n    /// for shutting down the last remaining connection to a database.\n    ///\n    /// sqlite3.h\n    /// Usually, when a database in [WAL mode] is closed or detached from a\n    /// database handle, SQLite checks if if there are other connections to the\n    /// same database, and if there are no other database connection (if the\n    /// connection being closed is the last open connection to the database),\n    /// then SQLite performs a [checkpoint] before closing the connection and\n    /// deletes the WAL file.\n    pub fn checkpoint_shutdown(\n        &self,\n        wal_auto_checkpoint_disabled: bool,\n        sync_mode: crate::SyncMode,\n    ) -> Result<()> {\n        let mut attempts = 0;\n        {\n            let Some(wal) = self.wal.as_ref() else {\n                turso_soft_unreachable!(\"checkpoint_shutdown() called on database without WAL\");\n                return Err(LimboError::InternalError(\n                    \"checkpoint_shutdown() called on database without WAL\".to_string(),\n                ));\n            };\n            // fsync the wal syncronously before beginning checkpoint\n            let c = wal.sync(self.get_sync_type())?;\n            self.io.wait_for_completion(c)?;\n        }\n        if !wal_auto_checkpoint_disabled {\n            while let Err(LimboError::Busy) = self.blocking_checkpoint(\n                CheckpointMode::Truncate {\n                    upper_bound_inclusive: None,\n                },\n                sync_mode,\n            ) {\n                if attempts == 3 {\n                    // don't return error on `close` if we are unable to checkpoint, we can silently fail\n                    tracing::warn!(\n                        \"Failed to checkpoint WAL on shutdown after 3 attempts, giving up\"\n                    );\n                    return Ok(());\n                }\n                attempts += 1;\n            }\n        }\n        // TODO: delete the WAL file here after truncate checkpoint, but *only* if we are sure that\n        // no other connections have opened since.\n        Ok(())\n    }\n\n    /// Perform a blocking checkpoint with the specified mode.\n    /// This is a convenience wrapper around `checkpoint()` that blocks until completion.\n    /// Explicit checkpoints clear the page cache after completion.\n    #[instrument(skip_all, level = Level::DEBUG)]\n    pub fn blocking_checkpoint(\n        &self,\n        mode: CheckpointMode,\n        sync_mode: crate::SyncMode,\n    ) -> Result<CheckpointResult> {\n        self.io.block(|| self.checkpoint(mode, sync_mode, true))\n    }\n\n    pub fn freepage_list(&self) -> u32 {\n        self.io\n            .block(|| HeaderRef::from_pager(self))\n            .map(|header_ref| header_ref.borrow().freelist_pages.get())\n            .unwrap_or(0)\n    }\n    // Providing a page is optional, if provided it will be used to avoid reading the page from disk.\n    // This is implemented in accordance with sqlite freepage2() function.\n    #[instrument(skip_all, level = Level::DEBUG)]\n    pub fn free_page(&self, mut page: Option<PageRef>, page_id: usize) -> Result<IOResult<()>> {\n        tracing::trace!(\"free_page(page_id={})\", page_id);\n        // Number of reserved slots in trunk header (next pointer + leaf count)\n        const RESERVED_SLOTS: usize = 2;\n\n        let header_ref = self.io.block(|| HeaderRefMut::from_pager(self))?;\n        let header = header_ref.borrow_mut();\n\n        let mut state = self.free_page_state.write();\n        tracing::debug!(?state);\n        loop {\n            match &mut *state {\n                FreePageState::Start => {\n                    if page_id < 2 || page_id > header.database_size.get() as usize {\n                        return Err(LimboError::Corrupt(format!(\n                            \"Invalid page number {page_id} for free operation\"\n                        )));\n                    }\n\n                    let (page, c) = match page.take() {\n                        Some(page) => {\n                            turso_assert_eq!(\n                                page.get().id,\n                                page_id,\n                                \"free_page page id mismatch\",\n                                { \"expected\": page_id, \"actual\": page.get().id }\n                            );\n                            if page.is_loaded() {\n                                let page_contents = page.get_contents();\n                                page_contents.overflow_cells.clear();\n                            }\n                            (page, None)\n                        }\n                        None => self.read_page(page_id as i64)?,\n                    };\n                    header.freelist_pages = (header.freelist_pages.get() + 1).into();\n\n                    let trunk_page_id = header.freelist_trunk_page.get();\n\n                    // Pin page to prevent eviction while stored in state machine\n                    page.pin();\n\n                    if trunk_page_id != 0 {\n                        *state = FreePageState::AddToTrunk { page };\n                    } else {\n                        *state = FreePageState::NewTrunk { page };\n                    }\n                    if let Some(c) = c {\n                        if !c.succeeded() {\n                            io_yield_one!(c);\n                        }\n                    }\n                }\n                FreePageState::AddToTrunk { page } => {\n                    let trunk_page_id = header.freelist_trunk_page.get();\n                    let (trunk_page, c) = self.read_page(trunk_page_id as i64)?;\n                    if let Some(c) = c {\n                        if !c.succeeded() {\n                            io_yield_one!(c);\n                        }\n                    }\n                    turso_assert!(trunk_page.is_loaded(), \"trunk_page should be loaded\");\n\n                    let trunk_page_contents = trunk_page.get_contents();\n                    let number_of_leaf_pages =\n                        trunk_page_contents.read_u32_no_offset(FREELIST_TRUNK_OFFSET_LEAF_COUNT);\n\n                    let max_free_list_entries =\n                        (header.usable_space() / FREELIST_LEAF_PTR_SIZE) - RESERVED_SLOTS;\n\n                    if number_of_leaf_pages < max_free_list_entries as u32 {\n                        turso_assert!(\n                            trunk_page.get().id == trunk_page_id as usize,\n                            \"trunk page has unexpected id\"\n                        );\n                        self.add_dirty(&trunk_page)?;\n\n                        trunk_page_contents.write_u32_no_offset(\n                            FREELIST_TRUNK_OFFSET_LEAF_COUNT,\n                            number_of_leaf_pages + 1,\n                        );\n                        trunk_page_contents.write_u32_no_offset(\n                            FREELIST_TRUNK_OFFSET_FIRST_LEAF_PTR\n                                + (number_of_leaf_pages as usize * FREELIST_LEAF_PTR_SIZE),\n                            page_id as u32,\n                        );\n\n                        // Unpin page before finishing - it's added to freelist\n                        page.unpin();\n                        break;\n                    }\n                    // page remains pinned as it transitions to NewTrunk state\n                    *state = FreePageState::NewTrunk { page: page.clone() };\n                }\n                FreePageState::NewTrunk { page } => {\n                    turso_assert!(page.is_loaded(), \"page should be loaded\");\n                    // If we get here, need to make this page a new trunk\n                    turso_assert!(page.get().id == page_id, \"page has unexpected id\");\n                    self.add_dirty(page)?;\n\n                    let trunk_page_id = header.freelist_trunk_page.get();\n\n                    let contents = page.get_contents();\n                    // Point to previous trunk\n                    contents\n                        .write_u32_no_offset(FREELIST_TRUNK_OFFSET_NEXT_TRUNK_PTR, trunk_page_id);\n                    // Zero leaf count\n                    contents.write_u32_no_offset(FREELIST_TRUNK_OFFSET_LEAF_COUNT, 0);\n                    // Update page 1 to point to new trunk\n                    header.freelist_trunk_page = (page_id as u32).into();\n                    // Unpin page before finishing - it's now a trunk page\n                    page.unpin();\n                    break;\n                }\n            }\n        }\n        *state = FreePageState::Start;\n        Ok(IOResult::Done(()))\n    }\n\n    #[instrument(skip_all, level = Level::DEBUG)]\n    pub fn allocate_page1(&self) -> Result<IOResult<PageRef>> {\n        let state = self.allocate_page1_state.read().clone();\n        match state {\n            AllocatePage1State::Start => {\n                turso_assert!(!self.db_initialized());\n                tracing::trace!(\"allocate_page1(Start)\");\n\n                let IOResult::Done(mut default_header) = self.with_header(|header| *header)? else {\n                    panic!(\"DB should not be initialized and should not do any IO\");\n                };\n\n                turso_assert_eq!(default_header.database_size.get(), 0);\n                default_header.database_size = 1.into();\n\n                // Use cached reserved_space if set (e.g., by sync engine before page allocation),\n                // otherwise fall back to IOContext's encryption/checksum requirements.\n                let reserved_space_bytes = self.get_reserved_space().unwrap_or_else(|| {\n                    let io_ctx = self.io_ctx.read();\n                    io_ctx.get_reserved_space_bytes()\n                });\n                default_header.reserved_space = reserved_space_bytes;\n                self.set_reserved_space(reserved_space_bytes);\n\n                if let Some(size) = self.get_page_size() {\n                    default_header.page_size = size;\n                }\n\n                tracing::debug!(\n                    \"allocate_page1(Start) page_size = {:?}, reserved_space = {}\",\n                    default_header.page_size,\n                    default_header.reserved_space\n                );\n\n                self.buffer_pool\n                    .finalize_with_page_size(default_header.page_size.get() as usize)?;\n                let page = allocate_new_page(1, &self.buffer_pool);\n\n                let contents = page.get_contents();\n                contents.write_database_header(&default_header);\n\n                let page1 = page;\n                // Create the sqlite_schema table, for this we just need to create the btree page\n                // for the first page of the database which is basically like any other btree page\n                // but with a 100 byte offset, so we just init the page so that sqlite understands\n                // this is a correct page.\n                btree_init_page(\n                    &page1,\n                    PageType::TableLeaf,\n                    DatabaseHeader::SIZE,\n                    (default_header.page_size.get() - default_header.reserved_space as u32)\n                        as usize,\n                );\n                let c = begin_write_btree_page(self, &page1)?;\n\n                // Pin page1 to prevent eviction while stored in state machine\n                page1.pin();\n                *self.allocate_page1_state.write() = AllocatePage1State::Writing { page: page1 };\n                io_yield_one!(c);\n            }\n            AllocatePage1State::Writing { page } => {\n                turso_assert!(page.is_loaded(), \"page should be loaded\");\n                tracing::trace!(\"allocate_page1(Writing done)\");\n                let page_key = PageCacheKey::new(page.get().id);\n                let mut cache = self.page_cache.write();\n                cache.insert(page_key, page.clone()).map_err(|e| {\n                    LimboError::InternalError(format!(\"Failed to insert page 1 into cache: {e:?}\"))\n                })?;\n                // After we wrote the header page, we may now set this None, to signify we initialized\n                self.init_page_1.store(None);\n                page.unpin();\n                *self.allocate_page1_state.write() = AllocatePage1State::Done;\n                Ok(IOResult::Done(page))\n            }\n            AllocatePage1State::Done => unreachable!(\"cannot try to allocate page 1 again\"),\n        }\n    }\n\n    pub fn allocating_page1(&self) -> bool {\n        matches!(\n            *self.allocate_page1_state.read(),\n            AllocatePage1State::Writing { .. }\n        )\n    }\n\n    /// Tries to reuse a page from the freelist if available.\n    /// If not, allocates a new page which increases the database size.\n    ///\n    /// FIXME: implement sqlite's 'nearby' parameter and use AllocMode.\n    ///        SQLite's allocate_page() equivalent has a parameter 'nearby' which is a hint about the page number we want to have for the allocated page.\n    ///        We should use this parameter to allocate the page in the same way as SQLite does; instead now we just either take the first available freelist page\n    ///        or allocate a new page.\n    #[allow(clippy::readonly_write_lock)]\n    #[instrument(skip_all, level = Level::DEBUG)]\n    pub fn allocate_page(&self) -> Result<IOResult<PageRef>> {\n        // Ensure cache has room before allocating (we may spill dirty pages first)\n        return_if_io!(self.ensure_cache_space());\n\n        let header_ref = self.io.block(|| HeaderRefMut::from_pager(self))?;\n        let header = header_ref.borrow_mut();\n\n        loop {\n            let mut state = self.allocate_page_state.write();\n            tracing::debug!(\"allocate_page(state={:?})\", state);\n            match &mut *state {\n                AllocatePageState::Start => {\n                    let old_db_size = header.database_size.get();\n                    #[cfg(not(feature = \"omit_autovacuum\"))]\n                    let mut new_db_size = old_db_size;\n                    #[cfg(feature = \"omit_autovacuum\")]\n                    let new_db_size = old_db_size;\n\n                    tracing::debug!(\"allocate_page(database_size={})\", new_db_size);\n                    #[cfg(not(feature = \"omit_autovacuum\"))]\n                    {\n                        //  If the following conditions are met, allocate a pointer map page, add to cache and increment the database size\n                        //  - autovacuum is enabled\n                        //  - the last page is a pointer map page\n                        if matches!(\n                            AutoVacuumMode::from(self.auto_vacuum_mode.load(Ordering::SeqCst)),\n                            AutoVacuumMode::Full\n                        ) && is_ptrmap_page(new_db_size + 1, header.page_size.get() as usize)\n                        {\n                            // we will allocate a ptrmap page, so increment size\n                            new_db_size += 1;\n                            let page = allocate_new_page(new_db_size as i64, &self.buffer_pool);\n                            self.add_dirty(&page)?;\n                            let page_key = PageCacheKey::new(page.get().id as usize);\n                            let mut cache = self.page_cache.write();\n                            cache.insert(page_key, page)?;\n                        }\n                    }\n\n                    let first_freelist_trunk_page_id = header.freelist_trunk_page.get();\n                    if first_freelist_trunk_page_id == 0 {\n                        *state = AllocatePageState::AllocateNewPage {\n                            current_db_size: new_db_size,\n                        };\n                        continue;\n                    }\n                    let (trunk_page, c) = self.read_page(first_freelist_trunk_page_id as i64)?;\n                    // Pin trunk_page to prevent eviction while stored in state machine\n                    trunk_page.pin();\n                    *state = AllocatePageState::SearchAvailableFreeListLeaf { trunk_page };\n                    if let Some(c) = c {\n                        io_yield_one!(c);\n                    }\n                }\n                AllocatePageState::SearchAvailableFreeListLeaf { trunk_page } => {\n                    turso_assert!(\n                        trunk_page.is_loaded(),\n                        \"Freelist trunk page is not loaded\",\n                        { \"page_id\": trunk_page.get().id }\n                    );\n                    let page_contents = trunk_page.get_contents();\n                    let next_trunk_page_id =\n                        page_contents.read_u32_no_offset(FREELIST_TRUNK_OFFSET_NEXT_TRUNK_PTR);\n                    let number_of_freelist_leaves =\n                        page_contents.read_u32_no_offset(FREELIST_TRUNK_OFFSET_LEAF_COUNT);\n\n                    // There are leaf pointers on this trunk page, so we can reuse one of the pages\n                    // for the allocation.\n                    if number_of_freelist_leaves != 0 {\n                        let page_contents = trunk_page.get_contents();\n                        let next_leaf_page_id =\n                            page_contents.read_u32_no_offset(FREELIST_TRUNK_OFFSET_FIRST_LEAF_PTR);\n                        let (leaf_page, c) = self.read_page(next_leaf_page_id as i64)?;\n\n                        turso_assert!(\n                            number_of_freelist_leaves > 0,\n                            \"Freelist trunk page has no leaves\",\n                            { \"page_id\": trunk_page.get().id }\n                        );\n\n                        // Pin leaf_page to prevent eviction while stored in state machine\n                        // trunk_page is already pinned from previous state\n                        leaf_page.pin();\n\n                        *state = AllocatePageState::ReuseFreelistLeaf {\n                            trunk_page: trunk_page.clone(),\n                            leaf_page,\n                            number_of_freelist_leaves,\n                        };\n                        if let Some(c) = c {\n                            io_yield_one!(c);\n                        }\n                        continue;\n                    }\n\n                    // No freelist leaves on this trunk page.\n                    // Reuse the trunk page itself (even if this is the last trunk).\n                    // Update the database's first freelist trunk page to the next trunk page (may be 0 if there are no more trunk pages).\n                    header.freelist_trunk_page = next_trunk_page_id.into();\n                    header.freelist_pages = (header.freelist_pages.get() - 1).into();\n                    self.add_dirty(trunk_page)?;\n                    // zero out the page\n                    turso_assert!(\n                        trunk_page.get_contents().overflow_cells.is_empty(),\n                        \"Freelist trunk page has overflow cells\",\n                        { \"page_id\": trunk_page.get().id }\n                    );\n                    trunk_page.get_contents().as_ptr().fill(0);\n                    let page_key = PageCacheKey::new(trunk_page.get().id);\n                    {\n                        let page_cache = self.page_cache.read();\n                        turso_assert!(\n                            page_cache.contains_key(&page_key),\n                            \"page is not in cache\",\n                            { \"page_id\": trunk_page.get().id }\n                        );\n                    }\n                    // Unpin trunk_page before returning - caller takes ownership\n                    trunk_page.unpin();\n                    let trunk_page = trunk_page.clone();\n                    *state = AllocatePageState::Start;\n                    return Ok(IOResult::Done(trunk_page));\n                }\n                AllocatePageState::ReuseFreelistLeaf {\n                    trunk_page,\n                    leaf_page,\n                    number_of_freelist_leaves,\n                } => {\n                    turso_assert!(\n                        leaf_page.is_loaded(),\n                        \"Leaf page is not loaded\",\n                        { \"page_id\": leaf_page.get().id }\n                    );\n                    let page_contents = trunk_page.get_contents();\n                    self.add_dirty(leaf_page)?;\n                    // zero out the page\n                    turso_assert!(\n                        leaf_page.get_contents().overflow_cells.is_empty(),\n                        \"Freelist leaf page has overflow cells\",\n                        { \"page_id\": leaf_page.get().id }\n                    );\n                    leaf_page.get_contents().as_ptr().fill(0);\n                    let page_key = PageCacheKey::new(leaf_page.get().id);\n                    {\n                        let page_cache = self.page_cache.read();\n                        turso_assert!(\n                            page_cache.contains_key(&page_key),\n                            \"page is not in cache\",\n                            { \"page_id\": leaf_page.get().id }\n                        );\n                    }\n\n                    // Mark trunk page dirty BEFORE modifying it so subjournal captures original content\n                    self.add_dirty(trunk_page)?;\n\n                    // Shift left all the other leaf pages in the trunk page and subtract 1 from the leaf count\n                    let remaining_leaves_count = (*number_of_freelist_leaves - 1) as usize;\n                    {\n                        let buf = page_contents.as_ptr();\n                        // use copy within the same page\n                        let offset_remaining_leaves_start =\n                            FREELIST_TRUNK_OFFSET_FIRST_LEAF_PTR + FREELIST_LEAF_PTR_SIZE;\n                        let offset_remaining_leaves_end = offset_remaining_leaves_start\n                            + remaining_leaves_count * FREELIST_LEAF_PTR_SIZE;\n                        buf.copy_within(\n                            offset_remaining_leaves_start..offset_remaining_leaves_end,\n                            FREELIST_TRUNK_OFFSET_FIRST_LEAF_PTR,\n                        );\n                    }\n                    // write the new leaf count\n                    page_contents.write_u32_no_offset(\n                        FREELIST_TRUNK_OFFSET_LEAF_COUNT,\n                        remaining_leaves_count as u32,\n                    );\n\n                    header.freelist_pages = (header.freelist_pages.get() - 1).into();\n                    // Unpin both pages before returning - caller takes ownership of leaf_page\n                    trunk_page.unpin();\n                    leaf_page.unpin();\n                    let leaf_page = leaf_page.clone();\n                    *state = AllocatePageState::Start;\n                    return Ok(IOResult::Done(leaf_page));\n                }\n                AllocatePageState::AllocateNewPage { current_db_size } => {\n                    let mut new_db_size = *current_db_size + 1;\n\n                    // if new_db_size reaches the pending page, we need to allocate a new one\n                    if Some(new_db_size) == self.pending_byte_page_id() {\n                        let richard_hipp_special_page =\n                            allocate_new_page(new_db_size as i64, &self.buffer_pool);\n                        self.add_dirty(&richard_hipp_special_page)?;\n                        let page_key = PageCacheKey::new(richard_hipp_special_page.get().id);\n                        {\n                            let mut cache = self.page_cache.write();\n                            cache.insert(page_key, richard_hipp_special_page).unwrap();\n                        }\n                        // HIPP special page is assumed to zeroed and should never be read or written to by the BTREE\n                        new_db_size += 1;\n                    }\n\n                    // Check if allocating a new page would exceed the maximum page count\n                    let max_page_count = self.get_max_page_count();\n                    if new_db_size > max_page_count {\n                        return Err(LimboError::DatabaseFull(\n                            \"database or disk is full\".to_string(),\n                        ));\n                    }\n\n                    // FIXME: should reserve page cache entry before modifying the database\n                    let page = allocate_new_page(new_db_size as i64, &self.buffer_pool);\n                    {\n                        // setup page and add to cache\n                        self.add_dirty(&page)?;\n\n                        let page_key = PageCacheKey::new(page.get().id as usize);\n                        {\n                            // Run in separate block to avoid deadlock on page cache write lock\n                            let mut cache = self.page_cache.write();\n                            cache.insert(page_key, page.clone())?;\n                        }\n                        header.database_size = new_db_size.into();\n                        *state = AllocatePageState::Start;\n                        return Ok(IOResult::Done(page));\n                    }\n                }\n            }\n        }\n    }\n\n    pub fn upsert_page_in_cache(\n        &self,\n        id: usize,\n        page: PageRef,\n        dirty_page_must_exist: bool,\n    ) -> Result<(), LimboError> {\n        let mut cache = self.page_cache.write();\n        let page_key = PageCacheKey::new(id);\n\n        // FIXME: use specific page key for writer instead of max frame, this will make readers not conflict\n        if dirty_page_must_exist {\n            turso_assert!(page.is_dirty(), \"page must be dirty for upsert\", { \"page_id\": id });\n        }\n        cache.upsert_page(page_key, page.clone()).map_err(|e| {\n            LimboError::InternalError(format!(\n                \"Failed to insert loaded page {id} into cache: {e:?}\"\n            ))\n        })?;\n        page.set_loaded();\n        page.clear_wal_tag();\n        Ok(())\n    }\n\n    #[instrument(skip_all, level = Level::DEBUG)]\n    pub fn rollback(&self, schema_did_change: bool, connection: &Connection, is_write: bool) {\n        tracing::debug!(schema_did_change);\n        if is_write {\n            let clear_dirty = true;\n            // The page cache only needs to be cleared if we are rolling back a write transaction.\n            // If a read transaction rolls back, and the next read transaction detects that the\n            // database has changed in between (see db_changed() in wal.rs), then the page cache\n            // will be cleared. Since the read transaction itself has not modified anything, it can proceed\n            // with its cached pages in case the database has NOT changed in between.\n            //\n            // Even in the case of a write transaction, clearing the entire page cache is overkill,\n            // since we only need to clear the dirty pages that were modified by the write transaction.\n            self.clear_page_cache(clear_dirty);\n            self.dirty_pages.write().clear();\n        } else {\n            turso_assert!(\n                self.dirty_pages.read().is_empty(),\n                \"dirty pages should be empty for read txn\"\n            );\n        }\n        self.reset_internal_states();\n        // Invalidate cached schema cookie since rollback may have restored the database schema cookie\n        self.set_schema_cookie(None);\n        if schema_did_change {\n            *connection.schema.write() = connection.db.clone_schema();\n        }\n        if is_write {\n            if let Some(wal) = self.wal.as_ref() {\n                wal.rollback(None);\n            }\n        }\n    }\n\n    fn reset_internal_states(&self) {\n        *self.checkpoint_state.write() = CheckpointState::default();\n        self.syncing.store(false, Ordering::SeqCst);\n        self.commit_info.write().reset();\n        *self.allocate_page_state.write() = AllocatePageState::Start;\n        *self.free_page_state.write() = FreePageState::Start;\n        *self.spill_state.write() = SpillState::Idle;\n        #[cfg(not(feature = \"omit_autovacuum\"))]\n        {\n            let mut vacuum_state = self.vacuum_state.write();\n            vacuum_state.ptrmap_get_state = PtrMapGetState::Start;\n            vacuum_state.ptrmap_put_state = PtrMapPutState::Start;\n            vacuum_state.btree_create_vacuum_full_state = BtreeCreateVacuumFullState::Start;\n        }\n\n        *self.header_ref_state.write() = HeaderRefState::Start;\n    }\n\n    pub fn with_header<T>(&self, f: impl Fn(&DatabaseHeader) -> T) -> Result<IOResult<T>> {\n        let header_ref = return_if_io!(HeaderRef::from_pager(self));\n        let header = header_ref.borrow();\n        // Update cached schema cookie when reading header\n        self.set_schema_cookie(Some(header.schema_cookie.get()));\n        Ok(IOResult::Done(f(header)))\n    }\n\n    pub fn with_header_mut<T>(&self, f: impl Fn(&mut DatabaseHeader) -> T) -> Result<IOResult<T>> {\n        let header_ref = return_if_io!(HeaderRefMut::from_pager(self));\n        let header = header_ref.borrow_mut();\n        let result = f(header);\n        // Update cached schema cookie after modification\n        self.set_schema_cookie(Some(header.schema_cookie.get()));\n        Ok(IOResult::Done(result))\n    }\n\n    pub fn is_encryption_ctx_set(&self) -> bool {\n        self.io_ctx.write().encryption_context().is_some()\n    }\n\n    pub fn is_encryption_enabled(&self) -> bool {\n        self.enable_encryption.load(Ordering::SeqCst)\n    }\n\n    pub fn set_encryption_context(\n        &self,\n        cipher_mode: CipherMode,\n        key: &EncryptionKey,\n    ) -> Result<()> {\n        // we will set the encryption context only if the encryption is opted-in.\n        if !self.enable_encryption.load(Ordering::SeqCst) {\n            return Err(LimboError::InvalidArgument(\n                \"encryption is an opt in feature. enable it via passing `--experimental-encryption`\"\n                    .into(),\n            ));\n        }\n\n        let page_size = self.get_page_size_unchecked().get() as usize;\n        let encryption_ctx = EncryptionContext::new(cipher_mode, key, page_size)?;\n        {\n            let mut io_ctx = self.io_ctx.write();\n            io_ctx.set_encryption(encryption_ctx);\n        }\n        let Some(wal) = self.wal.as_ref() else {\n            return Ok(());\n        };\n        wal.set_io_context(self.io_ctx.read().clone());\n        // whenever we set the encryption context, lets reset the page cache. The page cache\n        // might have been loaded with page 1 to initialise the connection. During initialisation,\n        // we only read the header which is unencrypted, but the rest of the page is. If so, lets\n        // clear the cache.\n        self.clear_page_cache(false);\n        // Also invalidate cached schema cookie to force re-read of page 1 with encryption\n        self.set_schema_cookie(None);\n        Ok(())\n    }\n\n    pub fn reset_checksum_context(&self) {\n        {\n            let mut io_ctx = self.io_ctx.write();\n            io_ctx.reset_checksum();\n        }\n        let Some(wal) = self.wal.as_ref() else { return };\n        wal.set_io_context(self.io_ctx.read().clone())\n    }\n\n    pub fn set_reserved_space_bytes(&self, value: u8) {\n        self.set_reserved_space(value);\n    }\n\n    /// Encryption is an opt-in feature. If the flag is passed, then enable the encryption on\n    /// pager, which is then used to set it on the IOContext.\n    pub fn enable_encryption(&self, enable: bool) {\n        self.enable_encryption.store(enable, Ordering::SeqCst);\n    }\n}\n\npub fn allocate_new_page(page_id: i64, buffer_pool: &Arc<BufferPool>) -> PageRef {\n    let page = Arc::new(Page::new(page_id));\n    {\n        let buffer = buffer_pool.get_page();\n        let inner = page.get();\n        inner.buffer = Some(Arc::new(buffer));\n        page.set_loaded();\n        page.clear_wal_tag();\n    }\n    page\n}\n\npub fn default_page1(cipher: Option<&CipherMode>) -> PageRef {\n    // New Database header for empty Database\n    let mut default_header = DatabaseHeader::default();\n\n    if let Some(cipher) = cipher {\n        // we will set the reserved space bytes as required by either the encryption\n        let reserved_space_bytes = cipher.metadata_size() as u8;\n        default_header.reserved_space = reserved_space_bytes;\n    }\n\n    let page = Arc::new(Page::new(DatabaseHeader::PAGE_ID as i64));\n\n    {\n        let inner = page.get();\n        inner.buffer = Some(Arc::new(Buffer::new_temporary(\n            default_header.page_size.get() as usize,\n        )));\n    }\n\n    page.get_contents().write_database_header(&default_header);\n    page.set_loaded();\n    page.clear_wal_tag();\n\n    btree_init_page(\n        &page,\n        PageType::TableLeaf,\n        DatabaseHeader::SIZE, // offset of 100 bytes\n        (default_header.page_size.get() - default_header.reserved_space as u32) as usize,\n    );\n\n    page\n}\n\n#[derive(Debug, Clone, Copy)]\npub struct CreateBTreeFlags(pub u8);\nimpl CreateBTreeFlags {\n    pub const TABLE: u8 = 0b0001;\n    pub const INDEX: u8 = 0b0010;\n\n    pub fn new_table() -> Self {\n        Self(CreateBTreeFlags::TABLE)\n    }\n\n    pub fn new_index() -> Self {\n        Self(CreateBTreeFlags::INDEX)\n    }\n\n    pub fn is_table(&self) -> bool {\n        (self.0 & CreateBTreeFlags::TABLE) != 0\n    }\n\n    pub fn is_index(&self) -> bool {\n        (self.0 & CreateBTreeFlags::INDEX) != 0\n    }\n\n    pub fn get_flags(&self) -> u8 {\n        self.0\n    }\n}\n\n/*\n** The pointer map is a lookup table that identifies the parent page for\n** each child page in the database file.  The parent page is the page that\n** contains a pointer to the child.  Every page in the database contains\n** 0 or 1 parent pages. Each pointer map entry consists of a single byte 'type'\n** and a 4 byte parent page number.\n**\n** The PTRMAP_XXX identifiers below are the valid types.\n**\n** The purpose of the pointer map is to facilitate moving pages from one\n** position in the file to another as part of autovacuum.  When a page\n** is moved, the pointer in its parent must be updated to point to the\n** new location.  The pointer map is used to locate the parent page quickly.\n**\n** PTRMAP_ROOTPAGE: The database page is a root-page. The page-number is not\n**                  used in this case.\n**\n** PTRMAP_FREEPAGE: The database page is an unused (free) page. The page-number\n**                  is not used in this case.\n**\n** PTRMAP_OVERFLOW1: The database page is the first page in a list of\n**                   overflow pages. The page number identifies the page that\n**                   contains the cell with a pointer to this overflow page.\n**\n** PTRMAP_OVERFLOW2: The database page is the second or later page in a list of\n**                   overflow pages. The page-number identifies the previous\n**                   page in the overflow page list.\n**\n** PTRMAP_BTREE: The database page is a non-root btree page. The page number\n**               identifies the parent page in the btree.\n*/\n#[cfg(not(feature = \"omit_autovacuum\"))]\npub(crate) mod ptrmap {\n    #[allow(unused_imports)]\n    use crate::{storage::sqlite3_ondisk::PageSize, LimboError, Result};\n    use crate::{turso_assert_greater_than_or_equal, turso_soft_unreachable};\n\n    // Constants\n    pub const PTRMAP_ENTRY_SIZE: usize = 5;\n    /// Page 1 is the schema page which contains the database header.\n    /// Page 2 is the first pointer map page if the database has any pointer map pages.\n    pub const FIRST_PTRMAP_PAGE_NO: u32 = 2;\n\n    #[derive(Debug, Clone, Copy, PartialEq, Eq)]\n    #[repr(u8)]\n    pub enum PtrmapType {\n        RootPage = 1,\n        FreePage = 2,\n        Overflow1 = 3,\n        Overflow2 = 4,\n        BTreeNode = 5,\n    }\n\n    impl PtrmapType {\n        pub fn from_u8(value: u8) -> Option<Self> {\n            match value {\n                1 => Some(PtrmapType::RootPage),\n                2 => Some(PtrmapType::FreePage),\n                3 => Some(PtrmapType::Overflow1),\n                4 => Some(PtrmapType::Overflow2),\n                5 => Some(PtrmapType::BTreeNode),\n                _ => None,\n            }\n        }\n    }\n\n    #[derive(Debug, Clone, Copy)]\n    pub struct PtrmapEntry {\n        pub entry_type: PtrmapType,\n        pub parent_page_no: u32,\n    }\n\n    impl PtrmapEntry {\n        pub fn serialize(&self, buffer: &mut [u8]) -> Result<()> {\n            if buffer.len() < PTRMAP_ENTRY_SIZE {\n                return Err(LimboError::InternalError(format!(\n                    \"Buffer too small to serialize ptrmap entry. Expected at least {} bytes, got {}\",\n                    PTRMAP_ENTRY_SIZE,\n                    buffer.len()\n                )));\n            }\n            buffer[0] = self.entry_type as u8;\n            buffer[1..5].copy_from_slice(&self.parent_page_no.to_be_bytes());\n            Ok(())\n        }\n\n        pub fn deserialize(buffer: &[u8]) -> Option<Self> {\n            if buffer.len() < PTRMAP_ENTRY_SIZE {\n                return None;\n            }\n            let entry_type_u8 = buffer[0];\n            let parent_bytes_slice = buffer.get(1..5)?;\n            let parent_page_no = u32::from_be_bytes(parent_bytes_slice.try_into().ok()?);\n            PtrmapType::from_u8(entry_type_u8).map(|entry_type| PtrmapEntry {\n                entry_type,\n                parent_page_no,\n            })\n        }\n    }\n\n    /// Calculates how many database pages are mapped by a single pointer map page.\n    /// This is based on the total page size, as ptrmap pages are filled with entries.\n    pub fn entries_per_ptrmap_page(page_size: usize) -> usize {\n        turso_assert_greater_than_or_equal!(page_size, PageSize::MIN as usize);\n        page_size / PTRMAP_ENTRY_SIZE\n    }\n\n    /// Calculates the cycle length of pointer map pages\n    /// The cycle length is the number of database pages that are mapped by a single pointer map page.\n    pub fn ptrmap_page_cycle_length(page_size: usize) -> usize {\n        turso_assert_greater_than_or_equal!(page_size, PageSize::MIN as usize);\n        (page_size / PTRMAP_ENTRY_SIZE) + 1\n    }\n\n    /// Determines if a given page number `db_page_no` (1-indexed) is a pointer map page in a database with autovacuum enabled\n    pub fn is_ptrmap_page(db_page_no: u32, page_size: usize) -> bool {\n        //  The first page cannot be a ptrmap page because its for the schema\n        if db_page_no == 1 {\n            return false;\n        }\n        if db_page_no == FIRST_PTRMAP_PAGE_NO {\n            return true;\n        }\n        get_ptrmap_page_no_for_db_page(db_page_no, page_size) == db_page_no\n    }\n\n    /// Calculates which pointer map page (1-indexed) contains the entry for `db_page_no_to_query` (1-indexed).\n    /// `db_page_no_to_query` is the page whose ptrmap entry we are interested in.\n    pub fn get_ptrmap_page_no_for_db_page(db_page_no_to_query: u32, page_size: usize) -> u32 {\n        let group_size = ptrmap_page_cycle_length(page_size) as u32;\n        if group_size == 0 {\n            panic!(\"Page size too small, a ptrmap page cannot map any db pages.\");\n        }\n\n        let effective_page_index = db_page_no_to_query - FIRST_PTRMAP_PAGE_NO;\n        let group_idx = effective_page_index / group_size;\n\n        (group_idx * group_size) + FIRST_PTRMAP_PAGE_NO\n    }\n\n    /// Calculates the byte offset of the entry for `db_page_no_to_query` (1-indexed)\n    /// within its pointer map page (`ptrmap_page_no`, 1-indexed).\n    pub fn get_ptrmap_offset_in_page(\n        db_page_no_to_query: u32,\n        ptrmap_page_no: u32,\n        page_size: usize,\n    ) -> Result<usize> {\n        // The data pages mapped by `ptrmap_page_no` are:\n        // `ptrmap_page_no + 1`, `ptrmap_page_no + 2`, ..., up to `ptrmap_page_no + n_data_pages_per_group`.\n        // `db_page_no_to_query` must be one of these.\n        // The 0-indexed position of `db_page_no_to_query` within this sequence of data pages is:\n        // `db_page_no_to_query - (ptrmap_page_no + 1)`.\n\n        let n_data_pages_per_group = entries_per_ptrmap_page(page_size);\n        let first_data_page_mapped = ptrmap_page_no + 1;\n        let last_data_page_mapped = ptrmap_page_no + n_data_pages_per_group as u32;\n\n        if db_page_no_to_query < first_data_page_mapped\n            || db_page_no_to_query > last_data_page_mapped\n        {\n            turso_soft_unreachable!(\"Page is not mapped by ptrmap data range\", { \"page\": db_page_no_to_query, \"range_start\": first_data_page_mapped, \"range_end\": last_data_page_mapped, \"ptrmap_page\": ptrmap_page_no });\n            return Err(LimboError::InternalError(format!(\n                \"Page {db_page_no_to_query} is not mapped by the data page range [{first_data_page_mapped}, {last_data_page_mapped}] of ptrmap page {ptrmap_page_no}\"\n            )));\n        }\n        if is_ptrmap_page(db_page_no_to_query, page_size) {\n            turso_soft_unreachable!(\"Page is a pointer map page and should not have an entry calculated this way\", { \"page\": db_page_no_to_query });\n            return Err(LimboError::InternalError(format!(\n                \"Page {db_page_no_to_query} is a pointer map page and should not have an entry calculated this way.\"\n            )));\n        }\n\n        let entry_index_on_page = (db_page_no_to_query - first_data_page_mapped) as usize;\n        Ok(entry_index_on_page * PTRMAP_ENTRY_SIZE)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::sync::Arc;\n\n    use crate::sync::RwLock;\n\n    use crate::storage::page_cache::{PageCache, PageCacheKey};\n\n    use super::Page;\n\n    #[test]\n    fn test_shared_cache() {\n        // ensure cache can be shared between threads\n        let cache = Arc::new(RwLock::new(PageCache::new(10)));\n\n        let thread = {\n            let cache = cache.clone();\n            std::thread::spawn(move || {\n                let mut cache = cache.write();\n                let page_key = PageCacheKey::new(1);\n                let page = Page::new(1);\n                // Set loaded so that we avoid eviction, as we evict the page from cache if it is not locked and not loaded\n                page.set_loaded();\n                cache.insert(page_key, Arc::new(page)).unwrap();\n            })\n        };\n        let _ = thread.join();\n        let mut cache = cache.write();\n        let page_key = PageCacheKey::new(1);\n        let page = cache.get(&page_key).unwrap();\n        assert_eq!(page.unwrap().get().id, 1);\n    }\n}\n\n#[cfg(test)]\n#[cfg(not(feature = \"omit_autovacuum\"))]\nmod ptrmap_tests {\n    use crate::sync::Arc;\n\n    use super::ptrmap::*;\n    use super::*;\n    use crate::io::{MemoryIO, OpenFlags, IO};\n    use crate::storage::buffer_pool::BufferPool;\n    use crate::storage::database::DatabaseFile;\n    use crate::storage::page_cache::PageCache;\n    use crate::storage::pager::{default_page1, Pager};\n    use crate::storage::sqlite3_ondisk::PageSize;\n    use crate::storage::wal::{WalFile, WalFileShared};\n    use arc_swap::ArcSwapOption;\n\n    pub fn run_until_done<T>(\n        mut action: impl FnMut() -> Result<IOResult<T>>,\n        pager: &Pager,\n    ) -> Result<T> {\n        loop {\n            match action()? {\n                IOResult::Done(res) => {\n                    return Ok(res);\n                }\n                IOResult::IO(io) => io.wait(pager.io.as_ref())?,\n            }\n        }\n    }\n    // Helper to create a Pager for testing\n    fn test_pager_setup(page_size: u32, initial_db_pages: u32) -> Pager {\n        let io: Arc<dyn IO> = Arc::new(MemoryIO::new());\n        let db_file: Arc<dyn DatabaseStorage> = Arc::new(DatabaseFile::new(\n            io.open_file(\"test.db\", OpenFlags::Create, true).unwrap(),\n        ));\n\n        //  Construct interfaces for the pager\n        let pages = initial_db_pages + 10;\n        let sz = std::cmp::max(std::cmp::min(pages, 64), pages);\n        let buffer_pool = BufferPool::begin_init(&io, (sz * page_size) as usize);\n\n        let wal_shared = WalFileShared::new_shared(\n            io.open_file(\"test.db-wal\", OpenFlags::Create, false)\n                .unwrap(),\n        )\n        .unwrap();\n        let last_checksum_and_max_frame = wal_shared.read().last_checksum_and_max_frame();\n        let wal: Arc<dyn Wal> = Arc::new(WalFile::new(\n            io.clone(),\n            wal_shared,\n            last_checksum_and_max_frame,\n            buffer_pool.clone(),\n        ));\n\n        // For new empty databases, init_page_1 must be Some(page) so allocate_page1() can be called\n        let init_page_1 = Arc::new(ArcSwapOption::new(Some(default_page1(None))));\n        let pager = Pager::new(\n            db_file,\n            Some(wal),\n            io,\n            PageCache::new(sz as usize),\n            buffer_pool,\n            Arc::new(Mutex::new(())),\n            init_page_1,\n        )\n        .unwrap();\n        run_until_done(|| pager.allocate_page1(), &pager).unwrap();\n        {\n            let page_cache = pager.page_cache.read();\n            println!(\n                \"Cache Len: {} Cap: {}\",\n                page_cache.len(),\n                page_cache.capacity()\n            );\n        }\n        pager\n            .io\n            .block(|| {\n                pager.with_header_mut(|header| header.vacuum_mode_largest_root_page = 1.into())\n            })\n            .unwrap();\n        pager.set_auto_vacuum_mode(AutoVacuumMode::Full);\n\n        //  Allocate all the pages as btree root pages\n        const EXPECTED_FIRST_ROOT_PAGE_ID: u32 = 3; // page1 = 1,  first ptrmap page = 2, root page = 3\n        for i in 0..initial_db_pages {\n            let res = run_until_done(\n                || pager.btree_create(&CreateBTreeFlags::new_table()),\n                &pager,\n            );\n            {\n                let page_cache = pager.page_cache.read();\n                println!(\n                    \"i: {} Cache Len: {} Cap: {}\",\n                    i,\n                    page_cache.len(),\n                    page_cache.capacity()\n                );\n            }\n            match res {\n                Ok(root_page_id) => {\n                    assert_eq!(root_page_id, EXPECTED_FIRST_ROOT_PAGE_ID + i);\n                }\n                Err(e) => {\n                    panic!(\"test_pager_setup: btree_create failed: {e:?}\");\n                }\n            }\n        }\n\n        pager\n    }\n\n    #[test]\n    fn test_ptrmap_page_allocation() {\n        let page_size = 4096;\n        let initial_db_pages = 10;\n        let pager = test_pager_setup(page_size, initial_db_pages);\n\n        // Page 5 should be mapped by ptrmap page 2.\n        let db_page_to_update: u32 = 5;\n        let expected_ptrmap_pg_no =\n            get_ptrmap_page_no_for_db_page(db_page_to_update, page_size as usize);\n        assert_eq!(expected_ptrmap_pg_no, FIRST_PTRMAP_PAGE_NO);\n\n        //  Ensure the pointer map page ref is created and loadable via the pager\n        let ptrmap_page_ref = pager.read_page(expected_ptrmap_pg_no as i64);\n        assert!(ptrmap_page_ref.is_ok());\n\n        //  Ensure that the database header size is correctly reflected\n        assert_eq!(\n            pager\n                .io\n                .block(|| pager.with_header(|header| header.database_size))\n                .unwrap()\n                .get(),\n            initial_db_pages + 2\n        ); // (1+1) -> (header + ptrmap)\n\n        //  Read the entry from the ptrmap page and verify it\n        let entry = pager\n            .io\n            .block(|| pager.ptrmap_get(db_page_to_update))\n            .unwrap()\n            .unwrap();\n        assert_eq!(entry.entry_type, PtrmapType::RootPage);\n        assert_eq!(entry.parent_page_no, 0);\n    }\n\n    #[test]\n    fn test_is_ptrmap_page_logic() {\n        let page_size = PageSize::MIN as usize;\n        let n_data_pages = entries_per_ptrmap_page(page_size);\n        assert_eq!(n_data_pages, 102); //   512/5 = 102\n\n        assert!(!is_ptrmap_page(1, page_size)); // Header\n        assert!(is_ptrmap_page(2, page_size)); // P0\n        assert!(!is_ptrmap_page(3, page_size)); // D0_1\n        assert!(!is_ptrmap_page(4, page_size)); // D0_2\n        assert!(!is_ptrmap_page(5, page_size)); // D0_3\n        assert!(is_ptrmap_page(105, page_size)); // P1\n        assert!(!is_ptrmap_page(106, page_size)); // D1_1\n        assert!(!is_ptrmap_page(107, page_size)); // D1_2\n        assert!(!is_ptrmap_page(108, page_size)); // D1_3\n        assert!(is_ptrmap_page(208, page_size)); // P2\n    }\n\n    #[test]\n    fn test_get_ptrmap_page_no() {\n        let page_size = PageSize::MIN as usize; // Maps 103 data pages\n\n        // Test pages mapped by P0 (page 2)\n        assert_eq!(get_ptrmap_page_no_for_db_page(3, page_size), 2); // D(3) -> P0(2)\n        assert_eq!(get_ptrmap_page_no_for_db_page(4, page_size), 2); // D(4) -> P0(2)\n        assert_eq!(get_ptrmap_page_no_for_db_page(5, page_size), 2); // D(5) -> P0(2)\n        assert_eq!(get_ptrmap_page_no_for_db_page(104, page_size), 2); // D(104) -> P0(2)\n\n        assert_eq!(get_ptrmap_page_no_for_db_page(105, page_size), 105); // Page 105 is a pointer map page.\n\n        // Test pages mapped by P1 (page 6)\n        assert_eq!(get_ptrmap_page_no_for_db_page(106, page_size), 105); // D(106) -> P1(105)\n        assert_eq!(get_ptrmap_page_no_for_db_page(107, page_size), 105); // D(107) -> P1(105)\n        assert_eq!(get_ptrmap_page_no_for_db_page(108, page_size), 105); // D(108) -> P1(105)\n\n        assert_eq!(get_ptrmap_page_no_for_db_page(208, page_size), 208); // Page 208 is a pointer map page.\n    }\n\n    #[test]\n    fn test_get_ptrmap_offset() {\n        let page_size = PageSize::MIN as usize; //  Maps 103 data pages\n\n        assert_eq!(get_ptrmap_offset_in_page(3, 2, page_size).unwrap(), 0);\n        assert_eq!(\n            get_ptrmap_offset_in_page(4, 2, page_size).unwrap(),\n            PTRMAP_ENTRY_SIZE\n        );\n        assert_eq!(\n            get_ptrmap_offset_in_page(5, 2, page_size).unwrap(),\n            2 * PTRMAP_ENTRY_SIZE\n        );\n\n        //  P1 (page 105) maps D(106)...D(207)\n        // D(106) is index 0 on P1. Offset 0.\n        // D(107) is index 1 on P1. Offset 5.\n        // D(108) is index 2 on P1. Offset 10.\n        assert_eq!(get_ptrmap_offset_in_page(106, 105, page_size).unwrap(), 0);\n        assert_eq!(\n            get_ptrmap_offset_in_page(107, 105, page_size).unwrap(),\n            PTRMAP_ENTRY_SIZE\n        );\n        assert_eq!(\n            get_ptrmap_offset_in_page(108, 105, page_size).unwrap(),\n            2 * PTRMAP_ENTRY_SIZE\n        );\n    }\n}\n"
  },
  {
    "path": "core/storage/slot_bitmap.rs",
    "content": "use crate::sync::atomic::{AtomicU64, AtomicUsize, Ordering};\nuse crate::turso_assert;\n\n/// Shared constants for bitmap operations.\nconst WORD_SHIFT: u32 = 6;\nconst WORD_BITS: u32 = 64;\nconst WORD_MASK: u32 = 63;\n\nconst ALL_FREE: u64 = u64::MAX;\nconst ALL_ALLOCATED: u64 = 0u64;\n\n#[inline]\nconst fn word_and_bit_to_slot(word_idx: usize, bit: u32) -> u32 {\n    (word_idx as u32) << WORD_SHIFT | bit\n}\n\n#[inline]\nconst fn slot_to_word_and_bit(slot_idx: u32) -> (usize, u32) {\n    ((slot_idx >> WORD_SHIFT) as usize, slot_idx & WORD_MASK)\n}\n\n/// Lock-free atomic bitmap for tracking allocated slots in an arena.\n///\n/// Bit meaning:\n/// - 1 = free\n/// - 0 = allocated\n///\n/// `alloc_one` is lock-free (CAS retry bounded by contention, not blocking).\n/// `free_one` is wait-free (single `fetch_or`).\npub(super) struct AtomicSlotBitmap {\n    words: Box<[AtomicU64]>,\n    n_slots: u32,\n    /// Performance hint for where to start scanning. Not correctness-critical.\n    next_word_hint: AtomicUsize,\n}\n\n// SAFETY: All fields are atomics or immutable after construction.\nunsafe impl Send for AtomicSlotBitmap {}\nunsafe impl Sync for AtomicSlotBitmap {}\n\nimpl std::fmt::Debug for AtomicSlotBitmap {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"AtomicSlotBitmap\")\n            .field(\"n_slots\", &self.n_slots)\n            .finish()\n    }\n}\n\nimpl AtomicSlotBitmap {\n    /// Creates a new `AtomicSlotBitmap` capable of tracking `n_slots` slots.\n    /// All slots start as free.\n    pub fn new(n_slots: u32) -> Self {\n        turso_assert!(n_slots > 0, \"number of slots must be non-zero\");\n        turso_assert!(\n            n_slots % WORD_BITS == 0,\n            \"number of slots in map must be a multiple of 64\"\n        );\n        let n_words = (n_slots / WORD_BITS) as usize;\n        let words: Vec<AtomicU64> = (0..n_words).map(|_| AtomicU64::new(ALL_FREE)).collect();\n        Self {\n            words: words.into_boxed_slice(),\n            n_slots,\n            next_word_hint: AtomicUsize::new(0),\n        }\n    }\n\n    /// Returns whether a slot is currently free (snapshot, may be stale).\n    pub fn is_free(&self, slot: u32) -> bool {\n        if slot >= self.n_slots {\n            return false;\n        }\n        let (word_idx, bit) = slot_to_word_and_bit(slot);\n        (self.words[word_idx].load(Ordering::Acquire) & (1u64 << bit)) != 0\n    }\n\n    /// Allocates a single free slot from the bitmap. Lock-free.\n    ///\n    /// Returns `Some(slot_index)` on success, `None` if all slots are allocated.\n    pub fn alloc_one(&self) -> Option<u32> {\n        let n_words = self.words.len();\n        if n_words == 0 {\n            return None;\n        }\n\n        let hint = self.next_word_hint.load(Ordering::Acquire).min(n_words - 1);\n\n        // Scan from hint to end, then wrap around from 0 to hint.\n        for offset in 0..n_words {\n            let word_idx = (hint + offset) % n_words;\n            let mut word = self.words[word_idx].load(Ordering::Acquire);\n\n            while word != ALL_ALLOCATED {\n                let bit = word.trailing_zeros();\n                let new_word = word & !(1u64 << bit);\n\n                match self.words[word_idx].compare_exchange_weak(\n                    word,\n                    new_word,\n                    Ordering::AcqRel,\n                    Ordering::Acquire,\n                ) {\n                    Ok(_) => {\n                        // Update hint: if word is now fully allocated, advance past it.\n                        let new_hint = if new_word == ALL_ALLOCATED {\n                            (word_idx + 1) % n_words\n                        } else {\n                            word_idx\n                        };\n                        self.next_word_hint.store(new_hint, Ordering::Release);\n                        return Some(word_and_bit_to_slot(word_idx, bit));\n                    }\n                    Err(actual) => {\n                        // CAS failed — another thread changed this word. Retry with fresh value.\n                        word = actual;\n                    }\n                }\n            }\n        }\n        None\n    }\n\n    /// Frees a previously allocated slot. Wait-free (single atomic op).\n    pub fn free_one(&self, slot: u32) {\n        turso_assert!(slot < self.n_slots, \"free_one out of bounds\");\n        let (word_idx, bit) = slot_to_word_and_bit(slot);\n        let mask = 1u64 << bit;\n        let old = self.words[word_idx].fetch_or(mask, Ordering::Release);\n        debug_assert!((old & mask) == 0, \"double-free detected for slot {slot}\");\n        // If this word is before the current hint, pull the hint back.\n        let hint = self.next_word_hint.load(Ordering::Acquire);\n        if word_idx < hint {\n            self.next_word_hint.store(word_idx, Ordering::Release);\n        }\n    }\n}\n\n#[cfg(test)]\npub mod tests {\n    use super::*;\n    use rand::{rngs::StdRng, Rng, SeedableRng};\n\n    fn atomic_free_vec(ab: &AtomicSlotBitmap) -> Vec<bool> {\n        (0..ab.n_slots).map(|i| ab.is_free(i)).collect()\n    }\n\n    fn assert_equivalent(ab: &AtomicSlotBitmap, model: &[bool]) {\n        let av = atomic_free_vec(ab);\n        assert_eq!(av, model, \"bitmap bits disagree with reference model\");\n    }\n\n    #[test]\n    fn alloc_one_exhausts_all() {\n        let ab = AtomicSlotBitmap::new(256);\n        let mut model = vec![true; 256];\n\n        let mut count = 0;\n        while let Some(idx) = ab.alloc_one() {\n            assert!(model[idx as usize], \"must be free in model\");\n            model[idx as usize] = false;\n            count += 1;\n        }\n        assert_eq!(count, 256, \"should allocate all slots once\");\n        assert!(ab.alloc_one().is_none(), \"no slots left\");\n        assert_equivalent(&ab, &model);\n    }\n\n    #[test]\n    fn free_one_allows_reuse() {\n        let ab = AtomicSlotBitmap::new(128);\n        let mut model = vec![true; 128];\n\n        let a = ab.alloc_one().unwrap();\n        let b = ab.alloc_one().unwrap();\n        model[a as usize] = false;\n        model[b as usize] = false;\n\n        ab.free_one(a);\n        model[a as usize] = true;\n        assert_equivalent(&ab, &model);\n\n        let c = ab.alloc_one().unwrap();\n        model[c as usize] = false;\n        assert_equivalent(&ab, &model);\n    }\n\n    #[test]\n    fn freeing_earlier_slot_updates_hint() {\n        let ab = AtomicSlotBitmap::new(64);\n        let mut allocated = Vec::new();\n        while let Some(s) = ab.alloc_one() {\n            allocated.push(s);\n        }\n        let freed = allocated[0];\n        ab.free_one(freed);\n        assert_eq!(ab.alloc_one(), Some(freed));\n    }\n\n    #[test]\n    fn fuzz_alloc_free_compare_with_reference_model() {\n        let seeds: &[u64] = &[\n            std::time::SystemTime::UNIX_EPOCH\n                .elapsed()\n                .unwrap_or_default()\n                .as_secs(),\n            1234567890,\n            0x69420,\n            94822,\n            165029,\n        ];\n        for &seed in seeds {\n            let mut rng = StdRng::seed_from_u64(seed);\n            let n_slots = rng.random_range(1..10) * 64;\n            let ab = AtomicSlotBitmap::new(n_slots);\n            let mut model = vec![true; n_slots as usize];\n\n            for _ in 0..2000usize {\n                match rng.random_range(0..100) {\n                    0..=59 => {\n                        let got = ab.alloc_one();\n                        if let Some(i) = got {\n                            assert!(i < n_slots, \"index in range\");\n                            assert!(model[i as usize], \"bit must be free\");\n                            model[i as usize] = false;\n                        } else {\n                            assert!(\n                                !model.iter().any(|&b| b),\n                                \"allocator returned None but a free slot exists\"\n                            );\n                        }\n                    }\n                    _ => {\n                        let idx = rng.random_range(0..model.len());\n                        if !model[idx] {\n                            ab.free_one(idx as u32);\n                            model[idx] = true;\n                        }\n                    }\n                }\n                assert_equivalent(&ab, &model);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "core/storage/sqlite3_ondisk.rs",
    "content": "//! SQLite on-disk file format.\n//!\n//! SQLite stores data in a single database file, which is divided into fixed-size\n//! pages:\n//!\n//! ```text\n//! +----------+----------+----------+-----------------------------+----------+\n//! |          |          |          |                             |          |\n//! |  Page 1  |  Page 2  |  Page 3  |           ...               |  Page N  |\n//! |          |          |          |                             |          |\n//! +----------+----------+----------+-----------------------------+----------+\n//! ```\n//!\n//! The first page is special because it contains a 100 byte header at the beginning.\n//!\n//! Each page consists of a page header and N cells, which contain the records.\n//!\n//! ```text\n//! +-----------------+----------------+---------------------+----------------+\n//! |                 |                |                     |                |\n//! |   Page header   |  Cell pointer  |     Unallocated     |  Cell content  |\n//! | (8 or 12 bytes) |     array      |        space        |      area      |\n//! |                 |                |                     |                |\n//! +-----------------+----------------+---------------------+----------------+\n//! ```\n//!\n//! The write-ahead log (WAL) is a separate file that contains the physical\n//! log of changes to a database file. The file starts with a WAL header and\n//! is followed by a sequence of WAL frames, which are database pages with\n//! additional metadata.\n//!\n//! ```text\n//! +-----------------+-----------------+-----------------+-----------------+\n//! |                 |                 |                 |                 |\n//! |    WAL header   |    WAL frame 1  |    WAL frame 2  |    WAL frame N  |\n//! |                 |                 |                 |                 |\n//! +-----------------+-----------------+-----------------+-----------------+\n//! ```\n//!\n//! For more information, see the SQLite file format specification:\n//!\n//! https://www.sqlite.org/fileformat.html\n\n#![allow(clippy::arc_with_non_send_sync)]\n\nuse crate::{turso_assert, turso_assert_eq, turso_assert_greater_than};\nuse branches::{mark_unlikely, unlikely};\nuse bytemuck::{Pod, Zeroable};\nuse pack1::{I32BE, U16BE, U32BE};\nuse tracing::{instrument, Level};\n\nuse super::pager::PageRef;\npub use super::pager::{PageContent, PageInner};\nuse super::wal::TursoRwLock;\nuse crate::error::LimboError;\nuse crate::fast_lock::SpinLock;\nuse crate::io::{Buffer, Completion, FileSyncType, ReadComplete};\nuse crate::numeric::Numeric;\nuse crate::storage::btree::{payload_overflow_threshold_max, payload_overflow_threshold_min};\nuse crate::storage::buffer_pool::BufferPool;\nuse crate::storage::database::{DatabaseStorage, EncryptionOrChecksum};\nuse crate::storage::pager::Pager;\nuse crate::storage::wal::READMARK_NOT_USED;\nuse crate::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, AtomicUsize, Ordering};\nuse crate::sync::Arc;\nuse crate::sync::RwLock;\nuse crate::types::{SerialType, SerialTypeKind, TextRef, TextSubtype, ValueRef};\nuse crate::{bail_corrupt_error, CompletionError, File, IOContext, Result, WalFileShared};\nuse rustc_hash::FxHashMap;\nuse std::collections::BTreeMap;\nuse std::pin::Pin;\n\n/// The minimum size of a cell in bytes.\npub const MINIMUM_CELL_SIZE: usize = 4;\n\npub const CELL_PTR_SIZE_BYTES: usize = 2;\npub const INTERIOR_PAGE_HEADER_SIZE_BYTES: usize = 12;\npub const LEAF_PAGE_HEADER_SIZE_BYTES: usize = 8;\npub const LEFT_CHILD_PTR_SIZE_BYTES: usize = 4;\n\n// Freelist trunk page layout:\n// - Bytes 0-3: Page number of next freelist trunk page (0 if none)\n// - Bytes 4-7: Number of leaf page pointers on this trunk page\n// - Bytes 8+: Array of 4-byte leaf page pointers\npub const FREELIST_TRUNK_OFFSET_NEXT_TRUNK_PTR: usize = 0;\npub const FREELIST_TRUNK_OFFSET_LEAF_COUNT: usize = 4;\npub const FREELIST_TRUNK_OFFSET_FIRST_LEAF_PTR: usize = 8;\npub const FREELIST_TRUNK_HEADER_SIZE: usize = 8;\npub const FREELIST_LEAF_PTR_SIZE: usize = 4;\n\n#[derive(PartialEq, Eq, Zeroable, Pod, Clone, Copy, Debug)]\n#[repr(transparent)]\n/// Read/Write file format version.\npub struct PageSize(U16BE);\n\nimpl PageSize {\n    pub const MIN: u32 = 512;\n    pub const MAX: u32 = 65536;\n    pub const DEFAULT: u16 = 4096;\n\n    /// Interpret a user-provided u32 as either a valid page size or None.\n    pub const fn new(size: u32) -> Option<Self> {\n        if size < PageSize::MIN || size > PageSize::MAX {\n            return None;\n        }\n\n        // Page size must be a power of two.\n        if size.count_ones() != 1 {\n            return None;\n        }\n\n        if size == PageSize::MAX {\n            // Internally, the value 1 represents 65536, since the on-disk value of the page size in the DB header is 2 bytes.\n            return Some(Self(U16BE::new(1)));\n        }\n\n        Some(Self(U16BE::new(size as u16)))\n    }\n\n    /// Interpret a u16 on disk (DB file header) as either a valid page size or\n    /// return a corrupt error.\n    pub fn new_from_header_u16(value: u16) -> Result<Self> {\n        match value {\n            1 => Ok(Self(U16BE::new(1))),\n            n => {\n                let Some(size) = Self::new(n as u32) else {\n                    bail_corrupt_error!(\"invalid page size in database header: {n}\");\n                };\n\n                Ok(size)\n            }\n        }\n    }\n\n    pub const fn get(self) -> u32 {\n        match self.0.get() {\n            1 => Self::MAX,\n            v => v as u32,\n        }\n    }\n\n    /// Get the raw u16 value stored internally\n    pub const fn get_raw(self) -> u16 {\n        self.0.get()\n    }\n}\n\nimpl Default for PageSize {\n    fn default() -> Self {\n        Self(U16BE::new(Self::DEFAULT))\n    }\n}\n\n#[derive(PartialEq, Eq, Zeroable, Pod, Clone, Copy, Debug)]\n#[repr(transparent)]\n/// Read/Write file format version.\npub struct CacheSize(I32BE);\n\nimpl CacheSize {\n    // The negative value means that we store the amount of pages a XKiB of memory can hold.\n    // We can calculate \"real\" cache size by diving by page size.\n    pub const DEFAULT: i32 = -2000;\n\n    // Minimum number of pages that cache can hold.\n    pub const MIN: i64 = super::page_cache::MINIMUM_PAGE_CACHE_SIZE_IN_PAGES as i64;\n\n    // SQLite uses this value as threshold for maximum cache size\n    pub const MAX_SAFE: i64 = 2147450880;\n\n    pub const fn new(size: i32) -> Self {\n        match size {\n            Self::DEFAULT => Self(I32BE::new(0)),\n            v => Self(I32BE::new(v)),\n        }\n    }\n\n    pub const fn get(self) -> i32 {\n        match self.0.get() {\n            0 => Self::DEFAULT,\n            v => v,\n        }\n    }\n}\n\nimpl Default for CacheSize {\n    fn default() -> Self {\n        Self(I32BE::new(Self::DEFAULT))\n    }\n}\n\n/// Read/Write file format version.\n#[derive(PartialEq, Eq, Clone, Copy, Debug)]\n#[repr(u8)]\npub enum Version {\n    Legacy = 1,\n    Wal = 2,\n    Mvcc = 255,\n}\n\nimpl Version {\n    #[inline]\n    pub fn wal(&self) -> bool {\n        matches!(self, Self::Wal)\n    }\n\n    #[inline]\n    pub fn mvcc(&self) -> bool {\n        matches!(self, Self::Mvcc)\n    }\n\n    #[inline]\n    pub fn legacy(&self) -> bool {\n        matches!(self, Self::Legacy)\n    }\n}\n\nimpl TryFrom<u8> for Version {\n    type Error = u8;\n\n    fn try_from(value: u8) -> std::result::Result<Self, Self::Error> {\n        match value {\n            1 => Ok(Version::Legacy),\n            2 => Ok(Version::Wal),\n            255 => Ok(Version::Mvcc),\n            v => Err(v),\n        }\n    }\n}\n\n/// Raw version byte for use in DatabaseHeader where Pod is required.\n/// Use `Version::try_from(raw.0)` to convert to the validated enum.\n#[derive(PartialEq, Eq, Zeroable, Pod, Clone, Copy)]\n#[repr(transparent)]\npub struct RawVersion(pub u8);\n\nimpl RawVersion {\n    pub fn to_version(self) -> std::result::Result<Version, u8> {\n        Version::try_from(self.0)\n    }\n}\n\nimpl From<Version> for RawVersion {\n    fn from(v: Version) -> Self {\n        Self(v as u8)\n    }\n}\n\nimpl std::fmt::Debug for RawVersion {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self.to_version() {\n            Ok(v) => write!(f, \"{v:?}\"),\n            Err(v) => write!(f, \"RawVersion::Invalid({v})\"),\n        }\n    }\n}\n\n#[derive(PartialEq, Eq, Zeroable, Pod, Clone, Copy)]\n#[repr(transparent)]\n/// Text encoding.\npub struct TextEncoding(U32BE);\n\nimpl TextEncoding {\n    #![allow(non_upper_case_globals)]\n    // SQLite doesn't write the text encoding bytes until the first table is written, so when\n    // opening an empty SQLite file, the encoding bytes will be 0. SQLite considers this to mean UTF-8.\n    pub const Unset: Self = Self(U32BE::new(0));\n    pub const Utf8: Self = Self(U32BE::new(1));\n    pub const Utf16Le: Self = Self(U32BE::new(2));\n    pub const Utf16Be: Self = Self(U32BE::new(3));\n\n    pub fn is_utf8(&self) -> bool {\n        self == &Self::Utf8 || self == &Self::Unset\n    }\n}\n\nimpl std::fmt::Display for TextEncoding {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match *self {\n            Self::Utf8 => f.write_str(\"UTF-8\"),\n            Self::Utf16Le => f.write_str(\"UTF-16le\"),\n            Self::Utf16Be => f.write_str(\"UTF-16be\"),\n            Self(v) => write!(f, \"TextEncoding::Invalid({})\", v.get()),\n        }\n    }\n}\n\nimpl std::fmt::Debug for TextEncoding {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match *self {\n            Self::Utf8 => f.write_str(\"TextEncoding::Utf8\"),\n            Self::Utf16Le => f.write_str(\"TextEncoding::Utf16Le\"),\n            Self::Utf16Be => f.write_str(\"TextEncoding::Utf16Be\"),\n            Self(v) => write!(f, \"TextEncoding::Invalid({})\", v.get()),\n        }\n    }\n}\n\nimpl Default for TextEncoding {\n    fn default() -> Self {\n        Self::Utf8\n    }\n}\n\n#[derive(Pod, Zeroable, Clone, Copy, Debug)]\n#[repr(C, packed)]\n/// Database Header Format\npub struct DatabaseHeader {\n    /// b\"SQLite format 3\\0\"\n    pub magic: [u8; 16],\n    /// Page size in bytes. Must be a power of two between 512 and 32768 inclusive, or the value 1 representing a page size of 65536.\n    pub page_size: PageSize,\n    /// File format write version. 1 for legacy; 2 for WAL.\n    pub write_version: RawVersion,\n    /// File format read version. 1 for legacy; 2 for WAL.\n    pub read_version: RawVersion,\n    /// Bytes of unused \"reserved\" space at the end of each page. Usually 0.\n    pub reserved_space: u8,\n    /// Maximum embedded payload fraction. Must be 64.\n    pub max_embed_frac: u8,\n    /// Minimum embedded payload fraction. Must be 32.\n    pub min_embed_frac: u8,\n    /// Leaf payload fraction. Must be 32.\n    pub leaf_frac: u8,\n    /// File change counter.\n    pub change_counter: U32BE,\n    /// Size of the database file in pages. The \"in-header database size\".\n    pub database_size: U32BE,\n    /// Page number of the first freelist trunk page.\n    pub freelist_trunk_page: U32BE,\n    /// Total number of freelist pages.\n    pub freelist_pages: U32BE,\n    /// The schema cookie.\n    pub schema_cookie: U32BE,\n    /// The schema format number. Supported schema formats are 1, 2, 3, and 4.\n    pub schema_format: U32BE,\n    /// Default page cache size.\n    pub default_page_cache_size: CacheSize,\n    /// The page number of the largest root b-tree page when in auto-vacuum or incremental-vacuum modes, or zero otherwise.\n    pub vacuum_mode_largest_root_page: U32BE,\n    /// Text encoding.\n    pub text_encoding: TextEncoding,\n    /// The \"user version\" as read and set by the user_version pragma.\n    pub user_version: I32BE,\n    /// True (non-zero) for incremental-vacuum mode. False (zero) otherwise.\n    pub incremental_vacuum_enabled: U32BE,\n    /// The \"Application ID\" set by PRAGMA application_id.\n    pub application_id: I32BE,\n    /// Reserved for expansion. Must be zero.\n    _padding: [u8; 20],\n    /// The version-valid-for number.\n    pub version_valid_for: U32BE,\n    /// SQLITE_VERSION_NUMBER\n    pub version_number: U32BE,\n}\n\nimpl DatabaseHeader {\n    pub const PAGE_ID: usize = 1;\n    pub const SIZE: usize = size_of::<Self>();\n\n    const _CHECK: () = {\n        assert!(Self::SIZE == 100);\n    };\n\n    pub fn usable_space(self) -> usize {\n        (self.page_size.get() as usize) - (self.reserved_space as usize)\n    }\n}\n\nimpl Default for DatabaseHeader {\n    fn default() -> Self {\n        Self {\n            magic: *b\"SQLite format 3\\0\",\n            page_size: Default::default(),\n            write_version: RawVersion::from(Version::Wal),\n            read_version: RawVersion::from(Version::Wal),\n            reserved_space: 0,\n            max_embed_frac: 64,\n            min_embed_frac: 32,\n            leaf_frac: 32,\n            change_counter: U32BE::new(1),\n            database_size: U32BE::new(0),\n            freelist_trunk_page: U32BE::new(0),\n            freelist_pages: U32BE::new(0),\n            schema_cookie: U32BE::new(0),\n            schema_format: U32BE::new(4), // latest format, new sqlite3 databases use this format\n            default_page_cache_size: Default::default(),\n            vacuum_mode_largest_root_page: U32BE::new(0),\n            text_encoding: TextEncoding::Utf8,\n            user_version: I32BE::new(0),\n            incremental_vacuum_enabled: U32BE::new(0),\n            application_id: I32BE::new(0),\n            _padding: [0; 20],\n            version_valid_for: U32BE::new(3047000),\n            version_number: U32BE::new(3047000),\n        }\n    }\n}\n\npub const WAL_HEADER_SIZE: usize = 32;\npub const WAL_FRAME_HEADER_SIZE: usize = 24;\n// magic is a single number represented as WAL_MAGIC_LE but the big endian\n// counterpart is just the same number with LSB set to 1.\npub const WAL_MAGIC_LE: u32 = 0x377f0682;\npub const WAL_MAGIC_BE: u32 = 0x377f0683;\n\n/// The Write-Ahead Log (WAL) header.\n/// The first 32 bytes of a WAL file comprise the WAL header.\n/// The WAL header is divided into the following fields stored in big-endian order.\n#[derive(Debug, Clone, Copy)]\n#[repr(C)] // This helps with encoding because rust does not respect the order in structs, so in\n           // this case we want to keep the order\npub struct WalHeader {\n    /// Magic number. 0x377f0682 or 0x377f0683\n    /// If the LSB is 0, checksums are native byte order, else checksums are serialized\n    pub magic: u32,\n\n    /// WAL format version. Currently 3007000\n    pub file_format: u32,\n\n    /// Database page size in bytes. Power of two between 512 and 65536 inclusive\n    pub page_size: u32,\n\n    /// Checkpoint sequence number. Increases with each checkpoint\n    pub checkpoint_seq: u32,\n\n    /// Random value used for the first salt in checksum calculations\n    /// TODO: Incremented with each checkpoint\n    pub salt_1: u32,\n\n    /// Random value used for the second salt in checksum calculations.\n    /// TODO: A different random value for each checkpoint\n    pub salt_2: u32,\n\n    /// First checksum value in the wal-header\n    pub checksum_1: u32,\n\n    /// Second checksum value in the wal-header\n    pub checksum_2: u32,\n}\n\nimpl WalHeader {\n    pub const fn new() -> Self {\n        let magic = if cfg!(target_endian = \"big\") {\n            WAL_MAGIC_BE\n        } else {\n            WAL_MAGIC_LE\n        };\n        WalHeader {\n            magic,\n            file_format: 3007000,\n            page_size: 0, // Signifies WAL header that is not persistent on disk yet.\n            checkpoint_seq: 0, // TODO implement sequence number\n            salt_1: 0,\n            salt_2: 0,\n            checksum_1: 0,\n            checksum_2: 0,\n        }\n    }\n}\n\nimpl Default for WalHeader {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\n/// Immediately following the wal-header are zero or more frames.\n/// Each frame consists of a 24-byte frame-header followed by <page-size> bytes of page data.\n/// The frame-header is six big-endian 32-bit unsigned integer values, as follows:\n#[allow(dead_code)]\n#[derive(Debug, Default, Copy, Clone)]\npub struct WalFrameHeader {\n    /// Page number\n    pub(crate) page_number: u32,\n\n    /// For commit records, the size of the database file in pages after the commit.\n    /// For all other records, zero.\n    pub(crate) db_size: u32,\n\n    /// Salt-1 copied from the WAL header\n    pub(crate) salt_1: u32,\n\n    /// Salt-2 copied from the WAL header\n    pub(crate) salt_2: u32,\n\n    /// Checksum-1: Cumulative checksum up through and including this page\n    pub(crate) checksum_1: u32,\n\n    /// Checksum-2: Second half of the cumulative checksum\n    pub(crate) checksum_2: u32,\n}\n\nimpl WalFrameHeader {\n    pub fn is_commit_frame(&self) -> bool {\n        self.db_size > 0\n    }\n}\n\n#[repr(u8)]\n#[derive(Debug, PartialEq, Clone, Copy)]\npub enum PageType {\n    IndexInterior = 2,\n    TableInterior = 5,\n    IndexLeaf = 10,\n    TableLeaf = 13,\n}\n\nimpl PageType {\n    pub fn is_table(&self) -> bool {\n        match self {\n            PageType::IndexInterior | PageType::IndexLeaf => false,\n            PageType::TableInterior | PageType::TableLeaf => true,\n        }\n    }\n}\n\nimpl TryFrom<u8> for PageType {\n    type Error = LimboError;\n\n    fn try_from(value: u8) -> Result<Self> {\n        match value {\n            2 => Ok(Self::IndexInterior),\n            5 => Ok(Self::TableInterior),\n            10 => Ok(Self::IndexLeaf),\n            13 => Ok(Self::TableLeaf),\n            _ => {\n                mark_unlikely();\n                Err(LimboError::Corrupt(format!(\"Invalid page type: {value}\")))\n            }\n        }\n    }\n}\n\n#[derive(Debug, Clone)]\npub struct OverflowCell {\n    pub index: usize,\n    pub payload: Pin<Vec<u8>>,\n}\n\n/// Send read request for DB page read to the IO\n/// if allow_empty_read is set, than empty read will be raise error for the page, but will not panic\n#[instrument(skip_all, level = Level::DEBUG)]\npub fn begin_read_page(\n    db_file: &dyn DatabaseStorage,\n    buffer_pool: Arc<BufferPool>,\n    page: PageRef,\n    page_idx: usize,\n    allow_empty_read: bool,\n    io_ctx: &IOContext,\n) -> Result<Completion> {\n    tracing::trace!(\"begin_read_btree_page(page_idx = {})\", page_idx);\n    let buf = buffer_pool.get_page();\n    #[allow(clippy::arc_with_non_send_sync)]\n    let buf = Arc::new(buf);\n    let complete = Box::new(move |res: Result<(Arc<Buffer>, i32), CompletionError>| {\n        let Ok((buf, bytes_read)) = res else {\n            page.clear_locked();\n            return None; // IO error already captured in completion\n        };\n        let buf_len = buf.len();\n        // Handle truncated database files: if we read fewer bytes than expected\n        // (and it's not an intentional empty read), return a ShortRead error.\n        if bytes_read == 0 {\n            if !allow_empty_read {\n                tracing::error!(\"short read on page {page_idx}: expected {buf_len} bytes, got 0\");\n                page.clear_locked();\n                return Some(CompletionError::ShortRead {\n                    page_idx,\n                    expected: buf_len,\n                    actual: 0,\n                });\n            }\n        } else if bytes_read != buf_len as i32 {\n            tracing::error!(\n                \"short read on page {page_idx}: expected {buf_len} bytes, got {bytes_read}\"\n            );\n            page.clear_locked();\n            return Some(CompletionError::ShortRead {\n                page_idx,\n                expected: buf_len,\n                actual: bytes_read as usize,\n            });\n        }\n        let page = page.clone();\n        let buffer = if bytes_read == 0 {\n            Arc::new(Buffer::new_temporary(0))\n        } else {\n            buf\n        };\n        finish_read_page(page_idx, buffer, page);\n        None\n    });\n    let c = Completion::new_read(buf, complete);\n    db_file.read_page(page_idx, io_ctx, c)\n}\n\n#[instrument(skip_all, level = Level::DEBUG)]\npub fn finish_read_page(page_idx: usize, buffer: Arc<Buffer>, page: PageRef) {\n    tracing::trace!(\"finish_read_page(page_idx = {page_idx})\");\n    {\n        let inner = page.get();\n        inner.buffer = Some(buffer);\n        page.clear_locked();\n        page.set_loaded();\n        // we set the wal tag only when reading page from log, or in allocate_page,\n        // we clear it here for safety in case page is being re-loaded.\n        page.clear_wal_tag();\n    }\n}\n\n#[instrument(skip_all, level = Level::DEBUG)]\npub fn begin_write_btree_page(pager: &Pager, page: &PageRef) -> Result<Completion> {\n    tracing::trace!(\"begin_write_btree_page(page={})\", page.get().id);\n    let page_source = &pager.db_file;\n    let page_finish = page.clone();\n\n    let page_id = page.get().id;\n    tracing::trace!(\"begin_write_btree_page(page_id={})\", page_id);\n\n    let buffer = page.get().buffer.clone().expect(\"buffer not loaded\");\n    let buf_len = buffer.len();\n\n    let write_complete = {\n        Box::new(move |res: Result<i32, CompletionError>| {\n            let Ok(bytes_written) = res else {\n                return;\n            };\n            tracing::trace!(\"finish_write_btree_page\");\n\n            page_finish.clear_dirty();\n            turso_assert!(\n                bytes_written == buf_len as i32,\n                \"wrote({bytes_written}) != expected({buf_len})\"\n            );\n        })\n    };\n    let c = Completion::new_write(write_complete);\n    let io_ctx = pager.io_ctx.read();\n    page_source.write_page(page_id, buffer, &io_ctx, c)\n}\n\n#[instrument(skip_all, level = Level::DEBUG)]\n/// Write a batch of pages to the database file.\n///\n/// we have a batch of pages to write, lets say the following:\n/// (they are already sorted by id thanks to BTreeMap)\n/// [1,2,3,6,7,9,10,11,12]\n//\n/// we want to collect this into runs of:\n/// [1,2,3], [6,7], [9,10,11,12]\n/// and submit each run as a `writev` call,\n/// for 3 total syscalls instead of 9.\npub fn write_pages_vectored(\n    pager: &Pager,\n    batch: BTreeMap<usize, Arc<Buffer>>,\n    done_flag: Arc<AtomicBool>,\n    err: Arc<crate::sync::OnceLock<CompletionError>>,\n) -> Result<Vec<Completion>> {\n    if batch.is_empty() {\n        done_flag.store(true, Ordering::Release);\n        return Ok(Vec::new());\n    }\n\n    let page_sz = pager.get_page_size_unchecked().get() as usize;\n\n    let mut run_count = 0;\n    let mut prev_id = None;\n    for &id in batch.keys() {\n        if let Some(prev) = prev_id {\n            if id != prev + 1 {\n                run_count += 1;\n            }\n        } else {\n            run_count = 1;\n        }\n        prev_id = Some(id);\n    }\n\n    let runs_left = Arc::new(AtomicUsize::new(run_count));\n\n    const EST_BUFF_CAPACITY: usize = 32;\n    let mut run_bufs = Vec::with_capacity(EST_BUFF_CAPACITY);\n    let mut run_start_id: Option<usize> = None;\n    let mut completions = Vec::with_capacity(run_count);\n\n    let mut iter = batch.iter().peekable();\n    while let Some((id, buffer)) = iter.next() {\n        if run_start_id.is_none() {\n            run_start_id = Some(*id);\n        }\n        run_bufs.push(buffer.clone());\n\n        let is_end_of_run = iter.peek().is_none_or(|(next_id, _)| **next_id != id + 1);\n        if !is_end_of_run {\n            continue;\n        }\n\n        let start_id = run_start_id.take().expect(\"start id\");\n        let runs_left_cl = runs_left.clone();\n        let done_cl = done_flag.clone();\n        let err_cl = err.clone();\n\n        let expected_bytes = (page_sz * run_bufs.len()) as i32;\n\n        let cmp = Completion::new_write(move |res| {\n            // Record error/mismatch, but always resolve the batch progress.\n            match res {\n                Ok(n) => {\n                    if n != expected_bytes {\n                        let _ = err_cl.set(CompletionError::ShortWrite);\n                        tracing::error!(\n                            \"write_pages_vectored: short write: wrote({n}) != expected({expected_bytes})\"\n                        );\n                    }\n                }\n                Err(e) => {\n                    tracing::error!(\"write_pages_vectored: write error: {:?}\", e);\n                    let _ = err_cl.set(e);\n                }\n            }\n            // we have to decrement runs_left on both paths\n            if runs_left_cl.fetch_sub(1, Ordering::AcqRel) == 1 {\n                tracing::debug!(\"write_pages_vectored: run complete\");\n                done_cl.store(true, Ordering::Release);\n            }\n        });\n        let io_ctx = pager.io_ctx.read();\n        let bufs = std::mem::replace(&mut run_bufs, Vec::with_capacity(EST_BUFF_CAPACITY));\n        match pager\n            .db_file\n            .write_pages(start_id, page_sz, bufs, &io_ctx, cmp)\n        {\n            Ok(c) => completions.push(c),\n            Err(e) => {\n                // We failed to submit this run at all. Mark batch failed+done and cancel already-submitted.\n                let _ = err.set(CompletionError::Aborted);\n                done_flag.store(true, Ordering::Release);\n                pager.io.cancel(&completions)?;\n                pager.io.drain()?;\n                return Err(e);\n            }\n        }\n    }\n    Ok(completions)\n}\n\n#[instrument(skip_all, level = Level::DEBUG)]\npub fn begin_sync(\n    db_file: &dyn DatabaseStorage,\n    syncing: Arc<AtomicBool>,\n    sync_type: FileSyncType,\n) -> Result<Completion> {\n    turso_assert!(!syncing.load(Ordering::SeqCst));\n    syncing.store(true, Ordering::SeqCst);\n    let completion = Completion::new_sync(move |_| {\n        syncing.store(false, Ordering::SeqCst);\n    });\n    #[allow(clippy::arc_with_non_send_sync)]\n    db_file.sync(completion, sync_type)\n}\n\n#[allow(clippy::enum_variant_names)]\n#[derive(Debug, Clone)]\npub enum BTreeCell {\n    TableInteriorCell(TableInteriorCell),\n    TableLeafCell(TableLeafCell),\n    IndexInteriorCell(IndexInteriorCell),\n    IndexLeafCell(IndexLeafCell),\n}\n\n#[derive(Debug, Clone)]\npub struct TableInteriorCell {\n    pub left_child_page: u32,\n    pub rowid: i64,\n}\n\n#[derive(Debug, Clone)]\npub struct TableLeafCell {\n    pub rowid: i64,\n    /// Payload of cell, if it overflows it won't include overflowed payload.\n    pub payload: &'static [u8],\n    /// This is the complete payload size including overflow pages.\n    pub payload_size: u64,\n    pub first_overflow_page: Option<u32>,\n}\n\n#[derive(Debug, Clone)]\npub struct IndexInteriorCell {\n    pub left_child_page: u32,\n    pub payload: &'static [u8],\n    /// This is the complete payload size including overflow pages.\n    pub payload_size: u64,\n    pub first_overflow_page: Option<u32>,\n}\n\n#[derive(Debug, Clone)]\npub struct IndexLeafCell {\n    pub payload: &'static [u8],\n    /// This is the complete payload size including overflow pages.\n    pub payload_size: u64,\n    pub first_overflow_page: Option<u32>,\n}\n\n/// read_btree_cell contructs a BTreeCell which is basically a wrapper around pointer to the payload of a cell.\n/// buffer input \"page\" is static because we want the cell to point to the data in the page in case it has any payload.\npub fn read_btree_cell(\n    page: &'static [u8],\n    page_content: &PageContent,\n    pos: usize,\n    usable_size: usize,\n) -> Result<BTreeCell> {\n    let page_type = page_content.page_type()?;\n    let max_local = payload_overflow_threshold_max(page_type, usable_size);\n    let min_local = payload_overflow_threshold_min(page_type, usable_size);\n    match page_type {\n        PageType::IndexInterior => {\n            let mut pos = pos;\n            crate::assert_or_bail_corrupt!(\n                pos + 4 <= page.len(),\n                \"cell offset {} out of bounds for page size {}\",\n                pos,\n                page.len()\n            );\n            let left_child_page =\n                u32::from_be_bytes([page[pos], page[pos + 1], page[pos + 2], page[pos + 3]]);\n            pos += 4;\n            let (payload_size, nr) = read_varint(crate::slice_in_bounds_or_corrupt!(page, pos..))?;\n            pos += nr;\n\n            let (overflows, to_read) =\n                payload_overflows(payload_size as usize, max_local, min_local, usable_size);\n            let to_read = if overflows { to_read } else { page.len() - pos };\n\n            crate::assert_or_bail_corrupt!(\n                pos + to_read <= page.len(),\n                \"payload range {}..{} out of bounds for page size {}\",\n                pos,\n                pos + to_read,\n                page.len()\n            );\n            let (payload, first_overflow_page) =\n                read_payload(&page[pos..pos + to_read], payload_size as usize)?;\n            Ok(BTreeCell::IndexInteriorCell(IndexInteriorCell {\n                left_child_page,\n                payload,\n                first_overflow_page,\n                payload_size,\n            }))\n        }\n        PageType::TableInterior => {\n            let mut pos = pos;\n            crate::assert_or_bail_corrupt!(\n                pos + 4 <= page.len(),\n                \"cell offset {} out of bounds for page size {}\",\n                pos,\n                page.len()\n            );\n            let left_child_page =\n                u32::from_be_bytes([page[pos], page[pos + 1], page[pos + 2], page[pos + 3]]);\n            pos += 4;\n            let (rowid, _) = read_varint(crate::slice_in_bounds_or_corrupt!(page, pos..))?;\n            Ok(BTreeCell::TableInteriorCell(TableInteriorCell {\n                left_child_page,\n                rowid: rowid as i64,\n            }))\n        }\n        PageType::IndexLeaf => {\n            let mut pos = pos;\n            let (payload_size, nr) = read_varint(crate::slice_in_bounds_or_corrupt!(page, pos..))?;\n            pos += nr;\n\n            let (overflows, to_read) =\n                payload_overflows(payload_size as usize, max_local, min_local, usable_size);\n            let to_read = if overflows { to_read } else { page.len() - pos };\n\n            crate::assert_or_bail_corrupt!(\n                pos + to_read <= page.len(),\n                \"payload range {}..{} out of bounds for page size {}\",\n                pos,\n                pos + to_read,\n                page.len()\n            );\n            let (payload, first_overflow_page) =\n                read_payload(&page[pos..pos + to_read], payload_size as usize)?;\n            Ok(BTreeCell::IndexLeafCell(IndexLeafCell {\n                payload,\n                first_overflow_page,\n                payload_size,\n            }))\n        }\n        PageType::TableLeaf => {\n            let mut pos = pos;\n            let (payload_size, nr) = read_varint(crate::slice_in_bounds_or_corrupt!(page, pos..))?;\n            pos += nr;\n            let (rowid, nr) = read_varint(crate::slice_in_bounds_or_corrupt!(page, pos..))?;\n            pos += nr;\n\n            let (overflows, to_read) =\n                payload_overflows(payload_size as usize, max_local, min_local, usable_size);\n            let to_read = if overflows { to_read } else { page.len() - pos };\n\n            crate::assert_or_bail_corrupt!(\n                pos + to_read <= page.len(),\n                \"payload range {}..{} out of bounds for page size {}\",\n                pos,\n                pos + to_read,\n                page.len()\n            );\n            let (payload, first_overflow_page) =\n                read_payload(&page[pos..pos + to_read], payload_size as usize)?;\n            Ok(BTreeCell::TableLeafCell(TableLeafCell {\n                rowid: rowid as i64,\n                payload,\n                first_overflow_page,\n                payload_size,\n            }))\n        }\n    }\n}\n\n/// read_payload takes in the unread bytearray with the payload size\n/// and returns the payload on the page, and optionally the first overflow page number.\n#[allow(clippy::readonly_write_lock)]\nfn read_payload(\n    unread: &'static [u8],\n    payload_size: usize,\n) -> Result<(&'static [u8], Option<u32>)> {\n    let cell_len = unread.len();\n    // We will let overflow be constructed back if needed or requested.\n    if payload_size <= cell_len {\n        // fit within 1 page\n        Ok((&unread[..payload_size], None))\n    } else {\n        // overflow\n        if cell_len < 4 {\n            bail_corrupt_error!(\n                \"overflow cell too small: {} bytes, need at least 4\",\n                cell_len\n            );\n        }\n        let first_overflow_page = u32::from_be_bytes([\n            unread[cell_len - 4],\n            unread[cell_len - 3],\n            unread[cell_len - 2],\n            unread[cell_len - 1],\n        ]);\n        Ok((&unread[..cell_len - 4], Some(first_overflow_page)))\n    }\n}\n\n#[inline(always)]\n#[allow(dead_code)]\npub fn validate_serial_type(value: u64) -> Result<()> {\n    if !SerialType::u64_is_valid_serial_type(value) {\n        crate::bail_corrupt_error!(\"Invalid serial type: {}\", value);\n    }\n    Ok(())\n}\n\n/// Reads a value that might reference the buffer it is reading from. Be sure to store RefValue with the buffer\n/// always.\n#[inline(always)]\npub fn read_value<'a>(buf: &'a [u8], serial_type: SerialType) -> Result<(ValueRef<'a>, usize)> {\n    match serial_type.kind() {\n        SerialTypeKind::Null => Ok((ValueRef::Null, 0)),\n        SerialTypeKind::I8 => {\n            let val = *buf.first().ok_or_else(|| {\n                mark_unlikely();\n                LimboError::Corrupt(\"Invalid UInt8 value\".into())\n            })?;\n            Ok((ValueRef::Numeric(Numeric::Integer(val as i8 as i64)), 1))\n        }\n        SerialTypeKind::I16 => {\n            let bytes: &[u8; 2] =\n                buf.get(..2)\n                    .and_then(|s| s.try_into().ok())\n                    .ok_or_else(|| {\n                        mark_unlikely();\n                        LimboError::Corrupt(\"Invalid BEInt16 value\".into())\n                    })?;\n            Ok((\n                ValueRef::Numeric(Numeric::Integer(i16::from_be_bytes(*bytes) as i64)),\n                2,\n            ))\n        }\n        SerialTypeKind::I24 => {\n            let bytes: &[u8; 3] =\n                buf.get(..3)\n                    .and_then(|s| s.try_into().ok())\n                    .ok_or_else(|| {\n                        mark_unlikely();\n                        LimboError::Corrupt(\"Invalid BEInt24 value\".into())\n                    })?;\n            let sign_extension = (bytes[0] as i8 >> 7) as u8;\n            Ok((\n                ValueRef::Numeric(Numeric::Integer(i32::from_be_bytes([\n                    sign_extension,\n                    bytes[0],\n                    bytes[1],\n                    bytes[2],\n                ]) as i64)),\n                3,\n            ))\n        }\n        SerialTypeKind::I32 => {\n            let bytes: &[u8; 4] =\n                buf.get(..4)\n                    .and_then(|s| s.try_into().ok())\n                    .ok_or_else(|| {\n                        mark_unlikely();\n                        LimboError::Corrupt(\"Invalid BEInt32 value\".into())\n                    })?;\n            Ok((\n                ValueRef::Numeric(Numeric::Integer(i32::from_be_bytes(*bytes) as i64)),\n                4,\n            ))\n        }\n        SerialTypeKind::I48 => {\n            let bytes: &[u8; 6] =\n                buf.get(..6)\n                    .and_then(|s| s.try_into().ok())\n                    .ok_or_else(|| {\n                        mark_unlikely();\n                        LimboError::Corrupt(\"Invalid BEInt48 value\".into())\n                    })?;\n            let sign_extension = (bytes[0] as i8 >> 7) as u8;\n            Ok((\n                ValueRef::Numeric(Numeric::Integer(i64::from_be_bytes([\n                    sign_extension,\n                    sign_extension,\n                    bytes[0],\n                    bytes[1],\n                    bytes[2],\n                    bytes[3],\n                    bytes[4],\n                    bytes[5],\n                ]))),\n                6,\n            ))\n        }\n        SerialTypeKind::I64 => {\n            let bytes: &[u8; 8] =\n                buf.get(..8)\n                    .and_then(|s| s.try_into().ok())\n                    .ok_or_else(|| {\n                        mark_unlikely();\n                        LimboError::Corrupt(\"Invalid BEInt64 value\".into())\n                    })?;\n            Ok((\n                ValueRef::Numeric(Numeric::Integer(i64::from_be_bytes(*bytes))),\n                8,\n            ))\n        }\n        SerialTypeKind::F64 => {\n            let bytes: &[u8; 8] = buf\n                .get(..8)\n                .and_then(|s| s.try_into().ok())\n                .ok_or_else(|| LimboError::Corrupt(\"Invalid BEFloat64 value\".into()))?;\n            Ok((ValueRef::from_f64(f64::from_be_bytes(*bytes)), 8))\n        }\n        SerialTypeKind::ConstInt0 => Ok((ValueRef::Numeric(Numeric::Integer(0)), 0)),\n        SerialTypeKind::ConstInt1 => Ok((ValueRef::Numeric(Numeric::Integer(1)), 0)),\n        SerialTypeKind::Blob => {\n            let content_size = serial_type.size();\n            let data = buf.get(..content_size).ok_or_else(|| {\n                mark_unlikely();\n                LimboError::Corrupt(\"Invalid Blob value\".into())\n            })?;\n            Ok((ValueRef::Blob(data), content_size))\n        }\n        SerialTypeKind::Text => {\n            let content_size = serial_type.size();\n            let data = buf.get(..content_size).ok_or_else(|| {\n                mark_unlikely();\n                LimboError::Corrupt(format!(\n                    \"Invalid String value, length {} < expected length {}\",\n                    buf.len(),\n                    content_size\n                ))\n            })?;\n            // SAFETY: SerialTypeKind is Text so this buffer is a valid string\n            let val = unsafe { std::str::from_utf8_unchecked(data) };\n            Ok((\n                ValueRef::Text(TextRef::new(val, TextSubtype::Text)),\n                content_size,\n            ))\n        }\n    }\n}\n\npub fn read_value_serial_type<'a>(\n    buf: &'a [u8],\n    serial_type: u64,\n) -> Result<(ValueRef<'a>, usize)> {\n    match serial_type {\n        0 => Ok((ValueRef::Null, 0)),\n        1 => {\n            if buf.is_empty() {\n                mark_unlikely();\n                crate::bail_corrupt_error!(\"Invalid 1-byte int\");\n            }\n            Ok((ValueRef::Numeric(Numeric::Integer(buf[0] as i8 as i64)), 1))\n        }\n        2 => {\n            if buf.len() < 2 {\n                mark_unlikely();\n                crate::bail_corrupt_error!(\"Invalid 2-byte int\");\n            }\n            Ok((\n                ValueRef::Numeric(Numeric::Integer(i16::from_be_bytes([buf[0], buf[1]]) as i64)),\n                2,\n            ))\n        }\n        3 => {\n            if buf.len() < 3 {\n                mark_unlikely();\n                crate::bail_corrupt_error!(\"Invalid 3-byte int\");\n            }\n            let sign_extension = if buf[0] <= 0x7F { 0 } else { 0xFF };\n            Ok((\n                ValueRef::Numeric(Numeric::Integer(i32::from_be_bytes([\n                    sign_extension,\n                    buf[0],\n                    buf[1],\n                    buf[2],\n                ]) as i64)),\n                3,\n            ))\n        }\n        4 => {\n            if buf.len() < 4 {\n                mark_unlikely();\n                crate::bail_corrupt_error!(\"Invalid 4-byte int\");\n            }\n            Ok((\n                ValueRef::Numeric(Numeric::Integer(i32::from_be_bytes([\n                    buf[0], buf[1], buf[2], buf[3],\n                ]) as i64)),\n                4,\n            ))\n        }\n        5 => {\n            if buf.len() < 6 {\n                mark_unlikely();\n                crate::bail_corrupt_error!(\"Invalid 6-byte int\");\n            }\n            let sign_extension = if buf[0] <= 0x7F { 0 } else { 0xFF };\n            Ok((\n                ValueRef::Numeric(Numeric::Integer(i64::from_be_bytes([\n                    sign_extension,\n                    sign_extension,\n                    buf[0],\n                    buf[1],\n                    buf[2],\n                    buf[3],\n                    buf[4],\n                    buf[5],\n                ]))),\n                6,\n            ))\n        }\n        6 => {\n            if buf.len() < 8 {\n                mark_unlikely();\n                crate::bail_corrupt_error!(\"Invalid 8-byte int\");\n            }\n            Ok((\n                ValueRef::Numeric(Numeric::Integer(i64::from_be_bytes([\n                    buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],\n                ]))),\n                8,\n            ))\n        }\n        7 => {\n            if buf.len() < 8 {\n                mark_unlikely();\n                crate::bail_corrupt_error!(\"Invalid 8-byte float\");\n            }\n            Ok((\n                ValueRef::from_f64(f64::from_be_bytes([\n                    buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],\n                ])),\n                8,\n            ))\n        }\n        8 => Ok((ValueRef::Numeric(Numeric::Integer(0)), 0)),\n        9 => Ok((ValueRef::Numeric(Numeric::Integer(1)), 0)),\n        n if n >= 12 => match n % 2 {\n            0 => {\n                // Blob\n                let content_size = ((n - 12) / 2) as usize;\n                let data = buf.get(..content_size).ok_or_else(|| {\n                    mark_unlikely();\n                    LimboError::Corrupt(\"Invalid Blob value\".into())\n                })?;\n                Ok((ValueRef::Blob(data), content_size))\n            }\n            1 => {\n                // Text\n                let content_size = ((n - 13) / 2) as usize;\n                let data = buf.get(..content_size).ok_or_else(|| {\n                    mark_unlikely();\n                    LimboError::Corrupt(format!(\n                        \"Invalid String value, length {} < expected length {}\",\n                        buf.len(),\n                        content_size\n                    ))\n                })?;\n                // SAFETY: SerialTypeKind is Text so this buffer is a valid string\n                let val = unsafe { std::str::from_utf8_unchecked(data) };\n                Ok((\n                    ValueRef::Text(TextRef::new(val, TextSubtype::Text)),\n                    content_size,\n                ))\n            }\n            _ => unreachable!(),\n        },\n        _ => {\n            mark_unlikely();\n            crate::bail_corrupt_error!(\"Invalid serial type for integer\")\n        }\n    }\n}\n\n#[inline(always)]\npub fn read_integer(buf: &[u8], serial_type: u8) -> Result<i64> {\n    match serial_type {\n        1 => {\n            if buf.is_empty() {\n                mark_unlikely();\n                crate::bail_corrupt_error!(\"Invalid 1-byte int\");\n            }\n            Ok(buf[0] as i8 as i64)\n        }\n        2 => {\n            if buf.len() < 2 {\n                mark_unlikely();\n                crate::bail_corrupt_error!(\"Invalid 2-byte int\");\n            }\n            Ok(i16::from_be_bytes([buf[0], buf[1]]) as i64)\n        }\n        3 => {\n            if buf.len() < 3 {\n                mark_unlikely();\n                crate::bail_corrupt_error!(\"Invalid 3-byte int\");\n            }\n            let sign_extension = if buf[0] <= 0x7F { 0 } else { 0xFF };\n            Ok(i32::from_be_bytes([sign_extension, buf[0], buf[1], buf[2]]) as i64)\n        }\n        4 => {\n            if buf.len() < 4 {\n                mark_unlikely();\n                crate::bail_corrupt_error!(\"Invalid 4-byte int\");\n            }\n            Ok(i32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]) as i64)\n        }\n        5 => {\n            if buf.len() < 6 {\n                mark_unlikely();\n                crate::bail_corrupt_error!(\"Invalid 6-byte int\");\n            }\n            let sign_extension = if buf[0] <= 0x7F { 0 } else { 0xFF };\n            Ok(i64::from_be_bytes([\n                sign_extension,\n                sign_extension,\n                buf[0],\n                buf[1],\n                buf[2],\n                buf[3],\n                buf[4],\n                buf[5],\n            ]))\n        }\n        6 => {\n            if buf.len() < 8 {\n                crate::bail_corrupt_error!(\"Invalid 8-byte int\");\n            }\n            Ok(i64::from_be_bytes([\n                buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],\n            ]))\n        }\n        8 => Ok(0),\n        9 => Ok(1),\n        _ => {\n            mark_unlikely();\n            crate::bail_corrupt_error!(\"Invalid serial type for integer\")\n        }\n    }\n}\n\n/// Reads varint integer from the buffer.\n/// This function is similar to `sqlite3GetVarint32`\n#[inline(always)]\npub fn read_varint(buf: &[u8]) -> Result<(u64, usize)> {\n    let mut v: u64 = 0;\n    for i in 0..8 {\n        match buf.get(i) {\n            Some(c) => {\n                v = (v << 7) + (c & 0x7f) as u64;\n                if (c & 0x80) == 0 {\n                    return Ok((v, i + 1));\n                }\n            }\n            None => {\n                mark_unlikely();\n                crate::bail_corrupt_error!(\"Invalid varint\");\n            }\n        }\n    }\n    match buf.get(8) {\n        Some(&c) => {\n            // Values requiring 9 bytes must have non-zero in the top 8 bits (value >= 1<<56).\n            // Since the final value is `(v<<8) + c`, the top 8 bits (v >> 48) must not be 0.\n            // If those are zero, this should be treated as corrupt.\n            // Perf? the comparison + branching happens only in parsing 9-byte varint which is rare.\n            if unlikely((v >> 48) == 0) {\n                bail_corrupt_error!(\"Invalid varint\");\n            }\n            v = (v << 8) + c as u64;\n            Ok((v, 9))\n        }\n        None => {\n            mark_unlikely();\n            bail_corrupt_error!(\"Invalid varint\");\n        }\n    }\n}\n\n#[inline(always)]\n/// Reads a varint from the buffer, returning None if more data is needed.\npub fn read_varint_partial(buf: &[u8]) -> Result<Option<(u64, usize)>> {\n    let mut v: u64 = 0;\n    for i in 0..8 {\n        let Some(&c) = buf.get(i) else {\n            return Ok(None);\n        };\n        v = (v << 7) + (c & 0x7f) as u64;\n        if (c & 0x80) == 0 {\n            return Ok(Some((v, i + 1)));\n        }\n    }\n    let Some(&c) = buf.get(8) else {\n        return Ok(None);\n    };\n    if unlikely((v >> 48) == 0) {\n        bail_corrupt_error!(\"Invalid varint\");\n    }\n    v = (v << 8) + c as u64;\n    Ok(Some((v, 9)))\n}\n\n/// Compute the length of a varint encoding for a given u64 value.\n///\n/// SQLite varint: bytes 1-8 each carry 7 payload bits (56 total).\n/// The optional 9th byte carries a full 8 bits (no continuation bit),\n/// giving 64 bits total.  So values needing >56 bits always take 9 bytes.\n#[inline(always)]\npub fn varint_len(value: u64) -> usize {\n    if value <= 0x7f {\n        1\n    } else if value > (1u64 << 56) - 1 {\n        9\n    } else {\n        let bits = 64 - value.leading_zeros() as usize;\n        bits.div_ceil(7)\n    }\n}\n\npub fn write_varint(buf: &mut [u8], value: u64) -> usize {\n    if value <= 0x7f {\n        buf[0] = (value & 0x7f) as u8;\n        return 1;\n    }\n\n    if value <= 0x3fff {\n        buf[0] = (((value >> 7) & 0x7f) | 0x80) as u8;\n        buf[1] = (value & 0x7f) as u8;\n        return 2;\n    }\n\n    let mut value = value;\n    if (value & ((0xff000000_u64) << 32)) > 0 {\n        buf[8] = value as u8;\n        value >>= 8;\n        for i in (0..8).rev() {\n            buf[i] = ((value & 0x7f) | 0x80) as u8;\n            value >>= 7;\n        }\n        return 9;\n    }\n\n    let mut encoded: [u8; 9] = [0; 9];\n    let mut bytes = value;\n    let mut n = 0;\n    while bytes != 0 {\n        let v = 0x80 | (bytes & 0x7f);\n        encoded[n] = v as u8;\n        bytes >>= 7;\n        n += 1;\n    }\n    encoded[0] &= 0x7f;\n    for i in 0..n {\n        buf[i] = encoded[n - 1 - i];\n    }\n    n\n}\n\npub fn write_varint_to_vec(value: u64, payload: &mut Vec<u8>) {\n    let mut varint = [0u8; 9];\n    let n = write_varint(&mut varint, value);\n    payload.extend_from_slice(&varint[0..n]);\n}\n\n/// Stream through frames in chunks, building frame_cache incrementally\n/// Track last valid commit frame for consistency\npub fn build_shared_wal(\n    file: &Arc<dyn File>,\n    io: &Arc<dyn crate::IO>,\n) -> Result<Arc<RwLock<WalFileShared>>> {\n    let size = file.size()?;\n\n    let header = Arc::new(SpinLock::new(WalHeader::default()));\n    let read_locks = std::array::from_fn(|_| TursoRwLock::new());\n    for (i, l) in read_locks.iter().enumerate() {\n        l.write();\n        l.set_value_exclusive(if i < 2 { 0 } else { READMARK_NOT_USED });\n        l.unlock();\n    }\n\n    let wal_file_shared = Arc::new(RwLock::new(WalFileShared {\n        enabled: AtomicBool::new(true),\n        wal_header: header.clone(),\n        min_frame: AtomicU64::new(0),\n        max_frame: AtomicU64::new(0),\n        nbackfills: AtomicU64::new(0),\n        transaction_count: AtomicU64::new(0),\n        frame_cache: Arc::new(SpinLock::new(FxHashMap::default())),\n        last_checksum: (0, 0),\n        file: Some(file.clone()),\n        read_locks,\n        write_lock: TursoRwLock::new(),\n        loaded: AtomicBool::new(false),\n        checkpoint_lock: TursoRwLock::new(),\n        initialized: AtomicBool::new(false),\n        epoch: AtomicU32::new(0),\n    }));\n\n    if size < WAL_HEADER_SIZE as u64 {\n        wal_file_shared.write().loaded.store(true, Ordering::SeqCst);\n        return Ok(wal_file_shared);\n    }\n\n    let reader = Arc::new(StreamingWalReader::new(\n        file.clone(),\n        wal_file_shared.clone(),\n        header,\n        size,\n    ));\n\n    let h = reader.clone().read_header()?;\n    io.wait_for_completion(h)?;\n\n    loop {\n        if reader.done.load(Ordering::Acquire) {\n            break;\n        }\n        let offset = reader.off_atomic.load(Ordering::Acquire);\n        if offset >= size {\n            reader.finalize_loading();\n            break;\n        }\n\n        let (_read_size, c) = reader.clone().submit_one_chunk(offset)?;\n        io.wait_for_completion(c)?;\n\n        let new_off = reader.off_atomic.load(Ordering::Acquire);\n        if new_off <= offset {\n            reader.finalize_loading();\n            break;\n        }\n    }\n\n    Ok(wal_file_shared)\n}\n\npub(super) struct StreamingWalReader {\n    file: Arc<dyn File>,\n    wal_shared: Arc<RwLock<WalFileShared>>,\n    header: Arc<SpinLock<WalHeader>>,\n    file_size: u64,\n    state: RwLock<StreamingState>,\n    off_atomic: AtomicU64,\n    page_atomic: AtomicU64,\n    pub(super) done: AtomicBool,\n}\n\n/// Mutable state for streaming reader\nstruct StreamingState {\n    frame_idx: u64,\n    cumulative_checksum: (u32, u32),\n    /// checksum of the last valid commit frame\n    last_valid_checksum: (u32, u32),\n    last_valid_frame: u64,\n    pending_frames: FxHashMap<u64, Vec<u64>>,\n    page_size: usize,\n    use_native_endian: bool,\n    header_valid: bool,\n}\n\nimpl StreamingWalReader {\n    fn new(\n        file: Arc<dyn File>,\n        wal_shared: Arc<RwLock<WalFileShared>>,\n        header: Arc<SpinLock<WalHeader>>,\n        file_size: u64,\n    ) -> Self {\n        Self {\n            file,\n            wal_shared,\n            header,\n            file_size,\n            off_atomic: AtomicU64::new(0),\n            page_atomic: AtomicU64::new(0),\n            done: AtomicBool::new(false),\n            state: RwLock::new(StreamingState {\n                frame_idx: 1,\n                cumulative_checksum: (0, 0),\n                last_valid_checksum: (0, 0),\n                last_valid_frame: 0,\n                pending_frames: FxHashMap::default(),\n                page_size: 0,\n                use_native_endian: false,\n                header_valid: false,\n            }),\n        }\n    }\n\n    fn read_header(self: Arc<Self>) -> crate::Result<Completion> {\n        let header_buf = Arc::new(Buffer::new_temporary(WAL_HEADER_SIZE));\n        let reader = self.clone();\n        let completion: Box<ReadComplete> = Box::new(move |res| {\n            let _reader = reader.clone();\n            _reader.handle_header_read(res);\n            None\n        });\n        let c = Completion::new_read(header_buf, completion);\n        self.file.pread(0, c)\n    }\n\n    fn submit_one_chunk(self: Arc<Self>, offset: u64) -> crate::Result<(usize, Completion)> {\n        let page_size = self.page_atomic.load(Ordering::Acquire) as usize;\n        if page_size == 0 {\n            return Err(crate::LimboError::InternalError(\n                \"page size not initialized\".into(),\n            ));\n        }\n        let frame_size = WAL_FRAME_HEADER_SIZE + page_size;\n        if frame_size == 0 {\n            return Err(crate::LimboError::InternalError(\n                \"invalid frame size\".into(),\n            ));\n        }\n        const BASE: usize = 16 * 1024 * 1024;\n        let aligned = (BASE / frame_size) * frame_size;\n        let read_size = aligned\n            .max(frame_size)\n            .min((self.file_size - offset) as usize);\n        if read_size == 0 {\n            // end-of-file; let caller finalize\n            return Ok((0, Completion::new_yield()));\n        }\n\n        let buf = Arc::new(Buffer::new_temporary(read_size));\n        let me = self.clone();\n        let completion: Box<ReadComplete> = Box::new(move |res| {\n            tracing::debug!(\"WAL chunk read complete\");\n            let reader = me.clone();\n            reader.handle_chunk_read(res);\n            None\n        });\n        let c = Completion::new_read(buf, completion);\n        let guard = self.file.pread(offset, c)?;\n        Ok((read_size, guard))\n    }\n\n    fn handle_header_read(self: Arc<Self>, res: Result<(Arc<Buffer>, i32), CompletionError>) {\n        let Ok((buf, bytes_read)) = res else {\n            self.finalize_loading();\n            return;\n        };\n        if bytes_read != WAL_HEADER_SIZE as i32 {\n            self.finalize_loading();\n            return;\n        }\n\n        let (page_sz, c1, c2, use_native, ok) = {\n            let mut h = self.header.lock();\n            let s = buf.as_slice();\n            h.magic = u32::from_be_bytes(s[0..4].try_into().unwrap());\n            h.file_format = u32::from_be_bytes(s[4..8].try_into().unwrap());\n            h.page_size = u32::from_be_bytes(s[8..12].try_into().unwrap());\n            h.checkpoint_seq = u32::from_be_bytes(s[12..16].try_into().unwrap());\n            h.salt_1 = u32::from_be_bytes(s[16..20].try_into().unwrap());\n            h.salt_2 = u32::from_be_bytes(s[20..24].try_into().unwrap());\n            h.checksum_1 = u32::from_be_bytes(s[24..28].try_into().unwrap());\n            h.checksum_2 = u32::from_be_bytes(s[28..32].try_into().unwrap());\n            tracing::debug!(\"WAL header: {:?}\", *h);\n\n            let use_native = cfg!(target_endian = \"big\") == ((h.magic & 1) != 0);\n            let calc = checksum_wal(&s[0..24], &h, (0, 0), use_native);\n            (\n                h.page_size,\n                h.checksum_1,\n                h.checksum_2,\n                use_native,\n                calc == (h.checksum_1, h.checksum_2),\n            )\n        };\n        if PageSize::new(page_sz).is_none() || !ok {\n            self.finalize_loading();\n            return;\n        }\n        {\n            let mut st = self.state.write();\n            st.page_size = page_sz as usize;\n            st.use_native_endian = use_native;\n            st.cumulative_checksum = (c1, c2);\n            st.last_valid_checksum = (c1, c2);\n            st.header_valid = true;\n        }\n        self.off_atomic\n            .store(WAL_HEADER_SIZE as u64, Ordering::Release);\n        self.page_atomic.store(page_sz as u64, Ordering::Release);\n    }\n\n    fn handle_chunk_read(self: Arc<Self>, res: Result<(Arc<Buffer>, i32), CompletionError>) {\n        let Ok((buf, bytes_read)) = res else {\n            self.finalize_loading();\n            return;\n        };\n        let buf_slice = &buf.as_slice()[..bytes_read as usize];\n        // Snapshot salts/endianness once to avoid per-frame header locks\n        let (header_copy, use_native) = {\n            let st = self.state.read();\n            let h = self.header.lock();\n            (*h, st.use_native_endian)\n        };\n\n        let consumed = self.process_frames(buf_slice, &header_copy, use_native);\n        self.off_atomic.fetch_add(consumed as u64, Ordering::AcqRel);\n        // If we didn’t consume the full chunk, we hit a stop condition\n        if consumed < buf_slice.len() || self.off_atomic.load(Ordering::Acquire) >= self.file_size {\n            self.finalize_loading();\n        }\n    }\n\n    // Processes frames from a buffer, returns bytes processed\n    fn process_frames(&self, buf: &[u8], header: &WalHeader, use_native: bool) -> usize {\n        let mut st = self.state.write();\n        let page_size = st.page_size;\n        let frame_size = WAL_FRAME_HEADER_SIZE + page_size;\n        let mut pos = 0;\n\n        while pos + frame_size <= buf.len() {\n            let fh = &buf[pos..pos + WAL_FRAME_HEADER_SIZE];\n            let page = &buf[pos + WAL_FRAME_HEADER_SIZE..pos + frame_size];\n\n            let page_no = u32::from_be_bytes(fh[0..4].try_into().unwrap());\n            let db_size = u32::from_be_bytes(fh[4..8].try_into().unwrap());\n            let s1 = u32::from_be_bytes(fh[8..12].try_into().unwrap());\n            let s2 = u32::from_be_bytes(fh[12..16].try_into().unwrap());\n            let c1 = u32::from_be_bytes(fh[16..20].try_into().unwrap());\n            let c2 = u32::from_be_bytes(fh[20..24].try_into().unwrap());\n\n            tracing::debug!(\"process_frames: page_no={page_no}, db_size={db_size}, s1={s1}, s2={s2}, c1={c1}, c2={c2}\");\n\n            if page_no == 0 {\n                tracing::debug!(\n                    \"process_frames: unexpected page_no, stop reading WAL at initialization phase\"\n                );\n                break;\n            }\n            if s1 != header.salt_1 || s2 != header.salt_2 {\n                tracing::debug!(\n                    \"process_frames: salt mismatch, stop reading WAL at initialization phase\"\n                );\n                break;\n            }\n\n            let seed = checksum_wal(&fh[0..8], header, st.cumulative_checksum, use_native);\n            let calc = checksum_wal(page, header, seed, use_native);\n            if calc != (c1, c2) {\n                tracing::debug!(\n                    \"process_frames: checksum mismatch, stop reading WAL at initialization phase\"\n                );\n                break;\n            }\n\n            st.cumulative_checksum = calc;\n            let frame_idx = st.frame_idx;\n            st.pending_frames\n                .entry(page_no as u64)\n                .or_default()\n                .push(frame_idx);\n\n            if db_size > 0 {\n                st.last_valid_frame = st.frame_idx;\n                st.last_valid_checksum = calc;\n                self.flush_pending_frames(&mut st);\n            }\n            st.frame_idx += 1;\n            pos += frame_size;\n        }\n        pos\n    }\n\n    fn flush_pending_frames(&self, state: &mut StreamingState) {\n        if state.pending_frames.is_empty() {\n            return;\n        }\n        let wfs = self.wal_shared.read();\n        {\n            let mut frame_cache = wfs.frame_cache.lock();\n            for (page, mut frames) in state.pending_frames.drain() {\n                // Only include frames up to last valid commit\n                frames.retain(|&f| f <= state.last_valid_frame);\n                if !frames.is_empty() {\n                    frame_cache.entry(page).or_default().extend(frames);\n                }\n            }\n        }\n        wfs.max_frame\n            .store(state.last_valid_frame, Ordering::Release);\n    }\n\n    /// Finalizes the loading process\n    fn finalize_loading(&self) {\n        let mut wfs = self.wal_shared.write();\n        let st = self.state.read();\n\n        let max_frame = st.last_valid_frame;\n        if max_frame > 0 {\n            let mut frame_cache = wfs.frame_cache.lock();\n            for frames in frame_cache.values_mut() {\n                frames.retain(|&f| f <= max_frame);\n            }\n            frame_cache.retain(|_, frames| !frames.is_empty());\n        }\n\n        wfs.max_frame.store(max_frame, Ordering::SeqCst);\n        // use checksum of last valid commit frame, not necessarily the last frame\n        wfs.last_checksum = st.last_valid_checksum;\n        if st.header_valid {\n            wfs.initialized.store(true, Ordering::SeqCst);\n        }\n        wfs.nbackfills.store(0, Ordering::SeqCst);\n        wfs.loaded.store(true, Ordering::SeqCst);\n\n        self.done.store(true, Ordering::Release);\n        tracing::debug!(\n            \"WAL loading complete: {} frames processed, last commit at frame {}\",\n            st.frame_idx - 1,\n            max_frame\n        );\n    }\n}\n\npub fn begin_read_wal_frame_raw<F: File + ?Sized>(\n    buffer_pool: &Arc<BufferPool>,\n    io: &F,\n    offset: u64,\n    complete: Box<ReadComplete>,\n) -> Result<Completion> {\n    tracing::trace!(\"begin_read_wal_frame_raw(offset={})\", offset);\n    let buf = Arc::new(buffer_pool.get_wal_frame());\n    let c = Completion::new_read(buf, complete);\n    let c = io.pread(offset, c)?;\n    Ok(c)\n}\n\npub fn begin_read_wal_frame<F: File + ?Sized>(\n    io: &F,\n    offset: u64,\n    buffer_pool: Arc<BufferPool>,\n    complete: Box<ReadComplete>,\n    page_idx: usize,\n    io_ctx: &IOContext,\n) -> Result<Completion> {\n    tracing::trace!(\n        \"begin_read_wal_frame(offset={}, page_idx={})\",\n        offset,\n        page_idx\n    );\n    let buf = buffer_pool.get_page();\n    let buf = Arc::new(buf);\n\n    match io_ctx.encryption_or_checksum() {\n        EncryptionOrChecksum::Encryption(ctx) => {\n            let encryption_ctx = ctx.clone();\n            let original_complete = complete;\n\n            let decrypt_complete =\n                Box::new(move |res: Result<(Arc<Buffer>, i32), CompletionError>| {\n                    let Ok((encrypted_buf, bytes_read)) = res else {\n                        return original_complete(res);\n                    };\n                    turso_assert_greater_than!(\n                        bytes_read, 0,\n                        \"expected to read data for encrypted page\",\n                        { \"page_idx\": page_idx }\n                    );\n                    match encryption_ctx.decrypt_page(encrypted_buf.as_slice(), page_idx) {\n                        Ok(decrypted_data) => {\n                            encrypted_buf\n                                .as_mut_slice()\n                                .copy_from_slice(&decrypted_data);\n                            original_complete(Ok((encrypted_buf, bytes_read)))\n                        }\n                        Err(e) => {\n                            tracing::error!(\n                                \"Failed to decrypt WAL frame data for page_idx={page_idx}: {e}\"\n                            );\n                            let err = CompletionError::DecryptionError { page_idx };\n                            original_complete(Err(err));\n                            Some(err)\n                        }\n                    }\n                });\n\n            let new_completion = Completion::new_read(buf, decrypt_complete);\n            io.pread(offset, new_completion)\n        }\n        EncryptionOrChecksum::Checksum(ctx) => {\n            let checksum_ctx = ctx.clone();\n            let original_c = complete;\n            let verify_complete =\n                Box::new(move |res: Result<(Arc<Buffer>, i32), CompletionError>| {\n                    let Ok((buf, bytes_read)) = res else {\n                        return original_c(res);\n                    };\n                    if bytes_read <= 0 {\n                        tracing::trace!(\"Read page {page_idx} with {} bytes\", bytes_read);\n                        return original_c(Ok((buf, bytes_read)));\n                    }\n\n                    match checksum_ctx.verify_checksum(buf.as_mut_slice(), page_idx) {\n                        Ok(_) => original_c(Ok((buf, bytes_read))),\n                        Err(e) => {\n                            mark_unlikely();\n                            tracing::error!(\n                                \"Failed to verify checksum for page_id={page_idx}: {e}\"\n                            );\n                            original_c(Err(e));\n                            Some(e)\n                        }\n                    }\n                });\n            let c = Completion::new_read(buf, verify_complete);\n            io.pread(offset, c)\n        }\n        EncryptionOrChecksum::None => {\n            let c = Completion::new_read(buf, complete);\n            io.pread(offset, c)\n        }\n    }\n}\n\npub fn parse_wal_frame_header(frame: &[u8]) -> (WalFrameHeader, &[u8]) {\n    let page_number = u32::from_be_bytes(frame[0..4].try_into().unwrap());\n    let db_size = u32::from_be_bytes(frame[4..8].try_into().unwrap());\n    let salt_1 = u32::from_be_bytes(frame[8..12].try_into().unwrap());\n    let salt_2 = u32::from_be_bytes(frame[12..16].try_into().unwrap());\n    let checksum_1 = u32::from_be_bytes(frame[16..20].try_into().unwrap());\n    let checksum_2 = u32::from_be_bytes(frame[20..24].try_into().unwrap());\n    let header = WalFrameHeader {\n        page_number,\n        db_size,\n        salt_1,\n        salt_2,\n        checksum_1,\n        checksum_2,\n    };\n    let page = &frame[WAL_FRAME_HEADER_SIZE..];\n    (header, page)\n}\n\npub fn prepare_wal_frame(\n    buffer_pool: &Arc<BufferPool>,\n    wal_header: &WalHeader,\n    prev_checksums: (u32, u32),\n    page_size: u32,\n    page_number: u32,\n    db_size: u32,\n    page: &[u8],\n) -> ((u32, u32), Arc<Buffer>) {\n    tracing::trace!(page_number);\n\n    let buffer = buffer_pool.get_wal_frame();\n    let frame = buffer.as_mut_slice();\n    frame[WAL_FRAME_HEADER_SIZE..].copy_from_slice(page);\n\n    frame[0..4].copy_from_slice(&page_number.to_be_bytes());\n    frame[4..8].copy_from_slice(&db_size.to_be_bytes());\n    frame[8..12].copy_from_slice(&wal_header.salt_1.to_be_bytes());\n    frame[12..16].copy_from_slice(&wal_header.salt_2.to_be_bytes());\n\n    let expects_be = wal_header.magic & 1;\n    let use_native_endian = cfg!(target_endian = \"big\") as u32 == expects_be;\n    let header_checksum = checksum_wal(&frame[0..8], wal_header, prev_checksums, use_native_endian);\n    let final_checksum = checksum_wal(\n        &frame[WAL_FRAME_HEADER_SIZE..WAL_FRAME_HEADER_SIZE + page_size as usize],\n        wal_header,\n        header_checksum,\n        use_native_endian,\n    );\n    frame[16..20].copy_from_slice(&final_checksum.0.to_be_bytes());\n    frame[20..24].copy_from_slice(&final_checksum.1.to_be_bytes());\n\n    (final_checksum, Arc::new(buffer))\n}\n\npub fn begin_write_wal_header<F: File + ?Sized>(io: &F, header: &WalHeader) -> Result<Completion> {\n    tracing::trace!(\"begin_write_wal_header\");\n    let buffer = {\n        let buffer = Buffer::new_temporary(WAL_HEADER_SIZE);\n        let buf = buffer.as_mut_slice();\n\n        buf[0..4].copy_from_slice(&header.magic.to_be_bytes());\n        buf[4..8].copy_from_slice(&header.file_format.to_be_bytes());\n        buf[8..12].copy_from_slice(&header.page_size.to_be_bytes());\n        buf[12..16].copy_from_slice(&header.checkpoint_seq.to_be_bytes());\n        buf[16..20].copy_from_slice(&header.salt_1.to_be_bytes());\n        buf[20..24].copy_from_slice(&header.salt_2.to_be_bytes());\n        buf[24..28].copy_from_slice(&header.checksum_1.to_be_bytes());\n        buf[28..32].copy_from_slice(&header.checksum_2.to_be_bytes());\n\n        #[allow(clippy::arc_with_non_send_sync)]\n        Arc::new(buffer)\n    };\n\n    let write_complete = move |res: Result<i32, CompletionError>| {\n        let Ok(bytes_written) = res else {\n            return;\n        };\n        turso_assert!(\n            bytes_written == WAL_HEADER_SIZE as i32,\n            \"wal header wrote({bytes_written}) != expected({WAL_HEADER_SIZE})\"\n        );\n    };\n    #[allow(clippy::arc_with_non_send_sync)]\n    let c = Completion::new_write(write_complete);\n    let c = io.pwrite(0, buffer, c)?;\n    Ok(c)\n}\n\n/// Checks if payload will overflow a cell based on the maximum allowed size.\n/// It will return the min size that will be stored in that case,\n/// including overflow pointer\n/// see e.g. https://github.com/sqlite/sqlite/blob/9591d3fe93936533c8c3b0dc4d025ac999539e11/src/dbstat.c#L371\n#[inline]\npub fn payload_overflows(\n    payload_size: usize,\n    payload_overflow_threshold_max: usize,\n    payload_overflow_threshold_min: usize,\n    usable_size: usize,\n) -> (bool, usize) {\n    if payload_size <= payload_overflow_threshold_max {\n        return (false, 0);\n    }\n\n    let mut space_left = payload_overflow_threshold_min\n        + (payload_size - payload_overflow_threshold_min) % (usable_size - 4);\n    if space_left > payload_overflow_threshold_max {\n        space_left = payload_overflow_threshold_min;\n    }\n    (true, space_left + 4)\n}\n\n/// The checksum is computed by interpreting the input as an even number of unsigned 32-bit integers: x(0) through x(N).\n/// The 32-bit integers are big-endian if the magic number in the first 4 bytes of the WAL header is 0x377f0683\n/// and the integers are little-endian if the magic number is 0x377f0682.\n/// The checksum values are always stored in the frame header in a big-endian format regardless of which byte order is used to compute the checksum.\n///\n/// The checksum algorithm only works for content which is a multiple of 8 bytes in length.\n/// In other words, if the inputs are x(0) through x(N) then N must be odd.\n/// The checksum algorithm is as follows:\n///\n/// s0 = s1 = 0\n/// for i from 0 to n-1 step 2:\n///    s0 += x(i) + s1;\n///    s1 += x(i+1) + s0;\n/// endfor\n///\n/// The outputs s0 and s1 are both weighted checksums using Fibonacci weights in reverse order.\n/// (The largest Fibonacci weight occurs on the first element of the sequence being summed.)\n/// The s1 value spans all 32-bit integer terms of the sequence whereas s0 omits the final term.\n#[inline]\npub fn checksum_wal(\n    buf: &[u8],\n    _wal_header: &WalHeader,\n    input: (u32, u32),\n    native_endian: bool, // Sqlite interprets big endian as \"native\"\n) -> (u32, u32) {\n    turso_assert_eq!(buf.len() % 8, 0, \"buffer must be a multiple of 8\");\n    let mut s0: u32 = input.0;\n    let mut s1: u32 = input.1;\n    let mut i = 0;\n    if native_endian {\n        while i < buf.len() {\n            let v0 = u32::from_ne_bytes(buf[i..i + 4].try_into().unwrap());\n            let v1 = u32::from_ne_bytes(buf[i + 4..i + 8].try_into().unwrap());\n            s0 = s0.wrapping_add(v0.wrapping_add(s1));\n            s1 = s1.wrapping_add(v1.wrapping_add(s0));\n            i += 8;\n        }\n    } else {\n        while i < buf.len() {\n            let v0 = u32::from_ne_bytes(buf[i..i + 4].try_into().unwrap()).swap_bytes();\n            let v1 = u32::from_ne_bytes(buf[i + 4..i + 8].try_into().unwrap()).swap_bytes();\n            s0 = s0.wrapping_add(v0.wrapping_add(s1));\n            s1 = s1.wrapping_add(v1.wrapping_add(s0));\n            i += 8;\n        }\n    }\n    (s0, s1)\n}\n\nimpl WalHeader {\n    pub fn as_bytes(&self) -> &[u8] {\n        unsafe { std::mem::transmute::<&WalHeader, &[u8; size_of::<WalHeader>()]>(self) }\n    }\n}\n\n#[inline]\npub fn read_u32(buf: &[u8], pos: usize) -> u32 {\n    u32::from_be_bytes([buf[pos], buf[pos + 1], buf[pos + 2], buf[pos + 3]])\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::Value;\n\n    use super::*;\n    use rstest::rstest;\n\n    #[rstest]\n    #[case(&[], SerialType::null(), Value::Null)]\n    #[case(&[255], SerialType::i8(), Value::from_i64(-1))]\n    #[case(&[0x12, 0x34], SerialType::i16(), Value::from_i64(0x1234))]\n    #[case(&[0xFE], SerialType::i8(), Value::from_i64(-2))]\n    #[case(&[0x12, 0x34, 0x56], SerialType::i24(), Value::from_i64(0x123456))]\n    #[case(&[0x12, 0x34, 0x56, 0x78], SerialType::i32(), Value::from_i64(0x12345678))]\n    #[case(&[0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC], SerialType::i48(), Value::from_i64(0x123456789ABC))]\n    #[case(&[0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xFF], SerialType::i64(), Value::from_i64(0x123456789ABCDEFF))]\n    #[case(&[0x40, 0x09, 0x21, 0xFB, 0x54, 0x44, 0x2D, 0x18], SerialType::f64(), Value::from_f64(std::f64::consts::PI))]\n    #[case(&[1, 2], SerialType::const_int0(), Value::from_i64(0))]\n    #[case(&[65, 66], SerialType::const_int1(), Value::from_i64(1))]\n    #[case(&[1, 2, 3], SerialType::blob(3), Value::Blob(vec![1, 2, 3]))]\n    #[case(&[], SerialType::blob(0), Value::Blob(vec![]))] // empty blob\n    #[case(&[65, 66, 67], SerialType::text(3), Value::build_text(\"ABC\"))]\n    #[case(&[0x80], SerialType::i8(), Value::from_i64(-128))]\n    #[case(&[0x80, 0], SerialType::i16(), Value::from_i64(-32768))]\n    #[case(&[0x80, 0, 0], SerialType::i24(), Value::from_i64(-8388608))]\n    #[case(&[0x80, 0, 0, 0], SerialType::i32(), Value::from_i64(-2147483648))]\n    #[case(&[0x80, 0, 0, 0, 0, 0], SerialType::i48(), Value::from_i64(-140737488355328))]\n    #[case(&[0x80, 0, 0, 0, 0, 0, 0, 0], SerialType::i64(), Value::from_i64(-9223372036854775808))]\n    #[case(&[0x7f], SerialType::i8(), Value::from_i64(127))]\n    #[case(&[0x7f, 0xff], SerialType::i16(), Value::from_i64(32767))]\n    #[case(&[0x7f, 0xff, 0xff], SerialType::i24(), Value::from_i64(8388607))]\n    #[case(&[0x7f, 0xff, 0xff, 0xff], SerialType::i32(), Value::from_i64(2147483647))]\n    #[case(&[0x7f, 0xff, 0xff, 0xff, 0xff, 0xff], SerialType::i48(), Value::from_i64(140737488355327))]\n    #[case(&[0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], SerialType::i64(), Value::from_i64(9223372036854775807))]\n    fn test_read_value(\n        #[case] buf: &[u8],\n        #[case] serial_type: SerialType,\n        #[case] expected: Value,\n    ) {\n        let result = read_value(buf, serial_type).unwrap();\n        assert_eq!(result.0.to_owned(), expected);\n    }\n\n    #[test]\n    fn test_serial_type_helpers() {\n        assert_eq!(\n            TryInto::<SerialType>::try_into(12u64).unwrap(),\n            SerialType::blob(0)\n        );\n        assert_eq!(\n            TryInto::<SerialType>::try_into(14u64).unwrap(),\n            SerialType::blob(1)\n        );\n        assert_eq!(\n            TryInto::<SerialType>::try_into(13u64).unwrap(),\n            SerialType::text(0)\n        );\n        assert_eq!(\n            TryInto::<SerialType>::try_into(15u64).unwrap(),\n            SerialType::text(1)\n        );\n        assert_eq!(\n            TryInto::<SerialType>::try_into(16u64).unwrap(),\n            SerialType::blob(2)\n        );\n        assert_eq!(\n            TryInto::<SerialType>::try_into(17u64).unwrap(),\n            SerialType::text(2)\n        );\n    }\n\n    #[rstest]\n    #[case(0, SerialType::null())]\n    #[case(1, SerialType::i8())]\n    #[case(2, SerialType::i16())]\n    #[case(3, SerialType::i24())]\n    #[case(4, SerialType::i32())]\n    #[case(5, SerialType::i48())]\n    #[case(6, SerialType::i64())]\n    #[case(7, SerialType::f64())]\n    #[case(8, SerialType::const_int0())]\n    #[case(9, SerialType::const_int1())]\n    #[case(12, SerialType::blob(0))]\n    #[case(13, SerialType::text(0))]\n    #[case(14, SerialType::blob(1))]\n    #[case(15, SerialType::text(1))]\n    fn test_parse_serial_type(#[case] input: u64, #[case] expected: SerialType) {\n        let result = SerialType::try_from(input).unwrap();\n        assert_eq!(result, expected);\n    }\n\n    #[test]\n    fn test_validate_serial_type() {\n        for i in 0..=9 {\n            let result = validate_serial_type(i);\n            assert!(result.is_ok());\n        }\n        for i in 10..=11 {\n            let result = validate_serial_type(i);\n            assert!(result.is_err());\n        }\n        for i in 12..=1000 {\n            let result = validate_serial_type(i);\n            assert!(result.is_ok());\n        }\n    }\n\n    #[rstest]\n    #[case(&[])] // empty buffer\n    #[case(&[0x80])] // truncated 1-byte with continuation\n    #[case(&[0x80, 0x80])] // truncated 2-byte\n    #[case(&[0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80])] // 9-byte truncated to 8\n    #[case(&[0x80; 9])] // bits set without end\n    fn test_read_varint_malformed_inputs(#[case] buf: &[u8]) {\n        assert!(read_varint(buf).is_err());\n    }\n\n    #[test]\n    fn streaming_reader_ignores_uncommitted_checksums() {\n        let io: Arc<dyn crate::IO> = Arc::new(crate::MemoryIO::new());\n        let file = io\n            .open_file(\"streaming-reader-wal\", crate::OpenFlags::Create, false)\n            .unwrap();\n\n        let page_size: usize = 1024;\n        let buffer_pool = BufferPool::begin_init(&io, BufferPool::TEST_ARENA_SIZE);\n        buffer_pool\n            .finalize_with_page_size(page_size)\n            .expect(\"initialize buffer pool\");\n\n        let mut wal_header = WalHeader {\n            magic: WAL_MAGIC_LE,\n            file_format: 3007000,\n            page_size: page_size as u32,\n            checkpoint_seq: 0,\n            salt_1: 0x1234_5678,\n            salt_2: 0x9abc_def0,\n            checksum_1: 0,\n            checksum_2: 0,\n        };\n        let header_prefix = &wal_header.as_bytes()[..WAL_HEADER_SIZE - 8];\n        let use_native = (wal_header.magic & 1) != 0;\n        let (c1, c2) = checksum_wal(header_prefix, &wal_header, (0, 0), use_native);\n        wal_header.checksum_1 = c1;\n        wal_header.checksum_2 = c2;\n        io.wait_for_completion(begin_write_wal_header(file.as_ref(), &wal_header).unwrap())\n            .unwrap();\n\n        let page = vec![0xAB; page_size];\n        let frame_size = WAL_FRAME_HEADER_SIZE + page_size;\n        let mut offset = WAL_HEADER_SIZE as u64;\n\n        let (commit_checksum, commit_frame) = prepare_wal_frame(\n            &buffer_pool,\n            &wal_header,\n            (wal_header.checksum_1, wal_header.checksum_2),\n            wal_header.page_size,\n            1,\n            1,\n            &page,\n        );\n        let commit_frame_clone = commit_frame.clone();\n        let c = file\n            .pwrite(\n                offset,\n                commit_frame,\n                Completion::new_write(move |res| {\n                    assert_eq!(res.unwrap() as usize, frame_size);\n                    let _keep = commit_frame_clone.clone();\n                }),\n            )\n            .unwrap();\n        io.wait_for_completion(c).unwrap();\n        offset += frame_size as u64;\n\n        let (after_frame2_checksum, frame2) = prepare_wal_frame(\n            &buffer_pool,\n            &wal_header,\n            commit_checksum,\n            wal_header.page_size,\n            2,\n            0,\n            &page,\n        );\n        let frame2_clone = frame2.clone();\n        let c = file\n            .pwrite(\n                offset,\n                frame2,\n                Completion::new_write(move |res| {\n                    assert_eq!(res.unwrap() as usize, frame_size);\n                    let _keep = frame2_clone.clone();\n                }),\n            )\n            .unwrap();\n        io.wait_for_completion(c).unwrap();\n        offset += frame_size as u64;\n\n        let (after_frame3_checksum, frame3) = prepare_wal_frame(\n            &buffer_pool,\n            &wal_header,\n            after_frame2_checksum,\n            wal_header.page_size,\n            3,\n            0,\n            &page,\n        );\n        let frame3_clone = frame3.clone();\n        let c = file\n            .pwrite(\n                offset,\n                frame3,\n                Completion::new_write(move |res| {\n                    assert_eq!(res.unwrap() as usize, frame_size);\n                    let _keep = frame3_clone.clone();\n                }),\n            )\n            .unwrap();\n        io.wait_for_completion(c).unwrap();\n\n        let shared = build_shared_wal(&file, &io).unwrap();\n        let guard = shared.read();\n        assert_eq!(guard.max_frame.load(Ordering::Acquire), 1);\n        assert_eq!(guard.last_checksum, commit_checksum);\n\n        // checksum should only include committed frame.\n        assert_ne!(guard.last_checksum, after_frame3_checksum);\n\n        let frame_cache = guard.frame_cache.lock();\n        assert_eq!(frame_cache.get(&1), Some(&vec![1u64]));\n        assert!(frame_cache.get(&2).is_none());\n    }\n\n    #[quickcheck_macros::quickcheck]\n    fn varint_len_matches_write_varint(value: u64) -> bool {\n        let mut buf = [0u8; 9];\n        let written = write_varint(&mut buf, value);\n        varint_len(value) == written\n    }\n}\n"
  },
  {
    "path": "core/storage/state_machines.rs",
    "content": "use crate::PageRef;\n\n#[derive(Debug, Clone)]\npub enum EmptyTableState {\n    Start,\n    ReadPage { page: PageRef },\n}\n\n#[derive(Debug, Clone, Copy)]\npub enum MoveToRightState {\n    Start,\n    ProcessPage,\n}\n\n#[derive(Debug, Clone, Copy)]\npub enum SeekToLastState {\n    Start,\n    IsEmpty,\n}\n\n#[derive(Debug, Clone, Copy)]\npub enum RewindState {\n    Start,\n    NextRecord,\n}\n\n#[derive(Debug, Clone, Copy)]\npub enum AdvanceState {\n    Start,\n    Advance,\n}\n\n#[derive(Debug, Clone, Copy)]\npub enum CountState {\n    Start,\n    Loop,\n    Finish,\n}\n\n#[derive(Debug, Clone, Copy)]\npub enum SeekEndState {\n    Start,\n    ProcessPage,\n}\n\n#[derive(Debug, Clone, Copy)]\npub enum MoveToState {\n    Start,\n    MoveToPage,\n}\n"
  },
  {
    "path": "core/storage/subjournal.rs",
    "content": "use crate::sync::{\n    atomic::{AtomicBool, Ordering},\n    Arc,\n};\nuse crate::{turso_assert, turso_assert_eq};\n\nuse crate::{\n    storage::sqlite3_ondisk::finish_read_page, Buffer, Completion, CompletionError, PageRef, Result,\n};\n\n#[derive(Clone)]\npub struct Subjournal {\n    file: Arc<dyn crate::io::File>,\n    in_use: Arc<AtomicBool>,\n}\n\nimpl Subjournal {\n    pub fn new(file: Arc<dyn crate::io::File>) -> Self {\n        Self {\n            file,\n            in_use: Arc::new(AtomicBool::new(false)),\n        }\n    }\n\n    pub fn try_use(&self) -> Result<()> {\n        let result = self\n            .in_use\n            .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst);\n        if result.is_err() {\n            return Err(crate::LimboError::Busy);\n        }\n        Ok(())\n    }\n\n    pub fn stop_use(&self) {\n        let result = self\n            .in_use\n            .compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst);\n        turso_assert!(\n            result.is_ok(),\n            \"try_start_use must succeed before stop_use call\"\n        );\n    }\n\n    pub fn in_use(&self) -> bool {\n        self.in_use.load(Ordering::SeqCst)\n    }\n\n    pub fn write_page(\n        &self,\n        offset: u64,\n        page_size: usize,\n        buffer: Arc<Buffer>,\n        c: Completion,\n    ) -> Result<Completion> {\n        turso_assert_eq!(\n            buffer.len(),\n            page_size + 4,\n            \"buffer length should be page_size + 4 bytes for page id\"\n        );\n        self.file.pwrite(offset, buffer, c)\n    }\n\n    pub fn read_page_number(&self, offset: u64, page_id_buffer: Arc<Buffer>) -> Result<Completion> {\n        turso_assert_eq!(\n            page_id_buffer.len(),\n            4,\n            \"page_id_buffer length should be 4 bytes\"\n        );\n        let c = Completion::new_read(\n            page_id_buffer,\n            move |res: Result<(Arc<Buffer>, i32), CompletionError>| {\n                let Ok((buf, bytes_read)) = res else {\n                    return None;\n                };\n                let expected = buf.len();\n                if bytes_read != expected as i32 {\n                    tracing::error!(\n                        \"subjournal short read: expected {expected} bytes, got {bytes_read}\"\n                    );\n                    return Some(CompletionError::ShortRead {\n                        page_idx: 0, // reading page number header, not a page\n                        expected,\n                        actual: bytes_read as usize,\n                    });\n                }\n                None\n            },\n        );\n        let c = self.file.pread(offset, c)?;\n        Ok(c)\n    }\n\n    pub fn read_page(\n        &self,\n        offset: u64,\n        buffer: Arc<Buffer>,\n        page: PageRef,\n        page_size: usize,\n    ) -> Result<Completion> {\n        turso_assert_eq!(buffer.len(), page_size, \"buffer length should be page_size\");\n        let c = Completion::new_read(\n            buffer,\n            move |res: Result<(Arc<Buffer>, i32), CompletionError>| {\n                let Ok((buf, bytes_read)) = res else {\n                    return None;\n                };\n                let page_idx = page.get().id;\n                if bytes_read != page_size as i32 {\n                    tracing::error!(\n                        \"subjournal short read on page {page_idx}: expected {page_size} bytes, got {bytes_read}\"\n                    );\n                    return Some(CompletionError::ShortRead {\n                        page_idx,\n                        expected: page_size,\n                        actual: bytes_read as usize,\n                    });\n                }\n                finish_read_page(page_idx, buf, page.clone());\n                None\n            },\n        );\n        let c = self.file.pread(offset, c)?;\n        Ok(c)\n    }\n\n    pub fn truncate(&self, offset: u64) -> Result<Completion> {\n        let c = Completion::new_trunc(move |res: Result<i32, CompletionError>| {\n            let Ok(_) = res else {\n                return;\n            };\n        });\n        self.file.truncate(offset, c)\n    }\n}\n"
  },
  {
    "path": "core/storage/wal.rs",
    "content": "#![allow(clippy::not_unsafe_ptr_arg_deref)]\n\nuse crate::io::FileSyncType;\nuse crate::sync::Mutex;\nuse crate::sync::OnceLock;\nuse crate::{turso_assert, turso_assert_greater_than, turso_debug_assert};\nuse branches::mark_unlikely;\nuse rustc_hash::{FxHashMap, FxHashSet};\nuse std::array;\nuse std::borrow::Cow;\nuse std::collections::BTreeMap;\nuse strum::EnumString;\nuse tracing::{instrument, Level};\n\nuse crate::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, AtomicUsize, Ordering};\nuse crate::sync::RwLock;\nuse std::fmt::{Debug, Formatter};\nuse std::{fmt, sync::Arc};\n\nuse super::buffer_pool::BufferPool;\nuse super::pager::{PageRef, Pager};\nuse super::sqlite3_ondisk::{self, checksum_wal, WalHeader, WAL_MAGIC_BE, WAL_MAGIC_LE};\nuse crate::fast_lock::SpinLock;\nuse crate::io::clock::MonotonicInstant;\nuse crate::io::CompletionGroup;\nuse crate::io::{File, IO};\nuse crate::storage::database::EncryptionOrChecksum;\nuse crate::storage::sqlite3_ondisk::{\n    begin_read_wal_frame, begin_read_wal_frame_raw, finish_read_page, prepare_wal_frame,\n    write_pages_vectored, PageSize, WAL_FRAME_HEADER_SIZE, WAL_HEADER_SIZE,\n};\nuse crate::types::{IOCompletions, IOResult};\nuse crate::{\n    bail_corrupt_error, io_yield_one, Buffer, Completion, CompletionError, IOContext, LimboError,\n    Result,\n};\n\n/// this contains the frame to rollback to and its associated checksum.\n#[derive(Debug, Clone)]\npub struct RollbackTo {\n    pub frame: u64,\n    pub checksum: (u32, u32),\n}\n\n#[derive(Debug, Clone, Default)]\npub struct CheckpointResult {\n    /// max frame in the WAL after checkpoint\n    /// note, that as we TRUNCATE wal outside of the main checkpoint routine - this field will be set to non-zero number even for TRUNCATE mode\n    pub wal_max_frame: u64,\n    /// total amount of frames backfilled to the DB file after checkpoint\n    pub wal_total_backfilled: u64,\n    /// amount of new frames backfilled to the DB file during this checkpoint procedure\n    pub wal_checkpoint_backfilled: u64,\n    /// In the case of everything backfilled, we need to hold the locks until the db\n    /// file is truncated.\n    maybe_guard: Option<CheckpointLocks>,\n    pub db_truncate_sent: bool,\n    pub db_sync_sent: bool,\n    /// Whether WAL truncation I/O has been submitted (for TRUNCATE checkpoint mode)\n    pub wal_truncate_sent: bool,\n    /// Whether WAL sync I/O has been submitted after truncation\n    pub wal_sync_sent: bool,\n}\n\nimpl Drop for CheckpointResult {\n    fn drop(&mut self) {\n        let _ = self.maybe_guard.take();\n    }\n}\n\nimpl CheckpointResult {\n    pub fn new(\n        wal_max_frame: u64,\n        wal_total_backfilled: u64,\n        wal_checkpoint_backfilled: u64,\n    ) -> Self {\n        Self {\n            wal_max_frame,\n            wal_total_backfilled,\n            wal_checkpoint_backfilled,\n            maybe_guard: None,\n            db_sync_sent: false,\n            db_truncate_sent: false,\n            wal_truncate_sent: false,\n            wal_sync_sent: false,\n        }\n    }\n\n    pub const fn everything_backfilled(&self) -> bool {\n        self.wal_max_frame == self.wal_total_backfilled\n    }\n    pub fn should_truncate(&self) -> bool {\n        // TRUNCATE should also clear any stale WAL bytes when the log was restarted\n        // (wal_max_frame=0) but the file still contains old frames.\n        self.everything_backfilled()\n    }\n    pub fn release_guard(&mut self) {\n        let _ = self.maybe_guard.take();\n    }\n}\n\n#[derive(Debug, Copy, Clone, PartialEq, EnumString)]\n#[strum(ascii_case_insensitive)]\npub enum CheckpointMode {\n    /// Checkpoint as many frames as possible without waiting for any database readers or writers to finish, then sync the database file if all frames in the log were checkpointed.\n    /// Passive never blocks readers or writers, only ensures (like all modes do) that there are no other checkpointers.\n    ///\n    /// Optional upper_bound_inclusive parameter can be set in order to checkpoint frames with number no larger than the parameter\n    Passive { upper_bound_inclusive: Option<u64> },\n    /// This mode blocks until there is no database writer and all readers are reading from the most recent database snapshot. It then checkpoints all frames in the log file and syncs the database file. This mode blocks new database writers while it is pending, but new database readers are allowed to continue unimpeded.\n    Full,\n    /// This mode works the same way as `Full` with the addition that after checkpointing the log file it blocks (calls the busy-handler callback) until all readers are reading from the database file only. This ensures that the next writer will restart the log file from the beginning. Like `Full`, this mode blocks new database writer attempts while it is pending, but does not impede readers.\n    Restart,\n    /// This mode works the same way as `Restart` with the addition that it also truncates the log file to zero bytes just prior to a successful return.\n    ///\n    /// Extra parameter can be set in order to perform conditional TRUNCATE: database will be checkpointed and truncated only if max_frames equals to the parameter value\n    /// this behaviour used by sync-engine which consolidate WAL before checkpoint and needs to be sure that no frames will be missed\n    Truncate { upper_bound_inclusive: Option<u64> },\n}\n\nimpl CheckpointMode {\n    fn should_restart_log(&self) -> bool {\n        matches!(\n            self,\n            CheckpointMode::Truncate { .. } | CheckpointMode::Restart\n        )\n    }\n    /// All modes other than Passive require a complete backfilling of all available frames\n    /// from `shared.nbackfills + 1 -> shared.max_frame`\n    fn require_all_backfilled(&self) -> bool {\n        !matches!(self, CheckpointMode::Passive { .. })\n    }\n}\n\n/// Immutable view of the WAL metadata a connection snapshots from shared state.\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\nstruct WalSnapshot {\n    max_frame: u64,\n    nbackfills: u64,\n    last_checksum: (u32, u32),\n    checkpoint_seq: u32,\n    transaction_count: u64,\n}\n\nimpl WalSnapshot {\n    /// First frame that is still visible in the WAL after checkpoint backfill.\n    const fn min_frame(self) -> u64 {\n        self.nbackfills + 1\n    }\n}\n\n/// Which read-mark, if any, currently protects this connection's snapshot.\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\nenum ReadGuardKind {\n    None,\n    DbFile,\n    ReadMark(usize),\n}\n\nimpl ReadGuardKind {\n    /// Convert the lock index stored on `WalFile` into a semantic guard kind.\n    const fn from_lock_index(lock_index: usize) -> Self {\n        match lock_index {\n            NO_LOCK_HELD => Self::None,\n            0 => Self::DbFile,\n            idx => Self::ReadMark(idx),\n        }\n    }\n\n    /// Convert the semantic guard kind back into the legacy lock index representation.\n    const fn lock_index(self) -> usize {\n        match self {\n            Self::None => NO_LOCK_HELD,\n            Self::DbFile => 0,\n            Self::ReadMark(idx) => idx,\n        }\n    }\n}\n\n/// Connection-local WAL state derived from a shared snapshot plus a held read guard.\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\nstruct WalConnectionState {\n    snapshot: WalSnapshot,\n    read_guard: ReadGuardKind,\n}\n\nimpl WalConnectionState {\n    /// Build a new connection-local WAL state bundle.\n    const fn new(snapshot: WalSnapshot, read_guard: ReadGuardKind) -> Self {\n        Self {\n            snapshot,\n            read_guard,\n        }\n    }\n\n    /// Replace just the shared snapshot while preserving the current read guard.\n    const fn with_snapshot(self, snapshot: WalSnapshot) -> Self {\n        Self {\n            snapshot,\n            read_guard: self.read_guard,\n        }\n    }\n}\n\n#[repr(transparent)]\n#[derive(Debug, Default)]\n/// A 64-bit read-write lock with embedded 32-bit value storage.\n/// Using a single Atomic allows the reader count and lock state are updated\n/// atomically together while sitting in a single cpu cache line.\n///\n/// # Memory Layout:\n/// ```ignore\n/// [63:32] Value bits    - 32 bits for stored value\n/// [31:1]  Reader count  - 31 bits for reader count\n/// [0]     Writer bit    - 1 bit indicating exclusive write lock\n/// ```\n///\n/// # Synchronization Guarantees:\n/// - Acquire semantics on lock acquisition ensure visibility of all writes\n///   made by the previous lock holder\n/// - Release semantics on unlock ensure all writes made while holding the\n///   lock are visible to the next acquirer\n/// - The embedded value can be atomically read without holding any lock\npub struct TursoRwLock(AtomicU64);\n\npub const READMARK_NOT_USED: u32 = 0xffffffff;\nconst NO_LOCK_HELD: usize = usize::MAX;\n\nimpl TursoRwLock {\n    /// Bit 0: Writer flag\n    const WRITER: u64 = 0b1;\n\n    /// Reader increment value (bit 1)\n    const READER_INC: u64 = 0b10;\n\n    /// Reader count starts at bit 1\n    const READER_SHIFT: u32 = 1;\n\n    /// Mask for 31 reader bits [31:1]\n    const READER_COUNT_MASK: u64 = 0x7fff_ffffu64 << Self::READER_SHIFT;\n\n    /// Value starts at bit 32\n    const VALUE_SHIFT: u32 = 32;\n\n    /// Mask for 32 value bits [63:32]\n    const VALUE_MASK: u64 = 0xffff_ffffu64 << Self::VALUE_SHIFT;\n\n    #[inline]\n    pub const fn new() -> Self {\n        Self(AtomicU64::new(0))\n    }\n\n    const fn has_writer(val: u64) -> bool {\n        val & Self::WRITER != 0\n    }\n\n    const fn has_readers(val: u64) -> bool {\n        val & Self::READER_COUNT_MASK != 0\n    }\n\n    #[inline]\n    /// Try to acquire a shared read lock.\n    pub fn read(&self) -> bool {\n        let mut count = 0;\n        // Bounded loop to avoid infinite loops\n        // Retry on Reader contention (should hopefully be spurious)\n        while count < 1_000_000 {\n            let cur = self.0.load(Ordering::Acquire);\n            // If a writer is present we cannot proceed.\n            if Self::has_writer(cur) {\n                return false;\n            }\n            // 2 billion readers is a high enough number where we will skip the branch\n            // and assume that we are not overflowing :)\n            let desired = cur.wrapping_add(Self::READER_INC);\n            // for success, Acquire establishes happens-before relationship with the previous Release from unlock\n            // for failure we only care about reading it for the next iteration so we can use Relaxed.\n            let res = self\n                .0\n                .compare_exchange(cur, desired, Ordering::Acquire, Ordering::Relaxed);\n            if res.is_err() {\n                count += 1;\n                crate::thread::spin_loop();\n                continue;\n            }\n            return true;\n        }\n        // Too much reader contention return Busy\n        false\n    }\n\n    /// Try to take an exclusive lock. Succeeds if no readers and no writer.\n    #[inline]\n    pub fn write(&self) -> bool {\n        let cur = self.0.load(Ordering::Acquire);\n        // exclusive lock, so require no readers and no writer\n        if Self::has_writer(cur) || Self::has_readers(cur) {\n            return false;\n        }\n        let desired = cur | Self::WRITER;\n        self.0 // Safety: Failure here can be Relaxed as we will read again on next iteration.\n            .compare_exchange(cur, desired, Ordering::Acquire, Ordering::Relaxed)\n            .is_ok()\n    }\n\n    /// upgrade read lock to the write lock\n    /// only possible if there is exactly single reader at the moment\n    /// return true if lock was upgraded succesfully - and false otherwise\n    #[inline]\n    pub fn upgrade(&self) -> bool {\n        let cur = self.0.load(Ordering::Acquire);\n        // Check for single reader: exactly one reader, any value\n        if (cur & !Self::VALUE_MASK) != Self::READER_INC {\n            return false;\n        }\n        // Preserve value bits, replace reader with writer\n        let desired = (cur & Self::VALUE_MASK) | Self::WRITER;\n        self.0\n            .compare_exchange(cur, desired, Ordering::Acquire, Ordering::Relaxed)\n            .is_ok()\n    }\n\n    /// downgrade write lock to the read lock\n    /// MUST be called for a lock acquired by the writer\n    #[inline]\n    pub fn downgrade(&self) {\n        let cur = self.0.load(Ordering::Acquire);\n        turso_debug_assert!(Self::has_writer(cur));\n        // Preserve value bits, replace writer with one reader\n        let desired = (cur & Self::VALUE_MASK) | Self::READER_INC;\n        self.0.store(desired, Ordering::Release);\n    }\n\n    #[inline]\n    /// Unlock whatever lock is currently held.\n    /// For write lock: clear writer bit\n    /// For read lock: decrement reader count\n    pub fn unlock(&self) {\n        let cur = self.0.load(Ordering::Acquire);\n        if (cur & Self::WRITER) != 0 {\n            // Clear writer bit, preserve everything else (including value)\n            // Release ordering ensures all our writes are visible to next acquirer\n            let cur = self.0.fetch_and(!Self::WRITER, Ordering::Release);\n            turso_assert!(!Self::has_readers(cur), \"write lock was held with readers\");\n        } else {\n            turso_assert!(\n                Self::has_readers(cur),\n                \"unlock called with no readers or writers\"\n            );\n            self.0.fetch_sub(Self::READER_INC, Ordering::Release);\n        }\n    }\n\n    #[inline]\n    /// Read the embedded 32-bit value atomically regardless of slot occupancy.\n    pub fn get_value(&self) -> u32 {\n        (self.0.load(Ordering::Acquire) >> Self::VALUE_SHIFT) as u32\n    }\n\n    #[inline]\n    /// Set the embedded value while holding the write lock.\n    pub fn set_value_exclusive(&self, v: u32) {\n        // Must be called only while WRITER bit is set\n        let cur = self.0.load(Ordering::Acquire);\n        turso_assert!(Self::has_writer(cur), \"must hold exclusive lock\");\n        let desired = (cur & !Self::VALUE_MASK) | ((v as u64) << Self::VALUE_SHIFT);\n        self.0.store(desired, Ordering::Release);\n    }\n}\n\n/// Represents a batch of WAL frames which will be appended to the log\n/// with a `pwritev` call and then sync'd to disk.\npub struct PreparedFrames {\n    /// File offset for the first frame\n    pub offset: u64,\n    /// Serialized frame buffers\n    pub bufs: Vec<Arc<Buffer>>,\n    /// Per-frame metadata: (page_ref, frame_id, cumulative_checksum)\n    pub metadata: Vec<(PageRef, u64, (u32, u32))>,\n    /// Checksum after all frames in this batch\n    pub final_checksum: (u32, u32),\n    /// Max frame ID after this batch\n    pub final_max_frame: u64,\n    /// Epoch at preparation time\n    pub epoch: u32,\n}\n\n/// Write-ahead log (WAL).\npub trait Wal: Debug + Send + Sync {\n    /// Begin a read transaction.\n    /// Returns whether the database state has changed since the last read transaction.\n    fn begin_read_tx(&self) -> Result<bool>;\n    /// MVCC helper: check if WAL state changed without starting a read tx.\n    fn mvcc_refresh_if_db_changed(&self) -> bool;\n\n    /// Begin a write transaction.\n    fn begin_write_tx(&self) -> Result<()>;\n\n    /// End a read transaction.\n    fn end_read_tx(&self);\n\n    /// End a write transaction.\n    fn end_write_tx(&self);\n\n    /// Returns true if this WAL instance currently holds a read lock.\n    fn holds_read_lock(&self) -> bool;\n\n    /// Returns true if this WAL instance currently holds the write lock.\n    fn holds_write_lock(&self) -> bool;\n\n    /// Find the latest frame containing a page.\n    ///\n    /// optional frame_watermark parameter can be passed to force WAL to find frame not larger than watermark value\n    /// caller must guarantee, that frame_watermark must be greater than last checkpointed frame, otherwise method will panic\n    fn find_frame(&self, page_id: u64, frame_watermark: Option<u64>) -> Result<Option<u64>>;\n\n    /// Read a frame from the WAL.\n    fn read_frame(\n        &self,\n        frame_id: u64,\n        page: PageRef,\n        buffer_pool: Arc<BufferPool>,\n    ) -> Result<Completion>;\n\n    /// Read a raw frame (header included) from the WAL.\n    fn read_frame_raw(&self, frame_id: u64, frame: &mut [u8]) -> Result<Completion>;\n\n    /// Write a raw frame (header included) from the WAL.\n    /// Note, that turso-db will use page_no and size_after fields from the header, but will overwrite checksum with proper value\n    fn write_frame_raw(\n        &self,\n        buffer_pool: Arc<BufferPool>,\n        frame_id: u64,\n        page_id: u64,\n        db_size: u64,\n        page: &[u8],\n        sync_type: FileSyncType,\n    ) -> Result<()>;\n\n    /// Prepare WAL header for the future append\n    /// Most of the time this method will return Ok(None)\n    fn prepare_wal_start(&self, page_sz: PageSize) -> Result<Option<Completion>>;\n\n    fn prepare_wal_finish(&self, sync_type: FileSyncType) -> Result<Completion>;\n\n    /// Prepare a batch of WAL frames for durable commit/append to the log.\n    fn prepare_frames(\n        &self,\n        pages: &[PageRef],\n        page_sz: PageSize,\n        db_size_on_commit: Option<u32>,\n        prev: Option<&PreparedFrames>,\n    ) -> Result<PreparedFrames>;\n\n    /// For each prepared frame, update in-memory WAL index and rolling checksum\n    /// and advance max_frame to make committed frames visible to readers.\n    fn commit_prepared_frames(&self, prepared: &[PreparedFrames]);\n\n    /// Mark in-memory pages clean and set WAL tags after durable commit.\n    fn finalize_committed_pages(&self, prepared: &[PreparedFrames]);\n\n    /// Return a handle to the underlying File.\n    fn wal_file(&self) -> Result<Arc<dyn File>>;\n\n    /// Write a bunch of frames to the WAL.\n    /// db_size is the database size in pages after the transaction finishes.\n    /// db_size is set  -> last frame written in transaction\n    /// db_size is none -> non-last frame written in transaction\n    fn append_frames_vectored(&self, pages: Vec<PageRef>, page_sz: PageSize) -> Result<Completion>;\n\n    /// Complete append of frames by updating shared wal state. Before this\n    /// all changes were stored locally.\n    fn finish_append_frames_commit(&self) -> Result<()>;\n\n    fn should_checkpoint(&self) -> bool;\n    fn checkpoint(&self, pager: &Pager, mode: CheckpointMode)\n        -> Result<IOResult<CheckpointResult>>;\n    fn sync(&self, sync_type: FileSyncType) -> Result<Completion>;\n    fn is_syncing(&self) -> bool;\n    fn get_max_frame_in_wal(&self) -> u64;\n    fn get_checkpoint_seq(&self) -> u32;\n    fn get_max_frame(&self) -> u64;\n    fn get_min_frame(&self) -> u64;\n    fn rollback(&self, rollback_to: Option<RollbackTo>);\n    fn abort_checkpoint(&self);\n    fn get_last_checksum(&self) -> (u32, u32);\n\n    /// Return unique set of pages changed **after** frame_watermark position and until current WAL session max_frame_no\n    fn changed_pages_after(&self, frame_watermark: u64) -> Result<Vec<u32>>;\n\n    fn set_io_context(&self, ctx: IOContext);\n\n    /// Update the max frame to the current shared max frame.\n    /// Currently this is only used for MVCC as it takes care of write conflicts on its own.\n    /// This should't be used with regular WAL mode.\n    fn update_max_frame(&self);\n\n    /// Truncate WAL file to zero and sync it. This is called AFTER the DB file has been\n    /// synced during TRUNCATE checkpoint mode, ensuring data durability.\n    /// The result parameter is used to track I/O progress (wal_truncate_sent, wal_sync_sent).\n    fn truncate_wal(\n        &self,\n        result: &mut CheckpointResult,\n        sync_type: FileSyncType,\n    ) -> Result<IOResult<()>>;\n\n    #[cfg(debug_assertions)]\n    fn as_any(&self) -> &dyn std::any::Any;\n}\n\n#[derive(Debug, Clone)]\npub enum CheckpointState {\n    Start,\n    Processing,\n    /// Determine the checkpoint result: update nBackfills, restart log if needed.\n    DetermineResult,\n    /// Final cleanup: release locks, clear internal state, return result.\n    /// WAL truncation (if needed) is handled by pager.rs via truncate_wal() AFTER the DB is synced.\n    Finalize {\n        checkpoint_result: Option<CheckpointResult>,\n    },\n}\n\n/// IOV_MAX is 1024 on most systems, lets use 512 to be safe\npub const CKPT_BATCH_PAGES: usize = 512;\n\n/// TODO: *ALL* of these need to be tuned for perf. It is tricky\n/// trying to figure out the ideal numbers here to work together concurrently\nconst MIN_AVG_RUN_FOR_FLUSH: f32 = 32.0;\nconst MIN_BATCH_LEN_FOR_FLUSH: usize = 512;\nconst MAX_INFLIGHT_WRITES: usize = 64;\npub const MAX_INFLIGHT_READS: usize = 512;\npub const IOV_MAX: usize = 1024;\n\ntype PageId = usize;\nstruct InflightRead {\n    completion: Completion,\n    page_id: PageId,\n    /// Buffer slot to contain the page content from the WAL read.\n    buf: Arc<SpinLock<Option<Arc<Buffer>>>>,\n}\n\n/// WriteBatch is a collection of pages that are being checkpointed together. It is used to\n/// aggregate contiguous pages into a single write operation to the database file.\n#[derive(Default)]\nstruct WriteBatch {\n    /// BTreeMap for sorting during insertion, helps create more efficient `writev` operations.\n    items: BTreeMap<PageId, Arc<Buffer>>,\n    /// total number of `runs`, each representing a contiguous group of `PageId`s\n    run_count: usize,\n}\n\nimpl WriteBatch {\n    fn new() -> Self {\n        Self {\n            items: BTreeMap::new(),\n            run_count: 0,\n        }\n    }\n\n    #[inline]\n    /// Add a pageId + Buffer to the batch of Writes to be submitted.\n    fn insert(&mut self, page_id: PageId, buf: Arc<Buffer>) {\n        if let std::collections::btree_map::Entry::Occupied(mut e) = self.items.entry(page_id) {\n            e.insert(buf);\n            return;\n        }\n        // Single range query to check neighbors\n        let start = page_id.saturating_sub(1);\n        let end = page_id.saturating_add(1);\n        let mut has_left = false;\n        let mut has_right = false;\n\n        for (k, _) in self.items.range(start..=end) {\n            if *k == page_id.wrapping_sub(1) {\n                has_left = true;\n            }\n            if *k == page_id.wrapping_add(1) {\n                has_right = true;\n            }\n        }\n        match (has_left, has_right) {\n            (false, false) => self.run_count += 1,\n            (true, true) => self.run_count = self.run_count.saturating_sub(1),\n            _ => {}\n        }\n        self.items.insert(page_id, buf);\n    }\n\n    #[inline]\n    fn len(&self) -> usize {\n        self.items.len()\n    }\n    #[inline]\n    fn is_empty(&self) -> bool {\n        self.items.is_empty()\n    }\n    #[inline]\n    fn is_full(&self) -> bool {\n        self.items.len() >= CKPT_BATCH_PAGES\n    }\n\n    #[inline]\n    fn avg_run_len(&self) -> f32 {\n        if self.run_count == 0 {\n            0.0\n        } else {\n            self.items.len() as f32 / self.run_count as f32\n        }\n    }\n\n    #[inline]\n    fn take(&mut self) -> BTreeMap<PageId, Arc<Buffer>> {\n        self.run_count = 0;\n        std::mem::take(&mut self.items)\n    }\n\n    #[inline]\n    fn clear(&mut self) {\n        self.items.clear();\n        self.run_count = 0;\n    }\n}\n\nimpl std::ops::Deref for WriteBatch {\n    type Target = BTreeMap<PageId, Arc<Buffer>>;\n    fn deref(&self) -> &Self::Target {\n        &self.items\n    }\n}\nimpl std::ops::DerefMut for WriteBatch {\n    fn deref_mut(&mut self) -> &mut Self::Target {\n        &mut self.items\n    }\n}\n\n/// Information and structures for processing a checkpoint operation.\nstruct OngoingCheckpoint {\n    /// Used for benchmarking/debugging a checkpoint operation.\n    time: MonotonicInstant,\n    /// minimum frame number to be backfilled by this checkpoint operation.\n    min_frame: u64,\n    /// maximum safe frame number that will be backfilled by this checkpoint operation.\n    max_frame: u64,\n    /// cursor used to iterate through all the pages that might have a frame in the safe range\n    current_page: u64,\n    /// State of the checkpoint\n    state: CheckpointState,\n    /// Batch repreesnts a collection of pages to be backfilled to the DB file.\n    pending_writes: WriteBatch,\n    /// Read operations currently ongoing.\n    inflight_reads: Vec<InflightRead>,\n    /// Array of atomic counters representing write operations that are currently in flight.\n    inflight_writes: Vec<InflightWriteBatch>,\n    /// List of all page_id + frame_id combinations to be backfilled\n    pages_to_checkpoint: Vec<(u64, u64)>,\n}\n\nstruct InflightWriteBatch {\n    done: Arc<AtomicBool>,\n    err: Arc<crate::sync::OnceLock<CompletionError>>,\n}\n\nimpl OngoingCheckpoint {\n    fn reset(&mut self) {\n        self.min_frame = 0;\n        self.max_frame = 0;\n        self.current_page = 0;\n        self.pages_to_checkpoint.clear();\n        self.pending_writes.clear();\n        self.inflight_reads.clear();\n        self.inflight_writes.clear();\n        self.state = CheckpointState::Start;\n    }\n\n    #[inline]\n    /// Whether or not new reads should be issued during checkpoint processing.\n    fn should_issue_reads(&self) -> bool {\n        (self.current_page as usize) < self.pages_to_checkpoint.len()\n            && !self.pending_writes.is_full()\n            && self.inflight_reads.len() < MAX_INFLIGHT_READS\n    }\n\n    #[inline]\n    /// Whether the backfilling/IO process is entirely completed during checkpoint processing.\n    fn complete(&self) -> bool {\n        (self.current_page as usize) >= self.pages_to_checkpoint.len()\n            && self.inflight_reads.is_empty()\n            && self.pending_writes.is_empty()\n            && self.inflight_writes.is_empty()\n    }\n\n    #[inline]\n    /// Whether we should flush an exisitng batch of writes and begin concurrently aggregating a new one.\n    fn should_flush_batch(&self) -> bool {\n        self.pending_writes.is_full()\n            || (self.pending_writes.len() >= MIN_BATCH_LEN_FOR_FLUSH\n                && self.pending_writes.avg_run_len() >= MIN_AVG_RUN_FOR_FLUSH)\n            || ((self.current_page as usize) >= self.pages_to_checkpoint.len()\n                && self.inflight_reads.is_empty()\n                && !self.pending_writes.is_empty())\n    }\n\n    #[inline]\n    /// Remove any completed write operations from `inflight_writes`,\n    /// returns whether any progress was made.\n    fn process_inflight_writes(&mut self) -> bool {\n        let before_len = self.inflight_writes.len();\n        self.inflight_writes\n            .retain(|w| !w.done.load(Ordering::Acquire));\n        before_len > self.inflight_writes.len()\n    }\n\n    #[inline]\n    /// Remove any completed read operations from `inflight_reads`\n    /// returns whether any progress was made.\n    fn process_pending_reads(&mut self) -> Result<bool> {\n        let mut moved = false;\n        let mut err: Option<CompletionError> = None;\n\n        self.inflight_reads.retain(|slot| {\n            if !slot.completion.finished() {\n                return true;\n            }\n            if slot.completion.succeeded() {\n                if let Some(buf) = slot.buf.lock().take() {\n                    self.pending_writes.insert(slot.page_id, buf);\n                    moved = true;\n                } else {\n                    err = Some(CompletionError::IOError(std::io::ErrorKind::Other, \"read\"));\n                }\n            } else {\n                err = Some(\n                    slot.completion\n                        .get_error()\n                        .unwrap_or(CompletionError::IOError(std::io::ErrorKind::Other, \"read\")),\n                );\n            }\n            false\n        });\n        if let Some(e) = err {\n            return Err(LimboError::CompletionError(e));\n        }\n        Ok(moved)\n    }\n\n    fn first_write_error(&self) -> Option<CompletionError>\n    where\n        CompletionError: Clone,\n    {\n        self.inflight_writes\n            .iter()\n            .find_map(|w| w.err.get().cloned())\n    }\n}\n\nimpl InflightWriteBatch {\n    #[inline]\n    fn new() -> InflightWriteBatch {\n        InflightWriteBatch {\n            done: Arc::new(AtomicBool::new(false)),\n            err: Arc::new(OnceLock::new()),\n        }\n    }\n}\n\nimpl fmt::Debug for OngoingCheckpoint {\n    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"OngoingCheckpoint\")\n            .field(\"state\", &self.state)\n            .field(\"min_frame\", &self.min_frame)\n            .field(\"max_frame\", &self.max_frame)\n            .field(\"current_page\", &self.current_page)\n            .finish()\n    }\n}\n\npub struct WalFile {\n    io: Arc<dyn IO>,\n    buffer_pool: Arc<BufferPool>,\n\n    syncing: Arc<AtomicBool>,\n    write_lock_held: AtomicBool,\n\n    shared: Arc<RwLock<WalFileShared>>,\n    ongoing_checkpoint: RwLock<OngoingCheckpoint>,\n    checkpoint_threshold: usize,\n    /// This is the index to the read_lock in WalFileShared that we are holding. This lock contains\n    /// the max frame for this connection.\n    max_frame_read_lock_index: AtomicUsize,\n    /// Max frame allowed to lookup range=(minframe..max_frame)\n    max_frame: AtomicU64,\n    /// Start of range to look for frames range=(minframe..max_frame)\n    min_frame: AtomicU64,\n    /// Check of last frame in WAL, this is a cumulative checksum over all frames in the WAL\n    last_checksum: RwLock<(u32, u32)>,\n    checkpoint_seq: AtomicU32,\n    transaction_count: AtomicU64,\n\n    /// Manages locks needed for checkpointing\n    checkpoint_guard: RwLock<Option<CheckpointLocks>>,\n\n    io_ctx: RwLock<IOContext>,\n}\n\nimpl fmt::Debug for WalFile {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"WalFile\")\n            .field(\"syncing\", &self.syncing.load(Ordering::Relaxed))\n            .field(\"page_size\", &self.page_size())\n            .field(\"shared\", &self.shared)\n            .field(\"ongoing_checkpoint\", &*self.ongoing_checkpoint.read())\n            .field(\"checkpoint_threshold\", &self.checkpoint_threshold)\n            .field(\"max_frame_read_lock_index\", &self.max_frame_read_lock_index)\n            .field(\"max_frame\", &self.max_frame)\n            .field(\"min_frame\", &self.min_frame)\n            // Excluding other fields\n            .finish()\n    }\n}\n\n/*\n* sqlite3/src/wal.c\n*\n** nBackfill is the number of frames in the WAL that have been written\n** back into the database. (We call the act of moving content from WAL to\n** database \"backfilling\".)  The nBackfill number is never greater than\n** WalIndexHdr.mxFrame.  nBackfill can only be increased by threads\n** holding the WAL_CKPT_LOCK lock (which includes a recovery thread).\n** However, a WAL_WRITE_LOCK thread can move the value of nBackfill from\n** mxFrame back to zero when the WAL is reset.\n**\n** nBackfillAttempted is the largest value of nBackfill that a checkpoint\n** has attempted to achieve.  Normally nBackfill==nBackfillAtempted, however\n** the nBackfillAttempted is set before any backfilling is done and the\n** nBackfill is only set after all backfilling completes.  So if a checkpoint\n** crashes, nBackfillAttempted might be larger than nBackfill.  The\n** WalIndexHdr.mxFrame must never be less than nBackfillAttempted.\n**\n** The aLock[] field is a set of bytes used for locking.  These bytes should\n** never be read or written.\n**\n** There is one entry in aReadMark[] for each reader lock.  If a reader\n** holds read-lock K, then the value in aReadMark[K] is no greater than\n** the mxFrame for that reader.  The value READMARK_NOT_USED (0xffffffff)\n** for any aReadMark[] means that entry is unused.  aReadMark[0] is\n** a special case; its value is never used and it exists as a place-holder\n** to avoid having to offset aReadMark[] indexes by one.  Readers holding\n** WAL_READ_LOCK(0) always ignore the entire WAL and read all content\n** directly from the database.\n**\n** The value of aReadMark[K] may only be changed by a thread that\n** is holding an exclusive lock on WAL_READ_LOCK(K).  Thus, the value of\n** aReadMark[K] cannot changed while there is a reader is using that mark\n** since the reader will be holding a shared lock on WAL_READ_LOCK(K).\n**\n** The checkpointer may only transfer frames from WAL to database where\n** the frame numbers are less than or equal to every aReadMark[] that is\n** in use (that is, every aReadMark[j] for which there is a corresponding\n** WAL_READ_LOCK(j)).  New readers (usually) pick the aReadMark[] with the\n** largest value and will increase an unused aReadMark[] to mxFrame if there\n** is not already an aReadMark[] equal to mxFrame.  The exception to the\n** previous sentence is when nBackfill equals mxFrame (meaning that everything\n** in the WAL has been backfilled into the database) then new readers\n** will choose aReadMark[0] which has value 0 and hence such reader will\n** get all their all content directly from the database file and ignore\n** the WAL.\n**\n** Writers normally append new frames to the end of the WAL.  However,\n** if nBackfill equals mxFrame (meaning that all WAL content has been\n** written back into the database) and if no readers are using the WAL\n** (in other words, if there are no WAL_READ_LOCK(i) where i>0) then\n** the writer will first \"reset\" the WAL back to the beginning and start\n** writing new content beginning at frame 1.\n*/\n\n// TODO(pere): lock only important parts + pin WalFileShared\n/// WalFileShared is the part of a WAL that will be shared between threads. A wal has information\n/// that needs to be communicated between threads so this struct does the job.\npub struct WalFileShared {\n    pub enabled: AtomicBool,\n    pub wal_header: Arc<SpinLock<WalHeader>>,\n    pub min_frame: AtomicU64,\n    pub max_frame: AtomicU64,\n    pub nbackfills: AtomicU64,\n    pub transaction_count: AtomicU64,\n    // Frame cache maps a Page to all the frames it has stored in WAL in ascending order.\n    // This is to easily find the frame it must checkpoint each connection if a checkpoint is\n    // necessary.\n    // One difference between SQLite and limbo is that we will never support multi process, meaning\n    // we don't need WAL's index file. So we can do stuff like this without shared memory.\n    // TODO: this will need refactoring because this is incredible memory inefficient.\n    pub frame_cache: Arc<SpinLock<FxHashMap<u64, Vec<u64>>>>,\n    pub last_checksum: (u32, u32), // Check of last frame in WAL, this is a cumulative checksum over all frames in the WAL\n    pub file: Option<Arc<dyn File>>,\n    /// Read locks advertise the maximum WAL frame a reader may access.\n    /// Slot 0 is special, when it is held (shared) the reader bypasses the WAL and uses the main DB file.\n    /// When checkpointing, we must acquire the exclusive read lock 0 to ensure that no readers read\n    /// from a partially checkpointed db file.\n    /// Slots 1‑4 carry a frame‑number in value and may be shared by many readers. Slot 1 is the\n    /// default read lock and is to contain the max_frame in WAL.\n    pub read_locks: [TursoRwLock; 5],\n    /// There is only one write allowed in WAL mode. This lock takes care of ensuring there is only\n    /// one used.\n    pub write_lock: TursoRwLock,\n\n    /// Serialises checkpointer threads, only one checkpoint can be in flight at any time. Blocking and exclusive only\n    pub checkpoint_lock: TursoRwLock,\n    pub loaded: AtomicBool,\n    pub initialized: AtomicBool,\n    /// Increments on each checkpoint, used to prevent stale cached pages being used for\n    /// backfilling.\n    pub epoch: AtomicU32,\n}\n\nimpl fmt::Debug for WalFileShared {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"WalFileShared\")\n            .field(\"enabled\", &self.enabled.load(Ordering::Relaxed))\n            .field(\"wal_header\", &self.wal_header)\n            .field(\"min_frame\", &self.min_frame)\n            .field(\"max_frame\", &self.max_frame)\n            .field(\"nbackfills\", &self.nbackfills)\n            .field(\"frame_cache\", &self.frame_cache)\n            .field(\"last_checksum\", &self.last_checksum)\n            // Excluding `file`, `read_locks`, and `write_lock`\n            .finish()\n    }\n}\n\n#[derive(Clone, Debug)]\n/// To manage and ensure that no locks are leaked during checkpointing in\n/// the case of errors. It is held by the WalFile while checkpoint is ongoing\n/// then transferred to the CheckpointResult if necessary.\nenum CheckpointLocks {\n    Writer { ptr: Arc<RwLock<WalFileShared>> },\n    Read0 { ptr: Arc<RwLock<WalFileShared>> },\n}\n\n/// Database checkpointers takes the following locks, in order:\n/// The exclusive CHECKPOINTER lock.\n/// The exclusive WRITER lock (FULL, RESTART and TRUNCATE only).\n/// Exclusive lock on read-mark slots 1-N. These are immediately released after being taken.\n/// Exclusive lock on read-mark 0.\n/// Exclusive lock on read-mark slots 1-N again. These are immediately released after being taken (RESTART and TRUNCATE only).\n/// All of the above use blocking locks.\nimpl CheckpointLocks {\n    fn new(ptr: Arc<RwLock<WalFileShared>>, mode: CheckpointMode) -> Result<Self> {\n        let ptr_clone = ptr.clone();\n        {\n            let shared = ptr.write();\n            if !shared.checkpoint_lock.write() {\n                tracing::trace!(\"CheckpointGuard::new: checkpoint lock failed, returning Busy\");\n                return Err(LimboError::Busy);\n            }\n            match mode {\n                CheckpointMode::Passive { .. } => {\n                    if !shared.read_locks[0].write() {\n                        shared.checkpoint_lock.unlock();\n                        tracing::trace!(\"CheckpointGuard: read0 lock failed, returning Busy\");\n                        return Err(LimboError::Busy);\n                    }\n                }\n                CheckpointMode::Full => {\n                    if !shared.read_locks[0].write() {\n                        shared.checkpoint_lock.unlock();\n                        tracing::trace!(\"CheckpointGuard: read0 lock failed (Full), Busy\");\n                        return Err(LimboError::Busy);\n                    }\n                    if !shared.write_lock.write() {\n                        shared.read_locks[0].unlock();\n                        shared.checkpoint_lock.unlock();\n                        tracing::trace!(\"CheckpointGuard: write lock failed (Full), Busy\");\n                        return Err(LimboError::Busy);\n                    }\n                }\n                CheckpointMode::Restart | CheckpointMode::Truncate { .. } => {\n                    if !shared.read_locks[0].write() {\n                        shared.checkpoint_lock.unlock();\n                        tracing::trace!(\"CheckpointGuard: read0 lock failed, returning Busy\");\n                        return Err(LimboError::Busy);\n                    }\n                    if !shared.write_lock.write() {\n                        shared.checkpoint_lock.unlock();\n                        shared.read_locks[0].unlock();\n                        tracing::trace!(\"CheckpointGuard: write lock failed, returning Busy\");\n                        return Err(LimboError::Busy);\n                    }\n                }\n            }\n        }\n\n        match mode {\n            CheckpointMode::Passive { .. } => Ok(Self::Read0 { ptr: ptr_clone }),\n            CheckpointMode::Full | CheckpointMode::Restart | CheckpointMode::Truncate { .. } => {\n                Ok(Self::Writer { ptr: ptr_clone })\n            }\n        }\n    }\n}\n\nimpl Drop for CheckpointLocks {\n    fn drop(&mut self) {\n        match self {\n            CheckpointLocks::Writer { ptr: shared } => {\n                let guard = shared.write();\n                guard.write_lock.unlock();\n                guard.read_locks[0].unlock();\n                guard.checkpoint_lock.unlock();\n            }\n            CheckpointLocks::Read0 { ptr: shared } => {\n                let guard = shared.write();\n                guard.read_locks[0].unlock();\n                guard.checkpoint_lock.unlock();\n            }\n        }\n    }\n}\n\n/// Result of try_begin_read_tx - either success or a retriable condition.\nenum TryBeginReadResult {\n    /// Successfully started read transaction, returns whether DB changed\n    Ok(bool),\n    /// Transient condition, caller should retry immediately (like SQLite's WAL_RETRY)\n    Retry,\n}\n\nimpl WalFile {\n    /// Read the shared WAL metadata that defines a connection snapshot.\n    fn load_shared_snapshot(shared: &WalFileShared) -> WalSnapshot {\n        WalSnapshot {\n            max_frame: shared.max_frame.load(Ordering::Acquire),\n            nbackfills: shared.nbackfills.load(Ordering::Acquire),\n            last_checksum: shared.last_checksum,\n            checkpoint_seq: shared.wal_header.lock().checkpoint_seq,\n            transaction_count: shared.transaction_count.load(Ordering::Acquire),\n        }\n    }\n\n    /// Reconstruct the connection-local WAL state stored on this `WalFile`.\n    fn connection_state(&self) -> WalConnectionState {\n        WalConnectionState::new(\n            WalSnapshot {\n                max_frame: self.max_frame.load(Ordering::Acquire),\n                nbackfills: self.min_frame.load(Ordering::Acquire).saturating_sub(1),\n                last_checksum: *self.last_checksum.read(),\n                checkpoint_seq: self.checkpoint_seq.load(Ordering::Acquire),\n                transaction_count: self.transaction_count.load(Ordering::Acquire),\n            },\n            ReadGuardKind::from_lock_index(self.max_frame_read_lock_index.load(Ordering::Acquire)),\n        )\n    }\n\n    /// Persist a connection-local WAL snapshot bundle back into the legacy fields on `WalFile`.\n    fn install_connection_state(&self, state: WalConnectionState) {\n        self.max_frame\n            .store(state.snapshot.max_frame, Ordering::Release);\n        self.min_frame\n            .store(state.snapshot.min_frame(), Ordering::Release);\n        *self.last_checksum.write() = state.snapshot.last_checksum;\n        self.checkpoint_seq\n            .store(state.snapshot.checkpoint_seq, Ordering::Release);\n        self.transaction_count\n            .store(state.snapshot.transaction_count, Ordering::Release);\n        self.max_frame_read_lock_index\n            .store(state.read_guard.lock_index(), Ordering::Release);\n    }\n\n    /// Compare a freshly loaded shared snapshot against the connection's current snapshot.\n    fn db_changed_against(&self, snapshot: WalSnapshot, local_state: WalConnectionState) -> bool {\n        snapshot != local_state.snapshot\n    }\n\n    /// Try to begin a read transaction. Returns Retry for transient conditions\n    /// that should be retried immediately, Ok for success.\n    fn try_begin_read_tx(&self) -> TryBeginReadResult {\n        turso_assert!(\n            self.max_frame_read_lock_index\n                .load(Ordering::Acquire)\n                .eq(&NO_LOCK_HELD),\n            \"cannot start a new read tx without ending an existing one\",\n            { \"lock_value\": self.max_frame_read_lock_index.load(Ordering::Acquire), \"expected\": NO_LOCK_HELD }\n        );\n\n        // Snapshot the shared WAL state. We haven't taken a read lock yet, so we need\n        // to validate these values later.\n        let shared_snapshot = self.with_shared(Self::load_shared_snapshot);\n        tracing::debug!(\n            \"try_begin_read_tx: shared_max={}, nbackfills={}, last_checksum={:?}, checkpoint_seq={:?}, transaction_count={}\",\n            shared_snapshot.max_frame,\n            shared_snapshot.nbackfills,\n            shared_snapshot.last_checksum,\n            shared_snapshot.checkpoint_seq,\n            shared_snapshot.transaction_count\n        );\n\n        // Check if database changed since this connection's last read transaction.\n        // If it has, the connection will invalidate its page cache.\n        let db_changed = self.db_changed_against(shared_snapshot, self.connection_state());\n\n        tracing::debug!(\"try_begin_read_tx: db_changed={}\", db_changed);\n\n        // If WAL is fully checkpointed (shared_max == nbackfills), readers can ignore\n        // the WAL and read directly from the DB file by holding read_locks[0].\n        if shared_snapshot.max_frame == shared_snapshot.nbackfills {\n            tracing::debug!(\n                \"begin_read_tx: WAL fully checkpointed, shared_max={}, nbackfills={}\",\n                shared_snapshot.max_frame,\n                shared_snapshot.nbackfills\n            );\n            if !self.with_shared(|shared| shared.read_locks[0].read()) {\n                tracing::debug!(\"begin_read_tx: unable to acquire read-0 lock slot, retrying\");\n                return TryBeginReadResult::Retry;\n            }\n            // Re-validate: a writer could have appended frames between our snapshot\n            // and lock acquisition. If so, we cannot proceed because we'd not be reading\n            // up to date committed content from the WAL.\n            let snapshot_after_lock = self.with_shared(Self::load_shared_snapshot);\n            if snapshot_after_lock != shared_snapshot {\n                tracing::debug!(\n                    \"begin_read_tx: shared data changed ({}, {}, {:?}, {}, {}) != ({}, {}, {:?}, {}, {}), retrying\",\n                    shared_snapshot.max_frame,\n                    shared_snapshot.nbackfills,\n                    shared_snapshot.last_checksum,\n                    shared_snapshot.checkpoint_seq,\n                    shared_snapshot.transaction_count,\n                    snapshot_after_lock.max_frame,\n                    snapshot_after_lock.nbackfills,\n                    snapshot_after_lock.last_checksum,\n                    snapshot_after_lock.checkpoint_seq,\n                    snapshot_after_lock.transaction_count\n                );\n                self.with_shared(|shared| shared.read_locks[0].unlock());\n                return TryBeginReadResult::Retry;\n            }\n            self.install_connection_state(WalConnectionState::new(\n                shared_snapshot,\n                ReadGuardKind::DbFile,\n            ));\n            return TryBeginReadResult::Ok(db_changed);\n        }\n\n        // If we get this far, it means that the reader will want to use\n        // the WAL to get at content from recent commits.  The job now is\n        // to select one of the aReadMark[] entries that is closest to\n        // but not exceeding pWal->hdr.mxFrame and lock that entry.\n        // Find largest mark <= mx among slots 1..N\n        let mut best_idx: i64 = -1;\n        let mut best_mark: u32 = 0;\n        self.with_shared(|shared| {\n            for (idx, lock) in shared.read_locks.iter().enumerate().skip(1) {\n                let m = lock.get_value();\n                if m != READMARK_NOT_USED && m <= shared_snapshot.max_frame as u32 && m > best_mark\n                {\n                    best_mark = m;\n                    best_idx = idx as i64;\n                }\n            }\n        });\n        tracing::debug!(\n            \"try_begin_read_tx: best_idx={}, best_mark={}\",\n            best_idx,\n            best_mark\n        );\n\n        // If none found or lagging, try to claim/update a slot\n        if best_idx == -1 || (best_mark as u64) < shared_snapshot.max_frame {\n            self.with_shared(|shared| {\n                for (idx, lock) in shared.read_locks.iter().enumerate().skip(1) {\n                    if !lock.write() {\n                        continue; // busy slot\n                    }\n                    // claim or bump this slot\n                    lock.set_value_exclusive(shared_snapshot.max_frame as u32);\n                    best_idx = idx as i64;\n                    best_mark = shared_snapshot.max_frame as u32;\n                    lock.unlock();\n                    break;\n                }\n            })\n        }\n\n        // SQLite only requires finding SOME slot (mxI != 0), not that the mark equals mxFrame.\n        // A stale mark is fine - the reader uses shared_max for reading,\n        // and the mark just tells the checkpointer what frames are protected.\n        if best_idx == -1 {\n            return TryBeginReadResult::Retry;\n        }\n\n        // Now acquire shared read lock on the chosen slot.\n        let read_result = self.with_shared(|shared| {\n            if !shared.read_locks[best_idx as usize].read() {\n                return None;\n            }\n            Some((\n                Self::load_shared_snapshot(shared),\n                shared.read_locks[best_idx as usize].get_value(),\n            ))\n        });\n\n        tracing::debug!(\"try_begin_read_tx: read_result={:?}\", read_result);\n\n        let Some((snapshot_after_lock, current_slot_mark)) = read_result else {\n            return TryBeginReadResult::Retry;\n        };\n\n        // Re-validate state after acquiring the lock. Each check prevents a correctness violation:\n        //\n        // - current_slot_mark != best_mark: Between releasing the exclusive lock (after updating\n        //   the slot) and acquiring this shared lock, another thread can exclusively lock and\n        //   modify the slot. The checkpointer uses the slot's value to decide how far it can\n        //   checkpoint. If the slot now says 700 but we recorded 500, the checkpointer may\n        //   overwrite DB pages for frames 501-700 that we expect to read from the WAL.\n        //\n        // - mx2 != shared_max: A writer appended frames. We must retry to see them.\n        //\n        // - nb2 != nbackfills: A checkpointer advanced. We'd set min_frame wrong, potentially\n        //   trying to read frames from WAL that were already overwritten.\n        //\n        // - cksm2 != last_checksum: WAL content changed (e.g., rollback reused frame slots).\n        //\n        // - ckpt_seq2 != checkpoint_seq: WAL was reset. Frame numbers are now meaningless.\n        if current_slot_mark != best_mark || snapshot_after_lock != shared_snapshot {\n            self.with_shared(|shared| shared.read_locks[best_idx as usize].unlock());\n            return TryBeginReadResult::Retry;\n        }\n        self.install_connection_state(WalConnectionState::new(\n            shared_snapshot,\n            ReadGuardKind::ReadMark(best_idx as usize),\n        ));\n        tracing::debug!(\n            \"begin_read_tx(min={}, max={}, slot={}, max_frame_in_wal={})\",\n            self.min_frame.load(Ordering::Acquire),\n            self.max_frame.load(Ordering::Acquire),\n            best_idx,\n            shared_snapshot.max_frame\n        );\n        TryBeginReadResult::Ok(db_changed)\n    }\n}\n\nimpl Wal for WalFile {\n    fn begin_read_tx(&self) -> Result<bool> {\n        // Implement progressive backoff because transient lock contention\n        // should resolve quickly, but under heavy contention busy-spinning wastes\n        // CPU. SQLite uses quadratic backoff after 5 retries, with total delay\n        // up to ~10 seconds before giving up, so we just mirror SQLite's implementation\n        // here.\n        let mut cnt = 0u32;\n        loop {\n            tracing::trace!(\"begin_read_tx: cnt={cnt}\");\n            match self.try_begin_read_tx() {\n                TryBeginReadResult::Ok(changed) => return Ok(changed),\n                TryBeginReadResult::Retry => {\n                    cnt += 1;\n                    if cnt > 100 {\n                        return Err(LimboError::Busy);\n                    }\n                    // Progressive backoff: first 5 retries are immediate, then we\n                    // start yielding/sleeping with increasing delays.\n                    if cnt > 5 {\n                        if cnt < 10 {\n                            // Retries 6-9: yield to scheduler (minimal delay)\n                            self.io.yield_now();\n                        } else {\n                            // Retries 10+: quadratic backoff in microseconds\n                            // Formula matches SQLite: (cnt-9)^2 * 39 microseconds\n                            let delay_us = ((cnt - 9) * (cnt - 9) * 39) as u64;\n                            self.io.sleep(std::time::Duration::from_micros(delay_us));\n                        }\n                    }\n                    continue;\n                }\n            }\n        }\n    }\n\n    fn mvcc_refresh_if_db_changed(&self) -> bool {\n        WalFile::mvcc_refresh_if_db_changed(self)\n    }\n\n    /// End a read transaction.\n    #[inline(always)]\n    #[instrument(skip_all, level = Level::DEBUG)]\n    fn end_read_tx(&self) {\n        let slot = self.max_frame_read_lock_index.load(Ordering::Acquire);\n        if slot != NO_LOCK_HELD {\n            self.with_shared(|shared| shared.read_locks[slot].unlock());\n            self.max_frame_read_lock_index\n                .store(NO_LOCK_HELD, Ordering::Release);\n            tracing::debug!(\"end_read_tx(slot={slot})\");\n        } else {\n            tracing::debug!(\"end_read_tx(slot=no_lock)\");\n        }\n    }\n\n    /// Begin a write transaction\n    #[instrument(skip_all, level = Level::DEBUG)]\n    fn begin_write_tx(&self) -> Result<()> {\n        tracing::debug!(\"begin_write_tx\");\n        self.with_shared(|shared| {\n            // sqlite/src/wal.c 3702\n            // Cannot start a write transaction without first holding a read\n            // transaction.\n            // assert(pWal->readLock >= 0);\n            // assert(pWal->writeLock == 0 && pWal->iReCksum == 0);\n            turso_assert!(\n                self.max_frame_read_lock_index.load(Ordering::Acquire) != NO_LOCK_HELD,\n                \"must have a read transaction to begin a write transaction\"\n            );\n            turso_assert!(\n                !self.holds_write_lock(),\n                \"write lock already held by this connection\"\n            );\n            if !shared.write_lock.write() {\n                return Err(LimboError::Busy);\n            }\n            let db_changed = self.db_changed(shared);\n            if db_changed {\n                // Snapshot is stale, give up and let caller retry from scratch.\n                // Return BusySnapshot instead of Busy so the caller knows it must\n                // restart the read transaction to get a fresh snapshot.\n                // Retrying with busy_timeout will NEVER HELP.\n                tracing::debug!(\"unable to upgrade transaction from read to write: snapshot is stale, give up and let caller retry from scratch, self.max_frame={}, shared_max={}\", self.max_frame.load(Ordering::Acquire), shared.max_frame.load(Ordering::Acquire));\n                shared.write_lock.unlock();\n                return Err(LimboError::BusySnapshot);\n            }\n\n            Ok(())\n        })?;\n        if self\n            .write_lock_held\n            .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)\n            .is_err()\n        {\n            self.with_shared(|shared| shared.write_lock.unlock());\n            turso_assert!(\n                false,\n                \"begin_write_tx called while write lock already held according to connection state\"\n            );\n        }\n\n        let result = self.try_restart_log_before_write();\n        if let Err(LimboError::Busy) | Ok(()) = &result {\n            // it's fine if we were unable to restart WAL file due to Busy errors\n            return Ok(());\n        }\n\n        // don't forget to release the write-lock if\n        self.with_shared(|shared| {\n            shared.write_lock.unlock();\n        });\n        turso_assert!(\n            self.write_lock_held\n                .compare_exchange(true, false, Ordering::AcqRel, Ordering::Acquire)\n                .is_ok(),\n            \"end_write_tx called while write lock not held according to connection state\"\n        );\n\n        Err(result.expect_err(\"Ok case handled above\"))\n    }\n\n    /// End a write transaction\n    #[instrument(skip_all, level = Level::DEBUG)]\n    fn end_write_tx(&self) {\n        turso_assert!(\n            self.write_lock_held\n                .compare_exchange(true, false, Ordering::AcqRel, Ordering::Acquire)\n                .is_ok(),\n            \"end_write_tx called while write lock not held according to connection state\"\n        );\n        self.with_shared(|shared| shared.write_lock.unlock());\n    }\n\n    /// Returns true if this WAL instance currently holds a read lock.\n    fn holds_read_lock(&self) -> bool {\n        self.max_frame_read_lock_index.load(Ordering::Acquire) != NO_LOCK_HELD\n    }\n\n    /// Returns true if this WAL instance currently holds the write lock.\n    fn holds_write_lock(&self) -> bool {\n        self.write_lock_held.load(Ordering::Acquire)\n    }\n\n    /// Find the latest frame containing a page.\n    #[instrument(skip_all, level = Level::DEBUG)]\n    fn find_frame(&self, page_id: u64, frame_watermark: Option<u64>) -> Result<Option<u64>> {\n        #[cfg(not(feature = \"conn_raw_api\"))]\n        turso_assert!(\n            frame_watermark.is_none(),\n            \"unexpected use of frame_watermark optional argument\"\n        );\n\n        turso_assert!(\n            frame_watermark.unwrap_or(0) <= self.max_frame.load(Ordering::Acquire),\n            \"frame_watermark must be <= than current WAL max_frame value\"\n        );\n\n        // we can guarantee correctness of the method, only if frame_watermark is strictly after the current checkpointed prefix\n        //\n        // if it's not, than pages from WAL range [frame_watermark..nBackfill] are already in the DB file,\n        // and in case if page first occurrence in WAL was after frame_watermark - we will be unable to read proper previous version of the page\n        self.with_shared(|shared| {\n            let nbackfills = shared.nbackfills.load(Ordering::Acquire);\n            turso_assert!(\n                frame_watermark.is_none() || frame_watermark.unwrap() >= nbackfills,\n                \"frame_watermark must be >= than current WAL backfill amount\",\n                { \"frame_watermark\": frame_watermark, \"nbackfills\": nbackfills }\n            );\n        });\n\n        // if we are holding read_lock 0 and didn't write anything to the WAL, skip and read right from db file.\n        //\n        // note, that max_frame_read_lock_index is set to 0 only when shared_max_frame == nbackfill in which case\n        // min_frame is set to nbackfill + 1 and max_frame is set to shared_max_frame\n        //\n        // by default, SQLite tries to restart log file in this case - but for now let's keep it simple in the turso-db\n        if self.max_frame_read_lock_index.load(Ordering::Acquire) == 0\n            && self.max_frame.load(Ordering::Acquire) < self.min_frame.load(Ordering::Acquire)\n        {\n            tracing::debug!(\n                \"find_frame(page_id={}, frame_watermark={:?}): max_frame is 0 - read from DB file\",\n                page_id,\n                frame_watermark,\n            );\n            return Ok(None);\n        }\n        self.with_shared(|shared| {\n            let frames = shared.frame_cache.lock();\n            let range = frame_watermark.map(|x| 0..=x).unwrap_or_else(|| {\n                self.min_frame.load(Ordering::Acquire)..=self.max_frame.load(Ordering::Acquire)\n            });\n            tracing::debug!(\n                \"find_frame(page_id={}, frame_watermark={:?}): min_frame={}, max_frame={}\",\n                page_id,\n                frame_watermark,\n                self.min_frame.load(Ordering::Acquire),\n                self.max_frame.load(Ordering::Acquire)\n            );\n            if let Some(list) = frames.get(&page_id) {\n                if let Some(f) = list.iter().rfind(|&&f| range.contains(&f)) {\n                    tracing::debug!(\n                        \"find_frame(page_id={}, frame_watermark={:?}): found frame={}\",\n                        page_id,\n                        frame_watermark,\n                        *f\n                    );\n                    return Ok(Some(*f));\n                }\n            }\n            Ok(None)\n        })\n    }\n\n    /// Read a frame from the WAL.\n    #[instrument(skip_all, level = Level::DEBUG)]\n    fn read_frame(\n        &self,\n        frame_id: u64,\n        page: PageRef,\n        buffer_pool: Arc<BufferPool>,\n    ) -> Result<Completion> {\n        tracing::debug!(\n            \"read_frame(page_idx = {}, frame_id = {})\",\n            page.get().id,\n            frame_id\n        );\n        let offset = self.frame_offset(frame_id);\n        page.set_locked();\n        let frame = page.clone();\n        let page_idx = page.get().id;\n        let shared_file = self.shared.clone();\n        let complete = Box::new(move |res: Result<(Arc<Buffer>, i32), CompletionError>| {\n            let Ok((buf, bytes_read)) = res else {\n                tracing::debug!(err = ?res.unwrap_err());\n                page.clear_locked();\n                page.clear_wal_tag();\n                return None; // IO error already captured in completion\n            };\n            let buf_len = buf.len();\n            if bytes_read != buf_len as i32 {\n                tracing::debug!(\n                    \"WAL short read at offset {offset}, page {page_idx}, frame_id={frame_id}: expected {buf_len} bytes, got {bytes_read}\"\n                );\n                page.clear_locked();\n                page.clear_wal_tag();\n                return Some(CompletionError::ShortReadWalFrame {\n                    offset,\n                    expected: buf_len,\n                    actual: bytes_read as usize,\n                });\n            }\n            let cloned = frame.clone();\n            finish_read_page(page.get().id, buf, cloned);\n            let epoch = shared_file.read().epoch.load(Ordering::Acquire);\n            frame.set_wal_tag(frame_id, epoch);\n            None\n        });\n        let file = self.with_shared(|shared| {\n            turso_assert!(\n                shared.enabled.load(Ordering::Relaxed),\n                \"WAL must be enabled\"\n            );\n            // important not to hold shared lock beyond this point to avoid deadlock scenario where:\n            // thread 1: takes readlock here, passes reference to shared.file to begin_read_wal_frame\n            // thread 2: tries to acquire write lock elsewhere\n            // thread 1: tries to re-acquire read lock in the completion (see 'complete' above)\n            //\n            // this causes a deadlock due to the locking policy in parking_lot:\n            // from https://docs.rs/parking_lot/latest/parking_lot/type.RwLock.html:\n            // \"This lock uses a task-fair locking policy which avoids both reader and writer starvation.\n            // This means that readers trying to acquire the lock will block even if the lock is unlocked\n            // when there are writers waiting to acquire the lock.\n            // Because of this, attempts to recursively acquire a read lock within a single thread may result in a deadlock.\"\n            shared.file.as_ref().unwrap().clone()\n        });\n        begin_read_wal_frame(\n            file.as_ref(),\n            offset + WAL_FRAME_HEADER_SIZE as u64,\n            buffer_pool,\n            complete,\n            page_idx,\n            &self.io_ctx.read(),\n        )\n    }\n\n    #[instrument(skip_all, level = Level::DEBUG)]\n    // todo(sivukhin): change API to accept Buffer or some other owned type\n    // this method involves IO and cross \"async\" boundary - so juggling with references is bad and dangerous\n    fn read_frame_raw(&self, frame_id: u64, frame: &mut [u8]) -> Result<Completion> {\n        tracing::debug!(\"read_frame_raw({})\", frame_id);\n        let offset = self.frame_offset(frame_id);\n\n        // HACK: *mut u8 can't be Sent between threads safely, cast it to usize then\n        // for the time of writing this comment - this is *safe* as all callers immediately call synchronous method wait_for_completion and hold necessary references\n        let (frame_ptr, frame_len) = (frame.as_mut_ptr() as usize, frame.len());\n\n        let encryption_ctx = {\n            let io_ctx = self.io_ctx.read();\n            io_ctx.encryption_context().cloned()\n        };\n        let complete = Box::new(move |res: Result<(Arc<Buffer>, i32), CompletionError>| {\n            let Ok((buf, bytes_read)) = res else {\n                return None; // IO error already captured in completion\n            };\n            let buf_len = buf.len();\n            if bytes_read != buf_len as i32 {\n                tracing::debug!(\n                    \"short read on WAL frame {frame_id} at offset {offset}: expected {buf_len} bytes, got {bytes_read}\"\n                );\n                return Some(CompletionError::ShortReadWalFrame {\n                    offset,\n                    expected: buf_len,\n                    actual: bytes_read as usize,\n                });\n            }\n            let buf_ptr = buf.as_ptr();\n            let frame_ptr = frame_ptr as *mut u8;\n            let frame_ref: &mut [u8] =\n                unsafe { std::slice::from_raw_parts_mut(frame_ptr, frame_len) };\n\n            // Copy the just-read WAL frame into the destination buffer\n            unsafe {\n                std::ptr::copy_nonoverlapping(buf_ptr, frame_ptr, frame_len);\n            }\n\n            // Now parse the header from the freshly-copied data\n            let (header, raw_page) = sqlite3_ondisk::parse_wal_frame_header(frame_ref);\n\n            if let Some(ctx) = encryption_ctx.clone() {\n                match ctx.decrypt_page(raw_page, header.page_number as usize) {\n                    Ok(decrypted_data) => {\n                        turso_assert!(\n                            (frame_len - WAL_FRAME_HEADER_SIZE) == decrypted_data.len(),\n                            \"frame_len minus header_size does not equal expected decrypted data length\",\n                            { \"frame_len_minus_header\": frame_len - WAL_FRAME_HEADER_SIZE, \"decrypted_data_len\": decrypted_data.len() }\n                        );\n                        frame_ref[WAL_FRAME_HEADER_SIZE..].copy_from_slice(&decrypted_data);\n                    }\n                    Err(_) => {\n                        tracing::debug!(\"Failed to decrypt page data for frame_id={frame_id}\");\n                    }\n                }\n            }\n            None\n        });\n        let file = self.with_shared(|shared| {\n            turso_assert!(\n                shared.enabled.load(Ordering::Relaxed),\n                \"WAL must be enabled\"\n            );\n            shared.file.as_ref().unwrap().clone()\n        });\n        let c = begin_read_wal_frame_raw(&self.buffer_pool, file.as_ref(), offset, complete)?;\n        Ok(c)\n    }\n\n    #[instrument(skip_all, level = Level::DEBUG)]\n    // todo(sivukhin): change API to accept Buffer or some other owned type\n    // this method involves IO and cross \"async\" boundary - so juggling with references is bad and dangerous\n    fn write_frame_raw(\n        &self,\n        buffer_pool: Arc<BufferPool>,\n        frame_id: u64,\n        page_id: u64,\n        db_size: u64,\n        page: &[u8],\n        sync_type: FileSyncType,\n    ) -> Result<()> {\n        let Some(page_size) = PageSize::new(page.len() as u32) else {\n            bail_corrupt_error!(\"invalid page size: {}\", page.len());\n        };\n        self.ensure_header_if_needed(page_size, sync_type)?;\n        tracing::debug!(\"write_raw_frame({})\", frame_id);\n        // if page_size wasn't initialized before - we will initialize it during that raw write\n        if self.page_size() != 0 && page.len() != self.page_size() as usize {\n            return Err(LimboError::InvalidArgument(format!(\n                \"unexpected page size in frame: got={}, expected={}\",\n                page.len(),\n                self.page_size(),\n            )));\n        }\n        if frame_id > self.max_frame.load(Ordering::Acquire) + 1 {\n            // attempt to write frame out of sequential order - error out\n            return Err(LimboError::InvalidArgument(format!(\n                \"frame_id is beyond next frame in the WAL: frame_id={}, max_frame={}\",\n                frame_id,\n                self.max_frame.load(Ordering::Acquire)\n            )));\n        }\n        if frame_id <= self.max_frame.load(Ordering::Acquire) {\n            // just validate if page content from the frame matches frame in the WAL\n            let offset = self.frame_offset(frame_id);\n            let conflict = Arc::new(Mutex::new(false));\n\n            // HACK: *mut u8 can't be shared between threads safely, cast it to usize then\n            // for the time of writing this comment - this is *safe* as the function immediately call synchronous method wait_for_completion and hold necessary references\n            let (page_ptr, page_len) = (page.as_ptr() as usize, page.len());\n\n            let complete = Box::new({\n                let conflict = conflict.clone();\n                move |res: Result<(Arc<Buffer>, i32), CompletionError>| {\n                    let Ok((buf, bytes_read)) = res else {\n                        return None; // IO error already captured in completion\n                    };\n                    let buf_len = buf.len();\n                    if bytes_read != buf_len as i32 {\n                        tracing::debug!(\n                            \"short read on WAL frame validation at offset {offset}, page_id={page_id}: expected {buf_len} bytes, got {bytes_read}\"\n                        );\n                        return Some(CompletionError::ShortReadWalFrame {\n                            offset,\n                            expected: buf_len,\n                            actual: bytes_read as usize,\n                        });\n                    }\n                    let page = unsafe { std::slice::from_raw_parts(page_ptr as *mut u8, page_len) };\n                    if buf.as_slice() != page {\n                        *conflict.lock() = true;\n                    }\n                    None\n                }\n            });\n            let file = self.with_shared(|shared| {\n                turso_assert!(\n                    shared.enabled.load(Ordering::Relaxed),\n                    \"WAL must be enabled\"\n                );\n                shared.file.as_ref().unwrap().clone()\n            });\n            let c = begin_read_wal_frame(\n                file.as_ref(),\n                offset + WAL_FRAME_HEADER_SIZE as u64,\n                buffer_pool,\n                complete,\n                page_id as usize,\n                &self.io_ctx.read(),\n            )?;\n            self.io.wait_for_completion(c)?;\n            return if *conflict.lock() {\n                Err(LimboError::Conflict(format!(\n                    \"frame content differs from the WAL: frame_id={frame_id}\"\n                )))\n            } else {\n                Ok(())\n            };\n        }\n\n        // perform actual write\n        let offset = self.frame_offset(frame_id);\n        let (header, file) = self.with_shared(|shared| {\n            let header = shared.wal_header.clone();\n            turso_assert!(\n                shared.enabled.load(Ordering::Relaxed),\n                \"WAL must be enabled\"\n            );\n            let file = shared.file.as_ref().unwrap().clone();\n            (header, file)\n        });\n        let header = header.lock();\n        let checksums = *self.last_checksum.read();\n        let (checksums, frame_bytes) = prepare_wal_frame(\n            &self.buffer_pool,\n            &header,\n            checksums,\n            header.page_size,\n            page_id as u32,\n            db_size as u32,\n            page,\n        );\n        let c = Completion::new_write(|_| {});\n        let c = file.pwrite(offset, frame_bytes, c)?;\n        self.io.wait_for_completion(c)?;\n        self.complete_append_frame(page_id, frame_id, checksums);\n        if db_size > 0 {\n            self.finish_append_frames_commit()?;\n        }\n        Ok(())\n    }\n\n    #[instrument(skip_all, level = Level::DEBUG)]\n    fn should_checkpoint(&self) -> bool {\n        self.with_shared(|shared| {\n            let frame_id = shared.max_frame.load(Ordering::Acquire) as usize;\n            let nbackfills = shared.nbackfills.load(Ordering::Acquire) as usize;\n            frame_id > self.checkpoint_threshold + nbackfills\n        })\n    }\n\n    #[instrument(skip_all, level = Level::DEBUG)]\n    fn checkpoint(\n        &self,\n        pager: &Pager,\n        mode: CheckpointMode,\n    ) -> Result<IOResult<CheckpointResult>> {\n        self.checkpoint_inner(pager, mode).inspect_err(|e| {\n            tracing::debug!(\"Wal Checkpoint failed: {e}\");\n            let _ = self.checkpoint_guard.write().take();\n            self.ongoing_checkpoint.write().state = CheckpointState::Start;\n        })\n    }\n\n    #[instrument(err, skip_all, level = Level::DEBUG)]\n    fn sync(&self, sync_type: FileSyncType) -> Result<Completion> {\n        tracing::debug!(\"wal_sync\");\n        let syncing = self.syncing.clone();\n        let completion = Completion::new_sync(move |result| {\n            tracing::debug!(\"wal_sync finish\");\n            if let Err(err) = result {\n                tracing::info!(\"wal_sync failed: {err}\");\n            }\n            syncing.store(false, Ordering::Release);\n        });\n        let file = self.with_shared(|shared| {\n            turso_assert!(\n                shared.enabled.load(Ordering::Relaxed),\n                \"WAL must be enabled\"\n            );\n            shared.file.as_ref().unwrap().clone()\n        });\n        self.syncing.store(true, Ordering::Release);\n        let c = file.sync(completion, sync_type)?;\n        Ok(c)\n    }\n\n    // Currently used for assertion purposes\n    fn is_syncing(&self) -> bool {\n        self.syncing.load(Ordering::Acquire)\n    }\n\n    fn get_max_frame_in_wal(&self) -> u64 {\n        self.with_shared(|shared| shared.max_frame.load(Ordering::Acquire))\n    }\n\n    fn get_checkpoint_seq(&self) -> u32 {\n        self.with_shared(|shared| shared.wal_header.lock().checkpoint_seq)\n    }\n\n    fn get_max_frame(&self) -> u64 {\n        self.max_frame.load(Ordering::Acquire)\n    }\n\n    fn get_min_frame(&self) -> u64 {\n        self.min_frame.load(Ordering::Acquire)\n    }\n\n    fn get_last_checksum(&self) -> (u32, u32) {\n        *self.last_checksum.read()\n    }\n    #[instrument(skip_all, level = Level::DEBUG)]\n\n    fn rollback(&self, rollback_to: Option<RollbackTo>) {\n        let is_savepoint = rollback_to.is_some();\n        let (max_frame, last_checksum) = self.with_shared(|shared| {\n            let max_frame = rollback_to\n                .as_ref()\n                .map(|r| r.frame)\n                .unwrap_or_else(|| shared.max_frame.load(Ordering::Acquire));\n            let last_checksum = rollback_to\n                .as_ref()\n                .map(|r| r.checksum)\n                .unwrap_or(shared.last_checksum);\n            let mut frame_cache = shared.frame_cache.lock();\n            frame_cache.retain(|_page_id, frames| {\n                // keep frames <= max_frame\n                while frames.last().is_some_and(|&f| f > max_frame) {\n                    frames.pop();\n                }\n                !frames.is_empty()\n            });\n            (max_frame, last_checksum)\n        });\n        *self.last_checksum.write() = last_checksum;\n        self.max_frame.store(max_frame, Ordering::Release);\n        if !is_savepoint {\n            self.reset_internal_states();\n        }\n    }\n\n    fn abort_checkpoint(&self) {\n        let _ = self.checkpoint_guard.write().take();\n        self.reset_internal_states();\n    }\n\n    #[instrument(skip_all, level = Level::DEBUG)]\n    fn finish_append_frames_commit(&self) -> Result<()> {\n        self.with_shared_mut_dangerous(|shared| {\n            shared\n                .max_frame\n                .store(self.max_frame.load(Ordering::Acquire), Ordering::Release);\n            let last_checksum = *self.last_checksum.read();\n            tracing::trace!(\n                max_frame = self.max_frame.load(Ordering::Acquire),\n                ?last_checksum\n            );\n            shared.last_checksum = last_checksum;\n            let new_count = self.transaction_count.fetch_add(1, Ordering::AcqRel) + 1;\n            shared.transaction_count.store(new_count, Ordering::Release);\n            Ok(())\n        })\n    }\n\n    fn changed_pages_after(&self, frame_watermark: u64) -> Result<Vec<u32>> {\n        let frame_count = self.get_max_frame();\n        let page_size = self.page_size();\n        let mut frame = vec![0u8; page_size as usize + WAL_FRAME_HEADER_SIZE];\n        let mut seen = FxHashSet::default();\n        turso_assert!(\n            frame_count >= frame_watermark,\n            \"frame_count must be not less than frame_watermark\",\n            { \"frame_count\": frame_count, \"frame_watermark\": frame_watermark }\n        );\n        let mut pages = Vec::with_capacity((frame_count - frame_watermark) as usize);\n        for frame_no in frame_watermark + 1..=frame_count {\n            let c = self.read_frame_raw(frame_no, &mut frame)?;\n            self.io.wait_for_completion(c)?;\n            let (header, _) = sqlite3_ondisk::parse_wal_frame_header(&frame);\n            if seen.insert(header.page_number) {\n                pages.push(header.page_number);\n            }\n        }\n        Ok(pages)\n    }\n\n    fn prepare_wal_start(&self, page_size: PageSize) -> Result<Option<Completion>> {\n        if self.with_shared(|shared| shared.is_initialized())? {\n            return Ok(None);\n        }\n        tracing::debug!(\"ensure_header_if_needed\");\n        *self.last_checksum.write() = self.with_shared_mut_dangerous(|shared| {\n            let checksum = {\n                let mut hdr = shared.wal_header.lock();\n                hdr.magic = if cfg!(target_endian = \"big\") {\n                    WAL_MAGIC_BE\n                } else {\n                    WAL_MAGIC_LE\n                };\n                if hdr.page_size == 0 {\n                    hdr.page_size = page_size.get();\n                }\n                if hdr.salt_1 == 0 && hdr.salt_2 == 0 {\n                    hdr.salt_1 = self.io.generate_random_number() as u32;\n                    hdr.salt_2 = self.io.generate_random_number() as u32;\n                }\n\n                // recompute header checksum\n                let prefix = &hdr.as_bytes()[..WAL_HEADER_SIZE - 8];\n                let use_native = (hdr.magic & 1) != 0;\n                let (c1, c2) = checksum_wal(prefix, &hdr, (0, 0), use_native);\n                hdr.checksum_1 = c1;\n                hdr.checksum_2 = c2;\n                (c1, c2)\n            };\n            shared.last_checksum = checksum;\n            checksum\n        });\n\n        self.max_frame.store(0, Ordering::Release);\n        let (header, file) = self.with_shared(|shared| {\n            turso_assert!(\n                shared.enabled.load(Ordering::Relaxed),\n                \"WAL must be enabled\"\n            );\n            (\n                *shared.wal_header.lock(),\n                shared.file.as_ref().unwrap().clone(),\n            )\n        });\n        let c = sqlite3_ondisk::begin_write_wal_header(file.as_ref(), &header)?;\n        Ok(Some(c))\n    }\n\n    fn prepare_wal_finish(&self, sync_type: FileSyncType) -> Result<Completion> {\n        let file = self.with_shared(|shared| {\n            turso_assert!(\n                shared.enabled.load(Ordering::Relaxed),\n                \"WAL must be enabled\"\n            );\n            shared.file.as_ref().unwrap().clone()\n        });\n        let shared = self.shared.clone();\n        let c = file.sync(\n            Completion::new_sync(move |_| {\n                shared.read().initialized.store(true, Ordering::Release);\n            }),\n            sync_type,\n        )?;\n        Ok(c)\n    }\n\n    /// Prepares a batch of dirty pages as WAL frames without modifying WAL state.\n    ///\n    /// This is the first phase of a three-phase commit protocol:\n    /// 1. prepare (`prepare_frames`) - serialize frames, compute checksums\n    /// 2. write + fsync - caller submits I/O and waits for durability\n    /// 3. commit/finalize (`commit_prepared_frames`) - update WAL index and page metadata\n    ///\n    /// WAL frames form a checksum chain for corruption detection. When writing\n    /// multiple batches in a single transaction, pass the previous batch via `prev`\n    /// to continue the chain. For the first batch, pass `None` to start from\n    /// the committed WAL state.\n    fn prepare_frames(\n        &self,\n        pages: &[PageRef],\n        page_sz: PageSize,\n        db_size_on_commit: Option<u32>,\n        prev: Option<&PreparedFrames>,\n    ) -> Result<PreparedFrames> {\n        turso_assert!(\n            pages.len() <= IOV_MAX,\n            \"supported up to IOV_MAX pages at once\"\n        );\n        turso_assert!(\n            self.with_shared(|shared| shared.is_initialized())?,\n            \"WAL must be initialized\"\n        );\n\n        let (header, epoch) = self.with_shared(|shared| {\n            let hdr = *shared.wal_header.lock();\n            let epoch = shared.epoch.load(Ordering::Acquire);\n            (hdr, epoch)\n        });\n\n        turso_assert!(\n            header.page_size == page_sz.get(),\n            \"page size mismatch between header and requested\",\n            { \"header_page_size\": header.page_size, \"requested_page_size\": page_sz.get() }\n        );\n\n        // Either chain from previous batch of PreparedFrames or use committed WAL state\n        let (mut rolling_checksum, mut next_frame_id) = match prev {\n            Some(p) => (p.final_checksum, p.final_max_frame + 1),\n            None => (\n                *self.last_checksum.read(),\n                self.max_frame.load(Ordering::Acquire) + 1,\n            ),\n        };\n\n        let first_frame_id = next_frame_id;\n\n        let mut bufs: Vec<Arc<Buffer>> = Vec::with_capacity(pages.len());\n        let mut metadata = Vec::with_capacity(pages.len());\n\n        for (idx, page) in pages.iter().enumerate() {\n            let page_id = page.get().id;\n            let plain = page.get_contents().as_ptr();\n\n            let data: Cow<[u8]> = {\n                let io_ctx = self.io_ctx.read();\n                match io_ctx.encryption_or_checksum() {\n                    EncryptionOrChecksum::Encryption(ctx) => {\n                        Cow::Owned(ctx.encrypt_page(plain, page_id)?)\n                    }\n                    EncryptionOrChecksum::Checksum(ctx) => {\n                        ctx.add_checksum_to_page(plain, page_id)?;\n                        Cow::Borrowed(plain)\n                    }\n                    EncryptionOrChecksum::None => Cow::Borrowed(plain),\n                }\n            };\n\n            // if DB size is included for commit frame, it will need to be included only in the last frame of the batch.\n            // however it might not be present in this batch so we cannot assert its presence\n            let frame_db_size = if idx + 1 == pages.len() {\n                db_size_on_commit.unwrap_or(0)\n            } else {\n                0\n            };\n            let (checksum, frame_buf) = prepare_wal_frame(\n                &self.buffer_pool,\n                &header,\n                rolling_checksum,\n                header.page_size,\n                page_id as u32,\n                frame_db_size,\n                &data,\n            );\n            bufs.push(frame_buf);\n            metadata.push((page.clone(), next_frame_id, checksum));\n            rolling_checksum = checksum;\n            next_frame_id += 1;\n        }\n        let offset = self.frame_offset(first_frame_id);\n        Ok(PreparedFrames {\n            offset,\n            bufs,\n            metadata,\n            final_checksum: rolling_checksum,\n            final_max_frame: next_frame_id - 1,\n            epoch,\n        })\n    }\n\n    /// For each prepared frame, update in-memory WAL index and rolling checksum.\n    /// and advance max_frame to make frames visible to readers.\n    fn commit_prepared_frames(&self, batches: &[PreparedFrames]) {\n        for batch in batches {\n            for (page, frame_id, checksum) in &batch.metadata {\n                // Update WAL index mapping page -> frame\n                self.complete_append_frame(page.get().id as u64, *frame_id, *checksum);\n            }\n            // Update rolling checksum\n            *self.last_checksum.write() = batch.final_checksum;\n            // Advance max_frame and make frames visible to readers\n            self.max_frame\n                .store(batch.final_max_frame, Ordering::Release);\n        }\n    }\n\n    /// Mark pages clean and set WAL tags after durable commit.\n    fn finalize_committed_pages(&self, prepared: &[PreparedFrames]) {\n        for batch in prepared {\n            for (page, frame_id, _) in &batch.metadata {\n                page.clear_dirty();\n                page.set_wal_tag(*frame_id, batch.epoch);\n            }\n        }\n    }\n\n    /// Get WAL file for durable writes.\n    fn wal_file(&self) -> Result<Arc<dyn File>> {\n        self.with_shared(|shared| {\n            turso_assert!(\n                shared.enabled.load(Ordering::Relaxed),\n                \"WAL must be enabled\"\n            );\n            shared.file.as_ref().cloned().ok_or_else(|| {\n                mark_unlikely();\n                LimboError::InternalError(\"WAL file not open\".into())\n            })\n        })\n    }\n\n    /// Use pwritev to append many frames to the log at once.\n    ///\n    /// # Safety:\n    /// this method should only be used for cacheflush/spilling,\n    /// the commit path should use prepare_frames + commit_prepared_frames instead,\n    /// as it prevents prematurely modifing WAL state before durability is ensured.\n    fn append_frames_vectored(&self, pages: Vec<PageRef>, page_sz: PageSize) -> Result<Completion> {\n        turso_assert!(\n            pages.len() <= IOV_MAX,\n            \"we limit number of iovecs to IOV_MAX\"\n        );\n        turso_assert!(\n            self.with_shared(|shared| shared.is_initialized())?,\n            \"WAL must be prepared with prepare_wal_start/prepare_wal_finish method\"\n        );\n\n        let (header, shared_page_size, epoch) = self.with_shared(|shared| {\n            let hdr_guard = shared.wal_header.lock();\n            let header: WalHeader = *hdr_guard;\n            let shared_page_size = header.page_size;\n            let epoch = shared.epoch.load(Ordering::Acquire);\n            (header, shared_page_size, epoch)\n        });\n        turso_assert!(\n            shared_page_size == page_sz.get(),\n            \"page size mismatch, tried to change page size after WAL header was already initialized\",\n            { \"shared_page_size\": shared_page_size, \"page_size\": page_sz.get() }\n        );\n\n        // Prepare write buffers and bookkeeping\n        let mut iovecs: Vec<Arc<Buffer>> = Vec::with_capacity(pages.len());\n        let mut page_frame_and_checksum: Vec<(PageRef, u64, (u32, u32))> =\n            Vec::with_capacity(pages.len());\n\n        // Rolling checksum input to each frame build\n        let mut rolling_checksum: (u32, u32) = *self.last_checksum.read();\n\n        let mut next_frame_id = self.max_frame.load(Ordering::Acquire) + 1;\n        // Build every frame in order, updating the rolling checksum\n        for page in pages.iter() {\n            tracing::debug!(\"append_frames_vectored: page_id={}\", page.get().id);\n            let page_id = page.get().id;\n            let plain = page.get_contents().as_ptr();\n\n            let data_to_write: std::borrow::Cow<[u8]> = {\n                let io_ctx = self.io_ctx.read();\n                match &io_ctx.encryption_or_checksum() {\n                    EncryptionOrChecksum::Encryption(ctx) => {\n                        Cow::Owned(ctx.encrypt_page(plain, page_id)?)\n                    }\n                    EncryptionOrChecksum::Checksum(ctx) => {\n                        ctx.add_checksum_to_page(plain, page_id)?;\n                        Cow::Borrowed(plain)\n                    }\n                    EncryptionOrChecksum::None => Cow::Borrowed(plain),\n                }\n            };\n\n            let frame_db_size = 0; // this method is not used for the commit path\n            let (new_checksum, frame_bytes) = prepare_wal_frame(\n                &self.buffer_pool,\n                &header,\n                rolling_checksum,\n                shared_page_size,\n                page_id as u32,\n                frame_db_size,\n                &data_to_write,\n            );\n            iovecs.push(frame_bytes);\n\n            // (page, assigned_frame_id, cumulative_checksum_at_this_frame)\n            page_frame_and_checksum.push((page.clone(), next_frame_id, new_checksum));\n\n            // Advance for the next frame\n            rolling_checksum = new_checksum;\n            next_frame_id += 1;\n        }\n\n        let first_frame_id = self.max_frame.load(Ordering::Acquire) + 1;\n        let start_off = self.frame_offset(first_frame_id);\n\n        // single completion for the whole batch\n        let total_len: i32 = iovecs.iter().map(|b| b.len() as i32).sum();\n        let page_frame_for_cb = page_frame_and_checksum.clone();\n        let cmp = move |res: Result<i32, CompletionError>| {\n            let Ok(bytes_written) = res else {\n                return;\n            };\n            turso_assert!(\n                bytes_written == total_len,\n                \"pwritev wrote unexpected number of bytes\",\n                { \"bytes_written\": bytes_written, \"expected\": total_len }\n            );\n\n            for (page, fid, _csum) in &page_frame_for_cb {\n                page.clear_dirty();\n                page.set_wal_tag(*fid, epoch);\n            }\n        };\n\n        let c = Completion::new_write(cmp);\n\n        let file = self.with_shared(|shared| {\n            turso_assert!(\n                shared.enabled.load(Ordering::Relaxed),\n                \"WAL must be enabled\"\n            );\n            shared.file.as_ref().unwrap().clone()\n        });\n        let c = file.pwritev(start_off, iovecs, c)?;\n\n        self.io.drain()?;\n\n        for (page, fid, csum) in &page_frame_and_checksum {\n            self.complete_append_frame(page.get().id as u64, *fid, *csum);\n        }\n\n        Ok(c)\n    }\n\n    #[cfg(debug_assertions)]\n    fn as_any(&self) -> &dyn std::any::Any {\n        self\n    }\n\n    fn set_io_context(&self, ctx: IOContext) {\n        *self.io_ctx.write() = ctx;\n    }\n\n    fn update_max_frame(&self) {\n        self.with_shared(|shared| {\n            let new_max_frame = shared.max_frame.load(Ordering::Acquire);\n            self.max_frame.store(new_max_frame, Ordering::Release);\n        })\n    }\n\n    fn truncate_wal(\n        &self,\n        result: &mut CheckpointResult,\n        sync_type: FileSyncType,\n    ) -> Result<IOResult<()>> {\n        self.truncate_log(result, sync_type)\n    }\n}\n\nimpl WalFile {\n    pub fn new(\n        io: Arc<dyn IO>,\n        shared: Arc<RwLock<WalFileShared>>,\n        (last_checksum, max_frame): ((u32, u32), u64),\n        buffer_pool: Arc<BufferPool>,\n    ) -> Self {\n        let now = io.current_time_monotonic();\n        Self {\n            io,\n            // default to max frame in WAL, so that when we read schema we can read from WAL too if it's there.\n            max_frame: AtomicU64::new(max_frame),\n            shared,\n            ongoing_checkpoint: RwLock::new(OngoingCheckpoint {\n                time: now,\n                pending_writes: WriteBatch::new(),\n                inflight_writes: Vec::new(),\n                state: CheckpointState::Start,\n                min_frame: 0,\n                max_frame: 0,\n                current_page: 0,\n                pages_to_checkpoint: Vec::new(),\n                inflight_reads: Vec::with_capacity(MAX_INFLIGHT_READS),\n            }),\n            checkpoint_threshold: 1000,\n            buffer_pool,\n            checkpoint_seq: AtomicU32::new(0),\n            syncing: Arc::new(AtomicBool::new(false)),\n            write_lock_held: AtomicBool::new(false),\n            min_frame: AtomicU64::new(0),\n            transaction_count: AtomicU64::new(0),\n            max_frame_read_lock_index: AtomicUsize::new(NO_LOCK_HELD),\n            last_checksum: RwLock::new(last_checksum),\n            checkpoint_guard: RwLock::new(None),\n            io_ctx: RwLock::new(IOContext::default()),\n        }\n    }\n\n    fn page_size(&self) -> u32 {\n        self.with_shared(|shared| shared.wal_header.lock().page_size)\n    }\n\n    fn frame_offset(&self, frame_id: u64) -> u64 {\n        turso_assert_greater_than!(frame_id, 0, \"Frame ID must be 1-based\");\n        let page_offset = (frame_id - 1) * (self.page_size() + WAL_FRAME_HEADER_SIZE as u32) as u64;\n        WAL_HEADER_SIZE as u64 + page_offset\n    }\n\n    fn _get_shared_mut(&self) -> crate::sync::RwLockWriteGuard<'_, WalFileShared> {\n        // WASM in browser main thread doesn't have a way to \"park\" a thread\n        // so, we spin way here instead of calling blocking lock\n        #[cfg(target_family = \"wasm\")]\n        {\n            loop {\n                let Some(lock) = self.shared.try_write() else {\n                    std::hint::spin_loop();\n                    continue;\n                };\n                return lock;\n            }\n        }\n        #[cfg(not(target_family = \"wasm\"))]\n        {\n            self.shared.write()\n        }\n    }\n\n    fn _get_shared(&self) -> crate::sync::RwLockReadGuard<'_, WalFileShared> {\n        // WASM in browser main thread doesn't have a way to \"park\" a thread\n        // so, we spin way here instead of calling blocking lock\n        #[cfg(target_family = \"wasm\")]\n        {\n            loop {\n                let Some(lock) = self.shared.try_read() else {\n                    std::hint::spin_loop();\n                    continue;\n                };\n                return lock;\n            }\n        }\n        #[cfg(not(target_family = \"wasm\"))]\n        {\n            self.shared.read()\n        }\n    }\n\n    #[inline]\n    /// Get a mutable shared lock on the WAL file shared state.\n    /// Be very intentional about when you need this because it can easily cause a deadlock.\n    /// If you're modifying e.g. the WAL locks, all of those operations are atomic and do not\n    /// need shared_mut.\n    fn with_shared_mut_dangerous<F, R>(&self, func: F) -> R\n    where\n        F: FnOnce(&mut WalFileShared) -> R,\n    {\n        let mut shared = self._get_shared_mut();\n        func(&mut shared)\n    }\n\n    #[inline]\n    fn with_shared<F, R>(&self, func: F) -> R\n    where\n        F: FnOnce(&WalFileShared) -> R,\n    {\n        let shared = self._get_shared();\n        func(&shared)\n    }\n\n    fn increment_checkpoint_epoch(&self) {\n        self.with_shared(|shared| {\n            let prev = shared.epoch.fetch_add(1, Ordering::Release);\n            tracing::debug!(\"increment checkpoint epoch: prev={}\", prev);\n        });\n    }\n\n    fn complete_append_frame(&self, page_id: u64, frame_id: u64, checksums: (u32, u32)) {\n        *self.last_checksum.write() = checksums;\n        self.max_frame.store(frame_id, Ordering::Release);\n        self.with_shared(|shared| {\n            let mut frame_cache = shared.frame_cache.lock();\n            match frame_cache.get_mut(&page_id) {\n                Some(frames) => {\n                    frames.push(frame_id);\n                }\n                None => {\n                    frame_cache.insert(page_id, vec![frame_id]);\n                }\n            }\n        })\n    }\n\n    /// Reset connection-private WAL state.\n    fn reset_internal_states(&self) {\n        self.ongoing_checkpoint.write().reset();\n        self.syncing.store(false, Ordering::Release);\n    }\n\n    /// the WAL file has been truncated and we are writing the first\n    /// frame since then. We need to ensure that the header is initialized.\n    fn ensure_header_if_needed(&self, page_size: PageSize, sync_type: FileSyncType) -> Result<()> {\n        let Some(c) = self.prepare_wal_start(page_size)? else {\n            return Ok(());\n        };\n        self.io.wait_for_completion(c)?;\n        let c = self.prepare_wal_finish(sync_type)?;\n        self.io.wait_for_completion(c)?;\n        Ok(())\n    }\n\n    fn checkpoint_inner(\n        &self,\n        pager: &Pager,\n        mode: CheckpointMode,\n    ) -> Result<IOResult<CheckpointResult>> {\n        loop {\n            let state = self.ongoing_checkpoint.read().state.clone();\n            tracing::debug!(?state);\n            match state {\n                // Acquire the relevant exclusive locks and checkpoint_lock\n                // so no other checkpointer can run. fsync WAL if there are unapplied frames.\n                // Decide the largest frame we are allowed to back‑fill.\n                CheckpointState::Start => {\n                    let (max_frame, nbackfills) = self.with_shared(|shared| {\n                        let max_frame = shared.max_frame.load(Ordering::Acquire);\n                        let n_backfills = shared.nbackfills.load(Ordering::Acquire);\n                        (max_frame, n_backfills)\n                    });\n                    tracing::debug!(\"shared_wal: max_frame={max_frame}, nbackfills={nbackfills}\");\n                    let needs_backfill = max_frame > nbackfills;\n                    if !needs_backfill && !mode.should_restart_log() {\n                        // there are no frames to copy over and we don't need to reset\n                        // the log so we can return early success.\n                        return Ok(IOResult::Done(CheckpointResult::new(\n                            max_frame, nbackfills, 0,\n                        )));\n                    }\n                    // acquire the appropriate exclusive locks depending on the checkpoint mode\n                    self.acquire_proper_checkpoint_guard(mode)?;\n                    let mut max_frame = self.determine_max_safe_checkpoint_frame();\n\n                    if let CheckpointMode::Truncate {\n                        upper_bound_inclusive: Some(upper_bound),\n                    } = mode\n                    {\n                        if max_frame > upper_bound {\n                            tracing::info!(\"abort checkpoint because latest frame in WAL is greater than upper_bound in TRUNCATE mode: {max_frame} != {upper_bound}\");\n                            return Err(LimboError::Busy);\n                        }\n                    }\n                    if let CheckpointMode::Passive {\n                        upper_bound_inclusive: Some(upper_bound),\n                    } = mode\n                    {\n                        max_frame = max_frame.min(upper_bound);\n                    }\n\n                    {\n                        let mut oc = self.ongoing_checkpoint.write();\n                        oc.max_frame = max_frame;\n                        oc.min_frame = nbackfills + 1;\n                    }\n                    let (oc_min_frame, oc_max_frame) = {\n                        let oc = self.ongoing_checkpoint.read();\n                        (oc.min_frame, oc.max_frame)\n                    };\n                    tracing::debug!(\"checkpoint_inner::Start: min_frame={oc_min_frame}, max_frame={oc_max_frame}\");\n                    let to_checkpoint = self.with_shared(|shared| {\n                        let frame_cache = shared.frame_cache.lock();\n                        let mut list = Vec::with_capacity(\n                            oc_max_frame.checked_sub(nbackfills).unwrap_or_default() as usize,\n                        );\n                        for (&page_id, frames) in frame_cache.iter() {\n                            // for each page in the frame cache, grab the last (latest) frame for\n                            // that page that falls in the range of our safe min..max frame\n                            if let Some(&frame) = frames\n                                .iter()\n                                .rev()\n                                .find(|&&f| f >= oc_min_frame && f <= oc_max_frame)\n                            {\n                                list.push((page_id, frame));\n                            }\n                        }\n                        // sort by frame_id for read locality\n                        list.sort_unstable_by(|a, b| (a.1, a.0).cmp(&(b.1, b.0)));\n                        list\n                    });\n                    {\n                        let mut oc = self.ongoing_checkpoint.write();\n                        oc.pages_to_checkpoint = to_checkpoint;\n                        oc.current_page = 0;\n                        oc.inflight_writes.clear();\n                        oc.inflight_reads.clear();\n                        oc.state = CheckpointState::Processing;\n                        oc.time = self.io.current_time_monotonic();\n                    }\n                    tracing::trace!(\n                        \"checkpoint_start(min_frame={}, max_frame={})\",\n                        oc_min_frame,\n                        oc_max_frame,\n                    );\n                }\n                // For locality, reading is ordered by frame ID, and writing ordered by page ID.\n                // the more consecutive page ID's that we submit together, the fewer overall\n                // write/writev syscalls made. All I/O during checkpointing is now in a single step\n                // to prevent serialization, and we try to issue reads and flush batches concurrently\n                // if at all possible, at the cost of some batching potential.\n                CheckpointState::Processing => {\n                    // Gather I/O completions using a completion group\n                    let mut nr_completions = 0;\n                    let mut group = CompletionGroup::new(|_| {});\n                    let mut ongoing_chkpt = self.ongoing_checkpoint.write();\n\n                    // Check and clean any completed writes from pending flush\n                    if ongoing_chkpt.process_inflight_writes() {\n                        tracing::trace!(\"Completed a write batch\");\n                    }\n                    // Process completed reads into current batch\n                    if ongoing_chkpt.process_pending_reads()? {\n                        tracing::trace!(\"Drained reads into batch\");\n                    }\n                    if let Some(e) = ongoing_chkpt.first_write_error() {\n                        mark_unlikely();\n                        // cancel everything still in-flight to avoid leaks\n                        let to_cancel: Vec<Completion> = ongoing_chkpt\n                            .inflight_reads\n                            .iter()\n                            .map(|r| r.completion.clone())\n                            .collect();\n                        pager.io.cancel(&to_cancel)?;\n                        pager.io.drain()?;\n                        return Err(LimboError::CompletionError(e));\n                    }\n                    let epoch = self.with_shared(|shared| shared.epoch.load(Ordering::Acquire));\n                    // Issue reads until we hit limits\n                    'inner: while ongoing_chkpt.should_issue_reads() {\n                        let (page_id, target_frame) = {\n                            ongoing_chkpt.pages_to_checkpoint[ongoing_chkpt.current_page as usize]\n                        };\n                        if let Some(cached_page) =\n                            pager.cache_get_for_checkpoint(page_id as usize, target_frame, epoch)?\n                        {\n                            let buffer = cached_page\n                                .get_contents()\n                                .buffer\n                                .as_ref()\n                                .expect(\"buffer missing\")\n                                .clone();\n                            // We debug assert that the cached page has the\n                            // exact contents as one read from the WAL.\n                            #[cfg(debug_assertions)]\n                            {\n                                let mut raw =\n                                    vec![0u8; self.page_size() as usize + WAL_FRAME_HEADER_SIZE];\n                                self.io.wait_for_completion(\n                                    self.read_frame_raw(target_frame, &mut raw)?,\n                                )?;\n                                let (_, wal_page) = sqlite3_ondisk::parse_wal_frame_header(&raw);\n                                let cached = buffer.as_slice();\n                                turso_assert!(wal_page == cached, \"cached page content differs from WAL read\", { \"page_id\": page_id, \"frame_id\": target_frame });\n                            }\n                            {\n                                ongoing_chkpt\n                                    .pending_writes\n                                    .insert(page_id as usize, buffer);\n                                // signify that a cached page was used, so it can be unpinned\n                                let current = ongoing_chkpt.current_page as usize;\n                                ongoing_chkpt.pages_to_checkpoint[current] =\n                                    (page_id, target_frame);\n                                ongoing_chkpt.current_page += 1;\n                            }\n                            continue 'inner;\n                        }\n                        // Issue read if page wasn't found in the page cache or doesnt meet\n                        // the frame requirements\n                        let inflight =\n                            self.issue_wal_read_into_buffer(page_id as usize, target_frame)?;\n                        group.add(&inflight.completion);\n                        nr_completions += 1;\n                        ongoing_chkpt.inflight_reads.push(inflight);\n                        ongoing_chkpt.current_page += 1;\n                    }\n\n                    // Start a write if batch is ready and we're not at write limit\n                    let should_flush = ongoing_chkpt.inflight_writes.len() < MAX_INFLIGHT_WRITES\n                        && ongoing_chkpt.should_flush_batch();\n                    if should_flush {\n                        let batch_map = ongoing_chkpt.pending_writes.take();\n                        if !batch_map.is_empty() {\n                            let new_write = InflightWriteBatch::new();\n                            for c in write_pages_vectored(\n                                pager,\n                                batch_map,\n                                new_write.done.clone(),\n                                new_write.err.clone(),\n                            )? {\n                                group.add(&c);\n                                nr_completions += 1;\n                            }\n                            ongoing_chkpt.inflight_writes.push(new_write);\n                        }\n                    }\n                    if nr_completions > 0 {\n                        io_yield_one!(group.build());\n                    } else if ongoing_chkpt.complete() {\n                        ongoing_chkpt.state = CheckpointState::DetermineResult;\n                    } else {\n                        // This should be impossible now so we treat it as logic error.\n                        mark_unlikely();\n                        return Err(LimboError::InternalError(\n                            \"checkpoint stuck: no inflight completions but not complete\".into(),\n                        ));\n                    }\n                }\n                // All eligible frames copied to the db file.\n                // Compute checkpoint result, update nBackfills, restart log if needed.\n                CheckpointState::DetermineResult => {\n                    let mut ongoing_chkpt = self.ongoing_checkpoint.write();\n                    turso_assert!(\n                        ongoing_chkpt.complete(),\n                        \"checkpoint pending flush must have finished\"\n                    );\n                    let checkpoint_result = self.with_shared(|shared| {\n                        let wal_max_frame = shared.max_frame.load(Ordering::Acquire);\n                        let wal_total_backfilled = ongoing_chkpt.max_frame;\n                        // Record two num pages fields to return as checkpoint result to caller.\n                        // Ref: pnLog, pnCkpt on https://www.sqlite.org/c3ref/wal_checkpoint_v2.html\n\n                        // the total # of frames we actually backfilled\n                        let wal_checkpoint_backfilled =\n                            wal_total_backfilled.saturating_sub(ongoing_chkpt.min_frame - 1);\n\n                        tracing::debug!(\"checkpoint: wal_max_frame={wal_max_frame}, wal_total_backfilled={wal_total_backfilled}, wal_checkpoint_backfilled={wal_checkpoint_backfilled}\");\n\n                        CheckpointResult::new(wal_max_frame, wal_total_backfilled, wal_checkpoint_backfilled)\n                    });\n                    tracing::debug!(\"checkpoint_result={:?}, mode={:?}\", checkpoint_result, mode);\n\n                    // store the max frame we were able to successfully checkpoint.\n                    // NOTE: we don't have a .shm file yet, so it's safe to update nbackfills here\n                    // before we sync, because if we crash and then recover, we will checkpoint the entire db anyway.\n                    self.with_shared(|shared| {\n                        shared\n                            .nbackfills\n                            .store(ongoing_chkpt.max_frame, Ordering::Release)\n                    });\n                    if mode.require_all_backfilled() && !checkpoint_result.everything_backfilled() {\n                        return Err(LimboError::Busy);\n                    }\n                    if mode.should_restart_log() {\n                        turso_assert!(\n                            matches!(\n                                *self.checkpoint_guard.read(),\n                                Some(CheckpointLocks::Writer { .. })\n                            ),\n                            \"We must hold writer and checkpoint locks to restart the log\",\n                            { \"checkpoint_guard\": *self.checkpoint_guard.read() }\n                        );\n                        self.restart_log()?;\n                    }\n                    ongoing_chkpt.state = CheckpointState::Finalize {\n                        checkpoint_result: Some(checkpoint_result),\n                    };\n                }\n                CheckpointState::Finalize { .. } => {\n                    // NOTE: For TRUNCATE mode, WAL truncation is NOT done here.\n                    // It is deferred to pager.rs after the DB file has been synced,\n                    // at which point it calls truncate_wal().\n                    // This ensures data durability: if a crash occurs after WAL truncation\n                    // but before DB sync, the data would be lost. By truncating the WAL\n                    // only after the DB is safely synced, we guarantee recoverability.\n                    if mode.should_restart_log() {\n                        Self::unlock_after_restart(&self.shared, None);\n                    }\n                    let mut checkpoint_result = {\n                        let mut oc = self.ongoing_checkpoint.write();\n                        let CheckpointState::Finalize {\n                            checkpoint_result, ..\n                        } = &mut oc.state\n                        else {\n                            panic!(\"unexpected state\");\n                        };\n                        checkpoint_result.take().unwrap()\n                    };\n                    // increment wal epoch to ensure no stale pages are used for backfilling\n                    self.increment_checkpoint_epoch();\n\n                    tracing::debug!(\"checkpoint_result={:?}\", checkpoint_result);\n                    // we cannot truncate the db file here because we are currently inside a\n                    // mut borrow of pager.wal, and accessing the header will attempt a borrow\n                    // during 'read_page', so the caller will use the result to determine if:\n                    // a. the max frame == num wal frames (everything backfilled)\n                    // b. the max frame > 0 (we have something to truncate)\n                    if checkpoint_result.should_truncate() {\n                        checkpoint_result.maybe_guard = self.checkpoint_guard.write().take();\n                    } else {\n                        let _ = self.checkpoint_guard.write().take();\n                    }\n                    {\n                        let mut oc = self.ongoing_checkpoint.write();\n                        oc.inflight_writes.clear();\n                        oc.pending_writes.clear();\n                        oc.pages_to_checkpoint.clear();\n                        oc.current_page = 0;\n                    }\n                    let oc_time = self.ongoing_checkpoint.read().time;\n                    tracing::debug!(\n                        \"total time spent checkpointing: {:?}\",\n                        self.io\n                            .current_time_monotonic()\n                            .duration_since(oc_time)\n                            .as_millis()\n                    );\n                    self.ongoing_checkpoint.write().state = CheckpointState::Start;\n                    return Ok(IOResult::Done(checkpoint_result));\n                }\n            }\n        }\n    }\n\n    /// Coordinate what the maximum safe frame is for us to backfill when checkpointing.\n    /// We can never backfill a frame with a higher number than any reader's read mark,\n    /// because we might overwrite content the reader is reading from the database file.\n    ///\n    /// A checkpoint must never overwrite a page in the main DB file if some\n    /// active reader might still need to read that page from the WAL.  \n    /// Concretely: the checkpoint may only copy frames `<= aReadMark[k]` for\n    /// every in-use reader slot `k > 0`.\n    ///\n    /// `read_locks[0]` is special: readers holding slot 0 ignore the WAL entirely\n    /// (they read only the DB file). Its value is a placeholder and does not\n    /// constrain `mxSafeFrame`.\n    ///\n    /// For each slot 1..N:\n    /// - If we can acquire the write lock (slot is free):\n    ///   - Slot 1: Set to mxSafeFrame (allowing new readers to see up to this point)\n    ///   - Slots 2+: Set to READMARK_NOT_USED (freeing the slot)\n    /// - If we cannot acquire the lock (SQLITE_BUSY):\n    ///   - Lower mxSafeFrame to that reader's mark\n    ///   - In PASSIVE mode: Already have no busy handler, continue scanning\n    ///   - In FULL/RESTART/TRUNCATE: Disable busy handler for remaining slots\n    ///\n    /// Locking behavior:\n    /// - PASSIVE: Never waits, no busy handler (xBusy==NULL)\n    /// - FULL/RESTART/TRUNCATE: May wait via busy handler, but after first BUSY,\n    ///   switches to non-blocking for remaining slots\n    ///\n    /// We never modify slot values while a reader holds that slot's lock.\n    /// TOOD: implement proper BUSY handling behavior\n    fn determine_max_safe_checkpoint_frame(&self) -> u64 {\n        self.with_shared(|shared| {\n            let shared_max = shared.max_frame.load(Ordering::Acquire);\n            let mut max_safe_frame = shared_max;\n\n            for (read_lock_idx, read_lock) in shared.read_locks.iter().enumerate().skip(1) {\n                let this_mark = read_lock.get_value();\n                if this_mark < max_safe_frame as u32 {\n                    let busy = !read_lock.write();\n                    if !busy {\n                        let val = if read_lock_idx == 1 {\n                            // store the max_frame for the default read slot 1\n                            max_safe_frame as u32\n                        } else {\n                            READMARK_NOT_USED\n                        };\n                        read_lock.set_value_exclusive(val);\n                        read_lock.unlock();\n                    } else {\n                        max_safe_frame = this_mark as u64;\n                    }\n                }\n            }\n            max_safe_frame\n        })\n    }\n\n    /// attempt to restart WAL header before write in order to keep WAL file size under the control\n    /// The conditions for WAL restart are following:\n    /// 1. we can do that only under write transaction\n    /// 2. max_frame_read_lock_index == 0 - this means that transaction was initiated to read data from DB file\n    /// 3. nbackfills > 0 - otherwise nothing was backfilled and there is no reason to truncate header\n    /// 4. max_frame == nbackfills - otherwise there are some non-checkpointed frames in the WAL and we can't truncate the log\n    pub fn try_restart_log_before_write(&self) -> Result<()> {\n        let max_frame_read_lock_index = self.max_frame_read_lock_index.load(Ordering::Acquire);\n        if max_frame_read_lock_index != 0 {\n            tracing::debug!(\"try_restart_log_before_write: max_frame_read_lock_index={max_frame_read_lock_index}, writer use WAL - can't restart the log\");\n            return Ok(());\n        }\n        let (max_frame, nbackfills) = self.with_shared(|s| {\n            (\n                s.max_frame.load(Ordering::Acquire),\n                s.nbackfills.load(Ordering::Acquire),\n            )\n        });\n        if nbackfills == 0 {\n            tracing::debug!(\"try_restart_log_before_write: nbackfills={nbackfills}, nothing were backfilled - can't restart the log\");\n            return Ok(());\n        }\n        turso_assert!(\n            max_frame >= nbackfills,\n            \"backfills can't be more than max_frame\"\n        );\n        if max_frame != nbackfills {\n            tracing::debug!(\n                \"try_restart_log_before_write: max_frame={max_frame}, nbackfills={nbackfills}, not everything is backfilled to the DB file - can't restart the log\"\n            );\n            return Ok(());\n        }\n        let read_lock_0 = self.with_shared(|s| s.read_locks[0].upgrade());\n        if !read_lock_0 {\n            return Ok(());\n        }\n        let result = self.restart_log();\n        if result.is_ok() {\n            self.increment_checkpoint_epoch();\n            let shared = self.shared.clone();\n            Self::unlock_after_restart(&shared, result.as_ref().err());\n        }\n        self.with_shared(|s| s.read_locks[0].downgrade());\n        tracing::debug!(\"try_restart_log_before_write: result={:?}\", result);\n        result\n    }\n\n    fn restart_log(&self) -> Result<()> {\n        tracing::debug!(\"restart_log\");\n        self.with_shared(|shared| {\n            // Block all readers\n            for idx in 1..shared.read_locks.len() {\n                let lock = &shared.read_locks[idx];\n                if !lock.write() {\n                    // release everything we got so far\n                    for j in 1..idx {\n                        shared.read_locks[j].unlock();\n                    }\n                    // Reader is active, cannot proceed\n                    return Err(LimboError::Busy);\n                }\n                // after the log is reset, we must set all secondary marks to READMARK_NOT_USED so the next reader selects a fresh slot\n                lock.set_value_exclusive(READMARK_NOT_USED);\n            }\n            Ok(())\n        })?;\n\n        // reinitialize in‑memory state\n        self.with_shared_mut_dangerous(|shared| shared.restart_wal_header(&self.io));\n        let cksm = self.with_shared(|shared| shared.last_checksum);\n        *self.last_checksum.write() = cksm;\n        self.max_frame.store(0, Ordering::Release);\n        self.min_frame.store(0, Ordering::Release);\n        self.checkpoint_seq.fetch_add(1, Ordering::Release);\n        Ok(())\n    }\n\n    /// Truncate WAL file to zero and sync it. Called by pager AFTER DB file is synced.\n    fn truncate_log(\n        &self,\n        result: &mut CheckpointResult,\n        sync_type: FileSyncType,\n    ) -> Result<IOResult<()>> {\n        let file = self.with_shared(|shared| {\n            turso_assert!(\n                shared.enabled.load(Ordering::Relaxed),\n                \"WAL must be enabled\"\n            );\n            shared.initialized.store(false, Ordering::Release);\n            shared.file.as_ref().unwrap().clone()\n        });\n\n        if !result.wal_truncate_sent {\n            let c = Completion::new_trunc({\n                move |res| {\n                    if let Err(err) = res {\n                        tracing::info!(\"WAL truncate failed: {err}\")\n                    } else {\n                        tracing::trace!(\"WAL file truncated to 0 B\");\n                    }\n                }\n            });\n            let c = file.truncate(0, c)?;\n            result.wal_truncate_sent = true;\n            // after truncation - there will be nothing in the WAL\n            result.wal_max_frame = 0;\n            result.wal_total_backfilled = 0;\n            io_yield_one!(c);\n        } else if !result.wal_sync_sent {\n            let c = file.sync(\n                Completion::new_sync(move |res| {\n                    if let Err(err) = res {\n                        tracing::info!(\"WAL sync failed: {err}\")\n                    } else {\n                        tracing::trace!(\"WAL file synced after truncation\");\n                    }\n                }),\n                sync_type,\n            )?;\n            result.wal_sync_sent = true;\n            io_yield_one!(c);\n        }\n        Ok(IOResult::Done(()))\n    }\n\n    // unlock shared read locks taken by RESTART/TRUNCATE checkpoint modes\n    fn unlock_after_restart(shared: &Arc<RwLock<WalFileShared>>, e: Option<&LimboError>) {\n        // release all read locks we just acquired, the caller will take care of the others\n        let shared = shared.write();\n        for idx in 1..shared.read_locks.len() {\n            shared.read_locks[idx].unlock();\n        }\n        if let Some(e) = e {\n            mark_unlikely();\n            tracing::debug!(\n                \"Failed to restart WAL header: {:?}, releasing read locks\",\n                e\n            );\n        }\n    }\n\n    fn acquire_proper_checkpoint_guard(&self, mode: CheckpointMode) -> Result<()> {\n        let needs_new_guard = {\n            let guard = self.checkpoint_guard.read();\n            !matches!(\n                (&*guard, mode),\n                (\n                    Some(CheckpointLocks::Read0 { .. }),\n                    CheckpointMode::Passive { .. },\n                ) | (\n                    Some(CheckpointLocks::Writer { .. }),\n                    CheckpointMode::Restart | CheckpointMode::Truncate { .. },\n                ),\n            )\n        };\n        if needs_new_guard {\n            // Drop any existing guard\n            if self.checkpoint_guard.read().is_some() {\n                let _ = self.checkpoint_guard.write().take();\n            }\n            let guard = CheckpointLocks::new(self.shared.clone(), mode)?;\n            *self.checkpoint_guard.write() = Some(guard);\n        }\n        Ok(())\n    }\n\n    fn issue_wal_read_into_buffer(&self, page_id: usize, frame_id: u64) -> Result<InflightRead> {\n        let offset = self.frame_offset(frame_id);\n        let buf_slot = Arc::new(SpinLock::new(None));\n        tracing::debug!(\n            \"Issuing WAL read: page_id={}, frame_id={}, offset={}\",\n            page_id,\n            frame_id,\n            offset\n        );\n\n        let complete = {\n            let buf_slot = buf_slot.clone();\n            Box::new(move |res: Result<(Arc<Buffer>, i32), CompletionError>| {\n                let Ok((buf, read)) = res else {\n                    return None;\n                };\n                let buf_len = buf.len();\n                turso_assert!(\n                    read == buf_len as i32,\n                    \"read bytes does not match expected buffer length\",\n                    { \"read\": read, \"expected\": buf_len, \"frame_id\": frame_id }\n                );\n                *buf_slot.lock() = Some(buf);\n                None\n            })\n        };\n        // schedule read of the page payload\n        let file = self.with_shared(|shared| {\n            turso_assert!(\n                shared.enabled.load(Ordering::Relaxed),\n                \"WAL must be enabled\"\n            );\n            shared.file.as_ref().unwrap().clone()\n        });\n        let c = begin_read_wal_frame(\n            file.as_ref(),\n            offset + WAL_FRAME_HEADER_SIZE as u64,\n            self.buffer_pool.clone(),\n            complete,\n            page_id,\n            &self.io_ctx.read(),\n        )?;\n\n        Ok(InflightRead {\n            completion: c,\n            page_id,\n            buf: buf_slot,\n        })\n    }\n\n    /// Check if database changed since this connection's last read transaction.\n    fn db_changed(&self, shared: &WalFileShared) -> bool {\n        self.db_changed_against(Self::load_shared_snapshot(shared), self.connection_state())\n    }\n\n    /// MVCC helper: check if WAL state changed and refresh local snapshot without starting a read tx.\n    /// FIXME: this isn't TOCTOU safe because we're not taking WAL read locks.\n    ///\n    /// This is only used to invalidate page cache, so false positives are sort of acceptable since\n    /// MVCC reads currently don't read from WAL frames ever.\n    /// FIXME: MVCC should start using pager read transactions anyway so that we can get rid of\n    /// the stop-the-world MVCC checkpoint that blocks all reads.\n    pub fn mvcc_refresh_if_db_changed(&self) -> bool {\n        self.with_shared(|shared| {\n            let snapshot = Self::load_shared_snapshot(shared);\n            let local_state = self.connection_state();\n            let changed = self.db_changed_against(snapshot, local_state);\n            if changed {\n                self.install_connection_state(local_state.with_snapshot(snapshot));\n            }\n            changed\n        })\n    }\n}\n\nimpl WalFileShared {\n    pub fn last_checksum_and_max_frame(&self) -> ((u32, u32), u64) {\n        (self.last_checksum, self.max_frame.load(Ordering::Acquire))\n    }\n    pub fn open_shared_if_exists(\n        io: &Arc<dyn IO>,\n        path: &str,\n        flags: crate::OpenFlags,\n    ) -> Result<Arc<RwLock<WalFileShared>>> {\n        let file = match io.open_file(path, flags, false) {\n            Ok(file) => file,\n            Err(LimboError::CompletionError(CompletionError::IOError(\n                std::io::ErrorKind::NotFound,\n                _,\n            ))) if flags.contains(crate::OpenFlags::ReadOnly) => {\n                // In readonly mode, if the WAL file doesn't exist, we just return a noop WAL\n                // since there's nothing to read from.\n                return Ok(WalFileShared::new_noop());\n            }\n            Err(e) => return Err(e),\n        };\n        let wal_file_shared = sqlite3_ondisk::build_shared_wal(&file, io)?;\n        turso_assert!(\n            wal_file_shared\n                .try_read()\n                .is_some_and(|wfs| wfs.loaded.load(Ordering::Acquire)),\n            \"Unable to read WAL shared state\"\n        );\n        Ok(wal_file_shared)\n    }\n\n    pub fn is_initialized(&self) -> Result<bool> {\n        Ok(self.initialized.load(Ordering::Acquire))\n    }\n\n    pub fn new_noop() -> Arc<RwLock<WalFileShared>> {\n        let wal_header = WalHeader::new();\n        let read_locks = array::from_fn(|_| TursoRwLock::new());\n        for (i, lock) in read_locks.iter().enumerate() {\n            lock.write();\n            lock.set_value_exclusive(if i < 2 { 0 } else { READMARK_NOT_USED });\n            lock.unlock();\n        }\n        let shared = WalFileShared {\n            enabled: AtomicBool::new(false),\n            wal_header: Arc::new(SpinLock::new(wal_header)),\n            min_frame: AtomicU64::new(0),\n            max_frame: AtomicU64::new(0),\n            nbackfills: AtomicU64::new(0),\n            transaction_count: AtomicU64::new(0),\n            frame_cache: Arc::new(SpinLock::new(FxHashMap::default())),\n            last_checksum: (0, 0),\n            file: None,\n            read_locks,\n            write_lock: TursoRwLock::new(),\n            checkpoint_lock: TursoRwLock::new(),\n            loaded: AtomicBool::new(true),\n            initialized: AtomicBool::new(false),\n            epoch: AtomicU32::new(0),\n        };\n        Arc::new(RwLock::new(shared))\n    }\n\n    #[cfg(test)]\n    pub(super) fn new_shared(file: Arc<dyn File>) -> Result<Arc<RwLock<WalFileShared>>> {\n        let wal_header = WalHeader::new();\n        let read_locks = array::from_fn(|_| TursoRwLock::new());\n        // slot zero is always zero as it signifies that reads can be done from the db file\n        // directly, and slot 1 is the default read mark containing the max frame. in this case\n        // our max frame is zero so both slots 0 and 1 begin at 0\n        for (i, lock) in read_locks.iter().enumerate() {\n            lock.write();\n            lock.set_value_exclusive(if i < 2 { 0 } else { READMARK_NOT_USED });\n            lock.unlock();\n        }\n        let shared = WalFileShared {\n            enabled: AtomicBool::new(true),\n            wal_header: Arc::new(SpinLock::new(wal_header)),\n            min_frame: AtomicU64::new(0),\n            max_frame: AtomicU64::new(0),\n            nbackfills: AtomicU64::new(0),\n            transaction_count: AtomicU64::new(0),\n            frame_cache: Arc::new(SpinLock::new(FxHashMap::default())),\n            last_checksum: (0, 0),\n            file: Some(file),\n            read_locks,\n            write_lock: TursoRwLock::new(),\n            checkpoint_lock: TursoRwLock::new(),\n            loaded: AtomicBool::new(true),\n            initialized: AtomicBool::new(false),\n            epoch: AtomicU32::new(0),\n        };\n        Ok(Arc::new(RwLock::new(shared)))\n    }\n\n    pub fn page_size(&self) -> u32 {\n        self.wal_header.lock().page_size\n    }\n\n    /// Called after a successful RESTART/TRUNCATE mode checkpoint\n    /// when all frames are back‑filled.\n    ///\n    /// sqlite3/src/wal.c\n    /// The following is guaranteed when this function is called:\n    ///\n    ///   a) the WRITER lock is held,\n    ///   b) the entire log file has been checkpointed, and\n    ///   c) any existing readers are reading exclusively from the database\n    ///      file - there are no readers that may attempt to read a frame from\n    ///      the log file.\n    ///\n    /// This function updates the shared-memory structures so that the next\n    /// client to write to the database (which may be this one) does so by\n    /// writing frames into the start of the log file.\n    fn restart_wal_header(&mut self, io: &Arc<dyn IO>) {\n        {\n            let mut hdr = self.wal_header.lock();\n            hdr.checkpoint_seq = hdr.checkpoint_seq.wrapping_add(1);\n            // keep hdr.magic, hdr.file_format, hdr.page_size as-is\n            hdr.salt_1 = hdr.salt_1.wrapping_add(1);\n            hdr.salt_2 = io.generate_random_number() as u32;\n\n            self.max_frame.store(0, Ordering::Release);\n            self.nbackfills.store(0, Ordering::Release);\n            self.last_checksum = (hdr.checksum_1, hdr.checksum_2);\n            // `prepare_wal_start` (used in the `commit_dirty_pages_inner`) do the work only if WAL is not initialized yet (so, self.initialized is false)\n            // we change WAL state here, so on next write attempt `prepare_wal_start` will update WAL header\n            self.initialized.store(false, Ordering::Release);\n        }\n\n        self.frame_cache.lock().clear();\n        // read-marks\n        self.read_locks[0].set_value_exclusive(0);\n        self.read_locks[1].set_value_exclusive(0);\n        for lock in &self.read_locks[2..] {\n            lock.set_value_exclusive(READMARK_NOT_USED);\n        }\n    }\n}\n\n#[cfg(test)]\npub mod test {\n    use super::{ReadGuardKind, WalConnectionState, WalFile, WalSnapshot};\n    use crate::sync::{atomic::Ordering, Arc};\n    use crate::sync::{Mutex, RwLock};\n    use crate::{\n        storage::{\n            buffer_pool::BufferPool,\n            sqlite3_ondisk::{self, WAL_HEADER_SIZE},\n            wal::READMARK_NOT_USED,\n        },\n        types::IOResult,\n        util::IOExt,\n        CheckpointMode, CheckpointResult, Completion, Connection, Database, LimboError, PlatformIO,\n        WalFileShared, IO,\n    };\n    #[cfg(unix)]\n    use std::os::unix::fs::MetadataExt;\n    #[allow(clippy::arc_with_non_send_sync)]\n    pub(crate) fn get_database() -> (Arc<Database>, std::path::PathBuf) {\n        let mut path = tempfile::tempdir().unwrap().keep();\n        let dbpath = path.clone();\n        path.push(\"test.db\");\n        {\n            let connection = rusqlite::Connection::open(&path).unwrap();\n            connection\n                .pragma_update(None, \"journal_mode\", \"wal\")\n                .unwrap();\n        }\n        let io: Arc<dyn IO> = Arc::new(PlatformIO::new().unwrap());\n        let db = Database::open_file(io.clone(), path.to_str().unwrap()).unwrap();\n        // db + tmp directory\n        (db, dbpath)\n    }\n    #[test]\n    fn test_truncate_file() {\n        let (db, _path) = get_database();\n        let conn = db.connect().unwrap();\n        conn.execute(\"create table test (id integer primary key, value text)\")\n            .unwrap();\n        let _ = conn.execute(\"insert into test (value) values ('test1'), ('test2'), ('test3')\");\n        let wal = db.shared_wal.write();\n        let wal_file = wal.file.as_ref().unwrap().clone();\n        let done = Arc::new(Mutex::new(false));\n        let _done = done.clone();\n        let _ = wal_file.truncate(\n            WAL_HEADER_SIZE as u64,\n            Completion::new_trunc(move |_| {\n                *_done.lock() = true;\n            }),\n        );\n        assert!(wal_file.size().unwrap() == WAL_HEADER_SIZE as u64);\n        assert!(*done.lock());\n    }\n\n    #[test]\n    fn test_wal_truncate_checkpoint() {\n        let (db, path) = get_database();\n        let mut walpath = path.clone().into_os_string().into_string().unwrap();\n        walpath.push_str(\"/test.db-wal\");\n        let walpath = std::path::PathBuf::from(walpath);\n\n        let conn = db.connect().unwrap();\n        conn.execute(\"create table test (id integer primary key, value text)\")\n            .unwrap();\n        for _i in 0..25 {\n            let _ = conn.execute(\"insert into test (value) values (randomblob(1024)), (randomblob(1024)), (randomblob(1024))\");\n        }\n        let pager = conn.pager.load();\n        let _ = pager.cacheflush();\n\n        let stat = std::fs::metadata(&walpath).unwrap();\n        let meta_before = std::fs::metadata(&walpath).unwrap();\n        let bytes_before = meta_before.len();\n        run_checkpoint_until_done(\n            &pager,\n            CheckpointMode::Truncate {\n                upper_bound_inclusive: None,\n            },\n        );\n\n        assert_eq!(pager.wal_state().unwrap().max_frame, 0);\n\n        tracing::info!(\"wal filepath: {walpath:?}, size: {}\", stat.len());\n        let meta_after = std::fs::metadata(&walpath).unwrap();\n        let bytes_after = meta_after.len();\n        assert_ne!(\n            bytes_before, bytes_after,\n            \"WAL file should not have been empty before checkpoint\"\n        );\n        assert_eq!(\n            bytes_after, 0,\n            \"WAL file should be truncated to 0 bytes, but is {bytes_after} bytes\",\n        );\n        std::fs::remove_dir_all(path).unwrap();\n    }\n\n    #[test]\n    fn test_shutdown_checkpoint_truncates_after_restart() {\n        let (db, path) = get_database();\n        let mut walpath = path.clone().into_os_string().into_string().unwrap();\n        walpath.push_str(\"/test.db-wal\");\n        let walpath = std::path::PathBuf::from(walpath);\n\n        let conn = db.connect().unwrap();\n        conn.execute(\"create table test (id integer primary key, value text)\")\n            .unwrap();\n        conn.execute(\"insert into test (value) values ('v1'), ('v2')\")\n            .unwrap();\n\n        let pager = conn.pager.load();\n        run_checkpoint_until_done(&pager, CheckpointMode::Restart);\n\n        let bytes_before = std::fs::metadata(&walpath).unwrap().len();\n        assert!(\n            bytes_before > 0,\n            \"WAL should still have data after RESTART checkpoint\"\n        );\n\n        conn.close().unwrap();\n\n        let bytes_after = std::fs::metadata(&walpath).unwrap().len();\n        assert_eq!(\n            bytes_after, 0,\n            \"Shutdown checkpoint should truncate WAL after RESTART, but WAL is {bytes_after} bytes\",\n        );\n        std::fs::remove_dir_all(path).unwrap();\n    }\n\n    fn bulk_inserts(conn: &Arc<Connection>, n_txns: usize, rows_per_txn: usize) {\n        for _ in 0..n_txns {\n            conn.execute(\"begin transaction\").unwrap();\n            for i in 0..rows_per_txn {\n                conn.execute(format!(\"insert into test(value) values ('v{i}')\"))\n                    .unwrap();\n            }\n            conn.execute(\"commit\").unwrap();\n        }\n    }\n\n    fn count_test_table(conn: &Arc<Connection>) -> i64 {\n        let mut stmt = conn.prepare(\"select count(*) from test\").unwrap();\n        let mut count: i64 = 0;\n        stmt.run_with_row_callback(|row| {\n            count = row.get(0).unwrap();\n            Ok(())\n        })\n        .unwrap();\n        count\n    }\n\n    fn run_checkpoint_until_done(pager: &crate::Pager, mode: CheckpointMode) -> CheckpointResult {\n        // Use pager.checkpoint() instead of wal.checkpoint() directly because\n        // WAL truncation (for TRUNCATE mode) now happens in pager's TruncateWalFile phase.\n        pager\n            .io\n            .block(|| pager.checkpoint(mode, crate::SyncMode::Full, true))\n            .unwrap()\n    }\n\n    fn make_test_wal() -> (Arc<RwLock<WalFileShared>>, WalFile) {\n        let io: Arc<dyn IO> = Arc::new(PlatformIO::new().unwrap());\n        let buffer_pool = BufferPool::begin_init(&io, BufferPool::TEST_ARENA_SIZE);\n        let shared = WalFileShared::new_noop();\n        let wal = WalFile::new(io, shared.clone(), ((0, 0), 0), buffer_pool);\n        (shared, wal)\n    }\n\n    fn set_shared_snapshot(shared: &Arc<RwLock<WalFileShared>>, snapshot: WalSnapshot) {\n        let mut guard = shared.write();\n        guard.max_frame.store(snapshot.max_frame, Ordering::Release);\n        guard\n            .nbackfills\n            .store(snapshot.nbackfills, Ordering::Release);\n        guard.last_checksum = snapshot.last_checksum;\n        guard.wal_header.lock().checkpoint_seq = snapshot.checkpoint_seq;\n        guard\n            .transaction_count\n            .store(snapshot.transaction_count, Ordering::Release);\n    }\n\n    #[cfg(test)]\n    fn read_slots_with_readers(shared: &WalFileShared) -> Vec<usize> {\n        shared\n            .read_locks\n            .iter()\n            .enumerate()\n            .filter_map(|(slot, lock)| {\n                let state = lock.0.load(Ordering::Acquire);\n                let has_readers = (state & super::TursoRwLock::READER_COUNT_MASK) != 0;\n                has_readers.then_some(slot)\n            })\n            .collect()\n    }\n\n    fn wal_header_snapshot(shared: &Arc<RwLock<WalFileShared>>) -> (u32, u32, u32, u32) {\n        // (checkpoint_seq, salt1, salt2, page_size)\n        let shared_guard = shared.read();\n        let hdr = shared_guard.wal_header.lock();\n        (hdr.checkpoint_seq, hdr.salt_1, hdr.salt_2, hdr.page_size)\n    }\n\n    #[test]\n    fn test_wal_connection_state_round_trip() {\n        let (_shared, wal) = make_test_wal();\n        let state = WalConnectionState::new(\n            WalSnapshot {\n                max_frame: 11,\n                nbackfills: 7,\n                last_checksum: (31, 47),\n                checkpoint_seq: 5,\n                transaction_count: 13,\n            },\n            ReadGuardKind::ReadMark(3),\n        );\n\n        wal.install_connection_state(state);\n\n        assert_eq!(wal.connection_state(), state);\n        assert_eq!(wal.connection_state().snapshot.min_frame(), 8);\n    }\n\n    #[test]\n    fn test_mvcc_refresh_updates_snapshot_without_changing_read_guard() {\n        let (shared, wal) = make_test_wal();\n        let initial = WalSnapshot {\n            max_frame: 4,\n            nbackfills: 2,\n            last_checksum: (9, 10),\n            checkpoint_seq: 1,\n            transaction_count: 3,\n        };\n        set_shared_snapshot(&shared, initial);\n        wal.install_connection_state(WalConnectionState::new(initial, ReadGuardKind::ReadMark(2)));\n\n        assert!(!wal.mvcc_refresh_if_db_changed());\n\n        let updated = WalSnapshot {\n            max_frame: 8,\n            nbackfills: 5,\n            last_checksum: (21, 34),\n            checkpoint_seq: 7,\n            transaction_count: 4,\n        };\n        set_shared_snapshot(&shared, updated);\n\n        assert!(wal.mvcc_refresh_if_db_changed());\n        assert_eq!(\n            wal.connection_state(),\n            WalConnectionState::new(updated, ReadGuardKind::ReadMark(2))\n        );\n    }\n\n    #[test]\n    fn restart_checkpoint_reset_wal_state_handling() {\n        let (db, path) = get_database();\n\n        let walpath = {\n            let mut p = path.clone().into_os_string().into_string().unwrap();\n            p.push_str(\"/test.db-wal\");\n            std::path::PathBuf::from(p)\n        };\n\n        let conn = db.connect().unwrap();\n        conn.execute(\"create table test(id integer primary key, value text)\")\n            .unwrap();\n        bulk_inserts(&conn, 20, 3);\n        let IOResult::Done(completions) = conn.pager.load().cacheflush().unwrap() else {\n            panic!()\n        };\n        for c in completions {\n            db.io.wait_for_completion(c).unwrap();\n        }\n\n        // Snapshot header & counters before the RESTART checkpoint.\n        let wal_shared = db.shared_wal.clone();\n        let (seq_before, salt1_before, salt2_before, _ps_before) = wal_header_snapshot(&wal_shared);\n        let (mx_before, backfill_before) = {\n            let s = wal_shared.read();\n            (\n                s.max_frame.load(Ordering::SeqCst),\n                s.nbackfills.load(Ordering::SeqCst),\n            )\n        };\n        assert!(mx_before > 0);\n        assert_eq!(backfill_before, 0);\n\n        let meta_before = std::fs::metadata(&walpath).unwrap();\n        #[cfg(unix)]\n        let size_before = meta_before.blocks();\n        #[cfg(not(unix))]\n        let size_before = meta_before.len();\n        // Run a RESTART checkpoint, should backfill everything and reset WAL counters,\n        // but NOT truncate the file.\n        {\n            let pager = conn.pager.load();\n            let res = run_checkpoint_until_done(&pager, CheckpointMode::Restart);\n            assert_eq!(res.wal_max_frame, mx_before);\n            assert_eq!(res.wal_total_backfilled, mx_before);\n            assert_eq!(res.wal_checkpoint_backfilled, mx_before);\n        }\n\n        // Validate post‑RESTART header & counters.\n        let (seq_after, salt1_after, salt2_after, _ps_after) = wal_header_snapshot(&wal_shared);\n        assert_eq!(\n            seq_after,\n            seq_before.wrapping_add(1),\n            \"checkpoint_seq must increment on RESTART\"\n        );\n        assert_eq!(\n            salt1_after,\n            salt1_before.wrapping_add(1),\n            \"salt_1 is incremented\"\n        );\n        assert_ne!(salt2_after, salt2_before, \"salt_2 is randomized\");\n\n        let (mx_after, backfill_after) = {\n            let s = wal_shared.read();\n            (\n                s.max_frame.load(Ordering::SeqCst),\n                s.nbackfills.load(Ordering::SeqCst),\n            )\n        };\n        assert_eq!(mx_after, 0, \"mxFrame reset to 0 after RESTART\");\n        assert_eq!(backfill_after, 0, \"nBackfill reset to 0 after RESTART\");\n\n        // File size should be unchanged for RESTART (no truncate).\n        let meta_after = std::fs::metadata(&walpath).unwrap();\n        #[cfg(unix)]\n        let size_after = meta_after.blocks();\n        #[cfg(not(unix))]\n        let size_after = meta_after.len();\n        assert_eq!(\n            size_before, size_after,\n            \"RESTART must not change WAL file size\"\n        );\n\n        // Next write should start a new sequence at frame 1.\n        conn.execute(\"insert into test(value) values ('post_restart')\")\n            .unwrap();\n        conn.pager\n            .load()\n            .wal\n            .as_ref()\n            .unwrap()\n            .finish_append_frames_commit()\n            .unwrap();\n        let new_max = wal_shared.read().max_frame.load(Ordering::SeqCst);\n        assert_eq!(new_max, 1, \"first append after RESTART starts at frame 1\");\n\n        std::fs::remove_dir_all(path).unwrap();\n    }\n\n    #[test]\n    fn test_wal_passive_partial_then_complete() {\n        let (db, _tmp) = get_database();\n        let conn1 = db.connect().unwrap();\n        let conn2 = db.connect().unwrap();\n\n        conn1\n            .execute(\"create table test(id integer primary key, value text)\")\n            .unwrap();\n        bulk_inserts(&conn1, 15, 2);\n        let IOResult::Done(completions) = conn1.pager.load().cacheflush().unwrap() else {\n            panic!()\n        };\n        for c in completions {\n            db.io.wait_for_completion(c).unwrap();\n        }\n\n        // Force a read transaction that will freeze a lower read mark\n        let readmark = {\n            let pager = conn2.pager.load();\n            let wal2 = pager.wal.as_ref().unwrap();\n            wal2.begin_read_tx().unwrap();\n            wal2.get_max_frame()\n        };\n\n        // generate more frames that the reader will not see.\n        bulk_inserts(&conn1, 15, 2);\n        let IOResult::Done(completions) = conn1.pager.load().cacheflush().unwrap() else {\n            panic!()\n        };\n        for c in completions {\n            db.io.wait_for_completion(c).unwrap();\n        }\n\n        // Run passive checkpoint, expect partial\n        let (res1, max_before) = {\n            let pager = conn1.pager.load();\n            let res = run_checkpoint_until_done(\n                &pager,\n                CheckpointMode::Passive {\n                    upper_bound_inclusive: None,\n                },\n            );\n            let maxf = db.shared_wal.read().max_frame.load(Ordering::SeqCst);\n            (res, maxf)\n        };\n        assert_eq!(res1.wal_max_frame, max_before);\n        assert!(\n            res1.wal_total_backfilled < res1.wal_max_frame,\n            \"Partial backfill expected, {} : {}\",\n            res1.wal_total_backfilled,\n            res1.wal_max_frame\n        );\n        assert_eq!(\n            res1.wal_total_backfilled, readmark,\n            \"Checkpointed frames should match read mark\"\n        );\n        // Release reader\n        {\n            let pager = conn2.pager.load();\n            let wal2 = pager.wal.as_ref().unwrap();\n            wal2.end_read_tx();\n        }\n\n        // Second passive checkpoint should finish\n        let pager = conn1.pager.load();\n        let res2 = run_checkpoint_until_done(\n            &pager,\n            CheckpointMode::Passive {\n                upper_bound_inclusive: None,\n            },\n        );\n        assert_eq!(\n            res2.wal_total_backfilled, res2.wal_max_frame,\n            \"Second checkpoint completes remaining frames\"\n        );\n    }\n\n    #[test]\n    fn test_wal_restart_blocks_readers() {\n        let (db, _) = get_database();\n        let conn1 = db.connect().unwrap();\n        let conn2 = db.connect().unwrap();\n\n        // Start a read transaction\n        conn2\n            .pager\n            .load()\n            .wal\n            .as_ref()\n            .unwrap()\n            .begin_read_tx()\n            .unwrap();\n\n        // checkpoint should succeed here because the wal is fully checkpointed (empty)\n        // so the reader is using readmark0 to read directly from the db file.\n        let p = conn1.pager.load();\n        let w = p.wal.as_ref().unwrap();\n        loop {\n            match w.checkpoint(&p, CheckpointMode::Restart) {\n                Ok(IOResult::IO(io)) => {\n                    io.wait(db.io.as_ref()).unwrap();\n                }\n                e => {\n                    assert!(\n                        matches!(e, Err(LimboError::Busy)),\n                        \"reader is holding readmark0 we should return Busy\"\n                    );\n                    break;\n                }\n            }\n        }\n        conn2.pager.load().end_read_tx();\n\n        conn1\n            .execute(\"create table test(id integer primary key, value text)\")\n            .unwrap();\n        for i in 0..10 {\n            conn1\n                .execute(format!(\"insert into test(value) values ('value{i}')\"))\n                .unwrap();\n        }\n        // now that we have some frames to checkpoint, try again\n        conn2.pager.load().begin_read_tx().unwrap();\n        let p = conn1.pager.load();\n        let w = p.wal.as_ref().unwrap();\n        loop {\n            match w.checkpoint(&p, CheckpointMode::Restart) {\n                Ok(IOResult::IO(io)) => {\n                    io.wait(db.io.as_ref()).unwrap();\n                }\n                Ok(IOResult::Done(_)) => {\n                    panic!(\"Checkpoint should not have succeeded\");\n                }\n                Err(e) => {\n                    assert!(\n                        matches!(e, LimboError::Busy),\n                        \"should return busy if we have readers\"\n                    );\n                    break;\n                }\n            }\n        }\n    }\n\n    #[test]\n    fn test_wal_read_marks_after_restart() {\n        let (db, _path) = get_database();\n        let wal_shared = db.shared_wal.clone();\n\n        let conn = db.connect().unwrap();\n        conn.execute(\"create table test(id integer primary key, value text)\")\n            .unwrap();\n        bulk_inserts(&conn, 10, 5);\n        // Checkpoint with restart\n        {\n            let pager = conn.pager.load();\n            let result = run_checkpoint_until_done(&pager, CheckpointMode::Restart);\n            assert!(result.everything_backfilled());\n        }\n\n        // Verify read marks after restart\n        let read_marks_after: Vec<_> = {\n            let s = wal_shared.read();\n            (0..5).map(|i| s.read_locks[i].get_value()).collect()\n        };\n\n        assert_eq!(read_marks_after[0], 0, \"Slot 0 should remain 0\");\n        assert_eq!(\n            read_marks_after[1], 0,\n            \"Slot 1 (default reader) should be reset to 0\"\n        );\n        for (i, item) in read_marks_after.iter().take(5).skip(2).enumerate() {\n            assert_eq!(\n                *item, READMARK_NOT_USED,\n                \"Slot {i} should be READMARK_NOT_USED after restart\",\n            );\n        }\n    }\n\n    #[test]\n    fn test_wal_concurrent_readers_during_checkpoint() {\n        let (db, _path) = get_database();\n        let conn_writer = db.connect().unwrap();\n\n        conn_writer\n            .execute(\"create table test(id integer primary key, value text)\")\n            .unwrap();\n        bulk_inserts(&conn_writer, 5, 10);\n\n        // Start multiple readers at different points\n        let conn_r1 = db.connect().unwrap();\n        let conn_r2 = db.connect().unwrap();\n\n        // R1 starts reading\n        let r1_max_frame = {\n            let pager = conn_r1.pager.load();\n            let wal = pager.wal.as_ref().unwrap();\n            wal.begin_read_tx().unwrap();\n            wal.get_max_frame()\n        };\n        bulk_inserts(&conn_writer, 5, 10);\n\n        // R2 starts reading, sees more frames than R1\n        let r2_max_frame = {\n            let pager = conn_r2.pager.load();\n            let wal = pager.wal.as_ref().unwrap();\n            wal.begin_read_tx().unwrap();\n            wal.get_max_frame()\n        };\n\n        // try passive checkpoint, should only checkpoint up to R1's position\n        let checkpoint_result = {\n            let pager = conn_writer.pager.load();\n            run_checkpoint_until_done(\n                &pager,\n                CheckpointMode::Passive {\n                    upper_bound_inclusive: None,\n                },\n            )\n        };\n\n        assert!(\n            checkpoint_result.wal_total_backfilled < checkpoint_result.wal_max_frame,\n            \"Should not checkpoint all frames when readers are active\"\n        );\n        assert_eq!(\n            checkpoint_result.wal_total_backfilled, r1_max_frame,\n            \"Should have checkpointed up to R1's max frame\"\n        );\n\n        // Verify R2 still sees its frames\n        assert_eq!(\n            conn_r2.pager.load().wal.as_ref().unwrap().get_max_frame(),\n            r2_max_frame,\n            \"Reader should maintain its snapshot\"\n        );\n    }\n\n    #[test]\n    fn test_wal_checkpoint_updates_read_marks() {\n        let (db, _path) = get_database();\n        let wal_shared = db.shared_wal.clone();\n\n        let conn = db.connect().unwrap();\n        conn.execute(\"create table test(id integer primary key, value text)\")\n            .unwrap();\n        bulk_inserts(&conn, 10, 5);\n\n        // get max frame before checkpoint\n        let max_frame_before = wal_shared.read().max_frame.load(Ordering::SeqCst);\n\n        {\n            let pager = conn.pager.load();\n            let _result = run_checkpoint_until_done(\n                &pager,\n                CheckpointMode::Passive {\n                    upper_bound_inclusive: None,\n                },\n            );\n        }\n\n        // check that read mark 1 (default reader) was updated to max_frame\n        let read_mark_1 = wal_shared.read().read_locks[1].get_value();\n\n        assert_eq!(\n            read_mark_1 as u64, max_frame_before,\n            \"Read mark 1 should be updated to max frame during checkpoint\"\n        );\n    }\n\n    #[test]\n    fn test_wal_writer_blocks_restart_checkpoint() {\n        let (db, _path) = get_database();\n        let conn1 = db.connect().unwrap();\n        let conn2 = db.connect().unwrap();\n\n        conn1\n            .execute(\"create table test(id integer primary key, value text)\")\n            .unwrap();\n        bulk_inserts(&conn1, 5, 5);\n\n        // start a write transaction\n        {\n            let pager = conn2.pager.load();\n            let wal = pager.wal.as_ref().unwrap();\n            let _ = wal.begin_read_tx().unwrap();\n            wal.begin_write_tx().unwrap();\n        }\n\n        // should fail because writer lock is held\n        let result = {\n            let pager = conn1.pager.load();\n            let wal = pager.wal.as_ref().unwrap();\n            wal.checkpoint(&pager, CheckpointMode::Restart)\n        };\n\n        assert!(\n            matches!(result, Err(LimboError::Busy)),\n            \"Restart checkpoint should fail when write lock is held\"\n        );\n\n        conn2.pager.load().wal.as_ref().unwrap().end_read_tx();\n        // release write lock\n        conn2.pager.load().wal.as_ref().unwrap().end_write_tx();\n\n        // now restart should succeed\n        let result = {\n            let pager = conn1.pager.load();\n            run_checkpoint_until_done(&pager, CheckpointMode::Restart)\n        };\n\n        assert!(result.everything_backfilled());\n    }\n\n    #[test]\n    #[should_panic(expected = \"must have a read transaction to begin a write transaction\")]\n    fn test_wal_read_transaction_required_before_write() {\n        let (db, _path) = get_database();\n        let conn = db.connect().unwrap();\n\n        conn.execute(\"create table test(id integer primary key, value text)\")\n            .unwrap();\n\n        // Attempt to start a write transaction without a read transaction\n        let pager = conn.pager.load();\n        let wal = pager.wal.as_ref().unwrap();\n        let _ = wal.begin_write_tx();\n    }\n\n    fn check_read_lock_slot(conn: &Arc<Connection>, _expected_slot: usize) -> bool {\n        let pager = conn.pager.load();\n        let _wal = pager.wal.as_ref().unwrap();\n        #[cfg(debug_assertions)]\n        {\n            let wal_any = _wal.as_any();\n            if let Some(wal_file) = wal_any.downcast_ref::<crate::WalFile>() {\n                return wal_file.max_frame_read_lock_index.load(Ordering::Acquire)\n                    == _expected_slot;\n            }\n        }\n\n        false\n    }\n\n    #[test]\n    fn test_wal_multiple_readers_at_different_frames() {\n        let (db, _path) = get_database();\n        let conn_writer = db.connect().unwrap();\n\n        conn_writer\n            .execute(\"CREATE TABLE test(id INTEGER PRIMARY KEY, value TEXT)\")\n            .unwrap();\n\n        fn start_reader(conn: &Arc<Connection>) -> (u64, crate::Statement) {\n            conn.execute(\"BEGIN\").unwrap();\n            let mut stmt = conn.prepare(\"SELECT * FROM test\").unwrap();\n            stmt.step().unwrap();\n            let frame = conn.pager.load().wal.as_ref().unwrap().get_max_frame();\n            (frame, stmt)\n        }\n\n        bulk_inserts(&conn_writer, 3, 5);\n\n        let conn1 = &db.connect().unwrap();\n        let (r1_frame, _stmt) = start_reader(conn1); // reader 1\n\n        bulk_inserts(&conn_writer, 3, 5);\n\n        let conn_r2 = db.connect().unwrap();\n        let (r2_frame, _stmt2) = start_reader(&conn_r2); // reader 2\n\n        bulk_inserts(&conn_writer, 3, 5);\n\n        let conn_r3 = db.connect().unwrap();\n        let (r3_frame, _stmt3) = start_reader(&conn_r3); // reader 3\n\n        assert!(r1_frame < r2_frame && r2_frame < r3_frame);\n\n        // passive checkpoint #1\n        let result1 = {\n            let pager = conn_writer.pager.load();\n            run_checkpoint_until_done(\n                &pager,\n                CheckpointMode::Passive {\n                    upper_bound_inclusive: None,\n                },\n            )\n        };\n        assert_eq!(result1.wal_total_backfilled, r1_frame);\n\n        // finish reader‑1\n        conn1.execute(\"COMMIT\").unwrap();\n\n        // passive checkpoint #2\n        let result2 = {\n            let pager = conn_writer.pager.load();\n            run_checkpoint_until_done(\n                &pager,\n                CheckpointMode::Passive {\n                    upper_bound_inclusive: None,\n                },\n            )\n        };\n        assert_eq!(\n            result1.wal_checkpoint_backfilled + result2.wal_checkpoint_backfilled,\n            r2_frame\n        );\n\n        // verify visible rows\n        let r2_cnt = count_test_table(&conn_r2);\n        let r3_cnt = count_test_table(&conn_r3);\n\n        assert_eq!(r2_cnt, 30);\n        assert_eq!(r3_cnt, 45);\n    }\n\n    #[test]\n    fn test_checkpoint_truncate_reset_handling() {\n        let (db, path) = get_database();\n        let conn = db.connect().unwrap();\n\n        let walpath = {\n            let mut p = path.clone().into_os_string().into_string().unwrap();\n            p.push_str(\"/test.db-wal\");\n            std::path::PathBuf::from(p)\n        };\n\n        conn.execute(\"create table test(id integer primary key, value text)\")\n            .unwrap();\n        bulk_inserts(&conn, 10, 10);\n\n        // Get size before checkpoint\n        let size_before = std::fs::metadata(&walpath).unwrap().len();\n        assert!(size_before > 0, \"WAL file should have content\");\n\n        // Do a TRUNCATE checkpoint\n        {\n            let pager = conn.pager.load();\n            run_checkpoint_until_done(\n                &pager,\n                CheckpointMode::Truncate {\n                    upper_bound_inclusive: None,\n                },\n            );\n        }\n\n        // Check file size after truncate\n        let size_after = std::fs::metadata(&walpath).unwrap().len();\n        assert_eq!(size_after, 0, \"WAL file should be truncated to 0 bytes\");\n\n        // Verify we can still write to the database\n        conn.execute(\"INSERT INTO test VALUES (1001, 'after-truncate')\")\n            .unwrap();\n\n        // Check WAL has new content\n        let new_size = std::fs::metadata(&walpath).unwrap().len();\n        assert!(new_size >= 32, \"WAL file too small\");\n        let hdr = read_wal_header(&walpath);\n        let expected_magic = if cfg!(target_endian = \"big\") {\n            sqlite3_ondisk::WAL_MAGIC_BE\n        } else {\n            sqlite3_ondisk::WAL_MAGIC_LE\n        };\n        assert!(\n            hdr.magic == expected_magic,\n            \"bad WAL magic: {:#X}, expected: {:#X}\",\n            hdr.magic,\n            sqlite3_ondisk::WAL_MAGIC_BE\n        );\n        assert_eq!(hdr.file_format, 3007000);\n        assert_eq!(hdr.page_size, 4096, \"invalid page size\");\n        assert_eq!(hdr.checkpoint_seq, 1, \"invalid checkpoint_seq\");\n        std::fs::remove_dir_all(path).unwrap();\n    }\n\n    #[test]\n    fn test_wal_checkpoint_truncate_db_file_contains_data() {\n        let (db, path) = get_database();\n        let conn = db.connect().unwrap();\n\n        let walpath = {\n            let mut p = path.clone().into_os_string().into_string().unwrap();\n            p.push_str(\"/test.db-wal\");\n            std::path::PathBuf::from(p)\n        };\n\n        conn.execute(\"create table test(id integer primary key, value text)\")\n            .unwrap();\n        bulk_inserts(&conn, 10, 100);\n\n        // Get size before checkpoint\n        let size_before = std::fs::metadata(&walpath).unwrap().len();\n        assert!(size_before > 0, \"WAL file should have content\");\n\n        // Do a TRUNCATE checkpoint\n        {\n            let pager = conn.pager.load();\n            run_checkpoint_until_done(\n                &pager,\n                CheckpointMode::Truncate {\n                    upper_bound_inclusive: None,\n                },\n            );\n        }\n\n        // Check file size after truncate\n        let size_after = std::fs::metadata(&walpath).unwrap().len();\n        assert_eq!(size_after, 0, \"WAL file should be truncated to 0 bytes\");\n\n        // Verify we can still write to the database\n        conn.execute(\"INSERT INTO test VALUES (1001, 'after-truncate')\")\n            .unwrap();\n\n        // Check WAL has new content\n        let new_size = std::fs::metadata(&walpath).unwrap().len();\n        assert!(new_size >= 32, \"WAL file too small\");\n        let hdr = read_wal_header(&walpath);\n        let expected_magic = if cfg!(target_endian = \"big\") {\n            sqlite3_ondisk::WAL_MAGIC_BE\n        } else {\n            sqlite3_ondisk::WAL_MAGIC_LE\n        };\n        assert!(\n            hdr.magic == expected_magic,\n            \"bad WAL magic: {:#X}, expected: {:#X}\",\n            hdr.magic,\n            sqlite3_ondisk::WAL_MAGIC_BE\n        );\n        assert_eq!(hdr.file_format, 3007000);\n        assert_eq!(hdr.page_size, 4096, \"invalid page size\");\n        assert_eq!(hdr.checkpoint_seq, 1, \"invalid checkpoint_seq\");\n        {\n            let pager = conn.pager.load();\n            run_checkpoint_until_done(\n                &pager,\n                CheckpointMode::Passive {\n                    upper_bound_inclusive: None,\n                },\n            );\n        }\n        // delete the WAL file so we can read right from db and assert\n        // that everything was backfilled properly\n        std::fs::remove_file(&walpath).unwrap();\n\n        let count = count_test_table(&conn);\n        assert_eq!(\n            count, 1001,\n            \"we should have 1001 rows in the table all together\"\n        );\n        std::fs::remove_dir_all(path).unwrap();\n    }\n\n    fn read_wal_header(path: &std::path::Path) -> sqlite3_ondisk::WalHeader {\n        use std::{fs::File, io::Read};\n        let mut hdr = [0u8; 32];\n        File::open(path).unwrap().read_exact(&mut hdr).unwrap();\n        let be = |i| u32::from_be_bytes(hdr[i..i + 4].try_into().unwrap());\n        sqlite3_ondisk::WalHeader {\n            magic: be(0x00),\n            file_format: be(0x04),\n            page_size: be(0x08),\n            checkpoint_seq: be(0x0C),\n            salt_1: be(0x10),\n            salt_2: be(0x14),\n            checksum_1: be(0x18),\n            checksum_2: be(0x1C),\n        }\n    }\n\n    #[test]\n    fn test_wal_stale_snapshot_in_write_transaction() {\n        let (db, _path) = get_database();\n        let conn1 = db.connect().unwrap();\n        let conn2 = db.connect().unwrap();\n\n        conn1\n            .execute(\"create table test(id integer primary key, value text)\")\n            .unwrap();\n        // Start a read transaction on conn2\n        {\n            let pager = conn2.pager.load();\n            let wal = pager.wal.as_ref().unwrap();\n            wal.begin_read_tx().unwrap();\n        }\n        // Make changes using conn1\n        bulk_inserts(&conn1, 5, 5);\n        // Try to start a write transaction on conn2 with a stale snapshot\n        let result = {\n            let pager = conn2.pager.load();\n            let wal = pager.wal.as_ref().unwrap();\n            wal.begin_write_tx()\n        };\n        // Should get BusySnapShot due to stale snapshot\n        assert!(matches!(result, Err(LimboError::BusySnapshot)));\n\n        // End read transaction and start a fresh one\n        {\n            let pager = conn2.pager.load();\n            let wal = pager.wal.as_ref().unwrap();\n            wal.end_read_tx();\n            wal.begin_read_tx().unwrap();\n        }\n        // Now write transaction should work\n        let result = {\n            let pager = conn2.pager.load();\n            let wal = pager.wal.as_ref().unwrap();\n            wal.begin_write_tx()\n        };\n        assert!(matches!(result, Ok(())));\n    }\n\n    #[test]\n    fn test_wal_readlock0_optimization_behavior() {\n        let (db, _path) = get_database();\n        let conn1 = db.connect().unwrap();\n        let conn2 = db.connect().unwrap();\n\n        conn1\n            .execute(\"create table test(id integer primary key, value text)\")\n            .unwrap();\n        bulk_inserts(&conn1, 5, 5);\n        // Do a full checkpoint to move all data to DB file\n        {\n            let pager = conn1.pager.load();\n            run_checkpoint_until_done(\n                &pager,\n                CheckpointMode::Passive {\n                    upper_bound_inclusive: None,\n                },\n            );\n        }\n\n        // Start a read transaction on conn2\n        {\n            let pager = conn2.pager.load();\n            let wal = pager.wal.as_ref().unwrap();\n            wal.begin_read_tx().unwrap();\n        }\n        // should use slot 0, as everything is backfilled\n        assert!(check_read_lock_slot(&conn2, 0));\n        {\n            let pager = conn1.pager.load();\n            let wal = pager.wal.as_ref().unwrap();\n            let frame = wal.find_frame(5, None);\n            // since we hold readlock0, we should ignore the db file and find_frame should return none\n            assert!(frame.is_ok_and(|f| f.is_none()));\n        }\n        // Try checkpoint, should fail because reader has slot 0\n        {\n            let pager = conn1.pager.load();\n            let wal = pager.wal.as_ref().unwrap();\n            let result = wal.checkpoint(&pager, CheckpointMode::Restart);\n\n            assert!(\n                matches!(result, Err(LimboError::Busy)),\n                \"RESTART checkpoint should fail when a reader is using slot 0\"\n            );\n        }\n        // End the read transaction\n        {\n            let pager = conn2.pager.load();\n            let wal = pager.wal.as_ref().unwrap();\n            wal.end_read_tx();\n        }\n        {\n            let pager = conn1.pager.load();\n            let result = run_checkpoint_until_done(&pager, CheckpointMode::Restart);\n            assert!(\n                result.everything_backfilled(),\n                \"RESTART checkpoint should succeed after reader releases slot 0\"\n            );\n        }\n    }\n\n    #[test]\n    fn test_wal_full_backfills_all() {\n        let (db, _tmp) = get_database();\n        let conn = db.connect().unwrap();\n\n        // Write some data to put frames in the WAL\n        conn.execute(\"create table test(id integer primary key, value text)\")\n            .unwrap();\n        bulk_inserts(&conn, 8, 4);\n\n        // Ensure frames are flushed to the WAL\n        let IOResult::Done(completions) = conn.pager.load().cacheflush().unwrap() else {\n            panic!()\n        };\n        for c in completions {\n            db.io.wait_for_completion(c).unwrap();\n        }\n\n        // Snapshot the current mxFrame before running FULL\n        let wal_shared = db.shared_wal.clone();\n        let mx_before = wal_shared.read().max_frame.load(Ordering::SeqCst);\n        assert!(mx_before > 0, \"expected frames in WAL before FULL\");\n\n        // Run FULL checkpoint - must backfill *all* frames up to mx_before\n        let result = {\n            let pager = conn.pager.load();\n            run_checkpoint_until_done(&pager, CheckpointMode::Full)\n        };\n\n        assert_eq!(result.wal_checkpoint_backfilled, mx_before);\n        assert_eq!(result.wal_total_backfilled, mx_before);\n    }\n\n    #[test]\n    fn test_wal_full_waits_for_old_reader_then_succeeds() {\n        let (db, _tmp) = get_database();\n        let writer = db.connect().unwrap();\n        let reader = db.connect().unwrap();\n\n        writer\n            .execute(\"create table test(id integer primary key, value text)\")\n            .unwrap();\n\n        // First commit some data and flush (reader will snapshot here)\n        bulk_inserts(&writer, 2, 3);\n        let IOResult::Done(completions) = writer.pager.load().cacheflush().unwrap() else {\n            panic!()\n        };\n        for c in completions {\n            db.io.wait_for_completion(c).unwrap();\n        }\n\n        // Start a read transaction pinned at the current snapshot\n        {\n            let pager = reader.pager.load();\n            let wal = pager.wal.as_ref().unwrap();\n            wal.begin_read_tx().unwrap();\n        }\n        let r_snapshot = {\n            let pager = reader.pager.load();\n            let wal = pager.wal.as_ref().unwrap();\n            wal.get_max_frame()\n        };\n\n        // Advance WAL beyond the reader's snapshot\n        bulk_inserts(&writer, 3, 4);\n        let IOResult::Done(completions) = writer.pager.load().cacheflush().unwrap() else {\n            panic!()\n        };\n        for c in completions {\n            db.io.wait_for_completion(c).unwrap();\n        }\n        let mx_now = db.shared_wal.read().max_frame.load(Ordering::SeqCst);\n        assert!(mx_now > r_snapshot);\n\n        // FULL must return Busy while a reader is stuck behind\n        {\n            let pager = writer.pager.load();\n            let wal = pager.wal.as_ref().unwrap();\n            loop {\n                match wal.checkpoint(&pager, CheckpointMode::Full) {\n                    Ok(IOResult::IO(io)) => {\n                        // Drive any pending IO (should quickly become Busy or Done)\n                        io.wait(db.io.as_ref()).unwrap();\n                    }\n                    Err(LimboError::Busy) => {\n                        break;\n                    }\n                    other => panic!(\"expected Busy from FULL with old reader, got {other:?}\"),\n                }\n            }\n        }\n\n        // Release the reader, now full mode should succeed and backfill everything\n        {\n            let pager = reader.pager.load();\n            let wal = pager.wal.as_ref().unwrap();\n            wal.end_read_tx();\n        }\n\n        let result = {\n            let pager = writer.pager.load();\n            run_checkpoint_until_done(&pager, CheckpointMode::Full)\n        };\n\n        assert_eq!(result.wal_checkpoint_backfilled, mx_now - r_snapshot);\n        assert!(result.everything_backfilled());\n    }\n\n    #[test]\n    fn test_rollback_releases_read_lock() {\n        let (db, _path) = get_database();\n        let conn = db.connect().unwrap();\n\n        conn.execute(\"CREATE TABLE t(x)\").unwrap();\n        conn.execute(\"BEGIN\").unwrap();\n        conn.execute(\"INSERT INTO t VALUES(1)\").unwrap();\n\n        {\n            let pager = conn.pager.load();\n            let wal = pager.wal.as_ref().unwrap();\n            assert!(\n                wal.holds_read_lock(),\n                \"read lock must be held during write tx\"\n            );\n        }\n\n        conn.execute(\"ROLLBACK\").unwrap();\n\n        {\n            let pager = conn.pager.load();\n            let wal = pager.wal.as_ref().unwrap();\n            assert!(\n                !wal.holds_read_lock(),\n                \"read lock must be released after ROLLBACK\"\n            );\n        }\n    }\n\n    #[test]\n    fn test_rollback_releases_shared_read_lock_slot() {\n        let (db, _path) = get_database();\n        let conn = db.connect().unwrap();\n\n        conn.execute(\"CREATE TABLE t(x)\").unwrap();\n        conn.execute(\"BEGIN\").unwrap();\n        conn.execute(\"INSERT INTO t VALUES(1)\").unwrap();\n\n        let locked_slots_before = {\n            let shared = db.shared_wal.read();\n            read_slots_with_readers(&shared)\n        };\n        assert_eq!(\n            locked_slots_before.len(),\n            1,\n            \"expected exactly one shared read-lock slot while transaction is active\"\n        );\n\n        conn.execute(\"ROLLBACK\").unwrap();\n\n        let locked_slots_after = {\n            let shared = db.shared_wal.read();\n            read_slots_with_readers(&shared)\n        };\n        assert!(\n            locked_slots_after.is_empty(),\n            \"ROLLBACK must release the shared read-lock slot\"\n        );\n    }\n\n    #[test]\n    fn test_rollback_releases_slot_zero_read_lock() {\n        let (db, _path) = get_database();\n        let conn = db.connect().unwrap();\n\n        conn.execute(\"CREATE TABLE test(id integer primary key, value text)\")\n            .unwrap();\n        bulk_inserts(&conn, 3, 3);\n        {\n            let pager = conn.pager.load();\n            let result = run_checkpoint_until_done(&pager, CheckpointMode::Restart);\n            assert!(\n                result.everything_backfilled(),\n                \"restart checkpoint setup must fully backfill WAL\"\n            );\n        }\n\n        conn.execute(\"BEGIN\").unwrap();\n        conn.execute(\"INSERT INTO test(value) VALUES('slot0')\")\n            .unwrap();\n\n        let locked_slots_before = {\n            let shared = db.shared_wal.read();\n            read_slots_with_readers(&shared)\n        };\n        assert_eq!(\n            locked_slots_before,\n            vec![0],\n            \"writer should use slot 0 when WAL is fully checkpointed\"\n        );\n\n        conn.execute(\"ROLLBACK\").unwrap();\n\n        let locked_slots_after = {\n            let shared = db.shared_wal.read();\n            read_slots_with_readers(&shared)\n        };\n        assert!(\n            locked_slots_after.is_empty(),\n            \"ROLLBACK must release slot 0 shared read-lock as well\"\n        );\n    }\n\n    #[test]\n    fn test_savepoint_rollback_preserves_read_lock() {\n        let (db, _path) = get_database();\n        let conn = db.connect().unwrap();\n\n        conn.execute(\"CREATE TABLE t(x INTEGER PRIMARY KEY)\")\n            .unwrap();\n        conn.execute(\"BEGIN\").unwrap();\n        conn.execute(\"INSERT INTO t VALUES(1)\").unwrap();\n\n        // Trigger a statement failure that causes savepoint rollback.\n        // A duplicate primary key on the second INSERT will fail the\n        // statement, rolling back to the anonymous savepoint while\n        // keeping the write transaction open.\n        let res = conn.execute(\"INSERT INTO t VALUES(1)\");\n        assert!(res.is_err(), \"duplicate PK insert must fail\");\n\n        {\n            let pager = conn.pager.load();\n            let wal = pager.wal.as_ref().unwrap();\n            assert!(\n                wal.holds_read_lock(),\n                \"read lock must still be held after savepoint rollback\"\n            );\n            assert!(\n                wal.holds_write_lock(),\n                \"write lock must still be held after savepoint rollback\"\n            );\n        }\n\n        // The transaction should still be usable: commit succeeds and\n        // the first insert is preserved.\n        conn.execute(\"COMMIT\").unwrap();\n\n        let mut stmt = conn.prepare(\"SELECT count(*) FROM t\").unwrap();\n        let mut count: i64 = 0;\n        stmt.run_with_row_callback(|row| {\n            count = row.get(0).unwrap();\n            Ok(())\n        })\n        .unwrap();\n        assert_eq!(count, 1, \"first insert should survive savepoint rollback\");\n    }\n\n    #[test]\n    fn test_savepoint_then_tx_rollback_allows_restart_checkpoint_from_other_connection() {\n        let (db, _path) = get_database();\n        let conn1 = db.connect().unwrap();\n        let conn2 = db.connect().unwrap();\n\n        conn1\n            .execute(\"CREATE TABLE test(id integer primary key, value text)\")\n            .unwrap();\n        bulk_inserts(&conn1, 2, 2);\n        let count_before = count_test_table(&conn1);\n\n        conn1.execute(\"BEGIN\").unwrap();\n        conn1\n            .execute(\"INSERT INTO test(id, value) VALUES(1000, 'first')\")\n            .unwrap();\n        let duplicate = conn1.execute(\"INSERT INTO test(id, value) VALUES(1000, 'dup')\");\n        assert!(duplicate.is_err(), \"duplicate PK insert must fail\");\n\n        {\n            let pager = conn1.pager.load();\n            let wal = pager.wal.as_ref().unwrap();\n            assert!(\n                wal.holds_read_lock(),\n                \"read lock must still be held after savepoint rollback\"\n            );\n            assert!(\n                wal.holds_write_lock(),\n                \"write lock must still be held after savepoint rollback\"\n            );\n        }\n\n        conn1.execute(\"ROLLBACK\").unwrap();\n\n        {\n            let pager = conn1.pager.load();\n            let wal = pager.wal.as_ref().unwrap();\n            assert!(\n                !wal.holds_read_lock(),\n                \"read lock must be released after transaction rollback\"\n            );\n            assert!(\n                !wal.holds_write_lock(),\n                \"write lock must be released after transaction rollback\"\n            );\n        }\n\n        let locked_slots_after_rollback = {\n            let shared = db.shared_wal.read();\n            read_slots_with_readers(&shared)\n        };\n        assert!(\n            locked_slots_after_rollback.is_empty(),\n            \"transaction rollback after savepoint failure must not leak shared read locks\"\n        );\n        assert_eq!(\n            count_test_table(&conn1),\n            count_before,\n            \"transaction rollback should remove writes made before savepoint failure\"\n        );\n\n        let result = {\n            let pager = conn2.pager.load();\n            run_checkpoint_until_done(&pager, CheckpointMode::Restart)\n        };\n        assert!(\n            result.everything_backfilled(),\n            \"restart checkpoint from another connection must succeed after full rollback\"\n        );\n    }\n\n    #[test]\n    fn test_checkpoint_succeeds_after_rollback() {\n        let (db, _path) = get_database();\n        let conn = db.connect().unwrap();\n\n        conn.execute(\"CREATE TABLE test(id integer primary key, value text)\")\n            .unwrap();\n        bulk_inserts(&conn, 5, 3);\n\n        conn.execute(\"BEGIN\").unwrap();\n        conn.execute(\"INSERT INTO test(value) VALUES('rollback_me')\")\n            .unwrap();\n        conn.execute(\"ROLLBACK\").unwrap();\n\n        let pager = conn.pager.load();\n        let result = run_checkpoint_until_done(&pager, CheckpointMode::Restart);\n        assert!(\n            result.everything_backfilled(),\n            \"checkpoint must succeed after rollback, not return Busy\"\n        );\n    }\n}\n"
  },
  {
    "path": "core/sync.rs",
    "content": "#[cfg(shuttle)]\npub(crate) use shuttle_adapter::*;\n\n#[cfg(not(shuttle))]\npub(crate) use std_adapter::*;\n\n#[cfg(shuttle)]\nmod shuttle_adapter {\n    pub use shuttle::sync::atomic;\n    pub use shuttle::sync::{Arc, Weak};\n    pub use std::sync::{LazyLock, OnceLock};\n\n    use std::fmt::{self, Debug};\n    use std::ops::{Deref, DerefMut};\n\n    pub struct Mutex<T: ?Sized>(shuttle::sync::Mutex<T>);\n\n    impl<T: ?Sized + fmt::Debug> fmt::Debug for Mutex<T> {\n        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n            let mut d = f.debug_struct(\"Mutex\");\n            match self.try_lock() {\n                Some(guard) => d.field(\"data\", &&*guard),\n                None => d.field(\"data\", &format_args!(\"<locked>\")),\n            };\n            d.finish()\n        }\n    }\n\n    impl<T> Mutex<T> {\n        pub fn new(val: T) -> Self {\n            Self(shuttle::sync::Mutex::new(val))\n        }\n    }\n\n    impl<T: ?Sized> Mutex<T> {\n        #[allow(dead_code)]\n        pub fn lock(&self) -> MutexGuard<'_, T> {\n            MutexGuard(self.0.lock().unwrap())\n        }\n\n        pub fn try_lock(&self) -> Option<MutexGuard<'_, T>> {\n            self.0.try_lock().ok().map(MutexGuard)\n        }\n\n        /// Lock the mutex through an Arc, returning an owned guard that can be stored\n        pub fn lock_arc(self: &Arc<Self>) -> ArcMutexGuard<T>\n        where\n            T: 'static,\n        {\n            // We need to lock the mutex and keep the Arc alive\n            // Safety: We hold the Arc which keeps the Mutex alive\n            let arc = Arc::clone(self);\n            let guard = arc.0.lock().unwrap();\n            // Transmute the guard to have 'static lifetime since Arc keeps it alive\n            let guard: shuttle::sync::MutexGuard<'static, T> =\n                unsafe { std::mem::transmute(guard) };\n            ArcMutexGuard { _arc: arc, guard }\n        }\n    }\n\n    impl<T: Default> Default for Mutex<T> {\n        fn default() -> Self {\n            Self::new(T::default())\n        }\n    }\n\n    pub struct MutexGuard<'a, T: ?Sized>(shuttle::sync::MutexGuard<'a, T>);\n\n    impl<T: ?Sized> Deref for MutexGuard<'_, T> {\n        type Target = T;\n        fn deref(&self) -> &T {\n            &self.0\n        }\n    }\n\n    impl<T: ?Sized> DerefMut for MutexGuard<'_, T> {\n        fn deref_mut(&mut self) -> &mut T {\n            &mut self.0\n        }\n    }\n\n    /// An owned mutex guard that holds an Arc to the Mutex.\n    /// This allows the guard to be stored across async yield points.\n    pub struct ArcMutexGuard<T: ?Sized + 'static> {\n        _arc: Arc<Mutex<T>>,\n        guard: shuttle::sync::MutexGuard<'static, T>,\n    }\n\n    unsafe impl<T: ?Sized + 'static> Send for ArcMutexGuard<T> {}\n    unsafe impl<T: ?Sized + 'static> Sync for ArcMutexGuard<T> {}\n\n    impl<T: ?Sized + 'static> Deref for ArcMutexGuard<T> {\n        type Target = T;\n        fn deref(&self) -> &T {\n            &self.guard\n        }\n    }\n\n    impl<T: ?Sized + 'static> DerefMut for ArcMutexGuard<T> {\n        fn deref_mut(&mut self) -> &mut T {\n            &mut self.guard\n        }\n    }\n\n    pub struct RwLock<T: ?Sized>(shuttle::sync::RwLock<T>);\n\n    impl<T: ?Sized + fmt::Debug> fmt::Debug for RwLock<T> {\n        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n            let mut d = f.debug_struct(\"RwLock\");\n            match self.try_read() {\n                Some(guard) => d.field(\"data\", &&*guard),\n                None => d.field(\"data\", &format_args!(\"<locked>\")),\n            };\n            d.finish()\n        }\n    }\n\n    impl<T> RwLock<T> {\n        pub fn new(val: T) -> Self {\n            Self(shuttle::sync::RwLock::new(val))\n        }\n\n        pub fn into_inner(self) -> T {\n            self.0.into_inner().unwrap()\n        }\n    }\n\n    impl<T: ?Sized> RwLock<T> {\n        pub fn read(&self) -> RwLockReadGuard<'_, T> {\n            RwLockReadGuard(self.0.read().unwrap())\n        }\n\n        pub fn write(&self) -> RwLockWriteGuard<'_, T> {\n            RwLockWriteGuard(self.0.write().unwrap())\n        }\n\n        pub fn try_read(&self) -> Option<RwLockReadGuard<'_, T>> {\n            self.0.try_read().ok().map(RwLockReadGuard)\n        }\n\n        pub fn try_write(&self) -> Option<RwLockWriteGuard<'_, T>> {\n            self.0.try_write().ok().map(RwLockWriteGuard)\n        }\n\n        pub fn get_mut(&mut self) -> &mut T {\n            self.0.get_mut().unwrap()\n        }\n    }\n\n    impl<T: Default> Default for RwLock<T> {\n        fn default() -> Self {\n            Self::new(T::default())\n        }\n    }\n\n    pub struct RwLockReadGuard<'a, T: ?Sized>(shuttle::sync::RwLockReadGuard<'a, T>);\n\n    impl<T: Debug> Debug for RwLockReadGuard<'_, T> {\n        fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n            Debug::fmt(&self.0, f)\n        }\n    }\n\n    impl<'a, T: fmt::Display + ?Sized + 'a> fmt::Display for RwLockReadGuard<'a, T> {\n        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n            (**self).fmt(f)\n        }\n    }\n\n    impl<T: ?Sized> Deref for RwLockReadGuard<'_, T> {\n        type Target = T;\n        fn deref(&self) -> &T {\n            &self.0\n        }\n    }\n\n    pub struct RwLockWriteGuard<'a, T: ?Sized>(shuttle::sync::RwLockWriteGuard<'a, T>);\n\n    impl<T: Debug> Debug for RwLockWriteGuard<'_, T> {\n        fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n            Debug::fmt(&self.0, f)\n        }\n    }\n\n    impl<'a, T: fmt::Display + ?Sized + 'a> fmt::Display for RwLockWriteGuard<'a, T> {\n        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n            (**self).fmt(f)\n        }\n    }\n\n    impl<T: ?Sized> Deref for RwLockWriteGuard<'_, T> {\n        type Target = T;\n        fn deref(&self) -> &T {\n            &self.0\n        }\n    }\n\n    impl<T: ?Sized> DerefMut for RwLockWriteGuard<'_, T> {\n        fn deref_mut(&mut self) -> &mut T {\n            &mut self.0\n        }\n    }\n}\n\n#[cfg(not(shuttle))]\nmod std_adapter {\n    pub use parking_lot::{Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard};\n    pub use std::sync::{atomic, Arc, LazyLock, OnceLock, Weak};\n\n    /// Type alias for ArcMutexGuard that hides the RawMutex type parameter\n    pub type ArcMutexGuard<T> = parking_lot::ArcMutexGuard<parking_lot::RawMutex, T>;\n}\n"
  },
  {
    "path": "core/thread.rs",
    "content": "#[cfg(shuttle)]\npub(crate) use shuttle_adapter::*;\n\n#[cfg(not(shuttle))]\npub(crate) use std_adapter::*;\n\n#[expect(unused_imports)]\n#[cfg(shuttle)]\nmod shuttle_adapter {\n    pub use shuttle::hint::spin_loop;\n    pub use shuttle::thread::{\n        current, panicking, park, scope, sleep, spawn, yield_now, Builder, JoinHandle, Scope,\n        ScopedJoinHandle, Thread, ThreadId,\n    };\n    pub use shuttle::thread_local;\n}\n\n#[expect(unused_imports)]\n#[cfg(not(shuttle))]\nmod std_adapter {\n    pub use std::hint::spin_loop;\n    pub use std::thread::{\n        current, panicking, park, scope, sleep, spawn, yield_now, Builder, JoinHandle, Scope,\n        ScopedJoinHandle, Thread, ThreadId,\n    };\n    pub use std::thread_local;\n}\n"
  },
  {
    "path": "core/time/internal.rs",
    "content": "use std::ops::{Deref, Sub};\n\nuse chrono::{self, DateTime, Timelike, Utc};\nuse chrono::{prelude::*, DurationRound};\n\nuse turso_ext::Value;\n\nuse crate::time::{Result, TimeError};\n\nconst DAYS_BEFORE_EPOCH: i64 = 719162;\nconst TIME_BLOB_SIZE: usize = 13;\nconst VERSION: u8 = 1;\n\n#[derive(Debug, PartialEq, PartialOrd, Eq)]\npub struct Time {\n    inner: DateTime<Utc>,\n}\n\n#[derive(Debug, PartialEq, Eq, PartialOrd)]\npub struct Duration {\n    inner: chrono::Duration,\n}\n\n#[derive(strum_macros::Display, strum_macros::EnumString)]\npub enum TimeField {\n    #[strum(to_string = \"millennium\")]\n    Millennium,\n    #[strum(to_string = \"century\")]\n    Century,\n    #[strum(to_string = \"decade\")]\n    Decade,\n    #[strum(to_string = \"year\")]\n    Year,\n    #[strum(to_string = \"quarter\")]\n    Quarter,\n    #[strum(to_string = \"month\")]\n    Month,\n    #[strum(to_string = \"day\")]\n    Day,\n    #[strum(to_string = \"hour\")]\n    Hour,\n    #[strum(to_string = \"minute\")]\n    Minute,\n    #[strum(to_string = \"second\")]\n    Second,\n    #[strum(to_string = \"millisecond\")]\n    MilliSecond,\n    #[strum(to_string = \"milli\")]\n    Milli,\n    #[strum(to_string = \"microsecond\")]\n    MicroSecond,\n    #[strum(to_string = \"micro\")]\n    Micro,\n    #[strum(to_string = \"nanosecond\")]\n    NanoSecond,\n    #[strum(to_string = \"nano\")]\n    Nano,\n    #[strum(to_string = \"isoyear\")]\n    IsoYear,\n    #[strum(to_string = \"isoweek\")]\n    IsoWeek,\n    #[strum(to_string = \"isodow\")]\n    IsoDow,\n    #[strum(to_string = \"yearday\")]\n    YearDay,\n    #[strum(to_string = \"weekday\")]\n    WeekDay,\n    #[strum(to_string = \"epoch\")]\n    Epoch,\n}\n\n#[derive(strum_macros::Display, strum_macros::EnumString)]\npub enum TimeRoundField {\n    #[strum(to_string = \"millennium\")]\n    Millennium,\n    #[strum(to_string = \"century\")]\n    Century,\n    #[strum(to_string = \"decade\")]\n    Decade,\n    #[strum(to_string = \"year\")]\n    Year,\n    #[strum(to_string = \"quarter\")]\n    Quarter,\n    #[strum(to_string = \"month\")]\n    Month,\n    #[strum(to_string = \"week\")]\n    Week,\n    #[strum(to_string = \"day\")]\n    Day,\n    #[strum(to_string = \"hour\")]\n    Hour,\n    #[strum(to_string = \"minute\")]\n    Minute,\n    #[strum(to_string = \"second\")]\n    Second,\n    #[strum(to_string = \"millisecond\")]\n    MilliSecond,\n    #[strum(to_string = \"milli\")]\n    Milli,\n    #[strum(to_string = \"microsecond\")]\n    MicroSecond,\n    #[strum(to_string = \"micro\")]\n    Micro,\n}\n\nimpl Default for Time {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl Time {\n    /// Returns a new instance of Time with tracking UTC::now\n    pub fn new() -> Self {\n        Self { inner: Utc::now() }\n    }\n\n    pub fn into_blob(self) -> Value {\n        let blob: [u8; 13] = self.into();\n        Value::from_blob(blob.to_vec())\n    }\n\n    pub fn fmt_iso(&self, offset_sec: i32) -> Result<String> {\n        if offset_sec == 0 {\n            if self.inner.nanosecond() == 0 {\n                return Ok(self.inner.format(\"%FT%TZ\").to_string());\n            } else {\n                return Ok(self.inner.format(\"%FT%T%.9fZ\").to_string());\n            }\n        }\n        // I do not see how this can error\n        let offset = &FixedOffset::east_opt(offset_sec).ok_or(TimeError::InvalidFormat)?;\n\n        let timezone_date = self.inner.with_timezone(offset);\n\n        if timezone_date.nanosecond() == 0 {\n            Ok(timezone_date.format(\"%FT%T%:z\").to_string())\n        } else {\n            Ok(timezone_date.format(\"%FT%T%.9f%:z\").to_string())\n        }\n    }\n\n    pub fn fmt_datetime(&self, offset_sec: i32) -> Result<String> {\n        let fmt = \"%F %T\";\n\n        if offset_sec == 0 {\n            return Ok(self.inner.format(fmt).to_string());\n        }\n        // I do not see how this can error\n        let offset = &FixedOffset::east_opt(offset_sec).ok_or(TimeError::InvalidFormat)?;\n\n        let timezone_date = self.inner.with_timezone(offset);\n\n        Ok(timezone_date.format(fmt).to_string())\n    }\n\n    pub fn fmt_date(&self, offset_sec: i32) -> Result<String> {\n        let fmt = \"%F\";\n\n        if offset_sec == 0 {\n            return Ok(self.inner.format(fmt).to_string());\n        }\n        // I do not see how this can error\n        let offset = &FixedOffset::east_opt(offset_sec).ok_or(TimeError::InvalidFormat)?;\n\n        let timezone_date = self.inner.with_timezone(offset);\n\n        Ok(timezone_date.format(fmt).to_string())\n    }\n\n    pub fn fmt_time(&self, offset_sec: i32) -> Result<String> {\n        let fmt = \"%T\";\n\n        if offset_sec == 0 {\n            return Ok(self.inner.format(fmt).to_string());\n        }\n        // I do not see how this can error\n        let offset = &FixedOffset::east_opt(offset_sec).ok_or(TimeError::InvalidFormat)?;\n\n        let timezone_date = self.inner.with_timezone(offset);\n\n        Ok(timezone_date.format(fmt).to_string())\n    }\n\n    /// Adjust the datetime to the offset\n    pub fn from_datetime(dt: DateTime<Utc>) -> Self {\n        Self { inner: dt }\n    }\n\n    #[allow(clippy::too_many_arguments)]\n    pub fn time_date(\n        year: i32,\n        month: i32,\n        day: i64,\n        hour: i64,\n        minutes: i64,\n        seconds: i64,\n        nano_secs: i64,\n        offset: FixedOffset,\n    ) -> Result<Option<Self>> {\n        let mut dt: NaiveDateTime = NaiveDate::from_ymd_opt(1, 1, 1)\n            .unwrap()\n            .and_hms_opt(0, 0, 0)\n            .unwrap();\n\n        match year.cmp(&0) {\n            std::cmp::Ordering::Greater => {\n                let months = match (year - 1).unsigned_abs().checked_mul(12) {\n                    Some(m) => m,\n                    None => return Ok(None),\n                };\n\n                dt = match dt.checked_add_months(chrono::Months::new(months)) {\n                    Some(d) => d,\n                    None => return Ok(None),\n                };\n            }\n            std::cmp::Ordering::Less => {\n                let months = match (year - 1).unsigned_abs().checked_mul(12) {\n                    Some(m) => m,\n                    None => return Ok(None),\n                };\n\n                dt = match dt.checked_sub_months(chrono::Months::new(months)) {\n                    Some(d) => d,\n                    None => return Ok(None),\n                };\n            }\n            std::cmp::Ordering::Equal => (),\n        };\n\n        match month.cmp(&0) {\n            std::cmp::Ordering::Greater => {\n                dt = match dt.checked_add_months(chrono::Months::new((month - 1).unsigned_abs())) {\n                    Some(d) => d,\n                    None => return Ok(None),\n                };\n            }\n            std::cmp::Ordering::Less => {\n                dt = match dt.checked_sub_months(chrono::Months::new((month - 1).unsigned_abs())) {\n                    Some(d) => d,\n                    None => return Ok(None),\n                };\n            }\n            std::cmp::Ordering::Equal => (),\n        };\n\n        if let Some(d) = chrono::Duration::try_days(day - 1) {\n            dt += d;\n        } else {\n            return Ok(None);\n        }\n\n        if let Some(d) = chrono::Duration::try_hours(hour) {\n            dt += d;\n        } else {\n            return Ok(None);\n        }\n\n        if let Some(d) = chrono::Duration::try_minutes(minutes) {\n            dt += d;\n        } else {\n            return Ok(None);\n        }\n\n        if let Some(d) = chrono::Duration::try_seconds(seconds) {\n            dt += d;\n        } else {\n            return Ok(None);\n        }\n\n        dt += chrono::Duration::nanoseconds(nano_secs);\n\n        let dt = dt\n            .and_local_timezone(offset)\n            .single()\n            .map(|d| d.naive_utc());\n\n        match dt {\n            Some(valid_dt) => Ok(Some(valid_dt.into())),\n            None => Ok(None),\n        }\n    }\n\n    pub fn time_add_date(self, years: i32, months: i32, days: i64) -> Result<Self> {\n        let mut dt: NaiveDateTime = self.into();\n\n        match years.cmp(&0) {\n            std::cmp::Ordering::Greater => {\n                dt = dt\n                    .checked_add_months(chrono::Months::new(years.unsigned_abs() * 12))\n                    .ok_or(TimeError::CreationError)?;\n            }\n            std::cmp::Ordering::Less => {\n                dt = dt\n                    .checked_sub_months(chrono::Months::new(years.unsigned_abs() * 12))\n                    .ok_or(TimeError::CreationError)?;\n            }\n            std::cmp::Ordering::Equal => (),\n        };\n\n        match months.cmp(&0) {\n            std::cmp::Ordering::Greater => {\n                dt = dt\n                    .checked_add_months(chrono::Months::new(months.unsigned_abs()))\n                    .ok_or(TimeError::CreationError)?\n            }\n            std::cmp::Ordering::Less => {\n                dt = dt\n                    .checked_sub_months(chrono::Months::new(months.unsigned_abs()))\n                    .ok_or(TimeError::CreationError)?\n            }\n            std::cmp::Ordering::Equal => (),\n        };\n\n        dt += chrono::Duration::try_days(days).ok_or(TimeError::CreationError)?;\n\n        Ok(dt.into())\n    }\n\n    pub fn get_second(&self) -> i64 {\n        self.inner.second() as i64\n    }\n\n    pub fn get_nanosecond(&self) -> i64 {\n        self.inner.timestamp_subsec_nanos() as i64\n    }\n\n    pub fn to_unix(&self) -> i64 {\n        self.inner.timestamp()\n    }\n\n    pub fn to_unix_milli(&self) -> i64 {\n        self.inner.timestamp_millis()\n    }\n\n    pub fn to_unix_micro(&self) -> i64 {\n        self.inner.timestamp_micros()\n    }\n\n    pub fn to_unix_nano(&self) -> Option<i64> {\n        self.inner.timestamp_nanos_opt()\n    }\n\n    pub fn add_duration(&self, d: Duration) -> Self {\n        Self {\n            inner: self.inner + d.inner,\n        }\n    }\n\n    pub fn sub_duration(&self, d: Duration) -> Self {\n        Self {\n            inner: self.inner - d.inner,\n        }\n    }\n\n    pub fn trunc_duration(&self, d: Duration) -> Result<Self> {\n        Ok(Self {\n            inner: self.inner.duration_trunc(d.inner)?,\n        })\n    }\n\n    pub fn trunc_field(&self, field: TimeRoundField) -> Result<Option<Self>> {\n        use TimeRoundField::*;\n\n        let year: i32;\n        let mut month: i32 = 1;\n        let mut week: i32 = 0;\n        let mut day: i64 = 1;\n        let mut hour: i64 = 0;\n        let mut minutes: i64 = 0;\n        let mut seconds: i64 = 0;\n        let mut nano_secs: i64 = 0;\n        let offset = FixedOffset::east_opt(0).unwrap(); // UTC\n\n        match field {\n            Millennium => {\n                let millennium = (self.inner.year() / 1000) * 1000;\n                year = millennium;\n            }\n            Century => {\n                let century = (self.inner.year() / 100) * 100;\n                year = century;\n            }\n            Decade => {\n                let decade = (self.inner.year() / 10) * 10;\n                year = decade;\n            }\n            Year => {\n                year = self.inner.year();\n            }\n            Quarter => {\n                let quarter = ((self.inner.month() - 1) / 3) as i32;\n                year = self.inner.year();\n                month = (quarter * 3) + 1;\n            }\n            Month => {\n                year = self.inner.year();\n                month = self.inner.month() as i32;\n            }\n            Week => {\n                let isoweek = self.inner.iso_week();\n                year = isoweek.year();\n                week = isoweek.week() as i32;\n            }\n            Day => {\n                year = self.inner.year();\n                month = self.inner.month() as i32;\n                day = self.inner.day() as i64;\n            }\n            Hour => {\n                year = self.inner.year();\n                month = self.inner.month() as i32;\n                day = self.inner.day() as i64;\n                hour = self.inner.hour() as i64;\n            }\n            Minute => {\n                year = self.inner.year();\n                month = self.inner.month() as i32;\n                day = self.inner.day() as i64;\n                hour = self.inner.hour() as i64;\n                minutes = self.inner.minute() as i64;\n            }\n            Second => {\n                year = self.inner.year();\n                month = self.inner.month() as i32;\n                day = self.inner.day() as i64;\n                hour = self.inner.hour() as i64;\n                minutes = self.inner.minute() as i64;\n                seconds = self.inner.second() as i64;\n            }\n            MilliSecond | Milli => {\n                year = self.inner.year();\n                month = self.inner.month() as i32;\n                day = self.inner.day() as i64;\n                hour = self.inner.hour() as i64;\n                minutes = self.inner.minute() as i64;\n                seconds = self.inner.second() as i64;\n                nano_secs = (self.inner.nanosecond() / 1_000_000 * 1_000_000) as i64;\n            }\n            MicroSecond | Micro => {\n                year = self.inner.year();\n                month = self.inner.month() as i32;\n                day = self.inner.day() as i64;\n                hour = self.inner.hour() as i64;\n                minutes = self.inner.minute() as i64;\n                seconds = self.inner.second() as i64;\n                nano_secs = (self.inner.nanosecond() / 1_000 * 1_000) as i64;\n            }\n        };\n\n        let ret = Self::time_date(year, month, day, hour, minutes, seconds, nano_secs, offset)?;\n\n        let mut ret = match ret {\n            Some(t) => t,\n            None => return Ok(None),\n        };\n\n        if week != 0 {\n            ret = ret.time_add_date(0, 0, ((week - 1) * 7) as i64)?;\n        }\n\n        Ok(Some(ret))\n    }\n\n    pub fn round_duration(&self, d: Duration) -> Result<Self> {\n        Ok(Self {\n            inner: self.inner.duration_round(d.inner)?,\n        })\n    }\n\n    pub fn time_get(&self, field: TimeField) -> Value {\n        use TimeField::*;\n\n        match field {\n            Millennium => Value::from_integer((self.inner.year() / 1000) as i64),\n            Century => Value::from_integer((self.inner.year() / 100) as i64),\n            Decade => Value::from_integer((self.inner.year() / 10) as i64),\n            Year => Value::from_integer(self.inner.year() as i64),\n            Quarter => Value::from_integer(self.inner.month().div_ceil(3) as i64),\n            Month => Value::from_integer(self.inner.month() as i64),\n            Day => Value::from_integer(self.inner.day() as i64),\n            Hour => Value::from_integer(self.inner.hour() as i64),\n            Minute => Value::from_integer(self.inner.minute() as i64),\n            Second => Value::from_float(\n                self.inner.second() as f64 + (self.inner.nanosecond() as f64) / (1_000_000_000_f64),\n            ),\n            MilliSecond | Milli => {\n                Value::from_integer((self.inner.nanosecond() / 1_000_000 % 1_000) as i64)\n            }\n            MicroSecond | Micro => {\n                Value::from_integer((self.inner.nanosecond() / 1_000 % 1_000_000) as i64)\n            }\n            NanoSecond | Nano => {\n                Value::from_integer((self.inner.nanosecond() % 1_000_000_000) as i64)\n            }\n            IsoYear => Value::from_integer(self.inner.iso_week().year() as i64),\n            IsoWeek => Value::from_integer(self.inner.iso_week().week() as i64),\n            IsoDow => Value::from_integer(self.inner.weekday().days_since(Weekday::Sun) as i64),\n            YearDay => Value::from_integer(self.inner.ordinal() as i64),\n            WeekDay => Value::from_integer(self.inner.weekday().num_days_from_sunday() as i64),\n            Epoch => Value::from_float(\n                self.inner.timestamp() as f64 + self.inner.nanosecond() as f64 / 1_000_000_000_f64,\n            ),\n        }\n    }\n}\n\nimpl From<Time> for [u8; TIME_BLOB_SIZE] {\n    fn from(value: Time) -> Self {\n        let mut blob = [0u8; 13];\n\n        let seconds = value.inner.timestamp() + (3600 * 24 * DAYS_BEFORE_EPOCH);\n        let nanoseconds = value.inner.timestamp_subsec_nanos();\n\n        blob[0] = VERSION;\n        blob[1] = (seconds >> 56) as u8; // bytes 1-8: seconds\n        blob[2] = (seconds >> 48) as u8;\n        blob[3] = (seconds >> 40) as u8;\n        blob[4] = (seconds >> 32) as u8;\n        blob[5] = (seconds >> 24) as u8;\n        blob[6] = (seconds >> 16) as u8;\n        blob[7] = (seconds >> 8) as u8;\n        blob[8] = seconds as u8;\n        blob[9] = (nanoseconds >> 24) as u8; // bytes 9-12: nanoseconds\n        blob[10] = (nanoseconds >> 16) as u8;\n        blob[11] = (nanoseconds >> 8) as u8;\n        blob[12] = (nanoseconds) as u8;\n\n        blob\n    }\n}\n\nimpl TryFrom<Vec<u8>> for Time {\n    type Error = TimeError;\n\n    fn try_from(value: Vec<u8>) -> Result<Time> {\n        if value.len() != TIME_BLOB_SIZE {\n            return Err(TimeError::InvalidSize);\n        }\n        if value[0] != VERSION {\n            return Err(TimeError::MismatchVersion);\n        }\n\n        let seconds = value[8] as i64\n            | (value[7] as i64) << 8\n            | (value[6] as i64) << 16\n            | (value[5] as i64) << 24\n            | (value[4] as i64) << 32\n            | (value[3] as i64) << 40\n            | (value[2] as i64) << 48\n            | (value[1] as i64) << 56;\n\n        let nanoseconds = value[12] as u32\n            | (value[11] as u32) << 8\n            | (value[10] as u32) << 16\n            | (value[9] as u32) << 24;\n\n        Ok(Self {\n            inner: DateTime::from_timestamp(seconds - (3600 * 24 * DAYS_BEFORE_EPOCH), nanoseconds)\n                .ok_or(TimeError::InvalidFormat)?\n                .to_utc(),\n        })\n    }\n}\n\nimpl From<NaiveDateTime> for Time {\n    fn from(value: NaiveDateTime) -> Self {\n        Self {\n            inner: value.and_utc(),\n        }\n    }\n}\n\nimpl From<Time> for NaiveDateTime {\n    fn from(value: Time) -> Self {\n        value.inner.naive_utc()\n    }\n}\n\nimpl Sub for Time {\n    type Output = Duration;\n    fn sub(self, rhs: Self) -> Self::Output {\n        Duration {\n            inner: self.inner - rhs.inner,\n        }\n    }\n}\n\nimpl From<i64> for Duration {\n    fn from(value: i64) -> Self {\n        Self {\n            inner: chrono::Duration::nanoseconds(value),\n        }\n    }\n}\n\nimpl Deref for Duration {\n    type Target = chrono::Duration;\n    fn deref(&self) -> &Self::Target {\n        &self.inner\n    }\n}\n"
  },
  {
    "path": "core/time/mod.rs",
    "content": "use std::str::FromStr as _;\n\nuse chrono::prelude::*;\nuse core::cmp::Ordering;\nuse thiserror::Error;\n\nuse crate::ext::register_scalar_function;\nuse turso_ext::{scalar, ExtensionApi, ResultCode, Value, ValueType};\n\nmod internal;\n\nuse internal::*;\n\npub fn register_extension(ext_api: &mut ExtensionApi) {\n    unsafe {\n        register_scalar_function(ext_api.ctx, c\"time_now\".as_ptr(), time_now);\n        register_scalar_function(ext_api.ctx, c\"time_date\".as_ptr(), time_date);\n        register_scalar_function(ext_api.ctx, c\"make_date\".as_ptr(), make_date);\n        register_scalar_function(ext_api.ctx, c\"make_timestamp\".as_ptr(), make_timestamp);\n        register_scalar_function(ext_api.ctx, c\"time_get\".as_ptr(), time_get);\n        register_scalar_function(ext_api.ctx, c\"time_get_year\".as_ptr(), time_get_year);\n        register_scalar_function(ext_api.ctx, c\"time_get_month\".as_ptr(), time_get_month);\n        register_scalar_function(ext_api.ctx, c\"time_get_day\".as_ptr(), time_get_day);\n        register_scalar_function(ext_api.ctx, c\"time_get_hour\".as_ptr(), time_get_hour);\n        register_scalar_function(ext_api.ctx, c\"time_get_minute\".as_ptr(), time_get_minute);\n        register_scalar_function(ext_api.ctx, c\"time_get_second\".as_ptr(), time_get_second);\n        register_scalar_function(ext_api.ctx, c\"time_get_nano\".as_ptr(), time_get_nano);\n        register_scalar_function(ext_api.ctx, c\"time_get_weekday\".as_ptr(), time_get_weekday);\n        register_scalar_function(ext_api.ctx, c\"time_get_yearday\".as_ptr(), time_get_yearday);\n        register_scalar_function(ext_api.ctx, c\"time_get_isoyear\".as_ptr(), time_get_isoyear);\n        register_scalar_function(ext_api.ctx, c\"time_get_isoweek\".as_ptr(), time_get_isoweek);\n        register_scalar_function(ext_api.ctx, c\"time_unix\".as_ptr(), time_unix);\n        register_scalar_function(ext_api.ctx, c\"to_timestamp\".as_ptr(), to_timestamp);\n        register_scalar_function(ext_api.ctx, c\"time_milli\".as_ptr(), time_milli);\n        register_scalar_function(ext_api.ctx, c\"time_micro\".as_ptr(), time_micro);\n        register_scalar_function(ext_api.ctx, c\"time_nano\".as_ptr(), time_nano);\n        register_scalar_function(ext_api.ctx, c\"time_to_unix\".as_ptr(), time_to_unix);\n        register_scalar_function(ext_api.ctx, c\"time_to_milli\".as_ptr(), time_to_milli);\n        register_scalar_function(ext_api.ctx, c\"time_to_micro\".as_ptr(), time_to_micro);\n        register_scalar_function(ext_api.ctx, c\"time_to_nano\".as_ptr(), time_to_nano);\n        register_scalar_function(ext_api.ctx, c\"time_after\".as_ptr(), time_after);\n        register_scalar_function(ext_api.ctx, c\"time_before\".as_ptr(), time_before);\n        register_scalar_function(ext_api.ctx, c\"time_compare\".as_ptr(), time_compare);\n        register_scalar_function(ext_api.ctx, c\"time_equal\".as_ptr(), time_equal);\n        register_scalar_function(ext_api.ctx, c\"dur_ns\".as_ptr(), dur_ns);\n        register_scalar_function(ext_api.ctx, c\"dur_us\".as_ptr(), dur_us);\n        register_scalar_function(ext_api.ctx, c\"dur_ms\".as_ptr(), dur_ms);\n        register_scalar_function(ext_api.ctx, c\"dur_s\".as_ptr(), dur_s);\n        register_scalar_function(ext_api.ctx, c\"dur_m\".as_ptr(), dur_m);\n        register_scalar_function(ext_api.ctx, c\"dur_h\".as_ptr(), dur_h);\n        register_scalar_function(ext_api.ctx, c\"time_add\".as_ptr(), time_add);\n        register_scalar_function(ext_api.ctx, c\"time_add_date\".as_ptr(), time_add_date);\n        register_scalar_function(ext_api.ctx, c\"time_sub\".as_ptr(), time_sub);\n        register_scalar_function(ext_api.ctx, c\"time_since\".as_ptr(), time_since);\n        register_scalar_function(ext_api.ctx, c\"time_until\".as_ptr(), time_until);\n        register_scalar_function(ext_api.ctx, c\"time_trunc\".as_ptr(), time_trunc);\n        register_scalar_function(ext_api.ctx, c\"time_round\".as_ptr(), time_round);\n        register_scalar_function(ext_api.ctx, c\"time_fmt_iso\".as_ptr(), time_fmt_iso);\n        register_scalar_function(\n            ext_api.ctx,\n            c\"time_fmt_datetime\".as_ptr(),\n            time_fmt_datetime,\n        );\n        register_scalar_function(ext_api.ctx, c\"time_fmt_date\".as_ptr(), time_fmt_date);\n        register_scalar_function(ext_api.ctx, c\"time_fmt_time\".as_ptr(), time_fmt_time);\n        register_scalar_function(ext_api.ctx, c\"time_parse\".as_ptr(), time_parse);\n    }\n}\n\nmacro_rules! ok_tri {\n    ($e:expr) => {\n        match $e {\n            Some(val) => val,\n            None => return Value::error(ResultCode::Error),\n        }\n    };\n    ($e:expr, $msg:expr) => {\n        match $e {\n            Some(val) => val,\n            None => return Value::error_with_message($msg.to_string()),\n        }\n    };\n}\n\nmacro_rules! tri {\n    ($e:expr) => {\n        match $e {\n            Ok(val) => val,\n            Err(err) => return Value::error_with_message(err.to_string()),\n        }\n    };\n    ($e:expr, $msg:expr) => {\n        match $e {\n            Ok(val) => val,\n            Err(_) => return Value::error_with_message($msg.to_string()),\n        }\n    };\n}\n\n/// Checks to see if e's enum is of type val\nmacro_rules! value_tri {\n    ($e:expr, $val:pat) => {\n        match $e {\n            $val => (),\n            _ => return Value::error(ResultCode::InvalidArgs),\n        }\n    };\n    ($e:expr, $val:pat, $msg:expr) => {\n        match $e {\n            $val => (),\n            _ => return Value::error_with_message($msg.to_string()),\n        }\n    };\n}\n\n#[derive(Error, Debug)]\npub enum TimeError {\n    /// Timezone offset is invalid\n    #[error(\"invalid timezone offset\")]\n    InvalidOffset,\n    #[error(\"invalid datetime format\")]\n    InvalidFormat,\n    /// Blob is not size of `TIME_BLOB_SIZE`\n    #[error(\"invalid time blob size\")]\n    InvalidSize,\n    /// Blob time version not matching\n    #[error(\"mismatch time blob version\")]\n    MismatchVersion,\n    #[error(\"unknown field\")]\n    UnknownField(#[from] <TimeField as ::core::str::FromStr>::Err),\n    #[error(\"rounding error\")]\n    RoundingError(#[from] chrono::RoundingError),\n    #[error(\"time creation error\")]\n    CreationError,\n}\n\ntype Result<T> = core::result::Result<T, TimeError>;\n\n#[scalar(name = \"time_now\", alias = \"now\")]\nfn time_now(args: &[Value]) -> Value {\n    if !args.is_empty() {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n    let t = Time::new();\n\n    t.into_blob()\n}\n\n/// ```text\n/// time_date(year, month, day[, hour, min, sec[, nsec[, offset_sec]]])\n/// ```\n///\n/// Returns the Time corresponding to a given date/time. The time part (hour+minute+second), the nanosecond part, and the timezone offset part are all optional.\n///\n/// The `month`, `day`, `hour`, `min`, `sec`, and `nsec` values may be outside their usual ranges and will be normalized during the conversion. For example, October 32 converts to November 1.\n///\n/// If `offset_sec` is not 0, the source time is treated as being in a given timezone (with an offset in seconds east of UTC) and converted back to UTC.\nfn time_date_internal(args: &[Value]) -> Value {\n    if args.len() != 3 && args.len() != 6 && args.len() != 7 && args.len() != 8 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    for arg in args {\n        value_tri!(\n            arg.value_type(),\n            ValueType::Integer,\n            \"all parameters should be integers\"\n        );\n    }\n\n    let year = ok_tri!(args[0].to_integer());\n    let month = ok_tri!(args[1].to_integer());\n    let day = ok_tri!(args[2].to_integer());\n    let mut hour = 0;\n    let mut minutes = 0;\n    let mut seconds = 0;\n    let mut nano_secs = 0;\n\n    if args.len() >= 6 {\n        hour = ok_tri!(args[3].to_integer());\n        minutes = ok_tri!(args[4].to_integer());\n        seconds = ok_tri!(args[5].to_integer());\n    }\n\n    if args.len() >= 7 {\n        nano_secs = ok_tri!(args[6].to_integer());\n    }\n\n    if args.len() == 8 {\n        let offset_sec = ok_tri!(args[7].to_integer()) as i32;\n        seconds -= offset_sec as i64;\n    }\n\n    let t = Time::time_date(\n        year as i32,\n        month as i32,\n        day,\n        hour,\n        minutes,\n        seconds,\n        nano_secs,\n        FixedOffset::east_opt(0).unwrap(),\n    );\n\n    match tri!(t) {\n        Some(t) => t.into_blob(),\n        None => Value::null(),\n    }\n}\n\n#[scalar(name = \"time_date\")]\nfn time_date(args: &[Value]) {\n    time_date_internal(args)\n}\n\n#[scalar(name = \"make_date\")]\nfn make_date(args: &[Value]) -> Value {\n    if args.len() != 3 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    time_date_internal(args)\n}\n\n#[scalar(name = \"make_timestamp\")]\nfn make_timestamp(args: &[Value]) -> Value {\n    if args.len() != 6 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    time_date_internal(args)\n}\n\n#[scalar(name = \"time_get\", alias = \"date_part\")]\nfn time_get(args: &[Value]) -> Value {\n    if args.len() != 2 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    let blob = ok_tri!(args[0].to_blob(), \"1st parameter: should be a time blob\");\n\n    let t = tri!(Time::try_from(blob));\n\n    let field = ok_tri!(args[1].to_text(), \"2nd parameter: should be a field name\");\n\n    let field = tri!(TimeField::from_str(field));\n\n    t.time_get(field)\n}\n\n#[scalar(name = \"time_get_year\")]\nfn time_get_year(args: &[Value]) -> Value {\n    if args.len() != 1 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    let blob = ok_tri!(args[0].to_blob(), \"parameter should be a time blob\");\n\n    let t = tri!(Time::try_from(blob));\n\n    t.time_get(TimeField::Year)\n}\n\n#[scalar(name = \"time_get_month\")]\nfn time_get_month(args: &[Value]) -> Value {\n    if args.len() != 1 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    let blob = ok_tri!(args[0].to_blob(), \"parameter should be a time blob\");\n\n    let t = tri!(Time::try_from(blob));\n\n    t.time_get(TimeField::Month)\n}\n\n#[scalar(name = \"time_get_day\")]\nfn time_get_day(args: &[Value]) -> Value {\n    if args.len() != 1 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    let blob = ok_tri!(args[0].to_blob(), \"parameter should be a time blob\");\n\n    let t = tri!(Time::try_from(blob));\n\n    t.time_get(TimeField::Day)\n}\n\n#[scalar(name = \"time_get_hour\")]\nfn time_get_hour(args: &[Value]) -> Value {\n    if args.len() != 1 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    let blob = ok_tri!(args[0].to_blob(), \"parameter should be a time blob\");\n\n    let t = tri!(Time::try_from(blob));\n\n    t.time_get(TimeField::Hour)\n}\n\n#[scalar(name = \"time_get_minute\")]\nfn time_get_minute(args: &[Value]) -> Value {\n    if args.len() != 1 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    let blob = ok_tri!(args[0].to_blob(), \"parameter should be a time blob\");\n\n    let t = tri!(Time::try_from(blob));\n\n    t.time_get(TimeField::Minute)\n}\n\n#[scalar(name = \"time_get_second\")]\nfn time_get_second(args: &[Value]) -> Value {\n    if args.len() != 1 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    let blob = ok_tri!(args[0].to_blob(), \"parameter should be a time blob\");\n\n    let t = tri!(Time::try_from(blob));\n\n    Value::from_integer(t.get_second())\n}\n\n#[scalar(name = \"time_get_nano\")]\nfn time_get_nano(args: &[Value]) -> Value {\n    if args.len() != 1 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    let blob = ok_tri!(args[0].to_blob(), \"parameter should be a time blob\");\n\n    let t = tri!(Time::try_from(blob));\n\n    Value::from_integer(t.get_nanosecond())\n}\n\n#[scalar(name = \"time_get_weekday\")]\nfn time_get_weekday(args: &[Value]) -> Value {\n    if args.len() != 1 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    let blob = ok_tri!(args[0].to_blob(), \"parameter should be a time blob\");\n\n    let t = tri!(Time::try_from(blob));\n\n    t.time_get(TimeField::WeekDay)\n}\n\n#[scalar(name = \"time_get_yearday\")]\nfn time_get_yearday(args: &[Value]) -> Value {\n    if args.len() != 1 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    let blob = ok_tri!(args[0].to_blob(), \"parameter should be a time blob\");\n\n    let t = tri!(Time::try_from(blob));\n\n    t.time_get(TimeField::YearDay)\n}\n\n#[scalar(name = \"time_get_isoyear\")]\nfn time_get_isoyear(args: &[Value]) -> Value {\n    if args.len() != 1 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    let blob = ok_tri!(args[0].to_blob(), \"parameter should be a time blob\");\n\n    let t = tri!(Time::try_from(blob));\n\n    t.time_get(TimeField::IsoYear)\n}\n\n#[scalar(name = \"time_get_isoweek\")]\nfn time_get_isoweek(args: &[Value]) -> Value {\n    if args.len() != 1 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    let blob = ok_tri!(args[0].to_blob(), \"parameter should be a time blob\");\n\n    let t = tri!(Time::try_from(blob));\n\n    t.time_get(TimeField::IsoWeek)\n}\n\nfn time_unix_internal(args: &[Value]) -> Value {\n    if args.len() != 1 && args.len() != 2 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    for arg in args {\n        value_tri!(\n            arg.value_type(),\n            ValueType::Integer,\n            \"all parameters should be integers\"\n        );\n    }\n\n    let seconds = ok_tri!(args[0].to_integer());\n\n    let mut nano_sec = 0;\n\n    if args.len() == 2 {\n        nano_sec = ok_tri!(args[1].to_integer());\n    }\n\n    let dt = ok_tri!(DateTime::from_timestamp(seconds, nano_sec as u32));\n\n    let t = Time::from_datetime(dt);\n\n    t.into_blob()\n}\n\n#[scalar(name = \"time_unix\")]\nfn time_unix(args: &[Value]) -> Value {\n    time_unix_internal(args)\n}\n\n#[scalar(name = \"to_timestamp\")]\nfn to_timestamp(args: &[Value]) -> Value {\n    if args.len() != 1 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    time_unix_internal(args)\n}\n\n#[scalar(name = \"time_milli\")]\nfn time_milli(args: &[Value]) -> Value {\n    if args.len() != 1 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    value_tri!(\n        &args[0].value_type(),\n        ValueType::Integer,\n        \"parameter should be an integer\"\n    );\n\n    let millis = ok_tri!(args[0].to_integer());\n\n    let dt = ok_tri!(DateTime::from_timestamp_millis(millis));\n\n    let t = Time::from_datetime(dt);\n\n    t.into_blob()\n}\n\n#[scalar(name = \"time_micro\")]\nfn time_micro(args: &[Value]) -> Value {\n    if args.len() != 1 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    value_tri!(\n        &args[0].value_type(),\n        ValueType::Integer,\n        \"parameter should be an integer\"\n    );\n\n    let micros = ok_tri!(args[0].to_integer());\n\n    let dt = ok_tri!(DateTime::from_timestamp_micros(micros));\n\n    let t = Time::from_datetime(dt);\n\n    t.into_blob()\n}\n\n#[scalar(name = \"time_nano\")]\nfn time_nano(args: &[Value]) -> Value {\n    if args.len() != 1 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    value_tri!(\n        &args[0].value_type(),\n        ValueType::Integer,\n        \"parameter should be an integer\"\n    );\n\n    let nanos = ok_tri!(args[0].to_integer());\n\n    let dt = DateTime::from_timestamp_nanos(nanos);\n\n    let t = Time::from_datetime(dt);\n\n    t.into_blob()\n}\n\n#[scalar(name = \"time_to_unix\")]\nfn time_to_unix(args: &[Value]) -> Value {\n    if args.len() != 1 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    let blob = ok_tri!(args[0].to_blob(), \"parameter should be a time blob\");\n\n    let t = tri!(Time::try_from(blob));\n\n    Value::from_integer(t.to_unix())\n}\n\n#[scalar(name = \"time_to_milli\")]\nfn time_to_milli(args: &[Value]) -> Value {\n    if args.len() != 1 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    let blob = ok_tri!(args[0].to_blob(), \"parameter should be a time blob\");\n\n    let t = tri!(Time::try_from(blob));\n\n    Value::from_integer(t.to_unix_milli())\n}\n\n#[scalar(name = \"time_to_micro\")]\nfn time_to_micro(args: &[Value]) -> Value {\n    if args.len() != 1 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    let blob = ok_tri!(args[0].to_blob(), \"parameter should be a time blob\");\n\n    let t = tri!(Time::try_from(blob));\n\n    Value::from_integer(t.to_unix_micro())\n}\n\n#[scalar(name = \"time_to_nano\")]\nfn time_to_nano(args: &[Value]) -> Value {\n    if args.len() != 1 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    let blob = ok_tri!(args[0].to_blob(), \"parameter should be a time blob\");\n\n    let t = tri!(Time::try_from(blob));\n\n    Value::from_integer(ok_tri!(t.to_unix_nano()))\n}\n\n// Comparisons\n\n#[scalar(name = \"time_after\")]\nfn time_after(args: &[Value]) -> Value {\n    if args.len() != 2 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    let blob = ok_tri!(args[0].to_blob(), \"1st parameter: should be a time blob\");\n\n    let t = tri!(Time::try_from(blob));\n\n    let blob = ok_tri!(args[1].to_blob(), \"2nd parameter: should be a time blob\");\n\n    let u = tri!(Time::try_from(blob));\n\n    Value::from_integer((t > u).into())\n}\n\n#[scalar(name = \"time_before\")]\nfn time_before(args: &[Value]) -> Value {\n    if args.len() != 2 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    let blob = ok_tri!(args[0].to_blob(), \"1st parameter: should be a time blob\");\n\n    let t = tri!(Time::try_from(blob));\n\n    let blob = ok_tri!(args[1].to_blob(), \"2nd parameter: should be a time blob\");\n\n    let u = tri!(Time::try_from(blob));\n\n    Value::from_integer((t < u).into())\n}\n\n#[scalar(name = \"time_compare\")]\nfn time_compare(args: &[Value]) -> Value {\n    if args.len() != 2 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    let blob = ok_tri!(args[0].to_blob(), \"1st parameter: should be a time blob\");\n\n    let t = tri!(Time::try_from(blob));\n\n    let blob = ok_tri!(args[1].to_blob(), \"2nd parameter: should be a time blob\");\n\n    let u = tri!(Time::try_from(blob));\n\n    let cmp = match ok_tri!(t.partial_cmp(&u)) {\n        Ordering::Less => -1,\n        Ordering::Greater => 1,\n        Ordering::Equal => 0,\n    };\n\n    Value::from_integer(cmp)\n}\n\n#[scalar(name = \"time_equal\")]\nfn time_equal(args: &[Value]) -> Value {\n    if args.len() != 2 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    let blob = ok_tri!(args[0].to_blob(), \"1st parameter: should be a time blob\");\n\n    let t = tri!(Time::try_from(blob));\n\n    let blob = ok_tri!(args[1].to_blob(), \"2nd parameter: should be a time blob\");\n\n    let u = tri!(Time::try_from(blob));\n\n    Value::from_integer(t.eq(&u).into())\n}\n\n// Duration Constants\n\n/// 1 nanosecond\n#[scalar(name = \"dur_ns\")]\nfn dur_ns(args: &[Value]) -> Value {\n    if !args.is_empty() {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    Value::from_integer(chrono::Duration::nanoseconds(1).num_nanoseconds().unwrap())\n}\n\n/// 1 microsecond\n#[scalar(name = \"dur_us\")]\nfn dur_us(args: &[Value]) -> Value {\n    if !args.is_empty() {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    Value::from_integer(chrono::Duration::microseconds(1).num_nanoseconds().unwrap())\n}\n\n/// 1 millisecond\n#[scalar(name = \"dur_ms\")]\nfn dur_ms(args: &[Value]) -> Value {\n    if !args.is_empty() {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    Value::from_integer(chrono::Duration::milliseconds(1).num_nanoseconds().unwrap())\n}\n\n/// 1 second\n#[scalar(name = \"dur_s\")]\nfn dur_s(args: &[Value]) -> Value {\n    if !args.is_empty() {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    Value::from_integer(chrono::Duration::seconds(1).num_nanoseconds().unwrap())\n}\n\n/// 1 minute\n#[scalar(name = \"dur_m\")]\nfn dur_m(args: &[Value]) -> Value {\n    if !args.is_empty() {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    Value::from_integer(chrono::Duration::minutes(1).num_nanoseconds().unwrap())\n}\n\n/// 1 hour\n#[scalar(name = \"dur_h\")]\nfn dur_h(args: &[Value]) -> Value {\n    if !args.is_empty() {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    Value::from_integer(chrono::Duration::hours(1).num_nanoseconds().unwrap())\n}\n\n// Time Arithmetic\n\n/// Do not use `time_add` to add days, months or years. Use `time_add_date` instead.\n#[scalar(name = \"time_add\", alias = \"date_add\")]\nfn time_add(args: &[Value]) -> Value {\n    if args.len() != 2 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    let blob = ok_tri!(args[0].to_blob(), \"1st parameter: should be a time blob\");\n    let t = tri!(Time::try_from(blob));\n\n    value_tri!(\n        args[1].value_type(),\n        ValueType::Integer,\n        \"2nd parameter: should be an integer\"\n    );\n\n    let d = ok_tri!(args[1].to_integer());\n\n    let d = Duration::from(d);\n\n    t.add_duration(d).into_blob()\n}\n\n#[scalar(name = \"time_add_date\")]\nfn time_add_date(args: &[Value]) -> Value {\n    if args.len() != 2 && args.len() != 3 && args.len() != 4 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    let blob = ok_tri!(args[0].to_blob(), \"1st parameter: should be a time blob\");\n    let t = tri!(Time::try_from(blob));\n\n    value_tri!(\n        args[1].value_type(),\n        ValueType::Integer,\n        \"2nd parameter: should be an integer\"\n    );\n\n    let years = ok_tri!(args[1].to_integer());\n    let mut months = 0;\n    let mut days = 0;\n\n    if args.len() >= 3 {\n        value_tri!(\n            args[2].value_type(),\n            ValueType::Integer,\n            \"3rd parameter: should be an integer\"\n        );\n\n        months = ok_tri!(args[2].to_integer());\n    }\n\n    if args.len() == 4 {\n        value_tri!(\n            args[3].value_type(),\n            ValueType::Integer,\n            \"4th parameter: should be an integer\"\n        );\n\n        days = ok_tri!(args[3].to_integer());\n    }\n\n    let t: Time = tri!(t.time_add_date(years as i32, months as i32, days));\n\n    t.into_blob()\n}\n\n/// Returns the duration between two time values t and u (in nanoseconds).\n/// If the result exceeds the maximum (or minimum) value that can be stored in a Duration,\n/// the maximum (or minimum) duration will be returned.\nfn time_sub_internal(t: Time, u: Time) -> Value {\n    let cmp = ok_tri!(t.partial_cmp(&u));\n\n    let diff = t - u;\n\n    let nano_secs = match diff.num_nanoseconds() {\n        Some(nano) => nano,\n        None => match cmp {\n            Ordering::Equal => ok_tri!(diff.num_nanoseconds()),\n            Ordering::Less => i64::MIN,\n            Ordering::Greater => i64::MAX,\n        },\n    };\n\n    Value::from_integer(nano_secs)\n}\n\n#[scalar(name = \"time_sub\", alias = \"age\")]\nfn time_sub(args: &[Value]) -> Value {\n    if args.len() != 2 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    let blob = ok_tri!(args[0].to_blob(), \"1st parameter: should be a time blob\");\n    let t = tri!(Time::try_from(blob));\n\n    let blob = ok_tri!(args[1].to_blob(), \"2nd parameter: should be a time blob\");\n    let u = tri!(Time::try_from(blob));\n\n    time_sub_internal(t, u)\n}\n\n#[scalar(name = \"time_since\")]\nfn time_since(args: &[Value]) -> Value {\n    if args.len() != 1 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    let now = Time::new();\n\n    let blob = ok_tri!(args[0].to_blob(), \"parameter should be a time blob\");\n    let t = tri!(Time::try_from(blob));\n\n    time_sub_internal(now, t)\n}\n\n#[scalar(name = \"time_until\")]\nfn time_until(args: &[Value]) -> Value {\n    if args.len() != 1 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    let now = Time::new();\n\n    let blob = ok_tri!(args[0].to_blob(), \"parameter should be a time blob\");\n    let t = tri!(Time::try_from(blob));\n\n    time_sub_internal(t, now)\n}\n\n// Rounding\n\n#[scalar(name = \"time_trunc\", alias = \"date_trunc\")]\nfn time_trunc(args: &[Value]) -> Value {\n    if args.len() != 2 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    let blob = ok_tri!(args[0].to_blob(), \"1st parameter: should be a time blob\");\n\n    let t = tri!(Time::try_from(blob));\n\n    match args[1].value_type() {\n        ValueType::Text => {\n            let field = ok_tri!(args[1].to_text());\n\n            let field = tri!(TimeRoundField::from_str(field));\n\n            match tri!(t.trunc_field(field)) {\n                Some(t) => t.into_blob(),\n                None => Value::null(),\n            }\n        }\n        ValueType::Integer => {\n            let duration = ok_tri!(args[1].to_integer());\n            let duration = Duration::from(duration);\n\n            tri!(t.trunc_duration(duration)).into_blob()\n        }\n        _ => Value::error_with_message(\"2nd parameter: should be a field name\".to_string()),\n    }\n}\n\n#[scalar(name = \"time_round\")]\nfn time_round(args: &[Value]) -> Value {\n    if args.len() != 2 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    let blob = ok_tri!(args[0].to_blob(), \"1st parameter: should be a time blob\");\n\n    let t = tri!(Time::try_from(blob));\n\n    value_tri!(\n        args[1].value_type(),\n        ValueType::Integer,\n        \"2nd parameter: should be an integer\"\n    );\n\n    let duration = ok_tri!(args[1].to_integer());\n    let duration = Duration::from(duration);\n\n    tri!(t.round_duration(duration)).into_blob()\n}\n\n// Formatting\n\n#[scalar(name = \"time_fmt_iso\")]\nfn time_fmt_iso(args: &[Value]) -> Value {\n    if args.len() != 1 && args.len() != 2 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n    let blob = ok_tri!(args[0].to_blob(), \"1st parameter: should be a time blob\");\n\n    let t = tri!(Time::try_from(blob));\n\n    let offset_sec = {\n        if args.len() == 2 {\n            value_tri!(\n                args[1].value_type(),\n                ValueType::Integer,\n                \"2nd parameter: should be an integer\"\n            );\n            ok_tri!(args[1].to_integer()) as i32\n        } else {\n            0\n        }\n    };\n\n    let fmt_str = tri!(t.fmt_iso(offset_sec));\n\n    Value::from_text(fmt_str)\n}\n\n#[scalar(name = \"time_fmt_datetime\")]\nfn time_fmt_datetime(args: &[Value]) -> Value {\n    if args.len() != 1 && args.len() != 2 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n    let blob = ok_tri!(args[0].to_blob(), \"1st parameter: should be a time blob\");\n\n    let t = tri!(Time::try_from(blob));\n\n    let offset_sec = {\n        if args.len() == 2 {\n            value_tri!(\n                args[1].value_type(),\n                ValueType::Integer,\n                \"2nd parameter: should be an integer\"\n            );\n            ok_tri!(args[1].to_integer()) as i32\n        } else {\n            0\n        }\n    };\n\n    let fmt_str = tri!(t.fmt_datetime(offset_sec));\n\n    Value::from_text(fmt_str)\n}\n\n#[scalar(name = \"time_fmt_date\")]\nfn time_fmt_date(args: &[Value]) -> Value {\n    if args.len() != 1 && args.len() != 2 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n    let blob = ok_tri!(args[0].to_blob(), \"1st parameter: should be a time blob\");\n\n    let t = tri!(Time::try_from(blob));\n\n    let offset_sec = {\n        if args.len() == 2 {\n            value_tri!(\n                args[1].value_type(),\n                ValueType::Integer,\n                \"2nd parameter: should be an integer\"\n            );\n            ok_tri!(args[1].to_integer()) as i32\n        } else {\n            0\n        }\n    };\n\n    let fmt_str = tri!(t.fmt_date(offset_sec));\n\n    Value::from_text(fmt_str)\n}\n\n#[scalar(name = \"time_fmt_time\")]\nfn time_fmt_time(args: &[Value]) -> Value {\n    if args.len() != 1 && args.len() != 2 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n    let blob = ok_tri!(args[0].to_blob(), \"1st parameter: should be a time blob\");\n\n    let t = tri!(Time::try_from(blob));\n\n    let offset_sec = {\n        if args.len() == 2 {\n            value_tri!(\n                args[1].value_type(),\n                ValueType::Integer,\n                \"2nd parameter: should be an integer\"\n            );\n            ok_tri!(args[1].to_integer()) as i32\n        } else {\n            0\n        }\n    };\n\n    let fmt_str = tri!(t.fmt_time(offset_sec));\n\n    Value::from_text(fmt_str)\n}\n\n#[scalar(name = \"time_parse\")]\nfn time_parse(args: &[Value]) -> Value {\n    if args.len() != 1 {\n        return Value::error(ResultCode::InvalidArgs);\n    }\n\n    let dt_str = ok_tri!(args[0].to_text());\n\n    if let Ok(dt) = chrono::DateTime::parse_from_rfc3339(dt_str) {\n        return Time::from_datetime(dt.to_utc()).into_blob();\n    }\n\n    if let Ok(mut dt) = chrono::NaiveDateTime::parse_from_str(dt_str, \"%Y-%m-%d %H:%M:%S\") {\n        // Unwrap is safe here\n        dt = dt.with_nanosecond(0).unwrap();\n        return Time::from_datetime(dt.and_utc()).into_blob();\n    }\n\n    if let Ok(date) = chrono::NaiveDate::parse_from_str(dt_str, \"%Y-%m-%d\") {\n        // Unwrap is safe here\n\n        let dt = date\n            .and_hms_opt(0, 0, 0)\n            .unwrap()\n            .with_nanosecond(0)\n            .unwrap();\n        return Time::from_datetime(dt.and_utc()).into_blob();\n    }\n\n    let time = tri!(\n        chrono::NaiveTime::parse_from_str(dt_str, \"%H:%M:%S\"),\n        \"error parsing datetime string\"\n    );\n    let dt = NaiveDateTime::new(NaiveDate::from_ymd_opt(1, 1, 1).unwrap(), time)\n        .with_nanosecond(0)\n        .unwrap();\n\n    Time::from_datetime(dt.and_utc()).into_blob()\n}\n"
  },
  {
    "path": "core/translate/aggregation.rs",
    "content": "use turso_parser::ast;\n\nuse crate::{\n    function::AggFunc,\n    schema::Table,\n    translate::collate::CollationSeq,\n    vdbe::{\n        builder::ProgramBuilder,\n        insn::{HashDistinctData, Insn},\n    },\n    LimboError, Result,\n};\n\nuse super::{\n    emitter::{Resolver, TranslateCtx},\n    expr::{\n        resolve_expr, translate_condition_expr, translate_expr, translate_expr_no_constant_opt,\n        ConditionMetadata, NoConstantOptReason,\n    },\n    plan::{Aggregate, Distinctness, SelectPlan, TableReferences},\n    result_row::emit_select_result,\n};\n\n/// Emits the bytecode for processing an aggregate without a GROUP BY clause.\n/// This is called when the main query execution loop has finished processing,\n/// and we can now materialize the aggregate results.\npub fn emit_ungrouped_aggregation<'a>(\n    program: &mut ProgramBuilder,\n    t_ctx: &mut TranslateCtx<'a>,\n    plan: &'a SelectPlan,\n) -> Result<()> {\n    let agg_start_reg = t_ctx.reg_agg_start.unwrap();\n\n    for (i, agg) in plan.aggregates.iter().enumerate() {\n        let agg_result_reg = agg_start_reg + i;\n        program.emit_insn(Insn::AggFinal {\n            register: agg_result_reg,\n            func: agg.func.clone(),\n        });\n    }\n    // we now have the agg results in (agg_start_reg..agg_start_reg + aggregates.len() - 1)\n    // we need to call translate_expr on each result column, but replace the expr with a register copy in case any part of the\n    // result column expression matches a) a group by column or b) an aggregation result.\n    for (i, agg) in plan.aggregates.iter().enumerate() {\n        t_ctx.resolver.cache_expr_reg(\n            std::borrow::Cow::Borrowed(&agg.original_expr),\n            agg_start_reg + i,\n            false,\n            None,\n        );\n    }\n    t_ctx.resolver.enable_expr_to_reg_cache();\n\n    // Allocate a label for the end (used by both HAVING and OFFSET to skip row emission)\n    let end_label = program.allocate_label();\n\n    // Handle HAVING clause without GROUP BY for ungrouped aggregation\n    if let Some(group_by) = &plan.group_by {\n        if group_by.exprs.is_empty() {\n            if let Some(having) = &group_by.having {\n                for expr in having.iter() {\n                    let if_true_target = program.allocate_label();\n                    translate_condition_expr(\n                        program,\n                        &plan.table_references,\n                        expr,\n                        ConditionMetadata {\n                            jump_if_condition_is_true: false,\n                            jump_target_when_false: end_label,\n                            jump_target_when_true: if_true_target,\n                            // treat null result as false\n                            jump_target_when_null: end_label,\n                        },\n                        &t_ctx.resolver,\n                    )?;\n                    program.preassign_label_to_next_insn(if_true_target);\n                }\n            }\n        }\n    }\n\n    // Handle OFFSET for ungrouped aggregates\n    // Since we only have one result row, either skip it (offset > 0) or emit it\n    if let Some(offset_reg) = t_ctx.reg_offset {\n        // If offset > 0, jump to end (skip the single row)\n        program.emit_insn(Insn::IfPos {\n            reg: offset_reg,\n            target_pc: end_label,\n            decrement_by: 0,\n        });\n    }\n\n    // If the loop never ran (once-flag is still 0), we need to evaluate non-aggregate columns now.\n    // This ensures literals return their values and column references return NULL (since cursor\n    // is not on a valid row). The once-flag mechanism normally evaluates non-agg columns on first\n    // iteration, but if there were no iterations, we must do it here.\n    //\n    // For direct table access, Column on an invalid cursor returns NULL naturally. But for\n    // coroutine-based sources (CTEs, FROM-clause subqueries), the output registers still hold\n    // the last yielded row's values. We null those registers first so that expressions\n    // referencing coroutine columns evaluate to NULL instead of leaking stale values.\n    if let Some(once_flag) = t_ctx.reg_nonagg_emit_once_flag {\n        let skip_nonagg_eval = program.allocate_label();\n        // If once-flag is non-zero (loop ran at least once), skip evaluation\n        program.emit_insn(Insn::If {\n            reg: once_flag,\n            target_pc: skip_nonagg_eval,\n            jump_if_null: false,\n        });\n        // Null out coroutine output registers so column references from CTEs/subqueries\n        // don't leak stale values from the last yielded row.\n        for table_ref in plan.table_references.joined_tables() {\n            if let Table::FromClauseSubquery(subquery) = &table_ref.table {\n                if let Some(start_reg) = subquery.result_columns_start_reg {\n                    let num_cols = subquery.columns.len();\n                    if num_cols > 0 {\n                        program.emit_insn(Insn::Null {\n                            dest: start_reg,\n                            dest_end: if num_cols > 1 {\n                                Some(start_reg + num_cols - 1)\n                            } else {\n                                None\n                            },\n                        });\n                    }\n                }\n            }\n        }\n        // Evaluate non-aggregate columns now (with cursor in invalid state, columns return NULL)\n        // Must use no_constant_opt to prevent constant hoisting which would place the label\n        // after the hoisted constants, causing infinite loops in compound selects.\n        let col_start = t_ctx.reg_result_cols_start.unwrap();\n        for (i, rc) in plan.result_columns.iter().enumerate() {\n            if !rc.contains_aggregates {\n                translate_expr_no_constant_opt(\n                    program,\n                    Some(&plan.table_references),\n                    &rc.expr,\n                    col_start + i,\n                    &t_ctx.resolver,\n                    NoConstantOptReason::RegisterReuse,\n                )?;\n            }\n        }\n        program.preassign_label_to_next_insn(skip_nonagg_eval);\n    }\n\n    // Emit the result row (if we didn't skip it due to HAVING or OFFSET)\n    emit_select_result(\n        program,\n        &t_ctx.resolver,\n        plan,\n        None,\n        None,\n        t_ctx.reg_nonagg_emit_once_flag,\n        None, // we've already handled offset\n        t_ctx.reg_result_cols_start.unwrap(),\n        t_ctx.limit_ctx,\n    )?;\n\n    // Resolve the SELECT DISTINCT label if present\n    // When a duplicate is found by the Found instruction, jump here to skip emitting the row\n    if let Distinctness::Distinct { ctx } = &plan.distinctness {\n        let distinct_ctx = ctx.as_ref().expect(\"distinct context must exist\");\n        program.preassign_label_to_next_insn(distinct_ctx.label_on_conflict);\n    }\n\n    program.resolve_label(end_label, program.offset());\n\n    Ok(())\n}\n\npub(crate) fn emit_collseq_if_needed(\n    program: &mut ProgramBuilder,\n    referenced_tables: &TableReferences,\n    expr: &ast::Expr,\n) {\n    // Check if this is a column expression with explicit COLLATE clause\n    if let ast::Expr::Collate(_, collation_name) = expr {\n        if let Ok(collation) = CollationSeq::new(collation_name.as_str()) {\n            program.emit_insn(Insn::CollSeq {\n                reg: None,\n                collation,\n            });\n        }\n        return;\n    }\n\n    // If no explicit collation, check if this is a column with table-defined collation\n    if let ast::Expr::Column { table, column, .. } = expr {\n        if let Some((_, table_ref)) = referenced_tables.find_table_by_internal_id(*table) {\n            if let Some(table_column) = table_ref.get_column_at(*column) {\n                if let Some(c) = table_column.collation_opt() {\n                    program.emit_insn(Insn::CollSeq {\n                        reg: None,\n                        collation: c,\n                    });\n                    return;\n                }\n            }\n        }\n    }\n\n    // Always emit a CollSeq to reset to BINARY default, preventing collation\n    // from a previous aggregate leaking into this one.\n    program.emit_insn(Insn::CollSeq {\n        reg: None,\n        collation: CollationSeq::Binary,\n    });\n}\n\n/// Emits the bytecode for handling duplicates in a distinct aggregate.\n/// This is used in both GROUP BY and non-GROUP BY aggregations to jump over\n/// the AggStep that would otherwise accumulate the same value multiple times.\npub fn handle_distinct(\n    program: &mut ProgramBuilder,\n    distinctness: &Distinctness,\n    agg_arg_reg: usize,\n) {\n    let Distinctness::Distinct { ctx } = distinctness else {\n        return;\n    };\n    let distinct_ctx = ctx\n        .as_ref()\n        .expect(\"distinct aggregate context not populated\");\n    let num_regs = 1;\n    program.emit_insn(Insn::HashDistinct {\n        data: Box::new(HashDistinctData {\n            hash_table_id: distinct_ctx.hash_table_id,\n            key_start_reg: agg_arg_reg,\n            num_keys: num_regs,\n            collations: distinct_ctx.collations.clone(),\n            target_pc: distinct_ctx.label_on_conflict,\n        }),\n    });\n}\n\n/// Source of aggregate function arguments during bytecode emission.\n///\n/// * `Register`: arguments were pre-computed into contiguous registers\n///   (used for GROUP BY without a sorter, where the main loop is already sorted).\n/// * `Expression`: arguments are evaluated on-the-fly from the original AST\n///   (used for ungrouped aggregates, window functions, and for the GROUP BY sorter\n///   path where leaf columns are cached in `expr_to_reg_cache` before evaluation).\npub enum AggArgumentSource<'a> {\n    Register {\n        src_reg_start: usize,\n        aggregate: &'a Aggregate,\n    },\n    Expression {\n        func: &'a AggFunc,\n        args: &'a Vec<ast::Expr>,\n        distinctness: &'a Distinctness,\n    },\n}\n\nimpl<'a> AggArgumentSource<'a> {\n    pub fn new_from_registers(src_reg_start: usize, aggregate: &'a Aggregate) -> Self {\n        Self::Register {\n            src_reg_start,\n            aggregate,\n        }\n    }\n\n    pub fn new_from_expression(\n        func: &'a AggFunc,\n        args: &'a Vec<ast::Expr>,\n        distinctness: &'a Distinctness,\n    ) -> Self {\n        Self::Expression {\n            func,\n            args,\n            distinctness,\n        }\n    }\n\n    pub fn distinctness(&self) -> &Distinctness {\n        match self {\n            AggArgumentSource::Register { aggregate, .. } => &aggregate.distinctness,\n            AggArgumentSource::Expression { distinctness, .. } => distinctness,\n        }\n    }\n\n    pub fn agg_func(&self) -> &AggFunc {\n        match self {\n            AggArgumentSource::Register { aggregate, .. } => &aggregate.func,\n            AggArgumentSource::Expression { func, .. } => func,\n        }\n    }\n\n    pub fn arg_at(&self, idx: usize) -> &ast::Expr {\n        match self {\n            AggArgumentSource::Register { aggregate, .. } => &aggregate.args[idx],\n            AggArgumentSource::Expression { args, .. } => &args[idx],\n        }\n    }\n\n    pub fn num_args(&self) -> usize {\n        match self {\n            AggArgumentSource::Register { aggregate, .. } => aggregate.args.len(),\n            AggArgumentSource::Expression { args, .. } => args.len(),\n        }\n    }\n\n    /// Emit bytecode to read an aggregate function argument into a register.\n    pub fn translate(\n        &self,\n        program: &mut ProgramBuilder,\n        referenced_tables: &TableReferences,\n        resolver: &Resolver,\n        arg_idx: usize,\n    ) -> Result<usize> {\n        match self {\n            AggArgumentSource::Register {\n                src_reg_start: start_reg,\n                ..\n            } => Ok(*start_reg + arg_idx),\n            AggArgumentSource::Expression { args, .. } => {\n                resolve_expr(program, Some(referenced_tables), &args[arg_idx], resolver)\n            }\n        }\n    }\n}\n\n/// Emits the bytecode for processing an aggregate step.\n///\n/// This is distinct from the final step, which is called after a single group has been entirely accumulated,\n/// and the actual result value of the aggregation is materialized.\n///\n/// Ungrouped aggregation is a special case of grouped aggregation that involves a single group.\n///\n/// Examples:\n/// * In `SELECT SUM(price) FROM t`, `price` is evaluated for each row and added to the accumulator.\n/// * In `SELECT product_category, SUM(price) FROM t GROUP BY product_category`, `price` is evaluated for\n///   each row in the group and added to that group’s accumulator.\npub fn translate_aggregation_step(\n    program: &mut ProgramBuilder,\n    referenced_tables: &TableReferences,\n    agg_arg_source: AggArgumentSource,\n    target_register: usize,\n    resolver: &Resolver,\n) -> Result<usize> {\n    let num_args = agg_arg_source.num_args();\n    let func = agg_arg_source.agg_func();\n    let dest = match func {\n        AggFunc::Avg => {\n            if num_args != 1 {\n                crate::bail_parse_error!(\"avg bad number of arguments\");\n            }\n            let expr_reg = agg_arg_source.translate(program, referenced_tables, resolver, 0)?;\n            handle_distinct(program, agg_arg_source.distinctness(), expr_reg);\n            program.emit_insn(Insn::AggStep {\n                acc_reg: target_register,\n                col: expr_reg,\n                delimiter: 0,\n                func: AggFunc::Avg,\n                comparator: None,\n            });\n            target_register\n        }\n        AggFunc::Count0 => {\n            let expr = ast::Expr::Literal(ast::Literal::Numeric(\"1\".to_string()));\n            let expr_reg = translate_const_arg(program, referenced_tables, resolver, &expr)?;\n            handle_distinct(program, agg_arg_source.distinctness(), expr_reg);\n            program.emit_insn(Insn::AggStep {\n                acc_reg: target_register,\n                col: expr_reg,\n                delimiter: 0,\n                func: AggFunc::Count0,\n                comparator: None,\n            });\n            target_register\n        }\n        AggFunc::Count => {\n            if num_args != 1 {\n                crate::bail_parse_error!(\"count bad number of arguments\");\n            }\n            let expr_reg = agg_arg_source.translate(program, referenced_tables, resolver, 0)?;\n            handle_distinct(program, agg_arg_source.distinctness(), expr_reg);\n            program.emit_insn(Insn::AggStep {\n                acc_reg: target_register,\n                col: expr_reg,\n                delimiter: 0,\n                func: AggFunc::Count,\n                comparator: None,\n            });\n            target_register\n        }\n        AggFunc::GroupConcat => {\n            if num_args != 1 && num_args != 2 {\n                crate::bail_parse_error!(\"group_concat bad number of arguments\");\n            }\n\n            let delimiter_reg = if num_args == 2 {\n                agg_arg_source.translate(program, referenced_tables, resolver, 1)?\n            } else {\n                let delimiter_expr =\n                    ast::Expr::Literal(ast::Literal::String(String::from(\"\\\",\\\"\")));\n                translate_const_arg(program, referenced_tables, resolver, &delimiter_expr)?\n            };\n\n            let expr_reg = agg_arg_source.translate(program, referenced_tables, resolver, 0)?;\n            handle_distinct(program, agg_arg_source.distinctness(), expr_reg);\n\n            program.emit_insn(Insn::AggStep {\n                acc_reg: target_register,\n                col: expr_reg,\n                delimiter: delimiter_reg,\n                func: AggFunc::GroupConcat,\n                comparator: None,\n            });\n\n            target_register\n        }\n        AggFunc::Max => {\n            if num_args != 1 {\n                crate::bail_parse_error!(\"max bad number of arguments\");\n            }\n            let expr_reg = agg_arg_source.translate(program, referenced_tables, resolver, 0)?;\n            handle_distinct(program, agg_arg_source.distinctness(), expr_reg);\n            let expr = &agg_arg_source.arg_at(0);\n            emit_collseq_if_needed(program, referenced_tables, expr);\n            let comparator =\n                super::order_by::custom_type_comparator(expr, referenced_tables, resolver.schema());\n            program.emit_insn(Insn::AggStep {\n                acc_reg: target_register,\n                col: expr_reg,\n                delimiter: 0,\n                func: AggFunc::Max,\n                comparator,\n            });\n            target_register\n        }\n        AggFunc::Min => {\n            if num_args != 1 {\n                crate::bail_parse_error!(\"min bad number of arguments\");\n            }\n            let expr_reg = agg_arg_source.translate(program, referenced_tables, resolver, 0)?;\n            handle_distinct(program, agg_arg_source.distinctness(), expr_reg);\n            let expr = &agg_arg_source.arg_at(0);\n            emit_collseq_if_needed(program, referenced_tables, expr);\n            let comparator =\n                super::order_by::custom_type_comparator(expr, referenced_tables, resolver.schema());\n            program.emit_insn(Insn::AggStep {\n                acc_reg: target_register,\n                col: expr_reg,\n                delimiter: 0,\n                func: AggFunc::Min,\n                comparator,\n            });\n            target_register\n        }\n        #[cfg(feature = \"json\")]\n        AggFunc::JsonGroupObject | AggFunc::JsonbGroupObject => {\n            if num_args != 2 {\n                crate::bail_parse_error!(\"max bad number of arguments\");\n            }\n            let expr_reg = agg_arg_source.translate(program, referenced_tables, resolver, 0)?;\n            handle_distinct(program, agg_arg_source.distinctness(), expr_reg);\n            let value_reg = agg_arg_source.translate(program, referenced_tables, resolver, 1)?;\n\n            program.emit_insn(Insn::AggStep {\n                acc_reg: target_register,\n                col: expr_reg,\n                delimiter: value_reg,\n                func: AggFunc::JsonGroupObject,\n                comparator: None,\n            });\n            target_register\n        }\n        #[cfg(feature = \"json\")]\n        AggFunc::JsonGroupArray | AggFunc::JsonbGroupArray => {\n            if num_args != 1 {\n                crate::bail_parse_error!(\"max bad number of arguments\");\n            }\n            let expr_reg = agg_arg_source.translate(program, referenced_tables, resolver, 0)?;\n            handle_distinct(program, agg_arg_source.distinctness(), expr_reg);\n            program.emit_insn(Insn::AggStep {\n                acc_reg: target_register,\n                col: expr_reg,\n                delimiter: 0,\n                func: AggFunc::JsonGroupArray,\n                comparator: None,\n            });\n            target_register\n        }\n        AggFunc::StringAgg => {\n            if num_args != 2 {\n                crate::bail_parse_error!(\"string_agg bad number of arguments\");\n            }\n\n            let expr_reg = agg_arg_source.translate(program, referenced_tables, resolver, 0)?;\n            let delimiter_reg =\n                agg_arg_source.translate(program, referenced_tables, resolver, 1)?;\n\n            program.emit_insn(Insn::AggStep {\n                acc_reg: target_register,\n                col: expr_reg,\n                delimiter: delimiter_reg,\n                func: AggFunc::StringAgg,\n                comparator: None,\n            });\n\n            target_register\n        }\n        AggFunc::Sum => {\n            if num_args != 1 {\n                crate::bail_parse_error!(\"sum bad number of arguments\");\n            }\n            let expr_reg = agg_arg_source.translate(program, referenced_tables, resolver, 0)?;\n            handle_distinct(program, agg_arg_source.distinctness(), expr_reg);\n            program.emit_insn(Insn::AggStep {\n                acc_reg: target_register,\n                col: expr_reg,\n                delimiter: 0,\n                func: AggFunc::Sum,\n                comparator: None,\n            });\n            target_register\n        }\n        AggFunc::Total => {\n            if num_args != 1 {\n                crate::bail_parse_error!(\"total bad number of arguments\");\n            }\n            let expr_reg = agg_arg_source.translate(program, referenced_tables, resolver, 0)?;\n            handle_distinct(program, agg_arg_source.distinctness(), expr_reg);\n            program.emit_insn(Insn::AggStep {\n                acc_reg: target_register,\n                col: expr_reg,\n                delimiter: 0,\n                func: AggFunc::Total,\n                comparator: None,\n            });\n            target_register\n        }\n        AggFunc::ArrayAgg => {\n            resolver.require_custom_types(\"Array features\")?;\n            if num_args != 1 {\n                crate::bail_parse_error!(\"array_agg bad number of arguments\");\n            }\n            let expr_reg = agg_arg_source.translate(program, referenced_tables, resolver, 0)?;\n            handle_distinct(program, agg_arg_source.distinctness(), expr_reg);\n            program.emit_insn(Insn::AggStep {\n                acc_reg: target_register,\n                col: expr_reg,\n                delimiter: 0,\n                func: AggFunc::ArrayAgg,\n                comparator: None,\n            });\n            target_register\n        }\n        AggFunc::External(ref func) => {\n            let argc = func.agg_args().map_err(|_| {\n                LimboError::ExtensionError(\n                    \"External aggregate function called with wrong number of arguments\".to_string(),\n                )\n            })?;\n            if argc != num_args {\n                crate::bail_parse_error!(\n                    \"External aggregate function called with wrong number of arguments\"\n                );\n            }\n            let expr_reg = agg_arg_source.translate(program, referenced_tables, resolver, 0)?;\n            for i in 0..argc {\n                if i != 0 {\n                    let _ = agg_arg_source.translate(program, referenced_tables, resolver, i)?;\n                }\n                // invariant: distinct aggregates are only supported for single-argument functions\n                if argc == 1 {\n                    handle_distinct(program, agg_arg_source.distinctness(), expr_reg + i);\n                }\n            }\n            program.emit_insn(Insn::AggStep {\n                acc_reg: target_register,\n                col: expr_reg,\n                delimiter: 0,\n                func: AggFunc::External(func.clone()),\n                comparator: None,\n            });\n            target_register\n        }\n    };\n    // Aggregate arguments can carry column or explicit COLLATE metadata for the\n    // aggregate's internal comparator, but that state must not leak to the\n    // surrounding expression that consumes the aggregate result.\n    program.reset_collation();\n    Ok(dest)\n}\n\nfn translate_const_arg(\n    program: &mut ProgramBuilder,\n    referenced_tables: &TableReferences,\n    resolver: &Resolver,\n    expr: &ast::Expr,\n) -> Result<usize> {\n    let target_register = program.alloc_register();\n    translate_expr(\n        program,\n        Some(referenced_tables),\n        expr,\n        target_register,\n        resolver,\n    )\n}\n"
  },
  {
    "path": "core/translate/alter.rs",
    "content": "use crate::sync::Arc;\nuse crate::{schema::BTreeTable, turso_assert_eq, turso_assert_ne};\nuse turso_parser::{\n    ast::{self, TableInternalId},\n    parser::Parser,\n};\n\nuse crate::{\n    error::SQLITE_CONSTRAINT_CHECK,\n    function::{AlterTableFunc, Func},\n    schema::{CheckConstraint, Column, ForeignKey, Table, RESERVED_TABLE_PREFIXES},\n    translate::{\n        emitter::Resolver,\n        expr::{translate_expr, walk_expr, walk_expr_mut, WalkControl},\n        plan::{ColumnUsedMask, OuterQueryReference, TableReferences},\n    },\n    util::{\n        check_expr_references_column, escape_sql_string_literal, normalize_ident,\n        parse_numeric_literal, rewrite_view_sql_for_column_rename,\n    },\n    vdbe::{\n        affinity::Affinity,\n        builder::{CursorType, ProgramBuilder},\n        insn::{to_u16, CmpInsFlags, Cookie, Insn, RegisterOrLiteral},\n    },\n    vtab::VirtualTable,\n    LimboError, Numeric, Result, Value,\n};\nuse either::Either;\n\nuse super::{\n    schema::{validate_check_expr, SQLITE_TABLEID},\n    update::translate_update_for_schema_change,\n};\n\nfn validate(alter_table: &ast::AlterTableBody, table_name: &str) -> Result<()> {\n    // Check if someone is trying to ALTER a system table\n    if crate::schema::is_system_table(table_name) {\n        crate::bail_parse_error!(\"table {} may not be modified\", table_name);\n    }\n    if let ast::AlterTableBody::RenameTo(new_table_name) = alter_table {\n        let normalized_new_name = normalize_ident(new_table_name.as_str());\n        if RESERVED_TABLE_PREFIXES\n            .iter()\n            .any(|prefix| normalized_new_name.starts_with(prefix))\n        {\n            crate::bail_parse_error!(\"Object name reserved for internal use: {}\", new_table_name);\n        }\n    }\n\n    Ok(())\n}\n\n/// Check if an expression is a valid \"constant\" default for ALTER TABLE ADD COLUMN.\n/// SQLite is very strict here - it only allows:\n/// - Literals (numbers, strings, blobs, NULL, CURRENT_TIME/DATE/TIMESTAMP)\n/// - Bare identifiers (treated as string literals, e.g., `DEFAULT hello` → \"hello\")\n/// - Signed literals (+5, -5, +NULL, -NULL)\n/// - Parenthesized versions of the above\n///\n/// It does NOT allow:\n/// - Binary operations like (5 + 3)\n/// - Function calls like COALESCE(NULL, 5)\n/// - Comparisons, CASE expressions, CAST, etc.\n///\n/// Note: CURRENT_TIME/DATE/TIMESTAMP are allowed here but will be rejected at\n/// runtime if the table has existing rows (see `default_requires_empty_table`).\nfn is_strict_constant_default(expr: &ast::Expr) -> bool {\n    match expr {\n        ast::Expr::Literal(_) => true,\n        // Bare identifiers are treated as string literals in DEFAULT clause\n        ast::Expr::Id(_) => true,\n        ast::Expr::Unary(ast::UnaryOperator::Positive | ast::UnaryOperator::Negative, inner) => {\n            // Only allow unary +/- on literals\n            matches!(inner.as_ref(), ast::Expr::Literal(_))\n        }\n        ast::Expr::Parenthesized(exprs) => {\n            // Parenthesized expression with a single inner expression\n            exprs.len() == 1 && is_strict_constant_default(&exprs[0])\n        }\n        _ => false,\n    }\n}\n\n/// Check if a default expression requires the table to be empty (non-deterministic defaults).\n/// CURRENT_TIME, CURRENT_DATE, CURRENT_TIMESTAMP cannot be used to backfill existing rows.\nfn default_requires_empty_table(expr: &ast::Expr) -> bool {\n    match expr {\n        ast::Expr::Literal(lit) => matches!(\n            lit,\n            ast::Literal::CurrentDate | ast::Literal::CurrentTime | ast::Literal::CurrentTimestamp\n        ),\n        ast::Expr::Parenthesized(exprs) => {\n            exprs.len() == 1 && default_requires_empty_table(&exprs[0])\n        }\n        _ => false,\n    }\n}\n\nfn emit_rename_sqlite_sequence_entry(\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    connection: &Arc<crate::Connection>,\n    database_id: usize,\n    old_table_name_norm: &str,\n    new_table_name_norm: &str,\n) {\n    let Some(sqlite_sequence) = resolver.with_schema(database_id, |s| {\n        s.get_btree_table(crate::schema::SQLITE_SEQUENCE_TABLE_NAME)\n    }) else {\n        return;\n    };\n\n    let seq_cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(sqlite_sequence.clone()));\n    let sequence_name_reg = program.alloc_register();\n    let sequence_value_reg = program.alloc_register();\n    let row_name_to_replace_reg = program.emit_string8_new_reg(old_table_name_norm.to_string());\n    program.mark_last_insn_constant();\n    let replacement_row_name_reg = program.emit_string8_new_reg(new_table_name_norm.to_string());\n    program.mark_last_insn_constant();\n\n    let affinity_str = sqlite_sequence\n        .columns\n        .iter()\n        .map(|col| col.affinity().aff_mask())\n        .collect::<String>();\n\n    program.emit_insn(Insn::OpenWrite {\n        cursor_id: seq_cursor_id,\n        root_page: RegisterOrLiteral::Literal(sqlite_sequence.root_page),\n        db: database_id,\n    });\n\n    program.cursor_loop(seq_cursor_id, |program, rowid| {\n        program.emit_column_or_rowid(seq_cursor_id, 0, sequence_name_reg);\n\n        let continue_loop_label = program.allocate_label();\n        program.emit_insn(Insn::Ne {\n            lhs: sequence_name_reg,\n            rhs: row_name_to_replace_reg,\n            target_pc: continue_loop_label,\n            flags: CmpInsFlags::default(),\n            collation: None,\n        });\n\n        program.emit_column_or_rowid(seq_cursor_id, 1, sequence_value_reg);\n\n        let record_start_reg = program.alloc_registers(2);\n        let record_reg = program.alloc_register();\n\n        program.emit_insn(Insn::Copy {\n            src_reg: replacement_row_name_reg,\n            dst_reg: record_start_reg,\n            extra_amount: 0,\n        });\n        program.emit_insn(Insn::Copy {\n            src_reg: sequence_value_reg,\n            dst_reg: record_start_reg + 1,\n            extra_amount: 0,\n        });\n        program.emit_insn(Insn::MakeRecord {\n            start_reg: to_u16(record_start_reg),\n            count: to_u16(2),\n            dest_reg: to_u16(record_reg),\n            index_name: None,\n            affinity_str: Some(affinity_str.clone()),\n        });\n\n        // In MVCC mode, we need to delete before insert to properly\n        // end the old version (Hekaton-style UPDATE = DELETE + INSERT)\n        if connection.mvcc_enabled() {\n            program.emit_insn(Insn::Delete {\n                cursor_id: seq_cursor_id,\n                table_name: crate::schema::SQLITE_SEQUENCE_TABLE_NAME.to_string(),\n                is_part_of_update: true,\n            });\n        }\n\n        program.emit_insn(Insn::Insert {\n            cursor: seq_cursor_id,\n            key_reg: rowid,\n            record_reg,\n            flag: crate::vdbe::insn::InsertFlags(0),\n            table_name: crate::schema::SQLITE_SEQUENCE_TABLE_NAME.to_string(),\n        });\n\n        program.resolve_label(continue_loop_label, program.offset());\n    });\n}\n\nfn literal_default_value(literal: &ast::Literal) -> Result<Value> {\n    match literal {\n        ast::Literal::Numeric(val) => parse_numeric_literal(val),\n        ast::Literal::String(s) => Ok(Value::from_text(crate::translate::expr::sanitize_string(s))),\n        ast::Literal::Blob(s) => Ok(Value::Blob(\n            s.as_bytes()\n                .chunks_exact(2)\n                .map(|pair| {\n                    let hex_byte = std::str::from_utf8(pair).expect(\"parser validated hex string\");\n                    u8::from_str_radix(hex_byte, 16).expect(\"parser validated hex digit\")\n                })\n                .collect(),\n        )),\n        ast::Literal::Null => Ok(Value::Null),\n        ast::Literal::True => Ok(Value::from_i64(1)),\n        ast::Literal::False => Ok(Value::from_i64(0)),\n        ast::Literal::CurrentDate => Ok(Value::from_text(\"CURRENT_DATE\")),\n        ast::Literal::CurrentTime => Ok(Value::from_text(\"CURRENT_TIME\")),\n        ast::Literal::CurrentTimestamp => Ok(Value::from_text(\"CURRENT_TIMESTAMP\")),\n        ast::Literal::Keyword(_) => Err(LimboError::ParseError(\n            \"Cannot add a column with non-constant default\".to_string(),\n        )),\n    }\n}\n\npub(crate) fn eval_constant_default_value(expr: &ast::Expr) -> Result<Value> {\n    match expr {\n        ast::Expr::Literal(literal) => literal_default_value(literal),\n        ast::Expr::Id(name) => Ok(Value::from_text(name.as_str().to_string())),\n        ast::Expr::Unary(op, inner) => {\n            let value = eval_constant_default_value(inner)?;\n            match (op, value) {\n                (ast::UnaryOperator::Positive, value) => Ok(value),\n                (ast::UnaryOperator::Negative, Value::Numeric(Numeric::Integer(i))) => {\n                    Ok(Value::from_i64(-i))\n                }\n                (ast::UnaryOperator::Negative, Value::Numeric(Numeric::Float(f))) => {\n                    Ok(Value::from_f64(-f64::from(f)))\n                }\n                (ast::UnaryOperator::Negative, Value::Null) => Ok(Value::Null),\n                (_, value) => Ok(value),\n            }\n        }\n        ast::Expr::Parenthesized(exprs) => {\n            if exprs.len() == 1 {\n                eval_constant_default_value(&exprs[0])\n            } else {\n                Err(LimboError::ParseError(\n                    \"Cannot add a column with non-constant default\".to_string(),\n                ))\n            }\n        }\n        _ => Err(LimboError::ParseError(\n            \"Cannot add a column with non-constant default\".to_string(),\n        )),\n    }\n}\n\nfn apply_affinity_to_value(value: &mut Value, affinity: Affinity) {\n    if let Some(converted) = affinity.convert(value) {\n        *value = match converted {\n            Either::Left(val_ref) => val_ref.to_owned(),\n            Either::Right(val) => val,\n        };\n    }\n}\n\nfn strict_default_type_mismatch(column: &Column) -> Result<bool> {\n    let Some(default_expr) = column.default.as_ref() else {\n        return Ok(false);\n    };\n\n    let mut value = eval_constant_default_value(default_expr)?;\n    if matches!(value, Value::Null) {\n        return Ok(false);\n    }\n\n    apply_affinity_to_value(&mut value, column.affinity());\n\n    let ty = column.ty_str.as_str();\n    if ty.eq_ignore_ascii_case(\"ANY\") {\n        return Ok(false);\n    };\n\n    let ok = if ty.eq_ignore_ascii_case(\"INT\") || ty.eq_ignore_ascii_case(\"INTEGER\") {\n        match value {\n            Value::Numeric(Numeric::Integer(_)) => true,\n            Value::Numeric(Numeric::Float(f)) => {\n                let f = f64::from(f);\n                let i = f as i64;\n                (i as f64) == f\n            }\n            _ => false,\n        }\n    } else if ty.eq_ignore_ascii_case(\"REAL\") {\n        matches!(value, Value::Numeric(Numeric::Float(_)))\n    } else if ty.eq_ignore_ascii_case(\"TEXT\") {\n        matches!(value, Value::Text(_))\n    } else if ty.eq_ignore_ascii_case(\"BLOB\") {\n        matches!(value, Value::Blob(_))\n    } else {\n        true\n    };\n\n    Ok(!ok)\n}\n\nfn emit_add_column_default_type_validation(\n    program: &mut ProgramBuilder,\n    original_btree: &Arc<BTreeTable>,\n) -> Result<()> {\n    let check_cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(original_btree.clone()));\n    program.emit_insn(Insn::OpenRead {\n        cursor_id: check_cursor_id,\n        root_page: original_btree.root_page,\n        db: crate::MAIN_DB_ID,\n    });\n\n    let skip_check_label = program.allocate_label();\n    program.emit_insn(Insn::Rewind {\n        cursor_id: check_cursor_id,\n        pc_if_empty: skip_check_label,\n    });\n\n    program.emit_insn(Insn::Halt {\n        err_code: 1,\n        description: \"type mismatch on DEFAULT\".to_string(),\n        on_error: None,\n        description_reg: None,\n    });\n\n    program.resolve_label(skip_check_label, program.offset());\n    Ok(())\n}\n\n/// Validate CHECK constraints on a newly added column against the column's DEFAULT value.\n///\n/// When a table has existing rows, the new column gets the DEFAULT value (or NULL).\n/// If that value would violate a CHECK constraint, the ALTER TABLE must be rejected.\n/// This emits bytecode that:\n/// 1. Checks if the table has any rows (Rewind)\n/// 2. Evaluates the CHECK expression with the column reference substituted by the DEFAULT\n/// 3. Halts with a CHECK constraint error if the result is false\n#[allow(clippy::too_many_arguments)]\nfn emit_add_column_check_validation(\n    program: &mut ProgramBuilder,\n    btree: &BTreeTable,\n    original_btree: &Arc<BTreeTable>,\n    new_column_name: &str,\n    column: &Column,\n    constraints: &[ast::NamedColumnConstraint],\n    resolver: &Resolver,\n    database_id: usize,\n) -> Result<()> {\n    // Determine the effective default value. If no DEFAULT, existing rows get NULL,\n    // which always passes CHECK per SQL standard (NULL is not false).\n    let default_expr = match &column.default {\n        Some(expr) if !crate::util::expr_contains_null(expr) => *expr.clone(),\n        _ => return Ok(()),\n    };\n\n    // Collect CHECK constraints from the column constraints being added.\n    let check_exprs: Vec<(&Option<ast::Name>, &Box<ast::Expr>)> = constraints\n        .iter()\n        .filter_map(|c| {\n            if let ast::ColumnConstraint::Check(expr) = &c.constraint {\n                Some((&c.name, expr))\n            } else {\n                None\n            }\n        })\n        .collect();\n\n    if check_exprs.is_empty() {\n        return Ok(());\n    }\n\n    let table_name = &btree.name;\n    let col_name_lower = normalize_ident(new_column_name);\n\n    // Open the table to check if it has rows.\n    let check_cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(original_btree.clone()));\n    program.emit_insn(Insn::OpenRead {\n        cursor_id: check_cursor_id,\n        root_page: original_btree.root_page,\n        db: database_id,\n    });\n\n    let skip_check_label = program.allocate_label();\n    program.emit_insn(Insn::Rewind {\n        cursor_id: check_cursor_id,\n        pc_if_empty: skip_check_label,\n    });\n\n    // Table has rows -- evaluate each CHECK constraint with the default value substituted.\n    for (constraint_name, check_expr) in &check_exprs {\n        let mut substituted = (*check_expr).clone();\n\n        // Replace references to the new column with the default value expression.\n        let _ = walk_expr_mut(\n            &mut substituted,\n            &mut |e: &mut ast::Expr| -> Result<WalkControl> {\n                match e {\n                    ast::Expr::Id(name) if normalize_ident(name.as_str()) == col_name_lower => {\n                        *e = default_expr.clone();\n                        Ok(WalkControl::SkipChildren)\n                    }\n                    ast::Expr::Qualified(tbl, col)\n                        if normalize_ident(tbl.as_str()) == normalize_ident(table_name)\n                            && normalize_ident(col.as_str()) == col_name_lower =>\n                    {\n                        *e = default_expr.clone();\n                        Ok(WalkControl::SkipChildren)\n                    }\n                    _ => Ok(WalkControl::Continue),\n                }\n            },\n        );\n\n        let result_reg = program.alloc_register();\n        translate_expr(program, None, &substituted, result_reg, resolver)?;\n\n        // CHECK passes if the result is NULL or non-zero (truthy).\n        let check_passed_label = program.allocate_label();\n\n        program.emit_insn(Insn::IsNull {\n            reg: result_reg,\n            target_pc: check_passed_label,\n        });\n\n        program.emit_insn(Insn::If {\n            reg: result_reg,\n            target_pc: check_passed_label,\n            jump_if_null: false,\n        });\n\n        // CHECK failed -- halt with constraint error.\n        let name = match constraint_name {\n            Some(name) => name.as_str().to_string(),\n            None => format!(\"{check_expr}\"),\n        };\n        program.emit_insn(Insn::Halt {\n            err_code: SQLITE_CONSTRAINT_CHECK,\n            description: name,\n            on_error: None,\n            description_reg: None,\n        });\n\n        program.preassign_label_to_next_insn(check_passed_label);\n    }\n\n    program.resolve_label(skip_check_label, program.offset());\n    Ok(())\n}\n\npub fn translate_alter_table(\n    alter: ast::AlterTable,\n    resolver: &Resolver,\n    program: &mut ProgramBuilder,\n    connection: &Arc<crate::Connection>,\n    input: &str,\n) -> Result<()> {\n    let ast::AlterTable {\n        name: qualified_name,\n        body: alter_table,\n    } = alter;\n    let database_id = resolver.resolve_database_id(&qualified_name)?;\n    if crate::is_attached_db(database_id) {\n        let schema_cookie = resolver.with_schema(database_id, |s| s.schema_version);\n        program.begin_write_on_database(database_id, schema_cookie);\n    }\n    program.begin_write_operation();\n    let table_name = qualified_name.name.as_str();\n    // For attached databases, qualify sqlite_schema with the database name\n    // so that the UPDATE targets the correct database's schema table.\n    let qualified_schema_table = match &qualified_name.db_name {\n        Some(db_name) => format!(\"{}.{}\", db_name.as_str(), SQLITE_TABLEID),\n        None => SQLITE_TABLEID.to_string(),\n    };\n    let schema_version = resolver.with_schema(database_id, |s| s.schema_version);\n    validate(&alter_table, table_name)?;\n\n    let table_indexes = resolver.with_schema(database_id, |s| {\n        s.get_indices(table_name).cloned().collect::<Vec<_>>()\n    });\n\n    let Some(table) = resolver.with_schema(database_id, |s| s.get_table(table_name)) else {\n        return Err(LimboError::ParseError(format!(\n            \"no such table: {table_name}\"\n        )));\n    };\n    if let Some(tbl) = table.virtual_table() {\n        if let ast::AlterTableBody::RenameTo(new_name) = &alter_table {\n            let new_name_norm = normalize_ident(new_name.as_str());\n            return translate_rename_virtual_table(\n                program,\n                tbl,\n                table_name,\n                new_name_norm,\n                resolver,\n                connection,\n                database_id,\n            );\n        }\n    }\n    let Some(original_btree) = table.btree() else {\n        crate::bail_parse_error!(\"ALTER TABLE is only supported for BTree tables\");\n    };\n\n    // Check if this table has dependent materialized views\n    let dependent_views = resolver.with_schema(database_id, |s| {\n        s.get_dependent_materialized_views(table_name)\n    });\n    if !dependent_views.is_empty() {\n        return Err(LimboError::ParseError(format!(\n            \"cannot alter table \\\"{table_name}\\\": it has dependent materialized view(s): {}\",\n            dependent_views.join(\", \")\n        )));\n    }\n\n    let mut btree = (*original_btree).clone();\n\n    match alter_table {\n        ast::AlterTableBody::DropColumn(column_name) => {\n            let column_name = column_name.as_str();\n\n            // Tables always have at least one column.\n            turso_assert_ne!(btree.columns.len(), 0);\n\n            if btree.columns.len() == 1 {\n                return Err(LimboError::ParseError(format!(\n                    \"cannot drop column \\\"{column_name}\\\": no other columns exist\"\n                )));\n            }\n\n            let (dropped_index, column) = btree.get_column(column_name).ok_or_else(|| {\n                LimboError::ParseError(format!(\"no such column: \\\"{column_name}\\\"\"))\n            })?;\n\n            // Column cannot be dropped if:\n            // The column is a PRIMARY KEY or part of one.\n            // The column has a UNIQUE constraint.\n            // The column is indexed.\n            // The column is referenced in an expression index.\n            // The column is named in the WHERE clause of a partial index.\n            // The column is named in a table or column CHECK constraint not associated with the column being dropped.\n            // The column is used in a foreign key constraint.\n            // The column is used in the expression of a generated column.\n            // The column appears in a trigger or view.\n\n            if column.primary_key() {\n                return Err(LimboError::ParseError(format!(\n                    \"cannot drop column \\\"{column_name}\\\": PRIMARY KEY\"\n                )));\n            }\n\n            if column.unique()\n                || btree.unique_sets.iter().any(|set| {\n                    set.columns\n                        .iter()\n                        .any(|(name, _)| name == &normalize_ident(column_name))\n                })\n            {\n                return Err(LimboError::ParseError(format!(\n                    \"cannot drop column \\\"{column_name}\\\": UNIQUE\"\n                )));\n            }\n\n            let col_normalized = normalize_ident(column_name);\n            for index in table_indexes.iter() {\n                // Referenced in regular index\n                let maybe_indexed_col = index\n                    .columns\n                    .iter()\n                    .enumerate()\n                    .find(|(_, col)| col.pos_in_table == dropped_index);\n                if let Some((pos_in_index, indexed_col)) = maybe_indexed_col {\n                    return Err(LimboError::ParseError(format!(\n                        \"cannot drop column \\\"{column_name}\\\": it is referenced in the index {}; position in index is {pos_in_index}, position in table is {}\",\n                        index.name, indexed_col.pos_in_table\n                    )));\n                }\n                // Referenced in expression index\n                for idx_col in &index.columns {\n                    if let Some(expr) = &idx_col.expr {\n                        if check_expr_references_column(expr, &col_normalized) {\n                            return Err(LimboError::ParseError(format!(\n                                \"error in index {} after drop column: no such column: {column_name}\",\n                                index.name\n                            )));\n                        }\n                    }\n                }\n                // Referenced in partial index\n                if index.where_clause.is_some() {\n                    let mut table_references = TableReferences::new(\n                        vec![],\n                        vec![OuterQueryReference {\n                            identifier: table_name.to_string(),\n                            internal_id: TableInternalId::from(0),\n                            table: Table::BTree(Arc::new(btree.clone())),\n                            col_used_mask: ColumnUsedMask::default(),\n                            cte_select: None,\n                            cte_explicit_columns: vec![],\n                            cte_id: None,\n                            cte_definition_only: false,\n                            rowid_referenced: false,\n                        }],\n                    );\n                    let where_copy = index\n                        .bind_where_expr(Some(&mut table_references), resolver)\n                        .ok_or_else(|| {\n                            LimboError::ParseError(\n                                \"index where clause unexpectedly missing\".to_string(),\n                            )\n                        })?;\n                    let mut column_referenced = false;\n                    walk_expr(\n                        &where_copy,\n                        &mut |e: &ast::Expr| -> crate::Result<WalkControl> {\n                            if let ast::Expr::Column {\n                                table,\n                                column: column_index,\n                                ..\n                            } = e\n                            {\n                                if *table == TableInternalId::from(0)\n                                    && *column_index == dropped_index\n                                {\n                                    column_referenced = true;\n                                    return Ok(WalkControl::SkipChildren);\n                                }\n                            }\n                            Ok(WalkControl::Continue)\n                        },\n                    )?;\n                    if column_referenced {\n                        return Err(LimboError::ParseError(format!(\n                            \"cannot drop column \\\"{column_name}\\\": indexed\"\n                        )));\n                    }\n                }\n            }\n\n            // Handle CHECK constraints:\n            // - Column-level CHECK constraints for the dropped column are silently removed\n            // - Table-level CHECK constraints referencing the dropped column cause an error\n            for check in &btree.check_constraints {\n                if check.column.is_some() {\n                    // Column-level constraint: will be removed below\n                    continue;\n                }\n                // Table-level constraint: check if it references the dropped column\n                if check_expr_references_column(&check.expr, &col_normalized) {\n                    return Err(LimboError::ParseError(format!(\n                        \"error in table {table_name} after drop column: no such column: {column_name}\"\n                    )));\n                }\n            }\n            // Remove column-level CHECK constraints for the dropped column\n            btree.check_constraints.retain(|c| {\n                c.column\n                    .as_ref()\n                    .is_none_or(|col| normalize_ident(col) != normalize_ident(column_name))\n            });\n\n            // Check if column is used in a foreign key constraint (child side)\n            // SQLite does not allow dropping a column that is part of a FK constraint\n            let column_name_norm = normalize_ident(column_name);\n            for fk in &btree.foreign_keys {\n                if fk.child_columns.contains(&column_name_norm) {\n                    return Err(LimboError::ParseError(format!(\n                        \"error in table {table_name} after drop column: unknown column \\\"{column_name}\\\" in foreign key definition\"\n                    )));\n                }\n            }\n\n            // TODO: check usage in generated column when implemented\n\n            // References in VIEWs are checked in the VDBE layer op_drop_column instruction.\n\n            // Like SQLite, re-validate ALL triggers after the drop. Any trigger whose\n            // body references a nonexistent column (in the altered table or any other\n            // table) causes the DROP to fail. This catches cross-table references to\n            // the dropped column as well as pre-existing errors that would surface at\n            // trigger execution time.\n            let post_drop_btree = {\n                let mut t = btree.clone();\n                t.columns.remove(dropped_index);\n                t\n            };\n            let all_triggers: Vec<_> = resolver.with_schema(database_id, |s| {\n                s.triggers.values().flatten().cloned().collect()\n            });\n            let table_name_norm = normalize_ident(table_name);\n            for trigger in &all_triggers {\n                if let Some(bad_col) = validate_trigger_columns_after_drop(\n                    trigger,\n                    &table_name_norm,\n                    &post_drop_btree,\n                    resolver,\n                    database_id,\n                )? {\n                    return Err(LimboError::ParseError(format!(\n                        \"error in trigger {} after drop column: no such column: {}\",\n                        trigger.name, bad_col\n                    )));\n                }\n            }\n\n            btree.columns.remove(dropped_index);\n\n            let sql = btree.to_sql().replace('\\'', \"''\");\n\n            let escaped_table_name = escape_sql_string_literal(table_name);\n            let stmt = format!(\n                r#\"\n                    UPDATE {qualified_schema_table}\n                    SET sql = '{sql}'\n                    WHERE name = '{escaped_table_name}' COLLATE NOCASE AND type = 'table'\n                \"#,\n            );\n\n            let mut parser = Parser::new(stmt.as_bytes());\n            let cmd = parser.next_cmd().map_err(|e| {\n                LimboError::ParseError(format!(\"failed to parse generated UPDATE statement: {e}\"))\n            })?;\n            let Some(ast::Cmd::Stmt(ast::Stmt::Update(update))) = cmd else {\n                return Err(LimboError::ParseError(\n                    \"generated UPDATE statement did not parse as expected\".to_string(),\n                ));\n            };\n\n            translate_update_for_schema_change(\n                update,\n                resolver,\n                program,\n                connection,\n                input,\n                |program| {\n                    let column_count = btree.columns.len();\n                    let root_page = btree.root_page;\n                    let table_name = btree.name.clone();\n\n                    let cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(original_btree));\n\n                    program.emit_insn(Insn::OpenWrite {\n                        cursor_id,\n                        root_page: RegisterOrLiteral::Literal(root_page),\n                        db: database_id,\n                    });\n\n                    program.cursor_loop(cursor_id, |program, rowid| {\n                        let first_column = program.alloc_registers(column_count);\n\n                        let mut iter = first_column;\n\n                        for i in 0..(column_count + 1) {\n                            if i == dropped_index {\n                                continue;\n                            }\n\n                            program.emit_column_or_rowid(cursor_id, i, iter);\n\n                            iter += 1;\n                        }\n\n                        let record = program.alloc_register();\n\n                        let affinity_str = btree\n                            .columns\n                            .iter()\n                            .map(|col| col.affinity_with_strict(btree.is_strict).aff_mask())\n                            .collect::<String>();\n\n                        program.emit_insn(Insn::MakeRecord {\n                            start_reg: to_u16(first_column),\n                            count: to_u16(column_count),\n                            dest_reg: to_u16(record),\n                            index_name: None,\n                            affinity_str: Some(affinity_str),\n                        });\n\n                        // In MVCC mode, we need to delete before insert to properly\n                        // end the old version (Hekaton-style UPDATE = DELETE + INSERT)\n                        if connection.mvcc_enabled() {\n                            program.emit_insn(Insn::Delete {\n                                cursor_id,\n                                table_name: table_name.clone(),\n                                is_part_of_update: true,\n                            });\n                        }\n\n                        program.emit_insn(Insn::Insert {\n                            cursor: cursor_id,\n                            key_reg: rowid,\n                            record_reg: record,\n                            flag: crate::vdbe::insn::InsertFlags(0),\n                            table_name: table_name.clone(),\n                        });\n                    });\n\n                    program.emit_insn(Insn::SetCookie {\n                        db: database_id,\n                        cookie: Cookie::SchemaVersion,\n                        value: schema_version as i32 + 1,\n                        p5: 0,\n                    });\n\n                    program.emit_insn(Insn::DropColumn {\n                        db: database_id,\n                        table: table_name,\n                        column_index: dropped_index,\n                    })\n                },\n            )?\n        }\n        ast::AlterTableBody::AddColumn(col_def) => {\n            if col_def\n                .constraints\n                .iter()\n                .any(|c| matches!(c.constraint, ast::ColumnConstraint::Generated { .. }))\n            {\n                return Err(LimboError::ParseError(\n                    \"Alter table does not support adding generated columns\".to_string(),\n                ));\n            }\n            let constraints = col_def.constraints.clone();\n            let mut column = Column::try_from(&col_def)?;\n\n            // SQLite is very strict about what constitutes a \"constant\" default for\n            // ALTER TABLE ADD COLUMN. It only allows literals and signed literals,\n            // not arbitrary constant expressions like (5 + 3) or COALESCE(NULL, 5).\n            if column\n                .default\n                .as_ref()\n                .is_some_and(|default| !is_strict_constant_default(default))\n            {\n                return Err(LimboError::ParseError(\n                    \"Cannot add a column with non-constant default\".to_string(),\n                ));\n            }\n\n            let new_column_name = column.name.as_ref().ok_or_else(|| {\n                LimboError::ParseError(\n                    \"column name is missing in ALTER TABLE ADD COLUMN\".to_string(),\n                )\n            })?;\n            if btree.get_column(new_column_name).is_some() {\n                return Err(LimboError::ParseError(\n                    \"duplicate column name: \".to_string() + new_column_name,\n                ));\n            }\n\n            let default_type_mismatch;\n            {\n                let ty = column.ty_str.as_str();\n                if btree.is_strict && ty.is_empty() {\n                    return Err(LimboError::ParseError(format!(\n                        \"missing datatype for {table_name}.{new_column_name}\"\n                    )));\n                }\n                let is_builtin = ty.is_empty()\n                    || ty.eq_ignore_ascii_case(\"INT\")\n                    || ty.eq_ignore_ascii_case(\"INTEGER\")\n                    || ty.eq_ignore_ascii_case(\"REAL\")\n                    || ty.eq_ignore_ascii_case(\"TEXT\")\n                    || ty.eq_ignore_ascii_case(\"BLOB\")\n                    || ty.eq_ignore_ascii_case(\"ANY\");\n                if !is_builtin && btree.is_strict {\n                    // On non-STRICT tables any type name is allowed and is\n                    // treated as a plain affinity hint (no encode/decode).\n                    // Custom type validation only applies to STRICT tables.\n                    let type_def = resolver\n                        .schema()\n                        .get_type_def_unchecked(&normalize_ident(ty));\n                    if type_def.is_none() {\n                        return Err(LimboError::ParseError(format!(\n                            \"unknown datatype for {table_name}.{new_column_name}: \\\"{ty}\\\"\"\n                        )));\n                    }\n                }\n\n                default_type_mismatch = strict_default_type_mismatch(&column)?;\n            }\n\n            // If a column has no explicit DEFAULT but its custom type defines\n            // one, propagate the type-level DEFAULT to the column so that\n            // existing rows get the type default instead of NULL.\n            if column.default.is_none() {\n                if let Some(type_def) = resolver\n                    .schema()\n                    .get_type_def(&column.ty_str, btree.is_strict)\n                {\n                    if let Some(ref type_default) = type_def.default {\n                        column.default = Some(type_default.clone());\n                    }\n                }\n            }\n\n            // TODO: All quoted ids will be quoted with `[]`, we should store some info from the parsed AST\n            btree.columns.push(column.clone());\n\n            // Add foreign key constraints and CHECK constraints to the btree table\n            for constraint in &constraints {\n                match &constraint.constraint {\n                    ast::ColumnConstraint::ForeignKey {\n                        clause,\n                        defer_clause,\n                    } => {\n                        if clause.columns.len() > 1 {\n                            return Err(LimboError::ParseError(format!(\n                                \"foreign key on {new_column_name} should reference only one column of table {}\",\n                                clause.tbl_name.as_str()\n                            )));\n                        }\n                        let fk = ForeignKey {\n                            parent_table: normalize_ident(clause.tbl_name.as_str()),\n                            parent_columns: clause\n                                .columns\n                                .iter()\n                                .map(|c| normalize_ident(c.col_name.as_str()))\n                                .collect(),\n                            on_delete: clause\n                                .args\n                                .iter()\n                                .find_map(|arg| {\n                                    if let ast::RefArg::OnDelete(act) = arg {\n                                        Some(*act)\n                                    } else {\n                                        None\n                                    }\n                                })\n                                .unwrap_or(ast::RefAct::NoAction),\n                            on_update: clause\n                                .args\n                                .iter()\n                                .find_map(|arg| {\n                                    if let ast::RefArg::OnUpdate(act) = arg {\n                                        Some(*act)\n                                    } else {\n                                        None\n                                    }\n                                })\n                                .unwrap_or(ast::RefAct::NoAction),\n                            child_columns: vec![new_column_name.to_string()],\n                            deferred: match defer_clause {\n                                Some(d) => {\n                                    d.deferrable\n                                        && matches!(\n                                            d.init_deferred,\n                                            Some(ast::InitDeferredPred::InitiallyDeferred)\n                                        )\n                                }\n                                None => false,\n                            },\n                        };\n                        btree.foreign_keys.push(Arc::new(fk));\n                    }\n                    ast::ColumnConstraint::Check(expr) => {\n                        let column_names: Vec<&str> = btree\n                            .columns\n                            .iter()\n                            .filter_map(|c| c.name.as_deref())\n                            .collect();\n                        validate_check_expr(expr, &btree.name, &column_names, resolver)?;\n                        btree.check_constraints.push(CheckConstraint::new(\n                            constraint.name.as_ref(),\n                            expr,\n                            Some(new_column_name),\n                        ));\n                    }\n                    _ => {\n                        // Other constraints (PRIMARY KEY, NOT NULL, etc.) are handled elsewhere\n                    }\n                }\n            }\n\n            let sql = btree.to_sql();\n            let mut escaped = String::with_capacity(sql.len());\n\n            for ch in sql.chars() {\n                match ch {\n                    '\\'' => escaped.push_str(\"''\"),\n                    ch => escaped.push(ch),\n                }\n            }\n\n            let escaped_table_name = escape_sql_string_literal(table_name);\n            let stmt = format!(\n                r#\"\n                    UPDATE {qualified_schema_table}\n                    SET sql = '{escaped}'\n                    WHERE name = '{escaped_table_name}' COLLATE NOCASE AND type = 'table'\n                \"#,\n            );\n\n            let mut parser = Parser::new(stmt.as_bytes());\n            let cmd = parser.next_cmd().map_err(|e| {\n                LimboError::ParseError(format!(\"failed to parse generated UPDATE statement: {e}\"))\n            })?;\n            let Some(ast::Cmd::Stmt(ast::Stmt::Update(update))) = cmd else {\n                return Err(LimboError::ParseError(\n                    \"generated UPDATE statement did not parse as expected\".to_string(),\n                ));\n            };\n\n            // Check if we need to verify the table is empty at runtime.\n            // This is required for:\n            // 1. NOT NULL columns without a non-null default (existing rows would get NULL)\n            // 2. Non-deterministic defaults like CURRENT_TIME (can't backfill existing rows)\n            // 3. CHECK constraints on the new column. SQLite evaluates the CHECK against\n            //    all existing rows via pragma_quick_check. We take a stricter approach and\n            //    reject the ALTER if the table has any rows, since we don't yet support\n            //    scanning existing data for constraint validation.\n            // Check if the column has an effective default (column-level or type-level).\n            let effective_default = column.default.as_ref().or_else(|| {\n                resolver\n                    .schema()\n                    .get_type_def(&column.ty_str, btree.is_strict)\n                    .and_then(|td| td.default.as_ref())\n            });\n            let needs_notnull_check = column.notnull()\n                && effective_default.is_none_or(|default| crate::util::expr_contains_null(default));\n\n            let needs_nondeterministic_check = column\n                .default\n                .as_ref()\n                .is_some_and(|default| default_requires_empty_table(default));\n\n            let (needs_empty_table_check, error_message) =\n                if needs_notnull_check && needs_nondeterministic_check {\n                    // Both conditions - use NOT NULL message (more specific)\n                    (true, \"Cannot add a NOT NULL column with default value NULL\")\n                } else if needs_notnull_check {\n                    (true, \"Cannot add a NOT NULL column with default value NULL\")\n                } else if needs_nondeterministic_check {\n                    (true, \"Cannot add a column with non-constant default\")\n                } else {\n                    (false, \"\")\n                };\n\n            if needs_empty_table_check {\n                // Emit bytecode to check if the table has any rows.\n                let check_cursor_id =\n                    program.alloc_cursor_id(CursorType::BTreeTable(original_btree.clone()));\n                program.emit_insn(Insn::OpenRead {\n                    cursor_id: check_cursor_id,\n                    root_page: original_btree.root_page,\n                    db: database_id,\n                });\n\n                let skip_error_label = program.allocate_label();\n                program.emit_insn(Insn::Rewind {\n                    cursor_id: check_cursor_id,\n                    pc_if_empty: skip_error_label,\n                });\n\n                // Table has rows - emit error\n                program.emit_insn(Insn::Halt {\n                    err_code: 1,\n                    description: error_message.to_string(),\n                    on_error: None,\n                    description_reg: None,\n                });\n\n                program.resolve_label(skip_error_label, program.offset());\n            }\n\n            if default_type_mismatch {\n                emit_add_column_default_type_validation(program, &original_btree)?;\n            }\n\n            // Validate CHECK constraints against the DEFAULT value for existing rows.\n            // When a column with a CHECK constraint is added and the table has rows,\n            // the default value (or NULL if no DEFAULT) must satisfy the CHECK.\n            // We substitute column references in the CHECK expression with the default\n            // value and evaluate it. If the result is false, we reject the ALTER when\n            // the table has rows.\n            emit_add_column_check_validation(\n                program,\n                &btree,\n                &original_btree,\n                new_column_name,\n                &column,\n                &constraints,\n                resolver,\n                database_id,\n            )?;\n\n            translate_update_for_schema_change(\n                update,\n                resolver,\n                program,\n                connection,\n                input,\n                |program| {\n                    program.emit_insn(Insn::SetCookie {\n                        db: database_id,\n                        cookie: Cookie::SchemaVersion,\n                        value: schema_version as i32 + 1,\n                        p5: 0,\n                    });\n                    program.emit_insn(Insn::AddColumn {\n                        db: database_id,\n                        table: table_name.to_owned(),\n                        column: Box::new(column),\n                        check_constraints: btree.check_constraints.clone(),\n                    });\n                },\n            )?\n        }\n        ast::AlterTableBody::RenameTo(new_name) => {\n            let new_name = new_name.as_str();\n            let normalized_old_name = normalize_ident(table_name);\n            let normalized_new_name = normalize_ident(new_name);\n\n            if resolver.with_schema(database_id, |s| {\n                s.get_table(new_name).is_some()\n                    || s.indexes\n                        .values()\n                        .flatten()\n                        .any(|index| index.name == normalize_ident(new_name))\n            }) {\n                return Err(LimboError::ParseError(format!(\n                    \"there is already another table or index with this name: {new_name}\"\n                )));\n            };\n\n            let sqlite_schema = resolver\n                .with_schema(database_id, |s| s.get_btree_table(SQLITE_TABLEID))\n                .ok_or_else(|| {\n                    LimboError::ParseError(\"sqlite_schema table not found in schema\".to_string())\n                })?;\n\n            let cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(sqlite_schema.clone()));\n\n            program.emit_insn(Insn::OpenWrite {\n                cursor_id,\n                root_page: RegisterOrLiteral::Literal(sqlite_schema.root_page),\n                db: database_id,\n            });\n\n            program.cursor_loop(cursor_id, |program, rowid| {\n                let sqlite_schema_column_len = sqlite_schema.columns.len();\n                turso_assert_eq!(sqlite_schema_column_len, 5);\n\n                let first_column = program.alloc_registers(sqlite_schema_column_len);\n\n                for i in 0..sqlite_schema_column_len {\n                    program.emit_column_or_rowid(cursor_id, i, first_column + i);\n                }\n\n                program.emit_string8_new_reg(table_name.to_string());\n                program.mark_last_insn_constant();\n\n                program.emit_string8_new_reg(new_name.to_string());\n                program.mark_last_insn_constant();\n\n                let out = program.alloc_registers(5);\n\n                program.emit_insn(Insn::Function {\n                    constant_mask: 0,\n                    start_reg: first_column,\n                    dest: out,\n                    func: crate::function::FuncCtx {\n                        func: Func::AlterTable(AlterTableFunc::RenameTable),\n                        arg_count: 7,\n                    },\n                });\n\n                let record = program.alloc_register();\n\n                program.emit_insn(Insn::MakeRecord {\n                    start_reg: to_u16(out),\n                    count: to_u16(sqlite_schema_column_len),\n                    dest_reg: to_u16(record),\n                    index_name: None,\n                    affinity_str: None,\n                });\n\n                // In MVCC mode, we need to delete before insert to properly\n                // end the old version (Hekaton-style UPDATE = DELETE + INSERT)\n                if connection.mvcc_enabled() {\n                    program.emit_insn(Insn::Delete {\n                        cursor_id,\n                        table_name: SQLITE_TABLEID.to_string(),\n                        is_part_of_update: true,\n                    });\n                }\n\n                program.emit_insn(Insn::Insert {\n                    cursor: cursor_id,\n                    key_reg: rowid,\n                    record_reg: record,\n                    flag: crate::vdbe::insn::InsertFlags(0),\n                    table_name: table_name.to_string(),\n                });\n            });\n\n            emit_rename_sqlite_sequence_entry(\n                program,\n                resolver,\n                connection,\n                database_id,\n                &normalized_old_name,\n                &normalized_new_name,\n            );\n\n            program.emit_insn(Insn::SetCookie {\n                db: database_id,\n                cookie: Cookie::SchemaVersion,\n                value: schema_version as i32 + 1,\n                p5: 0,\n            });\n\n            program.emit_insn(Insn::RenameTable {\n                db: database_id,\n                from: table_name.to_owned(),\n                to: new_name.to_owned(),\n            });\n        }\n        body @ (ast::AlterTableBody::AlterColumn { .. }\n        | ast::AlterTableBody::RenameColumn { .. }) => {\n            let from;\n            let definition;\n            let col_name;\n            let rename;\n\n            match body {\n                ast::AlterTableBody::AlterColumn { old, new } => {\n                    from = old;\n                    definition = new;\n                    col_name = definition.col_name.clone();\n                    rename = false;\n                }\n                ast::AlterTableBody::RenameColumn { old, new } => {\n                    from = old;\n                    definition = ast::ColumnDefinition {\n                        col_name: new.clone(),\n                        col_type: None,\n                        constraints: vec![],\n                    };\n                    col_name = new;\n                    rename = true;\n                }\n                _ => unreachable!(),\n            }\n\n            let from = from.as_str();\n            let col_name = col_name.as_str();\n\n            let Some((column_index, _)) = btree.get_column(from) else {\n                return Err(LimboError::ParseError(format!(\n                    \"no such column: \\\"{from}\\\"\"\n                )));\n            };\n\n            if btree.get_column(col_name).is_some() {\n                return Err(LimboError::ParseError(format!(\n                    \"duplicate column name: \\\"{col_name}\\\"\"\n                )));\n            };\n\n            if definition\n                .constraints\n                .iter()\n                .any(|c| matches!(c.constraint, ast::ColumnConstraint::PrimaryKey { .. }))\n            {\n                return Err(LimboError::ParseError(\n                    \"PRIMARY KEY constraint cannot be altered\".to_string(),\n                ));\n            }\n\n            if definition\n                .constraints\n                .iter()\n                .any(|c| matches!(c.constraint, ast::ColumnConstraint::Unique { .. }))\n            {\n                return Err(LimboError::ParseError(\n                    \"UNIQUE constraint cannot be altered\".to_string(),\n                ));\n            }\n\n            let is_making_column_generated = definition\n                .constraints\n                .iter()\n                .any(|c| matches!(c.constraint, ast::ColumnConstraint::Generated { .. }));\n\n            if is_making_column_generated {\n                let non_generated_count = btree\n                    .columns\n                    .iter()\n                    .enumerate()\n                    .filter(|(idx, col)| *idx != column_index && col.generated.is_none())\n                    .count();\n\n                if non_generated_count == 0 {\n                    return Err(LimboError::ParseError(\n                        \"must have at least one non-generated column\".to_string(),\n                    ));\n                }\n            }\n\n            // If renaming, rewrite trigger SQL for all triggers that reference this column\n            // We'll collect the triggers to rewrite and update them in sqlite_schema\n            let mut triggers_to_rewrite: Vec<(String, String)> = Vec::new();\n            let mut views_to_rewrite: Vec<(String, String)> = Vec::new();\n            if rename {\n                // Try to rewrite every trigger's SQL for the column rename.\n                // If the rewritten SQL differs from the original, include it\n                // in the update list. This matches SQLite's approach and avoids\n                // incomplete detection heuristics that miss expression-level refs\n                // (e.g., `SELECT b FROM src` in a trigger on a different table).\n                let all_triggers: Vec<_> = resolver.with_schema(database_id, |s| {\n                    s.triggers.values().flatten().cloned().collect()\n                });\n                for trigger in &all_triggers {\n                    match rewrite_trigger_sql_for_column_rename(\n                        &trigger.sql,\n                        table_name,\n                        from,\n                        col_name,\n                        database_id,\n                        resolver,\n                    ) {\n                        Ok(new_sql) => {\n                            if new_sql != trigger.sql {\n                                triggers_to_rewrite.push((trigger.name.clone(), new_sql));\n                            }\n                        }\n                        Err(e) => {\n                            // If we can't rewrite the trigger, fail the ALTER TABLE operation\n                            return Err(LimboError::ParseError(format!(\n                                \"error in trigger {} after rename column: {}\",\n                                trigger.name, e\n                            )));\n                        }\n                    }\n                }\n                let target_db_name = resolver\n                    .get_database_name_by_index(database_id)\n                    .ok_or_else(|| {\n                        LimboError::InternalError(format!(\n                            \"unknown database id {database_id} during ALTER TABLE\"\n                        ))\n                    })?;\n                views_to_rewrite = resolver.with_schema(database_id, |s| -> Result<_> {\n                    let mut rewrites = Vec::new();\n                    for (view_name, view) in s.views.iter() {\n                        if let Some(rewritten) = rewrite_view_sql_for_column_rename(\n                            &view.sql,\n                            s,\n                            table_name,\n                            &target_db_name,\n                            from,\n                            col_name,\n                        )? {\n                            rewrites.push((view_name.clone(), rewritten.sql));\n                        }\n                    }\n                    Ok(rewrites)\n                })?;\n            }\n\n            let sqlite_schema = resolver\n                .with_schema(database_id, |s| s.get_btree_table(SQLITE_TABLEID))\n                .ok_or_else(|| {\n                    LimboError::ParseError(\"sqlite_schema table not found in schema\".to_string())\n                })?;\n\n            let cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(sqlite_schema.clone()));\n\n            program.emit_insn(Insn::OpenWrite {\n                cursor_id,\n                root_page: RegisterOrLiteral::Literal(sqlite_schema.root_page),\n                db: database_id,\n            });\n\n            program.cursor_loop(cursor_id, |program, rowid| {\n                let sqlite_schema_column_len = sqlite_schema.columns.len();\n                turso_assert_eq!(sqlite_schema_column_len, 5);\n\n                let first_column = program.alloc_registers(sqlite_schema_column_len);\n\n                for i in 0..sqlite_schema_column_len {\n                    program.emit_column_or_rowid(cursor_id, i, first_column + i);\n                }\n\n                program.emit_string8_new_reg(table_name.to_string());\n                program.mark_last_insn_constant();\n\n                program.emit_string8_new_reg(from.to_string());\n                program.mark_last_insn_constant();\n\n                program.emit_string8_new_reg(definition.to_string());\n                program.mark_last_insn_constant();\n\n                let out = program.alloc_registers(sqlite_schema_column_len);\n\n                program.emit_insn(Insn::Function {\n                    constant_mask: 0,\n                    start_reg: first_column,\n                    dest: out,\n                    func: crate::function::FuncCtx {\n                        func: Func::AlterTable(if rename {\n                            AlterTableFunc::RenameColumn\n                        } else {\n                            AlterTableFunc::AlterColumn\n                        }),\n                        arg_count: 8,\n                    },\n                });\n\n                let record = program.alloc_register();\n\n                program.emit_insn(Insn::MakeRecord {\n                    start_reg: to_u16(out),\n                    count: to_u16(sqlite_schema_column_len),\n                    dest_reg: to_u16(record),\n                    index_name: None,\n                    affinity_str: None,\n                });\n\n                // In MVCC mode, we need to delete before insert to properly\n                // end the old version (Hekaton-style UPDATE = DELETE + INSERT)\n                if connection.mvcc_enabled() {\n                    program.emit_insn(Insn::Delete {\n                        cursor_id,\n                        table_name: SQLITE_TABLEID.to_string(),\n                        is_part_of_update: true,\n                    });\n                }\n\n                program.emit_insn(Insn::Insert {\n                    cursor: cursor_id,\n                    key_reg: rowid,\n                    record_reg: record,\n                    flag: crate::vdbe::insn::InsertFlags(0),\n                    table_name: table_name.to_string(),\n                });\n            });\n\n            // Update trigger SQL for renamed columns\n            for (trigger_name, new_sql) in triggers_to_rewrite {\n                let escaped_sql = new_sql.replace('\\'', \"''\");\n                let escaped_trigger_name = escape_sql_string_literal(&trigger_name);\n                let update_stmt = format!(\n                    r#\"\n                        UPDATE {qualified_schema_table}\n                        SET sql = '{escaped_sql}'\n                        WHERE name = '{escaped_trigger_name}' COLLATE NOCASE AND type = 'trigger'\n                    \"#,\n                );\n\n                let mut parser = Parser::new(update_stmt.as_bytes());\n                let cmd = parser.next_cmd().map_err(|e| {\n                    LimboError::ParseError(format!(\n                        \"failed to parse trigger update SQL for {trigger_name}: {e}\"\n                    ))\n                })?;\n                let Some(ast::Cmd::Stmt(ast::Stmt::Update(update))) = cmd else {\n                    return Err(LimboError::ParseError(format!(\n                        \"failed to parse trigger update SQL for {trigger_name}\",\n                    )));\n                };\n\n                translate_update_for_schema_change(\n                    update,\n                    resolver,\n                    program,\n                    connection,\n                    input,\n                    |_program| {},\n                )?;\n            }\n\n            // Update view SQL for renamed columns\n            for (view_name, new_sql) in views_to_rewrite {\n                let escaped_sql = new_sql.replace('\\'', \"''\");\n                let update_stmt = format!(\n                    r#\"\n                        UPDATE {qualified_schema_table}\n                        SET sql = '{escaped_sql}'\n                        WHERE name = '{view_name}' COLLATE NOCASE AND type = 'view'\n                    \"#,\n                );\n\n                let mut parser = Parser::new(update_stmt.as_bytes());\n                let cmd = parser.next_cmd().map_err(|e| {\n                    LimboError::ParseError(format!(\n                        \"failed to parse view update SQL for {view_name}: {e}\"\n                    ))\n                })?;\n                let Some(ast::Cmd::Stmt(ast::Stmt::Update(update))) = cmd else {\n                    return Err(LimboError::ParseError(format!(\n                        \"failed to parse view update SQL for {view_name}\",\n                    )));\n                };\n\n                translate_update_for_schema_change(\n                    update,\n                    resolver,\n                    program,\n                    connection,\n                    input,\n                    |_program| {},\n                )?;\n            }\n\n            program.emit_insn(Insn::SetCookie {\n                db: database_id,\n                cookie: Cookie::SchemaVersion,\n                value: schema_version as i32 + 1,\n                p5: 0,\n            });\n            program.emit_insn(Insn::AlterColumn {\n                db: database_id,\n                table: table_name.to_owned(),\n                column_index,\n                definition: Box::new(definition),\n                rename,\n            });\n        }\n    };\n\n    Ok(())\n}\n\nfn translate_rename_virtual_table(\n    program: &mut ProgramBuilder,\n    vtab: Arc<VirtualTable>,\n    old_name: &str,\n    new_name_norm: String,\n    resolver: &Resolver,\n    connection: &Arc<crate::Connection>,\n    database_id: usize,\n) -> Result<()> {\n    let schema_version = resolver.with_schema(database_id, |s| s.schema_version);\n    program.begin_write_operation();\n    let vtab_cur = program.alloc_cursor_id(CursorType::VirtualTable(vtab));\n    program.emit_insn(Insn::VOpen {\n        cursor_id: vtab_cur,\n    });\n\n    let new_name_reg = program.emit_string8_new_reg(new_name_norm.clone());\n    program.emit_insn(Insn::VRename {\n        cursor_id: vtab_cur,\n        new_name_reg,\n    });\n    // Rewrite sqlite_schema entry\n    let sqlite_schema = resolver\n        .schema()\n        .get_btree_table(SQLITE_TABLEID)\n        .ok_or_else(|| {\n            LimboError::ParseError(\"sqlite_schema table not found in schema\".to_string())\n        })?;\n\n    let schema_cur = program.alloc_cursor_id(CursorType::BTreeTable(sqlite_schema.clone()));\n    program.emit_insn(Insn::OpenWrite {\n        cursor_id: schema_cur,\n        root_page: RegisterOrLiteral::Literal(sqlite_schema.root_page),\n        db: database_id,\n    });\n\n    program.cursor_loop(schema_cur, |program, rowid| {\n        let ncols = sqlite_schema.columns.len();\n        turso_assert_eq!(ncols, 5);\n\n        let first_col = program.alloc_registers(ncols);\n        for i in 0..ncols {\n            program.emit_column_or_rowid(schema_cur, i, first_col + i);\n        }\n\n        program.emit_string8_new_reg(old_name.to_string());\n        program.mark_last_insn_constant();\n\n        program.emit_string8_new_reg(new_name_norm.clone());\n        program.mark_last_insn_constant();\n\n        let out = program.alloc_registers(ncols);\n\n        program.emit_insn(Insn::Function {\n            constant_mask: 0,\n            start_reg: first_col,\n            dest: out,\n            func: crate::function::FuncCtx {\n                func: Func::AlterTable(AlterTableFunc::RenameTable),\n                arg_count: 7,\n            },\n        });\n\n        let rec = program.alloc_register();\n        program.emit_insn(Insn::MakeRecord {\n            start_reg: to_u16(out),\n            count: to_u16(ncols),\n            dest_reg: to_u16(rec),\n            index_name: None,\n            affinity_str: None,\n        });\n\n        // In MVCC mode, we need to delete before insert to properly\n        // end the old version (Hekaton-style UPDATE = DELETE + INSERT)\n        if connection.mvcc_enabled() {\n            program.emit_insn(Insn::Delete {\n                cursor_id: schema_cur,\n                table_name: SQLITE_TABLEID.to_string(),\n                is_part_of_update: true,\n            });\n        }\n\n        program.emit_insn(Insn::Insert {\n            cursor: schema_cur,\n            key_reg: rowid,\n            record_reg: rec,\n            flag: crate::vdbe::insn::InsertFlags(0),\n            table_name: old_name.to_string(),\n        });\n    });\n\n    // Bump schema cookie\n    program.emit_insn(Insn::SetCookie {\n        db: database_id,\n        cookie: Cookie::SchemaVersion,\n        value: schema_version as i32 + 1,\n        p5: 0,\n    });\n\n    program.emit_insn(Insn::RenameTable {\n        db: database_id,\n        from: old_name.to_owned(),\n        to: new_name_norm,\n    });\n\n    program.emit_insn(Insn::Close {\n        cursor_id: schema_cur,\n    });\n    program.emit_insn(Insn::Close {\n        cursor_id: vtab_cur,\n    });\n\n    Ok(())\n}\n\n/* Triggers must be rewritten when a column is renamed, and DROP COLUMN on table T must be forbidden if any trigger on T references the column.\nHere are some helpers related to that: */\n\n/// Check if a trigger contains qualified references to a specific column in its owning table.\n/// Rewrite trigger SQL to replace old column name with new column name.\n/// This handles old.x, new.x, and unqualified x references.\nfn rewrite_trigger_sql_for_column_rename(\n    trigger_sql: &str,\n    table_name: &str,\n    old_column_name: &str,\n    new_column_name: &str,\n    database_id: usize,\n    resolver: &Resolver,\n) -> Result<String> {\n    use turso_parser::parser::Parser;\n\n    // Parse the trigger SQL\n    let mut parser = Parser::new(trigger_sql.as_bytes());\n    let cmd = parser\n        .next_cmd()\n        .map_err(|e| LimboError::ParseError(format!(\"failed to parse trigger SQL: {e}\")))?;\n    let Some(ast::Cmd::Stmt(ast::Stmt::CreateTrigger {\n        temporary,\n        if_not_exists,\n        trigger_name,\n        time,\n        event,\n        tbl_name,\n        for_each_row,\n        when_clause,\n        commands,\n    })) = cmd\n    else {\n        return Err(LimboError::ParseError(format!(\n            \"failed to parse trigger SQL: {trigger_sql}\"\n        )));\n    };\n\n    let old_col_norm = normalize_ident(old_column_name);\n    let new_col_norm = normalize_ident(new_column_name);\n\n    // Get the trigger's owning table to check unqualified column references\n    let trigger_table_name_raw = tbl_name.name.as_str();\n    let trigger_table_name = normalize_ident(trigger_table_name_raw);\n    let trigger_table = resolver\n        .with_schema(database_id, |schema| {\n            schema.get_btree_table(&trigger_table_name)\n        })\n        .ok_or_else(|| {\n            LimboError::ParseError(format!(\"trigger table not found: {trigger_table_name}\"))\n        })?;\n\n    // Check if this trigger references the column being renamed\n    // We need to check if the column exists in the table being renamed\n    let target_table_name = normalize_ident(table_name);\n    let target_table = resolver\n        .with_schema(database_id, |schema| {\n            schema.get_btree_table(&target_table_name)\n        })\n        .ok_or_else(|| {\n            LimboError::ParseError(format!(\"target table not found: {target_table_name}\"))\n        })?;\n\n    // Rewrite UPDATE OF column list if renaming a column in the trigger's owning table\n    let is_renaming_trigger_table = trigger_table_name == target_table_name;\n    let new_event = if is_renaming_trigger_table {\n        match event {\n            ast::TriggerEvent::UpdateOf(mut cols) => {\n                // Rewrite column names in UPDATE OF list\n                for col in &mut cols {\n                    let col_norm = normalize_ident(col.as_str());\n                    if col_norm == old_col_norm {\n                        *col = ast::Name::from_string(new_col_norm.clone());\n                    }\n                }\n                ast::TriggerEvent::UpdateOf(cols)\n            }\n            other => other,\n        }\n    } else {\n        event\n    };\n\n    // Rewrite WHEN clause column references if present.\n    // In WHEN clauses, only NEW.col / OLD.col qualified references are valid;\n    // bare column names are not valid in trigger WHEN clauses per SQLite semantics,\n    // so we only rewrite qualified NEW/OLD references here.\n    let new_when_clause = when_clause\n        .map(|e| {\n            let mut expr = *e;\n            walk_expr_mut(\n                &mut expr,\n                &mut |ex: &mut ast::Expr| -> Result<WalkControl> {\n                    if let ast::Expr::Qualified(ns, col) | ast::Expr::DoublyQualified(_, ns, col) =\n                        ex\n                    {\n                        let ns_norm = normalize_ident(ns.as_str());\n                        let col_norm = normalize_ident(col.as_str());\n                        if (ns_norm.eq_ignore_ascii_case(\"new\")\n                            || ns_norm.eq_ignore_ascii_case(\"old\"))\n                            && col_norm == *old_col_norm\n                            && is_renaming_trigger_table\n                            && trigger_table.get_column(&col_norm).is_some()\n                        {\n                            *col = ast::Name::from_string(&*new_col_norm);\n                        }\n                    }\n                    Ok(WalkControl::Continue)\n                },\n            )?;\n            Ok::<Box<ast::Expr>, LimboError>(Box::new(expr))\n        })\n        .transpose()?;\n\n    // Validate: if the WHEN clause still contains a bare reference to the old column,\n    // SQLite would error with \"no such column\". We must do the same.\n    if let Some(ref when_expr) = new_when_clause {\n        let mut has_bare_old_col = false;\n        let _ = walk_expr_mut(\n            &mut when_expr.clone(),\n            &mut |ex: &mut ast::Expr| -> Result<WalkControl> {\n                if let ast::Expr::Id(ref name) | ast::Expr::Name(ref name) = ex {\n                    if normalize_ident(name.as_str()) == *old_col_norm {\n                        has_bare_old_col = true;\n                    }\n                }\n                Ok(WalkControl::Continue)\n            },\n        );\n        if has_bare_old_col {\n            return Err(LimboError::ParseError(format!(\n                \"error in trigger {}: no such column: {}\",\n                trigger_name.name.as_str(),\n                old_col_norm\n            )));\n        }\n    }\n\n    let mut new_commands = Vec::new();\n    for cmd in commands {\n        let new_cmd = rewrite_trigger_cmd_for_column_rename(\n            cmd,\n            &trigger_table,\n            &target_table,\n            trigger_table_name_raw,\n            &target_table_name,\n            &old_col_norm,\n            &new_col_norm,\n            database_id,\n            resolver,\n        )?;\n        new_commands.push(new_cmd);\n    }\n\n    // Reconstruct the SQL\n    use crate::translate::trigger::create_trigger_to_sql;\n    let new_sql = create_trigger_to_sql(\n        temporary,\n        if_not_exists,\n        &trigger_name,\n        time,\n        &new_event,\n        &tbl_name,\n        for_each_row,\n        new_when_clause.as_deref(),\n        &new_commands,\n    );\n\n    Ok(new_sql)\n}\n\n/// Rewrite an expression to replace column references\n///\n/// `context_table_name` is used for UPDATE/DELETE WHERE clauses where unqualified column\n/// references refer to the UPDATE/DELETE target table, not the trigger's owning table.\n/// If `None`, unqualified references refer to the trigger's owning table.\n#[allow(clippy::too_many_arguments)]\nfn rewrite_expr_for_column_rename(\n    expr: &ast::Expr,\n    trigger_table: &BTreeTable,\n    trigger_table_name: &str,\n    target_table_name: &str,\n    old_col_norm: &str,\n    new_col_norm: &str,\n    context_table_name: Option<&str>,\n    from_target: Option<&str>,\n    database_id: usize,\n    resolver: &Resolver,\n) -> Result<ast::Expr> {\n    let trigger_table_name_norm = normalize_ident(trigger_table_name);\n    let target_table_name_norm = normalize_ident(target_table_name);\n    let is_renaming_trigger_table = trigger_table_name_norm == target_table_name_norm;\n\n    // Get context table if provided (for UPDATE/DELETE WHERE clauses)\n    let context_table_info: Option<(Arc<BTreeTable>, String, bool)> =\n        if let Some(ctx_name) = context_table_name {\n            let ctx_name_norm = normalize_ident(ctx_name);\n            let is_renaming = ctx_name_norm == target_table_name_norm;\n            let table = resolver\n                .with_schema(database_id, |schema| schema.get_btree_table(&ctx_name_norm))\n                .ok_or_else(|| {\n                    LimboError::ParseError(format!(\"context table not found: {ctx_name_norm}\"))\n                })?;\n            Some((table, ctx_name_norm, is_renaming))\n        } else {\n            None\n        };\n\n    let mut expr = expr.clone();\n    walk_expr_mut(&mut expr, &mut |e: &mut ast::Expr| -> Result<WalkControl> {\n        match e {\n            ast::Expr::Subquery(select) | ast::Expr::Exists(select) => {\n                // Subqueries don't inherit the context table scope\n                rewrite_select_for_column_rename(\n                    select,\n                    trigger_table,\n                    trigger_table_name,\n                    target_table_name,\n                    old_col_norm,\n                    new_col_norm,\n                )?;\n            }\n            ast::Expr::InSelect { rhs, .. } => {\n                rewrite_select_for_column_rename(\n                    rhs,\n                    trigger_table,\n                    trigger_table_name,\n                    target_table_name,\n                    old_col_norm,\n                    new_col_norm,\n                )?;\n                // lhs will be walked by walk_expr_mut\n            }\n            _ => {\n                rewrite_expr_column_ref_with_context(\n                    e,\n                    trigger_table,\n                    trigger_table_name,\n                    old_col_norm,\n                    new_col_norm,\n                    is_renaming_trigger_table,\n                    context_table_info\n                        .as_ref()\n                        .map(|(t, n, r)| (t.as_ref(), n, *r)),\n                    from_target,\n                )?;\n            }\n        }\n        Ok(WalkControl::Continue)\n    })?;\n\n    Ok(expr)\n}\n\n/// Rewrite a trigger command to replace column references\n#[allow(clippy::too_many_arguments)]\nfn rewrite_trigger_cmd_for_column_rename(\n    cmd: ast::TriggerCmd,\n    trigger_table: &BTreeTable,\n    _target_table: &BTreeTable,\n    trigger_table_name: &str,\n    target_table_name: &str,\n    old_col_norm: &str,\n    new_col_norm: &str,\n    database_id: usize,\n    resolver: &Resolver,\n) -> Result<ast::TriggerCmd> {\n    match cmd {\n        ast::TriggerCmd::Update {\n            or_conflict,\n            tbl_name,\n            mut sets,\n            from,\n            where_clause,\n        } => {\n            // Get the UPDATE target table to check if we're renaming a column in it\n            let update_table_name_norm = normalize_ident(tbl_name.as_str());\n            let is_renaming_update_table = update_table_name_norm == *target_table_name;\n            let from_target = if from_clause_references_target(&from, target_table_name) {\n                Some(target_table_name)\n            } else {\n                None\n            };\n\n            // Rewrite SET column names if renaming a column in the UPDATE target table\n            if is_renaming_update_table {\n                for set in &mut sets {\n                    for col_name in &mut set.col_names {\n                        let col_norm = normalize_ident(col_name.as_str());\n                        if col_norm == *old_col_norm {\n                            *col_name = ast::Name::from_string(new_col_norm);\n                        }\n                    }\n                }\n            }\n\n            // Rewrite SET expressions — unqualified refs in SET refer to the UPDATE target.\n            // Pass context_table so invalid qualified refs to trigger table are caught.\n            for set in &mut sets {\n                set.expr = Box::new(rewrite_expr_for_column_rename(\n                    &set.expr,\n                    trigger_table,\n                    trigger_table_name,\n                    target_table_name,\n                    old_col_norm,\n                    new_col_norm,\n                    Some(&update_table_name_norm),\n                    from_target,\n                    database_id,\n                    resolver,\n                )?);\n            }\n\n            // Rewrite WHERE clause - unqualified column references refer to UPDATE target table\n            let new_where = where_clause\n                .map(|e| {\n                    rewrite_expr_for_column_rename(\n                        &e,\n                        trigger_table,\n                        trigger_table_name,\n                        target_table_name,\n                        old_col_norm,\n                        new_col_norm,\n                        Some(&update_table_name_norm), // UPDATE WHERE: unqualified refs refer to UPDATE target\n                        from_target,\n                        database_id,\n                        resolver,\n                    )\n                    .map(Box::new)\n                })\n                .transpose()?;\n            let mut from = from;\n            if let Some(ref mut from_clause) = from {\n                rewrite_from_clause_for_column_rename(\n                    from_clause,\n                    trigger_table,\n                    trigger_table_name,\n                    target_table_name,\n                    old_col_norm,\n                    new_col_norm,\n                    None,\n                )?;\n            }\n            Ok(ast::TriggerCmd::Update {\n                or_conflict,\n                tbl_name,\n                sets,\n                from,\n                where_clause: new_where,\n            })\n        }\n        ast::TriggerCmd::Insert {\n            or_conflict,\n            tbl_name,\n            mut col_names,\n            select,\n            upsert,\n            returning,\n        } => {\n            // Rewrite column names in INSERT column list\n            // Check if the INSERT is targeting the table being renamed\n            let insert_table_name_norm = normalize_ident(tbl_name.as_str());\n            if insert_table_name_norm == *target_table_name {\n                // This INSERT targets the table being renamed, so rewrite column names\n                for col_name in &mut col_names {\n                    let col_norm = normalize_ident(col_name.as_str());\n                    if col_norm == *old_col_norm {\n                        *col_name = ast::Name::from_string(new_col_norm);\n                    }\n                }\n            }\n            // Rewrite SELECT expressions\n            let mut select = select;\n            rewrite_select_for_column_rename(\n                &mut select,\n                trigger_table,\n                trigger_table_name,\n                target_table_name,\n                old_col_norm,\n                new_col_norm,\n            )?;\n            let upsert = upsert\n                .map(|upsert| {\n                    rewrite_upsert_for_column_rename(\n                        *upsert,\n                        trigger_table,\n                        trigger_table_name,\n                        target_table_name,\n                        tbl_name.as_str(),\n                        old_col_norm,\n                        new_col_norm,\n                        database_id,\n                        resolver,\n                    )\n                    .map(Box::new)\n                })\n                .transpose()?;\n            Ok(ast::TriggerCmd::Insert {\n                or_conflict,\n                tbl_name,\n                col_names,\n                select,\n                upsert,\n                returning,\n            })\n        }\n        ast::TriggerCmd::Delete {\n            tbl_name,\n            where_clause,\n        } => {\n            // Get the DELETE target table to check if we're renaming a column in it\n            let delete_table_name_norm = normalize_ident(tbl_name.as_str());\n\n            // Rewrite WHERE clause - unqualified column references refer to DELETE target table\n            let new_where = where_clause\n                .map(|e| {\n                    rewrite_expr_for_column_rename(\n                        &e,\n                        trigger_table,\n                        trigger_table_name,\n                        target_table_name,\n                        old_col_norm,\n                        new_col_norm,\n                        Some(&delete_table_name_norm), // DELETE WHERE: unqualified refs refer to DELETE target\n                        None,\n                        database_id,\n                        resolver,\n                    )\n                    .map(Box::new)\n                })\n                .transpose()?;\n            Ok(ast::TriggerCmd::Delete {\n                tbl_name,\n                where_clause: new_where,\n            })\n        }\n        ast::TriggerCmd::Select(mut select) => {\n            rewrite_select_for_column_rename(\n                &mut select,\n                trigger_table,\n                trigger_table_name,\n                target_table_name,\n                old_col_norm,\n                new_col_norm,\n            )?;\n            Ok(ast::TriggerCmd::Select(select))\n        }\n    }\n}\n\nfn rename_excluded_column_refs(\n    expr: &mut ast::Expr,\n    old_col_norm: &str,\n    new_col_norm: &str,\n) -> Result<()> {\n    walk_expr_mut(expr, &mut |e: &mut ast::Expr| -> Result<WalkControl> {\n        if let ast::Expr::Qualified(ns, col) | ast::Expr::DoublyQualified(_, ns, col) = e {\n            if normalize_ident(ns.as_str()) == \"excluded\"\n                && normalize_ident(col.as_str()) == *old_col_norm\n            {\n                *col = ast::Name::from_string(new_col_norm);\n            }\n        }\n        Ok(WalkControl::Continue)\n    })?;\n    Ok(())\n}\n\n#[allow(clippy::too_many_arguments)]\nfn rewrite_upsert_for_column_rename(\n    mut upsert: ast::Upsert,\n    trigger_table: &BTreeTable,\n    trigger_table_name: &str,\n    target_table_name: &str,\n    insert_table_name: &str,\n    old_col_norm: &str,\n    new_col_norm: &str,\n    database_id: usize,\n    resolver: &Resolver,\n) -> Result<ast::Upsert> {\n    let insert_table_name_norm = normalize_ident(insert_table_name);\n    let insert_targets_renamed_table = insert_table_name_norm == *target_table_name;\n\n    if let Some(index) = &mut upsert.index {\n        for target in &mut index.targets {\n            target.expr = Box::new(rewrite_expr_for_column_rename(\n                &target.expr,\n                trigger_table,\n                trigger_table_name,\n                target_table_name,\n                old_col_norm,\n                new_col_norm,\n                Some(&insert_table_name_norm),\n                None,\n                database_id,\n                resolver,\n            )?);\n            if insert_targets_renamed_table {\n                rename_excluded_column_refs(&mut target.expr, old_col_norm, new_col_norm)?;\n            }\n        }\n        if let Some(where_clause) = &mut index.where_clause {\n            **where_clause = rewrite_expr_for_column_rename(\n                where_clause,\n                trigger_table,\n                trigger_table_name,\n                target_table_name,\n                old_col_norm,\n                new_col_norm,\n                Some(&insert_table_name_norm),\n                None,\n                database_id,\n                resolver,\n            )?;\n            if insert_targets_renamed_table {\n                rename_excluded_column_refs(where_clause, old_col_norm, new_col_norm)?;\n            }\n        }\n    }\n\n    if let ast::UpsertDo::Set { sets, where_clause } = &mut upsert.do_clause {\n        for set in sets {\n            if insert_targets_renamed_table {\n                for col_name in &mut set.col_names {\n                    if normalize_ident(col_name.as_str()) == *old_col_norm {\n                        *col_name = ast::Name::from_string(new_col_norm);\n                    }\n                }\n            }\n            set.expr = Box::new(rewrite_expr_for_column_rename(\n                &set.expr,\n                trigger_table,\n                trigger_table_name,\n                target_table_name,\n                old_col_norm,\n                new_col_norm,\n                Some(&insert_table_name_norm),\n                None,\n                database_id,\n                resolver,\n            )?);\n            if insert_targets_renamed_table {\n                rename_excluded_column_refs(&mut set.expr, old_col_norm, new_col_norm)?;\n            }\n        }\n        if let Some(expr) = where_clause {\n            **expr = rewrite_expr_for_column_rename(\n                expr,\n                trigger_table,\n                trigger_table_name,\n                target_table_name,\n                old_col_norm,\n                new_col_norm,\n                Some(&insert_table_name_norm),\n                None,\n                database_id,\n                resolver,\n            )?;\n            if insert_targets_renamed_table {\n                rename_excluded_column_refs(expr, old_col_norm, new_col_norm)?;\n            }\n        }\n    }\n\n    if let Some(next) = upsert.next.take() {\n        upsert.next = Some(Box::new(rewrite_upsert_for_column_rename(\n            *next,\n            trigger_table,\n            trigger_table_name,\n            target_table_name,\n            insert_table_name,\n            old_col_norm,\n            new_col_norm,\n            database_id,\n            resolver,\n        )?));\n    }\n\n    Ok(upsert)\n}\n\n/// Walk an expression tree for column renaming, handling subqueries that walk_expr_mut skips.\n/// This is the central expression walker for trigger column rename rewrites.\nfn walk_expr_for_column_rename(\n    expr: &mut ast::Expr,\n    trigger_table: &BTreeTable,\n    trigger_table_name: &str,\n    target_table_name: &str,\n    old_col_norm: &str,\n    new_col_norm: &str,\n    from_target: Option<&str>,\n) -> Result<()> {\n    use crate::translate::expr::walk_expr_mut;\n\n    walk_expr_mut(expr, &mut |e: &mut ast::Expr| -> Result<WalkControl> {\n        match e {\n            ast::Expr::Subquery(select) | ast::Expr::Exists(select) => {\n                rewrite_select_for_column_rename(\n                    select,\n                    trigger_table,\n                    trigger_table_name,\n                    target_table_name,\n                    old_col_norm,\n                    new_col_norm,\n                )?;\n            }\n            ast::Expr::InSelect { rhs, .. } => {\n                rewrite_select_for_column_rename(\n                    rhs,\n                    trigger_table,\n                    trigger_table_name,\n                    target_table_name,\n                    old_col_norm,\n                    new_col_norm,\n                )?;\n                // lhs will be walked by walk_expr_mut\n            }\n            _ => {\n                rewrite_expr_column_ref(\n                    e,\n                    trigger_table,\n                    trigger_table_name,\n                    target_table_name,\n                    old_col_norm,\n                    new_col_norm,\n                    from_target,\n                )?;\n            }\n        }\n        Ok(WalkControl::Continue)\n    })?;\n    Ok(())\n}\n\n/// Rewrite a SELECT statement in-place to replace column references.\nfn rewrite_select_for_column_rename(\n    select: &mut ast::Select,\n    trigger_table: &BTreeTable,\n    trigger_table_name: &str,\n    target_table_name: &str,\n    old_col_norm: &str,\n    new_col_norm: &str,\n) -> Result<()> {\n    // Rewrite WITH clause (CTEs)\n    if let Some(ref mut with_clause) = select.with {\n        for cte in &mut with_clause.ctes {\n            rewrite_select_for_column_rename(\n                &mut cte.select,\n                trigger_table,\n                trigger_table_name,\n                target_table_name,\n                old_col_norm,\n                new_col_norm,\n            )?;\n        }\n    }\n\n    // Rewrite main SELECT body\n    rewrite_one_select_for_column_rename(\n        &mut select.body.select,\n        trigger_table,\n        trigger_table_name,\n        target_table_name,\n        old_col_norm,\n        new_col_norm,\n    )?;\n\n    // Rewrite compound SELECTs (UNION, EXCEPT, INTERSECT)\n    for compound in &mut select.body.compounds {\n        rewrite_one_select_for_column_rename(\n            &mut compound.select,\n            trigger_table,\n            trigger_table_name,\n            target_table_name,\n            old_col_norm,\n            new_col_norm,\n        )?;\n    }\n\n    // Compute from_target for ORDER BY / LIMIT (same scope as body's FROM)\n    let body_from_target = match &select.body.select {\n        ast::OneSelect::Select { from, .. } => {\n            if from_clause_references_target(from, target_table_name) {\n                Some(target_table_name)\n            } else {\n                None\n            }\n        }\n        _ => None,\n    };\n\n    // Rewrite ORDER BY\n    for sorted_col in &mut select.order_by {\n        walk_expr_for_column_rename(\n            &mut sorted_col.expr,\n            trigger_table,\n            trigger_table_name,\n            target_table_name,\n            old_col_norm,\n            new_col_norm,\n            body_from_target,\n        )?;\n    }\n\n    // Rewrite LIMIT\n    if let Some(ref mut limit) = select.limit {\n        walk_expr_for_column_rename(\n            &mut limit.expr,\n            trigger_table,\n            trigger_table_name,\n            target_table_name,\n            old_col_norm,\n            new_col_norm,\n            body_from_target,\n        )?;\n        if let Some(ref mut offset) = limit.offset {\n            walk_expr_for_column_rename(\n                offset,\n                trigger_table,\n                trigger_table_name,\n                target_table_name,\n                old_col_norm,\n                new_col_norm,\n                body_from_target,\n            )?;\n        }\n    }\n\n    Ok(())\n}\n\n/// Rewrite a OneSelect to replace column references\nfn rewrite_one_select_for_column_rename(\n    one_select: &mut ast::OneSelect,\n    trigger_table: &BTreeTable,\n    trigger_table_name: &str,\n    target_table_name: &str,\n    old_col_norm: &str,\n    new_col_norm: &str,\n) -> Result<()> {\n    match one_select {\n        ast::OneSelect::Select {\n            columns,\n            from,\n            where_clause,\n            group_by,\n            window_clause,\n            ..\n        } => {\n            // Check if FROM references the target table (for cross-table column rename)\n            let from_target = if from_clause_references_target(from, target_table_name) {\n                Some(target_table_name)\n            } else {\n                None\n            };\n\n            // Rewrite columns\n            for col in columns {\n                if let ast::ResultColumn::Expr(expr, _) = col {\n                    walk_expr_for_column_rename(\n                        expr,\n                        trigger_table,\n                        trigger_table_name,\n                        target_table_name,\n                        old_col_norm,\n                        new_col_norm,\n                        from_target,\n                    )?;\n                }\n            }\n\n            // Rewrite FROM clause and JOIN conditions\n            if let Some(ref mut from_clause) = from {\n                rewrite_from_clause_for_column_rename(\n                    from_clause,\n                    trigger_table,\n                    trigger_table_name,\n                    target_table_name,\n                    old_col_norm,\n                    new_col_norm,\n                    from_target,\n                )?;\n            }\n\n            // Rewrite WHERE clause\n            if let Some(ref mut where_expr) = where_clause {\n                walk_expr_for_column_rename(\n                    where_expr,\n                    trigger_table,\n                    trigger_table_name,\n                    target_table_name,\n                    old_col_norm,\n                    new_col_norm,\n                    from_target,\n                )?;\n            }\n\n            // Rewrite GROUP BY and HAVING\n            if let Some(ref mut group_by) = group_by {\n                for expr in &mut group_by.exprs {\n                    walk_expr_for_column_rename(\n                        expr,\n                        trigger_table,\n                        trigger_table_name,\n                        target_table_name,\n                        old_col_norm,\n                        new_col_norm,\n                        from_target,\n                    )?;\n                }\n                if let Some(ref mut having_expr) = group_by.having {\n                    walk_expr_for_column_rename(\n                        having_expr,\n                        trigger_table,\n                        trigger_table_name,\n                        target_table_name,\n                        old_col_norm,\n                        new_col_norm,\n                        from_target,\n                    )?;\n                }\n            }\n\n            // Rewrite WINDOW clause\n            for window_def in window_clause {\n                rewrite_window_for_column_rename(\n                    &mut window_def.window,\n                    trigger_table,\n                    trigger_table_name,\n                    target_table_name,\n                    old_col_norm,\n                    new_col_norm,\n                    from_target,\n                )?;\n            }\n        }\n        ast::OneSelect::Values(values) => {\n            for row in values {\n                for expr in row {\n                    walk_expr_for_column_rename(\n                        expr,\n                        trigger_table,\n                        trigger_table_name,\n                        target_table_name,\n                        old_col_norm,\n                        new_col_norm,\n                        None,\n                    )?;\n                }\n            }\n        }\n    }\n    Ok(())\n}\n\n/// Rewrite expressions in a FROM clause (including JOIN conditions)\nfn rewrite_from_clause_for_column_rename(\n    from_clause: &mut ast::FromClause,\n    trigger_table: &BTreeTable,\n    trigger_table_name: &str,\n    target_table_name: &str,\n    old_col_norm: &str,\n    new_col_norm: &str,\n    from_target: Option<&str>,\n) -> Result<()> {\n    // Rewrite main table (could be a subquery)\n    rewrite_select_table_for_column_rename(\n        &mut from_clause.select,\n        trigger_table,\n        trigger_table_name,\n        target_table_name,\n        old_col_norm,\n        new_col_norm,\n    )?;\n\n    // Rewrite JOIN conditions\n    for join in &mut from_clause.joins {\n        rewrite_select_table_for_column_rename(\n            &mut join.table,\n            trigger_table,\n            trigger_table_name,\n            target_table_name,\n            old_col_norm,\n            new_col_norm,\n        )?;\n        if let Some(ref mut constraint) = join.constraint {\n            match constraint {\n                ast::JoinConstraint::On(expr) => {\n                    walk_expr_for_column_rename(\n                        expr,\n                        trigger_table,\n                        trigger_table_name,\n                        target_table_name,\n                        old_col_norm,\n                        new_col_norm,\n                        from_target,\n                    )?;\n                }\n                ast::JoinConstraint::Using(_) => {\n                    // USING clause contains column names, not expressions\n                }\n            }\n        }\n    }\n    Ok(())\n}\n\n/// Rewrite expressions in a SelectTable (table, subquery, or table function)\nfn rewrite_select_table_for_column_rename(\n    select_table: &mut ast::SelectTable,\n    trigger_table: &BTreeTable,\n    trigger_table_name: &str,\n    target_table_name: &str,\n    old_col_norm: &str,\n    new_col_norm: &str,\n) -> Result<()> {\n    match select_table {\n        ast::SelectTable::Select(select, _) => {\n            rewrite_select_for_column_rename(\n                select,\n                trigger_table,\n                trigger_table_name,\n                target_table_name,\n                old_col_norm,\n                new_col_norm,\n            )?;\n        }\n        ast::SelectTable::Sub(from_clause, _) => {\n            rewrite_from_clause_for_column_rename(\n                from_clause,\n                trigger_table,\n                trigger_table_name,\n                target_table_name,\n                old_col_norm,\n                new_col_norm,\n                None,\n            )?;\n        }\n        ast::SelectTable::TableCall(_, args, _) => {\n            for arg in args {\n                walk_expr_for_column_rename(\n                    arg,\n                    trigger_table,\n                    trigger_table_name,\n                    target_table_name,\n                    old_col_norm,\n                    new_col_norm,\n                    None,\n                )?;\n            }\n        }\n        ast::SelectTable::Table(_, _, _) => {\n            // Table reference, no expressions\n        }\n    }\n    Ok(())\n}\n\n/// Rewrite expressions in a Window definition\nfn rewrite_window_for_column_rename(\n    window: &mut ast::Window,\n    trigger_table: &BTreeTable,\n    trigger_table_name: &str,\n    target_table_name: &str,\n    old_col_norm: &str,\n    new_col_norm: &str,\n    from_target: Option<&str>,\n) -> Result<()> {\n    // Rewrite PARTITION BY expressions\n    for expr in &mut window.partition_by {\n        walk_expr_for_column_rename(\n            expr,\n            trigger_table,\n            trigger_table_name,\n            target_table_name,\n            old_col_norm,\n            new_col_norm,\n            from_target,\n        )?;\n    }\n\n    // Rewrite ORDER BY expressions\n    for sorted_col in &mut window.order_by {\n        walk_expr_for_column_rename(\n            &mut sorted_col.expr,\n            trigger_table,\n            trigger_table_name,\n            target_table_name,\n            old_col_norm,\n            new_col_norm,\n            from_target,\n        )?;\n    }\n\n    Ok(())\n}\n\n/// Check if a FROM clause references the target table being renamed.\n/// Returns the normalized target table name if found, None otherwise.\nfn from_clause_references_target(from: &Option<ast::FromClause>, target_table_name: &str) -> bool {\n    let Some(from_clause) = from else {\n        return false;\n    };\n    if select_table_is_target(&from_clause.select, target_table_name) {\n        return true;\n    }\n    from_clause\n        .joins\n        .iter()\n        .any(|j| select_table_is_target(&j.table, target_table_name))\n}\n\nfn select_table_is_target(select_table: &ast::SelectTable, target_table_name: &str) -> bool {\n    matches!(\n        select_table,\n        ast::SelectTable::Table(name, _, _)\n            if normalize_ident(name.name.as_str()) == *target_table_name\n    )\n}\n\n/// Rewrite a single expression's column reference\n///\n/// Handles column references in trigger expressions:\n/// - NEW.column and OLD.column: Always refer to the trigger's owning table\n/// - Qualified references (e.g., u.x): Refer to the specified table\n/// - Unqualified references (e.g., x): Resolution order:\n///   1. If `context_table` is provided (UPDATE/DELETE WHERE clauses), check the context table first\n///   2. Otherwise, check the trigger's owning table\n///   3. If `from_target` is provided (FROM clause has target table), rename the column\n///\n/// `context_table`: Optional tuple of (table, normalized_name, is_renaming) for UPDATE/DELETE\n///                  target tables.\n/// `from_target`: Normalized target table name when the enclosing SELECT's FROM clause\n///                references the table being renamed.\n#[allow(clippy::too_many_arguments)]\nfn rewrite_expr_column_ref_with_context(\n    e: &mut ast::Expr,\n    trigger_table: &BTreeTable,\n    trigger_table_name: &str,\n    old_col_norm: &str,\n    new_col_norm: &str,\n    is_renaming_trigger_table: bool,\n    context_table: Option<(&BTreeTable, &String, bool)>,\n    from_target: Option<&str>,\n) -> Result<()> {\n    match e {\n        ast::Expr::Qualified(ns, col) | ast::Expr::DoublyQualified(_, ns, col) => {\n            let ns_norm = normalize_ident(ns.as_str());\n            let col_norm = normalize_ident(col.as_str());\n\n            // Check if this is NEW.column or OLD.column\n            if (ns_norm.eq_ignore_ascii_case(\"new\") || ns_norm.eq_ignore_ascii_case(\"old\"))\n                && col_norm == *old_col_norm\n            {\n                // NEW.x and OLD.x always refer to the trigger's owning table\n                if is_renaming_trigger_table && trigger_table.get_column(&col_norm).is_some() {\n                    *col = ast::Name::from_string(new_col_norm);\n                }\n            } else if col_norm == *old_col_norm {\n                // This is a qualified column reference like u.x or t.x\n                // Check if it refers to the context table (UPDATE/DELETE target) or trigger table\n                if let Some((_, ctx_name_norm, is_renaming_ctx)) = context_table {\n                    if ns_norm == *ctx_name_norm && is_renaming_ctx {\n                        // Qualified reference to context table (e.g., u.x where u is UPDATE target)\n                        *col = ast::Name::from_string(new_col_norm);\n                    }\n                }\n                // Also check if it's a qualified reference to the trigger's owning table\n                // (e.g., t.x in a trigger on table t).\n                // Only do this when there's no context table (i.e., in a SELECT), or when\n                // the trigger table IS the context table. In UPDATE/DELETE on a different\n                // table, qualified refs to the trigger table (t.x) are invalid in SQLite\n                // — only NEW.x/OLD.x are allowed — so we must not rename them.\n                if is_renaming_trigger_table {\n                    let trigger_table_name_norm = normalize_ident(trigger_table_name);\n                    if ns_norm == trigger_table_name_norm\n                        && trigger_table.get_column(&col_norm).is_some()\n                    {\n                        // If we're inside an UPDATE/DELETE targeting a different table,\n                        // a qualified ref to the trigger table (t.x) is invalid in SQLite.\n                        let ctx_is_different_table = context_table\n                            .as_ref()\n                            .is_some_and(|(_, ctx_name, _)| **ctx_name != trigger_table_name_norm);\n                        if ctx_is_different_table {\n                            return Err(LimboError::ParseError(format!(\n                                \"no such column: {trigger_table_name}.{col_norm}\"\n                            )));\n                        }\n                        *col = ast::Name::from_string(new_col_norm);\n                    }\n                }\n                // Check if it's a qualified reference to a FROM clause table that is the\n                // rename target (e.g., src.b in SELECT src.b FROM src)\n                if let Some(target_name) = from_target {\n                    if ns_norm == *target_name {\n                        *col = ast::Name::from_string(new_col_norm);\n                    }\n                }\n            }\n        }\n        ast::Expr::Id(col) => {\n            // Unqualified column reference\n            let col_norm = normalize_ident(col.as_str());\n            if col_norm == *old_col_norm {\n                // Check context table first (for UPDATE/DELETE WHERE clauses)\n                if let Some((ctx_table, _, is_renaming_ctx)) = context_table {\n                    if ctx_table.get_column(&col_norm).is_some() {\n                        // This refers to the context table (UPDATE/DELETE target)\n                        if is_renaming_ctx {\n                            *e = ast::Expr::Id(ast::Name::from_string(new_col_norm));\n                        }\n                        return Ok(());\n                    }\n                }\n                // Check trigger's owning table or FROM clause target table\n                if (is_renaming_trigger_table && trigger_table.get_column(&col_norm).is_some())\n                    || from_target.is_some()\n                {\n                    *e = ast::Expr::Id(ast::Name::from_string(new_col_norm));\n                }\n            }\n        }\n        _ => {}\n    }\n    Ok(())\n}\n\n/// Rewrite a single expression's column reference (convenience wrapper for non-context cases)\nfn rewrite_expr_column_ref(\n    e: &mut ast::Expr,\n    trigger_table: &BTreeTable,\n    trigger_table_name: &str,\n    target_table_name: &str,\n    old_col_norm: &str,\n    new_col_norm: &str,\n    from_target: Option<&str>,\n) -> Result<()> {\n    let trigger_table_name_norm = normalize_ident(trigger_table_name);\n    let target_table_name_norm = normalize_ident(target_table_name);\n    let is_renaming_trigger_table = trigger_table_name_norm == target_table_name_norm;\n\n    rewrite_expr_column_ref_with_context(\n        e,\n        trigger_table,\n        trigger_table_name,\n        old_col_norm,\n        new_col_norm,\n        is_renaming_trigger_table,\n        None,\n        from_target,\n    )\n}\n\n/// Validate all column references in a trigger after a DROP COLUMN operation.\n/// Like SQLite, this re-validates the entire trigger — any unresolvable column\n/// reference (whether related to the drop or pre-existing) causes the drop to fail.\n///\n/// Returns `Some(column_name)` if a bad column reference is found, `None` if all OK.\nfn validate_trigger_columns_after_drop(\n    trigger: &crate::schema::Trigger,\n    altered_table_norm: &str,\n    post_drop_table: &BTreeTable,\n    resolver: &Resolver,\n    database_id: usize,\n) -> Result<Option<String>> {\n    let trigger_table_norm = normalize_ident(&trigger.table_name);\n\n    // Determine the trigger's owning table columns (post-drop if it's the altered table)\n    let owning_table_columns: Option<Vec<String>> = if trigger_table_norm == *altered_table_norm {\n        Some(\n            post_drop_table\n                .columns\n                .iter()\n                .filter_map(|c| c.name.as_deref().map(normalize_ident))\n                .collect(),\n        )\n    } else {\n        resolver.with_schema(database_id, |s| {\n            s.get_table(&trigger_table_norm).and_then(|t| {\n                t.btree().map(|bt| {\n                    bt.columns\n                        .iter()\n                        .filter_map(|c| c.name.as_deref().map(normalize_ident))\n                        .collect()\n                })\n            })\n        })\n    };\n\n    // Helper: walk an expression (including subqueries) and find bad column refs\n    let find_bad_in_expr = |expr: &ast::Expr,\n                            valid_columns: &[String],\n                            owning_cols: &Option<Vec<String>>|\n     -> Result<Option<String>> {\n        let mut bad: Option<String> = None;\n        crate::util::walk_expr_with_subqueries(expr, &mut |e: &ast::Expr| {\n            if bad.is_some() {\n                return Ok(WalkControl::SkipChildren);\n            }\n            bad = check_column_ref_valid(\n                e,\n                valid_columns,\n                owning_cols,\n                altered_table_norm,\n                post_drop_table,\n                resolver,\n                database_id,\n            );\n            if bad.is_some() {\n                Ok(WalkControl::SkipChildren)\n            } else {\n                Ok(WalkControl::Continue)\n            }\n        })?;\n        Ok(bad)\n    };\n\n    // Helper: walk a SELECT (including subqueries) and find bad column refs\n    let find_bad_in_select = |select: &ast::Select,\n                              valid_columns: &[String],\n                              owning_cols: &Option<Vec<String>>|\n     -> Result<Option<String>> {\n        let mut bad: Option<String> = None;\n        crate::util::walk_select_expressions(select, &mut |e: &ast::Expr| {\n            if bad.is_some() {\n                return Ok(WalkControl::SkipChildren);\n            }\n            bad = check_column_ref_valid(\n                e,\n                valid_columns,\n                owning_cols,\n                altered_table_norm,\n                post_drop_table,\n                resolver,\n                database_id,\n            );\n            if bad.is_some() {\n                Ok(WalkControl::SkipChildren)\n            } else {\n                Ok(WalkControl::Continue)\n            }\n        })?;\n        Ok(bad)\n    };\n\n    // Validate WHEN clause — NEW/OLD refs resolve against the trigger's owning table\n    if let Some(ref when_expr) = trigger.when_clause {\n        if let Some(ref cols) = owning_table_columns {\n            if let Some(bad) = find_bad_in_expr(when_expr, cols, &owning_table_columns)? {\n                return Ok(Some(bad));\n            }\n        }\n    }\n\n    for cmd in &trigger.commands {\n        match cmd {\n            ast::TriggerCmd::Update {\n                tbl_name,\n                sets,\n                where_clause,\n                ..\n            } => {\n                let cmd_table_norm = normalize_ident(tbl_name.as_str());\n                let cmd_table_cols = get_table_columns(\n                    &cmd_table_norm,\n                    altered_table_norm,\n                    post_drop_table,\n                    resolver,\n                    database_id,\n                );\n                // Check expressions in SET values and WHERE — these can reference\n                // both the command target table and the trigger's owning table (via NEW/OLD).\n                // Note: SET target column names are NOT checked here — SQLite defers\n                // that validation to trigger execution time.\n                let all_cols = merge_cols(&cmd_table_cols, &owning_table_columns);\n                for set in sets {\n                    if let Some(bad) =\n                        find_bad_in_expr(&set.expr, &all_cols, &owning_table_columns)?\n                    {\n                        return Ok(Some(bad));\n                    }\n                }\n                if let Some(ref where_expr) = where_clause {\n                    if let Some(bad) =\n                        find_bad_in_expr(where_expr, &all_cols, &owning_table_columns)?\n                    {\n                        return Ok(Some(bad));\n                    }\n                }\n            }\n            // Note: INSERT column lists are NOT checked — SQLite defers that\n            // validation to trigger execution time. But expressions in\n            // INSERT ... VALUES and INSERT ... SELECT are checked.\n            ast::TriggerCmd::Insert { select, .. } => {\n                let all_cols = merge_cols(&owning_table_columns, &None);\n                if let Some(bad) = find_bad_in_select(select, &all_cols, &owning_table_columns)? {\n                    return Ok(Some(bad));\n                }\n            }\n            ast::TriggerCmd::Delete {\n                tbl_name,\n                where_clause,\n                ..\n            } => {\n                let cmd_table_norm = normalize_ident(tbl_name.as_str());\n                let cmd_table_cols = get_table_columns(\n                    &cmd_table_norm,\n                    altered_table_norm,\n                    post_drop_table,\n                    resolver,\n                    database_id,\n                );\n                if let Some(ref where_expr) = where_clause {\n                    let all_cols = merge_cols(&cmd_table_cols, &owning_table_columns);\n                    if let Some(bad) =\n                        find_bad_in_expr(where_expr, &all_cols, &owning_table_columns)?\n                    {\n                        return Ok(Some(bad));\n                    }\n                }\n            }\n            ast::TriggerCmd::Select(select) => {\n                let all_cols = merge_cols(&owning_table_columns, &None);\n                if let Some(bad) = find_bad_in_select(select, &all_cols, &owning_table_columns)? {\n                    return Ok(Some(bad));\n                }\n            }\n        }\n    }\n\n    Ok(None)\n}\n\n/// Check a single expression node for invalid column references after a DROP COLUMN.\n/// Returns `Some(bad_column_description)` if invalid, `None` if OK.\nfn check_column_ref_valid(\n    e: &ast::Expr,\n    valid_columns: &[String],\n    owning_table_columns: &Option<Vec<String>>,\n    altered_table_norm: &str,\n    post_drop_table: &BTreeTable,\n    resolver: &Resolver,\n    database_id: usize,\n) -> Option<String> {\n    match e {\n        ast::Expr::Id(col) => {\n            let col_norm = normalize_ident(col.as_str());\n            if !valid_columns.contains(&col_norm) {\n                return Some(col.to_string());\n            }\n        }\n        ast::Expr::Qualified(ns, col) | ast::Expr::DoublyQualified(_, ns, col) => {\n            let ns_norm = normalize_ident(ns.as_str());\n            let col_norm = normalize_ident(col.as_str());\n            if ns_norm.eq_ignore_ascii_case(\"new\") || ns_norm.eq_ignore_ascii_case(\"old\") {\n                // NEW.col / OLD.col — validate against owning table columns\n                if let Some(ref cols) = owning_table_columns {\n                    if !cols.contains(&col_norm) {\n                        return Some(format!(\"{ns}.{col}\"));\n                    }\n                }\n            } else {\n                // table.col — validate against that table's columns\n                let table_cols = get_table_columns(\n                    &ns_norm,\n                    altered_table_norm,\n                    post_drop_table,\n                    resolver,\n                    database_id,\n                );\n                if let Some(cols) = table_cols {\n                    if !cols.contains(&col_norm) {\n                        return Some(format!(\"{ns}.{col}\"));\n                    }\n                }\n            }\n        }\n        _ => {}\n    }\n    None\n}\n\n/// Get the column names for a table, using the post-drop schema if it's the altered table.\nfn get_table_columns(\n    table_name_norm: &str,\n    altered_table_norm: &str,\n    post_drop_table: &BTreeTable,\n    resolver: &Resolver,\n    database_id: usize,\n) -> Option<Vec<String>> {\n    if table_name_norm == altered_table_norm {\n        Some(\n            post_drop_table\n                .columns\n                .iter()\n                .filter_map(|c| c.name.as_deref().map(normalize_ident))\n                .collect(),\n        )\n    } else {\n        resolver.with_schema(database_id, |s| {\n            s.get_table(table_name_norm).and_then(|t| {\n                t.btree().map(|bt| {\n                    bt.columns\n                        .iter()\n                        .filter_map(|c| c.name.as_deref().map(normalize_ident))\n                        .collect()\n                })\n            })\n        })\n    }\n}\n\n/// Merge two optional column lists into one combined list for expression validation.\nfn merge_cols(a: &Option<Vec<String>>, b: &Option<Vec<String>>) -> Vec<String> {\n    let mut result = Vec::new();\n    if let Some(cols) = a {\n        result.extend(cols.iter().cloned());\n    }\n    if let Some(cols) = b {\n        for c in cols {\n            if !result.contains(c) {\n                result.push(c.clone());\n            }\n        }\n    }\n    result\n}\n"
  },
  {
    "path": "core/translate/analyze.rs",
    "content": "use crate::sync::Arc;\n\nuse crate::{\n    bail_parse_error,\n    function::{Func, FuncCtx, ScalarFunc},\n    schema::{BTreeTable, Index, RESERVED_TABLE_PREFIXES},\n    storage::pager::CreateBTreeFlags,\n    translate::{\n        emitter::Resolver,\n        schema::{emit_schema_entry, SchemaEntryType, SQLITE_TABLEID},\n    },\n    util::normalize_ident,\n    vdbe::{\n        affinity::Affinity,\n        builder::{CursorType, ProgramBuilder},\n        insn::{to_u16, CmpInsFlags, Cookie, Insn, RegisterOrLiteral},\n    },\n    Result,\n};\nuse turso_parser::ast;\n\n/// A table paired with an optional specific index to analyze.\ntype AnalyzeTarget = (Arc<BTreeTable>, Option<Arc<Index>>);\n\n/// Resolve the target database_id and collect analyze targets from the QualifiedName.\n///\n/// ANALYZE can target:\n/// - Nothing (None): analyze all tables in main (database_id = 0)\n/// - A database name (\"main\", \"aux\"): analyze all tables in that database\n/// - A table name: analyze all indexes on that table\n/// - A qualified table name (db.table): analyze in the specified database\n/// - An index name: analyze just that index\nfn resolve_analyze_targets(\n    target_opt: &Option<ast::QualifiedName>,\n    resolver: &Resolver,\n) -> Result<(usize, Vec<AnalyzeTarget>)> {\n    match target_opt {\n        Some(target) => {\n            let normalized = normalize_ident(target.name.as_str());\n\n            // If db_name is specified, resolve to that database\n            if let Some(db_name) = &target.db_name {\n                let database_id = resolver.resolve_database_id(target)?;\n                let db_normalized = normalize_ident(db_name.as_str());\n\n                // \"ANALYZE db.table\" — the name part is the table/index\n                // But first check if the name is actually a database name too (shouldn't be with db_name set)\n                let targets = resolve_targets_in_db(&normalized, database_id, resolver)?;\n                if targets.is_empty() {\n                    bail_parse_error!(\"no such table or index: {}.{}\", db_normalized, normalized);\n                }\n                return Ok((database_id, targets));\n            }\n\n            // No db_name — check if the name is a database name first\n            if normalized.eq_ignore_ascii_case(\"main\") {\n                let targets = collect_all_tables_in_db(0, resolver);\n                return Ok((0, targets));\n            }\n\n            // Check if it's an attached database name\n            if let Some((db_id, _)) = resolver.get_attached_database(&normalized) {\n                let targets = collect_all_tables_in_db(db_id, resolver);\n                return Ok((db_id, targets));\n            }\n\n            // Not a database name — search main schema for table/index\n            let targets = resolve_targets_in_db(&normalized, 0, resolver)?;\n            if targets.is_empty() {\n                bail_parse_error!(\"no such table or index: {}\", target.name);\n            }\n            Ok((0, targets))\n        }\n        None => {\n            // ANALYZE with no target — analyze all tables in main\n            let targets = collect_all_tables_in_db(0, resolver);\n            Ok((0, targets))\n        }\n    }\n}\n\n/// Collect all user tables in the given database.\nfn collect_all_tables_in_db(database_id: usize, resolver: &Resolver) -> Vec<AnalyzeTarget> {\n    resolver.with_schema(database_id, |schema| {\n        schema\n            .tables\n            .iter()\n            .filter_map(|(name, table)| {\n                if RESERVED_TABLE_PREFIXES\n                    .iter()\n                    .any(|prefix| name.starts_with(prefix))\n                {\n                    return None;\n                }\n                table.btree().map(|bt| (bt, None))\n            })\n            .collect()\n    })\n}\n\n/// Resolve a name as a table or index within a specific database.\nfn resolve_targets_in_db(\n    name: &str,\n    database_id: usize,\n    resolver: &Resolver,\n) -> Result<Vec<AnalyzeTarget>> {\n    // Try as a table first\n    let table_opt: Option<Arc<BTreeTable>> =\n        resolver.with_schema(database_id, |s| s.get_btree_table(name));\n    if let Some(table) = table_opt {\n        return Ok(vec![(table, None)]);\n    }\n\n    // Try as an index\n    let found: Option<(Arc<BTreeTable>, Arc<Index>)> =\n        resolver.with_schema(database_id, |schema| {\n            for (table_name, indexes) in schema.indexes.iter() {\n                if let Some(index) = indexes\n                    .iter()\n                    .find(|idx| idx.name.eq_ignore_ascii_case(name))\n                {\n                    if let Some(table) = schema.get_btree_table(table_name) {\n                        return Some((table, index.clone()));\n                    }\n                }\n            }\n            None\n        });\n    if let Some((table, index)) = found {\n        return Ok(vec![(table, Some(index))]);\n    }\n\n    Ok(vec![])\n}\n\npub fn translate_analyze(\n    target_opt: Option<ast::QualifiedName>,\n    resolver: &Resolver,\n    program: &mut ProgramBuilder,\n) -> Result<()> {\n    // Resolve the target database and collect analyze targets.\n    let (database_id, analyze_targets) = resolve_analyze_targets(&target_opt, resolver)?;\n\n    if analyze_targets.is_empty() {\n        return Ok(());\n    }\n\n    // Register a write transaction for the target database so that the\n    // epilogue emits a Transaction instruction (which starts the MVCC\n    // exclusive transaction required by OpenWrite on sqlite_schema).\n    if crate::is_attached_db(database_id) {\n        let schema_cookie = resolver.with_schema(database_id, |s| s.schema_version);\n        program.begin_write_on_database(database_id, schema_cookie);\n    }\n    program.begin_write_operation();\n\n    // This is emitted early because SQLite does, and thus generated VDBE matches a bit closer.\n    let null_reg = program.alloc_register();\n    program.emit_insn(Insn::Null {\n        dest: null_reg,\n        dest_end: None,\n    });\n\n    // After preparing/creating sqlite_stat1, we need to OpenWrite it, and how we acquire\n    // the necessary BTreeTable for cursor creation and root page for the instruction changes\n    // depending on which path we take.\n    let sqlite_stat1_btreetable: Arc<BTreeTable>;\n    let sqlite_stat1_source: RegisterOrLiteral<_>;\n\n    let stat1_table: Option<Arc<BTreeTable>> =\n        resolver.with_schema(database_id, |s| s.get_btree_table(\"sqlite_stat1\"));\n    if let Some(sqlite_stat1) = stat1_table {\n        sqlite_stat1_btreetable = sqlite_stat1.clone();\n        sqlite_stat1_source = RegisterOrLiteral::Literal(sqlite_stat1.root_page);\n    } else {\n        // FIXME: Emit ReadCookie 0 3 2\n        // FIXME: Emit If 3 +2 0\n        // FIXME: Emit SetCookie 0 2 4\n        // FIXME: Emit SetCookie 0 5 1\n\n        // See the large comment in schema.rs:translate_create_table about\n        // deviating from SQLite codegen, as the same deviation is being done\n        // here.\n\n        // TODO: this code half-copies translate_create_table, because there's\n        // no way to get the table_root_reg back out, and it's needed for later\n        // codegen to open the table we just created.  It's worth a future\n        // refactoring to remove the duplication one the rest of ANALYZE is\n        // implemented.\n        let table_root_reg = program.alloc_register();\n        program.emit_insn(Insn::CreateBtree {\n            db: database_id,\n            root: table_root_reg,\n            flags: CreateBTreeFlags::new_table(),\n        });\n        let sql = \"CREATE TABLE sqlite_stat1(tbl,idx,stat)\";\n        // The root_page==0 is false, but we don't rely on it, and there's no\n        // way to initialize it with a correct value.\n        sqlite_stat1_btreetable = Arc::new(BTreeTable::from_sql(sql, 0)?);\n        sqlite_stat1_source = RegisterOrLiteral::Register(table_root_reg);\n\n        let table = resolver\n            .with_schema(database_id, |s| s.get_btree_table(SQLITE_TABLEID))\n            .unwrap();\n        let sqlite_schema_cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(table));\n        program.emit_insn(Insn::OpenWrite {\n            cursor_id: sqlite_schema_cursor_id,\n            root_page: 1i64.into(),\n            db: database_id,\n        });\n\n        // Add the table entry to sqlite_schema\n        emit_schema_entry(\n            program,\n            resolver,\n            sqlite_schema_cursor_id,\n            None,\n            SchemaEntryType::Table,\n            \"sqlite_stat1\",\n            \"sqlite_stat1\",\n            table_root_reg,\n            Some(sql.to_string()),\n        )?;\n\n        let parse_schema_where_clause =\n            \"tbl_name = 'sqlite_stat1' AND type != 'trigger'\".to_string();\n        program.emit_insn(Insn::ParseSchema {\n            db: database_id,\n            where_clause: Some(parse_schema_where_clause),\n        });\n\n        // Bump schema cookie so subsequent statements reparse schema.\n        let schema_version = resolver.with_schema(database_id, |s| s.schema_version);\n        program.emit_insn(Insn::SetCookie {\n            db: database_id,\n            cookie: Cookie::SchemaVersion,\n            value: schema_version as i32 + 1,\n            p5: 0,\n        });\n    };\n\n    // Count the number of rows in the target table(s), and insert into sqlite_stat1.\n    let sqlite_stat1 = sqlite_stat1_btreetable;\n    let stat_cursor = program.alloc_cursor_id(CursorType::BTreeTable(sqlite_stat1));\n    program.emit_insn(Insn::OpenWrite {\n        cursor_id: stat_cursor,\n        root_page: sqlite_stat1_source,\n        db: database_id,\n    });\n\n    for (target_table, target_index) in analyze_targets {\n        if !target_table.has_rowid {\n            bail_parse_error!(\"ANALYZE on tables without rowid is not supported\");\n        }\n\n        // Remove existing stat rows for this target before inserting fresh ones.\n        let rewind_done = program.allocate_label();\n        program.emit_insn(Insn::Rewind {\n            cursor_id: stat_cursor,\n            pc_if_empty: rewind_done,\n        });\n        let loop_start = program.allocate_label();\n        program.preassign_label_to_next_insn(loop_start);\n\n        let tbl_col_reg = program.alloc_register();\n        program.emit_insn(Insn::Column {\n            cursor_id: stat_cursor,\n            column: 0,\n            dest: tbl_col_reg,\n            default: None,\n        });\n        let target_tbl_reg = program.alloc_register();\n        program.emit_insn(Insn::String8 {\n            value: target_table.name.to_string(),\n            dest: target_tbl_reg,\n        });\n        program.mark_last_insn_constant();\n\n        let skip_label = program.allocate_label();\n        program.emit_insn(Insn::Ne {\n            lhs: tbl_col_reg,\n            rhs: target_tbl_reg,\n            target_pc: skip_label,\n            flags: Default::default(),\n            collation: None,\n        });\n\n        if let Some(idx) = target_index.clone() {\n            let idx_col_reg = program.alloc_register();\n            program.emit_insn(Insn::Column {\n                cursor_id: stat_cursor,\n                column: 1,\n                dest: idx_col_reg,\n                default: None,\n            });\n            let target_idx_reg = program.alloc_register();\n            program.emit_insn(Insn::String8 {\n                value: idx.name.to_string(),\n                dest: target_idx_reg,\n            });\n            program.mark_last_insn_constant();\n            program.emit_insn(Insn::Ne {\n                lhs: idx_col_reg,\n                rhs: target_idx_reg,\n                target_pc: skip_label,\n                flags: Default::default(),\n                collation: None,\n            });\n            let rowid_reg = program.alloc_register();\n            program.emit_insn(Insn::RowId {\n                cursor_id: stat_cursor,\n                dest: rowid_reg,\n            });\n            program.emit_insn(Insn::Delete {\n                cursor_id: stat_cursor,\n                table_name: \"sqlite_stat1\".to_string(),\n                is_part_of_update: false,\n            });\n            program.emit_insn(Insn::Next {\n                cursor_id: stat_cursor,\n                pc_if_next: loop_start,\n            });\n        } else {\n            let rowid_reg = program.alloc_register();\n            program.emit_insn(Insn::RowId {\n                cursor_id: stat_cursor,\n                dest: rowid_reg,\n            });\n            program.emit_insn(Insn::Delete {\n                cursor_id: stat_cursor,\n                table_name: \"sqlite_stat1\".to_string(),\n                is_part_of_update: false,\n            });\n            program.emit_insn(Insn::Next {\n                cursor_id: stat_cursor,\n                pc_if_next: loop_start,\n            });\n        }\n\n        program.preassign_label_to_next_insn(skip_label);\n        program.emit_insn(Insn::Next {\n            cursor_id: stat_cursor,\n            pc_if_next: loop_start,\n        });\n        program.preassign_label_to_next_insn(rewind_done);\n\n        let target_cursor = program.alloc_cursor_id(CursorType::BTreeTable(target_table.clone()));\n        program.emit_insn(Insn::OpenRead {\n            cursor_id: target_cursor,\n            root_page: target_table.root_page,\n            db: database_id,\n        });\n        let rowid_reg = program.alloc_register();\n        let tablename_reg = program.alloc_register();\n        let indexname_reg = program.alloc_register();\n        let stat_text_reg = program.alloc_register();\n        let record_reg = program.alloc_register();\n        let count_reg = program.alloc_register();\n        program.emit_insn(Insn::String8 {\n            value: target_table.name.to_string(),\n            dest: tablename_reg,\n        });\n        program.mark_last_insn_constant();\n        program.emit_insn(Insn::Count {\n            cursor_id: target_cursor,\n            target_reg: count_reg,\n            exact: true,\n        });\n        let after_insert = program.allocate_label();\n        program.emit_insn(Insn::IfNot {\n            reg: count_reg,\n            target_pc: after_insert,\n            jump_if_null: false,\n        });\n        program.emit_insn(Insn::Null {\n            dest: indexname_reg,\n            dest_end: None,\n        });\n        // stat = CAST(count AS TEXT)\n        program.emit_insn(Insn::Copy {\n            src_reg: count_reg,\n            dst_reg: stat_text_reg,\n            extra_amount: 0,\n        });\n        program.emit_insn(Insn::Cast {\n            reg: stat_text_reg,\n            affinity: Affinity::Text,\n        });\n        program.emit_insn(Insn::MakeRecord {\n            start_reg: to_u16(tablename_reg),\n            count: to_u16(3),\n            dest_reg: to_u16(record_reg),\n            index_name: None,\n            affinity_str: None,\n        });\n        program.emit_insn(Insn::NewRowid {\n            cursor: stat_cursor,\n            rowid_reg,\n            prev_largest_reg: 0,\n        });\n        // FIXME: SQLite sets OPFLAG_APPEND on the insert, but that's not supported in turso right now.\n        // SQLite doesn't emit the table name, but like... why not?\n        program.emit_insn(Insn::Insert {\n            cursor: stat_cursor,\n            key_reg: rowid_reg,\n            record_reg,\n            flag: Default::default(),\n            table_name: \"sqlite_stat1\".to_string(),\n        });\n        program.preassign_label_to_next_insn(after_insert);\n        // Emit index stats for this table (or for a single index target).\n        let indexes: Vec<Arc<Index>> = match target_index {\n            Some(idx) => vec![idx],\n            None => resolver.with_schema(database_id, |s| {\n                s.get_indices(&target_table.name)\n                    .filter(|idx| idx.index_method.is_none()) // skip custom for now\n                    .cloned()\n                    .collect()\n            }),\n        };\n        for index in indexes {\n            emit_index_stats(program, stat_cursor, &target_table, &index, database_id);\n        }\n    }\n\n    // FIXME: Emit LoadAnalysis\n    // FIXME: Emit Expire\n    Ok(())\n}\n\n/// Emit VDBE code to gather and insert statistics for a single index.\n///\n/// This uses the stat_init/stat_push/stat_get functions to collect statistics.\n/// The bytecode scans the index in sorted order, comparing columns to detect\n/// when prefixes change, and calls stat_push with the change index.\n///\n/// The stat string format is: \"total avg1 avg2 avg3\"\n/// where avgN = ceil(total / distinctN) = average rows per distinct prefix\nfn emit_index_stats(\n    program: &mut ProgramBuilder,\n    stat_cursor: usize,\n    table: &Arc<BTreeTable>,\n    index: &Arc<Index>,\n    database_id: usize,\n) {\n    let n_cols = index.columns.len();\n    if n_cols == 0 {\n        return;\n    }\n\n    // Open the index cursor\n    let idx_cursor = program.alloc_cursor_id(CursorType::BTreeIndex(index.clone()));\n    program.emit_insn(Insn::OpenRead {\n        cursor_id: idx_cursor,\n        root_page: index.root_page,\n        db: database_id,\n    });\n\n    // Allocate registers contiguously for stat_push(accum, chng):\n    let reg_accum = program.alloc_register();\n    let reg_chng = program.alloc_register();\n\n    // Registers for previous row values and comparison temp\n    let reg_prev_base = program.alloc_registers(n_cols);\n    let reg_temp = program.alloc_register();\n\n    // Initialize the accumulator with stat_init(n_cols)\n    // Reuse reg_chng temporarily for the n_cols argument\n    program.emit_insn(Insn::Integer {\n        value: n_cols as i64,\n        dest: reg_chng,\n    });\n    program.emit_insn(Insn::Function {\n        constant_mask: 0,\n        start_reg: reg_chng,\n        dest: reg_accum,\n        func: FuncCtx {\n            func: Func::Scalar(ScalarFunc::StatInit),\n            arg_count: 1,\n        },\n    });\n\n    // Labels for control flow\n    let lbl_empty = program.allocate_label();\n    let lbl_loop = program.allocate_label();\n    let lbl_stat_push = program.allocate_label();\n\n    // We need one label per column for the update_prev jump targets\n    let lbl_update_prev: Vec<_> = (0..n_cols).map(|_| program.allocate_label()).collect();\n\n    // Rewind the index cursor; if empty, skip to end\n    program.emit_insn(Insn::Rewind {\n        cursor_id: idx_cursor,\n        pc_if_empty: lbl_empty,\n    });\n\n    // First row: set chng=0 and jump to update all prev columns\n    program.emit_insn(Insn::Integer {\n        value: 0,\n        dest: reg_chng,\n    });\n    program.emit_insn(Insn::Goto {\n        target_pc: lbl_update_prev[0],\n    });\n\n    // Main loop: compare columns to find change point\n    program.preassign_label_to_next_insn(lbl_loop);\n\n    // Set reg_chng = 0, then check each column\n    program.emit_insn(Insn::Integer {\n        value: 0,\n        dest: reg_chng,\n    });\n\n    for (i, lbl) in lbl_update_prev.iter().enumerate().take(n_cols) {\n        program.emit_insn(Insn::Column {\n            cursor_id: idx_cursor,\n            column: i,\n            dest: reg_temp,\n            default: None,\n        });\n        program.emit_insn(Insn::Ne {\n            lhs: reg_temp,\n            rhs: reg_prev_base + i,\n            target_pc: *lbl,\n            flags: CmpInsFlags::default().null_eq(),\n            collation: index.columns[i].collation,\n        });\n        // If columns match, increment chng and continue to next column\n        if i < n_cols - 1 {\n            program.emit_insn(Insn::Integer {\n                value: (i + 1) as i64,\n                dest: reg_chng,\n            });\n        }\n    }\n\n    // All columns equal - chng = n_cols (duplicate row), jump over update section to stat_push\n    program.emit_insn(Insn::Integer {\n        value: n_cols as i64,\n        dest: reg_chng,\n    });\n    program.emit_insn(Insn::Goto {\n        target_pc: lbl_stat_push,\n    });\n\n    // Update prev section: emit n_cols consecutive Column instructions that cascade\n    // When col i differs from prev, jump here to update prev[i], prev[i+1], ..., prev[n_cols-1]\n    for (i, lbl) in lbl_update_prev.iter().enumerate().take(n_cols) {\n        program.preassign_label_to_next_insn(*lbl);\n        program.emit_insn(Insn::Column {\n            cursor_id: idx_cursor,\n            column: i,\n            dest: reg_prev_base + i,\n            default: None,\n        });\n        // Fall through to next column update, then to stat_push\n    }\n\n    program.preassign_label_to_next_insn(lbl_stat_push);\n    program.emit_insn(Insn::Function {\n        constant_mask: 0,\n        start_reg: reg_accum,\n        dest: reg_accum,\n        func: FuncCtx {\n            func: Func::Scalar(ScalarFunc::StatPush),\n            arg_count: 2,\n        },\n    });\n\n    // Next iteration\n    program.emit_insn(Insn::Next {\n        cursor_id: idx_cursor,\n        pc_if_next: lbl_loop,\n    });\n\n    // stat_get(accum) to get the final stat string\n    let reg_stat = program.alloc_register();\n    program.emit_insn(Insn::Function {\n        constant_mask: 0,\n        start_reg: reg_accum,\n        dest: reg_stat,\n        func: FuncCtx {\n            func: Func::Scalar(ScalarFunc::StatGet),\n            arg_count: 1,\n        },\n    });\n\n    // Skip insert if stat is NULL (empty index)\n    program.emit_insn(Insn::IsNull {\n        reg: reg_stat,\n        target_pc: lbl_empty,\n    });\n\n    // Insert record into sqlite_stat1\n    // Allocate contiguous registers for MakeRecord: tablename, indexname, stat\n    let record_start = program.alloc_registers(3);\n    program.emit_insn(Insn::String8 {\n        value: table.name.to_string(),\n        dest: record_start,\n    });\n    program.mark_last_insn_constant();\n    program.emit_insn(Insn::String8 {\n        value: index.name.to_string(),\n        dest: record_start + 1,\n    });\n    program.mark_last_insn_constant();\n    program.emit_insn(Insn::Copy {\n        src_reg: reg_stat,\n        dst_reg: record_start + 2,\n        extra_amount: 0,\n    });\n\n    let idx_record_reg = program.alloc_register();\n    program.emit_insn(Insn::MakeRecord {\n        start_reg: to_u16(record_start),\n        count: to_u16(3),\n        dest_reg: to_u16(idx_record_reg),\n        index_name: None,\n        affinity_str: None,\n    });\n\n    let idx_rowid_reg = program.alloc_register();\n    program.emit_insn(Insn::NewRowid {\n        cursor: stat_cursor,\n        rowid_reg: idx_rowid_reg,\n        prev_largest_reg: 0,\n    });\n    program.emit_insn(Insn::Insert {\n        cursor: stat_cursor,\n        key_reg: idx_rowid_reg,\n        record_reg: idx_record_reg,\n        flag: Default::default(),\n        table_name: \"sqlite_stat1\".to_string(),\n    });\n\n    // Label for empty index case, just skip the insert\n    program.preassign_label_to_next_insn(lbl_empty);\n}\n"
  },
  {
    "path": "core/translate/attach.rs",
    "content": "use crate::function::{Func, ScalarFunc};\nuse crate::translate::emitter::Resolver;\nuse crate::translate::expr::{sanitize_string, translate_expr};\nuse crate::translate::{ProgramBuilder, ProgramBuilderOpts};\nuse crate::util::normalize_ident;\nuse crate::vdbe::insn::Insn;\nuse crate::Connection;\nuse crate::Result;\nuse std::sync::Arc;\nuse turso_parser::ast::{Expr, Literal};\n\n/// Translate ATTACH statement\n/// SQLite implements ATTACH as a function call to sqlite_attach()\npub fn translate_attach(\n    expr: &Expr,\n    resolver: &Resolver,\n    db_name: &Expr,\n    key: &Option<Box<Expr>>,\n    program: &mut ProgramBuilder,\n    connection: Arc<Connection>,\n) -> Result<()> {\n    if !connection.experimental_attach_enabled() {\n        return Err(crate::LimboError::ParseError(\n            \"ATTACH is an experimental feature. Enable with --experimental-attach flag\".to_string(),\n        ));\n    }\n    // SQLite treats ATTACH as a function call to sqlite_attach(filename, dbname, key)\n    // We'll allocate registers for the arguments and call the function\n\n    program.extend(&ProgramBuilderOpts {\n        num_cursors: 0,\n        approx_num_insns: 10,\n        approx_num_labels: 0,\n    });\n\n    let arg_reg = program.alloc_registers(4); // 3 for args + 1 for result\n\n    // Load filename argument\n    // Handle different expression types as string literals for filenames\n    match expr {\n        Expr::Literal(Literal::String(s)) => {\n            // For ATTACH, string literals should be used directly (without quotes)\n            program.emit_insn(Insn::String8 {\n                value: sanitize_string(s),\n                dest: arg_reg,\n            });\n        }\n        Expr::Qualified(_, _) => {\n            // For ATTACH, qualified expressions like \"foo.db\" should be treated as filename strings\n            let filename = format!(\"{expr}\");\n            program.emit_insn(Insn::String8 {\n                value: filename,\n                dest: arg_reg,\n            });\n        }\n        Expr::Id(id) => {\n            // For ATTACH, identifiers should be treated as filename strings\n            // Use normalize_ident to strip quotes from double-quoted identifiers\n            program.emit_insn(Insn::String8 {\n                value: normalize_ident(id.as_str()),\n                dest: arg_reg,\n            });\n        }\n        _ => {\n            translate_expr(program, None, expr, arg_reg, resolver)?;\n        }\n    }\n\n    // Load database name argument\n    // Handle different expression types as string literals for database names\n    match db_name {\n        Expr::Literal(Literal::String(s)) => {\n            // For ATTACH, string literals should be used directly (without quotes)\n            program.emit_insn(Insn::String8 {\n                value: sanitize_string(s),\n                dest: arg_reg + 1,\n            });\n        }\n        Expr::Qualified(_, _) => {\n            // For ATTACH, qualified expressions should be treated as name strings\n            let db_name_str = format!(\"{db_name}\");\n            program.emit_insn(Insn::String8 {\n                value: db_name_str,\n                dest: arg_reg + 1,\n            });\n        }\n        Expr::Id(id) => {\n            // For ATTACH, identifiers should be treated as name strings\n            // Use normalize_ident to strip quotes from double-quoted identifiers\n            program.emit_insn(Insn::String8 {\n                value: normalize_ident(id.as_str()),\n                dest: arg_reg + 1,\n            });\n        }\n        _ => {\n            translate_expr(program, None, db_name, arg_reg + 1, resolver)?;\n        }\n    }\n\n    // Load key argument (NULL if not provided)\n    if let Some(key_expr) = key {\n        translate_expr(program, None, key_expr, arg_reg + 2, resolver)?;\n    } else {\n        program.emit_insn(Insn::Null {\n            dest: arg_reg + 2,\n            dest_end: None,\n        });\n    }\n\n    // Call sqlite_attach function\n    program.emit_insn(Insn::Function {\n        constant_mask: 0,\n        start_reg: arg_reg,\n        dest: arg_reg + 3, // Result register (not used but required)\n        func: crate::function::FuncCtx {\n            func: Func::Scalar(ScalarFunc::Attach),\n            arg_count: 3,\n        },\n    });\n\n    Ok(())\n}\n\n/// Translate DETACH statement\n/// SQLite implements DETACH as a function call to sqlite_detach()\npub fn translate_detach(\n    expr: &Expr,\n    resolver: &Resolver,\n    program: &mut ProgramBuilder,\n    connection: Arc<Connection>,\n) -> Result<()> {\n    if !connection.experimental_attach_enabled() {\n        return Err(crate::LimboError::ParseError(\n            \"DETACH is an experimental feature. Enable with --experimental-attach flag\".to_string(),\n        ));\n    }\n    // SQLite treats DETACH as a function call to sqlite_detach(dbname)\n\n    program.extend(&ProgramBuilderOpts {\n        num_cursors: 0,\n        approx_num_insns: 5,\n        approx_num_labels: 0,\n    });\n\n    let arg_reg = program.alloc_registers(2); // 1 for arg + 1 for result\n\n    // Load database name argument\n    // Handle different expression types as string literals for database names\n    match expr {\n        Expr::Literal(Literal::String(s)) => {\n            // For DETACH, string literals should be used directly (without quotes)\n            program.emit_insn(Insn::String8 {\n                value: sanitize_string(s),\n                dest: arg_reg,\n            });\n        }\n        Expr::Qualified(_, _) => {\n            // For DETACH, qualified expressions should be treated as name strings\n            let db_name_str = format!(\"{expr}\");\n            program.emit_insn(Insn::String8 {\n                value: db_name_str,\n                dest: arg_reg,\n            });\n        }\n        Expr::Id(id) => {\n            // For DETACH, identifiers should be treated as name strings\n            // Use normalize_ident to strip quotes from double-quoted identifiers\n            program.emit_insn(Insn::String8 {\n                value: normalize_ident(id.as_str()),\n                dest: arg_reg,\n            });\n        }\n        _ => {\n            translate_expr(program, None, expr, arg_reg, resolver)?;\n        }\n    }\n\n    // Call sqlite_detach function\n    program.emit_insn(Insn::Function {\n        constant_mask: 0,\n        start_reg: arg_reg,\n        dest: arg_reg + 1, // Result register (not used but required)\n        func: crate::function::FuncCtx {\n            func: Func::Scalar(ScalarFunc::Detach),\n            arg_count: 1,\n        },\n    });\n\n    Ok(())\n}\n"
  },
  {
    "path": "core/translate/collate.rs",
    "content": "use std::{cmp::Ordering, str::FromStr as _};\n\nuse turso_parser::ast::Expr;\n\nuse crate::{\n    translate::{\n        expr::{walk_expr, WalkControl},\n        plan::TableReferences,\n    },\n    Result,\n};\n\n// TODO: in the future allow user to define collation sequences\n// Will have to meddle with ffi for this\n#[derive(\n    Debug, Clone, Copy, Eq, PartialEq, strum_macros::Display, strum_macros::EnumString, Default,\n)]\n#[strum(ascii_case_insensitive)]\n/// **Pre defined collation sequences**\\\n/// Collating functions only matter when comparing string values.\n/// Numeric values are always compared numerically, and BLOBs are always compared byte-by-byte using memcmp().\n#[repr(u8)]\npub enum CollationSeq {\n    Unset = 0,\n    #[default]\n    Binary = 1,\n    NoCase = 2,\n    Rtrim = 3,\n}\n\nimpl CollationSeq {\n    pub fn new(collation: &str) -> crate::Result<Self> {\n        CollationSeq::from_str(collation).map_err(|_| {\n            crate::LimboError::ParseError(format!(\"no such collation sequence: {collation}\"))\n        })\n    }\n    #[inline]\n    /// Returns the collation, defaulting to BINARY if unset\n    pub const fn from_bits(bits: u8) -> Self {\n        match bits {\n            2 => CollationSeq::NoCase,\n            3 => CollationSeq::Rtrim,\n            _ => CollationSeq::Binary,\n        }\n    }\n\n    #[inline(always)]\n    pub fn compare_strings(&self, lhs: &str, rhs: &str) -> Ordering {\n        match self {\n            CollationSeq::Unset | CollationSeq::Binary => Self::binary_cmp(lhs, rhs),\n            CollationSeq::NoCase => Self::nocase_cmp(lhs, rhs),\n            CollationSeq::Rtrim => Self::rtrim_cmp(lhs, rhs),\n        }\n    }\n\n    #[inline(always)]\n    fn binary_cmp(lhs: &str, rhs: &str) -> Ordering {\n        lhs.cmp(rhs)\n    }\n\n    #[inline(always)]\n    fn nocase_cmp(lhs: &str, rhs: &str) -> Ordering {\n        let nocase_lhs = uncased::UncasedStr::new(lhs);\n        let nocase_rhs = uncased::UncasedStr::new(rhs);\n        nocase_lhs.cmp(nocase_rhs)\n    }\n\n    #[inline(always)]\n    fn rtrim_cmp(lhs: &str, rhs: &str) -> Ordering {\n        lhs.trim_end_matches(' ').cmp(rhs.trim_end_matches(' '))\n    }\n}\n\n/// Every column of every table has an associated collating function. If no collating function is explicitly defined,\n/// then the collating function defaults to BINARY.\n/// The COLLATE clause of the column definition is used to define alternative collating functions for a column.\n///\n/// The rules for determining which collating function to use for a binary comparison operator (=, <, >, <=, >=, !=, IS, and IS NOT) are as follows:\n///\n/// If either operand has an explicit collating function assignment using the postfix COLLATE operator,\n/// then the explicit collating function is used for comparison, with precedence to the collating function of the left operand.\n///\n/// If either operand is a column, then the collating function of that column is used with precedence to the left operand.\n/// For the purposes of the previous sentence, a column name preceded by one or more unary \"+\" operators and/or CAST operators is still considered a column name.\n///\n/// Otherwise, the BINARY collating function is used for comparison.\n///\n/// An operand of a comparison is considered to have an explicit collating function assignment\n/// if any subexpression of the operand uses the postfix COLLATE operator.\n/// Thus, if a COLLATE operator is used anywhere in a comparison expression,\n/// the collating function defined by that operator is used for string comparison\n/// regardless of what table columns might be a part of that expression.\n/// If two or more COLLATE operator subexpressions appear anywhere in a comparison,\n/// the left most explicit collating function is used regardless of how deeply\n/// the COLLATE operators are nested in the expression and regardless of how\n/// the expression is parenthesized.\npub fn get_collseq_from_expr(\n    top_expr: &Expr,\n    referenced_tables: &TableReferences,\n) -> Result<Option<CollationSeq>> {\n    let (explicit, column) = get_collseq_parts_from_expr(top_expr, referenced_tables)?;\n    Ok(explicit.or(column))\n}\n\n/// Return the collation context that standalone expression translation would\n/// propagate to a parent comparison when this expression is reused from cache.\n///\n/// This differs from `get_collseq_from_expr()` in one important way: plain\n/// column references keep their default BINARY collation, because standalone\n/// column translation records that fact in `ProgramBuilder::curr_collation_ctx()`.\n/// Synthetic expressions such as aggregates must opt out by storing `None` in\n/// the cache entry instead of calling this helper.\npub fn get_expr_collation_ctx(\n    top_expr: &Expr,\n    referenced_tables: &TableReferences,\n) -> Result<Option<(CollationSeq, bool)>> {\n    let mut maybe_column_collseq = None;\n    let mut maybe_explicit_collseq = None;\n\n    walk_expr(top_expr, &mut |expr: &Expr| -> Result<WalkControl> {\n        match expr {\n            Expr::Collate(_, seq) => {\n                if maybe_explicit_collseq.is_none() {\n                    maybe_explicit_collseq =\n                        Some(CollationSeq::new(seq.as_str()).unwrap_or_default());\n                }\n                return Ok(WalkControl::SkipChildren);\n            }\n            Expr::Column { table, column, .. } => {\n                let (_, table_ref) = referenced_tables\n                    .find_table_by_internal_id(*table)\n                    .ok_or_else(|| crate::LimboError::ParseError(\"table not found\".to_string()))?;\n                let column = table_ref\n                    .get_column_at(*column)\n                    .ok_or_else(|| crate::LimboError::ParseError(\"column not found\".to_string()))?;\n                if maybe_column_collseq.is_none() {\n                    maybe_column_collseq = Some(column.collation());\n                }\n            }\n            _ => {}\n        }\n        Ok(WalkControl::Continue)\n    })?;\n\n    Ok(maybe_explicit_collseq\n        .map(|collation| (collation, true))\n        .or_else(|| maybe_column_collseq.map(|collation| (collation, false))))\n}\n\n/// Resolve the collation for a binary comparison (=, <, >, etc.) per SQLite rules:\n/// 1. Explicit COLLATE operator on either side wins (LHS takes precedence)\n/// 2. Column with defined collation on either side wins (LHS takes precedence)\n/// 3. Otherwise BINARY\npub fn resolve_comparison_collseq(\n    lhs_expr: &Expr,\n    rhs_expr: &Expr,\n    referenced_tables: &TableReferences,\n) -> Result<CollationSeq> {\n    let (lhs_explicit, lhs_column) = get_collseq_parts_from_expr(lhs_expr, referenced_tables)?;\n    let (rhs_explicit, rhs_column) = get_collseq_parts_from_expr(rhs_expr, referenced_tables)?;\n    Ok(lhs_explicit\n        .or(rhs_explicit)\n        .or(lhs_column)\n        .or(rhs_column)\n        .unwrap_or(CollationSeq::Binary))\n}\n\n/// Returns (explicit_collation, column_collation) from a single expression.\n/// Explicit collation comes from COLLATE operators; column collation comes from\n/// column definitions. These are kept separate to allow proper precedence resolution\n/// in binary comparisons.\nfn get_collseq_parts_from_expr(\n    top_expr: &Expr,\n    referenced_tables: &TableReferences,\n) -> Result<(Option<CollationSeq>, Option<CollationSeq>)> {\n    let mut maybe_column_collseq = None;\n    let mut maybe_explicit_collseq = None;\n\n    walk_expr(top_expr, &mut |expr: &Expr| -> Result<WalkControl> {\n        match expr {\n            Expr::Collate(_, seq) => {\n                // Only store the first (leftmost) COLLATE operator we find\n                if maybe_explicit_collseq.is_none() {\n                    maybe_explicit_collseq =\n                        Some(CollationSeq::new(seq.as_str()).unwrap_or_default());\n                }\n                // Skip children since we've found a COLLATE operator\n                return Ok(WalkControl::SkipChildren);\n            }\n            Expr::Column { table, column, .. } => {\n                let (_, table_ref) = referenced_tables\n                    .find_table_by_internal_id(*table)\n                    .ok_or_else(|| crate::LimboError::ParseError(\"table not found\".to_string()))?;\n                let column = table_ref\n                    .get_column_at(*column)\n                    .ok_or_else(|| crate::LimboError::ParseError(\"column not found\".to_string()))?;\n                if maybe_column_collseq.is_none() {\n                    maybe_column_collseq = column.collation_opt();\n                }\n                return Ok(WalkControl::Continue);\n            }\n            Expr::RowId { table, .. } => {\n                let (_, table_ref) = referenced_tables\n                    .find_table_by_internal_id(*table)\n                    .ok_or_else(|| crate::LimboError::ParseError(\"table not found\".to_string()))?;\n                if let Some(btree) = table_ref.btree() {\n                    if let Some((_, rowid_alias_col)) = btree.get_rowid_alias_column() {\n                        if maybe_column_collseq.is_none() {\n                            maybe_column_collseq = rowid_alias_col.collation_opt();\n                        }\n                    }\n                }\n                return Ok(WalkControl::Continue);\n            }\n            _ => {}\n        }\n        Ok(WalkControl::Continue)\n    })?;\n\n    Ok((maybe_explicit_collseq, maybe_column_collseq))\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::sync::Arc;\n\n    use turso_parser::ast::{Literal, Name, Operator, TableInternalId, UnaryOperator};\n\n    use crate::{\n        schema::{BTreeTable, ColDef, Column, Table, Type},\n        translate::plan::{ColumnUsedMask, IterationDirection, JoinedTable, Operation, Scan},\n    };\n\n    use super::*;\n\n    #[test]\n    fn test_get_collseq_from_expr_single_table_single_column() {\n        // plain column\n        for collation in [\n            None,\n            Some(CollationSeq::Binary),\n            Some(CollationSeq::NoCase),\n            Some(CollationSeq::Rtrim),\n        ] {\n            let table_references =\n                get_table_references_single_table_single_column_with_collation(collation);\n            let expr = Expr::Column {\n                database: None,\n                table: TableInternalId::from(1),\n                column: 0,\n                is_rowid_alias: false,\n            };\n            let collseq = get_collseq_from_expr(&expr, &table_references).unwrap();\n            assert_eq!(collseq, collation);\n        }\n    }\n\n    #[test]\n    fn test_get_collseq_from_expr_single_table_single_column_with_collate() {\n        let table_references = get_table_references_single_table_single_column_with_collation(\n            Some(CollationSeq::Binary),\n        );\n        // col COLLATE RTRIM, col COLLATE NOCASE, col COLLATE BINARY\n        for collation in [\"RTRIM\", \"NOCASE\", \"BINARY\"] {\n            let expected_collation = CollationSeq::new(collation).unwrap();\n            let expr = Expr::Collate(\n                Box::new(Expr::Column {\n                    database: None,\n                    table: TableInternalId::from(1),\n                    column: 0,\n                    is_rowid_alias: false,\n                }),\n                Name::exact(collation.to_string()),\n            );\n            let collseq = get_collseq_from_expr(&expr, &table_references).unwrap();\n            assert_eq!(collseq, Some(expected_collation));\n        }\n    }\n\n    #[test]\n    fn test_get_collseq_from_expr_multiple_collate_leftmost_wins() {\n        let table_references = get_table_references_single_table_single_column_with_collation(\n            Some(CollationSeq::Binary),\n        );\n        // (col COLLATE NOCASE) COLLATE RTRIM -- RTRIM wins as it is the leftmost AST node with a COLLATE\n        let inner = Expr::Collate(\n            Box::new(Expr::Column {\n                database: None,\n                table: TableInternalId::from(1),\n                column: 0,\n                is_rowid_alias: false,\n            }),\n            Name::exact(\"NOCASE\".to_string()),\n        );\n        let expr = Expr::Collate(\n            Box::new(Expr::Parenthesized(vec![Box::new(inner)])),\n            Name::exact(\"RTRIM\".to_string()),\n        );\n        let collseq = get_collseq_from_expr(&expr, &table_references).unwrap();\n        assert_eq!(collseq, Some(CollationSeq::Rtrim));\n    }\n\n    #[test]\n    fn test_get_collseq_from_expr_unary_plus_and_cast_still_column() {\n        let table_references = get_table_references_single_table_single_column_with_collation(\n            Some(CollationSeq::NoCase),\n        );\n        // Unary plus on column\n        let expr_plus = Expr::unary(\n            UnaryOperator::Positive,\n            Expr::Column {\n                database: None,\n                table: TableInternalId::from(1),\n                column: 0,\n                is_rowid_alias: false,\n            },\n        );\n        let collseq_plus = get_collseq_from_expr(&expr_plus, &table_references).unwrap();\n        assert_eq!(collseq_plus, Some(CollationSeq::NoCase));\n\n        // CAST(column AS TEXT)\n        let cast_ty = Some(turso_parser::ast::Type {\n            name: \"TEXT\".to_string(),\n            size: None,\n            array_dimensions: 0,\n        });\n        let expr_cast = Expr::cast(\n            Expr::Column {\n                database: None,\n                table: TableInternalId::from(1),\n                column: 0,\n                is_rowid_alias: false,\n            },\n            cast_ty,\n        );\n        let collseq_cast = get_collseq_from_expr(&expr_cast, &table_references).unwrap();\n        assert_eq!(collseq_cast, Some(CollationSeq::NoCase));\n    }\n\n    #[test]\n    fn test_get_collseq_from_expr_explicit_collate_anywhere_in_operand() {\n        let table_references = get_table_references_two_tables_single_column_with_collations(\n            Some(CollationSeq::NoCase),\n            None,\n        );\n        // RTRIM wins because it's an explicit COLLATE even though it appears on the right side of the expression\n        let lhs = Expr::Column {\n            database: None,\n            table: TableInternalId::from(1),\n            column: 0,\n            is_rowid_alias: false,\n        };\n        let rhs = Expr::Parenthesized(vec![Box::new(Expr::Collate(\n            Box::new(Expr::Literal(Literal::String(\"x\".to_string()))),\n            Name::exact(\"RTRIM\".to_string()),\n        ))]);\n        let expr = Expr::binary(lhs, Operator::Add, rhs);\n        let collseq = get_collseq_from_expr(&expr, &table_references).unwrap();\n        assert_eq!(collseq, Some(CollationSeq::Rtrim));\n    }\n\n    #[test]\n    fn test_get_collseq_from_expr_column_plus_column_leftside_column_wins() {\n        let table_references = get_table_references_two_tables_single_column_with_collations(\n            Some(CollationSeq::NoCase),\n            Some(CollationSeq::Rtrim),\n        );\n        // col1 + col2 -- col1's NOCASE collation wins since it's on the left side\n        let lhs = Expr::Column {\n            database: None,\n            table: TableInternalId::from(1),\n            column: 0,\n            is_rowid_alias: false,\n        };\n        let rhs = Expr::Column {\n            database: None,\n            table: TableInternalId::from(2),\n            column: 0,\n            is_rowid_alias: false,\n        };\n        let expr = Expr::binary(lhs, Operator::Add, rhs);\n        let collseq = get_collseq_from_expr(&expr, &table_references).unwrap();\n        assert_eq!(collseq, Some(CollationSeq::NoCase));\n    }\n\n    #[test]\n    fn test_get_collseq_from_expr_collate_vs_collate_leftside_expr_wins() {\n        let table_references = TableReferences::new_empty();\n        // (x COLLATE NOCASE) + (y COLLATE RTRIM) -- NOCASE wins since it's on the left side\n        let lhs = Expr::Collate(\n            Box::new(Expr::Literal(Literal::String(\"x\".to_string()))),\n            Name::exact(\"NOCASE\".to_string()),\n        );\n        let rhs = Expr::Collate(\n            Box::new(Expr::Literal(Literal::String(\"y\".to_string()))),\n            Name::exact(\"RTRIM\".to_string()),\n        );\n        let expr = Expr::binary(lhs, Operator::Add, rhs);\n        let collseq = get_collseq_from_expr(&expr, &table_references).unwrap();\n        assert_eq!(collseq, Some(CollationSeq::NoCase));\n    }\n\n    #[test]\n    fn test_get_collseq_from_expr_default_binary_when_no_collate_or_column() {\n        let table_references = TableReferences::new_empty();\n        let expr = Expr::Literal(Literal::String(\"abc\".to_string()));\n        let collseq = get_collseq_from_expr(&expr, &table_references).unwrap();\n        assert_eq!(collseq, None);\n    }\n\n    #[test]\n    fn test_get_collseq_from_expr_rowid_uses_rowid_alias_collation() {\n        let table_references = get_table_references_single_table_rowid_alias_with_collation(Some(\n            CollationSeq::NoCase,\n        ));\n        let expr = Expr::RowId {\n            database: None,\n            table: TableInternalId::from(1),\n        };\n        let collseq = get_collseq_from_expr(&expr, &table_references).unwrap();\n        assert_eq!(collseq, Some(CollationSeq::NoCase));\n    }\n\n    #[test]\n    fn test_resolve_comparison_collseq_nocase_column_vs_binary_default() {\n        // LHS has NOCASE column, RHS has no collation → NOCASE\n        let table_refs = get_table_references_two_tables_single_column_with_collations(\n            Some(CollationSeq::NoCase),\n            None,\n        );\n        let lhs = Expr::Column {\n            database: None,\n            table: TableInternalId::from(1),\n            column: 0,\n            is_rowid_alias: false,\n        };\n        let rhs = Expr::Column {\n            database: None,\n            table: TableInternalId::from(2),\n            column: 0,\n            is_rowid_alias: false,\n        };\n        assert_eq!(\n            resolve_comparison_collseq(&lhs, &rhs, &table_refs).unwrap(),\n            CollationSeq::NoCase\n        );\n        // Swapped: RHS has NOCASE, LHS has no collation → still NOCASE\n        assert_eq!(\n            resolve_comparison_collseq(&rhs, &lhs, &table_refs).unwrap(),\n            CollationSeq::NoCase\n        );\n    }\n\n    #[test]\n    fn test_resolve_comparison_collseq_explicit_beats_column() {\n        // LHS column is NOCASE, but RHS has explicit RTRIM → RTRIM wins\n        let table_refs = get_table_references_two_tables_single_column_with_collations(\n            Some(CollationSeq::NoCase),\n            None,\n        );\n        let lhs = Expr::Column {\n            database: None,\n            table: TableInternalId::from(1),\n            column: 0,\n            is_rowid_alias: false,\n        };\n        let rhs = Expr::Collate(\n            Box::new(Expr::Column {\n                database: None,\n                table: TableInternalId::from(2),\n                column: 0,\n                is_rowid_alias: false,\n            }),\n            Name::exact(\"RTRIM\".to_string()),\n        );\n        assert_eq!(\n            resolve_comparison_collseq(&lhs, &rhs, &table_refs).unwrap(),\n            CollationSeq::Rtrim\n        );\n    }\n\n    #[test]\n    fn test_resolve_comparison_collseq_both_default_is_binary() {\n        let table_refs = get_table_references_two_tables_single_column_with_collations(None, None);\n        let lhs = Expr::Column {\n            database: None,\n            table: TableInternalId::from(1),\n            column: 0,\n            is_rowid_alias: false,\n        };\n        let rhs = Expr::Column {\n            database: None,\n            table: TableInternalId::from(2),\n            column: 0,\n            is_rowid_alias: false,\n        };\n        assert_eq!(\n            resolve_comparison_collseq(&lhs, &rhs, &table_refs).unwrap(),\n            CollationSeq::Binary\n        );\n    }\n\n    // Helpers //\n\n    fn get_table_references_single_table_single_column_with_collation(\n        collation: Option<CollationSeq>,\n    ) -> TableReferences {\n        let mut table_references = TableReferences::new_empty();\n        let table = Table::BTree(Arc::new(BTreeTable {\n            root_page: 0,\n            has_autoincrement: false,\n            has_rowid: false,\n            is_strict: false,\n            name: \"foo\".to_string(),\n            primary_key_columns: vec![],\n            columns: vec![Column::new(\n                Some(\"foo\".to_string()),\n                \"text\".to_string(),\n                None,\n                None,\n                Type::Text,\n                collation,\n                ColDef::default(),\n            )],\n            unique_sets: vec![],\n            foreign_keys: vec![],\n            check_constraints: vec![],\n            rowid_alias_conflict_clause: None,\n        }));\n        table_references.add_joined_table(JoinedTable {\n            op: Operation::Scan(Scan::BTreeTable {\n                iter_dir: IterationDirection::Forwards,\n                index: None,\n            }),\n            col_used_mask: ColumnUsedMask::default(),\n            column_use_counts: Vec::new(),\n            expression_index_usages: Vec::new(),\n            database_id: 0,\n            identifier: \"foo\".to_string(),\n            internal_id: TableInternalId::from(1),\n            join_info: None,\n            table,\n            indexed: None,\n        });\n\n        table_references\n    }\n\n    fn get_table_references_two_tables_single_column_with_collations(\n        left: Option<CollationSeq>,\n        right: Option<CollationSeq>,\n    ) -> TableReferences {\n        let mut table_references = TableReferences::new_empty();\n        // Left table t1(id=1)\n        table_references.add_joined_table(JoinedTable {\n            op: Operation::Scan(Scan::BTreeTable {\n                iter_dir: IterationDirection::Forwards,\n                index: None,\n            }),\n            col_used_mask: ColumnUsedMask::default(),\n            column_use_counts: Vec::new(),\n            expression_index_usages: Vec::new(),\n            database_id: 0,\n            identifier: \"t1\".to_string(),\n            internal_id: TableInternalId::from(1),\n            join_info: None,\n            table: Table::BTree(Arc::new(BTreeTable {\n                root_page: 0,\n                has_autoincrement: false,\n                has_rowid: true,\n                is_strict: false,\n                name: \"t1\".to_string(),\n                primary_key_columns: vec![],\n                columns: vec![Column::new(\n                    Some(\"a\".to_string()),\n                    \"text\".to_string(),\n                    None,\n                    None,\n                    Type::Text,\n                    left,\n                    ColDef::default(),\n                )],\n                unique_sets: vec![],\n                foreign_keys: vec![],\n                check_constraints: vec![],\n                rowid_alias_conflict_clause: None,\n            })),\n            indexed: None,\n        });\n        // Right table t2(id=2)\n        table_references.add_joined_table(JoinedTable {\n            op: Operation::Scan(Scan::BTreeTable {\n                iter_dir: IterationDirection::Forwards,\n                index: None,\n            }),\n            col_used_mask: ColumnUsedMask::default(),\n            column_use_counts: Vec::new(),\n            expression_index_usages: Vec::new(),\n            database_id: 0,\n            identifier: \"t2\".to_string(),\n            internal_id: TableInternalId::from(2),\n            join_info: None,\n            table: Table::BTree(Arc::new(BTreeTable {\n                root_page: 0,\n                has_autoincrement: false,\n                has_rowid: true,\n                is_strict: false,\n                name: \"t2\".to_string(),\n                primary_key_columns: vec![],\n                columns: vec![Column::new(\n                    Some(\"b\".to_string()),\n                    \"text\".to_string(),\n                    None,\n                    None,\n                    Type::Text,\n                    right,\n                    ColDef::default(),\n                )],\n                unique_sets: vec![],\n                foreign_keys: vec![],\n                check_constraints: vec![],\n                rowid_alias_conflict_clause: None,\n            })),\n            indexed: None,\n        });\n        table_references\n    }\n\n    fn get_table_references_single_table_rowid_alias_with_collation(\n        collation: Option<CollationSeq>,\n    ) -> TableReferences {\n        use turso_parser::ast::SortOrder;\n        let mut table_references = TableReferences::new_empty();\n        table_references.add_joined_table(JoinedTable {\n            op: Operation::Scan(Scan::BTreeTable {\n                iter_dir: IterationDirection::Forwards,\n                index: None,\n            }),\n            col_used_mask: ColumnUsedMask::default(),\n            column_use_counts: Vec::new(),\n            expression_index_usages: Vec::new(),\n            database_id: 0,\n            identifier: \"bar\".to_string(),\n            internal_id: TableInternalId::from(1),\n            join_info: None,\n            indexed: None,\n            table: Table::BTree(Arc::new(BTreeTable {\n                root_page: 0,\n                has_autoincrement: false,\n                has_rowid: true,\n                is_strict: false,\n                name: \"bar\".to_string(),\n                primary_key_columns: vec![(\"id\".to_string(), SortOrder::Asc)],\n                columns: vec![Column::new(\n                    Some(\"id\".to_string()),\n                    \"INTEGER\".to_string(),\n                    None,\n                    None,\n                    Type::Integer,\n                    collation,\n                    ColDef {\n                        primary_key: true,\n                        rowid_alias: true,\n                        notnull: false,\n                        unique: true,\n                        hidden: false,\n                        notnull_conflict_clause: None,\n                    },\n                )],\n                unique_sets: vec![],\n                foreign_keys: vec![],\n                check_constraints: vec![],\n                rowid_alias_conflict_clause: None,\n            })),\n        });\n        table_references\n    }\n}\n"
  },
  {
    "path": "core/translate/compound_select.rs",
    "content": "use crate::schema::{Index, IndexColumn};\nuse crate::sync::Arc;\nuse crate::translate::collate::get_collseq_from_expr;\nuse crate::translate::emitter::{select::emit_query, LimitCtx, Resolver, TranslateCtx};\nuse crate::translate::expr::translate_expr;\nuse crate::translate::plan::{Plan, QueryDestination, SelectPlan};\nuse crate::translate::result_row::emit_columns_to_destination;\nuse crate::vdbe::builder::{CursorType, ProgramBuilder};\nuse crate::vdbe::insn::Insn;\nuse crate::{emit_explain, LimboError};\nuse tracing::instrument;\nuse turso_parser::ast::{CompoundOperator, Expr, Literal, SortOrder};\n\nuse tracing::Level;\n\n/// Emits bytecode for a compound SELECT statement (UNION, INTERSECT, EXCEPT, UNION ALL).\n/// Returns the result column start register when in coroutine mode (for CTE subqueries),\n/// or None for top-level queries.\n#[instrument(skip_all, level = Level::DEBUG)]\npub fn emit_program_for_compound_select(\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    plan: Plan,\n) -> crate::Result<Option<usize>> {\n    let Plan::CompoundSelect {\n        left,\n        right_most,\n        limit,\n        offset,\n        ..\n    } = &plan\n    else {\n        crate::bail_parse_error!(\"expected compound select plan\");\n    };\n\n    let right_plan = right_most.clone();\n    let right_most_ctx = TranslateCtx::new(\n        program,\n        resolver.fork(),\n        right_most.table_references.joined_tables().len(),\n        false,\n    );\n\n    // Each subselect shares the same limit_ctx and offset, because the LIMIT, OFFSET applies to\n    // the entire compound select, not just a single subselect.\n    let limit_ctx = limit\n        .as_ref()\n        .map(|limit| {\n            let reg = program.alloc_register();\n            match limit.as_ref() {\n                Expr::Literal(Literal::Numeric(n)) => {\n                    if let Ok(value) = n.parse::<i64>() {\n                        program.add_comment(program.offset(), \"LIMIT counter\");\n                        program.emit_insn(Insn::Integer { value, dest: reg });\n                    } else {\n                        let value = n\n                            .parse::<f64>()\n                            .map_err(|_| LimboError::ParseError(\"invalid limit\".to_string()))?;\n                        program.emit_insn(Insn::Real { value, dest: reg });\n                        program.add_comment(program.offset(), \"LIMIT counter\");\n                        program.emit_insn(Insn::MustBeInt { reg });\n                    }\n                }\n                _ => {\n                    _ = translate_expr(program, None, limit, reg, &right_most_ctx.resolver);\n                    program.add_comment(program.offset(), \"LIMIT counter\");\n                    program.emit_insn(Insn::MustBeInt { reg });\n                }\n            }\n            Ok::<_, LimboError>(LimitCtx::new_shared(reg))\n        })\n        .transpose()?;\n    let offset_reg = offset\n        .as_ref()\n        .map(|offset_expr| {\n            let reg = program.alloc_register();\n            match offset_expr.as_ref() {\n                Expr::Literal(Literal::Numeric(n)) => {\n                    // Compile-time constant offset\n                    if let Ok(value) = n.parse::<i64>() {\n                        program.emit_insn(Insn::Integer { value, dest: reg });\n                    } else {\n                        let value = n\n                            .parse::<f64>()\n                            .map_err(|_| LimboError::ParseError(\"invalid offset\".to_string()))?;\n                        program.emit_insn(Insn::Real { value, dest: reg });\n                    }\n                }\n                _ => {\n                    _ = translate_expr(program, None, offset_expr, reg, &right_most_ctx.resolver);\n                }\n            }\n            program.add_comment(program.offset(), \"OFFSET counter\");\n            program.emit_insn(Insn::MustBeInt { reg });\n            let combined_reg = program.alloc_register();\n            program.add_comment(program.offset(), \"OFFSET + LIMIT\");\n            program.emit_insn(Insn::OffsetLimit {\n                offset_reg: reg,\n                combined_reg,\n                limit_reg: limit_ctx.as_ref().unwrap().reg_limit,\n            });\n\n            Ok::<_, LimboError>(reg)\n        })\n        .transpose()?;\n\n    // When a compound SELECT is part of a query that yields results to a coroutine (e.g. within an INSERT clause),\n    // we must allocate registers for the result columns to be yielded. Each subselect will then yield to\n    // the coroutine using the same set of registers.\n    // When the destination is an EphemeralTable or EphemeralIndex (for CTE materialization), we need to\n    // insert into that table/index.\n    // For top-level queries (ResultRows), we emit ResultRow instructions directly.\n    // Allocate registers for result columns when we need to hold values before emitting.\n    // For ResultRows we allocate fresh registers per-row in read_deduplicated_*.\n    let reg_result_cols_start = match &right_most.query_destination {\n        QueryDestination::CoroutineYield { .. }\n        | QueryDestination::EphemeralTable { .. }\n        | QueryDestination::EphemeralIndex { .. } => {\n            Some(program.alloc_registers(right_most.result_columns.len()))\n        }\n        QueryDestination::ResultRows => None,\n        other => {\n            return Err(LimboError::InternalError(format!(\n                \"Unexpected query destination: {other:?} for compound select\"\n            )));\n        }\n    };\n    // Clone the destination for passing to emit_compound_select\n    let query_destination = right_most.query_destination.clone();\n\n    emit_explain!(program, true, \"COMPOUND QUERY\".to_owned());\n\n    // This is inefficient, but emit_compound_select() takes ownership of 'plan' and we\n    // must set the result columns to the leftmost subselect's result columns to be compatible\n    // with SQLite.\n    program.result_columns.clone_from(&left[0].0.result_columns);\n\n    // These must also be set because we make the decision to start a transaction based on whether\n    // any tables are actually touched by the query. Previously this only used the rightmost subselect's\n    // table references, but that breaks down with e.g. \"SELECT * FROM t UNION VALUES(1)\" where VALUES(1)\n    // does not have any table references and we would erroneously not start a transaction.\n    for (plan, _) in left {\n        program\n            .table_references\n            .extend(plan.table_references.clone());\n    }\n    program.table_references.extend(right_plan.table_references);\n\n    program.with_scoped_result_cols_start(|program| {\n        emit_compound_select(\n            program,\n            plan,\n            &right_most_ctx.resolver,\n            limit_ctx,\n            offset_reg,\n            reg_result_cols_start,\n            &query_destination,\n        )\n    })?;\n    program.pop_current_parent_explain();\n    program.reg_result_cols_start = reg_result_cols_start;\n\n    Ok(reg_result_cols_start)\n}\n\n// Emits bytecode for a compound SELECT statement. This function processes the rightmost part of\n// the compound SELECT and handles the left parts recursively based on the compound operator type.\n#[allow(clippy::too_many_arguments)]\nfn emit_compound_select(\n    program: &mut ProgramBuilder,\n    plan: Plan,\n    resolver: &Resolver,\n    limit_ctx: Option<LimitCtx>,\n    offset_reg: Option<usize>,\n    reg_result_cols_start: Option<usize>,\n    query_destination: &QueryDestination,\n) -> crate::Result<()> {\n    let Plan::CompoundSelect {\n        mut left,\n        mut right_most,\n        limit,\n        offset,\n        order_by,\n    } = plan\n    else {\n        unreachable!()\n    };\n\n    let compound_select_end = program.allocate_label();\n    if let Some(limit_ctx) = &limit_ctx {\n        program.emit_insn(Insn::IfNot {\n            reg: limit_ctx.reg_limit,\n            target_pc: compound_select_end,\n            jump_if_null: false,\n        });\n    }\n    let mut right_most_ctx = TranslateCtx::new(\n        program,\n        resolver.fork(),\n        right_most.table_references.joined_tables().len(),\n        false,\n    );\n    right_most_ctx.reg_result_cols_start = reg_result_cols_start;\n    match left.pop() {\n        Some((mut plan, operator)) => match operator {\n            CompoundOperator::UnionAll => {\n                if matches!(\n                    right_most.query_destination,\n                    QueryDestination::EphemeralIndex { .. }\n                        | QueryDestination::CoroutineYield { .. }\n                        | QueryDestination::EphemeralTable { .. }\n                ) {\n                    plan.query_destination = right_most.query_destination.clone();\n                }\n                let compound_select = Plan::CompoundSelect {\n                    left,\n                    right_most: plan,\n                    limit: limit.clone(),\n                    offset: offset.clone(),\n                    order_by,\n                };\n                emit_compound_select(\n                    program,\n                    compound_select,\n                    resolver,\n                    limit_ctx,\n                    offset_reg,\n                    reg_result_cols_start,\n                    query_destination,\n                )?;\n\n                let label_next_select = program.allocate_label();\n                if let Some(limit_ctx) = limit_ctx {\n                    program.emit_insn(Insn::IfNot {\n                        reg: limit_ctx.reg_limit,\n                        target_pc: label_next_select,\n                        jump_if_null: true,\n                    });\n                    right_most.limit = limit;\n                    right_most_ctx.limit_ctx = Some(limit_ctx);\n                }\n                if offset_reg.is_some() {\n                    right_most.offset = offset;\n                    right_most_ctx.reg_offset = offset_reg;\n                }\n\n                emit_explain!(program, true, \"UNION ALL\".to_owned());\n                emit_query(program, &mut right_most, &mut right_most_ctx)?;\n                program.pop_current_parent_explain();\n                program.preassign_label_to_next_insn(label_next_select);\n            }\n            CompoundOperator::Union => {\n                let mut new_dedupe_index = false;\n                let dedupe_index = match right_most.query_destination {\n                    QueryDestination::EphemeralIndex {\n                        cursor_id, index, ..\n                    } => (cursor_id, index),\n                    _ => {\n                        new_dedupe_index = true;\n                        create_dedupe_index(program, &plan, &right_most)?\n                    }\n                };\n                plan.query_destination = QueryDestination::EphemeralIndex {\n                    cursor_id: dedupe_index.0,\n                    index: dedupe_index.1.clone(),\n                    affinity_str: None,\n                    is_delete: false,\n                };\n                let compound_select = Plan::CompoundSelect {\n                    left,\n                    right_most: plan,\n                    limit,\n                    offset,\n                    order_by,\n                };\n                emit_compound_select(\n                    program,\n                    compound_select,\n                    resolver,\n                    None,\n                    None,\n                    reg_result_cols_start,\n                    query_destination,\n                )?;\n\n                right_most.query_destination = QueryDestination::EphemeralIndex {\n                    cursor_id: dedupe_index.0,\n                    index: dedupe_index.1.clone(),\n                    affinity_str: None,\n                    is_delete: false,\n                };\n\n                emit_explain!(program, true, \"UNION USING TEMP B-TREE\".to_owned());\n                emit_query(program, &mut right_most, &mut right_most_ctx)?;\n                program.pop_current_parent_explain();\n\n                if new_dedupe_index {\n                    read_deduplicated_union_or_except_rows(\n                        program,\n                        dedupe_index.0,\n                        dedupe_index.1.as_ref(),\n                        limit_ctx,\n                        offset_reg,\n                        reg_result_cols_start,\n                        query_destination,\n                    )?;\n                }\n            }\n            CompoundOperator::Intersect => {\n                // For nested compound selects (e.g., A INTERSECT B UNION C), the outer UNION\n                // sets right_most.query_destination to its dedupe_index. We need to capture\n                // this BEFORE we overwrite it with our own indexes for the intersection.\n                let intersect_destination = right_most.query_destination.clone();\n\n                let (left_cursor_id, left_index) =\n                    create_dedupe_index(program, &plan, &right_most)?;\n                plan.query_destination = QueryDestination::EphemeralIndex {\n                    cursor_id: left_cursor_id,\n                    index: left_index.clone(),\n                    affinity_str: None,\n                    is_delete: false,\n                };\n\n                let (right_cursor_id, right_index) =\n                    create_dedupe_index(program, &plan, &right_most)?;\n                right_most.query_destination = QueryDestination::EphemeralIndex {\n                    cursor_id: right_cursor_id,\n                    index: right_index,\n                    affinity_str: None,\n                    is_delete: false,\n                };\n                let compound_select = Plan::CompoundSelect {\n                    left,\n                    right_most: plan,\n                    limit,\n                    offset,\n                    order_by,\n                };\n                emit_compound_select(\n                    program,\n                    compound_select,\n                    resolver,\n                    None,\n                    None,\n                    reg_result_cols_start,\n                    query_destination,\n                )?;\n\n                emit_explain!(program, true, \"INTERSECT USING TEMP B-TREE\".to_owned());\n                emit_query(program, &mut right_most, &mut right_most_ctx)?;\n                program.pop_current_parent_explain();\n                read_intersect_rows(\n                    program,\n                    left_cursor_id,\n                    &left_index,\n                    right_cursor_id,\n                    limit_ctx,\n                    offset_reg,\n                    reg_result_cols_start,\n                    &intersect_destination,\n                )?;\n            }\n            CompoundOperator::Except => {\n                let mut new_index = false;\n                let (cursor_id, index) = match right_most.query_destination {\n                    QueryDestination::EphemeralIndex {\n                        cursor_id, index, ..\n                    } => (cursor_id, index),\n                    _ => {\n                        new_index = true;\n                        create_dedupe_index(program, &plan, &right_most)?\n                    }\n                };\n                plan.query_destination = QueryDestination::EphemeralIndex {\n                    cursor_id,\n                    index: index.clone(),\n                    affinity_str: None,\n                    is_delete: false,\n                };\n                let compound_select = Plan::CompoundSelect {\n                    left,\n                    right_most: plan,\n                    limit,\n                    offset,\n                    order_by,\n                };\n                emit_compound_select(\n                    program,\n                    compound_select,\n                    resolver,\n                    None,\n                    None,\n                    reg_result_cols_start,\n                    query_destination,\n                )?;\n                right_most.query_destination = QueryDestination::EphemeralIndex {\n                    cursor_id,\n                    index: index.clone(),\n                    affinity_str: None,\n                    is_delete: true,\n                };\n                emit_explain!(program, true, \"EXCEPT USING TEMP B-TREE\".to_owned());\n                emit_query(program, &mut right_most, &mut right_most_ctx)?;\n                program.pop_current_parent_explain();\n                if new_index {\n                    read_deduplicated_union_or_except_rows(\n                        program,\n                        cursor_id,\n                        &index,\n                        limit_ctx,\n                        offset_reg,\n                        reg_result_cols_start,\n                        query_destination,\n                    )?;\n                }\n            }\n        },\n        None => {\n            if let Some(limit_ctx) = limit_ctx {\n                right_most_ctx.limit_ctx = Some(limit_ctx);\n                right_most.limit = limit;\n            }\n            if offset_reg.is_some() {\n                right_most.offset = offset;\n                right_most_ctx.reg_offset = offset_reg;\n            }\n            emit_explain!(program, true, \"LEFT-MOST SUBQUERY\".to_owned());\n            emit_query(program, &mut right_most, &mut right_most_ctx)?;\n            program.pop_current_parent_explain();\n        }\n    }\n\n    program.preassign_label_to_next_insn(compound_select_end);\n\n    Ok(())\n}\n\n// Creates an ephemeral index that will be used to deduplicate the results of any sub-selects\nfn create_dedupe_index(\n    program: &mut ProgramBuilder,\n    left_select: &SelectPlan,\n    right_select: &SelectPlan,\n) -> crate::Result<(usize, Arc<Index>)> {\n    let mut dedupe_columns = right_select\n        .result_columns\n        .iter()\n        .enumerate()\n        .map(|(i, c)| IndexColumn {\n            name: c\n                .name(&right_select.table_references)\n                .map(|n| n.to_string())\n                .unwrap_or_default(),\n            order: SortOrder::Asc,\n            pos_in_table: i,\n            default: None,\n            collation: None,\n            expr: None,\n        })\n        .collect::<Vec<_>>();\n    for (i, column) in dedupe_columns.iter_mut().enumerate() {\n        let left_collation = get_collseq_from_expr(\n            &left_select.result_columns[i].expr,\n            &left_select.table_references,\n        )?;\n        let right_collation = get_collseq_from_expr(\n            &right_select.result_columns[i].expr,\n            &right_select.table_references,\n        )?;\n        // Left precedence\n        let collation = match (left_collation, right_collation) {\n            (None, None) => None,\n            (Some(coll), None) | (None, Some(coll)) => Some(coll),\n            (Some(coll), Some(_)) => Some(coll),\n        };\n        column.collation = collation;\n    }\n\n    let dedupe_index = Arc::new(Index {\n        columns: dedupe_columns,\n        name: \"compound_dedupe\".to_string(),\n        root_page: 0,\n        ephemeral: true,\n        table_name: String::new(),\n        unique: false,\n        has_rowid: false,\n        where_clause: None,\n        index_method: None,\n        on_conflict: None,\n    });\n    let cursor_id = program.alloc_cursor_id(CursorType::BTreeIndex(dedupe_index.clone()));\n    program.emit_insn(Insn::OpenEphemeral {\n        cursor_id,\n        is_table: false,\n    });\n    Ok((cursor_id, dedupe_index))\n}\n\n/// Emits the bytecode for reading deduplicated rows from the ephemeral index created for\n/// UNION or EXCEPT operators.\n#[allow(clippy::too_many_arguments)]\nfn read_deduplicated_union_or_except_rows(\n    program: &mut ProgramBuilder,\n    dedupe_cursor_id: usize,\n    dedupe_index: &Index,\n    limit_ctx: Option<LimitCtx>,\n    offset_reg: Option<usize>,\n    reg_result_cols_start: Option<usize>,\n    query_destination: &QueryDestination,\n) -> crate::Result<()> {\n    let label_close = program.allocate_label();\n    let label_dedupe_next = program.allocate_label();\n    let label_dedupe_loop_start = program.allocate_label();\n    // When in coroutine mode or emitting to index/table, use the pre-allocated result column registers.\n    // Otherwise, allocate new registers for reading from the dedupe index.\n    let dedupe_cols_start_reg = reg_result_cols_start\n        .unwrap_or_else(|| program.alloc_registers(dedupe_index.columns.len()));\n    program.emit_insn(Insn::Rewind {\n        cursor_id: dedupe_cursor_id,\n        pc_if_empty: label_dedupe_next,\n    });\n    program.preassign_label_to_next_insn(label_dedupe_loop_start);\n    if let Some(reg) = offset_reg {\n        program.emit_insn(Insn::IfPos {\n            reg,\n            target_pc: label_dedupe_next,\n            decrement_by: 1,\n        });\n    }\n    for col_idx in 0..dedupe_index.columns.len() {\n        program.emit_insn(Insn::Column {\n            cursor_id: dedupe_cursor_id,\n            column: col_idx,\n            dest: dedupe_cols_start_reg + col_idx,\n            default: None,\n        });\n    }\n    emit_columns_to_destination(\n        program,\n        query_destination,\n        dedupe_cols_start_reg,\n        dedupe_index.columns.len(),\n    )?;\n\n    if let Some(limit_ctx) = limit_ctx {\n        program.emit_insn(Insn::DecrJumpZero {\n            reg: limit_ctx.reg_limit,\n            target_pc: label_close,\n        })\n    }\n    program.preassign_label_to_next_insn(label_dedupe_next);\n    program.emit_insn(Insn::Next {\n        cursor_id: dedupe_cursor_id,\n        pc_if_next: label_dedupe_loop_start,\n    });\n    program.preassign_label_to_next_insn(label_close);\n    program.emit_insn(Insn::Close {\n        cursor_id: dedupe_cursor_id,\n    });\n    Ok(())\n}\n\n/// Emits the bytecode for reading rows from the intersection of two cursors.\n#[allow(clippy::too_many_arguments)]\nfn read_intersect_rows(\n    program: &mut ProgramBuilder,\n    left_cursor_id: usize,\n    index: &Index,\n    right_cursor_id: usize,\n    limit_ctx: Option<LimitCtx>,\n    offset_reg: Option<usize>,\n    reg_result_cols_start: Option<usize>,\n    query_destination: &QueryDestination,\n) -> crate::Result<()> {\n    let label_close = program.allocate_label();\n    let label_loop_start = program.allocate_label();\n    program.emit_insn(Insn::Rewind {\n        cursor_id: left_cursor_id,\n        pc_if_empty: label_close,\n    });\n\n    program.preassign_label_to_next_insn(label_loop_start);\n    let row_content_reg = program.alloc_register();\n    program.emit_insn(Insn::RowData {\n        cursor_id: left_cursor_id,\n        dest: row_content_reg,\n    });\n    let label_next = program.allocate_label();\n    program.emit_insn(Insn::NotFound {\n        cursor_id: right_cursor_id,\n        target_pc: label_next,\n        record_reg: row_content_reg,\n        num_regs: 0,\n    });\n    if let Some(reg) = offset_reg {\n        program.emit_insn(Insn::IfPos {\n            reg,\n            target_pc: label_next,\n            decrement_by: 1,\n        });\n    }\n    let column_count = index.columns.len();\n    // When in coroutine mode, use the pre-allocated result column registers.\n    // Otherwise, allocate new registers for reading from the index.\n    let cols_start_reg =\n        reg_result_cols_start.unwrap_or_else(|| program.alloc_registers(column_count));\n    for i in 0..column_count {\n        program.emit_insn(Insn::Column {\n            cursor_id: left_cursor_id,\n            column: i,\n            dest: cols_start_reg + i,\n            default: None,\n        });\n    }\n\n    emit_columns_to_destination(program, query_destination, cols_start_reg, column_count)?;\n\n    if let Some(limit_ctx) = limit_ctx {\n        program.emit_insn(Insn::DecrJumpZero {\n            reg: limit_ctx.reg_limit,\n            target_pc: label_close,\n        });\n    }\n    program.preassign_label_to_next_insn(label_next);\n    program.emit_insn(Insn::Next {\n        cursor_id: left_cursor_id,\n        pc_if_next: label_loop_start,\n    });\n\n    program.preassign_label_to_next_insn(label_close);\n    program.emit_insn(Insn::Close {\n        cursor_id: right_cursor_id,\n    });\n    program.emit_insn(Insn::Close {\n        cursor_id: left_cursor_id,\n    });\n    Ok(())\n}\n"
  },
  {
    "path": "core/translate/delete.rs",
    "content": "use crate::schema::Table;\nuse crate::sync::Arc;\nuse crate::translate::emitter::{emit_program, Resolver};\nuse crate::translate::expr::{process_returning_clause, walk_expr, WalkControl};\nuse crate::translate::optimizer::optimize_plan;\nuse crate::translate::plan::{\n    DeletePlan, DmlSafety, DmlSafetyReason, IterationDirection, JoinOrderMember, Operation, Plan,\n    QueryDestination, ResultSetColumn, Scan, SelectPlan,\n};\nuse crate::translate::planner::{parse_limit, parse_where, plan_ctes_as_outer_refs};\nuse crate::translate::subquery::{\n    plan_subqueries_from_returning, plan_subqueries_from_select_plan,\n    plan_subqueries_from_where_clause,\n};\nuse crate::translate::trigger_exec::has_relevant_triggers_type_only;\nuse crate::util::normalize_ident;\nuse crate::vdbe::builder::{ProgramBuilder, ProgramBuilderOpts};\nuse crate::Result;\nuse turso_parser::ast::{Expr, Limit, QualifiedName, ResultColumn, TriggerEvent, With};\n\nuse super::plan::{ColumnUsedMask, JoinedTable, TableReferences, WhereTerm};\n\n#[allow(clippy::too_many_arguments)]\npub fn translate_delete(\n    tbl_name: &QualifiedName,\n    resolver: &Resolver,\n    where_clause: Option<Box<Expr>>,\n    limit: Option<Limit>,\n    returning: Vec<ResultColumn>,\n    indexed: Option<turso_parser::ast::Indexed>,\n    with: Option<With>,\n    program: &mut ProgramBuilder,\n    connection: &Arc<crate::Connection>,\n) -> Result<()> {\n    let database_id = resolver.resolve_database_id(tbl_name)?;\n    let normalized_table_name = normalize_ident(tbl_name.name.as_str());\n\n    // Check if this is a system table that should be protected from direct writes\n    if !connection.is_nested_stmt()\n        && !connection.is_mvcc_bootstrap_connection()\n        && crate::schema::is_system_table(&normalized_table_name)\n    {\n        crate::bail_parse_error!(\"table {} may not be modified\", normalized_table_name);\n    }\n\n    if crate::is_attached_db(database_id) {\n        let schema_cookie = resolver.with_schema(database_id, |s| s.schema_version);\n        program.begin_write_on_database(database_id, schema_cookie);\n    }\n\n    let mut delete_plan = prepare_delete_plan(\n        program,\n        resolver,\n        tbl_name,\n        where_clause,\n        limit,\n        returning,\n        indexed,\n        with,\n        connection,\n        database_id,\n    )?;\n\n    // Plan subqueries in the WHERE clause\n    if let Plan::Delete(ref mut delete_plan_inner) = delete_plan {\n        if let Some(ref mut rowset_plan) = delete_plan_inner.rowset_plan {\n            // When using rowset (triggers or subqueries present), subqueries are in the rowset_plan's WHERE\n            plan_subqueries_from_select_plan(program, rowset_plan, resolver, connection)?;\n        } else {\n            // Normal path: subqueries are in the DELETE plan's WHERE\n            plan_subqueries_from_where_clause(\n                program,\n                &mut delete_plan_inner.non_from_clause_subqueries,\n                &mut delete_plan_inner.table_references,\n                &mut delete_plan_inner.where_clause,\n                resolver,\n                connection,\n            )?;\n        }\n    }\n\n    optimize_plan(program, &mut delete_plan, resolver)?;\n    if let Plan::Delete(delete_plan_inner) = &mut delete_plan {\n        // Re-check after optimization: chosen access paths can make \"delete while scanning\"\n        // unsafe, so we may need to collect rowids first.\n        record_delete_optimizer_safety(delete_plan_inner);\n        if delete_plan_inner.safety.requires_stable_write_set() {\n            ensure_delete_uses_rowset(program, delete_plan_inner);\n        }\n\n        // Rewrite the Delete plan after optimization whenever a RowSet is used (trigger/subquery\n        // safety or optimizer-induced safety), so the joined table is treated as a plain table\n        // scan again.\n        //\n        // RowSets re-seek the base table cursor for every delete, so expressions that reference\n        // columns during index maintenance must bind to the table cursor again (not the index we\n        // originally used to find the rowids).\n        //\n        // e.g. DELETE using idx_x gathers rowids, but BEFORE DELETE trigger causes re-seek on\n        // table, so expression indexes must read from that table cursor.\n        if delete_plan_inner.rowset_plan.is_some() {\n            if let Some(joined_table) = delete_plan_inner\n                .table_references\n                .joined_tables_mut()\n                .first_mut()\n            {\n                if matches!(joined_table.table, Table::BTree(_)) {\n                    joined_table.op = Operation::Scan(Scan::BTreeTable {\n                        iter_dir: IterationDirection::Forwards,\n                        index: None,\n                    });\n                }\n            }\n        }\n    }\n    let Plan::Delete(ref delete) = delete_plan else {\n        panic!(\"delete_plan is not a DeletePlan\");\n    };\n    super::stmt_journal::set_delete_stmt_journal_flags(\n        program,\n        delete,\n        resolver,\n        connection,\n        database_id,\n    )?;\n    let opts = ProgramBuilderOpts {\n        num_cursors: 1,\n        approx_num_insns: estimate_num_instructions(delete),\n        approx_num_labels: 0,\n    };\n    program.extend(&opts);\n    emit_program(connection, resolver, program, delete_plan, |_| {})?;\n    Ok(())\n}\n\n#[allow(clippy::too_many_arguments)]\npub fn prepare_delete_plan(\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    tbl_name: &QualifiedName,\n    where_clause: Option<Box<Expr>>,\n    limit: Option<Limit>,\n    mut returning: Vec<ResultColumn>,\n    indexed: Option<turso_parser::ast::Indexed>,\n    with: Option<With>,\n    connection: &Arc<crate::Connection>,\n    database_id: usize,\n) -> Result<Plan> {\n    let table_name = normalize_ident(tbl_name.name.as_str());\n    let schema = resolver.schema();\n    let table = match resolver.with_schema(database_id, |s| s.get_table(&table_name)) {\n        Some(table) => table,\n        None => crate::bail_parse_error!(\"no such table: {}\", table_name),\n    };\n    if program.trigger.is_some() && table.virtual_table().is_some() {\n        crate::bail_parse_error!(\"unsafe use of virtual table \\\"{}\\\"\", table_name);\n    }\n\n    // Check if this is a materialized view\n    if schema.is_materialized_view(&table_name) {\n        crate::bail_parse_error!(\"cannot modify materialized view {}\", table_name);\n    }\n\n    // Check if this table has any incompatible dependent views\n    let incompatible_views = schema.has_incompatible_dependent_views(&table_name);\n    if !incompatible_views.is_empty() {\n        use crate::incremental::compiler::DBSP_CIRCUIT_VERSION;\n        crate::bail_parse_error!(\n            \"Cannot DELETE from table '{}' because it has incompatible dependent materialized view(s): {}. \\n\\\n             These views were created with a different DBSP version than the current version ({}). \\n\\\n             Please DROP and recreate the view(s) before modifying this table.\",\n            table_name,\n            incompatible_views.join(\", \"),\n            DBSP_CIRCUIT_VERSION\n        );\n    }\n\n    let btree_table_for_triggers = table.btree();\n\n    let table = if let Some(table) = table.virtual_table() {\n        Table::Virtual(table)\n    } else if let Some(table) = table.btree() {\n        Table::BTree(table)\n    } else {\n        crate::bail_parse_error!(\"Table is neither a virtual table nor a btree table\");\n    };\n    let indexes = schema.get_indices(table.get_name()).cloned().collect();\n    let joined_tables = vec![JoinedTable {\n        op: Operation::default_scan_for(&table),\n        table,\n        identifier: tbl_name\n            .alias\n            .as_ref()\n            .map_or_else(|| table_name.clone(), |alias| alias.as_str().to_string()),\n        internal_id: program.table_reference_counter.next(),\n        join_info: None,\n        col_used_mask: ColumnUsedMask::default(),\n        column_use_counts: Vec::new(),\n        expression_index_usages: Vec::new(),\n        database_id,\n        indexed,\n    }];\n    let mut table_references = TableReferences::new(joined_tables, vec![]);\n\n    // Plan CTEs and add them as outer query references for subquery resolution\n    plan_ctes_as_outer_refs(with, resolver, program, &mut table_references, connection)?;\n\n    let mut where_predicates = vec![];\n\n    // Parse the WHERE clause\n    parse_where(\n        where_clause.as_deref(),\n        &mut table_references,\n        None,\n        &mut where_predicates,\n        resolver,\n    )?;\n\n    // Plan subqueries in RETURNING expressions before processing\n    // (so SubqueryResult nodes are cloned into result_columns)\n    let mut non_from_clause_subqueries = vec![];\n    plan_subqueries_from_returning(\n        program,\n        &mut non_from_clause_subqueries,\n        &mut table_references,\n        &mut returning,\n        resolver,\n        connection,\n    )?;\n\n    let result_columns = process_returning_clause(&mut returning, &mut table_references, resolver)?;\n\n    // Parse the LIMIT/OFFSET clause\n    let (resolved_limit, resolved_offset) =\n        limit.map_or(Ok((None, None)), |l| parse_limit(l, resolver))?;\n\n    // Check if there are DELETE triggers. If so, we need to materialize the write set into a RowSet first.\n    // This is done in SQLite for all DELETE triggers on the affected table even if the trigger would not have an impact\n    // on the target table -- presumably due to lack of static analysis capabilities to determine whether it's safe\n    // to skip the rowset materialization.\n    let has_delete_triggers = btree_table_for_triggers\n        .as_ref()\n        .map(|bt| {\n            resolver.with_schema(database_id, |s| {\n                has_relevant_triggers_type_only(s, TriggerEvent::Delete, None, bt)\n            })\n        })\n        .unwrap_or(false);\n\n    let mut safety = DmlSafety::default();\n    if has_delete_triggers {\n        safety.require(DmlSafetyReason::Trigger);\n    }\n    if where_clause_has_subquery(&where_predicates) {\n        safety.require(DmlSafetyReason::SubqueryInWhere);\n    }\n\n    let mut delete_plan = DeletePlan {\n        table_references,\n        result_columns,\n        where_clause: where_predicates,\n        order_by: vec![],\n        limit: resolved_limit,\n        offset: resolved_offset,\n        contains_constant_false_condition: false,\n        indexes,\n        rowset_plan: None,\n        rowset_reg: None,\n        non_from_clause_subqueries,\n        safety,\n    };\n\n    if delete_plan.safety.requires_stable_write_set() {\n        ensure_delete_uses_rowset(program, &mut delete_plan);\n    }\n\n    Ok(Plan::Delete(delete_plan))\n}\n\n/// Check if any WHERE predicate contains a subquery (Subquery, InSelect, or Exists).\nfn where_clause_has_subquery(predicates: &[WhereTerm]) -> bool {\n    for pred in predicates {\n        let mut found = false;\n        let _ = walk_expr(&pred.expr, &mut |e| {\n            if matches!(\n                e,\n                Expr::Subquery(_) | Expr::InSelect { .. } | Expr::Exists(_)\n            ) {\n                found = true;\n            }\n            Ok(if found {\n                WalkControl::SkipChildren\n            } else {\n                WalkControl::Continue\n            })\n        });\n        if found {\n            return true;\n        }\n    }\n    false\n}\n\nfn estimate_num_instructions(plan: &DeletePlan) -> usize {\n    let base = 20;\n\n    base + plan.table_references.joined_tables().len() * 10\n}\n\n/// Add post-optimizer reasons that force \"collect rowids first, then delete\".\nfn record_delete_optimizer_safety(plan: &mut DeletePlan) {\n    if plan\n        .table_references\n        .joined_tables()\n        .first()\n        .is_some_and(|table| matches!(table.op, Operation::MultiIndexScan(_)))\n    {\n        plan.safety.require(DmlSafetyReason::MultiIndexScan);\n    }\n    if let Some(Operation::IndexMethodQuery(query)) =\n        plan.table_references.joined_tables().first().map(|t| &t.op)\n    {\n        let attachment = query\n            .index\n            .index_method\n            .as_ref()\n            .expect(\"IndexMethodQuery always has an index_method attachment\");\n        if !attachment.definition().results_materialized {\n            plan.safety\n                .require(DmlSafetyReason::IndexMethodNotMaterialized);\n        }\n    }\n}\n\n/// Convert a DELETE plan into a RowSet-driven delete:\n/// 1. execute a SELECT-like rowid producer into RowSet\n/// 2. iterate RowSet to perform actual deletes\nfn ensure_delete_uses_rowset(program: &mut ProgramBuilder, plan: &mut DeletePlan) {\n    if plan.rowset_plan.is_some() {\n        return;\n    }\n\n    let rowid_internal_id = plan\n        .table_references\n        .joined_tables()\n        .first()\n        .expect(\"DELETE should have one target table\")\n        .internal_id;\n    let rowset_reg = plan.rowset_reg.unwrap_or_else(|| {\n        let reg = program.alloc_register();\n        plan.rowset_reg = Some(reg);\n        reg\n    });\n\n    let rowset_plan = SelectPlan {\n        table_references: plan.table_references.clone(),\n        result_columns: vec![ResultSetColumn {\n            expr: Expr::RowId {\n                database: None,\n                table: rowid_internal_id,\n            },\n            alias: None,\n            contains_aggregates: false,\n        }],\n        where_clause: std::mem::take(&mut plan.where_clause),\n        group_by: None,\n        order_by: vec![],\n        aggregates: vec![],\n        limit: plan.limit.take(),\n        query_destination: QueryDestination::RowSet { rowset_reg },\n        join_order: plan\n            .table_references\n            .joined_tables()\n            .iter()\n            .enumerate()\n            .map(|(i, t)| JoinOrderMember {\n                table_id: t.internal_id,\n                original_idx: i,\n                is_outer: false,\n            })\n            .collect(),\n        offset: plan.offset.take(),\n        contains_constant_false_condition: false,\n        distinctness: super::plan::Distinctness::NonDistinct,\n        values: vec![],\n        window: None,\n        // WHERE subqueries should already be planned into this SelectPlan when needed.\n        non_from_clause_subqueries: vec![],\n        input_cardinality_hint: None,\n        estimated_output_rows: None,\n        simple_aggregate: None,\n    };\n    plan.rowset_plan = Some(rowset_plan);\n}\n"
  },
  {
    "path": "core/translate/display.rs",
    "content": "use core::fmt;\nuse std::fmt::{Display, Formatter};\nuse turso_parser::{\n    ast::{\n        self,\n        fmt::{BlankContext, ToSqlContext, ToTokens, TokenStream},\n        SortOrder, TableInternalId,\n    },\n    token::TokenType,\n};\n\nuse crate::{\n    schema::Table,\n    translate::plan::{SeekKeyComponent, TableReferences},\n    types::SeekOp,\n};\n\nuse super::plan::{\n    Aggregate, DeletePlan, JoinedTable, Operation, Plan, ResultSetColumn, Scan, Search, SeekDef,\n    SelectPlan, SetOperation, UpdatePlan,\n};\n\n/// Format the EXPLAIN QUERY PLAN detail string for a table operation.\n/// Used by DELETE/UPDATE emitters to emit EQP annotations.\npub(crate) fn format_eqp_detail(table: &JoinedTable) -> String {\n    match &table.op {\n        Operation::Scan(scan) => {\n            let table_name = if table.table.get_name() == table.identifier {\n                table.identifier.clone()\n            } else {\n                format!(\"{} AS {}\", table.table.get_name(), table.identifier)\n            };\n            match scan {\n                Scan::BTreeTable { index, .. } => {\n                    if let Some(index) = index {\n                        if table.utilizes_covering_index() {\n                            format!(\"SCAN {table_name} USING COVERING INDEX {}\", index.name)\n                        } else {\n                            format!(\"SCAN {table_name} USING INDEX {}\", index.name)\n                        }\n                    } else {\n                        format!(\"SCAN {table_name}\")\n                    }\n                }\n                Scan::VirtualTable { .. } | Scan::Subquery { .. } => {\n                    format!(\"SCAN {table_name}\")\n                }\n            }\n        }\n        Operation::Search(search) => match search {\n            Search::RowidEq { .. }\n            | Search::Seek { index: None, .. }\n            | Search::InSeek { index: None, .. } => {\n                format!(\n                    \"SEARCH {} USING INTEGER PRIMARY KEY (rowid=?)\",\n                    table.identifier\n                )\n            }\n            Search::Seek {\n                index: Some(index),\n                seek_def,\n            } => {\n                let constraints = seek_constraint_annotation(index, seek_def);\n                format!(\n                    \"SEARCH {} USING INDEX {}{}\",\n                    table.identifier, index.name, constraints\n                )\n            }\n            Search::InSeek {\n                index: Some(index), ..\n            } => {\n                let constraint = if let Some(col) = index.columns.first() {\n                    format!(\" ({}=?)\", col.name)\n                } else {\n                    String::new()\n                };\n                format!(\n                    \"SEARCH {} USING INDEX {}{}\",\n                    table.identifier, index.name, constraint\n                )\n            }\n        },\n        Operation::MultiIndexScan(multi_idx) => {\n            let index_names: Vec<&str> = multi_idx\n                .branches\n                .iter()\n                .map(|b| {\n                    b.index\n                        .as_ref()\n                        .map(|i| i.name.as_str())\n                        .unwrap_or(\"PRIMARY KEY\")\n                })\n                .collect();\n            format!(\n                \"MULTI-INDEX {} {} ({})\",\n                match multi_idx.set_op {\n                    SetOperation::Union => \"OR\",\n                    SetOperation::Intersection { .. } => \"AND\",\n                },\n                table.identifier,\n                index_names.join(\", \")\n            )\n        }\n        Operation::IndexMethodQuery(query) => {\n            let index_method = query.index.index_method.as_ref().unwrap();\n            format!(\n                \"QUERY INDEX METHOD {}\",\n                index_method.definition().method_name\n            )\n        }\n        Operation::HashJoin(_) => {\n            let table_name = if table.table.get_name() == table.identifier {\n                table.identifier.clone()\n            } else {\n                format!(\"{} AS {}\", table.table.get_name(), table.identifier)\n            };\n            format!(\"HASH JOIN {table_name}\")\n        }\n    }\n}\n\n/// Build SQLite-style constraint annotation string for an index seek.\n/// e.g. \"(label=? AND fromId>?)\"\npub(crate) fn seek_constraint_annotation(\n    index: &crate::schema::Index,\n    seek_def: &SeekDef,\n) -> String {\n    let mut parts = Vec::new();\n    // Equality prefix constraints\n    for (i, _constraint) in seek_def.prefix.iter().enumerate() {\n        if let Some(col) = index.columns.get(i) {\n            parts.push(format!(\"{}=?\", col.name));\n        }\n    }\n    // Range constraint from start key\n    let range_col_idx = seek_def.prefix.len();\n    if let SeekKeyComponent::Expr(_) = &seek_def.start.last_component {\n        if let Some(col) = index.columns.get(range_col_idx) {\n            let op_str = match seek_def.start.op {\n                SeekOp::GE { .. } => \">=\",\n                SeekOp::GT => \">\",\n                SeekOp::LE { .. } => \"<=\",\n                SeekOp::LT => \"<\",\n            };\n            parts.push(format!(\"{}{op_str}?\", col.name));\n        }\n    }\n    // Range constraint from end key.\n    // The end key's SeekOp is the B-tree termination condition (the negation of the\n    // user-facing SQL operator), so we reverse it for display.\n    if let SeekKeyComponent::Expr(_) = &seek_def.end.last_component {\n        if let Some(col) = index.columns.get(range_col_idx) {\n            let op_str = match seek_def.end.op {\n                SeekOp::GE { .. } => \"<\",\n                SeekOp::GT => \"<=\",\n                SeekOp::LE { .. } => \">\",\n                SeekOp::LT => \">=\",\n            };\n            parts.push(format!(\"{}{op_str}?\", col.name));\n        }\n    }\n    if parts.is_empty() {\n        String::new()\n    } else {\n        format!(\" ({})\", parts.join(\" AND \"))\n    }\n}\n\nimpl Display for Aggregate {\n    fn fmt(&self, f: &mut Formatter) -> fmt::Result {\n        let args_str = self\n            .args\n            .iter()\n            .map(|arg| arg.to_string())\n            .collect::<Vec<String>>()\n            .join(\", \");\n        write!(f, \"{:?}({})\", self.func, args_str)\n    }\n}\n\n/// For EXPLAIN QUERY PLAN\nimpl Display for Plan {\n    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {\n        match self {\n            Self::Select(select_plan) => select_plan.fmt(f),\n            Self::CompoundSelect {\n                left,\n                right_most,\n                limit,\n                offset,\n                order_by,\n            } => {\n                for (plan, operator) in left {\n                    plan.fmt(f)?;\n                    writeln!(f, \"{operator}\")?;\n                }\n                right_most.fmt(f)?;\n                if let Some(limit) = limit {\n                    writeln!(f, \"LIMIT: {limit}\")?;\n                }\n                if let Some(offset) = offset {\n                    writeln!(f, \"OFFSET: {offset}\")?;\n                }\n                if let Some(order_by) = order_by {\n                    writeln!(f, \"ORDER BY:\")?;\n                    for (expr, dir) in order_by {\n                        writeln!(\n                            f,\n                            \"  - {} {}\",\n                            expr,\n                            if *dir == SortOrder::Asc {\n                                \"ASC\"\n                            } else {\n                                \"DESC\"\n                            }\n                        )?;\n                    }\n                }\n                Ok(())\n            }\n            Self::Delete(delete_plan) => delete_plan.fmt(f),\n            Self::Update(update_plan) => update_plan.fmt(f),\n        }\n    }\n}\n\nimpl Display for SelectPlan {\n    fn fmt(&self, f: &mut Formatter) -> fmt::Result {\n        writeln!(f, \"QUERY PLAN\")?;\n\n        // Print each table reference with appropriate indentation based on join depth\n        for (i, member) in self.join_order.iter().enumerate() {\n            let reference = &self.table_references.joined_tables()[member.original_idx];\n            let is_last = i == self.join_order.len() - 1;\n            let indent = if i == 0 {\n                if is_last { \"`--\" } else { \"|--\" }.to_string()\n            } else {\n                format!(\n                    \"   {}{}\",\n                    \"|  \".repeat(i - 1),\n                    if is_last { \"`--\" } else { \"|--\" }\n                )\n            };\n\n            match &reference.op {\n                Operation::Scan(scan) => {\n                    let table_name = if reference.table.get_name() == reference.identifier {\n                        reference.identifier.clone()\n                    } else {\n                        format!(\"{} AS {}\", reference.table.get_name(), reference.identifier)\n                    };\n\n                    match scan {\n                        Scan::BTreeTable { index, .. } => {\n                            if let Some(index) = index {\n                                if reference.utilizes_covering_index() {\n                                    writeln!(\n                                        f,\n                                        \"{indent}SCAN {table_name} USING COVERING INDEX {}\",\n                                        index.name\n                                    )?;\n                                } else {\n                                    writeln!(\n                                        f,\n                                        \"{indent}SCAN {table_name} USING INDEX {}\",\n                                        index.name\n                                    )?;\n                                }\n                            } else {\n                                writeln!(f, \"{indent}SCAN {table_name}\")?;\n                            }\n                        }\n                        Scan::VirtualTable { .. } | Scan::Subquery { .. } => {\n                            writeln!(f, \"{indent}SCAN {table_name}\")?;\n                        }\n                    }\n                }\n                Operation::Search(search) => {\n                    let left_join_suffix = if member.is_outer { \" LEFT-JOIN\" } else { \"\" };\n                    match search {\n                        Search::RowidEq { .. }\n                        | Search::Seek { index: None, .. }\n                        | Search::InSeek { index: None, .. } => {\n                            writeln!(\n                                f,\n                                \"{indent}SEARCH {} USING INTEGER PRIMARY KEY (rowid=?){left_join_suffix}\",\n                                reference.identifier\n                            )?;\n                        }\n                        Search::Seek {\n                            index: Some(index),\n                            seek_def,\n                        } => {\n                            let constraints = seek_constraint_annotation(index, seek_def);\n                            writeln!(\n                                f,\n                                \"{indent}SEARCH {} USING INDEX {}{constraints}{left_join_suffix}\",\n                                reference.identifier, index.name\n                            )?;\n                        }\n                        Search::InSeek {\n                            index: Some(index), ..\n                        } => {\n                            let constraint = if let Some(col) = index.columns.first() {\n                                format!(\" ({}=?)\", col.name)\n                            } else {\n                                String::new()\n                            };\n                            writeln!(\n                                f,\n                                \"{indent}SEARCH {} USING INDEX {}{constraint}{left_join_suffix}\",\n                                reference.identifier, index.name\n                            )?;\n                        }\n                    }\n                }\n                Operation::IndexMethodQuery(query) => {\n                    let index_method = query.index.index_method.as_ref().unwrap();\n                    writeln!(\n                        f,\n                        \"{}QUERY INDEX METHOD {}\",\n                        indent,\n                        index_method.definition().method_name\n                    )?;\n                }\n                Operation::HashJoin(_) => {\n                    writeln!(f, \"{indent}HASH JOIN\")?;\n                }\n                Operation::MultiIndexScan(multi_idx) => {\n                    let index_names: Vec<&str> = multi_idx\n                        .branches\n                        .iter()\n                        .map(|b| {\n                            b.index\n                                .as_ref()\n                                .map(|i| i.name.as_str())\n                                .unwrap_or(\"PRIMARY KEY\")\n                        })\n                        .collect();\n                    let op_name = match multi_idx.set_op {\n                        SetOperation::Union => \"MULTI-INDEX OR\",\n                        SetOperation::Intersection { .. } => \"MULTI-INDEX AND\",\n                    };\n                    writeln!(\n                        f,\n                        \"{indent}{op_name} {} ({}) \",\n                        reference.identifier,\n                        index_names.join(\", \")\n                    )?;\n                }\n            }\n        }\n        if self.distinctness.is_distinct() {\n            writeln!(f, \"USE HASH TABLE FOR DISTINCT\")?;\n        }\n        Ok(())\n    }\n}\n\nimpl Display for DeletePlan {\n    fn fmt(&self, f: &mut Formatter) -> fmt::Result {\n        writeln!(f, \"QUERY PLAN\")?;\n\n        // Delete plan should only have one table reference\n        if let Some(reference) = self.table_references.joined_tables().first() {\n            let indent = \"`--\";\n\n            match &reference.op {\n                Operation::Scan(scan) => {\n                    let table_name = if reference.table.get_name() == reference.identifier {\n                        reference.identifier.clone()\n                    } else {\n                        format!(\"{} AS {}\", reference.table.get_name(), reference.identifier)\n                    };\n\n                    match scan {\n                        Scan::BTreeTable { index, .. } => {\n                            if let Some(index) = index {\n                                if reference.utilizes_covering_index() {\n                                    writeln!(\n                                        f,\n                                        \"{indent}DELETE FROM {table_name} USING COVERING INDEX {}\",\n                                        index.name\n                                    )?;\n                                } else {\n                                    writeln!(\n                                        f,\n                                        \"{indent}DELETE FROM {table_name} USING INDEX {}\",\n                                        index.name\n                                    )?;\n                                }\n                            } else {\n                                writeln!(f, \"{indent}DELETE FROM {table_name}\")?;\n                            }\n                        }\n                        Scan::VirtualTable { .. } | Scan::Subquery { .. } => {\n                            writeln!(f, \"{indent}DELETE FROM {table_name}\")?;\n                        }\n                    }\n                }\n                Operation::Search(search) => match search {\n                    Search::RowidEq { .. }\n                    | Search::Seek { index: None, .. }\n                    | Search::InSeek { index: None, .. } => {\n                        writeln!(\n                            f,\n                            \"{}SEARCH {} USING INTEGER PRIMARY KEY (rowid=?)\",\n                            indent, reference.identifier\n                        )?;\n                    }\n                    Search::Seek {\n                        index: Some(index), ..\n                    } => {\n                        writeln!(\n                            f,\n                            \"{}SEARCH {} USING INDEX {}\",\n                            indent, reference.identifier, index.name\n                        )?;\n                    }\n                    Search::InSeek {\n                        index: Some(index), ..\n                    } => {\n                        let constraint = if let Some(col) = index.columns.first() {\n                            format!(\" ({}=?)\", col.name)\n                        } else {\n                            String::new()\n                        };\n                        writeln!(\n                            f,\n                            \"{}SEARCH {} USING INDEX {}{constraint}\",\n                            indent, reference.identifier, index.name\n                        )?;\n                    }\n                },\n                Operation::IndexMethodQuery(query) => {\n                    let module = query.index.index_method.as_ref().unwrap();\n                    writeln!(\n                        f,\n                        \"{}QUERY MODULE {}\",\n                        indent,\n                        module.definition().method_name\n                    )?;\n                }\n                Operation::HashJoin(_) => {\n                    unreachable!(\"Delete plan should not have hash joins\");\n                }\n                Operation::MultiIndexScan(multi_idx) => {\n                    let index_names: Vec<&str> = multi_idx\n                        .branches\n                        .iter()\n                        .map(|b| {\n                            b.index\n                                .as_ref()\n                                .map(|i| i.name.as_str())\n                                .unwrap_or(\"PRIMARY KEY\")\n                        })\n                        .collect();\n                    let op_name = match multi_idx.set_op {\n                        SetOperation::Union => \"MULTI-INDEX OR\",\n                        SetOperation::Intersection { .. } => \"MULTI-INDEX AND\",\n                    };\n                    writeln!(\n                        f,\n                        \"{indent}{op_name} {} ({})\",\n                        reference.identifier,\n                        index_names.join(\", \")\n                    )?;\n                }\n            }\n        }\n        Ok(())\n    }\n}\n\nimpl fmt::Display for UpdatePlan {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        writeln!(f, \"QUERY PLAN\")?;\n\n        for (i, reference) in self.table_references.joined_tables().iter().enumerate() {\n            let is_last = i == self.table_references.joined_tables().len() - 1;\n            let indent = if i == 0 {\n                if is_last { \"`--\" } else { \"|--\" }.to_string()\n            } else {\n                format!(\n                    \"   {}{}\",\n                    \"|  \".repeat(i - 1),\n                    if is_last { \"`--\" } else { \"|--\" }\n                )\n            };\n\n            match &reference.op {\n                Operation::Scan(scan) => {\n                    let table_name = if reference.table.get_name() == reference.identifier {\n                        reference.identifier.clone()\n                    } else {\n                        format!(\"{} AS {}\", reference.table.get_name(), reference.identifier)\n                    };\n\n                    match scan {\n                        Scan::BTreeTable { index, .. } => {\n                            let action = if i == 0 { \"UPDATE\" } else { \"SCAN\" };\n                            if let Some(index) = index {\n                                if reference.utilizes_covering_index() {\n                                    writeln!(\n                                        f,\n                                        \"{indent}{action} {table_name} USING COVERING INDEX {}\",\n                                        index.name\n                                    )?;\n                                } else {\n                                    writeln!(\n                                        f,\n                                        \"{indent}{action} {table_name} USING INDEX {}\",\n                                        index.name\n                                    )?;\n                                }\n                            } else {\n                                writeln!(f, \"{indent}{action} {table_name}\")?;\n                            }\n                        }\n                        Scan::VirtualTable { .. } | Scan::Subquery { .. } => {\n                            if i == 0 {\n                                writeln!(f, \"{indent}UPDATE {table_name}\")?;\n                            } else {\n                                writeln!(f, \"{indent}SCAN {table_name}\")?;\n                            }\n                        }\n                    }\n                }\n                Operation::Search(search) => match search {\n                    Search::RowidEq { .. }\n                    | Search::Seek { index: None, .. }\n                    | Search::InSeek { index: None, .. } => {\n                        writeln!(\n                            f,\n                            \"{}SEARCH {} USING INTEGER PRIMARY KEY (rowid=?)\",\n                            indent, reference.identifier\n                        )?;\n                    }\n                    Search::Seek {\n                        index: Some(index), ..\n                    } => {\n                        writeln!(\n                            f,\n                            \"{}SEARCH {} USING INDEX {}\",\n                            indent, reference.identifier, index.name\n                        )?;\n                    }\n                    Search::InSeek {\n                        index: Some(index), ..\n                    } => {\n                        let constraint = if let Some(col) = index.columns.first() {\n                            format!(\" ({}=?)\", col.name)\n                        } else {\n                            String::new()\n                        };\n                        writeln!(\n                            f,\n                            \"{}SEARCH {} USING INDEX {}{constraint}\",\n                            indent, reference.identifier, index.name\n                        )?;\n                    }\n                },\n                Operation::IndexMethodQuery(query) => {\n                    let module = query.index.index_method.as_ref().unwrap();\n                    writeln!(\n                        f,\n                        \"{}QUERY MODULE {}\",\n                        indent,\n                        module.definition().method_name\n                    )?;\n                }\n                Operation::HashJoin(_) => {\n                    unreachable!(\"Update plan should not have hash joins\");\n                }\n                Operation::MultiIndexScan(_) => {\n                    unreachable!(\"Update plan should not have multi-index scans\");\n                }\n            }\n        }\n        if !self.order_by.is_empty() {\n            writeln!(f, \"ORDER BY:\")?;\n            for (expr, dir) in &self.order_by {\n                writeln!(\n                    f,\n                    \"  - {} {}\",\n                    expr,\n                    if *dir == SortOrder::Asc {\n                        \"ASC\"\n                    } else {\n                        \"DESC\"\n                    }\n                )?;\n            }\n        }\n        if let Some(limit) = self.limit.as_ref() {\n            writeln!(f, \"LIMIT: {limit}\")?;\n        }\n        if let Some(ret) = &self.returning {\n            writeln!(f, \"RETURNING:\")?;\n            for col in ret {\n                writeln!(f, \"  - {}\", col.expr)?;\n            }\n        }\n\n        Ok(())\n    }\n}\n\npub struct PlanContext<'a>(pub &'a [&'a TableReferences]);\n\n// Definitely not perfect yet\nimpl ToSqlContext for PlanContext<'_> {\n    fn get_column_name(&self, table_id: TableInternalId, col_idx: usize) -> Option<Option<&str>> {\n        let (_, table) = self\n            .0\n            .iter()\n            .find_map(|table_ref| table_ref.find_table_by_internal_id(table_id))?;\n        let cols = table.columns();\n        cols.get(col_idx)\n            .map(|col| col.name.as_ref().map(|name| name.as_ref()))\n    }\n\n    fn get_table_name(&self, id: TableInternalId) -> Option<&str> {\n        let table_ref = self\n            .0\n            .iter()\n            .find(|table_ref| table_ref.find_table_by_internal_id(id).is_some())?;\n        let joined_table = table_ref.find_joined_table_by_internal_id(id);\n        let outer_query = table_ref.find_outer_query_ref_by_internal_id(id);\n        match (joined_table, outer_query) {\n            (Some(table), None) => Some(&table.identifier),\n            (None, Some(table)) => Some(&table.identifier),\n            (Some(table), Some(_)) => Some(&table.identifier),\n            (None, None) => unreachable!(),\n        }\n    }\n}\n\nimpl ToTokens for Plan {\n    fn to_tokens<S: TokenStream + ?Sized, C: ToSqlContext>(\n        &self,\n        s: &mut S,\n        context: &C,\n    ) -> Result<(), S::Error> {\n        match self {\n            Self::Select(select) => {\n                select.to_tokens(s, &PlanContext(&[&select.table_references]))?;\n            }\n            Self::CompoundSelect {\n                left,\n                right_most,\n                limit,\n                offset,\n                order_by,\n            } => {\n                let all_refs = left\n                    .iter()\n                    .flat_map(|(plan, _)| std::iter::once(&plan.table_references))\n                    .chain(std::iter::once(&right_most.table_references))\n                    .collect::<Vec<_>>();\n                let context = &PlanContext(all_refs.as_slice());\n\n                for (plan, operator) in left {\n                    plan.to_tokens(s, context)?;\n                    operator.to_tokens(s, context)?;\n                }\n\n                right_most.to_tokens(s, context)?;\n\n                if let Some(order_by) = order_by {\n                    s.append(TokenType::TK_ORDER, None)?;\n                    s.append(TokenType::TK_BY, None)?;\n\n                    s.comma(\n                        order_by.iter().map(|(expr, order)| ast::SortedColumn {\n                            expr: expr.clone().into(),\n                            order: Some(*order),\n                            nulls: None,\n                        }),\n                        context,\n                    )?;\n                }\n\n                if let Some(limit) = &limit {\n                    s.append(TokenType::TK_LIMIT, None)?;\n                    s.append(TokenType::TK_FLOAT, Some(&limit.to_string()))?;\n                }\n\n                if let Some(offset) = &offset {\n                    s.append(TokenType::TK_OFFSET, None)?;\n                    s.append(TokenType::TK_FLOAT, Some(&offset.to_string()))?;\n                }\n            }\n            Self::Delete(delete) => delete.to_tokens(s, context)?,\n            Self::Update(update) => update.to_tokens(s, context)?,\n        }\n\n        Ok(())\n    }\n}\n\nimpl Display for JoinedTable {\n    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {\n        self.displayer(&BlankContext).fmt(f)\n    }\n}\n\nimpl ToTokens for JoinedTable {\n    fn to_tokens<S: TokenStream + ?Sized, C: ToSqlContext>(\n        &self,\n        s: &mut S,\n        _context: &C,\n    ) -> Result<(), S::Error> {\n        match &self.table {\n            Table::BTree(..) | Table::Virtual(..) => {\n                let name = self.table.get_name();\n                s.append(TokenType::TK_ID, Some(name))?;\n                if self.identifier != name {\n                    s.append(TokenType::TK_AS, None)?;\n                    s.append(TokenType::TK_ID, Some(&self.identifier))?;\n                }\n            }\n            Table::FromClauseSubquery(from_clause_subquery) => {\n                s.append(TokenType::TK_LP, None)?;\n                // Plan::to_tokens creates its own context internally, so we pass BlankContext here.\n                from_clause_subquery.plan.to_tokens(s, &BlankContext)?;\n                s.append(TokenType::TK_RP, None)?;\n\n                s.append(TokenType::TK_AS, None)?;\n                s.append(TokenType::TK_ID, Some(&self.identifier))?;\n            }\n        };\n\n        Ok(())\n    }\n}\n\n// TODO: currently cannot print the original CTE as it is optimized into a subquery\nimpl ToTokens for SelectPlan {\n    fn to_tokens<S: TokenStream + ?Sized, C: ToSqlContext>(\n        &self,\n        s: &mut S,\n        context: &C,\n    ) -> Result<(), S::Error> {\n        if !self.values.is_empty() {\n            ast::OneSelect::Values(\n                self.values\n                    .iter()\n                    .map(|values| values.iter().map(|v| Box::from(v.clone())).collect())\n                    .collect(),\n            )\n            .to_tokens(s, context)?;\n        } else {\n            s.append(TokenType::TK_SELECT, None)?;\n            if self.distinctness.is_distinct() {\n                s.append(TokenType::TK_DISTINCT, None)?;\n            }\n\n            for (i, ResultSetColumn { expr, alias, .. }) in self.result_columns.iter().enumerate() {\n                if i != 0 {\n                    s.append(TokenType::TK_COMMA, None)?;\n                }\n\n                expr.to_tokens(s, context)?;\n                if let Some(alias) = alias {\n                    s.append(TokenType::TK_AS, None)?;\n                    s.append(TokenType::TK_ID, Some(alias))?;\n                }\n            }\n            s.append(TokenType::TK_FROM, None)?;\n\n            for (i, order) in self.join_order.iter().enumerate() {\n                if i != 0 {\n                    if order.is_outer {\n                        s.append(TokenType::TK_ORDER, None)?;\n                    }\n                    s.append(TokenType::TK_JOIN, None)?;\n                }\n\n                let table_ref = self.joined_tables().get(order.original_idx).unwrap();\n                table_ref.to_tokens(s, context)?;\n            }\n\n            if !self.where_clause.is_empty() {\n                s.append(TokenType::TK_WHERE, None)?;\n\n                for (i, expr) in self\n                    .where_clause\n                    .iter()\n                    .map(|where_clause| where_clause.expr.clone())\n                    .enumerate()\n                {\n                    if i != 0 {\n                        s.append(TokenType::TK_AND, None)?;\n                    }\n                    expr.to_tokens(s, context)?;\n                }\n            }\n\n            if let Some(group_by) = &self.group_by {\n                if !group_by.exprs.is_empty() {\n                    s.append(TokenType::TK_GROUP, None)?;\n                    s.append(TokenType::TK_BY, None)?;\n\n                    s.comma(group_by.exprs.iter(), context)?;\n                }\n\n                // TODO: not sure where I need to place the group_by.sort_order\n                if let Some(having) = &group_by.having {\n                    s.append(TokenType::TK_HAVING, None)?;\n\n                    for (i, expr) in having.iter().enumerate() {\n                        if i != 0 {\n                            s.append(TokenType::TK_AND, None)?;\n                        }\n                        expr.to_tokens(s, context)?;\n                    }\n                }\n            }\n        }\n\n        if let Some(window) = &self.window {\n            if let Some(window_name) = &window.name {\n                s.append(TokenType::TK_WINDOW, None)?;\n                s.append(TokenType::TK_ID, Some(window_name))?;\n                s.append(TokenType::TK_AS, None)?;\n\n                s.append(TokenType::TK_LP, None)?;\n\n                if !window.partition_by.is_empty() {\n                    s.append(TokenType::TK_PARTITION, None)?;\n                    s.append(TokenType::TK_BY, None)?;\n                    s.comma(window.partition_by.iter(), context)?;\n                }\n\n                if !window.order_by.is_empty() {\n                    s.append(TokenType::TK_ORDER, None)?;\n                    s.append(TokenType::TK_BY, None)?;\n                    s.comma(\n                        window\n                            .order_by\n                            .iter()\n                            .map(|(expr, order)| ast::SortedColumn {\n                                expr: Box::new(expr.clone()),\n                                order: Some(*order),\n                                nulls: None,\n                            }),\n                        context,\n                    )?;\n                }\n\n                s.append(TokenType::TK_RP, None)?;\n            }\n        }\n\n        if !self.order_by.is_empty() {\n            s.append(TokenType::TK_ORDER, None)?;\n            s.append(TokenType::TK_BY, None)?;\n\n            s.comma(\n                self.order_by.iter().map(|(expr, order)| ast::SortedColumn {\n                    expr: expr.clone(),\n                    order: Some(*order),\n                    nulls: None,\n                }),\n                context,\n            )?;\n        }\n\n        if let Some(limit) = &self.limit {\n            s.append(TokenType::TK_LIMIT, None)?;\n            s.append(TokenType::TK_FLOAT, Some(&limit.to_string()))?;\n        }\n\n        if let Some(offset) = &self.offset {\n            s.append(TokenType::TK_OFFSET, None)?;\n            s.append(TokenType::TK_FLOAT, Some(&offset.to_string()))?;\n        }\n\n        Ok(())\n    }\n}\n\nimpl ToTokens for DeletePlan {\n    fn to_tokens<S: TokenStream + ?Sized, C: ToSqlContext>(\n        &self,\n        s: &mut S,\n        _: &C,\n    ) -> Result<(), S::Error> {\n        let table = self\n            .table_references\n            .joined_tables()\n            .first()\n            .expect(\"Delete Plan should have only one table reference\");\n        let context = &[&self.table_references];\n        let context = &PlanContext(context);\n\n        s.append(TokenType::TK_DELETE, None)?;\n        s.append(TokenType::TK_FROM, None)?;\n        s.append(TokenType::TK_ID, Some(table.table.get_name()))?;\n\n        if !self.where_clause.is_empty() {\n            s.append(TokenType::TK_WHERE, None)?;\n\n            for (i, expr) in self\n                .where_clause\n                .iter()\n                .map(|where_clause| where_clause.expr.clone())\n                .enumerate()\n            {\n                if i != 0 {\n                    s.append(TokenType::TK_AND, None)?;\n                }\n                expr.to_tokens(s, context)?;\n            }\n        }\n\n        if !self.order_by.is_empty() {\n            s.append(TokenType::TK_ORDER, None)?;\n            s.append(TokenType::TK_BY, None)?;\n\n            s.comma(\n                self.order_by.iter().map(|(expr, order)| ast::SortedColumn {\n                    expr: expr.clone(),\n                    order: Some(*order),\n                    nulls: None,\n                }),\n                context,\n            )?;\n        }\n\n        if let Some(limit) = &self.limit {\n            s.append(TokenType::TK_LIMIT, None)?;\n            s.append(TokenType::TK_FLOAT, Some(&limit.to_string()))?;\n        }\n\n        if let Some(offset) = &self.offset {\n            s.append(TokenType::TK_OFFSET, None)?;\n            s.append(TokenType::TK_FLOAT, Some(&offset.to_string()))?;\n        }\n\n        Ok(())\n    }\n}\n\nimpl ToTokens for UpdatePlan {\n    fn to_tokens<S: TokenStream + ?Sized, C: ToSqlContext>(\n        &self,\n        s: &mut S,\n        _: &C,\n    ) -> Result<(), S::Error> {\n        let table = self\n            .table_references\n            .joined_tables()\n            .first()\n            .expect(\"UPDATE Plan should have only one table reference\");\n        let context = [&self.table_references];\n        let context = &PlanContext(&context);\n\n        s.append(TokenType::TK_UPDATE, None)?;\n        s.append(TokenType::TK_ID, Some(table.table.get_name()))?;\n        s.append(TokenType::TK_SET, None)?;\n\n        s.comma(\n            self.set_clauses.iter().map(|(col_idx, set_expr)| {\n                let col_name = table\n                    .table\n                    .get_column_at(*col_idx)\n                    .as_ref()\n                    .unwrap()\n                    .name\n                    .as_ref()\n                    .unwrap();\n\n                ast::Set {\n                    col_names: vec![ast::Name::exact(col_name.clone())],\n                    expr: set_expr.clone(),\n                }\n            }),\n            context,\n        )?;\n\n        if !self.where_clause.is_empty() {\n            s.append(TokenType::TK_WHERE, None)?;\n\n            let mut iter = self\n                .where_clause\n                .iter()\n                .map(|where_clause| where_clause.expr.clone());\n            iter.next()\n                .expect(\"should not be empty\")\n                .to_tokens(s, context)?;\n            for expr in iter {\n                s.append(TokenType::TK_AND, None)?;\n                expr.to_tokens(s, context)?;\n            }\n        }\n\n        if !self.order_by.is_empty() {\n            s.append(TokenType::TK_ORDER, None)?;\n            s.append(TokenType::TK_BY, None)?;\n\n            s.comma(\n                self.order_by.iter().map(|(expr, order)| ast::SortedColumn {\n                    expr: expr.clone(),\n                    order: Some(*order),\n                    nulls: None,\n                }),\n                context,\n            )?;\n        }\n\n        if let Some(limit) = &self.limit {\n            s.append(TokenType::TK_LIMIT, None)?;\n            s.append(TokenType::TK_FLOAT, Some(&limit.to_string()))?;\n        }\n        if let Some(offset) = &self.offset {\n            s.append(TokenType::TK_OFFSET, None)?;\n            s.append(TokenType::TK_FLOAT, Some(&offset.to_string()))?;\n        }\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "core/translate/emitter/delete.rs",
    "content": "use super::{Resolver, Result, TranslateCtx};\nuse crate::{\n    emit_explain,\n    schema::BTreeTable,\n    sync::Arc,\n    translate::{\n        display::format_eqp_detail,\n        emitter::{\n            emit_cdc_autocommit_commit, emit_cdc_full_record, emit_cdc_insns,\n            emit_index_column_value_old_image, emit_program_for_select,\n            get_relevant_triggers_type_and_time, init_limit, OperationMode, TriggerTime,\n        },\n        expr::{\n            emit_returning_results, emit_returning_scan_back, restore_returning_row_image_in_cache,\n            seed_returning_row_image_in_cache, translate_expr_no_constant_opt, NoConstantOptReason,\n            ReturningBufferCtx,\n        },\n        fkeys::{\n            build_index_affinity_string, emit_guarded_fk_decrement, open_read_index,\n            open_read_table, ForeignKeyActions,\n        },\n        main_loop::{CloseLoop, InitLoop, OpenLoop},\n        plan::{\n            DeletePlan, EvalAt, JoinOrderMember, JoinedTable, NonFromClauseSubquery, Operation,\n            ResultSetColumn, Search, TableReferences,\n        },\n        subquery::{emit_non_from_clause_subqueries_for_eval_at, emit_non_from_clause_subquery},\n        trigger_exec::{fire_trigger, has_relevant_triggers_type_only, TriggerContext},\n    },\n    vdbe::{\n        builder::{CursorKey, CursorType, ProgramBuilder},\n        insn::{Insn, RegisterOrLiteral},\n    },\n    CaptureDataChangesExt, Connection,\n};\nuse tracing::{instrument, Level};\nuse turso_parser::ast::TriggerEvent;\n\n#[instrument(skip_all, level = Level::DEBUG)]\npub fn emit_program_for_delete(\n    connection: &Arc<Connection>,\n    resolver: &Resolver,\n    program: &mut ProgramBuilder,\n    mut plan: DeletePlan,\n) -> Result<()> {\n    let mut t_ctx = TranslateCtx::new(\n        program,\n        resolver.fork(),\n        plan.table_references.joined_tables().len(),\n        connection.db.opts.unsafe_testing,\n    );\n\n    let after_main_loop_label = program.allocate_label();\n    t_ctx.label_main_loop_end = Some(after_main_loop_label);\n\n    // Open an ephemeral table for buffering RETURNING results.\n    // All DML completes before any RETURNING rows are yielded to the caller.\n    let returning_buffer = if !plan.result_columns.is_empty() {\n        let table_ref = plan.table_references.joined_tables().first().unwrap();\n        let btree_table = table_ref\n            .table\n            .btree()\n            .expect(\"DELETE target must be a BTree table\");\n        let ret_cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(btree_table));\n        program.emit_insn(Insn::OpenEphemeral {\n            cursor_id: ret_cursor_id,\n            is_table: true,\n        });\n        Some(ReturningBufferCtx {\n            cursor_id: ret_cursor_id,\n            num_columns: plan.result_columns.len(),\n        })\n    } else {\n        None\n    };\n\n    init_limit(program, &mut t_ctx, &plan.limit, &plan.offset)?;\n\n    // No rows will be read from source table loops if there is a constant false condition eg. WHERE 0\n    if plan.contains_constant_false_condition {\n        program.emit_insn(Insn::Goto {\n            target_pc: after_main_loop_label,\n        });\n    }\n\n    let join_order = plan\n        .table_references\n        .joined_tables()\n        .iter()\n        .enumerate()\n        .map(|(i, t)| JoinOrderMember {\n            table_id: t.internal_id,\n            original_idx: i,\n            is_outer: false,\n        })\n        .collect::<Vec<_>>();\n\n    // Evaluate uncorrelated subqueries as early as possible (only for normal path without rowset)\n    // For the rowset path, subqueries are handled by emit_program_for_select on the rowset_plan.\n    if plan.rowset_plan.is_none() {\n        emit_non_from_clause_subqueries_for_eval_at(\n            program,\n            &t_ctx.resolver,\n            &mut plan.non_from_clause_subqueries,\n            &join_order,\n            Some(&plan.table_references),\n            EvalAt::BeforeLoop,\n            |_| true,\n        )?;\n    }\n\n    // Initialize cursors and other resources needed for query execution\n    InitLoop::emit(\n        program,\n        &mut t_ctx,\n        &plan.table_references,\n        &mut [],\n        &OperationMode::DELETE,\n        &plan.where_clause,\n        &join_order,\n        &mut plan.non_from_clause_subqueries,\n    )?;\n\n    // If there's a rowset_plan, materialize rowids into a RowSet first and then iterate the RowSet\n    // to delete the rows.\n    if let Some(rowset_plan) = plan.rowset_plan.take() {\n        let rowset_reg = plan\n            .rowset_reg\n            .expect(\"rowset_reg must be Some if rowset_plan is Some\");\n\n        // Initialize the RowSet register with NULL (RowSet will be created on first RowSetAdd)\n        program.emit_insn(Insn::Null {\n            dest: rowset_reg,\n            dest_end: None,\n        });\n\n        // Execute the rowset SELECT plan to populate the rowset.\n        program.nested(|program| emit_program_for_select(program, resolver, rowset_plan))?;\n\n        // Close the read cursor(s) opened by the rowset plan before opening for writing\n        let table_ref = plan.table_references.joined_tables().first().unwrap();\n        let table_cursor_id_read =\n            program.resolve_cursor_id(&CursorKey::table(table_ref.internal_id));\n        program.emit_insn(Insn::Close {\n            cursor_id: table_cursor_id_read,\n        });\n\n        // Open the table cursor for writing\n        let table_cursor_id = table_cursor_id_read;\n\n        if let Some(btree_table) = table_ref.table.btree() {\n            program.emit_insn(Insn::OpenWrite {\n                cursor_id: table_cursor_id,\n                root_page: RegisterOrLiteral::Literal(btree_table.root_page),\n                db: table_ref.database_id,\n            });\n\n            // Open all indexes for writing (needed for DELETE)\n            let write_indices: Vec<_> = resolver.with_schema(table_ref.database_id, |s| {\n                s.get_indices(table_ref.table.get_name()).cloned().collect()\n            });\n            for index in &write_indices {\n                let index_cursor_id = program.alloc_cursor_index(\n                    Some(CursorKey::index(table_ref.internal_id, index.clone())),\n                    index,\n                )?;\n                program.emit_insn(Insn::OpenWrite {\n                    cursor_id: index_cursor_id,\n                    root_page: RegisterOrLiteral::Literal(index.root_page),\n                    db: table_ref.database_id,\n                });\n            }\n        }\n\n        // Now iterate over the RowSet and delete each rowid\n        let rowset_loop_start = program.allocate_label();\n        let rowset_loop_end = program.allocate_label();\n        let rowid_reg = program.alloc_register();\n        if table_ref.table.virtual_table().is_some() {\n            // VUpdate requires a NULL second argument (\"new rowid\") for deletion\n            let new_rowid_reg = program.alloc_register();\n            program.emit_insn(Insn::Null {\n                dest: new_rowid_reg,\n                dest_end: None,\n            });\n        }\n\n        program.preassign_label_to_next_insn(rowset_loop_start);\n\n        // Read next rowid from RowSet\n        // Note: rowset_loop_end will be resolved later when we assign it\n        program.emit_insn(Insn::RowSetRead {\n            rowset_reg,\n            pc_if_empty: rowset_loop_end,\n            dest_reg: rowid_reg,\n        });\n\n        emit_delete_insns_when_triggers_present(\n            connection,\n            program,\n            &mut t_ctx,\n            &mut plan.table_references,\n            &mut plan.non_from_clause_subqueries,\n            &plan.result_columns,\n            rowid_reg,\n            table_cursor_id,\n            resolver,\n            returning_buffer.as_ref(),\n        )?;\n\n        // Continue loop\n        program.emit_insn(Insn::Goto {\n            target_pc: rowset_loop_start,\n        });\n\n        // Assign the end label here, after all loop body code\n        program.preassign_label_to_next_insn(rowset_loop_end);\n    } else {\n        // Normal DELETE path without RowSet\n\n        // Emit EXPLAIN QUERY PLAN annotation\n        let table_ref = plan\n            .table_references\n            .joined_tables()\n            .first()\n            .expect(\"DELETE always has one joined table\");\n        emit_explain!(program, true, format_eqp_detail(table_ref));\n\n        // Set up main query execution loop\n        OpenLoop::emit(\n            program,\n            &mut t_ctx,\n            &plan.table_references,\n            &join_order,\n            &plan.where_clause,\n            None,\n            OperationMode::DELETE,\n            &mut plan.non_from_clause_subqueries,\n        )?;\n\n        emit_delete_insns(\n            connection,\n            program,\n            &mut t_ctx,\n            &mut plan.table_references,\n            &mut plan.non_from_clause_subqueries,\n            &plan.result_columns,\n            resolver,\n            returning_buffer.as_ref(),\n        )?;\n\n        // Clean up and close the main execution loop\n        CloseLoop::emit(\n            program,\n            &mut t_ctx,\n            &plan.table_references,\n            &join_order,\n            OperationMode::DELETE,\n            None,\n        )?;\n    }\n    program.preassign_label_to_next_insn(after_main_loop_label);\n    if let Some(cdc_cursor_id) = t_ctx.cdc_cursor_id {\n        emit_cdc_autocommit_commit(program, resolver, cdc_cursor_id)?;\n    }\n    // Emit scan-back loop for buffered RETURNING results.\n    // All DML is complete at this point; now yield the buffered rows to the caller.\n    // FkCheck must come before the scan-back so that FK violations prevent\n    // RETURNING rows from being emitted (matching SQLite behavior).\n    if let Some(ref buf) = returning_buffer {\n        program.emit_insn(Insn::FkCheck { deferred: false });\n        emit_returning_scan_back(program, buf);\n    }\n    // Finalize program\n    program.result_columns = plan.result_columns;\n    program.table_references.extend(plan.table_references);\n    Ok(())\n}\n\n#[allow(clippy::too_many_arguments)]\npub fn emit_fk_child_decrement_on_delete(\n    program: &mut ProgramBuilder,\n    child_tbl: &BTreeTable,\n    child_table_name: &str,\n    child_cursor_id: usize,\n    child_rowid_reg: usize,\n    database_id: usize,\n    resolver: &Resolver,\n) -> crate::Result<()> {\n    for fk_ref in\n        resolver.with_schema(database_id, |s| s.resolved_fks_for_child(child_table_name))?\n    {\n        if !fk_ref.fk.deferred {\n            continue;\n        }\n        // Fast path: if any FK column is NULL can't be a violation\n        let null_skip = program.allocate_label();\n        for cname in &fk_ref.child_cols {\n            let (pos, col) = child_tbl.get_column(cname).unwrap();\n            let src = if col.is_rowid_alias() {\n                child_rowid_reg\n            } else {\n                let tmp = program.alloc_register();\n                program.emit_insn(Insn::Column {\n                    cursor_id: child_cursor_id,\n                    column: pos,\n                    dest: tmp,\n                    default: None,\n                });\n                tmp\n            };\n            program.emit_insn(Insn::IsNull {\n                reg: src,\n                target_pc: null_skip,\n            });\n        }\n\n        if fk_ref.parent_uses_rowid {\n            // Probe parent table by rowid\n            let parent_tbl = resolver\n                .with_schema(database_id, |s| s.get_btree_table(&fk_ref.fk.parent_table))\n                .expect(\"parent btree\");\n            let pcur = open_read_table(program, &parent_tbl, database_id);\n\n            let (pos, col) = child_tbl.get_column(&fk_ref.child_cols[0]).unwrap();\n            let val = if col.is_rowid_alias() {\n                child_rowid_reg\n            } else {\n                let tmp = program.alloc_register();\n                program.emit_insn(Insn::Column {\n                    cursor_id: child_cursor_id,\n                    column: pos,\n                    dest: tmp,\n                    default: None,\n                });\n                tmp\n            };\n            let tmpi = program.alloc_register();\n            program.emit_insn(Insn::Copy {\n                src_reg: val,\n                dst_reg: tmpi,\n                extra_amount: 0,\n            });\n            program.emit_insn(Insn::MustBeInt { reg: tmpi });\n\n            // NotExists jumps when the parent key is missing, so we decrement there\n            let missing = program.allocate_label();\n            let done = program.allocate_label();\n\n            program.emit_insn(Insn::NotExists {\n                cursor: pcur,\n                rowid_reg: tmpi,\n                target_pc: missing,\n            });\n\n            // Parent FOUND, no decrement\n            program.emit_insn(Insn::Close { cursor_id: pcur });\n            program.emit_insn(Insn::Goto { target_pc: done });\n\n            // Parent MISSING, decrement is guarded by FkIfZero to avoid underflow\n            program.preassign_label_to_next_insn(missing);\n            program.emit_insn(Insn::Close { cursor_id: pcur });\n            emit_guarded_fk_decrement(program, done, true);\n            program.preassign_label_to_next_insn(done);\n        } else {\n            // Probe parent unique index\n            let parent_tbl = resolver\n                .with_schema(database_id, |s| s.get_btree_table(&fk_ref.fk.parent_table))\n                .expect(\"parent btree\");\n            let idx = fk_ref.parent_unique_index.as_ref().expect(\"unique index\");\n            let icur = open_read_index(program, idx, database_id);\n\n            // Build probe from current child row\n            let n = fk_ref.child_cols.len();\n            let probe = program.alloc_registers(n);\n            for (i, cname) in fk_ref.child_cols.iter().enumerate() {\n                let (pos, col) = child_tbl.get_column(cname).unwrap();\n                let src = if col.is_rowid_alias() {\n                    child_rowid_reg\n                } else {\n                    let r = program.alloc_register();\n                    program.emit_insn(Insn::Column {\n                        cursor_id: child_cursor_id,\n                        column: pos,\n                        dest: r,\n                        default: None,\n                    });\n                    r\n                };\n                program.emit_insn(Insn::Copy {\n                    src_reg: src,\n                    dst_reg: probe + i,\n                    extra_amount: 0,\n                });\n            }\n            program.emit_insn(Insn::Affinity {\n                start_reg: probe,\n                count: std::num::NonZeroUsize::new(n).unwrap(),\n                affinities: build_index_affinity_string(idx, &parent_tbl),\n            });\n\n            let ok = program.allocate_label();\n            program.emit_insn(Insn::Found {\n                cursor_id: icur,\n                target_pc: ok,\n                record_reg: probe,\n                num_regs: n,\n            });\n            program.emit_insn(Insn::Close { cursor_id: icur });\n            emit_guarded_fk_decrement(program, ok, true);\n            program.preassign_label_to_next_insn(ok);\n            program.emit_insn(Insn::Close { cursor_id: icur });\n        }\n        program.preassign_label_to_next_insn(null_skip);\n    }\n    Ok(())\n}\n\n#[allow(clippy::too_many_arguments)]\nfn emit_delete_insns<'a>(\n    connection: &Arc<Connection>,\n    program: &mut ProgramBuilder,\n    t_ctx: &mut TranslateCtx<'a>,\n    table_references: &mut TableReferences,\n    non_from_clause_subqueries: &mut [NonFromClauseSubquery],\n    result_columns: &'a [ResultSetColumn],\n    resolver: &Resolver,\n    returning_buffer: Option<&ReturningBufferCtx>,\n) -> Result<()> {\n    // we can either use this obviously safe raw pointer or we can clone it\n    let table_reference: *const JoinedTable = table_references.joined_tables().first().unwrap();\n    if unsafe { &*table_reference }\n        .virtual_table()\n        .is_some_and(|t| t.readonly())\n    {\n        return Err(crate::LimboError::ReadOnly);\n    }\n    let internal_id = unsafe { (*table_reference).internal_id };\n\n    let cursor_id = match unsafe { &(*table_reference).op } {\n        Operation::Scan { .. } => program.resolve_cursor_id(&CursorKey::table(internal_id)),\n        Operation::Search(search) => match search {\n            Search::RowidEq { .. }\n            | Search::Seek { index: None, .. }\n            | Search::InSeek { index: None, .. } => {\n                program.resolve_cursor_id(&CursorKey::table(internal_id))\n            }\n            Search::Seek {\n                index: Some(index), ..\n            }\n            | Search::InSeek {\n                index: Some(index), ..\n            } => program.resolve_cursor_id(&CursorKey::index(internal_id, index.clone())),\n        },\n        Operation::IndexMethodQuery(query) => {\n            program.resolve_cursor_id(&CursorKey::index(internal_id, query.index.clone()))\n        }\n        Operation::HashJoin(_) => {\n            unreachable!(\"access through HashJoin is not supported for delete statements\")\n        }\n        Operation::MultiIndexScan(_) => {\n            unreachable!(\"access through MultiIndexScan is not supported for delete statements\")\n        }\n    };\n    let btree_table = unsafe { &*table_reference }.btree();\n    let database_id = unsafe { (*table_reference).database_id };\n    let main_table_cursor_id = program.resolve_cursor_id(&CursorKey::table(internal_id));\n    let has_returning = !result_columns.is_empty();\n    let has_delete_triggers = if let Some(btree_table) = btree_table {\n        t_ctx.resolver.with_schema(database_id, |s| {\n            has_relevant_triggers_type_only(s, TriggerEvent::Delete, None, &btree_table)\n        })\n    } else {\n        false\n    };\n\n    // Apply OFFSET: skip the first N matching rows before deleting\n    if let Some(offset) = t_ctx.reg_offset {\n        let loop_labels = *t_ctx\n            .labels_main_loop\n            .first()\n            .expect(\"loop labels to exist\");\n        program.emit_insn(Insn::IfPos {\n            reg: offset,\n            target_pc: loop_labels.next,\n            decrement_by: 1,\n        });\n    }\n\n    let cols_len = unsafe { &*table_reference }.columns().len();\n    let (columns_start_reg, rowid_reg): (Option<usize>, usize) = {\n        // Get rowid for RETURNING\n        let rowid_reg = program.alloc_register();\n        program.emit_insn(Insn::RowId {\n            cursor_id: main_table_cursor_id,\n            dest: rowid_reg,\n        });\n        if unsafe { &*table_reference }.virtual_table().is_some() {\n            // VUpdate requires a NULL second argument (\"new rowid\") for deletion\n            let new_rowid_reg = program.alloc_register();\n            program.emit_insn(Insn::Null {\n                dest: new_rowid_reg,\n                dest_end: None,\n            });\n        }\n\n        if !has_returning && !has_delete_triggers {\n            (None, rowid_reg)\n        } else {\n            // Allocate registers for column values\n            let columns_start_reg = program.alloc_registers(cols_len);\n\n            // Read all column values from the row to be deleted\n            for (i, _column) in unsafe { &*table_reference }.columns().iter().enumerate() {\n                program.emit_column_or_rowid(main_table_cursor_id, i, columns_start_reg + i);\n            }\n\n            (Some(columns_start_reg), rowid_reg)\n        }\n    };\n\n    // Get the index that is being used to iterate the deletion loop, if there is one.\n    let iteration_index = unsafe { &*table_reference }.op.index();\n\n    // Capture iteration index key values BEFORE deleting the main table row,\n    // since the main table cursor will be invalidated after deletion.\n    let iteration_idx_delete_ctx = if let Some(index) = iteration_index {\n        let iteration_index_cursor =\n            program.resolve_cursor_id(&CursorKey::index(internal_id, index.clone()));\n        let num_regs = index.columns.len() + 1;\n        let start_reg = program.alloc_registers(num_regs);\n        for (reg_offset, column_index) in index.columns.iter().enumerate() {\n            emit_index_column_value_old_image(\n                program,\n                &t_ctx.resolver,\n                table_references,\n                main_table_cursor_id,\n                column_index,\n                start_reg + reg_offset,\n            )?;\n        }\n        program.emit_insn(Insn::RowId {\n            cursor_id: main_table_cursor_id,\n            dest: start_reg + num_regs - 1,\n        });\n        Some((iteration_index_cursor, start_reg, num_regs, index))\n    } else {\n        None\n    };\n\n    emit_delete_row_common(\n        connection,\n        program,\n        t_ctx,\n        table_references,\n        non_from_clause_subqueries,\n        result_columns,\n        table_reference,\n        rowid_reg,\n        columns_start_reg,\n        main_table_cursor_id,\n        iteration_index,\n        Some(cursor_id), // Use the cursor_id from the operation for virtual tables\n        resolver,\n        returning_buffer,\n    )?;\n\n    // Delete from the iteration index after deleting from the main table,\n    // using the key values captured above.\n    if let Some((iteration_index_cursor, start_reg, num_regs, index)) = iteration_idx_delete_ctx {\n        program.emit_insn(Insn::IdxDelete {\n            start_reg,\n            num_regs,\n            cursor_id: iteration_index_cursor,\n            raise_error_if_no_matching_entry: index.where_clause.is_none(),\n        });\n    }\n    if let Some(limit_ctx) = t_ctx.limit_ctx {\n        program.emit_insn(Insn::DecrJumpZero {\n            reg: limit_ctx.reg_limit,\n            target_pc: t_ctx.label_main_loop_end.unwrap(),\n        })\n    }\n\n    Ok(())\n}\n\n/// Common deletion logic shared between normal DELETE and RowSet-based DELETE.\n///\n/// Parameters:\n/// - `rowid_reg`: Register containing the rowid of the row to delete\n/// - `columns_start_reg`: Start register containing column values (already read)\n/// - `skip_iteration_index`: If Some(index), skip deleting from this index (used when iterating over an index)\n/// - `virtual_table_cursor_id`: If Some, use this cursor for virtual table deletion\n#[allow(clippy::too_many_arguments)]\nfn emit_delete_row_common(\n    connection: &Arc<Connection>,\n    program: &mut ProgramBuilder,\n    t_ctx: &mut TranslateCtx,\n    table_references: &mut TableReferences,\n    non_from_clause_subqueries: &mut [NonFromClauseSubquery],\n    result_columns: &[ResultSetColumn],\n    table_reference: *const JoinedTable,\n    rowid_reg: usize,\n    columns_start_reg: Option<usize>, // must be provided when there are triggers or RETURNING\n    main_table_cursor_id: usize,\n    skip_iteration_index: Option<&Arc<crate::schema::Index>>,\n    virtual_table_cursor_id: Option<usize>,\n    resolver: &Resolver,\n    returning_buffer: Option<&ReturningBufferCtx>,\n) -> Result<()> {\n    let internal_id = unsafe { (*table_reference).internal_id };\n    let table_name = unsafe { &*table_reference }.table.get_name();\n\n    // Phase 1: Before Delete - build parent key registers and handle NoAction/Restrict.\n    // CASCADE/SetNull/SetDefault actions are prepared but deferred until after Delete.\n    let prepared_fk_actions = if connection.foreign_keys_enabled() {\n        let delete_db_id = unsafe { (*table_reference).database_id };\n        if let Some(table) = unsafe { &*table_reference }.btree() {\n            let prepared = if t_ctx\n                .resolver\n                .with_schema(delete_db_id, |s| s.any_resolved_fks_referencing(table_name))\n            {\n                ForeignKeyActions::prepare_fk_delete_actions(\n                    program,\n                    &mut t_ctx.resolver,\n                    table_name,\n                    main_table_cursor_id,\n                    rowid_reg,\n                    delete_db_id,\n                )?\n            } else {\n                ForeignKeyActions::default()\n            };\n            if t_ctx\n                .resolver\n                .with_schema(delete_db_id, |s| s.has_child_fks(table_name))\n            {\n                emit_fk_child_decrement_on_delete(\n                    program,\n                    &table,\n                    table_name,\n                    main_table_cursor_id,\n                    rowid_reg,\n                    delete_db_id,\n                    &t_ctx.resolver,\n                )?;\n            }\n            prepared\n        } else {\n            ForeignKeyActions::default()\n        }\n    } else {\n        ForeignKeyActions::default()\n    };\n\n    if unsafe { &*table_reference }.virtual_table().is_some() {\n        let conflict_action = 0u16;\n        let cursor_id = virtual_table_cursor_id.unwrap_or(main_table_cursor_id);\n\n        program.emit_insn(Insn::VUpdate {\n            cursor_id,\n            arg_count: 2,\n            start_reg: rowid_reg,\n            conflict_action,\n        });\n    } else {\n        // Delete from all indexes before deleting from the main table.\n        let db_id = unsafe { (*table_reference).database_id };\n        let all_indices: Vec<_> = t_ctx\n            .resolver\n            .with_schema(db_id, |s| s.get_indices(table_name).cloned().collect());\n\n        // Get indexes to delete from (skip the iteration index if specified)\n        let indexes_to_delete = all_indices\n            .iter()\n            .filter(|index| {\n                skip_iteration_index\n                    .as_ref()\n                    .is_none_or(|skip_idx| !Arc::ptr_eq(skip_idx, index))\n            })\n            .map(|index| {\n                (\n                    index.clone(),\n                    program.resolve_cursor_id(&CursorKey::index(internal_id, index.clone())),\n                )\n            })\n            .collect::<Vec<_>>();\n\n        for (index, index_cursor_id) in indexes_to_delete {\n            let skip_delete_label = if index.where_clause.is_some() {\n                let where_copy = index\n                    .bind_where_expr(Some(table_references), resolver)\n                    .expect(\"where clause to exist\");\n                let skip_label = program.allocate_label();\n                let reg = program.alloc_register();\n                translate_expr_no_constant_opt(\n                    program,\n                    Some(table_references),\n                    &where_copy,\n                    reg,\n                    &t_ctx.resolver,\n                    NoConstantOptReason::RegisterReuse,\n                )?;\n                program.emit_insn(Insn::IfNot {\n                    reg,\n                    jump_if_null: true,\n                    target_pc: skip_label,\n                });\n                Some(skip_label)\n            } else {\n                None\n            };\n            let num_regs = index.columns.len() + 1;\n            let start_reg = program.alloc_registers(num_regs);\n            for (reg_offset, column_index) in index.columns.iter().enumerate() {\n                emit_index_column_value_old_image(\n                    program,\n                    &t_ctx.resolver,\n                    table_references,\n                    main_table_cursor_id,\n                    column_index,\n                    start_reg + reg_offset,\n                )?;\n            }\n            program.emit_insn(Insn::RowId {\n                cursor_id: main_table_cursor_id,\n                dest: start_reg + num_regs - 1,\n            });\n            program.emit_insn(Insn::IdxDelete {\n                start_reg,\n                num_regs,\n                cursor_id: index_cursor_id,\n                raise_error_if_no_matching_entry: index.where_clause.is_none(),\n            });\n            if let Some(label) = skip_delete_label {\n                program.resolve_label(label, program.offset());\n            }\n        }\n\n        // Emit update in the CDC table if necessary (before DELETE updated the table)\n        if let Some(cdc_cursor_id) = t_ctx.cdc_cursor_id {\n            let cdc_has_before = program.capture_data_changes_info().has_before();\n            let before_record_reg = if cdc_has_before {\n                let table_reference = unsafe { &*table_reference };\n                Some(emit_cdc_full_record(\n                    program,\n                    table_reference.table.columns(),\n                    main_table_cursor_id,\n                    rowid_reg,\n                    table_reference\n                        .table\n                        .btree()\n                        .is_some_and(|btree| btree.is_strict),\n                ))\n            } else {\n                None\n            };\n            emit_cdc_insns(\n                program,\n                &t_ctx.resolver,\n                OperationMode::DELETE,\n                cdc_cursor_id,\n                rowid_reg,\n                before_record_reg,\n                None,\n                None,\n                table_name,\n            )?;\n        }\n\n        program.emit_insn(Insn::Delete {\n            cursor_id: main_table_cursor_id,\n            table_name: table_name.to_string(),\n            is_part_of_update: false,\n        });\n    }\n\n    // Emit RETURNING after the row is deleted, but against the cached OLD row image.\n    // This matches SQLite: target-table scans inside RETURNING see the post-delete table\n    // state, while direct column references still resolve to the deleted row.\n    if !result_columns.is_empty() {\n        let columns_start_reg = columns_start_reg\n            .expect(\"columns_start_reg must be provided when there are triggers or RETURNING\");\n        let delete_table = unsafe { &*table_reference };\n        let cache_state = seed_returning_row_image_in_cache(\n            program,\n            table_references,\n            columns_start_reg,\n            rowid_reg,\n            &mut t_ctx.resolver,\n        )?;\n        let result: Result<()> = (|| {\n            for subquery in non_from_clause_subqueries\n                .iter_mut()\n                .filter(|s| !s.has_been_evaluated() && s.is_post_write_returning())\n            {\n                let rerun_for_target_scan =\n                    subquery.reads_table(delete_table.database_id, delete_table.table.get_name());\n                let subquery_plan = subquery.consume_plan(EvalAt::Loop(0));\n                emit_non_from_clause_subquery(\n                    program,\n                    &t_ctx.resolver,\n                    *subquery_plan,\n                    &subquery.query_type,\n                    subquery.correlated || rerun_for_target_scan,\n                    true,\n                )?;\n            }\n            Ok(())\n        })();\n        restore_returning_row_image_in_cache(&mut t_ctx.resolver, cache_state);\n        result?;\n        emit_returning_results(\n            program,\n            table_references,\n            result_columns,\n            columns_start_reg,\n            rowid_reg,\n            &mut t_ctx.resolver,\n            returning_buffer,\n        )?;\n    }\n\n    // Phase 2: After Delete - fire CASCADE/SetNull/SetDefault FK actions.\n    // Per SQLite docs, the parent row must be deleted before FK cascade actions fire,\n    // so triggers during cascade see the parent row as already deleted.\n    {\n        let delete_db_id = unsafe { (*table_reference).database_id };\n        prepared_fk_actions.fire_prepared_fk_delete_actions(\n            program,\n            &mut t_ctx.resolver,\n            connection,\n            delete_db_id,\n        )?;\n    }\n\n    Ok(())\n}\n\n#[expect(clippy::too_many_arguments)]\n/// Helper function to delete a row when we've already seeked to it (e.g., from a RowSet).\n/// This is similar to emit_delete_insns but assumes the cursor is already positioned at the row.\nfn emit_delete_insns_when_triggers_present(\n    connection: &Arc<Connection>,\n    program: &mut ProgramBuilder,\n    t_ctx: &mut TranslateCtx,\n    table_references: &mut TableReferences,\n    non_from_clause_subqueries: &mut [NonFromClauseSubquery],\n    result_columns: &[ResultSetColumn],\n    rowid_reg: usize,\n    main_table_cursor_id: usize,\n    resolver: &Resolver,\n    returning_buffer: Option<&ReturningBufferCtx>,\n) -> Result<()> {\n    // Seek to the rowid and delete it\n    let skip_not_found_label = program.allocate_label();\n\n    // Skip if row with rowid pulled from the rowset does not exist in the table.\n    program.emit_insn(Insn::NotExists {\n        cursor: main_table_cursor_id,\n        rowid_reg,\n        target_pc: skip_not_found_label,\n    });\n\n    let table_reference: *const JoinedTable = table_references.joined_tables().first().unwrap();\n    if unsafe { &*table_reference }\n        .virtual_table()\n        .is_some_and(|t| t.readonly())\n    {\n        return Err(crate::LimboError::ReadOnly);\n    }\n    let btree_table = unsafe { &*table_reference }.btree();\n    let database_id = unsafe { (*table_reference).database_id };\n    let has_returning = !result_columns.is_empty();\n    let has_delete_triggers = if let Some(btree_table) = btree_table {\n        t_ctx.resolver.with_schema(database_id, |s| {\n            has_relevant_triggers_type_only(s, TriggerEvent::Delete, None, &btree_table)\n        })\n    } else {\n        false\n    };\n    let cols_len = unsafe { &*table_reference }.columns().len();\n\n    let columns_start_reg = if !has_returning && !has_delete_triggers {\n        None\n    } else {\n        let columns_start_reg = program.alloc_registers(cols_len);\n        for (i, _column) in unsafe { &*table_reference }.columns().iter().enumerate() {\n            program.emit_column_or_rowid(main_table_cursor_id, i, columns_start_reg + i);\n        }\n        Some(columns_start_reg)\n    };\n\n    let cols_len = unsafe { &*table_reference }.columns().len();\n\n    // Fire BEFORE DELETE triggers\n    if let Some(btree_table) = unsafe { &*table_reference }.btree() {\n        let relevant_triggers: Vec<_> = t_ctx.resolver.with_schema(database_id, |s| {\n            get_relevant_triggers_type_and_time(\n                s,\n                TriggerEvent::Delete,\n                TriggerTime::Before,\n                None,\n                &btree_table,\n            )\n            .collect()\n        });\n        if !relevant_triggers.is_empty() {\n            let columns_start_reg = columns_start_reg\n                .expect(\"columns_start_reg must be provided when there are triggers or RETURNING\");\n            let old_registers = (0..cols_len)\n                .map(|i| columns_start_reg + i)\n                .chain(std::iter::once(rowid_reg))\n                .collect::<Vec<_>>();\n            // If the program has a trigger_conflict_override, propagate it to the trigger context.\n            let trigger_ctx = if let Some(override_conflict) = program.trigger_conflict_override {\n                TriggerContext::new_with_override_conflict(\n                    btree_table,\n                    None, // No NEW for DELETE\n                    Some(old_registers),\n                    override_conflict,\n                )\n            } else {\n                TriggerContext::new(\n                    btree_table,\n                    None, // No NEW for DELETE\n                    Some(old_registers),\n                )\n            };\n\n            for trigger in relevant_triggers {\n                fire_trigger(\n                    program,\n                    &mut t_ctx.resolver,\n                    trigger,\n                    &trigger_ctx,\n                    connection,\n                    database_id,\n                    skip_not_found_label,\n                )?;\n            }\n        }\n    }\n\n    // BEFORE DELETE Triggers may have altered the btree so we need to seek again.\n    program.emit_insn(Insn::NotExists {\n        cursor: main_table_cursor_id,\n        rowid_reg,\n        target_pc: skip_not_found_label,\n    });\n\n    emit_delete_row_common(\n        connection,\n        program,\n        t_ctx,\n        table_references,\n        non_from_clause_subqueries,\n        result_columns,\n        table_reference,\n        rowid_reg,\n        columns_start_reg,\n        main_table_cursor_id,\n        None, // Don't skip any indexes when deleting from RowSet\n        None, // Use main_table_cursor_id for virtual tables\n        resolver,\n        returning_buffer,\n    )?;\n\n    // Fire AFTER DELETE triggers\n    if let Some(btree_table) = unsafe { &*table_reference }.btree() {\n        let relevant_triggers: Vec<_> = t_ctx.resolver.with_schema(database_id, |s| {\n            get_relevant_triggers_type_and_time(\n                s,\n                TriggerEvent::Delete,\n                TriggerTime::After,\n                None,\n                &btree_table,\n            )\n            .collect()\n        });\n        if !relevant_triggers.is_empty() {\n            let columns_start_reg = columns_start_reg\n                .expect(\"columns_start_reg must be provided when there are triggers or RETURNING\");\n            let old_registers = (0..cols_len)\n                .map(|i| columns_start_reg + i)\n                .chain(std::iter::once(rowid_reg))\n                .collect::<Vec<_>>();\n            // If the program has a trigger_conflict_override, propagate it to the trigger context.\n            let trigger_ctx_after =\n                if let Some(override_conflict) = program.trigger_conflict_override {\n                    TriggerContext::new_with_override_conflict(\n                        btree_table,\n                        None, // No NEW for DELETE\n                        Some(old_registers),\n                        override_conflict,\n                    )\n                } else {\n                    TriggerContext::new(\n                        btree_table,\n                        None, // No NEW for DELETE\n                        Some(old_registers),\n                    )\n                };\n\n            for trigger in relevant_triggers {\n                fire_trigger(\n                    program,\n                    &mut t_ctx.resolver,\n                    trigger,\n                    &trigger_ctx_after,\n                    connection,\n                    database_id,\n                    skip_not_found_label,\n                )?;\n            }\n        }\n    }\n\n    program.preassign_label_to_next_insn(skip_not_found_label);\n\n    Ok(())\n}\n"
  },
  {
    "path": "core/translate/emitter/mod.rs",
    "content": "use crate::translate::collate::{get_expr_collation_ctx, CollationSeq};\nuse crate::translate::emitter::delete::emit_program_for_delete;\nuse crate::translate::emitter::select::emit_program_for_select;\nuse crate::translate::emitter::update::emit_program_for_update;\nuse crate::translate::main_loop::SemiAntiJoinMetadata;\n// This module contains code for emitting bytecode instructions for SQL query execution.\n// It handles translating high-level SQL operations into low-level bytecode that can be executed by the virtual machine.\n\nuse crate::sync::Arc;\nuse rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};\nuse std::borrow::Cow;\nuse turso_macros::match_ignore_ascii_case;\n\nuse super::expr::translate_expr;\nuse super::group_by::GroupByMetadata;\nuse super::main_loop::{LeftJoinMetadata, LoopLabels};\nuse super::order_by::SortMetadata;\nuse super::plan::{HashJoinType, TableReferences};\nuse crate::error::SQLITE_CONSTRAINT_CHECK;\nuse crate::function::Func;\nuse crate::schema::{BTreeTable, CheckConstraint, Column, IndexColumn, Schema, Table};\nuse crate::translate::compound_select::emit_program_for_compound_select;\nuse crate::translate::expr::{\n    bind_and_rewrite_expr, translate_expr_no_constant_opt, walk_expr, walk_expr_mut,\n    BindingBehavior, NoConstantOptReason, WalkControl,\n};\nuse crate::translate::plan::{JoinedTable, NonFromClauseSubquery, Plan, ResultSetColumn};\nuse crate::translate::planner::TableMask;\nuse crate::translate::planner::ROWID_STRS;\nuse crate::translate::trigger_exec::get_relevant_triggers_type_and_time;\nuse crate::translate::window::WindowMetadata;\nuse crate::util::{\n    check_expr_references_column, exprs_are_equivalent, normalize_ident, parse_numeric_literal,\n};\nuse crate::vdbe::affinity::Affinity;\nuse crate::vdbe::builder::{CursorType, ProgramBuilder};\nuse crate::vdbe::insn::{to_u16, InsertFlags};\nuse crate::vdbe::{insn::Insn, BranchOffset, CursorID};\nuse crate::{bail_parse_error, Database, DatabaseCatalog, LimboError, Result, RwLock, SymbolTable};\nuse crate::{CaptureDataChangesExt, Connection};\nuse tracing::{instrument, Level};\nuse turso_parser::ast::{\n    self, Expr, Literal, ResolveType, SubqueryType, TableInternalId, TriggerTime,\n};\npub(crate) mod delete;\npub(crate) mod select;\npub(crate) mod update;\n\n/// Initialize EXISTS subquery result registers to 0, but only for subqueries that haven't\n/// been evaluated yet (i.e., correlated subqueries that will be evaluated in the loop).\n/// Non-correlated EXISTS subqueries are evaluated before the loop and their result_reg\n/// is already properly initialized and populated by emit_non_from_clause_subquery.\nfn init_exists_result_regs(\n    program: &mut ProgramBuilder,\n    expr: &ast::Expr,\n    non_from_clause_subqueries: &[NonFromClauseSubquery],\n) {\n    let _ = walk_expr(expr, &mut |e| {\n        if let ast::Expr::SubqueryResult {\n            subquery_id,\n            query_type: SubqueryType::Exists { result_reg },\n            ..\n        } = e\n        {\n            // Only initialize if the subquery hasn't been evaluated yet.\n            // Non-correlated EXISTS subqueries are evaluated before the loop and their\n            // result_reg is already set correctly. Initializing them here would overwrite\n            // the correct result with 0.\n            let already_evaluated = non_from_clause_subqueries\n                .iter()\n                .find(|s| s.internal_id == *subquery_id)\n                .is_some_and(|s| s.has_been_evaluated());\n            if !already_evaluated {\n                program.emit_insn(Insn::Integer {\n                    value: 0,\n                    dest: *result_reg,\n                });\n            }\n        }\n        Ok(WalkControl::Continue)\n    });\n}\n\n// Would make more sense to not have RwLock for the attached databases and get all the schemas on prepare,\n// because there could be some data race where at 1 point you check the attached db, it has a table,\n// but after some write it could not be there anymore. However, leaving it as it is to avoid more complicated logic on something that is experimental\n#[derive(Debug, Clone)]\npub struct CachedExprReg<'a> {\n    pub expr: Cow<'a, ast::Expr>,\n    pub reg: usize,\n    pub needs_decode: bool,\n    pub collation: CachedExprCollation,\n}\n\npub type CachedExprCollation = Option<(CollationSeq, bool)>;\npub type CachedExprRegHit = (usize, bool, CachedExprCollation);\n\npub struct Resolver<'a> {\n    schema: &'a Schema,\n    database_schemas: &'a RwLock<HashMap<usize, Arc<Schema>>>,\n    attached_databases: &'a RwLock<DatabaseCatalog>,\n    pub symbol_table: &'a SymbolTable,\n    pub expr_to_reg_cache_enabled: bool,\n    /// Cache entries for previously translated expressions.\n    /// The `needs_custom_type_decode` flag is true for hash-join payload registers\n    /// that contain raw encoded values and need DECODE applied when read.\n    pub expr_to_reg_cache: Vec<CachedExprReg<'a>>,\n    /// Maps register indices to column affinities for expression index evaluation.\n    /// Populated temporarily during UPDATE new-image expression index key computation,\n    /// where column references have been rewritten to Expr::Register and comparison\n    /// operators need the original column affinity. Analogous to SQLite's iSelfTab\n    /// mechanism, but operates as a side-channel since limbo rewrites the AST rather\n    /// than redirecting column reads at codegen time.\n    pub register_affinities: HashMap<usize, Affinity>,\n    pub enable_custom_types: bool,\n    /// When set, we are compiling a trigger subprogram for this database.\n    /// All table references must resolve to this same database; cross-database\n    /// references are forbidden (matching SQLite's behavior).\n    pub(crate) trigger_context: Option<TriggerDatabaseContext>,\n}\n\n/// Context for restricting table resolution during trigger subprogram compilation.\n#[derive(Debug, Clone)]\npub(crate) struct TriggerDatabaseContext {\n    /// The database ID the trigger belongs to.\n    database_id: usize,\n    /// The trigger name (for error messages).\n    trigger_name: String,\n}\n\nimpl<'a> Resolver<'a> {\n    const MAIN_DB: &'static str = \"main\";\n    const TEMP_DB: &'static str = \"temp\";\n\n    pub(crate) fn new(\n        schema: &'a Schema,\n        database_schemas: &'a RwLock<HashMap<usize, Arc<Schema>>>,\n        attached_databases: &'a RwLock<DatabaseCatalog>,\n        symbol_table: &'a SymbolTable,\n        enable_custom_types: bool,\n    ) -> Self {\n        Self {\n            schema,\n            database_schemas,\n            attached_databases,\n            symbol_table,\n            expr_to_reg_cache_enabled: false,\n            expr_to_reg_cache: Vec::new(),\n            register_affinities: HashMap::default(),\n            enable_custom_types,\n            trigger_context: None,\n        }\n    }\n\n    pub fn schema(&self) -> &Schema {\n        self.schema\n    }\n\n    pub fn fork(&self) -> Resolver<'a> {\n        Resolver {\n            schema: self.schema,\n            database_schemas: self.database_schemas,\n            attached_databases: self.attached_databases,\n            symbol_table: self.symbol_table,\n            expr_to_reg_cache_enabled: false,\n            expr_to_reg_cache: Vec::new(),\n            register_affinities: HashMap::default(),\n            enable_custom_types: self.enable_custom_types,\n            trigger_context: self.trigger_context.clone(),\n        }\n    }\n\n    pub fn fork_with_expr_cache(&self) -> Resolver<'a> {\n        Resolver {\n            schema: self.schema,\n            database_schemas: self.database_schemas,\n            attached_databases: self.attached_databases,\n            symbol_table: self.symbol_table,\n            expr_to_reg_cache_enabled: self.expr_to_reg_cache_enabled,\n            expr_to_reg_cache: self.expr_to_reg_cache.clone(),\n            register_affinities: self.register_affinities.clone(),\n            enable_custom_types: self.enable_custom_types,\n            trigger_context: self.trigger_context.clone(),\n        }\n    }\n\n    pub fn require_custom_types(&self, feature: &str) -> crate::Result<()> {\n        if !self.enable_custom_types {\n            crate::bail_parse_error!(\"{} require --experimental-custom-types flag\", feature);\n        }\n        Ok(())\n    }\n\n    /// Set trigger database context to restrict table resolution to the trigger's database.\n    pub(crate) fn set_trigger_context(&mut self, database_id: usize, trigger_name: String) {\n        self.trigger_context = Some(TriggerDatabaseContext {\n            database_id,\n            trigger_name,\n        });\n    }\n\n    pub fn resolve_function(&self, func_name: &str, arg_count: usize) -> Option<Func> {\n        match Func::resolve_function(func_name, arg_count).ok() {\n            Some(func) => Some(func),\n            None => self\n                .symbol_table\n                .resolve_function(func_name, arg_count)\n                .map(Func::External),\n        }\n    }\n\n    pub(crate) fn enable_expr_to_reg_cache(&mut self) {\n        self.expr_to_reg_cache_enabled = true;\n    }\n\n    pub fn cache_expr_reg(\n        &mut self,\n        expr: Cow<'a, ast::Expr>,\n        reg: usize,\n        needs_decode: bool,\n        collation: CachedExprCollation,\n    ) {\n        self.expr_to_reg_cache.push(CachedExprReg {\n            expr,\n            reg,\n            needs_decode,\n            collation,\n        });\n    }\n\n    /// Cache a scalar expression result together with the collation metadata that\n    /// standalone expression translation would have propagated to a parent comparison.\n    pub fn cache_scalar_expr_reg(\n        &mut self,\n        expr: Cow<'a, ast::Expr>,\n        reg: usize,\n        needs_decode: bool,\n        referenced_tables: &TableReferences,\n    ) -> Result<()> {\n        let collation = get_expr_collation_ctx(expr.as_ref(), referenced_tables)?;\n        self.cache_expr_reg(expr, reg, needs_decode, collation);\n        Ok(())\n    }\n\n    /// Returns the register, decode flag, and collation metadata for a previously translated expression.\n    ///\n    /// We scan from newest to oldest so later translations win when equivalent\n    /// expressions are seen multiple times in the same translation pass.\n    /// Returns `(register, needs_custom_type_decode, collation_ctx)`.\n    pub fn resolve_cached_expr_reg(&self, expr: &ast::Expr) -> Option<CachedExprRegHit> {\n        if self.expr_to_reg_cache_enabled {\n            self.expr_to_reg_cache\n                .iter()\n                .rev()\n                .find(|entry| exprs_are_equivalent(expr, &entry.expr))\n                .map(|entry| (entry.reg, entry.needs_decode, entry.collation))\n        } else {\n            None\n        }\n    }\n\n    /// Access schema for a database using a closure pattern to avoid cloning\n    pub(crate) fn with_schema<T>(&self, database_id: usize, f: impl FnOnce(&Schema) -> T) -> T {\n        if database_id == crate::MAIN_DB_ID || database_id == crate::TEMP_DB_ID {\n            f(self.schema)\n        } else {\n            // Attached database: prefer the connection-local copy (which may contain\n            // uncommitted schema changes from this connection's transaction), falling\n            // back to the shared Database schema (last committed state).\n            let schemas = self.database_schemas.read();\n            if let Some(local_schema) = schemas.get(&database_id) {\n                return f(local_schema);\n            }\n            drop(schemas);\n\n            let attached_dbs = self.attached_databases.read();\n            let (db, _pager) = attached_dbs\n                .index_to_data\n                .get(&database_id)\n                .expect(\"Database ID should be valid after resolve_database_id\");\n\n            let schema = db.schema.lock().clone();\n            f(&schema)\n        }\n    }\n\n    /// Resolve database ID from a qualified name\n    pub(crate) fn resolve_database_id(&self, qualified_name: &ast::QualifiedName) -> Result<usize> {\n        use crate::util::normalize_ident;\n\n        // Check if this is a qualified name (database.table) or unqualified\n        let resolved_id = if let Some(db_name) = &qualified_name.db_name {\n            let db_name_normalized = normalize_ident(db_name.as_str());\n            let name_bytes = db_name_normalized.as_bytes();\n            match_ignore_ascii_case!(match name_bytes {\n                b\"main\" => Ok(0),\n                b\"temp\" => Ok(1),\n                _ => {\n                    // Look up attached database\n                    if let Some((idx, _attached_db)) =\n                        self.get_attached_database(&db_name_normalized)\n                    {\n                        Ok(idx)\n                    } else {\n                        Err(LimboError::InvalidArgument(format!(\n                            \"no such database: {db_name_normalized}\"\n                        )))\n                    }\n                }\n            })\n        } else {\n            // Unqualified table name — when compiling a trigger subprogram,\n            // resolve to the trigger's database (matching SQLite behavior).\n            // Otherwise default to main.\n            if let Some(ref ctx) = self.trigger_context {\n                Ok(ctx.database_id)\n            } else {\n                Ok(0)\n            }\n        }?;\n\n        // Triggers can only reference tables in their own database.\n        // This only fires for explicitly qualified names (e.g. \"aux.table\")\n        // since unqualified names already resolve to the trigger's database above.\n        if let Some(ref ctx) = self.trigger_context {\n            if resolved_id != ctx.database_id {\n                let db_name = qualified_name\n                    .db_name\n                    .as_ref()\n                    .map(|n| n.as_str())\n                    .unwrap_or(\"main\");\n                return Err(LimboError::ParseError(format!(\n                    \"trigger {} cannot reference objects in database {}\",\n                    ctx.trigger_name, db_name\n                )));\n            }\n        }\n\n        Ok(resolved_id)\n    }\n\n    // Get an attached database by alias name\n    pub(crate) fn get_attached_database(&self, alias: &str) -> Option<(usize, Arc<Database>)> {\n        self.attached_databases.read().get_database_by_name(alias)\n    }\n\n    /// Get the database name for a given database index.\n    /// Returns \"main\" for index 0, \"temp\" for index 1, and the alias for attached databases.\n    pub(crate) fn get_database_name_by_index(&self, index: usize) -> Option<String> {\n        match index {\n            crate::MAIN_DB_ID => Some(Self::MAIN_DB.to_string()),\n            crate::TEMP_DB_ID => Some(Self::TEMP_DB.to_string()),\n            _ => self.attached_databases.read().get_name_by_index(index),\n        }\n    }\n}\n\n#[derive(Debug, Clone, Copy)]\npub struct LimitCtx {\n    /// Register holding the LIMIT value (e.g. LIMIT 5)\n    pub reg_limit: usize,\n    /// Whether to initialize the LIMIT counter to the LIMIT value;\n    /// There are cases like compound SELECTs where all the sub-selects\n    /// utilize the same limit register, but it is initialized only once.\n    pub initialize_counter: bool,\n}\n\n/// Identifies a value stored in a materialized hash-build input.\n///\n/// These references are used to map payload registers back to the original\n/// table expressions during hash-probe evaluation. They are deliberately\n/// table-qualified so payloads can span multiple tables when the build input\n/// is derived from a join prefix.\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\npub enum MaterializedColumnRef {\n    /// A concrete column from a specific table, including rowid alias metadata.\n    Column {\n        table_id: TableInternalId,\n        column_idx: usize,\n        is_rowid_alias: bool,\n    },\n    /// The implicit rowid (or integer primary key) of a specific table.\n    RowId { table_id: TableInternalId },\n}\n\n/// Describes how a hash-join build input was materialized.\n///\n/// Rowid-only materialization preserves prior join constraints while keeping\n/// the hash table payload small, but requires `SeekRowid` into the build table\n/// during probing. Key+payload materialization stores the join keys and needed\n/// payload columns directly so the hash build can operate without seeking.\n#[derive(Debug, Clone)]\npub enum MaterializedBuildInputMode {\n    /// Ephemeral table contains only build-side rowids.\n    RowidOnly,\n    /// Ephemeral table contains join keys followed by payload columns.\n    KeyPayload {\n        /// Number of join keys stored at the start of each row.\n        num_keys: usize,\n        /// Payload columns (after the keys) in ephemeral-table order.\n        payload_columns: Vec<MaterializedColumnRef>,\n    },\n}\n\n/// Metadata for a materialized build input keyed by build table index.\n///\n/// The cursor refers to the ephemeral table containing the materialized rows.\n/// `prefix_tables` tracks which join-prefix tables were captured so we can\n/// prune redundant scans from downstream join orders.\n#[derive(Debug, Clone)]\npub struct MaterializedBuildInput {\n    /// Cursor id for the ephemeral table holding the materialized rows.\n    pub cursor_id: CursorID,\n    /// Encoding mode for the materialized rows.\n    pub mode: MaterializedBuildInputMode,\n    /// Join-prefix table indices folded into this materialization.\n    pub prefix_tables: Vec<usize>,\n}\n\nimpl LimitCtx {\n    pub fn new(program: &mut ProgramBuilder) -> Self {\n        Self {\n            reg_limit: program.alloc_register(),\n            initialize_counter: true,\n        }\n    }\n\n    pub fn new_shared(reg_limit: usize) -> Self {\n        Self {\n            reg_limit,\n            initialize_counter: false,\n        }\n    }\n}\n\n#[derive(Clone, Debug)]\npub(crate) struct HashLabels {\n    /// Label for hash join match processing (points to just after HashProbe instruction)\n    /// Used by HashNext to jump back to process additional matches without re-probing\n    pub match_found: BranchOffset,\n    /// Label for advancing to the next hash match (points to HashNext instruction).\n    /// When conditions fail within a hash join, they should jump here to try the next\n    /// hash match, rather than jumping to the outer loop's next label.\n    pub next: BranchOffset,\n    /// Jump target for unmatched probe rows (outer joins only).\n    pub check_outer: Option<BranchOffset>,\n    /// Entry label for the inner-loop subroutine.\n    pub inner_loop_gosub: Option<BranchOffset>,\n    /// Label that skips past the subroutine body (resolved after Return).\n    pub inner_loop_skip: Option<BranchOffset>,\n    /// Label for the grace loop's own HashNext (resolved during grace loop emission).\n    pub grace_hash_next: Option<BranchOffset>,\n}\n\nimpl HashLabels {\n    pub fn new(match_found: BranchOffset, next: BranchOffset) -> Self {\n        Self {\n            match_found,\n            next,\n            check_outer: None,\n            inner_loop_gosub: None,\n            inner_loop_skip: None,\n            grace_hash_next: None,\n        }\n    }\n}\n\n#[derive(Debug, Clone)]\npub struct HashCtx {\n    pub match_reg: usize,\n    pub hash_table_reg: usize,\n    pub labels: HashLabels,\n    /// Starting register where payload columns are stored after HashProbe/HashNext.\n    /// None if payload optimization is not used for this hash join.\n    pub payload_start_reg: Option<usize>,\n    /// Column references stored in payload, in order.\n    /// `payload_start_reg + i` contains the value for `payload_columns[i]`.\n    /// These references may point at multiple tables when a build input was\n    /// materialized from a join prefix.\n    pub payload_columns: Vec<MaterializedColumnRef>,\n    /// Build table cursor (for NullRow in outer joins).\n    pub build_cursor_id: Option<CursorID>,\n    pub join_type: HashJoinType,\n    /// Gosub register for the inner-loop subroutine wrapping subsequent tables.\n    /// Outer hash joins wrap inner loops so unmatched-row paths can re-enter via Gosub.\n    pub inner_loop_gosub_reg: Option<usize>,\n    /// Probe-side rowid register for grace hash join (from RowId before HashProbe).\n    pub probe_rowid_reg: Option<usize>,\n    /// Starting register for probe key values.\n    pub key_start_reg: usize,\n    /// Number of join keys.\n    pub num_keys: usize,\n    /// Register: 0 during main probe loop, 1 during grace loop.\n    /// Used by IfPos dispatch before HashNext to route to the grace loop's HashNext.\n    pub grace_flag_reg: Option<usize>,\n}\n\n/// The TranslateCtx struct holds various information and labels used during bytecode generation.\n/// It is used for maintaining state and control flow during the bytecode\n/// generation process.\npub struct TranslateCtx<'a> {\n    // A typical query plan is a nested loop. Each loop has its own LoopLabels (see the definition of LoopLabels for more details)\n    pub labels_main_loop: Vec<LoopLabels>,\n    // label for the instruction that jumps to the next phase of the query after the main loop\n    // we don't know ahead of time what that is (GROUP BY, ORDER BY, etc.)\n    pub label_main_loop_end: Option<BranchOffset>,\n    // First register of the aggregation results\n    pub reg_agg_start: Option<usize>,\n    // In non-group-by statements with aggregations (e.g. SELECT foo, bar, sum(baz) FROM t),\n    // we want to emit the non-aggregate columns (foo and bar) only once.\n    // This register is a flag that tracks whether we have already done that.\n    pub reg_nonagg_emit_once_flag: Option<usize>,\n    // First register of the result columns of the query\n    pub reg_result_cols_start: Option<usize>,\n    pub limit_ctx: Option<LimitCtx>,\n    // The register holding the offset value, if any.\n    pub reg_offset: Option<usize>,\n    // The register holding the limit+offset value, if any.\n    pub reg_limit_offset_sum: Option<usize>,\n    // metadata for the group by operator\n    pub meta_group_by: Option<GroupByMetadata>,\n    // metadata for the order by operator\n    pub meta_sort: Option<SortMetadata>,\n    /// mapping between table loop index and associated metadata (for left joins only)\n    /// this metadata exists for the right table in a given left join\n    pub meta_left_joins: Vec<Option<LeftJoinMetadata>>,\n    /// mapping between table loop index and associated metadata (for semi/anti joins)\n    pub meta_semi_anti_joins: Vec<Option<SemiAntiJoinMetadata>>,\n    pub resolver: Resolver<'a>,\n    /// Hash table contexts for hash joins, keyed by build table index.\n    pub hash_table_contexts: HashMap<usize, HashCtx>,\n    /// Materialized build inputs for hash joins, keyed by build table index.\n    /// These entries are reused during nested materialization so we avoid\n    /// re-scanning prefix tables and preserve prior join constraints.\n    pub materialized_build_inputs: HashMap<usize, MaterializedBuildInput>,\n    /// A list of expressions that are not aggregates, along with a flag indicating\n    /// whether the expression should be included in the output for each group.\n    ///\n    /// Each entry is a tuple:\n    /// - `&'ast Expr`: the expression itself\n    /// - `bool`: `true` if the expression should be included in the output for each group, `false` otherwise.\n    ///\n    /// The order of expressions is **significant**:\n    /// - First: all `GROUP BY` expressions, in the order they appear in the `GROUP BY` clause.\n    /// - Then: remaining non-aggregate expressions that are not part of `GROUP BY`.\n    pub non_aggregate_expressions: Vec<(&'a Expr, bool)>,\n    /// Unique leaf column expressions extracted from aggregate function arguments.\n    /// Only populated when GROUP BY uses a sorter, enabling deferred expression\n    /// evaluation: the sorter stores raw columns instead of pre-computed expressions,\n    /// and full expressions are re-evaluated from the pseudo cursor during aggregation.\n    pub agg_leaf_columns: Vec<Expr>,\n    /// Cursor id for cdc table (if capture_data_changes PRAGMA is set and query can modify the data)\n    pub cdc_cursor_id: Option<usize>,\n    pub meta_window: Option<WindowMetadata<'a>>,\n    /// Metadata stored during `open_loop` for `Search::InSeek`, consumed by `close_loop`.\n    pub meta_in_seeks: Vec<Option<InSeekMetadata>>,\n    pub unsafe_testing: bool,\n}\n\n/// Metadata for the two-level loop emitted by `Search::InSeek`.\n#[derive(Debug)]\npub struct InSeekMetadata {\n    pub ephemeral_cursor_id: CursorID,\n    pub outer_loop_start: BranchOffset,\n    pub next_val_label: BranchOffset,\n}\n\nimpl<'a> TranslateCtx<'a> {\n    pub fn new(\n        program: &mut ProgramBuilder,\n        resolver: Resolver<'a>,\n        table_count: usize,\n        unsafe_testing: bool,\n    ) -> Self {\n        TranslateCtx {\n            labels_main_loop: (0..table_count).map(|_| LoopLabels::new(program)).collect(),\n            label_main_loop_end: None,\n            reg_agg_start: None,\n            reg_nonagg_emit_once_flag: None,\n            limit_ctx: None,\n            reg_offset: None,\n            reg_limit_offset_sum: None,\n            reg_result_cols_start: None,\n            meta_group_by: None,\n            meta_left_joins: (0..table_count).map(|_| None).collect(),\n            meta_semi_anti_joins: (0..table_count).map(|_| None).collect(),\n            meta_sort: None,\n            hash_table_contexts: HashMap::default(),\n            materialized_build_inputs: HashMap::default(),\n            resolver,\n            non_aggregate_expressions: Vec::new(),\n            agg_leaf_columns: Vec::new(),\n            cdc_cursor_id: None,\n            meta_window: None,\n            meta_in_seeks: (0..table_count).map(|_| None).collect(),\n            unsafe_testing,\n        }\n    }\n}\n\n#[derive(Debug, Clone)]\n/// Update row source for UPDATE statements\n/// `Normal` is the default mode, it will iterate either the table itself or an index on the table.\n/// `PrebuiltEphemeralTable` is used when an ephemeral table containing the target rowids to update has\n/// been built and it is being used for iteration.\npub enum UpdateRowSource {\n    /// Iterate over the table itself or an index on the table\n    Normal,\n    /// Iterate over an ephemeral table containing the target rowids to update\n    PrebuiltEphemeralTable {\n        /// The cursor id of the ephemeral table that is being used to iterate the target rowids to update.\n        ephemeral_table_cursor_id: usize,\n        /// The table that is being updated.\n        target_table: Arc<JoinedTable>,\n    },\n}\n\n/// Used to distinguish database operations\n#[allow(clippy::upper_case_acronyms, dead_code)]\n#[derive(Debug, Clone)]\npub enum OperationMode {\n    SELECT,\n    INSERT,\n    UPDATE(UpdateRowSource),\n    DELETE,\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\n/// Sqlite always considers Read transactions implicit\npub enum TransactionMode {\n    None,\n    Read,\n    Write,\n    Concurrent,\n}\n\n/// Main entry point for emitting bytecode for a SQL query\n/// Takes a query plan and generates the corresponding bytecode program\n#[instrument(skip_all, level = Level::DEBUG)]\npub fn emit_program(\n    connection: &Arc<Connection>,\n    resolver: &Resolver,\n    program: &mut ProgramBuilder,\n    plan: Plan,\n    after: impl FnOnce(&mut ProgramBuilder),\n) -> Result<()> {\n    match plan {\n        Plan::Select(plan) => emit_program_for_select(program, resolver, plan),\n        Plan::Delete(plan) => emit_program_for_delete(connection, resolver, program, plan),\n        Plan::Update(plan) => emit_program_for_update(connection, resolver, program, plan, after),\n        Plan::CompoundSelect { .. } => {\n            emit_program_for_compound_select(program, resolver, plan).map(|_| ())\n        }\n    }\n}\n\n/// Returns the single-column schema used by rowid-only hash build inputs.\nfn build_rowid_column() -> Column {\n    Column::new_default_integer(Some(\"build_rowid\".to_string()), \"INTEGER\".to_string(), None)\n}\n\npub fn prepare_cdc_if_necessary(\n    program: &mut ProgramBuilder,\n    schema: &Schema,\n    changed_table_name: &str,\n) -> Result<Option<(usize, Arc<BTreeTable>)>> {\n    let mode = program.capture_data_changes_info();\n    let cdc_table = mode.table();\n    let Some(cdc_table) = cdc_table else {\n        return Ok(None);\n    };\n    if changed_table_name == cdc_table\n        || changed_table_name == crate::translate::pragma::TURSO_CDC_VERSION_TABLE_NAME\n    {\n        return Ok(None);\n    }\n    let Some(turso_cdc_table) = schema.get_table(cdc_table) else {\n        crate::bail_parse_error!(\"no such table: {}\", cdc_table);\n    };\n    let Some(cdc_btree) = turso_cdc_table.btree() else {\n        crate::bail_parse_error!(\"no such table: {}\", cdc_table);\n    };\n    let cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(cdc_btree.clone()));\n    program.emit_insn(Insn::OpenWrite {\n        cursor_id,\n        root_page: cdc_btree.root_page.into(),\n        db: crate::MAIN_DB_ID, // CDC table always lives in the main database\n    });\n    Ok(Some((cursor_id, cdc_btree)))\n}\n\npub fn emit_cdc_patch_record(\n    program: &mut ProgramBuilder,\n    table: &Table,\n    columns_reg: usize,\n    record_reg: usize,\n    rowid_reg: usize,\n) -> usize {\n    let columns = table.columns();\n    let rowid_alias_position = columns.iter().position(|x| x.is_rowid_alias());\n    if let Some(rowid_alias_position) = rowid_alias_position {\n        let record_reg = program.alloc_register();\n        program.emit_insn(Insn::Copy {\n            src_reg: rowid_reg,\n            dst_reg: columns_reg + rowid_alias_position,\n            extra_amount: 0,\n        });\n        let is_strict = table.btree().is_some_and(|btree| btree.is_strict);\n        let affinity_str = table\n            .columns()\n            .iter()\n            .map(|col| col.affinity_with_strict(is_strict).aff_mask())\n            .collect::<String>();\n\n        program.emit_insn(Insn::MakeRecord {\n            start_reg: to_u16(columns_reg),\n            count: to_u16(table.columns().len()),\n            dest_reg: to_u16(record_reg),\n            index_name: None,\n            affinity_str: Some(affinity_str),\n        });\n        record_reg\n    } else {\n        record_reg\n    }\n}\n\npub fn emit_cdc_full_record(\n    program: &mut ProgramBuilder,\n    columns: &[Column],\n    table_cursor_id: usize,\n    rowid_reg: usize,\n    is_strict: bool,\n) -> usize {\n    let columns_reg = program.alloc_registers(columns.len() + 1);\n    for (i, column) in columns.iter().enumerate() {\n        if column.is_rowid_alias() {\n            program.emit_insn(Insn::Copy {\n                src_reg: rowid_reg,\n                dst_reg: columns_reg + 1 + i,\n                extra_amount: 0,\n            });\n        } else {\n            program.emit_column_or_rowid(table_cursor_id, i, columns_reg + 1 + i);\n        }\n    }\n    let affinity_str = columns\n        .iter()\n        .map(|col| col.affinity_with_strict(is_strict).aff_mask())\n        .collect::<String>();\n\n    program.emit_insn(Insn::MakeRecord {\n        start_reg: to_u16(columns_reg + 1),\n        count: to_u16(columns.len()),\n        dest_reg: to_u16(columns_reg),\n        index_name: None,\n        affinity_str: Some(affinity_str),\n    });\n    columns_reg\n}\n\n#[allow(clippy::too_many_arguments)]\npub fn emit_cdc_insns(\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    operation_mode: OperationMode,\n    cdc_cursor_id: usize,\n    rowid_reg: usize,\n    before_record_reg: Option<usize>,\n    after_record_reg: Option<usize>,\n    updates_record_reg: Option<usize>,\n    table_name: &str,\n) -> Result<()> {\n    let cdc_info = program.capture_data_changes_info().as_ref();\n    match cdc_info.map(|info| info.cdc_version()) {\n        Some(crate::CdcVersion::V2) => emit_cdc_insns_v2(\n            program,\n            resolver,\n            operation_mode,\n            cdc_cursor_id,\n            rowid_reg,\n            before_record_reg,\n            after_record_reg,\n            updates_record_reg,\n            table_name,\n        ),\n        Some(crate::CdcVersion::V1) => emit_cdc_insns_v1(\n            program,\n            resolver,\n            operation_mode,\n            cdc_cursor_id,\n            rowid_reg,\n            before_record_reg,\n            after_record_reg,\n            updates_record_reg,\n            table_name,\n        ),\n        None => Err(crate::LimboError::InternalError(\n            \"cdc info not set\".to_string(),\n        )),\n    }\n}\n\n#[allow(clippy::too_many_arguments)]\nfn emit_cdc_insns_v1(\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    operation_mode: OperationMode,\n    cdc_cursor_id: usize,\n    rowid_reg: usize,\n    before_record_reg: Option<usize>,\n    after_record_reg: Option<usize>,\n    updates_record_reg: Option<usize>,\n    table_name: &str,\n) -> Result<()> {\n    // v1: (change_id, change_time, change_type, table_name, id, before, after, updates)\n    let turso_cdc_registers = program.alloc_registers(8);\n    program.emit_insn(Insn::Null {\n        dest: turso_cdc_registers,\n        dest_end: None,\n    });\n    program.mark_last_insn_constant();\n\n    let Some(unixepoch_fn) = resolver.resolve_function(\"unixepoch\", 0) else {\n        bail_parse_error!(\"no function {}\", \"unixepoch\");\n    };\n    let unixepoch_fn_ctx = crate::function::FuncCtx {\n        func: unixepoch_fn,\n        arg_count: 0,\n    };\n\n    program.emit_insn(Insn::Function {\n        constant_mask: 0,\n        start_reg: 0,\n        dest: turso_cdc_registers + 1,\n        func: unixepoch_fn_ctx,\n    });\n\n    let change_type = match operation_mode {\n        OperationMode::INSERT => 1,\n        OperationMode::UPDATE { .. } | OperationMode::SELECT => 0,\n        OperationMode::DELETE => -1,\n    };\n    program.emit_int(change_type, turso_cdc_registers + 2);\n    program.mark_last_insn_constant();\n\n    program.emit_string8(table_name.to_string(), turso_cdc_registers + 3);\n    program.mark_last_insn_constant();\n\n    program.emit_insn(Insn::Copy {\n        src_reg: rowid_reg,\n        dst_reg: turso_cdc_registers + 4,\n        extra_amount: 0,\n    });\n\n    if let Some(before_record_reg) = before_record_reg {\n        program.emit_insn(Insn::Copy {\n            src_reg: before_record_reg,\n            dst_reg: turso_cdc_registers + 5,\n            extra_amount: 0,\n        });\n    } else {\n        program.emit_null(turso_cdc_registers + 5, None);\n        program.mark_last_insn_constant();\n    }\n\n    if let Some(after_record_reg) = after_record_reg {\n        program.emit_insn(Insn::Copy {\n            src_reg: after_record_reg,\n            dst_reg: turso_cdc_registers + 6,\n            extra_amount: 0,\n        });\n    } else {\n        program.emit_null(turso_cdc_registers + 6, None);\n        program.mark_last_insn_constant();\n    }\n\n    if let Some(updates_record_reg) = updates_record_reg {\n        program.emit_insn(Insn::Copy {\n            src_reg: updates_record_reg,\n            dst_reg: turso_cdc_registers + 7,\n            extra_amount: 0,\n        });\n    } else {\n        program.emit_null(turso_cdc_registers + 7, None);\n        program.mark_last_insn_constant();\n    }\n\n    let rowid_reg = program.alloc_register();\n    program.emit_insn(Insn::NewRowid {\n        cursor: cdc_cursor_id,\n        rowid_reg,\n        prev_largest_reg: 0, // todo(sivukhin): properly set value here from sqlite_sequence table when AUTOINCREMENT will be properly implemented in Turso\n    });\n\n    let record_reg = program.alloc_register();\n    program.emit_insn(Insn::MakeRecord {\n        start_reg: to_u16(turso_cdc_registers),\n        count: to_u16(8),\n        dest_reg: to_u16(record_reg),\n        index_name: None,\n        affinity_str: None,\n    });\n\n    program.emit_insn(Insn::Insert {\n        cursor: cdc_cursor_id,\n        key_reg: rowid_reg,\n        record_reg,\n        flag: InsertFlags::new(),\n        table_name: \"\".to_string(),\n    });\n    Ok(())\n}\n\n#[allow(clippy::too_many_arguments)]\nfn emit_cdc_insns_v2(\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    operation_mode: OperationMode,\n    cdc_cursor_id: usize,\n    rowid_reg: usize,\n    before_record_reg: Option<usize>,\n    after_record_reg: Option<usize>,\n    updates_record_reg: Option<usize>,\n    table_name: &str,\n) -> Result<()> {\n    // v2: (change_id, change_time, change_txn_id, change_type, table_name, id, before, after, updates)\n    let turso_cdc_registers = program.alloc_registers(9);\n    program.emit_insn(Insn::Null {\n        dest: turso_cdc_registers,\n        dest_end: None,\n    });\n    program.mark_last_insn_constant();\n\n    // change_time = unixepoch()\n    let Some(unixepoch_fn) = resolver.resolve_function(\"unixepoch\", 0) else {\n        bail_parse_error!(\"no function {}\", \"unixepoch\");\n    };\n    let unixepoch_fn_ctx = crate::function::FuncCtx {\n        func: unixepoch_fn,\n        arg_count: 0,\n    };\n    program.emit_insn(Insn::Function {\n        constant_mask: 0,\n        start_reg: 0,\n        dest: turso_cdc_registers + 1,\n        func: unixepoch_fn_ctx,\n    });\n\n    // change_txn_id = conn_txn_id(new_rowid)\n    // First generate a candidate rowid, then pass it to conn_txn_id for get-or-set.\n    let candidate_reg = program.alloc_register();\n    program.emit_insn(Insn::NewRowid {\n        cursor: cdc_cursor_id,\n        rowid_reg: candidate_reg,\n        prev_largest_reg: 0,\n    });\n    let Some(conn_txn_id_fn) = resolver.resolve_function(\"conn_txn_id\", 1) else {\n        bail_parse_error!(\"no function {}\", \"conn_txn_id\");\n    };\n    let conn_txn_id_fn_ctx = crate::function::FuncCtx {\n        func: conn_txn_id_fn,\n        arg_count: 1,\n    };\n    program.emit_insn(Insn::Function {\n        constant_mask: 0,\n        start_reg: candidate_reg,\n        dest: turso_cdc_registers + 2,\n        func: conn_txn_id_fn_ctx,\n    });\n\n    // change_type\n    let change_type = match operation_mode {\n        OperationMode::INSERT => 1,\n        OperationMode::UPDATE { .. } | OperationMode::SELECT => 0,\n        OperationMode::DELETE => -1,\n    };\n    program.emit_int(change_type, turso_cdc_registers + 3);\n    program.mark_last_insn_constant();\n\n    // table_name\n    program.emit_string8(table_name.to_string(), turso_cdc_registers + 4);\n    program.mark_last_insn_constant();\n\n    // id\n    program.emit_insn(Insn::Copy {\n        src_reg: rowid_reg,\n        dst_reg: turso_cdc_registers + 5,\n        extra_amount: 0,\n    });\n\n    // before\n    if let Some(before_record_reg) = before_record_reg {\n        program.emit_insn(Insn::Copy {\n            src_reg: before_record_reg,\n            dst_reg: turso_cdc_registers + 6,\n            extra_amount: 0,\n        });\n    } else {\n        program.emit_null(turso_cdc_registers + 6, None);\n        program.mark_last_insn_constant();\n    }\n\n    // after\n    if let Some(after_record_reg) = after_record_reg {\n        program.emit_insn(Insn::Copy {\n            src_reg: after_record_reg,\n            dst_reg: turso_cdc_registers + 7,\n            extra_amount: 0,\n        });\n    } else {\n        program.emit_null(turso_cdc_registers + 7, None);\n        program.mark_last_insn_constant();\n    }\n\n    // updates\n    if let Some(updates_record_reg) = updates_record_reg {\n        program.emit_insn(Insn::Copy {\n            src_reg: updates_record_reg,\n            dst_reg: turso_cdc_registers + 8,\n            extra_amount: 0,\n        });\n    } else {\n        program.emit_null(turso_cdc_registers + 8, None);\n        program.mark_last_insn_constant();\n    }\n\n    let rowid_reg = program.alloc_register();\n    program.emit_insn(Insn::NewRowid {\n        cursor: cdc_cursor_id,\n        rowid_reg,\n        prev_largest_reg: 0,\n    });\n\n    let record_reg = program.alloc_register();\n    program.emit_insn(Insn::MakeRecord {\n        start_reg: to_u16(turso_cdc_registers),\n        count: to_u16(9),\n        dest_reg: to_u16(record_reg),\n        index_name: None,\n        affinity_str: None,\n    });\n\n    program.emit_insn(Insn::Insert {\n        cursor: cdc_cursor_id,\n        key_reg: rowid_reg,\n        record_reg,\n        flag: InsertFlags::new(),\n        table_name: \"\".to_string(),\n    });\n    Ok(())\n}\n\n/// Emit a COMMIT record into the CDC table (v2 only).\n/// change_type=2, all other data fields NULL.\npub fn emit_cdc_commit_insns(\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    cdc_cursor_id: usize,\n) -> Result<()> {\n    // v2 COMMIT record: (NULL, unixepoch(), conn_txn_id(-1), 2, NULL, NULL, NULL, NULL, NULL)\n    let regs = program.alloc_registers(9);\n    // reg+0: NULL (change_id, autoincrement)\n    program.emit_insn(Insn::Null {\n        dest: regs,\n        dest_end: None,\n    });\n    program.mark_last_insn_constant();\n\n    // reg+1: change_time = unixepoch()\n    let Some(unixepoch_fn) = resolver.resolve_function(\"unixepoch\", 0) else {\n        bail_parse_error!(\"no function {}\", \"unixepoch\");\n    };\n    let unixepoch_fn_ctx = crate::function::FuncCtx {\n        func: unixepoch_fn,\n        arg_count: 0,\n    };\n    program.emit_insn(Insn::Function {\n        constant_mask: 0,\n        start_reg: 0,\n        dest: regs + 1,\n        func: unixepoch_fn_ctx,\n    });\n\n    // reg+2: change_txn_id = conn_txn_id(-1)\n    // Pass -1 as candidate: if a txn_id exists, return it; if not, -1 is stored (and will be reset).\n    let minus_one_reg = program.alloc_register();\n    program.emit_int(-1, minus_one_reg);\n    let Some(conn_txn_id_fn) = resolver.resolve_function(\"conn_txn_id\", 1) else {\n        bail_parse_error!(\"no function {}\", \"conn_txn_id\");\n    };\n    let conn_txn_id_fn_ctx = crate::function::FuncCtx {\n        func: conn_txn_id_fn,\n        arg_count: 1,\n    };\n    program.emit_insn(Insn::Function {\n        constant_mask: 0,\n        start_reg: minus_one_reg,\n        dest: regs + 2,\n        func: conn_txn_id_fn_ctx,\n    });\n\n    // reg+3: change_type = 2 (COMMIT)\n    program.emit_int(2, regs + 3);\n    program.mark_last_insn_constant();\n\n    // reg+4..8: NULL (table_name, id, before, after, updates)\n    program.emit_insn(Insn::Null {\n        dest: regs + 4,\n        dest_end: Some(regs + 8),\n    });\n    program.mark_last_insn_constant();\n\n    let rowid_reg = program.alloc_register();\n    program.emit_insn(Insn::NewRowid {\n        cursor: cdc_cursor_id,\n        rowid_reg,\n        prev_largest_reg: 0,\n    });\n\n    let record_reg = program.alloc_register();\n    program.emit_insn(Insn::MakeRecord {\n        start_reg: to_u16(regs),\n        count: to_u16(9),\n        dest_reg: to_u16(record_reg),\n        index_name: None,\n        affinity_str: None,\n    });\n\n    program.emit_insn(Insn::Insert {\n        cursor: cdc_cursor_id,\n        key_reg: rowid_reg,\n        record_reg,\n        flag: InsertFlags::new(),\n        table_name: \"\".to_string(),\n    });\n    Ok(())\n}\n\n/// Emit a CDC COMMIT record at end-of-statement when in autocommit mode (v2 only).\n/// This should be called once per statement, after the main loop, not per-row.\npub fn emit_cdc_autocommit_commit(\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    cdc_cursor_id: usize,\n) -> Result<()> {\n    let cdc_info = program.capture_data_changes_info().as_ref();\n    if cdc_info.is_some_and(|info| info.cdc_version().has_commit_record()) {\n        // Check if we're in autocommit mode; if so, emit a COMMIT record.\n        let Some(is_autocommit_fn) = resolver.resolve_function(\"is_autocommit\", 0) else {\n            bail_parse_error!(\"no function {}\", \"is_autocommit\");\n        };\n        let is_autocommit_fn_ctx = crate::function::FuncCtx {\n            func: is_autocommit_fn,\n            arg_count: 0,\n        };\n        let autocommit_reg = program.alloc_register();\n        program.emit_insn(Insn::Function {\n            constant_mask: 0,\n            start_reg: 0,\n            dest: autocommit_reg,\n            func: is_autocommit_fn_ctx,\n        });\n\n        // IfNot jumps when reg == 0 (not autocommit). Skip the COMMIT in that case.\n        let skip_label = program.allocate_label();\n        program.emit_insn(Insn::IfNot {\n            reg: autocommit_reg,\n            target_pc: skip_label,\n            jump_if_null: true,\n        });\n\n        emit_cdc_commit_insns(program, resolver, cdc_cursor_id)?;\n\n        program.resolve_label(skip_label, program.offset());\n    }\n\n    Ok(())\n}\n/// Initialize the limit/offset counters and registers.\n/// In case of compound SELECTs, the limit counter is initialized only once,\n/// hence [LimitCtx::initialize_counter] being false in those cases.\npub(crate) fn init_limit(\n    program: &mut ProgramBuilder,\n    t_ctx: &mut TranslateCtx,\n    limit: &Option<Box<Expr>>,\n    offset: &Option<Box<Expr>>,\n) -> Result<()> {\n    if t_ctx.limit_ctx.is_none() && limit.is_some() {\n        t_ctx.limit_ctx = Some(LimitCtx::new(program));\n    }\n    let Some(limit_ctx) = &t_ctx.limit_ctx else {\n        return Ok(());\n    };\n\n    if limit_ctx.initialize_counter {\n        if let Some(expr) = limit {\n            match expr.as_ref() {\n                Expr::Literal(Literal::Numeric(n)) => match parse_numeric_literal(n)? {\n                    crate::types::Value::Numeric(crate::Numeric::Integer(value)) => {\n                        program.add_comment(program.offset(), \"LIMIT counter\");\n                        program.emit_insn(Insn::Integer {\n                            value,\n                            dest: limit_ctx.reg_limit,\n                        });\n                    }\n                    crate::types::Value::Numeric(crate::Numeric::Float(value)) => {\n                        program.emit_insn(Insn::Real {\n                            value: value.into(),\n                            dest: limit_ctx.reg_limit,\n                        });\n                        program.add_comment(program.offset(), \"LIMIT counter\");\n                        program.emit_insn(Insn::MustBeInt {\n                            reg: limit_ctx.reg_limit,\n                        });\n                    }\n                    _ => unreachable!(\"parse_numeric_literal only returns Integer or Float\"),\n                },\n                _ => {\n                    let r = limit_ctx.reg_limit;\n\n                    _ = translate_expr(program, None, expr, r, &t_ctx.resolver)?;\n                    program.emit_insn(Insn::MustBeInt { reg: r });\n                }\n            }\n        }\n    }\n\n    if t_ctx.reg_offset.is_none() {\n        if let Some(expr) = offset {\n            let offset_reg = program.alloc_register();\n            t_ctx.reg_offset = Some(offset_reg);\n            match expr.as_ref() {\n                Expr::Literal(Literal::Numeric(n)) => match parse_numeric_literal(n)? {\n                    crate::types::Value::Numeric(crate::Numeric::Integer(value)) => {\n                        program.emit_insn(Insn::Integer {\n                            value,\n                            dest: offset_reg,\n                        });\n                    }\n                    crate::types::Value::Numeric(crate::Numeric::Float(value)) => {\n                        program.emit_insn(Insn::Real {\n                            value: value.into(),\n                            dest: offset_reg,\n                        });\n                        program.emit_insn(Insn::MustBeInt { reg: offset_reg });\n                    }\n                    _ => unreachable!(\"parse_numeric_literal only returns Integer or Float\"),\n                },\n                _ => {\n                    _ = translate_expr(program, None, expr, offset_reg, &t_ctx.resolver)?;\n                }\n            }\n            program.add_comment(program.offset(), \"OFFSET counter\");\n            program.emit_insn(Insn::MustBeInt { reg: offset_reg });\n\n            let combined_reg = program.alloc_register();\n            t_ctx.reg_limit_offset_sum = Some(combined_reg);\n            program.add_comment(program.offset(), \"OFFSET + LIMIT\");\n            program.emit_insn(Insn::OffsetLimit {\n                limit_reg: limit_ctx.reg_limit,\n                offset_reg,\n                combined_reg,\n            });\n        }\n    }\n\n    // exit early if LIMIT 0\n    let main_loop_end = t_ctx\n        .label_main_loop_end\n        .expect(\"label_main_loop_end must be set before init_limit\");\n    program.emit_insn(Insn::IfNot {\n        reg: limit_ctx.reg_limit,\n        target_pc: main_loop_end,\n        jump_if_null: false,\n    });\n\n    Ok(())\n}\n\n/// We have `Expr`s which have *not* had column references bound to them,\n/// so they are in the state of Expr::Id/Expr::Qualified, etc, and instead of binding Expr::Column\n/// we need to bind Expr::Register, as we have already loaded the *new* column values from the\n/// UPDATE statement into registers starting at `columns_start_reg`, which we want to reference.\nfn rewrite_where_for_update_registers(\n    expr: &mut Expr,\n    columns: &[Column],\n    columns_start_reg: usize,\n    rowid_reg: usize,\n) -> Result<WalkControl> {\n    walk_expr_mut(expr, &mut |e: &mut Expr| -> Result<WalkControl> {\n        match e {\n            Expr::Qualified(_, col) | Expr::DoublyQualified(_, _, col) => {\n                let normalized = normalize_ident(col.as_str());\n                if let Some((idx, c)) = columns.iter().enumerate().find(|(_, c)| {\n                    c.name\n                        .as_ref()\n                        .is_some_and(|n| n.eq_ignore_ascii_case(&normalized))\n                }) {\n                    if c.is_rowid_alias() {\n                        *e = Expr::Register(rowid_reg);\n                    } else {\n                        *e = Expr::Register(columns_start_reg + idx);\n                    }\n                }\n            }\n            Expr::Id(name) => {\n                let normalized = normalize_ident(name.as_str());\n                if ROWID_STRS\n                    .iter()\n                    .any(|s| s.eq_ignore_ascii_case(&normalized))\n                {\n                    *e = Expr::Register(rowid_reg);\n                } else if let Some((idx, c)) = columns.iter().enumerate().find(|(_, c)| {\n                    c.name\n                        .as_ref()\n                        .is_some_and(|n| n.eq_ignore_ascii_case(&normalized))\n                }) {\n                    if c.is_rowid_alias() {\n                        *e = Expr::Register(rowid_reg);\n                    } else {\n                        *e = Expr::Register(columns_start_reg + idx);\n                    }\n                }\n            }\n            Expr::RowId { .. } => {\n                *e = Expr::Register(rowid_reg);\n            }\n            _ => {}\n        }\n        Ok(WalkControl::Continue)\n    })\n}\n\n/// Emit code to load the value of an IndexColumn from the OLD image of the row being updated.\n/// Handling expression indexes and regular columns\npub(crate) fn emit_index_column_value_old_image(\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    table_references: &mut TableReferences,\n    table_cursor_id: usize,\n    idx_col: &IndexColumn,\n    dest_reg: usize,\n) -> Result<()> {\n    if let Some(expr) = &idx_col.expr {\n        let mut expr = expr.as_ref().clone();\n        bind_and_rewrite_expr(\n            &mut expr,\n            Some(table_references),\n            None,\n            resolver,\n            BindingBehavior::ResultColumnsNotAllowed,\n        )?;\n        translate_expr_no_constant_opt(\n            program,\n            Some(table_references),\n            &expr,\n            dest_reg,\n            resolver,\n            NoConstantOptReason::RegisterReuse,\n        )?;\n    } else {\n        program.emit_column_or_rowid(table_cursor_id, idx_col.pos_in_table, dest_reg);\n    }\n    Ok(())\n}\n\n/// Emit code to load the value of an IndexColumn from the NEW image of the row being updated.\n/// Handling expression indexes and regular columns\n#[allow(clippy::too_many_arguments)]\nfn emit_index_column_value_new_image(\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    columns: &[Column],\n    columns_start_reg: usize,\n    rowid_reg: usize,\n    idx_col: &IndexColumn,\n    dest_reg: usize,\n    is_strict: bool,\n) -> Result<()> {\n    if let Some(expr) = &idx_col.expr {\n        let mut expr = expr.as_ref().clone();\n        rewrite_where_for_update_registers(&mut expr, columns, columns_start_reg, rowid_reg)?;\n        // The caller must have populated resolver.register_affinities so that\n        // comparison instructions in the expression get the correct column\n        // affinity even though column references have been rewritten to\n        // Expr::Register.\n        // After rewrite, Expr::Register nodes reference encoded column registers.\n        // Decode custom type registers so the expression evaluates on user-facing\n        // values, matching what SELECT / CREATE INDEX see.\n        crate::translate::expr::decode_custom_type_registers_in_expr(\n            program,\n            resolver,\n            &mut expr,\n            columns,\n            columns_start_reg,\n            Some(rowid_reg),\n            is_strict,\n        )?;\n        translate_expr_no_constant_opt(\n            program,\n            None,\n            &expr,\n            dest_reg,\n            resolver,\n            NoConstantOptReason::RegisterReuse,\n        )?;\n    } else {\n        let col_in_table = columns\n            .get(idx_col.pos_in_table)\n            .expect(\"column index out of bounds\");\n        let src_reg = if col_in_table.is_rowid_alias() {\n            rowid_reg\n        } else {\n            columns_start_reg + idx_col.pos_in_table\n        };\n        program.emit_insn(Insn::Copy {\n            src_reg,\n            dst_reg: dest_reg,\n            extra_amount: 0,\n        });\n    }\n    Ok(())\n}\n\n/// Emit bytecode for evaluating CHECK constraints.\n/// Assumes the resolver cache is already populated with column-to-register mappings.\nfn emit_check_constraint_bytecode(\n    program: &mut ProgramBuilder,\n    check_constraints: &[CheckConstraint],\n    resolver: &mut Resolver,\n    or_conflict: ResolveType,\n    skip_row_label: BranchOffset,\n    referenced_tables: Option<&TableReferences>,\n    table_name: &str,\n) -> Result<()> {\n    for check_constraint in check_constraints {\n        let expr_result_reg = program.alloc_register();\n\n        let mut rewritten_expr = check_constraint.expr.clone();\n        if let Some(referenced_tables) = referenced_tables {\n            let mut binding_tables = referenced_tables.clone();\n            if let Some(joined_table) = binding_tables.joined_tables_mut().first_mut() {\n                // CHECK expressions come from schema SQL and may use the base table name\n                // even when the query references the table through an alias.\n                joined_table.identifier = table_name.to_string();\n            }\n            bind_and_rewrite_expr(\n                &mut rewritten_expr,\n                Some(&mut binding_tables),\n                None,\n                resolver,\n                BindingBehavior::ResultColumnsNotAllowed,\n            )?;\n        }\n\n        translate_expr_no_constant_opt(\n            program,\n            referenced_tables,\n            &rewritten_expr,\n            expr_result_reg,\n            resolver,\n            NoConstantOptReason::RegisterReuse,\n        )?;\n\n        // CHECK constraint passes if the result is NULL or non-zero (truthy)\n        let constraint_passed_label = program.allocate_label();\n\n        // NULL means unknown, which passes CHECK constraints in SQLite\n        program.emit_insn(Insn::IsNull {\n            reg: expr_result_reg,\n            target_pc: constraint_passed_label,\n        });\n\n        program.emit_insn(Insn::If {\n            reg: expr_result_reg,\n            target_pc: constraint_passed_label,\n            jump_if_null: false,\n        });\n\n        let constraint_name = match &check_constraint.name {\n            Some(name) => name.clone(),\n            None => format!(\"{}\", check_constraint.expr),\n        };\n\n        match or_conflict {\n            ResolveType::Ignore => {\n                program.emit_insn(Insn::Goto {\n                    target_pc: skip_row_label,\n                });\n            }\n            // In SQLite, REPLACE does not apply to CHECK constraints — it aborts,\n            // same as Abort/Fail/Rollback.\n            ResolveType::Abort\n            | ResolveType::Fail\n            | ResolveType::Rollback\n            | ResolveType::Replace => {\n                program.emit_insn(Insn::Halt {\n                    err_code: SQLITE_CONSTRAINT_CHECK,\n                    description: constraint_name.to_string(),\n                    on_error: None,\n                    description_reg: None,\n                });\n            }\n        }\n\n        program.preassign_label_to_next_insn(constraint_passed_label);\n    }\n    Ok(())\n}\n\n/// Returns true if the CHECK constraint expression references any column whose\n/// normalized name is in `column_names`. This is used during UPDATE to skip\n/// CHECK constraints that only reference columns not in the SET clause, matching\n/// SQLite's optimization behavior.\nfn check_expr_references_columns(expr: &ast::Expr, column_names: &HashSet<String>) -> bool {\n    column_names\n        .iter()\n        .any(|name| check_expr_references_column(expr, name))\n}\n\n/// Emit CHECK constraint evaluation with resolver cache setup and teardown.\n/// Takes column-to-register mappings as an iterator to avoid heap allocation.\n#[allow(clippy::too_many_arguments)]\npub(crate) fn emit_check_constraints<'a>(\n    program: &mut ProgramBuilder,\n    check_constraints: &[CheckConstraint],\n    resolver: &mut Resolver,\n    table_name: &str,\n    rowid_reg: usize,\n    column_mappings: impl Iterator<Item = (&'a str, usize)>,\n    connection: &Arc<Connection>,\n    or_conflict: ResolveType,\n    skip_row_label: BranchOffset,\n    referenced_tables: Option<&TableReferences>,\n) -> Result<()> {\n    if connection.check_constraints_ignored() || check_constraints.is_empty() {\n        return Ok(());\n    }\n\n    let column_mappings: Vec<(&str, usize)> = column_mappings.collect();\n    let initial_cache_size = resolver.expr_to_reg_cache.len();\n    let joined_table = referenced_tables.and_then(|tables| tables.joined_tables().first());\n\n    // Map rowid aliases to the actual rowid register.\n    // We cache both unqualified (Expr::Id) and qualified (Expr::Qualified) forms\n    // so that CHECK expressions like `CHECK(rowid > 0)` and `CHECK(t.rowid > 0)` both resolve.\n    for rowid_name in ROWID_STRS {\n        let rowid_expr = ast::Expr::Id(ast::Name::exact(rowid_name.to_string()));\n        resolver.cache_expr_reg(Cow::Owned(rowid_expr), rowid_reg, false, None);\n        let qualified_expr = ast::Expr::Qualified(\n            ast::Name::exact(table_name.to_string()),\n            ast::Name::exact(rowid_name.to_string()),\n        );\n        resolver.cache_expr_reg(Cow::Owned(qualified_expr), rowid_reg, false, None);\n    }\n\n    // Map each column to its register (both unqualified and qualified forms).\n    for (col_name, register) in column_mappings.iter().copied() {\n        let collation = joined_table\n            .and_then(|table| {\n                table.columns().iter().find(|col| {\n                    col.name\n                        .as_ref()\n                        .is_some_and(|name| name.eq_ignore_ascii_case(col_name))\n                })\n            })\n            .map(|col| (col.collation(), false));\n        let column_expr = ast::Expr::Id(ast::Name::exact(col_name.to_string()));\n        resolver.cache_expr_reg(Cow::Owned(column_expr), register, false, collation);\n        let qualified_expr = ast::Expr::Qualified(\n            ast::Name::exact(table_name.to_string()),\n            ast::Name::exact(col_name.to_string()),\n        );\n        resolver.cache_expr_reg(Cow::Owned(qualified_expr), register, false, collation);\n    }\n\n    if let Some(joined_table) = joined_table {\n        resolver.cache_expr_reg(\n            Cow::Owned(ast::Expr::RowId {\n                database: None,\n                table: joined_table.internal_id,\n            }),\n            rowid_reg,\n            false,\n            None,\n        );\n\n        for (col_name, register) in column_mappings.iter().copied() {\n            if let Some((idx, col)) = joined_table.columns().iter().enumerate().find(|(_, c)| {\n                c.name\n                    .as_ref()\n                    .is_some_and(|n| n.eq_ignore_ascii_case(col_name))\n            }) {\n                resolver.cache_expr_reg(\n                    Cow::Owned(ast::Expr::Column {\n                        database: None,\n                        table: joined_table.internal_id,\n                        column: idx,\n                        is_rowid_alias: col.is_rowid_alias(),\n                    }),\n                    register,\n                    false,\n                    Some((col.collation(), false)),\n                );\n            }\n        }\n    }\n\n    resolver.enable_expr_to_reg_cache();\n\n    let result = emit_check_constraint_bytecode(\n        program,\n        check_constraints,\n        resolver,\n        or_conflict,\n        skip_row_label,\n        referenced_tables,\n        table_name,\n    );\n\n    // Always restore resolver state, even on error.\n    resolver.expr_to_reg_cache.truncate(initial_cache_size);\n    resolver.expr_to_reg_cache_enabled = false;\n\n    result\n}\n"
  },
  {
    "path": "core/translate/emitter/select.rs",
    "content": "use crate::{\n    emit_explain,\n    schema::BTreeTable,\n    sync::Arc,\n    translate::{\n        aggregation::emit_ungrouped_aggregation,\n        emitter::{\n            build_rowid_column, init_exists_result_regs, init_limit, Column, CursorID, CursorType,\n            MaterializedBuildInput, MaterializedBuildInputMode, MaterializedColumnRef,\n            OperationMode, ResultSetColumn, TableMask, TranslateCtx,\n        },\n        group_by::{group_by_agg_phase, group_by_emit_row_phase, EmitGroupBy, GroupByRowSource},\n        main_loop::{init_distinct, CloseLoop, InitLoop, LoopBodyEmitter, OpenLoop},\n        order_by::EmitOrderBy,\n        plan::{\n            Distinctness, EphemeralRowidMode, EvalAt, IndexMethodQuery, JoinOrderMember, Operation,\n            QueryDestination, Scan, Search, SeekKeyComponent, SelectPlan, SimpleAggregate,\n        },\n        planner::table_mask_from_expr,\n        select::emit_simple_count,\n        subquery::{emit_from_clause_subqueries, emit_non_from_clause_subqueries_for_eval_at},\n        values::emit_values,\n        window::{emit_window_results, EmitWindow},\n        ProgramBuilder, Resolver,\n    },\n    vdbe::insn::Insn,\n    HashMap, HashSet, Result,\n};\nuse tracing::{instrument, Level};\nuse turso_macros::turso_assert;\nuse turso_parser::ast::Expr;\n\n#[instrument(skip_all, level = Level::DEBUG)]\npub fn emit_program_for_select(\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    plan: SelectPlan,\n) -> Result<()> {\n    emit_program_for_select_with_resolver(program, resolver.fork(), plan)\n}\n\npub fn emit_program_for_select_with_resolver(\n    program: &mut ProgramBuilder,\n    resolver: Resolver,\n    mut plan: SelectPlan,\n) -> Result<()> {\n    let materialized_build_inputs = emit_materialized_build_inputs(program, &resolver, &mut plan)?;\n    emit_program_for_select_with_inputs(program, &resolver, plan, materialized_build_inputs)\n}\n\nfn emit_program_for_select_with_inputs(\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    mut plan: SelectPlan,\n    materialized_build_inputs: HashMap<usize, MaterializedBuildInput>,\n) -> Result<()> {\n    let result_cols_start = program.with_scoped_result_cols_start(|program| {\n        let mut t_ctx = TranslateCtx::new(\n            program,\n            resolver.fork_with_expr_cache(),\n            plan.table_references.joined_tables().len(),\n            false,\n        );\n        t_ctx.materialized_build_inputs = materialized_build_inputs;\n        emit_query(program, &mut plan, &mut t_ctx)\n    })?;\n\n    program.result_columns = plan.result_columns;\n    program.table_references.extend(plan.table_references);\n    program.reg_result_cols_start = Some(result_cols_start);\n    Ok(())\n}\n\n#[instrument(skip_all, level = Level::DEBUG)]\npub fn emit_query<'a>(\n    program: &mut ProgramBuilder,\n    plan: &'a mut SelectPlan,\n    t_ctx: &mut TranslateCtx<'a>,\n) -> Result<usize> {\n    let after_main_loop_label = program.allocate_label();\n    t_ctx.label_main_loop_end = Some(after_main_loop_label);\n\n    // Evaluate uncorrelated subqueries as early as possible, because even LIMIT can reference a subquery.\n    // This must happen before VALUES emission since VALUES expressions may contain scalar subqueries.\n    emit_non_from_clause_subqueries_for_eval_at(\n        program,\n        &t_ctx.resolver,\n        &mut plan.non_from_clause_subqueries,\n        &plan.join_order,\n        Some(&plan.table_references),\n        EvalAt::BeforeLoop,\n        |_| true,\n    )?;\n\n    // Handle VALUES clause - emit values after subqueries are prepared\n    if !plan.values.is_empty() {\n        let reg_result_cols_start = emit_values(program, plan, t_ctx)?;\n        program.preassign_label_to_next_insn(after_main_loop_label);\n        return Ok(reg_result_cols_start);\n    }\n\n    // Emit FROM clause subqueries first so the results can be read in the main query loop.\n    emit_from_clause_subqueries(program, t_ctx, &mut plan.table_references, &plan.join_order)?;\n\n    // For non-grouped aggregation queries that also have non-aggregate columns,\n    // we need to ensure non-aggregate columns are only emitted once.\n    // This flag helps track whether we've already emitted these columns.\n    let has_ungrouped_nonagg_cols = !plan.aggregates.is_empty()\n        && plan.group_by.is_none()\n        && plan.result_columns.iter().any(|c| !c.contains_aggregates);\n\n    if has_ungrouped_nonagg_cols {\n        let flag = program.alloc_register();\n        program.emit_int(0, flag); // Initialize flag to 0 (not yet emitted)\n        t_ctx.reg_nonagg_emit_once_flag = Some(flag);\n    }\n\n    // Allocate registers for result columns\n    if t_ctx.reg_result_cols_start.is_none() {\n        t_ctx.reg_result_cols_start = Some(program.alloc_registers(plan.result_columns.len()));\n        program.reg_result_cols_start = t_ctx.reg_result_cols_start\n    }\n\n    // For ungrouped aggregates with non-aggregate columns, initialize EXISTS subquery\n    // result_regs to 0. EXISTS returns 0 (not NULL) when the subquery is never evaluated\n    // (correlated EXISTS in empty loop). Non-aggregate columns themselves are evaluated\n    // after the loop in emit_ungrouped_aggregation if the loop never ran.\n    // We only initialize EXISTS subqueries that haven't been evaluated yet (correlated ones).\n    if has_ungrouped_nonagg_cols {\n        for rc in plan.result_columns.iter() {\n            if !rc.contains_aggregates {\n                init_exists_result_regs(program, &rc.expr, &plan.non_from_clause_subqueries);\n            }\n        }\n    }\n\n    let has_group_by_exprs = plan\n        .group_by\n        .as_ref()\n        .is_some_and(|gb| !gb.exprs.is_empty());\n\n    // Initialize cursors and other resources needed for query execution\n    if !plan.order_by.is_empty() {\n        EmitOrderBy::init(\n            program,\n            t_ctx,\n            &plan.result_columns,\n            &plan.order_by,\n            &plan.table_references,\n            has_group_by_exprs,\n            plan.distinctness != Distinctness::NonDistinct,\n            &plan.aggregates,\n        )?;\n    }\n\n    if has_group_by_exprs {\n        if let Some(ref group_by) = plan.group_by {\n            EmitGroupBy::init(\n                program,\n                t_ctx,\n                group_by,\n                plan,\n                &plan.result_columns,\n                &plan.order_by,\n            )?;\n        }\n    } else if !plan.aggregates.is_empty() {\n        // Handle aggregation without GROUP BY (or HAVING without GROUP BY)\n        // Aggregate registers need to be NULLed at the start because the same registers might be reused on another invocation of a subquery,\n        // and if they are not NULLed, the 2nd invocation of the same subquery will have values left over from the first invocation.\n        t_ctx.reg_agg_start = Some(program.alloc_registers_and_init_w_null(plan.aggregates.len()));\n    } else if let Some(window) = &plan.window {\n        EmitWindow::init(\n            program,\n            t_ctx,\n            window,\n            plan,\n            &plan.result_columns,\n            &plan.order_by,\n        )?;\n    }\n\n    let distinct_ctx = if let Distinctness::Distinct { .. } = &plan.distinctness {\n        Some(init_distinct(program, plan)?)\n    } else {\n        None\n    };\n    if let Distinctness::Distinct { ctx } = &mut plan.distinctness {\n        *ctx = distinct_ctx\n    }\n    if let Distinctness::Distinct { ctx: Some(ctx) } = &plan.distinctness {\n        program.emit_insn(Insn::HashClear {\n            hash_table_id: ctx.hash_table_id,\n        });\n        emit_explain!(program, false, \"USE HASH TABLE FOR DISTINCT\".to_owned());\n    }\n\n    init_limit(program, t_ctx, &plan.limit, &plan.offset)?;\n\n    // No rows will be read from source table loops if there is a constant false condition eg. WHERE 0\n    // however an aggregation might still happen,\n    // e.g. SELECT COUNT(*) WHERE 0 returns a row with 0, not an empty result set.\n    // This Goto must be placed AFTER all initialization (cursors, sorters, etc.) so that\n    // resources like the GROUP BY sorter are properly opened before we skip to the aggregation phase.\n    if plan.contains_constant_false_condition {\n        program.emit_insn(Insn::Goto {\n            target_pc: after_main_loop_label,\n        });\n    }\n    InitLoop::emit(\n        program,\n        t_ctx,\n        &plan.table_references,\n        &mut plan.aggregates,\n        &OperationMode::SELECT,\n        &plan.where_clause,\n        &plan.join_order,\n        &mut plan.non_from_clause_subqueries,\n    )?;\n\n    if matches!(plan.simple_aggregate, Some(SimpleAggregate::Count))\n        && emit_simple_count(program, t_ctx, plan)?\n    {\n        // Keep LIMIT's early-exit jump target valid even on the simple_count fast path.\n        // init_limit may emit an IfNot to after_main_loop_label (e.g. scalar subquery injects LIMIT 1).\n        // Without resolving this label before the early return, bytecode assembly fails\n        // with an unresolved IfNot target.\n        program.preassign_label_to_next_insn(after_main_loop_label);\n        return Ok(t_ctx.reg_result_cols_start.unwrap());\n    }\n\n    // Set up main query execution loop\n    OpenLoop::emit(\n        program,\n        t_ctx,\n        &plan.table_references,\n        &plan.join_order,\n        &plan.where_clause,\n        None,\n        OperationMode::SELECT,\n        &mut plan.non_from_clause_subqueries,\n    )?;\n\n    // Process result columns and expressions in the inner loop\n    LoopBodyEmitter::emit(program, t_ctx, plan)?;\n\n    // Clean up and close the main execution loop\n    CloseLoop::emit(\n        program,\n        t_ctx,\n        &plan.table_references,\n        &plan.join_order,\n        OperationMode::SELECT,\n        Some(plan),\n    )?;\n\n    program.preassign_label_to_next_insn(after_main_loop_label);\n\n    let has_order_by = !plan.order_by.is_empty();\n    let order_by_necessary = has_order_by && !plan.contains_constant_false_condition;\n    let mut grouped_output_subqueries = plan.non_from_clause_subqueries.clone();\n\n    // Handle GROUP BY and aggregation processing\n    if has_group_by_exprs {\n        let row_source = &t_ctx\n            .meta_group_by\n            .as_ref()\n            .expect(\"group by metadata not found\")\n            .row_source;\n        if matches!(row_source, GroupByRowSource::Sorter { .. }) {\n            group_by_agg_phase(program, t_ctx, plan)?;\n        }\n        group_by_emit_row_phase(program, t_ctx, plan, &mut grouped_output_subqueries)?;\n    } else if !plan.aggregates.is_empty() {\n        // Handle aggregation without GROUP BY (or HAVING without GROUP BY)\n        emit_ungrouped_aggregation(program, t_ctx, plan)?;\n    } else if plan.window.is_some() {\n        emit_window_results(program, t_ctx, plan)?;\n    }\n\n    // Process ORDER BY results if needed\n    if has_order_by && order_by_necessary {\n        EmitOrderBy::emit(program, t_ctx, plan)?;\n    }\n\n    Ok(t_ctx.reg_result_cols_start.unwrap())\n}\n\n#[derive(Debug, Clone)]\n/// Captures the parameters needed to materialize one hash-build input.\nstruct MaterializationSpec {\n    build_table_idx: usize,\n    probe_table_idx: usize,\n    mode: MaterializedBuildInputMode,\n    prefix_tables: Vec<usize>,\n    key_exprs: Vec<Expr>,\n    payload_columns: Vec<MaterializedColumnRef>,\n}\n\n/// Build materialized hash-build inputs for hash joins that depend on prior joins.\n///\n/// A materialized build input is an ephemeral table that captures the rows\n/// a hash join is allowed to build from after earlier joins and filters have\n/// been applied. This prevents the build side from being re-scanned in its\n/// full, unfiltered form when prior join constraints must be respected.\n///\n/// The materialization uses a join-prefix: all tables that appear before the\n/// probe table in the join order, plus the build table itself. This prefix\n/// represents the minimal context needed to evaluate build-side constraints.\n/// For probe->build chaining we store join keys and payload columns directly\n/// in the ephemeral table; otherwise we only store rowids and `SeekRowid`\n/// during probing when needed.\nfn emit_materialized_build_inputs(\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    plan: &mut SelectPlan,\n) -> Result<HashMap<usize, MaterializedBuildInput>> {\n    let mut build_inputs: HashMap<usize, MaterializedBuildInput> = HashMap::default();\n    let mut materializations: Vec<MaterializationSpec> = Vec::new();\n    let mut hash_tables_to_keep_open: HashSet<usize> = HashSet::default();\n\n    // Keep hash tables open while running materialization subplans so we can reuse them.\n    // A build table may appear in multiple hash joins when chaining, so we do not\n    // treat repeated build tables as an error.\n    for table in plan.table_references.joined_tables().iter() {\n        if let Operation::HashJoin(hash_join_op) = &table.op {\n            let build_table = &plan.table_references.joined_tables()[hash_join_op.build_table_idx];\n            hash_tables_to_keep_open.insert(build_table.internal_id.into());\n        }\n    }\n\n    let mut seen_build_tables: HashSet<usize> = HashSet::default();\n\n    // decide per-hash-join materialization mode (rowid-only vs key+payload).\n    for member in plan.join_order.iter() {\n        let table = &plan.table_references.joined_tables()[member.original_idx];\n        if let Operation::HashJoin(hash_join_op) = &table.op {\n            if !hash_join_op.materialize_build_input\n                || !seen_build_tables.insert(hash_join_op.build_table_idx)\n            {\n                continue;\n            }\n\n            let probe_table_idx = hash_join_op.probe_table_idx;\n            let probe_pos = plan\n                .join_order\n                .iter()\n                .position(|member| member.original_idx == probe_table_idx)\n                .unwrap_or(plan.join_order.len());\n            let build_table_was_prior_probe = plan.join_order[..probe_pos].iter().any(|member| {\n                let table_ref = &plan.table_references.joined_tables()[member.original_idx];\n                matches!(\n                    table_ref.op,\n                    Operation::HashJoin(ref hj) if hj.probe_table_idx == hash_join_op.build_table_idx\n                )\n            });\n\n            // The join prefix is the set of tables we include when building this hash\n            // input (all tables before the probe + the build table). If the prefix\n            // has *any* table besides the build table, then rowid-only materialization\n            // is unsafe. Here's why:\n            //\n            // Rowid-only keeps each build-table rowid at most once. That throws away\n            // which prefix row it came from, so we lose the one-to-one link between\n            // a prefix match and a build row.\n            //\n            // Example (t1 is a left-side table earlier in the join order):\n            //   t1 rows:     t1_1(c=1), t1_2(c=2)\n            //   t2 rows:     t2_7(c=1), t2_8(c=2)   (build table)\n            //   t3 rows:     one row per t2 row\n            //\n            // Correct result after joining:\n            //   t1_1 + t2_7 + t2_7's t3 row\n            //   t1_2 + t2_8 + t2_8's t3 row   (2 rows)\n            //\n            // Key+payload materialization lets us PRUNE the prefix tables (like t1)\n            // from the main join order, because their needed columns now live in\n            // the payload. So the main plan does NOT loop t1 again.\n            //\n            // However, rowid-only materialization keeps just {t2_7, t2_8} with no link to t1_1/t1_2.\n            // Since t1 stays in the main join loop, each t1 row joins against the\n            // materialized t2 set. With no t1→t2 correlation, every t1 row matches\n            // both t2 rows, incorrectly producing 4 rows (a cross product).\n            //\n            // Therefore: if the prefix has other tables, we must store key+payload\n            // rows so each prefix match stays distinct and the main plan can drop\n            // the prefix loops.\n            let (_, included_tables) =\n                materialization_prefix(plan, hash_join_op.build_table_idx, probe_table_idx)?;\n            let prefix_has_other_tables = included_tables\n                .iter()\n                .any(|table_idx| *table_idx != hash_join_op.build_table_idx);\n\n            if build_table_was_prior_probe || prefix_has_other_tables {\n                // Prior probe -> build chaining OR any multi-table prefix requires keys+payload\n                // so we do not lose multiplicity or correlation.\n                let payload_columns = collect_materialized_payload_columns(plan, &included_tables)?;\n                let key_exprs: Vec<Expr> = hash_join_op\n                    .join_keys\n                    .iter()\n                    .map(|key| key.get_build_expr(&plan.where_clause).clone())\n                    .collect();\n                let mode = MaterializedBuildInputMode::KeyPayload {\n                    num_keys: key_exprs.len(),\n                    payload_columns: payload_columns.clone(),\n                };\n                materializations.push(MaterializationSpec {\n                    build_table_idx: hash_join_op.build_table_idx,\n                    probe_table_idx,\n                    mode,\n                    prefix_tables: included_tables,\n                    key_exprs,\n                    payload_columns,\n                });\n            } else {\n                // Single-table prefix: a rowid list preserves the build-side filters\n                // without losing multiplicity (as explained in the comment above).\n                materializations.push(MaterializationSpec {\n                    build_table_idx: hash_join_op.build_table_idx,\n                    probe_table_idx,\n                    mode: MaterializedBuildInputMode::RowidOnly,\n                    prefix_tables: Vec::new(),\n                    key_exprs: Vec::new(),\n                    payload_columns: Vec::new(),\n                });\n            }\n        }\n    }\n\n    // Now we emit each of the materialization subplans into an ephemeral table.\n    for spec in materializations.iter() {\n        let build_table = &plan.table_references.joined_tables()[spec.build_table_idx];\n        let build_table_name = if build_table.table.get_name() == build_table.identifier {\n            build_table.identifier.clone()\n        } else {\n            format!(\n                \"{} AS {}\",\n                build_table.table.get_name(),\n                build_table.identifier\n            )\n        };\n        let internal_id = program.table_reference_counter.next();\n        let columns = match &spec.mode {\n            MaterializedBuildInputMode::RowidOnly => vec![build_rowid_column()],\n            MaterializedBuildInputMode::KeyPayload {\n                num_keys,\n                payload_columns,\n            } => build_materialized_input_columns(*num_keys, payload_columns),\n        };\n        let ephemeral_table = Arc::new(BTreeTable {\n            root_page: 0,\n            name: format!(\"hash_build_input_{internal_id}\"),\n            has_rowid: true,\n            has_autoincrement: false,\n            primary_key_columns: vec![],\n            columns,\n            is_strict: false,\n            unique_sets: vec![],\n            foreign_keys: vec![],\n            check_constraints: vec![],\n            rowid_alias_conflict_clause: None,\n        });\n        let cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(ephemeral_table.clone()));\n\n        // Build a plan that emits only rowids for the build table using the join prefix\n        // that makes the hash join legal (including any earlier hash joins).\n        let materialize_plan = build_materialized_build_input_plan(\n            plan,\n            spec.build_table_idx,\n            spec.probe_table_idx,\n            cursor_id,\n            ephemeral_table,\n            &spec.mode,\n            &spec.key_exprs,\n            &spec.payload_columns,\n            &build_inputs,\n        )?;\n\n        // Make the materialization plan show up as a subtree in EXPLAIN QUERY PLAN output.\n        emit_explain!(\n            program,\n            true,\n            format!(\"MATERIALIZE hash build input for {build_table_name}\")\n        );\n        program.emit_insn(Insn::OpenEphemeral {\n            cursor_id,\n            is_table: true,\n        });\n        program.nested(|program| -> Result<()> {\n            program.set_hash_tables_to_keep_open(&hash_tables_to_keep_open);\n            emit_program_for_select_with_inputs(\n                program,\n                resolver,\n                materialize_plan,\n                build_inputs.clone(),\n            )?;\n            program.clear_hash_tables_to_keep_open();\n            Ok(())\n        })?;\n        program.pop_current_parent_explain();\n\n        build_inputs.insert(\n            spec.build_table_idx,\n            MaterializedBuildInput {\n                cursor_id,\n                mode: spec.mode.clone(),\n                prefix_tables: spec.prefix_tables.clone(),\n            },\n        );\n    }\n\n    // Drop any join-prefix tables already captured by key+payload materializations.\n    prune_join_order_for_materialized_inputs(plan, &build_inputs)?;\n\n    #[cfg(debug_assertions)]\n    turso_assert!(\n        {\n            let join_order_tables: HashSet<_> = plan\n                .join_order\n                .iter()\n                .map(|member| member.original_idx)\n                .collect();\n            let build_tables_in_plan: HashSet<_> = plan\n                .join_order\n                .iter()\n                .filter_map(|member| {\n                    let table = &plan.table_references.joined_tables()[member.original_idx];\n                    if let Operation::HashJoin(hash_join_op) = &table.op {\n                        Some(hash_join_op.build_table_idx)\n                    } else {\n                        None\n                    }\n                })\n                .collect();\n            build_inputs.iter().all(|(build_table_idx, input)| {\n                if !build_tables_in_plan.contains(build_table_idx) {\n                    return true;\n                }\n                if !matches!(input.mode, MaterializedBuildInputMode::KeyPayload { .. }) {\n                    return true;\n                }\n                input\n                    .prefix_tables\n                    .iter()\n                    .all(|table_idx| !join_order_tables.contains(table_idx))\n            })\n        },\n        \"materialized build input prefix table still present in join order\"\n    );\n    Ok(build_inputs)\n}\n\n/// Remove join-order entries already satisfied by key+payload materializations.\n///\n/// This prevents redundant scans (and cross products) when a hash-build input\n/// already captures a join prefix. It also marks fully covered WHERE terms as\n/// consumed so they are not re-applied later in the main plan.\nfn prune_join_order_for_materialized_inputs(\n    plan: &mut SelectPlan,\n    build_inputs: &HashMap<usize, MaterializedBuildInput>,\n) -> Result<()> {\n    if build_inputs.is_empty() {\n        return Ok(());\n    }\n\n    let mut build_tables_in_plan = HashSet::default();\n    for member in plan.join_order.iter() {\n        let table = &plan.table_references.joined_tables()[member.original_idx];\n        if let Operation::HashJoin(hash_join_op) = &table.op {\n            build_tables_in_plan.insert(hash_join_op.build_table_idx);\n        }\n    }\n\n    let mut tables_to_remove: HashSet<usize> = HashSet::default();\n    for (build_table_idx, input) in build_inputs.iter() {\n        if !build_tables_in_plan.contains(build_table_idx) {\n            continue;\n        }\n        if matches!(input.mode, MaterializedBuildInputMode::KeyPayload { .. }) {\n            tables_to_remove.extend(input.prefix_tables.iter().copied());\n        }\n    }\n\n    if tables_to_remove.is_empty() {\n        return Ok(());\n    }\n\n    let prefix_mask = TableMask::from_table_number_iter(tables_to_remove.iter().copied());\n    for term in plan.where_clause.iter_mut() {\n        if term.consumed {\n            continue;\n        }\n        if term.from_outer_join.is_some() {\n            // OUTER JOIN terms still belong to the right-table loop recorded in\n            // `from_outer_join`. Materializing and pruning the build-side prefix\n            // does not make those terms safe to consume here, because the\n            // materialization subplan does not include the probe table that\n            // determines the null-extension boundary.\n            continue;\n        }\n        let mask = table_mask_from_expr(\n            &term.expr,\n            &plan.table_references,\n            &plan.non_from_clause_subqueries,\n        )?;\n        if prefix_mask.contains_all(&mask) {\n            term.consumed = true;\n        }\n    }\n    plan.join_order\n        .retain(|member| !tables_to_remove.contains(&member.original_idx));\n    Ok(())\n}\n\n/// Compute the join-prefix used to materialize a hash-build input.\n///\n/// The prefix consists of all tables before the probe table plus the build\n/// table itself (if not already present). The returned `included_tables`\n/// list also includes build tables of earlier hash joins so payload collection\n/// can capture all referenced columns.\nfn materialization_prefix(\n    plan: &SelectPlan,\n    build_table_idx: usize,\n    probe_table_idx: usize,\n) -> Result<(Vec<JoinOrderMember>, Vec<usize>)> {\n    let mut join_order = plan.join_order.clone();\n    if join_order\n        .iter()\n        .all(|member| member.original_idx != probe_table_idx)\n    {\n        let probe_table = &plan.table_references.joined_tables()[probe_table_idx];\n        join_order.push(JoinOrderMember {\n            table_id: probe_table.internal_id,\n            original_idx: probe_table_idx,\n            is_outer: probe_table\n                .join_info\n                .as_ref()\n                .is_some_and(|join_info| join_info.is_outer()),\n        });\n    }\n    let probe_pos = join_order\n        .iter()\n        .position(|m| m.original_idx == probe_table_idx)\n        .expect(\"probe table just ensured in join order\");\n\n    // Only include tables prior to the probe table. The materialization subplan\n    // should filter the build table using prior join constraints, not scan the probe.\n    let mut prefix_join_order = join_order[..probe_pos].to_vec();\n    if prefix_join_order\n        .iter()\n        .all(|member| member.original_idx != build_table_idx)\n    {\n        let build_table = &plan.table_references.joined_tables()[build_table_idx];\n        prefix_join_order.push(JoinOrderMember {\n            table_id: build_table.internal_id,\n            original_idx: build_table_idx,\n            is_outer: build_table\n                .join_info\n                .as_ref()\n                .is_some_and(|join_info| join_info.is_outer()),\n        });\n    }\n\n    let mut included_tables: Vec<usize> =\n        prefix_join_order.iter().map(|m| m.original_idx).collect();\n    for member in prefix_join_order.iter() {\n        let table_ref = &plan.table_references.joined_tables()[member.original_idx];\n        if let Operation::HashJoin(hash_join_op) = &table_ref.op {\n            included_tables.push(hash_join_op.build_table_idx);\n        }\n    }\n    included_tables.sort_unstable();\n    included_tables.dedup();\n\n    Ok((prefix_join_order, included_tables))\n}\n\n/// Collect the payload columns needed for a materialized build input.\n///\n/// This gathers referenced columns from the included tables and always adds\n/// rowids for tables that have them so probe-side expressions can be satisfied\n/// without seeking back into base tables.\nfn collect_materialized_payload_columns(\n    plan: &SelectPlan,\n    included_tables: &[usize],\n) -> Result<Vec<MaterializedColumnRef>> {\n    let mut payload_columns: Vec<MaterializedColumnRef> = Vec::new();\n    let mut seen: HashSet<MaterializedColumnRef> = HashSet::default();\n    for table_idx in included_tables.iter().copied() {\n        let table = &plan.table_references.joined_tables()[table_idx];\n        for col_idx in table.col_used_mask.iter() {\n            let is_rowid_alias = table\n                .columns()\n                .get(col_idx)\n                .is_some_and(|col| col.is_rowid_alias());\n            let col_ref = MaterializedColumnRef::Column {\n                table_id: table.internal_id,\n                column_idx: col_idx,\n                is_rowid_alias,\n            };\n            if seen.insert(col_ref.clone()) {\n                payload_columns.push(col_ref);\n            }\n        }\n        if table.btree().is_some_and(|btree| btree.has_rowid) {\n            let rowid_ref = MaterializedColumnRef::RowId {\n                table_id: table.internal_id,\n            };\n            if seen.insert(rowid_ref.clone()) {\n                payload_columns.push(rowid_ref);\n            }\n        }\n    }\n    Ok(payload_columns)\n}\n\n/// Build the ephemeral-table schema for key+payload materializations.\n///\n/// Keys are stored first (typed as BLOB for join-key affinity handling),\n/// followed by payload columns with integer or blob affinity.\nfn build_materialized_input_columns(\n    num_keys: usize,\n    payload_columns: &[MaterializedColumnRef],\n) -> Vec<Column> {\n    let mut columns = Vec::with_capacity(num_keys + payload_columns.len());\n    for i in 0..num_keys {\n        columns.push(Column::new_default_text(\n            Some(format!(\"key_{i}\")),\n            \"BLOB\".to_string(),\n            None,\n        ));\n    }\n    for (i, payload) in payload_columns.iter().enumerate() {\n        let name = Some(format!(\"payload_{i}\"));\n        let column = match payload {\n            MaterializedColumnRef::RowId { .. } => {\n                Column::new_default_integer(name, \"INTEGER\".to_string(), None)\n            }\n            MaterializedColumnRef::Column { .. } => {\n                Column::new_default_text(name, \"BLOB\".to_string(), None)\n            }\n        };\n        columns.push(column);\n    }\n    columns\n}\n\n/// Construct a SELECT plan that materializes build-side inputs into an ephemeral table.\n/// This plan is separate from the main query plan and is exclusively used for the materialization.\n/// process.\n///\n/// The join order is the original prefix up to (but excluding) the probe table, plus\n/// the build table itself. This filters build rows using only prior join constraints\n/// and then prunes any tables already captured by earlier key+payload materializations.\n#[allow(clippy::too_many_arguments)]\nfn build_materialized_build_input_plan(\n    plan: &SelectPlan,\n    build_table_idx: usize,\n    probe_table_idx: usize,\n    cursor_id: CursorID,\n    table: Arc<BTreeTable>,\n    mode: &MaterializedBuildInputMode,\n    key_exprs: &[Expr],\n    payload_columns: &[MaterializedColumnRef],\n    materialized_build_inputs: &HashMap<usize, MaterializedBuildInput>,\n) -> Result<SelectPlan> {\n    // Build a materialization subplan that only includes the join prefix\n    // (all tables prior to the probe + the build table). The resulting plan\n    // is smaller than the original select plan, so any access methods or\n    // predicates that depend on tables outside this prefix must be dropped.\n    let (join_order, included_tables) =\n        materialization_prefix(plan, build_table_idx, probe_table_idx)?;\n    // Bitmask of tables that are actually in the prefix join order for\n    // this materialization subplan. Anything that depends on other tables\n    // cannot be evaluated during those table scans.\n    let join_prefix_mask =\n        TableMask::from_table_number_iter(join_order.iter().map(|m| m.original_idx));\n    // Expressions can also reference build tables of earlier hash joins in this subplan,\n    // because those tables are available during probe loops. Use the broader \"included\"\n    // set when deciding which WHERE terms can be evaluated inside the materialization.\n    let eval_prefix_mask = TableMask::from_table_number_iter(included_tables.iter().copied());\n\n    // Clone WHERE terms for the materialization subplan. We cannot reuse the\n    // parent plan's consumed flags because the optimizer may have consumed\n    // terms for access methods (e.g. ephemeral autoindex seeks) that get\n    // overwritten to scans inside the subplan. Reset each term's consumed\n    // flag: only terms referencing tables outside the prefix are consumed.\n    let mut where_clause = plan.where_clause.clone();\n    for term in where_clause.iter_mut() {\n        let mask = table_mask_from_expr(\n            &term.expr,\n            &plan.table_references,\n            &plan.non_from_clause_subqueries,\n        )?;\n        term.consumed = !eval_prefix_mask.contains_all(&mask);\n    }\n\n    // Clone table references and then \"sanitize\" each access method so that\n    // the materialization subplan does not try to use an access path that\n    // requires tables outside the prefix. If it does, we fall back to a scan.\n    let mut table_references = plan.table_references.clone();\n    for joined_table in table_references.joined_tables_mut().iter_mut() {\n        if let Operation::HashJoin(hash_join_op) = &mut joined_table.op {\n            if hash_join_op.build_table_idx == build_table_idx {\n                // Avoid recursive materialization and disable the hash join for the build table\n                // so it can be accessed using the join constraints.\n                hash_join_op.materialize_build_input = false;\n                joined_table.op = Operation::default_scan_for(&joined_table.table);\n            } else if hash_join_op.probe_table_idx == probe_table_idx {\n                // The probe table is not part of the materialization prefix, so\n                // disable hash joins anchored on it.\n                joined_table.op = Operation::default_scan_for(&joined_table.table);\n            }\n        }\n    }\n\n    // Helper to decide whether an expression depends on tables outside\n    // the prefix. If it does, any access method that relies on that\n    // expression must be invalidated for the materialization subplan.\n    let expr_depends_outside_prefix = |expr: &Expr| -> Result<bool> {\n        let mask = table_mask_from_expr(\n            expr,\n            &plan.table_references,\n            &plan.non_from_clause_subqueries,\n        )?;\n        Ok(!join_prefix_mask.contains_all(&mask))\n    };\n\n    // Walk each table in the cloned plan and ensure its access method is\n    // valid within the prefix. If the access method depends on tables\n    // outside the prefix, downgrade to a plain scan.\n    for (table_idx, joined_table) in table_references.joined_tables_mut().iter_mut().enumerate() {\n        if !join_prefix_mask.contains_table(table_idx) {\n            continue;\n        }\n\n        let mut reset_op = false;\n        match &joined_table.op {\n            Operation::Search(Search::RowidEq { cmp_expr }) => {\n                // Rowid equality searches may depend on other tables (e.g. column = other.col).\n                reset_op = expr_depends_outside_prefix(cmp_expr)?;\n            }\n            Operation::Search(Search::Seek { seek_def, .. }) => {\n                // Seek keys can include expressions bound by other tables. If so,\n                // the seek is not valid in the prefix-only subplan.\n                for component in seek_def.iter(&seek_def.start) {\n                    if let SeekKeyComponent::Expr(expr) = component {\n                        if expr_depends_outside_prefix(expr)? {\n                            reset_op = true;\n                            break;\n                        }\n                    }\n                }\n                if !reset_op {\n                    for component in seek_def.iter(&seek_def.end) {\n                        if let SeekKeyComponent::Expr(expr) = component {\n                            if expr_depends_outside_prefix(expr)? {\n                                reset_op = true;\n                                break;\n                            }\n                        }\n                    }\n                }\n            }\n            Operation::IndexMethodQuery(IndexMethodQuery { arguments, .. }) => {\n                // Index method queries are driven by argument expressions.\n                // If any argument depends on non-prefix tables, we cannot use it.\n                for expr in arguments {\n                    if expr_depends_outside_prefix(expr)? {\n                        reset_op = true;\n                        break;\n                    }\n                }\n            }\n            Operation::Scan(Scan::VirtualTable { constraints, .. }) => {\n                // Virtual table constraints are evaluated against expressions.\n                // If any constraint depends on non-prefix tables, drop the scan\n                // specialization and fall back to a full scan.\n                for expr in constraints {\n                    if expr_depends_outside_prefix(expr)? {\n                        reset_op = true;\n                        break;\n                    }\n                }\n            }\n            Operation::HashJoin(hash_join_op) => {\n                // Hash joins are driven by the probe table's loop. That probe table\n                // must be in the prefix; otherwise the hash join cannot be evaluated\n                // inside this subplan. The build table may live outside the prefix\n                // because the hash build phase scans it independently.\n                if !join_prefix_mask.contains_table(hash_join_op.probe_table_idx) {\n                    reset_op = true;\n                }\n            }\n            _ => {}\n        }\n\n        if reset_op {\n            // Downgrade to a default scan. This ensures the subplan only uses\n            // access paths that are valid within the prefix join order.\n            joined_table.op = Operation::default_scan_for(&joined_table.table);\n        }\n    }\n\n    let build_internal_id = plan.table_references.joined_tables()[build_table_idx].internal_id;\n    let result_columns = match mode {\n        MaterializedBuildInputMode::RowidOnly => vec![ResultSetColumn {\n            expr: Expr::RowId {\n                database: None,\n                table: build_internal_id,\n            },\n            alias: None,\n            contains_aggregates: false,\n        }],\n        MaterializedBuildInputMode::KeyPayload { num_keys, .. } => {\n            turso_assert!(\n                *num_keys == key_exprs.len(),\n                \"materialized hash build input key count mismatch\"\n            );\n            let mut result_columns: Vec<ResultSetColumn> = Vec::new();\n            for expr in key_exprs.iter() {\n                result_columns.push(ResultSetColumn {\n                    expr: expr.clone(),\n                    alias: None,\n                    contains_aggregates: false,\n                });\n            }\n            for payload in payload_columns.iter() {\n                let expr = match payload {\n                    MaterializedColumnRef::Column {\n                        table_id,\n                        column_idx,\n                        is_rowid_alias,\n                    } => Expr::Column {\n                        database: None,\n                        table: *table_id,\n                        column: *column_idx,\n                        is_rowid_alias: *is_rowid_alias,\n                    },\n                    MaterializedColumnRef::RowId { table_id } => Expr::RowId {\n                        database: None,\n                        table: *table_id,\n                    },\n                };\n                result_columns.push(ResultSetColumn {\n                    expr,\n                    alias: None,\n                    contains_aggregates: false,\n                });\n            }\n            result_columns\n        }\n    };\n\n    let mut materialize_plan = SelectPlan {\n        table_references,\n        join_order,\n        result_columns,\n        where_clause,\n        group_by: None,\n        order_by: vec![],\n        aggregates: vec![],\n        limit: None,\n        offset: None,\n        contains_constant_false_condition: false,\n        query_destination: QueryDestination::EphemeralTable {\n            cursor_id,\n            table,\n            rowid_mode: match mode {\n                MaterializedBuildInputMode::RowidOnly => EphemeralRowidMode::FromResultColumns,\n                MaterializedBuildInputMode::KeyPayload { .. } => EphemeralRowidMode::Auto,\n            },\n        },\n        distinctness: Distinctness::NonDistinct,\n        values: vec![],\n        window: None,\n        non_from_clause_subqueries: plan.non_from_clause_subqueries.clone(),\n        input_cardinality_hint: None,\n        estimated_output_rows: None,\n        simple_aggregate: None,\n    };\n\n    prune_join_order_for_materialized_inputs(&mut materialize_plan, materialized_build_inputs)?;\n\n    Ok(materialize_plan)\n}\n"
  },
  {
    "path": "core/translate/emitter/update.rs",
    "content": "use super::TranslateCtx;\nuse crate::translate::insert::halt_desc_and_on_error;\nuse crate::translate::stmt_journal::any_effective_replace;\nuse crate::{\n    ast, emit_explain,\n    error::{SQLITE_CONSTRAINT_PRIMARYKEY, SQLITE_CONSTRAINT_UNIQUE},\n    schema::{BTreeTable, CheckConstraint, Index, ROWID_SENTINEL},\n    sync::Arc,\n    translate::{\n        display::format_eqp_detail,\n        emitter::{\n            check_expr_references_columns, delete::emit_fk_child_decrement_on_delete,\n            emit_cdc_autocommit_commit, emit_cdc_full_record, emit_cdc_insns,\n            emit_cdc_patch_record, emit_check_constraints, emit_index_column_value_new_image,\n            emit_index_column_value_old_image, emit_program_for_select, init_limit,\n            rewrite_where_for_update_registers, OperationMode, Resolver, UpdateRowSource,\n        },\n        expr::{\n            emit_returning_results, emit_returning_scan_back, restore_returning_row_image_in_cache,\n            seed_returning_row_image_in_cache, translate_expr, translate_expr_no_constant_opt,\n            NoConstantOptReason, ReturningBufferCtx,\n        },\n        fkeys::{\n            emit_fk_child_update_counters, emit_fk_parent_new_key_reconcile,\n            emit_fk_update_parent_actions, fire_fk_update_actions, stabilize_new_row_for_fk,\n            ForeignKeyActions,\n        },\n        main_loop::{CloseLoop, InitLoop, OpenLoop},\n        plan::{\n            EvalAt, JoinOrderMember, JoinedTable, NonFromClauseSubquery, Operation,\n            QueryDestination, ResultSetColumn, Scan, Search, SelectPlan, SubqueryEvalPhase,\n            TableReferences, UpdatePlan,\n        },\n        planner::ROWID_STRS,\n        subquery::{emit_non_from_clause_subqueries_for_eval_at, emit_non_from_clause_subquery},\n        trigger_exec::{fire_trigger, get_relevant_triggers_type_and_time, TriggerContext},\n        ProgramBuilder,\n    },\n    util::normalize_ident,\n    vdbe::{\n        affinity::Affinity,\n        builder::{CursorKey, CursorType},\n        insn::{to_u16, CmpInsFlags, IdxInsertFlags, InsertFlags, Insn, RegisterOrLiteral},\n        BranchOffset,\n    },\n    CaptureDataChangesExt, Connection, HashSet, Result,\n};\nuse std::num::NonZeroUsize;\nuse tracing::{instrument, Level};\nuse turso_macros::{turso_assert, turso_assert_eq};\nuse turso_parser::ast::{ResolveType, TriggerEvent, TriggerTime};\n\n#[instrument(skip_all, level = Level::DEBUG)]\npub fn emit_program_for_update(\n    connection: &Arc<Connection>,\n    resolver: &Resolver,\n    program: &mut ProgramBuilder,\n    mut plan: UpdatePlan,\n    after: impl FnOnce(&mut ProgramBuilder),\n) -> Result<()> {\n    program.set_resolve_type(plan.or_conflict.unwrap_or(ResolveType::Abort));\n    program.has_statement_conflict = plan.or_conflict.is_some();\n\n    let mut t_ctx = TranslateCtx::new(\n        program,\n        resolver.fork(),\n        plan.table_references.joined_tables().len(),\n        connection.db.opts.unsafe_testing,\n    );\n\n    let after_main_loop_label = program.allocate_label();\n    t_ctx.label_main_loop_end = Some(after_main_loop_label);\n\n    // Open an ephemeral table for buffering RETURNING results.\n    // All DML completes before any RETURNING rows are yielded to the caller.\n    let returning_buffer = if plan.returning.as_ref().is_some_and(|r| !r.is_empty()) {\n        let table_ref = plan.table_references.joined_tables().first().unwrap();\n        let btree_table = table_ref\n            .table\n            .btree()\n            .expect(\"UPDATE target must be a BTree table\");\n        let ret_cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(btree_table));\n        program.emit_insn(Insn::OpenEphemeral {\n            cursor_id: ret_cursor_id,\n            is_table: true,\n        });\n        Some(ReturningBufferCtx {\n            cursor_id: ret_cursor_id,\n            num_columns: plan.returning.as_ref().unwrap().len(),\n        })\n    } else {\n        None\n    };\n\n    init_limit(program, &mut t_ctx, &plan.limit, &plan.offset)?;\n\n    // No rows will be read from source table loops if there is a constant false condition eg. WHERE 0\n    if plan.contains_constant_false_condition {\n        program.emit_insn(Insn::Goto {\n            target_pc: after_main_loop_label,\n        });\n    }\n\n    let ephemeral_plan = plan.ephemeral_plan.take();\n    let temp_cursor_id = ephemeral_plan.as_ref().map(|plan| {\n        let QueryDestination::EphemeralTable { cursor_id, .. } = &plan.query_destination else {\n            unreachable!()\n        };\n        *cursor_id\n    });\n    let has_ephemeral_table = ephemeral_plan.is_some();\n\n    let target_table = if let Some(ephemeral_plan) = ephemeral_plan {\n        let table = ephemeral_plan\n            .table_references\n            .joined_tables()\n            .first()\n            .unwrap()\n            .clone();\n        program.emit_insn(Insn::OpenEphemeral {\n            cursor_id: temp_cursor_id.unwrap(),\n            is_table: true,\n        });\n        program.nested(|program| emit_program_for_select(program, resolver, ephemeral_plan))?;\n        Arc::new(table)\n    } else {\n        Arc::new(\n            plan.table_references\n                .joined_tables()\n                .first()\n                .unwrap()\n                .clone(),\n        )\n    };\n\n    let mode = OperationMode::UPDATE(if has_ephemeral_table {\n        UpdateRowSource::PrebuiltEphemeralTable {\n            ephemeral_table_cursor_id: temp_cursor_id.expect(\n                \"ephemeral table cursor id is always allocated if has_ephemeral_table is true\",\n            ),\n            target_table: target_table.clone(),\n        }\n    } else {\n        UpdateRowSource::Normal\n    });\n\n    let join_order = plan\n        .table_references\n        .joined_tables()\n        .iter()\n        .enumerate()\n        .map(|(i, t)| JoinOrderMember {\n            table_id: t.internal_id,\n            original_idx: i,\n            is_outer: false,\n        })\n        .collect::<Vec<_>>();\n\n    // Evaluate uncorrelated subqueries as early as possible (only for normal path without ephemeral table).\n    // For the ephemeral path, WHERE clause subqueries are handled by emit_program_for_select\n    // on the ephemeral_plan. SET clause subqueries remain in the main plan and are emitted\n    // inside the update loop (after open_loop) where the write cursor is correctly positioned.\n    if !has_ephemeral_table {\n        emit_non_from_clause_subqueries_for_eval_at(\n            program,\n            &t_ctx.resolver,\n            &mut plan.non_from_clause_subqueries,\n            &join_order,\n            Some(&plan.table_references),\n            EvalAt::BeforeLoop,\n            |_| true,\n        )?;\n    }\n\n    // Drain write-phase subqueries so init_loop/open_loop only handle WHERE-clause\n    // subqueries. SET subqueries must run after NotExists positions the write cursor,\n    // and RETURNING subqueries must run after the row has been written.\n    // This applies to both the normal and ephemeral UPDATE paths.\n    let mut update_subqueries = Vec::new();\n    {\n        let mut i = 0;\n        while i < plan.non_from_clause_subqueries.len() {\n            let subquery = &plan.non_from_clause_subqueries[i];\n            if subquery.eval_phase == SubqueryEvalPhase::BeforeLoop {\n                i += 1;\n                continue;\n            }\n            if matches!(\n                subquery.eval_phase,\n                SubqueryEvalPhase::PreWrite | SubqueryEvalPhase::PostWriteReturning\n            ) {\n                update_subqueries.push(plan.non_from_clause_subqueries.remove(i));\n            } else {\n                i += 1;\n            }\n        }\n    }\n\n    // Initialize the main loop\n    InitLoop::emit(\n        program,\n        &mut t_ctx,\n        &plan.table_references,\n        &mut [],\n        &mode,\n        &plan.where_clause,\n        &join_order,\n        &mut plan.non_from_clause_subqueries,\n    )?;\n\n    // Prepare index cursors\n    // Use target_table.database_id because in the PrebuiltEphemeralTable case,\n    // plan.table_references contains the ephemeral table (database_id=0),\n    // not the actual target table.\n    let target_database_id = target_table.database_id;\n    let mut index_cursors = Vec::with_capacity(plan.indexes_to_update.len());\n    for index in &plan.indexes_to_update {\n        let index_cursor = if let Some(cursor) = program.resolve_cursor_id_safe(&CursorKey::index(\n            plan.table_references\n                .joined_tables()\n                .first()\n                .unwrap()\n                .internal_id,\n            index.clone(),\n        )) {\n            cursor\n        } else {\n            let cursor = program.alloc_cursor_index(None, index)?;\n            program.emit_insn(Insn::OpenWrite {\n                cursor_id: cursor,\n                root_page: RegisterOrLiteral::Literal(index.root_page),\n                db: target_database_id,\n            });\n            cursor\n        };\n        let record_reg = program.alloc_register();\n        index_cursors.push((index_cursor, record_reg));\n    }\n\n    // Emit EXPLAIN QUERY PLAN annotation (only for non-ephemeral path;\n    // ephemeral path already emits EQP via emit_program_for_select).\n    if !has_ephemeral_table {\n        let table_ref = plan\n            .table_references\n            .joined_tables()\n            .first()\n            .expect(\"UPDATE must have a joined table\");\n        emit_explain!(program, true, format_eqp_detail(table_ref));\n    }\n\n    // Open the main loop\n    OpenLoop::emit(\n        program,\n        &mut t_ctx,\n        &plan.table_references,\n        &join_order,\n        &plan.where_clause,\n        temp_cursor_id,\n        mode.clone(),\n        &mut plan.non_from_clause_subqueries,\n    )?;\n\n    let target_table_cursor_id =\n        program.resolve_cursor_id(&CursorKey::table(target_table.internal_id));\n\n    let iteration_cursor_id = if has_ephemeral_table {\n        temp_cursor_id.unwrap()\n    } else {\n        target_table_cursor_id\n    };\n\n    // When any conflict resolution path may use REPLACE, we need cursors on ALL\n    // indexes — deleting a conflicting row requires removing its entries from every\n    // index, not just the ones touched by SET clauses.\n    //\n    // REPLACE can come from the statement (UPDATE OR REPLACE), the PK DDL\n    // (INTEGER PRIMARY KEY ON CONFLICT REPLACE), or a unique index DDL\n    // (UNIQUE ON CONFLICT REPLACE). Only indexes whose columns are being\n    // updated can trigger a conflict, so we only check indexes_to_update.\n    // Only consider PK REPLACE when the UPDATE actually changes the rowid,\n    // since PK REPLACE can only fire on rowid collisions.\n    let updates_rowid = {\n        let has_direct_rowid = plan\n            .set_clauses\n            .iter()\n            .any(|(idx, _)| *idx == ROWID_SENTINEL);\n        let has_alias_rowid = target_table\n            .table\n            .columns()\n            .iter()\n            .position(|c| c.is_rowid_alias())\n            .is_some_and(|alias_idx| plan.set_clauses.iter().any(|(idx, _)| *idx == alias_idx));\n        has_direct_rowid || has_alias_rowid\n    };\n    let rowid_alias_conflict = if updates_rowid {\n        target_table\n            .table\n            .btree()\n            .and_then(|bt| bt.rowid_alias_conflict_clause)\n    } else {\n        None\n    };\n    let any_replace = any_effective_replace(\n        program.has_statement_conflict,\n        program.resolve_type,\n        rowid_alias_conflict,\n        plan.indexes_to_update.iter().map(|idx| idx.on_conflict),\n    );\n    let all_index_cursors = if any_replace {\n        let table_name = target_table.table.get_name();\n        let all_indexes: Vec<_> = resolver.with_schema(target_database_id, |s| {\n            s.get_indices(table_name).cloned().collect()\n        });\n        let source_table = plan\n            .table_references\n            .joined_tables()\n            .first()\n            .expect(\"UPDATE must have a joined table\");\n        let internal_id = source_table.internal_id;\n\n        // Determine which index (if any) is being used for iteration\n        // We need to reuse that cursor to avoid corruption when deleting from it\n        let iteration_index_name = match &source_table.op {\n            Operation::Scan(Scan::BTreeTable { index, .. }) => index.as_ref().map(|i| &i.name),\n            Operation::Search(Search::Seek {\n                index: Some(index), ..\n            }) => Some(&index.name),\n            _ => None,\n        };\n\n        all_indexes\n            .into_iter()\n            .map(|index| {\n                // Check if this index already has a cursor opened (from indexes_to_update)\n                let existing_cursor = plan\n                    .indexes_to_update\n                    .iter()\n                    .zip(&index_cursors)\n                    .find(|(idx, _)| idx.name == index.name)\n                    .map(|(_, (cursor_id, _))| *cursor_id);\n\n                let cursor = if let Some(cursor) = existing_cursor {\n                    cursor\n                } else if iteration_index_name == Some(&index.name) {\n                    // This index is being used for iteration - reuse that cursor\n                    program.resolve_cursor_id(&CursorKey::index(internal_id, index.clone()))\n                } else {\n                    // This index is not in indexes_to_update and not used for iteration\n                    // Open a new cursor\n                    let cursor = program\n                        .alloc_cursor_index(None, &index)\n                        .expect(\"to allocate index cursor\");\n                    program.emit_insn(Insn::OpenWrite {\n                        cursor_id: cursor,\n                        root_page: RegisterOrLiteral::Literal(index.root_page),\n                        db: target_database_id,\n                    });\n                    cursor\n                };\n                (index, cursor)\n            })\n            .collect::<Vec<(Arc<Index>, usize)>>()\n    } else {\n        Vec::new()\n    };\n\n    // Emit update instructions\n    emit_update_insns(\n        connection,\n        &mut plan.table_references,\n        &plan.set_clauses,\n        plan.cdc_update_alter_statement.as_deref(),\n        &plan.indexes_to_update,\n        plan.returning.as_ref(),\n        plan.ephemeral_plan.as_ref(),\n        &mut t_ctx,\n        program,\n        &index_cursors,\n        &all_index_cursors,\n        iteration_cursor_id,\n        target_table_cursor_id,\n        target_table,\n        resolver,\n        returning_buffer.as_ref(),\n        &mut update_subqueries,\n    )?;\n\n    // Close the main loop\n    CloseLoop::emit(\n        program,\n        &mut t_ctx,\n        &plan.table_references,\n        &join_order,\n        mode,\n        None,\n    )?;\n\n    program.preassign_label_to_next_insn(after_main_loop_label);\n    if let Some(cdc_cursor_id) = t_ctx.cdc_cursor_id {\n        emit_cdc_autocommit_commit(program, resolver, cdc_cursor_id)?;\n    }\n    // Emit scan-back loop for buffered RETURNING results.\n    // All DML is complete at this point; now yield the buffered rows to the caller.\n    // FkCheck must come before the scan-back so that FK violations prevent\n    // RETURNING rows from being emitted (matching SQLite behavior).\n    if let Some(ref buf) = returning_buffer {\n        program.emit_insn(Insn::FkCheck { deferred: false });\n        emit_returning_scan_back(program, buf);\n    }\n    after(program);\n\n    program.result_columns = plan.returning.unwrap_or_default();\n    program.table_references.extend(plan.table_references);\n    Ok(())\n}\n\n/// Helper function to evaluate SET expressions and read column values for UPDATE.\n/// This is invoked once for every UPDATE, but will be invoked again if there are\n/// any BEFORE UPDATE triggers that fired, because the triggers may have modified the row,\n/// in which case the previously read values are stale.\n#[allow(clippy::too_many_arguments)]\nfn emit_update_column_values<'a>(\n    program: &mut ProgramBuilder,\n    table_references: &mut TableReferences,\n    set_clauses: &[(usize, Box<ast::Expr>)],\n    cdc_update_alter_statement: Option<&str>,\n    target_table: &Arc<JoinedTable>,\n    target_table_cursor_id: usize,\n    start: usize,\n    col_len: usize,\n    table_name: &str,\n    has_direct_rowid_update: bool,\n    has_user_provided_rowid: bool,\n    rowid_set_clause_reg: Option<usize>,\n    is_virtual: bool,\n    index: &Option<(Arc<Index>, usize)>,\n    cdc_updates_register: Option<usize>,\n    t_ctx: &mut TranslateCtx<'a>,\n    skip_set_clauses: bool,\n    skip_row_label: BranchOffset,\n    skip_notnull_checks: bool,\n) -> crate::Result<()> {\n    let or_conflict = program.resolve_type;\n    if has_direct_rowid_update {\n        if let Some((_, expr)) = set_clauses.iter().find(|(i, _)| *i == ROWID_SENTINEL) {\n            if !skip_set_clauses {\n                let rowid_set_clause_reg = rowid_set_clause_reg.unwrap();\n                translate_expr(\n                    program,\n                    Some(table_references),\n                    expr,\n                    rowid_set_clause_reg,\n                    &t_ctx.resolver,\n                )?;\n                program.emit_insn(Insn::MustBeInt {\n                    reg: rowid_set_clause_reg,\n                });\n            }\n        }\n    }\n    for (idx, table_column) in target_table.table.columns().iter().enumerate() {\n        let target_reg = start + idx;\n        if let Some((col_idx, expr)) = set_clauses.iter().find(|(i, _)| *i == idx) {\n            if !skip_set_clauses {\n                // Skip if this is the sentinel value\n                if *col_idx == ROWID_SENTINEL {\n                    continue;\n                }\n                if has_user_provided_rowid\n                    && (table_column.primary_key() || table_column.is_rowid_alias())\n                    && !is_virtual\n                {\n                    let rowid_set_clause_reg = rowid_set_clause_reg.unwrap();\n                    translate_expr(\n                        program,\n                        Some(table_references),\n                        expr,\n                        rowid_set_clause_reg,\n                        &t_ctx.resolver,\n                    )?;\n\n                    program.emit_insn(Insn::MustBeInt {\n                        reg: rowid_set_clause_reg,\n                    });\n\n                    program.emit_null(target_reg, None);\n                } else {\n                    // Columns with custom type encode must not have their\n                    // SET expressions hoisted as constants. See the doc\n                    // comment on NoConstantOptReason::CustomTypeEncode.\n                    let has_custom_encode = {\n                        let ty = &table_column.ty_str;\n                        !ty.is_empty()\n                            && t_ctx\n                                .resolver\n                                .schema\n                                .get_type_def_unchecked(ty)\n                                .is_some_and(|td| td.encode.is_some())\n                    };\n                    if has_custom_encode {\n                        translate_expr_no_constant_opt(\n                            program,\n                            Some(table_references),\n                            expr,\n                            target_reg,\n                            &t_ctx.resolver,\n                            NoConstantOptReason::CustomTypeEncode,\n                        )?;\n                    } else {\n                        translate_expr(\n                            program,\n                            Some(table_references),\n                            expr,\n                            target_reg,\n                            &t_ctx.resolver,\n                        )?;\n                    }\n                    if table_column.notnull() && !skip_notnull_checks {\n                        let notnull_conflict = if program.has_statement_conflict {\n                            or_conflict\n                        } else {\n                            table_column\n                                .notnull_conflict_clause\n                                .unwrap_or(ResolveType::Abort)\n                        };\n                        match notnull_conflict {\n                            ResolveType::Ignore => {\n                                // For IGNORE, skip this row on NOT NULL violation\n                                program.emit_insn(Insn::IsNull {\n                                    reg: target_reg,\n                                    target_pc: skip_row_label,\n                                });\n                            }\n                            ResolveType::Replace => {\n                                // For REPLACE with NOT NULL, use default value if available\n                                if let Some(default_expr) = table_column.default.as_ref() {\n                                    let continue_label = program.allocate_label();\n\n                                    // If not null, skip to continue\n                                    program.emit_insn(Insn::NotNull {\n                                        reg: target_reg,\n                                        target_pc: continue_label,\n                                    });\n\n                                    // Value is null, use default.\n                                    translate_expr_no_constant_opt(\n                                        program,\n                                        Some(table_references),\n                                        default_expr,\n                                        target_reg,\n                                        &t_ctx.resolver,\n                                        NoConstantOptReason::RegisterReuse,\n                                    )?;\n\n                                    program.preassign_label_to_next_insn(continue_label);\n                                } else {\n                                    // No default value, fall through to ABORT behavior\n                                    use crate::error::SQLITE_CONSTRAINT_NOTNULL;\n                                    program.emit_insn(Insn::HaltIfNull {\n                                        target_reg,\n                                        err_code: SQLITE_CONSTRAINT_NOTNULL,\n                                        description: format!(\n                                            \"{}.{}\",\n                                            table_name,\n                                            table_column\n                                                .name\n                                                .as_ref()\n                                                .expect(\"Column name must be present\")\n                                        ),\n                                    });\n                                }\n                            }\n                            _ => {\n                                // Default ABORT behavior\n                                use crate::error::SQLITE_CONSTRAINT_NOTNULL;\n                                program.emit_insn(Insn::HaltIfNull {\n                                    target_reg,\n                                    err_code: SQLITE_CONSTRAINT_NOTNULL,\n                                    description: format!(\n                                        \"{}.{}\",\n                                        table_name,\n                                        table_column\n                                            .name\n                                            .as_ref()\n                                            .expect(\"Column name must be present\")\n                                    ),\n                                });\n                            }\n                        }\n                    }\n                }\n\n                if let Some(cdc_updates_register) = cdc_updates_register {\n                    let change_reg = cdc_updates_register + idx;\n                    let value_reg = cdc_updates_register + col_len + idx;\n                    program.emit_bool(true, change_reg);\n                    program.mark_last_insn_constant();\n                    let mut updated = false;\n                    if let Some(ddl_query_for_cdc_update) = cdc_update_alter_statement {\n                        if table_column.name.as_deref() == Some(\"sql\") {\n                            program.emit_string8(ddl_query_for_cdc_update.to_string(), value_reg);\n                            updated = true;\n                        }\n                    }\n                    if !updated {\n                        program.emit_insn(Insn::Copy {\n                            src_reg: target_reg,\n                            dst_reg: value_reg,\n                            extra_amount: 0,\n                        });\n                    }\n                }\n            }\n        } else {\n            // Column is not being updated, read it from the table\n            let column_idx_in_index = index.as_ref().and_then(|(idx, _)| {\n                idx.columns.iter().position(|c| {\n                    table_column\n                        .name\n                        .as_ref()\n                        .is_some_and(|tc_name| c.name.eq_ignore_ascii_case(tc_name))\n                })\n            });\n\n            // don't emit null for pkey of virtual tables. they require first two args\n            // before the 'record' to be explicitly non-null\n            if table_column.is_rowid_alias() && !is_virtual {\n                program.emit_null(target_reg, None);\n            } else if is_virtual {\n                program.emit_insn(Insn::VColumn {\n                    cursor_id: target_table_cursor_id,\n                    column: idx,\n                    dest: target_reg,\n                });\n            } else {\n                let cursor_id = *index\n                    .as_ref()\n                    .and_then(|(_, id)| {\n                        if column_idx_in_index.is_some() {\n                            Some(id)\n                        } else {\n                            None\n                        }\n                    })\n                    .unwrap_or(&target_table_cursor_id);\n                program.emit_column_or_rowid(\n                    cursor_id,\n                    column_idx_in_index.unwrap_or(idx),\n                    target_reg,\n                );\n            }\n\n            if let Some(cdc_updates_register) = cdc_updates_register {\n                let change_bit_reg = cdc_updates_register + idx;\n                let value_reg = cdc_updates_register + col_len + idx;\n                program.emit_bool(false, change_bit_reg);\n                program.mark_last_insn_constant();\n                program.emit_null(value_reg, None);\n                program.mark_last_insn_constant();\n            }\n        }\n    }\n    Ok(())\n}\n\n/// Emit NOT NULL constraint checks for SET clause columns after BEFORE triggers have fired.\n/// This is deferred from the first `emit_update_column_values` call so that triggers\n/// run before constraint checks, matching SQLite's behavior.\n#[allow(clippy::too_many_arguments)]\nfn emit_deferred_notnull_checks<'a>(\n    program: &mut ProgramBuilder,\n    table_references: &mut TableReferences,\n    target_table: &Arc<JoinedTable>,\n    set_clauses: &[(usize, Box<ast::Expr>)],\n    start: usize,\n    table_name: &str,\n    skip_row_label: BranchOffset,\n    t_ctx: &mut TranslateCtx<'a>,\n) -> crate::Result<()> {\n    let or_conflict = program.resolve_type;\n    for (idx, table_column) in target_table.table.columns().iter().enumerate() {\n        if !table_column.notnull() {\n            continue;\n        }\n        // Only check columns that are in SET clauses\n        if !set_clauses.iter().any(|(i, _)| *i == idx) {\n            continue;\n        }\n        let target_reg = start + idx;\n        match or_conflict {\n            ResolveType::Ignore => {\n                program.emit_insn(Insn::IsNull {\n                    reg: target_reg,\n                    target_pc: skip_row_label,\n                });\n            }\n            ResolveType::Replace => {\n                if let Some(default_expr) = table_column.default.as_ref() {\n                    let continue_label = program.allocate_label();\n                    program.emit_insn(Insn::NotNull {\n                        reg: target_reg,\n                        target_pc: continue_label,\n                    });\n                    translate_expr_no_constant_opt(\n                        program,\n                        Some(table_references),\n                        default_expr,\n                        target_reg,\n                        &t_ctx.resolver,\n                        NoConstantOptReason::RegisterReuse,\n                    )?;\n                    program.preassign_label_to_next_insn(continue_label);\n                } else {\n                    use crate::error::SQLITE_CONSTRAINT_NOTNULL;\n                    program.emit_insn(Insn::HaltIfNull {\n                        target_reg,\n                        err_code: SQLITE_CONSTRAINT_NOTNULL,\n                        description: format!(\n                            \"{}.{}\",\n                            table_name,\n                            table_column\n                                .name\n                                .as_ref()\n                                .expect(\"Column name must be present\")\n                        ),\n                    });\n                }\n            }\n            _ => {\n                use crate::error::SQLITE_CONSTRAINT_NOTNULL;\n                program.emit_insn(Insn::HaltIfNull {\n                    target_reg,\n                    err_code: SQLITE_CONSTRAINT_NOTNULL,\n                    description: format!(\n                        \"{}.{}\",\n                        table_name,\n                        table_column\n                            .name\n                            .as_ref()\n                            .expect(\"Column name must be present\")\n                    ),\n                });\n            }\n        }\n    }\n    Ok(())\n}\n\n#[instrument(skip_all, level = Level::DEBUG)]\n#[allow(clippy::too_many_arguments)]\n/// Emits the instructions for the UPDATE loop.\n///\n/// `iteration_cursor_id` is the cursor id of the table that is being iterated over. This can be either the table itself, an index, or an ephemeral table (see [crate::translate::plan::UpdatePlan]).\n///\n/// `target_table_cursor_id` is the cursor id of the table that is being updated.\n///\n/// `target_table` is the table that is being updated.\n///\n/// `or_conflict` specifies the conflict resolution strategy (IGNORE, REPLACE, ABORT).\n///\n/// `all_index_cursors` contains cursors for ALL indexes on the table (used for REPLACE to delete\n/// conflicting rows from all indexes, not just those being updated).\nfn emit_update_insns<'a>(\n    connection: &Arc<Connection>,\n    table_references: &mut TableReferences,\n    set_clauses: &[(usize, Box<ast::Expr>)],\n    cdc_update_alter_statement: Option<&str>,\n    indexes_to_update: &[Arc<Index>],\n    returning: Option<&'a Vec<ResultSetColumn>>,\n    ephemeral_plan: Option<&SelectPlan>,\n    t_ctx: &mut TranslateCtx<'a>,\n    program: &mut ProgramBuilder,\n    index_cursors: &[(usize, usize)],\n    all_index_cursors: &[(Arc<Index>, usize)],\n    iteration_cursor_id: usize,\n    target_table_cursor_id: usize,\n    target_table: Arc<JoinedTable>,\n    resolver: &Resolver,\n    returning_buffer: Option<&ReturningBufferCtx>,\n    non_from_clause_subqueries: &mut [NonFromClauseSubquery],\n) -> crate::Result<()> {\n    let or_conflict = program.resolve_type;\n    let internal_id = target_table.internal_id;\n    // Copy loop labels early to avoid borrow conflicts with mutable t_ctx borrow later\n    let loop_labels = *t_ctx\n        .labels_main_loop\n        .first()\n        .expect(\"loop labels to exist\");\n    // Label to skip to the next row on conflict (for IGNORE mode)\n    let skip_row_label = loop_labels.next;\n    let source_table = table_references\n        .joined_tables()\n        .first()\n        .expect(\"UPDATE must have a source table\");\n    let (index, is_virtual) = match &source_table.op {\n        Operation::Scan(Scan::BTreeTable { index, .. }) => (\n            index.as_ref().map(|index| {\n                (\n                    index.clone(),\n                    program.resolve_cursor_id(&CursorKey::index(internal_id, index.clone())),\n                )\n            }),\n            false,\n        ),\n        Operation::Scan(_) => (None, target_table.virtual_table().is_some()),\n        Operation::Search(search) => match search {\n            &Search::RowidEq { .. }\n            | Search::Seek { index: None, .. }\n            | Search::InSeek { index: None, .. } => (None, false),\n            Search::Seek {\n                index: Some(index), ..\n            }\n            | Search::InSeek {\n                index: Some(index), ..\n            } => (\n                Some((\n                    index.clone(),\n                    program.resolve_cursor_id(&CursorKey::index(internal_id, index.clone())),\n                )),\n                false,\n            ),\n        },\n        Operation::IndexMethodQuery(_) => {\n            // IndexMethodQuery indexes (e.g. FTS) don't store original column values\n            // like B-tree indexes do, so we must read unchanged columns from the table cursor.\n            (None, false)\n        }\n        Operation::HashJoin(_) => {\n            unreachable!(\"access through HashJoin is not supported for update operations\")\n        }\n        Operation::MultiIndexScan(_) => {\n            unreachable!(\"access through MultiIndexScan is not supported for update operations\")\n        }\n    };\n\n    let beg = program.alloc_registers(\n        target_table.table.columns().len()\n            + if is_virtual {\n                2 // two args before the relevant columns for VUpdate\n            } else {\n                1 // rowid reg\n            },\n    );\n    program.emit_insn(Insn::RowId {\n        cursor_id: iteration_cursor_id,\n        dest: beg,\n    });\n\n    // Check if rowid was provided (through INTEGER PRIMARY KEY as a rowid alias)\n    let rowid_alias_index = target_table\n        .table\n        .columns()\n        .iter()\n        .position(|c| c.is_rowid_alias());\n\n    let has_direct_rowid_update = set_clauses.iter().any(|(idx, _)| *idx == ROWID_SENTINEL);\n\n    let has_user_provided_rowid = if let Some(index) = rowid_alias_index {\n        set_clauses.iter().any(|(idx, _)| *idx == index)\n    } else {\n        has_direct_rowid_update\n    };\n\n    let rowid_set_clause_reg = if has_user_provided_rowid {\n        Some(program.alloc_register())\n    } else {\n        None\n    };\n\n    turso_assert!(\n        !has_user_provided_rowid || rowid_set_clause_reg.is_some(),\n        \"has_user_provided_rowid requires rowid_set_clause_reg\"\n    );\n\n    // Effective INTEGER PK conflict resolution: statement-level OR clause takes precedence;\n    // otherwise use the constraint-level rowid_alias_conflict_clause from the table DDL.\n    let constraint_rowid_alias_conflict = target_table\n        .table\n        .btree()\n        .and_then(|bt| bt.rowid_alias_conflict_clause);\n    let effective_rowid_alias_conflict = if program.has_statement_conflict {\n        or_conflict\n    } else {\n        constraint_rowid_alias_conflict.unwrap_or(ResolveType::Abort)\n    };\n\n    let not_exists_check_required =\n        has_user_provided_rowid || iteration_cursor_id != target_table_cursor_id;\n\n    let check_rowid_not_exists_label = if not_exists_check_required {\n        Some(program.allocate_label())\n    } else {\n        None\n    };\n\n    // Label for RAISE(IGNORE) to skip the current row during UPDATE triggers\n    let trigger_ignore_jump_label = program.allocate_label();\n\n    if not_exists_check_required {\n        program.emit_insn(Insn::NotExists {\n            cursor: target_table_cursor_id,\n            rowid_reg: beg,\n            target_pc: check_rowid_not_exists_label.unwrap(),\n        });\n    } else {\n        // if no rowid, we're done\n        program.emit_insn(Insn::IsNull {\n            reg: beg,\n            target_pc: t_ctx.label_main_loop_end.unwrap(),\n        });\n    }\n\n    // Emit remaining SET clause subqueries inside the loop, after the write cursor\n    // is positioned via NotExists. In the ephemeral path, these subqueries were kept\n    // in the main plan (not moved to the ephemeral plan) and need the write cursor\n    // to be positioned so correlated references resolve correctly.\n    // RETURNING subqueries are skipped here and emitted after Insert so that\n    // correlated column references read post-UPDATE values from the cursor.\n    for subquery in non_from_clause_subqueries\n        .iter_mut()\n        .filter(|s| !s.has_been_evaluated() && !s.is_post_write_returning())\n    {\n        let subquery_plan = subquery.consume_plan(EvalAt::Loop(0));\n        emit_non_from_clause_subquery(\n            program,\n            &t_ctx.resolver,\n            *subquery_plan,\n            &subquery.query_type,\n            subquery.correlated,\n            false,\n        )?;\n    }\n\n    if is_virtual {\n        program.emit_insn(Insn::Copy {\n            src_reg: beg,\n            dst_reg: beg + 1,\n            extra_amount: 0,\n        })\n    }\n\n    if let Some(offset) = t_ctx.reg_offset {\n        program.emit_insn(Insn::IfPos {\n            reg: offset,\n            target_pc: loop_labels.next,\n            decrement_by: 1,\n        });\n    }\n    let col_len = target_table.table.columns().len();\n\n    // we scan a column at a time, loading either the column's values, or the new value\n    // from the Set expression, into registers so we can emit a MakeRecord and update the row.\n\n    // we allocate 2C registers for \"updates\" as the structure of this column for CDC table is following:\n    // [C boolean values where true set for changed columns] [C values with updates where NULL is set for not-changed columns]\n    let cdc_updates_register = if program.capture_data_changes_info().has_updates() {\n        Some(program.alloc_registers(2 * col_len))\n    } else {\n        None\n    };\n    let table_name = target_table.table.get_name();\n\n    let start = if is_virtual { beg + 2 } else { beg + 1 };\n\n    let skip_set_clauses = false;\n\n    // Check early whether BEFORE UPDATE triggers exist, so we can defer NOT NULL\n    // constraint checks until after the triggers fire (matching SQLite behavior).\n    let update_database_id = target_table.database_id;\n    let has_before_triggers_early = if let Some(btree_table) = target_table.table.btree() {\n        let updated_column_indices: HashSet<usize> =\n            set_clauses.iter().map(|(col_idx, _)| *col_idx).collect();\n        t_ctx.resolver.with_schema(update_database_id, |s| {\n            get_relevant_triggers_type_and_time(\n                s,\n                TriggerEvent::Update,\n                TriggerTime::Before,\n                Some(updated_column_indices),\n                &btree_table,\n            )\n            .next()\n            .is_some()\n        })\n    } else {\n        false\n    };\n\n    emit_update_column_values(\n        program,\n        table_references,\n        set_clauses,\n        cdc_update_alter_statement,\n        &target_table,\n        target_table_cursor_id,\n        start,\n        col_len,\n        table_name,\n        has_direct_rowid_update,\n        has_user_provided_rowid,\n        rowid_set_clause_reg,\n        is_virtual,\n        &index,\n        cdc_updates_register,\n        t_ctx,\n        skip_set_clauses,\n        skip_row_label,\n        has_before_triggers_early,\n    )?;\n\n    // For non-STRICT tables, apply column affinity to the NEW values early.\n    // This must happen before index operations and triggers so that all operations\n    // use the converted values.\n    if let Some(btree_table) = target_table.table.btree() {\n        if !btree_table.is_strict {\n            let affinity = btree_table.columns.iter().map(|c| c.affinity());\n\n            // Only emit Affinity if there's meaningful affinity to apply\n            if affinity.clone().any(|a| a != Affinity::Blob) {\n                if let Ok(count) = std::num::NonZeroUsize::try_from(col_len) {\n                    program.emit_insn(Insn::Affinity {\n                        start_reg: start,\n                        count,\n                        affinities: affinity.map(|a| a.aff_mask()).collect(),\n                    });\n                }\n            }\n        }\n    }\n\n    // Fire BEFORE UPDATE triggers and preserve old_registers for AFTER triggers\n    let mut has_before_triggers = false;\n    let preserved_old_registers: Option<Vec<usize>> = if let Some(btree_table) =\n        target_table.table.btree()\n    {\n        let updated_column_indices: HashSet<usize> =\n            set_clauses.iter().map(|(col_idx, _)| *col_idx).collect();\n        let relevant_before_update_triggers: Vec<_> =\n            t_ctx.resolver.with_schema(update_database_id, |s| {\n                get_relevant_triggers_type_and_time(\n                    s,\n                    TriggerEvent::Update,\n                    TriggerTime::Before,\n                    Some(updated_column_indices.clone()),\n                    &btree_table,\n                )\n                .collect()\n            });\n\n        let has_after_triggers = t_ctx.resolver.with_schema(update_database_id, |s| {\n            get_relevant_triggers_type_and_time(\n                s,\n                TriggerEvent::Update,\n                TriggerTime::After,\n                Some(updated_column_indices.clone()),\n                &btree_table,\n            )\n            .count()\n                > 0\n        });\n\n        let has_fk_cascade = connection.foreign_keys_enabled()\n            && t_ctx.resolver.with_schema(update_database_id, |s| {\n                s.any_resolved_fks_referencing(table_name)\n            });\n\n        has_before_triggers = !relevant_before_update_triggers.is_empty();\n        let needs_old_registers = has_before_triggers || has_after_triggers || has_fk_cascade;\n\n        // Only read OLD row values when triggers or FK cascades need them\n        let old_registers: Option<Vec<usize>> = if needs_old_registers {\n            Some(\n                (0..col_len)\n                    .map(|i| {\n                        let reg = program.alloc_register();\n                        program.emit_column_or_rowid(target_table_cursor_id, i, reg);\n                        reg\n                    })\n                    .chain(std::iter::once(beg))\n                    .collect(),\n            )\n        } else {\n            None\n        };\n\n        if has_before_triggers {\n            let old_registers =\n                old_registers.expect(\"old_registers allocated when has_before_triggers\");\n            // NEW row values are already in 'start' registers.\n            // If the rowid is being updated (INTEGER PRIMARY KEY in SET clause),\n            // use the new rowid register; otherwise use the current rowid (beg).\n            let new_rowid_reg = rowid_set_clause_reg.unwrap_or(beg);\n            let new_registers = (0..col_len)\n                .map(|i| start + i)\n                .chain(std::iter::once(new_rowid_reg))\n                .collect();\n\n            // Propagate conflict resolution to trigger context:\n            // 1. UPSERT DO UPDATE override takes precedence\n            // 2. Outer UPDATE's explicit ON CONFLICT overrides trigger body\n            // 3. Otherwise, use trigger's own conflict resolution\n            let trigger_ctx = if let Some(override_conflict) = program.trigger_conflict_override {\n                TriggerContext::new_with_override_conflict(\n                    btree_table,\n                    Some(new_registers),\n                    Some(old_registers.clone()), // Clone for AFTER trigger\n                    override_conflict,\n                )\n            } else if !matches!(or_conflict, ResolveType::Abort) {\n                TriggerContext::new_with_override_conflict(\n                    btree_table,\n                    Some(new_registers),\n                    Some(old_registers.clone()),\n                    or_conflict,\n                )\n            } else {\n                TriggerContext::new(\n                    btree_table,\n                    Some(new_registers),\n                    Some(old_registers.clone()), // Clone for AFTER trigger\n                )\n            };\n\n            for trigger in relevant_before_update_triggers {\n                fire_trigger(\n                    program,\n                    &mut t_ctx.resolver,\n                    trigger,\n                    &trigger_ctx,\n                    connection,\n                    update_database_id,\n                    trigger_ignore_jump_label,\n                )?;\n            }\n\n            // BEFORE UPDATE Triggers may have altered the btree so we need to seek again.\n            program.emit_insn(Insn::NotExists {\n                cursor: target_table_cursor_id,\n                rowid_reg: beg,\n                target_pc: check_rowid_not_exists_label.expect(\n                    \"check_rowid_not_exists_label must be set if there are BEFORE UPDATE triggers\",\n                ),\n            });\n\n            if has_after_triggers {\n                // Preserve pseudo-row 'OLD' for AFTER triggers by copying to new registers\n                // (since registers might be overwritten during trigger execution)\n                let preserved: Vec<usize> = old_registers\n                    .iter()\n                    .map(|old_reg| {\n                        let preserved_reg = program.alloc_register();\n                        program.emit_insn(Insn::Copy {\n                            src_reg: *old_reg,\n                            dst_reg: preserved_reg,\n                            extra_amount: 0,\n                        });\n                        preserved_reg\n                    })\n                    .collect();\n                Some(preserved)\n            } else {\n                Some(old_registers)\n            }\n        } else {\n            // No BEFORE triggers — pass through whatever old_registers we have\n            old_registers\n        }\n    } else {\n        None\n    };\n\n    // If BEFORE UPDATE triggers fired, they may have modified the row being updated.\n    // According to the SQLite documentation, the behavior in these cases is undefined:\n    // https://sqlite.org/lang_createtrigger.html\n    // However, based on fuzz testing and observations, the logic seems to be:\n    // The values that are NOT referred to in SET clauses will be evaluated again,\n    // and values in SET clauses are evaluated using the old values.\n    // sqlite> create table t(c0,c1,c2);\n    // sqlite> create trigger tu before update on t begin update t set c1=666, c2=666; end;\n    // sqlite> insert into t values (1,1,1);\n    // sqlite> update t set c0 = c1+1;\n    // sqlite> select * from t;\n    // 2|666|666\n    if target_table.table.btree().is_some() && has_before_triggers {\n        let skip_set_clauses = true;\n        // Re-read non-SET columns (triggers may have changed them).\n        // NOT NULL checks are NOT skipped here — they cover non-SET columns.\n        emit_update_column_values(\n            program,\n            table_references,\n            set_clauses,\n            cdc_update_alter_statement,\n            &target_table,\n            target_table_cursor_id,\n            start,\n            col_len,\n            table_name,\n            has_direct_rowid_update,\n            has_user_provided_rowid,\n            rowid_set_clause_reg,\n            is_virtual,\n            &index,\n            cdc_updates_register,\n            t_ctx,\n            skip_set_clauses,\n            skip_row_label,\n            false,\n        )?;\n\n        // Now emit NOT NULL checks for SET clause columns that were deferred\n        // from the first emit_update_column_values call. In SQLite, NOT NULL\n        // constraint checks happen after BEFORE triggers fire.\n        emit_deferred_notnull_checks(\n            program,\n            table_references,\n            &target_table,\n            set_clauses,\n            start,\n            table_name,\n            skip_row_label,\n            t_ctx,\n        )?;\n    }\n\n    if connection.foreign_keys_enabled() {\n        let rowid_new_reg = rowid_set_clause_reg.unwrap_or(beg);\n        if let Some(table_btree) = target_table.table.btree() {\n            stabilize_new_row_for_fk(\n                program,\n                &table_btree,\n                set_clauses,\n                target_table_cursor_id,\n                start,\n                rowid_new_reg,\n            )?;\n            // Child-side FK checks are deferred to AFTER custom type encoding (see below).\n            // This is because child FK checks probe the parent's index which contains\n            // encoded values, so the NEW values must also be encoded.\n\n            // Parent-side NO ACTION/RESTRICT checks must happen BEFORE the update.\n            // This checks that no child rows reference the old parent key values.\n            // CASCADE/SET NULL actions are fired AFTER the update (see below after Insert).\n            if t_ctx.resolver.with_schema(update_database_id, |s| {\n                s.any_resolved_fks_referencing(table_name)\n            }) {\n                emit_fk_update_parent_actions(\n                    program,\n                    &table_btree,\n                    indexes_to_update.iter(),\n                    target_table_cursor_id,\n                    beg,\n                    start,\n                    rowid_new_reg,\n                    rowid_set_clause_reg,\n                    set_clauses,\n                    update_database_id,\n                    &t_ctx.resolver,\n                )?;\n            }\n        }\n    }\n\n    // Populate register-to-affinity map for expression index evaluation.\n    // When column references are rewritten to Expr::Register during UPDATE, comparison\n    // operators need the original column affinity. This is set once here and cleared at\n    // the end of the function.\n    {\n        let rowid_reg = rowid_set_clause_reg.unwrap_or(beg);\n        for (idx, col) in target_table.table.columns().iter().enumerate() {\n            t_ctx\n                .resolver\n                .register_affinities\n                .insert(start + idx, col.affinity());\n        }\n        t_ctx\n            .resolver\n            .register_affinities\n            .insert(rowid_reg, Affinity::Integer);\n    }\n    let target_is_strict = target_table\n        .table\n        .btree()\n        .is_some_and(|btree| btree.is_strict);\n\n    // Non-REPLACE PK constraint check. Must run BEFORE the index preflight so that\n    // PK ABORT/FAIL/ROLLBACK fires before an index IGNORE can silently skip the row.\n    // SQLite checks PK constraints before index constraints in the UPDATE path.\n    if target_table.table.btree().is_some()\n        && has_user_provided_rowid\n        && !matches!(effective_rowid_alias_conflict, ResolveType::Replace)\n    {\n        let record_label = program.allocate_label();\n        let target_reg = rowid_set_clause_reg.unwrap();\n\n        // If the new rowid equals the old rowid, no conflict\n        program.emit_insn(Insn::Eq {\n            lhs: target_reg,\n            rhs: beg,\n            target_pc: record_label,\n            flags: CmpInsFlags::default(),\n            collation: program.curr_collation(),\n        });\n\n        // If a row with the new rowid doesn't exist, no conflict\n        program.emit_insn(Insn::NotExists {\n            cursor: target_table_cursor_id,\n            rowid_reg: target_reg,\n            target_pc: record_label,\n        });\n\n        // Handle conflict resolution for rowid/primary key conflict.\n        // Replace is excluded by the outer guard; only Ignore/Abort/Fail/Rollback reach here.\n        match effective_rowid_alias_conflict {\n            ResolveType::Ignore => {\n                // For IGNORE, skip this row's update but continue with other rows\n                program.emit_insn(Insn::Goto {\n                    target_pc: skip_row_label,\n                });\n            }\n            _ => {\n                // ABORT/FAIL/ROLLBACK behavior\n                let raw_desc = if let Some(idx) = rowid_alias_index {\n                    String::from(table_name)\n                        + \".\"\n                        + target_table\n                            .table\n                            .columns()\n                            .get(idx)\n                            .unwrap()\n                            .name\n                            .as_ref()\n                            .map_or(\"\", |v| v)\n                } else {\n                    String::from(table_name) + \".rowid\"\n                };\n                let (description, on_error) = halt_desc_and_on_error(\n                    &raw_desc,\n                    effective_rowid_alias_conflict,\n                    program.has_statement_conflict,\n                );\n                program.emit_insn(Insn::Halt {\n                    err_code: SQLITE_CONSTRAINT_PRIMARYKEY,\n                    description,\n                    on_error,\n                    description_reg: None,\n                });\n            }\n        }\n\n        program.preassign_label_to_next_insn(record_label);\n    }\n\n    // After the PK check above, NotExists may have repositioned the cursor.\n    // Re-seek to the row under update so old-image reads in Phase 2 are correct.\n    if has_user_provided_rowid && !matches!(effective_rowid_alias_conflict, ResolveType::Replace) {\n        if let Some(label) = check_rowid_not_exists_label {\n            program.emit_insn(Insn::NotExists {\n                cursor: target_table_cursor_id,\n                rowid_reg: beg,\n                target_pc: label,\n            });\n        }\n    }\n\n    // Evaluate STRICT type checks and CHECK constraints before any index mutations.\n    // This ensures that if a constraint fails, indexes remain consistent.\n    if let Some(btree_table) = target_table.table.btree() {\n        if btree_table.is_strict {\n            let set_col_indices: std::collections::HashSet<usize> =\n                set_clauses.iter().map(|(idx, _)| *idx).collect();\n\n            // Pre-encode TypeCheck: validate SET column input types.\n            // Non-SET columns hold encoded values from disk, so skip them (ANY).\n            program.emit_insn(Insn::TypeCheck {\n                start_reg: start,\n                count: col_len,\n                check_generated: true,\n                table_reference: BTreeTable::input_type_check_table_ref(\n                    &btree_table,\n                    t_ctx.resolver.schema(),\n                    Some(&set_col_indices),\n                ),\n            });\n\n            // Encode only SET clause columns. Non-SET columns were read from disk\n            // and are already encoded; re-encoding them would corrupt data.\n            crate::translate::expr::emit_custom_type_encode_columns(\n                program,\n                &t_ctx.resolver,\n                &btree_table.columns,\n                start,\n                Some(&set_col_indices),\n                table_name,\n            )?;\n\n            // Post-encode TypeCheck: validate encoded values match storage type.\n            program.emit_insn(Insn::TypeCheck {\n                start_reg: start,\n                count: col_len,\n                check_generated: true,\n                table_reference: BTreeTable::type_check_table_ref(\n                    &btree_table,\n                    t_ctx.resolver.schema(),\n                ),\n            });\n        }\n\n        if !btree_table.check_constraints.is_empty() {\n            // SQLite only evaluates CHECK constraints that reference at least one\n            // column in the SET clause. Build a set of updated column names to filter.\n            let mut updated_col_names: HashSet<String> = set_clauses\n                .iter()\n                .filter_map(|(col_idx, _)| {\n                    btree_table\n                        .columns\n                        .get(*col_idx)\n                        .and_then(|c| c.name.as_deref())\n                        .map(normalize_ident)\n                })\n                .collect();\n\n            // If the rowid is being updated (either directly via ROWID_SENTINEL or\n            // through a rowid alias column), also include the rowid pseudo-column\n            // names so that CHECK(rowid > 0) etc. are properly triggered.\n            let rowid_updated = set_clauses.iter().any(|(idx, _)| *idx == ROWID_SENTINEL)\n                || btree_table.columns.iter().enumerate().any(|(i, c)| {\n                    c.is_rowid_alias() && set_clauses.iter().any(|(idx, _)| *idx == i)\n                });\n            if rowid_updated {\n                for name in ROWID_STRS {\n                    updated_col_names.insert(name.to_string());\n                }\n            }\n\n            let relevant_checks: Vec<CheckConstraint> = btree_table\n                .check_constraints\n                .iter()\n                .filter(|cc| check_expr_references_columns(&cc.expr, &updated_col_names))\n                .cloned()\n                .collect();\n\n            let check_constraint_tables =\n                TableReferences::new(vec![target_table.as_ref().clone()], vec![]);\n            emit_check_constraints(\n                program,\n                &relevant_checks,\n                &mut t_ctx.resolver,\n                &btree_table.name,\n                rowid_set_clause_reg.unwrap_or(beg),\n                btree_table\n                    .columns\n                    .iter()\n                    .enumerate()\n                    .filter_map(|(idx, col)| {\n                        col.name.as_deref().map(|n| {\n                            if col.is_rowid_alias() {\n                                (n, rowid_set_clause_reg.unwrap_or(beg))\n                            } else {\n                                (n, start + idx)\n                            }\n                        })\n                    }),\n                connection,\n                or_conflict,\n                skip_row_label,\n                Some(&check_constraint_tables),\n            )?;\n        }\n    }\n\n    // Child-side FK checks must run AFTER custom type encoding so that NEW values\n    // being probed against the parent's index are encoded (matching the index contents).\n    if connection.foreign_keys_enabled() {\n        if let Some(table_btree) = target_table.table.btree() {\n            if t_ctx.resolver.schema().has_child_fks(table_name) {\n                let rowid_new_reg = rowid_set_clause_reg.unwrap_or(beg);\n                emit_fk_child_update_counters(\n                    program,\n                    &table_btree,\n                    table_name,\n                    target_table_cursor_id,\n                    start,\n                    rowid_new_reg,\n                    &set_clauses.iter().map(|(i, _)| *i).collect::<HashSet<_>>(),\n                    update_database_id,\n                    &t_ctx.resolver,\n                )?;\n            }\n        }\n    }\n\n    // =========================================================================\n    // Three-phase index update — matches SQLite's separated architecture.\n    // Phase 1: Evaluate partial WHERE predicates, build new index keys,\n    //          and check unique constraints (with inline REPLACE deletion;\n    //          REPLACE indexes are ordered last, after all other indexes,\n    //          because they are the only mutative ones).\n    // Phase 2: Delete old index entries from ALL indexes.\n    // Phase 3: Insert new index entries into ALL indexes.\n    // This ensures no index mutations happen until ALL constraint checks pass.\n    // =========================================================================\n\n    // Per-index context collected in Phase 1, consumed by Phases 2 and 3.\n    struct IndexUpdatePhaseCtx {\n        idx_cursor_id: usize,\n        record_reg: usize,\n        idx_start_reg: usize,\n        num_cols: usize,\n        old_satisfies_where: Option<usize>,\n        new_satisfies_where: Option<usize>,\n    }\n\n    let mut idx_phase_ctxs: Vec<IndexUpdatePhaseCtx> = Vec::with_capacity(indexes_to_update.len());\n\n    // ---- Phase 1: Constraint checks + new key build ----\n    let mut seen_replace = false;\n    for (index, (idx_cursor_id, record_reg)) in indexes_to_update.iter().zip(index_cursors) {\n        let (old_satisfies_where, new_satisfies_where) = if index.where_clause.is_some() {\n            // This means that we need to bind the column references to a copy of the index Expr,\n            // so we can emit Insn::Column instructions and refer to the old values.\n            let where_clause = index\n                .bind_where_expr(Some(table_references), resolver)\n                .expect(\"where clause to exist\");\n            let old_satisfied_reg = program.alloc_register();\n            translate_expr_no_constant_opt(\n                program,\n                Some(table_references),\n                &where_clause,\n                old_satisfied_reg,\n                &t_ctx.resolver,\n                NoConstantOptReason::RegisterReuse,\n            )?;\n\n            // grab a new copy of the original where clause from the index\n            let mut new_where = index\n                .where_clause\n                .as_ref()\n                .expect(\"checked where clause to exist\")\n                .clone();\n            // Now we need to rewrite the Expr::Id and Expr::Qualified/Expr::RowID (from a copy of the original, un-bound `where` expr),\n            // to refer to the new values, which are already loaded into registers starting at `start`.\n            rewrite_where_for_update_registers(\n                &mut new_where,\n                target_table.table.columns(),\n                start,\n                rowid_set_clause_reg.unwrap_or(beg),\n            )?;\n\n            let new_satisfied_reg = program.alloc_register();\n            translate_expr_no_constant_opt(\n                program,\n                None,\n                &new_where,\n                new_satisfied_reg,\n                &t_ctx.resolver,\n                NoConstantOptReason::RegisterReuse,\n            )?;\n\n            // now we have two registers that tell us whether or not the old and new values satisfy\n            // the partial index predicate, and we can use those to decide whether or not to\n            // delete/insert a new index entry for this partial index.\n            (Some(old_satisfied_reg), Some(new_satisfied_reg))\n        } else {\n            (None, None)\n        };\n\n        // Build new index key for constraint checking and later insertion (Phase 3).\n        let num_cols = index.columns.len();\n        let idx_start_reg = program.alloc_registers(num_cols + 1);\n        let rowid_reg = rowid_set_clause_reg.unwrap_or(beg);\n\n        for (i, col) in index.columns.iter().enumerate() {\n            emit_index_column_value_new_image(\n                program,\n                &t_ctx.resolver,\n                target_table.table.columns(),\n                start,\n                rowid_reg,\n                col,\n                idx_start_reg + i,\n                target_table.table.is_strict(),\n            )?;\n        }\n        // last register is the rowid\n        program.emit_insn(Insn::Copy {\n            src_reg: rowid_reg,\n            dst_reg: idx_start_reg + num_cols,\n            extra_amount: 0,\n        });\n\n        // Apply affinity BEFORE MakeRecord so the index record has correctly converted values.\n        // This is needed for all indexes (not just unique) because the index should store\n        // values with proper affinity conversion.\n        let aff = index\n            .columns\n            .iter()\n            .map(|ic| {\n                if ic.expr.is_some() {\n                    Affinity::Blob.aff_mask()\n                } else {\n                    target_table.table.columns()[ic.pos_in_table]\n                        .affinity_with_strict(target_is_strict)\n                        .aff_mask()\n                }\n            })\n            .collect::<String>();\n        program.emit_insn(Insn::Affinity {\n            start_reg: idx_start_reg,\n            count: NonZeroUsize::new(num_cols).expect(\"nonzero col count\"),\n            affinities: aff,\n        });\n\n        program.emit_insn(Insn::MakeRecord {\n            start_reg: to_u16(idx_start_reg),\n            count: to_u16(num_cols + 1),\n            dest_reg: to_u16(*record_reg),\n            index_name: Some(index.name.clone()),\n            affinity_str: None,\n        });\n\n        // Handle unique constraint BEFORE IdxDelete (matches SQLite order).\n        // If the constraint check fails (Halt/Ignore/Replace), the old index\n        // entry is still intact — no statement journal needed for rollback.\n        if index.unique {\n            let idx_conflict = if program.has_statement_conflict {\n                or_conflict\n            } else {\n                index.on_conflict.unwrap_or(ResolveType::Abort)\n            };\n            // REPLACE indexes must be sorted after all non-REPLACE indexes\n            // (schema.rs:add_index ensures this). If a non-REPLACE index\n            // appears after a REPLACE one, constraint check ordering is wrong.\n            if idx_conflict == ResolveType::Replace {\n                seen_replace = true;\n            } else {\n                turso_assert!(\n                    !seen_replace,\n                    \"non-REPLACE index after REPLACE index — sort order invariant violated\"\n                );\n            }\n\n            let constraint_check = program.allocate_label();\n\n            // For partial indexes, skip the constraint check if new values don't\n            // satisfy the WHERE clause (no insert → no conflict possible).\n            if let Some(new_satisfied) = new_satisfies_where {\n                program.emit_insn(Insn::IfNot {\n                    reg: new_satisfied,\n                    target_pc: constraint_check,\n                    jump_if_null: true,\n                });\n            }\n\n            // check if the record already exists in the index for unique indexes and abort if so\n            program.emit_insn(Insn::NoConflict {\n                cursor_id: *idx_cursor_id,\n                target_pc: constraint_check,\n                record_reg: idx_start_reg,\n                num_regs: num_cols,\n            });\n\n            let idx_rowid_reg = program.alloc_register();\n            program.emit_insn(Insn::IdxRowId {\n                cursor_id: *idx_cursor_id,\n                dest: idx_rowid_reg,\n            });\n\n            // Skip over the UNIQUE constraint failure if the existing row is the one that we are currently changing\n            program.emit_insn(Insn::Eq {\n                lhs: beg,\n                rhs: idx_rowid_reg,\n                target_pc: constraint_check,\n                flags: CmpInsFlags::default(),\n                collation: program.curr_collation(),\n            });\n            match idx_conflict {\n                ResolveType::Ignore => {\n                    // For IGNORE, skip this row's update but continue with other rows\n                    program.emit_insn(Insn::Goto {\n                        target_pc: skip_row_label,\n                    });\n                }\n                ResolveType::Replace => {\n                    // For REPLACE with unique constraint, delete the conflicting row\n                    // Save original rowid before seeking to conflicting row\n                    let original_rowid_reg = program.alloc_register();\n                    program.emit_insn(Insn::Copy {\n                        src_reg: beg,\n                        dst_reg: original_rowid_reg,\n                        extra_amount: 0,\n                    });\n\n                    // Seek to the conflicting row\n                    let after_delete_label = program.allocate_label();\n                    program.emit_insn(Insn::SeekRowid {\n                        cursor_id: target_table_cursor_id,\n                        src_reg: idx_rowid_reg,\n                        target_pc: after_delete_label, // Skip if row doesn't exist\n                    });\n\n                    // Phase 1: Before Delete - prepare FK cascade actions for implicitly-deleted row\n                    // CASCADE/SetNull/SetDefault actions are prepared but deferred until after Delete.\n                    let prepared_fk_actions = if connection.foreign_keys_enabled() {\n                        let prepared = if t_ctx.resolver.with_schema(update_database_id, |s| {\n                            s.any_resolved_fks_referencing(table_name)\n                        }) {\n                            ForeignKeyActions::prepare_fk_delete_actions(\n                                program,\n                                &mut t_ctx.resolver,\n                                table_name,\n                                target_table_cursor_id,\n                                idx_rowid_reg,\n                                update_database_id,\n                            )?\n                        } else {\n                            ForeignKeyActions::default()\n                        };\n                        if t_ctx\n                            .resolver\n                            .with_schema(update_database_id, |s| s.has_child_fks(table_name))\n                        {\n                            emit_fk_child_decrement_on_delete(\n                                program,\n                                &target_table\n                                    .table\n                                    .btree()\n                                    .expect(\"UPDATE target must be a BTree table\"),\n                                table_name,\n                                target_table_cursor_id,\n                                idx_rowid_reg,\n                                update_database_id,\n                                &t_ctx.resolver,\n                            )?;\n                        }\n                        prepared\n                    } else {\n                        ForeignKeyActions::default()\n                    };\n\n                    // Delete from ALL indexes for the conflicting row\n                    // We must delete from all indexes, not just indexes_to_update,\n                    // because the conflicting row may have entries in indexes\n                    // whose columns are not being modified by this UPDATE.\n                    for (other_index, other_idx_cursor_id) in all_index_cursors {\n                        // Build index key for the conflicting row\n                        let other_num_regs = other_index.columns.len() + 1;\n                        let other_start_reg = program.alloc_registers(other_num_regs);\n\n                        for (reg_offset, column_index) in other_index.columns.iter().enumerate() {\n                            emit_index_column_value_old_image(\n                                program,\n                                &t_ctx.resolver,\n                                table_references,\n                                target_table_cursor_id,\n                                column_index,\n                                other_start_reg + reg_offset,\n                            )?;\n                        }\n\n                        // Add the conflicting rowid\n                        program.emit_insn(Insn::Copy {\n                            src_reg: idx_rowid_reg,\n                            dst_reg: other_start_reg + other_num_regs - 1,\n                            extra_amount: 0,\n                        });\n\n                        program.emit_insn(Insn::IdxDelete {\n                            start_reg: other_start_reg,\n                            num_regs: other_num_regs,\n                            cursor_id: *other_idx_cursor_id,\n                            raise_error_if_no_matching_entry: other_index.where_clause.is_none(),\n                        });\n                    }\n\n                    // Delete the conflicting row from the main table\n                    program.emit_insn(Insn::Delete {\n                        cursor_id: target_table_cursor_id,\n                        table_name: table_name.to_string(),\n                        is_part_of_update: false,\n                    });\n\n                    // Phase 2: After Delete - fire CASCADE/SetNull/SetDefault FK actions.\n                    prepared_fk_actions.fire_prepared_fk_delete_actions(\n                        program,\n                        &mut t_ctx.resolver,\n                        connection,\n                        update_database_id,\n                    )?;\n\n                    program.preassign_label_to_next_insn(after_delete_label);\n\n                    // Seek back to the original row we're updating\n                    let continue_label = program.allocate_label();\n                    program.emit_insn(Insn::SeekRowid {\n                        cursor_id: target_table_cursor_id,\n                        src_reg: original_rowid_reg,\n                        target_pc: continue_label, // Should always succeed\n                    });\n                    program.preassign_label_to_next_insn(continue_label);\n                }\n                _ => {\n                    // ABORT/FAIL/ROLLBACK behavior\n                    let column_names = index.columns.iter().enumerate().fold(\n                        String::with_capacity(50),\n                        |mut accum, (idx, col)| {\n                            if idx > 0 {\n                                accum.push_str(\", \");\n                            }\n                            accum.push_str(table_name);\n                            accum.push('.');\n                            accum.push_str(&col.name);\n                            accum\n                        },\n                    );\n                    let (description, on_error) = halt_desc_and_on_error(\n                        &column_names,\n                        idx_conflict,\n                        program.has_statement_conflict,\n                    );\n                    program.emit_insn(Insn::Halt {\n                        err_code: SQLITE_CONSTRAINT_UNIQUE,\n                        description,\n                        on_error,\n                        description_reg: None,\n                    });\n                }\n            }\n\n            program.preassign_label_to_next_insn(constraint_check);\n        }\n\n        idx_phase_ctxs.push(IndexUpdatePhaseCtx {\n            idx_cursor_id: *idx_cursor_id,\n            record_reg: *record_reg,\n            idx_start_reg,\n            num_cols,\n            old_satisfies_where,\n            new_satisfies_where,\n        });\n    }\n\n    turso_assert_eq!(\n        idx_phase_ctxs.len(),\n        indexes_to_update.len(),\n        \"idx_phase_ctxs.len() != indexes_to_update.len()\"\n    );\n\n    // PK REPLACE: when the new rowid conflicts with an existing row, delete it.\n    // Runs AFTER Phase 1 (all index constraint checks) so that non-REPLACE index\n    // constraints fire before this deletion, matching SQLite's ordering.\n    if target_table.table.btree().is_some()\n        && has_user_provided_rowid\n        && matches!(effective_rowid_alias_conflict, ResolveType::Replace)\n    {\n        let target_reg = rowid_set_clause_reg.expect(\"rowid_set_clause_reg must be set\");\n        let no_rowid_conflict_label = program.allocate_label();\n        let row_not_found_label = check_rowid_not_exists_label\n            .expect(\"check_rowid_not_exists_label must be set when rowid is updated\");\n\n        // If the new rowid equals the old rowid, no conflict.\n        program.emit_insn(Insn::Eq {\n            lhs: target_reg,\n            rhs: beg,\n            target_pc: no_rowid_conflict_label,\n            flags: CmpInsFlags::default(),\n            collation: program.curr_collation(),\n        });\n\n        // If a row with the new rowid doesn't exist, no conflict.\n        program.emit_insn(Insn::NotExists {\n            cursor: target_table_cursor_id,\n            rowid_reg: target_reg,\n            target_pc: no_rowid_conflict_label,\n        });\n\n        // Before Delete - prepare FK cascade actions for implicitly-deleted row.\n        let prepared_fk_actions = if connection.foreign_keys_enabled() {\n            let prepared = if t_ctx.resolver.with_schema(update_database_id, |s| {\n                s.any_resolved_fks_referencing(table_name)\n            }) {\n                ForeignKeyActions::prepare_fk_delete_actions(\n                    program,\n                    &mut t_ctx.resolver,\n                    table_name,\n                    target_table_cursor_id,\n                    target_reg,\n                    update_database_id,\n                )?\n            } else {\n                ForeignKeyActions::default()\n            };\n            if t_ctx\n                .resolver\n                .with_schema(update_database_id, |s| s.has_child_fks(table_name))\n            {\n                emit_fk_child_decrement_on_delete(\n                    program,\n                    &target_table\n                        .table\n                        .btree()\n                        .expect(\"UPDATE target must be a BTree table\"),\n                    table_name,\n                    target_table_cursor_id,\n                    target_reg,\n                    update_database_id,\n                    &t_ctx.resolver,\n                )?;\n            }\n            prepared\n        } else {\n            ForeignKeyActions::default()\n        };\n\n        for (other_index, other_idx_cursor_id) in all_index_cursors {\n            let other_num_regs = other_index.columns.len() + 1;\n            let other_start_reg = program.alloc_registers(other_num_regs);\n\n            for (reg_offset, column_index) in other_index.columns.iter().enumerate() {\n                emit_index_column_value_old_image(\n                    program,\n                    &t_ctx.resolver,\n                    table_references,\n                    target_table_cursor_id,\n                    column_index,\n                    other_start_reg + reg_offset,\n                )?;\n            }\n\n            program.emit_insn(Insn::Copy {\n                src_reg: target_reg,\n                dst_reg: other_start_reg + other_num_regs - 1,\n                extra_amount: 0,\n            });\n\n            program.emit_insn(Insn::IdxDelete {\n                start_reg: other_start_reg,\n                num_regs: other_num_regs,\n                cursor_id: *other_idx_cursor_id,\n                raise_error_if_no_matching_entry: other_index.where_clause.is_none(),\n            });\n        }\n\n        program.emit_insn(Insn::Delete {\n            cursor_id: target_table_cursor_id,\n            table_name: table_name.to_string(),\n            is_part_of_update: false,\n        });\n\n        // After Delete - fire CASCADE/SetNull/SetDefault FK actions.\n        prepared_fk_actions.fire_prepared_fk_delete_actions(\n            program,\n            &mut t_ctx.resolver,\n            connection,\n            update_database_id,\n        )?;\n\n        // Re-seek to the row under update so Phase 2's old-image reads are correct.\n        program.preassign_label_to_next_insn(no_rowid_conflict_label);\n        program.emit_insn(Insn::NotExists {\n            cursor: target_table_cursor_id,\n            rowid_reg: beg,\n            target_pc: row_not_found_label,\n        });\n    }\n\n    // ---- Phase 2: Delete old index entries ----\n    // All constraint checks passed. Now safe to mutate indexes.\n    for (index, ctx) in indexes_to_update.iter().zip(idx_phase_ctxs.iter()) {\n        let mut skip_delete_label = None;\n\n        if let Some(old_satisfied) = ctx.old_satisfies_where {\n            skip_delete_label = Some(program.allocate_label());\n            program.emit_insn(Insn::IfNot {\n                reg: old_satisfied,\n                target_pc: skip_delete_label.unwrap(),\n                jump_if_null: true,\n            });\n        }\n\n        let num_regs = index.columns.len() + 1;\n        let delete_start_reg = program.alloc_registers(num_regs);\n        for (reg_offset, column_index) in index.columns.iter().enumerate() {\n            emit_index_column_value_old_image(\n                program,\n                &t_ctx.resolver,\n                table_references,\n                target_table_cursor_id,\n                column_index,\n                delete_start_reg + reg_offset,\n            )?;\n        }\n        program.emit_insn(Insn::RowId {\n            cursor_id: target_table_cursor_id,\n            dest: delete_start_reg + num_regs - 1,\n        });\n        program.emit_insn(Insn::IdxDelete {\n            start_reg: delete_start_reg,\n            num_regs,\n            cursor_id: ctx.idx_cursor_id,\n            raise_error_if_no_matching_entry: true,\n        });\n\n        if let Some(label) = skip_delete_label {\n            program.resolve_label(label, program.offset());\n        }\n    }\n\n    // ---- Phase 3: Insert new index entries ----\n    for ctx in idx_phase_ctxs.iter() {\n        let mut skip_insert_label = None;\n\n        if let Some(new_satisfied) = ctx.new_satisfies_where {\n            skip_insert_label = Some(program.allocate_label());\n            program.emit_insn(Insn::IfNot {\n                reg: new_satisfied,\n                target_pc: skip_insert_label.unwrap(),\n                jump_if_null: true,\n            });\n        }\n\n        program.emit_insn(Insn::IdxInsert {\n            cursor_id: ctx.idx_cursor_id,\n            record_reg: ctx.record_reg,\n            unpacked_start: Some(ctx.idx_start_reg),\n            unpacked_count: Some((ctx.num_cols + 1) as u16),\n            flags: IdxInsertFlags::new().nchange(true),\n        });\n\n        if let Some(label) = skip_insert_label {\n            program.resolve_label(label, program.offset());\n        }\n    }\n\n    if target_table.table.btree().is_some() {\n        let record_reg = program.alloc_register();\n\n        let is_strict = target_table\n            .table\n            .btree()\n            .is_some_and(|btree| btree.is_strict);\n        let affinity_str = target_table\n            .table\n            .columns()\n            .iter()\n            .map(|col| col.affinity_with_strict(is_strict).aff_mask())\n            .collect::<String>();\n\n        program.emit_insn(Insn::MakeRecord {\n            start_reg: to_u16(start),\n            count: to_u16(col_len),\n            dest_reg: to_u16(record_reg),\n            index_name: None,\n            affinity_str: Some(affinity_str),\n        });\n\n        if not_exists_check_required {\n            program.emit_insn(Insn::NotExists {\n                cursor: target_table_cursor_id,\n                rowid_reg: beg,\n                target_pc: check_rowid_not_exists_label.unwrap(),\n            });\n        }\n\n        // create alias for CDC rowid after the change (will differ from cdc_rowid_before_reg only in case of UPDATE with change in rowid alias)\n        let cdc_rowid_after_reg = rowid_set_clause_reg.unwrap_or(beg);\n\n        // create separate register with rowid before UPDATE for CDC\n        let cdc_rowid_before_reg = if t_ctx.cdc_cursor_id.is_some() {\n            let cdc_rowid_before_reg = program.alloc_register();\n            if has_user_provided_rowid {\n                program.emit_insn(Insn::RowId {\n                    cursor_id: target_table_cursor_id,\n                    dest: cdc_rowid_before_reg,\n                });\n                Some(cdc_rowid_before_reg)\n            } else {\n                Some(cdc_rowid_after_reg)\n            }\n        } else {\n            None\n        };\n\n        // create full CDC record before update if necessary\n        let cdc_before_reg = if program.capture_data_changes_info().has_before() {\n            Some(emit_cdc_full_record(\n                program,\n                target_table.table.columns(),\n                target_table_cursor_id,\n                cdc_rowid_before_reg.expect(\"cdc_rowid_before_reg must be set\"),\n                target_table\n                    .table\n                    .btree()\n                    .is_some_and(|btree| btree.is_strict),\n            ))\n        } else {\n            None\n        };\n\n        // If we are updating the rowid, we cannot rely on overwrite on the\n        // Insert instruction to update the cell. We need to first delete the current cell\n        // and later insert the updated record.\n        // In MVCC mode, we also need DELETE+INSERT to properly version the row (Hekaton model).\n        let needs_delete = not_exists_check_required || connection.mvcc_enabled();\n        if needs_delete {\n            program.emit_insn(Insn::Delete {\n                cursor_id: target_table_cursor_id,\n                table_name: table_name.to_string(),\n                is_part_of_update: true,\n            });\n        }\n\n        program.emit_insn(Insn::Insert {\n            cursor: target_table_cursor_id,\n            key_reg: rowid_set_clause_reg.unwrap_or(beg),\n            record_reg,\n            flag: if not_exists_check_required {\n                // The previous Insn::NotExists and Insn::Delete seek to the old rowid,\n                // so to insert a new user-provided rowid, we need to seek to the correct place.\n                InsertFlags::new()\n                    .require_seek()\n                    .update_rowid_change()\n                    .skip_last_rowid()\n            } else {\n                InsertFlags::new().skip_last_rowid()\n            },\n            table_name: target_table.identifier.clone(),\n        });\n\n        // Reconcile deferred FK violations after REPLACE.\n        // If Phase 1 REPLACE deleted a parent row referenced by deferred FK children,\n        // the counter was incremented. Now that the new row is inserted with the\n        // (potentially same) parent key, scan children and decrement.\n        if connection.foreign_keys_enabled() {\n            if let Some(table_btree) = target_table.table.btree() {\n                emit_fk_parent_new_key_reconcile(\n                    program,\n                    &table_btree,\n                    start,\n                    rowid_set_clause_reg.unwrap_or(beg),\n                    set_clauses,\n                    update_database_id,\n                    &t_ctx.resolver,\n                )?;\n            }\n        }\n\n        // Fire FK CASCADE/SET NULL actions AFTER the parent row is updated\n        // This ensures the new parent key exists when cascade actions update child rows\n        if connection.foreign_keys_enabled()\n            && t_ctx.resolver.with_schema(update_database_id, |s| {\n                s.any_resolved_fks_referencing(table_name)\n            })\n        {\n            let new_rowid_reg = rowid_set_clause_reg.unwrap_or(beg);\n            // OLD column values are stored in preserved_old_registers (contiguous registers)\n            let old_values_start = preserved_old_registers\n                .as_ref()\n                .expect(\"FK check requires OLD values\")[0];\n            fire_fk_update_actions(\n                program,\n                &mut t_ctx.resolver,\n                table_name,\n                beg, // old_rowid_reg\n                old_values_start,\n                start, // new_values_start\n                new_rowid_reg,\n                connection,\n                update_database_id,\n            )?;\n        }\n\n        // Fire AFTER UPDATE triggers\n        if let Some(btree_table) = target_table.table.btree() {\n            let updated_column_indices: HashSet<usize> =\n                set_clauses.iter().map(|(col_idx, _)| *col_idx).collect();\n            let relevant_triggers: Vec<_> = t_ctx.resolver.with_schema(update_database_id, |s| {\n                get_relevant_triggers_type_and_time(\n                    s,\n                    TriggerEvent::Update,\n                    TriggerTime::After,\n                    Some(updated_column_indices),\n                    &btree_table,\n                )\n                .collect()\n            });\n            if !relevant_triggers.is_empty() {\n                let new_rowid_reg = rowid_set_clause_reg.unwrap_or(beg);\n                // Build raw NEW registers. Values are encoded at this point;\n                // fire_trigger will decode them via decode_trigger_registers.\n                let new_registers_after: Vec<usize> = (0..col_len)\n                    .map(|i| start + i)\n                    .chain(std::iter::once(new_rowid_reg))\n                    .collect();\n\n                // Use preserved OLD registers from BEFORE trigger\n                let old_registers_after = preserved_old_registers;\n\n                // Propagate conflict resolution to AFTER trigger context (same logic as BEFORE)\n                let trigger_ctx_after =\n                    if let Some(override_conflict) = program.trigger_conflict_override {\n                        TriggerContext::new_after_with_override_conflict(\n                            btree_table,\n                            Some(new_registers_after),\n                            old_registers_after, // OLD values preserved from BEFORE trigger\n                            override_conflict,\n                        )\n                    } else if !matches!(or_conflict, ResolveType::Abort) {\n                        TriggerContext::new_after_with_override_conflict(\n                            btree_table,\n                            Some(new_registers_after),\n                            old_registers_after,\n                            or_conflict,\n                        )\n                    } else {\n                        TriggerContext::new_after(\n                            btree_table,\n                            Some(new_registers_after),\n                            old_registers_after, // OLD values preserved from BEFORE trigger\n                        )\n                    };\n\n                // RAISE(IGNORE) in an AFTER trigger should only abort the trigger body,\n                // not skip post-row work (RETURNING, CDC).\n                let after_trigger_done = program.allocate_label();\n                for trigger in relevant_triggers {\n                    fire_trigger(\n                        program,\n                        &mut t_ctx.resolver,\n                        trigger,\n                        &trigger_ctx_after,\n                        connection,\n                        update_database_id,\n                        after_trigger_done,\n                    )?;\n                }\n                program.preassign_label_to_next_insn(after_trigger_done);\n            }\n        }\n\n        let has_post_write_returning_subqueries = non_from_clause_subqueries\n            .iter()\n            .any(|s| !s.has_been_evaluated() && s.is_post_write_returning());\n        if has_post_write_returning_subqueries {\n            let cache_state = seed_returning_row_image_in_cache(\n                program,\n                table_references,\n                start,\n                rowid_set_clause_reg.unwrap_or(beg),\n                &mut t_ctx.resolver,\n            )?;\n            let result: Result<()> = (|| {\n                // Emit RETURNING subqueries after Insert so correlated references\n                // resolve against the post-write row image, not the old cursor state.\n                for subquery in non_from_clause_subqueries\n                    .iter_mut()\n                    .filter(|s| !s.has_been_evaluated() && s.is_post_write_returning())\n                {\n                    let rerun_for_target_scan = subquery\n                        .reads_table(target_table.database_id, target_table.table.get_name());\n                    let subquery_plan = subquery.consume_plan(EvalAt::Loop(0));\n                    emit_non_from_clause_subquery(\n                        program,\n                        &t_ctx.resolver,\n                        *subquery_plan,\n                        &subquery.query_type,\n                        subquery.correlated || rerun_for_target_scan,\n                        true,\n                    )?;\n                }\n                Ok(())\n            })();\n            restore_returning_row_image_in_cache(&mut t_ctx.resolver, cache_state);\n            result?;\n        }\n\n        // Emit RETURNING results if specified\n        if let Some(returning_columns) = &returning {\n            if !returning_columns.is_empty() {\n                emit_returning_results(\n                    program,\n                    table_references,\n                    returning_columns,\n                    start,\n                    rowid_set_clause_reg.unwrap_or(beg),\n                    &mut t_ctx.resolver,\n                    returning_buffer,\n                )?;\n            }\n        }\n\n        // create full CDC record after update if necessary\n        let cdc_after_reg = if program.capture_data_changes_info().has_after() {\n            Some(emit_cdc_patch_record(\n                program,\n                &target_table.table,\n                start,\n                record_reg,\n                cdc_rowid_after_reg,\n            ))\n        } else {\n            None\n        };\n\n        let cdc_updates_record = if let Some(cdc_updates_register) = cdc_updates_register {\n            let record_reg = program.alloc_register();\n            program.emit_insn(Insn::MakeRecord {\n                start_reg: to_u16(cdc_updates_register),\n                count: to_u16(2 * col_len),\n                dest_reg: to_u16(record_reg),\n                index_name: None,\n                affinity_str: None,\n            });\n            Some(record_reg)\n        } else {\n            None\n        };\n\n        // emit actual CDC instructions for write to the CDC table\n        if let Some(cdc_cursor_id) = t_ctx.cdc_cursor_id {\n            let cdc_rowid_before_reg =\n                cdc_rowid_before_reg.expect(\"cdc_rowid_before_reg must be set\");\n            if has_user_provided_rowid {\n                emit_cdc_insns(\n                    program,\n                    &t_ctx.resolver,\n                    OperationMode::DELETE,\n                    cdc_cursor_id,\n                    cdc_rowid_before_reg,\n                    cdc_before_reg,\n                    None,\n                    None,\n                    table_name,\n                )?;\n                emit_cdc_insns(\n                    program,\n                    &t_ctx.resolver,\n                    OperationMode::INSERT,\n                    cdc_cursor_id,\n                    cdc_rowid_after_reg,\n                    cdc_after_reg,\n                    None,\n                    None,\n                    table_name,\n                )?;\n            } else {\n                emit_cdc_insns(\n                    program,\n                    &t_ctx.resolver,\n                    OperationMode::UPDATE(if ephemeral_plan.is_some() {\n                        UpdateRowSource::PrebuiltEphemeralTable {\n                            ephemeral_table_cursor_id: iteration_cursor_id,\n                            target_table: target_table.clone(),\n                        }\n                    } else {\n                        UpdateRowSource::Normal\n                    }),\n                    cdc_cursor_id,\n                    cdc_rowid_before_reg,\n                    cdc_before_reg,\n                    cdc_after_reg,\n                    cdc_updates_record,\n                    table_name,\n                )?;\n            }\n        }\n    } else if target_table.virtual_table().is_some() {\n        let arg_count = col_len + 2;\n        program.emit_insn(Insn::VUpdate {\n            cursor_id: target_table_cursor_id,\n            arg_count,\n            start_reg: beg,\n            conflict_action: 0u16,\n        });\n    }\n\n    if let Some(limit_ctx) = t_ctx.limit_ctx {\n        program.emit_insn(Insn::DecrJumpZero {\n            reg: limit_ctx.reg_limit,\n            target_pc: t_ctx.label_main_loop_end.unwrap(),\n        })\n    }\n    // TODO(pthorpe): handle RETURNING clause\n\n    if let Some(label) = check_rowid_not_exists_label {\n        program.preassign_label_to_next_insn(label);\n    }\n    program.preassign_label_to_next_insn(trigger_ignore_jump_label);\n\n    t_ctx.resolver.register_affinities.clear();\n    Ok(())\n}\n"
  },
  {
    "path": "core/translate/expr.rs",
    "content": "use crate::error::{SQLITE_CONSTRAINT, SQLITE_CONSTRAINT_TRIGGER, SQLITE_ERROR};\nuse crate::translate::optimizer::constraints::ConstraintOperator;\nuse crate::turso_assert;\n\nuse tracing::{instrument, Level};\nuse turso_parser::ast::{self, Expr, ResolveType, SubqueryType, TableInternalId, UnaryOperator};\n\nuse super::emitter::Resolver;\nuse super::optimizer::Optimizable;\nuse super::plan::TableReferences;\n#[cfg(all(feature = \"fts\", not(target_family = \"wasm\")))]\nuse crate::function::FtsFunc;\n#[cfg(feature = \"json\")]\nuse crate::function::JsonFunc;\nuse crate::function::{AggFunc, Func, FuncCtx, MathFuncArity, ScalarFunc, VectorFunc};\nuse crate::functions::datetime;\nuse crate::schema::{ColDef, Column, Table, Type, TypeDef};\nuse crate::sync::Arc;\nuse crate::translate::expression_index::{\n    normalize_expr_for_index_matching, single_table_column_usage,\n};\nuse crate::translate::plan::{Operation, ResultSetColumn, Search};\nuse crate::translate::planner::parse_row_id;\nuse crate::util::{exprs_are_equivalent, normalize_ident, parse_numeric_literal};\nuse crate::vdbe::affinity::Affinity;\nuse crate::vdbe::builder::CursorKey;\nuse crate::vdbe::{\n    builder::ProgramBuilder,\n    insn::{CmpInsFlags, InsertFlags, Insn},\n    BranchOffset,\n};\nuse crate::{LimboError, Numeric, Result, Value};\nuse std::collections::HashSet;\n\nuse super::collate::{get_collseq_from_expr, CollationSeq};\n\n#[derive(Debug, Clone, Copy)]\npub struct ConditionMetadata {\n    pub jump_if_condition_is_true: bool,\n    pub jump_target_when_true: BranchOffset,\n    pub jump_target_when_false: BranchOffset,\n    pub jump_target_when_null: BranchOffset,\n}\n\nfn translate_between_expr(\n    program: &mut ProgramBuilder,\n    referenced_tables: Option<&TableReferences>,\n    mut between_expr: ast::Expr,\n    target_register: usize,\n    resolver: &Resolver,\n) -> Result<usize> {\n    let ast::Expr::Between {\n        ref mut lhs,\n        not,\n        ref mut start,\n        ref mut end,\n    } = between_expr\n    else {\n        unreachable!(\"translate_between_expr expects Expr::Between\");\n    };\n\n    let lhs_reg = program.alloc_register();\n    translate_expr(program, referenced_tables, &*lhs, lhs_reg, resolver)?;\n\n    let mut between_resolver = resolver.fork_with_expr_cache();\n    between_resolver.enable_expr_to_reg_cache();\n    #[allow(clippy::or_fun_call)]\n    between_resolver.cache_scalar_expr_reg(\n        std::borrow::Cow::Owned(*lhs.to_owned()),\n        lhs_reg,\n        false,\n        referenced_tables.unwrap_or(&TableReferences::default()),\n    )?;\n\n    let (lower_expr, upper_expr, combine_op) = build_between_terms(\n        std::mem::take(lhs),\n        not,\n        std::mem::take(start),\n        std::mem::take(end),\n    );\n    let lower_reg = program.alloc_register();\n    translate_expr(\n        program,\n        referenced_tables,\n        &lower_expr,\n        lower_reg,\n        &between_resolver,\n    )?;\n    let upper_reg = program.alloc_register();\n    translate_expr(\n        program,\n        referenced_tables,\n        &upper_expr,\n        upper_reg,\n        &between_resolver,\n    )?;\n\n    program.emit_insn(match combine_op {\n        ast::Operator::And => Insn::And {\n            lhs: lower_reg,\n            rhs: upper_reg,\n            dest: target_register,\n        },\n        ast::Operator::Or => Insn::Or {\n            lhs: lower_reg,\n            rhs: upper_reg,\n            dest: target_register,\n        },\n        _ => unreachable!(\"BETWEEN combine operator must be AND/OR\"),\n    });\n\n    Ok(target_register)\n}\n\nfn build_between_terms(\n    lhs: ast::Expr,\n    not: bool,\n    start: ast::Expr,\n    end: ast::Expr,\n) -> (ast::Expr, ast::Expr, ast::Operator) {\n    let (lower_op, upper_op, combine_op) = if not {\n        (\n            ast::Operator::Less,\n            ast::Operator::Greater,\n            ast::Operator::Or,\n        )\n    } else {\n        (\n            ast::Operator::GreaterEquals,\n            ast::Operator::LessEquals,\n            ast::Operator::And,\n        )\n    };\n    let lower_expr = ast::Expr::Binary(Box::new(lhs.clone()), lower_op, Box::new(start));\n    let upper_expr = ast::Expr::Binary(Box::new(lhs), upper_op, Box::new(end));\n    (lower_expr, upper_expr, combine_op)\n}\n\n#[instrument(skip_all, level = Level::DEBUG)]\nfn emit_cond_jump(program: &mut ProgramBuilder, cond_meta: ConditionMetadata, reg: usize) {\n    if cond_meta.jump_if_condition_is_true {\n        program.emit_insn(Insn::If {\n            reg,\n            target_pc: cond_meta.jump_target_when_true,\n            jump_if_null: false,\n        });\n    } else {\n        program.emit_insn(Insn::IfNot {\n            reg,\n            target_pc: cond_meta.jump_target_when_false,\n            jump_if_null: true,\n        });\n    }\n}\n\nfn assert_register_range_allocated(\n    program: &mut ProgramBuilder,\n    start_register: usize,\n    count: usize,\n) -> Result<()> {\n    // Invariant: callers must have pre-allocated [start_register, start_register + count)\n    // before asking expression translation to write a vector into that range.\n    let required_next = start_register + count;\n    let next_free = program.peek_next_register();\n    if required_next <= next_free {\n        Ok(())\n    } else {\n        crate::bail_parse_error!(\n            \"insufficient registers allocated for expression vector write (start={start_register}, count={count}, next_free={next_free})\"\n        )\n    }\n}\n\nfn supports_row_value_binary_comparison(operator: &ast::Operator) -> bool {\n    matches!(\n        operator,\n        ast::Operator::Equals\n            | ast::Operator::NotEquals\n            | ast::Operator::Less\n            | ast::Operator::LessEquals\n            | ast::Operator::Greater\n            | ast::Operator::GreaterEquals\n            | ast::Operator::Is\n            | ast::Operator::IsNot\n    )\n}\n\nmacro_rules! expect_arguments_exact {\n    (\n        $args:expr,\n        $expected_arguments:expr,\n        $func:ident\n    ) => {{\n        let args = $args;\n        let args = if !args.is_empty() {\n            if args.len() != $expected_arguments {\n                crate::bail_parse_error!(\n                    \"{} function called with not exactly {} arguments\",\n                    $func.to_string(),\n                    $expected_arguments,\n                );\n            }\n            args\n        } else {\n            crate::bail_parse_error!(\"{} function with no arguments\", $func.to_string());\n        };\n\n        args\n    }};\n}\n\nmacro_rules! expect_arguments_max {\n    (\n        $args:expr,\n        $expected_arguments:expr,\n        $func:ident\n    ) => {{\n        let args = $args;\n        let args = if !args.is_empty() {\n            if args.len() > $expected_arguments {\n                crate::bail_parse_error!(\n                    \"{} function called with more than {} arguments\",\n                    $func.to_string(),\n                    $expected_arguments,\n                );\n            }\n            args\n        } else {\n            crate::bail_parse_error!(\"{} function with no arguments\", $func.to_string());\n        };\n\n        args\n    }};\n}\n\n#[inline]\n/// For expression indexes, try to emit code that directly reads the value from the index\n/// under the following conditions:\n/// - The expression only references columns from a single table\n/// - The referenced table has an index whose expression matches the given expression\n///\n/// If an expression index exactly matches the requested expression, we can\n/// fetch the precomputed value from the index key instead of re-evaluating\n/// the expression. That matters for:\n/// - SELECT a/b FROM t with INDEX ON t(a/b) (avoid computing a/b for every row)\n/// - ORDER BY a+b when the index already stores a+b (preserves ordering)\n///\n/// We mut do this check early in translate_expr so downstream translation does\n/// not build redundant bytecode.\nfn try_emit_expression_index_value(\n    program: &mut ProgramBuilder,\n    referenced_tables: Option<&TableReferences>,\n    expr: &ast::Expr,\n    target_register: usize,\n) -> Result<bool> {\n    let Some(referenced_tables) = referenced_tables else {\n        return Ok(false);\n    };\n    let Some((table_id, _)) = single_table_column_usage(expr) else {\n        return Ok(false);\n    };\n    let Some(table_reference) = referenced_tables.find_joined_table_by_internal_id(table_id) else {\n        return Ok(false);\n    };\n    let Some(index) = table_reference.op.index() else {\n        return Ok(false);\n    };\n    let normalized = normalize_expr_for_index_matching(expr, table_reference, referenced_tables);\n    let Some(expr_pos) = index.expression_to_index_pos(&normalized) else {\n        return Ok(false);\n    };\n    let Some(cursor_id) =\n        program.resolve_cursor_id_safe(&CursorKey::index(table_id, index.clone()))\n    else {\n        return Ok(false);\n    };\n    program.emit_column_or_rowid(cursor_id, expr_pos, target_register);\n    Ok(true)\n}\n\nmacro_rules! expect_arguments_min {\n    (\n        $args:expr,\n        $expected_arguments:expr,\n        $func:ident\n    ) => {{\n        let args = $args;\n        let args = if !args.is_empty() {\n            if args.len() < $expected_arguments {\n                crate::bail_parse_error!(\n                    \"{} function with less than {} arguments\",\n                    $func.to_string(),\n                    $expected_arguments\n                );\n            }\n            args\n        } else {\n            crate::bail_parse_error!(\"{} function with no arguments\", $func.to_string());\n        };\n        args\n    }};\n}\n\n#[allow(unused_macros)]\nmacro_rules! expect_arguments_even {\n    (\n        $args:expr,\n        $func:ident\n    ) => {{\n        let args = $args;\n        if args.len() % 2 != 0 {\n            crate::bail_parse_error!(\n                \"{} function requires an even number of arguments\",\n                $func.to_string()\n            );\n        };\n        // The only function right now that requires an even number is `json_object` and it allows\n        // to have no arguments, so thats why in this macro we do not bail with the `function with no arguments` error\n        args\n    }};\n}\n\n/// Core implementation of IN expression logic that can be used in both conditional and expression contexts.\n/// This follows SQLite's approach where a single core function handles all InList cases.\n///\n/// This is extracted from the original conditional implementation to be reusable.\n/// The logic exactly matches the original conditional InList implementation.\n///\n/// An IN expression has one of the following formats:\n///  ```sql\n///      x IN (y1, y2,...,yN)\n///      x IN (subquery) (Not yet implemented)\n///  ```\n/// The result of an IN operator is one of TRUE, FALSE, or NULL.  A NULL result\n/// means that it cannot be determined if the LHS is contained in the RHS due\n/// to the presence of NULL values.\n///\n/// Currently, we do a simple full-scan, yet it's not ideal when there are many rows\n/// on RHS. (Check sqlite's in-operator.md)\n///\n/// Algorithm:\n/// 1. Set the null-flag to false\n/// 2. For each row in the RHS:\n///     - Compare LHS and RHS\n///     - If LHS matches RHS, returns TRUE\n///     - If the comparison results in NULL, set the null-flag to true\n/// 3. If the null-flag is true, return NULL\n/// 4. Return FALSE\n///\n/// A \"NOT IN\" operator is computed by first computing the equivalent IN\n/// operator, then interchanging the TRUE and FALSE results.\n/// Compute the affinity for an IN expression.\n/// For `x IN (y1, y2, ..., yN)`, the affinity is determined by the LHS expression `x`.\n/// This follows SQLite's `exprINAffinity()` function.\nfn in_expr_affinity(\n    lhs: &ast::Expr,\n    referenced_tables: Option<&TableReferences>,\n    resolver: Option<&Resolver>,\n) -> Affinity {\n    // For parenthesized expressions (vectors), we take the first element's affinity\n    // since scalar IN comparisons only use the first element\n    match lhs {\n        Expr::Parenthesized(exprs) if !exprs.is_empty() => {\n            get_expr_affinity(&exprs[0], referenced_tables, resolver)\n        }\n        _ => get_expr_affinity(lhs, referenced_tables, resolver),\n    }\n}\n\n#[instrument(skip(program, referenced_tables, resolver), level = Level::DEBUG)]\nfn translate_in_list(\n    program: &mut ProgramBuilder,\n    referenced_tables: Option<&TableReferences>,\n    lhs: &ast::Expr,\n    rhs: &[Box<ast::Expr>],\n    condition_metadata: ConditionMetadata,\n    // dest if null should be in ConditionMetadata\n    resolver: &Resolver,\n) -> Result<()> {\n    let lhs_arity = expr_vector_size(lhs)?;\n    let lhs_reg = program.alloc_registers(lhs_arity);\n    let _ = translate_expr(program, referenced_tables, lhs, lhs_reg, resolver)?;\n    let mut check_null_reg = 0;\n    let label_ok = program.allocate_label();\n\n    // Compute the affinity for the IN comparison based on the LHS expression\n    // This follows SQLite's exprINAffinity() approach\n    let affinity = in_expr_affinity(lhs, referenced_tables, Some(resolver));\n    let cmp_flags = CmpInsFlags::default().with_affinity(affinity);\n\n    if condition_metadata.jump_target_when_false != condition_metadata.jump_target_when_null {\n        check_null_reg = program.alloc_register();\n        program.emit_insn(Insn::BitAnd {\n            lhs: lhs_reg,\n            rhs: lhs_reg,\n            dest: check_null_reg,\n        });\n    }\n\n    for (i, expr) in rhs.iter().enumerate() {\n        let last_condition = i == rhs.len() - 1;\n        let rhs_reg = program.alloc_registers(lhs_arity);\n        let _ = translate_expr(program, referenced_tables, expr, rhs_reg, resolver)?;\n\n        if check_null_reg != 0 && expr.can_be_null() {\n            program.emit_insn(Insn::BitAnd {\n                lhs: check_null_reg,\n                rhs: rhs_reg,\n                dest: check_null_reg,\n            });\n        }\n\n        if lhs_arity == 1 {\n            // Scalar comparison path\n            if !last_condition\n                || condition_metadata.jump_target_when_false\n                    != condition_metadata.jump_target_when_null\n            {\n                if lhs_reg != rhs_reg {\n                    program.emit_insn(Insn::Eq {\n                        lhs: lhs_reg,\n                        rhs: rhs_reg,\n                        target_pc: label_ok,\n                        flags: cmp_flags,\n                        collation: program.curr_collation(),\n                    });\n                } else {\n                    program.emit_insn(Insn::NotNull {\n                        reg: lhs_reg,\n                        target_pc: label_ok,\n                    });\n                }\n            } else if lhs_reg != rhs_reg {\n                program.emit_insn(Insn::Ne {\n                    lhs: lhs_reg,\n                    rhs: rhs_reg,\n                    target_pc: condition_metadata.jump_target_when_false,\n                    flags: cmp_flags.jump_if_null(),\n                    collation: program.curr_collation(),\n                });\n            } else {\n                program.emit_insn(Insn::IsNull {\n                    reg: lhs_reg,\n                    target_pc: condition_metadata.jump_target_when_false,\n                });\n            }\n        } else {\n            // Row-valued comparison path: compare each component\n            if !last_condition\n                || condition_metadata.jump_target_when_false\n                    != condition_metadata.jump_target_when_null\n            {\n                // If all components match, jump to label_ok; otherwise skip to next RHS item\n                let skip_label = program.allocate_label();\n                for j in 0..lhs_arity {\n                    let (aff, collation) = row_component_affinity_collation(\n                        lhs,\n                        expr,\n                        j,\n                        referenced_tables,\n                        Some(resolver),\n                    )?;\n                    let flags = CmpInsFlags::default().with_affinity(aff);\n                    if j < lhs_arity - 1 {\n                        program.emit_insn(Insn::Ne {\n                            lhs: lhs_reg + j,\n                            rhs: rhs_reg + j,\n                            target_pc: skip_label,\n                            flags,\n                            collation,\n                        });\n                    } else {\n                        program.emit_insn(Insn::Eq {\n                            lhs: lhs_reg + j,\n                            rhs: rhs_reg + j,\n                            target_pc: label_ok,\n                            flags,\n                            collation,\n                        });\n                    }\n                }\n                program.preassign_label_to_next_insn(skip_label);\n            } else {\n                // Last condition, simple case: jump to false if any component doesn't match\n                for j in 0..lhs_arity {\n                    let (aff, collation) = row_component_affinity_collation(\n                        lhs,\n                        expr,\n                        j,\n                        referenced_tables,\n                        Some(resolver),\n                    )?;\n                    let flags = CmpInsFlags::default().with_affinity(aff).jump_if_null();\n                    program.emit_insn(Insn::Ne {\n                        lhs: lhs_reg + j,\n                        rhs: rhs_reg + j,\n                        target_pc: condition_metadata.jump_target_when_false,\n                        flags,\n                        collation,\n                    });\n                }\n            }\n        }\n    }\n\n    if check_null_reg != 0 {\n        program.emit_insn(Insn::IsNull {\n            reg: check_null_reg,\n            target_pc: condition_metadata.jump_target_when_null,\n        });\n        program.emit_insn(Insn::Goto {\n            target_pc: condition_metadata.jump_target_when_false,\n        });\n    }\n\n    // we don't know exactly what instruction will came next and it's important to chain label to the execution flow rather then exact next instruction\n    // for example, next instruction can be register assignment, which can be moved by optimized to the constant section\n    // in this case, label_ok must be changed accordingly and be re-binded to another instruction followed the current translation unit after constants reording\n    program.preassign_label_to_next_insn(label_ok);\n\n    // by default if IN expression is true we just continue to the next instruction\n    if condition_metadata.jump_if_condition_is_true {\n        program.emit_insn(Insn::Goto {\n            target_pc: condition_metadata.jump_target_when_true,\n        });\n    }\n    // todo: deallocate check_null_reg\n\n    Ok(())\n}\n\n#[instrument(skip(program, referenced_tables, expr, resolver), level = Level::DEBUG)]\npub fn translate_condition_expr(\n    program: &mut ProgramBuilder,\n    referenced_tables: &TableReferences,\n    expr: &ast::Expr,\n    condition_metadata: ConditionMetadata,\n    resolver: &Resolver,\n) -> Result<()> {\n    match expr {\n        ast::Expr::SubqueryResult { query_type, .. } => match query_type {\n            SubqueryType::Exists { result_reg } => {\n                emit_cond_jump(program, condition_metadata, *result_reg);\n            }\n            SubqueryType::In { .. } => {\n                let result_reg = program.alloc_register();\n                translate_expr(program, Some(referenced_tables), expr, result_reg, resolver)?;\n                emit_cond_jump(program, condition_metadata, result_reg);\n            }\n            SubqueryType::RowValue { num_regs, .. } => {\n                if *num_regs != 1 {\n                    // A query like SELECT * FROM t WHERE (SELECT ...) must return a single column.\n                    crate::bail_parse_error!(\"sub-select returns {num_regs} columns - expected 1\");\n                }\n                let result_reg = program.alloc_register();\n                translate_expr(program, Some(referenced_tables), expr, result_reg, resolver)?;\n                emit_cond_jump(program, condition_metadata, result_reg);\n            }\n        },\n        ast::Expr::Register(_) => {\n            crate::bail_parse_error!(\n                \"Register in WHERE clause is currently unused. Consider removing Resolver::expr_to_reg_cache and using Expr::Register instead\"\n            );\n        }\n        ast::Expr::Collate(_, _) => {\n            crate::bail_parse_error!(\"Collate in WHERE clause is not supported\");\n        }\n        ast::Expr::DoublyQualified(_, _, _) | ast::Expr::Id(_) | ast::Expr::Qualified(_, _) => {\n            crate::bail_parse_error!(\n                \"DoublyQualified/Id/Qualified should have been rewritten in optimizer\"\n            );\n        }\n        ast::Expr::Exists(_) => {\n            crate::bail_parse_error!(\"EXISTS in WHERE clause is not supported\");\n        }\n        ast::Expr::Subquery(_) => {\n            crate::bail_parse_error!(\"Subquery in WHERE clause is not supported\");\n        }\n        ast::Expr::InSelect { .. } => {\n            crate::bail_parse_error!(\"IN (...subquery) in WHERE clause is not supported\");\n        }\n        ast::Expr::InTable { .. } => {\n            crate::bail_parse_error!(\"Table expression in WHERE clause is not supported\");\n        }\n        ast::Expr::FunctionCallStar { .. } => {\n            crate::bail_parse_error!(\"FunctionCallStar in WHERE clause is not supported\");\n        }\n        ast::Expr::Raise(_, _) => {\n            crate::bail_parse_error!(\"RAISE in WHERE clause is not supported\");\n        }\n        ast::Expr::Between { .. } => {\n            let between_result_reg = program.alloc_register();\n            translate_between_expr(\n                program,\n                Some(referenced_tables),\n                expr.clone(),\n                between_result_reg,\n                resolver,\n            )?;\n            emit_cond_jump(program, condition_metadata, between_result_reg);\n        }\n        ast::Expr::Variable(_) => {\n            crate::bail_parse_error!(\n                \"Variable as a direct predicate in WHERE clause is not supported\"\n            );\n        }\n        ast::Expr::Name(_) => {\n            crate::bail_parse_error!(\"Name as a direct predicate in WHERE clause is not supported\");\n        }\n        ast::Expr::Binary(lhs, ast::Operator::And, rhs) => {\n            // In a binary AND, never jump to the parent 'jump_target_when_true' label on the first condition, because\n            // the second condition MUST also be true. Instead we instruct the child expression to jump to a local\n            // true label.\n            let jump_target_when_true = program.allocate_label();\n            translate_condition_expr(\n                program,\n                referenced_tables,\n                lhs,\n                ConditionMetadata {\n                    jump_if_condition_is_true: false,\n                    jump_target_when_true,\n                    ..condition_metadata\n                },\n                resolver,\n            )?;\n            program.preassign_label_to_next_insn(jump_target_when_true);\n            translate_condition_expr(\n                program,\n                referenced_tables,\n                rhs,\n                condition_metadata,\n                resolver,\n            )?;\n        }\n        ast::Expr::Binary(lhs, ast::Operator::Or, rhs) => {\n            // In a binary OR, never jump to the parent 'jump_target_when_false' or\n            // 'jump_target_when_null' label on the first condition, because the second\n            // condition CAN also be true. Instead we instruct the child expression to\n            // jump to a local false label so the right side of OR gets evaluated.\n            // This is critical for cases like `x IN (NULL, 3) OR b` where the left side\n            // evaluates to NULL — we must still evaluate the right side.\n            let jump_target_when_false = program.allocate_label();\n            translate_condition_expr(\n                program,\n                referenced_tables,\n                lhs,\n                ConditionMetadata {\n                    jump_if_condition_is_true: true,\n                    jump_target_when_false,\n                    jump_target_when_null: jump_target_when_false,\n                    ..condition_metadata\n                },\n                resolver,\n            )?;\n            program.preassign_label_to_next_insn(jump_target_when_false);\n            translate_condition_expr(\n                program,\n                referenced_tables,\n                rhs,\n                condition_metadata,\n                resolver,\n            )?;\n        }\n        // Handle IS TRUE/IS FALSE/IS NOT TRUE/IS NOT FALSE in conditions\n        // Delegate to translate_expr which handles these correctly with IsTrue instruction\n        ast::Expr::Binary(_, ast::Operator::Is | ast::Operator::IsNot, e2)\n            if matches!(\n                e2.as_ref(),\n                ast::Expr::Literal(ast::Literal::True) | ast::Expr::Literal(ast::Literal::False)\n            ) =>\n        {\n            let reg = program.alloc_register();\n            translate_expr(program, Some(referenced_tables), expr, reg, resolver)?;\n            emit_cond_jump(program, condition_metadata, reg);\n        }\n        // Handle IS NULL/IS NOT NULL in conditions using IsNull/NotNull opcodes.\n        // \"a IS NULL\" is parsed as Binary(a, Is, Null), but we need to use the IsNull opcode\n        // (not Eq/Ne with null_eq flag) for correct NULL handling in WHERE clauses.\n        ast::Expr::Binary(e1, ast::Operator::Is, e2)\n            if matches!(e2.as_ref(), ast::Expr::Literal(ast::Literal::Null)) =>\n        {\n            let cur_reg = program.alloc_register();\n            translate_expr(program, Some(referenced_tables), e1, cur_reg, resolver)?;\n            if condition_metadata.jump_if_condition_is_true {\n                program.emit_insn(Insn::IsNull {\n                    reg: cur_reg,\n                    target_pc: condition_metadata.jump_target_when_true,\n                });\n            } else {\n                program.emit_insn(Insn::NotNull {\n                    reg: cur_reg,\n                    target_pc: condition_metadata.jump_target_when_false,\n                });\n            }\n        }\n        ast::Expr::Binary(e1, ast::Operator::IsNot, e2)\n            if matches!(e2.as_ref(), ast::Expr::Literal(ast::Literal::Null)) =>\n        {\n            let cur_reg = program.alloc_register();\n            translate_expr(program, Some(referenced_tables), e1, cur_reg, resolver)?;\n            if condition_metadata.jump_if_condition_is_true {\n                program.emit_insn(Insn::NotNull {\n                    reg: cur_reg,\n                    target_pc: condition_metadata.jump_target_when_true,\n                });\n            } else {\n                program.emit_insn(Insn::IsNull {\n                    reg: cur_reg,\n                    target_pc: condition_metadata.jump_target_when_false,\n                });\n            }\n        }\n        ast::Expr::Binary(e1, op, e2) => {\n            // Check if either operand has a custom type with a matching operator\n            if let Some(resolved) =\n                find_custom_type_operator(e1, e2, op, Some(referenced_tables), resolver)\n            {\n                let result_reg = emit_custom_type_operator(\n                    program,\n                    Some(referenced_tables),\n                    e1,\n                    e2,\n                    &resolved,\n                    resolver,\n                )?;\n                emit_cond_jump(program, condition_metadata, result_reg);\n            } else {\n                let result_reg = program.alloc_register();\n                binary_expr_shared(\n                    program,\n                    Some(referenced_tables),\n                    e1,\n                    e2,\n                    op,\n                    result_reg,\n                    resolver,\n                    BinaryEmitMode::Condition(condition_metadata),\n                )?;\n            }\n        }\n        ast::Expr::Literal(_)\n        | ast::Expr::Cast { .. }\n        | ast::Expr::FunctionCall { .. }\n        | ast::Expr::Column { .. }\n        | ast::Expr::RowId { .. }\n        | ast::Expr::Case { .. } => {\n            let reg = program.alloc_register();\n            translate_expr(program, Some(referenced_tables), expr, reg, resolver)?;\n            emit_cond_jump(program, condition_metadata, reg);\n        }\n\n        ast::Expr::InList { lhs, not, rhs } => {\n            let ConditionMetadata {\n                jump_if_condition_is_true,\n                jump_target_when_true,\n                jump_target_when_false,\n                jump_target_when_null,\n            } = condition_metadata;\n\n            // Adjust targets if `NOT IN`\n            let (adjusted_metadata, not_true_label, not_false_label) = if *not {\n                let not_true_label = program.allocate_label();\n                let not_false_label = program.allocate_label();\n                (\n                    ConditionMetadata {\n                        jump_if_condition_is_true,\n                        jump_target_when_true: not_true_label,\n                        jump_target_when_false: not_false_label,\n                        jump_target_when_null,\n                    },\n                    Some(not_true_label),\n                    Some(not_false_label),\n                )\n            } else {\n                (condition_metadata, None, None)\n            };\n\n            translate_in_list(\n                program,\n                Some(referenced_tables),\n                lhs,\n                rhs,\n                adjusted_metadata,\n                resolver,\n            )?;\n\n            if *not {\n                // When IN is TRUE (match found), NOT IN should be FALSE\n                program.resolve_label(not_true_label.unwrap(), program.offset());\n                program.emit_insn(Insn::Goto {\n                    target_pc: jump_target_when_false,\n                });\n\n                // When IN is FALSE (no match), NOT IN should be TRUE\n                program.resolve_label(not_false_label.unwrap(), program.offset());\n                program.emit_insn(Insn::Goto {\n                    target_pc: jump_target_when_true,\n                });\n            }\n        }\n        ast::Expr::Like { not, .. } => {\n            let cur_reg = program.alloc_register();\n            translate_like_base(program, Some(referenced_tables), expr, cur_reg, resolver)?;\n            if !*not {\n                emit_cond_jump(program, condition_metadata, cur_reg);\n            } else if condition_metadata.jump_if_condition_is_true {\n                program.emit_insn(Insn::IfNot {\n                    reg: cur_reg,\n                    target_pc: condition_metadata.jump_target_when_true,\n                    jump_if_null: false,\n                });\n            } else {\n                program.emit_insn(Insn::If {\n                    reg: cur_reg,\n                    target_pc: condition_metadata.jump_target_when_false,\n                    jump_if_null: true,\n                });\n            }\n        }\n        ast::Expr::Parenthesized(exprs) => {\n            if exprs.len() == 1 {\n                translate_condition_expr(\n                    program,\n                    referenced_tables,\n                    &exprs[0],\n                    condition_metadata,\n                    resolver,\n                )?;\n            } else {\n                crate::bail_parse_error!(\n                    \"parenthesized conditional should have exactly one expression\"\n                );\n            }\n        }\n        ast::Expr::NotNull(expr) => {\n            let cur_reg = program.alloc_register();\n            translate_expr(program, Some(referenced_tables), expr, cur_reg, resolver)?;\n            if condition_metadata.jump_if_condition_is_true {\n                program.emit_insn(Insn::NotNull {\n                    reg: cur_reg,\n                    target_pc: condition_metadata.jump_target_when_true,\n                });\n            } else {\n                program.emit_insn(Insn::IsNull {\n                    reg: cur_reg,\n                    target_pc: condition_metadata.jump_target_when_false,\n                });\n            }\n        }\n        ast::Expr::IsNull(expr) => {\n            let cur_reg = program.alloc_register();\n            translate_expr(program, Some(referenced_tables), expr, cur_reg, resolver)?;\n            if condition_metadata.jump_if_condition_is_true {\n                program.emit_insn(Insn::IsNull {\n                    reg: cur_reg,\n                    target_pc: condition_metadata.jump_target_when_true,\n                });\n            } else {\n                program.emit_insn(Insn::NotNull {\n                    reg: cur_reg,\n                    target_pc: condition_metadata.jump_target_when_false,\n                });\n            }\n        }\n        ast::Expr::Unary(_, _) => {\n            // This is an inefficient implementation for op::NOT, because translate_expr() will emit an Insn::Not,\n            // and then we immediately emit an Insn::If/Insn::IfNot for the conditional jump. In reality we would not\n            // like to emit the negation instruction Insn::Not at all, since we could just emit the \"opposite\" jump instruction\n            // directly. However, using translate_expr() directly simplifies our conditional jump code for unary expressions,\n            // and we'd rather be correct than maximally efficient, for now.\n            let expr_reg = program.alloc_register();\n            translate_expr(program, Some(referenced_tables), expr, expr_reg, resolver)?;\n            emit_cond_jump(program, condition_metadata, expr_reg);\n        }\n        ast::Expr::Array { .. } | ast::Expr::Subscript { .. } => {\n            unreachable!(\"Array and Subscript are desugared into function calls by the parser\")\n        }\n    }\n    Ok(())\n}\n\n/// Reason why [translate_expr_no_constant_opt()] was called.\n#[derive(Debug)]\npub enum NoConstantOptReason {\n    /// The expression translation involves reusing register(s),\n    /// so hoisting those register assignments is not safe.\n    /// e.g. SELECT COALESCE(1, t.x, NULL) would overwrite 1 with NULL, which is invalid.\n    RegisterReuse,\n    /// The column has a custom type encode function that will be applied\n    /// in-place after this expression is evaluated. We must not hoist the\n    /// expression because:\n    ///\n    /// 1. The encode function may be non-deterministic (e.g. it could use\n    ///    datetime('now')), so hoisting would produce incorrect results.\n    ///\n    /// 2. Even if the encode function were deterministic, the encode is\n    ///    applied in-place to the target register inside the update loop.\n    ///    If the original value were hoisted (evaluated once before the\n    ///    loop), the second iteration would read the already-encoded value\n    ///    from the register and encode it again, causing progressive\n    ///    double-encoding (e.g. 99 → 9900 → 990000 → ...).\n    ///\n    /// The correct fix for deterministic encode functions would be to hoist\n    /// the *encoded* result (i.e. `encode_fn(99)` not `99`), but that\n    /// requires tracking the encode through the hoisting machinery. For now\n    /// we simply disable hoisting for these columns.\n    CustomTypeEncode,\n    /// IN-list values are inserted into an ephemeral table in a loop.\n    /// Each value reuses the same register, so hoisting would collapse\n    /// all values into the last one.\n    InListEphemeral,\n}\n\n/// Controls how binary expressions are emitted.\n///\n/// This makes scalar and row-valued paths explicit:\n/// - scalar binary expressions use mode to pick either value emission or conditional jump emission\n/// - row-valued binary expressions always emit a value register first, then optionally a conditional jump\n#[derive(Clone, Copy)]\nenum BinaryEmitMode {\n    Value,\n    Condition(ConditionMetadata),\n}\n\n/// Translate an expression into bytecode via [translate_expr()], and forbid any constant values from being hoisted\n/// into the beginning of the program. This is a good idea in most cases where\n/// a register will end up being reused e.g. in a coroutine.\npub fn translate_expr_no_constant_opt(\n    program: &mut ProgramBuilder,\n    referenced_tables: Option<&TableReferences>,\n    expr: &ast::Expr,\n    target_register: usize,\n    resolver: &Resolver,\n    deopt_reason: NoConstantOptReason,\n) -> Result<usize> {\n    tracing::debug!(\n        \"translate_expr_no_constant_opt: expr={:?}, deopt_reason={:?}\",\n        expr,\n        deopt_reason\n    );\n    let next_span_idx = program.constant_spans_next_idx();\n    let translated = translate_expr(program, referenced_tables, expr, target_register, resolver)?;\n    program.constant_spans_invalidate_after(next_span_idx);\n    Ok(translated)\n}\n\n/// Resolve an expression to a register, reusing an existing register when possible.\n///\n/// Unlike `translate_expr`, this does not require a pre-allocated target register.\n/// If the expression is found in the `expr_to_reg_cache`, the cached register is\n/// returned directly without emitting a Copy instruction. Otherwise, a new register\n/// is allocated and the expression is translated into it.\n///\n/// Callers MUST use the returned register — they cannot assume a specific destination.\n#[must_use = \"the returned register must be used, because that is where the expression value is stored\"]\npub fn resolve_expr(\n    program: &mut ProgramBuilder,\n    referenced_tables: Option<&TableReferences>,\n    expr: &ast::Expr,\n    resolver: &Resolver,\n) -> Result<usize> {\n    if let Some((reg, needs_decode, _collation)) = resolver.resolve_cached_expr_reg(expr) {\n        if !needs_decode {\n            return Ok(reg);\n        }\n    }\n    let dest_reg = program.alloc_register();\n    translate_expr(program, referenced_tables, expr, dest_reg, resolver)\n}\n\n/// Translate an expression into bytecode.\npub fn translate_expr(\n    program: &mut ProgramBuilder,\n    referenced_tables: Option<&TableReferences>,\n    expr: &ast::Expr,\n    target_register: usize,\n    resolver: &Resolver,\n) -> Result<usize> {\n    let constant_span = if expr.is_constant(resolver) {\n        if !program.constant_span_is_open() {\n            Some(program.constant_span_start())\n        } else {\n            None\n        }\n    } else {\n        program.constant_span_end_all();\n        None\n    };\n\n    if let Some((reg, needs_decode, collation_ctx)) = resolver.resolve_cached_expr_reg(expr) {\n        program.emit_insn(Insn::Copy {\n            src_reg: reg,\n            dst_reg: target_register,\n            extra_amount: 0,\n        });\n        // Hash join payloads store raw encoded values; apply DECODE for custom\n        // type columns so the result set contains human-readable text.\n        if needs_decode && !program.suppress_custom_type_decode {\n            if let ast::Expr::Column {\n                table: table_ref_id,\n                column,\n                ..\n            } = expr\n            {\n                if let Some(referenced_tables) = referenced_tables {\n                    if let Some((_, table)) =\n                        referenced_tables.find_table_by_internal_id(*table_ref_id)\n                    {\n                        if let Some(col) = table.get_column_at(*column) {\n                            if let Some(type_def) = resolver\n                                .schema()\n                                .get_type_def(&col.ty_str, table.is_strict())\n                            {\n                                if let Some(ref decode_expr) = type_def.decode {\n                                    let skip_label = program.allocate_label();\n                                    program.emit_insn(Insn::IsNull {\n                                        reg: target_register,\n                                        target_pc: skip_label,\n                                    });\n                                    emit_type_expr(\n                                        program,\n                                        decode_expr,\n                                        target_register,\n                                        target_register,\n                                        col,\n                                        type_def,\n                                        resolver,\n                                    )?;\n                                    program.preassign_label_to_next_insn(skip_label);\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n        program.set_collation(collation_ctx);\n        if let Some(span) = constant_span {\n            program.constant_span_end(span);\n        }\n        return Ok(target_register);\n    }\n\n    // At the very start we try to satisfy the expression from an expression index\n    let has_expression_indexes = referenced_tables.is_some_and(|tables| {\n        tables\n            .joined_tables()\n            .iter()\n            .any(|t| !t.expression_index_usages.is_empty())\n    });\n    if has_expression_indexes\n        && try_emit_expression_index_value(program, referenced_tables, expr, target_register)?\n    {\n        if let Some(span) = constant_span {\n            program.constant_span_end(span);\n        }\n        return Ok(target_register);\n    }\n\n    match expr {\n        ast::Expr::SubqueryResult {\n            lhs,\n            not_in,\n            query_type,\n            ..\n        } => {\n            match query_type {\n                SubqueryType::Exists { result_reg } => {\n                    program.emit_insn(Insn::Copy {\n                        src_reg: *result_reg,\n                        dst_reg: target_register,\n                        extra_amount: 0,\n                    });\n                    Ok(target_register)\n                }\n                SubqueryType::In {\n                    cursor_id,\n                    affinity_str,\n                } => {\n                    // jump here when we can definitely skip the row (result = 0/false)\n                    let label_skip_row = program.allocate_label();\n                    // jump here when we can definitely include the row (result = 1/true)\n                    let label_include_row = program.allocate_label();\n                    // jump here when the result should be NULL (unknown)\n                    let label_null_result = program.allocate_label();\n                    // jump here when we need to make extra null-related checks\n                    let label_null_rewind = program.allocate_label();\n                    let label_null_checks_loop_start = program.allocate_label();\n                    let label_null_checks_next = program.allocate_label();\n                    program.emit_insn(Insn::Integer {\n                        value: 0,\n                        dest: target_register,\n                    });\n                    let lhs_columns = match unwrap_parens(lhs.as_ref().unwrap())? {\n                        ast::Expr::Parenthesized(exprs) => {\n                            exprs.iter().map(|e| e.as_ref()).collect()\n                        }\n                        expr => vec![expr],\n                    };\n                    let lhs_column_count = lhs_columns.len();\n                    let lhs_column_regs_start = program.alloc_registers(lhs_column_count);\n                    for (i, lhs_column) in lhs_columns.iter().enumerate() {\n                        translate_expr(\n                            program,\n                            referenced_tables,\n                            lhs_column,\n                            lhs_column_regs_start + i,\n                            resolver,\n                        )?;\n                        // If LHS is NULL, we need to check if ephemeral is empty first.\n                        // - If empty: IN returns FALSE, NOT IN returns TRUE\n                        // - If not empty: result is NULL (unknown)\n                        // Jump to label_null_rewind which does Rewind and handles empty case.\n                        //\n                        // Always emit this check even for NOT NULL columns because NullRow\n                        // (used in ungrouped aggregates when no rows match) overrides all\n                        // column values to NULL regardless of the NOT NULL constraint.\n                        program.emit_insn(Insn::IsNull {\n                            reg: lhs_column_regs_start + i,\n                            target_pc: label_null_rewind,\n                        });\n                    }\n\n                    // Only emit Affinity instruction if there's meaningful affinity to apply\n                    // (i.e., not all BLOB/NONE affinity)\n                    if affinity_str\n                        .chars()\n                        .map(Affinity::from_char)\n                        .any(|a| a != Affinity::Blob)\n                    {\n                        if let Ok(count) = std::num::NonZeroUsize::try_from(lhs_column_count) {\n                            program.emit_insn(Insn::Affinity {\n                                start_reg: lhs_column_regs_start,\n                                count,\n                                affinities: affinity_str.as_ref().clone(),\n                            });\n                        }\n                    }\n\n                    // For NOT IN: empty ephemeral or no all-NULL row means TRUE (include)\n                    // For IN: empty ephemeral or no all-NULL row means FALSE (skip)\n                    let label_on_no_null = if *not_in {\n                        label_include_row\n                    } else {\n                        label_skip_row\n                    };\n\n                    if *not_in {\n                        // NOT IN: skip row if value is found\n                        program.emit_insn(Insn::Found {\n                            cursor_id: *cursor_id,\n                            target_pc: label_skip_row,\n                            record_reg: lhs_column_regs_start,\n                            num_regs: lhs_column_count,\n                        });\n                    } else {\n                        // IN: if value found, include row; otherwise check for NULLs\n                        program.emit_insn(Insn::NotFound {\n                            cursor_id: *cursor_id,\n                            target_pc: label_null_rewind,\n                            record_reg: lhs_column_regs_start,\n                            num_regs: lhs_column_count,\n                        });\n                        program.emit_insn(Insn::Goto {\n                            target_pc: label_include_row,\n                        });\n                    }\n\n                    // Null checking loop: scan ephemeral for any all-NULL tuples.\n                    // If found, result is NULL (unknown). If not found, result depends on IN vs NOT IN.\n                    program.preassign_label_to_next_insn(label_null_rewind);\n                    program.emit_insn(Insn::Rewind {\n                        cursor_id: *cursor_id,\n                        pc_if_empty: label_on_no_null,\n                    });\n                    program.preassign_label_to_next_insn(label_null_checks_loop_start);\n                    let column_check_reg = program.alloc_register();\n                    for (i, affinity) in affinity_str.chars().map(Affinity::from_char).enumerate() {\n                        program.emit_insn(Insn::Column {\n                            cursor_id: *cursor_id,\n                            column: i,\n                            dest: column_check_reg,\n                            default: None,\n                        });\n                        // Ne with NULL operand does NOT jump (comparison is NULL/unknown)\n                        program.emit_insn(Insn::Ne {\n                            lhs: lhs_column_regs_start + i,\n                            rhs: column_check_reg,\n                            target_pc: label_null_checks_next,\n                            flags: CmpInsFlags::default().with_affinity(affinity),\n                            collation: program.curr_collation(),\n                        });\n                    }\n                    // All Ne comparisons fell through -> this row has all NULLs -> result is NULL\n                    program.emit_insn(Insn::Goto {\n                        target_pc: label_null_result,\n                    });\n                    program.preassign_label_to_next_insn(label_null_checks_next);\n                    program.emit_insn(Insn::Next {\n                        cursor_id: *cursor_id,\n                        pc_if_next: label_null_checks_loop_start,\n                    });\n                    // Loop exhausted without finding all-NULL row\n                    program.emit_insn(Insn::Goto {\n                        target_pc: label_on_no_null,\n                    });\n                    // Final result handling:\n                    // label_include_row: result = 1 (TRUE)\n                    // label_skip_row: result = 0 (FALSE)\n                    // label_null_result: result = NULL (unknown)\n                    let label_done = program.allocate_label();\n                    program.preassign_label_to_next_insn(label_include_row);\n                    program.emit_insn(Insn::Integer {\n                        value: 1,\n                        dest: target_register,\n                    });\n                    program.emit_insn(Insn::Goto {\n                        target_pc: label_done,\n                    });\n                    program.preassign_label_to_next_insn(label_skip_row);\n                    program.emit_insn(Insn::Integer {\n                        value: 0,\n                        dest: target_register,\n                    });\n                    program.emit_insn(Insn::Goto {\n                        target_pc: label_done,\n                    });\n                    program.preassign_label_to_next_insn(label_null_result);\n                    program.emit_insn(Insn::Null {\n                        dest: target_register,\n                        dest_end: None,\n                    });\n                    program.preassign_label_to_next_insn(label_done);\n                    Ok(target_register)\n                }\n                SubqueryType::RowValue {\n                    result_reg_start,\n                    num_regs,\n                } => {\n                    assert_register_range_allocated(program, target_register, *num_regs)?;\n                    program.emit_insn(Insn::Copy {\n                        src_reg: *result_reg_start,\n                        dst_reg: target_register,\n                        extra_amount: num_regs - 1,\n                    });\n                    Ok(target_register)\n                }\n            }\n        }\n        ast::Expr::Between { .. } => {\n            translate_between_expr(\n                program,\n                referenced_tables,\n                expr.clone(),\n                target_register,\n                resolver,\n            )?;\n            Ok(target_register)\n        }\n        ast::Expr::Binary(e1, op, e2) => {\n            // Handle IS TRUE/IS FALSE/IS NOT TRUE/IS NOT FALSE specially.\n            // These use truth semantics (only non-zero numbers are truthy) rather than equality.\n            if let Some((is_not, is_true_literal)) = match (op, e2.as_ref()) {\n                (ast::Operator::Is, ast::Expr::Literal(ast::Literal::True)) => Some((false, true)),\n                (ast::Operator::Is, ast::Expr::Literal(ast::Literal::False)) => {\n                    Some((false, false))\n                }\n                (ast::Operator::IsNot, ast::Expr::Literal(ast::Literal::True)) => {\n                    Some((true, true))\n                }\n                (ast::Operator::IsNot, ast::Expr::Literal(ast::Literal::False)) => {\n                    Some((true, false))\n                }\n                _ => None,\n            } {\n                let reg = program.alloc_register();\n                translate_expr(program, referenced_tables, e1, reg, resolver)?;\n                // For NULL: IS variants return 0, IS NOT variants return 1\n                // For non-NULL: IS TRUE/IS NOT FALSE return truthy, IS FALSE/IS NOT TRUE return !truthy\n                let null_value = is_not;\n                let invert = is_not == is_true_literal;\n                program.emit_insn(Insn::IsTrue {\n                    reg,\n                    dest: target_register,\n                    null_value,\n                    invert,\n                });\n                if let Some(span) = constant_span {\n                    program.constant_span_end(span);\n                }\n                return Ok(target_register);\n            }\n\n            // Check if either operand has a custom type with a matching operator\n            if let Some(resolved) =\n                find_custom_type_operator(e1, e2, op, referenced_tables, resolver)\n            {\n                let result_reg = emit_custom_type_operator(\n                    program,\n                    referenced_tables,\n                    e1,\n                    e2,\n                    &resolved,\n                    resolver,\n                )?;\n                if result_reg != target_register {\n                    program.emit_insn(Insn::Copy {\n                        src_reg: result_reg,\n                        dst_reg: target_register,\n                        extra_amount: 0,\n                    });\n                }\n                return Ok(target_register);\n            }\n\n            binary_expr_shared(\n                program,\n                referenced_tables,\n                e1,\n                e2,\n                op,\n                target_register,\n                resolver,\n                BinaryEmitMode::Value,\n            )?;\n            Ok(target_register)\n        }\n        ast::Expr::Case {\n            base,\n            when_then_pairs,\n            else_expr,\n        } => {\n            // There's two forms of CASE, one which checks a base expression for equality\n            // against the WHEN values, and returns the corresponding THEN value if it matches:\n            //   CASE 2 WHEN 1 THEN 'one' WHEN 2 THEN 'two' ELSE 'many' END\n            // And one which evaluates a series of boolean predicates:\n            //   CASE WHEN is_good THEN 'good' WHEN is_bad THEN 'bad' ELSE 'okay' END\n            // This just changes which sort of branching instruction to issue, after we\n            // generate the expression if needed.\n            let return_label = program.allocate_label();\n            let mut next_case_label = program.allocate_label();\n            // Only allocate a reg to hold the base expression if one was provided.\n            // And base_reg then becomes the flag we check to see which sort of\n            // case statement we're processing.\n            let base_reg = base.as_ref().map(|_| program.alloc_register());\n            let expr_reg = program.alloc_register();\n            if let Some(base_expr) = base {\n                translate_expr(\n                    program,\n                    referenced_tables,\n                    base_expr,\n                    base_reg.unwrap(),\n                    resolver,\n                )?;\n            };\n            for (when_expr, then_expr) in when_then_pairs {\n                translate_expr_no_constant_opt(\n                    program,\n                    referenced_tables,\n                    when_expr,\n                    expr_reg,\n                    resolver,\n                    NoConstantOptReason::RegisterReuse,\n                )?;\n                match base_reg {\n                    // CASE 1 WHEN 0 THEN 0 ELSE 1 becomes 1==0, Ne branch to next clause\n                    Some(base_reg) => program.emit_insn(Insn::Ne {\n                        lhs: base_reg,\n                        rhs: expr_reg,\n                        target_pc: next_case_label,\n                        // A NULL result is considered untrue when evaluating WHEN terms.\n                        flags: CmpInsFlags::default().jump_if_null(),\n                        collation: program.curr_collation(),\n                    }),\n                    // CASE WHEN 0 THEN 0 ELSE 1 becomes ifnot 0 branch to next clause\n                    None => program.emit_insn(Insn::IfNot {\n                        reg: expr_reg,\n                        target_pc: next_case_label,\n                        jump_if_null: true,\n                    }),\n                };\n                // THEN...\n                translate_expr_no_constant_opt(\n                    program,\n                    referenced_tables,\n                    then_expr,\n                    target_register,\n                    resolver,\n                    NoConstantOptReason::RegisterReuse,\n                )?;\n                program.emit_insn(Insn::Goto {\n                    target_pc: return_label,\n                });\n                // This becomes either the next WHEN, or in the last WHEN/THEN, we're\n                // assured to have at least one instruction corresponding to the ELSE immediately follow.\n                program.preassign_label_to_next_insn(next_case_label);\n                next_case_label = program.allocate_label();\n            }\n            match else_expr {\n                Some(expr) => {\n                    translate_expr_no_constant_opt(\n                        program,\n                        referenced_tables,\n                        expr,\n                        target_register,\n                        resolver,\n                        NoConstantOptReason::RegisterReuse,\n                    )?;\n                }\n                // If ELSE isn't specified, it means ELSE null.\n                None => {\n                    program.emit_insn(Insn::Null {\n                        dest: target_register,\n                        dest_end: None,\n                    });\n                }\n            };\n            program.preassign_label_to_next_insn(return_label);\n            Ok(target_register)\n        }\n        ast::Expr::Cast { expr, type_name } => {\n            translate_expr(program, referenced_tables, expr, target_register, resolver)?;\n\n            // Check if casting to a custom type\n            if let Some(ref tn) = type_name {\n                if let Some(type_def) = resolver.schema().get_type_def_unchecked(&tn.name) {\n                    // Build ty_params from AST TypeSize so parametric types\n                    // (e.g. numeric(10,2)) get their parameters passed through.\n                    let ty_params: Vec<Box<ast::Expr>> = match &tn.size {\n                        Some(ast::TypeSize::MaxSize(e)) => vec![e.clone()],\n                        Some(ast::TypeSize::TypeSize(e1, e2)) => {\n                            vec![e1.clone(), e2.clone()]\n                        }\n                        None => Vec::new(),\n                    };\n\n                    // If the custom type requires parameters but the CAST\n                    // doesn't provide them (e.g. CAST(x AS NUMERIC) vs\n                    // CAST(x AS numeric(10,2))), fall through to regular CAST.\n                    let user_param_count = type_def.user_params().count();\n                    if user_param_count == 0 || ty_params.len() == user_param_count {\n                        let mut cast_col = Column::new(\n                            None,\n                            tn.name.clone(),\n                            None,\n                            None,\n                            Type::Null,\n                            None,\n                            ColDef::default(),\n                        );\n                        cast_col.ty_params = ty_params;\n\n                        // CAST to custom type applies only the encode function,\n                        // producing the stored representation.\n                        // e.g. CAST(42 AS cents) → 4200\n                        if let Some(ref encode_expr) = type_def.encode {\n                            emit_type_expr(\n                                program,\n                                encode_expr,\n                                target_register,\n                                target_register,\n                                &cast_col,\n                                type_def,\n                                resolver,\n                            )?;\n                        }\n                        return Ok(target_register);\n                    }\n                }\n            }\n\n            // SQLite allows CAST(x AS) without a type name, treating it as NUMERIC affinity\n            let type_affinity = type_name\n                .as_ref()\n                .map(|t| Affinity::affinity(&t.name))\n                .unwrap_or(Affinity::Numeric);\n            program.emit_insn(Insn::Cast {\n                reg: target_register,\n                affinity: type_affinity,\n            });\n            Ok(target_register)\n        }\n        ast::Expr::Collate(expr, collation) => {\n            // First translate inner expr, then set the curr collation. If we set curr collation before,\n            // it may be overwritten later by inner translate.\n            translate_expr(program, referenced_tables, expr, target_register, resolver)?;\n            let collation = CollationSeq::new(collation.as_str())?;\n            program.set_collation(Some((collation, true)));\n            Ok(target_register)\n        }\n        ast::Expr::DoublyQualified(_, _, _) => {\n            crate::bail_parse_error!(\"DoublyQualified should have been rewritten in optimizer\")\n        }\n        ast::Expr::Exists(_) => {\n            crate::bail_parse_error!(\"EXISTS is not supported in this position\")\n        }\n        ast::Expr::FunctionCall {\n            name,\n            distinctness: _,\n            args,\n            filter_over,\n            order_by: _,\n        } => {\n            let args_count = args.len();\n            let func_type = resolver.resolve_function(name.as_str(), args_count);\n\n            if func_type.is_none() {\n                crate::bail_parse_error!(\"unknown function {}\", name.as_str());\n            }\n\n            let func_ctx = FuncCtx {\n                func: func_type.unwrap(),\n                arg_count: args_count,\n            };\n\n            match &func_ctx.func {\n                Func::Agg(_) => {\n                    crate::bail_parse_error!(\n                        \"misuse of {} function {}()\",\n                        if filter_over.over_clause.is_some() {\n                            \"window\"\n                        } else {\n                            \"aggregate\"\n                        },\n                        name.as_str()\n                    )\n                }\n                Func::Window(_) => {\n                    crate::bail_parse_error!(\"misuse of window function {}()\", name.as_str())\n                }\n                Func::External(_) => {\n                    let regs = program.alloc_registers(args_count);\n                    for (i, arg_expr) in args.iter().enumerate() {\n                        translate_expr(program, referenced_tables, arg_expr, regs + i, resolver)?;\n                    }\n\n                    // Use shared function call helper\n                    let arg_registers: Vec<usize> = (regs..regs + args_count).collect();\n                    emit_function_call(program, func_ctx, &arg_registers, target_register)?;\n\n                    Ok(target_register)\n                }\n                #[cfg(feature = \"json\")]\n                Func::Json(j) => match j {\n                    JsonFunc::Json | JsonFunc::Jsonb => {\n                        let args = expect_arguments_exact!(args, 1, j);\n\n                        translate_function(\n                            program,\n                            args,\n                            referenced_tables,\n                            resolver,\n                            target_register,\n                            func_ctx,\n                        )\n                    }\n                    JsonFunc::JsonArray\n                    | JsonFunc::JsonbArray\n                    | JsonFunc::JsonExtract\n                    | JsonFunc::JsonSet\n                    | JsonFunc::JsonbSet\n                    | JsonFunc::JsonbExtract\n                    | JsonFunc::JsonReplace\n                    | JsonFunc::JsonbReplace\n                    | JsonFunc::JsonbRemove\n                    | JsonFunc::JsonInsert\n                    | JsonFunc::JsonbInsert => translate_function(\n                        program,\n                        args,\n                        referenced_tables,\n                        resolver,\n                        target_register,\n                        func_ctx,\n                    ),\n                    JsonFunc::JsonArrowExtract | JsonFunc::JsonArrowShiftExtract => {\n                        unreachable!(\n                            \"These two functions are only reachable via the -> and ->> operators\"\n                        )\n                    }\n                    JsonFunc::JsonArrayLength | JsonFunc::JsonType => {\n                        let args = expect_arguments_max!(args, 2, j);\n\n                        translate_function(\n                            program,\n                            args,\n                            referenced_tables,\n                            resolver,\n                            target_register,\n                            func_ctx,\n                        )\n                    }\n                    JsonFunc::JsonErrorPosition => {\n                        if args.len() != 1 {\n                            crate::bail_parse_error!(\n                                \"{} function with not exactly 1 argument\",\n                                j.to_string()\n                            );\n                        }\n                        let json_reg = program.alloc_register();\n                        translate_expr(program, referenced_tables, &args[0], json_reg, resolver)?;\n                        program.emit_insn(Insn::Function {\n                            constant_mask: 0,\n                            start_reg: json_reg,\n                            dest: target_register,\n                            func: func_ctx,\n                        });\n                        Ok(target_register)\n                    }\n                    JsonFunc::JsonObject | JsonFunc::JsonbObject => {\n                        let args = expect_arguments_even!(args, j);\n\n                        translate_function(\n                            program,\n                            args,\n                            referenced_tables,\n                            resolver,\n                            target_register,\n                            func_ctx,\n                        )\n                    }\n                    JsonFunc::JsonValid => {\n                        let args = expect_arguments_exact!(args, 1, j);\n                        translate_function(\n                            program,\n                            args,\n                            referenced_tables,\n                            resolver,\n                            target_register,\n                            func_ctx,\n                        )\n                    }\n                    JsonFunc::JsonPatch | JsonFunc::JsonbPatch => {\n                        let args = expect_arguments_exact!(args, 2, j);\n                        translate_function(\n                            program,\n                            args,\n                            referenced_tables,\n                            resolver,\n                            target_register,\n                            func_ctx,\n                        )\n                    }\n                    JsonFunc::JsonRemove => {\n                        let start_reg = program.alloc_registers(args.len().max(1));\n                        for (i, arg) in args.iter().enumerate() {\n                            // register containing result of each argument expression\n                            translate_expr(\n                                program,\n                                referenced_tables,\n                                arg,\n                                start_reg + i,\n                                resolver,\n                            )?;\n                        }\n                        program.emit_insn(Insn::Function {\n                            constant_mask: 0,\n                            start_reg,\n                            dest: target_register,\n                            func: func_ctx,\n                        });\n                        Ok(target_register)\n                    }\n                    JsonFunc::JsonQuote => {\n                        let args = expect_arguments_exact!(args, 1, j);\n                        translate_function(\n                            program,\n                            args,\n                            referenced_tables,\n                            resolver,\n                            target_register,\n                            func_ctx,\n                        )\n                    }\n                    JsonFunc::JsonPretty => {\n                        let args = expect_arguments_max!(args, 2, j);\n\n                        translate_function(\n                            program,\n                            args,\n                            referenced_tables,\n                            resolver,\n                            target_register,\n                            func_ctx,\n                        )\n                    }\n                },\n                Func::Vector(vector_func) => match vector_func {\n                    VectorFunc::Vector | VectorFunc::Vector32 => {\n                        let args = expect_arguments_exact!(args, 1, vector_func);\n                        let start_reg = program.alloc_register();\n                        translate_expr(program, referenced_tables, &args[0], start_reg, resolver)?;\n\n                        emit_function_call(program, func_ctx, &[start_reg], target_register)?;\n                        Ok(target_register)\n                    }\n                    VectorFunc::Vector32Sparse => {\n                        let args = expect_arguments_exact!(args, 1, vector_func);\n                        let start_reg = program.alloc_register();\n                        translate_expr(program, referenced_tables, &args[0], start_reg, resolver)?;\n\n                        emit_function_call(program, func_ctx, &[start_reg], target_register)?;\n                        Ok(target_register)\n                    }\n                    VectorFunc::Vector64 => {\n                        let args = expect_arguments_exact!(args, 1, vector_func);\n                        let start_reg = program.alloc_register();\n                        translate_expr(program, referenced_tables, &args[0], start_reg, resolver)?;\n\n                        emit_function_call(program, func_ctx, &[start_reg], target_register)?;\n                        Ok(target_register)\n                    }\n                    VectorFunc::Vector8 => {\n                        let args = expect_arguments_exact!(args, 1, vector_func);\n                        let start_reg = program.alloc_register();\n                        translate_expr(program, referenced_tables, &args[0], start_reg, resolver)?;\n\n                        emit_function_call(program, func_ctx, &[start_reg], target_register)?;\n                        Ok(target_register)\n                    }\n                    VectorFunc::Vector1Bit => {\n                        let args = expect_arguments_exact!(args, 1, vector_func);\n                        let start_reg = program.alloc_register();\n                        translate_expr(program, referenced_tables, &args[0], start_reg, resolver)?;\n\n                        emit_function_call(program, func_ctx, &[start_reg], target_register)?;\n                        Ok(target_register)\n                    }\n                    VectorFunc::VectorExtract => {\n                        let args = expect_arguments_exact!(args, 1, vector_func);\n                        let start_reg = program.alloc_register();\n                        translate_expr(program, referenced_tables, &args[0], start_reg, resolver)?;\n\n                        emit_function_call(program, func_ctx, &[start_reg], target_register)?;\n                        Ok(target_register)\n                    }\n                    VectorFunc::VectorDistanceCos => {\n                        let args = expect_arguments_exact!(args, 2, vector_func);\n                        let regs = program.alloc_registers(2);\n                        translate_expr(program, referenced_tables, &args[0], regs, resolver)?;\n                        translate_expr(program, referenced_tables, &args[1], regs + 1, resolver)?;\n\n                        emit_function_call(program, func_ctx, &[regs, regs + 1], target_register)?;\n                        Ok(target_register)\n                    }\n                    VectorFunc::VectorDistanceL2 => {\n                        let args = expect_arguments_exact!(args, 2, vector_func);\n                        let regs = program.alloc_registers(2);\n                        translate_expr(program, referenced_tables, &args[0], regs, resolver)?;\n                        translate_expr(program, referenced_tables, &args[1], regs + 1, resolver)?;\n\n                        emit_function_call(program, func_ctx, &[regs, regs + 1], target_register)?;\n                        Ok(target_register)\n                    }\n                    VectorFunc::VectorDistanceJaccard => {\n                        let args = expect_arguments_exact!(args, 2, vector_func);\n                        let regs = program.alloc_registers(2);\n                        translate_expr(program, referenced_tables, &args[0], regs, resolver)?;\n                        translate_expr(program, referenced_tables, &args[1], regs + 1, resolver)?;\n\n                        emit_function_call(program, func_ctx, &[regs, regs + 1], target_register)?;\n                        Ok(target_register)\n                    }\n                    VectorFunc::VectorDistanceDot => {\n                        let args = expect_arguments_exact!(args, 2, vector_func);\n                        let regs = program.alloc_registers(2);\n                        translate_expr(program, referenced_tables, &args[0], regs, resolver)?;\n                        translate_expr(program, referenced_tables, &args[1], regs + 1, resolver)?;\n\n                        emit_function_call(program, func_ctx, &[regs, regs + 1], target_register)?;\n                        Ok(target_register)\n                    }\n                    VectorFunc::VectorConcat => {\n                        let args = expect_arguments_exact!(args, 2, vector_func);\n                        let regs = program.alloc_registers(2);\n                        translate_expr(program, referenced_tables, &args[0], regs, resolver)?;\n                        translate_expr(program, referenced_tables, &args[1], regs + 1, resolver)?;\n\n                        emit_function_call(program, func_ctx, &[regs, regs + 1], target_register)?;\n                        Ok(target_register)\n                    }\n                    VectorFunc::VectorSlice => {\n                        let args = expect_arguments_exact!(args, 3, vector_func);\n                        let regs = program.alloc_registers(3);\n                        translate_expr(program, referenced_tables, &args[0], regs, resolver)?;\n                        translate_expr(program, referenced_tables, &args[1], regs + 1, resolver)?;\n                        translate_expr(program, referenced_tables, &args[2], regs + 2, resolver)?;\n\n                        emit_function_call(program, func_ctx, &[regs, regs + 2], target_register)?;\n                        Ok(target_register)\n                    }\n                },\n                Func::Scalar(srf) => {\n                    match srf {\n                        ScalarFunc::Cast => {\n                            unreachable!(\"this is always ast::Expr::Cast\")\n                        }\n                        ScalarFunc::Array => {\n                            resolver.require_custom_types(\"Array features\")?;\n                            let start_reg = program.alloc_registers(args.len());\n                            for (i, arg) in args.iter().enumerate() {\n                                translate_expr(\n                                    program,\n                                    referenced_tables,\n                                    arg,\n                                    start_reg + i,\n                                    resolver,\n                                )?;\n                            }\n                            program.emit_insn(Insn::MakeArray {\n                                start_reg,\n                                count: args.len(),\n                                dest: target_register,\n                            });\n                            Ok(target_register)\n                        }\n                        ScalarFunc::ArrayElement => {\n                            resolver.require_custom_types(\"Array features\")?;\n                            let args = expect_arguments_exact!(args, 2, srf);\n                            let base_reg = program.alloc_register();\n                            translate_expr(\n                                program,\n                                referenced_tables,\n                                &args[0],\n                                base_reg,\n                                resolver,\n                            )?;\n                            let index_reg = program.alloc_register();\n                            translate_expr(\n                                program,\n                                referenced_tables,\n                                &args[1],\n                                index_reg,\n                                resolver,\n                            )?;\n                            program.emit_insn(Insn::ArrayElement {\n                                array_reg: base_reg,\n                                index_reg,\n                                dest: target_register,\n                            });\n                            Ok(target_register)\n                        }\n                        ScalarFunc::ArraySetElement => {\n                            resolver.require_custom_types(\"Array features\")?;\n                            let args = expect_arguments_exact!(args, 3, srf);\n                            let array_reg = program.alloc_register();\n                            translate_expr(\n                                program,\n                                referenced_tables,\n                                &args[0],\n                                array_reg,\n                                resolver,\n                            )?;\n                            let index_reg = program.alloc_register();\n                            translate_expr(\n                                program,\n                                referenced_tables,\n                                &args[1],\n                                index_reg,\n                                resolver,\n                            )?;\n                            let value_reg = program.alloc_register();\n                            translate_expr(\n                                program,\n                                referenced_tables,\n                                &args[2],\n                                value_reg,\n                                resolver,\n                            )?;\n                            program.emit_insn(Insn::ArraySetElement {\n                                array_reg,\n                                index_reg,\n                                value_reg,\n                                dest: target_register,\n                            });\n                            Ok(target_register)\n                        }\n                        ScalarFunc::Changes => {\n                            if !args.is_empty() {\n                                crate::bail_parse_error!(\n                                    \"{} function with more than 0 arguments\",\n                                    srf\n                                );\n                            }\n                            let start_reg = program.alloc_register();\n                            program.emit_insn(Insn::Function {\n                                constant_mask: 0,\n                                start_reg,\n                                dest: target_register,\n                                func: func_ctx,\n                            });\n                            Ok(target_register)\n                        }\n                        ScalarFunc::Char => translate_function(\n                            program,\n                            args,\n                            referenced_tables,\n                            resolver,\n                            target_register,\n                            func_ctx,\n                        ),\n                        ScalarFunc::Coalesce => {\n                            let args = expect_arguments_min!(args, 2, srf);\n\n                            // coalesce function is implemented as a series of not null checks\n                            // whenever a not null check succeeds, we jump to the end of the series\n                            let label_coalesce_end = program.allocate_label();\n                            for (index, arg) in args.iter().enumerate() {\n                                let reg = translate_expr_no_constant_opt(\n                                    program,\n                                    referenced_tables,\n                                    arg,\n                                    target_register,\n                                    resolver,\n                                    NoConstantOptReason::RegisterReuse,\n                                )?;\n                                if index < args.len() - 1 {\n                                    program.emit_insn(Insn::NotNull {\n                                        reg,\n                                        target_pc: label_coalesce_end,\n                                    });\n                                }\n                            }\n                            program.preassign_label_to_next_insn(label_coalesce_end);\n\n                            Ok(target_register)\n                        }\n                        ScalarFunc::LastInsertRowid => {\n                            let regs = program.alloc_register();\n                            program.emit_insn(Insn::Function {\n                                constant_mask: 0,\n                                start_reg: regs,\n                                dest: target_register,\n                                func: func_ctx,\n                            });\n                            Ok(target_register)\n                        }\n                        ScalarFunc::Concat => {\n                            if args.is_empty() {\n                                crate::bail_parse_error!(\n                                    \"{} function with no arguments\",\n                                    srf.to_string()\n                                );\n                            };\n                            // Allocate all registers upfront to ensure they're consecutive,\n                            // since translate_expr may allocate internal registers.\n                            let start_reg = program.alloc_registers(args.len());\n                            for (i, arg) in args.iter().enumerate() {\n                                translate_expr(\n                                    program,\n                                    referenced_tables,\n                                    arg,\n                                    start_reg + i,\n                                    resolver,\n                                )?;\n                            }\n                            program.emit_insn(Insn::Function {\n                                constant_mask: 0,\n                                start_reg,\n                                dest: target_register,\n                                func: func_ctx,\n                            });\n                            Ok(target_register)\n                        }\n                        ScalarFunc::ConcatWs => {\n                            let args = expect_arguments_min!(args, 2, srf);\n\n                            let temp_register = program.alloc_registers(args.len() + 1);\n                            for (i, arg) in args.iter().enumerate() {\n                                translate_expr(\n                                    program,\n                                    referenced_tables,\n                                    arg,\n                                    temp_register + i + 1,\n                                    resolver,\n                                )?;\n                            }\n                            program.emit_insn(Insn::Function {\n                                constant_mask: 0,\n                                start_reg: temp_register + 1,\n                                dest: temp_register,\n                                func: func_ctx,\n                            });\n\n                            program.emit_insn(Insn::Copy {\n                                src_reg: temp_register,\n                                dst_reg: target_register,\n                                extra_amount: 0,\n                            });\n                            Ok(target_register)\n                        }\n                        ScalarFunc::IfNull => {\n                            if args.len() != 2 {\n                                crate::bail_parse_error!(\n                                    \"{} function requires exactly 2 arguments\",\n                                    srf.to_string()\n                                );\n                            }\n\n                            let temp_reg = program.alloc_register();\n                            translate_expr_no_constant_opt(\n                                program,\n                                referenced_tables,\n                                &args[0],\n                                temp_reg,\n                                resolver,\n                                NoConstantOptReason::RegisterReuse,\n                            )?;\n                            let before_copy_label = program.allocate_label();\n                            program.emit_insn(Insn::NotNull {\n                                reg: temp_reg,\n                                target_pc: before_copy_label,\n                            });\n\n                            translate_expr_no_constant_opt(\n                                program,\n                                referenced_tables,\n                                &args[1],\n                                temp_reg,\n                                resolver,\n                                NoConstantOptReason::RegisterReuse,\n                            )?;\n                            program.resolve_label(before_copy_label, program.offset());\n                            program.emit_insn(Insn::Copy {\n                                src_reg: temp_reg,\n                                dst_reg: target_register,\n                                extra_amount: 0,\n                            });\n\n                            Ok(target_register)\n                        }\n                        ScalarFunc::Iif => {\n                            let args = expect_arguments_min!(args, 2, srf);\n\n                            let iif_end_label = program.allocate_label();\n                            let condition_reg = program.alloc_register();\n\n                            for pair in args.chunks_exact(2) {\n                                let condition_expr = &pair[0];\n                                let value_expr = &pair[1];\n                                let next_check_label = program.allocate_label();\n\n                                translate_expr_no_constant_opt(\n                                    program,\n                                    referenced_tables,\n                                    condition_expr,\n                                    condition_reg,\n                                    resolver,\n                                    NoConstantOptReason::RegisterReuse,\n                                )?;\n\n                                program.emit_insn(Insn::IfNot {\n                                    reg: condition_reg,\n                                    target_pc: next_check_label,\n                                    jump_if_null: true,\n                                });\n\n                                translate_expr_no_constant_opt(\n                                    program,\n                                    referenced_tables,\n                                    value_expr,\n                                    target_register,\n                                    resolver,\n                                    NoConstantOptReason::RegisterReuse,\n                                )?;\n                                program.emit_insn(Insn::Goto {\n                                    target_pc: iif_end_label,\n                                });\n\n                                program.preassign_label_to_next_insn(next_check_label);\n                            }\n\n                            if args.len() % 2 != 0 {\n                                translate_expr_no_constant_opt(\n                                    program,\n                                    referenced_tables,\n                                    args.last().unwrap(),\n                                    target_register,\n                                    resolver,\n                                    NoConstantOptReason::RegisterReuse,\n                                )?;\n                            } else {\n                                program.emit_insn(Insn::Null {\n                                    dest: target_register,\n                                    dest_end: None,\n                                });\n                            }\n\n                            program.preassign_label_to_next_insn(iif_end_label);\n                            Ok(target_register)\n                        }\n\n                        ScalarFunc::Glob | ScalarFunc::Like => {\n                            if args.len() < 2 {\n                                crate::bail_parse_error!(\n                                    \"{} function with less than 2 arguments\",\n                                    srf.to_string()\n                                );\n                            }\n                            let func_registers = program.alloc_registers(args.len());\n                            for (i, arg) in args.iter().enumerate() {\n                                let _ = translate_expr(\n                                    program,\n                                    referenced_tables,\n                                    arg,\n                                    func_registers + i,\n                                    resolver,\n                                )?;\n                            }\n                            program.emit_insn(Insn::Function {\n                                constant_mask: 0,\n                                start_reg: func_registers,\n                                dest: target_register,\n                                func: func_ctx,\n                            });\n                            Ok(target_register)\n                        }\n                        ScalarFunc::Abs\n                        | ScalarFunc::Lower\n                        | ScalarFunc::Upper\n                        | ScalarFunc::Length\n                        | ScalarFunc::OctetLength\n                        | ScalarFunc::Typeof\n                        | ScalarFunc::Unicode\n                        | ScalarFunc::Quote\n                        | ScalarFunc::RandomBlob\n                        | ScalarFunc::Sign\n                        | ScalarFunc::Soundex\n                        | ScalarFunc::ZeroBlob => {\n                            let args = expect_arguments_exact!(args, 1, srf);\n                            let start_reg = program.alloc_register();\n                            translate_expr(\n                                program,\n                                referenced_tables,\n                                &args[0],\n                                start_reg,\n                                resolver,\n                            )?;\n                            program.emit_insn(Insn::Function {\n                                constant_mask: 0,\n                                start_reg,\n                                dest: target_register,\n                                func: func_ctx,\n                            });\n                            Ok(target_register)\n                        }\n                        #[cfg(feature = \"fs\")]\n                        #[cfg(not(target_family = \"wasm\"))]\n                        ScalarFunc::LoadExtension => {\n                            let args = expect_arguments_exact!(args, 1, srf);\n                            let start_reg = program.alloc_register();\n                            translate_expr(\n                                program,\n                                referenced_tables,\n                                &args[0],\n                                start_reg,\n                                resolver,\n                            )?;\n                            program.emit_insn(Insn::Function {\n                                constant_mask: 0,\n                                start_reg,\n                                dest: target_register,\n                                func: func_ctx,\n                            });\n                            Ok(target_register)\n                        }\n                        ScalarFunc::Random => {\n                            if !args.is_empty() {\n                                crate::bail_parse_error!(\n                                    \"{} function with arguments\",\n                                    srf.to_string()\n                                );\n                            }\n                            let regs = program.alloc_register();\n                            program.emit_insn(Insn::Function {\n                                constant_mask: 0,\n                                start_reg: regs,\n                                dest: target_register,\n                                func: func_ctx,\n                            });\n                            Ok(target_register)\n                        }\n                        ScalarFunc::Date | ScalarFunc::DateTime | ScalarFunc::JulianDay => {\n                            let start_reg = program.alloc_registers(args.len().max(1));\n                            for (i, arg) in args.iter().enumerate() {\n                                // register containing result of each argument expression\n                                translate_expr(\n                                    program,\n                                    referenced_tables,\n                                    arg,\n                                    start_reg + i,\n                                    resolver,\n                                )?;\n                            }\n                            program.emit_insn(Insn::Function {\n                                constant_mask: 0,\n                                start_reg,\n                                dest: target_register,\n                                func: func_ctx,\n                            });\n                            Ok(target_register)\n                        }\n                        ScalarFunc::Substr | ScalarFunc::Substring => {\n                            if !(args.len() == 2 || args.len() == 3) {\n                                crate::bail_parse_error!(\n                                    \"{} function with wrong number of arguments\",\n                                    srf.to_string()\n                                )\n                            }\n\n                            let str_reg = program.alloc_register();\n                            let start_reg = program.alloc_register();\n                            let length_reg = program.alloc_register();\n                            let str_reg = translate_expr(\n                                program,\n                                referenced_tables,\n                                &args[0],\n                                str_reg,\n                                resolver,\n                            )?;\n                            let _ = translate_expr(\n                                program,\n                                referenced_tables,\n                                &args[1],\n                                start_reg,\n                                resolver,\n                            )?;\n                            if args.len() == 3 {\n                                translate_expr(\n                                    program,\n                                    referenced_tables,\n                                    &args[2],\n                                    length_reg,\n                                    resolver,\n                                )?;\n                            }\n                            program.emit_insn(Insn::Function {\n                                constant_mask: 0,\n                                start_reg: str_reg,\n                                dest: target_register,\n                                func: func_ctx,\n                            });\n                            Ok(target_register)\n                        }\n                        ScalarFunc::Hex => {\n                            if args.len() != 1 {\n                                crate::bail_parse_error!(\n                                    \"hex function must have exactly 1 argument\",\n                                );\n                            }\n                            let start_reg = program.alloc_register();\n                            translate_expr(\n                                program,\n                                referenced_tables,\n                                &args[0],\n                                start_reg,\n                                resolver,\n                            )?;\n                            program.emit_insn(Insn::Function {\n                                constant_mask: 0,\n                                start_reg,\n                                dest: target_register,\n                                func: func_ctx,\n                            });\n                            Ok(target_register)\n                        }\n                        ScalarFunc::UnixEpoch => {\n                            let start_reg = program.alloc_registers(args.len().max(1));\n                            for (i, arg) in args.iter().enumerate() {\n                                // register containing result of each argument expression\n                                translate_expr(\n                                    program,\n                                    referenced_tables,\n                                    arg,\n                                    start_reg + i,\n                                    resolver,\n                                )?;\n                            }\n                            program.emit_insn(Insn::Function {\n                                constant_mask: 0,\n                                start_reg,\n                                dest: target_register,\n                                func: func_ctx,\n                            });\n                            Ok(target_register)\n                        }\n                        ScalarFunc::Time => {\n                            let start_reg = program.alloc_registers(args.len().max(1));\n                            for (i, arg) in args.iter().enumerate() {\n                                // register containing result of each argument expression\n                                translate_expr(\n                                    program,\n                                    referenced_tables,\n                                    arg,\n                                    start_reg + i,\n                                    resolver,\n                                )?;\n                            }\n                            program.emit_insn(Insn::Function {\n                                constant_mask: 0,\n                                start_reg,\n                                dest: target_register,\n                                func: func_ctx,\n                            });\n                            Ok(target_register)\n                        }\n                        ScalarFunc::TimeDiff => {\n                            let args = expect_arguments_exact!(args, 2, srf);\n\n                            let start_reg = program.alloc_registers(2);\n                            translate_expr(\n                                program,\n                                referenced_tables,\n                                &args[0],\n                                start_reg,\n                                resolver,\n                            )?;\n                            translate_expr(\n                                program,\n                                referenced_tables,\n                                &args[1],\n                                start_reg + 1,\n                                resolver,\n                            )?;\n\n                            program.emit_insn(Insn::Function {\n                                constant_mask: 0,\n                                start_reg,\n                                dest: target_register,\n                                func: func_ctx,\n                            });\n                            Ok(target_register)\n                        }\n                        ScalarFunc::TotalChanges => {\n                            if !args.is_empty() {\n                                crate::bail_parse_error!(\n                                    \"{} function with more than 0 arguments\",\n                                    srf.to_string()\n                                );\n                            }\n                            let start_reg = program.alloc_register();\n                            program.emit_insn(Insn::Function {\n                                constant_mask: 0,\n                                start_reg,\n                                dest: target_register,\n                                func: func_ctx,\n                            });\n                            Ok(target_register)\n                        }\n                        ScalarFunc::Trim\n                        | ScalarFunc::LTrim\n                        | ScalarFunc::RTrim\n                        | ScalarFunc::Round\n                        | ScalarFunc::Unhex => {\n                            let args = expect_arguments_max!(args, 2, srf);\n\n                            let start_reg = program.alloc_registers(args.len());\n                            for (i, arg) in args.iter().enumerate() {\n                                translate_expr(\n                                    program,\n                                    referenced_tables,\n                                    arg,\n                                    start_reg + i,\n                                    resolver,\n                                )?;\n                            }\n                            program.emit_insn(Insn::Function {\n                                constant_mask: 0,\n                                start_reg,\n                                dest: target_register,\n                                func: func_ctx,\n                            });\n                            Ok(target_register)\n                        }\n                        ScalarFunc::Min => {\n                            if args.is_empty() {\n                                crate::bail_parse_error!(\"min function with no arguments\");\n                            }\n                            let start_reg = program.alloc_registers(args.len());\n                            for (i, arg) in args.iter().enumerate() {\n                                translate_expr(\n                                    program,\n                                    referenced_tables,\n                                    arg,\n                                    start_reg + i,\n                                    resolver,\n                                )?;\n                            }\n\n                            program.emit_insn(Insn::Function {\n                                constant_mask: 0,\n                                start_reg,\n                                dest: target_register,\n                                func: func_ctx,\n                            });\n                            Ok(target_register)\n                        }\n                        ScalarFunc::Max => {\n                            if args.is_empty() {\n                                crate::bail_parse_error!(\"min function with no arguments\");\n                            }\n                            let start_reg = program.alloc_registers(args.len());\n                            for (i, arg) in args.iter().enumerate() {\n                                translate_expr(\n                                    program,\n                                    referenced_tables,\n                                    arg,\n                                    start_reg + i,\n                                    resolver,\n                                )?;\n                            }\n\n                            program.emit_insn(Insn::Function {\n                                constant_mask: 0,\n                                start_reg,\n                                dest: target_register,\n                                func: func_ctx,\n                            });\n                            Ok(target_register)\n                        }\n                        ScalarFunc::Nullif | ScalarFunc::Instr => {\n                            if args.len() != 2 {\n                                crate::bail_parse_error!(\n                                    \"{} function must have two argument\",\n                                    srf.to_string()\n                                );\n                            }\n\n                            // Allocate both registers first to ensure they're consecutive,\n                            // since translate_expr may allocate internal registers.\n                            let first_reg = program.alloc_register();\n                            let second_reg = program.alloc_register();\n                            translate_expr(\n                                program,\n                                referenced_tables,\n                                &args[0],\n                                first_reg,\n                                resolver,\n                            )?;\n                            translate_expr(\n                                program,\n                                referenced_tables,\n                                &args[1],\n                                second_reg,\n                                resolver,\n                            )?;\n                            program.emit_insn(Insn::Function {\n                                constant_mask: 0,\n                                start_reg: first_reg,\n                                dest: target_register,\n                                func: func_ctx,\n                            });\n\n                            Ok(target_register)\n                        }\n                        ScalarFunc::SqliteVersion\n                        | ScalarFunc::TursoVersion\n                        | ScalarFunc::SqliteSourceId => {\n                            if !args.is_empty() {\n                                crate::bail_parse_error!(\"sqlite_version function with arguments\");\n                            }\n\n                            let output_register = program.alloc_register();\n                            program.emit_insn(Insn::Function {\n                                constant_mask: 0,\n                                start_reg: output_register,\n                                dest: output_register,\n                                func: func_ctx,\n                            });\n\n                            program.emit_insn(Insn::Copy {\n                                src_reg: output_register,\n                                dst_reg: target_register,\n                                extra_amount: 0,\n                            });\n                            Ok(target_register)\n                        }\n                        ScalarFunc::Replace => {\n                            if args.len() != 3 {\n                                crate::bail_parse_error!(\n                                    \"wrong number of arguments to function {}()\",\n                                    srf.to_string()\n                                )\n                            }\n\n                            let str_reg = program.alloc_register();\n                            let pattern_reg = program.alloc_register();\n                            let replacement_reg = program.alloc_register();\n                            let _ = translate_expr(\n                                program,\n                                referenced_tables,\n                                &args[0],\n                                str_reg,\n                                resolver,\n                            )?;\n                            let _ = translate_expr(\n                                program,\n                                referenced_tables,\n                                &args[1],\n                                pattern_reg,\n                                resolver,\n                            )?;\n                            let _ = translate_expr(\n                                program,\n                                referenced_tables,\n                                &args[2],\n                                replacement_reg,\n                                resolver,\n                            )?;\n                            program.emit_insn(Insn::Function {\n                                constant_mask: 0,\n                                start_reg: str_reg,\n                                dest: target_register,\n                                func: func_ctx,\n                            });\n                            Ok(target_register)\n                        }\n                        ScalarFunc::StrfTime => {\n                            let start_reg = program.alloc_registers(args.len().max(1));\n                            for (i, arg) in args.iter().enumerate() {\n                                // register containing result of each argument expression\n                                translate_expr(\n                                    program,\n                                    referenced_tables,\n                                    arg,\n                                    start_reg + i,\n                                    resolver,\n                                )?;\n                            }\n                            program.emit_insn(Insn::Function {\n                                constant_mask: 0,\n                                start_reg,\n                                dest: target_register,\n                                func: func_ctx,\n                            });\n                            Ok(target_register)\n                        }\n                        ScalarFunc::Printf => translate_function(\n                            program,\n                            args,\n                            referenced_tables,\n                            resolver,\n                            target_register,\n                            func_ctx,\n                        ),\n                        ScalarFunc::Likely => {\n                            if args.len() != 1 {\n                                crate::bail_parse_error!(\n                                    \"likely function must have exactly 1 argument\",\n                                );\n                            }\n                            translate_expr(\n                                program,\n                                referenced_tables,\n                                &args[0],\n                                target_register,\n                                resolver,\n                            )?;\n                            Ok(target_register)\n                        }\n                        ScalarFunc::Likelihood => {\n                            if args.len() != 2 {\n                                crate::bail_parse_error!(\n                                    \"likelihood() function must have exactly 2 arguments\",\n                                );\n                            }\n\n                            if let ast::Expr::Literal(ast::Literal::Numeric(ref value)) =\n                                args[1].as_ref()\n                            {\n                                if let Ok(probability) = value.parse::<f64>() {\n                                    if !(0.0..=1.0).contains(&probability) {\n                                        crate::bail_parse_error!(\n                                            \"second argument of likelihood() must be between 0.0 and 1.0\",\n                                        );\n                                    }\n                                    if !value.contains('.') {\n                                        crate::bail_parse_error!(\n                                            \"second argument of likelihood() must be a floating point number with decimal point\",\n                                        );\n                                    }\n                                } else {\n                                    crate::bail_parse_error!(\n                                        \"second argument of likelihood() must be a floating point constant\",\n                                    );\n                                }\n                            } else {\n                                crate::bail_parse_error!(\n                                    \"second argument of likelihood() must be a numeric literal\",\n                                );\n                            }\n                            translate_expr(\n                                program,\n                                referenced_tables,\n                                &args[0],\n                                target_register,\n                                resolver,\n                            )?;\n                            Ok(target_register)\n                        }\n                        ScalarFunc::TableColumnsJsonArray => {\n                            if args.len() != 1 {\n                                crate::bail_parse_error!(\n                                    \"table_columns_json_array() function must have exactly 1 argument\",\n                                );\n                            }\n                            let start_reg = program.alloc_register();\n                            translate_expr(\n                                program,\n                                referenced_tables,\n                                &args[0],\n                                start_reg,\n                                resolver,\n                            )?;\n                            program.emit_insn(Insn::Function {\n                                constant_mask: 0,\n                                start_reg,\n                                dest: target_register,\n                                func: func_ctx,\n                            });\n                            Ok(target_register)\n                        }\n                        ScalarFunc::BinRecordJsonObject => {\n                            if args.len() != 2 {\n                                crate::bail_parse_error!(\n                                    \"bin_record_json_object() function must have exactly 2 arguments\",\n                                );\n                            }\n                            let start_reg = program.alloc_registers(2);\n                            translate_expr(\n                                program,\n                                referenced_tables,\n                                &args[0],\n                                start_reg,\n                                resolver,\n                            )?;\n                            translate_expr(\n                                program,\n                                referenced_tables,\n                                &args[1],\n                                start_reg + 1,\n                                resolver,\n                            )?;\n                            program.emit_insn(Insn::Function {\n                                constant_mask: 0,\n                                start_reg,\n                                dest: target_register,\n                                func: func_ctx,\n                            });\n                            Ok(target_register)\n                        }\n                        ScalarFunc::Attach => {\n                            // ATTACH is handled by the attach.rs module, not here\n                            crate::bail_parse_error!(\n                                \"ATTACH should be handled at statement level, not as expression\"\n                            );\n                        }\n                        ScalarFunc::Detach => {\n                            // DETACH is handled by the attach.rs module, not here\n                            crate::bail_parse_error!(\n                                \"DETACH should be handled at statement level, not as expression\"\n                            );\n                        }\n                        ScalarFunc::Unlikely => {\n                            if args.len() != 1 {\n                                crate::bail_parse_error!(\n                                    \"Unlikely function must have exactly 1 argument\",\n                                );\n                            }\n                            translate_expr(\n                                program,\n                                referenced_tables,\n                                &args[0],\n                                target_register,\n                                resolver,\n                            )?;\n\n                            Ok(target_register)\n                        }\n                        ScalarFunc::StatInit | ScalarFunc::StatPush | ScalarFunc::StatGet => {\n                            crate::bail_parse_error!(\n                                \"{} is an internal function used by ANALYZE\",\n                                srf\n                            );\n                        }\n                        ScalarFunc::ConnTxnId | ScalarFunc::IsAutocommit => {\n                            crate::bail_parse_error!(\"{} is an internal function used by CDC\", srf);\n                        }\n                        ScalarFunc::TestUintEncode\n                        | ScalarFunc::TestUintDecode\n                        | ScalarFunc::TestUintAdd\n                        | ScalarFunc::TestUintSub\n                        | ScalarFunc::TestUintMul\n                        | ScalarFunc::TestUintDiv\n                        | ScalarFunc::TestUintLt\n                        | ScalarFunc::TestUintEq\n                        | ScalarFunc::StringReverse\n                        | ScalarFunc::BooleanToInt\n                        | ScalarFunc::IntToBoolean\n                        | ScalarFunc::ValidateIpAddr\n                        | ScalarFunc::NumericEncode\n                        | ScalarFunc::NumericDecode\n                        | ScalarFunc::NumericAdd\n                        | ScalarFunc::NumericSub\n                        | ScalarFunc::NumericMul\n                        | ScalarFunc::NumericDiv\n                        | ScalarFunc::NumericLt\n                        | ScalarFunc::NumericEq => translate_function(\n                            program,\n                            args,\n                            referenced_tables,\n                            resolver,\n                            target_register,\n                            func_ctx,\n                        ),\n                        ScalarFunc::ArrayLength\n                        | ScalarFunc::ArrayAppend\n                        | ScalarFunc::ArrayPrepend\n                        | ScalarFunc::ArrayCat\n                        | ScalarFunc::ArrayRemove\n                        | ScalarFunc::ArrayContains\n                        | ScalarFunc::ArrayPosition\n                        | ScalarFunc::ArraySlice\n                        | ScalarFunc::StringToArray\n                        | ScalarFunc::ArrayToString\n                        | ScalarFunc::ArrayOverlap\n                        | ScalarFunc::ArrayContainsAll => {\n                            resolver.require_custom_types(\"Array features\")?;\n                            translate_function(\n                                program,\n                                args,\n                                referenced_tables,\n                                resolver,\n                                target_register,\n                                func_ctx,\n                            )\n                        }\n                    }\n                }\n                Func::Math(math_func) => match math_func.arity() {\n                    MathFuncArity::Nullary => {\n                        if !args.is_empty() {\n                            crate::bail_parse_error!(\"{} function with arguments\", math_func);\n                        }\n\n                        program.emit_insn(Insn::Function {\n                            constant_mask: 0,\n                            start_reg: 0,\n                            dest: target_register,\n                            func: func_ctx,\n                        });\n                        Ok(target_register)\n                    }\n\n                    MathFuncArity::Unary => {\n                        let args = expect_arguments_exact!(args, 1, math_func);\n                        let start_reg = program.alloc_register();\n                        translate_expr(program, referenced_tables, &args[0], start_reg, resolver)?;\n                        program.emit_insn(Insn::Function {\n                            constant_mask: 0,\n                            start_reg,\n                            dest: target_register,\n                            func: func_ctx,\n                        });\n                        Ok(target_register)\n                    }\n\n                    MathFuncArity::Binary => {\n                        let args = expect_arguments_exact!(args, 2, math_func);\n                        let start_reg = program.alloc_registers(2);\n                        let _ = translate_expr(\n                            program,\n                            referenced_tables,\n                            &args[0],\n                            start_reg,\n                            resolver,\n                        )?;\n                        let _ = translate_expr(\n                            program,\n                            referenced_tables,\n                            &args[1],\n                            start_reg + 1,\n                            resolver,\n                        )?;\n                        program.emit_insn(Insn::Function {\n                            constant_mask: 0,\n                            start_reg,\n                            dest: target_register,\n                            func: func_ctx,\n                        });\n                        Ok(target_register)\n                    }\n\n                    MathFuncArity::UnaryOrBinary => {\n                        let args = expect_arguments_max!(args, 2, math_func);\n\n                        let regs = program.alloc_registers(args.len());\n                        for (i, arg) in args.iter().enumerate() {\n                            translate_expr(program, referenced_tables, arg, regs + i, resolver)?;\n                        }\n\n                        program.emit_insn(Insn::Function {\n                            constant_mask: 0,\n                            start_reg: regs,\n                            dest: target_register,\n                            func: func_ctx,\n                        });\n                        Ok(target_register)\n                    }\n                },\n                #[cfg(all(feature = \"fts\", not(target_family = \"wasm\")))]\n                Func::Fts(_) => {\n                    // FTS functions are handled via index method pattern matching.\n                    // If we reach here, no index matched, so translate as a regular function call.\n                    translate_function(\n                        program,\n                        args,\n                        referenced_tables,\n                        resolver,\n                        target_register,\n                        func_ctx,\n                    )\n                }\n                Func::AlterTable(_) => unreachable!(),\n            }\n        }\n        ast::Expr::FunctionCallStar { name, filter_over } => {\n            // Handle func(*) syntax as a function call with 0 arguments\n            // This is equivalent to func() for functions that accept 0 arguments\n            let args_count = 0;\n            let func_type = resolver.resolve_function(name.as_str(), args_count);\n\n            if func_type.is_none() {\n                crate::bail_parse_error!(\"unknown function {}\", name.as_str());\n            }\n\n            let func = func_type.unwrap();\n\n            // Check if this function supports the (*) syntax by verifying it can be called with 0 args\n            match &func {\n                Func::Agg(_) => {\n                    crate::bail_parse_error!(\n                        \"misuse of {} function {}(*)\",\n                        if filter_over.over_clause.is_some() {\n                            \"window\"\n                        } else {\n                            \"aggregate\"\n                        },\n                        name.as_str()\n                    )\n                }\n                Func::Window(_) => {\n                    crate::bail_parse_error!(\"misuse of window function {}()\", name.as_str())\n                }\n                // For functions that need star expansion (json_object, jsonb_object),\n                // expand the * to all columns from the referenced tables as key-value pairs\n                _ if func.needs_star_expansion() => {\n                    let tables = referenced_tables.ok_or_else(|| {\n                        LimboError::ParseError(format!(\n                            \"{}(*) requires a FROM clause\",\n                            name.as_str()\n                        ))\n                    })?;\n\n                    // Verify there's at least one table to expand\n                    if tables.joined_tables().is_empty() {\n                        return Err(LimboError::ParseError(format!(\n                            \"{}(*) requires a FROM clause\",\n                            name.as_str()\n                        )));\n                    }\n\n                    // Build arguments: alternating column_name (as string literal), column_value (as column reference)\n                    let mut args: Vec<Box<ast::Expr>> = Vec::new();\n\n                    for table in tables.joined_tables().iter() {\n                        for (col_idx, col) in table.columns().iter().enumerate() {\n                            // Skip hidden columns (like rowid in some cases)\n                            if col.hidden() {\n                                continue;\n                            }\n\n                            // Add column name as a string literal\n                            // Note: ast::Literal::String values must be wrapped in single quotes\n                            // because sanitize_string() strips the first and last character\n                            let col_name = col\n                                .name\n                                .clone()\n                                .unwrap_or_else(|| format!(\"column{}\", col_idx + 1));\n                            let quoted_col_name = format!(\"'{col_name}'\");\n                            args.push(Box::new(ast::Expr::Literal(ast::Literal::String(\n                                quoted_col_name,\n                            ))));\n\n                            // Add column reference using Expr::Column\n                            args.push(Box::new(ast::Expr::Column {\n                                database: None,\n                                table: table.internal_id,\n                                column: col_idx,\n                                is_rowid_alias: col.is_rowid_alias(),\n                            }));\n                        }\n                    }\n\n                    // Create a synthetic FunctionCall with the expanded arguments\n                    let synthetic_call = ast::Expr::FunctionCall {\n                        name: name.clone(),\n                        distinctness: None,\n                        args,\n                        filter_over: filter_over.clone(),\n                        order_by: vec![],\n                    };\n\n                    // Recursively call translate_expr with the synthetic function call\n                    translate_expr(\n                        program,\n                        referenced_tables,\n                        &synthetic_call,\n                        target_register,\n                        resolver,\n                    )\n                }\n                // For supported functions, delegate to the existing FunctionCall logic\n                // by creating a synthetic FunctionCall with empty args\n                _ => {\n                    let synthetic_call = ast::Expr::FunctionCall {\n                        name: name.clone(),\n                        distinctness: None,\n                        args: vec![], // Empty args for func(*)\n                        filter_over: filter_over.clone(),\n                        order_by: vec![], // Empty order_by for func(*)\n                    };\n\n                    // Recursively call translate_expr with the synthetic function call\n                    translate_expr(\n                        program,\n                        referenced_tables,\n                        &synthetic_call,\n                        target_register,\n                        resolver,\n                    )\n                }\n            }\n        }\n        ast::Expr::Id(id) => {\n            // Check for custom type expression overrides (e.g. `value` placeholder)\n            if let Some(&reg) = program.id_register_overrides.get(id.as_str()) {\n                program.emit_insn(Insn::Copy {\n                    src_reg: reg,\n                    dst_reg: target_register,\n                    extra_amount: 0,\n                });\n                return Ok(target_register);\n            }\n            // Treat double-quoted identifiers as string literals (SQLite compatibility)\n            program.emit_insn(Insn::String8 {\n                value: id.as_str().to_string(),\n                dest: target_register,\n            });\n            Ok(target_register)\n        }\n        ast::Expr::Column {\n            database: _,\n            table: table_ref_id,\n            column,\n            is_rowid_alias,\n        } => {\n            // When a cursor override is active for this table, we bypass all index logic\n            // and read directly from the override cursor. This is used during hash join\n            // build phases where we iterate using a separate cursor and don't want to use any index.\n            let has_cursor_override = program.has_cursor_override(*table_ref_id);\n\n            let (index, index_method, use_covering_index) = {\n                if has_cursor_override {\n                    (None, None, false)\n                } else if let Some(table_reference) = referenced_tables\n                    .expect(\"table_references needed translating Expr::Column\")\n                    .find_joined_table_by_internal_id(*table_ref_id)\n                {\n                    (\n                        table_reference.op.index(),\n                        if let Operation::IndexMethodQuery(index_method) = &table_reference.op {\n                            Some(index_method)\n                        } else {\n                            None\n                        },\n                        table_reference.utilizes_covering_index(),\n                    )\n                } else {\n                    (None, None, false)\n                }\n            };\n            let use_index_method = index_method.and_then(|m| m.covered_columns.get(column));\n\n            let (is_from_outer_query_scope, table) = referenced_tables\n                .unwrap()\n                .find_table_by_internal_id(*table_ref_id)\n                .unwrap_or_else(|| {\n                    unreachable!(\n                        \"table reference should be found: {} (referenced_tables: {:?})\",\n                        table_ref_id, referenced_tables\n                    )\n                });\n\n            if use_index_method.is_none() {\n                let Some(table_column) = table.get_column_at(*column) else {\n                    crate::bail_parse_error!(\"column index out of bounds\");\n                };\n                // Counter intuitive but a column always needs to have a collation\n                program.set_collation(Some((table_column.collation(), false)));\n            }\n\n            // If we are reading a column from a table, we find the cursor that corresponds to\n            // the table and read the column from the cursor.\n            // If we have a covering index, we don't have an open table cursor so we read from the index cursor.\n            match &table {\n                Table::BTree(_) => {\n                    let (table_cursor_id, index_cursor_id) = if is_from_outer_query_scope {\n                        // Due to a limitation of our translation system, a subquery that references an outer query table\n                        // cannot know whether a table cursor, index cursor, or both were opened for that table reference.\n                        // Hence: currently we first try to resolve a table cursor, and if that fails,\n                        // we resolve an index cursor.\n                        if let Some(table_cursor_id) =\n                            program.resolve_cursor_id_safe(&CursorKey::table(*table_ref_id))\n                        {\n                            (Some(table_cursor_id), None)\n                        } else {\n                            (\n                                None,\n                                Some(program.resolve_any_index_cursor_id_for_table(*table_ref_id)),\n                            )\n                        }\n                    } else {\n                        let table_cursor_id = if use_covering_index || use_index_method.is_some() {\n                            None\n                        } else {\n                            Some(program.resolve_cursor_id(&CursorKey::table(*table_ref_id)))\n                        };\n                        let index_cursor_id = index.map(|index| {\n                            program\n                                .resolve_cursor_id(&CursorKey::index(*table_ref_id, index.clone()))\n                        });\n                        (table_cursor_id, index_cursor_id)\n                    };\n\n                    if let Some(custom_module_column) = use_index_method {\n                        program.emit_column_or_rowid(\n                            index_cursor_id.unwrap(),\n                            *custom_module_column,\n                            target_register,\n                        );\n                    } else {\n                        if *is_rowid_alias {\n                            if let Some(index_cursor_id) = index_cursor_id {\n                                program.emit_insn(Insn::IdxRowId {\n                                    cursor_id: index_cursor_id,\n                                    dest: target_register,\n                                });\n                            } else if let Some(table_cursor_id) = table_cursor_id {\n                                program.emit_insn(Insn::RowId {\n                                    cursor_id: table_cursor_id,\n                                    dest: target_register,\n                                });\n                            } else {\n                                unreachable!(\"Either index or table cursor must be opened\");\n                            }\n                        } else {\n                            let is_btree_index = index_cursor_id.is_some_and(|cid| {\n                                program.get_cursor_type(cid).is_some_and(|ct| ct.is_index())\n                            });\n                            // FIXME(https://github.com/tursodatabase/turso/issues/4801):\n                            // This is a defensive workaround for cursor desynchronization.\n                            //\n                            // When `use_covering_index` is false, both table AND index cursors\n                            // are open and positioned at the same row. If we read some columns\n                            // from the index cursor and others from the table cursor, we rely\n                            // on both cursors staying synchronized.\n                            //\n                            // The problem: AFTER triggers can INSERT into the same table,\n                            // which modifies the index btree. This repositions or invalidates\n                            // the parent program's index cursor, while the table cursor remains\n                            // at the correct position. Result: we read a mix of data from\n                            // different rows - corruption.\n                            //\n                            // Why does the table cursor not have this problem? Because it's\n                            // explicitly re-sought by rowid (via NotExists instruction) before\n                            // each use. The rowid is stored in a register and used as a stable\n                            // key. The index cursor, by contrast, just trusts its internal\n                            // position (page + cell index) without re-seeking.\n                            //\n                            // Why not check if the table has triggers and allow the optimization\n                            // when there are none? Several reasons:\n                            // 1. ProgramBuilder.trigger indicates if THIS program is a trigger\n                            //    subprogram, not whether the table has triggers.\n                            // 2. In translate_expr(), we lack context about which table is being\n                            //    modified or whether we're even in an UPDATE/INSERT/DELETE.\n                            // 3. Triggers can be recursive (trigger on T inserts into U, whose\n                            //    trigger inserts back into T).\n                            //\n                            // The proper fix is to implement SQLite's `saveAllCursors()` approach:\n                            // before ANY btree write, find all cursors pointing to that btree\n                            // (by root_page) and save their positions. When those cursors are\n                            // next accessed, they re-seek to their saved position. This could\n                            // be done lazily with a generation number per btree - cursors check\n                            // if the generation changed and re-seek if needed. This would\n                            // require a global cursor registry and significant refactoring.\n                            //\n                            // For now, we only read from the index cursor when `use_covering_index`\n                            // is true, meaning only the index cursor exists (no table cursor to\n                            // get out of sync with). This foregoes the optimization of reading\n                            // individual columns from a non-covering index.\n                            let read_from_index = if is_from_outer_query_scope {\n                                is_btree_index\n                            } else if is_btree_index && use_covering_index {\n                                index.as_ref().is_some_and(|idx| {\n                                    idx.column_table_pos_to_index_pos(*column).is_some()\n                                })\n                            } else {\n                                false\n                            };\n                            let read_cursor = if read_from_index {\n                                index_cursor_id.expect(\"index cursor should be opened\")\n                            } else {\n                                table_cursor_id\n                                    .or(index_cursor_id)\n                                    .expect(\"cursor should be opened\")\n                            };\n                            let column = if read_from_index {\n                                let index = program.resolve_index_for_cursor_id(\n                                    index_cursor_id.expect(\"index cursor should be opened\"),\n                                );\n                                index\n                                    .column_table_pos_to_index_pos(*column)\n                                    .unwrap_or_else(|| {\n                                        panic!(\n                                        \"index {} does not contain column number {} of table {}\",\n                                        index.name, column, table_ref_id\n                                    )\n                                    })\n                            } else {\n                                *column\n                            };\n\n                            // For custom type columns with a default, suppress the\n                            // default in the Column instruction so we can encode it\n                            // ourselves. Without this, pre-existing rows (from before\n                            // ALTER TABLE ADD COLUMN) would get the raw un-encoded\n                            // default, causing decode to fail.\n                            let col_ref = table.get_column_at(column);\n                            if let Some(col) = col_ref {\n                                if col.default.is_some()\n                                    && resolver\n                                        .schema()\n                                        .get_type_def(&col.ty_str, table.is_strict())\n                                        .is_some()\n                                {\n                                    program.suppress_column_default = true;\n                                }\n                            }\n                            program.emit_column_or_rowid(read_cursor, column, target_register);\n                        }\n                        let Some(column) = table.get_column_at(*column) else {\n                            crate::bail_parse_error!(\"column index out of bounds\");\n                        };\n                        // Skip affinity for custom types — the stored value is\n                        // already in BASE type format; the custom type name may\n                        // produce wrong affinity (e.g. \"doubled\" → REAL due to \"DOUB\").\n                        if resolver\n                            .schema()\n                            .get_type_def(&column.ty_str, table.is_strict())\n                            .is_none()\n                        {\n                            maybe_apply_affinity(column.ty(), target_register, program);\n                        }\n\n                        // Decode custom type columns (skipped when building ORDER BY sort keys\n                        // for types without a `<` operator, so the sorter sorts on encoded values)\n                        if !program.suppress_custom_type_decode {\n                            // For custom type columns with a default, the Column\n                            // instruction returns NULL for pre-existing rows\n                            // (since we suppressed the default). Load the default\n                            // and encode it so decode produces the correct value.\n                            if let Some(type_def) = resolver\n                                .schema()\n                                .get_type_def(&column.ty_str, table.is_strict())\n                            {\n                                if let Some(ref default_expr) = column.default {\n                                    if type_def.encode.is_some() {\n                                        let skip_default_label = program.allocate_label();\n                                        program.emit_insn(Insn::NotNull {\n                                            reg: target_register,\n                                            target_pc: skip_default_label,\n                                        });\n                                        translate_expr_no_constant_opt(\n                                            program,\n                                            referenced_tables,\n                                            default_expr,\n                                            target_register,\n                                            resolver,\n                                            NoConstantOptReason::RegisterReuse,\n                                        )?;\n                                        if let Some(ref encode_expr) = type_def.encode {\n                                            emit_type_expr(\n                                                program,\n                                                encode_expr,\n                                                target_register,\n                                                target_register,\n                                                column,\n                                                type_def,\n                                                resolver,\n                                            )?;\n                                        }\n                                        program.preassign_label_to_next_insn(skip_default_label);\n                                    }\n                                }\n                            }\n                            emit_user_facing_column_value(\n                                program,\n                                target_register,\n                                target_register,\n                                column,\n                                table.is_strict(),\n                                resolver,\n                            )?;\n                        }\n                    }\n                    Ok(target_register)\n                }\n                Table::FromClauseSubquery(from_clause_subquery) => {\n                    // For outer-scope references during table-backed materialized-subquery\n                    // seeks, read from the auxiliary index cursor: coroutine result\n                    // registers are not refreshed while the seek path is iterating.\n                    if is_from_outer_query_scope {\n                        if let Some(cursor_id) =\n                            program.resolve_any_index_cursor_id_for_table_safe(*table_ref_id)\n                        {\n                            let index = program.resolve_index_for_cursor_id(cursor_id);\n                            let idx_col = index\n                                .columns\n                                .iter()\n                                .position(|c| c.pos_in_table == *column)\n                                .expect(\"index column not found for subquery column\");\n                            program.emit_insn(Insn::Column {\n                                cursor_id,\n                                column: idx_col,\n                                dest: target_register,\n                                default: None,\n                            });\n                            if let Some(col) = from_clause_subquery.columns.get(*column) {\n                                maybe_apply_affinity(col.ty(), target_register, program);\n                            }\n                            return Ok(target_register);\n                        }\n                    }\n\n                    // Check if this subquery was materialized with an ephemeral index.\n                    // If so, read from the index cursor; otherwise copy from result registers.\n                    if let Some(refs) = referenced_tables {\n                        if let Some(table_reference) = refs\n                            .joined_tables()\n                            .iter()\n                            .find(|t| t.internal_id == *table_ref_id)\n                        {\n                            // Check if the operation is Search::Seek with an ephemeral index\n                            if let Operation::Search(Search::Seek {\n                                index: Some(index), ..\n                            }) = &table_reference.op\n                            {\n                                if index.ephemeral {\n                                    // Read from the index cursor. Index columns may be reordered\n                                    // (key columns first), so find the index column position that\n                                    // corresponds to the original subquery column position.\n                                    let idx_col = index\n                                        .columns\n                                        .iter()\n                                        .position(|c| c.pos_in_table == *column)\n                                        .expect(\"index column not found for subquery column\");\n                                    let cursor_id = program.resolve_cursor_id(&CursorKey::index(\n                                        *table_ref_id,\n                                        index.clone(),\n                                    ));\n                                    program.emit_insn(Insn::Column {\n                                        cursor_id,\n                                        column: idx_col,\n                                        dest: target_register,\n                                        default: None,\n                                    });\n                                    if let Some(col) = from_clause_subquery.columns.get(*column) {\n                                        maybe_apply_affinity(col.ty(), target_register, program);\n                                    }\n                                    return Ok(target_register);\n                                }\n                            }\n                        }\n                    }\n\n                    // Fallback: copy from result registers (coroutine-based subquery)\n                    let result_columns_start = if is_from_outer_query_scope {\n                        // For outer query subqueries, look up the register from the program builder\n                        // since the cloned subquery doesn't have the register set yet.\n                        program.get_subquery_result_reg(*table_ref_id).expect(\n                            \"Outer query subquery result_columns_start_reg must be set in program\",\n                        )\n                    } else {\n                        from_clause_subquery\n                            .result_columns_start_reg\n                            .expect(\"Subquery result_columns_start_reg must be set\")\n                    };\n                    program.emit_insn(Insn::Copy {\n                        src_reg: result_columns_start + *column,\n                        dst_reg: target_register,\n                        extra_amount: 0,\n                    });\n                    Ok(target_register)\n                }\n                Table::Virtual(_) => {\n                    let cursor_id = program.resolve_cursor_id(&CursorKey::table(*table_ref_id));\n                    program.emit_insn(Insn::VColumn {\n                        cursor_id,\n                        column: *column,\n                        dest: target_register,\n                    });\n                    Ok(target_register)\n                }\n            }\n        }\n        ast::Expr::RowId {\n            database: _,\n            table: table_ref_id,\n        } => {\n            let referenced_tables =\n                referenced_tables.expect(\"table_references needed translating Expr::RowId\");\n            let (_, table) = referenced_tables\n                .find_table_by_internal_id(*table_ref_id)\n                .expect(\"table reference should be found\");\n            let Table::BTree(btree) = table else {\n                crate::bail_parse_error!(\"no such column: rowid\");\n            };\n            if !btree.has_rowid {\n                crate::bail_parse_error!(\"no such column: rowid\");\n            }\n\n            // When a cursor override is active, always read rowid from the override cursor.\n            let has_cursor_override = program.has_cursor_override(*table_ref_id);\n            let (index, use_covering_index) = if has_cursor_override {\n                (None, false)\n            } else if let Some(table_reference) =\n                referenced_tables.find_joined_table_by_internal_id(*table_ref_id)\n            {\n                (\n                    table_reference.op.index(),\n                    table_reference.utilizes_covering_index(),\n                )\n            } else {\n                (None, false)\n            };\n\n            if use_covering_index {\n                let index =\n                    index.expect(\"index cursor should be opened when use_covering_index=true\");\n                let cursor_id =\n                    program.resolve_cursor_id(&CursorKey::index(*table_ref_id, index.clone()));\n                program.emit_insn(Insn::IdxRowId {\n                    cursor_id,\n                    dest: target_register,\n                });\n            } else {\n                let cursor_id = program.resolve_cursor_id(&CursorKey::table(*table_ref_id));\n                program.emit_insn(Insn::RowId {\n                    cursor_id,\n                    dest: target_register,\n                });\n            }\n            Ok(target_register)\n        }\n        ast::Expr::InList { lhs, rhs, not } => {\n            // Following SQLite's approach: use the same core logic as conditional InList,\n            // but wrap it with appropriate expression context handling\n            let result_reg = target_register;\n\n            let dest_if_false = program.allocate_label();\n            let dest_if_null = program.allocate_label();\n            let dest_if_true = program.allocate_label();\n\n            // Ideally we wouldn't need a tmp register, but currently if an IN expression\n            // is used inside an aggregator the target_register is cleared on every iteration,\n            // losing the state of the aggregator.\n            let tmp = program.alloc_register();\n            program.emit_no_constant_insn(Insn::Null {\n                dest: tmp,\n                dest_end: None,\n            });\n\n            translate_in_list(\n                program,\n                referenced_tables,\n                lhs,\n                rhs,\n                ConditionMetadata {\n                    jump_if_condition_is_true: false,\n                    jump_target_when_true: dest_if_true,\n                    jump_target_when_false: dest_if_false,\n                    jump_target_when_null: dest_if_null,\n                },\n                resolver,\n            )?;\n\n            // condition true: set result to 1\n            program.emit_insn(Insn::Integer {\n                value: 1,\n                dest: tmp,\n            });\n\n            // False path: set result to 0\n            program.resolve_label(dest_if_false, program.offset());\n\n            // Force integer conversion with AddImm 0\n            program.emit_insn(Insn::AddImm {\n                register: tmp,\n                value: 0,\n            });\n\n            if *not {\n                program.emit_insn(Insn::Not {\n                    reg: tmp,\n                    dest: tmp,\n                });\n            }\n            program.resolve_label(dest_if_null, program.offset());\n            program.emit_insn(Insn::Copy {\n                src_reg: tmp,\n                dst_reg: result_reg,\n                extra_amount: 0,\n            });\n            Ok(result_reg)\n        }\n        ast::Expr::InSelect { .. } => {\n            crate::bail_parse_error!(\"IN (...subquery) is not supported in this position\")\n        }\n        ast::Expr::InTable { .. } => {\n            crate::bail_parse_error!(\"Table expression is not supported in this position\")\n        }\n        ast::Expr::IsNull(expr) => {\n            let reg = program.alloc_register();\n            translate_expr(program, referenced_tables, expr, reg, resolver)?;\n            program.emit_insn(Insn::Integer {\n                value: 1,\n                dest: target_register,\n            });\n            let label = program.allocate_label();\n            program.emit_insn(Insn::IsNull {\n                reg,\n                target_pc: label,\n            });\n            program.emit_insn(Insn::Integer {\n                value: 0,\n                dest: target_register,\n            });\n            program.preassign_label_to_next_insn(label);\n            Ok(target_register)\n        }\n        ast::Expr::Like { not, .. } => {\n            let like_reg = if *not {\n                program.alloc_register()\n            } else {\n                target_register\n            };\n            translate_like_base(program, referenced_tables, expr, like_reg, resolver)?;\n            if *not {\n                program.emit_insn(Insn::Not {\n                    reg: like_reg,\n                    dest: target_register,\n                });\n            }\n            Ok(target_register)\n        }\n        ast::Expr::Literal(lit) => emit_literal(program, lit, target_register),\n        ast::Expr::Name(_) => {\n            crate::bail_parse_error!(\"ast::Expr::Name is not supported in this position\")\n        }\n        ast::Expr::NotNull(expr) => {\n            let reg = program.alloc_register();\n            translate_expr(program, referenced_tables, expr, reg, resolver)?;\n            program.emit_insn(Insn::Integer {\n                value: 1,\n                dest: target_register,\n            });\n            let label = program.allocate_label();\n            program.emit_insn(Insn::NotNull {\n                reg,\n                target_pc: label,\n            });\n            program.emit_insn(Insn::Integer {\n                value: 0,\n                dest: target_register,\n            });\n            program.preassign_label_to_next_insn(label);\n            Ok(target_register)\n        }\n        ast::Expr::Parenthesized(exprs) => {\n            if exprs.is_empty() {\n                crate::bail_parse_error!(\"parenthesized expression with no arguments\");\n            }\n            assert_register_range_allocated(program, target_register, exprs.len())?;\n            for (i, expr) in exprs.iter().enumerate() {\n                translate_expr(\n                    program,\n                    referenced_tables,\n                    expr,\n                    target_register + i,\n                    resolver,\n                )?;\n            }\n            Ok(target_register)\n        }\n        ast::Expr::Qualified(_, _) => {\n            unreachable!(\"Qualified should be resolved to a Column before translation\")\n        }\n        ast::Expr::Raise(resolve_type, msg_expr) => {\n            let in_trigger = program.trigger.is_some();\n            match resolve_type {\n                ResolveType::Ignore => {\n                    if !in_trigger {\n                        crate::bail_parse_error!(\n                            \"RAISE() may only be used within a trigger-program\"\n                        );\n                    }\n                    // RAISE(IGNORE): halt the trigger subprogram and skip the triggering row\n                    program.emit_insn(Insn::Halt {\n                        err_code: 0,\n                        description: String::new(),\n                        on_error: Some(ResolveType::Ignore),\n                        description_reg: None,\n                    });\n                }\n                ResolveType::Fail | ResolveType::Abort | ResolveType::Rollback => {\n                    if !in_trigger && *resolve_type != ResolveType::Abort {\n                        crate::bail_parse_error!(\n                            \"RAISE() may only be used within a trigger-program\"\n                        );\n                    }\n                    let err_code = if in_trigger {\n                        SQLITE_CONSTRAINT_TRIGGER\n                    } else {\n                        SQLITE_ERROR\n                    };\n                    match msg_expr {\n                        Some(e) => match e.as_ref() {\n                            ast::Expr::Literal(ast::Literal::String(s)) => {\n                                program.emit_insn(Insn::Halt {\n                                    err_code,\n                                    description: sanitize_string(s),\n                                    on_error: Some(*resolve_type),\n                                    description_reg: None,\n                                });\n                            }\n                            _ => {\n                                // Expression-based error message: evaluate at runtime\n                                let reg = program.alloc_register();\n                                translate_expr(program, referenced_tables, e, reg, resolver)?;\n                                program.emit_insn(Insn::Halt {\n                                    err_code,\n                                    description: String::new(),\n                                    on_error: Some(*resolve_type),\n                                    description_reg: Some(reg),\n                                });\n                            }\n                        },\n                        None => {\n                            crate::bail_parse_error!(\"RAISE requires an error message\");\n                        }\n                    };\n                }\n                ResolveType::Replace => {\n                    crate::bail_parse_error!(\"REPLACE is not valid for RAISE\");\n                }\n            }\n            Ok(target_register)\n        }\n        ast::Expr::Subquery(_) => {\n            crate::bail_parse_error!(\"Subquery is not supported in this position\")\n        }\n        ast::Expr::Unary(op, expr) => match (op, expr.as_ref()) {\n            (UnaryOperator::Positive, expr) => {\n                translate_expr(program, referenced_tables, expr, target_register, resolver)\n            }\n            (UnaryOperator::Negative, ast::Expr::Literal(ast::Literal::Numeric(numeric_value))) => {\n                let numeric_value = \"-\".to_owned() + numeric_value;\n                match parse_numeric_literal(&numeric_value)? {\n                    Value::Numeric(Numeric::Integer(int_value)) => {\n                        program.emit_insn(Insn::Integer {\n                            value: int_value,\n                            dest: target_register,\n                        });\n                    }\n                    Value::Numeric(Numeric::Float(real_value)) => {\n                        program.emit_insn(Insn::Real {\n                            value: real_value.into(),\n                            dest: target_register,\n                        });\n                    }\n                    _ => unreachable!(),\n                }\n                Ok(target_register)\n            }\n            (UnaryOperator::Negative, _) => {\n                let value = 0;\n\n                let reg = program.alloc_register();\n                translate_expr(program, referenced_tables, expr, reg, resolver)?;\n                let zero_reg = program.alloc_register();\n                program.emit_insn(Insn::Integer {\n                    value,\n                    dest: zero_reg,\n                });\n                program.mark_last_insn_constant();\n                program.emit_insn(Insn::Subtract {\n                    lhs: zero_reg,\n                    rhs: reg,\n                    dest: target_register,\n                });\n                Ok(target_register)\n            }\n            (UnaryOperator::BitwiseNot, ast::Expr::Literal(ast::Literal::Numeric(num_val))) => {\n                match parse_numeric_literal(num_val)? {\n                    Value::Numeric(Numeric::Integer(int_value)) => {\n                        program.emit_insn(Insn::Integer {\n                            value: !int_value,\n                            dest: target_register,\n                        });\n                    }\n                    Value::Numeric(Numeric::Float(real_value)) => {\n                        program.emit_insn(Insn::Integer {\n                            value: !(f64::from(real_value) as i64),\n                            dest: target_register,\n                        });\n                    }\n                    _ => unreachable!(),\n                }\n                Ok(target_register)\n            }\n            (UnaryOperator::BitwiseNot, ast::Expr::Literal(ast::Literal::Null)) => {\n                program.emit_insn(Insn::Null {\n                    dest: target_register,\n                    dest_end: None,\n                });\n                Ok(target_register)\n            }\n            (UnaryOperator::BitwiseNot, _) => {\n                let reg = program.alloc_register();\n                translate_expr(program, referenced_tables, expr, reg, resolver)?;\n                program.emit_insn(Insn::BitNot {\n                    reg,\n                    dest: target_register,\n                });\n                Ok(target_register)\n            }\n            (UnaryOperator::Not, _) => {\n                let reg = program.alloc_register();\n                translate_expr(program, referenced_tables, expr, reg, resolver)?;\n                program.emit_insn(Insn::Not {\n                    reg,\n                    dest: target_register,\n                });\n                Ok(target_register)\n            }\n        },\n        ast::Expr::Variable(variable) => {\n            let index = usize::try_from(variable.index.get())\n                .expect(\"u32 variable index must fit into usize\")\n                .try_into()\n                .expect(\"variable index must be non-zero\");\n            if let Some(name) = variable.name.as_deref() {\n                program.parameters.push_named_at(name, index);\n            } else {\n                program.parameters.push_index(index);\n            }\n            program.emit_insn(Insn::Variable {\n                index,\n                dest: target_register,\n            });\n            Ok(target_register)\n        }\n        ast::Expr::Register(src_reg) => {\n            // For DBSP expression compilation: copy from source register to target\n            program.emit_insn(Insn::Copy {\n                src_reg: *src_reg,\n                dst_reg: target_register,\n                extra_amount: 0,\n            });\n            Ok(target_register)\n        }\n        ast::Expr::Array { .. } | ast::Expr::Subscript { .. } => {\n            unreachable!(\"Array and Subscript are desugared into function calls by the parser\")\n        }\n    }?;\n\n    if let Some(span) = constant_span {\n        program.constant_span_end(span);\n    }\n\n    Ok(target_register)\n}\n\n#[allow(clippy::too_many_arguments)]\nfn binary_expr_shared(\n    program: &mut ProgramBuilder,\n    referenced_tables: Option<&TableReferences>,\n    e1: &ast::Expr,\n    e2: &ast::Expr,\n    op: &ast::Operator,\n    target_register: usize,\n    resolver: &Resolver,\n    emit_mode: BinaryEmitMode,\n) -> Result<usize> {\n    let lhs_arity = expr_vector_size(e1)?;\n    let rhs_arity = expr_vector_size(e2)?;\n    if lhs_arity != rhs_arity {\n        crate::bail_parse_error!(\n            \"all arguments to binary operator {op} must return the same number of values. Got: ({lhs_arity}) {op} ({rhs_arity})\"\n        );\n    }\n\n    if lhs_arity == 1 {\n        emit_binary_expr_scalar(\n            program,\n            referenced_tables,\n            e1,\n            e2,\n            op,\n            target_register,\n            resolver,\n            emit_mode,\n        )?;\n        return Ok(target_register);\n    }\n\n    if !supports_row_value_binary_comparison(op) {\n        crate::bail_parse_error!(\"row value misused\");\n    }\n\n    let lhs_reg = program.alloc_registers(lhs_arity);\n    let rhs_reg = program.alloc_registers(lhs_arity);\n    translate_expr(program, referenced_tables, e1, lhs_reg, resolver)?;\n    translate_expr(program, referenced_tables, e2, rhs_reg, resolver)?;\n\n    emit_binary_expr_row_valued(\n        program,\n        op,\n        lhs_reg,\n        rhs_reg,\n        lhs_arity,\n        target_register,\n        e1,\n        e2,\n        referenced_tables,\n        Some(resolver),\n    )?;\n\n    if let BinaryEmitMode::Condition(metadata) = emit_mode {\n        emit_cond_jump(program, metadata, target_register);\n    }\n    Ok(target_register)\n}\n\n#[allow(clippy::too_many_arguments)]\nfn emit_binary_expr_scalar(\n    program: &mut ProgramBuilder,\n    referenced_tables: Option<&TableReferences>,\n    e1: &ast::Expr,\n    e2: &ast::Expr,\n    op: &ast::Operator,\n    target_register: usize,\n    resolver: &Resolver,\n    emit_mode: BinaryEmitMode,\n) -> Result<usize> {\n    let (emit_fn, condition_metadata) = match emit_mode {\n        BinaryEmitMode::Value => (\n            emit_binary_insn\n                as fn(\n                    &mut ProgramBuilder,\n                    &ast::Operator,\n                    usize,\n                    usize,\n                    usize,\n                    &ast::Expr,\n                    &ast::Expr,\n                    Option<&TableReferences>,\n                    Option<ConditionMetadata>,\n                    Option<&Resolver>,\n                ) -> Result<()>,\n            None,\n        ),\n        BinaryEmitMode::Condition(metadata) => (\n            emit_binary_condition_insn\n                as fn(\n                    &mut ProgramBuilder,\n                    &ast::Operator,\n                    usize,\n                    usize,\n                    usize,\n                    &ast::Expr,\n                    &ast::Expr,\n                    Option<&TableReferences>,\n                    Option<ConditionMetadata>,\n                    Option<&Resolver>,\n                ) -> Result<()>,\n            Some(metadata),\n        ),\n    };\n\n    // Check if both sides of the expression are equivalent and reuse the same register if so\n    if exprs_are_equivalent(e1, e2) {\n        let shared_reg = program.alloc_register();\n        translate_expr(program, referenced_tables, e1, shared_reg, resolver)?;\n\n        emit_fn(\n            program,\n            op,\n            shared_reg,\n            shared_reg,\n            target_register,\n            e1,\n            e2,\n            referenced_tables,\n            condition_metadata,\n            Some(resolver),\n        )?;\n        if op.is_comparison() {\n            program.reset_collation();\n        }\n        Ok(target_register)\n    } else {\n        let e1_reg = program.alloc_registers(2);\n        let e2_reg = e1_reg + 1;\n\n        translate_expr(program, referenced_tables, e1, e1_reg, resolver)?;\n        let left_collation_ctx = program.curr_collation_ctx();\n        program.reset_collation();\n\n        translate_expr(program, referenced_tables, e2, e2_reg, resolver)?;\n        let right_collation_ctx = program.curr_collation_ctx();\n        program.reset_collation();\n\n        /*\n         * The rules for determining which collating function to use for a binary comparison\n         * operator (=, <, >, <=, >=, !=, IS, and IS NOT) are as follows:\n         *\n         * 1. If either operand has an explicit collating function assignment using the postfix COLLATE operator,\n         * then the explicit collating function is used for comparison,\n         * with precedence to the collating function of the left operand.\n         *\n         * 2. If either operand is a column, then the collating function of that column is used\n         * with precedence to the left operand. For the purposes of the previous sentence,\n         * a column name preceded by one or more unary \"+\" operators and/or CAST operators is still considered a column name.\n         *\n         * 3. Otherwise, the BINARY collating function is used for comparison.\n         */\n        let collation_ctx = {\n            match (left_collation_ctx, right_collation_ctx) {\n                (Some((c_left, true)), _) => Some((c_left, true)),\n                (_, Some((c_right, true))) => Some((c_right, true)),\n                (Some((c_left, from_collate_left)), None) => Some((c_left, from_collate_left)),\n                (None, Some((c_right, from_collate_right))) => Some((c_right, from_collate_right)),\n                (Some((c_left, from_collate_left)), Some((_, false))) => {\n                    Some((c_left, from_collate_left))\n                }\n                _ => None,\n            }\n        };\n        program.set_collation(collation_ctx);\n\n        emit_fn(\n            program,\n            op,\n            e1_reg,\n            e2_reg,\n            target_register,\n            e1,\n            e2,\n            referenced_tables,\n            condition_metadata,\n            Some(resolver),\n        )?;\n        // Only reset collation for comparison operators, which consume it.\n        // Non-comparison operators (Concat, Add, etc.) must propagate the\n        // collation to the parent expression so that e.g.\n        //   (name COLLATE NOCASE || '') <> 'admin'\n        // correctly applies NOCASE to the Ne comparison.\n        if op.is_comparison() {\n            program.reset_collation();\n        }\n        Ok(target_register)\n    }\n}\n\n#[allow(clippy::too_many_arguments)]\nfn emit_binary_expr_row_valued(\n    program: &mut ProgramBuilder,\n    op: &ast::Operator,\n    lhs_start: usize,\n    rhs_start: usize,\n    arity: usize,\n    target_register: usize,\n    lhs_expr: &Expr,\n    rhs_expr: &Expr,\n    referenced_tables: Option<&TableReferences>,\n    resolver: Option<&Resolver>,\n) -> Result<()> {\n    enum RowOrderingOp {\n        Less,\n        Greater,\n    }\n\n    let mut emit_eq = |result_reg: usize, null_eq: bool| -> Result<()> {\n        let null_seen_reg = if null_eq {\n            None\n        } else {\n            let reg = program.alloc_register();\n            program.emit_insn(Insn::Integer {\n                value: 0,\n                dest: reg,\n            });\n            Some(reg)\n        };\n\n        let done_label = program.allocate_label();\n        for i in 0..arity {\n            let next_label = program.allocate_label();\n            let (affinity, collation) = row_component_affinity_collation(\n                lhs_expr,\n                rhs_expr,\n                i,\n                referenced_tables,\n                resolver,\n            )?;\n            program.emit_insn(Insn::Eq {\n                lhs: lhs_start + i,\n                rhs: rhs_start + i,\n                target_pc: next_label,\n                flags: if null_eq {\n                    CmpInsFlags::default().null_eq().with_affinity(affinity)\n                } else {\n                    CmpInsFlags::default().with_affinity(affinity)\n                },\n                collation,\n            });\n            if null_eq {\n                program.emit_insn(Insn::Integer {\n                    value: 0,\n                    dest: result_reg,\n                });\n                program.emit_insn(Insn::Goto {\n                    target_pc: done_label,\n                });\n            } else {\n                let mark_null_label = program.allocate_label();\n                program.emit_insn(Insn::IsNull {\n                    reg: lhs_start + i,\n                    target_pc: mark_null_label,\n                });\n                program.emit_insn(Insn::IsNull {\n                    reg: rhs_start + i,\n                    target_pc: mark_null_label,\n                });\n                program.emit_insn(Insn::Integer {\n                    value: 0,\n                    dest: result_reg,\n                });\n                program.emit_insn(Insn::Goto {\n                    target_pc: done_label,\n                });\n                program.preassign_label_to_next_insn(mark_null_label);\n                program.emit_insn(Insn::Integer {\n                    value: 1,\n                    dest: null_seen_reg.expect(\"null tracking register must exist\"),\n                });\n            }\n            program.preassign_label_to_next_insn(next_label);\n        }\n        program.emit_insn(Insn::Integer {\n            value: 1,\n            dest: result_reg,\n        });\n        if !null_eq {\n            let finish_label = program.allocate_label();\n            program.emit_insn(Insn::IfNot {\n                reg: null_seen_reg.expect(\"null tracking register must exist\"),\n                target_pc: finish_label,\n                jump_if_null: true,\n            });\n            program.emit_insn(Insn::Null {\n                dest: result_reg,\n                dest_end: None,\n            });\n            program.preassign_label_to_next_insn(finish_label);\n        }\n        program.preassign_label_to_next_insn(done_label);\n        Ok(())\n    };\n\n    let emit_order =\n        |program: &mut ProgramBuilder, order_op: RowOrderingOp, include_eq: bool| -> Result<()> {\n            let done_label = program.allocate_label();\n            let null_result_label = program.allocate_label();\n            for i in 0..arity {\n                let next_cmp_label = program.allocate_label();\n                let (aff, collation) = row_component_affinity_collation(\n                    lhs_expr,\n                    rhs_expr,\n                    i,\n                    referenced_tables,\n                    resolver,\n                )?;\n                let lhs = lhs_start + i;\n                let rhs = rhs_start + i;\n                program.emit_insn(Insn::IsNull {\n                    reg: lhs,\n                    target_pc: null_result_label,\n                });\n                program.emit_insn(Insn::IsNull {\n                    reg: rhs,\n                    target_pc: null_result_label,\n                });\n                program.emit_insn(Insn::Eq {\n                    lhs,\n                    rhs,\n                    target_pc: next_cmp_label,\n                    flags: CmpInsFlags::default().with_affinity(aff),\n                    collation,\n                });\n                let true_label = program.allocate_label();\n                match order_op {\n                    RowOrderingOp::Less => {\n                        program.emit_insn(Insn::Lt {\n                            lhs,\n                            rhs,\n                            target_pc: true_label,\n                            flags: CmpInsFlags::default().with_affinity(aff),\n                            collation,\n                        });\n                    }\n                    RowOrderingOp::Greater => {\n                        program.emit_insn(Insn::Gt {\n                            lhs,\n                            rhs,\n                            target_pc: true_label,\n                            flags: CmpInsFlags::default().with_affinity(aff),\n                            collation,\n                        });\n                    }\n                }\n                program.emit_insn(Insn::Integer {\n                    value: 0,\n                    dest: target_register,\n                });\n                program.emit_insn(Insn::Goto {\n                    target_pc: done_label,\n                });\n                program.preassign_label_to_next_insn(true_label);\n                program.emit_insn(Insn::Integer {\n                    value: 1,\n                    dest: target_register,\n                });\n                program.emit_insn(Insn::Goto {\n                    target_pc: done_label,\n                });\n                program.preassign_label_to_next_insn(next_cmp_label);\n            }\n            program.emit_insn(Insn::Integer {\n                value: if include_eq { 1 } else { 0 },\n                dest: target_register,\n            });\n            program.emit_insn(Insn::Goto {\n                target_pc: done_label,\n            });\n            program.preassign_label_to_next_insn(null_result_label);\n            program.emit_insn(Insn::Null {\n                dest: target_register,\n                dest_end: None,\n            });\n            program.preassign_label_to_next_insn(done_label);\n            Ok(())\n        };\n\n    match op {\n        ast::Operator::Equals => emit_eq(target_register, false)?,\n        ast::Operator::NotEquals => {\n            emit_eq(target_register, false)?;\n            invert_boolean_register(program, target_register);\n        }\n        ast::Operator::Is => emit_eq(target_register, true)?,\n        ast::Operator::IsNot => {\n            emit_eq(target_register, true)?;\n            invert_boolean_register(program, target_register);\n        }\n        ast::Operator::Less => emit_order(program, RowOrderingOp::Less, false)?,\n        ast::Operator::LessEquals => emit_order(program, RowOrderingOp::Less, true)?,\n        ast::Operator::Greater => emit_order(program, RowOrderingOp::Greater, false)?,\n        ast::Operator::GreaterEquals => emit_order(program, RowOrderingOp::Greater, true)?,\n        _ => crate::bail_parse_error!(\"row value misused\"),\n    }\n    Ok(())\n}\n\nfn invert_boolean_register(program: &mut ProgramBuilder, target_register: usize) {\n    program.emit_insn(Insn::Not {\n        reg: target_register,\n        dest: target_register,\n    });\n}\n\nfn row_value_component_expr(expr: &Expr, idx: usize) -> Result<Option<&Expr>> {\n    match unwrap_parens(expr)? {\n        Expr::Parenthesized(exprs) if exprs.len() > 1 => Ok(exprs.get(idx).map(Box::as_ref)),\n        _ => Ok(None),\n    }\n}\n\nfn row_component_affinity_collation(\n    lhs_expr: &Expr,\n    rhs_expr: &Expr,\n    idx: usize,\n    referenced_tables: Option<&TableReferences>,\n    resolver: Option<&Resolver>,\n) -> Result<(Affinity, Option<CollationSeq>)> {\n    // If one side is a decomposable row literal and the other is not, still prefer\n    // the component that is available instead of falling back both sides.\n    // TODO: when both sides are non-decomposable row sources (e.g. subquery row-values),\n    // this falls back to whole-expression affinity/collation and cannot distinguish\n    // per-component metadata.\n    let lhs_for_cmp = row_value_component_expr(lhs_expr, idx)?.unwrap_or(lhs_expr);\n    let rhs_for_cmp = row_value_component_expr(rhs_expr, idx)?.unwrap_or(rhs_expr);\n    Ok((\n        comparison_affinity(lhs_for_cmp, rhs_for_cmp, referenced_tables, resolver),\n        comparison_collation(lhs_for_cmp, rhs_for_cmp, referenced_tables)?,\n    ))\n}\n\nfn explicit_collation(expr: &Expr) -> Result<Option<CollationSeq>> {\n    let mut found = None;\n    walk_expr(expr, &mut |e| -> Result<WalkControl> {\n        if let Expr::Collate(_, seq) = e {\n            if found.is_none() {\n                found = Some(CollationSeq::new(seq.as_str()).unwrap_or_default());\n            }\n            return Ok(WalkControl::SkipChildren);\n        }\n        Ok(WalkControl::Continue)\n    })?;\n    Ok(found)\n}\n\nfn comparison_collation(\n    lhs_expr: &Expr,\n    rhs_expr: &Expr,\n    referenced_tables: Option<&TableReferences>,\n) -> Result<Option<CollationSeq>> {\n    if let Some(tables) = referenced_tables {\n        let lhs_collation = get_collseq_from_expr(lhs_expr, tables)?;\n        if lhs_collation.is_some() {\n            return Ok(lhs_collation);\n        }\n        return get_collseq_from_expr(rhs_expr, tables);\n    }\n\n    let lhs_collation = explicit_collation(lhs_expr)?;\n    if lhs_collation.is_some() {\n        return Ok(lhs_collation);\n    }\n    explicit_collation(rhs_expr)\n}\n\n#[allow(clippy::too_many_arguments)]\nfn emit_binary_insn(\n    program: &mut ProgramBuilder,\n    op: &ast::Operator,\n    lhs: usize,\n    rhs: usize,\n    target_register: usize,\n    lhs_expr: &Expr,\n    rhs_expr: &Expr,\n    referenced_tables: Option<&TableReferences>,\n    _: Option<ConditionMetadata>,\n    resolver: Option<&Resolver>,\n) -> Result<()> {\n    let mut affinity = Affinity::Blob;\n    if op.is_comparison() {\n        affinity = comparison_affinity(lhs_expr, rhs_expr, referenced_tables, resolver);\n    }\n    let is_array_cmp =\n        expr_is_array(lhs_expr, referenced_tables) && expr_is_array(rhs_expr, referenced_tables);\n    let cmp_flags = || {\n        let f = CmpInsFlags::default().with_affinity(affinity);\n        if is_array_cmp {\n            f.array_cmp()\n        } else {\n            f\n        }\n    };\n\n    match op {\n        ast::Operator::NotEquals => {\n            let if_true_label = program.allocate_label();\n            wrap_eval_jump_expr_zero_or_null(\n                program,\n                Insn::Ne {\n                    lhs,\n                    rhs,\n                    target_pc: if_true_label,\n                    flags: cmp_flags(),\n                    collation: program.curr_collation(),\n                },\n                target_register,\n                if_true_label,\n                lhs,\n                rhs,\n            );\n        }\n        ast::Operator::Equals => {\n            let if_true_label = program.allocate_label();\n            wrap_eval_jump_expr_zero_or_null(\n                program,\n                Insn::Eq {\n                    lhs,\n                    rhs,\n                    target_pc: if_true_label,\n                    flags: cmp_flags(),\n                    collation: program.curr_collation(),\n                },\n                target_register,\n                if_true_label,\n                lhs,\n                rhs,\n            );\n        }\n        ast::Operator::Less => {\n            let if_true_label = program.allocate_label();\n            wrap_eval_jump_expr_zero_or_null(\n                program,\n                Insn::Lt {\n                    lhs,\n                    rhs,\n                    target_pc: if_true_label,\n                    flags: cmp_flags(),\n                    collation: program.curr_collation(),\n                },\n                target_register,\n                if_true_label,\n                lhs,\n                rhs,\n            );\n        }\n        ast::Operator::LessEquals => {\n            let if_true_label = program.allocate_label();\n            wrap_eval_jump_expr_zero_or_null(\n                program,\n                Insn::Le {\n                    lhs,\n                    rhs,\n                    target_pc: if_true_label,\n                    flags: cmp_flags(),\n                    collation: program.curr_collation(),\n                },\n                target_register,\n                if_true_label,\n                lhs,\n                rhs,\n            );\n        }\n        ast::Operator::Greater => {\n            let if_true_label = program.allocate_label();\n            wrap_eval_jump_expr_zero_or_null(\n                program,\n                Insn::Gt {\n                    lhs,\n                    rhs,\n                    target_pc: if_true_label,\n                    flags: cmp_flags(),\n                    collation: program.curr_collation(),\n                },\n                target_register,\n                if_true_label,\n                lhs,\n                rhs,\n            );\n        }\n        ast::Operator::GreaterEquals => {\n            let if_true_label = program.allocate_label();\n            wrap_eval_jump_expr_zero_or_null(\n                program,\n                Insn::Ge {\n                    lhs,\n                    rhs,\n                    target_pc: if_true_label,\n                    flags: cmp_flags(),\n                    collation: program.curr_collation(),\n                },\n                target_register,\n                if_true_label,\n                lhs,\n                rhs,\n            );\n        }\n        ast::Operator::Add => {\n            program.emit_insn(Insn::Add {\n                lhs,\n                rhs,\n                dest: target_register,\n            });\n        }\n        ast::Operator::Subtract => {\n            program.emit_insn(Insn::Subtract {\n                lhs,\n                rhs,\n                dest: target_register,\n            });\n        }\n        ast::Operator::Multiply => {\n            program.emit_insn(Insn::Multiply {\n                lhs,\n                rhs,\n                dest: target_register,\n            });\n        }\n        ast::Operator::Divide => {\n            program.emit_insn(Insn::Divide {\n                lhs,\n                rhs,\n                dest: target_register,\n            });\n        }\n        ast::Operator::Modulus => {\n            program.emit_insn(Insn::Remainder {\n                lhs,\n                rhs,\n                dest: target_register,\n            });\n        }\n        ast::Operator::And => {\n            program.emit_insn(Insn::And {\n                lhs,\n                rhs,\n                dest: target_register,\n            });\n        }\n        ast::Operator::Or => {\n            program.emit_insn(Insn::Or {\n                lhs,\n                rhs,\n                dest: target_register,\n            });\n        }\n        ast::Operator::BitwiseAnd => {\n            program.emit_insn(Insn::BitAnd {\n                lhs,\n                rhs,\n                dest: target_register,\n            });\n        }\n        ast::Operator::BitwiseOr => {\n            program.emit_insn(Insn::BitOr {\n                lhs,\n                rhs,\n                dest: target_register,\n            });\n        }\n        ast::Operator::RightShift => {\n            program.emit_insn(Insn::ShiftRight {\n                lhs,\n                rhs,\n                dest: target_register,\n            });\n        }\n        ast::Operator::LeftShift => {\n            program.emit_insn(Insn::ShiftLeft {\n                lhs,\n                rhs,\n                dest: target_register,\n            });\n        }\n        ast::Operator::Is => {\n            let if_true_label = program.allocate_label();\n            wrap_eval_jump_expr(\n                program,\n                Insn::Eq {\n                    lhs,\n                    rhs,\n                    target_pc: if_true_label,\n                    flags: CmpInsFlags::default().null_eq().with_affinity(affinity),\n                    collation: program.curr_collation(),\n                },\n                target_register,\n                if_true_label,\n            );\n        }\n        ast::Operator::IsNot => {\n            let if_true_label = program.allocate_label();\n            wrap_eval_jump_expr(\n                program,\n                Insn::Ne {\n                    lhs,\n                    rhs,\n                    target_pc: if_true_label,\n                    flags: CmpInsFlags::default().null_eq().with_affinity(affinity),\n                    collation: program.curr_collation(),\n                },\n                target_register,\n                if_true_label,\n            );\n        }\n        #[cfg(feature = \"json\")]\n        op @ (ast::Operator::ArrowRight | ast::Operator::ArrowRightShift) => {\n            let json_func = match op {\n                ast::Operator::ArrowRight => JsonFunc::JsonArrowExtract,\n                ast::Operator::ArrowRightShift => JsonFunc::JsonArrowShiftExtract,\n                _ => unreachable!(),\n            };\n\n            program.emit_insn(Insn::Function {\n                constant_mask: 0,\n                start_reg: lhs,\n                dest: target_register,\n                func: FuncCtx {\n                    func: Func::Json(json_func),\n                    arg_count: 2,\n                },\n            })\n        }\n        ast::Operator::Concat => {\n            if expr_is_array(lhs_expr, referenced_tables)\n                || expr_is_array(rhs_expr, referenced_tables)\n            {\n                program.emit_insn(Insn::ArrayConcat {\n                    lhs,\n                    rhs,\n                    dest: target_register,\n                });\n            } else {\n                program.emit_insn(Insn::Concat {\n                    lhs,\n                    rhs,\n                    dest: target_register,\n                });\n            }\n        }\n        ast::Operator::ArrayContains | ast::Operator::ArrayOverlap => {\n            if let Some(r) = resolver {\n                r.require_custom_types(\"Array features\")?;\n            }\n            // Function instructions read contiguous registers start_reg..start_reg+arg_count.\n            // When both operands are equivalent the compiler reuses a single shared register,\n            // so we must copy it into a contiguous pair.\n            let start = if lhs == rhs {\n                let regs = program.alloc_registers(2);\n                program.emit_insn(Insn::Copy {\n                    src_reg: lhs,\n                    dst_reg: regs,\n                    extra_amount: 0,\n                });\n                program.emit_insn(Insn::Copy {\n                    src_reg: lhs,\n                    dst_reg: regs + 1,\n                    extra_amount: 0,\n                });\n                regs\n            } else {\n                lhs\n            };\n            let func = match op {\n                ast::Operator::ArrayContains => ScalarFunc::ArrayContainsAll,\n                ast::Operator::ArrayOverlap => ScalarFunc::ArrayOverlap,\n                _ => unreachable!(),\n            };\n            program.emit_insn(Insn::Function {\n                constant_mask: 0,\n                start_reg: start,\n                dest: target_register,\n                func: FuncCtx {\n                    func: Func::Scalar(func),\n                    arg_count: 2,\n                },\n            });\n        }\n        other_unimplemented => todo!(\"{:?}\", other_unimplemented),\n    }\n\n    Ok(())\n}\n\n/// Check if an expression is known to produce an array value.\npub(crate) fn expr_is_array(expr: &Expr, referenced_tables: Option<&TableReferences>) -> bool {\n    match expr {\n        Expr::Column { table, column, .. } => {\n            if let Some(tables) = referenced_tables {\n                tables\n                    .find_table_by_internal_id(*table)\n                    .map(|(_, t)| t)\n                    .and_then(|t| t.get_column_at(*column))\n                    .is_some_and(|col| col.is_array())\n            } else {\n                false\n            }\n        }\n        Expr::FunctionCall { name, args, .. } => {\n            if let Ok(f) = Func::resolve_function(name.as_str(), args.len()) {\n                match &f {\n                    Func::Scalar(sf) if sf.returns_array_blob() => return true,\n                    Func::Agg(AggFunc::ArrayAgg) => return true,\n                    _ => {}\n                }\n            }\n            // Wrapper functions that pass through an array value\n            match name.as_str().to_lowercase().as_str() {\n                \"coalesce\" | \"ifnull\" | \"min\" | \"max\" => {\n                    args.iter().any(|a| expr_is_array(a, referenced_tables))\n                }\n                \"iif\" => {\n                    // args: condition, then_val, else_val\n                    args.get(1)\n                        .is_some_and(|a| expr_is_array(a, referenced_tables))\n                        || args\n                            .get(2)\n                            .is_some_and(|a| expr_is_array(a, referenced_tables))\n                }\n                \"nullif\" => args\n                    .first()\n                    .is_some_and(|a| expr_is_array(a, referenced_tables)),\n                \"array_element\" => {\n                    // Subscripting a multi-dim array yields a lower-dim array\n                    if let Some(tables) = referenced_tables {\n                        args.first()\n                            .is_some_and(|a| expr_array_dimensions(a, tables) > 1)\n                    } else {\n                        false\n                    }\n                }\n                _ => false,\n            }\n        }\n        Expr::Array { .. } | Expr::Subscript { .. } => {\n            unreachable!(\"Array and Subscript are desugared into function calls by the parser\")\n        }\n        Expr::Binary(lhs, ast::Operator::Concat, rhs) => {\n            expr_is_array(lhs, referenced_tables) || expr_is_array(rhs, referenced_tables)\n        }\n        Expr::Case {\n            when_then_pairs,\n            else_expr,\n            ..\n        } => {\n            when_then_pairs\n                .iter()\n                .any(|(_, then_expr)| expr_is_array(then_expr, referenced_tables))\n                || else_expr\n                    .as_ref()\n                    .is_some_and(|e| expr_is_array(e, referenced_tables))\n        }\n        _ => false,\n    }\n}\n\n/// Return the number of array dimensions for an expression, or 0 for non-array.\nfn expr_array_dimensions(expr: &Expr, tables: &TableReferences) -> u32 {\n    match expr {\n        Expr::Column { table, column, .. } => tables\n            .find_table_by_internal_id(*table)\n            .map(|(_, t)| t)\n            .and_then(|t| t.get_column_at(*column))\n            .map(|col| col.array_dimensions())\n            .unwrap_or(0),\n        Expr::FunctionCall { name, args, .. }\n            if name.as_str().eq_ignore_ascii_case(\"array_element\") =>\n        {\n            let d = args\n                .first()\n                .map(|a| expr_array_dimensions(a, tables))\n                .unwrap_or(0);\n            d.saturating_sub(1)\n        }\n        Expr::FunctionCall { name, .. } if name.as_str().eq_ignore_ascii_case(\"array\") => 1,\n        Expr::Subscript { .. } | Expr::Array { .. } => {\n            unreachable!(\"Array and Subscript are desugared into function calls by the parser\")\n        }\n        _ => 0,\n    }\n}\n\n#[allow(clippy::too_many_arguments)]\nfn emit_binary_condition_insn(\n    program: &mut ProgramBuilder,\n    op: &ast::Operator,\n    lhs: usize,\n    rhs: usize,\n    target_register: usize,\n    lhs_expr: &Expr,\n    rhs_expr: &Expr,\n    referenced_tables: Option<&TableReferences>,\n    condition_metadata: Option<ConditionMetadata>,\n    resolver: Option<&Resolver>,\n) -> Result<()> {\n    let condition_metadata = condition_metadata\n        .expect(\"condition metadata must be provided for emit_binary_insn_conditional\");\n    let mut affinity = Affinity::Blob;\n    if op.is_comparison() {\n        affinity = comparison_affinity(lhs_expr, rhs_expr, referenced_tables, resolver);\n    }\n\n    let opposite_op = match op {\n        ast::Operator::NotEquals => ast::Operator::Equals,\n        ast::Operator::Equals => ast::Operator::NotEquals,\n        ast::Operator::Less => ast::Operator::GreaterEquals,\n        ast::Operator::LessEquals => ast::Operator::Greater,\n        ast::Operator::Greater => ast::Operator::LessEquals,\n        ast::Operator::GreaterEquals => ast::Operator::Less,\n        ast::Operator::Is => ast::Operator::IsNot,\n        ast::Operator::IsNot => ast::Operator::Is,\n        other => *other,\n    };\n\n    // For conditional jumps we need to use the opposite comparison operator\n    // when we intend to jump if the condition is false. Jumping when the condition is false\n    // is the common case, e.g.:\n    // WHERE x=1 turns into \"jump if x != 1\".\n    // However, in e.g. \"WHERE x=1 OR y=2\" we want to jump if the condition is true\n    // when evaluating \"x=1\", because we are jumping over the \"y=2\" condition, and if the condition\n    // is false we move on to the \"y=2\" condition without jumping.\n    let op_to_use = if condition_metadata.jump_if_condition_is_true {\n        *op\n    } else {\n        opposite_op\n    };\n\n    // Set the \"jump if NULL\" flag when the NULL target matches the jump target.\n    // When jump_if_condition_is_true: we jump on true, so set jump_if_null when NULL should also jump (e.g. CHECK constraints in integrity_check).\n    // When !jump_if_condition_is_true: we jump on false, so set jump_if_null when NULL should also jump (standard SQL 3-valued logic).\n    let mut flags = CmpInsFlags::default().with_affinity(affinity);\n    if expr_is_array(lhs_expr, referenced_tables) && expr_is_array(rhs_expr, referenced_tables) {\n        flags = flags.array_cmp();\n    }\n    if condition_metadata.jump_if_condition_is_true {\n        if condition_metadata.jump_target_when_null == condition_metadata.jump_target_when_true {\n            flags = flags.jump_if_null()\n        }\n    } else if condition_metadata.jump_target_when_null == condition_metadata.jump_target_when_false\n    {\n        flags = flags.jump_if_null()\n    };\n\n    let target_pc = if condition_metadata.jump_if_condition_is_true {\n        condition_metadata.jump_target_when_true\n    } else {\n        condition_metadata.jump_target_when_false\n    };\n\n    // For conditional jumps that don't have a clear \"opposite op\" (e.g. x+y), we check whether the result is nonzero/nonnull\n    // (or zero/null) depending on the condition metadata.\n    let eval_result = |program: &mut ProgramBuilder, result_reg: usize| {\n        if condition_metadata.jump_if_condition_is_true {\n            program.emit_insn(Insn::If {\n                reg: result_reg,\n                target_pc,\n                jump_if_null: false,\n            });\n        } else {\n            program.emit_insn(Insn::IfNot {\n                reg: result_reg,\n                target_pc,\n                jump_if_null: true,\n            });\n        }\n    };\n\n    match op_to_use {\n        ast::Operator::NotEquals => {\n            program.emit_insn(Insn::Ne {\n                lhs,\n                rhs,\n                target_pc,\n                flags,\n                collation: program.curr_collation(),\n            });\n        }\n        ast::Operator::Equals => {\n            program.emit_insn(Insn::Eq {\n                lhs,\n                rhs,\n                target_pc,\n                flags,\n                collation: program.curr_collation(),\n            });\n        }\n        ast::Operator::Less => {\n            program.emit_insn(Insn::Lt {\n                lhs,\n                rhs,\n                target_pc,\n                flags,\n                collation: program.curr_collation(),\n            });\n        }\n        ast::Operator::LessEquals => {\n            program.emit_insn(Insn::Le {\n                lhs,\n                rhs,\n                target_pc,\n                flags,\n                collation: program.curr_collation(),\n            });\n        }\n        ast::Operator::Greater => {\n            program.emit_insn(Insn::Gt {\n                lhs,\n                rhs,\n                target_pc,\n                flags,\n                collation: program.curr_collation(),\n            });\n        }\n        ast::Operator::GreaterEquals => {\n            program.emit_insn(Insn::Ge {\n                lhs,\n                rhs,\n                target_pc,\n                flags,\n                collation: program.curr_collation(),\n            });\n        }\n        ast::Operator::Is => {\n            program.emit_insn(Insn::Eq {\n                lhs,\n                rhs,\n                target_pc,\n                flags: flags.null_eq(),\n                collation: program.curr_collation(),\n            });\n        }\n        ast::Operator::IsNot => {\n            program.emit_insn(Insn::Ne {\n                lhs,\n                rhs,\n                target_pc,\n                flags: flags.null_eq(),\n                collation: program.curr_collation(),\n            });\n        }\n        ast::Operator::Add => {\n            program.emit_insn(Insn::Add {\n                lhs,\n                rhs,\n                dest: target_register,\n            });\n            eval_result(program, target_register);\n        }\n        ast::Operator::Subtract => {\n            program.emit_insn(Insn::Subtract {\n                lhs,\n                rhs,\n                dest: target_register,\n            });\n            eval_result(program, target_register);\n        }\n        ast::Operator::Multiply => {\n            program.emit_insn(Insn::Multiply {\n                lhs,\n                rhs,\n                dest: target_register,\n            });\n            eval_result(program, target_register);\n        }\n        ast::Operator::Divide => {\n            program.emit_insn(Insn::Divide {\n                lhs,\n                rhs,\n                dest: target_register,\n            });\n            eval_result(program, target_register);\n        }\n        ast::Operator::Modulus => {\n            program.emit_insn(Insn::Remainder {\n                lhs,\n                rhs,\n                dest: target_register,\n            });\n            eval_result(program, target_register);\n        }\n        ast::Operator::And => {\n            program.emit_insn(Insn::And {\n                lhs,\n                rhs,\n                dest: target_register,\n            });\n            eval_result(program, target_register);\n        }\n        ast::Operator::Or => {\n            program.emit_insn(Insn::Or {\n                lhs,\n                rhs,\n                dest: target_register,\n            });\n            eval_result(program, target_register);\n        }\n        ast::Operator::BitwiseAnd => {\n            program.emit_insn(Insn::BitAnd {\n                lhs,\n                rhs,\n                dest: target_register,\n            });\n            eval_result(program, target_register);\n        }\n        ast::Operator::BitwiseOr => {\n            program.emit_insn(Insn::BitOr {\n                lhs,\n                rhs,\n                dest: target_register,\n            });\n            eval_result(program, target_register);\n        }\n        ast::Operator::RightShift => {\n            program.emit_insn(Insn::ShiftRight {\n                lhs,\n                rhs,\n                dest: target_register,\n            });\n            eval_result(program, target_register);\n        }\n        ast::Operator::LeftShift => {\n            program.emit_insn(Insn::ShiftLeft {\n                lhs,\n                rhs,\n                dest: target_register,\n            });\n            eval_result(program, target_register);\n        }\n        #[cfg(feature = \"json\")]\n        op @ (ast::Operator::ArrowRight | ast::Operator::ArrowRightShift) => {\n            let json_func = match op {\n                ast::Operator::ArrowRight => JsonFunc::JsonArrowExtract,\n                ast::Operator::ArrowRightShift => JsonFunc::JsonArrowShiftExtract,\n                _ => unreachable!(),\n            };\n\n            program.emit_insn(Insn::Function {\n                constant_mask: 0,\n                start_reg: lhs,\n                dest: target_register,\n                func: FuncCtx {\n                    func: Func::Json(json_func),\n                    arg_count: 2,\n                },\n            });\n            eval_result(program, target_register);\n        }\n        ast::Operator::Concat => {\n            if expr_is_array(lhs_expr, referenced_tables)\n                || expr_is_array(rhs_expr, referenced_tables)\n            {\n                program.emit_insn(Insn::ArrayConcat {\n                    lhs,\n                    rhs,\n                    dest: target_register,\n                });\n            } else {\n                program.emit_insn(Insn::Concat {\n                    lhs,\n                    rhs,\n                    dest: target_register,\n                });\n            }\n            eval_result(program, target_register);\n        }\n        ast::Operator::ArrayContains | ast::Operator::ArrayOverlap => {\n            if let Some(r) = resolver {\n                r.require_custom_types(\"Array features\")?;\n            }\n            let start = if lhs == rhs {\n                let regs = program.alloc_registers(2);\n                program.emit_insn(Insn::Copy {\n                    src_reg: lhs,\n                    dst_reg: regs,\n                    extra_amount: 0,\n                });\n                program.emit_insn(Insn::Copy {\n                    src_reg: lhs,\n                    dst_reg: regs + 1,\n                    extra_amount: 0,\n                });\n                regs\n            } else {\n                lhs\n            };\n            let func = match op {\n                ast::Operator::ArrayContains => ScalarFunc::ArrayContainsAll,\n                ast::Operator::ArrayOverlap => ScalarFunc::ArrayOverlap,\n                _ => unreachable!(),\n            };\n            program.emit_insn(Insn::Function {\n                constant_mask: 0,\n                start_reg: start,\n                dest: target_register,\n                func: FuncCtx {\n                    func: Func::Scalar(func),\n                    arg_count: 2,\n                },\n            });\n            eval_result(program, target_register);\n        }\n        other_unimplemented => todo!(\"{:?}\", other_unimplemented),\n    }\n\n    Ok(())\n}\n\n/// The base logic for translating LIKE and GLOB expressions.\n/// The logic for handling \"NOT LIKE\" is different depending on whether the expression\n/// is a conditional jump or not. This is why the caller handles the \"NOT LIKE\" behavior;\n/// see [translate_condition_expr] and [translate_expr] for implementations.\nfn translate_like_base(\n    program: &mut ProgramBuilder,\n    referenced_tables: Option<&TableReferences>,\n    expr: &ast::Expr,\n    target_register: usize,\n    resolver: &Resolver,\n) -> Result<usize> {\n    let ast::Expr::Like {\n        lhs,\n        op,\n        rhs,\n        escape,\n        ..\n    } = expr\n    else {\n        crate::bail_parse_error!(\"expected Like expression\");\n    };\n    match op {\n        ast::LikeOperator::Like | ast::LikeOperator::Glob => {\n            let arg_count = if escape.is_some() { 3 } else { 2 };\n            let start_reg = program.alloc_registers(arg_count);\n            let mut constant_mask = 0;\n            translate_expr(program, referenced_tables, lhs, start_reg + 1, resolver)?;\n            let _ = translate_expr(program, referenced_tables, rhs, start_reg, resolver)?;\n            if arg_count == 3 {\n                if let Some(escape) = escape {\n                    translate_expr(program, referenced_tables, escape, start_reg + 2, resolver)?;\n                }\n            }\n            if matches!(rhs.as_ref(), ast::Expr::Literal(_)) {\n                program.mark_last_insn_constant();\n                constant_mask = 1;\n            }\n            let func = match op {\n                ast::LikeOperator::Like => ScalarFunc::Like,\n                ast::LikeOperator::Glob => ScalarFunc::Glob,\n                _ => unreachable!(),\n            };\n            program.emit_insn(Insn::Function {\n                constant_mask,\n                start_reg,\n                dest: target_register,\n                func: FuncCtx {\n                    func: Func::Scalar(func),\n                    arg_count,\n                },\n            });\n        }\n        #[cfg(all(feature = \"fts\", not(target_family = \"wasm\")))]\n        ast::LikeOperator::Match => {\n            // Transform MATCH to fts_match():\n            // - `col MATCH 'query'` -> `fts_match(col, 'query')`\n            // - `(col1, col2) MATCH 'query'` -> `fts_match(col1, col2, 'query')`\n            let columns: Vec<&ast::Expr> = match lhs.as_ref() {\n                ast::Expr::Parenthesized(cols) => cols.iter().map(|c| c.as_ref()).collect(),\n                other => vec![other],\n            };\n            let arg_count = columns.len() + 1; // columns + query\n            let start_reg = program.alloc_registers(arg_count);\n\n            for (i, col) in columns.iter().enumerate() {\n                translate_expr(program, referenced_tables, col, start_reg + i, resolver)?;\n            }\n            translate_expr(\n                program,\n                referenced_tables,\n                rhs,\n                start_reg + columns.len(),\n                resolver,\n            )?;\n\n            program.emit_insn(Insn::Function {\n                constant_mask: 0,\n                start_reg,\n                dest: target_register,\n                func: FuncCtx {\n                    func: Func::Fts(FtsFunc::Match),\n                    arg_count,\n                },\n            });\n        }\n        #[cfg(any(not(feature = \"fts\"), target_family = \"wasm\"))]\n        ast::LikeOperator::Match => {\n            crate::bail_parse_error!(\"MATCH requires the 'fts' feature to be enabled\")\n        }\n        ast::LikeOperator::Regexp => {\n            if escape.is_some() {\n                crate::bail_parse_error!(\"wrong number of arguments to function regexp()\");\n            }\n            let func = resolver.resolve_function(\"regexp\", 2);\n            let Some(func) = func else {\n                crate::bail_parse_error!(\"no such function: regexp\");\n            };\n            let arg_count = 2;\n            let start_reg = program.alloc_registers(arg_count);\n            // regexp(pattern, haystack) — pattern is rhs, haystack is lhs\n            translate_expr(program, referenced_tables, rhs, start_reg, resolver)?;\n            translate_expr(program, referenced_tables, lhs, start_reg + 1, resolver)?;\n            program.emit_insn(Insn::Function {\n                constant_mask: 0,\n                start_reg,\n                dest: target_register,\n                func: FuncCtx { func, arg_count },\n            });\n        }\n    }\n\n    Ok(target_register)\n}\n\n/// Emits a whole insn for a function call.\n/// Assumes the number of parameters is valid for the given function.\n/// Returns the target register for the function.\nfn translate_function(\n    program: &mut ProgramBuilder,\n    args: &[Box<ast::Expr>],\n    referenced_tables: Option<&TableReferences>,\n    resolver: &Resolver,\n    target_register: usize,\n    func_ctx: FuncCtx,\n) -> Result<usize> {\n    let start_reg = program.alloc_registers(args.len());\n    let mut current_reg = start_reg;\n\n    for arg in args.iter() {\n        translate_expr(program, referenced_tables, arg, current_reg, resolver)?;\n        current_reg += 1;\n    }\n\n    program.emit_insn(Insn::Function {\n        constant_mask: 0,\n        start_reg,\n        dest: target_register,\n        func: func_ctx,\n    });\n\n    Ok(target_register)\n}\n\nfn wrap_eval_jump_expr(\n    program: &mut ProgramBuilder,\n    insn: Insn,\n    target_register: usize,\n    if_true_label: BranchOffset,\n) {\n    program.emit_insn(Insn::Integer {\n        value: 1, // emit True by default\n        dest: target_register,\n    });\n    program.emit_insn(insn);\n    program.emit_insn(Insn::Integer {\n        value: 0, // emit False if we reach this point (no jump)\n        dest: target_register,\n    });\n    program.preassign_label_to_next_insn(if_true_label);\n}\n\nfn wrap_eval_jump_expr_zero_or_null(\n    program: &mut ProgramBuilder,\n    insn: Insn,\n    target_register: usize,\n    if_true_label: BranchOffset,\n    e1_reg: usize,\n    e2_reg: usize,\n) {\n    program.emit_insn(Insn::Integer {\n        value: 1, // emit True by default\n        dest: target_register,\n    });\n    program.emit_insn(insn);\n    program.emit_insn(Insn::ZeroOrNull {\n        rg1: e1_reg,\n        rg2: e2_reg,\n        dest: target_register,\n    });\n    program.preassign_label_to_next_insn(if_true_label);\n}\n\npub fn maybe_apply_affinity(col_type: Type, target_register: usize, program: &mut ProgramBuilder) {\n    if col_type == Type::Real {\n        program.emit_insn(Insn::RealAffinity {\n            register: target_register,\n        })\n    }\n}\n\n/// Sanitizes a string literal by removing single quote at front and back\n/// and escaping double single quotes\npub fn sanitize_string(input: &str) -> String {\n    let inner = &input[1..input.len() - 1];\n\n    // Fast path, avoid replacing.\n    if !inner.contains(\"''\") {\n        return inner.to_string();\n    }\n\n    inner.replace(\"''\", \"'\")\n}\n\n/// Returns the components of a binary expression\n/// e.g. t.x = 5 -> Some((t.x, =, 5))\npub fn as_binary_components(\n    expr: &ast::Expr,\n) -> Result<Option<(&ast::Expr, ConstraintOperator, &ast::Expr)>> {\n    match unwrap_parens(expr)? {\n        ast::Expr::Binary(lhs, operator, rhs)\n            if matches!(\n                operator,\n                ast::Operator::Equals\n                    | ast::Operator::NotEquals\n                    | ast::Operator::Greater\n                    | ast::Operator::Less\n                    | ast::Operator::GreaterEquals\n                    | ast::Operator::LessEquals\n                    | ast::Operator::Is\n                    | ast::Operator::IsNot\n            ) =>\n        {\n            // Row-valued binary comparisons are translated directly in expression codegen.\n            // They are not safe to expose as scalar binary constraints in optimizer paths.\n            if expr_vector_size(lhs)? > 1 || expr_vector_size(rhs)? > 1 {\n                return Ok(None);\n            }\n            Ok(Some((lhs.as_ref(), (*operator).into(), rhs.as_ref())))\n        }\n        ast::Expr::Like { lhs, not, rhs, .. } => Ok(Some((\n            lhs.as_ref(),\n            ConstraintOperator::Like { not: *not },\n            rhs.as_ref(),\n        ))),\n        _ => Ok(None),\n    }\n}\n\n/// Recursively unwrap parentheses from an expression\n/// e.g. (((t.x > 5))) -> t.x > 5\npub fn unwrap_parens(expr: &ast::Expr) -> Result<&ast::Expr> {\n    match expr {\n        ast::Expr::Column { .. } => Ok(expr),\n        ast::Expr::Parenthesized(exprs) => match exprs.len() {\n            1 => unwrap_parens(exprs.first().unwrap()),\n            _ => Ok(expr), // If the expression is e.g. (x, y), as used in e.g. (x, y) IN (SELECT ...), return as is.\n        },\n        _ => Ok(expr),\n    }\n}\n\n/// Recursively unwrap parentheses from an owned Expr.\n/// Returns how many pairs of parentheses were removed.\npub fn unwrap_parens_owned(expr: ast::Expr) -> Result<(ast::Expr, usize)> {\n    let mut paren_count = 0;\n    match expr {\n        ast::Expr::Parenthesized(mut exprs) => match exprs.len() {\n            1 => {\n                paren_count += 1;\n                let (expr, count) = unwrap_parens_owned(*exprs.pop().unwrap())?;\n                paren_count += count;\n                Ok((expr, paren_count))\n            }\n            _ => crate::bail_parse_error!(\"expected single expression in parentheses\"),\n        },\n        _ => Ok((expr, paren_count)),\n    }\n}\n\npub enum WalkControl {\n    Continue,     // Visit children\n    SkipChildren, // Skip children but continue walking siblings\n}\n\n/// Recursively walks an immutable expression, applying a function to each sub-expression.\npub fn walk_expr<'a, F>(expr: &'a ast::Expr, func: &mut F) -> Result<WalkControl>\nwhere\n    F: FnMut(&'a ast::Expr) -> Result<WalkControl>,\n{\n    match func(expr)? {\n        WalkControl::Continue => {\n            match expr {\n                ast::Expr::SubqueryResult { lhs, .. } => {\n                    if let Some(lhs) = lhs {\n                        walk_expr(lhs, func)?;\n                    }\n                }\n                ast::Expr::Between {\n                    lhs, start, end, ..\n                } => {\n                    walk_expr(lhs, func)?;\n                    walk_expr(start, func)?;\n                    walk_expr(end, func)?;\n                }\n                ast::Expr::Binary(lhs, _, rhs) => {\n                    walk_expr(lhs, func)?;\n                    walk_expr(rhs, func)?;\n                }\n                ast::Expr::Case {\n                    base,\n                    when_then_pairs,\n                    else_expr,\n                } => {\n                    if let Some(base_expr) = base {\n                        walk_expr(base_expr, func)?;\n                    }\n                    for (when_expr, then_expr) in when_then_pairs {\n                        walk_expr(when_expr, func)?;\n                        walk_expr(then_expr, func)?;\n                    }\n                    if let Some(else_expr) = else_expr {\n                        walk_expr(else_expr, func)?;\n                    }\n                }\n                ast::Expr::Cast { expr, .. } => {\n                    walk_expr(expr, func)?;\n                }\n                ast::Expr::Collate(expr, _) => {\n                    walk_expr(expr, func)?;\n                }\n                ast::Expr::Exists(_select) | ast::Expr::Subquery(_select) => {\n                    // TODO: Walk through select statements if needed\n                }\n                ast::Expr::FunctionCall {\n                    args,\n                    order_by,\n                    filter_over,\n                    ..\n                } => {\n                    for arg in args {\n                        walk_expr(arg, func)?;\n                    }\n                    for sort_col in order_by {\n                        walk_expr(&sort_col.expr, func)?;\n                    }\n                    if let Some(filter_clause) = &filter_over.filter_clause {\n                        walk_expr(filter_clause, func)?;\n                    }\n                    if let Some(over_clause) = &filter_over.over_clause {\n                        match over_clause {\n                            ast::Over::Window(window) => {\n                                for part_expr in &window.partition_by {\n                                    walk_expr(part_expr, func)?;\n                                }\n                                for sort_col in &window.order_by {\n                                    walk_expr(&sort_col.expr, func)?;\n                                }\n                                if let Some(frame_clause) = &window.frame_clause {\n                                    walk_expr_frame_bound(&frame_clause.start, func)?;\n                                    if let Some(end_bound) = &frame_clause.end {\n                                        walk_expr_frame_bound(end_bound, func)?;\n                                    }\n                                }\n                            }\n                            ast::Over::Name(_) => {}\n                        }\n                    }\n                }\n                ast::Expr::FunctionCallStar { filter_over, .. } => {\n                    if let Some(filter_clause) = &filter_over.filter_clause {\n                        walk_expr(filter_clause, func)?;\n                    }\n                    if let Some(over_clause) = &filter_over.over_clause {\n                        match over_clause {\n                            ast::Over::Window(window) => {\n                                for part_expr in &window.partition_by {\n                                    walk_expr(part_expr, func)?;\n                                }\n                                for sort_col in &window.order_by {\n                                    walk_expr(&sort_col.expr, func)?;\n                                }\n                                if let Some(frame_clause) = &window.frame_clause {\n                                    walk_expr_frame_bound(&frame_clause.start, func)?;\n                                    if let Some(end_bound) = &frame_clause.end {\n                                        walk_expr_frame_bound(end_bound, func)?;\n                                    }\n                                }\n                            }\n                            ast::Over::Name(_) => {}\n                        }\n                    }\n                }\n                ast::Expr::InList { lhs, rhs, .. } => {\n                    walk_expr(lhs, func)?;\n                    for expr in rhs {\n                        walk_expr(expr, func)?;\n                    }\n                }\n                ast::Expr::InSelect { lhs, rhs: _, .. } => {\n                    walk_expr(lhs, func)?;\n                    // TODO: Walk through select statements if needed\n                }\n                ast::Expr::InTable { lhs, args, .. } => {\n                    walk_expr(lhs, func)?;\n                    for expr in args {\n                        walk_expr(expr, func)?;\n                    }\n                }\n                ast::Expr::IsNull(expr) | ast::Expr::NotNull(expr) => {\n                    walk_expr(expr, func)?;\n                }\n                ast::Expr::Like {\n                    lhs, rhs, escape, ..\n                } => {\n                    walk_expr(lhs, func)?;\n                    walk_expr(rhs, func)?;\n                    if let Some(esc_expr) = escape {\n                        walk_expr(esc_expr, func)?;\n                    }\n                }\n                ast::Expr::Parenthesized(exprs) => {\n                    for expr in exprs {\n                        walk_expr(expr, func)?;\n                    }\n                }\n                ast::Expr::Raise(_, expr) => {\n                    if let Some(raise_expr) = expr {\n                        walk_expr(raise_expr, func)?;\n                    }\n                }\n                ast::Expr::Unary(_, expr) => {\n                    walk_expr(expr, func)?;\n                }\n                ast::Expr::Array { .. } | ast::Expr::Subscript { .. } => {\n                    unreachable!(\n                        \"Array and Subscript are desugared into function calls by the parser\"\n                    )\n                }\n                ast::Expr::Id(_)\n                | ast::Expr::Column { .. }\n                | ast::Expr::RowId { .. }\n                | ast::Expr::Literal(_)\n                | ast::Expr::DoublyQualified(..)\n                | ast::Expr::Name(_)\n                | ast::Expr::Qualified(..)\n                | ast::Expr::Variable(_)\n                | ast::Expr::Register(_) => {\n                    // No nested expressions\n                }\n            }\n        }\n        WalkControl::SkipChildren => return Ok(WalkControl::Continue),\n    };\n    Ok(WalkControl::Continue)\n}\n\npub fn expr_references_subquery_id(expr: &ast::Expr, subquery_id: TableInternalId) -> bool {\n    let mut found = false;\n    let _ = walk_expr(expr, &mut |e: &ast::Expr| -> Result<WalkControl> {\n        if let ast::Expr::SubqueryResult {\n            subquery_id: sid, ..\n        } = e\n        {\n            if *sid == subquery_id {\n                found = true;\n                return Ok(WalkControl::SkipChildren);\n            }\n        }\n        Ok(WalkControl::Continue)\n    });\n    found\n}\n\npub fn expr_references_any_subquery(expr: &ast::Expr) -> bool {\n    let mut found = false;\n    let _ = walk_expr(expr, &mut |e: &ast::Expr| -> Result<WalkControl> {\n        if matches!(e, ast::Expr::SubqueryResult { .. }) {\n            found = true;\n            return Ok(WalkControl::SkipChildren);\n        }\n        Ok(WalkControl::Continue)\n    });\n    found\n}\n\nfn walk_expr_frame_bound<'a, F>(bound: &'a ast::FrameBound, func: &mut F) -> Result<WalkControl>\nwhere\n    F: FnMut(&'a ast::Expr) -> Result<WalkControl>,\n{\n    match bound {\n        ast::FrameBound::Following(expr) | ast::FrameBound::Preceding(expr) => {\n            walk_expr(expr, func)?;\n        }\n        ast::FrameBound::CurrentRow\n        | ast::FrameBound::UnboundedFollowing\n        | ast::FrameBound::UnboundedPreceding => {}\n    }\n\n    Ok(WalkControl::Continue)\n}\n\n/// The precedence of binding identifiers to columns.\n///\n/// TryResultColumnsFirst means that result columns (e.g. SELECT x AS y, ...) take precedence over canonical columns (e.g. SELECT x, y AS z, ...). This is the default behavior.\n///\n/// TryCanonicalColumnsFirst means that canonical columns take precedence over result columns. This is used for e.g. WHERE clauses.\n///\n/// ResultColumnsNotAllowed means that referring to result columns is not allowed. This is used e.g. for DML statements.\n///\n/// AllowUnboundIdentifiers means that unbound identifiers are allowed. This is used for INSERT ... ON CONFLICT DO UPDATE SET ... where binding is handled later than this phase.\n#[derive(Debug, Clone, PartialEq, Eq)]\npub enum BindingBehavior {\n    TryResultColumnsFirst,\n    TryCanonicalColumnsFirst,\n    ResultColumnsNotAllowed,\n    AllowUnboundIdentifiers,\n}\n\n/// Rewrite ast::Expr in place, binding Column references/rewriting Expr::Id -> Expr::Column\n/// using the provided TableReferences, and replacing anonymous parameters with internal named\n/// ones\npub fn bind_and_rewrite_expr<'a>(\n    top_level_expr: &mut ast::Expr,\n    mut referenced_tables: Option<&'a mut TableReferences>,\n    result_columns: Option<&'a [ResultSetColumn]>,\n    resolver: &Resolver<'_>,\n    binding_behavior: BindingBehavior,\n) -> Result<()> {\n    walk_expr_mut(\n        top_level_expr,\n        &mut |expr: &mut ast::Expr| -> Result<WalkControl> {\n            match expr {\n                Expr::Id(id) => {\n                    let Some(referenced_tables) = &mut referenced_tables else {\n                        if binding_behavior == BindingBehavior::AllowUnboundIdentifiers {\n                            return Ok(WalkControl::Continue);\n                        }\n                        crate::bail_parse_error!(\"no such column: {}\", id.as_str());\n                    };\n                    let normalized_id = normalize_ident(id.as_str());\n\n                    if binding_behavior == BindingBehavior::TryResultColumnsFirst {\n                        if let Some(result_columns) = result_columns {\n                            for result_column in result_columns.iter() {\n                                if let Some(alias) = &result_column.alias {\n                                    if alias.eq_ignore_ascii_case(&normalized_id) {\n                                        *expr = result_column.expr.clone();\n                                        return Ok(WalkControl::Continue);\n                                    }\n                                }\n                            }\n                        }\n                    }\n                    let mut match_result = None;\n\n                    // First check joined tables\n                    for joined_table in referenced_tables.joined_tables().iter() {\n                        let col_idx = joined_table.table.columns().iter().position(|c| {\n                            c.name\n                                .as_ref()\n                                .is_some_and(|name| name.eq_ignore_ascii_case(&normalized_id))\n                        });\n                        if col_idx.is_some() {\n                            if match_result.is_some() {\n                                let mut ok = false;\n                                // Column name ambiguity is ok if it is in the USING clause because then it is deduplicated\n                                // and the left table is used.\n                                if let Some(join_info) = &joined_table.join_info {\n                                    if join_info.using.iter().any(|using_col| {\n                                        using_col.as_str().eq_ignore_ascii_case(&normalized_id)\n                                    }) {\n                                        ok = true;\n                                    }\n                                }\n                                if !ok {\n                                    crate::bail_parse_error!(\"Column {} is ambiguous\", id.as_str());\n                                }\n                            } else {\n                                let col =\n                                    joined_table.table.columns().get(col_idx.unwrap()).unwrap();\n                                match_result = Some((\n                                    joined_table.internal_id,\n                                    col_idx.unwrap(),\n                                    col.is_rowid_alias(),\n                                ));\n                            }\n                        // only if we haven't found a match, check for explicit rowid reference\n                        } else if let Table::BTree(btree) = &joined_table.table {\n                            if let Some(row_id_expr) = parse_row_id(\n                                &normalized_id,\n                                referenced_tables.joined_tables()[0].internal_id,\n                                || referenced_tables.joined_tables().len() != 1,\n                            )? {\n                                if !btree.has_rowid {\n                                    crate::bail_parse_error!(\"no such column: {}\", id.as_str());\n                                }\n                                *expr = row_id_expr;\n                                return Ok(WalkControl::Continue);\n                            }\n                        }\n                    }\n\n                    // Then check outer query references, if we still didn't find something.\n                    // Normally finding multiple matches for a non-qualified column is an error (column x is ambiguous)\n                    // but in the case of subqueries, the inner query takes precedence.\n                    // For example:\n                    // SELECT * FROM t WHERE x = (SELECT x FROM t2)\n                    // In this case, there is no ambiguity:\n                    // - x in the outer query refers to t.x,\n                    // - x in the inner query refers to t2.x.\n                    if match_result.is_none() {\n                        for outer_ref in referenced_tables.outer_query_refs().iter() {\n                            // CTEs (FromClauseSubquery) in outer_query_refs are only for table\n                            // lookup (e.g., FROM cte1), not for column resolution. Columns from\n                            // CTEs should only be accessible when the CTE is explicitly in the\n                            // FROM clause, not as implicit outer references.\n                            if matches!(outer_ref.table, Table::FromClauseSubquery(_)) {\n                                continue;\n                            }\n                            let col_idx = outer_ref.table.columns().iter().position(|c| {\n                                c.name\n                                    .as_ref()\n                                    .is_some_and(|name| name.eq_ignore_ascii_case(&normalized_id))\n                            });\n                            if col_idx.is_some() {\n                                if match_result.is_some() {\n                                    crate::bail_parse_error!(\"Column {} is ambiguous\", id.as_str());\n                                }\n                                let col = outer_ref.table.columns().get(col_idx.unwrap()).unwrap();\n                                match_result = Some((\n                                    outer_ref.internal_id,\n                                    col_idx.unwrap(),\n                                    col.is_rowid_alias(),\n                                ));\n                            }\n                        }\n                    }\n\n                    if let Some((table_id, col_idx, is_rowid_alias)) = match_result {\n                        *expr = Expr::Column {\n                            database: None, // TODO: support different databases\n                            table: table_id,\n                            column: col_idx,\n                            is_rowid_alias,\n                        };\n                        referenced_tables.mark_column_used(table_id, col_idx);\n                        return Ok(WalkControl::Continue);\n                    }\n\n                    if binding_behavior == BindingBehavior::TryCanonicalColumnsFirst {\n                        if let Some(result_columns) = result_columns {\n                            for result_column in result_columns.iter() {\n                                if let Some(alias) = &result_column.alias {\n                                    if alias.eq_ignore_ascii_case(&normalized_id) {\n                                        *expr = result_column.expr.clone();\n                                        return Ok(WalkControl::Continue);\n                                    }\n                                }\n                            }\n                        }\n                    }\n\n                    // SQLite behavior: Only double-quoted identifiers get fallback to string literals\n                    // Single quotes are handled as literals earlier, unquoted identifiers must resolve to columns\n                    if id.quoted_with('\"') {\n                        // Convert failed double-quoted identifier to string literal\n                        *expr = Expr::Literal(ast::Literal::String(id.as_literal()));\n                        return Ok(WalkControl::Continue);\n                    } else {\n                        // Unquoted identifiers must resolve to columns - no fallback\n                        crate::bail_parse_error!(\"no such column: {}\", id.as_str())\n                    }\n                }\n                Expr::Qualified(tbl, id) => {\n                    tracing::debug!(\"bind_and_rewrite_expr({:?}, {:?})\", tbl, id);\n                    let Some(referenced_tables) = &mut referenced_tables else {\n                        if binding_behavior == BindingBehavior::AllowUnboundIdentifiers {\n                            return Ok(WalkControl::Continue);\n                        }\n                        crate::bail_parse_error!(\n                            \"no such column: {}.{}\",\n                            tbl.as_str(),\n                            id.as_str()\n                        );\n                    };\n                    let normalized_table_name = normalize_ident(tbl.as_str());\n                    let matching_tbl = referenced_tables\n                        .find_table_and_internal_id_by_identifier(&normalized_table_name);\n                    if matching_tbl.is_none() {\n                        // CTEs preplanned for subquery FROM visibility are kept as\n                        // definition-only outer refs. They are not valid column sources\n                        // unless explicitly referenced in this scope's FROM clause.\n                        // Restrict this branch to actual CTE definition refs so other\n                        // definition-only uses (if added later) still report \"no such table\".\n                        if referenced_tables\n                            .find_outer_query_ref_by_identifier(&normalized_table_name)\n                            .is_some_and(|outer_ref| {\n                                outer_ref.cte_definition_only\n                                    && (outer_ref.cte_id.is_some()\n                                        || outer_ref.cte_select.is_some())\n                            })\n                        {\n                            crate::bail_parse_error!(\n                                \"no such column: {}.{}\",\n                                tbl.as_str(),\n                                id.as_str()\n                            );\n                        }\n                        crate::bail_parse_error!(\"no such table: {}\", normalized_table_name);\n                    }\n                    let (tbl_id, tbl) = matching_tbl.unwrap();\n                    let normalized_id = normalize_ident(id.as_str());\n                    let col_idx = tbl.columns().iter().position(|c| {\n                        c.name\n                            .as_ref()\n                            .is_some_and(|name| name.eq_ignore_ascii_case(&normalized_id))\n                    });\n                    // User-defined columns take precedence over rowid aliases\n                    // (oid, rowid, _rowid_). Only fall back to parse_row_id()\n                    // when no matching user column exists.\n                    // Note: Only BTree tables have rowid; derived tables (FromClauseSubquery)\n                    // don't have a rowid.\n                    let Some(col_idx) = col_idx else {\n                        if let Table::BTree(btree) = tbl {\n                            if let Some(row_id_expr) =\n                                parse_row_id(&normalized_id, tbl_id, || false)?\n                            {\n                                if !btree.has_rowid {\n                                    crate::bail_parse_error!(\"no such column: {}\", normalized_id);\n                                }\n                                *expr = row_id_expr;\n                                // Mark the table's rowid as referenced so correlated\n                                // subquery detection works correctly when a rowid\n                                // reference is the only link to the outer query.\n                                referenced_tables.mark_rowid_referenced(tbl_id);\n                                return Ok(WalkControl::Continue);\n                            }\n                        }\n                        crate::bail_parse_error!(\"no such column: {}\", normalized_id);\n                    };\n                    let col = tbl.columns().get(col_idx).unwrap();\n                    *expr = Expr::Column {\n                        database: None, // TODO: support different databases\n                        table: tbl_id,\n                        column: col_idx,\n                        is_rowid_alias: col.is_rowid_alias(),\n                    };\n                    tracing::debug!(\"rewritten to column\");\n                    referenced_tables.mark_column_used(tbl_id, col_idx);\n                    return Ok(WalkControl::Continue);\n                }\n                Expr::DoublyQualified(db_name, tbl_name, col_name) => {\n                    let Some(referenced_tables) = &mut referenced_tables else {\n                        if binding_behavior == BindingBehavior::AllowUnboundIdentifiers {\n                            return Ok(WalkControl::Continue);\n                        }\n                        crate::bail_parse_error!(\n                            \"no such column: {}.{}.{}\",\n                            db_name.as_str(),\n                            tbl_name.as_str(),\n                            col_name.as_str()\n                        );\n                    };\n                    let normalized_col_name = normalize_ident(col_name.as_str());\n\n                    // Create a QualifiedName and use existing resolve_database_id method\n                    let qualified_name = ast::QualifiedName {\n                        db_name: Some(db_name.clone()),\n                        name: tbl_name.clone(),\n                        alias: None,\n                    };\n                    let database_id = resolver.resolve_database_id(&qualified_name)?;\n\n                    // Get the table from the specified database\n                    let table = resolver\n                        .with_schema(database_id, |schema| schema.get_table(tbl_name.as_str()))\n                        .ok_or_else(|| {\n                            LimboError::ParseError(format!(\n                                \"no such table: {}.{}\",\n                                db_name.as_str(),\n                                tbl_name.as_str()\n                            ))\n                        })?;\n\n                    // Find the column in the table\n                    let col_idx = table\n                        .columns()\n                        .iter()\n                        .position(|c| {\n                            c.name\n                                .as_ref()\n                                .is_some_and(|name| name.eq_ignore_ascii_case(&normalized_col_name))\n                        })\n                        .ok_or_else(|| {\n                            LimboError::ParseError(format!(\n                                \"Column: {}.{}.{} not found\",\n                                db_name.as_str(),\n                                tbl_name.as_str(),\n                                col_name.as_str()\n                            ))\n                        })?;\n\n                    let col = table.columns().get(col_idx).unwrap();\n\n                    // Check if this is a rowid alias\n                    let is_rowid_alias = col.is_rowid_alias();\n\n                    // Convert to Column expression - since this is a cross-database reference,\n                    // we need to create a synthetic table reference for it\n                    // For now, we'll error if the table isn't already in the referenced tables\n                    let normalized_tbl_name = normalize_ident(tbl_name.as_str());\n                    let matching_tbl = referenced_tables\n                        .find_table_and_internal_id_by_identifier(&normalized_tbl_name);\n\n                    if let Some((tbl_id, _)) = matching_tbl {\n                        // Table is already in referenced tables, use existing internal ID\n                        *expr = Expr::Column {\n                            database: Some(database_id),\n                            table: tbl_id,\n                            column: col_idx,\n                            is_rowid_alias,\n                        };\n                        referenced_tables.mark_column_used(tbl_id, col_idx);\n                    } else {\n                        return Err(LimboError::ParseError(format!(\n                            \"table {normalized_tbl_name} is not in FROM clause - cross-database column references require the table to be explicitly joined\"\n                        )));\n                    }\n                }\n                Expr::FunctionCallStar { name, filter_over } => {\n                    // For functions that need star expansion (json_object, jsonb_object),\n                    // expand the * to all columns from the referenced tables as key-value pairs\n                    // This needs to happen during bind/rewrite so WHERE clauses can use these functions\n                    if let Some(referenced_tables) = &mut referenced_tables {\n                        if let Ok(func) = Func::resolve_function(name.as_str(), 0) {\n                            if func.needs_star_expansion() {\n                                // Only expand if there are actual tables - otherwise leave as\n                                // FunctionCallStar so translate_expr can generate the error\n                                if !referenced_tables.joined_tables().is_empty() {\n                                    // Mark all columns as used so the optimizer doesn't\n                                    // create partial covering indexes that would miss columns\n                                    for table in referenced_tables.joined_tables_mut().iter_mut() {\n                                        for col_idx in 0..table.columns().len() {\n                                            table.mark_column_used(col_idx);\n                                        }\n                                    }\n\n                                    // Build arguments: alternating column_name (as string literal), column_value (as column reference)\n                                    let mut args: Vec<Box<ast::Expr>> = Vec::new();\n\n                                    for table in referenced_tables.joined_tables().iter() {\n                                        for (col_idx, col) in table.columns().iter().enumerate() {\n                                            // Skip hidden columns (like rowid in some cases)\n                                            if col.hidden() {\n                                                continue;\n                                            }\n\n                                            // Add column name as a string literal\n                                            let col_name = col.name.clone().unwrap_or_else(|| {\n                                                format!(\"column{}\", col_idx + 1)\n                                            });\n                                            let quoted_col_name = format!(\"'{col_name}'\");\n                                            args.push(Box::new(ast::Expr::Literal(\n                                                ast::Literal::String(quoted_col_name),\n                                            )));\n\n                                            // Add column reference using Expr::Column\n                                            args.push(Box::new(ast::Expr::Column {\n                                                database: None,\n                                                table: table.internal_id,\n                                                column: col_idx,\n                                                is_rowid_alias: col.is_rowid_alias(),\n                                            }));\n                                        }\n                                    }\n\n                                    // Replace FunctionCallStar with expanded FunctionCall\n                                    *expr = ast::Expr::FunctionCall {\n                                        name: name.clone(),\n                                        distinctness: None,\n                                        args,\n                                        filter_over: filter_over.clone(),\n                                        order_by: vec![],\n                                    };\n                                }\n                            }\n                        }\n                    }\n                }\n                _ => {}\n            }\n            Ok(WalkControl::Continue)\n        },\n    )?;\n    Ok(())\n}\n\n/// Recursively walks a mutable expression, applying a function to each sub-expression.\npub fn walk_expr_mut<F>(expr: &mut ast::Expr, func: &mut F) -> Result<WalkControl>\nwhere\n    F: FnMut(&mut ast::Expr) -> Result<WalkControl>,\n{\n    match func(expr)? {\n        WalkControl::Continue => {\n            match expr {\n                ast::Expr::SubqueryResult { lhs, .. } => {\n                    if let Some(lhs) = lhs {\n                        walk_expr_mut(lhs, func)?;\n                    }\n                }\n                ast::Expr::Between {\n                    lhs, start, end, ..\n                } => {\n                    walk_expr_mut(lhs, func)?;\n                    walk_expr_mut(start, func)?;\n                    walk_expr_mut(end, func)?;\n                }\n                ast::Expr::Binary(lhs, _, rhs) => {\n                    walk_expr_mut(lhs, func)?;\n                    walk_expr_mut(rhs, func)?;\n                }\n                ast::Expr::Case {\n                    base,\n                    when_then_pairs,\n                    else_expr,\n                } => {\n                    if let Some(base_expr) = base {\n                        walk_expr_mut(base_expr, func)?;\n                    }\n                    for (when_expr, then_expr) in when_then_pairs {\n                        walk_expr_mut(when_expr, func)?;\n                        walk_expr_mut(then_expr, func)?;\n                    }\n                    if let Some(else_expr) = else_expr {\n                        walk_expr_mut(else_expr, func)?;\n                    }\n                }\n                ast::Expr::Cast { expr, .. } => {\n                    walk_expr_mut(expr, func)?;\n                }\n                ast::Expr::Collate(expr, _) => {\n                    walk_expr_mut(expr, func)?;\n                }\n                ast::Expr::Exists(_) | ast::Expr::Subquery(_) => {\n                    // TODO: Walk through select statements if needed\n                }\n                ast::Expr::FunctionCall {\n                    args,\n                    order_by,\n                    filter_over,\n                    ..\n                } => {\n                    for arg in args {\n                        walk_expr_mut(arg, func)?;\n                    }\n                    for sort_col in order_by {\n                        walk_expr_mut(&mut sort_col.expr, func)?;\n                    }\n                    if let Some(filter_clause) = &mut filter_over.filter_clause {\n                        walk_expr_mut(filter_clause, func)?;\n                    }\n                    if let Some(over_clause) = &mut filter_over.over_clause {\n                        match over_clause {\n                            ast::Over::Window(window) => {\n                                for part_expr in &mut window.partition_by {\n                                    walk_expr_mut(part_expr, func)?;\n                                }\n                                for sort_col in &mut window.order_by {\n                                    walk_expr_mut(&mut sort_col.expr, func)?;\n                                }\n                                if let Some(frame_clause) = &mut window.frame_clause {\n                                    walk_expr_mut_frame_bound(&mut frame_clause.start, func)?;\n                                    if let Some(end_bound) = &mut frame_clause.end {\n                                        walk_expr_mut_frame_bound(end_bound, func)?;\n                                    }\n                                }\n                            }\n                            ast::Over::Name(_) => {}\n                        }\n                    }\n                }\n                ast::Expr::FunctionCallStar { filter_over, .. } => {\n                    if let Some(ref mut filter_clause) = filter_over.filter_clause {\n                        walk_expr_mut(filter_clause, func)?;\n                    }\n                    if let Some(ref mut over_clause) = filter_over.over_clause {\n                        match over_clause {\n                            ast::Over::Window(window) => {\n                                for part_expr in &mut window.partition_by {\n                                    walk_expr_mut(part_expr, func)?;\n                                }\n                                for sort_col in &mut window.order_by {\n                                    walk_expr_mut(&mut sort_col.expr, func)?;\n                                }\n                                if let Some(frame_clause) = &mut window.frame_clause {\n                                    walk_expr_mut_frame_bound(&mut frame_clause.start, func)?;\n                                    if let Some(end_bound) = &mut frame_clause.end {\n                                        walk_expr_mut_frame_bound(end_bound, func)?;\n                                    }\n                                }\n                            }\n                            ast::Over::Name(_) => {}\n                        }\n                    }\n                }\n                ast::Expr::InList { lhs, rhs, .. } => {\n                    walk_expr_mut(lhs, func)?;\n                    for expr in rhs {\n                        walk_expr_mut(expr, func)?;\n                    }\n                }\n                ast::Expr::InSelect { lhs, rhs: _, .. } => {\n                    walk_expr_mut(lhs, func)?;\n                    // TODO: Walk through select statements if needed\n                }\n                ast::Expr::InTable { lhs, args, .. } => {\n                    walk_expr_mut(lhs, func)?;\n                    for expr in args {\n                        walk_expr_mut(expr, func)?;\n                    }\n                }\n                ast::Expr::IsNull(expr) | ast::Expr::NotNull(expr) => {\n                    walk_expr_mut(expr, func)?;\n                }\n                ast::Expr::Like {\n                    lhs, rhs, escape, ..\n                } => {\n                    walk_expr_mut(lhs, func)?;\n                    walk_expr_mut(rhs, func)?;\n                    if let Some(esc_expr) = escape {\n                        walk_expr_mut(esc_expr, func)?;\n                    }\n                }\n                ast::Expr::Parenthesized(exprs) => {\n                    for expr in exprs {\n                        walk_expr_mut(expr, func)?;\n                    }\n                }\n                ast::Expr::Raise(_, expr) => {\n                    if let Some(raise_expr) = expr {\n                        walk_expr_mut(raise_expr, func)?;\n                    }\n                }\n                ast::Expr::Unary(_, expr) => {\n                    walk_expr_mut(expr, func)?;\n                }\n                ast::Expr::Array { .. } | ast::Expr::Subscript { .. } => {\n                    unreachable!(\n                        \"Array and Subscript are desugared into function calls by the parser\"\n                    )\n                }\n                ast::Expr::Id(_)\n                | ast::Expr::Column { .. }\n                | ast::Expr::RowId { .. }\n                | ast::Expr::Literal(_)\n                | ast::Expr::DoublyQualified(..)\n                | ast::Expr::Name(_)\n                | ast::Expr::Qualified(..)\n                | ast::Expr::Variable(_)\n                | ast::Expr::Register(_) => {\n                    // No nested expressions\n                }\n            }\n        }\n        WalkControl::SkipChildren => return Ok(WalkControl::Continue),\n    };\n    Ok(WalkControl::Continue)\n}\n\nfn walk_expr_mut_frame_bound<F>(bound: &mut ast::FrameBound, func: &mut F) -> Result<WalkControl>\nwhere\n    F: FnMut(&mut ast::Expr) -> Result<WalkControl>,\n{\n    match bound {\n        ast::FrameBound::Following(expr) | ast::FrameBound::Preceding(expr) => {\n            walk_expr_mut(expr, func)?;\n        }\n        ast::FrameBound::CurrentRow\n        | ast::FrameBound::UnboundedFollowing\n        | ast::FrameBound::UnboundedPreceding => {}\n    }\n\n    Ok(WalkControl::Continue)\n}\n\n#[derive(Debug, Clone, Copy)]\npub(crate) struct ExprAffinityInfo {\n    affinity: Affinity,\n    has_affinity: bool,\n}\n\nimpl ExprAffinityInfo {\n    const fn with_affinity(affinity: Affinity) -> Self {\n        Self {\n            affinity,\n            has_affinity: true,\n        }\n    }\n\n    const fn no_affinity() -> Self {\n        Self {\n            affinity: Affinity::Blob,\n            has_affinity: false,\n        }\n    }\n}\n\npub(crate) fn get_expr_affinity_info(\n    expr: &ast::Expr,\n    referenced_tables: Option<&TableReferences>,\n    resolver: Option<&Resolver>,\n) -> ExprAffinityInfo {\n    match expr {\n        ast::Expr::Column { table, column, .. } => {\n            if let Some(tables) = referenced_tables {\n                if let Some((_, table_ref)) = tables.find_table_by_internal_id(*table) {\n                    if let Some(col) = table_ref.get_column_at(*column) {\n                        if let Some(btree) = table_ref.btree() {\n                            return ExprAffinityInfo::with_affinity(\n                                col.affinity_with_strict(btree.is_strict),\n                            );\n                        }\n                        return ExprAffinityInfo::with_affinity(col.affinity());\n                    }\n                }\n            }\n            ExprAffinityInfo::no_affinity()\n        }\n        ast::Expr::RowId { .. } => ExprAffinityInfo::with_affinity(Affinity::Integer),\n        ast::Expr::Cast { type_name, .. } => {\n            if let Some(type_name) = type_name {\n                ExprAffinityInfo::with_affinity(Affinity::affinity(&type_name.name))\n            } else {\n                ExprAffinityInfo::no_affinity()\n            }\n        }\n        ast::Expr::Parenthesized(exprs) if exprs.len() == 1 => {\n            get_expr_affinity_info(exprs.first().unwrap(), referenced_tables, resolver)\n        }\n        ast::Expr::Collate(expr, _) => get_expr_affinity_info(expr, referenced_tables, resolver),\n        // Literals have NO affinity in SQLite.\n        ast::Expr::Literal(_) => ExprAffinityInfo::no_affinity(),\n        ast::Expr::Register(reg) => {\n            // During UPDATE expression index evaluation, column references are\n            // rewritten to Expr::Register. Look up the original column affinity\n            // from the resolver's register_affinities map.\n            if let Some(resolver) = resolver {\n                if let Some(aff) = resolver.register_affinities.get(reg) {\n                    return ExprAffinityInfo::with_affinity(*aff);\n                }\n            }\n            ExprAffinityInfo::no_affinity()\n        }\n        _ => ExprAffinityInfo::no_affinity(),\n    }\n}\n\npub fn get_expr_affinity(\n    expr: &ast::Expr,\n    referenced_tables: Option<&TableReferences>,\n    resolver: Option<&Resolver>,\n) -> Affinity {\n    get_expr_affinity_info(expr, referenced_tables, resolver).affinity\n}\n\npub fn comparison_affinity(\n    lhs_expr: &ast::Expr,\n    rhs_expr: &ast::Expr,\n    referenced_tables: Option<&TableReferences>,\n    resolver: Option<&Resolver>,\n) -> Affinity {\n    compare_affinity(\n        rhs_expr,\n        get_expr_affinity_info(lhs_expr, referenced_tables, resolver),\n        referenced_tables,\n        resolver,\n    )\n}\n\nfn comparison_affinity_from_info(lhs: ExprAffinityInfo, rhs: ExprAffinityInfo) -> Affinity {\n    if lhs.has_affinity && rhs.has_affinity {\n        // Both sides have affinity - use numeric if either is numeric\n        if lhs.affinity.is_numeric() || rhs.affinity.is_numeric() {\n            Affinity::Numeric\n        } else {\n            Affinity::Blob\n        }\n    } else if lhs.has_affinity {\n        lhs.affinity\n    } else if rhs.has_affinity {\n        rhs.affinity\n    } else {\n        Affinity::Blob\n    }\n}\n\npub(crate) fn compare_affinity(\n    expr: &ast::Expr,\n    other: ExprAffinityInfo,\n    referenced_tables: Option<&TableReferences>,\n    resolver: Option<&Resolver>,\n) -> Affinity {\n    comparison_affinity_from_info(\n        other,\n        get_expr_affinity_info(expr, referenced_tables, resolver),\n    )\n}\n\n/// Emit literal values - shared between regular and RETURNING expression evaluation\npub fn emit_literal(\n    program: &mut ProgramBuilder,\n    literal: &ast::Literal,\n    target_register: usize,\n) -> Result<usize> {\n    match literal {\n        ast::Literal::Numeric(val) => {\n            match parse_numeric_literal(val)? {\n                Value::Numeric(Numeric::Integer(int_value)) => {\n                    program.emit_insn(Insn::Integer {\n                        value: int_value,\n                        dest: target_register,\n                    });\n                }\n                Value::Numeric(Numeric::Float(real_value)) => {\n                    program.emit_insn(Insn::Real {\n                        value: real_value.into(),\n                        dest: target_register,\n                    });\n                }\n                _ => unreachable!(),\n            }\n            Ok(target_register)\n        }\n        ast::Literal::String(s) => {\n            program.emit_insn(Insn::String8 {\n                value: sanitize_string(s),\n                dest: target_register,\n            });\n            Ok(target_register)\n        }\n        ast::Literal::Blob(s) => {\n            let bytes = s\n                .as_bytes()\n                .chunks_exact(2)\n                .map(|pair| {\n                    // We assume that sqlite3-parser has already validated that\n                    // the input is valid hex string, thus unwrap is safe.\n                    let hex_byte = std::str::from_utf8(pair).unwrap();\n                    u8::from_str_radix(hex_byte, 16).unwrap()\n                })\n                .collect();\n            program.emit_insn(Insn::Blob {\n                value: bytes,\n                dest: target_register,\n            });\n            Ok(target_register)\n        }\n        ast::Literal::Keyword(_) => {\n            crate::bail_parse_error!(\"Keyword in WHERE clause is not supported\")\n        }\n        ast::Literal::Null => {\n            program.emit_insn(Insn::Null {\n                dest: target_register,\n                dest_end: None,\n            });\n            Ok(target_register)\n        }\n        ast::Literal::True => {\n            program.emit_insn(Insn::Integer {\n                value: 1,\n                dest: target_register,\n            });\n            Ok(target_register)\n        }\n        ast::Literal::False => {\n            program.emit_insn(Insn::Integer {\n                value: 0,\n                dest: target_register,\n            });\n            Ok(target_register)\n        }\n        ast::Literal::CurrentDate => {\n            program.emit_insn(Insn::String8 {\n                value: datetime::exec_date::<&[_; 0], std::slice::Iter<'_, Value>, &Value>(&[])\n                    .to_string(),\n                dest: target_register,\n            });\n            Ok(target_register)\n        }\n        ast::Literal::CurrentTime => {\n            program.emit_insn(Insn::String8 {\n                value: datetime::exec_time::<&[_; 0], std::slice::Iter<'_, Value>, &Value>(&[])\n                    .to_string(),\n                dest: target_register,\n            });\n            Ok(target_register)\n        }\n        ast::Literal::CurrentTimestamp => {\n            program.emit_insn(\n                Insn::String8 {\n                    value: datetime::exec_datetime_full::<\n                        &[_; 0],\n                        std::slice::Iter<'_, Value>,\n                        &Value,\n                    >(&[])\n                    .to_string(),\n                    dest: target_register,\n                },\n            );\n            Ok(target_register)\n        }\n    }\n}\n\n/// Emit a function call instruction with pre-allocated argument registers\n/// This is shared between different function call contexts\npub fn emit_function_call(\n    program: &mut ProgramBuilder,\n    func_ctx: FuncCtx,\n    arg_registers: &[usize],\n    target_register: usize,\n) -> Result<()> {\n    let start_reg = if arg_registers.is_empty() {\n        target_register // If no arguments, use target register as start\n    } else {\n        arg_registers[0] // Use first argument register as start\n    };\n\n    program.emit_insn(Insn::Function {\n        constant_mask: 0,\n        start_reg,\n        dest: target_register,\n        func: func_ctx,\n    });\n\n    Ok(())\n}\n\n/// Process a RETURNING clause, converting ResultColumn expressions into ResultSetColumn structures\n/// with proper column binding and alias handling.\npub fn process_returning_clause(\n    returning: &mut [ast::ResultColumn],\n    table_references: &mut TableReferences,\n    resolver: &Resolver<'_>,\n) -> Result<Vec<ResultSetColumn>> {\n    let mut result_columns = Vec::with_capacity(returning.len());\n\n    let alias_to_string = |alias: &ast::As| match alias {\n        ast::As::Elided(alias) => alias.as_str().to_string(),\n        ast::As::As(alias) => alias.as_str().to_string(),\n    };\n\n    for rc in returning.iter_mut() {\n        match rc {\n            ast::ResultColumn::Expr(expr, alias) => {\n                bind_and_rewrite_expr(\n                    expr,\n                    Some(table_references),\n                    None,\n                    resolver,\n                    BindingBehavior::TryResultColumnsFirst,\n                )?;\n\n                let vec_size = expr_vector_size(expr)?;\n                if vec_size != 1 {\n                    crate::bail_parse_error!(\n                        \"sub-select returns {} columns - expected 1\",\n                        vec_size\n                    );\n                }\n\n                result_columns.push(ResultSetColumn {\n                    expr: expr.as_ref().clone(),\n                    alias: alias.as_ref().map(alias_to_string),\n                    contains_aggregates: false,\n                });\n            }\n            ast::ResultColumn::Star => {\n                let table = table_references\n                    .joined_tables()\n                    .first()\n                    .expect(\"RETURNING clause must reference at least one table\");\n                let internal_id = table.internal_id;\n\n                // Handle RETURNING * by expanding to all table columns\n                // Use the shared internal_id for all columns\n                for (column_index, column) in table.columns().iter().enumerate() {\n                    let column_expr = Expr::Column {\n                        database: None,\n                        table: internal_id,\n                        column: column_index,\n                        is_rowid_alias: column.is_rowid_alias(),\n                    };\n\n                    result_columns.push(ResultSetColumn {\n                        expr: column_expr,\n                        alias: column.name.clone(),\n                        contains_aggregates: false,\n                    });\n                }\n            }\n            ast::ResultColumn::TableStar(_) => {\n                crate::bail_parse_error!(\"RETURNING may not use \\\"TABLE.*\\\" wildcards\");\n            }\n        }\n    }\n\n    Ok(result_columns)\n}\n\n/// Context for buffering RETURNING results into an ephemeral table\n/// instead of yielding them immediately via ResultRow.\n/// When used, the DML loop buffers each result row into the ephemeral table,\n/// and a scan-back loop after the DML loop yields them to the caller.\npub struct ReturningBufferCtx {\n    /// Cursor ID of the ephemeral table to buffer results into\n    pub cursor_id: usize,\n    /// Number of RETURNING columns (used for scan-back)\n    pub num_columns: usize,\n}\n\n/// Emit the scan-back loop that reads all buffered RETURNING rows from the\n/// ephemeral table and yields them via ResultRow. Called after all DML is complete.\npub(crate) fn emit_returning_scan_back(program: &mut ProgramBuilder, buf: &ReturningBufferCtx) {\n    let end_label = program.allocate_label();\n    let scan_start = program.allocate_label();\n\n    program.emit_insn(Insn::Rewind {\n        cursor_id: buf.cursor_id,\n        pc_if_empty: end_label,\n    });\n    program.preassign_label_to_next_insn(scan_start);\n\n    let result_start_reg = program.alloc_registers(buf.num_columns);\n    for i in 0..buf.num_columns {\n        program.emit_insn(Insn::Column {\n            cursor_id: buf.cursor_id,\n            column: i,\n            dest: result_start_reg + i,\n            default: None,\n        });\n    }\n    program.emit_insn(Insn::ResultRow {\n        start_reg: result_start_reg,\n        count: buf.num_columns,\n    });\n    program.emit_insn(Insn::Next {\n        cursor_id: buf.cursor_id,\n        pc_if_next: scan_start,\n    });\n    program.preassign_label_to_next_insn(end_label);\n}\n\n/// Emit bytecode to evaluate RETURNING expressions and produce result rows.\n/// RETURNING result expressions are otherwise evaluated as normal, but the columns of the target table\n/// are added to [Resolver::expr_to_reg_cache], meaning a reference to e.g tbl.col will effectively\n/// refer to a register where the OLD/NEW value of tbl.col is stored after an INSERT/UPDATE/DELETE.\n///\n/// When `returning_buffer` is `Some`, the results are buffered into an ephemeral table\n/// instead of being yielded immediately. A subsequent call to `emit_returning_scan_back`\n/// will drain the buffer and yield the rows to the caller.\npub(crate) fn emit_returning_results<'a>(\n    program: &mut ProgramBuilder,\n    table_references: &TableReferences,\n    result_columns: &[super::plan::ResultSetColumn],\n    reg_columns_start: usize,\n    rowid_reg: usize,\n    resolver: &mut Resolver<'a>,\n    returning_buffer: Option<&ReturningBufferCtx>,\n) -> Result<()> {\n    if result_columns.is_empty() {\n        return Ok(());\n    }\n\n    let cache_state = seed_returning_row_image_in_cache(\n        program,\n        table_references,\n        reg_columns_start,\n        rowid_reg,\n        resolver,\n    )?;\n\n    let result = (|| {\n        let result_start_reg = program.alloc_registers(result_columns.len());\n\n        for (i, result_column) in result_columns.iter().enumerate() {\n            let reg = result_start_reg + i;\n            translate_expr_no_constant_opt(\n                program,\n                Some(table_references),\n                &result_column.expr,\n                reg,\n                resolver,\n                NoConstantOptReason::RegisterReuse,\n            )?;\n        }\n\n        // Decode array columns in RETURNING results (record blob -> JSON text).\n        super::result_row::emit_array_decode_for_results(\n            program,\n            result_columns,\n            table_references,\n            result_start_reg,\n            resolver,\n        )?;\n\n        if let Some(buf) = returning_buffer {\n            // Buffer into ephemeral table instead of yielding directly.\n            // All DML completes before any RETURNING rows are yielded to the caller.\n            let record_reg = program.alloc_register();\n            let eph_rowid_reg = program.alloc_register();\n            program.emit_insn(Insn::MakeRecord {\n                start_reg: crate::vdbe::insn::to_u16(result_start_reg),\n                count: crate::vdbe::insn::to_u16(result_columns.len()),\n                dest_reg: crate::vdbe::insn::to_u16(record_reg),\n                index_name: None,\n                affinity_str: None,\n            });\n            program.emit_insn(Insn::NewRowid {\n                cursor: buf.cursor_id,\n                rowid_reg: eph_rowid_reg,\n                prev_largest_reg: 0,\n            });\n            program.emit_insn(Insn::Insert {\n                cursor: buf.cursor_id,\n                key_reg: eph_rowid_reg,\n                record_reg,\n                flag: InsertFlags::new().is_ephemeral_table_insert(),\n                table_name: String::new(),\n            });\n        } else {\n            program.emit_insn(Insn::ResultRow {\n                start_reg: result_start_reg,\n                count: result_columns.len(),\n            });\n        }\n\n        Ok(())\n    })();\n\n    restore_returning_row_image_in_cache(resolver, cache_state);\n    result\n}\n\npub(crate) struct ReturningRowImageCacheState {\n    cache_len: usize,\n    cache_enabled: bool,\n}\n\npub(crate) fn seed_returning_row_image_in_cache<'a>(\n    program: &mut ProgramBuilder,\n    table_references: &TableReferences,\n    reg_columns_start: usize,\n    rowid_reg: usize,\n    resolver: &mut Resolver<'a>,\n) -> Result<ReturningRowImageCacheState> {\n    turso_assert!(\n        table_references.joined_tables().len() == 1,\n        \"RETURNING is only used with INSERT, UPDATE, or DELETE statements, which target a single table\"\n    );\n    let table = table_references.joined_tables().first().unwrap();\n\n    let cache_len = resolver.expr_to_reg_cache.len();\n    let cache_enabled = resolver.expr_to_reg_cache_enabled;\n    resolver.enable_expr_to_reg_cache();\n    resolver.cache_expr_reg(\n        std::borrow::Cow::Owned(Expr::RowId {\n            database: None,\n            table: table.internal_id,\n        }),\n        rowid_reg,\n        false,\n        None,\n    );\n    for (i, column) in table.columns().iter().enumerate() {\n        let raw_reg = if column.is_rowid_alias() {\n            rowid_reg\n        } else {\n            reg_columns_start + i\n        };\n        // The write registers hold stored (encoded) values. Produce the\n        // user-facing value in a fresh register so RETURNING shows decoded\n        // results — this is a no-op for regular columns.\n        let decoded_reg = program.alloc_register();\n        emit_user_facing_column_value(\n            program,\n            raw_reg,\n            decoded_reg,\n            column,\n            table.table.is_strict(),\n            resolver,\n        )?;\n        let expr = Expr::Column {\n            database: None,\n            table: table.internal_id,\n            column: i,\n            is_rowid_alias: column.is_rowid_alias(),\n        };\n        resolver.cache_scalar_expr_reg(\n            std::borrow::Cow::Owned(expr),\n            decoded_reg,\n            false,\n            table_references,\n        )?;\n    }\n\n    Ok(ReturningRowImageCacheState {\n        cache_len,\n        cache_enabled,\n    })\n}\n\npub(crate) fn restore_returning_row_image_in_cache(\n    resolver: &mut Resolver<'_>,\n    state: ReturningRowImageCacheState,\n) {\n    resolver.expr_to_reg_cache.truncate(state.cache_len);\n    resolver.expr_to_reg_cache_enabled = state.cache_enabled;\n}\n\n/// Get the number of values returned by an expression\npub fn expr_vector_size(expr: &Expr) -> Result<usize> {\n    Ok(match unwrap_parens(expr)? {\n        Expr::Between {\n            lhs, start, end, ..\n        } => {\n            let evs_left = expr_vector_size(lhs)?;\n            let evs_start = expr_vector_size(start)?;\n            let evs_end = expr_vector_size(end)?;\n            if evs_left != evs_start || evs_left != evs_end {\n                crate::bail_parse_error!(\n                    \"all arguments to BETWEEN must return the same number of values. Got: ({evs_left}) BETWEEN ({evs_start}) AND ({evs_end})\"\n                );\n            }\n            1\n        }\n        Expr::Binary(expr, operator, expr1) => {\n            let evs_left = expr_vector_size(expr)?;\n            let evs_right = expr_vector_size(expr1)?;\n            if evs_left != evs_right {\n                crate::bail_parse_error!(\n                    \"all arguments to binary operator {operator} must return the same number of values. Got: ({evs_left}) {operator} ({evs_right})\"\n                );\n            }\n            if evs_left > 1 && !supports_row_value_binary_comparison(operator) {\n                crate::bail_parse_error!(\"row value misused\");\n            }\n            1\n        }\n        Expr::Register(_) => 1,\n        Expr::Case {\n            base,\n            when_then_pairs,\n            else_expr,\n        } => {\n            if let Some(base) = base {\n                let evs_base = expr_vector_size(base)?;\n                if evs_base != 1 {\n                    crate::bail_parse_error!(\n                        \"base expression in CASE must return 1 value. Got: ({evs_base})\"\n                    );\n                }\n            }\n            for (when, then) in when_then_pairs {\n                let evs_when = expr_vector_size(when)?;\n                if evs_when != 1 {\n                    crate::bail_parse_error!(\n                        \"when expression in CASE must return 1 value. Got: ({evs_when})\"\n                    );\n                }\n                let evs_then = expr_vector_size(then)?;\n                if evs_then != 1 {\n                    crate::bail_parse_error!(\n                        \"then expression in CASE must return 1 value. Got: ({evs_then})\"\n                    );\n                }\n            }\n            if let Some(else_expr) = else_expr {\n                let evs_else_expr = expr_vector_size(else_expr)?;\n                if evs_else_expr != 1 {\n                    crate::bail_parse_error!(\n                        \"else expression in CASE must return 1 value. Got: ({evs_else_expr})\"\n                    );\n                }\n            }\n            1\n        }\n        Expr::Cast { expr, .. } => {\n            let evs_expr = expr_vector_size(expr)?;\n            if evs_expr != 1 {\n                crate::bail_parse_error!(\"argument to CAST must return 1 value. Got: ({evs_expr})\");\n            }\n            1\n        }\n        Expr::Collate(expr, _) => {\n            let evs_expr = expr_vector_size(expr)?;\n            if evs_expr != 1 {\n                crate::bail_parse_error!(\n                    \"argument to COLLATE must return 1 value. Got: ({evs_expr})\"\n                );\n            }\n            1\n        }\n        Expr::DoublyQualified(..) => 1,\n        Expr::Exists(_) => 1, // EXISTS returns a single boolean value (0 or 1)\n        Expr::FunctionCall { name, args, .. } => {\n            for (pos, arg) in args.iter().enumerate() {\n                let evs_arg = expr_vector_size(arg)?;\n                if evs_arg != 1 {\n                    crate::bail_parse_error!(\n                        \"argument {} to function call {name} must return 1 value. Got: ({evs_arg})\",\n                        pos + 1\n                    );\n                }\n            }\n            1\n        }\n        Expr::FunctionCallStar { .. } => 1,\n        Expr::Id(_) => 1,\n        Expr::Column { .. } => 1,\n        Expr::RowId { .. } => 1,\n        Expr::InList { lhs, rhs, .. } => {\n            let evs_lhs = expr_vector_size(lhs)?;\n            for rhs in rhs.iter() {\n                let evs_rhs = expr_vector_size(rhs)?;\n                if evs_lhs != evs_rhs {\n                    crate::bail_parse_error!(\n                        \"all arguments to IN list must return the same number of values, got: ({evs_lhs}) IN ({evs_rhs})\"\n                    );\n                }\n            }\n            1\n        }\n        Expr::InSelect { .. } => {\n            crate::bail_parse_error!(\"InSelect is not supported in this position\")\n        }\n        Expr::InTable { .. } => {\n            crate::bail_parse_error!(\"InTable is not supported in this position\")\n        }\n        Expr::IsNull(expr) => {\n            let evs_expr = expr_vector_size(expr)?;\n            if evs_expr != 1 {\n                crate::bail_parse_error!(\n                    \"argument to IS NULL must return 1 value. Got: ({evs_expr})\"\n                );\n            }\n            1\n        }\n        Expr::Like { lhs, rhs, op, .. } => {\n            let evs_lhs = expr_vector_size(lhs)?;\n            // MATCH allows multi-column LHS: (col1, col2) MATCH 'query'\n            if evs_lhs != 1 && *op != ast::LikeOperator::Match {\n                crate::bail_parse_error!(\n                    \"left operand of LIKE must return 1 value. Got: ({evs_lhs})\"\n                );\n            }\n            let evs_rhs = expr_vector_size(rhs)?;\n            if evs_rhs != 1 {\n                crate::bail_parse_error!(\n                    \"right operand of LIKE must return 1 value. Got: ({evs_rhs})\"\n                );\n            }\n            1\n        }\n        Expr::Literal(_) => 1,\n        Expr::Name(_) => 1,\n        Expr::NotNull(expr) => {\n            let evs_expr = expr_vector_size(expr)?;\n            if evs_expr != 1 {\n                crate::bail_parse_error!(\n                    \"argument to NOT NULL must return 1 value. Got: ({evs_expr})\"\n                );\n            }\n            1\n        }\n        Expr::Parenthesized(exprs) => exprs.len(),\n        Expr::Qualified(..) => 1,\n        Expr::Raise(..) => 1,\n        Expr::Subquery(_) => {\n            crate::bail_parse_error!(\"Scalar subquery is not supported in this context\")\n        }\n        Expr::Unary(unary_operator, expr) => {\n            let evs_expr = expr_vector_size(expr)?;\n            if evs_expr != 1 {\n                crate::bail_parse_error!(\n                    \"argument to unary operator {unary_operator} must return 1 value. Got: ({evs_expr})\"\n                );\n            }\n            1\n        }\n        Expr::Variable(_) => 1,\n        Expr::SubqueryResult { query_type, .. } => match query_type {\n            SubqueryType::Exists { .. } => 1,\n            SubqueryType::In { .. } => 1,\n            SubqueryType::RowValue { num_regs, .. } => *num_regs,\n        },\n        Expr::Array { .. } | Expr::Subscript { .. } => {\n            unreachable!(\"Array and Subscript are desugared into function calls by the parser\")\n        }\n    })\n}\n\n/// Map an AST operator to the string representation used in custom type operator definitions.\nfn operator_to_str(op: &ast::Operator) -> Option<&'static str> {\n    match op {\n        ast::Operator::Add => Some(\"+\"),\n        ast::Operator::Subtract => Some(\"-\"),\n        ast::Operator::Multiply => Some(\"*\"),\n        ast::Operator::Divide => Some(\"/\"),\n        ast::Operator::Modulus => Some(\"%\"),\n        ast::Operator::Less => Some(\"<\"),\n        ast::Operator::LessEquals => Some(\"<=\"),\n        ast::Operator::Greater => Some(\">\"),\n        ast::Operator::GreaterEquals => Some(\">=\"),\n        ast::Operator::Equals => Some(\"=\"),\n        ast::Operator::NotEquals => Some(\"!=\"),\n        _ => None,\n    }\n}\n\n/// Emit bytecode for a resolved custom type operator call.\n/// Handles argument swapping, literal encoding, and result negation.\nfn emit_custom_type_operator(\n    program: &mut ProgramBuilder,\n    referenced_tables: Option<&TableReferences>,\n    e1: &ast::Expr,\n    e2: &ast::Expr,\n    resolved: &ResolvedOperator,\n    resolver: &Resolver,\n) -> Result<usize> {\n    let func = resolver\n        .resolve_function(&resolved.func_name, 2)\n        .ok_or_else(|| {\n            LimboError::InternalError(format!(\"function not found: {}\", resolved.func_name))\n        })?;\n    let (first, second) = if resolved.swap_args {\n        (e2, e1)\n    } else {\n        (e1, e2)\n    };\n\n    // When encoding a literal operand, we must use separate registers for the\n    // function call arguments. translate_expr may place literals in preamble\n    // registers (constant optimization), and encoding in-place would clobber\n    // that register — breaking subsequent loop iterations.\n    let func_start = if let Some(ref encode_info) = resolved.encode_info {\n        if let Some(ref encode_expr) = encode_info.type_def.encode {\n            // Translate operands into temporary registers first.\n            let tmp1 = program.alloc_register();\n            let tmp2 = program.alloc_register();\n            translate_expr(program, referenced_tables, first, tmp1, resolver)?;\n            translate_expr(program, referenced_tables, second, tmp2, resolver)?;\n\n            // Determine which tmp holds the literal and which holds the column.\n            let (lit_tmp, col_tmp) = match encode_info.which {\n                EncodeArg::First if resolved.swap_args => (tmp2, tmp1),\n                EncodeArg::First => (tmp1, tmp2),\n                EncodeArg::Second if resolved.swap_args => (tmp1, tmp2),\n                EncodeArg::Second => (tmp2, tmp1),\n            };\n\n            // Allocate fresh contiguous registers for the function call.\n            let func_args = program.alloc_registers(2);\n            // The literal goes in the same position it occupied in arg layout.\n            let (lit_dst, col_dst) = match encode_info.which {\n                EncodeArg::First if resolved.swap_args => (func_args + 1, func_args),\n                EncodeArg::First => (func_args, func_args + 1),\n                EncodeArg::Second if resolved.swap_args => (func_args, func_args + 1),\n                EncodeArg::Second => (func_args + 1, func_args),\n            };\n\n            // Copy column value as-is.\n            program.emit_insn(Insn::Copy {\n                src_reg: col_tmp,\n                dst_reg: col_dst,\n                extra_amount: 0,\n            });\n            // Encode the literal into the fresh function arg slot.\n            emit_type_expr(\n                program,\n                encode_expr,\n                lit_tmp,\n                lit_dst,\n                &encode_info.column,\n                &encode_info.type_def,\n                resolver,\n            )?;\n            func_args\n        } else {\n            // Type has no encode expression; translate directly into arg slots.\n            let arg_reg = program.alloc_registers(2);\n            translate_expr(program, referenced_tables, first, arg_reg, resolver)?;\n            translate_expr(program, referenced_tables, second, arg_reg + 1, resolver)?;\n            arg_reg\n        }\n    } else {\n        // No encoding needed; translate directly into arg slots.\n        let arg_reg = program.alloc_registers(2);\n        translate_expr(program, referenced_tables, first, arg_reg, resolver)?;\n        translate_expr(program, referenced_tables, second, arg_reg + 1, resolver)?;\n        arg_reg\n    };\n\n    let result_reg = program.alloc_register();\n    program.emit_insn(Insn::Function {\n        constant_mask: 0,\n        start_reg: func_start,\n        dest: result_reg,\n        func: FuncCtx { func, arg_count: 2 },\n    });\n    if resolved.negate {\n        program.emit_insn(Insn::Not {\n            reg: result_reg,\n            dest: result_reg,\n        });\n    }\n    Ok(result_reg)\n}\n\n/// Info about a column with a custom type, extracted from an expression.\nstruct ExprCustomTypeInfo {\n    type_name: String,\n    column: Column,\n    type_def: Arc<TypeDef>,\n}\n\n/// If the expression is a column reference to a custom type, return the type info.\nfn expr_custom_type_info(\n    expr: &ast::Expr,\n    referenced_tables: Option<&TableReferences>,\n    resolver: &Resolver,\n) -> Option<ExprCustomTypeInfo> {\n    if let ast::Expr::Column {\n        table: table_ref_id,\n        column,\n        ..\n    } = expr\n    {\n        let tables = referenced_tables?;\n        let (_, table) = tables.find_table_by_internal_id(*table_ref_id)?;\n        let col = table.get_column_at(*column)?;\n        let type_name = &col.ty_str;\n        let type_def = resolver\n            .schema()\n            .get_type_def(type_name, table.is_strict())?;\n        return Some(ExprCustomTypeInfo {\n            type_name: type_name.to_lowercase(),\n            column: col.clone(),\n            type_def: Arc::clone(type_def),\n        });\n    }\n    None\n}\n\n/// Get the effective type name of a literal expression.\nfn literal_type_name(expr: &ast::Expr) -> Option<&'static str> {\n    match expr {\n        ast::Expr::Literal(lit) => match lit {\n            ast::Literal::Numeric(s) => {\n                if s.contains('.') || s.contains('e') || s.contains('E') {\n                    Some(\"real\")\n                } else {\n                    Some(\"integer\")\n                }\n            }\n            ast::Literal::String(_) => Some(\"text\"),\n            ast::Literal::Blob(_) => Some(\"blob\"),\n            ast::Literal::True | ast::Literal::False => Some(\"integer\"),\n            _ => None,\n        },\n        _ => None,\n    }\n}\n\n/// Check if a literal type is compatible with a custom type's value input type.\n/// \"any\" matches everything; otherwise exact match (case-insensitive).\nfn literal_compatible_with_value_type(literal_type: &str, value_input_type: &str) -> bool {\n    value_input_type.eq_ignore_ascii_case(\"any\")\n        || literal_type.eq_ignore_ascii_case(value_input_type)\n}\n\n/// Which operand of a binary expression needs encoding before the operator call.\nenum EncodeArg {\n    /// Encode the first argument (e1 is a literal, e2 is the custom type column)\n    First,\n    /// Encode the second argument (e1 is the custom type column, e2 is a literal)\n    Second,\n}\n\n/// Info needed to encode a literal argument for an operator call.\nstruct OperatorEncodeInfo {\n    column: Column,\n    type_def: Arc<TypeDef>,\n    which: EncodeArg,\n}\n\n/// Result of resolving a custom type operator. May be a direct match or derived\n/// from `<` and `=` operators (e.g. `>` is derived as swap_args + `<`).\nstruct ResolvedOperator {\n    func_name: String,\n    swap_args: bool,\n    negate: bool,\n    /// If a literal operand needs encoding before the operator call.\n    encode_info: Option<OperatorEncodeInfo>,\n}\n\n/// Find a custom type operator function for a binary expression.\n///\n/// Operators fire when:\n/// 1. Both operands are columns of the same custom type, OR\n/// 2. One operand is a custom type column and the other is a literal whose type\n///    is compatible with the custom type's `value` input type.\n///\n/// When case 2 applies, the literal is encoded before being passed to the operator\n/// function so both arguments are in the same (encoded) representation.\nfn find_custom_type_operator(\n    e1: &ast::Expr,\n    e2: &ast::Expr,\n    op: &ast::Operator,\n    referenced_tables: Option<&TableReferences>,\n    resolver: &Resolver,\n) -> Option<ResolvedOperator> {\n    let op_str = operator_to_str(op)?;\n    let lhs_info = expr_custom_type_info(e1, referenced_tables, resolver);\n    let rhs_info = expr_custom_type_info(e2, referenced_tables, resolver);\n\n    // Try to find a direct or derived operator match on a type definition.\n    let find_in_type_def = |type_def: &TypeDef| -> Option<(String, bool, bool)> {\n        // Direct match: just check op symbol (no right_type constraint)\n        for op_def in &type_def.operators {\n            if op_def.op == op_str {\n                // Naked operator (func_name = None): fall through to standard comparison\n                let func_name = op_def.func_name.as_ref()?;\n                return Some((func_name.clone(), false, false));\n            }\n        }\n\n        // Derive missing operators from < and =\n        let find_op = |sym: &str| -> Option<String> {\n            type_def\n                .operators\n                .iter()\n                .find(|o| o.op == sym)\n                .and_then(|o| o.func_name.clone())\n        };\n\n        match *op {\n            // a > b  →  lt(b, a)\n            ast::Operator::Greater => find_op(\"<\").map(|f| (f, true, false)),\n            // a >= b  →  NOT lt(a, b)\n            ast::Operator::GreaterEquals => find_op(\"<\").map(|f| (f, false, true)),\n            // a <= b  →  NOT lt(b, a)\n            ast::Operator::LessEquals => find_op(\"<\").map(|f| (f, true, true)),\n            // a != b  →  NOT eq(a, b)\n            ast::Operator::NotEquals => find_op(\"=\").map(|f| (f, false, true)),\n            _ => None,\n        }\n    };\n\n    // Case 1: Both operands are custom type columns of the SAME type.\n    if let (Some(ref lhs), Some(ref rhs)) = (&lhs_info, &rhs_info) {\n        if lhs.type_name == rhs.type_name {\n            if let Some((func_name, swap_args, negate)) = find_in_type_def(&lhs.type_def) {\n                return Some(ResolvedOperator {\n                    func_name,\n                    swap_args,\n                    negate,\n                    encode_info: None,\n                });\n            }\n        }\n        // Different custom types: fall through to standard operator.\n        return None;\n    }\n\n    // Case 2: LHS is custom type, RHS is a compatible literal.\n    if let Some(ref lhs) = lhs_info {\n        if let Some(lit_type) = literal_type_name(e2) {\n            if literal_compatible_with_value_type(lit_type, lhs.type_def.value_input_type()) {\n                if let Some((func_name, swap_args, negate)) = find_in_type_def(&lhs.type_def) {\n                    return Some(ResolvedOperator {\n                        func_name,\n                        swap_args,\n                        negate,\n                        encode_info: Some(OperatorEncodeInfo {\n                            column: lhs.column.clone(),\n                            type_def: lhs.type_def.clone(),\n                            which: EncodeArg::Second,\n                        }),\n                    });\n                }\n            }\n        }\n    }\n\n    // Case 3: RHS is custom type, LHS is a compatible literal (reversed).\n    if let Some(ref rhs) = rhs_info {\n        if let Some(lit_type) = literal_type_name(e1) {\n            if literal_compatible_with_value_type(lit_type, rhs.type_def.value_input_type()) {\n                if let Some((func_name, swap_args, negate)) = find_in_type_def(&rhs.type_def) {\n                    return Some(ResolvedOperator {\n                        func_name,\n                        swap_args,\n                        negate,\n                        encode_info: Some(OperatorEncodeInfo {\n                            column: rhs.column.clone(),\n                            type_def: rhs.type_def.clone(),\n                            which: EncodeArg::First,\n                        }),\n                    });\n                }\n            }\n        }\n    }\n\n    None\n}\n\n/// Emit bytecode that transforms a stored column value into its user-facing\n/// representation.\n///\n/// For regular columns this is a simple copy (or no-op when source == dest).\n/// For custom type columns with a DECODE function the decode expression is\n/// applied, converting the internal storage form back to the value the user\n/// expects to see.\n///\n/// Every code path that surfaces a stored column value to the user — SELECT,\n/// RETURNING, trigger OLD/NEW — should go through this function so decode\n/// logic lives in one place.\npub(crate) fn emit_user_facing_column_value(\n    program: &mut ProgramBuilder,\n    source_reg: usize,\n    dest_reg: usize,\n    column: &Column,\n    is_strict: bool,\n    resolver: &Resolver,\n) -> Result<()> {\n    if source_reg != dest_reg {\n        program.emit_insn(Insn::Copy {\n            src_reg: source_reg,\n            dst_reg: dest_reg,\n            extra_amount: 0,\n        });\n    }\n    // Array columns: pass through raw record blob. ArrayDecode is emitted\n    // at display time (ResultRow) so that functions/subscripts see raw blobs.\n    if column.is_array() {\n        return Ok(());\n    }\n    if let Some(type_def) = resolver.schema().get_type_def(&column.ty_str, is_strict) {\n        if let Some(ref decode_expr) = type_def.decode {\n            let skip_label = program.allocate_label();\n            program.emit_insn(Insn::IsNull {\n                reg: dest_reg,\n                target_pc: skip_label,\n            });\n            emit_type_expr(\n                program,\n                decode_expr,\n                dest_reg,\n                dest_reg,\n                column,\n                type_def,\n                resolver,\n            )?;\n            program.preassign_label_to_next_insn(skip_label);\n        }\n    }\n    Ok(())\n}\n\n/// Walk an expression tree that has been rewritten to use `Expr::Register` for column\n/// references (e.g. by `rewrite_index_expr_for_insertion`). For each register that maps\n/// to a custom type column, emit decode bytecode into a fresh temporary register and\n/// rewrite the expression node to reference the decoded register.\n///\n/// This ensures expression indexes on custom type columns evaluate the expression on\n/// **decoded** (user-facing) values, matching what SELECT / CREATE INDEX see.\npub(crate) fn decode_custom_type_registers_in_expr(\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    expr: &mut ast::Expr,\n    columns: &[Column],\n    start_reg: usize,\n    key_reg: Option<usize>,\n    is_strict: bool,\n) -> Result<()> {\n    walk_expr_mut(expr, &mut |e| {\n        if let ast::Expr::Register(reg) = e {\n            let reg_val = *reg;\n            // Skip the rowid register — it's not a custom type column.\n            if key_reg == Some(reg_val) {\n                return Ok(WalkControl::Continue);\n            }\n            // Map register back to column index.\n            if reg_val >= start_reg {\n                let col_idx = reg_val - start_reg;\n                if let Some(column) = columns.get(col_idx) {\n                    if let Some(type_def) =\n                        resolver.schema().get_type_def(&column.ty_str, is_strict)\n                    {\n                        if type_def.decode.is_some() {\n                            let decoded_reg = program.alloc_register();\n                            emit_user_facing_column_value(\n                                program,\n                                reg_val,\n                                decoded_reg,\n                                column,\n                                is_strict,\n                                resolver,\n                            )?;\n                            *e = ast::Expr::Register(decoded_reg);\n                        }\n                    }\n                }\n            }\n        }\n        Ok(WalkControl::Continue)\n    })?;\n    Ok(())\n}\n\n/// Emit bytecode for a custom type encode/decode expression.\n/// Sets up `value` to reference `value_reg`, and type parameter overrides\n/// from `column.ty_params` matched against `type_def.params`.\n/// The expression result is written to `dest_reg`.\npub(crate) fn emit_type_expr(\n    program: &mut ProgramBuilder,\n    expr: &ast::Expr,\n    value_reg: usize,\n    dest_reg: usize,\n    column: &Column,\n    type_def: &TypeDef,\n    resolver: &Resolver,\n) -> Result<usize> {\n    // Set up value override\n    program\n        .id_register_overrides\n        .insert(\"value\".to_string(), value_reg);\n\n    // Set up type parameter overrides. Capture the result so we can\n    // clean up overrides even if param translation fails.\n    let param_result: Result<()> = (|| {\n        // Skip `value` param (already handled above); match remaining params\n        // against the user-provided ty_params by position.\n        let user_params: Vec<_> = type_def.user_params().collect();\n        for (i, param) in user_params.iter().enumerate() {\n            if let Some(param_expr) = column.ty_params.get(i) {\n                let reg = program.alloc_register();\n                translate_expr(program, None, param_expr, reg, resolver)?;\n                program\n                    .id_register_overrides\n                    .insert(param.name.clone(), reg);\n            }\n        }\n        Ok(())\n    })();\n\n    // Translate body expression only if param setup succeeded\n    let result = param_result.and_then(|()| {\n        // Translate the expression, disabling constant optimization since\n        // the `value` placeholder refers to a register that changes per row.\n        translate_expr_no_constant_opt(\n            program,\n            None,\n            expr,\n            dest_reg,\n            resolver,\n            NoConstantOptReason::RegisterReuse,\n        )\n    });\n\n    // Always clean up overrides, even on error\n    program.id_register_overrides.clear();\n\n    result\n}\n\n/// Decode custom type columns for AFTER trigger NEW registers.\n///\n/// For each column with a custom type decode expression, copies the encoded register\n/// to a new register and emits the decode expression. NULL values are skipped.\n/// Returns a Vec of registers: one per column (decoded or original) plus the rowid at the end.\npub(crate) fn emit_trigger_decode_registers(\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    columns: &[Column],\n    source_regs: &dyn Fn(usize) -> usize,\n    rowid_reg: usize,\n    is_strict: bool,\n) -> Result<Vec<usize>> {\n    columns\n        .iter()\n        .enumerate()\n        .map(|(i, col)| -> Result<usize> {\n            let type_def = resolver.schema().get_type_def(&col.ty_str, is_strict);\n            if let Some(type_def) = type_def {\n                if let Some(ref decode_expr) = type_def.decode {\n                    let src = source_regs(i);\n                    let decoded_reg = program.alloc_register();\n                    program.emit_insn(Insn::Copy {\n                        src_reg: src,\n                        dst_reg: decoded_reg,\n                        extra_amount: 0,\n                    });\n                    let skip_label = program.allocate_label();\n                    program.emit_insn(Insn::IsNull {\n                        reg: decoded_reg,\n                        target_pc: skip_label,\n                    });\n                    emit_type_expr(\n                        program,\n                        decode_expr,\n                        decoded_reg,\n                        decoded_reg,\n                        col,\n                        type_def,\n                        resolver,\n                    )?;\n                    program.preassign_label_to_next_insn(skip_label);\n                    return Ok(decoded_reg);\n                }\n            }\n            Ok(source_regs(i))\n        })\n        .chain(std::iter::once(Ok(rowid_reg)))\n        .collect::<Result<Vec<usize>>>()\n}\n\n/// Maximum number of array elements supported in the per-element transform loop.\n/// Limited by the fixed register block allocated at compile time.\nconst MAX_ARRAY_LOOP_ELEMENTS: usize = 1024;\n\n/// Emit a per-element transform loop on an array blob in `reg`.\n/// Extracts each element, applies `transform_expr` via emit_type_expr,\n/// stores results into contiguous registers, then rebuilds the blob with\n/// MakeArrayDynamic. O(N) instead of O(N²) ArraySetElement per iteration.\nfn emit_array_element_loop(\n    program: &mut ProgramBuilder,\n    reg: usize,\n    transform_expr: &ast::Expr,\n    col: &Column,\n    type_def: &TypeDef,\n    resolver: &Resolver,\n) -> Result<()> {\n    let reg_len = program.alloc_register();\n    let reg_idx = program.alloc_register();\n    let reg_elem = program.alloc_register();\n    // Reserve a contiguous block for transformed elements.\n    // At runtime, we only use registers[elem_base..elem_base+len].\n    let elem_base = program.alloc_registers(MAX_ARRAY_LOOP_ELEMENTS);\n\n    program.emit_insn(Insn::ArrayLength { reg, dest: reg_len });\n\n    // Guard: halt if the array exceeds the register block size.\n    let max_reg = program.alloc_register();\n    program.emit_insn(Insn::Integer {\n        value: MAX_ARRAY_LOOP_ELEMENTS as i64,\n        dest: max_reg,\n    });\n    let ok_label = program.allocate_label();\n    program.emit_insn(Insn::Le {\n        lhs: reg_len,\n        rhs: max_reg,\n        target_pc: ok_label,\n        flags: CmpInsFlags::default(),\n        collation: None,\n    });\n    program.emit_insn(Insn::Halt {\n        err_code: SQLITE_CONSTRAINT,\n        description: format!(\n            \"array exceeds maximum element count for custom type transform ({MAX_ARRAY_LOOP_ELEMENTS})\"\n        ),\n        on_error: None,\n        description_reg: None,\n    });\n    program.preassign_label_to_next_insn(ok_label);\n    // reg_idx is the 1-based array index for ArrayElement (PG convention)\n    program.emit_insn(Insn::Integer {\n        value: 1,\n        dest: reg_idx,\n    });\n    // reg_offset is the 0-based offset for RegCopyOffset into the register block\n    let reg_offset = program.alloc_register();\n    program.emit_insn(Insn::Integer {\n        value: 0,\n        dest: reg_offset,\n    });\n\n    let loop_start = program.offset();\n    let loop_end_label = program.allocate_label();\n\n    program.emit_insn(Insn::Gt {\n        lhs: reg_idx,\n        rhs: reg_len,\n        target_pc: loop_end_label,\n        flags: CmpInsFlags::default(),\n        collation: None,\n    });\n\n    // Extract element from record blob (1-based index)\n    program.emit_insn(Insn::ArrayElement {\n        array_reg: reg,\n        index_reg: reg_idx,\n        dest: reg_elem,\n    });\n\n    // Apply per-element transform expression\n    emit_type_expr(\n        program,\n        transform_expr,\n        reg_elem,\n        reg_elem,\n        col,\n        type_def,\n        resolver,\n    )?;\n\n    // Store transformed element into contiguous register block at 0-based offset\n    program.emit_insn(Insn::RegCopyOffset {\n        src: reg_elem,\n        base: elem_base,\n        offset_reg: reg_offset,\n    });\n\n    program.emit_insn(Insn::AddImm {\n        register: reg_idx,\n        value: 1,\n    });\n    program.emit_insn(Insn::AddImm {\n        register: reg_offset,\n        value: 1,\n    });\n    program.emit_insn(Insn::Goto {\n        target_pc: loop_start,\n    });\n\n    program.preassign_label_to_next_insn(loop_end_label);\n\n    // Rebuild the array blob from the contiguous register block in one pass\n    program.emit_insn(Insn::MakeArrayDynamic {\n        start_reg: elem_base,\n        count_reg: reg_len,\n        dest: reg,\n    });\n\n    Ok(())\n}\n\n/// Emit bytecode to encode an array value: parse JSON text input, validate/coerce\n/// elements, and serialize to a native record-format BLOB.\n/// For custom element types with encode expressions, a per-element bytecode loop\n/// normalizes input to blob, applies encode per element, then rebuilds the blob.\nfn emit_array_encode(\n    program: &mut ProgramBuilder,\n    reg: usize,\n    col: &Column,\n    resolver: &Resolver,\n    table_name: &str,\n) -> Result<()> {\n    if let Some(type_def) = resolver.schema().get_type_def_unchecked(&col.ty_str) {\n        if let Some(encode_expr) = type_def.encode.as_ref() {\n            // Normalize input (text or blob) to blob with ANY affinity first\n            program.emit_insn(Insn::ArrayEncode {\n                reg,\n                element_affinity: Affinity::Blob,\n                element_type: \"ANY\".into(),\n                table_name: table_name.into(),\n                col_name: col.name.as_deref().unwrap_or(\"\").into(),\n            });\n\n            emit_array_element_loop(program, reg, encode_expr, col, type_def, resolver)?;\n        }\n    }\n\n    // ArrayEncode: parse JSON text → validate/coerce → serialize to record blob.\n    // For multi-dimensional arrays (e.g. INTEGER[][]), the outer array's elements\n    // are themselves arrays (blobs), so we use ANY/Blob for validation.\n    // Only 1-dimensional arrays validate elements against the declared base type.\n    let is_any = col.ty_str.eq_ignore_ascii_case(\"ANY\");\n    let is_multidim = col.array_dimensions() > 1;\n    let col_name = col.name.as_deref().unwrap_or(\"\");\n    let element_affinity = if is_any || is_multidim {\n        Affinity::Blob\n    } else {\n        Affinity::affinity(&col.ty_str)\n    };\n    let element_type = if is_any || is_multidim {\n        \"ANY\".into()\n    } else {\n        col.ty_str.to_uppercase().into()\n    };\n    program.emit_insn(Insn::ArrayEncode {\n        reg,\n        element_affinity,\n        element_type,\n        table_name: table_name.into(),\n        col_name: col_name.into(),\n    });\n    Ok(())\n}\n\n/// Emit bytecode to decode an array value: convert record-format BLOB to JSON text.\n/// For base element types, this is a single ArrayDecode instruction.\n/// For custom element types with decode expressions, a per-element loop\n/// extracts elements via ArrayElement, applies decode, then rebuilds the blob.\npub(crate) fn emit_array_decode(\n    program: &mut ProgramBuilder,\n    reg: usize,\n    col: &Column,\n    resolver: &Resolver,\n) -> Result<()> {\n    if let Some(type_def) = resolver.schema().get_type_def_unchecked(&col.ty_str) {\n        if let Some(decode_expr) = type_def.decode.as_ref() {\n            emit_array_element_loop(program, reg, decode_expr, col, type_def, resolver)?;\n        }\n    }\n\n    // Convert record blob to JSON text for display\n    program.emit_insn(Insn::ArrayDecode { reg });\n    Ok(())\n}\n\n/// Emit encode expressions for columns with custom types in a contiguous register range.\n/// Used by INSERT, UPDATE, and UPSERT paths to encode values before TypeCheck.\n///\n/// If `only_columns` is `Some`, only encode columns whose index is in the set.\n/// This is needed for UPDATE/UPSERT where non-SET columns are already encoded\n/// (read from disk), and re-encoding them would corrupt data.\npub(crate) fn emit_custom_type_encode_columns(\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    columns: &[Column],\n    start_reg: usize,\n    only_columns: Option<&HashSet<usize>>,\n    table_name: &str,\n) -> Result<()> {\n    for (i, col) in columns.iter().enumerate() {\n        if let Some(filter) = only_columns {\n            if !filter.contains(&i) {\n                continue;\n            }\n        }\n\n        let reg = start_reg + i;\n\n        // Handle array columns: encode input (text or blob) -> record blob for storage\n        if col.is_array() {\n            let skip_label = program.allocate_label();\n            program.emit_insn(Insn::IsNull {\n                reg,\n                target_pc: skip_label,\n            });\n            emit_array_encode(program, reg, col, resolver, table_name)?;\n            program.preassign_label_to_next_insn(skip_label);\n            continue;\n        }\n\n        let type_name = &col.ty_str;\n        if type_name.is_empty() {\n            continue;\n        }\n        let Some(type_def) = resolver.schema().get_type_def_unchecked(type_name) else {\n            continue;\n        };\n        let Some(ref encode_expr) = type_def.encode else {\n            continue;\n        };\n\n        // Skip NULL values: jump over encode if NULL\n        let skip_label = program.allocate_label();\n        program.emit_insn(Insn::IsNull {\n            reg,\n            target_pc: skip_label,\n        });\n\n        emit_type_expr(program, encode_expr, reg, reg, col, type_def, resolver)?;\n\n        program.preassign_label_to_next_insn(skip_label);\n    }\n    Ok(())\n}\n\n/// Emit decode expressions for columns with custom types in a contiguous register range.\n/// Used by the UPSERT path to decode values that were read from disk (encoded) so that\n/// WHERE/SET expressions in DO UPDATE see user-facing values.\n///\n/// If `only_columns` is `Some`, only decode columns whose index is in the set.\npub(crate) fn emit_custom_type_decode_columns(\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    columns: &[Column],\n    start_reg: usize,\n    only_columns: Option<&HashSet<usize>>,\n) -> Result<()> {\n    for (i, col) in columns.iter().enumerate() {\n        if let Some(filter) = only_columns {\n            if !filter.contains(&i) {\n                continue;\n            }\n        }\n\n        let reg = start_reg + i;\n\n        // Handle array columns: decode record blob -> JSON text for display\n        if col.is_array() {\n            let skip_label = program.allocate_label();\n            program.emit_insn(Insn::IsNull {\n                reg,\n                target_pc: skip_label,\n            });\n            emit_array_decode(program, reg, col, resolver)?;\n            program.preassign_label_to_next_insn(skip_label);\n            continue;\n        }\n\n        let type_name = &col.ty_str;\n        if type_name.is_empty() {\n            continue;\n        }\n        let Some(type_def) = resolver.schema().get_type_def_unchecked(type_name) else {\n            continue;\n        };\n        let Some(ref decode_expr) = type_def.decode else {\n            continue;\n        };\n\n        // Skip NULL values: jump over decode if NULL\n        let skip_label = program.allocate_label();\n        program.emit_insn(Insn::IsNull {\n            reg,\n            target_pc: skip_label,\n        });\n\n        emit_type_expr(program, decode_expr, reg, reg, col, type_def, resolver)?;\n\n        program.preassign_label_to_next_insn(skip_label);\n    }\n    Ok(())\n}\n"
  },
  {
    "path": "core/translate/expression_index.rs",
    "content": "use crate::translate::emitter::Resolver;\nuse crate::translate::expr::{\n    bind_and_rewrite_expr, walk_expr, walk_expr_mut, BindingBehavior, WalkControl,\n};\nuse crate::translate::plan::{ColumnUsedMask, JoinedTable, TableReferences};\nuse crate::translate::planner::ROWID_STRS;\nuse crate::Result;\nuse turso_parser::ast;\nuse turso_parser::ast::TableInternalId;\n\n/// Normalize a query expression so it can be compared with an\n/// expression stored on an index definition.\n///\n/// We need to remove the bindings and turn them back into identifiers so we can say:\n///\n/// - `CREATE INDEX idx ON t(Expr::Id(a) + Expr::Id(b));`\n/// - `SELECT * FROM t WHERE Expr::Column(name: 'a') + Expr::Column(name: 'b') = 10;`\n///\n/// After normalization, both sides look like `Expr::Id('a') + Expr::Id('b')`, allowing an\n/// equality check to spot the match.\npub fn normalize_expr_for_index_matching(\n    expr: &ast::Expr,\n    table_reference: &JoinedTable,\n    table_references: &TableReferences,\n) -> ast::Expr {\n    let mut expr = expr.clone();\n    let _table_idx = table_references\n        .joined_tables()\n        .iter()\n        .position(|t| t.internal_id == table_reference.internal_id)\n        .expect(\"table must exist in table_references\");\n    let columns = table_reference.table.columns();\n    let mut normalize = |e: &mut ast::Expr| -> Result<WalkControl> {\n        match e {\n            ast::Expr::Column { column, .. } => {\n                if let Some(name) = columns.get(*column).and_then(|c| c.name.as_ref()) {\n                    *e = ast::Expr::Id(ast::Name::exact(name.clone()));\n                }\n            }\n            ast::Expr::RowId { .. } => {\n                *e = ast::Expr::Id(ast::Name::exact(ROWID_STRS[0].to_string()));\n            }\n            _ => {}\n        }\n        Ok(WalkControl::Continue)\n    };\n    let _ = walk_expr_mut(&mut expr, &mut normalize);\n    expr\n}\n\n/// Determine whether an expression references columns from exactly one table\n/// and, if so, which specific columns are used.\n///\n/// The optimizer only treats an expression index as covering if every column\n/// required to compute that expression is satisfied by the index key itself.\n/// This helper tells us:\n///\n/// - `a + b` on table `t` -> returns table `t` plus a mask for `a` and `b`.\n/// - `t.a + u.b` -> returns `None` so we do not mis-apply a single-table expression index.\npub fn single_table_column_usage(expr: &ast::Expr) -> Option<(TableInternalId, ColumnUsedMask)> {\n    let mut table_id: Option<TableInternalId> = None;\n    let mut columns = ColumnUsedMask::default();\n    let mut ok = true;\n    let _ = walk_expr(expr, &mut |e: &ast::Expr| -> Result<WalkControl> {\n        if let ast::Expr::Column { table, column, .. } = e {\n            if let Some(existing) = table_id {\n                if existing != *table {\n                    ok = false;\n                    return Ok(WalkControl::SkipChildren);\n                }\n            } else {\n                table_id = Some(*table);\n            }\n            columns.set(*column);\n        }\n        Ok(WalkControl::Continue)\n    });\n\n    if ok {\n        table_id.map(|id| (id, columns))\n    } else {\n        None\n    }\n}\n\n/// Bind an expression index key expression against the target table and return\n/// the set of referenced columns.\n///\n/// Expression index SQL is stored in schema form and may use the base table\n/// name even when the query uses an alias. We bind using the base table name\n/// to keep dependency analysis stable across aliases.\npub fn expression_index_column_usage(\n    expr: &ast::Expr,\n    table_reference: &JoinedTable,\n    resolver: &Resolver<'_>,\n) -> Result<ColumnUsedMask> {\n    let mut bound_expr = expr.clone();\n    let mut binding_table = table_reference.clone();\n    if let Some(btree_table) = binding_table.table.btree() {\n        binding_table.identifier.clone_from(&btree_table.name);\n    }\n    let mut binding_tables = TableReferences::new(vec![binding_table], vec![]);\n    bind_and_rewrite_expr(\n        &mut bound_expr,\n        Some(&mut binding_tables),\n        None,\n        resolver,\n        BindingBehavior::ResultColumnsNotAllowed,\n    )?;\n\n    Ok(single_table_column_usage(&bound_expr)\n        .map(|(_, columns_mask)| columns_mask)\n        .unwrap_or_default())\n}\n"
  },
  {
    "path": "core/translate/fkeys.rs",
    "content": "use rustc_hash::FxHashSet as HashSet;\nuse turso_parser::ast::{self, Expr, Literal, Name, QualifiedName, RefAct};\n\nuse super::{translate_inner, ProgramBuilder, ProgramBuilderOpts};\nuse crate::{\n    error::SQLITE_CONSTRAINT_FOREIGNKEY,\n    schema::{BTreeTable, ForeignKey, Index, ResolvedFkRef, ROWID_SENTINEL},\n    translate::{collate::CollationSeq, emitter::Resolver, planner::ROWID_STRS},\n    vdbe::{\n        builder::{CursorType, QueryMode},\n        insn::{CmpInsFlags, Insn},\n        BranchOffset,\n    },\n    Connection, LimboError, Result, Value,\n};\nuse std::{num::NonZero, num::NonZeroUsize, sync::Arc};\n\n#[inline]\npub fn emit_guarded_fk_decrement(\n    program: &mut ProgramBuilder,\n    label: BranchOffset,\n    deferred: bool,\n) {\n    program.emit_insn(Insn::FkIfZero {\n        deferred,\n        target_pc: label,\n    });\n    program.emit_insn(Insn::FkCounter {\n        increment_value: -1,\n        deferred,\n    });\n}\n\n/// Open a read cursor on an index and return its cursor id.\n#[inline]\npub fn open_read_index(program: &mut ProgramBuilder, idx: &Arc<Index>, db: usize) -> usize {\n    let icur = program.alloc_cursor_id(CursorType::BTreeIndex(idx.clone()));\n    program.emit_insn(Insn::OpenRead {\n        cursor_id: icur,\n        root_page: idx.root_page,\n        db,\n    });\n    icur\n}\n\n/// Open a read cursor on a table and return its cursor id.\n#[inline]\npub fn open_read_table(program: &mut ProgramBuilder, tbl: &Arc<BTreeTable>, db: usize) -> usize {\n    let tcur = program.alloc_cursor_id(CursorType::BTreeTable(tbl.clone()));\n    program.emit_insn(Insn::OpenRead {\n        cursor_id: tcur,\n        root_page: tbl.root_page,\n        db,\n    });\n    tcur\n}\n\n/// Copy `len` registers starting at `src_start` to a fresh block and apply index affinities.\n/// Returns the destination start register.\n#[inline]\nfn copy_with_affinity(\n    program: &mut ProgramBuilder,\n    src_start: usize,\n    len: usize,\n    idx: &Index,\n    aff_from_tbl: &BTreeTable,\n) -> usize {\n    let dst = program.alloc_registers(len);\n    for i in 0..len {\n        program.emit_insn(Insn::Copy {\n            src_reg: src_start + i,\n            dst_reg: dst + i,\n            extra_amount: 0,\n        });\n    }\n    if let Some(count) = NonZeroUsize::new(len) {\n        program.emit_insn(Insn::Affinity {\n            start_reg: dst,\n            count,\n            affinities: build_index_affinity_string(idx, aff_from_tbl),\n        });\n    }\n    dst\n}\n\n/// Issue an index probe using `Found`/`NotFound` and route to `on_found`/`on_not_found`.\npub fn index_probe<F, G>(\n    program: &mut ProgramBuilder,\n    icur: usize,\n    record_reg: usize,\n    num_regs: usize,\n    mut on_found: F,\n    mut on_not_found: G,\n) -> Result<()>\nwhere\n    F: FnMut(&mut ProgramBuilder) -> Result<()>,\n    G: FnMut(&mut ProgramBuilder) -> Result<()>,\n{\n    let lbl_found = program.allocate_label();\n    let lbl_join = program.allocate_label();\n\n    program.emit_insn(Insn::Found {\n        cursor_id: icur,\n        target_pc: lbl_found,\n        record_reg,\n        num_regs,\n    });\n\n    // NOT FOUND path\n    on_not_found(program)?;\n    program.emit_insn(Insn::Goto {\n        target_pc: lbl_join,\n    });\n\n    // FOUND path\n    program.preassign_label_to_next_insn(lbl_found);\n    on_found(program)?;\n\n    // Join & close once\n    program.preassign_label_to_next_insn(lbl_join);\n    program.emit_insn(Insn::Close { cursor_id: icur });\n    Ok(())\n}\n\n/// Iterate a table and call `on_match` when all child columns equal the key at `parent_key_start`.\n/// Skips rows where any FK column is NULL. If `self_exclude_rowid` is Some, the row with that rowid is skipped.\nfn table_scan_match_any<F>(\n    program: &mut ProgramBuilder,\n    child_tbl: &Arc<BTreeTable>,\n    child_cols: &[String],\n    parent_key_start: usize,\n    self_exclude_rowid: Option<usize>,\n    database_id: usize,\n    mut on_match: F,\n) -> Result<()>\nwhere\n    F: FnMut(&mut ProgramBuilder) -> Result<()>,\n{\n    let ccur = open_read_table(program, child_tbl, database_id);\n    let done = program.allocate_label();\n    program.emit_insn(Insn::Rewind {\n        cursor_id: ccur,\n        pc_if_empty: done,\n    });\n\n    let loop_top = program.allocate_label();\n    program.preassign_label_to_next_insn(loop_top);\n    let next_row = program.allocate_label();\n\n    // Compare each FK column to parent key component.\n    for (i, cname) in child_cols.iter().enumerate() {\n        let (pos, _) = child_tbl\n            .get_column(cname)\n            .ok_or_else(|| LimboError::InternalError(format!(\"child col {cname} missing\")))?;\n        let tmp = program.alloc_register();\n        program.emit_insn(Insn::Column {\n            cursor_id: ccur,\n            column: pos,\n            dest: tmp,\n            default: None,\n        });\n        program.emit_insn(Insn::IsNull {\n            reg: tmp,\n            target_pc: next_row,\n        });\n\n        let cont = program.allocate_label();\n        program.emit_insn(Insn::Eq {\n            lhs: tmp,\n            rhs: parent_key_start + i,\n            target_pc: cont,\n            flags: CmpInsFlags::default().jump_if_null(),\n            collation: Some(CollationSeq::Binary),\n        });\n        program.emit_insn(Insn::Goto {\n            target_pc: next_row,\n        });\n        program.preassign_label_to_next_insn(cont);\n    }\n\n    //self-reference exclusion on rowid\n    if let Some(parent_rowid) = self_exclude_rowid {\n        let child_rowid = program.alloc_register();\n        let skip = program.allocate_label();\n        program.emit_insn(Insn::RowId {\n            cursor_id: ccur,\n            dest: child_rowid,\n        });\n        program.emit_insn(Insn::Eq {\n            lhs: child_rowid,\n            rhs: parent_rowid,\n            target_pc: skip,\n            flags: CmpInsFlags::default(),\n            collation: None,\n        });\n        on_match(program)?;\n        program.preassign_label_to_next_insn(skip);\n    } else {\n        on_match(program)?;\n    }\n\n    program.preassign_label_to_next_insn(next_row);\n    program.emit_insn(Insn::Next {\n        cursor_id: ccur,\n        pc_if_next: loop_top,\n    });\n\n    program.preassign_label_to_next_insn(done);\n    program.emit_insn(Insn::Close { cursor_id: ccur });\n    Ok(())\n}\n\n/// Build the index affinity mask string (one char per indexed column).\n#[inline]\npub fn build_index_affinity_string(idx: &Index, table: &BTreeTable) -> String {\n    idx.columns\n        .iter()\n        .map(|ic| {\n            table.columns[ic.pos_in_table]\n                .affinity_with_strict(table.is_strict)\n                .aff_mask()\n        })\n        .collect()\n}\n\n/// Increment a foreign key violation counter; for deferred FKs, this is a global counter\n/// on the connection; for immediate FKs, this is a per-statement counter in the program state.\n/// Used for NO ACTION behavior where violation is checked at statement/transaction end.\npub fn emit_fk_violation(program: &mut ProgramBuilder, fk: &ForeignKey) -> Result<()> {\n    program.emit_insn(Insn::FkCounter {\n        increment_value: 1,\n        deferred: fk.deferred,\n    });\n    Ok(())\n}\n\n/// Emit an immediate HALT for FK violations.\npub fn emit_fk_restrict_halt(program: &mut ProgramBuilder) -> Result<()> {\n    program.emit_insn(Insn::Halt {\n        err_code: SQLITE_CONSTRAINT_FOREIGNKEY,\n        description: \"FOREIGN KEY constraint failed\".to_string(),\n        on_error: None,\n        description_reg: None,\n    });\n    Ok(())\n}\n\n/// Stabilize the NEW row image for FK checks (UPDATE):\n/// fill in unmodified PK columns from the current row so the NEW PK vector is complete.\npub fn stabilize_new_row_for_fk(\n    program: &mut ProgramBuilder,\n    table_btree: &BTreeTable,\n    set_clauses: &[(usize, Box<Expr>)],\n    cursor_id: usize,\n    start: usize,\n    rowid_new_reg: usize,\n) -> Result<()> {\n    if table_btree.primary_key_columns.is_empty() {\n        return Ok(());\n    }\n    let set_cols: HashSet<usize> = set_clauses\n        .iter()\n        .filter_map(|(i, _)| if *i == ROWID_SENTINEL { None } else { Some(*i) })\n        .collect();\n\n    for (pk_name, _) in &table_btree.primary_key_columns {\n        let (pos, col) = table_btree\n            .get_column(pk_name)\n            .ok_or_else(|| LimboError::InternalError(format!(\"pk col {pk_name} missing\")))?;\n        if !set_cols.contains(&pos) {\n            if col.is_rowid_alias() {\n                program.emit_insn(Insn::Copy {\n                    src_reg: rowid_new_reg,\n                    dst_reg: start + pos,\n                    extra_amount: 0,\n                });\n            } else {\n                program.emit_insn(Insn::Column {\n                    cursor_id,\n                    column: pos,\n                    dest: start + pos,\n                    default: None,\n                });\n            }\n        }\n    }\n    Ok(())\n}\n\n/// Parent-side checks when the parent key might change (UPDATE on parent):\n/// Detect if any child references the OLD key (potential violation), and if any references the NEW key\n/// (which cancels one potential violation). For composite keys this builds OLD/NEW vectors first.\n#[allow(clippy::too_many_arguments)]\npub fn emit_parent_key_change_checks(\n    program: &mut ProgramBuilder,\n    table_btree: &BTreeTable,\n    indexes_to_update: impl Iterator<Item = impl AsRef<Index>>,\n    cursor_id: usize,\n    old_rowid_reg: usize,\n    start: usize,\n    rowid_new_reg: usize,\n    rowid_set_clause_reg: Option<usize>,\n    set_clauses: &[(usize, Box<Expr>)],\n    database_id: usize,\n    resolver: &Resolver,\n) -> Result<()> {\n    let updated_positions: HashSet<usize> = set_clauses.iter().map(|(i, _)| *i).collect();\n    let incoming = resolver.with_schema(database_id, |s| {\n        s.resolved_fks_referencing(&table_btree.name)\n    })?;\n    let affects_pk = incoming\n        .iter()\n        .any(|r| r.parent_key_may_change(&updated_positions, table_btree));\n    if !affects_pk {\n        return Ok(());\n    }\n\n    let primary_key_is_rowid_alias = table_btree.get_rowid_alias_column().is_some();\n\n    if primary_key_is_rowid_alias || table_btree.primary_key_columns.is_empty() {\n        emit_rowid_pk_change_check(\n            program,\n            &incoming,\n            old_rowid_reg,\n            rowid_set_clause_reg.unwrap_or(old_rowid_reg),\n            database_id,\n            resolver,\n        )?;\n    }\n\n    for index in indexes_to_update {\n        emit_parent_index_key_change_checks(\n            program,\n            cursor_id,\n            start,\n            old_rowid_reg,\n            rowid_new_reg,\n            &incoming,\n            table_btree,\n            index.as_ref(),\n            database_id,\n            resolver,\n        )?;\n    }\n    Ok(())\n}\n\n/// Rowid-table parent PK change: compare rowid OLD vs NEW; if changed, run two-pass counters.\npub fn emit_rowid_pk_change_check(\n    program: &mut ProgramBuilder,\n    incoming: &[ResolvedFkRef],\n    old_rowid_reg: usize,\n    new_rowid_reg: usize,\n    database_id: usize,\n    resolver: &Resolver,\n) -> Result<()> {\n    let skip = program.allocate_label();\n    program.emit_insn(Insn::Eq {\n        lhs: new_rowid_reg,\n        rhs: old_rowid_reg,\n        target_pc: skip,\n        flags: CmpInsFlags::default(),\n        collation: None,\n    });\n\n    let old_pk = program.alloc_register();\n    let new_pk = program.alloc_register();\n    program.emit_insn(Insn::Copy {\n        src_reg: old_rowid_reg,\n        dst_reg: old_pk,\n        extra_amount: 0,\n    });\n    program.emit_insn(Insn::Copy {\n        src_reg: new_rowid_reg,\n        dst_reg: new_pk,\n        extra_amount: 0,\n    });\n\n    emit_fk_parent_pk_change_counters(program, incoming, old_pk, new_pk, 1, database_id, resolver)?;\n    program.preassign_label_to_next_insn(skip);\n    Ok(())\n}\n\n/// Foreign keys are only legal if the referenced parent key is:\n/// 1. The rowid alias (no separate index)\n/// 2. Part of a primary key / unique index (there is no practical difference between the two)\n///\n/// If the foreign key references a composite key, all of the columns in the key must be referenced.\n/// E.g.\n/// CREATE TABLE parent (a, b, c, PRIMARY KEY (a, b, c));\n/// CREATE TABLE child (a, b, c, FOREIGN KEY (a, b, c) REFERENCES parent (a, b, c));\n///\n/// Whereas this is not allowed:\n/// CREATE TABLE parent (a, b, c, PRIMARY KEY (a, b, c));\n/// CREATE TABLE child (a, b, c, FOREIGN KEY (a, b) REFERENCES parent (a, b, c));\n///\n/// This function checks if the parent key has changed by comparing the OLD and NEW values.\n/// If the parent key has changed, it emits the counters for the foreign keys.\n/// If the parent key has not changed, it does nothing.\n#[allow(clippy::too_many_arguments)]\npub fn emit_parent_index_key_change_checks(\n    program: &mut ProgramBuilder,\n    cursor_id: usize,\n    new_values_start: usize,\n    old_rowid_reg: usize,\n    new_rowid_reg: usize,\n    incoming: &[ResolvedFkRef],\n    table_btree: &BTreeTable,\n    index: &Index,\n    database_id: usize,\n    resolver: &Resolver,\n) -> Result<()> {\n    // Only process FKs that:\n    // 1. Reference this specific index (OLD/NEW key vectors are built from this index's columns)\n    // 2. Have NO ACTION or RESTRICT on_update action (CASCADE/SET NULL/SET DEFAULT are handled\n    //    later by fire_fk_update_actions AFTER the update completes)\n    let matching_fks: Vec<_> = incoming\n        .iter()\n        .filter(|fk_ref| {\n            let matches_index = fk_ref\n                .parent_unique_index\n                .as_ref()\n                .is_some_and(|idx| idx.name == index.name);\n            let is_noaction_or_restrict =\n                matches!(fk_ref.fk.on_update, RefAct::NoAction | RefAct::Restrict);\n            matches_index && is_noaction_or_restrict\n        })\n        .cloned()\n        .collect();\n\n    if matching_fks.is_empty() {\n        return Ok(());\n    }\n\n    let idx_len = index.columns.len();\n\n    let old_key = program.alloc_registers(idx_len);\n    for (i, index_col) in index.columns.iter().enumerate() {\n        let pos_in_table = index_col.pos_in_table;\n        let column = &table_btree.columns[pos_in_table];\n        if column.is_rowid_alias() {\n            program.emit_insn(Insn::Copy {\n                src_reg: old_rowid_reg,\n                dst_reg: old_key + i,\n                extra_amount: 0,\n            });\n        } else {\n            program.emit_insn(Insn::Column {\n                cursor_id,\n                column: pos_in_table,\n                dest: old_key + i,\n                default: None,\n            });\n        }\n    }\n    let new_key = program.alloc_registers(idx_len);\n    for (i, index_col) in index.columns.iter().enumerate() {\n        let pos_in_table = index_col.pos_in_table;\n        let column = &table_btree.columns[pos_in_table];\n        let src = if column.is_rowid_alias() {\n            new_rowid_reg\n        } else {\n            new_values_start + pos_in_table\n        };\n        program.emit_insn(Insn::Copy {\n            src_reg: src,\n            dst_reg: new_key + i,\n            extra_amount: 0,\n        });\n    }\n\n    let skip = program.allocate_label();\n    let changed = program.allocate_label();\n    for i in 0..idx_len {\n        let next = if i + 1 == idx_len {\n            None\n        } else {\n            Some(program.allocate_label())\n        };\n        program.emit_insn(Insn::Eq {\n            lhs: old_key + i,\n            rhs: new_key + i,\n            target_pc: next.unwrap_or(skip),\n            flags: CmpInsFlags::default(),\n            collation: None,\n        });\n        program.emit_insn(Insn::Goto { target_pc: changed });\n        if let Some(n) = next {\n            program.preassign_label_to_next_insn(n);\n        }\n    }\n\n    program.preassign_label_to_next_insn(changed);\n    emit_fk_parent_pk_change_counters(\n        program,\n        &matching_fks,\n        old_key,\n        new_key,\n        idx_len,\n        database_id,\n        resolver,\n    )?;\n    program.preassign_label_to_next_insn(skip);\n    Ok(())\n}\n\n/// Two-pass parent-side maintenance for UPDATE of a parent key:\n/// 1. Probe child for OLD key, increment deferred counter if any references exist.\n/// 2. Probe child for NEW key, guarded decrement cancels exactly one increment if present\n#[allow(clippy::too_many_arguments)]\npub fn emit_fk_parent_pk_change_counters(\n    program: &mut ProgramBuilder,\n    incoming: &[ResolvedFkRef],\n    old_pk_start: usize,\n    new_pk_start: usize,\n    n_cols: usize,\n    database_id: usize,\n    resolver: &Resolver,\n) -> Result<()> {\n    for fk_ref in incoming {\n        emit_fk_parent_key_probe(\n            program,\n            fk_ref,\n            old_pk_start,\n            n_cols,\n            ParentProbePass::Old,\n            database_id,\n            resolver,\n        )?;\n        emit_fk_parent_key_probe(\n            program,\n            fk_ref,\n            new_pk_start,\n            n_cols,\n            ParentProbePass::New,\n            database_id,\n            resolver,\n        )?;\n    }\n    Ok(())\n}\n\n/// After INSERT in the UPDATE path, if REPLACE fired during Phase 1,\n/// children referencing the NEW parent key may resolve deferred FK violations\n/// that the REPLACE delete incremented. This emits a FkIfZero-guarded scan\n/// that decrements the counter for each matching child row.\n///\n/// Matches SQLite's post-delete / pre-insert FK reconciliation\n/// (sqlite3FkCheck → fkScanChildren with isIgnoreErr=1 path).\n#[allow(clippy::too_many_arguments)]\npub fn emit_fk_parent_new_key_reconcile(\n    program: &mut ProgramBuilder,\n    table_btree: &BTreeTable,\n    new_values_start: usize,\n    new_rowid_reg: usize,\n    set_clauses: &[(usize, Box<ast::Expr>)],\n    database_id: usize,\n    resolver: &Resolver,\n) -> Result<()> {\n    let updated_positions: HashSet<usize> = set_clauses.iter().map(|(i, _)| *i).collect();\n    let incoming = resolver.with_schema(database_id, |s| {\n        s.resolved_fks_referencing(&table_btree.name)\n    })?;\n\n    let is_relevant = |fk: &ResolvedFkRef| -> bool {\n        fk.fk.deferred && fk.parent_key_may_change(&updated_positions, table_btree)\n    };\n    if !incoming.iter().any(&is_relevant) {\n        return Ok(());\n    }\n\n    // FkIfZero guard: skip entire section if counter is already zero.\n    let skip_all = program.allocate_label();\n    program.emit_insn(Insn::FkIfZero {\n        deferred: true,\n        target_pc: skip_all,\n    });\n\n    for fk_ref in incoming.iter().filter(|fk| is_relevant(fk)) {\n        if fk_ref.parent_uses_rowid {\n            emit_fk_parent_key_probe(\n                program,\n                fk_ref,\n                new_rowid_reg,\n                1,\n                ParentProbePass::New,\n                database_id,\n                resolver,\n            )?;\n            continue;\n        }\n\n        // Build contiguous NEW key registers from the parent column positions.\n        // Parent columns come from explicit FK declaration, or implicitly from PK.\n        let explicit = &fk_ref.fk.parent_columns;\n        let pk = &table_btree.primary_key_columns;\n        let n_cols = if explicit.is_empty() {\n            pk.len()\n        } else {\n            explicit.len()\n        };\n        let new_key = program.alloc_registers(n_cols);\n\n        for i in 0..n_cols {\n            let col_name = if explicit.is_empty() {\n                pk[i].0.as_str()\n            } else {\n                explicit[i].as_str()\n            };\n            let (pos, col) = table_btree\n                .get_column(col_name)\n                .ok_or_else(|| LimboError::InternalError(format!(\"col {col_name} missing\")))?;\n            let src = if col.is_rowid_alias() {\n                new_rowid_reg\n            } else {\n                new_values_start + pos\n            };\n            program.emit_insn(Insn::Copy {\n                src_reg: src,\n                dst_reg: new_key + i,\n                extra_amount: 0,\n            });\n        }\n\n        emit_fk_parent_key_probe(\n            program,\n            fk_ref,\n            new_key,\n            n_cols,\n            ParentProbePass::New,\n            database_id,\n            resolver,\n        )?;\n    }\n\n    program.preassign_label_to_next_insn(skip_all);\n    Ok(())\n}\n\n#[derive(Clone, Copy)]\nenum ParentProbePass {\n    Old,\n    New,\n}\n\n/// Probe the child side for a given parent key\n/// For RESTRICT on OLD pass: emits immediate HALT\n/// For NO ACTION on OLD pass: increments FK violation counter\n#[allow(clippy::too_many_arguments)]\nfn emit_fk_parent_key_probe(\n    program: &mut ProgramBuilder,\n    fk_ref: &ResolvedFkRef,\n    parent_key_start: usize,\n    n_cols: usize,\n    pass: ParentProbePass,\n    database_id: usize,\n    resolver: &Resolver,\n) -> Result<()> {\n    let child_tbl = &fk_ref.child_table;\n    let child_cols = &fk_ref.fk.child_columns;\n    let is_deferred = fk_ref.fk.deferred;\n    let is_restrict = matches!(fk_ref.fk.on_update, RefAct::Restrict);\n\n    let on_match = |p: &mut ProgramBuilder| -> Result<()> {\n        match (is_deferred, pass) {\n            // OLD key referenced by a child\n            (_, ParentProbePass::Old) => {\n                if is_restrict {\n                    // RESTRICT: immediate halt\n                    emit_fk_restrict_halt(p)?;\n                } else {\n                    // NO ACTION: increment counter (checked at statement/transaction end)\n                    emit_fk_violation(p, &fk_ref.fk)?;\n                }\n            }\n\n            // NEW key referenced by a child (cancel one deferred violation)\n            // Note: for RESTRICT, we already halted on OLD pass if child exists,\n            // so this branch only applies to NO ACTION deferred FKs\n            (true, ParentProbePass::New) => {\n                // Guard to avoid underflow if OLD pass didn't increment.\n                let skip = p.allocate_label();\n                emit_guarded_fk_decrement(p, skip, fk_ref.fk.deferred);\n                p.preassign_label_to_next_insn(skip);\n            }\n            // Immediate FK on NEW pass: nothing to cancel; do nothing.\n            (false, ParentProbePass::New) => {}\n        }\n        Ok(())\n    };\n\n    // Prefer exact child index on (child_cols...)\n    let indices: Vec<_> = resolver.with_schema(database_id, |s| {\n        s.get_indices(&child_tbl.name).cloned().collect()\n    });\n    let idx = indices.iter().find(|ix| {\n        ix.columns.len() == child_cols.len()\n            && ix\n                .columns\n                .iter()\n                .zip(child_cols.iter())\n                .all(|(ic, cc)| ic.name.eq_ignore_ascii_case(cc))\n    });\n\n    if let Some(ix) = idx {\n        let icur = open_read_index(program, ix, database_id);\n        let probe = copy_with_affinity(program, parent_key_start, n_cols, ix, child_tbl);\n\n        // FOUND => on_match; NOT FOUND => no-op\n        index_probe(program, icur, probe, n_cols, on_match, |_p| Ok(()))?;\n    } else {\n        // Table scan fallback\n        table_scan_match_any(\n            program,\n            child_tbl,\n            child_cols,\n            parent_key_start,\n            None,\n            database_id,\n            on_match,\n        )?;\n    }\n\n    Ok(())\n}\n\n/// Build a parent key vector (in FK parent-column order) into `dest_start`.\n/// Handles rowid aliasing and explicit ROWID names; uses current row for non-rowid columns.\nfn build_parent_key(\n    program: &mut ProgramBuilder,\n    parent_bt: &BTreeTable,\n    parent_cols: &[String],\n    parent_cursor_id: usize,\n    parent_rowid_reg: usize,\n    dest_start: usize,\n) -> Result<()> {\n    for (i, pcol) in parent_cols.iter().enumerate() {\n        let src = if ROWID_STRS.iter().any(|s| pcol.eq_ignore_ascii_case(s)) {\n            parent_rowid_reg\n        } else {\n            let (pos, col) = parent_bt\n                .get_column(pcol)\n                .ok_or_else(|| LimboError::InternalError(format!(\"col {pcol} missing\")))?;\n            if col.is_rowid_alias() {\n                parent_rowid_reg\n            } else {\n                program.emit_insn(Insn::Column {\n                    cursor_id: parent_cursor_id,\n                    column: pos,\n                    dest: dest_start + i,\n                    default: None,\n                });\n                continue;\n            }\n        };\n        program.emit_insn(Insn::Copy {\n            src_reg: src,\n            dst_reg: dest_start + i,\n            extra_amount: 0,\n        });\n    }\n    Ok(())\n}\n\n/// Child-side FK maintenance for UPDATE/UPSERT:\n/// If any FK columns of this child row changed:\n///  Pass 1 (OLD tuple): if OLD is non-NULL and parent is missing: decrement deferred counter (guarded).\n///  Pass 2 (NEW tuple): if NEW is non-NULL and parent is missing: immediate error or deferred(+1).\n#[allow(clippy::too_many_arguments)]\npub fn emit_fk_child_update_counters(\n    program: &mut ProgramBuilder,\n    child_tbl: &BTreeTable,\n    child_table_name: &str,\n    child_cursor_id: usize,\n    new_start_reg: usize,\n    new_rowid_reg: usize,\n    updated_cols: &HashSet<usize>,\n    database_id: usize,\n    resolver: &Resolver,\n) -> Result<()> {\n    // Helper: materialize OLD tuple for this FK; returns (start_reg, ncols, null_skip_label).\n    // The null_skip_label is unresolved and must be resolved by the caller after the FK check\n    // block, so that when any OLD column is NULL the entire FK check is skipped.\n    let load_old_tuple = |program: &mut ProgramBuilder,\n                          fk_cols: &[String]|\n     -> Option<(usize, usize, BranchOffset)> {\n        let n = fk_cols.len();\n        let start = program.alloc_registers(n);\n        let null_jmp = program.allocate_label();\n\n        for (k, cname) in fk_cols.iter().enumerate() {\n            let (pos, _col) = match child_tbl.get_column(cname) {\n                Some(v) => v,\n                None => {\n                    return None;\n                }\n            };\n            program.emit_column_or_rowid(child_cursor_id, pos, start + k);\n            program.emit_insn(Insn::IsNull {\n                reg: start + k,\n                target_pc: null_jmp,\n            });\n        }\n\n        Some((start, n, null_jmp))\n    };\n\n    for fk_ref in\n        resolver.with_schema(database_id, |s| s.resolved_fks_for_child(child_table_name))?\n    {\n        // If the child-side FK columns did not change, there is nothing to do.\n        if !fk_ref.child_key_changed(updated_cols, child_tbl) {\n            continue;\n        }\n\n        let ncols = fk_ref.child_cols.len();\n\n        // Pass 1: OLD tuple handling only for deferred FKs\n        if fk_ref.fk.deferred {\n            if let Some((old_start, _, null_skip)) = load_old_tuple(program, &fk_ref.child_cols) {\n                if fk_ref.parent_uses_rowid {\n                    // Parent key is rowid: probe parent table by rowid\n                    let parent_tbl = resolver\n                        .with_schema(database_id, |s| s.get_btree_table(&fk_ref.fk.parent_table))\n                        .expect(\"parent btree\");\n                    let pcur = open_read_table(program, &parent_tbl, database_id);\n\n                    // first FK col is the rowid value\n                    let rid = program.alloc_register();\n                    program.emit_insn(Insn::Copy {\n                        src_reg: old_start,\n                        dst_reg: rid,\n                        extra_amount: 0,\n                    });\n                    program.emit_insn(Insn::MustBeInt { reg: rid });\n\n                    // If NOT exists => decrement\n                    let miss = program.allocate_label();\n                    program.emit_insn(Insn::NotExists {\n                        cursor: pcur,\n                        rowid_reg: rid,\n                        target_pc: miss,\n                    });\n                    // found: close & continue\n                    let join = program.allocate_label();\n                    program.emit_insn(Insn::Close { cursor_id: pcur });\n                    program.emit_insn(Insn::Goto { target_pc: join });\n\n                    // missing: guarded decrement\n                    program.preassign_label_to_next_insn(miss);\n                    program.emit_insn(Insn::Close { cursor_id: pcur });\n                    let skip = program.allocate_label();\n                    emit_guarded_fk_decrement(program, skip, fk_ref.fk.deferred);\n                    program.preassign_label_to_next_insn(skip);\n\n                    program.preassign_label_to_next_insn(join);\n                } else {\n                    // Parent key is a unique index: use index probe and guarded decrement on NOT FOUND\n                    let parent_tbl = resolver\n                        .with_schema(database_id, |s| s.get_btree_table(&fk_ref.fk.parent_table))\n                        .expect(\"parent btree\");\n                    let idx = fk_ref\n                        .parent_unique_index\n                        .as_ref()\n                        .expect(\"parent unique index required\");\n                    let icur = open_read_index(program, idx, database_id);\n\n                    // Copy OLD tuple and apply parent index affinities\n                    let probe = copy_with_affinity(program, old_start, ncols, idx, &parent_tbl);\n                    // Found: nothing; Not found: guarded decrement\n                    index_probe(\n                        program,\n                        icur,\n                        probe,\n                        ncols,\n                        |_p| Ok(()),\n                        |p| {\n                            let skip = p.allocate_label();\n                            emit_guarded_fk_decrement(p, skip, fk_ref.fk.deferred);\n                            p.preassign_label_to_next_insn(skip);\n                            Ok(())\n                        },\n                    )?;\n                }\n                // Resolve the null skip label after the FK check block so that\n                // when any OLD column is NULL, the entire check is bypassed.\n                program.preassign_label_to_next_insn(null_skip);\n            }\n        }\n\n        // Pass 2: NEW tuple handling\n        let fk_ok = program.allocate_label();\n        for cname in &fk_ref.fk.child_columns {\n            let (i, col) = child_tbl.get_column(cname).unwrap();\n            let src = if col.is_rowid_alias() {\n                new_rowid_reg\n            } else {\n                new_start_reg + i\n            };\n            program.emit_insn(Insn::IsNull {\n                reg: src,\n                target_pc: fk_ok,\n            });\n        }\n\n        if fk_ref.parent_uses_rowid {\n            let parent_tbl = resolver\n                .with_schema(database_id, |s| s.get_btree_table(&fk_ref.fk.parent_table))\n                .expect(\"parent btree\");\n            let pcur = open_read_table(program, &parent_tbl, database_id);\n\n            // Take the first child column value from NEW image\n            let (i_child, col_child) = child_tbl.get_column(&fk_ref.child_cols[0]).unwrap();\n            let val_reg = if col_child.is_rowid_alias() {\n                new_rowid_reg\n            } else {\n                new_start_reg + i_child\n            };\n\n            let tmp = program.alloc_register();\n            program.emit_insn(Insn::Copy {\n                src_reg: val_reg,\n                dst_reg: tmp,\n                extra_amount: 0,\n            });\n            program.emit_insn(Insn::MustBeInt { reg: tmp });\n\n            let violation = program.allocate_label();\n            program.emit_insn(Insn::NotExists {\n                cursor: pcur,\n                rowid_reg: tmp,\n                target_pc: violation,\n            });\n            // found: close and continue\n            program.emit_insn(Insn::Close { cursor_id: pcur });\n            program.emit_insn(Insn::Goto { target_pc: fk_ok });\n\n            // missing: violation (immediate HALT or deferred +1)\n            program.preassign_label_to_next_insn(violation);\n            program.emit_insn(Insn::Close { cursor_id: pcur });\n            emit_fk_violation(program, &fk_ref.fk)?;\n        } else {\n            let parent_tbl = resolver\n                .with_schema(database_id, |s| s.get_btree_table(&fk_ref.fk.parent_table))\n                .expect(\"parent btree\");\n            let idx = fk_ref\n                .parent_unique_index\n                .as_ref()\n                .expect(\"parent unique index required\");\n            let icur = open_read_index(program, idx, database_id);\n\n            // Build NEW probe (in FK child column order, aligns with parent index columns)\n            let probe = {\n                let start = program.alloc_registers(ncols);\n                for (k, cname) in fk_ref.child_cols.iter().enumerate() {\n                    let (i, col) = child_tbl.get_column(cname).unwrap();\n                    program.emit_insn(Insn::Copy {\n                        src_reg: if col.is_rowid_alias() {\n                            new_rowid_reg\n                        } else {\n                            new_start_reg + i\n                        },\n                        dst_reg: start + k,\n                        extra_amount: 0,\n                    });\n                }\n                // Apply affinities of the parent index/table\n                if let Some(cnt) = NonZeroUsize::new(ncols) {\n                    program.emit_insn(Insn::Affinity {\n                        start_reg: start,\n                        count: cnt,\n                        affinities: build_index_affinity_string(idx, &parent_tbl),\n                    });\n                }\n                start\n            };\n\n            // FOUND: ok; NOT FOUND: violation path\n            index_probe(\n                program,\n                icur,\n                probe,\n                ncols,\n                |_p| Ok(()),\n                |p| {\n                    emit_fk_violation(p, &fk_ref.fk)?;\n                    Ok(())\n                },\n            )?;\n            program.emit_insn(Insn::Goto { target_pc: fk_ok });\n        }\n\n        // Skip label for NEW tuple NULL short-circuit\n        program.preassign_label_to_next_insn(fk_ok);\n    }\n\n    Ok(())\n}\n\n/// Single FK existence check for NO ACTION/RESTRICT on DELETE.\n/// Raises a violation if any child row references the parent key.\n/// For RESTRICT: emits immediate HALT\n/// For NO ACTION: increments FK violation counter (checked at statement/transaction end)\n#[allow(clippy::too_many_arguments)]\nfn emit_fk_delete_parent_existence_check_single(\n    program: &mut ProgramBuilder,\n    fk_ref: &ResolvedFkRef,\n    parent_bt: &Arc<BTreeTable>,\n    parent_table_name: &str,\n    parent_cursor_id: usize,\n    parent_rowid_reg: usize,\n    database_id: usize,\n    resolver: &Resolver,\n) -> Result<()> {\n    let is_self_ref = fk_ref\n        .child_table\n        .name\n        .eq_ignore_ascii_case(parent_table_name);\n\n    let is_restrict = matches!(fk_ref.fk.on_delete, RefAct::Restrict);\n\n    // Build parent key in FK's parent-column order\n    let parent_cols: Vec<String> = if fk_ref.fk.parent_columns.is_empty() {\n        parent_bt\n            .primary_key_columns\n            .iter()\n            .map(|(n, _)| n.clone())\n            .collect()\n    } else {\n        fk_ref.fk.parent_columns.clone()\n    };\n    let ncols = parent_cols.len();\n\n    let parent_key_start = program.alloc_registers(ncols);\n    build_parent_key(\n        program,\n        parent_bt,\n        &parent_cols,\n        parent_cursor_id,\n        parent_rowid_reg,\n        parent_key_start,\n    )?;\n\n    let child_cols = &fk_ref.fk.child_columns;\n    let child_idx = if !is_self_ref {\n        let indices: Vec<_> = resolver.with_schema(database_id, |s| {\n            s.get_indices(&fk_ref.child_table.name).cloned().collect()\n        });\n        indices.into_iter().find(|idx| {\n            idx.columns.len() == child_cols.len()\n                && idx\n                    .columns\n                    .iter()\n                    .zip(child_cols.iter())\n                    .all(|(ic, cc)| ic.name.eq_ignore_ascii_case(cc))\n        })\n    } else {\n        None\n    };\n\n    // Closure to emit the appropriate violation based on action type\n    let emit_violation = |p: &mut ProgramBuilder| -> Result<()> {\n        if is_restrict {\n            emit_fk_restrict_halt(p)?;\n        } else {\n            emit_fk_violation(p, &fk_ref.fk)?;\n        }\n        Ok(())\n    };\n\n    if let Some(ref idx) = child_idx {\n        let icur = open_read_index(program, idx, database_id);\n        let probe = copy_with_affinity(program, parent_key_start, ncols, idx, &fk_ref.child_table);\n        index_probe(\n            program,\n            icur,\n            probe,\n            ncols,\n            |p| {\n                emit_violation(p)?;\n                Ok(())\n            },\n            |_p| Ok(()),\n        )?;\n    } else {\n        table_scan_match_any(\n            program,\n            &fk_ref.child_table,\n            child_cols,\n            parent_key_start,\n            if is_self_ref {\n                Some(parent_rowid_reg)\n            } else {\n                None\n            },\n            database_id,\n            |p| {\n                emit_violation(p)?;\n                Ok(())\n            },\n        )?;\n    }\n    Ok(())\n}\n\n/// Handle all parent-side FK actions on UPDATE based on ON UPDATE action type.\n#[allow(clippy::too_many_arguments)]\npub fn emit_fk_update_parent_actions(\n    program: &mut ProgramBuilder,\n    table_btree: &BTreeTable,\n    indexes_to_update: impl Iterator<Item = impl AsRef<Index>>,\n    cursor_id: usize,\n    old_rowid_reg: usize,\n    start: usize,\n    rowid_new_reg: usize,\n    rowid_set_clause_reg: Option<usize>,\n    set_clauses: &[(usize, Box<Expr>)],\n    database_id: usize,\n    resolver: &Resolver,\n) -> Result<()> {\n    let updated_positions: HashSet<usize> = set_clauses.iter().map(|(i, _)| *i).collect();\n    let incoming = resolver.with_schema(database_id, |s| {\n        s.resolved_fks_referencing(&table_btree.name)\n    })?;\n    let affects_pk = incoming\n        .iter()\n        .any(|r| r.parent_key_may_change(&updated_positions, table_btree));\n    if !affects_pk {\n        return Ok(());\n    }\n\n    // Collect indexes to update into a Vec so we can iterate multiple times\n    let indexes: Vec<_> = indexes_to_update.collect();\n\n    // Check if any FK has CASCADE or SET NULL/SET DEFAULT action\n    let has_cascade_or_set = incoming\n        .iter()\n        .any(|fk_ref| !matches!(fk_ref.fk.on_update, RefAct::NoAction | RefAct::Restrict));\n\n    if has_cascade_or_set {\n        // We have CASCADE or SET NULL/DEFAULT - need to handle them\n        let primary_key_is_rowid_alias = table_btree.get_rowid_alias_column().is_some();\n\n        for fk_ref in &incoming {\n            if !fk_ref.parent_key_may_change(&updated_positions, table_btree) {\n                continue;\n            }\n\n            match fk_ref.fk.on_update {\n                RefAct::NoAction | RefAct::Restrict => {\n                    // Use existing counter-based logic for just this FK\n                    // We need to emit check for this specific FK\n                    if (primary_key_is_rowid_alias || table_btree.primary_key_columns.is_empty())\n                        && fk_ref.parent_uses_rowid\n                    {\n                        emit_rowid_pk_change_check(\n                            program,\n                            &[fk_ref.clone()],\n                            old_rowid_reg,\n                            rowid_set_clause_reg.unwrap_or(old_rowid_reg),\n                            database_id,\n                            resolver,\n                        )?;\n                    }\n\n                    for index in &indexes {\n                        emit_parent_index_key_change_checks(\n                            program,\n                            cursor_id,\n                            start,\n                            old_rowid_reg,\n                            rowid_new_reg,\n                            &[fk_ref.clone()],\n                            table_btree,\n                            index.as_ref(),\n                            database_id,\n                            resolver,\n                        )?;\n                    }\n                }\n                RefAct::Cascade | RefAct::SetNull | RefAct::SetDefault => {\n                    // CASCADE, SET NULL, and SET DEFAULT actions are handled by\n                    // fire_fk_update_actions AFTER the parent update (not here).\n                    // This function only handles NO ACTION/RESTRICT checks.\n                }\n            }\n        }\n    } else {\n        // All FKs use NoAction/Restrict - use original counter-based approach\n        let primary_key_is_rowid_alias = table_btree.get_rowid_alias_column().is_some();\n\n        if primary_key_is_rowid_alias || table_btree.primary_key_columns.is_empty() {\n            emit_rowid_pk_change_check(\n                program,\n                &incoming,\n                old_rowid_reg,\n                rowid_set_clause_reg.unwrap_or(old_rowid_reg),\n                database_id,\n                resolver,\n            )?;\n        }\n\n        for index in indexes {\n            emit_parent_index_key_change_checks(\n                program,\n                cursor_id,\n                start,\n                old_rowid_reg,\n                rowid_new_reg,\n                &incoming,\n                table_btree,\n                index.as_ref(),\n                database_id,\n                resolver,\n            )?;\n        }\n    }\n\n    Ok(())\n}\n\n/// Context for FK action execution: holds register info for OLD/NEW parent key values\n#[derive(Debug)]\npub struct FkActionContext {\n    /// Registers containing OLD parent key values (for DELETE and UPDATE)\n    pub old_key_registers: Vec<usize>,\n    /// Registers containing NEW parent key values (for UPDATE only)\n    pub new_key_registers: Option<Vec<usize>>,\n}\n\nimpl FkActionContext {\n    pub fn new_for_delete(old_key_registers: Vec<usize>) -> Self {\n        Self {\n            old_key_registers,\n            new_key_registers: None,\n        }\n    }\n\n    pub fn new_for_update(old_key_registers: Vec<usize>, new_key_registers: Vec<usize>) -> Self {\n        Self {\n            old_key_registers,\n            new_key_registers: Some(new_key_registers),\n        }\n    }\n}\n\n/// Context for compiling FK action subprograms - maps parameter indices to column values\n#[derive(Debug)]\nstruct FkSubprogramContext {\n    /// Map from column index to parameter index (1-indexed) for OLD key values\n    old_param_start: usize,\n    /// Map from column index to parameter index (1-indexed) for NEW key values (UPDATE only)\n    new_param_start: Option<usize>,\n}\n\nimpl FkSubprogramContext {\n    fn new(num_cols: usize, has_new: bool) -> Self {\n        Self {\n            old_param_start: 1,\n            new_param_start: if has_new { Some(num_cols + 1) } else { None },\n        }\n    }\n\n    fn old_param_index(&self, col_idx: usize) -> NonZero<usize> {\n        NonZero::new(self.old_param_start + col_idx).expect(\"param index should be non-zero\")\n    }\n\n    fn new_param_index(&self, col_idx: usize) -> Option<NonZero<usize>> {\n        self.new_param_start\n            .map(|start| NonZero::new(start + col_idx).expect(\"param index should be non-zero\"))\n    }\n}\n\n/// Decode FK key registers in-place for custom type columns.\n/// FK action subprograms are compiled as normal SQL (via translate_inner), which\n/// means column reads in the WHERE clause apply decode automatically. Therefore,\n/// the parameter values passed to subprograms must also be in decoded (user-facing)\n/// form for the comparison to match.\nfn decode_fk_key_registers(\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    parent_bt: &BTreeTable,\n    parent_cols: &[String],\n    key_start: usize,\n) -> Result<()> {\n    for (i, pcol) in parent_cols.iter().enumerate() {\n        if let Some((_, col)) = parent_bt.get_column(pcol) {\n            let reg = key_start + i;\n            super::expr::emit_user_facing_column_value(\n                program, reg, reg, col, true, // custom types require STRICT tables\n                resolver,\n            )?;\n        }\n    }\n    Ok(())\n}\n\n/// Get the parent column names for an FK reference.\n/// Uses primary key columns if parent_columns is empty.\n#[inline]\nfn get_fk_parent_cols(fk_ref: &ResolvedFkRef, parent_bt: &BTreeTable) -> Vec<String> {\n    if fk_ref.fk.parent_columns.is_empty() {\n        parent_bt\n            .primary_key_columns\n            .iter()\n            .map(|(n, _)| n.clone())\n            .collect()\n    } else {\n        fk_ref.fk.parent_columns.clone()\n    }\n}\n\n/// Copy key values from value registers into destination registers.\n/// Handles rowid aliasing for columns that are rowid aliases.\nfn copy_key_from_values(\n    program: &mut ProgramBuilder,\n    parent_bt: &BTreeTable,\n    parent_cols: &[String],\n    values_start: usize,\n    rowid_reg: usize,\n    dest_start: usize,\n) -> Result<()> {\n    for (i, pcol) in parent_cols.iter().enumerate() {\n        let src = if ROWID_STRS.iter().any(|s| pcol.eq_ignore_ascii_case(s)) {\n            rowid_reg\n        } else {\n            let (pos, col) = parent_bt\n                .get_column(pcol)\n                .ok_or_else(|| LimboError::InternalError(format!(\"col {pcol} missing\")))?;\n            if col.is_rowid_alias() {\n                rowid_reg\n            } else {\n                values_start + pos\n            }\n        };\n        program.emit_insn(Insn::Copy {\n            src_reg: src,\n            dst_reg: dest_start + i,\n            extra_amount: 0,\n        });\n    }\n    Ok(())\n}\n\n/// Emit instructions to detect if key values have changed between old and new registers.\n/// Jumps to `skip_label` if all values are equal, falls through to `changed_label` if any differ.\nfn emit_key_change_check(\n    program: &mut ProgramBuilder,\n    old_key_start: usize,\n    new_key_start: usize,\n    ncols: usize,\n    skip_label: BranchOffset,\n    changed_label: BranchOffset,\n) {\n    for i in 0..ncols {\n        let next = if i + 1 == ncols {\n            None\n        } else {\n            Some(program.allocate_label())\n        };\n        program.emit_insn(Insn::Eq {\n            lhs: old_key_start + i,\n            rhs: new_key_start + i,\n            target_pc: next.unwrap_or(skip_label),\n            flags: CmpInsFlags::default(),\n            collation: None,\n        });\n        program.emit_insn(Insn::Goto {\n            target_pc: changed_label,\n        });\n        if let Some(n) = next {\n            program.preassign_label_to_next_insn(n);\n        }\n    }\n}\n\n/// Common options for FK action subprogram builders.\nconst FK_SUBPROGRAM_OPTS: ProgramBuilderOpts = ProgramBuilderOpts {\n    num_cursors: 2,\n    approx_num_insns: 32,\n    approx_num_labels: 4,\n};\n\n/// Compile and emit an FK action as a sub-program.\n/// This is the common implementation for CASCADE DELETE, SET NULL, SET DEFAULT, and CASCADE UPDATE.\nfn emit_fk_action_subprogram(\n    program: &mut ProgramBuilder,\n    resolver: &mut Resolver,\n    connection: &Arc<Connection>,\n    stmt: ast::Stmt,\n    ctx: &FkActionContext,\n    description: &'static str,\n) -> Result<()> {\n    let mut subprogram_builder = ProgramBuilder::new_for_subprogram(\n        QueryMode::Normal,\n        program.capture_data_changes_info().clone(),\n        FK_SUBPROGRAM_OPTS,\n    );\n    subprogram_builder.prologue();\n    translate_inner(\n        stmt,\n        resolver,\n        &mut subprogram_builder,\n        connection,\n        description,\n    )?;\n    subprogram_builder.epilogue(resolver.schema());\n    let built_subprogram = subprogram_builder.build(connection.clone(), true, description)?;\n\n    // Build params: OLD key register indices, then optionally NEW key register indices\n    let mut params: Vec<Value> = ctx\n        .old_key_registers\n        .iter()\n        .copied()\n        .map(|reg_idx| Value::from_i64(reg_idx as i64))\n        .collect();\n\n    if let Some(new_regs) = &ctx.new_key_registers {\n        params.extend(\n            new_regs\n                .iter()\n                .copied()\n                .map(|reg_idx| Value::from_i64(reg_idx as i64)),\n        );\n    }\n\n    // FK action subprograms can't contain RAISE(IGNORE), so ignore_jump_target\n    // is a no-op that resolves to the next instruction (just falls through).\n    let ignore_jump_target = program.allocate_label();\n    program.emit_insn(Insn::Program {\n        params,\n        program: built_subprogram.prepared().clone(),\n        ignore_jump_target,\n    });\n    program.preassign_label_to_next_insn(ignore_jump_target);\n\n    Ok(())\n}\n\n/// Build a QualifiedName with db_name set for non-main databases.\nfn qualified_table_name(table_name: &str, db_name: Option<&str>) -> QualifiedName {\n    QualifiedName {\n        db_name: db_name.map(Name::from_string),\n        name: Name::from_string(table_name),\n        alias: None,\n    }\n}\n\n/// Generate a DELETE statement AST for CASCADE DELETE:\n/// DELETE FROM child_table WHERE fk_col1 = ?1 AND fk_col2 = ?2 ...\nfn generate_cascade_delete_stmt(\n    child_table: &str,\n    child_cols: &[String],\n    ctx: &FkSubprogramContext,\n    db_name: Option<&str>,\n) -> ast::Stmt {\n    ast::Stmt::Delete {\n        with: None,\n        tbl_name: qualified_table_name(child_table, db_name),\n        indexed: None,\n        where_clause: Some(Box::new(build_fk_match_where_clause(child_cols, ctx))),\n        returning: vec![],\n        order_by: vec![],\n        limit: None,\n    }\n}\n\n/// Generate an UPDATE statement AST for SET NULL:\n/// UPDATE child_table SET fk_col1 = NULL, fk_col2 = NULL ... WHERE fk_col1 = ?1 AND fk_col2 = ?2 ...\nfn generate_set_null_stmt(\n    child_table: &str,\n    child_cols: &[String],\n    ctx: &FkSubprogramContext,\n    db_name: Option<&str>,\n) -> ast::Stmt {\n    // Build SET clause: fk_col1 = NULL, fk_col2 = NULL ...\n    let sets: Vec<ast::Set> = child_cols\n        .iter()\n        .map(|col| ast::Set {\n            col_names: vec![Name::from_string(col)],\n            expr: Box::new(Expr::Literal(Literal::Null)),\n        })\n        .collect();\n    ast::Stmt::Update(ast::Update {\n        with: None,\n        or_conflict: None,\n        tbl_name: qualified_table_name(child_table, db_name),\n        indexed: None,\n        sets,\n        from: None,\n        where_clause: Some(Box::new(build_fk_match_where_clause(child_cols, ctx))),\n        returning: vec![],\n        order_by: vec![],\n        limit: None,\n    })\n}\n\n/// Generate an UPDATE statement AST for SET DEFAULT:\n/// UPDATE child_table SET fk_col1 = default1, fk_col2 = default2 ... WHERE fk_col1 = ?old1 AND fk_col2 = ?old2 ...\nfn generate_set_default_stmt(\n    child_table: &BTreeTable,\n    child_cols: &[String],\n    ctx: &FkSubprogramContext,\n    db_name: Option<&str>,\n) -> ast::Stmt {\n    // Build SET clause: if no default is defined for a column, we use NULL\n    let sets: Vec<ast::Set> = child_cols\n        .iter()\n        .map(|col| {\n            let default_expr = child_table\n                .get_column(col)\n                .and_then(|(_, c)| c.default.as_ref())\n                .map(|d| (**d).clone())\n                .unwrap_or(Expr::Literal(Literal::Null));\n            ast::Set {\n                col_names: vec![Name::from_string(col)],\n                expr: Box::new(default_expr),\n            }\n        })\n        .collect();\n\n    ast::Stmt::Update(ast::Update {\n        with: None,\n        or_conflict: None,\n        tbl_name: qualified_table_name(&child_table.name, db_name),\n        indexed: None,\n        sets,\n        from: None,\n        where_clause: Some(Box::new(build_fk_match_where_clause(child_cols, ctx))),\n        returning: vec![],\n        order_by: vec![],\n        limit: None,\n    })\n}\n\n/// Generate an UPDATE statement AST for CASCADE UPDATE:\n/// UPDATE child_table SET fk_col1 = ?new1, fk_col2 = ?new2 ... WHERE fk_col1 = ?old1 AND fk_col2 = ?old2 ...\nfn generate_cascade_update_stmt(\n    child_table: &str,\n    child_cols: &[String],\n    ctx: &FkSubprogramContext,\n    db_name: Option<&str>,\n) -> ast::Stmt {\n    // Build SET clause\n    let sets: Vec<ast::Set> = child_cols\n        .iter()\n        .enumerate()\n        .map(|(i, col)| {\n            let param_idx = ctx\n                .new_param_index(i)\n                .expect(\"new params required for cascade update\");\n            ast::Set {\n                col_names: vec![Name::from_string(col)],\n                expr: Box::new(Expr::Variable(ast::Variable::indexed(\n                    u32::try_from(param_idx.get())\n                        .ok()\n                        .and_then(std::num::NonZeroU32::new)\n                        .expect(\"fk parameter index must fit into NonZeroU32\"),\n                ))),\n            }\n        })\n        .collect();\n\n    let where_clause = build_fk_match_where_clause(child_cols, ctx);\n    ast::Stmt::Update(ast::Update {\n        with: None,\n        or_conflict: None,\n        tbl_name: qualified_table_name(child_table, db_name),\n        indexed: None,\n        sets,\n        from: None,\n        where_clause: Some(Box::new(where_clause)),\n        returning: vec![],\n        order_by: vec![],\n        limit: None,\n    })\n}\n\n/// Build a WHERE clause that matches FK columns to parameter values:\n/// fk_col1 = ?1 AND fk_col2 = ?2 ...\nfn build_fk_match_where_clause(child_cols: &[String], ctx: &FkSubprogramContext) -> Expr {\n    let mut conditions: Vec<Expr> = Vec::with_capacity(child_cols.len());\n\n    for (i, col) in child_cols.iter().enumerate() {\n        let param_idx = ctx.old_param_index(i);\n        let cond = Expr::Binary(\n            Box::new(Expr::Id(Name::from_string(col))),\n            ast::Operator::Equals,\n            Box::new(Expr::Variable(ast::Variable::indexed(\n                u32::try_from(param_idx.get())\n                    .ok()\n                    .and_then(std::num::NonZeroU32::new)\n                    .expect(\"fk parameter index must fit into NonZeroU32\"),\n            ))),\n        );\n        conditions.push(cond);\n    }\n\n    // Combine the clauses with AND\n    if conditions.len() == 1 {\n        conditions.remove(0)\n    } else {\n        conditions\n            .into_iter()\n            .reduce(|acc, cond| Expr::Binary(Box::new(acc), ast::Operator::And, Box::new(cond)))\n            .expect(\"at least one condition\")\n    }\n}\n\n/// Compile and emit an FK CASCADE DELETE action as a sub-program.\n/// This creates a sub-program that deletes all child rows matching the parent key.\nfn fire_fk_cascade_delete(\n    program: &mut ProgramBuilder,\n    resolver: &mut Resolver,\n    fk_ref: &ResolvedFkRef,\n    connection: &Arc<Connection>,\n    ctx: &FkActionContext,\n    database_id: usize,\n) -> Result<()> {\n    let db_name = if database_id != crate::MAIN_DB_ID {\n        resolver.get_database_name_by_index(database_id)\n    } else {\n        None\n    };\n    let child_cols = &fk_ref.fk.child_columns;\n    let subprog_ctx = FkSubprogramContext::new(child_cols.len(), false);\n    let stmt = generate_cascade_delete_stmt(\n        &fk_ref.child_table.name,\n        child_cols,\n        &subprog_ctx,\n        db_name.as_deref(),\n    );\n    emit_fk_action_subprogram(\n        program,\n        resolver,\n        connection,\n        stmt,\n        ctx,\n        \"fk cascade delete\",\n    )\n}\n\n/// Compile and emit an FK SET NULL action as a sub-program.\n/// This creates a sub-program that sets FK columns to NULL for all matching child rows.\nfn fire_fk_set_null(\n    program: &mut ProgramBuilder,\n    resolver: &mut Resolver,\n    fk_ref: &ResolvedFkRef,\n    connection: &Arc<Connection>,\n    ctx: &FkActionContext,\n    database_id: usize,\n) -> Result<()> {\n    let db_name = if database_id != crate::MAIN_DB_ID {\n        resolver.get_database_name_by_index(database_id)\n    } else {\n        None\n    };\n    let child_cols = &fk_ref.fk.child_columns;\n    let subprog_ctx = FkSubprogramContext::new(child_cols.len(), false);\n    let stmt = generate_set_null_stmt(\n        &fk_ref.child_table.name,\n        child_cols,\n        &subprog_ctx,\n        db_name.as_deref(),\n    );\n    emit_fk_action_subprogram(program, resolver, connection, stmt, ctx, \"fk set null\")\n}\n\n/// Compile and emit an FK SET DEFAULT action as a sub-program.\n/// This creates a sub-program that sets FK columns to their default values for all matching child rows.\nfn fire_fk_set_default(\n    program: &mut ProgramBuilder,\n    resolver: &mut Resolver,\n    fk_ref: &ResolvedFkRef,\n    connection: &Arc<Connection>,\n    ctx: &FkActionContext,\n    database_id: usize,\n) -> Result<()> {\n    let db_name = if database_id != crate::MAIN_DB_ID {\n        resolver.get_database_name_by_index(database_id)\n    } else {\n        None\n    };\n    let child_cols = &fk_ref.fk.child_columns;\n    let subprog_ctx = FkSubprogramContext::new(child_cols.len(), false);\n    let stmt = generate_set_default_stmt(\n        &fk_ref.child_table,\n        child_cols,\n        &subprog_ctx,\n        db_name.as_deref(),\n    );\n    emit_fk_action_subprogram(program, resolver, connection, stmt, ctx, \"fk set default\")\n}\n\n/// Compile and emit an FK CASCADE UPDATE action as a sub-program.\n/// This creates a sub-program that updates FK columns to new values for all matching child rows.\nfn fire_fk_cascade_update(\n    program: &mut ProgramBuilder,\n    resolver: &mut Resolver,\n    fk_ref: &ResolvedFkRef,\n    connection: &Arc<Connection>,\n    ctx: &FkActionContext,\n    database_id: usize,\n) -> Result<()> {\n    let db_name = if database_id != crate::MAIN_DB_ID {\n        resolver.get_database_name_by_index(database_id)\n    } else {\n        None\n    };\n    let child_cols = &fk_ref.fk.child_columns;\n    // CASCADE UPDATE needs new params for the SET clause\n    let subprog_ctx = FkSubprogramContext::new(child_cols.len(), true);\n    let stmt = generate_cascade_update_stmt(\n        &fk_ref.child_table.name,\n        child_cols,\n        &subprog_ctx,\n        db_name.as_deref(),\n    );\n    emit_fk_action_subprogram(\n        program,\n        resolver,\n        connection,\n        stmt,\n        ctx,\n        \"fk cascade update\",\n    )\n}\n\n/// Fire FK actions for DELETE on parent table using Program opcode.\n/// This is called after the DELETE is performed but before AFTER triggers.\n/// Holds a prepared FK cascade/set-null/set-default action whose parent key\n/// values have already been read into registers.\npub struct PreparedFkDeleteAction {\n    fk_ref: ResolvedFkRef,\n    ctx: FkActionContext,\n}\n\npub struct ForeignKeyActions<T>(Vec<T>);\n\nimpl<T> Default for ForeignKeyActions<T> {\n    fn default() -> Self {\n        Self(Vec::new())\n    }\n}\n\nimpl ForeignKeyActions<PreparedFkDeleteAction> {\n    /// Phase 1 of FK delete actions: build parent keys into registers and handle\n    /// NoAction/Restrict checks. Returns prepared actions for CASCADE/SetNull/\n    /// SetDefault that must be fired AFTER the parent row is deleted (step 4 per\n    /// SQLite docs: delete parent row first, then perform FK cascade actions).\n    pub fn prepare_fk_delete_actions(\n        program: &mut ProgramBuilder,\n        resolver: &mut Resolver,\n        parent_table_name: &str,\n        parent_cursor_id: usize,\n        parent_rowid_reg: usize,\n        database_id: usize,\n    ) -> Result<ForeignKeyActions<PreparedFkDeleteAction>> {\n        let parent_bt = resolver\n            .with_schema(database_id, |s| s.get_btree_table(parent_table_name))\n            .ok_or_else(|| LimboError::InternalError(\"parent not btree\".into()))?;\n\n        let mut prepared = Vec::new();\n\n        for fk_ref in resolver.with_schema(database_id, |s| {\n            s.resolved_fks_referencing(parent_table_name)\n        })? {\n            let parent_cols = get_fk_parent_cols(&fk_ref, &parent_bt);\n            let ncols = parent_cols.len();\n            let key_regs_start = program.alloc_registers(ncols);\n\n            build_parent_key(\n                program,\n                &parent_bt,\n                &parent_cols,\n                parent_cursor_id,\n                parent_rowid_reg,\n                key_regs_start,\n            )?;\n\n            match fk_ref.fk.on_delete {\n                RefAct::NoAction | RefAct::Restrict => {\n                    emit_fk_delete_parent_existence_check_single(\n                        program,\n                        &fk_ref,\n                        &parent_bt,\n                        parent_table_name,\n                        parent_cursor_id,\n                        parent_rowid_reg,\n                        database_id,\n                        resolver,\n                    )?;\n                }\n                RefAct::Cascade | RefAct::SetNull | RefAct::SetDefault => {\n                    // Decode encoded values so they match the subprogram's decoded column reads\n                    decode_fk_key_registers(\n                        program,\n                        resolver,\n                        &parent_bt,\n                        &parent_cols,\n                        key_regs_start,\n                    )?;\n                    let old_key_registers: Vec<usize> =\n                        (key_regs_start..key_regs_start + ncols).collect();\n                    let ctx = FkActionContext::new_for_delete(old_key_registers);\n                    prepared.push(PreparedFkDeleteAction { fk_ref, ctx });\n                }\n            }\n        }\n\n        Ok(ForeignKeyActions(prepared))\n    }\n\n    /// Phase 2 of FK delete actions: fire CASCADE/SetNull/SetDefault sub-programs.\n    /// Must be called AFTER the parent row is deleted from the B-tree.\n    pub fn fire_prepared_fk_delete_actions(\n        self,\n        program: &mut ProgramBuilder,\n        resolver: &mut Resolver,\n        connection: &Arc<Connection>,\n        database_id: usize,\n    ) -> Result<()> {\n        let prepared = self.0;\n        if prepared.is_empty() {\n            return Ok(());\n        }\n        for action in prepared {\n            match action.fk_ref.fk.on_delete {\n                RefAct::Cascade => {\n                    fire_fk_cascade_delete(\n                        program,\n                        resolver,\n                        &action.fk_ref,\n                        connection,\n                        &action.ctx,\n                        database_id,\n                    )?;\n                }\n                RefAct::SetNull => {\n                    fire_fk_set_null(\n                        program,\n                        resolver,\n                        &action.fk_ref,\n                        connection,\n                        &action.ctx,\n                        database_id,\n                    )?;\n                }\n                RefAct::SetDefault => {\n                    fire_fk_set_default(\n                        program,\n                        resolver,\n                        &action.fk_ref,\n                        connection,\n                        &action.ctx,\n                        database_id,\n                    )?;\n                }\n                _ => unreachable!(),\n            }\n        }\n\n        Ok(())\n    }\n}\n\n/// Fire FK actions for UPDATE on parent table using Program opcode.\n/// This is called after the UPDATE is performed but before AFTER triggers.\n/// `old_values_start` is the register where OLD column values are stored (loaded before Delete+Insert).\n#[allow(clippy::too_many_arguments)]\npub fn fire_fk_update_actions(\n    program: &mut ProgramBuilder,\n    resolver: &mut Resolver,\n    parent_table_name: &str,\n    old_rowid_reg: usize,\n    old_values_start: usize,\n    new_values_start: usize,\n    new_rowid_reg: usize,\n    connection: &Arc<Connection>,\n    database_id: usize,\n) -> Result<()> {\n    let parent_bt = resolver\n        .with_schema(database_id, |s| s.get_btree_table(parent_table_name))\n        .ok_or_else(|| LimboError::InternalError(\"parent not btree\".into()))?;\n\n    for fk_ref in resolver.with_schema(database_id, |s| {\n        s.resolved_fks_referencing(parent_table_name)\n    })? {\n        let parent_cols = get_fk_parent_cols(&fk_ref, &parent_bt);\n        let ncols = parent_cols.len();\n\n        // Copy OLD and NEW parent key values using the helper\n        let old_key_start = program.alloc_registers(ncols);\n        copy_key_from_values(\n            program,\n            &parent_bt,\n            &parent_cols,\n            old_values_start,\n            old_rowid_reg,\n            old_key_start,\n        )?;\n\n        let new_key_start = program.alloc_registers(ncols);\n        copy_key_from_values(\n            program,\n            &parent_bt,\n            &parent_cols,\n            new_values_start,\n            new_rowid_reg,\n            new_key_start,\n        )?;\n\n        // Decode encoded values so they match the subprogram's decoded column reads\n        decode_fk_key_registers(program, resolver, &parent_bt, &parent_cols, old_key_start)?;\n        decode_fk_key_registers(program, resolver, &parent_bt, &parent_cols, new_key_start)?;\n\n        let old_key_registers: Vec<usize> = (old_key_start..old_key_start + ncols).collect();\n        let new_key_registers: Vec<usize> = (new_key_start..new_key_start + ncols).collect();\n\n        // Check if parent key changed - skip action if all values are equal\n        let skip_action = program.allocate_label();\n        let key_changed = program.allocate_label();\n        emit_key_change_check(\n            program,\n            old_key_start,\n            new_key_start,\n            ncols,\n            skip_action,\n            key_changed,\n        );\n\n        program.preassign_label_to_next_insn(key_changed);\n\n        let ctx = FkActionContext::new_for_update(old_key_registers, new_key_registers);\n\n        match fk_ref.fk.on_update {\n            RefAct::NoAction | RefAct::Restrict => {\n                // NO ACTION/RESTRICT checks are handled by emit_fk_update_parent_actions\n                // which is called BEFORE the update using the counter-based approach.\n            }\n            RefAct::Cascade => {\n                fire_fk_cascade_update(program, resolver, &fk_ref, connection, &ctx, database_id)?;\n            }\n            RefAct::SetNull => {\n                fire_fk_set_null(program, resolver, &fk_ref, connection, &ctx, database_id)?;\n            }\n            RefAct::SetDefault => {\n                fire_fk_set_default(program, resolver, &fk_ref, connection, &ctx, database_id)?;\n            }\n        }\n\n        program.preassign_label_to_next_insn(skip_action);\n    }\n\n    Ok(())\n}\n\n/// Emit pre-DROP FK checks and actions for a table.\n///\n/// SQLite's algorithm:\n/// 1. Collect all parent rowids into a RowSet for iteration\n/// 2. For each parent rowid:\n///    - Seek to parent row and extract parent key column values\n///    - Based on the ON DELETE action:\n///      - RESTRICT/NO ACTION: Scan child table for matching FK values and count violations\n///      - CASCADE/SET NULL/SET DEFAULT: Take appropriate action for child rows that reference the parent\n///\n/// Only fails if a child row actually references an existing parent row being deleted\n/// and the action is RESTRICT/NO ACTION. Correctly handles orphaned FK values.\npub fn emit_fk_drop_table_check(\n    program: &mut ProgramBuilder,\n    resolver: &mut Resolver,\n    parent_table_name: &str,\n    connection: &Arc<Connection>,\n    database_id: usize,\n) -> Result<()> {\n    let parent_tbl = resolver\n        .with_schema(database_id, |s| s.get_btree_table(parent_table_name))\n        .ok_or_else(|| {\n            LimboError::InternalError(format!(\"parent table {parent_table_name} not found\"))\n        })?;\n\n    // Get all FK references to this parent table\n    let fk_refs = resolver.with_schema(database_id, |s| {\n        s.resolved_fks_referencing(parent_table_name)\n    })?;\n\n    if fk_refs.is_empty() {\n        return Ok(());\n    }\n\n    // Separate FK refs by action type:\n    // - action_fk_refs: CASCADE, SET NULL, SET DEFAULT - need to fire action subprograms\n    // - check_fk_refs: RESTRICT, NO ACTION - need violation counting\n    let action_fk_refs: Vec<_> = fk_refs\n        .iter()\n        .filter(|fk| {\n            matches!(\n                fk.fk.on_delete,\n                RefAct::Cascade | RefAct::SetNull | RefAct::SetDefault\n            )\n        })\n        .collect();\n    let check_fk_refs: Vec<_> = fk_refs\n        .iter()\n        .filter(|fk| matches!(fk.fk.on_delete, RefAct::Restrict | RefAct::NoAction))\n        .collect();\n\n    // Collect all parent rowids into a RowSet\n    // r[rowset_reg] = NULL (initializes RowSet)\n    let rowset_reg = program.alloc_register();\n    program.emit_null(rowset_reg, None);\n\n    let parent_cur = open_read_table(program, &parent_tbl, database_id);\n    let collect_done = program.allocate_label();\n\n    program.emit_insn(Insn::Rewind {\n        cursor_id: parent_cur,\n        pc_if_empty: collect_done,\n    });\n\n    let collect_loop = program.allocate_label();\n    program.preassign_label_to_next_insn(collect_loop);\n\n    // Get parent rowid and add to RowSet\n    let parent_rowid_reg = program.alloc_register();\n    program.emit_insn(Insn::RowId {\n        cursor_id: parent_cur,\n        dest: parent_rowid_reg,\n    });\n    program.emit_insn(Insn::RowSetAdd {\n        rowset_reg,\n        value_reg: parent_rowid_reg,\n    });\n\n    program.emit_insn(Insn::Next {\n        cursor_id: parent_cur,\n        pc_if_next: collect_loop,\n    });\n\n    program.preassign_label_to_next_insn(collect_done);\n    program.emit_insn(Insn::Close {\n        cursor_id: parent_cur,\n    });\n\n    // For each parent rowid, check/execute FK actions\n    let parent_write_cur = program.alloc_cursor_id(CursorType::BTreeTable(parent_tbl.clone()));\n    program.emit_insn(Insn::OpenWrite {\n        cursor_id: parent_write_cur,\n        root_page: parent_tbl.root_page.into(),\n        db: database_id,\n    });\n\n    let rowset_done = program.allocate_label();\n    let rowset_loop = program.allocate_label();\n    program.preassign_label_to_next_insn(rowset_loop);\n    // Read next rowid from RowSet\n    let current_rowid_reg = program.alloc_register();\n    program.emit_insn(Insn::RowSetRead {\n        rowset_reg,\n        pc_if_empty: rowset_done,\n        dest_reg: current_rowid_reg,\n    });\n\n    // Verify row still exists, jumps if not found\n    let skip_row = program.allocate_label();\n    program.emit_insn(Insn::NotExists {\n        cursor: parent_write_cur,\n        rowid_reg: current_rowid_reg,\n        target_pc: skip_row,\n    });\n\n    // Fire FK actions for CASCADE, SET NULL, SET DEFAULT\n    for fk_ref in &action_fk_refs {\n        let parent_cols = get_fk_parent_cols(fk_ref, &parent_tbl);\n        let ncols = parent_cols.len();\n        let key_regs_start = program.alloc_registers(ncols);\n\n        build_parent_key(\n            program,\n            &parent_tbl,\n            &parent_cols,\n            parent_write_cur,\n            current_rowid_reg,\n            key_regs_start,\n        )?;\n\n        // Decode encoded values so they match the subprogram's decoded column reads\n        decode_fk_key_registers(program, resolver, &parent_tbl, &parent_cols, key_regs_start)?;\n\n        let old_key_registers: Vec<usize> = (key_regs_start..key_regs_start + ncols).collect();\n        let ctx = FkActionContext::new_for_delete(old_key_registers);\n\n        match fk_ref.fk.on_delete {\n            RefAct::Cascade => {\n                fire_fk_cascade_delete(program, resolver, fk_ref, connection, &ctx, database_id)?;\n            }\n            RefAct::SetNull => {\n                fire_fk_set_null(program, resolver, fk_ref, connection, &ctx, database_id)?;\n            }\n            RefAct::SetDefault => {\n                fire_fk_set_default(program, resolver, fk_ref, connection, &ctx, database_id)?;\n            }\n            RefAct::NoAction | RefAct::Restrict => {\n                // These are handled below in the check_fk_refs loop\n            }\n        }\n    }\n\n    // For RESTRICT/NO ACTION FKs, scan child table for matching rows and count violations\n    for fk_ref in &check_fk_refs {\n        let child_tbl = &fk_ref.child_table;\n        let child_cols = &fk_ref.fk.child_columns;\n\n        // Determine which parent columns are referenced\n        let parent_cols = get_fk_parent_cols(fk_ref, &parent_tbl);\n        let ncols = parent_cols.len();\n\n        // Build the parent key vector from the current parent row\n        let parent_key_start = program.alloc_registers(ncols);\n        build_parent_key(\n            program,\n            &parent_tbl,\n            &parent_cols,\n            parent_write_cur,\n            current_rowid_reg,\n            parent_key_start,\n        )?;\n\n        // Scan child table for matching rows\n        let child_cur = open_read_table(program, child_tbl, database_id);\n        let child_done = program.allocate_label();\n\n        program.emit_insn(Insn::Rewind {\n            cursor_id: child_cur,\n            pc_if_empty: child_done,\n        });\n\n        let child_loop = program.allocate_label();\n        program.preassign_label_to_next_insn(child_loop);\n        let child_next = program.allocate_label();\n\n        // Compare each FK column to corresponding parent key column\n        // All columns must match for a violation\n        for (i, cname) in child_cols.iter().enumerate() {\n            let (pos, _) = child_tbl\n                .get_column(cname)\n                .ok_or_else(|| LimboError::InternalError(format!(\"child col {cname} missing\")))?;\n\n            let child_val_reg = program.alloc_register();\n            program.emit_insn(Insn::Column {\n                cursor_id: child_cur,\n                column: pos,\n                dest: child_val_reg,\n                default: None,\n            });\n            // If child FK column is NULL, skip (no reference)\n            program.emit_insn(Insn::IsNull {\n                reg: child_val_reg,\n                target_pc: child_next,\n            });\n\n            // Compare child FK column to corresponding parent key column\n            program.emit_insn(Insn::Ne {\n                lhs: child_val_reg,\n                rhs: parent_key_start + i,\n                target_pc: child_next,\n                flags: CmpInsFlags::default().jump_if_null(),\n                collation: Some(CollationSeq::Binary),\n            });\n        }\n\n        // If we reach here, all FK columns match: increment violation counter\n        program.emit_insn(Insn::FkCounter {\n            increment_value: 1,\n            deferred: false,\n        });\n\n        program.preassign_label_to_next_insn(child_next);\n        program.emit_insn(Insn::Next {\n            cursor_id: child_cur,\n            pc_if_next: child_loop,\n        });\n\n        program.preassign_label_to_next_insn(child_done);\n        program.emit_insn(Insn::Close {\n            cursor_id: child_cur,\n        });\n    }\n\n    // Note: SQLite deletes the parent row here, but we skip that since\n    // the actual deletion happens later in the DROP TABLE logic\n    program.preassign_label_to_next_insn(skip_row);\n    program.emit_insn(Insn::Goto {\n        target_pc: rowset_loop,\n    });\n\n    // After processing all rows, check if there were any violations\n    program.preassign_label_to_next_insn(rowset_done);\n    program.emit_insn(Insn::Close {\n        cursor_id: parent_write_cur,\n    });\n\n    // Only check for violations if there are RESTRICT/NO ACTION FKs\n    if !check_fk_refs.is_empty() {\n        // FkIfZero: if counter == 0, skip the halt\n        let no_violations = program.allocate_label();\n        program.emit_insn(Insn::FkIfZero {\n            deferred: false,\n            target_pc: no_violations,\n        });\n\n        // There were violations, halt with FK error\n        emit_fk_restrict_halt(program)?;\n        program.preassign_label_to_next_insn(no_violations);\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "core/translate/group_by.rs",
    "content": "use turso_parser::ast::{self, SortOrder};\n\nuse super::{\n    emitter::TranslateCtx,\n    expr::{translate_condition_expr, translate_expr, ConditionMetadata},\n    plan::{Distinctness, GroupBy, SelectPlan, SubqueryEvalPhase, SubqueryOrigin},\n    result_row::emit_select_result,\n};\nuse crate::translate::{\n    aggregation::{translate_aggregation_step, AggArgumentSource},\n    order_by::{custom_type_comparator, EmitOrderBy},\n    plan::{Aggregate, NonFromClauseSubquery},\n    subquery::emit_non_from_clause_subqueries_for_phase,\n};\nuse crate::translate::{\n    emitter::Resolver,\n    expr::{walk_expr, WalkControl},\n    optimizer::Optimizable,\n};\nuse crate::{\n    emit_explain,\n    schema::PseudoCursorType,\n    translate::collate::{get_collseq_from_expr, CollationSeq},\n    util::exprs_are_equivalent,\n    vdbe::{\n        builder::{CursorType, ProgramBuilder},\n        insn::Insn,\n        BranchOffset,\n    },\n    Result,\n};\nuse crate::{translate::plan::ResultSetColumn, types::KeyInfo};\n\n/// Labels needed for various jumps in GROUP BY handling.\n#[derive(Debug)]\npub struct GroupByLabels {\n    /// Label for the subroutine that clears the accumulator registers (temporary storage for per-group aggregate calculations)\n    pub label_subrtn_acc_clear: BranchOffset,\n    /// Label for the subroutine that outputs the current group's data\n    pub label_subrtn_acc_output: BranchOffset,\n    /// Label for the instruction that sets the accumulator indicator to true (indicating data exists in the accumulator for the current group)\n    pub label_acc_indicator_set_flag_true: BranchOffset,\n    /// Label for the instruction that jumps to the end of the grouping process without emitting a row\n    pub label_group_by_end_without_emitting_row: BranchOffset,\n    /// Label for the instruction that jumps to the end of the grouping process\n    pub label_agg_final: BranchOffset,\n    /// Label for the instruction that jumps to the end of the grouping process\n    pub label_group_by_end: BranchOffset,\n    /// Label for the instruction that jumps to the start of the loop that processed sorted data for GROUP BY.\n    /// Not relevant for cases where the data is already sorted.\n    pub label_sort_loop_start: BranchOffset,\n    /// Label for the instruction that jumps to the end of the loop that processed sorted data for GROUP BY.\n    /// Not relevant for cases where the data is already sorted.\n    pub label_sort_loop_end: BranchOffset,\n    /// Label for the instruction that jumps to the start of the aggregation step\n    pub label_grouping_agg_step: BranchOffset,\n}\n\n/// Registers allocated for GROUP BY operations.\n#[derive(Debug)]\npub struct GroupByRegisters {\n    pub reg_group_by_source_cols_start: usize,\n    /// Register holding the return offset for the accumulator clear subroutine\n    pub reg_subrtn_acc_clear_return_offset: usize,\n    /// Register holding a flag to abort the grouping process if necessary\n    pub reg_abort_flag: usize,\n    /// Register holding the start of the non aggregate query members (all columns except aggregate arguments)\n    pub reg_non_aggregate_exprs_acc: usize,\n    /// Register holding the return offset for the accumulator output subroutine\n    pub reg_subrtn_acc_output_return_offset: usize,\n    /// Register holding a flag to indicate if data exists in the accumulator for the current group\n    pub reg_data_in_acc_flag: usize,\n    /// Starting index of the register(s) that hold the comparison result between the current row and the previous row\n    /// The comparison result is used to determine if the current row belongs to the same group as the previous row\n    /// Each group by expression has a corresponding register\n    pub reg_group_exprs_cmp: usize,\n}\n\n// Metadata for handling GROUP BY operations\n#[derive(Debug)]\npub struct GroupByMetadata {\n    // Source of rows for the GROUP BY operation - either a sorter or the main loop itself, incase the rows are already sorted in GROUP BY required order\n    pub row_source: GroupByRowSource,\n    pub labels: GroupByLabels,\n    pub registers: GroupByRegisters,\n}\n\npub struct EmitGroupBy;\nimpl EmitGroupBy {\n    /// Initialize resources needed for GROUP BY processing\n    pub fn init<'a>(\n        program: &mut ProgramBuilder,\n        t_ctx: &mut TranslateCtx<'a>,\n        group_by: &'a GroupBy,\n        plan: &SelectPlan,\n        result_columns: &'a [ResultSetColumn],\n        order_by: &'a [(Box<ast::Expr>, ast::SortOrder)],\n    ) -> Result<()> {\n        collect_non_aggregate_expressions(\n            &mut t_ctx.non_aggregate_expressions,\n            group_by,\n            plan,\n            result_columns,\n            order_by,\n        )?;\n\n        let label_subrtn_acc_output = program.allocate_label();\n        let label_group_by_end_without_emitting_row = program.allocate_label();\n        let label_acc_indicator_set_flag_true = program.allocate_label();\n        let label_agg_final = program.allocate_label();\n        let label_group_by_end = program.allocate_label();\n        let label_subrtn_acc_clear = program.allocate_label();\n        let label_sort_loop_start = program.allocate_label();\n        let label_sort_loop_end = program.allocate_label();\n        let label_grouping_agg_step = program.allocate_label();\n\n        let reg_subrtn_acc_output_return_offset = program.alloc_register();\n        let reg_data_in_acc_flag = program.alloc_register();\n        let reg_abort_flag = program.alloc_register();\n        let reg_group_exprs_cmp = program.alloc_registers(group_by.exprs.len());\n\n        // The following two blocks of registers should always be allocated contiguously,\n        // because they are cleared in a contiguous block in the GROUP BYs clear accumulator subroutine.\n        // START BLOCK\n        let reg_non_aggregate_exprs_acc =\n            program.alloc_registers(t_ctx.non_aggregate_expressions.len());\n        if !plan.aggregates.is_empty() {\n            // Aggregate registers need to be NULLed at the start because the same registers might be reused on another invocation of a subquery,\n            // and if they are not NULLed, the 2nd invocation of the same subquery will have values left over from the first invocation.\n            t_ctx.reg_agg_start =\n                Some(program.alloc_registers_and_init_w_null(plan.aggregates.len()));\n        }\n        // END BLOCK\n\n        let reg_sorter_key = program.alloc_register();\n        let column_count = if !group_by.sort_elided {\n            // Sorter path: store only unique leaf columns from aggregate args\n            // instead of pre-computed expression results.\n            t_ctx.agg_leaf_columns = collect_agg_leaf_columns(&plan.aggregates, plan)?;\n            t_ctx.non_aggregate_expressions.len() + t_ctx.agg_leaf_columns.len()\n        } else {\n            plan.agg_args_count() + t_ctx.non_aggregate_expressions.len()\n        };\n        let reg_group_by_source_cols_start = program.alloc_registers(column_count);\n\n        let row_source = if !group_by.sort_elided {\n            let sort_order = &group_by.sort_order;\n            let sort_cursor = program.alloc_cursor_id(CursorType::Sorter);\n            // Should work the same way as Order By\n            /*\n             * Terms of the ORDER BY clause that is part of a SELECT statement may be assigned a collating sequence using the COLLATE operator,\n             * in which case the specified collating function is used for sorting.\n             * Otherwise, if the expression sorted by an ORDER BY clause is a column,\n             * then the collating sequence of the column is used to determine sort order.\n             * If the expression is not a column and has no COLLATE clause, then the BINARY collating sequence is used.\n             */\n            let order_and_collations: Vec<(SortOrder, Option<CollationSeq>)> = group_by\n                .exprs\n                .iter()\n                .zip(sort_order.iter())\n                .map(|(expr, ord)| {\n                    let collation = get_collseq_from_expr(expr, &plan.table_references)?;\n                    Ok((*ord, collation))\n                })\n                .collect::<Result<Vec<_>>>()?;\n\n            // Resolve custom type comparators for GROUP BY columns (e.g. array_lt).\n            let comparators = group_by\n                .exprs\n                .iter()\n                .map(|expr| {\n                    custom_type_comparator(expr, &plan.table_references, t_ctx.resolver.schema())\n                })\n                .collect();\n\n            program.emit_insn(Insn::SorterOpen {\n                cursor_id: sort_cursor,\n                columns: column_count,\n                order_and_collations,\n                comparators,\n            });\n            emit_explain!(program, false, \"USE SORTER FOR GROUP BY\".to_owned());\n            let pseudo_cursor = group_by_create_pseudo_table(program, column_count);\n            GroupByRowSource::Sorter {\n                pseudo_cursor,\n                sort_cursor,\n                reg_sorter_key,\n                sorter_column_count: column_count,\n                start_reg_dest: reg_non_aggregate_exprs_acc,\n            }\n        } else {\n            GroupByRowSource::MainLoop {\n                start_reg_src: reg_group_by_source_cols_start,\n                start_reg_dest: reg_non_aggregate_exprs_acc,\n            }\n        };\n\n        program.add_comment(program.offset(), \"clear group by abort flag\");\n        program.emit_insn(Insn::Integer {\n            value: 0,\n            dest: reg_abort_flag,\n        });\n\n        program.add_comment(\n            program.offset(),\n            \"initialize group by comparison registers to NULL\",\n        );\n        program.emit_insn(Insn::Null {\n            dest: reg_group_exprs_cmp,\n            dest_end: if group_by.exprs.len() > 1 {\n                Some(reg_group_exprs_cmp + group_by.exprs.len() - 1)\n            } else {\n                None\n            },\n        });\n\n        program.add_comment(program.offset(), \"go to clear accumulator subroutine\");\n\n        let reg_subrtn_acc_clear_return_offset = program.alloc_register();\n        program.emit_insn(Insn::Gosub {\n            target_pc: label_subrtn_acc_clear,\n            return_reg: reg_subrtn_acc_clear_return_offset,\n        });\n\n        t_ctx.meta_group_by = Some(GroupByMetadata {\n            row_source,\n            labels: GroupByLabels {\n                label_subrtn_acc_output,\n                label_group_by_end_without_emitting_row,\n                label_acc_indicator_set_flag_true,\n                label_agg_final,\n                label_group_by_end,\n                label_subrtn_acc_clear,\n                label_sort_loop_start,\n                label_sort_loop_end,\n                label_grouping_agg_step,\n            },\n            registers: GroupByRegisters {\n                reg_subrtn_acc_output_return_offset,\n                reg_data_in_acc_flag,\n                reg_abort_flag,\n                reg_non_aggregate_exprs_acc,\n                reg_group_exprs_cmp,\n                reg_subrtn_acc_clear_return_offset,\n                reg_group_by_source_cols_start,\n            },\n        });\n        Ok(())\n    }\n}\n\n/// Returns whether an ORDER BY expression should be treated as an\n/// aggregate-position term for the purposes of tie-ordering.\n///\n/// We classify an ORDER BY term as \"aggregate or constant\" when:\n/// it is syntactically equivalent to one of the finalized aggregate\n/// expressions for this SELECT (`COUNT(*)`, `SUM(col)`, `MAX(price)`), or\n/// it is a constant literal\n///\n/// Why this matters:\n/// When ORDER BY consists only of aggregates and/or constants, SQLite relies\n/// on the stability of the ORDER BY sorter to preserve the traversal order\n/// of groups established by GROUP BY iteration, and no extra tiebreak\n/// `Sequence` column is appended\npub fn is_orderby_agg_or_const(resolver: &Resolver, e: &ast::Expr, aggs: &[Aggregate]) -> bool {\n    if aggs\n        .iter()\n        .any(|agg| exprs_are_equivalent(&agg.original_expr, e))\n    {\n        return true;\n    }\n    e.is_constant(resolver)\n}\n\n/// Computes the traversal order of GROUP BY keys so that the final\n/// ORDER BY matches SQLite's tie-breaking semantics.\n///\n/// If there are no GROUP BY keys or no ORDER BY terms, all keys default to ascending.\n///\n/// If *every* ORDER BY term is an aggregate or a constant then we mirror the\n/// direction of the first ORDER BY term across all GROUP BY keys.\n///\n/// Otherwise (mixed ORDER BY: at least one non-aggregate, non-constant term),\n/// we try to mirror explicit directions for any GROUP BY expression that\n/// appears in ORDER BY, and the remaining keys default to `ASC`.\npub fn compute_group_by_sort_order(\n    group_by_exprs: &[ast::Expr],\n    order_by: &[(Box<ast::Expr>, SortOrder)],\n    aggs: &[Aggregate],\n    resolver: &Resolver,\n) -> Vec<SortOrder> {\n    let groupby_len = group_by_exprs.len();\n    if groupby_len == 0 || order_by.is_empty() {\n        return vec![SortOrder::Asc; groupby_len];\n    }\n    let only_agg_or_const = order_by\n        .iter()\n        .all(|(e, _)| is_orderby_agg_or_const(resolver, e, aggs));\n\n    if only_agg_or_const {\n        let first_direction = order_by[0].1;\n        return vec![first_direction; groupby_len];\n    }\n\n    let mut result = vec![SortOrder::Asc; groupby_len];\n    for (idx, groupby_expr) in group_by_exprs.iter().enumerate() {\n        if let Some((_, direction)) = order_by\n            .iter()\n            .find(|(expr, _)| exprs_are_equivalent(expr, groupby_expr))\n        {\n            result[idx] = *direction;\n        }\n    }\n    result\n}\n\n/// Extracts unique leaf column references from all aggregate function arguments.\n/// These are the base table columns that aggregate expressions depend on.\n/// By storing only these in the GROUP BY sorter (instead of pre-computed expression\n/// results), we reduce sorter record size and avoid redundant B-tree column reads.\nfn collect_agg_leaf_columns(aggregates: &[Aggregate], plan: &SelectPlan) -> Result<Vec<ast::Expr>> {\n    let mut leaf_columns: Vec<ast::Expr> = Vec::new();\n    for agg in aggregates {\n        for arg in &agg.args {\n            walk_expr(arg, &mut |expr: &ast::Expr| -> Result<WalkControl> {\n                match expr {\n                    ast::Expr::Column { table, .. } | ast::Expr::RowId { table, .. } => {\n                        if plan\n                            .table_references\n                            .find_joined_table_by_internal_id(*table)\n                            .is_some()\n                            && !leaf_columns.iter().any(|e| exprs_are_equivalent(e, expr))\n                        {\n                            leaf_columns.push(expr.clone());\n                        }\n                        Ok(WalkControl::SkipChildren)\n                    }\n                    _ => Ok(WalkControl::Continue),\n                }\n            })?;\n        }\n    }\n    Ok(leaf_columns)\n}\n\nfn collect_non_aggregate_expressions<'a>(\n    non_aggregate_expressions: &mut Vec<(&'a ast::Expr, bool)>,\n    group_by: &'a GroupBy,\n    plan: &SelectPlan,\n    root_result_columns: &'a [ResultSetColumn],\n    order_by: &'a [(Box<ast::Expr>, ast::SortOrder)],\n) -> Result<()> {\n    let mut result_columns = Vec::new();\n    for expr in root_result_columns\n        .iter()\n        .map(|col| &col.expr)\n        .chain(order_by.iter().map(|(e, _)| e.as_ref()))\n        .chain(group_by.having.iter().flatten())\n    {\n        collect_result_columns(expr, plan, &mut result_columns)?;\n    }\n\n    for group_expr in &group_by.exprs {\n        let expr_appears_in_result_columns = result_columns\n            .iter()\n            .any(|expr| exprs_are_equivalent(expr, group_expr))\n            || root_result_columns\n                .iter()\n                .any(|rc| exprs_are_equivalent(&rc.expr, group_expr));\n        non_aggregate_expressions.push((group_expr, expr_appears_in_result_columns));\n    }\n    for expr in result_columns {\n        let in_group_by = group_by\n            .exprs\n            .iter()\n            .any(|group_expr| exprs_are_equivalent(expr, group_expr));\n        if !in_group_by {\n            non_aggregate_expressions.push((expr, true));\n        }\n    }\n    Ok(())\n}\n\n/// Collects columns from different parts of a SELECT that are needed for\n/// GROUP BY.\nfn collect_result_columns<'a>(\n    root_expr: &'a ast::Expr,\n    plan: &SelectPlan,\n    result_columns: &mut Vec<&'a ast::Expr>,\n) -> Result<()> {\n    fn is_deferred_grouped_output_subquery(\n        plan: &SelectPlan,\n        subquery_id: turso_parser::ast::TableInternalId,\n    ) -> bool {\n        plan.non_from_clause_subqueries\n            .iter()\n            .find(|subquery| subquery.internal_id == subquery_id)\n            .is_some_and(|subquery| matches!(subquery.eval_phase, SubqueryEvalPhase::GroupedOutput))\n    }\n\n    walk_expr(root_expr, &mut |expr: &ast::Expr| -> Result<WalkControl> {\n        match expr {\n            ast::Expr::Column { table, .. } | ast::Expr::RowId { table, .. } => {\n                if plan\n                    .table_references\n                    .find_joined_table_by_internal_id(*table)\n                    .is_some()\n                {\n                    result_columns.push(expr);\n                }\n            }\n            // SubqueryResult is an exception because we can't \"extract\" columns from it\n            // unlike other expressions like function calls or direct column references,\n            // so we must add it so that the subquery result gets collected to the GROUP BY\n            // columns.\n            //\n            // However, if the subquery is of the form: 'aggregate_result IN (SELECT...)', we need to skip it because the aggregation\n            // is done later.\n            ast::Expr::SubqueryResult {\n                subquery_id, lhs, ..\n            } => {\n                if is_deferred_grouped_output_subquery(plan, *subquery_id) {\n                    return Ok(WalkControl::SkipChildren);\n                }\n                if let Some(ref lhs) = lhs {\n                    let mut lhs_contains_agg = false;\n                    walk_expr(lhs, &mut |expr: &ast::Expr| -> Result<WalkControl> {\n                        if plan.aggregates.iter().any(|a| a.original_expr == *expr) {\n                            lhs_contains_agg = true;\n                            return Ok(WalkControl::SkipChildren);\n                        }\n                        Ok(WalkControl::Continue)\n                    })?;\n                    if lhs_contains_agg {\n                        return Ok(WalkControl::SkipChildren);\n                    }\n                }\n                result_columns.push(expr);\n            }\n            _ => {\n                if plan.aggregates.iter().any(|a| a.original_expr == *expr) {\n                    return Ok(WalkControl::SkipChildren);\n                }\n                // Skip children of GROUP BY expressions — their leaf columns\n                // are already covered by the GROUP BY key and don't need\n                // separate materialization in the sorter.\n                if plan\n                    .group_by\n                    .as_ref()\n                    .is_some_and(|gb| gb.exprs.iter().any(|ge| exprs_are_equivalent(ge, expr)))\n                {\n                    return Ok(WalkControl::SkipChildren);\n                }\n            }\n        };\n        Ok(WalkControl::Continue)\n    })?;\n    Ok(())\n}\n\n/// In case sorting is needed for GROUP BY, creates a pseudo table that matches\n/// the number of columns in the GROUP BY sorter. Rows are individually read\n/// from the sorter into this pseudo table and processed.\npub fn group_by_create_pseudo_table(\n    program: &mut ProgramBuilder,\n    sorter_column_count: usize,\n) -> usize {\n    // Create a pseudo-table to read one row at a time from the sorter\n    // This allows us to use standard table access operations on the sorted data\n    program.alloc_cursor_id(CursorType::Pseudo(PseudoCursorType {\n        column_count: sorter_column_count,\n    }))\n}\n\n/// In case sorting is needed for GROUP BY, sorts the rows in the GROUP BY sorter\n/// and opens a pseudo table from which the sorted rows are read.\npub fn emit_group_by_sort_loop_start(\n    program: &mut ProgramBuilder,\n    row_source: &GroupByRowSource,\n    label_sort_loop_end: BranchOffset,\n) -> Result<()> {\n    let GroupByRowSource::Sorter {\n        sort_cursor,\n        pseudo_cursor,\n        reg_sorter_key,\n        sorter_column_count,\n        ..\n    } = row_source\n    else {\n        crate::bail_parse_error!(\"sort cursor must be opened for GROUP BY if we got here\");\n    };\n    program.emit_insn(Insn::OpenPseudo {\n        cursor_id: *pseudo_cursor,\n        content_reg: *reg_sorter_key,\n        num_fields: *sorter_column_count,\n    });\n\n    // Sort the sorter based on the group by columns\n    program.emit_insn(Insn::SorterSort {\n        cursor_id: *sort_cursor,\n        pc_if_empty: label_sort_loop_end,\n    });\n\n    Ok(())\n}\n\n/// In case sorting is needed for GROUP BY, advances to the next row\n/// in the GROUP BY sorter.\npub fn emit_group_by_sort_loop_end(\n    program: &mut ProgramBuilder,\n    sort_cursor: usize,\n    label_sort_loop_start: BranchOffset,\n    label_sort_loop_end: BranchOffset,\n) {\n    // Continue to the next row in the sorter\n    program.emit_insn(Insn::SorterNext {\n        cursor_id: sort_cursor,\n        pc_if_next: label_sort_loop_start,\n    });\n    program.preassign_label_to_next_insn(label_sort_loop_end);\n}\n\n/// Enum representing the source for the rows processed during a GROUP BY.\n/// In case sorting is needed (which is most of the time), the variant\n/// [GroupByRowSource::Sorter] encodes the necessary information about that\n/// sorter.\n///\n/// In case where the rows are already ordered, for example:\n/// \"SELECT indexed_col, count(1) FROM t GROUP BY indexed_col\"\n/// the rows are processed directly in the order they arrive from\n/// the main query loop.\n#[derive(Debug)]\npub enum GroupByRowSource {\n    Sorter {\n        /// Cursor opened for the pseudo table that GROUP BY reads rows from.\n        pseudo_cursor: usize,\n        /// The sorter opened for ensuring the rows are in GROUP BY order.\n        sort_cursor: usize,\n        /// Register holding the key used for sorting in the Sorter\n        reg_sorter_key: usize,\n        /// Number of columns in the GROUP BY sorter\n        sorter_column_count: usize,\n        start_reg_dest: usize,\n    },\n    MainLoop {\n        /// If GROUP BY rows are read directly in the main loop, start_reg is the first register\n        /// holding the value of a relevant column.\n        start_reg_src: usize,\n        /// The grouping columns for a group that is not yet finalized must be placed in new registers,\n        /// so that they don't get overwritten by the next group's data.\n        /// This is because the emission of a group that is \"done\" is made after a comparison between the \"current\" and \"next\" grouping\n        /// columns returns nonequal. If we don't store the \"current\" group in a separate set of registers, the \"next\" group's data will\n        /// overwrite the \"current\" group's columns and the wrong grouping column values will be emitted.\n        /// Aggregation results do not require new registers as they are not at risk of being overwritten before a given group\n        /// is processed.\n        start_reg_dest: usize,\n    },\n}\n\n/// Emits bytecode for processing a single GROUP BY group.\npub fn group_by_process_single_group(\n    program: &mut ProgramBuilder,\n    group_by: &GroupBy,\n    plan: &SelectPlan,\n    t_ctx: &mut TranslateCtx,\n) -> Result<()> {\n    let GroupByMetadata {\n        registers,\n        labels,\n        row_source,\n        ..\n    } = t_ctx\n        .meta_group_by\n        .as_ref()\n        .expect(\"group by metadata not found\");\n    program.preassign_label_to_next_insn(labels.label_sort_loop_start);\n    let groups_start_reg = match &row_source {\n        GroupByRowSource::Sorter {\n            sort_cursor,\n            pseudo_cursor,\n            reg_sorter_key,\n            ..\n        } => {\n            // Read a row from the sorted data in the sorter into the pseudo cursor\n            program.emit_insn(Insn::SorterData {\n                cursor_id: *sort_cursor,\n                dest_reg: *reg_sorter_key,\n                pseudo_cursor: *pseudo_cursor,\n            });\n            // Read the group by columns from the pseudo cursor\n            let groups_start_reg = program.alloc_registers(group_by.exprs.len());\n            for i in 0..group_by.exprs.len() {\n                let sorter_column_index = i;\n                let group_reg = groups_start_reg + i;\n                program.emit_column_or_rowid(*pseudo_cursor, sorter_column_index, group_reg);\n            }\n            groups_start_reg\n        }\n\n        GroupByRowSource::MainLoop { start_reg_src, .. } => *start_reg_src,\n    };\n\n    let mut compare_key_info = group_by\n        .exprs\n        .iter()\n        .map(|_| KeyInfo {\n            sort_order: SortOrder::Asc,\n            collation: CollationSeq::default(),\n        })\n        .collect::<Vec<_>>();\n    for (i, c) in compare_key_info\n        .iter_mut()\n        .enumerate()\n        .take(group_by.exprs.len())\n    {\n        let maybe_collation = get_collseq_from_expr(&group_by.exprs[i], &plan.table_references)?;\n        c.collation = maybe_collation.unwrap_or_default();\n    }\n\n    // Compare the group by columns to the previous group by columns to see if we are at a new group or not\n    program.emit_insn(Insn::Compare {\n        start_reg_a: registers.reg_group_exprs_cmp,\n        start_reg_b: groups_start_reg,\n        count: group_by.exprs.len(),\n        key_info: compare_key_info,\n    });\n\n    program.add_comment(\n        program.offset(),\n        \"start new group if comparison is not equal\",\n    );\n    // If we are at a new group, continue. If we are at the same group, jump to the aggregation step (i.e. accumulate more values into the aggregations)\n    let label_jump_after_comparison = program.allocate_label();\n    program.emit_insn(Insn::Jump {\n        target_pc_lt: label_jump_after_comparison,\n        target_pc_eq: labels.label_grouping_agg_step,\n        target_pc_gt: label_jump_after_comparison,\n    });\n\n    program.add_comment(\n        program.offset(),\n        \"check if ended group had data, and output if so\",\n    );\n    program.resolve_label(label_jump_after_comparison, program.offset());\n    program.emit_insn(Insn::Gosub {\n        target_pc: labels.label_subrtn_acc_output,\n        return_reg: registers.reg_subrtn_acc_output_return_offset,\n    });\n\n    // New group, move current group by columns into the comparison register\n    program.emit_insn(Insn::Move {\n        source_reg: groups_start_reg,\n        dest_reg: registers.reg_group_exprs_cmp,\n        count: group_by.exprs.len(),\n    });\n\n    program.add_comment(program.offset(), \"check abort flag\");\n    program.emit_insn(Insn::IfPos {\n        reg: registers.reg_abort_flag,\n        target_pc: labels.label_group_by_end,\n        decrement_by: 0,\n    });\n\n    program.add_comment(program.offset(), \"goto clear accumulator subroutine\");\n    program.emit_insn(Insn::Gosub {\n        target_pc: labels.label_subrtn_acc_clear,\n        return_reg: registers.reg_subrtn_acc_clear_return_offset,\n    });\n\n    // Process each aggregate function for the current row\n    program.preassign_label_to_next_insn(labels.label_grouping_agg_step);\n\n    match &row_source {\n        GroupByRowSource::Sorter { pseudo_cursor, .. } => {\n            // Read leaf columns from the pseudo cursor and cache them so that\n            // translate_expr can resolve column references during expression evaluation.\n            let leaf_start_idx = t_ctx.non_aggregate_expressions.len();\n            let leaf_regs = program.alloc_registers(t_ctx.agg_leaf_columns.len());\n            for i in 0..t_ctx.agg_leaf_columns.len() {\n                program.emit_column_or_rowid(*pseudo_cursor, leaf_start_idx + i, leaf_regs + i);\n            }\n\n            let cache_len = t_ctx.resolver.expr_to_reg_cache.len();\n            let cache_was_enabled = t_ctx.resolver.expr_to_reg_cache_enabled;\n            for (i, leaf_expr) in t_ctx.agg_leaf_columns.drain(..).enumerate() {\n                t_ctx.resolver.cache_expr_reg(\n                    std::borrow::Cow::Owned(leaf_expr),\n                    leaf_regs + i,\n                    false,\n                    None,\n                );\n            }\n            t_ctx.resolver.enable_expr_to_reg_cache();\n\n            for (i, agg) in plan.aggregates.iter().enumerate() {\n                let agg_start_reg = t_ctx\n                    .reg_agg_start\n                    .expect(\"aggregate registers must be initialized\");\n                let agg_result_reg = agg_start_reg + i;\n                let agg_arg_source =\n                    AggArgumentSource::new_from_expression(&agg.func, &agg.args, &agg.distinctness);\n                translate_aggregation_step(\n                    program,\n                    &plan.table_references,\n                    agg_arg_source,\n                    agg_result_reg,\n                    &t_ctx.resolver,\n                )?;\n                if let Distinctness::Distinct { ctx } = &agg.distinctness {\n                    let ctx = ctx\n                        .as_ref()\n                        .expect(\"distinct aggregate context not populated\");\n                    program.preassign_label_to_next_insn(ctx.label_on_conflict);\n                }\n            }\n\n            t_ctx.resolver.expr_to_reg_cache.truncate(cache_len);\n            t_ctx.resolver.expr_to_reg_cache_enabled = cache_was_enabled;\n        }\n        GroupByRowSource::MainLoop { start_reg_src, .. } => {\n            let mut offset = 0;\n            for (i, agg) in plan.aggregates.iter().enumerate() {\n                let agg_start_reg = t_ctx\n                    .reg_agg_start\n                    .expect(\"aggregate registers must be initialized\");\n                let agg_result_reg = agg_start_reg + i;\n                let start_reg_aggs = start_reg_src + t_ctx.non_aggregate_expressions.len();\n                let agg_arg_source =\n                    AggArgumentSource::new_from_registers(start_reg_aggs + offset, agg);\n                translate_aggregation_step(\n                    program,\n                    &plan.table_references,\n                    agg_arg_source,\n                    agg_result_reg,\n                    &t_ctx.resolver,\n                )?;\n                if let Distinctness::Distinct { ctx } = &agg.distinctness {\n                    let ctx = ctx\n                        .as_ref()\n                        .expect(\"distinct aggregate context not populated\");\n                    program.preassign_label_to_next_insn(ctx.label_on_conflict);\n                }\n                offset += agg.args.len();\n            }\n        }\n    }\n\n    // We only need to store non-aggregate columns once per group\n    // Skip if we've already stored them for this group\n    program.add_comment(\n        program.offset(),\n        \"don't emit group columns if continuing existing group\",\n    );\n    program.emit_insn(Insn::If {\n        target_pc: labels.label_acc_indicator_set_flag_true,\n        reg: registers.reg_data_in_acc_flag,\n        jump_if_null: false,\n    });\n\n    // Read non-aggregate columns from the current row\n    match row_source {\n        GroupByRowSource::Sorter {\n            pseudo_cursor,\n            start_reg_dest,\n            ..\n        } => {\n            let mut next_reg = *start_reg_dest;\n\n            for (sorter_column_index, (expr, expr_appears_in_result_columns)) in\n                t_ctx.non_aggregate_expressions.iter().enumerate()\n            {\n                if *expr_appears_in_result_columns {\n                    program.emit_column_or_rowid(*pseudo_cursor, sorter_column_index, next_reg);\n                    t_ctx.resolver.cache_scalar_expr_reg(\n                        std::borrow::Cow::Borrowed(expr),\n                        next_reg,\n                        false,\n                        &plan.table_references,\n                    )?;\n                    next_reg += 1;\n                }\n            }\n        }\n        GroupByRowSource::MainLoop { start_reg_dest, .. } => {\n            // Re-translate all the non-aggregate expressions into destination registers. We cannot use the same registers as emitted\n            // in the earlier part of the main loop, because they would be overwritten by the next group before the group results\n            // are processed.\n            for (i, expr) in t_ctx\n                .non_aggregate_expressions\n                .iter()\n                .filter_map(|(expr, in_result)| if *in_result { Some(expr) } else { None })\n                .enumerate()\n            {\n                let dest_reg = start_reg_dest + i;\n                translate_expr(\n                    program,\n                    Some(&plan.table_references),\n                    expr,\n                    dest_reg,\n                    &t_ctx.resolver,\n                )?;\n                t_ctx.resolver.cache_scalar_expr_reg(\n                    std::borrow::Cow::Borrowed(expr),\n                    dest_reg,\n                    false,\n                    &plan.table_references,\n                )?;\n            }\n        }\n    }\n\n    // Mark that we've stored data for this group\n    program.resolve_label(labels.label_acc_indicator_set_flag_true, program.offset());\n    program.add_comment(program.offset(), \"indicate data in accumulator\");\n    program.emit_insn(Insn::Integer {\n        value: 1,\n        dest: registers.reg_data_in_acc_flag,\n    });\n\n    Ok(())\n}\n\n/// Emits the bytecode for processing the aggregation phase of a GROUP BY clause.\n/// This is called either when:\n/// 1. the main query execution loop has finished processing,\n///    and we now have data in the GROUP BY sorter.\n/// 2. the rows are already sorted in the order that the GROUP BY keys are defined,\n///    and we can start aggregating inside the main loop.\npub fn group_by_agg_phase(\n    program: &mut ProgramBuilder,\n    t_ctx: &mut TranslateCtx,\n    plan: &SelectPlan,\n) -> Result<()> {\n    let GroupByMetadata {\n        labels, row_source, ..\n    } = t_ctx.meta_group_by.as_mut().unwrap();\n    let group_by = plan.group_by.as_ref().unwrap();\n\n    let label_sort_loop_start = labels.label_sort_loop_start;\n    let label_sort_loop_end = labels.label_sort_loop_end;\n\n    if matches!(row_source, GroupByRowSource::Sorter { .. }) {\n        emit_group_by_sort_loop_start(program, row_source, label_sort_loop_end)?;\n    }\n\n    group_by_process_single_group(program, group_by, plan, t_ctx)?;\n\n    let row_source = &t_ctx.meta_group_by.as_ref().unwrap().row_source;\n\n    // Continue to the next row in the sorter\n    if let GroupByRowSource::Sorter { sort_cursor, .. } = row_source {\n        emit_group_by_sort_loop_end(\n            program,\n            *sort_cursor,\n            label_sort_loop_start,\n            label_sort_loop_end,\n        );\n    }\n    Ok(())\n}\n\npub fn group_by_emit_row_phase<'a>(\n    program: &mut ProgramBuilder,\n    t_ctx: &mut TranslateCtx<'a>,\n    plan: &SelectPlan,\n    non_from_clause_subqueries: &mut [NonFromClauseSubquery],\n) -> Result<()> {\n    let group_by = plan.group_by.as_ref().expect(\"group by not found\");\n    let GroupByMetadata {\n        labels, registers, ..\n    } = t_ctx\n        .meta_group_by\n        .as_ref()\n        .expect(\"group by metadata not found\");\n    program.add_comment(program.offset(), \"emit row for final group\");\n    program.emit_insn(Insn::Gosub {\n        target_pc: labels.label_subrtn_acc_output,\n        return_reg: registers.reg_subrtn_acc_output_return_offset,\n    });\n\n    program.add_comment(program.offset(), \"group by finished\");\n    program.emit_insn(Insn::Goto {\n        target_pc: labels.label_group_by_end,\n    });\n    program.emit_insn(Insn::Integer {\n        value: 1,\n        dest: registers.reg_abort_flag,\n    });\n    program.emit_insn(Insn::Return {\n        return_reg: registers.reg_subrtn_acc_output_return_offset,\n        can_fallthrough: false,\n    });\n\n    program.resolve_label(labels.label_subrtn_acc_output, program.offset());\n\n    // Only output a row if there's data in the accumulator\n    program.add_comment(program.offset(), \"output group by row subroutine start\");\n    program.emit_insn(Insn::IfPos {\n        reg: registers.reg_data_in_acc_flag,\n        target_pc: labels.label_agg_final,\n        decrement_by: 0,\n    });\n\n    // If no data, return without outputting a row\n    program.resolve_label(\n        labels.label_group_by_end_without_emitting_row,\n        program.offset(),\n    );\n    // SELECT DISTINCT also jumps here if there is a duplicate.\n    if let Distinctness::Distinct { ctx } = &plan.distinctness {\n        let distinct_ctx = ctx.as_ref().expect(\"distinct context must exist\");\n        program.resolve_label(distinct_ctx.label_on_conflict, program.offset());\n    }\n    program.emit_insn(Insn::Return {\n        return_reg: registers.reg_subrtn_acc_output_return_offset,\n        can_fallthrough: false,\n    });\n\n    // Resolve the label for the start of the group by output row subroutine\n    program.resolve_label(labels.label_agg_final, program.offset());\n    // Finalize aggregate values for output\n    for (i, agg) in plan.aggregates.iter().enumerate() {\n        let agg_start_reg = t_ctx\n            .reg_agg_start\n            .expect(\"aggregate registers must be initialized\");\n        let agg_result_reg = agg_start_reg + i;\n        program.emit_insn(Insn::AggFinal {\n            register: agg_result_reg,\n            func: agg.func.clone(),\n        });\n        t_ctx.resolver.cache_expr_reg(\n            std::borrow::Cow::Owned(agg.original_expr.clone()),\n            agg_result_reg,\n            false,\n            None,\n        );\n    }\n\n    t_ctx.resolver.enable_expr_to_reg_cache();\n\n    if let Some(having) = &group_by.having {\n        emit_non_from_clause_subqueries_for_phase(\n            program,\n            &t_ctx.resolver,\n            non_from_clause_subqueries,\n            &plan.join_order,\n            Some(&plan.table_references),\n            SubqueryEvalPhase::GroupedOutput,\n            |subquery| matches!(subquery.origin, SubqueryOrigin::SelectHaving),\n        )?;\n\n        for expr in having.iter() {\n            let if_true_target = program.allocate_label();\n            translate_condition_expr(\n                program,\n                &plan.table_references,\n                expr,\n                ConditionMetadata {\n                    jump_if_condition_is_true: false,\n                    jump_target_when_false: labels.label_group_by_end_without_emitting_row,\n                    jump_target_when_true: if_true_target,\n                    // treat null result has false for now\n                    jump_target_when_null: labels.label_group_by_end_without_emitting_row,\n                },\n                &t_ctx.resolver,\n            )?;\n            program.preassign_label_to_next_insn(if_true_target);\n        }\n    }\n\n    // Disable constant optimization within the GROUP BY output subroutine.\n    // Constants hoisted to the init section would cause the IfPos jump\n    // (targeting label_agg_final) to land in the init block, which ends\n    // with Goto back to the start of the program, creating an infinite loop.\n    let span_idx = program.constant_spans_next_idx();\n    emit_non_from_clause_subqueries_for_phase(\n        program,\n        &t_ctx.resolver,\n        non_from_clause_subqueries,\n        &plan.join_order,\n        Some(&plan.table_references),\n        SubqueryEvalPhase::GroupedOutput,\n        |subquery| !matches!(subquery.origin, SubqueryOrigin::SelectHaving),\n    )?;\n\n    match plan.order_by.is_empty() {\n        true => {\n            emit_select_result(\n                program,\n                &t_ctx.resolver,\n                plan,\n                Some(labels.label_group_by_end),\n                Some(labels.label_group_by_end_without_emitting_row),\n                t_ctx.reg_nonagg_emit_once_flag,\n                t_ctx.reg_offset,\n                t_ctx.reg_result_cols_start.unwrap(),\n                t_ctx.limit_ctx,\n            )?;\n        }\n        false => {\n            EmitOrderBy::sorter_insert(program, t_ctx, plan)?;\n        }\n    }\n    program.constant_spans_invalidate_after(span_idx);\n\n    program.emit_insn(Insn::Return {\n        return_reg: registers.reg_subrtn_acc_output_return_offset,\n        can_fallthrough: false,\n    });\n\n    // Subroutine to clear accumulators for a new group\n    program.add_comment(program.offset(), \"clear accumulator subroutine start\");\n    program.resolve_label(labels.label_subrtn_acc_clear, program.offset());\n    let start_reg = registers.reg_non_aggregate_exprs_acc;\n\n    // Reset all accumulator registers to NULL\n    program.emit_insn(Insn::Null {\n        dest: start_reg,\n        dest_end: Some(\n            start_reg + t_ctx.non_aggregate_expressions.len() + plan.aggregates.len() - 1,\n        ),\n    });\n\n    // Clear hash tables for distinct aggregates\n    plan.aggregates\n        .iter()\n        .filter_map(|agg| {\n            if let Distinctness::Distinct { ctx } = &agg.distinctness {\n                Some(ctx)\n            } else {\n                None\n            }\n        })\n        .for_each(|ctx| {\n            let ctx = ctx\n                .as_ref()\n                .expect(\"distinct aggregate context not populated\");\n            program.emit_insn(Insn::HashClear {\n                hash_table_id: ctx.hash_table_id,\n            });\n        });\n\n    program.emit_insn(Insn::Integer {\n        value: 0,\n        dest: registers.reg_data_in_acc_flag,\n    });\n    program.emit_insn(Insn::Return {\n        return_reg: registers.reg_subrtn_acc_clear_return_offset,\n        can_fallthrough: false,\n    });\n    program.preassign_label_to_next_insn(labels.label_group_by_end);\n    Ok(())\n}\n"
  },
  {
    "path": "core/translate/index.rs",
    "content": "use crate::sync::Arc;\nuse rustc_hash::FxHashMap as HashMap;\n\nuse crate::error::SQLITE_CONSTRAINT_UNIQUE;\nuse crate::function::{Deterministic, Func, ScalarFunc};\nuse crate::index_method::IndexMethodConfiguration;\nuse crate::numeric::Numeric;\nuse crate::schema::{Column, Table, EXPR_INDEX_SENTINEL, RESERVED_TABLE_PREFIXES};\nuse crate::translate::collate::CollationSeq;\nuse crate::translate::emitter::{\n    emit_cdc_autocommit_commit, emit_cdc_full_record, emit_cdc_insns, prepare_cdc_if_necessary,\n    OperationMode, Resolver,\n};\nuse crate::translate::expr::{\n    bind_and_rewrite_expr, translate_condition_expr, translate_expr, unwrap_parens, walk_expr,\n    BindingBehavior, ConditionMetadata, WalkControl,\n};\nuse crate::translate::insert::format_unique_violation_desc;\nuse crate::translate::plan::{\n    ColumnUsedMask, IterationDirection, JoinedTable, Operation, Scan, TableReferences,\n};\nuse crate::vdbe::builder::{CursorKey, ProgramBuilderOpts};\nuse crate::vdbe::insn::{to_u16, CmpInsFlags, Cookie};\nuse crate::{bail_parse_error, CaptureDataChangesExt, LimboError};\nuse crate::{\n    schema::{BTreeTable, Index, IndexColumn, PseudoCursorType},\n    storage::pager::CreateBTreeFlags,\n    util::{escape_sql_string_literal, normalize_ident},\n    vdbe::{\n        builder::{CursorType, ProgramBuilder},\n        insn::{IdxInsertFlags, Insn, RegisterOrLiteral},\n    },\n};\nuse turso_parser::ast::{self, Expr, QualifiedName, SortOrder, SortedColumn};\n\nuse super::schema::{emit_schema_entry, SchemaEntryType, SQLITE_TABLEID};\n\npub fn translate_create_index(\n    program: &mut ProgramBuilder,\n    connection: &Arc<crate::Connection>,\n    resolver: &Resolver,\n    stmt: ast::Stmt,\n) -> crate::Result<()> {\n    let sql = stmt.to_string();\n    let ast::Stmt::CreateIndex {\n        unique,\n        if_not_exists,\n        idx_name,\n        tbl_name,\n        columns,\n        where_clause,\n        with_clause,\n        using,\n    } = stmt\n    else {\n        panic!(\"translate_create_index must be called with CreateIndex AST node\");\n    };\n\n    if !connection.experimental_index_method_enabled()\n        && (using.is_some() || !with_clause.is_empty())\n    {\n        bail_parse_error!(\n            \"index method is an experimental feature. Enable with --experimental-index-method flag\"\n        )\n    }\n\n    if connection.mvcc_enabled() && using.is_some() {\n        bail_parse_error!(\"Custom index modules are not supported in MVCC mode\");\n    }\n\n    let original_idx_name = idx_name;\n    let database_id = resolver.resolve_database_id(&original_idx_name)?;\n    let idx_name = normalize_ident(original_idx_name.name.as_str());\n    let tbl_name = normalize_ident(tbl_name.as_str());\n\n    if tbl_name.eq_ignore_ascii_case(\"sqlite_sequence\") {\n        crate::bail_parse_error!(\"table sqlite_sequence may not be indexed\");\n    }\n    if RESERVED_TABLE_PREFIXES\n        .iter()\n        .any(|prefix| idx_name.starts_with(prefix) || tbl_name.starts_with(prefix))\n        && !connection.is_nested_stmt()\n    {\n        bail_parse_error!(\n            \"Object name reserved for internal use: {}\",\n            original_idx_name\n        );\n    }\n    let opts = ProgramBuilderOpts {\n        num_cursors: 5,\n        approx_num_insns: 40,\n        approx_num_labels: 5,\n    };\n    program.extend(&opts);\n\n    if crate::is_attached_db(database_id) {\n        let schema_cookie = resolver.with_schema(database_id, |s| s.schema_version);\n        program.begin_write_on_database(database_id, schema_cookie);\n    }\n\n    // Check if the index is being created on a valid btree table and\n    // the name is globally unique in the schema.\n    let schema_unique = resolver.with_schema(database_id, |s| s.is_unique_idx_name(&idx_name));\n    if !schema_unique {\n        // If IF NOT EXISTS is specified, silently return without error\n        if if_not_exists {\n            return Ok(());\n        }\n        crate::bail_parse_error!(\"Error: index with name '{idx_name}' already exists.\");\n    }\n    let table = resolver.with_schema(database_id, |s| s.get_table(&tbl_name));\n    let Some(table) = table else {\n        crate::bail_parse_error!(\"Error: table '{tbl_name}' does not exist.\");\n    };\n    let Some(tbl) = table.btree() else {\n        crate::bail_parse_error!(\"Error: table '{tbl_name}' is not a b-tree table.\");\n    };\n    let columns = resolve_sorted_columns(&tbl, &columns)?;\n\n    // Block CREATE INDEX on non-orderable custom type columns\n    for col in &columns {\n        if col.expr.is_none() {\n            // Simple column reference (not expression index)\n            if let Some(column) = tbl.columns.get(col.pos_in_table) {\n                if let Some(type_def) = resolver\n                    .schema()\n                    .get_type_def(&column.ty_str, tbl.is_strict)\n                {\n                    if type_def.decode.is_some()\n                        && !type_def.operators.iter().any(|op| op.op == \"<\")\n                    {\n                        bail_parse_error!(\n                            \"cannot create index on column '{}' of type '{}': type does not declare OPERATOR '<'\",\n                            col.name,\n                            type_def.name\n                        );\n                    }\n                }\n            }\n        }\n    }\n\n    if !with_clause.is_empty() && using.is_none() {\n        crate::bail_parse_error!(\n            \"Error: additional parameters are allowed only for custom module indices: '{idx_name}' is not custom module index\"\n        );\n    }\n\n    let mut index_method = None;\n    if let Some(using) = &using {\n        let index_modules = &resolver.symbol_table.index_methods;\n        let using = using.as_str();\n        let index_module = index_modules.get(using);\n        if index_module.is_none() {\n            crate::bail_parse_error!(\"Error: unknown module name '{}'\", using);\n        }\n        if let Some(index_module) = index_module {\n            let parameters = resolve_index_method_parameters(with_clause)?;\n            index_method = Some(index_module.attach(&IndexMethodConfiguration {\n                table_name: tbl.name.clone(),\n                index_name: idx_name.clone(),\n                columns: columns.clone(),\n                parameters,\n            })?);\n        }\n    }\n    let idx = Arc::new(Index {\n        name: idx_name.clone(),\n        table_name: tbl.name.clone(),\n        root_page: 0, //  we dont have access till its created, after we parse the schema table\n        columns: columns.clone(),\n        unique,\n        ephemeral: false,\n        has_rowid: tbl.has_rowid,\n        // store the *original* where clause, because we need to rewrite it\n        // before translating, and it cannot reference a table alias\n        where_clause: where_clause.clone(),\n        index_method: index_method.clone(),\n        on_conflict: None,\n    });\n\n    if !idx.validate_where_expr(&table, resolver) {\n        crate::bail_parse_error!(\n            \"Error: cannot use aggregate, window functions or reference other tables in WHERE clause of CREATE INDEX:\\n {}\",\n            where_clause\n                .as_ref()\n                .expect(\"where expr has to exist in order to fail\")\n                .to_string()\n        );\n    }\n\n    // Allocate the necessary cursors:\n    //\n    // 1. sqlite_schema_cursor_id - sqlite_schema table\n    // 2. index_cursor_id         - new index cursor\n    // 3. table_cursor_id         - table we are creating the index on\n    // 4. sorter_cursor_id        - sorter\n    // 5. pseudo_cursor_id        - pseudo table to store the sorted index values\n    let sqlite_table = resolver.schema().get_btree_table(SQLITE_TABLEID).unwrap();\n    let sqlite_schema_cursor_id =\n        program.alloc_cursor_id(CursorType::BTreeTable(sqlite_table.clone()));\n    let table_ref = program.table_reference_counter.next();\n    let index_cursor_id = program.alloc_cursor_index(None, &idx)?;\n    let table_cursor_id = program.alloc_cursor_id_keyed(\n        CursorKey::table(table_ref),\n        CursorType::BTreeTable(tbl.clone()),\n    );\n    let sorter_cursor_id = program.alloc_cursor_id(CursorType::Sorter);\n    let pseudo_cursor_id = program.alloc_cursor_id(CursorType::Pseudo(PseudoCursorType {\n        column_count: tbl.columns.len(),\n    }));\n\n    let mut table_references = TableReferences::new(\n        vec![JoinedTable {\n            op: Operation::Scan(Scan::BTreeTable {\n                iter_dir: IterationDirection::Forwards,\n                index: None,\n            }),\n            table: Table::BTree(tbl.clone()),\n            identifier: tbl_name.clone(),\n            internal_id: table_ref,\n            join_info: None,\n            col_used_mask: ColumnUsedMask::default(),\n            column_use_counts: Vec::new(),\n            expression_index_usages: Vec::new(),\n            database_id: 0,\n            indexed: None,\n        }],\n        vec![],\n    );\n    let where_clause = idx.bind_where_expr(Some(&mut table_references), resolver);\n\n    // Create a new B-Tree and store the root page index in a register\n    let root_page_reg = program.alloc_register();\n    if idx.index_method.is_some() && !idx.is_backing_btree_index() {\n        program.emit_insn(Insn::IndexMethodCreate {\n            db: database_id,\n            cursor_id: index_cursor_id,\n        });\n        // index method sqlite_schema row always has root_page equals to zero in the schema (same as virtual tables)\n        program.emit_int(0, root_page_reg);\n    } else {\n        program.emit_insn(Insn::CreateBtree {\n            db: database_id,\n            root: root_page_reg,\n            flags: CreateBTreeFlags::new_index(),\n        });\n    }\n\n    // open the sqlite schema table for writing and create a new entry for the index\n    program.emit_insn(Insn::OpenWrite {\n        cursor_id: sqlite_schema_cursor_id,\n        root_page: RegisterOrLiteral::Literal(sqlite_table.root_page),\n        db: database_id,\n    });\n    let cdc_table = prepare_cdc_if_necessary(program, resolver.schema(), SQLITE_TABLEID)?;\n    emit_schema_entry(\n        program,\n        resolver,\n        sqlite_schema_cursor_id,\n        cdc_table.map(|x| x.0),\n        SchemaEntryType::Index,\n        &idx_name,\n        &tbl_name,\n        root_page_reg,\n        Some(sql),\n    )?;\n\n    if index_method\n        .as_ref()\n        .is_some_and(|m| !m.definition().backing_btree)\n    {\n        // open the table we are creating the index on for reading\n        program.emit_insn(Insn::OpenRead {\n            cursor_id: table_cursor_id,\n            root_page: tbl.root_page,\n            db: database_id,\n        });\n\n        // Open the index btree we created for writing to insert the\n        // newly sorted index records.\n        program.emit_insn(Insn::OpenWrite {\n            cursor_id: index_cursor_id,\n            root_page: RegisterOrLiteral::Register(root_page_reg),\n            db: database_id,\n        });\n\n        let loop_start_label = program.allocate_label();\n        let loop_end_label = program.allocate_label();\n        program.emit_insn(Insn::Rewind {\n            cursor_id: table_cursor_id,\n            pc_if_empty: loop_end_label,\n        });\n        program.preassign_label_to_next_insn(loop_start_label);\n\n        // Loop start:\n        // Collect index values into start_reg..rowid_reg\n        // emit MakeRecord (index key + rowid) into record_reg.\n        //\n        // Then insert the record into the sorter\n        let mut skip_row_label = None;\n        if let Some(where_clause) = where_clause {\n            let label = program.allocate_label();\n            let condition_true_label = program.allocate_label();\n            translate_condition_expr(\n                program,\n                &table_references,\n                &where_clause,\n                ConditionMetadata {\n                    jump_if_condition_is_true: false,\n                    jump_target_when_false: label,\n                    jump_target_when_true: condition_true_label,\n                    jump_target_when_null: label,\n                },\n                resolver,\n            )?;\n            program.preassign_label_to_next_insn(condition_true_label);\n            skip_row_label = Some(label);\n        }\n\n        let start_reg = program.alloc_registers(columns.len() + 1);\n        for (i, col) in columns.iter().enumerate() {\n            emit_index_column_value_from_cursor(\n                program,\n                resolver,\n                &mut table_references,\n                table_cursor_id,\n                col,\n                start_reg + i,\n            )?;\n        }\n        let rowid_reg = start_reg + columns.len();\n        program.emit_insn(Insn::RowId {\n            cursor_id: table_cursor_id,\n            dest: rowid_reg,\n        });\n        let record_reg = program.alloc_register();\n        program.emit_insn(Insn::MakeRecord {\n            start_reg: to_u16(start_reg),\n            count: to_u16(columns.len() + 1),\n            dest_reg: to_u16(record_reg),\n            index_name: Some(idx_name.clone()),\n            affinity_str: None,\n        });\n\n        // insert new index record\n        program.emit_insn(Insn::IdxInsert {\n            cursor_id: index_cursor_id,\n            record_reg,\n            unpacked_start: Some(start_reg),\n            unpacked_count: Some((columns.len() + 1) as u16),\n            flags: IdxInsertFlags::new().use_seek(false),\n        });\n\n        if let Some(skip_row_label) = skip_row_label {\n            program.resolve_label(skip_row_label, program.offset());\n        }\n        program.emit_insn(Insn::Next {\n            cursor_id: table_cursor_id,\n            pc_if_next: loop_start_label,\n        });\n        program.preassign_label_to_next_insn(loop_end_label);\n    } else if index_method.is_none() {\n        // determine the order and collation of the columns in the index for the sorter\n        let order_and_collations = idx.columns.iter().map(|c| (c.order, c.collation)).collect();\n        // open the sorter and the pseudo table\n        program.emit_insn(Insn::SorterOpen {\n            cursor_id: sorter_cursor_id,\n            columns: columns.len(),\n            order_and_collations,\n            comparators: vec![],\n        });\n        let content_reg = program.alloc_register();\n        program.emit_insn(Insn::OpenPseudo {\n            cursor_id: pseudo_cursor_id,\n            content_reg,\n            num_fields: columns.len() + 1,\n        });\n\n        // open the table we are creating the index on for reading\n        program.emit_insn(Insn::OpenRead {\n            cursor_id: table_cursor_id,\n            root_page: tbl.root_page,\n            db: database_id,\n        });\n\n        let loop_start_label = program.allocate_label();\n        let loop_end_label = program.allocate_label();\n        program.emit_insn(Insn::Rewind {\n            cursor_id: table_cursor_id,\n            pc_if_empty: loop_end_label,\n        });\n        program.preassign_label_to_next_insn(loop_start_label);\n\n        // Loop start:\n        // Collect index values into start_reg..rowid_reg\n        // emit MakeRecord (index key + rowid) into record_reg.\n        //\n        // Then insert the record into the sorter\n        let mut skip_row_label = None;\n        if let Some(where_clause) = where_clause {\n            let label = program.allocate_label();\n            let condition_true_label = program.allocate_label();\n            translate_condition_expr(\n                program,\n                &table_references,\n                &where_clause,\n                ConditionMetadata {\n                    jump_if_condition_is_true: false,\n                    jump_target_when_false: label,\n                    jump_target_when_true: condition_true_label,\n                    jump_target_when_null: label,\n                },\n                resolver,\n            )?;\n            program.preassign_label_to_next_insn(condition_true_label);\n            skip_row_label = Some(label);\n        }\n\n        let start_reg = program.alloc_registers(columns.len() + 1);\n        for (i, col) in columns.iter().enumerate() {\n            emit_index_column_value_from_cursor(\n                program,\n                resolver,\n                &mut table_references,\n                table_cursor_id,\n                col,\n                start_reg + i,\n            )?;\n        }\n        let rowid_reg = start_reg + columns.len();\n        program.emit_insn(Insn::RowId {\n            cursor_id: table_cursor_id,\n            dest: rowid_reg,\n        });\n        let record_reg = program.alloc_register();\n        program.emit_insn(Insn::MakeRecord {\n            start_reg: to_u16(start_reg),\n            count: to_u16(columns.len() + 1),\n            dest_reg: to_u16(record_reg),\n            index_name: Some(idx_name.clone()),\n            affinity_str: None,\n        });\n        program.emit_insn(Insn::SorterInsert {\n            cursor_id: sorter_cursor_id,\n            record_reg,\n        });\n\n        if let Some(skip_row_label) = skip_row_label {\n            program.resolve_label(skip_row_label, program.offset());\n        }\n        program.emit_insn(Insn::Next {\n            cursor_id: table_cursor_id,\n            pc_if_next: loop_start_label,\n        });\n        program.preassign_label_to_next_insn(loop_end_label);\n\n        // Open the index btree we created for writing to insert the\n        // newly sorted index records.\n        program.emit_insn(Insn::OpenWrite {\n            cursor_id: index_cursor_id,\n            root_page: RegisterOrLiteral::Register(root_page_reg),\n            db: database_id,\n        });\n\n        let sorted_loop_start = program.allocate_label();\n        let sorted_loop_end = program.allocate_label();\n\n        // Sort the index records in the sorter\n        program.emit_insn(Insn::SorterSort {\n            cursor_id: sorter_cursor_id,\n            pc_if_empty: sorted_loop_end,\n        });\n\n        let sorted_record_reg = program.alloc_register();\n\n        if unique {\n            // Since the records to be inserted are sorted, we can compare prev with current and if they are equal,\n            // we fall through to Halt with a unique constraint violation error.\n            let goto_label = program.allocate_label();\n            let label_after_sorter_compare = program.allocate_label();\n            program.resolve_label(goto_label, program.offset());\n            program.emit_insn(Insn::Goto {\n                target_pc: label_after_sorter_compare,\n            });\n            program.preassign_label_to_next_insn(sorted_loop_start);\n            program.emit_insn(Insn::SorterCompare {\n                cursor_id: sorter_cursor_id,\n                sorted_record_reg,\n                num_regs: columns.len(),\n                pc_when_nonequal: goto_label,\n            });\n            program.emit_insn(Insn::Halt {\n                err_code: SQLITE_CONSTRAINT_UNIQUE,\n                description: format_unique_violation_desc(tbl_name.as_str(), &idx),\n                on_error: None,\n                description_reg: None,\n            });\n            program.preassign_label_to_next_insn(label_after_sorter_compare);\n        } else {\n            program.preassign_label_to_next_insn(sorted_loop_start);\n        }\n\n        program.emit_insn(Insn::SorterData {\n            pseudo_cursor: pseudo_cursor_id,\n            cursor_id: sorter_cursor_id,\n            dest_reg: sorted_record_reg,\n        });\n\n        // seek to the end of the index btree to position the cursor for appending\n        program.emit_insn(Insn::SeekEnd {\n            cursor_id: index_cursor_id,\n        });\n        // insert new index record\n        program.emit_insn(Insn::IdxInsert {\n            cursor_id: index_cursor_id,\n            record_reg: sorted_record_reg,\n            unpacked_start: None, // TODO: optimize with these to avoid decoding record twice\n            unpacked_count: None,\n            flags: IdxInsertFlags::new().use_seek(false),\n        });\n        program.emit_insn(Insn::SorterNext {\n            cursor_id: sorter_cursor_id,\n            pc_if_next: sorted_loop_start,\n        });\n        program.preassign_label_to_next_insn(sorted_loop_end);\n    }\n\n    // End of the outer loop\n    //\n    // Keep schema table open to emit ParseSchema, close the other cursors.\n    program.close_cursors(&[sorter_cursor_id, table_cursor_id, index_cursor_id]);\n\n    let current_schema_version = resolver.with_schema(database_id, |s| s.schema_version);\n    program.emit_insn(Insn::SetCookie {\n        db: database_id,\n        cookie: Cookie::SchemaVersion,\n        value: current_schema_version as i32 + 1,\n        p5: 0,\n    });\n    // Parse the schema table to get the index root page and add new index to Schema\n    let escaped_idx_name = escape_sql_string_literal(&idx_name);\n    let parse_schema_where_clause = format!(\"name = '{escaped_idx_name}' AND type = 'index'\");\n    program.emit_insn(Insn::ParseSchema {\n        db: database_id,\n        where_clause: Some(parse_schema_where_clause),\n    });\n    // Close the final sqlite_schema cursor\n    program.emit_insn(Insn::Close {\n        cursor_id: sqlite_schema_cursor_id,\n    });\n\n    Ok(())\n}\n\npub fn resolve_sorted_columns(\n    table: &BTreeTable,\n    cols: &[SortedColumn],\n) -> crate::Result<Vec<IndexColumn>> {\n    let mut resolved = Vec::with_capacity(cols.len());\n    for sc in cols {\n        let order = sc.order.unwrap_or(SortOrder::Asc);\n        let (explicit_collation, base_expr) = extract_collation(sc.expr.as_ref())?;\n        // Unwrap parentheses for column resolution (SQLite treats (('col')) same as 'col')\n        let unwrapped_expr = unwrap_parens(base_expr)?;\n        if let Some((pos, column_name, column)) = resolve_index_column(unwrapped_expr, table) {\n            let collation = explicit_collation.or_else(|| column.collation_opt());\n            resolved.push(IndexColumn {\n                name: column_name,\n                order,\n                pos_in_table: pos,\n                collation,\n                default: column.default.clone(),\n                expr: None,\n            });\n            continue;\n        }\n        if !validate_index_expression(unwrapped_expr, table) {\n            crate::bail_parse_error!(\"Error: invalid expression in CREATE INDEX: {}\", sc.expr);\n        }\n        resolved.push(IndexColumn {\n            name: sc.expr.to_string(),\n            order,\n            pos_in_table: EXPR_INDEX_SENTINEL,\n            collation: explicit_collation,\n            default: None,\n            expr: Some(sc.expr.clone()),\n        });\n    }\n    Ok(resolved)\n}\n\n/// Extracts collation sequence from an expression if it is a Collate expression.\n/// Given the example: `col1 COLLATE NOCASE` / Expr::Collation(Expr::Id(col1), CollationSeq)\n/// returns (Some(CollationSeq), Expr::Id(col1))\nfn extract_collation(expr: &Expr) -> crate::Result<(Option<CollationSeq>, &Expr)> {\n    let mut current = expr;\n    let mut coll = None;\n    while let Expr::Collate(inner, seq) = current {\n        coll = Some(CollationSeq::new(seq.as_str())?);\n        current = inner.as_ref();\n    }\n    Ok((coll, current))\n}\n\n/// For a given Index Expression, attempts to resolve it to a column position in the table.\n/// Returning (position_in_table, column_name, column_reference).\n/// This is needed to support Collated indexes, where the ast node is not simply the column name,\n/// but isn't treated like an arbitrary expression either.\nfn resolve_index_column<'a>(\n    expr: &'a Expr,\n    table: &'a BTreeTable,\n) -> Option<(usize, String, &'a Column)> {\n    let (pos, column) = match expr {\n        Expr::Id(col_name) | Expr::Name(col_name) => table.get_column(col_name.as_str())?,\n        // SQLite interprets single-quoted strings as column names in index expressions\n        // (backwards compatibility quirk). We do the same. The string includes quotes.\n        Expr::Literal(ast::Literal::String(col_name)) => {\n            let unquoted = col_name.trim_matches('\\'');\n            table.get_column(unquoted)?\n        }\n        Expr::Qualified(_, col) | Expr::DoublyQualified(_, _, col) => {\n            table.get_column(col.as_str())?\n        }\n        Expr::RowId { .. } => table.get_rowid_alias_column()?,\n        _ => return None,\n    };\n    let column_name = column\n        .name\n        .as_ref()\n        .expect(\"column name must exist for indexed column\")\n        .clone();\n    Some((pos, column_name, column))\n}\n\n/// Validates that an index expression only contains allowed constructs.\n///\n/// https://sqlite.org/expridx.html\n/// There are certain reasonable restrictions on expressions that appear in CREATE INDEX statements:\n/// Expressions in CREATE INDEX statements may only refer to columns of the table being indexed, not to columns in other tables.\n/// Expressions in CREATE INDEX statements may contain function calls, but only to functions whose output is always determined completely by its input parameters\n/// (a.k.a.: deterministic functions). Obviously, functions like random() will not work well in an index. But also functions like sqlite_version(), though they\n/// are constant across any one database connection, are not constant across the life of the underlying database file, and hence may not be used in a CREATE INDEX statement.\n/// Expressions in CREATE INDEX statements may not use subqueries.\n/// Additionally, a standalone string literal is interpreted as a column name (for backwards\n/// compatibility with SQLite), not as a string literal. It is rejected if no such column exists.\nfn validate_index_expression(expr: &Expr, table: &BTreeTable) -> bool {\n    // A top-level string literal would have been handled by resolve_index_column().\n    // If we get here with a string literal, it means the column doesn't exist.\n    // (SQLite interprets standalone string literals as column names for backwards compat.)\n    // Note: extract_collation already unwraps parentheses, so we check the unwrapped expr.\n    if matches!(expr, Expr::Literal(ast::Literal::String(_))) {\n        return false;\n    }\n\n    let tbl_norm = normalize_ident(table.name.as_str());\n    let has_col = |name: &str| {\n        let n = normalize_ident(name);\n        table\n            .columns\n            .iter()\n            .any(|c| c.name.as_ref().is_some_and(|cn| normalize_ident(cn) == n))\n    };\n    let is_tbl = |ns: &str| normalize_ident(ns).eq_ignore_ascii_case(&tbl_norm);\n    let is_deterministic_fn = |name: &str, args: &[Box<Expr>]| {\n        let n = normalize_ident(name);\n        Func::resolve_function(&n, args.len()).is_ok_and(|f| is_valid_index_function_call(&f, args))\n    };\n\n    let mut ok = true;\n    let _ = walk_expr(expr, &mut |e: &Expr| -> crate::Result<WalkControl> {\n        if !ok {\n            return Ok(WalkControl::SkipChildren);\n        }\n        match e {\n            // String literals inside expressions are allowed (e.g., `c0 || 'suffix'`).\n            // Standalone string literals are handled by resolve_index_column() which interprets\n            // them as column names (SQLite backwards compat quirk).\n            Expr::Literal(\n                ast::Literal::CurrentDate\n                | ast::Literal::CurrentTime\n                | ast::Literal::CurrentTimestamp,\n            ) => {\n                ok = false;\n            }\n            Expr::Literal(_) | Expr::RowId { .. } => {}\n            // must be a column of the target table\n            Expr::Id(n) | Expr::Name(n) => {\n                if !has_col(n.as_str()) {\n                    ok = false;\n                }\n            }\n            // Qualified: qualifier must match this index's table, column must exist\n            Expr::Qualified(ns, col) | Expr::DoublyQualified(_, ns, col) => {\n                if !is_tbl(ns.as_str()) || !has_col(col.as_str()) {\n                    ok = false;\n                }\n            }\n            Expr::FunctionCall {\n                name, filter_over, ..\n            }\n            | Expr::FunctionCallStar {\n                name, filter_over, ..\n            } => {\n                // reject windowed\n                if filter_over.over_clause.is_some() {\n                    ok = false;\n                } else {\n                    let argc = match e {\n                        Expr::FunctionCall { args, .. } => args.as_slice(),\n                        Expr::FunctionCallStar { .. } => &[] as &[Box<Expr>],\n                        _ => unreachable!(),\n                    };\n                    if !is_deterministic_fn(name.as_str(), argc) {\n                        ok = false;\n                    }\n                }\n            }\n            // Explicitly disallowed constructs\n            Expr::Exists(_)\n            | Expr::InSelect { .. }\n            | Expr::Subquery(_)\n            | Expr::Raise { .. }\n            | Expr::Variable(_) => {\n                ok = false;\n            }\n            _ => {}\n        }\n        Ok(if ok {\n            WalkControl::Continue\n        } else {\n            WalkControl::SkipChildren\n        })\n    });\n    ok\n}\n\nfn is_valid_index_function_call(func: &Func, args: &[Box<Expr>]) -> bool {\n    match func {\n        Func::Scalar(\n            ScalarFunc::Date\n            | ScalarFunc::Time\n            | ScalarFunc::DateTime\n            | ScalarFunc::UnixEpoch\n            | ScalarFunc::JulianDay\n            | ScalarFunc::StrfTime\n            | ScalarFunc::TimeDiff,\n        ) => is_deterministic_datetime_index_call(func, args),\n        _ => func.is_deterministic(),\n    }\n}\n\n// SQLite allows date/time functions in expression indexes only when they do not\n// depend on the current clock or local timezone.\nfn is_deterministic_datetime_index_call(func: &Func, args: &[Box<Expr>]) -> bool {\n    match func {\n        Func::Scalar(ScalarFunc::Date)\n        | Func::Scalar(ScalarFunc::Time)\n        | Func::Scalar(ScalarFunc::DateTime)\n        | Func::Scalar(ScalarFunc::UnixEpoch)\n        | Func::Scalar(ScalarFunc::JulianDay) => {\n            !args.is_empty()\n                && !is_current_time_expr(args[0].as_ref())\n                && !args[1..]\n                    .iter()\n                    .any(|arg| is_unsafe_datetime_modifier(arg.as_ref()))\n        }\n        Func::Scalar(ScalarFunc::StrfTime) => {\n            args.len() >= 2\n                && !is_current_time_expr(args[1].as_ref())\n                && !args[2..]\n                    .iter()\n                    .any(|arg| is_unsafe_datetime_modifier(arg.as_ref()))\n        }\n        Func::Scalar(ScalarFunc::TimeDiff) => {\n            !args.iter().any(|arg| is_current_time_expr(arg.as_ref()))\n        }\n        _ => unreachable!(\"non-datetime function passed to datetime index validator\"),\n    }\n}\n\nfn is_current_time_expr(expr: &Expr) -> bool {\n    matches!(\n        expr,\n        Expr::Literal(ast::Literal::String(value)) if string_literal_eq(value, \"now\")\n    ) || matches!(\n        expr,\n        Expr::Literal(\n            ast::Literal::CurrentDate | ast::Literal::CurrentTime | ast::Literal::CurrentTimestamp\n        )\n    )\n}\n\nfn is_unsafe_datetime_modifier(expr: &Expr) -> bool {\n    matches!(\n        expr,\n        Expr::Literal(ast::Literal::String(value))\n            if string_literal_eq(value, \"localtime\") || string_literal_eq(value, \"utc\")\n    ) || is_current_time_expr(expr)\n}\n\nfn string_literal_eq(value: &str, expected: &str) -> bool {\n    value.trim_matches('\\'').eq_ignore_ascii_case(expected)\n}\n\nfn emit_index_column_value_from_cursor(\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    table_references: &mut TableReferences,\n    table_cursor_id: usize,\n    idx_col: &IndexColumn,\n    dest_reg: usize,\n) -> crate::Result<()> {\n    if let Some(expr) = &idx_col.expr {\n        let mut expr = expr.as_ref().clone();\n        bind_and_rewrite_expr(\n            &mut expr,\n            Some(table_references),\n            None,\n            resolver,\n            BindingBehavior::ResultColumnsNotAllowed,\n        )?;\n        translate_expr(program, Some(table_references), &expr, dest_reg, resolver)?;\n    } else {\n        program.emit_column_or_rowid(table_cursor_id, idx_col.pos_in_table, dest_reg);\n    }\n    Ok(())\n}\n\npub fn resolve_index_method_parameters(\n    parameters: Vec<(turso_parser::ast::Name, Box<Expr>)>,\n) -> crate::Result<HashMap<String, crate::Value>> {\n    let mut resolved = HashMap::default();\n    for (key, value) in parameters {\n        let value = match *value {\n            Expr::Literal(literal) => match literal {\n                ast::Literal::Numeric(s) => match Numeric::from(s) {\n                    Numeric::Integer(v) => crate::Value::Numeric(Numeric::Integer(v)),\n                    Numeric::Float(v) => crate::Value::Numeric(Numeric::Float(v)),\n                },\n                ast::Literal::Null => crate::Value::Null,\n                ast::Literal::String(s) => crate::Value::Text(s.into()),\n                ast::Literal::Blob(b) => crate::Value::Blob(\n                    b.as_bytes()\n                        .chunks_exact(2)\n                        .map(|pair| {\n                            // We assume that sqlite3-parser has already validated that\n                            // the input is valid hex string, thus unwrap is safe.\n                            let hex_byte = std::str::from_utf8(pair).unwrap();\n                            u8::from_str_radix(hex_byte, 16).unwrap()\n                        })\n                        .collect(),\n                ),\n                _ => bail_parse_error!(\"parameters must be constant literals\"),\n            },\n            _ => bail_parse_error!(\"parameters must be constant literals\"),\n        };\n        resolved.insert(key.as_str().to_string(), value);\n    }\n    Ok(resolved)\n}\n\npub fn translate_drop_index(\n    qualified_name: &QualifiedName,\n    resolver: &Resolver,\n    if_exists: bool,\n    program: &mut ProgramBuilder,\n) -> crate::Result<()> {\n    let database_id = resolver.resolve_database_id(qualified_name)?;\n    let idx_name = normalize_ident(qualified_name.name.as_str());\n    let opts = ProgramBuilderOpts {\n        num_cursors: 5,\n        approx_num_insns: 40,\n        approx_num_labels: 5,\n    };\n    program.extend(&opts);\n\n    if crate::is_attached_db(database_id) {\n        let schema_cookie = resolver.with_schema(database_id, |s| s.schema_version);\n        program.begin_write_on_database(database_id, schema_cookie);\n    }\n\n    // Find the index in Schema\n    let mut maybe_index = None;\n    let indexes: Vec<_> = resolver.with_schema(database_id, |s| {\n        s.indexes\n            .values()\n            .flat_map(|v| v.iter())\n            .filter(|idx| idx.name == idx_name)\n            .cloned()\n            .collect()\n    });\n    if let Some(idx) = indexes.first() {\n        maybe_index = Some(idx.clone());\n    }\n\n    // If there's no index if_exist is true,\n    // then return normaly, otherwise show an error.\n    if maybe_index.is_none() {\n        if if_exists {\n            return Ok(());\n        } else {\n            return Err(crate::error::LimboError::InvalidArgument(format!(\n                \"No such index: {}\",\n                &idx_name\n            )));\n        }\n    }\n    // Return an error if the index is associated with a unique or primary key constraint.\n    if let Some(ref idx) = maybe_index {\n        if idx.unique {\n            return Err(crate::error::LimboError::InvalidArgument(\n                \"index associated with UNIQUE or PRIMARY KEY constraint cannot be dropped\"\n                    .to_string(),\n            ));\n        }\n    }\n\n    let cdc_table = prepare_cdc_if_necessary(program, resolver.schema(), SQLITE_TABLEID)?;\n\n    // According to sqlite should emit Null instruction\n    // but why?\n    let null_reg = program.alloc_register();\n    program.emit_null(null_reg, None);\n\n    // String8; r[3] = 'some idx name'\n    let index_name_reg = program.emit_string8_new_reg(idx_name);\n    // String8; r[4] = 'index'\n    let index_str_reg = program.emit_string8_new_reg(\"index\".to_string());\n\n    // for r[5]=rowid\n    let row_id_reg = program.alloc_register();\n\n    // We're going to use this cursor to search through sqlite_schema\n    let sqlite_table = resolver.schema().get_btree_table(SQLITE_TABLEID).unwrap();\n    let sqlite_schema_cursor_id =\n        program.alloc_cursor_id(CursorType::BTreeTable(sqlite_table.clone()));\n\n    // Open sqlite_schema for writing\n    program.emit_insn(Insn::OpenWrite {\n        cursor_id: sqlite_schema_cursor_id,\n        root_page: RegisterOrLiteral::Literal(sqlite_table.root_page),\n        db: database_id,\n    });\n\n    let loop_start_label = program.allocate_label();\n    let loop_end_label = program.allocate_label();\n    program.emit_insn(Insn::Rewind {\n        cursor_id: sqlite_schema_cursor_id,\n        pc_if_empty: loop_end_label,\n    });\n    program.resolve_label(loop_start_label, program.offset());\n\n    // Read sqlite_schema.name into dest_reg\n    let dest_reg = program.alloc_register();\n    program.emit_column_or_rowid(sqlite_schema_cursor_id, 1, dest_reg);\n\n    // if current column is not index_name then jump to Next\n    // skip if sqlite_schema.name != index_name_reg\n    let next_label = program.allocate_label();\n    program.emit_insn(Insn::Ne {\n        lhs: index_name_reg,\n        rhs: dest_reg,\n        target_pc: next_label,\n        flags: CmpInsFlags::default(),\n        collation: program.curr_collation(),\n    });\n\n    // read type of table\n    // skip if sqlite_schema.type != 'index' (index_str_reg)\n    program.emit_column_or_rowid(sqlite_schema_cursor_id, 0, dest_reg);\n    // if current column is not index then jump to Next\n    program.emit_insn(Insn::Ne {\n        lhs: index_str_reg,\n        rhs: dest_reg,\n        target_pc: next_label,\n        flags: CmpInsFlags::default(),\n        collation: program.curr_collation(),\n    });\n\n    program.emit_insn(Insn::RowId {\n        cursor_id: sqlite_schema_cursor_id,\n        dest: row_id_reg,\n    });\n\n    let label_once_end = program.allocate_label();\n    program.emit_insn(Insn::Once {\n        target_pc_when_reentered: label_once_end,\n    });\n    program.resolve_label(label_once_end, program.offset());\n\n    if let Some((cdc_cursor_id, _)) = cdc_table {\n        let before_record_reg = if program.capture_data_changes_info().has_before() {\n            Some(emit_cdc_full_record(\n                program,\n                &sqlite_table.columns,\n                sqlite_schema_cursor_id,\n                row_id_reg,\n                sqlite_table.is_strict,\n            ))\n        } else {\n            None\n        };\n        emit_cdc_insns(\n            program,\n            resolver,\n            OperationMode::DELETE,\n            cdc_cursor_id,\n            row_id_reg,\n            before_record_reg,\n            None,\n            None,\n            SQLITE_TABLEID,\n        )?;\n    }\n\n    program.emit_insn(Insn::Delete {\n        cursor_id: sqlite_schema_cursor_id,\n        table_name: \"sqlite_schema\".to_string(),\n        is_part_of_update: false,\n    });\n\n    program.resolve_label(next_label, program.offset());\n    program.emit_insn(Insn::Next {\n        cursor_id: sqlite_schema_cursor_id,\n        pc_if_next: loop_start_label,\n    });\n\n    program.resolve_label(loop_end_label, program.offset());\n    if let Some((cdc_cursor_id, _)) = cdc_table {\n        emit_cdc_autocommit_commit(program, resolver, cdc_cursor_id)?;\n    }\n\n    let current_schema_version = resolver.with_schema(database_id, |s| s.schema_version);\n    program.emit_insn(Insn::SetCookie {\n        db: database_id,\n        cookie: Cookie::SchemaVersion,\n        value: current_schema_version as i32 + 1,\n        p5: 0,\n    });\n\n    let index = maybe_index.unwrap();\n    if index.index_method.is_some() && !index.is_backing_btree_index() {\n        let cursor_id = program.alloc_cursor_index(None, &index)?;\n        program.emit_insn(Insn::IndexMethodDestroy {\n            db: database_id,\n            cursor_id,\n        });\n    } else {\n        // Destroy index btree\n        program.emit_insn(Insn::Destroy {\n            db: database_id,\n            root: index.root_page,\n            former_root_reg: 0,\n            is_temp: 0,\n        });\n    }\n\n    // Remove from the Schema any mention of the index\n    program.emit_insn(Insn::DropIndex {\n        index: index.clone(),\n        db: database_id,\n    });\n\n    Ok(())\n}\n\n/// Translate `OPTIMIZE INDEX [idx_name]` statement.\n/// If idx_name is provided, optimize that specific index.\n/// If idx_name is None, optimize all index method indexes.\npub fn translate_optimize(\n    idx_name: Option<ast::QualifiedName>,\n    resolver: &Resolver,\n    program: &mut ProgramBuilder,\n    connection: &Arc<crate::Connection>,\n) -> crate::Result<()> {\n    if !connection.experimental_index_method_enabled() {\n        crate::bail_parse_error!(\n            \"OPTIMIZE INDEX requires experimental index method feature. Enable with --experimental-index-method flag\"\n        )\n    }\n\n    let opts = ProgramBuilderOpts {\n        num_cursors: 5,\n        approx_num_insns: 20,\n        approx_num_labels: 2,\n    };\n    program.extend(&opts);\n\n    let mut indexes_to_optimize = Vec::new();\n\n    if let Some(name) = idx_name {\n        // Optimize a specific index\n        let idx_name = normalize_ident(name.name.as_str());\n        let mut found = false;\n\n        for val in resolver.schema().indexes.values() {\n            for idx in val {\n                if idx.name == idx_name {\n                    if idx.index_method.is_some() && !idx.is_backing_btree_index() {\n                        indexes_to_optimize.push(idx.clone());\n                    } else {\n                        // Not an index method index - nothing to optimize\n                        tracing::debug!(\n                            \"OPTIMIZE INDEX: {} is not an index method index, nothing to optimize\",\n                            idx_name\n                        );\n                    }\n                    found = true;\n                    break;\n                }\n            }\n            if found {\n                break;\n            }\n        }\n\n        if !found {\n            return Err(LimboError::InvalidArgument(format!(\n                \"No such index: {idx_name}\"\n            )));\n        }\n    } else {\n        // Optimize all index method indexes\n        for val in resolver.schema().indexes.values() {\n            for idx in val {\n                if idx.index_method.is_some() && !idx.is_backing_btree_index() {\n                    indexes_to_optimize.push(idx.clone());\n                }\n            }\n        }\n\n        if indexes_to_optimize.is_empty() {\n            tracing::debug!(\"OPTIMIZE INDEX: no index method indexes found to optimize\");\n            return Ok(());\n        }\n    }\n\n    // Emit optimize instructions for each index method index\n    for idx in &indexes_to_optimize {\n        let cursor_id = program.alloc_cursor_index(None, idx)?;\n        program.emit_insn(Insn::IndexMethodOptimize {\n            db: crate::MAIN_DB_ID,\n            cursor_id,\n        });\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "core/translate/insert.rs",
    "content": "use crate::turso_debug_assert;\nuse crate::{\n    error::{SQLITE_CONSTRAINT_NOTNULL, SQLITE_CONSTRAINT_PRIMARYKEY, SQLITE_CONSTRAINT_UNIQUE},\n    schema::{\n        self, BTreeTable, ColDef, Column, Index, IndexColumn, ResolvedFkRef, Table,\n        SQLITE_SEQUENCE_TABLE_NAME,\n    },\n    sync::Arc,\n    translate::{\n        emitter::{\n            delete::emit_fk_child_decrement_on_delete, emit_cdc_autocommit_commit,\n            emit_cdc_full_record, emit_cdc_insns, emit_cdc_patch_record, emit_check_constraints,\n            prepare_cdc_if_necessary, OperationMode, Resolver,\n        },\n        expr::{\n            bind_and_rewrite_expr, emit_returning_results, emit_returning_scan_back,\n            process_returning_clause, restore_returning_row_image_in_cache,\n            seed_returning_row_image_in_cache, translate_expr, translate_expr_no_constant_opt,\n            walk_expr_mut, BindingBehavior, NoConstantOptReason, ReturningBufferCtx, WalkControl,\n        },\n        fkeys::{\n            build_index_affinity_string, emit_fk_restrict_halt, emit_fk_violation,\n            emit_guarded_fk_decrement, index_probe, open_read_index, open_read_table,\n            ForeignKeyActions,\n        },\n        plan::{\n            ColumnUsedMask, EvalAt, JoinedTable, Operation, QueryDestination, ResultSetColumn,\n            TableReferences,\n        },\n        planner::{plan_ctes_as_outer_refs, ROWID_STRS},\n        select::translate_select,\n        stmt_journal::{any_index_or_ipk_has_replace, set_insert_stmt_journal_flags},\n        subquery::{\n            emit_non_from_clause_subqueries_for_eval_at, emit_non_from_clause_subquery,\n            plan_subqueries_from_returning,\n        },\n        trigger_exec::{\n            fire_trigger, get_relevant_triggers_type_and_time, has_relevant_triggers_type_only,\n            TriggerContext,\n        },\n        upsert::{\n            collect_set_clauses_for_upsert, emit_upsert, resolve_upsert_target,\n            ResolvedUpsertTarget,\n        },\n    },\n    util::normalize_ident,\n    vdbe::{\n        affinity::Affinity,\n        builder::{CursorKey, CursorType, ProgramBuilder, ProgramBuilderOpts},\n        insn::{to_u16, CmpInsFlags, IdxInsertFlags, InsertFlags, Insn, RegisterOrLiteral},\n        BranchOffset,\n    },\n    CaptureDataChangesExt, Connection, LimboError, Result, VirtualTable,\n};\nuse std::num::NonZeroUsize;\nuse turso_macros::turso_assert;\nuse turso_parser::ast::{\n    self, Expr, InsertBody, OneSelect, QualifiedName, ResolveType, ResultColumn, TriggerEvent,\n    TriggerTime, Upsert, UpsertDo, With,\n};\n\n/// Validate anything with this insert statement that should throw an early parse error\nfn validate(\n    table_name: &str,\n    resolver: &Resolver,\n    table: &Table,\n    conn: &Arc<Connection>,\n) -> Result<()> {\n    // Check if this is a system table that should be protected from direct writes\n    if !conn.is_nested_stmt()\n        && !conn.is_mvcc_bootstrap_connection()\n        && !crate::schema::can_write_to_table(table_name)\n    {\n        crate::bail_parse_error!(\"table {} may not be modified\", table_name);\n    }\n    // Check if this table has any incompatible dependent views\n    let incompatible_views = resolver\n        .schema()\n        .has_incompatible_dependent_views(table_name);\n    if !incompatible_views.is_empty() {\n        use crate::incremental::compiler::DBSP_CIRCUIT_VERSION;\n        crate::bail_parse_error!(\n            \"Cannot INSERT into table '{}' because it has incompatible dependent materialized view(s): {}. \\n\\\n             These views were created with a different DBSP version than the current version ({}). \\n\\\n             Please DROP and recreate the view(s) before modifying this table.\",\n            table_name,\n            incompatible_views.join(\", \"),\n            DBSP_CIRCUIT_VERSION\n        );\n    }\n\n    // Check if this is a materialized view\n    if resolver.schema().is_materialized_view(table_name) {\n        crate::bail_parse_error!(\"cannot modify materialized view {}\", table_name);\n    }\n    if table.btree().is_some_and(|t| !t.has_rowid) {\n        crate::bail_parse_error!(\"INSERT into WITHOUT ROWID table is not supported\");\n    }\n    if table.btree().is_some_and(|t| t.has_autoincrement) && conn.mvcc_enabled() {\n        crate::bail_parse_error!(\n            \"AUTOINCREMENT is not supported in MVCC mode (journal_mode=experimental_mvcc)\"\n        );\n    }\n\n    Ok(())\n}\n\npub struct TempTableCtx {\n    cursor_id: usize,\n    loop_start_label: BranchOffset,\n    loop_end_label: BranchOffset,\n}\n\n/// Labels for INSERT loop control flow\npub struct InsertLoopLabels {\n    /// Beginning of the loop for multiple-row inserts\n    pub loop_start: BranchOffset,\n    /// Label to jump to when a row is done processing (either inserted or upserted)\n    pub row_done: BranchOffset,\n    /// Jump here at the complete end of the statement\n    pub stmt_epilogue: BranchOffset,\n    /// Jump here when the insert value SELECT source has been fully exhausted\n    pub select_exhausted: Option<BranchOffset>,\n}\n\n/// Labels for rowid/key generation flow\npub struct InsertKeyLabels {\n    /// Label to jump to when a generated key is ready for uniqueness check\n    pub key_ready_for_check: BranchOffset,\n    /// Label to jump to when no key is provided and one must be generated\n    pub key_generation: BranchOffset,\n}\n\n#[allow(dead_code)]\npub struct InsertEmitCtx<'a> {\n    /// Parent table being inserted into\n    pub table: &'a Arc<BTreeTable>,\n\n    /// Index cursors we need to populate for this table\n    /// (idx name, root_page, idx cursor id)\n    pub idx_cursors: Vec<(String, i64, usize)>,\n\n    /// Context for if the insert values are materialized first\n    /// into a temporary table\n    pub temp_table_ctx: Option<TempTableCtx>,\n    /// on conflict, default to ABORT\n    pub on_conflict: ResolveType,\n    /// The original statement-level ON CONFLICT clause (None = no explicit clause)\n    pub statement_on_conflict: Option<ResolveType>,\n    /// Arity of the insert values\n    pub num_values: usize,\n    /// The yield register, if a coroutine is used to yield multiple rows\n    pub yield_reg_opt: Option<usize>,\n    /// The register to hold the rowid of a conflicting row\n    pub conflict_rowid_reg: usize,\n    /// The cursor id of the table being inserted into\n    pub cursor_id: usize,\n\n    /// Label to jump to on HALT\n    pub halt_label: BranchOffset,\n    /// Labels for loop control flow\n    pub loop_labels: InsertLoopLabels,\n    /// Labels for key generation flow\n    pub key_labels: InsertKeyLabels,\n\n    /// CDC table info\n    pub cdc_table: Option<(usize, Arc<BTreeTable>)>,\n    /// Autoincrement sequence table info\n    pub autoincrement_meta: Option<AutoincMeta>,\n    /// The database index (0 = main, 1 = temp, 2+ = attached)\n    pub database_id: usize,\n    /// Ephemeral table for buffering RETURNING results.\n    /// When present, RETURNING rows are buffered into an ephemeral table during the DML loop,\n    /// then scanned back and yielded to the caller after all DML is complete.\n    pub returning_buffer: Option<ReturningBufferCtx>,\n}\n\nimpl<'a> InsertEmitCtx<'a> {\n    #[allow(clippy::too_many_arguments)]\n    fn new(\n        program: &mut ProgramBuilder,\n        resolver: &Resolver,\n        table: &'a Arc<BTreeTable>,\n        on_conflict: Option<ResolveType>,\n        cdc_table: Option<(usize, Arc<BTreeTable>)>,\n        num_values: usize,\n        temp_table_ctx: Option<TempTableCtx>,\n        database_id: usize,\n        _connection: &Arc<crate::Connection>,\n    ) -> Result<Self> {\n        // allocate cursor id's for each btree index cursor we'll need to populate the indexes\n        let indices: Vec<_> = resolver.with_schema(database_id, |s| {\n            s.get_indices(table.name.as_str()).cloned().collect()\n        });\n        let mut idx_cursors = Vec::new();\n        for idx in &indices {\n            idx_cursors.push((\n                idx.name.clone(),\n                idx.root_page,\n                program.alloc_cursor_index(None, idx)?,\n            ));\n        }\n        let loop_labels = InsertLoopLabels {\n            loop_start: program.allocate_label(),\n            row_done: program.allocate_label(),\n            stmt_epilogue: program.allocate_label(),\n            select_exhausted: None,\n        };\n        let key_labels = InsertKeyLabels {\n            key_ready_for_check: program.allocate_label(),\n            key_generation: program.allocate_label(),\n        };\n        Ok(Self {\n            table,\n            idx_cursors,\n            temp_table_ctx,\n            on_conflict: on_conflict.unwrap_or(ResolveType::Abort),\n            statement_on_conflict: on_conflict,\n            yield_reg_opt: None,\n            conflict_rowid_reg: program.alloc_register(),\n            cursor_id: 0, // set later in emit_source_emission\n            halt_label: program.allocate_label(),\n            loop_labels,\n            key_labels,\n            cdc_table,\n            num_values,\n            autoincrement_meta: None,\n            database_id,\n            returning_buffer: None,\n        })\n    }\n}\n\n#[allow(clippy::too_many_arguments)]\npub fn translate_insert(\n    resolver: &mut Resolver,\n    on_conflict: Option<ResolveType>,\n    tbl_name: QualifiedName,\n    columns: Vec<ast::Name>,\n    mut body: InsertBody,\n    mut returning: Vec<ResultColumn>,\n    with: Option<With>,\n    program: &mut ProgramBuilder,\n    connection: &Arc<crate::Connection>,\n) -> Result<()> {\n    let opts = ProgramBuilderOpts {\n        num_cursors: 1,\n        approx_num_insns: 30,\n        approx_num_labels: 5,\n    };\n    program.extend(&opts);\n\n    // Merge INSERT's WITH clause into the SELECT source's WITH clause.\n    // For VALUES/DEFAULT VALUES with subqueries, we route through the multi-row\n    // path which goes through translate_select and handles CTEs properly.\n    // We also keep a copy for RETURNING clause subqueries.\n    let with_for_returning = with.clone();\n    if let Some(insert_with) = with {\n        if let InsertBody::Select(select, _) = &mut body {\n            match &mut select.with {\n                Some(select_with) => {\n                    // Prepend INSERT's CTEs to SELECT's CTEs\n                    let mut merged = insert_with.ctes;\n                    merged.append(&mut select_with.ctes);\n                    select_with.ctes = merged;\n                    select_with.recursive |= insert_with.recursive;\n                }\n                None => select.with = Some(insert_with),\n            }\n        } else {\n            // WITH clause on INSERT with VALUES or DEFAULT VALUES is not useful\n            // e.g. WITH unused AS (SELECT c FROM a) INSERT INTO b VALUES (1, 2, 3)\n            // but: we can, and indeed must, just ignore it instead of erroring.\n            // leaving this empty else block here for documentation.\n        }\n        // For DEFAULT VALUES/VALUES without SELECT body, CTEs are still needed\n        // for RETURNING clause subqueries - handled below via with_for_returning.\n    }\n\n    let database_id = resolver.resolve_database_id(&tbl_name)?;\n    let table_name = &tbl_name.name;\n    let table = match resolver.with_schema(database_id, |s| s.get_table(table_name.as_str())) {\n        Some(table) => table,\n        None => crate::bail_parse_error!(\"no such table: {}\", table_name),\n    };\n    if program.trigger.is_some() && table.virtual_table().is_some() {\n        crate::bail_parse_error!(\"unsafe use of virtual table \\\"{}\\\"\", tbl_name.name.as_str());\n    }\n    validate(table_name.as_str(), resolver, &table, connection)?;\n\n    let fk_enabled = connection.foreign_keys_enabled();\n    if let Some(virtual_table) = &table.virtual_table() {\n        translate_virtual_table_insert(\n            program,\n            virtual_table.clone(),\n            columns,\n            body,\n            on_conflict,\n            resolver,\n            connection,\n        )?;\n        return Ok(());\n    }\n\n    let Some(btree_table) = table.btree() else {\n        crate::bail_parse_error!(\"no such table: {}\", table_name);\n    };\n\n    let BoundInsertResult {\n        mut values,\n        mut upsert_actions,\n        inserting_multiple_rows,\n    } = bind_insert(\n        program,\n        resolver,\n        &table,\n        &mut body,\n        on_conflict.unwrap_or(ResolveType::Abort),\n        database_id,\n    )?;\n\n    if inserting_multiple_rows && btree_table.has_autoincrement {\n        ensure_sequence_initialized(program, resolver, &btree_table, database_id)?;\n    }\n\n    let cdc_table = prepare_cdc_if_necessary(program, resolver.schema(), table.get_name())?;\n\n    if crate::is_attached_db(database_id) {\n        let schema_cookie = resolver.with_schema(database_id, |s| s.schema_version);\n        program.begin_write_on_database(database_id, schema_cookie);\n    }\n\n    let mut table_references = TableReferences::new(\n        vec![JoinedTable {\n            table: Table::BTree(\n                table\n                    .btree()\n                    .expect(\"we shouldn't have got here without a BTree table\"),\n            ),\n            identifier: table_name.to_string(),\n            internal_id: program.table_reference_counter.next(),\n            op: Operation::default_scan_for(&table),\n            join_info: None,\n            col_used_mask: ColumnUsedMask::default(),\n            column_use_counts: Vec::new(),\n            expression_index_usages: Vec::new(),\n            database_id,\n            indexed: None,\n        }],\n        vec![],\n    );\n\n    // Plan CTEs and add them as outer query references for RETURNING subquery resolution\n    plan_ctes_as_outer_refs(\n        with_for_returning,\n        resolver,\n        program,\n        &mut table_references,\n        connection,\n    )?;\n\n    // Plan subqueries in RETURNING expressions before processing\n    // (so SubqueryResult nodes are cloned into result_columns)\n    let mut returning_subqueries = vec![];\n    plan_subqueries_from_returning(\n        program,\n        &mut returning_subqueries,\n        &mut table_references,\n        &mut returning,\n        resolver,\n        connection,\n    )?;\n\n    // Process RETURNING clause using shared module\n    let mut result_columns =\n        process_returning_clause(&mut returning, &mut table_references, resolver)?;\n    let has_fks = fk_enabled\n        && (resolver.with_schema(database_id, |s| s.has_child_fks(table_name.as_str()))\n            || resolver.with_schema(database_id, |s| {\n                s.any_resolved_fks_referencing(table_name.as_str())\n            }));\n\n    let mut ctx = InsertEmitCtx::new(\n        program,\n        resolver,\n        &btree_table,\n        on_conflict,\n        cdc_table,\n        values.len(),\n        None,\n        database_id,\n        connection,\n    )?;\n    program.has_statement_conflict = on_conflict.is_some();\n\n    // Open an ephemeral table for buffering RETURNING results.\n    // All DML completes before any RETURNING rows are yielded to the caller.\n    if !result_columns.is_empty() {\n        let ret_cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(btree_table.clone()));\n        program.emit_insn(Insn::OpenEphemeral {\n            cursor_id: ret_cursor_id,\n            is_table: true,\n        });\n        ctx.returning_buffer = Some(ReturningBufferCtx {\n            cursor_id: ret_cursor_id,\n            num_columns: result_columns.len(),\n        });\n    }\n\n    init_source_emission(\n        program,\n        &table,\n        connection,\n        &mut ctx,\n        resolver,\n        &mut values,\n        body,\n        &columns,\n        &table_references,\n        database_id,\n    )?;\n    let has_upsert = !upsert_actions.is_empty();\n\n    // Set up the program to return result columns if RETURNING is specified\n    if !result_columns.is_empty() {\n        program.result_columns.clone_from(&result_columns);\n    }\n    let insertion = build_insertion(program, &table, &columns, ctx.num_values)?;\n\n    translate_rows_and_open_tables(\n        program,\n        resolver,\n        &insertion,\n        &ctx,\n        &values,\n        inserting_multiple_rows,\n    )?;\n\n    // Emit subqueries for RETURNING clause (uncorrelated subqueries are evaluated once)\n    emit_non_from_clause_subqueries_for_eval_at(\n        program,\n        resolver,\n        &mut returning_subqueries,\n        &[],\n        Some(&table_references),\n        EvalAt::BeforeLoop,\n        |_| true,\n    )?;\n\n    let has_user_provided_rowid = insertion.key.is_provided_by_user();\n\n    if ctx.table.has_autoincrement {\n        init_autoincrement(program, &mut ctx, resolver)?;\n    }\n\n    // Fire BEFORE INSERT triggers\n\n    let relevant_before_triggers: Vec<_> = resolver.with_schema(database_id, |s| {\n        get_relevant_triggers_type_and_time(\n            s,\n            TriggerEvent::Insert,\n            TriggerTime::Before,\n            None,\n            &btree_table,\n        )\n        .collect()\n    });\n\n    let has_before_triggers = !relevant_before_triggers.is_empty();\n    if has_before_triggers {\n        // In SQLite, NEW.<rowid_alias> returns -1 in BEFORE INSERT triggers when the rowid\n        // hasn't been assigned yet (i.e., it's NULL). We need to temporarily set the key\n        // register to -1 so the trigger sees the correct value.\n        let saved_key_reg = if has_user_provided_rowid {\n            // User provided a value that might be NULL. Save original value, replace NULL with -1.\n            let save_reg = program.alloc_register();\n            program.emit_insn(Insn::Copy {\n                src_reg: insertion.key_register(),\n                dst_reg: save_reg,\n                extra_amount: 0,\n            });\n            let skip_label = program.allocate_label();\n            program.emit_insn(Insn::NotNull {\n                reg: insertion.key_register(),\n                target_pc: skip_label,\n            });\n            program.emit_insn(Insn::Integer {\n                value: -1,\n                dest: insertion.key_register(),\n            });\n            program.preassign_label_to_next_insn(skip_label);\n            Some(save_reg)\n        } else {\n            // Key is auto-generated, register is uninitialized. Set to -1.\n            program.emit_insn(Insn::Integer {\n                value: -1,\n                dest: insertion.key_register(),\n            });\n            None\n        };\n        // Build NEW registers: for rowid alias columns, use the rowid register; otherwise use column register\n        let new_registers: Vec<usize> = insertion\n            .col_mappings\n            .iter()\n            .map(|col_mapping| {\n                if col_mapping.column.is_rowid_alias() {\n                    insertion.key_register()\n                } else {\n                    col_mapping.register\n                }\n            })\n            .chain(std::iter::once(insertion.key_register()))\n            .collect();\n        // Determine the conflict resolution to propagate to triggers:\n        // 1. If there's already an override from UPSERT DO UPDATE context, use it (forces ABORT)\n        // 2. If the INSERT uses an explicit ON CONFLICT clause (not default ABORT),\n        //    propagate it to trigger statements (per SQLite semantics, the outer\n        //    statement's conflict resolution overrides the trigger body's)\n        // 3. Otherwise, don't override (use statement's own conflict resolution)\n        let trigger_ctx = if let Some(override_conflict) = program.trigger_conflict_override {\n            TriggerContext::new_with_override_conflict(\n                btree_table.clone(),\n                Some(new_registers),\n                None, // No OLD for INSERT\n                override_conflict,\n            )\n        } else if !matches!(ctx.on_conflict, ResolveType::Abort) {\n            TriggerContext::new_with_override_conflict(\n                btree_table.clone(),\n                Some(new_registers),\n                None, // No OLD for INSERT\n                ctx.on_conflict,\n            )\n        } else {\n            TriggerContext::new(\n                btree_table.clone(),\n                Some(new_registers),\n                None, // No OLD for INSERT\n            )\n        };\n        for trigger in relevant_before_triggers {\n            fire_trigger(\n                program,\n                resolver,\n                trigger,\n                &trigger_ctx,\n                connection,\n                database_id,\n                ctx.loop_labels.row_done,\n            )?;\n        }\n        // Restore the original key register value so the post-trigger NotNull check\n        // correctly routes NULL keys to NewRowid generation.\n        if let Some(save_reg) = saved_key_reg {\n            program.emit_insn(Insn::Copy {\n                src_reg: save_reg,\n                dst_reg: insertion.key_register(),\n                extra_amount: 0,\n            });\n        }\n    }\n\n    if has_user_provided_rowid {\n        let must_be_int_label = program.allocate_label();\n\n        program.emit_insn(Insn::NotNull {\n            reg: insertion.key_register(),\n            target_pc: must_be_int_label,\n        });\n\n        program.emit_insn(Insn::Goto {\n            target_pc: ctx.key_labels.key_generation,\n        });\n\n        program.preassign_label_to_next_insn(must_be_int_label);\n        program.emit_insn(Insn::MustBeInt {\n            reg: insertion.key_register(),\n        });\n\n        program.emit_insn(Insn::Goto {\n            target_pc: ctx.key_labels.key_ready_for_check,\n        });\n    }\n\n    program.preassign_label_to_next_insn(ctx.key_labels.key_generation);\n\n    emit_rowid_generation(program, &ctx, &insertion, resolver)?;\n\n    program.preassign_label_to_next_insn(ctx.key_labels.key_ready_for_check);\n\n    if ctx.table.is_strict {\n        // Pre-encode TypeCheck: validate input types match the custom type's\n        // declared value type BEFORE encoding. This catches type mismatches\n        // (e.g. TEXT into an INTEGER-based custom type) that would otherwise\n        // be silently converted by the encode expression.\n        program.emit_insn(Insn::TypeCheck {\n            start_reg: insertion.first_col_register(),\n            count: insertion.col_mappings.len(),\n            check_generated: true,\n            table_reference: BTreeTable::input_type_check_table_ref(\n                ctx.table,\n                resolver.schema(),\n                None,\n            ),\n        });\n\n        // Encode values for columns with custom types.\n        emit_custom_type_encode(program, resolver, &insertion, &ctx.table.name)?;\n\n        // Post-encode TypeCheck: validate that encode produced the correct\n        // storage type (BASE).\n        program.emit_insn(Insn::TypeCheck {\n            start_reg: insertion.first_col_register(),\n            count: insertion.col_mappings.len(),\n            check_generated: true,\n            table_reference: BTreeTable::type_check_table_ref(ctx.table, resolver.schema()),\n        });\n    } else {\n        // For non-STRICT tables, apply column affinity to the values.\n        // This must happen early so that both index records and the table record\n        // use the converted values. SQLite does this with OP_Affinity before\n        // any index or constraint checks.\n        let affinity = insertion\n            .col_mappings\n            .iter()\n            .map(|col_mapping| col_mapping.column.affinity());\n\n        // Only emit Affinity if there's meaningful affinity to apply\n        // (i.e., not all BLOB/NONE affinity)\n        if affinity.clone().any(|a| a != Affinity::Blob) {\n            if let Ok(count) = std::num::NonZeroUsize::try_from(insertion.col_mappings.len()) {\n                program.emit_insn(Insn::Affinity {\n                    start_reg: insertion.first_col_register(),\n                    count,\n                    affinities: affinity.map(|a| a.aff_mask()).collect(),\n                });\n            }\n        }\n    }\n\n    // For AUTOINCREMENT tables with an explicit rowid, update sqlite_sequence\n    // before CHECK constraints. SQLite updates sqlite_sequence even when\n    // INSERT OR IGNORE skips the row due to a CHECK failure.\n    if has_user_provided_rowid {\n        if let Some(AutoincMeta {\n            seq_cursor_id,\n            r_seq,\n            r_seq_rowid,\n            table_name_reg,\n        }) = ctx.autoincrement_meta\n        {\n            turso_assert!(ctx.table.has_autoincrement);\n            reload_autoincrement_state(\n                program,\n                AutoincMeta {\n                    seq_cursor_id,\n                    r_seq,\n                    r_seq_rowid,\n                    table_name_reg,\n                },\n            );\n            // Existing sqlite_sequence row: update only when explicit key advances seq.\n            let missing_row_label = program.allocate_label();\n            let explicit_done_label = program.allocate_label();\n            program.emit_insn(Insn::IsNull {\n                reg: r_seq_rowid,\n                target_pc: missing_row_label,\n            });\n\n            let skip_seq_update_label = program.allocate_label();\n            program.emit_insn(Insn::Le {\n                lhs: insertion.key_register(),\n                rhs: r_seq,\n                target_pc: skip_seq_update_label,\n                flags: Default::default(),\n                collation: None,\n            });\n\n            emit_update_sqlite_sequence(\n                program,\n                resolver,\n                ctx.database_id,\n                seq_cursor_id,\n                r_seq_rowid,\n                table_name_reg,\n                insertion.key_register(),\n            )?;\n            program.emit_insn(Insn::Goto {\n                target_pc: explicit_done_label,\n            });\n            program.preassign_label_to_next_insn(skip_seq_update_label);\n\n            // Missing sqlite_sequence row: materialize it once with max(existing_seq, explicit_key).\n            // For first explicit negative insert this yields seq=0, matching SQLite.\n            program.preassign_label_to_next_insn(missing_row_label);\n            let seq_to_write_reg = program.alloc_register();\n            program.emit_insn(Insn::Copy {\n                src_reg: r_seq,\n                dst_reg: seq_to_write_reg,\n                extra_amount: 0,\n            });\n            program.emit_insn(Insn::MemMax {\n                dest_reg: seq_to_write_reg,\n                src_reg: insertion.key_register(),\n            });\n            emit_update_sqlite_sequence(\n                program,\n                resolver,\n                ctx.database_id,\n                seq_cursor_id,\n                r_seq_rowid,\n                table_name_reg,\n                seq_to_write_reg,\n            )?;\n            program.preassign_label_to_next_insn(explicit_done_label);\n        }\n    }\n\n    // Evaluate CHECK constraints after type affinity/TypeCheck but before other constraints\n    emit_check_constraints(\n        program,\n        &ctx.table.check_constraints,\n        resolver,\n        &ctx.table.name,\n        insertion.key_register(),\n        insertion.col_mappings.iter().filter_map(|m| {\n            m.column.name.as_deref().map(|n| {\n                // Rowid alias columns have NULL in their register (the real value\n                // lives in the key register), so point CHECK to the key register.\n                let reg = if m.column.is_rowid_alias() {\n                    insertion.key_register()\n                } else {\n                    m.register\n                };\n                (n, reg)\n            })\n        }),\n        connection,\n        ctx.on_conflict,\n        ctx.loop_labels.row_done,\n        Some(&table_references),\n    )?;\n\n    // Build a list of upsert constraints/indexes we need to run preflight\n    // checks against, in the proper order of evaluation,\n    let constraints = build_constraints_to_check(\n        table_name.as_str(),\n        &upsert_actions,\n        has_user_provided_rowid,\n        resolver,\n        connection,\n        ctx.database_id,\n        ctx.table.rowid_alias_conflict_clause,\n        ctx.statement_on_conflict.is_some(),\n    );\n\n    // We need to separate index handling and insertion into a `preflight` and a\n    // `commit` phase, because in UPSERT mode we might need to skip the actual insertion, as we can\n    // have a naked ON CONFLICT DO NOTHING, so if we eagerly insert any indexes, we could insert\n    // invalid index entries before we hit a conflict down the line.\n    //\n    // REPLACE (whether statement-level OR REPLACE or constraint-level ON CONFLICT REPLACE)\n    // inserts eagerly in preflight because it needs to delete-then-insert per index.\n    // When there's no statement-level override (e.g. INSERT OR ...) and no UPSERT,\n    // individual constraints keep their DDL modes. If some use REPLACE and others\n    // don't, the preflight eagerly handles REPLACE indexes (delete+reinsert) while\n    // deferring non-REPLACE indexes to the commit phase (skip_replace_indexes).\n    // This mixed-mode detection is unnecessary when a statement override exists,\n    // because the override applies uniformly to all constraints.\n    let has_ddl_replace = ctx.statement_on_conflict.is_none()\n        && upsert_actions.is_empty()\n        && resolver.with_schema(ctx.database_id, |schema| {\n            any_index_or_ipk_has_replace(\n                ctx.table.rowid_alias_conflict_clause,\n                schema\n                    .get_indices(ctx.table.name.as_str())\n                    .map(|idx| idx.on_conflict),\n            )\n        });\n    let on_replace = (matches!(ctx.on_conflict, ResolveType::Replace) && upsert_actions.is_empty())\n        || has_ddl_replace;\n    let mut preflight_ctx = PreflightCtx {\n        upsert_actions: &upsert_actions,\n        on_replace,\n        effective_on_conflict: ctx.on_conflict,\n        connection,\n        table_references: &mut table_references,\n    };\n    // NOT NULL default substitution must happen before index key registers are\n    // copied in preflight constraint checks. Otherwise the index entry gets NULL\n    // while the table row gets the default value, causing integrity_check failures.\n    emit_notnulls(program, &ctx, &insertion, resolver)?;\n\n    emit_preflight_constraint_checks(\n        program,\n        &mut ctx,\n        resolver,\n        &insertion,\n        &constraints,\n        &mut preflight_ctx,\n    )?;\n\n    // Create and insert the record\n    let affinity_str = insertion\n        .col_mappings\n        .iter()\n        .map(|col_mapping| {\n            col_mapping\n                .column\n                .affinity_with_strict(ctx.table.is_strict)\n                .aff_mask()\n        })\n        .collect::<String>();\n\n    program.emit_insn(Insn::MakeRecord {\n        start_reg: to_u16(insertion.first_col_register()),\n        count: to_u16(insertion.col_mappings.len()),\n        dest_reg: to_u16(insertion.record_register()),\n        index_name: None,\n        affinity_str: Some(affinity_str),\n    });\n\n    if has_fks {\n        // Child-side FK check must run before any writes (IdxInsert / Insert).\n        // For immediate FKs this emits a direct Halt, so no index entry is written\n        // when the parent is missing — matching SQLite's bytecode order.\n        emit_fk_child_insert_checks(\n            program,\n            &btree_table,\n            insertion.first_col_register(),\n            insertion.key_register(),\n            resolver,\n            database_id,\n        )?;\n    }\n\n    // Emit deferred index inserts for cases where preflight only checked constraints\n    // but didn't insert. This covers UPSERT and non-REPLACE conflict types (ABORT/FAIL/\n    // IGNORE/ROLLBACK). REPLACE inserts eagerly in the preflight phase because it needs\n    // to delete-then-insert per index.\n    //\n    // When statement-level REPLACE is active, ALL indexes use REPLACE and are eagerly\n    // inserted in preflight, so the commit phase can be skipped entirely. But when only\n    // some constraints have REPLACE (mixed mode via DDL), non-REPLACE indexes still need\n    // their entries committed here.\n    // Pure statement-level REPLACE (no upsert). When upsert actions exist,\n    // ON CONFLICT DO UPDATE takes precedence over REPLACE for matching\n    // constraints, so we can't skip the commit phase.\n    let statement_replace = matches!(ctx.on_conflict, ResolveType::Replace);\n    let skip_replace_indexes = has_ddl_replace && !statement_replace;\n    if has_upsert || !statement_replace {\n        emit_commit_phase(program, resolver, &insertion, &ctx, skip_replace_indexes)?;\n    }\n\n    let mut insert_flags = InsertFlags::new();\n\n    // For REPLACE (statement-level or constraint-level), we need to force a seek on the\n    // insert, as we may have already deleted the conflicting row and the cursor is not\n    // guaranteed to be positioned.\n    if matches!(ctx.on_conflict, ResolveType::Replace) || has_ddl_replace {\n        insert_flags = insert_flags.require_seek();\n    }\n    program.emit_insn(Insn::Insert {\n        cursor: ctx.cursor_id,\n        key_reg: insertion.key_register(),\n        record_reg: insertion.record_register(),\n        flag: insert_flags,\n        table_name: table_name.to_string(),\n    });\n\n    // Fire AFTER INSERT triggers\n    let relevant_after_triggers: Vec<_> = resolver.with_schema(database_id, |s| {\n        get_relevant_triggers_type_and_time(\n            s,\n            TriggerEvent::Insert,\n            TriggerTime::After,\n            None,\n            &btree_table,\n        )\n        .collect()\n    });\n    let has_after_triggers = !relevant_after_triggers.is_empty();\n    if has_after_triggers {\n        // Build raw NEW registers for AFTER triggers. Values are encoded at this point;\n        // fire_trigger will decode them via decode_trigger_registers.\n        let key_reg = insertion.key_register();\n        let new_registers_after: Vec<usize> = insertion\n            .col_mappings\n            .iter()\n            .map(|cm| {\n                if cm.column.is_rowid_alias() {\n                    key_reg\n                } else {\n                    cm.register\n                }\n            })\n            .chain(std::iter::once(key_reg))\n            .collect();\n        // Determine the conflict resolution to propagate to AFTER triggers (same logic as BEFORE)\n        let trigger_ctx_after = if let Some(override_conflict) = program.trigger_conflict_override {\n            TriggerContext::new_after_with_override_conflict(\n                btree_table.clone(),\n                Some(new_registers_after),\n                None,\n                override_conflict,\n            )\n        } else if !matches!(ctx.on_conflict, ResolveType::Abort) {\n            TriggerContext::new_after_with_override_conflict(\n                btree_table.clone(),\n                Some(new_registers_after),\n                None,\n                ctx.on_conflict,\n            )\n        } else {\n            TriggerContext::new_after(btree_table.clone(), Some(new_registers_after), None)\n        };\n        // RAISE(IGNORE) in an AFTER trigger should only abort the trigger body,\n        // not skip post-row work (FK counters, autoincrement, CDC, RETURNING).\n        // Use a label that falls through to the next instruction after the trigger loop.\n        let after_trigger_done = program.allocate_label();\n        for trigger in relevant_after_triggers {\n            fire_trigger(\n                program,\n                resolver,\n                trigger,\n                &trigger_ctx_after,\n                connection,\n                database_id,\n                after_trigger_done,\n            )?;\n        }\n        program.preassign_label_to_next_insn(after_trigger_done);\n    }\n\n    if has_fks {\n        // After the row is actually present, repair deferred counters for children referencing this NEW parent key.\n        // For REPLACE: delete increments counters above; the insert path should try to repay\n        // them, even for immediate/self-ref FKs.\n        emit_parent_side_fk_decrement_on_insert(\n            program,\n            &btree_table,\n            &insertion,\n            on_replace,\n            resolver,\n            database_id,\n        )?;\n    }\n\n    if let Some(AutoincMeta {\n        seq_cursor_id,\n        r_seq,\n        r_seq_rowid,\n        table_name_reg,\n    }) = ctx.autoincrement_meta\n    {\n        reload_autoincrement_state(\n            program,\n            AutoincMeta {\n                seq_cursor_id,\n                r_seq,\n                r_seq_rowid,\n                table_name_reg,\n            },\n        );\n        let no_update_needed_label = program.allocate_label();\n        program.emit_insn(Insn::Le {\n            lhs: insertion.key_register(),\n            rhs: r_seq,\n            target_pc: no_update_needed_label,\n            flags: Default::default(),\n            collation: None,\n        });\n\n        emit_update_sqlite_sequence(\n            program,\n            resolver,\n            ctx.database_id,\n            seq_cursor_id,\n            r_seq_rowid,\n            table_name_reg,\n            insertion.key_register(),\n        )?;\n\n        program.preassign_label_to_next_insn(no_update_needed_label);\n        program.emit_insn(Insn::Close {\n            cursor_id: seq_cursor_id,\n        });\n    }\n\n    // Emit update in the CDC table if necessary (after the INSERT updated the table)\n    if let Some((cdc_cursor_id, _)) = &ctx.cdc_table {\n        let cdc_has_after = program.capture_data_changes_info().has_after();\n        let after_record_reg = if cdc_has_after {\n            Some(emit_cdc_patch_record(\n                program,\n                &table,\n                insertion.first_col_register(),\n                insertion.record_register(),\n                insertion.key_register(),\n            ))\n        } else {\n            None\n        };\n        emit_cdc_insns(\n            program,\n            resolver,\n            OperationMode::INSERT,\n            *cdc_cursor_id,\n            insertion.key_register(),\n            None,\n            after_record_reg,\n            None,\n            table_name.as_str(),\n        )?;\n    }\n\n    if !returning_subqueries.is_empty() {\n        let target_table = table_references\n            .joined_tables()\n            .first()\n            .expect(\"INSERT RETURNING target table must exist\");\n        let cache_state = seed_returning_row_image_in_cache(\n            program,\n            &table_references,\n            insertion.first_col_register(),\n            insertion.key_register(),\n            resolver,\n        )?;\n        let result: Result<()> = (|| {\n            for subquery in returning_subqueries\n                .iter_mut()\n                .filter(|s| !s.has_been_evaluated())\n            {\n                let rerun_for_target_scan =\n                    subquery.reads_table(target_table.database_id, target_table.table.get_name());\n                let subquery_plan = subquery.consume_plan(EvalAt::Loop(0));\n                emit_non_from_clause_subquery(\n                    program,\n                    resolver,\n                    *subquery_plan,\n                    &subquery.query_type,\n                    subquery.correlated || rerun_for_target_scan,\n                    true,\n                )?;\n            }\n            Ok(())\n        })();\n        restore_returning_row_image_in_cache(resolver, cache_state);\n        result?;\n    }\n\n    // Emit RETURNING results if specified\n    if !result_columns.is_empty() {\n        emit_returning_results(\n            program,\n            &table_references,\n            &result_columns,\n            insertion.first_col_register(),\n            insertion.key_register(),\n            resolver,\n            ctx.returning_buffer.as_ref(),\n        )?;\n    }\n    program.emit_insn(Insn::Goto {\n        target_pc: ctx.loop_labels.row_done,\n    });\n    if !upsert_actions.is_empty() {\n        resolve_upserts(\n            program,\n            resolver,\n            &mut upsert_actions,\n            &ctx,\n            &insertion,\n            &table,\n            &mut result_columns,\n            connection,\n            &mut table_references,\n        )?;\n    }\n\n    emit_epilogue(program, resolver, &ctx, inserting_multiple_rows)?;\n\n    {\n        let has_statement_conflict = ctx.statement_on_conflict.is_some();\n        let notnull_col_exists = insertion\n            .col_mappings\n            .iter()\n            .any(|m| m.column.notnull() && !m.column.is_rowid_alias());\n        let has_unique = !constraints.constraints_to_check.is_empty();\n        let has_triggers = has_before_triggers || has_after_triggers;\n        set_insert_stmt_journal_flags(\n            program,\n            resolver,\n            database_id,\n            ctx.table,\n            has_statement_conflict,\n            ctx.on_conflict,\n            inserting_multiple_rows,\n            has_triggers,\n            has_fks,\n            has_upsert,\n            btree_table.has_autoincrement,\n            notnull_col_exists,\n            has_unique,\n        );\n    }\n\n    program.result_columns = result_columns;\n    program.table_references.extend(table_references);\n    Ok(())\n}\n\nfn emit_epilogue(\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    ctx: &InsertEmitCtx,\n    inserting_multiple_rows: bool,\n) -> Result<()> {\n    if inserting_multiple_rows {\n        if let Some(temp_table_ctx) = &ctx.temp_table_ctx {\n            program.resolve_label(ctx.loop_labels.row_done, program.offset());\n\n            program.emit_insn(Insn::Next {\n                cursor_id: temp_table_ctx.cursor_id,\n                pc_if_next: temp_table_ctx.loop_start_label,\n            });\n            program.preassign_label_to_next_insn(temp_table_ctx.loop_end_label);\n\n            program.emit_insn(Insn::Close {\n                cursor_id: temp_table_ctx.cursor_id,\n            });\n            program.emit_insn(Insn::Goto {\n                target_pc: ctx.loop_labels.stmt_epilogue,\n            });\n        } else {\n            // For multiple rows which not require a temp table, loop back\n            program.resolve_label(ctx.loop_labels.row_done, program.offset());\n            program.emit_insn(Insn::Goto {\n                target_pc: ctx.loop_labels.loop_start,\n            });\n            if let Some(sel_eof) = ctx.loop_labels.select_exhausted {\n                program.preassign_label_to_next_insn(sel_eof);\n                program.emit_insn(Insn::Goto {\n                    target_pc: ctx.loop_labels.stmt_epilogue,\n                });\n            }\n        }\n    } else {\n        program.resolve_label(ctx.loop_labels.row_done, program.offset());\n        // single-row falls through to epilogue\n        program.emit_insn(Insn::Goto {\n            target_pc: ctx.loop_labels.stmt_epilogue,\n        });\n    }\n    program.preassign_label_to_next_insn(ctx.loop_labels.stmt_epilogue);\n    if let Some((cdc_cursor_id, _)) = &ctx.cdc_table {\n        emit_cdc_autocommit_commit(program, resolver, *cdc_cursor_id)?;\n    }\n    // Emit scan-back loop for buffered RETURNING results.\n    // All DML is complete at this point; now yield the buffered rows to the caller.\n    // FkCheck must come before the scan-back so that FK violations prevent\n    // RETURNING rows from being emitted (matching SQLite behavior).\n    if let Some(ref buf) = ctx.returning_buffer {\n        program.emit_insn(Insn::FkCheck { deferred: false });\n        emit_returning_scan_back(program, buf);\n    }\n    program.resolve_label(ctx.halt_label, program.offset());\n    Ok(())\n}\n\n/// Evaluates a partial index WHERE clause and emits code to skip if the predicate is false.\n/// Returns Some(label) if there was a WHERE clause (label should be resolved after the guarded code),\n/// or None if there was no WHERE clause.\nfn emit_partial_index_check(\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    index: &Index,\n    insertion: &Insertion,\n) -> Result<Option<BranchOffset>> {\n    let Some(where_clause) = &index.where_clause else {\n        return Ok(None);\n    };\n    let mut where_for_eval = where_clause.as_ref().clone();\n    rewrite_partial_index_where(&mut where_for_eval, insertion)?;\n    let reg = program.alloc_register();\n    translate_expr_no_constant_opt(\n        program,\n        Some(&TableReferences::new_empty()),\n        &where_for_eval,\n        reg,\n        resolver,\n        NoConstantOptReason::RegisterReuse,\n    )?;\n    let skip_label = program.allocate_label();\n    program.emit_insn(Insn::IfNot {\n        reg,\n        target_pc: skip_label,\n        jump_if_null: true,\n    });\n    Ok(Some(skip_label))\n}\n\n// COMMIT PHASE: no preflight jumps happened; emit the actual index writes now\n// We re-check partial-index predicates against the NEW image, produce packed records,\n// and insert into all applicable indexes, we do not re-probe uniqueness here, as preflight\n// already guaranteed non-conflict.\nfn emit_commit_phase(\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    insertion: &Insertion,\n    ctx: &InsertEmitCtx,\n    skip_replace_indexes: bool,\n) -> Result<()> {\n    let indices: Vec<_> = resolver.with_schema(ctx.database_id, |s| {\n        s.get_indices(ctx.table.name.as_str()).cloned().collect()\n    });\n    for index in &indices {\n        // In mixed mode (some constraints REPLACE, some not), REPLACE indexes\n        // were already eagerly inserted in the preflight phase. Skip them here\n        // to avoid double-insertion.\n        if skip_replace_indexes && index.on_conflict == Some(ResolveType::Replace) {\n            continue;\n        }\n        let idx_cursor_id = ctx\n            .idx_cursors\n            .iter()\n            .find(|(name, _, _)| name == &index.name)\n            .map(|(_, _, c_id)| *c_id)\n            .expect(\"no cursor found for index\");\n\n        // Re-evaluate partial predicate on the would-be inserted image\n        let commit_skip_label = emit_partial_index_check(program, resolver, index, insertion)?;\n\n        let num_cols = index.columns.len();\n        let idx_start_reg = program.alloc_registers(num_cols + 1);\n\n        // Build [key cols..., rowid] from insertion registers\n        for (i, idx_col) in index.columns.iter().enumerate() {\n            emit_index_column_value_for_insert(\n                program,\n                resolver,\n                insertion,\n                ctx.table,\n                idx_col,\n                idx_start_reg + i,\n            )?;\n        }\n        program.emit_insn(Insn::Copy {\n            src_reg: insertion.key_register(),\n            dst_reg: idx_start_reg + num_cols,\n            extra_amount: 0,\n        });\n\n        let record_reg = program.alloc_register();\n        program.emit_insn(Insn::MakeRecord {\n            start_reg: to_u16(idx_start_reg),\n            count: to_u16(num_cols + 1),\n            dest_reg: to_u16(record_reg),\n            index_name: Some(index.name.clone()),\n            affinity_str: None,\n        });\n        program.emit_insn(Insn::IdxInsert {\n            cursor_id: idx_cursor_id,\n            record_reg,\n            unpacked_start: Some(idx_start_reg),\n            unpacked_count: Some((num_cols + 1) as u16),\n            flags: IdxInsertFlags::new().nchange(true),\n        });\n\n        if let Some(lbl) = commit_skip_label {\n            program.resolve_label(lbl, program.offset());\n        }\n    }\n    Ok(())\n}\n\nfn translate_rows_and_open_tables(\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    insertion: &Insertion,\n    ctx: &InsertEmitCtx,\n    values: &[Box<Expr>],\n    inserting_multiple_rows: bool,\n) -> Result<()> {\n    if inserting_multiple_rows {\n        let select_result_start_reg = program\n            .reg_result_cols_start\n            .unwrap_or_else(|| ctx.yield_reg_opt.unwrap() + 1);\n        translate_rows_multiple(\n            program,\n            insertion,\n            select_result_start_reg,\n            resolver,\n            &ctx.temp_table_ctx,\n            ctx.table.is_strict,\n        )?;\n    } else {\n        // Single row - populate registers directly\n        program.emit_insn(Insn::OpenWrite {\n            cursor_id: ctx.cursor_id,\n            root_page: RegisterOrLiteral::Literal(ctx.table.root_page),\n            db: ctx.database_id,\n        });\n\n        translate_rows_single(program, values, insertion, resolver, ctx.table.is_strict)?;\n    }\n\n    // Open all the index btrees for writing\n    for idx_cursor in ctx.idx_cursors.iter() {\n        program.emit_insn(Insn::OpenWrite {\n            cursor_id: idx_cursor.2,\n            root_page: idx_cursor.1.into(),\n            db: ctx.database_id,\n        });\n    }\n    Ok(())\n}\n\nfn emit_rowid_generation(\n    program: &mut ProgramBuilder,\n    ctx: &InsertEmitCtx,\n    insertion: &Insertion,\n    resolver: &Resolver,\n) -> Result<()> {\n    if let Some(AutoincMeta {\n        r_seq,\n        seq_cursor_id,\n        r_seq_rowid,\n        table_name_reg,\n        ..\n    }) = ctx.autoincrement_meta\n    {\n        reload_autoincrement_state(\n            program,\n            AutoincMeta {\n                seq_cursor_id,\n                r_seq,\n                r_seq_rowid,\n                table_name_reg,\n            },\n        );\n        let r_max = program.alloc_register();\n\n        let dummy_reg = program.alloc_register();\n\n        program.emit_insn(Insn::NewRowid {\n            cursor: ctx.cursor_id,\n            rowid_reg: dummy_reg,\n            prev_largest_reg: r_max,\n        });\n\n        program.emit_insn(Insn::Copy {\n            src_reg: r_seq,\n            dst_reg: insertion.key_register(),\n            extra_amount: 0,\n        });\n        program.emit_insn(Insn::MemMax {\n            dest_reg: insertion.key_register(),\n            src_reg: r_max,\n        });\n\n        let no_overflow_label = program.allocate_label();\n        let max_i64_reg = program.alloc_register();\n        program.emit_insn(Insn::Integer {\n            dest: max_i64_reg,\n            value: i64::MAX,\n        });\n        program.emit_insn(Insn::Ne {\n            lhs: insertion.key_register(),\n            rhs: max_i64_reg,\n            target_pc: no_overflow_label,\n            flags: Default::default(),\n            collation: None,\n        });\n\n        program.emit_insn(Insn::Halt {\n            err_code: crate::error::SQLITE_FULL,\n            description: \"database or disk is full\".to_string(),\n            on_error: None,\n            description_reg: None,\n        });\n\n        program.preassign_label_to_next_insn(no_overflow_label);\n\n        program.emit_insn(Insn::AddImm {\n            register: insertion.key_register(),\n            value: 1,\n        });\n\n        emit_update_sqlite_sequence(\n            program,\n            resolver,\n            ctx.database_id,\n            seq_cursor_id,\n            r_seq_rowid,\n            table_name_reg,\n            insertion.key_register(),\n        )?;\n    } else {\n        program.emit_insn(Insn::NewRowid {\n            cursor: ctx.cursor_id,\n            rowid_reg: insertion.key_register(),\n            prev_largest_reg: 0,\n        });\n    }\n    Ok(())\n}\n\n#[allow(clippy::too_many_arguments)]\nfn resolve_upserts(\n    program: &mut ProgramBuilder,\n    resolver: &mut Resolver,\n    upsert_actions: &mut [(ResolvedUpsertTarget, BranchOffset, Box<Upsert>)],\n    ctx: &InsertEmitCtx,\n    insertion: &Insertion,\n    table: &Table,\n    result_columns: &mut [ResultSetColumn],\n    connection: &Arc<crate::Connection>,\n    table_references: &mut TableReferences,\n) -> Result<()> {\n    for (_, label, upsert) in upsert_actions {\n        program.preassign_label_to_next_insn(*label);\n\n        if let UpsertDo::Set {\n            ref mut sets,\n            ref mut where_clause,\n        } = upsert.do_clause\n        {\n            // Normalize SET pairs once\n            let mut rewritten_sets = collect_set_clauses_for_upsert(table, sets)?;\n\n            emit_upsert(\n                program,\n                table,\n                ctx,\n                insertion,\n                &mut rewritten_sets,\n                where_clause,\n                resolver,\n                result_columns,\n                connection,\n                table_references,\n            )?;\n        } else {\n            // UpsertDo::Nothing case\n            program.emit_insn(Insn::Goto {\n                target_pc: ctx.loop_labels.row_done,\n            });\n        }\n    }\n    Ok(())\n}\n\nfn get_valid_sqlite_sequence_table(\n    resolver: &Resolver,\n    database_id: usize,\n) -> Result<Arc<BTreeTable>> {\n    let Some(seq_table) = resolver.with_schema(database_id, |s| {\n        s.get_btree_table(SQLITE_SEQUENCE_TABLE_NAME)\n    }) else {\n        crate::bail_corrupt_error!(\"missing sqlite_sequence table\");\n    };\n\n    if !seq_table.has_rowid {\n        crate::bail_corrupt_error!(\"malformed sqlite_sequence: table must have rowid\");\n    }\n\n    if seq_table.columns.len() != 2 {\n        crate::bail_corrupt_error!(\n            \"malformed sqlite_sequence: expected 2 columns, got {}\",\n            seq_table.columns.len()\n        );\n    }\n\n    let col0_name = seq_table.columns[0].name.as_deref();\n    let col1_name = seq_table.columns[1].name.as_deref();\n    if !matches!(col0_name, Some(name) if name.eq_ignore_ascii_case(\"name\"))\n        || !matches!(col1_name, Some(name) if name.eq_ignore_ascii_case(\"seq\"))\n    {\n        crate::bail_corrupt_error!(\"malformed sqlite_sequence: expected columns (name, seq)\");\n    }\n\n    Ok(seq_table)\n}\n\nfn init_autoincrement(\n    program: &mut ProgramBuilder,\n    ctx: &mut InsertEmitCtx,\n    resolver: &Resolver,\n) -> Result<()> {\n    open_autoincrement_state(program, ctx, resolver)?;\n    reload_autoincrement_state(\n        program,\n        ctx.autoincrement_meta\n            .expect(\"AUTOINCREMENT metadata should be initialized\"),\n    );\n    Ok(())\n}\n\nfn open_autoincrement_state(\n    program: &mut ProgramBuilder,\n    ctx: &mut InsertEmitCtx,\n    resolver: &Resolver,\n) -> Result<()> {\n    let seq_table = get_valid_sqlite_sequence_table(resolver, ctx.database_id)?;\n    let seq_cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(seq_table.clone()));\n    program.emit_insn(Insn::OpenWrite {\n        cursor_id: seq_cursor_id,\n        root_page: seq_table.root_page.into(),\n        db: ctx.database_id,\n    });\n\n    let table_name_reg = program.emit_string8_new_reg(ctx.table.name.clone());\n    let r_seq = program.alloc_register();\n    let r_seq_rowid = program.alloc_register();\n\n    ctx.autoincrement_meta = Some(AutoincMeta {\n        seq_cursor_id,\n        r_seq,\n        r_seq_rowid,\n        table_name_reg,\n    });\n\n    program.emit_insn(Insn::Integer {\n        dest: r_seq,\n        value: 0,\n    });\n    program.emit_insn(Insn::Null {\n        dest: r_seq_rowid,\n        dest_end: None,\n    });\n    Ok(())\n}\n\nfn reload_autoincrement_state(program: &mut ProgramBuilder, meta: AutoincMeta) {\n    let AutoincMeta {\n        seq_cursor_id,\n        r_seq,\n        r_seq_rowid,\n        table_name_reg,\n    } = meta;\n\n    program.emit_insn(Insn::Integer {\n        dest: r_seq,\n        value: 0,\n    });\n    program.emit_insn(Insn::Null {\n        dest: r_seq_rowid,\n        dest_end: None,\n    });\n\n    let loop_start_label = program.allocate_label();\n    let loop_end_label = program.allocate_label();\n    let found_label = program.allocate_label();\n\n    program.emit_insn(Insn::Rewind {\n        cursor_id: seq_cursor_id,\n        pc_if_empty: loop_end_label,\n    });\n    program.preassign_label_to_next_insn(loop_start_label);\n\n    let name_col_reg = program.alloc_register();\n    program.emit_column_or_rowid(seq_cursor_id, 0, name_col_reg);\n    program.emit_insn(Insn::Ne {\n        lhs: table_name_reg,\n        rhs: name_col_reg,\n        target_pc: found_label,\n        flags: Default::default(),\n        collation: None,\n    });\n\n    program.emit_column_or_rowid(seq_cursor_id, 1, r_seq);\n    program.emit_insn(Insn::RowId {\n        cursor_id: seq_cursor_id,\n        dest: r_seq_rowid,\n    });\n    program.emit_insn(Insn::Goto {\n        target_pc: loop_end_label,\n    });\n\n    program.preassign_label_to_next_insn(found_label);\n    program.emit_insn(Insn::Next {\n        cursor_id: seq_cursor_id,\n        pc_if_next: loop_start_label,\n    });\n    program.preassign_label_to_next_insn(loop_end_label);\n}\n\nfn emit_notnulls(\n    program: &mut ProgramBuilder,\n    ctx: &InsertEmitCtx,\n    insertion: &Insertion,\n    resolver: &Resolver,\n) -> Result<()> {\n    for column_mapping in insertion\n        .col_mappings\n        .iter()\n        .filter(|column_mapping| column_mapping.column.notnull())\n    {\n        // if this is rowid alias - turso-db will emit NULL as a column value and always use rowid for the row as a column value\n        if column_mapping.column.is_rowid_alias() {\n            continue;\n        }\n\n        // Compute effective conflict for this NOT NULL constraint:\n        // Statement-level OR clause overrides; otherwise use column's clause.\n        let effective = if ctx.statement_on_conflict.is_some() {\n            ctx.on_conflict\n        } else {\n            column_mapping\n                .column\n                .notnull_conflict_clause\n                .unwrap_or(ResolveType::Abort)\n        };\n        let on_replace = matches!(effective, ResolveType::Replace);\n        let on_ignore = matches!(effective, ResolveType::Ignore);\n\n        // If a NOT NULL constraint violation occurs, the REPLACE conflict resolution replaces the NULL value with the default value for that column,\n        // or if the column has no default value, then the ABORT algorithm is used\n        if on_replace {\n            if let Some(default_expr) = column_mapping.column.default.as_ref() {\n                let skip_label = program.allocate_label();\n\n                program.emit_insn(Insn::NotNull {\n                    reg: column_mapping.register,\n                    target_pc: skip_label,\n                });\n\n                // Evaluate default expression into the column register.\n                translate_expr_no_constant_opt(\n                    program,\n                    None,\n                    default_expr,\n                    column_mapping.register,\n                    resolver,\n                    NoConstantOptReason::RegisterReuse,\n                )?;\n\n                program.preassign_label_to_next_insn(skip_label);\n            }\n            // OR REPLACE but no DEFAULT, fall through to ABORT behavior\n        }\n\n        // Determine which register to check: for custom type columns with\n        // a DECODE expression, decode the encoded value into a temp register\n        // and check the *decoded* value. This prevents \"ghost NULLs\" where\n        // ENCODE produces a non-NULL value but DECODE returns NULL.\n        let check_reg = if let Some(type_def) = resolver\n            .schema()\n            .get_type_def(&column_mapping.column.ty_str, ctx.table.is_strict)\n        {\n            if type_def.decode.is_some() {\n                let decoded_reg = program.alloc_register();\n                crate::translate::expr::emit_user_facing_column_value(\n                    program,\n                    column_mapping.register,\n                    decoded_reg,\n                    column_mapping.column,\n                    ctx.table.is_strict,\n                    resolver,\n                )?;\n                decoded_reg\n            } else {\n                column_mapping.register\n            }\n        } else {\n            column_mapping.register\n        };\n\n        // For IGNORE, skip to the next row if NULL\n        if on_ignore {\n            program.emit_insn(Insn::IsNull {\n                reg: check_reg,\n                target_pc: ctx.loop_labels.row_done,\n            });\n        } else {\n            program.emit_insn(Insn::HaltIfNull {\n                target_reg: check_reg,\n                err_code: SQLITE_CONSTRAINT_NOTNULL,\n                description: {\n                    let mut description = String::with_capacity(\n                        ctx.table.name.as_str().len()\n                            + column_mapping\n                                .column\n                                .name\n                                .as_ref()\n                                .expect(\"Column name must be present\")\n                                .len()\n                            + 2,\n                    );\n                    description.push_str(ctx.table.name.as_str());\n                    description.push('.');\n                    description.push_str(\n                        column_mapping\n                            .column\n                            .name\n                            .as_ref()\n                            .expect(\"Column name must be present\"),\n                    );\n                    description\n                },\n            });\n        }\n    }\n    Ok(())\n}\n\nstruct BoundInsertResult {\n    #[allow(clippy::vec_box)]\n    values: Vec<Box<Expr>>,\n    upsert_actions: Vec<(ResolvedUpsertTarget, BranchOffset, Box<Upsert>)>,\n    inserting_multiple_rows: bool,\n}\n\n/// Check if an expression contains a subquery (Subquery, InSelect, or Exists).\n/// This is used to detect when single-row VALUES should be routed through the\n/// multi-row path which has proper subquery handling.\nfn expr_contains_subquery(expr: &Expr) -> bool {\n    use crate::translate::expr::{walk_expr, WalkControl};\n    let mut found_subquery = false;\n    let _ = walk_expr(expr, &mut |e| {\n        if matches!(\n            e,\n            Expr::Subquery(_) | Expr::InSelect { .. } | Expr::Exists(_)\n        ) {\n            found_subquery = true;\n            return Ok(WalkControl::SkipChildren);\n        }\n        Ok(WalkControl::Continue)\n    });\n    found_subquery\n}\n\nfn bind_insert(\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    table: &Table,\n    body: &mut InsertBody,\n    on_conflict: ResolveType,\n    database_id: usize,\n) -> Result<BoundInsertResult> {\n    let mut values: Vec<Box<Expr>> = vec![];\n    let mut upsert: Option<Box<Upsert>> = None;\n    let mut upsert_actions: Vec<(ResolvedUpsertTarget, BranchOffset, Box<Upsert>)> = Vec::new();\n    let mut inserting_multiple_rows = false;\n    match body {\n        InsertBody::DefaultValues => {\n            // Generate default values for the table.\n            // Check column-level default first, then type-level default.\n            let is_strict = table.is_strict();\n            values = table\n                .columns()\n                .iter()\n                .filter(|c| !c.hidden())\n                .map(|c| {\n                    c.default.clone().unwrap_or_else(|| {\n                        if let Some(type_def) = resolver.schema().get_type_def(&c.ty_str, is_strict)\n                        {\n                            if let Some(ref default_expr) = type_def.default {\n                                return default_expr.clone();\n                            }\n                        }\n                        Box::new(ast::Expr::Literal(ast::Literal::Null))\n                    })\n                })\n                .collect();\n        }\n        InsertBody::Select(select, upsert_opt) => {\n            if select.body.compounds.is_empty() {\n                match &mut select.body.select {\n                    // TODO see how to avoid clone\n                    OneSelect::Values(values_expr) if values_expr.len() <= 1 => {\n                        if values_expr.is_empty() {\n                            crate::bail_parse_error!(\"no values to insert\");\n                        }\n                        // Check if any VALUES expression contains a subquery.\n                        // If so, route through multi-row path which handles subqueries.\n                        let has_subquery = values_expr\n                            .iter()\n                            .any(|row| row.iter().any(|expr| expr_contains_subquery(expr)));\n                        if has_subquery {\n                            inserting_multiple_rows = true;\n                        } else {\n                            for expr in values_expr.iter_mut().flat_map(|v| v.iter_mut()) {\n                                match expr.as_mut() {\n                                    Expr::Id(name) => {\n                                        if name.quoted_with('\"') {\n                                            *expr = Expr::Literal(ast::Literal::String(\n                                                name.as_literal(),\n                                            ))\n                                            .into();\n                                        } else {\n                                            // an INSERT INTO ... VALUES (...) cannot reference columns\n                                            crate::bail_parse_error!(\"no such column: {name}\");\n                                        }\n                                    }\n                                    Expr::Qualified(first_name, second_name) => {\n                                        // an INSERT INTO ... VALUES (...) cannot reference columns\n                                        crate::bail_parse_error!(\n                                            \"no such column: {first_name}.{second_name}\"\n                                        );\n                                    }\n                                    _ => {}\n                                }\n                                bind_and_rewrite_expr(\n                                    expr,\n                                    None,\n                                    None,\n                                    resolver,\n                                    BindingBehavior::ResultColumnsNotAllowed,\n                                )?;\n                            }\n                            values = values_expr.pop().unwrap_or_else(Vec::new);\n                        }\n                    }\n                    _ => inserting_multiple_rows = true,\n                }\n            } else {\n                inserting_multiple_rows = true;\n            }\n            upsert = upsert_opt.take();\n        }\n    }\n    if let ResolveType::Ignore = on_conflict {\n        program.set_resolve_type(ResolveType::Ignore);\n        upsert.replace(Box::new(ast::Upsert {\n            do_clause: UpsertDo::Nothing,\n            index: None,\n            next: None,\n        }));\n    } else {\n        program.set_resolve_type(on_conflict);\n    }\n    while let Some(mut upsert_opt) = upsert.take() {\n        if let UpsertDo::Set {\n            ref mut sets,\n            ref mut where_clause,\n        } = &mut upsert_opt.do_clause\n        {\n            for set in sets.iter_mut() {\n                bind_and_rewrite_expr(\n                    &mut set.expr,\n                    None,\n                    None,\n                    resolver,\n                    BindingBehavior::AllowUnboundIdentifiers,\n                )?;\n            }\n            if let Some(ref mut where_expr) = where_clause {\n                bind_and_rewrite_expr(\n                    where_expr,\n                    None,\n                    None,\n                    resolver,\n                    BindingBehavior::AllowUnboundIdentifiers,\n                )?;\n            }\n        }\n        let next = upsert_opt.next.take();\n        upsert_actions.push((\n            // resolve the constrained target for UPSERT in the chain\n            resolver.with_schema(database_id, |s| {\n                resolve_upsert_target(s, table, &upsert_opt)\n            })?,\n            program.allocate_label(),\n            upsert_opt,\n        ));\n        upsert = next;\n    }\n    Ok(BoundInsertResult {\n        values,\n        upsert_actions,\n        inserting_multiple_rows,\n    })\n}\n\n/// Depending on the InsertBody, we begin to initialize the source of the insert values\n/// into registers using the following methods:\n///\n/// Values with a single row, expressions are directly evaluated into registers, so nothing\n/// is emitted here, we simply allocate the cursor ID and store the arity.\n///\n/// Values with multiple rows, we use a coroutine to yield each row into registers directly.\n///\n/// Select, we use a coroutine to yield each row from the SELECT into registers,\n/// materializing into a temporary table if the target table is also read by the SELECT.\n///\n/// For DefaultValues, we allocate the cursor and extend the empty values vector with either the\n/// default expressions registered for the columns, or NULLs, so they can be translated into\n/// registers later.\n#[allow(clippy::too_many_arguments, clippy::vec_box)]\nfn init_source_emission<'a>(\n    program: &mut ProgramBuilder,\n    table: &Table,\n    connection: &Arc<Connection>,\n    ctx: &mut InsertEmitCtx<'a>,\n    resolver: &Resolver,\n    values: &mut Vec<Box<Expr>>,\n    body: InsertBody,\n    columns: &'a [ast::Name],\n    table_references: &TableReferences,\n    database_id: usize,\n) -> Result<()> {\n    let required_column_count = if columns.is_empty() {\n        table.columns().len()\n    } else {\n        columns.len()\n    };\n    if !values.is_empty() {\n        // If we had a single tuple in VALUES, it was inserted into the values vector parameter.\n        if values.len() != required_column_count {\n            crate::bail_parse_error!(\n                \"{} values for {required_column_count} columns\",\n                values.len()\n            );\n        }\n    }\n    // Check if INSERT triggers exist - if so, we need to use ephemeral table for VALUES with more than one row\n    let has_insert_triggers = resolver.with_schema(database_id, |s| {\n        has_relevant_triggers_type_only(s, TriggerEvent::Insert, None, ctx.table.as_ref())\n    });\n\n    let (num_values, cursor_id) = match body {\n        InsertBody::Select(select, _) => {\n            // Simple common case of INSERT INTO <table> VALUES (...) without compounds.\n            // Note: values.is_empty() check ensures we use the multi-row path when\n            // single-row VALUES contains subqueries (values extraction was skipped).\n            if !values.is_empty()\n                && select.body.compounds.is_empty()\n                && matches!(&select.body.select, OneSelect::Values(values) if values.len() <= 1)\n            {\n                (\n                    values.len(),\n                    program.alloc_cursor_id_keyed(\n                        CursorKey::table(table_references.joined_tables()[0].internal_id),\n                        CursorType::BTreeTable(ctx.table.clone()),\n                    ),\n                )\n            } else {\n                // Multiple rows - use coroutine for value population\n                let yield_reg = program.alloc_register();\n                let jump_on_definition_label = program.allocate_label();\n                let start_offset_label = program.allocate_label();\n                program.emit_insn(Insn::InitCoroutine {\n                    yield_reg,\n                    jump_on_definition: jump_on_definition_label,\n                    start_offset: start_offset_label,\n                });\n                program.preassign_label_to_next_insn(start_offset_label);\n\n                let query_destination = QueryDestination::CoroutineYield {\n                    yield_reg,\n                    coroutine_implementation_start: ctx.halt_label,\n                };\n                let num_result_cols = program.nested(|program| {\n                    translate_select(select, resolver, program, query_destination, connection)\n                })?;\n                if num_result_cols != required_column_count {\n                    crate::bail_parse_error!(\n                        \"{} values for {required_column_count} columns\",\n                        num_result_cols,\n                    );\n                }\n\n                program.emit_insn(Insn::EndCoroutine { yield_reg });\n                program.preassign_label_to_next_insn(jump_on_definition_label);\n                let cursor_id = program.alloc_cursor_id_keyed(\n                    CursorKey::table(table_references.joined_tables()[0].internal_id),\n                    CursorType::BTreeTable(ctx.table.clone()),\n                );\n\n                // From SQLite\n                /* Set useTempTable to TRUE if the result of the SELECT statement\n                 ** should be written into a temporary table (template 4).  Set to\n                 ** FALSE if each output row of the SELECT can be written directly into\n                 ** the destination table (template 3).\n                 **\n                 ** A temp table must be used if the table being updated is also one\n                 ** of the tables being read by the SELECT statement.  Also use a\n                 ** temp table in the case of row triggers.\n                 */\n                if program.is_table_open(table) || has_insert_triggers {\n                    let temp_cursor_id =\n                        program.alloc_cursor_id(CursorType::BTreeTable(ctx.table.clone()));\n                    ctx.temp_table_ctx = Some(TempTableCtx {\n                        cursor_id: temp_cursor_id,\n                        loop_start_label: program.allocate_label(),\n                        loop_end_label: program.allocate_label(),\n                    });\n\n                    program.emit_insn(Insn::OpenEphemeral {\n                        cursor_id: temp_cursor_id,\n                        is_table: true,\n                    });\n\n                    // Main loop\n                    program.preassign_label_to_next_insn(ctx.loop_labels.loop_start);\n                    let yield_label = program.allocate_label();\n                    program.emit_insn(Insn::Yield {\n                        yield_reg,\n                        end_offset: yield_label, // stays local, we’ll route at loop end\n                        subtype_clear_start_reg: 0,\n                        subtype_clear_count: 0,\n                    });\n\n                    let record_reg = program.alloc_register();\n                    let affinity_str = if columns.is_empty() {\n                        ctx.table\n                            .columns\n                            .iter()\n                            .filter(|col| !col.hidden())\n                            .map(|col| col.affinity_with_strict(ctx.table.is_strict).aff_mask())\n                            .collect::<String>()\n                    } else {\n                        columns\n                            .iter()\n                            .map(|col_name| {\n                                let column_name = normalize_ident(col_name.as_str());\n                                if ROWID_STRS\n                                    .iter()\n                                    .any(|s| s.eq_ignore_ascii_case(&column_name))\n                                {\n                                    return Ok(Affinity::Integer.aff_mask());\n                                }\n                                table\n                                    .get_column_by_name(&column_name)\n                                    .map(|(_, col)| {\n                                        col.affinity_with_strict(ctx.table.is_strict).aff_mask()\n                                    })\n                                    .ok_or_else(|| {\n                                        crate::error::LimboError::ParseError(format!(\n                                            \"table {} has no column named {}\",\n                                            table.get_name(),\n                                            column_name\n                                        ))\n                                    })\n                            })\n                            .collect::<Result<String>>()?\n                    };\n\n                    program.emit_insn(Insn::MakeRecord {\n                        start_reg: to_u16(program.reg_result_cols_start.unwrap_or(yield_reg + 1)),\n                        count: to_u16(num_result_cols),\n                        dest_reg: to_u16(record_reg),\n                        index_name: None,\n                        affinity_str: Some(affinity_str),\n                    });\n\n                    let rowid_reg = program.alloc_register();\n                    program.emit_insn(Insn::NewRowid {\n                        cursor: temp_cursor_id,\n                        rowid_reg,\n                        prev_largest_reg: 0,\n                    });\n                    program.emit_insn(Insn::Insert {\n                        cursor: temp_cursor_id,\n                        key_reg: rowid_reg,\n                        record_reg,\n                        // since we are not doing an Insn::NewRowid or an Insn::NotExists here, we need to seek to ensure the insertion happens in the correct place.\n                        flag: InsertFlags::new().require_seek(),\n                        table_name: \"\".to_string(),\n                    });\n                    // loop back\n                    program.emit_insn(Insn::Goto {\n                        target_pc: ctx.loop_labels.loop_start,\n                    });\n                    program.preassign_label_to_next_insn(yield_label);\n\n                    program.emit_insn(Insn::OpenWrite {\n                        cursor_id,\n                        root_page: RegisterOrLiteral::Literal(ctx.table.root_page),\n                        db: ctx.database_id,\n                    });\n                } else {\n                    program.emit_insn(Insn::OpenWrite {\n                        cursor_id,\n                        root_page: RegisterOrLiteral::Literal(ctx.table.root_page),\n                        db: ctx.database_id,\n                    });\n\n                    program.preassign_label_to_next_insn(ctx.loop_labels.loop_start);\n\n                    // on EOF, jump to select_exhausted to check FK constraints\n                    let select_exhausted = program.allocate_label();\n                    ctx.loop_labels.select_exhausted = Some(select_exhausted);\n                    program.emit_insn(Insn::Yield {\n                        yield_reg,\n                        end_offset: select_exhausted,\n                        subtype_clear_start_reg: 0,\n                        subtype_clear_count: 0,\n                    });\n                }\n\n                ctx.yield_reg_opt = Some(yield_reg);\n                (num_result_cols, cursor_id)\n            }\n        }\n        InsertBody::DefaultValues => {\n            let num_values = table.columns().len();\n            let is_strict = table.is_strict();\n            values.extend(table.columns().iter().map(|c| {\n                c.default.clone().unwrap_or_else(|| {\n                    if let Some(type_def) = resolver.schema().get_type_def(&c.ty_str, is_strict) {\n                        if let Some(ref default_expr) = type_def.default {\n                            return default_expr.clone();\n                        }\n                    }\n                    Box::new(ast::Expr::Literal(ast::Literal::Null))\n                })\n            }));\n            (\n                num_values,\n                program.alloc_cursor_id_keyed(\n                    CursorKey::table(table_references.joined_tables()[0].internal_id),\n                    CursorType::BTreeTable(ctx.table.clone()),\n                ),\n            )\n        }\n    };\n    ctx.num_values = num_values;\n    ctx.cursor_id = cursor_id;\n    Ok(())\n}\n\n#[derive(Clone, Copy)]\npub struct AutoincMeta {\n    seq_cursor_id: usize,\n    r_seq: usize,\n    r_seq_rowid: usize,\n    table_name_reg: usize,\n}\n\npub static ROWID_COLUMN: std::sync::LazyLock<Column> = std::sync::LazyLock::new(|| {\n    Column::new(\n        None,          // name\n        String::new(), // type string\n        None,          // default\n        None,          // generated\n        schema::Type::Integer,\n        None,\n        ColDef {\n            primary_key: true,\n            rowid_alias: true,\n            notnull: true,\n            hidden: false,\n            unique: false,\n            notnull_conflict_clause: None,\n        },\n    )\n});\n\n/// Represents how a table should be populated during an INSERT.\n#[derive(Debug)]\npub struct Insertion<'a> {\n    /// The integer key (\"rowid\") provided to the VDBE.\n    key: InsertionKey<'a>,\n    /// The column values that will be fed to the MakeRecord instruction to insert the row.\n    /// If the table has a rowid alias column, it will also be included in this record,\n    /// but a NULL will be stored for it.\n    col_mappings: Vec<ColMapping<'a>>,\n    /// The register that will contain the record built using the MakeRecord instruction.\n    record_reg: usize,\n}\n\nimpl<'a> Insertion<'a> {\n    /// Return the register that contains the rowid.\n    pub fn key_register(&self) -> usize {\n        self.key.register()\n    }\n\n    /// Return the first register of the values that used to build the record\n    /// for the main table insert.\n    pub fn first_col_register(&self) -> usize {\n        self.col_mappings\n            .first()\n            .expect(\"columns must be present\")\n            .register\n    }\n\n    /// Return the register that contains the record built using the MakeRecord instruction.\n    pub fn record_register(&self) -> usize {\n        self.record_reg\n    }\n\n    /// Returns the column mapping for a given column name.\n    pub fn get_col_mapping_by_name(&self, name: &str) -> Option<&ColMapping<'a>> {\n        if let InsertionKey::RowidAlias(mapping) = &self.key {\n            // If the key is a rowid alias, a NULL is emitted as the column value,\n            // so we need to return the key mapping instead so that the non-NULL rowid is used\n            // for the index insert.\n            if mapping\n                .column\n                .name\n                .as_ref()\n                .is_some_and(|n| n.eq_ignore_ascii_case(name))\n            {\n                return Some(mapping);\n            }\n        }\n        self.col_mappings.iter().find(|col| {\n            col.column\n                .name\n                .as_ref()\n                .is_some_and(|n| n.eq_ignore_ascii_case(name))\n        })\n    }\n}\n\n#[derive(Debug)]\nenum InsertionKey<'a> {\n    /// Rowid is not provided by user and will be autogenerated.\n    Autogenerated { register: usize },\n    /// Rowid is provided via the 'rowid' keyword.\n    LiteralRowid {\n        value_index: Option<usize>,\n        register: usize,\n    },\n    /// Rowid is provided via a rowid alias column.\n    RowidAlias(ColMapping<'a>),\n}\n\nimpl InsertionKey<'_> {\n    fn register(&self) -> usize {\n        match self {\n            InsertionKey::Autogenerated { register } => *register,\n            InsertionKey::LiteralRowid { register, .. } => *register,\n            InsertionKey::RowidAlias(x) => x.register,\n        }\n    }\n    fn is_provided_by_user(&self) -> bool {\n        !matches!(self, InsertionKey::Autogenerated { .. })\n    }\n\n    fn column_name(&self) -> &str {\n        match self {\n            InsertionKey::RowidAlias(x) => x\n                .column\n                .name\n                .as_ref()\n                .expect(\"rowid alias column must be present\")\n                .as_str(),\n            InsertionKey::LiteralRowid { .. } => ROWID_STRS[0],\n            InsertionKey::Autogenerated { .. } => ROWID_STRS[0],\n        }\n    }\n}\n\n/// Represents how a column in a table should be populated during an INSERT.\n/// In a vector of [ColMapping], the index of a given [ColMapping] is\n/// the position of the column in the table.\n#[derive(Debug)]\npub struct ColMapping<'a> {\n    /// Column definition\n    pub column: &'a Column,\n    /// Index of the value to use from a tuple in the insert statement.\n    /// This is needed because the values in the insert statement are not necessarily\n    /// in the same order as the columns in the table, nor do they necessarily contain\n    /// all of the columns in the table.\n    /// If None, a NULL will be emitted for the column, unless it has a default value.\n    /// A NULL rowid alias column's value will be autogenerated.\n    pub value_index: Option<usize>,\n    /// Register where the value will be stored for insertion into the table.\n    pub register: usize,\n}\n\n/// Resolves how each column in a table should be populated during an INSERT.\n/// Returns an [Insertion] struct that contains the key and record for the insertion.\nfn build_insertion<'a>(\n    program: &mut ProgramBuilder,\n    table: &'a Table,\n    columns: &'a [ast::Name],\n    num_values: usize,\n) -> Result<Insertion<'a>> {\n    let table_columns = table.columns();\n    let rowid_register = program.alloc_register();\n    let mut insertion_key = InsertionKey::Autogenerated {\n        register: rowid_register,\n    };\n    let mut column_mappings = table\n        .columns()\n        .iter()\n        .map(|c| ColMapping {\n            column: c,\n            value_index: None,\n            register: program.alloc_register(),\n        })\n        .collect::<Vec<_>>();\n\n    if columns.is_empty() {\n        // Case 1: No columns specified - map values to columns in order\n        if num_values != table_columns.iter().filter(|c| !c.hidden()).count() {\n            crate::bail_parse_error!(\n                \"table {} has {} columns but {} values were supplied\",\n                &table.get_name(),\n                table_columns.len(),\n                num_values\n            );\n        }\n        let mut value_idx = 0;\n        for (i, col) in table_columns.iter().enumerate() {\n            if col.hidden() {\n                // Hidden columns are not taken into account.\n                continue;\n            }\n            if col.is_rowid_alias() {\n                insertion_key = InsertionKey::RowidAlias(ColMapping {\n                    column: col,\n                    value_index: Some(value_idx),\n                    register: rowid_register,\n                });\n            } else {\n                column_mappings[i].value_index = Some(value_idx);\n            }\n            value_idx += 1;\n        }\n    } else {\n        // Case 2: Columns specified - map named columns to their values\n        // Map each named column to its value index\n        for (value_index, column_name) in columns.iter().enumerate() {\n            let column_name = normalize_ident(column_name.as_str());\n            if let Some((idx_in_table, col_in_table)) = table.get_column_by_name(&column_name) {\n                // Named column\n                if col_in_table.is_rowid_alias() {\n                    insertion_key = InsertionKey::RowidAlias(ColMapping {\n                        column: col_in_table,\n                        value_index: Some(value_index),\n                        register: rowid_register,\n                    });\n                } else if column_mappings[idx_in_table].value_index.is_none() {\n                    column_mappings[idx_in_table].value_index = Some(value_index);\n                }\n            } else if ROWID_STRS\n                .iter()\n                .any(|s| s.eq_ignore_ascii_case(&column_name))\n            {\n                // Explicit use of the 'rowid' keyword\n                if let Some(col_in_table) = table.columns().iter().find(|c| c.is_rowid_alias()) {\n                    insertion_key = InsertionKey::RowidAlias(ColMapping {\n                        column: col_in_table,\n                        value_index: Some(value_index),\n                        register: rowid_register,\n                    });\n                } else {\n                    insertion_key = InsertionKey::LiteralRowid {\n                        value_index: Some(value_index),\n                        register: rowid_register,\n                    };\n                }\n            } else {\n                crate::bail_parse_error!(\n                    \"table {} has no column named {}\",\n                    &table.get_name(),\n                    column_name\n                );\n            }\n        }\n    }\n\n    Ok(Insertion {\n        key: insertion_key,\n        col_mappings: column_mappings,\n        record_reg: program.alloc_register(),\n    })\n}\n\n/// Populates the column registers with values for multiple rows.\n/// This is used for INSERT INTO <table> VALUES (...), (...), ... or INSERT INTO <table> SELECT ...\n/// which use either a coroutine or an ephemeral table as the value source.\nfn translate_rows_multiple<'short, 'long: 'short>(\n    program: &mut ProgramBuilder,\n    insertion: &'short Insertion<'long>,\n    yield_reg: usize,\n    resolver: &Resolver,\n    temp_table_ctx: &Option<TempTableCtx>,\n    is_strict: bool,\n) -> Result<()> {\n    if let Some(ref temp_table_ctx) = temp_table_ctx {\n        // Rewind loop to read from ephemeral table\n        program.emit_insn(Insn::Rewind {\n            cursor_id: temp_table_ctx.cursor_id,\n            pc_if_empty: temp_table_ctx.loop_end_label,\n        });\n        program.preassign_label_to_next_insn(temp_table_ctx.loop_start_label);\n    }\n    let translate_value_fn =\n        |prg: &mut ProgramBuilder, value_index: usize, column_register: usize| {\n            if let Some(temp_table_ctx) = temp_table_ctx {\n                prg.emit_insn(Insn::Column {\n                    cursor_id: temp_table_ctx.cursor_id,\n                    column: value_index,\n                    dest: column_register,\n                    default: None,\n                });\n            } else {\n                prg.emit_insn(Insn::Copy {\n                    src_reg: yield_reg + value_index,\n                    dst_reg: column_register,\n                    extra_amount: 0,\n                });\n            }\n            Ok(())\n        };\n    translate_rows_base(program, insertion, translate_value_fn, resolver, is_strict)\n}\n/// Populates the column registers with values for a single row\nfn translate_rows_single(\n    program: &mut ProgramBuilder,\n    value: &[Box<Expr>],\n    insertion: &Insertion,\n    resolver: &Resolver,\n    is_strict: bool,\n) -> Result<()> {\n    let translate_value_fn =\n        |prg: &mut ProgramBuilder, value_index: usize, column_register: usize| -> Result<()> {\n            translate_expr_no_constant_opt(\n                prg,\n                None,\n                value.get(value_index).unwrap_or_else(|| {\n                    panic!(\"value index out of bounds: {value_index} for value: {value:?}\")\n                }),\n                column_register,\n                resolver,\n                NoConstantOptReason::RegisterReuse,\n            )?;\n            Ok(())\n        };\n    translate_rows_base(program, insertion, translate_value_fn, resolver, is_strict)\n}\n\n/// Translate the key and the columns of the insertion.\n/// This function is called by both [translate_rows_single] and [translate_rows_multiple],\n/// each providing a different [translate_value_fn] implementation, because for multiple rows\n/// we need to emit the values in a loop, from either an ephemeral table or a coroutine,\n/// whereas for the single row the translation happens in a single pass without looping.\nfn translate_rows_base<'short, 'long: 'short>(\n    program: &mut ProgramBuilder,\n    insertion: &'short Insertion<'long>,\n    mut translate_value_fn: impl FnMut(&mut ProgramBuilder, usize, usize) -> Result<()>,\n    resolver: &Resolver,\n    is_strict: bool,\n) -> Result<()> {\n    translate_key(\n        program,\n        insertion,\n        &mut translate_value_fn,\n        resolver,\n        is_strict,\n    )?;\n    for col in insertion.col_mappings.iter() {\n        translate_column(\n            program,\n            col.column,\n            col.register,\n            col.value_index,\n            &mut translate_value_fn,\n            resolver,\n            is_strict,\n        )?;\n    }\n\n    Ok(())\n}\n\n/// Translate the [InsertionKey].\nfn translate_key(\n    program: &mut ProgramBuilder,\n    insertion: &Insertion,\n    mut translate_value_fn: impl FnMut(&mut ProgramBuilder, usize, usize) -> Result<()>,\n    resolver: &Resolver,\n    is_strict: bool,\n) -> Result<()> {\n    match &insertion.key {\n        InsertionKey::RowidAlias(rowid_alias_column) => translate_column(\n            program,\n            rowid_alias_column.column,\n            rowid_alias_column.register,\n            rowid_alias_column.value_index,\n            &mut translate_value_fn,\n            resolver,\n            is_strict,\n        ),\n        InsertionKey::LiteralRowid {\n            value_index,\n            register,\n        } => translate_column(\n            program,\n            &ROWID_COLUMN,\n            *register,\n            *value_index,\n            &mut translate_value_fn,\n            resolver,\n            is_strict,\n        ),\n        InsertionKey::Autogenerated { .. } => Ok(()), // will be populated later\n    }\n}\n\nfn translate_column(\n    program: &mut ProgramBuilder,\n    column: &Column,\n    column_register: usize,\n    value_index: Option<usize>,\n    translate_value_fn: &mut impl FnMut(&mut ProgramBuilder, usize, usize) -> Result<()>,\n    resolver: &Resolver,\n    is_strict: bool,\n) -> Result<()> {\n    if let Some(value_index) = value_index {\n        translate_value_fn(program, value_index, column_register)?;\n    } else if column.is_rowid_alias() {\n        // Although a non-NULL integer key is used for the insertion key,\n        // the rowid alias column is emitted as NULL.\n        program.emit_insn(Insn::SoftNull {\n            reg: column_register,\n        });\n    } else if column.hidden() {\n        // Emit NULL for not-explicitly-mentioned hidden columns, even ignoring DEFAULT.\n        program.emit_insn(Insn::Null {\n            dest: column_register,\n            dest_end: None,\n        });\n    } else if let Some(default_expr) = column.default.as_ref() {\n        translate_expr(program, None, default_expr, column_register, resolver)?;\n    } else if let Some(type_def) = resolver.schema().get_type_def(&column.ty_str, is_strict) {\n        if let Some(ref default_expr) = type_def.default {\n            translate_expr(program, None, default_expr, column_register, resolver)?;\n        } else {\n            program.emit_insn(Insn::Null {\n                dest: column_register,\n                dest_end: None,\n            });\n        }\n    } else {\n        let nullable = !column.notnull() && !column.is_rowid_alias();\n        if !nullable {\n            crate::bail_parse_error!(\n                \"column {} is not nullable\",\n                column\n                    .name\n                    .as_ref()\n                    .expect(\"column name must be present\")\n                    .as_str()\n            );\n        }\n        program.emit_insn(Insn::Null {\n            dest: column_register,\n            dest_end: None,\n        });\n    }\n    Ok(())\n}\n\n/// Emit bytecode to check PRIMARY KEY uniqueness constraint.\n/// Handles ON REPLACE (delete conflicting row) and UPSERT routing.\nfn emit_pk_uniqueness_check(\n    program: &mut ProgramBuilder,\n    ctx: &mut InsertEmitCtx,\n    resolver: &mut Resolver,\n    insertion: &Insertion,\n    position: Option<usize>,\n    upsert_catch_all: Option<usize>,\n    preflight: &mut PreflightCtx,\n) -> Result<()> {\n    let make_record_label = program.allocate_label();\n    program.emit_insn(Insn::NotExists {\n        cursor: ctx.cursor_id,\n        rowid_reg: insertion.key_register(),\n        target_pc: make_record_label,\n    });\n    let rowid_column_name = insertion.key.column_name();\n\n    // Conflict on rowid: attempt to route through UPSERT if it targets the PK, otherwise raise constraint.\n    // emit Halt for every case *except* when upsert handles the conflict\n    'emit_halt: {\n        if preflight.on_replace {\n            // copy the conflicting rowid into the key register and delete the existing row inline\n            program.emit_insn(Insn::Copy {\n                src_reg: insertion.key_register(),\n                dst_reg: ctx.conflict_rowid_reg,\n                extra_amount: 0,\n            });\n            emit_replace_delete_conflicting_row(\n                program,\n                resolver,\n                preflight.connection,\n                ctx,\n                preflight.table_references,\n            )?;\n            program.emit_insn(Insn::Goto {\n                target_pc: make_record_label,\n            });\n            break 'emit_halt;\n        }\n        if matches!(preflight.effective_on_conflict, ResolveType::Ignore) {\n            // IGNORE: skip this row entirely on PK conflict\n            program.emit_insn(Insn::Goto {\n                target_pc: ctx.loop_labels.row_done,\n            });\n            break 'emit_halt;\n        }\n        if let Some(position) = position.or(upsert_catch_all) {\n            // PK conflict: the conflicting rowid is exactly the attempted key\n            program.emit_insn(Insn::Copy {\n                src_reg: insertion.key_register(),\n                dst_reg: ctx.conflict_rowid_reg,\n                extra_amount: 0,\n            });\n            program.emit_insn(Insn::Goto {\n                target_pc: preflight.upsert_actions[position].1,\n            });\n            break 'emit_halt;\n        }\n        let raw_desc = format!(\"{}.{}\", ctx.table.name, rowid_column_name);\n        let (description, on_error) = halt_desc_and_on_error(\n            &raw_desc,\n            preflight.effective_on_conflict,\n            program.has_statement_conflict,\n        );\n        program.emit_insn(Insn::Halt {\n            err_code: SQLITE_CONSTRAINT_PRIMARYKEY,\n            description,\n            on_error,\n            description_reg: None,\n        });\n    }\n    program.preassign_label_to_next_insn(make_record_label);\n    Ok(())\n}\n\n/// Emit bytecode to check index uniqueness constraint.\n/// Handles partial index predicates, ON REPLACE, UPSERT routing, and non-unique indexes.\n#[allow(clippy::too_many_arguments)]\nfn emit_index_uniqueness_check(\n    program: &mut ProgramBuilder,\n    ctx: &mut InsertEmitCtx,\n    resolver: &mut Resolver,\n    insertion: &Insertion,\n    index: &Index,\n    position: Option<usize>,\n    upsert_catch_all: Option<usize>,\n    preflight: &mut PreflightCtx,\n) -> Result<()> {\n    // find which cursor we opened earlier for this index\n    let idx_cursor_id = ctx\n        .idx_cursors\n        .iter()\n        .find(|(name, _, _)| name == &index.name)\n        .map(|(_, _, c_id)| *c_id)\n        .expect(\"no cursor found for index\");\n\n    // For partial indexes, evaluate the WHERE clause and skip if false\n    let maybe_skip_probe_label = emit_partial_index_check(program, resolver, index, insertion)?;\n\n    let num_cols = index.columns.len();\n    // allocate scratch registers for the index columns plus rowid\n    let idx_start_reg = program.alloc_registers(num_cols + 1);\n\n    // build unpacked key [idx_start_reg .. idx_start_reg+num_cols-1], and rowid in last reg,\n    // copy each index column from the table's column registers into these scratch regs\n    for (i, idx_col) in index.columns.iter().enumerate() {\n        emit_index_column_value_for_insert(\n            program,\n            resolver,\n            insertion,\n            ctx.table,\n            idx_col,\n            idx_start_reg + i,\n        )?;\n    }\n    // last register is the rowid\n    program.emit_insn(Insn::Copy {\n        src_reg: insertion.key_register(),\n        dst_reg: idx_start_reg + num_cols,\n        extra_amount: 0,\n    });\n\n    if index.unique {\n        emit_unique_index_check(\n            program,\n            ctx,\n            resolver,\n            index,\n            idx_cursor_id,\n            idx_start_reg,\n            num_cols,\n            position,\n            upsert_catch_all,\n            preflight,\n        )?;\n    } else {\n        // Non-unique index: insert eagerly only for REPLACE (which doesn't use commit phase).\n        // For UPSERT and ABORT/FAIL/IGNORE/ROLLBACK, defer to commit phase.\n        if preflight.on_replace {\n            let record_reg = program.alloc_register();\n            program.emit_insn(Insn::MakeRecord {\n                start_reg: to_u16(idx_start_reg),\n                count: to_u16(num_cols + 1),\n                dest_reg: to_u16(record_reg),\n                index_name: Some(index.name.clone()),\n                affinity_str: None,\n            });\n            program.emit_insn(Insn::IdxInsert {\n                cursor_id: idx_cursor_id,\n                record_reg,\n                unpacked_start: Some(idx_start_reg),\n                unpacked_count: Some((num_cols + 1) as u16),\n                flags: IdxInsertFlags::new().nchange(true),\n            });\n        }\n    }\n\n    // Close the partial-index skip (preflight)\n    if let Some(lbl) = maybe_skip_probe_label {\n        program.resolve_label(lbl, program.offset());\n    }\n    Ok(())\n}\n\n/// Emit bytecode for unique index conflict detection and handling.\n#[allow(clippy::too_many_arguments)]\nfn emit_unique_index_check(\n    program: &mut ProgramBuilder,\n    ctx: &mut InsertEmitCtx,\n    resolver: &mut Resolver,\n    index: &Index,\n    idx_cursor_id: usize,\n    idx_start_reg: usize,\n    num_cols: usize,\n    position: Option<usize>,\n    upsert_catch_all: Option<usize>,\n    preflight: &mut PreflightCtx,\n) -> Result<()> {\n    let aff = index\n        .columns\n        .iter()\n        .map(|ic| {\n            if ic.expr.is_some() {\n                Affinity::Blob.aff_mask()\n            } else {\n                ctx.table.columns[ic.pos_in_table]\n                    .affinity_with_strict(ctx.table.is_strict)\n                    .aff_mask()\n            }\n        })\n        .collect::<String>();\n    program.emit_insn(Insn::Affinity {\n        start_reg: idx_start_reg,\n        count: NonZeroUsize::new(num_cols).expect(\"nonzero col count\"),\n        affinities: aff,\n    });\n\n    if !preflight.upsert_actions.is_empty() {\n        let next_check = program.allocate_label();\n        program.emit_insn(Insn::NoConflict {\n            cursor_id: idx_cursor_id,\n            target_pc: next_check,\n            record_reg: idx_start_reg,\n            num_regs: num_cols,\n        });\n        // Conflict detected, figure out if this UPSERT handles the conflict\n        if let Some(position) = position.or(upsert_catch_all) {\n            match &preflight.upsert_actions[position].2.do_clause {\n                UpsertDo::Nothing => {\n                    // Bail out without writing anything\n                    program.emit_insn(Insn::Goto {\n                        target_pc: ctx.loop_labels.row_done,\n                    });\n                }\n                UpsertDo::Set { .. } => {\n                    // Route to DO UPDATE: capture conflicting rowid then jump\n                    program.emit_insn(Insn::IdxRowId {\n                        cursor_id: idx_cursor_id,\n                        dest: ctx.conflict_rowid_reg,\n                    });\n                    program.emit_insn(Insn::Goto {\n                        target_pc: preflight.upsert_actions[position].1,\n                    });\n                }\n            }\n        }\n        // No matching UPSERT handler so we emit constraint error\n        // (if conflict clause matched - VM will jump to later instructions and skip halt)\n        let raw_desc = format_unique_violation_desc(ctx.table.name.as_str(), index);\n        let (description, on_error) = halt_desc_and_on_error(\n            &raw_desc,\n            preflight.effective_on_conflict,\n            program.has_statement_conflict,\n        );\n        program.emit_insn(Insn::Halt {\n            err_code: SQLITE_CONSTRAINT_UNIQUE,\n            description,\n            on_error,\n            description_reg: None,\n        });\n\n        // continue preflight with next constraint\n        program.preassign_label_to_next_insn(next_check);\n    } else {\n        // No UPSERT: probe for conflicts.\n        let ok = program.allocate_label();\n        program.emit_insn(Insn::NoConflict {\n            cursor_id: idx_cursor_id,\n            target_pc: ok,\n            record_reg: idx_start_reg,\n            num_regs: num_cols,\n        });\n        if preflight.on_replace {\n            // REPLACE: delete conflicting row immediately, then insert eagerly.\n            program.emit_insn(Insn::IdxRowId {\n                cursor_id: idx_cursor_id,\n                dest: ctx.conflict_rowid_reg,\n            });\n            emit_replace_delete_conflicting_row(\n                program,\n                resolver,\n                preflight.connection,\n                ctx,\n                preflight.table_references,\n            )?;\n            program.emit_insn(Insn::Goto { target_pc: ok });\n        } else if matches!(preflight.effective_on_conflict, ResolveType::Ignore) {\n            // IGNORE: skip this row entirely on unique conflict.\n            program.emit_insn(Insn::Goto {\n                target_pc: ctx.loop_labels.row_done,\n            });\n        } else {\n            // ABORT/FAIL/ROLLBACK: halt on conflict.\n            let raw_desc = format_unique_violation_desc(ctx.table.name.as_str(), index);\n            let (description, on_error) = halt_desc_and_on_error(\n                &raw_desc,\n                preflight.effective_on_conflict,\n                program.has_statement_conflict,\n            );\n            program.emit_insn(Insn::Halt {\n                err_code: SQLITE_CONSTRAINT_UNIQUE,\n                description,\n                on_error,\n                description_reg: None,\n            });\n        }\n        program.preassign_label_to_next_insn(ok);\n\n        if preflight.on_replace {\n            // REPLACE: insert index entry eagerly (right after delete).\n            // IdxDelete repositions the cursor, so we must NOT use USE_SEEK.\n            let record_reg = program.alloc_register();\n            program.emit_insn(Insn::MakeRecord {\n                start_reg: to_u16(idx_start_reg),\n                count: to_u16(num_cols + 1),\n                dest_reg: to_u16(record_reg),\n                index_name: Some(index.name.clone()),\n                affinity_str: None,\n            });\n            program.emit_insn(Insn::IdxInsert {\n                cursor_id: idx_cursor_id,\n                record_reg,\n                unpacked_start: Some(idx_start_reg),\n                unpacked_count: Some((num_cols + 1) as u16),\n                flags: IdxInsertFlags::new().nchange(true),\n            });\n        }\n        // For non-REPLACE cases (ABORT/FAIL/IGNORE/ROLLBACK), index inserts are\n        // deferred to the commit phase after all constraint checks pass.\n        // This prevents stale index entries when a later constraint check fails.\n    }\n    Ok(())\n}\n\n// Preflight phase: evaluate each applicable UNIQUE constraint and probe with NoConflict.\n// If any probe hits:\n// DO NOTHING -> jump to row_done_label.\n//\n// DO UPDATE (matching target) -> fetch conflicting rowid and jump to `upsert_entry`.\n//\n// otherwise, raise SQLITE_CONSTRAINT_UNIQUE\nfn emit_preflight_constraint_checks(\n    program: &mut ProgramBuilder,\n    ctx: &mut InsertEmitCtx,\n    resolver: &mut Resolver,\n    insertion: &Insertion,\n    constraints: &ConstraintsToCheck,\n    preflight: &mut PreflightCtx,\n) -> Result<()> {\n    let mut seen_replace = false;\n    for (constraint, position) in &constraints.constraints_to_check {\n        // Compute per-constraint effective conflict resolution:\n        // Statement-level OR clause overrides; otherwise use constraint's clause.\n        let effective = if ctx.statement_on_conflict.is_some() {\n            ctx.on_conflict\n        } else {\n            match constraint {\n                ResolvedUpsertTarget::PrimaryKey => {\n                    // Use rowid_alias_conflict_clause from BTreeTable, which is preserved\n                    // even for rowid-alias PKs (whose UniqueSet is removed).\n                    ctx.table\n                        .rowid_alias_conflict_clause\n                        .unwrap_or(ResolveType::Abort)\n                }\n                ResolvedUpsertTarget::Index(index) => {\n                    index.on_conflict.unwrap_or(ResolveType::Abort)\n                }\n                ResolvedUpsertTarget::CatchAll => unreachable!(),\n            }\n        };\n        // REPLACE constraints must sort after all non-REPLACE ones\n        // (schema.rs:add_index + IPK deferral ensure this).\n        if effective == ResolveType::Replace {\n            seen_replace = true;\n        } else {\n            turso_assert!(\n                !seen_replace,\n                \"non-REPLACE constraint after REPLACE constraint — sort order invariant violated\"\n            );\n        }\n\n        let effective_on_replace =\n            matches!(effective, ResolveType::Replace) && preflight.upsert_actions.is_empty();\n        preflight.on_replace = effective_on_replace;\n        preflight.effective_on_conflict = effective;\n\n        match constraint {\n            ResolvedUpsertTarget::PrimaryKey => {\n                emit_pk_uniqueness_check(\n                    program,\n                    ctx,\n                    resolver,\n                    insertion,\n                    *position,\n                    constraints.upsert_catch_all_position,\n                    preflight,\n                )?;\n            }\n            ResolvedUpsertTarget::Index(index) => {\n                emit_index_uniqueness_check(\n                    program,\n                    ctx,\n                    resolver,\n                    insertion,\n                    index,\n                    *position,\n                    constraints.upsert_catch_all_position,\n                    preflight,\n                )?;\n            }\n            ResolvedUpsertTarget::CatchAll => unreachable!(),\n        }\n    }\n    Ok(())\n}\n\n// TODO: comeback here later to apply the same improvements on select\nfn translate_virtual_table_insert(\n    program: &mut ProgramBuilder,\n    virtual_table: Arc<VirtualTable>,\n    columns: Vec<ast::Name>,\n    mut body: InsertBody,\n    on_conflict: Option<ResolveType>,\n    resolver: &Resolver,\n    connection: &Arc<crate::Connection>,\n) -> Result<()> {\n    #[cfg(not(feature = \"cli_only\"))]\n    let _ = connection;\n    let allow_dbpage_write = {\n        #[cfg(feature = \"cli_only\")]\n        {\n            virtual_table.name == crate::dbpage::DBPAGE_TABLE_NAME\n                && connection.db.opts.unsafe_testing\n        }\n        #[cfg(not(feature = \"cli_only\"))]\n        {\n            false\n        }\n    };\n    if virtual_table.readonly() && !allow_dbpage_write {\n        crate::bail_constraint_error!(\"Table is read-only: {}\", virtual_table.name);\n    }\n    let (num_values, value) = match &mut body {\n        InsertBody::Select(select, None) => match &mut select.body.select {\n            OneSelect::Values(values) => (values[0].len(), values.pop().unwrap()),\n            _ => crate::bail_parse_error!(\"Virtual tables only support VALUES clause in INSERT\"),\n        },\n        InsertBody::DefaultValues => (0, vec![]),\n        _ => crate::bail_parse_error!(\"Unsupported INSERT body for virtual tables\"),\n    };\n    let table = Table::Virtual(virtual_table.clone());\n    let cursor_id = program.alloc_cursor_id(CursorType::VirtualTable(virtual_table));\n    program.emit_insn(Insn::VOpen { cursor_id });\n    if !allow_dbpage_write {\n        program.emit_insn(Insn::VBegin { cursor_id });\n    }\n    /* *\n     * Inserts for virtual tables are done in a single step.\n     * argv[0] = (NULL for insert)\n     */\n    let registers_start = program.alloc_register();\n    program.emit_insn(Insn::Null {\n        dest: registers_start,\n        dest_end: None,\n    });\n    /* *\n     * argv[1] = (rowid for insert - NULL in most cases)\n     * argv[2..] = column values\n     * */\n    let insertion = build_insertion(program, &table, &columns, num_values)?;\n\n    translate_rows_single(program, &value, &insertion, resolver, false)?;\n    let conflict_action = on_conflict.as_ref().map(|c| c.bit_value()).unwrap_or(0) as u16;\n\n    program.emit_insn(Insn::VUpdate {\n        cursor_id,\n        arg_count: insertion.col_mappings.len() + 2, // +1 for NULL, +1 for rowid\n        start_reg: registers_start,\n        conflict_action,\n    });\n\n    program.emit_insn(Insn::Close { cursor_id });\n\n    let halt_label = program.allocate_label();\n    program.resolve_label(halt_label, program.offset());\n\n    Ok(())\n}\n\n///  makes sure that an AUTOINCREMENT table has a sequence row in `sqlite_sequence`, inserting one with 0 if missing.\nfn ensure_sequence_initialized(\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    table: &schema::BTreeTable,\n    database_id: usize,\n) -> Result<()> {\n    let seq_table = get_valid_sqlite_sequence_table(resolver, database_id)?;\n\n    let seq_cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(seq_table.clone()));\n\n    program.emit_insn(Insn::OpenWrite {\n        cursor_id: seq_cursor_id,\n        root_page: seq_table.root_page.into(),\n        db: database_id,\n    });\n\n    let table_name_reg = program.emit_string8_new_reg(table.name.clone());\n\n    let loop_start_label = program.allocate_label();\n    let entry_exists_label = program.allocate_label();\n    let insert_new_label = program.allocate_label();\n\n    program.emit_insn(Insn::Rewind {\n        cursor_id: seq_cursor_id,\n        pc_if_empty: insert_new_label,\n    });\n\n    program.preassign_label_to_next_insn(loop_start_label);\n\n    let name_col_reg = program.alloc_register();\n\n    program.emit_column_or_rowid(seq_cursor_id, 0, name_col_reg);\n\n    program.emit_insn(Insn::Eq {\n        lhs: table_name_reg,\n        rhs: name_col_reg,\n        target_pc: entry_exists_label,\n        flags: Default::default(),\n        collation: None,\n    });\n\n    program.emit_insn(Insn::Next {\n        cursor_id: seq_cursor_id,\n        pc_if_next: loop_start_label,\n    });\n\n    program.preassign_label_to_next_insn(insert_new_label);\n\n    let record_reg = program.alloc_register();\n    let record_start_reg = program.alloc_registers(2);\n    let zero_reg = program.alloc_register();\n\n    program.emit_insn(Insn::Integer {\n        dest: zero_reg,\n        value: 0,\n    });\n\n    program.emit_insn(Insn::Copy {\n        src_reg: table_name_reg,\n        dst_reg: record_start_reg,\n        extra_amount: 0,\n    });\n\n    program.emit_insn(Insn::Copy {\n        src_reg: zero_reg,\n        dst_reg: record_start_reg + 1,\n        extra_amount: 0,\n    });\n\n    let affinity_str = seq_table\n        .columns\n        .iter()\n        .map(|c| c.affinity().aff_mask())\n        .collect();\n\n    program.emit_insn(Insn::MakeRecord {\n        start_reg: to_u16(record_start_reg),\n        count: to_u16(2),\n        dest_reg: to_u16(record_reg),\n        index_name: None,\n        affinity_str: Some(affinity_str),\n    });\n\n    let new_rowid_reg = program.alloc_register();\n    program.emit_insn(Insn::NewRowid {\n        cursor: seq_cursor_id,\n        rowid_reg: new_rowid_reg,\n        prev_largest_reg: 0,\n    });\n    program.emit_insn(Insn::Insert {\n        cursor: seq_cursor_id,\n        key_reg: new_rowid_reg,\n        record_reg,\n        flag: InsertFlags::new(),\n        table_name: SQLITE_SEQUENCE_TABLE_NAME.to_string(),\n    });\n\n    program.preassign_label_to_next_insn(entry_exists_label);\n    program.emit_insn(Insn::Close {\n        cursor_id: seq_cursor_id,\n    });\n\n    Ok(())\n}\n#[inline]\n/// Build the UNIQUE constraint error description to match sqlite\n/// single column: `t.c1`\n/// multi-column:  `t.(k, c1)`\n/// For constraint-level FAIL/ROLLBACK ON CONFLICT, pre-format the description\n/// and set on_error so the VM's halt() produces Raise(rt, msg) with correct semantics.\n/// `has_statement_conflict` should be true when the statement has its own OR clause\n/// (e.g. INSERT OR FAIL), in which case program.resolve_type already handles it.\npub(crate) fn halt_desc_and_on_error(\n    raw_desc: &str,\n    effective: ResolveType,\n    has_statement_conflict: bool,\n) -> (String, Option<ResolveType>) {\n    if has_statement_conflict {\n        return (raw_desc.to_string(), None);\n    }\n    match effective {\n        ResolveType::Fail | ResolveType::Rollback => (\n            format!(\"UNIQUE constraint failed: {raw_desc} (19)\"),\n            Some(effective),\n        ),\n        _ => (raw_desc.to_string(), None),\n    }\n}\n\npub fn format_unique_violation_desc(table_name: &str, index: &Index) -> String {\n    if index.columns.len() == 1 {\n        let mut s = String::with_capacity(table_name.len() + 1 + index.columns[0].name.len());\n        s.push_str(table_name);\n        s.push('.');\n        s.push_str(&index.columns[0].name);\n        s\n    } else {\n        let mut s = String::with_capacity(table_name.len() + 3 + 4 * index.columns.len());\n        s.push_str(table_name);\n        s.push_str(\".(\");\n        s.push_str(\n            &index\n                .columns\n                .iter()\n                .map(|c| c.name.as_str())\n                .collect::<Vec<_>>()\n                .join(\", \"),\n        );\n        s.push(')');\n        s\n    }\n}\n\n/// Rewrite WHERE clause for partial index to reference insertion registers\npub fn rewrite_partial_index_where(\n    expr: &mut ast::Expr,\n    insertion: &Insertion,\n) -> crate::Result<WalkControl> {\n    let col_reg = |name: &str| -> Option<usize> {\n        if ROWID_STRS.iter().any(|s| s.eq_ignore_ascii_case(name)) {\n            Some(insertion.key_register())\n        } else if let Some(c) = insertion.get_col_mapping_by_name(name) {\n            if c.column.is_rowid_alias() {\n                Some(insertion.key_register())\n            } else {\n                Some(c.register)\n            }\n        } else {\n            None\n        }\n    };\n    walk_expr_mut(\n        expr,\n        &mut |e: &mut ast::Expr| -> crate::Result<WalkControl> {\n            match e {\n                // NOTE: should not have ANY Expr::Columns bound to the expr\n                Expr::Id(name) => {\n                    let normalized = normalize_ident(name.as_str());\n                    if let Some(reg) = col_reg(&normalized) {\n                        *e = Expr::Register(reg);\n                    }\n                }\n                Expr::Qualified(_, col) | Expr::DoublyQualified(_, _, col) => {\n                    let normalized = normalize_ident(col.as_str());\n                    if let Some(reg) = col_reg(&normalized) {\n                        *e = Expr::Register(reg);\n                    }\n                }\n                _ => {}\n            }\n            Ok(WalkControl::Continue)\n        },\n    )\n}\n\n/// For an index expression, rewrite column references to use the insertion registers.\nfn rewrite_index_expr_for_insertion(expr: &mut ast::Expr, insertion: &Insertion) -> Result<()> {\n    let mut missing_column = None;\n    let col_reg = |name: &str| -> Option<usize> {\n        if ROWID_STRS.iter().any(|s| s.eq_ignore_ascii_case(name)) {\n            Some(insertion.key_register())\n        } else if let Some(c) = insertion.get_col_mapping_by_name(name) {\n            if c.column.is_rowid_alias() {\n                Some(insertion.key_register())\n            } else {\n                Some(c.register)\n            }\n        } else {\n            None\n        }\n    };\n    walk_expr_mut(\n        expr,\n        &mut |e: &mut ast::Expr| -> crate::Result<WalkControl> {\n            match e {\n                Expr::Id(name) | Expr::Name(name) => {\n                    let normalized = normalize_ident(name.as_str());\n                    if let Some(reg) = col_reg(&normalized) {\n                        *e = Expr::Register(reg);\n                    } else {\n                        missing_column = Some(normalized);\n                    }\n                }\n                Expr::Qualified(_, col) | Expr::DoublyQualified(_, _, col) => {\n                    let normalized = normalize_ident(col.as_str());\n                    if let Some(reg) = col_reg(&normalized) {\n                        *e = Expr::Register(reg);\n                    } else {\n                        missing_column = Some(normalized);\n                    }\n                }\n                _ => {}\n            }\n            Ok(if missing_column.is_some() {\n                WalkControl::SkipChildren\n            } else {\n                WalkControl::Continue\n            })\n        },\n    )?;\n\n    if let Some(col) = missing_column {\n        return Err(LimboError::PlanningError(format!(\n            \"Column not found in INSERT: {col}\"\n        )));\n    }\n    Ok(())\n}\n\nfn emit_index_column_value_for_insert(\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    insertion: &Insertion,\n    table: &BTreeTable,\n    idx_col: &IndexColumn,\n    dest_reg: usize,\n) -> Result<()> {\n    if let Some(expr) = &idx_col.expr {\n        let mut expr = expr.as_ref().clone();\n        rewrite_index_expr_for_insertion(&mut expr, insertion)?;\n        // After rewrite, Expr::Register nodes reference encoded column registers.\n        // Decode custom type registers so the expression evaluates on user-facing\n        // values, matching what SELECT / CREATE INDEX see.\n        crate::translate::expr::decode_custom_type_registers_in_expr(\n            program,\n            resolver,\n            &mut expr,\n            &table.columns,\n            insertion.first_col_register(),\n            Some(insertion.key_register()),\n            table.is_strict,\n        )?;\n        translate_expr_no_constant_opt(\n            program,\n            Some(&TableReferences::new_empty()),\n            &expr,\n            dest_reg,\n            resolver,\n            NoConstantOptReason::RegisterReuse,\n        )?;\n    } else {\n        let Some(cm) = insertion.get_col_mapping_by_name(&idx_col.name) else {\n            return Err(LimboError::PlanningError(\n                \"Column not found in INSERT\".to_string(),\n            ));\n        };\n        // For rowid alias columns (INTEGER PRIMARY KEY), the actual value lives\n        // in the key register, not the column register (which may hold NULL,\n        // e.g. when the rowid is auto-generated).\n        let src_reg = if cm.column.is_rowid_alias() {\n            insertion.key_register()\n        } else {\n            cm.register\n        };\n        program.emit_insn(Insn::Copy {\n            src_reg,\n            dst_reg: dest_reg,\n            extra_amount: 0,\n        });\n    }\n    Ok(())\n}\n\nstruct ConstraintsToCheck {\n    constraints_to_check: Vec<(ResolvedUpsertTarget, Option<usize>)>,\n    upsert_catch_all_position: Option<usize>,\n}\n\n/// Context for preflight constraint checks\nstruct PreflightCtx<'a, 'b> {\n    /// UPSERT action handlers (target, label, upsert clause)\n    upsert_actions: &'a [(ResolvedUpsertTarget, BranchOffset, Box<Upsert>)],\n    /// Whether ON CONFLICT REPLACE is active globally (without UPSERT).\n    /// This is true when the statement has INSERT OR REPLACE (applies to all constraints).\n    on_replace: bool,\n    /// The effective conflict resolution for the current constraint being checked.\n    /// Updated per-constraint in emit_preflight_constraint_checks.\n    effective_on_conflict: ResolveType,\n    /// Database connection for FK checks\n    connection: &'a Arc<Connection>,\n    /// Table references for expression evaluation\n    table_references: &'b mut TableReferences,\n}\n\n#[allow(clippy::too_many_arguments)]\nfn build_constraints_to_check(\n    table_name: &str,\n    upsert_actions: &[(ResolvedUpsertTarget, BranchOffset, Box<Upsert>)],\n    has_user_provided_rowid: bool,\n    resolver: &Resolver,\n    _connection: &Arc<crate::Connection>,\n    database_id: usize,\n    rowid_alias_conflict_clause: Option<ResolveType>,\n    has_statement_conflict: bool,\n) -> ConstraintsToCheck {\n    let mut constraints_to_check = Vec::new();\n    if has_user_provided_rowid {\n        // Check uniqueness constraint for rowid if it was provided by user.\n        // When the DB allocates it there are no need for separate uniqueness checks.\n        let position = upsert_actions\n            .iter()\n            .position(|(target, ..)| matches!(target, ResolvedUpsertTarget::PrimaryKey));\n        constraints_to_check.push((ResolvedUpsertTarget::PrimaryKey, position));\n    }\n    let indices: Vec<_> = resolver.with_schema(database_id, |s| {\n        s.get_indices(table_name).cloned().collect()\n    });\n    for index in &indices {\n        let position = upsert_actions\n            .iter()\n            .position(|(target, ..)| matches!(target, ResolvedUpsertTarget::Index(x) if Arc::ptr_eq(x, index)));\n        constraints_to_check.push((ResolvedUpsertTarget::Index(index.clone()), position));\n    }\n\n    constraints_to_check.sort_by(|(_, p1), (_, p2)| match (p1, p2) {\n        (Some(p1), Some(p2)) => p1.cmp(p2),\n        (Some(_), None) => std::cmp::Ordering::Less,\n        (None, Some(_)) => std::cmp::Ordering::Greater,\n        (None, None) => std::cmp::Ordering::Equal,\n    });\n\n    // Defer INTEGER PRIMARY KEY REPLACE to run after all other constraint checks.\n    // When IPK has REPLACE and other\n    // constraints exist with potentially different modes, the IPK check\n    // runs last to avoid premature row deletion before FAIL/IGNORE fires.\n    let defer_ipk_replace_after_other_checks = !has_statement_conflict\n        && rowid_alias_conflict_clause == Some(ResolveType::Replace)\n        && constraints_to_check.len() > 1\n        && !upsert_actions\n            .iter()\n            .any(|(t, ..)| matches!(t, ResolvedUpsertTarget::PrimaryKey));\n    if defer_ipk_replace_after_other_checks {\n        if let Some(pos) = constraints_to_check\n            .iter()\n            .position(|(c, _)| matches!(c, ResolvedUpsertTarget::PrimaryKey))\n        {\n            let pk = constraints_to_check.remove(pos);\n            constraints_to_check.push(pk);\n        }\n    }\n\n    // Post-condition: when no statement-level override exists, all REPLACE\n    // constraints (by DDL mode) must form a contiguous suffix. When a statement\n    // override exists, all constraints get the same effective mode, so the DDL\n    // ordering is irrelevant.\n    turso_debug_assert!(\n        has_statement_conflict || {\n            let mut saw_replace = false;\n            constraints_to_check.iter().all(|(c, _)| {\n                let mode = match c {\n                    ResolvedUpsertTarget::PrimaryKey => {\n                        rowid_alias_conflict_clause.unwrap_or(ResolveType::Abort)\n                    }\n                    ResolvedUpsertTarget::Index(idx) => {\n                        idx.on_conflict.unwrap_or(ResolveType::Abort)\n                    }\n                    ResolvedUpsertTarget::CatchAll => return true,\n                };\n                if mode == ResolveType::Replace {\n                    saw_replace = true;\n                    true\n                } else {\n                    !saw_replace\n                }\n            })\n        },\n        \"constraints must have all REPLACE entries at the end\"\n    );\n\n    let upsert_catch_all_position =\n        if let Some((ResolvedUpsertTarget::CatchAll, ..)) = upsert_actions.last() {\n            Some(upsert_actions.len() - 1)\n        } else {\n            None\n        };\n    ConstraintsToCheck {\n        constraints_to_check,\n        upsert_catch_all_position,\n    }\n}\n\nfn emit_update_sqlite_sequence(\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    database_id: usize,\n    seq_cursor_id: usize,\n    r_seq_rowid: usize,\n    table_name_reg: usize,\n    new_key_reg: usize,\n) -> Result<()> {\n    let record_reg = program.alloc_register();\n    let record_start_reg = program.alloc_registers(2);\n    program.emit_insn(Insn::Copy {\n        src_reg: table_name_reg,\n        dst_reg: record_start_reg,\n        extra_amount: 0,\n    });\n    program.emit_insn(Insn::Copy {\n        src_reg: new_key_reg,\n        dst_reg: record_start_reg + 1,\n        extra_amount: 0,\n    });\n\n    let seq_table = get_valid_sqlite_sequence_table(resolver, database_id)?;\n    let affinity_str = seq_table\n        .columns\n        .iter()\n        .map(|col| col.affinity().aff_mask())\n        .collect::<String>();\n    program.emit_insn(Insn::MakeRecord {\n        start_reg: to_u16(record_start_reg),\n        count: to_u16(2),\n        dest_reg: to_u16(record_reg),\n        index_name: None,\n        affinity_str: Some(affinity_str),\n    });\n\n    let update_existing_label = program.allocate_label();\n    let end_update_label = program.allocate_label();\n    program.emit_insn(Insn::NotNull {\n        reg: r_seq_rowid,\n        target_pc: update_existing_label,\n    });\n\n    program.emit_insn(Insn::NewRowid {\n        cursor: seq_cursor_id,\n        rowid_reg: r_seq_rowid,\n        prev_largest_reg: 0,\n    });\n    program.emit_insn(Insn::Insert {\n        cursor: seq_cursor_id,\n        key_reg: r_seq_rowid,\n        record_reg,\n        flag: InsertFlags::new(),\n        table_name: SQLITE_SEQUENCE_TABLE_NAME.to_string(),\n    });\n    program.emit_insn(Insn::Goto {\n        target_pc: end_update_label,\n    });\n\n    program.preassign_label_to_next_insn(update_existing_label);\n    program.emit_insn(Insn::Insert {\n        cursor: seq_cursor_id,\n        key_reg: r_seq_rowid,\n        record_reg,\n        flag: InsertFlags(turso_parser::ast::ResolveType::Replace.bit_value() as u8),\n        table_name: SQLITE_SEQUENCE_TABLE_NAME.to_string(),\n    });\n\n    program.preassign_label_to_next_insn(end_update_label);\n\n    Ok(())\n}\n\nfn emit_replace_delete_conflicting_row(\n    program: &mut ProgramBuilder,\n    resolver: &mut Resolver,\n    connection: &Arc<Connection>,\n    ctx: &mut InsertEmitCtx,\n    table_references: &mut TableReferences,\n) -> Result<()> {\n    program.emit_insn(Insn::SeekRowid {\n        cursor_id: ctx.cursor_id,\n        src_reg: ctx.conflict_rowid_reg,\n        target_pc: ctx.halt_label,\n    });\n\n    // Phase 1: Before Delete - build parent key registers and handle NoAction/Restrict.\n    // CASCADE/SetNull/SetDefault actions are prepared but deferred until after Delete.\n    let prepared_fk_actions = if connection.foreign_keys_enabled() {\n        let prepared = ForeignKeyActions::prepare_fk_delete_actions(\n            program,\n            resolver,\n            ctx.table.name.as_str(),\n            ctx.cursor_id,\n            ctx.conflict_rowid_reg,\n            ctx.database_id,\n        )?;\n        if resolver.schema().has_child_fks(ctx.table.name.as_str()) {\n            emit_fk_child_decrement_on_delete(\n                program,\n                ctx.table.as_ref(),\n                ctx.table.name.as_str(),\n                ctx.cursor_id,\n                ctx.conflict_rowid_reg,\n                ctx.database_id,\n                resolver,\n            )?;\n        }\n        prepared\n    } else {\n        ForeignKeyActions::default()\n    };\n\n    let table = &ctx.table;\n    let table_name = table.name.as_str();\n    let main_cursor_id = ctx.cursor_id;\n\n    for (name, _, index_cursor_id) in ctx.idx_cursors.iter() {\n        let index = resolver\n            .schema()\n            .get_index(table_name, name)\n            .expect(\"index to exist\");\n        let skip_delete_label = if index.where_clause.is_some() {\n            let where_copy = index\n                .bind_where_expr(Some(table_references), resolver)\n                .expect(\"where clause to exist\");\n            let skip_label = program.allocate_label();\n            let reg = program.alloc_register();\n            translate_expr_no_constant_opt(\n                program,\n                Some(table_references),\n                &where_copy,\n                reg,\n                resolver,\n                NoConstantOptReason::RegisterReuse,\n            )?;\n            program.emit_insn(Insn::IfNot {\n                reg,\n                jump_if_null: true,\n                target_pc: skip_label,\n            });\n            Some(skip_label)\n        } else {\n            None\n        };\n\n        let num_regs = index.columns.len() + 1;\n        let start_reg = program.alloc_registers(num_regs);\n\n        for (reg_offset, column_index) in index.columns.iter().enumerate() {\n            if let Some(expr) = &column_index.expr {\n                let mut expr = expr.as_ref().clone();\n                bind_and_rewrite_expr(\n                    &mut expr,\n                    Some(table_references),\n                    None,\n                    resolver,\n                    BindingBehavior::ResultColumnsNotAllowed,\n                )?;\n                translate_expr_no_constant_opt(\n                    program,\n                    Some(table_references),\n                    &expr,\n                    start_reg + reg_offset,\n                    resolver,\n                    NoConstantOptReason::RegisterReuse,\n                )?;\n            } else {\n                program.emit_column_or_rowid(\n                    main_cursor_id,\n                    column_index.pos_in_table,\n                    start_reg + reg_offset,\n                );\n            }\n        }\n        program.emit_insn(Insn::Copy {\n            src_reg: ctx.conflict_rowid_reg,\n            dst_reg: start_reg + num_regs - 1,\n            extra_amount: 0,\n        });\n        program.emit_insn(Insn::IdxDelete {\n            start_reg,\n            num_regs,\n            cursor_id: *index_cursor_id,\n            raise_error_if_no_matching_entry: index.where_clause.is_none(),\n        });\n\n        if let Some(label) = skip_delete_label {\n            program.resolve_label(label, program.offset());\n        }\n    }\n\n    // CDC BEFORE, using rowid_reg\n    if let Some(cdc_cursor_id) = ctx.cdc_table.as_ref().map(|(id, _tbl)| *id) {\n        let cdc_has_before = program.capture_data_changes_info().has_before();\n        let before_record_reg = if cdc_has_before {\n            Some(emit_cdc_full_record(\n                program,\n                &table.columns,\n                main_cursor_id,\n                ctx.conflict_rowid_reg,\n                table.is_strict,\n            ))\n        } else {\n            None\n        };\n        emit_cdc_insns(\n            program,\n            resolver,\n            OperationMode::DELETE,\n            cdc_cursor_id,\n            ctx.conflict_rowid_reg,\n            before_record_reg,\n            None,\n            None,\n            table_name,\n        )?;\n    }\n    program.emit_insn(Insn::Delete {\n        cursor_id: main_cursor_id,\n        table_name: table_name.to_string(),\n        is_part_of_update: true,\n    });\n\n    // Phase 2: After Delete - fire CASCADE/SetNull/SetDefault FK actions.\n    prepared_fk_actions.fire_prepared_fk_delete_actions(\n        program,\n        resolver,\n        connection,\n        ctx.database_id,\n    )?;\n\n    Ok(())\n}\n\n/// Child-side FK checks for INSERT of a single row:\n/// For each outgoing FK on `child_tbl`, if the NEW tuple's FK columns are all non-NULL,\n/// verify that the referenced parent key exists.\npub fn emit_fk_child_insert_checks(\n    program: &mut ProgramBuilder,\n    child_tbl: &BTreeTable,\n    new_start_reg: usize,\n    new_rowid_reg: usize,\n    resolver: &Resolver,\n    database_id: usize,\n) -> crate::Result<()> {\n    for fk_ref in\n        resolver.with_schema(database_id, |s| s.resolved_fks_for_child(&child_tbl.name))?\n    {\n        let is_self_ref = fk_ref.fk.parent_table.eq_ignore_ascii_case(&child_tbl.name);\n\n        // Short-circuit if any NEW component is NULL\n        let fk_ok = program.allocate_label();\n        for cname in &fk_ref.child_cols {\n            let (i, col) = child_tbl.get_column(cname).unwrap();\n            let src = if col.is_rowid_alias() {\n                new_rowid_reg\n            } else {\n                new_start_reg + i\n            };\n            program.emit_insn(Insn::IsNull {\n                reg: src,\n                target_pc: fk_ok,\n            });\n        }\n        let parent_tbl = resolver\n            .with_schema(database_id, |s| s.get_btree_table(&fk_ref.fk.parent_table))\n            .expect(\"parent btree\");\n        if fk_ref.parent_uses_rowid {\n            let pcur = open_read_table(program, &parent_tbl, database_id);\n\n            // first child col carries rowid\n            let (i_child, col_child) = child_tbl.get_column(&fk_ref.child_cols[0]).unwrap();\n            let val_reg = if col_child.is_rowid_alias() {\n                new_rowid_reg\n            } else {\n                new_start_reg + i_child\n            };\n\n            // Normalize rowid to integer for both the probe and the same-row fast path.\n            let tmp = program.alloc_register();\n            program.emit_insn(Insn::Copy {\n                src_reg: val_reg,\n                dst_reg: tmp,\n                extra_amount: 0,\n            });\n            program.emit_insn(Insn::MustBeInt { reg: tmp });\n\n            // If this is a self-reference *and* the child FK equals NEW rowid,\n            // the constraint will be satisfied once this row is inserted\n            if is_self_ref {\n                program.emit_insn(Insn::Eq {\n                    lhs: tmp,\n                    rhs: new_rowid_reg,\n                    target_pc: fk_ok,\n                    flags: CmpInsFlags::default(),\n                    collation: None,\n                });\n            }\n\n            let violation = program.allocate_label();\n            program.emit_insn(Insn::NotExists {\n                cursor: pcur,\n                rowid_reg: tmp,\n                target_pc: violation,\n            });\n            program.emit_insn(Insn::Close { cursor_id: pcur });\n            program.emit_insn(Insn::Goto { target_pc: fk_ok });\n\n            // Missing parent: immediate → Halt before Insert; deferred → counter\n            program.preassign_label_to_next_insn(violation);\n            program.emit_insn(Insn::Close { cursor_id: pcur });\n            if fk_ref.fk.deferred {\n                emit_fk_violation(program, &fk_ref.fk)?;\n            } else {\n                emit_fk_restrict_halt(program)?;\n            }\n            program.preassign_label_to_next_insn(fk_ok);\n        } else {\n            let idx = fk_ref\n                .parent_unique_index\n                .as_ref()\n                .expect(\"parent unique index required\");\n            let icur = open_read_index(program, idx, database_id);\n            let ncols = fk_ref.child_cols.len();\n\n            // Build NEW child probe from child NEW values, apply parent-index affinities.\n            let probe = {\n                let start = program.alloc_registers(ncols);\n                for (k, cname) in fk_ref.child_cols.iter().enumerate() {\n                    let (i, col) = child_tbl.get_column(cname).unwrap();\n                    program.emit_insn(Insn::Copy {\n                        src_reg: if col.is_rowid_alias() {\n                            new_rowid_reg\n                        } else {\n                            new_start_reg + i\n                        },\n                        dst_reg: start + k,\n                        extra_amount: 0,\n                    });\n                }\n                if let Some(cnt) = NonZeroUsize::new(ncols) {\n                    program.emit_insn(Insn::Affinity {\n                        start_reg: start,\n                        count: cnt,\n                        affinities: build_index_affinity_string(idx, &parent_tbl),\n                    });\n                }\n                start\n            };\n            if is_self_ref {\n                // Determine the parent column order to compare against:\n                let parent_cols: Vec<&str> =\n                    idx.columns.iter().map(|ic| ic.name.as_str()).collect();\n\n                // Build new parent-key image from this same row’s new values, in the index order.\n                let parent_new = program.alloc_registers(ncols);\n                for (i, pname) in parent_cols.iter().enumerate() {\n                    let (pos, col) = child_tbl.get_column(pname).unwrap();\n                    program.emit_insn(Insn::Copy {\n                        src_reg: if col.is_rowid_alias() {\n                            new_rowid_reg\n                        } else {\n                            new_start_reg + pos\n                        },\n                        dst_reg: parent_new + i,\n                        extra_amount: 0,\n                    });\n                }\n                if let Some(cnt) = NonZeroUsize::new(ncols) {\n                    program.emit_insn(Insn::Affinity {\n                        start_reg: parent_new,\n                        count: cnt,\n                        affinities: build_index_affinity_string(idx, &parent_tbl),\n                    });\n                }\n\n                // Compare child probe to NEW parent image column-by-column.\n                let mismatch = program.allocate_label();\n                for i in 0..ncols {\n                    let cont = program.allocate_label();\n                    program.emit_insn(Insn::Eq {\n                        lhs: probe + i,\n                        rhs: parent_new + i,\n                        target_pc: cont,\n                        flags: CmpInsFlags::default().jump_if_null(),\n                        collation: Some(super::collate::CollationSeq::Binary),\n                    });\n                    program.emit_insn(Insn::Goto {\n                        target_pc: mismatch,\n                    });\n                    program.preassign_label_to_next_insn(cont);\n                }\n                // All equal: same-row OK\n                program.emit_insn(Insn::Goto { target_pc: fk_ok });\n                program.preassign_label_to_next_insn(mismatch);\n            }\n            index_probe(\n                program,\n                icur,\n                probe,\n                ncols,\n                // on_found: parent exists, FK satisfied\n                |_p| Ok(()),\n                // on_not_found: immediate → Halt; deferred → counter\n                |p| {\n                    if fk_ref.fk.deferred {\n                        emit_fk_violation(p, &fk_ref.fk)?;\n                    } else {\n                        emit_fk_restrict_halt(p)?;\n                    }\n                    Ok(())\n                },\n            )?;\n            program.emit_insn(Insn::Goto { target_pc: fk_ok });\n            program.preassign_label_to_next_insn(fk_ok);\n        }\n    }\n    Ok(())\n}\n\n/// Build NEW parent key image in FK parent-column order into a contiguous register block.\n/// Handles 3 shapes:\n/// - parent_uses_rowid: single \"rowid\" component\n/// - explicit fk.parent_columns\n/// - fk.parent_columns empty => use parent's declared PK columns (order-preserving)\nfn build_parent_key_image_for_insert(\n    program: &mut ProgramBuilder,\n    parent_table: &BTreeTable,\n    pref: &ResolvedFkRef,\n    insertion: &Insertion,\n) -> crate::Result<(usize, usize)> {\n    // Decide column list\n    let parent_cols: Vec<String> = if pref.parent_uses_rowid {\n        vec![\"rowid\".to_string()]\n    } else if !pref.fk.parent_columns.is_empty() {\n        pref.fk.parent_columns.clone()\n    } else {\n        // fall back to the declared PK of the parent table, in schema order\n        parent_table\n            .primary_key_columns\n            .iter()\n            .map(|(n, _)| n.clone())\n            .collect()\n    };\n\n    let ncols = parent_cols.len();\n    let start = program.alloc_registers(ncols);\n    // Copy from the would-be parent insertion\n    for (i, pname) in parent_cols.iter().enumerate() {\n        let src = if pname.eq_ignore_ascii_case(\"rowid\") {\n            insertion.key_register()\n        } else {\n            // For rowid-alias parents, get_col_mapping_by_name will return the key mapping,\n            // not the NULL placeholder in col_mappings.\n            insertion\n                .get_col_mapping_by_name(pname)\n                .ok_or_else(|| {\n                    crate::LimboError::PlanningError(format!(\n                        \"Column '{}' not present in INSERT image for parent {}\",\n                        pname, parent_table.name\n                    ))\n                })?\n                .register\n        };\n        program.emit_insn(Insn::Copy {\n            src_reg: src,\n            dst_reg: start + i,\n            extra_amount: 0,\n        });\n    }\n\n    // Apply affinities of the parent columns (or integer for rowid)\n    let aff: String = if pref.parent_uses_rowid {\n        \"i\".to_string()\n    } else {\n        parent_cols\n            .iter()\n            .map(|name| {\n                let (_, col) = parent_table.get_column(name).ok_or_else(|| {\n                    crate::LimboError::InternalError(format!(\"parent col {name} missing\"))\n                })?;\n                Ok::<_, crate::LimboError>(\n                    col.affinity_with_strict(parent_table.is_strict).aff_mask(),\n                )\n            })\n            .collect::<Result<String, _>>()?\n    };\n    if let Some(count) = NonZeroUsize::new(ncols) {\n        program.emit_insn(Insn::Affinity {\n            start_reg: start,\n            count,\n            affinities: aff,\n        });\n    }\n\n    Ok((start, ncols))\n}\n\n/// Parent-side: when inserting into the parent, decrement the counter\n/// if any child rows reference the NEW parent key.\n/// We *always* do this for deferred FKs, and we *also* do it for\n/// self-referential FKs (even if immediate) because the insert can\n/// “repair” a prior child-insert count recorded earlier in the same statement.\npub fn emit_parent_side_fk_decrement_on_insert(\n    program: &mut ProgramBuilder,\n    parent_table: &BTreeTable,\n    insertion: &Insertion,\n    force_immediate: bool,\n    resolver: &Resolver,\n    database_id: usize,\n) -> crate::Result<()> {\n    for pref in resolver.with_schema(database_id, |s| {\n        s.resolved_fks_referencing(&parent_table.name)\n    })? {\n        let is_self_ref = pref\n            .child_table\n            .name\n            .eq_ignore_ascii_case(&parent_table.name);\n        // Skip only when it cannot repair anything: non-deferred and not self-referencing\n        if !force_immediate && !pref.fk.deferred && !is_self_ref {\n            continue;\n        }\n        let (new_pk_start, n_cols) =\n            build_parent_key_image_for_insert(program, parent_table, &pref, insertion)?;\n\n        let child_tbl = &pref.child_table;\n        let child_cols = &pref.fk.child_columns;\n        let indices: Vec<_> = resolver.with_schema(database_id, |s| {\n            s.get_indices(&child_tbl.name).cloned().collect()\n        });\n        let idx = indices.iter().find(|ix| {\n            ix.columns.len() == child_cols.len()\n                && ix\n                    .columns\n                    .iter()\n                    .zip(child_cols.iter())\n                    .all(|(ic, cc)| ic.name.eq_ignore_ascii_case(cc))\n        });\n\n        if let Some(ix) = idx {\n            let icur = open_read_index(program, ix, database_id);\n            // Copy key into probe regs and apply child-index affinities\n            let probe_start = program.alloc_registers(n_cols);\n            for i in 0..n_cols {\n                program.emit_insn(Insn::Copy {\n                    src_reg: new_pk_start + i,\n                    dst_reg: probe_start + i,\n                    extra_amount: 0,\n                });\n            }\n            if let Some(count) = NonZeroUsize::new(n_cols) {\n                program.emit_insn(Insn::Affinity {\n                    start_reg: probe_start,\n                    count,\n                    affinities: build_index_affinity_string(ix, child_tbl),\n                });\n            }\n\n            let found = program.allocate_label();\n            program.emit_insn(Insn::Found {\n                cursor_id: icur,\n                target_pc: found,\n                record_reg: probe_start,\n                num_regs: n_cols,\n            });\n\n            // Not found, nothing to decrement\n            program.emit_insn(Insn::Close { cursor_id: icur });\n            let skip = program.allocate_label();\n            program.emit_insn(Insn::Goto { target_pc: skip });\n\n            // Found: guarded counter decrement\n            program.resolve_label(found, program.offset());\n            program.emit_insn(Insn::Close { cursor_id: icur });\n            emit_guarded_fk_decrement(program, skip, pref.fk.deferred);\n            program.resolve_label(skip, program.offset());\n        } else {\n            // fallback scan :(\n            let ccur = open_read_table(program, child_tbl, database_id);\n            let done = program.allocate_label();\n            program.emit_insn(Insn::Rewind {\n                cursor_id: ccur,\n                pc_if_empty: done,\n            });\n            let loop_top = program.allocate_label();\n            let next_row = program.allocate_label();\n            program.resolve_label(loop_top, program.offset());\n\n            for (i, child_name) in child_cols.iter().enumerate() {\n                let (pos, _) = child_tbl.get_column(child_name).ok_or_else(|| {\n                    crate::LimboError::InternalError(format!(\"child col {child_name} missing\"))\n                })?;\n                let tmp = program.alloc_register();\n                program.emit_insn(Insn::Column {\n                    cursor_id: ccur,\n                    column: pos,\n                    dest: tmp,\n                    default: None,\n                });\n\n                program.emit_insn(Insn::IsNull {\n                    reg: tmp,\n                    target_pc: next_row,\n                });\n\n                let cont = program.allocate_label();\n                program.emit_insn(Insn::Eq {\n                    lhs: tmp,\n                    rhs: new_pk_start + i,\n                    target_pc: cont,\n                    flags: CmpInsFlags::default().jump_if_null(),\n                    collation: Some(super::collate::CollationSeq::Binary),\n                });\n                program.emit_insn(Insn::Goto {\n                    target_pc: next_row,\n                });\n                program.resolve_label(cont, program.offset());\n            }\n            // Matched one child row: guarded decrement of counter\n            emit_guarded_fk_decrement(program, next_row, pref.fk.deferred);\n            program.resolve_label(next_row, program.offset());\n            program.emit_insn(Insn::Next {\n                cursor_id: ccur,\n                pc_if_next: loop_top,\n            });\n            program.resolve_label(done, program.offset());\n            program.emit_insn(Insn::Close { cursor_id: ccur });\n        }\n    }\n    Ok(())\n}\n\n/// Emit encode expressions for columns with custom types.\n/// For each column that has a custom type with an encode expression,\n/// evaluates the expression with `value` bound to the column register.\nfn emit_custom_type_encode(\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    insertion: &Insertion,\n    table_name: &str,\n) -> Result<()> {\n    let columns: Vec<_> = insertion\n        .col_mappings\n        .iter()\n        .map(|m| m.column.clone())\n        .collect();\n    crate::translate::expr::emit_custom_type_encode_columns(\n        program,\n        resolver,\n        &columns,\n        insertion.first_col_register(),\n        None, // INSERT: encode all columns\n        table_name,\n    )\n}\n"
  },
  {
    "path": "core/translate/integrity_check.rs",
    "content": "use crate::{\n    schema::{Index, Schema, Table},\n    translate::{\n        emitter::Resolver,\n        expr::{\n            bind_and_rewrite_expr, translate_condition_expr, translate_expr_no_constant_opt,\n            BindingBehavior, ConditionMetadata, NoConstantOptReason,\n        },\n        plan::{ColumnUsedMask, IterationDirection, JoinedTable, Operation, Scan, TableReferences},\n    },\n    vdbe::{\n        builder::{CursorKey, CursorType, ProgramBuilder},\n        insn::{CmpInsFlags, Insn},\n    },\n};\nuse turso_parser::ast;\n\n/// Maximum number of errors to report with integrity check. If we exceed this number we will\n/// short circuit the procedure and return early to not waste time. SQLite uses 100 as default.\npub const MAX_INTEGRITY_CHECK_ERRORS: usize = 100;\n\nenum BoundIndexColumn {\n    Column(usize),\n    Expr(Box<ast::Expr>),\n}\n\nstruct BoundIntegrityIndex {\n    index: crate::sync::Arc<Index>,\n    cursor_id: usize,\n    expected_count_reg: usize,\n    where_expr: Option<ast::Expr>,\n    columns: Vec<BoundIndexColumn>,\n    unique_nullable: Vec<bool>,\n}\n\n/// Translate PRAGMA integrity_check.\npub fn translate_integrity_check(\n    schema: &Schema,\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    database_id: usize,\n    max_errors: usize,\n) -> crate::Result<()> {\n    translate_integrity_check_impl(schema, program, resolver, database_id, max_errors, false)\n}\n\n/// Translate PRAGMA quick_check.\npub fn translate_quick_check(\n    schema: &Schema,\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    database_id: usize,\n    max_errors: usize,\n) -> crate::Result<()> {\n    translate_integrity_check_impl(schema, program, resolver, database_id, max_errors, true)\n}\n\nfn emit_integrity_result_row(\n    program: &mut ProgramBuilder,\n    remaining_errors_reg: usize,\n    message_reg: usize,\n    had_error_reg: usize,\n) {\n    program.emit_int(1, had_error_reg);\n    program.emit_result_row(message_reg, 1);\n\n    let continue_label = program.allocate_label();\n    program.emit_insn(Insn::IfPos {\n        reg: remaining_errors_reg,\n        target_pc: continue_label,\n        decrement_by: 1,\n    });\n    program.emit_insn(Insn::Halt {\n        err_code: 0,\n        on_error: None,\n        description_reg: None,\n        description: String::new(),\n    });\n    program.preassign_label_to_next_insn(continue_label);\n}\n\nfn emit_row_missing_from_index_error(\n    program: &mut ProgramBuilder,\n    row_number_reg: usize,\n    scratch_reg: usize,\n    message_reg: usize,\n    index_name: &str,\n    remaining_errors_reg: usize,\n    had_error_reg: usize,\n) {\n    program.emit_string8(\"row \".to_string(), message_reg);\n    program.emit_insn(Insn::Concat {\n        lhs: message_reg,\n        rhs: row_number_reg,\n        dest: message_reg,\n    });\n    program.emit_string8(\" missing from index \".to_string(), scratch_reg);\n    program.emit_insn(Insn::Concat {\n        lhs: message_reg,\n        rhs: scratch_reg,\n        dest: message_reg,\n    });\n    program.emit_string8(index_name.to_string(), scratch_reg);\n    program.emit_insn(Insn::Concat {\n        lhs: message_reg,\n        rhs: scratch_reg,\n        dest: message_reg,\n    });\n    emit_integrity_result_row(program, remaining_errors_reg, message_reg, had_error_reg);\n}\n\nfn bind_expr_for_table(\n    expr: &ast::Expr,\n    table_references: &mut TableReferences,\n    resolver: &Resolver,\n) -> crate::Result<ast::Expr> {\n    let mut out = expr.clone();\n    bind_and_rewrite_expr(\n        &mut out,\n        Some(table_references),\n        None,\n        resolver,\n        BindingBehavior::ResultColumnsNotAllowed,\n    )?;\n    Ok(out)\n}\n\nfn translate_integrity_check_impl(\n    schema: &Schema,\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    database_id: usize,\n    max_errors: usize,\n    quick: bool,\n) -> crate::Result<()> {\n    // 1) Run low-level btree/freelist/overflow verification first. This mirrors\n    // SQLite's OP_IntegrityCk front-pass and can already emit corruption errors\n    // before any row-by-row semantic checks run.\n    let mut root_pages = Vec::with_capacity(schema.tables.len() + schema.indexes.len());\n\n    for table in schema.tables.values() {\n        if let Table::BTree(btree_table) = table.as_ref() {\n            if btree_table.root_page < 0 {\n                continue;\n            }\n            root_pages.push(btree_table.root_page);\n            if let Some(indexes) = schema.indexes.get(btree_table.name.as_str()) {\n                for index in indexes {\n                    if index.root_page > 0 {\n                        root_pages.push(index.root_page);\n                    }\n                }\n            }\n        }\n    }\n\n    for &dropped_root in &schema.dropped_root_pages {\n        root_pages.push(dropped_root);\n    }\n\n    let remaining_errors_reg = program.alloc_register();\n    program.emit_int((max_errors.saturating_sub(1)) as i64, remaining_errors_reg);\n\n    let had_error_reg = program.alloc_register();\n    program.emit_int(0, had_error_reg);\n\n    let message_reg = program.alloc_register();\n    let scratch_reg = program.alloc_register();\n\n    program.emit_insn(Insn::IntegrityCk {\n        db: database_id,\n        max_errors,\n        roots: root_pages,\n        message_register: message_reg,\n    });\n\n    let no_structural_error_label = program.allocate_label();\n    program.emit_insn(Insn::IsNull {\n        reg: message_reg,\n        target_pc: no_structural_error_label,\n    });\n\n    program.emit_string8(\"*** in database main ***\\n\".to_string(), scratch_reg);\n    program.emit_insn(Insn::Concat {\n        lhs: scratch_reg,\n        rhs: message_reg,\n        dest: message_reg,\n    });\n    emit_integrity_result_row(program, remaining_errors_reg, message_reg, had_error_reg);\n    program.preassign_label_to_next_insn(no_structural_error_label);\n\n    // 2) For each ordinary btree table, scan every row and validate:\n    //    - NOT NULL constraints\n    //    - CHECK constraints\n    //    - index membership/uniqueness (integrity_check only)\n    //    - index cardinality cross-checks\n    for table in schema.tables.values() {\n        let Table::BTree(btree_table) = table.as_ref() else {\n            continue;\n        };\n\n        if btree_table.root_page <= 0 {\n            continue;\n        }\n\n        let table_ref_id = program.table_reference_counter.next();\n        let table_cursor_id = program.alloc_cursor_id_keyed(\n            CursorKey::table(table_ref_id),\n            CursorType::BTreeTable(btree_table.clone()),\n        );\n        program.emit_insn(Insn::OpenRead {\n            cursor_id: table_cursor_id,\n            root_page: btree_table.root_page,\n            db: database_id,\n        });\n\n        let mut table_references = TableReferences::new(\n            vec![JoinedTable {\n                op: Operation::Scan(Scan::BTreeTable {\n                    iter_dir: IterationDirection::Forwards,\n                    index: None,\n                }),\n                table: Table::BTree(btree_table.clone()),\n                identifier: btree_table.name.clone(),\n                internal_id: table_ref_id,\n                join_info: None,\n                col_used_mask: ColumnUsedMask::default(),\n                column_use_counts: Vec::new(),\n                expression_index_usages: Vec::new(),\n                database_id,\n                indexed: None,\n            }],\n            vec![],\n        );\n\n        let mut bound_indexes = Vec::new();\n        if let Some(indexes) = schema.indexes.get(btree_table.name.as_str()) {\n            for index in indexes {\n                if index.root_page <= 0 {\n                    continue;\n                }\n\n                let cursor_id = program.alloc_cursor_index(None, index)?;\n                program.emit_insn(Insn::OpenRead {\n                    cursor_id,\n                    root_page: index.root_page,\n                    db: database_id,\n                });\n\n                let expected_count_reg = program.alloc_register();\n                program.emit_int(0, expected_count_reg);\n\n                let mut where_expr = None;\n                if let Some(pred) = index.where_clause.as_deref() {\n                    where_expr = Some(bind_expr_for_table(pred, &mut table_references, resolver)?);\n                }\n\n                let mut columns = Vec::with_capacity(index.columns.len());\n                let mut unique_nullable = Vec::with_capacity(index.columns.len());\n                for col in &index.columns {\n                    if let Some(expr) = col.expr.as_deref() {\n                        columns.push(BoundIndexColumn::Expr(Box::new(bind_expr_for_table(\n                            expr,\n                            &mut table_references,\n                            resolver,\n                        )?)));\n                        unique_nullable.push(true);\n                    } else {\n                        columns.push(BoundIndexColumn::Column(col.pos_in_table));\n                        unique_nullable.push(!btree_table.columns[col.pos_in_table].notnull());\n                    }\n                }\n\n                bound_indexes.push(BoundIntegrityIndex {\n                    index: index.clone(),\n                    cursor_id,\n                    expected_count_reg,\n                    where_expr,\n                    columns,\n                    unique_nullable,\n                });\n            }\n        }\n\n        let mut bound_checks = Vec::with_capacity(btree_table.check_constraints.len());\n        for check in &btree_table.check_constraints {\n            bound_checks.push(bind_expr_for_table(\n                &check.expr,\n                &mut table_references,\n                resolver,\n            )?);\n        }\n\n        let not_null_columns: Vec<(usize, String)> = btree_table\n            .columns\n            .iter()\n            .enumerate()\n            .filter_map(|(idx, col)| {\n                if col.notnull() && !col.is_rowid_alias() {\n                    Some((\n                        idx,\n                        col.name.clone().unwrap_or_else(|| format!(\"column{idx}\")),\n                    ))\n                } else {\n                    None\n                }\n            })\n            .collect();\n\n        let row_number_reg = program.alloc_register();\n        program.emit_int(0, row_number_reg);\n\n        let table_empty_label = program.allocate_label();\n        let loop_start_label = program.allocate_label();\n\n        program.emit_insn(Insn::Rewind {\n            cursor_id: table_cursor_id,\n            pc_if_empty: table_empty_label,\n        });\n        program.preassign_label_to_next_insn(loop_start_label);\n\n        program.emit_insn(Insn::AddImm {\n            register: row_number_reg,\n            value: 1,\n        });\n\n        for (col_idx, col_name) in &not_null_columns {\n            let col_value_reg = program.alloc_register();\n            program.emit_column_or_rowid(table_cursor_id, *col_idx, col_value_reg);\n\n            let not_null_ok = program.allocate_label();\n            program.emit_insn(Insn::NotNull {\n                reg: col_value_reg,\n                target_pc: not_null_ok,\n            });\n            program.emit_string8(\n                format!(\"NULL value in {}.{}\", btree_table.name, col_name),\n                message_reg,\n            );\n            emit_integrity_result_row(program, remaining_errors_reg, message_reg, had_error_reg);\n            program.preassign_label_to_next_insn(not_null_ok);\n        }\n\n        for check_expr in &bound_checks {\n            let check_ok = program.allocate_label();\n            let check_fail = program.allocate_label();\n            translate_condition_expr(\n                program,\n                &table_references,\n                check_expr,\n                ConditionMetadata {\n                    jump_if_condition_is_true: true,\n                    jump_target_when_true: check_ok,\n                    jump_target_when_false: check_fail,\n                    jump_target_when_null: check_ok,\n                },\n                resolver,\n            )?;\n            program.preassign_label_to_next_insn(check_fail);\n            program.emit_string8(\n                format!(\"CHECK constraint failed in {}\", btree_table.name),\n                message_reg,\n            );\n            emit_integrity_result_row(program, remaining_errors_reg, message_reg, had_error_reg);\n            program.preassign_label_to_next_insn(check_ok);\n        }\n\n        for bound_index in &bound_indexes {\n            let skip_current_index = program.allocate_label();\n\n            if let Some(where_expr) = bound_index.where_expr.as_ref() {\n                let where_failed = skip_current_index;\n                let where_true_fallthrough = program.allocate_label();\n                translate_condition_expr(\n                    program,\n                    &table_references,\n                    where_expr,\n                    ConditionMetadata {\n                        // For partial indexes, rows that evaluate predicate to FALSE/NULL\n                        // are not part of the index and must be skipped.\n                        jump_if_condition_is_true: false,\n                        jump_target_when_true: where_true_fallthrough,\n                        jump_target_when_false: where_failed,\n                        jump_target_when_null: where_failed,\n                    },\n                    resolver,\n                )?;\n                program.preassign_label_to_next_insn(where_true_fallthrough);\n            }\n\n            // Count rows that are expected to appear in this index. For partial\n            // indexes this is only rows where the predicate is true.\n            program.emit_insn(Insn::AddImm {\n                register: bound_index.expected_count_reg,\n                value: 1,\n            });\n\n            let key_start_reg = program.alloc_registers(bound_index.columns.len() + 1);\n            for (i, col) in bound_index.columns.iter().enumerate() {\n                let target = key_start_reg + i;\n                match col {\n                    BoundIndexColumn::Column(pos) => {\n                        program.emit_column_or_rowid(table_cursor_id, *pos, target);\n                    }\n                    BoundIndexColumn::Expr(expr) => {\n                        translate_expr_no_constant_opt(\n                            program,\n                            Some(&table_references),\n                            expr,\n                            target,\n                            resolver,\n                            NoConstantOptReason::RegisterReuse,\n                        )?;\n                    }\n                }\n            }\n\n            let rowid_reg = key_start_reg + bound_index.columns.len();\n            program.emit_insn(Insn::RowId {\n                cursor_id: table_cursor_id,\n                dest: rowid_reg,\n            });\n\n            if !quick {\n                let found_label = program.allocate_label();\n                // Verify the table row has a matching index entry (key columns + rowid).\n                program.emit_insn(Insn::Found {\n                    cursor_id: bound_index.cursor_id,\n                    target_pc: found_label,\n                    record_reg: key_start_reg,\n                    num_regs: bound_index.columns.len() + 1,\n                });\n                emit_row_missing_from_index_error(\n                    program,\n                    row_number_reg,\n                    scratch_reg,\n                    message_reg,\n                    &bound_index.index.name,\n                    remaining_errors_reg,\n                    had_error_reg,\n                );\n                program.preassign_label_to_next_insn(found_label);\n\n                if bound_index.index.unique {\n                    // This intentionally runs even after a \"missing from index\"\n                    // report above. SQLite does the same: a single corrupt row\n                    // can violate multiple invariants and each should be\n                    // independently reportable.\n                    //\n                    // Uniqueness rule matches SQLite:\n                    //   unique key is valid if any key column is NULL, OR\n                    //   the next index entry is strictly greater on key columns.\n                    let unique_ok = program.allocate_label();\n                    for (i, is_nullable) in bound_index.unique_nullable.iter().enumerate() {\n                        if *is_nullable {\n                            program.emit_insn(Insn::IsNull {\n                                reg: key_start_reg + i,\n                                target_pc: unique_ok,\n                            });\n                        }\n                    }\n\n                    let next_exists = program.allocate_label();\n                    program.emit_insn(Insn::Next {\n                        cursor_id: bound_index.cursor_id,\n                        pc_if_next: next_exists,\n                    });\n                    program.emit_insn(Insn::Goto {\n                        target_pc: unique_ok,\n                    });\n                    program.preassign_label_to_next_insn(next_exists);\n\n                    program.emit_insn(Insn::IdxGT {\n                        cursor_id: bound_index.cursor_id,\n                        start_reg: key_start_reg,\n                        num_regs: bound_index.columns.len(),\n                        target_pc: unique_ok,\n                    });\n                    program.emit_string8(\n                        format!(\"non-unique entry in index {}\", bound_index.index.name),\n                        message_reg,\n                    );\n                    emit_integrity_result_row(\n                        program,\n                        remaining_errors_reg,\n                        message_reg,\n                        had_error_reg,\n                    );\n                    program.preassign_label_to_next_insn(unique_ok);\n                }\n            }\n            program.preassign_label_to_next_insn(skip_current_index);\n        }\n\n        program.emit_insn(Insn::Next {\n            cursor_id: table_cursor_id,\n            pc_if_next: loop_start_label,\n        });\n        program.preassign_label_to_next_insn(table_empty_label);\n\n        for bound_index in &bound_indexes {\n            if bound_index.where_expr.is_none() {\n                let actual_count_reg = program.alloc_register();\n                program.emit_insn(Insn::Count {\n                    cursor_id: bound_index.cursor_id,\n                    target_reg: actual_count_reg,\n                    exact: true,\n                });\n\n                let counts_match = program.allocate_label();\n                program.emit_insn(Insn::Eq {\n                    lhs: actual_count_reg,\n                    rhs: bound_index.expected_count_reg,\n                    target_pc: counts_match,\n                    flags: CmpInsFlags::default(),\n                    collation: None,\n                });\n                program.emit_string8(\n                    format!(\"wrong # of entries in index {}\", bound_index.index.name),\n                    message_reg,\n                );\n                emit_integrity_result_row(\n                    program,\n                    remaining_errors_reg,\n                    message_reg,\n                    had_error_reg,\n                );\n                program.preassign_label_to_next_insn(counts_match);\n            }\n\n            program.emit_insn(Insn::Close {\n                cursor_id: bound_index.cursor_id,\n            });\n        }\n\n        program.emit_insn(Insn::Close {\n            cursor_id: table_cursor_id,\n        });\n    }\n\n    let has_errors_label = program.allocate_label();\n    program.emit_insn(Insn::If {\n        reg: had_error_reg,\n        target_pc: has_errors_label,\n        jump_if_null: false,\n    });\n    program.emit_string8(\"ok\".to_string(), message_reg);\n    program.emit_result_row(message_reg, 1);\n    program.preassign_label_to_next_insn(has_errors_label);\n\n    let column_name = if quick {\n        \"quick_check\"\n    } else {\n        \"integrity_check\"\n    };\n    program.add_pragma_result_column(column_name.into());\n\n    Ok(())\n}\n"
  },
  {
    "path": "core/translate/logical.rs",
    "content": "//! Logical plan representation for SQL queries\n//!\n//! This module provides a platform-independent intermediate representation\n//! for SQL queries. The logical plan is a DAG (Directed Acyclic Graph) that\n//! supports CTEs and can be used for query optimization before being compiled\n//! to an execution plan (e.g., DBSP circuits).\n//!\n//! The main entry point is `LogicalPlanBuilder` which constructs logical plans\n//! from SQL AST nodes.\nuse crate::function::AggFunc;\nuse crate::numeric::Numeric;\nuse crate::schema::{Schema, Type};\nuse crate::sync::Arc;\nuse crate::turso_assert_ne;\nuse crate::types::Value;\nuse crate::{LimboError, Result};\nuse rustc_hash::FxHashMap as HashMap;\nuse std::fmt::{self, Display, Formatter};\nuse turso_macros::match_ignore_ascii_case;\nuse turso_parser::ast;\n\n/// Result type for preprocessing aggregate expressions\ntype PreprocessAggregateResult = (\n    bool,             // needs_pre_projection\n    Vec<LogicalExpr>, // pre_projection_exprs\n    Vec<ColumnInfo>,  // pre_projection_schema\n    Vec<LogicalExpr>, // modified_aggr_exprs\n    Vec<LogicalExpr>, // modified_group_exprs\n);\n\n/// Result type for parsing join conditions\ntype JoinConditionsResult = (Vec<(LogicalExpr, LogicalExpr)>, Option<LogicalExpr>);\n\n/// Information about a column in a logical schema\n#[derive(Debug, Clone, PartialEq)]\npub struct ColumnInfo {\n    pub name: String,\n    pub ty: Type,\n    pub database: Option<String>,\n    pub table: Option<String>,\n    pub table_alias: Option<String>,\n}\n\n/// Schema information for logical plan nodes\n#[derive(Debug, Clone, PartialEq)]\npub struct LogicalSchema {\n    pub columns: Vec<ColumnInfo>,\n}\n/// A reference to a schema that can be shared between nodes\npub type SchemaRef = Arc<LogicalSchema>;\n\nimpl LogicalSchema {\n    pub fn new(columns: Vec<ColumnInfo>) -> Self {\n        Self { columns }\n    }\n\n    pub fn empty() -> Self {\n        Self {\n            columns: Vec::new(),\n        }\n    }\n\n    pub fn column_count(&self) -> usize {\n        self.columns.len()\n    }\n\n    pub fn find_column(&self, name: &str, table: Option<&str>) -> Option<(usize, &ColumnInfo)> {\n        if let Some(table_ref) = table {\n            // Check if it's a database.table format\n            if table_ref.contains('.') {\n                let parts: Vec<&str> = table_ref.splitn(2, '.').collect();\n                if parts.len() == 2 {\n                    let db = parts[0];\n                    let tbl = parts[1];\n                    return self\n                        .columns\n                        .iter()\n                        .position(|c| {\n                            c.name == name\n                                && c.database.as_deref() == Some(db)\n                                && c.table.as_deref() == Some(tbl)\n                        })\n                        .map(|idx| (idx, &self.columns[idx]));\n                }\n            }\n\n            // Try to match against table alias first, then table name\n            self.columns\n                .iter()\n                .position(|c| {\n                    c.name == name\n                        && (c.table_alias.as_deref() == Some(table_ref)\n                            || c.table.as_deref() == Some(table_ref))\n                })\n                .map(|idx| (idx, &self.columns[idx]))\n        } else {\n            // Unqualified lookup - just match by name\n            self.columns\n                .iter()\n                .position(|c| c.name == name)\n                .map(|idx| (idx, &self.columns[idx]))\n        }\n    }\n}\n\n/// Logical representation of a SQL query plan\n#[derive(Debug, Clone, PartialEq)]\npub enum LogicalPlan {\n    /// Projection - SELECT expressions\n    Projection(Projection),\n    /// Filter - WHERE/HAVING clause\n    Filter(Filter),\n    /// Aggregate - GROUP BY with aggregate functions\n    Aggregate(Aggregate),\n    /// Join - combining two relations\n    Join(Join),\n    /// Sort - ORDER BY clause\n    Sort(Sort),\n    /// Limit - LIMIT/OFFSET clause\n    Limit(Limit),\n    /// Table scan - reading from a base table\n    TableScan(TableScan),\n    /// Union - UNION/UNION ALL/INTERSECT/EXCEPT\n    Union(Union),\n    /// Distinct - remove duplicates\n    Distinct(Distinct),\n    /// Empty relation - no rows\n    EmptyRelation(EmptyRelation),\n    /// Values - literal rows (VALUES clause)\n    Values(Values),\n    /// CTE support - WITH clause\n    WithCTE(WithCTE),\n    /// Reference to a CTE\n    CTERef(CTERef),\n}\n\nimpl LogicalPlan {\n    /// Get the schema of this plan node\n    pub fn schema(&self) -> &SchemaRef {\n        match self {\n            LogicalPlan::Projection(p) => &p.schema,\n            LogicalPlan::Filter(f) => f.input.schema(),\n            LogicalPlan::Aggregate(a) => &a.schema,\n            LogicalPlan::Join(j) => &j.schema,\n            LogicalPlan::Sort(s) => s.input.schema(),\n            LogicalPlan::Limit(l) => l.input.schema(),\n            LogicalPlan::TableScan(t) => &t.schema,\n            LogicalPlan::Union(u) => &u.schema,\n            LogicalPlan::Distinct(d) => d.input.schema(),\n            LogicalPlan::EmptyRelation(e) => &e.schema,\n            LogicalPlan::Values(v) => &v.schema,\n            LogicalPlan::WithCTE(w) => w.body.schema(),\n            LogicalPlan::CTERef(c) => &c.schema,\n        }\n    }\n}\n\n/// Projection operator - SELECT expressions\n#[derive(Debug, Clone, PartialEq)]\npub struct Projection {\n    pub input: Arc<LogicalPlan>,\n    pub exprs: Vec<LogicalExpr>,\n    pub schema: SchemaRef,\n}\n\n/// Filter operator - WHERE/HAVING predicates\n#[derive(Debug, Clone, PartialEq)]\npub struct Filter {\n    pub input: Arc<LogicalPlan>,\n    pub predicate: LogicalExpr,\n}\n\n/// Aggregate operator - GROUP BY with aggregations\n#[derive(Debug, Clone, PartialEq)]\npub struct Aggregate {\n    pub input: Arc<LogicalPlan>,\n    pub group_expr: Vec<LogicalExpr>,\n    pub aggr_expr: Vec<LogicalExpr>,\n    pub schema: SchemaRef,\n}\n\n/// Types of joins\n#[derive(Debug, Clone, Copy, PartialEq)]\npub enum JoinType {\n    Inner,\n    Left,\n    Right,\n    Full,\n    Cross,\n}\n\n/// Join operator - combines two relations\n#[derive(Debug, Clone, PartialEq)]\npub struct Join {\n    pub left: Arc<LogicalPlan>,\n    pub right: Arc<LogicalPlan>,\n    pub join_type: JoinType,\n    pub on: Vec<(LogicalExpr, LogicalExpr)>, // Equijoin conditions (left_expr, right_expr)\n    pub filter: Option<LogicalExpr>,         // Additional filter conditions\n    pub schema: SchemaRef,\n}\n\n/// Sort operator - ORDER BY\n#[derive(Debug, Clone, PartialEq)]\npub struct Sort {\n    pub input: Arc<LogicalPlan>,\n    pub exprs: Vec<SortExpr>,\n}\n\n/// Sort expression with direction\n#[derive(Debug, Clone, PartialEq)]\npub struct SortExpr {\n    pub expr: LogicalExpr,\n    pub asc: bool,\n    pub nulls_first: bool,\n}\n\n/// Limit operator - LIMIT/OFFSET\n#[derive(Debug, Clone, PartialEq)]\npub struct Limit {\n    pub input: Arc<LogicalPlan>,\n    pub skip: Option<usize>,\n    pub fetch: Option<usize>,\n}\n\n/// Table scan operator\n#[derive(Debug, Clone, PartialEq)]\npub struct TableScan {\n    pub table_name: String,\n    pub alias: Option<String>,\n    pub schema: SchemaRef,\n    pub projection: Option<Vec<usize>>, // Column indices to project\n}\n\n/// Union operator\n#[derive(Debug, Clone, PartialEq)]\npub struct Union {\n    pub inputs: Vec<Arc<LogicalPlan>>,\n    pub all: bool, // true for UNION ALL, false for UNION\n    pub schema: SchemaRef,\n}\n\n/// Distinct operator\n#[derive(Debug, Clone, PartialEq)]\npub struct Distinct {\n    pub input: Arc<LogicalPlan>,\n}\n\n/// Empty relation - produces no rows\n#[derive(Debug, Clone, PartialEq)]\npub struct EmptyRelation {\n    pub produce_one_row: bool,\n    pub schema: SchemaRef,\n}\n\n/// Values operator - literal rows\n#[derive(Debug, Clone, PartialEq)]\npub struct Values {\n    pub rows: Vec<Vec<LogicalExpr>>,\n    pub schema: SchemaRef,\n}\n\n/// WITH clause - CTEs\n#[derive(Debug, Clone, PartialEq)]\npub struct WithCTE {\n    pub ctes: HashMap<String, Arc<LogicalPlan>>,\n    pub body: Arc<LogicalPlan>,\n}\n\n/// Reference to a CTE\n#[derive(Debug, Clone, PartialEq)]\npub struct CTERef {\n    pub name: String,\n    pub schema: SchemaRef,\n}\n\n/// Logical expression representation\n#[derive(Debug, Clone, PartialEq)]\npub enum LogicalExpr {\n    /// Column reference\n    Column(Column),\n    /// Literal value\n    Literal(Value),\n    /// Binary expression\n    BinaryExpr {\n        left: Box<LogicalExpr>,\n        op: BinaryOperator,\n        right: Box<LogicalExpr>,\n    },\n    /// Unary expression\n    UnaryExpr {\n        op: UnaryOperator,\n        expr: Box<LogicalExpr>,\n    },\n    /// Aggregate function\n    AggregateFunction {\n        fun: AggregateFunction,\n        args: Vec<LogicalExpr>,\n        distinct: bool,\n    },\n    /// Scalar function call\n    ScalarFunction { fun: String, args: Vec<LogicalExpr> },\n    /// CASE expression\n    Case {\n        expr: Option<Box<LogicalExpr>>,\n        when_then: Vec<(LogicalExpr, LogicalExpr)>,\n        else_expr: Option<Box<LogicalExpr>>,\n    },\n    /// IN list\n    InList {\n        expr: Box<LogicalExpr>,\n        list: Vec<LogicalExpr>,\n        negated: bool,\n    },\n    /// IN subquery\n    InSubquery {\n        expr: Box<LogicalExpr>,\n        subquery: Arc<LogicalPlan>,\n        negated: bool,\n    },\n    /// EXISTS subquery\n    Exists {\n        subquery: Arc<LogicalPlan>,\n        negated: bool,\n    },\n    /// Scalar subquery\n    ScalarSubquery(Arc<LogicalPlan>),\n    /// Alias for an expression\n    Alias {\n        expr: Box<LogicalExpr>,\n        alias: String,\n    },\n    /// IS NULL / IS NOT NULL\n    IsNull {\n        expr: Box<LogicalExpr>,\n        negated: bool,\n    },\n    /// BETWEEN\n    Between {\n        expr: Box<LogicalExpr>,\n        low: Box<LogicalExpr>,\n        high: Box<LogicalExpr>,\n        negated: bool,\n    },\n    /// LIKE pattern matching\n    Like {\n        expr: Box<LogicalExpr>,\n        pattern: Box<LogicalExpr>,\n        escape: Option<char>,\n        negated: bool,\n    },\n    /// CAST expression\n    Cast {\n        expr: Box<LogicalExpr>,\n        type_name: Option<ast::Type>,\n    },\n}\n\n/// Column reference\n#[derive(Debug, Clone, PartialEq)]\npub struct Column {\n    pub name: String,\n    pub table: Option<String>,\n}\n\nimpl Column {\n    pub fn new(name: impl Into<String>) -> Self {\n        Self {\n            name: name.into(),\n            table: None,\n        }\n    }\n\n    pub fn with_table(name: impl Into<String>, table: impl Into<String>) -> Self {\n        Self {\n            name: name.into(),\n            table: Some(table.into()),\n        }\n    }\n}\n\nimpl Display for Column {\n    fn fmt(&self, f: &mut Formatter) -> fmt::Result {\n        match &self.table {\n            Some(t) => write!(f, \"{}.{}\", t, self.name),\n            None => write!(f, \"{}\", self.name),\n        }\n    }\n}\n\n/// Strip alias wrapper from an expression, returning the underlying expression.\n/// This is useful when comparing expressions where one might be aliased and the other not,\n/// such as when matching SELECT expressions with GROUP BY expressions.\npub fn strip_alias(expr: &LogicalExpr) -> &LogicalExpr {\n    match expr {\n        LogicalExpr::Alias { expr, .. } => expr,\n        _ => expr,\n    }\n}\n\n/// Type alias for binary operators\npub type BinaryOperator = ast::Operator;\n\n/// Type alias for unary operators\npub type UnaryOperator = ast::UnaryOperator;\n\n/// Type alias for aggregate functions\npub type AggregateFunction = AggFunc;\n\n/// Compiler from AST to LogicalPlan\npub struct LogicalPlanBuilder<'a> {\n    schema: &'a Schema,\n    ctes: HashMap<String, Arc<LogicalPlan>>,\n}\n\nimpl<'a> LogicalPlanBuilder<'a> {\n    pub fn new(schema: &'a Schema) -> Self {\n        Self {\n            schema,\n            ctes: HashMap::default(),\n        }\n    }\n\n    /// Main entry point: compile a statement to a logical plan\n    pub fn build_statement(&mut self, stmt: &ast::Stmt) -> Result<LogicalPlan> {\n        match stmt {\n            ast::Stmt::Select(select) => self.build_select(select),\n            _ => Err(LimboError::ParseError(\n                \"Only SELECT statements are currently supported in logical plans\".to_string(),\n            )),\n        }\n    }\n\n    // Convert Name to String\n    fn name_to_string(name: &ast::Name) -> String {\n        name.as_str().to_string()\n    }\n\n    // Build a SELECT statement\n    // Build a logical plan from a SELECT statement\n    fn build_select(&mut self, select: &ast::Select) -> Result<LogicalPlan> {\n        // Handle WITH clause if present\n        if let Some(with) = &select.with {\n            return self.build_with_cte(with, select);\n        }\n\n        // Build the main query body\n        let order_by = &select.order_by;\n        let limit = &select.limit;\n        self.build_select_body(&select.body, order_by, limit)\n    }\n\n    // Build WITH CTE\n    fn build_with_cte(&mut self, with: &ast::With, select: &ast::Select) -> Result<LogicalPlan> {\n        let mut cte_plans = HashMap::default();\n\n        // Build each CTE\n        for cte in &with.ctes {\n            let cte_plan = self.build_select(&cte.select)?;\n            let cte_name = Self::name_to_string(&cte.tbl_name);\n            cte_plans.insert(cte_name.clone(), Arc::new(cte_plan));\n            self.ctes\n                .insert(cte_name.clone(), cte_plans[&cte_name].clone());\n        }\n\n        // Build the main body with CTEs available\n        let order_by = &select.order_by;\n        let limit = &select.limit;\n        let body = self.build_select_body(&select.body, order_by, limit)?;\n\n        // Clear CTEs from builder context\n        for cte in &with.ctes {\n            self.ctes.remove(&Self::name_to_string(&cte.tbl_name));\n        }\n\n        Ok(LogicalPlan::WithCTE(WithCTE {\n            ctes: cte_plans,\n            body: Arc::new(body),\n        }))\n    }\n\n    // Build SELECT body\n    fn build_select_body(\n        &mut self,\n        body: &ast::SelectBody,\n        order_by: &[ast::SortedColumn],\n        limit: &Option<ast::Limit>,\n    ) -> Result<LogicalPlan> {\n        let mut plan = self.build_one_select(&body.select)?;\n\n        // Handle compound operators (UNION, INTERSECT, EXCEPT)\n        if !body.compounds.is_empty() {\n            for compound in &body.compounds {\n                let right = self.build_one_select(&compound.select)?;\n                plan = Self::build_compound(plan, right, &compound.operator)?;\n            }\n        }\n\n        // Apply ORDER BY\n        if !order_by.is_empty() {\n            plan = self.build_sort(plan, order_by)?;\n        }\n\n        // Apply LIMIT\n        if let Some(limit) = limit {\n            plan = Self::build_limit(plan, limit)?;\n        }\n\n        Ok(plan)\n    }\n\n    // Build a single SELECT (without compounds)\n    fn build_one_select(&mut self, select: &ast::OneSelect) -> Result<LogicalPlan> {\n        match select {\n            ast::OneSelect::Select {\n                distinctness,\n                columns,\n                from,\n                where_clause,\n                group_by,\n                window_clause: _,\n            } => {\n                // Start with FROM clause\n                let mut plan = if let Some(from) = from {\n                    self.build_from(from)?\n                } else {\n                    // No FROM clause - single row\n                    LogicalPlan::EmptyRelation(EmptyRelation {\n                        produce_one_row: true,\n                        schema: Arc::new(LogicalSchema::empty()),\n                    })\n                };\n\n                // Apply WHERE\n                if let Some(where_expr) = where_clause {\n                    let predicate = self.build_expr(where_expr, plan.schema())?;\n                    plan = LogicalPlan::Filter(Filter {\n                        input: Arc::new(plan),\n                        predicate,\n                    });\n                }\n\n                // Apply GROUP BY and aggregations\n                if let Some(group_by) = group_by {\n                    plan = self.build_aggregate(plan, group_by, columns)?;\n                } else if Self::has_aggregates(columns) {\n                    // Aggregation without GROUP BY\n                    plan = self.build_aggregate_no_group(plan, columns)?;\n                } else {\n                    // Regular projection\n                    plan = self.build_projection(plan, columns)?;\n                }\n\n                // Apply HAVING (part of GROUP BY)\n                if let Some(ref group_by) = group_by {\n                    if let Some(ref having_expr) = group_by.having {\n                        let predicate = self.build_expr(having_expr, plan.schema())?;\n                        plan = LogicalPlan::Filter(Filter {\n                            input: Arc::new(plan),\n                            predicate,\n                        });\n                    }\n                }\n\n                // Apply DISTINCT\n                if distinctness.is_some() {\n                    plan = LogicalPlan::Distinct(Distinct {\n                        input: Arc::new(plan),\n                    });\n                }\n\n                Ok(plan)\n            }\n            ast::OneSelect::Values(values) => self.build_values(values),\n        }\n    }\n\n    // Build FROM clause\n    fn build_from(&mut self, from: &ast::FromClause) -> Result<LogicalPlan> {\n        let mut plan = { self.build_select_table(&from.select)? };\n\n        // Handle JOINs\n        if !from.joins.is_empty() {\n            for join in &from.joins {\n                let right = self.build_select_table(&join.table)?;\n                plan = self.build_join(plan, right, &join.operator, &join.constraint)?;\n            }\n        }\n\n        Ok(plan)\n    }\n\n    // Build a table reference\n    fn build_select_table(&mut self, table: &ast::SelectTable) -> Result<LogicalPlan> {\n        match table {\n            ast::SelectTable::Table(name, alias, _indexed) => {\n                let table_name = Self::name_to_string(&name.name);\n                // Check if it's a CTE reference\n                if let Some(cte_plan) = self.ctes.get(&table_name) {\n                    return Ok(LogicalPlan::CTERef(CTERef {\n                        name: table_name.clone(),\n                        schema: cte_plan.schema().clone(),\n                    }));\n                }\n\n                // Regular table scan\n                let table_alias = alias.as_ref().map(|a| match a {\n                    ast::As::As(name) => Self::name_to_string(name),\n                    ast::As::Elided(name) => Self::name_to_string(name),\n                });\n                let table_schema = self.get_table_schema(&table_name, table_alias.as_deref())?;\n                Ok(LogicalPlan::TableScan(TableScan {\n                    table_name,\n                    alias: table_alias,\n                    schema: table_schema,\n                    projection: None,\n                }))\n            }\n            ast::SelectTable::Select(subquery, _alias) => self.build_select(subquery),\n            ast::SelectTable::TableCall(_, _, _) => Err(LimboError::ParseError(\n                \"Table-valued functions are not supported in logical plans\".to_string(),\n            )),\n            ast::SelectTable::Sub(_, _) => Err(LimboError::ParseError(\n                \"Subquery in FROM clause not yet supported\".to_string(),\n            )),\n        }\n    }\n\n    // Build JOIN\n    fn build_join(\n        &mut self,\n        left: LogicalPlan,\n        right: LogicalPlan,\n        op: &ast::JoinOperator,\n        constraint: &Option<ast::JoinConstraint>,\n    ) -> Result<LogicalPlan> {\n        // Determine join type\n        let join_type = match op {\n            ast::JoinOperator::Comma => JoinType::Cross, // Comma is essentially a cross join\n            ast::JoinOperator::TypedJoin(Some(jt)) => {\n                // Check the join type flags\n                // Note: JoinType can have multiple flags set\n                if jt.contains(ast::JoinType::NATURAL) {\n                    // Natural joins need special handling - find common columns\n                    return self.build_natural_join(left, right, JoinType::Inner);\n                } else if jt.contains(ast::JoinType::LEFT)\n                    && jt.contains(ast::JoinType::RIGHT)\n                    && jt.contains(ast::JoinType::OUTER)\n                {\n                    // FULL OUTER JOIN (has LEFT, RIGHT, and OUTER)\n                    JoinType::Full\n                } else if jt.contains(ast::JoinType::LEFT) && jt.contains(ast::JoinType::OUTER) {\n                    JoinType::Left\n                } else if jt.contains(ast::JoinType::RIGHT) && jt.contains(ast::JoinType::OUTER) {\n                    JoinType::Right\n                } else if jt.contains(ast::JoinType::OUTER)\n                    && !jt.contains(ast::JoinType::LEFT)\n                    && !jt.contains(ast::JoinType::RIGHT)\n                {\n                    // Plain OUTER JOIN should also be FULL\n                    JoinType::Full\n                } else if jt.contains(ast::JoinType::LEFT) {\n                    JoinType::Left\n                } else if jt.contains(ast::JoinType::RIGHT) {\n                    JoinType::Right\n                } else if jt.contains(ast::JoinType::CROSS)\n                    || (jt.contains(ast::JoinType::INNER) && jt.contains(ast::JoinType::CROSS))\n                {\n                    JoinType::Cross\n                } else {\n                    JoinType::Inner // Default to inner\n                }\n            }\n            ast::JoinOperator::TypedJoin(None) => JoinType::Inner, // Default JOIN is INNER JOIN\n        };\n\n        // Build join conditions\n        let (on_conditions, filter) = match constraint {\n            Some(ast::JoinConstraint::On(expr)) => {\n                // Parse ON clause into equijoin conditions and filters\n                self.parse_join_conditions(expr, left.schema(), right.schema())?\n            }\n            Some(ast::JoinConstraint::Using(columns)) => {\n                // Build equijoin conditions from USING clause\n                let on = self.build_using_conditions(columns, left.schema(), right.schema())?;\n                (on, None)\n            }\n            None => {\n                // Cross join or natural join\n                (Vec::new(), None)\n            }\n        };\n\n        // Build combined schema\n        let schema = self.build_join_schema(&left, &right, &join_type)?;\n\n        Ok(LogicalPlan::Join(Join {\n            left: Arc::new(left),\n            right: Arc::new(right),\n            join_type,\n            on: on_conditions,\n            filter,\n            schema,\n        }))\n    }\n\n    // Helper: Parse join conditions into equijoins and filters\n    fn parse_join_conditions(\n        &mut self,\n        expr: &ast::Expr,\n        left_schema: &SchemaRef,\n        right_schema: &SchemaRef,\n    ) -> Result<JoinConditionsResult> {\n        // For now, we'll handle simple equality conditions\n        // More complex conditions will go into the filter\n        let mut equijoins = Vec::new();\n        let mut filters = Vec::new();\n\n        // Try to extract equijoin conditions from the expression\n        self.extract_equijoin_conditions(\n            expr,\n            left_schema,\n            right_schema,\n            &mut equijoins,\n            &mut filters,\n        )?;\n\n        let filter = if filters.is_empty() {\n            None\n        } else {\n            // Combine multiple filters with AND\n            Some(\n                filters\n                    .into_iter()\n                    .reduce(|acc, e| LogicalExpr::BinaryExpr {\n                        left: Box::new(acc),\n                        op: BinaryOperator::And,\n                        right: Box::new(e),\n                    })\n                    .unwrap(),\n            )\n        };\n\n        Ok((equijoins, filter))\n    }\n\n    // Helper: Extract equijoin conditions from expression\n    fn extract_equijoin_conditions(\n        &mut self,\n        expr: &ast::Expr,\n        left_schema: &SchemaRef,\n        right_schema: &SchemaRef,\n        equijoins: &mut Vec<(LogicalExpr, LogicalExpr)>,\n        filters: &mut Vec<LogicalExpr>,\n    ) -> Result<()> {\n        match expr {\n            ast::Expr::Binary(lhs, ast::Operator::Equals, rhs) => {\n                // Check if this is an equijoin condition (left.col = right.col)\n                let left_expr = self.build_expr(lhs, left_schema)?;\n                let right_expr = self.build_expr(rhs, right_schema)?;\n\n                // For simplicity, we'll check if one references left and one references right\n                // In a real implementation, we'd need more sophisticated column resolution\n                equijoins.push((left_expr, right_expr));\n            }\n            ast::Expr::Binary(lhs, ast::Operator::And, rhs) => {\n                // Recursively extract from AND conditions\n                self.extract_equijoin_conditions(\n                    lhs,\n                    left_schema,\n                    right_schema,\n                    equijoins,\n                    filters,\n                )?;\n                self.extract_equijoin_conditions(\n                    rhs,\n                    left_schema,\n                    right_schema,\n                    equijoins,\n                    filters,\n                )?;\n            }\n            _ => {\n                // Other conditions go into the filter\n                // We need a combined schema to build the expression\n                let combined_schema = self.combine_schemas(left_schema, right_schema)?;\n                let filter_expr = self.build_expr(expr, &combined_schema)?;\n                filters.push(filter_expr);\n            }\n        }\n        Ok(())\n    }\n\n    // Helper: Build equijoin conditions from USING clause\n    fn build_using_conditions(\n        &mut self,\n        columns: &[ast::Name],\n        left_schema: &SchemaRef,\n        right_schema: &SchemaRef,\n    ) -> Result<Vec<(LogicalExpr, LogicalExpr)>> {\n        let mut conditions = Vec::new();\n\n        for col_name in columns {\n            let name = Self::name_to_string(col_name);\n\n            // Find the column in both schemas\n            let _left_idx = left_schema\n                .columns\n                .iter()\n                .position(|col| col.name == name)\n                .ok_or_else(|| {\n                    LimboError::ParseError(format!(\"Column {name} not found in left table\"))\n                })?;\n            let _right_idx = right_schema\n                .columns\n                .iter()\n                .position(|col| col.name == name)\n                .ok_or_else(|| {\n                    LimboError::ParseError(format!(\"Column {name} not found in right table\"))\n                })?;\n\n            conditions.push((\n                LogicalExpr::Column(Column {\n                    name: name.clone(),\n                    table: None, // Will be resolved later\n                }),\n                LogicalExpr::Column(Column {\n                    name,\n                    table: None, // Will be resolved later\n                }),\n            ));\n        }\n\n        Ok(conditions)\n    }\n\n    // Helper: Build natural join by finding common columns\n    fn build_natural_join(\n        &mut self,\n        left: LogicalPlan,\n        right: LogicalPlan,\n        join_type: JoinType,\n    ) -> Result<LogicalPlan> {\n        let left_schema = left.schema();\n        let right_schema = right.schema();\n\n        // Find common column names\n        let mut common_columns = Vec::new();\n        for left_col in &left_schema.columns {\n            if right_schema\n                .columns\n                .iter()\n                .any(|col| col.name == left_col.name)\n            {\n                common_columns.push(ast::Name::exact(left_col.name.clone()));\n            }\n        }\n\n        if common_columns.is_empty() {\n            // Natural join with no common columns becomes a cross join\n            let schema = self.build_join_schema(&left, &right, &JoinType::Cross)?;\n            return Ok(LogicalPlan::Join(Join {\n                left: Arc::new(left),\n                right: Arc::new(right),\n                join_type: JoinType::Cross,\n                on: Vec::new(),\n                filter: None,\n                schema,\n            }));\n        }\n\n        // Build equijoin conditions for common columns\n        let on = self.build_using_conditions(&common_columns, left_schema, right_schema)?;\n        let schema = self.build_join_schema(&left, &right, &join_type)?;\n\n        Ok(LogicalPlan::Join(Join {\n            left: Arc::new(left),\n            right: Arc::new(right),\n            join_type,\n            on,\n            filter: None,\n            schema,\n        }))\n    }\n\n    // Helper: Build schema for join result\n    fn build_join_schema(\n        &self,\n        left: &LogicalPlan,\n        right: &LogicalPlan,\n        _join_type: &JoinType,\n    ) -> Result<SchemaRef> {\n        let left_schema = left.schema();\n        let right_schema = right.schema();\n\n        // Concatenate the schemas, preserving all column information\n        let mut columns = Vec::new();\n\n        // Keep all columns from left with their table info\n        for col in &left_schema.columns {\n            columns.push(col.clone());\n        }\n\n        // Keep all columns from right with their table info\n        for col in &right_schema.columns {\n            columns.push(col.clone());\n        }\n\n        Ok(Arc::new(LogicalSchema::new(columns)))\n    }\n\n    // Helper: Combine two schemas for expression building\n    fn combine_schemas(&self, left: &SchemaRef, right: &SchemaRef) -> Result<SchemaRef> {\n        let mut columns = left.columns.clone();\n        columns.extend(right.columns.clone());\n        Ok(Arc::new(LogicalSchema::new(columns)))\n    }\n\n    // Build projection\n    fn build_projection(\n        &mut self,\n        input: LogicalPlan,\n        columns: &[ast::ResultColumn],\n    ) -> Result<LogicalPlan> {\n        let input_schema = input.schema();\n        let mut proj_exprs = Vec::new();\n        let mut schema_columns = Vec::new();\n\n        for col in columns {\n            match col {\n                ast::ResultColumn::Expr(expr, alias) => {\n                    let logical_expr = self.build_expr(expr, input_schema)?;\n                    let col_name = match alias {\n                        Some(as_alias) => match as_alias {\n                            ast::As::As(name) | ast::As::Elided(name) => Self::name_to_string(name),\n                        },\n                        None => Self::expr_to_column_name(expr),\n                    };\n                    let col_type = Self::infer_expr_type(&logical_expr, input_schema)?;\n\n                    schema_columns.push(ColumnInfo {\n                        name: col_name.clone(),\n                        ty: col_type,\n                        database: None,\n                        table: None,\n                        table_alias: None,\n                    });\n\n                    if let Some(as_alias) = alias {\n                        let alias_name = match as_alias {\n                            ast::As::As(name) | ast::As::Elided(name) => Self::name_to_string(name),\n                        };\n                        proj_exprs.push(LogicalExpr::Alias {\n                            expr: Box::new(logical_expr),\n                            alias: alias_name,\n                        });\n                    } else {\n                        proj_exprs.push(logical_expr);\n                    }\n                }\n                ast::ResultColumn::Star => {\n                    // Expand * to all columns\n                    for col in &input_schema.columns {\n                        proj_exprs.push(LogicalExpr::Column(Column::new(col.name.clone())));\n                        schema_columns.push(col.clone());\n                    }\n                }\n                ast::ResultColumn::TableStar(table) => {\n                    // Expand table.* to all columns from that table\n                    let table_name = Self::name_to_string(table);\n                    for col in &input_schema.columns {\n                        // Simple check - would need proper table tracking in real implementation\n                        proj_exprs.push(LogicalExpr::Column(Column::with_table(\n                            col.name.clone(),\n                            table_name.clone(),\n                        )));\n                        schema_columns.push(col.clone());\n                    }\n                }\n            }\n        }\n\n        Ok(LogicalPlan::Projection(Projection {\n            input: Arc::new(input),\n            exprs: proj_exprs,\n            schema: Arc::new(LogicalSchema::new(schema_columns)),\n        }))\n    }\n\n    // Helper function to preprocess aggregate expressions that contain complex arguments\n    // Returns: (needs_pre_projection, pre_projection_exprs, pre_projection_schema, modified_aggr_exprs)\n    //\n    // This will be used in expressions like select sum(hex(a + 2)) from tbl => hex(a + 2) is a\n    // pre-projection.\n    //\n    // Another alternative is to always generate a projection together with an aggregation, and\n    // just have \"a\" be the identity projection if we don't have a complex case. But that's quite\n    // wasteful.\n    fn preprocess_aggregate_expressions(\n        aggr_exprs: &[LogicalExpr],\n        group_exprs: &[LogicalExpr],\n        input_schema: &SchemaRef,\n    ) -> Result<PreprocessAggregateResult> {\n        let mut needs_pre_projection = false;\n        let mut pre_projection_exprs = Vec::new();\n        let mut pre_projection_schema = Vec::new();\n        let mut modified_aggr_exprs = Vec::new();\n        let mut modified_group_exprs = Vec::new();\n        let mut projected_col_counter = 0;\n\n        // First, add all group by expressions to the pre-projection\n        for expr in group_exprs {\n            if let LogicalExpr::Column(col) = expr {\n                pre_projection_exprs.push(expr.clone());\n                let col_type = Self::infer_expr_type(expr, input_schema)?;\n                pre_projection_schema.push(ColumnInfo {\n                    name: col.name.clone(),\n                    ty: col_type,\n                    database: None,\n                    table: col.table.clone(),\n                    table_alias: None,\n                });\n                // Column references stay as-is in the modified group expressions\n                modified_group_exprs.push(expr.clone());\n            } else {\n                // Complex group by expression - project it\n                needs_pre_projection = true;\n                let proj_col_name = format!(\"__group_proj_{projected_col_counter}\");\n                projected_col_counter += 1;\n                pre_projection_exprs.push(expr.clone());\n                let col_type = Self::infer_expr_type(expr, input_schema)?;\n                pre_projection_schema.push(ColumnInfo {\n                    name: proj_col_name.clone(),\n                    ty: col_type,\n                    database: None,\n                    table: None,\n                    table_alias: None,\n                });\n                // Replace complex expression with reference to projected column\n                modified_group_exprs.push(LogicalExpr::Column(Column {\n                    name: proj_col_name,\n                    table: None,\n                }));\n            }\n        }\n\n        // Check each aggregate expression\n        for agg_expr in aggr_exprs {\n            if let LogicalExpr::AggregateFunction {\n                fun,\n                args,\n                distinct,\n            } = agg_expr\n            {\n                let mut modified_args = Vec::new();\n                for arg in args {\n                    // Check if the argument is a simple column reference or a complex expression\n                    match arg {\n                        LogicalExpr::Column(_) => {\n                            // Simple column - just use it\n                            modified_args.push(arg.clone());\n                            // Make sure the column is in the pre-projection\n                            if !pre_projection_exprs.iter().any(|e| e == arg) {\n                                pre_projection_exprs.push(arg.clone());\n                                let col_type = Self::infer_expr_type(arg, input_schema)?;\n                                if let LogicalExpr::Column(col) = arg {\n                                    pre_projection_schema.push(ColumnInfo {\n                                        name: col.name.clone(),\n                                        ty: col_type,\n                                        database: None,\n                                        table: col.table.clone(),\n                                        table_alias: None,\n                                    });\n                                }\n                            }\n                        }\n                        _ => {\n                            // Complex expression - we need to project it first\n                            needs_pre_projection = true;\n                            let proj_col_name = format!(\"__agg_arg_proj_{projected_col_counter}\");\n                            projected_col_counter += 1;\n\n                            // Add the expression to the pre-projection\n                            pre_projection_exprs.push(arg.clone());\n                            let col_type = Self::infer_expr_type(arg, input_schema)?;\n                            pre_projection_schema.push(ColumnInfo {\n                                name: proj_col_name.clone(),\n                                ty: col_type,\n                                database: None,\n                                table: None,\n                                table_alias: None,\n                            });\n\n                            // In the aggregate, reference the projected column\n                            modified_args.push(LogicalExpr::Column(Column::new(proj_col_name)));\n                        }\n                    }\n                }\n\n                // Create the modified aggregate expression\n                modified_aggr_exprs.push(LogicalExpr::AggregateFunction {\n                    fun: fun.clone(),\n                    args: modified_args,\n                    distinct: *distinct,\n                });\n            } else {\n                modified_aggr_exprs.push(agg_expr.clone());\n            }\n        }\n\n        Ok((\n            needs_pre_projection,\n            pre_projection_exprs,\n            pre_projection_schema,\n            modified_aggr_exprs,\n            modified_group_exprs,\n        ))\n    }\n\n    // Build aggregate with GROUP BY\n    fn build_aggregate(\n        &mut self,\n        input: LogicalPlan,\n        group_by: &ast::GroupBy,\n        columns: &[ast::ResultColumn],\n    ) -> Result<LogicalPlan> {\n        let input_schema = input.schema();\n\n        // Build grouping expressions\n        let mut group_exprs = Vec::new();\n        for expr in &group_by.exprs {\n            group_exprs.push(self.build_expr(expr, input_schema)?);\n        }\n\n        // Use the unified aggregate builder\n        self.build_aggregate_internal(input, group_exprs, columns)\n    }\n\n    // Build aggregate without GROUP BY\n    fn build_aggregate_no_group(\n        &mut self,\n        input: LogicalPlan,\n        columns: &[ast::ResultColumn],\n    ) -> Result<LogicalPlan> {\n        // Use the unified aggregate builder with empty group expressions\n        self.build_aggregate_internal(input, vec![], columns)\n    }\n\n    // Unified internal aggregate builder that handles both GROUP BY and non-GROUP BY cases\n    fn build_aggregate_internal(\n        &mut self,\n        input: LogicalPlan,\n        group_exprs: Vec<LogicalExpr>,\n        columns: &[ast::ResultColumn],\n    ) -> Result<LogicalPlan> {\n        let input_schema = input.schema();\n        let has_group_by = !group_exprs.is_empty();\n\n        // First pass: build a map of aliases to expressions from the SELECT list\n        // and a vector of SELECT expressions for positional references\n        // This allows GROUP BY to reference SELECT aliases (e.g., GROUP BY year)\n        // or positions (e.g., GROUP BY 1)\n        let mut alias_to_expr = HashMap::default();\n        let mut select_exprs = Vec::new();\n        for col in columns {\n            if let ast::ResultColumn::Expr(expr, alias) = col {\n                let logical_expr = self.build_expr(expr, input_schema)?;\n                select_exprs.push(logical_expr.clone());\n\n                if let Some(alias) = alias {\n                    let alias_name = match alias {\n                        ast::As::As(name) | ast::As::Elided(name) => Self::name_to_string(name),\n                    };\n                    alias_to_expr.insert(alias_name, logical_expr);\n                }\n            }\n        }\n\n        // Resolve GROUP BY expressions: replace column references that match SELECT aliases\n        // or integer literals that represent positions\n        let group_exprs = group_exprs\n            .into_iter()\n            .map(|expr| {\n                // Check for positional reference (integer literal)\n                if let LogicalExpr::Literal(crate::types::Value::Numeric(\n                    crate::Numeric::Integer(pos),\n                )) = &expr\n                {\n                    // SQLite uses 1-based indexing\n                    if *pos > 0 && (*pos as usize) <= select_exprs.len() {\n                        return select_exprs[(*pos as usize) - 1].clone();\n                    }\n                }\n\n                // Check for alias reference (unqualified column name)\n                if let LogicalExpr::Column(col) = &expr {\n                    if col.table.is_none() {\n                        // Unqualified column - check if it matches an alias\n                        if let Some(aliased_expr) = alias_to_expr.get(&col.name) {\n                            return aliased_expr.clone();\n                        }\n                    }\n                }\n                expr\n            })\n            .collect::<Vec<_>>();\n\n        // Build aggregate expressions and projection expressions\n        let mut aggr_exprs = Vec::new();\n        let mut projection_exprs = Vec::new();\n        let mut aggregate_schema_columns = Vec::new();\n\n        // First, add GROUP BY columns to the aggregate output schema\n        // These are always part of the aggregate operator's output\n        for group_expr in &group_exprs {\n            match group_expr {\n                LogicalExpr::Column(col) => {\n                    // For column references in GROUP BY, preserve the original column info\n                    if let Some((_, col_info)) =\n                        input_schema.find_column(&col.name, col.table.as_deref())\n                    {\n                        // Preserve the column with all its table information\n                        aggregate_schema_columns.push(col_info.clone());\n                    } else {\n                        // Fallback if column not found (shouldn't happen)\n                        let col_type = Self::infer_expr_type(group_expr, input_schema)?;\n                        aggregate_schema_columns.push(ColumnInfo {\n                            name: col.name.clone(),\n                            ty: col_type,\n                            database: None,\n                            table: col.table.clone(),\n                            table_alias: None,\n                        });\n                    }\n                }\n                _ => {\n                    // For complex GROUP BY expressions, generate a name\n                    let col_name = format!(\"__group_{}\", aggregate_schema_columns.len());\n                    let col_type = Self::infer_expr_type(group_expr, input_schema)?;\n                    aggregate_schema_columns.push(ColumnInfo {\n                        name: col_name,\n                        ty: col_type,\n                        database: None,\n                        table: None,\n                        table_alias: None,\n                    });\n                }\n            }\n        }\n\n        // Track aggregates we've already seen to avoid duplicates\n        let mut aggregate_map: HashMap<String, String> = HashMap::default();\n\n        for col in columns {\n            match col {\n                ast::ResultColumn::Expr(expr, alias) => {\n                    let logical_expr = self.build_expr(expr, input_schema)?;\n\n                    // Determine the column name for this expression\n                    let col_name = match alias {\n                        Some(as_alias) => match as_alias {\n                            ast::As::As(name) | ast::As::Elided(name) => Self::name_to_string(name),\n                        },\n                        None => Self::expr_to_column_name(expr),\n                    };\n\n                    // Check if the TOP-LEVEL expression is an aggregate\n                    // We only care about immediate aggregates, not nested ones\n                    if Self::is_aggregate_expr(&logical_expr) {\n                        // Pure aggregate function - check if we've seen it before\n                        let agg_key = format!(\"{logical_expr:?}\");\n\n                        let agg_col_name = if let Some(existing_name) = aggregate_map.get(&agg_key)\n                        {\n                            // Reuse existing aggregate\n                            existing_name.clone()\n                        } else {\n                            // New aggregate - add it\n                            let col_type = Self::infer_expr_type(&logical_expr, input_schema)?;\n                            aggregate_schema_columns.push(ColumnInfo {\n                                name: col_name.clone(),\n                                ty: col_type,\n                                database: None,\n                                table: None,\n                                table_alias: None,\n                            });\n                            aggr_exprs.push(logical_expr);\n                            aggregate_map.insert(agg_key, col_name.clone());\n                            col_name.clone()\n                        };\n\n                        // In the projection, reference this aggregate by name\n                        projection_exprs.push(LogicalExpr::Column(Column {\n                            name: agg_col_name,\n                            table: None,\n                        }));\n                    } else if Self::contains_aggregate(&logical_expr) {\n                        // This is an expression that contains an aggregate somewhere\n                        // (e.g., sum(a + 2) * 2)\n                        // We need to extract aggregates and replace them with column references\n                        let (processed_expr, extracted_aggs) =\n                            Self::extract_and_replace_aggregates_with_dedup(\n                                logical_expr,\n                                &mut aggregate_map,\n                            )?;\n\n                        // Add only new aggregates\n                        for (agg_expr, agg_name) in extracted_aggs {\n                            let agg_type = Self::infer_expr_type(&agg_expr, input_schema)?;\n                            aggregate_schema_columns.push(ColumnInfo {\n                                name: agg_name,\n                                ty: agg_type,\n                                database: None,\n                                table: None,\n                                table_alias: None,\n                            });\n                            aggr_exprs.push(agg_expr);\n                        }\n\n                        // Add the processed expression (with column refs) to projection\n                        projection_exprs.push(processed_expr);\n                    } else {\n                        // Non-aggregate expression - validation depends on GROUP BY presence\n                        if has_group_by {\n                            // With GROUP BY: only allow constants and grouped columns\n                            // TODO: SQLite actually allows any column here and returns the value from\n                            // the first row encountered in each group. We should support this in the\n                            // future for full SQLite compatibility, but for now we're stricter to\n                            // simplify the DBSP compilation.\n                            if !Self::is_constant_expr(&logical_expr)\n                                && !Self::is_valid_in_group_by(&logical_expr, &group_exprs)\n                            {\n                                return Err(LimboError::ParseError(format!(\n                                    \"Column '{col_name}' must appear in the GROUP BY clause or be used in an aggregate function\"\n                                )));\n                            }\n\n                            // If this expression matches a GROUP BY expression, replace it with a reference\n                            // to the corresponding column in the aggregate output\n                            let logical_expr_stripped = strip_alias(&logical_expr);\n                            if let Some(group_idx) = group_exprs\n                                .iter()\n                                .position(|g| logical_expr_stripped == strip_alias(g))\n                            {\n                                // Reference the GROUP BY column in the aggregate output by its name\n                                let group_col_name = &aggregate_schema_columns[group_idx].name;\n                                projection_exprs.push(LogicalExpr::Column(Column {\n                                    name: group_col_name.clone(),\n                                    table: None,\n                                }));\n                            } else {\n                                projection_exprs.push(logical_expr);\n                            }\n                        } else {\n                            // Without GROUP BY: only allow constant expressions\n                            // TODO: SQLite allows any column here and returns a value from an\n                            // arbitrary row. We should support this for full compatibility,\n                            // but for now we're stricter to simplify DBSP compilation.\n                            if !Self::is_constant_expr(&logical_expr) {\n                                return Err(LimboError::ParseError(format!(\n                                    \"Column '{col_name}' must be used in an aggregate function when using aggregates without GROUP BY\"\n                                )));\n                            }\n                            projection_exprs.push(logical_expr);\n                        }\n                    }\n                }\n                _ => {\n                    let error_msg = if has_group_by {\n                        \"* not supported with GROUP BY\".to_string()\n                    } else {\n                        \"* not supported with aggregate functions\".to_string()\n                    };\n                    return Err(LimboError::ParseError(error_msg));\n                }\n            }\n        }\n\n        // Check if any aggregate functions have complex expressions as arguments\n        // or if GROUP BY has complex expressions\n        // If so, we need to insert a projection before the aggregate\n        let (\n            needs_pre_projection,\n            pre_projection_exprs,\n            pre_projection_schema,\n            modified_aggr_exprs,\n            modified_group_exprs,\n        ) = Self::preprocess_aggregate_expressions(&aggr_exprs, &group_exprs, input_schema)?;\n\n        // Build the final schema for the projection\n        let mut projection_schema_columns = Vec::new();\n        for (i, expr) in projection_exprs.iter().enumerate() {\n            let col_name = if i < columns.len() {\n                match &columns[i] {\n                    ast::ResultColumn::Expr(e, alias) => match alias {\n                        Some(as_alias) => match as_alias {\n                            ast::As::As(name) | ast::As::Elided(name) => Self::name_to_string(name),\n                        },\n                        None => Self::expr_to_column_name(e),\n                    },\n                    _ => format!(\"col_{i}\"),\n                }\n            } else {\n                format!(\"col_{i}\")\n            };\n\n            // For type inference, we need the aggregate schema for column references\n            let aggregate_schema = LogicalSchema::new(aggregate_schema_columns.clone());\n            let col_type = Self::infer_expr_type(expr, &Arc::new(aggregate_schema))?;\n            projection_schema_columns.push(ColumnInfo {\n                name: col_name,\n                ty: col_type,\n                database: None,\n                table: None,\n                table_alias: None,\n            });\n        }\n\n        // Create the input plan (with pre-projection if needed)\n        let aggregate_input = if needs_pre_projection {\n            Arc::new(LogicalPlan::Projection(Projection {\n                input: Arc::new(input),\n                exprs: pre_projection_exprs,\n                schema: Arc::new(LogicalSchema::new(pre_projection_schema)),\n            }))\n        } else {\n            Arc::new(input)\n        };\n\n        // Use modified aggregate and group expressions if we inserted a pre-projection\n        let final_aggr_exprs = if needs_pre_projection {\n            modified_aggr_exprs\n        } else {\n            aggr_exprs\n        };\n        let final_group_exprs = if needs_pre_projection {\n            modified_group_exprs\n        } else {\n            group_exprs\n        };\n\n        // Check if we need the outer projection\n        // We need a projection if:\n        // 1. We have expressions that compute new values (e.g., SUM(x) * 2)\n        // 2. We're selecting a different set of columns than GROUP BY + aggregates\n        // 3. We're reordering columns from their natural aggregate output order\n        let needs_outer_projection = {\n            // Check for complex expressions\n            let has_complex_exprs = projection_exprs\n                .iter()\n                .any(|expr| !matches!(expr, LogicalExpr::Column(_)));\n\n            if has_complex_exprs {\n                true\n            } else {\n                // Check if we're selecting exactly what aggregate outputs in the same order\n                // The aggregate outputs: all GROUP BY columns, then all aggregate expressions\n                // The projection might select a subset or reorder these\n\n                if projection_exprs.len() != aggregate_schema_columns.len() {\n                    // Different number of columns\n                    true\n                } else {\n                    // Check if columns match in order and name\n                    !projection_exprs.iter().zip(&aggregate_schema_columns).all(\n                        |(expr, agg_col)| {\n                            if let LogicalExpr::Column(col) = expr {\n                                col.name == agg_col.name\n                            } else {\n                                false\n                            }\n                        },\n                    )\n                }\n            }\n        };\n\n        // Create the aggregate node with its natural schema\n        let aggregate_plan = LogicalPlan::Aggregate(Aggregate {\n            input: aggregate_input,\n            group_expr: final_group_exprs,\n            aggr_expr: final_aggr_exprs,\n            schema: Arc::new(LogicalSchema::new(aggregate_schema_columns)),\n        });\n\n        if needs_outer_projection {\n            Ok(LogicalPlan::Projection(Projection {\n                input: Arc::new(aggregate_plan),\n                exprs: projection_exprs,\n                schema: Arc::new(LogicalSchema::new(projection_schema_columns)),\n            }))\n        } else {\n            // No projection needed - aggregate output matches what we want\n            Ok(aggregate_plan)\n        }\n    }\n\n    /// Build VALUES clause\n    #[allow(clippy::vec_box)]\n    fn build_values(&mut self, values: &[Vec<Box<ast::Expr>>]) -> Result<LogicalPlan> {\n        if values.is_empty() {\n            return Err(LimboError::ParseError(\"Empty VALUES clause\".to_string()));\n        }\n\n        let mut rows = Vec::new();\n        let first_row_len = values[0].len();\n\n        // Infer schema from first row\n        let mut schema_columns = Vec::new();\n        for (i, _) in values[0].iter().enumerate() {\n            schema_columns.push(ColumnInfo {\n                name: format!(\"column{}\", i + 1),\n                ty: Type::Text,\n                database: None,\n                table: None,\n                table_alias: None,\n            });\n        }\n\n        for row in values {\n            if row.len() != first_row_len {\n                return Err(LimboError::ParseError(\n                    \"All rows in VALUES must have the same number of columns\".to_string(),\n                ));\n            }\n\n            let mut logical_row = Vec::new();\n            for expr in row {\n                // VALUES doesn't have input schema\n                let empty_schema = Arc::new(LogicalSchema::empty());\n                logical_row.push(self.build_expr(expr, &empty_schema)?);\n            }\n            rows.push(logical_row);\n        }\n\n        Ok(LogicalPlan::Values(Values {\n            rows,\n            schema: Arc::new(LogicalSchema::new(schema_columns)),\n        }))\n    }\n\n    // Build SORT\n    fn build_sort(\n        &mut self,\n        input: LogicalPlan,\n        exprs: &[ast::SortedColumn],\n    ) -> Result<LogicalPlan> {\n        let input_schema = input.schema();\n        let mut sort_exprs = Vec::new();\n\n        for sorted_col in exprs {\n            let expr = self.build_expr(&sorted_col.expr, input_schema)?;\n            sort_exprs.push(SortExpr {\n                expr,\n                asc: sorted_col.order != Some(ast::SortOrder::Desc),\n                nulls_first: sorted_col.nulls == Some(ast::NullsOrder::First),\n            });\n        }\n\n        Ok(LogicalPlan::Sort(Sort {\n            input: Arc::new(input),\n            exprs: sort_exprs,\n        }))\n    }\n\n    // Build LIMIT\n    fn build_limit(input: LogicalPlan, limit: &ast::Limit) -> Result<LogicalPlan> {\n        let fetch = match limit.expr.as_ref() {\n            ast::Expr::Literal(ast::Literal::Numeric(s)) => s.parse::<usize>().ok(),\n            _ => {\n                return Err(LimboError::ParseError(\n                    \"LIMIT must be a literal integer\".to_string(),\n                ));\n            }\n        };\n\n        let skip = if let Some(offset) = &limit.offset {\n            match offset.as_ref() {\n                ast::Expr::Literal(ast::Literal::Numeric(s)) => s.parse::<usize>().ok(),\n                _ => {\n                    return Err(LimboError::ParseError(\n                        \"OFFSET must be a literal integer\".to_string(),\n                    ));\n                }\n            }\n        } else {\n            None\n        };\n\n        Ok(LogicalPlan::Limit(Limit {\n            input: Arc::new(input),\n            skip,\n            fetch,\n        }))\n    }\n\n    // Build compound operator (UNION, INTERSECT, EXCEPT)\n    fn build_compound(\n        left: LogicalPlan,\n        right: LogicalPlan,\n        op: &ast::CompoundOperator,\n    ) -> Result<LogicalPlan> {\n        // Check schema compatibility\n        if left.schema().column_count() != right.schema().column_count() {\n            return Err(LimboError::ParseError(\n                \"UNION/INTERSECT/EXCEPT requires same number of columns\".to_string(),\n            ));\n        }\n\n        let all = matches!(op, ast::CompoundOperator::UnionAll);\n\n        match op {\n            ast::CompoundOperator::Union | ast::CompoundOperator::UnionAll => {\n                let schema = left.schema().clone();\n                Ok(LogicalPlan::Union(Union {\n                    inputs: vec![Arc::new(left), Arc::new(right)],\n                    all,\n                    schema,\n                }))\n            }\n            _ => Err(LimboError::ParseError(\n                \"INTERSECT and EXCEPT not yet supported in logical plans\".to_string(),\n            )),\n        }\n    }\n\n    // Build expression from AST\n    fn build_expr(&mut self, expr: &ast::Expr, _schema: &SchemaRef) -> Result<LogicalExpr> {\n        match expr {\n            ast::Expr::Id(name) => Ok(LogicalExpr::Column(Column::new(Self::name_to_string(name)))),\n\n            ast::Expr::DoublyQualified(db, table, col) => {\n                Ok(LogicalExpr::Column(Column::with_table(\n                    Self::name_to_string(col),\n                    format!(\n                        \"{}.{}\",\n                        Self::name_to_string(db),\n                        Self::name_to_string(table)\n                    ),\n                )))\n            }\n\n            ast::Expr::Qualified(table, col) => Ok(LogicalExpr::Column(Column::with_table(\n                Self::name_to_string(col),\n                Self::name_to_string(table),\n            ))),\n\n            ast::Expr::Literal(lit) => Ok(LogicalExpr::Literal(Self::build_literal(lit)?)),\n\n            ast::Expr::Binary(lhs, op, rhs) => {\n                // Special case: IS NULL and IS NOT NULL\n                if matches!(op, ast::Operator::Is | ast::Operator::IsNot) {\n                    if let ast::Expr::Literal(ast::Literal::Null) = rhs.as_ref() {\n                        let expr = Box::new(self.build_expr(lhs, _schema)?);\n                        return Ok(LogicalExpr::IsNull {\n                            expr,\n                            negated: matches!(op, ast::Operator::IsNot),\n                        });\n                    }\n                }\n\n                let left = Box::new(self.build_expr(lhs, _schema)?);\n                let right = Box::new(self.build_expr(rhs, _schema)?);\n                Ok(LogicalExpr::BinaryExpr {\n                    left,\n                    op: *op,\n                    right,\n                })\n            }\n\n            ast::Expr::Unary(op, expr) => {\n                let inner = Box::new(self.build_expr(expr, _schema)?);\n                Ok(LogicalExpr::UnaryExpr {\n                    op: *op,\n                    expr: inner,\n                })\n            }\n\n            ast::Expr::FunctionCall {\n                name,\n                distinctness,\n                args,\n                filter_over,\n                ..\n            } => {\n                // Check for window functions (OVER clause)\n                if filter_over.over_clause.is_some() {\n                    return Err(LimboError::ParseError(\n                        \"Unsupported expression type: window functions are not yet supported\"\n                            .to_string(),\n                    ));\n                }\n\n                let func_name = Self::name_to_string(name);\n                let arg_count = args.len();\n                // Check if it's an aggregate function (considering argument count for min/max)\n                if let Some(agg_fun) = Self::parse_aggregate_function(&func_name, arg_count) {\n                    let distinct = distinctness.is_some();\n                    let arg_exprs = args\n                        .iter()\n                        .map(|e| self.build_expr(e, _schema))\n                        .collect::<Result<Vec<_>>>()?;\n                    Ok(LogicalExpr::AggregateFunction {\n                        fun: agg_fun,\n                        args: arg_exprs,\n                        distinct,\n                    })\n                } else {\n                    // Regular scalar function\n                    let arg_exprs = args\n                        .iter()\n                        .map(|e| self.build_expr(e, _schema))\n                        .collect::<Result<Vec<_>>>()?;\n                    Ok(LogicalExpr::ScalarFunction {\n                        fun: func_name,\n                        args: arg_exprs,\n                    })\n                }\n            }\n\n            ast::Expr::FunctionCallStar { name, .. } => {\n                // Handle COUNT(*) and similar\n                let func_name = Self::name_to_string(name);\n                // FunctionCallStar always has 0 args (it's the * form)\n                if let Some(agg_fun) = Self::parse_aggregate_function(&func_name, 0) {\n                    Ok(LogicalExpr::AggregateFunction {\n                        fun: agg_fun,\n                        args: vec![],\n                        distinct: false,\n                    })\n                } else if let Ok(func) = crate::function::Func::resolve_function(&func_name, 0) {\n                    // Check if this function supports star expansion (e.g., json_object, jsonb_object)\n                    if func.needs_star_expansion() {\n                        // Expand * to all columns as alternating key-value pairs\n                        let mut args = Vec::new();\n                        for col in &_schema.columns {\n                            // Add column name as string literal\n                            args.push(LogicalExpr::Literal(crate::types::Value::Text(\n                                col.name.clone().into(),\n                            )));\n                            // Add column reference\n                            args.push(LogicalExpr::Column(Column::new(col.name.clone())));\n                        }\n                        Ok(LogicalExpr::ScalarFunction {\n                            fun: func_name,\n                            args,\n                        })\n                    } else {\n                        Err(LimboError::ParseError(format!(\n                            \"Function {func_name}(*) is not supported\"\n                        )))\n                    }\n                } else {\n                    Err(LimboError::ParseError(format!(\n                        \"Function {func_name}(*) is not supported\"\n                    )))\n                }\n            }\n\n            ast::Expr::Case {\n                base,\n                when_then_pairs,\n                else_expr,\n            } => {\n                let case_expr = if let Some(e) = base {\n                    Some(Box::new(self.build_expr(e, _schema)?))\n                } else {\n                    None\n                };\n\n                let when_then_exprs = when_then_pairs\n                    .iter()\n                    .map(|(when, then)| {\n                        Ok((\n                            self.build_expr(when, _schema)?,\n                            self.build_expr(then, _schema)?,\n                        ))\n                    })\n                    .collect::<Result<Vec<_>>>()?;\n\n                let else_result = if let Some(e) = else_expr {\n                    Some(Box::new(self.build_expr(e, _schema)?))\n                } else {\n                    None\n                };\n\n                Ok(LogicalExpr::Case {\n                    expr: case_expr,\n                    when_then: when_then_exprs,\n                    else_expr: else_result,\n                })\n            }\n\n            ast::Expr::InList { lhs, not, rhs } => {\n                let expr = Box::new(self.build_expr(lhs, _schema)?);\n                let list = rhs\n                    .iter()\n                    .map(|e| self.build_expr(e, _schema))\n                    .collect::<Result<Vec<_>>>()?;\n                Ok(LogicalExpr::InList {\n                    expr,\n                    list,\n                    negated: *not,\n                })\n            }\n\n            ast::Expr::InSelect { lhs, not, rhs } => {\n                let expr = Box::new(self.build_expr(lhs, _schema)?);\n                let subquery = Arc::new(self.build_select(rhs)?);\n                Ok(LogicalExpr::InSubquery {\n                    expr,\n                    subquery,\n                    negated: *not,\n                })\n            }\n\n            ast::Expr::Exists(select) => {\n                let subquery = Arc::new(self.build_select(select)?);\n                Ok(LogicalExpr::Exists {\n                    subquery,\n                    negated: false,\n                })\n            }\n\n            ast::Expr::Subquery(select) => {\n                let subquery = Arc::new(self.build_select(select)?);\n                Ok(LogicalExpr::ScalarSubquery(subquery))\n            }\n\n            ast::Expr::IsNull(lhs) => {\n                let expr = Box::new(self.build_expr(lhs, _schema)?);\n                Ok(LogicalExpr::IsNull {\n                    expr,\n                    negated: false,\n                })\n            }\n\n            ast::Expr::NotNull(lhs) => {\n                let expr = Box::new(self.build_expr(lhs, _schema)?);\n                Ok(LogicalExpr::IsNull {\n                    expr,\n                    negated: true,\n                })\n            }\n\n            ast::Expr::Between {\n                lhs,\n                not,\n                start,\n                end,\n            } => {\n                let expr = Box::new(self.build_expr(lhs, _schema)?);\n                let low = Box::new(self.build_expr(start, _schema)?);\n                let high = Box::new(self.build_expr(end, _schema)?);\n                Ok(LogicalExpr::Between {\n                    expr,\n                    low,\n                    high,\n                    negated: *not,\n                })\n            }\n\n            ast::Expr::Like {\n                lhs,\n                not,\n                op: _,\n                rhs,\n                escape,\n            } => {\n                let expr = Box::new(self.build_expr(lhs, _schema)?);\n                let pattern = Box::new(self.build_expr(rhs, _schema)?);\n                let escape_char = escape.as_ref().and_then(|e| {\n                    if let ast::Expr::Literal(ast::Literal::String(s)) = e.as_ref() {\n                        s.chars().next()\n                    } else {\n                        None\n                    }\n                });\n                Ok(LogicalExpr::Like {\n                    expr,\n                    pattern,\n                    escape: escape_char,\n                    negated: *not,\n                })\n            }\n\n            ast::Expr::Parenthesized(exprs) => {\n                // the assumption is that there is at least one parenthesis here.\n                // If this is not true, then I don't understand this code and can't be trusted.\n                turso_assert_ne!(exprs.len(), 0);\n                // Multiple expressions in parentheses is unusual but handle it\n                // by building the first one (SQLite behavior)\n                self.build_expr(&exprs[0], _schema)\n            }\n\n            ast::Expr::Cast { expr, type_name } => {\n                let inner = self.build_expr(expr, _schema)?;\n                Ok(LogicalExpr::Cast {\n                    expr: Box::new(inner),\n                    type_name: type_name.clone(),\n                })\n            }\n\n            _ => Err(LimboError::ParseError(format!(\n                \"Unsupported expression type in logical plan: {expr:?}\"\n            ))),\n        }\n    }\n\n    /// Build literal value\n    fn build_literal(lit: &ast::Literal) -> Result<Value> {\n        match lit {\n            ast::Literal::Null => Ok(Value::Null),\n            ast::Literal::True => Ok(Value::from_i64(1)),\n            ast::Literal::False => Ok(Value::from_i64(0)),\n            ast::Literal::Keyword(k) => {\n                let k_bytes = k.as_bytes();\n                match_ignore_ascii_case!(match k_bytes {\n                    b\"true\" => Ok(Value::from_i64(1)),  // SQLite uses int for bool\n                    b\"false\" => Ok(Value::from_i64(0)), // SQLite uses int for bool\n                    _ => Ok(Value::Text(k.clone().into())),\n                })\n            }\n            ast::Literal::Numeric(s) => {\n                if let Ok(i) = s.parse::<i64>() {\n                    Ok(Value::from_i64(i))\n                } else if let Ok(f) = s.parse::<f64>() {\n                    Ok(Value::from_f64(f))\n                } else {\n                    Ok(Value::Text(s.clone().into()))\n                }\n            }\n            ast::Literal::String(s) => {\n                // Strip surrounding quotes from the SQL literal\n                // The parser includes quotes in the string value\n                let unquoted = if s.starts_with('\\'') && s.ends_with('\\'') && s.len() > 1 {\n                    &s[1..s.len() - 1]\n                } else {\n                    s.as_str()\n                };\n                Ok(Value::Text(unquoted.to_string().into()))\n            }\n            ast::Literal::Blob(b) => Ok(Value::Blob(b.clone().into())),\n            ast::Literal::CurrentDate\n            | ast::Literal::CurrentTime\n            | ast::Literal::CurrentTimestamp => Err(LimboError::ParseError(\n                \"Temporal literals not yet supported\".to_string(),\n            )),\n        }\n    }\n\n    /// Parse aggregate function name (considering argument count for min/max)\n    fn parse_aggregate_function(name: &str, arg_count: usize) -> Option<AggregateFunction> {\n        let name_bytes = name.as_bytes();\n        match_ignore_ascii_case!(match name_bytes {\n            b\"COUNT\" => Some(AggFunc::Count),\n            b\"SUM\" => Some(AggFunc::Sum),\n            b\"AVG\" => Some(AggFunc::Avg),\n            // MIN and MAX are only aggregates with 1 argument\n            // With 2+ arguments, they're scalar functions\n            b\"MIN\" if arg_count == 1 => Some(AggFunc::Min),\n            b\"MAX\" if arg_count == 1 => Some(AggFunc::Max),\n            b\"GROUP_CONCAT\" => Some(AggFunc::GroupConcat),\n            b\"STRING_AGG\" => Some(AggFunc::StringAgg),\n            b\"TOTAL\" => Some(AggFunc::Total),\n            b\"ARRAY_AGG\" => Some(AggFunc::ArrayAgg),\n            _ => None,\n        })\n    }\n\n    // Check if expression contains aggregates\n    fn has_aggregates(columns: &[ast::ResultColumn]) -> bool {\n        for col in columns {\n            if let ast::ResultColumn::Expr(expr, _) = col {\n                if Self::expr_has_aggregate(expr) {\n                    return true;\n                }\n            }\n        }\n        false\n    }\n\n    // Check if AST expression contains aggregates\n    fn expr_has_aggregate(expr: &ast::Expr) -> bool {\n        match expr {\n            ast::Expr::FunctionCall { name, args, .. } => {\n                // Check if the function itself is an aggregate (considering arg count for min/max)\n                let arg_count = args.len();\n                if Self::parse_aggregate_function(&Self::name_to_string(name), arg_count).is_some()\n                {\n                    return true;\n                }\n                // Also check if any arguments contain aggregates (for nested functions like HEX(SUM(...)))\n                args.iter().any(|arg| Self::expr_has_aggregate(arg))\n            }\n            ast::Expr::FunctionCallStar { name, .. } => {\n                // FunctionCallStar always has 0 args\n                Self::parse_aggregate_function(&Self::name_to_string(name), 0).is_some()\n            }\n            ast::Expr::Binary(lhs, _, rhs) => {\n                Self::expr_has_aggregate(lhs) || Self::expr_has_aggregate(rhs)\n            }\n            ast::Expr::Unary(_, e) => Self::expr_has_aggregate(e),\n            ast::Expr::Case {\n                when_then_pairs,\n                else_expr,\n                ..\n            } => {\n                when_then_pairs\n                    .iter()\n                    .any(|(w, t)| Self::expr_has_aggregate(w) || Self::expr_has_aggregate(t))\n                    || else_expr\n                        .as_ref()\n                        .is_some_and(|e| Self::expr_has_aggregate(e))\n            }\n            ast::Expr::Parenthesized(exprs) => {\n                // Check if any parenthesized expression contains an aggregate\n                exprs.iter().any(|e| Self::expr_has_aggregate(e))\n            }\n            _ => false,\n        }\n    }\n\n    // Check if logical expression is an aggregate\n    fn is_aggregate_expr(expr: &LogicalExpr) -> bool {\n        match expr {\n            LogicalExpr::AggregateFunction { .. } => true,\n            LogicalExpr::Alias { expr, .. } => Self::is_aggregate_expr(expr),\n            _ => false,\n        }\n    }\n\n    // Check if logical expression contains an aggregate anywhere\n    fn contains_aggregate(expr: &LogicalExpr) -> bool {\n        match expr {\n            LogicalExpr::AggregateFunction { .. } => true,\n            LogicalExpr::Alias { expr, .. } => Self::contains_aggregate(expr),\n            LogicalExpr::BinaryExpr { left, right, .. } => {\n                Self::contains_aggregate(left) || Self::contains_aggregate(right)\n            }\n            LogicalExpr::UnaryExpr { expr, .. } => Self::contains_aggregate(expr),\n            LogicalExpr::ScalarFunction { args, .. } => args.iter().any(Self::contains_aggregate),\n            LogicalExpr::Case {\n                when_then,\n                else_expr,\n                ..\n            } => {\n                when_then\n                    .iter()\n                    .any(|(w, t)| Self::contains_aggregate(w) || Self::contains_aggregate(t))\n                    || else_expr\n                        .as_ref()\n                        .is_some_and(|e| Self::contains_aggregate(e))\n            }\n            _ => false,\n        }\n    }\n\n    // Check if an expression is a constant (contains only literals)\n    fn is_constant_expr(expr: &LogicalExpr) -> bool {\n        match expr {\n            LogicalExpr::Literal(_) => true,\n            LogicalExpr::BinaryExpr { left, right, .. } => {\n                Self::is_constant_expr(left) && Self::is_constant_expr(right)\n            }\n            LogicalExpr::UnaryExpr { expr, .. } => Self::is_constant_expr(expr),\n            LogicalExpr::ScalarFunction { args, .. } => args.iter().all(Self::is_constant_expr),\n            LogicalExpr::Alias { expr, .. } => Self::is_constant_expr(expr),\n            _ => false,\n        }\n    }\n\n    // Check if an expression is valid in GROUP BY context\n    // An expression is valid if it's:\n    // 1. A constant literal\n    // 2. An aggregate function\n    // 3. A grouping column (or expression involving only grouping columns)\n    fn is_valid_in_group_by(expr: &LogicalExpr, group_exprs: &[LogicalExpr]) -> bool {\n        // First check if the entire expression appears in GROUP BY\n        // Strip aliases before comparing since SELECT might have aliases but GROUP BY might not\n        let expr_stripped = strip_alias(expr);\n        if group_exprs.iter().any(|g| expr_stripped == strip_alias(g)) {\n            return true;\n        }\n\n        // If not, check recursively based on expression type\n        match expr {\n            LogicalExpr::Literal(_) => true, // Constants are always valid\n            LogicalExpr::AggregateFunction { .. } => true, // Aggregates are valid\n            LogicalExpr::Column(col) => {\n                // Check if this column is in the GROUP BY\n                group_exprs.iter().any(|g| match g {\n                    LogicalExpr::Column(gcol) => gcol.name == col.name,\n                    _ => false,\n                })\n            }\n            LogicalExpr::BinaryExpr { left, right, .. } => {\n                // Both sides must be valid\n                Self::is_valid_in_group_by(left, group_exprs)\n                    && Self::is_valid_in_group_by(right, group_exprs)\n            }\n            LogicalExpr::UnaryExpr { expr, .. } => Self::is_valid_in_group_by(expr, group_exprs),\n            LogicalExpr::ScalarFunction { args, .. } => {\n                // All arguments must be valid\n                args.iter()\n                    .all(|arg| Self::is_valid_in_group_by(arg, group_exprs))\n            }\n            LogicalExpr::Alias { expr, .. } => Self::is_valid_in_group_by(expr, group_exprs),\n            _ => false, // Other expressions are not valid\n        }\n    }\n\n    // Extract aggregates from an expression and replace them with column references, with deduplication\n    // Returns the modified expression and a list of NEW (aggregate_expr, column_name) pairs\n    fn extract_and_replace_aggregates_with_dedup(\n        expr: LogicalExpr,\n        aggregate_map: &mut HashMap<String, String>,\n    ) -> Result<(LogicalExpr, Vec<(LogicalExpr, String)>)> {\n        let mut new_aggregates = Vec::new();\n        let mut counter = aggregate_map.len();\n        let new_expr = Self::replace_aggregates_with_columns_dedup(\n            expr,\n            &mut new_aggregates,\n            aggregate_map,\n            &mut counter,\n        )?;\n        Ok((new_expr, new_aggregates))\n    }\n\n    // Recursively replace aggregate functions with column references, with deduplication\n    fn replace_aggregates_with_columns_dedup(\n        expr: LogicalExpr,\n        new_aggregates: &mut Vec<(LogicalExpr, String)>,\n        aggregate_map: &mut HashMap<String, String>,\n        counter: &mut usize,\n    ) -> Result<LogicalExpr> {\n        match expr {\n            LogicalExpr::AggregateFunction { .. } => {\n                // Found an aggregate - check if we've seen it before\n                let agg_key = format!(\"{expr:?}\");\n\n                let col_name = if let Some(existing_name) = aggregate_map.get(&agg_key) {\n                    // Reuse existing aggregate\n                    existing_name.clone()\n                } else {\n                    // New aggregate\n                    let col_name = format!(\"__agg_{}\", *counter);\n                    *counter += 1;\n                    aggregate_map.insert(agg_key, col_name.clone());\n                    new_aggregates.push((expr, col_name.clone()));\n                    col_name\n                };\n\n                Ok(LogicalExpr::Column(Column {\n                    name: col_name,\n                    table: None,\n                }))\n            }\n            LogicalExpr::BinaryExpr { left, op, right } => {\n                let new_left = Self::replace_aggregates_with_columns_dedup(\n                    *left,\n                    new_aggregates,\n                    aggregate_map,\n                    counter,\n                )?;\n                let new_right = Self::replace_aggregates_with_columns_dedup(\n                    *right,\n                    new_aggregates,\n                    aggregate_map,\n                    counter,\n                )?;\n                Ok(LogicalExpr::BinaryExpr {\n                    left: Box::new(new_left),\n                    op,\n                    right: Box::new(new_right),\n                })\n            }\n            LogicalExpr::UnaryExpr { op, expr } => {\n                let new_expr = Self::replace_aggregates_with_columns_dedup(\n                    *expr,\n                    new_aggregates,\n                    aggregate_map,\n                    counter,\n                )?;\n                Ok(LogicalExpr::UnaryExpr {\n                    op,\n                    expr: Box::new(new_expr),\n                })\n            }\n            LogicalExpr::ScalarFunction { fun, args } => {\n                let mut new_args = Vec::new();\n                for arg in args {\n                    new_args.push(Self::replace_aggregates_with_columns_dedup(\n                        arg,\n                        new_aggregates,\n                        aggregate_map,\n                        counter,\n                    )?);\n                }\n                Ok(LogicalExpr::ScalarFunction {\n                    fun,\n                    args: new_args,\n                })\n            }\n            LogicalExpr::Case {\n                expr: case_expr,\n                when_then,\n                else_expr,\n            } => {\n                let new_case_expr = if let Some(e) = case_expr {\n                    Some(Box::new(Self::replace_aggregates_with_columns_dedup(\n                        *e,\n                        new_aggregates,\n                        aggregate_map,\n                        counter,\n                    )?))\n                } else {\n                    None\n                };\n\n                let mut new_when_then = Vec::new();\n                for (when, then) in when_then {\n                    let new_when = Self::replace_aggregates_with_columns_dedup(\n                        when,\n                        new_aggregates,\n                        aggregate_map,\n                        counter,\n                    )?;\n                    let new_then = Self::replace_aggregates_with_columns_dedup(\n                        then,\n                        new_aggregates,\n                        aggregate_map,\n                        counter,\n                    )?;\n                    new_when_then.push((new_when, new_then));\n                }\n\n                let new_else = if let Some(e) = else_expr {\n                    Some(Box::new(Self::replace_aggregates_with_columns_dedup(\n                        *e,\n                        new_aggregates,\n                        aggregate_map,\n                        counter,\n                    )?))\n                } else {\n                    None\n                };\n\n                Ok(LogicalExpr::Case {\n                    expr: new_case_expr,\n                    when_then: new_when_then,\n                    else_expr: new_else,\n                })\n            }\n            LogicalExpr::Alias { expr, alias } => {\n                let new_expr = Self::replace_aggregates_with_columns_dedup(\n                    *expr,\n                    new_aggregates,\n                    aggregate_map,\n                    counter,\n                )?;\n                Ok(LogicalExpr::Alias {\n                    expr: Box::new(new_expr),\n                    alias,\n                })\n            }\n            // Other expressions - keep as is\n            _ => Ok(expr),\n        }\n    }\n\n    // Get column name from expression\n    fn expr_to_column_name(expr: &ast::Expr) -> String {\n        match expr {\n            ast::Expr::Id(name) => Self::name_to_string(name),\n            ast::Expr::Qualified(_, col) => Self::name_to_string(col),\n            ast::Expr::FunctionCall { name, .. } => Self::name_to_string(name),\n            ast::Expr::FunctionCallStar { name, .. } => {\n                format!(\"{}(*)\", Self::name_to_string(name))\n            }\n            _ => \"expr\".to_string(),\n        }\n    }\n\n    // Get table schema\n    fn get_table_schema(&self, table_name: &str, alias: Option<&str>) -> Result<SchemaRef> {\n        // Look up table in schema\n        let table = self\n            .schema\n            .get_table(table_name)\n            .ok_or_else(|| LimboError::ParseError(format!(\"Table '{table_name}' not found\")))?;\n\n        // Parse table_name which might be \"db.table\" for attached databases\n        let (database, actual_table) = if table_name.contains('.') {\n            let parts: Vec<&str> = table_name.splitn(2, '.').collect();\n            (Some(parts[0].to_string()), parts[1].to_string())\n        } else {\n            (None, table_name.to_string())\n        };\n\n        let mut columns = Vec::new();\n        for col in table.columns() {\n            if let Some(ref name) = col.name {\n                columns.push(ColumnInfo {\n                    name: name.clone(),\n                    ty: col.ty(),\n                    database: database.clone(),\n                    table: Some(actual_table.clone()),\n                    table_alias: alias.map(|s| s.to_string()),\n                });\n            }\n        }\n\n        Ok(Arc::new(LogicalSchema::new(columns)))\n    }\n\n    // Infer expression type\n    fn infer_expr_type(expr: &LogicalExpr, schema: &SchemaRef) -> Result<Type> {\n        match expr {\n            LogicalExpr::Column(col) => {\n                if let Some((_, col_info)) = schema.find_column(&col.name, col.table.as_deref()) {\n                    Ok(col_info.ty)\n                } else {\n                    Ok(Type::Text)\n                }\n            }\n            LogicalExpr::Literal(Value::Numeric(Numeric::Integer(_))) => Ok(Type::Integer),\n            LogicalExpr::Literal(Value::Numeric(Numeric::Float(_))) => Ok(Type::Real),\n            LogicalExpr::Literal(Value::Text(_)) => Ok(Type::Text),\n            LogicalExpr::Literal(Value::Null) => Ok(Type::Null),\n            LogicalExpr::Literal(Value::Blob(_)) => Ok(Type::Blob),\n            LogicalExpr::BinaryExpr { op, left, right } => {\n                match op {\n                    ast::Operator::Add | ast::Operator::Subtract | ast::Operator::Multiply => {\n                        // Infer types of operands to match SQLite/Numeric behavior\n                        let left_type = Self::infer_expr_type(left, schema)?;\n                        let right_type = Self::infer_expr_type(right, schema)?;\n\n                        // Integer op Integer = Integer (matching core/numeric/mod.rs behavior)\n                        // Any operation with Real = Real\n                        match (left_type, right_type) {\n                            (Type::Integer, Type::Integer) => Ok(Type::Integer),\n                            (Type::Integer, Type::Real)\n                            | (Type::Real, Type::Integer)\n                            | (Type::Real, Type::Real) => Ok(Type::Real),\n                            (Type::Null, _) | (_, Type::Null) => Ok(Type::Null),\n                            // For Text/Blob, SQLite coerces to numeric, defaulting to Real\n                            _ => Ok(Type::Real),\n                        }\n                    }\n                    ast::Operator::Divide => {\n                        // Division always produces Real in SQLite\n                        Ok(Type::Real)\n                    }\n                    ast::Operator::Modulus => {\n                        // Modulus follows same rules as other arithmetic ops\n                        let left_type = Self::infer_expr_type(left, schema)?;\n                        let right_type = Self::infer_expr_type(right, schema)?;\n                        match (left_type, right_type) {\n                            (Type::Integer, Type::Integer) => Ok(Type::Integer),\n                            _ => Ok(Type::Real),\n                        }\n                    }\n                    ast::Operator::Equals\n                    | ast::Operator::NotEquals\n                    | ast::Operator::Less\n                    | ast::Operator::LessEquals\n                    | ast::Operator::Greater\n                    | ast::Operator::GreaterEquals\n                    | ast::Operator::And\n                    | ast::Operator::Or\n                    | ast::Operator::Is\n                    | ast::Operator::IsNot => Ok(Type::Integer),\n                    ast::Operator::Concat => Ok(Type::Text),\n                    _ => Ok(Type::Text), // Default for other operators\n                }\n            }\n            LogicalExpr::UnaryExpr { op, expr } => match op {\n                ast::UnaryOperator::Not => Ok(Type::Integer),\n                ast::UnaryOperator::Negative | ast::UnaryOperator::Positive => {\n                    Self::infer_expr_type(expr, schema)\n                }\n                ast::UnaryOperator::BitwiseNot => Ok(Type::Integer),\n            },\n            LogicalExpr::AggregateFunction { fun, .. } => match fun {\n                AggFunc::Count | AggFunc::Count0 => Ok(Type::Integer),\n                AggFunc::Sum | AggFunc::Avg | AggFunc::Total => Ok(Type::Real),\n                AggFunc::Min | AggFunc::Max => Ok(Type::Text),\n                AggFunc::GroupConcat | AggFunc::StringAgg => Ok(Type::Text),\n                AggFunc::ArrayAgg => Ok(Type::Blob),\n                #[cfg(feature = \"json\")]\n                AggFunc::JsonbGroupArray\n                | AggFunc::JsonGroupArray\n                | AggFunc::JsonbGroupObject\n                | AggFunc::JsonGroupObject => Ok(Type::Text),\n                AggFunc::External(_) => Ok(Type::Text), // Default for external\n            },\n            LogicalExpr::Alias { expr, .. } => Self::infer_expr_type(expr, schema),\n            LogicalExpr::IsNull { .. } => Ok(Type::Integer),\n            LogicalExpr::InList { .. } | LogicalExpr::InSubquery { .. } => Ok(Type::Integer),\n            LogicalExpr::Exists { .. } => Ok(Type::Integer),\n            LogicalExpr::Between { .. } => Ok(Type::Integer),\n            LogicalExpr::Like { .. } => Ok(Type::Integer),\n            _ => Ok(Type::Text),\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::schema::{BTreeTable, ColDef, Column as SchemaColumn, Schema, Type};\n    use turso_parser::parser::Parser;\n\n    fn create_test_schema() -> Schema {\n        let mut schema = Schema::new();\n\n        // Create users table\n        let users_table = BTreeTable {\n            name: \"users\".to_string(),\n            root_page: 2,\n            primary_key_columns: vec![(\"id\".to_string(), turso_parser::ast::SortOrder::Asc)],\n            foreign_keys: vec![],\n            check_constraints: vec![],\n            rowid_alias_conflict_clause: None,\n            columns: vec![\n                SchemaColumn::new(\n                    Some(\"id\".to_string()),\n                    \"INTEGER\".to_string(),\n                    None,\n                    None,\n                    Type::Integer,\n                    None,\n                    ColDef {\n                        primary_key: true,\n                        rowid_alias: true,\n                        notnull: true,\n                        ..Default::default()\n                    },\n                ),\n                SchemaColumn::new_default_text(Some(\"name\".to_string()), \"TEXT\".to_string(), None),\n                SchemaColumn::new_default_integer(\n                    Some(\"age\".to_string()),\n                    \"INTEGER\".to_string(),\n                    None,\n                ),\n                SchemaColumn::new_default_text(Some(\"email\".to_string()), \"TEXT\".to_string(), None),\n            ],\n            has_rowid: true,\n            is_strict: false,\n            has_autoincrement: false,\n            unique_sets: vec![],\n        };\n        schema\n            .add_btree_table(Arc::new(users_table))\n            .expect(\"Test setup: failed to add users table\");\n\n        // Create orders table\n        let orders_table = BTreeTable {\n            name: \"orders\".to_string(),\n            root_page: 3,\n            primary_key_columns: vec![(\"id\".to_string(), turso_parser::ast::SortOrder::Asc)],\n            columns: vec![\n                SchemaColumn::new(\n                    Some(\"id\".to_string()),\n                    \"INTEGER\".to_string(),\n                    None,\n                    None,\n                    Type::Integer,\n                    None,\n                    ColDef {\n                        primary_key: true,\n                        rowid_alias: true,\n                        notnull: true,\n                        ..Default::default()\n                    },\n                ),\n                SchemaColumn::new_default_integer(\n                    Some(\"user_id\".to_string()),\n                    \"INTEGER\".to_string(),\n                    None,\n                ),\n                SchemaColumn::new_default_text(\n                    Some(\"product\".to_string()),\n                    \"TEXT\".to_string(),\n                    None,\n                ),\n                SchemaColumn::new(\n                    Some(\"amount\".to_string()),\n                    \"REAL\".to_string(),\n                    None,\n                    None,\n                    Type::Real,\n                    None,\n                    ColDef::default(),\n                ),\n            ],\n            has_rowid: true,\n            is_strict: false,\n            has_autoincrement: false,\n            unique_sets: vec![],\n            foreign_keys: vec![],\n            check_constraints: vec![],\n            rowid_alias_conflict_clause: None,\n        };\n        schema\n            .add_btree_table(Arc::new(orders_table))\n            .expect(\"Test setup: failed to add orders table\");\n\n        // Create products table\n        let products_table = BTreeTable {\n            name: \"products\".to_string(),\n            root_page: 4,\n            primary_key_columns: vec![(\"id\".to_string(), turso_parser::ast::SortOrder::Asc)],\n            columns: vec![\n                SchemaColumn::new(\n                    Some(\"id\".to_string()),\n                    \"INTEGER\".to_string(),\n                    None,\n                    None,\n                    Type::Integer,\n                    None,\n                    ColDef {\n                        primary_key: true,\n                        rowid_alias: true,\n                        notnull: true,\n                        ..Default::default()\n                    },\n                ),\n                SchemaColumn::new_default_text(Some(\"name\".to_string()), \"TEXT\".to_string(), None),\n                SchemaColumn::new(\n                    Some(\"price\".to_string()),\n                    \"REAL\".to_string(),\n                    None,\n                    None,\n                    Type::Real,\n                    None,\n                    ColDef::default(),\n                ),\n                SchemaColumn::new_default_integer(\n                    Some(\"product_id\".to_string()),\n                    \"INTEGER\".to_string(),\n                    None,\n                ),\n            ],\n            has_rowid: true,\n            is_strict: false,\n            has_autoincrement: false,\n            unique_sets: vec![],\n            foreign_keys: vec![],\n            check_constraints: vec![],\n            rowid_alias_conflict_clause: None,\n        };\n        schema\n            .add_btree_table(Arc::new(products_table))\n            .expect(\"Test setup: failed to add products table\");\n\n        schema\n    }\n\n    fn parse_and_build(sql: &str, schema: &Schema) -> Result<LogicalPlan> {\n        let mut parser = Parser::new(sql.as_bytes());\n        let cmd = parser\n            .next()\n            .ok_or_else(|| LimboError::ParseError(\"Empty statement\".to_string()))?\n            .map_err(|e| LimboError::ParseError(e.to_string()))?;\n        match cmd {\n            ast::Cmd::Stmt(stmt) => {\n                let mut builder = LogicalPlanBuilder::new(schema);\n                builder.build_statement(&stmt)\n            }\n            _ => Err(LimboError::ParseError(\n                \"Only SQL statements are supported\".to_string(),\n            )),\n        }\n    }\n\n    #[test]\n    fn test_simple_select() {\n        let schema = create_test_schema();\n        let sql = \"SELECT id, name FROM users\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        match plan {\n            LogicalPlan::Projection(proj) => {\n                assert_eq!(proj.exprs.len(), 2);\n                assert!(matches!(proj.exprs[0], LogicalExpr::Column(_)));\n                assert!(matches!(proj.exprs[1], LogicalExpr::Column(_)));\n\n                match &*proj.input {\n                    LogicalPlan::TableScan(scan) => {\n                        assert_eq!(scan.table_name, \"users\");\n                    }\n                    _ => panic!(\"Expected TableScan\"),\n                }\n            }\n            _ => panic!(\"Expected Projection\"),\n        }\n    }\n\n    #[test]\n    fn test_select_with_filter() {\n        let schema = create_test_schema();\n        let sql = \"SELECT name FROM users WHERE age > 18\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        match plan {\n            LogicalPlan::Projection(proj) => {\n                assert_eq!(proj.exprs.len(), 1);\n\n                match &*proj.input {\n                    LogicalPlan::Filter(filter) => {\n                        assert!(matches!(\n                            filter.predicate,\n                            LogicalExpr::BinaryExpr {\n                                op: ast::Operator::Greater,\n                                ..\n                            }\n                        ));\n\n                        match &*filter.input {\n                            LogicalPlan::TableScan(scan) => {\n                                assert_eq!(scan.table_name, \"users\");\n                            }\n                            _ => panic!(\"Expected TableScan\"),\n                        }\n                    }\n                    _ => panic!(\"Expected Filter\"),\n                }\n            }\n            _ => panic!(\"Expected Projection\"),\n        }\n    }\n\n    #[test]\n    fn test_aggregate_with_group_by() {\n        let schema = create_test_schema();\n        let sql = \"SELECT user_id, SUM(amount) FROM orders GROUP BY user_id\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        match plan {\n            LogicalPlan::Aggregate(agg) => {\n                assert_eq!(agg.group_expr.len(), 1);\n                assert_eq!(agg.aggr_expr.len(), 1);\n                assert_eq!(agg.schema.column_count(), 2);\n\n                assert!(matches!(\n                    agg.aggr_expr[0],\n                    LogicalExpr::AggregateFunction {\n                        fun: AggFunc::Sum,\n                        ..\n                    }\n                ));\n\n                match &*agg.input {\n                    LogicalPlan::TableScan(scan) => {\n                        assert_eq!(scan.table_name, \"orders\");\n                    }\n                    _ => panic!(\"Expected TableScan\"),\n                }\n            }\n            _ => panic!(\"Expected Aggregate (no projection)\"),\n        }\n    }\n\n    #[test]\n    fn test_aggregate_without_group_by() {\n        let schema = create_test_schema();\n        let sql = \"SELECT COUNT(*), MAX(age) FROM users\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        match plan {\n            LogicalPlan::Aggregate(agg) => {\n                assert_eq!(agg.group_expr.len(), 0);\n                assert_eq!(agg.aggr_expr.len(), 2);\n                assert_eq!(agg.schema.column_count(), 2);\n\n                assert!(matches!(\n                    agg.aggr_expr[0],\n                    LogicalExpr::AggregateFunction {\n                        fun: AggFunc::Count,\n                        ..\n                    }\n                ));\n\n                assert!(matches!(\n                    agg.aggr_expr[1],\n                    LogicalExpr::AggregateFunction {\n                        fun: AggFunc::Max,\n                        ..\n                    }\n                ));\n            }\n            _ => panic!(\"Expected Aggregate (no projection)\"),\n        }\n    }\n\n    #[test]\n    fn test_order_by() {\n        let schema = create_test_schema();\n        let sql = \"SELECT name FROM users ORDER BY age DESC, name ASC\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        match plan {\n            LogicalPlan::Sort(sort) => {\n                assert_eq!(sort.exprs.len(), 2);\n                assert!(!sort.exprs[0].asc); // DESC\n                assert!(sort.exprs[1].asc); // ASC\n\n                match &*sort.input {\n                    LogicalPlan::Projection(_) => {}\n                    _ => panic!(\"Expected Projection\"),\n                }\n            }\n            _ => panic!(\"Expected Sort\"),\n        }\n    }\n\n    #[test]\n    fn test_limit_offset() {\n        let schema = create_test_schema();\n        let sql = \"SELECT * FROM users LIMIT 10 OFFSET 5\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        match plan {\n            LogicalPlan::Limit(limit) => {\n                assert_eq!(limit.fetch, Some(10));\n                assert_eq!(limit.skip, Some(5));\n            }\n            _ => panic!(\"Expected Limit\"),\n        }\n    }\n\n    #[test]\n    fn test_order_by_with_limit() {\n        let schema = create_test_schema();\n        let sql = \"SELECT name FROM users ORDER BY age DESC LIMIT 5\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        // Should produce: Limit -> Sort -> Projection -> TableScan\n        match plan {\n            LogicalPlan::Limit(limit) => {\n                assert_eq!(limit.fetch, Some(5));\n                assert_eq!(limit.skip, None);\n\n                match &*limit.input {\n                    LogicalPlan::Sort(sort) => {\n                        assert_eq!(sort.exprs.len(), 1);\n                        assert!(!sort.exprs[0].asc); // DESC\n\n                        match &*sort.input {\n                            LogicalPlan::Projection(_) => {}\n                            _ => panic!(\"Expected Projection under Sort\"),\n                        }\n                    }\n                    _ => panic!(\"Expected Sort under Limit\"),\n                }\n            }\n            _ => panic!(\"Expected Limit at top level\"),\n        }\n    }\n\n    #[test]\n    fn test_distinct() {\n        let schema = create_test_schema();\n        let sql = \"SELECT DISTINCT name FROM users\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        match plan {\n            LogicalPlan::Distinct(distinct) => match &*distinct.input {\n                LogicalPlan::Projection(_) => {}\n                _ => panic!(\"Expected Projection\"),\n            },\n            _ => panic!(\"Expected Distinct\"),\n        }\n    }\n\n    #[test]\n    fn test_union() {\n        let schema = create_test_schema();\n        let sql = \"SELECT id FROM users UNION SELECT user_id FROM orders\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        match plan {\n            LogicalPlan::Union(union) => {\n                assert!(!union.all);\n                assert_eq!(union.inputs.len(), 2);\n            }\n            _ => panic!(\"Expected Union\"),\n        }\n    }\n\n    #[test]\n    fn test_union_all() {\n        let schema = create_test_schema();\n        let sql = \"SELECT id FROM users UNION ALL SELECT user_id FROM orders\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        match plan {\n            LogicalPlan::Union(union) => {\n                assert!(union.all);\n                assert_eq!(union.inputs.len(), 2);\n            }\n            _ => panic!(\"Expected Union\"),\n        }\n    }\n\n    #[test]\n    fn test_union_with_order_by() {\n        let schema = create_test_schema();\n        let sql = \"SELECT id, name FROM users UNION SELECT user_id, name FROM orders ORDER BY id\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        match plan {\n            LogicalPlan::Sort(sort) => {\n                assert_eq!(sort.exprs.len(), 1);\n                assert!(sort.exprs[0].asc); // Default ASC\n\n                match &*sort.input {\n                    LogicalPlan::Union(union) => {\n                        assert!(!union.all); // UNION (not UNION ALL)\n                        assert_eq!(union.inputs.len(), 2);\n                    }\n                    _ => panic!(\"Expected Union under Sort\"),\n                }\n            }\n            _ => panic!(\"Expected Sort at top level\"),\n        }\n    }\n\n    #[test]\n    fn test_with_cte() {\n        let schema = create_test_schema();\n        let sql = \"WITH active_users AS (SELECT * FROM users WHERE age > 18) SELECT name FROM active_users\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        match plan {\n            LogicalPlan::WithCTE(with) => {\n                assert_eq!(with.ctes.len(), 1);\n                assert!(with.ctes.contains_key(\"active_users\"));\n\n                let cte = &with.ctes[\"active_users\"];\n                match &**cte {\n                    LogicalPlan::Projection(proj) => match &*proj.input {\n                        LogicalPlan::Filter(_) => {}\n                        _ => panic!(\"Expected Filter in CTE\"),\n                    },\n                    _ => panic!(\"Expected Projection in CTE\"),\n                }\n\n                match &*with.body {\n                    LogicalPlan::Projection(proj) => match &*proj.input {\n                        LogicalPlan::CTERef(cte_ref) => {\n                            assert_eq!(cte_ref.name, \"active_users\");\n                        }\n                        _ => panic!(\"Expected CTERef\"),\n                    },\n                    _ => panic!(\"Expected Projection in body\"),\n                }\n            }\n            _ => panic!(\"Expected WithCTE\"),\n        }\n    }\n\n    #[test]\n    fn test_case_expression() {\n        let schema = create_test_schema();\n        let sql = \"SELECT CASE WHEN age < 18 THEN 'minor' WHEN age < 65 THEN 'adult' ELSE 'senior' END FROM users\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        match plan {\n            LogicalPlan::Projection(proj) => {\n                assert_eq!(proj.exprs.len(), 1);\n                assert!(matches!(proj.exprs[0], LogicalExpr::Case { .. }));\n            }\n            _ => panic!(\"Expected Projection\"),\n        }\n    }\n\n    #[test]\n    fn test_in_list() {\n        let schema = create_test_schema();\n        let sql = \"SELECT * FROM users WHERE id IN (1, 2, 3)\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        match plan {\n            LogicalPlan::Projection(proj) => match &*proj.input {\n                LogicalPlan::Filter(filter) => match &filter.predicate {\n                    LogicalExpr::InList { list, negated, .. } => {\n                        assert!(!negated);\n                        assert_eq!(list.len(), 3);\n                    }\n                    _ => panic!(\"Expected InList\"),\n                },\n                _ => panic!(\"Expected Filter\"),\n            },\n            _ => panic!(\"Expected Projection\"),\n        }\n    }\n\n    #[test]\n    fn test_in_subquery() {\n        let schema = create_test_schema();\n        let sql = \"SELECT * FROM users WHERE id IN (SELECT user_id FROM orders)\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        match plan {\n            LogicalPlan::Projection(proj) => match &*proj.input {\n                LogicalPlan::Filter(filter) => {\n                    assert!(matches!(filter.predicate, LogicalExpr::InSubquery { .. }));\n                }\n                _ => panic!(\"Expected Filter\"),\n            },\n            _ => panic!(\"Expected Projection\"),\n        }\n    }\n\n    #[test]\n    fn test_exists_subquery() {\n        let schema = create_test_schema();\n        let sql = \"SELECT * FROM users WHERE EXISTS (SELECT 1 FROM orders WHERE orders.user_id = users.id)\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        match plan {\n            LogicalPlan::Projection(proj) => match &*proj.input {\n                LogicalPlan::Filter(filter) => {\n                    assert!(matches!(filter.predicate, LogicalExpr::Exists { .. }));\n                }\n                _ => panic!(\"Expected Filter\"),\n            },\n            _ => panic!(\"Expected Projection\"),\n        }\n    }\n\n    #[test]\n    fn test_between() {\n        let schema = create_test_schema();\n        let sql = \"SELECT * FROM users WHERE age BETWEEN 18 AND 65\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        match plan {\n            LogicalPlan::Projection(proj) => match &*proj.input {\n                LogicalPlan::Filter(filter) => match &filter.predicate {\n                    LogicalExpr::Between { negated, .. } => {\n                        assert!(!negated);\n                    }\n                    _ => panic!(\"Expected Between\"),\n                },\n                _ => panic!(\"Expected Filter\"),\n            },\n            _ => panic!(\"Expected Projection\"),\n        }\n    }\n\n    #[test]\n    fn test_like() {\n        let schema = create_test_schema();\n        let sql = \"SELECT * FROM users WHERE name LIKE 'John%'\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        match plan {\n            LogicalPlan::Projection(proj) => match &*proj.input {\n                LogicalPlan::Filter(filter) => match &filter.predicate {\n                    LogicalExpr::Like {\n                        negated, escape, ..\n                    } => {\n                        assert!(!negated);\n                        assert!(escape.is_none());\n                    }\n                    _ => panic!(\"Expected Like\"),\n                },\n                _ => panic!(\"Expected Filter\"),\n            },\n            _ => panic!(\"Expected Projection\"),\n        }\n    }\n\n    #[test]\n    fn test_is_null() {\n        let schema = create_test_schema();\n        let sql = \"SELECT * FROM users WHERE email IS NULL\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        match plan {\n            LogicalPlan::Projection(proj) => match &*proj.input {\n                LogicalPlan::Filter(filter) => match &filter.predicate {\n                    LogicalExpr::IsNull { negated, .. } => {\n                        assert!(!negated);\n                    }\n                    _ => panic!(\"Expected IsNull, got: {:?}\", filter.predicate),\n                },\n                _ => panic!(\"Expected Filter\"),\n            },\n            _ => panic!(\"Expected Projection\"),\n        }\n    }\n\n    #[test]\n    fn test_is_not_null() {\n        let schema = create_test_schema();\n        let sql = \"SELECT * FROM users WHERE email IS NOT NULL\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        match plan {\n            LogicalPlan::Projection(proj) => match &*proj.input {\n                LogicalPlan::Filter(filter) => match &filter.predicate {\n                    LogicalExpr::IsNull { negated, .. } => {\n                        assert!(negated);\n                    }\n                    _ => panic!(\"Expected IsNull\"),\n                },\n                _ => panic!(\"Expected Filter\"),\n            },\n            _ => panic!(\"Expected Projection\"),\n        }\n    }\n\n    #[test]\n    fn test_values_clause() {\n        let schema = create_test_schema();\n        let sql = \"SELECT * FROM (VALUES (1, 'a'), (2, 'b'))\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        match plan {\n            LogicalPlan::Projection(proj) => match &*proj.input {\n                LogicalPlan::Values(values) => {\n                    assert_eq!(values.rows.len(), 2);\n                    assert_eq!(values.rows[0].len(), 2);\n                }\n                _ => panic!(\"Expected Values\"),\n            },\n            _ => panic!(\"Expected Projection\"),\n        }\n    }\n\n    #[test]\n    fn test_complex_expression_with_aggregation() {\n        // Test: SELECT sum(id + 2) * 2 FROM orders GROUP BY user_id\n        let schema = create_test_schema();\n\n        // Test the complex case: sum((id + 2)) * 2 with parentheses\n        let sql = \"SELECT sum((id + 2)) * 2 FROM orders GROUP BY user_id\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        match plan {\n            LogicalPlan::Projection(proj) => {\n                assert_eq!(proj.exprs.len(), 1);\n                match &proj.exprs[0] {\n                    LogicalExpr::BinaryExpr { left, op, right } => {\n                        assert_eq!(*op, BinaryOperator::Multiply);\n                        assert!(matches!(**left, LogicalExpr::Column(_)));\n                        assert!(matches!(**right, LogicalExpr::Literal(_)));\n                    }\n                    _ => panic!(\"Expected BinaryExpr in projection\"),\n                }\n\n                match &*proj.input {\n                    LogicalPlan::Aggregate(agg) => {\n                        assert_eq!(agg.group_expr.len(), 1);\n\n                        assert_eq!(agg.aggr_expr.len(), 1);\n                        match &agg.aggr_expr[0] {\n                            LogicalExpr::AggregateFunction { fun, args, .. } => {\n                                assert_eq!(*fun, AggregateFunction::Sum);\n                                assert_eq!(args.len(), 1);\n                                match &args[0] {\n                                    LogicalExpr::Column(col) => {\n                                        assert!(col.name.starts_with(\"__agg_arg_proj_\"));\n                                    }\n                                    _ => panic!(\n                                        \"Expected Column reference to projected expression in aggregate args, got {:?}\",\n                                        args[0]\n                                    ),\n                                }\n                            }\n                            _ => panic!(\"Expected AggregateFunction\"),\n                        }\n\n                        match &*agg.input {\n                            LogicalPlan::Projection(inner_proj) => {\n                                assert!(inner_proj.exprs.len() >= 2);\n                                let has_binary_add = inner_proj.exprs.iter().any(|e| {\n                                    matches!(\n                                        e,\n                                        LogicalExpr::BinaryExpr {\n                                            op: BinaryOperator::Add,\n                                            ..\n                                        }\n                                    )\n                                });\n                                assert!(\n                                    has_binary_add,\n                                    \"Should have id + 2 expression in inner projection\"\n                                );\n                            }\n                            _ => panic!(\"Expected Projection as input to Aggregate\"),\n                        }\n                    }\n                    _ => panic!(\"Expected Aggregate under Projection\"),\n                }\n            }\n            _ => panic!(\"Expected Projection at top level\"),\n        }\n    }\n\n    #[test]\n    fn test_function_on_aggregate_result() {\n        let schema = create_test_schema();\n\n        let sql = \"SELECT abs(sum(id)) FROM orders GROUP BY user_id\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n        match plan {\n            LogicalPlan::Projection(proj) => {\n                assert_eq!(proj.exprs.len(), 1);\n                match &proj.exprs[0] {\n                    LogicalExpr::ScalarFunction { fun, args } => {\n                        assert_eq!(fun, \"abs\");\n                        assert_eq!(args.len(), 1);\n                        assert!(matches!(args[0], LogicalExpr::Column(_)));\n                    }\n                    _ => panic!(\"Expected ScalarFunction in projection\"),\n                }\n            }\n            _ => panic!(\"Expected Projection\"),\n        }\n    }\n\n    #[test]\n    fn test_multiple_aggregates_with_arithmetic() {\n        let schema = create_test_schema();\n\n        let sql = \"SELECT sum(id) * 2 + count(*) FROM orders GROUP BY user_id\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n        match plan {\n            LogicalPlan::Projection(proj) => {\n                assert_eq!(proj.exprs.len(), 1);\n                match &proj.exprs[0] {\n                    LogicalExpr::BinaryExpr { op, .. } => {\n                        assert_eq!(*op, BinaryOperator::Add);\n                    }\n                    _ => panic!(\"Expected BinaryExpr\"),\n                }\n\n                match &*proj.input {\n                    LogicalPlan::Aggregate(agg) => {\n                        assert_eq!(agg.aggr_expr.len(), 2);\n                    }\n                    _ => panic!(\"Expected Aggregate\"),\n                }\n            }\n            _ => panic!(\"Expected Projection\"),\n        }\n    }\n\n    #[test]\n    fn test_projection_aggregation_projection() {\n        let schema = create_test_schema();\n\n        // This tests: projection -> aggregation -> projection\n        // The inner projection computes (id + 2), then we aggregate sum(), then apply abs()\n        let sql = \"SELECT abs(sum(id + 2)) FROM orders GROUP BY user_id\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        // Should produce: Projection(abs) -> Aggregate(sum) -> Projection(id + 2) -> TableScan\n        match plan {\n            LogicalPlan::Projection(outer_proj) => {\n                assert_eq!(outer_proj.exprs.len(), 1);\n\n                // Outer projection should apply abs() function\n                match &outer_proj.exprs[0] {\n                    LogicalExpr::ScalarFunction { fun, args } => {\n                        assert_eq!(fun, \"abs\");\n                        assert_eq!(args.len(), 1);\n                        assert!(matches!(args[0], LogicalExpr::Column(_)));\n                    }\n                    _ => panic!(\"Expected abs() function in outer projection\"),\n                }\n\n                // Next should be the Aggregate\n                match &*outer_proj.input {\n                    LogicalPlan::Aggregate(agg) => {\n                        assert_eq!(agg.group_expr.len(), 1);\n                        assert_eq!(agg.aggr_expr.len(), 1);\n\n                        // The aggregate should be summing a column reference\n                        match &agg.aggr_expr[0] {\n                            LogicalExpr::AggregateFunction { fun, args, .. } => {\n                                assert_eq!(*fun, AggregateFunction::Sum);\n                                assert_eq!(args.len(), 1);\n\n                                // Should reference the projected column\n                                match &args[0] {\n                                    LogicalExpr::Column(col) => {\n                                        assert!(col.name.starts_with(\"__agg_arg_proj_\"));\n                                    }\n                                    _ => panic!(\"Expected column reference in aggregate\"),\n                                }\n                            }\n                            _ => panic!(\"Expected AggregateFunction\"),\n                        }\n\n                        // Input to aggregate should be a projection computing id + 2\n                        match &*agg.input {\n                            LogicalPlan::Projection(inner_proj) => {\n                                // Should have at least the group column and the computed expression\n                                assert!(inner_proj.exprs.len() >= 2);\n\n                                // Check for the id + 2 expression\n                                let has_add_expr = inner_proj.exprs.iter().any(|e| {\n                                    matches!(\n                                        e,\n                                        LogicalExpr::BinaryExpr {\n                                            op: BinaryOperator::Add,\n                                            ..\n                                        }\n                                    )\n                                });\n                                assert!(\n                                    has_add_expr,\n                                    \"Should have id + 2 expression in inner projection\"\n                                );\n                            }\n                            _ => panic!(\"Expected inner Projection under Aggregate\"),\n                        }\n                    }\n                    _ => panic!(\"Expected Aggregate under outer Projection\"),\n                }\n            }\n            _ => panic!(\"Expected Projection at top level\"),\n        }\n    }\n\n    #[test]\n    fn test_group_by_validation_allow_grouped_column() {\n        let schema = create_test_schema();\n\n        // Test that grouped columns are allowed\n        let sql = \"SELECT user_id, COUNT(*) FROM orders GROUP BY user_id\";\n        let result = parse_and_build(sql, &schema);\n\n        assert!(result.is_ok(), \"Should allow grouped column in SELECT\");\n    }\n\n    #[test]\n    fn test_group_by_validation_allow_constants() {\n        let schema = create_test_schema();\n\n        // Test that simple constants are allowed even when not grouped\n        let sql = \"SELECT user_id, 42, COUNT(*) FROM orders GROUP BY user_id\";\n        let result = parse_and_build(sql, &schema);\n\n        assert!(\n            result.is_ok(),\n            \"Should allow simple constants in SELECT with GROUP BY\"\n        );\n\n        let sql_complex = \"SELECT user_id, (100 + 50) * 2, COUNT(*) FROM orders GROUP BY user_id\";\n        let result_complex = parse_and_build(sql_complex, &schema);\n\n        assert!(\n            result_complex.is_ok(),\n            \"Should allow complex constant expressions in SELECT with GROUP BY\"\n        );\n    }\n\n    #[test]\n    fn test_parenthesized_aggregate_expressions() {\n        let schema = create_test_schema();\n\n        let sql = \"SELECT 25, (MAX(id) / 3), 39 FROM orders\";\n        let result = parse_and_build(sql, &schema);\n\n        assert!(\n            result.is_ok(),\n            \"Should handle parenthesized aggregate expressions\"\n        );\n\n        let plan = result.unwrap();\n\n        match plan {\n            LogicalPlan::Projection(proj) => {\n                assert_eq!(proj.exprs.len(), 3);\n\n                assert!(matches!(\n                    proj.exprs[0],\n                    LogicalExpr::Literal(Value::Numeric(Numeric::Integer(25)))\n                ));\n\n                match &proj.exprs[1] {\n                    LogicalExpr::BinaryExpr { left, op, right } => {\n                        assert_eq!(*op, BinaryOperator::Divide);\n                        assert!(matches!(&**left, LogicalExpr::Column(_)));\n                        assert!(matches!(\n                            &**right,\n                            LogicalExpr::Literal(Value::Numeric(Numeric::Integer(3)))\n                        ));\n                    }\n                    _ => panic!(\"Expected BinaryExpr for (MAX(id) / 3)\"),\n                }\n\n                assert!(matches!(\n                    proj.exprs[2],\n                    LogicalExpr::Literal(Value::Numeric(Numeric::Integer(39)))\n                ));\n\n                match &*proj.input {\n                    LogicalPlan::Aggregate(agg) => {\n                        assert_eq!(agg.aggr_expr.len(), 1);\n                        assert!(matches!(\n                            agg.aggr_expr[0],\n                            LogicalExpr::AggregateFunction {\n                                fun: AggFunc::Max,\n                                ..\n                            }\n                        ));\n                    }\n                    _ => panic!(\"Expected Aggregate node under Projection\"),\n                }\n            }\n            _ => panic!(\"Expected Projection at top level\"),\n        }\n    }\n\n    #[test]\n    fn test_duplicate_aggregate_reuse() {\n        let schema = create_test_schema();\n\n        let sql = \"SELECT (COUNT(*) - 225), 30, COUNT(*) FROM orders\";\n        let result = parse_and_build(sql, &schema);\n\n        assert!(result.is_ok(), \"Should handle duplicate aggregates\");\n\n        let plan = result.unwrap();\n\n        match plan {\n            LogicalPlan::Projection(proj) => {\n                assert_eq!(proj.exprs.len(), 3);\n\n                match &proj.exprs[0] {\n                    LogicalExpr::BinaryExpr { left, op, right } => {\n                        assert_eq!(*op, BinaryOperator::Subtract);\n                        match &**left {\n                            LogicalExpr::Column(col) => {\n                                assert!(col.name.starts_with(\"__agg_\") || col.name == \"COUNT(*)\");\n                            }\n                            _ => panic!(\"Expected Column reference for COUNT(*)\"),\n                        }\n                        assert!(matches!(\n                            &**right,\n                            LogicalExpr::Literal(Value::Numeric(Numeric::Integer(225)))\n                        ));\n                    }\n                    _ => panic!(\"Expected BinaryExpr for (COUNT(*) - 225)\"),\n                }\n\n                assert!(matches!(\n                    proj.exprs[1],\n                    LogicalExpr::Literal(Value::Numeric(Numeric::Integer(30)))\n                ));\n\n                match &proj.exprs[2] {\n                    LogicalExpr::Column(col) => {\n                        assert!(col.name.starts_with(\"__agg_\") || col.name == \"COUNT(*)\");\n                    }\n                    _ => panic!(\"Expected Column reference for COUNT(*)\"),\n                }\n\n                match &*proj.input {\n                    LogicalPlan::Aggregate(agg) => {\n                        assert_eq!(\n                            agg.aggr_expr.len(),\n                            1,\n                            \"Should have only one COUNT(*) aggregate\"\n                        );\n                        assert!(matches!(\n                            agg.aggr_expr[0],\n                            LogicalExpr::AggregateFunction {\n                                fun: AggFunc::Count,\n                                ..\n                            }\n                        ));\n                    }\n                    _ => panic!(\"Expected Aggregate node under Projection\"),\n                }\n            }\n            _ => panic!(\"Expected Projection at top level\"),\n        }\n    }\n\n    #[test]\n    fn test_aggregate_without_group_by_allow_constants() {\n        let schema = create_test_schema();\n\n        // Test that constants are allowed with aggregates even without GROUP BY\n        let sql = \"SELECT 42, COUNT(*), MAX(amount) FROM orders\";\n        let result = parse_and_build(sql, &schema);\n\n        assert!(\n            result.is_ok(),\n            \"Should allow simple constants with aggregates without GROUP BY\"\n        );\n\n        // Test complex constant expressions\n        let sql_complex = \"SELECT (9 / 6) % 5, COUNT(*), MAX(amount) FROM orders\";\n        let result_complex = parse_and_build(sql_complex, &schema);\n\n        assert!(\n            result_complex.is_ok(),\n            \"Should allow complex constant expressions with aggregates without GROUP BY\"\n        );\n    }\n\n    #[test]\n    fn test_aggregate_without_group_by_creates_aggregate_node() {\n        let schema = create_test_schema();\n\n        // Test that aggregate without GROUP BY creates proper Aggregate node\n        let sql = \"SELECT MAX(amount) FROM orders\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        // Should be: Aggregate -> TableScan (no projection needed for simple aggregate)\n        match plan {\n            LogicalPlan::Aggregate(agg) => {\n                assert_eq!(agg.group_expr.len(), 0, \"Should have no group expressions\");\n                assert_eq!(\n                    agg.aggr_expr.len(),\n                    1,\n                    \"Should have one aggregate expression\"\n                );\n                assert_eq!(\n                    agg.schema.column_count(),\n                    1,\n                    \"Schema should have one column\"\n                );\n            }\n            _ => panic!(\"Expected Aggregate at top level (no projection)\"),\n        }\n    }\n\n    #[test]\n    fn test_scalar_vs_aggregate_function_classification() {\n        let schema = create_test_schema();\n\n        // Test MIN/MAX with 1 argument - should be aggregate\n        let sql = \"SELECT MIN(amount) FROM orders\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n        match plan {\n            LogicalPlan::Aggregate(agg) => {\n                assert_eq!(agg.aggr_expr.len(), 1, \"MIN(x) should be an aggregate\");\n                match &agg.aggr_expr[0] {\n                    LogicalExpr::AggregateFunction { fun, args, .. } => {\n                        assert!(matches!(fun, AggFunc::Min));\n                        assert_eq!(args.len(), 1);\n                    }\n                    _ => panic!(\"Expected AggregateFunction\"),\n                }\n            }\n            _ => panic!(\"Expected Aggregate node for MIN(x)\"),\n        }\n\n        // Test MIN/MAX with 2 arguments - should be scalar in projection\n        let sql = \"SELECT MIN(amount, user_id) FROM orders\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n        match plan {\n            LogicalPlan::Projection(proj) => {\n                assert_eq!(proj.exprs.len(), 1, \"Should have one projection expression\");\n                match &proj.exprs[0] {\n                    LogicalExpr::ScalarFunction { fun, args } => {\n                        assert_eq!(\n                            fun.to_lowercase(),\n                            \"min\",\n                            \"MIN(x,y) should be a scalar function\"\n                        );\n                        assert_eq!(args.len(), 2);\n                    }\n                    _ => panic!(\"Expected ScalarFunction for MIN(x,y)\"),\n                }\n            }\n            _ => panic!(\"Expected Projection node for scalar MIN(x,y)\"),\n        }\n\n        // Test MAX with 3 arguments - should be scalar\n        let sql = \"SELECT MAX(amount, user_id, id) FROM orders\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n        match plan {\n            LogicalPlan::Projection(proj) => {\n                assert_eq!(proj.exprs.len(), 1);\n                match &proj.exprs[0] {\n                    LogicalExpr::ScalarFunction { fun, args } => {\n                        assert_eq!(\n                            fun.to_lowercase(),\n                            \"max\",\n                            \"MAX(x,y,z) should be a scalar function\"\n                        );\n                        assert_eq!(args.len(), 3);\n                    }\n                    _ => panic!(\"Expected ScalarFunction for MAX(x,y,z)\"),\n                }\n            }\n            _ => panic!(\"Expected Projection node for scalar MAX(x,y,z)\"),\n        }\n\n        // Test that MIN with 0 args is treated as scalar (will fail later in execution)\n        let sql = \"SELECT MIN() FROM orders\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n        match plan {\n            LogicalPlan::Projection(proj) => match &proj.exprs[0] {\n                LogicalExpr::ScalarFunction { fun, args } => {\n                    assert_eq!(fun.to_lowercase(), \"min\");\n                    assert_eq!(args.len(), 0, \"MIN() should be scalar with 0 args\");\n                }\n                _ => panic!(\"Expected ScalarFunction for MIN()\"),\n            },\n            _ => panic!(\"Expected Projection for MIN()\"),\n        }\n\n        // Test other functions that are always aggregate (COUNT, SUM, AVG)\n        let sql = \"SELECT COUNT(*), SUM(amount), AVG(amount) FROM orders\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n        match plan {\n            LogicalPlan::Aggregate(agg) => {\n                assert_eq!(agg.aggr_expr.len(), 3, \"Should have 3 aggregate functions\");\n                for expr in &agg.aggr_expr {\n                    assert!(matches!(expr, LogicalExpr::AggregateFunction { .. }));\n                }\n            }\n            _ => panic!(\"Expected Aggregate node\"),\n        }\n\n        // Test scalar functions that are never aggregates (ABS, ROUND, etc.)\n        let sql = \"SELECT ABS(amount), ROUND(amount), LENGTH(product) FROM orders\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n        match plan {\n            LogicalPlan::Projection(proj) => {\n                assert_eq!(proj.exprs.len(), 3, \"Should have 3 scalar functions\");\n                for expr in &proj.exprs {\n                    match expr {\n                        LogicalExpr::ScalarFunction { .. } => {}\n                        _ => panic!(\"Expected all ScalarFunctions\"),\n                    }\n                }\n            }\n            _ => panic!(\"Expected Projection node for scalar functions\"),\n        }\n    }\n\n    #[test]\n    fn test_mixed_aggregate_and_group_columns() {\n        let schema = create_test_schema();\n\n        // When selecting both aggregate and grouping columns\n        let sql = \"SELECT user_id, sum(id) FROM orders GROUP BY user_id\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        // No projection needed - aggregate outputs exactly what we select\n        match plan {\n            LogicalPlan::Aggregate(agg) => {\n                assert_eq!(agg.group_expr.len(), 1);\n                assert_eq!(agg.aggr_expr.len(), 1);\n                assert_eq!(agg.schema.column_count(), 2);\n            }\n            _ => panic!(\"Expected Aggregate (no projection)\"),\n        }\n    }\n\n    #[test]\n    fn test_scalar_function_wrapping_aggregate_no_group_by() {\n        // Test: SELECT HEX(SUM(age + 2)) FROM users\n        // Expected structure:\n        // Projection { exprs: [ScalarFunction(HEX, [Column])] }\n        //   -> Aggregate { aggr_expr: [Sum(BinaryExpr(age + 2))], group_expr: [] }\n        //     -> Projection { exprs: [BinaryExpr(age + 2)] }\n        //       -> TableScan(\"users\")\n\n        let schema = create_test_schema();\n        let sql = \"SELECT HEX(SUM(age + 2)) FROM users\";\n        let mut parser = Parser::new(sql.as_bytes());\n        let stmt = parser.next().unwrap().unwrap();\n\n        let plan = match stmt {\n            ast::Cmd::Stmt(stmt) => {\n                let mut builder = LogicalPlanBuilder::new(&schema);\n                builder.build_statement(&stmt).unwrap()\n            }\n            _ => panic!(\"Expected SQL statement\"),\n        };\n\n        match &plan {\n            LogicalPlan::Projection(proj) => {\n                assert_eq!(proj.exprs.len(), 1, \"Should have one expression\");\n\n                match &proj.exprs[0] {\n                    LogicalExpr::ScalarFunction { fun, args } => {\n                        assert_eq!(fun, \"HEX\", \"Outer function should be HEX\");\n                        assert_eq!(args.len(), 1, \"HEX should have one argument\");\n\n                        match &args[0] {\n                            LogicalExpr::Column(_) => {}\n                            LogicalExpr::AggregateFunction { .. } => {\n                                panic!(\n                                    \"Aggregate function should not be embedded in projection! It should be in a separate Aggregate operator\"\n                                );\n                            }\n                            _ => panic!(\n                                \"Expected column reference as argument to HEX, got: {:?}\",\n                                args[0]\n                            ),\n                        }\n                    }\n                    _ => panic!(\"Expected ScalarFunction (HEX), got: {:?}\", proj.exprs[0]),\n                }\n\n                match &*proj.input {\n                    LogicalPlan::Aggregate(agg) => {\n                        assert_eq!(agg.group_expr.len(), 0, \"Should have no GROUP BY\");\n                        assert_eq!(\n                            agg.aggr_expr.len(),\n                            1,\n                            \"Should have one aggregate expression\"\n                        );\n\n                        match &agg.aggr_expr[0] {\n                            LogicalExpr::AggregateFunction {\n                                fun,\n                                args,\n                                distinct,\n                            } => {\n                                assert_eq!(*fun, crate::function::AggFunc::Sum, \"Should be SUM\");\n                                assert!(!distinct, \"Should not be DISTINCT\");\n                                assert_eq!(args.len(), 1, \"SUM should have one argument\");\n\n                                match &args[0] {\n                                    LogicalExpr::Column(col) => {\n                                        // When aggregate arguments are complex, they get pre-projected\n                                        assert!(\n                                            col.name.starts_with(\"__agg_arg_proj_\"),\n                                            \"Should reference pre-projected column, got: {}\",\n                                            col.name\n                                        );\n                                    }\n                                    LogicalExpr::BinaryExpr { left, op, right } => {\n                                        // Simple case without pre-projection (shouldn't happen with current implementation)\n                                        assert_eq!(*op, ast::Operator::Add, \"Should be addition\");\n\n                                        match (&**left, &**right) {\n                                            (\n                                                LogicalExpr::Column(col),\n                                                LogicalExpr::Literal(val),\n                                            ) => {\n                                                assert_eq!(\n                                                    col.name, \"age\",\n                                                    \"Should reference age column\"\n                                                );\n                                                assert_eq!(\n                                                    *val,\n                                                    Value::from_i64(2),\n                                                    \"Should add 2\"\n                                                );\n                                            }\n                                            _ => panic!(\"Expected age + 2\"),\n                                        }\n                                    }\n                                    _ => panic!(\n                                        \"Expected Column reference or BinaryExpr for aggregate argument, got: {:?}\",\n                                        args[0]\n                                    ),\n                                }\n                            }\n                            _ => panic!(\"Expected AggregateFunction\"),\n                        }\n\n                        match &*agg.input {\n                            LogicalPlan::TableScan(scan) => {\n                                assert_eq!(scan.table_name, \"users\");\n                            }\n                            LogicalPlan::Projection(proj) => match &*proj.input {\n                                LogicalPlan::TableScan(scan) => {\n                                    assert_eq!(scan.table_name, \"users\");\n                                }\n                                _ => panic!(\"Expected TableScan under projection\"),\n                            },\n                            _ => panic!(\"Expected TableScan or Projection under Aggregate\"),\n                        }\n                    }\n                    _ => panic!(\n                        \"Expected Aggregate operator under Projection, got: {:?}\",\n                        proj.input\n                    ),\n                }\n            }\n            _ => panic!(\"Expected Projection as top-level operator, got: {plan:?}\"),\n        }\n    }\n\n    // ===== JOIN TESTS =====\n\n    #[test]\n    fn test_inner_join() {\n        let schema = create_test_schema();\n        let sql = \"SELECT * FROM users u INNER JOIN orders o ON u.id = o.user_id\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        match plan {\n            LogicalPlan::Projection(proj) => {\n                match &*proj.input {\n                    LogicalPlan::Join(join) => {\n                        assert_eq!(join.join_type, JoinType::Inner);\n                        assert!(!join.on.is_empty(), \"Should have join conditions\");\n\n                        // Check left input is users\n                        match &*join.left {\n                            LogicalPlan::TableScan(scan) => {\n                                assert_eq!(scan.table_name, \"users\");\n                            }\n                            _ => panic!(\"Expected TableScan for left input\"),\n                        }\n\n                        // Check right input is orders\n                        match &*join.right {\n                            LogicalPlan::TableScan(scan) => {\n                                assert_eq!(scan.table_name, \"orders\");\n                            }\n                            _ => panic!(\"Expected TableScan for right input\"),\n                        }\n                    }\n                    _ => panic!(\"Expected Join under Projection\"),\n                }\n            }\n            _ => panic!(\"Expected Projection at top level\"),\n        }\n    }\n\n    #[test]\n    fn test_left_join() {\n        let schema = create_test_schema();\n        let sql = \"SELECT u.name, o.amount FROM users u LEFT JOIN orders o ON u.id = o.user_id\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        match plan {\n            LogicalPlan::Projection(proj) => {\n                assert_eq!(proj.exprs.len(), 2); // name and amount\n                match &*proj.input {\n                    LogicalPlan::Join(join) => {\n                        assert_eq!(join.join_type, JoinType::Left);\n                        assert!(!join.on.is_empty(), \"Should have join conditions\");\n                    }\n                    _ => panic!(\"Expected Join under Projection\"),\n                }\n            }\n            _ => panic!(\"Expected Projection at top level\"),\n        }\n    }\n\n    #[test]\n    fn test_right_join() {\n        let schema = create_test_schema();\n        let sql = \"SELECT * FROM orders o RIGHT JOIN users u ON o.user_id = u.id\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        match plan {\n            LogicalPlan::Projection(proj) => match &*proj.input {\n                LogicalPlan::Join(join) => {\n                    assert_eq!(join.join_type, JoinType::Right);\n                    assert!(!join.on.is_empty(), \"Should have join conditions\");\n                }\n                _ => panic!(\"Expected Join under Projection\"),\n            },\n            _ => panic!(\"Expected Projection at top level\"),\n        }\n    }\n\n    #[test]\n    fn test_full_outer_join() {\n        let schema = create_test_schema();\n        let sql = \"SELECT * FROM users u FULL OUTER JOIN orders o ON u.id = o.user_id\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        match plan {\n            LogicalPlan::Projection(proj) => match &*proj.input {\n                LogicalPlan::Join(join) => {\n                    assert_eq!(join.join_type, JoinType::Full);\n                    assert!(!join.on.is_empty(), \"Should have join conditions\");\n                }\n                _ => panic!(\"Expected Join under Projection\"),\n            },\n            _ => panic!(\"Expected Projection at top level\"),\n        }\n    }\n\n    #[test]\n    fn test_cross_join() {\n        let schema = create_test_schema();\n        let sql = \"SELECT * FROM users CROSS JOIN orders\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        match plan {\n            LogicalPlan::Projection(proj) => match &*proj.input {\n                LogicalPlan::Join(join) => {\n                    assert_eq!(join.join_type, JoinType::Cross);\n                    assert!(join.on.is_empty(), \"Cross join should have no conditions\");\n                    assert!(join.filter.is_none(), \"Cross join should have no filter\");\n                }\n                _ => panic!(\"Expected Join under Projection\"),\n            },\n            _ => panic!(\"Expected Projection at top level\"),\n        }\n    }\n\n    #[test]\n    fn test_join_with_multiple_conditions() {\n        let schema = create_test_schema();\n        let sql = \"SELECT * FROM users u JOIN orders o ON u.id = o.user_id AND u.age > 18\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        match plan {\n            LogicalPlan::Projection(proj) => {\n                match &*proj.input {\n                    LogicalPlan::Join(join) => {\n                        assert_eq!(join.join_type, JoinType::Inner);\n                        // Should have at least one equijoin condition\n                        assert!(!join.on.is_empty(), \"Should have join conditions\");\n                        // Additional conditions may be in filter\n                        // The exact distribution depends on our implementation\n                    }\n                    _ => panic!(\"Expected Join under Projection\"),\n                }\n            }\n            _ => panic!(\"Expected Projection at top level\"),\n        }\n    }\n\n    #[test]\n    fn test_join_using_clause() {\n        let schema = create_test_schema();\n        // Note: Both tables should have an 'id' column for this to work\n        let sql = \"SELECT * FROM users JOIN orders USING (id)\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        match plan {\n            LogicalPlan::Projection(proj) => match &*proj.input {\n                LogicalPlan::Join(join) => {\n                    assert_eq!(join.join_type, JoinType::Inner);\n                    assert!(\n                        !join.on.is_empty(),\n                        \"USING clause should create join conditions\"\n                    );\n                }\n                _ => panic!(\"Expected Join under Projection\"),\n            },\n            _ => panic!(\"Expected Projection at top level\"),\n        }\n    }\n\n    #[test]\n    fn test_natural_join() {\n        let schema = create_test_schema();\n        let sql = \"SELECT * FROM users NATURAL JOIN orders\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        match plan {\n            LogicalPlan::Projection(proj) => {\n                match &*proj.input {\n                    LogicalPlan::Join(join) => {\n                        // Natural join finds common columns (id in this case)\n                        // If no common columns, it becomes a cross join\n                        assert!(\n                            !join.on.is_empty() || join.join_type == JoinType::Cross,\n                            \"Natural join should either find common columns or become cross join\"\n                        );\n                    }\n                    _ => panic!(\"Expected Join under Projection\"),\n                }\n            }\n            _ => panic!(\"Expected Projection at top level\"),\n        }\n    }\n\n    #[test]\n    fn test_three_way_join() {\n        let schema = create_test_schema();\n        let sql = \"SELECT * FROM users u\n                   JOIN orders o ON u.id = o.user_id\n                   JOIN products p ON o.product_id = p.id\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        match plan {\n            LogicalPlan::Projection(proj) => {\n                match &*proj.input {\n                    LogicalPlan::Join(join2) => {\n                        // Second join (with products)\n                        assert_eq!(join2.join_type, JoinType::Inner);\n                        match &*join2.left {\n                            LogicalPlan::Join(join1) => {\n                                // First join (users with orders)\n                                assert_eq!(join1.join_type, JoinType::Inner);\n                            }\n                            _ => panic!(\"Expected nested Join for three-way join\"),\n                        }\n                    }\n                    _ => panic!(\"Expected Join under Projection\"),\n                }\n            }\n            _ => panic!(\"Expected Projection at top level\"),\n        }\n    }\n\n    #[test]\n    fn test_mixed_join_types() {\n        let schema = create_test_schema();\n        let sql = \"SELECT * FROM users u\n                   LEFT JOIN orders o ON u.id = o.user_id\n                   INNER JOIN products p ON o.product_id = p.id\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        match plan {\n            LogicalPlan::Projection(proj) => {\n                match &*proj.input {\n                    LogicalPlan::Join(join2) => {\n                        // Second join should be INNER\n                        assert_eq!(join2.join_type, JoinType::Inner);\n                        match &*join2.left {\n                            LogicalPlan::Join(join1) => {\n                                // First join should be LEFT\n                                assert_eq!(join1.join_type, JoinType::Left);\n                            }\n                            _ => panic!(\"Expected nested Join\"),\n                        }\n                    }\n                    _ => panic!(\"Expected Join under Projection\"),\n                }\n            }\n            _ => panic!(\"Expected Projection at top level\"),\n        }\n    }\n\n    #[test]\n    fn test_join_with_filter() {\n        let schema = create_test_schema();\n        let sql = \"SELECT * FROM users u JOIN orders o ON u.id = o.user_id WHERE o.amount > 100\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        match plan {\n            LogicalPlan::Projection(proj) => {\n                match &*proj.input {\n                    LogicalPlan::Filter(filter) => {\n                        // WHERE clause creates a Filter above the Join\n                        match &*filter.input {\n                            LogicalPlan::Join(join) => {\n                                assert_eq!(join.join_type, JoinType::Inner);\n                            }\n                            _ => panic!(\"Expected Join under Filter\"),\n                        }\n                    }\n                    _ => panic!(\"Expected Filter under Projection\"),\n                }\n            }\n            _ => panic!(\"Expected Projection at top level\"),\n        }\n    }\n\n    #[test]\n    fn test_join_with_projection() {\n        let schema = create_test_schema();\n        let sql = \"SELECT u.name, o.amount FROM users u JOIN orders o ON u.id = o.user_id\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        match plan {\n            LogicalPlan::Projection(proj) => {\n                assert_eq!(proj.exprs.len(), 2); // u.name and o.amount\n                match &*proj.input {\n                    LogicalPlan::Join(join) => {\n                        assert_eq!(join.join_type, JoinType::Inner);\n                    }\n                    _ => panic!(\"Expected Join under Projection\"),\n                }\n            }\n            _ => panic!(\"Expected Projection at top level\"),\n        }\n    }\n\n    #[test]\n    fn test_join_with_aggregation() {\n        let schema = create_test_schema();\n        let sql = \"SELECT u.name, SUM(o.amount)\n                   FROM users u JOIN orders o ON u.id = o.user_id\n                   GROUP BY u.name\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        match plan {\n            LogicalPlan::Aggregate(agg) => {\n                assert_eq!(agg.group_expr.len(), 1); // GROUP BY u.name\n                assert_eq!(agg.aggr_expr.len(), 1); // SUM(o.amount)\n                match &*agg.input {\n                    LogicalPlan::Join(join) => {\n                        assert_eq!(join.join_type, JoinType::Inner);\n                    }\n                    _ => panic!(\"Expected Join under Aggregate\"),\n                }\n            }\n            _ => panic!(\"Expected Aggregate\"),\n        }\n    }\n\n    #[test]\n    fn test_join_with_order_by() {\n        let schema = create_test_schema();\n        let sql = \"SELECT * FROM users u JOIN orders o ON u.id = o.user_id ORDER BY o.amount DESC\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        match plan {\n            LogicalPlan::Sort(sort) => {\n                assert_eq!(sort.exprs.len(), 1);\n                assert!(!sort.exprs[0].asc); // DESC\n                match &*sort.input {\n                    LogicalPlan::Projection(proj) => match &*proj.input {\n                        LogicalPlan::Join(join) => {\n                            assert_eq!(join.join_type, JoinType::Inner);\n                        }\n                        _ => panic!(\"Expected Join under Projection\"),\n                    },\n                    _ => panic!(\"Expected Projection under Sort\"),\n                }\n            }\n            _ => panic!(\"Expected Sort at top level\"),\n        }\n    }\n\n    #[test]\n    fn test_join_in_subquery() {\n        let schema = create_test_schema();\n        let sql = \"SELECT * FROM (\n                     SELECT u.id, u.name, o.amount\n                     FROM users u JOIN orders o ON u.id = o.user_id\n                   ) WHERE amount > 100\";\n        let plan = parse_and_build(sql, &schema).unwrap();\n\n        match plan {\n            LogicalPlan::Projection(outer_proj) => match &*outer_proj.input {\n                LogicalPlan::Filter(filter) => match &*filter.input {\n                    LogicalPlan::Projection(inner_proj) => match &*inner_proj.input {\n                        LogicalPlan::Join(join) => {\n                            assert_eq!(join.join_type, JoinType::Inner);\n                        }\n                        _ => panic!(\"Expected Join in subquery\"),\n                    },\n                    _ => panic!(\"Expected Projection for subquery\"),\n                },\n                _ => panic!(\"Expected Filter\"),\n            },\n            _ => panic!(\"Expected Projection at top level\"),\n        }\n    }\n\n    #[test]\n    fn test_join_ambiguous_column() {\n        let schema = create_test_schema();\n        // Both users and orders have an 'id' column\n        let sql = \"SELECT id FROM users JOIN orders ON users.id = orders.user_id\";\n        let result = parse_and_build(sql, &schema);\n        // This might error or succeed depending on how we handle ambiguous columns\n        // For now, just check that parsing completes\n        match result {\n            Ok(_) => {\n                // If successful, the implementation handles ambiguous columns somehow\n            }\n            Err(_) => {\n                // If error, the implementation rejects ambiguous columns\n            }\n        }\n    }\n\n    // Tests for strip_alias function\n    #[test]\n    fn test_strip_alias_with_alias() {\n        let inner_expr = LogicalExpr::Column(Column::new(\"test\"));\n        let aliased = LogicalExpr::Alias {\n            expr: Box::new(inner_expr.clone()),\n            alias: \"my_alias\".to_string(),\n        };\n\n        let stripped = strip_alias(&aliased);\n        assert_eq!(stripped, &inner_expr);\n    }\n\n    #[test]\n    fn test_strip_alias_without_alias() {\n        let expr = LogicalExpr::Column(Column::new(\"test\"));\n        let stripped = strip_alias(&expr);\n        assert_eq!(stripped, &expr);\n    }\n\n    #[test]\n    fn test_strip_alias_literal() {\n        let expr = LogicalExpr::Literal(Value::from_i64(42));\n        let stripped = strip_alias(&expr);\n        assert_eq!(stripped, &expr);\n    }\n\n    #[test]\n    fn test_strip_alias_scalar_function() {\n        let expr = LogicalExpr::ScalarFunction {\n            fun: \"substr\".to_string(),\n            args: vec![\n                LogicalExpr::Column(Column::new(\"name\")),\n                LogicalExpr::Literal(Value::from_i64(1)),\n                LogicalExpr::Literal(Value::from_i64(4)),\n            ],\n        };\n        let stripped = strip_alias(&expr);\n        assert_eq!(stripped, &expr);\n    }\n\n    #[test]\n    fn test_strip_alias_nested_alias() {\n        // Test that strip_alias only removes the outermost alias\n        let inner_expr = LogicalExpr::Column(Column::new(\"test\"));\n        let inner_alias = LogicalExpr::Alias {\n            expr: Box::new(inner_expr.clone()),\n            alias: \"inner_alias\".to_string(),\n        };\n        let outer_alias = LogicalExpr::Alias {\n            expr: Box::new(inner_alias.clone()),\n            alias: \"outer_alias\".to_string(),\n        };\n\n        let stripped = strip_alias(&outer_alias);\n        assert_eq!(stripped, &inner_alias);\n\n        // Stripping again should give us the inner expression\n        let double_stripped = strip_alias(stripped);\n        assert_eq!(double_stripped, &inner_expr);\n    }\n\n    #[test]\n    fn test_strip_alias_comparison_with_alias() {\n        // Test that two expressions match when one has an alias and one doesn't\n        let base_expr = LogicalExpr::ScalarFunction {\n            fun: \"substr\".to_string(),\n            args: vec![\n                LogicalExpr::Column(Column::new(\"orderdate\")),\n                LogicalExpr::Literal(Value::from_i64(1)),\n                LogicalExpr::Literal(Value::from_i64(4)),\n            ],\n        };\n\n        let aliased_expr = LogicalExpr::Alias {\n            expr: Box::new(base_expr.clone()),\n            alias: \"year\".to_string(),\n        };\n\n        // Without strip_alias, they wouldn't match\n        assert_ne!(&aliased_expr, &base_expr);\n\n        // With strip_alias, they should match\n        assert_eq!(strip_alias(&aliased_expr), &base_expr);\n        assert_eq!(strip_alias(&base_expr), &base_expr);\n    }\n\n    #[test]\n    fn test_strip_alias_binary_expr() {\n        let expr = LogicalExpr::BinaryExpr {\n            left: Box::new(LogicalExpr::Column(Column::new(\"a\"))),\n            op: BinaryOperator::Add,\n            right: Box::new(LogicalExpr::Literal(Value::from_i64(1))),\n        };\n        let stripped = strip_alias(&expr);\n        assert_eq!(stripped, &expr);\n    }\n\n    #[test]\n    fn test_strip_alias_aggregate_function() {\n        let expr = LogicalExpr::AggregateFunction {\n            fun: AggFunc::Sum,\n            args: vec![LogicalExpr::Column(Column::new(\"amount\"))],\n            distinct: false,\n        };\n        let stripped = strip_alias(&expr);\n        assert_eq!(stripped, &expr);\n    }\n\n    #[test]\n    fn test_strip_alias_comparison_multiple_expressions() {\n        // Test comparing a list of expressions with and without aliases\n        let expr1 = LogicalExpr::Column(Column::new(\"a\"));\n        let expr2 = LogicalExpr::ScalarFunction {\n            fun: \"substr\".to_string(),\n            args: vec![\n                LogicalExpr::Column(Column::new(\"b\")),\n                LogicalExpr::Literal(Value::from_i64(1)),\n                LogicalExpr::Literal(Value::from_i64(4)),\n            ],\n        };\n\n        let aliased1 = LogicalExpr::Alias {\n            expr: Box::new(expr1.clone()),\n            alias: \"col_a\".to_string(),\n        };\n        let aliased2 = LogicalExpr::Alias {\n            expr: Box::new(expr2.clone()),\n            alias: \"year\".to_string(),\n        };\n\n        let select_exprs = [aliased1, aliased2];\n        let group_exprs = [expr1, expr2];\n\n        // Verify that stripping aliases allows matching\n        for (select_expr, group_expr) in select_exprs.iter().zip(group_exprs.iter()) {\n            assert_eq!(strip_alias(select_expr), group_expr);\n        }\n    }\n}\n"
  },
  {
    "path": "core/translate/main_loop/body.rs",
    "content": "use crate::translate::plan::SimpleAggregate;\nuse crate::translate::{\n    aggregation::emit_collseq_if_needed,\n    order_by::{custom_type_comparator, EmitOrderBy},\n    window::EmitWindow,\n};\n\nuse super::*;\n\n/// SQLite (and so Turso) processes joins as a nested loop.\n/// The loop may emit rows to various destinations depending on the query:\n/// - a GROUP BY sorter (grouping is done by sorting based on the GROUP BY keys and aggregating while the GROUP BY keys match)\n/// - a GROUP BY phase with no sorting (when the rows are already in the order required by the GROUP BY keys)\n/// - an AggStep (the columns are collected for aggregation, which is finished later)\n/// - a Window (rows are buffered and returned according to the rules of the window definition)\n/// - an ORDER BY sorter (when there is none of the above, but there is an ORDER BY)\n/// - a QueryResult (there is none of the above, so the loop either emits a ResultRow, or if it's a subquery, yields to the parent query)\nenum LoopEmitTarget {\n    GroupBy,\n    OrderBySorter,\n    AggStep,\n    Window,\n    QueryResult,\n}\n\n/// Emits the bytecode for the inner loop of a query.\n/// At this point the cursors for all tables have been opened and rewound.\npub fn emit_loop<'a>(\n    program: &mut ProgramBuilder,\n    t_ctx: &mut TranslateCtx<'a>,\n    plan: &'a SelectPlan,\n) -> Result<()> {\n    LoopBodyEmitter::emit(program, t_ctx, plan)\n}\n\n/// Emits the select-loop body.\npub struct LoopBodyEmitter;\n\n/// Internal state for loop-body emission.\n///\n/// The body has one non-obvious ordering rule: anti-join body entry must be\n/// resolved before any body instructions are emitted, otherwise relocated\n/// constants can make the backward jump land incorrectly.\nstruct LoopBody<'prog, 'ctx, 'plan> {\n    program: &'prog mut ProgramBuilder,\n    t_ctx: &'ctx mut TranslateCtx<'plan>,\n    plan: &'plan SelectPlan,\n}\n\nimpl LoopBodyEmitter {\n    pub fn emit<'a>(\n        program: &mut ProgramBuilder,\n        t_ctx: &mut TranslateCtx<'a>,\n        plan: &'a SelectPlan,\n    ) -> Result<()> {\n        LoopBody::new(program, t_ctx, plan).emit()\n    }\n}\n\nimpl<'prog, 'ctx, 'plan> LoopBody<'prog, 'ctx, 'plan> {\n    const fn new(\n        program: &'prog mut ProgramBuilder,\n        t_ctx: &'ctx mut TranslateCtx<'plan>,\n        plan: &'plan SelectPlan,\n    ) -> Self {\n        Self {\n            program,\n            t_ctx,\n            plan,\n        }\n    }\n\n    /// Resolve the final anti-join body target before any row-emission logic runs.\n    fn resolve_anti_join_entry(&mut self) {\n        // The innermost anti-join body entry must be resolved before any row\n        // emission target is chosen, otherwise the late jump back into the body\n        // can land on the wrong relocated instruction.\n        if let Some(last_join) = self.plan.join_order.last() {\n            let last_idx = last_join.original_idx;\n            let is_anti = self.plan.table_references.joined_tables()[last_idx]\n                .join_info\n                .as_ref()\n                .is_some_and(|ji| ji.is_anti());\n            if is_anti {\n                if let Some(sa_meta) = self.t_ctx.meta_semi_anti_joins[last_idx].as_ref() {\n                    self.program\n                        .preassign_label_to_next_insn(sa_meta.label_body);\n                }\n            }\n        }\n    }\n\n    /// Choose the row-consumption target for the already-open main loop.\n    fn select_emit_target(&self) -> LoopEmitTarget {\n        if self\n            .plan\n            .group_by\n            .as_ref()\n            .is_some_and(|gb| !gb.exprs.is_empty())\n        {\n            return LoopEmitTarget::GroupBy;\n        }\n        if !self.plan.aggregates.is_empty() {\n            return LoopEmitTarget::AggStep;\n        }\n        if self.plan.window.is_some() {\n            return LoopEmitTarget::Window;\n        }\n        if !self.plan.order_by.is_empty() {\n            return LoopEmitTarget::OrderBySorter;\n        }\n        LoopEmitTarget::QueryResult\n    }\n\n    /// Emit the loop body once all required entry labels are fixed.\n    fn emit(mut self) -> Result<()> {\n        self.resolve_anti_join_entry();\n        emit_loop_source(\n            self.program,\n            self.t_ctx,\n            self.plan,\n            self.select_emit_target(),\n        )\n    }\n}\n\n/// This is a helper function for inner_loop_emit,\n/// which does a different thing depending on the emit target.\n/// See the InnerLoopEmitTarget enum for more details.\nfn emit_loop_source<'a>(\n    program: &mut ProgramBuilder,\n    t_ctx: &mut TranslateCtx<'a>,\n    plan: &'a SelectPlan,\n    emit_target: LoopEmitTarget,\n) -> Result<()> {\n    match emit_target {\n        LoopEmitTarget::GroupBy => {\n            let GroupByMetadata {\n                row_source,\n                registers,\n                ..\n            } = t_ctx.meta_group_by.as_ref().unwrap();\n\n            let start_reg = registers.reg_group_by_source_cols_start;\n            let mut cur_reg = start_reg;\n\n            // Collect all non-aggregate expressions in the following order:\n            // 1. GROUP BY expressions. These serve as sort keys.\n            // 2. Remaining non-aggregate expressions that are not in GROUP BY.\n            //\n            // Example:\n            //   SELECT col1, col2, SUM(col3) FROM table GROUP BY col1\n            //   - col1 is added first (from GROUP BY)\n            //   - col2 is added second (non-aggregate, in SELECT, not in GROUP BY)\n            for (expr, _) in t_ctx.non_aggregate_expressions.iter() {\n                let key_reg = cur_reg;\n                cur_reg += 1;\n                translate_expr(\n                    program,\n                    Some(&plan.table_references),\n                    expr,\n                    key_reg,\n                    &t_ctx.resolver,\n                )?;\n            }\n\n            match row_source {\n                GroupByRowSource::Sorter {\n                    sort_cursor,\n                    sorter_column_count,\n                    reg_sorter_key,\n                    ..\n                } => {\n                    // Sorter path: store only unique leaf columns from aggregate args.\n                    // Full expressions are re-evaluated from the pseudo cursor during aggregation.\n                    for leaf_expr in t_ctx.agg_leaf_columns.iter() {\n                        let reg = cur_reg;\n                        cur_reg += 1;\n                        translate_expr(\n                            program,\n                            Some(&plan.table_references),\n                            leaf_expr,\n                            reg,\n                            &t_ctx.resolver,\n                        )?;\n                    }\n                    sorter_insert(\n                        program,\n                        start_reg,\n                        *sorter_column_count,\n                        *sort_cursor,\n                        *reg_sorter_key,\n                    );\n                }\n                GroupByRowSource::MainLoop { .. } => {\n                    for agg in plan.aggregates.iter() {\n                        for expr in agg.args.iter() {\n                            let agg_reg = cur_reg;\n                            cur_reg += 1;\n                            translate_expr(\n                                program,\n                                Some(&plan.table_references),\n                                expr,\n                                agg_reg,\n                                &t_ctx.resolver,\n                            )?;\n                        }\n                    }\n                    group_by_agg_phase(program, t_ctx, plan)?;\n                }\n            }\n\n            Ok(())\n        }\n        LoopEmitTarget::OrderBySorter => {\n            EmitOrderBy::sorter_insert(program, t_ctx, plan)?;\n\n            if let Distinctness::Distinct { ctx } = &plan.distinctness {\n                let distinct_ctx = ctx.as_ref().expect(\"distinct context must exist\");\n                program.preassign_label_to_next_insn(distinct_ctx.label_on_conflict);\n            }\n\n            Ok(())\n        }\n        LoopEmitTarget::AggStep => {\n            let start_reg = t_ctx\n                .reg_agg_start\n                .expect(\"aggregate registers must be initialized\");\n            if let Some(SimpleAggregate::MinMax(min_max)) = &plan.simple_aggregate {\n                let expr_reg = program.alloc_register();\n                translate_expr(\n                    program,\n                    Some(&plan.table_references),\n                    &min_max.argument,\n                    expr_reg,\n                    &t_ctx.resolver,\n                )?;\n                let loop_end = t_ctx\n                    .label_main_loop_end\n                    .expect(\"simple min/max requires the main-loop end label\");\n                let label_on_null = if matches!(min_max.func, crate::function::AggFunc::Min) {\n                    // Ascending index order places NULLs first. Keep scanning until\n                    // the first non-NULL value, then jump straight to AggFinal.\n                    let label_on_null = program.allocate_label();\n                    program.emit_insn(Insn::IsNull {\n                        reg: expr_reg,\n                        target_pc: label_on_null,\n                    });\n                    Some(label_on_null)\n                } else {\n                    None\n                };\n\n                emit_collseq_if_needed(program, &plan.table_references, &min_max.argument);\n                let comparator = custom_type_comparator(\n                    &min_max.argument,\n                    &plan.table_references,\n                    t_ctx.resolver.schema(),\n                );\n                program.emit_insn(Insn::AggStep {\n                    acc_reg: start_reg,\n                    col: expr_reg,\n                    delimiter: 0,\n                    func: min_max.func.clone(),\n                    comparator,\n                });\n                program.emit_insn(Insn::Goto {\n                    target_pc: loop_end,\n                });\n\n                if let Some(label_on_null) = label_on_null {\n                    program.preassign_label_to_next_insn(label_on_null);\n                }\n                return Ok(());\n            }\n\n            // In planner.rs, we have collected all aggregates from the SELECT clause, including ones where the aggregate is embedded inside\n            // a more complex expression. Some examples: length(sum(x)), sum(x) + avg(y), sum(x) + 1, etc.\n            // The result of those more complex expressions depends on the final result of the aggregate, so we don't translate the complete expressions here.\n            // Instead, we accumulate the intermediate results of all aggreagates, and evaluate any expressions that do not contain aggregates.\n            for (i, agg) in plan.aggregates.iter().enumerate() {\n                let reg = start_reg + i;\n                translate_aggregation_step(\n                    program,\n                    &plan.table_references,\n                    AggArgumentSource::new_from_expression(&agg.func, &agg.args, &agg.distinctness),\n                    reg,\n                    &t_ctx.resolver,\n                )?;\n                if let Distinctness::Distinct { ctx } = &agg.distinctness {\n                    let ctx = ctx\n                        .as_ref()\n                        .expect(\"distinct aggregate context not populated\");\n                    program.preassign_label_to_next_insn(ctx.label_on_conflict);\n                }\n            }\n\n            let label_emit_nonagg_only_once = if let Some(flag) = t_ctx.reg_nonagg_emit_once_flag {\n                let if_label = program.allocate_label();\n                program.emit_insn(Insn::If {\n                    reg: flag,\n                    target_pc: if_label,\n                    jump_if_null: false,\n                });\n                Some(if_label)\n            } else {\n                None\n            };\n\n            let col_start = t_ctx.reg_result_cols_start.unwrap();\n\n            // Process only non-aggregate columns\n            let non_agg_columns = plan\n                .result_columns\n                .iter()\n                .enumerate()\n                .filter(|(_, rc)| !rc.contains_aggregates);\n\n            for (i, rc) in non_agg_columns {\n                let reg = col_start + i;\n\n                // Must use no_constant_opt to prevent constant hoisting: in compound\n                // selects (UNION ALL), all branches share the same result registers,\n                // so hoisted constants from the last branch overwrite earlier branches.\n                translate_expr_no_constant_opt(\n                    program,\n                    Some(&plan.table_references),\n                    &rc.expr,\n                    reg,\n                    &t_ctx.resolver,\n                    NoConstantOptReason::RegisterReuse,\n                )?;\n            }\n\n            // For result columns that contain aggregates but also reference\n            // non-aggregate columns (e.g. CASE WHEN SUM(1) THEN a ELSE b END),\n            // pre-read those column references while the cursor is still valid.\n            // They are cached in expr_to_reg_cache so that when the full\n            // expression is evaluated after AggFinal, translate_expr finds\n            // the cached values instead of reading from the exhausted cursor.\n            for rc in plan\n                .result_columns\n                .iter()\n                .filter(|rc| rc.contains_aggregates)\n            {\n                walk_expr(&rc.expr, &mut |expr: &Expr| -> Result<WalkControl> {\n                    match expr {\n                        Expr::Column { .. } | Expr::RowId { .. } => {\n                            let reg = program.alloc_register();\n                            translate_expr(\n                                program,\n                                Some(&plan.table_references),\n                                expr,\n                                reg,\n                                &t_ctx.resolver,\n                            )?;\n                            t_ctx.resolver.cache_scalar_expr_reg(\n                                Cow::Owned(expr.clone()),\n                                reg,\n                                false,\n                                &plan.table_references,\n                            )?;\n                            Ok(WalkControl::SkipChildren)\n                        }\n                        _ => {\n                            if plan.aggregates.iter().any(|a| a.original_expr == *expr) {\n                                return Ok(WalkControl::SkipChildren);\n                            }\n                            Ok(WalkControl::Continue)\n                        }\n                    }\n                })?;\n            }\n\n            if let Some(label) = label_emit_nonagg_only_once {\n                program.resolve_label(label, program.offset());\n                let flag = t_ctx.reg_nonagg_emit_once_flag.unwrap();\n                program.emit_int(1, flag);\n            }\n\n            Ok(())\n        }\n        LoopEmitTarget::QueryResult => {\n            turso_assert!(\n                plan.aggregates.is_empty(),\n                \"QueryResult target should not have aggregates\"\n            );\n            let offset_jump_to = t_ctx\n                .labels_main_loop\n                .first()\n                .map(|l| l.next)\n                .or(t_ctx.label_main_loop_end);\n            emit_select_result(\n                program,\n                &t_ctx.resolver,\n                plan,\n                t_ctx.label_main_loop_end,\n                offset_jump_to,\n                t_ctx.reg_nonagg_emit_once_flag,\n                t_ctx.reg_offset,\n                t_ctx.reg_result_cols_start.unwrap(),\n                t_ctx.limit_ctx,\n            )?;\n\n            if let Distinctness::Distinct { ctx } = &plan.distinctness {\n                let distinct_ctx = ctx.as_ref().expect(\"distinct context must exist\");\n                program.preassign_label_to_next_insn(distinct_ctx.label_on_conflict);\n            }\n\n            Ok(())\n        }\n        LoopEmitTarget::Window => {\n            EmitWindow::emit_window_loop_source(program, t_ctx, plan)?;\n\n            Ok(())\n        }\n    }\n}\n\n/// Emit WHERE conditions and inner-loop entry for an unmatched outer hash join row.\n///\n/// Filters applicable WHERE terms (non-ON, non-consumed), optionally restricted to\n/// `build_table_idx` / `probe_table_idx` when a Gosub wraps inner tables. Then either\n/// enters the inner-loop subroutine via Gosub or calls `emit_loop` directly.\npub(super) fn emit_unmatched_row_conditions_and_loop<'a>(\n    program: &mut ProgramBuilder,\n    t_ctx: &mut TranslateCtx<'a>,\n    plan: &'a SelectPlan,\n    build_table_idx: usize,\n    probe_table_idx: usize,\n    skip_label: BranchOffset,\n    gosub: Option<(usize, BranchOffset)>,\n) -> Result<()> {\n    let has_gosub = gosub.is_some();\n    let allowed_tables = {\n        let mut m = TableMask::new();\n        m.add_table(build_table_idx);\n        m.add_table(probe_table_idx);\n        // When there's a gosub wrapping inner tables, we must also allow\n        // conditions that reference outer tables (those appearing before the\n        // hash join probe in the join order), since their cursors are valid\n        // at the unmatched-scan point.\n        if has_gosub {\n            let probe_pos = plan\n                .join_order\n                .iter()\n                .position(|j| j.original_idx == probe_table_idx)\n                .expect(\"probe table must be in join order\");\n            for join in &plan.join_order[..probe_pos] {\n                m.add_table(join.original_idx);\n            }\n        }\n        m\n    };\n    for cond in plan\n        .where_clause\n        .iter()\n        .filter(|c| !c.consumed && c.from_outer_join.is_none())\n        .filter(|c| {\n            !has_gosub || expr_tables_subset_of(&c.expr, &plan.table_references, &allowed_tables)\n        })\n    {\n        let jump_target_when_true = program.allocate_label();\n        let condition_metadata = ConditionMetadata {\n            jump_if_condition_is_true: false,\n            jump_target_when_true,\n            jump_target_when_false: skip_label,\n            jump_target_when_null: skip_label,\n        };\n        translate_condition_expr(\n            program,\n            &plan.table_references,\n            &cond.expr,\n            condition_metadata,\n            &t_ctx.resolver,\n        )?;\n        program.preassign_label_to_next_insn(jump_target_when_true);\n    }\n\n    if let Some((reg, label)) = gosub {\n        program.emit_insn(Insn::Gosub {\n            target_pc: label,\n            return_reg: reg,\n        });\n    } else {\n        emit_loop(program, t_ctx, plan)?;\n    }\n    Ok(())\n}\n"
  },
  {
    "path": "core/translate/main_loop/close.rs",
    "content": "use super::*;\nuse crate::translate::main_loop::hash::{\n    emit_hash_join_unmatched_build_rows, GraceHashLoop, HashProbeCloseEmitter,\n};\n\n/// Represents final step of Loop emission\npub struct CloseLoop;\n\nimpl CloseLoop {\n    pub fn emit<'a>(\n        program: &mut ProgramBuilder,\n        t_ctx: &mut TranslateCtx<'a>,\n        tables: &TableReferences,\n        join_order: &[JoinOrderMember],\n        mode: OperationMode,\n        select_plan: Option<&'a SelectPlan>,\n    ) -> Result<()> {\n        // We close the loops for all tables in reverse order, i.e. innermost first.\n        // OPEN t1\n        //   OPEN t2\n        //     OPEN t3\n        //       <do stuff>\n        //     CLOSE t3\n        //   CLOSE t2\n        // CLOSE t1\n        for join in join_order.iter().rev() {\n            let table_index = join.original_idx;\n            let table = &tables.joined_tables()[table_index];\n            let loop_labels = *t_ctx\n                .labels_main_loop\n                .get(table_index)\n                .expect(\"source has no loop labels\");\n\n            // SEMI/ANTI-JOIN: emit Goto -> outer_next right after the body.\n            // For semi-join: after body runs (one match found), skip inner's Next.\n            // For anti-join: after body runs (inner exhausted), move to next outer row.\n            let is_semi_or_anti = table\n                .join_info\n                .as_ref()\n                .is_some_and(|ji| ji.is_semi_or_anti());\n            if is_semi_or_anti {\n                let sa_meta = t_ctx.meta_semi_anti_joins[table_index]\n                    .as_ref()\n                    .expect(\"semi/anti-join must have SemiAntiJoinMetadata\");\n                let comment = if table.join_info.as_ref().unwrap().is_semi() {\n                    \"semi-join: early out after first match\"\n                } else {\n                    \"anti-join: exit body, next outer row\"\n                };\n                program.add_comment(program.offset(), comment);\n                program.emit_insn(Insn::Goto {\n                    target_pc: sa_meta.label_next_outer,\n                });\n            }\n\n            let (table_cursor_id, index_cursor_id) =\n                table.resolve_cursors(program, mode.clone())?;\n            // Track the \"next iteration\" offset for semi/anti-join label resolution.\n            // For most operations this equals the loop_labels.next resolution offset;\n            // HashJoin overrides it to point at the Gosub Return or HashNext instead.\n            let mut semi_anti_next_pc = None;\n            // Helper: resolve loop_labels.next and record its offset for semi/anti-join.\n            let mut resolve_next = |program: &mut ProgramBuilder| {\n                let pc = program.offset();\n                program.resolve_label(loop_labels.next, pc);\n                semi_anti_next_pc = Some(pc);\n            };\n            match &table.op {\n                Operation::Scan(scan) => {\n                    resolve_next(program);\n                    match scan {\n                        Scan::BTreeTable { iter_dir, .. } => {\n                            let iteration_cursor_id = if let OperationMode::UPDATE(\n                                UpdateRowSource::PrebuiltEphemeralTable {\n                                    ephemeral_table_cursor_id,\n                                    ..\n                                },\n                            ) = &mode\n                            {\n                                *ephemeral_table_cursor_id\n                            } else {\n                                index_cursor_id.unwrap_or_else(|| {\n                                    table_cursor_id.expect(\n                                        \"Either ephemeral or index or table cursor must be opened\",\n                                    )\n                                })\n                            };\n                            if *iter_dir == IterationDirection::Backwards {\n                                program.emit_insn(Insn::Prev {\n                                    cursor_id: iteration_cursor_id,\n                                    pc_if_prev: loop_labels.loop_start,\n                                });\n                            } else {\n                                program.emit_insn(Insn::Next {\n                                    cursor_id: iteration_cursor_id,\n                                    pc_if_next: loop_labels.loop_start,\n                                });\n                            }\n                        }\n                        Scan::VirtualTable { .. } => {\n                            program.emit_insn(Insn::VNext {\n                                cursor_id: table_cursor_id\n                                    .expect(\"Virtual tables do not support covering indexes\"),\n                                pc_if_next: loop_labels.loop_start,\n                            });\n                        }\n                        Scan::Subquery { iter_dir } => {\n                            // Check if this is a materialized CTE (EphemeralTable) or coroutine\n                            if let Table::FromClauseSubquery(subquery) = &table.table {\n                                if let Some(QueryDestination::EphemeralTable {\n                                    cursor_id, ..\n                                }) = subquery.plan.select_query_destination()\n                                {\n                                    if *iter_dir == IterationDirection::Backwards {\n                                        program.emit_insn(Insn::Prev {\n                                            cursor_id: *cursor_id,\n                                            pc_if_prev: loop_labels.loop_start,\n                                        });\n                                    } else {\n                                        program.emit_insn(Insn::Next {\n                                            cursor_id: *cursor_id,\n                                            pc_if_next: loop_labels.loop_start,\n                                        });\n                                    }\n                                } else {\n                                    turso_assert_eq!(\n                                        *iter_dir,\n                                        IterationDirection::Forwards,\n                                        \"coroutine-backed subqueries cannot scan backwards\"\n                                    );\n                                    // Coroutine-based subquery - use Goto to Yield\n                                    program.emit_insn(Insn::Goto {\n                                        target_pc: loop_labels.loop_start,\n                                    });\n                                }\n                            } else {\n                                // A subquery has no cursor to call Next on, so it just emits a Goto\n                                // to the Yield instruction, which in turn jumps back to the main loop of the subquery,\n                                // so that the next row from the subquery can be read.\n                                program.emit_insn(Insn::Goto {\n                                    target_pc: loop_labels.loop_start,\n                                });\n                            }\n                        }\n                    }\n                    program.preassign_label_to_next_insn(loop_labels.loop_end);\n                }\n                Operation::Search(search) => {\n                    // Materialized subqueries with ephemeral indexes are allowed\n                    let is_materialized_subquery = matches!(\n                        &table.table,\n                        Table::FromClauseSubquery(_)\n                    ) && matches!(search, Search::Seek { index: Some(idx), .. } if idx.ephemeral);\n                    turso_assert_some!(\n                        {\n                            is_from_clause: !matches!(table.table, Table::FromClauseSubquery(_)),\n                            is_materialized_subquery: is_materialized_subquery\n                        },\n                        \"Subqueries do not support index seeks unless materialized\"\n                    );\n                    resolve_next(program);\n                    let iteration_cursor_id =\n                        if let OperationMode::UPDATE(UpdateRowSource::PrebuiltEphemeralTable {\n                            ephemeral_table_cursor_id,\n                            ..\n                        }) = &mode\n                        {\n                            *ephemeral_table_cursor_id\n                        } else if is_materialized_subquery {\n                            // Table-backed materialized subquery seeks iterate the\n                            // auxiliary ephemeral index cursor.\n                            index_cursor_id.expect(\"materialized subquery must have index cursor\")\n                        } else {\n                            index_cursor_id.unwrap_or_else(|| {\n                                table_cursor_id.expect(\n                                    \"Either ephemeral or index or table cursor must be opened\",\n                                )\n                            })\n                        };\n                    // Rowid equality point lookups are handled with a SeekRowid instruction which does not loop, so there is no need to emit a Next instruction.\n                    match search {\n                        Search::RowidEq { .. } => {}\n                        Search::Seek { seek_def, .. } => {\n                            if seek_def.iter_dir == IterationDirection::Backwards {\n                                program.emit_insn(Insn::Prev {\n                                    cursor_id: iteration_cursor_id,\n                                    pc_if_prev: loop_labels.loop_start,\n                                });\n                            } else {\n                                program.emit_insn(Insn::Next {\n                                    cursor_id: iteration_cursor_id,\n                                    pc_if_next: loop_labels.loop_start,\n                                });\n                            }\n                        }\n                        Search::InSeek { index, .. } => {\n                            let meta = t_ctx.meta_in_seeks[table_index]\n                                .as_ref()\n                                .expect(\"InSeek must have metadata\");\n                            let ephemeral_cursor_id = meta.ephemeral_cursor_id;\n                            let outer_loop_start = meta.outer_loop_start;\n                            let next_val_label = meta.next_val_label;\n\n                            let can_have_multiple_matches = index.is_some();\n                            if can_have_multiple_matches {\n                                // Rowid InSeek uses SeekRowid, so one RHS key can produce at\n                                // most one row. Index-backed InSeek can hit duplicates, so\n                                // keep scanning the current key's match range before advancing\n                                // the ephemeral cursor to the next IN value.\n                                program.emit_insn(Insn::Next {\n                                    cursor_id: iteration_cursor_id,\n                                    pc_if_next: loop_labels.loop_start,\n                                });\n                            }\n\n                            // Once the current key is exhausted (or a seek found nothing),\n                            // advance the outer ephemeral cursor and restart the equality seek.\n                            program.resolve_label(next_val_label, program.offset());\n                            program.emit_insn(Insn::Next {\n                                cursor_id: ephemeral_cursor_id,\n                                pc_if_next: outer_loop_start,\n                            });\n                        }\n                    }\n                    program.preassign_label_to_next_insn(loop_labels.loop_end);\n                }\n                Operation::IndexMethodQuery(_) => {\n                    resolve_next(program);\n                    program.emit_insn(Insn::Next {\n                        cursor_id: index_cursor_id.unwrap(),\n                        pc_if_next: loop_labels.loop_start,\n                    });\n                    program.preassign_label_to_next_insn(loop_labels.loop_end);\n                }\n                Operation::HashJoin(ref hash_join_op) => {\n                    if let Some(hash_ctx) = t_ctx\n                        .hash_table_contexts\n                        .get(&hash_join_op.build_table_idx)\n                        .cloned()\n                    {\n                        // Emit the close-loop teardown for a hash-join probe table.\n                        semi_anti_next_pc = HashProbeCloseEmitter::new(\n                            program,\n                            t_ctx,\n                            hash_join_op,\n                            hash_ctx,\n                            select_plan,\n                            table_index,\n                        )\n                        .emit()?\n                        .semi_anti_next_pc;\n                    }\n\n                    // Advance probe cursor.\n                    program.resolve_label(loop_labels.next, program.offset());\n                    let probe_cursor_id = table_cursor_id.expect(\"Probe table must have a cursor\");\n                    program.emit_insn(Insn::Next {\n                        cursor_id: probe_cursor_id,\n                        pc_if_next: loop_labels.loop_start,\n                    });\n                    program.preassign_label_to_next_insn(loop_labels.loop_end);\n\n                    // Outer joins: emit unmatched build rows with NULLs for the probe side.\n                    // This runs BEFORE grace so that in-memory partitions (with valid\n                    // matched_bits from the main probe) are scanned while still available.\n                    // At runtime, the scan skips spilled partitions — those are handled\n                    // per-partition inside the grace loop where matched_bits are still live.\n                    if matches!(\n                        hash_join_op.join_type,\n                        HashJoinType::LeftOuter | HashJoinType::FullOuter\n                    ) {\n                        if let Some(hash_ctx) = t_ctx\n                            .hash_table_contexts\n                            .get(&hash_join_op.build_table_idx)\n                            .cloned()\n                        {\n                            emit_hash_join_unmatched_build_rows(\n                                program,\n                                t_ctx,\n                                hash_join_op,\n                                &hash_ctx,\n                                select_plan,\n                                table_index,\n                                probe_cursor_id,\n                            )?;\n                        }\n                    }\n\n                    // Grace hash join processing: process spilled partition pairs.\n                    // At runtime, this is a no-op if the build side didn't spill.\n                    // For LEFT/FULL OUTER, each grace partition gets its own unmatched\n                    // scan before eviction (so matched_bits are still live).\n                    if let Some(hash_ctx) = t_ctx\n                        .hash_table_contexts\n                        .get(&hash_join_op.build_table_idx)\n                        .cloned()\n                    {\n                        // emit grace processing loop after the probe cursor is exhausted.\n                        GraceHashLoop::emit(\n                            program,\n                            t_ctx,\n                            hash_join_op,\n                            &hash_ctx,\n                            select_plan,\n                            table_index,\n                            probe_cursor_id,\n                        )?;\n                    }\n                }\n                Operation::MultiIndexScan(_) => {\n                    // MultiIndexScan uses RowSetRead for iteration - the next is handled\n                    // at the end of the RowSet read loop in emit_multi_index_scan_loop\n                    resolve_next(program);\n                    program.emit_insn(Insn::Goto {\n                        target_pc: loop_labels.loop_start,\n                    });\n                    program.preassign_label_to_next_insn(loop_labels.loop_end);\n                }\n            }\n\n            // Resolve any semi/anti-join \"outer next\" labels targeting this table.\n            if let Some(pc) = semi_anti_next_pc {\n                for meta in t_ctx.meta_semi_anti_joins.iter().flatten() {\n                    if meta.outer_table_idx == table_index {\n                        program.resolve_label(meta.label_next_outer, pc);\n                    }\n                }\n            }\n\n            // SEMI/ANTI-JOIN: after loop_end (inner loop exhausted).\n            // Semi-join: no match found -> skip outer row (Goto -> next_outer).\n            // Anti-join: no match found -> run body (Goto -> label_body, jumps backward).\n            if is_semi_or_anti {\n                let sa_meta = t_ctx.meta_semi_anti_joins[table_index]\n                    .as_ref()\n                    .expect(\"semi/anti-join must have SemiAntiJoinMetadata\");\n                let join_info = table.join_info.as_ref().unwrap();\n                if join_info.is_semi() {\n                    program.add_comment(program.offset(), \"semi-join: no match, skip outer row\");\n                    program.emit_insn(Insn::Goto {\n                        target_pc: sa_meta.label_next_outer,\n                    });\n                } else {\n                    // Anti-join: inner exhausted without match -> run body\n                    program.add_comment(program.offset(), \"anti-join: no match, emit outer row\");\n                    program.emit_insn(Insn::Goto {\n                        target_pc: sa_meta.label_body,\n                    });\n                }\n            }\n\n            // OUTER JOIN: may still need to emit NULLs for the right table.\n            // Outer hash join probes are handled above via check_outer / unmatched scan.\n            let is_outer_hash_join_probe = matches!(\n                table.op,\n                Operation::HashJoin(ref hj) if matches!(\n                    hj.join_type,\n                    HashJoinType::LeftOuter | HashJoinType::FullOuter\n                )\n            );\n            if let Some(join_info) = table.join_info.as_ref() {\n                if join_info.is_outer() && !is_outer_hash_join_probe {\n                    let lj_meta = t_ctx.meta_left_joins[table_index].as_ref().unwrap();\n                    // The left join match flag is set to 1 when there is any match on the right table\n                    // (e.g. SELECT * FROM t1 LEFT JOIN t2 ON t1.a = t2.a).\n                    // If the left join match flag has been set to 1, we jump to the next row on the outer table,\n                    // i.e. continue to the next row of t1 in our example.\n                    program.resolve_label(lj_meta.label_match_flag_check_value, program.offset());\n                    let label_when_right_table_notnull = program.allocate_label();\n                    program.emit_insn(Insn::IfPos {\n                        reg: lj_meta.reg_match_flag,\n                        target_pc: label_when_right_table_notnull,\n                        decrement_by: 0,\n                    });\n                    // If the left join match flag is still 0, it means there was no match on the right table,\n                    // but since it's a LEFT JOIN, we still need to emit a row with NULLs for the right table.\n                    // In that case, we now enter the routine that does exactly that.\n                    // First we set the right table cursor's \"pseudo null bit\" on, which means any Insn::Column will return NULL.\n                    // This needs to be set for both the table and the index cursor, if present,\n                    // since even if the iteration cursor is the index cursor, it might fetch values from the table cursor.\n                    [table_cursor_id, index_cursor_id]\n                        .iter()\n                        .filter_map(|maybe_cursor_id| maybe_cursor_id.as_ref())\n                        .for_each(|cursor_id| {\n                            program.emit_insn(Insn::NullRow {\n                                cursor_id: *cursor_id,\n                            });\n                        });\n                    if let Table::FromClauseSubquery(from_clause_subquery) = &table.table {\n                        if let Some(start_reg) = from_clause_subquery.result_columns_start_reg {\n                            let column_count = from_clause_subquery.columns.len();\n                            if column_count > 0 {\n                                // Subqueries materialize their row into registers rather than being read back\n                                // through a cursor. NullRow only affects cursor reads, so we also have to\n                                // explicitly null out the cached registers or stale values would be re-emitted.\n                                program.emit_insn(Insn::Null {\n                                    dest: start_reg,\n                                    dest_end: Some(start_reg + column_count - 1),\n                                });\n                            }\n                        }\n                    }\n                    // Re-enter the loop body at match-flag set so\n                    // post-join predicates are re-evaluated with right-table NULLs.\n                    program.emit_insn(Insn::Goto {\n                        target_pc: lj_meta.label_match_flag_set_true,\n                    });\n                    program.preassign_label_to_next_insn(label_when_right_table_notnull);\n                }\n            }\n        }\n\n        // After ALL loops are closed, emit HashClose for any hash tables that were built.\n        // This must happen at the very end because hash join probe loops may be nested\n        // inside outer loops that re-enter them. Hash tables used by materialization\n        // subplans can be kept open and are skipped here.\n        //\n        // When inside a nested subquery (correlated or non-correlated), skip HashClose\n        // because the hash build is protected by Once and must persist across subquery\n        // re-invocations. The hash table will be cleaned up by ProgramState::reset().\n        if !program.is_nested() {\n            for join in join_order.iter() {\n                let table_index = join.original_idx;\n                let table = &tables.joined_tables()[table_index];\n                if let Operation::HashJoin(hash_join_op) = &table.op {\n                    let build_table = &tables.joined_tables()[hash_join_op.build_table_idx];\n                    let hash_table_reg: usize = build_table.internal_id.into();\n                    if !program.should_keep_hash_table_open(hash_table_reg) {\n                        program.emit_insn(Insn::HashClose {\n                            hash_table_id: hash_table_reg,\n                        });\n                        program.clear_hash_build_signature(hash_table_reg);\n                    }\n                }\n            }\n        }\n\n        Ok(())\n    }\n}\n\npub(super) struct AutoIndexResult {\n    pub(super) use_bloom_filter: bool,\n}\n\npub(super) struct AutoIndexBuild<'a> {\n    pub(super) index: &'a Arc<Index>,\n    pub(super) table_cursor_id: CursorID,\n    pub(super) index_cursor_id: CursorID,\n    pub(super) table_has_rowid: bool,\n    pub(super) num_seek_keys: usize,\n    pub(super) seek_def: &'a SeekDef,\n    pub(super) affinity_str: Option<&'a Arc<String>>,\n}\n\n/// Open an ephemeral index cursor and build an automatic index on a table.\n/// This is used as a last-resort to avoid a nested full table scan\n/// Returns the cursor id of the ephemeral index cursor.\npub(super) fn emit_autoindex(\n    program: &mut ProgramBuilder,\n    build: AutoIndexBuild<'_>,\n) -> Result<AutoIndexResult> {\n    let AutoIndexBuild {\n        index,\n        table_cursor_id,\n        index_cursor_id,\n        table_has_rowid,\n        num_seek_keys,\n        seek_def,\n        affinity_str,\n    } = build;\n    turso_assert!(index.ephemeral, \"index must be ephemeral\", { \"index_name\": &index.name });\n    let label_ephemeral_build_end = program.allocate_label();\n    // Since this typically happens in an inner loop, we only build it once.\n    program.emit_insn(Insn::Once {\n        target_pc_when_reentered: label_ephemeral_build_end,\n    });\n    program.emit_insn(Insn::OpenAutoindex {\n        cursor_id: index_cursor_id,\n    });\n    // Rewind source table\n    let label_ephemeral_build_loop_start = program.allocate_label();\n    program.emit_insn(Insn::Rewind {\n        cursor_id: table_cursor_id,\n        pc_if_empty: label_ephemeral_build_loop_start,\n    });\n    program.preassign_label_to_next_insn(label_ephemeral_build_loop_start);\n    // Emit all columns from source table that are needed in the ephemeral index.\n    // Also reserve a register for the rowid if the source table has rowids.\n    let num_regs_to_reserve = index.columns.len() + table_has_rowid as usize;\n    let ephemeral_cols_start_reg = program.alloc_registers(num_regs_to_reserve);\n    for (i, col) in index.columns.iter().enumerate() {\n        let reg = ephemeral_cols_start_reg + i;\n        program.emit_column_or_rowid(table_cursor_id, col.pos_in_table, reg);\n    }\n    if table_has_rowid {\n        program.emit_insn(Insn::RowId {\n            cursor_id: table_cursor_id,\n            dest: ephemeral_cols_start_reg + index.columns.len(),\n        });\n    }\n    let record_reg = program.alloc_register();\n    program.emit_insn(Insn::MakeRecord {\n        start_reg: to_u16(ephemeral_cols_start_reg),\n        count: to_u16(num_regs_to_reserve),\n        dest_reg: to_u16(record_reg),\n        index_name: Some(index.name.clone()),\n        affinity_str: affinity_str.map(|s| (**s).clone()),\n    });\n    // Skip bloom filter for non-binary collations since it uses binary hashing.\n    let use_bloom_filter = index.columns.iter().take(num_seek_keys).all(|col| {\n        col.collation\n            .is_none_or(|coll| matches!(coll, CollationSeq::Binary | CollationSeq::Unset))\n    }) && seek_def.start.op.eq_only();\n    if use_bloom_filter {\n        program.emit_insn(Insn::FilterAdd {\n            cursor_id: index_cursor_id,\n            key_reg: ephemeral_cols_start_reg,\n            num_keys: num_seek_keys,\n        });\n    }\n    program.emit_insn(Insn::IdxInsert {\n        cursor_id: index_cursor_id,\n        record_reg,\n        unpacked_start: Some(ephemeral_cols_start_reg),\n        unpacked_count: Some(num_regs_to_reserve as u16),\n        flags: IdxInsertFlags::new().use_seek(false),\n    });\n    program.emit_insn(Insn::Next {\n        cursor_id: table_cursor_id,\n        pc_if_next: label_ephemeral_build_loop_start,\n    });\n    program.preassign_label_to_next_insn(label_ephemeral_build_end);\n    Ok(AutoIndexResult { use_bloom_filter })\n}\n"
  },
  {
    "path": "core/translate/main_loop/conditions.rs",
    "content": "use super::*;\nuse crate::translate::subquery::emit_non_from_clause_subqueries_for_eval_at;\n\nfn condition_references_subquery(expr: &Expr, subqueries: &[NonFromClauseSubquery]) -> bool {\n    subqueries\n        .iter()\n        .any(|s| expr_references_subquery_id(expr, s.internal_id))\n}\n\nfn subquery_referenced_in_predicates(\n    predicates: &[WhereTerm],\n    from_outer_join: bool,\n    subquery_id: TableInternalId,\n) -> bool {\n    predicates\n        .iter()\n        .filter(|cond| cond.from_outer_join.is_some() == from_outer_join)\n        .any(|cond| expr_references_subquery_id(&cond.expr, subquery_id))\n}\n\n#[allow(clippy::too_many_arguments)]\nfn emit_correlated_subqueries(\n    program: &mut ProgramBuilder,\n    resolver: &Resolver<'_>,\n    table_references: &TableReferences,\n    join_order: &[JoinOrderMember],\n    join_index: usize,\n    predicates: &[WhereTerm],\n    subqueries: &mut [NonFromClauseSubquery],\n    on_only: bool,\n) -> Result<()> {\n    emit_non_from_clause_subqueries_for_eval_at(\n        program,\n        resolver,\n        subqueries,\n        join_order,\n        Some(table_references),\n        EvalAt::Loop(join_index),\n        |subquery| {\n            subquery.correlated\n                && (!on_only\n                    || subquery_referenced_in_predicates(predicates, true, subquery.internal_id))\n        },\n    )\n}\n\n#[derive(Clone, Copy, PartialEq, Eq)]\nenum SubqueryRefFilter {\n    WithoutSubqueryRefs,\n    WithSubqueryRefs,\n}\n\n#[allow(clippy::too_many_arguments)]\nfn emit_conditions(\n    program: &mut ProgramBuilder,\n    t_ctx: &TranslateCtx<'_>,\n    table_references: &TableReferences,\n    join_order: &[JoinOrderMember],\n    predicates: &[WhereTerm],\n    join_index: usize,\n    next: BranchOffset,\n    from_outer_join: bool,\n    subqueries: &[NonFromClauseSubquery],\n    subquery_ref_filter: SubqueryRefFilter,\n) -> Result<()> {\n    for cond in predicates\n        .iter()\n        .filter(|cond| cond.from_outer_join.is_some() == from_outer_join)\n        .filter(|cond| {\n            cond.should_eval_at_loop(join_index, join_order, subqueries, Some(table_references))\n        })\n        .filter(|cond| match subquery_ref_filter {\n            SubqueryRefFilter::WithoutSubqueryRefs => {\n                !condition_references_subquery(&cond.expr, subqueries)\n            }\n            SubqueryRefFilter::WithSubqueryRefs => {\n                condition_references_subquery(&cond.expr, subqueries)\n            }\n        })\n    {\n        let jump_target_when_true = program.allocate_label();\n        let condition_metadata = ConditionMetadata {\n            jump_if_condition_is_true: false,\n            jump_target_when_true,\n            jump_target_when_false: next,\n            jump_target_when_null: next,\n        };\n        translate_condition_expr(\n            program,\n            table_references,\n            &cond.expr,\n            condition_metadata,\n            &t_ctx.resolver,\n        )?;\n        program.preassign_label_to_next_insn(jump_target_when_true);\n    }\n\n    Ok(())\n}\n\n/// Per-loop predicate emission.\n///\n/// Conditions that reference subquery results cannot be emitted until their\n/// correlated subqueries have run, so emission proceeds in three ordered steps.\npub(super) struct LoopConditionEmitter<'a, 'ctx> {\n    program: &'a mut ProgramBuilder,\n    t_ctx: &'a TranslateCtx<'ctx>,\n    table_references: &'a TableReferences,\n    join_order: &'a [JoinOrderMember],\n    predicates: &'a [WhereTerm],\n    join_index: usize,\n    condition_fail_target: BranchOffset,\n    from_outer_join: bool,\n    subqueries: &'a mut [NonFromClauseSubquery],\n}\n\nimpl<'a, 'ctx> LoopConditionEmitter<'a, 'ctx> {\n    #[allow(clippy::too_many_arguments)]\n    pub(super) const fn new(\n        program: &'a mut ProgramBuilder,\n        t_ctx: &'a TranslateCtx<'ctx>,\n        table_references: &'a TableReferences,\n        join_order: &'a [JoinOrderMember],\n        predicates: &'a [WhereTerm],\n        join_index: usize,\n        condition_fail_target: BranchOffset,\n        from_outer_join: bool,\n        subqueries: &'a mut [NonFromClauseSubquery],\n    ) -> Self {\n        Self {\n            program,\n            t_ctx,\n            table_references,\n            join_order,\n            predicates,\n            join_index,\n            condition_fail_target,\n            from_outer_join,\n            subqueries,\n        }\n    }\n\n    /// Emit predicates that do not depend on subquery result registers.\n    fn emit_early_conditions(&mut self) -> Result<()> {\n        emit_conditions(\n            self.program,\n            self.t_ctx,\n            self.table_references,\n            self.join_order,\n            self.predicates,\n            self.join_index,\n            self.condition_fail_target,\n            self.from_outer_join,\n            self.subqueries,\n            SubqueryRefFilter::WithoutSubqueryRefs,\n        )\n    }\n\n    /// Materialize correlated subqueries that become valid at this loop depth.\n    fn emit_correlated_subqueries(&mut self) -> Result<()> {\n        emit_correlated_subqueries(\n            self.program,\n            &self.t_ctx.resolver,\n            self.table_references,\n            self.join_order,\n            self.join_index,\n            self.predicates,\n            self.subqueries,\n            self.from_outer_join,\n        )\n    }\n\n    /// Emit predicates that read registers populated by correlated subqueries.\n    fn emit_late_conditions(&mut self) -> Result<()> {\n        emit_conditions(\n            self.program,\n            self.t_ctx,\n            self.table_references,\n            self.join_order,\n            self.predicates,\n            self.join_index,\n            self.condition_fail_target,\n            self.from_outer_join,\n            self.subqueries,\n            SubqueryRefFilter::WithSubqueryRefs,\n        )\n    }\n\n    pub(super) fn emit(mut self) -> Result<()> {\n        self.emit_early_conditions()?;\n        self.emit_correlated_subqueries()?;\n        self.emit_late_conditions()\n    }\n}\n"
  },
  {
    "path": "core/translate/main_loop/hash.rs",
    "content": "use crate::translate::emitter::HashLabels;\n\nuse super::*;\n\n#[derive(Debug, Clone)]\n/// Payload layout metadata recorded during hash-build planning or reuse.\npub(super) struct HashBuildPayloadInfo {\n    pub payload_columns: Vec<MaterializedColumnRef>,\n    pub key_affinities: String,\n    pub use_bloom_filter: bool,\n    pub bloom_filter_cursor_id: CursorID,\n    pub allow_seek: bool,\n}\n\nfn expr_references_outer_query(expr: &Expr, table_references: &TableReferences) -> bool {\n    let mut has_outer_ref = false;\n    let _ = walk_expr(expr, &mut |e: &Expr| -> Result<WalkControl> {\n        match e {\n            Expr::Column { table, .. } | Expr::RowId { table, .. } => {\n                if table_references\n                    .find_outer_query_ref_by_internal_id(*table)\n                    .is_some()\n                {\n                    has_outer_ref = true;\n                }\n            }\n            _ => {}\n        }\n        Ok(WalkControl::Continue)\n    });\n    has_outer_ref\n}\n\n/// Static configuration for a fresh hash-table build.\nstruct HashBuildConfig {\n    payload_columns: Vec<MaterializedColumnRef>,\n    payload_signature_columns: Vec<usize>,\n    key_affinities: String,\n    collations: Vec<CollationSeq>,\n    use_bloom_filter: bool,\n    bloom_filter_cursor_id: CursorID,\n    materialized_cursor_id: Option<CursorID>,\n    use_materialized_keys: bool,\n    allow_seek: bool,\n    signature: HashBuildSignature,\n}\n\n/// Typestate entry point for hash-build planning.\n///\n/// Planning decides whether an existing hash build can be reused and, if not,\n/// captures all configuration needed to emit a fresh build deterministically.\npub(crate) struct HashBuildPlanner<'a, 'plan> {\n    program: &'a mut ProgramBuilder,\n    t_ctx: &'a mut TranslateCtx<'plan>,\n    table_references: &'a TableReferences,\n    non_from_clause_subqueries: &'a [NonFromClauseSubquery],\n    predicates: &'a [WhereTerm],\n    hash_join_op: &'a HashJoinOp,\n    hash_build_cursor_id: CursorID,\n    hash_table_id: usize,\n}\n\n/// A planned hash build whose signature check has already completed.\npub(super) struct PreparedHashBuild<'a, 'plan> {\n    planner: HashBuildPlanner<'a, 'plan>,\n    config: HashBuildConfig,\n}\n\n/// Result of hash-build planning.\n///\n/// Reuse means the caller can immediately probe an existing compatible hash\n/// table. Build means the caller must execute the prepared build before probing.\npub(super) enum HashBuildPlan<'a, 'plan> {\n    Reuse(HashBuildPayloadInfo),\n    Build(Box<PreparedHashBuild<'a, 'plan>>),\n}\n\nimpl<'a, 'plan> HashBuildPlanner<'a, 'plan> {\n    #[allow(clippy::too_many_arguments)]\n    /// Capture the immutable inputs needed to decide whether to reuse or build.\n    pub(super) const fn new(\n        program: &'a mut ProgramBuilder,\n        t_ctx: &'a mut TranslateCtx<'plan>,\n        table_references: &'a TableReferences,\n        non_from_clause_subqueries: &'a [NonFromClauseSubquery],\n        predicates: &'a [WhereTerm],\n        hash_join_op: &'a HashJoinOp,\n        hash_build_cursor_id: CursorID,\n        hash_table_id: usize,\n    ) -> Self {\n        Self {\n            program,\n            t_ctx,\n            table_references,\n            non_from_clause_subqueries,\n            predicates,\n            hash_join_op,\n            hash_build_cursor_id,\n            hash_table_id,\n        }\n    }\n\n    /// Decide whether the hash table can be reused or must be rebuilt.\n    pub(super) fn prepare(self) -> Result<HashBuildPlan<'a, 'plan>> {\n        let materialized_input = self\n            .t_ctx\n            .materialized_build_inputs\n            .get(&self.hash_join_op.build_table_idx);\n        let materialized_cursor_id = materialized_input.map(|input| input.cursor_id);\n        let num_keys = self.hash_join_op.join_keys.len();\n\n        let mut key_affinities = String::new();\n        for join_key in &self.hash_join_op.join_keys {\n            let build_expr = join_key.get_build_expr(self.predicates);\n            let probe_expr = join_key.get_probe_expr(self.predicates);\n            let affinity =\n                comparison_affinity(build_expr, probe_expr, Some(self.table_references), None);\n            key_affinities.push(affinity.aff_mask());\n        }\n\n        let collations: Vec<CollationSeq> = self\n            .hash_join_op\n            .join_keys\n            .iter()\n            .map(|join_key| {\n                let (original_lhs, original_rhs) = match join_key.build_side {\n                    BinaryExprSide::Lhs => (\n                        join_key.get_build_expr(self.predicates),\n                        join_key.get_probe_expr(self.predicates),\n                    ),\n                    BinaryExprSide::Rhs => (\n                        join_key.get_probe_expr(self.predicates),\n                        join_key.get_build_expr(self.predicates),\n                    ),\n                };\n                resolve_comparison_collseq(original_lhs, original_rhs, self.table_references)\n                    .unwrap_or(CollationSeq::Binary)\n            })\n            .collect();\n\n        let use_bloom_filter = self.hash_join_op.use_bloom_filter\n            && collations\n                .iter()\n                .all(|c| matches!(c, CollationSeq::Binary | CollationSeq::Unset));\n\n        let build_table = &self.table_references.joined_tables()[self.hash_join_op.build_table_idx];\n        let (payload_columns, payload_signature_columns, use_materialized_keys, allow_seek) =\n            match materialized_input.map(|input| &input.mode) {\n                Some(MaterializedBuildInputMode::KeyPayload {\n                    num_keys: payload_num_keys,\n                    payload_columns,\n                }) => {\n                    turso_assert!(\n                        *payload_num_keys == num_keys,\n                        \"materialized hash build input key count mismatch\"\n                    );\n                    let payload_signature_columns = (0..payload_columns.len())\n                        .map(|i| *payload_num_keys + i)\n                        .collect();\n                    (\n                        payload_columns.clone(),\n                        payload_signature_columns,\n                        true,\n                        false,\n                    )\n                }\n                _ => {\n                    let payload_signature_columns: Vec<usize> =\n                        build_table.col_used_mask.iter().collect();\n                    let payload_columns = payload_signature_columns\n                        .iter()\n                        .map(|col_idx| {\n                            let column = build_table\n                                .columns()\n                                .get(*col_idx)\n                                .expect(\"build table column missing\");\n                            MaterializedColumnRef::Column {\n                                table_id: build_table.internal_id,\n                                column_idx: *col_idx,\n                                is_rowid_alias: column.is_rowid_alias(),\n                            }\n                        })\n                        .collect();\n                    (payload_columns, payload_signature_columns, false, true)\n                }\n            };\n\n        let bloom_filter_cursor_id = if use_materialized_keys {\n            materialized_cursor_id.expect(\"materialized input cursor is required\")\n        } else {\n            self.hash_build_cursor_id\n        };\n\n        let join_key_indices = self\n            .hash_join_op\n            .join_keys\n            .iter()\n            .map(|key| key.where_clause_idx)\n            .collect::<Vec<_>>();\n        let signature = HashBuildSignature {\n            join_key_indices,\n            payload_refs: payload_columns.clone(),\n            key_affinities: key_affinities.clone(),\n            use_bloom_filter,\n            materialized_input_cursor: materialized_cursor_id,\n            materialized_mode: materialized_input.as_ref().map(|input| match input.mode {\n                MaterializedBuildInputMode::RowidOnly => MaterializedBuildInputModeTag::RowidOnly,\n                MaterializedBuildInputMode::KeyPayload { .. } => {\n                    MaterializedBuildInputModeTag::Payload\n                }\n            }),\n        };\n\n        if self\n            .program\n            .hash_build_signature_matches(self.hash_table_id, &signature)\n        {\n            return Ok(HashBuildPlan::Reuse(HashBuildPayloadInfo {\n                payload_columns,\n                key_affinities,\n                use_bloom_filter,\n                bloom_filter_cursor_id,\n                allow_seek,\n            }));\n        }\n        if self.program.has_hash_build_signature(self.hash_table_id) {\n            self.program.emit_insn(Insn::HashClose {\n                hash_table_id: self.hash_table_id,\n            });\n            self.program.clear_hash_build_signature(self.hash_table_id);\n        }\n\n        Ok(HashBuildPlan::Build(Box::new(PreparedHashBuild {\n            planner: self,\n            config: HashBuildConfig {\n                payload_columns,\n                payload_signature_columns,\n                key_affinities,\n                collations,\n                use_bloom_filter,\n                bloom_filter_cursor_id,\n                materialized_cursor_id,\n                use_materialized_keys,\n                allow_seek,\n                signature,\n            },\n        })))\n    }\n}\n\nimpl<'a, 'plan> PreparedHashBuild<'a, 'plan> {\n    /// Emit the fresh hash build after planning has fixed its configuration.\n    pub(super) fn emit(self) -> Result<HashBuildPayloadInfo> {\n        let Self { planner, config } = self;\n        let build_table =\n            &planner.table_references.joined_tables()[planner.hash_join_op.build_table_idx];\n        let btree = build_table\n            .btree()\n            .expect(\"Hash join build table must be a BTree table\");\n        let num_keys = planner.hash_join_op.join_keys.len();\n\n        let build_key_start_reg = planner.program.alloc_registers(num_keys);\n        let mut build_rowid_reg = None;\n        let mut build_iter_cursor_id = planner.hash_build_cursor_id;\n        let materialized_input = planner\n            .t_ctx\n            .materialized_build_inputs\n            .get(&planner.hash_join_op.build_table_idx);\n        if let Some(input) = materialized_input {\n            match &input.mode {\n                MaterializedBuildInputMode::RowidOnly => {\n                    build_rowid_reg = Some(planner.program.alloc_register());\n                    build_iter_cursor_id = input.cursor_id;\n                }\n                MaterializedBuildInputMode::KeyPayload { .. } => {\n                    build_iter_cursor_id = input.cursor_id;\n                }\n            }\n        }\n\n        let (key_source_cursor_id, payload_source_cursor_id, hash_build_rowid_cursor_id) =\n            if config.use_materialized_keys {\n                (\n                    build_iter_cursor_id,\n                    build_iter_cursor_id,\n                    build_iter_cursor_id,\n                )\n            } else {\n                (\n                    planner.hash_build_cursor_id,\n                    planner.hash_build_cursor_id,\n                    planner.hash_build_cursor_id,\n                )\n            };\n\n        let build_loop_start = planner.program.allocate_label();\n        let build_loop_end = planner.program.allocate_label();\n        let skip_to_next = planner.program.allocate_label();\n        let label_hash_build_end = planner.program.allocate_label();\n        planner.program.emit_insn(Insn::Once {\n            target_pc_when_reentered: label_hash_build_end,\n        });\n\n        if !config.use_materialized_keys {\n            planner.program.emit_insn(Insn::OpenRead {\n                cursor_id: planner.hash_build_cursor_id,\n                root_page: btree.root_page,\n                db: build_table.database_id,\n            });\n        }\n\n        planner.program.emit_insn(Insn::Rewind {\n            cursor_id: build_iter_cursor_id,\n            pc_if_empty: build_loop_end,\n        });\n\n        if !config.use_materialized_keys {\n            planner\n                .program\n                .set_cursor_override(build_table.internal_id, planner.hash_build_cursor_id);\n        }\n\n        planner\n            .program\n            .preassign_label_to_next_insn(build_loop_start);\n\n        if let (Some(rowid_reg), Some(input_cursor_id)) =\n            (build_rowid_reg, config.materialized_cursor_id)\n        {\n            planner\n                .program\n                .emit_column_or_rowid(input_cursor_id, 0, rowid_reg);\n            planner.program.emit_insn(Insn::SeekRowid {\n                cursor_id: planner.hash_build_cursor_id,\n                src_reg: rowid_reg,\n                target_pc: skip_to_next,\n            });\n        }\n\n        if !config.use_materialized_keys {\n            let build_only_mask = TableMask::from_table_number_iter(\n                [planner.hash_join_op.build_table_idx].into_iter(),\n            );\n            for cond in planner.predicates.iter() {\n                if cond.from_outer_join.is_some() {\n                    // OUTER JOIN predicates must stay on the right-table loop\n                    // recorded in `from_outer_join`; applying them while\n                    // building the hash table would drop unmatched build rows\n                    // before null-extension.\n                    continue;\n                }\n                let mask = table_mask_from_expr(\n                    &cond.expr,\n                    planner.table_references,\n                    planner.non_from_clause_subqueries,\n                )?;\n                if !mask.contains_table(planner.hash_join_op.build_table_idx)\n                    || !build_only_mask.contains_all(&mask)\n                {\n                    continue;\n                }\n                if expr_references_outer_query(&cond.expr, planner.table_references) {\n                    continue;\n                }\n                let jump_target_when_true = planner.program.allocate_label();\n                let condition_metadata = ConditionMetadata {\n                    jump_if_condition_is_true: false,\n                    jump_target_when_true,\n                    jump_target_when_false: skip_to_next,\n                    jump_target_when_null: skip_to_next,\n                };\n                translate_condition_expr(\n                    planner.program,\n                    planner.table_references,\n                    &cond.expr,\n                    condition_metadata,\n                    &planner.t_ctx.resolver,\n                )?;\n                planner\n                    .program\n                    .preassign_label_to_next_insn(jump_target_when_true);\n            }\n        }\n\n        if config.use_materialized_keys {\n            for idx in 0..num_keys {\n                planner.program.emit_column_or_rowid(\n                    key_source_cursor_id,\n                    idx,\n                    build_key_start_reg + idx,\n                );\n            }\n        } else {\n            for (idx, join_key) in planner.hash_join_op.join_keys.iter().enumerate() {\n                let build_expr = join_key.get_build_expr(planner.predicates);\n                translate_expr(\n                    planner.program,\n                    Some(planner.table_references),\n                    build_expr,\n                    build_key_start_reg + idx,\n                    &planner.t_ctx.resolver,\n                )?;\n            }\n        }\n\n        if let Some(count) = std::num::NonZeroUsize::new(num_keys) {\n            planner.program.emit_insn(Insn::Affinity {\n                start_reg: build_key_start_reg,\n                count,\n                affinities: config.key_affinities.clone(),\n            });\n        }\n\n        let num_payload = config.payload_columns.len();\n        let (payload_start_reg, mut payload_info) = if num_payload > 0 {\n            let payload_reg = planner.program.alloc_registers(num_payload);\n            for (i, &col_idx) in config.payload_signature_columns.iter().enumerate() {\n                planner.program.emit_column_or_rowid(\n                    payload_source_cursor_id,\n                    col_idx,\n                    payload_reg + i,\n                );\n            }\n            (\n                Some(payload_reg),\n                HashBuildPayloadInfo {\n                    payload_columns: config.payload_columns.clone(),\n                    key_affinities: config.key_affinities.clone(),\n                    use_bloom_filter: false,\n                    bloom_filter_cursor_id: config.bloom_filter_cursor_id,\n                    allow_seek: config.allow_seek,\n                },\n            )\n        } else {\n            (\n                None,\n                HashBuildPayloadInfo {\n                    payload_columns: vec![],\n                    key_affinities: config.key_affinities.clone(),\n                    use_bloom_filter: false,\n                    bloom_filter_cursor_id: config.bloom_filter_cursor_id,\n                    allow_seek: config.allow_seek,\n                },\n            )\n        };\n\n        if !config.use_materialized_keys {\n            planner\n                .program\n                .clear_cursor_override(build_table.internal_id);\n        }\n\n        planner.program.emit_insn(Insn::HashBuild {\n            data: Box::new(HashBuildData {\n                cursor_id: hash_build_rowid_cursor_id,\n                key_start_reg: build_key_start_reg,\n                num_keys,\n                hash_table_id: planner.hash_table_id,\n                mem_budget: planner.hash_join_op.mem_budget,\n                collations: config.collations,\n                payload_start_reg,\n                num_payload,\n                track_matched: matches!(\n                    planner.hash_join_op.join_type,\n                    HashJoinType::LeftOuter | HashJoinType::FullOuter\n                ),\n            }),\n        });\n        if config.use_bloom_filter {\n            planner.program.emit_insn(Insn::FilterAdd {\n                cursor_id: config.bloom_filter_cursor_id,\n                key_reg: build_key_start_reg,\n                num_keys,\n            });\n            payload_info.use_bloom_filter = true;\n        }\n\n        planner.program.preassign_label_to_next_insn(skip_to_next);\n        planner.program.emit_insn(Insn::Next {\n            cursor_id: build_iter_cursor_id,\n            pc_if_next: build_loop_start,\n        });\n\n        planner.program.preassign_label_to_next_insn(build_loop_end);\n        planner.program.emit_insn(Insn::HashBuildFinalize {\n            hash_table_id: planner.hash_table_id,\n        });\n        planner\n            .program\n            .record_hash_build_signature(planner.hash_table_id, config.signature);\n\n        planner\n            .program\n            .preassign_label_to_next_insn(label_hash_build_end);\n        Ok(payload_info)\n    }\n}\n\nstruct PreparedProbeBuild {\n    build_cursor_id: CursorID,\n    payload_info: HashBuildPayloadInfo,\n}\n\nstruct ProbeSetupState {\n    build_cursor_id: CursorID,\n    payload_info: HashBuildPayloadInfo,\n    payload_dest_reg: Option<usize>,\n    match_reg: usize,\n    hash_probe_miss_label: BranchOffset,\n    match_found_label: BranchOffset,\n    hash_next_label: BranchOffset,\n    probe_rowid_reg: Option<usize>,\n    key_start_reg: usize,\n    num_keys: usize,\n    grace_flag_reg: Option<usize>,\n}\n\n/// Hash-join probe setup in `open_loop`.\n///\n/// Setup still runs in three ordered phases, but plain helper methods are enough:\n/// build or reuse the hash table, emit probe instructions, then install `HashCtx`.\npub(super) struct HashProbeSetupEmitter<'a, 'plan> {\n    program: &'a mut ProgramBuilder,\n    t_ctx: &'a mut TranslateCtx<'plan>,\n    table_references: &'a TableReferences,\n    subqueries: &'a [NonFromClauseSubquery],\n    predicates: &'a [WhereTerm],\n    hash_join_op: &'a HashJoinOp,\n    mode: &'a OperationMode,\n    probe_cursor_id: CursorID,\n    loop_start: BranchOffset,\n    loop_end: BranchOffset,\n    next: BranchOffset,\n    live_table_ids: &'a HashSet<TableInternalId>,\n}\n\nimpl<'a, 'plan> HashProbeSetupEmitter<'a, 'plan> {\n    #[allow(clippy::too_many_arguments)]\n    pub(super) const fn new(\n        program: &'a mut ProgramBuilder,\n        t_ctx: &'a mut TranslateCtx<'plan>,\n        table_references: &'a TableReferences,\n        subqueries: &'a [NonFromClauseSubquery],\n        predicates: &'a [WhereTerm],\n        hash_join_op: &'a HashJoinOp,\n        mode: &'a OperationMode,\n        probe_cursor_id: CursorID,\n        loop_start: BranchOffset,\n        loop_end: BranchOffset,\n        next: BranchOffset,\n        live_table_ids: &'a HashSet<TableInternalId>,\n    ) -> Self {\n        Self {\n            program,\n            t_ctx,\n            table_references,\n            subqueries,\n            predicates,\n            hash_join_op,\n            mode,\n            probe_cursor_id,\n            loop_start,\n            loop_end,\n            next,\n            live_table_ids,\n        }\n    }\n\n    /// Ensure the build cursor exists and the hash table is ready for probing.\n    fn prepare_build(&mut self) -> Result<PreparedProbeBuild> {\n        let build_table = &self.table_references.joined_tables()[self.hash_join_op.build_table_idx];\n        let (build_cursor_id, _) = build_table.resolve_cursors(self.program, self.mode.clone())?;\n        let build_cursor_id = if let Some(cursor_id) = build_cursor_id {\n            cursor_id\n        } else {\n            let btree = build_table\n                .btree()\n                .expect(\"Hash join build table must be a BTree table\");\n            let cursor_id = self.program.alloc_cursor_id_keyed_if_not_exists(\n                CursorKey::table(build_table.internal_id),\n                CursorType::BTreeTable(btree.clone()),\n            );\n            self.program.emit_insn(Insn::OpenRead {\n                cursor_id,\n                root_page: btree.root_page,\n                db: build_table.database_id,\n            });\n            cursor_id\n        };\n\n        let hash_table_id: usize = build_table.internal_id.into();\n        let btree = build_table\n            .btree()\n            .expect(\"Hash join build table must be a BTree table\");\n        let hash_build_cursor_id = self.program.alloc_cursor_id_keyed_if_not_exists(\n            CursorKey::hash_build(build_table.internal_id),\n            CursorType::BTreeTable(btree),\n        );\n        let payload_info = match HashBuildPlanner::new(\n            self.program,\n            self.t_ctx,\n            self.table_references,\n            self.subqueries,\n            self.predicates,\n            self.hash_join_op,\n            hash_build_cursor_id,\n            hash_table_id,\n        )\n        .prepare()?\n        {\n            HashBuildPlan::Reuse(info) => Ok(info),\n            HashBuildPlan::Build(prepared) => prepared.emit(),\n        }?;\n\n        Ok(PreparedProbeBuild {\n            build_cursor_id,\n            payload_info,\n        })\n    }\n\n    /// Emit the probe-side cursor positioning, key loading, and `HashProbe`. Advance\n    /// to the state needed to install the resulting `HashCtx`.\n    fn emit_probe(&mut self, prepared: PreparedProbeBuild) -> Result<ProbeSetupState> {\n        let PreparedProbeBuild {\n            build_cursor_id,\n            payload_info,\n        } = prepared;\n        let build_table = &self.table_references.joined_tables()[self.hash_join_op.build_table_idx];\n        let hash_table_id: usize = build_table.internal_id.into();\n        let num_keys = self.hash_join_op.join_keys.len();\n\n        // For LEFT/FULL OUTER hash joins, reset matched_bits at the start of\n        // each outer-loop iteration so marks from a previous probe pass don't\n        // suppress NULL-fill rows in the current one.\n        if matches!(\n            self.hash_join_op.join_type,\n            HashJoinType::LeftOuter | HashJoinType::FullOuter\n        ) {\n            self.program\n                .emit_insn(Insn::HashResetMatched { hash_table_id });\n        }\n\n        self.program.emit_insn(Insn::Rewind {\n            cursor_id: self.probe_cursor_id,\n            pc_if_empty: self.loop_end,\n        });\n        self.program.preassign_label_to_next_insn(self.loop_start);\n\n        let probe_key_start_reg = self.program.alloc_registers(num_keys);\n        for (idx, join_key) in self.hash_join_op.join_keys.iter().enumerate() {\n            let probe_expr = join_key.get_probe_expr(self.predicates);\n            translate_expr(\n                self.program,\n                Some(self.table_references),\n                probe_expr,\n                probe_key_start_reg + idx,\n                &self.t_ctx.resolver,\n            )?;\n        }\n\n        if let Some(count) = std::num::NonZeroUsize::new(num_keys) {\n            self.program.emit_insn(Insn::Affinity {\n                start_reg: probe_key_start_reg,\n                count,\n                affinities: payload_info.key_affinities.clone(),\n            });\n        }\n\n        if payload_info.use_bloom_filter && self.hash_join_op.join_type != HashJoinType::FullOuter {\n            self.program.emit_insn(Insn::Filter {\n                cursor_id: payload_info.bloom_filter_cursor_id,\n                target_pc: self.next,\n                key_reg: probe_key_start_reg,\n                num_keys,\n            });\n        }\n\n        let num_payload = payload_info.payload_columns.len();\n        let payload_dest_reg = if num_payload > 0 {\n            Some(self.program.alloc_registers(num_payload))\n        } else {\n            None\n        };\n\n        if matches!(self.hash_join_op.join_type, HashJoinType::FullOuter) {\n            let probe_table_idx = self.hash_join_op.probe_table_idx;\n            if let Some(lj_meta) = self.t_ctx.meta_left_joins[probe_table_idx].as_ref() {\n                self.program.emit_insn(Insn::Integer {\n                    value: 0,\n                    dest: lj_meta.reg_match_flag,\n                });\n            }\n        }\n\n        let hash_probe_miss_label = if self.hash_join_op.join_type == HashJoinType::FullOuter {\n            self.program.allocate_label()\n        } else {\n            self.next\n        };\n\n        let (probe_rowid_reg, grace_flag_reg) = {\n            let rowid_reg = self.program.alloc_register();\n            self.program.emit_insn(Insn::RowId {\n                cursor_id: self.probe_cursor_id,\n                dest: rowid_reg,\n            });\n            // grace_flag_reg: 0 during main probe loop, 1 during grace loop\n            let flag_reg = self.program.alloc_register();\n            self.program.emit_insn(Insn::Integer {\n                value: 0,\n                dest: flag_reg,\n            });\n            (Some(rowid_reg), Some(flag_reg))\n        };\n\n        let match_reg = self.program.alloc_register();\n        self.program.emit_insn(Insn::HashProbe {\n            hash_table_id: to_u16(hash_table_id),\n            key_start_reg: to_u16(probe_key_start_reg),\n            num_keys: to_u16(num_keys),\n            dest_reg: to_u16(match_reg),\n            target_pc: hash_probe_miss_label,\n            payload_dest_reg: payload_dest_reg.map(to_u16),\n            num_payload: to_u16(num_payload),\n            // Main probe loop always carries the probe rowid so spilled build\n            // partitions are deferred to grace processing instead of loaded here.\n            probe_rowid_reg: probe_rowid_reg.map(to_u16),\n        });\n\n        let match_found_label = self.program.allocate_label();\n        self.program.preassign_label_to_next_insn(match_found_label);\n        let hash_next_label = self.program.allocate_label();\n\n        Ok(ProbeSetupState {\n            build_cursor_id,\n            payload_info,\n            payload_dest_reg,\n            match_reg,\n            hash_probe_miss_label,\n            match_found_label,\n            hash_next_label,\n            probe_rowid_reg,\n            key_start_reg: probe_key_start_reg,\n            num_keys,\n            grace_flag_reg,\n        })\n    }\n\n    /// Install `HashCtx` and cache any payload-backed expressions for later reads.\n    fn install_context(&mut self, state: ProbeSetupState) -> Result<()> {\n        let ProbeSetupState {\n            build_cursor_id,\n            payload_info,\n            payload_dest_reg,\n            match_reg,\n            hash_probe_miss_label,\n            match_found_label,\n            hash_next_label,\n            probe_rowid_reg,\n            key_start_reg,\n            num_keys,\n            grace_flag_reg,\n        } = state;\n        let build_table = &self.table_references.joined_tables()[self.hash_join_op.build_table_idx];\n        let hash_table_id: usize = build_table.internal_id.into();\n        let payload_columns = payload_info.payload_columns.clone();\n\n        let mut labels = HashLabels::new(match_found_label, hash_next_label);\n        if self.hash_join_op.join_type == HashJoinType::FullOuter {\n            labels.check_outer = Some(hash_probe_miss_label);\n        };\n        self.t_ctx.hash_table_contexts.insert(\n            self.hash_join_op.build_table_idx,\n            HashCtx {\n                labels,\n                hash_table_reg: hash_table_id,\n                match_reg,\n                payload_start_reg: payload_dest_reg,\n                payload_columns: payload_info.payload_columns,\n                build_cursor_id: if payload_info.allow_seek {\n                    Some(build_cursor_id)\n                } else {\n                    None\n                },\n                join_type: self.hash_join_op.join_type,\n                inner_loop_gosub_reg: None,\n                probe_rowid_reg,\n                key_start_reg,\n                num_keys,\n                grace_flag_reg,\n            },\n        );\n\n        self.t_ctx.resolver.enable_expr_to_reg_cache();\n        let rowid_expr = Expr::RowId {\n            database: None,\n            table: build_table.internal_id,\n        };\n        let payload_has_build_rowid = payload_columns.iter().any(|payload| {\n            matches!(\n                payload,\n                MaterializedColumnRef::RowId { table_id } if *table_id == build_table.internal_id\n            )\n        });\n        let build_table_is_live = self.live_table_ids.contains(&build_table.internal_id);\n        if payload_info.allow_seek && !payload_has_build_rowid && !build_table_is_live {\n            self.t_ctx\n                .resolver\n                .cache_expr_reg(Cow::Owned(rowid_expr), match_reg, false, None);\n        }\n        if let Some(payload_reg) = payload_dest_reg {\n            for (i, payload) in payload_columns.iter().enumerate() {\n                let (payload_table_id, expr, is_column) = match payload {\n                    MaterializedColumnRef::Column {\n                        table_id,\n                        column_idx,\n                        is_rowid_alias,\n                    } => (\n                        *table_id,\n                        Expr::Column {\n                            database: None,\n                            table: *table_id,\n                            column: *column_idx,\n                            is_rowid_alias: *is_rowid_alias,\n                        },\n                        true,\n                    ),\n                    MaterializedColumnRef::RowId { table_id } => (\n                        *table_id,\n                        Expr::RowId {\n                            database: None,\n                            table: *table_id,\n                        },\n                        false,\n                    ),\n                };\n                if self.live_table_ids.contains(&payload_table_id) {\n                    continue;\n                }\n                if is_column {\n                    self.t_ctx.resolver.cache_scalar_expr_reg(\n                        Cow::Owned(expr),\n                        payload_reg + i,\n                        true,\n                        self.table_references,\n                    )?;\n                } else {\n                    self.t_ctx.resolver.cache_expr_reg(\n                        Cow::Owned(expr),\n                        payload_reg + i,\n                        false,\n                        None,\n                    );\n                }\n            }\n        } else if payload_info.allow_seek && !build_table_is_live {\n            self.program.emit_insn(Insn::SeekRowid {\n                cursor_id: build_cursor_id,\n                src_reg: match_reg,\n                target_pc: hash_next_label,\n            });\n        }\n\n        Ok(())\n    }\n\n    pub(super) fn emit(mut self) -> Result<()> {\n        let prepared = self.prepare_build()?;\n        let state = self.emit_probe(prepared)?;\n        self.install_context(state)\n    }\n}\n\nstruct ProbeCloseState {\n    label_next_probe_row: BranchOffset,\n    semi_anti_next_pc: Option<BranchOffset>,\n}\n\n/// Result of emitting hash-join probe teardown.\npub(super) struct HashProbeCloseOutcome {\n    pub semi_anti_next_pc: Option<BranchOffset>,\n}\n\n/// Close-loop path of a hash-join probe.\n///\n/// The teardown remains ordered: emit `HashNext`, optionally emit FULL OUTER\n/// unmatched probe rows, then return the probe-row advance state.\npub(super) struct HashProbeCloseEmitter<'a, 'plan> {\n    program: &'a mut ProgramBuilder,\n    t_ctx: &'a mut TranslateCtx<'plan>,\n    hash_join_op: &'a HashJoinOp,\n    hash_ctx: HashCtx,\n    select_plan: Option<&'plan SelectPlan>,\n    table_index: usize,\n}\n\nimpl<'a, 'plan> HashProbeCloseEmitter<'a, 'plan> {\n    /// Capture the mutable close-loop inputs for a single hash probe table.\n    pub(super) fn new(\n        program: &'a mut ProgramBuilder,\n        t_ctx: &'a mut TranslateCtx<'plan>,\n        hash_join_op: &'a HashJoinOp,\n        hash_ctx: HashCtx,\n        select_plan: Option<&'plan SelectPlan>,\n        table_index: usize,\n    ) -> Self {\n        Self {\n            program,\n            t_ctx,\n            hash_join_op,\n            hash_ctx,\n            select_plan,\n            table_index,\n        }\n    }\n\n    /// Emit `HashNext` and loop back into the existing match-processing path.\n    fn emit_matched_iteration(&mut self) -> Result<ProbeCloseState> {\n        let hash_table_reg = self.hash_ctx.hash_table_reg;\n        let match_reg = self.hash_ctx.match_reg;\n        let match_found_label = self.hash_ctx.labels.match_found;\n        let hash_next_label = self.hash_ctx.labels.next;\n        let payload_dest_reg = self.hash_ctx.payload_start_reg;\n        let num_payload = self.hash_ctx.payload_columns.len();\n        let check_outer_label = self.hash_ctx.labels.check_outer;\n        let join_type = self.hash_ctx.join_type;\n        let inner_loop_gosub_reg = self.hash_ctx.inner_loop_gosub_reg;\n        let inner_loop_skip_label = self.hash_ctx.labels.inner_loop_skip;\n        let label_next_probe_row = self.program.allocate_label();\n        let mut semi_anti_next_pc = None;\n\n        if let Some(gosub_reg) = inner_loop_gosub_reg {\n            semi_anti_next_pc = Some(self.program.offset());\n            self.program.emit_insn(Insn::Return {\n                return_reg: gosub_reg,\n                can_fallthrough: false,\n            });\n            if let Some(skip_label) = inner_loop_skip_label {\n                self.program.preassign_label_to_next_insn(skip_label);\n            }\n        }\n\n        let hash_next_target = if join_type == HashJoinType::FullOuter {\n            check_outer_label.unwrap_or(label_next_probe_row)\n        } else {\n            label_next_probe_row\n        };\n\n        if semi_anti_next_pc.is_none() {\n            semi_anti_next_pc = Some(self.program.offset());\n        }\n        self.program\n            .resolve_label(hash_next_label, self.program.offset());\n\n        // Grace dispatch: if grace_flag_reg > 0, jump to the grace loop's own\n        // HashNext (which has a different miss target). This lets the inner body\n        // be shared between the main probe loop and the grace loop.\n        if let Some(grace_flag_reg) = self.hash_ctx.grace_flag_reg {\n            let grace_hash_next_label = self.program.allocate_label();\n            // Store in hash_ctx for the grace loop emitter to resolve later.\n            let build_table_idx = self.hash_join_op.build_table_idx;\n            if let Some(ctx) = self.t_ctx.hash_table_contexts.get_mut(&build_table_idx) {\n                ctx.labels.grace_hash_next = Some(grace_hash_next_label);\n            }\n            self.program.emit_insn(Insn::IfPos {\n                reg: grace_flag_reg,\n                target_pc: grace_hash_next_label,\n                decrement_by: 0,\n            });\n        }\n\n        self.program.emit_insn(Insn::HashNext {\n            hash_table_id: hash_table_reg,\n            dest_reg: match_reg,\n            target_pc: hash_next_target,\n            payload_dest_reg,\n            num_payload,\n        });\n        self.program.emit_insn(Insn::Goto {\n            target_pc: match_found_label,\n        });\n\n        Ok(ProbeCloseState {\n            label_next_probe_row,\n            semi_anti_next_pc,\n        })\n    }\n\n    /// Emit FULL OUTER unmatched probe rows before advancing to the next probe row.\n    fn emit_probe_miss_rows(&mut self, state: ProbeCloseState) -> Result<ProbeCloseState> {\n        let ProbeCloseState {\n            label_next_probe_row,\n            semi_anti_next_pc,\n        } = state;\n\n        if matches!(self.hash_ctx.join_type, HashJoinType::FullOuter) {\n            let probe_table_idx = self.hash_join_op.probe_table_idx;\n            let lj_meta = self.t_ctx.meta_left_joins[probe_table_idx]\n                .as_ref()\n                .expect(\"FULL OUTER probe table must have left join metadata\");\n            let reg_match_flag = lj_meta.reg_match_flag;\n\n            if let Some(check_outer_label) = self.hash_ctx.labels.check_outer {\n                self.program\n                    .resolve_label(check_outer_label, self.program.offset());\n            }\n            self.program\n                .resolve_label(lj_meta.label_match_flag_check_value, self.program.offset());\n\n            self.program.emit_insn(Insn::IfPos {\n                reg: reg_match_flag,\n                target_pc: label_next_probe_row,\n                decrement_by: 0,\n            });\n\n            if let Some(cursor_id) = self.hash_ctx.build_cursor_id {\n                self.program.emit_insn(Insn::NullRow { cursor_id });\n            }\n\n            if let Some(payload_reg) = self.hash_ctx.payload_start_reg {\n                let num_payload = self.hash_ctx.payload_columns.len();\n                if num_payload > 0 {\n                    self.program.emit_insn(Insn::Null {\n                        dest: payload_reg,\n                        dest_end: Some(payload_reg + num_payload - 1),\n                    });\n                }\n            }\n\n            if let Some(plan) = self.select_plan {\n                emit_unmatched_row_conditions_and_loop(\n                    self.program,\n                    self.t_ctx,\n                    plan,\n                    self.hash_join_op.build_table_idx,\n                    self.table_index,\n                    label_next_probe_row,\n                    self.hash_ctx\n                        .inner_loop_gosub_reg\n                        .zip(self.hash_ctx.labels.inner_loop_gosub),\n                )?;\n            }\n        }\n\n        Ok(ProbeCloseState {\n            label_next_probe_row,\n            semi_anti_next_pc,\n        })\n    }\n\n    /// Anchor the next probe-row label and return the close-loop control-flow state.\n    fn finish(&mut self, state: ProbeCloseState) -> HashProbeCloseOutcome {\n        let ProbeCloseState {\n            label_next_probe_row,\n            semi_anti_next_pc,\n        } = state;\n\n        self.program\n            .preassign_label_to_next_insn(label_next_probe_row);\n\n        HashProbeCloseOutcome { semi_anti_next_pc }\n    }\n\n    pub(super) fn emit(mut self) -> Result<HashProbeCloseOutcome> {\n        let state = self.emit_matched_iteration()?;\n        let state = self.emit_probe_miss_rows(state)?;\n        Ok(self.finish(state))\n    }\n}\n\n/// Emit unmatched build rows after the probe cursor has been exhausted.\npub(super) fn emit_hash_join_unmatched_build_rows<'a>(\n    program: &mut ProgramBuilder,\n    t_ctx: &mut TranslateCtx<'a>,\n    hash_join_op: &HashJoinOp,\n    hash_ctx: &HashCtx,\n    select_plan: Option<&'a SelectPlan>,\n    table_index: usize,\n    probe_cursor_id: CursorID,\n) -> Result<()> {\n    if !matches!(\n        hash_join_op.join_type,\n        HashJoinType::LeftOuter | HashJoinType::FullOuter\n    ) {\n        return Ok(());\n    }\n    let Some(plan) = select_plan else {\n        return Ok(());\n    };\n\n    let hash_table_reg = hash_ctx.hash_table_reg;\n    let match_reg = hash_ctx.match_reg;\n    let payload_dest_reg = hash_ctx.payload_start_reg;\n    let num_payload = hash_ctx.payload_columns.len();\n    let build_cursor_id = hash_ctx.build_cursor_id;\n    let done_unmatched = program.allocate_label();\n\n    program.emit_insn(Insn::NullRow {\n        cursor_id: probe_cursor_id,\n    });\n\n    program.emit_insn(Insn::HashScanUnmatched {\n        hash_table_id: hash_table_reg,\n        dest_reg: match_reg,\n        target_pc: done_unmatched,\n        payload_dest_reg,\n        num_payload,\n    });\n\n    let unmatched_loop = program.allocate_label();\n    let label_next_unmatched = program.allocate_label();\n    program.preassign_label_to_next_insn(unmatched_loop);\n\n    if let Some(cursor_id) = build_cursor_id {\n        program.emit_insn(Insn::SeekRowid {\n            cursor_id,\n            src_reg: match_reg,\n            target_pc: done_unmatched,\n        });\n    }\n\n    emit_unmatched_row_conditions_and_loop(\n        program,\n        t_ctx,\n        plan,\n        hash_join_op.build_table_idx,\n        table_index,\n        label_next_unmatched,\n        hash_ctx\n            .inner_loop_gosub_reg\n            .zip(hash_ctx.labels.inner_loop_gosub),\n    )?;\n\n    program.resolve_label(label_next_unmatched, program.offset());\n    program.emit_insn(Insn::HashNextUnmatched {\n        hash_table_id: hash_table_reg,\n        dest_reg: match_reg,\n        target_pc: done_unmatched,\n        payload_dest_reg,\n        num_payload,\n    });\n    program.emit_insn(Insn::Goto {\n        target_pc: unmatched_loop,\n    });\n    program.preassign_label_to_next_insn(done_unmatched);\n    Ok(())\n}\n\n/// Grace Hash Join processing loop after the probe cursor is exhausted.\npub(crate) struct GraceHashLoop;\n\nimpl GraceHashLoop {\n    /// Emit VDBE-driven grace hash join processing loop.\n    /// Uses the shared inner body via `Goto match_found_label` and `grace_flag_reg`\n    /// dispatch so that aggregates, LIMIT, ORDER BY, etc. all work naturally.\n    /// At runtime, HashGraceInit is a no-op if the build side didn't spill.\n    pub fn emit<'a>(\n        program: &mut ProgramBuilder,\n        t_ctx: &mut TranslateCtx<'a>,\n        hash_join_op: &HashJoinOp,\n        hash_ctx: &HashCtx,\n        select_plan: Option<&'a SelectPlan>,\n        table_index: usize,\n        probe_cursor_id: CursorID,\n    ) -> Result<()> {\n        // Need grace_flag_reg + probe_rowid_reg for grace processing\n        let Some(probe_rowid_reg) = hash_ctx.probe_rowid_reg else {\n            return Ok(());\n        };\n        let Some(grace_flag_reg) = hash_ctx.grace_flag_reg else {\n            return Ok(());\n        };\n\n        let hash_table_reg = hash_ctx.hash_table_reg;\n        let match_reg = hash_ctx.match_reg;\n        let match_found_label = hash_ctx.labels.match_found;\n        let payload_dest_reg = hash_ctx.payload_start_reg;\n        let num_payload = hash_ctx.payload_columns.len();\n        let is_full_outer = hash_join_op.join_type == HashJoinType::FullOuter;\n\n        let grace_done = program.allocate_label();\n        let grace_partition_top = program.allocate_label();\n        let grace_probe_top = program.allocate_label();\n        let grace_advance = program.allocate_label();\n        let grace_cleanup = program.allocate_label();\n\n        // HashGraceInit: finalize probe spill + grace_begin\n        program.emit_insn(Insn::HashGraceInit {\n            hash_table_id: to_u16(hash_table_reg),\n            target_pc: grace_done,\n        });\n\n        // Set grace mode flag = 1\n        program.emit_insn(Insn::Integer {\n            value: 1,\n            dest: grace_flag_reg,\n        });\n\n        // grace_partition_top: load build partition + first probe chunk\n        program.preassign_label_to_next_insn(grace_partition_top);\n        program.emit_insn(Insn::HashGraceLoadPartition {\n            hash_table_id: to_u16(hash_table_reg),\n            target_pc: grace_cleanup,\n        });\n\n        // grace_probe_top: get next probe entry (writes keys + rowid to registers)\n        program.preassign_label_to_next_insn(grace_probe_top);\n\n        // FULL OUTER: reset match flag before each probe entry so we can detect misses\n        if is_full_outer {\n            let probe_table_idx = hash_join_op.probe_table_idx;\n            if let Some(lj_meta) = t_ctx.meta_left_joins[probe_table_idx].as_ref() {\n                program.emit_insn(Insn::Integer {\n                    value: 0,\n                    dest: lj_meta.reg_match_flag,\n                });\n            }\n        }\n\n        program.emit_insn(Insn::HashGraceNextProbe {\n            hash_table_id: to_u16(hash_table_reg),\n            key_start_reg: to_u16(hash_ctx.key_start_reg),\n            num_keys: to_u16(hash_ctx.num_keys),\n            probe_rowid_dest: to_u16(probe_rowid_reg),\n            target_pc: grace_advance,\n        });\n\n        // Re-position probe cursor via SeekRowid\n        program.emit_insn(Insn::SeekRowid {\n            cursor_id: probe_cursor_id,\n            src_reg: probe_rowid_reg,\n            target_pc: grace_probe_top,\n        });\n\n        // For FULL OUTER, HashProbe miss needs to go to the outer-check path\n        // (emit unmatched probe row with NULL build columns).\n        // For INNER/LEFT OUTER, miss just advances to next probe entry.\n        let grace_outer_check = if is_full_outer {\n            program.allocate_label()\n        } else {\n            grace_probe_top\n        };\n\n        // HashProbe the loaded build partition with the probe keys\n        program.emit_insn(Insn::HashProbe {\n            hash_table_id: to_u16(hash_table_reg),\n            key_start_reg: to_u16(hash_ctx.key_start_reg),\n            num_keys: to_u16(hash_ctx.num_keys),\n            dest_reg: to_u16(match_reg),\n            target_pc: grace_outer_check,\n            payload_dest_reg: payload_dest_reg.map(to_u16),\n            num_payload: to_u16(num_payload),\n            probe_rowid_reg: None, // grace-only: HashGraceLoadPartition already loaded this partition\n        });\n\n        // Jump INTO the shared inner body (conditions, result columns, aggregation).\n        // The IfPos dispatch before the main loop's HashNext will route back here.\n        program.emit_insn(Insn::Goto {\n            target_pc: match_found_label,\n        });\n\n        // grace_hash_next: the grace loop's own HashNext, reached via IfPos dispatch\n        // from the shared body.\n        if let Some(grace_hash_next_label) = hash_ctx.labels.grace_hash_next {\n            program.resolve_label(grace_hash_next_label, program.offset());\n        }\n\n        // For FULL OUTER, HashNext miss goes to outer check (unmatched probe row).\n        // For INNER/LEFT OUTER, miss advances to next probe entry.\n        program.emit_insn(Insn::HashNext {\n            hash_table_id: hash_table_reg,\n            dest_reg: match_reg,\n            target_pc: grace_outer_check,\n            payload_dest_reg,\n            num_payload,\n        });\n        // Another match found, loop back to shared body\n        program.emit_insn(Insn::Goto {\n            target_pc: match_found_label,\n        });\n\n        // FULL OUTER: unmatched probe row path.\n        // If match_flag is still 0, emit the probe row with NULL build columns.\n        if is_full_outer {\n            program.resolve_label(grace_outer_check, program.offset());\n\n            let probe_table_idx = hash_join_op.probe_table_idx;\n            if let Some(lj_meta) = t_ctx.meta_left_joins[probe_table_idx].as_ref() {\n                // If match_flag > 0, a match was found, skip to next probe entry\n                program.emit_insn(Insn::IfPos {\n                    reg: lj_meta.reg_match_flag,\n                    target_pc: grace_probe_top,\n                    decrement_by: 0,\n                });\n            }\n\n            // Set build cursor to NULL row\n            if let Some(cursor_id) = hash_ctx.build_cursor_id {\n                program.emit_insn(Insn::NullRow { cursor_id });\n            }\n\n            // NULL out payload registers\n            if let Some(payload_reg) = hash_ctx.payload_start_reg {\n                if num_payload > 0 {\n                    program.emit_insn(Insn::Null {\n                        dest: payload_reg,\n                        dest_end: Some(payload_reg + num_payload - 1),\n                    });\n                }\n            }\n\n            // Emit the unmatched row through the shared body\n            if let Some(plan) = select_plan {\n                emit_unmatched_row_conditions_and_loop(\n                    program,\n                    t_ctx,\n                    plan,\n                    hash_join_op.build_table_idx,\n                    table_index,\n                    grace_probe_top,\n                    hash_ctx\n                        .inner_loop_gosub_reg\n                        .zip(hash_ctx.labels.inner_loop_gosub),\n                )?;\n            }\n\n            // Advance to next probe entry\n            program.emit_insn(Insn::Goto {\n                target_pc: grace_probe_top,\n            });\n        }\n\n        // grace_advance: probe entries exhausted for this partition.\n        program.resolve_label(grace_advance, program.offset());\n\n        // LEFT/FULL OUTER: emit unmatched build rows for this partition BEFORE evicting.\n        // After eviction, matched_bits are lost, so the global unmatched scan can't\n        // see which build rows were matched during grace probing.\n        if matches!(\n            hash_join_op.join_type,\n            HashJoinType::LeftOuter | HashJoinType::FullOuter\n        ) {\n            if let Some(plan) = select_plan {\n                let done_grace_unmatched = program.allocate_label();\n                let grace_unmatched_loop = program.allocate_label();\n                let grace_next_unmatched = program.allocate_label();\n\n                // Set probe cursor to NULL row (unmatched build rows have no probe match)\n                program.emit_insn(Insn::NullRow {\n                    cursor_id: probe_cursor_id,\n                });\n\n                program.emit_insn(Insn::HashScanUnmatched {\n                    hash_table_id: hash_table_reg,\n                    dest_reg: match_reg,\n                    target_pc: done_grace_unmatched,\n                    payload_dest_reg,\n                    num_payload,\n                });\n\n                program.preassign_label_to_next_insn(grace_unmatched_loop);\n\n                if let Some(cursor_id) = hash_ctx.build_cursor_id {\n                    program.emit_insn(Insn::SeekRowid {\n                        cursor_id,\n                        src_reg: match_reg,\n                        target_pc: done_grace_unmatched,\n                    });\n                }\n\n                emit_unmatched_row_conditions_and_loop(\n                    program,\n                    t_ctx,\n                    plan,\n                    hash_join_op.build_table_idx,\n                    table_index,\n                    grace_next_unmatched,\n                    hash_ctx\n                        .inner_loop_gosub_reg\n                        .zip(hash_ctx.labels.inner_loop_gosub),\n                )?;\n\n                program.resolve_label(grace_next_unmatched, program.offset());\n                program.emit_insn(Insn::HashNextUnmatched {\n                    hash_table_id: hash_table_reg,\n                    dest_reg: match_reg,\n                    target_pc: done_grace_unmatched,\n                    payload_dest_reg,\n                    num_payload,\n                });\n                program.emit_insn(Insn::Goto {\n                    target_pc: grace_unmatched_loop,\n                });\n                program.preassign_label_to_next_insn(done_grace_unmatched);\n            }\n        }\n\n        // Evict current partition, advance to next\n        program.emit_insn(Insn::HashGraceAdvancePartition {\n            hash_table_id: to_u16(hash_table_reg),\n            target_pc: grace_cleanup,\n        });\n        program.emit_insn(Insn::Goto {\n            target_pc: grace_partition_top,\n        });\n\n        // grace_cleanup: clear grace mode flag\n        program.resolve_label(grace_cleanup, program.offset());\n        program.emit_insn(Insn::Integer {\n            value: 0,\n            dest: grace_flag_reg,\n        });\n\n        // grace_done\n        program.preassign_label_to_next_insn(grace_done);\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "core/translate/main_loop/in_seek.rs",
    "content": "use super::*;\n\n/// Open or reuse the ephemeral cursor that supplies RHS values for an IN-seek.\n///\n/// Literal lists are materialized once into a unique ephemeral index so both\n/// ordinary `Search::InSeek` and multi-index OR branches can drive repeated\n/// equality seeks from the same bytecode pattern. IN-subqueries already have an\n/// ephemeral cursor from subquery translation, so they are reused directly.\npub(super) fn open_in_seek_source_cursor(\n    program: &mut ProgramBuilder,\n    table_references: &TableReferences,\n    resolver: &Resolver<'_>,\n    index: Option<&Arc<Index>>,\n    source: &InSeekSource,\n) -> Result<CursorID> {\n    match source {\n        InSeekSource::LiteralList { values, affinity } => {\n            let label_once_end = program.allocate_label();\n            program.emit_insn(Insn::Once {\n                target_pc_when_reentered: label_once_end,\n            });\n            let collation = index\n                .as_ref()\n                .and_then(|idx| idx.columns.first())\n                .and_then(|c| c.collation);\n            let ephemeral_index = Arc::new(Index {\n                name: String::new(),\n                table_name: String::new(),\n                root_page: 0,\n                columns: vec![IndexColumn {\n                    name: String::new(),\n                    order: SortOrder::Asc,\n                    pos_in_table: 0,\n                    collation,\n                    default: None,\n                    expr: None,\n                }],\n                unique: true,\n                ephemeral: true,\n                has_rowid: false,\n                where_clause: None,\n                index_method: None,\n                on_conflict: None,\n            });\n            let eph_cursor = program.alloc_cursor_id(CursorType::BTreeIndex(ephemeral_index));\n            program.emit_insn(Insn::OpenEphemeral {\n                cursor_id: eph_cursor,\n                is_table: false,\n            });\n            let val_reg = program.alloc_register();\n            let record_reg = program.alloc_register();\n            let affinity_str = affinity.aff_mask().to_string();\n            for value in values.iter() {\n                translate_expr_no_constant_opt(\n                    program,\n                    Some(table_references),\n                    value,\n                    val_reg,\n                    resolver,\n                    NoConstantOptReason::InListEphemeral,\n                )?;\n                program.emit_insn(Insn::MakeRecord {\n                    start_reg: to_u16(val_reg),\n                    count: 1,\n                    dest_reg: to_u16(record_reg),\n                    index_name: None,\n                    affinity_str: Some(affinity_str.clone()),\n                });\n                program.emit_insn(Insn::IdxInsert {\n                    cursor_id: eph_cursor,\n                    record_reg,\n                    unpacked_start: Some(val_reg),\n                    unpacked_count: Some(1),\n                    flags: IdxInsertFlags::new().no_op_duplicate(),\n                });\n            }\n            program.preassign_label_to_next_insn(label_once_end);\n            Ok(eph_cursor)\n        }\n        InSeekSource::Subquery { cursor_id } => Ok(*cursor_id),\n    }\n}\n"
  },
  {
    "path": "core/translate/main_loop/init.rs",
    "content": "use super::*;\n\npub fn init_distinct(program: &mut ProgramBuilder, plan: &SelectPlan) -> Result<DistinctCtx> {\n    let collations = plan\n        .result_columns\n        .iter()\n        .map(|col| {\n            get_collseq_from_expr(&col.expr, &plan.table_references)\n                .map(|c| c.unwrap_or(CollationSeq::Binary))\n        })\n        .collect::<Result<Vec<_>>>()?;\n    let hash_table_id = program.alloc_hash_table_id();\n    let ctx = DistinctCtx {\n        hash_table_id,\n        collations,\n        label_on_conflict: program.allocate_label(),\n    };\n\n    Ok(ctx)\n}\n\n/// First step of Loop emission, opens cursors for all tables and initializes distinct aggregate\n/// hash tables. Also emits condition checks for any WHERE clause terms that need to be evaluated\n/// before the loop (e.g. those that reference only tables that are on the outermost level of the\n/// join order).\npub struct InitLoop;\nimpl InitLoop {\n    #[allow(clippy::too_many_arguments)]\n    pub fn emit<'a>(\n        program: &mut ProgramBuilder,\n        t_ctx: &mut TranslateCtx<'a>,\n        tables: &TableReferences,\n        aggregates: &mut [Aggregate],\n        mode: &OperationMode,\n        where_clause: &[WhereTerm],\n        join_order: &[JoinOrderMember],\n        subqueries: &mut [NonFromClauseSubquery],\n    ) -> Result<()> {\n        turso_assert_eq!(\n            t_ctx.meta_left_joins.len(),\n            tables.joined_tables().len(),\n            \"meta_left_joins length must match tables length\"\n        );\n\n        if matches!(\n            &mode,\n            OperationMode::INSERT | OperationMode::UPDATE { .. } | OperationMode::DELETE\n        ) {\n            turso_assert_eq!(tables.joined_tables().len(), 1);\n            let changed_table = &tables.joined_tables()[0].table;\n            let prepared = prepare_cdc_if_necessary(\n                program,\n                t_ctx.resolver.schema(),\n                changed_table.get_name(),\n            )?;\n            if let Some((cdc_cursor_id, _)) = prepared {\n                t_ctx.cdc_cursor_id = Some(cdc_cursor_id);\n            }\n        }\n\n        // Initialize distinct aggregates using hash tables\n        for agg in aggregates.iter_mut().filter(|agg| agg.is_distinct()) {\n            turso_assert_eq!(\n                agg.args.len(),\n                1,\n                \"DISTINCT aggregate functions must have exactly one argument\"\n            );\n            let collations =\n                vec![get_collseq_from_expr(&agg.original_expr, tables)?\n                    .unwrap_or(CollationSeq::Binary)];\n            let hash_table_id = program.alloc_hash_table_id();\n            agg.distinctness = Distinctness::Distinct {\n                ctx: Some(DistinctCtx {\n                    hash_table_id,\n                    collations,\n                    label_on_conflict: program.allocate_label(),\n                }),\n            };\n            // DISTINCT aggregate hash tables live in ProgramState, so a correlated\n            // subquery can re-enter with rows from the previous invocation still\n            // recorded unless we clear the seen-set here.\n            program.emit_insn(Insn::HashClear { hash_table_id });\n            emit_explain!(\n                program,\n                false,\n                format!(\"USE HASH TABLE FOR {}(DISTINCT)\", agg.func)\n            );\n        }\n        // Include hash-join build tables so their cursors are opened for hash build.\n        let mut required_tables: HashSet<usize> = join_order\n            .iter()\n            .map(|member| member.original_idx)\n            .collect();\n        for table in tables.joined_tables().iter() {\n            if let Operation::HashJoin(hash_join_op) = &table.op {\n                required_tables.insert(hash_join_op.build_table_idx);\n            }\n        }\n\n        for (table_index, table) in tables.joined_tables().iter().enumerate() {\n            if !required_tables.contains(&table_index) {\n                continue;\n            }\n            // Ensure attached databases have a Transaction instruction for read access\n            if crate::is_attached_db(table.database_id) {\n                let schema_cookie = t_ctx\n                    .resolver\n                    .with_schema(table.database_id, |s| s.schema_version);\n                program.begin_read_on_database(table.database_id, schema_cookie);\n            }\n            // Initialize bookkeeping for OUTER JOIN\n            if let Some(join_info) = table.join_info.as_ref() {\n                if join_info.is_outer() {\n                    let lj_metadata = LeftJoinMetadata {\n                        reg_match_flag: program.alloc_register(),\n                        label_match_flag_set_true: program.allocate_label(),\n                        label_match_flag_check_value: program.allocate_label(),\n                    };\n                    t_ctx.meta_left_joins[table_index] = Some(lj_metadata);\n                }\n                if join_info.is_semi_or_anti() {\n                    let join_idx = join_order\n                        .iter()\n                        .position(|m| m.original_idx == table_index)\n                        .expect(\"table must be in join_order\");\n                    let outer_table_idx =\n                        find_non_semi_anti_ancestor(join_order, tables.joined_tables(), join_idx);\n                    // For hash join probe tables, loop_labels.next points to the probe\n                    // cursor's Next (which advances to the next outer row), but we need\n                    // to jump to the HashNext (which advances to the next hash match\n                    // for the current outer row). We allocate a fresh label here and\n                    // resolve it in close_loop at the right point.\n                    let sa_metadata = SemiAntiJoinMetadata {\n                        label_body: program.allocate_label(),\n                        label_next_outer: program.allocate_label(),\n                        outer_table_idx,\n                    };\n                    t_ctx.meta_semi_anti_joins[table_index] = Some(sa_metadata);\n                }\n            }\n            let (table_cursor_id, index_cursor_id) =\n                table.open_cursors(program, mode.clone(), t_ctx.resolver.schema())?;\n            match &table.op {\n                Operation::Scan(Scan::BTreeTable { index, .. }) => match (&mode, &table.table) {\n                    (OperationMode::SELECT, Table::BTree(btree)) => {\n                        let root_page = btree.root_page;\n                        if let Some(cursor_id) = table_cursor_id {\n                            program.emit_insn(Insn::OpenRead {\n                                cursor_id,\n                                root_page,\n                                db: table.database_id,\n                            });\n                        }\n                        if let Some(index_cursor_id) = index_cursor_id {\n                            program.emit_insn(Insn::OpenRead {\n                                cursor_id: index_cursor_id,\n                                root_page: index.as_ref().unwrap().root_page,\n                                db: table.database_id,\n                            });\n                        }\n                    }\n                    (OperationMode::DELETE, Table::BTree(btree)) => {\n                        let root_page = btree.root_page;\n                        program.emit_insn(Insn::OpenWrite {\n                            cursor_id: table_cursor_id\n                                .expect(\"table cursor is always opened in OperationMode::DELETE\"),\n                            root_page: root_page.into(),\n                            db: table.database_id,\n                        });\n                        if let Some(index_cursor_id) = index_cursor_id {\n                            program.emit_insn(Insn::OpenWrite {\n                                cursor_id: index_cursor_id,\n                                root_page: index.as_ref().unwrap().root_page.into(),\n                                db: table.database_id,\n                            });\n                        }\n                        // For delete, we need to open all the other indexes too for writing\n                        let indices: Vec<_> = t_ctx.resolver.with_schema(table.database_id, |s| {\n                            s.get_indices(table.table.get_name()).cloned().collect()\n                        });\n                        for index in &indices {\n                            if table\n                                .op\n                                .index()\n                                .is_some_and(|table_index| table_index.name == index.name)\n                            {\n                                continue;\n                            }\n                            let cursor_id = program.alloc_cursor_index(\n                                Some(CursorKey::index(table.internal_id, index.clone())),\n                                index,\n                            )?;\n                            program.emit_insn(Insn::OpenWrite {\n                                cursor_id,\n                                root_page: index.root_page.into(),\n                                db: table.database_id,\n                            });\n                        }\n                    }\n                    (OperationMode::UPDATE(update_mode), Table::BTree(btree)) => {\n                        let root_page = btree.root_page;\n                        match &update_mode {\n                            UpdateRowSource::Normal => {\n                                program.emit_insn(Insn::OpenWrite {\n                                    cursor_id: table_cursor_id.expect(\n                                        \"table cursor is always opened in OperationMode::UPDATE\",\n                                    ),\n                                    root_page: root_page.into(),\n                                    db: table.database_id,\n                                });\n                            }\n                            UpdateRowSource::PrebuiltEphemeralTable { target_table, .. } => {\n                                let target_table_cursor_id = program\n                                    .resolve_cursor_id(&CursorKey::table(target_table.internal_id));\n                                program.emit_insn(Insn::OpenWrite {\n                                    cursor_id: target_table_cursor_id,\n                                    root_page: target_table.btree().unwrap().root_page.into(),\n                                    db: target_table.database_id,\n                                });\n                            }\n                        }\n                        let write_db_id = match &update_mode {\n                            UpdateRowSource::PrebuiltEphemeralTable { target_table, .. } => {\n                                target_table.database_id\n                            }\n                            _ => table.database_id,\n                        };\n                        if let Some(index_cursor_id) = index_cursor_id {\n                            program.emit_insn(Insn::OpenWrite {\n                                cursor_id: index_cursor_id,\n                                root_page: index.as_ref().unwrap().root_page.into(),\n                                db: write_db_id,\n                            });\n                        }\n                    }\n                    _ => {}\n                },\n                Operation::Scan(Scan::VirtualTable { .. }) => {\n                    if let Table::Virtual(tbl) = &table.table {\n                        let is_write = matches!(\n                            mode,\n                            OperationMode::INSERT\n                                | OperationMode::UPDATE { .. }\n                                | OperationMode::DELETE\n                        );\n                        let allow_dbpage_write = {\n                            #[cfg(feature = \"cli_only\")]\n                            {\n                                t_ctx.unsafe_testing && tbl.name == crate::dbpage::DBPAGE_TABLE_NAME\n                            }\n                            #[cfg(not(feature = \"cli_only\"))]\n                            {\n                                false\n                            }\n                        };\n                        if is_write && tbl.readonly() && !allow_dbpage_write {\n                            return Err(crate::LimboError::ReadOnly);\n                        }\n                        if let Some(cursor_id) = table_cursor_id {\n                            program.emit_insn(Insn::VOpen { cursor_id });\n                            if is_write && !allow_dbpage_write {\n                                program.emit_insn(Insn::VBegin { cursor_id });\n                            }\n                        }\n                    }\n                }\n                Operation::Scan(_) => {}\n                Operation::Search(search) => {\n                    match mode {\n                        OperationMode::SELECT => {\n                            if let Some(table_cursor_id) = table_cursor_id {\n                                program.emit_insn(Insn::OpenRead {\n                                    cursor_id: table_cursor_id,\n                                    root_page: table.table.get_root_page()?,\n                                    db: table.database_id,\n                                });\n                            }\n                        }\n                        OperationMode::DELETE | OperationMode::UPDATE { .. } => {\n                            let table_cursor_id = table_cursor_id.expect(\n                                        \"table cursor is always opened in OperationMode::DELETE or OperationMode::UPDATE\",\n                                    );\n\n                            program.emit_insn(Insn::OpenWrite {\n                                cursor_id: table_cursor_id,\n                                root_page: table.table.get_root_page()?.into(),\n                                db: table.database_id,\n                            });\n\n                            // For DELETE, we need to open all the indexes for writing\n                            // UPDATE opens these in emit_program_for_update() separately\n                            if matches!(mode, OperationMode::DELETE) {\n                                let indices: Vec<_> =\n                                    t_ctx.resolver.with_schema(table.database_id, |s| {\n                                        s.get_indices(table.table.get_name()).cloned().collect()\n                                    });\n                                for index in &indices {\n                                    if table\n                                        .op\n                                        .index()\n                                        .is_some_and(|table_index| table_index.name == index.name)\n                                    {\n                                        continue;\n                                    }\n                                    let cursor_id = program.alloc_cursor_index(\n                                        Some(CursorKey::index(table.internal_id, index.clone())),\n                                        index,\n                                    )?;\n                                    program.emit_insn(Insn::OpenWrite {\n                                        cursor_id,\n                                        root_page: index.root_page.into(),\n                                        db: table.database_id,\n                                    });\n                                }\n                            }\n                        }\n                        _ => {\n                            return Err(crate::LimboError::InternalError(\n                                \"INSERT mode is not supported for Search operations\".to_string(),\n                            ));\n                        }\n                    }\n\n                    let search_index = match search {\n                        Search::Seek {\n                            index: Some(index), ..\n                        }\n                        | Search::InSeek {\n                            index: Some(index), ..\n                        } => Some(index),\n                        _ => None,\n                    };\n                    if let Some(index) = search_index {\n                        // Ephemeral index cursor are opened ad-hoc when needed.\n                        if !index.ephemeral {\n                            match mode {\n                                OperationMode::SELECT => {\n                                    program.emit_insn(Insn::OpenRead {\n                                        cursor_id: index_cursor_id.expect(\n                                            \"index cursor is always opened in Seek with index\",\n                                        ),\n                                        root_page: index.root_page,\n                                        db: table.database_id,\n                                    });\n                                }\n                                OperationMode::UPDATE { .. } | OperationMode::DELETE => {\n                                    program.emit_insn(Insn::OpenWrite {\n                                        cursor_id: index_cursor_id.expect(\n                                            \"index cursor is always opened in Seek with index\",\n                                        ),\n                                        root_page: index.root_page.into(),\n                                        db: table.database_id,\n                                    });\n                                }\n                                _ => {\n                                    return Err(crate::LimboError::InternalError(\n                                    \"INSERT mode is not supported for indexed Search operations\"\n                                        .to_string(),\n                                ));\n                                }\n                            }\n                        }\n                    }\n                }\n                Operation::IndexMethodQuery(_) => match mode {\n                    OperationMode::SELECT => {\n                        if let Some(table_cursor_id) = table_cursor_id {\n                            program.emit_insn(Insn::OpenRead {\n                                cursor_id: table_cursor_id,\n                                root_page: table.table.get_root_page()?,\n                                db: table.database_id,\n                            });\n                        }\n                        let index_cursor_id = index_cursor_id.unwrap();\n                        program.emit_insn(Insn::OpenRead {\n                            cursor_id: index_cursor_id,\n                            root_page: table.op.index().unwrap().root_page,\n                            db: table.database_id,\n                        });\n                    }\n                    OperationMode::DELETE => {\n                        if let Some(table_cursor_id) = table_cursor_id {\n                            program.emit_insn(Insn::OpenWrite {\n                                cursor_id: table_cursor_id,\n                                root_page: table.table.get_root_page()?.into(),\n                                db: table.database_id,\n                            });\n                        }\n                        let index_cursor_id = index_cursor_id.expect(\"index cursor is always opened in OperationMode::DELETE for IndexMethodQuery\");\n                        program.emit_insn(Insn::OpenWrite {\n                            cursor_id: index_cursor_id,\n                            root_page: table.op.index().expect(\"index to exist\").root_page.into(),\n                            db: table.database_id,\n                        });\n                        let indices: Vec<_> = t_ctx.resolver.with_schema(table.database_id, |s| {\n                            s.get_indices(table.table.get_name()).cloned().collect()\n                        });\n                        for index in &indices {\n                            if table\n                                .op\n                                .index()\n                                .is_some_and(|table_index| table_index.name == index.name)\n                            {\n                                continue;\n                            }\n                            let cursor_id = program.alloc_cursor_index(\n                                Some(CursorKey::index(table.internal_id, index.clone())),\n                                index,\n                            )?;\n                            program.emit_insn(Insn::OpenWrite {\n                                cursor_id,\n                                root_page: index.root_page.into(),\n                                db: table.database_id,\n                            });\n                        }\n                    }\n                    OperationMode::UPDATE { .. } => {\n                        let table_cursor_id = table_cursor_id.expect(\n                        \"table cursor is always opened in OperationMode::UPDATE for IndexMethodQuery\",\n                    );\n                        program.emit_insn(Insn::OpenWrite {\n                            cursor_id: table_cursor_id,\n                            root_page: table.table.get_root_page()?.into(),\n                            db: table.database_id,\n                        });\n                        let index_cursor_id = index_cursor_id.unwrap();\n                        program.emit_insn(Insn::OpenWrite {\n                            cursor_id: index_cursor_id,\n                            root_page: table.op.index().expect(\"index to exist\").root_page.into(),\n                            db: table.database_id,\n                        });\n                    }\n                    _ => panic!(\"Unsupported operation mode for index method\"),\n                },\n                Operation::HashJoin(_) => {\n                    match mode {\n                        OperationMode::SELECT => {\n                            // Open probe table cursor, the build table cursor should already be open from a previous iteration.\n                            if let Some(table_cursor_id) = table_cursor_id {\n                                let Table::BTree(btree) = &table.table else {\n                                    panic!(\"Expected hash join probe table to be a BTree table\");\n                                };\n                                program.emit_insn(Insn::OpenRead {\n                                    cursor_id: table_cursor_id,\n                                    root_page: btree.root_page,\n                                    db: table.database_id,\n                                });\n                            }\n                        }\n                        _ => unreachable!(\"Hash joins should only occur in SELECT operations\"),\n                    }\n                }\n                Operation::MultiIndexScan(multi_idx_op) => {\n                    match mode {\n                        OperationMode::SELECT => {\n                            let Table::BTree(btree) = &table.table else {\n                                panic!(\"Expected multi-index scan table to be a BTree table\");\n                            };\n                            // Open the table cursor\n                            if let Some(table_cursor_id) = table_cursor_id {\n                                program.emit_insn(Insn::OpenRead {\n                                    cursor_id: table_cursor_id,\n                                    root_page: btree.root_page,\n                                    db: table.database_id,\n                                });\n                            }\n                            // Open cursors for each index branch\n                            for branch in &multi_idx_op.branches {\n                                if let Some(index) = &branch.index {\n                                    let branch_cursor_id = program.alloc_cursor_index(\n                                        Some(CursorKey::index(table.internal_id, index.clone())),\n                                        index,\n                                    )?;\n                                    program.emit_insn(Insn::OpenRead {\n                                        cursor_id: branch_cursor_id,\n                                        root_page: index.root_page,\n                                        db: table.database_id,\n                                    });\n                                }\n                            }\n                        }\n                        _ => {\n                            unreachable!(\"Multi-index scans should only occur in SELECT operations\")\n                        }\n                    }\n                }\n            }\n        }\n\n        for cond in where_clause\n            .iter()\n            .filter(|c| c.should_eval_before_loop(join_order, subqueries, Some(tables)))\n        {\n            let jump_target = program.allocate_label();\n            let meta = ConditionMetadata {\n                jump_if_condition_is_true: false,\n                jump_target_when_true: jump_target,\n                jump_target_when_false: t_ctx.label_main_loop_end.expect(\n                    \"main_loop_end label should be set before emitting condition expressions\",\n                ),\n                jump_target_when_null: t_ctx.label_main_loop_end.expect(\n                    \"main_loop_end label should be set before emitting condition expressions\",\n                ),\n            };\n            translate_condition_expr(program, tables, &cond.expr, meta, &t_ctx.resolver)?;\n            program.preassign_label_to_next_insn(jump_target);\n        }\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "core/translate/main_loop/mod.rs",
    "content": "use turso_parser::ast::{Expr, SortOrder, TableInternalId};\n\nuse super::{\n    aggregation::{translate_aggregation_step, AggArgumentSource},\n    emitter::{\n        InSeekMetadata, MaterializedBuildInputMode, MaterializedColumnRef, OperationMode, Resolver,\n        TranslateCtx, UpdateRowSource,\n    },\n    expr::{\n        expr_references_subquery_id, translate_condition_expr, translate_expr,\n        translate_expr_no_constant_opt, walk_expr, ConditionMetadata, NoConstantOptReason,\n        WalkControl,\n    },\n    group_by::{group_by_agg_phase, GroupByMetadata, GroupByRowSource},\n    optimizer::{constraints::BinaryExprSide, Optimizable},\n    order_by::sorter_insert,\n    plan::{\n        Aggregate, DistinctCtx, Distinctness, EvalAt, HashJoinOp, HashJoinType, InSeekSource,\n        IterationDirection, JoinOrderMember, JoinedTable, MultiIndexScanOp, NonFromClauseSubquery,\n        Operation, QueryDestination, Scan, Search, SeekDef, SeekKey, SeekKeyComponent, SelectPlan,\n        SetOperation, TableReferences, WhereTerm,\n    },\n};\nuse crate::{\n    emit_explain,\n    schema::{Index, IndexColumn, Table},\n    translate::{\n        collate::{get_collseq_from_expr, resolve_comparison_collseq, CollationSeq},\n        emitter::{prepare_cdc_if_necessary, HashCtx},\n        expr::comparison_affinity,\n        planner::{table_mask_from_expr, TableMask},\n        result_row::emit_select_result,\n    },\n    turso_assert, turso_assert_eq,\n    types::SeekOp,\n    util::expr_tables_subset_of,\n    vdbe::{\n        affinity::{self, Affinity},\n        builder::{\n            CursorKey, CursorType, HashBuildSignature, MaterializedBuildInputModeTag,\n            ProgramBuilder,\n        },\n        insn::{to_u16, CmpInsFlags, HashBuildData, IdxInsertFlags, Insn},\n        BranchOffset, CursorID,\n    },\n    Result,\n};\nuse std::{borrow::Cow, collections::HashSet, sync::Arc};\nuse turso_macros::turso_assert_some;\n\nmod body;\nmod close;\nmod conditions;\nmod hash;\nmod in_seek;\nmod init;\nmod multi_index;\nmod open;\nmod seek;\n\nuse body::emit_unmatched_row_conditions_and_loop;\npub(crate) use body::LoopBodyEmitter;\npub(crate) use close::CloseLoop;\nuse close::{emit_autoindex, AutoIndexResult};\nuse in_seek::open_in_seek_source_cursor;\npub(crate) use init::{init_distinct, InitLoop};\nuse multi_index::emit_multi_index_scan_loop;\npub(crate) use open::OpenLoop;\nuse seek::SeekEmitter;\n\n#[derive(Debug)]\npub struct LeftJoinMetadata {\n    pub reg_match_flag: usize,\n    pub label_match_flag_set_true: BranchOffset,\n    pub label_match_flag_check_value: BranchOffset,\n}\n\n#[derive(Debug)]\npub struct SemiAntiJoinMetadata {\n    pub label_body: BranchOffset,\n    pub label_next_outer: BranchOffset,\n    pub outer_table_idx: usize,\n}\n\n#[derive(Debug, Clone, Copy)]\npub struct LoopLabels {\n    pub loop_start: BranchOffset,\n    pub next: BranchOffset,\n    pub loop_end: BranchOffset,\n}\n\nimpl LoopLabels {\n    pub fn new(program: &mut ProgramBuilder) -> Self {\n        Self {\n            loop_start: program.allocate_label(),\n            next: program.allocate_label(),\n            loop_end: program.allocate_label(),\n        }\n    }\n}\n\nfn find_non_semi_anti_ancestor(\n    join_order: &[JoinOrderMember],\n    tables: &[JoinedTable],\n    join_idx: usize,\n) -> usize {\n    assert!(join_idx > 0, \"semi/anti-join cannot be the first table\");\n    let mut idx = join_idx - 1;\n    while idx > 0 {\n        let prev = &tables[join_order[idx].original_idx];\n        if !prev\n            .join_info\n            .as_ref()\n            .is_some_and(|ji| ji.is_semi_or_anti())\n        {\n            break;\n        }\n        idx -= 1;\n    }\n    join_order[idx].original_idx\n}\n"
  },
  {
    "path": "core/translate/main_loop/multi_index.rs",
    "content": "use super::*;\nuse crate::translate::plan::{MultiIndexBranch, MultiIndexBranchAccess};\n\n#[expect(clippy::too_many_arguments)]\nfn emit_multi_index_rowset_update(\n    program: &mut ProgramBuilder,\n    is_intersection: bool,\n    branch_idx: usize,\n    rowid_reg: usize,\n    rowset1_reg: usize,\n    current_read_rowset: usize,\n    current_write_rowset: usize,\n    skip_row_label: BranchOffset,\n    found_in_prev_label: Option<BranchOffset>,\n) {\n    if is_intersection {\n        if branch_idx == 0 {\n            program.emit_insn(Insn::RowSetAdd {\n                rowset_reg: rowset1_reg,\n                value_reg: rowid_reg,\n            });\n        } else {\n            program.emit_insn(Insn::RowSetTest {\n                rowset_reg: current_read_rowset,\n                pc_if_found: found_in_prev_label\n                    .expect(\"intersection branch must have found label\"),\n                value_reg: rowid_reg,\n                batch: -1,\n            });\n            program.emit_insn(Insn::Goto {\n                target_pc: skip_row_label,\n            });\n            program.preassign_label_to_next_insn(\n                found_in_prev_label.expect(\"intersection branch must have found label\"),\n            );\n            program.emit_insn(Insn::RowSetAdd {\n                rowset_reg: current_write_rowset,\n                value_reg: rowid_reg,\n            });\n        }\n    } else {\n        program.emit_insn(Insn::RowSetAdd {\n            rowset_reg: rowset1_reg,\n            value_reg: rowid_reg,\n        });\n    }\n}\n\n#[expect(clippy::too_many_arguments)]\nfn emit_multi_index_or_residual_filters(\n    program: &mut ProgramBuilder,\n    t_ctx: &mut TranslateCtx,\n    table_references: &TableReferences,\n    residual_exprs: &[Expr],\n    jump_target: BranchOffset,\n    index_cursor_id: Option<CursorID>,\n    table_cursor_id: CursorID,\n    requires_table_cursor: bool,\n) -> Result<()> {\n    if residual_exprs.is_empty() {\n        return Ok(());\n    }\n\n    if requires_table_cursor {\n        if let Some(index_cursor_id) = index_cursor_id {\n            program.emit_insn(Insn::DeferredSeek {\n                index_cursor_id,\n                table_cursor_id,\n            });\n        }\n    }\n\n    for residual_expr in residual_exprs {\n        let jump_target_when_true = program.allocate_label();\n        let condition_metadata = ConditionMetadata {\n            jump_if_condition_is_true: false,\n            jump_target_when_true,\n            jump_target_when_false: jump_target,\n            jump_target_when_null: jump_target,\n        };\n        translate_condition_expr(\n            program,\n            table_references,\n            residual_expr,\n            condition_metadata,\n            &t_ctx.resolver,\n        )?;\n        program.preassign_label_to_next_insn(jump_target_when_true);\n    }\n\n    Ok(())\n}\n\n#[allow(clippy::too_many_arguments)]\nfn emit_seek_multi_index_branch(\n    program: &mut ProgramBuilder,\n    t_ctx: &mut TranslateCtx,\n    table: &JoinedTable,\n    table_references: &TableReferences,\n    branch: &MultiIndexBranch,\n    table_cursor_id: CursorID,\n    rowid_reg: usize,\n    is_intersection: bool,\n    branch_idx: usize,\n    rowset1_reg: usize,\n    current_read_rowset: usize,\n    current_write_rowset: usize,\n    found_in_prev_label: Option<BranchOffset>,\n) -> Result<()> {\n    let MultiIndexBranchAccess::Seek { seek_def } = &branch.access else {\n        unreachable!(\"seek branch helper called for non-seek branch\");\n    };\n    let branch_loop_start = program.allocate_label();\n    let branch_loop_end = program.allocate_label();\n    let branch_next = program.allocate_label();\n    let is_index = branch.index.is_some();\n    let branch_cursor_id = if let Some(index) = &branch.index {\n        program.resolve_cursor_id(&CursorKey::index(table.internal_id, index.clone()))\n    } else {\n        table_cursor_id\n    };\n\n    if let Some(r) = &branch.union_residuals {\n        emit_multi_index_or_residual_filters(\n            program,\n            t_ctx,\n            table_references,\n            &r.pre_filter_exprs,\n            branch_loop_end,\n            None,\n            table_cursor_id,\n            false,\n        )?;\n    }\n\n    let max_key_regs = seek_def\n        .size(&seek_def.start)\n        .max(seek_def.size(&seek_def.end))\n        .max(1);\n    let key_start_reg = program.alloc_registers(max_key_regs);\n    SeekEmitter::new(\n        program,\n        table_references,\n        seek_def,\n        t_ctx,\n        branch_cursor_id,\n        key_start_reg,\n        branch_loop_end,\n        branch.index.as_ref(),\n    )\n    .emit(branch_loop_start, false)?;\n\n    if is_index {\n        program.emit_insn(Insn::IdxRowId {\n            cursor_id: branch_cursor_id,\n            dest: rowid_reg,\n        });\n    } else {\n        program.emit_insn(Insn::RowId {\n            cursor_id: branch_cursor_id,\n            dest: rowid_reg,\n        });\n    }\n\n    if let Some(r) = &branch.union_residuals {\n        emit_multi_index_or_residual_filters(\n            program,\n            t_ctx,\n            table_references,\n            &r.post_filter_exprs,\n            branch_next,\n            is_index.then_some(branch_cursor_id),\n            table_cursor_id,\n            r.requires_table_cursor,\n        )?;\n    }\n\n    emit_multi_index_rowset_update(\n        program,\n        is_intersection,\n        branch_idx,\n        rowid_reg,\n        rowset1_reg,\n        current_read_rowset,\n        current_write_rowset,\n        branch_next,\n        found_in_prev_label,\n    );\n\n    program.preassign_label_to_next_insn(branch_next);\n    match seek_def.iter_dir {\n        IterationDirection::Forwards => program.emit_insn(Insn::Next {\n            cursor_id: branch_cursor_id,\n            pc_if_next: branch_loop_start,\n        }),\n        IterationDirection::Backwards => program.emit_insn(Insn::Prev {\n            cursor_id: branch_cursor_id,\n            pc_if_prev: branch_loop_start,\n        }),\n    }\n    program.preassign_label_to_next_insn(branch_loop_end);\n\n    Ok(())\n}\n\n#[allow(clippy::too_many_arguments)]\nfn emit_in_seek_multi_index_branch(\n    program: &mut ProgramBuilder,\n    t_ctx: &mut TranslateCtx,\n    table: &JoinedTable,\n    table_references: &TableReferences,\n    branch: &MultiIndexBranch,\n    table_cursor_id: CursorID,\n    rowid_reg: usize,\n    is_intersection: bool,\n    branch_idx: usize,\n    rowset1_reg: usize,\n    current_read_rowset: usize,\n    current_write_rowset: usize,\n    found_in_prev_label: Option<BranchOffset>,\n) -> Result<()> {\n    let MultiIndexBranchAccess::InSeek { source } = &branch.access else {\n        unreachable!(\"IN-seek branch helper called for non-IN branch\");\n    };\n    let branch_cursor_id = branch.index.as_ref().map(|index| {\n        program.resolve_cursor_id(&CursorKey::index(table.internal_id, index.clone()))\n    });\n    let ephemeral_cursor_id = open_in_seek_source_cursor(\n        program,\n        table_references,\n        &t_ctx.resolver,\n        branch.index.as_ref(),\n        source,\n    )?;\n\n    let branch_loop_end = program.allocate_label();\n\n    if let Some(r) = &branch.union_residuals {\n        emit_multi_index_or_residual_filters(\n            program,\n            t_ctx,\n            table_references,\n            &r.pre_filter_exprs,\n            branch_loop_end,\n            None,\n            table_cursor_id,\n            false,\n        )?;\n    }\n\n    program.emit_insn(Insn::NullRow {\n        cursor_id: ephemeral_cursor_id,\n    });\n    program.emit_insn(Insn::Rewind {\n        cursor_id: ephemeral_cursor_id,\n        pc_if_empty: branch_loop_end,\n    });\n\n    let outer_loop_start = program.allocate_label();\n    program.preassign_label_to_next_insn(outer_loop_start);\n    let seek_reg = program.alloc_register();\n    program.emit_insn(Insn::Column {\n        cursor_id: ephemeral_cursor_id,\n        column: 0,\n        dest: seek_reg,\n        default: None,\n    });\n\n    let next_value_label = program.allocate_label();\n    program.emit_insn(Insn::IsNull {\n        reg: seek_reg,\n        target_pc: next_value_label,\n    });\n\n    if let Some(branch_cursor_id) = branch_cursor_id {\n        let branch_loop_start = program.allocate_label();\n        let branch_next = program.allocate_label();\n        program.emit_insn(Insn::SeekGE {\n            cursor_id: branch_cursor_id,\n            start_reg: seek_reg,\n            num_regs: 1,\n            target_pc: next_value_label,\n            is_index: true,\n            eq_only: false,\n        });\n        program.preassign_label_to_next_insn(branch_loop_start);\n        program.emit_insn(Insn::IdxGT {\n            cursor_id: branch_cursor_id,\n            start_reg: seek_reg,\n            num_regs: 1,\n            target_pc: next_value_label,\n        });\n        program.emit_insn(Insn::IdxRowId {\n            cursor_id: branch_cursor_id,\n            dest: rowid_reg,\n        });\n        if let Some(r) = &branch.union_residuals {\n            emit_multi_index_or_residual_filters(\n                program,\n                t_ctx,\n                table_references,\n                &r.post_filter_exprs,\n                branch_next,\n                Some(branch_cursor_id),\n                table_cursor_id,\n                r.requires_table_cursor,\n            )?;\n        }\n        emit_multi_index_rowset_update(\n            program,\n            is_intersection,\n            branch_idx,\n            rowid_reg,\n            rowset1_reg,\n            current_read_rowset,\n            current_write_rowset,\n            branch_next,\n            found_in_prev_label,\n        );\n        program.preassign_label_to_next_insn(branch_next);\n        program.emit_insn(Insn::Next {\n            cursor_id: branch_cursor_id,\n            pc_if_next: branch_loop_start,\n        });\n    } else {\n        program.emit_insn(Insn::SeekRowid {\n            cursor_id: table_cursor_id,\n            src_reg: seek_reg,\n            target_pc: next_value_label,\n        });\n        program.emit_insn(Insn::RowId {\n            cursor_id: table_cursor_id,\n            dest: rowid_reg,\n        });\n        if let Some(r) = &branch.union_residuals {\n            emit_multi_index_or_residual_filters(\n                program,\n                t_ctx,\n                table_references,\n                &r.post_filter_exprs,\n                next_value_label,\n                None,\n                table_cursor_id,\n                r.requires_table_cursor,\n            )?;\n        }\n        emit_multi_index_rowset_update(\n            program,\n            is_intersection,\n            branch_idx,\n            rowid_reg,\n            rowset1_reg,\n            current_read_rowset,\n            current_write_rowset,\n            next_value_label,\n            found_in_prev_label,\n        );\n    }\n\n    program.preassign_label_to_next_insn(next_value_label);\n    program.emit_insn(Insn::Next {\n        cursor_id: ephemeral_cursor_id,\n        pc_if_next: outer_loop_start,\n    });\n    program.preassign_label_to_next_insn(branch_loop_end);\n\n    Ok(())\n}\n\n#[allow(clippy::too_many_arguments)]\npub(super) fn emit_multi_index_scan_loop(\n    program: &mut ProgramBuilder,\n    t_ctx: &mut TranslateCtx,\n    table: &JoinedTable,\n    table_references: &TableReferences,\n    multi_idx_op: &MultiIndexScanOp,\n    loop_start: BranchOffset,\n    loop_end: BranchOffset,\n) -> Result<()> {\n    let table_cursor_id = program.resolve_cursor_id(&CursorKey::table(table.internal_id));\n    let rowid_reg = program.alloc_register();\n    let is_intersection = matches!(multi_idx_op.set_op, SetOperation::Intersection { .. });\n    let rowset1_reg = program.alloc_register();\n    let rowset2_reg = if is_intersection {\n        program.alloc_register()\n    } else {\n        rowset1_reg\n    };\n\n    program.emit_insn(Insn::Null {\n        dest: rowset1_reg,\n        dest_end: None,\n    });\n    if is_intersection {\n        program.emit_insn(Insn::Null {\n            dest: rowset2_reg,\n            dest_end: None,\n        });\n    }\n\n    let mut current_read_rowset = rowset1_reg;\n    let mut current_write_rowset = if is_intersection {\n        rowset2_reg\n    } else {\n        rowset1_reg\n    };\n\n    for (branch_idx, branch) in multi_idx_op.branches.iter().enumerate() {\n        let found_in_prev_label = if is_intersection && branch_idx > 0 {\n            Some(program.allocate_label())\n        } else {\n            None\n        };\n        match &branch.access {\n            MultiIndexBranchAccess::Seek { .. } => emit_seek_multi_index_branch(\n                program,\n                t_ctx,\n                table,\n                table_references,\n                branch,\n                table_cursor_id,\n                rowid_reg,\n                is_intersection,\n                branch_idx,\n                rowset1_reg,\n                current_read_rowset,\n                current_write_rowset,\n                found_in_prev_label,\n            )?,\n            MultiIndexBranchAccess::InSeek { .. } => emit_in_seek_multi_index_branch(\n                program,\n                t_ctx,\n                table,\n                table_references,\n                branch,\n                table_cursor_id,\n                rowid_reg,\n                is_intersection,\n                branch_idx,\n                rowset1_reg,\n                current_read_rowset,\n                current_write_rowset,\n                found_in_prev_label,\n            )?,\n        }\n\n        if is_intersection && branch_idx > 0 && branch_idx < multi_idx_op.branches.len() - 1 {\n            std::mem::swap(&mut current_read_rowset, &mut current_write_rowset);\n            program.emit_insn(Insn::Null {\n                dest: current_write_rowset,\n                dest_end: None,\n            });\n        }\n    }\n\n    let final_rowset = if is_intersection && multi_idx_op.branches.len() > 1 {\n        let num_swaps = multi_idx_op.branches.len().saturating_sub(2);\n        if num_swaps % 2 == 0 {\n            rowset2_reg\n        } else {\n            rowset1_reg\n        }\n    } else {\n        rowset1_reg\n    };\n\n    program.preassign_label_to_next_insn(loop_start);\n    program.emit_insn(Insn::RowSetRead {\n        rowset_reg: final_rowset,\n        pc_if_empty: loop_end,\n        dest_reg: rowid_reg,\n    });\n\n    let skip_label = program.allocate_label();\n    program.emit_insn(Insn::SeekRowid {\n        cursor_id: table_cursor_id,\n        src_reg: rowid_reg,\n        target_pc: skip_label,\n    });\n\n    let rowid_expr = Expr::RowId {\n        database: None,\n        table: table.internal_id,\n    };\n    t_ctx\n        .resolver\n        .cache_expr_reg(Cow::Owned(rowid_expr), rowid_reg, false, None);\n\n    program.preassign_label_to_next_insn(skip_label);\n    Ok(())\n}\n"
  },
  {
    "path": "core/translate/main_loop/open.rs",
    "content": "use super::*;\nuse crate::translate::main_loop::{conditions::LoopConditionEmitter, hash::HashProbeSetupEmitter};\nuse crate::translate::{\n    main_loop::close::AutoIndexBuild,\n    plan::{self, SubqueryEvalPhase},\n    subquery::{materialized_from_clause_subquery_storage, MaterializedFromClauseSubqueryStorage},\n};\n\nfn emit_materialized_subquery_result_columns(\n    program: &mut ProgramBuilder,\n    from_clause_subquery: &crate::schema::FromClauseSubquery,\n    cursor_id: CursorID,\n    index: Option<&Index>,\n) {\n    let Some(start_reg) = from_clause_subquery.result_columns_start_reg else {\n        return;\n    };\n\n    let index_to_table = index.map(|index| {\n        let mut source_cols = vec![None; from_clause_subquery.columns.len()];\n        for (source_col, idx_col) in index.columns.iter().enumerate() {\n            source_cols[idx_col.pos_in_table] = Some(source_col);\n        }\n        source_cols\n    });\n\n    for col_idx in 0..from_clause_subquery.columns.len() {\n        let source_col = index_to_table\n            .as_ref()\n            .map(|source_cols| {\n                source_cols[col_idx]\n                    .expect(\"direct materialized subquery index must cover every result column\")\n            })\n            .unwrap_or(col_idx);\n        program.emit_insn(Insn::Column {\n            cursor_id,\n            column: source_col,\n            dest: start_reg + col_idx,\n            default: None,\n        });\n    }\n}\n\n/// Opens the main loop for each table in the join order, emitting instructions to initialize\n/// cursors and perform index seeks as necessary.\npub struct OpenLoop;\n\nimpl OpenLoop {\n    #[allow(clippy::too_many_arguments)]\n    pub fn emit(\n        program: &mut ProgramBuilder,\n        t_ctx: &mut TranslateCtx,\n        table_references: &TableReferences,\n        join_order: &[JoinOrderMember],\n        predicates: &[WhereTerm],\n        temp_cursor_id: Option<CursorID>,\n        mode: OperationMode,\n        subqueries: &mut [NonFromClauseSubquery],\n    ) -> Result<()> {\n        let live_table_ids: HashSet<_> = join_order.iter().map(|member| member.table_id).collect();\n        for (join_index, join) in join_order.iter().enumerate() {\n            let joined_table_index = join.original_idx;\n            let table = &table_references.joined_tables()[joined_table_index];\n            let LoopLabels {\n                loop_start,\n                loop_end,\n                next,\n            } = *t_ctx\n                .labels_main_loop\n                .get(joined_table_index)\n                .expect(\"table has no loop labels\");\n\n            // For chained anti-joins (e.g. NOT EXISTS t2 AND NOT EXISTS t3),\n            // when anti-join N exhausts without a match, execution should continue\n            // to anti-join N+1's open_loop (not jump to the body). Resolve the\n            // previous anti-join's label_body to the current program offset.\n            if join_index > 0 {\n                let prev_table_idx = join_order[join_index - 1].original_idx;\n                let prev_is_anti = table_references.joined_tables()[prev_table_idx]\n                    .join_info\n                    .as_ref()\n                    .is_some_and(|ji| ji.is_anti());\n                if prev_is_anti {\n                    if let Some(prev_sa_meta) = t_ctx.meta_semi_anti_joins[prev_table_idx].as_ref()\n                    {\n                        program.resolve_label(prev_sa_meta.label_body, program.offset());\n                    }\n                }\n            }\n\n            // Each OUTER JOIN has a \"match flag\" that is initially set to false,\n            // and is set to true when a match is found for the OUTER JOIN.\n            // This is used to determine whether to emit actual columns or NULLs for the columns of the right table.\n            if let Some(join_info) = table.join_info.as_ref() {\n                if join_info.is_outer() {\n                    let lj_meta = t_ctx.meta_left_joins[joined_table_index].as_ref().unwrap();\n                    program.emit_insn(Insn::Integer {\n                        value: 0,\n                        dest: lj_meta.reg_match_flag,\n                    });\n                }\n            }\n\n            let (table_cursor_id, index_cursor_id) =\n                table.resolve_cursors(program, mode.clone())?;\n\n            match &table.op {\n                Operation::Scan(scan) => {\n                    match (scan, &table.table) {\n                        (Scan::BTreeTable { iter_dir, .. }, Table::BTree(_)) => {\n                            let iteration_cursor_id = temp_cursor_id.unwrap_or_else(|| {\n                                index_cursor_id.unwrap_or_else(|| {\n                                    table_cursor_id.expect(\n                                        \"Either ephemeral or index or table cursor must be opened\",\n                                    )\n                                })\n                            });\n                            if *iter_dir == IterationDirection::Backwards {\n                                program.emit_insn(Insn::Last {\n                                    cursor_id: iteration_cursor_id,\n                                    pc_if_empty: loop_end,\n                                });\n                            } else {\n                                program.emit_insn(Insn::Rewind {\n                                    cursor_id: iteration_cursor_id,\n                                    pc_if_empty: loop_end,\n                                });\n                            }\n                            program.preassign_label_to_next_insn(loop_start);\n                        }\n                        (\n                            Scan::VirtualTable {\n                                idx_num,\n                                idx_str,\n                                constraints,\n                            },\n                            Table::Virtual(_),\n                        ) => {\n                            let (start_reg, count, maybe_idx_str, maybe_idx_int) = {\n                                let args_needed = constraints.len();\n                                let start_reg = program.alloc_registers(args_needed);\n\n                                for (argv_index, expr) in constraints.iter().enumerate() {\n                                    let target_reg = start_reg + argv_index;\n                                    translate_expr(\n                                        program,\n                                        Some(table_references),\n                                        expr,\n                                        target_reg,\n                                        &t_ctx.resolver,\n                                    )?;\n                                }\n\n                                // If best_index provided an idx_str, translate it.\n                                let maybe_idx_str = if let Some(idx_str) = idx_str {\n                                    let reg = program.alloc_register();\n                                    program.emit_insn(Insn::String8 {\n                                        dest: reg,\n                                        value: idx_str.to_owned(),\n                                    });\n                                    Some(reg)\n                                } else {\n                                    None\n                                };\n                                (start_reg, args_needed, maybe_idx_str, Some(*idx_num))\n                            };\n\n                            // Emit VFilter with the computed arguments.\n                            program.emit_insn(Insn::VFilter {\n                                cursor_id: table_cursor_id\n                                    .expect(\"Virtual tables do not support covering indexes\"),\n                                arg_count: count,\n                                args_reg: start_reg,\n                                idx_str: maybe_idx_str,\n                                idx_num: maybe_idx_int.unwrap_or(0) as usize,\n                                pc_if_empty: loop_end,\n                            });\n                            program.preassign_label_to_next_insn(loop_start);\n                        }\n                        (\n                            Scan::Subquery { iter_dir },\n                            Table::FromClauseSubquery(from_clause_subquery),\n                        ) => {\n                            match from_clause_subquery.plan.select_query_destination() {\n                                Some(QueryDestination::CoroutineYield {\n                                    yield_reg,\n                                    coroutine_implementation_start,\n                                }) => {\n                                    turso_assert_eq!(\n                                        *iter_dir,\n                                        IterationDirection::Forwards,\n                                        \"coroutine-backed subqueries cannot scan backwards\"\n                                    );\n                                    // Coroutine-based subquery execution\n                                    // In case the subquery is an inner loop, it needs to be reinitialized on each iteration of the outer loop.\n                                    program.emit_insn(Insn::InitCoroutine {\n                                        yield_reg: *yield_reg,\n                                        jump_on_definition: BranchOffset::Offset(0),\n                                        start_offset: *coroutine_implementation_start,\n                                    });\n                                    program.preassign_label_to_next_insn(loop_start);\n                                    // A subquery within the main loop of a parent query has no cursor, so instead of advancing the cursor,\n                                    // it emits a Yield which jumps back to the main loop of the subquery itself to retrieve the next row.\n                                    // When the subquery coroutine completes, this instruction jumps to the label at the top of the termination_label_stack,\n                                    // which in this case is the end of the Yield-Goto loop in the parent query.\n                                    program.emit_insn(Insn::Yield {\n                                        yield_reg: *yield_reg,\n                                        end_offset: loop_end,\n                                        subtype_clear_start_reg: 0,\n                                        subtype_clear_count: 0,\n                                    });\n                                }\n                                Some(QueryDestination::EphemeralTable { cursor_id, .. }) => {\n                                    // Materialized CTE - scan the ephemeral table with Rewind/Next\n                                    if *iter_dir == IterationDirection::Backwards {\n                                        program.emit_insn(Insn::Last {\n                                            cursor_id: *cursor_id,\n                                            pc_if_empty: loop_end,\n                                        });\n                                    } else {\n                                        program.emit_insn(Insn::Rewind {\n                                            cursor_id: *cursor_id,\n                                            pc_if_empty: loop_end,\n                                        });\n                                    }\n                                    program.preassign_label_to_next_insn(loop_start);\n                                    emit_materialized_subquery_result_columns(\n                                        program,\n                                        from_clause_subquery,\n                                        *cursor_id,\n                                        None,\n                                    );\n                                }\n                                _ => {\n                                    unreachable!(\"Subquery table with unexpected query destination\")\n                                }\n                            }\n                        }\n                        _ => unreachable!(\n                            \"{:?} scan cannot be used with {:?} table\",\n                            scan, table.table\n                        ),\n                    }\n                    if let Some(table_cursor_id) = table_cursor_id {\n                        if let Some(index_cursor_id) = index_cursor_id {\n                            program.emit_insn(Insn::DeferredSeek {\n                                index_cursor_id,\n                                table_cursor_id,\n                            });\n                        }\n                    }\n                }\n                Operation::Search(search) => {\n                    let materialized_subquery_storage = match (&table.table, search) {\n                        (\n                            Table::FromClauseSubquery(from_clause_subquery),\n                            Search::Seek {\n                                index: Some(index), ..\n                            },\n                        ) if index.ephemeral => {\n                            materialized_from_clause_subquery_storage(from_clause_subquery)\n                        }\n                        _ => None,\n                    };\n\n                    // Open the loop for the index search.\n                    // Rowid equality point lookups are handled with a SeekRowid instruction which does not loop, since it is a single row lookup.\n                    match search {\n                        Search::RowidEq { cmp_expr } => {\n                            assert!(\n                                !matches!(table.table, Table::FromClauseSubquery(_)),\n                                \"Subqueries do not support rowid seeks\"\n                            );\n                            let src_reg = program.alloc_register();\n                            translate_expr(\n                                program,\n                                Some(table_references),\n                                cmp_expr,\n                                src_reg,\n                                &t_ctx.resolver,\n                            )?;\n                            program.emit_insn(Insn::SeekRowid {\n                                cursor_id: table_cursor_id\n                                    .expect(\"Search::RowidEq requires a table cursor\"),\n                                src_reg,\n                                target_pc: next,\n                            });\n                        }\n                        Search::Seek {\n                            index, seek_def, ..\n                        } => {\n                            // Otherwise, it's an index/rowid scan, i.e. first a seek is performed and then a scan until the comparison expression is not satisfied anymore.\n                            let mut bloom_filter = false;\n                            if let Some(index) = index {\n                                if index.ephemeral\n                                    && !matches!(\n                                        materialized_subquery_storage,\n                                        Some(MaterializedFromClauseSubqueryStorage::DirectIndex)\n                                    )\n                                {\n                                    // Build auxiliary ephemeral indexes lazily from the row source,\n                                    // whether it is a base table or a table-backed materialized subquery.\n                                    let table_has_rowid = if let Table::BTree(btree) = &table.table\n                                    {\n                                        btree.has_rowid\n                                    } else {\n                                        matches!(&table.table, Table::FromClauseSubquery(_))\n                                    };\n                                    let num_seek_keys = seek_def.size(&seek_def.start);\n                                    let AutoIndexResult {\n                                        use_bloom_filter, ..\n                                    } = emit_autoindex(\n                                        program,\n                                        AutoIndexBuild {\n                                            index,\n                                            table_cursor_id: table_cursor_id.expect(\n                                                \"an ephemeral index must have a source table cursor\",\n                                            ),\n                                            index_cursor_id: index_cursor_id.expect(\n                                                \"an ephemeral index must have an index cursor\",\n                                            ),\n                                            table_has_rowid,\n                                            num_seek_keys,\n                                            seek_def,\n                                            affinity_str: plan::synthesized_seek_affinity_str(\n                                                index, seek_def,\n                                            )\n                                            .as_ref(),\n                                        },\n                                    )?;\n                                    bloom_filter = use_bloom_filter;\n                                }\n                            }\n\n                            let seek_cursor_id = if materialized_subquery_storage.is_some() {\n                                index_cursor_id\n                                    .expect(\"materialized subquery must have index cursor\")\n                            } else {\n                                temp_cursor_id.unwrap_or_else(|| {\n                                    index_cursor_id.unwrap_or_else(|| {\n                                        table_cursor_id.expect(\n                                        \"Either ephemeral or index or table cursor must be opened\",\n                                    )\n                                    })\n                                })\n                            };\n\n                            let max_registers = seek_def\n                                .size(&seek_def.start)\n                                .max(seek_def.size(&seek_def.end));\n                            let start_reg = program.alloc_registers(max_registers);\n                            SeekEmitter::new(\n                                program,\n                                table_references,\n                                seek_def,\n                                t_ctx,\n                                seek_cursor_id,\n                                start_reg,\n                                loop_end,\n                                index.as_ref(),\n                            )\n                            .emit(loop_start, bloom_filter)?;\n\n                            if let Some(materialized_subquery_storage) =\n                                materialized_subquery_storage\n                            {\n                                let index_cursor_id = index_cursor_id\n                                    .expect(\"materialized subquery seek requires index cursor\");\n                                let Table::FromClauseSubquery(from_clause_subquery) = &table.table\n                                else {\n                                    unreachable!(\"materialized subquery seek requires subquery\")\n                                };\n                                match materialized_subquery_storage {\n                                    MaterializedFromClauseSubqueryStorage::TableBacked => {\n                                        let table_cursor_id = table_cursor_id\n                                            .expect(\"materialized subquery must have table cursor\");\n                                        program.emit_insn(Insn::DeferredSeek {\n                                            index_cursor_id,\n                                            table_cursor_id,\n                                        });\n                                        emit_materialized_subquery_result_columns(\n                                            program,\n                                            from_clause_subquery,\n                                            table_cursor_id,\n                                            None,\n                                        );\n                                    }\n                                    MaterializedFromClauseSubqueryStorage::DirectIndex => {\n                                        let index = index.as_ref().expect(\n                                            \"direct-index materialized subquery requires index\",\n                                        );\n                                        emit_materialized_subquery_result_columns(\n                                            program,\n                                            from_clause_subquery,\n                                            index_cursor_id,\n                                            Some(index.as_ref()),\n                                        );\n                                    }\n                                }\n                            } else {\n                                // Only emit DeferredSeek for non-subquery tables\n                                if let Some(index_cursor_id) = index_cursor_id {\n                                    if let Some(table_cursor_id) = table_cursor_id {\n                                        // Don't do a btree table seek until it's actually necessary to read from the table.\n                                        program.emit_insn(Insn::DeferredSeek {\n                                            index_cursor_id,\n                                            table_cursor_id,\n                                        });\n                                    }\n                                }\n                            }\n                        }\n                        Search::InSeek { index, source } => {\n                            let is_rowid = index.is_none();\n                            let ephemeral_cursor_id = open_in_seek_source_cursor(\n                                program,\n                                table_references,\n                                &t_ctx.resolver,\n                                index.as_ref(),\n                                source,\n                            )?;\n\n                            program.emit_insn(Insn::NullRow {\n                                cursor_id: ephemeral_cursor_id,\n                            });\n                            program.emit_insn(Insn::Rewind {\n                                cursor_id: ephemeral_cursor_id,\n                                pc_if_empty: loop_end,\n                            });\n\n                            let outer_loop_start = program.allocate_label();\n                            program.preassign_label_to_next_insn(outer_loop_start);\n                            let seek_reg = program.alloc_register();\n                            // The emitted loop is:\n                            //   for each RHS key in the ephemeral cursor\n                            //     seek table/index to that key\n                            //     scan all matching rows for that key\n                            program.emit_insn(Insn::Column {\n                                cursor_id: ephemeral_cursor_id,\n                                column: 0,\n                                dest: seek_reg,\n                                default: None,\n                            });\n\n                            let next_val_label = program.allocate_label();\n                            program.emit_insn(Insn::IsNull {\n                                reg: seek_reg,\n                                target_pc: next_val_label,\n                            });\n\n                            if is_rowid {\n                                program.emit_insn(Insn::SeekRowid {\n                                    cursor_id: table_cursor_id\n                                        .expect(\"InSeek rowid requires table cursor\"),\n                                    src_reg: seek_reg,\n                                    target_pc: next_val_label,\n                                });\n                            } else {\n                                let idx_cursor = index_cursor_id\n                                    .expect(\"InSeek with index requires index cursor\");\n                                program.emit_insn(Insn::SeekGE {\n                                    cursor_id: idx_cursor,\n                                    start_reg: seek_reg,\n                                    num_regs: 1,\n                                    target_pc: next_val_label,\n                                    is_index: true,\n                                    eq_only: false,\n                                });\n                                program.preassign_label_to_next_insn(loop_start);\n                                program.emit_insn(Insn::IdxGT {\n                                    cursor_id: idx_cursor,\n                                    start_reg: seek_reg,\n                                    num_regs: 1,\n                                    target_pc: next_val_label,\n                                });\n                                if let Some(table_cursor_id) = table_cursor_id {\n                                    program.emit_insn(Insn::DeferredSeek {\n                                        index_cursor_id: idx_cursor,\n                                        table_cursor_id,\n                                    });\n                                }\n                            }\n\n                            // `close_loop` uses this metadata to stitch together the outer\n                            // ephemeral-value loop and the inner scan over matches for the\n                            // current value.\n                            t_ctx.meta_in_seeks[joined_table_index] = Some(InSeekMetadata {\n                                ephemeral_cursor_id,\n                                outer_loop_start,\n                                next_val_label,\n                            });\n                        }\n                    }\n                }\n                Operation::IndexMethodQuery(query) => {\n                    let start_reg = program.alloc_registers(query.arguments.len() + 1);\n                    program.emit_int(query.pattern_idx as i64, start_reg);\n                    for i in 0..query.arguments.len() {\n                        translate_expr(\n                            program,\n                            Some(table_references),\n                            &query.arguments[i],\n                            start_reg + 1 + i,\n                            &t_ctx.resolver,\n                        )?;\n                    }\n                    program.emit_insn(Insn::IndexMethodQuery {\n                        db: crate::MAIN_DB_ID,\n                        cursor_id: index_cursor_id.expect(\"IndexMethod requires a index cursor\"),\n                        start_reg,\n                        count_reg: query.arguments.len() + 1,\n                        pc_if_empty: loop_end,\n                    });\n                    program.preassign_label_to_next_insn(loop_start);\n                    if let Some(table_cursor_id) = table_cursor_id {\n                        if let Some(index_cursor_id) = index_cursor_id {\n                            program.emit_insn(Insn::DeferredSeek {\n                                index_cursor_id,\n                                table_cursor_id,\n                            });\n                        }\n                    }\n                }\n                Operation::HashJoin(hash_join_op) => {\n                    HashProbeSetupEmitter::new(\n                        program,\n                        t_ctx,\n                        table_references,\n                        subqueries,\n                        predicates,\n                        hash_join_op,\n                        &mode,\n                        table_cursor_id.expect(\"Probe table must have a cursor\"),\n                        loop_start,\n                        loop_end,\n                        next,\n                        &live_table_ids,\n                    )\n                    .emit()?;\n                }\n                Operation::MultiIndexScan(multi_idx_op) => {\n                    emit_multi_index_scan_loop(\n                        program,\n                        t_ctx,\n                        table,\n                        table_references,\n                        multi_idx_op,\n                        loop_start,\n                        loop_end,\n                    )?;\n                }\n            }\n\n            let condition_fail_target = if let Operation::HashJoin(ref hj) = table.op {\n                t_ctx\n                    .hash_table_contexts\n                    .get(&hj.build_table_idx)\n                    .map(|ctx| ctx.labels.next)\n                    .expect(\"should have hash context for build table\")\n            } else {\n                next\n            };\n            let is_outer_hj_probe = matches!(table.op, Operation::HashJoin(ref hj) if matches!(\n                hj.join_type,\n                HashJoinType::LeftOuter | HashJoinType::FullOuter\n            ));\n\n            // Emit OUTER JOIN conditions (must run before setting match flags).\n            LoopConditionEmitter::new(\n                program,\n                t_ctx,\n                table_references,\n                join_order,\n                predicates,\n                join_index,\n                condition_fail_target,\n                true,\n                subqueries,\n            )\n            .emit()?;\n\n            // Set the LEFT JOIN match flag. Skip outer hash join probes - they use\n            // HashMarkMatched / check_outer instead.\n            if let Some(join_info) = table.join_info.as_ref() {\n                if join_info.is_outer() && !is_outer_hj_probe {\n                    let lj_meta = t_ctx.meta_left_joins[joined_table_index].as_ref().unwrap();\n                    program.resolve_label(lj_meta.label_match_flag_set_true, program.offset());\n                    program.emit_insn(Insn::Integer {\n                        value: 1,\n                        dest: lj_meta.reg_match_flag,\n                    });\n                }\n            }\n\n            // Outer hash joins: mark the build entry as matched.\n            if let Operation::HashJoin(ref hj) = table.op {\n                if matches!(\n                    hj.join_type,\n                    HashJoinType::LeftOuter | HashJoinType::FullOuter\n                ) {\n                    let build_table = &table_references.joined_tables()[hj.build_table_idx];\n                    let hash_table_id: usize = build_table.internal_id.into();\n                    program.emit_insn(Insn::HashMarkMatched { hash_table_id });\n\n                    // FULL OUTER: also set the probe-side match flag.\n                    if matches!(hj.join_type, HashJoinType::FullOuter) {\n                        let probe_idx = hj.probe_table_idx;\n                        if let Some(lj_meta) = t_ctx.meta_left_joins[probe_idx].as_ref() {\n                            program\n                                .resolve_label(lj_meta.label_match_flag_set_true, program.offset());\n                            program.emit_insn(Insn::Integer {\n                                value: 1,\n                                dest: lj_meta.reg_match_flag,\n                            });\n                        }\n                    }\n                }\n            }\n\n            // Emit non-OUTER JOIN conditions.\n            let from_outer_join = false;\n            LoopConditionEmitter::new(\n                program,\n                t_ctx,\n                table_references,\n                join_order,\n                predicates,\n                join_index,\n                condition_fail_target,\n                from_outer_join,\n                subqueries,\n            )\n            .emit()?;\n\n            // ANTI-JOIN: all conditions passed means a match was found.\n            // Skip the outer row by jumping to the outer loop's Next.\n            // label_body is resolved later in emit_loop, right before the body is emitted.\n            if let Some(join_info) = table.join_info.as_ref() {\n                if join_info.is_anti() {\n                    let sa_meta = t_ctx.meta_semi_anti_joins[joined_table_index]\n                        .as_ref()\n                        .expect(\"anti-join must have SemiAntiJoinMetadata\");\n                    program.add_comment(program.offset(), \"anti-join: match found, skip outer row\");\n                    program.emit_insn(Insn::Goto {\n                        target_pc: sa_meta.label_next_outer,\n                    });\n                }\n            }\n\n            // Outer hash joins wrap inner loops in a Gosub subroutine so that\n            // unmatched-row emission paths can re-enter them (cursors get Rewind'd).\n            if let Operation::HashJoin(ref hj) = table.op {\n                if matches!(\n                    hj.join_type,\n                    HashJoinType::LeftOuter | HashJoinType::FullOuter\n                ) {\n                    let return_reg = program.alloc_register();\n                    let gosub_label = program.allocate_label();\n                    let skip_label = program.allocate_label();\n\n                    program.emit_insn(Insn::Gosub {\n                        target_pc: gosub_label,\n                        return_reg,\n                    });\n                    program.emit_insn(Insn::Goto {\n                        target_pc: skip_label,\n                    });\n                    // Subroutine body starts here (inner loops follow)\n                    program.preassign_label_to_next_insn(gosub_label);\n\n                    if let Some(hash_ctx) = t_ctx.hash_table_contexts.get_mut(&hj.build_table_idx) {\n                        hash_ctx.inner_loop_gosub_reg = Some(return_reg);\n                        hash_ctx.labels.inner_loop_gosub = Some(gosub_label);\n                        hash_ctx.labels.inner_loop_skip = Some(skip_label);\n                    }\n                }\n            }\n        }\n\n        if subqueries.iter().any(|s| {\n            !s.has_been_evaluated() && matches!(s.eval_phase, SubqueryEvalPhase::BeforeLoop)\n        }) {\n            crate::bail_parse_error!(\n                \"all before-loop subqueries should have already been emitted, but found {} unevaluated subqueries\",\n                subqueries\n                    .iter()\n                    .filter(|s| {\n                        !s.has_been_evaluated()\n                            && matches!(s.eval_phase, SubqueryEvalPhase::BeforeLoop)\n                    })\n                    .count()\n            );\n        }\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "core/translate/main_loop/seek.rs",
    "content": "use super::*;\n\nfn index_seek_affinities(\n    idx: &Index,\n    tables: &TableReferences,\n    seek_def: &SeekDef,\n    seek_key: &SeekKey,\n) -> String {\n    let table = tables\n        .joined_tables()\n        .iter()\n        .find(|jt| jt.table.get_name() == idx.table_name)\n        .expect(\"index source table not found in table references\");\n\n    idx.columns\n        .iter()\n        .zip(seek_def.iter(seek_key))\n        .map(|(ic, key_component)| {\n            let col_aff = if ic.expr.is_some() {\n                Affinity::Blob\n            } else {\n                table\n                    .table\n                    .get_column_at(ic.pos_in_table)\n                    .expect(\"index column position out of bounds\")\n                    .affinity()\n            };\n            match key_component {\n                SeekKeyComponent::Expr(expr) if col_aff.expr_needs_no_affinity_change(expr) => {\n                    affinity::SQLITE_AFF_NONE\n                }\n                _ => col_aff.aff_mask(),\n            }\n        })\n        .collect()\n}\n\nfn encode_seek_keys_for_custom_types(\n    program: &mut ProgramBuilder,\n    tables: &TableReferences,\n    seek_index: &Arc<Index>,\n    start_reg: usize,\n    num_keys: usize,\n    idx_col_offset: usize,\n    resolver: &Resolver<'_>,\n) -> crate::Result<()> {\n    let table = tables\n        .find_table_by_identifier(&seek_index.table_name)\n        .or_else(|| tables.find_table_by_table_name(&seek_index.table_name));\n    let table = match table {\n        Some(t) => t,\n        None => return Ok(()),\n    };\n    let columns = table.columns();\n    for i in 0..num_keys {\n        let idx_col_pos = idx_col_offset + i;\n        if idx_col_pos >= seek_index.columns.len() {\n            break;\n        }\n        let idx_col = &seek_index.columns[idx_col_pos];\n        let table_col = match columns.get(idx_col.pos_in_table) {\n            Some(c) => c,\n            None => continue,\n        };\n        let type_def = match resolver\n            .schema()\n            .get_type_def(&table_col.ty_str, table.is_strict())\n        {\n            Some(td) => td,\n            None => continue,\n        };\n        let encode_expr = match &type_def.encode {\n            Some(e) => e,\n            None => continue,\n        };\n        let reg = start_reg + i;\n        let skip_label = program.allocate_label();\n        program.emit_insn(Insn::IsNull {\n            reg,\n            target_pc: skip_label,\n        });\n        crate::translate::expr::emit_type_expr(\n            program,\n            encode_expr,\n            reg,\n            reg,\n            table_col,\n            type_def,\n            resolver,\n        )?;\n        program.resolve_label(skip_label, program.offset());\n    }\n    Ok(())\n}\n\n/// Seek-based loop setup.\n///\n/// A seek loop has a real two-phase contract:\n/// 1. Emit and position using the start bound.\n/// 2. Emit the termination bound and anchor `loop_start`.\npub(super) struct SeekEmitter<'a, 'plan> {\n    program: &'a mut ProgramBuilder,\n    tables: &'a TableReferences,\n    seek_def: &'a SeekDef,\n    t_ctx: &'a mut TranslateCtx<'plan>,\n    seek_cursor_id: usize,\n    start_reg: usize,\n    loop_end: BranchOffset,\n    seek_index: Option<&'a Arc<Index>>,\n    is_index: bool,\n}\n\nimpl<'a, 'plan> SeekEmitter<'a, 'plan> {\n    #[allow(clippy::too_many_arguments)]\n    pub(super) fn new(\n        program: &'a mut ProgramBuilder,\n        tables: &'a TableReferences,\n        seek_def: &'a SeekDef,\n        t_ctx: &'a mut TranslateCtx<'plan>,\n        seek_cursor_id: usize,\n        start_reg: usize,\n        loop_end: BranchOffset,\n        seek_index: Option<&'a Arc<Index>>,\n    ) -> Self {\n        Self {\n            program,\n            tables,\n            seek_def,\n            t_ctx,\n            seek_cursor_id,\n            start_reg,\n            loop_end,\n            seek_index,\n            is_index: seek_index.is_some(),\n        }\n    }\n\n    /// Emit the start bound and position the cursor at the first candidate row.\n    fn emit_start_bound(&mut self, use_bloom_filter: bool) -> Result<()> {\n        if self.seek_def.prefix.is_empty()\n            && matches!(self.seek_def.start.last_component, SeekKeyComponent::None)\n        {\n            match self.seek_def.iter_dir {\n                IterationDirection::Forwards => {\n                    if self\n                        .seek_index\n                        .is_some_and(|index| index.columns[0].order == SortOrder::Asc)\n                    {\n                        self.program.emit_null(self.start_reg, None);\n                        self.program.emit_insn(Insn::SeekGT {\n                            is_index: self.is_index,\n                            cursor_id: self.seek_cursor_id,\n                            start_reg: self.start_reg,\n                            num_regs: 1,\n                            target_pc: self.loop_end,\n                        });\n                    } else {\n                        self.program.emit_insn(Insn::Rewind {\n                            cursor_id: self.seek_cursor_id,\n                            pc_if_empty: self.loop_end,\n                        });\n                    }\n                }\n                IterationDirection::Backwards => {\n                    if self\n                        .seek_index\n                        .is_some_and(|index| index.columns[0].order == SortOrder::Desc)\n                    {\n                        self.program.emit_null(self.start_reg, None);\n                        self.program.emit_insn(Insn::SeekLT {\n                            is_index: self.is_index,\n                            cursor_id: self.seek_cursor_id,\n                            start_reg: self.start_reg,\n                            num_regs: 1,\n                            target_pc: self.loop_end,\n                        });\n                    } else {\n                        self.program.emit_insn(Insn::Last {\n                            cursor_id: self.seek_cursor_id,\n                            pc_if_empty: self.loop_end,\n                        });\n                    }\n                }\n            }\n            return Ok(());\n        }\n\n        for (i, key) in self.seek_def.iter(&self.seek_def.start).enumerate() {\n            let reg = self.start_reg + i;\n            match key {\n                SeekKeyComponent::Expr(expr) => {\n                    translate_expr_no_constant_opt(\n                        self.program,\n                        Some(self.tables),\n                        expr,\n                        reg,\n                        &self.t_ctx.resolver,\n                        NoConstantOptReason::RegisterReuse,\n                    )?;\n                    if !expr.is_nonnull(self.tables) {\n                        self.program.emit_insn(Insn::IsNull {\n                            reg,\n                            target_pc: self.loop_end,\n                        });\n                    }\n                }\n                SeekKeyComponent::Null => self.program.emit_null(reg, None),\n                SeekKeyComponent::None => {\n                    unreachable!(\"None component is not possible in iterator\")\n                }\n            }\n        }\n        let num_regs = self.seek_def.size(&self.seek_def.start);\n\n        if let Some(idx) = self.seek_index {\n            encode_seek_keys_for_custom_types(\n                self.program,\n                self.tables,\n                idx,\n                self.start_reg,\n                num_regs,\n                0,\n                &self.t_ctx.resolver,\n            )?;\n            let affinities =\n                index_seek_affinities(idx, self.tables, self.seek_def, &self.seek_def.start);\n            if affinities.chars().any(|c| c != affinity::SQLITE_AFF_NONE) {\n                self.program.emit_insn(Insn::Affinity {\n                    start_reg: self.start_reg,\n                    count: std::num::NonZeroUsize::new(num_regs).unwrap(),\n                    affinities,\n                });\n            }\n            if use_bloom_filter {\n                turso_assert!(\n                    idx.ephemeral,\n                    \"bloom filter can only be used with ephemeral indexes\"\n                );\n                self.program.emit_insn(Insn::Filter {\n                    cursor_id: self.seek_cursor_id,\n                    key_reg: self.start_reg,\n                    num_keys: num_regs,\n                    target_pc: self.loop_end,\n                });\n            }\n        }\n\n        match self.seek_def.start.op {\n            SeekOp::GE { eq_only } => self.program.emit_insn(Insn::SeekGE {\n                is_index: self.is_index,\n                cursor_id: self.seek_cursor_id,\n                start_reg: self.start_reg,\n                num_regs,\n                target_pc: self.loop_end,\n                eq_only,\n            }),\n            SeekOp::GT => self.program.emit_insn(Insn::SeekGT {\n                is_index: self.is_index,\n                cursor_id: self.seek_cursor_id,\n                start_reg: self.start_reg,\n                num_regs,\n                target_pc: self.loop_end,\n            }),\n            SeekOp::LE { eq_only } => self.program.emit_insn(Insn::SeekLE {\n                is_index: self.is_index,\n                cursor_id: self.seek_cursor_id,\n                start_reg: self.start_reg,\n                num_regs,\n                target_pc: self.loop_end,\n                eq_only,\n            }),\n            SeekOp::LT => self.program.emit_insn(Insn::SeekLT {\n                is_index: self.is_index,\n                cursor_id: self.seek_cursor_id,\n                start_reg: self.start_reg,\n                num_regs,\n                target_pc: self.loop_end,\n            }),\n        };\n\n        Ok(())\n    }\n\n    /// Emit the end bound check and anchor the loop-start label.\n    fn emit_termination(&mut self, loop_start: BranchOffset) -> Result<()> {\n        if self.seek_def.prefix.is_empty()\n            && matches!(self.seek_def.end.last_component, SeekKeyComponent::None)\n        {\n            self.program.preassign_label_to_next_insn(loop_start);\n            match self.seek_def.iter_dir {\n                IterationDirection::Forwards => {\n                    if self\n                        .seek_index\n                        .is_some_and(|index| index.columns[0].order == SortOrder::Desc)\n                    {\n                        self.program.emit_null(self.start_reg, None);\n                        self.program.emit_insn(Insn::IdxGE {\n                            cursor_id: self.seek_cursor_id,\n                            start_reg: self.start_reg,\n                            num_regs: 1,\n                            target_pc: self.loop_end,\n                        });\n                    }\n                }\n                IterationDirection::Backwards => {\n                    if self\n                        .seek_index\n                        .is_some_and(|index| index.columns[0].order == SortOrder::Asc)\n                    {\n                        self.program.emit_null(self.start_reg, None);\n                        self.program.emit_insn(Insn::IdxLE {\n                            cursor_id: self.seek_cursor_id,\n                            start_reg: self.start_reg,\n                            num_regs: 1,\n                            target_pc: self.loop_end,\n                        });\n                    }\n                }\n            }\n            return Ok(());\n        }\n\n        let num_regs = self.seek_def.size(&self.seek_def.end);\n        let last_reg = self.start_reg + self.seek_def.prefix.len();\n        match &self.seek_def.end.last_component {\n            SeekKeyComponent::Expr(expr) => {\n                translate_expr_no_constant_opt(\n                    self.program,\n                    Some(self.tables),\n                    expr,\n                    last_reg,\n                    &self.t_ctx.resolver,\n                    NoConstantOptReason::RegisterReuse,\n                )?;\n                if let Some(idx) = self.seek_index {\n                    encode_seek_keys_for_custom_types(\n                        self.program,\n                        self.tables,\n                        idx,\n                        last_reg,\n                        1,\n                        self.seek_def.prefix.len(),\n                        &self.t_ctx.resolver,\n                    )?;\n                    let affinities =\n                        index_seek_affinities(idx, self.tables, self.seek_def, &self.seek_def.end);\n                    if affinities.chars().any(|c| c != affinity::SQLITE_AFF_NONE) {\n                        self.program.emit_insn(Insn::Affinity {\n                            start_reg: self.start_reg,\n                            count: std::num::NonZeroUsize::new(num_regs).unwrap(),\n                            affinities,\n                        });\n                    }\n                }\n                if !expr.is_nonnull(self.tables) {\n                    self.program.emit_insn(Insn::IsNull {\n                        reg: last_reg,\n                        target_pc: self.loop_end,\n                    });\n                }\n            }\n            SeekKeyComponent::Null => self.program.emit_null(last_reg, None),\n            SeekKeyComponent::None => {}\n        }\n\n        self.program.preassign_label_to_next_insn(loop_start);\n        let mut rowid_reg = None;\n        let mut affinity = None;\n        if !self.is_index {\n            rowid_reg = Some(self.program.alloc_register());\n            self.program.emit_insn(Insn::RowId {\n                cursor_id: self.seek_cursor_id,\n                dest: rowid_reg.unwrap(),\n            });\n\n            affinity = if let Some(table_ref) = self\n                .tables\n                .joined_tables()\n                .iter()\n                .find(|t| t.columns().iter().any(|c| c.is_rowid_alias()))\n            {\n                if let Some(rowid_col_idx) =\n                    table_ref.columns().iter().position(|c| c.is_rowid_alias())\n                {\n                    Some(table_ref.columns()[rowid_col_idx].affinity())\n                } else {\n                    Some(Affinity::Numeric)\n                }\n            } else {\n                Some(Affinity::Numeric)\n            };\n        }\n\n        match (self.is_index, self.seek_def.end.op) {\n            (true, SeekOp::GE { .. }) => self.program.emit_insn(Insn::IdxGE {\n                cursor_id: self.seek_cursor_id,\n                start_reg: self.start_reg,\n                num_regs,\n                target_pc: self.loop_end,\n            }),\n            (true, SeekOp::GT) => self.program.emit_insn(Insn::IdxGT {\n                cursor_id: self.seek_cursor_id,\n                start_reg: self.start_reg,\n                num_regs,\n                target_pc: self.loop_end,\n            }),\n            (true, SeekOp::LE { .. }) => self.program.emit_insn(Insn::IdxLE {\n                cursor_id: self.seek_cursor_id,\n                start_reg: self.start_reg,\n                num_regs,\n                target_pc: self.loop_end,\n            }),\n            (true, SeekOp::LT) => self.program.emit_insn(Insn::IdxLT {\n                cursor_id: self.seek_cursor_id,\n                start_reg: self.start_reg,\n                num_regs,\n                target_pc: self.loop_end,\n            }),\n            (false, SeekOp::GE { .. }) => self.program.emit_insn(Insn::Ge {\n                lhs: rowid_reg.unwrap(),\n                rhs: self.start_reg,\n                target_pc: self.loop_end,\n                flags: CmpInsFlags::default()\n                    .jump_if_null()\n                    .with_affinity(affinity.unwrap()),\n                collation: self.program.curr_collation(),\n            }),\n            (false, SeekOp::GT) => self.program.emit_insn(Insn::Gt {\n                lhs: rowid_reg.unwrap(),\n                rhs: self.start_reg,\n                target_pc: self.loop_end,\n                flags: CmpInsFlags::default()\n                    .jump_if_null()\n                    .with_affinity(affinity.unwrap()),\n                collation: self.program.curr_collation(),\n            }),\n            (false, SeekOp::LE { .. }) => self.program.emit_insn(Insn::Le {\n                lhs: rowid_reg.unwrap(),\n                rhs: self.start_reg,\n                target_pc: self.loop_end,\n                flags: CmpInsFlags::default()\n                    .jump_if_null()\n                    .with_affinity(affinity.unwrap()),\n                collation: self.program.curr_collation(),\n            }),\n            (false, SeekOp::LT) => self.program.emit_insn(Insn::Lt {\n                lhs: rowid_reg.unwrap(),\n                rhs: self.start_reg,\n                target_pc: self.loop_end,\n                flags: CmpInsFlags::default()\n                    .jump_if_null()\n                    .with_affinity(affinity.unwrap()),\n                collation: self.program.curr_collation(),\n            }),\n        }\n        Ok(())\n    }\n\n    pub(super) fn emit(mut self, loop_start: BranchOffset, use_bloom_filter: bool) -> Result<()> {\n        self.emit_start_bound(use_bloom_filter)?;\n        self.emit_termination(loop_start)\n    }\n}\n"
  },
  {
    "path": "core/translate/mod.rs",
    "content": "//! The VDBE bytecode code generator.\n//!\n//! This module is responsible for translating the SQL AST into a sequence of\n//! instructions for the VDBE. The VDBE is a register-based virtual machine that\n//! executes bytecode instructions. This code generator is responsible for taking\n//! the SQL AST and generating the corresponding VDBE instructions. For example,\n//! a SELECT statement will be translated into a sequence of instructions that\n//! will read rows from the database and filter them according to a WHERE clause.\n\npub(crate) mod aggregation;\npub(crate) mod alter;\npub(crate) mod analyze;\npub(crate) mod attach;\npub(crate) mod collate;\nmod compound_select;\npub(crate) mod delete;\npub(crate) mod display;\npub(crate) mod emitter;\npub(crate) mod expr;\npub(crate) mod expression_index;\npub(crate) mod fkeys;\npub(crate) mod group_by;\npub(crate) mod index;\npub(crate) mod insert;\npub(crate) mod integrity_check;\npub(crate) mod logical;\npub(crate) mod main_loop;\npub(crate) mod optimizer;\npub(crate) mod order_by;\npub(crate) mod plan;\npub(crate) mod planner;\npub(crate) mod pragma;\npub(crate) mod result_row;\npub(crate) mod rollback;\npub(crate) mod schema;\npub(crate) mod select;\npub(crate) mod stmt_journal;\npub(crate) mod subquery;\npub(crate) mod transaction;\npub(crate) mod trigger;\npub(crate) mod trigger_exec;\npub(crate) mod update;\npub(crate) mod upsert;\npub(crate) mod vacuum;\nmod values;\npub(crate) mod view;\nmod window;\n\nuse crate::schema::Schema;\nuse crate::storage::pager::Pager;\nuse crate::sync::Arc;\nuse crate::translate::delete::translate_delete;\nuse crate::translate::emitter::Resolver;\nuse crate::vdbe::builder::{ProgramBuilder, ProgramBuilderOpts, QueryMode};\nuse crate::vdbe::Program;\nuse crate::{bail_parse_error, Connection, Result, SymbolTable};\nuse alter::translate_alter_table;\nuse analyze::translate_analyze;\nuse index::{translate_create_index, translate_drop_index, translate_optimize};\nuse insert::translate_insert;\nuse rollback::{translate_release, translate_rollback, translate_savepoint};\nuse schema::{translate_create_table, translate_create_virtual_table, translate_drop_table};\nuse select::translate_select;\nuse tracing::{instrument, Level};\nuse transaction::{translate_tx_begin, translate_tx_commit};\nuse turso_parser::ast;\nuse update::translate_update;\n\n#[instrument(skip_all, level = Level::DEBUG)]\n#[allow(clippy::too_many_arguments)]\npub fn translate(\n    schema: &Schema,\n    stmt: ast::Stmt,\n    pager: Arc<Pager>,\n    connection: Arc<Connection>,\n    syms: &SymbolTable,\n    query_mode: QueryMode,\n    input: &str,\n) -> Result<Program> {\n    tracing::trace!(\"querying {}\", input);\n    let change_cnt_on = matches!(\n        stmt,\n        ast::Stmt::CreateIndex { .. }\n            | ast::Stmt::Delete { .. }\n            | ast::Stmt::Insert { .. }\n            | ast::Stmt::Update { .. }\n    );\n\n    let mut program = ProgramBuilder::new(\n        query_mode,\n        connection.get_capture_data_changes_info().clone(),\n        // These options will be extended whithin each translate program\n        ProgramBuilderOpts {\n            num_cursors: 1,\n            approx_num_insns: 32,\n            approx_num_labels: 2,\n        },\n    );\n\n    program.prologue();\n    let mut resolver = Resolver::new(\n        schema,\n        connection.database_schemas(),\n        connection.attached_databases(),\n        syms,\n        connection.experimental_custom_types_enabled(),\n    );\n\n    match stmt {\n        // There can be no nesting with pragma, so lift it up here\n        ast::Stmt::Pragma { name, body } => {\n            pragma::translate_pragma(\n                &resolver,\n                &name,\n                body,\n                pager,\n                connection.clone(),\n                &mut program,\n            )?;\n        }\n        stmt => translate_inner(stmt, &mut resolver, &mut program, &connection, input)?,\n    };\n\n    program.epilogue(schema);\n\n    program.build(connection, change_cnt_on, input)\n}\n\n// TODO: for now leaving the return value as a Program. But ideally to support nested parsing of arbitraty\n// statements, we would have to return a program builder instead\n/// Translate SQL statement into bytecode program.\npub fn translate_inner(\n    stmt: ast::Stmt,\n    resolver: &mut Resolver,\n    program: &mut ProgramBuilder,\n    connection: &Arc<Connection>,\n    input: &str,\n) -> Result<()> {\n    let is_write = matches!(\n        stmt,\n        ast::Stmt::AlterTable { .. }\n            | ast::Stmt::Analyze { .. }\n            | ast::Stmt::CreateIndex { .. }\n            | ast::Stmt::CreateTable { .. }\n            | ast::Stmt::CreateTrigger { .. }\n            | ast::Stmt::CreateView { .. }\n            | ast::Stmt::CreateMaterializedView { .. }\n            | ast::Stmt::CreateVirtualTable(..)\n            | ast::Stmt::CreateType { .. }\n            | ast::Stmt::Delete { .. }\n            | ast::Stmt::DropIndex { .. }\n            | ast::Stmt::DropTable { .. }\n            | ast::Stmt::DropType { .. }\n            | ast::Stmt::DropView { .. }\n            | ast::Stmt::Reindex { .. }\n            | ast::Stmt::Optimize { .. }\n            | ast::Stmt::Update { .. }\n            | ast::Stmt::Insert { .. }\n    );\n\n    if is_write && connection.get_query_only() {\n        bail_parse_error!(\"Cannot execute write statement in query_only mode\")\n    }\n\n    let is_select = matches!(stmt, ast::Stmt::Select { .. });\n\n    match stmt {\n        ast::Stmt::AlterTable(alter) => {\n            translate_alter_table(alter, resolver, program, connection, input)?;\n        }\n        ast::Stmt::Analyze { name } => translate_analyze(name, resolver, program)?,\n        ast::Stmt::Attach { expr, db_name, key } => {\n            attach::translate_attach(&expr, resolver, &db_name, &key, program, connection.clone())?;\n        }\n        ast::Stmt::Begin { typ, name } => {\n            translate_tx_begin(typ, name, resolver.schema(), program)?\n        }\n        ast::Stmt::Commit { name } => {\n            translate_tx_commit(name, resolver.schema(), resolver, program)?\n        }\n        ast::Stmt::CreateIndex { .. } => {\n            translate_create_index(program, connection, resolver, stmt)?;\n        }\n        ast::Stmt::CreateTable {\n            temporary,\n            if_not_exists,\n            tbl_name,\n            body,\n        } => translate_create_table(\n            tbl_name,\n            resolver,\n            temporary,\n            if_not_exists,\n            body,\n            program,\n            connection,\n        )?,\n        ast::Stmt::CreateTrigger {\n            temporary,\n            if_not_exists,\n            trigger_name,\n            time,\n            event,\n            tbl_name,\n            for_each_row,\n            when_clause,\n            commands,\n        } => {\n            // Reconstruct SQL for storage\n            let sql = trigger::create_trigger_to_sql(\n                temporary,\n                if_not_exists,\n                &trigger_name,\n                time,\n                &event,\n                &tbl_name,\n                for_each_row,\n                when_clause.as_deref(),\n                &commands,\n            );\n            trigger::translate_create_trigger(\n                trigger_name,\n                resolver,\n                temporary,\n                if_not_exists,\n                time,\n                tbl_name,\n                program,\n                sql,\n                &commands,\n                when_clause.as_deref(),\n            )?\n        }\n        ast::Stmt::CreateView {\n            view_name,\n            select,\n            columns,\n            ..\n        } => view::translate_create_view(&view_name, resolver, &select, &columns, program)?,\n        ast::Stmt::CreateMaterializedView {\n            view_name, select, ..\n        } => view::translate_create_materialized_view(\n            &view_name,\n            resolver,\n            &select,\n            connection.clone(),\n            program,\n        )?,\n        ast::Stmt::CreateVirtualTable(vtab) => {\n            translate_create_virtual_table(vtab, resolver, program, connection)?\n        }\n        ast::Stmt::Delete {\n            tbl_name,\n            where_clause,\n            limit,\n            returning,\n            indexed,\n            order_by,\n            with,\n        } => {\n            if !order_by.is_empty() {\n                bail_parse_error!(\"ORDER BY clause is not supported in DELETE\");\n            }\n            if where_clause.is_none() && connection.get_dml_require_where() {\n                bail_parse_error!(\n                    \"DELETE without a WHERE clause is not allowed when require_where (or i_am_a_dummy) is enabled\"\n                );\n            }\n            translate_delete(\n                &tbl_name,\n                resolver,\n                where_clause,\n                limit,\n                returning,\n                indexed,\n                with,\n                program,\n                connection,\n            )?\n        }\n        ast::Stmt::Detach { name } => {\n            attach::translate_detach(&name, resolver, program, connection.clone())?\n        }\n        ast::Stmt::DropIndex {\n            if_exists,\n            idx_name,\n        } => translate_drop_index(&idx_name, resolver, if_exists, program)?,\n        ast::Stmt::DropTable {\n            if_exists,\n            tbl_name,\n        } => translate_drop_table(tbl_name, resolver, if_exists, program, connection)?,\n        ast::Stmt::DropTrigger {\n            if_exists,\n            trigger_name,\n        } => trigger::translate_drop_trigger(resolver, &trigger_name, if_exists, program)?,\n        ast::Stmt::DropView {\n            if_exists,\n            view_name,\n        } => view::translate_drop_view(resolver, &view_name, if_exists, program)?,\n        ast::Stmt::CreateType {\n            if_not_exists,\n            type_name,\n            body,\n        } => {\n            if !connection.experimental_custom_types_enabled() {\n                bail_parse_error!(\"Custom types require --experimental-custom-types flag\");\n            }\n            schema::translate_create_type(&type_name, &body, if_not_exists, resolver, program)?\n        }\n        ast::Stmt::DropType {\n            if_exists,\n            type_name,\n        } => {\n            if !connection.experimental_custom_types_enabled() {\n                bail_parse_error!(\"Custom types require --experimental-custom-types flag\");\n            }\n            schema::translate_drop_type(&type_name, if_exists, resolver, program)?\n        }\n        ast::Stmt::Pragma { .. } => {\n            bail_parse_error!(\"PRAGMA statement cannot be evaluated in a nested context\")\n        }\n        ast::Stmt::Reindex { .. } => bail_parse_error!(\"REINDEX not supported yet\"),\n        ast::Stmt::Optimize { idx_name } => {\n            translate_optimize(idx_name, resolver, program, connection)?\n        }\n        ast::Stmt::Release { name } => translate_release(program, name)?,\n        ast::Stmt::Rollback {\n            tx_name,\n            savepoint_name,\n        } => translate_rollback(program, tx_name, savepoint_name)?,\n        ast::Stmt::Savepoint { name } => translate_savepoint(program, name)?,\n        ast::Stmt::Select(select) => {\n            translate_select(\n                select,\n                resolver,\n                program,\n                plan::QueryDestination::ResultRows,\n                connection,\n            )?;\n        }\n        ast::Stmt::Update(update) => {\n            if update.where_clause.is_none() && connection.get_dml_require_where() {\n                bail_parse_error!(\n                    \"UPDATE without a WHERE clause is not allowed when require_where (or i_am_a_dummy) is enabled\"\n                );\n            }\n            translate_update(update, resolver, program, connection)?\n        }\n        ast::Stmt::Vacuum { name, into } => {\n            vacuum::translate_vacuum(program, name.as_ref(), into.as_deref())?\n        }\n        ast::Stmt::Insert {\n            with,\n            or_conflict,\n            tbl_name,\n            columns,\n            body,\n            returning,\n        } => translate_insert(\n            resolver,\n            or_conflict,\n            tbl_name,\n            columns,\n            body,\n            returning,\n            with,\n            program,\n            connection,\n        )?,\n    };\n\n    // Indicate write operations so that in the epilogue we can emit the correct type of transaction\n    if is_write {\n        program.begin_write_operation();\n    }\n\n    // Indicate read operations so that in the epilogue we can emit the correct type of transaction\n    if is_select && !program.table_references.is_empty() {\n        program.begin_read_operation();\n    }\n\n    Ok(())\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::io::MemoryIO;\n    use crate::schema::{BTreeTable, Table, SQLITE_SEQUENCE_TABLE_NAME};\n    use crate::Database;\n\n    /// Verify that REGEXP produces the correct error when no regexp function is registered.\n    #[test]\n    fn test_regexp_no_function_registered() {\n        let io = Arc::new(MemoryIO::new());\n        let db = Database::open_file(io, \":memory:\").unwrap();\n        let conn = db.connect().unwrap();\n        let schema = db.schema.lock().clone();\n        let pager = conn.pager.load().clone();\n\n        // Use an empty SymbolTable so regexp() is not available.\n        let empty_syms = SymbolTable::new();\n        let mut parser = turso_parser::parser::Parser::new(b\"SELECT 'x' REGEXP 'y'\");\n        let cmd = parser.next().unwrap().unwrap();\n        let stmt = match cmd {\n            ast::Cmd::Stmt(s) => s,\n            _ => panic!(\"expected statement\"),\n        };\n\n        let result = translate(\n            &schema,\n            stmt,\n            pager,\n            conn,\n            &empty_syms,\n            QueryMode::Normal,\n            \"\",\n        );\n        let err = result.unwrap_err().to_string();\n        assert!(\n            err.contains(\"no such function: regexp\"),\n            \"expected 'no such function: regexp', got: {err}\"\n        );\n    }\n\n    #[test]\n    fn test_insert_autoincrement_with_malformed_sqlite_sequence_is_corrupt() {\n        let io = Arc::new(MemoryIO::new());\n        let db = Database::open_file(io, \":memory:\").unwrap();\n        let conn = db.connect().unwrap();\n        conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY AUTOINCREMENT, v TEXT)\")\n            .unwrap();\n\n        let mut schema = db.schema.lock().as_ref().clone();\n        let seq_root_page = schema\n            .get_btree_table(SQLITE_SEQUENCE_TABLE_NAME)\n            .expect(\"sqlite_sequence should exist after creating AUTOINCREMENT table\")\n            .root_page;\n        let malformed_seq =\n            BTreeTable::from_sql(\"CREATE TABLE sqlite_sequence(name)\", seq_root_page)\n                .expect(\"malformed sqlite_sequence SQL should parse\");\n        schema.tables.insert(\n            SQLITE_SEQUENCE_TABLE_NAME.to_string(),\n            Arc::new(Table::BTree(Arc::new(malformed_seq))),\n        );\n\n        let pager = conn.pager.load().clone();\n        let syms = SymbolTable::new();\n\n        let mut parser = turso_parser::parser::Parser::new(b\"INSERT INTO t(v) VALUES('x')\");\n        let cmd = parser.next().unwrap().unwrap();\n        let stmt = match cmd {\n            ast::Cmd::Stmt(s) => s,\n            _ => panic!(\"expected statement\"),\n        };\n\n        let err = translate(&schema, stmt, pager, conn, &syms, QueryMode::Normal, \"\")\n            .expect_err(\"translation should fail with malformed sqlite_sequence\");\n        match err {\n            crate::LimboError::Corrupt(msg) => {\n                assert!(\n                    msg.contains(\"sqlite_sequence\"),\n                    \"expected sqlite_sequence corruption error, got: {msg}\"\n                );\n            }\n            other => panic!(\"expected LimboError::Corrupt, got: {other}\"),\n        }\n    }\n\n    #[test]\n    fn test_insert_autoincrement_with_missing_sqlite_sequence_is_corrupt() {\n        let io = Arc::new(MemoryIO::new());\n        let db = Database::open_file(io, \":memory:\").unwrap();\n        let conn = db.connect().unwrap();\n        conn.execute(\"CREATE TABLE t(id INTEGER PRIMARY KEY AUTOINCREMENT, v TEXT)\")\n            .unwrap();\n\n        let mut schema = db.schema.lock().as_ref().clone();\n        schema.tables.remove(SQLITE_SEQUENCE_TABLE_NAME);\n\n        let pager = conn.pager.load().clone();\n        let syms = SymbolTable::new();\n\n        let mut parser = turso_parser::parser::Parser::new(b\"INSERT INTO t(v) VALUES('x')\");\n        let cmd = parser.next().unwrap().unwrap();\n        let stmt = match cmd {\n            ast::Cmd::Stmt(s) => s,\n            _ => panic!(\"expected statement\"),\n        };\n\n        let err = translate(&schema, stmt, pager, conn, &syms, QueryMode::Normal, \"\")\n            .expect_err(\"translation should fail with missing sqlite_sequence\");\n        match err {\n            crate::LimboError::Corrupt(msg) => {\n                assert!(\n                    msg.contains(\"missing sqlite_sequence\"),\n                    \"expected missing sqlite_sequence error, got: {msg}\"\n                );\n            }\n            other => panic!(\"expected LimboError::Corrupt, got: {other}\"),\n        }\n    }\n}\n"
  },
  {
    "path": "core/translate/optimizer/OPTIMIZER.md",
    "content": "# Overview of the current state of the query optimizer in Limbo\n\nQuery optimization is obviously an important part of any SQL-based database engine. This document is an overview of what we currently do.\n\n## Structure of the optimizer directory\n\n1. `mod.rs`\n   - Provides the high-level optimization interface through `optimize_plan()`\n\n2. `access_method.rs`\n   - Determines what is the best index to use when joining a table to a set of preceding tables\n\n3. `constraints.rs` - Manages query constraints:\n   - Extracts constraints from the WHERE clause\n   - Determines which constraints are usable with indexes\n\n4. `cost.rs`\n   - Calculates the cost of doing a seek vs a scan, for example\n\n5. `join.rs`\n   - Implements the System R style dynamic programming join ordering algorithm\n\n6. `order.rs`\n   - Determines if sort operations can be eliminated based on the chosen access methods and join order\n\n## Join reordering and optimal index selection\n\n**The goals of query optimization are at least the following:**\n\n1. Do as little page I/O as possible\n2. Do as little CPU work as possible\n3. Retain query correctness.\n\n**The most important ways to achieve no. 1 and no. 2 are:**\n\n1. Choose the optimal access method for each table (e.g. an index or a rowid-based seek, or a full table scan if all else fails).\n2. Choose the best or near-best way to reorder the tables in the query so that those optimal access methods can be used.\n3. Also factor in whether the chosen join order and indexes allow removal of any sort operations that are necessary for query correctness.\n\n## Limbo's optimizer\n\nLimbo's optimizer is an implementation of an extremely traditional [IBM System R](https://www.cs.cmu.edu/~15721-f24/slides/02-Selinger-SystemR-opt.pdf) style optimizer,\ni.e. straight from the 70s! The DP algorithm is explained below.\n\n### Current high level flow of the optimizer\n\n1. **SQL rewriting**\n  - Rewrite certain SQL expressions to another form (not a lot currently; e.g. rewrite BETWEEN as two comparisons)\n  - Eliminate constant conditions: e.g. `WHERE 1` is removed, `WHERE 0` short-circuits the whole query because it is trivially false.\n2. **Check whether there is an \"interesting order\"** that we should consider when evaluating indexes and join orders\n    - Is there a GROUP BY? an ORDER BY? Both?\n3. **Convert WHERE clause conjucts to Constraints**\n    - E.g. in `WHERE t.x = 5`, the expression `5` _constrains_  table `t` to values of `x` that are exactly `5`.\n    - E.g. in `Where t.x = u.x`, the expression `u.x` constrains `t`, AND `t.x` constrains `u`.\n    - Per table, each constraint has an estimated _selectivity_ (how much it filters the result set); this affects join order calculations, see the paragraph on _Estimation_  below.\n    - Per table, constraints are also analyzed for whether one or multiple of them can be used as an index seek key to avoid a full scan.\n4. **Compute the best join order using a dynamic programming algorithm:**\n  - `n` = number of tables considered\n  - `n=1`: find the lowest _cost_ way to access each single table, given the constraints of the query. Memoize the result.\n  - `n=2`: for each table found in the `n=1` step, find the best way to join that table with each other table. Memoize the result.\n  - `n=3`: for each 2-table subset found, find the best way to join that result to each other table. Memoize the result.\n  - `n=m`: for each `m-1` table subset found, find the best way to join that result to the `m'th` table\n  - **Use pruning to reduce search space:**\n    - Compute the literal query order first, and store its _cost_ as an upper threshold.\n      In some cases it is not possible to compute this upper threshold from the literal order—for example, when \n      table-valued functions are involved and their arguments reference tables that appear to the right in the join order. \n      In such situations, the literal order cannot be executed directly, so no meaningful _cost_ can be assigned. \n      In these cases, the threshold is set to infinity, ensuring that valid plans are still considered.\n    - If at any point a considered join order exceeds the upper threshold, discard that search path since it cannot be better than the current best.\n      - For example, we have `SELECT * FROM a JOIN b JOIN c JOIN d`. Compute `JOIN(a,b,c,d)` first. If `JOIN (b,a)` is already worse than `JOIN(a,b,c,d)`, we don't have to even try `JOIN(b,a,c)`.\n    - Also keep track of the best plan per _subset_:\n      - If we find that `JOIN(b,a,c)` is better than any other permutation of the same tables, e.g. `JOIN(a,b,c)`, then we can discard _ALL_ of the other permutations for that subset. For example, we don't need to consider `JOIN(a,b,c,d)` because we know it's worse than `JOIN(b,a,c,d)`.\n      - This is possible due to the associativity and commutativity of INNER JOINs.\n  - Also keep track of the best _ordered plan_ , i.e. one that provides the \"interesting order\" mentioned above.\n  - At the end, apply a cost penalty to the best overall plan\n    - If it is now worse than the best sorted plan, then choose the sorted plan as the best plan for the query.\n      - This allows us to eliminate a sorting operation.\n    - If the best overall plan is still best even with the sorting penalty, then keep it. A sorting operation is later applied to sort the rows according to the desired order.\n5. **Mutate the plan's `join_order` and `Operation`s to match the computed best plan.**\n\n### Estimation of cost and cardinalities + a note on table statistics\n\nCurrently, in the absence of `ANALYZE`, `sqlite_stat1` etc. we assume the following:\n\n1. Each table has `1,000,000` rows.\n2. Each equality (`=`) filter will filter out some percentage of the result set.\n3. Each nonequality (e.g. `>`) will filter out some smaller percentage of the result set.\n4. Each `4096` byte database page holds `50` rows, i.e. roughly `80` bytes per row \n5. Sort operations have some CPU cost dependent on the number of input rows to the sort operation.\n\nFrom the above, we derive the following formula for estimating the cost of joining `t1` with `t2`\n\n```\nJOIN_COST = PAGE_IO(t1.rows) + t1.rows * PAGE_IO(t2.rows)\n```\n\nFor example, let's take the query `SELECT * FROM t1 JOIN t2 USING(foo) WHERE t2.foo > 10`. Let's assume the following:\n\n- `t1` has `6400` rows and `t2` has `8000` rows\n- there are no indexes at all\n- let's ignore the CPU cost from the equation for simplicity.\n\nThe best access method for both is a full table scan. The output cardinality of `t1` is the full table, because nothing is filtering it. Hence, the cost of `t1 JOIN t2` becomes:\n\n```\nJOIN_COST = PAGE_IO(t1.input_rows) + t1.output_rows * PAGE_IO(t2.input_rows)\n\n// plugging in the values:\n\nJOIN_COST = PAGE_IO(6400) + 6400 * PAGE_IO(8000)\nJOIN_COST = 80 + 6400 * 100 = 640080\n```\n\nNow let's consider `t2 JOIN t1`. The best access method for both is still a full scan, but since we can filter on `t2.foo > 10`, its output cardinality decreases. Let's assume only 1/4 of the rows of `t2` match the condition `t2.foo > 10`. Hence, the cost of `t2 join t1` becomes:\n\n```\nJOIN_COST = PAGE_IO(t2.input_rows) + t2.output_rows * PAGE_IO(t1.input_rows)\n\n// plugging in the values:\n\nJOIN_COST = PAGE_IO(8000) + 1/4 * 8000 * PAGE_IO(6400)\nJOIN_COST = 100 + 2000 * 80 = 160100\n```\n\nEven though `t2` is a larger table, because we were able to reduce the input set to the join operation, it's dramatically cheaper.\n\n#### Statistics\n\nSince we don't support `ANALYZE`, nor can we assume that users will call `ANALYZE` anyway, we use simple magic constants to estimate the selectivity of join predicates, row count of tables, and so on. When we have support for `ANALYZE`, we should plug the statistics from `sqlite_stat1` and friends into the optimizer to make more informed decisions.\n\n### Estimating the output cardinality of a join\n\nThe output cardinality (output row count) of an operation is as follows:\n\n```\nOUTPUT_CARDINALITY_JOIN = INPUT_CARDINALITY_RHS * OUTPUT_CARDINALITY_RHS\n\nwhere\n\nINPUT_CARDINALITY_RHS = OUTPUT_CARDINALITY_LHS\n```\n\nexample:\n\n```\nSELECT * FROM products p JOIN order_lines o ON p.id = o.product_id\n```\nAssuming there are 100 products, i.e. just selecting all products would yield 100 rows:\n\n```\nOUTPUT_CARDINALITY_LHS = 100\nINPUT_CARDINALITY_RHS = 100\n```\n\nAssuming p.id = o.product_id will return three orders per each product:\n\n```\nOUTPUT_CARDINALITY_RHS = 3\n\nOUTPUT_CARDINALITY_JOIN = 100 * 3 = 300\n```\ni.e. the join is estimated to return 300 rows, 3 for each product.\n\nAgain, in the absence of statistics, we use magic constants to estimate these cardinalities.\n\nEstimating them is important because in multi-way joins the output cardinality of the previous join becomes the input cardinality of the next one."
  },
  {
    "path": "core/translate/optimizer/access_method.rs",
    "content": "use crate::sync::Arc;\nuse rustc_hash::FxHashMap as HashMap;\nuse smallvec::SmallVec;\nuse std::collections::VecDeque;\n\nuse turso_ext::{ConstraintInfo, ConstraintUsage, ResultCode};\nuse turso_parser::ast::{self, SortOrder, TableInternalId};\n\nuse crate::schema::Schema;\nuse crate::stats::AnalyzeStats;\nuse crate::translate::expr::{as_binary_components, walk_expr, WalkControl};\nuse crate::translate::optimizer::constraints::{\n    convert_to_vtab_constraint, ordered_materialized_key_columns, BinaryExprSide, Constraint,\n    ConstraintOperator, RangeConstraintRef,\n};\nuse crate::translate::optimizer::cost::{rows_per_leaf_page_for_index, RowCountEstimate};\nuse crate::translate::optimizer::cost_params::CostModelParams;\nuse crate::translate::plan::{\n    plan_has_outer_scope_dependency, HashJoinKey, HashJoinType, NonFromClauseSubquery,\n    SetOperation, SubqueryState, TableReferences, WhereTerm,\n};\nuse crate::vdbe::affinity::Affinity;\nuse crate::vdbe::hash_table::DEFAULT_MEM_BUDGET;\nuse crate::{\n    schema::{FromClauseSubquery, Index, IndexColumn, Table},\n    translate::plan::{IndexMethodQuery, IterationDirection, JoinOrderMember, JoinedTable},\n    vtab::VirtualTable,\n    LimboError, Result,\n};\n\nuse super::{\n    constraints::{\n        usable_constraints_for_join_order, usable_constraints_for_lhs_mask, TableConstraints,\n    },\n    cost::{\n        estimate_cost_for_scan_or_seek, estimate_index_cost, estimate_rows_per_seek, AnalyzeCtx,\n        Cost, IndexInfo,\n    },\n    join::JoinPlanningContext,\n    multi_index::{\n        consider_multi_index_intersection, consider_multi_index_union, MultiIndexBranchParams,\n    },\n    order::{\n        btree_access_order_consumed, subquery_intrinsic_order_consumed, ColumnTarget,\n        EqualityPrefixScope, OrderTarget,\n    },\n};\nuse crate::translate::planner::TableMask;\n\n#[derive(Debug, Clone)]\n/// Represents a way to access a table.\npub struct AccessMethod {\n    /// The estimated number of page fetches.\n    /// CPU costs are folded into the same scalar cost model.\n    pub cost: Cost,\n    /// Estimated rows produced per outer row before applying remaining filters.\n    pub estimated_rows_per_outer_row: f64,\n    /// Whether join cardinality should still apply planner-side selectivity after\n    /// using this access path's own row estimate.\n    pub residual_constraints: ResidualConstraintMode,\n    /// WHERE-term indices already accounted for by this access path's row estimate.\n    pub consumed_where_terms: SmallVec<[usize; 4]>,\n    /// Table-type specific access method details.\n    pub params: AccessMethodParams,\n}\n\n/// Describes whether join planning should still apply residual WHERE-term\n/// selectivity after choosing an access path.\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum ResidualConstraintMode {\n    /// Apply the selectivity of all relevant WHERE terms that this access path\n    /// did not already consume.\n    ApplyUnconsumed,\n    /// The access path already provided its own final row estimate; do not\n    /// multiply any planner-side residual selectivity on top.\n    None,\n}\n\n/// Table‑specific details of how an [`AccessMethod`] operates.\n#[derive(Debug, Clone)]\npub enum AccessMethodParams {\n    BTreeTable {\n        /// The direction of iteration for the access method.\n        /// Typically this is backwards only if it helps satisfy an [OrderTarget].\n        iter_dir: IterationDirection,\n        /// The index that is being used, if any. For rowid based searches (and full table scans), this is None.\n        index: Option<Arc<Index>>,\n        /// The constraint references that are being used, if any.\n        /// An empty list of constraint refs means a scan (full table or index);\n        /// a non-empty list means a search.\n        constraint_refs: Vec<RangeConstraintRef>,\n    },\n    VirtualTable {\n        /// Index identifier returned by the table's `best_index` method.\n        idx_num: i32,\n        /// Optional index string returned by the table's `best_index` method.\n        idx_str: Option<String>,\n        /// Constraint descriptors passed to the virtual table’s `filter` method.\n        /// Each corresponds to a column/operator pair from the WHERE clause.\n        constraints: Vec<ConstraintInfo>,\n        /// Information returned by the virtual table's `best_index` method\n        /// describing how each constraint will be used.\n        constraint_usages: Vec<ConstraintUsage>,\n    },\n    /// FROM-subquery scan. Coroutine-backed scans run forwards; materialized\n    /// subqueries may also be scanned backwards when their intrinsic order\n    /// matches the requested extremum order.\n    Subquery { iter_dir: IterationDirection },\n    /// Materialized subquery with an ephemeral index for seeking.\n    /// The subquery results are materialized once into an ephemeral index,\n    /// which can then be seeked using join conditions.\n    MaterializedSubquery {\n        /// The ephemeral index to build and seek into.\n        index: Arc<Index>,\n        /// The constraint references used for seeking.\n        constraint_refs: Vec<RangeConstraintRef>,\n        /// The direction to iterate the ephemeral index once positioned.\n        iter_dir: IterationDirection,\n    },\n    HashJoin {\n        /// The table to build the hash table from.\n        build_table_idx: usize,\n        /// The table to probe the hash table with.\n        probe_table_idx: usize,\n        /// Join key references - each entry contains the where_clause index and which side\n        /// of the equality belongs to the build table. Supports expression-based join keys.\n        join_keys: Vec<HashJoinKey>,\n        /// Memory budget for the hash table in bytes.\n        mem_budget: usize,\n        /// Whether the build input should be materialized as a rowid list before hash build.\n        materialize_build_input: bool,\n        /// Whether to use a bloom filter on the probe side.\n        use_bloom_filter: bool,\n        /// Join semantics: Inner, LeftOuter, or FullOuter.\n        join_type: HashJoinType,\n    },\n    /// Custom index method access (e.g., FTS).\n    /// This variant is used when the optimizer determines that a custom index method\n    /// should be used for table access in a join query.\n    IndexMethod {\n        /// The fully constructed IndexMethodQuery operation to apply to this table.\n        query: IndexMethodQuery,\n        /// Index in WHERE clause that was covered by this index method (if any).\n        where_covered: Option<usize>,\n    },\n    /// Multi-index scan for OR-by-union or AND-by-intersection optimization.\n    /// Used when a WHERE clause has OR/AND terms that can each use a different index.\n    /// Example: WHERE a = 1 AND|OR b = 2 with separate indexes on a and b.\n    MultiIndexScan {\n        /// Each branch represents one term with its own index access.\n        branches: Vec<MultiIndexBranchParams>,\n        /// Index of the primary WHERE term.\n        where_term_idx: usize,\n        /// The set operation (Union for OR, Intersection for AND).\n        set_op: SetOperation,\n    },\n    /// IN-list driven index seek.\n    InSeek {\n        index: Option<Arc<Index>>,\n        affinity: Affinity,\n        where_term_idx: usize,\n    },\n}\n\n/// Result of generic btree candidate selection before it is wrapped into a full\n/// [`AccessMethod`].\npub(super) struct ChosenBtreeCandidate {\n    pub(super) iter_dir: IterationDirection,\n    pub(super) index: Option<Arc<Index>>,\n    pub(super) constraint_refs: Vec<RangeConstraintRef>,\n    pub(super) cost: Cost,\n}\n\n#[derive(Debug, Clone)]\npub(super) struct ChosenInSeekCandidate {\n    pub(super) index: Option<Arc<Index>>,\n    pub(super) affinity: Affinity,\n    pub(super) constraint_idx: usize,\n    pub(super) cost: Cost,\n    pub(super) estimated_rows_per_outer_row: f64,\n}\n\n/// Describes what a caller needs to read from a branch-local scan.\n///\n/// Ordinary table access needs the scanned rows themselves, but multi-index\n/// branches only harvest rowids into a RowSet and fetch full rows later.\n/// Making this explicit avoids threading \"mystery bool\" flags through the cost\n/// model.\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub(super) enum BranchReadMode {\n    /// Cost the branch as if it only needs rowids from the scan.\n    RowIdOnly,\n    /// Cost the branch as a normal table/index access that may need full row data.\n    FullRow,\n}\n\n#[allow(clippy::too_many_arguments)]\n/// Choose the best ordinary btree lookup candidate for one table under the\n/// current join-order prefix.\npub(super) fn choose_best_btree_candidate(\n    rhs_table: &JoinedTable,\n    rhs_constraints: &TableConstraints,\n    lhs_mask: &TableMask,\n    rhs_table_idx: usize,\n    maybe_order_target: Option<&OrderTarget>,\n    schema: &Schema,\n    analyze_stats: &AnalyzeStats,\n    input_cardinality: f64,\n    base_row_count: RowCountEstimate,\n    params: &CostModelParams,\n) -> Option<ChosenBtreeCandidate> {\n    // Seed the baseline with a table scan only if a rowid candidate exists\n    // (i.e. no INDEXED BY has removed it). Otherwise start at infinite cost\n    // so the forced index candidate always wins.\n    let has_rowid_candidate = rhs_constraints.candidates.iter().any(|c| c.index.is_none());\n    let mut best_cost = if has_rowid_candidate {\n        estimate_cost_for_scan_or_seek(\n            None,\n            &[],\n            &[],\n            input_cardinality,\n            base_row_count,\n            false,\n            params,\n            None,\n        )\n    } else {\n        Cost(f64::MAX)\n    };\n    let mut best_choice = ChosenBtreeCandidate {\n        iter_dir: IterationDirection::Forwards,\n        index: None,\n        constraint_refs: vec![],\n        cost: best_cost,\n    };\n    let mut best_adjusted_output = f64::MAX;\n    let mut best_is_ordered = false;\n\n    // Build a mask for the rhs table itself.\n    let mut rhs_table_mask = TableMask::new();\n    rhs_table_mask.add_table(rhs_table_idx);\n\n    // Estimate cost for each candidate index (including the rowid index) and\n    // keep the best candidate.\n    for candidate in rhs_constraints.candidates.iter() {\n        let usable_constraint_refs = usable_constraints_for_lhs_mask(\n            &rhs_constraints.constraints,\n            &candidate.refs,\n            lhs_mask,\n            rhs_table_idx,\n        );\n\n        let index_info = match candidate.index.as_ref() {\n            Some(index) => IndexInfo {\n                unique: index.unique,\n                covering: rhs_table.index_is_covering(index),\n                column_count: index.columns.len(),\n                rows_per_leaf_page: rows_per_leaf_page_for_index(\n                    index.columns.len(),\n                    rhs_table,\n                    params.rows_per_table_page,\n                ),\n            },\n            None => IndexInfo {\n                unique: true,\n                covering: !usable_constraint_refs.is_empty(),\n                column_count: 1,\n                rows_per_leaf_page: params.rows_per_table_page,\n            },\n        };\n\n        let (iter_dir, is_index_ordered, order_satisfiability_bonus) =\n            if let Some(order_target) = maybe_order_target {\n                // Reuse the same index-vs-order matching logic as final plan\n                // validation, but allow any equality-constrained seek prefix to\n                // be skipped here. Candidate scoring only needs to know whether\n                // this specific access path can emit rows ordered after its seek\n                // key; final global ORDER BY validation is stricter and only\n                // skips globally constant prefixes.\n                let all_same_direction = btree_access_order_consumed(\n                    rhs_table,\n                    IterationDirection::Forwards,\n                    candidate.index.as_deref(),\n                    &usable_constraint_refs,\n                    &order_target.columns,\n                    schema,\n                    EqualityPrefixScope::AnyEquality,\n                ) == order_target.columns.len();\n                let all_opposite_direction = btree_access_order_consumed(\n                    rhs_table,\n                    IterationDirection::Backwards,\n                    candidate.index.as_deref(),\n                    &usable_constraint_refs,\n                    &order_target.columns,\n                    schema,\n                    EqualityPrefixScope::AnyEquality,\n                ) == order_target.columns.len();\n\n                let satisfies_order = all_same_direction || all_opposite_direction;\n                if satisfies_order {\n                    // Bonus = estimated sort cost saved. Sorting is O(n log n).\n                    let n = *base_row_count;\n                    let sort_cost_saved = Cost(n * (n.max(1.0).log2()) * params.sort_cpu_per_row);\n                    (\n                        if all_same_direction {\n                            IterationDirection::Forwards\n                        } else {\n                            IterationDirection::Backwards\n                        },\n                        true,\n                        sort_cost_saved,\n                    )\n                } else {\n                    (IterationDirection::Forwards, false, Cost(0.0))\n                }\n            } else {\n                (IterationDirection::Forwards, false, Cost(0.0))\n            };\n\n        let analyze_ctx = AnalyzeCtx {\n            rhs_table,\n            index: candidate.index.as_ref(),\n            stats: analyze_stats,\n        };\n        let cost = estimate_cost_for_scan_or_seek(\n            Some(index_info),\n            &rhs_constraints.constraints,\n            &usable_constraint_refs,\n            input_cardinality,\n            base_row_count,\n            is_index_ordered,\n            params,\n            Some(&analyze_ctx),\n        );\n\n        // Residual filter output adjustment (mirrors SQLite's whereLoopOutputAdjust).\n        //\n        // When two indexes have the same seek cost, the one whose seek\n        // prerequisites already cover more residual WHERE constraints will\n        // produce fewer output rows (because those residual filters can be\n        // accounted for). This breaks ties correctly: a join-driven seek\n        // like fromId=e1.toId (prereqs={e1}) can claim credit for the\n        // constant residual label='requires', but a constant seek like\n        // label='requires' (prereqs={}) cannot claim credit for the\n        // join-dependent residual fromId=e1.toId.\n        let loop_prereq_mask = {\n            let mut mask = TableMask::new();\n            for ucref in usable_constraint_refs.iter() {\n                for idx in [\n                    ucref.eq.as_ref().map(|e| e.constraint_pos),\n                    ucref.lower_bound,\n                    ucref.upper_bound,\n                ]\n                .into_iter()\n                .flatten()\n                {\n                    let c = &rhs_constraints.constraints[idx];\n                    mask = TableMask::from_table_number_iter(\n                        mask.tables_iter().chain(c.lhs_mask.tables_iter()),\n                    );\n                }\n            }\n            mask\n        };\n        // Tables whose constraints this loop can account for: the loop's own\n        // prerequisite tables plus the current table itself.\n        let allowed_mask = TableMask::from_table_number_iter(\n            loop_prereq_mask\n                .tables_iter()\n                .chain(rhs_table_mask.tables_iter()),\n        );\n\n        // Collect which constraint positions are consumed by the index seek.\n        let consumed: SmallVec<[usize; 8]> = usable_constraint_refs\n            .iter()\n            .flat_map(|ucref| {\n                [\n                    ucref.eq.as_ref().map(|e| e.constraint_pos),\n                    ucref.lower_bound,\n                    ucref.upper_bound,\n                ]\n                .into_iter()\n                .flatten()\n            })\n            .collect();\n\n        // Multiply selectivities of residual constraints whose prerequisites\n        // are within the allowed mask (i.e. already satisfied by this loop).\n        let residual_selectivity: f64 = rhs_constraints\n            .constraints\n            .iter()\n            .enumerate()\n            .filter(|(i, c)| {\n                !consumed.contains(i)\n                    && c.usable\n                    && allowed_mask.contains_all(&c.lhs_mask)\n                    && matches!(\n                        c.operator,\n                        ConstraintOperator::AstNativeOperator(ast::Operator::Equals)\n                            | ConstraintOperator::AstNativeOperator(ast::Operator::Greater)\n                            | ConstraintOperator::AstNativeOperator(ast::Operator::GreaterEquals)\n                            | ConstraintOperator::AstNativeOperator(ast::Operator::Less)\n                            | ConstraintOperator::AstNativeOperator(ast::Operator::LessEquals)\n                    )\n            })\n            .map(|(_, c)| c.selectivity)\n            .product();\n\n        // Adjusted output: lower means the loop delivers fewer rows downstream.\n        let adjusted_output = residual_selectivity;\n\n        // Only apply the order bonus when this candidate satisfies order but\n        // the current best does not. When both satisfy order, switching saves\n        // no additional sort cost.\n        let effective_bonus = if is_index_ordered && !best_is_ordered {\n            order_satisfiability_bonus\n        } else {\n            Cost(0.0)\n        };\n        let adjusted_best = best_cost + effective_bonus;\n        let costs_equal = (cost.0 - adjusted_best.0).abs() < 1e-9;\n        if cost < adjusted_best || (costs_equal && adjusted_output < best_adjusted_output - 1e-12) {\n            best_cost = cost;\n            best_adjusted_output = adjusted_output;\n            best_is_ordered = is_index_ordered;\n            best_choice = ChosenBtreeCandidate {\n                iter_dir,\n                index: candidate.index.clone(),\n                constraint_refs: usable_constraint_refs.clone(),\n                cost,\n            };\n        }\n    }\n\n    Some(best_choice)\n}\n\nfn consumed_where_terms_from_constraint_refs(\n    constraints: &[Constraint],\n    constraint_refs: &[RangeConstraintRef],\n) -> SmallVec<[usize; 4]> {\n    let mut consumed = SmallVec::new();\n    for cref in constraint_refs {\n        for constraint_idx in [\n            cref.eq.as_ref().map(|eq| eq.constraint_pos),\n            cref.lower_bound,\n            cref.upper_bound,\n        ]\n        .into_iter()\n        .flatten()\n        {\n            let where_term_idx = constraints[constraint_idx].where_clause_pos.0;\n            if !consumed.contains(&where_term_idx) {\n                consumed.push(where_term_idx);\n            }\n        }\n    }\n    consumed\n}\n\n#[allow(clippy::too_many_arguments)]\n/// Evaluate whether an `IN (...)` predicate should replace the ordinary btree\n/// access path with repeated equality seeks.\n///\n/// This is intentionally separate from `choose_best_btree_candidate()`: the\n/// generic btree chooser reasons about a single continuous scan/seek over one\n/// candidate, while `InSeek` emits a two-level loop that materializes the RHS\n/// into an ephemeral cursor and performs one equality seek per RHS value.\n/// Because of that execution shape, only rowid or the first column of an index\n/// can drive `InSeek`, and the comparison collation must match the chosen\n/// index's first-key collation.\npub(super) fn choose_best_in_seek_candidate(\n    rhs_table: &JoinedTable,\n    rhs_constraints: &TableConstraints,\n    lhs_mask: &TableMask,\n    input_cardinality: f64,\n    base_row_count: RowCountEstimate,\n    params: &CostModelParams,\n    best_cost: Cost,\n    read_mode: BranchReadMode,\n) -> Result<Option<ChosenInSeekCandidate>> {\n    let Table::BTree(btree) = &rhs_table.table else {\n        return Err(LimboError::InternalError(\n            \"consider_in_seek_access_method called on non-BTree table\".into(),\n        ));\n    };\n\n    let base = *base_row_count;\n    let tree_depth = if base <= 1.0 {\n        1.0\n    } else {\n        (base.ln() / params.rows_per_table_page.ln())\n            .ceil()\n            .max(1.0)\n    };\n    let mut best_in_seek = None;\n    let mut best_in_seek_cost = best_cost;\n\n    for candidate in rhs_constraints.candidates.iter() {\n        let first_col_pos = candidate\n            .index\n            .as_ref()\n            .and_then(|idx| idx.columns.first().map(|c| c.pos_in_table));\n\n        let rowid_only = matches!(read_mode, BranchReadMode::RowIdOnly);\n        let index_info = match candidate.index.as_ref() {\n            Some(index) => IndexInfo {\n                unique: index.unique,\n                covering: rowid_only || rhs_table.index_is_covering(index),\n                column_count: index.columns.len(),\n                rows_per_leaf_page: rows_per_leaf_page_for_index(\n                    index.columns.len(),\n                    rhs_table,\n                    params.rows_per_table_page,\n                ),\n            },\n            None => IndexInfo {\n                unique: true,\n                covering: false,\n                column_count: 1,\n                rows_per_leaf_page: params.rows_per_table_page,\n            },\n        };\n\n        for constraint in &rhs_constraints.constraints {\n            let ConstraintOperator::In {\n                not,\n                estimated_values,\n            } = constraint.operator\n            else {\n                continue;\n            };\n            if not || !lhs_mask.contains_all(&constraint.lhs_mask) {\n                continue;\n            }\n\n            let matches = if candidate.index.is_none() {\n                constraint.is_rowid\n            } else {\n                !constraint.is_rowid\n                    && constraint.table_col_pos.is_some()\n                    && constraint.table_col_pos == first_col_pos\n            };\n            if !matches {\n                continue;\n            }\n\n            // `open_loop` copies the chosen index collation onto the ephemeral\n            // IN cursor. Reject mismatches here so a BINARY `IN` comparison\n            // cannot silently become `NOCASE`/`RTRIM` just because the index is.\n            if let (Some(index), Some(col_pos)) = (&candidate.index, constraint.table_col_pos) {\n                let constrained_column = &rhs_table.table.columns()[col_pos];\n                let table_collation = constrained_column.collation();\n                let index_collation = index.columns[0].collation.unwrap_or_default();\n                if table_collation != index_collation {\n                    continue;\n                }\n            }\n\n            let rows_per_seek = if (index_info.unique && index_info.column_count == 1)\n                || candidate.index.is_none()\n            {\n                1.0\n            } else {\n                (base * params.sel_eq_indexed).sqrt().max(1.0)\n            };\n            let in_cost = estimate_index_cost(\n                base,\n                tree_depth,\n                index_info,\n                estimated_values * input_cardinality,\n                rows_per_seek,\n                params,\n            );\n            if in_cost >= best_in_seek_cost {\n                continue;\n            }\n\n            let affinity = if let Some(col_pos) = constraint.table_col_pos {\n                btree\n                    .columns\n                    .get(col_pos)\n                    .map(|col| col.affinity())\n                    .unwrap_or(Affinity::Blob)\n            } else {\n                Affinity::Integer\n            };\n            best_in_seek_cost = in_cost;\n            best_in_seek = Some(ChosenInSeekCandidate {\n                index: candidate.index.clone(),\n                affinity,\n                constraint_idx: constraint.where_clause_pos.0,\n                cost: in_cost,\n                estimated_rows_per_outer_row: (constraint.selectivity * base).max(1.0),\n            });\n        }\n    }\n\n    Ok(best_in_seek)\n}\n\nfn consider_in_seek_access_method(\n    rhs_table: &JoinedTable,\n    rhs_constraints: &TableConstraints,\n    lhs_mask: &TableMask,\n    input_cardinality: f64,\n    base_row_count: RowCountEstimate,\n    params: &CostModelParams,\n    best_cost: Cost,\n) -> Result<Option<AccessMethod>> {\n    Ok(choose_best_in_seek_candidate(\n        rhs_table,\n        rhs_constraints,\n        lhs_mask,\n        input_cardinality,\n        base_row_count,\n        params,\n        best_cost,\n        BranchReadMode::FullRow,\n    )?\n    .map(|chosen| AccessMethod {\n        cost: chosen.cost,\n        estimated_rows_per_outer_row: chosen.estimated_rows_per_outer_row,\n        residual_constraints: ResidualConstraintMode::ApplyUnconsumed,\n        consumed_where_terms: smallvec::smallvec![chosen.constraint_idx],\n        params: AccessMethodParams::InSeek {\n            index: chosen.index,\n            affinity: chosen.affinity,\n            where_term_idx: chosen.constraint_idx,\n        },\n    }))\n}\n\n/// Return the best [AccessMethod] for a given join order.\n#[allow(clippy::too_many_arguments)]\npub fn find_best_access_method_for_join_order(\n    rhs_table: &JoinedTable,\n    rhs_constraints: &TableConstraints,\n    join_order: &[JoinOrderMember],\n    planning_context: JoinPlanningContext<'_>,\n    where_clause: &[WhereTerm],\n    available_indexes: &HashMap<String, VecDeque<Arc<Index>>>,\n    table_references: &TableReferences,\n    subqueries: &[NonFromClauseSubquery],\n    schema: &Schema,\n    analyze_stats: &AnalyzeStats,\n    input_cardinality: f64,\n    base_row_count: RowCountEstimate,\n    params: &CostModelParams,\n) -> Result<Option<AccessMethod>> {\n    match &rhs_table.table {\n        Table::BTree(_) => find_best_access_method_for_btree(\n            rhs_table,\n            rhs_constraints,\n            join_order,\n            planning_context.maybe_order_target,\n            where_clause,\n            available_indexes,\n            table_references,\n            subqueries,\n            schema,\n            analyze_stats,\n            input_cardinality,\n            base_row_count,\n            params,\n        ),\n        Table::Virtual(vtab) => find_best_access_method_for_vtab(\n            vtab,\n            &rhs_constraints.constraints,\n            join_order,\n            input_cardinality,\n            base_row_count,\n            params,\n        ),\n        Table::FromClauseSubquery(subquery) => find_best_access_method_for_subquery(\n            rhs_table,\n            subquery,\n            rhs_constraints,\n            join_order,\n            planning_context,\n            schema,\n            input_cardinality,\n            base_row_count,\n            params,\n        ),\n    }\n}\n\n#[allow(clippy::too_many_arguments)]\nfn find_best_access_method_for_btree(\n    rhs_table: &JoinedTable,\n    rhs_constraints: &TableConstraints,\n    join_order: &[JoinOrderMember],\n    maybe_order_target: Option<&OrderTarget>,\n    where_clause: &[WhereTerm],\n    available_indexes: &HashMap<String, VecDeque<Arc<Index>>>,\n    table_references: &TableReferences,\n    subqueries: &[NonFromClauseSubquery],\n    schema: &Schema,\n    analyze_stats: &AnalyzeStats,\n    input_cardinality: f64,\n    base_row_count: RowCountEstimate,\n    params: &CostModelParams,\n) -> Result<Option<AccessMethod>> {\n    let rhs_table_idx = join_order.last().unwrap().original_idx;\n    let lhs_mask = TableMask::from_table_number_iter(\n        join_order\n            .iter()\n            .take(join_order.len() - 1)\n            .map(|member| member.original_idx),\n    );\n    let best = choose_best_btree_candidate(\n        rhs_table,\n        rhs_constraints,\n        &lhs_mask,\n        rhs_table_idx,\n        maybe_order_target,\n        schema,\n        analyze_stats,\n        input_cardinality,\n        base_row_count,\n        params,\n    )\n    .expect(\"btree candidate selection must always consider the rowid candidate\");\n\n    let estimated_rows_per_outer_row = if best.constraint_refs.is_empty() {\n        *base_row_count\n    } else {\n        let index_info = match best.index.as_ref() {\n            Some(index) => IndexInfo {\n                unique: index.unique,\n                covering: rhs_table.index_is_covering(index),\n                column_count: index.columns.len(),\n                rows_per_leaf_page: rows_per_leaf_page_for_index(\n                    index.columns.len(),\n                    rhs_table,\n                    params.rows_per_table_page,\n                ),\n            },\n            None => IndexInfo {\n                unique: true,\n                covering: true,\n                column_count: 1,\n                rows_per_leaf_page: params.rows_per_table_page,\n            },\n        };\n        let analyze_ctx = AnalyzeCtx {\n            rhs_table,\n            index: best.index.as_ref(),\n            stats: analyze_stats,\n        };\n        estimate_rows_per_seek(\n            index_info,\n            &rhs_constraints.constraints,\n            &best.constraint_refs,\n            base_row_count,\n            Some(&analyze_ctx),\n        )\n    };\n    let mut best_access_method = AccessMethod {\n        cost: best.cost,\n        estimated_rows_per_outer_row,\n        residual_constraints: ResidualConstraintMode::ApplyUnconsumed,\n        consumed_where_terms: consumed_where_terms_from_constraint_refs(\n            &rhs_constraints.constraints,\n            &best.constraint_refs,\n        ),\n        params: AccessMethodParams::BTreeTable {\n            iter_dir: best.iter_dir,\n            index: best.index,\n            constraint_refs: best.constraint_refs,\n        },\n    };\n\n    // Skip alternative access methods (in-seek, multi-index) when INDEXED BY or NOT INDEXED\n    // is specified — the user explicitly requested a specific index or no index.\n    if rhs_table.indexed.is_none() && rhs_table.btree().is_some_and(|b| b.has_rowid) {\n        if let Some(in_seek_method) = consider_in_seek_access_method(\n            rhs_table,\n            rhs_constraints,\n            &lhs_mask,\n            input_cardinality,\n            base_row_count,\n            params,\n            best_access_method.cost,\n        )? {\n            best_access_method = in_seek_method;\n        }\n\n        if let Some(multi_idx_method) = consider_multi_index_union(\n            rhs_table,\n            where_clause,\n            available_indexes,\n            table_references,\n            subqueries,\n            schema,\n            input_cardinality,\n            base_row_count,\n            params,\n            best_access_method.cost,\n            &lhs_mask,\n            analyze_stats,\n        ) {\n            best_access_method = multi_idx_method;\n        }\n\n        if let Some(multi_idx_and_method) = consider_multi_index_intersection(\n            rhs_table,\n            where_clause,\n            available_indexes,\n            table_references,\n            subqueries,\n            schema,\n            input_cardinality,\n            base_row_count,\n            params,\n            best_access_method.cost,\n            &lhs_mask,\n            analyze_stats,\n        ) {\n            best_access_method = multi_idx_and_method;\n        }\n    }\n\n    Ok(Some(best_access_method))\n}\n\nfn find_best_access_method_for_vtab(\n    vtab: &VirtualTable,\n    constraints: &[Constraint],\n    join_order: &[JoinOrderMember],\n    input_cardinality: f64,\n    base_row_count: RowCountEstimate,\n    params: &CostModelParams,\n) -> Result<Option<AccessMethod>> {\n    let vtab_constraints = convert_to_vtab_constraint(constraints, join_order);\n\n    // TODO: get proper order_by information to pass to the vtab.\n    // maybe encode more info on t_ctx? we need: [col_idx , is_descending]\n    let best_index_result = vtab.best_index(&vtab_constraints, &[]);\n\n    match best_index_result {\n        Ok(index_info) => {\n            Ok(Some(AccessMethod {\n                // TODO: Base cost on `IndexInfo::estimated_cost` and output cardinality on `IndexInfo::estimated_rows`\n                cost: estimate_cost_for_scan_or_seek(\n                    None,\n                    &[],\n                    &[],\n                    input_cardinality,\n                    base_row_count,\n                    false,\n                    params,\n                    None,\n                ),\n                estimated_rows_per_outer_row: *base_row_count,\n                residual_constraints: ResidualConstraintMode::ApplyUnconsumed,\n                consumed_where_terms: SmallVec::new(),\n                params: AccessMethodParams::VirtualTable {\n                    idx_num: index_info.idx_num,\n                    idx_str: index_info.idx_str,\n                    constraints: vtab_constraints,\n                    constraint_usages: index_info.constraint_usages,\n                },\n            }))\n        }\n        Err(ResultCode::ConstraintViolation) => Ok(None),\n        Err(e) => Err(LimboError::from(e)),\n    }\n}\n\n/// Collect all table IDs referenced in an expression.\nfn collect_table_refs(expr: &ast::Expr) -> Option<Vec<TableInternalId>> {\n    let mut tables = Vec::new();\n    let result = walk_expr(expr, &mut |e| {\n        match e {\n            ast::Expr::Column { table, .. } | ast::Expr::RowId { table, .. } => {\n                if !tables.contains(table) {\n                    tables.push(*table);\n                }\n            }\n            _ => {}\n        }\n        Ok(WalkControl::Continue)\n    });\n    result.ok().map(|_| tables)\n}\n\n/// Detect equi-join conditions between exactly two tables for hash join.\n///\n/// Returns `HashJoinKey` entries pointing at `WHERE` terms of the form:\n///   <build-only expr> = <probe-only expr>\n/// or\n///   <probe-only expr> = <build-only expr>\n///\n/// Both sides may be arbitrary expressions (e.g. `lower(t1.a) = substr(t2.b,1,3)`),\n/// but each side must reference columns from exactly one table:\n/// - the build side must reference only `build_table_id`\n/// - the probe side must reference only `probe_table_id`\n///\n/// This function does *not* mark any terms as consumed; the caller is responsible\n/// for doing so if a hash join is selected.\npub fn find_equijoin_conditions(\n    build_table_id: TableInternalId,\n    probe_table_id: TableInternalId,\n    where_clause: &[WhereTerm],\n) -> Vec<HashJoinKey> {\n    let mut join_keys = Vec::new();\n\n    for (where_idx, where_term) in where_clause.iter().enumerate() {\n        if where_term.consumed {\n            continue;\n        }\n\n        let Ok(Some((lhs, op, rhs))) = as_binary_components(&where_term.expr) else {\n            continue;\n        };\n        if !matches!(op.as_ast_operator(), Some(ast::Operator::Equals)) {\n            continue;\n        }\n\n        let Some(lhs_tables) = collect_table_refs(lhs) else {\n            continue;\n        };\n        let Some(rhs_tables) = collect_table_refs(rhs) else {\n            continue;\n        };\n\n        // Require each side to reference exactly one table. This prevents\n        // constants or multi-table expressions from being considered join keys.\n        if lhs_tables.len() != 1 || rhs_tables.len() != 1 {\n            continue;\n        }\n\n        let lhs_tid = lhs_tables[0];\n        let rhs_tid = rhs_tables[0];\n\n        // Accept either orientation: build=probe or probe=build.\n        let build_side = if lhs_tid == build_table_id && rhs_tid == probe_table_id {\n            Some(BinaryExprSide::Lhs)\n        } else if rhs_tid == build_table_id && lhs_tid == probe_table_id {\n            Some(BinaryExprSide::Rhs)\n        } else {\n            None\n        };\n\n        if let Some(build_side) = build_side {\n            join_keys.push(HashJoinKey {\n                where_clause_idx: where_idx,\n                build_side,\n            });\n        }\n    }\n\n    join_keys\n}\n\n/// Estimate the cost of a hash join between two tables.\n///\n/// The cost model accounts for:\n/// - Build phase: Creating the hash table from the build side (one-time cost)\n/// - Probe phase: Looking up each probe row in the hash table (one scan of probe table)\n/// - Memory pressure: Additional IO cost if the hash table spills to disk\npub fn estimate_hash_join_cost(\n    build_cardinality: f64,\n    probe_cardinality: f64,\n    mem_budget: usize,\n    probe_multiplier: f64,\n    params: &CostModelParams,\n) -> Cost {\n    // Estimate if the hash table will fit in memory based on actual row counts\n    let estimated_hash_table_size =\n        (build_cardinality as usize).saturating_mul(params.hash_bytes_per_row as usize);\n    let will_spill = estimated_hash_table_size > mem_budget;\n\n    // Build phase: hash and insert all rows from build table (one-time cost)\n    // With real ANALYZE stats, this accurately reflects the actual build table size\n    let build_cost = build_cardinality * (params.hash_cpu_cost + params.hash_insert_cost);\n\n    // Probe phase: scan probe table, hash each row and lookup in hash table.\n    // If the hash-join probe loop is nested under prior tables, the probe\n    // scan repeats per outer row, so scale by probe_multiplier.\n    let probe_cost =\n        probe_cardinality * (params.hash_cpu_cost + params.hash_lookup_cost) * probe_multiplier;\n\n    // Spill cost: if hash table exceeds memory budget, we need to write/read partitions to disk.\n    // Grace hash join writes partitions and reads them back, so it's 2x the page IO.\n    // Use page-based IO cost (rows / rows_per_page) rather than per-row IO.\n    let spill_cost = if will_spill {\n        let build_pages = (build_cardinality / params.rows_per_table_page).ceil();\n        let probe_pages = (probe_cardinality / params.rows_per_table_page).ceil();\n        // Write both sides to partitions, then read back: 2 * (build_pages + probe_pages)\n        (build_pages + probe_pages) * 2.0 * probe_multiplier\n    } else {\n        0.0\n    };\n\n    Cost(build_cost + probe_cost + spill_cost)\n}\n\n/// Try to create a hash join access method for joining two tables.\n#[allow(clippy::too_many_arguments)]\npub fn try_hash_join_access_method(\n    build_table: &JoinedTable,\n    probe_table: &JoinedTable,\n    build_table_idx: usize,\n    probe_table_idx: usize,\n    build_constraints: &TableConstraints,\n    probe_constraints: &TableConstraints,\n    where_clause: &mut [WhereTerm],\n    build_cardinality: f64,\n    probe_cardinality: f64,\n    probe_multiplier: f64,\n    subqueries: &[NonFromClauseSubquery],\n    params: &CostModelParams,\n) -> Option<AccessMethod> {\n    // Only works for B-tree tables\n    if !matches!(build_table.table, Table::BTree(_))\n        || !matches!(probe_table.table, Table::BTree(_))\n    {\n        return None;\n    }\n    // Avoid hash join on self-joins over the same underlying table. The current\n    // implementation assumes distinct build/probe sources; sharing storage can\n    // lead to incorrect matches.\n    let probe_root_page = probe_table.table.btree().expect(\"table is BTree\").root_page;\n    let build_root_page = build_table.table.btree().expect(\"table is BTree\").root_page;\n    if build_root_page == probe_root_page {\n        return None;\n    }\n    // No hash join for semi/anti-joins (nested loop with index seek is preferred).\n    if probe_table\n        .join_info\n        .as_ref()\n        .is_some_and(|ji| ji.is_semi_or_anti())\n        || build_table\n            .join_info\n            .as_ref()\n            .is_some_and(|ji| ji.is_semi_or_anti())\n    {\n        return None;\n    }\n    // Determine join type from the probe table's join_info.\n    let hash_join_type = if probe_table\n        .join_info\n        .as_ref()\n        .is_some_and(|ji| ji.is_full_outer())\n    {\n        HashJoinType::FullOuter\n    } else if probe_table\n        .join_info\n        .as_ref()\n        .is_some_and(|ji| ji.is_outer())\n    {\n        HashJoinType::LeftOuter\n    } else {\n        HashJoinType::Inner\n    };\n\n    // Can't build from a NullRow'd table — the hash table would hold real data\n    // even when the cursor is in NullRow mode.\n    if build_table\n        .join_info\n        .as_ref()\n        .is_some_and(|ji| ji.is_outer())\n    {\n        return None;\n    }\n\n    // Skip hash join on USING/NATURAL joins.\n    if build_table\n        .join_info\n        .as_ref()\n        .is_some_and(|ji| !ji.using.is_empty())\n        || probe_table\n            .join_info\n            .as_ref()\n            .is_some_and(|ji| !ji.using.is_empty())\n    {\n        return None;\n    }\n\n    // Avoid hash joins when there are correlated subqueries that reference the joined tables.\n    for subquery in subqueries {\n        if !subquery.correlated {\n            continue;\n        }\n        // Check if the subquery references the build or probe table\n        if let SubqueryState::Unevaluated { plan } = &subquery.state {\n            if let Some(plan) = plan.as_ref() {\n                let outer_refs = plan.table_references.outer_query_refs();\n                for outer_ref in outer_refs {\n                    if outer_ref.internal_id == build_table.internal_id\n                        || outer_ref.internal_id == probe_table.internal_id\n                    {\n                        return None;\n                    }\n                }\n            }\n        }\n    }\n\n    let join_keys = find_equijoin_conditions(\n        build_table.internal_id,\n        probe_table.internal_id,\n        where_clause,\n    )\n    .into_iter()\n    .filter(|join_key| {\n        let probe_expr = join_key.get_probe_expr(where_clause);\n        let Some(probe_tables) = collect_table_refs(probe_expr) else {\n            return false;\n        };\n        probe_tables.len() == 1 && probe_tables[0] == probe_table.internal_id\n    })\n    .collect::<Vec<_>>();\n    tracing::debug!(\n        build_table = build_table.table.get_name(),\n        probe_table = probe_table.table.get_name(),\n        join_key_count = join_keys.len(),\n        \"hash-join equi-join keys\"\n    );\n\n    // Need at least one equi-join condition\n    if join_keys.is_empty() {\n        return None;\n    }\n\n    // Prefer nested-loop with index lookup when an index exists on join columns.\n    // FULL OUTER must use hash join (needed for the unmatched-build scan).\n    // Check both tables because we could potentially use a different\n    // join order where the indexed table becomes the probe/inner table.\n    if hash_join_type != HashJoinType::FullOuter {\n        for join_key in &join_keys {\n            let probe_expr = join_key.get_probe_expr(where_clause);\n            let probe_tables = collect_table_refs(probe_expr).unwrap_or_default();\n            let probe_is_single_table =\n                probe_tables.len() == 1 && probe_tables[0] == probe_table.internal_id;\n            let probe_is_simple_column =\n                expr_is_simple_column_from_table(probe_expr, probe_table.internal_id);\n            let build_expr = join_key.get_build_expr(where_clause);\n            let build_is_simple_column =\n                expr_is_simple_column_from_table(build_expr, build_table.internal_id);\n            // Check probe table constraints for index on join column, only when the probe side\n            // references the probe table alone and is a simple column/rowid reference.\n            if probe_is_single_table && probe_is_simple_column {\n                if let Some(constraint) = probe_constraints\n                    .constraints\n                    .iter()\n                    .find(|c| c.where_clause_pos.0 == join_key.where_clause_idx)\n                {\n                    if let Some(col_pos) = constraint.table_col_pos {\n                        // Check if the join column is a rowid alias directly from the table schema\n                        if let Some(column) = probe_table.columns().get(col_pos) {\n                            if column.is_rowid_alias() {\n                                return None;\n                            }\n                        }\n                        // Also check regular indexes\n                        for candidate in &probe_constraints.candidates {\n                            if let Some(index) = &candidate.index {\n                                if index.column_table_pos_to_index_pos(col_pos).is_some() {\n                                    return None;\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n\n            // Check build table constraints for index on join column, only when the build side\n            // is a simple column/rowid reference.\n            if build_is_simple_column {\n                if let Some(constraint) = build_constraints\n                    .constraints\n                    .iter()\n                    .find(|c| c.where_clause_pos.0 == join_key.where_clause_idx)\n                {\n                    if let Some(col_pos) = constraint.table_col_pos {\n                        // Check if the join column is a rowid alias directly from the table schema\n                        if let Some(column) = build_table.columns().get(col_pos) {\n                            if column.is_rowid_alias() {\n                                return None;\n                            }\n                        }\n                        // Also check regular indexes\n                        for candidate in &build_constraints.candidates {\n                            if let Some(index) = &candidate.index {\n                                if index.column_table_pos_to_index_pos(col_pos).is_some() {\n                                    return None;\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    let cost = estimate_hash_join_cost(\n        build_cardinality,\n        probe_cardinality,\n        DEFAULT_MEM_BUDGET,\n        probe_multiplier,\n        params,\n    );\n    Some(AccessMethod {\n        cost,\n        estimated_rows_per_outer_row: probe_cardinality,\n        residual_constraints: ResidualConstraintMode::ApplyUnconsumed,\n        consumed_where_terms: join_keys.iter().map(|key| key.where_clause_idx).collect(),\n        params: AccessMethodParams::HashJoin {\n            build_table_idx,\n            probe_table_idx,\n            join_keys,\n            mem_budget: DEFAULT_MEM_BUDGET,\n            materialize_build_input: false,\n            use_bloom_filter: false,\n            join_type: hash_join_type,\n        },\n    })\n}\n\n/// Returns true when the expression is a simple column/rowid reference to the table.\n/// Used to decide if an index seek could replace a hash join.\nfn expr_is_simple_column_from_table(expr: &ast::Expr, table_id: TableInternalId) -> bool {\n    matches!(\n        expr,\n        ast::Expr::Column { table, .. } | ast::Expr::RowId { table, .. } if *table == table_id\n    )\n}\n\n/// Check whether a subquery's intrinsic row order (from its ORDER BY or\n/// finalized inner scan) already satisfies the outer order target, and if so\n/// in which direction.\n///\n/// Backwards iteration (`Last`/`Prev`) is only possible when\n/// `table_materialization_required` is true — coroutine scans cannot be\n/// reversed at runtime.\nfn intrinsic_subquery_scan_direction(\n    rhs_table: &JoinedTable,\n    subquery: &FromClauseSubquery,\n    maybe_order_target: Option<&OrderTarget>,\n    table_materialization_required: bool,\n    schema: &Schema,\n) -> Option<IterationDirection> {\n    let order_target = maybe_order_target?;\n    let cols = &order_target.columns;\n\n    let matches_forwards = subquery_intrinsic_order_consumed(\n        rhs_table.internal_id,\n        subquery,\n        IterationDirection::Forwards,\n        cols,\n        schema,\n    ) == cols.len();\n    if matches_forwards {\n        return Some(IterationDirection::Forwards);\n    }\n\n    let matches_backwards = table_materialization_required\n        && subquery_intrinsic_order_consumed(\n            rhs_table.internal_id,\n            subquery,\n            IterationDirection::Backwards,\n            cols,\n            schema,\n        ) == cols.len();\n    matches_backwards.then_some(IterationDirection::Backwards)\n}\n\n/// Find the best access method for a FROM clause subquery.\n///\n/// Uncorrelated FROM-subqueries can either stay as coroutine scans or be treated\n/// like a table-backed row source with a synthesized ephemeral probe index. When\n/// the latter is worthwhile, we materialize the subquery into an EphemeralTable\n/// and later build the probe index lazily in the main-loop open phase.\n#[expect(clippy::too_many_arguments)]\nfn find_best_access_method_for_subquery(\n    rhs_table: &JoinedTable,\n    subquery: &FromClauseSubquery,\n    rhs_constraints: &TableConstraints,\n    join_order: &[JoinOrderMember],\n    planning_context: JoinPlanningContext<'_>,\n    schema: &Schema,\n    input_cardinality: f64,\n    base_row_count: RowCountEstimate,\n    params: &CostModelParams,\n) -> Result<Option<AccessMethod>> {\n    use super::constraints::ConstraintRef;\n    let maybe_order_target = planning_context.maybe_order_target;\n\n    let table_materialization_required = subquery.requires_table_materialization();\n    let can_direct_materialize_index = subquery.supports_direct_index_materialization();\n    let coroutine_scan_cost = estimate_cost_for_scan_or_seek(\n        None,\n        &[],\n        &[],\n        input_cardinality,\n        base_row_count,\n        false,\n        params,\n        None,\n    );\n    let coroutine_reexecution_overhead =\n        Cost((input_cardinality - 1.0).max(0.0) * *base_row_count * params.cpu_cost_per_seek);\n    let coroutine_cost = coroutine_scan_cost + coroutine_reexecution_overhead;\n    let scan_cost = if table_materialization_required {\n        // Explicit MATERIALIZED hints and shared CTEs already produce a table-backed\n        // row source. Scanning them behaves like rescanning cached rows, not rerunning\n        // a coroutine body for each outer probe.\n        coroutine_scan_cost\n    } else {\n        // The generic scan model treats repeated probes like cached rescans of a\n        // row source. A coroutine-backed subquery is slightly more expensive: each\n        // extra outer row reruns the subquery program instead of probing a\n        // materialized result. Charge that extra work explicitly here.\n        coroutine_cost\n    };\n\n    // Plans with outer-scope dependencies cannot be materialized once -\n    // they must re-execute for each outer row. Use coroutine for these.\n    // This check must come first because correlated CTEs should NOT share materialized data.\n    if plan_has_outer_scope_dependency(&subquery.plan) {\n        return Ok(Some(AccessMethod {\n            // Correlated subqueries always rerun for each outer row, even if the\n            // enclosing CTE/subquery might otherwise be shareable.\n            cost: coroutine_cost,\n            estimated_rows_per_outer_row: *base_row_count,\n            residual_constraints: ResidualConstraintMode::ApplyUnconsumed,\n            consumed_where_terms: SmallVec::new(),\n            params: AccessMethodParams::Subquery {\n                iter_dir: IterationDirection::Forwards,\n            },\n        }));\n    }\n\n    // Build synthetic index columns from the constraints this materialized\n    // subquery could probe. Because the index is ephemeral, we can order its\n    // key columns to fit the chosen seek shape: equality columns first, then\n    // range-only columns, then the remaining payload columns.\n    let usable: Vec<(usize, &Constraint)> = rhs_constraints\n        .constraints\n        .iter()\n        .enumerate()\n        .filter(|(_, c)| {\n            c.usable\n                && c.table_col_pos.is_some()\n                && matches!(\n                    c.operator.as_ast_operator(),\n                    Some(\n                        ast::Operator::Equals\n                            | ast::Operator::Greater\n                            | ast::Operator::GreaterEquals\n                            | ast::Operator::Less\n                            | ast::Operator::LessEquals\n                    )\n                )\n        })\n        .collect();\n\n    // For extremum (MIN/MAX) targets we can reuse the subquery's intrinsic\n    // order as a plain scan when every usable constraint is on the extremum\n    // column itself. Once other key columns participate, the direct table\n    // scan no longer has a simple \"walk from one end\" shape.\n    let extremum_constraints_compatible = maybe_order_target.is_some_and(|ot| ot.is_extremum())\n        && match maybe_order_target\n            .and_then(|ot| ot.columns.first())\n            .map(|c| &c.target)\n        {\n            Some(ColumnTarget::Column(pos)) => {\n                usable.iter().all(|(_, c)| c.table_col_pos == Some(*pos))\n            }\n            _ => false,\n        };\n\n    // Try to reuse the subquery's intrinsic row order (from its ORDER BY or\n    // finalized inner scan) to satisfy the outer order target directly.\n    // For non-extremum targets this only applies when there are no seek\n    // constraints — with constraints, we fall through to the materialized\n    // index path which has its own order-satisfaction logic.\n    if extremum_constraints_compatible || usable.is_empty() {\n        if let Some(iter_dir) = intrinsic_subquery_scan_direction(\n            rhs_table,\n            subquery,\n            maybe_order_target,\n            table_materialization_required,\n            schema,\n        ) {\n            return Ok(Some(AccessMethod {\n                cost: scan_cost,\n                estimated_rows_per_outer_row: *base_row_count,\n                residual_constraints: ResidualConstraintMode::ApplyUnconsumed,\n                consumed_where_terms: SmallVec::new(),\n                params: AccessMethodParams::Subquery { iter_dir },\n            }));\n        }\n    }\n\n    if usable.is_empty() {\n        return Ok(Some(AccessMethod {\n            cost: scan_cost,\n            estimated_rows_per_outer_row: *base_row_count,\n            residual_constraints: ResidualConstraintMode::ApplyUnconsumed,\n            consumed_where_terms: SmallVec::new(),\n            params: AccessMethodParams::Subquery {\n                iter_dir: IterationDirection::Forwards,\n            },\n        }));\n    }\n\n    let usable_constraints: Vec<&Constraint> = usable.iter().map(|(_, c)| *c).collect();\n    let key_col_positions = ordered_materialized_key_columns(&usable_constraints);\n    if key_col_positions.is_empty() {\n        return Ok(Some(AccessMethod {\n            cost: scan_cost,\n            estimated_rows_per_outer_row: *base_row_count,\n            residual_constraints: ResidualConstraintMode::ApplyUnconsumed,\n            consumed_where_terms: SmallVec::new(),\n            params: AccessMethodParams::Subquery {\n                iter_dir: IterationDirection::Forwards,\n            },\n        }));\n    }\n\n    let key_col_pos_to_index_pos: HashMap<usize, usize> = key_col_positions\n        .iter()\n        .enumerate()\n        .map(|(index_col_pos, table_col_pos)| (*table_col_pos, index_col_pos))\n        .collect();\n\n    // Map each usable constraint to a ConstraintRef\n    let mut temp_constraint_refs: Vec<ConstraintRef> = usable\n        .iter()\n        .map(|(orig_idx, c)| {\n            let table_col_pos = c.table_col_pos.expect(\"table_col_pos was Some above\");\n            let index_col_pos = *key_col_pos_to_index_pos\n                .get(&table_col_pos)\n                .expect(\"table_col_pos must exist in key_col_positions\");\n            ConstraintRef {\n                constraint_vec_pos: *orig_idx,\n                index_col_pos,\n                sort_order: SortOrder::Asc,\n            }\n        })\n        .collect();\n\n    temp_constraint_refs.sort_by_key(|x| x.index_col_pos);\n\n    // Filter to only constraints that can be used given the current join order\n    let usable_constraint_refs = usable_constraints_for_join_order(\n        &rhs_constraints.constraints,\n        &temp_constraint_refs,\n        join_order,\n    );\n\n    let has_search_constraints = !usable_constraint_refs.is_empty();\n    if !has_search_constraints {\n        tracing::trace!(\n            table = rhs_table.table.get_name(),\n            cost = ?scan_cost,\n            \"using coroutine subquery access because no usable seek constraints remain\"\n        );\n        return Ok(Some(AccessMethod {\n            cost: scan_cost,\n            estimated_rows_per_outer_row: *base_row_count,\n            residual_constraints: ResidualConstraintMode::ApplyUnconsumed,\n            consumed_where_terms: SmallVec::new(),\n            params: AccessMethodParams::Subquery {\n                iter_dir: IterationDirection::Forwards,\n            },\n        }));\n    }\n\n    let ephemeral_index =\n        materialized_subquery_ephemeral_index(rhs_table, subquery, &key_col_positions);\n    let (iter_dir, _is_index_ordered, order_satisfiability_bonus) =\n        materialized_subquery_order_properties(\n            rhs_table,\n            &ephemeral_index,\n            &usable_constraint_refs,\n            maybe_order_target,\n            schema,\n            base_row_count,\n            params,\n        );\n\n    let estimated_rows_per_outer_row = estimate_rows_per_seek(\n        IndexInfo {\n            unique: false,\n            column_count: key_col_positions.len(),\n            covering: true,\n            rows_per_leaf_page: params.rows_per_table_page,\n        },\n        &rhs_constraints.constraints,\n        &usable_constraint_refs,\n        base_row_count,\n        None,\n    );\n    let one_pass_scan_cost =\n        estimate_cost_for_scan_or_seek(None, &[], &[], 1.0, base_row_count, false, params, None);\n    let append_build_cost = Cost(*base_row_count * params.cpu_cost_per_seek);\n    let seek_setup_cost = if table_materialization_required || can_direct_materialize_index {\n        // Both table-backed materialization and direct-index materialization avoid\n        // the extra \"scan table into probe index\" pass. They differ in storage,\n        // not in setup work.\n        one_pass_scan_cost + append_build_cost\n    } else {\n        // Compound SELECTs and other table-backed materializations need two passes:\n        // first produce the ephemeral table, then scan it once to build the probe\n        // index used by SEARCH.\n        one_pass_scan_cost + one_pass_scan_cost + append_build_cost\n    };\n    let seek_cost = Cost(\n        input_cardinality * params.cpu_cost_per_seek\n            + input_cardinality * estimated_rows_per_outer_row * params.cpu_cost_per_row,\n    );\n    let total_cost = seek_setup_cost + seek_cost;\n\n    if total_cost >= scan_cost + order_satisfiability_bonus {\n        return Ok(Some(AccessMethod {\n            cost: scan_cost,\n            estimated_rows_per_outer_row: *base_row_count,\n            residual_constraints: ResidualConstraintMode::ApplyUnconsumed,\n            consumed_where_terms: SmallVec::new(),\n            params: AccessMethodParams::Subquery {\n                iter_dir: IterationDirection::Forwards,\n            },\n        }));\n    }\n\n    Ok(Some(AccessMethod {\n        cost: total_cost,\n        estimated_rows_per_outer_row,\n        residual_constraints: ResidualConstraintMode::ApplyUnconsumed,\n        consumed_where_terms: consumed_where_terms_from_constraint_refs(\n            &rhs_constraints.constraints,\n            &usable_constraint_refs,\n        ),\n        params: AccessMethodParams::MaterializedSubquery {\n            index: ephemeral_index,\n            constraint_refs: usable_constraint_refs,\n            iter_dir,\n        },\n    }))\n}\n\n/// Describe the temporary index layout we would build on top of a materialized\n/// subquery if the planner chooses a seekable access path.\n///\n/// This is planner metadata, not the runtime build step itself. The optimizer\n/// needs this shape up front so it can reason about seek prefixes, order\n/// coverage, and result-column remapping before any bytecode is emitted.\nfn materialized_subquery_ephemeral_index(\n    rhs_table: &JoinedTable,\n    subquery: &FromClauseSubquery,\n    key_col_positions: &[usize],\n) -> Arc<Index> {\n    let mut index_columns: Vec<IndexColumn> = Vec::new();\n    let mut seen_col_positions = std::collections::HashSet::new();\n\n    for &col_pos in key_col_positions {\n        let column = subquery\n            .columns\n            .get(col_pos)\n            .expect(\"key column position out of bounds for materialized subquery\");\n        if !seen_col_positions.insert(col_pos) {\n            continue;\n        }\n        index_columns.push(IndexColumn {\n            name: column.name.clone().unwrap_or_default(),\n            order: SortOrder::Asc,\n            pos_in_table: col_pos,\n            collation: column.collation_opt(),\n            default: column.default.clone(),\n            expr: None,\n        });\n    }\n\n    for (col_pos, column) in subquery.columns.iter().enumerate() {\n        if seen_col_positions.contains(&col_pos) {\n            continue;\n        }\n        index_columns.push(IndexColumn {\n            name: column.name.clone().unwrap_or_default(),\n            order: SortOrder::Asc,\n            pos_in_table: col_pos,\n            collation: column.collation_opt(),\n            default: column.default.clone(),\n            expr: None,\n        });\n    }\n\n    Arc::new(Index {\n        // Match the runtime autoindex naming so EQP and bytecode make it clear\n        // that this is a synthetic probe/index-on-temp-table path.\n        name: format!(\"ephemeral_subquery_{}\", rhs_table.internal_id),\n        columns: index_columns,\n        unique: false,\n        ephemeral: true,\n        table_name: subquery.name.clone(),\n        root_page: 0,\n        where_clause: None,\n        has_rowid: true,\n        index_method: None,\n        on_conflict: None,\n    })\n}\n\n/// Decide whether the synthetic materialized-subquery index would also satisfy\n/// the requested order target, and if so in which direction.\n///\n/// The returned bonus is the estimated sorter work avoided by getting rows in\n/// the right order directly from the temporary index.\nfn materialized_subquery_order_properties(\n    rhs_table: &JoinedTable,\n    index: &Arc<Index>,\n    constraint_refs: &[RangeConstraintRef],\n    maybe_order_target: Option<&OrderTarget>,\n    schema: &Schema,\n    base_row_count: RowCountEstimate,\n    params: &CostModelParams,\n) -> (IterationDirection, bool, Cost) {\n    let Some(order_target) = maybe_order_target else {\n        return (IterationDirection::Forwards, false, Cost(0.0));\n    };\n\n    // Candidate scoring may ignore any equality-constrained prefix because a\n    // seek fixes those columns to one value before iteration begins.\n    let all_same_direction = btree_access_order_consumed(\n        rhs_table,\n        IterationDirection::Forwards,\n        Some(index.as_ref()),\n        constraint_refs,\n        &order_target.columns,\n        schema,\n        EqualityPrefixScope::AnyEquality,\n    ) == order_target.columns.len();\n    let all_opposite_direction = btree_access_order_consumed(\n        rhs_table,\n        IterationDirection::Backwards,\n        Some(index.as_ref()),\n        constraint_refs,\n        &order_target.columns,\n        schema,\n        EqualityPrefixScope::AnyEquality,\n    ) == order_target.columns.len();\n\n    if !(all_same_direction || all_opposite_direction) {\n        return (IterationDirection::Forwards, false, Cost(0.0));\n    }\n\n    // Reuse the same rough sorter cost model as ordinary ORDER BY planning:\n    // if this index yields the needed order, we avoid an O(n log n) sort.\n    let n = *base_row_count;\n    let order_bonus = Cost(n * n.max(1.0).log2() * params.sort_cpu_per_row);\n    (\n        if all_same_direction {\n            IterationDirection::Forwards\n        } else {\n            IterationDirection::Backwards\n        },\n        true,\n        order_bonus,\n    )\n}\n"
  },
  {
    "path": "core/translate/optimizer/constraints.rs",
    "content": "use crate::{\n    schema::{Column, Index, Schema},\n    translate::{\n        collate::get_collseq_from_expr,\n        expr::{as_binary_components, comparison_affinity},\n        expression_index::normalize_expr_for_index_matching,\n        plan::{JoinOrderMember, JoinedTable, NonFromClauseSubquery, TableReferences, WhereTerm},\n        planner::{table_mask_from_expr, TableMask},\n    },\n    util::exprs_are_equivalent,\n    vdbe::affinity::Affinity,\n    Result,\n};\nuse crate::{turso_assert, turso_debug_assert};\nuse rustc_hash::FxHashMap as HashMap;\nuse std::{cmp::Ordering, collections::VecDeque, sync::Arc};\nuse turso_ext::{ConstraintInfo, ConstraintOp};\nuse turso_parser::ast::{self, SortOrder, TableInternalId};\n\nuse super::cost_params::CostModelParams;\n\n/// Represents a single condition derived from a `WHERE` clause term\n/// that constrains a specific column of a table.\n///\n/// Constraints are precomputed for each table involved in a query. They are used\n/// during query optimization to estimate the cost of different access paths (e.g., using an index)\n/// and to determine the optimal join order. A constraint can only be applied if all tables\n/// referenced in its expression (other than the constrained table itself) are already\n/// available in the current join context, i.e. on the left side in the join order\n/// relative to the table. Expression indexes are represented by leaving `table_col_pos` empty\n/// and storing the indexed expression in `expr`.\n#[derive(Debug, Clone)]\npub struct Constraint {\n    /// The position of the original `WHERE` clause term this constraint derives from,\n    /// and which side of the [ast::Expr::Binary] comparison contains the expression\n    /// that constrains the column.\n    /// E.g. in SELECT * FROM t WHERE t.x = 10, the constraint is (0, BinaryExprSide::Rhs)\n    /// because the RHS '10' is the constraining expression.\n    ///\n    /// This is tracked so we can:\n    ///\n    /// 1. Extract the constraining expression for use in an index seek key, and\n    /// 2. Remove the relevant binary expression from the WHERE clause, if used as an index seek key.\n    pub where_clause_pos: (usize, BinaryExprSide),\n    /// The comparison operator (e.g., `=`, `>`, `<`) used in the constraint.\n    pub operator: ConstraintOperator,\n    /// The zero-based index of the constrained column within the table's schema.\n    /// None for expression-index constraints.\n    pub table_col_pos: Option<usize>,\n    /// The expression constrained by this constraint, if it is not a simple column reference.\n    pub expr: Option<ast::Expr>,\n    /// For multi-index scan branches: the constraining expression and its affinity.\n    /// When set, `get_constraining_expr` uses this instead of looking up in where_clause.\n    /// This is needed because multi-index branches come from sub-expressions of an OR/AND,\n    /// not directly from a top-level WHERE term.\n    pub constraining_expr: Option<(ast::Operator, ast::Expr, Affinity)>,\n    /// A bitmask representing the set of tables that appear on the *constraining* side\n    /// of the comparison expression. For example, in SELECT * FROM t1,t2,t3 WHERE t1.x = t2.x + t3.x,\n    /// the lhs_mask contains t2 and t3. Thus, this constraint can only be used if t2 and t3\n    /// have already been joined (i.e. are on the left side of the join order relative to t1).\n    pub lhs_mask: TableMask,\n    /// An estimated selectivity factor (0.0 to 1.0) indicating the fraction of rows\n    /// expected to satisfy this constraint. Used for cost and cardinality estimation.\n    pub selectivity: f64,\n    /// Whether the constraint can participate in range-seek index matching\n    /// (the eq/lower_bound/upper_bound model in RangeConstraintRef).\n    /// False for IN constraints (which use a separate multi-value seek path)\n    /// and for collation mismatches.\n    pub usable: bool,\n    /// Whether this constraint references the implicit rowid (tables without an INTEGER PRIMARY KEY alias).\n    /// When true and `table_col_pos` is None, this constraint targets the rowid pseudo-column.\n    pub is_rowid: bool,\n}\n\n#[derive(Debug, Clone, Copy, PartialEq)]\npub enum ConstraintOperator {\n    AstNativeOperator(ast::Operator),\n    Like { not: bool },\n    In { not: bool, estimated_values: f64 },\n}\n\nimpl ConstraintOperator {\n    pub fn as_ast_operator(&self) -> Option<ast::Operator> {\n        let ConstraintOperator::AstNativeOperator(op) = self else {\n            return None;\n        };\n        Some(*op)\n    }\n}\n\nimpl From<ast::Operator> for ConstraintOperator {\n    fn from(op: ast::Operator) -> Self {\n        ConstraintOperator::AstNativeOperator(op)\n    }\n}\n\n#[derive(Debug, Clone, Copy, PartialEq)]\npub enum BinaryExprSide {\n    Lhs,\n    Rhs,\n}\n\nimpl Constraint {\n    /// Get the constraining expression and operator, e.g. ('>=', '2+3') from 't.x >= 2+3'\n    pub fn get_constraining_expr(\n        &self,\n        where_clause: &[WhereTerm],\n        referenced_tables: Option<&TableReferences>,\n    ) -> (ast::Operator, ast::Expr, Affinity) {\n        // For multi-index branches, use the pre-computed constraining expression\n        if let Some(constraining) = &self.constraining_expr {\n            return constraining.clone();\n        }\n\n        let (idx, side) = self.where_clause_pos;\n        let where_term = &where_clause[idx];\n        let Ok(Some((lhs, op, rhs))) = as_binary_components(&where_term.expr) else {\n            panic!(\"Expected a valid binary expression\");\n        };\n        let mut affinity = Affinity::Blob;\n        if op.as_ast_operator().is_some_and(|op| op.is_comparison()) && self.table_col_pos.is_some()\n        {\n            affinity = comparison_affinity(lhs, rhs, referenced_tables, None);\n        }\n\n        if side == BinaryExprSide::Lhs {\n            if affinity.expr_needs_no_affinity_change(lhs) {\n                affinity = Affinity::Blob;\n            }\n            (\n                self.operator\n                    .as_ast_operator()\n                    .expect(\"expected an ast operator because as_binary_components returned Some\"),\n                lhs.clone(),\n                affinity,\n            )\n        } else {\n            if affinity.expr_needs_no_affinity_change(rhs) {\n                affinity = Affinity::Blob;\n            }\n            (\n                self.operator\n                    .as_ast_operator()\n                    .expect(\"expected an ast operator because as_binary_components returned Some\"),\n                rhs.clone(),\n                affinity,\n            )\n        }\n    }\n\n    pub fn get_constraining_expr_ref<'a>(&self, where_clause: &'a [WhereTerm]) -> &'a ast::Expr {\n        let (idx, side) = self.where_clause_pos;\n        let where_term = &where_clause[idx];\n        let Ok(Some((lhs, _, rhs))) = as_binary_components(&where_term.expr) else {\n            panic!(\"Expected a valid binary expression\");\n        };\n        if side == BinaryExprSide::Lhs {\n            lhs\n        } else {\n            rhs\n        }\n    }\n}\n\n#[derive(Debug, Clone)]\n/// A reference to a [Constraint] in a [TableConstraints].\n///\n/// This is used to track which constraints may be used as an index seek key.\npub struct ConstraintRef {\n    /// The position of the constraint in the [TableConstraints::constraints] vector.\n    pub constraint_vec_pos: usize,\n    /// The position of the constrained column in the index. Always 0 for rowid indices.\n    pub index_col_pos: usize,\n    /// The sort order of the constrained column in the index. Always ascending for rowid indices.\n    pub sort_order: SortOrder,\n}\n\n/// A collection of [ConstraintRef]s for a given index, or if index is None, for the table's rowid index.\n/// For example, given a table `T (x,y,z)` with an index `T_I (y desc,z)`, take the following query:\n/// ```sql\n/// SELECT * FROM T WHERE y = 10 AND z = 20;\n/// ```\n///\n/// This will produce the following [ConstraintUseCandidate]:\n///\n/// ConstraintUseCandidate {\n///     index: Some(T_I)\n///     refs: [\n///         ConstraintRef {\n///             constraint_vec_pos: 0, // y = 10\n///             index_col_pos: 0, // y\n///             sort_order: SortOrder::Desc,\n///         },\n///         ConstraintRef {\n///             constraint_vec_pos: 1, // z = 20\n///             index_col_pos: 1, // z\n///             sort_order: SortOrder::Asc,\n///         },\n///     ],\n/// }\n///\n#[derive(Debug)]\npub struct ConstraintUseCandidate {\n    /// The index that may be used to satisfy the constraints. If none, the table's rowid index is used.\n    pub index: Option<Arc<Index>>,\n    /// References to the constraints that may be used as an access path for the index.\n    /// Refs are sorted by [ConstraintRef::index_col_pos]\n    pub refs: Vec<ConstraintRef>,\n}\n\n#[derive(Debug)]\n/// A collection of [Constraint]s and their potential [ConstraintUseCandidate]s for a given table.\npub struct TableConstraints {\n    /// The internal ID of the [TableReference] that these constraints are for.\n    pub table_id: TableInternalId,\n    /// The constraints for the table, i.e. any [WhereTerm]s that reference columns from this table.\n    pub constraints: Vec<Constraint>,\n    /// Candidates for indexes that may use the constraints to perform a lookup.\n    pub candidates: Vec<ConstraintUseCandidate>,\n}\n\n/// Estimate selectivity for IN expressions given the number of values and table row count.\nfn estimate_in_selectivity(in_list_len: f64, row_count: f64, not: bool) -> f64 {\n    if not {\n        // NOT IN: each value in the list excludes roughly 1/ndv of the rows.\n        // Without ANALYZE stats we don't know ndv, so we use the equality\n        // selectivity heuristic (sel_eq_unindexed = 0.1) per excluded value.\n        // This gives NOT IN (v1,v2,v3) ≈ (1 - 0.1)^3 ≈ 0.729, which is a\n        // reasonable estimate that the filter does meaningful work.\n        let per_value_sel = 0.1_f64; // matches sel_eq_unindexed default\n        (1.0 - per_value_sel).powf(in_list_len).max(0.01)\n    } else {\n        (in_list_len / row_count).min(1.0)\n    }\n}\n\n/// Estimate the selectivity of a constraint based on the operator, column type, and ANALYZE stats.\n///\n/// When ANALYZE stats are available, we use:\n/// - For unique/PK columns: 1 / row_count (one row expected per lookup)\n/// - For non-unique indexed columns: uses index stats to find avg rows per distinct value\n///\n/// The sqlite_stat1 format stores: total_rows, avg_rows_per_key_col1, avg_rows_per_key_col1_col2, ...\n/// So selectivity = avg_rows_per_key / total_rows\n///\n/// Falls back to hardcoded estimates when stats are unavailable.\n#[allow(clippy::too_many_arguments)]\nfn estimate_selectivity(\n    schema: &Schema,\n    table_name: &str,\n    column: Option<&Column>,\n    column_pos: Option<usize>,\n    available_indexes: &HashMap<String, VecDeque<Arc<Index>>>,\n    op: ConstraintOperator,\n    params: &CostModelParams,\n    is_rowid: bool,\n) -> f64 {\n    // Get ANALYZE stats for this table if available\n    let table_stats = schema.analyze_stats.table_stats(table_name);\n    let row_count = table_stats.and_then(|s| s.row_count).unwrap_or(0);\n\n    match op {\n        ConstraintOperator::AstNativeOperator(ast::Operator::Equals) => {\n            let is_pk_or_rowid_alias =\n                is_rowid || column.is_some_and(|c| c.is_rowid_alias() || c.primary_key());\n\n            let selectivity_when_unique = if row_count > 0 {\n                1.0 / row_count as f64\n            } else {\n                // Fallback: use hardcoded estimate based on expected table size\n                1.0 / params.rows_per_table_fallback\n            };\n\n            if is_pk_or_rowid_alias {\n                selectivity_when_unique\n            } else if let Some(col_pos) = column_pos {\n                // For non-unique columns, find an index containing this column and use its stats\n                if let Some(indexes) = available_indexes.get(table_name) {\n                    for index in indexes {\n                        // Check if this index has our column as its first column\n                        // (selectivity is most accurate when column is leftmost in index)\n                        if let Some(idx_col_pos) = index.column_table_pos_to_index_pos(col_pos) {\n                            // Only use stats if column is first in index (idx_col_pos == 0)\n                            // because that's when the distinct count is most useful\n                            if idx_col_pos == 0 {\n                                // Only use unique selectivity for single-column unique indexes.\n                                // For composite unique indexes like tpc-h (l_orderkey, l_linenumber),\n                                // the first column alone is NOT unique.\n                                if index.unique && index.columns.len() == 1 {\n                                    return selectivity_when_unique;\n                                }\n                                if let Some(stats) = table_stats {\n                                    if let Some(idx_stat) = stats.index_stats.get(&index.name) {\n                                        if let (Some(total), Some(&avg_rows)) = (\n                                            idx_stat.total_rows,\n                                            idx_stat.avg_rows_per_distinct_prefix.first(),\n                                        ) {\n                                            if total > 0 && avg_rows > 0 {\n                                                // selectivity = avg_rows_per_key / total_rows\n                                                return avg_rows as f64 / total as f64;\n                                            }\n                                        }\n                                    }\n                                } else {\n                                    return params.sel_eq_indexed;\n                                }\n                            }\n                        }\n                    }\n                }\n                // Fallback: use hardcoded selectivity for non-indexed columns\n                // Don't scale by row_count - keep it distinct from PK selectivity\n                params.sel_eq_unindexed\n            } else {\n                params.sel_eq_unindexed\n            }\n        }\n        ConstraintOperator::AstNativeOperator(ast::Operator::Greater)\n        | ConstraintOperator::AstNativeOperator(ast::Operator::GreaterEquals)\n        | ConstraintOperator::AstNativeOperator(ast::Operator::Less)\n        | ConstraintOperator::AstNativeOperator(ast::Operator::LessEquals) => params.sel_range,\n        ConstraintOperator::AstNativeOperator(ast::Operator::Is) => params.sel_is_null,\n        ConstraintOperator::AstNativeOperator(ast::Operator::IsNot) => params.sel_is_not_null,\n        ConstraintOperator::Like { not: false } => params.sel_like,\n        ConstraintOperator::Like { not: true } => params.sel_not_like,\n        ConstraintOperator::In {\n            not,\n            estimated_values,\n        } => estimate_in_selectivity(estimated_values, row_count as f64, not),\n        _ => params.sel_other,\n    }\n}\n\n#[allow(clippy::too_many_arguments)]\n/// Estimate selectivity for a single WHERE/ON constraint applied to `table_reference`.\nfn estimate_constraint_selectivity(\n    schema: &Schema,\n    table_reference: &JoinedTable,\n    column: Option<&Column>,\n    column_pos: Option<usize>,\n    operator: ConstraintOperator,\n    available_indexes: &HashMap<String, VecDeque<Arc<Index>>>,\n    params: &CostModelParams,\n    is_rowid: bool,\n) -> f64 {\n    estimate_selectivity(\n        schema,\n        table_reference.table.get_name(),\n        column,\n        column_pos,\n        available_indexes,\n        operator,\n        params,\n        is_rowid,\n    )\n}\n\nfn expression_matches_table(\n    expr: &ast::Expr,\n    table_reference: &JoinedTable,\n    table_references: &TableReferences,\n    subqueries: &[NonFromClauseSubquery],\n) -> bool {\n    match table_mask_from_expr(expr, table_references, subqueries) {\n        Ok(mask) => table_references\n            .joined_tables()\n            .iter()\n            .position(|t| t.internal_id == table_reference.internal_id)\n            .is_some_and(|idx| mask.contains_table(idx) && mask.table_count() == 1),\n        Err(_) => false,\n    }\n}\n\n/// Precompute all potentially usable [Constraints] from a WHERE clause.\n/// The resulting list of [TableConstraints] is then used to evaluate the best access methods for various join orders.\n///\n/// This method do not perform much filtering of constraints and delegate this tasks to the consumers of the method\n/// Consumers must inspect [TableConstraints] and its candidates and pick best constraints for optimized access\npub fn constraints_from_where_clause(\n    where_clause: &[WhereTerm],\n    table_references: &TableReferences,\n    available_indexes: &HashMap<String, VecDeque<Arc<Index>>>,\n    subqueries: &[NonFromClauseSubquery],\n    schema: &Schema,\n    params: &CostModelParams,\n) -> Result<Vec<TableConstraints>> {\n    let mut constraints = Vec::new();\n\n    // For each table, collect all the Constraints and all potential index candidates that may use them.\n    for table_reference in table_references.joined_tables() {\n        let rowid_alias_column = table_reference\n            .columns()\n            .iter()\n            .position(|c| c.is_rowid_alias());\n\n        let mut cs = TableConstraints {\n            table_id: table_reference.internal_id,\n            constraints: Vec::new(),\n            candidates: available_indexes\n                .get(table_reference.table.get_name())\n                .map_or(Vec::new(), |indexes| {\n                    indexes\n                        .iter()\n                        // Skip IndexMethod-based indexes (FTS, vector, etc.) - they use\n                        // pattern matching rather than btree index scans\n                        .filter(|index| index.index_method.is_none())\n                        .map(|index| ConstraintUseCandidate {\n                            index: Some(index.clone()),\n                            refs: Vec::new(),\n                        })\n                        .collect()\n                }),\n        };\n        // Add a candidate for the rowid index, which is always available when the table has a rowid alias.\n        cs.candidates.push(ConstraintUseCandidate {\n            index: None,\n            refs: Vec::new(),\n        });\n\n        for (i, term) in where_clause.iter().enumerate() {\n            // Constraints originating from a LEFT JOIN must always be evaluated in that join's RHS table's loop,\n            // regardless of which tables the constraint references.\n            if let Some(outer_join_tbl) = term.from_outer_join {\n                if outer_join_tbl != table_reference.internal_id {\n                    continue;\n                }\n            }\n\n            // Try to extract as binary expression first\n            if let Some((lhs, operator, rhs)) = as_binary_components(&term.expr)? {\n                // If either the LHS or RHS of the constraint is a column from the table, add the constraint.\n                match lhs {\n                    ast::Expr::Column { table, column, .. } => {\n                        if *table == table_reference.internal_id {\n                            let table_column = &table_reference.table.columns()[*column];\n                            cs.constraints.push(Constraint {\n                                where_clause_pos: (i, BinaryExprSide::Rhs),\n                                operator,\n                                table_col_pos: Some(*column),\n                                expr: None,\n                                constraining_expr: None,\n                                lhs_mask: table_mask_from_expr(rhs, table_references, subqueries)?,\n                                selectivity: estimate_constraint_selectivity(\n                                    schema,\n                                    table_reference,\n                                    Some(table_column),\n                                    Some(*column),\n                                    operator,\n                                    available_indexes,\n                                    params,\n                                    false,\n                                ),\n                                usable: true,\n                                is_rowid: false,\n                            });\n                        }\n                    }\n                    ast::Expr::RowId { table, .. } => {\n                        if *table == table_reference.internal_id {\n                            let (col, col_pos) = if let Some(alias) = rowid_alias_column {\n                                (Some(&table_reference.table.columns()[alias]), Some(alias))\n                            } else {\n                                (None, None)\n                            };\n                            cs.constraints.push(Constraint {\n                                where_clause_pos: (i, BinaryExprSide::Rhs),\n                                operator,\n                                table_col_pos: col_pos,\n                                expr: None,\n                                constraining_expr: None,\n                                lhs_mask: table_mask_from_expr(rhs, table_references, subqueries)?,\n                                selectivity: estimate_constraint_selectivity(\n                                    schema,\n                                    table_reference,\n                                    col,\n                                    col_pos,\n                                    operator,\n                                    available_indexes,\n                                    params,\n                                    true,\n                                ),\n                                usable: true,\n                                is_rowid: true,\n                            });\n                        }\n                    }\n                    _ if expression_matches_table(\n                        lhs,\n                        table_reference,\n                        table_references,\n                        subqueries,\n                    ) =>\n                    {\n                        let selectivity = estimate_constraint_selectivity(\n                            schema,\n                            table_reference,\n                            None,\n                            None,\n                            operator,\n                            available_indexes,\n                            params,\n                            false,\n                        );\n                        tracing::debug!(\n                            table = table_reference.table.get_name(),\n                            where_clause_pos = i,\n                            operator = ?operator,\n                            lhs_mask = ?table_mask_from_expr(rhs, table_references, subqueries)?,\n                            selectivity,\n                            \"expr constraint (lhs matches table)\"\n                        );\n                        cs.constraints.push(Constraint {\n                            where_clause_pos: (i, BinaryExprSide::Rhs),\n                            operator,\n                            table_col_pos: None,\n                            expr: Some(lhs.clone()),\n                            constraining_expr: None,\n                            lhs_mask: table_mask_from_expr(rhs, table_references, subqueries)?,\n                            selectivity,\n                            usable: true,\n                            is_rowid: false,\n                        });\n                    }\n                    _ => {}\n                };\n                match rhs {\n                    ast::Expr::Column { table, column, .. } => {\n                        if *table == table_reference.internal_id {\n                            let table_column = &table_reference.table.columns()[*column];\n                            cs.constraints.push(Constraint {\n                                where_clause_pos: (i, BinaryExprSide::Lhs),\n                                operator: opposite_cmp_op(operator),\n                                table_col_pos: Some(*column),\n                                expr: None,\n                                constraining_expr: None,\n                                lhs_mask: table_mask_from_expr(lhs, table_references, subqueries)?,\n                                selectivity: estimate_constraint_selectivity(\n                                    schema,\n                                    table_reference,\n                                    Some(table_column),\n                                    Some(*column),\n                                    operator,\n                                    available_indexes,\n                                    params,\n                                    false,\n                                ),\n                                usable: true,\n                                is_rowid: false,\n                            });\n                        }\n                    }\n                    ast::Expr::RowId { table, .. } => {\n                        if *table == table_reference.internal_id {\n                            let (col, col_pos) = if let Some(alias) = rowid_alias_column {\n                                (Some(&table_reference.table.columns()[alias]), Some(alias))\n                            } else {\n                                (None, None)\n                            };\n                            cs.constraints.push(Constraint {\n                                where_clause_pos: (i, BinaryExprSide::Lhs),\n                                operator: opposite_cmp_op(operator),\n                                table_col_pos: col_pos,\n                                expr: None,\n                                constraining_expr: None,\n                                lhs_mask: table_mask_from_expr(lhs, table_references, subqueries)?,\n                                selectivity: estimate_constraint_selectivity(\n                                    schema,\n                                    table_reference,\n                                    col,\n                                    col_pos,\n                                    operator,\n                                    available_indexes,\n                                    params,\n                                    true,\n                                ),\n                                usable: true,\n                                is_rowid: true,\n                            });\n                        }\n                    }\n                    _ if expression_matches_table(\n                        rhs,\n                        table_reference,\n                        table_references,\n                        subqueries,\n                    ) =>\n                    {\n                        let selectivity = estimate_constraint_selectivity(\n                            schema,\n                            table_reference,\n                            None,\n                            None,\n                            operator,\n                            available_indexes,\n                            params,\n                            false,\n                        );\n                        tracing::debug!(\n                            table = table_reference.table.get_name(),\n                            where_clause_pos = i,\n                            operator = ?operator,\n                            lhs_mask = ?table_mask_from_expr(lhs, table_references, subqueries)?,\n                            selectivity,\n                            \"expr constraint (rhs matches table)\"\n                        );\n                        cs.constraints.push(Constraint {\n                            where_clause_pos: (i, BinaryExprSide::Lhs),\n                            operator: opposite_cmp_op(operator),\n                            table_col_pos: None,\n                            expr: Some(rhs.clone()),\n                            constraining_expr: None,\n                            lhs_mask: table_mask_from_expr(lhs, table_references, subqueries)?,\n                            selectivity,\n                            usable: true,\n                            is_rowid: false,\n                        });\n                    }\n                    _ => {}\n                };\n            }\n\n            // IN expressions are handled separately from binary expressions above because:\n            // - as_binary_components returns (&Expr, ConstraintOperator, &Expr) - a single RHS\n            // - InList has Vec<Expr> as RHS, SubqueryResult has a different structure entirely\n            // - They don't fit the binary expression abstraction without a more complex return type\n\n            // Handle IN list: col IN (val1, val2, ...)\n            if let ast::Expr::InList { lhs, not, rhs } = &term.expr {\n                let estimated_values = rhs.len() as f64;\n                let mut rhs_mask = TableMask::new();\n                for rhs_expr in rhs.iter() {\n                    rhs_mask |= table_mask_from_expr(rhs_expr, table_references, subqueries)?;\n                }\n                let table_stats = schema\n                    .analyze_stats\n                    .table_stats(table_reference.table.get_name());\n                let row_count = table_stats\n                    .and_then(|s| s.row_count)\n                    .unwrap_or(params.rows_per_table_fallback as u64)\n                    as f64;\n                let selectivity = estimate_in_selectivity(estimated_values, row_count, *not);\n\n                match lhs.as_ref() {\n                    ast::Expr::Column { table, column, .. }\n                        if *table == table_reference.internal_id =>\n                    {\n                        let is_rowid = rowid_alias_column == Some(*column);\n                        cs.constraints.push(Constraint {\n                            where_clause_pos: (i, BinaryExprSide::Rhs),\n                            operator: ConstraintOperator::In {\n                                not: *not,\n                                estimated_values,\n                            },\n                            table_col_pos: Some(*column),\n                            expr: None,\n                            constraining_expr: None,\n                            lhs_mask: rhs_mask,\n                            selectivity,\n                            usable: false, // IN uses a separate seek path, not the range-seek model\n                            is_rowid,\n                        });\n                    }\n                    ast::Expr::RowId { table, .. } if *table == table_reference.internal_id => {\n                        cs.constraints.push(Constraint {\n                            where_clause_pos: (i, BinaryExprSide::Rhs),\n                            operator: ConstraintOperator::In {\n                                not: *not,\n                                estimated_values,\n                            },\n                            table_col_pos: rowid_alias_column,\n                            expr: None,\n                            constraining_expr: None,\n                            lhs_mask: rhs_mask,\n                            selectivity,\n                            usable: false,\n                            is_rowid: true,\n                        });\n                    }\n                    _ => {}\n                }\n            }\n\n            // Handle IN subquery: col IN (SELECT ...)\n            if let ast::Expr::SubqueryResult {\n                subquery_id,\n                lhs: Some(lhs_expr),\n                not_in,\n                query_type: ast::SubqueryType::In { .. },\n            } = &term.expr\n            {\n                // Find the subquery to check if it's correlated\n                let subquery = subqueries\n                    .iter()\n                    .find(|s| s.internal_id == *subquery_id)\n                    .expect(\"subquery not found\");\n                // Only use as constraint if NOT correlated\n                if !subquery.correlated {\n                    let estimated_values = params.in_subquery_rows;\n                    let table_stats = schema\n                        .analyze_stats\n                        .table_stats(table_reference.table.get_name());\n                    let row_count = table_stats\n                        .and_then(|s| s.row_count)\n                        .unwrap_or(params.rows_per_table_fallback as u64)\n                        as f64;\n                    let selectivity = estimate_in_selectivity(estimated_values, row_count, *not_in);\n\n                    match lhs_expr.as_ref() {\n                        ast::Expr::Column { table, column, .. }\n                            if *table == table_reference.internal_id =>\n                        {\n                            let is_rowid = rowid_alias_column == Some(*column);\n                            cs.constraints.push(Constraint {\n                                where_clause_pos: (i, BinaryExprSide::Rhs),\n                                operator: ConstraintOperator::In {\n                                    not: *not_in,\n                                    estimated_values,\n                                },\n                                table_col_pos: Some(*column),\n                                expr: None,\n                                constraining_expr: None,\n                                lhs_mask: TableMask::new(), // non-correlated = no dependencies\n                                selectivity,\n                                usable: false, // IN uses a separate seek path (consider_in_list_seek)\n                                is_rowid,\n                            });\n                        }\n                        ast::Expr::RowId { table, .. } if *table == table_reference.internal_id => {\n                            cs.constraints.push(Constraint {\n                                where_clause_pos: (i, BinaryExprSide::Rhs),\n                                operator: ConstraintOperator::In {\n                                    not: *not_in,\n                                    estimated_values,\n                                },\n                                table_col_pos: rowid_alias_column,\n                                expr: None,\n                                constraining_expr: None,\n                                lhs_mask: TableMask::new(),\n                                selectivity,\n                                usable: false,\n                                is_rowid: true,\n                            });\n                        }\n                        _ => {}\n                    }\n                }\n            }\n        }\n        // sort equalities first so that index keys will be properly constructed.\n        // see e.g.: https://www.solarwinds.com/blog/the-left-prefix-index-rule\n        cs.constraints.sort_by(|a, b| {\n            if a.operator == ast::Operator::Equals.into() {\n                Ordering::Less\n            } else if b.operator == ast::Operator::Equals.into() {\n                Ordering::Greater\n            } else {\n                Ordering::Equal\n            }\n        });\n\n        // For each constraint we found, add a reference to it for each index that may be able to use it.\n        for (i, constraint) in cs.constraints.iter_mut().enumerate() {\n            // Skip constraints that don't participate in range-seek matching (IN, collation mismatches)\n            if !constraint.usable {\n                continue;\n            }\n\n            let constrained_column = constraint\n                .table_col_pos\n                .and_then(|pos| table_reference.table.columns().get(pos));\n            let column_collation = constrained_column.map(|c| c.collation());\n            let constraining_expr = constraint.get_constraining_expr_ref(where_clause);\n            // Index seek keys must use the same collation as the constrained column.\n            match (\n                get_collseq_from_expr(constraining_expr, table_references)?,\n                column_collation,\n            ) {\n                (Some(collation), Some(column_collation)) if collation != column_collation => {\n                    constraint.usable = false;\n                    continue;\n                }\n                _ => {}\n            }\n\n            if constraint.is_rowid\n                || rowid_alias_column.is_some_and(|p| constraint.table_col_pos == Some(p))\n            {\n                let rowid_candidate = cs\n                    .candidates\n                    .iter_mut()\n                    .find_map(|candidate| {\n                        if candidate.index.is_none() {\n                            Some(candidate)\n                        } else {\n                            None\n                        }\n                    })\n                    .unwrap();\n                rowid_candidate.refs.push(ConstraintRef {\n                    constraint_vec_pos: i,\n                    index_col_pos: 0,\n                    sort_order: SortOrder::Asc,\n                });\n            }\n            for index in available_indexes\n                .get(table_reference.table.get_name())\n                .unwrap_or(&VecDeque::new())\n                .iter()\n                .filter(|idx| idx.index_method.is_none())\n            {\n                if let Some(position_in_index) = match constraint.table_col_pos {\n                    Some(pos) => index.column_table_pos_to_index_pos(pos),\n                    None => constraint.expr.as_ref().and_then(|e| {\n                        let normalized =\n                            normalize_expr_for_index_matching(e, table_reference, table_references);\n                        index.expression_to_index_pos(&normalized)\n                    }),\n                } {\n                    turso_assert!(\n                        constraint.usable,\n                        \"constraint collation must match table column collation\"\n                    );\n                    if let Some(table_col_pos) = constraint.table_col_pos {\n                        let constrained_column = &table_reference.table.columns()[table_col_pos];\n                        let table_collation = constrained_column.collation();\n                        let index_collation = index.columns[position_in_index]\n                            .collation\n                            .unwrap_or_default();\n                        if table_collation != index_collation {\n                            continue;\n                        }\n                        // Custom type columns encode values as blobs. Blob ordering (memcmp)\n                        // doesn't necessarily match the custom type's semantic ordering, so\n                        // range constraints (>, <, >=, <=) can't use the index. Equality (=)\n                        // still works because encoded(A) == encoded(B) iff A == B.\n                        if schema\n                            .get_type_def(\n                                &constrained_column.ty_str,\n                                table_reference.table.is_strict(),\n                            )\n                            .is_some()\n                            && constraint.operator != ast::Operator::Equals.into()\n                        {\n                            continue;\n                        }\n                    }\n                    if let Some(index_candidate) = cs.candidates.iter_mut().find_map(|candidate| {\n                        if candidate.index.as_ref().is_some_and(|i| {\n                            Arc::ptr_eq(index, i) && can_use_partial_index(index, where_clause)\n                        }) {\n                            Some(candidate)\n                        } else {\n                            None\n                        }\n                    }) {\n                        index_candidate.refs.push(ConstraintRef {\n                            constraint_vec_pos: i,\n                            index_col_pos: position_in_index,\n                            sort_order: index.columns[position_in_index].order,\n                        });\n                    }\n                }\n            }\n        }\n\n        for candidate in cs.candidates.iter_mut() {\n            // Sort by index_col_pos, ascending -- index columns must be consumed in contiguous order.\n            candidate.refs.sort_by_key(|cref| cref.index_col_pos);\n        }\n        cs.candidates.retain(|c| {\n            if let Some(idx) = &c.index {\n                if idx.where_clause.is_some() && c.refs.is_empty() {\n                    // prevent a partial index from even being considered as a scan driver.\n                    return false;\n                }\n            }\n            true\n        });\n        constraints.push(cs);\n    }\n\n    Ok(constraints)\n}\n\n/// A reference to a [Constraint]s in a [TableConstraints] for single column.\n///\n/// This is specialized version of [ConstraintRef] which specifically holds range-like constraints:\n/// - x = 10 (eq is set)\n/// - x >= 10, x > 10 (lower_bound is set)\n/// - x <= 10, x < 10 (upper_bound is set)\n/// - x > 10 AND x < 20 (both lower_bound and upper_bound are set)\n///\n/// eq, lower_bound and upper_bound holds None or position of the constraint in the [Constraint] array\n\n#[derive(Debug, Clone)]\npub struct EqConstraintRef {\n    /// Position of the constraint in the [Constraint] array.\n    pub constraint_pos: usize,\n    /// Whether this equality constrains the column to a single value for the\n    /// entire query (true for `col = 5`, false for `t2.x = t1.b` where the\n    /// value changes per outer row in a nested-loop join).\n    pub is_const: bool,\n}\n\n#[derive(Debug, Clone)]\npub struct RangeConstraintRef {\n    /// position of the column in the table definition\n    pub table_col_pos: Option<usize>,\n    /// position of the column in the index definition\n    pub index_col_pos: usize,\n    /// sort order for the column in the index definition\n    pub sort_order: SortOrder,\n    /// equality constraint\n    pub eq: Option<EqConstraintRef>,\n    /// lower bound constraint (either > or >=)\n    pub lower_bound: Option<usize>,\n    /// upper bound constraint (either < or <=)\n    pub upper_bound: Option<usize>,\n}\n\n#[derive(Debug, Clone)]\n/// Represent seek range which can be used in query planning to emit range scan over table or index\npub struct SeekRangeConstraint {\n    pub sort_order: SortOrder,\n    pub eq: Option<(ast::Operator, ast::Expr, Affinity)>,\n    pub lower_bound: Option<(ast::Operator, ast::Expr, Affinity)>,\n    pub upper_bound: Option<(ast::Operator, ast::Expr, Affinity)>,\n}\n\nimpl SeekRangeConstraint {\n    pub fn new_eq(sort_order: SortOrder, eq: (ast::Operator, ast::Expr, Affinity)) -> Self {\n        Self {\n            sort_order,\n            eq: Some(eq),\n            lower_bound: None,\n            upper_bound: None,\n        }\n    }\n    pub fn new_range(\n        sort_order: SortOrder,\n        lower_bound: Option<(ast::Operator, ast::Expr, Affinity)>,\n        upper_bound: Option<(ast::Operator, ast::Expr, Affinity)>,\n    ) -> Self {\n        turso_assert!(lower_bound.is_some() || upper_bound.is_some());\n        Self {\n            sort_order,\n            eq: None,\n            lower_bound,\n            upper_bound,\n        }\n    }\n}\n\nimpl RangeConstraintRef {\n    /// Convert the [RangeConstraintRef] to a [SeekRangeConstraint] usable in a [crate::translate::plan::SeekDef::key].\n    pub fn as_seek_range_constraint(\n        &self,\n        constraints: &[Constraint],\n        where_clause: &[WhereTerm],\n        referenced_tables: Option<&TableReferences>,\n    ) -> SeekRangeConstraint {\n        if let Some(ref eq) = self.eq {\n            return SeekRangeConstraint::new_eq(\n                self.sort_order,\n                constraints[eq.constraint_pos]\n                    .get_constraining_expr(where_clause, referenced_tables),\n            );\n        }\n        SeekRangeConstraint::new_range(\n            self.sort_order,\n            self.lower_bound\n                .map(|x| constraints[x].get_constraining_expr(where_clause, referenced_tables)),\n            self.upper_bound\n                .map(|x| constraints[x].get_constraining_expr(where_clause, referenced_tables)),\n        )\n    }\n}\n\n/// Find which [Constraint]s are usable for a given join order.\n/// Returns a slice of the references to the constraints that are usable.\n/// A constraint is considered usable for a given table if all of the other tables referenced by the constraint\n/// are on the left side in the join order relative to the table.\n///\n/// This enforces the normal B-tree prefix rules:\n/// - usable index columns must form a contiguous prefix starting at column 0\n/// - once a prefix column has no usable constraint, later columns cannot be used\n/// - once a prefix column uses a range constraint, later columns cannot be used\n///\n/// Multiple constraints on the same index column are merged into a single\n/// [RangeConstraintRef]. Equality wins over range constraints; otherwise we keep\n/// at most one lower bound and one upper bound for that column.\npub fn usable_constraints_for_lhs_mask(\n    constraints: &[Constraint],\n    refs: &[ConstraintRef],\n    lhs_mask: &TableMask,\n    table_idx: usize,\n) -> Vec<RangeConstraintRef> {\n    turso_debug_assert!(refs.is_sorted_by_key(|x| x.index_col_pos));\n\n    let mut usable: Vec<RangeConstraintRef> = Vec::new();\n    let mut current_required_column_pos = 0;\n    for cref in refs.iter() {\n        let constraint = &constraints[cref.constraint_vec_pos];\n        let other_side_refers_to_self = constraint.lhs_mask.contains_table(table_idx);\n        if other_side_refers_to_self {\n            // Self-referential constraints cannot seed a lookup, but if they are\n            // on a later index column they also terminate the usable prefix.\n            if cref.index_col_pos != current_required_column_pos {\n                break;\n            }\n            continue;\n        }\n        if !lhs_mask.contains_all(&constraint.lhs_mask) {\n            // Join-dependent constraints are only usable when every referenced\n            // outer table is already on the left side of the join order. As\n            // above, a missing earlier prefix column terminates the prefix.\n            if cref.index_col_pos != current_required_column_pos {\n                break;\n            }\n            continue;\n        }\n        if Some(cref.index_col_pos) == usable.last().map(|x| x.index_col_pos) {\n            // Merge multiple usable constraints for the same index column into a\n            // single equality-or-range group.\n            assert_eq!(cref.sort_order, usable.last().unwrap().sort_order);\n            assert_eq!(cref.index_col_pos, usable.last().unwrap().index_col_pos);\n            assert_eq!(\n                constraints[cref.constraint_vec_pos].table_col_pos,\n                usable.last().unwrap().table_col_pos\n            );\n            if usable.last().unwrap().eq.is_some() {\n                // An equality already fixes this column exactly, so extra\n                // constraints on the same column do not change the seek shape.\n                continue;\n            }\n            match constraints[cref.constraint_vec_pos]\n                .operator\n                .as_ast_operator()\n            {\n                Some(ast::Operator::Greater) | Some(ast::Operator::GreaterEquals) => {\n                    usable.last_mut().unwrap().lower_bound = Some(cref.constraint_vec_pos);\n                }\n                Some(ast::Operator::Less) | Some(ast::Operator::LessEquals) => {\n                    usable.last_mut().unwrap().upper_bound = Some(cref.constraint_vec_pos);\n                }\n                _ => {}\n            }\n            continue;\n        }\n        if cref.index_col_pos != current_required_column_pos {\n            // We found a gap in the usable prefix, so later index columns are\n            // not usable for the lookup.\n            break;\n        }\n        if usable.last().is_some_and(|x| x.eq.is_none()) {\n            // The previous prefix column is already a range, so no later column\n            // can participate in the seek key.\n            break;\n        }\n        let operator = constraints[cref.constraint_vec_pos].operator;\n        let table_col_pos = constraints[cref.constraint_vec_pos].table_col_pos;\n        if operator == ast::Operator::Equals.into()\n            && usable\n                .last()\n                .is_some_and(|x| x.table_col_pos == table_col_pos)\n        {\n            // Duplicate equalities on the same column do not expand the usable\n            // prefix or change the seek shape.\n            continue;\n        }\n        let constraint_group = match operator.as_ast_operator() {\n            Some(ast::Operator::Equals) => RangeConstraintRef {\n                table_col_pos,\n                index_col_pos: cref.index_col_pos,\n                sort_order: cref.sort_order,\n                eq: Some(EqConstraintRef {\n                    constraint_pos: cref.constraint_vec_pos,\n                    is_const: constraints[cref.constraint_vec_pos].lhs_mask.is_empty(),\n                }),\n                lower_bound: None,\n                upper_bound: None,\n            },\n            Some(ast::Operator::Greater) | Some(ast::Operator::GreaterEquals) => {\n                RangeConstraintRef {\n                    table_col_pos,\n                    index_col_pos: cref.index_col_pos,\n                    sort_order: cref.sort_order,\n                    eq: None,\n                    lower_bound: Some(cref.constraint_vec_pos),\n                    upper_bound: None,\n                }\n            }\n            Some(ast::Operator::Less) | Some(ast::Operator::LessEquals) => RangeConstraintRef {\n                table_col_pos,\n                index_col_pos: cref.index_col_pos,\n                sort_order: cref.sort_order,\n                eq: None,\n                lower_bound: None,\n                upper_bound: Some(cref.constraint_vec_pos),\n            },\n            _ => continue,\n        };\n        usable.push(constraint_group);\n        current_required_column_pos += 1;\n    }\n    usable\n}\n\npub fn usable_constraints_for_join_order<'a>(\n    constraints: &'a [Constraint],\n    refs: &'a [ConstraintRef],\n    join_order: &[JoinOrderMember],\n) -> Vec<RangeConstraintRef> {\n    turso_debug_assert!(refs.is_sorted_by_key(|x| x.index_col_pos));\n\n    let table_idx = join_order.last().unwrap().original_idx;\n    let lhs_mask = TableMask::from_table_number_iter(\n        join_order\n            .iter()\n            .take(join_order.len() - 1)\n            .map(|j| j.original_idx),\n    );\n    usable_constraints_for_lhs_mask(constraints, refs, &lhs_mask, table_idx)\n}\n\n/// Order synthetic key columns for a materialized subquery seek index.\n///\n/// Unlike ordinary index analysis, the ephemeral index does not have a fixed\n/// on-disk column order, so we can choose one that matches the intended probe\n/// shape. Equalities come first, followed by columns that are constrained only\n/// by ranges. Columns that have both equality and range predicates stay in the\n/// equality prefix; the range side is redundant for key ordering.\npub fn ordered_materialized_key_columns(constraints: &[&Constraint]) -> Vec<usize> {\n    let mut equality_cols = Vec::new();\n    let mut range_only_cols = Vec::new();\n\n    for constraint in constraints {\n        let Some(col_pos) = constraint.table_col_pos else {\n            continue;\n        };\n        match constraint.operator.as_ast_operator() {\n            Some(ast::Operator::Equals) => equality_cols.push(col_pos),\n            Some(\n                ast::Operator::Greater\n                | ast::Operator::GreaterEquals\n                | ast::Operator::Less\n                | ast::Operator::LessEquals,\n            ) => range_only_cols.push(col_pos),\n            _ => {}\n        }\n    }\n\n    equality_cols.sort_unstable();\n    equality_cols.dedup();\n    range_only_cols.sort_unstable();\n    range_only_cols.dedup();\n    range_only_cols.retain(|col_pos| !equality_cols.contains(col_pos));\n\n    let mut ordered = equality_cols;\n    ordered.extend(range_only_cols);\n    ordered\n}\n\nfn can_use_partial_index(index: &Index, query_where_clause: &[WhereTerm]) -> bool {\n    let Some(index_where) = &index.where_clause else {\n        // Full index, always usable\n        return true;\n    };\n    // Check if query WHERE contains the exact same predicate\n    for term in query_where_clause {\n        if exprs_are_equivalent(&term.expr, index_where.as_ref()) {\n            return true;\n        }\n    }\n    // TODO: do better to determine if we should use partial index\n    false\n}\n\npub fn convert_to_vtab_constraint(\n    constraints: &[Constraint],\n    join_order: &[JoinOrderMember],\n) -> Vec<ConstraintInfo> {\n    let table_idx = join_order.last().unwrap().original_idx;\n    let lhs_mask = TableMask::from_table_number_iter(\n        join_order\n            .iter()\n            .take(join_order.len() - 1)\n            .map(|j| j.original_idx),\n    );\n    constraints\n        .iter()\n        .enumerate()\n        .filter_map(|(i, constraint)| {\n            let table_col_pos = constraint.table_col_pos?;\n            let other_side_refers_to_self = constraint.lhs_mask.contains_table(table_idx);\n            if other_side_refers_to_self {\n                return None;\n            }\n            let all_required_tables_are_on_left_side = lhs_mask.contains_all(&constraint.lhs_mask);\n            to_ext_constraint_op(&constraint.operator).map(|op| ConstraintInfo {\n                column_index: table_col_pos as u32,\n                op,\n                usable: all_required_tables_are_on_left_side,\n                index: i,\n            })\n        })\n        .collect()\n}\n\nfn to_ext_constraint_op(op: &ConstraintOperator) -> Option<ConstraintOp> {\n    let ConstraintOperator::AstNativeOperator(op) = op else {\n        return None;\n    };\n    match op {\n        ast::Operator::Equals => Some(ConstraintOp::Eq),\n        ast::Operator::Less => Some(ConstraintOp::Lt),\n        ast::Operator::LessEquals => Some(ConstraintOp::Le),\n        ast::Operator::Greater => Some(ConstraintOp::Gt),\n        ast::Operator::GreaterEquals => Some(ConstraintOp::Ge),\n        ast::Operator::NotEquals => Some(ConstraintOp::Ne),\n        _ => None,\n    }\n}\n\nfn opposite_cmp_op(op: ConstraintOperator) -> ConstraintOperator {\n    let ConstraintOperator::AstNativeOperator(op_inner) = &op else {\n        return op;\n    };\n    match op_inner {\n        ast::Operator::Equals => ast::Operator::Equals,\n        ast::Operator::Greater => ast::Operator::Less,\n        ast::Operator::GreaterEquals => ast::Operator::LessEquals,\n        ast::Operator::Less => ast::Operator::Greater,\n        ast::Operator::LessEquals => ast::Operator::GreaterEquals,\n        ast::Operator::NotEquals => ast::Operator::NotEquals,\n        ast::Operator::Is => ast::Operator::Is,\n        ast::Operator::IsNot => ast::Operator::IsNot,\n        _ => panic!(\"unexpected operator: {op:?}\"),\n    }\n    .into()\n}\n\n/// Result of analyzing a single term for multi-index scan potential.\n/// This is a shared intermediate structure used by both OR and AND analysis.\n#[derive(Debug)]\npub struct AnalyzedTerm {\n    /// The constraint derived from this term.\n    pub constraint: Constraint,\n    /// The best index for this term, if any.\n    pub best_index: Option<Arc<Index>>,\n    /// Constraint references for this term.\n    pub constraint_refs: Vec<RangeConstraintRef>,\n}\n\n/// Analyzes a single binary expression to determine if it can use an index.\n///\n/// This is a shared helper for both OR and AND multi-index analysis.\n/// Returns `Some(AnalyzedTerm)` if the expression is a usable indexed constraint,\n/// `None` otherwise.\n#[allow(clippy::too_many_arguments)]\npub(crate) fn analyze_binary_term_for_index(\n    expr: &ast::Expr,\n    where_term_idx: usize,\n    table_id: TableInternalId,\n    table_reference: &JoinedTable,\n    indexes: Option<&VecDeque<Arc<Index>>>,\n    rowid_alias_column: Option<usize>,\n    available_indexes: &HashMap<String, VecDeque<Arc<Index>>>,\n    table_references: &TableReferences,\n    subqueries: &[NonFromClauseSubquery],\n    schema: &Schema,\n    params: &CostModelParams,\n) -> Option<AnalyzedTerm> {\n    // Try to extract a binary comparison\n    let (lhs, operator, rhs) = as_binary_components(expr).ok().flatten()?;\n\n    // Check if the operator is usable for index seeks\n    let is_usable_op = matches!(\n        operator.as_ast_operator(),\n        Some(\n            ast::Operator::Equals\n                | ast::Operator::Greater\n                | ast::Operator::GreaterEquals\n                | ast::Operator::Less\n                | ast::Operator::LessEquals\n        )\n    );\n\n    if !is_usable_op {\n        return None;\n    }\n\n    // Check if this is an indexable constraint on our table\n    let (table_col_pos, constraining_expr, side, is_rowid) = match lhs {\n        ast::Expr::Column { table, column, .. } if *table == table_id => {\n            (Some(*column), rhs.clone(), BinaryExprSide::Rhs, false)\n        }\n        ast::Expr::RowId { table, .. } if *table == table_id => {\n            (rowid_alias_column, rhs.clone(), BinaryExprSide::Rhs, true)\n        }\n        _ => match rhs {\n            ast::Expr::Column { table, column, .. } if *table == table_id => {\n                (Some(*column), lhs.clone(), BinaryExprSide::Lhs, false)\n            }\n            ast::Expr::RowId { table, .. } if *table == table_id => {\n                (rowid_alias_column, lhs.clone(), BinaryExprSide::Lhs, true)\n            }\n            _ => return None, // Doesn't reference our table\n        },\n    };\n\n    // Normalize operator direction so it matches the constrained table column.\n    // Example: `1 > t.b` constrains `t.b < 1`.\n    let operator = if side == BinaryExprSide::Lhs {\n        opposite_cmp_op(operator)\n    } else {\n        operator\n    };\n\n    // Find the best index for this constraint\n    let (best_index, constraint_refs) = find_best_index_for_constraint(\n        table_col_pos,\n        operator,\n        indexes,\n        rowid_alias_column,\n        is_rowid,\n    );\n\n    // If no index can be used, this term is not indexable\n    if constraint_refs.is_empty() {\n        return None;\n    }\n\n    let table_column = table_col_pos.and_then(|pos| table_reference.table.columns().get(pos));\n    let selectivity = estimate_constraint_selectivity(\n        schema,\n        table_reference,\n        table_column,\n        table_col_pos,\n        operator,\n        available_indexes,\n        params,\n        is_rowid,\n    );\n\n    let lhs_mask = table_mask_from_expr(&constraining_expr, table_references, subqueries)\n        .unwrap_or_else(|_| TableMask::new());\n\n    // Cannot use index seek if the constraining expression references the same table\n    // being scanned, since the expression value varies per row and cannot be evaluated\n    // before the scan (e.g. TYPEOF(b) NOT BETWEEN a AND a where both columns are from\n    // the same table).\n    if let Some(table_pos) = table_references\n        .joined_tables()\n        .iter()\n        .position(|t| t.internal_id == table_id)\n    {\n        if lhs_mask.contains_table(table_pos) {\n            return None;\n        }\n    }\n\n    // Compute the affinity for the constraining expression\n    let affinity = if let Some(ast_op) = operator.as_ast_operator() {\n        if ast_op.is_comparison() && table_col_pos.is_some() {\n            comparison_affinity(lhs, rhs, Some(table_references), None)\n        } else {\n            Affinity::Blob\n        }\n    } else {\n        Affinity::Blob\n    };\n\n    // Store the pre-computed constraining expression for multi-index branches\n    let stored_constraining_expr = operator\n        .as_ast_operator()\n        .map(|ast_op| (ast_op, constraining_expr.clone(), affinity));\n\n    let constraint = Constraint {\n        where_clause_pos: (where_term_idx, side),\n        operator,\n        table_col_pos,\n        expr: None,\n        constraining_expr: stored_constraining_expr,\n        lhs_mask,\n        selectivity,\n        usable: true,\n        is_rowid,\n    };\n\n    Some(AnalyzedTerm {\n        constraint,\n        best_index,\n        constraint_refs,\n    })\n}\n\n/// Find the best index for a single constraint.\nfn find_best_index_for_constraint(\n    table_col_pos: Option<usize>,\n    operator: ConstraintOperator,\n    indexes: Option<&VecDeque<Arc<Index>>>,\n    rowid_alias_column: Option<usize>,\n    is_rowid: bool,\n) -> (Option<Arc<Index>>, Vec<RangeConstraintRef>) {\n    // Handle implicit rowid (no alias column, table_col_pos is None)\n    if is_rowid && table_col_pos.is_none() {\n        let constraint_ref = RangeConstraintRef {\n            table_col_pos: None,\n            index_col_pos: 0,\n            sort_order: SortOrder::Asc,\n            eq: if operator.as_ast_operator() == Some(ast::Operator::Equals) {\n                Some(EqConstraintRef {\n                    constraint_pos: 0,\n                    is_const: false,\n                })\n            } else {\n                None\n            },\n            lower_bound: match operator.as_ast_operator() {\n                Some(ast::Operator::Greater | ast::Operator::GreaterEquals) => Some(0),\n                _ => None,\n            },\n            upper_bound: match operator.as_ast_operator() {\n                Some(ast::Operator::Less | ast::Operator::LessEquals) => Some(0),\n                _ => None,\n            },\n        };\n        return (None, vec![constraint_ref]);\n    }\n\n    let Some(col_pos) = table_col_pos else {\n        return (None, vec![]);\n    };\n\n    // Check rowid index first if this is a rowid constraint\n    if rowid_alias_column == Some(col_pos) {\n        let constraint_ref = RangeConstraintRef {\n            table_col_pos: Some(col_pos),\n            index_col_pos: 0,\n            sort_order: SortOrder::Asc,\n            eq: if operator.as_ast_operator() == Some(ast::Operator::Equals) {\n                Some(EqConstraintRef {\n                    constraint_pos: 0,\n                    is_const: false,\n                })\n            } else {\n                None\n            },\n            lower_bound: match operator.as_ast_operator() {\n                Some(ast::Operator::Greater | ast::Operator::GreaterEquals) => Some(0),\n                _ => None,\n            },\n            upper_bound: match operator.as_ast_operator() {\n                Some(ast::Operator::Less | ast::Operator::LessEquals) => Some(0),\n                _ => None,\n            },\n        };\n        return (None, vec![constraint_ref]);\n    }\n\n    // Find the best index that has this column as its first column\n    if let Some(indexes) = indexes {\n        for index in indexes.iter().filter(|idx| idx.index_method.is_none()) {\n            if let Some(idx_col_pos) = index.column_table_pos_to_index_pos(col_pos) {\n                // For multi-index OR, we prefer indexes where the constraint column\n                // is the first column (leftmost prefix)\n                if idx_col_pos == 0 {\n                    let constraint_ref = RangeConstraintRef {\n                        table_col_pos: Some(col_pos),\n                        index_col_pos: 0,\n                        sort_order: index.columns[0].order,\n                        eq: if operator.as_ast_operator() == Some(ast::Operator::Equals) {\n                            Some(EqConstraintRef {\n                                constraint_pos: 0,\n                                is_const: false,\n                            })\n                        } else {\n                            None\n                        },\n                        lower_bound: match operator.as_ast_operator() {\n                            Some(ast::Operator::Greater | ast::Operator::GreaterEquals) => Some(0),\n                            _ => None,\n                        },\n                        upper_bound: match operator.as_ast_operator() {\n                            Some(ast::Operator::Less | ast::Operator::LessEquals) => Some(0),\n                            _ => None,\n                        },\n                    };\n                    return (Some(index.clone()), vec![constraint_ref]);\n                }\n            }\n        }\n    }\n\n    (None, vec![])\n}\n"
  },
  {
    "path": "core/translate/optimizer/cost.rs",
    "content": "use crate::schema::Index;\nuse crate::stats::AnalyzeStats;\nuse crate::sync::Arc;\nuse crate::translate::optimizer::constraints::RangeConstraintRef;\nuse crate::translate::plan::JoinedTable;\n\nuse super::constraints::Constraint;\nuse super::cost_params::CostModelParams;\n\n/// A simple newtype wrapper over a f64 that represents the cost of an operation.\n///\n/// This is used to estimate the cost of scans, seeks, and joins.\n#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]\npub struct Cost(pub f64);\n\nimpl std::ops::Add for Cost {\n    type Output = Cost;\n\n    fn add(self, other: Cost) -> Cost {\n        Cost(self.0 + other.0)\n    }\n}\n\nimpl std::ops::Deref for Cost {\n    type Target = f64;\n\n    fn deref(&self) -> &f64 {\n        &self.0\n    }\n}\n\n#[derive(Debug, Clone, Copy, PartialEq)]\npub struct IndexInfo {\n    pub unique: bool,\n    pub column_count: usize,\n    /// Whether the index satisfies the query without table lookups.\n    /// True for genuinely covering indexes and for multi-index branches\n    /// that only harvest rowids into a RowSet.\n    pub covering: bool,\n    /// Estimated rows per index leaf page, derived from the column count\n    /// ratio between the index and its parent table.\n    pub rows_per_leaf_page: f64,\n}\n\n/// Estimate rows per index leaf page based on the index/table width ratio.\n/// Narrower indexes have smaller entries, fitting more rows per page.\npub fn index_leaf_rows_per_page(\n    index_column_count: usize,\n    table_column_count: usize,\n    has_rowid_alias: bool,\n    rows_per_table_page: f64,\n) -> f64 {\n    // Table width: all columns + implicit rowid (unless one column IS the rowid alias).\n    let table_width = table_column_count as f64 + if has_rowid_alias { 0.0 } else { 1.0 };\n    // Index width: indexed columns + rowid suffix (always present).\n    let index_width = index_column_count as f64 + 1.0;\n    (rows_per_table_page * table_width / index_width).max(1.0)\n}\n\n/// Compute `rows_per_leaf_page` for an index on the given table.\npub fn rows_per_leaf_page_for_index(\n    index_column_count: usize,\n    rhs_table: &JoinedTable,\n    rows_per_table_page: f64,\n) -> f64 {\n    let table_column_count = rhs_table.columns().len();\n    let has_rowid_alias = rhs_table\n        .btree()\n        .is_some_and(|bt| bt.get_rowid_alias_column().is_some());\n    index_leaf_rows_per_page(\n        index_column_count,\n        table_column_count,\n        has_rowid_alias,\n        rows_per_table_page,\n    )\n}\n\n/// Estimate IO and CPU cost for a full table scan.\n///\n/// # Arguments\n/// * `base_row_count` - Total rows in the table\n/// * `num_scans` - Number of times we scan the table (e.g., from outer loop in nested loop join)\n/// * `params` - Cost model parameters\nfn estimate_scan_cost(base_row_count: f64, num_scans: f64, params: &CostModelParams) -> Cost {\n    let table_pages = (base_row_count / params.rows_per_table_page).max(1.0);\n\n    // First scan reads all pages; subsequent scans benefit from caching\n    let io_cost = if num_scans <= 1.0 {\n        table_pages\n    } else {\n        // First scan + discounted cost for subsequent scans\n        table_pages + (num_scans - 1.0) * table_pages * params.cache_reuse_factor\n    };\n\n    // CPU cost for processing all rows on each scan\n    let cpu_cost = num_scans * base_row_count * params.cpu_cost_per_row;\n\n    Cost(io_cost + cpu_cost)\n}\n\n/// Estimate IO and CPU cost for index-based access.\n///\n/// This properly separates the number of B-tree seeks from the number of rows\n/// returned per seek. A range scan does ONE seek followed by sequential leaf\n/// page reads, not one seek per row.\n///\n/// # Arguments\n/// * `base_row_count` - Total rows in the table (for estimating tree depth and page counts)\n/// * `tree_depth` - B-tree depth (number of pages to traverse per seek)\n/// * `index_info` - Index properties (covering, unique, etc.)\n/// * `num_seeks` - Number of B-tree traversals (typically = outer cardinality for joins)\n/// * `rows_per_seek` - Expected rows returned per seek (1 for point lookup, more for range)\n/// * `params` - Cost model parameters\npub fn estimate_index_cost(\n    base_row_count: f64,\n    tree_depth: f64,\n    index_info: IndexInfo,\n    input_cardinality: f64,\n    rows_per_seek: f64,\n    params: &CostModelParams,\n) -> Cost {\n    // Detect full index scan: when rows_per_seek equals base_row_count, we're scanning\n    // the entire index, not seeking to specific positions.\n    let is_full_scan = (rows_per_seek - base_row_count).abs() < 1.0;\n\n    // Cost of B-tree traversals: each seek traverses tree_depth pages.\n    let seek_cost = if is_full_scan {\n        // Full scan: one seek to start, then sequential reads.\n        // When re-scanned (nested loop inner), first scan is cold, rest are cached.\n        if input_cardinality <= 1.0 {\n            tree_depth\n        } else {\n            tree_depth + (input_cardinality - 1.0) * tree_depth * params.cache_reuse_factor\n        }\n    } else {\n        input_cardinality * tree_depth\n    };\n\n    let index_leaf_pages_count = (rows_per_seek / index_info.rows_per_leaf_page).max(1.0);\n    let leaf_scan_cost = if is_full_scan {\n        // Full scan of all leaf pages. Repeated scans benefit from caching.\n        if input_cardinality <= 1.0 {\n            index_leaf_pages_count\n        } else {\n            index_leaf_pages_count\n                + (input_cardinality - 1.0) * index_leaf_pages_count * params.cache_reuse_factor\n        }\n    } else if rows_per_seek <= 1.0 {\n        // Point lookup: the leaf page is the last page of the B-tree traversal,\n        // already counted in seek_cost.\n        0.0\n    } else if input_cardinality <= 1.0 {\n        index_leaf_pages_count\n    } else {\n        // Range scan in a nested-loop join: after the first iteration the inner\n        // table's pages are largely in the buffer pool.  Apply the same caching\n        // discount as full scans.\n        index_leaf_pages_count\n            + (input_cardinality - 1.0) * index_leaf_pages_count * params.cache_reuse_factor\n    };\n\n    // For non-covering indexes, we need to fetch from the table for each row.\n    let table_lookup_cost = if index_info.covering {\n        0.0\n    } else {\n        let table_pages_count = (base_row_count / params.rows_per_table_page).max(1.0);\n        let selectivity = rows_per_seek / base_row_count.max(1.0);\n        input_cardinality * selectivity * table_pages_count\n    };\n\n    let io_cost = seek_cost + leaf_scan_cost + table_lookup_cost;\n\n    // CPU cost: key comparisons during seeks + row processing\n    let total_rows = input_cardinality * rows_per_seek;\n    let cpu_cost =\n        input_cardinality * params.cpu_cost_per_seek + total_rows * params.cpu_cost_per_row;\n\n    Cost((io_cost + cpu_cost - params.index_bonus).max(0.001))\n}\n\npub(crate) fn is_unique_point_lookup(\n    index_info: IndexInfo,\n    usable_constraint_refs: &[RangeConstraintRef],\n) -> bool {\n    let eq_count = usable_constraint_refs\n        .iter()\n        .take_while(|cref| cref.eq.is_some())\n        .count();\n    index_info.unique && eq_count >= index_info.column_count\n}\n\n#[derive(Debug, Clone, Copy, PartialEq)]\npub enum RowCountEstimate {\n    HardcodedFallback(f64),\n    AnalyzeStats(f64),\n}\n\nimpl RowCountEstimate {\n    /// Create a hardcoded fallback using the given params.\n    pub fn hardcoded_fallback(params: &CostModelParams) -> Self {\n        RowCountEstimate::HardcodedFallback(params.rows_per_table_fallback)\n    }\n}\n\nimpl std::ops::Deref for RowCountEstimate {\n    type Target = f64;\n\n    fn deref(&self) -> &f64 {\n        match self {\n            RowCountEstimate::HardcodedFallback(val) => val,\n            RowCountEstimate::AnalyzeStats(val) => val,\n        }\n    }\n}\n\n/// ANALYZE-based context for cost estimation.\n/// Uses sqlite_stat1 histogram data for row estimates when available,\n/// otherwise falls through to heuristic selectivity multipliers.\npub struct AnalyzeCtx<'a> {\n    pub rhs_table: &'a JoinedTable,\n    pub index: Option<&'a Arc<Index>>,\n    pub stats: &'a AnalyzeStats,\n}\n\npub(crate) fn estimate_rows_per_seek(\n    index_info: IndexInfo,\n    constraints: &[Constraint],\n    usable_constraint_refs: &[RangeConstraintRef],\n    base_row_count: RowCountEstimate,\n    analyze_ctx: Option<&AnalyzeCtx>,\n) -> f64 {\n    if is_unique_point_lookup(index_info, usable_constraint_refs) {\n        return 1.0;\n    }\n\n    if let Some(ctx) = analyze_ctx {\n        if !usable_constraint_refs.is_empty() {\n            if let Some(eq_prefix_rows) =\n                estimate_rows_from_analyze_stats(ctx, usable_constraint_refs)\n            {\n                // Apply range selectivity for any trailing non-equality constraints\n                // beyond the equality prefix. SQLite does this via whereRangeAdjust\n                // which divides by ~4 per range bound.\n                let eq_prefix_len = usable_constraint_refs\n                    .iter()\n                    .take_while(|cref| cref.eq.is_some())\n                    .count();\n                let range_selectivity: f64 = usable_constraint_refs[eq_prefix_len..]\n                    .iter()\n                    .map(|cref| {\n                        let mut sel = 1.0;\n                        if let Some(lb) = cref.lower_bound {\n                            sel *= constraints[lb].selectivity;\n                        }\n                        if let Some(ub) = cref.upper_bound {\n                            sel *= constraints[ub].selectivity;\n                        }\n                        sel\n                    })\n                    .product();\n                return (eq_prefix_rows * range_selectivity).max(1.0);\n            }\n        }\n    }\n\n    let selectivity_multiplier: f64 = usable_constraint_refs\n        .iter()\n        .map(|cref| {\n            if let Some(ref eq) = cref.eq {\n                return constraints[eq.constraint_pos].selectivity;\n            }\n            let mut selectivity = 1.0;\n            if let Some(lower_bound) = cref.lower_bound {\n                selectivity *= constraints[lower_bound].selectivity;\n            }\n            if let Some(upper_bound) = cref.upper_bound {\n                selectivity *= constraints[upper_bound].selectivity;\n            }\n            selectivity\n        })\n        .product();\n\n    (selectivity_multiplier * *base_row_count).max(1.0)\n}\n\n/// Estimate rows per seek using ANALYZE stats (sqlite_stat1 histogram data).\n/// Returns `None` when no actual stats exist for this index, signaling the\n/// caller to fall back to selectivity-based estimation.\nfn estimate_rows_from_analyze_stats(\n    ctx: &AnalyzeCtx,\n    constraint_refs: &[RangeConstraintRef],\n) -> Option<f64> {\n    let index = ctx.index?;\n\n    // Only count leading equality-constrained columns. ANALYZE stats give\n    // avg rows per distinct prefix value, which is only meaningful for\n    // equality lookups. A range constraint (>, <, >=, <=) scans a portion\n    // of the index rather than fixing a prefix value, so it should not\n    // consume a prefix position in the stats lookup.\n    let eq_prefix_len = constraint_refs\n        .iter()\n        .take_while(|cref| cref.eq.is_some())\n        .count();\n\n    if eq_prefix_len == 0 {\n        // Pure range scan — ANALYZE per-distinct-value stats don't apply.\n        return None;\n    }\n\n    let table_name = ctx.rhs_table.table.get_name();\n\n    let table_stats = ctx.stats.table_stats(table_name)?;\n    let idx_stats = table_stats.index_stats.get(&index.name)?;\n\n    if eq_prefix_len <= idx_stats.avg_rows_per_distinct_prefix.len() {\n        Some(idx_stats.avg_rows_per_distinct_prefix[eq_prefix_len - 1] as f64)\n    } else {\n        // Stats exist for this index but don't cover the equality prefix length.\n        // Return None to fall through to selectivity-based estimation.\n        None\n    }\n}\n\n/// Estimate the cost of a scan or seek operation.\n#[expect(clippy::too_many_arguments)]\npub fn estimate_cost_for_scan_or_seek(\n    index_info: Option<IndexInfo>,\n    constraints: &[Constraint],\n    usable_constraint_refs: &[RangeConstraintRef],\n    input_cardinality: f64,\n    base_row_count: RowCountEstimate,\n    is_index_ordered: bool,\n    params: &CostModelParams,\n    analyze_ctx: Option<&AnalyzeCtx>,\n) -> Cost {\n    let base_row_count = *base_row_count;\n\n    let tree_depth = if base_row_count <= 1.0 {\n        1.0\n    } else {\n        (base_row_count.ln() / params.rows_per_table_page.ln())\n            .ceil()\n            .max(1.0)\n    };\n\n    let Some(index_info) = index_info else {\n        // Full table scan (no index)\n        return estimate_scan_cost(base_row_count, input_cardinality, params);\n    };\n\n    if is_unique_point_lookup(index_info, usable_constraint_refs) {\n        // Unique point lookup: 1 seek per input row, 1 row returned per seek\n        return estimate_index_cost(\n            base_row_count,\n            tree_depth,\n            index_info,\n            input_cardinality, // num_seeks = outer cardinality\n            1.0,               // rows_per_seek = 1 for unique point lookup\n            params,\n        );\n    }\n\n    let rows_per_seek = estimate_rows_per_seek(\n        index_info,\n        constraints,\n        usable_constraint_refs,\n        RowCountEstimate::AnalyzeStats(base_row_count),\n        analyze_ctx,\n    );\n\n    let base_cost = estimate_index_cost(\n        base_row_count,\n        tree_depth,\n        index_info,\n        input_cardinality, // num_seeks = outer cardinality\n        rows_per_seek,\n        params,\n    );\n\n    let is_full_scan = usable_constraint_refs.is_empty();\n    // Penalize non-covering indexes doing full scans when not ordered by the index.\n    // Without ordering benefit, a full scan on a non-covering index requires random\n    // table lookups for each row, which is expensive.\n    if !index_info.covering && is_full_scan && !is_index_ordered {\n        // Full index scan without ordering benefit - prefer table scan instead\n        Cost(base_cost.0 * 2.0)\n    } else {\n        base_cost\n    }\n}\n"
  },
  {
    "path": "core/translate/optimizer/cost_params.rs",
    "content": "/// Cost model parameters for query optimization.\n///\n/// These parameters control the heuristics used by the query optimizer for\n/// cost estimation. They can be tuned to improve plan selection for specific\n/// workloads (e.g., TPC-H).\n///\n/// # JSON Loading (requires `optimizer_params` feature)\n///\n/// When the `optimizer_params` feature is enabled, parameters can be loaded\n/// from a JSON file via the `TURSO_OPTIMIZER_PARAMS` environment variable.\n/// The JSON file does not need to specify all fields, and unspecified fields will use the default values.\n#[derive(Debug, Clone)]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"serde\", serde(default))]\npub struct CostModelParams {\n    // === Cardinality Fallbacks (when no ANALYZE stats) ===\n    /// Assumed rows per table when statistics unavailable.\n    pub rows_per_table_fallback: f64,\n\n    /// Estimated rows per table B-tree page.\n    pub rows_per_table_page: f64,\n\n    // === Selectivity Fallbacks ===\n    /// Selectivity for equality predicate on unindexed column (e.g., `x = 5`).\n    pub sel_eq_unindexed: f64,\n\n    /// Selectivity for equality predicate on indexed column.\n    /// Should be <= sel_eq_unindexed since indexes imply higher selectivity.\n    pub sel_eq_indexed: f64,\n\n    /// Selectivity for range predicates (>, >=, <, <=).\n    pub sel_range: f64,\n\n    /// Selectivity for IS NULL predicate.\n    pub sel_is_null: f64,\n\n    /// Selectivity for IS NOT NULL predicate.\n    pub sel_is_not_null: f64,\n\n    /// Selectivity for LIKE predicate.\n    pub sel_like: f64,\n\n    /// Selectivity for NOT LIKE predicate.\n    pub sel_not_like: f64,\n\n    /// Selectivity for other/unknown predicates.\n    pub sel_other: f64,\n\n    /// Estimated rows from IN subquery when actual count unknown.\n    /// Matches SQLite's estimate (where.c line 3230).\n    pub in_subquery_rows: f64,\n\n    // === Scan/Seek Cost Weights ===\n    /// Discount factor for repeated scans (cache benefit).\n    /// Range: [0, 1). Higher = more cache benefit assumed.\n    pub cache_reuse_factor: f64,\n\n    /// CPU cost per row processed (relative to page IO = 1.0).\n    pub cpu_cost_per_row: f64,\n\n    /// CPU cost per index seek (key comparisons).\n    pub cpu_cost_per_seek: f64,\n\n    /// Bonus subtracted from cost when using an index (encourages index usage).\n    pub index_bonus: f64,\n\n    // === Sort Cost ===\n    /// CPU cost per row for sorting (used in O(n log n) estimate).\n    /// This is used when estimating the cost saved by using an ordered index.\n    pub sort_cpu_per_row: f64,\n\n    // === Hash Join Cost ===\n    /// CPU cost to compute hash of a row.\n    pub hash_cpu_cost: f64,\n\n    /// CPU cost to insert row into hash table.\n    pub hash_insert_cost: f64,\n\n    /// CPU cost to probe hash table.\n    pub hash_lookup_cost: f64,\n\n    /// Estimated bytes per row for hash table spill estimation.\n    pub hash_bytes_per_row: f64,\n\n    /// Selectivity threshold for hash join build-side materialization.\n    /// Below this threshold, materialization may be beneficial.\n    pub hash_materialize_selectivity_threshold: f64,\n\n    /// Stricter selectivity threshold for nested hash probe operations.\n    pub hash_nested_probe_selectivity_threshold: f64,\n\n    // === Join Optimization ===\n    /// Selectivity heuristic factor for closed ranges (e.g., `x > 5 AND x < 10`).\n    /// Applied when both lower and upper bounds exist on an index column.\n    pub closed_range_selectivity_factor: f64,\n}\n\nimpl CostModelParams {\n    /// Create default parameters as a const fn (for compile-time static).\n    pub const fn new() -> Self {\n        Self {\n            // Cardinality fallbacks\n            rows_per_table_fallback: 1_000_000.0,\n            rows_per_table_page: 50.0,\n\n            // Selectivity fallbacks\n            sel_eq_unindexed: 0.1,\n            sel_eq_indexed: 0.001,\n            sel_range: 0.4,\n            sel_is_null: 0.1,\n            sel_is_not_null: 0.9,\n            sel_like: 0.2,\n            sel_not_like: 0.2,\n            sel_other: 0.9,\n            in_subquery_rows: 25.0,\n\n            // Scan/Seek costs\n            cache_reuse_factor: 0.2,\n            cpu_cost_per_row: 0.003,\n            cpu_cost_per_seek: 0.01,\n            index_bonus: 0.5,\n\n            // Sort costs\n            sort_cpu_per_row: 0.002,\n\n            // Hash join specific costs and thresholds\n            hash_cpu_cost: 0.001,\n            hash_insert_cost: 0.002,\n            hash_lookup_cost: 0.003,\n            hash_bytes_per_row: 100.0,\n            hash_materialize_selectivity_threshold: 0.5,\n            hash_nested_probe_selectivity_threshold: 0.15,\n\n            // Join optimization\n            closed_range_selectivity_factor: 0.2,\n        }\n    }\n}\n\n/// Compile-time static default parameters (zero runtime overhead).\n#[cfg(any(\n    not(feature = \"optimizer_params\"),\n    all(test, feature = \"optimizer_params\")\n))]\npub static DEFAULT_PARAMS: CostModelParams = CostModelParams::new();\n\nimpl Default for CostModelParams {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl CostModelParams {\n    /// Load parameters from a JSON file.\n    ///\n    /// Returns default parameters if the file cannot be read, parsed, or validated.\n    #[cfg(feature = \"optimizer_params\")]\n    pub fn load_from_file(path: &std::path::Path) -> Self {\n        match std::fs::read_to_string(path) {\n            Ok(contents) => match serde_json::from_str::<Self>(&contents) {\n                Ok(params) => {\n                    if let Err(e) = params.validate() {\n                        tracing::warn!(?path, error = %e, \"Invalid cost params, using defaults\");\n                        return Self::default();\n                    }\n                    tracing::info!(?path, \"Loaded optimizer cost parameters from file\");\n                    params\n                }\n                Err(e) => {\n                    tracing::warn!(?path, error = %e, \"Failed to parse cost params JSON, using defaults\");\n                    Self::default()\n                }\n            },\n            Err(e) => {\n                tracing::warn!(?path, error = %e, \"Failed to read cost params file, using defaults\");\n                Self::default()\n            }\n        }\n    }\n\n    /// Load parameters from the `TURSO_OPTIMIZER_PARAMS` environment variable path,\n    /// or return defaults if not set or loading fails.\n    #[cfg(feature = \"optimizer_params\")]\n    fn from_env_or_default() -> Self {\n        match std::env::var(\"TURSO_OPTIMIZER_PARAMS\") {\n            Ok(path) => Self::load_from_file(std::path::Path::new(&path)),\n            Err(_) => Self::default(),\n        }\n    }\n}\n\n/// Lazily-loaded parameters from `TURSO_OPTIMIZER_PARAMS` env var (cached process-wide).\n/// Falls back to defaults if env var not set or loading fails.\n#[cfg(feature = \"optimizer_params\")]\npub static LOADED_PARAMS: std::sync::LazyLock<CostModelParams> =\n    std::sync::LazyLock::new(CostModelParams::from_env_or_default);\n\n#[cfg(feature = \"optimizer_params\")]\nimpl CostModelParams {\n    /// Validate that parameters are within sensible bounds.\n    ///\n    /// Returns an error message if any parameter is invalid.\n    #[cfg(feature = \"optimizer_params\")]\n    pub fn validate(&self) -> Result<(), String> {\n        // Selectivity must be in (0, 1]\n        let selectivity_params = [\n            (\"sel_eq_unindexed\", self.sel_eq_unindexed),\n            (\"sel_eq_indexed\", self.sel_eq_indexed),\n            (\"sel_range\", self.sel_range),\n            (\"sel_is_null\", self.sel_is_null),\n            (\"sel_is_not_null\", self.sel_is_not_null),\n            (\"sel_like\", self.sel_like),\n            (\"sel_not_like\", self.sel_not_like),\n            (\"sel_other\", self.sel_other),\n        ];\n\n        for (name, val) in selectivity_params {\n            if val <= 0.0 || val > 1.0 {\n                return Err(format!(\"{name} must be in (0, 1], got {val}\"));\n            }\n        }\n\n        // Indexed selectivity should be <= unindexed (indexes are more selective)\n        if self.sel_eq_indexed > self.sel_eq_unindexed {\n            return Err(format!(\n                \"sel_eq_indexed ({}) should be <= sel_eq_unindexed ({})\",\n                self.sel_eq_indexed, self.sel_eq_unindexed\n            ));\n        }\n\n        // Positive value checks\n        if self.rows_per_table_fallback <= 0.0 {\n            return Err(\"rows_per_table_fallback must be positive\".into());\n        }\n        if self.rows_per_table_page <= 0.0 {\n            return Err(\"rows_per_table_page must be positive\".into());\n        }\n        if self.in_subquery_rows <= 0.0 {\n            return Err(\"in_subquery_rows must be positive\".into());\n        }\n\n        // Cache reuse factor must be in [0, 1)\n        if self.cache_reuse_factor < 0.0 || self.cache_reuse_factor >= 1.0 {\n            return Err(format!(\n                \"cache_reuse_factor must be in [0, 1), got {}\",\n                self.cache_reuse_factor\n            ));\n        }\n\n        // Cost multipliers must be non-negative\n        let cost_params = [\n            (\"cpu_cost_per_row\", self.cpu_cost_per_row),\n            (\"cpu_cost_per_seek\", self.cpu_cost_per_seek),\n            (\"sort_cpu_per_row\", self.sort_cpu_per_row),\n            (\"hash_cpu_cost\", self.hash_cpu_cost),\n            (\"hash_insert_cost\", self.hash_insert_cost),\n            (\"hash_lookup_cost\", self.hash_lookup_cost),\n        ];\n\n        for (name, val) in cost_params {\n            if val < 0.0 {\n                return Err(format!(\"{name} must be non-negative, got {val}\"));\n            }\n        }\n\n        Ok(())\n    }\n}\n\n#[cfg(all(test, feature = \"optimizer_params\"))]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_default_params_are_valid() {\n        let params = CostModelParams::default();\n        assert!(params.validate().is_ok());\n    }\n\n    #[test]\n    fn test_invalid_selectivity_rejected() {\n        let mut params = CostModelParams {\n            sel_eq_unindexed: 1.5,\n            ..Default::default()\n        };\n        assert!(params.validate().is_err());\n\n        params = CostModelParams {\n            sel_range: 0.0,\n            ..Default::default()\n        };\n        assert!(params.validate().is_err());\n\n        params = CostModelParams {\n            sel_is_null: -0.1,\n            ..Default::default()\n        };\n        assert!(params.validate().is_err());\n    }\n\n    #[test]\n    fn test_indexed_selectivity_constraint() {\n        let params = CostModelParams {\n            sel_eq_indexed: 0.5,\n            sel_eq_unindexed: 0.1,\n            ..Default::default()\n        };\n        assert!(params.validate().is_err());\n    }\n\n    #[test]\n    fn test_cache_reuse_bounds() {\n        let mut params = CostModelParams {\n            cache_reuse_factor: 1.0,\n            ..Default::default()\n        };\n        assert!(params.validate().is_err());\n\n        params.cache_reuse_factor = -0.1;\n        assert!(params.validate().is_err());\n\n        params.cache_reuse_factor = 0.99;\n        assert!(params.validate().is_ok());\n    }\n\n    #[cfg(feature = \"serde\")]\n    #[test]\n    fn test_serde_roundtrip() {\n        let params = CostModelParams::default();\n        let json = serde_json::to_string(&params).unwrap();\n        let parsed: CostModelParams = serde_json::from_str(&json).unwrap();\n        assert!((params.sel_eq_unindexed - parsed.sel_eq_unindexed).abs() < f64::EPSILON);\n    }\n\n    #[cfg(feature = \"serde\")]\n    #[test]\n    fn test_partial_json_uses_defaults() {\n        let defaults = CostModelParams::new();\n        let json = r#\"{\"sel_eq_unindexed\": 0.05}\"#;\n        let params: CostModelParams = serde_json::from_str(json).unwrap();\n        assert!((params.sel_eq_unindexed - 0.05).abs() < f64::EPSILON);\n        // Other fields should be defaults\n        assert!((params.sel_range - defaults.sel_range).abs() < f64::EPSILON);\n    }\n\n    #[test]\n    fn test_load_from_file() {\n        let dir = std::env::temp_dir();\n        let path = dir.join(\"test_cost_params.json\");\n        let defaults = CostModelParams::new();\n\n        // Write a partial JSON file - unspecified fields should use defaults\n        let json = r#\"{\n            \"sel_eq_unindexed\": 0.15,\n            \"sel_eq_indexed\": 0.005,\n            \"rows_per_table_fallback\": 500000.0\n        }\"#;\n        std::fs::write(&path, json).unwrap();\n\n        let params = CostModelParams::load_from_file(&path);\n\n        // Specified values should be loaded\n        assert!((params.sel_eq_unindexed - 0.15).abs() < f64::EPSILON);\n        assert!((params.sel_eq_indexed - 0.005).abs() < f64::EPSILON);\n        assert!((params.rows_per_table_fallback - 500000.0).abs() < f64::EPSILON);\n\n        // Unspecified values should be defaults\n        assert!((params.sel_range - defaults.sel_range).abs() < f64::EPSILON);\n        assert!((params.rows_per_table_page - defaults.rows_per_table_page).abs() < f64::EPSILON);\n\n        std::fs::remove_file(&path).ok();\n    }\n\n    #[test]\n    fn test_load_from_file_invalid_json_returns_defaults() {\n        let dir = std::env::temp_dir();\n        let path = dir.join(\"test_invalid_cost_params.json\");\n        let defaults = CostModelParams::new();\n\n        std::fs::write(&path, \"not valid json {{{\").unwrap();\n\n        let params = CostModelParams::load_from_file(&path);\n\n        // Should return defaults on parse error\n        assert!((params.sel_eq_unindexed - defaults.sel_eq_unindexed).abs() < f64::EPSILON);\n        assert!(\n            (params.rows_per_table_fallback - defaults.rows_per_table_fallback).abs()\n                < f64::EPSILON\n        );\n\n        std::fs::remove_file(&path).ok();\n    }\n\n    #[test]\n    fn test_load_from_file_missing_returns_defaults() {\n        let path = std::path::Path::new(\"/nonexistent/path/to/params.json\");\n        let defaults = CostModelParams::new();\n\n        let params = CostModelParams::load_from_file(path);\n\n        // Should return defaults when file doesn't exist\n        assert!((params.sel_eq_unindexed - defaults.sel_eq_unindexed).abs() < f64::EPSILON);\n        assert!(\n            (params.rows_per_table_fallback - defaults.rows_per_table_fallback).abs()\n                < f64::EPSILON\n        );\n    }\n}\n"
  },
  {
    "path": "core/translate/optimizer/join.rs",
    "content": "use std::collections::VecDeque;\nuse std::sync::Arc;\n\nuse crate::{turso_assert_eq, turso_assert_greater_than};\nuse rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};\n\nuse smallvec::SmallVec;\n\nuse turso_parser::ast::{Expr, Operator, TableInternalId};\n\nuse super::{\n    access_method::{find_best_access_method_for_join_order, AccessMethod},\n    constraints::TableConstraints,\n    cost_params::CostModelParams,\n    order::OrderTarget,\n    IndexMethodCandidate,\n};\nuse crate::{\n    schema::{Index, Schema},\n    stats::AnalyzeStats,\n    translate::{\n        expr::{walk_expr, WalkControl},\n        optimizer::{\n            access_method::{\n                estimate_hash_join_cost, try_hash_join_access_method, AccessMethodParams,\n                ResidualConstraintMode,\n            },\n            cost::{Cost, RowCountEstimate},\n            order::plan_satisfies_order_target,\n        },\n        plan::{\n            HashJoinKey, HashJoinType, JoinOrderMember, JoinedTable, NonFromClauseSubquery,\n            TableReferences, WhereTerm,\n        },\n        planner::TableMask,\n    },\n    LimboError, Result,\n};\n\n#[derive(Debug, Clone, Copy)]\n/// Small bag of planner context that needs to flow through join enumeration.\n///\n/// Keeping this as a struct avoids threading more ad-hoc parameters through the\n/// join planner as we add order-aware access path choices.\npub(crate) struct JoinPlanningContext<'a> {\n    pub maybe_order_target: Option<&'a OrderTarget>,\n}\n\nimpl<'a> JoinPlanningContext<'a> {\n    /// Convenience constructor used by the default planner entrypoints and tests.\n    #[cfg_attr(not(test), allow(dead_code))]\n    fn default_with_order_target(maybe_order_target: Option<&'a OrderTarget>) -> Self {\n        Self { maybe_order_target }\n    }\n}\n\n// Upper bound on rowids to materialize for a hash build input.\n// This is a safety limit, not a cost tuning parameter.\nconst MAX_MATERIALIZED_BUILD_ROWS: f64 = 200_000.0;\n\nfn constraint_output_multipliers(\n    rhs_constraints: &TableConstraints,\n    lhs_mask: &TableMask,\n    rhs_self_mask: TableMask,\n    consumed_where_terms: &[usize],\n    params: &CostModelParams,\n) -> f64 {\n    let mut multiplier = 1.0;\n    let mut bounds: SmallVec<[(Option<usize>, bool, bool); 4]> = SmallVec::new();\n\n    let record_bound = |bounds: &mut SmallVec<[(Option<usize>, bool, bool); 4]>,\n                        dominated_col: Option<usize>,\n                        is_lower: bool,\n                        is_upper: bool| {\n        if !(is_lower || is_upper) {\n            return;\n        }\n        if let Some(entry) = bounds.iter_mut().find(|(col, _, _)| *col == dominated_col) {\n            entry.1 |= is_lower;\n            entry.2 |= is_upper;\n        } else {\n            bounds.push((dominated_col, is_lower, is_upper));\n        }\n    };\n\n    for constraint in rhs_constraints.constraints.iter().filter(|constraint| {\n        (lhs_mask.contains_all(&constraint.lhs_mask)\n            || constraint.lhs_mask == rhs_self_mask\n            || constraint.lhs_mask.is_empty())\n            && !consumed_where_terms.contains(&constraint.where_clause_pos.0)\n    }) {\n        multiplier *= constraint.selectivity;\n\n        let dominated_col = constraint.table_col_pos;\n        let is_lower = matches!(\n            constraint.operator.as_ast_operator(),\n            Some(Operator::Greater | Operator::GreaterEquals)\n        );\n        let is_upper = matches!(\n            constraint.operator.as_ast_operator(),\n            Some(Operator::Less | Operator::LessEquals)\n        );\n        record_bound(&mut bounds, dominated_col, is_lower, is_upper);\n    }\n\n    for (_, has_lower, has_upper) in &bounds {\n        if *has_lower && *has_upper {\n            multiplier *= params.closed_range_selectivity_factor;\n        }\n    }\n\n    multiplier\n}\n\n/// Represents an n-ary join, anywhere from 1 table to N tables.\n#[derive(Debug, Clone)]\npub struct JoinN {\n    /// Tuple: (table_number, access_method_index)\n    pub data: Vec<(usize, usize)>,\n    /// The estimated number of rows returned by joining these n tables together.\n    pub output_cardinality: f64,\n    /// Estimated execution cost of this N-ary join.\n    pub cost: Cost,\n}\n\nimpl JoinN {\n    pub fn table_numbers(&self) -> impl Iterator<Item = usize> + use<'_> {\n        self.data.iter().map(|(table_number, _)| *table_number)\n    }\n\n    pub fn best_access_methods(&self) -> impl Iterator<Item = usize> + use<'_> {\n        self.data\n            .iter()\n            .map(|(_, access_method_index)| *access_method_index)\n    }\n}\n\n/// Join n-1 tables with the n'th table.\n/// Returns None if the plan is worse than the provided cost upper bound or if no valid access method is found.\n///\n/// Hash-joins:\n/// - We only consider hash join once there is a non-empty LHS.\n/// - The build side is the most recently joined table (left-deep hash join); the RHS is the probe.\n/// - We avoid hash-join shapes that would drop build-side filters unless we can preserve them\n///   via materialized build rowids.\n/// - Probe->build chaining is only allowed when the build input is materialized from the\n///   join prefix; rebuilding from the full table would ignore prior join filters.\n#[allow(clippy::too_many_arguments)]\npub fn join_lhs_and_rhs<'a>(\n    lhs: Option<&JoinN>,\n    initial_input_cardinality: f64,\n    rhs_table_reference: &JoinedTable,\n    rhs_constraints: &'a TableConstraints,\n    all_constraints: &'a [TableConstraints],\n    base_table_rows: &[RowCountEstimate],\n    join_order: &[JoinOrderMember],\n    planning_context: JoinPlanningContext<'_>,\n    access_methods_arena: &'a mut Vec<AccessMethod>,\n    cost_upper_bound: Cost,\n    joined_tables: &[JoinedTable],\n    where_clause: &mut [WhereTerm],\n    where_term_table_ids: &[HashSet<TableInternalId>],\n    subqueries: &[NonFromClauseSubquery],\n    index_method_candidates: &[IndexMethodCandidate],\n    params: &CostModelParams,\n    analyze_stats: &AnalyzeStats,\n    available_indexes: &HashMap<String, VecDeque<Arc<Index>>>,\n    table_references: &TableReferences,\n    schema: &Schema,\n) -> Result<Option<JoinN>> {\n    // The input cardinality for this join is the output cardinality of the previous join.\n    // For example, in a 2-way join, if the left table has 1000 rows, and the right table will return 2 rows for each of the left table's rows,\n    // then the output cardinality of the join will be 2000.\n    let input_cardinality = lhs.map_or(initial_input_cardinality, |l| l.output_cardinality);\n\n    let rhs_table_number = join_order.last().unwrap().original_idx;\n    let rhs_base_rows = base_table_rows\n        .get(rhs_table_number)\n        .copied()\n        .unwrap_or_else(|| RowCountEstimate::hardcoded_fallback(params));\n\n    let Some(mut method) = find_best_access_method_for_join_order(\n        rhs_table_reference,\n        rhs_constraints,\n        join_order,\n        planning_context,\n        where_clause,\n        available_indexes,\n        table_references,\n        subqueries,\n        schema,\n        analyze_stats,\n        input_cardinality,\n        rhs_base_rows,\n        params,\n    )?\n    else {\n        return Ok(None);\n    };\n\n    // Check if this access method will trigger ephemeral index creation.\n    if let AccessMethodParams::BTreeTable {\n        index: None,\n        constraint_refs,\n        ..\n    } = &method.params\n    {\n        if constraint_refs.is_empty() {\n            // Check if there are usable constraints that will create an ephemeral index\n            let lhs_mask_for_ephemeral = lhs.map_or_else(TableMask::new, |l| {\n                TableMask::from_table_number_iter(l.table_numbers())\n            });\n            let has_usable_constraints = rhs_constraints.constraints.iter().any(|c| {\n                c.usable\n                    && c.table_col_pos.is_some()\n                    && lhs_mask_for_ephemeral.contains_all(&c.lhs_mask)\n            });\n\n            if has_usable_constraints && lhs.is_some() {\n                // Add ephemeral index build cost: scan the table once to build the index\n                // This is similar to the build phase of a hash join\n                let ephemeral_build_cost = *rhs_base_rows * 0.003;\n                method.cost = method.cost + Cost(ephemeral_build_cost);\n            }\n        }\n    }\n\n    let lhs_cost = lhs.map_or(Cost(0.0), |l| l.cost);\n    // If we have a previous table, consider hash join as an alternative\n    let mut best_access_method = method;\n\n    // Reuse for hash cost and output cardinality computation\n    let lhs_mask = lhs.map_or_else(TableMask::new, |l| {\n        TableMask::from_table_number_iter(l.table_numbers())\n    });\n\n    // Self-constraints are conditions comparing columns within the same table\n    // (e.g., t.col1 < t.col2). Include them in selectivity since they filter rows.\n    let rhs_self_mask = {\n        let mut m = TableMask::new();\n        m.add_table(rhs_table_number);\n        m\n    };\n\n    let rhs_internal_id = rhs_table_reference.internal_id;\n    let lhs_internal_ids: HashSet<TableInternalId> = lhs\n        .map(|l| {\n            l.table_numbers()\n                .map(|table_no| joined_tables[table_no].internal_id)\n                .collect()\n        })\n        .unwrap_or_default();\n    let has_join_constraint = lhs.is_some()\n        && where_term_table_ids.iter().any(|table_ids| {\n            table_ids.contains(&rhs_internal_id)\n                && table_ids.iter().any(|id| lhs_internal_ids.contains(id))\n        });\n    if lhs.is_some() && !has_join_constraint {\n        let rhs_self_constraint_selectivity =\n            build_self_constraint_selectivity(rhs_constraints, rhs_table_number);\n        // Penalize cross products so we don't introduce a table before it can join.\n        let effective_rhs_rows = (*rhs_base_rows) * rhs_self_constraint_selectivity;\n        let cross_cost = (input_cardinality) * effective_rhs_rows;\n        best_access_method.cost = best_access_method.cost + Cost(cross_cost);\n    }\n\n    // If we already have a non-empty LHS (at least one table has been joined),\n    // consider a hash-join alternative for the current RHS. This is a left-deep\n    // join: the last table in the LHS becomes the build side, and the new RHS\n    // table is the probe side. We only allow hash joins when:\n    //\n    // - The would-be build table is accessed via a scan, or we can preserve its\n    //   filters by materializing rowids.\n    // - The probe table is not using a selective index seek we’d prefer to keep.\n    // - The build table has no remaining constraints from prior tables that are\n    //   not already consumed as hash-join keys in earlier hash joins.\n    if let Some(lhs) = lhs {\n        let rhs_table_idx = join_order.last().unwrap().original_idx;\n        let last_lhs_table_idx = join_order[join_order.len() - 2].original_idx;\n        let lhs_table_numbers: Vec<usize> = lhs.table_numbers().collect();\n\n        let rhs_has_selective_seek = matches!(\n            best_access_method.params,\n            AccessMethodParams::BTreeTable {\n                ref constraint_refs,\n                ..\n            } if !constraint_refs.is_empty()\n        );\n\n        // The probe table must NOT be the build table of any earlier hash join,\n        // otherwise we would need to re-probe a table that is already being\n        // produced by a hash build.\n        let arena = &access_methods_arena;\n        let probe_table_is_prior_build = lhs.data.iter().any(|(_, am_idx)| {\n            arena.get(*am_idx).is_some_and(|am| {\n                if let AccessMethodParams::HashJoin {\n                    build_table_idx, ..\n                } = &am.params\n                {\n                    *build_table_idx == rhs_table_idx\n                } else {\n                    false\n                }\n            })\n        });\n\n        for build_table_idx in lhs_table_numbers {\n            if build_table_idx != last_lhs_table_idx {\n                continue;\n            }\n            let build_table = &joined_tables[build_table_idx];\n            let build_has_rowid = build_table.btree().is_some_and(|btree| btree.has_rowid);\n\n            // If the chosen access method for the build table already uses constraints,\n            // skip hash join to avoid dropping those filters (unless we later decide\n            // to materialize the filtered rowids).\n            let build_access_method_uses_constraints = lhs\n                .data\n                .iter()\n                .find(|(table_no, _)| *table_no == build_table_idx)\n                .map(|(_, am_idx)| *am_idx)\n                .map(|am_idx| {\n                    let arena = &access_methods_arena;\n                    arena.get(am_idx).is_some_and(|am| {\n                        if let AccessMethodParams::BTreeTable {\n                            constraint_refs, ..\n                        } = &am.params\n                        {\n                            !constraint_refs.is_empty()\n                        } else {\n                            false\n                        }\n                    })\n                })\n                .unwrap_or(false);\n\n            let build_constraints = &all_constraints[build_table_idx];\n            let build_base_rows = base_table_rows\n                .get(build_table_idx)\n                .copied()\n                .unwrap_or_else(|| RowCountEstimate::hardcoded_fallback(params));\n            let build_self_selectivity =\n                build_self_constraint_selectivity(build_constraints, build_table_idx);\n            let build_cardinality = (*build_base_rows) * build_self_selectivity;\n            let probe_cardinality = *rhs_base_rows;\n            let prior_mask = lhs_mask.without_table(build_table_idx);\n            let prior_constraint_selectivity =\n                build_prior_constraint_selectivity(build_constraints, &prior_mask);\n            let probe_multiplier = if lhs.data.len() == 1 && lhs.data[0].0 == build_table_idx {\n                1.0\n            } else {\n                let join_selectivity = prior_constraint_selectivity.clamp(0.0, 1.0);\n                let denom = (build_cardinality * join_selectivity).max(1.0);\n                (input_cardinality / denom).max(1.0)\n            };\n\n            // The build table must NOT have any constraints from prior tables that won't be\n            // consumed as hash-join keys. When a table becomes a hash build table, its\n            // cursor is exhausted after building. If there are constraints like\n            // `prior.x = build.x` that aren't part of the build & probe hash join, they\n            // can't be evaluated because the build cursor is no longer positioned.\n            //\n            // HOWEVER: If the constraint references only tables that are the BUILD side\n            // of earlier hash joins where this proposed build table was the PROBE, then\n            // that equality is already used as a hash-join key. In that case, when we\n            // later SeekRowid into the build table for that earlier join, the cursor is\n            // correctly positioned and the constraint is effectively \"consumed\".\n            //\n            // Example:\n            // `SELECT... items JOIN products ON items.name = products.name JOIN order_items ON products.price = order_items.price`:\n            // - First hash join: items(build) - products(probe)\n            // - When considering: products(build) - order_items(probe)\n            // - products has constraint from items, BUT items is build of an earlier hash join where products was probe\n            // - So the constraint IS consumed, and products cursor IS positioned via SeekRowid\n            // Get the set of tables that are build tables for hash joins where build_table_idx was probe\n            let tables_already_hash_joined_as_build: Vec<usize> = {\n                let arena = &access_methods_arena;\n                lhs.data\n                    .iter()\n                    .filter_map(|(_, am_idx)| {\n                        arena.get(*am_idx).and_then(|am| {\n                            if let AccessMethodParams::HashJoin {\n                                build_table_idx: prior_build_table_idx,\n                                probe_table_idx,\n                                ..\n                            } = &am.params\n                            {\n                                if *probe_table_idx == build_table_idx {\n                                    Some(*prior_build_table_idx)\n                                } else {\n                                    None\n                                }\n                            } else {\n                                None\n                            }\n                        })\n                    })\n                    .collect()\n            };\n            let prior_hash_build_mask = TableMask::from_table_number_iter(\n                tables_already_hash_joined_as_build.iter().copied(),\n            );\n\n            let build_has_prior_constraints = {\n                build_constraints.constraints.iter().any(|c| {\n                    // Check if this constraint references prior tables that are NOT already\n                    // handled by a hash join where we were the probe table\n                    if !c.lhs_mask.intersects(&prior_mask) {\n                        return false; // Constraint doesn't reference prior tables\n                    }\n                    // Check if ALL referenced prior tables are already hash-joined with us as probe\n                    // If so, the constraint is consumed and we're OK\n                    for table_idx in 0..64 {\n                        if c.lhs_mask.contains_table(table_idx)\n                            && prior_mask.contains_table(table_idx)\n                            && !tables_already_hash_joined_as_build.contains(&table_idx)\n                        {\n                            // This prior table is NOT handled by a hash join, constraint not consumed\n                            return true;\n                        }\n                    }\n                    false // All referenced prior tables are hash-joined\n                })\n            };\n\n            // If this build table was already a probe in a prior hash join, scanning it again\n            // for a hash build would ignore the prior join filters. The planner disallows\n            // probe-to-build chaining, even with materialization.\n            let build_table_is_prior_probe = lhs.data.iter().any(|(_, am_idx)| {\n                let arena = &access_methods_arena;\n                arena.get(*am_idx).is_some_and(|am| {\n                    if let AccessMethodParams::HashJoin {\n                        probe_table_idx, ..\n                    } = &am.params\n                    {\n                        *probe_table_idx == build_table_idx\n                    } else {\n                        false\n                    }\n                })\n            });\n            // Avoid probe->build chaining across outer-join boundaries.\n            let prefix_has_outer = join_order\n                .iter()\n                .take(join_order.len().saturating_sub(1))\n                .any(|member| member.is_outer);\n            let chaining_across_outer = build_table_is_prior_probe && prefix_has_outer;\n\n            // Hash joins are safe only if we won't drop build-side filters:\n            // - If the build scan is unconstrained, we can build directly.\n            // - If there are prior/self constraints, we need materialization to preserve them.\n            // Full index scans are treated as unconstrained for hash-join eligibility.\n            //\n            // We intentionally do NOT (yet) allow a table that is already the probe side of\n            // a hash join to become the build side of another hash join; the second hash join\n            // would rebuild from ALL rows of the middle table, not just the matching rows from the first.\n            let build_am_is_plain_table_scan = lhs\n                .data\n                .iter()\n                .find(|(table_no, _)| *table_no == build_table_idx)\n                .map(|(_, am_idx)| {\n                    let arena = &access_methods_arena;\n                    arena.get(*am_idx).is_some_and(|am| {\n                        matches!(\n                            &am.params,\n                            AccessMethodParams::BTreeTable {\n                                constraint_refs,\n                                ..\n                            } if constraint_refs.is_empty()\n                        )\n                    })\n                })\n                .unwrap_or(false);\n\n            let build_table_is_last = build_table_idx == last_lhs_table_idx;\n\n            // Eligibility gate: prefer nested-loop when uses a selective probe seek.\n            // Probe->build chaining is only allowed when the\n            // build input is materialized from the join prefix.\n            let allow_hash_join = !rhs_has_selective_seek\n                && !probe_table_is_prior_build\n                && (!build_has_prior_constraints || build_has_rowid)\n                && !chaining_across_outer;\n\n            tracing::debug!(\n                lhs_table = build_table.table.get_name(),\n                rhs_table = rhs_table_reference.table.get_name(),\n                allow_hash_join,\n                rhs_has_selective_seek,\n                probe_table_is_prior_build,\n                build_table_is_prior_probe,\n                chaining_across_outer,\n                build_am_is_plain_table_scan,\n                build_has_rowid,\n                \"hash-join eligibility check\"\n            );\n            if allow_hash_join {\n                let lhs_constraints = build_constraints;\n                if let Some(hash_join_method) = try_hash_join_access_method(\n                    build_table,\n                    rhs_table_reference,\n                    build_table_idx,\n                    rhs_table_idx,\n                    lhs_constraints,\n                    rhs_constraints,\n                    where_clause,\n                    build_cardinality,\n                    probe_cardinality,\n                    probe_multiplier,\n                    subqueries,\n                    params,\n                ) {\n                    let mut hash_join_method = hash_join_method;\n                    let mut hash_join_allowed = true;\n                    let mem_budget = match &hash_join_method.params {\n                        AccessMethodParams::HashJoin { mem_budget, .. } => *mem_budget,\n                        _ => unreachable!(\"hash join params expected\"),\n                    };\n                    if let AccessMethodParams::HashJoin {\n                        materialize_build_input,\n                        use_bloom_filter,\n                        join_keys,\n                        ..\n                    } = &mut hash_join_method.params\n                    {\n                        let needs_materialization = build_has_uncovered_prior_constraints(\n                            lhs_constraints,\n                            join_keys,\n                            &prior_mask,\n                            &prior_hash_build_mask,\n                        ) || build_table_is_prior_probe\n                            || !build_table_is_last;\n                        let estimated_filtered_rows = (*build_base_rows)\n                            * build_self_selectivity\n                            * prior_constraint_selectivity;\n\n                        // Hard cap: avoid materializing huge lists when materialization is required.\n                        let materialization_too_large = needs_materialization\n                            && estimated_filtered_rows > MAX_MATERIALIZED_BUILD_ROWS;\n                        let can_materialize =\n                            build_has_indexable_prior_constraints(lhs_constraints, &prior_mask);\n                        let selectivity_threshold = if probe_multiplier > 1.0 {\n                            params.hash_nested_probe_selectivity_threshold\n                        } else {\n                            params.hash_materialize_selectivity_threshold\n                        };\n                        // When probe is nested under prior loops, require stricter selectivity\n                        // to justify materialization.\n                        let wants_materialization = needs_materialization\n                            || (build_access_method_uses_constraints\n                                && prior_constraint_selectivity < selectivity_threshold);\n\n                        let optional_materialization_too_large = !needs_materialization\n                            && wants_materialization\n                            && estimated_filtered_rows > MAX_MATERIALIZED_BUILD_ROWS;\n\n                        // Build eligibility: a plain scan is always safe; otherwise we need\n                        // materialization or existing constraints that make the scan selective.\n                        let build_is_eligible = build_am_is_plain_table_scan\n                            || needs_materialization\n                            || build_access_method_uses_constraints;\n\n                        hash_join_allowed = build_is_eligible\n                            && (!needs_materialization || build_has_rowid)\n                            && !materialization_too_large;\n\n                        if hash_join_allowed {\n                            let should_materialize = if needs_materialization {\n                                build_has_rowid\n                            } else {\n                                wants_materialization\n                                    && build_has_rowid\n                                    && can_materialize\n                                    && !optional_materialization_too_large\n                            };\n                            let hash_probe_multiplier = if should_materialize {\n                                1.0\n                            } else {\n                                probe_multiplier\n                            };\n                            let effective_build_cardinality = if should_materialize {\n                                estimated_filtered_rows\n                            } else {\n                                build_cardinality\n                            };\n                            // Estimate probe filters that apply only to the probe table itself\n                            // (not join predicates) to inform the bloom filter heuristic.\n                            let probe_self_selectivity = rhs_constraints\n                                .constraints\n                                .iter()\n                                .filter(|c| c.lhs_mask.is_empty())\n                                .map(|c| c.selectivity)\n                                .product::<f64>();\n                            let probe_filtered_rows =\n                                (*rhs_base_rows) * probe_self_selectivity.clamp(0.0, 1.0);\n                            if probe_filtered_rows > 0.0 {\n                                let build_filtered_rows = if build_is_eligible {\n                                    effective_build_cardinality\n                                } else {\n                                    build_cardinality\n                                };\n                                // Bloom filters help when the probe side is much larger than the build.\n                                *use_bloom_filter = build_filtered_rows > 0.0\n                                    && probe_filtered_rows / build_filtered_rows >= 2.0;\n                            } else {\n                                *use_bloom_filter = false;\n                            }\n                            if should_materialize {\n                                hash_join_method.cost = estimate_hash_join_cost(\n                                    effective_build_cardinality,\n                                    probe_cardinality,\n                                    mem_budget,\n                                    hash_probe_multiplier,\n                                    params,\n                                );\n                            }\n                            if should_materialize {\n                                // Materialize build-side rowids so the hash build only includes\n                                // rows that already match prior join constraints.\n                                *materialize_build_input = true;\n                                let materialize_cost = effective_build_cardinality * 0.003;\n                                hash_join_method.cost =\n                                    hash_join_method.cost + Cost(materialize_cost);\n                                // When two materialized hash-join plans have equal cost,\n                                // prefer the one that filters by earlier prior tables.\n                                //\n                                // This is a deterministic tie-breaker that nudges the planner\n                                // toward chaining a more selective prefix without changing\n                                // the primary cost model.\n                                let tie_breaker =\n                                    prior_mask.tables_iter().min().unwrap_or(0) as f64 * 1.0e-6;\n                                hash_join_method.cost = hash_join_method.cost + Cost(tie_breaker);\n                            } else {\n                                *materialize_build_input = false;\n                            }\n                            tracing::debug!(\n                                lhs_table = build_table.table.get_name(),\n                                rhs_table = rhs_table_reference.table.get_name(),\n                                materialize_build_input = *materialize_build_input,\n                                needs_materialization,\n                                estimated_filtered_rows,\n                                prior_constraint_selectivity,\n                                materialization_too_large,\n                                can_materialize,\n                                build_cardinality,\n                                effective_build_cardinality,\n                                probe_cardinality,\n                                probe_multiplier,\n                                hash_probe_multiplier,\n                                prior_mask = ?prior_mask,\n                                lhs_mask = ?lhs_mask,\n                                hash_join_cost = ?hash_join_method.cost,\n                                \"hash-join candidate\"\n                            );\n                        }\n                    }\n                    // FULL OUTER requires hash join for the unmatched-build scan.\n                    let is_full_outer = matches!(\n                        &hash_join_method.params,\n                        AccessMethodParams::HashJoin {\n                            join_type: HashJoinType::FullOuter,\n                            ..\n                        }\n                    );\n                    if hash_join_allowed\n                        && (is_full_outer || hash_join_method.cost < best_access_method.cost)\n                    {\n                        best_access_method = hash_join_method;\n                    }\n                }\n            }\n        }\n    }\n\n    // Check if there's an index method candidate for this table (e.g., FTS)\n    // and compare its cost against the current best access method.\n    if let Some(candidate) = index_method_candidates\n        .iter()\n        .find(|c| c.table_idx == rhs_table_number)\n    {\n        if let Some(cost_estimate) = &candidate.cost_estimate {\n            // FTS cost depends on whether it's the outer table (no LHS) or inner table\n            let fts_cost = if lhs.is_none() {\n                // Outer table: FTS cost is fixed\n                Cost(cost_estimate.estimated_cost)\n            } else {\n                // Inner table: FTS cost is multiplied by input cardinality\n                Cost(cost_estimate.estimated_cost * input_cardinality)\n            };\n\n            if fts_cost < best_access_method.cost {\n                best_access_method = AccessMethod {\n                    cost: fts_cost,\n                    estimated_rows_per_outer_row: cost_estimate.estimated_rows as f64,\n                    residual_constraints: ResidualConstraintMode::None,\n                    consumed_where_terms: candidate.where_covered.into_iter().collect(),\n                    params: AccessMethodParams::IndexMethod {\n                        query: candidate.to_query(),\n                        where_covered: candidate.where_covered,\n                    },\n                };\n            }\n        }\n    }\n\n    // FULL OUTER needs a hash join. If the optimizer couldn't pick one, bail.\n    if lhs.is_some() {\n        let is_full_outer = rhs_table_reference\n            .join_info\n            .as_ref()\n            .is_some_and(|ji| ji.is_full_outer());\n        if is_full_outer\n            && !matches!(\n                best_access_method.params,\n                AccessMethodParams::HashJoin {\n                    join_type: HashJoinType::FullOuter,\n                    ..\n                }\n            )\n        {\n            // This ordering can't satisfy FULL OUTER. Let the planner try others.\n            return Ok(None);\n        }\n    }\n\n    let cost = lhs_cost + best_access_method.cost;\n\n    if cost > cost_upper_bound {\n        return Ok(None);\n    }\n    // ============================================================================\n    // OUTPUT CARDINALITY CALCULATION\n    // ============================================================================\n    //\n    // Formula: output_rows = input_rows × rows_from_access_path × remaining_filter_selectivity.\n    //\n    // Each access method provides its own per-outer-row row estimate for:\n    // - full BTree scans and full index scans\n    // - rowid seeks\n    // - ordinary secondary-index seeks\n    // - multi-index OR/AND scans\n    // - index-method access such as FTS\n    //\n    // Join planning only applies the selectivity of WHERE terms that the chosen\n    // access path did not already consume.\n    //\n    let unconsumed_constraint_multiplier = constraint_output_multipliers(\n        rhs_constraints,\n        &lhs_mask,\n        rhs_self_mask,\n        &best_access_method.consumed_where_terms,\n        params,\n    );\n    let residual_multiplier = match best_access_method.residual_constraints {\n        ResidualConstraintMode::ApplyUnconsumed => unconsumed_constraint_multiplier,\n        ResidualConstraintMode::None => 1.0,\n    };\n    let output_cardinality =\n        input_cardinality * best_access_method.estimated_rows_per_outer_row * residual_multiplier;\n\n    access_methods_arena.push(best_access_method);\n\n    let mut best_access_methods = Vec::with_capacity(join_order.len());\n    best_access_methods.extend(lhs.map_or(vec![], |l| l.data.clone()));\n    best_access_methods.push((rhs_table_number, access_methods_arena.len() - 1));\n\n    Ok(Some(JoinN {\n        data: best_access_methods,\n        output_cardinality,\n        cost,\n    }))\n}\n\n/// Returns true when build-side constraints reference prior tables in ways that\n/// are not already consumed by hash-join keys.\nfn build_has_uncovered_prior_constraints(\n    build_constraints: &TableConstraints,\n    join_keys: &[HashJoinKey],\n    prior_mask: &TableMask,\n    prior_hash_build_mask: &TableMask,\n) -> bool {\n    let mut join_key_indices = HashSet::default();\n    for join_key in join_keys {\n        join_key_indices.insert(join_key.where_clause_idx);\n    }\n\n    build_constraints.constraints.iter().any(|constraint| {\n        if !constraint.lhs_mask.intersects(prior_mask) {\n            return false;\n        }\n        if join_key_indices.contains(&constraint.where_clause_pos.0) {\n            return false;\n        }\n        if constraint.operator != Operator::Equals.into() {\n            return true;\n        }\n        if !constraint.lhs_mask.intersects(prior_hash_build_mask) {\n            return true;\n        }\n        for table_idx in prior_mask.tables_iter() {\n            if constraint.lhs_mask.contains_table(table_idx)\n                && !prior_hash_build_mask.contains_table(table_idx)\n            {\n                return true;\n            }\n        }\n        false\n    })\n}\n\n/// Estimates selectivity from prior equality constraints on the build side.\nfn build_prior_constraint_selectivity(\n    build_constraints: &TableConstraints,\n    prior_mask: &TableMask,\n) -> f64 {\n    let mut selectivity = 1.0;\n    let mut saw_constraint = false;\n    for constraint in build_constraints.constraints.iter() {\n        if constraint.operator == Operator::Equals.into()\n            && constraint.lhs_mask.intersects(prior_mask)\n        {\n            tracing::debug!(\n                where_clause_pos = ?constraint.where_clause_pos,\n                lhs_mask = ?constraint.lhs_mask,\n                prior_mask = ?prior_mask,\n                selectivity = constraint.selectivity,\n                \"prior constraint selectivity contributor\"\n            );\n            selectivity *= constraint.selectivity;\n            saw_constraint = true;\n        }\n    }\n    if !saw_constraint {\n        return 1.0;\n    }\n    selectivity.clamp(0.0, 1.0)\n}\n\n/// Estimates selectivity from build-side constraints that reference only the build table.\nfn build_self_constraint_selectivity(\n    build_constraints: &TableConstraints,\n    build_table_idx: usize,\n) -> f64 {\n    let build_only_mask = TableMask::from_table_number_iter([build_table_idx].into_iter());\n    let mut selectivity = 1.0;\n    let mut saw_constraint = false;\n    for constraint in build_constraints.constraints.iter() {\n        if !build_only_mask.contains_all(&constraint.lhs_mask) {\n            continue;\n        }\n        selectivity *= constraint.selectivity;\n        saw_constraint = true;\n    }\n    if !saw_constraint {\n        return 1.0;\n    }\n    selectivity.clamp(0.0, 1.0)\n}\n\n/// Returns true if any prior constraints can be turned into an index lookup.\nfn build_has_indexable_prior_constraints(\n    build_constraints: &TableConstraints,\n    prior_mask: &TableMask,\n) -> bool {\n    build_constraints.candidates.iter().any(|candidate| {\n        candidate.refs.iter().any(|constraint_ref| {\n            let constraint = &build_constraints.constraints[constraint_ref.constraint_vec_pos];\n            constraint.usable && constraint.lhs_mask.intersects(prior_mask)\n        })\n    })\n}\n\n/// The result of [compute_best_join_order].\n#[derive(Debug)]\npub struct BestJoinOrderResult {\n    /// The best plan overall.\n    pub best_plan: JoinN,\n    /// The best plan for the given order target, if it isn't the overall best.\n    pub best_ordered_plan: Option<JoinN>,\n}\n\n/// Compute the best way to join a given set of tables.\n/// Returns the best [JoinN] if one exists, otherwise returns None.\n#[allow(clippy::too_many_arguments)]\n#[cfg_attr(not(test), allow(dead_code))]\npub fn compute_best_join_order<'a>(\n    joined_tables: &[JoinedTable],\n    initial_input_cardinality: f64,\n    maybe_order_target: Option<&OrderTarget>,\n    constraints: &'a [TableConstraints],\n    base_table_rows: &[RowCountEstimate],\n    access_methods_arena: &'a mut Vec<AccessMethod>,\n    where_clause: &mut [WhereTerm],\n    subqueries: &[NonFromClauseSubquery],\n    index_method_candidates: &[IndexMethodCandidate],\n    params: &CostModelParams,\n    analyze_stats: &AnalyzeStats,\n    available_indexes: &HashMap<String, VecDeque<Arc<Index>>>,\n    table_references: &TableReferences,\n    schema: &Schema,\n) -> Result<Option<BestJoinOrderResult>> {\n    compute_best_join_order_with_context(\n        joined_tables,\n        initial_input_cardinality,\n        JoinPlanningContext::default_with_order_target(maybe_order_target),\n        constraints,\n        base_table_rows,\n        access_methods_arena,\n        where_clause,\n        subqueries,\n        index_method_candidates,\n        params,\n        analyze_stats,\n        available_indexes,\n        table_references,\n        schema,\n    )\n}\n\n/// Enumerate join orders while carrying a small amount of planner context that\n/// influences access-path scoring, such as an order target for sort elimination\n/// or simple MIN/MAX planning.\n#[expect(clippy::too_many_arguments)]\npub(crate) fn compute_best_join_order_with_context<'a>(\n    joined_tables: &[JoinedTable],\n    initial_input_cardinality: f64,\n    planning_context: JoinPlanningContext<'_>,\n    constraints: &'a [TableConstraints],\n    base_table_rows: &[RowCountEstimate],\n    access_methods_arena: &'a mut Vec<AccessMethod>,\n    where_clause: &mut [WhereTerm],\n    subqueries: &[NonFromClauseSubquery],\n    index_method_candidates: &[IndexMethodCandidate],\n    params: &CostModelParams,\n    analyze_stats: &AnalyzeStats,\n    available_indexes: &HashMap<String, VecDeque<Arc<Index>>>,\n    table_references: &TableReferences,\n    schema: &Schema,\n) -> Result<Option<BestJoinOrderResult>> {\n    // Skip work if we have no tables to consider.\n    if joined_tables.is_empty() {\n        return Ok(None);\n    }\n\n    let num_tables = joined_tables.len();\n\n    // For large queries, use greedy join ordering instead of exhaustive DP.\n    // The DP algorithm has O(2^n) complexity which becomes prohibitively slow\n    // beyond ~12 tables. The greedy algorithm is O(n²) and produces good\n    // (though not always optimal) plans.\n    let where_term_table_ids = build_where_term_table_ids(where_clause, joined_tables);\n    if num_tables > GREEDY_JOIN_THRESHOLD {\n        return compute_greedy_join_order(\n            joined_tables,\n            initial_input_cardinality,\n            planning_context,\n            constraints,\n            base_table_rows,\n            access_methods_arena,\n            where_clause,\n            &where_term_table_ids,\n            subqueries,\n            index_method_candidates,\n            params,\n            analyze_stats,\n            available_indexes,\n            table_references,\n            schema,\n        );\n    }\n\n    // Compute naive left-to-right plan to use as pruning threshold\n    let naive_plan = compute_naive_left_deep_plan(\n        joined_tables,\n        initial_input_cardinality,\n        planning_context,\n        base_table_rows,\n        access_methods_arena,\n        constraints,\n        where_clause,\n        &where_term_table_ids,\n        subqueries,\n        index_method_candidates,\n        params,\n        analyze_stats,\n        available_indexes,\n        table_references,\n        schema,\n    )?;\n\n    // Keep track of both 1. the best plan overall (not considering sorting), and 2. the best ordered plan (which might not be the same).\n    // We assign Some Cost (tm) to any required sort operation, so the best ordered plan may end up being\n    // the one we choose, if the cost reduction from avoiding sorting brings it below the cost of the overall best one.\n    let mut best_ordered_plan: Option<JoinN> = None;\n    let mut best_plan_is_also_ordered =\n        match (naive_plan.as_ref(), planning_context.maybe_order_target) {\n            (Some(plan), Some(order_target)) => plan_satisfies_order_target(\n                plan,\n                access_methods_arena,\n                joined_tables,\n                order_target,\n                schema,\n            ),\n            _ => false,\n        };\n\n    // If we have one table, then the \"naive left-to-right plan\" is always the best.\n    if joined_tables.len() == 1 {\n        return match naive_plan {\n            Some(plan) => Ok(Some(BestJoinOrderResult {\n                best_plan: plan,\n                best_ordered_plan: None,\n            })),\n            None => Err(LimboError::PlanningError(\n                \"No valid query plan found\".to_string(),\n            )),\n        };\n    }\n    let mut best_plan = naive_plan;\n\n    // Reuse a single mutable join order to avoid allocating join orders per permutation.\n    let mut join_order = Vec::with_capacity(num_tables);\n    join_order.push(JoinOrderMember {\n        table_id: TableInternalId::default(),\n        original_idx: 0,\n        is_outer: false,\n    });\n\n    // Keep track of the current best cost so we can short-circuit planning for subplans\n    // that already exceed the cost of the current best plan.\n    let cost_upper_bound = best_plan.as_ref().map_or(Cost(f64::MAX), |plan| plan.cost);\n\n    // Keep track of the best plan for a given subset of tables.\n    // Consider this example: we have tables a,b,c,d to join.\n    // if we find that 'b JOIN a' is better than 'a JOIN b', then we don't need to even try\n    // to do 'a JOIN b JOIN c', because we know 'b JOIN a JOIN c' is going to be better.\n    // This is due to the commutativity and associativity of inner joins.\n    // Memo table keyed by a subset mask, then by the last table in the join order.\n    //\n    // We keep multiple plans per subset instead of only the cheapest one. The cheapest\n    // subset plan is not always the best foundation for the next join. Keeping variants\n    // lets the planner choose a better join order later (e.g. for hash-join chaining).\n    let mut best_plan_memo: HashMap<TableMask, HashMap<usize, JoinN>> =\n        HashMap::with_capacity_and_hasher(2usize.pow(num_tables as u32 - 1), Default::default());\n\n    // Dynamic programming base case: calculate the best way to access each single table, as if\n    // there were no other tables.\n    for i in 0..num_tables {\n        let mut mask = TableMask::new();\n        mask.add_table(i);\n        let table_ref = &joined_tables[i];\n        join_order[0] = JoinOrderMember {\n            table_id: table_ref.internal_id,\n            original_idx: i,\n            is_outer: false,\n        };\n        turso_assert_eq!(join_order.len(), 1);\n        let rel = join_lhs_and_rhs(\n            None,\n            initial_input_cardinality,\n            table_ref,\n            &constraints[i],\n            constraints,\n            base_table_rows,\n            &join_order,\n            planning_context,\n            access_methods_arena,\n            cost_upper_bound,\n            joined_tables,\n            where_clause,\n            &where_term_table_ids,\n            subqueries,\n            index_method_candidates,\n            params,\n            analyze_stats,\n            available_indexes,\n            table_references,\n            schema,\n        )?;\n        if let Some(rel) = rel {\n            best_plan_memo.entry(mask).or_default().insert(i, rel);\n        }\n    }\n    join_order.clear();\n\n    // As mentioned, inner joins are commutative. Outer joins are NOT.\n    // Example:\n    // \"a LEFT JOIN b\" can NOT be reordered as \"b LEFT JOIN a\".\n    // If there are outer joins in the plan, ensure correct ordering.\n    let left_join_illegal_map = {\n        let ordering_constrained_count = joined_tables\n            .iter()\n            .filter(|t| {\n                t.join_info\n                    .as_ref()\n                    .is_some_and(|j| j.is_ordering_constrained())\n            })\n            .count();\n        if ordering_constrained_count == 0 {\n            None\n        } else {\n            // map from rhs table index to lhs table index\n            let mut left_join_illegal_map: HashMap<usize, TableMask> =\n                HashMap::with_capacity_and_hasher(ordering_constrained_count, Default::default());\n            for (i, _) in joined_tables.iter().enumerate() {\n                for (j, joined_table) in joined_tables.iter().enumerate().skip(i + 1) {\n                    // LEFT/FULL OUTER, SEMI, and ANTI joins all require the RHS table\n                    // to appear after the LHS table in the join order.\n                    if joined_table\n                        .join_info\n                        .as_ref()\n                        .is_some_and(|j| j.is_ordering_constrained())\n                    {\n                        // bitwise OR the masks\n                        if let Some(illegal_lhs) = left_join_illegal_map.get_mut(&i) {\n                            illegal_lhs.add_table(j);\n                        } else {\n                            let mut mask = TableMask::new();\n                            mask.add_table(j);\n                            left_join_illegal_map.insert(i, mask);\n                        }\n                    }\n                }\n            }\n            Some(left_join_illegal_map)\n        }\n    };\n\n    // Now that we have our single-table base cases, we can start considering join subsets of 2 tables and more.\n    // Try to join each single table to each other table.\n    for subset_size in 2..=num_tables {\n        for mask in generate_join_bitmasks(num_tables, subset_size) {\n            // Keep track of the best way to join this subset of tables per possible last table.\n            // This preserves alternative join orders that may be more expensive for the subset\n            // but enable cheaper joins when adding more tables.\n            let mut best_for_mask_by_last: HashMap<usize, JoinN> = HashMap::default();\n            // Also keep track of the best plan for this subset that orders the rows in an\n            // Interesting Way (tm), i.e. allows us to eliminate sort operations downstream.\n            let mut best_ordered_for_mask: Option<JoinN> = None;\n\n            // Try to join all subsets (masks) with all other tables.\n            // In this block, LHS is always (n-1) tables, and RHS is a single table.\n            for rhs_idx in 0..num_tables {\n                // If the RHS table isn't a member of this join subset, skip.\n                if !mask.contains_table(rhs_idx) {\n                    continue;\n                }\n\n                // If there are no other tables except RHS, skip.\n                let lhs_mask = mask.without_table(rhs_idx);\n                if lhs_mask.is_empty() {\n                    continue;\n                }\n\n                // If this join ordering would violate LEFT JOIN ordering restrictions, skip.\n                if let Some(illegal_lhs) = left_join_illegal_map\n                    .as_ref()\n                    .and_then(|deps| deps.get(&rhs_idx))\n                {\n                    let legal = !lhs_mask.intersects(illegal_lhs);\n                    if !legal {\n                        continue; // Don't allow RHS before its LEFT in LEFT JOIN\n                    }\n                }\n\n                let Some(lhs_variants) = best_plan_memo.get(&lhs_mask) else {\n                    continue;\n                };\n\n                // Stable iteration keeps tie-breaks consistent across runs.\n                let mut lhs_keys: Vec<usize> = lhs_variants.keys().copied().collect();\n                lhs_keys.sort_unstable();\n                for lhs_key in lhs_keys {\n                    let lhs = &lhs_variants[&lhs_key];\n                    // Build a JoinOrder out of the table bitmask under consideration.\n                    for table_no in lhs.table_numbers() {\n                        join_order.push(JoinOrderMember {\n                            table_id: joined_tables[table_no].internal_id,\n                            original_idx: table_no,\n                            is_outer: joined_tables[table_no]\n                                .join_info\n                                .as_ref()\n                                .is_some_and(|j| j.is_outer()),\n                        });\n                    }\n                    join_order.push(JoinOrderMember {\n                        table_id: joined_tables[rhs_idx].internal_id,\n                        original_idx: rhs_idx,\n                        is_outer: joined_tables[rhs_idx]\n                            .join_info\n                            .as_ref()\n                            .is_some_and(|j| j.is_outer()),\n                    });\n                    turso_assert_eq!(join_order.len(), subset_size);\n\n                    // Calculate the best way to join LHS with RHS.\n                    let rel = join_lhs_and_rhs(\n                        Some(lhs),\n                        initial_input_cardinality,\n                        &joined_tables[rhs_idx],\n                        &constraints[rhs_idx],\n                        constraints,\n                        base_table_rows,\n                        &join_order,\n                        planning_context,\n                        access_methods_arena,\n                        cost_upper_bound,\n                        joined_tables,\n                        where_clause,\n                        &where_term_table_ids,\n                        subqueries,\n                        index_method_candidates,\n                        params,\n                        analyze_stats,\n                        available_indexes,\n                        table_references,\n                        schema,\n                    )?;\n                    join_order.clear();\n\n                    let Some(rel) = rel else {\n                        continue;\n                    };\n\n                    let satisfies_order_target =\n                        if let Some(order_target) = planning_context.maybe_order_target {\n                            plan_satisfies_order_target(\n                                &rel,\n                                access_methods_arena,\n                                joined_tables,\n                                order_target,\n                                schema,\n                            )\n                        } else {\n                            false\n                        };\n\n                    // If this plan is worse than our overall best, it might still be the best ordered plan.\n                    if rel.cost >= cost_upper_bound {\n                        // But if it isn't, skip.\n                        if !satisfies_order_target {\n                            continue;\n                        }\n                        let existing_ordered_cost: Cost = best_ordered_for_mask\n                            .as_ref()\n                            .map_or(Cost(f64::MAX), |p: &JoinN| p.cost);\n                        if rel.cost < existing_ordered_cost {\n                            best_ordered_for_mask = Some(rel);\n                        }\n                        continue;\n                    }\n\n                    let should_replace = match best_for_mask_by_last.get(&rhs_idx) {\n                        Some(existing) => rel.cost < existing.cost,\n                        None => true,\n                    };\n                    if should_replace {\n                        best_for_mask_by_last.insert(rhs_idx, rel);\n                    }\n                }\n            }\n\n            let has_all_tables = mask.table_count() == num_tables;\n            if has_all_tables {\n                for rel in best_for_mask_by_last.into_values() {\n                    if cost_upper_bound <= rel.cost {\n                        continue;\n                    }\n                    let satisfies_order_target =\n                        if let Some(order_target) = planning_context.maybe_order_target {\n                            plan_satisfies_order_target(\n                                &rel,\n                                access_methods_arena,\n                                joined_tables,\n                                order_target,\n                                schema,\n                            )\n                        } else {\n                            false\n                        };\n                    if best_plan.as_ref().is_none_or(|plan| rel.cost < plan.cost) {\n                        best_plan = Some(rel);\n                        best_plan_is_also_ordered = satisfies_order_target;\n                    }\n                }\n                if let Some(rel) = best_ordered_for_mask.take() {\n                    let cost = rel.cost;\n                    if cost_upper_bound > cost {\n                        best_ordered_plan = Some(rel);\n                    }\n                }\n            } else if !best_for_mask_by_last.is_empty() {\n                best_plan_memo.insert(mask, best_for_mask_by_last);\n            }\n        }\n    }\n\n    match best_plan {\n        Some(best_plan) => Ok(Some(BestJoinOrderResult {\n            best_plan,\n            best_ordered_plan: if best_plan_is_also_ordered {\n                None\n            } else {\n                best_ordered_plan\n            },\n        })),\n        None => {\n            // Give a targeted error for FULL OUTER when no plan was found.\n            let has_full_outer = joined_tables\n                .iter()\n                .any(|t| t.join_info.as_ref().is_some_and(|ji| ji.is_full_outer()));\n            if has_full_outer {\n                // Distinguish chaining from a missing equi-join condition.\n                let build_is_outer = joined_tables.iter().any(|t| {\n                    let is_full = t.join_info.as_ref().is_some_and(|ji| ji.is_full_outer());\n                    if !is_full {\n                        return false;\n                    }\n                    // Check if any earlier table (potential build) is also outer.\n                    joined_tables.iter().any(|other| {\n                        !std::ptr::eq(t, other)\n                            && other.join_info.as_ref().is_some_and(|ji| ji.is_outer())\n                    })\n                });\n                let has_correlated_subquery = subqueries.iter().any(|sq| sq.correlated);\n                let msg = if build_is_outer {\n                    \"FULL OUTER JOIN chaining is not yet supported\"\n                } else if has_correlated_subquery {\n                    \"FULL OUTER JOIN is not supported with correlated subqueries that reference the joined tables\"\n                } else {\n                    \"FULL OUTER JOIN requires an equality condition in the ON clause\"\n                };\n                Err(LimboError::ParseError(msg.to_string()))\n            } else {\n                Err(LimboError::PlanningError(\n                    \"No valid query plan found\".to_string(),\n                ))\n            }\n        }\n    }\n}\n\n/// Above this threshold, use greedy O(n²) ordering instead of exhaustive O(2^n) DP.\npub const GREEDY_JOIN_THRESHOLD: usize = 12;\n\n/// Greedy Operator Ordering (GOO) for join optimization. O(n²) time, O(n) space.\n///\n/// Builds a left-deep join tree by:\n/// 1. Starting with the table that has best hub score (enables most index lookups)\n/// 2. Greedily adding the remaining table with lowest marginal cost\n///\n/// Respects outer join ordering constraints.\n#[allow(clippy::too_many_arguments)]\npub fn compute_greedy_join_order<'a>(\n    joined_tables: &[JoinedTable],\n    initial_input_cardinality: f64,\n    planning_context: JoinPlanningContext<'_>,\n    constraints: &'a [TableConstraints],\n    base_table_rows: &[RowCountEstimate],\n    access_methods_arena: &'a mut Vec<AccessMethod>,\n    where_clause: &mut [WhereTerm],\n    where_term_table_ids: &[HashSet<TableInternalId>],\n    subqueries: &[NonFromClauseSubquery],\n    index_method_candidates: &[IndexMethodCandidate],\n    params: &CostModelParams,\n    analyze_stats: &AnalyzeStats,\n    available_indexes: &HashMap<String, VecDeque<Arc<Index>>>,\n    table_references: &TableReferences,\n    schema: &Schema,\n) -> Result<Option<BestJoinOrderResult>> {\n    let num_tables = joined_tables.len();\n    if num_tables == 0 {\n        return Ok(None);\n    }\n\n    // Outer join RHS tables require all preceding tables to be joined first.\n    let left_join_deps: HashMap<usize, TableMask> = joined_tables\n        .iter()\n        .enumerate()\n        .filter(|(_, t)| {\n            t.join_info\n                .as_ref()\n                .is_some_and(|ji| ji.is_ordering_constrained())\n        })\n        .map(|(j, _)| {\n            let mut required = TableMask::new();\n            for k in 0..j {\n                required.add_table(k);\n            }\n            (j, required)\n        })\n        .collect();\n\n    let mut remaining: Vec<usize> = (0..num_tables).collect();\n    let mut join_order: Vec<JoinOrderMember> = Vec::with_capacity(num_tables);\n\n    // Pick starting table: prefer tables with high \"hub score\" (referenced by many constraints).\n    let first_idx =\n        find_best_starting_table(num_tables, constraints, base_table_rows, &left_join_deps);\n    let first_table = &joined_tables[first_idx];\n    join_order.push(JoinOrderMember {\n        table_id: first_table.internal_id,\n        original_idx: first_idx,\n        is_outer: false, // First table cannot be outer join RHS\n    });\n    remaining.retain(|&x| x != first_idx);\n\n    let mut current_plan: Option<JoinN> = join_lhs_and_rhs(\n        None,\n        initial_input_cardinality,\n        first_table,\n        &constraints[first_idx],\n        constraints,\n        base_table_rows,\n        &join_order,\n        planning_context,\n        access_methods_arena,\n        Cost(f64::MAX),\n        joined_tables,\n        where_clause,\n        where_term_table_ids,\n        subqueries,\n        index_method_candidates,\n        params,\n        analyze_stats,\n        available_indexes,\n        table_references,\n        schema,\n    )?;\n\n    if current_plan.is_none() {\n        return Err(LimboError::PlanningError(\n            \"No valid query plan found for first table\".to_string(),\n        ));\n    }\n\n    // Greedily add remaining tables, always picking lowest marginal cost.\n    while !remaining.is_empty() {\n        let current_mask =\n            TableMask::from_table_number_iter(join_order.iter().map(|m| m.original_idx));\n\n        // Placeholder for candidate evaluation (avoids cloning)\n        join_order.push(JoinOrderMember::default());\n\n        let mut best: Option<(usize, JoinN)> = None;\n\n        let mut has_connected_candidate = false;\n        for &idx in &remaining {\n            // Outer join RHS requires all preceding tables joined first\n            if let Some(required) = left_join_deps.get(&idx) {\n                if !current_mask.contains_all(required) {\n                    continue;\n                }\n            }\n            let connected = where_term_table_ids.iter().any(|table_ids| {\n                let table_id = joined_tables[idx].internal_id;\n                table_ids.contains(&table_id)\n                    && current_mask\n                        .tables_iter()\n                        .map(|table_no| joined_tables[table_no].internal_id)\n                        .any(|id| table_ids.contains(&id))\n            });\n            if connected {\n                has_connected_candidate = true;\n                break;\n            }\n        }\n\n        for &idx in &remaining {\n            // Outer join RHS requires all preceding tables joined first\n            if let Some(required) = left_join_deps.get(&idx) {\n                if !current_mask.contains_all(required) {\n                    continue;\n                }\n            }\n            if has_connected_candidate {\n                let connected = where_term_table_ids.iter().any(|table_ids| {\n                    let table_id = joined_tables[idx].internal_id;\n                    table_ids.contains(&table_id)\n                        && current_mask\n                            .tables_iter()\n                            .map(|table_no| joined_tables[table_no].internal_id)\n                            .any(|id| table_ids.contains(&id))\n                });\n                if !connected {\n                    continue;\n                }\n            }\n\n            let table = &joined_tables[idx];\n            let last = join_order.last_mut().unwrap();\n            last.table_id = table.internal_id;\n            last.original_idx = idx;\n            last.is_outer = table.join_info.as_ref().is_some_and(|ji| ji.is_outer());\n\n            if let Some(plan) = join_lhs_and_rhs(\n                current_plan.as_ref(),\n                initial_input_cardinality,\n                table,\n                &constraints[idx],\n                constraints,\n                base_table_rows,\n                &join_order,\n                planning_context,\n                access_methods_arena,\n                Cost(f64::MAX),\n                joined_tables,\n                where_clause,\n                where_term_table_ids,\n                subqueries,\n                index_method_candidates,\n                params,\n                analyze_stats,\n                available_indexes,\n                table_references,\n                schema,\n            )? {\n                if best.as_ref().is_none_or(|(_, b)| plan.cost < b.cost) {\n                    best = Some((idx, plan));\n                }\n            }\n        }\n\n        join_order.pop();\n\n        let (next_idx, next_plan) = best.ok_or_else(|| {\n            LimboError::PlanningError(\"Greedy join ordering: no valid next table\".to_string())\n        })?;\n\n        let next_table = &joined_tables[next_idx];\n        join_order.push(JoinOrderMember {\n            table_id: next_table.internal_id,\n            original_idx: next_idx,\n            is_outer: next_table\n                .join_info\n                .as_ref()\n                .is_some_and(|ji| ji.is_outer()),\n        });\n        remaining.retain(|&x| x != next_idx);\n        current_plan = Some(next_plan);\n    }\n\n    Ok(Some(BestJoinOrderResult {\n        best_plan: current_plan.expect(\"loop invariant: current_plan always Some\"),\n        best_ordered_plan: None, // Greedy doesn't track ordered variants\n    }))\n}\n\n/// Select starting table for greedy join ordering.\n///\n/// Prefers tables with high \"hub score\": tables referenced by many other tables' usable\n/// constraints. Starting with such tables enables index lookups on subsequent joins.\n/// E.g., in a star schema, the fact table is referenced by all dimension FKs, so\n/// starting there allows all dimensions to use their PK indexes.\n///\n/// Score = (base_rows * filter_selectivity) / (1 + hub_score)\n/// Lower score wins. Outer join RHS tables are excluded (have ordering dependencies).\nfn find_best_starting_table(\n    num_tables: usize,\n    constraints: &[TableConstraints],\n    base_table_rows: &[RowCountEstimate],\n    left_join_deps: &HashMap<usize, TableMask>,\n) -> usize {\n    // hub_score[t] = count of usable constraints on OTHER tables that reference t.\n    // If we join t first, each such constraint becomes usable for an index lookup.\n    let mut hub_score = vec![0usize; num_tables];\n    for (t, tc) in constraints.iter().enumerate() {\n        for c in &tc.constraints {\n            if c.usable && c.table_col_pos.is_some() {\n                for other in (0..num_tables).filter(|&x| x != t && c.lhs_mask.contains_table(x)) {\n                    hub_score[other] += 1;\n                }\n            }\n        }\n    }\n\n    let mut best: Option<(usize, f64)> = None;\n    for t in 0..num_tables {\n        if left_join_deps.contains_key(&t) {\n            continue; // Outer join RHS - cannot be first\n        }\n\n        let base_rows = *base_table_rows[t];\n\n        // Self-constraints compare columns within the same table (e.g., t.col1 < t.col2).\n        let self_mask = {\n            let mut m = TableMask::new();\n            m.add_table(t);\n            m\n        };\n\n        // Include literal constraints (lhs_mask empty) and self-constraints in selectivity\n        let selectivity: f64 = constraints[t]\n            .constraints\n            .iter()\n            .filter(|c| c.lhs_mask.is_empty() || c.lhs_mask == self_mask)\n            .map(|c| c.selectivity)\n            .product();\n\n        let score = base_rows * selectivity / (1.0 + hub_score[t] as f64);\n\n        if best.is_none_or(|(_, s)| score < s) {\n            best = Some((t, score));\n        }\n    }\n\n    // Table 0 can never be outer join RHS, so best is always Some.\n    best.expect(\"no valid starting table\").0\n}\n\n/// Specialized version of [compute_best_join_order] that just joins tables in the order they are given\n/// in the SQL query. This is used as an upper bound for any other plans -- we can give up enumerating\n/// permutations if they exceed this cost during enumeration.\n#[allow(clippy::too_many_arguments)]\npub fn compute_naive_left_deep_plan<'a>(\n    joined_tables: &[JoinedTable],\n    initial_input_cardinality: f64,\n    planning_context: JoinPlanningContext<'_>,\n    base_table_rows: &[RowCountEstimate],\n    access_methods_arena: &'a mut Vec<AccessMethod>,\n    constraints: &'a [TableConstraints],\n    where_clause: &mut [WhereTerm],\n    where_term_table_ids: &[HashSet<TableInternalId>],\n    subqueries: &[NonFromClauseSubquery],\n    index_method_candidates: &[IndexMethodCandidate],\n    params: &CostModelParams,\n    analyze_stats: &AnalyzeStats,\n    available_indexes: &HashMap<String, VecDeque<Arc<Index>>>,\n    table_references: &TableReferences,\n    schema: &Schema,\n) -> Result<Option<JoinN>> {\n    let n = joined_tables.len();\n    turso_assert_greater_than!(n, 0);\n\n    let join_order = joined_tables\n        .iter()\n        .enumerate()\n        .map(|(i, t)| JoinOrderMember {\n            table_id: t.internal_id,\n            original_idx: i,\n            is_outer: t.join_info.as_ref().is_some_and(|j| j.is_outer()),\n        })\n        .collect::<Vec<_>>();\n\n    // Start with first table\n    let mut best_plan = join_lhs_and_rhs(\n        None,\n        initial_input_cardinality,\n        &joined_tables[0],\n        &constraints[0],\n        constraints,\n        base_table_rows,\n        &join_order[..1],\n        planning_context,\n        access_methods_arena,\n        Cost(f64::MAX),\n        joined_tables,\n        where_clause,\n        where_term_table_ids,\n        subqueries,\n        index_method_candidates,\n        params,\n        analyze_stats,\n        available_indexes,\n        table_references,\n        schema,\n    )?;\n    if best_plan.is_none() {\n        return Ok(None);\n    }\n\n    // Add remaining tables one at a time from left to right\n    for i in 1..n {\n        best_plan = join_lhs_and_rhs(\n            best_plan.as_ref(),\n            initial_input_cardinality,\n            &joined_tables[i],\n            &constraints[i],\n            constraints,\n            base_table_rows,\n            &join_order[..=i],\n            planning_context,\n            access_methods_arena,\n            Cost(f64::MAX),\n            joined_tables,\n            where_clause,\n            where_term_table_ids,\n            subqueries,\n            index_method_candidates,\n            params,\n            analyze_stats,\n            available_indexes,\n            table_references,\n            schema,\n        )?;\n        if best_plan.is_none() {\n            return Ok(None);\n        }\n    }\n\n    Ok(best_plan)\n}\n\n/// Precompute table IDs referenced by each WHERE term for join-order decisions.\nfn build_where_term_table_ids(\n    where_clause: &[WhereTerm],\n    joined_tables: &[JoinedTable],\n) -> Vec<HashSet<TableInternalId>> {\n    let joined_ids: HashSet<TableInternalId> =\n        joined_tables.iter().map(|t| t.internal_id).collect();\n    where_clause\n        .iter()\n        .map(|term| expr_table_ids_filtered(&term.expr, &joined_ids))\n        .collect()\n}\n\n/// Collect table IDs from an expression that belong to the joined tables set.\nfn expr_table_ids_filtered(\n    expr: &Expr,\n    joined_ids: &HashSet<TableInternalId>,\n) -> HashSet<TableInternalId> {\n    let mut tables = HashSet::default();\n    let _ = walk_expr(expr, &mut |node| {\n        match node {\n            Expr::Column { table, .. } | Expr::RowId { table, .. } => {\n                if joined_ids.contains(table) {\n                    tables.insert(*table);\n                }\n            }\n            _ => {}\n        }\n        Ok(WalkControl::Continue)\n    });\n    tables\n}\n\n/// Iterator that generates all possible size k bitmasks for a given number of tables.\n/// For example, given: 3 tables and k=2, the bitmasks are:\n/// - 0b011 (tables 0, 1)\n/// - 0b101 (tables 0, 2)\n/// - 0b110 (tables 1, 2)\n///\n/// This is used in the dynamic programming approach to finding the best way to join a subset of N tables.\nstruct JoinBitmaskIter {\n    current: u128,\n    max_exclusive: u128,\n}\n\nimpl JoinBitmaskIter {\n    fn new(table_number_max_exclusive: usize, how_many: usize) -> Self {\n        Self {\n            current: (1 << how_many) - 1, // Start with smallest k-bit number (e.g., 000111 for k=3)\n            max_exclusive: 1 << table_number_max_exclusive,\n        }\n    }\n}\n\nimpl Iterator for JoinBitmaskIter {\n    type Item = TableMask;\n\n    fn next(&mut self) -> Option<Self::Item> {\n        if self.current >= self.max_exclusive {\n            return None;\n        }\n\n        let result = TableMask::from_bits(self.current);\n\n        // Gosper's hack: compute next k-bit combination in lexicographic order\n        let c = self.current & (!self.current + 1); // rightmost set bit\n        let r = self.current + c; // add it to get a carry\n        let ones = self.current ^ r; // changed bits\n        let ones = (ones >> 2) / c; // right-adjust shifted bits\n        self.current = r | ones; // form the next combination\n\n        Some(result)\n    }\n}\n\n/// Generate all possible bitmasks of size `how_many` for a given number of tables.\nfn generate_join_bitmasks(table_number_max_exclusive: usize, how_many: usize) -> JoinBitmaskIter {\n    JoinBitmaskIter::new(table_number_max_exclusive, how_many)\n}\n\n#[cfg(test)]\nmod tests {\n    use std::{collections::VecDeque, sync::Arc};\n\n    use turso_parser::ast::{self, Expr, Operator, SortOrder, TableInternalId};\n\n    use super::*;\n    use crate::{\n        schema::{BTreeTable, ColDef, Column, Index, IndexColumn, Schema, Table, Type},\n        stats::AnalyzeStats,\n        translate::{\n            optimizer::{\n                access_method::AccessMethodParams,\n                constraints::{constraints_from_where_clause, BinaryExprSide, RangeConstraintRef},\n                cost_params::DEFAULT_PARAMS,\n            },\n            plan::{\n                ColumnUsedMask, IterationDirection, JoinInfo, JoinType, Operation, TableReferences,\n                WhereTerm,\n            },\n            planner::TableMask,\n        },\n        vdbe::builder::TableRefIdCounter,\n    };\n\n    fn default_base_rows(n: usize) -> Vec<RowCountEstimate> {\n        vec![RowCountEstimate::hardcoded_fallback(&DEFAULT_PARAMS); n]\n    }\n\n    fn empty_schema() -> Schema {\n        Schema::default()\n    }\n\n    #[test]\n    fn test_generate_bitmasks() {\n        let bitmasks = generate_join_bitmasks(4, 2).collect::<Vec<_>>();\n        assert!(bitmasks.contains(&TableMask(0b110))); // {0,1} -- first bit is always set to 0 so that a Mask with value 0 means \"no tables are referenced\".\n        assert!(bitmasks.contains(&TableMask(0b1010))); // {0,2}\n        assert!(bitmasks.contains(&TableMask(0b1100))); // {1,2}\n        assert!(bitmasks.contains(&TableMask(0b10010))); // {0,3}\n        assert!(bitmasks.contains(&TableMask(0b10100))); // {1,3}\n        assert!(bitmasks.contains(&TableMask(0b11000))); // {2,3}\n    }\n\n    #[test]\n    /// Test that [compute_best_join_order] returns None when there are no table references.\n    fn test_compute_best_join_order_empty() {\n        let table_references = TableReferences::new(vec![], vec![]);\n        let available_indexes = HashMap::default();\n        let mut where_clause = vec![];\n\n        let mut access_methods_arena = Vec::new();\n        let table_constraints = constraints_from_where_clause(\n            &where_clause,\n            &table_references,\n            &available_indexes,\n            &[],\n            &empty_schema(),\n            &DEFAULT_PARAMS,\n        )\n        .unwrap();\n\n        let base_table_rows = default_base_rows(table_references.joined_tables().len());\n        let schema = empty_schema();\n        let result = compute_best_join_order(\n            table_references.joined_tables(),\n            1.0,\n            None,\n            &table_constraints,\n            &base_table_rows,\n            &mut access_methods_arena,\n            &mut where_clause,\n            &[],\n            &[],\n            &DEFAULT_PARAMS,\n            &AnalyzeStats::default(),\n            &available_indexes,\n            &table_references,\n            &schema,\n        )\n        .unwrap();\n        assert!(result.is_none());\n    }\n\n    #[test]\n    /// Test that [compute_best_join_order] returns a table scan access method when the where clause is empty.\n    fn test_compute_best_join_order_single_table_no_indexes() {\n        let t1 = _create_btree_table(\"test_table\", _create_column_list(&[\"id\"], Type::Integer));\n        let mut table_id_counter = TableRefIdCounter::new();\n        let joined_tables = vec![_create_table_reference(t1, None, table_id_counter.next())];\n        let table_references = TableReferences::new(joined_tables, vec![]);\n        let available_indexes = HashMap::default();\n        let mut where_clause = vec![];\n\n        let mut access_methods_arena = Vec::new();\n        let table_constraints = constraints_from_where_clause(\n            &where_clause,\n            &table_references,\n            &available_indexes,\n            &[],\n            &empty_schema(),\n            &DEFAULT_PARAMS,\n        )\n        .unwrap();\n\n        // SELECT * from test_table\n        // expecting best_best_plan() not to do any work due to empty where clause.\n        let base_table_rows = default_base_rows(table_references.joined_tables().len());\n        let schema = empty_schema();\n        let BestJoinOrderResult { best_plan, .. } = compute_best_join_order(\n            table_references.joined_tables(),\n            1.0,\n            None,\n            &table_constraints,\n            &base_table_rows,\n            &mut access_methods_arena,\n            &mut where_clause,\n            &[],\n            &[],\n            &DEFAULT_PARAMS,\n            &AnalyzeStats::default(),\n            &available_indexes,\n            &table_references,\n            &schema,\n        )\n        .unwrap()\n        .unwrap();\n        // Should just be a table scan access method\n        let access_method = &access_methods_arena[best_plan.data[0].1];\n        let (iter_dir, _, constraint_refs) = _as_btree(access_method);\n        assert!(constraint_refs.is_empty());\n        assert!(iter_dir == IterationDirection::Forwards);\n    }\n\n    #[test]\n    /// Test that [compute_best_join_order] returns a RowidEq access method when the where clause has an EQ constraint on the rowid alias.\n    fn test_compute_best_join_order_single_table_rowid_eq() {\n        let t1 = _create_btree_table(\"test_table\", vec![_create_column_rowid_alias(\"id\")]);\n        let mut table_id_counter = TableRefIdCounter::new();\n        let joined_tables = vec![_create_table_reference(t1, None, table_id_counter.next())];\n\n        let mut where_clause = vec![_create_binary_expr(\n            _create_column_expr(joined_tables[0].internal_id, 0, true), // table 0, column 0 (rowid)\n            ast::Operator::Equals,\n            _create_numeric_literal(\"42\"),\n        )];\n\n        let table_references = TableReferences::new(joined_tables, vec![]);\n        let mut access_methods_arena = Vec::new();\n        let available_indexes = HashMap::default();\n        let table_constraints = constraints_from_where_clause(\n            &where_clause,\n            &table_references,\n            &available_indexes,\n            &[],\n            &empty_schema(),\n            &DEFAULT_PARAMS,\n        )\n        .unwrap();\n\n        // SELECT * FROM test_table WHERE id = 42\n        // expecting a RowidEq access method because id is a rowid alias.\n        let base_table_rows = default_base_rows(table_references.joined_tables().len());\n        let schema = empty_schema();\n        let result = compute_best_join_order(\n            table_references.joined_tables(),\n            1.0,\n            None,\n            &table_constraints,\n            &base_table_rows,\n            &mut access_methods_arena,\n            &mut where_clause,\n            &[],\n            &[],\n            &DEFAULT_PARAMS,\n            &AnalyzeStats::default(),\n            &available_indexes,\n            &table_references,\n            &schema,\n        )\n        .unwrap();\n        assert!(result.is_some());\n        let BestJoinOrderResult { best_plan, .. } = result.unwrap();\n        assert_eq!(best_plan.table_numbers().collect::<Vec<_>>(), vec![0]);\n        let access_method = &access_methods_arena[best_plan.data[0].1];\n        let (iter_dir, _, constraint_refs) = _as_btree(access_method);\n        assert!(!constraint_refs.is_empty());\n        assert!(iter_dir == IterationDirection::Forwards);\n        assert!(constraint_refs.len() == 1);\n        assert!(\n            table_constraints[0].constraints\n                [constraint_refs[0].eq.as_ref().unwrap().constraint_pos]\n                .where_clause_pos\n                == (0, BinaryExprSide::Rhs)\n        );\n    }\n\n    #[test]\n    /// Test that [compute_best_join_order] returns an IndexScan access method when the where clause has an EQ constraint on a primary key.\n    fn test_compute_best_join_order_single_table_pk_eq() {\n        let t1 = _create_btree_table(\n            \"test_table\",\n            vec![_create_column_of_type(\"id\", Type::Integer)],\n        );\n        let mut table_id_counter = TableRefIdCounter::new();\n        let joined_tables = vec![_create_table_reference(t1, None, table_id_counter.next())];\n\n        let mut where_clause = vec![_create_binary_expr(\n            _create_column_expr(joined_tables[0].internal_id, 0, false), // table 0, column 0 (id)\n            ast::Operator::Equals,\n            _create_numeric_literal(\"42\"),\n        )];\n\n        let table_references = TableReferences::new(joined_tables, vec![]);\n        let mut access_methods_arena = Vec::new();\n        let mut available_indexes = HashMap::default();\n        let index = Arc::new(Index {\n            name: \"sqlite_autoindex_test_table_1\".to_string(),\n            table_name: \"test_table\".to_string(),\n            where_clause: None,\n            columns: vec![IndexColumn {\n                name: \"id\".to_string(),\n                order: SortOrder::Asc,\n                pos_in_table: 0,\n                collation: None,\n                default: None,\n                expr: None,\n            }],\n            unique: true,\n            ephemeral: false,\n            root_page: 1,\n            has_rowid: true,\n            index_method: None,\n            on_conflict: None,\n        });\n        available_indexes.insert(\"test_table\".to_string(), VecDeque::from([index]));\n\n        let table_constraints = constraints_from_where_clause(\n            &where_clause,\n            &table_references,\n            &available_indexes,\n            &[],\n            &empty_schema(),\n            &DEFAULT_PARAMS,\n        )\n        .unwrap();\n        // SELECT * FROM test_table WHERE id = 42\n        // expecting an IndexScan access method because id is a primary key with an index\n        let base_table_rows = default_base_rows(table_references.joined_tables().len());\n        let schema = empty_schema();\n        let result = compute_best_join_order(\n            table_references.joined_tables(),\n            1.0,\n            None,\n            &table_constraints,\n            &base_table_rows,\n            &mut access_methods_arena,\n            &mut where_clause,\n            &[],\n            &[],\n            &DEFAULT_PARAMS,\n            &AnalyzeStats::default(),\n            &available_indexes,\n            &table_references,\n            &schema,\n        )\n        .unwrap();\n        assert!(result.is_some());\n        let BestJoinOrderResult { best_plan, .. } = result.unwrap();\n        assert_eq!(best_plan.table_numbers().collect::<Vec<_>>(), vec![0]);\n        let access_method = &access_methods_arena[best_plan.data[0].1];\n        let (iter_dir, index, constraint_refs) = _as_btree(access_method);\n        assert!(!constraint_refs.is_empty());\n        assert!(iter_dir == IterationDirection::Forwards);\n        assert!(index.as_ref().unwrap().name == \"sqlite_autoindex_test_table_1\");\n        assert!(constraint_refs.len() == 1);\n        assert!(\n            table_constraints[0].constraints\n                [constraint_refs[0].eq.as_ref().unwrap().constraint_pos]\n                .where_clause_pos\n                == (0, BinaryExprSide::Rhs)\n        );\n    }\n\n    #[test]\n    /// Test that [compute_best_join_order] moves the outer table to the inner position when an index can be used on it, but not the original inner table.\n    fn test_compute_best_join_order_two_tables() {\n        let t1 = _create_btree_table(\"table1\", _create_column_list(&[\"id\"], Type::Integer));\n        let t2 = _create_btree_table(\"table2\", _create_column_list(&[\"id\"], Type::Integer));\n\n        let mut table_id_counter = TableRefIdCounter::new();\n        let joined_tables = vec![\n            _create_table_reference(t1, None, table_id_counter.next()),\n            _create_table_reference(\n                t2,\n                Some(JoinInfo {\n                    join_type: JoinType::Inner,\n                    using: vec![],\n                    no_reorder: false,\n                }),\n                table_id_counter.next(),\n            ),\n        ];\n\n        const TABLE1: usize = 0;\n        const TABLE2: usize = 1;\n\n        let mut available_indexes = HashMap::default();\n        // Index on the outer table (table1)\n        let index1 = Arc::new(Index {\n            name: \"index1\".to_string(),\n            table_name: \"table1\".to_string(),\n            where_clause: None,\n            columns: vec![IndexColumn {\n                name: \"id\".to_string(),\n                order: SortOrder::Asc,\n                pos_in_table: 0,\n                collation: None,\n                default: None,\n                expr: None,\n            }],\n            unique: true,\n            ephemeral: false,\n            root_page: 1,\n            has_rowid: true,\n            index_method: None,\n            on_conflict: None,\n        });\n        available_indexes.insert(\"table1\".to_string(), VecDeque::from([index1]));\n\n        // SELECT * FROM table1 JOIN table2 WHERE table1.id = table2.id\n        // expecting table2 to be chosen first due to the index on table1.id\n        let mut where_clause = vec![_create_binary_expr(\n            _create_column_expr(joined_tables[TABLE1].internal_id, 0, false), // table1.id\n            ast::Operator::Equals,\n            _create_column_expr(joined_tables[TABLE2].internal_id, 0, false), // table2.id\n        )];\n\n        let table_references = TableReferences::new(joined_tables, vec![]);\n        let mut access_methods_arena = Vec::new();\n        let table_constraints = constraints_from_where_clause(\n            &where_clause,\n            &table_references,\n            &available_indexes,\n            &[],\n            &empty_schema(),\n            &DEFAULT_PARAMS,\n        )\n        .unwrap();\n\n        let base_table_rows = default_base_rows(table_references.joined_tables().len());\n        let schema = empty_schema();\n        let result = compute_best_join_order(\n            table_references.joined_tables(),\n            1.0,\n            None,\n            &table_constraints,\n            &base_table_rows,\n            &mut access_methods_arena,\n            &mut where_clause,\n            &[],\n            &[],\n            &DEFAULT_PARAMS,\n            &AnalyzeStats::default(),\n            &available_indexes,\n            &table_references,\n            &schema,\n        )\n        .unwrap();\n        assert!(result.is_some());\n        let BestJoinOrderResult { best_plan, .. } = result.unwrap();\n        assert_eq!(best_plan.table_numbers().collect::<Vec<_>>(), vec![1, 0]);\n        let access_method = &access_methods_arena[best_plan.data[0].1];\n        let (iter_dir, _, constraint_refs) = _as_btree(access_method);\n        assert!(constraint_refs.is_empty());\n        assert!(iter_dir == IterationDirection::Forwards);\n        let access_method = &access_methods_arena[best_plan.data[1].1];\n        let (iter_dir, index, constraint_refs) = _as_btree(access_method);\n        assert!(!constraint_refs.is_empty());\n        assert!(iter_dir == IterationDirection::Forwards);\n        assert!(index.as_ref().unwrap().name == \"index1\");\n        assert!(constraint_refs.len() == 1);\n        assert!(\n            table_constraints[TABLE1].constraints\n                [constraint_refs[0].eq.as_ref().unwrap().constraint_pos]\n                .where_clause_pos\n                == (0, BinaryExprSide::Rhs)\n        );\n    }\n\n    #[test]\n    /// Test that [compute_best_join_order] returns a sensible order and plan for three tables, each with indexes.\n    fn test_compute_best_join_order_three_tables_indexed() {\n        let table_orders = _create_btree_table(\n            \"orders\",\n            vec![\n                _create_column_of_type(\"id\", Type::Integer),\n                _create_column_of_type(\"customer_id\", Type::Integer),\n                _create_column_of_type(\"total\", Type::Integer),\n            ],\n        );\n        let table_customers = _create_btree_table(\n            \"customers\",\n            vec![\n                _create_column_of_type(\"id\", Type::Integer),\n                _create_column_of_type(\"name\", Type::Integer),\n            ],\n        );\n        let table_order_items = _create_btree_table(\n            \"order_items\",\n            vec![\n                _create_column_of_type(\"id\", Type::Integer),\n                _create_column_of_type(\"order_id\", Type::Integer),\n                _create_column_of_type(\"product_id\", Type::Integer),\n                _create_column_of_type(\"quantity\", Type::Integer),\n            ],\n        );\n\n        let mut table_id_counter = TableRefIdCounter::new();\n        let joined_tables = vec![\n            _create_table_reference(table_orders, None, table_id_counter.next()),\n            _create_table_reference(\n                table_customers,\n                Some(JoinInfo {\n                    join_type: JoinType::Inner,\n                    using: vec![],\n                    no_reorder: false,\n                }),\n                table_id_counter.next(),\n            ),\n            _create_table_reference(\n                table_order_items,\n                Some(JoinInfo {\n                    join_type: JoinType::Inner,\n                    using: vec![],\n                    no_reorder: false,\n                }),\n                table_id_counter.next(),\n            ),\n        ];\n\n        const TABLE_NO_ORDERS: usize = 0;\n        const TABLE_NO_CUSTOMERS: usize = 1;\n        const TABLE_NO_ORDER_ITEMS: usize = 2;\n\n        let mut available_indexes = HashMap::default();\n        [\"orders\", \"customers\", \"order_items\"]\n            .iter()\n            .for_each(|table_name| {\n                // add primary key index called sqlite_autoindex_<tablename>_1\n                let index_name = format!(\"sqlite_autoindex_{table_name}_1\");\n                let index = Arc::new(Index {\n                    name: index_name,\n                    where_clause: None,\n                    table_name: table_name.to_string(),\n                    columns: vec![IndexColumn {\n                        name: \"id\".to_string(),\n                        order: SortOrder::Asc,\n                        pos_in_table: 0,\n                        collation: None,\n                        default: None,\n                        expr: None,\n                    }],\n                    unique: true,\n                    ephemeral: false,\n                    root_page: 1,\n                    has_rowid: true,\n                    index_method: None,\n                    on_conflict: None,\n                });\n                available_indexes.insert(table_name.to_string(), VecDeque::from([index]));\n            });\n        let customer_id_idx = Arc::new(Index {\n            name: \"orders_customer_id_idx\".to_string(),\n            table_name: \"orders\".to_string(),\n            where_clause: None,\n            columns: vec![IndexColumn {\n                name: \"customer_id\".to_string(),\n                order: SortOrder::Asc,\n                pos_in_table: 1,\n                collation: None,\n                default: None,\n                expr: None,\n            }],\n            unique: false,\n            ephemeral: false,\n            root_page: 1,\n            has_rowid: true,\n            index_method: None,\n            on_conflict: None,\n        });\n        let order_id_idx = Arc::new(Index {\n            name: \"order_items_order_id_idx\".to_string(),\n            table_name: \"order_items\".to_string(),\n            where_clause: None,\n            columns: vec![IndexColumn {\n                name: \"order_id\".to_string(),\n                order: SortOrder::Asc,\n                pos_in_table: 1,\n                collation: None,\n                default: None,\n                expr: None,\n            }],\n            unique: false,\n            ephemeral: false,\n            root_page: 1,\n            has_rowid: true,\n            index_method: None,\n            on_conflict: None,\n        });\n\n        available_indexes\n            .entry(\"orders\".to_string())\n            .and_modify(|v| v.push_front(customer_id_idx));\n        available_indexes\n            .entry(\"order_items\".to_string())\n            .and_modify(|v| v.push_front(order_id_idx));\n\n        // SELECT * FROM orders JOIN customers JOIN order_items\n        // WHERE orders.customer_id = customers.id AND orders.id = order_items.order_id AND customers.id = 42\n        // expecting customers to be chosen first due to the index on customers.id and it having a selective filter (=42)\n        // then orders to be chosen next due to the index on orders.customer_id\n        // then order_items to be chosen last due to the index on order_items.order_id\n        let mut where_clause = vec![\n            // orders.customer_id = customers.id\n            _create_binary_expr(\n                _create_column_expr(joined_tables[TABLE_NO_ORDERS].internal_id, 1, false), // orders.customer_id\n                ast::Operator::Equals,\n                _create_column_expr(joined_tables[TABLE_NO_CUSTOMERS].internal_id, 0, false), // customers.id\n            ),\n            // orders.id = order_items.order_id\n            _create_binary_expr(\n                _create_column_expr(joined_tables[TABLE_NO_ORDERS].internal_id, 0, false), // orders.id\n                ast::Operator::Equals,\n                _create_column_expr(joined_tables[TABLE_NO_ORDER_ITEMS].internal_id, 1, false), // order_items.order_id\n            ),\n            // customers.id = 42\n            _create_binary_expr(\n                _create_column_expr(joined_tables[TABLE_NO_CUSTOMERS].internal_id, 0, false), // customers.id\n                ast::Operator::Equals,\n                _create_numeric_literal(\"42\"),\n            ),\n        ];\n\n        let table_references = TableReferences::new(joined_tables, vec![]);\n        let mut access_methods_arena = Vec::new();\n        let table_constraints = constraints_from_where_clause(\n            &where_clause,\n            &table_references,\n            &available_indexes,\n            &[],\n            &empty_schema(),\n            &DEFAULT_PARAMS,\n        )\n        .unwrap();\n\n        let base_table_rows = default_base_rows(table_references.joined_tables().len());\n        let schema = empty_schema();\n        let result = compute_best_join_order(\n            table_references.joined_tables(),\n            1.0,\n            None,\n            &table_constraints,\n            &base_table_rows,\n            &mut access_methods_arena,\n            &mut where_clause,\n            &[],\n            &[],\n            &DEFAULT_PARAMS,\n            &AnalyzeStats::default(),\n            &available_indexes,\n            &table_references,\n            &schema,\n        )\n        .unwrap();\n        assert!(result.is_some());\n        let BestJoinOrderResult { best_plan, .. } = result.unwrap();\n\n        // Customers (due to =42 filter) -> Orders (due to index on customer_id) -> Order_items (due to index on order_id)\n        assert_eq!(\n            best_plan.table_numbers().collect::<Vec<_>>(),\n            vec![TABLE_NO_CUSTOMERS, TABLE_NO_ORDERS, TABLE_NO_ORDER_ITEMS]\n        );\n\n        let access_method = &access_methods_arena[best_plan.data[0].1];\n        let (iter_dir, index, constraint_refs) = _as_btree(access_method);\n        assert!(iter_dir == IterationDirection::Forwards);\n        assert!(index.as_ref().unwrap().name == \"sqlite_autoindex_customers_1\");\n        assert!(constraint_refs.len() == 1);\n        let constraint = &table_constraints[TABLE_NO_CUSTOMERS].constraints\n            [constraint_refs[0].eq.as_ref().unwrap().constraint_pos];\n        assert!(constraint.lhs_mask.is_empty());\n\n        let access_method = &access_methods_arena[best_plan.data[1].1];\n        let (iter_dir, index, constraint_refs) = _as_btree(access_method);\n        assert!(iter_dir == IterationDirection::Forwards);\n        assert!(index.as_ref().unwrap().name == \"orders_customer_id_idx\");\n        assert!(constraint_refs.len() == 1);\n        let constraint = &table_constraints[TABLE_NO_ORDERS].constraints\n            [constraint_refs[0].eq.as_ref().unwrap().constraint_pos];\n        assert!(constraint.lhs_mask.contains_table(TABLE_NO_CUSTOMERS));\n\n        let access_method = &access_methods_arena[best_plan.data[2].1];\n        let (iter_dir, index, constraint_refs) = _as_btree(access_method);\n        assert!(iter_dir == IterationDirection::Forwards);\n        assert!(index.as_ref().unwrap().name == \"order_items_order_id_idx\");\n        assert!(constraint_refs.len() == 1);\n        let constraint = &table_constraints[TABLE_NO_ORDER_ITEMS].constraints\n            [constraint_refs[0].eq.as_ref().unwrap().constraint_pos];\n        assert!(constraint.lhs_mask.contains_table(TABLE_NO_ORDERS));\n    }\n\n    struct TestColumn {\n        name: String,\n        ty: Type,\n        is_rowid_alias: bool,\n    }\n\n    impl Default for TestColumn {\n        fn default() -> Self {\n            Self {\n                name: \"a\".to_string(),\n                ty: Type::Integer,\n                is_rowid_alias: false,\n            }\n        }\n    }\n\n    #[test]\n    fn test_join_order_three_tables_no_indexes() {\n        let t1 = _create_btree_table(\"t1\", _create_column_list(&[\"id\", \"foo\"], Type::Integer));\n        let t2 = _create_btree_table(\"t2\", _create_column_list(&[\"id\", \"foo\"], Type::Integer));\n        let t3 = _create_btree_table(\"t3\", _create_column_list(&[\"id\", \"foo\"], Type::Integer));\n\n        let mut table_id_counter = TableRefIdCounter::new();\n        let joined_tables = vec![\n            _create_table_reference(t1, None, table_id_counter.next()),\n            _create_table_reference(\n                t2,\n                Some(JoinInfo {\n                    join_type: JoinType::Inner,\n                    using: vec![],\n                    no_reorder: false,\n                }),\n                table_id_counter.next(),\n            ),\n            _create_table_reference(\n                t3,\n                Some(JoinInfo {\n                    join_type: JoinType::Inner,\n                    using: vec![],\n                    no_reorder: false,\n                }),\n                table_id_counter.next(),\n            ),\n        ];\n\n        let mut where_clause = vec![\n            // t2.foo = 42 (equality filter, more selective)\n            _create_binary_expr(\n                _create_column_expr(joined_tables[1].internal_id, 1, false), // table 1, column 1 (foo)\n                ast::Operator::Equals,\n                _create_numeric_literal(\"42\"),\n            ),\n            // t1.foo > 10 (inequality filter, less selective)\n            _create_binary_expr(\n                _create_column_expr(joined_tables[0].internal_id, 1, false), // table 0, column 1 (foo)\n                ast::Operator::Greater,\n                _create_numeric_literal(\"10\"),\n            ),\n        ];\n\n        let table_references = TableReferences::new(joined_tables, vec![]);\n        let available_indexes = HashMap::default();\n        let mut access_methods_arena = Vec::new();\n        let table_constraints = constraints_from_where_clause(\n            &where_clause,\n            &table_references,\n            &available_indexes,\n            &[],\n            &empty_schema(),\n            &DEFAULT_PARAMS,\n        )\n        .unwrap();\n\n        let base_table_rows = default_base_rows(table_references.joined_tables().len());\n        let schema = empty_schema();\n        let BestJoinOrderResult { best_plan, .. } = compute_best_join_order(\n            table_references.joined_tables(),\n            1.0,\n            None,\n            &table_constraints,\n            &base_table_rows,\n            &mut access_methods_arena,\n            &mut where_clause,\n            &[],\n            &[],\n            &DEFAULT_PARAMS,\n            &AnalyzeStats::default(),\n            &available_indexes,\n            &table_references,\n            &schema,\n        )\n        .unwrap()\n        .unwrap();\n\n        // Verify that t2 is chosen first due to its equality filter\n        assert_eq!(best_plan.table_numbers().next().unwrap(), 1);\n        // Verify table scan is used since there are no indexes\n        let access_method = &access_methods_arena[best_plan.data[0].1];\n        let (iter_dir, index, constraint_refs) = _as_btree(access_method);\n        assert!(constraint_refs.is_empty());\n        assert!(iter_dir == IterationDirection::Forwards);\n        assert!(index.is_none());\n        // Verify that t1 is chosen next due to its inequality filter\n        let access_method = &access_methods_arena[best_plan.data[1].1];\n        let (iter_dir, index, constraint_refs) = _as_btree(access_method);\n        assert!(constraint_refs.is_empty());\n        assert!(iter_dir == IterationDirection::Forwards);\n        assert!(index.is_none());\n        // Verify that t3 is chosen last due to no filters\n        let access_method = &access_methods_arena[best_plan.data[2].1];\n        let (iter_dir, index, constraint_refs) = _as_btree(access_method);\n        assert!(constraint_refs.is_empty());\n        assert!(iter_dir == IterationDirection::Forwards);\n        assert!(index.is_none());\n    }\n\n    #[test]\n    /// Test that [compute_best_join_order] chooses a \"fact table\" as the outer table,\n    /// when it has a foreign key to all dimension tables.\n    fn test_compute_best_join_order_star_schema() {\n        const NUM_DIM_TABLES: usize = 9;\n        const FACT_TABLE_IDX: usize = 9;\n\n        // Create fact table with foreign keys to all dimension tables\n        let mut fact_columns = vec![_create_column_rowid_alias(\"id\")];\n        for i in 0..NUM_DIM_TABLES {\n            fact_columns.push(_create_column_of_type(&format!(\"dim{i}_id\"), Type::Integer));\n        }\n        let fact_table = _create_btree_table(\"fact\", fact_columns);\n\n        // Create dimension tables, each with an id and value column\n        let dim_tables: Vec<_> = (0..NUM_DIM_TABLES)\n            .map(|i| {\n                _create_btree_table(\n                    &format!(\"dim{i}\"),\n                    vec![\n                        _create_column_rowid_alias(\"id\"),\n                        _create_column_of_type(\"value\", Type::Integer),\n                    ],\n                )\n            })\n            .collect();\n\n        let mut table_id_counter = TableRefIdCounter::new();\n        let joined_tables = {\n            let mut refs = vec![_create_table_reference(\n                dim_tables[0].clone(),\n                None,\n                table_id_counter.next(),\n            )];\n            refs.extend(dim_tables.iter().skip(1).map(|t| {\n                _create_table_reference(\n                    t.clone(),\n                    Some(JoinInfo {\n                        join_type: JoinType::Inner,\n                        using: vec![],\n                        no_reorder: false,\n                    }),\n                    table_id_counter.next(),\n                )\n            }));\n            refs.push(_create_table_reference(\n                fact_table,\n                Some(JoinInfo {\n                    join_type: JoinType::Inner,\n                    using: vec![],\n                    no_reorder: false,\n                }),\n                table_id_counter.next(),\n            ));\n            refs\n        };\n\n        let mut where_clause = vec![];\n\n        // Add join conditions between fact and each dimension table\n        for i in 0..NUM_DIM_TABLES {\n            let internal_id_fact = joined_tables[FACT_TABLE_IDX].internal_id;\n            let internal_id_other = joined_tables[i].internal_id;\n            where_clause.push(_create_binary_expr(\n                _create_column_expr(internal_id_fact, i + 1, false), // fact.dimX_id\n                ast::Operator::Equals,\n                _create_column_expr(internal_id_other, 0, true), // dimX.id\n            ));\n        }\n\n        let table_references = TableReferences::new(joined_tables, vec![]);\n        let mut access_methods_arena = Vec::new();\n        let available_indexes = HashMap::default();\n        let table_constraints = constraints_from_where_clause(\n            &where_clause,\n            &table_references,\n            &available_indexes,\n            &[],\n            &empty_schema(),\n            &DEFAULT_PARAMS,\n        )\n        .unwrap();\n\n        let base_table_rows = default_base_rows(table_references.joined_tables().len());\n        let schema = empty_schema();\n        let result = compute_best_join_order(\n            table_references.joined_tables(),\n            1.0,\n            None,\n            &table_constraints,\n            &base_table_rows,\n            &mut access_methods_arena,\n            &mut where_clause,\n            &[],\n            &[],\n            &DEFAULT_PARAMS,\n            &AnalyzeStats::default(),\n            &available_indexes,\n            &table_references,\n            &schema,\n        )\n        .unwrap();\n        assert!(result.is_some());\n        let BestJoinOrderResult { best_plan, .. } = result.unwrap();\n\n        // Expected optimal order: fact table as outer, with rowid seeks in any order on each dimension table\n        // Verify fact table is selected as the outer table as all the other tables can use SeekRowid\n        assert_eq!(\n            best_plan.table_numbers().next().unwrap(),\n            FACT_TABLE_IDX,\n            \"First table should be fact (table {}) due to available index, got table {} instead\",\n            FACT_TABLE_IDX,\n            best_plan.table_numbers().next().unwrap()\n        );\n\n        // Verify access methods\n        let access_method = &access_methods_arena[best_plan.data[0].1];\n        let (iter_dir, index, constraint_refs) = _as_btree(access_method);\n        assert!(iter_dir == IterationDirection::Forwards);\n        assert!(index.is_none());\n        assert!(constraint_refs.is_empty());\n\n        for (table_number, access_method_index) in best_plan.data.iter().skip(1) {\n            let access_method = &access_methods_arena[*access_method_index];\n            let (iter_dir, index, constraint_refs) = _as_btree(access_method);\n            assert!(iter_dir == IterationDirection::Forwards);\n            assert!(index.is_none());\n            assert!(constraint_refs.len() == 1);\n            let constraint = &table_constraints[*table_number].constraints\n                [constraint_refs[0].eq.as_ref().unwrap().constraint_pos];\n            assert!(constraint.lhs_mask.contains_table(FACT_TABLE_IDX));\n            assert!(constraint.operator.as_ast_operator() == Some(ast::Operator::Equals));\n        }\n    }\n\n    #[test]\n    /// Test that [compute_best_join_order] figures out that the tables form a \"linked list\" pattern\n    /// where a column in each table points to an indexed column in the next table,\n    /// and chooses the best order based on that.\n    fn test_compute_best_join_order_linked_list() {\n        const NUM_TABLES: usize = 5;\n\n        // Create tables t1 -> t2 -> t3 -> t4 -> t5 where there is a foreign key from each table to the next\n        let mut tables = Vec::with_capacity(NUM_TABLES);\n        for i in 0..NUM_TABLES {\n            let mut columns = vec![_create_column_rowid_alias(\"id\")];\n            if i < NUM_TABLES - 1 {\n                columns.push(_create_column_of_type(\"next_id\", Type::Integer));\n            }\n            tables.push(_create_btree_table(&format!(\"t{}\", i + 1), columns));\n        }\n\n        let available_indexes = HashMap::default();\n\n        let mut table_id_counter = TableRefIdCounter::new();\n        // Create table references\n        let joined_tables: Vec<_> = tables\n            .iter()\n            .map(|t| _create_table_reference(t.clone(), None, table_id_counter.next()))\n            .collect();\n\n        // Create where clause linking each table to the next\n        let mut where_clause = Vec::new();\n        for i in 0..NUM_TABLES - 1 {\n            let internal_id_left = joined_tables[i].internal_id;\n            let internal_id_right = joined_tables[i + 1].internal_id;\n            where_clause.push(_create_binary_expr(\n                _create_column_expr(internal_id_left, 1, false), // ti.next_id\n                ast::Operator::Equals,\n                _create_column_expr(internal_id_right, 0, true), // t(i+1).id\n            ));\n        }\n\n        let table_references = TableReferences::new(joined_tables, vec![]);\n        let mut access_methods_arena = Vec::new();\n        let table_constraints = constraints_from_where_clause(\n            &where_clause,\n            &table_references,\n            &available_indexes,\n            &[],\n            &empty_schema(),\n            &DEFAULT_PARAMS,\n        )\n        .unwrap();\n\n        // Run the optimizer\n        let base_table_rows = default_base_rows(table_references.joined_tables().len());\n        let schema = empty_schema();\n        let BestJoinOrderResult { best_plan, .. } = compute_best_join_order(\n            table_references.joined_tables(),\n            1.0,\n            None,\n            &table_constraints,\n            &base_table_rows,\n            &mut access_methods_arena,\n            &mut where_clause,\n            &[],\n            &[],\n            &DEFAULT_PARAMS,\n            &AnalyzeStats::default(),\n            &available_indexes,\n            &table_references,\n            &schema,\n        )\n        .unwrap()\n        .unwrap();\n\n        // Verify the join order is exactly t1 -> t2 -> t3 -> t4 -> t5\n        for i in 0..NUM_TABLES {\n            assert_eq!(\n                best_plan.table_numbers().nth(i).unwrap(),\n                i,\n                \"Expected table {} at position {}, got table {} instead\",\n                i,\n                i,\n                best_plan.table_numbers().nth(i).unwrap()\n            );\n        }\n\n        // Verify access methods:\n        // - First table should use Table scan\n        let access_method = &access_methods_arena[best_plan.data[0].1];\n        let (iter_dir, index, constraint_refs) = _as_btree(access_method);\n        assert!(iter_dir == IterationDirection::Forwards);\n        assert!(index.is_none());\n        assert!(constraint_refs.is_empty());\n\n        // all of the rest should use rowid equality\n        for (i, table_constraints) in table_constraints\n            .iter()\n            .enumerate()\n            .take(NUM_TABLES)\n            .skip(1)\n        {\n            let access_method = &access_methods_arena[best_plan.data[i].1];\n            let (iter_dir, index, constraint_refs) = _as_btree(access_method);\n            assert!(iter_dir == IterationDirection::Forwards);\n            assert!(index.is_none());\n            assert!(constraint_refs.len() == 1);\n            let constraint = &table_constraints.constraints\n                [constraint_refs[0].eq.as_ref().unwrap().constraint_pos];\n            assert!(constraint.lhs_mask.contains_table(i - 1));\n            assert!(constraint.operator.as_ast_operator() == Some(ast::Operator::Equals));\n        }\n    }\n\n    #[test]\n    /// Test that [compute_best_join_order] figures out that the index can't be used when only the second column is referenced\n    fn test_index_second_column_only() {\n        let mut joined_tables = Vec::new();\n\n        let mut table_id_counter = TableRefIdCounter::new();\n\n        // Create a table with two columns\n        let table = _create_btree_table(\"t1\", _create_column_list(&[\"x\", \"y\"], Type::Integer));\n\n        // Create a two-column index on (x,y)\n        let index = Arc::new(Index {\n            name: \"idx_xy\".to_string(),\n            table_name: \"t1\".to_string(),\n            where_clause: None,\n            columns: vec![\n                IndexColumn {\n                    name: \"x\".to_string(),\n                    order: SortOrder::Asc,\n                    pos_in_table: 0,\n                    collation: None,\n                    default: None,\n                    expr: None,\n                },\n                IndexColumn {\n                    name: \"y\".to_string(),\n                    order: SortOrder::Asc,\n                    pos_in_table: 1,\n                    collation: None,\n                    default: None,\n                    expr: None,\n                },\n            ],\n            unique: false,\n            root_page: 2,\n            ephemeral: false,\n            has_rowid: true,\n            index_method: None,\n            on_conflict: None,\n        });\n\n        let mut available_indexes = HashMap::default();\n        available_indexes.insert(\"t1\".to_string(), VecDeque::from([index]));\n\n        let table = Table::BTree(table);\n        joined_tables.push(JoinedTable {\n            op: Operation::default_scan_for(&table),\n            table,\n            internal_id: table_id_counter.next(),\n            identifier: \"t1\".to_string(),\n            join_info: None,\n            col_used_mask: ColumnUsedMask::default(),\n            column_use_counts: Vec::new(),\n            expression_index_usages: Vec::new(),\n            database_id: 0,\n            indexed: None,\n        });\n\n        // Create where clause that only references second column\n        let mut where_clause = vec![WhereTerm {\n            expr: Expr::Binary(\n                Box::new(Expr::Column {\n                    database: None,\n                    table: joined_tables[0].internal_id,\n                    column: 1,\n                    is_rowid_alias: false,\n                }),\n                ast::Operator::Equals,\n                Box::new(Expr::Literal(ast::Literal::Numeric(5.to_string()))),\n            ),\n            from_outer_join: None,\n            consumed: false,\n        }];\n\n        let table_references = TableReferences::new(joined_tables, vec![]);\n        let mut access_methods_arena = Vec::new();\n        let table_constraints = constraints_from_where_clause(\n            &where_clause,\n            &table_references,\n            &available_indexes,\n            &[],\n            &empty_schema(),\n            &DEFAULT_PARAMS,\n        )\n        .unwrap();\n\n        let base_table_rows = default_base_rows(table_references.joined_tables().len());\n        let schema = empty_schema();\n        let BestJoinOrderResult { best_plan, .. } = compute_best_join_order(\n            table_references.joined_tables(),\n            1.0,\n            None,\n            &table_constraints,\n            &base_table_rows,\n            &mut access_methods_arena,\n            &mut where_clause,\n            &[],\n            &[],\n            &DEFAULT_PARAMS,\n            &AnalyzeStats::default(),\n            &available_indexes,\n            &table_references,\n            &schema,\n        )\n        .unwrap()\n        .unwrap();\n\n        // Verify access method is a scan, not a seek, because the index can't be used when only the second column is referenced\n        let access_method = &access_methods_arena[best_plan.data[0].1];\n        let (_, _, constraint_refs) = _as_btree(access_method);\n        assert!(constraint_refs.is_empty());\n    }\n\n    #[test]\n    /// Test that an index with a gap in referenced columns (e.g. index on (a,b,c), where clause on a and c)\n    /// only uses the prefix before the gap.\n    fn test_index_skips_middle_column() {\n        let mut table_id_counter = TableRefIdCounter::new();\n        let mut joined_tables = Vec::new();\n        let mut available_indexes = HashMap::default();\n\n        let columns = _create_column_list(&[\"c1\", \"c2\", \"c3\"], Type::Integer);\n        let table = _create_btree_table(\"t1\", columns);\n        let index = Arc::new(Index {\n            name: \"idx1\".to_string(),\n            table_name: \"t1\".to_string(),\n            where_clause: None,\n            columns: vec![\n                IndexColumn {\n                    name: \"c1\".to_string(),\n                    order: SortOrder::Asc,\n                    pos_in_table: 0,\n                    collation: None,\n                    default: None,\n                    expr: None,\n                },\n                IndexColumn {\n                    name: \"c2\".to_string(),\n                    order: SortOrder::Asc,\n                    pos_in_table: 1,\n                    collation: None,\n                    default: None,\n                    expr: None,\n                },\n                IndexColumn {\n                    name: \"c3\".to_string(),\n                    order: SortOrder::Asc,\n                    pos_in_table: 2,\n                    collation: None,\n                    default: None,\n                    expr: None,\n                },\n            ],\n            unique: false,\n            root_page: 2,\n            ephemeral: false,\n            has_rowid: true,\n            index_method: None,\n            on_conflict: None,\n        });\n        available_indexes.insert(\"t1\".to_string(), VecDeque::from([index]));\n\n        let table = Table::BTree(table);\n        joined_tables.push(JoinedTable {\n            op: Operation::default_scan_for(&table),\n            table,\n            internal_id: table_id_counter.next(),\n            identifier: \"t1\".to_string(),\n            join_info: None,\n            col_used_mask: ColumnUsedMask::default(),\n            column_use_counts: Vec::new(),\n            expression_index_usages: Vec::new(),\n            database_id: 0,\n            indexed: None,\n        });\n\n        // Create where clause that references first and third columns\n        let mut where_clause = vec![\n            WhereTerm {\n                expr: Expr::Binary(\n                    Box::new(Expr::Column {\n                        database: None,\n                        table: joined_tables[0].internal_id,\n                        column: 0, // c1\n                        is_rowid_alias: false,\n                    }),\n                    ast::Operator::Equals,\n                    Box::new(Expr::Literal(ast::Literal::Numeric(5.to_string()))),\n                ),\n                from_outer_join: None,\n                consumed: false,\n            },\n            WhereTerm {\n                expr: Expr::Binary(\n                    Box::new(Expr::Column {\n                        database: None,\n                        table: joined_tables[0].internal_id,\n                        column: 2, // c3\n                        is_rowid_alias: false,\n                    }),\n                    ast::Operator::Equals,\n                    Box::new(Expr::Literal(ast::Literal::Numeric(7.to_string()))),\n                ),\n                from_outer_join: None,\n                consumed: false,\n            },\n        ];\n\n        let table_references = TableReferences::new(joined_tables, vec![]);\n        let mut access_methods_arena = Vec::new();\n        let table_constraints = constraints_from_where_clause(\n            &where_clause,\n            &table_references,\n            &available_indexes,\n            &[],\n            &empty_schema(),\n            &DEFAULT_PARAMS,\n        )\n        .unwrap();\n\n        let base_table_rows = default_base_rows(table_references.joined_tables().len());\n        let schema = empty_schema();\n        let BestJoinOrderResult { best_plan, .. } = compute_best_join_order(\n            table_references.joined_tables(),\n            1.0,\n            None,\n            &table_constraints,\n            &base_table_rows,\n            &mut access_methods_arena,\n            &mut where_clause,\n            &[],\n            &[],\n            &DEFAULT_PARAMS,\n            &AnalyzeStats::default(),\n            &available_indexes,\n            &table_references,\n            &schema,\n        )\n        .unwrap()\n        .unwrap();\n\n        // Verify access method is a seek, and only uses the first column of the index\n        let access_method = &access_methods_arena[best_plan.data[0].1];\n        let (_, index, constraint_refs) = _as_btree(access_method);\n        assert!(index.as_ref().is_some_and(|i| i.name == \"idx1\"));\n        assert!(constraint_refs.len() == 1);\n        let constraint = &table_constraints[0].constraints\n            [constraint_refs[0].eq.as_ref().unwrap().constraint_pos];\n        assert!(constraint.operator.as_ast_operator() == Some(ast::Operator::Equals));\n        assert!(constraint.table_col_pos == Some(0)); // c1\n    }\n\n    #[test]\n    /// Test that an index seek stops after a range operator.\n    /// e.g. index on (a,b,c), where clause a=1, b>2, c=3. Only a and b should be used for seek.\n    fn test_index_stops_at_range_operator() {\n        let mut table_id_counter = TableRefIdCounter::new();\n        let mut joined_tables = Vec::new();\n        let mut available_indexes = HashMap::default();\n\n        let columns = _create_column_list(&[\"c1\", \"c2\", \"c3\"], Type::Integer);\n        let table = _create_btree_table(\"t1\", columns);\n        let index = Arc::new(Index {\n            name: \"idx1\".to_string(),\n            table_name: \"t1\".to_string(),\n            where_clause: None,\n            columns: vec![\n                IndexColumn {\n                    name: \"c1\".to_string(),\n                    order: SortOrder::Asc,\n                    pos_in_table: 0,\n                    collation: None,\n                    default: None,\n                    expr: None,\n                },\n                IndexColumn {\n                    name: \"c2\".to_string(),\n                    order: SortOrder::Asc,\n                    pos_in_table: 1,\n                    collation: None,\n                    default: None,\n                    expr: None,\n                },\n                IndexColumn {\n                    name: \"c3\".to_string(),\n                    order: SortOrder::Asc,\n                    pos_in_table: 2,\n                    collation: None,\n                    default: None,\n                    expr: None,\n                },\n            ],\n            root_page: 2,\n            ephemeral: false,\n            has_rowid: true,\n            unique: false,\n            index_method: None,\n            on_conflict: None,\n        });\n        available_indexes.insert(\"t1\".to_string(), VecDeque::from([index]));\n\n        let table = Table::BTree(table);\n        joined_tables.push(JoinedTable {\n            op: Operation::default_scan_for(&table),\n            table,\n            internal_id: table_id_counter.next(),\n            identifier: \"t1\".to_string(),\n            join_info: None,\n            col_used_mask: ColumnUsedMask::default(),\n            column_use_counts: Vec::new(),\n            expression_index_usages: Vec::new(),\n            database_id: 0,\n            indexed: None,\n        });\n\n        // Create where clause: c1 = 5 AND c2 > 10 AND c3 = 7\n        let mut where_clause = vec![\n            WhereTerm {\n                expr: Expr::Binary(\n                    Box::new(Expr::Column {\n                        database: None,\n                        table: joined_tables[0].internal_id,\n                        column: 0, // c1\n                        is_rowid_alias: false,\n                    }),\n                    ast::Operator::Equals,\n                    Box::new(Expr::Literal(ast::Literal::Numeric(5.to_string()))),\n                ),\n                from_outer_join: None,\n                consumed: false,\n            },\n            WhereTerm {\n                expr: Expr::Binary(\n                    Box::new(Expr::Column {\n                        database: None,\n                        table: joined_tables[0].internal_id,\n                        column: 1, // c2\n                        is_rowid_alias: false,\n                    }),\n                    ast::Operator::Greater,\n                    Box::new(Expr::Literal(ast::Literal::Numeric(10.to_string()))),\n                ),\n                from_outer_join: None,\n                consumed: false,\n            },\n            WhereTerm {\n                expr: Expr::Binary(\n                    Box::new(Expr::Column {\n                        database: None,\n                        table: joined_tables[0].internal_id,\n                        column: 2, // c3\n                        is_rowid_alias: false,\n                    }),\n                    ast::Operator::Equals,\n                    Box::new(Expr::Literal(ast::Literal::Numeric(7.to_string()))),\n                ),\n                from_outer_join: None,\n                consumed: false,\n            },\n        ];\n\n        let table_references = TableReferences::new(joined_tables, vec![]);\n        let mut access_methods_arena = Vec::new();\n        let table_constraints = constraints_from_where_clause(\n            &where_clause,\n            &table_references,\n            &available_indexes,\n            &[],\n            &empty_schema(),\n            &DEFAULT_PARAMS,\n        )\n        .unwrap();\n\n        let base_table_rows = default_base_rows(table_references.joined_tables().len());\n        let schema = empty_schema();\n        let BestJoinOrderResult { best_plan, .. } = compute_best_join_order(\n            table_references.joined_tables(),\n            1.0,\n            None,\n            &table_constraints,\n            &base_table_rows,\n            &mut access_methods_arena,\n            &mut where_clause,\n            &[],\n            &[],\n            &DEFAULT_PARAMS,\n            &AnalyzeStats::default(),\n            &available_indexes,\n            &table_references,\n            &schema,\n        )\n        .unwrap()\n        .unwrap();\n\n        // Verify access method is a seek, and uses the first two columns of the index.\n        // The third column can't be used because the second is a range query.\n        let access_method = &access_methods_arena[best_plan.data[0].1];\n        let (_, index, constraint_refs) = _as_btree(access_method);\n        assert!(index.as_ref().is_some_and(|i| i.name == \"idx1\"));\n        assert!(constraint_refs.len() == 2);\n        let constraint = &table_constraints[0].constraints\n            [constraint_refs[0].eq.as_ref().unwrap().constraint_pos];\n        assert!(constraint.operator.as_ast_operator() == Some(ast::Operator::Equals));\n        assert!(constraint.table_col_pos == Some(0)); // c1\n        let constraint = &table_constraints[0].constraints[constraint_refs[1].lower_bound.unwrap()];\n        assert!(constraint.operator.as_ast_operator() == Some(ast::Operator::Greater));\n        assert!(constraint.table_col_pos == Some(1)); // c2\n    }\n\n    fn _create_column(c: &TestColumn) -> Column {\n        Column::new(\n            Some(c.name.clone()),\n            c.ty.to_string(),\n            None,\n            None,\n            c.ty,\n            None,\n            ColDef {\n                primary_key: false,\n                rowid_alias: c.is_rowid_alias,\n                ..Default::default()\n            },\n        )\n    }\n    fn _create_column_of_type(name: &str, ty: Type) -> Column {\n        _create_column(&TestColumn {\n            name: name.to_string(),\n            ty,\n            is_rowid_alias: false,\n        })\n    }\n\n    fn _create_column_list(names: &[&str], ty: Type) -> Vec<Column> {\n        names\n            .iter()\n            .map(|name| _create_column_of_type(name, ty))\n            .collect()\n    }\n\n    fn _create_column_rowid_alias(name: &str) -> Column {\n        _create_column(&TestColumn {\n            name: name.to_string(),\n            ty: Type::Integer,\n            is_rowid_alias: true,\n        })\n    }\n\n    /// Creates a BTreeTable with the given name and columns\n    fn _create_btree_table(name: &str, columns: Vec<Column>) -> Arc<BTreeTable> {\n        Arc::new(BTreeTable {\n            root_page: 1, // Page number doesn't matter for tests\n            name: name.to_string(),\n            has_autoincrement: false,\n            primary_key_columns: vec![],\n            columns,\n            has_rowid: true,\n            is_strict: false,\n            unique_sets: vec![],\n            foreign_keys: vec![],\n            check_constraints: vec![],\n            rowid_alias_conflict_clause: None,\n        })\n    }\n\n    /// Creates a TableReference for a BTreeTable\n    fn _create_table_reference(\n        table: Arc<BTreeTable>,\n        join_info: Option<JoinInfo>,\n        internal_id: TableInternalId,\n    ) -> JoinedTable {\n        let name = table.name.clone();\n        let table = Table::BTree(table);\n        JoinedTable {\n            op: Operation::default_scan_for(&table),\n            table,\n            identifier: name,\n            internal_id,\n            join_info,\n            col_used_mask: ColumnUsedMask::default(),\n            column_use_counts: Vec::new(),\n            expression_index_usages: Vec::new(),\n            database_id: 0,\n            indexed: None,\n        }\n    }\n\n    /// Creates a column expression\n    fn _create_column_expr(table: TableInternalId, column: usize, is_rowid_alias: bool) -> Expr {\n        Expr::Column {\n            database: None,\n            table,\n            column,\n            is_rowid_alias,\n        }\n    }\n\n    /// Creates a binary expression for a WHERE clause\n    fn _create_binary_expr(lhs: Expr, op: Operator, rhs: Expr) -> WhereTerm {\n        WhereTerm {\n            expr: Expr::Binary(Box::new(lhs), op, Box::new(rhs)),\n            from_outer_join: None,\n            consumed: false,\n        }\n    }\n\n    /// Creates a numeric literal expression\n    fn _create_numeric_literal(value: &str) -> Expr {\n        Expr::Literal(ast::Literal::Numeric(value.to_string()))\n    }\n\n    fn _as_btree(\n        access_method: &AccessMethod,\n    ) -> (\n        IterationDirection,\n        Option<Arc<Index>>,\n        &'_ [RangeConstraintRef],\n    ) {\n        match &access_method.params {\n            AccessMethodParams::BTreeTable {\n                iter_dir,\n                index,\n                constraint_refs,\n            } => (*iter_dir, index.clone(), constraint_refs),\n            _ => panic!(\"expected BTreeTable access method\"),\n        }\n    }\n\n    #[test]\n    /// Test that when an index is available on the join column, the optimizer prefers\n    /// index lookup over hash join.\n    fn test_prefer_index_lookup_over_hash_join() {\n        // CREATE TABLE t1(a,b,c);\n        // CREATE TABLE t2(a,b,c);\n        // CREATE INDEX idx_t2_a ON t2(a);\n        // SELECT * FROM t1 JOIN t2 ON t1.a = t2.a;\n        // Expected: SCAN t1, SEARCH t2 USING INDEX idx_t2_a (a=?)\n        // Not: HASH JOIN\n\n        let t1 = _create_btree_table(\"t1\", _create_column_list(&[\"a\", \"b\", \"c\"], Type::Integer));\n        let t2 = _create_btree_table(\"t2\", _create_column_list(&[\"a\", \"b\", \"c\"], Type::Integer));\n\n        let mut table_id_counter = TableRefIdCounter::new();\n        let joined_tables = vec![\n            _create_table_reference(t1, None, table_id_counter.next()),\n            _create_table_reference(\n                t2,\n                Some(JoinInfo {\n                    join_type: JoinType::Inner,\n                    using: vec![],\n                    no_reorder: false,\n                }),\n                table_id_counter.next(),\n            ),\n        ];\n\n        const TABLE1: usize = 0;\n        const TABLE2: usize = 1;\n\n        // Index on t2.a\n        let mut available_indexes = HashMap::default();\n        let index_t2_a = Arc::new(Index {\n            name: \"idx_t2_a\".to_string(),\n            table_name: \"t2\".to_string(),\n            where_clause: None,\n            columns: vec![IndexColumn {\n                name: \"a\".to_string(),\n                order: SortOrder::Asc,\n                pos_in_table: 0,\n                collation: None,\n                default: None,\n                expr: None,\n            }],\n            unique: false, // Non-unique index\n            ephemeral: false,\n            root_page: 2,\n            has_rowid: true,\n            index_method: None,\n            on_conflict: None,\n        });\n        available_indexes.insert(\"t2\".to_string(), VecDeque::from([index_t2_a]));\n\n        // WHERE t1.a = t2.a\n        let mut where_clause = vec![_create_binary_expr(\n            _create_column_expr(joined_tables[TABLE1].internal_id, 0, false), // t1.a\n            ast::Operator::Equals,\n            _create_column_expr(joined_tables[TABLE2].internal_id, 0, false), // t2.a\n        )];\n\n        let table_references = TableReferences::new(joined_tables, vec![]);\n        let mut access_methods_arena = Vec::new();\n        let table_constraints = constraints_from_where_clause(\n            &where_clause,\n            &table_references,\n            &available_indexes,\n            &[],\n            &empty_schema(),\n            &DEFAULT_PARAMS,\n        )\n        .unwrap();\n\n        let base_table_rows = default_base_rows(table_references.joined_tables().len());\n        let schema = empty_schema();\n        let result = compute_best_join_order(\n            table_references.joined_tables(),\n            1.0,\n            None,\n            &table_constraints,\n            &base_table_rows,\n            &mut access_methods_arena,\n            &mut where_clause,\n            &[],\n            &[],\n            &DEFAULT_PARAMS,\n            &AnalyzeStats::default(),\n            &available_indexes,\n            &table_references,\n            &schema,\n        )\n        .unwrap();\n        assert!(result.is_some());\n        let BestJoinOrderResult { best_plan, .. } = result.unwrap();\n\n        // Expected: t1 first (scan), t2 second (index seek)\n        assert_eq!(\n            best_plan.table_numbers().collect::<Vec<_>>(),\n            vec![TABLE1, TABLE2],\n            \"Expected join order [t1, t2] to use index on t2.a\"\n        );\n\n        // t1 should use table scan (no constraints)\n        let access_method_t1 = &access_methods_arena[best_plan.data[0].1];\n        let (_, _, constraint_refs_t1) = _as_btree(access_method_t1);\n        assert!(\n            constraint_refs_t1.is_empty(),\n            \"t1 should use table scan with no constraints\"\n        );\n\n        // t2 should use index seek, NOT hash join\n        let access_method_t2 = &access_methods_arena[best_plan.data[1].1];\n        match &access_method_t2.params {\n            AccessMethodParams::BTreeTable {\n                index,\n                constraint_refs,\n                ..\n            } => {\n                assert!(\n                    index.is_some(),\n                    \"t2 should use index idx_t2_a, not a hash join\"\n                );\n                assert_eq!(\n                    index.as_ref().unwrap().name,\n                    \"idx_t2_a\",\n                    \"t2 should use index idx_t2_a\"\n                );\n                assert!(\n                    !constraint_refs.is_empty(),\n                    \"t2 should have constraints for index seek\"\n                );\n            }\n            AccessMethodParams::HashJoin { .. } => {\n                panic!(\"Expected index lookup on t2, but got hash join instead\");\n            }\n            _ => panic!(\"Unexpected access method for t2\"),\n        }\n    }\n}\n"
  },
  {
    "path": "core/translate/optimizer/lift_common_subexpressions.rs",
    "content": "use crate::{turso_assert, turso_assert_greater_than};\nuse turso_parser::ast::{Expr, Operator};\n\nuse crate::{\n    translate::{expr::unwrap_parens_owned, plan::WhereTerm},\n    util::exprs_are_equivalent,\n    Result,\n};\n/// Lifts shared conjuncts (ANDs) from sibling OR terms.\n/// For example, given:\n/// (a AND b AND c AND d)\n///     OR\n/// (a AND b AND e AND f)\n/// Notice that both OR terms contain the same conjuncts (a AND b).\n///\n/// This function will lift the common conjuncts (a AND b) to the top level,\n/// resulting in a Vec of three [WhereTerm]s like:\n/// 1. (c AND d) OR (e AND f)\n/// 2. a,\n/// 3. b,\n///\n/// where `a` and `b` become separate WhereTerms, and the original WhereTerm\n/// is updated to `(c AND d) OR (e AND f)`.\n///\n/// This optimization is important because we rely on individual [WhereTerm]s\n/// for index selection. Imagine an index on (a,b) -- with our current optimizer\n/// we wouldn't be able to use the index based on the original [WhereTerm]s, but\n/// if we can lift [a,b] to the top level, we can use the index.\n///\n/// This function is horribly inefficient atm, but it at least makes certain\n/// less trivial queries (e.g. perf/tpc-h/queries/19.sql) finish reasonably fast.\npub(crate) fn lift_common_subexpressions_from_binary_or_terms(\n    where_clause: &mut Vec<WhereTerm>,\n) -> Result<()> {\n    let mut i = 0;\n    while i < where_clause.len() {\n        if !matches!(where_clause[i].expr, Expr::Binary(_, Operator::Or, _)) {\n            // Not an OR term, skip.\n            i += 1;\n            continue;\n        }\n        let term_expr_owned = where_clause[i].expr.clone(); // Own the expression for flattening\n        let term_from_outer_join = where_clause[i].from_outer_join; // This needs to be remembered for the new WhereTerms\n\n        // e.g. a OR b OR c becomes effectively OR [a,b,c].\n        let or_operands = flatten_or_expr_owned(term_expr_owned)?;\n\n        turso_assert!(or_operands.len() > 1);\n\n        // Each OR operand is potentially an AND chain, e.g.\n        // (a AND b) OR (c AND d).\n        // Flatten them.\n        // It's safe to remove parentheses with `unwrap_parens_owned` because\n        // we will add them back once we reconstruct the OR term's child expressions.\n        // e.g. (a AND b) OR (c AND d) becomes effectively AND [[a,b], [c,d]].\n        let all_or_operands_conjunct_lists: Vec<(Vec<Expr>, usize)> = or_operands\n            .into_iter()\n            .map(|expr| {\n                let (expr, paren_count) = unwrap_parens_owned(expr)?;\n                Ok((flatten_and_expr_owned(expr)?, paren_count))\n            })\n            .collect::<Result<Vec<_>>>()?;\n\n        // Find common conjuncts across all OR branches.\n        // Initialize with conjuncts from the first OR branch.\n        // We clone because `common_conjuncts_accumulator` will be modified.\n        let mut common_conjuncts_accumulator = all_or_operands_conjunct_lists[0].0.clone();\n\n        for (other_conjunct_list, _) in all_or_operands_conjunct_lists.iter().skip(1) {\n            // Retain only those expressions in `common_conjuncts_accumulator`\n            // that are also present in `other_conjunct_list`.\n            common_conjuncts_accumulator.retain(|common_expr| {\n                other_conjunct_list\n                    .iter()\n                    .any(|expr| exprs_are_equivalent(common_expr, expr))\n            });\n        }\n\n        // If no common conjuncts were found, move to the next WhereTerm.\n        if common_conjuncts_accumulator.is_empty() {\n            i += 1;\n            continue;\n        }\n\n        // We found common conjuncts. Let's remove the common ones and rebuild the OR branches.\n        // E.g. (a AND b) OR (a AND c) -> (b OR c) AND a.\n        let mut new_or_operands_for_original_term = Vec::new();\n        let mut found_non_empty_or_branches = false;\n        for (mut conjunct_list_for_or_branch, mut num_unwrapped_parens) in\n            all_or_operands_conjunct_lists.into_iter()\n        {\n            // Remove the common conjuncts from this specific OR branch's list of conjuncts.\n            conjunct_list_for_or_branch\n                .retain(|expr_in_list| !common_conjuncts_accumulator.contains(expr_in_list));\n\n            if conjunct_list_for_or_branch.is_empty() {\n                // If any of the OR branches are empty, we can remove the entire OR term.\n                // E.g. (a AND b) OR (a) OR (a AND c) just becomes a.\n                found_non_empty_or_branches = true;\n                break;\n            }\n\n            // Rebuild this OR branch from its remaining (non-common) conjuncts.\n            // If we unwrapped parentheses before, let's add them back.\n            let mut top_level_expr = rebuild_and_expr_from_list(conjunct_list_for_or_branch);\n            while num_unwrapped_parens > 0 {\n                top_level_expr = Expr::Parenthesized(vec![top_level_expr.into()]);\n                num_unwrapped_parens -= 1;\n            }\n            new_or_operands_for_original_term.push(top_level_expr);\n        }\n\n        if found_non_empty_or_branches {\n            // If we found an empty OR branch, we can remove the entire OR term.\n            // E.g. (a AND b) OR (a) OR (a AND c) just becomes a.\n            where_clause[i].consumed = true;\n        } else {\n            turso_assert_greater_than!(new_or_operands_for_original_term.len(), 1);\n            // Update the original WhereTerm's expression with the new OR structure (without common parts).\n            where_clause[i].expr = rebuild_or_expr_from_list(new_or_operands_for_original_term);\n        }\n\n        // Add the lifted common conjuncts as new, separate WhereTerms.\n        for common_expr_to_add in common_conjuncts_accumulator {\n            where_clause.push(WhereTerm {\n                expr: common_expr_to_add,\n                from_outer_join: term_from_outer_join,\n                consumed: false,\n            });\n        }\n\n        // Simply incrementing i is correct because we added new WhereTerms at the end.\n        i += 1;\n    }\n    Ok(())\n}\n\n/// Flatten an ast::Expr::Binary(lhs, OR, rhs) into a list of disjuncts.\nfn flatten_or_expr_owned(expr: Expr) -> Result<Vec<Expr>> {\n    let Expr::Binary(lhs, Operator::Or, rhs) = expr else {\n        return Ok(vec![expr]);\n    };\n    let mut flattened = flatten_or_expr_owned(*lhs)?;\n    flattened.extend(flatten_or_expr_owned(*rhs)?);\n    Ok(flattened)\n}\n\n/// Flatten an ast::Expr::Binary(lhs, AND, rhs) into a list of conjuncts.\nfn flatten_and_expr_owned(expr: Expr) -> Result<Vec<Expr>> {\n    let Expr::Binary(lhs, Operator::And, rhs) = expr else {\n        return Ok(vec![expr]);\n    };\n    let mut flattened = flatten_and_expr_owned(*lhs)?;\n    flattened.extend(flatten_and_expr_owned(*rhs)?);\n    Ok(flattened)\n}\n\n/// Rebuild an ast::Expr::Binary(lhs, AND, rhs) for a list of conjuncts.\nfn rebuild_and_expr_from_list(mut conjuncts: Vec<Expr>) -> Expr {\n    turso_assert!(!conjuncts.is_empty());\n\n    if conjuncts.len() == 1 {\n        return conjuncts.pop().unwrap();\n    }\n\n    let mut current_expr = conjuncts.remove(0);\n    for next_expr in conjuncts {\n        current_expr = Expr::Binary(Box::new(current_expr), Operator::And, Box::new(next_expr));\n    }\n    current_expr\n}\n\n/// Rebuild an ast::Expr::Binary(lhs, OR, rhs) for a list of operands.\nfn rebuild_or_expr_from_list(mut operands: Vec<Expr>) -> Expr {\n    turso_assert!(!operands.is_empty());\n\n    if operands.len() == 1 {\n        return operands.pop().unwrap();\n    }\n\n    let mut current_expr = operands.remove(0);\n    for next_expr in operands {\n        current_expr = Expr::Binary(Box::new(current_expr), Operator::Or, Box::new(next_expr));\n    }\n    current_expr\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::translate::plan::WhereTerm;\n    use turso_parser::ast::{self, Expr, Literal, Operator, TableInternalId};\n\n    #[test]\n    fn test_lift_common_subexpressions() -> Result<()> {\n        // SELECT * FROM t WHERE (a = 1 and x = 1 and b = 1) OR (a = 1 and y = 1 and b = 1)\n        // should be rewritten to:\n        // SELECT * FROM t WHERE (x = 1 OR y = 1) and a = 1 and b = 1\n\n        // assume the table has 4 columns: a, b, x, y\n        let a_expr = Expr::Binary(\n            Box::new(Expr::Column {\n                database: None,\n                table: TableInternalId::default(),\n                column: 0,\n                is_rowid_alias: false,\n            }),\n            Operator::Equals,\n            Box::new(Expr::Literal(Literal::Numeric(\"1\".to_string()))),\n        );\n\n        let b_expr = Expr::Binary(\n            Box::new(Expr::Column {\n                database: None,\n                table: TableInternalId::default(),\n                column: 1,\n                is_rowid_alias: false,\n            }),\n            Operator::Equals,\n            Box::new(Expr::Literal(Literal::Numeric(\"1\".to_string()))),\n        );\n\n        let x_expr = Expr::Binary(\n            Box::new(Expr::Column {\n                database: None,\n                table: TableInternalId::default(),\n                column: 2,\n                is_rowid_alias: false,\n            }),\n            Operator::Equals,\n            Box::new(Expr::Literal(Literal::Numeric(\"1\".to_string()))),\n        );\n\n        let y_expr = Expr::Binary(\n            Box::new(Expr::Column {\n                database: None,\n                table: TableInternalId::default(),\n                column: 3,\n                is_rowid_alias: false,\n            }),\n            Operator::Equals,\n            Box::new(Expr::Literal(Literal::Numeric(\"1\".to_string()))),\n        );\n\n        // Create (a = 1 AND x = 1 AND b = 1) OR (a = 1 AND y = 1 AND b = 1)\n        let or_expr = Expr::Binary(\n            Box::new(ast::Expr::Parenthesized(vec![rebuild_and_expr_from_list(\n                vec![a_expr.clone(), x_expr.clone(), b_expr.clone()],\n            )\n            .into()])),\n            Operator::Or,\n            Box::new(ast::Expr::Parenthesized(vec![rebuild_and_expr_from_list(\n                vec![a_expr.clone(), y_expr.clone(), b_expr.clone()],\n            )\n            .into()])),\n        );\n\n        let mut where_clause = vec![WhereTerm {\n            expr: or_expr,\n            from_outer_join: None,\n            consumed: false,\n        }];\n\n        lift_common_subexpressions_from_binary_or_terms(&mut where_clause)?;\n\n        // Should now have 3 terms:\n        // 1. (x = 1) OR (y = 1)\n        // 2. a = 1\n        // 3. b = 1\n        let nonconsumed_terms = where_clause\n            .iter()\n            .filter(|term| !term.consumed)\n            .collect::<Vec<_>>();\n        assert_eq!(nonconsumed_terms.len(), 3);\n        assert_eq!(\n            nonconsumed_terms[0].expr,\n            Expr::Binary(\n                Box::new(ast::Expr::Parenthesized(vec![x_expr.into()])),\n                Operator::Or,\n                Box::new(ast::Expr::Parenthesized(vec![y_expr.into()]))\n            )\n        );\n        assert_eq!(nonconsumed_terms[1].expr, a_expr);\n        assert_eq!(nonconsumed_terms[2].expr, b_expr);\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_lift_common_subexpressions_three_branches() -> Result<()> {\n        // Test case with three OR branches and one common term:\n        // (a = 1 AND x = 1) OR (a = 1 AND y = 1) OR (a = 1 AND z = 1)\n        // Should become:\n        // (x = 1 OR y = 1 OR z = 1) AND a = 1\n\n        let a_expr = Expr::Binary(\n            Box::new(Expr::Column {\n                database: None,\n                table: TableInternalId::default(),\n                column: 0,\n                is_rowid_alias: false,\n            }),\n            Operator::Equals,\n            Box::new(Expr::Literal(Literal::Numeric(\"1\".to_string()))),\n        );\n\n        let x_expr = Expr::Binary(\n            Box::new(Expr::Column {\n                database: None,\n                table: TableInternalId::default(),\n                column: 1,\n                is_rowid_alias: false,\n            }),\n            Operator::Equals,\n            Box::new(Expr::Literal(Literal::Numeric(\"1\".to_string()))),\n        );\n\n        let y_expr = Expr::Binary(\n            Box::new(Expr::Column {\n                database: None,\n                table: TableInternalId::default(),\n                column: 2,\n                is_rowid_alias: false,\n            }),\n            Operator::Equals,\n            Box::new(Expr::Literal(Literal::Numeric(\"1\".to_string()))),\n        );\n\n        let z_expr = Expr::Binary(\n            Box::new(Expr::Column {\n                database: None,\n                table: TableInternalId::default(),\n                column: 3,\n                is_rowid_alias: false,\n            }),\n            Operator::Equals,\n            Box::new(Expr::Literal(Literal::Numeric(\"1\".to_string()))),\n        );\n\n        // Create (a = 1 AND x = 1) OR (a = 1 AND y = 1) OR (a = 1 AND z = 1)\n        let or_expr = Expr::Binary(\n            Box::new(Expr::Binary(\n                Box::new(ast::Expr::Parenthesized(vec![rebuild_and_expr_from_list(\n                    vec![a_expr.clone(), x_expr.clone()],\n                )\n                .into()])),\n                Operator::Or,\n                Box::new(ast::Expr::Parenthesized(vec![rebuild_and_expr_from_list(\n                    vec![a_expr.clone(), y_expr.clone()],\n                )\n                .into()])),\n            )),\n            Operator::Or,\n            Box::new(ast::Expr::Parenthesized(vec![rebuild_and_expr_from_list(\n                vec![a_expr.clone(), z_expr.clone()],\n            )\n            .into()])),\n        );\n\n        let mut where_clause = vec![WhereTerm {\n            expr: or_expr,\n            from_outer_join: None,\n            consumed: false,\n        }];\n\n        lift_common_subexpressions_from_binary_or_terms(&mut where_clause)?;\n\n        // Should now have 2 terms:\n        // 1. (x = 1) OR (y = 1) OR (z = 1)\n        // 2. a = 1\n        let nonconsumed_terms = where_clause\n            .iter()\n            .filter(|term| !term.consumed)\n            .collect::<Vec<_>>();\n        assert_eq!(nonconsumed_terms.len(), 2);\n        assert_eq!(\n            nonconsumed_terms[0].expr,\n            Expr::Binary(\n                Box::new(Expr::Binary(\n                    Box::new(ast::Expr::Parenthesized(vec![x_expr.into()])),\n                    Operator::Or,\n                    Box::new(ast::Expr::Parenthesized(vec![y_expr.into()])),\n                )),\n                Operator::Or,\n                Box::new(ast::Expr::Parenthesized(vec![z_expr.into()])),\n            )\n        );\n        assert_eq!(nonconsumed_terms[1].expr, a_expr);\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_lift_common_subexpressions_no_common_terms() -> Result<()> {\n        // Test case where there are no common terms between OR branches:\n        // SELECT * FROM t WHERE (x = 1) OR (y = 1)\n        // should remain unchanged.\n\n        let x_expr = Expr::Binary(\n            Box::new(Expr::Column {\n                database: None,\n                table: TableInternalId::default(),\n                column: 0,\n                is_rowid_alias: false,\n            }),\n            Operator::Equals,\n            Box::new(Expr::Literal(Literal::Numeric(\"1\".to_string()))),\n        );\n\n        let y_expr = Expr::Binary(\n            Box::new(Expr::Column {\n                database: None,\n                table: TableInternalId::default(),\n                column: 1,\n                is_rowid_alias: false,\n            }),\n            Operator::Equals,\n            Box::new(Expr::Literal(Literal::Numeric(\"1\".to_string()))),\n        );\n\n        let or_expr = Expr::Binary(\n            Box::new(ast::Expr::Parenthesized(vec![x_expr.into()])),\n            Operator::Or,\n            Box::new(ast::Expr::Parenthesized(vec![y_expr.into()])),\n        );\n\n        let mut where_clause = vec![WhereTerm {\n            expr: or_expr.clone(),\n            from_outer_join: None,\n            consumed: false,\n        }];\n\n        lift_common_subexpressions_from_binary_or_terms(&mut where_clause)?;\n\n        // Should remain unchanged since no common terms\n        let nonconsumed_terms = where_clause\n            .iter()\n            .filter(|term| !term.consumed)\n            .collect::<Vec<_>>();\n        assert_eq!(nonconsumed_terms.len(), 1);\n        assert_eq!(nonconsumed_terms[0].expr, or_expr);\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_lift_common_subexpressions_from_outer_join() -> Result<()> {\n        // Test case with from_outer_join flag set;\n        // it should be retained in the new WhereTerms, for outer join correctness.\n\n        let a_expr = Expr::Binary(\n            Box::new(Expr::Column {\n                database: None,\n                table: TableInternalId::default(),\n                column: 0,\n                is_rowid_alias: false,\n            }),\n            Operator::Equals,\n            Box::new(Expr::Literal(Literal::Numeric(\"1\".to_string()))),\n        );\n\n        let x_expr = Expr::Binary(\n            Box::new(Expr::Column {\n                database: None,\n                table: TableInternalId::default(),\n                column: 1,\n                is_rowid_alias: false,\n            }),\n            Operator::Equals,\n            Box::new(Expr::Literal(Literal::Numeric(\"1\".to_string()))),\n        );\n\n        let y_expr = Expr::Binary(\n            Box::new(Expr::Column {\n                database: None,\n                table: TableInternalId::default(),\n                column: 2,\n                is_rowid_alias: false,\n            }),\n            Operator::Equals,\n            Box::new(Expr::Literal(Literal::Numeric(\"1\".to_string()))),\n        );\n\n        let or_expr = Expr::Binary(\n            Box::new(ast::Expr::Parenthesized(vec![rebuild_and_expr_from_list(\n                vec![a_expr.clone(), x_expr.clone()],\n            )\n            .into()])),\n            Operator::Or,\n            Box::new(ast::Expr::Parenthesized(vec![rebuild_and_expr_from_list(\n                vec![a_expr.clone(), y_expr.clone()],\n            )\n            .into()])),\n        );\n\n        let mut where_clause = vec![WhereTerm {\n            expr: or_expr,\n            from_outer_join: Some(TableInternalId::default()), // Set from_outer_join\n            consumed: false,\n        }];\n\n        lift_common_subexpressions_from_binary_or_terms(&mut where_clause)?;\n\n        // Should have 2 terms, both with from_outer_join set\n        let nonconsumed_terms = where_clause\n            .iter()\n            .filter(|term| !term.consumed)\n            .collect::<Vec<_>>();\n        assert_eq!(nonconsumed_terms.len(), 2);\n        assert_eq!(\n            nonconsumed_terms[0].expr,\n            Expr::Binary(\n                Box::new(ast::Expr::Parenthesized(vec![x_expr.into()])),\n                Operator::Or,\n                Box::new(ast::Expr::Parenthesized(vec![y_expr.into()]))\n            )\n        );\n        assert_eq!(\n            nonconsumed_terms[0].from_outer_join,\n            Some(TableInternalId::default())\n        );\n        assert_eq!(nonconsumed_terms[1].expr, a_expr);\n        assert_eq!(\n            nonconsumed_terms[1].from_outer_join,\n            Some(TableInternalId::default())\n        );\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_lift_common_subexpressions_single_term() -> Result<()> {\n        // Test case with a single non-OR term:\n        // SELECT * FROM t WHERE a = 1\n        // should remain unchanged.\n\n        let single_expr = Expr::Binary(\n            Box::new(Expr::Column {\n                database: None,\n                table: TableInternalId::default(),\n                column: 0,\n                is_rowid_alias: false,\n            }),\n            Operator::Equals,\n            Box::new(Expr::Literal(Literal::Numeric(\"1\".to_string()))),\n        );\n\n        let mut where_clause = vec![WhereTerm {\n            expr: single_expr.clone(),\n            from_outer_join: None,\n            consumed: false,\n        }];\n\n        lift_common_subexpressions_from_binary_or_terms(&mut where_clause)?;\n\n        // Should remain unchanged\n        let nonconsumed_terms = where_clause\n            .iter()\n            .filter(|term| !term.consumed)\n            .collect::<Vec<_>>();\n        assert_eq!(nonconsumed_terms.len(), 1);\n        assert_eq!(nonconsumed_terms[0].expr, single_expr);\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_lift_common_subexpressions_empty_or_branch() -> Result<()> {\n        // Test case where OR becomes redundant:\n        // (a = 1 AND b = 1) OR (a = 1) becomes -> a = 1.\n        let exprs = (0..=1)\n            .map(|i| {\n                Expr::Binary(\n                    Box::new(Expr::Column {\n                        database: None,\n                        table: TableInternalId::default(),\n                        column: i,\n                        is_rowid_alias: false,\n                    }),\n                    Operator::Equals,\n                    Box::new(Expr::Literal(Literal::Numeric(\"1\".to_string()))),\n                )\n            })\n            .collect::<Vec<_>>();\n\n        let a_expr = exprs[0].clone();\n        let b_expr = exprs[1].clone();\n\n        let a_and_b_expr = Expr::Binary(Box::new(a_expr.clone()), Operator::And, Box::new(b_expr));\n\n        let or_expr = Expr::Binary(\n            Box::new(a_and_b_expr),\n            Operator::Or,\n            Box::new(a_expr.clone()),\n        );\n\n        let mut where_clause = vec![WhereTerm {\n            expr: or_expr,\n            from_outer_join: None,\n            consumed: false,\n        }];\n\n        lift_common_subexpressions_from_binary_or_terms(&mut where_clause)?;\n\n        let nonconsumed_terms = where_clause\n            .iter()\n            .filter(|term| !term.consumed)\n            .collect::<Vec<_>>();\n        assert_eq!(nonconsumed_terms.len(), 1);\n        assert_eq!(nonconsumed_terms[0].expr, a_expr);\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "core/translate/optimizer/mod.rs",
    "content": "use crate::translate::expression_index::expression_index_column_usage;\nuse crate::translate::plan::MultiIndexBranchAccess;\nuse crate::{\n    function::{AggFunc, Deterministic},\n    index_method::IndexMethodCostEstimate,\n    numeric::Numeric,\n    schema::{BTreeTable, Index, IndexColumn, Schema, Table, ROWID_SENTINEL},\n    translate::{\n        insert::ROWID_COLUMN,\n        optimizer::{\n            access_method::AccessMethodParams,\n            constraints::{\n                ConstraintUseCandidate, RangeConstraintRef, SeekRangeConstraint, TableConstraints,\n            },\n            cost::RowCountEstimate,\n            multi_index::MultiIndexBranchAccessParams,\n            order::{ColumnTarget, OrderTarget},\n        },\n        plan::{\n            ColumnUsedMask, DmlSafetyReason, EphemeralRowidMode, HashJoinOp, IndexMethodQuery,\n            NonFromClauseSubquery, OuterQueryReference, QueryDestination, ResultSetColumn, Scan,\n            SeekKeyComponent, SubqueryState,\n        },\n        trigger_exec::has_relevant_triggers_type_only,\n    },\n    types::SeekOp,\n    util::{\n        count_fts_column_args, exprs_are_equivalent, simple_bind_expr, try_capture_parameters,\n        try_capture_parameters_column_agnostic, try_substitute_parameters,\n    },\n    vdbe::{\n        affinity::Affinity,\n        builder::{CursorKey, CursorType, ProgramBuilder},\n    },\n    LimboError, Result,\n};\nuse crate::{turso_assert, turso_assert_eq, turso_debug_assert, turso_soft_unreachable};\nuse constraints::{\n    constraints_from_where_clause, usable_constraints_for_join_order, Constraint,\n    ConstraintOperator, ConstraintRef,\n};\nuse cost::Cost;\nuse join::{compute_best_join_order_with_context, BestJoinOrderResult, JoinPlanningContext};\nuse lift_common_subexpressions::lift_common_subexpressions_from_binary_or_terms;\nuse order::{\n    compute_order_target, plan_satisfies_order_target, simple_aggregate_order_target,\n    EliminatesSortBy, OrderTargetPurpose,\n};\nuse rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};\nuse std::{cmp::Ordering, collections::VecDeque, sync::Arc};\nuse turso_ext::{ConstraintInfo, ConstraintUsage};\nuse turso_parser::ast::{self, Expr, SortOrder, SubqueryType, TriggerEvent};\n\nuse super::{\n    collate::get_collseq_from_expr,\n    emitter::Resolver,\n    plan::{\n        DeletePlan, GroupBy, InSeekSource, IterationDirection, JoinOrderMember, JoinType,\n        JoinedTable, MinMaxDef, MultiIndexBranch, MultiIndexScanOp, Operation, Plan, Search,\n        SeekDef, SeekKey, SelectPlan, SetOperation, SimpleAggregate, TableReferences, UpdatePlan,\n        WhereTerm,\n    },\n    planner::TableMask,\n};\n\npub(crate) mod access_method;\npub(crate) mod constraints;\npub(crate) mod cost;\nmod cost_params;\npub(crate) mod join;\npub(crate) mod lift_common_subexpressions;\npub(crate) mod multi_index;\npub(crate) mod order;\npub(crate) mod unnest;\n\n/// A candidate index method that could be used for table access in a join query.\n/// This struct captures all information needed to construct an IndexMethodQuery\n/// operation, allowing the DP join ordering algorithm to consider custom index\n/// methods alongside BTree indexes.\n#[derive(Debug, Clone)]\npub struct IndexMethodCandidate {\n    /// Index of the table in the joined_tables list\n    pub table_idx: usize,\n    /// The index that defines this index method\n    pub index: Arc<Index>,\n    /// Pattern index from the index method definition that matched\n    pub pattern_idx: usize,\n    /// Arguments captured from pattern matching\n    pub arguments: Vec<ast::Expr>,\n    /// Mapping from synthetic column IDs to pattern column IDs for covered columns\n    pub covered_columns: HashMap<usize, usize>,\n    /// Index in WHERE clause that was covered by this pattern (if any)\n    pub where_covered: Option<usize>,\n    /// Cost estimate from the index method\n    pub cost_estimate: Option<IndexMethodCostEstimate>,\n}\n\nimpl IndexMethodCandidate {\n    /// Build the IndexMethodQuery operation from this candidate\n    pub fn to_query(&self) -> IndexMethodQuery {\n        IndexMethodQuery {\n            index: self.index.clone(),\n            pattern_idx: self.pattern_idx,\n            arguments: self.arguments.clone(),\n            covered_columns: self.covered_columns.clone(),\n        }\n    }\n}\n\n/// Result of successfully matching an index method pattern against a query.\n/// This intermediate struct allows both `collect_index_method_candidates` and\n/// `optimize_table_access_with_custom_modules` to share pattern matching logic.\n#[derive(Debug, Clone)]\nstruct IndexMethodPatternMatch {\n    /// Pattern index from the index method definition that matched\n    pattern_idx: usize,\n    /// Parameters captured from pattern matching (positional placeholders)\n    parameters: HashMap<i32, ast::Expr>,\n    /// Index in WHERE clause that was covered by this pattern (if any)\n    where_covered: Option<usize>,\n    /// Whether the pattern explicitly handles ORDER BY\n    pattern_has_order_by: bool,\n    /// Whether the pattern explicitly handles LIMIT\n    pattern_has_limit: bool,\n    /// Pattern result columns (needed for covered columns calculation)\n    pattern_columns: Vec<ast::ResultColumn>,\n}\n\n/// Try to match an index method pattern against a query's clauses.\n#[allow(clippy::too_many_arguments)]\nfn try_match_index_method_pattern(\n    pattern: &ast::Select,\n    table: &JoinedTable,\n    query_where_terms: &[WhereTerm],\n    order_by: &[(Box<ast::Expr>, SortOrder)],\n    limit: &Option<Box<Expr>>,\n    offset: &Option<Box<Expr>>,\n    pattern_idx: usize,\n    soft_bind_errors: bool,\n) -> Option<IndexMethodPatternMatch> {\n    let mut pattern = pattern.clone();\n    if pattern.with.is_some() || !pattern.body.compounds.is_empty() {\n        return None;\n    }\n\n    let ast::OneSelect::Select {\n        columns,\n        from: Some(ast::FromClause { select, joins }),\n        distinctness: None,\n        where_clause: ref mut pattern_where_clause,\n        group_by: None,\n        window_clause,\n    } = &mut pattern.body.select\n    else {\n        if soft_bind_errors {\n            return None;\n        }\n        panic!(\"unexpected select pattern body\");\n    };\n\n    if !window_clause.is_empty() || !joins.is_empty() {\n        return None;\n    }\n\n    let ast::SelectTable::Table(name, _, _) = select.as_ref() else {\n        if soft_bind_errors {\n            return None;\n        }\n        panic!(\"unexpected from clause\");\n    };\n\n    // Bind expressions to this table\n    for column in columns.iter_mut() {\n        if let ast::ResultColumn::Expr(e, _) = column {\n            if soft_bind_errors {\n                if simple_bind_expr(table, &[], e).is_err() {\n                    return None;\n                }\n            } else {\n                simple_bind_expr(table, &[], e).ok()?;\n            }\n        }\n    }\n    for column in pattern.order_by.iter_mut() {\n        if soft_bind_errors {\n            if simple_bind_expr(table, columns, &mut column.expr).is_err() {\n                return None;\n            }\n        } else {\n            simple_bind_expr(table, columns, &mut column.expr).ok()?;\n        }\n    }\n    if let Some(pattern_where) = pattern_where_clause {\n        if soft_bind_errors {\n            if simple_bind_expr(table, columns, pattern_where).is_err() {\n                return None;\n            }\n        } else {\n            simple_bind_expr(table, columns, pattern_where).ok()?;\n        }\n    }\n\n    if name.name.as_str() != table.table.get_name() {\n        return None;\n    }\n\n    let pattern_has_order_by = !pattern.order_by.is_empty();\n    let pattern_has_limit = pattern.limit.is_some();\n\n    // If pattern has ORDER BY, it must match exactly\n    if pattern_has_order_by && order_by.len() != pattern.order_by.len() {\n        return None;\n    }\n\n    let mut where_query_covered: Option<usize> = None;\n    let mut parameters = HashMap::default();\n\n    // Match ORDER BY if pattern has it\n    if pattern_has_order_by {\n        for (pattern_column, (query_column, query_order)) in\n            pattern.order_by.iter().zip(order_by.iter())\n        {\n            if *query_order != pattern_column.order.unwrap_or(SortOrder::Asc) {\n                return None;\n            }\n            let num_col_args = count_fts_column_args(&pattern_column.expr);\n            let captured = if num_col_args > 0 {\n                try_capture_parameters_column_agnostic(\n                    &pattern_column.expr,\n                    query_column,\n                    num_col_args,\n                )\n            } else {\n                try_capture_parameters(&pattern_column.expr, query_column)\n            };\n            parameters.extend(captured?);\n        }\n    }\n\n    // Match LIMIT if pattern has it\n    match (pattern.limit.as_ref().map(|x| &x.expr), limit) {\n        (Some(_), None) => return None,\n        (Some(pattern_limit), Some(query_limit)) => {\n            let captured = try_capture_parameters(pattern_limit, query_limit)?;\n            parameters.extend(captured);\n        }\n        (None, Some(_)) | (None, None) => {}\n    }\n\n    // Match OFFSET if pattern has it\n    match (\n        pattern.limit.as_ref().and_then(|x| x.offset.as_ref()),\n        offset,\n    ) {\n        (Some(_), None) => return None,\n        (Some(pattern_off), Some(query_off)) => {\n            let captured = try_capture_parameters(pattern_off, query_off)?;\n            parameters.extend(captured);\n        }\n        (None, Some(_)) | (None, None) => {}\n    }\n\n    // Match WHERE clause\n    if let Some(pattern_where) = pattern_where_clause {\n        for (i, query_where) in query_where_terms.iter().enumerate() {\n            let num_col_args = count_fts_column_args(pattern_where);\n            let captured = if num_col_args > 0 {\n                try_capture_parameters_column_agnostic(\n                    pattern_where,\n                    &query_where.expr,\n                    num_col_args,\n                )\n            } else {\n                try_capture_parameters(pattern_where, &query_where.expr)\n            };\n            let Some(captured) = captured else {\n                continue;\n            };\n            parameters.extend(captured);\n            where_query_covered = Some(i);\n            break;\n        }\n    }\n\n    // Pattern requires WHERE but we didn't match any\n    if pattern_where_clause.is_some() && where_query_covered.is_none() {\n        return None;\n    }\n\n    let where_covered_completely = query_where_terms.is_empty()\n        || (where_query_covered.is_some() && query_where_terms.len() == 1);\n\n    // When WHERE is not completely covered, skip patterns with ORDER BY/LIMIT\n    // because post-filtering would disrupt the order or apply limits incorrectly\n    if !where_covered_completely && (pattern_has_order_by || pattern_has_limit) {\n        return None;\n    }\n\n    Some(IndexMethodPatternMatch {\n        pattern_idx,\n        parameters,\n        where_covered: where_query_covered,\n        pattern_has_order_by,\n        pattern_has_limit,\n        pattern_columns: columns.clone(),\n    })\n}\n\n/// Build covered columns mapping from pattern columns.\n/// Returns a HashMap mapping synthetic column IDs to pattern column IDs.\nfn build_covered_columns_mapping(\n    pattern_columns: &[ast::ResultColumn],\n    parameters: &HashMap<i32, ast::Expr>,\n) -> HashMap<usize, usize> {\n    let mut covered_column_id = 1_000_000;\n    let mut covered_columns = HashMap::default();\n    for (pattern_column_id, pattern_column) in pattern_columns.iter().enumerate() {\n        let ast::ResultColumn::Expr(pattern_expr, _) = pattern_column else {\n            continue;\n        };\n        let Some(_substituted) = try_substitute_parameters(pattern_expr, parameters) else {\n            continue;\n        };\n        covered_columns.insert(covered_column_id, pattern_column_id);\n        covered_column_id += 1;\n    }\n    covered_columns\n}\n\n/// Sort parameters by key and extract just the expressions as a Vec.\nfn sorted_arguments_from_parameters(parameters: &HashMap<i32, ast::Expr>) -> Vec<ast::Expr> {\n    let mut arguments: Vec<_> = parameters.iter().collect();\n    arguments.sort_by_key(|(&i, _)| i);\n    arguments.iter().map(|(_, e)| (*e).clone()).collect()\n}\n\n/// Collect index method candidates for all tables that have custom index methods.\n/// This function performs pattern matching but does NOT apply the operations,\n/// allowing the DP join ordering algorithm to consider index methods as candidates.\n#[allow(clippy::too_many_arguments)]\nfn collect_index_method_candidates(\n    table_references: &TableReferences,\n    available_indexes: &HashMap<String, VecDeque<Arc<Index>>>,\n    where_clause: &[WhereTerm],\n    order_by: &[(Box<ast::Expr>, SortOrder)],\n    group_by: &Option<GroupBy>,\n    limit: &Option<Box<Expr>>,\n    offset: &Option<Box<Expr>>,\n    base_table_rows: &[RowCountEstimate],\n    params: &cost_params::CostModelParams,\n) -> Result<Vec<IndexMethodCandidate>> {\n    let mut candidates = Vec::new();\n\n    // Group by is not supported for index methods\n    if group_by.is_some() {\n        return Ok(candidates);\n    }\n\n    let tables = table_references.joined_tables();\n    for (table_idx, table) in tables.iter().enumerate() {\n        let Some(indexes) = available_indexes.get(table.table.get_name()) else {\n            continue;\n        };\n\n        for index in indexes {\n            let Some(module) = &index.index_method else {\n                continue;\n            };\n            if index.is_backing_btree_index() {\n                continue;\n            }\n\n            let definition = module.definition();\n            for (pattern_idx, pattern) in definition.patterns.iter().enumerate() {\n                // Use shared helper for pattern matching\n                let Some(pattern_match) = try_match_index_method_pattern(\n                    pattern,\n                    table,\n                    where_clause,\n                    order_by,\n                    limit,\n                    offset,\n                    pattern_idx,\n                    true, // continue on binding failures\n                ) else {\n                    continue;\n                };\n\n                // Build covered columns mapping from pattern match\n                let covered_columns = build_covered_columns_mapping(\n                    &pattern_match.pattern_columns,\n                    &pattern_match.parameters,\n                );\n\n                // Get cost estimate from the index method\n                let cost_estimate = module.init().ok().and_then(|cursor| {\n                    let base_rows = base_table_rows\n                        .get(table_idx)\n                        .map(|r| **r)\n                        .unwrap_or(params.rows_per_table_fallback);\n                    cursor.estimate_cost(pattern_match.pattern_idx, base_rows)\n                });\n\n                // Sort and collect arguments\n                let arguments = sorted_arguments_from_parameters(&pattern_match.parameters);\n\n                candidates.push(IndexMethodCandidate {\n                    table_idx,\n                    index: index.clone(),\n                    pattern_idx: pattern_match.pattern_idx,\n                    arguments,\n                    covered_columns,\n                    where_covered: pattern_match.where_covered,\n                    cost_estimate,\n                });\n\n                // Found a match for this table+index, try next index\n                break;\n            }\n        }\n    }\n\n    Ok(candidates)\n}\n\n#[tracing::instrument(skip_all, level = tracing::Level::DEBUG)]\npub fn optimize_plan(\n    program: &mut ProgramBuilder,\n    plan: &mut Plan,\n    resolver: &Resolver,\n) -> Result<()> {\n    let schema = resolver.schema();\n    match plan {\n        Plan::Select(plan) => optimize_select_plan(plan, schema)?,\n        Plan::Delete(plan) => optimize_delete_plan(plan, schema)?,\n        Plan::Update(plan) => optimize_update_plan(program, plan, resolver)?,\n        Plan::CompoundSelect {\n            left, right_most, ..\n        } => {\n            optimize_select_plan(right_most, schema)?;\n            for (plan, _) in left {\n                optimize_select_plan(plan, schema)?;\n            }\n        }\n    }\n    // When debug tracing is enabled, print the optimized plan as a SQL string for debugging\n    tracing::debug!(plan_sql = plan.to_string());\n    Ok(())\n}\n\n#[cfg(all(feature = \"fts\", not(target_family = \"wasm\")))]\n/// Transform MATCH expressions to fts_match() function calls.\nfn transform_match_to_fts_match(\n    where_clause: &mut [WhereTerm],\n    schema: &Schema,\n    table_references: &TableReferences,\n) -> Result<()> {\n    use super::ast::{FunctionTail, LikeOperator, Name, TableInternalId};\n    use super::expr::{walk_expr_mut, WalkControl};\n\n    // Helper to extract table ID from a column expression\n    fn get_table_id_from_expr(expr: &Expr) -> Option<TableInternalId> {\n        match expr {\n            Expr::Column { table, .. } => Some(*table),\n            Expr::Parenthesized(exprs) if !exprs.is_empty() => get_table_id_from_expr(&exprs[0]),\n            _ => None,\n        }\n    }\n\n    // Helper to check if a table has an FTS index by its internal ID\n    let table_has_fts_index = |table_id: TableInternalId| -> bool {\n        table_references\n            .joined_tables()\n            .iter()\n            .find(|t| t.internal_id == table_id)\n            .and_then(|t| {\n                if let Table::BTree(btree) = &t.table {\n                    Some(schema.has_fts_index(&btree.name))\n                } else {\n                    None\n                }\n            })\n            .unwrap_or(false)\n    };\n\n    let mut match_without_fts = false;\n    for term in where_clause.iter_mut() {\n        let _ = walk_expr_mut(&mut term.expr, &mut |e: &mut Expr| -> Result<WalkControl> {\n            match e {\n                Expr::Like {\n                    lhs,\n                    not,\n                    op: LikeOperator::Match,\n                    rhs,\n                    escape: _,\n                } => {\n                    // Check if the specific table referenced by this MATCH has an FTS index\n                    let has_fts = get_table_id_from_expr(lhs).is_some_and(table_has_fts_index);\n\n                    if !has_fts {\n                        match_without_fts = true;\n                        // Don't transform, we'll error after the walk\n                        return Ok(WalkControl::SkipChildren);\n                    }\n\n                    // Transform MATCH to fts_match():\n                    // - `col MATCH 'query'` -> `fts_match(col, 'query')`\n                    // - `(col1, col2) MATCH 'query'` -> `fts_match(col1, col2, 'query')`\n                    let mut args: Vec<Box<Expr>> = match lhs.as_ref() {\n                        Expr::Parenthesized(cols) => cols.clone(),\n                        _ => vec![lhs.clone()],\n                    };\n                    args.push(rhs.clone());\n\n                    let func_call = Expr::FunctionCall {\n                        name: Name::exact(\"fts_match\".to_string()),\n                        distinctness: None,\n                        args,\n                        order_by: vec![],\n                        filter_over: FunctionTail {\n                            filter_clause: None,\n                            over_clause: None,\n                        },\n                    };\n                    if *not {\n                        // For NOT MATCH, just wrap the whole thing in a unary NOT\n                        *e = Expr::Unary(ast::UnaryOperator::Not, Box::new(func_call));\n                    } else {\n                        *e = func_call;\n                    }\n                    Ok(WalkControl::Continue)\n                }\n                _ => Ok(WalkControl::Continue),\n            }\n        });\n    }\n\n    if match_without_fts {\n        return Err(LimboError::ParseError(\n            \"unable to use function MATCH in the requested context\".to_string(),\n        ));\n    }\n\n    Ok(())\n}\n\n/// Detect whether this plan qualifies for the simple-aggregate fast path.\n///\n/// Analogous to SQLite's `isSimpleCount()` + `minMaxQuery()`.\n/// Must be called before `optimize_table_access` so the order target is available.\nfn detect_simple_aggregate(plan: &SelectPlan) -> Option<SimpleAggregate> {\n    // Common preconditions shared by count(*) and min/max.\n    if plan.aggregates.len() != 1\n        || plan.table_references.joined_tables().len() != 1\n        || plan.result_columns.len() != 1\n        || plan.group_by.is_some()\n        || plan.contains_constant_false_condition\n    {\n        return None;\n    }\n\n    let table_ref = plan.table_references.joined_tables().first().unwrap();\n    let agg = plan.aggregates.first().unwrap();\n    let result_expr = &plan.result_columns.first().unwrap().expr;\n\n    // The result column must be exactly the aggregate expression (not wrapped in\n    // something like `length(count(*))`).\n    if !exprs_are_equivalent(result_expr, &agg.original_expr) {\n        return None;\n    }\n\n    match agg.func {\n        AggFunc::Count0\n            if matches!(table_ref.table, Table::BTree(..))\n                && plan.table_references.outer_query_refs().is_empty()\n                && plan.where_clause.is_empty()\n                && plan.limit.is_none()\n                && plan.offset.is_none() =>\n        {\n            Some(SimpleAggregate::Count)\n        }\n        AggFunc::Min | AggFunc::Max\n            if agg.args.len() == 1\n                && matches!(\n                    table_ref.table,\n                    Table::BTree(..) | Table::FromClauseSubquery(..)\n                ) =>\n        {\n            // Unlike COUNT(*), MIN/MAX may still use the fast path with a\n            // WHERE clause as long as the chosen access path can walk directly\n            // to the first qualifying extremum row.\n            let argument = agg.args[0].clone();\n            let order = if matches!(agg.func, AggFunc::Min) {\n                SortOrder::Asc\n            } else {\n                SortOrder::Desc\n            };\n            let collation = get_collseq_from_expr(&argument, &plan.table_references)\n                .ok()\n                .flatten();\n            Some(SimpleAggregate::MinMax(Box::new(MinMaxDef {\n                func: agg.func.clone(),\n                argument,\n                order,\n                collation,\n            })))\n        }\n        _ => None,\n    }\n}\n\nstruct OptimizeTableAccessResult {\n    join_order: Vec<JoinOrderMember>,\n    output_rows: f64,\n    min_max_fast_path: bool,\n}\n\n/**\n * Make a few passes over the plan to optimize it.\n * TODO: these could probably be done in less passes,\n * but having them separate makes them easier to understand\n */\npub fn optimize_select_plan(plan: &mut SelectPlan, schema: &Schema) -> Result<()> {\n    // Transform MATCH expressions to fts_match() for FTS optimizer recognition\n    #[cfg(all(feature = \"fts\", not(target_family = \"wasm\")))]\n    transform_match_to_fts_match(&mut plan.where_clause, schema, &plan.table_references)?;\n\n    unnest::unnest_exists_subqueries(plan)?;\n    // EXISTS only needs 1 row. Add LIMIT 1 to surviving (non-unnested) EXISTS\n    // subqueries. This is done here rather than in the subquery planner so that\n    // unnesting sees the plan without an artificial LIMIT.\n    for sub in &mut plan.non_from_clause_subqueries {\n        if matches!(sub.query_type, ast::SubqueryType::Exists { .. }) {\n            if let SubqueryState::Unevaluated { plan: Some(inner) } = &mut sub.state {\n                if inner.limit.is_none() {\n                    inner.limit = Some(Box::new(Expr::Literal(ast::Literal::Numeric(\n                        \"1\".to_string(),\n                    ))));\n                }\n            }\n        }\n    }\n    optimize_subqueries(plan, schema)?;\n    lift_common_subexpressions_from_binary_or_terms(&mut plan.where_clause)?;\n    if let ConstantConditionEliminationResult::ImpossibleCondition =\n        eliminate_constant_conditions(&mut plan.where_clause)?\n    {\n        plan.contains_constant_false_condition = true;\n        return Ok(());\n    }\n\n    plan.simple_aggregate = detect_simple_aggregate(plan);\n    let best_join_order = optimize_table_access(\n        schema,\n        &mut plan.result_columns,\n        &mut plan.table_references,\n        &schema.indexes,\n        &mut plan.where_clause,\n        &mut plan.order_by,\n        &mut plan.group_by,\n        plan.simple_aggregate.as_ref(),\n        &plan.non_from_clause_subqueries,\n        &mut plan.limit,\n        &mut plan.offset,\n        plan.input_cardinality_hint.unwrap_or(1.0),\n    )?;\n\n    if matches!(plan.simple_aggregate, Some(SimpleAggregate::MinMax(_)))\n        && !best_join_order\n            .as_ref()\n            .is_some_and(|result| result.min_max_fast_path)\n    {\n        plan.simple_aggregate = None;\n    }\n\n    if let Some(OptimizeTableAccessResult {\n        join_order,\n        output_rows,\n        ..\n    }) = best_join_order\n    {\n        plan.join_order = join_order;\n        let mut est = output_rows;\n        // Clamp to LIMIT when it's a literal non-negative number.\n        // Negative LIMIT means \"no limit\" in SQLite, so we skip those.\n        if let Some(limit_expr) = &plan.limit {\n            if let Ok(val) = crate::util::parse_signed_number(limit_expr) {\n                let limit_f64 = match val {\n                    crate::types::Value::Numeric(Numeric::Integer(i)) if i >= 0 => Some(i as f64),\n                    crate::types::Value::Numeric(Numeric::Float(f)) => {\n                        let f: f64 = f.into();\n                        if f >= 0.0 {\n                            Some(f)\n                        } else {\n                            None\n                        }\n                    }\n                    _ => None,\n                };\n                if let Some(limit_val) = limit_f64 {\n                    est = est.min(limit_val);\n                }\n            }\n        }\n        plan.estimated_output_rows = Some(est);\n    }\n\n    reoptimize_correlated_subqueries(plan, schema)?;\n\n    Ok(())\n}\n\nfn optimize_delete_plan(plan: &mut DeletePlan, schema: &Schema) -> Result<()> {\n    #[cfg(all(feature = \"fts\", not(target_family = \"wasm\")))]\n    transform_match_to_fts_match(&mut plan.where_clause, schema, &plan.table_references)?;\n\n    lift_common_subexpressions_from_binary_or_terms(&mut plan.where_clause)?;\n    if let ConstantConditionEliminationResult::ImpossibleCondition =\n        eliminate_constant_conditions(&mut plan.where_clause)?\n    {\n        plan.contains_constant_false_condition = true;\n        return Ok(());\n    }\n\n    if let Some(rowset_plan) = plan.rowset_plan.as_mut() {\n        optimize_select_plan(rowset_plan, schema)?;\n    }\n\n    let _ = optimize_table_access(\n        schema,\n        &mut plan.result_columns,\n        &mut plan.table_references,\n        &schema.indexes,\n        &mut plan.where_clause,\n        &mut plan.order_by,\n        &mut None,\n        None,\n        &plan.non_from_clause_subqueries,\n        &mut plan.limit,\n        &mut plan.offset,\n        1.0,\n    )?;\n\n    Ok(())\n}\n\nfn optimize_update_plan(\n    program: &mut ProgramBuilder,\n    plan: &mut UpdatePlan,\n    resolver: &Resolver,\n) -> Result<()> {\n    let schema = resolver.schema();\n    #[cfg(all(feature = \"fts\", not(target_family = \"wasm\")))]\n    transform_match_to_fts_match(&mut plan.where_clause, schema, &plan.table_references)?;\n    lift_common_subexpressions_from_binary_or_terms(&mut plan.where_clause)?;\n    if let ConstantConditionEliminationResult::ImpossibleCondition =\n        eliminate_constant_conditions(&mut plan.where_clause)?\n    {\n        plan.contains_constant_false_condition = true;\n        return Ok(());\n    }\n    let _ = optimize_table_access(\n        schema,\n        &mut [],\n        &mut plan.table_references,\n        &schema.indexes,\n        &mut plan.where_clause,\n        &mut plan.order_by,\n        &mut None,\n        None,\n        &plan.non_from_clause_subqueries,\n        &mut plan.limit,\n        &mut plan.offset,\n        1.0,\n    )?;\n\n    if let Some(reason) = first_update_safety_reason(plan, resolver)? {\n        plan.safety.require(reason);\n    }\n\n    if !plan.safety.requires_stable_write_set() {\n        return Ok(());\n    }\n\n    add_ephemeral_table_to_update_plan(program, plan)\n}\n\nfn first_update_safety_reason(\n    plan: &UpdatePlan,\n    resolver: &Resolver,\n) -> Result<Option<DmlSafetyReason>> {\n    let table_ref = &plan.table_references.joined_tables()[0];\n    let reason = 'requires: {\n        let Some(btree_table_arc) = table_ref.table.btree() else {\n            break 'requires None;\n        };\n        let btree_table = btree_table_arc.as_ref();\n\n        // Multi-index scans gather rowids from multiple index branches.\n        // For UPDATE, we always use the prebuilt ephemeral-table path so writes run against\n        // that fixed rowid list (no surprises from branch/index overlap).\n        if matches!(table_ref.op, Operation::MultiIndexScan(_)) {\n            break 'requires Some(DmlSafetyReason::MultiIndexScan);\n        }\n\n        // Index method cursors that stream lazily need rowids collected first.\n        if let Operation::IndexMethodQuery(query) = &table_ref.op {\n            let attachment = query\n                .index\n                .index_method\n                .as_ref()\n                .expect(\"IndexMethodQuery always has an index_method attachment\");\n            if !attachment.definition().results_materialized {\n                break 'requires Some(DmlSafetyReason::IndexMethodNotMaterialized);\n            }\n        }\n\n        // Check if there are UPDATE triggers\n        let updated_cols: HashSet<usize> = plan.set_clauses.iter().map(|(i, _)| *i).collect();\n        let database_id = table_ref.database_id;\n        if resolver.with_schema(database_id, |s| {\n            has_relevant_triggers_type_only(\n                s,\n                TriggerEvent::Update,\n                Some(&updated_cols),\n                btree_table,\n            )\n        }) {\n            break 'requires Some(DmlSafetyReason::Trigger);\n        }\n\n        // REPLACE mode requires ephemeral table because REPLACE deletes conflicting rows,\n        // which can corrupt the iteration order when iterating via an index.\n        if matches!(\n            plan.or_conflict,\n            Some(turso_parser::ast::ResolveType::Replace)\n        ) {\n            break 'requires Some(DmlSafetyReason::ReplaceMode);\n        }\n\n        let Some(index) = table_ref.op.index() else {\n            let rowid_alias_used = plan.set_clauses.iter().fold(false, |accum, (idx, _)| {\n                accum || (*idx != ROWID_SENTINEL && btree_table.columns[*idx].is_rowid_alias())\n            });\n            if rowid_alias_used {\n                break 'requires Some(DmlSafetyReason::KeyMutation);\n            }\n            let direct_rowid_update = plan\n                .set_clauses\n                .iter()\n                .any(|(idx, _)| *idx == ROWID_SENTINEL);\n            if direct_rowid_update {\n                break 'requires Some(DmlSafetyReason::KeyMutation);\n            }\n            break 'requires None;\n        };\n\n        for (set_clause_col_idx, _) in plan.set_clauses.iter() {\n            for c in index.columns.iter() {\n                if let Some(ref expr) = c.expr {\n                    let expr_idx_cols_mask =\n                        expression_index_column_usage(expr.as_ref(), table_ref, resolver)?;\n                    if expr_idx_cols_mask.get(*set_clause_col_idx) {\n                        break 'requires Some(DmlSafetyReason::KeyMutation);\n                    }\n                } else if c.pos_in_table == *set_clause_col_idx {\n                    break 'requires Some(DmlSafetyReason::KeyMutation);\n                }\n            }\n        }\n        break 'requires None;\n    };\n\n    Ok(reason)\n}\n\n/// Collect SubqueryResult IDs referenced in SET clause and RETURNING expressions.\n/// These subqueries must stay in the main update plan (evaluated during the update phase),\n/// not be moved to the ephemeral plan (which only collects rowids).\nfn collect_update_phase_subquery_ids(\n    plan: &UpdatePlan,\n) -> HashSet<turso_parser::ast::TableInternalId> {\n    use crate::translate::expr::walk_expr;\n    use crate::translate::expr::WalkControl;\n\n    let mut ids = HashSet::default();\n    let mut collector = |e: &ast::Expr| -> Result<WalkControl> {\n        if let ast::Expr::SubqueryResult { subquery_id, .. } = e {\n            ids.insert(*subquery_id);\n        }\n        Ok(WalkControl::Continue)\n    };\n    for (_, expr) in plan.set_clauses.iter() {\n        let _ = walk_expr(expr, &mut collector);\n    }\n    if let Some(returning) = &plan.returning {\n        for rc in returning {\n            let _ = walk_expr(&rc.expr, &mut collector);\n        }\n    }\n    ids\n}\n\n/// An ephemeral table is required if:\n/// 1. The UPDATE modifies any column that is present in the key of the btree used to iterate over the table.\n///    For regular table scans or seeks, the key is the rowid or the rowid alias column (INTEGER PRIMARY KEY).\n///    For index scans and seeks, the key is any column in the index used.\n/// 2. There are UPDATE triggers on the table (SQLite always uses ephemeral tables when triggers exist).\n///\n/// The ephemeral table will accumulate all the rowids of the rows that are affected by the UPDATE,\n/// and then the temp table will be iterated over and the actual row updates performed.\n///\n/// This is necessary because an UPDATE is implemented as a DELETE-then-INSERT operation, which could\n/// mess up the iteration order of the rows by changing the keys in the table/index that the iteration\n/// is performed over. The ephemeral table ensures stable iteration because it is not modified during\n/// the UPDATE loop.\nfn add_ephemeral_table_to_update_plan(\n    program: &mut ProgramBuilder,\n    plan: &mut UpdatePlan,\n) -> Result<()> {\n    let internal_id = program.table_reference_counter.next();\n    let ephemeral_table = Arc::new(BTreeTable {\n        root_page: 0, // Not relevant for ephemeral table definition\n        name: \"ephemeral_scratch\".to_string(),\n        has_rowid: true,\n        has_autoincrement: false,\n        primary_key_columns: vec![],\n        columns: vec![(*ROWID_COLUMN).clone()],\n        is_strict: false,\n        unique_sets: vec![],\n        foreign_keys: vec![],\n        check_constraints: vec![],\n        rowid_alias_conflict_clause: None,\n    });\n\n    let temp_cursor_id = program.alloc_cursor_id_keyed(\n        CursorKey::table(internal_id),\n        CursorType::BTreeTable(ephemeral_table.clone()),\n    );\n\n    // The actual update loop will use the ephemeral table as the single [JoinedTable] which it then loops over.\n    let table_references_update = TableReferences::new(\n        vec![JoinedTable {\n            table: Table::BTree(ephemeral_table.clone()),\n            identifier: \"ephemeral_scratch\".to_string(),\n            internal_id,\n            op: Operation::Scan(Scan::BTreeTable {\n                iter_dir: IterationDirection::Forwards,\n                index: None,\n            }),\n            join_info: None,\n            col_used_mask: ColumnUsedMask::default(),\n            column_use_counts: Vec::new(),\n            expression_index_usages: Vec::new(),\n            database_id: 0,\n            indexed: None,\n        }],\n        vec![],\n    );\n\n    // Building the ephemeral table will use the TableReferences from the original plan -- i.e. if we chose an index scan originally,\n    // we will build the ephemeral table by using the same index scan and using the same WHERE filters.\n    let table_references_ephemeral_select =\n        std::mem::replace(&mut plan.table_references, table_references_update);\n\n    for table in table_references_ephemeral_select.joined_tables() {\n        // The update loop needs to reference columns from the original source table, so we add it as an outer query reference.\n        plan.table_references\n            .add_outer_query_reference(OuterQueryReference {\n                identifier: table.identifier.clone(),\n                internal_id: table.internal_id,\n                table: table.table.clone(),\n                col_used_mask: table.col_used_mask.clone(),\n                cte_select: None,\n                cte_explicit_columns: vec![],\n                cte_id: None,\n                cte_definition_only: false,\n                rowid_referenced: false,\n            });\n    }\n\n    // Preserve outer query references (e.g. CTEs) from the original plan.\n    for outer_ref in table_references_ephemeral_select.outer_query_refs() {\n        plan.table_references\n            .add_outer_query_reference(outer_ref.clone());\n    }\n\n    let join_order = table_references_ephemeral_select\n        .joined_tables()\n        .iter()\n        .enumerate()\n        .map(|(i, t)| JoinOrderMember {\n            table_id: t.internal_id,\n            original_idx: i,\n            is_outer: t\n                .join_info\n                .as_ref()\n                .is_some_and(|join_info| join_info.is_outer()),\n        })\n        .collect();\n    let rowid_internal_id = table_references_ephemeral_select\n        .joined_tables()\n        .first()\n        .unwrap()\n        .internal_id;\n\n    let ephemeral_plan = SelectPlan {\n        table_references: table_references_ephemeral_select,\n        result_columns: vec![ResultSetColumn {\n            expr: Expr::RowId {\n                database: None,\n                table: rowid_internal_id,\n            },\n            alias: None,\n            contains_aggregates: false,\n        }],\n        where_clause: plan.where_clause.drain(..).collect(),\n        group_by: None,     // N/A\n        order_by: vec![],   // N/A\n        aggregates: vec![], // N/A\n        limit: None,        // N/A\n        query_destination: QueryDestination::EphemeralTable {\n            cursor_id: temp_cursor_id,\n            table: ephemeral_table,\n            rowid_mode: EphemeralRowidMode::FromResultColumns,\n        },\n        join_order,\n        offset: None,\n        contains_constant_false_condition: false,\n        distinctness: super::plan::Distinctness::NonDistinct,\n        values: vec![],\n        window: None,\n        input_cardinality_hint: None,\n        estimated_output_rows: None,\n        // Only move WHERE clause subqueries to the ephemeral plan.\n        // SET clause and RETURNING clause subqueries must remain in the main update plan\n        // because they compute new column values during the update phase (second pass),\n        // not during row collection (first pass). Moving them here would cause correlated\n        // subqueries in SET to evaluate with wrong cursor positions.\n        non_from_clause_subqueries: {\n            let update_phase_ids = collect_update_phase_subquery_ids(plan);\n            let mut ephemeral_subs = Vec::new();\n            let mut remaining = Vec::new();\n            for sq in plan.non_from_clause_subqueries.drain(..) {\n                if update_phase_ids.contains(&sq.internal_id) {\n                    remaining.push(sq);\n                } else {\n                    ephemeral_subs.push(sq);\n                }\n            }\n            plan.non_from_clause_subqueries = remaining;\n            ephemeral_subs\n        },\n        simple_aggregate: None,\n    };\n\n    plan.ephemeral_plan = Some(ephemeral_plan);\n\n    Ok(())\n}\n\nfn optimize_subqueries(plan: &mut SelectPlan, schema: &Schema) -> Result<()> {\n    for table in plan.table_references.joined_tables_mut() {\n        if let Table::FromClauseSubquery(from_clause_subquery) = &mut table.table {\n            let from_clause_subquery = Arc::make_mut(from_clause_subquery);\n            // Use match to handle both SelectPlan and CompoundSelect variants\n            match from_clause_subquery.plan.as_mut() {\n                Plan::Select(select_plan) => optimize_select_plan(select_plan, schema)?,\n                Plan::CompoundSelect {\n                    left, right_most, ..\n                } => {\n                    optimize_select_plan(right_most, schema)?;\n                    for (select_plan, _) in left {\n                        optimize_select_plan(select_plan, schema)?;\n                    }\n                }\n                Plan::Delete(_) | Plan::Update(_) => {\n                    turso_soft_unreachable!(\n                        \"DELETE/UPDATE plans should not appear in FROM clause subqueries\"\n                    );\n                    return Err(LimboError::InternalError(\n                        \"DELETE/UPDATE plans should not appear in FROM clause subqueries\"\n                            .to_string(),\n                    ));\n                }\n            }\n        }\n    }\n\n    Ok(())\n}\n\n/// Re-run correlated subqueries once the enclosing plan has learned a better\n/// estimate for how many times they will be invoked.\n///\n/// This converges because `input_cardinality_hint` is monotonic within one\n/// optimization pass: once a plan receives a hint, later re-entry compares\n/// against that stored hint and skips re-optimization unless the new hint is\n/// strictly larger. The recursive call therefore only propagates larger hints\n/// down the subquery tree; it does not oscillate based on newly estimated row\n/// counts.\nfn reoptimize_correlated_subqueries(plan: &mut SelectPlan, schema: &Schema) -> Result<()> {\n    let Some(invocation_hint) = plan\n        .input_cardinality_hint\n        .or(plan.estimated_output_rows)\n        .filter(|hint| *hint > 1.0)\n    else {\n        return Ok(());\n    };\n\n    for subquery in &mut plan.non_from_clause_subqueries {\n        if !subquery.correlated {\n            continue;\n        }\n        let SubqueryState::Unevaluated {\n            plan: Some(inner_plan),\n        } = &mut subquery.state\n        else {\n            continue;\n        };\n        if !select_plan_contains_cte_from_clause_subquery(inner_plan) {\n            continue;\n        }\n\n        if inner_plan\n            .input_cardinality_hint\n            .is_some_and(|hint| hint >= invocation_hint)\n        {\n            continue;\n        }\n\n        inner_plan.input_cardinality_hint = Some(invocation_hint);\n        optimize_select_plan(inner_plan, schema)?;\n    }\n\n    Ok(())\n}\n\n/// Return whether this plan contains any FROM-clause CTE reference whose\n/// access-path choice may change when the enclosing invocation count grows.\nfn select_plan_contains_cte_from_clause_subquery(plan: &SelectPlan) -> bool {\n    plan.table_references\n        .joined_tables()\n        .iter()\n        .any(|table| match &table.table {\n            Table::FromClauseSubquery(subquery) => {\n                subquery.cte_id().is_some()\n                    || match subquery.plan.as_ref() {\n                        Plan::Select(select_plan) => {\n                            select_plan_contains_cte_from_clause_subquery(select_plan)\n                        }\n                        Plan::CompoundSelect {\n                            left, right_most, ..\n                        } => {\n                            left.iter().any(|(select_plan, _)| {\n                                select_plan_contains_cte_from_clause_subquery(select_plan)\n                            }) || select_plan_contains_cte_from_clause_subquery(right_most)\n                        }\n                        Plan::Delete(_) | Plan::Update(_) => false,\n                    }\n            }\n            _ => false,\n        })\n}\n\n#[allow(clippy::too_many_arguments)]\nfn optimize_table_access_with_custom_modules(\n    result_columns: &mut [ResultSetColumn],\n    table_references: &mut TableReferences,\n    available_indexes: &HashMap<String, VecDeque<Arc<Index>>>,\n    where_query: &mut [WhereTerm],\n    order_by: &mut Vec<(Box<ast::Expr>, SortOrder)>,\n    group_by: &mut Option<GroupBy>,\n    limit: &mut Option<Box<Expr>>,\n    offset: &mut Option<Box<Expr>>,\n) -> Result<bool> {\n    let tables = table_references.joined_tables_mut();\n    if tables.is_empty() {\n        return Ok(false);\n    }\n\n    // group by is not supported for now\n    if group_by.is_some() {\n        return Ok(false);\n    }\n\n    // Only optimize the first table with custom index methods.\n    // This allows FTS to be used as the driving table in joins.\n    let table = &mut tables[0];\n    let Some(indexes) = available_indexes.get(table.table.get_name()) else {\n        return Ok(false);\n    };\n    for index in indexes {\n        let Some(module) = &index.index_method else {\n            continue;\n        };\n        if index.is_backing_btree_index() {\n            continue;\n        }\n        let definition = module.definition();\n        for (pattern_idx, pattern) in definition.patterns.iter().enumerate() {\n            let Some(pattern_match) = try_match_index_method_pattern(\n                pattern,\n                table,\n                where_query,\n                order_by,\n                limit,\n                offset,\n                pattern_idx,\n                false, // panic on binding failures\n            ) else {\n                continue;\n            };\n\n            // Mark WHERE clause as consumed\n            if let Some(where_covered) = pattern_match.where_covered {\n                where_query[where_covered].consumed = true;\n            }\n\n            // Build covered columns mapping and update result_columns.\n            // This differs from collect_index_method_candidates: we modify result_columns\n            // and increment covered_column_id per matching query column, not per pattern column.\n            let mut covered_column_id = 1_000_000;\n            let mut covered_columns = HashMap::default();\n            for (pattern_column_id, pattern_column) in\n                pattern_match.pattern_columns.iter().enumerate()\n            {\n                let ast::ResultColumn::Expr(pattern_expr, _) = pattern_column else {\n                    continue;\n                };\n                let Some(substituted) =\n                    try_substitute_parameters(pattern_expr, &pattern_match.parameters)\n                else {\n                    continue;\n                };\n                for query_column in result_columns.iter_mut() {\n                    if !exprs_are_equivalent(&query_column.expr, &substituted) {\n                        continue;\n                    }\n                    query_column.expr = ast::Expr::Column {\n                        database: None,\n                        table: table.internal_id,\n                        column: covered_column_id,\n                        is_rowid_alias: false,\n                    };\n                    covered_columns.insert(covered_column_id, pattern_column_id);\n                    covered_column_id += 1;\n                }\n            }\n\n            // Calculate whether WHERE is completely covered for ORDER BY/LIMIT clearing\n            let where_covered_completely = where_query.is_empty()\n                || (pattern_match.where_covered.is_some() && where_query.len() == 1);\n\n            // Only clear ORDER BY/LIMIT/OFFSET if:\n            // 1. The pattern explicitly handles them (has ORDER BY/LIMIT), AND\n            // 2. WHERE is completely covered (no post-filtering needed)\n            // Otherwise, keep them so they're applied after post-filtering\n            if pattern_match.pattern_has_order_by && where_covered_completely {\n                let _ = order_by.drain(..);\n            }\n            if pattern_match.pattern_has_limit && where_covered_completely {\n                let _ = limit.take();\n                let _ = offset.take();\n            }\n\n            // Sort and collect arguments\n            let arguments = sorted_arguments_from_parameters(&pattern_match.parameters);\n\n            table.op = Operation::IndexMethodQuery(IndexMethodQuery {\n                index: index.clone(),\n                pattern_idx: pattern_match.pattern_idx,\n                covered_columns,\n                arguments,\n            });\n            return Ok(true);\n        }\n    }\n    Ok(false)\n}\n\n/// We do a single pass over projected, grouping, and ordering expressions to\n/// capture every expression that could be served directly from an expression index.\n/// Example:\n///   CREATE INDEX idx ON t(lower(a));\n///   SELECT lower(a) FROM t ORDER BY lower(a);\n/// Both the SELECT list and ORDER BY can be covered by idx, avoiding a\n/// table cursor entirely. Recording them upfront lets both the cost model\n/// and covering checks reuse the same facts.\nfn register_expression_index_usages_for_plan(\n    table_references: &mut TableReferences,\n    result_columns: &[ResultSetColumn],\n    order_by: &[(Box<ast::Expr>, SortOrder)],\n    group_by: Option<&GroupBy>,\n) {\n    table_references.reset_expression_index_usages();\n    for rc in result_columns {\n        table_references.register_expression_index_usage(&rc.expr);\n    }\n    for (expr, _) in order_by {\n        table_references.register_expression_index_usage(expr);\n    }\n    if let Some(group_by) = group_by {\n        for expr in &group_by.exprs {\n            table_references.register_expression_index_usage(expr);\n        }\n        if let Some(having) = &group_by.having {\n            for expr in having {\n                table_references.register_expression_index_usage(expr);\n            }\n        }\n    }\n}\n\n/// Derive a base row-count estimate for a table, preferring ANALYZE stats.\nfn base_row_estimate(\n    schema: &Schema,\n    table: &JoinedTable,\n    params: &cost_params::CostModelParams,\n) -> RowCountEstimate {\n    match &table.table {\n        Table::BTree(btree) => {\n            if let Some(stats) = schema.analyze_stats.table_stats(&btree.name) {\n                if let Some(rows) = stats.row_count.or_else(|| {\n                    stats\n                        .index_stats\n                        .values()\n                        .find_map(|idx_stat| idx_stat.total_rows)\n                }) {\n                    return RowCountEstimate::AnalyzeStats(rows as f64);\n                }\n            }\n            RowCountEstimate::hardcoded_fallback(params)\n        }\n        Table::FromClauseSubquery(subquery) => match subquery.plan.as_ref() {\n            Plan::Select(plan) => {\n                if let Some(rows) = plan.estimated_output_rows {\n                    return RowCountEstimate::AnalyzeStats(rows.max(1.0));\n                }\n                RowCountEstimate::hardcoded_fallback(params)\n            }\n            Plan::CompoundSelect {\n                left, right_most, ..\n            } => {\n                // Combine estimates from all branches according to set operation semantics.\n                // left = [(A, op1), (B, op2)], right_most = C\n                // represents: (A op1 B) op2 C\n                // We fold left-to-right: seed with A, then apply op_i with plan_{i+1}.\n                let fallback = *RowCountEstimate::hardcoded_fallback(params);\n                let est = |p: &SelectPlan| p.estimated_output_rows.unwrap_or(fallback);\n                let mut combined = left.first().map_or(\n                    right_most.estimated_output_rows.unwrap_or(fallback),\n                    |(p, _)| est(p),\n                );\n                // The estimates to the right of each operator: left[1..].est, then right_most.est\n                let rhs_estimates = left\n                    .iter()\n                    .skip(1)\n                    .map(|(p, _)| est(p))\n                    .chain(std::iter::once(est(right_most)));\n                for ((_, op), rhs) in left.iter().zip(rhs_estimates) {\n                    combined = match op {\n                        ast::CompoundOperator::UnionAll | ast::CompoundOperator::Union => {\n                            combined + rhs\n                        }\n                        ast::CompoundOperator::Intersect => combined.min(rhs),\n                        ast::CompoundOperator::Except => combined,\n                    };\n                }\n                RowCountEstimate::AnalyzeStats(combined.max(1.0))\n            }\n            _ => RowCountEstimate::hardcoded_fallback(params),\n        },\n        _ => RowCountEstimate::hardcoded_fallback(params),\n    }\n}\n\n/// Returns true if a WHERE-term predicate is null-rejecting for a table.\n///\n/// Non-rejecting cases include:\n/// - `IS`/`IS NOT` comparisons, which can evaluate true for NULL-containing inputs.\n/// - Expressions that route the table's values through NULL-masking functions\n///   like `ifnull`/`coalesce`.\nfn where_term_is_null_rejecting_for_table(\n    expr: &ast::Expr,\n    operator: ConstraintOperator,\n    table_id: ast::TableInternalId,\n) -> bool {\n    if matches!(\n        operator,\n        ConstraintOperator::AstNativeOperator(ast::Operator::Is | ast::Operator::IsNot)\n    ) {\n        return false;\n    }\n\n    !expr_has_null_masking_for_table(expr, table_id)\n}\n\n/// Returns true if an expression references a column from `table_id`.\nfn expr_references_table(expr: &ast::Expr, table_id: ast::TableInternalId) -> bool {\n    use crate::translate::expr::{walk_expr, WalkControl};\n    let mut found = false;\n    let _ = walk_expr(expr, &mut |inner: &ast::Expr| -> Result<WalkControl> {\n        match inner {\n            ast::Expr::Column { table, .. } | ast::Expr::RowId { table, .. }\n                if *table == table_id =>\n            {\n                found = true;\n                return Ok(WalkControl::SkipChildren);\n            }\n            _ => {}\n        }\n        Ok(WalkControl::Continue)\n    });\n    found\n}\n\n/// Returns true if an expression is a NULL check on a column from `table_id`.\n/// Matches patterns like `col IS NULL`, `col IS NOT NULL`, `IsNull(col)`, `NotNull(col)`.\nfn is_null_check_on_table(expr: &ast::Expr, table_id: ast::TableInternalId) -> bool {\n    match expr {\n        ast::Expr::IsNull(inner) | ast::Expr::NotNull(inner) => {\n            expr_references_table(inner, table_id)\n        }\n        ast::Expr::Binary(lhs, ast::Operator::Is | ast::Operator::IsNot, rhs) => {\n            (matches!(rhs.as_ref(), ast::Expr::Literal(ast::Literal::Null))\n                && expr_references_table(lhs, table_id))\n                || (matches!(lhs.as_ref(), ast::Expr::Literal(ast::Literal::Null))\n                    && expr_references_table(rhs, table_id))\n        }\n        _ => false,\n    }\n}\n\n/// Returns true if an expression uses a NULL-masking construct over columns from `table_id`.\n/// This includes NULL-masking functions (COALESCE, IFNULL) and CASE/IIF expressions\n/// that explicitly handle the NULL case for columns from the target table.\nfn expr_has_null_masking_for_table(expr: &ast::Expr, table_id: ast::TableInternalId) -> bool {\n    use crate::translate::expr::{walk_expr, WalkControl};\n    let mut found = false;\n    let _ = walk_expr(expr, &mut |e: &ast::Expr| -> Result<WalkControl> {\n        match e {\n            ast::Expr::FunctionCall { name, args, .. } => {\n                if let Ok(func) = crate::function::Func::resolve_function(name.as_str(), args.len())\n                {\n                    // IIF(cond, then, else) is like CASE WHEN cond THEN then ELSE else END.\n                    // If the condition is a null check on the target table, IIF masks nulls.\n                    if matches!(\n                        func,\n                        crate::function::Func::Scalar(crate::function::ScalarFunc::Iif)\n                    ) {\n                        if let Some(cond) = args.first() {\n                            if is_null_check_on_table(cond, table_id) {\n                                found = true;\n                                return Ok(WalkControl::SkipChildren);\n                            }\n                        }\n                        return Ok(WalkControl::Continue);\n                    }\n                    if !func.can_mask_nulls() {\n                        return Ok(WalkControl::Continue);\n                    }\n                    for arg in args {\n                        if expr_references_table(arg, table_id) {\n                            found = true;\n                            return Ok(WalkControl::SkipChildren);\n                        }\n                    }\n                }\n            }\n            // CASE WHEN <null-check-on-table> THEN ... ELSE ... END\n            // If any WHEN condition checks for NULL on a column from the target table,\n            // the CASE explicitly handles NULLs and can produce non-NULL results.\n            ast::Expr::Case {\n                when_then_pairs, ..\n            } => {\n                for (when_expr, _) in when_then_pairs {\n                    if is_null_check_on_table(when_expr, table_id) {\n                        found = true;\n                        return Ok(WalkControl::SkipChildren);\n                    }\n                }\n            }\n            _ => {}\n        }\n        Ok(WalkControl::Continue)\n    });\n    found\n}\n\n/// Enforce INDEXED BY / NOT INDEXED hints by validating index existence and\n/// filtering constraint candidates accordingly.\nfn enforce_indexed_by_hints(\n    table_references: &TableReferences,\n    available_indexes: &HashMap<String, VecDeque<Arc<Index>>>,\n    constraints_per_table: &mut [TableConstraints],\n) -> Result<()> {\n    for (i, table_ref) in table_references.joined_tables().iter().enumerate() {\n        let Some(ref indexed) = table_ref.indexed else {\n            continue;\n        };\n        let Some(btree) = table_ref.btree() else {\n            continue;\n        };\n        let Some(cs) = constraints_per_table.get_mut(i) else {\n            continue;\n        };\n        match indexed {\n            ast::Indexed::IndexedBy(name) => {\n                let idx_name = name.as_str();\n                // Verify the index exists and belongs to this table.\n                let forced_index = available_indexes.get(&btree.name).and_then(|indexes| {\n                    indexes.iter().find(|idx| {\n                        idx.name.eq_ignore_ascii_case(idx_name) && idx.index_method.is_none()\n                    })\n                });\n                let Some(forced_index) = forced_index else {\n                    crate::bail_parse_error!(\"no such index: {}\", idx_name);\n                };\n                // Keep only the candidate for the forced index.\n                let forced_index = forced_index.clone();\n                cs.candidates.retain(|c| {\n                    c.index\n                        .as_ref()\n                        .is_some_and(|idx| Arc::ptr_eq(idx, &forced_index))\n                });\n                // If no candidate survived (no WHERE constraints matched), add an empty one\n                // so the optimizer can still scan the index.\n                if cs.candidates.is_empty() {\n                    cs.candidates.push(ConstraintUseCandidate {\n                        index: Some(forced_index),\n                        refs: Vec::new(),\n                    });\n                }\n            }\n            ast::Indexed::NotIndexed => {\n                // Remove all secondary index candidates, keep only rowid.\n                cs.candidates.retain(|c| c.index.is_none());\n            }\n        }\n    }\n    Ok(())\n}\n\n/// Optimize the join order and index selection for a query.\n///\n/// This function does the following:\n/// - Computes a set of [Constraint]s for each table.\n/// - Using those constraints, computes the best join order for the list of [TableReference]s\n///   and selects the best [crate::translate::optimizer::access_method::AccessMethod] for each table in the join order.\n/// - Mutates the [Operation]s in `joined_tables` to use the selected access methods.\n/// - Removes predicates from the `where_clause` that are now redundant due to the selected access methods.\n/// - Removes sorting operations if the selected join order and access methods satisfy the [crate::translate::optimizer::order::OrderTarget].\n///\n/// Returns the join order if it was optimized, or None if the default join order was considered best.\n#[allow(clippy::too_many_arguments)]\nfn optimize_table_access(\n    schema: &Schema,\n    result_columns: &mut [ResultSetColumn],\n    table_references: &mut TableReferences,\n    available_indexes: &HashMap<String, VecDeque<Arc<Index>>>,\n    where_clause: &mut [WhereTerm],\n    order_by: &mut Vec<(Box<ast::Expr>, SortOrder)>,\n    group_by: &mut Option<GroupBy>,\n    simple_aggregate: Option<&SimpleAggregate>,\n    subqueries: &[NonFromClauseSubquery],\n    limit: &mut Option<Box<Expr>>,\n    offset: &mut Option<Box<Expr>>,\n    initial_input_cardinality: f64,\n) -> Result<Option<OptimizeTableAccessResult>> {\n    // When optimizer_params feature is enabled, use lazily-loaded params (cached process-wide).\n    // Otherwise, use the compile-time static for zero overhead.\n    #[cfg(feature = \"optimizer_params\")]\n    let params: &cost_params::CostModelParams = &cost_params::LOADED_PARAMS;\n    #[cfg(not(feature = \"optimizer_params\"))]\n    let params: &cost_params::CostModelParams = &cost_params::DEFAULT_PARAMS;\n\n    if table_references.joined_tables().is_empty() {\n        return Ok(None);\n    }\n    if table_references.joined_tables().len() > TableReferences::MAX_JOINED_TABLES {\n        crate::bail_parse_error!(\n            \"Only up to {} tables can be joined\",\n            TableReferences::MAX_JOINED_TABLES\n        );\n    }\n\n    let has_expression_index = table_references.joined_tables().iter().any(|t| {\n        matches!(&t.table, Table::BTree(btree) if available_indexes\n            .get(&btree.name)\n            .is_some_and(|indexes| indexes.iter().any(|index| index.is_expression_index())))\n    });\n\n    if has_expression_index {\n        register_expression_index_usages_for_plan(\n            table_references,\n            result_columns,\n            order_by.as_slice(),\n            group_by.as_ref(),\n        );\n    }\n\n    // For single-table queries, try to optimize with custom index methods directly.\n    // This is the fast path that preserves the original behavior.\n    // Skip when INDEXED BY / NOT INDEXED is specified — those force a specific btree index or table scan.\n    let is_single_table = table_references.joined_tables().len() == 1;\n    let has_indexed_by_hint = table_references\n        .joined_tables()\n        .iter()\n        .any(|t| t.indexed.is_some());\n    if is_single_table && !has_indexed_by_hint {\n        let optimized = optimize_table_access_with_custom_modules(\n            result_columns,\n            table_references,\n            available_indexes,\n            where_clause,\n            order_by,\n            group_by,\n            limit,\n            offset,\n        )?;\n        if optimized {\n            return Ok(None);\n        }\n    }\n\n    let mut access_methods_arena = Vec::new();\n\n    // For multi-table queries, collect index method candidates to pass to the DP algorithm.\n    // This allows the optimizer to consider index methods at any position in the join order.\n    let base_table_rows_for_candidates = table_references\n        .joined_tables()\n        .iter()\n        .map(|t| base_row_estimate(schema, t, params))\n        .collect::<Vec<_>>();\n\n    let index_method_candidates = if !is_single_table {\n        collect_index_method_candidates(\n            table_references,\n            available_indexes,\n            where_clause,\n            order_by,\n            group_by,\n            limit,\n            offset,\n            &base_table_rows_for_candidates,\n            params,\n        )?\n    } else {\n        Vec::new()\n    };\n    let maybe_order_target = simple_aggregate\n        .and_then(|sa| simple_aggregate_order_target(sa, table_references))\n        .or_else(|| compute_order_target(order_by, group_by.as_mut(), table_references));\n    let mut constraints_per_table = constraints_from_where_clause(\n        where_clause,\n        table_references,\n        available_indexes,\n        subqueries,\n        schema,\n        params,\n    )?;\n\n    // Enforce INDEXED BY / NOT INDEXED hints on constraint candidates.\n    enforce_indexed_by_hints(\n        table_references,\n        available_indexes,\n        &mut constraints_per_table,\n    )?;\n\n    let base_table_rows = table_references\n        .joined_tables()\n        .iter()\n        .map(|t| base_row_estimate(schema, t, params))\n        .collect::<Vec<_>>();\n\n    // Currently the expressions we evaluate as constraints are binary comparisons that (except for IS/IS NOT)\n    // will never be true for a NULL operand.\n    // If there are any constraints on the right hand side table of an outer join that are not part of the outer join condition,\n    // the outer join can be converted into an inner join.\n    // for example:\n    // - SELECT * FROM t1 LEFT JOIN t2 ON false WHERE t2.id = 5\n    // there can never be a situation where null columns are emitted for t2 because t2.id = 5 will never be true in that case.\n    // hence: we can convert the outer join into an inner join.\n    //\n    // Converting a LEFT JOIN into an INNER JOIN is an optimization opportunity:\n    // it can enable join reordering and let more predicates participate in key selection.\n    // -> recompute constraints if we rewrote a LEFT JOIN into an INNER JOIN.\n    loop {\n        let mut outer_join_rewritten = false;\n        for (i, t) in table_references\n            .joined_tables_mut()\n            .iter_mut()\n            .enumerate()\n            .filter(|(_, t)| {\n                t.join_info\n                    .as_ref()\n                    // Skip FULL OUTER JOIN tables: removing `outer` would suppress\n                    // unmatched-probe-row emission and prevent LeftJoinMetadata\n                    // allocation needed by the hash join.\n                    .is_some_and(|join_info| join_info.is_outer() && !join_info.is_full_outer())\n            })\n        {\n            // Check if there's a constraint that would filter out NULL rows,\n            // allowing us to convert the LEFT JOIN into an INNER JOIN for join reordering purposes.\n            // Most binary ops like x = foo filter out NULL rows, but\n            // IS NULL constraints do NOT - they specifically KEEP them.\n            // So we should not convert LEFT JOIN to INNER JOIN based on IS NULL constraints.\n            // Also, expressions wrapped in ifnull()/coalesce() are NOT null-rejecting because\n            // they explicitly handle NULLs and can produce non-NULL values for NULL inputs.\n            if constraints_per_table[i].constraints.iter().any(|c| {\n                let is_from_where = where_clause[c.where_clause_pos.0].from_outer_join.is_none();\n                let is_null_rejecting = where_term_is_null_rejecting_for_table(\n                    &where_clause[c.where_clause_pos.0].expr,\n                    c.operator,\n                    t.internal_id,\n                );\n                is_from_where && is_null_rejecting\n            }) {\n                t.join_info.as_mut().unwrap().join_type = JoinType::Inner;\n                for term in where_clause.iter_mut() {\n                    if let Some(from_outer_join) = term.from_outer_join {\n                        if from_outer_join == t.internal_id {\n                            term.from_outer_join = None;\n                        }\n                    }\n                }\n                outer_join_rewritten = true;\n            }\n        }\n        if !outer_join_rewritten {\n            break;\n        }\n        constraints_per_table = constraints_from_where_clause(\n            where_clause,\n            table_references,\n            available_indexes,\n            subqueries,\n            schema,\n            params,\n        )?;\n        enforce_indexed_by_hints(\n            table_references,\n            available_indexes,\n            &mut constraints_per_table,\n        )?;\n    }\n\n    let planning_context = JoinPlanningContext {\n        maybe_order_target: maybe_order_target.as_ref(),\n    };\n\n    let Some(best_join_order_result) = compute_best_join_order_with_context(\n        table_references.joined_tables(),\n        initial_input_cardinality,\n        planning_context,\n        &constraints_per_table,\n        &base_table_rows,\n        &mut access_methods_arena,\n        where_clause,\n        subqueries,\n        &index_method_candidates,\n        params,\n        &schema.analyze_stats,\n        available_indexes,\n        table_references,\n        schema,\n    )?\n    else {\n        return Ok(None);\n    };\n\n    let BestJoinOrderResult {\n        best_plan,\n        best_ordered_plan,\n    } = best_join_order_result;\n\n    // See if best_ordered_plan is better than the overall best_plan if we add a sorting penalty\n    // to the unordered plan's cost.\n    let best_plan = if let Some(best_ordered_plan) = best_ordered_plan {\n        let best_unordered_plan_cost = best_plan.cost;\n        let best_ordered_plan_cost = best_ordered_plan.cost;\n        const SORT_COST_PER_ROW_MULTIPLIER: f64 = 0.001;\n        let sorting_penalty = Cost(best_plan.output_cardinality * SORT_COST_PER_ROW_MULTIPLIER);\n        if best_unordered_plan_cost + sorting_penalty > best_ordered_plan_cost {\n            best_ordered_plan\n        } else {\n            best_plan\n        }\n    } else {\n        best_plan\n    };\n\n    let final_output_cardinality = best_plan.output_cardinality;\n\n    let mut sort_eliminated = false;\n\n    // Eliminate sorting if possible.\n    if let Some(order_target) = maybe_order_target.as_ref() {\n        let satisfies_order_target = plan_satisfies_order_target(\n            &best_plan,\n            &access_methods_arena,\n            table_references.joined_tables_mut(),\n            order_target,\n            schema,\n        );\n        if satisfies_order_target {\n            match &order_target.purpose {\n                OrderTargetPurpose::EliminatesSort(EliminatesSortBy::Group) => {\n                    if let Some(g) = group_by.as_mut() {\n                        g.sort_elided = true;\n                    }\n                }\n                OrderTargetPurpose::EliminatesSort(EliminatesSortBy::Order) => {\n                    order_by.clear();\n                }\n                OrderTargetPurpose::EliminatesSort(EliminatesSortBy::GroupByAndOrder) => {\n                    if let Some(g) = group_by.as_mut() {\n                        g.sort_elided = true;\n                    }\n                    order_by.clear();\n                }\n                OrderTargetPurpose::Extremum => {}\n            }\n        }\n        sort_eliminated = satisfies_order_target;\n    }\n\n    let (best_access_methods, best_table_numbers) = (\n        best_plan.best_access_methods().collect::<Vec<_>>(),\n        best_plan.table_numbers().collect::<Vec<_>>(),\n    );\n\n    // Collect hash join build/probe table indices. Build tables are excluded from the main\n    // join order because they are consumed during hash build. A table may appear as both\n    // probe and build (probe->build chaining) only when the build input is materialized.\n    let (hash_join_build_tables, hash_join_probe_tables): (Vec<usize>, Vec<usize>) =\n        best_access_methods\n            .iter()\n            .filter_map(|&am_idx| {\n                let arena = &access_methods_arena;\n                arena.get(am_idx).and_then(|am| {\n                    if let AccessMethodParams::HashJoin {\n                        build_table_idx,\n                        probe_table_idx,\n                        ..\n                    } = &am.params\n                    {\n                        Some((*build_table_idx, *probe_table_idx))\n                    } else {\n                        None\n                    }\n                })\n            })\n            .unzip();\n    #[cfg(debug_assertions)]\n    {\n        let mut probe_tables: HashSet<usize> = HashSet::default();\n        let mut build_tables: HashMap<usize, bool> = HashMap::default();\n        let mut pos_by_table: Vec<Option<usize>> =\n            vec![None; table_references.joined_tables().len()];\n        for (pos, table_idx) in best_table_numbers.iter().enumerate() {\n            pos_by_table[*table_idx] = Some(pos);\n        }\n\n        for &am_idx in best_access_methods.iter() {\n            let arena = &access_methods_arena;\n            let Some(am) = arena.get(am_idx) else {\n                continue;\n            };\n            if let AccessMethodParams::HashJoin {\n                build_table_idx,\n                probe_table_idx,\n                materialize_build_input,\n                ..\n            } = &am.params\n            {\n                if let (Some(build_pos), Some(probe_pos)) = (\n                    pos_by_table[*build_table_idx],\n                    pos_by_table[*probe_table_idx],\n                ) {\n                    turso_assert!(\n                        probe_pos == build_pos + 1,\n                        \"hash join build/probe tables are not adjacent in join order\"\n                    );\n                }\n                probe_tables.insert(*probe_table_idx);\n                build_tables.insert(*build_table_idx, *materialize_build_input);\n            }\n        }\n\n        for (build_table_idx, materialize_build_input) in build_tables {\n            if probe_tables.contains(&build_table_idx) {\n                turso_assert!(\n                    materialize_build_input,\n                    \"probe->build chaining requires materialized build input\"\n                );\n            }\n        }\n    }\n    let hash_join_build_only_tables: HashSet<usize> = hash_join_build_tables\n        .iter()\n        .copied()\n        .filter(|table_idx| !hash_join_probe_tables.contains(table_idx))\n        .collect();\n\n    let best_join_order: Vec<JoinOrderMember> = best_table_numbers\n        .iter()\n        .filter(|table_number| {\n            !hash_join_build_tables.contains(table_number)\n                || hash_join_probe_tables.contains(table_number)\n        })\n        .map(|&table_number| JoinOrderMember {\n            table_id: table_references.joined_tables_mut()[table_number].internal_id,\n            original_idx: table_number,\n            is_outer: table_references.joined_tables_mut()[table_number]\n                .join_info\n                .as_ref()\n                .is_some_and(|join_info| join_info.is_outer()),\n        })\n        .collect();\n\n    // Mutate the Operations in `joined_tables` to use the selected access methods.\n    // We iterate over ALL tables (including hash join build tables) to set their operations,\n    // even though build tables are not in best_join_order.\n    for (i, &table_idx) in best_table_numbers.iter().enumerate() {\n        // Skip tables that already have an IndexMethodQuery operation set.\n        // This happens when the first table was optimized with a custom index (e.g., FTS)\n        // and we're continuing to optimize remaining tables in a multi-table query.\n        if matches!(\n            table_references.joined_tables()[table_idx].op,\n            Operation::IndexMethodQuery(_)\n        ) {\n            continue;\n        }\n        let access_method = &mut access_methods_arena[best_access_methods[i]];\n        match &mut access_method.params {\n            AccessMethodParams::BTreeTable {\n                iter_dir,\n                index,\n                constraint_refs,\n            } => {\n                maybe_remove_index_candidate(\n                    index,\n                    &table_references.joined_tables()[table_idx],\n                    maybe_order_target.as_ref(),\n                    sort_eliminated,\n                );\n                if constraint_refs.is_empty() {\n                    let is_leftmost_table = i == 0;\n                    let uses_index = index.is_some();\n                    let try_to_build_ephemeral_index = !is_leftmost_table && !uses_index;\n\n                    if !try_to_build_ephemeral_index {\n                        table_references.joined_tables_mut()[table_idx].op =\n                            Operation::Scan(Scan::BTreeTable {\n                                iter_dir: *iter_dir,\n                                index: index.clone(),\n                            });\n                        continue;\n                    }\n                    // This branch means we have a full table scan for a non-outermost table.\n                    // Try to construct an ephemeral index since it's going to be better than a scan.\n                    let table_id = table_references.joined_tables()[table_idx].internal_id;\n                    let table_constraints = constraints_per_table\n                        .iter()\n                        .find(|c| c.table_id == table_id);\n                    let Some(table_constraints) = table_constraints else {\n                        table_references.joined_tables_mut()[table_idx].op =\n                            Operation::Scan(Scan::BTreeTable {\n                                iter_dir: *iter_dir,\n                                index: index.clone(),\n                            });\n                        continue;\n                    };\n                    // Ephemeral indexes mirror rowid/column lookups. If the constraint targets an\n                    // expression (table_col_pos == None) we cannot derive a seek key that matches\n                    // the row layout, so fall back to a scan in that situation.\n                    let usable: Vec<(usize, &Constraint)> = table_constraints\n                        .constraints\n                        .iter()\n                        .enumerate()\n                        .filter(|(_, c)| c.usable && c.table_col_pos.is_some())\n                        .collect();\n                    // Find this table's position in best_join_order (which excludes build tables)\n                    let join_order_pos = best_join_order\n                        .iter()\n                        .position(|m| m.original_idx == table_idx)\n                        .unwrap_or_else(|| best_join_order.len().saturating_sub(1));\n\n                    // Build a mapping from table_col_pos to index_col_pos.\n                    // Multiple constraints on the same column should share the same index_col_pos.\n                    //\n                    // This is important when a column appears in multiple constraints.\n                    // For example, in:\n                    //   SELECT * FROM t1 LEFT JOIN t2 ON t1.a = t2.a AND t1.c = t2.c WHERE t2.a = 17\n                    //\n                    // The constraints on t2 are:\n                    //   t2.a = t1.a (from ON clause)\n                    //   t2.c = t1.c (from ON clause)\n                    //   t2.a = 17   (from WHERE clause)\n                    //\n                    // Both t2.a constraints must map to index_col_pos=0. If we incorrectly\n                    // assigned sequential index positions (0, 1, 2), the seek key would include\n                    // 3 components but the ephemeral index only has 2 key columns (t2.a, t2.c),\n                    // causing the seek to compare against the wrong columns and return no results.\n                    let mut unique_col_positions: Vec<usize> = usable\n                        .iter()\n                        .map(|(_, c)| c.table_col_pos.expect(\"table_col_pos was Some above\"))\n                        .collect();\n                    unique_col_positions.sort_unstable();\n                    unique_col_positions.dedup();\n                    // Map each usable constraint to a ConstraintRef.\n                    // Multiple constraints with the same table_col_pos share the same index_col_pos.\n                    let mut temp_constraint_refs: Vec<ConstraintRef> = usable\n                        .iter()\n                        .map(|(orig_idx, c)| {\n                            let table_col_pos =\n                                c.table_col_pos.expect(\"table_col_pos was Some above\");\n                            let index_col_pos = unique_col_positions\n                                .binary_search(&table_col_pos)\n                                .expect(\"table_col_pos must exist in unique_col_positions\");\n                            ConstraintRef {\n                                constraint_vec_pos: *orig_idx, // index in the original constraints vec\n                                index_col_pos,\n                                sort_order: SortOrder::Asc,\n                            }\n                        })\n                        .collect();\n\n                    temp_constraint_refs.sort_by_key(|x| x.index_col_pos);\n                    let usable_constraint_refs = usable_constraints_for_join_order(\n                        &table_constraints.constraints,\n                        &temp_constraint_refs,\n                        &best_join_order[..=join_order_pos],\n                    );\n\n                    if usable_constraint_refs.is_empty() {\n                        table_references.joined_tables_mut()[table_idx].op =\n                            Operation::Scan(Scan::BTreeTable {\n                                iter_dir: *iter_dir,\n                                index: index.clone(),\n                            });\n                        continue;\n                    }\n                    let ephemeral_index = ephemeral_index_build(\n                        &table_references.joined_tables_mut()[table_idx],\n                        &usable_constraint_refs,\n                    );\n\n                    mark_seek_constraints_consumed(\n                        &table_constraints.constraints,\n                        &usable_constraint_refs,\n                        where_clause,\n                        table_references.joined_tables()[table_idx]\n                            .join_info\n                            .as_ref()\n                            .is_some_and(|ji| ji.is_outer()),\n                        hash_join_build_only_tables.contains(&table_idx),\n                    );\n\n                    let ephemeral_index = Arc::new(ephemeral_index);\n                    table_references.joined_tables_mut()[table_idx].op =\n                        Operation::Search(Search::Seek {\n                            index: Some(ephemeral_index),\n                            seek_def: build_seek_def_from_constraints(\n                                &table_constraints.constraints,\n                                &usable_constraint_refs,\n                                *iter_dir,\n                                where_clause,\n                                Some(table_references),\n                            )?,\n                        });\n                } else {\n                    let is_outer_join = table_references.joined_tables_mut()[table_idx]\n                        .join_info\n                        .as_ref()\n                        .is_some_and(|join_info| join_info.is_outer());\n                    let defer_cross_table_constraints =\n                        hash_join_build_only_tables.contains(&table_idx);\n                    mark_seek_constraints_consumed(\n                        &constraints_per_table[table_idx].constraints,\n                        constraint_refs,\n                        where_clause,\n                        is_outer_join,\n                        defer_cross_table_constraints,\n                    );\n                    if let Some(index) = &index {\n                        table_references.joined_tables_mut()[table_idx].op =\n                            Operation::Search(Search::Seek {\n                                index: Some(index.clone()),\n                                seek_def: build_seek_def_from_constraints(\n                                    &constraints_per_table[table_idx].constraints,\n                                    constraint_refs,\n                                    *iter_dir,\n                                    where_clause,\n                                    Some(table_references),\n                                )?,\n                            });\n                        continue;\n                    }\n                    turso_assert_eq!(\n                        constraint_refs.len(),\n                        1,\n                        \"expected exactly one constraint for rowid seek\",\n                        {\"constraint_refs\": format!(\"{constraint_refs:?}\")}\n                    );\n                    table_references.joined_tables_mut()[table_idx].op =\n                        if let Some(ref eq) = constraint_refs[0].eq {\n                            Operation::Search(Search::RowidEq {\n                                cmp_expr: constraints_per_table[table_idx].constraints\n                                    [eq.constraint_pos]\n                                    .get_constraining_expr(where_clause, Some(table_references))\n                                    .1,\n                            })\n                        } else {\n                            Operation::Search(Search::Seek {\n                                index: None,\n                                seek_def: build_seek_def_from_constraints(\n                                    &constraints_per_table[table_idx].constraints,\n                                    constraint_refs,\n                                    *iter_dir,\n                                    where_clause,\n                                    Some(table_references),\n                                )?,\n                            })\n                        };\n                }\n            }\n            AccessMethodParams::VirtualTable {\n                idx_num,\n                idx_str,\n                constraints,\n                constraint_usages,\n            } => {\n                table_references.joined_tables_mut()[table_idx].op = build_vtab_scan_op(\n                    where_clause,\n                    &constraints_per_table[table_idx],\n                    idx_num,\n                    idx_str,\n                    constraints,\n                    constraint_usages,\n                    Some(table_references),\n                )?;\n            }\n            AccessMethodParams::Subquery { iter_dir } => {\n                table_references.joined_tables_mut()[table_idx].op =\n                    Operation::Scan(Scan::Subquery {\n                        iter_dir: *iter_dir,\n                    });\n            }\n            AccessMethodParams::MaterializedSubquery {\n                index,\n                constraint_refs,\n                iter_dir,\n            } => {\n                let table_constraints = constraints_per_table\n                    .iter()\n                    .find(|c| c.table_id == table_references.joined_tables()[table_idx].internal_id)\n                    .expect(\"should have constraints for this table\");\n\n                mark_seek_constraints_consumed(\n                    &table_constraints.constraints,\n                    constraint_refs,\n                    where_clause,\n                    false,\n                    false,\n                );\n\n                // Build seek definition from the constraints\n                let seek_def = build_seek_def_from_constraints(\n                    &table_constraints.constraints,\n                    constraint_refs,\n                    *iter_dir,\n                    where_clause,\n                    Some(table_references),\n                )?;\n\n                table_references.joined_tables_mut()[table_idx].op =\n                    Operation::Search(Search::Seek {\n                        index: Some(index.clone()),\n                        seek_def,\n                    });\n            }\n            AccessMethodParams::HashJoin {\n                build_table_idx,\n                probe_table_idx,\n                join_keys,\n                mem_budget,\n                materialize_build_input,\n                use_bloom_filter,\n                join_type,\n            } => {\n                // Mark WHERE clause terms as consumed since we're using hash join\n                for join_key in join_keys.iter() {\n                    where_clause[join_key.where_clause_idx].consumed = true;\n                }\n                // Set up hash join operation on the probe table\n                table_references.joined_tables_mut()[table_idx].op =\n                    Operation::HashJoin(HashJoinOp {\n                        build_table_idx: *build_table_idx,\n                        probe_table_idx: *probe_table_idx,\n                        join_keys: join_keys.clone(),\n                        mem_budget: *mem_budget,\n                        materialize_build_input: *materialize_build_input,\n                        use_bloom_filter: *use_bloom_filter,\n                        join_type: *join_type,\n                    });\n            }\n            AccessMethodParams::IndexMethod {\n                query,\n                where_covered,\n            } => {\n                // Mark WHERE clause term as consumed if the index method covered it\n                if let Some(idx) = where_covered {\n                    where_clause[*idx].consumed = true;\n                }\n                // Set up the index method query operation\n                table_references.joined_tables_mut()[table_idx].op =\n                    Operation::IndexMethodQuery(query.clone());\n            }\n            AccessMethodParams::MultiIndexScan {\n                branches,\n                where_term_idx,\n                set_op,\n            } => {\n                // Mark the primary WHERE clause term as consumed\n                where_clause[*where_term_idx].consumed = true;\n                // For intersection, also mark additional consumed terms\n                if let SetOperation::Intersection {\n                    additional_consumed_terms,\n                } = set_op\n                {\n                    for term_idx in additional_consumed_terms.iter() {\n                        where_clause[*term_idx].consumed = true;\n                    }\n                }\n\n                let w_idx = *where_term_idx;\n                let s_op = set_op.clone();\n                // Build the MultiIndexScanOp from the branch parameters\n                let mut multi_idx_branches = Vec::with_capacity(branches.len());\n                for branch in std::mem::take(branches) {\n                    let access = match branch.access {\n                        MultiIndexBranchAccessParams::Seek {\n                            constraints,\n                            constraint_refs,\n                        } => MultiIndexBranchAccess::Seek {\n                            seek_def: build_seek_def_from_constraints(\n                                &constraints,\n                                &constraint_refs,\n                                IterationDirection::Forwards, // Multi-index always scans forward\n                                where_clause,\n                                Some(table_references),\n                            )?,\n                        },\n                        MultiIndexBranchAccessParams::InSeek { source } => {\n                            MultiIndexBranchAccess::InSeek { source }\n                        }\n                    };\n                    multi_idx_branches.push(MultiIndexBranch {\n                        index: branch.index,\n                        access,\n                        estimated_rows: branch.estimated_rows,\n                        union_residuals: branch.residuals,\n                    });\n                }\n\n                table_references.joined_tables_mut()[table_idx].op =\n                    Operation::MultiIndexScan(MultiIndexScanOp {\n                        branches: multi_idx_branches,\n                        where_term_idx: w_idx,\n                        set_op: s_op,\n                    });\n            }\n            AccessMethodParams::InSeek {\n                index,\n                affinity,\n                where_term_idx,\n            } => {\n                let source = match &where_clause[*where_term_idx].expr {\n                    Expr::InList { rhs, .. } => {\n                        let in_values: Vec<ast::Expr> = rhs.iter().map(|e| *e.clone()).collect();\n                        InSeekSource::LiteralList {\n                            values: in_values,\n                            affinity: *affinity,\n                        }\n                    }\n                    Expr::SubqueryResult {\n                        query_type: SubqueryType::In { cursor_id, .. },\n                        ..\n                    } => InSeekSource::Subquery {\n                        cursor_id: *cursor_id,\n                    },\n                    _ => {\n                        return Err(crate::LimboError::InternalError(\n                            \"InSeek where term is not an InList or SubqueryResult expression\"\n                                .into(),\n                        ));\n                    }\n                };\n                where_clause[*where_term_idx].consumed = true;\n                table_references.joined_tables_mut()[table_idx].op =\n                    Operation::Search(Search::InSeek {\n                        index: index.clone(),\n                        source,\n                    });\n            }\n        }\n    }\n\n    let mut probe_pos_by_table: Vec<Option<usize>> =\n        vec![None; table_references.joined_tables().len()];\n    for (pos, member) in best_join_order.iter().enumerate() {\n        let table = &table_references.joined_tables()[member.original_idx];\n        if matches!(table.op, Operation::HashJoin(_)) {\n            probe_pos_by_table[member.original_idx] = Some(pos);\n        }\n    }\n\n    // If hash-join build constraints are still evaluated later (not consumed),\n    // avoid materializing the build input to reduce redundant scans.\n    for table in table_references.joined_tables_mut().iter_mut() {\n        let Operation::HashJoin(hash_join_op) = &mut table.op else {\n            continue;\n        };\n        if !hash_join_op.materialize_build_input {\n            continue;\n        }\n        let Some(probe_pos) = best_join_order\n            .iter()\n            .position(|member| member.original_idx == hash_join_op.probe_table_idx)\n        else {\n            continue;\n        };\n        let build_table_was_prior_probe = probe_pos_by_table\n            .get(hash_join_op.build_table_idx)\n            .copied()\n            .flatten()\n            .is_some_and(|pos| pos < probe_pos);\n        if build_table_was_prior_probe {\n            continue;\n        }\n        let prior_mask = TableMask::from_table_number_iter(\n            best_join_order[..probe_pos]\n                .iter()\n                .map(|member| member.original_idx),\n        );\n        let join_key_indices: HashSet<usize> = hash_join_op\n            .join_keys\n            .iter()\n            .map(|key| key.where_clause_idx)\n            .collect();\n        let build_constraints = &constraints_per_table[hash_join_op.build_table_idx];\n        let mut has_prior_constraints = false;\n        for constraint in build_constraints.constraints.iter() {\n            if !constraint.lhs_mask.intersects(&prior_mask) {\n                continue;\n            }\n            if join_key_indices.contains(&constraint.where_clause_pos.0) {\n                continue;\n            }\n            has_prior_constraints = true;\n            break;\n        }\n        if !has_prior_constraints {\n            hash_join_op.materialize_build_input = false;\n        }\n    }\n\n    Ok(Some(OptimizeTableAccessResult {\n        join_order: best_join_order,\n        output_rows: final_output_cardinality,\n        min_max_fast_path: matches!(simple_aggregate, Some(SimpleAggregate::MinMax(_)))\n            && sort_eliminated,\n    }))\n}\n\nfn build_vtab_scan_op(\n    where_clause: &mut [WhereTerm],\n    table_constraints: &TableConstraints,\n    idx_num: &i32,\n    idx_str: &Option<String>,\n    vtab_constraints: &[ConstraintInfo],\n    constraint_usages: &[ConstraintUsage],\n    referenced_tables: Option<&TableReferences>,\n) -> Result<Operation> {\n    if constraint_usages.len() != vtab_constraints.len() {\n        return Err(LimboError::ExtensionError(format!(\n            \"Constraint usage count mismatch (expected {}, got {})\",\n            vtab_constraints.len(),\n            constraint_usages.len()\n        )));\n    }\n\n    let mut constraints = vec![None; constraint_usages.len()];\n    let mut arg_count = 0;\n\n    for (i, vtab_constraint) in vtab_constraints.iter().enumerate() {\n        let usage = constraint_usages[i];\n        let argv_index = match usage.argv_index {\n            Some(idx) if idx >= 1 && (idx as usize) <= constraint_usages.len() => idx,\n            Some(idx) => {\n                return Err(LimboError::ExtensionError(format!(\n                    \"argv_index {} is out of valid range [1..{}]\",\n                    idx,\n                    constraint_usages.len()\n                )));\n            }\n            None => continue,\n        };\n\n        let zero_based_argv_index = (argv_index - 1) as usize;\n        if constraints[zero_based_argv_index].is_some() {\n            return Err(LimboError::ExtensionError(format!(\n                \"duplicate argv_index {argv_index}\"\n            )));\n        }\n\n        let constraint = &table_constraints.constraints[vtab_constraint.index];\n        if usage.omit {\n            where_clause[constraint.where_clause_pos.0].consumed = true;\n        }\n        let (_, expr, _) = constraint.get_constraining_expr(where_clause, referenced_tables);\n        constraints[zero_based_argv_index] = Some(expr);\n        arg_count += 1;\n    }\n\n    // Verify that used indices form a contiguous sequence starting from 1\n    let constraints = constraints\n        .into_iter()\n        .take(arg_count)\n        .enumerate()\n        .map(|(i, c)| {\n            c.ok_or_else(|| {\n                LimboError::ExtensionError(format!(\n                    \"argv_index values must form contiguous sequence starting from 1, missing index {}\",\n                    i + 1\n                ))\n            })\n        })\n        .collect::<Result<Vec<_>>>()?;\n\n    Ok(Operation::Scan(Scan::VirtualTable {\n        idx_num: *idx_num,\n        idx_str: idx_str.clone(),\n        constraints,\n    }))\n}\n\n/// Mark WHERE clause terms as consumed when they are covered by a seek\n/// (index seek, ephemeral auto-index seek, or rowid seek).\n///\n/// `is_outer_join`: skip consuming non-ON WHERE terms for outer joins, because\n/// the cursor may land on a NULL-extended row that the WHERE filter must still\n/// reject (e.g. `SELECT * FROM t1 LEFT JOIN t2 ON false WHERE t2.id = 5`).\n///\n/// `defer_cross_table`: skip cross-table constraints for hash-join build-only\n/// tables that lack a main-loop cursor — the probe side will evaluate them.\nfn mark_seek_constraints_consumed(\n    constraints: &[Constraint],\n    constraint_refs: &[RangeConstraintRef],\n    where_clause: &mut [WhereTerm],\n    is_outer_join: bool,\n    defer_cross_table: bool,\n) {\n    for cref in constraint_refs.iter() {\n        for pos in [\n            cref.eq.as_ref().map(|e| e.constraint_pos),\n            cref.lower_bound,\n            cref.upper_bound,\n        ] {\n            let Some(pos) = pos else { continue };\n            let constraint = &constraints[pos];\n            let where_term = &mut where_clause[constraint.where_clause_pos.0];\n            if where_term.consumed {\n                continue;\n            }\n            if is_outer_join && where_term.from_outer_join.is_none() {\n                continue;\n            }\n            if defer_cross_table && !constraint.lhs_mask.is_empty() {\n                continue;\n            }\n            where_term.consumed = true;\n        }\n    }\n}\n\n#[derive(Debug, PartialEq, Clone)]\nenum ConstantConditionEliminationResult {\n    Continue,\n    ImpossibleCondition,\n}\n\n/// Removes predicates that are always true.\n/// Returns a ConstantEliminationResult indicating whether any predicates are always false.\n/// This is used to determine whether the query can be aborted early.\nfn eliminate_constant_conditions(\n    where_clause: &mut [WhereTerm],\n) -> Result<ConstantConditionEliminationResult> {\n    let mut i = 0;\n    while i < where_clause.len() {\n        let predicate = &where_clause[i];\n        if predicate.expr.is_always_true()? {\n            // true predicates can be removed since they don't affect the result\n            where_clause[i].consumed = true;\n            i += 1;\n        } else if predicate.expr.is_always_false()? {\n            // any false predicate in a list of conjuncts (AND-ed predicates) will make the whole list false,\n            // except an outer join condition, because that just results in NULLs, not skipping the whole loop\n            if predicate.from_outer_join.is_some() {\n                i += 1;\n                continue;\n            }\n            where_clause\n                .iter_mut()\n                .for_each(|term| term.consumed = true);\n            return Ok(ConstantConditionEliminationResult::ImpossibleCondition);\n        } else {\n            i += 1;\n        }\n    }\n\n    Ok(ConstantConditionEliminationResult::Continue)\n}\n\n/// Check if the order target collation matches index column collations.\n/// Only remove the index when sort elimination selected this plan.\nfn maybe_remove_index_candidate(\n    index: &mut Option<Arc<Index>>,\n    table_reference: &JoinedTable,\n    order_target: Option<&OrderTarget>,\n    sort_eliminated: bool,\n) {\n    if !sort_eliminated {\n        return;\n    }\n    if let Some((idx, order_target)) = index.as_mut().zip(order_target) {\n        for col_order in &order_target.columns {\n            // Only check columns from this table\n            if col_order.table_id != table_reference.internal_id {\n                continue;\n            }\n\n            // Find matching index column\n            let matching_idx_col = match &col_order.target {\n                ColumnTarget::Column(col_no) => {\n                    idx.columns.iter().find(|ic| ic.pos_in_table == *col_no)\n                }\n                ColumnTarget::RowId => {\n                    continue;\n                }\n                ColumnTarget::Expr(_expr) => {\n                    continue;\n                }\n            };\n\n            if let Some(idx_col) = matching_idx_col {\n                // Index columns without explicit COLLATE use BINARY.\n                // Treat them as BINARY for ordering compatibility checks.\n                if col_order.collation != idx_col.collation.unwrap_or_default() {\n                    *index = None;\n                    return;\n                }\n            }\n        }\n    }\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum AlwaysTrueOrFalse {\n    AlwaysTrue,\n    AlwaysFalse,\n}\n\n/**\n  Helper trait for expressions that can be optimized\n  Implemented for ast::Expr\n*/\npub trait Optimizable {\n    // if the expression is a constant expression that, when evaluated as a condition, is always true or false\n    // return a [ConstantPredicate].\n    fn check_always_true_or_false(&self) -> Result<Option<AlwaysTrueOrFalse>>;\n    fn is_always_true(&self) -> Result<bool> {\n        Ok(self.check_always_true_or_false()? == Some(AlwaysTrueOrFalse::AlwaysTrue))\n    }\n    fn is_always_false(&self) -> Result<bool> {\n        Ok(self.check_always_true_or_false()? == Some(AlwaysTrueOrFalse::AlwaysFalse))\n    }\n    fn is_constant(&self, resolver: &Resolver<'_>) -> bool;\n    fn is_nonnull(&self, tables: &TableReferences) -> bool;\n}\n\nimpl Optimizable for ast::Expr {\n    /// Returns true if the expressions is (verifiably) non-NULL.\n    /// It might still be non-NULL even if we return false; we just\n    /// weren't able to prove it.\n    /// This function is currently very conservative, and will return false\n    /// for any expression where we aren't sure and didn't bother to find out\n    /// by writing more complex code.\n    fn is_nonnull(&self, tables: &TableReferences) -> bool {\n        match self {\n            Expr::SubqueryResult { .. } => false,\n            Expr::Between {\n                lhs, start, end, ..\n            } => lhs.is_nonnull(tables) && start.is_nonnull(tables) && end.is_nonnull(tables),\n            Expr::Binary(_, ast::Operator::Modulus | ast::Operator::Divide, _) => false, // 1 % 0, 1 / 0\n            Expr::Binary(expr, _, expr1) => expr.is_nonnull(tables) && expr1.is_nonnull(tables),\n            Expr::Case {\n                base,\n                when_then_pairs,\n                else_expr,\n                ..\n            } => {\n                base.as_ref().is_none_or(|base| base.is_nonnull(tables))\n                    && when_then_pairs\n                        .iter()\n                        .all(|(_, then)| then.is_nonnull(tables))\n                    && else_expr\n                        .as_ref()\n                        .is_none_or(|else_expr| else_expr.is_nonnull(tables))\n            }\n            Expr::Cast { expr, .. } => expr.is_nonnull(tables),\n            Expr::Collate(expr, _) => expr.is_nonnull(tables),\n            Expr::DoublyQualified(..) => {\n                panic!(\"Do not call is_nonnull before DoublyQualified has been rewritten as Column\")\n            }\n            Expr::Exists(..) => false,\n            Expr::FunctionCall { .. } => false,\n            Expr::FunctionCallStar { .. } => false,\n            Expr::Id(..) => panic!(\"Do not call is_nonnull before Id has been rewritten as Column\"),\n            Expr::Column {\n                table,\n                column,\n                is_rowid_alias,\n                ..\n            } => {\n                if *is_rowid_alias {\n                    return true;\n                }\n\n                let (_, table_ref) = tables\n                    .find_table_by_internal_id(*table)\n                    .expect(\"table not found\");\n                let columns = table_ref.columns();\n                let column = &columns[*column];\n                // Only INTEGER PRIMARY KEY (rowid alias) is implicitly NOT NULL.\n                // Other PRIMARY KEY types (e.g., TEXT PRIMARY KEY) can contain NULL.\n                column.is_rowid_alias() || column.notnull()\n            }\n            Expr::RowId { .. } => true,\n            Expr::InList { lhs, rhs, .. } => {\n                lhs.is_nonnull(tables)\n                    && (rhs.is_empty() || rhs.iter().all(|v| v.is_nonnull(tables)))\n            }\n            Expr::InSelect { .. } => false,\n            Expr::InTable { .. } => false,\n            Expr::IsNull(..) => true,\n            Expr::Like { lhs, rhs, .. } => lhs.is_nonnull(tables) && rhs.is_nonnull(tables),\n            Expr::Literal(literal) => match literal {\n                ast::Literal::Numeric(_) => true,\n                ast::Literal::String(_) => true,\n                ast::Literal::Blob(_) => true,\n                ast::Literal::Keyword(_) => true,\n                ast::Literal::Null => false,\n                ast::Literal::True => true,\n                ast::Literal::False => true,\n                ast::Literal::CurrentDate => true,\n                ast::Literal::CurrentTime => true,\n                ast::Literal::CurrentTimestamp => true,\n            },\n            Expr::Name(..) => false,\n            Expr::NotNull(..) => true,\n            Expr::Parenthesized(exprs) => exprs.iter().all(|expr| expr.is_nonnull(tables)),\n            Expr::Qualified(..) => {\n                panic!(\"Do not call is_nonnull before Qualified has been rewritten as Column\")\n            }\n            Expr::Raise(..) => false,\n            Expr::Subquery(..) => false,\n            Expr::Unary(_, expr) => expr.is_nonnull(tables),\n            Expr::Variable(..) => false,\n            Expr::Register(..) => false, // Register values can be null\n            Expr::Array { .. } | Expr::Subscript { .. } => {\n                unreachable!(\"Array and Subscript are desugared into function calls by the parser\")\n            }\n        }\n    }\n    /// Returns true if the expression is a constant i.e. does not depend on columns and can be evaluated only once during the execution\n    fn is_constant(&self, resolver: &Resolver<'_>) -> bool {\n        match self {\n            Expr::SubqueryResult { .. } => false,\n            Expr::Between {\n                lhs, start, end, ..\n            } => {\n                lhs.is_constant(resolver)\n                    && start.is_constant(resolver)\n                    && end.is_constant(resolver)\n            }\n            Expr::Binary(expr, _, expr1) => {\n                expr.is_constant(resolver) && expr1.is_constant(resolver)\n            }\n            Expr::Case {\n                base,\n                when_then_pairs,\n                else_expr,\n            } => {\n                base.as_ref().is_none_or(|base| base.is_constant(resolver))\n                    && when_then_pairs.iter().all(|(when, then)| {\n                        when.is_constant(resolver) && then.is_constant(resolver)\n                    })\n                    && else_expr\n                        .as_ref()\n                        .is_none_or(|else_expr| else_expr.is_constant(resolver))\n            }\n            Expr::Cast { expr, .. } => expr.is_constant(resolver),\n            Expr::Collate(expr, _) => expr.is_constant(resolver),\n            // Not constant. Normally rewritten to Expr::Column by the optimizer,\n            // but CHECK constraints bypass the rewrite pass and legitimately\n            // contain DoublyQualified nodes.\n            Expr::DoublyQualified(_, _, _) => false,\n            Expr::Exists(_) => false,\n            Expr::FunctionCall {\n                args,\n                name,\n                filter_over,\n                ..\n            } => {\n                if filter_over.over_clause.is_some() {\n                    return false;\n                }\n                let Some(func) = resolver.resolve_function(name.as_str(), args.len()) else {\n                    return false;\n                };\n                func.is_deterministic() && args.iter().all(|arg| arg.is_constant(resolver))\n            }\n            Expr::FunctionCallStar { .. } => false,\n            Expr::Id(_) => true,\n            Expr::Column { .. } => false,\n            Expr::RowId { .. } => false,\n            Expr::InList { lhs, rhs, .. } => {\n                lhs.is_constant(resolver)\n                    && (rhs.is_empty() || rhs.iter().all(|v| v.is_constant(resolver)))\n            }\n            Expr::InSelect { .. } => {\n                false // might be constant, too annoying to check subqueries etc. implement later\n            }\n            Expr::InTable { .. } => false,\n            Expr::IsNull(expr) => expr.is_constant(resolver),\n            Expr::Like {\n                lhs, rhs, escape, ..\n            } => {\n                lhs.is_constant(resolver)\n                    && rhs.is_constant(resolver)\n                    && escape\n                        .as_ref()\n                        .is_none_or(|escape| escape.is_constant(resolver))\n            }\n            Expr::Literal(_) => true,\n            Expr::Name(_) => false,\n            Expr::NotNull(expr) => expr.is_constant(resolver),\n            Expr::Parenthesized(exprs) => exprs.iter().all(|expr| expr.is_constant(resolver)),\n            // Not constant. Normally rewritten to Expr::Column by the optimizer,\n            // but CHECK constraints bypass the rewrite pass and legitimately\n            // contain Qualified nodes.\n            Expr::Qualified(_, _) => false,\n            Expr::Raise(_, expr) => expr.as_ref().is_none_or(|expr| expr.is_constant(resolver)),\n            Expr::Subquery(_) => false,\n            Expr::Unary(_, expr) => expr.is_constant(resolver),\n            Expr::Variable(_) => true,\n            Expr::Register(_) => false,\n            Expr::Array { .. } | Expr::Subscript { .. } => {\n                unreachable!(\"Array and Subscript are desugared into function calls by the parser\")\n            }\n        }\n    }\n    /// Returns true if the expression is a constant expression that, when evaluated as a condition, is always true or false\n    fn check_always_true_or_false(&self) -> Result<Option<AlwaysTrueOrFalse>> {\n        match self {\n            Self::Literal(lit) => match lit {\n                ast::Literal::Numeric(b) => {\n                    if let Ok(int_value) = b.parse::<i64>() {\n                        return Ok(Some(if int_value == 0 {\n                            AlwaysTrueOrFalse::AlwaysFalse\n                        } else {\n                            AlwaysTrueOrFalse::AlwaysTrue\n                        }));\n                    }\n                    if let Ok(float_value) = b.parse::<f64>() {\n                        return Ok(Some(if float_value == 0.0 {\n                            AlwaysTrueOrFalse::AlwaysFalse\n                        } else {\n                            AlwaysTrueOrFalse::AlwaysTrue\n                        }));\n                    }\n\n                    Ok(None)\n                }\n                ast::Literal::String(s) => {\n                    // Use Numeric::from to match SQLite's string-to-numeric conversion,\n                    // which extracts leading numeric prefixes (e.g., '9S' -> 9, 'abc' -> 0)\n                    let without_quotes = s.trim_matches('\\'');\n                    let numeric = Numeric::from(without_quotes);\n                    match numeric.to_bool() {\n                        true => Ok(Some(AlwaysTrueOrFalse::AlwaysTrue)),\n                        false => Ok(Some(AlwaysTrueOrFalse::AlwaysFalse)),\n                    }\n                }\n                _ => Ok(None),\n            },\n            Self::Unary(op, expr) => {\n                if *op == ast::UnaryOperator::Not {\n                    let trivial = expr.check_always_true_or_false()?;\n                    return Ok(trivial.map(|t| match t {\n                        AlwaysTrueOrFalse::AlwaysTrue => AlwaysTrueOrFalse::AlwaysFalse,\n                        AlwaysTrueOrFalse::AlwaysFalse => AlwaysTrueOrFalse::AlwaysTrue,\n                    }));\n                }\n\n                if *op == ast::UnaryOperator::Negative {\n                    let trivial = expr.check_always_true_or_false()?;\n                    return Ok(trivial);\n                }\n\n                Ok(None)\n            }\n            Self::InList { lhs: _, not, rhs } => {\n                if rhs.is_empty() {\n                    return Ok(Some(if *not {\n                        AlwaysTrueOrFalse::AlwaysTrue\n                    } else {\n                        AlwaysTrueOrFalse::AlwaysFalse\n                    }));\n                }\n\n                Ok(None)\n            }\n            Self::Binary(lhs, op, rhs) => {\n                let lhs_trivial = lhs.check_always_true_or_false()?;\n                let rhs_trivial = rhs.check_always_true_or_false()?;\n                match op {\n                    ast::Operator::And => {\n                        if lhs_trivial == Some(AlwaysTrueOrFalse::AlwaysFalse)\n                            || rhs_trivial == Some(AlwaysTrueOrFalse::AlwaysFalse)\n                        {\n                            return Ok(Some(AlwaysTrueOrFalse::AlwaysFalse));\n                        }\n                        if lhs_trivial == Some(AlwaysTrueOrFalse::AlwaysTrue)\n                            && rhs_trivial == Some(AlwaysTrueOrFalse::AlwaysTrue)\n                        {\n                            return Ok(Some(AlwaysTrueOrFalse::AlwaysTrue));\n                        }\n\n                        Ok(None)\n                    }\n                    ast::Operator::Or => {\n                        if lhs_trivial == Some(AlwaysTrueOrFalse::AlwaysTrue)\n                            || rhs_trivial == Some(AlwaysTrueOrFalse::AlwaysTrue)\n                        {\n                            return Ok(Some(AlwaysTrueOrFalse::AlwaysTrue));\n                        }\n                        if lhs_trivial == Some(AlwaysTrueOrFalse::AlwaysFalse)\n                            && rhs_trivial == Some(AlwaysTrueOrFalse::AlwaysFalse)\n                        {\n                            return Ok(Some(AlwaysTrueOrFalse::AlwaysFalse));\n                        }\n\n                        Ok(None)\n                    }\n                    _ => Ok(None),\n                }\n            }\n            _ => Ok(None),\n        }\n    }\n}\n\nfn ephemeral_index_build(\n    table_reference: &JoinedTable,\n    constraint_refs: &[RangeConstraintRef],\n) -> Index {\n    let mut ephemeral_columns: Vec<IndexColumn> = table_reference\n        .columns()\n        .iter()\n        .enumerate()\n        .map(|(i, c)| IndexColumn {\n            name: c.name.clone().unwrap(),\n            order: SortOrder::Asc,\n            pos_in_table: i,\n            collation: c.collation_opt(),\n            default: c.default.clone(),\n            expr: None,\n        })\n        // only include columns that are used in the query\n        .filter(|c| table_reference.column_is_used(c.pos_in_table))\n        .collect();\n    // sort so that constraints first, then rest in whatever order they were in in the table\n    ephemeral_columns.sort_by(|a, b| {\n        let a_constraint = constraint_refs\n            .iter()\n            .enumerate()\n            .find(|(_, c)| c.table_col_pos == Some(a.pos_in_table));\n        let b_constraint = constraint_refs\n            .iter()\n            .enumerate()\n            .find(|(_, c)| c.table_col_pos == Some(b.pos_in_table));\n        match (a_constraint, b_constraint) {\n            (Some(_), None) => Ordering::Less,\n            (None, Some(_)) => Ordering::Greater,\n            (Some((a_idx, _)), Some((b_idx, _))) => a_idx.cmp(&b_idx),\n            (None, None) => Ordering::Equal,\n        }\n    });\n    let ephemeral_index = Index {\n        name: format!(\n            \"ephemeral_{}_{}\",\n            table_reference.table.get_name(),\n            table_reference.internal_id\n        ),\n        columns: ephemeral_columns,\n        unique: false,\n        ephemeral: true,\n        table_name: table_reference.table.get_name().to_string(),\n        root_page: 0,\n        where_clause: None,\n        has_rowid: table_reference\n            .table\n            .btree()\n            .is_some_and(|btree| btree.has_rowid),\n        index_method: None,\n        on_conflict: None,\n    };\n\n    ephemeral_index\n}\n\n/// Build a [SeekDef] for a given list of [Constraint]s\npub fn build_seek_def_from_constraints(\n    constraints: &[Constraint],\n    constraint_refs: &[RangeConstraintRef],\n    iter_dir: IterationDirection,\n    where_clause: &[WhereTerm],\n    referenced_tables: Option<&TableReferences>,\n) -> Result<SeekDef> {\n    if constraint_refs.is_empty() {\n        // Zero-prefix seeks are used for extremum scans over an already ordered\n        // source: start at one end of the cursor and stop after the first\n        // qualifying row.\n        let (start_op, end_op) = match iter_dir {\n            IterationDirection::Forwards => (SeekOp::GE { eq_only: true }, SeekOp::GT),\n            IterationDirection::Backwards => (SeekOp::LE { eq_only: true }, SeekOp::LT),\n        };\n        return Ok(SeekDef {\n            prefix: Vec::new(),\n            iter_dir,\n            start: SeekKey {\n                last_component: SeekKeyComponent::None,\n                op: start_op,\n                affinity: Affinity::Blob,\n            },\n            end: SeekKey {\n                last_component: SeekKeyComponent::None,\n                op: end_op,\n                affinity: Affinity::Blob,\n            },\n        });\n    }\n    // Extract the key values and operators\n    let key = constraint_refs\n        .iter()\n        .map(|cref| cref.as_seek_range_constraint(constraints, where_clause, referenced_tables))\n        .collect();\n\n    let seek_def = build_seek_def(iter_dir, key)?;\n    Ok(seek_def)\n}\n\n/// Build a [SeekDef] for a given [SeekRangeConstraint] and [IterationDirection].\n/// To be usable as a seek key, all but potentially the last term must be equalities.\n/// The last term can be a nonequality (range with potentially one unbounded range).\n///\n/// There are two parts to the seek definition:\n/// 1. start [SeekKey], which specifies the key that we will use to seek to the first row that matches the index key.\n/// 2. end [SeekKey], which specifies the key that we will use to terminate the index scan that follows the seek.\n///\n/// There are some nuances to how, and which parts of, the index key can be used in the start and end [SeekKey]s,\n/// depending on the operator and iteration order. This function explains those nuances inline when dealing with\n/// each case.\n///\n/// But to illustrate the general idea, consider the following examples:\n///\n/// 1. For example, having two conditions like (x>10 AND y>20) cannot be used as a valid [SeekKey] GT(x:10, y:20)\n///    because the first row greater than (x:10, y:20) might be (x:11, y:19), which does not satisfy the where clause.\n///    In this case, only GT(x:10) must be used as the [SeekKey], and rows with y <= 20 must be filtered as a regular condition expression for each value of x.\n///\n/// 2. In contrast, having (x=10 AND y>20) forms a valid index key GT(x:10, y:20) because after the seek, we can simply terminate as soon as x > 10,\n///    i.e. use GT(x:10, y:20) as the start [SeekKey] and GT(x:10) as the end.\n///\n/// The preceding examples are for an ascending index. The logic is similar for descending indexes, but an important distinction is that\n/// since a descending index is laid out in reverse order, the comparison operators are reversed, e.g. LT becomes GT, LE becomes GE, etc.\n/// So when you see e.g. a SeekOp::GT below for a descending index, it actually means that we are seeking the first row where the index key is LESS than the seek key.\n///\nfn build_seek_def(\n    iter_dir: IterationDirection,\n    mut key: Vec<SeekRangeConstraint>,\n) -> Result<SeekDef> {\n    turso_assert!(!key.is_empty());\n    let last = key.last().unwrap();\n\n    // if we searching for exact key - emit definition immediately with prefix as a full key\n    if last.eq.is_some() {\n        let (start_op, end_op) = match iter_dir {\n            IterationDirection::Forwards => (SeekOp::GE { eq_only: true }, SeekOp::GT),\n            IterationDirection::Backwards => (SeekOp::LE { eq_only: true }, SeekOp::LT),\n        };\n        return Ok(SeekDef {\n            prefix: key,\n            iter_dir,\n            start: SeekKey {\n                last_component: SeekKeyComponent::None,\n                op: start_op,\n                affinity: Affinity::Blob,\n            },\n            end: SeekKey {\n                last_component: SeekKeyComponent::None,\n                op: end_op,\n                affinity: Affinity::Blob,\n            },\n        });\n    }\n    turso_assert!(last.lower_bound.is_some() || last.upper_bound.is_some());\n\n    // pop last key as we will do some form of range search\n    let last = key.pop().unwrap();\n    let has_upper_bound_only = last.upper_bound.is_some() && last.lower_bound.is_none();\n    let upper_bound_is_lt_or_le = matches!(\n        last.upper_bound.as_ref().map(|(op, _, _)| op),\n        Some(ast::Operator::Less | ast::Operator::LessEquals)\n    );\n    // after that all key components must be equality constraints\n    turso_debug_assert!(key.iter().all(|k| k.eq.is_some()));\n\n    let has_prefix = !key.is_empty();\n    let apply_null_boundaries = |start: &mut SeekKey, end: &mut SeekKey| {\n        // Sometimes we must add an extra NULL to the key on purpose.\n        // We do this so scans over composite indexes match SQLite exactly.\n        let start_is_prefix_only = matches!(start.last_component, SeekKeyComponent::None);\n        let start_has_range_component = matches!(start.last_component, SeekKeyComponent::Expr(_));\n        let end_is_prefix_only = matches!(end.last_component, SeekKeyComponent::None);\n        let end_has_range_component = matches!(end.last_component, SeekKeyComponent::Expr(_));\n        let start_is_forward_ge = matches!(start.op, SeekOp::GE { .. })\n            && matches!(iter_dir, IterationDirection::Forwards);\n        let start_is_backward_le = matches!(start.op, SeekOp::LE { .. })\n            && matches!(iter_dir, IterationDirection::Backwards);\n        let end_is_forward_gt =\n            matches!(end.op, SeekOp::GT) && matches!(iter_dir, IterationDirection::Forwards);\n        let end_is_backward_lt =\n            matches!(end.op, SeekOp::LT) && matches!(iter_dir, IterationDirection::Backwards);\n        // 1) Choose a better starting point.\n        //\n        // Example:\n        //   INDEX(c1, c2 ASC)\n        //   WHERE c1='a' AND c2<=999\n        //\n        // If we start from key [c1='a'], we hit rows where c2 is NULL first.\n        // For this case we want to start right after that NULL boundary.\n        // So we:\n        // - use start key [c1='a', NULL]\n        // - change start op from GE to GT\n        // - for backward scans in the symmetric shape, change LE to LT\n        //\n        // We only do this for \"< / <=\" ranges that do not also have a lower bound.\n        if has_prefix\n            && start_is_prefix_only\n            && end_has_range_component\n            && start_is_forward_ge\n            && has_upper_bound_only\n            && upper_bound_is_lt_or_le\n        {\n            start.last_component = SeekKeyComponent::Null;\n            start.op = SeekOp::GT;\n        } else if has_prefix\n            && start_is_prefix_only\n            && end_has_range_component\n            && start_is_backward_le\n            && has_upper_bound_only\n            && upper_bound_is_lt_or_le\n        {\n            start.last_component = SeekKeyComponent::Null;\n            start.op = SeekOp::LT;\n        }\n\n        // 2) Choose a better stopping point.\n        //\n        // Example:\n        //   INDEX(c1, c2 DESC)\n        //   WHERE c1='a' AND c2<=999\n        //\n        // The stop check must also respect the NULL boundary for c2.\n        // So we:\n        // - use stop key [c1='a', NULL]\n        // - change end op from GT to GE\n        // - for backward scans, change LT to LE\n        //\n        // Same limit as above: only \"< / <=\" ranges with no lower bound.\n        if has_prefix\n            && end_is_prefix_only\n            && start_has_range_component\n            && end_is_forward_gt\n            && has_upper_bound_only\n            && upper_bound_is_lt_or_le\n        {\n            end.last_component = SeekKeyComponent::Null;\n            end.op = SeekOp::GE { eq_only: false };\n        } else if has_prefix\n            && end_is_prefix_only\n            && start_has_range_component\n            && end_is_backward_lt\n            && has_upper_bound_only\n            && upper_bound_is_lt_or_le\n        {\n            end.last_component = SeekKeyComponent::Null;\n            end.op = SeekOp::LE { eq_only: false };\n        }\n    };\n\n    // For the commented examples below, keep in mind that since a descending index is laid out in reverse order, the comparison operators are reversed, e.g. LT becomes GT, LE becomes GE, etc.\n    // Also keep in mind that index keys are compared based on the number of columns given, so for example:\n    // - if key is GT(x:10), then (x=10, y=usize::MAX) is not GT because only X is compared. (x=11, y=<any>) is GT.\n    // - if key is GT(x:10, y:20), then (x=10, y=21) is GT because both X and Y are compared.\n    // - if key is GT(x:10, y:NULL), then (x=10, y=0) is GT because NULL is always LT in index key comparisons.\n    Ok(match iter_dir {\n        IterationDirection::Forwards => {\n            let (mut start, mut end) = match last.sort_order {\n                SortOrder::Asc => {\n                    let start = match last.lower_bound {\n                        // Forwards, Asc, GT: (x=10 AND y>20)\n                        // Start key: start from the first GT(x:10, y:20)\n                        Some((ast::Operator::Greater, bound, affinity)) => SeekKey {\n                            last_component: SeekKeyComponent::Expr(bound),\n                            op: SeekOp::GT,\n                            affinity,\n                        },\n                        // Forwards, Asc, GE: (x=10 AND y>=20)\n                        // Start key: start from the first GE(x:10, y:20)\n                        Some((ast::Operator::GreaterEquals, bound, affinity)) => SeekKey {\n                            last_component: SeekKeyComponent::Expr(bound),\n                            op: SeekOp::GE { eq_only: false },\n                            affinity,\n                        },\n                        // Forwards, Asc, None, (x=10 AND y<30)\n                        // Start key: start from the first GE(x:10)\n                        None => SeekKey {\n                            last_component: SeekKeyComponent::None,\n                            op: SeekOp::GE { eq_only: false },\n                            affinity: Affinity::Blob,\n                        },\n                        Some((op, _, _)) => {\n                            crate::bail_parse_error!(\"build_seek_def: invalid operator: {:?}\", op,)\n                        }\n                    };\n                    let end = match last.upper_bound {\n                        // Forwards, Asc, LT, (x=10 AND y<30)\n                        // End key: end at first GE(x:10, y:30)\n                        Some((ast::Operator::Less, bound, affinity)) => SeekKey {\n                            last_component: SeekKeyComponent::Expr(bound),\n                            op: SeekOp::GE { eq_only: false },\n                            affinity,\n                        },\n                        // Forwards, Asc, LE, (x=10 AND y<=30)\n                        // End key: end at first GT(x:10, y:30)\n                        Some((ast::Operator::LessEquals, bound, affinity)) => SeekKey {\n                            last_component: SeekKeyComponent::Expr(bound),\n                            op: SeekOp::GT,\n                            affinity,\n                        },\n                        // Forwards, Asc, None, (x=10 AND y>20)\n                        // End key: end at first GT(x:10)\n                        None => SeekKey {\n                            last_component: SeekKeyComponent::None,\n                            op: SeekOp::GT,\n                            affinity: Affinity::Blob,\n                        },\n                        Some((op, _, _)) => {\n                            crate::bail_parse_error!(\"build_seek_def: invalid operator: {:?}\", op,)\n                        }\n                    };\n                    (start, end)\n                }\n                SortOrder::Desc => {\n                    let start = match last.upper_bound {\n                        // Forwards, Desc, LT: (x=10 AND y<30)\n                        // Start key: start from the first GT(x:10, y:30)\n                        Some((ast::Operator::Less, bound, affinity)) => SeekKey {\n                            last_component: SeekKeyComponent::Expr(bound),\n                            op: SeekOp::GT,\n                            affinity,\n                        },\n                        // Forwards, Desc, LE: (x=10 AND y<=30)\n                        // Start key: start from the first GE(x:10, y:30)\n                        Some((ast::Operator::LessEquals, bound, affinity)) => SeekKey {\n                            last_component: SeekKeyComponent::Expr(bound),\n                            op: SeekOp::GE { eq_only: false },\n                            affinity,\n                        },\n                        // Forwards, Desc, None: (x=10 AND y>20)\n                        // Start key: start from the first GE(x:10)\n                        None => SeekKey {\n                            last_component: SeekKeyComponent::None,\n                            op: SeekOp::GE { eq_only: false },\n                            affinity: Affinity::Blob,\n                        },\n                        Some((op, _, _)) => {\n                            crate::bail_parse_error!(\"build_seek_def: invalid operator: {:?}\", op,)\n                        }\n                    };\n                    let end = match last.lower_bound {\n                        // Forwards, Asc, GT, (x=10 AND y>20)\n                        // End key: end at first GE(x:10, y:20)\n                        Some((ast::Operator::Greater, bound, affinity)) => SeekKey {\n                            last_component: SeekKeyComponent::Expr(bound),\n                            op: SeekOp::GE { eq_only: false },\n                            affinity,\n                        },\n                        // Forwards, Asc, GE, (x=10 AND y>=20)\n                        // End key: end at first GT(x:10, y:20)\n                        Some((ast::Operator::GreaterEquals, bound, affinity)) => SeekKey {\n                            last_component: SeekKeyComponent::Expr(bound),\n                            op: SeekOp::GT,\n                            affinity,\n                        },\n                        // Forwards, Asc, None, (x=10 AND y<30)\n                        // End key: end at first GT(x:10)\n                        None => SeekKey {\n                            last_component: SeekKeyComponent::None,\n                            op: SeekOp::GT,\n                            affinity: Affinity::Blob,\n                        },\n                        Some((op, _, _)) => {\n                            crate::bail_parse_error!(\"build_seek_def: invalid operator: {:?}\", op,)\n                        }\n                    };\n                    (start, end)\n                }\n            };\n            apply_null_boundaries(&mut start, &mut end);\n            SeekDef {\n                prefix: key,\n                iter_dir,\n                start,\n                end,\n            }\n        }\n        IterationDirection::Backwards => {\n            let (mut start, mut end) = match last.sort_order {\n                SortOrder::Asc => {\n                    let start = match last.upper_bound {\n                        // Backwards, Asc, LT: (x=10 AND y<30)\n                        // Start key: start from the first LT(x:10, y:30)\n                        Some((ast::Operator::Less, bound, affinity)) => SeekKey {\n                            last_component: SeekKeyComponent::Expr(bound),\n                            op: SeekOp::LT,\n                            affinity,\n                        },\n                        // Backwards, Asc, LT: (x=10 AND y<=30)\n                        // Start key: start from the first LE(x:10, y:30)\n                        Some((ast::Operator::LessEquals, bound, affinity)) => SeekKey {\n                            last_component: SeekKeyComponent::Expr(bound),\n                            op: SeekOp::LE { eq_only: false },\n                            affinity,\n                        },\n                        // Backwards, Asc, None: (x=10 AND y>20)\n                        // Start key: start from the first LE(x:10)\n                        None => SeekKey {\n                            last_component: SeekKeyComponent::None,\n                            op: SeekOp::LE { eq_only: false },\n                            affinity: Affinity::Blob,\n                        },\n                        Some((op, _, _)) => {\n                            crate::bail_parse_error!(\"build_seek_def: invalid operator: {:?}\", op)\n                        }\n                    };\n                    let end = match last.lower_bound {\n                        // Backwards, Asc, GT, (x=10 AND y>20)\n                        // End key: end at first LE(x:10, y:20)\n                        Some((ast::Operator::Greater, bound, affinity)) => SeekKey {\n                            last_component: SeekKeyComponent::Expr(bound),\n                            op: SeekOp::LE { eq_only: false },\n                            affinity,\n                        },\n                        // Backwards, Asc, GT, (x=10 AND y>=20)\n                        // End key: end at first LT(x:10, y:20)\n                        Some((ast::Operator::GreaterEquals, bound, affinity)) => SeekKey {\n                            last_component: SeekKeyComponent::Expr(bound),\n                            op: SeekOp::LT,\n                            affinity,\n                        },\n                        // Backwards, Asc, None, (x=10 AND y<30)\n                        // End key: end at first LT(x:10)\n                        None => SeekKey {\n                            last_component: SeekKeyComponent::None,\n                            op: SeekOp::LT,\n                            affinity: Affinity::Blob,\n                        },\n                        Some((op, _, _)) => {\n                            crate::bail_parse_error!(\"build_seek_def: invalid operator: {:?}\", op,)\n                        }\n                    };\n                    (start, end)\n                }\n                SortOrder::Desc => {\n                    let start = match last.lower_bound {\n                        // Backwards, Desc, LT: (x=10 AND y>20)\n                        // Start key: start from the first LT(x:10, y:20)\n                        Some((ast::Operator::Greater, bound, affinity)) => SeekKey {\n                            last_component: SeekKeyComponent::Expr(bound),\n                            op: SeekOp::LT,\n                            affinity,\n                        },\n                        // Backwards, Desc, LE: (x=10 AND y>=20)\n                        // Start key: start from the first LE(x:10, y:20)\n                        Some((ast::Operator::GreaterEquals, bound, affinity)) => SeekKey {\n                            last_component: SeekKeyComponent::Expr(bound),\n                            op: SeekOp::LE { eq_only: false },\n                            affinity,\n                        },\n                        // Backwards, Desc, LE: (x=10 AND y<30)\n                        // Start key: start from the first LE(x:10)\n                        None => SeekKey {\n                            last_component: SeekKeyComponent::None,\n                            op: SeekOp::LE { eq_only: false },\n                            affinity: Affinity::Blob,\n                        },\n                        Some((op, _, _)) => {\n                            crate::bail_parse_error!(\"build_seek_def: invalid operator: {:?}\", op,)\n                        }\n                    };\n                    let end = match last.upper_bound {\n                        // Backwards, Desc, LT, (x=10 AND y<30)\n                        // End key: end at first LE(x:10, y:30)\n                        Some((ast::Operator::Less, bound, affinity)) => SeekKey {\n                            last_component: SeekKeyComponent::Expr(bound),\n                            op: SeekOp::LE { eq_only: false },\n                            affinity,\n                        },\n                        // Backwards, Desc, LT, (x=10 AND y<=30)\n                        // End key: end at first LT(x:10, y:30)\n                        Some((ast::Operator::LessEquals, bound, affinity)) => SeekKey {\n                            last_component: SeekKeyComponent::Expr(bound),\n                            op: SeekOp::LT,\n                            affinity,\n                        },\n                        // Backwards, Desc, LT, (x=10 AND y>20)\n                        // End key: end at first LT(x:10)\n                        None => SeekKey {\n                            last_component: SeekKeyComponent::None,\n                            op: SeekOp::LT,\n                            affinity: Affinity::Blob,\n                        },\n                        Some((op, _, _)) => {\n                            crate::bail_parse_error!(\"build_seek_def: invalid operator: {:?}\", op,)\n                        }\n                    };\n                    (start, end)\n                }\n            };\n            apply_null_boundaries(&mut start, &mut end);\n            SeekDef {\n                prefix: key,\n                iter_dir,\n                start,\n                end,\n            }\n        }\n    })\n}\n\n#[cfg(test)]\nmod tests {\n    use super::{where_term_is_null_rejecting_for_table, Optimizable};\n    use crate::translate::emitter::Resolver;\n    use crate::{schema::Schema, DatabaseCatalog, RwLock, SymbolTable};\n    use rustc_hash::FxHashMap as HashMap;\n    use turso_parser::ast::{self, Expr, FunctionTail, Name, TableInternalId};\n\n    fn empty_resolver<'a>(\n        schema: &'a Schema,\n        database_schemas: &'a RwLock<HashMap<usize, crate::sync::Arc<Schema>>>,\n        attached_databases: &'a RwLock<DatabaseCatalog>,\n        syms: &'a SymbolTable,\n    ) -> Resolver<'a> {\n        Resolver::new(schema, database_schemas, attached_databases, syms, true)\n    }\n\n    fn no_tail() -> FunctionTail {\n        FunctionTail {\n            filter_clause: None,\n            over_clause: None,\n        }\n    }\n\n    fn fn_call(name: &str, args: Vec<Expr>) -> Expr {\n        Expr::FunctionCall {\n            name: Name::exact(name.to_string()),\n            distinctness: None,\n            args: args.into_iter().map(Box::new).collect(),\n            order_by: vec![],\n            filter_over: no_tail(),\n        }\n    }\n\n    #[test]\n    fn constant_classifier_for_coalesce_with_in_list() {\n        let schema = Schema::new();\n        let syms = SymbolTable::new();\n        let database_schemas = RwLock::new(HashMap::default());\n        let attached_databases = RwLock::new(DatabaseCatalog::new());\n        let resolver = empty_resolver(&schema, &database_schemas, &attached_databases, &syms);\n\n        let expr = fn_call(\n            \"coalesce\",\n            vec![\n                fn_call(\n                    \"length\",\n                    vec![Expr::Literal(ast::Literal::String(\"a\".into()))],\n                ),\n                Expr::InList {\n                    lhs: Box::new(fn_call(\n                        \"hex\",\n                        vec![Expr::Literal(ast::Literal::Blob(\"01\".into()))],\n                    )),\n                    not: false,\n                    rhs: vec![Box::new(Expr::Literal(ast::Literal::Blob(\"02\".into())))],\n                },\n            ],\n        );\n\n        assert!(expr.is_constant(&resolver));\n    }\n\n    #[test]\n    fn constant_classifier_for_quote_of_column() {\n        let schema = Schema::new();\n        let syms = SymbolTable::new();\n        let database_schemas = RwLock::new(HashMap::default());\n        let attached_databases = RwLock::new(DatabaseCatalog::new());\n        let resolver = empty_resolver(&schema, &database_schemas, &attached_databases, &syms);\n\n        let expr = fn_call(\n            \"quote\",\n            vec![Expr::Column {\n                database: None,\n                table: TableInternalId::default(),\n                column: 0,\n                is_rowid_alias: false,\n            }],\n        );\n\n        assert!(!expr.is_constant(&resolver));\n    }\n\n    #[test]\n    fn null_rejection_detection_uses_function_resolution() {\n        let table = TableInternalId::from(42);\n        let expr = Expr::Binary(\n            Box::new(fn_call(\n                \"IFNULL\",\n                vec![\n                    Expr::Column {\n                        database: None,\n                        table,\n                        column: 0,\n                        is_rowid_alias: false,\n                    },\n                    Expr::Literal(ast::Literal::Numeric(\"2147483647\".into())),\n                ],\n            )),\n            ast::Operator::GreaterEquals,\n            Box::new(Expr::Literal(ast::Literal::Numeric(\"127\".into()))),\n        );\n\n        assert!(!where_term_is_null_rejecting_for_table(\n            &expr,\n            ast::Operator::GreaterEquals.into(),\n            table\n        ));\n    }\n\n    #[test]\n    fn null_rejection_detection_requires_target_table_reference() {\n        let target_table = TableInternalId::from(7);\n        let other_table = TableInternalId::from(8);\n        let expr = Expr::Binary(\n            Box::new(fn_call(\n                \"coalesce\",\n                vec![\n                    Expr::Column {\n                        database: None,\n                        table: other_table,\n                        column: 0,\n                        is_rowid_alias: false,\n                    },\n                    Expr::Literal(ast::Literal::Numeric(\"0\".into())),\n                ],\n            )),\n            ast::Operator::Greater,\n            Box::new(Expr::Literal(ast::Literal::Numeric(\"1\".into()))),\n        );\n\n        assert!(where_term_is_null_rejecting_for_table(\n            &expr,\n            ast::Operator::Greater.into(),\n            target_table\n        ));\n    }\n\n    #[test]\n    fn null_rejection_detection_handles_nested_null_masking_functions() {\n        let table = TableInternalId::from(9);\n        let expr = Expr::Binary(\n            Box::new(fn_call(\n                \"coalesce\",\n                vec![\n                    fn_call(\n                        \"ifnull\",\n                        vec![\n                            Expr::Column {\n                                database: None,\n                                table,\n                                column: 1,\n                                is_rowid_alias: false,\n                            },\n                            Expr::Literal(ast::Literal::Numeric(\"0\".into())),\n                        ],\n                    ),\n                    Expr::Literal(ast::Literal::Numeric(\"2\".into())),\n                ],\n            )),\n            ast::Operator::Equals,\n            Box::new(Expr::Literal(ast::Literal::Numeric(\"2\".into()))),\n        );\n\n        assert!(!where_term_is_null_rejecting_for_table(\n            &expr,\n            ast::Operator::Equals.into(),\n            table\n        ));\n    }\n\n    #[test]\n    fn null_rejection_detection_treats_is_operator_as_non_rejecting() {\n        let table = TableInternalId::from(11);\n        let expr = Expr::Binary(\n            Box::new(Expr::Column {\n                database: None,\n                table,\n                column: 0,\n                is_rowid_alias: false,\n            }),\n            ast::Operator::Is,\n            Box::new(Expr::Literal(ast::Literal::Null)),\n        );\n\n        assert!(!where_term_is_null_rejecting_for_table(\n            &expr,\n            ast::Operator::Is.into(),\n            table\n        ));\n    }\n\n    #[test]\n    fn null_rejection_detection_treats_is_between_columns_as_non_rejecting() {\n        let table = TableInternalId::from(12);\n        let expr = Expr::Binary(\n            Box::new(Expr::Column {\n                database: None,\n                table,\n                column: 0,\n                is_rowid_alias: false,\n            }),\n            ast::Operator::Is,\n            Box::new(Expr::Column {\n                database: None,\n                table,\n                column: 1,\n                is_rowid_alias: false,\n            }),\n        );\n\n        assert!(!where_term_is_null_rejecting_for_table(\n            &expr,\n            ast::Operator::Is.into(),\n            table\n        ));\n    }\n\n    #[test]\n    fn null_rejection_detection_treats_is_with_non_null_literal_as_non_rejecting() {\n        let table = TableInternalId::from(13);\n        let expr = Expr::Binary(\n            Box::new(Expr::Column {\n                database: None,\n                table,\n                column: 0,\n                is_rowid_alias: false,\n            }),\n            ast::Operator::Is,\n            Box::new(Expr::Literal(ast::Literal::Numeric(\"5\".into()))),\n        );\n\n        assert!(!where_term_is_null_rejecting_for_table(\n            &expr,\n            ast::Operator::Is.into(),\n            table\n        ));\n    }\n\n    #[test]\n    fn null_rejection_detection_treats_is_not_with_non_null_literal_as_non_rejecting() {\n        let table = TableInternalId::from(14);\n        let expr = Expr::Binary(\n            Box::new(Expr::Column {\n                database: None,\n                table,\n                column: 0,\n                is_rowid_alias: false,\n            }),\n            ast::Operator::IsNot,\n            Box::new(Expr::Literal(ast::Literal::Numeric(\"5\".into()))),\n        );\n\n        assert!(!where_term_is_null_rejecting_for_table(\n            &expr,\n            ast::Operator::IsNot.into(),\n            table\n        ));\n    }\n\n    #[test]\n    fn null_rejection_detection_case_with_is_null_check_not_rejecting() {\n        let table = TableInternalId::from(15);\n        // CASE WHEN t.col IS NULL THEN 1 ELSE t.col END > 0\n        let expr = Expr::Binary(\n            Box::new(Expr::Case {\n                base: None,\n                when_then_pairs: vec![(\n                    Box::new(Expr::IsNull(Box::new(Expr::Column {\n                        database: None,\n                        table,\n                        column: 0,\n                        is_rowid_alias: false,\n                    }))),\n                    Box::new(Expr::Literal(ast::Literal::Numeric(\"1\".into()))),\n                )],\n                else_expr: Some(Box::new(Expr::Column {\n                    database: None,\n                    table,\n                    column: 0,\n                    is_rowid_alias: false,\n                })),\n            }),\n            ast::Operator::Greater,\n            Box::new(Expr::Literal(ast::Literal::Numeric(\"0\".into()))),\n        );\n\n        assert!(!where_term_is_null_rejecting_for_table(\n            &expr,\n            ast::Operator::Greater.into(),\n            table\n        ));\n    }\n\n    #[test]\n    fn null_rejection_detection_case_without_null_check_is_rejecting() {\n        let table = TableInternalId::from(16);\n        // CASE WHEN t.col > 5 THEN t.col ELSE 0 END > 0\n        let expr = Expr::Binary(\n            Box::new(Expr::Case {\n                base: None,\n                when_then_pairs: vec![(\n                    Box::new(Expr::Binary(\n                        Box::new(Expr::Column {\n                            database: None,\n                            table,\n                            column: 0,\n                            is_rowid_alias: false,\n                        }),\n                        ast::Operator::Greater,\n                        Box::new(Expr::Literal(ast::Literal::Numeric(\"5\".into()))),\n                    )),\n                    Box::new(Expr::Column {\n                        database: None,\n                        table,\n                        column: 0,\n                        is_rowid_alias: false,\n                    }),\n                )],\n                else_expr: Some(Box::new(Expr::Literal(ast::Literal::Numeric(\"0\".into())))),\n            }),\n            ast::Operator::Greater,\n            Box::new(Expr::Literal(ast::Literal::Numeric(\"0\".into()))),\n        );\n\n        // CASE without IS NULL check doesn't mask nulls, so it IS null-rejecting\n        assert!(where_term_is_null_rejecting_for_table(\n            &expr,\n            ast::Operator::Greater.into(),\n            table\n        ));\n    }\n\n    #[test]\n    fn null_rejection_detection_iif_with_is_null_check_not_rejecting() {\n        let table = TableInternalId::from(17);\n        // IIF(t.col IS NULL, 1, t.col) > 0\n        let expr = Expr::Binary(\n            Box::new(fn_call(\n                \"iif\",\n                vec![\n                    Expr::IsNull(Box::new(Expr::Column {\n                        database: None,\n                        table,\n                        column: 0,\n                        is_rowid_alias: false,\n                    })),\n                    Expr::Literal(ast::Literal::Numeric(\"1\".into())),\n                    Expr::Column {\n                        database: None,\n                        table,\n                        column: 0,\n                        is_rowid_alias: false,\n                    },\n                ],\n            )),\n            ast::Operator::Greater,\n            Box::new(Expr::Literal(ast::Literal::Numeric(\"0\".into()))),\n        );\n\n        assert!(!where_term_is_null_rejecting_for_table(\n            &expr,\n            ast::Operator::Greater.into(),\n            table\n        ));\n    }\n}\n"
  },
  {
    "path": "core/translate/optimizer/multi_index.rs",
    "content": "//! Multi-index-specific planning for OR-by-union and AND-by-intersection.\n//!\n//! This module owns the parts of planning that are unique to combining several\n//! index probes for the same table. It reuses the generic btree candidate\n//! chooser from `access_method.rs` for each individual branch, then layers the\n//! union/intersection-specific decomposition, costing, and residual handling on\n//! top.\n\nuse crate::schema::{Index, Schema};\nuse crate::stats::AnalyzeStats;\nuse crate::translate::expr::expr_references_any_subquery;\nuse crate::translate::optimizer::access_method::{\n    choose_best_btree_candidate, choose_best_in_seek_candidate, AccessMethod, AccessMethodParams,\n    BranchReadMode, ChosenInSeekCandidate, ResidualConstraintMode,\n};\nuse crate::translate::optimizer::constraints::{\n    analyze_binary_term_for_index, constraints_from_where_clause, Constraint, RangeConstraintRef,\n    TableConstraints,\n};\nuse crate::translate::optimizer::cost::{\n    estimate_cost_for_scan_or_seek, estimate_rows_per_seek, rows_per_leaf_page_for_index,\n    AnalyzeCtx, Cost, IndexInfo, RowCountEstimate,\n};\nuse crate::translate::optimizer::cost_params::CostModelParams;\nuse crate::translate::plan::{\n    InSeekSource, JoinedTable, NonFromClauseSubquery, SetOperation, TableReferences,\n    UnionBranchPrePostFilters, WhereTerm,\n};\nuse crate::translate::planner::{table_mask_from_expr, TableMask};\nuse rustc_hash::FxHashMap as HashMap;\nuse smallvec::SmallVec;\nuse std::{collections::VecDeque, sync::Arc};\nuse turso_parser::ast::{self, TableInternalId};\n\n#[derive(Debug, Clone)]\n/// Parameters for a single branch of a multi-index scan.\npub struct MultiIndexBranchParams {\n    /// The index to use for this branch, or None for rowid access.\n    pub index: Option<Arc<Index>>,\n    /// How this branch probes the table/index.\n    pub access: MultiIndexBranchAccessParams,\n    /// Estimated number of rows from this branch.\n    pub estimated_rows: f64,\n    /// Residual filters for union (OR) branches. `None` for intersection branches.\n    pub residuals: Option<UnionBranchPrePostFilters>,\n}\n\n#[derive(Debug, Clone)]\npub enum MultiIndexBranchAccessParams {\n    Seek {\n        constraints: Vec<Constraint>,\n        constraint_refs: Vec<RangeConstraintRef>,\n    },\n    InSeek {\n        source: InSeekSource,\n    },\n}\n\n/// Internal decomposition of an AND clause into intersection branches.\n#[derive(Debug)]\nstruct AndClauseDecomposition {\n    term_indices: Vec<usize>,\n    branches: Vec<AndBranch>,\n}\n\n/// One term that can participate in an AND-by-intersection plan.\n#[derive(Debug)]\nstruct AndBranch {\n    where_term_idx: usize,\n    constraint: Constraint,\n    index: Option<Arc<Index>>,\n    constraint_refs: Vec<RangeConstraintRef>,\n}\n\n/// Internal branch representation while evaluating a candidate multi-index plan.\nstruct MultiIdxBranch {\n    index: Option<Arc<Index>>,\n    access: MultiIdxBranchAccess,\n    cost: Cost,\n    estimated_rows: f64,\n    union_prepost_filters: Option<UnionBranchPrePostFilters>,\n}\n\nenum MultiIdxBranchAccess {\n    Seek {\n        constraints: Vec<Constraint>,\n        constraint_refs: Vec<RangeConstraintRef>,\n    },\n    InSeek {\n        source: InSeekSource,\n        constraint_idx: usize,\n    },\n}\n\n/// Flattens nested OR expressions into a list of disjuncts.\n///\n/// For example, `(a OR b) OR c` becomes `[a, b, c]`.\nfn flatten_or_expr(expr: &ast::Expr) -> Vec<&ast::Expr> {\n    match expr {\n        ast::Expr::Binary(lhs, ast::Operator::Or, rhs) => {\n            let mut result = flatten_or_expr(lhs);\n            result.extend(flatten_or_expr(rhs));\n            result\n        }\n        _ => vec![expr],\n    }\n}\n\n/// Flattens nested AND expressions into a list of conjuncts.\n///\n/// For example, `(a AND b) AND c` becomes `[a, b, c]`.\nfn flatten_and_expr(expr: &ast::Expr) -> Vec<&ast::Expr> {\n    match expr {\n        ast::Expr::Binary(lhs, ast::Operator::And, rhs) => {\n            let mut result = flatten_and_expr(lhs);\n            result.extend(flatten_and_expr(rhs));\n            result\n        }\n        _ => vec![expr],\n    }\n}\n\n/// Build temporary `WhereTerm`s from branch-local expressions and extract the\n/// constraints for exactly one target table.\n///\n/// This is narrower than `constraints_from_where_clause()`:\n/// - `exprs` are synthetic planner inputs, not the query's real top-level\n///   `WHERE` terms.\n/// - The returned `WhereTerm`s are only suitable for branch-local planning\n///   and constraint bookkeeping for `table_reference`; they must not be reused\n///   for global predicate consumption or join rewrites.\n///\n/// FIXME: stop synthesizing `WhereTerm`s here just to reuse\n/// `constraints_from_where_clause()`. Branch-local planning should have a\n/// direct constraint-extraction path that does not fabricate top-level planner\n/// terms.\n#[expect(clippy::too_many_arguments)]\nfn get_table_local_constraints_for_branch(\n    exprs: &[ast::Expr],\n    from_outer_join: Option<TableInternalId>,\n    table_reference: &JoinedTable,\n    table_references: &TableReferences,\n    available_indexes: &HashMap<String, VecDeque<Arc<Index>>>,\n    subqueries: &[NonFromClauseSubquery],\n    schema: &Schema,\n    params: &CostModelParams,\n) -> crate::Result<(Vec<WhereTerm>, TableConstraints)> {\n    let synthetic_where_terms = exprs\n        .iter()\n        .cloned()\n        .map(|expr| WhereTerm {\n            expr,\n            from_outer_join,\n            consumed: false,\n        })\n        .collect::<Vec<_>>();\n    let table_constraints = constraints_from_where_clause(\n        &synthetic_where_terms,\n        table_references,\n        available_indexes,\n        subqueries,\n        schema,\n        params,\n    )?\n    .into_iter()\n    .find(|constraints| constraints.table_id == table_reference.internal_id)\n    .expect(\"constraints_from_where_clause must return constraints for every joined table\");\n    let mut table_constraints = table_constraints;\n    // Branch-local constraints originate from synthetic `WhereTerm`s, so copy\n    // out their constraining expressions while those temporary terms still\n    // exist.\n    for constraint in table_constraints.constraints.iter_mut() {\n        if constraint.constraining_expr.is_some() || constraint.operator.as_ast_operator().is_none()\n        {\n            continue;\n        }\n        constraint.constraining_expr =\n            Some(constraint.get_constraining_expr(&synthetic_where_terms, Some(table_references)));\n    }\n    Ok((synthetic_where_terms, table_constraints))\n}\n\n/// Estimate the cost of a multi-index union scan (OR-by-union optimization).\n///\n/// The cost model accounts for:\n/// 1. Cost of each branch scan\n/// 2. RowSet insert/test work needed to deduplicate rowids\n/// 3. Table fetches after deduplication\n/// 4. Overlap between branches, approximated from independent selectivities\nfn estimate_multi_index_scan_cost(\n    branch_costs: &[Cost],\n    branch_rows: &[f64],\n    base_row_count: RowCountEstimate,\n    input_cardinality: f64,\n    params: &CostModelParams,\n) -> (Cost, f64) {\n    let base_row_count = *base_row_count;\n    // Total cost of all branch scans.\n    let branch_scan_cost: f64 = branch_costs.iter().map(|c| c.0).sum();\n    // Sum of branch row counts before RowSet deduplication.\n    let total_rows_before_dedup: f64 = branch_rows.iter().sum();\n\n    // Estimate overlap between branches. For independent predicates:\n    //   P(A OR B) = 1 - (1 - P(A)) * (1 - P(B))\n    let mut unique_row_ratio = 1.0f64;\n    for rows in branch_rows.iter() {\n        let branch_selectivity = (*rows / base_row_count).min(1.0);\n        unique_row_ratio *= 1.0 - branch_selectivity;\n    }\n    let estimated_unique_rows = base_row_count * (1.0 - unique_row_ratio);\n\n    // RowSet operations do an insert and membership test per candidate rowid.\n    let rowset_ops_cost = total_rows_before_dedup * params.cpu_cost_per_row * 2.0;\n\n    // Table fetch cost mirrors single-index lookup costing, assuming some\n    // locality benefit from rowid-ordered access after RowSet deduplication.\n    let table_pages = (base_row_count / params.rows_per_table_page).max(1.0);\n    let selectivity = estimated_unique_rows / base_row_count.max(1.0);\n    let table_fetch_cost = selectivity * table_pages;\n    let total_cost = (branch_scan_cost + rowset_ops_cost + table_fetch_cost) * input_cardinality;\n\n    (Cost(total_cost), estimated_unique_rows)\n}\n\n/// Estimate the cost of a multi-index intersection (AND-by-intersection).\n///\n/// The cost model accounts for:\n/// 1. Cost of each branch scan\n/// 2. RowSet test work while intersecting rowids\n/// 3. Table fetches for the surviving rowids\n/// 4. Final result size as the product of branch selectivities\nfn estimate_multi_index_intersection_cost(\n    branch_costs: &[Cost],\n    branch_rows: &[f64],\n    base_row_count: RowCountEstimate,\n    input_cardinality: f64,\n    params: &CostModelParams,\n) -> (Cost, f64) {\n    let base_row_count = *base_row_count;\n    // Total cost of all branch scans.\n    let branch_scan_cost: f64 = branch_costs.iter().map(|c| c.0).sum();\n\n    // Estimate intersection result as the product of selectivities:\n    //   P(A AND B) = P(A) * P(B)\n    let mut intersection_selectivity = 1.0f64;\n    for rows in branch_rows.iter() {\n        let branch_selectivity = (*rows / base_row_count).min(1.0);\n        intersection_selectivity *= branch_selectivity;\n    }\n    let estimated_intersection_rows = (base_row_count * intersection_selectivity).max(1.0);\n\n    // First branch inserts rowids; later branches test against the RowSet.\n    let first_branch_rows = branch_rows.first().copied().unwrap_or(0.0);\n    let subsequent_branch_rows: f64 = branch_rows.iter().skip(1).sum();\n    let rowset_ops_cost =\n        (first_branch_rows + subsequent_branch_rows) * params.cpu_cost_per_row * 1.5;\n\n    // Table fetch cost mirrors single-index lookup costing, assuming some\n    // locality benefit from rowid-ordered access after intersection.\n    let table_pages = (base_row_count / params.rows_per_table_page).max(1.0);\n    let selectivity = estimated_intersection_rows / base_row_count.max(1.0);\n    let table_fetch_cost = selectivity * table_pages;\n    let total_cost = (branch_scan_cost + rowset_ops_cost + table_fetch_cost) * input_cardinality;\n\n    (Cost(total_cost), estimated_intersection_rows)\n}\n\n/// Compute [`IndexInfo`] for a multi-index branch.\n///\n/// RowSet-building branches only need rowids from the scan, so an index can be\n/// treated as covering even if it does not contain all later table columns.\nfn index_info_for_branch(\n    index: Option<&Index>,\n    rhs_table: &JoinedTable,\n    read_mode: BranchReadMode,\n    rows_per_table_page: f64,\n) -> Option<IndexInfo> {\n    let rowid_only = matches!(read_mode, BranchReadMode::RowIdOnly);\n    match index {\n        Some(index) => Some(IndexInfo {\n            unique: index.unique,\n            covering: rowid_only || rhs_table.index_is_covering(index),\n            column_count: index.columns.len(),\n            rows_per_leaf_page: rows_per_leaf_page_for_index(\n                index.columns.len(),\n                rhs_table,\n                rows_per_table_page,\n            ),\n        }),\n        None => Some(IndexInfo {\n            unique: true,\n            covering: true,\n            column_count: 1,\n            rows_per_leaf_page: rows_per_table_page,\n        }),\n    }\n}\n\nfn in_seek_source_from_expr(\n    expr: &ast::Expr,\n    chosen: &ChosenInSeekCandidate,\n) -> Option<InSeekSource> {\n    match expr {\n        ast::Expr::InList { rhs, .. } => Some(InSeekSource::LiteralList {\n            values: rhs.iter().map(|e| *e.clone()).collect(),\n            affinity: chosen.affinity,\n        }),\n        ast::Expr::SubqueryResult {\n            query_type: ast::SubqueryType::In { cursor_id, .. },\n            ..\n        } => Some(InSeekSource::Subquery {\n            cursor_id: *cursor_id,\n        }),\n        _ => None,\n    }\n}\n\n#[allow(clippy::too_many_arguments)]\nfn choose_multi_index_branch_access(\n    rhs_table: &JoinedTable,\n    table_constraints: &TableConstraints,\n    branch_terms: &[WhereTerm],\n    lhs_mask: &TableMask,\n    rhs_idx: usize,\n    schema: &Schema,\n    base_row_count: RowCountEstimate,\n    analyze_stats: &AnalyzeStats,\n    params: &CostModelParams,\n) -> crate::Result<Option<MultiIdxBranch>> {\n    let chosen_seek = choose_best_btree_candidate(\n        rhs_table,\n        table_constraints,\n        lhs_mask,\n        rhs_idx,\n        None,\n        schema,\n        analyze_stats,\n        1.0,\n        base_row_count,\n        params,\n    );\n\n    let mut best_branch = chosen_seek\n        .as_ref()\n        .filter(|chosen| !chosen.constraint_refs.is_empty())\n        .map(|chosen| {\n            let index_info = index_info_for_branch(\n                chosen.index.as_deref(),\n                rhs_table,\n                BranchReadMode::RowIdOnly,\n                params.rows_per_table_page,\n            )\n            .expect(\"multi-index branches always have costable access\");\n            let analyze_ctx = AnalyzeCtx {\n                rhs_table,\n                index: chosen.index.as_ref(),\n                stats: analyze_stats,\n            };\n            let branch_cost = estimate_cost_for_scan_or_seek(\n                Some(index_info),\n                &table_constraints.constraints,\n                &chosen.constraint_refs,\n                1.0,\n                base_row_count,\n                false,\n                params,\n                Some(&analyze_ctx),\n            );\n            MultiIdxBranch {\n                index: chosen.index.clone(),\n                access: MultiIdxBranchAccess::Seek {\n                    constraints: table_constraints.constraints.clone(),\n                    constraint_refs: chosen.constraint_refs.clone(),\n                },\n                cost: branch_cost,\n                estimated_rows: estimate_rows_per_seek(\n                    index_info,\n                    &table_constraints.constraints,\n                    &chosen.constraint_refs,\n                    base_row_count,\n                    Some(&analyze_ctx),\n                ),\n                union_prepost_filters: None,\n            }\n        });\n\n    let in_seek_threshold = best_branch\n        .as_ref()\n        .map(|branch| branch.cost)\n        .unwrap_or(Cost(f64::INFINITY));\n    if let Some(chosen_in_seek) = choose_best_in_seek_candidate(\n        rhs_table,\n        table_constraints,\n        lhs_mask,\n        1.0,\n        base_row_count,\n        params,\n        in_seek_threshold,\n        BranchReadMode::RowIdOnly,\n    )? {\n        let Some(source) = in_seek_source_from_expr(\n            &branch_terms[chosen_in_seek.constraint_idx].expr,\n            &chosen_in_seek,\n        ) else {\n            return Ok(None);\n        };\n        best_branch = Some(MultiIdxBranch {\n            index: chosen_in_seek.index,\n            access: MultiIdxBranchAccess::InSeek {\n                source,\n                constraint_idx: chosen_in_seek.constraint_idx,\n            },\n            cost: chosen_in_seek.cost,\n            estimated_rows: chosen_in_seek.estimated_rows_per_outer_row,\n            union_prepost_filters: None,\n        });\n    }\n\n    Ok(best_branch)\n}\n\n/// Residual output from [`partition_residual_multi_or_exprs`].\nstruct MultiOrResidualPrePostFilters {\n    pre_filter_exprs: Vec<ast::Expr>,\n    post_filter_exprs: Vec<ast::Expr>,\n    /// Combined table mask for `post_filter_exprs`.\n    post_mask: TableMask,\n}\n\n/// Classify unconsumed branch conjuncts into pre-filters (outer-table-only,\n/// evaluated before the index seek) and post-filters (evaluated after the seek).\n///\n/// Returns `None` if any residual contains a subquery or has an unresolvable\n/// table mask—matching the old `residual_tables_mask` rejection.\nfn partition_residual_multi_or_exprs(\n    branch_terms: &[WhereTerm],\n    access: &MultiIdxBranchAccess,\n    lhs_mask: &TableMask,\n    table_references: &TableReferences,\n    subqueries: &[NonFromClauseSubquery],\n) -> Option<MultiOrResidualPrePostFilters> {\n    let mut consumed = vec![false; branch_terms.len()];\n    match access {\n        MultiIdxBranchAccess::Seek {\n            constraints,\n            constraint_refs,\n        } => {\n            for cref in constraint_refs.iter() {\n                for idx in [\n                    cref.eq.as_ref().map(|e| e.constraint_pos),\n                    cref.lower_bound,\n                    cref.upper_bound,\n                ]\n                .into_iter()\n                .flatten()\n                {\n                    consumed[constraints[idx].where_clause_pos.0] = true;\n                }\n            }\n        }\n        MultiIdxBranchAccess::InSeek { constraint_idx, .. } => consumed[*constraint_idx] = true,\n    }\n\n    let mut pre_filter_exprs = Vec::new();\n    let mut post_filter_exprs = Vec::new();\n    let mut post_mask = TableMask::new();\n\n    for (idx, term) in branch_terms.iter().enumerate() {\n        if consumed[idx] {\n            continue;\n        }\n        let expr = &term.expr;\n        if expr_references_any_subquery(expr) {\n            return None;\n        }\n        let mask = table_mask_from_expr(expr, table_references, subqueries).ok()?;\n        if lhs_mask.contains_all(&mask) {\n            pre_filter_exprs.push(expr.clone());\n        } else {\n            post_mask |= mask;\n            post_filter_exprs.push(expr.clone());\n        }\n    }\n\n    Some(MultiOrResidualPrePostFilters {\n        pre_filter_exprs,\n        post_filter_exprs,\n        post_mask,\n    })\n}\n\n/// Estimate selectivity for a residual predicate that remains after a branch\n/// seek is chosen.\n///\n/// We keep this intentionally heuristic: recurse through boolean structure and,\n/// for leaf predicates, reuse normal constraint selectivity analysis when the\n/// expression can be recognized as a single-table constraint.\n#[allow(clippy::too_many_arguments)]\nfn estimate_residual_expr_selectivity(\n    expr: &ast::Expr,\n    rhs_table: &JoinedTable,\n    table_references: &TableReferences,\n    available_indexes: &HashMap<String, VecDeque<Arc<Index>>>,\n    subqueries: &[NonFromClauseSubquery],\n    schema: &Schema,\n    params: &CostModelParams,\n) -> f64 {\n    let Ok(expr) = crate::translate::expr::unwrap_parens(expr) else {\n        return params.sel_other;\n    };\n\n    match expr {\n        ast::Expr::Binary(lhs, ast::Operator::And, rhs) => {\n            estimate_residual_expr_selectivity(\n                lhs,\n                rhs_table,\n                table_references,\n                available_indexes,\n                subqueries,\n                schema,\n                params,\n            ) * estimate_residual_expr_selectivity(\n                rhs,\n                rhs_table,\n                table_references,\n                available_indexes,\n                subqueries,\n                schema,\n                params,\n            )\n        }\n        ast::Expr::Binary(lhs, ast::Operator::Or, rhs) => {\n            let lhs_selectivity = estimate_residual_expr_selectivity(\n                lhs,\n                rhs_table,\n                table_references,\n                available_indexes,\n                subqueries,\n                schema,\n                params,\n            );\n            let rhs_selectivity = estimate_residual_expr_selectivity(\n                rhs,\n                rhs_table,\n                table_references,\n                available_indexes,\n                subqueries,\n                schema,\n                params,\n            );\n            1.0 - (1.0 - lhs_selectivity) * (1.0 - rhs_selectivity)\n        }\n        ast::Expr::Unary(ast::UnaryOperator::Not, inner) => {\n            1.0 - estimate_residual_expr_selectivity(\n                inner,\n                rhs_table,\n                table_references,\n                available_indexes,\n                subqueries,\n                schema,\n                params,\n            )\n        }\n        _ => {\n            let Ok((_, table_constraints)) = get_table_local_constraints_for_branch(\n                &[expr.clone()],\n                None,\n                rhs_table,\n                table_references,\n                available_indexes,\n                subqueries,\n                schema,\n                params,\n            ) else {\n                return params.sel_other;\n            };\n\n            table_constraints\n                .constraints\n                .iter()\n                .filter(|constraint| constraint.where_clause_pos.0 == 0)\n                .map(|constraint| constraint.selectivity)\n                // A single residual expression can sometimes yield multiple\n                // derived constraints (for example, self-comparisons). Use the\n                // strongest single estimate instead of multiplying duplicates.\n                .reduce(f64::min)\n                .unwrap_or(params.sel_other)\n        }\n    }\n    .clamp(0.0, 1.0)\n}\n\n#[allow(clippy::too_many_arguments)]\nfn estimate_multi_or_residual_selectivity(\n    residual_exprs: &[ast::Expr],\n    rhs_table: &JoinedTable,\n    table_references: &TableReferences,\n    available_indexes: &HashMap<String, VecDeque<Arc<Index>>>,\n    subqueries: &[NonFromClauseSubquery],\n    schema: &Schema,\n    params: &CostModelParams,\n) -> f64 {\n    residual_exprs\n        .iter()\n        .map(|expr| {\n            estimate_residual_expr_selectivity(\n                expr,\n                rhs_table,\n                table_references,\n                available_indexes,\n                subqueries,\n                schema,\n                params,\n            )\n        })\n        .product::<f64>()\n        .clamp(0.0, 1.0)\n}\n\n#[allow(clippy::too_many_arguments)]\n/// Evaluate a fully decomposed multi-index plan and return it if it beats the\n/// current best non-multi-index access cost.\nfn evaluate_multi_index_branches(\n    branches: Vec<MultiIdxBranch>,\n    set_op: SetOperation,\n    where_term_idx: usize,\n    rhs_table: &JoinedTable,\n    table_references: &TableReferences,\n    available_indexes: &HashMap<String, VecDeque<Arc<Index>>>,\n    subqueries: &[NonFromClauseSubquery],\n    schema: &Schema,\n    base_row_count: RowCountEstimate,\n    input_cardinality: f64,\n    params: &CostModelParams,\n    best_cost: Cost,\n) -> Option<AccessMethod> {\n    let mut branch_costs = Vec::with_capacity(branches.len());\n    let mut branch_rows = Vec::with_capacity(branches.len());\n    let mut branch_params = Vec::with_capacity(branches.len());\n\n    for branch in branches {\n        let post_filter_exprs = branch\n            .union_prepost_filters\n            .as_ref()\n            .map(|r| &r.post_filter_exprs);\n        let selectivity = if let Some(post_filter_exprs) = post_filter_exprs {\n            estimate_multi_or_residual_selectivity(\n                post_filter_exprs,\n                rhs_table,\n                table_references,\n                available_indexes,\n                subqueries,\n                schema,\n                params,\n            )\n        } else {\n            1.0\n        };\n        let estimated_rows = branch.estimated_rows * selectivity;\n\n        let params_for_branch = MultiIndexBranchParams {\n            index: branch.index.clone(),\n            access: match branch.access {\n                MultiIdxBranchAccess::Seek {\n                    constraints,\n                    constraint_refs,\n                } => MultiIndexBranchAccessParams::Seek {\n                    constraints,\n                    constraint_refs,\n                },\n                MultiIdxBranchAccess::InSeek { source, .. } => {\n                    MultiIndexBranchAccessParams::InSeek { source }\n                }\n            },\n            estimated_rows,\n            residuals: branch.union_prepost_filters,\n        };\n\n        branch_costs.push(branch.cost);\n        branch_rows.push(params_for_branch.estimated_rows);\n        branch_params.push(params_for_branch);\n    }\n\n    let (multi_index_cost, estimated_rows) = match &set_op {\n        SetOperation::Union => estimate_multi_index_scan_cost(\n            &branch_costs,\n            &branch_rows,\n            base_row_count,\n            input_cardinality,\n            params,\n        ),\n        SetOperation::Intersection { .. } => estimate_multi_index_intersection_cost(\n            &branch_costs,\n            &branch_rows,\n            base_row_count,\n            input_cardinality,\n            params,\n        ),\n    };\n\n    if multi_index_cost < best_cost {\n        let mut consumed_where_terms = SmallVec::<[usize; 4]>::new();\n        consumed_where_terms.push(where_term_idx);\n        if let SetOperation::Intersection {\n            additional_consumed_terms,\n        } = &set_op\n        {\n            for term_idx in additional_consumed_terms.iter().copied() {\n                if !consumed_where_terms.contains(&term_idx) {\n                    consumed_where_terms.push(term_idx);\n                }\n            }\n        }\n        for branch in &branch_params {\n            if let MultiIndexBranchAccessParams::Seek { constraints, .. } = &branch.access {\n                for constraint in constraints {\n                    let where_term_idx = constraint.where_clause_pos.0;\n                    if !consumed_where_terms.contains(&where_term_idx) {\n                        consumed_where_terms.push(where_term_idx);\n                    }\n                }\n            }\n        }\n        Some(AccessMethod {\n            cost: multi_index_cost,\n            estimated_rows_per_outer_row: estimated_rows,\n            residual_constraints: ResidualConstraintMode::ApplyUnconsumed,\n            consumed_where_terms,\n            params: AccessMethodParams::MultiIndexScan {\n                branches: branch_params,\n                where_term_idx,\n                set_op,\n            },\n        })\n    } else {\n        None\n    }\n}\n\n#[allow(clippy::too_many_arguments)]\n/// Analyze top-level AND terms to determine whether they can be executed as an\n/// AND-by-intersection plan.\n///\n/// Returns `Some(...)` only when:\n/// 1. Multiple terms constrain the same table\n/// 2. Each term is individually indexable\n/// 3. No single composite index already covers multiple terms more directly\n/// 4. At least two distinct indexes participate in the final branch set\nfn analyze_and_terms_for_multi_index(\n    table_reference: &JoinedTable,\n    where_clause: &[WhereTerm],\n    available_indexes: &HashMap<String, VecDeque<Arc<Index>>>,\n    table_references: &TableReferences,\n    subqueries: &[NonFromClauseSubquery],\n    schema: &Schema,\n    params: &CostModelParams,\n) -> Option<AndClauseDecomposition> {\n    let table_id = table_reference.internal_id;\n    let table_name = table_reference.table.get_name();\n    let indexes = available_indexes.get(table_name);\n    let rowid_alias_column = table_reference\n        .columns()\n        .iter()\n        .position(|c| c.is_rowid_alias());\n\n    // Collect AND terms that:\n    // 1. Reference this table\n    // 2. Are simple binary comparisons\n    // 3. Can use an index\n    // 4. Are not already consumed\n    // 5. Are local constraints rather than cross-table join conditions\n    let mut candidate_branches: Vec<AndBranch> = Vec::new();\n    let mut columns_used: Vec<Option<usize>> = Vec::new();\n\n    for (where_term_idx, term) in where_clause.iter().enumerate() {\n        if term.consumed {\n            continue;\n        }\n        if matches!(&term.expr, ast::Expr::Binary(_, ast::Operator::Or, _)) {\n            continue;\n        }\n\n        let Some(analyzed) = analyze_binary_term_for_index(\n            &term.expr,\n            where_term_idx,\n            table_id,\n            table_reference,\n            indexes,\n            rowid_alias_column,\n            available_indexes,\n            table_references,\n            subqueries,\n            schema,\n            params,\n        ) else {\n            continue;\n        };\n\n        if !analyzed.constraint.lhs_mask.is_empty() {\n            continue;\n        }\n\n        columns_used.push(analyzed.constraint.table_col_pos);\n        candidate_branches.push(AndBranch {\n            where_term_idx,\n            constraint: analyzed.constraint,\n            index: analyzed.best_index,\n            constraint_refs: analyzed.constraint_refs,\n        });\n    }\n\n    if candidate_branches.len() < 2 {\n        return None;\n    }\n\n    // If a composite index already covers multiple constrained columns, prefer\n    // that single lookup path over intersection.\n    if let Some(indexes) = indexes {\n        for index in indexes.iter().filter(|idx| idx.index_method.is_none()) {\n            let mut columns_covered = 0;\n            for (i, col_pos) in columns_used.iter().enumerate() {\n                if let Some(col_pos) = col_pos {\n                    if let Some(idx_pos) = index.column_table_pos_to_index_pos(*col_pos) {\n                        if idx_pos < index.columns.len() {\n                            let earlier_covered =\n                                columns_used[..i].iter().filter_map(|c| *c).any(|c| {\n                                    index\n                                        .column_table_pos_to_index_pos(c)\n                                        .is_some_and(|p| p < idx_pos)\n                                });\n                            if idx_pos == 0 || earlier_covered {\n                                columns_covered += 1;\n                            }\n                        }\n                    }\n                }\n            }\n            if columns_covered >= 2 {\n                return None;\n            }\n        }\n    }\n\n    // Keep only branches that use distinct named indexes. Rowid (`None`) may\n    // still appear more than once because it is not tied to a named index.\n    let mut unique_branches: Vec<AndBranch> = Vec::new();\n    let mut seen_indexes: Vec<Option<String>> = Vec::new();\n    for branch in candidate_branches {\n        let index_name = branch.index.as_ref().map(|idx| idx.name.clone());\n        if index_name.is_some() && seen_indexes.contains(&index_name) {\n            continue;\n        }\n        seen_indexes.push(index_name);\n        unique_branches.push(branch);\n    }\n\n    if unique_branches.len() < 2 {\n        return None;\n    }\n\n    Some(AndClauseDecomposition {\n        term_indices: unique_branches.iter().map(|b| b.where_term_idx).collect(),\n        branches: unique_branches,\n    })\n}\n\n#[allow(clippy::too_many_arguments)]\n/// Analyze OR clauses for OR-by-union optimization.\n///\n/// Returns a `MultiIndexScan` access method when every disjunct can be planned\n/// as an individual lookup branch and the combined cost beats the current best\n/// non-multi-index alternative.\npub fn consider_multi_index_union(\n    rhs_table: &JoinedTable,\n    where_clause: &[WhereTerm],\n    available_indexes: &HashMap<String, VecDeque<Arc<Index>>>,\n    table_references: &TableReferences,\n    subqueries: &[NonFromClauseSubquery],\n    schema: &Schema,\n    input_cardinality: f64,\n    base_row_count: RowCountEstimate,\n    params: &CostModelParams,\n    best_cost: Cost,\n    lhs_mask: &TableMask,\n    analyze_stats: &AnalyzeStats,\n) -> Option<AccessMethod> {\n    for (where_term_idx, term) in where_clause.iter().enumerate() {\n        if term.consumed {\n            continue;\n        }\n\n        let ast::Expr::Binary(_, ast::Operator::Or, _) = &term.expr else {\n            continue;\n        };\n\n        let disjuncts = flatten_or_expr(&term.expr);\n        if disjuncts.len() < 2 {\n            continue;\n        }\n\n        let mut allowed_mask = *lhs_mask;\n        let Some(rhs_idx) = table_references\n            .joined_tables()\n            .iter()\n            .position(|t| t.internal_id == rhs_table.internal_id)\n        else {\n            continue;\n        };\n        allowed_mask.add_table(rhs_idx);\n\n        // Each disjunct is replanned with branch-local `TableConstraints`, so\n        // compound conjuncts can reuse the same compound-seek analysis as\n        // ordinary btree access.\n        let branches: Option<Vec<_>> = disjuncts\n            .into_iter()\n            .map(|disjunct_expr| {\n                let Ok(disjunct_expr) = crate::translate::expr::unwrap_parens(disjunct_expr) else {\n                    return None;\n                };\n                let conjuncts = flatten_and_expr(disjunct_expr)\n                    .into_iter()\n                    .cloned()\n                    .collect::<Vec<_>>();\n                let (synthetic_where_terms, table_constraints) =\n                    get_table_local_constraints_for_branch(\n                        &conjuncts,\n                        term.from_outer_join,\n                        rhs_table,\n                        table_references,\n                        available_indexes,\n                        subqueries,\n                        schema,\n                        params,\n                    )\n                    .ok()?;\n                let mut chosen = choose_multi_index_branch_access(\n                    rhs_table,\n                    &table_constraints,\n                    &synthetic_where_terms,\n                    lhs_mask,\n                    rhs_idx,\n                    schema,\n                    base_row_count,\n                    analyze_stats,\n                    params,\n                )\n                .ok()??;\n                // Partition residuals in a single pass: pre-filters reference\n                // only outer (lhs) tables and can short-circuit the branch\n                // before the index seek; post-filters reference the target\n                // table and are evaluated after the seek.\n                let partitioned_pre_post = partition_residual_multi_or_exprs(\n                    &synthetic_where_terms,\n                    &chosen.access,\n                    lhs_mask,\n                    table_references,\n                    subqueries,\n                )?;\n                if !allowed_mask.contains_all(&partitioned_pre_post.post_mask) {\n                    return None;\n                }\n                chosen.union_prepost_filters = Some(UnionBranchPrePostFilters {\n                    requires_table_cursor: partitioned_pre_post.post_mask.contains_table(rhs_idx),\n                    pre_filter_exprs: partitioned_pre_post.pre_filter_exprs,\n                    post_filter_exprs: partitioned_pre_post.post_filter_exprs,\n                });\n                Some(chosen)\n            })\n            .collect();\n\n        let Some(branches) = branches else {\n            continue;\n        };\n\n        if let Some(access_method) = evaluate_multi_index_branches(\n            branches,\n            SetOperation::Union,\n            where_term_idx,\n            rhs_table,\n            table_references,\n            available_indexes,\n            subqueries,\n            schema,\n            base_row_count,\n            input_cardinality,\n            params,\n            best_cost,\n        ) {\n            return Some(access_method);\n        }\n    }\n\n    None\n}\n\n/// Analyze top-level AND terms for AND-by-intersection optimization.\n///\n/// This is more restrictive than OR-by-union because every branch must be a\n/// local term on the current table, and the final plan only survives if it\n/// beats the best ordinary access path.\n#[expect(clippy::too_many_arguments)]\npub fn consider_multi_index_intersection(\n    rhs_table: &JoinedTable,\n    where_clause: &[WhereTerm],\n    available_indexes: &HashMap<String, VecDeque<Arc<Index>>>,\n    table_references: &TableReferences,\n    subqueries: &[NonFromClauseSubquery],\n    schema: &Schema,\n    input_cardinality: f64,\n    base_row_count: RowCountEstimate,\n    params: &CostModelParams,\n    best_cost: Cost,\n    lhs_mask: &TableMask,\n    analyze_stats: &AnalyzeStats,\n) -> Option<AccessMethod> {\n    let decomposition = analyze_and_terms_for_multi_index(\n        rhs_table,\n        where_clause,\n        available_indexes,\n        table_references,\n        subqueries,\n        schema,\n        params,\n    )?;\n\n    if decomposition.branches.len() < 2 {\n        return None;\n    }\n\n    let all_usable = decomposition\n        .branches\n        .iter()\n        .all(|b| lhs_mask.contains_all(&b.constraint.lhs_mask));\n    if !all_usable {\n        return None;\n    }\n\n    let branches: Vec<_> = decomposition\n        .branches\n        .iter()\n        .map(|b| {\n            let constraints = vec![b.constraint.clone()];\n            let index_info = index_info_for_branch(\n                b.index.as_deref(),\n                rhs_table,\n                BranchReadMode::RowIdOnly,\n                params.rows_per_table_page,\n            )\n            .expect(\"intersection branches always have costable access\");\n            let analyze_ctx = AnalyzeCtx {\n                rhs_table,\n                index: b.index.as_ref(),\n                stats: analyze_stats,\n            };\n            MultiIdxBranch {\n                index: b.index.clone(),\n                access: MultiIdxBranchAccess::Seek {\n                    constraints: constraints.clone(),\n                    constraint_refs: b.constraint_refs.clone(),\n                },\n                cost: estimate_cost_for_scan_or_seek(\n                    Some(index_info),\n                    &constraints,\n                    &b.constraint_refs,\n                    1.0,\n                    base_row_count,\n                    false,\n                    params,\n                    Some(&analyze_ctx),\n                ),\n                estimated_rows: estimate_rows_per_seek(\n                    index_info,\n                    &constraints,\n                    &b.constraint_refs,\n                    base_row_count,\n                    Some(&analyze_ctx),\n                ),\n                union_prepost_filters: None,\n            }\n        })\n        .collect();\n\n    let where_term_idx = decomposition.term_indices[0];\n    let additional_consumed_terms: Vec<usize> =\n        decomposition.term_indices.iter().skip(1).copied().collect();\n\n    evaluate_multi_index_branches(\n        branches,\n        SetOperation::Intersection {\n            additional_consumed_terms,\n        },\n        where_term_idx,\n        rhs_table,\n        table_references,\n        available_indexes,\n        subqueries,\n        schema,\n        base_row_count,\n        input_cardinality,\n        params,\n        best_cost,\n    )\n}\n\n#[cfg(test)]\nmod tests {\n    use super::{\n        consider_multi_index_intersection, consider_multi_index_union, AnalyzeStats,\n        MultiIndexBranchParams,\n    };\n    use crate::{\n        schema::{BTreeTable, ColDef, Column, Index, IndexColumn, Schema, Table, Type},\n        translate::{\n            optimizer::{\n                access_method::AccessMethodParams,\n                cost::{Cost, RowCountEstimate},\n                cost_params::DEFAULT_PARAMS,\n            },\n            plan::{\n                ColumnUsedMask, JoinInfo, JoinType, JoinedTable, Operation, TableReferences,\n                WhereTerm,\n            },\n            planner::TableMask,\n        },\n        vdbe::builder::TableRefIdCounter,\n    };\n    use rustc_hash::FxHashMap as HashMap;\n    use std::{collections::VecDeque, sync::Arc};\n    use turso_parser::ast::{self, Expr, Operator, SortOrder, TableInternalId};\n\n    struct TestColumn {\n        name: String,\n        ty: Type,\n        is_rowid_alias: bool,\n    }\n\n    fn empty_schema() -> Schema {\n        Schema::default()\n    }\n\n    fn create_column(c: &TestColumn) -> Column {\n        Column::new(\n            Some(c.name.clone()),\n            c.ty.to_string(),\n            None,\n            None,\n            c.ty,\n            None,\n            ColDef {\n                primary_key: false,\n                rowid_alias: c.is_rowid_alias,\n                ..Default::default()\n            },\n        )\n    }\n\n    fn create_column_of_type(name: &str, ty: Type) -> Column {\n        create_column(&TestColumn {\n            name: name.to_string(),\n            ty,\n            is_rowid_alias: false,\n        })\n    }\n\n    fn create_btree_table(name: &str, columns: Vec<Column>) -> Arc<BTreeTable> {\n        Arc::new(BTreeTable {\n            root_page: 1,\n            name: name.to_string(),\n            has_autoincrement: false,\n            primary_key_columns: vec![],\n            columns,\n            has_rowid: true,\n            is_strict: false,\n            unique_sets: vec![],\n            foreign_keys: vec![],\n            check_constraints: vec![],\n            rowid_alias_conflict_clause: None,\n        })\n    }\n\n    fn create_table_reference(\n        table: Arc<BTreeTable>,\n        join_info: Option<JoinInfo>,\n        internal_id: TableInternalId,\n    ) -> JoinedTable {\n        let name = table.name.clone();\n        let table = Table::BTree(table);\n        JoinedTable {\n            op: Operation::default_scan_for(&table),\n            table,\n            identifier: name,\n            internal_id,\n            join_info,\n            col_used_mask: ColumnUsedMask::default(),\n            column_use_counts: Vec::new(),\n            expression_index_usages: Vec::new(),\n            database_id: 0,\n            indexed: None,\n        }\n    }\n\n    fn create_column_expr(table: TableInternalId, column: usize, is_rowid_alias: bool) -> Expr {\n        Expr::Column {\n            database: None,\n            table,\n            column,\n            is_rowid_alias,\n        }\n    }\n\n    fn create_numeric_literal(value: &str) -> Expr {\n        Expr::Literal(ast::Literal::Numeric(value.to_string()))\n    }\n\n    fn create_string_literal(value: &str) -> Expr {\n        Expr::Literal(ast::Literal::String(value.to_string()))\n    }\n\n    fn assert_is_multi_index(\n        access_method: &crate::translate::optimizer::access_method::AccessMethod,\n    ) -> &Vec<MultiIndexBranchParams> {\n        let AccessMethodParams::MultiIndexScan { branches, .. } = &access_method.params else {\n            panic!(\"expected multi-index scan access method\");\n        };\n        branches\n    }\n\n    #[test]\n    fn test_multi_index_union_rejects_residuals_on_future_tables() {\n        let link = create_btree_table(\n            \"link\",\n            vec![\n                create_column_of_type(\"src\", Type::Integer),\n                create_column_of_type(\"dst\", Type::Integer),\n            ],\n        );\n        let item = create_btree_table(\n            \"item\",\n            vec![\n                create_column_of_type(\"id\", Type::Integer),\n                create_column_of_type(\"kind\", Type::Text),\n            ],\n        );\n        let meta = create_btree_table(\n            \"meta\",\n            vec![\n                create_column_of_type(\"id\", Type::Integer),\n                create_column_of_type(\"kind\", Type::Text),\n            ],\n        );\n\n        let mut table_id_counter = TableRefIdCounter::new();\n        let joined_tables = vec![\n            create_table_reference(link, None, table_id_counter.next()),\n            create_table_reference(\n                item,\n                Some(JoinInfo {\n                    join_type: JoinType::Inner,\n                    using: vec![],\n                    no_reorder: false,\n                }),\n                table_id_counter.next(),\n            ),\n            create_table_reference(\n                meta,\n                Some(JoinInfo {\n                    join_type: JoinType::Inner,\n                    using: vec![],\n                    no_reorder: false,\n                }),\n                table_id_counter.next(),\n            ),\n        ];\n\n        const LINK: usize = 0;\n        const ITEM: usize = 1;\n        const META: usize = 2;\n\n        let mut available_indexes = HashMap::default();\n        available_indexes.insert(\n            \"item\".to_string(),\n            VecDeque::from([Arc::new(Index {\n                name: \"idx_item_id\".to_string(),\n                table_name: \"item\".to_string(),\n                where_clause: None,\n                columns: vec![IndexColumn {\n                    name: \"id\".to_string(),\n                    order: SortOrder::Asc,\n                    pos_in_table: 0,\n                    collation: None,\n                    default: None,\n                    expr: None,\n                }],\n                unique: false,\n                ephemeral: false,\n                root_page: 2,\n                has_rowid: true,\n                index_method: None,\n                on_conflict: None,\n            })]),\n        );\n\n        let lhs_link_src = Expr::Binary(\n            Box::new(create_column_expr(\n                joined_tables[LINK].internal_id,\n                0,\n                false,\n            )),\n            Operator::Equals,\n            Box::new(create_numeric_literal(\"1\")),\n        );\n        let lhs_link_dst_item_id = Expr::Binary(\n            Box::new(create_column_expr(\n                joined_tables[LINK].internal_id,\n                1,\n                false,\n            )),\n            Operator::Equals,\n            Box::new(create_column_expr(\n                joined_tables[ITEM].internal_id,\n                0,\n                false,\n            )),\n        );\n        let rhs_link_dst = Expr::Binary(\n            Box::new(create_column_expr(\n                joined_tables[LINK].internal_id,\n                1,\n                false,\n            )),\n            Operator::Equals,\n            Box::new(create_numeric_literal(\"1\")),\n        );\n        let rhs_link_src_item_id = Expr::Binary(\n            Box::new(create_column_expr(\n                joined_tables[LINK].internal_id,\n                0,\n                false,\n            )),\n            Operator::Equals,\n            Box::new(create_column_expr(\n                joined_tables[ITEM].internal_id,\n                0,\n                false,\n            )),\n        );\n        let future_meta_kind = Expr::Binary(\n            Box::new(create_column_expr(\n                joined_tables[META].internal_id,\n                1,\n                false,\n            )),\n            Operator::Equals,\n            Box::new(create_string_literal(\"entity\")),\n        );\n\n        let left_disjunct = Expr::Binary(\n            Box::new(Expr::Binary(\n                Box::new(lhs_link_src),\n                Operator::And,\n                Box::new(lhs_link_dst_item_id),\n            )),\n            Operator::And,\n            Box::new(future_meta_kind.clone()),\n        );\n        let right_disjunct = Expr::Binary(\n            Box::new(Expr::Binary(\n                Box::new(rhs_link_dst),\n                Operator::And,\n                Box::new(rhs_link_src_item_id),\n            )),\n            Operator::And,\n            Box::new(future_meta_kind),\n        );\n        let where_clause = vec![WhereTerm {\n            expr: Expr::Binary(\n                Box::new(left_disjunct),\n                Operator::Or,\n                Box::new(right_disjunct),\n            ),\n            from_outer_join: None,\n            consumed: false,\n        }];\n\n        let table_references = TableReferences::new(joined_tables, vec![]);\n        let base_row_count = RowCountEstimate::hardcoded_fallback(&DEFAULT_PARAMS);\n        let lhs_mask = TableMask::from_table_number_iter([LINK].into_iter());\n\n        let access_method = consider_multi_index_union(\n            &table_references.joined_tables()[ITEM],\n            &where_clause,\n            &available_indexes,\n            &table_references,\n            &[],\n            &empty_schema(),\n            1.0,\n            base_row_count,\n            &DEFAULT_PARAMS,\n            Cost(f64::INFINITY),\n            &lhs_mask,\n            &AnalyzeStats::default(),\n        );\n\n        assert!(\n            access_method.is_none(),\n            \"future-table residuals must not produce a multi-index OR access method\"\n        );\n    }\n\n    #[test]\n    fn test_multi_index_intersection_supports_rowid_and_secondary_index_branches() {\n        let item = create_btree_table(\n            \"item\",\n            vec![\n                create_column(&TestColumn {\n                    name: \"id\".to_string(),\n                    ty: Type::Integer,\n                    is_rowid_alias: true,\n                }),\n                create_column_of_type(\"a\", Type::Integer),\n            ],\n        );\n\n        let mut table_id_counter = TableRefIdCounter::new();\n        let joined_tables = vec![create_table_reference(item, None, table_id_counter.next())];\n        let item_id = joined_tables[0].internal_id;\n\n        let mut available_indexes = HashMap::default();\n        available_indexes.insert(\n            \"item\".to_string(),\n            VecDeque::from([Arc::new(Index {\n                name: \"idx_item_a\".to_string(),\n                table_name: \"item\".to_string(),\n                where_clause: None,\n                columns: vec![IndexColumn {\n                    name: \"a\".to_string(),\n                    order: SortOrder::Asc,\n                    pos_in_table: 1,\n                    collation: None,\n                    default: None,\n                    expr: None,\n                }],\n                unique: false,\n                ephemeral: false,\n                root_page: 2,\n                has_rowid: true,\n                index_method: None,\n                on_conflict: None,\n            })]),\n        );\n\n        let where_clause = vec![\n            WhereTerm {\n                expr: Expr::Binary(\n                    Box::new(create_column_expr(item_id, 0, true)),\n                    Operator::Greater,\n                    Box::new(create_numeric_literal(\"10\")),\n                ),\n                from_outer_join: None,\n                consumed: false,\n            },\n            WhereTerm {\n                expr: Expr::Binary(\n                    Box::new(create_column_expr(item_id, 1, false)),\n                    Operator::Equals,\n                    Box::new(create_numeric_literal(\"7\")),\n                ),\n                from_outer_join: None,\n                consumed: false,\n            },\n        ];\n\n        let table_references = TableReferences::new(joined_tables, vec![]);\n        let base_row_count = RowCountEstimate::hardcoded_fallback(&DEFAULT_PARAMS);\n\n        let access_method = consider_multi_index_intersection(\n            &table_references.joined_tables()[0],\n            &where_clause,\n            &available_indexes,\n            &table_references,\n            &[],\n            &empty_schema(),\n            1.0,\n            base_row_count,\n            &DEFAULT_PARAMS,\n            Cost(f64::INFINITY),\n            &TableMask::new(),\n            &AnalyzeStats::default(),\n        )\n        .expect(\"rowid and secondary-index terms should be eligible for intersection\");\n\n        let branches = assert_is_multi_index(&access_method);\n        assert_eq!(branches.len(), 2);\n        assert!(\n            branches.iter().any(|branch| branch.index.is_none()),\n            \"expected one rowid branch\"\n        );\n        assert!(\n            branches\n                .iter()\n                .any(|branch| branch.index.as_ref().map(|idx| idx.name.as_str())\n                    == Some(\"idx_item_a\")),\n            \"expected one secondary-index branch\"\n        );\n    }\n\n    #[test]\n    fn test_multi_index_union_branch_reuses_compound_seek_analysis() {\n        let link = create_btree_table(\n            \"link\",\n            vec![\n                create_column_of_type(\"src\", Type::Integer),\n                create_column_of_type(\"dst\", Type::Integer),\n            ],\n        );\n        let item = create_btree_table(\n            \"item\",\n            vec![\n                create_column_of_type(\"id\", Type::Integer),\n                create_column_of_type(\"kind\", Type::Integer),\n            ],\n        );\n\n        let mut table_id_counter = TableRefIdCounter::new();\n        let joined_tables = vec![\n            create_table_reference(link, None, table_id_counter.next()),\n            create_table_reference(\n                item,\n                Some(JoinInfo {\n                    join_type: JoinType::Inner,\n                    using: vec![],\n                    no_reorder: false,\n                }),\n                table_id_counter.next(),\n            ),\n        ];\n\n        const LINK: usize = 0;\n        const ITEM: usize = 1;\n\n        let mut available_indexes = HashMap::default();\n        available_indexes.insert(\n            \"item\".to_string(),\n            VecDeque::from([Arc::new(Index {\n                name: \"idx_item_id_kind\".to_string(),\n                table_name: \"item\".to_string(),\n                where_clause: None,\n                columns: vec![\n                    IndexColumn {\n                        name: \"id\".to_string(),\n                        order: SortOrder::Asc,\n                        pos_in_table: 0,\n                        collation: None,\n                        default: None,\n                        expr: None,\n                    },\n                    IndexColumn {\n                        name: \"kind\".to_string(),\n                        order: SortOrder::Asc,\n                        pos_in_table: 1,\n                        collation: None,\n                        default: None,\n                        expr: None,\n                    },\n                ],\n                unique: false,\n                ephemeral: false,\n                root_page: 2,\n                has_rowid: true,\n                index_method: None,\n                on_conflict: None,\n            })]),\n        );\n\n        let left_disjunct = Expr::Binary(\n            Box::new(Expr::Binary(\n                Box::new(Expr::Binary(\n                    Box::new(create_column_expr(\n                        joined_tables[LINK].internal_id,\n                        0,\n                        false,\n                    )),\n                    Operator::Equals,\n                    Box::new(create_numeric_literal(\"1\")),\n                )),\n                Operator::And,\n                Box::new(Expr::Binary(\n                    Box::new(create_column_expr(\n                        joined_tables[ITEM].internal_id,\n                        0,\n                        false,\n                    )),\n                    Operator::Equals,\n                    Box::new(create_column_expr(\n                        joined_tables[LINK].internal_id,\n                        1,\n                        false,\n                    )),\n                )),\n            )),\n            Operator::And,\n            Box::new(Expr::Binary(\n                Box::new(create_column_expr(\n                    joined_tables[ITEM].internal_id,\n                    1,\n                    false,\n                )),\n                Operator::Equals,\n                Box::new(create_numeric_literal(\"7\")),\n            )),\n        );\n        let right_disjunct = Expr::Binary(\n            Box::new(Expr::Binary(\n                Box::new(Expr::Binary(\n                    Box::new(create_column_expr(\n                        joined_tables[LINK].internal_id,\n                        1,\n                        false,\n                    )),\n                    Operator::Equals,\n                    Box::new(create_numeric_literal(\"1\")),\n                )),\n                Operator::And,\n                Box::new(Expr::Binary(\n                    Box::new(create_column_expr(\n                        joined_tables[ITEM].internal_id,\n                        0,\n                        false,\n                    )),\n                    Operator::Equals,\n                    Box::new(create_column_expr(\n                        joined_tables[LINK].internal_id,\n                        0,\n                        false,\n                    )),\n                )),\n            )),\n            Operator::And,\n            Box::new(Expr::Binary(\n                Box::new(create_column_expr(\n                    joined_tables[ITEM].internal_id,\n                    1,\n                    false,\n                )),\n                Operator::Equals,\n                Box::new(create_numeric_literal(\"7\")),\n            )),\n        );\n\n        let where_clause = vec![WhereTerm {\n            expr: Expr::Binary(\n                Box::new(left_disjunct),\n                Operator::Or,\n                Box::new(right_disjunct),\n            ),\n            from_outer_join: None,\n            consumed: false,\n        }];\n\n        let table_references = TableReferences::new(joined_tables, vec![]);\n        let lhs_mask = TableMask::from_table_number_iter([LINK].into_iter());\n        let base_row_count = RowCountEstimate::hardcoded_fallback(&DEFAULT_PARAMS);\n\n        let access_method = consider_multi_index_union(\n            &table_references.joined_tables()[ITEM],\n            &where_clause,\n            &available_indexes,\n            &table_references,\n            &[],\n            &empty_schema(),\n            1.0,\n            base_row_count,\n            &DEFAULT_PARAMS,\n            Cost(f64::INFINITY),\n            &lhs_mask,\n            &AnalyzeStats::default(),\n        )\n        .expect(\"compound OR branches should produce a multi-index union\");\n\n        let branches = assert_is_multi_index(&access_method);\n        assert_eq!(branches.len(), 2);\n        for branch in branches {\n            assert_eq!(\n                branch.index.as_ref().map(|idx| idx.name.as_str()),\n                Some(\"idx_item_id_kind\")\n            );\n            let super::MultiIndexBranchAccessParams::Seek {\n                constraint_refs, ..\n            } = &branch.access\n            else {\n                panic!(\"compound OR test should choose ordinary seek branches\");\n            };\n            assert_eq!(\n                constraint_refs.len(),\n                2,\n                \"branch should use both id and kind in the compound seek\"\n            );\n        }\n    }\n\n    #[test]\n    fn test_multi_index_union_residual_selectivity_reduces_row_estimate() {\n        let link = create_btree_table(\n            \"link\",\n            vec![\n                create_column_of_type(\"src\", Type::Integer),\n                create_column_of_type(\"dst\", Type::Integer),\n            ],\n        );\n        let item = create_btree_table(\n            \"item\",\n            vec![\n                create_column_of_type(\"id\", Type::Integer),\n                create_column_of_type(\"kind\", Type::Integer),\n            ],\n        );\n\n        let mut table_id_counter = TableRefIdCounter::new();\n        let joined_tables = vec![\n            create_table_reference(link, None, table_id_counter.next()),\n            create_table_reference(\n                item,\n                Some(JoinInfo {\n                    join_type: JoinType::Inner,\n                    using: vec![],\n                    no_reorder: false,\n                }),\n                table_id_counter.next(),\n            ),\n        ];\n\n        const LINK: usize = 0;\n        const ITEM: usize = 1;\n        let link_id = joined_tables[LINK].internal_id;\n        let item_id = joined_tables[ITEM].internal_id;\n\n        let mut available_indexes = HashMap::default();\n        available_indexes.insert(\n            \"item\".to_string(),\n            VecDeque::from([Arc::new(Index {\n                name: \"idx_item_id\".to_string(),\n                table_name: \"item\".to_string(),\n                where_clause: None,\n                columns: vec![IndexColumn {\n                    name: \"id\".to_string(),\n                    order: SortOrder::Asc,\n                    pos_in_table: 0,\n                    collation: None,\n                    default: None,\n                    expr: None,\n                }],\n                unique: false,\n                ephemeral: false,\n                root_page: 2,\n                has_rowid: true,\n                index_method: None,\n                on_conflict: None,\n            })]),\n        );\n\n        let make_branch = |literal_col, join_col, item_kind: Option<&str>| {\n            let branch = Expr::Binary(\n                Box::new(Expr::Binary(\n                    Box::new(create_column_expr(link_id, literal_col, false)),\n                    Operator::Equals,\n                    Box::new(create_numeric_literal(\"1\")),\n                )),\n                Operator::And,\n                Box::new(Expr::Binary(\n                    Box::new(create_column_expr(item_id, 0, false)),\n                    Operator::Equals,\n                    Box::new(create_column_expr(link_id, join_col, false)),\n                )),\n            );\n\n            if let Some(kind) = item_kind {\n                Expr::Binary(\n                    Box::new(branch),\n                    Operator::And,\n                    Box::new(Expr::Binary(\n                        Box::new(create_column_expr(item_id, 1, false)),\n                        Operator::Equals,\n                        Box::new(create_numeric_literal(kind)),\n                    )),\n                )\n            } else {\n                branch\n            }\n        };\n        let make_join_expr = |item_kind: Option<&str>| {\n            vec![WhereTerm {\n                expr: Expr::Binary(\n                    Box::new(make_branch(0, 1, item_kind)),\n                    Operator::Or,\n                    Box::new(make_branch(1, 0, item_kind)),\n                ),\n                from_outer_join: None,\n                consumed: false,\n            }]\n        };\n\n        let table_references = TableReferences::new(joined_tables, vec![]);\n        let lhs_mask = TableMask::from_table_number_iter([LINK].into_iter());\n        let base_row_count = RowCountEstimate::hardcoded_fallback(&DEFAULT_PARAMS);\n\n        let without_residual = consider_multi_index_union(\n            &table_references.joined_tables()[ITEM],\n            &make_join_expr(None),\n            &available_indexes,\n            &table_references,\n            &[],\n            &empty_schema(),\n            1.0,\n            base_row_count,\n            &DEFAULT_PARAMS,\n            Cost(f64::INFINITY),\n            &lhs_mask,\n            &AnalyzeStats::default(),\n        )\n        .expect(\"plain OR branches should produce a multi-index union\");\n\n        let with_residual = consider_multi_index_union(\n            &table_references.joined_tables()[ITEM],\n            &make_join_expr(Some(\"7\")),\n            &available_indexes,\n            &table_references,\n            &[],\n            &empty_schema(),\n            1.0,\n            base_row_count,\n            &DEFAULT_PARAMS,\n            Cost(f64::INFINITY),\n            &lhs_mask,\n            &AnalyzeStats::default(),\n        )\n        .expect(\"residual-filtered OR branches should still produce a multi-index union\");\n\n        assert!(\n            with_residual.estimated_rows_per_outer_row\n                < without_residual.estimated_rows_per_outer_row,\n            \"branch-local residual filters must reduce the multi-index row estimate\"\n        );\n    }\n}\n"
  },
  {
    "path": "core/translate/optimizer/order.rs",
    "content": "use crate::schema::Table;\nuse crate::turso_assert_greater_than_or_equal;\nuse crate::{\n    schema::{FromClauseSubquery, Index, Schema},\n    translate::{\n        collate::{get_collseq_from_expr, CollationSeq},\n        expression_index::normalize_expr_for_index_matching,\n        optimizer::access_method::AccessMethodParams,\n        optimizer::constraints::RangeConstraintRef,\n        plan::{\n            GroupBy, HashJoinType, IterationDirection, JoinedTable, Operation, Plan, Scan,\n            SimpleAggregate, TableReferences,\n        },\n        planner::table_mask_from_expr,\n    },\n    util::exprs_are_equivalent,\n};\nuse turso_parser::ast::{self, SortOrder, TableInternalId};\n\nuse super::{\n    access_method::AccessMethod,\n    cost::{is_unique_point_lookup, IndexInfo},\n    join::JoinN,\n};\n\n/// Target component in an ORDER BY/GROUP BY that may be a plain column or an expression.\n#[derive(Debug, PartialEq, Clone)]\npub enum ColumnTarget {\n    Column(usize),\n    RowId,\n    /// We know that the ast lives at least as long as the Statement/Program,\n    /// so we store a raw pointer here to avoid cloning yet another ast::Expr\n    Expr(*const ast::Expr),\n}\n\n/// A convenience struct for representing a (table_no, column_target, [SortOrder]) tuple.\n#[derive(Debug, PartialEq, Clone)]\npub struct ColumnOrder {\n    pub table_id: TableInternalId,\n    pub target: ColumnTarget,\n    pub order: SortOrder,\n    pub collation: CollationSeq,\n}\n\n#[derive(Debug, PartialEq, Clone)]\n/// If an [OrderTarget] is satisfied, then [EliminatesSort] describes which part\n/// of the query no longer requires sorting.\npub enum EliminatesSortBy {\n    Group,\n    Order,\n    GroupByAndOrder,\n}\n\n#[derive(Debug, PartialEq, Clone)]\npub enum OrderTargetPurpose {\n    /// Matching this target lets the planner eliminate a later ORDER BY and/or\n    /// GROUP BY sort step.\n    EliminatesSort(EliminatesSortBy),\n    /// Matching this target enables an extremum fast path, analogous to\n    /// SQLite's WHERE_ORDERBY_MIN/MAX planning mode.\n    Extremum,\n}\n\n#[derive(Debug, PartialEq, Clone)]\n/// An [OrderTarget] is considered in join optimization and index selection,\n/// so that if a given join ordering and its access methods satisfy the [OrderTarget],\n/// then the join ordering and its access methods are preferred, all other things being equal.\npub struct OrderTarget {\n    pub columns: Vec<ColumnOrder>,\n    pub purpose: OrderTargetPurpose,\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum EqualityPrefixScope {\n    /// Candidate scoring may skip any equality-constrained seek prefix because\n    /// it only reasons about the order within that specific seek.\n    AnyEquality,\n    /// Final ORDER BY / GROUP BY elimination may only skip globally constant\n    /// equality prefixes. Join-dependent equalities vary per outer row and do\n    /// not guarantee a globally ordered concatenation of inner scans.\n    ConstantEquality,\n}\n\nimpl OrderTarget {\n    /// Build an `OrderTarget` from a list of expressions if they can all be\n    /// satisfied by a single-table ordering (needed for index satisfaction).\n    fn maybe_from_iterator<'a>(\n        list: impl Iterator<Item = (&'a ast::Expr, SortOrder)> + Clone,\n        tables: &crate::translate::plan::TableReferences,\n        purpose: OrderTargetPurpose,\n    ) -> Option<Self> {\n        if list.clone().count() == 0 {\n            return None;\n        }\n        let mut cols = Vec::new();\n        for (expr, order) in list {\n            let col = expr_to_column_order(expr, order, tables)?;\n            cols.push(col);\n        }\n        Some(OrderTarget {\n            columns: cols,\n            purpose,\n        })\n    }\n\n    pub fn is_extremum(&self) -> bool {\n        matches!(self.purpose, OrderTargetPurpose::Extremum)\n    }\n}\n\n/// Build the synthetic ordering requirement used by simple MIN/MAX aggregation.\npub fn simple_aggregate_order_target(\n    simple_aggregate: &SimpleAggregate,\n    tables: &TableReferences,\n) -> Option<OrderTarget> {\n    let SimpleAggregate::MinMax(min_max) = simple_aggregate else {\n        return None;\n    };\n\n    let mut target = OrderTarget::maybe_from_iterator(\n        std::iter::once((&min_max.argument, min_max.order)),\n        tables,\n        OrderTargetPurpose::Extremum,\n    )?;\n    if let Some(coll) = min_max.collation {\n        target.columns[0].collation = coll;\n    }\n    Some(target)\n}\n\n/// Compute an [OrderTarget] for the join optimizer to use.\n/// Ideally, a join order is both efficient in joining the tables\n/// but also returns the results in an order that minimizes the amount of\n/// sorting that needs to be done later (either in GROUP BY, ORDER BY, or both).\n///\n/// TODO: this does not currently handle the case where we definitely cannot eliminate\n/// the ORDER BY sorter, but we could still eliminate the GROUP BY sorter.\npub fn compute_order_target(\n    order_by: &mut Vec<(Box<ast::Expr>, SortOrder)>,\n    group_by_opt: Option<&mut GroupBy>,\n    tables: &TableReferences,\n) -> Option<OrderTarget> {\n    match (order_by.is_empty(), group_by_opt) {\n        // No ordering demands - we don't care what order the joined result rows are in\n        (true, None) => None,\n        // Only ORDER BY - we would like the joined result rows to be in the order specified by the ORDER BY\n        (false, None) => OrderTarget::maybe_from_iterator(\n            order_by.iter().map(|(expr, order)| (expr.as_ref(), *order)),\n            tables,\n            OrderTargetPurpose::EliminatesSort(EliminatesSortBy::Order),\n        ),\n        // Only GROUP BY - we would like the joined result rows to be in the order specified by the GROUP BY\n        (true, Some(group_by)) => OrderTarget::maybe_from_iterator(\n            group_by.exprs.iter().map(|expr| (expr, SortOrder::Asc)),\n            tables,\n            OrderTargetPurpose::EliminatesSort(EliminatesSortBy::Group),\n        ),\n        // Both ORDER BY and GROUP BY:\n        // If the GROUP BY does not contain all the expressions in the ORDER BY,\n        // then we must separately sort the result rows for ORDER BY anyway.\n        // However, in that case we can use the GROUP BY expressions as the target order for the join,\n        // so that we don't have to sort twice.\n        //\n        // If the GROUP BY contains all the expressions in the ORDER BY,\n        // then we again can use the GROUP BY expressions as the target order for the join;\n        // however in this case we must take the ASC/DESC from ORDER BY into account.\n        (false, Some(group_by)) => {\n            // Does the group by contain all expressions in the order by?\n            let group_by_contains_all = order_by.iter().all(|(expr, _)| {\n                group_by\n                    .exprs\n                    .iter()\n                    .any(|group_by_expr| exprs_are_equivalent(expr, group_by_expr))\n            });\n            // If not, let's try to target an ordering that matches the group by -- we don't care about ASC/DESC\n            if !group_by_contains_all {\n                return OrderTarget::maybe_from_iterator(\n                    group_by.exprs.iter().map(|expr| (expr, SortOrder::Asc)),\n                    tables,\n                    OrderTargetPurpose::EliminatesSort(EliminatesSortBy::Group),\n                );\n            }\n            // If yes, let's try to target an ordering that matches the GROUP BY columns,\n            // but the ORDER BY orderings. First, we need to reorder the GROUP BY columns to match the ORDER BY columns.\n            group_by.exprs.sort_by_key(|expr| {\n                order_by\n                    .iter()\n                    .position(|(order_by_expr, _)| exprs_are_equivalent(expr, order_by_expr))\n                    .map_or(usize::MAX, |i| i)\n            });\n\n            // Now, regardless of whether we can eventually eliminate the sorting entirely in the optimizer,\n            // we know that we don't need ORDER BY sorting anyway, because the GROUP BY will sort the result since\n            // it contains all the necessary columns required for the ORDER BY, and the GROUP BY columns are now in the correct order.\n            // First, however, we need to make sure the GROUP BY sorter's column sort directions match the ORDER BY requirements.\n            turso_assert_greater_than_or_equal!(group_by.exprs.len(), order_by.len());\n            let sort_order = &mut group_by.sort_order;\n            for (i, (_, order_by_dir)) in order_by.iter().enumerate() {\n                sort_order[i] = *order_by_dir;\n            }\n            // The sort_by_key above reordered group_by.exprs but not sort_order,\n            // so remaining positions may have stale values. GROUP BY columns not\n            // in ORDER BY should default to ASC (matching SQLite's tie-breaking).\n            for s in &mut sort_order[order_by.len()..] {\n                *s = SortOrder::Asc;\n            }\n            // Now we can remove the ORDER BY from the query.\n            order_by.clear();\n\n            OrderTarget::maybe_from_iterator(\n                group_by\n                    .exprs\n                    .iter()\n                    .zip(group_by.sort_order.iter())\n                    .map(|(expr, dir)| (expr, *dir)),\n                tables,\n                OrderTargetPurpose::EliminatesSort(EliminatesSortBy::GroupByAndOrder),\n            )\n        }\n    }\n}\n\n/// Check if the plan's row iteration order matches the [OrderTarget]'s column order.\n/// If yes, and this plan is selected, then a sort operation can be eliminated.\npub fn plan_satisfies_order_target(\n    plan: &JoinN,\n    access_methods_arena: &[AccessMethod],\n    joined_tables: &[JoinedTable],\n    order_target: &OrderTarget,\n    schema: &Schema,\n) -> bool {\n    // Outer hash joins emit unmatched rows in hash-bucket order, not scan order.\n    for (_, access_method_index) in plan.data.iter() {\n        let access_method = &access_methods_arena[*access_method_index];\n        if let AccessMethodParams::HashJoin { join_type, .. } = &access_method.params {\n            if matches!(join_type, HashJoinType::LeftOuter | HashJoinType::FullOuter) {\n                return false;\n            }\n        }\n    }\n\n    let mut target_col_idx = 0;\n    let num_cols_in_order_target = order_target.columns.len();\n    for (loop_pos, (table_index, access_method_index)) in plan.data.iter().enumerate() {\n        let access_method = &access_methods_arena[*access_method_index];\n        let table_ref = &joined_tables[*table_index];\n\n        // Outer joins can emit an extra row with NULLs on the right-hand side\n        // when no match is found. Because that row is produced after the scan or\n        // seek, we cannot rely on the right-hand table's access order to satisfy\n        // ORDER BY / GROUP BY terms that reference that table.\n        if table_ref\n            .join_info\n            .as_ref()\n            .is_some_and(|join_info| join_info.is_outer())\n            && order_target.columns[target_col_idx..]\n                .iter()\n                .any(|target_col| target_col.table_id == table_ref.internal_id)\n        {\n            return false;\n        }\n\n        // Check if this table has an access method that provides the right ordering.\n        let consumed = match &access_method.params {\n            AccessMethodParams::BTreeTable {\n                iter_dir,\n                index: index_opt,\n                constraint_refs,\n            } => btree_access_order_consumed(\n                table_ref,\n                *iter_dir,\n                index_opt.as_deref(),\n                constraint_refs,\n                &order_target.columns[target_col_idx..],\n                schema,\n                EqualityPrefixScope::ConstantEquality,\n            ),\n            AccessMethodParams::MaterializedSubquery {\n                index,\n                constraint_refs,\n                iter_dir,\n            } => btree_access_order_consumed(\n                table_ref,\n                *iter_dir,\n                Some(index.as_ref()),\n                constraint_refs,\n                &order_target.columns[target_col_idx..],\n                schema,\n                EqualityPrefixScope::ConstantEquality,\n            ),\n            AccessMethodParams::Subquery { iter_dir } => {\n                let Table::FromClauseSubquery(from_clause_subquery) = &table_ref.table else {\n                    unreachable!(\n                        \"access_method.params::Subquery must be for a FromClauseSubquery table\"\n                    );\n                };\n                subquery_intrinsic_order_consumed(\n                    table_ref.internal_id,\n                    from_clause_subquery,\n                    *iter_dir,\n                    &order_target.columns[target_col_idx..],\n                    schema,\n                )\n            }\n            _ => return false,\n        };\n\n        if consumed == 0 {\n            return false;\n        }\n        target_col_idx += consumed;\n        if target_col_idx == num_cols_in_order_target {\n            return true;\n        }\n\n        // The next ORDER BY column can only come from a deeper loop if the rows\n        // output by this loop are unique for the columns so far. If they're not unique,\n        // the inner loop would repeat the same values for each duplicate, resulting in\n        // an output like `A B C ... A B C ...` instead of the correct fully sorted order `A A B B ...`.\n        let next_term_comes_from_later_loop =\n            order_target\n                .columns\n                .get(target_col_idx)\n                .is_some_and(|target_col| {\n                    plan.data[loop_pos + 1..]\n                        .iter()\n                        .any(|(later_table_index, _)| {\n                            joined_tables[*later_table_index].internal_id == target_col.table_id\n                        })\n                });\n        if next_term_comes_from_later_loop\n            && !access_method_emits_unique_order_prefix(access_method, consumed)\n        {\n            return false;\n        }\n    }\n    target_col_idx == num_cols_in_order_target\n}\n\nfn access_method_emits_unique_order_prefix(\n    access_method: &AccessMethod,\n    consumed_order_terms: usize,\n) -> bool {\n    match &access_method.params {\n        AccessMethodParams::BTreeTable {\n            index,\n            constraint_refs,\n            ..\n        } => access_path_makes_consumed_prefix_unique(\n            index.as_deref(),\n            constraint_refs,\n            consumed_order_terms,\n        ),\n        AccessMethodParams::MaterializedSubquery {\n            index,\n            constraint_refs,\n            ..\n        } => access_path_makes_consumed_prefix_unique(\n            Some(index.as_ref()),\n            constraint_refs,\n            consumed_order_terms,\n        ),\n        AccessMethodParams::Subquery { .. }\n        | AccessMethodParams::HashJoin { .. }\n        | AccessMethodParams::VirtualTable { .. }\n        | AccessMethodParams::IndexMethod { .. }\n        | AccessMethodParams::MultiIndexScan { .. }\n        | AccessMethodParams::InSeek { .. } => false,\n    }\n}\n\nfn access_path_makes_consumed_prefix_unique(\n    index: Option<&Index>,\n    constraint_refs: &[RangeConstraintRef],\n    consumed_order_terms: usize,\n) -> bool {\n    if is_unique_point_lookup(index_info_for_access(index), constraint_refs) {\n        return true;\n    }\n\n    match index {\n        // Table scans only provide rowid order. If that rowid term was consumed,\n        // the prefix is unique even though the scan obviously returns many rows.\n        None => consumed_order_terms >= 1,\n        Some(index) => {\n            let eq_prefix_len = constraint_refs\n                .iter()\n                .take_while(|constraint| constraint.eq.is_some())\n                .count();\n            let unique_prefix_terms = eq_prefix_len + consumed_order_terms;\n\n            // Unique indexes become prefix-unique once all key columns are either\n            // fixed by equality or consumed as ORDER BY terms.\n            if index.unique && unique_prefix_terms >= index.columns.len() {\n                return true;\n            }\n\n            // Rowid tables keep duplicate secondary-index keys ordered by rowid.\n            // If the consumed ORDER BY terms already include that implicit rowid\n            // suffix, the emitted prefix is unique too.\n            index.has_rowid && unique_prefix_terms > index.columns.len()\n        }\n    }\n}\n\nfn index_info_for_access(index: Option<&Index>) -> IndexInfo {\n    match index {\n        Some(index) => IndexInfo {\n            unique: index.unique,\n            column_count: index.columns.len(),\n            covering: false,\n            rows_per_leaf_page: 0.0, // unused here — only unique/column_count matter\n        },\n        None => IndexInfo {\n            unique: true,\n            column_count: 1,\n            covering: false,\n            rows_per_leaf_page: 0.0, // unused here — only unique/column_count matter\n        },\n    }\n}\n\n/// Return how many leading target columns a FROM-subquery can provide from its\n/// own output order, without fabricating an extra probe index.\n///\n/// We recognize three sources of intrinsic order:\n/// 1. An explicit final `ORDER BY` on the subquery.\n/// 2. GROUP BY keys (we always use a sorter, never hashing - FOR NOW).\n/// 3. A simple single-source finalized scan whose output order is already known.\npub fn subquery_intrinsic_order_consumed(\n    table_id: TableInternalId,\n    subquery: &FromClauseSubquery,\n    iter_dir: IterationDirection,\n    target: &[ColumnOrder],\n    schema: &Schema,\n) -> usize {\n    let Plan::Select(select_plan) = subquery.plan.as_ref() else {\n        // Don't consider sort elision for compound selects\n        return 0;\n    };\n    // Explicit ORDER BY takes priority.\n    if !select_plan.order_by.is_empty() {\n        let intrinsic = build_intrinsic_order(\n            table_id,\n            select_plan,\n            select_plan\n                .order_by\n                .iter()\n                .map(|(expr, order)| (expr.as_ref(), *order)),\n        );\n        return match_intrinsic_order(&intrinsic, iter_dir, target);\n    }\n    // When ORDER BY was merged into GROUP BY and cleared, the GROUP BY\n    // sort_order still describes the output row order.\n    if let Some(group_by) = &select_plan.group_by {\n        let intrinsic = build_intrinsic_order(\n            table_id,\n            select_plan,\n            group_by\n                .exprs\n                .iter()\n                .zip(group_by.sort_order.iter().copied()),\n        );\n        let consumed = match_intrinsic_order(&intrinsic, iter_dir, target);\n        if consumed > 0 {\n            return consumed;\n        }\n    }\n    finalized_scan_subquery_order_consumed(table_id, select_plan, iter_dir, target, schema)\n}\n\n/// Build a `ColumnOrder` list from expressions and sort directions by mapping\n/// each expression to a result column position.\nfn build_intrinsic_order(\n    table_id: TableInternalId,\n    select_plan: &crate::translate::plan::SelectPlan,\n    exprs: impl Iterator<Item = (impl std::borrow::Borrow<ast::Expr>, SortOrder)>,\n) -> Vec<ColumnOrder> {\n    let mut intrinsic = Vec::new();\n    for (expr, order) in exprs {\n        let expr = expr.borrow();\n        let Some((col_idx, result_col)) = select_plan\n            .result_columns\n            .iter()\n            .enumerate()\n            .find(|(_, result_col)| exprs_are_equivalent(expr, &result_col.expr))\n        else {\n            break;\n        };\n        let Ok(collation) = get_collseq_from_expr(expr, &select_plan.table_references) else {\n            break;\n        };\n        intrinsic.push(ColumnOrder {\n            table_id,\n            target: ColumnTarget::Column(col_idx),\n            order,\n            collation: collation.unwrap_or_else(|| {\n                get_collseq_from_expr(&result_col.expr, &select_plan.table_references)\n                    .ok()\n                    .flatten()\n                    .unwrap_or_default()\n            }),\n        });\n    }\n    intrinsic\n}\n\n/// Compare a subquery's intrinsic column order against an outer order target,\n/// accounting for iteration direction. Returns how many leading target columns\n/// are satisfied.\nfn match_intrinsic_order(\n    intrinsic: &[ColumnOrder],\n    iter_dir: IterationDirection,\n    target: &[ColumnOrder],\n) -> usize {\n    let target_len = target.len().min(intrinsic.len());\n    for (intrinsic_col, target_col) in intrinsic.iter().zip(target.iter()).take(target_len) {\n        if intrinsic_col.table_id != target_col.table_id\n            || intrinsic_col.target != target_col.target\n            || intrinsic_col.collation != target_col.collation\n        {\n            return 0;\n        }\n        let expected_order = match iter_dir {\n            IterationDirection::Forwards => intrinsic_col.order,\n            IterationDirection::Backwards => match intrinsic_col.order {\n                SortOrder::Asc => SortOrder::Desc,\n                SortOrder::Desc => SortOrder::Asc,\n            },\n        };\n        if expected_order != target_col.order {\n            return 0;\n        }\n    }\n    target_len\n}\n\n/// Derive subquery output order from the finalized inner scan when there is no\n/// explicit `ORDER BY`.\n///\n/// This intentionally starts narrow: single-source, non-aggregate,\n/// non-window, non-distinct SELECTs only. Those are the cases where insertion\n/// order into the materialized table is just the underlying scan order.\nfn finalized_scan_subquery_order_consumed(\n    table_id: TableInternalId,\n    select_plan: &crate::translate::plan::SelectPlan,\n    iter_dir: IterationDirection,\n    target: &[ColumnOrder],\n    schema: &Schema,\n) -> usize {\n    if select_plan.group_by.is_some()\n        || !select_plan.aggregates.is_empty()\n        || select_plan.limit.is_some()\n        || select_plan.offset.is_some()\n        || select_plan.window.is_some()\n        || select_plan.distinctness.is_distinct()\n        || !select_plan.values.is_empty()\n        || select_plan.join_order.len() != 1\n        || select_plan.joined_tables().len() != 1\n    {\n        return 0;\n    }\n\n    let joined_table = &select_plan.joined_tables()[select_plan.join_order[0].original_idx];\n\n    // Extract inner iteration direction from the scan operation.\n    let inner_iter_dir = match &joined_table.op {\n        Operation::Scan(Scan::BTreeTable { iter_dir, .. })\n        | Operation::Scan(Scan::Subquery { iter_dir }) => *iter_dir,\n        _ => return 0,\n    };\n\n    // The outer scan direction composes with the direction used to populate the\n    // materialized table. Reversing a backwards-populated table restores the\n    // original key order.\n    let effective_iter_dir = match (inner_iter_dir, iter_dir) {\n        (IterationDirection::Forwards, IterationDirection::Forwards)\n        | (IterationDirection::Backwards, IterationDirection::Backwards) => {\n            IterationDirection::Forwards\n        }\n        (IterationDirection::Forwards, IterationDirection::Backwards)\n        | (IterationDirection::Backwards, IterationDirection::Forwards) => {\n            IterationDirection::Backwards\n        }\n    };\n\n    // Map outer target columns to inner scan columns through result column expressions.\n    let mut mapped_target = Vec::with_capacity(target.len());\n    for target_col in target {\n        if target_col.table_id != table_id {\n            return 0;\n        }\n        let ColumnTarget::Column(result_col_idx) = target_col.target else {\n            return 0;\n        };\n        let Some(result_col) = select_plan.result_columns.get(result_col_idx) else {\n            return 0;\n        };\n        // The outer query sees result columns of the materialized subquery, but\n        // the ordering proof has to be checked against the inner scan columns.\n        let Some(mut inner_target_col) = expr_to_column_order(\n            &result_col.expr,\n            target_col.order,\n            &select_plan.table_references,\n        ) else {\n            return 0;\n        };\n        if inner_target_col.table_id != joined_table.internal_id\n            || inner_target_col.collation != target_col.collation\n        {\n            return 0;\n        }\n        inner_target_col.order = target_col.order;\n        mapped_target.push(inner_target_col);\n    }\n\n    match &joined_table.op {\n        Operation::Scan(Scan::BTreeTable { index, .. }) => btree_access_order_consumed(\n            joined_table,\n            effective_iter_dir,\n            index.as_deref(),\n            &[],\n            &mapped_target,\n            schema,\n            EqualityPrefixScope::ConstantEquality,\n        ),\n        Operation::Scan(Scan::Subquery { .. }) => {\n            let Table::FromClauseSubquery(from_clause_subquery) = &joined_table.table else {\n                return 0;\n            };\n            subquery_intrinsic_order_consumed(\n                joined_table.internal_id,\n                from_clause_subquery,\n                effective_iter_dir,\n                &mapped_target,\n                schema,\n            )\n        }\n        _ => 0,\n    }\n}\n\nfn expr_to_column_order(\n    expr: &ast::Expr,\n    order: SortOrder,\n    tables: &TableReferences,\n) -> Option<ColumnOrder> {\n    match expr {\n        ast::Expr::Column {\n            table: table_id,\n            column,\n            ..\n        } => {\n            let table = tables.find_joined_table_by_internal_id(*table_id)?;\n            let col = table.columns().get(*column)?;\n            return Some(ColumnOrder {\n                table_id: *table_id,\n                target: ColumnTarget::Column(*column),\n                order,\n                collation: col.collation(),\n            });\n        }\n        ast::Expr::Collate(expr, collation) => {\n            if let ast::Expr::Column {\n                table: table_id,\n                column,\n                ..\n            } = expr.as_ref()\n            {\n                let collation = CollationSeq::new(collation.as_str()).unwrap_or_default();\n                return Some(ColumnOrder {\n                    table_id: *table_id,\n                    target: ColumnTarget::Column(*column),\n                    order,\n                    collation,\n                });\n            };\n        }\n        ast::Expr::RowId { table, .. } => {\n            return Some(ColumnOrder {\n                table_id: *table,\n                target: ColumnTarget::RowId,\n                order,\n                collation: CollationSeq::default(),\n            });\n        }\n        _ => {}\n    }\n    let mask = table_mask_from_expr(expr, tables, &[]).ok()?;\n    if mask.table_count() != 1 {\n        return None;\n    }\n    let collation = get_collseq_from_expr(expr, tables)\n        .ok()?\n        .unwrap_or_default();\n    let table_no = tables\n        .joined_tables()\n        .iter()\n        .enumerate()\n        .find_map(|(i, _)| mask.contains_table(i).then_some(i))?;\n    let table_id = tables.joined_tables()[table_no].internal_id;\n    Some(ColumnOrder {\n        table_id,\n        target: ColumnTarget::Expr(expr as *const ast::Expr),\n        order,\n        collation,\n    })\n}\n\nfn target_matches_index_column(\n    target_col: &ColumnOrder,\n    idx_col: &crate::schema::IndexColumn,\n    table_ref: &JoinedTable,\n) -> bool {\n    if target_col.table_id != table_ref.internal_id {\n        return false;\n    }\n    match (&target_col.target, &idx_col.expr) {\n        (ColumnTarget::Column(col_no), None) => idx_col.pos_in_table == *col_no,\n        (ColumnTarget::Expr(expr), Some(idx_expr)) => {\n            let target_expr = unsafe { &**expr };\n            if exprs_are_equivalent(target_expr, idx_expr) {\n                return true;\n            }\n            // Expression indexes are compared against the normalized form that\n            // was stored in the schema. A query may write the same expression in\n            // a slightly different but equivalent way, so normalize before the\n            // final comparison.\n            let refs = TableReferences::new(vec![table_ref.clone()], Vec::new());\n            let normalized = normalize_expr_for_index_matching(target_expr, table_ref, &refs);\n            exprs_are_equivalent(&normalized, idx_expr)\n        }\n        _ => false,\n    }\n}\n\n/// Return how many leading `order_target` columns this single-table btree\n/// access path can satisfy.\n///\n/// This is shared by both candidate scoring and final ORDER BY / GROUP BY\n/// elimination so they use the same column-matching, collation, custom-type,\n/// and hidden-rowid-suffix rules. The caller supplies\n/// [`EqualityPrefixScope`] because candidate scoring may skip any equality\n/// prefix in the chosen seek key, while final global ordering proof may only\n/// skip prefixes that are constant across all output rows.\npub(super) fn btree_access_order_consumed(\n    table_ref: &JoinedTable,\n    iter_dir: IterationDirection,\n    index: Option<&Index>,\n    constraint_refs: &[RangeConstraintRef],\n    order_target: &[ColumnOrder],\n    schema: &Schema,\n    equality_prefix_scope: EqualityPrefixScope,\n) -> usize {\n    let Some(first_target_col) = order_target.first() else {\n        return 0;\n    };\n\n    let rowid_alias_col = table_ref\n        .table\n        .columns()\n        .iter()\n        .position(|c| c.is_rowid_alias());\n\n    match index {\n        None => {\n            // Without an index, only rowid order is available.\n            if first_target_col.table_id != table_ref.internal_id {\n                return 0;\n            }\n            match first_target_col.target {\n                ColumnTarget::RowId => {}\n                ColumnTarget::Column(col_no) => {\n                    let Some(rowid_alias_col) = rowid_alias_col else {\n                        return 0;\n                    };\n                    if col_no != rowid_alias_col {\n                        return 0;\n                    }\n                }\n                ColumnTarget::Expr(_) => return 0,\n            }\n            let correct_order = if iter_dir == IterationDirection::Forwards {\n                first_target_col.order == SortOrder::Asc\n            } else {\n                first_target_col.order == SortOrder::Desc\n            };\n            usize::from(correct_order)\n        }\n        Some(index) => {\n            let mut col_idx = 0;\n            let mut idx_pos = 0;\n            while col_idx < order_target.len() && idx_pos < index.columns.len() {\n                let target_col = &order_target[col_idx];\n                if target_col.table_id != table_ref.internal_id {\n                    break;\n                }\n\n                let idx_col = &index.columns[idx_pos];\n                let eq_prefix_usable = constraint_refs.iter().any(|constraint| {\n                    constraint.index_col_pos == idx_pos\n                        && constraint.eq.as_ref().is_some_and(|eq| {\n                            equality_prefix_scope == EqualityPrefixScope::AnyEquality || eq.is_const\n                        })\n                });\n                if eq_prefix_usable {\n                    // Equality-constrained prefix columns produce a single value\n                    // per seek, so they do not disturb the ordering of the\n                    // remaining suffix. If the ORDER BY / GROUP BY also mentions\n                    // the same column with the same collation, that target term\n                    // is satisfied trivially and can be consumed here too.\n                    if target_matches_index_column(target_col, idx_col, table_ref) {\n                        let same_collation =\n                            target_col.collation == idx_col.collation.unwrap_or_default();\n                        if !same_collation {\n                            break;\n                        }\n                        col_idx += 1;\n                    }\n                    idx_pos += 1;\n                    continue;\n                }\n\n                if !target_matches_index_column(target_col, idx_col, table_ref) {\n                    break;\n                }\n\n                // Custom type columns store encoded blobs. The B-tree's bytewise\n                // ordering does not match the custom type's semantic ordering, so\n                // the index cannot satisfy ORDER BY for those columns.\n                if let ColumnTarget::Column(col_no) = &target_col.target {\n                    if let Some(col) = table_ref.table.columns().get(*col_no) {\n                        if schema\n                            .get_type_def(&col.ty_str, table_ref.table.is_strict())\n                            .is_some()\n                        {\n                            break;\n                        }\n                    }\n                }\n\n                if target_col.collation != idx_col.collation.unwrap_or_default() {\n                    break;\n                }\n\n                let correct_order = if iter_dir == IterationDirection::Forwards {\n                    target_col.order == idx_col.order\n                } else {\n                    target_col.order != idx_col.order\n                };\n                if !correct_order {\n                    break;\n                }\n                col_idx += 1;\n                idx_pos += 1;\n            }\n\n            // SQLite-style rowid tables keep equal secondary-index keys ordered\n            // by rowid. That implicit suffix can satisfy one extra ORDER BY term.\n            if col_idx < order_target.len() && idx_pos == index.columns.len() && index.has_rowid {\n                let target_col = &order_target[col_idx];\n                let rowid_matches = match target_col.target {\n                    ColumnTarget::RowId => true,\n                    ColumnTarget::Column(col_no) => {\n                        rowid_alias_col.is_some_and(|alias| alias == col_no)\n                    }\n                    ColumnTarget::Expr(_) => false,\n                };\n                let correct_order = if iter_dir == IterationDirection::Forwards {\n                    target_col.order == SortOrder::Asc\n                } else {\n                    target_col.order == SortOrder::Desc\n                };\n                if target_col.table_id == table_ref.internal_id && rowid_matches && correct_order {\n                    col_idx += 1;\n                }\n            }\n\n            col_idx\n        }\n    }\n}\n"
  },
  {
    "path": "core/translate/optimizer/unnest.rs",
    "content": "//! Unnesting pass: rewrites EXISTS/NOT EXISTS correlated subqueries into semi/anti-joins.\n//!\n//! A correlated EXISTS subquery:\n//!   SELECT * FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE t2.a = t1.a)\n//! is rewritten into a semi-join:\n//!   SELECT * FROM t1 SEMI JOIN t2 ON t2.a = t1.a\n//!\n//! Similarly, NOT EXISTS becomes an anti-join.\n//!\n//! Base intuition for correctness:\n//! - `EXISTS(subquery)` is a yes/no test: did we find at least one inner row?\n//! - A semi-join is the same yes/no test, just run as a join loop:\n//!   keep the outer row once a matching inner row is found.\n//! - For correlated equality predicates (for example `inner.k = outer.k`), the\n//!   answer depends only on the key value `k`, not on outer row identity. If two\n//!   outer rows have the same `k`, they both either match or do not match.\n//!   That is why precomputing/joining by `k` preserves `EXISTS` truth values.\n//! - `NOT EXISTS(subquery)` is the opposite yes/no test.\n//! - An anti-join does exactly that:\n//!   keep the outer row only if no matching inner row is found.\n//! - The same key-value argument applies to anti-join: for a given `k`, either\n//!   every outer row with `k` survives (no inner match) or none survive.\n//!\n//! So the rewrite is semantics-preserving when we keep the same notion of\n//! \"matching row\" and do not move predicates across boundaries that change\n//! row existence (for example OUTER JOIN null-extension or inner-independent\n//! gates under `NOT EXISTS`, e.g. `NOT EXISTS (... WHERE corr AND 0)` is TRUE for every outer row).\n//!\n//! Canonical references used for blocker rationale in this module:\n//! - [SQLITE-EXISTS] https://sqlite.org/lang_expr.html#the_exists_operator\n//! - [PG-SUBQUERY] https://www.postgresql.org/docs/current/functions-subquery.html\n//! - [PG-JOIN-ORDER] https://www.postgresql.org/docs/current/queries-table-expressions.html\n//! - [MYSQL-SEMIJOIN] https://dev.mysql.com/doc/refman/8.4/en/semijoins-antijoins.html\n\nuse smallvec::SmallVec;\nuse turso_parser::ast::{self, Expr, TableInternalId, UnaryOperator};\n\nuse crate::function::{Deterministic, Func};\nuse crate::translate::{\n    expr::{walk_expr, WalkControl},\n    plan::{JoinInfo, JoinType, SelectPlan, SubqueryState, WhereTerm},\n};\nuse crate::Result;\n\n/// Attempt to unnest EXISTS/NOT EXISTS correlated subqueries into semi/anti-joins.\n/// This is called during the optimizer pipeline, after constant condition elimination\n/// and before table access optimization.\npub fn unnest_exists_subqueries(plan: &mut SelectPlan) -> Result<()> {\n    let mut i = 0;\n    while i < plan.non_from_clause_subqueries.len() {\n        let subquery = &plan.non_from_clause_subqueries[i];\n        // Only consider unevaluated, correlated EXISTS subqueries.\n        if !subquery.correlated {\n            i += 1;\n            continue;\n        }\n        let is_exists = matches!(subquery.query_type, ast::SubqueryType::Exists { .. });\n        if !is_exists {\n            i += 1;\n            continue;\n        }\n        if try_unnest_exists(plan, i) {\n            // Subquery was removed from the vec; don't increment i.\n            continue;\n        }\n        i += 1;\n    }\n    Ok(())\n}\n\n/// Try to unnest a single EXISTS subquery at index `subquery_idx`.\n/// Returns true if the subquery was successfully unnested and removed.\nfn try_unnest_exists(plan: &mut SelectPlan, subquery_idx: usize) -> bool {\n    // 1. Extract the inner plan (if available).\n    let inner_plan = {\n        let subquery = &plan.non_from_clause_subqueries[subquery_idx];\n        let SubqueryState::Unevaluated { plan: inner } = &subquery.state else {\n            return false;\n        };\n        let Some(inner) = inner.as_ref() else {\n            return false;\n        };\n        inner.clone()\n    };\n    let subquery_id = plan.non_from_clause_subqueries[subquery_idx].internal_id;\n\n    // 2. Check blockers: the inner plan must be simple enough to unnest.\n    if !can_unnest_inner_plan(&inner_plan) {\n        return false;\n    }\n\n    // 3. Determine if this is EXISTS or NOT EXISTS by scanning the outer WHERE clause.\n    let Some(where_info) = find_exists_in_where(&plan.where_clause, subquery_id) else {\n        return false;\n    };\n\n    let join_type = if where_info.negated {\n        JoinType::Anti\n    } else {\n        JoinType::Semi\n    };\n\n    // 4. Extract correlation predicates from the inner WHERE clause.\n    // These are predicates of the form `inner_col = outer_col` where one side\n    // references an outer query ref and the other side references an inner table.\n    let outer_table_ids: Vec<TableInternalId> = inner_plan\n        .table_references\n        .outer_query_refs()\n        .iter()\n        .map(|r| r.internal_id)\n        .collect();\n    let inner_table_ids: Vec<TableInternalId> = inner_plan\n        .table_references\n        .joined_tables()\n        .iter()\n        .map(|t| t.internal_id)\n        .collect();\n\n    // All inner WHERE terms must be expressible as join predicates or filters\n    // on inner tables only. If any term references outer tables in a non-equality\n    // context, we bail out.\n    for term in &inner_plan.where_clause {\n        if !is_valid_unnesting_predicate(&term.expr, &outer_table_ids, &inner_table_ids) {\n            return false;\n        }\n    }\n\n    // For anti-join rewrites, every inner WHERE term must reference an inner table.\n    // Principle ([SQLITE-EXISTS], [PG-SUBQUERY]): NOT EXISTS depends on inner-row\n    // emptiness, so inner-independent gates must stay under the quantifier.\n    // Example: `NOT EXISTS (... WHERE corr AND 0)` is TRUE for every outer row.\n    // Hoisting `0` to outer WHERE would reject all rows, so this rewrite is unsafe.\n    if join_type == JoinType::Anti {\n        for term in &inner_plan.where_clause {\n            let refs = collect_table_refs(&term.expr);\n            if !refs.iter().any(|t| inner_table_ids.contains(t)) {\n                return false;\n            }\n        }\n    }\n\n    // 4b. Block unnesting if any correlation predicate references a table that\n    // is on the nullable side of a LEFT/FULL OUTER JOIN in the outer plan.\n    // Principle ([PG-JOIN-ORDER]): OUTER JOIN null-extension is defined before\n    // WHERE filtering; moving such predicates across the boundary is not safe.\n    // Example: correlating to nullable RHS columns can drop rows that should\n    // survive as NULL-extended rows.\n    let mut nullable_outer_table_ids: Vec<TableInternalId> = Vec::new();\n    let joined = plan.table_references.joined_tables();\n    for (i, t) in joined.iter().enumerate() {\n        if let Some(ji) = &t.join_info {\n            if ji.is_outer() || ji.is_full_outer() {\n                // Right-side table of LEFT/FULL OUTER JOIN is nullable.\n                nullable_outer_table_ids.push(t.internal_id);\n            }\n            if ji.is_full_outer() && i > 0 {\n                // Left-side table of FULL OUTER JOIN is also nullable.\n                nullable_outer_table_ids.push(joined[i - 1].internal_id);\n            }\n        }\n    }\n    if !nullable_outer_table_ids.is_empty() {\n        // Check if any correlation predicate touches a nullable outer table.\n        for term in &inner_plan.where_clause {\n            let refs = collect_table_refs(&term.expr);\n            if refs.iter().any(|t| nullable_outer_table_ids.contains(t)) {\n                return false;\n            }\n        }\n    }\n\n    // 5. Perform the rewrite.\n    // Move inner tables into the outer plan as semi/anti-joined tables.\n    let mut inner_plan = inner_plan;\n    let inner_tables = std::mem::take(inner_plan.table_references.joined_tables_mut());\n    for (idx, mut table) in inner_tables.into_iter().enumerate() {\n        if idx == 0 {\n            // First inner table gets the semi/anti-join annotation.\n            table.join_info = Some(JoinInfo {\n                join_type,\n                using: vec![],\n                no_reorder: false,\n            });\n        }\n        plan.table_references.add_joined_table(table);\n    }\n\n    // Move inner WHERE terms to the outer plan's WHERE clause.\n    // The outer_query_ref column references in these terms already point to the\n    // correct table IDs (they were set up during subquery planning), so they\n    // work correctly in the outer scope.\n    // Reset `consumed` since the inner optimizer may have marked terms consumed\n    // during its own optimization pass; in the outer plan they need re-evaluation.\n    for mut term in inner_plan.where_clause {\n        term.consumed = false;\n        plan.where_clause.push(term);\n    }\n\n    // Move any inner non-FROM subqueries to the outer plan.\n    for inner_subquery in inner_plan.non_from_clause_subqueries {\n        plan.non_from_clause_subqueries.push(inner_subquery);\n    }\n\n    // Replace the EXISTS/NOT EXISTS expression in the outer WHERE with a no-op (true).\n    // The semi/anti-join handles the filtering.\n    replace_exists_with_true(&mut plan.where_clause, where_info.where_term_idx);\n\n    // Remove the subquery from the outer plan's subquery list.\n    // Note: subquery_idx may have shifted if we inserted inner subqueries above,\n    // but we inserted at the END, so the original index is still valid.\n    plan.non_from_clause_subqueries.remove(subquery_idx);\n\n    true\n}\n\n/// Check if the inner plan is simple enough to unnest.\nfn can_unnest_inner_plan(plan: &SelectPlan) -> bool {\n    // Blocker ([MYSQL-SEMIJOIN]): only rewrite simple single-source subqueries.\n    // Principle: current VM early-out is loop-local; multi-table inners would\n    // need additional state to preserve existential semantics.\n    // Example: EXISTS over `i1 JOIN i2` can match only at deeper loop levels.\n    if plan.table_references.joined_tables().len() != 1 {\n        return false;\n    }\n    // Blocker ([PG-SUBQUERY], [SQLITE-EXISTS]): LIMIT can change emptiness.\n    // Example: `EXISTS(... LIMIT 0)` is always FALSE.\n    if plan.limit.is_some() {\n        return false;\n    }\n    // Blocker ([MYSQL-SEMIJOIN]): grouped subqueries require grouped rewrite.\n    // Example: GROUP BY/HAVING subquery is not equivalent to row-level semi-join.\n    if plan.group_by.is_some() {\n        return false;\n    }\n    // Blocker ([MYSQL-SEMIJOIN]): ORDER BY on a plain EXISTS is semantically\n    // irrelevant, but in practice appears with other complex constructs we\n    // don't decorrelate here (keep this pass intentionally conservative).\n    if !plan.order_by.is_empty() {\n        return false;\n    }\n    // Blocker ([MYSQL-SEMIJOIN]): DISTINCT + existential checks may require\n    // duplicate-elimination-aware planning.\n    // Example: DISTINCT in inner subquery should not change outer cardinality.\n    if !matches!(\n        plan.distinctness,\n        crate::translate::plan::Distinctness::NonDistinct\n    ) {\n        return false;\n    }\n    // Blocker ([MYSQL-SEMIJOIN]): window frames are not row-local filters.\n    // Example: window function values depend on partition context.\n    if plan.window.is_some() {\n        return false;\n    }\n    // Blocker ([PG-SUBQUERY]): OFFSET changes emptiness independently of joins.\n    // Example: `EXISTS(... OFFSET 1000)` may become FALSE even with matches.\n    if plan.offset.is_some() {\n        return false;\n    }\n    // Blocker ([MYSQL-SEMIJOIN]): VALUES-based inners are not handled by this\n    // table-based rewrite path.\n    if !plan.values.is_empty() {\n        return false;\n    }\n    // Blocker ([PG-SUBQUERY]): aggregate subqueries can produce a row even when\n    // no base rows match, which breaks existential rewrite assumptions.\n    // Example: `EXISTS(SELECT count(*) FROM i WHERE false)` is TRUE.\n    if !plan.aggregates.is_empty() {\n        return false;\n    }\n    // Blocker ([MYSQL-SEMIJOIN]): nested correlated subqueries need layered\n    // decorrelation ordering not implemented in this pass.\n    // Example: inner WHERE contains `EXISTS (SELECT ... correlated to inner)`.\n    if plan.non_from_clause_subqueries.iter().any(|s| s.correlated) {\n        return false;\n    }\n    // Blocker ([PG-SUBQUERY]): side-effecting/volatile expressions may be\n    // evaluated a different number of times after rewrite.\n    // Example: `random()` under EXISTS should keep original evaluation behavior.\n    for term in &plan.where_clause {\n        if contains_nondeterministic_function(&term.expr) {\n            return false;\n        }\n    }\n    true\n}\n\n/// Information about where an EXISTS/NOT EXISTS expression appears in the WHERE clause.\nstruct ExistsWhereInfo {\n    /// Index into the WHERE clause vector.\n    where_term_idx: usize,\n    /// Whether the EXISTS is negated (NOT EXISTS).\n    negated: bool,\n}\n\n/// Find the WHERE term that references the EXISTS subquery with the given ID.\n/// Returns None if the subquery is referenced in a context we can't unnest\n/// (e.g., inside OR, or referenced multiple times).\nfn find_exists_in_where(\n    where_clause: &[WhereTerm],\n    subquery_id: TableInternalId,\n) -> Option<ExistsWhereInfo> {\n    for (idx, term) in where_clause.iter().enumerate() {\n        // Blocker ([PG-JOIN-ORDER]): OUTER JOIN ON terms cannot be rewritten as\n        // normal WHERE terms without changing null-extension behavior.\n        // Example: `LEFT JOIN ... ON EXISTS(...)` must still emit unmatched rows.\n        if term.from_outer_join.is_some() {\n            continue;\n        }\n        // Check for direct EXISTS reference: SubqueryResult { Exists }\n        if let Expr::SubqueryResult {\n            subquery_id: sid,\n            query_type: ast::SubqueryType::Exists { .. },\n            ..\n        } = &term.expr\n        {\n            if *sid == subquery_id {\n                return Some(ExistsWhereInfo {\n                    where_term_idx: idx,\n                    negated: false,\n                });\n            }\n        }\n        // Check for NOT EXISTS: Unary(Not, SubqueryResult { Exists })\n        if let Expr::Unary(UnaryOperator::Not, inner) = &term.expr {\n            if let Expr::SubqueryResult {\n                subquery_id: sid,\n                query_type: ast::SubqueryType::Exists { .. },\n                ..\n            } = inner.as_ref()\n            {\n                if *sid == subquery_id {\n                    return Some(ExistsWhereInfo {\n                        where_term_idx: idx,\n                        negated: true,\n                    });\n                }\n            }\n        }\n    }\n    None\n}\n\n/// Check if a predicate expression is valid for unnesting.\n/// Valid predicates are:\n/// - Pure inner-table predicates (no outer refs)\n/// - Equality predicates between outer and inner columns (correlation predicates)\n/// - Any expression that doesn't reference outer tables in non-equality positions\nfn is_valid_unnesting_predicate(\n    expr: &Expr,\n    outer_table_ids: &[TableInternalId],\n    inner_table_ids: &[TableInternalId],\n) -> bool {\n    // Check if the expression references any outer tables.\n    let mut has_outer_ref = false;\n    let _ = walk_expr(expr, &mut |e: &Expr| -> Result<WalkControl> {\n        if let Expr::Column { table, .. } = e {\n            if outer_table_ids.contains(table) {\n                has_outer_ref = true;\n            }\n        }\n        Ok(WalkControl::Continue)\n    });\n\n    if !has_outer_ref {\n        // Pure inner predicate: always valid.\n        return true;\n    }\n\n    // For predicates with outer refs, we only support simple equality:\n    // inner_col = outer_col or outer_col = inner_col\n    is_correlation_equality(expr, outer_table_ids, inner_table_ids)\n}\n\n/// Check if an expression is a simple equality between an outer and inner column reference.\nfn is_correlation_equality(\n    expr: &Expr,\n    outer_table_ids: &[TableInternalId],\n    inner_table_ids: &[TableInternalId],\n) -> bool {\n    if let Expr::Binary(lhs, ast::Operator::Equals, rhs) = expr {\n        let lhs_tables = collect_table_refs(lhs);\n        let rhs_tables = collect_table_refs(rhs);\n\n        // One side references only outer tables, the other only inner tables.\n        let lhs_is_outer =\n            lhs_tables.iter().all(|t| outer_table_ids.contains(t)) && !lhs_tables.is_empty();\n        let lhs_is_inner =\n            lhs_tables.iter().all(|t| inner_table_ids.contains(t)) && !lhs_tables.is_empty();\n        let rhs_is_outer =\n            rhs_tables.iter().all(|t| outer_table_ids.contains(t)) && !rhs_tables.is_empty();\n        let rhs_is_inner =\n            rhs_tables.iter().all(|t| inner_table_ids.contains(t)) && !rhs_tables.is_empty();\n\n        (lhs_is_outer && rhs_is_inner) || (lhs_is_inner && rhs_is_outer)\n    } else {\n        false\n    }\n}\n\n/// Collect all table IDs referenced by column expressions in an expression tree.\nfn collect_table_refs(expr: &Expr) -> SmallVec<[TableInternalId; 2]> {\n    let mut refs = SmallVec::new();\n    let _ = walk_expr(expr, &mut |e: &Expr| -> Result<WalkControl> {\n        if let Expr::Column { table, .. } = e {\n            if !refs.contains(table) {\n                refs.push(*table);\n            }\n        }\n        Ok(WalkControl::Continue)\n    });\n    refs\n}\n\n/// Check if an expression tree contains any non-deterministic function calls\n/// (e.g. random(), changes(), last_insert_rowid()).\nfn contains_nondeterministic_function(expr: &Expr) -> bool {\n    let mut found = false;\n    let _ = walk_expr(expr, &mut |e: &Expr| -> Result<WalkControl> {\n        match e {\n            Expr::FunctionCall { name, args, .. } => {\n                if let Ok(func) = Func::resolve_function(name.as_str(), args.len()) {\n                    if !func.is_deterministic() {\n                        found = true;\n                    }\n                }\n            }\n            Expr::FunctionCallStar { name, .. } => {\n                // Star functions like count(*) — resolve with 0 args\n                if let Ok(func) = Func::resolve_function(name.as_str(), 0) {\n                    if !func.is_deterministic() {\n                        found = true;\n                    }\n                }\n            }\n            _ => {}\n        }\n        Ok(WalkControl::Continue)\n    });\n    found\n}\n\n/// Replace the WHERE term at the given index with a trivially-true expression.\nfn replace_exists_with_true(where_clause: &mut [WhereTerm], idx: usize) {\n    where_clause[idx].expr = Expr::Literal(ast::Literal::Numeric(\"1\".to_string()));\n}\n"
  },
  {
    "path": "core/translate/order_by.rs",
    "content": "use crate::sync::Arc;\n\nuse turso_parser::ast::{self, SortOrder};\n\nuse crate::{\n    emit_explain,\n    schema::{Index, IndexColumn, PseudoCursorType, Schema},\n    translate::{\n        collate::{get_collseq_from_expr, CollationSeq},\n        group_by::is_orderby_agg_or_const,\n        plan::Aggregate,\n    },\n    util::exprs_are_equivalent,\n    vdbe::{\n        builder::{CursorType, ProgramBuilder},\n        insn::{to_u16, IdxInsertFlags, Insn},\n    },\n    Result,\n};\n\nuse super::{\n    emitter::TranslateCtx,\n    expr::translate_expr,\n    plan::{Distinctness, ResultSetColumn, SelectPlan, TableReferences},\n    result_row::{emit_offset, emit_result_row_and_limit},\n};\n\nuse crate::vdbe::insn::SortComparatorType;\n\n/// Maps a custom type `<` operator function name to a SortComparatorType.\n/// Returns None if the function name is not recognized.\nfn sort_comparator_from_func_name(func_name: &str) -> Option<SortComparatorType> {\n    match func_name {\n        \"numeric_lt\" => Some(SortComparatorType::NumericLt),\n        \"test_uint_lt\" => Some(SortComparatorType::TestUintLt),\n        \"string_reverse\" => Some(SortComparatorType::StringReverse),\n        \"array_lt\" => Some(SortComparatorType::ArrayLt),\n        _ => None,\n    }\n}\n\n/// For an ORDER BY expression that is a column reference to a custom type,\n/// returns the SortComparatorType if the type has a `<` operator with a known\n/// comparator. Returns None otherwise, which causes the sorter to use encoded\n/// blob ordering instead of silently wrong results.\npub(crate) fn custom_type_comparator(\n    expr: &ast::Expr,\n    referenced_tables: &TableReferences,\n    schema: &Schema,\n) -> Option<SortComparatorType> {\n    if let ast::Expr::Column {\n        table: table_ref_id,\n        column,\n        ..\n    } = expr\n    {\n        let (_, table) = referenced_tables.find_table_by_internal_id(*table_ref_id)?;\n        let col = table.get_column_at(*column)?;\n        // Array columns use element-wise comparison\n        if col.is_array() {\n            return Some(SortComparatorType::ArrayLt);\n        }\n        let type_def = schema.get_type_def(&col.ty_str, table.is_strict())?;\n        type_def\n            .operators\n            .iter()\n            .find(|op| op.op == \"<\")\n            .and_then(|op| op.func_name.as_ref())\n            .and_then(|func_name| sort_comparator_from_func_name(func_name))\n    } else if super::expr::expr_is_array(expr, Some(referenced_tables)) {\n        Some(SortComparatorType::ArrayLt)\n    } else {\n        None\n    }\n}\n\n/// For a result column expression that is a column reference to a custom type,\n/// returns the column definition and type definition.\nfn result_column_custom_type_info<'a>(\n    expr: &ast::Expr,\n    referenced_tables: &'a TableReferences,\n    schema: &'a Schema,\n) -> Option<(\n    &'a crate::schema::Column,\n    std::sync::Arc<crate::schema::TypeDef>,\n)> {\n    if let ast::Expr::Column {\n        table: table_ref_id,\n        column,\n        ..\n    } = expr\n    {\n        let (_, table) = referenced_tables.find_table_by_internal_id(*table_ref_id)?;\n        let col = table.get_column_at(*column)?;\n        let type_def = schema.get_type_def(&col.ty_str, table.is_strict())?.clone();\n        Some((col, type_def))\n    } else {\n        None\n    }\n}\n\n/// Returns true if the expression is a column reference to a custom type\n/// (with encode/decode) that does NOT have a `<` operator with a known\n/// sort comparator. This includes types with no `<` operator at all, and\n/// types whose `<` function is not recognized by the sorter.\nfn is_custom_type_without_lt(\n    expr: &ast::Expr,\n    referenced_tables: &TableReferences,\n    schema: &Schema,\n) -> bool {\n    if let ast::Expr::Column {\n        table: table_ref_id,\n        column,\n        ..\n    } = expr\n    {\n        if let Some((_, table)) = referenced_tables.find_table_by_internal_id(*table_ref_id) {\n            if let Some(col) = table.get_column_at(*column) {\n                if let Some(type_def) = schema.get_type_def(&col.ty_str, table.is_strict()) {\n                    if type_def.decode.is_some() {\n                        // No `<` operator at all (naked or with function)\n                        return !type_def.operators.iter().any(|op| op.op == \"<\");\n                    }\n                }\n            }\n        }\n    }\n    false\n}\n\n// Metadata for handling ORDER BY operations\n#[derive(Debug)]\npub struct SortMetadata {\n    // cursor id for the Sorter table where the sorted rows are stored\n    pub sort_cursor: usize,\n    // register where the sorter data is inserted and later retrieved from\n    pub reg_sorter_data: usize,\n    // We need to emit result columns in the order they are present in the SELECT, but they may not be in the same order in the ORDER BY sorter.\n    // This vector holds the indexes of the result columns in the ORDER BY sorter.\n    // This vector must be the same length as the result columns.\n    pub remappings: Vec<OrderByRemapping>,\n    /// Whether we append an extra ascending \"Sequence\" key to the ORDER BY sort keys.\n    /// This is used *only* when a GROUP BY is present *and* ORDER BY is not purely\n    /// aggregates/constants, so that rows that tie on ORDER BY terms are output in\n    /// the same relative order the underlying row stream produced them.\n    pub has_sequence: bool,\n    /// Whether to use heap-sort with BTreeIndex instead of full-collection sort through Sorter\n    pub use_heap_sort: bool,\n}\npub struct EmitOrderBy;\n\nimpl EmitOrderBy {\n    /// Initialize resources needed for ORDER BY processing\n    #[allow(clippy::too_many_arguments)]\n    pub fn init(\n        program: &mut ProgramBuilder,\n        t_ctx: &mut TranslateCtx,\n        result_columns: &[ResultSetColumn],\n        order_by: &[(Box<ast::Expr>, SortOrder)],\n        referenced_tables: &TableReferences,\n        has_group_by: bool,\n        has_distinct: bool,\n        aggregates: &[Aggregate],\n    ) -> Result<()> {\n        // Block ORDER BY on custom type columns without OPERATOR '<'\n        for (expr, _) in order_by.iter() {\n            if is_custom_type_without_lt(expr, referenced_tables, t_ctx.resolver.schema()) {\n                if let Some((col, type_def)) =\n                    result_column_custom_type_info(expr, referenced_tables, t_ctx.resolver.schema())\n                {\n                    let col_name = col.name.as_deref().unwrap_or(\"?\");\n                    crate::bail_parse_error!(\n                    \"cannot ORDER BY column '{}' of type '{}': type does not declare OPERATOR '<'\",\n                    col_name,\n                    type_def.name\n                );\n                }\n                crate::bail_parse_error!(\n                    \"cannot ORDER BY a custom type column that does not declare OPERATOR '<'\"\n                );\n            }\n        }\n\n        let only_aggs = order_by\n            .iter()\n            .all(|(e, _)| is_orderby_agg_or_const(&t_ctx.resolver, e, aggregates));\n\n        let use_heap_sort = !has_distinct && !has_group_by && t_ctx.limit_ctx.is_some();\n\n        // only emit sequence column if (we have GROUP BY and ORDER BY is not only aggregates or constants) OR (we decided to use heap-sort)\n        let has_sequence = (has_group_by && !only_aggs) || use_heap_sort;\n\n        let remappings =\n            order_by_deduplicate_result_columns(order_by, result_columns, has_sequence);\n        let sort_cursor = if use_heap_sort {\n            let index_name = format!(\"heap_sort_{}\", program.offset().as_offset_int()); // we don't really care about the name that much, just enough that we don't get name collisions\n            let mut index_columns = Vec::with_capacity(order_by.len() + result_columns.len());\n            for (column, order) in order_by {\n                let collation = get_collseq_from_expr(column, referenced_tables)?;\n                let pos_in_table = index_columns.len();\n                index_columns.push(IndexColumn {\n                    name: pos_in_table.to_string(),\n                    order: *order,\n                    pos_in_table,\n                    collation,\n                    default: None,\n                    expr: None,\n                })\n            }\n            let pos_in_table = index_columns.len();\n            // add sequence number between ORDER BY columns and result column\n            index_columns.push(IndexColumn {\n                name: pos_in_table.to_string(),\n                order: SortOrder::Asc,\n                pos_in_table,\n                collation: None,\n                default: None,\n                expr: None,\n            });\n            for _ in remappings.iter().filter(|r| !r.deduplicated) {\n                let pos_in_table = index_columns.len();\n                index_columns.push(IndexColumn {\n                    name: pos_in_table.to_string(),\n                    order: SortOrder::Asc,\n                    pos_in_table,\n                    collation: None,\n                    default: None,\n                    expr: None,\n                })\n            }\n            let index = Arc::new(Index {\n                name: index_name,\n                table_name: String::new(),\n                ephemeral: true,\n                root_page: 0,\n                columns: index_columns,\n                unique: false,\n                has_rowid: false,\n                where_clause: None,\n                index_method: None,\n                on_conflict: None,\n            });\n            program.alloc_cursor_id(CursorType::BTreeIndex(index))\n        } else {\n            program.alloc_cursor_id(CursorType::Sorter)\n        };\n        t_ctx.meta_sort = Some(SortMetadata {\n            sort_cursor,\n            reg_sorter_data: program.alloc_register(),\n            remappings,\n            has_sequence,\n            use_heap_sort,\n        });\n\n        if use_heap_sort {\n            program.emit_insn(Insn::OpenEphemeral {\n                cursor_id: sort_cursor,\n                is_table: false,\n            });\n        } else {\n            /*\n             * Terms of the ORDER BY clause that is part of a SELECT statement may be assigned a collating sequence using the COLLATE operator,\n             * in which case the specified collating function is used for sorting.\n             * Otherwise, if the expression sorted by an ORDER BY clause is a column,\n             * then the collating sequence of the column is used to determine sort order.\n             * If the expression is not a column and has no COLLATE clause, then the BINARY collating sequence is used.\n             */\n            let mut order_and_collations: Vec<(SortOrder, Option<CollationSeq>)> = order_by\n                .iter()\n                .map(|(expr, dir)| {\n                    let collation = get_collseq_from_expr(expr, referenced_tables)?;\n                    Ok((*dir, collation))\n                })\n                .collect::<Result<Vec<_>>>()?;\n\n            // Resolve custom type comparators for ORDER BY columns.\n            // For types with a `<` operator, the comparator is used for correct sort ordering.\n            let mut comparators: Vec<Option<SortComparatorType>> = order_by\n                .iter()\n                .map(|(expr, _)| {\n                    custom_type_comparator(expr, referenced_tables, t_ctx.resolver.schema())\n                })\n                .collect();\n\n            if has_sequence {\n                // sequence column: ascending with BINARY collation, no comparator\n                order_and_collations.push((SortOrder::Asc, Some(CollationSeq::default())));\n                comparators.push(None);\n            }\n\n            let key_len = order_and_collations.len();\n\n            program.emit_insn(Insn::SorterOpen {\n                cursor_id: sort_cursor,\n                columns: key_len,\n                order_and_collations,\n                comparators,\n            });\n        }\n        Ok(())\n    }\n\n    /// Emits the bytecode for outputting rows from an ORDER BY sorter.\n    /// This is called when the main query execution loop has finished processing,\n    /// and we can now emit rows from the ORDER BY sorter.\n    pub fn emit(\n        program: &mut ProgramBuilder,\n        t_ctx: &mut TranslateCtx,\n        plan: &SelectPlan,\n    ) -> Result<()> {\n        let order_by = &plan.order_by;\n        let result_columns = &plan.result_columns;\n        let sort_loop_start_label = program.allocate_label();\n        let sort_loop_next_label = program.allocate_label();\n        let sort_loop_end_label = program.allocate_label();\n        let SortMetadata {\n            sort_cursor,\n            reg_sorter_data,\n            ref remappings,\n            has_sequence,\n            use_heap_sort,\n        } = *t_ctx.meta_sort.as_ref().unwrap();\n\n        let sorter_column_count = order_by.len()\n            + if has_sequence { 1 } else { 0 }\n            + remappings.iter().filter(|r| !r.deduplicated).count();\n\n        if use_heap_sort {\n            emit_explain!(program, false, \"USE TEMP B-TREE FOR ORDER BY\".to_owned());\n        } else {\n            emit_explain!(program, false, \"USE SORTER FOR ORDER BY\".to_owned());\n        }\n\n        let cursor_id = if !use_heap_sort {\n            let pseudo_cursor = program.alloc_cursor_id(CursorType::Pseudo(PseudoCursorType {\n                column_count: sorter_column_count,\n            }));\n\n            program.emit_insn(Insn::OpenPseudo {\n                cursor_id: pseudo_cursor,\n                content_reg: reg_sorter_data,\n                num_fields: sorter_column_count,\n            });\n\n            program.emit_insn(Insn::SorterSort {\n                cursor_id: sort_cursor,\n                pc_if_empty: sort_loop_end_label,\n            });\n            pseudo_cursor\n        } else {\n            program.emit_insn(Insn::Rewind {\n                cursor_id: sort_cursor,\n                pc_if_empty: sort_loop_end_label,\n            });\n            sort_cursor\n        };\n\n        program.preassign_label_to_next_insn(sort_loop_start_label);\n\n        emit_offset(program, sort_loop_next_label, t_ctx.reg_offset);\n\n        if !use_heap_sort {\n            program.emit_insn(Insn::SorterData {\n                cursor_id: sort_cursor,\n                dest_reg: reg_sorter_data,\n                pseudo_cursor: cursor_id,\n            });\n        }\n\n        // We emit the columns in SELECT order, not sorter order (sorter always has the sort keys first).\n        // This is tracked in sort_metadata.remappings.\n        let start_reg = t_ctx.reg_result_cols_start.unwrap();\n        for (i, rc) in result_columns.iter().enumerate() {\n            let reg = start_reg + i;\n            let remapping = remappings\n                .get(i)\n                .expect(\"remapping must exist for all result columns\");\n\n            let column_idx = remapping.orderby_sorter_idx;\n            program.emit_column_or_rowid(cursor_id, column_idx, reg);\n\n            // Deduplicated columns share a sort key slot, which stores the encoded\n            // (on-disk) value (decode was suppressed during sorter insert). Apply\n            // DECODE now so the result set contains human-readable values.\n            if remapping.deduplicated {\n                if let Some((col, type_def)) = result_column_custom_type_info(\n                    &rc.expr,\n                    &plan.table_references,\n                    t_ctx.resolver.schema(),\n                ) {\n                    if let Some(ref decode_expr) = type_def.decode {\n                        let skip_label = program.allocate_label();\n                        program.emit_insn(Insn::IsNull {\n                            reg,\n                            target_pc: skip_label,\n                        });\n                        super::expr::emit_type_expr(\n                            program,\n                            decode_expr,\n                            reg,\n                            reg,\n                            col,\n                            &type_def,\n                            &t_ctx.resolver,\n                        )?;\n                        program.resolve_label(skip_label, program.offset());\n                    }\n                }\n            }\n        }\n\n        // Decode array blobs to JSON text for display, after extracting from sorter\n        super::result_row::emit_array_decode_for_results(\n            program,\n            result_columns,\n            &plan.table_references,\n            start_reg,\n            &t_ctx.resolver,\n        )?;\n\n        emit_result_row_and_limit(\n            program,\n            plan,\n            start_reg,\n            t_ctx.limit_ctx,\n            if !use_heap_sort {\n                Some(sort_loop_end_label)\n            } else {\n                None\n            },\n        )?;\n\n        program.resolve_label(sort_loop_next_label, program.offset());\n        if !use_heap_sort {\n            program.emit_insn(Insn::SorterNext {\n                cursor_id: sort_cursor,\n                pc_if_next: sort_loop_start_label,\n            });\n        } else {\n            program.emit_insn(Insn::Next {\n                cursor_id: sort_cursor,\n                pc_if_next: sort_loop_start_label,\n            });\n        }\n        program.preassign_label_to_next_insn(sort_loop_end_label);\n\n        Ok(())\n    }\n\n    /// Emits the bytecode for inserting a row into an ORDER BY sorter.\n    pub fn sorter_insert(\n        program: &mut ProgramBuilder,\n        t_ctx: &TranslateCtx,\n        plan: &SelectPlan,\n    ) -> Result<()> {\n        let resolver = &t_ctx.resolver;\n        let sort_metadata = t_ctx.meta_sort.as_ref().expect(\"sort metadata must exist\");\n        let order_by = &plan.order_by;\n        let order_by_len = order_by.len();\n        let result_columns = &plan.result_columns;\n        let result_columns_to_skip_len = sort_metadata\n            .remappings\n            .iter()\n            .filter(|r| r.deduplicated)\n            .count();\n\n        // The ORDER BY sorter has the sort keys first, then the result columns.\n        let orderby_sorter_column_count =\n            order_by_len + if sort_metadata.has_sequence { 1 } else { 0 } + result_columns.len()\n                - result_columns_to_skip_len;\n\n        let start_reg = program.alloc_registers(orderby_sorter_column_count);\n        for (i, (expr, _)) in order_by.iter().enumerate() {\n            let key_reg = start_reg + i;\n\n            // Check if this ORDER BY expression matches a finalized aggregate\n            if let Some(agg_idx) = plan\n                .aggregates\n                .iter()\n                .position(|agg| exprs_are_equivalent(&agg.original_expr, expr))\n            {\n                // This ORDER BY expression is an aggregate, so copy from register\n                let agg_start_reg = t_ctx\n                    .reg_agg_start\n                    .expect(\"aggregate registers must be initialized\");\n                let src_reg = agg_start_reg + agg_idx;\n                program.emit_insn(Insn::Copy {\n                    src_reg,\n                    dst_reg: key_reg,\n                    extra_amount: 0,\n                });\n            } else {\n                // Sort keys must be encoded (on-disk) values. Suppress decode so the\n                // sorter compares encoded representations, using either the base type's\n                // built-in comparison (naked OPERATOR '<') or a custom comparator function.\n                let is_custom =\n                    result_column_custom_type_info(expr, &plan.table_references, resolver.schema())\n                        .is_some_and(|(_, td)| td.decode.is_some());\n                if is_custom {\n                    program.suppress_custom_type_decode = true;\n                }\n                let result = translate_expr(\n                    program,\n                    Some(&plan.table_references),\n                    expr,\n                    key_reg,\n                    resolver,\n                );\n                if is_custom {\n                    program.suppress_custom_type_decode = false;\n                }\n                result?;\n            }\n        }\n\n        let SortMetadata {\n            sort_cursor,\n            reg_sorter_data,\n            use_heap_sort,\n            ..\n        } = sort_metadata;\n\n        let skip_label = if *use_heap_sort {\n            // skip records which greater than current top-k maintained in a separate BTreeIndex\n            let insert_label = program.allocate_label();\n            let skip_label = program.allocate_label();\n            let limit = t_ctx.limit_ctx.as_ref().expect(\"limit must be set\");\n            let limit_reg = t_ctx.reg_limit_offset_sum.unwrap_or(limit.reg_limit);\n            program.emit_insn(Insn::IfPos {\n                reg: limit_reg,\n                target_pc: insert_label,\n                decrement_by: 1,\n            });\n            program.emit_insn(Insn::Last {\n                cursor_id: *sort_cursor,\n                pc_if_empty: insert_label,\n            });\n            program.emit_insn(Insn::IdxLE {\n                cursor_id: *sort_cursor,\n                start_reg,\n                num_regs: orderby_sorter_column_count,\n                target_pc: skip_label,\n            });\n            program.emit_insn(Insn::Delete {\n                cursor_id: *sort_cursor,\n                table_name: \"\".to_string(),\n                is_part_of_update: false,\n            });\n            program.preassign_label_to_next_insn(insert_label);\n            Some(skip_label)\n        } else {\n            None\n        };\n\n        let mut cur_reg = start_reg + order_by_len;\n        if sort_metadata.has_sequence {\n            program.emit_insn(Insn::Sequence {\n                cursor_id: sort_metadata.sort_cursor,\n                target_reg: cur_reg,\n            });\n            cur_reg += 1;\n        }\n\n        for (i, rc) in result_columns.iter().enumerate() {\n            // If the result column is an exact duplicate of a sort key, we skip it.\n            if sort_metadata\n                .remappings\n                .get(i)\n                .expect(\"remapping must exist for all result columns\")\n                .deduplicated\n            {\n                continue;\n            }\n            translate_expr(\n                program,\n                Some(&plan.table_references),\n                &rc.expr,\n                cur_reg,\n                resolver,\n            )?;\n            cur_reg += 1;\n        }\n\n        // Handle SELECT DISTINCT deduplication\n        if let Distinctness::Distinct { ctx } = &plan.distinctness {\n            let distinct_ctx = ctx.as_ref().expect(\"distinct context must exist\");\n\n            // For distinctness checking with Insn::Found, we need a contiguous run of registers containing all the result columns.\n            // The emitted columns are in the ORDER BY sorter order, which may be different from the SELECT order, and obviously the\n            // ORDER BY clause may not have all the result columns.\n            // Hence, we need to allocate new registers and Copy from the existing ones to make a contiguous run of registers.\n            let mut needs_reordering = false;\n\n            // Check if result columns in sorter are in SELECT order\n            let mut prev = None;\n            for (select_idx, _rc) in result_columns.iter().enumerate() {\n                let sorter_idx = sort_metadata\n                    .remappings\n                    .get(select_idx)\n                    .expect(\"remapping must exist for all result columns\")\n                    .orderby_sorter_idx;\n\n                if prev.is_some_and(|p| sorter_idx != p + 1) {\n                    needs_reordering = true;\n                    break;\n                }\n                prev = Some(sorter_idx);\n            }\n\n            if needs_reordering {\n                // Allocate registers for reordered result columns.\n                // TODO: it may be possible to optimize this to minimize the number of Insn::Copy we do, but for now\n                // we will just allocate a new reg for every result column.\n                let reordered_start_reg = program.alloc_registers(result_columns.len());\n\n                for (select_idx, _rc) in result_columns.iter().enumerate() {\n                    let remapping = sort_metadata\n                        .remappings\n                        .get(select_idx)\n                        .expect(\"remapping must exist for all result columns\");\n\n                    let src_reg = start_reg + remapping.orderby_sorter_idx;\n                    let dst_reg = reordered_start_reg + select_idx;\n\n                    program.emit_insn(Insn::Copy {\n                        src_reg,\n                        dst_reg,\n                        extra_amount: 0,\n                    });\n                }\n\n                distinct_ctx.emit_deduplication_insns(\n                    program,\n                    result_columns.len(),\n                    reordered_start_reg,\n                );\n            } else {\n                // Result columns are already in SELECT order, use them directly\n                let start_reg = sort_metadata\n                    .remappings\n                    .first()\n                    .map(|r| start_reg + r.orderby_sorter_idx)\n                    .expect(\"remapping must exist for all result columns\");\n                distinct_ctx.emit_deduplication_insns(program, result_columns.len(), start_reg);\n            }\n        }\n\n        if *use_heap_sort {\n            program.emit_insn(Insn::MakeRecord {\n                start_reg: to_u16(start_reg),\n                count: to_u16(orderby_sorter_column_count),\n                dest_reg: to_u16(*reg_sorter_data),\n                index_name: None,\n                affinity_str: None,\n            });\n            program.emit_insn(Insn::IdxInsert {\n                cursor_id: *sort_cursor,\n                record_reg: *reg_sorter_data,\n                unpacked_start: None,\n                unpacked_count: None,\n                flags: IdxInsertFlags::new(),\n            });\n            program.preassign_label_to_next_insn(skip_label.unwrap());\n        } else {\n            sorter_insert(\n                program,\n                start_reg,\n                orderby_sorter_column_count,\n                *sort_cursor,\n                *reg_sorter_data,\n            );\n        }\n        Ok(())\n    }\n}\n\n/// Emits the bytecode for inserting a row into a sorter.\n/// This can be either a GROUP BY sorter or an ORDER BY sorter.\npub fn sorter_insert(\n    program: &mut ProgramBuilder,\n    start_reg: usize,\n    column_count: usize,\n    cursor_id: usize,\n    record_reg: usize,\n) {\n    program.emit_insn(Insn::MakeRecord {\n        start_reg: to_u16(start_reg),\n        count: to_u16(column_count),\n        dest_reg: to_u16(record_reg),\n        index_name: None,\n        affinity_str: None,\n    });\n    program.emit_insn(Insn::SorterInsert {\n        cursor_id,\n        record_reg,\n    });\n}\n\n#[derive(Debug)]\n/// A mapping between a result column and its index in the ORDER BY sorter.\n/// ORDER BY columns are emitted first, then the result columns.\n/// If a result column is an exact duplicate of a sort key, we skip it.\n/// If we skip a result column, we need to keep track which ORDER BY column it matches.\npub struct OrderByRemapping {\n    pub orderby_sorter_idx: usize,\n    pub deduplicated: bool,\n}\n\n/// In case any of the ORDER BY sort keys are exactly equal to a result column, we can skip emitting that result column.\n/// If we skip a result column, we need to keep track what index in the ORDER BY sorter the result columns have,\n/// because the result columns should be emitted in the SELECT clause order, not the ORDER BY clause order.\npub fn order_by_deduplicate_result_columns(\n    order_by: &[(Box<ast::Expr>, SortOrder)],\n    result_columns: &[ResultSetColumn],\n    has_sequence: bool,\n) -> Vec<OrderByRemapping> {\n    let mut result_column_remapping: Vec<OrderByRemapping> = Vec::new();\n    let order_by_len = order_by.len();\n    // `sequence_offset` shifts the base index where non-deduped SELECT columns begin,\n    // because Sequence sits after ORDER BY keys but before result columns.\n    let sequence_offset = if has_sequence { 1 } else { 0 };\n\n    let mut i = 0;\n    for rc in result_columns.iter() {\n        let found = order_by\n            .iter()\n            .enumerate()\n            .find(|(_, (expr, _))| exprs_are_equivalent(expr, &rc.expr));\n        if let Some((j, _)) = found {\n            result_column_remapping.push(OrderByRemapping {\n                orderby_sorter_idx: j,\n                deduplicated: true,\n            });\n        } else {\n            // This result column is not a duplicate of any ORDER BY key, so its sorter\n            // index comes after all ORDER BY entries (hence the +order_by_len). The\n            // counter `i` tracks how many such non-duplicate result columns we've seen.\n            result_column_remapping.push(OrderByRemapping {\n                orderby_sorter_idx: order_by_len + sequence_offset + i,\n                deduplicated: false,\n            });\n            i += 1;\n        }\n    }\n\n    result_column_remapping\n}\n"
  },
  {
    "path": "core/translate/plan.rs",
    "content": "use rustc_hash::FxHashMap as HashMap;\nuse smallvec::SmallVec;\nuse std::{cmp::Ordering, marker::PhantomData, sync::Arc};\nuse turso_parser::ast::{\n    self, FrameBound, FrameClause, FrameExclude, FrameMode, ResolveType, SortOrder, SubqueryType,\n};\n\nuse crate::{\n    function::{AggFunc, WindowFunc},\n    schema::{BTreeTable, ColDef, Column, FromClauseSubquery, Index, Schema, Table},\n    translate::{\n        collate::{get_collseq_from_expr, CollationSeq},\n        emitter::UpdateRowSource,\n        expr::{as_binary_components, get_expr_affinity},\n        expression_index::{normalize_expr_for_index_matching, single_table_column_usage},\n        optimizer::constraints::{BinaryExprSide, SeekRangeConstraint},\n        planner::determine_where_to_eval_term,\n    },\n    vdbe::{\n        affinity::{self, Affinity},\n        builder::{CursorKey, CursorType, ProgramBuilder},\n        insn::{HashDistinctData, Insn},\n        BranchOffset, CursorID,\n    },\n    Result, VirtualTable,\n};\nuse crate::{schema::Type, types::SeekOp};\n\nuse turso_parser::ast::TableInternalId;\n\nuse super::emitter::OperationMode;\n\n/// Infer the Type and type name from an expression's affinity.\n///\n/// Used for subquery result columns. SQLite derives column affinity from:\n/// - Column references: the declared column type\n/// - CAST expressions: the cast target type\n/// - Subqueries: recursively from the subquery's result expression\n/// - Literals: BLOB affinity (no affinity)\n///\n/// The affinity determines comparison behavior in IN expressions, etc.\nfn infer_type_from_expr(\n    expr: &ast::Expr,\n    tables: Option<&TableReferences>,\n) -> (Type, &'static str) {\n    let affinity = get_expr_affinity(expr, tables, None);\n    match affinity {\n        Affinity::Integer => (Type::Integer, \"INTEGER\"),\n        Affinity::Real => (Type::Real, \"REAL\"),\n        Affinity::Text => (Type::Text, \"TEXT\"),\n        Affinity::Numeric => (Type::Numeric, \"NUMERIC\"),\n        Affinity::Blob => (Type::Blob, \"BLOB\"),\n    }\n}\n\n#[derive(Debug, Clone)]\npub struct ResultSetColumn {\n    pub expr: ast::Expr,\n    pub alias: Option<String>,\n    // TODO: encode which aggregates (e.g. index bitmask of plan.aggregates) are present in this column\n    pub contains_aggregates: bool,\n}\n\nimpl ResultSetColumn {\n    pub fn name<'a>(&'a self, tables: &'a TableReferences) -> Option<&'a str> {\n        if let Some(alias) = &self.alias {\n            return Some(alias);\n        }\n        match &self.expr {\n            ast::Expr::Column { table, column, .. } => {\n                if let Some(joined_table_ref) = tables.find_joined_table_by_internal_id(*table) {\n                    if let Operation::IndexMethodQuery(module) = &joined_table_ref.op {\n                        if module.covered_columns.contains_key(column) {\n                            return None;\n                        }\n                    }\n                    joined_table_ref\n                        .table\n                        .get_column_at(*column)\n                        .unwrap()\n                        .name\n                        .as_deref()\n                } else {\n                    // Column references an outer query table (correlated subquery).\n                    let (_, table_ref) = tables.find_table_by_internal_id(*table)?;\n                    table_ref.get_column_at(*column)?.name.as_deref()\n                }\n            }\n            ast::Expr::RowId { table, .. } => {\n                // If there is a rowid alias column, use its name\n                let (_, table_ref) = tables.find_table_by_internal_id(*table)?;\n                if let Table::BTree(table) = &table_ref {\n                    if let Some(rowid_alias_column) = table.get_rowid_alias_column() {\n                        if let Some(name) = &rowid_alias_column.1.name {\n                            return Some(name);\n                        }\n                    }\n                }\n\n                // If there is no rowid alias, use \"rowid\".\n                Some(\"rowid\")\n            }\n            _ => None,\n        }\n    }\n}\n\n#[derive(Debug, Clone)]\npub struct GroupBy {\n    pub exprs: Vec<ast::Expr>,\n    /// Sort direction for each GROUP BY key column. Always present once\n    /// `compute_group_by_sort_order` has run; the outer optimizer reads\n    /// this to derive the materialized CTE's output order.\n    pub sort_order: Vec<SortOrder>,\n    /// When true the scan already provides the GROUP BY order and no\n    /// sorter is emitted. The `sort_order` is kept so that the outer\n    /// query can still read the effective output order.\n    pub sort_elided: bool,\n    /// having clause split into a vec at 'AND' boundaries.\n    pub having: Option<Vec<ast::Expr>>,\n}\n\n/// In a query plan, WHERE clause conditions and JOIN conditions are all folded into a vector of WhereTerm.\n/// This is done so that we can evaluate the conditions at the correct loop depth.\n/// We also need to keep track of whether the condition came from an OUTER JOIN. Take this example:\n/// SELECT * FROM users u LEFT JOIN products p ON u.id = 5.\n/// Even though the condition only refers to 'u', we CANNOT evaluate it at the users loop, because we need to emit NULL\n/// values for the columns of 'p', for EVERY row in 'u', instead of completely skipping any rows in 'u' where the condition is false.\n#[derive(Debug, Clone)]\npub struct WhereTerm {\n    /// The original condition expression.\n    pub expr: ast::Expr,\n    /// For normal JOIN conditions (ON or WHERE clauses), we break them up into individual [WhereTerm] conditions\n    /// and let the optimizer determine when each should be evaluated based on the tables they reference.\n    /// See e.g. [EvalAt].\n    /// For example, in \"SELECT * FROM x JOIN y WHERE x.a = 2\", we want to evaluate x.a = 2 right after opening x\n    /// since it only depends on x.\n    ///\n    /// However, OUTER JOIN conditions require special handling. Consider:\n    ///   SELECT * FROM t LEFT JOIN s ON t.a = 2\n    ///\n    /// Even though t.a = 2 only references t, we cannot evaluate it during t's loop and skip rows where t.a != 2.\n    /// Instead, we must:\n    /// 1. Process ALL rows from t\n    /// 2. For each t row where t.a != 2, emit NULL values for s's columns\n    /// 3. For each t row where t.a = 2, emit the actual s values\n    ///\n    /// This means the condition must be evaluated during s's loop, regardless of which tables it references.\n    /// We track this requirement using [WhereTerm::from_outer_join], which contains the [TableInternalId] of the\n    /// right-side table of the OUTER JOIN (in this case, s). When evaluating conditions, if [WhereTerm::from_outer_join]\n    /// is set, we force evaluation to happen during that table's loop.\n    pub from_outer_join: Option<TableInternalId>,\n    /// Whether the condition has been consumed by the optimizer in some way, and it should not be evaluated\n    /// in the normal place where WHERE terms are evaluated.\n    /// A term may have been consumed e.g. if:\n    /// - it has been converted into a constraint in a seek key\n    /// - it has been removed due to being trivially true or false\n    pub consumed: bool,\n}\n\nimpl WhereTerm {\n    pub fn should_eval_before_loop(\n        &self,\n        join_order: &[JoinOrderMember],\n        subqueries: &[NonFromClauseSubquery],\n        table_references: Option<&TableReferences>,\n    ) -> bool {\n        if self.consumed {\n            return false;\n        }\n        let Ok(eval_at) = self.eval_at(join_order, subqueries, table_references) else {\n            return false;\n        };\n        eval_at == EvalAt::BeforeLoop\n    }\n\n    pub fn should_eval_at_loop(\n        &self,\n        loop_idx: usize,\n        join_order: &[JoinOrderMember],\n        subqueries: &[NonFromClauseSubquery],\n        table_references: Option<&TableReferences>,\n    ) -> bool {\n        if self.consumed {\n            return false;\n        }\n        let Ok(eval_at) = self.eval_at(join_order, subqueries, table_references) else {\n            return false;\n        };\n        eval_at == EvalAt::Loop(loop_idx)\n    }\n\n    fn eval_at(\n        &self,\n        join_order: &[JoinOrderMember],\n        subqueries: &[NonFromClauseSubquery],\n        table_references: Option<&TableReferences>,\n    ) -> Result<EvalAt> {\n        determine_where_to_eval_term(self, join_order, subqueries, table_references)\n    }\n}\n\nimpl From<Expr> for WhereTerm {\n    fn from(value: Expr) -> Self {\n        Self {\n            expr: value,\n            from_outer_join: None,\n            consumed: false,\n        }\n    }\n}\n\nuse crate::ast::Expr;\nuse crate::util::exprs_are_equivalent;\n\n/// The loop index where to evaluate the condition.\n/// For example, in `SELECT * FROM u JOIN p WHERE u.id = 5`, the condition can already be evaluated at the first loop (idx 0),\n/// because that is the rightmost table that it references.\n///\n/// Conditions like 1=2 can be evaluated before the main loop is opened, because they are constant.\n/// In theory we should be able to statically analyze them all and reduce them to a single boolean value,\n/// but that is not implemented yet.\n#[derive(Debug, Clone, PartialEq, Eq, Copy)]\npub enum EvalAt {\n    Loop(usize),\n    BeforeLoop,\n}\n\n#[allow(clippy::non_canonical_partial_ord_impl)]\nimpl PartialOrd for EvalAt {\n    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {\n        match (self, other) {\n            (EvalAt::Loop(a), EvalAt::Loop(b)) => a.partial_cmp(b),\n            (EvalAt::BeforeLoop, EvalAt::BeforeLoop) => Some(Ordering::Equal),\n            (EvalAt::BeforeLoop, _) => Some(Ordering::Less),\n            (_, EvalAt::BeforeLoop) => Some(Ordering::Greater),\n        }\n    }\n}\n\nimpl Ord for EvalAt {\n    fn cmp(&self, other: &Self) -> Ordering {\n        self.partial_cmp(other)\n            .expect(\"total ordering not implemented for EvalAt\")\n    }\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum SubqueryEvalPhase {\n    BeforeLoop,\n    Loop(usize),\n    GroupedOutput,\n    UngroupedAggregateOutput,\n    WindowOutput,\n    PreWrite,\n    PostWriteReturning,\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum SubqueryOrigin {\n    SelectList,\n    SelectWhere,\n    SelectGroupBy,\n    SelectHaving,\n    SelectOrderBy,\n    SelectLimitOffset,\n    DmlWhere,\n    DmlSet,\n    DmlReturning,\n    TriggerWhen,\n}\n\nimpl SubqueryOrigin {\n    pub fn phase_floor(self) -> SubqueryEvalPhase {\n        match self {\n            SubqueryOrigin::SelectList\n            | SubqueryOrigin::SelectWhere\n            | SubqueryOrigin::SelectGroupBy\n            | SubqueryOrigin::SelectHaving\n            | SubqueryOrigin::SelectOrderBy\n            | SubqueryOrigin::SelectLimitOffset\n            | SubqueryOrigin::TriggerWhen => SubqueryEvalPhase::BeforeLoop,\n            SubqueryOrigin::DmlWhere => SubqueryEvalPhase::BeforeLoop,\n            SubqueryOrigin::DmlSet => SubqueryEvalPhase::PreWrite,\n            SubqueryOrigin::DmlReturning => SubqueryEvalPhase::PostWriteReturning,\n        }\n    }\n\n    pub fn is_post_write_returning(self) -> bool {\n        matches!(self, SubqueryOrigin::DmlReturning)\n    }\n}\n\n/// A query plan is either a SELECT or a DELETE (for now)\n#[derive(Debug, Clone)]\npub enum Plan {\n    Select(SelectPlan),\n    CompoundSelect {\n        left: Vec<(SelectPlan, ast::CompoundOperator)>,\n        right_most: SelectPlan,\n        limit: Option<Box<Expr>>,\n        offset: Option<Box<Expr>>,\n        order_by: Option<Vec<(ast::Expr, SortOrder)>>,\n    },\n    Delete(DeletePlan),\n    Update(UpdatePlan),\n}\n\nimpl Plan {\n    /// Returns true if this SELECT plan contains a reference to the given table.\n    /// For compound selects, checks all component selects.\n    /// Returns false for Delete/Update plans.\n    pub fn select_contains_table(&self, table: &Table) -> bool {\n        match self {\n            Plan::Select(select_plan) => select_plan.table_references.contains_table(table),\n            Plan::CompoundSelect {\n                left, right_most, ..\n            } => {\n                right_most.table_references.contains_table(table)\n                    || left\n                        .iter()\n                        .any(|(plan, _)| plan.table_references.contains_table(table))\n            }\n            Plan::Delete(_) | Plan::Update(_) => false,\n        }\n    }\n\n    /// Returns the query destination for Select/CompoundSelect plans.\n    /// Returns None for Delete/Update plans.\n    pub fn select_query_destination(&self) -> Option<&QueryDestination> {\n        match self {\n            Plan::Select(select_plan) => Some(&select_plan.query_destination),\n            Plan::CompoundSelect { right_most, .. } => Some(&right_most.query_destination),\n            Plan::Delete(_) | Plan::Update(_) => None,\n        }\n    }\n\n    /// Returns a mutable reference to the query destination for Select/CompoundSelect plans.\n    /// Returns None for Delete/Update plans.\n    pub fn select_query_destination_mut(&mut self) -> Option<&mut QueryDestination> {\n        match self {\n            Plan::Select(select_plan) => Some(&mut select_plan.query_destination),\n            Plan::CompoundSelect { right_most, .. } => Some(&mut right_most.query_destination),\n            Plan::Delete(_) | Plan::Update(_) => None,\n        }\n    }\n\n    /// Returns the result columns for Select/CompoundSelect plans.\n    /// Returns None for Delete/Update plans.\n    pub fn select_result_columns(&self) -> Option<&[ResultSetColumn]> {\n        match self {\n            Plan::Select(select_plan) => Some(&select_plan.result_columns),\n            Plan::CompoundSelect { right_most, .. } => Some(&right_most.result_columns),\n            Plan::Delete(_) | Plan::Update(_) => None,\n        }\n    }\n\n    /// Returns the table references for Select/CompoundSelect plans.\n    /// Returns None for Delete/Update plans.\n    pub fn select_table_references(&self) -> Option<&TableReferences> {\n        match self {\n            Plan::Select(select_plan) => Some(&select_plan.table_references),\n            Plan::CompoundSelect { right_most, .. } => Some(&right_most.table_references),\n            Plan::Delete(_) | Plan::Update(_) => None,\n        }\n    }\n\n    /// Returns true if this plan or any of its subplans read from the given table.\n    /// (Not for Delete/Update plans)\n    fn reads_table(&self, database_id: usize, table_name: &str) -> bool {\n        match self {\n            Plan::Select(select_plan) => select_plan.reads_table(database_id, table_name),\n            Plan::CompoundSelect {\n                left, right_most, ..\n            } => {\n                left.iter()\n                    .any(|(select_plan, _)| select_plan.reads_table(database_id, table_name))\n                    || right_most.reads_table(database_id, table_name)\n            }\n            Plan::Delete(_) | Plan::Update(_) => false,\n        }\n    }\n}\n\n/// The destination of the results of a query.\n/// Typically, the results of a query are returned to the caller.\n/// However, there are some cases where the results are not returned to the caller,\n/// but rather are yielded to a parent query via coroutine, or stored in a temp table,\n/// later used by the parent query.\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum EphemeralRowidMode {\n    /// The last result column is used as the rowid key.\n    FromResultColumns,\n    /// Generate a fresh rowid for each inserted row.\n    Auto,\n}\n\n#[derive(Debug, Clone)]\npub enum QueryDestination {\n    /// The results of the query are returned to the caller.\n    ResultRows,\n    /// The results of the query are yielded to a parent query via coroutine.\n    CoroutineYield {\n        /// The register that holds the program offset that handles jumping to/from the coroutine.\n        yield_reg: usize,\n        /// The index of the first instruction in the bytecode that implements the coroutine.\n        coroutine_implementation_start: BranchOffset,\n    },\n    /// The results of the query are stored in an ephemeral index,\n    /// later used by the parent query.\n    EphemeralIndex {\n        /// The cursor ID of the ephemeral index that will be used to store the results.\n        cursor_id: CursorID,\n        /// The index that will be used to store the results.\n        index: Arc<Index>,\n        /// Optional MakeRecord affinity string to apply before inserting keys.\n        /// For `IN (SELECT ...)` this must match the left-hand side expression affinity.\n        affinity_str: Option<Arc<String>>,\n        /// Whether this is a delete operation that will remove the index entries\n        is_delete: bool,\n    },\n    /// The results of the query are stored in an ephemeral table,\n    /// later used by the parent query.\n    EphemeralTable {\n        /// The cursor ID of the ephemeral table that will be used to store the results.\n        cursor_id: CursorID,\n        /// The table that will be used to store the results.\n        table: Arc<BTreeTable>,\n        /// How to determine the rowid key for inserts.\n        rowid_mode: EphemeralRowidMode,\n    },\n    /// The result of an EXISTS subquery are stored in a single register.\n    ExistsSubqueryResult {\n        /// The register that holds the result of the EXISTS subquery.\n        result_reg: usize,\n    },\n    /// The results of a subquery that is neither 'EXISTS' nor 'IN' are stored in a range of registers.\n    RowValueSubqueryResult {\n        /// The start register of the range that holds the result of the subquery.\n        result_reg_start: usize,\n        /// The number of registers that hold the result of the subquery.\n        num_regs: usize,\n    },\n    /// The results of the query are stored in a RowSet (for DELETE operations with triggers).\n    /// Rowids are added to the RowSet using RowSetAdd, then read back using RowSetRead.\n    RowSet {\n        /// The register that holds the RowSet object.\n        rowset_reg: usize,\n    },\n    /// Decision made at some point after query plan construction.\n    Unset,\n}\n\nimpl QueryDestination {\n    pub fn placeholder_for_subquery() -> Self {\n        QueryDestination::CoroutineYield {\n            yield_reg: usize::MAX, // will be set later in bytecode emission\n            coroutine_implementation_start: BranchOffset::Placeholder, // will be set later in bytecode emission\n        }\n    }\n}\n\n#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]\npub struct JoinOrderMember {\n    /// The internal ID of the[TableReference]\n    pub table_id: TableInternalId,\n    /// The index of the table in the original join order.\n    /// This is used to index into e.g. [TableReferences::joined_tables()]\n    pub original_idx: usize,\n    /// Whether this member is the right side of an OUTER JOIN\n    pub is_outer: bool,\n}\n\n#[derive(Debug, Clone, PartialEq)]\n\n/// Whether a column is DISTINCT or not.\npub enum Distinctness {\n    /// The column is not a DISTINCT column.\n    NonDistinct,\n    /// The column is a DISTINCT column,\n    /// and includes a translation context for handling duplicates.\n    Distinct { ctx: Option<DistinctCtx> },\n}\n\nimpl Distinctness {\n    pub fn from_ast(distinctness: Option<&ast::Distinctness>) -> Self {\n        match distinctness {\n            Some(ast::Distinctness::Distinct) => Self::Distinct { ctx: None },\n            Some(ast::Distinctness::All) => Self::NonDistinct,\n            None => Self::NonDistinct,\n        }\n    }\n    pub fn is_distinct(&self) -> bool {\n        matches!(self, Distinctness::Distinct { .. })\n    }\n}\n\n/// Translation context for handling DISTINCT columns.\n#[derive(Debug, Clone, PartialEq)]\npub struct DistinctCtx {\n    /// Hash table id used to deduplicate results.\n    pub hash_table_id: usize,\n    /// Collations for each distinct key column.\n    pub collations: Vec<CollationSeq>,\n    /// The label for the on conflict branch.\n    /// When a duplicate is found, the program will jump to the offset this label points to.\n    pub label_on_conflict: BranchOffset,\n}\n\nimpl DistinctCtx {\n    pub fn emit_deduplication_insns(\n        &self,\n        program: &mut ProgramBuilder,\n        num_regs: usize,\n        start_reg: usize,\n    ) {\n        program.emit_insn(Insn::HashDistinct {\n            data: Box::new(HashDistinctData {\n                hash_table_id: self.hash_table_id,\n                key_start_reg: start_reg,\n                num_keys: num_regs,\n                collations: self.collations.clone(),\n                target_pc: self.label_on_conflict,\n            }),\n        });\n    }\n}\n\n/// Detected simple-aggregate optimization.\n///\n/// Analogous to SQLite's `isSimpleCount()` / `minMaxQuery()`. When set on a\n/// `SelectPlan`, the emitter can use a specialised fast path instead of a full\n/// scan + accumulate loop.\n#[derive(Debug, Clone)]\npub struct MinMaxDef {\n    pub func: AggFunc,\n    pub argument: ast::Expr,\n    pub order: SortOrder,\n    /// Explicit COLLATE override, if any. `None` means use the column default.\n    pub collation: Option<CollationSeq>,\n}\n\n#[derive(Debug, Clone)]\npub enum SimpleAggregate {\n    /// `SELECT count(*) FROM <tbl>` — uses the `Insn::Count` opcode directly.\n    Count,\n    /// `SELECT min(expr) FROM …` or `SELECT max(expr) FROM …` — the optimizer\n    /// will pick an index that delivers rows in the right order so the emitter\n    /// only needs to read the first (non-NULL for MIN) row.\n    MinMax(Box<MinMaxDef>),\n}\n\n#[derive(Debug, Clone)]\npub struct SelectPlan {\n    pub table_references: TableReferences,\n    /// The order in which the tables are joined. Tables have usize Ids (their index in joined_tables)\n    pub join_order: Vec<JoinOrderMember>,\n    /// the columns inside SELECT ... FROM\n    pub result_columns: Vec<ResultSetColumn>,\n    /// where clause split into a vec at 'AND' boundaries. all join conditions also get shoved in here,\n    /// and we keep track of which join they came from (mainly for OUTER JOIN processing)\n    pub where_clause: Vec<WhereTerm>,\n    /// group by clause\n    pub group_by: Option<GroupBy>,\n    /// order by clause\n    pub order_by: Vec<(Box<ast::Expr>, SortOrder)>,\n    /// all the aggregates collected from the result columns, order by, and (TODO) having clauses\n    pub aggregates: Vec<Aggregate>,\n    /// limit clause\n    pub limit: Option<Box<Expr>>,\n    /// offset clause\n    pub offset: Option<Box<Expr>>,\n    /// query contains a constant condition that is always false\n    pub contains_constant_false_condition: bool,\n    /// the destination of the resulting rows from this plan.\n    pub query_destination: QueryDestination,\n    /// whether the query is DISTINCT\n    pub distinctness: Distinctness,\n    /// values: https://sqlite.org/syntax/select-core.html\n    pub values: Vec<Vec<Expr>>,\n    /// The window definition and all window functions associated with it. There is at most one\n    /// window per SELECT. If the original query contains more, they are pushed down into subqueries.\n    pub window: Option<Window>,\n    /// Subqueries that appear in any part of the query apart from the FROM clause\n    pub non_from_clause_subqueries: Vec<NonFromClauseSubquery>,\n    /// Estimated number of times this SELECT will be invoked by its parent scope.\n    ///\n    /// Top-level queries and standalone FROM-subqueries default to 1. Correlated\n    /// non-FROM subqueries may be re-optimized after their parent join order is\n    /// known so their inner FROM-subqueries can cost repeated probes correctly.\n    pub input_cardinality_hint: Option<f64>,\n    /// Estimated output rows from the optimizer's join order computation.\n    /// Used to propagate cardinality estimates for CTE/subquery tables.\n    pub estimated_output_rows: Option<f64>,\n    /// When set, this query is a simple aggregate (COUNT(*), MIN, or MAX)\n    /// that can be satisfied without a full table scan.\n    pub simple_aggregate: Option<SimpleAggregate>,\n}\n\nimpl SelectPlan {\n    pub fn joined_tables(&self) -> &[JoinedTable] {\n        self.table_references.joined_tables()\n    }\n\n    pub fn agg_args_count(&self) -> usize {\n        self.aggregates.iter().map(|agg| agg.args.len()).sum()\n    }\n\n    /// Whether this query or any of its subqueries reference columns from the outer query.\n    pub fn is_correlated(&self) -> bool {\n        self.table_references\n            .outer_query_refs()\n            .iter()\n            .any(|t| t.is_used())\n            || self.non_from_clause_subqueries.iter().any(|s| s.correlated)\n            || self\n                .table_references\n                .joined_tables()\n                .iter()\n                .any(|t| match &t.table {\n                    Table::FromClauseSubquery(subquery) => plan_is_correlated(&subquery.plan),\n                    _ => false,\n                })\n    }\n\n    fn reads_table(&self, database_id: usize, table_name: &str) -> bool {\n        self.table_references.joined_tables().iter().any(|table| {\n            table.matches(database_id, table_name)\n                || match &table.table {\n                    Table::FromClauseSubquery(subquery) => {\n                        subquery.plan.reads_table(database_id, table_name)\n                    }\n                    Table::BTree(_) | Table::Virtual(_) => false,\n                }\n        }) || self\n            .non_from_clause_subqueries\n            .iter()\n            .any(|subquery| subquery.reads_table(database_id, table_name))\n    }\n}\n\n/// Why an UPDATE/DELETE must gather target rowids first, then apply writes.\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum DmlSafetyReason {\n    /// Triggers exist, so we lock in target rows before writing.\n    Trigger,\n    /// WHERE has a subquery, so we lock in target rows before writing.\n    SubqueryInWhere,\n    /// The plan reads rowids from multiple index branches (multi-index scan).\n    MultiIndexScan,\n    /// REPLACE may delete conflicting rows while we are scanning.\n    ReplaceMode,\n    /// The statement updates key columns used by the scan itself.\n    KeyMutation,\n    /// The index method cursor does not materialize results up front,\n    /// so writes could invalidate the live iterator.\n    IndexMethodNotMaterialized,\n}\n\n/// Safety decisions made while planning UPDATE/DELETE.\n#[derive(Debug, Clone, Default)]\npub struct DmlSafety {\n    /// Why the safer \"collect first, write later\" mode was enabled.\n    pub reasons: SmallVec<[DmlSafetyReason; 2]>,\n}\n\nimpl DmlSafety {\n    pub fn requires_stable_write_set(&self) -> bool {\n        !self.reasons.is_empty()\n    }\n\n    pub fn require(&mut self, reason: DmlSafetyReason) {\n        if !self.reasons.contains(&reason) {\n            self.reasons.push(reason);\n        }\n    }\n}\n\n#[allow(dead_code)]\n#[derive(Debug, Clone)]\npub struct DeletePlan {\n    pub table_references: TableReferences,\n    /// the columns inside SELECT ... FROM\n    pub result_columns: Vec<ResultSetColumn>,\n    /// where clause split into a vec at 'AND' boundaries.\n    pub where_clause: Vec<WhereTerm>,\n    /// order by clause\n    pub order_by: Vec<(Box<ast::Expr>, SortOrder)>,\n    /// limit clause\n    pub limit: Option<Box<Expr>>,\n    /// offset clause\n    pub offset: Option<Box<Expr>>,\n    /// query contains a constant condition that is always false\n    pub contains_constant_false_condition: bool,\n    /// Indexes that must be updated by the delete operation.\n    pub indexes: Vec<Arc<Index>>,\n    /// When DELETE cannot safely write while scanning, we first collect rowids into a RowSet.\n    pub rowset_plan: Option<SelectPlan>,\n    /// Register ID for the RowSet (if rowset_plan is Some)\n    pub rowset_reg: Option<usize>,\n    /// Subqueries that appear in the WHERE clause (for non-rowset path)\n    pub non_from_clause_subqueries: Vec<NonFromClauseSubquery>,\n    /// Whether this DELETE plan uses the safer pre-materialization path, and why.\n    pub safety: DmlSafety,\n}\n\n#[derive(Debug, Clone)]\npub struct UpdatePlan {\n    pub table_references: TableReferences,\n    /// Conflict resolution strategy (e.g., OR IGNORE, OR REPLACE)\n    pub or_conflict: Option<ResolveType>,\n    // (column index, new value) pairs\n    pub set_clauses: Vec<(usize, Box<ast::Expr>)>,\n    pub where_clause: Vec<WhereTerm>,\n    pub order_by: Vec<(Box<ast::Expr>, SortOrder)>,\n    pub limit: Option<Box<Expr>>,\n    pub offset: Option<Box<Expr>>,\n    // TODO: optional RETURNING clause\n    pub returning: Option<Vec<ResultSetColumn>>,\n    // whether the WHERE clause is always false\n    pub contains_constant_false_condition: bool,\n    pub indexes_to_update: Vec<Arc<Index>>,\n    // If the UPDATE modifies any column that is present in the key of the btree used to iterate over the table (either the table itself or an index),\n    // gather all the target rowids into an ephemeral table, and then use that table as the single JoinedTable for the actual UPDATE loop.\n    // This ensures the keys of the btree used to iterate cannot be changed during the UPDATE loop itself, ensuring all the intended rows actually get\n    // updated and none are skipped.\n    pub ephemeral_plan: Option<SelectPlan>,\n    // For ALTER TABLE turso-db emits appropriate DDL statement in the \"updates\" cell of CDC table\n    // This field is present only for update plan created for ALTER TABLE when CDC mode has \"updates\" values\n    pub cdc_update_alter_statement: Option<String>,\n    /// Subqueries that appear in the WHERE clause (for non-ephemeral path)\n    pub non_from_clause_subqueries: Vec<NonFromClauseSubquery>,\n    /// Whether this UPDATE plan uses the safer pre-materialization path, and why.\n    pub safety: DmlSafety,\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub enum IterationDirection {\n    Forwards,\n    Backwards,\n}\n\npub fn select_star(\n    tables: &[JoinedTable],\n    out_columns: &mut Vec<ResultSetColumn>,\n    right_join_swapped: bool,\n) {\n    // RIGHT JOIN swapped tables; iterate in reverse to restore original column order.\n    let table_iter: Vec<&JoinedTable> = if right_join_swapped {\n        tables.iter().rev().collect()\n    } else {\n        tables.iter().collect()\n    };\n    for table in table_iter {\n        // Semi/anti-join tables are internal (from EXISTS/NOT EXISTS unnesting)\n        // and should not contribute columns to SELECT *.\n        if table\n            .join_info\n            .as_ref()\n            .is_some_and(|ji| ji.is_semi_or_anti())\n        {\n            continue;\n        }\n        out_columns.extend(\n            table\n                .columns()\n                .iter()\n                .enumerate()\n                .filter(|(_, col)| !col.hidden())\n                .filter(|(_, col)| {\n                    // If we are joining with USING, we need to deduplicate the columns from the right table\n                    // that are also present in the USING clause.\n                    if let Some(join_info) = &table.join_info {\n                        !join_info.using.iter().any(|using_col| {\n                            col.name\n                                .as_ref()\n                                .is_some_and(|name| name.eq_ignore_ascii_case(using_col.as_str()))\n                        })\n                    } else {\n                        true\n                    }\n                })\n                .map(|(i, col)| ResultSetColumn {\n                    alias: None,\n                    expr: ast::Expr::Column {\n                        database: None,\n                        table: table.internal_id,\n                        column: i,\n                        is_rowid_alias: col.is_rowid_alias(),\n                    },\n                    contains_aggregates: false,\n                }),\n        );\n    }\n}\n\n/// The type of join between two tables.\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum JoinType {\n    Inner,\n    LeftOuter,\n    FullOuter,\n    /// Semi-join: keep outer row if inner match found (EXISTS).\n    Semi,\n    /// Anti-join: keep outer row if NO inner match found (NOT EXISTS).\n    Anti,\n}\n\n/// Join information for a table reference.\n#[derive(Debug, Clone)]\npub struct JoinInfo {\n    /// The type of join.\n    pub join_type: JoinType,\n    /// The USING clause for the join, if any. NATURAL JOIN is transformed into USING (col1, col2, ...).\n    pub using: Vec<ast::Name>,\n    /// When true, the optimizer must not reorder this table relative to its\n    /// neighbors. Set for CROSS JOIN to match SQLite semantics.\n    pub no_reorder: bool,\n}\n\nimpl JoinInfo {\n    /// Whether this is an OUTER JOIN (LEFT OUTER or FULL OUTER).\n    pub fn is_outer(&self) -> bool {\n        matches!(self.join_type, JoinType::LeftOuter | JoinType::FullOuter)\n    }\n\n    /// Whether this is a FULL OUTER JOIN.\n    pub fn is_full_outer(&self) -> bool {\n        self.join_type == JoinType::FullOuter\n    }\n\n    /// Whether this is a semi-join (EXISTS).\n    pub fn is_semi(&self) -> bool {\n        self.join_type == JoinType::Semi\n    }\n\n    /// Whether this is an anti-join (NOT EXISTS).\n    pub fn is_anti(&self) -> bool {\n        self.join_type == JoinType::Anti\n    }\n\n    /// Whether this is a semi-join or anti-join (EXISTS/NOT EXISTS).\n    pub fn is_semi_or_anti(&self) -> bool {\n        matches!(self.join_type, JoinType::Semi | JoinType::Anti)\n    }\n\n    /// Whether the optimizer must preserve this table's position in the join order.\n    pub fn is_ordering_constrained(&self) -> bool {\n        self.is_outer() || self.is_semi_or_anti() || self.no_reorder\n    }\n}\n\n/// A joined table in the query plan.\n/// For example,\n/// ```sql\n/// SELECT * FROM users u JOIN products p JOIN (SELECT * FROM users) sub;\n/// ```\n/// has three table references where\n/// - all have [Operation::Scan]\n/// - identifiers are `t`, `p`, `sub`\n/// - `t` and `p` are [Table::BTree] while `sub` is [Table::FromClauseSubquery]\n/// - join_info is None for the first table reference, and Some(JoinInfo { join_type: JoinType::Inner, using: vec![] }) for the second and third table references\n#[derive(Debug, Clone)]\npub struct JoinedTable {\n    /// The operation that this table reference performs.\n    pub op: Operation,\n    /// Table object, which contains metadata about the table, e.g. columns.\n    pub table: Table,\n    /// The name of the table as referred to in the query, either the literal name or an alias e.g. \"users\" or \"u\"\n    pub identifier: String,\n    /// Internal ID of the table reference, used in e.g. [Expr::Column] to refer to this table.\n    pub internal_id: TableInternalId,\n    /// The join info for this table reference, if it is the right side of a join (which all except the first table reference have)\n    pub join_info: Option<JoinInfo>,\n    /// Bitmask of columns that are referenced in the query.\n    /// Used to decide whether a covering index can be used.\n    pub col_used_mask: ColumnUsedMask,\n    /// Count of how many times each column is referenced.\n    ///\n    /// Expression indexes can satisfy a column requirement if the column is\n    /// only used to build the expression itself. Tracking counts lets us\n    /// subtract a column from the covering set only when every usage is\n    /// accounted for by an expression index.\n    pub column_use_counts: Vec<usize>,\n    /// Expressions referencing this table that may be satisfied by an expression index.\n    ///\n    /// Each entry stores the normalized expression text and the columns it\n    /// needs. During covering checks we ask: does an index contain this\n    /// expression? If yes, all columns that *only* feed this expression can be\n    /// removed from the required-column set.\n    pub expression_index_usages: Vec<ExpressionIndexUsage>,\n    /// The index of the database. \"main\" is always zero.\n    pub database_id: usize,\n    /// INDEXED BY / NOT INDEXED hint from the SQL statement.\n    pub indexed: Option<ast::Indexed>,\n}\n\n#[derive(Debug, Clone)]\npub struct OuterQueryReference {\n    /// The name of the table as referred to in the query, either the literal name or an alias e.g. \"users\" or \"u\"\n    pub identifier: String,\n    /// Internal ID of the table reference, used in e.g. [Expr::Column] to refer to this table.\n    pub internal_id: TableInternalId,\n    /// Table object, which contains metadata about the table, e.g. columns.\n    pub table: Table,\n    /// Bitmask of columns that are referenced in the query.\n    /// Used to track dependencies, so that it can be resolved\n    /// when a WHERE clause subquery should be evaluated;\n    /// i.e., if the subquery depends on tables T and U,\n    /// then both T and U need to be in scope for the subquery to be evaluated.\n    pub col_used_mask: ColumnUsedMask,\n    /// Original CTE SELECT AST for re-planning. When a CTE is referenced\n    /// multiple times, each reference needs a fresh plan with unique\n    /// internal_ids to avoid cursor key collisions.\n    pub cte_select: Option<ast::Select>,\n    /// Explicit column names from WITH t(a, b) AS (...) syntax.\n    pub cte_explicit_columns: Vec<String>,\n    /// CTE ID if this is a CTE reference. Used to track CTE reference counts\n    /// for materialization decisions.\n    pub cte_id: Option<usize>,\n    /// When true, this entry is only for CTE definition lookup in subquery\n    /// FROM clauses, not for column resolution. This is set when the CTE\n    /// has been consumed by a FROM clause (with or without an alias), so\n    /// column resolution goes through the joined_table instead.\n    pub cte_definition_only: bool,\n    /// Whether the rowid of this table is referenced. Tracked separately from\n    /// col_used_mask because rowid is not a real column and setting a fake\n    /// column index in col_used_mask could mislead covering index decisions.\n    pub rowid_referenced: bool,\n}\n\nimpl OuterQueryReference {\n    /// Returns the columns of the table that this outer query reference refers to.\n    pub fn columns(&self) -> &[Column] {\n        self.table.columns()\n    }\n\n    /// Marks a column as used; used means that the column is referenced in the query.\n    pub fn mark_column_used(&mut self, column_index: usize) {\n        self.col_used_mask.set(column_index);\n    }\n\n    /// Whether the OuterQueryReference is used by the current query scope.\n    /// This is used primarily to determine at what loop depth a subquery should be evaluated.\n    pub fn is_used(&self) -> bool {\n        !self.col_used_mask.is_empty() || self.rowid_referenced\n    }\n}\n\n#[derive(Debug, Clone)]\n/// A collection of table references in a given SQL statement.\n///\n/// `TableReferences::joined_tables` is the list of tables that are joined together.\n/// Example: SELECT * FROM t JOIN u JOIN v -- the joined tables are t, u and v.\n///\n/// `TableReferences::outer_query_refs` are references to tables outside the current scope.\n/// Example: SELECT * FROM t WHERE EXISTS (SELECT * FROM u WHERE u.foo = t.foo)\n/// -- here, 'u' is an outer query reference for the subquery (SELECT * FROM u WHERE u.foo = t.foo),\n/// since that query does not declare 't' in its FROM clause.\n///\n///\n/// Typically a query will only have joined tables, but the following may have outer query references:\n/// - CTEs that refer to other preceding CTEs\n/// - Correlated subqueries, i.e. subqueries that depend on the outer scope\npub struct TableReferences {\n    /// Tables that are joined together in this query scope.\n    joined_tables: Vec<JoinedTable>,\n    /// Tables from outer scopes that are referenced in this query scope.\n    outer_query_refs: Vec<OuterQueryReference>,\n    /// Set when a RIGHT JOIN is rewritten as LEFT JOIN by swapping the two tables,\n    /// so `select_star` emits columns in the original user-visible order.\n    right_join_swapped: bool,\n}\n\nimpl Default for TableReferences {\n    fn default() -> Self {\n        Self::new_empty()\n    }\n}\n\nimpl TableReferences {\n    /// The maximum number of tables that can be joined together in a query.\n    /// This limit is arbitrary, although we currently use a u128 to represent the [crate::translate::planner::TableMask],\n    /// which can represent up to 128 tables.\n    /// Even at 63 tables we currently cannot handle the optimization performantly, hence the arbitrary cap.\n    pub const MAX_JOINED_TABLES: usize = 63;\n    pub fn new(\n        joined_tables: Vec<JoinedTable>,\n        outer_query_refs: Vec<OuterQueryReference>,\n    ) -> Self {\n        Self {\n            joined_tables,\n            outer_query_refs,\n            right_join_swapped: false,\n        }\n    }\n    pub fn new_empty() -> Self {\n        Self {\n            joined_tables: Vec::new(),\n            outer_query_refs: Vec::new(),\n            right_join_swapped: false,\n        }\n    }\n\n    pub fn is_empty(&self) -> bool {\n        self.joined_tables.is_empty() && self.outer_query_refs.is_empty()\n    }\n\n    /// Mark that tables were swapped for a RIGHT-to-LEFT JOIN rewrite.\n    pub fn set_right_join_swapped(&mut self) {\n        self.right_join_swapped = true;\n    }\n\n    /// Whether tables were swapped for a RIGHT JOIN rewrite.\n    pub fn right_join_swapped(&self) -> bool {\n        self.right_join_swapped\n    }\n\n    /// Add a new [JoinedTable] to the query plan.\n    pub fn add_joined_table(&mut self, joined_table: JoinedTable) {\n        self.joined_tables.push(joined_table);\n    }\n\n    /// Add a new [OuterQueryReference] to the query plan.\n    pub fn add_outer_query_reference(&mut self, outer_query_reference: OuterQueryReference) {\n        self.outer_query_refs.push(outer_query_reference);\n    }\n\n    /// Returns an immutable reference to the [JoinedTable]s in the query plan.\n    pub fn joined_tables(&self) -> &[JoinedTable] {\n        &self.joined_tables\n    }\n\n    /// Returns a mutable reference to the [JoinedTable]s in the query plan.\n    pub fn joined_tables_mut(&mut self) -> &mut Vec<JoinedTable> {\n        &mut self.joined_tables\n    }\n\n    /// Resets the expression index usages for all joined tables.\n    pub fn reset_expression_index_usages(&mut self) {\n        for table in self.joined_tables.iter_mut() {\n            table.clear_expression_index_usages();\n        }\n    }\n\n    /// Called before optimization so we can reuse the same registration\n    /// for result columns, ORDER BY, and GROUP BY expressions. If a\n    /// SELECT lists `LOWER(name)` and an index exists on `LOWER(name)`, we\n    /// can plan a covering scan because the expression value lives inside\n    /// the index key.\n    pub fn register_expression_index_usage(&mut self, expr: &ast::Expr) {\n        let Some((table_id, columns_mask)) = single_table_column_usage(expr) else {\n            return;\n        };\n        let Some(table_ref) = self\n            .joined_tables()\n            .iter()\n            .find(|t| t.internal_id == table_id)\n        else {\n            return;\n        };\n        let normalized = normalize_expr_for_index_matching(expr, table_ref, self);\n        if let Some(table_ref_mut) = self\n            .joined_tables_mut()\n            .iter_mut()\n            .find(|t| t.internal_id == table_id)\n        {\n            table_ref_mut.register_expression_index_usage(normalized, columns_mask);\n        }\n    }\n\n    /// Returns an immutable reference to the [OuterQueryReference]s in the query plan.\n    pub fn outer_query_refs(&self) -> &[OuterQueryReference] {\n        &self.outer_query_refs\n    }\n\n    /// Returns an immutable reference to the [OuterQueryReference] with the given internal ID.\n    pub fn find_outer_query_ref_by_internal_id(\n        &self,\n        internal_id: TableInternalId,\n    ) -> Option<&OuterQueryReference> {\n        self.outer_query_refs\n            .iter()\n            .find(|t| t.internal_id == internal_id)\n    }\n\n    /// Returns a mutable reference to the [OuterQueryReference] with the given internal ID.\n    pub fn find_outer_query_ref_by_internal_id_mut(\n        &mut self,\n        internal_id: TableInternalId,\n    ) -> Option<&mut OuterQueryReference> {\n        self.outer_query_refs\n            .iter_mut()\n            .find(|t| t.internal_id == internal_id)\n    }\n\n    /// Returns an immutable reference to the [Table] with the given internal ID,\n    /// plus a boolean indicating whether the table is a joined table from the current query scope (false),\n    /// or an outer query reference (true).\n    pub fn find_table_by_internal_id(\n        &self,\n        internal_id: TableInternalId,\n    ) -> Option<(bool, &Table)> {\n        self.joined_tables\n            .iter()\n            .find(|t| t.internal_id == internal_id)\n            .map(|t| (false, &t.table))\n            .or_else(|| {\n                self.outer_query_refs\n                    .iter()\n                    .find(|t| t.internal_id == internal_id)\n                    .map(|t| (true, &t.table))\n            })\n    }\n\n    /// Returns an immutable reference to the [Table] with the given identifier,\n    /// where identifier is either the literal name of the table or an alias.\n    pub fn find_table_by_identifier(&self, identifier: &str) -> Option<&Table> {\n        self.joined_tables\n            .iter()\n            .find(|t| t.identifier == identifier)\n            .map(|t| &t.table)\n            .or_else(|| {\n                self.outer_query_refs\n                    .iter()\n                    .find(|t| t.identifier == identifier)\n                    .map(|t| &t.table)\n            })\n    }\n\n    /// Returns an immutable reference to the first [Table] whose underlying\n    /// table name matches `name`. Unlike [find_table_by_identifier], this\n    /// searches by the base table name (e.g. \"t1\") rather than the alias\n    /// (e.g. \"a\"). This is needed when looking up column metadata for\n    /// ephemeral auto-indexes, whose `table_name` field stores the base name\n    /// while the table reference may be aliased.\n    pub fn find_table_by_table_name(&self, name: &str) -> Option<&Table> {\n        self.joined_tables\n            .iter()\n            .find(|t| t.table.get_name() == name)\n            .map(|t| &t.table)\n            .or_else(|| {\n                self.outer_query_refs\n                    .iter()\n                    .find(|t| t.table.get_name() == name)\n                    .map(|t| &t.table)\n            })\n    }\n\n    /// Returns an immutable reference to the [OuterQueryReference] with the given identifier,\n    /// where identifier is either the literal name of the table or an alias.\n    pub fn find_outer_query_ref_by_identifier(\n        &self,\n        identifier: &str,\n    ) -> Option<&OuterQueryReference> {\n        self.outer_query_refs\n            .iter()\n            .find(|t| t.identifier == identifier)\n    }\n\n    /// Marks the pre-planned [OuterQueryReference] with the given identifier as\n    /// \"CTE definition only\". This prevents it from being used for column\n    /// resolution while still allowing CTE definition lookup in subquery FROM\n    /// clauses. Called when a CTE is consumed by a FROM clause, since column\n    /// resolution is then handled by the joined_table entry instead.\n    pub fn mark_outer_query_ref_cte_definition_only(&mut self, identifier: &str) {\n        if let Some(outer_ref) = self\n            .outer_query_refs\n            .iter_mut()\n            .find(|t| t.identifier == identifier)\n        {\n            outer_ref.cte_definition_only = true;\n        }\n    }\n\n    /// Returns the internal ID and immutable reference to the [Table] with the given identifier,\n    pub fn find_table_and_internal_id_by_identifier(\n        &self,\n        identifier: &str,\n    ) -> Option<(TableInternalId, &Table)> {\n        self.joined_tables\n            .iter()\n            .find(|t| t.identifier == identifier)\n            .map(|t| (t.internal_id, &t.table))\n            .or_else(|| {\n                self.outer_query_refs\n                    .iter()\n                    .find(|t| t.identifier == identifier && !t.cte_definition_only)\n                    .map(|t| (t.internal_id, &t.table))\n            })\n    }\n\n    /// Returns an immutable reference to the [JoinedTable] with the given internal ID.\n    pub fn find_joined_table_by_internal_id(\n        &self,\n        internal_id: TableInternalId,\n    ) -> Option<&JoinedTable> {\n        self.joined_tables\n            .iter()\n            .find(|t| t.internal_id == internal_id)\n    }\n\n    /// Returns a mutable reference to the [JoinedTable] with the given internal ID.\n    pub fn find_joined_table_by_internal_id_mut(\n        &mut self,\n        internal_id: TableInternalId,\n    ) -> Option<&mut JoinedTable> {\n        self.joined_tables\n            .iter_mut()\n            .find(|t| t.internal_id == internal_id)\n    }\n\n    /// Marks a column as used; used means that the column is referenced in the query.\n    pub fn mark_column_used(&mut self, internal_id: TableInternalId, column_index: usize) {\n        if let Some(joined_table) = self.find_joined_table_by_internal_id_mut(internal_id) {\n            joined_table.mark_column_used(column_index);\n        } else if let Some(outer_query_ref) =\n            self.find_outer_query_ref_by_internal_id_mut(internal_id)\n        {\n            outer_query_ref.mark_column_used(column_index);\n        } else {\n            panic!(\"table with internal id {internal_id} not found in table references\");\n        }\n    }\n\n    /// Marks the rowid of a table as referenced. This is tracked separately\n    /// from column usage because rowid is not a real column.\n    pub fn mark_rowid_referenced(&mut self, internal_id: TableInternalId) {\n        if let Some(outer_query_ref) = self.find_outer_query_ref_by_internal_id_mut(internal_id) {\n            outer_query_ref.rowid_referenced = true;\n        }\n        // For joined tables, rowid references don't need special tracking\n        // since correlated subquery detection only looks at outer_query_refs.\n    }\n\n    pub fn contains_table(&self, table: &Table) -> bool {\n        self.joined_tables\n            .iter()\n            .map(|t| &t.table)\n            .chain(self.outer_query_refs.iter().map(|t| &t.table))\n            .any(|t| match t {\n                Table::FromClauseSubquery(subquery_table) => {\n                    subquery_table.plan.select_contains_table(table)\n                }\n                _ => t == table,\n            })\n    }\n\n    pub fn extend(&mut self, other: TableReferences) {\n        self.joined_tables.extend(other.joined_tables);\n        self.outer_query_refs.extend(other.outer_query_refs);\n    }\n}\n\n/// Tracks which columns are used in a query. Optimized for the common case\n/// of ≤64 columns (single u64), with heap-allocated overflow\n#[derive(Clone, Debug, Default, PartialEq)]\npub struct ColumnUsedMask {\n    inline: u64,\n    overflow: Option<Vec<u64>>,\n}\n\nimpl ColumnUsedMask {\n    const INLINE_BITS: usize = 64;\n\n    pub fn set(&mut self, index: usize) {\n        if index < Self::INLINE_BITS {\n            self.inline |= 1 << index;\n        } else {\n            let overflow_idx = (index - Self::INLINE_BITS) / 64;\n            let bit = (index - Self::INLINE_BITS) % 64;\n            let overflow = self.overflow.get_or_insert_with(Vec::new);\n            if overflow_idx >= overflow.len() {\n                overflow.resize(overflow_idx + 1, 0);\n            }\n            overflow[overflow_idx] |= 1 << bit;\n        }\n    }\n\n    pub fn get(&self, index: usize) -> bool {\n        if index < Self::INLINE_BITS {\n            (self.inline >> index) & 1 != 0\n        } else {\n            let Some(overflow) = &self.overflow else {\n                return false;\n            };\n            let overflow_idx = (index - Self::INLINE_BITS) / 64;\n            let bit = (index - Self::INLINE_BITS) % 64;\n            overflow\n                .get(overflow_idx)\n                .is_some_and(|word| (word >> bit) & 1 != 0)\n        }\n    }\n\n    pub fn clear(&mut self, index: usize) {\n        if index < Self::INLINE_BITS {\n            self.inline &= !(1 << index);\n        } else if let Some(overflow) = &mut self.overflow {\n            let overflow_idx = (index - Self::INLINE_BITS) / 64;\n            let bit = (index - Self::INLINE_BITS) % 64;\n            if let Some(word) = overflow.get_mut(overflow_idx) {\n                *word &= !(1 << bit);\n            }\n        }\n    }\n\n    pub fn contains_all_set_bits_of(&self, other: &Self) -> bool {\n        if (self.inline & other.inline) != other.inline {\n            return false;\n        }\n        match (&self.overflow, &other.overflow) {\n            (None, None) => true,\n            (None, Some(other_ov)) => other_ov.iter().all(|&w| w == 0),\n            (Some(_), None) => true,\n            (Some(self_ov), Some(other_ov)) => other_ov.iter().enumerate().all(|(i, &other_w)| {\n                let self_w = self_ov.get(i).copied().unwrap_or(0);\n                (self_w & other_w) == other_w\n            }),\n        }\n    }\n\n    pub fn is_empty(&self) -> bool {\n        self.inline == 0\n            && self\n                .overflow\n                .as_ref()\n                .is_none_or(|ov| ov.iter().all(|&w| w == 0))\n    }\n\n    pub fn is_only(&self, index: usize) -> bool {\n        if index < Self::INLINE_BITS {\n            self.inline == (1 << index)\n                && self\n                    .overflow\n                    .as_ref()\n                    .is_none_or(|ov| ov.iter().all(|&w| w == 0))\n        } else {\n            if self.inline != 0 {\n                return false;\n            }\n            let Some(overflow) = &self.overflow else {\n                return false;\n            };\n            let overflow_idx = (index - Self::INLINE_BITS) / 64;\n            let bit = (index - Self::INLINE_BITS) % 64;\n            // The overflow vector must be long enough to contain the target index\n            if overflow_idx >= overflow.len() {\n                return false;\n            }\n            overflow.iter().enumerate().all(|(i, &w)| {\n                if i == overflow_idx {\n                    w == (1 << bit)\n                } else {\n                    w == 0\n                }\n            })\n        }\n    }\n\n    pub fn subtract(&mut self, other: &Self) {\n        self.inline &= !other.inline;\n        if let (Some(self_ov), Some(other_ov)) = (&mut self.overflow, &other.overflow) {\n            for (i, other_w) in other_ov.iter().enumerate() {\n                if let Some(self_w) = self_ov.get_mut(i) {\n                    *self_w &= !other_w;\n                }\n            }\n        }\n    }\n\n    pub fn iter(&self) -> impl Iterator<Item = usize> + '_ {\n        let inline_iter = (0..Self::INLINE_BITS).filter(|&i| (self.inline >> i) & 1 != 0);\n        let overflow_iter = self\n            .overflow\n            .iter()\n            .flat_map(|ov| ov.iter().enumerate())\n            .flat_map(|(word_idx, &word)| {\n                (0..64).filter_map(move |bit| {\n                    if (word >> bit) & 1 != 0 {\n                        Some(Self::INLINE_BITS + word_idx * 64 + bit)\n                    } else {\n                        None\n                    }\n                })\n            });\n        inline_iter.chain(overflow_iter)\n    }\n}\n\nimpl std::ops::BitOrAssign<&Self> for ColumnUsedMask {\n    fn bitor_assign(&mut self, rhs: &Self) {\n        self.inline |= rhs.inline;\n        if let Some(rhs_ov) = &rhs.overflow {\n            let self_ov = self.overflow.get_or_insert_with(Vec::new);\n            if self_ov.len() < rhs_ov.len() {\n                self_ov.resize(rhs_ov.len(), 0);\n            }\n            for (i, &rhs_w) in rhs_ov.iter().enumerate() {\n                self_ov[i] |= rhs_w;\n            }\n        }\n    }\n}\n\n#[derive(Clone, Debug)]\npub struct ExpressionIndexUsage {\n    /// Normalized (non-bound) ast of the expression as stored on an index column.\n    /// Example: `lower(name)` for INDEX ON t(lower(name)).\n    pub normalized_expr: Box<ast::Expr>,\n    /// Columns required to compute the expression. Helps decide whether using\n    /// the expression value from the index fully covers those column reads.\n    pub columns_mask: ColumnUsedMask,\n}\n\n/// Represents one key pair in a hash join equality condition.\n/// For `expr1 = expr2`, this tracks which WHERE term contains the equality\n/// and which side of the equality belongs to the build table.\n#[derive(Debug, Clone, Copy)]\npub struct HashJoinKey {\n    /// Index into the where_clause vector\n    pub where_clause_idx: usize,\n    /// Which side of the binary equality expression belongs to the build table.\n    /// The other side belongs to the probe table.\n    pub build_side: BinaryExprSide,\n}\n\nimpl HashJoinKey {\n    /// Get the build table's expression from the WHERE clause.\n    pub fn get_build_expr<'a>(&self, where_clause: &'a [WhereTerm]) -> &'a ast::Expr {\n        let where_term = &where_clause[self.where_clause_idx];\n        let Ok(Some((lhs, _, rhs))) = as_binary_components(&where_term.expr) else {\n            panic!(\"HashJoinKey: expected a valid binary expression\");\n        };\n        if self.build_side == BinaryExprSide::Lhs {\n            lhs\n        } else {\n            rhs\n        }\n    }\n\n    /// Get the probe table's expression from the WHERE clause.\n    pub fn get_probe_expr<'a>(&self, where_clause: &'a [WhereTerm]) -> &'a ast::Expr {\n        let where_term = &where_clause[self.where_clause_idx];\n        let Ok(Some((lhs, _, rhs))) = as_binary_components(&where_term.expr) else {\n            panic!(\"HashJoinKey: expected a valid binary expression\");\n        };\n        if self.build_side == BinaryExprSide::Lhs {\n            rhs // probe is the opposite side\n        } else {\n            lhs\n        }\n    }\n}\n\n/// Hash join semantics. Build = LHS (populates hash table), Probe = RHS (scanned).\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum HashJoinType {\n    /// Only matching rows emitted.\n    Inner,\n    /// All build rows appear; unmatched build rows get NULLs for the probe side.\n    LeftOuter,\n    /// Like LeftOuter, plus unmatched probe rows get NULLs for the build side.\n    FullOuter,\n}\n\n/// Hash join operation metadata\n#[derive(Debug, Clone)]\npub struct HashJoinOp {\n    /// Index of the build table in the join order\n    pub build_table_idx: usize,\n    /// Index of the probe table in the join order\n    pub probe_table_idx: usize,\n    /// Join key references, each entry points to an equality condition in the [WhereTerm]\n    /// and indicates which side of the equality belongs to the build table.\n    pub join_keys: Vec<HashJoinKey>,\n    /// Memory budget for hash table\n    pub mem_budget: usize,\n    /// Whether the build input should be materialized as a rowid list before hash build.\n    pub materialize_build_input: bool,\n    /// Whether to use a bloom filter on the probe side.\n    pub use_bloom_filter: bool,\n    /// Join semantics (inner, left outer, or full outer).\n    pub join_type: HashJoinType,\n}\n\n/// Distinguishes union (OR) from intersection (AND) operations for multi-index scans.\n#[derive(Debug, Clone, PartialEq, Eq)]\npub enum SetOperation {\n    /// Union: rowid appears in result if it's in ANY branch (OR)\n    Union,\n    /// Intersection: rowid appears in result only if it's in ALL branches (AND).\n    /// Carries the indices of additional WHERE terms consumed beyond the primary one.\n    Intersection {\n        additional_consumed_terms: Vec<usize>,\n    },\n}\n\n/// Multi-index scan operation metadata for OR-by-union or AND-by-intersection optimization.\n///\n/// When a WHERE clause contains an OR of terms that can each use a different index,\n/// we can scan each index separately and combine the results using a RowSet for deduplication.\n/// For example: `WHERE a = 1 OR b = 2` with indexes on `a` and `b`.\n///\n/// Similarly, when a WHERE clause contains AND terms on different indexed columns,\n/// we can scan each index and intersect the results to reduce the number of table fetches.\n/// For example: `WHERE a = 1 AND b = 2` with separate indexes on `a` and `b`.\n#[derive(Debug, Clone)]\npub struct MultiIndexScanOp {\n    /// Each branch represents one term with its own index access\n    pub branches: Vec<MultiIndexBranch>,\n    /// Index of the primary WHERE term.\n    /// For Union: the index of the OR expression.\n    /// For Intersection: the index of the first AND term consumed.\n    pub where_term_idx: usize,\n    /// The set operation to perform when combining branches\n    pub set_op: SetOperation,\n}\n\n/// Residual filters that apply only to union (OR) branches.\n///\n/// Each OR disjunct may be a compound expression (e.g. `a = 1 AND c > 5`), so\n/// after the index seek satisfies the indexable part, these residuals filter\n/// the remaining conditions.\n#[derive(Debug, Clone)]\npub struct UnionBranchPrePostFilters {\n    /// Outer-table-only residuals evaluated before the branch's index seek.\n    /// These reference only tables from earlier (outer) loops, so they can\n    /// short-circuit the entire branch without touching the index.\n    pub pre_filter_exprs: Vec<ast::Expr>,\n    /// Residual filter expressions that could not be satisfied by the index seek.\n    /// Applied within the branch loop after positioning on the table row.\n    pub post_filter_exprs: Vec<ast::Expr>,\n    /// Whether residual evaluation needs the scanned table cursor positioned.\n    pub requires_table_cursor: bool,\n}\n\n/// A single branch of a multi-index scan, representing one disjunct of an OR expression.\n#[derive(Debug, Clone)]\npub struct MultiIndexBranch {\n    /// The index to use for this branch, or None for rowid access\n    pub index: Option<Arc<Index>>,\n    /// How this branch probes the table/index.\n    pub access: MultiIndexBranchAccess,\n    /// Estimated number of rows from this branch\n    pub estimated_rows: f64,\n    /// Residual filters for union (OR) branches. `None` for intersection branches.\n    pub union_residuals: Option<UnionBranchPrePostFilters>,\n}\n\n/// Access shape for a single multi-index branch.\n#[derive(Debug, Clone)]\n#[expect(clippy::large_enum_variant)]\npub enum MultiIndexBranchAccess {\n    /// Ordinary seek/range scan on either the rowid btree or a secondary index.\n    Seek { seek_def: SeekDef },\n    /// Repeated equality seeks driven by an IN-list or IN-subquery RHS.\n    InSeek { source: InSeekSource },\n}\n\n#[derive(Clone, Debug)]\n#[allow(clippy::large_enum_variant)]\npub enum Operation {\n    // Scan operation\n    // This operation is used to scan a table.\n    Scan(Scan),\n    // Search operation\n    // This operation is used to search for a row in a table using an index\n    // (i.e. a primary key or a secondary index)\n    Search(Search),\n    // Access through custom index method query\n    IndexMethodQuery(IndexMethodQuery),\n    // Hash join operation\n    // This operation is used on the probe side of a hash join.\n    // The build table is accessed normally (via Scan), and the probe table\n    // uses this operation to indicate it should probe the hash table.\n    HashJoin(HashJoinOp),\n    // Multi-index scan operation for OR-by-union optimization.\n    // This operation scans multiple indexes (one per OR branch) and combines\n    // results using RowSet deduplication.\n    MultiIndexScan(MultiIndexScanOp),\n}\n\nimpl Operation {\n    pub fn default_scan_for(table: &Table) -> Self {\n        match table {\n            Table::BTree(_) => Operation::Scan(Scan::BTreeTable {\n                iter_dir: IterationDirection::Forwards,\n                index: None,\n            }),\n            Table::Virtual(_) => Operation::Scan(Scan::VirtualTable {\n                idx_num: -1,\n                idx_str: None,\n                constraints: Vec::new(),\n            }),\n            Table::FromClauseSubquery(_) => Operation::Scan(Scan::Subquery {\n                iter_dir: IterationDirection::Forwards,\n            }),\n        }\n    }\n\n    pub fn index(&self) -> Option<&Arc<Index>> {\n        match self {\n            Operation::Scan(Scan::BTreeTable { index, .. }) => index.as_ref(),\n            Operation::Search(Search::Seek { index, .. })\n            | Operation::Search(Search::InSeek { index, .. }) => index.as_ref(),\n            Operation::IndexMethodQuery(IndexMethodQuery { index, .. }) => Some(index),\n            Operation::Scan(_) => None,\n            Operation::Search(Search::RowidEq { .. }) => None,\n            Operation::HashJoin(_) => None,\n            // Multi-index scan uses multiple indexes; return None as there's no single index\n            Operation::MultiIndexScan(_) => None,\n        }\n    }\n\n    /// Returns true if this operation is guaranteed to access at most one row.\n    /// Used to determine whether UPDATE/DELETE is single-write.\n    ///\n    /// Conservative: returns false when unsure (e.g. table scans, range seeks,\n    /// non-unique index seeks).\n    pub fn affects_max_1_row(&self) -> bool {\n        match self {\n            // RowidEq is always a single-row point lookup.\n            Operation::Search(Search::RowidEq { .. }) => true,\n            // Seek on a unique index with all columns equality-constrained.\n            Operation::Search(Search::Seek { index, seek_def }) => {\n                let Some(idx) = index else {\n                    // Seek on rowid (no index): check if the seek is an equality\n                    // point lookup. This happens when prefix has one eq constraint\n                    // and no range component.\n                    return seek_def.prefix.len() == 1\n                        && seek_def.prefix[0].eq.is_some()\n                        && matches!(seek_def.start.last_component, SeekKeyComponent::None);\n                };\n                if !idx.unique {\n                    return false;\n                }\n                // All index columns must have equality constraints.\n                let num_index_cols = idx.columns.len();\n                let num_eq_prefix = seek_def.prefix.iter().filter(|c| c.eq.is_some()).count();\n                num_eq_prefix == num_index_cols\n            }\n            // Table scans, hash joins, multi-index scans, etc. are not single-row.\n            _ => false,\n        }\n    }\n}\n\nimpl JoinedTable {\n    /// Returns the btree table for this table reference, if it is a BTreeTable.\n    pub fn btree(&self) -> Option<Arc<BTreeTable>> {\n        match &self.table {\n            Table::BTree(_) => self.table.btree(),\n            _ => None,\n        }\n    }\n    pub fn virtual_table(&self) -> Option<Arc<VirtualTable>> {\n        match &self.table {\n            Table::Virtual(_) => self.table.virtual_table(),\n            _ => None,\n        }\n    }\n\n    fn matches(&self, database_id: usize, table_name: &str) -> bool {\n        self.database_id == database_id\n            && matches!(self.table, Table::BTree(_) | Table::Virtual(_))\n            && self.table.get_name().eq_ignore_ascii_case(table_name)\n    }\n\n    /// Creates a new TableReference for a subquery from a SelectPlan.\n    pub fn new_subquery(\n        identifier: String,\n        plan: SelectPlan,\n        join_info: Option<JoinInfo>,\n        internal_id: TableInternalId,\n    ) -> Result<Self> {\n        let mut columns = plan\n            .result_columns\n            .iter()\n            .map(|rc| {\n                let (col_type, type_name) =\n                    infer_type_from_expr(&rc.expr, Some(&plan.table_references));\n                Column::new(\n                    rc.name(&plan.table_references).map(String::from),\n                    type_name.to_string(),\n                    None,\n                    None,\n                    col_type,\n                    None,\n                    ColDef::default(),\n                )\n            })\n            .collect::<Vec<_>>();\n\n        for (i, column) in columns.iter_mut().enumerate() {\n            if super::expr::expr_is_array(\n                &plan.result_columns[i].expr,\n                Some(&plan.table_references),\n            ) {\n                column.set_array_dimensions(1);\n            }\n            column.set_collation(get_collseq_from_expr(\n                &plan.result_columns[i].expr,\n                &plan.table_references,\n            )?);\n        }\n\n        let table = Table::FromClauseSubquery(Arc::new(FromClauseSubquery {\n            name: identifier.clone(),\n            plan: Box::new(Plan::Select(plan)),\n            columns,\n            result_columns_start_reg: None,\n            materialized_cursor_id: None,\n            cte: None,\n        }));\n        Ok(Self {\n            op: Operation::default_scan_for(&table),\n            table,\n            identifier,\n            internal_id,\n            join_info,\n            col_used_mask: ColumnUsedMask::default(),\n            column_use_counts: Vec::new(),\n            expression_index_usages: Vec::new(),\n            database_id: 0,\n            indexed: None,\n        })\n    }\n\n    /// Creates a new TableReference for a subquery from a Plan (either SelectPlan or CompoundSelect).\n    /// If `explicit_columns` is provided, those names override the derived column names from the SELECT.\n    /// If `cte_id` is provided, this subquery is a CTE reference that can share materialized data.\n    /// If `materialize_hint` is true, the CTE was declared with AS MATERIALIZED and should always\n    /// be materialized regardless of reference count.\n    pub fn new_subquery_from_plan(\n        identifier: String,\n        plan: Plan,\n        join_info: Option<JoinInfo>,\n        internal_id: TableInternalId,\n        explicit_columns: Option<&[String]>,\n        cte_id: Option<usize>,\n        materialize_hint: bool,\n    ) -> Result<Self> {\n        // Get result columns and table references from the plan\n        let (result_columns, table_references) = match &plan {\n            Plan::Select(select_plan) => {\n                (&select_plan.result_columns, &select_plan.table_references)\n            }\n            Plan::CompoundSelect {\n                left, right_most, ..\n            } => {\n                // For compound selects, SQLite uses the leftmost select's column names.\n                // The leftmost select is left[0] if the vec is not empty, otherwise right_most.\n                if !left.is_empty() {\n                    (&left[0].0.result_columns, &left[0].0.table_references)\n                } else {\n                    (&right_most.result_columns, &right_most.table_references)\n                }\n            }\n            Plan::Delete(_) | Plan::Update(_) => {\n                unreachable!(\"DELETE/UPDATE plans cannot be subqueries\")\n            }\n        };\n\n        // Note: column count validation (explicit_columns.len() vs result_columns.len())\n        // is intentionally NOT done here. SQLite defers this check until the CTE is\n        // actually referenced. Callers that represent actual CTE references should\n        // validate the count before calling this method.\n\n        let mut columns = result_columns\n            .iter()\n            .enumerate()\n            .map(|(i, rc)| {\n                // Use explicit column name if provided, otherwise derive from result column\n                let col_name = explicit_columns\n                    .and_then(|cols| cols.get(i).cloned())\n                    .or_else(|| rc.name(table_references).map(String::from));\n                let (col_type, type_name) = infer_type_from_expr(&rc.expr, Some(table_references));\n                Column::new(\n                    col_name,\n                    type_name.to_string(),\n                    None,\n                    None,\n                    col_type,\n                    None,\n                    ColDef::default(),\n                )\n            })\n            .collect::<Vec<_>>();\n\n        for (i, column) in columns.iter_mut().enumerate() {\n            if super::expr::expr_is_array(&result_columns[i].expr, Some(table_references)) {\n                column.set_array_dimensions(1);\n            }\n            column.set_collation(get_collseq_from_expr(\n                &result_columns[i].expr,\n                table_references,\n            )?);\n        }\n\n        // materialize_hint is set true for explicit WITH ... AS MATERIALIZED hint.\n        // Multi-reference CTEs are also detected at emission time via reference counting,\n        // and they may be materialized regardless of explicit keyword usage.\n        let cte = cte_id.map(|id| crate::schema::FromClauseSubqueryCteMetadata {\n            id,\n            shared_materialization: false,\n            materialize_hint,\n        });\n        let table = Table::FromClauseSubquery(Arc::new(FromClauseSubquery {\n            name: identifier.clone(),\n            plan: Box::new(plan),\n            columns,\n            result_columns_start_reg: None,\n            materialized_cursor_id: None,\n            cte,\n        }));\n        Ok(Self {\n            op: Operation::default_scan_for(&table),\n            table,\n            identifier,\n            internal_id,\n            join_info,\n            col_used_mask: ColumnUsedMask::default(),\n            column_use_counts: Vec::new(),\n            expression_index_usages: Vec::new(),\n            database_id: 0,\n            indexed: None,\n        })\n    }\n\n    pub fn columns(&self) -> &[Column] {\n        self.table.columns()\n    }\n\n    /// Mark a column as used in the query.\n    /// This is used to determine whether a covering index can be used.\n    pub fn mark_column_used(&mut self, index: usize) {\n        if index >= self.column_use_counts.len() {\n            self.column_use_counts.resize(index + 1, 0);\n        }\n        self.column_use_counts[index] += 1;\n        self.col_used_mask.set(index);\n    }\n\n    /// Clear any previously registered expression index usages.\n    pub fn clear_expression_index_usages(&mut self) {\n        self.expression_index_usages.clear();\n    }\n\n    /// Example: SELECT a+b FROM t WHERE a+b=5 with INDEX ON t(a+b)\n    /// We want to remember that (a+b) is available on an index key and that\n    /// columns a and b are only needed to produce that expression. Later we\n    /// can avoid opening the table cursor if all column references are\n    /// covered by expression keys.\n    pub fn register_expression_index_usage(\n        &mut self,\n        normalized_expr: ast::Expr,\n        columns_mask: ColumnUsedMask,\n    ) {\n        if columns_mask.is_empty() {\n            return;\n        }\n        if self\n            .expression_index_usages\n            .iter()\n            .any(|usage| exprs_are_equivalent(&usage.normalized_expr, &normalized_expr))\n        {\n            return;\n        }\n        self.expression_index_usages.push(ExpressionIndexUsage {\n            normalized_expr: Box::new(normalized_expr),\n            columns_mask,\n        });\n    }\n\n    /// Provided an index that may contain expression keys, remove any\n    /// columns from `required_columns` that are fully covered by expression index values.\n    fn apply_expression_index_coverage(\n        &self,\n        index: &Index,\n        required_columns: &mut ColumnUsedMask,\n    ) {\n        let mut coverage_counts = vec![0usize; self.column_use_counts.len()];\n        let mut any_covered = false;\n        for usage in &self.expression_index_usages {\n            // If the index stores the expression (e.g. idx on lower(name)), all\n            // columns needed *solely* for that expression can be treated as\n            // covered by the index key. Example:\n            //   CREATE INDEX idx ON t(lower(name));\n            //   SELECT lower(name) FROM t;\n            // Column `name` is not otherwise needed, so we can rely on the\n            // expression value from the index and drop the table cursor.\n            if index\n                .expression_to_index_pos(&usage.normalized_expr)\n                .is_some()\n            {\n                any_covered = true;\n                for col_idx in usage.columns_mask.iter() {\n                    if col_idx >= coverage_counts.len() {\n                        coverage_counts.resize(col_idx + 1, 0);\n                    }\n                    coverage_counts[col_idx] += 1;\n                }\n            }\n        }\n        if !any_covered {\n            return;\n        }\n        for (col_idx, &covered) in coverage_counts.iter().enumerate() {\n            if covered == 0 {\n                continue;\n            }\n            // Only drop the requirement if *all* references to this column are\n            // satisfied by expression-index values. If the column is also\n            // selected or filtered directly, the table data is still needed.\n            if self.column_use_counts.get(col_idx).copied().unwrap_or(0) == covered {\n                required_columns.clear(col_idx);\n            }\n        }\n    }\n\n    /// Open the necessary cursors for this table reference.\n    /// Generally a table cursor is always opened unless a SELECT query can use a covering index.\n    /// An index cursor is opened if an index is used in any way for reading data from the table.\n    pub fn open_cursors(\n        &self,\n        program: &mut ProgramBuilder,\n        mode: OperationMode,\n        schema: &Schema,\n    ) -> Result<(Option<CursorID>, Option<CursorID>)> {\n        let index = self.op.index();\n        match &self.table {\n            Table::BTree(btree) => {\n                let use_covering_index = self.utilizes_covering_index();\n                let index_is_ephemeral = index.is_some_and(|index| index.ephemeral);\n                let table_not_required = matches!(mode, OperationMode::SELECT)\n                    && use_covering_index\n                    && !index_is_ephemeral;\n                let table_cursor_id = if table_not_required {\n                    None\n                } else if let OperationMode::UPDATE(UpdateRowSource::PrebuiltEphemeralTable {\n                    target_table,\n                    ..\n                }) = &mode\n                {\n                    // The cursor for the ephemeral table was already allocated earlier. Let's allocate one for the target table,\n                    // in case it wasn't already allocated when populating the ephemeral table.\n                    Some(program.alloc_cursor_id_keyed_if_not_exists(\n                        CursorKey::table(target_table.internal_id),\n                        match &target_table.table {\n                            Table::BTree(btree) => CursorType::BTreeTable(btree.clone()),\n                            Table::Virtual(virtual_table) => {\n                                CursorType::VirtualTable(virtual_table.clone())\n                            }\n                            _ => unreachable!(\"target table must be a btree or virtual table\"),\n                        },\n                    ))\n                } else {\n                    // Check if this is a materialized view\n                    let cursor_type =\n                        if let Some(view_mutex) = schema.get_materialized_view(&btree.name) {\n                            CursorType::MaterializedView(btree.clone(), view_mutex)\n                        } else {\n                            CursorType::BTreeTable(btree.clone())\n                        };\n                    Some(program.alloc_cursor_id_keyed_if_not_exists(\n                        CursorKey::table(self.internal_id),\n                        cursor_type,\n                    ))\n                };\n\n                let index_cursor_id = index\n                    .map(|index| {\n                        program.alloc_cursor_index_if_not_exists(\n                            CursorKey::index(self.internal_id, index.clone()),\n                            index,\n                        )\n                    })\n                    .transpose()?;\n                Ok((table_cursor_id, index_cursor_id))\n            }\n            Table::Virtual(virtual_table) => {\n                let table_cursor_id = Some(program.alloc_cursor_id_keyed(\n                    CursorKey::table(self.internal_id),\n                    CursorType::VirtualTable(virtual_table.clone()),\n                ));\n                let index_cursor_id = None;\n                Ok((table_cursor_id, index_cursor_id))\n            }\n            Table::FromClauseSubquery(..) => {\n                let index_cursor_id = index\n                    .map(|index| {\n                        program.alloc_cursor_index_if_not_exists(\n                            CursorKey::index(self.internal_id, index.clone()),\n                            index,\n                        )\n                    })\n                    .transpose()?;\n                Ok((None, index_cursor_id))\n            }\n        }\n    }\n\n    /// Resolve the already opened cursors for this table reference.\n    pub fn resolve_cursors(\n        &self,\n        program: &mut ProgramBuilder,\n        mode: OperationMode,\n    ) -> Result<(Option<CursorID>, Option<CursorID>)> {\n        let index = self.op.index();\n        let table_cursor_id = if let Table::FromClauseSubquery(from_clause_subquery) = &self.table {\n            from_clause_subquery.materialized_cursor_id\n        } else if let OperationMode::UPDATE(UpdateRowSource::PrebuiltEphemeralTable {\n            target_table,\n            ..\n        }) = &mode\n        {\n            program.resolve_cursor_id_safe(&CursorKey::table(target_table.internal_id))\n        } else {\n            program.resolve_cursor_id_safe(&CursorKey::table(self.internal_id))\n        };\n        let index_cursor_id = index.map(|index| {\n            program.resolve_cursor_id(&CursorKey::index(self.internal_id, index.clone()))\n        });\n        Ok((table_cursor_id, index_cursor_id))\n    }\n\n    /// Returns true if a given index is a covering index for this [TableReference].\n    pub fn index_is_covering(&self, index: &Index) -> bool {\n        let Table::BTree(btree) = &self.table else {\n            return false;\n        };\n        if self.col_used_mask.is_empty() {\n            return false;\n        }\n        if index.index_method.is_some() {\n            return false;\n        }\n\n        if self.expression_index_usages.is_empty() {\n            Self::index_covers_columns(index, btree, &self.col_used_mask)\n        } else {\n            let mut required_columns = self.col_used_mask.clone();\n            self.apply_expression_index_coverage(index, &mut required_columns);\n            if required_columns.is_empty() {\n                return true;\n            }\n            Self::index_covers_columns(index, btree, &required_columns)\n        }\n    }\n\n    fn index_covers_columns(\n        index: &Index,\n        btree: &BTreeTable,\n        required_columns: &ColumnUsedMask,\n    ) -> bool {\n        // If a table has a rowid, the index is guaranteed to contain it as well.\n        let rowid_alias_pos = if btree.has_rowid {\n            btree.get_rowid_alias_column().map(|(pos, _)| pos)\n        } else {\n            None\n        };\n\n        if let Some(pos) = rowid_alias_pos {\n            if required_columns.is_only(pos) {\n                // If the index would be ONLY used for the rowid, don't bother.\n                // Example: SELECT id FROM t where id is a rowid alias - just scan the table.\n                return false;\n            }\n        }\n\n        // Check that every required column is covered by the index\n        for required_col in required_columns.iter() {\n            if rowid_alias_pos == Some(required_col) {\n                // rowid is always implicitly covered by the index\n                continue;\n            }\n            let covered_by_index = index\n                .columns\n                .iter()\n                .any(|c| c.expr.is_none() && c.pos_in_table == required_col);\n            if !covered_by_index {\n                return false;\n            }\n        }\n        true\n    }\n\n    /// Returns true if the index selected for use with this [TableReference] is a covering index,\n    /// meaning that it contains all the columns that are referenced in the query.\n    pub fn utilizes_covering_index(&self) -> bool {\n        let Some(index) = self.op.index() else {\n            return false;\n        };\n        self.index_is_covering(index.as_ref())\n    }\n\n    pub fn column_is_used(&self, index: usize) -> bool {\n        self.col_used_mask.get(index)\n    }\n}\n\n/// A definition of a rowid/index search.\n///\n/// [SeekKey] is the condition that is used to seek to a specific row in a table/index.\n/// [SeekKey] also used to represent range scan termination condition.\n#[derive(Debug, Clone)]\npub struct SeekDef {\n    /// Common prefix of the key which is shared between start/end fields\n    /// For example, given:\n    /// - CREATE INDEX i ON t (x, y desc)\n    /// - SELECT * FROM t WHERE x = 1 AND y >= 30\n    ///\n    /// Then, prefix=[(eq=1, ASC)], start=Some((ge, Expr(30))), end=Some((gt, Sentinel))\n    pub prefix: Vec<SeekRangeConstraint>,\n    /// The condition to use when seeking. See [SeekKey] for more details.\n    pub start: SeekKey,\n    /// The condition to use when terminating the scan that follows the seek. See [SeekKey] for more details.\n    pub end: SeekKey,\n    /// The direction of the scan that follows the seek.\n    pub iter_dir: IterationDirection,\n}\n\npub struct SeekDefKeyIterator<'a, T> {\n    seek_def: &'a SeekDef,\n    seek_key: &'a SeekKey,\n    pos: usize,\n    _t: PhantomData<T>,\n}\n\nimpl<'a> Iterator for SeekDefKeyIterator<'a, SeekKeyComponent<&'a ast::Expr>> {\n    type Item = SeekKeyComponent<&'a ast::Expr>;\n\n    fn next(&mut self) -> Option<Self::Item> {\n        let result = if self.pos < self.seek_def.prefix.len() {\n            Some(SeekKeyComponent::Expr(\n                &self.seek_def.prefix[self.pos].eq.as_ref().unwrap().1,\n            ))\n        } else if self.pos == self.seek_def.prefix.len() {\n            match &self.seek_key.last_component {\n                SeekKeyComponent::Expr(expr) => Some(SeekKeyComponent::Expr(expr)),\n                SeekKeyComponent::Null => Some(SeekKeyComponent::Null),\n                SeekKeyComponent::None => None,\n            }\n        } else {\n            None\n        };\n        self.pos += 1;\n        result\n    }\n}\n\nimpl<'a> Iterator for SeekDefKeyIterator<'a, Affinity> {\n    type Item = Affinity;\n\n    fn next(&mut self) -> Option<Self::Item> {\n        let result = if self.pos < self.seek_def.prefix.len() {\n            Some(self.seek_def.prefix[self.pos].eq.as_ref().unwrap().2)\n        } else if self.pos == self.seek_def.prefix.len() {\n            match &self.seek_key.last_component {\n                SeekKeyComponent::Expr(..) => Some(self.seek_key.affinity),\n                // NULL sentinel does not require conversion; use NONE affinity so width matches.\n                SeekKeyComponent::Null => Some(Affinity::Blob),\n                SeekKeyComponent::None => None,\n            }\n        } else {\n            None\n        };\n        self.pos += 1;\n        result\n    }\n}\n\nimpl SeekDef {\n    /// returns amount of values in the given seek key\n    /// - so, for SELECT * FROM t WHERE x = 10 AND y = 20 AND y >= 30 there will be 3 values (10, 20, 30)\n    pub fn size(&self, key: &SeekKey) -> usize {\n        self.prefix.len()\n            + match key.last_component {\n                SeekKeyComponent::Expr(_) => 1,\n                SeekKeyComponent::Null => 1,\n                SeekKeyComponent::None => 0,\n            }\n    }\n    /// iterate over value expressions in the given seek key\n    pub fn iter<'a>(\n        &'a self,\n        key: &'a SeekKey,\n    ) -> SeekDefKeyIterator<'a, SeekKeyComponent<&'a ast::Expr>> {\n        SeekDefKeyIterator {\n            seek_def: self,\n            seek_key: key,\n            pos: 0,\n            _t: PhantomData,\n        }\n    }\n\n    /// iterate over affinity in the given seek key\n    pub fn iter_affinity<'a>(&'a self, key: &'a SeekKey) -> SeekDefKeyIterator<'a, Affinity> {\n        SeekDefKeyIterator {\n            seek_def: self,\n            seek_key: key,\n            pos: 0,\n            _t: PhantomData,\n        }\n    }\n}\n\n/// Build the affinity string for a synthesized ephemeral seek index.\n///\n/// The seek key only constrains the leading key prefix, but the backing record\n/// stored in the ephemeral index still includes the remaining payload columns\n/// (and possibly a synthetic rowid). Pad those trailing slots with NONE affinity\n/// so MakeRecord sees the same layout the index insert path produced.\npub fn synthesized_seek_affinity_str(index: &Index, seek_def: &SeekDef) -> Option<Arc<String>> {\n    let num_key_cols = seek_def.size(&seek_def.start);\n    let total_cols = index.columns.len() + if index.has_rowid { 1 } else { 0 };\n    let mut aff: String = seek_def\n        .iter_affinity(&seek_def.start)\n        .map(|a| a.aff_mask())\n        .collect();\n    for _ in num_key_cols..total_cols {\n        aff.push(affinity::SQLITE_AFF_NONE);\n    }\n    aff.chars()\n        .any(|c| c != affinity::SQLITE_AFF_NONE)\n        .then(|| Arc::new(aff))\n}\n\n/// [SeekKeyComponent] represents the optional trailing component of a seek key.\n/// Besides user-provided expressions, planner logic may inject a synthetic NULL sentinel\n/// to encode SQLite-compatible boundary behavior on composite indexes.\n/// This enum accepts generic argument E so we can use both\n/// SeekKeyComponent<ast::Expr> and SeekKeyComponent<&ast::Expr>.\n#[derive(Debug, Clone)]\npub enum SeekKeyComponent<E> {\n    Expr(E),\n    Null,\n    None,\n}\n\n/// A condition to use when seeking.\n#[derive(Debug, Clone)]\npub struct SeekKey {\n    /// Complete key must be constructed from common [SeekDef::prefix] and optional last_component\n    pub last_component: SeekKeyComponent<ast::Expr>,\n\n    /// The comparison operator to use when seeking.\n    pub op: SeekOp,\n\n    /// Affinity of the comparison\n    pub affinity: Affinity,\n}\n\n/// Represents the type of table scan performed during query execution.\n#[derive(Clone, Debug)]\npub enum Scan {\n    /// A scan of a B-tree–backed table, optionally using an index, and with an iteration direction.\n    BTreeTable {\n        /// The iter_dir is used to indicate the direction of the iterator.\n        iter_dir: IterationDirection,\n        /// The index that we are using to scan the table, if any.\n        index: Option<Arc<Index>>,\n    },\n    /// A scan of a virtual table, delegated to the table’s `filter` and related methods.\n    VirtualTable {\n        /// Index identifier returned by the table's `best_index` method.\n        idx_num: i32,\n        /// Optional index name returned by the table’s `best_index` method.\n        idx_str: Option<String>,\n        /// Constraining expressions to be passed to the table’s `filter` method.\n        /// The order of expressions matches the argument order expected by the virtual table.\n        constraints: Vec<Expr>,\n    },\n    /// A scan of a subquery in the `FROM` clause.\n    Subquery {\n        /// Coroutine-backed scans run forwards. Materialized subqueries may\n        /// also be scanned backwards when the planner relies on intrinsic\n        /// subquery order for an extremum fast path.\n        iter_dir: IterationDirection,\n    },\n}\n\n/// An enum that represents a search operation that can be used to search for a row in a table using an index\n/// (i.e. a primary key or a secondary index)\n#[allow(clippy::large_enum_variant)]\n#[derive(Clone, Debug)]\npub enum Search {\n    /// A rowid equality point lookup. This is a special case that uses the SeekRowid bytecode instruction and does not loop.\n    RowidEq { cmp_expr: ast::Expr },\n    /// A search on a table btree (via `rowid`) or a secondary index search. Uses bytecode instructions like SeekGE, SeekGT etc.\n    Seek {\n        index: Option<Arc<Index>>,\n        seek_def: SeekDef,\n    },\n    /// An IN-driven index seek. Iterates an ephemeral B-tree of IN values and\n    /// for each value seeks into the real index (or table, if seek by rowid).\n    InSeek {\n        index: Option<Arc<Index>>,\n        source: InSeekSource,\n    },\n}\n\n/// Where IN-seek values come from.\n#[derive(Clone, Debug)]\npub enum InSeekSource {\n    /// Literal values to materialize into a new ephemeral index at open_loop time.\n    LiteralList {\n        values: Vec<ast::Expr>,\n        affinity: Affinity,\n    },\n    /// Subquery already materialized by emit_non_from_clause_subquery;\n    /// open_loop reuses the existing ephemeral cursor.\n    Subquery { cursor_id: CursorID },\n}\n\n#[allow(clippy::large_enum_variant)]\n#[derive(Clone, Debug)]\npub struct IndexMethodQuery {\n    /// index method to use\n    pub index: Arc<Index>,\n    /// idx of the pattern from [crate::index_method::IndexMethodAttachment::definition] which planner chose to use for the access\n    pub pattern_idx: usize,\n    /// captured arguments for the pattern chosen by the planner\n    pub arguments: Vec<Expr>,\n    /// mapping from index of [ast::Expr::Column] to the column index of IndexMethod response\n    pub covered_columns: HashMap<usize, usize>,\n}\n\n#[derive(Debug, Clone, PartialEq)]\npub struct Aggregate {\n    pub func: AggFunc,\n    pub args: Vec<ast::Expr>,\n    pub original_expr: ast::Expr,\n    pub distinctness: Distinctness,\n}\n\nimpl Aggregate {\n    pub fn new(func: AggFunc, args: &[Box<Expr>], expr: &Expr, distinctness: Distinctness) -> Self {\n        Aggregate {\n            func,\n            args: args.iter().map(|arg| *arg.clone()).collect(),\n            original_expr: expr.clone(),\n            distinctness,\n        }\n    }\n\n    pub fn is_distinct(&self) -> bool {\n        self.distinctness.is_distinct()\n    }\n}\n\n/// Represents the window definition and all window functions associated with a single SELECT.\n#[derive(Debug, Clone)]\npub struct Window {\n    /// The window name, either provided in the original statement or synthetically generated by\n    /// the planner. This is optional because it can be assigned at different stages of query\n    /// processing, but it should eventually always be set.\n    pub name: Option<String>,\n    /// Expressions from the PARTITION BY clause.\n    pub partition_by: Vec<Expr>,\n    /// The number of unique expressions in the PARTITION BY clause. This determines how many of\n    /// the leftmost columns in the subquery output make up the partition key.\n    pub deduplicated_partition_by_len: Option<usize>,\n    /// Expressions from the ORDER BY clause.\n    pub order_by: Vec<(Expr, SortOrder)>,\n    /// All window functions associated with this window.\n    pub functions: Vec<WindowFunction>,\n}\n\nimpl Window {\n    const DEFAULT_SORT_ORDER: SortOrder = SortOrder::Asc;\n\n    pub fn new(name: Option<String>, ast: &ast::Window) -> Result<Self> {\n        if !Self::is_default_frame_spec(&ast.frame_clause) {\n            crate::bail_parse_error!(\"Custom frame specifications are not supported yet\");\n        }\n\n        Ok(Window {\n            name,\n            partition_by: ast.partition_by.iter().map(|arg| *arg.clone()).collect(),\n            deduplicated_partition_by_len: None,\n            order_by: ast\n                .order_by\n                .iter()\n                .map(|col| {\n                    (\n                        *col.expr.clone(),\n                        col.order.unwrap_or(Self::DEFAULT_SORT_ORDER),\n                    )\n                })\n                .collect(),\n            functions: vec![],\n        })\n    }\n\n    pub fn is_equivalent(&self, ast: &ast::Window) -> bool {\n        if !Self::is_default_frame_spec(&ast.frame_clause) {\n            return false;\n        }\n\n        if self.partition_by.len() != ast.partition_by.len() {\n            return false;\n        }\n        if !self\n            .partition_by\n            .iter()\n            .zip(&ast.partition_by)\n            .all(|(a, b)| exprs_are_equivalent(a, b))\n        {\n            return false;\n        }\n\n        if self.order_by.len() != ast.order_by.len() {\n            return false;\n        }\n        self.order_by\n            .iter()\n            .zip(&ast.order_by)\n            .all(|((expr_a, order_a), col_b)| {\n                exprs_are_equivalent(expr_a, &col_b.expr)\n                    && *order_a == col_b.order.unwrap_or(Self::DEFAULT_SORT_ORDER)\n            })\n    }\n\n    fn is_default_frame_spec(frame: &Option<FrameClause>) -> bool {\n        if let Some(frame_clause) = frame {\n            let FrameClause {\n                mode,\n                start,\n                end,\n                exclude,\n            } = frame_clause;\n            if *mode != FrameMode::Range {\n                return false;\n            }\n            if *start != FrameBound::UnboundedPreceding {\n                return false;\n            }\n            if *end != Some(FrameBound::CurrentRow) {\n                return false;\n            }\n            if let Some(exclude) = exclude {\n                if *exclude != FrameExclude::NoOthers {\n                    return false;\n                }\n            }\n        }\n        true\n    }\n}\n\n#[derive(Debug, Clone)]\npub enum WindowFunctionKind {\n    Agg(AggFunc),\n    Window(WindowFunc),\n}\n\n#[derive(Debug, Clone)]\npub struct WindowFunction {\n    /// The resolved function. Aggregate window functions and specialized window\n    /// functions such as ROW_NUMBER() are supported.\n    pub func: WindowFunctionKind,\n    /// The expression from which the function was resolved.\n    pub original_expr: Expr,\n}\n\n#[derive(Debug, Clone)]\npub enum SubqueryState {\n    /// The subquery has not been evaluated yet.\n    /// The 'plan' field is only optional because it is .take()'d when the the subquery\n    /// is translated into bytecode.\n    Unevaluated { plan: Option<Box<SelectPlan>> },\n    /// The subquery has been evaluated.\n    /// The [evaluated_at] field contains the loop index where the subquery was evaluated.\n    /// The query plan struct no longer exists because translating the plan currently\n    /// requires an ownership transfer. We retain the outer table references so\n    /// later masking/evaluation logic can still reason about dependencies.\n    Evaluated {\n        /// Join-loop position where the subquery was emitted into bytecode.\n        evaluated_at: EvalAt,\n        /// Outer table ids referenced by the subquery when it was planned.\n        /// We keep these so later analysis can still understand dependencies\n        /// even after the plan is consumed.\n        outer_ref_ids: Vec<TableInternalId>,\n    },\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum SubqueryPosition {\n    ResultColumn,\n    Where,\n    GroupBy,\n    Having,\n    OrderBy,\n    LimitOffset,\n}\n\nimpl SubqueryPosition {\n    /// Returns true if a subquery in this position of the SELECT can be correlated, i.e. if it can reference columns from the outer query.\n    pub fn allow_correlated(&self) -> bool {\n        matches!(\n            self,\n            SubqueryPosition::ResultColumn\n                | SubqueryPosition::Where\n                | SubqueryPosition::GroupBy\n                | SubqueryPosition::OrderBy\n        )\n    }\n\n    pub fn name(&self) -> &'static str {\n        match self {\n            SubqueryPosition::ResultColumn => \"SELECT list\",\n            SubqueryPosition::Where => \"WHERE\",\n            SubqueryPosition::GroupBy => \"GROUP BY\",\n            SubqueryPosition::Having => \"HAVING\",\n            SubqueryPosition::OrderBy => \"ORDER BY\",\n            SubqueryPosition::LimitOffset => \"LIMIT/OFFSET\",\n        }\n    }\n}\n\n#[derive(Debug, Clone)]\n/// A subquery that is not part of the `FROM` clause.\n/// This is used for subqueries in the WHERE clause, HAVING clause, ORDER BY clause, LIMIT clause, OFFSET clause, etc.\n/// Currently only subqueries in the WHERE clause are supported.\npub struct NonFromClauseSubquery {\n    pub internal_id: TableInternalId,\n    pub query_type: SubqueryType,\n    pub state: SubqueryState,\n    pub correlated: bool,\n    pub origin: SubqueryOrigin,\n    pub eval_phase: SubqueryEvalPhase,\n}\n\nimpl NonFromClauseSubquery {\n    /// Returns true if the subquery has been evaluated (translated into bytecode).\n    pub fn has_been_evaluated(&self) -> bool {\n        matches!(self.state, SubqueryState::Evaluated { .. })\n    }\n\n    pub fn is_post_write_returning(&self) -> bool {\n        self.origin.is_post_write_returning()\n            && matches!(self.eval_phase, SubqueryEvalPhase::PostWriteReturning)\n    }\n\n    pub fn reads_table(&self, database_id: usize, table_name: &str) -> bool {\n        match &self.state {\n            SubqueryState::Unevaluated { plan: Some(plan) } => {\n                plan.reads_table(database_id, table_name)\n            }\n            _ => false,\n        }\n    }\n\n    /// Returns the loop index where the subquery should be evaluated in this join order.\n    ///\n    /// If the subquery references tables from the parent query, it is evaluated at\n    /// the right-most loop that makes those tables available. For hash joins, this\n    /// may map a build-table reference to the probe loop where its rows are produced.\n    pub fn get_eval_at(\n        &self,\n        join_order: &[JoinOrderMember],\n        table_references: Option<&TableReferences>,\n    ) -> Result<EvalAt> {\n        let plan = match &self.state {\n            SubqueryState::Unevaluated { plan } => plan.as_ref().unwrap(),\n            SubqueryState::Evaluated { evaluated_at, .. } => {\n                return Ok(*evaluated_at);\n            }\n        };\n        eval_at_for_select_plan(plan, join_order, table_references)\n    }\n\n    /// Consumes the plan and returns it, and sets the subquery to the evaluated state.\n    ///\n    /// This captures any outer references before the plan is moved so later\n    /// phases can still reason about dependencies.\n    pub fn consume_plan(&mut self, evaluated_at: EvalAt) -> Box<SelectPlan> {\n        match &mut self.state {\n            SubqueryState::Unevaluated { plan } => {\n                let outer_ref_ids = plan\n                    .as_ref()\n                    .map(|plan| {\n                        plan.table_references\n                            .outer_query_refs()\n                            .iter()\n                            .filter(|t| t.is_used())\n                            .map(|t| t.internal_id)\n                            .collect::<Vec<_>>()\n                    })\n                    .unwrap_or_default();\n                let plan = plan.take().unwrap();\n                self.state = SubqueryState::Evaluated {\n                    evaluated_at,\n                    outer_ref_ids,\n                };\n                plan\n            }\n            SubqueryState::Evaluated { .. } => {\n                panic!(\"subquery has already been evaluated\");\n            }\n        }\n    }\n}\n\n/// Determine the earliest evaluation point for a nested plan by walking all SELECT components.\nfn eval_at_for_plan(\n    plan: &Plan,\n    join_order: &[JoinOrderMember],\n    table_references: Option<&TableReferences>,\n) -> Result<EvalAt> {\n    match plan {\n        Plan::Select(select_plan) => {\n            eval_at_for_select_plan(select_plan, join_order, table_references)\n        }\n        Plan::CompoundSelect {\n            left, right_most, ..\n        } => {\n            let mut eval_at = EvalAt::BeforeLoop;\n            for (select_plan, _) in left.iter() {\n                eval_at = eval_at.max(eval_at_for_select_plan(\n                    select_plan,\n                    join_order,\n                    table_references,\n                )?);\n            }\n            eval_at = eval_at.max(eval_at_for_select_plan(\n                right_most,\n                join_order,\n                table_references,\n            )?);\n            Ok(eval_at)\n        }\n        Plan::Delete(_) | Plan::Update(_) => Ok(EvalAt::BeforeLoop),\n    }\n}\n\n/// Returns true if a plan (including compound SELECTs) references outer-scope tables.\npub fn plan_is_correlated(plan: &Plan) -> bool {\n    match plan {\n        Plan::Select(select_plan) => select_plan.is_correlated(),\n        Plan::CompoundSelect {\n            left, right_most, ..\n        } => left.iter().any(|(plan, _)| plan.is_correlated()) || right_most.is_correlated(),\n        Plan::Delete(_) | Plan::Update(_) => false,\n    }\n}\n\n/// Returns true when evaluating this plan depends on table values from an\n/// enclosing query scope outside the plan itself.\n///\n/// This is narrower than [`plan_is_correlated()`]: a plan may contain\n/// internally correlated scalar subqueries (for example, a scalar subquery that\n/// references another table in the same CTE) without depending on an enclosing\n/// query row. Those plans are still safe to materialize once and reuse.\npub fn plan_has_outer_scope_dependency(plan: &Plan) -> bool {\n    fn select_plan_has_outer_scope_dependency(\n        plan: &SelectPlan,\n        accessible_table_ids: &mut Vec<TableInternalId>,\n    ) -> bool {\n        let outer_scope_base_len = accessible_table_ids.len();\n        accessible_table_ids.extend(\n            plan.table_references\n                .joined_tables()\n                .iter()\n                .map(|table| table.internal_id),\n        );\n\n        let has_outer_scope_dependency =\n            plan.table_references\n                .outer_query_refs()\n                .iter()\n                .any(|outer_ref| {\n                    outer_ref.is_used() && !accessible_table_ids.contains(&outer_ref.internal_id)\n                })\n                || plan\n                    .non_from_clause_subqueries\n                    .iter()\n                    .any(|subquery| match &subquery.state {\n                        SubqueryState::Unevaluated {\n                            plan: Some(subquery_plan),\n                        } => select_plan_has_outer_scope_dependency(\n                            subquery_plan,\n                            accessible_table_ids,\n                        ),\n                        SubqueryState::Unevaluated { plan: None } => false,\n                        SubqueryState::Evaluated { outer_ref_ids, .. } => outer_ref_ids\n                            .iter()\n                            .any(|outer_ref_id| !accessible_table_ids.contains(outer_ref_id)),\n                    })\n                || plan\n                    .table_references\n                    .joined_tables()\n                    .iter()\n                    .any(|table| match &table.table {\n                        Table::FromClauseSubquery(subquery) => {\n                            plan_has_outer_scope_dependency_with_tables(\n                                subquery.plan.as_ref(),\n                                accessible_table_ids,\n                            )\n                        }\n                        _ => false,\n                    });\n\n        accessible_table_ids.truncate(outer_scope_base_len);\n        has_outer_scope_dependency\n    }\n\n    fn plan_has_outer_scope_dependency_with_tables(\n        plan: &Plan,\n        accessible_table_ids: &mut Vec<TableInternalId>,\n    ) -> bool {\n        match plan {\n            Plan::Select(select_plan) => {\n                select_plan_has_outer_scope_dependency(select_plan, accessible_table_ids)\n            }\n            Plan::CompoundSelect {\n                left, right_most, ..\n            } => {\n                left.iter().any(|(select_plan, _)| {\n                    select_plan_has_outer_scope_dependency(select_plan, accessible_table_ids)\n                }) || select_plan_has_outer_scope_dependency(right_most, accessible_table_ids)\n            }\n            Plan::Delete(_) | Plan::Update(_) => false,\n        }\n    }\n\n    plan_has_outer_scope_dependency_with_tables(plan, &mut Vec::new())\n}\n\n/// Determine when a SELECT plan can be evaluated, including nested non-FROM and FROM-clause subqueries.\nfn eval_at_for_select_plan(\n    plan: &SelectPlan,\n    join_order: &[JoinOrderMember],\n    table_references: Option<&TableReferences>,\n) -> Result<EvalAt> {\n    let mut eval_at = EvalAt::BeforeLoop;\n    let used_outer_refs = plan\n        .table_references\n        .outer_query_refs()\n        .iter()\n        .filter(|t| t.is_used());\n\n    for outer_ref in used_outer_refs {\n        if let Some(loop_idx) =\n            resolve_outer_ref_loop(outer_ref.internal_id, join_order, table_references)\n        {\n            eval_at = eval_at.max(EvalAt::Loop(loop_idx));\n        }\n    }\n    for subquery in plan.non_from_clause_subqueries.iter() {\n        let eval_at_inner = subquery.get_eval_at(join_order, table_references)?;\n        eval_at = eval_at.max(eval_at_inner);\n    }\n    for joined_table in plan.table_references.joined_tables().iter() {\n        if let Table::FromClauseSubquery(from_clause_subquery) = &joined_table.table {\n            eval_at = eval_at.max(eval_at_for_plan(\n                from_clause_subquery.plan.as_ref(),\n                join_order,\n                table_references,\n            )?);\n        }\n    }\n    Ok(eval_at)\n}\n\n/// Resolves the loop index for an outer-table reference.\n///\n/// If the table is not present in the join order, we look for a hash join\n/// where that table is the build side and map it to the probe loop.\nfn resolve_outer_ref_loop(\n    table_id: TableInternalId,\n    join_order: &[JoinOrderMember],\n    table_references: Option<&TableReferences>,\n) -> Option<usize> {\n    if let Some(loop_idx) = join_order.iter().position(|t| t.table_id == table_id) {\n        return Some(loop_idx);\n    }\n    let tables = table_references?;\n    for (probe_idx, member) in join_order.iter().enumerate() {\n        let probe_table = &tables.joined_tables()[member.original_idx];\n        if let Operation::HashJoin(ref hj) = probe_table.op {\n            let build_table = &tables.joined_tables()[hj.build_table_idx];\n            if build_table.internal_id == table_id {\n                return Some(probe_idx);\n            }\n        }\n    }\n    None\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use rand_chacha::{\n        rand_core::{RngCore, SeedableRng},\n        ChaCha8Rng,\n    };\n\n    #[test]\n    fn test_column_used_mask_empty() {\n        let mask = ColumnUsedMask::default();\n        assert!(mask.is_empty());\n\n        let mut mask2 = ColumnUsedMask::default();\n        mask2.set(0);\n        assert!(!mask2.is_empty());\n    }\n\n    #[test]\n    fn test_column_used_mask_set_and_get() {\n        let mut mask = ColumnUsedMask::default();\n\n        let max_columns = 10000;\n        let mut set_indices = Vec::new();\n        let mut rng = ChaCha8Rng::seed_from_u64(\n            std::time::SystemTime::now()\n                .duration_since(std::time::UNIX_EPOCH)\n                .unwrap()\n                .as_secs(),\n        );\n\n        for i in 0..max_columns {\n            if rng.next_u32() % 3 == 0 {\n                set_indices.push(i);\n                mask.set(i);\n            }\n        }\n\n        // Verify set bits are present\n        for &i in &set_indices {\n            assert!(mask.get(i), \"Expected bit {i} to be set\");\n        }\n\n        // Verify unset bits are not present\n        for i in 0..max_columns {\n            if !set_indices.contains(&i) {\n                assert!(!mask.get(i), \"Expected bit {i} to not be set\");\n            }\n        }\n    }\n\n    #[test]\n    fn test_column_used_mask_subset_relationship() {\n        let mut full_mask = ColumnUsedMask::default();\n        let mut subset_mask = ColumnUsedMask::default();\n\n        let max_columns = 5000;\n        let mut rng = ChaCha8Rng::seed_from_u64(\n            std::time::SystemTime::now()\n                .duration_since(std::time::UNIX_EPOCH)\n                .unwrap()\n                .as_secs(),\n        );\n\n        // Create a pattern where subset has fewer bits\n        for i in 0..max_columns {\n            if rng.next_u32() % 5 == 0 {\n                full_mask.set(i);\n                if i % 2 == 0 {\n                    subset_mask.set(i);\n                }\n            }\n        }\n\n        // full_mask contains all bits of subset_mask\n        assert!(full_mask.contains_all_set_bits_of(&subset_mask));\n\n        // subset_mask does not contain all bits of full_mask\n        assert!(!subset_mask.contains_all_set_bits_of(&full_mask));\n\n        // A mask contains itself\n        assert!(full_mask.contains_all_set_bits_of(&full_mask));\n        assert!(subset_mask.contains_all_set_bits_of(&subset_mask));\n    }\n\n    #[test]\n    fn test_column_used_mask_empty_subset() {\n        let mut mask = ColumnUsedMask::default();\n        for i in (0..1000).step_by(7) {\n            mask.set(i);\n        }\n\n        let empty_mask = ColumnUsedMask::default();\n\n        // Empty mask is subset of everything\n        assert!(mask.contains_all_set_bits_of(&empty_mask));\n        assert!(empty_mask.contains_all_set_bits_of(&empty_mask));\n    }\n\n    #[test]\n    fn test_column_used_mask_sparse_indices() {\n        let mut sparse_mask = ColumnUsedMask::default();\n\n        // Test with very sparse, large indices\n        let sparse_indices = vec![0, 137, 1042, 5389, 10000, 50000, 100000, 500000, 1000000];\n\n        for &idx in &sparse_indices {\n            sparse_mask.set(idx);\n        }\n\n        for &idx in &sparse_indices {\n            assert!(sparse_mask.get(idx), \"Expected bit {idx} to be set\");\n        }\n\n        // Check some indices that shouldn't be set\n        let unset_indices = vec![1, 100, 1000, 5000, 25000, 75000, 250000, 750000];\n        for &idx in &unset_indices {\n            assert!(!sparse_mask.get(idx), \"Expected bit {idx} to not be set\");\n        }\n\n        assert!(!sparse_mask.is_empty());\n    }\n\n    #[test]\n    fn test_column_used_mask_clear() {\n        let mut mask = ColumnUsedMask::default();\n\n        // Test inline clear\n        mask.set(5);\n        mask.set(10);\n        assert!(mask.get(5));\n        mask.clear(5);\n        assert!(!mask.get(5));\n        assert!(mask.get(10));\n\n        // Test overflow clear\n        mask.set(100);\n        mask.set(200);\n        assert!(mask.get(100));\n        mask.clear(100);\n        assert!(!mask.get(100));\n        assert!(mask.get(200));\n\n        // Clear non-existent bit should be no-op\n        mask.clear(999);\n        assert!(!mask.get(999));\n    }\n\n    #[test]\n    fn test_column_used_mask_is_only() {\n        // Test inline is_only\n        let mut mask = ColumnUsedMask::default();\n        mask.set(5);\n        assert!(mask.is_only(5));\n        assert!(!mask.is_only(0));\n        assert!(!mask.is_only(100));\n\n        mask.set(10);\n        assert!(!mask.is_only(5));\n        assert!(!mask.is_only(10));\n\n        // Test overflow is_only\n        let mut mask2 = ColumnUsedMask::default();\n        mask2.set(100);\n        assert!(mask2.is_only(100));\n        assert!(!mask2.is_only(0));\n        assert!(!mask2.is_only(50));\n\n        mask2.set(200);\n        assert!(!mask2.is_only(100));\n\n        // Test empty mask\n        let empty = ColumnUsedMask::default();\n        assert!(!empty.is_only(0));\n        assert!(!empty.is_only(100));\n    }\n\n    #[test]\n    fn test_column_used_mask_subtract() {\n        let mut mask1 = ColumnUsedMask::default();\n        let mut mask2 = ColumnUsedMask::default();\n\n        // Set up mask1 with inline and overflow bits\n        for i in [1, 5, 10, 63, 64, 100, 200] {\n            mask1.set(i);\n        }\n\n        // Set up mask2 with some overlapping bits\n        for i in [5, 10, 100] {\n            mask2.set(i);\n        }\n\n        mask1.subtract(&mask2);\n\n        // Should remain\n        assert!(mask1.get(1));\n        assert!(mask1.get(63));\n        assert!(mask1.get(64));\n        assert!(mask1.get(200));\n\n        // Should be cleared\n        assert!(!mask1.get(5));\n        assert!(!mask1.get(10));\n        assert!(!mask1.get(100));\n    }\n\n    #[test]\n    fn test_column_used_mask_iter() {\n        let mut mask = ColumnUsedMask::default();\n        let indices = vec![0, 5, 63, 64, 65, 127, 128, 200, 1000];\n\n        for &i in &indices {\n            mask.set(i);\n        }\n\n        let collected: Vec<usize> = mask.iter().collect();\n        assert_eq!(collected, indices);\n\n        // Empty mask iter\n        let empty = ColumnUsedMask::default();\n        assert_eq!(empty.iter().count(), 0);\n    }\n\n    #[test]\n    fn test_column_used_mask_bitor_assign() {\n        let mut mask1 = ColumnUsedMask::default();\n        let mut mask2 = ColumnUsedMask::default();\n\n        // Inline bits\n        mask1.set(1);\n        mask1.set(5);\n        mask2.set(5);\n        mask2.set(10);\n\n        // Overflow bits\n        mask1.set(100);\n        mask2.set(200);\n\n        mask1 |= &mask2;\n\n        assert!(mask1.get(1));\n        assert!(mask1.get(5));\n        assert!(mask1.get(10));\n        assert!(mask1.get(100));\n        assert!(mask1.get(200));\n\n        // mask2 should be unchanged\n        assert!(!mask2.get(1));\n        assert!(mask2.get(5));\n        assert!(mask2.get(10));\n        assert!(!mask2.get(100));\n        assert!(mask2.get(200));\n    }\n\n    #[test]\n    fn test_column_used_mask_boundary_conditions() {\n        let mut mask = ColumnUsedMask::default();\n\n        // Test at inline/overflow boundary\n        mask.set(63); // last inline bit\n        mask.set(64); // first overflow bit\n\n        assert!(mask.get(63));\n        assert!(mask.get(64));\n        assert!(!mask.get(62));\n        assert!(!mask.get(65));\n\n        // Test is_only at boundary\n        let mut mask2 = ColumnUsedMask::default();\n        mask2.set(63);\n        assert!(mask2.is_only(63));\n\n        let mut mask3 = ColumnUsedMask::default();\n        mask3.set(64);\n        assert!(mask3.is_only(64));\n    }\n\n    fn rng_from_env_or_time() -> (ChaCha8Rng, u64) {\n        let seed = std::env::var(\"TEST_SEED\")\n            .ok()\n            .and_then(|s| s.parse().ok())\n            .unwrap_or_else(|| {\n                std::time::SystemTime::now()\n                    .duration_since(std::time::UNIX_EPOCH)\n                    .unwrap()\n                    .as_nanos() as u64\n            });\n        (ChaCha8Rng::seed_from_u64(seed), seed)\n    }\n\n    /// Reference implementation using BTreeSet for correctness comparison\n    struct ReferenceMask(std::collections::BTreeSet<usize>);\n\n    impl ReferenceMask {\n        fn new() -> Self {\n            Self(std::collections::BTreeSet::new())\n        }\n        fn set(&mut self, index: usize) {\n            self.0.insert(index);\n        }\n        fn get(&self, index: usize) -> bool {\n            self.0.contains(&index)\n        }\n        fn clear(&mut self, index: usize) {\n            self.0.remove(&index);\n        }\n        fn is_empty(&self) -> bool {\n            self.0.is_empty()\n        }\n        fn is_only(&self, index: usize) -> bool {\n            self.0.len() == 1 && self.0.contains(&index)\n        }\n        fn contains_all_set_bits_of(&self, other: &Self) -> bool {\n            other.0.is_subset(&self.0)\n        }\n        fn subtract(&mut self, other: &Self) {\n            for &idx in &other.0 {\n                self.0.remove(&idx);\n            }\n        }\n        fn bitor_assign(&mut self, other: &Self) {\n            for &idx in &other.0 {\n                self.0.insert(idx);\n            }\n        }\n    }\n\n    #[test]\n    fn test_column_used_mask_fuzz() {\n        let (mut rng, seed) = rng_from_env_or_time();\n        eprintln!(\"test_column_used_mask_random_ops seed: {seed}\");\n\n        let mut mask = ColumnUsedMask::default();\n        let mut reference = ReferenceMask::new();\n\n        let num_ops = 100000;\n        let max_index = 4096;\n\n        for _ in 0..num_ops {\n            let op = rng.next_u32() % 10;\n            let idx = (rng.next_u32() % max_index) as usize;\n\n            match op {\n                0..=2 => {\n                    // Set (more frequent)\n                    mask.set(idx);\n                    reference.set(idx);\n                }\n                3 => {\n                    // Get\n                    assert_eq!(\n                        mask.get(idx),\n                        reference.get(idx),\n                        \"get({idx}) mismatch, seed={seed}\"\n                    );\n                }\n                4 => {\n                    // Clear\n                    mask.clear(idx);\n                    reference.clear(idx);\n                }\n                5 => {\n                    // IsEmpty\n                    assert_eq!(\n                        mask.is_empty(),\n                        reference.is_empty(),\n                        \"is_empty mismatch, seed={seed}\"\n                    );\n                }\n                6 => {\n                    // IsOnly\n                    assert_eq!(\n                        mask.is_only(idx),\n                        reference.is_only(idx),\n                        \"is_only({idx}) mismatch, seed={seed}\"\n                    );\n                }\n                7 => {\n                    // ContainsAllSetBitsOf with random other mask\n                    let mut other_mask = ColumnUsedMask::default();\n                    let mut other_ref = ReferenceMask::new();\n                    for _ in 0..(rng.next_u32() % 20) {\n                        let other_idx = (rng.next_u32() % max_index) as usize;\n                        other_mask.set(other_idx);\n                        other_ref.set(other_idx);\n                    }\n                    assert_eq!(\n                        mask.contains_all_set_bits_of(&other_mask),\n                        reference.contains_all_set_bits_of(&other_ref),\n                        \"contains_all_set_bits_of mismatch, seed={seed}\"\n                    );\n                }\n                8 => {\n                    // BitOrAssign with random other mask\n                    let mut other_mask = ColumnUsedMask::default();\n                    let mut other_ref = ReferenceMask::new();\n                    for _ in 0..(rng.next_u32() % 20) {\n                        let other_idx = (rng.next_u32() % max_index) as usize;\n                        other_mask.set(other_idx);\n                        other_ref.set(other_idx);\n                    }\n                    mask |= &other_mask;\n                    reference.bitor_assign(&other_ref);\n                }\n                9 => {\n                    // Subtract with random other mask\n                    let mut other_mask = ColumnUsedMask::default();\n                    let mut other_ref = ReferenceMask::new();\n                    for _ in 0..(rng.next_u32() % 20) {\n                        let other_idx = (rng.next_u32() % max_index) as usize;\n                        other_mask.set(other_idx);\n                        other_ref.set(other_idx);\n                    }\n                    mask.subtract(&other_mask);\n                    reference.subtract(&other_ref);\n                }\n                _ => unreachable!(),\n            }\n        }\n\n        // Final verification: iter should produce same results\n        let mask_set: std::collections::BTreeSet<usize> = mask.iter().collect();\n        assert_eq!(mask_set, reference.0, \"final iter mismatch, seed={seed}\");\n    }\n}\n"
  },
  {
    "path": "core/translate/planner.rs",
    "content": "use crate::sync::Arc;\nuse crate::{turso_assert, turso_assert_greater_than_or_equal, turso_assert_less_than};\nuse std::cmp::PartialEq;\n\nuse super::{\n    expr::{walk_expr, walk_expr_mut},\n    plan::{\n        Aggregate, ColumnUsedMask, Distinctness, EvalAt, IterationDirection, JoinInfo,\n        JoinOrderMember, JoinType as PlanJoinType, JoinedTable, Operation, OuterQueryReference,\n        Plan, QueryDestination, ResultSetColumn, Scan, TableReferences, WhereTerm,\n    },\n    select::prepare_select_plan,\n};\nuse crate::translate::{\n    emitter::Resolver,\n    expr::{expr_vector_size, unwrap_parens, BindingBehavior, WalkControl},\n    plan::{NonFromClauseSubquery, SubqueryState},\n};\nuse crate::{\n    ast::Limit,\n    function::Func,\n    schema::Table,\n    util::{exprs_are_equivalent, normalize_ident, validate_aggregate_function_tail},\n    Result,\n};\nuse crate::{\n    function::{AggFunc, ExtFunc},\n    translate::expr::bind_and_rewrite_expr,\n};\nuse crate::{\n    translate::plan::{Window, WindowFunction, WindowFunctionKind},\n    vdbe::builder::ProgramBuilder,\n};\nuse smallvec::SmallVec;\nuse turso_parser::ast::Literal::Null;\nuse turso_parser::ast::{\n    self, As, Expr, FromClause, JoinType, Materialized, Over, QualifiedName, Select,\n    TableInternalId, With,\n};\n\n/// A CTE definition stored for deferred planning.\n/// Instead of planning CTEs once and cloning the result, we store the AST and\n/// re-plan each time the CTE is referenced. This ensures each reference gets\n/// truly unique internal_ids and cursor IDs.\nstruct CteDefinition {\n    /// Globally unique CTE identity for sharing materialized data.\n    /// Multiple references to this CTE will use this ID to look up shared cursors.\n    cte_id: usize,\n    /// Normalized CTE name\n    name: String,\n    /// The original AST SELECT statement (cloned for each reference)\n    select: Select,\n    /// Explicit column names from WITH t(a, b) AS (...) syntax\n    explicit_columns: Vec<String>,\n    /// Indexes of CTEs that this CTE directly references.\n    /// Only includes CTEs that appear in this CTE's FROM clause,\n    /// avoiding exponential re-planning when CTEs have transitive dependencies.\n    referenced_cte_indices: SmallVec<[usize; 2]>,\n    /// True if WITH ... AS MATERIALIZED was specified, forcing materialization\n    materialize_hint: bool,\n}\n\n/// Collect all table names referenced in a SELECT's FROM clause.\n/// Used to determine which earlier CTEs a CTE directly depends on.\nfn collect_from_clause_table_refs(select: &Select, out: &mut Vec<String>) {\n    collect_from_select_body(&select.body, out);\n    collect_subquery_table_refs_in_select_exprs(select, out);\n}\n\nfn collect_from_select_body(body: &ast::SelectBody, out: &mut Vec<String>) {\n    collect_from_one_select(&body.select, out);\n    for compound in &body.compounds {\n        collect_from_one_select(&compound.select, out);\n    }\n}\n\nfn collect_from_one_select(one: &ast::OneSelect, out: &mut Vec<String>) {\n    match one {\n        ast::OneSelect::Select { from, .. } => {\n            if let Some(from_clause) = from {\n                collect_from_select_table(&from_clause.select, out);\n                for join in &from_clause.joins {\n                    collect_from_select_table(&join.table, out);\n                }\n            }\n        }\n        ast::OneSelect::Values(_) => {}\n    }\n}\n\nfn collect_from_select_table(table: &ast::SelectTable, out: &mut Vec<String>) {\n    match table {\n        ast::SelectTable::Table(qualified_name, _, _)\n        | ast::SelectTable::TableCall(qualified_name, _, _) => {\n            out.push(normalize_ident(qualified_name.name.as_str()));\n        }\n        ast::SelectTable::Select(subselect, _) => {\n            collect_from_clause_table_refs(subselect, out);\n        }\n        ast::SelectTable::Sub(from_clause, _) => {\n            collect_from_select_table(&from_clause.select, out);\n            for join in &from_clause.joins {\n                collect_from_select_table(&join.table, out);\n            }\n        }\n    }\n}\n\n/// Collect table references from subqueries embedded in expressions.\nfn collect_subquery_table_refs_in_select_exprs(select: &Select, out: &mut Vec<String>) {\n    collect_subquery_table_refs_in_one_select(&select.body.select, out);\n    for compound in &select.body.compounds {\n        collect_subquery_table_refs_in_one_select(&compound.select, out);\n    }\n\n    for sorted in &select.order_by {\n        collect_subquery_table_refs_in_expr(&sorted.expr, out);\n    }\n\n    if let Some(limit) = &select.limit {\n        collect_subquery_table_refs_in_expr(&limit.expr, out);\n        if let Some(offset) = &limit.offset {\n            collect_subquery_table_refs_in_expr(offset, out);\n        }\n    }\n}\n\nfn collect_subquery_table_refs_in_one_select(one: &ast::OneSelect, out: &mut Vec<String>) {\n    match one {\n        ast::OneSelect::Select {\n            columns,\n            where_clause,\n            group_by,\n            ..\n        } => {\n            for column in columns {\n                if let ast::ResultColumn::Expr(expr, _) = column {\n                    collect_subquery_table_refs_in_expr(expr, out);\n                }\n            }\n            if let Some(expr) = where_clause {\n                collect_subquery_table_refs_in_expr(expr, out);\n            }\n            if let Some(group_by) = group_by {\n                for expr in &group_by.exprs {\n                    collect_subquery_table_refs_in_expr(expr, out);\n                }\n                if let Some(having) = &group_by.having {\n                    collect_subquery_table_refs_in_expr(having, out);\n                }\n            }\n        }\n        ast::OneSelect::Values(rows) => {\n            for row in rows {\n                for expr in row {\n                    collect_subquery_table_refs_in_expr(expr, out);\n                }\n            }\n        }\n    }\n}\n\nfn collect_subquery_table_refs_in_expr(expr: &Expr, out: &mut Vec<String>) {\n    let _ = walk_expr(expr, &mut |node: &Expr| -> Result<WalkControl> {\n        match node {\n            Expr::Exists(select) | Expr::Subquery(select) => {\n                collect_from_clause_table_refs(select, out);\n                Ok(WalkControl::SkipChildren)\n            }\n            Expr::InSelect { rhs, .. } => {\n                collect_from_clause_table_refs(rhs, out);\n                Ok(WalkControl::SkipChildren)\n            }\n            _ => Ok(WalkControl::Continue),\n        }\n    });\n}\n\n/// Valid ways to refer to the rowid of a btree table.\npub const ROWID_STRS: [&str; 3] = [\"rowid\", \"_rowid_\", \"oid\"];\n\n/// This function walks the expression tree and identifies aggregate\n/// and window functions.\n///\n/// # Window functions\n/// - If `windows` is `Some`, window functions will be resolved against the\n///   provided set of windows or added to it if not present.\n/// - If `windows` is `None`, any encountered window function is treated\n///   as a misuse and results in a parse error.\n///\n/// # Aggregates\n/// Aggregate functions are always allowed. They are collected in `aggs`.\n///\n/// # Returns\n/// - `Ok(true)` if at least one aggregate function was found.\n/// - `Ok(false)` if no aggregates were found.\n/// - `Err(..)` if an invalid function usage is detected (e.g., window\n///   function encountered while `windows` is `None`).\npub fn resolve_window_and_aggregate_functions(\n    top_level_expr: &Expr,\n    resolver: &Resolver,\n    aggs: &mut Vec<Aggregate>,\n    mut windows: Option<&mut Vec<Window>>,\n) -> Result<bool> {\n    let mut contains_aggregates = false;\n    walk_expr(top_level_expr, &mut |expr: &Expr| -> Result<WalkControl> {\n        match expr {\n            Expr::FunctionCall {\n                name,\n                args,\n                distinctness,\n                filter_over,\n                order_by,\n            } => {\n                validate_aggregate_function_tail(filter_over, order_by)?;\n                let args_count = args.len();\n                let distinctness = Distinctness::from_ast(distinctness.as_ref());\n\n                match Func::resolve_function(name.as_str(), args_count) {\n                    Ok(Func::Agg(f)) => {\n                        if let Some(over_clause) = filter_over.over_clause.as_ref() {\n                            link_with_window(\n                                windows.as_deref_mut(),\n                                expr,\n                                WindowFunctionKind::Agg(f),\n                                over_clause,\n                                distinctness,\n                            )?;\n                        } else {\n                            add_aggregate_if_not_exists(aggs, expr, args, distinctness, f)?;\n                            contains_aggregates = true;\n                        }\n                        return Ok(WalkControl::SkipChildren);\n                    }\n                    Ok(Func::Window(f)) => {\n                        if let Some(over_clause) = filter_over.over_clause.as_ref() {\n                            link_with_window(\n                                windows.as_deref_mut(),\n                                expr,\n                                WindowFunctionKind::Window(f),\n                                over_clause,\n                                distinctness,\n                            )?;\n                        } else {\n                            crate::bail_parse_error!(\"misuse of window function: {}()\", f);\n                        }\n                        return Ok(WalkControl::SkipChildren);\n                    }\n                    Err(e) => {\n                        if let Some(f) = resolver\n                            .symbol_table\n                            .resolve_function(name.as_str(), args_count)\n                        {\n                            let func = AggFunc::External(f.func.clone().into());\n                            if let ExtFunc::Aggregate { .. } = f.as_ref().func {\n                                if let Some(over_clause) = filter_over.over_clause.as_ref() {\n                                    link_with_window(\n                                        windows.as_deref_mut(),\n                                        expr,\n                                        WindowFunctionKind::Agg(func),\n                                        over_clause,\n                                        distinctness,\n                                    )?;\n                                } else {\n                                    add_aggregate_if_not_exists(\n                                        aggs,\n                                        expr,\n                                        args,\n                                        distinctness,\n                                        func,\n                                    )?;\n                                    contains_aggregates = true;\n                                }\n                                return Ok(WalkControl::SkipChildren);\n                            }\n                        } else {\n                            return Err(e);\n                        }\n                    }\n                    _ => {\n                        if filter_over.over_clause.is_some() {\n                            crate::bail_parse_error!(\n                                \"{} may not be used as a window function\",\n                                name.as_str()\n                            );\n                        }\n                    }\n                }\n            }\n            Expr::FunctionCallStar { name, filter_over } => {\n                validate_aggregate_function_tail(filter_over, &[])?;\n                match Func::resolve_function(name.as_str(), 0) {\n                    Ok(Func::Agg(f)) => {\n                        if let Some(over_clause) = filter_over.over_clause.as_ref() {\n                            link_with_window(\n                                windows.as_deref_mut(),\n                                expr,\n                                WindowFunctionKind::Agg(f),\n                                over_clause,\n                                Distinctness::NonDistinct,\n                            )?;\n                        } else {\n                            add_aggregate_if_not_exists(\n                                aggs,\n                                expr,\n                                &[],\n                                Distinctness::NonDistinct,\n                                f,\n                            )?;\n                            contains_aggregates = true;\n                        }\n                        return Ok(WalkControl::SkipChildren);\n                    }\n                    Ok(Func::Window(f)) => {\n                        if let Some(over_clause) = filter_over.over_clause.as_ref() {\n                            link_with_window(\n                                windows.as_deref_mut(),\n                                expr,\n                                WindowFunctionKind::Window(f),\n                                over_clause,\n                                Distinctness::NonDistinct,\n                            )?;\n                        } else {\n                            crate::bail_parse_error!(\"misuse of window function: {}()\", f);\n                        }\n                        return Ok(WalkControl::SkipChildren);\n                    }\n                    Ok(_) => {\n                        if filter_over.over_clause.is_some() {\n                            crate::bail_parse_error!(\n                                \"{} may not be used as a window function\",\n                                name.as_str()\n                            );\n                        }\n\n                        // Check if the function supports (*) syntax using centralized logic\n                        match crate::function::Func::resolve_function(name.as_str(), 0) {\n                            Ok(func) => {\n                                if func.supports_star_syntax() {\n                                    return Ok(WalkControl::Continue);\n                                } else {\n                                    crate::bail_parse_error!(\n                                        \"wrong number of arguments to function {}()\",\n                                        name.as_str()\n                                    );\n                                }\n                            }\n                            Err(_) => {\n                                crate::bail_parse_error!(\n                                    \"wrong number of arguments to function {}()\",\n                                    name.as_str()\n                                );\n                            }\n                        }\n                    }\n                    Err(e) => match e {\n                        crate::LimboError::ParseError(e) => {\n                            crate::bail_parse_error!(\"{}\", e);\n                        }\n                        _ => {\n                            crate::bail_parse_error!(\n                                \"Invalid aggregate function: {}\",\n                                name.as_str()\n                            );\n                        }\n                    },\n                }\n            }\n            _ => {}\n        }\n\n        Ok(WalkControl::Continue)\n    })?;\n\n    Ok(contains_aggregates)\n}\n\nfn link_with_window(\n    windows: Option<&mut Vec<Window>>,\n    expr: &Expr,\n    func: WindowFunctionKind,\n    over_clause: &Over,\n    distinctness: Distinctness,\n) -> Result<()> {\n    if distinctness.is_distinct() {\n        crate::bail_parse_error!(\"DISTINCT is not supported for window functions\");\n    }\n    expr_vector_size(expr)?;\n    if let Some(windows) = windows {\n        let window = resolve_window(windows, over_clause)?;\n        window.functions.push(WindowFunction {\n            func,\n            original_expr: expr.clone(),\n        });\n    } else {\n        let func_name = match &func {\n            WindowFunctionKind::Agg(f) => f.as_str().to_string(),\n            WindowFunctionKind::Window(f) => f.to_string(),\n        };\n        crate::bail_parse_error!(\"misuse of window function: {}()\", func_name);\n    }\n    Ok(())\n}\n\nfn resolve_window<'a>(windows: &'a mut Vec<Window>, over_clause: &Over) -> Result<&'a mut Window> {\n    match over_clause {\n        Over::Window(window) => {\n            if let Some(idx) = windows.iter().position(|w| w.is_equivalent(window)) {\n                return Ok(&mut windows[idx]);\n            }\n\n            windows.push(Window::new(None, window)?);\n            Ok(windows.last_mut().expect(\"just pushed, so must exist\"))\n        }\n        Over::Name(name) => {\n            let window_name = normalize_ident(name.as_str());\n            // When multiple windows share the same name, SQLite uses the most recent\n            // definition. Iterate in reverse so we find the last definition first.\n            for window in windows.iter_mut().rev() {\n                if window.name.as_ref() == Some(&window_name) {\n                    return Ok(window);\n                }\n            }\n            crate::bail_parse_error!(\"no such window: {}\", window_name);\n        }\n    }\n}\n\nfn add_aggregate_if_not_exists(\n    aggs: &mut Vec<Aggregate>,\n    expr: &Expr,\n    args: &[Box<Expr>],\n    distinctness: Distinctness,\n    func: AggFunc,\n) -> Result<()> {\n    if distinctness.is_distinct() && args.len() != 1 {\n        crate::bail_parse_error!(\"DISTINCT aggregate functions must have exactly one argument\");\n    }\n    if aggs\n        .iter()\n        .all(|a| !exprs_are_equivalent(&a.original_expr, expr))\n    {\n        aggs.push(Aggregate::new(func, args, expr, distinctness));\n    }\n    Ok(())\n}\n\n/// Plan a CTE when it's referenced in a query.\n/// Each call produces a fresh plan with unique internal_ids, ensuring that\n/// multiple references to the same CTE get independent cursor IDs,\n/// yield registers and so on.\n///\n/// `count_reference`: Controls whether this call increments the CTE reference count.\n///\n/// **Reference counting determines materialization strategy:**\n/// - ref_count = 1: CTE can use efficient coroutine (no materialization needed)\n/// - ref_count > 1: CTE must be materialized into ephemeral table for sharing\n///\n/// **When to count (true):**\n/// - CTE appears in a FROM/JOIN clause (actual usage site)\n/// - CTE is referenced via outer_query_refs (e.g., in scalar subqueries)\n///\n/// **When NOT to count (false):**\n/// - Pre-planning CTEs for outer_query_refs visibility (making CTEs available\n///   for potential use by nested subqueries - not actual usage)\n/// - Recursively planning CTE dependencies (CTE A references CTE B internally -\n///   B needs to be visible to A's planning, but this isn't a usage from the\n///   main query's perspective)\n#[allow(clippy::too_many_arguments)]\nfn plan_cte(\n    cte_idx: usize,\n    cte_definitions: &[CteDefinition],\n    base_outer_query_refs: &[OuterQueryReference],\n    resolver: &Resolver,\n    program: &mut ProgramBuilder,\n    connection: &Arc<crate::Connection>,\n    count_reference: bool,\n) -> Result<JoinedTable> {\n    let cte_def = &cte_definitions[cte_idx];\n\n    // Build outer_query_refs including only the CTEs this one directly references.\n    // By tracking direct dependencies instead of all preceding CTEs, we avoid\n    // exponential re-planning when CTEs have transitive dependencies.\n    let mut outer_query_refs = base_outer_query_refs.to_vec();\n    for &ref_idx in &cte_def.referenced_cte_indices {\n        let ref_cte_name = &cte_definitions[ref_idx].name;\n        // Check if this CTE has already been planned and is in outer_query_refs.\n        // This avoids exponential re-planning when CTEs have transitive dependencies.\n        if outer_query_refs\n            .iter()\n            .any(|r| &r.identifier == ref_cte_name)\n        {\n            continue;\n        }\n        // Recursively plan the referenced CTE so it's visible within this CTE's body.\n        // Example: WITH a AS (...), b AS (SELECT * FROM a) - when planning b, we need\n        // a to be in scope. But this internal dependency doesn't count as a \"reference\"\n        // for materialization purposes - only actual usage in the main query counts.\n        let referenced_table = plan_cte(\n            ref_idx,\n            cte_definitions,\n            base_outer_query_refs,\n            resolver,\n            program,\n            connection,\n            false,\n        )?;\n        outer_query_refs.push(OuterQueryReference {\n            identifier: referenced_table.identifier.clone(),\n            internal_id: referenced_table.internal_id,\n            table: referenced_table.table.clone(),\n            col_used_mask: ColumnUsedMask::default(),\n            cte_select: None,\n            cte_explicit_columns: vec![],\n            cte_id: Some(cte_definitions[ref_idx].cte_id),\n            cte_definition_only: false,\n            rowid_referenced: false,\n        });\n    }\n\n    // Block the CTE's own name from resolving to a schema object during\n    // planning of its body. Without this, a same-named view would be expanded\n    // recursively (stack overflow) and a same-named table would give wrong\n    // results. `parse_table` checks this and produces \"circular reference\".\n    program.push_cte_being_defined(cte_def.name.clone());\n\n    // Plan this CTE with fresh IDs\n    let cte_plan = prepare_select_plan(\n        cte_def.select.clone(),\n        resolver,\n        program,\n        &outer_query_refs,\n        QueryDestination::placeholder_for_subquery(),\n        connection,\n    );\n    program.pop_cte_being_defined();\n    let cte_plan = cte_plan?;\n\n    // CTEs can be either simple SELECT or compound SELECT (UNION/INTERSECT/EXCEPT)\n    let explicit_cols = if cte_def.explicit_columns.is_empty() {\n        None\n    } else {\n        Some(cte_def.explicit_columns.as_slice())\n    };\n\n    // Track CTE reference count globally for materialization decisions during emission.\n    // Multi-ref CTEs should be materialized; single-ref CTEs can use coroutine.\n    // Only count actual references (FROM/JOIN usage), not pre-planning or recursive deps.\n    if count_reference {\n        program.increment_cte_reference(cte_def.cte_id);\n\n        // Validate explicit column count only on actual references (matching SQLite behavior,\n        // which defers this check until the CTE is used).\n        if let Some(cols) = explicit_cols {\n            let result_col_count = cte_plan\n                .select_result_columns()\n                .expect(\"should be a select plan\")\n                .len();\n            if cols.len() != result_col_count {\n                crate::bail_parse_error!(\n                    \"table {} has {} columns but {} column names were provided\",\n                    cte_def.name,\n                    result_col_count,\n                    cols.len()\n                );\n            }\n        }\n    }\n\n    match cte_plan {\n        Plan::Select(_) | Plan::CompoundSelect { .. } => JoinedTable::new_subquery_from_plan(\n            cte_def.name.clone(),\n            cte_plan,\n            None,\n            program.table_reference_counter.next(),\n            explicit_cols,\n            Some(cte_def.cte_id), // Pass the CTE identity for sharing materialized data\n            cte_def.materialize_hint,\n        ),\n        Plan::Delete(_) | Plan::Update(_) => {\n            crate::bail_parse_error!(\"DELETE/UPDATE queries are not supported in CTEs\")\n        }\n    }\n}\n\n/// Plan CTEs from a WITH clause and add them as outer query references.\n/// This is used by DML statements (DELETE, UPDATE) to make CTEs available\n/// for subqueries in WHERE and SET clauses.\npub fn plan_ctes_as_outer_refs(\n    with: Option<With>,\n    resolver: &Resolver,\n    program: &mut ProgramBuilder,\n    table_references: &mut TableReferences,\n    connection: &Arc<crate::Connection>,\n) -> Result<()> {\n    let Some(with) = with else {\n        return Ok(());\n    };\n\n    if with.recursive {\n        crate::bail_parse_error!(\"Recursive CTEs are not yet supported\");\n    }\n\n    for cte in with.ctes {\n        // Normalize explicit column names\n        let explicit_columns: Vec<String> = cte\n            .columns\n            .iter()\n            .map(|c| normalize_ident(c.col_name.as_str()))\n            .collect();\n\n        let cte_name = normalize_ident(cte.tbl_name.as_str());\n\n        // Check for duplicate CTE names\n        if table_references\n            .outer_query_refs()\n            .iter()\n            .any(|r| r.identifier == cte_name)\n        {\n            crate::bail_parse_error!(\"duplicate WITH table name: {}\", cte.tbl_name.as_str());\n        }\n\n        // Clone the CTE select AST before planning, so we can store it for re-planning\n        let cte_select_ast = cte.select.clone();\n        // AS MATERIALIZED forces materialization\n        let materialize_hint = cte.materialized == Materialized::Yes;\n\n        // Block the CTE's own name from resolving to a schema object during\n        // planning of its body (see push_cte_being_defined).\n        program.push_cte_being_defined(cte_name.clone());\n\n        // Plan the CTE SELECT\n        let cte_plan = prepare_select_plan(\n            cte.select,\n            resolver,\n            program,\n            table_references.outer_query_refs(),\n            QueryDestination::placeholder_for_subquery(),\n            connection,\n        );\n        program.pop_cte_being_defined();\n        let cte_plan = cte_plan?;\n\n        // Convert plan to JoinedTable to extract column info\n        let explicit_cols = if explicit_columns.is_empty() {\n            None\n        } else {\n            Some(explicit_columns.as_slice())\n        };\n        let joined_table = match cte_plan {\n            Plan::Select(_) | Plan::CompoundSelect { .. } => JoinedTable::new_subquery_from_plan(\n                cte_name.clone(),\n                cte_plan,\n                None,\n                program.table_reference_counter.next(),\n                explicit_cols,\n                None, // CTEs in DML don't share materialized data (TODO: implement if needed)\n                materialize_hint,\n            )?,\n            Plan::Delete(_) | Plan::Update(_) => {\n                crate::bail_parse_error!(\"Only SELECT queries are supported in CTEs\")\n            }\n        };\n\n        // Add CTE as outer query reference so it's available to subqueries.\n        // cte_definition_only = true: the CTE is only for subquery FROM lookup\n        // (e.g. UPDATE t SET b = (SELECT v FROM c)), not for direct column\n        // resolution (e.g. UPDATE t SET b = c.v which SQLite rejects as\n        // \"no such column\").\n        table_references.add_outer_query_reference(OuterQueryReference {\n            identifier: cte_name,\n            internal_id: joined_table.internal_id,\n            table: joined_table.table,\n            col_used_mask: ColumnUsedMask::default(),\n            cte_select: Some(cte_select_ast),\n            cte_explicit_columns: explicit_columns,\n            cte_id: None, // DML CTEs don't track CTE sharing (TODO: implement if needed)\n            cte_definition_only: true,\n            rowid_referenced: false,\n        });\n    }\n\n    Ok(())\n}\n\nfn parse_from_clause_table(\n    table: ast::SelectTable,\n    resolver: &Resolver,\n    program: &mut ProgramBuilder,\n    table_references: &mut TableReferences,\n    vtab_predicates: &mut Vec<Expr>,\n    cte_definitions: &[CteDefinition],\n    connection: &Arc<crate::Connection>,\n) -> Result<()> {\n    match table {\n        ast::SelectTable::Table(qualified_name, maybe_alias, indexed) => parse_table(\n            table_references,\n            resolver,\n            program,\n            cte_definitions,\n            vtab_predicates,\n            &qualified_name,\n            maybe_alias.as_ref(),\n            &[],\n            indexed,\n            connection,\n        ),\n        ast::SelectTable::Select(subselect, maybe_alias) => {\n            // For inline subqueries, we plan all CTEs once and pass them as outer_query_refs.\n            // This allows the subquery to reference CTEs defined in the parent's WITH clause.\n            let mut outer_query_refs_for_subquery = table_references.outer_query_refs().to_vec();\n            let base_outer_query_refs_for_subquery = base_outer_refs_for_cte_planning(\n                table_references.outer_query_refs(),\n                cte_definitions,\n            );\n            for (idx, cte_def) in cte_definitions.iter().enumerate() {\n                // Check if this CTE has already been planned and is in outer_query_refs.\n                // This avoids exponential re-planning when CTEs have transitive dependencies.\n                if outer_query_refs_for_subquery\n                    .iter()\n                    .any(|r| r.identifier == cte_def.name)\n                {\n                    continue;\n                }\n                // Plan each CTE so it's visible to this inline subquery's FROM clause.\n                // Example: WITH cte AS (...) SELECT * FROM (SELECT * FROM cte) sub\n                // The inline subquery \"(SELECT * FROM cte)\" needs cte in scope, but\n                // planning this visibility isn't a reference - the actual reference\n                // happens when the inline subquery's FROM clause resolves \"cte\".\n                let cte_table = plan_cte(\n                    idx,\n                    cte_definitions,\n                    &base_outer_query_refs_for_subquery,\n                    resolver,\n                    program,\n                    connection,\n                    false,\n                )?;\n                outer_query_refs_for_subquery.push(OuterQueryReference {\n                    identifier: cte_def.name.clone(),\n                    internal_id: cte_table.internal_id,\n                    table: cte_table.table,\n                    col_used_mask: ColumnUsedMask::default(),\n                    cte_select: Some(cte_def.select.clone()),\n                    cte_explicit_columns: cte_def.explicit_columns.clone(),\n                    cte_id: Some(cte_def.cte_id),\n                    cte_definition_only: false,\n                    rowid_referenced: false,\n                });\n            }\n\n            let subplan = prepare_select_plan(\n                subselect,\n                resolver,\n                program,\n                &outer_query_refs_for_subquery,\n                QueryDestination::placeholder_for_subquery(),\n                connection,\n            )?;\n            match &subplan {\n                Plan::Select(_) | Plan::CompoundSelect { .. } => {}\n                Plan::Delete(_) | Plan::Update(_) => {\n                    crate::bail_parse_error!(\n                        \"DELETE/UPDATE queries are not supported in FROM clause subqueries\"\n                    );\n                }\n            }\n            let cur_table_index = table_references.joined_tables().len();\n            let identifier = maybe_alias\n                .map(|a| match a {\n                    ast::As::As(id) => id,\n                    ast::As::Elided(id) => id,\n                })\n                .map(|id| normalize_ident(id.as_str()))\n                .unwrap_or_else(|| format!(\"subquery_{cur_table_index}\"));\n            table_references.add_joined_table(JoinedTable::new_subquery_from_plan(\n                identifier,\n                subplan,\n                None,\n                program.table_reference_counter.next(),\n                None,  // No explicit columns for regular subqueries\n                None,  // Regular inline subqueries don't have a CTE identity\n                false, // No materialize hint for inline subqueries\n            )?);\n            Ok(())\n        }\n        ast::SelectTable::TableCall(qualified_name, args, maybe_alias) => parse_table(\n            table_references,\n            resolver,\n            program,\n            cte_definitions,\n            vtab_predicates,\n            &qualified_name,\n            maybe_alias.as_ref(),\n            &args,\n            None, // table-valued functions don't support INDEXED BY\n            connection,\n        ),\n        ast::SelectTable::Sub(..) => {\n            crate::bail_parse_error!(\"Parenthesized FROM clause subqueries are not supported\")\n        }\n    }\n}\n\n#[allow(clippy::too_many_arguments)]\nfn parse_table(\n    table_references: &mut TableReferences,\n    resolver: &Resolver,\n    program: &mut ProgramBuilder,\n    cte_definitions: &[CteDefinition],\n    vtab_predicates: &mut Vec<Expr>,\n    qualified_name: &QualifiedName,\n    maybe_alias: Option<&As>,\n    args: &[Box<Expr>],\n    indexed: Option<ast::Indexed>,\n    connection: &Arc<crate::Connection>,\n) -> Result<()> {\n    let normalized_qualified_name = normalize_ident(qualified_name.name.as_str());\n    let database_id = resolver.resolve_database_id(qualified_name)?;\n    let table_name = &qualified_name.name;\n\n    // Check if the FROM clause table is referring to a CTE in the current scope.\n    // Each reference gets a freshly planned CTE to ensure unique internal_ids and cursor IDs.\n    if let Some(cte_idx) = cte_definitions\n        .iter()\n        .position(|cte| cte.name == normalized_qualified_name)\n    {\n        let planning_outer_query_refs =\n            base_outer_refs_for_cte_planning(table_references.outer_query_refs(), cte_definitions);\n        // This is an actual CTE reference in the FROM/JOIN clause - count it\n        let mut cte_table = plan_cte(\n            cte_idx,\n            cte_definitions,\n            &planning_outer_query_refs,\n            resolver,\n            program,\n            connection,\n            true, // Actual FROM/JOIN reference - count it\n        )?;\n\n        // If there's an alias provided, update the identifier to use that alias\n        if let Some(a) = maybe_alias {\n            let alias = match a {\n                ast::As::As(id) => id,\n                ast::As::Elided(id) => id,\n            };\n            cte_table.identifier = normalize_ident(alias.as_str());\n        }\n\n        // Mark the pre-planned outer_query_ref as \"CTE definition only\" so it is\n        // still available for CTE lookup in subquery FROM clauses (e.g.\n        // EXISTS (SELECT 1 FROM <cte_name> ...)), but no longer participates in\n        // column resolution. Column resolution now goes through the joined_table\n        // which has the alias (if any) or the original name.\n        table_references.mark_outer_query_ref_cte_definition_only(&normalized_qualified_name);\n\n        table_references.add_joined_table(cte_table);\n        return Ok(());\n    }\n\n    // A non-recursive CTE's body cannot reference its own name. The CTE name\n    // shadows any same-named schema object, but without RECURSIVE it's circular.\n    if program.is_cte_being_defined(&normalized_qualified_name) {\n        crate::bail_parse_error!(\"circular reference: {}\", table_name.as_str());\n    }\n\n    // Check if the table is a CTE from an outer scope (e.g., a CTE referencing another CTE).\n    // This handles cases like: WITH a AS (...), b AS (SELECT ... FROM a) SELECT * FROM b;\n    // When planning b's body, 'a' is in outer_query_refs.\n    if let Some(outer_ref) =\n        table_references.find_outer_query_ref_by_identifier(&normalized_qualified_name)\n    {\n        // If this is a CTE reference (via outer_query_refs), count it for materialization decisions.\n        // This handles scalar subqueries that reference CTEs.\n        if let Some(cte_id) = outer_ref.cte_id {\n            program.increment_cte_reference(cte_id);\n        }\n        let alias = maybe_alias\n            .map(|a| match a {\n                ast::As::As(id) => id,\n                ast::As::Elided(id) => id,\n            })\n            .map(|a| normalize_ident(a.as_str()));\n        // Clone fields we need before dropping the borrow on table_references.\n        let cte_select = outer_ref.cte_select.clone();\n        let cte_explicit_columns = outer_ref.cte_explicit_columns.clone();\n        let cte_id = outer_ref.cte_id;\n        let outer_table = outer_ref.table.clone();\n        let materialize_hint = match &outer_table {\n            Table::FromClauseSubquery(subquery) => subquery.materialize_hint(),\n            _ => false,\n        };\n\n        if let Some(cte_ast) = cte_select {\n            // Re-plan the CTE from its original AST to get fresh internal_ids.\n            // This prevents cursor key collisions when the same CTE is\n            // referenced multiple times in the same scope.\n            let cte_plan = prepare_select_plan(\n                cte_ast,\n                resolver,\n                program,\n                table_references.outer_query_refs(),\n                QueryDestination::placeholder_for_subquery(),\n                connection,\n            )?;\n            let explicit_cols = if cte_explicit_columns.is_empty() {\n                None\n            } else {\n                Some(cte_explicit_columns.as_slice())\n            };\n            // Validate explicit column count on actual CTE reference (matching SQLite\n            // behavior, which defers this check until the CTE is used).\n            if let Some(cols) = explicit_cols {\n                let result_col_count = cte_plan\n                    .select_result_columns()\n                    .expect(\"should be a select plan\")\n                    .len();\n                if cols.len() != result_col_count {\n                    crate::bail_parse_error!(\n                        \"table {} has {} columns but {} column names were provided\",\n                        normalized_qualified_name,\n                        result_col_count,\n                        cols.len()\n                    );\n                }\n            }\n            // Use the CTE name for the subquery name so query plans show\n            // \"SCAN cte_name AS alias\" instead of just \"SCAN alias\".\n            let mut jt = JoinedTable::new_subquery_from_plan(\n                normalized_qualified_name.clone(),\n                cte_plan,\n                None,\n                program.table_reference_counter.next(),\n                explicit_cols,\n                cte_id,\n                materialize_hint,\n            )?;\n            if let Some(alias) = alias {\n                jt.identifier = alias;\n            }\n            jt.database_id = database_id;\n            table_references.add_joined_table(jt);\n        } else {\n            let internal_id = program.table_reference_counter.next();\n            table_references.add_joined_table(JoinedTable {\n                op: Operation::default_scan_for(&outer_table),\n                table: outer_table,\n                identifier: alias.unwrap_or(normalized_qualified_name),\n                internal_id,\n                join_info: None,\n                col_used_mask: ColumnUsedMask::default(),\n                column_use_counts: Vec::new(),\n                expression_index_usages: Vec::new(),\n                database_id,\n                indexed: None,\n            });\n        }\n        return Ok(());\n    }\n\n    // Resolve table using connection's with_schema method\n    let table = resolver.with_schema(database_id, |schema| schema.get_table(table_name.as_str()));\n\n    if let Some(table) = table {\n        let alias = maybe_alias\n            .map(|a| match a {\n                ast::As::As(id) => id,\n                ast::As::Elided(id) => id,\n            })\n            .map(|a| normalize_ident(a.as_str()));\n        let internal_id = program.table_reference_counter.next();\n        let tbl_ref = if let Table::Virtual(tbl) = table.as_ref() {\n            transform_args_into_where_terms(args, internal_id, vtab_predicates, table.as_ref())?;\n            Table::Virtual(tbl.clone())\n        } else if let Table::BTree(table) = table.as_ref() {\n            Table::BTree(table.clone())\n        } else {\n            return Err(crate::LimboError::InvalidArgument(\n                \"Table type not supported\".to_string(),\n            ));\n        };\n        table_references.add_joined_table(JoinedTable {\n            op: Operation::default_scan_for(&tbl_ref),\n            table: tbl_ref,\n            identifier: alias.unwrap_or(normalized_qualified_name),\n            internal_id,\n            join_info: None,\n            col_used_mask: ColumnUsedMask::default(),\n            column_use_counts: Vec::new(),\n            expression_index_usages: Vec::new(),\n            database_id,\n            indexed,\n        });\n        return Ok(());\n    };\n\n    let regular_view =\n        resolver.with_schema(database_id, |schema| schema.get_view(table_name.as_str()));\n    if let Some(view) = regular_view {\n        // Views are essentially query aliases, so just Expand the view as a subquery\n        view.process()?;\n        let mut view_select = view.select_stmt.clone();\n        if let ast::OneSelect::Select {\n            ref mut columns, ..\n        } = view_select.body.select\n        {\n            for (col, result_col) in view.columns.iter().zip(columns.iter_mut()) {\n                if let (Some(name_str), ast::ResultColumn::Expr(_, ref mut alias)) =\n                    (&col.name, result_col)\n                {\n                    *alias = Some(ast::As::As(ast::Name::exact(name_str.clone())));\n                }\n            }\n        }\n        let subselect = Box::new(view_select);\n\n        // Use the view name as alias if no explicit alias was provided\n        let view_alias = maybe_alias\n            .cloned()\n            .or_else(|| Some(ast::As::As(table_name.clone())));\n\n        // Views are pre-defined definitions — their body resolves against the\n        // schema only, not against CTEs from the calling query context.\n        // Pass empty cte_definitions and temporarily clear the ctes_being_defined\n        // stack so that e.g. `WITH t AS (...) SELECT * FROM v` where view v\n        // references table t will correctly use the real table, not the CTE.\n        let saved_ctes = program.take_ctes_being_defined();\n        let result = parse_from_clause_table(\n            ast::SelectTable::Select(*subselect, view_alias),\n            resolver,\n            program,\n            table_references,\n            vtab_predicates,\n            &[],\n            connection,\n        );\n        program.restore_ctes_being_defined(saved_ctes);\n        view.done();\n        return result;\n    }\n\n    let view = resolver.with_schema(database_id, |schema| {\n        schema.get_materialized_view(table_name.as_str())\n    });\n    if let Some(view) = view {\n        // First check if the DBSP state table exists with the correct version\n        let has_compatible_state = resolver.with_schema(database_id, |schema| {\n            schema.has_compatible_dbsp_state_table(table_name.as_str())\n        });\n\n        if !has_compatible_state {\n            use crate::incremental::compiler::DBSP_CIRCUIT_VERSION;\n            return Err(crate::LimboError::InternalError(format!(\n                \"Materialized view '{table_name}' has an incompatible version. \\n\\\n                 The current version is {DBSP_CIRCUIT_VERSION}, but the view was created with a different version. \\n\\\n                 Please DROP and recreate the view to use it.\"\n            )));\n        }\n\n        // Check if this materialized view has persistent storage\n        let view_guard = view.lock();\n        let root_page = view_guard.get_root_page();\n\n        if root_page == 0 {\n            drop(view_guard);\n            return Err(crate::LimboError::InternalError(\n                \"Materialized view has no storage allocated\".to_string(),\n            ));\n        }\n\n        // This is a materialized view with storage - treat it as a regular BTree table\n        // Create a BTreeTable from the view's metadata\n        let btree_table = Arc::new(crate::schema::BTreeTable {\n            name: view_guard.name().to_string(),\n            root_page,\n            columns: view_guard.column_schema.flat_columns(),\n            primary_key_columns: Vec::new(),\n            has_rowid: true,\n            is_strict: false,\n            has_autoincrement: false,\n\n            unique_sets: vec![],\n            foreign_keys: vec![],\n            check_constraints: vec![],\n            rowid_alias_conflict_clause: None,\n        });\n        drop(view_guard);\n\n        let alias = maybe_alias\n            .map(|a| match a {\n                ast::As::As(id) => id,\n                ast::As::Elided(id) => id,\n            })\n            .map(|a| normalize_ident(a.as_str()));\n\n        table_references.add_joined_table(JoinedTable {\n            op: Operation::Scan(Scan::BTreeTable {\n                iter_dir: IterationDirection::Forwards,\n                index: None,\n            }),\n            table: Table::BTree(btree_table),\n            identifier: alias.unwrap_or(normalized_qualified_name),\n            internal_id: program.table_reference_counter.next(),\n            join_info: None,\n            col_used_mask: ColumnUsedMask::default(),\n            column_use_counts: Vec::new(),\n            expression_index_usages: Vec::new(),\n            database_id,\n            indexed: None,\n        });\n        return Ok(());\n    }\n\n    // CTEs are transformed into FROM clause subqueries.\n    // If we find a CTE with this name in our outer query references,\n    // we can use it as a joined table, but we must clone it since it's not MATERIALIZED.\n    //\n    // For other types of tables in the outer query references, we do not add them as joined tables,\n    // because the query can simply _reference_ them in e.g. the SELECT columns or the WHERE clause,\n    // but it's not part of the join order.\n    if let Some(outer_ref) =\n        table_references.find_outer_query_ref_by_identifier(&normalized_qualified_name)\n    {\n        if matches!(outer_ref.table, Table::FromClauseSubquery(_)) {\n            table_references.add_joined_table(JoinedTable {\n                op: Operation::default_scan_for(&outer_ref.table),\n                table: outer_ref.table.clone(),\n                identifier: outer_ref.identifier.clone(),\n                internal_id: program.table_reference_counter.next(),\n                join_info: None,\n                col_used_mask: ColumnUsedMask::default(),\n                column_use_counts: Vec::new(),\n                expression_index_usages: Vec::new(),\n                database_id,\n                indexed: None,\n            });\n            return Ok(());\n        }\n    }\n\n    // Check if this is an incompatible view\n    let is_incompatible = resolver.with_schema(database_id, |schema| {\n        schema\n            .incompatible_views\n            .contains(&normalized_qualified_name)\n    });\n\n    if is_incompatible {\n        use crate::incremental::compiler::DBSP_CIRCUIT_VERSION;\n        crate::bail_parse_error!(\n            \"Materialized view '{}' has an incompatible version. \\n\\\n             The view was created with a different DBSP version than the current version ({}). \\n\\\n             Please DROP and recreate the view to use it.\",\n            normalized_qualified_name,\n            DBSP_CIRCUIT_VERSION\n        );\n    }\n\n    crate::bail_parse_error!(\"no such table: {}\", normalized_qualified_name);\n}\n\nfn transform_args_into_where_terms(\n    args: &[Box<Expr>],\n    internal_id: TableInternalId,\n    predicates: &mut Vec<Expr>,\n    table: &Table,\n) -> Result<()> {\n    let mut args_iter = args.iter();\n    let mut hidden_count = 0;\n    for (i, col) in table.columns().iter().enumerate() {\n        if !col.hidden() {\n            continue;\n        }\n        hidden_count += 1;\n\n        if let Some(arg_expr) = args_iter.next() {\n            let column_expr = Expr::Column {\n                database: None,\n                table: internal_id,\n                column: i,\n                is_rowid_alias: col.is_rowid_alias(),\n            };\n            let expr = match arg_expr.as_ref() {\n                Expr::Literal(Null) => Expr::IsNull(Box::new(column_expr)),\n                other => Expr::Binary(\n                    column_expr.into(),\n                    ast::Operator::Equals,\n                    other.clone().into(),\n                ),\n            };\n            predicates.push(expr);\n        }\n    }\n\n    if args_iter.next().is_some() {\n        return Err(crate::LimboError::ParseError(format!(\n            \"Too many arguments for {}: expected at most {}, got {}\",\n            table.get_name(),\n            hidden_count,\n            hidden_count + 1 + args_iter.count()\n        )));\n    }\n\n    Ok(())\n}\n\n/// Build a stable outer-scope reference set for CTE planning.\n/// Current WITH-scope CTE entries are excluded to avoid cloning/replanning cascades.\nfn base_outer_refs_for_cte_planning(\n    refs: &[OuterQueryReference],\n    cte_definitions: &[CteDefinition],\n) -> Vec<OuterQueryReference> {\n    refs.iter()\n        .filter(|r| !cte_definitions.iter().any(|cte| cte.name == r.identifier))\n        .cloned()\n        .map(|mut r| {\n            if matches!(r.table, Table::FromClauseSubquery(_)) {\n                r.cte_select = None;\n            }\n            r\n        })\n        .collect()\n}\n\n#[allow(clippy::too_many_arguments)]\npub fn parse_from(\n    from: Option<FromClause>,\n    resolver: &Resolver,\n    program: &mut ProgramBuilder,\n    with: Option<With>,\n    preplan_ctes_for_non_from_subqueries: bool,\n    out_where_clause: &mut Vec<WhereTerm>,\n    vtab_predicates: &mut Vec<Expr>,\n    table_references: &mut TableReferences,\n    connection: &Arc<crate::Connection>,\n) -> Result<()> {\n    // Collect CTE definitions instead of planning them immediately.\n    // Each CTE reference will be planned fresh when encountered, ensuring unique internal_ids.\n    let mut cte_definitions: Vec<CteDefinition> = vec![];\n\n    if let Some(with) = with {\n        if with.recursive {\n            crate::bail_parse_error!(\"Recursive CTEs are not yet supported\");\n        }\n\n        for (idx, cte) in with.ctes.into_iter().enumerate() {\n            // Normalize explicit column names\n            let explicit_columns: Vec<String> = cte\n                .columns\n                .iter()\n                .map(|c| normalize_ident(c.col_name.as_str()))\n                .collect();\n\n            let cte_name_normalized = normalize_ident(cte.tbl_name.as_str());\n            if cte_definitions\n                .iter()\n                .any(|d| d.name == cte_name_normalized)\n            {\n                crate::bail_parse_error!(\"duplicate WITH table name: {}\", cte.tbl_name.as_str());\n            }\n            // Collect table names referenced in this CTE's FROM clause.\n            let mut referenced_tables = Vec::new();\n            collect_from_clause_table_refs(&cte.select, &mut referenced_tables);\n\n            // Find which preceding CTEs are directly referenced by this CTE.\n            // This avoids exponential re-planning when CTEs have transitive dependencies.\n            let referenced_cte_indices: SmallVec<[usize; 2]> = (0..idx)\n                .filter(|&i| referenced_tables.contains(&cte_definitions[i].name))\n                .collect();\n\n            // AS MATERIALIZED forces materialization; AS NOT MATERIALIZED prevents it\n            let materialize_hint = cte.materialized == Materialized::Yes;\n\n            cte_definitions.push(CteDefinition {\n                cte_id: program.alloc_cte_id(),\n                name: cte_name_normalized,\n                select: cte.select,\n                explicit_columns,\n                referenced_cte_indices,\n                materialize_hint,\n            });\n        }\n\n        if preplan_ctes_for_non_from_subqueries {\n            // Pre-plan all CTEs and add them to outer_query_refs for visibility.\n            // This is needed when non-FROM expressions contain subqueries that may reference\n            // CTEs from this WITH scope.\n            let base_outer_query_refs = base_outer_refs_for_cte_planning(\n                table_references.outer_query_refs(),\n                &cte_definitions,\n            );\n            for (idx, cte_def) in cte_definitions.iter().enumerate() {\n                let cte_table = plan_cte(\n                    idx,\n                    &cte_definitions,\n                    &base_outer_query_refs,\n                    resolver,\n                    program,\n                    connection,\n                    false,\n                )?;\n                table_references.add_outer_query_reference(OuterQueryReference {\n                    identifier: cte_def.name.clone(),\n                    internal_id: cte_table.internal_id,\n                    table: cte_table.table,\n                    col_used_mask: ColumnUsedMask::default(),\n                    cte_select: Some(cte_def.select.clone()),\n                    cte_explicit_columns: cte_def.explicit_columns.clone(),\n                    cte_id: Some(cte_def.cte_id),\n                    // Preplanned CTE refs are for subquery FROM lookup only. They are not\n                    // visible as column sources unless the CTE is explicitly referenced in\n                    // this scope's FROM/JOIN clause.\n                    cte_definition_only: true,\n                    rowid_referenced: false,\n                });\n            }\n        }\n    }\n\n    // Process FROM clause if present\n    if let Some(from_owned) = from {\n        let select_owned = from_owned.select;\n        let joins_owned = from_owned.joins;\n        parse_from_clause_table(\n            *select_owned,\n            resolver,\n            program,\n            table_references,\n            vtab_predicates,\n            &cte_definitions,\n            connection,\n        )?;\n\n        for join in joins_owned.into_iter() {\n            parse_join(\n                join,\n                resolver,\n                program,\n                &cte_definitions,\n                out_where_clause,\n                vtab_predicates,\n                table_references,\n                connection,\n            )?;\n        }\n    }\n\n    Ok(())\n}\n\npub fn parse_where(\n    where_clause: Option<&Expr>,\n    table_references: &mut TableReferences,\n    result_columns: Option<&[ResultSetColumn]>,\n    out_where_clause: &mut Vec<WhereTerm>,\n    resolver: &Resolver,\n) -> Result<()> {\n    if let Some(where_expr) = where_clause {\n        let start_idx = out_where_clause.len();\n        break_predicate_at_and_boundaries(where_expr, out_where_clause);\n        for expr in out_where_clause[start_idx..].iter_mut() {\n            bind_and_rewrite_expr(\n                &mut expr.expr,\n                Some(table_references),\n                result_columns,\n                resolver,\n                BindingBehavior::TryCanonicalColumnsFirst,\n            )?;\n            let _ = walk_expr_mut(&mut expr.expr, &mut |e: &mut Expr| -> Result<WalkControl> {\n                if let Expr::Between {\n                    lhs,\n                    not,\n                    start,\n                    end,\n                } = e\n                {\n                    let lhs_expr = std::mem::take(lhs.as_mut());\n                    let start_expr = std::mem::take(start.as_mut());\n                    let end_expr = std::mem::take(end.as_mut());\n\n                    let (lower, upper, combine_op) = if *not {\n                        (\n                            Expr::Binary(\n                                Box::new(start_expr),\n                                ast::Operator::Greater,\n                                Box::new(lhs_expr.clone()),\n                            ),\n                            Expr::Binary(\n                                Box::new(lhs_expr),\n                                ast::Operator::Greater,\n                                Box::new(end_expr),\n                            ),\n                            ast::Operator::Or,\n                        )\n                    } else {\n                        (\n                            Expr::Binary(\n                                Box::new(start_expr),\n                                ast::Operator::LessEquals,\n                                Box::new(lhs_expr.clone()),\n                            ),\n                            Expr::Binary(\n                                Box::new(lhs_expr),\n                                ast::Operator::LessEquals,\n                                Box::new(end_expr),\n                            ),\n                            ast::Operator::And,\n                        )\n                    };\n                    *e = Expr::Binary(Box::new(lower), combine_op, Box::new(upper));\n                }\n                Ok(WalkControl::Continue)\n            });\n        }\n        // BETWEEN in WHERE is rewritten to binary terms here so each side can be\n        // considered independently by constraint extraction and range planning.\n        // Re-break any ANDs that were created so they become separate WhereTerms for\n        // constraint extraction.\n        let mut i = start_idx;\n        while i < out_where_clause.len() {\n            if matches!(\n                &out_where_clause[i].expr,\n                Expr::Binary(_, ast::Operator::And, _)\n            ) {\n                let term = out_where_clause.remove(i);\n                let mut new_terms: Vec<WhereTerm> = Vec::new();\n                break_predicate_at_and_boundaries(&term.expr, &mut new_terms);\n                // Preserve from_outer_join from the original term\n                for new_term in new_terms.iter_mut() {\n                    new_term.from_outer_join = term.from_outer_join;\n                }\n                let count = new_terms.len();\n                for (j, new_term) in new_terms.into_iter().enumerate() {\n                    out_where_clause.insert(i + j, new_term);\n                }\n                i += count;\n            } else {\n                i += 1;\n            }\n        }\n        Ok(())\n    } else {\n        Ok(())\n    }\n}\n\n/**\n  Returns the earliest point at which a WHERE term can be evaluated.\n  For expressions referencing tables, this is the innermost loop that contains a row for each\n  table referenced in the expression.\n  For expressions not referencing any tables (e.g. constants), this is before the main loop is\n  opened, because they do not need any table data.\n*/\npub fn determine_where_to_eval_term(\n    term: &WhereTerm,\n    join_order: &[JoinOrderMember],\n    subqueries: &[NonFromClauseSubquery],\n    table_references: Option<&TableReferences>,\n) -> Result<EvalAt> {\n    if let Some(table_id) = term.from_outer_join {\n        return Ok(EvalAt::Loop(\n            join_order\n                .iter()\n                .position(|t| t.table_id == table_id)\n                .unwrap_or(usize::MAX),\n        ));\n    }\n\n    determine_where_to_eval_expr(&term.expr, join_order, subqueries, table_references)\n}\n\n/// A bitmask representing a set of tables in a query plan.\n/// Tables are numbered by their index in [SelectPlan::joined_tables].\n/// In the bitmask, the first bit is unused so that a mask with all zeros\n/// can represent \"no tables\".\n///\n/// E.g. table 0 is represented by bit index 1, table 1 by bit index 2, etc.\n///\n/// Usage in Join Optimization\n///\n/// In join optimization, [TableMask] is used to:\n/// - Generate subsets of tables for dynamic programming in join optimization\n/// - Ensure tables are joined in valid orders (e.g., respecting LEFT JOIN order)\n///\n/// Usage with constraints (WHERE clause)\n///\n/// [TableMask] helps determine:\n/// - Which tables are referenced in a constraint\n/// - When a constraint can be applied as a join condition (all referenced tables must be on the left side of the table being joined)\n///\n/// Note that although [TableReference]s contain an internal ID as well, in join order optimization\n/// the [TableMask] refers to the index of the table in the original join order, not the internal ID.\n/// This is simply because we want to represent the tables as a contiguous set of bits, and the internal ID\n/// might not be contiguous after e.g. subquery unnesting or other transformations.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub struct TableMask(pub u128);\n\nimpl std::ops::BitOrAssign for TableMask {\n    fn bitor_assign(&mut self, rhs: Self) {\n        self.0 |= rhs.0;\n    }\n}\n\nimpl TableMask {\n    /// Creates a new empty table mask.\n    ///\n    /// The initial mask represents an empty set of tables.\n    pub fn new() -> Self {\n        Self(0)\n    }\n\n    /// Returns true if the mask represents an empty set of tables.\n    pub fn is_empty(&self) -> bool {\n        self.0 == 0\n    }\n\n    /// Creates a new mask that is the same as this one but without the specified table.\n    pub fn without_table(&self, table_no: usize) -> Self {\n        turso_assert_less_than!(table_no, 127, \"table_no must be less than 127\");\n        Self(self.0 ^ (1 << (table_no + 1)))\n    }\n\n    /// Creates a table mask from raw bits.\n    ///\n    /// The bits are shifted left by 1 to maintain the convention that table 0 is at bit 1.\n    pub fn from_bits(bits: u128) -> Self {\n        Self(bits << 1)\n    }\n\n    /// Creates a table mask from an iterator of table numbers.\n    pub fn from_table_number_iter(iter: impl Iterator<Item = usize>) -> Self {\n        iter.fold(Self::new(), |mut mask, table_no| {\n            turso_assert_less_than!(table_no, 127, \"table_no must be less than 127\");\n            mask.add_table(table_no);\n            mask\n        })\n    }\n\n    /// Adds a table to the mask.\n    pub fn add_table(&mut self, table_no: usize) {\n        turso_assert_less_than!(table_no, 127, \"table_no must be less than 127\");\n        self.0 |= 1 << (table_no + 1);\n    }\n\n    /// Returns true if the mask contains the specified table.\n    pub fn contains_table(&self, table_no: usize) -> bool {\n        turso_assert_less_than!(table_no, 127, \"table_no must be less than 127\");\n        self.0 & (1 << (table_no + 1)) != 0\n    }\n\n    /// Returns true if this mask contains all tables in the other mask.\n    pub fn contains_all(&self, other: &TableMask) -> bool {\n        self.0 & other.0 == other.0\n    }\n\n    /// Returns the number of tables in the mask.\n    pub fn table_count(&self) -> usize {\n        self.0.count_ones() as usize\n    }\n\n    /// Returns true if this mask shares any tables with the other mask.\n    pub fn intersects(&self, other: &TableMask) -> bool {\n        self.0 & other.0 != 0\n    }\n\n    /// Iterate the table indices present in this mask.\n    pub fn tables_iter(&self) -> impl Iterator<Item = usize> + '_ {\n        (0..127).filter(move |table_no| self.contains_table(*table_no))\n    }\n}\n\n/// Returns a [TableMask] representing the tables referenced in the given expression.\n///\n/// This includes outer references from subqueries, even if the subquery plan has\n/// already been consumed, by relying on the cached outer reference ids.\n/// Used in the optimizer for constraint analysis.\npub fn table_mask_from_expr(\n    top_level_expr: &Expr,\n    table_references: &TableReferences,\n    subqueries: &[NonFromClauseSubquery],\n) -> Result<TableMask> {\n    let mut mask = TableMask::new();\n    walk_expr(top_level_expr, &mut |expr: &Expr| -> Result<WalkControl> {\n        match expr {\n            Expr::Column { table, .. } | Expr::RowId { table, .. } => {\n                if let Some(table_idx) = table_references\n                    .joined_tables()\n                    .iter()\n                    .position(|t| t.internal_id == *table)\n                {\n                    mask.add_table(table_idx);\n                } else if table_references\n                    .find_outer_query_ref_by_internal_id(*table)\n                    .is_none()\n                {\n                    // Tables from outer query scopes are guaranteed to be 'in scope' for this query,\n                    // so they don't need to be added to the table mask. However, if the table is not found\n                    // in the outer scope either, then it's an invalid reference.\n                    crate::bail_parse_error!(\"table not found in joined_tables\");\n                }\n            }\n            // Given something like WHERE t.a = (SELECT ...), we can only evaluate that expression\n            // when all both table 't' and all outer scope tables referenced by the subquery OR its nested subqueries are in scope.\n            // Hence, the tables referenced in subqueries must be added to the table mask.\n            Expr::SubqueryResult { subquery_id, .. } => {\n                let Some(subquery) = subqueries.iter().find(|s| s.internal_id == *subquery_id)\n                else {\n                    crate::bail_parse_error!(\"subquery not found\");\n                };\n                match &subquery.state {\n                    SubqueryState::Unevaluated { plan } => {\n                        let used_outer_query_refs = plan\n                            .as_ref()\n                            .unwrap()\n                            .table_references\n                            .outer_query_refs()\n                            .iter()\n                            .filter(|t| t.is_used());\n                        for outer_query_ref in used_outer_query_refs {\n                            if let Some(table_idx) = table_references\n                                .joined_tables()\n                                .iter()\n                                .position(|t| t.internal_id == outer_query_ref.internal_id)\n                            {\n                                mask.add_table(table_idx);\n                            }\n                        }\n                    }\n                    SubqueryState::Evaluated { outer_ref_ids, .. } => {\n                        // Now hash-join plans can now translate some correlated subqueries early, we\n                        // still revisit those predicates even though the plan has already been consumed.\n                        // Without this cache we'd panic or lose the knowledge that an outer table was required.\n                        //\n                        // Example: `SELECT t.a FROM t WHERE t.a = (SELECT MAX(x.a) FROM x WHERE x.b = t.b)`.\n                        // The outer expression `x.b = t.b` is visited after the subquery is translated,\n                        // so we need cached `outer_ref_ids` to realize that `t` must already be in scope.\n                        for outer_ref_id in outer_ref_ids {\n                            if let Some(table_idx) = table_references\n                                .joined_tables()\n                                .iter()\n                                .position(|t| t.internal_id == *outer_ref_id)\n                            {\n                                mask.add_table(table_idx);\n                            }\n                        }\n                    }\n                }\n            }\n            _ => {}\n        }\n        Ok(WalkControl::Continue)\n    })?;\n\n    Ok(mask)\n}\n\n/// Determines the earliest loop where an expression can be safely evaluated.\n///\n/// When a referenced table is not found in `join_order`, we check if it's a hash-join\n/// build table and map the condition to the probe loop where its rows are produced.\n/// Subquery references are also respected, even after their plans are consumed.\npub fn determine_where_to_eval_expr(\n    top_level_expr: &Expr,\n    join_order: &[JoinOrderMember],\n    subqueries: &[NonFromClauseSubquery],\n    table_references: Option<&TableReferences>,\n) -> Result<EvalAt> {\n    // If the expression references no tables, it can be evaluated before any table loops are opened.\n    let mut eval_at: EvalAt = EvalAt::BeforeLoop;\n    walk_expr(top_level_expr, &mut |expr: &Expr| -> Result<WalkControl> {\n        match expr {\n            Expr::Column { table, .. } | Expr::RowId { table, .. } => {\n                let Some(join_idx) = join_order.iter().position(|t| t.table_id == *table) else {\n                    // Table not found in join_order. Check if it's a hash join build table.\n                    // If so, we need to evaluate the condition at the probe table's loop position.\n                    if let Some(tables) = table_references {\n                        for (probe_idx, member) in join_order.iter().enumerate() {\n                            let probe_table = &tables.joined_tables()[member.original_idx];\n                            if let Operation::HashJoin(ref hj) = probe_table.op {\n                                let build_table = &tables.joined_tables()[hj.build_table_idx];\n                                if build_table.internal_id == *table {\n                                    // This table is the build side of a hash join.\n                                    // Evaluate the condition at the probe table's loop position.\n                                    eval_at = eval_at.max(EvalAt::Loop(probe_idx));\n                                    return Ok(WalkControl::Continue);\n                                }\n                            }\n                        }\n                    }\n                    // Must be an outer query reference; in that case, the table is already in scope.\n                    return Ok(WalkControl::Continue);\n                };\n                eval_at = eval_at.max(EvalAt::Loop(join_idx));\n            }\n            // Given something like WHERE t.a = (SELECT ...), we can only evaluate that expression\n            // when all both table 't' and all outer scope tables referenced by the subquery OR its nested subqueries are in scope.\n            Expr::SubqueryResult { subquery_id, .. } => {\n                let Some(subquery) = subqueries.iter().find(|s| s.internal_id == *subquery_id)\n                else {\n                    crate::bail_parse_error!(\"subquery not found\");\n                };\n                match &subquery.state {\n                    SubqueryState::Evaluated { evaluated_at, .. } => {\n                        eval_at = eval_at.max(*evaluated_at);\n                    }\n                    SubqueryState::Unevaluated { plan } => {\n                        let used_outer_refs = plan\n                            .as_ref()\n                            .unwrap()\n                            .table_references\n                            .outer_query_refs()\n                            .iter()\n                            .filter(|t| t.is_used());\n                        for outer_ref in used_outer_refs {\n                            let join_idx = join_order\n                                .iter()\n                                .position(|t| t.table_id == outer_ref.internal_id)\n                                .or_else(|| {\n                                    let tables = table_references?;\n                                    for (probe_idx, member) in join_order.iter().enumerate() {\n                                        let probe_table =\n                                            &tables.joined_tables()[member.original_idx];\n                                        if let Operation::HashJoin(ref hj) = probe_table.op {\n                                            let build_table =\n                                                &tables.joined_tables()[hj.build_table_idx];\n                                            if build_table.internal_id == outer_ref.internal_id {\n                                                return Some(probe_idx);\n                                            }\n                                        }\n                                    }\n                                    None\n                                });\n                            if let Some(join_idx) = join_idx {\n                                eval_at = eval_at.max(EvalAt::Loop(join_idx));\n                            }\n                        }\n                        return Ok(WalkControl::Continue);\n                    }\n                }\n            }\n            _ => {}\n        }\n        Ok(WalkControl::Continue)\n    })?;\n\n    Ok(eval_at)\n}\n\n#[allow(clippy::too_many_arguments)]\nfn parse_join(\n    join: ast::JoinedSelectTable,\n    resolver: &Resolver,\n    program: &mut ProgramBuilder,\n    cte_definitions: &[CteDefinition],\n    out_where_clause: &mut Vec<WhereTerm>,\n    vtab_predicates: &mut Vec<Expr>,\n    table_references: &mut TableReferences,\n    connection: &Arc<crate::Connection>,\n) -> Result<()> {\n    let ast::JoinedSelectTable {\n        operator: join_operator,\n        table,\n        constraint,\n    } = join;\n\n    parse_from_clause_table(\n        table.as_ref().clone(),\n        resolver,\n        program,\n        table_references,\n        vtab_predicates,\n        cte_definitions,\n        connection,\n    )?;\n\n    let is_cross = matches!(join_operator, ast::JoinOperator::TypedJoin(Some(jt)) if jt.contains(JoinType::CROSS));\n\n    let (outer, natural, full_outer) = match join_operator {\n        ast::JoinOperator::TypedJoin(Some(join_type)) => {\n            let is_right = join_type.contains(JoinType::RIGHT);\n            let is_left = join_type.contains(JoinType::LEFT);\n            let is_outer = join_type.contains(JoinType::OUTER);\n            let is_natural = join_type.contains(JoinType::NATURAL);\n            // FULL OUTER: LEFT+RIGHT or bare OUTER\n            let is_full = (is_left && is_right) || (is_outer && !is_left && !is_right);\n\n            if is_right && !is_left && !is_full {\n                // RIGHT JOIN: swap the last two tables, then treat as LEFT JOIN.\n                let len = table_references.joined_tables().len();\n                // Only valid for a two-table FROM clause; with prior joins the swap\n                // would break ON clause column references.\n                if len > 2 {\n                    crate::bail_parse_error!(\n                        \"RIGHT JOIN following another join is not yet supported. \\\n                         Try rewriting as LEFT JOIN or using a subquery.\"\n                    );\n                }\n                table_references.joined_tables_mut().swap(len - 2, len - 1);\n                table_references.set_right_join_swapped();\n                // outer flag goes on the originally-left table (now rightmost after swap).\n                (true, is_natural, false)\n            } else if is_full {\n                (true, is_natural, true)\n            } else {\n                (is_outer || is_left, is_natural, false)\n            }\n        }\n        _ => (false, false, false),\n    };\n\n    if natural && constraint.is_some() {\n        crate::bail_parse_error!(\"NATURAL JOIN cannot be combined with ON or USING clause\");\n    }\n\n    // this is called once for each join, so we only need to check the rightmost table\n    // against all previous tables for duplicates\n    let rightmost_table = table_references.joined_tables().last().unwrap();\n    let has_duplicate = table_references\n        .joined_tables()\n        .iter()\n        .take(table_references.joined_tables().len() - 1)\n        .any(|t| t.identifier == rightmost_table.identifier);\n\n    if has_duplicate\n        && !natural\n        && constraint\n            .as_ref()\n            .is_none_or(|c| !matches!(c, ast::JoinConstraint::Using(_)))\n    {\n        // Duplicate table names are only allowed for NATURAL or USING joins\n        crate::bail_parse_error!(\n            \"table name {} specified more than once - use an alias to disambiguate\",\n            rightmost_table.identifier\n        );\n    }\n    let constraint = if natural {\n        turso_assert_greater_than_or_equal!(table_references.joined_tables().len(), 2);\n        // NATURAL JOIN is first transformed into a USING join with the common columns\n        let mut distinct_names: Vec<ast::Name> = vec![];\n        // TODO: O(n^2) maybe not great for large tables or big multiway joins\n        // SQLite doesn't use HIDDEN columns for NATURAL joins: https://www3.sqlite.org/src/info/ab09ef427181130b\n        for right_col in rightmost_table.columns().iter().filter(|col| !col.hidden()) {\n            let mut found_match = false;\n            for left_table in table_references\n                .joined_tables()\n                .iter()\n                .take(table_references.joined_tables().len() - 1)\n            {\n                for left_col in left_table.columns().iter().filter(|col| !col.hidden()) {\n                    if left_col.name == right_col.name {\n                        distinct_names.push(ast::Name::exact(\n                            left_col.name.clone().expect(\"column name is None\"),\n                        ));\n                        found_match = true;\n                        break;\n                    }\n                }\n                if found_match {\n                    break;\n                }\n            }\n        }\n        if distinct_names.is_empty() {\n            None // No common columns = cross join\n        } else {\n            Some(ast::JoinConstraint::Using(distinct_names))\n        }\n    } else {\n        constraint\n    };\n\n    let mut using = vec![];\n\n    if let Some(constraint) = constraint {\n        match constraint {\n            ast::JoinConstraint::On(ref expr) => {\n                let start_idx = out_where_clause.len();\n                break_predicate_at_and_boundaries(expr, out_where_clause);\n                for predicate in out_where_clause[start_idx..].iter_mut() {\n                    predicate.from_outer_join = if outer {\n                        Some(table_references.joined_tables().last().unwrap().internal_id)\n                    } else {\n                        None\n                    };\n                    bind_and_rewrite_expr(\n                        &mut predicate.expr,\n                        Some(table_references),\n                        None,\n                        resolver,\n                        BindingBehavior::TryResultColumnsFirst,\n                    )?;\n                }\n            }\n            ast::JoinConstraint::Using(distinct_names) => {\n                // USING join is replaced with a list of equality predicates\n                for distinct_name in distinct_names.iter() {\n                    let name_normalized = normalize_ident(distinct_name.as_str());\n                    let cur_table_idx = table_references.joined_tables().len() - 1;\n                    let left_tables = &table_references.joined_tables()[..cur_table_idx];\n                    turso_assert!(!left_tables.is_empty());\n                    let right_table = table_references.joined_tables().last().unwrap();\n                    let mut left_col = None;\n                    for (left_table_idx, left_table) in left_tables.iter().enumerate() {\n                        left_col = left_table\n                            .columns()\n                            .iter()\n                            .enumerate()\n                            .filter(|(_, col)| !natural || !col.hidden())\n                            .find(|(_, col)| {\n                                col.name\n                                    .as_ref()\n                                    .is_some_and(|name| *name == name_normalized)\n                            })\n                            .map(|(idx, col)| (left_table_idx, left_table.internal_id, idx, col));\n                        if left_col.is_some() {\n                            break;\n                        }\n                    }\n                    if left_col.is_none() {\n                        crate::bail_parse_error!(\n                            \"cannot join using column {} - column not present in all tables\",\n                            distinct_name.as_str()\n                        );\n                    }\n                    let right_col = right_table.columns().iter().enumerate().find(|(_, col)| {\n                        col.name\n                            .as_ref()\n                            .is_some_and(|name| *name == name_normalized)\n                    });\n                    if right_col.is_none() {\n                        crate::bail_parse_error!(\n                            \"cannot join using column {} - column not present in all tables\",\n                            distinct_name.as_str()\n                        );\n                    }\n                    let (left_table_idx, left_table_id, left_col_idx, left_col) = left_col.unwrap();\n                    let (right_col_idx, right_col) = right_col.unwrap();\n                    let expr = Expr::Binary(\n                        Box::new(Expr::Column {\n                            database: None,\n                            table: left_table_id,\n                            column: left_col_idx,\n                            is_rowid_alias: left_col.is_rowid_alias(),\n                        }),\n                        ast::Operator::Equals,\n                        Box::new(Expr::Column {\n                            database: None,\n                            table: right_table.internal_id,\n                            column: right_col_idx,\n                            is_rowid_alias: right_col.is_rowid_alias(),\n                        }),\n                    );\n\n                    let left_table: &mut JoinedTable = table_references\n                        .joined_tables_mut()\n                        .get_mut(left_table_idx)\n                        .unwrap();\n                    left_table.mark_column_used(left_col_idx);\n                    let right_table: &mut JoinedTable = table_references\n                        .joined_tables_mut()\n                        .get_mut(cur_table_idx)\n                        .unwrap();\n                    right_table.mark_column_used(right_col_idx);\n                    out_where_clause.push(WhereTerm {\n                        expr,\n                        from_outer_join: if outer {\n                            Some(right_table.internal_id)\n                        } else {\n                            None\n                        },\n                        consumed: false,\n                    });\n                }\n                using = distinct_names;\n            }\n        }\n    }\n\n    assert!(table_references.joined_tables().len() >= 2);\n    let last_idx = table_references.joined_tables().len() - 1;\n    let rightmost_table = table_references\n        .joined_tables_mut()\n        .get_mut(last_idx)\n        .unwrap();\n    let plan_join_type = if full_outer {\n        PlanJoinType::FullOuter\n    } else if outer {\n        PlanJoinType::LeftOuter\n    } else {\n        PlanJoinType::Inner\n    };\n    rightmost_table.join_info = Some(JoinInfo {\n        join_type: plan_join_type,\n        using,\n        no_reorder: is_cross,\n    });\n\n    Ok(())\n}\n\npub fn break_predicate_at_and_boundaries<T: From<Expr>>(\n    predicate: &Expr,\n    out_predicates: &mut Vec<T>,\n) {\n    // Unwrap single-element parenthesized expressions recursively: ((expr)) -> expr.\n    // This is semantically equivalent since single-element Parenthesized is purely\n    // syntactic grouping. Multi-element Parenthesized (row values like (x, y)) are\n    // left as-is by unwrap_parens.\n    let predicate = unwrap_parens(predicate).unwrap_or(predicate);\n    match predicate {\n        Expr::Binary(left, ast::Operator::And, right) => {\n            break_predicate_at_and_boundaries(left, out_predicates);\n            break_predicate_at_and_boundaries(right, out_predicates);\n        }\n        _ => {\n            out_predicates.push(predicate.clone().into());\n        }\n    }\n}\n\npub fn parse_row_id<F>(\n    column_name: &str,\n    table_id: TableInternalId,\n    fn_check: F,\n) -> Result<Option<Expr>>\nwhere\n    F: FnOnce() -> bool,\n{\n    if ROWID_STRS\n        .iter()\n        .any(|s| s.eq_ignore_ascii_case(column_name))\n    {\n        if fn_check() {\n            crate::bail_parse_error!(\"ROWID is ambiguous\");\n        }\n\n        return Ok(Some(Expr::RowId {\n            database: None, // TODO: support different databases\n            table: table_id,\n        }));\n    }\n    Ok(None)\n}\n\n#[allow(clippy::type_complexity)]\npub fn parse_limit(\n    mut limit: Limit,\n    resolver: &Resolver,\n) -> Result<(Option<Box<Expr>>, Option<Box<Expr>>)> {\n    bind_and_rewrite_expr(\n        &mut limit.expr,\n        None,\n        None,\n        resolver,\n        BindingBehavior::TryResultColumnsFirst,\n    )?;\n    if let Some(ref mut off_expr) = limit.offset {\n        bind_and_rewrite_expr(\n            off_expr,\n            None,\n            None,\n            resolver,\n            BindingBehavior::TryResultColumnsFirst,\n        )?;\n    }\n    Ok((Some(limit.expr), limit.offset))\n}\n"
  },
  {
    "path": "core/translate/pragma.rs",
    "content": "//! VDBE bytecode generation for pragma statements.\n//! More info: https://www.sqlite.org/pragma.html.\n\nuse crate::sync::Arc;\nuse crate::turso_soft_unreachable;\nuse chrono::Datelike;\nuse turso_macros::match_ignore_ascii_case;\nuse turso_parser::ast::PragmaName;\nuse turso_parser::ast::{self, Expr, Literal};\n\nuse super::integrity_check::{\n    translate_integrity_check, translate_quick_check, MAX_INTEGRITY_CHECK_ERRORS,\n};\nuse crate::function::Func;\nuse crate::pragma::pragma_for;\nuse crate::schema::Schema;\nuse crate::storage::encryption::{CipherMode, EncryptionKey};\nuse crate::storage::pager::AutoVacuumMode;\nuse crate::storage::pager::Pager;\nuse crate::storage::sqlite3_ondisk::CacheSize;\nuse crate::storage::wal::CheckpointMode;\nuse crate::translate::emitter::{Resolver, TransactionMode};\nuse crate::util::{normalize_ident, parse_signed_number, parse_string, IOExt as _};\nuse crate::vdbe::builder::{ProgramBuilder, ProgramBuilderOpts};\nuse crate::vdbe::insn::{Cookie, Insn};\nuse crate::{bail_parse_error, CaptureDataChangesInfo, LimboError, Numeric, Value};\nuse std::str::FromStr;\nuse strum::IntoEnumIterator;\n\nfn list_pragmas(program: &mut ProgramBuilder) {\n    for x in PragmaName::iter() {\n        let register = program.emit_string8_new_reg(x.to_string());\n        program.emit_result_row(register, 1);\n    }\n    program.add_pragma_result_column(\"pragma_list\".into());\n}\n\n/// Parse max_errors from an optional value expression.\n/// Returns the parsed integer if value is a numeric literal, otherwise returns the default.\nfn parse_max_errors_from_value(value: &Option<Expr>) -> usize {\n    match value {\n        Some(Expr::Literal(Literal::Numeric(n))) => {\n            n.parse::<usize>().unwrap_or(MAX_INTEGRITY_CHECK_ERRORS)\n        }\n        _ => MAX_INTEGRITY_CHECK_ERRORS,\n    }\n}\n\npub fn translate_pragma(\n    resolver: &Resolver,\n    name: &ast::QualifiedName,\n    body: Option<ast::PragmaBody>,\n    pager: Arc<Pager>,\n    connection: Arc<crate::Connection>,\n    program: &mut ProgramBuilder,\n) -> crate::Result<()> {\n    let opts = ProgramBuilderOpts {\n        num_cursors: 0,\n        approx_num_insns: 20,\n        approx_num_labels: 0,\n    };\n    program.extend(&opts);\n\n    if name.name.as_str().eq_ignore_ascii_case(\"pragma_list\") {\n        list_pragmas(program);\n        return Ok(());\n    }\n\n    let pragma = match PragmaName::from_str(name.name.as_str()) {\n        Ok(pragma) => pragma,\n        Err(_) => bail_parse_error!(\"Not a valid pragma name\"),\n    };\n\n    let database_id = resolver.resolve_database_id(name)?;\n\n    let mode = match body {\n        None => query_pragma(\n            pragma,\n            resolver,\n            None,\n            pager,\n            connection,\n            database_id,\n            program,\n        )?,\n        Some(ast::PragmaBody::Equals(value) | ast::PragmaBody::Call(value)) => match pragma {\n            // These pragmas take a parameter but are queries, not setters\n            PragmaName::IndexInfo\n            | PragmaName::IndexXinfo\n            | PragmaName::IndexList\n            | PragmaName::TableList\n            | PragmaName::TableInfo\n            | PragmaName::TableXinfo\n            | PragmaName::IntegrityCheck\n            | PragmaName::DatabaseList\n            | PragmaName::QuickCheck => query_pragma(\n                pragma,\n                resolver,\n                Some(*value),\n                pager,\n                connection,\n                database_id,\n                program,\n            )?,\n            _ => update_pragma(\n                pragma,\n                resolver,\n                *value,\n                pager,\n                connection,\n                database_id,\n                program,\n            )?,\n        },\n    };\n    match mode {\n        TransactionMode::None => {}\n        TransactionMode::Read => {\n            if crate::is_attached_db(database_id) {\n                let schema_cookie = resolver.with_schema(database_id, |s| s.schema_version);\n                program.begin_read_on_database(database_id, schema_cookie);\n            }\n            program.begin_read_operation();\n        }\n        TransactionMode::Write => {\n            if crate::is_attached_db(database_id) {\n                let schema_cookie = resolver.with_schema(database_id, |s| s.schema_version);\n                program.begin_write_on_database(database_id, schema_cookie);\n            }\n            program.begin_write_operation();\n        }\n        TransactionMode::Concurrent => {\n            program.begin_concurrent_operation();\n        }\n    }\n\n    Ok(())\n}\n\nfn update_pragma(\n    pragma: PragmaName,\n    resolver: &Resolver,\n    value: ast::Expr,\n    pager: Arc<Pager>,\n    connection: Arc<crate::Connection>,\n    database_id: usize,\n    program: &mut ProgramBuilder,\n) -> crate::Result<TransactionMode> {\n    let parse_pragma_enabled = |expr: &ast::Expr| -> bool {\n        if let Expr::Literal(Literal::Numeric(n)) = expr {\n            return !matches!(n.as_str(), \"0\");\n        };\n        let name_bytes = match expr {\n            Expr::Literal(Literal::Keyword(name)) => name.as_bytes(),\n            Expr::Name(name) | Expr::Id(name) => name.as_str().as_bytes(),\n            _ => \"\".as_bytes(),\n        };\n        match_ignore_ascii_case!(match name_bytes {\n            b\"ON\" | b\"TRUE\" | b\"YES\" | b\"1\" => true,\n            _ => false,\n        })\n    };\n    match pragma {\n        PragmaName::ApplicationId => {\n            let data = parse_signed_number(&value)?;\n            let app_id_value = match data {\n                Value::Numeric(Numeric::Integer(i)) => i as i32,\n                Value::Numeric(Numeric::Float(f)) => f64::from(f) as i32,\n                _ => bail_parse_error!(\"expected integer, got {:?}\", data),\n            };\n\n            program.emit_insn(Insn::SetCookie {\n                db: database_id,\n                cookie: Cookie::ApplicationId,\n                value: app_id_value,\n                p5: 1,\n            });\n            Ok(TransactionMode::Write)\n        }\n        PragmaName::BusyTimeout => {\n            let data = parse_signed_number(&value)?;\n            let busy_timeout_ms = match data {\n                Value::Numeric(Numeric::Integer(i)) => i as i32,\n                Value::Numeric(Numeric::Float(f)) => f64::from(f) as i32,\n                _ => bail_parse_error!(\"expected integer, got {:?}\", data),\n            };\n            let busy_timeout_ms = busy_timeout_ms.max(0);\n            connection.set_busy_timeout(std::time::Duration::from_millis(busy_timeout_ms as u64));\n            Ok(TransactionMode::Write)\n        }\n        PragmaName::CacheSize => {\n            let cache_size = match parse_signed_number(&value)? {\n                Value::Numeric(Numeric::Integer(size)) => size,\n                Value::Numeric(Numeric::Float(size)) => f64::from(size) as i64,\n                _ => bail_parse_error!(\"Invalid value for cache size pragma\"),\n            };\n            update_cache_size(cache_size, pager, connection)?;\n            Ok(TransactionMode::None)\n        }\n        PragmaName::CacheSpill => {\n            let enabled = parse_pragma_enabled(&value);\n            connection.get_pager().set_spill_enabled(enabled);\n            connection.bump_prepare_context_generation();\n            Ok(TransactionMode::None)\n        }\n        PragmaName::Encoding => {\n            let year = chrono::Local::now().year();\n            bail_parse_error!(\"It's {year}. UTF-8 won.\");\n        }\n        PragmaName::JournalMode => {\n            // For JournalMode, when setting a value, we use the opcode\n            let mode_str = match value {\n                Expr::Name(name) => name.as_str().to_string(),\n                Expr::Literal(Literal::Keyword(ref kw)) => kw.clone(),\n                _ => parse_string(&value)?,\n            };\n\n            let result_reg = program.alloc_register();\n            program.emit_insn(Insn::JournalMode {\n                db: database_id,\n                dest: result_reg,\n                new_mode: Some(mode_str),\n            });\n\n            program.emit_result_row(result_reg, 1);\n            program.add_pragma_result_column(\"journal_mode\".into());\n            Ok(TransactionMode::None)\n        }\n        PragmaName::LegacyFileFormat => Ok(TransactionMode::None),\n        PragmaName::WalCheckpoint => query_pragma(\n            PragmaName::WalCheckpoint,\n            resolver,\n            Some(value),\n            pager,\n            connection,\n            database_id,\n            program,\n        ),\n        PragmaName::ModuleList => Ok(TransactionMode::None),\n        PragmaName::PageCount => query_pragma(\n            PragmaName::PageCount,\n            resolver,\n            None,\n            pager,\n            connection,\n            database_id,\n            program,\n        ),\n        PragmaName::MaxPageCount => {\n            let data = parse_signed_number(&value)?;\n            let max_page_count_value = match data {\n                Value::Numeric(Numeric::Integer(i)) => i as usize,\n                Value::Numeric(Numeric::Float(f)) => f64::from(f) as usize,\n                _ => unreachable!(),\n            };\n\n            let result_reg = program.alloc_register();\n            program.emit_insn(Insn::MaxPgcnt {\n                db: database_id,\n                dest: result_reg,\n                new_max: max_page_count_value,\n            });\n            program.emit_result_row(result_reg, 1);\n            program.add_pragma_result_column(\"max_page_count\".into());\n            Ok(TransactionMode::Write)\n        }\n        PragmaName::UserVersion => {\n            let data = parse_signed_number(&value)?;\n            let version_value = match data {\n                Value::Numeric(Numeric::Integer(i)) => i as i32,\n                Value::Numeric(Numeric::Float(f)) => f64::from(f) as i32,\n                _ => unreachable!(),\n            };\n\n            program.emit_insn(Insn::SetCookie {\n                db: database_id,\n                cookie: Cookie::UserVersion,\n                value: version_value,\n                p5: 1,\n            });\n            Ok(TransactionMode::Write)\n        }\n        PragmaName::SchemaVersion => {\n            // SQLite allowing this to be set is an incredibly stupid idea in my view.\n            // In \"defensive mode\", this is a silent nop. So let's emulate that always.\n            program.emit_insn(Insn::Noop {});\n            Ok(TransactionMode::None)\n        }\n        PragmaName::TableInfo => {\n            // because we need control over the write parameter for the transaction,\n            // this should be unreachable. We have to force-call query_pragma before\n            // getting here\n            unreachable!();\n        }\n        PragmaName::TableXinfo => {\n            // because we need control over the write parameter for the transaction,\n            // this should be unreachable. We have to force-call query_pragma before\n            // getting here\n            unreachable!();\n        }\n        PragmaName::PageSize => {\n            let page_size = match parse_signed_number(&value)? {\n                Value::Numeric(Numeric::Integer(size)) => size,\n                Value::Numeric(Numeric::Float(size)) => f64::from(size) as i64,\n                _ => bail_parse_error!(\"Invalid value for page size pragma\"),\n            };\n            update_page_size(connection, page_size as u32)?;\n            Ok(TransactionMode::None)\n        }\n        PragmaName::AutoVacuum => {\n            // Check if autovacuum is enabled in database opts\n            if !connection.db.opts.enable_autovacuum {\n                return Err(LimboError::InvalidArgument(\n                    \"Autovacuum is not enabled. Use --experimental-autovacuum flag to enable it.\"\n                        .to_string(),\n                ));\n            }\n\n            let is_empty = is_database_empty(resolver.schema(), &pager)?;\n            tracing::debug!(\n                \"Checking if database is empty for auto_vacuum pragma: {}\",\n                is_empty\n            );\n\n            if !is_empty {\n                // SQLite's behavior is to silently ignore this pragma if the database is not empty.\n                tracing::debug!(\n                    \"Attempted to set auto_vacuum, database is not empty so we are ignoring pragma.\"\n                );\n                return Ok(TransactionMode::None);\n            }\n\n            let auto_vacuum_mode = match value {\n                Expr::Name(name) => {\n                    let name = name.as_str().as_bytes();\n                    match_ignore_ascii_case!(match name {\n                        b\"none\" => 0,\n                        b\"full\" => 1,\n                        b\"incremental\" => 2,\n                        _ => {\n                            return Err(LimboError::InvalidArgument(\n                                \"invalid auto vacuum mode\".to_string(),\n                            ));\n                        }\n                    })\n                }\n                _ => {\n                    return Err(LimboError::InvalidArgument(\n                        \"invalid auto vacuum mode\".to_string(),\n                    ));\n                }\n            };\n            match auto_vacuum_mode {\n                0 => update_auto_vacuum_mode(AutoVacuumMode::None, 0, pager)?,\n                1 => update_auto_vacuum_mode(AutoVacuumMode::Full, 1, pager)?,\n                2 => update_auto_vacuum_mode(AutoVacuumMode::Incremental, 1, pager)?,\n                _ => {\n                    return Err(LimboError::InvalidArgument(\n                        \"invalid auto vacuum mode\".to_string(),\n                    ));\n                }\n            }\n            let largest_root_page_number_reg = program.alloc_register();\n            program.emit_insn(Insn::ReadCookie {\n                db: database_id,\n                dest: largest_root_page_number_reg,\n                cookie: Cookie::LargestRootPageNumber,\n            });\n            let set_cookie_label = program.allocate_label();\n            program.emit_insn(Insn::If {\n                reg: largest_root_page_number_reg,\n                target_pc: set_cookie_label,\n                jump_if_null: false,\n            });\n            program.emit_insn(Insn::Halt {\n                err_code: 0,\n                description: \"Early halt because auto vacuum mode is not enabled\".to_string(),\n                on_error: None,\n                description_reg: None,\n            });\n            program.resolve_label(set_cookie_label, program.offset());\n            program.emit_insn(Insn::SetCookie {\n                db: database_id,\n                cookie: Cookie::IncrementalVacuum,\n                value: auto_vacuum_mode - 1,\n                p5: 0,\n            });\n            Ok(TransactionMode::None)\n        }\n        PragmaName::IntegrityCheck => unreachable!(\"integrity_check cannot be set\"),\n        PragmaName::QuickCheck => unreachable!(\"quick_check cannot be set\"),\n        PragmaName::CaptureDataChangesConn | PragmaName::UnstableCaptureDataChangesConn => {\n            let value = parse_string(&value)?;\n            let opts = CaptureDataChangesInfo::parse(&value, Some(CDC_VERSION_CURRENT))?;\n            if opts.is_some() && connection.mvcc_enabled() {\n                bail_parse_error!(\"CDC is not supported in MVCC mode\");\n            }\n            // InitCdcVersion handles everything at execution time:\n            // - For enable: creates CDC table + version table, records version,\n            //   reads back actual version, defers CDC state to Halt\n            // - For disable (\"off\"): defers CDC=None to Halt\n            let cdc_table_name = opts\n                .as_ref()\n                .map(|i| i.table.to_string())\n                .unwrap_or_default();\n            program.emit_insn(Insn::InitCdcVersion {\n                cdc_table_name,\n                version: CDC_VERSION_CURRENT,\n                cdc_mode: value,\n            });\n            Ok(TransactionMode::Write)\n        }\n        PragmaName::DatabaseList => unreachable!(\"database_list cannot be set\"),\n        PragmaName::IndexInfo => unreachable!(\"index_info cannot be set\"),\n        PragmaName::IndexXinfo => unreachable!(\"index_xinfo cannot be set\"),\n        PragmaName::IndexList => unreachable!(\"index_list cannot be set\"),\n        PragmaName::TableList => unreachable!(\"table_list cannot be set\"),\n        PragmaName::QueryOnly => query_pragma(\n            PragmaName::QueryOnly,\n            resolver,\n            Some(value),\n            pager,\n            connection,\n            database_id,\n            program,\n        ),\n        PragmaName::FreelistCount => query_pragma(\n            PragmaName::FreelistCount,\n            resolver,\n            Some(value),\n            pager,\n            connection,\n            database_id,\n            program,\n        ),\n        PragmaName::EncryptionKey => {\n            let value = parse_string(&value)?;\n            let key = EncryptionKey::from_hex_string(&value)?;\n            connection.set_encryption_key(key)?;\n            Ok(TransactionMode::None)\n        }\n        PragmaName::EncryptionCipher => {\n            let value = parse_string(&value)?;\n            let cipher = CipherMode::try_from(value.as_str())?;\n            connection.set_encryption_cipher(cipher)?;\n            Ok(TransactionMode::None)\n        }\n        PragmaName::Synchronous => {\n            use crate::SyncMode;\n            let mode = if let Expr::Literal(Literal::Numeric(n)) = &value {\n                match n.as_str() {\n                    \"0\" => SyncMode::Off,\n                    \"1\" => SyncMode::Normal,\n                    _ => SyncMode::Full, // SQLite defaults to NORMAL for invalid values, but we want to default to a higher durability level so deviating here.\n                }\n            } else {\n                let name_bytes = match &value {\n                    Expr::Literal(Literal::Keyword(name)) => name.as_bytes(),\n                    Expr::Name(name) | Expr::Id(name) => name.as_str().as_bytes(),\n                    _ => b\"\",\n                };\n                match_ignore_ascii_case!(match name_bytes {\n                    b\"OFF\" | b\"0\" => SyncMode::Off,\n                    b\"NORMAL\" | b\"1\" => SyncMode::Normal,\n                    _ => SyncMode::Full,\n                })\n            };\n            connection.set_sync_mode(mode);\n            Ok(TransactionMode::None)\n        }\n        PragmaName::DataSyncRetry => {\n            let retry_enabled = parse_pragma_enabled(&value);\n            connection.set_data_sync_retry(retry_enabled);\n            Ok(TransactionMode::None)\n        }\n        PragmaName::MvccCheckpointThreshold => {\n            let threshold = match parse_signed_number(&value)? {\n                Value::Numeric(Numeric::Integer(size)) if size >= -1 => size,\n                _ => bail_parse_error!(\n                    \"mvcc_checkpoint_threshold must be -1, 0, or a positive integer\"\n                ),\n            };\n\n            connection.set_mvcc_checkpoint_threshold(threshold)?;\n            Ok(TransactionMode::None)\n        }\n        PragmaName::ForeignKeys => {\n            let enabled = parse_pragma_enabled(&value);\n            connection.set_foreign_keys_enabled(enabled);\n            Ok(TransactionMode::None)\n        }\n        PragmaName::IAmADummy | PragmaName::RequireWhere => {\n            let enabled = parse_pragma_enabled(&value);\n            connection.set_dml_require_where(enabled);\n            Ok(TransactionMode::None)\n        }\n        PragmaName::IgnoreCheckConstraints => {\n            let enabled = parse_pragma_enabled(&value);\n            connection.set_check_constraints_ignored(enabled);\n            Ok(TransactionMode::None)\n        }\n        #[cfg(target_vendor = \"apple\")]\n        PragmaName::Fullfsync => {\n            let enabled = parse_pragma_enabled(&value);\n            let sync_type = if enabled {\n                crate::io::FileSyncType::FullFsync\n            } else {\n                crate::io::FileSyncType::Fsync\n            };\n            connection.set_sync_type(sync_type);\n            Ok(TransactionMode::None)\n        }\n        PragmaName::ListTypes => bail_parse_error!(\"list_types cannot be set\"),\n        PragmaName::TempStore => {\n            use crate::TempStore;\n            // Try to parse as a string first (default, file, memory)\n            let temp_store = if let Expr::Literal(Literal::Numeric(n)) = &value {\n                // Numeric value: 0, 1, or 2\n                match n.as_str() {\n                    \"0\" => TempStore::Default,\n                    \"1\" => TempStore::File,\n                    \"2\" => TempStore::Memory,\n                    _ => bail_parse_error!(\"temp_store must be 0, 1, 2, DEFAULT, FILE, or MEMORY\"),\n                }\n            } else {\n                // Try as keyword/identifier: DEFAULT, FILE, MEMORY\n                let name_bytes = match &value {\n                    Expr::Literal(Literal::Keyword(name)) => name.as_bytes(),\n                    Expr::Name(name) | Expr::Id(name) => name.as_str().as_bytes(),\n                    Expr::Literal(Literal::String(s)) => s.as_bytes(),\n                    _ => bail_parse_error!(\"temp_store must be 0, 1, 2, DEFAULT, FILE, or MEMORY\"),\n                };\n                match_ignore_ascii_case!(match name_bytes {\n                    b\"DEFAULT\" | b\"0\" => TempStore::Default,\n                    b\"FILE\" | b\"1\" => TempStore::File,\n                    b\"MEMORY\" | b\"2\" => TempStore::Memory,\n                    _ => bail_parse_error!(\"temp_store must be 0, 1, 2, DEFAULT, FILE, or MEMORY\"),\n                })\n            };\n            connection.set_temp_store(temp_store);\n            Ok(TransactionMode::None)\n        }\n        PragmaName::FunctionList => query_pragma(\n            PragmaName::FunctionList,\n            resolver,\n            Some(value),\n            pager,\n            connection,\n            database_id,\n            program,\n        ),\n    }\n}\n\nfn query_pragma(\n    pragma: PragmaName,\n    resolver: &Resolver,\n    value: Option<ast::Expr>,\n    pager: Arc<Pager>,\n    connection: Arc<crate::Connection>,\n    database_id: usize,\n    program: &mut ProgramBuilder,\n) -> crate::Result<TransactionMode> {\n    let schema = resolver.schema();\n    let register = program.alloc_register();\n    match pragma {\n        PragmaName::ApplicationId => {\n            program.emit_insn(Insn::ReadCookie {\n                db: database_id,\n                dest: register,\n                cookie: Cookie::ApplicationId,\n            });\n            program.add_pragma_result_column(pragma.to_string());\n            program.emit_result_row(register, 1);\n            Ok(TransactionMode::Read)\n        }\n        PragmaName::BusyTimeout => {\n            program.emit_int(connection.get_busy_timeout().as_millis() as i64, register);\n            program.emit_result_row(register, 1);\n            program.add_pragma_result_column(pragma.to_string());\n            Ok(TransactionMode::None)\n        }\n        PragmaName::CacheSize => {\n            program.emit_int(connection.get_cache_size() as i64, register);\n            program.emit_result_row(register, 1);\n            program.add_pragma_result_column(pragma.to_string());\n            Ok(TransactionMode::None)\n        }\n        PragmaName::CacheSpill => {\n            let spill_enabled = connection.get_pager().get_spill_enabled();\n            program.emit_int(spill_enabled as i64, register);\n            program.emit_result_row(register, 1);\n            program.add_pragma_result_column(pragma.to_string());\n            Ok(TransactionMode::None)\n        }\n        PragmaName::DatabaseList => {\n            let base_reg = register;\n            program.alloc_registers(2);\n\n            // Get all databases (main + attached) and emit a row for each\n            let all_databases = connection.list_all_databases();\n            for (seq_number, name, file_path) in all_databases {\n                // seq (sequence number)\n                program.emit_int(seq_number as i64, base_reg);\n\n                // name (alias)\n                program.emit_string8(name, base_reg + 1);\n\n                // file path\n                program.emit_string8(file_path, base_reg + 2);\n\n                program.emit_result_row(base_reg, 3);\n            }\n\n            let pragma = pragma_for(&pragma);\n            for col_name in pragma.columns.iter() {\n                program.add_pragma_result_column(col_name.to_string());\n            }\n            Ok(TransactionMode::None)\n        }\n        PragmaName::Encoding => {\n            let encoding = pager\n                .io\n                .block(|| pager.with_header(|header| header.text_encoding))\n                .unwrap_or_default()\n                .to_string();\n            program.emit_string8(encoding, register);\n            program.emit_result_row(register, 1);\n            program.add_pragma_result_column(pragma.to_string());\n            Ok(TransactionMode::None)\n        }\n        PragmaName::JournalMode => {\n            // Use the JournalMode opcode to get the current journal mode\n            program.emit_insn(Insn::JournalMode {\n                db: database_id,\n                dest: register,\n                new_mode: None,\n            });\n            program.emit_result_row(register, 1);\n            program.add_pragma_result_column(pragma.to_string());\n            Ok(TransactionMode::None)\n        }\n        PragmaName::LegacyFileFormat => Ok(TransactionMode::None),\n        PragmaName::WalCheckpoint => {\n            // Checkpoint uses 3 registers: P1, P2, P3. Ref Insn::Checkpoint for more info.\n            // Allocate two more here as one was allocated at the top.\n            let mode = match value {\n                Some(ast::Expr::Name(name)) => {\n                    let mode_name = normalize_ident(name.as_str());\n                    CheckpointMode::from_str(&mode_name).map_err(|e| {\n                        LimboError::ParseError(format!(\"Unknown Checkpoint Mode: {e}\"))\n                    })?\n                }\n                _ => CheckpointMode::Passive {\n                    upper_bound_inclusive: None,\n                },\n            };\n\n            program.alloc_registers(2);\n            program.emit_insn(Insn::Checkpoint {\n                database: database_id,\n                checkpoint_mode: mode,\n                dest: register,\n            });\n            program.emit_result_row(register, 3);\n            program.add_pragma_result_column(\"busy\".to_string());\n            program.add_pragma_result_column(\"log\".to_string());\n            program.add_pragma_result_column(\"checkpointed\".to_string());\n            Ok(TransactionMode::None)\n        }\n        PragmaName::ModuleList => {\n            let modules = connection.get_syms_vtab_mods();\n            for module in modules {\n                program.emit_string8(module.to_string(), register);\n                program.emit_result_row(register, 1);\n            }\n\n            program.add_pragma_result_column(pragma.to_string());\n            Ok(TransactionMode::None)\n        }\n        PragmaName::FunctionList => {\n            // 6 columns: name, builtin, type, enc, narg, flags\n            let base_reg = register;\n            program.alloc_registers(5);\n\n            const SQLITE_DETERMINISTIC: i64 = 0x800;\n            const SQLITE_INNOCUOUS: i64 = 0x200000;\n\n            // Built-in functions\n            for entry in Func::builtin_function_list() {\n                let mut flags: i64 = 0;\n                if entry.deterministic {\n                    flags |= SQLITE_DETERMINISTIC;\n                }\n                flags |= SQLITE_INNOCUOUS;\n\n                program.emit_string8(entry.name, base_reg);\n                program.emit_int(1, base_reg + 1); // builtin = 1\n                program.emit_string8(entry.func_type.to_string(), base_reg + 2);\n                program.emit_string8(\"utf8\".to_string(), base_reg + 3);\n                program.emit_int(entry.narg as i64, base_reg + 4);\n                program.emit_int(flags, base_reg + 5);\n                program.emit_result_row(base_reg, 6);\n            }\n\n            // External (extension) functions\n            for (name, is_agg, argc) in connection.get_syms_functions() {\n                let func_type = if is_agg { \"a\" } else { \"s\" };\n                program.emit_string8(name, base_reg);\n                program.emit_int(0, base_reg + 1); // builtin = 0\n                program.emit_string8(func_type.to_string(), base_reg + 2);\n                program.emit_string8(\"utf8\".to_string(), base_reg + 3);\n                program.emit_int(argc as i64, base_reg + 4);\n                program.emit_int(0, base_reg + 5); // flags = 0 for extensions\n                program.emit_result_row(base_reg, 6);\n            }\n\n            let pragma_meta = pragma_for(&pragma);\n            for col_name in pragma_meta.columns.iter() {\n                program.add_pragma_result_column(col_name.to_string());\n            }\n            Ok(TransactionMode::None)\n        }\n        PragmaName::PageCount => {\n            program.emit_insn(Insn::PageCount {\n                db: database_id,\n                dest: register,\n            });\n            program.emit_result_row(register, 1);\n            program.add_pragma_result_column(pragma.to_string());\n            Ok(TransactionMode::Read)\n        }\n        PragmaName::MaxPageCount => {\n            program.emit_insn(Insn::MaxPgcnt {\n                db: database_id,\n                dest: register,\n                new_max: 0, // 0 means just return current max\n            });\n            program.emit_result_row(register, 1);\n            program.add_pragma_result_column(pragma.to_string());\n            Ok(TransactionMode::Read)\n        }\n        PragmaName::IndexInfo => {\n            let index_name = match value {\n                Some(ast::Expr::Name(name)) => Some(normalize_ident(name.as_str())),\n                _ => None,\n            };\n\n            let base_reg = register;\n            // 3 columns: seqno, cid, name\n            program.alloc_registers(2);\n\n            if let Some(index_name) = index_name {\n                let index = schema\n                    .indexes\n                    .values()\n                    .flatten()\n                    .find(|idx| idx.name.eq_ignore_ascii_case(&index_name));\n\n                if let Some(index) = index {\n                    for (seqno, col) in index.columns.iter().enumerate() {\n                        program.emit_int(seqno as i64, base_reg);\n                        program.emit_int(col.pos_in_table as i64, base_reg + 1);\n                        program.emit_string8(col.name.clone(), base_reg + 2);\n                        program.emit_result_row(base_reg, 3);\n                    }\n                }\n            }\n\n            let pragma_meta = pragma_for(&pragma);\n            for col_name in pragma_meta.columns.iter() {\n                program.add_pragma_result_column(col_name.to_string());\n            }\n            Ok(TransactionMode::None)\n        }\n        PragmaName::IndexXinfo => {\n            let index_name = match value {\n                Some(ast::Expr::Name(name)) => Some(normalize_ident(name.as_str())),\n                _ => None,\n            };\n\n            let base_reg = register;\n            // 6 columns: seqno, cid, name, desc, coll, key\n            program.alloc_registers(5);\n\n            if let Some(index_name) = index_name {\n                let index = schema\n                    .indexes\n                    .values()\n                    .flatten()\n                    .find(|idx| idx.name.eq_ignore_ascii_case(&index_name));\n\n                if let Some(index) = index {\n                    for (seqno, col) in index.columns.iter().enumerate() {\n                        let desc = matches!(col.order, ast::SortOrder::Desc);\n                        let coll = col\n                            .collation\n                            .map(|c| c.to_string().to_uppercase())\n                            .unwrap_or_else(|| \"BINARY\".to_string());\n\n                        program.emit_int(seqno as i64, base_reg);\n                        program.emit_int(col.pos_in_table as i64, base_reg + 1);\n                        program.emit_string8(col.name.clone(), base_reg + 2);\n                        program.emit_int(desc as i64, base_reg + 3);\n                        program.emit_string8(coll, base_reg + 4);\n                        program.emit_int(1, base_reg + 5); // key column\n                        program.emit_result_row(base_reg, 6);\n                    }\n\n                    // Emit trailing rowid row if the index has one\n                    if index.has_rowid {\n                        let seqno = index.columns.len();\n                        program.emit_int(seqno as i64, base_reg);\n                        program.emit_int(-1, base_reg + 1);\n                        program.emit_string8(String::new(), base_reg + 2);\n                        program.emit_int(0, base_reg + 3);\n                        program.emit_string8(\"BINARY\".to_string(), base_reg + 4);\n                        program.emit_int(0, base_reg + 5); // not a key column\n                        program.emit_result_row(base_reg, 6);\n                    }\n                }\n            }\n\n            let pragma_meta = pragma_for(&pragma);\n            for col_name in pragma_meta.columns.iter() {\n                program.add_pragma_result_column(col_name.to_string());\n            }\n            Ok(TransactionMode::None)\n        }\n        PragmaName::IndexList => {\n            let table_name = match value {\n                Some(ast::Expr::Name(name)) => Some(normalize_ident(name.as_str())),\n                _ => None,\n            };\n\n            let base_reg = register;\n            // 5 columns: seq, name, unique, origin, partial\n            program.alloc_registers(4);\n\n            if let Some(table_name) = table_name {\n                if let Some(table) = schema.get_table(&table_name) {\n                    let pk_cols: Vec<String> = table\n                        .btree()\n                        .map(|bt| {\n                            bt.primary_key_columns\n                                .iter()\n                                .map(|(name, _)| name.clone())\n                                .collect()\n                        })\n                        .unwrap_or_default();\n\n                    for (seq, index) in schema.get_indices(&table_name).enumerate() {\n                        let origin = if index.name.starts_with(\"sqlite_autoindex_\") {\n                            let idx_cols: Vec<&str> =\n                                index.columns.iter().map(|c| c.name.as_str()).collect();\n                            if idx_cols.len() == pk_cols.len()\n                                && idx_cols\n                                    .iter()\n                                    .zip(pk_cols.iter())\n                                    .all(|(a, b)| a.eq_ignore_ascii_case(b))\n                            {\n                                \"pk\"\n                            } else {\n                                \"u\"\n                            }\n                        } else {\n                            \"c\"\n                        };\n\n                        program.emit_int(seq as i64, base_reg);\n                        program.emit_string8(index.name.clone(), base_reg + 1);\n                        program.emit_int(index.unique as i64, base_reg + 2);\n                        program.emit_string8(origin.to_string(), base_reg + 3);\n                        program.emit_int(index.where_clause.is_some() as i64, base_reg + 4);\n                        program.emit_result_row(base_reg, 5);\n                    }\n                }\n            }\n\n            let pragma_meta = pragma_for(&pragma);\n            for col_name in pragma_meta.columns.iter() {\n                program.add_pragma_result_column(col_name.to_string());\n            }\n            Ok(TransactionMode::None)\n        }\n        PragmaName::TableList => {\n            let name = match value {\n                Some(ast::Expr::Name(name)) => Some(normalize_ident(name.as_str())),\n                _ => None,\n            };\n\n            let base_reg = register;\n            // 6 columns: schema, name, type, ncol, wr, strict\n            program.alloc_registers(5);\n\n            let emit_table_row = |program: &mut ProgramBuilder,\n                                  name: &str,\n                                  obj_type: &str,\n                                  ncol: usize,\n                                  wr: bool,\n                                  strict: bool| {\n                program.emit_string8(\"main\".to_string(), base_reg);\n                program.emit_string8(name.to_string(), base_reg + 1);\n                program.emit_string8(obj_type.to_string(), base_reg + 2);\n                program.emit_int(ncol as i64, base_reg + 3);\n                program.emit_int(wr as i64, base_reg + 4);\n                program.emit_int(strict as i64, base_reg + 5);\n                program.emit_result_row(base_reg, 6);\n            };\n\n            if let Some(name) = name {\n                // Specific table/view lookup\n                if let Some(table) = schema.get_table(&name) {\n                    let (wr, strict) = match table.btree() {\n                        Some(bt) => (!bt.has_rowid, bt.is_strict),\n                        None => (false, false),\n                    };\n                    emit_table_row(\n                        program,\n                        table.get_name(),\n                        \"table\",\n                        table.columns().len(),\n                        wr,\n                        strict,\n                    );\n                } else if let Some(view) = schema.get_view(&name) {\n                    emit_table_row(\n                        program,\n                        &view.name,\n                        \"view\",\n                        view.columns.len(),\n                        false,\n                        false,\n                    );\n                }\n            } else {\n                // List all tables and views (only BTree tables, not built-in virtual tables)\n                for table in schema.tables.values() {\n                    let Some(bt) = table.btree() else {\n                        continue;\n                    };\n                    emit_table_row(\n                        program,\n                        &bt.name,\n                        \"table\",\n                        bt.columns.len(),\n                        !bt.has_rowid,\n                        bt.is_strict,\n                    );\n                }\n                for view in schema.views.values() {\n                    emit_table_row(\n                        program,\n                        &view.name,\n                        \"view\",\n                        view.columns.len(),\n                        false,\n                        false,\n                    );\n                }\n            }\n\n            let pragma_meta = pragma_for(&pragma);\n            for col_name in pragma_meta.columns.iter() {\n                program.add_pragma_result_column(col_name.to_string());\n            }\n            Ok(TransactionMode::None)\n        }\n        PragmaName::TableInfo => {\n            let name = match value {\n                Some(ast::Expr::Name(name)) => Some(normalize_ident(name.as_str())),\n                _ => None,\n            };\n\n            let base_reg = register;\n            // we need 6 registers, but first register was allocated at the beginning  of the \"query_pragma\" function\n            program.alloc_registers(5);\n            if let Some(name) = name {\n                resolver.with_schema(database_id, |db_schema| {\n                    if let Some(table) = db_schema.get_table(&name) {\n                        emit_columns_for_table_info(program, table.columns(), base_reg, false);\n                    } else if let Some(view_mutex) = db_schema.get_materialized_view(&name) {\n                        let view = view_mutex.lock();\n                        let flat_columns = view.column_schema.flat_columns();\n                        emit_columns_for_table_info(program, &flat_columns, base_reg, false);\n                    } else if let Some(view) = db_schema.get_view(&name) {\n                        emit_columns_for_table_info(program, &view.columns, base_reg, false);\n                    }\n                });\n            }\n            let col_names = [\"cid\", \"name\", \"type\", \"notnull\", \"dflt_value\", \"pk\"];\n            for name in col_names {\n                program.add_pragma_result_column(name.into());\n            }\n            Ok(TransactionMode::None)\n        }\n        PragmaName::TableXinfo => {\n            let name = match value {\n                Some(ast::Expr::Name(name)) => Some(normalize_ident(name.as_str())),\n                _ => None,\n            };\n\n            let base_reg = register;\n            // we need 7 registers, but first register was allocated at the beginning  of the \"query_pragma\" function\n            program.alloc_registers(6);\n            if let Some(name) = name {\n                resolver.with_schema(database_id, |db_schema| {\n                    if let Some(table) = db_schema.get_table(&name) {\n                        emit_columns_for_table_info(program, table.columns(), base_reg, true);\n                    } else if let Some(view_mutex) = db_schema.get_materialized_view(&name) {\n                        let view = view_mutex.lock();\n                        let flat_columns = view.column_schema.flat_columns();\n                        emit_columns_for_table_info(program, &flat_columns, base_reg, true);\n                    } else if let Some(view) = db_schema.get_view(&name) {\n                        emit_columns_for_table_info(program, &view.columns, base_reg, true);\n                    }\n                });\n            }\n            let col_names = [\n                \"cid\",\n                \"name\",\n                \"type\",\n                \"notnull\",\n                \"dflt_value\",\n                \"pk\",\n                \"hidden\",\n            ];\n            for name in col_names {\n                program.add_pragma_result_column(name.into());\n            }\n            Ok(TransactionMode::None)\n        }\n        PragmaName::UserVersion => {\n            program.emit_insn(Insn::ReadCookie {\n                db: database_id,\n                dest: register,\n                cookie: Cookie::UserVersion,\n            });\n            program.add_pragma_result_column(pragma.to_string());\n            program.emit_result_row(register, 1);\n            Ok(TransactionMode::Read)\n        }\n        PragmaName::SchemaVersion => {\n            program.emit_insn(Insn::ReadCookie {\n                db: database_id,\n                dest: register,\n                cookie: Cookie::SchemaVersion,\n            });\n            program.add_pragma_result_column(pragma.to_string());\n            program.emit_result_row(register, 1);\n            Ok(TransactionMode::Read)\n        }\n        PragmaName::PageSize => {\n            program.emit_int(\n                pager\n                    .io\n                    .block(|| pager.with_header(|header| header.page_size.get()))\n                    .unwrap_or_else(|_| connection.get_page_size().get()) as i64,\n                register,\n            );\n            program.emit_result_row(register, 1);\n            program.add_pragma_result_column(pragma.to_string());\n            Ok(TransactionMode::None)\n        }\n        PragmaName::AutoVacuum => {\n            let auto_vacuum_mode = pager.get_auto_vacuum_mode();\n            let auto_vacuum_mode_i64: i64 = match auto_vacuum_mode {\n                AutoVacuumMode::None => 0,\n                AutoVacuumMode::Full => 1,\n                AutoVacuumMode::Incremental => 2,\n            };\n            let register = program.alloc_register();\n            program.emit_insn(Insn::Int64 {\n                _p1: 0,\n                out_reg: register,\n                _p3: 0,\n                value: auto_vacuum_mode_i64,\n            });\n            program.emit_result_row(register, 1);\n            Ok(TransactionMode::None)\n        }\n        PragmaName::IntegrityCheck => {\n            let max_errors = parse_max_errors_from_value(&value);\n            translate_integrity_check(schema, program, resolver, database_id, max_errors)?;\n            Ok(TransactionMode::Read)\n        }\n        PragmaName::QuickCheck => {\n            let max_errors = parse_max_errors_from_value(&value);\n            translate_quick_check(schema, program, resolver, database_id, max_errors)?;\n            Ok(TransactionMode::Read)\n        }\n        PragmaName::CaptureDataChangesConn | PragmaName::UnstableCaptureDataChangesConn => {\n            let pragma = pragma_for(&pragma);\n            let second_column = program.alloc_register();\n            let third_column = program.alloc_register();\n            let opts = connection.get_capture_data_changes_info();\n            match opts.as_ref() {\n                Some(info) => {\n                    program.emit_string8(info.mode_name().to_string(), register);\n                    program.emit_string8(info.table.clone(), second_column);\n                    match &info.version {\n                        Some(v) => program.emit_string8(v.to_string(), third_column),\n                        None => program.emit_null(third_column, None),\n                    }\n                }\n                None => {\n                    program.emit_string8(\"off\".to_string(), register);\n                    program.emit_null(second_column, None);\n                    program.emit_null(third_column, None);\n                }\n            }\n            program.emit_result_row(register, 3);\n            program.add_pragma_result_column(pragma.columns[0].to_string());\n            program.add_pragma_result_column(pragma.columns[1].to_string());\n            program.add_pragma_result_column(pragma.columns[2].to_string());\n            Ok(TransactionMode::Read)\n        }\n        PragmaName::QueryOnly => {\n            if let Some(value_expr) = value {\n                let is_query_only = match value_expr {\n                    ast::Expr::Literal(Literal::Numeric(i)) => i\n                        .parse::<i64>()\n                        .map(|v| v != 0)\n                        .or_else(|_| i.parse::<f64>().map(|v| v != 0.0))\n                        .map_err(|_| {\n                            LimboError::ParseError(format!(\n                                \"Invalid numeric value for PRAGMA query_only: {i}\"\n                            ))\n                        })?,\n                    ast::Expr::Literal(Literal::String(..)) | ast::Expr::Name(..) => {\n                        let s = match &value_expr {\n                            ast::Expr::Literal(Literal::String(s)) => s.as_bytes(),\n                            ast::Expr::Name(n) => n.as_str().as_bytes(),\n                            _ => unreachable!(),\n                        };\n                        match_ignore_ascii_case!(match s {\n                            b\"1\" | b\"on\" | b\"true\" => true,\n                            _ => false,\n                        })\n                    }\n                    _ => {\n                        return Err(LimboError::ParseError(format!(\n                            \"Invalid value for PRAGMA query_only: {value_expr:?}\"\n                        )));\n                    }\n                };\n                connection.set_query_only(is_query_only);\n                return Ok(TransactionMode::None);\n            };\n\n            let register = program.alloc_register();\n            let is_query_only = connection.get_query_only();\n            program.emit_int(is_query_only as i64, register);\n            program.emit_result_row(register, 1);\n            program.add_pragma_result_column(pragma.to_string());\n\n            Ok(TransactionMode::None)\n        }\n        PragmaName::FreelistCount => {\n            let value = pager.freepage_list();\n            let register = program.alloc_register();\n            program.emit_int(value as i64, register);\n            program.emit_result_row(register, 1);\n            program.add_pragma_result_column(pragma.to_string());\n            Ok(TransactionMode::None)\n        }\n        PragmaName::EncryptionKey => {\n            let msg = {\n                if connection.encryption_key.read().is_some() {\n                    \"encryption key is set for this session\"\n                } else {\n                    \"encryption key is not set for this session\"\n                }\n            };\n            let register = program.alloc_register();\n            program.emit_string8(msg.to_string(), register);\n            program.emit_result_row(register, 1);\n            program.add_pragma_result_column(pragma.to_string());\n            Ok(TransactionMode::None)\n        }\n        PragmaName::EncryptionCipher => {\n            if let Some(cipher) = connection.get_encryption_cipher_mode() {\n                let register = program.alloc_register();\n                program.emit_string8(cipher.to_string(), register);\n                program.emit_result_row(register, 1);\n                program.add_pragma_result_column(pragma.to_string());\n            }\n            Ok(TransactionMode::None)\n        }\n        PragmaName::Synchronous => {\n            let mode = connection.get_sync_mode();\n            let register = program.alloc_register();\n            program.emit_int(mode as i64, register);\n            program.emit_result_row(register, 1);\n            program.add_pragma_result_column(pragma.to_string());\n            Ok(TransactionMode::None)\n        }\n        PragmaName::DataSyncRetry => {\n            let retry_enabled = connection.get_data_sync_retry();\n            let register = program.alloc_register();\n            program.emit_int(retry_enabled as i64, register);\n            program.emit_result_row(register, 1);\n            program.add_pragma_result_column(pragma.to_string());\n            Ok(TransactionMode::None)\n        }\n        PragmaName::MvccCheckpointThreshold => {\n            let threshold = connection.mvcc_checkpoint_threshold()?;\n            let register = program.alloc_register();\n            program.emit_int(threshold, register);\n            program.emit_result_row(register, 1);\n            program.add_pragma_result_column(pragma.to_string());\n            Ok(TransactionMode::None)\n        }\n        PragmaName::ForeignKeys => {\n            let enabled = connection.foreign_keys_enabled();\n            let register = program.alloc_register();\n            program.emit_int(enabled as i64, register);\n            program.emit_result_row(register, 1);\n            program.add_pragma_result_column(pragma.to_string());\n            Ok(TransactionMode::None)\n        }\n        PragmaName::IAmADummy | PragmaName::RequireWhere => {\n            let register = program.alloc_register();\n            let enabled = connection.get_dml_require_where();\n            program.emit_int(enabled as i64, register);\n            program.emit_result_row(register, 1);\n            program.add_pragma_result_column(pragma.to_string());\n            Ok(TransactionMode::None)\n        }\n        PragmaName::IgnoreCheckConstraints => {\n            let ignored = connection.check_constraints_ignored();\n            let register = program.alloc_register();\n            program.emit_int(ignored as i64, register);\n            program.emit_result_row(register, 1);\n            program.add_pragma_result_column(pragma.to_string());\n            Ok(TransactionMode::None)\n        }\n        #[cfg(target_vendor = \"apple\")]\n        PragmaName::Fullfsync => {\n            let enabled = connection.get_sync_type() == crate::io::FileSyncType::FullFsync;\n            let register = program.alloc_register();\n            program.emit_int(enabled as i64, register);\n            program.emit_result_row(register, 1);\n            program.add_pragma_result_column(pragma.to_string());\n            Ok(TransactionMode::None)\n        }\n        PragmaName::TempStore => {\n            let temp_store = connection.get_temp_store();\n            let register = program.alloc_register();\n            program.emit_int(temp_store as i64, register);\n            program.emit_result_row(register, 1);\n            program.add_pragma_result_column(pragma.to_string());\n            Ok(TransactionMode::None)\n        }\n        PragmaName::ListTypes => {\n            let base_reg = register;\n            program.alloc_registers(5); // 6 total (1 already allocated)\n\n            // Built-in types: NULL parent, encode, decode, default, operators\n            for builtin in &[\"INTEGER\", \"REAL\", \"TEXT\", \"BLOB\", \"ANY\"] {\n                program.emit_string8(builtin.to_string(), base_reg);\n                program.emit_null(base_reg + 1, None);\n                program.emit_null(base_reg + 2, None);\n                program.emit_null(base_reg + 3, None);\n                program.emit_null(base_reg + 4, None);\n                program.emit_null(base_reg + 5, None);\n                program.emit_result_row(base_reg, 6);\n            }\n\n            // Custom types from the type registry are only shown when strict mode is enabled\n            // Custom types are always shown since strict mode is always enabled\n            {\n                // Skip aliases where key != canonical name\n                let mut type_names: Vec<_> = schema\n                    .type_registry\n                    .iter()\n                    .filter(|(key, td)| *key == &td.name.to_lowercase())\n                    .map(|(key, _)| key)\n                    .collect();\n                type_names.sort();\n                for type_name in type_names {\n                    let type_def = &schema.type_registry[type_name];\n                    let display_name = if type_def.params.is_empty() {\n                        type_def.name.clone()\n                    } else {\n                        let params: Vec<String> = type_def\n                            .params\n                            .iter()\n                            .map(|p| match &p.ty {\n                                Some(ty) => format!(\"{} {}\", p.name, ty),\n                                None => p.name.clone(),\n                            })\n                            .collect();\n                        format!(\"{}({})\", type_def.name, params.join(\", \"))\n                    };\n                    program.emit_string8(display_name, base_reg);\n                    program.emit_string8(type_def.base.clone(), base_reg + 1);\n                    if let Some(ref expr) = type_def.encode {\n                        program.emit_string8(expr.to_string(), base_reg + 2);\n                    } else {\n                        program.emit_null(base_reg + 2, None);\n                    }\n                    if let Some(ref expr) = type_def.decode {\n                        program.emit_string8(expr.to_string(), base_reg + 3);\n                    } else {\n                        program.emit_null(base_reg + 3, None);\n                    }\n                    if let Some(ref expr) = type_def.default {\n                        program.emit_string8(expr.to_string(), base_reg + 4);\n                    } else {\n                        program.emit_null(base_reg + 4, None);\n                    }\n                    if type_def.operators.is_empty() {\n                        program.emit_null(base_reg + 5, None);\n                    } else {\n                        let ops: Vec<String> = type_def\n                            .operators\n                            .iter()\n                            .map(|op| match &op.func_name {\n                                Some(f) => format!(\"'{}' {}\", op.op, f),\n                                None => format!(\"'{}'\", op.op),\n                            })\n                            .collect();\n                        program.emit_string8(ops.join(\", \"), base_reg + 5);\n                    }\n                    program.emit_result_row(base_reg, 6);\n                }\n            }\n\n            let pragma_meta = pragma_for(&pragma);\n            for col_name in pragma_meta.columns.iter() {\n                program.add_pragma_result_column(col_name.to_string());\n            }\n            Ok(TransactionMode::None)\n        }\n    }\n}\n\n/// Helper function to emit column information for PRAGMA table_info\n/// Used by both tables and views since they now have the same column emission logic\nfn emit_columns_for_table_info(\n    program: &mut ProgramBuilder,\n    columns: &[crate::schema::Column],\n    base_reg: usize,\n    extended: bool,\n) {\n    // According to the SQLite documentation: \"The 'cid' column should not be taken to\n    // mean more than 'rank within the current result set'.\"\n    // Therefore, we enumerate only after filtering out hidden columns (if extended set to false).\n    let mut cid = 0;\n    for column in columns.iter() {\n        // Determine column type which will be used for filtering in table_info pragma or as \"hidden\" column for table_xinfo pragma.\n        //\n        // SQLite docs about table_xinfo:\n        // > The output has the same columns as for PRAGMA table_info plus a column, \"hidden\",\n        // > whose value signifies a normal column (0), a dynamic or stored generated column (2 or 3),\n        // > or a hidden column in a virtual table (1). The rows for which this field is non-zero are those omitted for PRAGMA table_info.\n        //\n        // (see https://sqlite.org/pragma.html#pragma_table_xinfo)\n        let column_type = if column.hidden() {\n            // hidden column\n            1\n        } else {\n            // normal column\n            0\n        };\n\n        if !extended && column_type != 0 {\n            // This pragma (table_info) does not show information about generated columns or hidden columns.\n            continue;\n        }\n\n        // cid\n        program.emit_int(cid as i64, base_reg);\n        cid += 1;\n\n        // name\n        program.emit_string8(column.name.clone().unwrap_or_default(), base_reg + 1);\n\n        // type\n        program.emit_string8(column.ty_str.clone(), base_reg + 2);\n\n        // notnull\n        program.emit_bool(column.notnull(), base_reg + 3);\n\n        // dflt_value\n        match &column.default {\n            None => {\n                program.emit_null(base_reg + 4, None);\n            }\n            Some(expr) => {\n                program.emit_string8(expr.to_string(), base_reg + 4);\n            }\n        }\n\n        // pk\n        program.emit_bool(column.primary_key(), base_reg + 5);\n\n        if extended {\n            program.emit_int(column_type, base_reg + 6);\n        }\n\n        program.emit_result_row(base_reg, 6 + if extended { 1 } else { 0 });\n    }\n}\n\nfn update_auto_vacuum_mode(\n    auto_vacuum_mode: AutoVacuumMode,\n    largest_root_page_number: u32,\n    pager: Arc<Pager>,\n) -> crate::Result<()> {\n    pager.io.block(|| {\n        pager.with_header_mut(|header| {\n            header.vacuum_mode_largest_root_page = largest_root_page_number.into()\n        })\n    })?;\n    pager.set_auto_vacuum_mode(auto_vacuum_mode);\n    Ok(())\n}\n\nfn update_cache_size(\n    value: i64,\n    pager: Arc<Pager>,\n    connection: Arc<crate::Connection>,\n) -> crate::Result<()> {\n    let mut cache_size_unformatted: i64 = value;\n\n    let mut cache_size = if cache_size_unformatted < 0 {\n        let kb = cache_size_unformatted\n            .checked_abs()\n            .unwrap_or(i64::MAX)\n            .saturating_mul(1024);\n        let page_size = pager\n            .io\n            .block(|| pager.with_header(|header| header.page_size))\n            .unwrap_or_default()\n            .get() as i64;\n        if page_size == 0 {\n            turso_soft_unreachable!(\"Page size cannot be zero\");\n            return Err(LimboError::InternalError(\n                \"Page size cannot be zero\".to_string(),\n            ));\n        }\n        kb / page_size\n    } else {\n        value\n    };\n\n    if cache_size > CacheSize::MAX_SAFE {\n        cache_size = 0;\n        cache_size_unformatted = 0;\n    }\n\n    if cache_size < 0 {\n        cache_size = 0;\n        cache_size_unformatted = 0;\n    }\n\n    let final_cache_size = if cache_size < CacheSize::MIN {\n        cache_size_unformatted = CacheSize::MIN;\n        CacheSize::MIN\n    } else {\n        cache_size\n    };\n\n    connection.set_cache_size(cache_size_unformatted as i32);\n\n    pager\n        .change_page_cache_size(final_cache_size as usize)\n        .map_err(|e| LimboError::InternalError(format!(\"Failed to update page cache size: {e}\")))?;\n\n    Ok(())\n}\n\npub const TURSO_CDC_DEFAULT_TABLE_NAME: &str = \"turso_cdc\";\npub const TURSO_CDC_VERSION_TABLE_NAME: &str = \"turso_cdc_version\";\n\npub use crate::CDC_VERSION_CURRENT;\n\nfn update_page_size(connection: Arc<crate::Connection>, page_size: u32) -> crate::Result<()> {\n    connection.reset_page_size(page_size)?;\n    Ok(())\n}\n\nfn is_database_empty(schema: &Schema, pager: &Arc<Pager>) -> crate::Result<bool> {\n    if schema.tables.len() > 1 {\n        return Ok(false);\n    }\n    if let Some(table_arc) = schema.tables.values().next() {\n        let table_name = match table_arc.as_ref() {\n            crate::schema::Table::BTree(tbl) => &tbl.name,\n            crate::schema::Table::Virtual(tbl) => &tbl.name,\n            crate::schema::Table::FromClauseSubquery(tbl) => &tbl.name,\n        };\n\n        if table_name != \"sqlite_schema\" {\n            return Ok(false);\n        }\n    }\n\n    let db_size_result = pager\n        .io\n        .block(|| pager.with_header(|header| header.database_size.get()));\n\n    match db_size_result {\n        Err(_) => Ok(true),\n        Ok(0 | 1) => Ok(true),\n        Ok(_) => Ok(false),\n    }\n}\n"
  },
  {
    "path": "core/translate/result_row.rs",
    "content": "use crate::turso_assert_eq;\nuse crate::{\n    vdbe::{\n        builder::ProgramBuilder,\n        insn::{to_u16, IdxInsertFlags, InsertFlags, Insn},\n        BranchOffset,\n    },\n    Result,\n};\n\nuse turso_parser::ast;\n\nuse super::{\n    emitter::{LimitCtx, Resolver},\n    expr::{\n        emit_array_decode, expr_is_array, translate_expr, translate_expr_no_constant_opt,\n        NoConstantOptReason,\n    },\n    plan::{Distinctness, QueryDestination, ResultSetColumn, SelectPlan, TableReferences},\n};\n\n/// Emits the bytecode for:\n/// - all result columns\n/// - result row (or if a subquery, yields to the parent query)\n/// - limit\n#[allow(clippy::too_many_arguments)]\npub fn emit_select_result(\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    plan: &SelectPlan,\n    label_on_limit_reached: Option<BranchOffset>,\n    offset_jump_to: Option<BranchOffset>,\n    reg_nonagg_emit_once_flag: Option<usize>,\n    reg_offset: Option<usize>,\n    reg_result_cols_start: usize,\n    limit_ctx: Option<LimitCtx>,\n) -> Result<()> {\n    let has_distinct = matches!(plan.distinctness, Distinctness::Distinct { .. });\n    if !has_distinct {\n        if let (Some(jump_to), Some(_)) = (offset_jump_to, label_on_limit_reached) {\n            emit_offset(program, jump_to, reg_offset);\n        }\n    }\n\n    let start_reg = reg_result_cols_start;\n\n    // For EXISTS subqueries, we usually only need to determine whether any row exists, not\n    // the row's column values. The result is simply writing `1` to the result register.\n    //\n    // Important: SELECT DISTINCT deduplication reads the result registers as its key. So for\n    // EXISTS(SELECT DISTINCT ...), we must still evaluate the result expressions; otherwise the\n    // dedup key is uninitialized and EXISTS can incorrectly evaluate to false.\n    let skip_column_eval = matches!(\n        plan.query_destination,\n        QueryDestination::ExistsSubqueryResult { .. }\n    ) && !matches!(plan.distinctness, Distinctness::Distinct { .. });\n\n    // For compound selects (UNION, UNION ALL, etc.), multiple subselects may share the same\n    // result column registers. If constants are moved to the init section, they can be\n    // overwritten by subsequent subselects before being used.\n    //\n    // We conservatively disable constant optimization for EphemeralIndex, CoroutineYield,\n    // and EphemeralTable destinations because these are used in compound select contexts\n    // and CTE materialization. This is slightly over-broad (e.g., simple INSERT INTO ...\n    // SELECT with no UNION doesn't need this), but we lack context here to distinguish\n    // compound vs non-compound cases.\n    let disable_constant_opt = matches!(\n        plan.query_destination,\n        QueryDestination::EphemeralIndex { .. }\n            | QueryDestination::CoroutineYield { .. }\n            | QueryDestination::EphemeralTable { .. }\n    );\n\n    if !skip_column_eval {\n        for (i, rc) in plan.result_columns.iter().enumerate().filter(|(_, rc)| {\n            // For aggregate queries, we handle columns differently; example: select id, first_name, sum(age) from users limit 1;\n            // 1. Columns with aggregates (e.g., sum(age)) are computed in each iteration of aggregation\n            // 2. Non-aggregate columns (e.g., id, first_name) are only computed once in the first iteration\n            // This filter ensures we only emit expressions for non aggregate columns once,\n            // preserving previously calculated values while updating aggregate results\n            // For all other queries where reg_nonagg_emit_once_flag is none we do nothing.\n            reg_nonagg_emit_once_flag.is_some() && rc.contains_aggregates\n                || reg_nonagg_emit_once_flag.is_none()\n        }) {\n            let reg = start_reg + i;\n            if disable_constant_opt {\n                translate_expr_no_constant_opt(\n                    program,\n                    Some(&plan.table_references),\n                    &rc.expr,\n                    reg,\n                    resolver,\n                    NoConstantOptReason::RegisterReuse,\n                )?;\n            } else {\n                translate_expr(\n                    program,\n                    Some(&plan.table_references),\n                    &rc.expr,\n                    reg,\n                    resolver,\n                )?;\n            }\n        }\n    }\n\n    // Emit ArrayDecode for result columns that produce array blobs.\n    // Array values are stored as record-format blobs internally; decode\n    // them to JSON text for user-facing display.\n    if !skip_column_eval {\n        emit_array_decode_for_results(\n            program,\n            &plan.result_columns,\n            &plan.table_references,\n            start_reg,\n            resolver,\n        )?;\n    }\n\n    // Handle SELECT DISTINCT deduplication\n    if let Distinctness::Distinct { ctx } = &plan.distinctness {\n        let distinct_ctx = ctx.as_ref().expect(\"distinct context must exist\");\n        let num_regs = plan.result_columns.len();\n        distinct_ctx.emit_deduplication_insns(program, num_regs, start_reg);\n    }\n\n    if has_distinct {\n        if let (Some(jump_to), Some(_)) = (offset_jump_to, label_on_limit_reached) {\n            emit_offset(program, jump_to, reg_offset);\n        }\n    }\n\n    emit_result_row_and_limit(program, plan, start_reg, limit_ctx, label_on_limit_reached)?;\n    Ok(())\n}\n\n/// Emits bytecode to send column values to a destination.\n/// This is the core \"emit to destination\" logic shared by both regular SELECT emission\n/// and compound SELECT (UNION/INTERSECT/EXCEPT) final output.\n///\n/// Parameters:\n/// - `start_reg`: First register containing column values\n/// - `num_columns`: Number of columns to emit\n/// - `destination`: Where to send the columns (ResultRow, EphemeralIndex, etc.)\npub fn emit_columns_to_destination(\n    program: &mut ProgramBuilder,\n    destination: &QueryDestination,\n    start_reg: usize,\n    num_columns: usize,\n) -> Result<()> {\n    match destination {\n        QueryDestination::ResultRows => {\n            program.emit_insn(Insn::ResultRow {\n                start_reg,\n                count: num_columns,\n            });\n        }\n        QueryDestination::EphemeralIndex {\n            cursor_id: index_cursor_id,\n            index: dedupe_index,\n            affinity_str,\n            is_delete,\n        } => {\n            if *is_delete {\n                program.emit_insn(Insn::IdxDelete {\n                    start_reg,\n                    num_regs: num_columns,\n                    cursor_id: *index_cursor_id,\n                    raise_error_if_no_matching_entry: false,\n                });\n            } else {\n                let record_reg = program.alloc_register();\n\n                // For ephemeral indexes, we may need to:\n                // 1. Reorder columns if index key order differs from result order (seek indexes)\n                // 2. Append a unique rowid to allow duplicate key values (has_rowid indexes)\n                let (record_start, record_count) =\n                    if dedupe_index.ephemeral && dedupe_index.columns.len() == num_columns {\n                        // Check if reordering is needed (any pos_in_table != idx_pos)\n                        let needs_reorder = dedupe_index\n                            .columns\n                            .iter()\n                            .enumerate()\n                            .any(|(idx_pos, col)| col.pos_in_table != idx_pos);\n\n                        if needs_reorder {\n                            // Reorder columns to match index key order\n                            let extra_for_rowid = if dedupe_index.has_rowid { 1 } else { 0 };\n                            let reordered_start =\n                                program.alloc_registers(num_columns + extra_for_rowid);\n\n                            for (idx_pos, idx_col) in dedupe_index.columns.iter().enumerate() {\n                                program.emit_insn(Insn::Copy {\n                                    src_reg: start_reg + idx_col.pos_in_table,\n                                    dst_reg: reordered_start + idx_pos,\n                                    extra_amount: 0,\n                                });\n                            }\n\n                            if dedupe_index.has_rowid {\n                                program.emit_insn(Insn::Sequence {\n                                    cursor_id: *index_cursor_id,\n                                    target_reg: reordered_start + num_columns,\n                                });\n                            }\n\n                            (reordered_start, num_columns + extra_for_rowid)\n                        } else if dedupe_index.has_rowid {\n                            // No reordering needed, but need to append rowid\n                            let new_start = program.alloc_registers(num_columns + 1);\n                            program.emit_insn(Insn::Copy {\n                                src_reg: start_reg,\n                                dst_reg: new_start,\n                                extra_amount: num_columns - 1,\n                            });\n                            program.emit_insn(Insn::Sequence {\n                                cursor_id: *index_cursor_id,\n                                target_reg: new_start + num_columns,\n                            });\n                            (new_start, num_columns + 1)\n                        } else {\n                            // No reordering or rowid needed - use registers directly\n                            (start_reg, num_columns)\n                        }\n                    } else {\n                        (start_reg, num_columns)\n                    };\n\n                program.emit_insn(Insn::MakeRecord {\n                    start_reg: to_u16(record_start),\n                    count: to_u16(record_count),\n                    dest_reg: to_u16(record_reg),\n                    index_name: Some(dedupe_index.name.clone()),\n                    affinity_str: affinity_str.as_ref().map(|s| (**s).clone()),\n                });\n                program.emit_insn(Insn::IdxInsert {\n                    cursor_id: *index_cursor_id,\n                    record_reg,\n                    unpacked_start: None,\n                    unpacked_count: None,\n                    flags: IdxInsertFlags::new().no_op_duplicate(),\n                });\n            }\n        }\n        QueryDestination::EphemeralTable {\n            cursor_id: table_cursor_id,\n            table,\n            rowid_mode,\n        } => {\n            // Prevent constant hoisting so that each row's constants are evaluated inline.\n            // This is critical for UNION ALL where each arm should insert its own values.\n            program.constant_span_end_all();\n            let record_reg = program.alloc_register();\n            match rowid_mode {\n                super::plan::EphemeralRowidMode::FromResultColumns => {\n                    // For single-column case (RowidOnly materialization), we still need to\n                    // create a record containing the rowid so it can be read back later.\n                    if num_columns == 1 {\n                        program.emit_insn(Insn::MakeRecord {\n                            start_reg: to_u16(start_reg),\n                            count: to_u16(1),\n                            dest_reg: to_u16(record_reg),\n                            index_name: Some(table.name.clone()),\n                            affinity_str: None,\n                        });\n                    } else if num_columns > 1 {\n                        program.emit_insn(Insn::MakeRecord {\n                            start_reg: to_u16(start_reg),\n                            count: to_u16(num_columns - 1),\n                            dest_reg: to_u16(record_reg),\n                            index_name: Some(table.name.clone()),\n                            affinity_str: None,\n                        });\n                    }\n                    program.emit_insn(Insn::Insert {\n                        cursor: *table_cursor_id,\n                        key_reg: start_reg + (num_columns - 1),\n                        record_reg,\n                        flag: InsertFlags::new()\n                            .require_seek()\n                            .is_ephemeral_table_insert(),\n                        table_name: table.name.clone(),\n                    });\n                }\n                super::plan::EphemeralRowidMode::Auto => {\n                    if num_columns > 0 {\n                        program.emit_insn(Insn::MakeRecord {\n                            start_reg: to_u16(start_reg),\n                            count: to_u16(num_columns),\n                            dest_reg: to_u16(record_reg),\n                            index_name: Some(table.name.clone()),\n                            affinity_str: None,\n                        });\n                    }\n                    let rowid_reg = program.alloc_register();\n                    program.emit_insn(Insn::NewRowid {\n                        cursor: *table_cursor_id,\n                        rowid_reg,\n                        prev_largest_reg: 0,\n                    });\n                    program.emit_insn(Insn::Insert {\n                        cursor: *table_cursor_id,\n                        key_reg: rowid_reg,\n                        record_reg,\n                        flag: InsertFlags::new().is_ephemeral_table_insert(),\n                        table_name: table.name.clone(),\n                    });\n                }\n            }\n        }\n        QueryDestination::CoroutineYield { yield_reg, .. } => {\n            program.emit_insn(Insn::Yield {\n                yield_reg: *yield_reg,\n                end_offset: BranchOffset::Offset(0),\n                subtype_clear_start_reg: start_reg,\n                subtype_clear_count: num_columns,\n            });\n        }\n        QueryDestination::ExistsSubqueryResult { result_reg } => {\n            program.emit_insn(Insn::Integer {\n                value: 1,\n                dest: *result_reg,\n            });\n        }\n        QueryDestination::RowValueSubqueryResult {\n            result_reg_start,\n            num_regs,\n        } => {\n            turso_assert_eq!(\n                num_columns,\n                *num_regs,\n                \"Row value subqueries should have the same number of result columns as the number of registers\"\n            );\n            program.emit_insn(Insn::Copy {\n                src_reg: start_reg,\n                dst_reg: *result_reg_start,\n                extra_amount: num_regs - 1,\n            });\n        }\n        QueryDestination::RowSet { rowset_reg } => {\n            turso_assert_eq!(\n                num_columns,\n                1,\n                \"RowSet should only have one result column (rowid)\"\n            );\n            program.emit_insn(Insn::RowSetAdd {\n                rowset_reg: *rowset_reg,\n                value_reg: start_reg,\n            });\n        }\n        QueryDestination::Unset => unreachable!(\"Unset query destination should not be reached\"),\n    }\n    Ok(())\n}\n\n/// Emits the bytecode for:\n/// - result row (or if a subquery, yields to the parent query)\n/// - limit\npub fn emit_result_row_and_limit(\n    program: &mut ProgramBuilder,\n    plan: &SelectPlan,\n    result_columns_start_reg: usize,\n    limit_ctx: Option<LimitCtx>,\n    label_on_limit_reached: Option<BranchOffset>,\n) -> Result<()> {\n    emit_columns_to_destination(\n        program,\n        &plan.query_destination,\n        result_columns_start_reg,\n        plan.result_columns.len(),\n    )?;\n\n    if plan.limit.is_some() {\n        if label_on_limit_reached.is_none() {\n            return Ok(());\n        }\n        let limit_ctx = limit_ctx.expect(\"limit_ctx must be Some if plan.limit is Some\");\n\n        program.emit_insn(Insn::DecrJumpZero {\n            reg: limit_ctx.reg_limit,\n            target_pc: label_on_limit_reached.unwrap(),\n        });\n    }\n    Ok(())\n}\n\npub fn emit_offset(program: &mut ProgramBuilder, jump_to: BranchOffset, reg_offset: Option<usize>) {\n    let Some(reg_offset) = &reg_offset else {\n        return;\n    };\n    program.emit_insn(Insn::IfPos {\n        reg: *reg_offset,\n        target_pc: jump_to,\n        decrement_by: 1,\n    });\n}\n\n/// Emit ArrayDecode for result columns that produce array record blobs.\n/// Array values are stored as record-format blobs internally; this converts\n/// them to JSON text for user-facing display, just before ResultRow.\npub(crate) fn emit_array_decode_for_results(\n    program: &mut ProgramBuilder,\n    result_columns: &[ResultSetColumn],\n    table_references: &TableReferences,\n    start_reg: usize,\n    resolver: &Resolver,\n) -> Result<()> {\n    for (i, rc) in result_columns.iter().enumerate() {\n        // Check if this is a column reference to an array column\n        let array_col = if let ast::Expr::Column { table, column, .. } = &rc.expr {\n            table_references\n                .find_table_by_internal_id(*table)\n                .map(|(_, t)| t)\n                .and_then(|t| t.get_column_at(*column))\n                .filter(|col| col.is_array())\n        } else {\n            None\n        };\n\n        // Check if this expression produces an array (function call, || operator, etc.)\n        let is_array_expr = array_col.is_none() && expr_is_array(&rc.expr, Some(table_references));\n\n        if array_col.is_some() || is_array_expr {\n            let reg = start_reg + i;\n            let skip = program.allocate_label();\n            program.emit_insn(Insn::IsNull {\n                reg,\n                target_pc: skip,\n            });\n\n            if let Some(col) = array_col {\n                emit_array_decode(program, reg, col, resolver)?;\n            } else {\n                program.emit_insn(Insn::ArrayDecode { reg });\n            }\n\n            program.preassign_label_to_next_insn(skip);\n        }\n    }\n    Ok(())\n}\n"
  },
  {
    "path": "core/translate/rollback.rs",
    "content": "use crate::{\n    vdbe::{\n        builder::ProgramBuilder,\n        insn::{Insn, SavepointOp},\n    },\n    Result,\n};\nuse turso_parser::ast::Name;\n\n/// Emits bytecode for `SAVEPOINT <name>`.\npub fn translate_savepoint(program: &mut ProgramBuilder, name: Name) -> Result<()> {\n    program.emit_insn(Insn::Savepoint {\n        op: SavepointOp::Begin,\n        name: name.as_str().to_ascii_lowercase(),\n    });\n    Ok(())\n}\n\n/// Emits bytecode for `RELEASE [SAVEPOINT] <name>`.\npub fn translate_release(program: &mut ProgramBuilder, name: Name) -> Result<()> {\n    program.emit_insn(Insn::Savepoint {\n        op: SavepointOp::Release,\n        name: name.as_str().to_ascii_lowercase(),\n    });\n    Ok(())\n}\n\n/// Emits bytecode for either full transaction rollback or `ROLLBACK TO` named savepoint.\npub fn translate_rollback(\n    program: &mut ProgramBuilder,\n    _txn_name: Option<Name>,\n    savepoint_name: Option<Name>,\n) -> Result<()> {\n    if let Some(savepoint_name) = savepoint_name {\n        program.emit_insn(Insn::Savepoint {\n            op: SavepointOp::RollbackTo,\n            name: savepoint_name.as_str().to_ascii_lowercase(),\n        });\n    } else {\n        program.emit_insn(Insn::AutoCommit {\n            auto_commit: true,\n            rollback: true,\n        });\n        program.rollback();\n    }\n    Ok(())\n}\n"
  },
  {
    "path": "core/translate/schema.rs",
    "content": "use crate::sync::Arc;\n\nuse crate::ast;\nuse crate::ext::VTabImpl;\nuse crate::function::{Deterministic, Func, MathFunc, ScalarFunc};\nuse crate::schema::{\n    create_table, translate_ident_to_string_literal, BTreeTable, ColDef, Column, SchemaObjectType,\n    Table, Type, RESERVED_TABLE_PREFIXES, SQLITE_SEQUENCE_TABLE_NAME, TURSO_TYPES_TABLE_NAME,\n};\nuse crate::stats::STATS_TABLE;\nuse crate::storage::pager::CreateBTreeFlags;\nuse crate::translate::emitter::{\n    emit_cdc_autocommit_commit, emit_cdc_full_record, emit_cdc_insns, prepare_cdc_if_necessary,\n    OperationMode, Resolver,\n};\nuse crate::translate::expr::{walk_expr, WalkControl};\nuse crate::translate::fkeys::emit_fk_drop_table_check;\nuse crate::translate::planner::ROWID_STRS;\nuse crate::translate::{ProgramBuilder, ProgramBuilderOpts};\nuse crate::util::{\n    escape_sql_string_literal, normalize_ident, PRIMARY_KEY_AUTOMATIC_INDEX_NAME_PREFIX,\n};\nuse crate::vdbe::builder::CursorType;\nuse crate::vdbe::insn::{\n    to_u16, {CmpInsFlags, Cookie, InsertFlags, Insn, RegisterOrLiteral},\n};\nuse crate::Connection;\nuse crate::{bail_parse_error, CaptureDataChangesExt, Result};\n\nuse turso_ext::VTabKind;\nuse turso_parser::ast::ColumnDefinition;\n\n/// Validate a CHECK constraint expression at CREATE TABLE / ALTER TABLE ADD COLUMN time.\n/// Rejects non-existent columns, non-existent functions, aggregates, window functions,\n/// bind parameters, and subqueries.\npub(crate) fn validate_check_expr(\n    expr: &ast::Expr,\n    table_name: &str,\n    column_names: &[&str],\n    resolver: &Resolver,\n) -> Result<()> {\n    let normalized_table = normalize_ident(table_name);\n    walk_expr(expr, &mut |e: &ast::Expr| -> Result<WalkControl> {\n        match e {\n            ast::Expr::Id(name) | ast::Expr::Name(name) => {\n                let n = normalize_ident(name.as_str());\n                if !column_names.iter().any(|c| normalize_ident(c) == n)\n                    && !ROWID_STRS.iter().any(|r| r.eq_ignore_ascii_case(&n))\n                {\n                    bail_parse_error!(\"no such column: {}\", name.as_str());\n                }\n            }\n            ast::Expr::Qualified(tbl, col) => {\n                if normalize_ident(tbl.as_str()) != normalized_table {\n                    bail_parse_error!(\"no such column: {}.{}\", tbl.as_str(), col.as_str());\n                }\n                let cn = normalize_ident(col.as_str());\n                if !column_names.iter().any(|c| normalize_ident(c) == cn)\n                    && !ROWID_STRS.iter().any(|r| r.eq_ignore_ascii_case(&cn))\n                {\n                    bail_parse_error!(\"no such column: {}\", col.as_str());\n                }\n            }\n            ast::Expr::DoublyQualified(db, tbl, col) => {\n                bail_parse_error!(\n                    \"no such column: {}.{}.{}\",\n                    db.as_str(),\n                    tbl.as_str(),\n                    col.as_str()\n                );\n            }\n            ast::Expr::FunctionCall {\n                name,\n                args,\n                filter_over,\n                ..\n            } => {\n                if filter_over.over_clause.is_some() {\n                    bail_parse_error!(\"misuse of window function {}()\", name.as_str());\n                }\n                if let Some(func) = resolver.resolve_function(name.as_str(), args.len()) {\n                    if matches!(func, Func::Agg(..)) {\n                        bail_parse_error!(\"misuse of aggregate function {}()\", name.as_str());\n                    }\n                    if matches!(func, Func::Window(..)) {\n                        bail_parse_error!(\"misuse of window function {}()\", name.as_str());\n                    }\n                } else {\n                    bail_parse_error!(\"no such function: {}\", name.as_str());\n                }\n            }\n            ast::Expr::FunctionCallStar { name, filter_over } => {\n                if filter_over.over_clause.is_some() {\n                    bail_parse_error!(\"misuse of window function {}()\", name.as_str());\n                }\n                if let Some(func) = resolver.resolve_function(name.as_str(), 0) {\n                    if matches!(func, Func::Agg(..)) {\n                        bail_parse_error!(\"misuse of aggregate function {}()\", name.as_str());\n                    }\n                    if matches!(func, Func::Window(..)) {\n                        bail_parse_error!(\"misuse of window function {}()\", name.as_str());\n                    }\n                } else {\n                    bail_parse_error!(\"no such function: {}\", name.as_str());\n                }\n            }\n            ast::Expr::Variable(_) => {\n                bail_parse_error!(\"parameters prohibited in CHECK constraints\");\n            }\n            ast::Expr::Subquery(_) | ast::Expr::Exists(_) | ast::Expr::InSelect { .. } => {\n                bail_parse_error!(\"subqueries prohibited in CHECK constraints\");\n            }\n            _ => {}\n        }\n        Ok(WalkControl::Continue)\n    })?;\n    Ok(())\n}\n\nfn validate_default_expr(expr: &ast::Expr, col: &ColumnDefinition) -> Result<()> {\n    walk_expr(expr, &mut |e: &ast::Expr| -> Result<WalkControl> {\n        match e {\n            ast::Expr::Column { .. }\n            | ast::Expr::RowId { .. }\n            | ast::Expr::Name(_)\n            | ast::Expr::Qualified(_, _)\n            | ast::Expr::DoublyQualified(_, _, _)\n            | ast::Expr::Variable(_)\n            | ast::Expr::Raise(_, _)\n            | ast::Expr::Exists(_)\n            | ast::Expr::InSelect { .. }\n            | ast::Expr::InTable { .. }\n            | ast::Expr::Subquery(_)\n            | ast::Expr::SubqueryResult { .. }\n            | ast::Expr::Id(_) => {\n                bail_parse_error!(\n                    \"default value of column [{}] is not constant\",\n                    col.col_name.as_str()\n                );\n            }\n            _ => Ok(WalkControl::Continue),\n        }\n    })?;\n    Ok(())\n}\n\n/// Resolved type of an expression node for strict type checking of CHECK constraints.\n/// In STRICT tables, every comparison operand must have a determinable, compatible type.\n/// If a type cannot be determined (e.g. function calls), the user must use an explicit CAST.\n#[derive(Debug, Clone, PartialEq)]\nenum CheckExprType {\n    Integer,\n    Real,\n    Text,\n    Blob,\n    Any,\n    Null,\n    CustomType(String),\n}\n\nimpl CheckExprType {\n    fn is_numeric(&self) -> bool {\n        matches!(self, Self::Integer | Self::Real)\n    }\n\n    fn is_compatible_with(&self, other: &Self) -> bool {\n        match (self, other) {\n            (Self::Null, _) | (_, Self::Null) => true,\n            (Self::Any, _) | (_, Self::Any) => true,\n            (a, b) if a == b => true,\n            (a, b) if a.is_numeric() && b.is_numeric() => true,\n            _ => false,\n        }\n    }\n\n    fn display_name(&self) -> &str {\n        match self {\n            Self::Integer => \"INTEGER\",\n            Self::Real => \"REAL\",\n            Self::Text => \"TEXT\",\n            Self::Blob => \"BLOB\",\n            Self::Any => \"ANY\",\n            Self::Null => \"NULL\",\n            Self::CustomType(name) => name.as_str(),\n        }\n    }\n}\n\n/// Resolve the type of an expression node in a CHECK constraint.\n/// Returns an error if the type cannot be determined — the user must use CAST.\nfn resolve_check_expr_type(\n    expr: &ast::Expr,\n    columns: &[&ast::ColumnDefinition],\n    resolver: &Resolver,\n) -> Result<CheckExprType> {\n    use ast::{Literal, Operator, UnaryOperator};\n    match expr {\n        ast::Expr::Id(name) | ast::Expr::Name(name) => {\n            let n = normalize_ident(name.as_str());\n            // rowid/oid/_rowid_ are INTEGER\n            if ROWID_STRS.iter().any(|r| r.eq_ignore_ascii_case(&n)) {\n                return Ok(CheckExprType::Integer);\n            }\n            for col in columns {\n                if normalize_ident(col.col_name.as_str()) == n {\n                    return resolve_column_type(col, resolver);\n                }\n            }\n            bail_parse_error!(\"no such column: {}\", name.as_str());\n        }\n        ast::Expr::Qualified(_tbl, col) => {\n            let cn = normalize_ident(col.as_str());\n            if ROWID_STRS.iter().any(|r| r.eq_ignore_ascii_case(&cn)) {\n                return Ok(CheckExprType::Integer);\n            }\n            for c in columns {\n                if normalize_ident(c.col_name.as_str()) == cn {\n                    return resolve_column_type(c, resolver);\n                }\n            }\n            bail_parse_error!(\"no such column: {}\", col.as_str());\n        }\n        ast::Expr::Literal(lit) => match lit {\n            Literal::Numeric(s) => {\n                if s.contains('.') || s.contains('e') || s.contains('E') {\n                    Ok(CheckExprType::Real)\n                } else {\n                    Ok(CheckExprType::Integer)\n                }\n            }\n            Literal::String(_) => Ok(CheckExprType::Text),\n            Literal::Blob(_) => Ok(CheckExprType::Blob),\n            Literal::Null => Ok(CheckExprType::Null),\n            Literal::True | Literal::False => Ok(CheckExprType::Integer),\n            Literal::CurrentDate | Literal::CurrentTime | Literal::CurrentTimestamp => {\n                Ok(CheckExprType::Text)\n            }\n            Literal::Keyword(s) => {\n                bail_parse_error!(\n                    \"cannot determine type of '{}' in CHECK constraint; use CAST\",\n                    s\n                );\n            }\n        },\n        ast::Expr::Parenthesized(exprs) => {\n            if exprs.len() == 1 {\n                resolve_check_expr_type(&exprs[0], columns, resolver)\n            } else {\n                bail_parse_error!(\n                    \"cannot determine type of expression in CHECK constraint; use CAST\"\n                );\n            }\n        }\n        ast::Expr::Cast { type_name, .. } => {\n            if let Some(ref tn) = type_name {\n                resolve_type_name(&tn.name, resolver)\n            } else {\n                bail_parse_error!(\n                    \"cannot determine type of CAST in CHECK constraint; use CAST with explicit type\"\n                );\n            }\n        }\n        ast::Expr::Unary(op, inner) => match op {\n            UnaryOperator::Negative | UnaryOperator::Positive => {\n                let inner_ty = resolve_check_expr_type(inner, columns, resolver)?;\n                if !inner_ty.is_numeric() && inner_ty != CheckExprType::Null {\n                    bail_parse_error!(\n                        \"unary minus/plus requires a numeric type, got {}\",\n                        inner_ty.display_name()\n                    );\n                }\n                Ok(inner_ty)\n            }\n            UnaryOperator::BitwiseNot => Ok(CheckExprType::Integer),\n            UnaryOperator::Not => Ok(CheckExprType::Integer),\n        },\n        ast::Expr::Binary(lhs, op, rhs) => {\n            match op {\n                // Arithmetic: both must be numeric, result follows promotion rules\n                Operator::Add | Operator::Subtract | Operator::Multiply | Operator::Divide => {\n                    let lty = resolve_check_expr_type(lhs, columns, resolver)?;\n                    let rty = resolve_check_expr_type(rhs, columns, resolver)?;\n                    if lty == CheckExprType::Null || rty == CheckExprType::Null {\n                        return Ok(CheckExprType::Null);\n                    }\n                    if !lty.is_numeric() || !rty.is_numeric() {\n                        bail_parse_error!(\n                            \"arithmetic requires numeric types, got {} and {}\",\n                            lty.display_name(),\n                            rty.display_name()\n                        );\n                    }\n                    if lty == CheckExprType::Real || rty == CheckExprType::Real {\n                        Ok(CheckExprType::Real)\n                    } else {\n                        Ok(CheckExprType::Integer)\n                    }\n                }\n                Operator::Modulus => Ok(CheckExprType::Integer),\n                Operator::Concat => Ok(CheckExprType::Text),\n                Operator::BitwiseAnd\n                | Operator::BitwiseOr\n                | Operator::LeftShift\n                | Operator::RightShift => Ok(CheckExprType::Integer),\n                // Logical: recurse to find nested comparisons\n                Operator::And | Operator::Or => {\n                    // The result of AND/OR is boolean (integer), but we need to\n                    // recurse to validate any comparisons inside.\n                    validate_check_types_in_expr(lhs, columns, resolver)?;\n                    validate_check_types_in_expr(rhs, columns, resolver)?;\n                    Ok(CheckExprType::Integer)\n                }\n                // Comparison operators: validate type compatibility and return Integer (boolean)\n                Operator::Equals\n                | Operator::NotEquals\n                | Operator::Less\n                | Operator::LessEquals\n                | Operator::Greater\n                | Operator::GreaterEquals => {\n                    let lty = resolve_check_expr_type(lhs, columns, resolver)?;\n                    let rty = resolve_check_expr_type(rhs, columns, resolver)?;\n                    if !lty.is_compatible_with(&rty) {\n                        bail_parse_error!(\n                            \"type mismatch in CHECK constraint: cannot compare {} with {}\",\n                            lty.display_name(),\n                            rty.display_name()\n                        );\n                    }\n                    Ok(CheckExprType::Integer)\n                }\n                // IS/IS NOT are NULL-checking operators, skip type validation\n                Operator::Is | Operator::IsNot => Ok(CheckExprType::Integer),\n                _ => {\n                    bail_parse_error!(\n                        \"cannot determine type of expression in CHECK constraint; use CAST\"\n                    );\n                }\n            }\n        }\n        ast::Expr::NotNull(_) | ast::Expr::IsNull(_) => Ok(CheckExprType::Integer),\n        ast::Expr::FunctionCall { name, args, .. } => {\n            if let Some(func) = resolver.resolve_function(name.as_str(), args.len()) {\n                resolve_func_return_type(&func, name.as_str(), args, columns, resolver)\n            } else {\n                bail_parse_error!(\n                    \"cannot determine return type of function {}() in CHECK constraint; \\\n                     wrap with CAST to specify the type, e.g. CAST({}(...) AS INTEGER)\",\n                    name.as_str(),\n                    name.as_str()\n                );\n            }\n        }\n        ast::Expr::FunctionCallStar { name, .. } => {\n            if let Some(func) = resolver.resolve_function(name.as_str(), 0) {\n                resolve_func_return_type(&func, name.as_str(), &[], columns, resolver)\n            } else {\n                bail_parse_error!(\n                    \"cannot determine return type of function {}() in CHECK constraint; \\\n                     wrap with CAST to specify the type, e.g. CAST({}(...) AS INTEGER)\",\n                    name.as_str(),\n                    name.as_str()\n                );\n            }\n        }\n        _ => {\n            bail_parse_error!(\"cannot determine type of expression in CHECK constraint; use CAST\");\n        }\n    }\n}\n\n/// Resolve the return type of a built-in function for CHECK constraint type checking.\nfn resolve_func_return_type(\n    func: &Func,\n    name: &str,\n    args: &[Box<ast::Expr>],\n    columns: &[&ast::ColumnDefinition],\n    resolver: &Resolver,\n) -> Result<CheckExprType> {\n    match func {\n        Func::Scalar(sf) => resolve_scalar_func_return_type(sf, args, columns, resolver),\n        Func::Math(mf) => resolve_math_func_return_type(mf),\n        #[cfg(feature = \"json\")]\n        Func::Json(jf) => resolve_json_func_return_type(jf),\n        Func::Agg(_) => bail_parse_error!(\"misuse of aggregate function {}()\", name),\n        Func::External(_) => {\n            bail_parse_error!(\n                \"cannot determine return type of function {}() in CHECK constraint; \\\n                 wrap with CAST to specify the type, e.g. CAST({}(...) AS INTEGER)\",\n                name,\n                name\n            );\n        }\n        _ => Ok(CheckExprType::Any),\n    }\n}\n\n/// Resolve the return type of a scalar function.\nfn resolve_scalar_func_return_type(\n    func: &ScalarFunc,\n    args: &[Box<ast::Expr>],\n    columns: &[&ast::ColumnDefinition],\n    resolver: &Resolver,\n) -> Result<CheckExprType> {\n    match func {\n        // Functions that always return INTEGER\n        ScalarFunc::Length\n        | ScalarFunc::OctetLength\n        | ScalarFunc::Instr\n        | ScalarFunc::Unicode\n        | ScalarFunc::Sign\n        | ScalarFunc::Random\n        | ScalarFunc::Changes\n        | ScalarFunc::TotalChanges\n        | ScalarFunc::LastInsertRowid\n        | ScalarFunc::Glob\n        | ScalarFunc::Like\n        | ScalarFunc::Likely\n        | ScalarFunc::Unlikely\n        | ScalarFunc::Likelihood\n        | ScalarFunc::BooleanToInt\n        | ScalarFunc::IntToBoolean\n        | ScalarFunc::IsAutocommit\n        | ScalarFunc::ConnTxnId\n        | ScalarFunc::TestUintLt\n        | ScalarFunc::TestUintEq\n        | ScalarFunc::NumericLt\n        | ScalarFunc::NumericEq\n        | ScalarFunc::ValidateIpAddr\n        | ScalarFunc::UnixEpoch => Ok(CheckExprType::Integer),\n\n        // Functions that always return TEXT\n        ScalarFunc::Upper\n        | ScalarFunc::Lower\n        | ScalarFunc::Trim\n        | ScalarFunc::LTrim\n        | ScalarFunc::RTrim\n        | ScalarFunc::Hex\n        | ScalarFunc::Soundex\n        | ScalarFunc::Quote\n        | ScalarFunc::Replace\n        | ScalarFunc::Substr\n        | ScalarFunc::Substring\n        | ScalarFunc::Char\n        | ScalarFunc::Concat\n        | ScalarFunc::ConcatWs\n        | ScalarFunc::Typeof\n        | ScalarFunc::SqliteVersion\n        | ScalarFunc::TursoVersion\n        | ScalarFunc::SqliteSourceId\n        | ScalarFunc::Date\n        | ScalarFunc::Time\n        | ScalarFunc::DateTime\n        | ScalarFunc::StrfTime\n        | ScalarFunc::TimeDiff\n        | ScalarFunc::Printf\n        | ScalarFunc::StringReverse => Ok(CheckExprType::Text),\n\n        // Functions that always return REAL\n        ScalarFunc::Round | ScalarFunc::JulianDay => Ok(CheckExprType::Real),\n\n        // Functions that always return BLOB\n        ScalarFunc::RandomBlob | ScalarFunc::ZeroBlob | ScalarFunc::Unhex => {\n            Ok(CheckExprType::Blob)\n        }\n\n        // Functions whose return type depends on arguments\n        ScalarFunc::Abs | ScalarFunc::Nullif => {\n            if let Some(arg) = args.first() {\n                resolve_check_expr_type(arg, columns, resolver)\n            } else {\n                Ok(CheckExprType::Any)\n            }\n        }\n\n        ScalarFunc::Coalesce | ScalarFunc::IfNull => {\n            for arg in args {\n                let ty = resolve_check_expr_type(arg, columns, resolver)?;\n                if ty != CheckExprType::Null {\n                    return Ok(ty);\n                }\n            }\n            Ok(CheckExprType::Null)\n        }\n\n        ScalarFunc::Min | ScalarFunc::Max => {\n            if let Some(first) = args.first() {\n                resolve_check_expr_type(first, columns, resolver)\n            } else {\n                Ok(CheckExprType::Any)\n            }\n        }\n\n        ScalarFunc::Iif => {\n            // iif(cond, then_val, else_val) — return type of then_val\n            if args.len() >= 2 {\n                resolve_check_expr_type(&args[1], columns, resolver)\n            } else {\n                Ok(CheckExprType::Any)\n            }\n        }\n\n        // Internal/custom type functions\n        ScalarFunc::TestUintEncode\n        | ScalarFunc::TestUintDecode\n        | ScalarFunc::TestUintAdd\n        | ScalarFunc::TestUintSub\n        | ScalarFunc::TestUintMul\n        | ScalarFunc::TestUintDiv\n        | ScalarFunc::NumericEncode\n        | ScalarFunc::NumericDecode\n        | ScalarFunc::NumericAdd\n        | ScalarFunc::NumericSub\n        | ScalarFunc::NumericMul\n        | ScalarFunc::NumericDiv => Ok(CheckExprType::Blob),\n\n        // Remaining functions — treat as ANY\n        _ => Ok(CheckExprType::Any),\n    }\n}\n\n/// Resolve the return type of a math function.\nfn resolve_math_func_return_type(func: &MathFunc) -> Result<CheckExprType> {\n    match func {\n        // Floor/ceil/trunc return INTEGER for integer inputs, but always produce numeric results\n        MathFunc::Ceil | MathFunc::Ceiling | MathFunc::Floor | MathFunc::Trunc => {\n            Ok(CheckExprType::Integer)\n        }\n        // All other math functions return REAL\n        _ => Ok(CheckExprType::Real),\n    }\n}\n\n/// Resolve the return type of a JSON function.\n#[cfg(feature = \"json\")]\nfn resolve_json_func_return_type(func: &crate::function::JsonFunc) -> Result<CheckExprType> {\n    use crate::function::JsonFunc;\n    match func {\n        // Functions that return TEXT (JSON text)\n        JsonFunc::Json\n        | JsonFunc::JsonArray\n        | JsonFunc::JsonObject\n        | JsonFunc::JsonPatch\n        | JsonFunc::JsonRemove\n        | JsonFunc::JsonReplace\n        | JsonFunc::JsonInsert\n        | JsonFunc::JsonSet\n        | JsonFunc::JsonPretty\n        | JsonFunc::JsonQuote\n        | JsonFunc::JsonType => Ok(CheckExprType::Text),\n\n        // Functions that return BLOB (JSONB binary)\n        JsonFunc::Jsonb\n        | JsonFunc::JsonbArray\n        | JsonFunc::JsonbObject\n        | JsonFunc::JsonbPatch\n        | JsonFunc::JsonbRemove\n        | JsonFunc::JsonbReplace\n        | JsonFunc::JsonbInsert\n        | JsonFunc::JsonbSet => Ok(CheckExprType::Blob),\n\n        // Functions that return INTEGER\n        JsonFunc::JsonArrayLength | JsonFunc::JsonErrorPosition | JsonFunc::JsonValid => {\n            Ok(CheckExprType::Integer)\n        }\n\n        // Extract functions can return any type\n        JsonFunc::JsonExtract\n        | JsonFunc::JsonbExtract\n        | JsonFunc::JsonArrowExtract\n        | JsonFunc::JsonArrowShiftExtract => Ok(CheckExprType::Any),\n    }\n}\n\n/// Resolve a column's type from its definition.\nfn resolve_column_type(col: &ast::ColumnDefinition, resolver: &Resolver) -> Result<CheckExprType> {\n    if let Some(ref col_type) = col.col_type {\n        resolve_type_name(&col_type.name, resolver)\n    } else {\n        // No type specified — in STRICT tables this would be caught elsewhere,\n        // but treat as ANY for CHECK validation purposes.\n        Ok(CheckExprType::Any)\n    }\n}\n\n/// Resolve a type name string to a CheckExprType.\nfn resolve_type_name(type_name: &str, resolver: &Resolver) -> Result<CheckExprType> {\n    let name_bytes = type_name.as_bytes();\n    let result = turso_macros::match_ignore_ascii_case!(match name_bytes {\n        b\"INT\" | b\"INTEGER\" => Some(CheckExprType::Integer),\n        b\"REAL\" | b\"FLOAT\" | b\"DOUBLE\" => Some(CheckExprType::Real),\n        b\"TEXT\" => Some(CheckExprType::Text),\n        b\"BLOB\" => Some(CheckExprType::Blob),\n        b\"ANY\" => Some(CheckExprType::Any),\n        _ => None,\n    });\n    if let Some(ty) = result {\n        return Ok(ty);\n    }\n    // Check if it's a known custom type\n    if resolver\n        .schema()\n        .get_type_def_unchecked(type_name)\n        .is_some()\n    {\n        return Ok(CheckExprType::CustomType(type_name.to_lowercase()));\n    }\n    bail_parse_error!(\"unknown type '{}' in CHECK constraint\", type_name);\n}\n\n/// Walk a CHECK expression and validate that all comparisons have compatible types.\n/// Only called for STRICT tables.\nfn validate_check_types_in_expr(\n    expr: &ast::Expr,\n    columns: &[&ast::ColumnDefinition],\n    resolver: &Resolver,\n) -> Result<()> {\n    use ast::Operator;\n    match expr {\n        ast::Expr::Binary(lhs, op, rhs) => {\n            match op {\n                Operator::Equals\n                | Operator::NotEquals\n                | Operator::Less\n                | Operator::LessEquals\n                | Operator::Greater\n                | Operator::GreaterEquals => {\n                    let lty = resolve_check_expr_type(lhs, columns, resolver)?;\n                    let rty = resolve_check_expr_type(rhs, columns, resolver)?;\n                    if !lty.is_compatible_with(&rty) {\n                        bail_parse_error!(\n                            \"type mismatch in CHECK constraint: cannot compare {} with {}\",\n                            lty.display_name(),\n                            rty.display_name()\n                        );\n                    }\n                }\n                Operator::And | Operator::Or => {\n                    validate_check_types_in_expr(lhs, columns, resolver)?;\n                    validate_check_types_in_expr(rhs, columns, resolver)?;\n                }\n                // Arithmetic, concat, bitwise — recurse to find nested comparisons\n                _ => {\n                    validate_check_types_in_expr(lhs, columns, resolver)?;\n                    validate_check_types_in_expr(rhs, columns, resolver)?;\n                }\n            }\n        }\n        ast::Expr::Between {\n            lhs, start, end, ..\n        } => {\n            let lty = resolve_check_expr_type(lhs, columns, resolver)?;\n            let sty = resolve_check_expr_type(start, columns, resolver)?;\n            let ety = resolve_check_expr_type(end, columns, resolver)?;\n            if !lty.is_compatible_with(&sty) {\n                bail_parse_error!(\n                    \"type mismatch in CHECK BETWEEN: cannot compare {} with {}\",\n                    lty.display_name(),\n                    sty.display_name()\n                );\n            }\n            if !lty.is_compatible_with(&ety) {\n                bail_parse_error!(\n                    \"type mismatch in CHECK BETWEEN: cannot compare {} with {}\",\n                    lty.display_name(),\n                    ety.display_name()\n                );\n            }\n        }\n        ast::Expr::InList { lhs, rhs, .. } => {\n            let lty = resolve_check_expr_type(lhs, columns, resolver)?;\n            for item in rhs {\n                let ity = resolve_check_expr_type(item, columns, resolver)?;\n                if !lty.is_compatible_with(&ity) {\n                    bail_parse_error!(\n                        \"type mismatch in CHECK IN list: cannot compare {} with {}\",\n                        lty.display_name(),\n                        ity.display_name()\n                    );\n                }\n            }\n        }\n        ast::Expr::Parenthesized(exprs) => {\n            for e in exprs {\n                validate_check_types_in_expr(e, columns, resolver)?;\n            }\n        }\n        ast::Expr::Unary(_, inner) => {\n            validate_check_types_in_expr(inner, columns, resolver)?;\n        }\n        ast::Expr::Case {\n            base,\n            when_then_pairs,\n            else_expr,\n        } => {\n            if let Some(op) = base {\n                validate_check_types_in_expr(op, columns, resolver)?;\n            }\n            for (when_expr, then_expr) in when_then_pairs {\n                validate_check_types_in_expr(when_expr, columns, resolver)?;\n                validate_check_types_in_expr(then_expr, columns, resolver)?;\n            }\n            if let Some(else_e) = else_expr {\n                validate_check_types_in_expr(else_e, columns, resolver)?;\n            }\n        }\n        ast::Expr::FunctionCall { args, .. } => {\n            for arg in args {\n                validate_check_types_in_expr(arg, columns, resolver)?;\n            }\n        }\n        // Leaf nodes and other expressions: no nested comparisons to validate\n        _ => {}\n    }\n    Ok(())\n}\n\nfn validate(body: &ast::CreateTableBody, table_name: &str, resolver: &Resolver) -> Result<()> {\n    if let ast::CreateTableBody::ColumnsAndConstraints {\n        options,\n        columns,\n        constraints,\n    } = &body\n    {\n        if options.contains_without_rowid() {\n            bail_parse_error!(\"WITHOUT ROWID tables are not supported\");\n        }\n        let column_names: Vec<&str> = columns.iter().map(|c| c.col_name.as_str()).collect();\n        for i in 0..columns.len() {\n            let col_i = &columns[i];\n            for constraint in &col_i.constraints {\n                match &constraint.constraint {\n                    ast::ColumnConstraint::Check(expr) => {\n                        validate_check_expr(expr, table_name, &column_names, resolver)?;\n                    }\n                    ast::ColumnConstraint::Generated { .. } => {\n                        bail_parse_error!(\"GENERATED columns are not supported yet\");\n                    }\n                    ast::ColumnConstraint::Default(expr) => {\n                        let expr =\n                            translate_ident_to_string_literal(expr).unwrap_or_else(|| expr.clone());\n                        validate_default_expr(&expr, col_i)?\n                    }\n                    _ => {}\n                }\n            }\n            for j in &columns[(i + 1)..] {\n                if col_i\n                    .col_name\n                    .as_str()\n                    .eq_ignore_ascii_case(j.col_name.as_str())\n                {\n                    bail_parse_error!(\"duplicate column name: {}\", j.col_name.as_str());\n                }\n            }\n        }\n        for constraint in constraints {\n            if let ast::TableConstraint::Check(ref expr) = constraint.constraint {\n                validate_check_expr(expr, table_name, &column_names, resolver)?;\n            }\n        }\n\n        let is_strict = options.contains_strict();\n\n        for c in columns {\n            if let Some(ref col_type) = c.col_type {\n                let type_name = &col_type.name;\n                let name_bytes = type_name.as_bytes();\n                let is_builtin = turso_macros::match_ignore_ascii_case!(match name_bytes {\n                    b\"INT\" | b\"INTEGER\" | b\"REAL\" | b\"TEXT\" | b\"BLOB\" | b\"ANY\" => true,\n                    _ => false,\n                });\n\n                // Array columns require STRICT tables because the encode/decode\n                // pipeline is only emitted for STRICT tables.\n                if col_type.is_array() && !is_strict {\n                    bail_parse_error!(\n                        \"array type columns require STRICT tables: {}.{}\",\n                        table_name,\n                        c.col_name\n                    );\n                }\n\n                if !is_builtin && is_strict {\n                    // On non-STRICT tables any type name is allowed and is\n                    // treated as a plain affinity hint (no encode/decode).\n                    // Custom type validation only applies to STRICT tables.\n                    let type_def = resolver.schema().get_type_def_unchecked(type_name);\n                    {\n                        match type_def {\n                            None => {\n                                bail_parse_error!(\n                                    \"unknown datatype for {}.{}: \\\"{}\\\"\",\n                                    table_name,\n                                    c.col_name,\n                                    type_name\n                                );\n                            }\n                            Some(td) if td.user_params().next().is_some() => {\n                                // Parametric type: verify the column provides the right\n                                // number of user parameters (excluding `value`).\n                                let provided = match &col_type.size {\n                                    Some(ast::TypeSize::TypeSize(_, _)) => 2,\n                                    Some(ast::TypeSize::MaxSize(_)) => 1,\n                                    None => 0,\n                                };\n                                let expected = td.user_params().count();\n                                if provided != expected {\n                                    bail_parse_error!(\n                                        \"type \\\"{}\\\" requires {} parameter(s), got {}\",\n                                        type_name,\n                                        expected,\n                                        provided\n                                    );\n                                }\n                            }\n                            Some(_) => {}\n                        }\n                    }\n                }\n            }\n        }\n\n        // In STRICT tables, validate that CHECK constraint comparisons have\n        // compatible types. This catches type mismatches at CREATE TABLE time\n        // rather than producing wrong results at INSERT/UPDATE time.\n        if is_strict {\n            let col_refs: Vec<&ast::ColumnDefinition> = columns.iter().collect();\n            for col in columns {\n                for constraint in &col.constraints {\n                    if let ast::ColumnConstraint::Check(expr) = &constraint.constraint {\n                        validate_check_types_in_expr(expr, &col_refs, resolver)?;\n                    }\n                }\n            }\n            for constraint in constraints {\n                if let ast::TableConstraint::Check(ref expr) = constraint.constraint {\n                    validate_check_types_in_expr(expr, &col_refs, resolver)?;\n                }\n            }\n        }\n    }\n    Ok(())\n}\n\npub fn translate_create_table(\n    tbl_name: ast::QualifiedName,\n    resolver: &Resolver,\n    temporary: bool,\n    if_not_exists: bool,\n    body: ast::CreateTableBody,\n    program: &mut ProgramBuilder,\n    connection: &Connection,\n) -> Result<()> {\n    let database_id = resolver.resolve_database_id(&tbl_name)?;\n    if crate::is_attached_db(database_id) {\n        let schema_cookie = resolver.with_schema(database_id, |s| s.schema_version);\n        program.begin_write_on_database(database_id, schema_cookie);\n    }\n    let normalized_tbl_name = normalize_ident(tbl_name.name.as_str());\n    if temporary {\n        bail_parse_error!(\"TEMPORARY table not supported yet\");\n    }\n    validate(&body, &normalized_tbl_name, resolver)?;\n\n    // Gate array column types behind the experimental custom types flag.\n    if !connection.experimental_custom_types_enabled() {\n        if let ast::CreateTableBody::ColumnsAndConstraints { columns, .. } = &body {\n            for col in columns {\n                if col.col_type.as_ref().is_some_and(|t| t.is_array()) {\n                    bail_parse_error!(\n                        \"Array column types require --experimental-custom-types flag\"\n                    );\n                }\n            }\n        }\n    }\n\n    let opts = ProgramBuilderOpts {\n        num_cursors: 1,\n        approx_num_insns: 30,\n        approx_num_labels: 1,\n    };\n    program.extend(&opts);\n\n    if !connection.is_mvcc_bootstrap_connection()\n        && RESERVED_TABLE_PREFIXES\n            .iter()\n            .any(|prefix| normalized_tbl_name.starts_with(prefix))\n        && !connection.is_nested_stmt()\n    {\n        bail_parse_error!(\n            \"Object name reserved for internal use: {}\",\n            tbl_name.name.as_str()\n        );\n    }\n\n    // Check for name conflicts with existing schema objects\n    if let Some(object_type) =\n        resolver.with_schema(database_id, |s| s.get_object_type(&normalized_tbl_name))\n    {\n        match object_type {\n            // IF NOT EXISTS suppresses errors for table/view conflicts\n            SchemaObjectType::Table | SchemaObjectType::View if if_not_exists => {\n                return Ok(());\n            }\n            _ => {\n                let type_str = match object_type {\n                    SchemaObjectType::Table => \"table\",\n                    SchemaObjectType::View => \"view\",\n                    SchemaObjectType::Index => \"index\",\n                };\n                bail_parse_error!(\"{} {} already exists\", type_str, normalized_tbl_name);\n            }\n        }\n    }\n\n    let mut has_autoincrement = false;\n    if let ast::CreateTableBody::ColumnsAndConstraints {\n        columns,\n        constraints,\n        ..\n    } = &body\n    {\n        for col in columns {\n            for constraint in &col.constraints {\n                if let ast::ColumnConstraint::PrimaryKey { auto_increment, .. } =\n                    constraint.constraint\n                {\n                    if auto_increment {\n                        has_autoincrement = true;\n                        break;\n                    }\n                }\n            }\n            if has_autoincrement {\n                break;\n            }\n        }\n        if !has_autoincrement {\n            for constraint in constraints {\n                if let ast::TableConstraint::PrimaryKey { auto_increment, .. } =\n                    constraint.constraint\n                {\n                    if auto_increment {\n                        has_autoincrement = true;\n                        break;\n                    }\n                }\n            }\n        }\n    }\n\n    if has_autoincrement && connection.mvcc_enabled() {\n        bail_parse_error!(\n            \"AUTOINCREMENT is not supported in MVCC mode (journal_mode=experimental_mvcc)\"\n        );\n    }\n\n    let schema_master_table = resolver.schema().get_btree_table(SQLITE_TABLEID).unwrap();\n    let sqlite_schema_cursor_id =\n        program.alloc_cursor_id(CursorType::BTreeTable(schema_master_table));\n    program.emit_insn(Insn::OpenWrite {\n        cursor_id: sqlite_schema_cursor_id,\n        root_page: 1i64.into(),\n        db: database_id,\n    });\n    let cdc_table = prepare_cdc_if_necessary(program, resolver.schema(), SQLITE_TABLEID)?;\n\n    let created_sequence_table = if has_autoincrement\n        && resolver.with_schema(database_id, |s| {\n            s.get_table(SQLITE_SEQUENCE_TABLE_NAME).is_none()\n        }) {\n        let seq_table_root_reg = program.alloc_register();\n        program.emit_insn(Insn::CreateBtree {\n            db: database_id,\n            root: seq_table_root_reg,\n            flags: CreateBTreeFlags::new_table(),\n        });\n\n        let seq_sql = \"CREATE TABLE sqlite_sequence(name,seq)\";\n        emit_schema_entry(\n            program,\n            resolver,\n            sqlite_schema_cursor_id,\n            cdc_table.as_ref().map(|x| x.0),\n            SchemaEntryType::Table,\n            SQLITE_SEQUENCE_TABLE_NAME,\n            SQLITE_SEQUENCE_TABLE_NAME,\n            seq_table_root_reg,\n            Some(seq_sql.to_string()),\n        )?;\n        true\n    } else {\n        false\n    };\n\n    let sql = create_table_body_to_str(&tbl_name, &body)?;\n\n    let parse_schema_label = program.allocate_label();\n    // TODO: ReadCookie\n    // TODO: If\n    // TODO: SetCookie\n    // TODO: SetCookie\n\n    let table_root_reg = program.alloc_register();\n    program.emit_insn(Insn::CreateBtree {\n        db: database_id,\n        root: table_root_reg,\n        flags: CreateBTreeFlags::new_table(),\n    });\n\n    // Create an automatic index B-tree if needed\n    //\n    // NOTE: we are deviating from SQLite bytecode here. For some reason, SQLite first creates a placeholder entry\n    // for the table in sqlite_schema, then writes the index to sqlite_schema, then UPDATEs the table placeholder entry\n    // in sqlite_schema with actual data.\n    //\n    // What we do instead is:\n    // 1. Create the table B-tree\n    // 2. Create the index B-tree\n    // 3. Add the table entry to sqlite_schema\n    // 4. Add the index entry to sqlite_schema\n    //\n    // I.e. we skip the weird song and dance with the placeholder entry. Unclear why sqlite does this.\n    // The sqlite code has this comment:\n    //\n    // \"This just creates a place-holder record in the sqlite_schema table.\n    // The record created does not contain anything yet.  It will be replaced\n    // by the real entry in code generated at sqlite3EndTable().\"\n    //\n    // References:\n    // https://github.com/sqlite/sqlite/blob/95f6df5b8d55e67d1e34d2bff217305a2f21b1fb/src/build.c#L1355\n    // https://github.com/sqlite/sqlite/blob/95f6df5b8d55e67d1e34d2bff217305a2f21b1fb/src/build.c#L2856-L2871\n    // https://github.com/sqlite/sqlite/blob/95f6df5b8d55e67d1e34d2bff217305a2f21b1fb/src/build.c#L1334C5-L1336C65\n\n    let index_regs = collect_autoindexes(&body, program, &normalized_tbl_name)?;\n    if let Some(index_regs) = index_regs.as_ref() {\n        for index_reg in index_regs.iter() {\n            program.emit_insn(Insn::CreateBtree {\n                db: database_id,\n                root: *index_reg,\n                flags: CreateBTreeFlags::new_index(),\n            });\n        }\n    }\n\n    let table = resolver.schema().get_btree_table(SQLITE_TABLEID).unwrap();\n    let sqlite_schema_cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(table));\n    program.emit_insn(Insn::OpenWrite {\n        cursor_id: sqlite_schema_cursor_id,\n        root_page: 1i64.into(),\n        db: database_id,\n    });\n\n    let cdc_table = prepare_cdc_if_necessary(program, resolver.schema(), SQLITE_TABLEID)?;\n\n    emit_schema_entry(\n        program,\n        resolver,\n        sqlite_schema_cursor_id,\n        cdc_table.as_ref().map(|x| x.0),\n        SchemaEntryType::Table,\n        &normalized_tbl_name,\n        &normalized_tbl_name,\n        table_root_reg,\n        Some(sql),\n    )?;\n\n    if let Some(index_regs) = index_regs {\n        for (idx, index_reg) in index_regs.into_iter().enumerate() {\n            let index_name = format!(\n                \"{PRIMARY_KEY_AUTOMATIC_INDEX_NAME_PREFIX}{}_{}\",\n                normalized_tbl_name,\n                idx + 1\n            );\n            emit_schema_entry(\n                program,\n                resolver,\n                sqlite_schema_cursor_id,\n                None,\n                SchemaEntryType::Index,\n                &index_name,\n                &normalized_tbl_name,\n                index_reg,\n                None,\n            )?;\n        }\n    }\n\n    program.resolve_label(parse_schema_label, program.offset());\n    let schema_version = resolver.with_schema(database_id, |s| s.schema_version);\n    program.emit_insn(Insn::SetCookie {\n        db: database_id,\n        cookie: Cookie::SchemaVersion,\n        value: schema_version as i32 + 1,\n        p5: 0,\n    });\n\n    // TODO: remove format, it sucks for performance but is convenient\n    let escaped_tbl_name = escape_sql_string_literal(&normalized_tbl_name);\n    let mut parse_schema_where_clause =\n        format!(\"tbl_name = '{escaped_tbl_name}' AND type != 'trigger'\");\n    if created_sequence_table {\n        parse_schema_where_clause.push_str(\" OR tbl_name = 'sqlite_sequence'\");\n    }\n\n    program.emit_insn(Insn::ParseSchema {\n        db: database_id,\n        where_clause: Some(parse_schema_where_clause),\n    });\n\n    // TODO: SqlExec\n\n    Ok(())\n}\n\n#[derive(Debug, Clone, Copy)]\npub enum SchemaEntryType {\n    Table,\n    Index,\n    View,\n    Trigger,\n}\n\nimpl SchemaEntryType {\n    fn as_str(&self) -> &'static str {\n        match self {\n            SchemaEntryType::Table => \"table\",\n            SchemaEntryType::Index => \"index\",\n            SchemaEntryType::View => \"view\",\n            SchemaEntryType::Trigger => \"trigger\",\n        }\n    }\n}\npub const SQLITE_TABLEID: &str = \"sqlite_schema\";\n\n#[allow(clippy::too_many_arguments)]\npub fn emit_schema_entry(\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    sqlite_schema_cursor_id: usize,\n    cdc_table_cursor_id: Option<usize>,\n    entry_type: SchemaEntryType,\n    name: &str,\n    tbl_name: &str,\n    root_page_reg: usize,\n    sql: Option<String>,\n) -> Result<()> {\n    let rowid_reg = program.alloc_register();\n    program.emit_insn(Insn::NewRowid {\n        cursor: sqlite_schema_cursor_id,\n        rowid_reg,\n        prev_largest_reg: 0,\n    });\n\n    let type_reg = program.emit_string8_new_reg(entry_type.as_str().to_string());\n    program.emit_string8_new_reg(name.to_string());\n    program.emit_string8_new_reg(tbl_name.to_string());\n\n    let table_root_reg = program.alloc_register();\n    if root_page_reg == 0 {\n        program.emit_insn(Insn::Integer {\n            dest: table_root_reg,\n            value: 0, // virtual tables in sqlite always have rootpage=0\n        });\n    } else {\n        program.emit_insn(Insn::Copy {\n            src_reg: root_page_reg,\n            dst_reg: table_root_reg,\n            extra_amount: 0,\n        });\n    }\n\n    let sql_reg = program.alloc_register();\n    if let Some(sql) = sql {\n        program.emit_string8(sql, sql_reg);\n    } else {\n        program.emit_null(sql_reg, None);\n    }\n\n    let record_reg = program.alloc_register();\n    program.emit_insn(Insn::MakeRecord {\n        start_reg: to_u16(type_reg),\n        count: to_u16(5),\n        dest_reg: to_u16(record_reg),\n        index_name: None,\n        affinity_str: None,\n    });\n\n    program.emit_insn(Insn::Insert {\n        cursor: sqlite_schema_cursor_id,\n        key_reg: rowid_reg,\n        record_reg,\n        flag: InsertFlags::new(),\n        table_name: tbl_name.to_string(),\n    });\n\n    if let Some(cdc_table_cursor_id) = cdc_table_cursor_id {\n        let after_record_reg = if program.capture_data_changes_info().has_after() {\n            Some(record_reg)\n        } else {\n            None\n        };\n        emit_cdc_insns(\n            program,\n            resolver,\n            OperationMode::INSERT,\n            cdc_table_cursor_id,\n            rowid_reg,\n            None,\n            after_record_reg,\n            None,\n            SQLITE_TABLEID,\n        )?;\n        emit_cdc_autocommit_commit(program, resolver, cdc_table_cursor_id)?;\n    }\n    Ok(())\n}\n\n/// Check if an automatic PRIMARY KEY index is required for the table.\n/// If so, create a register for the index root page and return it.\n///\n/// An automatic PRIMARY KEY index is not required if:\n/// - The table has no PRIMARY KEY\n/// - The table has a single-column PRIMARY KEY whose typename is _exactly_ \"INTEGER\" e.g. not \"INT\".\n///   In this case, the PRIMARY KEY column becomes an alias for the rowid.\n///\n/// Otherwise, an automatic PRIMARY KEY index is required.\nfn collect_autoindexes(\n    body: &ast::CreateTableBody,\n    program: &mut ProgramBuilder,\n    tbl_name: &str,\n) -> Result<Option<Vec<usize>>> {\n    let table = create_table(tbl_name, body, 0)?;\n\n    let mut regs: Vec<usize> = Vec::new();\n\n    // include UNIQUE singles, include PK single only if not rowid alias\n    for us in table.unique_sets.iter().filter(|us| us.columns.len() == 1) {\n        let (col_name, _sort) = us.columns.first().unwrap();\n        let Some((_pos, col)) = table.get_column(col_name) else {\n            bail_parse_error!(\"Column {col_name} not found in table {}\", table.name);\n        };\n\n        let needs_index = if us.is_primary_key {\n            !(col.primary_key() && col.is_rowid_alias())\n        } else {\n            // UNIQUE single needs an index\n            true\n        };\n\n        if needs_index {\n            regs.push(program.alloc_register());\n        }\n    }\n\n    for _us in table.unique_sets.iter().filter(|us| us.columns.len() > 1) {\n        regs.push(program.alloc_register());\n    }\n    if regs.is_empty() {\n        Ok(None)\n    } else {\n        Ok(Some(regs))\n    }\n}\n\nfn create_table_body_to_str(\n    tbl_name: &ast::QualifiedName,\n    body: &ast::CreateTableBody,\n) -> crate::Result<String> {\n    let mut sql = String::new();\n    sql.push_str(format!(\"CREATE TABLE {} {}\", tbl_name.name.as_ident(), body).as_str());\n    match body {\n        ast::CreateTableBody::ColumnsAndConstraints {\n            columns: _,\n            constraints: _,\n            options: _,\n        } => {}\n        ast::CreateTableBody::AsSelect(_select) => {\n            crate::bail_parse_error!(\"CREATE TABLE AS SELECT is not supported\")\n        }\n    }\n    Ok(sql)\n}\n\nfn create_vtable_body_to_str(vtab: &ast::CreateVirtualTable, module: Arc<VTabImpl>) -> String {\n    let args = vtab\n        .args\n        .iter()\n        .map(|arg| arg.to_string())\n        .collect::<Vec<String>>()\n        .join(\", \");\n    let if_not_exists = if vtab.if_not_exists {\n        \"IF NOT EXISTS \"\n    } else {\n        \"\"\n    };\n    let ext_args = vtab\n        .args\n        .iter()\n        .map(|a| turso_ext::Value::from_text(a.to_string()))\n        .collect::<Vec<_>>();\n    let schema = module\n        .implementation\n        .create_schema(ext_args)\n        .unwrap_or_default();\n    let vtab_args = if let Some(first_paren) = schema.find('(') {\n        let closing_paren = schema.rfind(')').unwrap_or_default();\n        &schema[first_paren..=closing_paren]\n    } else {\n        \"()\"\n    };\n    format!(\n        \"CREATE VIRTUAL TABLE {} {} USING {}{}\\n /*{}{}*/\",\n        vtab.tbl_name.name.as_ident(),\n        if_not_exists,\n        vtab.module_name.as_ident(),\n        if args.is_empty() {\n            String::new()\n        } else {\n            format!(\"({args})\")\n        },\n        vtab.tbl_name.name.as_ident(),\n        vtab_args\n    )\n}\n\npub fn translate_create_virtual_table(\n    vtab: ast::CreateVirtualTable,\n    resolver: &Resolver,\n    program: &mut ProgramBuilder,\n    connection: &Arc<crate::Connection>,\n) -> Result<()> {\n    if connection.mvcc_enabled() {\n        bail_parse_error!(\"Virtual tables are not supported in MVCC mode\");\n    }\n    let ast::CreateVirtualTable {\n        if_not_exists,\n        tbl_name,\n        module_name,\n        args,\n    } = &vtab;\n\n    let table_name = tbl_name.name.as_str().to_string();\n    let module_name_str = module_name.as_str().to_string();\n    let args_vec = args.clone();\n    let Some(vtab_module) = resolver.symbol_table.vtab_modules.get(&module_name_str) else {\n        bail_parse_error!(\"no such module: {}\", module_name_str);\n    };\n    if !vtab_module.module_kind.eq(&VTabKind::VirtualTable) {\n        bail_parse_error!(\"module {} is not a virtual table\", module_name_str);\n    };\n    if resolver.schema().get_table(&table_name).is_some() {\n        if *if_not_exists {\n            return Ok(());\n        }\n        bail_parse_error!(\"Table {} already exists\", tbl_name);\n    }\n\n    let opts = ProgramBuilderOpts {\n        num_cursors: 2,\n        approx_num_insns: 40,\n        approx_num_labels: 2,\n    };\n    program.extend(&opts);\n    let module_name_reg = program.emit_string8_new_reg(module_name_str.clone());\n    let table_name_reg = program.emit_string8_new_reg(table_name.clone());\n    let args_reg = if !args_vec.is_empty() {\n        let args_start = program.alloc_register();\n\n        // Emit string8 instructions for each arg\n        for (i, arg) in args_vec.iter().enumerate() {\n            program.emit_string8(arg.clone(), args_start + i);\n        }\n        let args_record_reg = program.alloc_register();\n\n        // VCreate expects an array of args as a record\n        program.emit_insn(Insn::MakeRecord {\n            start_reg: to_u16(args_start),\n            count: to_u16(args_vec.len()),\n            dest_reg: to_u16(args_record_reg),\n            index_name: None,\n            affinity_str: None,\n        });\n        Some(args_record_reg)\n    } else {\n        None\n    };\n\n    program.emit_insn(Insn::VCreate {\n        module_name: module_name_reg,\n        table_name: table_name_reg,\n        args_reg,\n    });\n    let table = resolver.schema().get_btree_table(SQLITE_TABLEID).unwrap();\n    let sqlite_schema_cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(table));\n    program.emit_insn(Insn::OpenWrite {\n        cursor_id: sqlite_schema_cursor_id,\n        root_page: 1i64.into(),\n        db: crate::MAIN_DB_ID,\n    });\n\n    let cdc_table = prepare_cdc_if_necessary(program, resolver.schema(), SQLITE_TABLEID)?;\n    let sql = create_vtable_body_to_str(&vtab, vtab_module.clone());\n    emit_schema_entry(\n        program,\n        resolver,\n        sqlite_schema_cursor_id,\n        cdc_table.map(|x| x.0),\n        SchemaEntryType::Table,\n        tbl_name.name.as_str(),\n        tbl_name.name.as_str(),\n        0, // virtual tables dont have a root page\n        Some(sql),\n    )?;\n\n    program.emit_insn(Insn::SetCookie {\n        db: crate::MAIN_DB_ID,\n        cookie: Cookie::SchemaVersion,\n        value: resolver.schema().schema_version as i32 + 1,\n        p5: 0,\n    });\n    let escaped_table_name = escape_sql_string_literal(&table_name);\n    let parse_schema_where_clause =\n        format!(\"tbl_name = '{escaped_table_name}' AND type != 'trigger'\");\n    program.emit_insn(Insn::ParseSchema {\n        db: sqlite_schema_cursor_id,\n        where_clause: Some(parse_schema_where_clause),\n    });\n\n    Ok(())\n}\n\n/// Validates whether a DROP TABLE operation is allowed on the given table name.\nfn validate_drop_table(\n    resolver: &Resolver,\n    tbl_name: &str,\n    connection: &Arc<Connection>,\n) -> Result<()> {\n    if !connection.is_nested_stmt()\n        && crate::schema::is_system_table(tbl_name)\n        // special case, allow dropping `sqlite_stat1`\n        && !tbl_name.eq_ignore_ascii_case(STATS_TABLE)\n    {\n        bail_parse_error!(\"Cannot drop system table {}\", tbl_name);\n    }\n    // Check if this is a materialized view - if so, refuse to drop it with DROP TABLE\n    if resolver.schema().is_materialized_view(tbl_name) {\n        bail_parse_error!(\n            \"Cannot DROP TABLE on materialized view {tbl_name}. Use DROP VIEW instead.\",\n        );\n    }\n    Ok(())\n}\n\npub fn translate_drop_table(\n    tbl_name: ast::QualifiedName,\n    resolver: &mut Resolver,\n    if_exists: bool,\n    program: &mut ProgramBuilder,\n    connection: &Arc<Connection>,\n) -> Result<()> {\n    let database_id = resolver.resolve_database_id(&tbl_name)?;\n    let name = tbl_name.name.as_str();\n    let opts = ProgramBuilderOpts {\n        num_cursors: 4,\n        approx_num_insns: 40,\n        approx_num_labels: 4,\n    };\n    program.extend(&opts);\n\n    if crate::is_attached_db(database_id) {\n        let schema_cookie = resolver.with_schema(database_id, |s| s.schema_version);\n        program.begin_write_on_database(database_id, schema_cookie);\n    }\n\n    let Some(table) = resolver.with_schema(database_id, |s| s.get_table(name)) else {\n        if if_exists {\n            return Ok(());\n        }\n        bail_parse_error!(\"No such table: {name}\");\n    };\n    validate_drop_table(resolver, name, connection)?;\n    // Check if foreign keys are enabled and if this table is referenced by foreign keys\n    // Fire FK actions (CASCADE, SET NULL, SET DEFAULT) or check for violations (RESTRICT, NO ACTION)\n    if connection.foreign_keys_enabled()\n        && resolver.with_schema(database_id, |s| s.any_resolved_fks_referencing(name))\n    {\n        emit_fk_drop_table_check(program, resolver, name, connection, database_id)?;\n    }\n    let cdc_table = prepare_cdc_if_necessary(program, resolver.schema(), SQLITE_TABLEID)?;\n\n    let null_reg = program.alloc_register(); //  r1\n    program.emit_null(null_reg, None);\n    let table_name_and_root_page_register = program.alloc_register(); //  r2, this register is special because it's first used to track table name and then moved root page\n    let table_reg = program.emit_string8_new_reg(normalize_ident(tbl_name.name.as_str())); //  r3\n    program.mark_last_insn_constant();\n    let _table_type = program.emit_string8_new_reg(\"trigger\".to_string()); //  r4\n    program.mark_last_insn_constant();\n    let row_id_reg = program.alloc_register(); //  r5\n\n    let schema_table = resolver.schema().get_btree_table(SQLITE_TABLEID).unwrap();\n    let sqlite_schema_cursor_id_0 = program.alloc_cursor_id(\n        //  cursor 0\n        CursorType::BTreeTable(schema_table.clone()),\n    );\n    program.emit_insn(Insn::OpenWrite {\n        cursor_id: sqlite_schema_cursor_id_0,\n        root_page: 1i64.into(),\n        db: database_id,\n    });\n\n    //  1. Remove all entries from the schema table related to the table we are dropping (including triggers)\n    //  loop to beginning of schema table\n    let end_metadata_label = program.allocate_label();\n    let metadata_loop = program.allocate_label();\n    program.emit_insn(Insn::Rewind {\n        cursor_id: sqlite_schema_cursor_id_0,\n        pc_if_empty: end_metadata_label,\n    });\n    program.preassign_label_to_next_insn(metadata_loop);\n\n    // start loop on schema table\n    program.emit_column_or_rowid(\n        sqlite_schema_cursor_id_0,\n        2,\n        table_name_and_root_page_register,\n    );\n    let next_label = program.allocate_label();\n    program.emit_insn(Insn::Ne {\n        lhs: table_name_and_root_page_register,\n        rhs: table_reg,\n        target_pc: next_label,\n        flags: CmpInsFlags::default(),\n        collation: program.curr_collation(),\n    });\n    program.emit_insn(Insn::RowId {\n        cursor_id: sqlite_schema_cursor_id_0,\n        dest: row_id_reg,\n    });\n    if let Some((cdc_cursor_id, _)) = cdc_table {\n        let table_type = program.emit_string8_new_reg(\"table\".to_string()); // r4\n        program.mark_last_insn_constant();\n\n        let skip_cdc_label = program.allocate_label();\n\n        let entry_type_reg = program.alloc_register();\n        program.emit_column_or_rowid(sqlite_schema_cursor_id_0, 0, entry_type_reg);\n        program.emit_insn(Insn::Ne {\n            lhs: entry_type_reg,\n            rhs: table_type,\n            target_pc: skip_cdc_label,\n            flags: CmpInsFlags::default(),\n            collation: None,\n        });\n        let before_record_reg = if program.capture_data_changes_info().has_before() {\n            Some(emit_cdc_full_record(\n                program,\n                &schema_table.columns,\n                sqlite_schema_cursor_id_0,\n                row_id_reg,\n                schema_table.is_strict,\n            ))\n        } else {\n            None\n        };\n        emit_cdc_insns(\n            program,\n            resolver,\n            OperationMode::DELETE,\n            cdc_cursor_id,\n            row_id_reg,\n            before_record_reg,\n            None,\n            None,\n            SQLITE_TABLEID,\n        )?;\n        program.resolve_label(skip_cdc_label, program.offset());\n    }\n    program.emit_insn(Insn::Delete {\n        cursor_id: sqlite_schema_cursor_id_0,\n        table_name: SQLITE_TABLEID.to_string(),\n        is_part_of_update: false,\n    });\n\n    program.resolve_label(next_label, program.offset());\n    program.emit_insn(Insn::Next {\n        cursor_id: sqlite_schema_cursor_id_0,\n        pc_if_next: metadata_loop,\n    });\n    program.preassign_label_to_next_insn(end_metadata_label);\n    // end of loop on schema table\n    if let Some((cdc_cursor_id, _)) = cdc_table {\n        emit_cdc_autocommit_commit(program, resolver, cdc_cursor_id)?;\n    }\n\n    //  2. Destroy the indices within a loop\n    let indices = resolver.schema().get_indices(tbl_name.name.as_str());\n    for index in indices {\n        if index.index_method.is_some() && !index.is_backing_btree_index() {\n            // Index methods without backing btree need special destroy handling\n            let cursor_id = program.alloc_cursor_index(None, index)?;\n            program.emit_insn(Insn::IndexMethodDestroy {\n                db: database_id,\n                cursor_id,\n            });\n        } else {\n            program.emit_insn(Insn::Destroy {\n                db: database_id,\n                root: index.root_page,\n                former_root_reg: 0, //  no autovacuum (https://www.sqlite.org/opcode.html#Destroy)\n                is_temp: 0,\n            });\n        }\n\n        //  3. TODO: Open an ephemeral table, and read over triggers from schema table into ephemeral table\n        //  Requires support via https://github.com/tursodatabase/turso/pull/768\n\n        //  4. TODO: Open a write cursor to the schema table and re-insert all triggers into the sqlite schema table from the ephemeral table and delete old trigger\n        //  Requires support via https://github.com/tursodatabase/turso/pull/768\n    }\n\n    //  3. Destroy the table structure\n    match table.as_ref() {\n        Table::BTree(table) => {\n            program.emit_insn(Insn::Destroy {\n                db: database_id,\n                root: table.root_page,\n                former_root_reg: table_name_and_root_page_register,\n                is_temp: 0,\n            });\n        }\n        Table::Virtual(vtab) => {\n            // From what I see, TableValuedFunction is not stored in the schema as a table.\n            // But this line here below is a safeguard in case this behavior changes in the future\n            // And mirrors what SQLite does.\n            if matches!(vtab.kind, turso_ext::VTabKind::TableValuedFunction) {\n                return Err(crate::LimboError::ParseError(format!(\n                    \"table {} may not be dropped\",\n                    vtab.name\n                )));\n            }\n            program.emit_insn(Insn::VDestroy {\n                table_name: vtab.name.clone(),\n                db: database_id,\n            });\n        }\n        Table::FromClauseSubquery(..) => panic!(\"FromClauseSubquery can't be dropped\"),\n    };\n\n    let schema_data_register = program.alloc_register();\n    let schema_row_id_register = program.alloc_register();\n    program.emit_null(schema_data_register, Some(schema_row_id_register));\n\n    // All of the following processing needs to be done only if the table is not a virtual table\n    if table.btree().is_some() {\n        // 4. Open an ephemeral table, and read over the entry from the schema table whose root page was moved in the destroy operation\n\n        // cursor id 1\n        let sqlite_schema_cursor_id_1 =\n            program.alloc_cursor_id(CursorType::BTreeTable(schema_table.clone()));\n        let simple_table_rc = Arc::new(BTreeTable {\n            root_page: 0, // Not relevant for ephemeral table definition\n            name: \"ephemeral_scratch\".to_string(),\n            has_rowid: true,\n            has_autoincrement: false,\n            primary_key_columns: vec![],\n            columns: vec![Column::new(\n                Some(\"rowid\".to_string()),\n                \"INTEGER\".to_string(),\n                None,\n                None,\n                Type::Integer,\n                None,\n                ColDef::default(),\n            )],\n            is_strict: false,\n            unique_sets: vec![],\n            foreign_keys: vec![],\n            check_constraints: vec![],\n            rowid_alias_conflict_clause: None,\n        });\n        // cursor id 2\n        let ephemeral_cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(simple_table_rc));\n        program.emit_insn(Insn::OpenEphemeral {\n            cursor_id: ephemeral_cursor_id,\n            is_table: true,\n        });\n        let if_not_label = program.allocate_label();\n        program.emit_insn(Insn::IfNot {\n            reg: table_name_and_root_page_register,\n            target_pc: if_not_label,\n            jump_if_null: true, //  jump anyway\n        });\n        program.emit_insn(Insn::OpenRead {\n            cursor_id: sqlite_schema_cursor_id_1,\n            root_page: 1i64,\n            db: database_id,\n        });\n\n        let schema_column_0_register = program.alloc_register();\n        let schema_column_1_register = program.alloc_register();\n        let schema_column_2_register = program.alloc_register();\n        let moved_to_root_page_register = program.alloc_register(); //  the register that will contain the root page number the last root page is moved to\n        let schema_column_4_register = program.alloc_register();\n        let prev_root_page_register = program.alloc_register(); //  the register that will contain the root page number that the last root page was on before VACUUM\n        let _r14 = program.alloc_register(); //  Unsure why this register is allocated but putting it in here to make comparison with SQLite easier\n        let new_record_register = program.alloc_register();\n\n        //  Loop to copy over row id's from the schema table for rows that have the same root page as the one that was moved\n        let copy_schema_to_temp_table_loop_end_label = program.allocate_label();\n        let copy_schema_to_temp_table_loop = program.allocate_label();\n        program.emit_insn(Insn::Rewind {\n            cursor_id: sqlite_schema_cursor_id_1,\n            pc_if_empty: copy_schema_to_temp_table_loop_end_label,\n        });\n        program.preassign_label_to_next_insn(copy_schema_to_temp_table_loop);\n        // start loop on schema table\n        program.emit_column_or_rowid(sqlite_schema_cursor_id_1, 3, prev_root_page_register);\n        // The label and Insn::Ne are used to skip over any rows in the schema table that don't have the root page that was moved\n        let next_label = program.allocate_label();\n        program.emit_insn(Insn::Ne {\n            lhs: prev_root_page_register,\n            rhs: table_name_and_root_page_register,\n            target_pc: next_label,\n            flags: CmpInsFlags::default(),\n            collation: program.curr_collation(),\n        });\n        program.emit_insn(Insn::RowId {\n            cursor_id: sqlite_schema_cursor_id_1,\n            dest: schema_row_id_register,\n        });\n        program.emit_insn(Insn::Insert {\n            cursor: ephemeral_cursor_id,\n            key_reg: schema_row_id_register,\n            record_reg: schema_data_register,\n            flag: InsertFlags::new(),\n            table_name: \"scratch_table\".to_string(),\n        });\n\n        program.resolve_label(next_label, program.offset());\n        program.emit_insn(Insn::Next {\n            cursor_id: sqlite_schema_cursor_id_1,\n            pc_if_next: copy_schema_to_temp_table_loop,\n        });\n        program.preassign_label_to_next_insn(copy_schema_to_temp_table_loop_end_label);\n        // End loop to copy over row id's from the schema table for rows that have the same root page as the one that was moved\n\n        program.resolve_label(if_not_label, program.offset());\n\n        // 5. Open a write cursor to the schema table and re-insert the records placed in the ephemeral table but insert the correct root page now\n        program.emit_insn(Insn::OpenWrite {\n            cursor_id: sqlite_schema_cursor_id_1,\n            root_page: 1i64.into(),\n            db: database_id,\n        });\n\n        // Loop to copy over row id's from the ephemeral table and then re-insert into the schema table with the correct root page\n        let copy_temp_table_to_schema_loop_end_label = program.allocate_label();\n        let copy_temp_table_to_schema_loop = program.allocate_label();\n        program.emit_insn(Insn::Rewind {\n            cursor_id: ephemeral_cursor_id,\n            pc_if_empty: copy_temp_table_to_schema_loop_end_label,\n        });\n        program.preassign_label_to_next_insn(copy_temp_table_to_schema_loop);\n        //  start loop on schema table\n        program.emit_insn(Insn::RowId {\n            cursor_id: ephemeral_cursor_id,\n            dest: schema_row_id_register,\n        });\n        //  the next_label and Insn::NotExists are used to skip patching any rows in the schema table that don't have the row id that was written to the ephemeral table\n        let next_label = program.allocate_label();\n        program.emit_insn(Insn::NotExists {\n            cursor: sqlite_schema_cursor_id_1,\n            rowid_reg: schema_row_id_register,\n            target_pc: next_label,\n        });\n        program.emit_column_or_rowid(sqlite_schema_cursor_id_1, 0, schema_column_0_register);\n        program.emit_column_or_rowid(sqlite_schema_cursor_id_1, 1, schema_column_1_register);\n        program.emit_column_or_rowid(sqlite_schema_cursor_id_1, 2, schema_column_2_register);\n        let root_page = table.get_root_page()?;\n        program.emit_insn(Insn::Integer {\n            value: root_page,\n            dest: moved_to_root_page_register,\n        });\n        program.emit_column_or_rowid(sqlite_schema_cursor_id_1, 4, schema_column_4_register);\n        program.emit_insn(Insn::MakeRecord {\n            start_reg: to_u16(schema_column_0_register),\n            count: to_u16(5),\n            dest_reg: to_u16(new_record_register),\n            index_name: None,\n            affinity_str: None,\n        });\n        program.emit_insn(Insn::Delete {\n            cursor_id: sqlite_schema_cursor_id_1,\n            table_name: SQLITE_TABLEID.to_string(),\n            is_part_of_update: false,\n        });\n        program.emit_insn(Insn::Insert {\n            cursor: sqlite_schema_cursor_id_1,\n            key_reg: schema_row_id_register,\n            record_reg: new_record_register,\n            flag: InsertFlags::new(),\n            table_name: SQLITE_TABLEID.to_string(),\n        });\n\n        program.resolve_label(next_label, program.offset());\n        program.emit_insn(Insn::Next {\n            cursor_id: ephemeral_cursor_id,\n            pc_if_next: copy_temp_table_to_schema_loop,\n        });\n        program.preassign_label_to_next_insn(copy_temp_table_to_schema_loop_end_label);\n        // End loop to copy over row id's from the ephemeral table and then re-insert into the schema table with the correct root page\n    }\n\n    // if drops table, sequence table should reset.\n    if let Some(seq_table) = resolver\n        .schema()\n        .get_table(SQLITE_SEQUENCE_TABLE_NAME)\n        .and_then(|t| t.btree())\n    {\n        let seq_cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(seq_table.clone()));\n        let seq_table_name_reg = program.alloc_register();\n        let dropped_table_name_reg =\n            program.emit_string8_new_reg(tbl_name.name.as_str().to_string());\n        program.mark_last_insn_constant();\n\n        program.emit_insn(Insn::OpenWrite {\n            cursor_id: seq_cursor_id,\n            root_page: seq_table.root_page.into(),\n            db: database_id,\n        });\n\n        let end_loop_label = program.allocate_label();\n        let loop_start_label = program.allocate_label();\n\n        program.emit_insn(Insn::Rewind {\n            cursor_id: seq_cursor_id,\n            pc_if_empty: end_loop_label,\n        });\n\n        program.preassign_label_to_next_insn(loop_start_label);\n\n        program.emit_column_or_rowid(seq_cursor_id, 0, seq_table_name_reg);\n\n        let continue_loop_label = program.allocate_label();\n        program.emit_insn(Insn::Ne {\n            lhs: seq_table_name_reg,\n            rhs: dropped_table_name_reg,\n            target_pc: continue_loop_label,\n            flags: CmpInsFlags::default(),\n            collation: None,\n        });\n\n        program.emit_insn(Insn::Delete {\n            cursor_id: seq_cursor_id,\n            table_name: SQLITE_SEQUENCE_TABLE_NAME.to_string(),\n            is_part_of_update: false,\n        });\n\n        program.resolve_label(continue_loop_label, program.offset());\n        program.emit_insn(Insn::Next {\n            cursor_id: seq_cursor_id,\n            pc_if_next: loop_start_label,\n        });\n\n        program.preassign_label_to_next_insn(end_loop_label);\n    }\n\n    // Clean up turso_cdc_version entry for the dropped table (if version table exists)\n    if let Some(version_table) = resolver\n        .schema()\n        .get_table(crate::translate::pragma::TURSO_CDC_VERSION_TABLE_NAME)\n        .and_then(|t| t.btree())\n    {\n        let ver_cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(version_table.clone()));\n        let ver_table_name_reg = program.alloc_register();\n        let dropped_name_reg = program.emit_string8_new_reg(tbl_name.name.as_str().to_string());\n        program.mark_last_insn_constant();\n\n        program.emit_insn(Insn::OpenWrite {\n            cursor_id: ver_cursor_id,\n            root_page: version_table.root_page.into(),\n            db: crate::MAIN_DB_ID,\n        });\n\n        let end_ver_loop_label = program.allocate_label();\n        let ver_loop_start_label = program.allocate_label();\n\n        program.emit_insn(Insn::Rewind {\n            cursor_id: ver_cursor_id,\n            pc_if_empty: end_ver_loop_label,\n        });\n\n        program.preassign_label_to_next_insn(ver_loop_start_label);\n\n        program.emit_column_or_rowid(ver_cursor_id, 0, ver_table_name_reg);\n\n        let continue_ver_label = program.allocate_label();\n        program.emit_insn(Insn::Ne {\n            lhs: ver_table_name_reg,\n            rhs: dropped_name_reg,\n            target_pc: continue_ver_label,\n            flags: CmpInsFlags::default(),\n            collation: None,\n        });\n\n        program.emit_insn(Insn::Delete {\n            cursor_id: ver_cursor_id,\n            table_name: crate::translate::pragma::TURSO_CDC_VERSION_TABLE_NAME.to_string(),\n            is_part_of_update: false,\n        });\n\n        program.resolve_label(continue_ver_label, program.offset());\n        program.emit_insn(Insn::Next {\n            cursor_id: ver_cursor_id,\n            pc_if_next: ver_loop_start_label,\n        });\n\n        program.preassign_label_to_next_insn(end_ver_loop_label);\n    }\n\n    // Drop the in-memory structures for the table\n    program.emit_insn(Insn::DropTable {\n        db: database_id,\n        _p2: 0,\n        _p3: 0,\n        table_name: tbl_name.name.as_str().to_string(),\n    });\n\n    let current_schema_version = resolver.with_schema(database_id, |s| s.schema_version);\n    program.emit_insn(Insn::SetCookie {\n        db: database_id,\n        cookie: Cookie::SchemaVersion,\n        value: current_schema_version as i32 + 1,\n        p5: 0,\n    });\n\n    Ok(())\n}\n\n/// Validate an encode or decode expression for safety.\n/// Rejects subqueries, aggregates, and window functions.\nfn validate_type_expr(expr: &ast::Expr, kind: &str, resolver: &Resolver) -> Result<()> {\n    walk_expr(expr, &mut |e: &ast::Expr| -> Result<WalkControl> {\n        match e {\n            ast::Expr::Subquery(_) | ast::Expr::Exists(_) | ast::Expr::InSelect { .. } => {\n                bail_parse_error!(\"subqueries prohibited in {kind} expressions\");\n            }\n            ast::Expr::FunctionCall {\n                name,\n                args,\n                filter_over,\n                ..\n            } => {\n                if filter_over.over_clause.is_some() {\n                    bail_parse_error!(\"window functions prohibited in {kind} expressions\");\n                }\n                if let Some(func) = resolver.resolve_function(name.as_str(), args.len()) {\n                    if matches!(func, Func::Agg(..)) {\n                        bail_parse_error!(\n                            \"aggregate functions prohibited in {kind} expressions: {}\",\n                            name.as_str()\n                        );\n                    }\n                    // Reject known non-deterministic built-in functions.\n                    // External functions are excluded from this check since\n                    // they default to non-deterministic but may actually be\n                    // deterministic (e.g. uuid_blob).\n                    if !matches!(func, Func::External(_)) && !func.is_deterministic() {\n                        bail_parse_error!(\n                            \"non-deterministic functions prohibited in {kind} expressions: {}\",\n                            name.as_str()\n                        );\n                    }\n                }\n            }\n            ast::Expr::FunctionCallStar { name, .. } => {\n                bail_parse_error!(\n                    \"aggregate functions prohibited in {kind} expressions: {}\",\n                    name.as_str()\n                );\n            }\n            _ => {}\n        }\n        Ok(WalkControl::Continue)\n    })?;\n    Ok(())\n}\n\npub fn translate_create_type(\n    type_name: &str,\n    body: &ast::CreateTypeBody,\n    if_not_exists: bool,\n    resolver: &Resolver,\n    program: &mut ProgramBuilder,\n) -> Result<()> {\n    let normalized_name = normalize_ident(type_name);\n\n    // Reject names that shadow SQLite base types — these are not in the\n    // type_registry but are handled by the column type system. Allowing\n    // them would create confusion and undropable types.\n    let is_base_type = turso_macros::match_ignore_ascii_case!(match normalized_name.as_bytes() {\n        b\"INT\" | b\"INTEGER\" | b\"REAL\" | b\"TEXT\" | b\"BLOB\" | b\"ANY\" => true,\n        _ => false,\n    });\n    if is_base_type {\n        bail_parse_error!(\"cannot create type \\\"{normalized_name}\\\": name is a built-in type\");\n    }\n\n    // Check if type already exists\n    if resolver\n        .schema()\n        .get_type_def_unchecked(&normalized_name)\n        .is_some()\n    {\n        if if_not_exists {\n            return Ok(());\n        }\n        bail_parse_error!(\"type {normalized_name} already exists\");\n    }\n\n    // Validate encode/decode expressions for safety\n    if let Some(ref encode) = body.encode {\n        validate_type_expr(encode, \"ENCODE\", resolver)?;\n    }\n    if let Some(ref decode) = body.decode {\n        validate_type_expr(decode, \"DECODE\", resolver)?;\n    }\n\n    // Reconstruct the SQL string (without IF NOT EXISTS) using TypeDef::to_sql()\n    let type_def = crate::schema::TypeDef::from_create_type(&normalized_name, body, false);\n    let sql = type_def.to_sql();\n\n    // Ensure sqlite_turso_types table exists (lazy creation)\n    let types_table: Arc<BTreeTable>;\n    let types_root_page: RegisterOrLiteral<i64>;\n\n    if let Some(existing) = resolver.schema().get_btree_table(TURSO_TYPES_TABLE_NAME) {\n        types_table = existing.clone();\n        types_root_page = RegisterOrLiteral::Literal(existing.root_page);\n    } else {\n        // Create the sqlite_turso_types btree\n        let table_root_reg = program.alloc_register();\n        program.emit_insn(Insn::CreateBtree {\n            db: 0,\n            root: table_root_reg,\n            flags: CreateBTreeFlags::new_table(),\n        });\n        let create_sql =\n            format!(\"CREATE TABLE {TURSO_TYPES_TABLE_NAME}(name TEXT PRIMARY KEY, sql TEXT)\");\n        types_table = Arc::new(BTreeTable::from_sql(&create_sql, 0)?);\n        types_root_page = RegisterOrLiteral::Register(table_root_reg);\n\n        // Register it in sqlite_schema so it persists\n        let schema_table = resolver.schema().get_btree_table(SQLITE_TABLEID).unwrap();\n        let schema_cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(schema_table));\n        program.emit_insn(Insn::OpenWrite {\n            cursor_id: schema_cursor_id,\n            root_page: 1i64.into(),\n            db: 0,\n        });\n        emit_schema_entry(\n            program,\n            resolver,\n            schema_cursor_id,\n            None,\n            SchemaEntryType::Table,\n            TURSO_TYPES_TABLE_NAME,\n            TURSO_TYPES_TABLE_NAME,\n            table_root_reg,\n            Some(create_sql),\n        )?;\n\n        // Parse schema to register the new table in-memory\n        program.emit_insn(Insn::ParseSchema {\n            db: schema_cursor_id,\n            where_clause: Some(format!(\n                \"tbl_name = '{TURSO_TYPES_TABLE_NAME}' AND type != 'trigger'\"\n            )),\n        });\n    }\n\n    // Open sqlite_turso_types for writing\n    let types_cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(types_table));\n    program.emit_insn(Insn::OpenWrite {\n        cursor_id: types_cursor_id,\n        root_page: types_root_page,\n        db: 0,\n    });\n\n    // Insert (name, sql) record\n    let rowid_reg = program.alloc_register();\n    program.emit_insn(Insn::NewRowid {\n        cursor: types_cursor_id,\n        rowid_reg,\n        prev_largest_reg: 0,\n    });\n    let name_reg = program.emit_string8_new_reg(normalized_name);\n    program.emit_string8_new_reg(sql.clone());\n    let record_reg = program.alloc_register();\n    program.emit_insn(Insn::MakeRecord {\n        start_reg: to_u16(name_reg),\n        count: to_u16(2),\n        dest_reg: to_u16(record_reg),\n        index_name: None,\n        affinity_str: None,\n    });\n    program.emit_insn(Insn::Insert {\n        cursor: types_cursor_id,\n        key_reg: rowid_reg,\n        record_reg,\n        flag: InsertFlags::new(),\n        table_name: TURSO_TYPES_TABLE_NAME.to_string(),\n    });\n\n    // Add the type to the in-memory registry\n    program.emit_insn(Insn::AddType { db: 0, sql });\n\n    program.emit_insn(Insn::SetCookie {\n        db: 0,\n        cookie: Cookie::SchemaVersion,\n        value: (resolver.schema().schema_version + 1) as i32,\n        p5: 0,\n    });\n\n    Ok(())\n}\n\npub fn translate_drop_type(\n    type_name: &str,\n    if_exists: bool,\n    resolver: &Resolver,\n    program: &mut ProgramBuilder,\n) -> Result<()> {\n    let normalized_name = normalize_ident(type_name);\n\n    // Check if type exists\n    let type_def = resolver.schema().get_type_def_unchecked(&normalized_name);\n    if type_def.is_none() {\n        if if_exists {\n            return Ok(());\n        }\n        bail_parse_error!(\"no such type: {normalized_name}\");\n    }\n\n    // Check if built-in type\n    if type_def.unwrap().is_builtin {\n        bail_parse_error!(\"cannot drop built-in type: {normalized_name}\");\n    }\n\n    // Check if any table uses this type\n    for (_, table) in resolver.schema().tables.iter() {\n        for col in table.columns() {\n            if normalize_ident(&col.ty_str) == normalized_name {\n                bail_parse_error!(\n                    \"cannot drop type {normalized_name}: used by column {} in table {}\",\n                    col.name.as_deref().unwrap_or(\"?\"),\n                    table.get_name()\n                );\n            }\n        }\n    }\n\n    // Open cursor to sqlite_turso_types table\n    let types_table = resolver\n        .schema()\n        .get_btree_table(TURSO_TYPES_TABLE_NAME)\n        .ok_or_else(|| crate::LimboError::ParseError(format!(\"no such type: {normalized_name}\")))?;\n    let types_cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(types_table.clone()));\n    program.emit_insn(Insn::OpenWrite {\n        cursor_id: types_cursor_id,\n        root_page: types_table.root_page.into(),\n        db: 0,\n    });\n\n    // Search for matching row: name=type_name (col 0)\n    let name_reg = program.alloc_register();\n    program.emit_insn(Insn::String8 {\n        dest: name_reg,\n        value: normalized_name.clone(),\n    });\n\n    let end_loop_label = program.allocate_label();\n    let loop_start_label = program.allocate_label();\n\n    program.emit_insn(Insn::Rewind {\n        cursor_id: types_cursor_id,\n        pc_if_empty: end_loop_label,\n    });\n    program.preassign_label_to_next_insn(loop_start_label);\n\n    // Read name (col 0)\n    let col0_reg = program.alloc_register();\n    program.emit_column_or_rowid(types_cursor_id, 0, col0_reg);\n\n    let skip_delete_label = program.allocate_label();\n\n    // Check name=type_name\n    program.emit_insn(Insn::Ne {\n        lhs: col0_reg,\n        rhs: name_reg,\n        target_pc: skip_delete_label,\n        flags: CmpInsFlags::default(),\n        collation: program.curr_collation(),\n    });\n\n    // Delete matching row\n    program.emit_insn(Insn::Delete {\n        cursor_id: types_cursor_id,\n        table_name: TURSO_TYPES_TABLE_NAME.to_string(),\n        is_part_of_update: false,\n    });\n\n    program.resolve_label(skip_delete_label, program.offset());\n\n    program.emit_insn(Insn::Next {\n        cursor_id: types_cursor_id,\n        pc_if_next: loop_start_label,\n    });\n\n    program.preassign_label_to_next_insn(end_loop_label);\n\n    // Remove from in-memory schema\n    program.emit_insn(Insn::DropType {\n        db: 0,\n        type_name: normalized_name,\n    });\n\n    program.emit_insn(Insn::SetCookie {\n        db: 0,\n        cookie: Cookie::SchemaVersion,\n        value: (resolver.schema().schema_version + 1) as i32,\n        p5: 0,\n    });\n\n    Ok(())\n}\n"
  },
  {
    "path": "core/translate/select.rs",
    "content": "use super::emitter::{emit_program, TranslateCtx};\nuse super::plan::{\n    select_star, Distinctness, InSeekSource, JoinOrderMember, Operation, OuterQueryReference,\n    QueryDestination, Search, TableReferences, WhereTerm, Window,\n};\nuse crate::schema::Table;\nuse crate::sync::Arc;\nuse crate::translate::emitter::{OperationMode, Resolver};\nuse crate::translate::expr::{bind_and_rewrite_expr, expr_vector_size, BindingBehavior};\nuse crate::translate::group_by::compute_group_by_sort_order;\nuse crate::translate::optimizer::optimize_plan;\nuse crate::translate::plan::{GroupBy, Plan, ResultSetColumn, SelectPlan, SubqueryState};\nuse crate::translate::planner::{\n    break_predicate_at_and_boundaries, parse_from, parse_limit, parse_where,\n    plan_ctes_as_outer_refs, resolve_window_and_aggregate_functions,\n};\nuse crate::translate::result_row::emit_select_result;\nuse crate::translate::subquery::{plan_subqueries_from_select_plan, plan_subqueries_from_values};\nuse crate::translate::window::plan_windows;\nuse crate::util::{exprs_are_equivalent, normalize_ident};\nuse crate::vdbe::builder::ProgramBuilderOpts;\nuse crate::vdbe::insn::Insn;\nuse crate::{vdbe::builder::ProgramBuilder, Result};\nuse std::borrow::Cow;\nuse turso_parser::ast::ResultColumn;\nuse turso_parser::ast::{self, CompoundSelect, Expr};\n\n/// Maximum number of columns in a result set.\n/// SQLite's default SQLITE_MAX_COLUMN is 2000, with a hard upper limit of 32767.\nconst SQLITE_MAX_COLUMN: usize = 2000;\n\npub fn translate_select(\n    select: ast::Select,\n    resolver: &Resolver,\n    program: &mut ProgramBuilder,\n    query_destination: QueryDestination,\n    connection: &Arc<crate::Connection>,\n) -> Result<usize> {\n    let mut select_plan = prepare_select_plan(\n        select,\n        resolver,\n        program,\n        &[],\n        query_destination,\n        connection,\n    )?;\n    if program.trigger.is_some() {\n        if let Some(virtual_table) = plan_first_virtual_table_name(&select_plan) {\n            crate::bail_parse_error!(\"unsafe use of virtual table \\\"{}\\\"\", virtual_table);\n        }\n    }\n    optimize_plan(program, &mut select_plan, resolver)?;\n    let num_result_cols;\n    let opts = match &select_plan {\n        Plan::Select(select) => {\n            num_result_cols = select.result_columns.len();\n            ProgramBuilderOpts {\n                num_cursors: count_required_cursors_for_simple_select(select),\n                approx_num_insns: estimate_num_instructions_for_simple_select(select),\n                approx_num_labels: estimate_num_labels_for_simple_select(select),\n            }\n        }\n        Plan::CompoundSelect {\n            left, right_most, ..\n        } => {\n            // Compound Selects must return the same number of columns\n            num_result_cols = right_most.result_columns.len();\n\n            ProgramBuilderOpts {\n                num_cursors: count_required_cursors_for_simple_select(right_most)\n                    + left\n                        .iter()\n                        .map(|(plan, _)| count_required_cursors_for_simple_select(plan))\n                        .sum::<usize>(),\n                approx_num_insns: estimate_num_instructions_for_simple_select(right_most)\n                    + left\n                        .iter()\n                        .map(|(plan, _)| estimate_num_instructions_for_simple_select(plan))\n                        .sum::<usize>(),\n                approx_num_labels: estimate_num_labels_for_simple_select(right_most)\n                    + left\n                        .iter()\n                        .map(|(plan, _)| estimate_num_labels_for_simple_select(plan))\n                        .sum::<usize>(),\n            }\n        }\n        other => panic!(\"plan is not a SelectPlan: {other:?}\"),\n    };\n\n    program.extend(&opts);\n    emit_program(connection, resolver, program, select_plan, |_| {})?;\n    Ok(num_result_cols)\n}\n\nfn plan_first_virtual_table_name(plan: &Plan) -> Option<String> {\n    match plan {\n        Plan::Select(select_plan) => select_plan_first_virtual_table_name(select_plan),\n        Plan::CompoundSelect {\n            left, right_most, ..\n        } => select_plan_first_virtual_table_name(right_most).or_else(|| {\n            left.iter()\n                .find_map(|(plan, _)| select_plan_first_virtual_table_name(plan))\n        }),\n        Plan::Delete(_) | Plan::Update(_) => None,\n    }\n}\n\nfn select_plan_first_virtual_table_name(select_plan: &SelectPlan) -> Option<String> {\n    for joined_table in select_plan.joined_tables() {\n        match &joined_table.table {\n            Table::Virtual(virtual_table) if !virtual_table.innocuous => {\n                return Some(virtual_table.name.clone())\n            }\n            Table::FromClauseSubquery(from_clause_subquery) => {\n                if let Some(name) = plan_first_virtual_table_name(&from_clause_subquery.plan) {\n                    return Some(name);\n                }\n            }\n            _ => {}\n        }\n    }\n    for subquery in &select_plan.non_from_clause_subqueries {\n        if let SubqueryState::Unevaluated { plan: Some(plan) } = &subquery.state {\n            if let Some(name) = select_plan_first_virtual_table_name(plan) {\n                return Some(name);\n            }\n        }\n    }\n    None\n}\n\npub fn prepare_select_plan(\n    select: ast::Select,\n    resolver: &Resolver,\n    program: &mut ProgramBuilder,\n    outer_query_refs: &[OuterQueryReference],\n    query_destination: QueryDestination,\n    connection: &Arc<crate::Connection>,\n) -> Result<Plan> {\n    let compounds = select.body.compounds;\n    match compounds.is_empty() {\n        true => Ok(Plan::Select(prepare_one_select_plan(\n            select.body.select,\n            resolver,\n            program,\n            select.limit,\n            select.order_by,\n            select.with,\n            outer_query_refs,\n            query_destination,\n            connection,\n        )?)),\n        false => {\n            // For compound SELECTs, the WITH clause applies to all parts.\n            // We clone the WITH clause for each SELECT in the compound so that\n            // each one can resolve CTE references independently.\n            let with = select.with;\n\n            let mut last = prepare_one_select_plan(\n                select.body.select,\n                resolver,\n                program,\n                None,\n                vec![],\n                with.clone(),\n                outer_query_refs,\n                query_destination.clone(),\n                connection,\n            )?;\n\n            let mut left = Vec::with_capacity(compounds.len());\n            for CompoundSelect {\n                select: compound_select,\n                operator,\n            } in compounds\n            {\n                left.push((last, operator));\n                last = prepare_one_select_plan(\n                    compound_select,\n                    resolver,\n                    program,\n                    None,\n                    vec![],\n                    with.clone(),\n                    outer_query_refs,\n                    query_destination.clone(),\n                    connection,\n                )?;\n            }\n\n            // Ensure all subplans have the same number of result columns\n            let right_most_num_result_columns = last.result_columns.len();\n            for (plan, operator) in left.iter() {\n                if plan.result_columns.len() != right_most_num_result_columns {\n                    crate::bail_parse_error!(\n                        \"SELECTs to the left and right of {} do not have the same number of result columns\",\n                        operator\n                    );\n                }\n            }\n            let (limit, offset) = select\n                .limit\n                .map_or(Ok((None, None)), |l| parse_limit(l, resolver))?;\n\n            // FIXME: handle ORDER BY for compound selects\n            if !select.order_by.is_empty() {\n                crate::bail_parse_error!(\"ORDER BY is not supported for compound SELECTs yet\");\n            }\n            Ok(Plan::CompoundSelect {\n                left,\n                right_most: last,\n                limit,\n                offset,\n                order_by: None,\n            })\n        }\n    }\n}\n\n#[allow(clippy::too_many_arguments)]\nfn prepare_one_select_plan(\n    select: ast::OneSelect,\n    resolver: &Resolver,\n    program: &mut ProgramBuilder,\n    limit: Option<ast::Limit>,\n    order_by: Vec<ast::SortedColumn>,\n    with: Option<ast::With>,\n    outer_query_refs: &[OuterQueryReference],\n    query_destination: QueryDestination,\n    connection: &Arc<crate::Connection>,\n) -> Result<SelectPlan> {\n    if order_by\n        .iter()\n        .filter_map(|o| o.nulls)\n        .any(|n| n == ast::NullsOrder::Last)\n    {\n        crate::bail_parse_error!(\"NULLS LAST is not supported yet in ORDER BY\");\n    }\n    match select {\n        ast::OneSelect::Select {\n            columns,\n            from,\n            where_clause,\n            group_by,\n            distinctness,\n            window_clause,\n        } => {\n            let col_count = columns.len();\n            if col_count == 0 {\n                crate::bail_parse_error!(\"SELECT without columns is not allowed\");\n            }\n\n            let mut where_predicates = vec![];\n            let mut vtab_predicates = vec![];\n\n            let mut table_references = TableReferences::new(vec![], outer_query_refs.to_vec());\n\n            if from.is_none() {\n                for column in &columns {\n                    if matches!(column, ResultColumn::Star) {\n                        crate::bail_parse_error!(\"no tables specified\");\n                    }\n                }\n            }\n\n            // Parse the FROM clause into a vec of TableReferences. Fold all the join conditions expressions into the WHERE clause.\n            let preplan_ctes_for_non_from_subqueries = with.is_some()\n                && select_has_non_from_subqueries(\n                    &columns,\n                    where_clause.as_deref(),\n                    group_by.as_ref(),\n                    &window_clause,\n                    &order_by,\n                    limit.as_ref(),\n                );\n            parse_from(\n                from,\n                resolver,\n                program,\n                with,\n                preplan_ctes_for_non_from_subqueries,\n                &mut where_predicates,\n                &mut vtab_predicates,\n                &mut table_references,\n                connection,\n            )?;\n\n            // Preallocate space for the result columns\n            let result_columns = Vec::with_capacity(\n                columns\n                    .iter()\n                    .map(|c| match c {\n                        // Allocate space for all columns in all tables\n                        ResultColumn::Star => table_references\n                            .joined_tables()\n                            .iter()\n                            .map(|t| t.columns().iter().filter(|col| !col.hidden()).count())\n                            .sum(),\n                        // Guess 5 columns if we can't find the table using the identifier (maybe it's in [brackets] or `tick_quotes`, or miXeDcAse)\n                        ResultColumn::TableStar(n) => table_references\n                            .joined_tables()\n                            .iter()\n                            .find(|t| t.identifier == n.as_str())\n                            .map(|t| t.columns().iter().filter(|col| !col.hidden()).count())\n                            .unwrap_or(5),\n                        // Otherwise allocate space for 1 column\n                        ResultColumn::Expr(_, _) => 1,\n                    })\n                    .sum(),\n            );\n            let mut plan = SelectPlan {\n                join_order: table_references\n                    .joined_tables()\n                    .iter()\n                    .enumerate()\n                    .map(|(i, t)| JoinOrderMember {\n                        table_id: t.internal_id,\n                        original_idx: i,\n                        is_outer: t.join_info.as_ref().is_some_and(|j| j.is_outer()),\n                    })\n                    .collect(),\n                table_references,\n                result_columns,\n                where_clause: where_predicates,\n                group_by: None,\n                order_by: vec![],\n                aggregates: vec![],\n                limit: None,\n                offset: None,\n                contains_constant_false_condition: false,\n                query_destination,\n                distinctness: Distinctness::from_ast(distinctness.as_ref()),\n                values: vec![],\n                window: None,\n                non_from_clause_subqueries: vec![],\n                input_cardinality_hint: None,\n                estimated_output_rows: None,\n                simple_aggregate: None,\n            };\n\n            let mut windows = Vec::with_capacity(window_clause.len());\n            for window_def in window_clause.iter() {\n                let name = normalize_ident(window_def.name.as_str());\n                let mut window = Window::new(Some(name), &window_def.window)?;\n\n                for expr in window.partition_by.iter_mut() {\n                    bind_and_rewrite_expr(\n                        expr,\n                        Some(&mut plan.table_references),\n                        None,\n                        resolver,\n                        BindingBehavior::ResultColumnsNotAllowed,\n                    )?;\n                }\n                for (expr, _) in window.order_by.iter_mut() {\n                    bind_and_rewrite_expr(\n                        expr,\n                        Some(&mut plan.table_references),\n                        None,\n                        resolver,\n                        BindingBehavior::ResultColumnsNotAllowed,\n                    )?;\n                }\n\n                windows.push(window);\n            }\n\n            let mut aggregate_expressions = Vec::new();\n            for column in columns.into_iter() {\n                match column {\n                    ResultColumn::Star => {\n                        select_star(\n                            plan.table_references.joined_tables(),\n                            &mut plan.result_columns,\n                            plan.table_references.right_join_swapped(),\n                        );\n                        for table in plan.table_references.joined_tables_mut() {\n                            for idx in 0..table.columns().len() {\n                                let column = &table.columns()[idx];\n                                if column.hidden() {\n                                    continue;\n                                }\n                                table.mark_column_used(idx);\n                            }\n                        }\n                    }\n                    ResultColumn::TableStar(name) => {\n                        let name_normalized = normalize_ident(name.as_str());\n                        let referenced_table = plan\n                            .table_references\n                            .joined_tables_mut()\n                            .iter_mut()\n                            .find(|t| t.identifier == name_normalized);\n\n                        if referenced_table.is_none() {\n                            crate::bail_parse_error!(\"no such table: {}\", name.as_str());\n                        }\n                        let table = referenced_table.unwrap();\n                        let num_columns = table.columns().len();\n                        for idx in 0..num_columns {\n                            let column = &table.columns()[idx];\n                            if column.hidden() {\n                                continue;\n                            }\n                            plan.result_columns.push(ResultSetColumn {\n                                expr: ast::Expr::Column {\n                                    database: None, // TODO: support different databases\n                                    table: table.internal_id,\n                                    column: idx,\n                                    is_rowid_alias: column.is_rowid_alias(),\n                                },\n                                alias: None,\n                                contains_aggregates: false,\n                            });\n                            table.mark_column_used(idx);\n                        }\n                    }\n                    ResultColumn::Expr(mut expr, maybe_alias) => {\n                        bind_and_rewrite_expr(\n                            &mut expr,\n                            Some(&mut plan.table_references),\n                            None,\n                            resolver,\n                            BindingBehavior::ResultColumnsNotAllowed,\n                        )?;\n                        let contains_aggregates = resolve_window_and_aggregate_functions(\n                            &expr,\n                            resolver,\n                            &mut aggregate_expressions,\n                            Some(&mut windows),\n                        )?;\n                        plan.result_columns.push(ResultSetColumn {\n                            alias: maybe_alias.as_ref().map(|alias| match alias {\n                                ast::As::Elided(alias) => alias.as_str().to_string(),\n                                ast::As::As(alias) => alias.as_str().to_string(),\n                            }),\n                            expr: *expr,\n                            contains_aggregates,\n                        });\n                    }\n                }\n            }\n\n            if plan.result_columns.len() > SQLITE_MAX_COLUMN {\n                crate::bail_parse_error!(\"too many columns in result set\");\n            }\n\n            // This step can only be performed at this point, because all table references are now available.\n            // Virtual table predicates may depend on column bindings from tables to the right in the join order,\n            // so we must wait until the full set of references has been collected.\n            add_vtab_predicates_to_where_clause(&mut vtab_predicates, &mut plan, resolver)?;\n\n            // Parse the actual WHERE clause and add its conditions to the plan WHERE clause that already contains the join conditions.\n            parse_where(\n                where_clause.as_deref(),\n                &mut plan.table_references,\n                Some(&plan.result_columns),\n                &mut plan.where_clause,\n                resolver,\n            )?;\n\n            if let Some(mut group_by) = group_by {\n                // Process HAVING clause if present\n                let having_predicates = if let Some(having) = group_by.having {\n                    Some(process_having_clause(\n                        having,\n                        &mut plan.table_references,\n                        &plan.result_columns,\n                        resolver,\n                        &mut aggregate_expressions,\n                    )?)\n                } else {\n                    None\n                };\n\n                if !group_by.exprs.is_empty() {\n                    // Normal GROUP BY with expressions\n                    for expr in group_by.exprs.iter_mut() {\n                        replace_column_number_with_copy_of_column_expr(expr, &plan.result_columns)?;\n                        bind_and_rewrite_expr(\n                            expr,\n                            Some(&mut plan.table_references),\n                            Some(&plan.result_columns),\n                            resolver,\n                            BindingBehavior::TryCanonicalColumnsFirst,\n                        )?;\n                    }\n\n                    plan.group_by = Some(GroupBy {\n                        sort_order: Vec::new(),\n                        sort_elided: false,\n                        exprs: group_by.exprs.iter().map(|expr| *expr.clone()).collect(),\n                        having: having_predicates,\n                    });\n                } else {\n                    // HAVING without GROUP BY: treat as ungrouped aggregation with filter\n                    plan.group_by = Some(GroupBy {\n                        sort_order: Vec::new(),\n                        sort_elided: false,\n                        exprs: vec![],\n                        having: having_predicates,\n                    });\n                }\n            }\n\n            plan.aggregates = aggregate_expressions;\n\n            // HAVING without GROUP BY requires aggregates in the SELECT\n            if let Some(ref group_by) = plan.group_by {\n                if group_by.exprs.is_empty()\n                    && group_by.having.is_some()\n                    && plan.aggregates.is_empty()\n                {\n                    crate::bail_parse_error!(\"HAVING clause on a non-aggregate query\");\n                }\n            }\n\n            // Parse the ORDER BY clause\n            let mut key = Vec::new();\n\n            for mut o in order_by {\n                replace_column_number_with_copy_of_column_expr(&mut o.expr, &plan.result_columns)?;\n\n                bind_and_rewrite_expr(\n                    &mut o.expr,\n                    Some(&mut plan.table_references),\n                    Some(&plan.result_columns),\n                    resolver,\n                    BindingBehavior::TryResultColumnsFirst,\n                )?;\n                resolve_window_and_aggregate_functions(\n                    &o.expr,\n                    resolver,\n                    &mut plan.aggregates,\n                    Some(&mut windows),\n                )?;\n\n                key.push((o.expr, o.order.unwrap_or(ast::SortOrder::Asc)));\n            }\n            // Remove duplicate ORDER BY expressions, keeping the first occurrence.\n            // Duplicates are semantically redundant.\n            let mut i = 0;\n            while i < key.len() {\n                if key[..i]\n                    .iter()\n                    .any(|(prev, _)| exprs_are_equivalent(prev, &key[i].0))\n                {\n                    key.remove(i);\n                } else {\n                    i += 1;\n                }\n            }\n            plan.order_by = key;\n\n            // Single-row aggregate queries (aggregates without GROUP BY and without window functions)\n            // produce exactly one row, so ORDER BY is meaningless. Clearing it here also avoids\n            // eagerly validating subqueries in ORDER BY that SQLite would skip due to optimization.\n            // Note: HAVING without GROUP BY sets group_by to Some with empty exprs, still single-row.\n            let is_single_row_aggregate = !plan.aggregates.is_empty()\n                && plan.group_by.as_ref().is_none_or(|gb| gb.exprs.is_empty())\n                && windows.is_empty();\n            if is_single_row_aggregate {\n                plan.order_by.clear();\n            }\n\n            // SQLite optimizes away ORDER BY clauses after a rowid/INTEGER PRIMARY KEY column\n            // when it's FIRST in the ORDER BY, since the table is stored in rowid order.\n            // This means we truncate the ORDER BY to just the rowid column.\n            // We do this for SQLite compatibility - SQLite truncates before validating, so\n            // even invalid constructions like ORDER BY rowid, a IN (SELECT a, b FROM t) pass.\n            if plan.order_by.len() > 1 && plan.table_references.joined_tables().len() == 1 {\n                let joined = &plan.table_references.joined_tables()[0];\n                let table_id = joined.internal_id;\n                let rowid_alias_col = joined\n                    .btree()\n                    .and_then(|t| t.get_rowid_alias_column().map(|(idx, _)| idx));\n\n                let first_is_rowid = match plan.order_by[0].0.as_ref() {\n                    ast::Expr::Column { table, column, .. } => {\n                        *table == table_id && rowid_alias_col == Some(*column)\n                    }\n                    ast::Expr::RowId { table, .. } => *table == table_id,\n                    _ => false,\n                };\n                if first_is_rowid {\n                    plan.order_by.truncate(1);\n                }\n            }\n\n            if let Some(group_by) = &mut plan.group_by {\n                // now that we have resolved the ORDER BY expressions and aggregates, we can\n                // compute the necessary sort order for the GROUP BY clause\n                group_by.sort_order = compute_group_by_sort_order(\n                    &group_by.exprs,\n                    &plan.order_by,\n                    &plan.aggregates,\n                    resolver,\n                );\n                debug_assert_eq!(\n                    group_by.exprs.len(),\n                    group_by.sort_order.len(),\n                    \"GROUP BY exprs and sort_order must have the same length\"\n                );\n            }\n\n            // Parse the LIMIT/OFFSET clause\n            (plan.limit, plan.offset) =\n                limit.map_or(Ok((None, None)), |l| parse_limit(l, resolver))?;\n\n            if !windows.is_empty() {\n                plan_windows(\n                    &mut plan,\n                    resolver,\n                    &mut program.table_reference_counter,\n                    &mut windows,\n                )?;\n            }\n\n            plan_subqueries_from_select_plan(program, &mut plan, resolver, connection)?;\n\n            validate_expr_correct_column_counts(&plan)?;\n\n            // Return the unoptimized query plan\n            Ok(plan)\n        }\n        ast::OneSelect::Values(mut values) => {\n            if !order_by.is_empty() {\n                crate::bail_parse_error!(\"ORDER BY clause is not allowed with VALUES clause\");\n            }\n            if limit.is_some() {\n                crate::bail_parse_error!(\"LIMIT clause is not allowed with VALUES clause\");\n            }\n            let len = values[0].len();\n            if len > SQLITE_MAX_COLUMN {\n                crate::bail_parse_error!(\"too many columns in result set\");\n            }\n            let mut result_columns = Vec::with_capacity(len);\n            for i in 0..len {\n                result_columns.push(ResultSetColumn {\n                    // these result_columns work as placeholders for the values, so the expr doesn't matter\n                    expr: ast::Expr::Literal(ast::Literal::Numeric(i.to_string())),\n                    alias: Some(format!(\"column{}\", i + 1)),\n                    contains_aggregates: false,\n                });\n            }\n\n            let mut table_references = TableReferences::new(vec![], outer_query_refs.to_vec());\n\n            // Plan CTEs from WITH clause so they're available for subqueries in VALUES\n            plan_ctes_as_outer_refs(with, resolver, program, &mut table_references, connection)?;\n\n            for value_row in values.iter_mut() {\n                for value in value_row.iter_mut() {\n                    // Before binding, we check for unquoted literals. Sqlite throws an error in this case\n                    bind_and_rewrite_expr(\n                        value,\n                        Some(&mut table_references),\n                        None,\n                        resolver,\n                        // Allow sqlite quirk of inserting \"double-quoted\" literals (which our AST maps as identifiers)\n                        BindingBehavior::TryResultColumnsFirst,\n                    )?;\n                }\n            }\n\n            // Plan subqueries in VALUES expressions\n            let mut non_from_clause_subqueries = vec![];\n            plan_subqueries_from_values(\n                program,\n                &mut non_from_clause_subqueries,\n                &mut table_references,\n                &mut values,\n                resolver,\n                connection,\n            )?;\n\n            let plan = SelectPlan {\n                join_order: vec![],\n                table_references,\n                result_columns,\n                where_clause: vec![],\n                group_by: None,\n                order_by: vec![],\n                aggregates: vec![],\n                limit: None,\n                offset: None,\n                contains_constant_false_condition: false,\n                query_destination,\n                distinctness: Distinctness::NonDistinct,\n                values: values\n                    .iter()\n                    .map(|values| values.iter().map(|value| *value.clone()).collect())\n                    .collect(),\n                window: None,\n                non_from_clause_subqueries,\n                input_cardinality_hint: None,\n                estimated_output_rows: None,\n                simple_aggregate: None,\n            };\n\n            validate_expr_correct_column_counts(&plan)?;\n\n            Ok(plan)\n        }\n    }\n}\n\n/// Validate that all expressions in the plan return the correct number of values;\n/// generally this only applies to parenthesized lists and subqueries.\nfn validate_expr_correct_column_counts(plan: &SelectPlan) -> Result<()> {\n    for result_column in plan.result_columns.iter() {\n        let vec_size = expr_vector_size(&result_column.expr)?;\n        if vec_size != 1 {\n            crate::bail_parse_error!(\"result column must return 1 value, got {}\", vec_size);\n        }\n    }\n    for (expr, _) in plan.order_by.iter() {\n        let vec_size = expr_vector_size(expr)?;\n        if vec_size != 1 {\n            crate::bail_parse_error!(\"order by expression must return 1 value, got {}\", vec_size);\n        }\n    }\n    if let Some(group_by) = &plan.group_by {\n        for expr in group_by.exprs.iter() {\n            let vec_size = expr_vector_size(expr)?;\n            if vec_size != 1 {\n                crate::bail_parse_error!(\n                    \"group by expression must return 1 value, got {}\",\n                    vec_size\n                );\n            }\n        }\n        if let Some(having) = &group_by.having {\n            for expr in having.iter() {\n                let vec_size = expr_vector_size(expr)?;\n                if vec_size != 1 {\n                    crate::bail_parse_error!(\n                        \"having expression must return 1 value, got {}\",\n                        vec_size\n                    );\n                }\n            }\n        }\n    }\n    for aggregate in plan.aggregates.iter() {\n        for arg in aggregate.args.iter() {\n            let vec_size = expr_vector_size(arg)?;\n            if vec_size != 1 {\n                crate::bail_parse_error!(\n                    \"aggregate argument must return 1 value, got {}\",\n                    vec_size\n                );\n            }\n        }\n    }\n    for term in plan.where_clause.iter() {\n        let vec_size = expr_vector_size(&term.expr)?;\n        if vec_size != 1 {\n            crate::bail_parse_error!(\n                \"where clause expression must return 1 value, got {}\",\n                vec_size\n            );\n        }\n    }\n    for expr in plan.values.iter() {\n        for value in expr.iter() {\n            let vec_size = expr_vector_size(value)?;\n            if vec_size != 1 {\n                crate::bail_parse_error!(\"value must return 1 value, got {}\", vec_size);\n            }\n        }\n    }\n    if let Some(limit) = &plan.limit {\n        let vec_size = expr_vector_size(limit)?;\n        if vec_size != 1 {\n            crate::bail_parse_error!(\"limit expression must return 1 value, got {}\", vec_size);\n        }\n    }\n    if let Some(offset) = &plan.offset {\n        let vec_size = expr_vector_size(offset)?;\n        if vec_size != 1 {\n            crate::bail_parse_error!(\"offset expression must return 1 value, got {}\", vec_size);\n        }\n    }\n    Ok(())\n}\n\nfn add_vtab_predicates_to_where_clause(\n    vtab_predicates: &mut Vec<Expr>,\n    plan: &mut SelectPlan,\n    resolver: &Resolver,\n) -> Result<()> {\n    for expr in vtab_predicates.iter_mut() {\n        bind_and_rewrite_expr(\n            expr,\n            Some(&mut plan.table_references),\n            Some(&plan.result_columns),\n            resolver,\n            BindingBehavior::TryCanonicalColumnsFirst,\n        )?;\n    }\n    for expr in vtab_predicates.drain(..) {\n        // Virtual table argument predicates (e.g. the 't2' in pragma_table_info('t2'))\n        // must be associated with the virtual table's outer join context if the table is\n        // the RHS of a LEFT JOIN. Otherwise the optimizer may incorrectly simplify the\n        // LEFT JOIN into an INNER JOIN, breaking NULL row emission for unmatched rows.\n        let from_outer_join = vtab_predicate_table_id(&expr).and_then(|table_id| {\n            plan.table_references\n                .find_joined_table_by_internal_id(table_id)\n                .and_then(|t| {\n                    t.join_info\n                        .as_ref()\n                        .and_then(|ji| ji.is_outer().then_some(table_id))\n                })\n        });\n        plan.where_clause.push(WhereTerm {\n            expr,\n            from_outer_join,\n            consumed: false,\n        });\n    }\n    Ok(())\n}\n\n/// Extract the table internal_id from a virtual table argument predicate.\n/// These are always of the form `Column { table, .. } = literal` or `IsNull(Column { table, .. })`.\nfn vtab_predicate_table_id(expr: &Expr) -> Option<ast::TableInternalId> {\n    match expr {\n        Expr::Binary(lhs, _, _) | Expr::IsNull(lhs) => match lhs.as_ref() {\n            Expr::Column { table, .. } => Some(*table),\n            _ => None,\n        },\n        _ => None,\n    }\n}\n\n/// Replaces a column number in an ORDER BY or GROUP BY expression with a copy of the column expression.\n/// For example, in SELECT u.first_name, count(1) FROM users u GROUP BY 1 ORDER BY 2,\n/// the column number 1 is replaced with u.first_name and the column number 2 is replaced with count(1).\n///\n/// Per SQLite documentation, only constant integers are treated as column references.\n/// Non-integer numeric literals (floats) are treated as constant expressions.\nfn replace_column_number_with_copy_of_column_expr(\n    order_by_or_group_by_expr: &mut ast::Expr,\n    columns: &[ResultSetColumn],\n) -> Result<()> {\n    if let ast::Expr::Literal(ast::Literal::Numeric(num)) = order_by_or_group_by_expr {\n        // Only treat as column reference if it parses as a positive integer.\n        // Float literals like \"0.5\" or \"1.0\" are valid constant expressions, not column references.\n        if let Ok(column_number) = num.parse::<usize>() {\n            if column_number == 0 {\n                crate::bail_parse_error!(\"invalid column index: {}\", column_number);\n            }\n            let maybe_result_column = columns.get(column_number - 1);\n            match maybe_result_column {\n                Some(ResultSetColumn { expr, .. }) => {\n                    *order_by_or_group_by_expr = expr.clone();\n                }\n                None => {\n                    crate::bail_parse_error!(\"invalid column index: {}\", column_number)\n                }\n            };\n        }\n        // Otherwise, leave the expression as-is (constant expression, case 3 per SQLite docs)\n    }\n    Ok(())\n}\n\n/// Count required cursors for a Plan (either Select or CompoundSelect)\nfn count_required_cursors_for_simple_or_compound_select(plan: &Plan) -> usize {\n    match plan {\n        Plan::Select(select_plan) => count_required_cursors_for_simple_select(select_plan),\n        Plan::CompoundSelect {\n            left, right_most, ..\n        } => {\n            count_required_cursors_for_simple_select(right_most)\n                + left\n                    .iter()\n                    .map(|(p, _)| count_required_cursors_for_simple_select(p))\n                    .sum::<usize>()\n        }\n        Plan::Delete(_) | Plan::Update(_) => 0,\n    }\n}\n\nfn count_required_cursors_for_simple_select(plan: &SelectPlan) -> usize {\n    let num_table_cursors: usize = plan\n        .joined_tables()\n        .iter()\n        .map(|t| match &t.op {\n            Operation::Scan { .. } => 1,\n            Operation::Search(search) => match search {\n                Search::RowidEq { .. } => 1,\n                Search::Seek { index, .. } => 1 + index.is_some() as usize,\n                Search::InSeek { index, source } => match source {\n                    // table cursor + new ephemeral cursor + optional index cursor\n                    InSeekSource::LiteralList { .. } => 2 + index.is_some() as usize,\n                    // table cursor + optional index cursor (ephemeral already counted)\n                    InSeekSource::Subquery { .. } => 1 + index.is_some() as usize,\n                },\n            }\n            Operation::IndexMethodQuery(_) => 1,\n            Operation::HashJoin(_) => 2,\n            // One table cursor + one cursor per index branch\n            Operation::MultiIndexScan(multi_idx) => 1 + multi_idx.branches.len(),\n        } + if let Table::FromClauseSubquery(from_clause_subquery) = &t.table {\n            count_required_cursors_for_simple_or_compound_select(&from_clause_subquery.plan)\n        } else {\n            0\n        })\n        .sum();\n    let has_group_by_with_exprs = plan\n        .group_by\n        .as_ref()\n        .is_some_and(|gb| !gb.exprs.is_empty());\n    let num_sorter_cursors = has_group_by_with_exprs as usize + !plan.order_by.is_empty() as usize;\n    let num_pseudo_cursors = has_group_by_with_exprs as usize + !plan.order_by.is_empty() as usize;\n\n    num_table_cursors + num_sorter_cursors + num_pseudo_cursors\n}\n\n/// Estimate number of instructions for a Plan (either Select or CompoundSelect)\nfn estimate_num_instructions_for_simple_or_compound_select(plan: &Plan) -> usize {\n    match plan {\n        Plan::Select(select_plan) => estimate_num_instructions_for_simple_select(select_plan),\n        Plan::CompoundSelect {\n            left, right_most, ..\n        } => {\n            estimate_num_instructions_for_simple_select(right_most)\n                + left\n                    .iter()\n                    .map(|(p, _)| estimate_num_instructions_for_simple_select(p))\n                    .sum::<usize>()\n                + 20 // overhead for compound select operations\n        }\n        Plan::Delete(_) | Plan::Update(_) => 0,\n    }\n}\n\nfn estimate_num_instructions_for_simple_select(select: &SelectPlan) -> usize {\n    let table_instructions: usize = select\n        .joined_tables()\n        .iter()\n        .map(|t| match &t.op {\n            Operation::Scan { .. } => 10,\n            Operation::Search(_) => 15,\n            Operation::IndexMethodQuery(_) => 15,\n            Operation::HashJoin(_) => 20,\n            // Multi-index scan: scan overhead per branch + deduplication + final rowid fetch\n            Operation::MultiIndexScan(multi_idx) => 15 * multi_idx.branches.len() + 10,\n        } + if let Table::FromClauseSubquery(from_clause_subquery) = &t.table {\n            10 + estimate_num_instructions_for_simple_or_compound_select(&from_clause_subquery.plan)\n        } else {\n            0\n        })\n        .sum();\n\n    let group_by_instructions = select.group_by.is_some() as usize * 10;\n    let order_by_instructions = !select.order_by.is_empty() as usize * 10;\n    let condition_instructions = select.where_clause.len() * 3;\n\n    20 + table_instructions + group_by_instructions + order_by_instructions + condition_instructions\n}\n\nfn push_function_tail_exprs<'a>(stack: &mut Vec<&'a Expr>, tail: &'a ast::FunctionTail) {\n    if let Some(filter_expr) = tail.filter_clause.as_deref() {\n        stack.push(filter_expr);\n    }\n\n    let Some(ast::Over::Window(window)) = tail.over_clause.as_ref() else {\n        return;\n    };\n\n    if let Some(frame_clause) = window.frame_clause.as_ref() {\n        if let ast::FrameBound::Following(expr) | ast::FrameBound::Preceding(expr) =\n            &frame_clause.start\n        {\n            stack.push(expr.as_ref());\n        }\n        if let Some(ast::FrameBound::Following(expr) | ast::FrameBound::Preceding(expr)) =\n            frame_clause.end.as_ref()\n        {\n            stack.push(expr.as_ref());\n        }\n    }\n\n    for sorted in window.order_by.iter().rev() {\n        stack.push(sorted.expr.as_ref());\n    }\n    for part_expr in window.partition_by.iter().rev() {\n        stack.push(part_expr.as_ref());\n    }\n}\n\nfn expr_contains_subquery(expr: &Expr) -> bool {\n    // Iterative traversal avoids stack overflows on deeply nested expression trees\n    // such as very large left-associative AND chains.\n    let mut stack = vec![expr];\n    while let Some(node) = stack.pop() {\n        match node {\n            Expr::Subquery(_) | Expr::InSelect { .. } | Expr::Exists(_) => return true,\n            Expr::Between {\n                lhs, start, end, ..\n            } => {\n                stack.push(lhs.as_ref());\n                stack.push(start.as_ref());\n                stack.push(end.as_ref());\n            }\n            Expr::Binary(lhs, _, rhs) => {\n                stack.push(rhs.as_ref());\n                stack.push(lhs.as_ref());\n            }\n            Expr::Case {\n                base,\n                when_then_pairs,\n                else_expr,\n            } => {\n                if let Some(expr) = else_expr.as_deref() {\n                    stack.push(expr);\n                }\n                for (when_expr, then_expr) in when_then_pairs.iter().rev() {\n                    stack.push(then_expr.as_ref());\n                    stack.push(when_expr.as_ref());\n                }\n                if let Some(base_expr) = base.as_deref() {\n                    stack.push(base_expr);\n                }\n            }\n            Expr::Cast { expr, .. }\n            | Expr::Collate(expr, _)\n            | Expr::IsNull(expr)\n            | Expr::NotNull(expr)\n            | Expr::Unary(_, expr) => {\n                stack.push(expr.as_ref());\n            }\n            Expr::FunctionCall {\n                args,\n                order_by,\n                filter_over,\n                ..\n            } => {\n                push_function_tail_exprs(&mut stack, filter_over);\n                for sorted in order_by.iter().rev() {\n                    stack.push(sorted.expr.as_ref());\n                }\n                for arg in args.iter().rev() {\n                    stack.push(arg.as_ref());\n                }\n            }\n            Expr::FunctionCallStar { filter_over, .. } => {\n                push_function_tail_exprs(&mut stack, filter_over);\n            }\n            Expr::InList { lhs, rhs, .. } => {\n                for item in rhs.iter().rev() {\n                    stack.push(item.as_ref());\n                }\n                stack.push(lhs.as_ref());\n            }\n            Expr::InTable { lhs, args, .. } => {\n                for arg in args.iter().rev() {\n                    stack.push(arg.as_ref());\n                }\n                stack.push(lhs.as_ref());\n            }\n            Expr::Like {\n                lhs, rhs, escape, ..\n            } => {\n                if let Some(escape_expr) = escape.as_deref() {\n                    stack.push(escape_expr);\n                }\n                stack.push(rhs.as_ref());\n                stack.push(lhs.as_ref());\n            }\n            Expr::Parenthesized(exprs) => {\n                for expr in exprs.iter().rev() {\n                    stack.push(expr.as_ref());\n                }\n            }\n            Expr::Raise(_, raise_expr) => {\n                if let Some(expr) = raise_expr.as_deref() {\n                    stack.push(expr);\n                }\n            }\n            Expr::SubqueryResult { lhs, .. } => {\n                if let Some(expr) = lhs.as_deref() {\n                    stack.push(expr);\n                }\n            }\n            Expr::Array { .. } | Expr::Subscript { .. } => {\n                unreachable!(\"Array and Subscript are desugared into function calls by the parser\")\n            }\n            Expr::Column { .. }\n            | Expr::DoublyQualified(_, _, _)\n            | Expr::Id(_)\n            | Expr::Literal(_)\n            | Expr::Name(_)\n            | Expr::Qualified(_, _)\n            | Expr::Register(_)\n            | Expr::RowId { .. }\n            | Expr::Variable(_) => {}\n        }\n    }\n    false\n}\n\nfn select_has_non_from_subqueries(\n    columns: &[ResultColumn],\n    where_clause: Option<&Expr>,\n    group_by: Option<&ast::GroupBy>,\n    window_clause: &[ast::WindowDef],\n    order_by: &[ast::SortedColumn],\n    limit: Option<&ast::Limit>,\n) -> bool {\n    if columns.iter().any(|column| match column {\n        ResultColumn::Expr(expr, _) => expr_contains_subquery(expr),\n        ResultColumn::Star | ResultColumn::TableStar(_) => false,\n    }) {\n        return true;\n    }\n\n    if where_clause.is_some_and(expr_contains_subquery) {\n        return true;\n    }\n\n    if let Some(group_by) = group_by {\n        if group_by.exprs.iter().any(|e| expr_contains_subquery(e))\n            || group_by\n                .having\n                .as_deref()\n                .is_some_and(expr_contains_subquery)\n        {\n            return true;\n        }\n    }\n\n    if window_clause.iter().any(|w| {\n        w.window\n            .partition_by\n            .iter()\n            .any(|e| expr_contains_subquery(e))\n            || w.window\n                .order_by\n                .iter()\n                .any(|s| expr_contains_subquery(&s.expr))\n    }) {\n        return true;\n    }\n\n    if order_by.iter().any(|s| expr_contains_subquery(&s.expr)) {\n        return true;\n    }\n\n    if let Some(limit) = limit {\n        if expr_contains_subquery(&limit.expr)\n            || limit.offset.as_deref().is_some_and(expr_contains_subquery)\n        {\n            return true;\n        }\n    }\n\n    false\n}\n\n/// Estimate number of labels for a Plan (either Select or CompoundSelect)\nfn estimate_num_labels_for_simple_or_compound_select(plan: &Plan) -> usize {\n    match plan {\n        Plan::Select(select_plan) => estimate_num_labels_for_simple_select(select_plan),\n        Plan::CompoundSelect {\n            left, right_most, ..\n        } => {\n            estimate_num_labels_for_simple_select(right_most)\n                + left\n                    .iter()\n                    .map(|(p, _)| estimate_num_labels_for_simple_select(p))\n                    .sum::<usize>()\n                + 10 // overhead for compound select operations\n        }\n        Plan::Delete(_) | Plan::Update(_) => 0,\n    }\n}\n\nfn estimate_num_labels_for_simple_select(select: &SelectPlan) -> usize {\n    let init_halt_labels = 2;\n    // 3 loop labels for each table in main loop + 1 to signify end of main loop\n    let table_labels = select\n        .joined_tables()\n        .iter()\n        .map(|t| match &t.op {\n            Operation::Scan { .. } => 3,\n            Operation::Search(_) => 3,\n            Operation::IndexMethodQuery(_) => 3,\n            Operation::HashJoin(_) => 3,\n            // Multi-index scan needs extra labels for each branch + rowset loop\n            Operation::MultiIndexScan(multi_idx) => 3 + multi_idx.branches.len() * 2,\n        } + if let Table::FromClauseSubquery(from_clause_subquery) = &t.table {\n            3 + estimate_num_labels_for_simple_or_compound_select(&from_clause_subquery.plan)\n        } else {\n            0\n        })\n        .sum::<usize>()\n        + 1;\n\n    let group_by_labels = select.group_by.is_some() as usize * 10;\n    let order_by_labels = !select.order_by.is_empty() as usize * 10;\n    let condition_labels = select.where_clause.len() * 2;\n\n    init_halt_labels + table_labels + group_by_labels + order_by_labels + condition_labels\n}\n\npub fn emit_simple_count(\n    program: &mut ProgramBuilder,\n    t_ctx: &mut TranslateCtx,\n    plan: &SelectPlan,\n) -> Result<bool> {\n    let cursors = plan\n        .joined_tables()\n        .first()\n        .unwrap()\n        .resolve_cursors(program, OperationMode::SELECT)?;\n\n    let cursor_id = {\n        match cursors {\n            (_, Some(cursor_id)) | (Some(cursor_id), None) => cursor_id,\n            _ => return Ok(false),\n        }\n    };\n\n    // Count opcode only works on BTree cursors. Materialized view trigger\n    // queries may have pseudo cursors — fall back to normal aggregation.\n    if !program.cursor_is_btree(cursor_id) {\n        return Ok(false);\n    }\n\n    let target_reg = program.alloc_register();\n\n    program.emit_insn(Insn::Count {\n        cursor_id,\n        target_reg,\n        exact: true,\n    });\n\n    program.emit_insn(Insn::Close { cursor_id });\n\n    let agg = plan\n        .aggregates\n        .first()\n        .expect(\"simple count requires exactly one aggregate\");\n    t_ctx.resolver.cache_expr_reg(\n        Cow::Owned(agg.original_expr.clone()),\n        target_reg,\n        false,\n        None,\n    );\n    t_ctx.resolver.enable_expr_to_reg_cache();\n\n    emit_select_result(\n        program,\n        &t_ctx.resolver,\n        plan,\n        None,\n        None,\n        None,\n        None,\n        t_ctx.reg_result_cols_start.unwrap(),\n        t_ctx.limit_ctx,\n    )?;\n    Ok(true)\n}\n\nfn process_having_clause(\n    having: Box<ast::Expr>,\n    table_references: &mut TableReferences,\n    result_columns: &[ResultSetColumn],\n    resolver: &Resolver,\n    aggregate_expressions: &mut Vec<super::plan::Aggregate>,\n) -> Result<Vec<ast::Expr>> {\n    let mut predicates = vec![];\n    break_predicate_at_and_boundaries(&having, &mut predicates);\n\n    // Before alias resolution replaces identifiers with their underlying expressions,\n    // check for aliased aggregate misuse. SQLite does this during name resolution by\n    // checking the NC_AllowAgg flag on the NameContext (see resolve.c). When an identifier\n    // inside an aggregate function's arguments resolves to an alias whose original expression\n    // has EP_Agg, SQLite reports \"misuse of aliased aggregate X\".\n    for expr in predicates.iter() {\n        check_aliased_aggregate_misuse(expr, result_columns)?;\n    }\n\n    for expr in predicates.iter_mut() {\n        bind_and_rewrite_expr(\n            expr,\n            Some(table_references),\n            Some(result_columns),\n            resolver,\n            BindingBehavior::TryResultColumnsFirst,\n        )?;\n        resolve_window_and_aggregate_functions(expr, resolver, aggregate_expressions, None)?;\n    }\n\n    Ok(predicates)\n}\n\n/// Walk a HAVING expression looking for aggregate function calls whose arguments\n/// reference aliases of aggregate result columns (SQLite ticket #2526).\nfn check_aliased_aggregate_misuse(\n    expr: &ast::Expr,\n    result_columns: &[ResultSetColumn],\n) -> Result<()> {\n    use crate::translate::expr::{walk_expr, WalkControl};\n\n    walk_expr(expr, &mut |e| {\n        match e {\n            Expr::FunctionCall { name, args, .. } => {\n                let is_agg = matches!(\n                    crate::function::Func::resolve_function(name.as_str(), args.len()),\n                    Ok(crate::function::Func::Agg(_))\n                );\n                if is_agg {\n                    for arg in args.iter() {\n                        find_aliased_aggregate_ref(arg, result_columns)?;\n                    }\n                    return Ok(WalkControl::SkipChildren);\n                }\n            }\n            Expr::FunctionCallStar { name, .. } => {\n                if matches!(\n                    crate::function::Func::resolve_function(name.as_str(), 0),\n                    Ok(crate::function::Func::Agg(_))\n                ) {\n                    return Ok(WalkControl::SkipChildren);\n                }\n            }\n            _ => {}\n        }\n        Ok(WalkControl::Continue)\n    })?;\n    Ok(())\n}\n\n/// Check if an expression (inside an aggregate's arguments) contains an identifier\n/// that matches an alias of an aggregate result column.\nfn find_aliased_aggregate_ref(expr: &ast::Expr, result_columns: &[ResultSetColumn]) -> Result<()> {\n    use crate::translate::expr::{walk_expr, WalkControl};\n\n    walk_expr(expr, &mut |e| {\n        if let Expr::Id(id) = e {\n            let normalized = normalize_ident(id.as_str());\n            for rc in result_columns.iter() {\n                if let Some(alias) = &rc.alias {\n                    if alias.eq_ignore_ascii_case(&normalized) && rc.contains_aggregates {\n                        crate::bail_parse_error!(\"misuse of aliased aggregate {}\", normalized);\n                    }\n                }\n            }\n        }\n        Ok(WalkControl::Continue)\n    })?;\n    Ok(())\n}\n"
  },
  {
    "path": "core/translate/stmt_journal.rs",
    "content": "//! Statement journal flag analysis (`is_multi_write` / `may_abort`).\n//!\n//! Inside an explicit transaction (BEGIN...COMMIT), each statement runs within\n//! the larger transaction. If a statement partially completes and then aborts\n//! (e.g. a UNIQUE constraint violation on the third row of a multi-row INSERT),\n//! the partial writes must be rolled back without discarding the entire\n//! transaction. SQLite solves this with a \"statement journal\" (subjournal): a\n//! savepoint taken at the start of each statement, rolled back on abort.\n//!\n//! Statement journals are expensive, so SQLite skips them when provably\n//! unnecessary. The condition is: `usesStmtJournal = isMultiWrite && mayAbort`.\n//!\n//! - **isMultiWrite**: the statement may modify more than one row (or more than\n//!   one table, e.g. FK counter + data table). A single-row write is atomic —\n//!   either all writes happen or none do — so no partial state to roll back.\n//!\n//! - **mayAbort**: the statement may fail mid-execution with an ABORT (e.g.\n//!   constraint violation, FK violation, RAISE(ABORT) in a trigger). If a\n//!   multi-write statement can never abort, partial rollback is moot.\n//!\n//! Both flags default to `true` (conservative). Each DML translate path calls\n//! into this module to set them to `false` when safe.\n\nuse crate::translate::emitter::Resolver;\nuse crate::translate::plan::{DeletePlan, DmlSafetyReason, UpdatePlan};\nuse crate::translate::trigger_exec::has_relevant_triggers_type_only;\nuse crate::vdbe::builder::ProgramBuilder;\nuse crate::{sync::Arc, Connection, HashSet, Result};\nuse turso_parser::ast::{ResolveType, TriggerEvent};\n\n/// Check whether any DDL-level constraint (IPK or index) uses REPLACE.\npub(crate) fn any_index_or_ipk_has_replace(\n    rowid_alias_conflict: Option<ResolveType>,\n    mut indexes: impl Iterator<Item = Option<ResolveType>>,\n) -> bool {\n    rowid_alias_conflict == Some(ResolveType::Replace)\n        || indexes.any(|oc| oc == Some(ResolveType::Replace))\n}\n\n/// Check whether any constraint's effective resolution is REPLACE.\n///\n/// When a statement-level override exists, only the statement conflict mode matters.\n/// Otherwise, both the PK's DDL mode and each index's DDL mode are checked.\npub(crate) fn any_effective_replace(\n    has_statement_conflict: bool,\n    statement_conflict: ResolveType,\n    rowid_alias_conflict: Option<ResolveType>,\n    indexes: impl Iterator<Item = Option<ResolveType>>,\n) -> bool {\n    if has_statement_conflict {\n        matches!(statement_conflict, ResolveType::Replace)\n    } else {\n        any_index_or_ipk_has_replace(rowid_alias_conflict, indexes)\n    }\n}\n\n/// Check whether a table has any FK relationships (child or parent side).\nfn table_has_fks(\n    connection: &crate::Connection,\n    resolver: &Resolver,\n    database_id: usize,\n    table_name: &str,\n) -> bool {\n    connection.foreign_keys_enabled()\n        && (resolver.with_schema(database_id, |s| s.has_child_fks(table_name))\n            || resolver.with_schema(database_id, |s| s.any_resolved_fks_referencing(table_name)))\n}\n\n/// Determine whether any constraint's effective resolution can trigger an\n/// ABORT. Each constraint has an effective resolution mode — either the\n/// statement-level override (when present) or its DDL-level mode (defaulting\n/// to ABORT). A constraint can cause an ABORT when:\n///\n/// - Its effective mode is ABORT and it has any checkable constraint\n///   (NOT NULL, CHECK, UNIQUE).\n/// - Its effective mode is REPLACE and the table has NOT NULL or CHECK\n///   constraints, because REPLACE falls back to ABORT for those.\n///\n/// IGNORE and FAIL never trigger statement-level ABORT.\n/// Each index is represented as `(on_conflict, is_unique)`.\npub(crate) fn constraint_may_abort(\n    has_statement_conflict: bool,\n    statement_conflict: ResolveType,\n    rowid_alias_conflict: Option<ResolveType>,\n    mut indexes: impl Iterator<Item = (Option<ResolveType>, bool)>,\n    has_notnull: bool,\n    has_check: bool,\n    has_unique: bool,\n) -> bool {\n    if has_statement_conflict {\n        // Statement-level override applies uniformly to all constraints.\n        return match statement_conflict {\n            ResolveType::Abort => has_notnull || has_check || has_unique,\n            ResolveType::Replace => has_notnull || has_check, // UNIQUE conflict gets replaced, not aborted.\n            _ => false, // IGNORE, FAIL, ROLLBACK don't need statement journal\n        };\n    }\n    // No statement-level override — each constraint uses its DDL-level mode.\n    let pk_mode = rowid_alias_conflict.unwrap_or(ResolveType::Abort);\n    let pk_aborts = match pk_mode {\n        ResolveType::Abort => has_unique, // PK is a unique constraint\n        ResolveType::Replace => false,    // PK REPLACE doesn't fall back for unique\n        _ => false,\n    };\n    let idx_aborts = indexes.any(|(on_conflict, unique)| {\n        let mode = on_conflict.unwrap_or(ResolveType::Abort);\n        match mode {\n            ResolveType::Abort => unique, // only unique indexes can conflict\n            ResolveType::Replace => has_notnull || has_check,\n            _ => false,\n        }\n    });\n    // Default ABORT applies to NOT NULL and CHECK (they aren't per-index).\n    let default_aborts = has_notnull || has_check;\n    pk_aborts || idx_aborts || default_aborts\n}\n\n/// Set multi_write / may_abort for INSERT statements.\n///\n/// Constraint analysis (any_replace, constraint_may_abort) is computed internally\n/// from the table schema and resolver. The caller provides INSERT-specific flags\n/// that come from the emitter's own analysis.\n#[allow(clippy::too_many_arguments)]\npub(crate) fn set_insert_stmt_journal_flags(\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    database_id: usize,\n    table: &crate::schema::BTreeTable,\n    has_statement_conflict: bool,\n    statement_conflict: ResolveType,\n    inserting_multiple_rows: bool,\n    has_triggers: bool,\n    has_fks: bool,\n    has_upsert: bool,\n    has_autoincrement: bool,\n    notnull_col_exists: bool,\n    has_unique: bool,\n) {\n    let index_modes: Vec<(Option<ResolveType>, bool)> = resolver.with_schema(database_id, |s| {\n        s.get_indices(&table.name)\n            .map(|idx| (idx.on_conflict, idx.unique))\n            .collect()\n    });\n    let any_replace = any_effective_replace(\n        has_statement_conflict,\n        statement_conflict,\n        table.rowid_alias_conflict_clause,\n        index_modes.iter().map(|(oc, _)| *oc),\n    );\n    let has_check = !table.check_constraints.is_empty();\n    let may_abort = has_triggers\n        || has_fks\n        || constraint_may_abort(\n            has_statement_conflict,\n            statement_conflict,\n            table.rowid_alias_conflict_clause,\n            index_modes.into_iter(),\n            notnull_col_exists,\n            has_check,\n            has_unique,\n        );\n\n    // UPSERT is multi-write because DO UPDATE modifies an existing row.\n    // AUTOINCREMENT is multi-write because sqlite_sequence is updated before constraint checks.\n    if !inserting_multiple_rows\n        && !has_triggers\n        && !any_replace\n        && !has_upsert\n        && !has_autoincrement\n    {\n        program.set_multi_write(false);\n    }\n    program.set_may_abort(may_abort);\n}\n\n/// Set multi_write / may_abort for UPDATE statements.\npub(crate) fn set_update_stmt_journal_flags(\n    program: &mut ProgramBuilder,\n    plan: &UpdatePlan,\n    resolver: &Resolver,\n    connection: &crate::sync::Arc<crate::Connection>,\n) -> Result<()> {\n    // When an ephemeral table is used (key mutation / Halloween protection),\n    // the actual target table is in the ephemeral_plan's table_references.\n    let table_refs = plan\n        .ephemeral_plan\n        .as_ref()\n        .map(|ep| &ep.table_references)\n        .unwrap_or(&plan.table_references);\n    let Some(target_table) = table_refs.joined_tables().first() else {\n        crate::bail_parse_error!(\"UPDATE should have one target table\");\n    };\n    let Some(btree_table) = target_table.btree() else {\n        return Ok(()); // Virtual table — keep conservative defaults.\n    };\n    let database_id = target_table.database_id;\n\n    let updated_cols: HashSet<usize> = plan.set_clauses.iter().map(|(i, _)| *i).collect();\n    let has_triggers = resolver.with_schema(database_id, |s| {\n        has_relevant_triggers_type_only(s, TriggerEvent::Update, Some(&updated_cols), &btree_table)\n    });\n    let has_fks = table_has_fks(connection, resolver, database_id, btree_table.name.as_str());\n\n    let or_conflict = plan.or_conflict.unwrap_or(ResolveType::Abort);\n    let has_statement_conflict = plan.or_conflict.is_some();\n\n    let any_replace = any_effective_replace(\n        has_statement_conflict,\n        or_conflict,\n        btree_table.rowid_alias_conflict_clause,\n        plan.indexes_to_update.iter().map(|idx| idx.on_conflict),\n    );\n\n    // Ephemeral tables (used for key mutation / Halloween protection) always scan all\n    // collected rows, so affects_max_1_row() returns false — multi_write stays true.\n    let is_single_row =\n        plan.limit.is_none() && plan.offset.is_none() && target_table.op.affects_max_1_row();\n    if is_single_row && !has_triggers && !any_replace && !has_fks {\n        program.set_multi_write(false);\n    }\n\n    let has_notnull_cols = plan.set_clauses.iter().any(|(col_idx, _)| {\n        if *col_idx == crate::schema::ROWID_SENTINEL {\n            return false;\n        }\n        btree_table\n            .columns\n            .get(*col_idx)\n            .is_some_and(|c| c.notnull() && !c.is_rowid_alias())\n    });\n    let has_check = !btree_table.check_constraints.is_empty();\n    let has_unique =\n        !btree_table.unique_sets.is_empty() || plan.indexes_to_update.iter().any(|idx| idx.unique);\n\n    let may_abort = has_triggers\n        || has_fks\n        || constraint_may_abort(\n            has_statement_conflict,\n            or_conflict,\n            btree_table.rowid_alias_conflict_clause,\n            plan.indexes_to_update\n                .iter()\n                .map(|idx| (idx.on_conflict, idx.unique)),\n            has_notnull_cols,\n            has_check,\n            has_unique,\n        );\n    program.set_may_abort(may_abort);\n    Ok(())\n}\n\n/// Set multi_write / may_abort for DELETE statements.\npub(crate) fn set_delete_stmt_journal_flags(\n    program: &mut ProgramBuilder,\n    plan: &DeletePlan,\n    resolver: &Resolver,\n    connection: &Arc<Connection>,\n    database_id: usize,\n) -> Result<()> {\n    let Some(target_table) = plan.table_references.joined_tables().first() else {\n        crate::bail_parse_error!(\"DELETE should have one target table\");\n    };\n    let Some(btree_table) = target_table.btree() else {\n        return Ok(()); // Virtual table — keep conservative defaults.\n    };\n    let has_triggers = plan.safety.reasons.contains(&DmlSafetyReason::Trigger);\n    let has_fks = table_has_fks(connection, resolver, database_id, btree_table.name.as_str());\n\n    // After rowset rewriting (for triggers/safety), the target table op is reset to a\n    // Scan, so affects_max_1_row correctly returns false — no false optimization.\n    let is_single_row =\n        plan.limit.is_none() && plan.offset.is_none() && target_table.op.affects_max_1_row();\n    if is_single_row && !has_triggers && !has_fks {\n        program.set_multi_write(false);\n    }\n\n    // DELETE has no ON CONFLICT clause, so NOT NULL/CHECK/UNIQUE don't apply —\n    // only triggers (RAISE(ABORT)) or FK violations can abort.\n    if !has_triggers && !has_fks {\n        program.set_may_abort(false);\n    }\n    Ok(())\n}\n"
  },
  {
    "path": "core/translate/subquery.rs",
    "content": "use std::sync::Arc;\n\nuse rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};\nuse turso_parser::ast::{self, SortOrder, SubqueryType};\n\nuse crate::{\n    emit_explain,\n    schema::{BTreeTable, Column, Index, IndexColumn, Table},\n    translate::{\n        collate::get_collseq_from_expr,\n        compound_select::emit_program_for_compound_select,\n        emitter::select::{\n            emit_program_for_select, emit_program_for_select_with_resolver, emit_query,\n        },\n        expr::{\n            compare_affinity, get_expr_affinity_info, unwrap_parens, walk_expr_mut, WalkControl,\n        },\n        optimizer::optimize_select_plan,\n        plan::{\n            plan_has_outer_scope_dependency, plan_is_correlated, ColumnUsedMask, EvalAt,\n            JoinOrderMember, NonFromClauseSubquery, OuterQueryReference, Plan, SetOperation,\n            SubqueryEvalPhase, SubqueryOrigin, SubqueryPosition, SubqueryState, TableReferences,\n            WhereTerm,\n        },\n        select::prepare_select_plan,\n    },\n    types::Value,\n    util::parse_signed_number,\n    vdbe::{\n        builder::{CursorKey, CursorType, MaterializedCteInfo, ProgramBuilder},\n        insn::Insn,\n        CursorID,\n    },\n    Connection, Numeric, Result,\n};\n\nuse super::{\n    emitter::{Resolver, TranslateCtx},\n    main_loop::LoopLabels,\n    plan::{Aggregate, Operation, QueryDestination, Scan, Search, SelectPlan},\n    planner::resolve_window_and_aggregate_functions,\n};\n\nstruct DirectMaterializedSubquery {\n    index: Arc<Index>,\n    affinity_str: Option<Arc<String>>,\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub(crate) enum MaterializedFromClauseSubqueryStorage {\n    TableBacked,\n    DirectIndex,\n}\n\nenum FromClauseSubqueryExecutionMode {\n    Coroutine,\n    MaterializedTable,\n    DirectMaterializedIndex(DirectMaterializedSubquery),\n}\n\npub(crate) fn materialized_from_clause_subquery_storage(\n    subquery: &crate::schema::FromClauseSubquery,\n) -> Option<MaterializedFromClauseSubqueryStorage> {\n    match subquery.plan.select_query_destination() {\n        Some(QueryDestination::EphemeralTable { .. }) => {\n            Some(MaterializedFromClauseSubqueryStorage::TableBacked)\n        }\n        Some(QueryDestination::EphemeralIndex { .. }) => {\n            Some(MaterializedFromClauseSubqueryStorage::DirectIndex)\n        }\n        _ => None,\n    }\n}\n\n/// Mark CTE references that must be materialized once and shared across\n/// multiple reads of the same query tree.\n///\n/// Correlated plans are explicitly excluded: they must re-run for each outer\n/// row, so sharing a single materialized result would be semantically wrong.\nfn mark_shared_cte_materialization_requirements(program: &ProgramBuilder, plan: &mut SelectPlan) {\n    fn annotate_plan(program: &ProgramBuilder, plan: &mut Plan) {\n        match plan {\n            Plan::Select(select_plan) => {\n                mark_shared_cte_materialization_requirements(program, select_plan)\n            }\n            Plan::CompoundSelect {\n                left, right_most, ..\n            } => {\n                for (select_plan, _) in left.iter_mut() {\n                    mark_shared_cte_materialization_requirements(program, select_plan);\n                }\n                mark_shared_cte_materialization_requirements(program, right_most);\n            }\n            Plan::Delete(_) | Plan::Update(_) => unreachable!(\"DML plans cannot be subqueries\"),\n        }\n    }\n\n    for table in plan.table_references.joined_tables_mut().iter_mut() {\n        if let Table::FromClauseSubquery(from_clause_subquery) = &mut table.table {\n            let from_clause_subquery = Arc::make_mut(from_clause_subquery);\n            let shared_materialization = from_clause_subquery.cte_id().is_some_and(|cte_id| {\n                program.get_cte_reference_count(cte_id) > 1\n                    && !plan_has_outer_scope_dependency(&from_clause_subquery.plan)\n            });\n            from_clause_subquery.set_shared_materialization(shared_materialization);\n            if let Some(cte_id) = from_clause_subquery.cte_id() {\n                tracing::trace!(\n                    cte_id,\n                    reference_count = program.get_cte_reference_count(cte_id),\n                    shared_materialization,\n                    outer_scope_dependency = plan_has_outer_scope_dependency(\n                        &from_clause_subquery.plan,\n                    ),\n                    contains_nested_correlation = plan_is_correlated(&from_clause_subquery.plan),\n                    identifier = %table.identifier,\n                    \"annotated CTE materialization requirements\"\n                );\n            }\n            annotate_plan(program, from_clause_subquery.plan.as_mut());\n        }\n    }\n\n    for subquery in plan.non_from_clause_subqueries.iter_mut() {\n        let SubqueryState::Unevaluated {\n            plan: Some(subquery_plan),\n        } = &mut subquery.state\n        else {\n            continue;\n        };\n        mark_shared_cte_materialization_requirements(program, subquery_plan);\n    }\n}\n\n// Compute query plans for subqueries occurring in any position other than the FROM clause.\n// This includes the WHERE clause, HAVING clause, GROUP BY clause, ORDER BY clause, LIMIT clause, and OFFSET clause.\n/// The AST expression containing the subquery ([ast::Expr::Exists], [ast::Expr::Subquery], [ast::Expr::InSelect]) is replaced with a [ast::Expr::SubqueryResult] expression.\n/// The [ast::Expr::SubqueryResult] expression contains the subquery ID, the left-hand side expression (only applicable to IN subqueries), the NOT IN flag (only applicable to IN subqueries), and the subquery type.\n/// The computed plans are stored in the [NonFromClauseSubquery] structs on the [SelectPlan], and evaluated at the appropriate time during the translation of the main query.\n/// The appropriate time is determined by whether the subquery is correlated or uncorrelated;\n/// if it is uncorrelated, it can be evaluated as early as possible, but if it is correlated, it must be evaluated after all of its dependencies from the\n/// outer query are 'in scope', i.e. their cursors are open and rewound.\npub fn plan_subqueries_from_select_plan(\n    program: &mut ProgramBuilder,\n    plan: &mut SelectPlan,\n    resolver: &Resolver,\n    connection: &Arc<Connection>,\n) -> Result<()> {\n    // WHERE\n    plan_subqueries_with_outer_query_access(\n        program,\n        &mut plan.non_from_clause_subqueries,\n        &mut plan.table_references,\n        resolver,\n        plan.where_clause.iter_mut().map(|t| &mut t.expr),\n        connection,\n        SubqueryPosition::Where,\n        SubqueryOrigin::SelectWhere,\n        SubqueryPosition::Where.allow_correlated(),\n    )?;\n\n    // GROUP BY\n    if let Some(group_by) = &mut plan.group_by {\n        plan_subqueries_with_outer_query_access(\n            program,\n            &mut plan.non_from_clause_subqueries,\n            &mut plan.table_references,\n            resolver,\n            group_by.exprs.iter_mut(),\n            connection,\n            SubqueryPosition::GroupBy,\n            SubqueryOrigin::SelectGroupBy,\n            SubqueryPosition::GroupBy.allow_correlated(),\n        )?;\n        if let Some(having) = group_by.having.as_mut() {\n            plan_subqueries_with_outer_query_access(\n                program,\n                &mut plan.non_from_clause_subqueries,\n                &mut plan.table_references,\n                resolver,\n                having.iter_mut(),\n                connection,\n                SubqueryPosition::Having,\n                SubqueryOrigin::SelectHaving,\n                !group_by.exprs.is_empty(),\n            )?;\n        }\n    }\n\n    // Result columns\n    plan_subqueries_with_outer_query_access(\n        program,\n        &mut plan.non_from_clause_subqueries,\n        &mut plan.table_references,\n        resolver,\n        plan.result_columns.iter_mut().map(|c| &mut c.expr),\n        connection,\n        SubqueryPosition::ResultColumn,\n        SubqueryOrigin::SelectList,\n        SubqueryPosition::ResultColumn.allow_correlated(),\n    )?;\n\n    // ORDER BY\n    plan_subqueries_with_outer_query_access(\n        program,\n        &mut plan.non_from_clause_subqueries,\n        &mut plan.table_references,\n        resolver,\n        plan.order_by.iter_mut().map(|(expr, _)| &mut **expr),\n        connection,\n        SubqueryPosition::OrderBy,\n        SubqueryOrigin::SelectOrderBy,\n        SubqueryPosition::OrderBy.allow_correlated(),\n    )?;\n\n    // LIMIT and OFFSET cannot reference columns from the outer query\n    let get_outer_query_refs = |_: &TableReferences| vec![];\n    {\n        let mut subquery_parser = get_subquery_parser(\n            program,\n            &mut plan.non_from_clause_subqueries,\n            &mut plan.table_references,\n            resolver,\n            connection,\n            get_outer_query_refs,\n            SubqueryPosition::LimitOffset,\n            SubqueryOrigin::SelectLimitOffset,\n            false,\n        );\n        // Limit\n        if let Some(limit) = &mut plan.limit {\n            walk_expr_mut(limit, &mut subquery_parser)?;\n        }\n        // Offset\n        if let Some(offset) = &mut plan.offset {\n            walk_expr_mut(offset, &mut subquery_parser)?;\n        }\n    }\n\n    // Recollect aggregates after all subquery planning.\n    // This is necessary because:\n    // 1. Aggregates are collected with cloned expressions before subquery planning modifies them\n    //    (e.g., EXISTS -> SubqueryResult), causing stale args in aggregates.\n    // 2. ORDER BY may be cleared for single-row aggregates AFTER aggregates were collected from it,\n    //    leaving orphaned aggregates with unprocessed subqueries in their args.\n    // Recollecting from the current state of result_columns, HAVING, and ORDER BY ensures\n    // aggregates have updated expressions and excludes aggregates from cleared ORDER BY.\n    if !plan.aggregates.is_empty() {\n        recollect_aggregates(plan, resolver)?;\n    }\n\n    assign_select_subquery_eval_phases(plan);\n    mark_shared_cte_materialization_requirements(program, plan);\n\n    update_column_used_masks(\n        &mut plan.table_references,\n        &mut plan.non_from_clause_subqueries,\n    );\n    Ok(())\n}\n\n/// Compute query plans for subqueries in a DML statement's WHERE clause.\n/// This is used by DELETE and UPDATE statements which only have subqueries in the WHERE clause.\n/// Similar to [plan_subqueries_from_select_plan] but only handles the WHERE clause\n/// since these statements don't have GROUP BY, ORDER BY, or result column subqueries.\npub fn plan_subqueries_from_where_clause(\n    program: &mut ProgramBuilder,\n    non_from_clause_subqueries: &mut Vec<NonFromClauseSubquery>,\n    table_references: &mut TableReferences,\n    where_clause: &mut [WhereTerm],\n    resolver: &Resolver,\n    connection: &Arc<Connection>,\n) -> Result<()> {\n    plan_subqueries_with_outer_query_access(\n        program,\n        non_from_clause_subqueries,\n        table_references,\n        resolver,\n        where_clause.iter_mut().map(|t| &mut t.expr),\n        connection,\n        SubqueryPosition::Where,\n        SubqueryOrigin::DmlWhere,\n        SubqueryPosition::Where.allow_correlated(),\n    )?;\n\n    update_column_used_masks(table_references, non_from_clause_subqueries);\n    Ok(())\n}\n\n/// Compute query plans for subqueries in VALUES expressions.\n/// This is used by INSERT statements with VALUES clauses and SELECT with VALUES.\n/// The VALUES expressions may contain scalar subqueries that need to be planned.\n#[allow(clippy::vec_box)]\npub fn plan_subqueries_from_values(\n    program: &mut ProgramBuilder,\n    non_from_clause_subqueries: &mut Vec<NonFromClauseSubquery>,\n    table_references: &mut TableReferences,\n    values: &mut [Vec<Box<ast::Expr>>],\n    resolver: &Resolver,\n    connection: &Arc<Connection>,\n) -> Result<()> {\n    plan_subqueries_with_outer_query_access(\n        program,\n        non_from_clause_subqueries,\n        table_references,\n        resolver,\n        values.iter_mut().flatten().map(|e| e.as_mut()),\n        connection,\n        SubqueryPosition::ResultColumn, // VALUES are similar to result columns in terms of subquery handling\n        SubqueryOrigin::SelectList,\n        SubqueryPosition::ResultColumn.allow_correlated(),\n    )?;\n\n    update_column_used_masks(table_references, non_from_clause_subqueries);\n    Ok(())\n}\n\n/// Compute query plans for subqueries in UPDATE SET clause expressions.\n/// This is used by UPDATE statements where SET clause values contain scalar subqueries.\n/// e.g. `UPDATE t SET col = (SELECT max(id) FROM t2)`\npub fn plan_subqueries_from_set_clauses(\n    program: &mut ProgramBuilder,\n    non_from_clause_subqueries: &mut Vec<NonFromClauseSubquery>,\n    table_references: &mut TableReferences,\n    set_clauses: &mut [(usize, Box<ast::Expr>)],\n    resolver: &Resolver,\n    connection: &Arc<Connection>,\n) -> Result<()> {\n    plan_subqueries_with_outer_query_access(\n        program,\n        non_from_clause_subqueries,\n        table_references,\n        resolver,\n        set_clauses.iter_mut().map(|(_, expr)| expr.as_mut()),\n        connection,\n        SubqueryPosition::ResultColumn, // SET clause subqueries are similar to result columns\n        SubqueryOrigin::DmlSet,\n        SubqueryPosition::ResultColumn.allow_correlated(),\n    )?;\n\n    update_column_used_masks(table_references, non_from_clause_subqueries);\n    Ok(())\n}\n\n/// Compute query plans for subqueries in RETURNING expressions.\n/// This is used by INSERT, UPDATE, and DELETE statements with RETURNING clauses.\n/// RETURNING expressions may contain scalar subqueries that need to be planned.\npub fn plan_subqueries_from_returning(\n    program: &mut ProgramBuilder,\n    non_from_clause_subqueries: &mut Vec<NonFromClauseSubquery>,\n    table_references: &mut TableReferences,\n    returning: &mut [ast::ResultColumn],\n    resolver: &Resolver,\n    connection: &Arc<Connection>,\n) -> Result<()> {\n    // Extract mutable references to expressions from ResultColumn::Expr variants\n    let exprs = returning.iter_mut().filter_map(|rc| match rc {\n        ast::ResultColumn::Expr(expr, _) => Some(expr.as_mut()),\n        ast::ResultColumn::Star | ast::ResultColumn::TableStar(_) => None,\n    });\n\n    plan_subqueries_with_outer_query_access(\n        program,\n        non_from_clause_subqueries,\n        table_references,\n        resolver,\n        exprs,\n        connection,\n        SubqueryPosition::ResultColumn,\n        SubqueryOrigin::DmlReturning,\n        SubqueryPosition::ResultColumn.allow_correlated(),\n    )?;\n\n    update_column_used_masks(table_references, non_from_clause_subqueries);\n    Ok(())\n}\n\n/// Plan subqueries in a trigger WHEN clause expression.\n/// The WHEN clause has no FROM clause, so there are no outer query references.\n/// NEW/OLD references should already be rewritten to Expr::Register before calling this.\npub fn plan_subqueries_from_trigger_when_clause(\n    program: &mut ProgramBuilder,\n    non_from_clause_subqueries: &mut Vec<NonFromClauseSubquery>,\n    expr: &mut ast::Expr,\n    resolver: &Resolver,\n    connection: &Arc<Connection>,\n) -> Result<()> {\n    let mut table_references = TableReferences::new(vec![], vec![]);\n    plan_subqueries_with_outer_query_access(\n        program,\n        non_from_clause_subqueries,\n        &mut table_references,\n        resolver,\n        std::iter::once(expr),\n        connection,\n        SubqueryPosition::Where,\n        SubqueryOrigin::TriggerWhen,\n        false,\n    )\n}\n\n/// Compute query plans for subqueries in the WHERE clause and HAVING clause (both of which have access to the outer query scope)\n#[allow(clippy::too_many_arguments)]\nfn plan_subqueries_with_outer_query_access<'a>(\n    program: &mut ProgramBuilder,\n    out_subqueries: &mut Vec<NonFromClauseSubquery>,\n    referenced_tables: &mut TableReferences,\n    resolver: &Resolver,\n    exprs: impl Iterator<Item = &'a mut ast::Expr>,\n    connection: &Arc<Connection>,\n    position: SubqueryPosition,\n    origin: SubqueryOrigin,\n    allow_correlated: bool,\n) -> Result<()> {\n    // Most subqueries can reference columns from the outer query,\n    // including nested cases where a subquery inside a subquery references columns from its parent's parent\n    // and so on.\n    let get_outer_query_refs = |referenced_tables: &TableReferences| {\n        referenced_tables\n            .joined_tables()\n            .iter()\n            .map(|t| {\n                // Extract cte_id from FromClauseSubquery if this is a CTE reference\n                let cte_id = match &t.table {\n                    Table::FromClauseSubquery(subq) => subq.cte_id(),\n                    _ => None,\n                };\n                OuterQueryReference {\n                    table: t.table.clone(),\n                    identifier: t.identifier.clone(),\n                    internal_id: t.internal_id,\n                    col_used_mask: ColumnUsedMask::default(),\n                    cte_select: None,\n                    cte_explicit_columns: vec![],\n                    cte_id,\n                    cte_definition_only: false,\n                    rowid_referenced: false,\n                }\n            })\n            .chain(\n                referenced_tables\n                    .outer_query_refs()\n                    .iter()\n                    .map(|t| OuterQueryReference {\n                        table: t.table.clone(),\n                        identifier: t.identifier.clone(),\n                        internal_id: t.internal_id,\n                        col_used_mask: ColumnUsedMask::default(),\n                        cte_select: t.cte_select.clone(),\n                        cte_explicit_columns: t.cte_explicit_columns.clone(),\n                        cte_id: t.cte_id, // Preserve CTE ID from outer query refs\n                        cte_definition_only: t.cte_definition_only,\n                        rowid_referenced: false,\n                    }),\n            )\n            .collect::<Vec<_>>()\n    };\n\n    let mut subquery_parser = get_subquery_parser(\n        program,\n        out_subqueries,\n        referenced_tables,\n        resolver,\n        connection,\n        get_outer_query_refs,\n        position,\n        origin,\n        allow_correlated,\n    );\n    for expr in exprs {\n        walk_expr_mut(expr, &mut subquery_parser)?;\n    }\n\n    Ok(())\n}\n\n/// Create a closure that will walk the AST and replace subqueries with [ast::Expr::SubqueryResult] expressions.]\n#[allow(clippy::too_many_arguments)]\nfn get_subquery_parser<'a>(\n    program: &'a mut ProgramBuilder,\n    out_subqueries: &'a mut Vec<NonFromClauseSubquery>,\n    referenced_tables: &'a mut TableReferences,\n    resolver: &'a Resolver,\n    connection: &'a Arc<Connection>,\n    get_outer_query_refs: fn(&TableReferences) -> Vec<OuterQueryReference>,\n    position: SubqueryPosition,\n    origin: SubqueryOrigin,\n    allow_correlated: bool,\n) -> impl FnMut(&mut ast::Expr) -> Result<WalkControl> + 'a {\n    let handle_unsupported_correlation =\n        |correlated: bool, position: SubqueryPosition, allow_correlated: bool| -> Result<()> {\n            if correlated && !allow_correlated {\n                crate::bail_parse_error!(\n                    \"correlated subqueries in {} clause are not supported yet\",\n                    position.name()\n                );\n            }\n            Ok(())\n        };\n\n    move |expr: &mut ast::Expr| -> Result<WalkControl> {\n        match expr {\n            ast::Expr::Exists(_) => {\n                let subquery_id = program.table_reference_counter.next();\n                let outer_query_refs = get_outer_query_refs(referenced_tables);\n\n                let result_reg = program.alloc_register();\n                let subquery_type = SubqueryType::Exists { result_reg };\n                let result_expr = ast::Expr::SubqueryResult {\n                    subquery_id,\n                    lhs: None,\n                    not_in: false,\n                    query_type: subquery_type.clone(),\n                };\n                let ast::Expr::Exists(subselect) = std::mem::replace(expr, result_expr) else {\n                    unreachable!();\n                };\n\n                let plan = prepare_select_plan(\n                    subselect,\n                    resolver,\n                    program,\n                    &outer_query_refs,\n                    QueryDestination::ExistsSubqueryResult { result_reg },\n                    connection,\n                )?;\n                let Plan::Select(mut plan) = plan else {\n                    crate::bail_parse_error!(\n                        \"compound SELECT queries not supported yet in WHERE clause subqueries\"\n                    );\n                };\n                optimize_select_plan(&mut plan, resolver.schema())?;\n                let correlated = plan.is_correlated();\n                handle_unsupported_correlation(correlated, position, allow_correlated)?;\n                out_subqueries.push(NonFromClauseSubquery {\n                    internal_id: subquery_id,\n                    query_type: subquery_type,\n                    state: SubqueryState::Unevaluated {\n                        plan: Some(Box::new(plan)),\n                    },\n                    correlated,\n                    origin,\n                    eval_phase: origin.phase_floor(),\n                });\n                Ok(WalkControl::Continue)\n            }\n            ast::Expr::Subquery(_) => {\n                let subquery_id = program.table_reference_counter.next();\n                let outer_query_refs = get_outer_query_refs(referenced_tables);\n\n                let result_expr = ast::Expr::SubqueryResult {\n                    subquery_id,\n                    lhs: None,\n                    not_in: false,\n                    // Placeholder values because the number of columns returned is not known until the plan is prepared.\n                    // These are replaced below after planning.\n                    query_type: SubqueryType::RowValue {\n                        result_reg_start: 0,\n                        num_regs: 0,\n                    },\n                };\n                let ast::Expr::Subquery(subselect) = std::mem::replace(expr, result_expr) else {\n                    unreachable!();\n                };\n                let plan = prepare_select_plan(\n                    subselect,\n                    resolver,\n                    program,\n                    &outer_query_refs,\n                    QueryDestination::Unset,\n                    connection,\n                )?;\n                let Plan::Select(mut plan) = plan else {\n                    crate::bail_parse_error!(\n                        \"compound SELECT queries not supported yet in WHERE clause subqueries\"\n                    );\n                };\n                optimize_select_plan(&mut plan, resolver.schema())?;\n                let reg_count = plan.result_columns.len();\n                let reg_start = program.alloc_registers(reg_count);\n\n                plan.query_destination = QueryDestination::RowValueSubqueryResult {\n                    result_reg_start: reg_start,\n                    num_regs: reg_count,\n                };\n\n                // Only inject LIMIT 1 if there's no existing limit, or the existing limit is > 1,\n                // If LIMIT 0, subquery should return no rows (NULL).\n                let limit = match &plan.limit {\n                    Some(expr) => match parse_signed_number(expr) {\n                        Ok(Value::Numeric(Numeric::Integer(v))) => !(0..=1).contains(&v),\n                        _ => true,\n                    },\n                    None => true,\n                };\n                if limit {\n                    // RowValue subqueries are satisfied after at most 1 row has been returned,\n                    // as they are used in comparisons with a scalar or a tuple of scalars like (x,y) = (SELECT ...) or x = (SELECT ...).\n                    plan.limit = Some(Box::new(ast::Expr::Literal(ast::Literal::Numeric(\n                        \"1\".to_string(),\n                    ))));\n                }\n\n                let ast::Expr::SubqueryResult {\n                    subquery_id,\n                    lhs: None,\n                    not_in: false,\n                    query_type:\n                        SubqueryType::RowValue {\n                            result_reg_start,\n                            num_regs,\n                        },\n                } = &mut *expr\n                else {\n                    unreachable!();\n                };\n                *result_reg_start = reg_start;\n                *num_regs = reg_count;\n\n                let correlated = plan.is_correlated();\n                handle_unsupported_correlation(correlated, position, allow_correlated)?;\n\n                out_subqueries.push(NonFromClauseSubquery {\n                    internal_id: *subquery_id,\n                    query_type: SubqueryType::RowValue {\n                        result_reg_start: reg_start,\n                        num_regs: reg_count,\n                    },\n                    state: SubqueryState::Unevaluated {\n                        plan: Some(Box::new(plan)),\n                    },\n                    correlated,\n                    origin,\n                    eval_phase: origin.phase_floor(),\n                });\n                Ok(WalkControl::Continue)\n            }\n            ast::Expr::InSelect { .. } => {\n                let subquery_id = program.table_reference_counter.next();\n                let outer_query_refs = get_outer_query_refs(referenced_tables);\n\n                let ast::Expr::InSelect { lhs, not, rhs } = std::mem::take(expr) else {\n                    unreachable!();\n                };\n                let plan = prepare_select_plan(\n                    rhs,\n                    resolver,\n                    program,\n                    &outer_query_refs,\n                    QueryDestination::Unset,\n                    connection,\n                )?;\n                let Plan::Select(mut plan) = plan else {\n                    crate::bail_parse_error!(\n                        \"compound SELECT queries not supported yet in WHERE clause subqueries\"\n                    );\n                };\n                optimize_select_plan(&mut plan, resolver.schema())?;\n                // e.g. (x,y) IN (SELECT ...)\n                // or x IN (SELECT ...)\n                let lhs_columns = match unwrap_parens(lhs.as_ref())? {\n                    ast::Expr::Parenthesized(exprs) => {\n                        either::Left(exprs.iter().map(|e| e.as_ref()))\n                    }\n                    expr => either::Right(core::iter::once(expr)),\n                };\n                let lhs_column_count = lhs_columns.len();\n                if lhs_column_count != plan.result_columns.len() {\n                    crate::bail_parse_error!(\n                        \"sub-select returns {} columns - expected {lhs_column_count}\",\n                        plan.result_columns.len()\n                    );\n                }\n                // Collect affinity and LHS collation in a single pass over lhs_columns.\n                // \"x IN (SELECT y ...)\" uses the collation of x\n                // (https://www.sqlite.org/datatype3.html#collation §7.1),\n                // so the ephemeral index must use the LHS collation for correct\n                // NotFound/Found probe comparisons.\n                let mut affinity_chars = String::with_capacity(lhs_column_count);\n                let mut lhs_collations = Vec::with_capacity(lhs_column_count);\n                for (i, lhs_expr) in lhs_columns.enumerate() {\n                    let lhs_affinity =\n                        get_expr_affinity_info(lhs_expr, Some(referenced_tables), None);\n                    affinity_chars.push(\n                        compare_affinity(\n                            &plan.result_columns[i].expr,\n                            lhs_affinity,\n                            Some(&plan.table_references),\n                            None,\n                        )\n                        .aff_mask(),\n                    );\n                    lhs_collations.push(get_collseq_from_expr(lhs_expr, referenced_tables)?);\n                }\n                let in_affinity_str: Arc<String> = Arc::new(affinity_chars);\n\n                let columns = plan\n                    .result_columns\n                    .iter()\n                    .enumerate()\n                    .map(|(i, c)| {\n                        let rhs_collation = get_collseq_from_expr(&c.expr, &plan.table_references)?;\n                        Ok(IndexColumn {\n                            name: c.name(&plan.table_references).unwrap_or(\"\").to_string(),\n                            order: SortOrder::Asc,\n                            pos_in_table: i,\n                            collation: lhs_collations[i].or(rhs_collation),\n                            default: None,\n                            expr: None,\n                        })\n                    })\n                    .collect::<Result<Vec<_>>>()?;\n\n                let ephemeral_index = Arc::new(Index {\n                    columns,\n                    name: format!(\"ephemeral_index_where_sub_{subquery_id}\"),\n                    table_name: String::new(),\n                    ephemeral: true,\n                    has_rowid: false,\n                    root_page: 0,\n                    unique: false,\n                    where_clause: None,\n                    index_method: None,\n                    on_conflict: None,\n                });\n\n                let cursor_id =\n                    program.alloc_cursor_id(CursorType::BTreeIndex(ephemeral_index.clone()));\n\n                plan.query_destination = QueryDestination::EphemeralIndex {\n                    cursor_id,\n                    index: ephemeral_index,\n                    affinity_str: Some(in_affinity_str.clone()),\n                    is_delete: false,\n                };\n\n                *expr = ast::Expr::SubqueryResult {\n                    subquery_id,\n                    lhs: Some(lhs),\n                    not_in: not,\n                    query_type: SubqueryType::In {\n                        cursor_id,\n                        affinity_str: in_affinity_str.clone(),\n                    },\n                };\n\n                let correlated = plan.is_correlated();\n                handle_unsupported_correlation(correlated, position, allow_correlated)?;\n\n                out_subqueries.push(NonFromClauseSubquery {\n                    internal_id: subquery_id,\n                    query_type: SubqueryType::In {\n                        cursor_id,\n                        affinity_str: in_affinity_str,\n                    },\n                    state: SubqueryState::Unevaluated {\n                        plan: Some(Box::new(plan)),\n                    },\n                    correlated,\n                    origin,\n                    eval_phase: origin.phase_floor(),\n                });\n                Ok(WalkControl::Continue)\n            }\n            _ => Ok(WalkControl::Continue),\n        }\n    }\n}\n\n/// Recollect all aggregates after subquery planning.\n///\n/// Aggregates are collected during parsing with cloned expressions. When subquery planning\n/// modifies expressions in place (e.g. replacing EXISTS with SubqueryResult), the aggregate's\n/// cloned original_expr and args become stale. This causes cache misses during translation.\n///\n/// Instead of trying to sync stale clones, this function recollects all aggregates fresh\n/// from the updated expressions in result_columns, HAVING, and ORDER BY.\nfn recollect_aggregates(plan: &mut SelectPlan, resolver: &Resolver) -> Result<()> {\n    let mut new_aggregates: Vec<Aggregate> = Vec::new();\n\n    // Collect from result columns (same order as original collection)\n    for rc in &plan.result_columns {\n        resolve_window_and_aggregate_functions(&rc.expr, resolver, &mut new_aggregates, None)?;\n    }\n\n    // Collect from HAVING\n    if let Some(group_by) = &plan.group_by {\n        if let Some(having) = &group_by.having {\n            for expr in having {\n                resolve_window_and_aggregate_functions(expr, resolver, &mut new_aggregates, None)?;\n            }\n        }\n    }\n\n    // Collect from ORDER BY\n    for (expr, _) in &plan.order_by {\n        resolve_window_and_aggregate_functions(expr, resolver, &mut new_aggregates, None)?;\n    }\n\n    plan.aggregates = new_aggregates;\n    Ok(())\n}\n\n/// We make decisions about when to evaluate expressions or whether to use covering indexes based on\n/// which columns of a table have been referenced.\n/// Since subquery nesting is arbitrarily deep, a reference to a column must propagate recursively\n/// up to the parent. Example:\n///\n/// SELECT * FROM t WHERE EXISTS (SELECT * FROM u WHERE EXISTS (SELECT * FROM v WHERE v.foo = t.foo))\n///\n/// In this case, t.foo is referenced in the innermost subquery, so the top level query must be notified\n/// that t.foo has been used.\nfn update_column_used_masks(\n    table_refs: &mut TableReferences,\n    subqueries: &mut [NonFromClauseSubquery],\n) {\n    fn propagate_outer_refs_from_select_plan(table_refs: &mut TableReferences, plan: &SelectPlan) {\n        for child_outer_query_ref in plan\n            .table_references\n            .outer_query_refs()\n            .iter()\n            .filter(|t| t.is_used())\n        {\n            if let Some(joined_table) =\n                table_refs.find_joined_table_by_internal_id_mut(child_outer_query_ref.internal_id)\n            {\n                // Propagate column_use_counts so that expression index coverage\n                // checks see the additional references from correlated subqueries.\n                // Without this, apply_expression_index_coverage() may conclude that\n                // all uses of a column are satisfied by an expression index when in\n                // fact the correlated subquery needs the column directly.\n                for col_idx in child_outer_query_ref.col_used_mask.iter() {\n                    if col_idx >= joined_table.column_use_counts.len() {\n                        joined_table.column_use_counts.resize(col_idx + 1, 0);\n                    }\n                    joined_table.column_use_counts[col_idx] += 1;\n                }\n                joined_table.col_used_mask |= &child_outer_query_ref.col_used_mask;\n            }\n            if let Some(outer_query_ref) = table_refs\n                .find_outer_query_ref_by_internal_id_mut(child_outer_query_ref.internal_id)\n            {\n                outer_query_ref.col_used_mask |= &child_outer_query_ref.col_used_mask;\n            }\n        }\n\n        for joined_table in plan.table_references.joined_tables().iter() {\n            if let Table::FromClauseSubquery(from_clause_subquery) = &joined_table.table {\n                propagate_outer_refs_from_plan(table_refs, from_clause_subquery.plan.as_ref());\n            }\n        }\n    }\n\n    fn propagate_outer_refs_from_plan(table_refs: &mut TableReferences, plan: &Plan) {\n        match plan {\n            Plan::Select(select_plan) => {\n                propagate_outer_refs_from_select_plan(table_refs, select_plan);\n            }\n            Plan::CompoundSelect {\n                left, right_most, ..\n            } => {\n                for (select_plan, _) in left.iter() {\n                    propagate_outer_refs_from_select_plan(table_refs, select_plan);\n                }\n                propagate_outer_refs_from_select_plan(table_refs, right_most);\n            }\n            Plan::Delete(_) | Plan::Update(_) => {}\n        }\n    }\n\n    for subquery in subqueries.iter_mut() {\n        let SubqueryState::Unevaluated { plan } = &mut subquery.state else {\n            panic!(\"subquery has already been evaluated\");\n        };\n        let Some(child_plan) = plan.as_mut() else {\n            panic!(\"subquery has no plan\");\n        };\n\n        propagate_outer_refs_from_select_plan(table_refs, child_plan);\n    }\n\n    // Collect raw plan pointers to avoid cloning while sidestepping borrow rules.\n    let from_clause_plans = table_refs\n        .joined_tables()\n        .iter()\n        .filter_map(|t| match &t.table {\n            Table::FromClauseSubquery(from_clause_subquery) => {\n                Some(from_clause_subquery.plan.as_ref() as *const Plan)\n            }\n            _ => None,\n        })\n        .collect::<Vec<_>>();\n    for plan in from_clause_plans {\n        // SAFETY: plans live within table_refs for the duration of this function.\n        let plan = unsafe { &*plan };\n        propagate_outer_refs_from_plan(table_refs, plan);\n    }\n}\n\n/// Recursively pre-materialize all multi-ref CTEs in a plan tree.\n/// This must be called BEFORE emitting any coroutines to ensure CTEs referenced\n/// inside coroutines have their cursors opened at the top level.\nfn pre_materialize_multi_ref_ctes(\n    program: &mut ProgramBuilder,\n    plan: &mut Plan,\n    t_ctx: &mut TranslateCtx,\n) -> Result<()> {\n    match plan {\n        Plan::Select(select_plan) => {\n            pre_materialize_multi_ref_ctes_in_select_plan(program, select_plan, t_ctx)?;\n        }\n        Plan::CompoundSelect {\n            left, right_most, ..\n        } => {\n            for (select_plan, _) in left.iter_mut() {\n                pre_materialize_multi_ref_ctes_in_select_plan(program, select_plan, t_ctx)?;\n            }\n            pre_materialize_multi_ref_ctes_in_select_plan(program, right_most, t_ctx)?;\n        }\n        Plan::Delete(_) | Plan::Update(_) => {}\n    }\n    Ok(())\n}\n\nfn pre_materialize_multi_ref_ctes_in_select_plan(\n    program: &mut ProgramBuilder,\n    plan: &mut SelectPlan,\n    t_ctx: &mut TranslateCtx,\n) -> Result<()> {\n    pre_materialize_multi_ref_ctes_in_tables(program, &mut plan.table_references, t_ctx)?;\n    pre_materialize_multi_ref_ctes_in_non_from_subqueries(\n        program,\n        &mut plan.non_from_clause_subqueries,\n        t_ctx,\n    )\n}\n\nfn pre_materialize_multi_ref_ctes_in_non_from_subqueries(\n    program: &mut ProgramBuilder,\n    subqueries: &mut [NonFromClauseSubquery],\n    t_ctx: &mut TranslateCtx,\n) -> Result<()> {\n    for subquery in subqueries.iter_mut() {\n        let SubqueryState::Unevaluated {\n            plan: Some(subquery_plan),\n        } = &mut subquery.state\n        else {\n            continue;\n        };\n        pre_materialize_multi_ref_ctes_in_select_plan(program, subquery_plan.as_mut(), t_ctx)?;\n    }\n    Ok(())\n}\n\nfn pre_materialize_multi_ref_ctes_in_tables(\n    program: &mut ProgramBuilder,\n    tables: &mut TableReferences,\n    t_ctx: &mut TranslateCtx,\n) -> Result<()> {\n    for table_reference in tables.joined_tables_mut().iter_mut() {\n        if let Table::FromClauseSubquery(from_clause_subquery) = &mut table_reference.table {\n            let from_clause_subquery = Arc::make_mut(from_clause_subquery);\n            // First, recursively process nested plans\n            pre_materialize_multi_ref_ctes(program, from_clause_subquery.plan.as_mut(), t_ctx)?;\n\n            // Then check if THIS CTE should be materialized\n            if let Some(cte_id) = from_clause_subquery.cte_id() {\n                if program.get_materialized_cte(cte_id).is_some() {\n                    continue;\n                }\n                if from_clause_subquery.requires_table_materialization() {\n                    tracing::trace!(\n                        cte_id,\n                        identifier = %table_reference.identifier,\n                        \"pre-materializing shared CTE\"\n                    );\n                    let (result_columns_start, cte_cursor_id, cte_table) =\n                        emit_materialized_subquery_table(\n                            program,\n                            from_clause_subquery.plan.as_mut(),\n                            t_ctx,\n                            &from_clause_subquery.columns,\n                        )?;\n                    program.register_materialized_cte(\n                        cte_id,\n                        MaterializedCteInfo {\n                            cursor_id: cte_cursor_id,\n                            table: cte_table,\n                            num_columns: from_clause_subquery.columns.len(),\n                        },\n                    );\n                    from_clause_subquery.materialized_cursor_id = Some(cte_cursor_id);\n                    from_clause_subquery.result_columns_start_reg = Some(result_columns_start);\n                    program\n                        .set_subquery_result_reg(table_reference.internal_id, result_columns_start);\n                }\n            }\n        }\n    }\n    Ok(())\n}\n\nfn choose_from_clause_subquery_execution_mode(\n    operation: &Operation,\n    from_clause_subquery: &crate::schema::FromClauseSubquery,\n) -> FromClauseSubqueryExecutionMode {\n    let needs_materialized_seek = matches!(\n        operation,\n        Operation::Search(Search::Seek {\n            index: Some(index), ..\n        }) if index.ephemeral\n    );\n\n    // Compound SELECTs still need their own internal ephemeral indexes for\n    // UNION/INTERSECT/EXCEPT bookkeeping. Reusing the subquery's synthesized\n    // seek index as the storage target would collapse those roles together and\n    // break set-operation semantics, so keep the direct-index fast path limited\n    // to simple SELECT plans.\n    let can_direct_materialize_index = from_clause_subquery.supports_direct_index_materialization();\n\n    match operation {\n        Operation::Search(Search::Seek {\n            index: Some(index),\n            seek_def,\n        }) if index.ephemeral && can_direct_materialize_index => {\n            FromClauseSubqueryExecutionMode::DirectMaterializedIndex(DirectMaterializedSubquery {\n                index: index.clone(),\n                affinity_str: super::plan::synthesized_seek_affinity_str(index, seek_def),\n            })\n        }\n        _ if needs_materialized_seek => FromClauseSubqueryExecutionMode::MaterializedTable,\n        _ if from_clause_subquery.requires_table_materialization() => {\n            FromClauseSubqueryExecutionMode::MaterializedTable\n        }\n        _ => FromClauseSubqueryExecutionMode::Coroutine,\n    }\n}\n\n/// Emit the subqueries contained in the FROM clause.\n/// This is done first so the results can be read in the main query loop.\npub fn emit_from_clause_subqueries(\n    program: &mut ProgramBuilder,\n    t_ctx: &mut TranslateCtx,\n    tables: &mut TableReferences,\n    join_order: &[JoinOrderMember],\n) -> Result<()> {\n    if tables.joined_tables().is_empty() {\n        emit_explain!(program, false, \"SCAN CONSTANT ROW\".to_owned());\n    }\n\n    // FIRST PASS: Pre-materialize all recursively reachable multi-ref / hinted CTEs\n    // before any coroutine bodies are emitted. Otherwise a coroutine could try to\n    // OpenDup a CTE whose backing table has not been created yet.\n    pre_materialize_multi_ref_ctes_in_tables(program, tables, t_ctx)?;\n\n    // Build the iteration order: join_order first (execution order), then any\n    // hash-join build tables that aren't already in the join order.\n    let mut visit_order: Vec<usize> = join_order\n        .iter()\n        .map(|member| member.original_idx)\n        .collect();\n    let visit_set: HashSet<usize> = visit_order.iter().copied().collect();\n    for table in tables.joined_tables().iter() {\n        if let Operation::HashJoin(hash_join_op) = &table.op {\n            let build_idx = hash_join_op.build_table_idx;\n            if !visit_set.contains(&build_idx) {\n                visit_order.push(build_idx);\n            }\n        }\n    }\n\n    // Build lookup from table index to is_outer for LEFT-JOIN annotations\n    let outer_table_set: HashSet<usize> = join_order\n        .iter()\n        .filter(|m| m.is_outer)\n        .map(|m| m.original_idx)\n        .collect();\n\n    for table_index in visit_order {\n        let table_reference = &mut tables.joined_tables_mut()[table_index];\n        let left_join_suffix = if outer_table_set.contains(&table_index) {\n            \" LEFT-JOIN\"\n        } else {\n            \"\"\n        };\n        emit_explain!(\n            program,\n            true,\n            match &table_reference.op {\n                Operation::Scan(scan) => {\n                    let table_name =\n                        if table_reference.table.get_name() == table_reference.identifier {\n                            table_reference.identifier.clone()\n                        } else {\n                            format!(\n                                \"{} AS {}\",\n                                table_reference.table.get_name(),\n                                table_reference.identifier\n                            )\n                        };\n\n                    match scan {\n                        Scan::BTreeTable { index, .. } => {\n                            if let Some(index) = index {\n                                if table_reference.utilizes_covering_index() {\n                                    format!(\"SCAN {table_name} USING COVERING INDEX {}\", index.name)\n                                } else {\n                                    format!(\"SCAN {table_name} USING INDEX {}\", index.name)\n                                }\n                            } else {\n                                format!(\"SCAN {table_name}\")\n                            }\n                        }\n                        Scan::VirtualTable { .. } | Scan::Subquery { .. } => {\n                            format!(\"SCAN {table_name}\")\n                        }\n                    }\n                }\n                Operation::Search(search) => match search {\n                    Search::RowidEq { .. }\n                    | Search::Seek { index: None, .. }\n                    | Search::InSeek { index: None, .. } => {\n                        format!(\n                            \"SEARCH {} USING INTEGER PRIMARY KEY (rowid=?){left_join_suffix}\",\n                            table_reference.identifier\n                        )\n                    }\n                    Search::Seek {\n                        index: Some(index),\n                        seek_def,\n                    } => {\n                        let constraints =\n                            super::display::seek_constraint_annotation(index, seek_def);\n                        format!(\n                            \"SEARCH {} USING INDEX {}{constraints}{left_join_suffix}\",\n                            table_reference.identifier, index.name\n                        )\n                    }\n                    Search::InSeek {\n                        index: Some(index), ..\n                    } => {\n                        let constraint = if let Some(col) = index.columns.first() {\n                            format!(\" ({}=?)\", col.name)\n                        } else {\n                            String::new()\n                        };\n                        format!(\n                            \"SEARCH {} USING INDEX {}{constraint}{left_join_suffix}\",\n                            table_reference.identifier, index.name\n                        )\n                    }\n                },\n                Operation::IndexMethodQuery(query) => {\n                    let index_method = query.index.index_method.as_ref().unwrap();\n                    format!(\n                        \"QUERY INDEX METHOD {}\",\n                        index_method.definition().method_name\n                    )\n                }\n                Operation::HashJoin(_) => {\n                    let table_name =\n                        if table_reference.table.get_name() == table_reference.identifier {\n                            table_reference.identifier.clone()\n                        } else {\n                            format!(\n                                \"{} AS {}\",\n                                table_reference.table.get_name(),\n                                table_reference.identifier\n                            )\n                        };\n                    format!(\"HASH JOIN {table_name}\")\n                }\n                Operation::MultiIndexScan(multi_idx) => {\n                    let index_names: Vec<&str> = multi_idx\n                        .branches\n                        .iter()\n                        .map(|b| {\n                            b.index\n                                .as_ref()\n                                .map(|i| i.name.as_str())\n                                .unwrap_or(\"PRIMARY KEY\")\n                        })\n                        .collect();\n                    format!(\n                        \"MULTI-INDEX {} {} ({})\",\n                        match multi_idx.set_op {\n                            SetOperation::Union => \"OR\",\n                            SetOperation::Intersection { .. } => \"AND\",\n                        },\n                        table_reference.identifier,\n                        index_names.join(\", \")\n                    )\n                }\n            }\n        );\n\n        if let Table::FromClauseSubquery(from_clause_subquery) = &mut table_reference.table {\n            let execution_mode = {\n                let from_clause_subquery = from_clause_subquery.as_ref();\n                choose_from_clause_subquery_execution_mode(\n                    &table_reference.op,\n                    from_clause_subquery,\n                )\n            };\n            let from_clause_subquery = Arc::make_mut(from_clause_subquery);\n            // Check if this is a CTE that's already materialized\n            if let Some(cte_id) = from_clause_subquery.cte_id() {\n                if let Some(cte_info) = program.get_materialized_cte(cte_id).cloned() {\n                    if from_clause_subquery.materialized_cursor_id.is_some() {\n                        tracing::trace!(\n                            cte_id,\n                            identifier = %table_reference.identifier,\n                            \"reusing pre-materialized CTE on original reference\"\n                        );\n                        program.pop_current_parent_explain();\n                        continue;\n                    }\n                    // === SUBSEQUENT CTE REFERENCE: Use OpenDup ===\n                    // Create a dup cursor pointing to the same ephemeral table\n                    let dup_cursor_id =\n                        program.alloc_cursor_id(CursorType::BTreeTable(cte_info.table.clone()));\n                    program.emit_insn(Insn::OpenDup {\n                        new_cursor_id: dup_cursor_id,\n                        original_cursor_id: cte_info.cursor_id,\n                    });\n                    tracing::trace!(\n                        cte_id,\n                        identifier = %table_reference.identifier,\n                        original_cursor_id = cte_info.cursor_id,\n                        dup_cursor_id,\n                        \"opening duplicate cursor for materialized CTE\"\n                    );\n\n                    // Update the plan's query destination to EphemeralTable so that\n                    // main_loop knows to use Rewind/Next instead of coroutine Yield\n                    if let Some(dest) = from_clause_subquery.plan.select_query_destination_mut() {\n                        *dest = QueryDestination::EphemeralTable {\n                            cursor_id: dup_cursor_id,\n                            table: cte_info.table.clone(),\n                            rowid_mode: super::plan::EphemeralRowidMode::Auto,\n                        };\n                    }\n\n                    // Each CTE reference needs its OWN registers to read column values into.\n                    // We cannot share the original's result_columns_start_reg because multiple\n                    // iterators of the same CTE (e.g., outer query and subquery) would\n                    // overwrite each other's values when reading columns from their cursors.\n                    let result_columns_start = program.alloc_registers(cte_info.num_columns);\n                    from_clause_subquery.materialized_cursor_id = Some(dup_cursor_id);\n                    from_clause_subquery.result_columns_start_reg = Some(result_columns_start);\n                    program\n                        .set_subquery_result_reg(table_reference.internal_id, result_columns_start);\n                    program.pop_current_parent_explain();\n                    continue; // Skip normal emission\n                }\n            }\n\n            let result_columns_start = match execution_mode {\n                FromClauseSubqueryExecutionMode::Coroutine => {\n                    emit_from_clause_subquery(program, from_clause_subquery.plan.as_mut(), t_ctx)?\n                }\n                FromClauseSubqueryExecutionMode::MaterializedTable => {\n                    let (result_columns_start, cte_cursor_id, cte_table) =\n                        emit_materialized_subquery_table(\n                            program,\n                            from_clause_subquery.plan.as_mut(),\n                            t_ctx,\n                            &from_clause_subquery.columns,\n                        )?;\n                    from_clause_subquery.materialized_cursor_id = Some(cte_cursor_id);\n                    if let Some(cte_id) = from_clause_subquery.cte_id() {\n                        program.register_materialized_cte(\n                            cte_id,\n                            MaterializedCteInfo {\n                                cursor_id: cte_cursor_id,\n                                table: cte_table,\n                                num_columns: from_clause_subquery.columns.len(),\n                            },\n                        );\n                    }\n                    result_columns_start\n                }\n                FromClauseSubqueryExecutionMode::DirectMaterializedIndex(direct_index) => {\n                    emit_indexed_materialized_subquery(\n                        program,\n                        from_clause_subquery.plan.as_mut(),\n                        t_ctx,\n                        table_reference.internal_id,\n                        direct_index.index,\n                        direct_index.affinity_str,\n                        from_clause_subquery.columns.len(),\n                    )?\n                }\n            };\n\n            from_clause_subquery.result_columns_start_reg = Some(result_columns_start);\n            program.set_subquery_result_reg(table_reference.internal_id, result_columns_start);\n        }\n\n        program.pop_current_parent_explain();\n    }\n    Ok(())\n}\n\n/// Emit a FROM clause subquery and return the start register of the result columns.\n/// This is done by emitting a coroutine that stores the result columns in sequential registers.\n/// Each FROM clause subquery has its own Plan (either SelectPlan or CompoundSelect) which is wrapped in a coroutine.\n///\n/// The resulting bytecode from a subquery is mostly exactly the same as a regular query, except:\n/// - it ends in an EndCoroutine instead of a Halt.\n/// - instead of emitting ResultRows, the coroutine yields to the main query loop.\n/// - the first register of the result columns is returned to the parent query,\n///   so that translate_expr() can read the result columns of the subquery,\n///   as if it were reading from a regular table.\n///\n/// Since a subquery has its own Plan, it can contain nested subqueries,\n/// which can contain even more nested subqueries, etc.\npub fn emit_from_clause_subquery(\n    program: &mut ProgramBuilder,\n    plan: &mut Plan,\n    t_ctx: &mut TranslateCtx,\n) -> Result<usize> {\n    let yield_reg = program.alloc_register();\n    let coroutine_implementation_start_offset = program.allocate_label();\n\n    // Set up the coroutine yield destination for the plan\n    match plan.select_query_destination_mut() {\n        Some(QueryDestination::CoroutineYield {\n            yield_reg: y,\n            coroutine_implementation_start,\n        }) => {\n            // The parent query will use this register to jump to/from the subquery.\n            *y = yield_reg;\n            // The parent query will use this register to reinitialize the coroutine when it needs to run multiple times.\n            *coroutine_implementation_start = coroutine_implementation_start_offset;\n        }\n        _ => unreachable!(\"emit_from_clause_subquery called on non-subquery\"),\n    }\n\n    let subquery_body_end_label = program.allocate_label();\n\n    program.emit_insn(Insn::InitCoroutine {\n        yield_reg,\n        jump_on_definition: subquery_body_end_label,\n        start_offset: coroutine_implementation_start_offset,\n    });\n    program.preassign_label_to_next_insn(coroutine_implementation_start_offset);\n\n    let result_column_start_reg = match plan {\n        Plan::Select(select_plan) => {\n            let mut metadata = TranslateCtx {\n                labels_main_loop: (0..select_plan.joined_tables().len())\n                    .map(|_| LoopLabels::new(program))\n                    .collect(),\n                label_main_loop_end: None,\n                meta_group_by: None,\n                meta_left_joins: (0..select_plan.joined_tables().len())\n                    .map(|_| None)\n                    .collect(),\n                meta_semi_anti_joins: (0..select_plan.joined_tables().len())\n                    .map(|_| None)\n                    .collect(),\n                meta_sort: None,\n                reg_agg_start: None,\n                reg_nonagg_emit_once_flag: None,\n                reg_result_cols_start: None,\n                limit_ctx: None,\n                reg_offset: None,\n                reg_limit_offset_sum: None,\n                resolver: t_ctx.resolver.fork(),\n                non_aggregate_expressions: Vec::new(),\n                agg_leaf_columns: Vec::new(),\n                cdc_cursor_id: None,\n                meta_window: None,\n                meta_in_seeks: (0..select_plan.joined_tables().len())\n                    .map(|_| None)\n                    .collect(),\n                materialized_build_inputs: HashMap::default(),\n                hash_table_contexts: HashMap::default(),\n                unsafe_testing: t_ctx.unsafe_testing,\n            };\n            emit_query(program, select_plan, &mut metadata)?\n        }\n        Plan::CompoundSelect { .. } => {\n            // Clone the plan to pass to emit_program_for_compound_select (it takes ownership)\n            let plan_clone = plan.clone();\n            let resolver = t_ctx.resolver.fork();\n            // emit_program_for_compound_select returns the result column start register\n            // for coroutine mode, which is needed by the outer query.\n            emit_program_for_compound_select(program, &resolver, plan_clone)?\n                .expect(\"compound CTE in coroutine mode must have result register\")\n        }\n        Plan::Delete(_) | Plan::Update(_) => {\n            unreachable!(\"DELETE/UPDATE plans cannot be FROM clause subqueries\")\n        }\n    };\n\n    program.emit_insn(Insn::EndCoroutine { yield_reg });\n    program.preassign_label_to_next_insn(subquery_body_end_label);\n    Ok(result_column_start_reg)\n}\n/// Materialize a single-reference seekable FROM-subquery directly into an\n/// ephemeral index.\n///\n/// This skips the intermediate EphemeralTable when we only need seek access and do\n/// not need table-backed sharing via OpenDup. Result columns for this path are read\n/// back from the index using `pos_in_table` mapping rather than raw index position.\nfn emit_indexed_materialized_subquery(\n    program: &mut ProgramBuilder,\n    plan: &mut Plan,\n    t_ctx: &mut TranslateCtx,\n    internal_id: ast::TableInternalId,\n    index: Arc<Index>,\n    affinity_str: Option<Arc<String>>,\n    num_columns: usize,\n) -> Result<usize> {\n    let cursor_id = program\n        .alloc_cursor_index_if_not_exists(CursorKey::index(internal_id, index.clone()), &index)?;\n    let result_columns_start_reg = program.alloc_registers(num_columns);\n\n    if let Some(dest) = plan.select_query_destination_mut() {\n        *dest = QueryDestination::EphemeralIndex {\n            cursor_id,\n            index,\n            affinity_str,\n            is_delete: false,\n        };\n    }\n\n    program.emit_insn(Insn::OpenEphemeral {\n        cursor_id,\n        is_table: false,\n    });\n\n    match plan {\n        Plan::Select(select_plan) => {\n            let mut metadata = TranslateCtx {\n                labels_main_loop: (0..select_plan.joined_tables().len())\n                    .map(|_| LoopLabels::new(program))\n                    .collect(),\n                label_main_loop_end: None,\n                meta_group_by: None,\n                meta_left_joins: (0..select_plan.joined_tables().len())\n                    .map(|_| None)\n                    .collect(),\n                meta_semi_anti_joins: (0..select_plan.joined_tables().len())\n                    .map(|_| None)\n                    .collect(),\n                meta_sort: None,\n                reg_agg_start: None,\n                reg_nonagg_emit_once_flag: None,\n                reg_result_cols_start: None,\n                limit_ctx: None,\n                reg_offset: None,\n                reg_limit_offset_sum: None,\n                resolver: t_ctx.resolver.fork(),\n                non_aggregate_expressions: Vec::new(),\n                agg_leaf_columns: Vec::new(),\n                cdc_cursor_id: None,\n                meta_window: None,\n                meta_in_seeks: (0..select_plan.joined_tables().len())\n                    .map(|_| None)\n                    .collect(),\n                materialized_build_inputs: HashMap::default(),\n                hash_table_contexts: HashMap::default(),\n                unsafe_testing: t_ctx.unsafe_testing,\n            };\n            emit_query(program, select_plan, &mut metadata)?;\n        }\n        Plan::CompoundSelect { .. } => {\n            let plan_clone = plan.clone();\n            let resolver = t_ctx.resolver.fork();\n            emit_program_for_compound_select(program, &resolver, plan_clone)?;\n        }\n        Plan::Delete(_) | Plan::Update(_) => {\n            unreachable!(\"DELETE/UPDATE plans cannot be FROM clause subqueries\")\n        }\n    }\n\n    Ok(result_columns_start_reg)\n}\n\nfn emit_materialized_subquery_table(\n    program: &mut ProgramBuilder,\n    plan: &mut Plan,\n    t_ctx: &mut TranslateCtx,\n    columns: &[Column],\n) -> Result<(usize, CursorID, Arc<BTreeTable>)> {\n    use super::plan::EphemeralRowidMode;\n\n    // EphemeralTable (not EphemeralIndex) is required because it preserves\n    // insertion order, which SQL semantics require for UNION ALL. It also\n    // needs the subquery's column layout so later Column opcodes can read\n    // materialized rows through the normal table-cursor path.\n    let ephemeral_table = Arc::new(BTreeTable {\n        root_page: 0,\n        name: String::new(),\n        columns: columns.to_vec(),\n        primary_key_columns: vec![],\n        has_rowid: true,\n        is_strict: false,\n        has_autoincrement: false,\n        unique_sets: vec![],\n        foreign_keys: vec![],\n        check_constraints: vec![],\n        rowid_alias_conflict_clause: None,\n    });\n\n    let cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(ephemeral_table.clone()));\n\n    // Allocate registers for reading result columns\n    let result_columns_start_reg = program.alloc_registers(columns.len());\n\n    // Open the ephemeral table\n    program.emit_insn(Insn::OpenEphemeral {\n        cursor_id,\n        is_table: true,\n    });\n\n    // Set the query destination to write to the ephemeral table\n    if let Some(dest) = plan.select_query_destination_mut() {\n        *dest = QueryDestination::EphemeralTable {\n            cursor_id,\n            table: ephemeral_table.clone(),\n            rowid_mode: EphemeralRowidMode::Auto,\n        };\n    }\n\n    // Emit the subquery - it will insert rows into the ephemeral table\n    match plan {\n        Plan::Select(select_plan) => {\n            let mut metadata = TranslateCtx {\n                labels_main_loop: (0..select_plan.joined_tables().len())\n                    .map(|_| LoopLabels::new(program))\n                    .collect(),\n                label_main_loop_end: None,\n                meta_group_by: None,\n                meta_left_joins: (0..select_plan.joined_tables().len())\n                    .map(|_| None)\n                    .collect(),\n                meta_semi_anti_joins: (0..select_plan.joined_tables().len())\n                    .map(|_| None)\n                    .collect(),\n                meta_sort: None,\n                reg_agg_start: None,\n                reg_nonagg_emit_once_flag: None,\n                reg_result_cols_start: None,\n                limit_ctx: None,\n                reg_offset: None,\n                reg_limit_offset_sum: None,\n                resolver: t_ctx.resolver.fork(),\n                non_aggregate_expressions: Vec::new(),\n                agg_leaf_columns: Vec::new(),\n                cdc_cursor_id: None,\n                meta_window: None,\n                meta_in_seeks: (0..select_plan.joined_tables().len())\n                    .map(|_| None)\n                    .collect(),\n                materialized_build_inputs: HashMap::default(),\n                hash_table_contexts: HashMap::default(),\n                unsafe_testing: t_ctx.unsafe_testing,\n            };\n            emit_query(program, select_plan, &mut metadata)?;\n        }\n        Plan::CompoundSelect { .. } => {\n            // Clone the plan to pass to emit_program_for_compound_select (it takes ownership)\n            let plan_clone = plan.clone();\n            let resolver = t_ctx.resolver.fork();\n            emit_program_for_compound_select(program, &resolver, plan_clone)?;\n        }\n        Plan::Delete(_) | Plan::Update(_) => {\n            unreachable!(\"DELETE/UPDATE plans cannot be FROM clause subqueries\")\n        }\n    }\n\n    Ok((result_columns_start_reg, cursor_id, ephemeral_table))\n}\n\n/// Translate a subquery that is not part of the FROM clause.\n/// If a subquery is uncorrelated (i.e. does not reference columns from the outer query),\n/// it will be executed only once.\n///\n/// If it is correlated (i.e. references columns from the outer query),\n/// it will be executed for each row of the outer query.\n///\n/// The result of the subquery is stored in:\n///\n/// - a single register for EXISTS subqueries,\n/// - a range of registers for RowValue subqueries,\n/// - an ephemeral index for IN subqueries.\npub fn emit_non_from_clause_subquery(\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    plan: SelectPlan,\n    query_type: &SubqueryType,\n    is_correlated: bool,\n    preserve_outer_expr_cache: bool,\n) -> Result<()> {\n    program.nested(|program| {\n        let subquery_id = program.next_subquery_eqp_id();\n        let correlated_prefix = if is_correlated { \"CORRELATED \" } else { \"\" };\n        match query_type {\n            SubqueryType::Exists { .. } => {\n                // EXISTS subqueries don't get a separate EQP annotation in SQLite;\n                // instead the SEARCH/SCAN line gets an \"EXISTS\" suffix handled elsewhere.\n            }\n            SubqueryType::In { .. } => {\n                emit_explain!(\n                    program,\n                    true,\n                    format!(\"{correlated_prefix}LIST SUBQUERY {subquery_id}\")\n                );\n            }\n            SubqueryType::RowValue { .. } => {\n                emit_explain!(\n                    program,\n                    true,\n                    format!(\"{correlated_prefix}SCALAR SUBQUERY {subquery_id}\")\n                );\n            }\n        }\n\n        let label_skip_after_first_run = if !is_correlated {\n            let label = program.allocate_label();\n            program.emit_insn(Insn::Once {\n                target_pc_when_reentered: label,\n            });\n            Some(label)\n        } else {\n            None\n        };\n\n        match query_type {\n            SubqueryType::Exists { result_reg, .. } => {\n                let subroutine_reg = program.alloc_register();\n                program.emit_insn(Insn::BeginSubrtn {\n                    dest: subroutine_reg,\n                    dest_end: None,\n                });\n                program.emit_insn(Insn::Integer {\n                    value: 0,\n                    dest: *result_reg,\n                });\n                if preserve_outer_expr_cache {\n                    emit_program_for_select_with_resolver(\n                        program,\n                        resolver.fork_with_expr_cache(),\n                        plan,\n                    )?;\n                } else {\n                    emit_program_for_select(program, resolver, plan)?;\n                }\n                program.emit_insn(Insn::Return {\n                    return_reg: subroutine_reg,\n                    can_fallthrough: true,\n                });\n            }\n            SubqueryType::In { cursor_id, .. } => {\n                program.emit_insn(Insn::OpenEphemeral {\n                    cursor_id: *cursor_id,\n                    is_table: false,\n                });\n                if preserve_outer_expr_cache {\n                    emit_program_for_select_with_resolver(\n                        program,\n                        resolver.fork_with_expr_cache(),\n                        plan,\n                    )?;\n                } else {\n                    emit_program_for_select(program, resolver, plan)?;\n                }\n            }\n            SubqueryType::RowValue {\n                result_reg_start,\n                num_regs,\n            } => {\n                let subroutine_reg = program.alloc_register();\n                program.emit_insn(Insn::BeginSubrtn {\n                    dest: subroutine_reg,\n                    dest_end: None,\n                });\n                for result_reg in *result_reg_start..*result_reg_start + *num_regs {\n                    program.emit_insn(Insn::Null {\n                        dest: result_reg,\n                        dest_end: None,\n                    });\n                }\n                if preserve_outer_expr_cache {\n                    emit_program_for_select_with_resolver(\n                        program,\n                        resolver.fork_with_expr_cache(),\n                        plan,\n                    )?;\n                } else {\n                    emit_program_for_select(program, resolver, plan)?;\n                }\n                program.emit_insn(Insn::Return {\n                    return_reg: subroutine_reg,\n                    can_fallthrough: true,\n                });\n            }\n        }\n        // Pop the parent explain for LIST/SCALAR SUBQUERY annotations.\n        if !matches!(query_type, SubqueryType::Exists { .. }) {\n            program.pop_current_parent_explain();\n        }\n        if let Some(label) = label_skip_after_first_run {\n            program.preassign_label_to_next_insn(label);\n        }\n        Ok(())\n    })\n}\n\npub fn emit_non_from_clause_subqueries_for_phase(\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    subqueries: &mut [NonFromClauseSubquery],\n    join_order: &[JoinOrderMember],\n    table_references: Option<&TableReferences>,\n    phase: SubqueryEvalPhase,\n    mut should_emit: impl FnMut(&NonFromClauseSubquery) -> bool,\n) -> Result<()> {\n    for subquery in subqueries.iter_mut() {\n        if subquery.has_been_evaluated() || !should_emit(subquery) {\n            continue;\n        }\n\n        let evaluated_at = match phase {\n            SubqueryEvalPhase::BeforeLoop | SubqueryEvalPhase::Loop(_) => {\n                if !matches!(subquery.eval_phase, SubqueryEvalPhase::BeforeLoop) {\n                    continue;\n                }\n                let expected_eval_at = match phase {\n                    SubqueryEvalPhase::BeforeLoop => EvalAt::BeforeLoop,\n                    SubqueryEvalPhase::Loop(loop_idx) => EvalAt::Loop(loop_idx),\n                    _ => unreachable!(),\n                };\n                let evaluated_at = subquery.get_eval_at(join_order, table_references)?;\n                if evaluated_at != expected_eval_at {\n                    continue;\n                }\n                evaluated_at\n            }\n            _ => {\n                if subquery.eval_phase != phase {\n                    continue;\n                }\n                subquery.get_eval_at(join_order, table_references)?\n            }\n        };\n\n        let subquery_plan = subquery.consume_plan(evaluated_at);\n        emit_non_from_clause_subquery(\n            program,\n            resolver,\n            *subquery_plan,\n            &subquery.query_type,\n            subquery.correlated,\n            !matches!(\n                phase,\n                SubqueryEvalPhase::BeforeLoop | SubqueryEvalPhase::Loop(_)\n            ),\n        )?;\n    }\n\n    Ok(())\n}\n\npub fn emit_non_from_clause_subqueries_for_eval_at(\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    subqueries: &mut [NonFromClauseSubquery],\n    join_order: &[JoinOrderMember],\n    table_references: Option<&TableReferences>,\n    eval_at: EvalAt,\n    should_emit: impl FnMut(&NonFromClauseSubquery) -> bool,\n) -> Result<()> {\n    emit_non_from_clause_subqueries_for_phase(\n        program,\n        resolver,\n        subqueries,\n        join_order,\n        table_references,\n        match eval_at {\n            EvalAt::BeforeLoop => SubqueryEvalPhase::BeforeLoop,\n            EvalAt::Loop(loop_idx) => SubqueryEvalPhase::Loop(loop_idx),\n        },\n        should_emit,\n    )\n}\n\nfn assign_select_subquery_eval_phases(plan: &mut SelectPlan) {\n    let has_grouped_output = plan\n        .group_by\n        .as_ref()\n        .is_some_and(|group_by| !group_by.exprs.is_empty());\n\n    for subquery in plan.non_from_clause_subqueries.iter_mut() {\n        subquery.eval_phase = match subquery.origin {\n            SubqueryOrigin::SelectHaving | SubqueryOrigin::SelectOrderBy if has_grouped_output => {\n                SubqueryEvalPhase::GroupedOutput\n            }\n            _ => subquery.origin.phase_floor(),\n        };\n    }\n}\n"
  },
  {
    "path": "core/translate/transaction.rs",
    "content": "use crate::schema::Schema;\nuse crate::translate::emitter::{\n    emit_cdc_commit_insns, prepare_cdc_if_necessary, Resolver, TransactionMode,\n};\nuse crate::translate::{ProgramBuilder, ProgramBuilderOpts};\nuse crate::vdbe::insn::Insn;\nuse crate::Result;\nuse turso_parser::ast::{Name, TransactionType};\n\npub fn translate_tx_begin(\n    tx_type: Option<TransactionType>,\n    _tx_name: Option<Name>,\n    schema: &Schema,\n    program: &mut ProgramBuilder,\n) -> Result<()> {\n    program.extend(&ProgramBuilderOpts {\n        num_cursors: 0,\n        approx_num_insns: 0,\n        approx_num_labels: 0,\n    });\n    let tx_type = tx_type.unwrap_or(TransactionType::Deferred);\n    match tx_type {\n        TransactionType::Deferred => {\n            program.emit_insn(Insn::AutoCommit {\n                auto_commit: false,\n                rollback: false,\n            });\n        }\n        TransactionType::Immediate | TransactionType::Exclusive => {\n            program.emit_insn(Insn::Transaction {\n                db: crate::MAIN_DB_ID,\n                tx_mode: TransactionMode::Write,\n                schema_cookie: schema.schema_version,\n            });\n            // TODO: Emit transaction instruction on temporary tables when we support them.\n            program.emit_insn(Insn::AutoCommit {\n                auto_commit: false,\n                rollback: false,\n            });\n        }\n        TransactionType::Concurrent => {\n            program.emit_insn(Insn::Transaction {\n                db: crate::MAIN_DB_ID,\n                tx_mode: TransactionMode::Concurrent,\n                schema_cookie: schema.schema_version,\n            });\n            // TODO: Emit transaction instruction on temporary tables when we support them.\n            program.emit_insn(Insn::AutoCommit {\n                auto_commit: false,\n                rollback: false,\n            });\n        }\n    }\n    Ok(())\n}\n\npub fn translate_tx_commit(\n    _tx_name: Option<Name>,\n    schema: &Schema,\n    resolver: &Resolver,\n    program: &mut ProgramBuilder,\n) -> Result<()> {\n    program.extend(&ProgramBuilderOpts {\n        num_cursors: 0,\n        approx_num_insns: 0,\n        approx_num_labels: 0,\n    });\n\n    let cdc_info = program.capture_data_changes_info().as_ref();\n    if cdc_info.is_some_and(|info| info.cdc_version().has_commit_record()) {\n        // Use a dummy table name for prepare_cdc_if_necessary — any name that isn't the\n        // CDC table itself will work.\n        if let Some((cdc_cursor_id, _)) =\n            prepare_cdc_if_necessary(program, schema, \"__tx_commit__\")?\n        {\n            emit_cdc_commit_insns(program, resolver, cdc_cursor_id)?;\n        }\n    }\n\n    program.emit_insn(Insn::AutoCommit {\n        auto_commit: true,\n        rollback: false,\n    });\n    Ok(())\n}\n"
  },
  {
    "path": "core/translate/trigger.rs",
    "content": "use crate::translate::emitter::Resolver;\nuse crate::translate::schema::{emit_schema_entry, SchemaEntryType, SQLITE_TABLEID};\nuse crate::translate::ProgramBuilder;\nuse crate::translate::ProgramBuilderOpts;\nuse crate::util::{escape_sql_string_literal, normalize_ident};\nuse crate::vdbe::builder::CursorType;\nuse crate::vdbe::insn::{Cookie, Insn};\nuse crate::{bail_parse_error, Result};\nuse turso_parser::ast::{self, QualifiedName};\n\n/// Reconstruct SQL string from CREATE TRIGGER AST\n#[allow(clippy::too_many_arguments)]\npub(crate) fn create_trigger_to_sql(\n    temporary: bool,\n    if_not_exists: bool,\n    trigger_name: &QualifiedName,\n    time: Option<ast::TriggerTime>,\n    event: &ast::TriggerEvent,\n    tbl_name: &QualifiedName,\n    for_each_row: bool,\n    when_clause: Option<&ast::Expr>,\n    commands: &[ast::TriggerCmd],\n) -> String {\n    let mut sql = String::new();\n    sql.push_str(\"CREATE\");\n    if temporary {\n        sql.push_str(\" TEMP\");\n    }\n    sql.push_str(\" TRIGGER\");\n    if if_not_exists {\n        sql.push_str(\" IF NOT EXISTS\");\n    }\n    sql.push(' ');\n    sql.push_str(&trigger_name.name.as_ident());\n    sql.push(' ');\n\n    if let Some(t) = time {\n        match t {\n            ast::TriggerTime::Before => sql.push_str(\"BEFORE \"),\n            ast::TriggerTime::After => sql.push_str(\"AFTER \"),\n            ast::TriggerTime::InsteadOf => sql.push_str(\"INSTEAD OF \"),\n        }\n    }\n\n    match event {\n        ast::TriggerEvent::Delete => sql.push_str(\"DELETE\"),\n        ast::TriggerEvent::Insert => sql.push_str(\"INSERT\"),\n        ast::TriggerEvent::Update => sql.push_str(\"UPDATE\"),\n        ast::TriggerEvent::UpdateOf(cols) => {\n            sql.push_str(\"UPDATE OF \");\n            for (i, col) in cols.iter().enumerate() {\n                if i > 0 {\n                    sql.push_str(\", \");\n                }\n                sql.push_str(&col.as_ident());\n            }\n        }\n    }\n\n    sql.push_str(\" ON \");\n    sql.push_str(&tbl_name.name.as_ident());\n    if for_each_row {\n        sql.push_str(\" FOR EACH ROW\");\n    }\n\n    if let Some(when) = when_clause {\n        sql.push_str(\" WHEN \");\n        sql.push_str(&when.to_string());\n    }\n\n    sql.push_str(\" BEGIN\");\n    for cmd in commands {\n        sql.push(' ');\n        sql.push_str(&cmd.to_string());\n        sql.push(';');\n    }\n    sql.push_str(\" END\");\n\n    sql\n}\n\n/// Translate CREATE TRIGGER statement\n#[allow(clippy::too_many_arguments)]\npub fn translate_create_trigger(\n    trigger_name: QualifiedName,\n    resolver: &Resolver,\n    temporary: bool,\n    if_not_exists: bool,\n    time: Option<ast::TriggerTime>,\n    tbl_name: QualifiedName,\n    program: &mut ProgramBuilder,\n    sql: String,\n    commands: &[ast::TriggerCmd],\n    when_clause: Option<&ast::Expr>,\n) -> Result<()> {\n    let database_id = resolver.resolve_database_id(&trigger_name)?;\n    if crate::is_attached_db(database_id) {\n        let schema_cookie = resolver.with_schema(database_id, |s| s.schema_version);\n        program.begin_write_on_database(database_id, schema_cookie);\n    }\n    program.begin_write_operation();\n    let normalized_trigger_name = normalize_ident(trigger_name.name.as_str());\n    let normalized_table_name = normalize_ident(tbl_name.name.as_str());\n\n    // Validate that trigger body does not reference other databases.\n    validate_trigger_no_cross_db_refs(\n        resolver,\n        database_id,\n        &normalized_trigger_name,\n        commands,\n        when_clause,\n    )?;\n\n    if crate::schema::is_system_table(&normalized_table_name) {\n        bail_parse_error!(\"cannot create trigger on system table\");\n    }\n\n    // Check if trigger already exists\n    if resolver.with_schema(database_id, |s| {\n        s.get_trigger(&normalized_trigger_name).is_some()\n    }) {\n        if if_not_exists {\n            return Ok(());\n        }\n        bail_parse_error!(\"Trigger {} already exists\", normalized_trigger_name);\n    }\n\n    // Verify the table exists\n    let table = resolver.with_schema(database_id, |s| s.get_table(&normalized_table_name));\n    let Some(table) = table else {\n        bail_parse_error!(\"no such table: {}\", normalized_table_name);\n    };\n    if table.virtual_table().is_some() {\n        bail_parse_error!(\"cannot create triggers on virtual tables\");\n    }\n\n    if time\n        .as_ref()\n        .is_some_and(|t| *t == ast::TriggerTime::InsteadOf)\n    {\n        bail_parse_error!(\"INSTEAD OF triggers are not supported yet\");\n    }\n\n    if temporary {\n        bail_parse_error!(\"TEMPORARY triggers are not supported yet\");\n    }\n\n    let opts = ProgramBuilderOpts {\n        num_cursors: 1,\n        approx_num_insns: 30,\n        approx_num_labels: 1,\n    };\n    program.extend(&opts);\n\n    // Open cursor to sqlite_schema table\n    let table = resolver.schema().get_btree_table(SQLITE_TABLEID).unwrap();\n    let sqlite_schema_cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(table));\n    program.emit_insn(Insn::OpenWrite {\n        cursor_id: sqlite_schema_cursor_id,\n        root_page: 1i64.into(),\n        db: database_id,\n    });\n\n    // Add the trigger entry to sqlite_schema\n    emit_schema_entry(\n        program,\n        resolver,\n        sqlite_schema_cursor_id,\n        None, // cdc_table_cursor_id, no cdc for triggers\n        SchemaEntryType::Trigger,\n        &normalized_trigger_name,\n        &normalized_table_name,\n        0, // triggers don't have a root page\n        Some(sql),\n    )?;\n\n    // Update schema version\n    let schema_version = resolver.with_schema(database_id, |s| s.schema_version);\n    program.emit_insn(Insn::SetCookie {\n        db: database_id,\n        cookie: Cookie::SchemaVersion,\n        value: (schema_version + 1) as i32,\n        p5: 0,\n    });\n\n    // Parse schema to load the new trigger\n    let escaped_trigger_name = escape_sql_string_literal(&normalized_trigger_name);\n    program.emit_insn(Insn::ParseSchema {\n        db: database_id,\n        where_clause: Some(format!(\"name = '{escaped_trigger_name}'\")),\n    });\n\n    Ok(())\n}\n\n/// Validate that no table or expression reference in a trigger body points to a\n/// database other than the trigger's own database. SQLite forbids this with:\n///   \"trigger X cannot reference objects in database Y\"\nfn validate_trigger_no_cross_db_refs(\n    resolver: &Resolver,\n    trigger_db_id: usize,\n    trigger_name: &str,\n    commands: &[ast::TriggerCmd],\n    when_clause: Option<&ast::Expr>,\n) -> Result<()> {\n    let ctx = CrossDbCheckCtx {\n        resolver,\n        trigger_db_id,\n        trigger_name,\n    };\n\n    if let Some(when) = when_clause {\n        ctx.check_expr(when)?;\n    }\n\n    for cmd in commands {\n        match cmd {\n            ast::TriggerCmd::Insert { select, .. } => {\n                ctx.check_select(select)?;\n            }\n            ast::TriggerCmd::Update {\n                sets,\n                from,\n                where_clause,\n                ..\n            } => {\n                for set in sets {\n                    ctx.check_expr(&set.expr)?;\n                }\n                if let Some(from) = from {\n                    ctx.check_from_clause(from)?;\n                }\n                if let Some(wc) = where_clause {\n                    ctx.check_expr(wc)?;\n                }\n            }\n            ast::TriggerCmd::Delete { where_clause, .. } => {\n                if let Some(wc) = where_clause {\n                    ctx.check_expr(wc)?;\n                }\n            }\n            ast::TriggerCmd::Select(select) => {\n                ctx.check_select(select)?;\n            }\n        }\n    }\n\n    Ok(())\n}\n\nstruct CrossDbCheckCtx<'a> {\n    resolver: &'a Resolver<'a>,\n    trigger_db_id: usize,\n    trigger_name: &'a str,\n}\n\nimpl CrossDbCheckCtx<'_> {\n    fn check_qname(&self, qn: &QualifiedName) -> Result<()> {\n        if let Some(ref db_name) = qn.db_name {\n            let resolved = self.resolver.resolve_database_id(qn)?;\n            if resolved != self.trigger_db_id {\n                bail_parse_error!(\n                    \"trigger {} cannot reference objects in database {}\",\n                    self.trigger_name,\n                    db_name\n                );\n            }\n        }\n        Ok(())\n    }\n\n    /// Check an expression tree for cross-database references.\n    /// Descends into subqueries that walk_expr skips.\n    /// Note: DoublyQualified expressions (e.g. aux.table.column) are NOT checked\n    /// here — they are column references, not table references. SQLite allows them\n    /// at CREATE TRIGGER time and only rejects them at runtime as \"no such column\".\n    fn check_expr(&self, expr: &ast::Expr) -> Result<()> {\n        use crate::translate::expr::WalkControl;\n        crate::translate::expr::walk_expr(expr, &mut |e| -> Result<WalkControl> {\n            match e {\n                // walk_expr doesn't descend into subqueries, so handle them here\n                ast::Expr::Exists(select) | ast::Expr::Subquery(select) => {\n                    self.check_select(select)?;\n                }\n                ast::Expr::InSelect { rhs, .. } => {\n                    self.check_select(rhs)?;\n                }\n                _ => {}\n            }\n            Ok(WalkControl::Continue)\n        })?;\n        Ok(())\n    }\n\n    fn check_select(&self, select: &ast::Select) -> Result<()> {\n        check_select_table_refs(select, &|qn| self.check_qname(qn), &|e| self.check_expr(e))\n    }\n\n    fn check_from_clause(&self, from: &ast::FromClause) -> Result<()> {\n        check_from_clause_refs(from, &|qn| self.check_qname(qn), &|e| self.check_expr(e))\n    }\n}\n\nfn check_select_table_refs(\n    select: &ast::Select,\n    check_qname: &dyn Fn(&QualifiedName) -> Result<()>,\n    check_expr: &dyn Fn(&ast::Expr) -> Result<()>,\n) -> Result<()> {\n    // Check CTEs\n    if let Some(with) = &select.with {\n        for cte in &with.ctes {\n            check_select_table_refs(&cte.select, check_qname, check_expr)?;\n        }\n    }\n\n    check_one_select_refs(&select.body.select, check_qname, check_expr)?;\n\n    for compound in &select.body.compounds {\n        check_one_select_refs(&compound.select, check_qname, check_expr)?;\n    }\n\n    // Check ORDER BY / LIMIT / OFFSET expressions\n    for col in &select.order_by {\n        check_expr(&col.expr)?;\n    }\n    if let Some(limit) = &select.limit {\n        check_expr(&limit.expr)?;\n        if let Some(offset) = &limit.offset {\n            check_expr(offset)?;\n        }\n    }\n\n    Ok(())\n}\n\nfn check_one_select_refs(\n    one_select: &ast::OneSelect,\n    check_qname: &dyn Fn(&QualifiedName) -> Result<()>,\n    check_expr: &dyn Fn(&ast::Expr) -> Result<()>,\n) -> Result<()> {\n    match one_select {\n        ast::OneSelect::Select {\n            columns,\n            from,\n            where_clause,\n            group_by,\n            ..\n        } => {\n            for col in columns {\n                if let ast::ResultColumn::Expr(expr, _) = col {\n                    check_expr(expr)?;\n                }\n            }\n            if let Some(from) = from {\n                check_from_clause_refs(from, check_qname, check_expr)?;\n            }\n            if let Some(wc) = where_clause {\n                check_expr(wc)?;\n            }\n            if let Some(gb) = group_by {\n                for expr in &gb.exprs {\n                    check_expr(expr)?;\n                }\n                if let Some(having) = &gb.having {\n                    check_expr(having)?;\n                }\n            }\n        }\n        ast::OneSelect::Values(rows) => {\n            for row in rows {\n                for expr in row {\n                    check_expr(expr)?;\n                }\n            }\n        }\n    }\n    Ok(())\n}\n\nfn check_from_clause_refs(\n    from: &ast::FromClause,\n    check_qname: &dyn Fn(&QualifiedName) -> Result<()>,\n    check_expr: &dyn Fn(&ast::Expr) -> Result<()>,\n) -> Result<()> {\n    check_select_table_ref(&from.select, check_qname, check_expr)?;\n    for join in &from.joins {\n        check_select_table_ref(&join.table, check_qname, check_expr)?;\n        if let Some(ast::JoinConstraint::On(expr)) = &join.constraint {\n            check_expr(expr)?;\n        }\n    }\n    Ok(())\n}\n\nfn check_select_table_ref(\n    table: &ast::SelectTable,\n    check_qname: &dyn Fn(&QualifiedName) -> Result<()>,\n    check_expr: &dyn Fn(&ast::Expr) -> Result<()>,\n) -> Result<()> {\n    match table {\n        ast::SelectTable::Table(qname, ..) => {\n            check_qname(qname)?;\n        }\n        ast::SelectTable::TableCall(qname, args, _) => {\n            check_qname(qname)?;\n            for arg in args {\n                check_expr(arg)?;\n            }\n        }\n        ast::SelectTable::Select(select, _) => {\n            check_select_table_refs(select, check_qname, check_expr)?;\n        }\n        ast::SelectTable::Sub(from, _) => {\n            check_from_clause_refs(from, check_qname, check_expr)?;\n        }\n    }\n    Ok(())\n}\n\n/// Translate DROP TRIGGER statement\npub fn translate_drop_trigger(\n    resolver: &Resolver,\n    trigger_name: &ast::QualifiedName,\n    if_exists: bool,\n    program: &mut ProgramBuilder,\n) -> Result<()> {\n    let database_id = resolver.resolve_database_id(trigger_name)?;\n    if crate::is_attached_db(database_id) {\n        let schema_cookie = resolver.with_schema(database_id, |s| s.schema_version);\n        program.begin_write_on_database(database_id, schema_cookie);\n    }\n    program.begin_write_operation();\n    let normalized_trigger_name = normalize_ident(trigger_name.name.as_str());\n\n    // Check if trigger exists\n    if resolver.with_schema(database_id, |s| {\n        s.get_trigger(&normalized_trigger_name).is_none()\n    }) {\n        if if_exists {\n            return Ok(());\n        }\n        bail_parse_error!(\"no such trigger: {}\", normalized_trigger_name);\n    }\n\n    let opts = ProgramBuilderOpts {\n        num_cursors: 1,\n        approx_num_insns: 30,\n        approx_num_labels: 1,\n    };\n    program.extend(&opts);\n\n    // Open cursor to sqlite_schema table (structure is the same for all databases)\n    let table = resolver.with_schema(0, |s| s.get_btree_table(SQLITE_TABLEID).unwrap());\n    let sqlite_schema_cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(table));\n    program.emit_insn(Insn::OpenWrite {\n        cursor_id: sqlite_schema_cursor_id,\n        root_page: 1i64.into(),\n        db: database_id,\n    });\n\n    let search_loop_label = program.allocate_label();\n    let skip_non_trigger_label = program.allocate_label();\n    let done_label = program.allocate_label();\n    let rewind_done_label = program.allocate_label();\n\n    // Find and delete the trigger from sqlite_schema\n    program.emit_insn(Insn::Rewind {\n        cursor_id: sqlite_schema_cursor_id,\n        pc_if_empty: rewind_done_label,\n    });\n\n    program.preassign_label_to_next_insn(search_loop_label);\n\n    // Check if this is the trigger we're looking for\n    // sqlite_schema columns: type, name, tbl_name, rootpage, sql\n    // Column 0: type (should be \"trigger\")\n    // Column 1: name (should match trigger_name)\n    let type_reg = program.alloc_register();\n    let name_reg = program.alloc_register();\n    program.emit_insn(Insn::Column {\n        cursor_id: sqlite_schema_cursor_id,\n        column: 0,\n        dest: type_reg,\n        default: None,\n    });\n    program.emit_insn(Insn::Column {\n        cursor_id: sqlite_schema_cursor_id,\n        column: 1,\n        dest: name_reg,\n        default: None,\n    });\n\n    // Check if type == \"trigger\"\n    let type_str_reg = program.emit_string8_new_reg(\"trigger\".to_string());\n    program.emit_insn(Insn::Ne {\n        lhs: type_reg,\n        rhs: type_str_reg,\n        target_pc: skip_non_trigger_label,\n        flags: crate::vdbe::insn::CmpInsFlags::default(),\n        collation: program.curr_collation(),\n    });\n\n    // Check if name matches\n    let trigger_name_str_reg = program.emit_string8_new_reg(normalized_trigger_name.clone());\n    program.emit_insn(Insn::Ne {\n        lhs: name_reg,\n        rhs: trigger_name_str_reg,\n        target_pc: skip_non_trigger_label,\n        flags: crate::vdbe::insn::CmpInsFlags::default(),\n        collation: program.curr_collation(),\n    });\n\n    // Found it! Delete the row\n    program.emit_insn(Insn::Delete {\n        cursor_id: sqlite_schema_cursor_id,\n        table_name: SQLITE_TABLEID.to_string(),\n        is_part_of_update: false,\n    });\n    program.emit_insn(Insn::Goto {\n        target_pc: done_label,\n    });\n\n    program.preassign_label_to_next_insn(skip_non_trigger_label);\n    // Continue to next row\n    program.emit_insn(Insn::Next {\n        cursor_id: sqlite_schema_cursor_id,\n        pc_if_next: search_loop_label,\n    });\n\n    program.preassign_label_to_next_insn(done_label);\n\n    program.preassign_label_to_next_insn(rewind_done_label);\n\n    // Update schema version\n    let schema_version = resolver.with_schema(database_id, |s| s.schema_version);\n    program.emit_insn(Insn::SetCookie {\n        db: database_id,\n        cookie: Cookie::SchemaVersion,\n        value: (schema_version + 1) as i32,\n        p5: 0,\n    });\n\n    program.emit_insn(Insn::DropTrigger {\n        db: database_id,\n        trigger_name: normalized_trigger_name,\n    });\n\n    Ok(())\n}\n"
  },
  {
    "path": "core/translate/trigger_exec.rs",
    "content": "use crate::schema::{BTreeTable, Trigger};\nuse crate::sync::Arc;\nuse crate::translate::expr::WalkControl;\nuse crate::translate::subquery::{\n    emit_non_from_clause_subquery, plan_subqueries_from_trigger_when_clause,\n};\nuse crate::translate::{\n    emitter::Resolver,\n    expr::{self, translate_expr, walk_expr_mut},\n    planner::ROWID_STRS,\n    translate_inner, ProgramBuilder, ProgramBuilderOpts,\n};\nuse crate::util::normalize_ident;\nuse crate::vdbe::affinity::Affinity;\nuse crate::vdbe::insn::Insn;\nuse crate::vdbe::BranchOffset;\nuse crate::HashSet;\nuse crate::{bail_parse_error, QueryMode, Result};\nuse std::num::NonZero;\nuse turso_parser::ast::{self, Expr, TriggerEvent, TriggerTime};\n\n/// Context for trigger execution\n#[derive(Debug)]\npub struct TriggerContext {\n    /// Table the trigger is attached to\n    pub table: Arc<BTreeTable>,\n    /// NEW row registers (for INSERT/UPDATE). The last element is always the rowid.\n    pub new_registers: Option<Vec<usize>>,\n    /// OLD row registers (for UPDATE/DELETE). The last element is always the rowid.\n    pub old_registers: Option<Vec<usize>>,\n    /// Override conflict resolution for statements within this trigger.\n    /// When set, all INSERT/UPDATE statements in the trigger will use this\n    /// conflict resolution instead of their specified OR clause.\n    /// This is needed for UPSERT DO UPDATE triggers where SQLite requires\n    /// that nested OR IGNORE/REPLACE clauses do not suppress errors.\n    pub override_conflict: Option<ast::ResolveType>,\n    /// Whether NEW registers contain encoded custom type values that need decoding.\n    /// True for AFTER triggers (values have been encoded for storage).\n    /// False for BEFORE triggers (values are still user-facing).\n    pub new_encoded: bool,\n}\n\nimpl TriggerContext {\n    pub fn new(\n        table: Arc<BTreeTable>,\n        new_registers: Option<Vec<usize>>,\n        old_registers: Option<Vec<usize>>,\n    ) -> Self {\n        Self {\n            table,\n            new_registers,\n            old_registers,\n            override_conflict: None,\n            new_encoded: false,\n        }\n    }\n\n    /// Create a trigger context for AFTER triggers where NEW values are encoded.\n    pub fn new_after(\n        table: Arc<BTreeTable>,\n        new_registers: Option<Vec<usize>>,\n        old_registers: Option<Vec<usize>>,\n    ) -> Self {\n        Self {\n            table,\n            new_registers,\n            old_registers,\n            override_conflict: None,\n            new_encoded: true,\n        }\n    }\n\n    /// Create a trigger context with a conflict resolution override.\n    /// Used for UPSERT DO UPDATE triggers where nested OR IGNORE/REPLACE\n    /// clauses should not suppress errors.\n    pub fn new_with_override_conflict(\n        table: Arc<BTreeTable>,\n        new_registers: Option<Vec<usize>>,\n        old_registers: Option<Vec<usize>>,\n        override_conflict: ast::ResolveType,\n    ) -> Self {\n        Self {\n            table,\n            new_registers,\n            old_registers,\n            override_conflict: Some(override_conflict),\n            new_encoded: false,\n        }\n    }\n\n    /// Create a trigger context with a conflict resolution override for AFTER triggers.\n    pub fn new_after_with_override_conflict(\n        table: Arc<BTreeTable>,\n        new_registers: Option<Vec<usize>>,\n        old_registers: Option<Vec<usize>>,\n        override_conflict: ast::ResolveType,\n    ) -> Self {\n        Self {\n            table,\n            new_registers,\n            old_registers,\n            override_conflict: Some(override_conflict),\n            new_encoded: true,\n        }\n    }\n}\n\n#[derive(Debug)]\nstruct ParamMap(Vec<NonZero<usize>>);\n\nimpl ParamMap {\n    pub fn len(&self) -> usize {\n        self.0.len()\n    }\n}\n\n/// Context for compiling trigger subprograms - maps NEW/OLD to parameter indices\n#[derive(Debug)]\nstruct TriggerSubprogramContext {\n    /// Map from column index to parameter index for NEW values (1-indexed)\n    new_param_map: Option<ParamMap>,\n    /// Map from column index to parameter index for OLD values (1-indexed)\n    old_param_map: Option<ParamMap>,\n    table: Arc<BTreeTable>,\n    /// Override conflict resolution for statements within this trigger.\n    override_conflict: Option<ast::ResolveType>,\n    /// Database name for the trigger's database (used to qualify unqualified table names in body)\n    db_name: Option<ast::Name>,\n}\n\nfn variable_from_parameter_index(index: NonZero<usize>) -> Expr {\n    Expr::Variable(ast::Variable::indexed(\n        u32::try_from(index.get())\n            .ok()\n            .and_then(std::num::NonZeroU32::new)\n            .expect(\"trigger parameter index must fit into NonZeroU32\"),\n    ))\n}\n\nimpl TriggerSubprogramContext {\n    pub fn get_new_param(&self, idx: usize) -> Option<NonZero<usize>> {\n        self.new_param_map\n            .as_ref()\n            .and_then(|map| map.0.get(idx).copied())\n    }\n\n    pub fn get_new_rowid_param(&self) -> Option<NonZero<usize>> {\n        self.new_param_map\n            .as_ref()\n            .and_then(|map| map.0.last().copied())\n    }\n\n    pub fn get_old_param(&self, idx: usize) -> Option<NonZero<usize>> {\n        self.old_param_map\n            .as_ref()\n            .and_then(|map| map.0.get(idx).copied())\n    }\n\n    pub fn get_old_rowid_param(&self) -> Option<NonZero<usize>> {\n        self.old_param_map\n            .as_ref()\n            .and_then(|map| map.0.last().copied())\n    }\n}\n\n/// Rewrite NEW and OLD references in trigger expressions to use Variable instructions (parameters)\nfn rewrite_trigger_expr_for_subprogram(\n    expr: &mut ast::Expr,\n    ctx: &TriggerSubprogramContext,\n) -> Result<()> {\n    walk_expr_mut(expr, &mut |e: &mut ast::Expr| -> Result<WalkControl> {\n        rewrite_trigger_expr_single_for_subprogram(e, ctx)?;\n        Ok(WalkControl::Continue)\n    })?;\n    Ok(())\n}\n\n/// Rewrite NEW/OLD references in all expressions within an Upsert clause for subprogram\nfn rewrite_upsert_exprs_for_subprogram(\n    upsert: &mut Option<Box<ast::Upsert>>,\n    ctx: &TriggerSubprogramContext,\n) -> Result<()> {\n    let mut current = upsert.as_mut();\n    while let Some(u) = current {\n        if let ast::UpsertDo::Set {\n            ref mut sets,\n            ref mut where_clause,\n        } = u.do_clause\n        {\n            for set in sets.iter_mut() {\n                rewrite_trigger_expr_for_subprogram(&mut set.expr, ctx)?;\n            }\n            if let Some(ref mut wc) = where_clause {\n                rewrite_trigger_expr_for_subprogram(wc, ctx)?;\n            }\n        }\n        if let Some(ref mut idx) = u.index {\n            if let Some(ref mut wc) = idx.where_clause {\n                rewrite_trigger_expr_for_subprogram(wc, ctx)?;\n            }\n        }\n        current = u.next.as_mut();\n    }\n    Ok(())\n}\n\n/// Convert TriggerCmd to Stmt, rewriting NEW/OLD to Variable expressions (for subprogram compilation)\nfn trigger_cmd_to_stmt_for_subprogram(\n    cmd: &ast::TriggerCmd,\n    subprogram_ctx: &TriggerSubprogramContext,\n) -> Result<ast::Stmt> {\n    use ast::{InsertBody, QualifiedName};\n\n    match cmd {\n        ast::TriggerCmd::Insert {\n            or_conflict,\n            tbl_name,\n            col_names,\n            select,\n            upsert,\n            returning,\n        } => {\n            // Rewrite NEW/OLD references in the SELECT\n            let mut select_clone = select.clone();\n            rewrite_expressions_in_select_for_subprogram(&mut select_clone, subprogram_ctx)?;\n\n            // Rewrite NEW/OLD references in the UPSERT clause (if present)\n            let mut upsert_clone = upsert.clone();\n            rewrite_upsert_exprs_for_subprogram(&mut upsert_clone, subprogram_ctx)?;\n\n            let body = InsertBody::Select(select_clone, upsert_clone);\n            // If override_conflict is set (e.g., in UPSERT DO UPDATE context),\n            // use it instead of the command's or_conflict to ensure errors propagate.\n            let effective_or_conflict = subprogram_ctx.override_conflict.or(*or_conflict);\n            Ok(ast::Stmt::Insert {\n                with: None,\n                or_conflict: effective_or_conflict,\n                tbl_name: QualifiedName {\n                    db_name: subprogram_ctx.db_name.clone(),\n                    name: tbl_name.clone(),\n                    alias: None,\n                },\n                columns: col_names.clone(),\n                body,\n                returning: returning.clone(),\n            })\n        }\n        ast::TriggerCmd::Update {\n            or_conflict,\n            tbl_name,\n            sets,\n            from,\n            where_clause,\n        } => {\n            // Rewrite NEW/OLD references in SET clauses and WHERE clause\n            let mut sets_clone = sets.clone();\n            for set in &mut sets_clone {\n                rewrite_trigger_expr_for_subprogram(&mut set.expr, subprogram_ctx)?;\n            }\n\n            let mut where_clause_clone = where_clause.clone();\n            if let Some(ref mut where_expr) = where_clause_clone {\n                rewrite_trigger_expr_for_subprogram(where_expr, subprogram_ctx)?;\n            }\n\n            // If override_conflict is set (e.g., in UPSERT DO UPDATE context),\n            // use it instead of the command's or_conflict to ensure errors propagate.\n            let effective_or_conflict = subprogram_ctx.override_conflict.or(*or_conflict);\n            Ok(ast::Stmt::Update(ast::Update {\n                with: None,\n                or_conflict: effective_or_conflict,\n                tbl_name: QualifiedName {\n                    db_name: subprogram_ctx.db_name.clone(),\n                    name: tbl_name.clone(),\n                    alias: None,\n                },\n                indexed: None,\n                sets: sets_clone,\n                from: from.clone(),\n                where_clause: where_clause_clone,\n                returning: vec![],\n                order_by: vec![],\n                limit: None,\n            }))\n        }\n        ast::TriggerCmd::Delete {\n            tbl_name,\n            where_clause,\n        } => {\n            // Rewrite NEW/OLD references in WHERE clause\n            let mut where_clause_clone = where_clause.clone();\n            if let Some(ref mut where_expr) = where_clause_clone {\n                rewrite_trigger_expr_for_subprogram(where_expr, subprogram_ctx)?;\n            }\n\n            Ok(ast::Stmt::Delete {\n                tbl_name: QualifiedName {\n                    db_name: subprogram_ctx.db_name.clone(),\n                    name: tbl_name.clone(),\n                    alias: None,\n                },\n                where_clause: where_clause_clone,\n                limit: None,\n                returning: vec![],\n                indexed: None,\n                order_by: vec![],\n                with: None,\n            })\n        }\n        ast::TriggerCmd::Select(select) => {\n            // Rewrite NEW/OLD references in the SELECT\n            let mut select_clone = select.clone();\n            rewrite_expressions_in_select_for_subprogram(&mut select_clone, subprogram_ctx)?;\n            Ok(ast::Stmt::Select(select_clone))\n        }\n    }\n}\n\n/// Rewrite NEW/OLD references in all expressions within a SELECT statement for subprogram\nfn rewrite_expressions_in_select_for_subprogram(\n    select: &mut ast::Select,\n    ctx: &TriggerSubprogramContext,\n) -> Result<()> {\n    rewrite_select_expressions(select, &mut |e: &mut ast::Expr| {\n        rewrite_trigger_expr_single_for_subprogram(e, ctx)\n    })\n}\n\n/// Rewrite a single NEW/OLD reference for subprogram (called from walk_expr_mut)\nfn rewrite_trigger_expr_single_for_subprogram(\n    e: &mut ast::Expr,\n    ctx: &TriggerSubprogramContext,\n) -> Result<()> {\n    match e {\n        Expr::Exists(select) | Expr::Subquery(select) => {\n            rewrite_expressions_in_select_for_subprogram(select, ctx)?;\n            return Ok(());\n        }\n        Expr::InSelect { rhs, .. } => {\n            rewrite_expressions_in_select_for_subprogram(rhs, ctx)?;\n            return Ok(());\n        }\n        Expr::Qualified(ns, col) | Expr::DoublyQualified(_, ns, col) => {\n            let ns = normalize_ident(ns.as_str());\n            let col = normalize_ident(col.as_str());\n\n            // Handle NEW.column references\n            if ns.eq_ignore_ascii_case(\"new\") {\n                if let Some(new_params) = &ctx.new_param_map {\n                    if let Some((idx, col_def)) = ctx.table.get_column(&col) {\n                        if col_def.is_rowid_alias() {\n                            *e = variable_from_parameter_index(\n                                ctx.get_new_rowid_param()\n                                    .expect(\"NEW parameters must be provided\"),\n                            );\n                            return Ok(());\n                        }\n                        if idx < new_params.len() {\n                            *e = variable_from_parameter_index(\n                                ctx.get_new_param(idx)\n                                    .expect(\"NEW parameters must be provided\"),\n                            );\n                            return Ok(());\n                        } else {\n                            crate::bail_parse_error!(\"no such column in NEW: {}\", col);\n                        }\n                    }\n                    // Handle NEW.rowid\n                    if ROWID_STRS.iter().any(|s| s.eq_ignore_ascii_case(&col)) {\n                        *e = variable_from_parameter_index(\n                            ctx.get_new_rowid_param()\n                                .expect(\"NEW parameters must be provided\"),\n                        );\n                        return Ok(());\n                    }\n                    bail_parse_error!(\"no such column in NEW: {}\", col);\n                } else {\n                    bail_parse_error!(\n                        \"NEW references are only valid in INSERT and UPDATE triggers\"\n                    );\n                }\n            }\n\n            // Handle OLD.column references\n            if ns.eq_ignore_ascii_case(\"old\") {\n                if let Some(old_params) = &ctx.old_param_map {\n                    if let Some((idx, col_def)) = ctx.table.get_column(&col) {\n                        if col_def.is_rowid_alias() {\n                            *e = variable_from_parameter_index(\n                                ctx.get_old_rowid_param()\n                                    .expect(\"OLD parameters must be provided\"),\n                            );\n                            return Ok(());\n                        }\n                        if idx < old_params.len() {\n                            *e = variable_from_parameter_index(\n                                ctx.get_old_param(idx)\n                                    .expect(\"OLD parameters must be provided\"),\n                            );\n                            return Ok(());\n                        } else {\n                            crate::bail_parse_error!(\"no such column in OLD: {}\", col)\n                        }\n                    }\n                    // Handle OLD.rowid\n                    if ROWID_STRS.iter().any(|s| s.eq_ignore_ascii_case(&col)) {\n                        *e = variable_from_parameter_index(\n                            ctx.get_old_rowid_param()\n                                .expect(\"OLD parameters must be provided\"),\n                        );\n                        return Ok(());\n                    }\n                    bail_parse_error!(\"no such column in OLD: {}\", col);\n                } else {\n                    bail_parse_error!(\n                        \"OLD references are only valid in UPDATE and DELETE triggers\"\n                    );\n                }\n            }\n\n            // If the namespace is neither NEW nor OLD, this can be a regular\n            // table-qualified reference inside the SELECT statement of the\n            // trigger subprogram. Leave it untouched so the normal SELECT\n            // binding/resolution phase can handle it.\n            return Ok(());\n        }\n        _ => {}\n    }\n    Ok(())\n}\n\n/// Execute trigger commands by compiling them as a subprogram and emitting Program instruction\n/// Returns true if there are triggers that will fire.\nfn execute_trigger_commands(\n    program: &mut ProgramBuilder,\n    resolver: &mut Resolver,\n    trigger: &Arc<Trigger>,\n    ctx: &TriggerContext,\n    connection: &Arc<crate::Connection>,\n    database_id: usize,\n    ignore_jump_target: BranchOffset,\n) -> Result<bool> {\n    if connection.trigger_is_compiling(trigger) {\n        // Do not recursively compile the same trigger\n        return Ok(false);\n    }\n    connection.start_trigger_compilation(trigger.clone());\n    // Build parameter mapping: parameters are 1-indexed and sequential\n    // Order: [NEW values..., OLD values..., rowid]\n    // So if we have 2 NEW columns, 2 OLD columns: NEW params are 1,2; OLD params are 3,4; rowid is 5\n    let num_new = ctx.new_registers.as_ref().map(|r| r.len()).unwrap_or(0);\n\n    let new_param_map = ctx\n        .new_registers\n        .as_ref()\n        .map(|new_regs| {\n            (1..=new_regs.len())\n                .map(|i| NonZero::new(i).unwrap())\n                .collect()\n        })\n        .map(ParamMap);\n\n    let old_param_map = ctx\n        .old_registers\n        .as_ref()\n        .map(|old_regs| {\n            (1..=old_regs.len())\n                .map(|i| NonZero::new(i + num_new).unwrap())\n                .collect()\n        })\n        .map(ParamMap);\n\n    // For triggers on attached databases, resolve the database name so unqualified\n    // table references in the trigger body are correctly qualified to the trigger's database.\n    let db_name = if database_id == crate::MAIN_DB_ID {\n        None\n    } else {\n        resolver\n            .get_database_name_by_index(database_id)\n            .map(ast::Name::exact)\n    };\n    let subprogram_ctx = TriggerSubprogramContext {\n        new_param_map,\n        old_param_map,\n        table: ctx.table.clone(),\n        override_conflict: ctx.override_conflict,\n        db_name,\n    };\n    let mut subprogram_builder = ProgramBuilder::new_for_trigger(\n        QueryMode::Normal,\n        program.capture_data_changes_info().clone(),\n        ProgramBuilderOpts {\n            num_cursors: 1,\n            approx_num_insns: 32,\n            approx_num_labels: 2,\n        },\n        trigger.clone(),\n    );\n    // If we have an override_conflict (e.g. from UPSERT DO UPDATE context),\n    // propagate it to the subprogram so that nested trigger firing will also use it.\n    if let Some(override_conflict) = ctx.override_conflict {\n        subprogram_builder.set_trigger_conflict_override(override_conflict);\n    }\n    // Restrict table resolution to the trigger's database during subprogram compilation.\n    let prev_trigger_context = resolver.trigger_context.clone();\n    resolver.set_trigger_context(database_id, trigger.name.clone());\n    let compile_result = (|| -> Result<()> {\n        for command in trigger.commands.iter() {\n            let stmt = trigger_cmd_to_stmt_for_subprogram(command, &subprogram_ctx)?;\n            subprogram_builder.prologue();\n            translate_inner(\n                stmt,\n                resolver,\n                &mut subprogram_builder,\n                connection,\n                \"trigger subprogram\",\n            )?;\n        }\n        Ok(())\n    })();\n    // Restore previous trigger context (supports nested triggers).\n    resolver.trigger_context = prev_trigger_context;\n    compile_result?;\n    subprogram_builder.epilogue(resolver.schema());\n    let built_subprogram =\n        subprogram_builder.build(connection.clone(), true, \"trigger subprogram\")?;\n\n    let mut params = Vec::with_capacity(\n        ctx.new_registers.as_ref().map(|r| r.len()).unwrap_or(0)\n            + ctx.old_registers.as_ref().map(|r| r.len()).unwrap_or(0),\n    );\n    if let Some(new_regs) = &ctx.new_registers {\n        params.extend(\n            new_regs\n                .iter()\n                .copied()\n                .map(|reg_idx| crate::types::Value::from_i64(reg_idx as i64)),\n        );\n    }\n    if let Some(old_regs) = &ctx.old_registers {\n        params.extend(\n            old_regs\n                .iter()\n                .copied()\n                .map(|reg_idx| crate::types::Value::from_i64(reg_idx as i64)),\n        );\n    }\n\n    program.emit_insn(Insn::Program {\n        params,\n        program: built_subprogram.prepared().clone(),\n        ignore_jump_target,\n    });\n    connection.end_trigger_compilation();\n\n    Ok(true)\n}\n\n/// Check if there are any triggers for a given event (regardless of time).\n/// This is used during plan preparation to determine if materialization is needed.\npub fn has_relevant_triggers_type_only(\n    schema: &crate::schema::Schema,\n    event: TriggerEvent,\n    updated_column_indices: Option<&HashSet<usize>>,\n    table: &BTreeTable,\n) -> bool {\n    let mut triggers = schema.get_triggers_for_table(table.name.as_str());\n\n    // Filter triggers by event\n    triggers.any(|trigger| {\n        // Check event matches\n        let event_matches = match (&trigger.event, &event) {\n            (TriggerEvent::Delete, TriggerEvent::Delete) => true,\n            (TriggerEvent::Insert, TriggerEvent::Insert) => true,\n            (TriggerEvent::Update, TriggerEvent::Update) => true,\n            (TriggerEvent::UpdateOf(trigger_cols), TriggerEvent::Update) => {\n                // For UPDATE OF, we need to check if any of the specified columns\n                // are in the UPDATE SET clause\n                let updated_cols =\n                    updated_column_indices.expect(\"UPDATE should contain some updated columns\");\n                // Check if any of the trigger's specified columns are being updated\n                trigger_cols.iter().any(|col_name| {\n                    let normalized_col = normalize_ident(col_name.as_str());\n                    if let Some((col_idx, _)) = table.get_column(&normalized_col) {\n                        updated_cols.contains(&col_idx)\n                    } else {\n                        // Column doesn't exist - according to SQLite docs, unrecognized\n                        // column names in UPDATE OF are silently ignored\n                        false\n                    }\n                })\n            }\n            _ => false,\n        };\n\n        event_matches\n    })\n}\n\n/// Check if there are any triggers for a given event (regardless of time).\n/// This is used during plan preparation to determine if materialization is needed.\npub fn get_relevant_triggers_type_and_time<'a>(\n    schema: &'a crate::schema::Schema,\n    event: TriggerEvent,\n    time: TriggerTime,\n    updated_column_indices: Option<HashSet<usize>>,\n    table: &'a BTreeTable,\n) -> impl Iterator<Item = Arc<Trigger>> + 'a + Clone {\n    let triggers = schema.get_triggers_for_table(table.name.as_str());\n\n    // Filter triggers by event\n    triggers\n        .filter(move |trigger| -> bool {\n            // Check event matches\n            let event_matches = match (&trigger.event, &event) {\n                (TriggerEvent::Delete, TriggerEvent::Delete) => true,\n                (TriggerEvent::Insert, TriggerEvent::Insert) => true,\n                (TriggerEvent::Update, TriggerEvent::Update) => true,\n                (TriggerEvent::UpdateOf(trigger_cols), TriggerEvent::Update) => {\n                    // For UPDATE OF, we need to check if any of the specified columns\n                    // are in the UPDATE SET clause\n                    if let Some(ref updated_cols) = updated_column_indices {\n                        // Check if any of the trigger's specified columns are being updated\n                        trigger_cols.iter().any(|col_name| {\n                            let normalized_col = normalize_ident(col_name.as_str());\n                            if let Some((col_idx, _)) = table.get_column(&normalized_col) {\n                                updated_cols.contains(&col_idx)\n                            } else {\n                                // Column doesn't exist - according to SQLite docs, unrecognized\n                                // column names in UPDATE OF are silently ignored\n                                false\n                            }\n                        })\n                    } else {\n                        false\n                    }\n                }\n                _ => false,\n            };\n\n            if !event_matches {\n                return false;\n            }\n\n            trigger.time == time\n        })\n        .cloned()\n}\n\npub fn fire_trigger(\n    program: &mut ProgramBuilder,\n    resolver: &mut Resolver,\n    trigger: Arc<Trigger>,\n    ctx: &TriggerContext,\n    connection: &Arc<crate::Connection>,\n    database_id: usize,\n    ignore_jump_target: BranchOffset,\n) -> Result<()> {\n    // Decode custom type registers so trigger bodies see user-facing values,\n    // not raw encoded blobs from disk.\n    // - OLD registers always come from cursor reads → always encoded → always decode\n    // - NEW registers are only encoded for AFTER triggers (post-encode) → decode when new_encoded\n    let decoded_ctx = decode_trigger_registers(program, resolver, ctx)?;\n    // Apply column affinity to copies of NEW values so that both WHEN clauses\n    // and trigger bodies see affinity-applied values (e.g., integer 42 becomes\n    // real 42.0 for REAL columns). SQLite applies affinity before trigger\n    // evaluation via OP_Affinity + OP_Copy before OP_Program.\n    let affinity_ctx = apply_new_column_affinity(program, &decoded_ctx)?;\n    let ctx = &affinity_ctx;\n\n    let saved_register_affinities = std::mem::take(&mut resolver.register_affinities);\n    populate_trigger_register_affinities(resolver, ctx);\n    let result = (|| -> Result<()> {\n        // Evaluate WHEN clause if present\n        if let Some(mut when_expr) = trigger.when_clause.clone() {\n            // Rewrite NEW/OLD references in WHEN clause to use registers\n            rewrite_trigger_expr_for_when_clause(&mut when_expr, &ctx.table, ctx)?;\n\n            // Plan and emit any subqueries in the WHEN clause (e.g. IN (SELECT ...), EXISTS, scalar subqueries).\n            // This transforms InSelect/Exists/Subquery nodes into SubqueryResult nodes that translate_expr can handle.\n            let mut subqueries = Vec::new();\n            plan_subqueries_from_trigger_when_clause(\n                program,\n                &mut subqueries,\n                &mut when_expr,\n                resolver,\n                connection,\n            )?;\n            // Emit the planned subqueries so their results are available when we evaluate the WHEN expression.\n            // Always treat these as correlated (no `Once` caching) because the WHEN clause is evaluated\n            // per-row, and trigger bodies may modify the tables referenced by the subquery between evaluations.\n            for subquery in &mut subqueries {\n                let plan = subquery.consume_plan(crate::translate::plan::EvalAt::BeforeLoop);\n                emit_non_from_clause_subquery(\n                    program,\n                    resolver,\n                    *plan,\n                    &subquery.query_type,\n                    true, // always re-evaluate: trigger WHEN is checked per-row\n                    false,\n                )?;\n            }\n\n            let when_reg = program.alloc_register();\n            translate_expr(program, None, &when_expr, when_reg, resolver)?;\n\n            let skip_label = program.allocate_label();\n            program.emit_insn(Insn::IfNot {\n                reg: when_reg,\n                jump_if_null: true,\n                target_pc: skip_label,\n            });\n\n            // Execute trigger commands if WHEN clause is true\n            execute_trigger_commands(\n                program,\n                resolver,\n                &trigger,\n                ctx,\n                connection,\n                database_id,\n                ignore_jump_target,\n            )?;\n\n            program.preassign_label_to_next_insn(skip_label);\n        } else {\n            // No WHEN clause - always execute\n            execute_trigger_commands(\n                program,\n                resolver,\n                &trigger,\n                ctx,\n                connection,\n                database_id,\n                ignore_jump_target,\n            )?;\n        }\n\n        Ok(())\n    })();\n    resolver.register_affinities = saved_register_affinities;\n    result\n}\n\n/// Decode encoded custom type registers in a TriggerContext.\n/// OLD registers are always decoded (they always come from cursor reads on disk).\n/// NEW registers are decoded only when `ctx.new_encoded` is true (AFTER triggers).\nfn decode_trigger_registers(\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    ctx: &TriggerContext,\n) -> Result<TriggerContext> {\n    if !ctx.table.is_strict {\n        // Non-STRICT tables never have custom type encoding\n        return Ok(TriggerContext {\n            table: ctx.table.clone(),\n            new_registers: ctx.new_registers.clone(),\n            old_registers: ctx.old_registers.clone(),\n            override_conflict: ctx.override_conflict,\n            new_encoded: false,\n        });\n    }\n\n    let columns = &ctx.table.columns;\n\n    let decoded_new = if ctx.new_encoded {\n        if let Some(new_regs) = &ctx.new_registers {\n            let rowid_reg = *new_regs.last().expect(\"NEW registers must include rowid\");\n            Some(expr::emit_trigger_decode_registers(\n                program,\n                resolver,\n                columns,\n                &|i| new_regs[i],\n                rowid_reg,\n                true, // is_strict\n            )?)\n        } else {\n            None\n        }\n    } else {\n        ctx.new_registers.clone()\n    };\n\n    let decoded_old = if let Some(old_regs) = &ctx.old_registers {\n        let rowid_reg = *old_regs.last().expect(\"OLD registers must include rowid\");\n        Some(expr::emit_trigger_decode_registers(\n            program,\n            resolver,\n            columns,\n            &|i| old_regs[i],\n            rowid_reg,\n            true, // is_strict\n        )?)\n    } else {\n        None\n    };\n\n    Ok(TriggerContext {\n        table: ctx.table.clone(),\n        new_registers: decoded_new,\n        old_registers: decoded_old,\n        override_conflict: ctx.override_conflict,\n        new_encoded: false, // decoded now\n    })\n}\n\n/// Apply column affinity to copies of NEW registers for non-strict tables.\n/// This ensures both WHEN clauses and trigger bodies see affinity-applied values\n/// (e.g., integer 42 becomes real 42.0 for REAL columns), matching SQLite's behavior.\nfn apply_new_column_affinity(\n    program: &mut ProgramBuilder,\n    ctx: &TriggerContext,\n) -> Result<TriggerContext> {\n    let new_registers = if let Some(new_regs) = &ctx.new_registers {\n        let num_cols = ctx.table.columns.len();\n        if !ctx.table.is_strict && num_cols > 0 {\n            let affinities: String = ctx\n                .table\n                .columns\n                .iter()\n                .map(|c| c.affinity().aff_mask())\n                .collect();\n            if affinities.chars().any(|c| c != Affinity::Blob.aff_mask()) {\n                let temp_start = program.alloc_registers(num_cols);\n                for (i, &reg) in new_regs.iter().take(num_cols).enumerate() {\n                    program.emit_insn(Insn::Copy {\n                        src_reg: reg,\n                        dst_reg: temp_start + i,\n                        extra_amount: 0,\n                    });\n                }\n                if let Ok(count) = std::num::NonZeroUsize::try_from(num_cols) {\n                    program.emit_insn(Insn::Affinity {\n                        start_reg: temp_start,\n                        count,\n                        affinities,\n                    });\n                }\n                let mut regs: Vec<usize> = (temp_start..temp_start + num_cols).collect();\n                // Preserve the rowid register (always last in the NEW registers list)\n                if new_regs.len() > num_cols {\n                    regs.push(*new_regs.last().unwrap());\n                }\n                Some(regs)\n            } else {\n                Some(new_regs.clone())\n            }\n        } else {\n            Some(new_regs.clone())\n        }\n    } else {\n        None\n    };\n    Ok(TriggerContext {\n        table: ctx.table.clone(),\n        new_registers,\n        old_registers: ctx.old_registers.clone(),\n        override_conflict: ctx.override_conflict,\n        new_encoded: ctx.new_encoded,\n    })\n}\n\nfn populate_trigger_register_affinities(resolver: &mut Resolver, ctx: &TriggerContext) {\n    populate_trigger_row_register_affinities(resolver, &ctx.table, ctx.new_registers.as_deref());\n    populate_trigger_row_register_affinities(resolver, &ctx.table, ctx.old_registers.as_deref());\n}\n\nfn populate_trigger_row_register_affinities(\n    resolver: &mut Resolver,\n    table: &BTreeTable,\n    row_registers: Option<&[usize]>,\n) {\n    let Some(registers) = row_registers else {\n        return;\n    };\n\n    for (idx, column) in table.columns.iter().enumerate() {\n        let affinity = if column.is_rowid_alias() {\n            Affinity::Integer\n        } else {\n            column.affinity_with_strict(table.is_strict)\n        };\n        if let Some(&register) = registers.get(idx) {\n            resolver.register_affinities.insert(register, affinity);\n        }\n    }\n\n    if let Some(&rowid_register) = registers.last() {\n        resolver\n            .register_affinities\n            .insert(rowid_register, Affinity::Integer);\n    }\n}\n\n/// Rewrite NEW/OLD references in WHEN clause expressions (uses Register expressions, not Variable)\nfn rewrite_trigger_expr_for_when_clause(\n    expr: &mut ast::Expr,\n    table: &BTreeTable,\n    ctx: &TriggerContext,\n) -> Result<()> {\n    walk_expr_mut(expr, &mut |e: &mut ast::Expr| -> Result<WalkControl> {\n        rewrite_trigger_expr_single_for_when_clause(e, table, ctx, false)?;\n        Ok(WalkControl::Continue)\n    })?;\n    Ok(())\n}\n\n/// Rewrite NEW/OLD references in all expressions within a SELECT statement for trigger WHEN clauses.\nfn rewrite_expressions_in_select_for_when_clause(\n    select: &mut ast::Select,\n    table: &BTreeTable,\n    ctx: &TriggerContext,\n) -> Result<()> {\n    rewrite_select_expressions(select, &mut |e: &mut ast::Expr| {\n        rewrite_trigger_expr_single_for_when_clause(e, table, ctx, true)\n    })\n}\n\n/// Rewrite all expressions in a SELECT tree, including CTEs, compounds, ORDER BY,\n/// LIMIT/OFFSET, FROM/JOIN subqueries, and window clauses.\nfn rewrite_select_expressions<F>(select: &mut ast::Select, rewrite_expr: &mut F) -> Result<()>\nwhere\n    F: FnMut(&mut ast::Expr) -> Result<()>,\n{\n    // Rewrite WITH clause (CTEs)\n    if let Some(with_clause) = &mut select.with {\n        for cte in &mut with_clause.ctes {\n            rewrite_select_expressions(&mut cte.select, rewrite_expr)?;\n        }\n    }\n\n    rewrite_one_select_expressions(&mut select.body.select, rewrite_expr)?;\n\n    // Rewrite compound SELECT arms (UNION/EXCEPT/INTERSECT)\n    for compound in &mut select.body.compounds {\n        rewrite_one_select_expressions(&mut compound.select, rewrite_expr)?;\n    }\n\n    // Rewrite top-level ORDER BY\n    for sorted_col in &mut select.order_by {\n        rewrite_expression_tree(&mut sorted_col.expr, rewrite_expr)?;\n    }\n\n    // Rewrite top-level LIMIT/OFFSET\n    if let Some(limit) = &mut select.limit {\n        rewrite_expression_tree(&mut limit.expr, rewrite_expr)?;\n        if let Some(offset) = &mut limit.offset {\n            rewrite_expression_tree(offset, rewrite_expr)?;\n        }\n    }\n\n    Ok(())\n}\n\nfn rewrite_one_select_expressions<F>(\n    one_select: &mut ast::OneSelect,\n    rewrite_expr: &mut F,\n) -> Result<()>\nwhere\n    F: FnMut(&mut ast::Expr) -> Result<()>,\n{\n    match one_select {\n        ast::OneSelect::Select {\n            columns,\n            from,\n            where_clause,\n            group_by,\n            window_clause,\n            ..\n        } => {\n            for col in columns {\n                if let ast::ResultColumn::Expr(expr, _) = col {\n                    rewrite_expression_tree(expr, rewrite_expr)?;\n                }\n            }\n\n            if let Some(from_clause) = from {\n                rewrite_from_clause_expressions(from_clause, rewrite_expr)?;\n            }\n\n            if let Some(where_expr) = where_clause {\n                rewrite_expression_tree(where_expr, rewrite_expr)?;\n            }\n\n            if let Some(group_by) = group_by {\n                for expr in &mut group_by.exprs {\n                    rewrite_expression_tree(expr, rewrite_expr)?;\n                }\n                if let Some(having_expr) = &mut group_by.having {\n                    rewrite_expression_tree(having_expr, rewrite_expr)?;\n                }\n            }\n\n            for window_def in window_clause {\n                rewrite_window_expressions(&mut window_def.window, rewrite_expr)?;\n            }\n        }\n        ast::OneSelect::Values(values) => {\n            for row in values {\n                for expr in row {\n                    rewrite_expression_tree(expr, rewrite_expr)?;\n                }\n            }\n        }\n    }\n\n    Ok(())\n}\n\nfn rewrite_from_clause_expressions<F>(\n    from_clause: &mut ast::FromClause,\n    rewrite_expr: &mut F,\n) -> Result<()>\nwhere\n    F: FnMut(&mut ast::Expr) -> Result<()>,\n{\n    rewrite_select_table_expressions(&mut from_clause.select, rewrite_expr)?;\n\n    for join in &mut from_clause.joins {\n        rewrite_select_table_expressions(&mut join.table, rewrite_expr)?;\n        if let Some(ast::JoinConstraint::On(expr)) = &mut join.constraint {\n            rewrite_expression_tree(expr, rewrite_expr)?;\n        }\n    }\n\n    Ok(())\n}\n\nfn rewrite_select_table_expressions<F>(\n    select_table: &mut ast::SelectTable,\n    rewrite_expr: &mut F,\n) -> Result<()>\nwhere\n    F: FnMut(&mut ast::Expr) -> Result<()>,\n{\n    match select_table {\n        ast::SelectTable::Table(..) => {}\n        ast::SelectTable::TableCall(_, args, _) => {\n            for arg in args {\n                rewrite_expression_tree(arg, rewrite_expr)?;\n            }\n        }\n        ast::SelectTable::Select(select, _) => {\n            rewrite_select_expressions(select, rewrite_expr)?;\n        }\n        ast::SelectTable::Sub(from_clause, _) => {\n            rewrite_from_clause_expressions(from_clause, rewrite_expr)?;\n        }\n    }\n    Ok(())\n}\n\nfn rewrite_window_expressions<F>(window: &mut ast::Window, rewrite_expr: &mut F) -> Result<()>\nwhere\n    F: FnMut(&mut ast::Expr) -> Result<()>,\n{\n    for expr in &mut window.partition_by {\n        rewrite_expression_tree(expr, rewrite_expr)?;\n    }\n\n    for sorted_col in &mut window.order_by {\n        rewrite_expression_tree(&mut sorted_col.expr, rewrite_expr)?;\n    }\n\n    if let Some(frame_clause) = &mut window.frame_clause {\n        rewrite_frame_bound_expressions(&mut frame_clause.start, rewrite_expr)?;\n        if let Some(end) = &mut frame_clause.end {\n            rewrite_frame_bound_expressions(end, rewrite_expr)?;\n        }\n    }\n\n    Ok(())\n}\n\nfn rewrite_frame_bound_expressions<F>(\n    frame_bound: &mut ast::FrameBound,\n    rewrite_expr: &mut F,\n) -> Result<()>\nwhere\n    F: FnMut(&mut ast::Expr) -> Result<()>,\n{\n    match frame_bound {\n        ast::FrameBound::Following(expr) | ast::FrameBound::Preceding(expr) => {\n            rewrite_expression_tree(expr, rewrite_expr)?;\n        }\n        ast::FrameBound::CurrentRow\n        | ast::FrameBound::UnboundedFollowing\n        | ast::FrameBound::UnboundedPreceding => {}\n    }\n    Ok(())\n}\n\nfn rewrite_expression_tree<F>(expr: &mut ast::Expr, rewrite_expr: &mut F) -> Result<()>\nwhere\n    F: FnMut(&mut ast::Expr) -> Result<()>,\n{\n    walk_expr_mut(\n        expr,\n        &mut |e: &mut ast::Expr| -> Result<expr::WalkControl> {\n            rewrite_expr(e)?;\n            Ok(WalkControl::Continue)\n        },\n    )?;\n\n    Ok(())\n}\n\nfn rewrite_trigger_expr_single_for_when_clause(\n    expr: &mut ast::Expr,\n    table: &BTreeTable,\n    ctx: &TriggerContext,\n    allow_non_trigger_qualified: bool,\n) -> Result<()> {\n    match expr {\n        // Bare column references are not valid in trigger WHEN clauses.\n        // Per SQLite docs, columns must be qualified with NEW or OLD.\n        Expr::Id(name) if !allow_non_trigger_qualified => {\n            let ident = normalize_ident(name.as_str());\n            if table.get_column(&ident).is_some()\n                || ROWID_STRS.iter().any(|s| s.eq_ignore_ascii_case(&ident))\n            {\n                crate::bail_parse_error!(\"no such column: {}\", ident);\n            }\n            return Ok(());\n        }\n        Expr::Exists(select) | Expr::Subquery(select) => {\n            rewrite_expressions_in_select_for_when_clause(select, table, ctx)?;\n            return Ok(());\n        }\n        Expr::InSelect { rhs, .. } => {\n            rewrite_expressions_in_select_for_when_clause(rhs, table, ctx)?;\n            return Ok(());\n        }\n        Expr::Qualified(ns, col) | Expr::DoublyQualified(_, ns, col) => {\n            let ns = normalize_ident(ns.as_str());\n            let col = normalize_ident(col.as_str());\n\n            // Handle NEW.column references\n            if ns.eq_ignore_ascii_case(\"new\") {\n                if let Some(new_regs) = &ctx.new_registers {\n                    if let Some((idx, col_def)) = table.get_column(&col) {\n                        if col_def.is_rowid_alias() {\n                            // Rowid alias columns map to the rowid register (last element)\n                            *expr = Expr::Register(\n                                *new_regs.last().expect(\"NEW registers must be provided\"),\n                            );\n                            return Ok(());\n                        }\n                        if idx < new_regs.len() {\n                            *expr = Expr::Register(new_regs[idx]);\n                            return Ok(());\n                        }\n                    }\n                    // Handle NEW.rowid\n                    if ROWID_STRS.iter().any(|s| s.eq_ignore_ascii_case(&col)) {\n                        *expr = Expr::Register(\n                            *ctx.new_registers\n                                .as_ref()\n                                .expect(\"NEW registers must be provided\")\n                                .last()\n                                .expect(\"NEW registers must be provided\"),\n                        );\n                        return Ok(());\n                    }\n                    bail_parse_error!(\"no such column in NEW: {}\", col);\n                } else {\n                    bail_parse_error!(\n                        \"NEW references are only valid in INSERT and UPDATE triggers\"\n                    );\n                }\n            }\n\n            // Handle OLD.column references\n            if ns.eq_ignore_ascii_case(\"old\") {\n                if let Some(old_regs) = &ctx.old_registers {\n                    if let Some((idx, _)) = table.get_column(&col) {\n                        if idx < old_regs.len() {\n                            *expr = Expr::Register(old_regs[idx]);\n                            return Ok(());\n                        }\n                    }\n                    // Handle OLD.rowid\n                    if ROWID_STRS.iter().any(|s| s.eq_ignore_ascii_case(&col)) {\n                        *expr = Expr::Register(\n                            *ctx.old_registers\n                                .as_ref()\n                                .expect(\"OLD registers must be provided\")\n                                .last()\n                                .expect(\"OLD registers must be provided\"),\n                        );\n                        return Ok(());\n                    }\n                    bail_parse_error!(\"no such column in OLD: {}\", col);\n                } else {\n                    bail_parse_error!(\n                        \"OLD references are only valid in UPDATE and DELETE triggers\"\n                    );\n                }\n            }\n\n            if !allow_non_trigger_qualified {\n                bail_parse_error!(\"no such column: {ns}.{col}\");\n            }\n        }\n        _ => {}\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "core/translate/update.rs",
    "content": "use crate::sync::Arc;\nuse rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};\n\nuse crate::schema::ROWID_SENTINEL;\nuse crate::translate::emitter::Resolver;\nuse crate::translate::expr::{bind_and_rewrite_expr, BindingBehavior};\nuse crate::translate::expression_index::expression_index_column_usage;\nuse crate::translate::plan::{Operation, Scan};\nuse crate::translate::planner::{parse_limit, ROWID_STRS};\nuse crate::{\n    bail_parse_error,\n    schema::{Schema, Table},\n    util::normalize_ident,\n    vdbe::builder::{ProgramBuilder, ProgramBuilderOpts},\n    CaptureDataChangesExt, Connection,\n};\nuse turso_parser::ast::{self, Expr, SortOrder};\n\nuse super::emitter::emit_program;\nuse super::expr::process_returning_clause;\nuse super::optimizer::optimize_plan;\nuse super::plan::{\n    ColumnUsedMask, DmlSafety, IterationDirection, JoinedTable, Plan, TableReferences, UpdatePlan,\n};\nuse super::planner::{parse_where, plan_ctes_as_outer_refs};\nuse super::subquery::{\n    plan_subqueries_from_returning, plan_subqueries_from_select_plan,\n    plan_subqueries_from_set_clauses, plan_subqueries_from_where_clause,\n};\n/*\n* Update is simple. By default we scan the table, and for each row, we check the WHERE\n* clause. If it evaluates to true, we build the new record with the updated value and insert.\n*\n* EXAMPLE:\n*\nsqlite> explain update t set a = 100 where b = 5;\naddr  opcode         p1    p2    p3    p4             p5  comment\n----  -------------  ----  ----  ----  -------------  --  -------------\n0     Init           0     16    0                    0   Start at 16\n1     Null           0     1     2                    0   r[1..2]=NULL\n2     Noop           1     0     1                    0\n3     OpenWrite      0     2     0     3              0   root=2 iDb=0; t\n4     Rewind         0     15    0                    0\n5       Column         0     1     6                    0   r[6]= cursor 0 column 1\n6       Ne             7     14    6     BINARY-8       81  if r[6]!=r[7] goto 14\n7       Rowid          0     2     0                    0   r[2]= rowid of 0\n8       IsNull         2     15    0                    0   if r[2]==NULL goto 15\n9       Integer        100   3     0                    0   r[3]=100\n10      Column         0     1     4                    0   r[4]= cursor 0 column 1\n11      Column         0     2     5                    0   r[5]= cursor 0 column 2\n12      MakeRecord     3     3     1                    0   r[1]=mkrec(r[3..5])\n13      Insert         0     1     2     t              7   intkey=r[2] data=r[1]\n14    Next           0     5     0                    1\n15    Halt           0     0     0                    0\n16    Transaction    0     1     1     0              1   usesStmtJournal=0\n17    Integer        5     7     0                    0   r[7]=5\n18    Goto           0     1     0                    0\n*/\npub fn translate_update(\n    body: ast::Update,\n    resolver: &Resolver,\n    program: &mut ProgramBuilder,\n    connection: &Arc<crate::Connection>,\n) -> crate::Result<()> {\n    let mut plan = prepare_update_plan(program, resolver, body, connection, false)?;\n\n    // Plan subqueries in the WHERE clause and SET clause\n    if let Plan::Update(ref mut update_plan) = plan {\n        if let Some(ref mut ephemeral_plan) = update_plan.ephemeral_plan {\n            // When using ephemeral plan (key columns are being updated), subqueries are in the ephemeral_plan's WHERE\n            plan_subqueries_from_select_plan(program, ephemeral_plan, resolver, connection)?;\n        } else {\n            // Normal path: subqueries are in the UPDATE plan's WHERE\n            plan_subqueries_from_where_clause(\n                program,\n                &mut update_plan.non_from_clause_subqueries,\n                &mut update_plan.table_references,\n                &mut update_plan.where_clause,\n                resolver,\n                connection,\n            )?;\n        }\n        // Plan subqueries in the SET clause (e.g. UPDATE t SET col = (SELECT ...))\n        plan_subqueries_from_set_clauses(\n            program,\n            &mut update_plan.non_from_clause_subqueries,\n            &mut update_plan.table_references,\n            &mut update_plan.set_clauses,\n            resolver,\n            connection,\n        )?;\n    }\n\n    optimize_plan(program, &mut plan, resolver)?;\n\n    if let Plan::Update(ref update_plan) = plan {\n        super::stmt_journal::set_update_stmt_journal_flags(\n            program,\n            update_plan,\n            resolver,\n            connection,\n        )?;\n    }\n\n    let opts = ProgramBuilderOpts {\n        num_cursors: 1,\n        approx_num_insns: 20,\n        approx_num_labels: 4,\n    };\n    program.extend(&opts);\n    emit_program(connection, resolver, program, plan, |_| {})?;\n    Ok(())\n}\n\npub fn translate_update_for_schema_change(\n    body: ast::Update,\n    resolver: &Resolver,\n    program: &mut ProgramBuilder,\n    connection: &Arc<crate::Connection>,\n    ddl_query: &str,\n    after: impl FnOnce(&mut ProgramBuilder),\n) -> crate::Result<()> {\n    let mut plan = prepare_update_plan(program, resolver, body, connection, true)?;\n\n    if let Plan::Update(update_plan) = &mut plan {\n        if program.capture_data_changes_info().has_updates() {\n            update_plan.cdc_update_alter_statement = Some(ddl_query.to_string());\n        }\n\n        // Plan subqueries in the WHERE clause\n        if let Some(ref mut ephemeral_plan) = update_plan.ephemeral_plan {\n            plan_subqueries_from_select_plan(program, ephemeral_plan, resolver, connection)?;\n        } else {\n            plan_subqueries_from_where_clause(\n                program,\n                &mut update_plan.non_from_clause_subqueries,\n                &mut update_plan.table_references,\n                &mut update_plan.where_clause,\n                resolver,\n                connection,\n            )?;\n        }\n        // Plan subqueries in the SET clause (e.g. UPDATE t SET col = (SELECT ...))\n        plan_subqueries_from_set_clauses(\n            program,\n            &mut update_plan.non_from_clause_subqueries,\n            &mut update_plan.table_references,\n            &mut update_plan.set_clauses,\n            resolver,\n            connection,\n        )?;\n    }\n\n    optimize_plan(program, &mut plan, resolver)?;\n    let opts = ProgramBuilderOpts {\n        num_cursors: 1,\n        approx_num_insns: 20,\n        approx_num_labels: 4,\n    };\n    program.extend(&opts);\n    emit_program(connection, resolver, program, plan, after)?;\n    Ok(())\n}\n\nfn validate_update(\n    schema: &Schema,\n    body: &ast::Update,\n    table_name: &str,\n    is_internal_schema_change: bool,\n    conn: &Arc<Connection>,\n) -> crate::Result<()> {\n    // Check if this is a system table that should be protected from direct writes\n    if !is_internal_schema_change\n        && !conn.is_nested_stmt()\n        && !conn.is_mvcc_bootstrap_connection()\n        && !crate::schema::can_write_to_table(table_name)\n    {\n        crate::bail_parse_error!(\"table {} may not be modified\", table_name);\n    }\n    if body.from.is_some() {\n        bail_parse_error!(\"FROM clause is not supported in UPDATE\");\n    }\n    if !body.order_by.is_empty() {\n        bail_parse_error!(\"ORDER BY is not supported in UPDATE\");\n    }\n    // Check if this is a materialized view\n    if schema.is_materialized_view(table_name) {\n        bail_parse_error!(\"cannot modify materialized view {}\", table_name);\n    }\n\n    // Check if this table has any incompatible dependent views\n    let incompatible_views = schema.has_incompatible_dependent_views(table_name);\n    if !incompatible_views.is_empty() {\n        use crate::incremental::compiler::DBSP_CIRCUIT_VERSION;\n        bail_parse_error!(\n            \"Cannot UPDATE table '{}' because it has incompatible dependent materialized view(s): {}. \\n\\\n             These views were created with a different DBSP version than the current version ({}). \\n\\\n             Please DROP and recreate the view(s) before modifying this table.\",\n            table_name,\n            incompatible_views.join(\", \"),\n            DBSP_CIRCUIT_VERSION\n        );\n    }\n    Ok(())\n}\n\npub fn prepare_update_plan(\n    program: &mut ProgramBuilder,\n    resolver: &Resolver,\n    mut body: ast::Update,\n    connection: &Arc<crate::Connection>,\n    is_internal_schema_change: bool,\n) -> crate::Result<Plan> {\n    let database_id = resolver.resolve_database_id(&body.tbl_name)?;\n    let schema = resolver.schema();\n    let table_name = &body.tbl_name.name;\n    let table = match resolver.with_schema(database_id, |s| s.get_table(table_name.as_str())) {\n        Some(table) => table,\n        None => bail_parse_error!(\"Parse error: no such table: {}\", table_name),\n    };\n    if program.trigger.is_some() && table.virtual_table().is_some() {\n        bail_parse_error!(\n            \"unsafe use of virtual table \\\"{}\\\"\",\n            body.tbl_name.name.as_str()\n        );\n    }\n    if crate::is_attached_db(database_id) {\n        let schema_cookie = resolver.with_schema(database_id, |s| s.schema_version);\n        program.begin_write_on_database(database_id, schema_cookie);\n    }\n    validate_update(\n        schema,\n        &body,\n        table_name.as_str(),\n        is_internal_schema_change,\n        connection,\n    )?;\n\n    // Extract WITH, OR conflict clause, and INDEXED BY before borrowing body mutably\n    let with = body.with.take();\n    let or_conflict = body.or_conflict.take();\n    let indexed = body.indexed.take();\n\n    let table_name = table.get_name();\n    let iter_dir = body\n        .order_by\n        .first()\n        .and_then(|ob| {\n            ob.order.map(|o| match o {\n                SortOrder::Asc => IterationDirection::Forwards,\n                SortOrder::Desc => IterationDirection::Backwards,\n            })\n        })\n        .unwrap_or(IterationDirection::Forwards);\n\n    let joined_tables = vec![JoinedTable {\n        table: match table.as_ref() {\n            Table::Virtual(vtab) => Table::Virtual(vtab.clone()),\n            Table::BTree(btree_table) => Table::BTree(btree_table.clone()),\n            _ => unreachable!(),\n        },\n        identifier: body.tbl_name.alias.as_ref().map_or_else(\n            || table_name.to_string(),\n            |alias| alias.as_str().to_string(),\n        ),\n        internal_id: program.table_reference_counter.next(),\n        op: build_scan_op(&table, iter_dir),\n        join_info: None,\n        col_used_mask: ColumnUsedMask::default(),\n        column_use_counts: Vec::new(),\n        expression_index_usages: Vec::new(),\n        database_id,\n        indexed,\n    }];\n    let mut table_references = TableReferences::new(joined_tables, vec![]);\n\n    // Plan CTEs and add them as outer query references for subquery resolution\n    plan_ctes_as_outer_refs(with, resolver, program, &mut table_references, connection)?;\n\n    let column_lookup: HashMap<String, usize> = table\n        .columns()\n        .iter()\n        .enumerate()\n        .filter_map(|(i, col)| col.name.as_ref().map(|name| (name.to_lowercase(), i)))\n        .collect();\n\n    let mut set_clauses: Vec<(usize, Box<Expr>)> = Vec::with_capacity(body.sets.len());\n\n    // Process each SET assignment and map column names to expressions\n    // e.g the statement `SET x = 1, y = 2, z = 3` has 3 set assigments\n    for set in &mut body.sets {\n        bind_and_rewrite_expr(\n            &mut set.expr,\n            Some(&mut table_references),\n            None,\n            resolver,\n            BindingBehavior::ResultColumnsNotAllowed,\n        )?;\n\n        let values = match set.expr.as_ref() {\n            Expr::Parenthesized(vals) => vals.clone(),\n            expr => vec![expr.clone().into()],\n        };\n\n        if set.col_names.len() != values.len() {\n            bail_parse_error!(\n                \"{} columns assigned {} values\",\n                set.col_names.len(),\n                values.len()\n            );\n        }\n\n        for (col_name, expr) in set.col_names.iter().zip(values.iter()) {\n            let ident = normalize_ident(col_name.as_str());\n\n            let col_index = match column_lookup.get(&ident) {\n                Some(idx) => *idx,\n                None => {\n                    // Check if this is the 'rowid' keyword\n                    if ROWID_STRS.iter().any(|s| s.eq_ignore_ascii_case(&ident)) {\n                        // Find the rowid alias column if it exists\n                        if let Some((idx, _col)) = table\n                            .columns()\n                            .iter()\n                            .enumerate()\n                            .find(|(_i, c)| c.is_rowid_alias())\n                        {\n                            // Use the rowid alias column index\n                            match set_clauses.iter_mut().find(|(i, _)| i == &idx) {\n                                Some((_, existing_expr)) => existing_expr.clone_from(expr),\n                                None => set_clauses.push((idx, expr.clone())),\n                            }\n                            idx\n                        } else {\n                            // No rowid alias, use sentinel value for actual rowid\n                            match set_clauses.iter_mut().find(|(i, _)| *i == ROWID_SENTINEL) {\n                                Some((_, existing_expr)) => existing_expr.clone_from(expr),\n                                None => set_clauses.push((ROWID_SENTINEL, expr.clone())),\n                            }\n                            ROWID_SENTINEL\n                        }\n                    } else {\n                        crate::bail_parse_error!(\"no such column: {}.{}\", table_name, col_name);\n                    }\n                }\n            };\n            match set_clauses.iter_mut().find(|(idx, _)| *idx == col_index) {\n                Some((_, existing_expr)) => {\n                    // When multiple SET col[n] = val for the same column are desugared,\n                    // compose them: replace the column reference in the new expression\n                    // with the existing expression, so\n                    //   col = array_set_element(col, 0, 'X')  then  col = array_set_element(col, 2, 'Z')\n                    // becomes col = array_set_element(array_set_element(col, 0, 'X'), 2, 'Z')\n                    if let Expr::FunctionCall {\n                        name,\n                        args: new_args,\n                        ..\n                    } = expr.as_ref()\n                    {\n                        if name.as_str().eq_ignore_ascii_case(\"array_set_element\")\n                            && new_args.len() == 3\n                        {\n                            let mut composed_args = new_args.clone();\n                            composed_args[0].clone_from(existing_expr);\n                            *existing_expr = Box::new(Expr::FunctionCall {\n                                name: name.clone(),\n                                distinctness: None,\n                                args: composed_args,\n                                order_by: vec![],\n                                filter_over: turso_parser::ast::FunctionTail {\n                                    filter_clause: None,\n                                    over_clause: None,\n                                },\n                            });\n                        } else {\n                            existing_expr.clone_from(expr);\n                        }\n                    } else {\n                        existing_expr.clone_from(expr);\n                    }\n                }\n                None => set_clauses.push((col_index, expr.clone())),\n            }\n        }\n    }\n\n    // Plan subqueries in RETURNING expressions before processing\n    // (so SubqueryResult nodes are cloned into result_columns)\n    let mut non_from_clause_subqueries = vec![];\n    plan_subqueries_from_returning(\n        program,\n        &mut non_from_clause_subqueries,\n        &mut table_references,\n        &mut body.returning,\n        resolver,\n        connection,\n    )?;\n\n    let result_columns =\n        process_returning_clause(&mut body.returning, &mut table_references, resolver)?;\n\n    let order_by = body\n        .order_by\n        .iter_mut()\n        .map(|o| {\n            let _ = bind_and_rewrite_expr(\n                &mut o.expr,\n                Some(&mut table_references),\n                Some(&result_columns),\n                resolver,\n                BindingBehavior::ResultColumnsNotAllowed,\n            );\n            (o.expr.clone(), o.order.unwrap_or(SortOrder::Asc))\n        })\n        .collect();\n\n    // Sqlite determines we should create an ephemeral table if we do not have a FROM clause\n    // Difficult to say what items from the plan can be checked for this so currently just checking if a RowId Alias is referenced\n    // https://github.com/sqlite/sqlite/blob/master/src/update.c#L395\n    // https://github.com/sqlite/sqlite/blob/master/src/update.c#L670\n    let columns = table.columns();\n    let mut where_clause = vec![];\n    // Parse the WHERE clause\n    parse_where(\n        body.where_clause.as_deref(),\n        &mut table_references,\n        Some(&result_columns),\n        &mut where_clause,\n        resolver,\n    )?;\n\n    // Parse the LIMIT/OFFSET clause\n    let (limit, offset) = body\n        .limit\n        .map_or(Ok((None, None)), |l| parse_limit(l, resolver))?;\n\n    // Check what indexes will need to be updated by checking set_clauses and see\n    // if a column is contained in an index.\n    let indexes: Vec<_> = resolver.with_schema(database_id, |s| {\n        s.get_indices(table_name).cloned().collect()\n    });\n    let updated_cols: HashSet<usize> = set_clauses.iter().map(|(i, _)| *i).collect();\n    let rowid_alias_used = set_clauses\n        .iter()\n        .any(|(idx, _)| *idx == ROWID_SENTINEL || columns[*idx].is_rowid_alias());\n    let target_table_ref = table_references\n        .joined_tables()\n        .first()\n        .expect(\"UPDATE must have a target table reference\");\n    let indexes_to_update = if rowid_alias_used {\n        // If the rowid alias is used in the SET clause, we need to update all indexes\n        indexes\n    } else {\n        // otherwise we need to update the indexes whose columns are set in the SET clause,\n        // or if the columns used in the partial index WHERE clause are being updated.\n        let mut indexes_to_update = Vec::new();\n        for idx in indexes {\n            let mut needs = false;\n            for col in idx.columns.iter() {\n                if let Some(expr) = col.expr.as_ref() {\n                    let cols_used =\n                        expression_index_column_usage(expr.as_ref(), target_table_ref, resolver)?;\n                    if cols_used.iter().any(|cidx| updated_cols.contains(&cidx)) {\n                        needs = true;\n                        break;\n                    }\n                } else if updated_cols.contains(&col.pos_in_table) {\n                    needs = true;\n                    break;\n                }\n            }\n\n            if !needs {\n                if let Some(where_expr) = &idx.where_clause {\n                    let cols_used = expression_index_column_usage(\n                        where_expr.as_ref(),\n                        target_table_ref,\n                        resolver,\n                    )?;\n                    // If any column used in the partial index WHERE clause is being updated,\n                    // this index must be updated as well.\n                    needs = cols_used.iter().any(|cidx| updated_cols.contains(&cidx));\n                }\n            }\n\n            if needs {\n                indexes_to_update.push(idx);\n            }\n        }\n        indexes_to_update\n    };\n\n    Ok(Plan::Update(UpdatePlan {\n        table_references,\n        or_conflict,\n        set_clauses,\n        where_clause,\n        returning: if result_columns.is_empty() {\n            None\n        } else {\n            Some(result_columns)\n        },\n        order_by,\n        limit,\n        offset,\n        contains_constant_false_condition: false,\n        indexes_to_update,\n        ephemeral_plan: None,\n        cdc_update_alter_statement: None,\n        non_from_clause_subqueries,\n        safety: DmlSafety::default(),\n    }))\n}\n\nfn build_scan_op(table: &Table, iter_dir: IterationDirection) -> Operation {\n    match table {\n        Table::BTree(_) => Operation::Scan(Scan::BTreeTable {\n            iter_dir,\n            index: None,\n        }),\n        Table::Virtual(_) => Operation::default_scan_for(table),\n        _ => unreachable!(),\n    }\n}\n"
  },
  {
    "path": "core/translate/upsert.rs",
    "content": "use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};\nuse std::num::NonZeroUsize;\nuse std::sync::Arc;\n\nuse turso_parser::ast::{self, TriggerEvent, TriggerTime, Upsert};\n\nuse crate::error::SQLITE_CONSTRAINT_PRIMARYKEY;\nuse crate::schema::{BTreeTable, IndexColumn, ROWID_SENTINEL};\nuse crate::translate::emitter::{emit_check_constraints, UpdateRowSource};\nuse crate::translate::expr::{walk_expr, WalkControl};\nuse crate::translate::fkeys::{\n    emit_fk_child_update_counters, emit_parent_key_change_checks, fire_fk_update_actions,\n};\nuse crate::translate::insert::{format_unique_violation_desc, InsertEmitCtx};\nuse crate::translate::planner::ROWID_STRS;\nuse crate::translate::trigger_exec::{\n    fire_trigger, get_relevant_triggers_type_and_time, TriggerContext,\n};\nuse crate::vdbe::insn::{to_u16, CmpInsFlags};\nuse crate::{\n    bail_parse_error,\n    error::SQLITE_CONSTRAINT_NOTNULL,\n    schema::{Index, Schema, Table},\n    translate::{\n        emitter::{\n            emit_cdc_full_record, emit_cdc_insns, emit_cdc_patch_record, OperationMode, Resolver,\n        },\n        expr::{\n            emit_returning_results, translate_expr, translate_expr_no_constant_opt, walk_expr_mut,\n            NoConstantOptReason,\n        },\n        insert::Insertion,\n        plan::{ResultSetColumn, TableReferences},\n    },\n    util::{exprs_are_equivalent, normalize_ident},\n    vdbe::{\n        affinity::Affinity,\n        builder::ProgramBuilder,\n        insn::{IdxInsertFlags, InsertFlags, Insn},\n    },\n};\nuse crate::{CaptureDataChangesExt, Connection};\n\n// The following comment is copied directly from SQLite source and should be used as a guiding light\n// whenever we encounter compatibility bugs related to conflict clause handling:\n\n/* UNIQUE and PRIMARY KEY constraints should be handled in the following\n** order:\n**\n**   (1)  OE_Update\n**   (2)  OE_Abort, OE_Fail, OE_Rollback, OE_Ignore\n**   (3)  OE_Replace\n**\n** OE_Fail and OE_Ignore must happen before any changes are made.\n** OE_Update guarantees that only a single row will change, so it\n** must happen before OE_Replace.  Technically, OE_Abort and OE_Rollback\n** could happen in any order, but they are grouped up front for\n** convenience.\n**\n** 2018-08-14: Ticket https://www.sqlite.org/src/info/908f001483982c43\n** The order of constraints used to have OE_Update as (2) and OE_Abort\n** and so forth as (1). But apparently PostgreSQL checks the OE_Update\n** constraint before any others, so it had to be moved.\n**\n** Constraint checking code is generated in this order:\n**   (A)  The rowid constraint\n**   (B)  Unique index constraints that do not have OE_Replace as their\n**        default conflict resolution strategy\n**   (C)  Unique index that do use OE_Replace by default.\n**\n** The ordering of (2) and (3) is accomplished by making sure the linked\n** list of indexes attached to a table puts all OE_Replace indexes last\n** in the list.  See sqlite3CreateIndex() for where that happens.\n*/\n\n/// A ConflictTarget is extracted from each ON CONFLICT target,\n// e.g. INSERT INTO x(a) ON CONFLICT  *(a COLLATE nocase)*\n#[derive(Debug, Clone)]\npub struct ConflictTarget {\n    /// The normalized column name in question\n    col_name: String,\n    /// Possible collation name, normalized to lowercase\n    collate: Option<String>,\n}\n\n// Extract `(column, optional_collate)` from an ON CONFLICT target Expr.\n// Accepts: Id, Qualified, DoublyQualified, Parenthesized, Collate\nfn extract_target_key(e: &ast::Expr) -> Option<ConflictTarget> {\n    match e {\n        ast::Expr::Collate(inner, c) => {\n            let mut tk = extract_target_key(inner.as_ref())?;\n            let cstr = c.as_str();\n            tk.collate = Some(cstr.to_ascii_lowercase());\n            Some(tk)\n        }\n        ast::Expr::Parenthesized(v) if v.len() == 1 => extract_target_key(&v[0]),\n\n        ast::Expr::Id(name) => Some(ConflictTarget {\n            col_name: normalize_ident(name.as_str()),\n            collate: None,\n        }),\n        // t.a or db.t.a: accept ident or quoted in the column position\n        ast::Expr::Qualified(_, col) | ast::Expr::DoublyQualified(_, _, col) => {\n            let cname = col.as_str();\n            Some(ConflictTarget {\n                col_name: normalize_ident(cname),\n                collate: None,\n            })\n        }\n        _ => None,\n    }\n}\n\n/// For an ON CONFLICT target that is an expression (not a simple column),\n/// extract the inner expression and an optional COLLATE annotation.\n/// E.g. `lower(val) COLLATE nocase` -> (lower(val), Some(\"nocase\"))\nfn extract_target_expr(e: &ast::Expr) -> (&ast::Expr, Option<String>) {\n    match e {\n        ast::Expr::Collate(inner, c) => {\n            let (expr, _) = extract_target_expr(inner.as_ref());\n            (expr, Some(c.as_str().to_ascii_lowercase()))\n        }\n        ast::Expr::Parenthesized(v) if v.len() == 1 => extract_target_expr(&v[0]),\n        _ => (e, None),\n    }\n}\n\n// Return the index key’s effective collation.\n// If `idx_col.collation` is None, fall back to the column default or \"BINARY\".\nfn effective_collation_for_index_col(idx_col: &IndexColumn, table: &Table) -> String {\n    if let Some(c) = idx_col.collation.as_ref() {\n        return c.to_string().to_ascii_lowercase();\n    }\n    // Otherwise use the table default, or default to BINARY\n    table\n        .get_column_by_name(&idx_col.name)\n        .map(|s| s.1.collation().to_string())\n        .unwrap_or_else(|| \"binary\".to_string())\n}\n\n/// Match ON CONFLICT target to the PRIMARY KEY/rowid alias.\npub fn upsert_matches_rowid_alias(upsert: &Upsert, table: &Table) -> bool {\n    let Some(t) = upsert.index.as_ref() else {\n        // omitted target matches everything, CatchAll handled elsewhere\n        return false;\n    };\n    if t.targets.len() != 1 {\n        return false;\n    }\n    // Only treat as PK if the PK is the rowid alias (INTEGER PRIMARY KEY)\n    let pk = table.columns().iter().find(|c| c.is_rowid_alias());\n    if let Some(pkcol) = pk {\n        extract_target_key(&t.targets[0].expr).is_some_and(|tk| {\n            tk.col_name\n                .eq_ignore_ascii_case(pkcol.name.as_ref().unwrap_or(&String::new()))\n        })\n    } else {\n        false\n    }\n}\n\n/// Returns array of chaned column indicies and whether rowid was changed.\nfn collect_changed_cols(\n    table: &Table,\n    set_pairs: &[(usize, Box<ast::Expr>)],\n) -> (HashSet<usize>, bool) {\n    let mut cols_changed =\n        HashSet::with_capacity_and_hasher(table.columns().len(), Default::default());\n    let mut rowid_changed = false;\n    for (col_idx, _) in set_pairs {\n        if let Some(c) = table.columns().get(*col_idx) {\n            if c.is_rowid_alias() {\n                rowid_changed = true;\n            } else {\n                cols_changed.insert(*col_idx);\n            }\n        }\n    }\n    (cols_changed, rowid_changed)\n}\n\n#[inline]\nfn upsert_index_is_affected(\n    table: &Table,\n    idx: &Index,\n    changed_cols: &HashSet<usize>,\n    rowid_changed: bool,\n) -> bool {\n    if rowid_changed {\n        return true;\n    }\n    let km: HashSet<usize> = idx\n        .columns\n        .iter()\n        .filter_map(|ic| ic.expr.is_none().then_some(ic.pos_in_table))\n        .collect();\n    let pm = referenced_index_cols(idx, table);\n    for c in km.iter().chain(pm.iter()) {\n        if changed_cols.contains(c) {\n            return true;\n        }\n    }\n    false\n}\n\n/// Collect HashSet of columns referenced by the partial WHERE (empty if none), or\n/// by the expression of any IndexColumn on the index.\nfn referenced_index_cols(idx: &Index, table: &Table) -> HashSet<usize> {\n    let mut out = HashSet::default();\n    if let Some(expr) = &idx.where_clause {\n        index_expression_cols(table, &mut out, expr);\n    }\n    for ic in &idx.columns {\n        if let Some(expr) = &ic.expr {\n            index_expression_cols(table, &mut out, expr);\n        }\n    }\n    out\n}\n\n/// Columns referenced by any expression index columns on the index.\nfn index_expression_cols(table: &Table, out: &mut HashSet<usize>, expr: &ast::Expr) {\n    use ast::Expr;\n    let _ = walk_expr(expr, &mut |e: &ast::Expr| -> crate::Result<WalkControl> {\n        match e {\n            Expr::Id(n) => {\n                if let Some((i, _)) = table.get_column_by_name(&normalize_ident(n.as_str())) {\n                    out.insert(i);\n                } else if ROWID_STRS\n                    .iter()\n                    .any(|r| r.eq_ignore_ascii_case(n.as_str()))\n                {\n                    if let Some(rowid_pos) = table\n                        .btree()\n                        .and_then(|t| t.get_rowid_alias_column().map(|(p, _)| p))\n                    {\n                        out.insert(rowid_pos);\n                    }\n                }\n            }\n            Expr::Qualified(ns, c) | Expr::DoublyQualified(_, ns, c) => {\n                let nsn = normalize_ident(ns.as_str());\n                let tname = normalize_ident(table.get_name());\n                if nsn.eq_ignore_ascii_case(&tname) {\n                    if let Some((i, _)) = table.get_column_by_name(&normalize_ident(c.as_str())) {\n                        out.insert(i);\n                    }\n                }\n            }\n            _ => {}\n        }\n        Ok(WalkControl::Continue)\n    });\n}\n\n/// Match ON CONFLICT target to a UNIQUE index, *ignoring order* but requiring\n/// exact coverage (same column multiset). If the target specifies a COLLATED\n/// column, the collation must match the index column's effective collation.\n/// If the target omits collation, any index collation is accepted.\n/// Partial (WHERE) indexes never match.\npub fn upsert_matches_index(upsert: &Upsert, index: &Index, table: &Table) -> bool {\n    let Some(target) = upsert.index.as_ref() else {\n        return true;\n    };\n    // must be a non-partial UNIQUE index with identical arity\n    if !index.unique || index.where_clause.is_some() || target.targets.len() != index.columns.len()\n    {\n        return false;\n    }\n\n    // Track which index columns have been matched (consumed).\n    let mut matched = vec![false; index.columns.len()];\n\n    for te in &target.targets {\n        let mut found = None;\n\n        if let Some(tk) = extract_target_key(&te.expr) {\n            // Simple column reference target: match by name and collation.\n            let tname = &tk.col_name;\n            for (i, ic) in index.columns.iter().enumerate() {\n                if matched[i] || ic.expr.is_some() {\n                    continue;\n                }\n                let iname = normalize_ident(&ic.name);\n                let icoll = effective_collation_for_index_col(ic, table);\n                if tname.eq_ignore_ascii_case(&iname)\n                    && match tk.collate.as_ref() {\n                        Some(c) => c.eq_ignore_ascii_case(&icoll),\n                        None => true, // unspecified collation -> accept any\n                    }\n                {\n                    found = Some(i);\n                    break;\n                }\n            }\n        } else {\n            // Expression target (e.g. lower(val)): match against expression index\n            // columns using semantic equivalence.\n            let (target_expr, target_collate) = extract_target_expr(&te.expr);\n            for (i, ic) in index.columns.iter().enumerate() {\n                if matched[i] {\n                    continue;\n                }\n                if let Some(idx_expr) = &ic.expr {\n                    if exprs_are_equivalent(target_expr, idx_expr) {\n                        // If target specifies a collation, it must match the index column's.\n                        if let Some(ref tc) = target_collate {\n                            let icoll = effective_collation_for_index_col(ic, table);\n                            if !tc.eq_ignore_ascii_case(&icoll) {\n                                continue;\n                            }\n                        }\n                        found = Some(i);\n                        break;\n                    }\n                }\n            }\n        }\n\n        if let Some(i) = found {\n            matched[i] = true;\n        } else {\n            return false;\n        }\n    }\n    // All target columns matched exactly once, and all index columns consumed\n    matched.iter().all(|&m| m)\n}\n\n#[derive(Clone, Debug)]\npub enum ResolvedUpsertTarget {\n    // ON CONFLICT DO\n    CatchAll,\n    // ON CONFLICT(pk) DO\n    PrimaryKey,\n    // matched this non-partial UNIQUE index\n    Index(Arc<Index>),\n}\n\npub fn resolve_upsert_target(\n    schema: &Schema,\n    table: &Table,\n    upsert: &Upsert,\n) -> crate::Result<ResolvedUpsertTarget> {\n    // Omitted target, catch-all\n    if upsert.index.is_none() {\n        return Ok(ResolvedUpsertTarget::CatchAll);\n    }\n\n    // Targeted: must match PK, only if PK is a rowid alias\n    if upsert_matches_rowid_alias(upsert, table) {\n        return Ok(ResolvedUpsertTarget::PrimaryKey);\n    }\n\n    // Otherwise match a UNIQUE index, also covering non-rowid PRIMARY KEYs\n    for idx in schema.get_indices(table.get_name()) {\n        if idx.unique && upsert_matches_index(upsert, idx, table) {\n            return Ok(ResolvedUpsertTarget::Index(Arc::clone(idx)));\n        }\n    }\n    crate::bail_parse_error!(\n        \"ON CONFLICT clause does not match any PRIMARY KEY or UNIQUE constraint\"\n    );\n}\n\n#[allow(clippy::too_many_arguments)]\n/// Emit the bytecode to implement the `DO UPDATE` arm of an UPSERT.\n///\n/// This routine is entered after the caller has determined that an INSERT\n/// would violate a UNIQUE/PRIMARY KEY constraint and that the user requested\n/// `ON CONFLICT ... DO UPDATE`.\n///\n/// High-level flow:\n/// 1. Seek to the conflicting row by rowid and load the current row snapshot\n///    into a contiguous set of registers.\n/// 2. Optionally duplicate CURRENT into BEFORE* (for index rebuild and CDC).\n/// 3. Copy CURRENT into NEW, then evaluate SET expressions into NEW,\n///    with all references to the target table columns rewritten to read from\n///    the CURRENT registers (per SQLite semantics).\n/// 4. Enforce NOT NULL constraints and (if STRICT) type checks on NEW.\n/// 5. Rebuild indexes (delete keys using BEFORE, insert keys using NEW).\n/// 6. Rewrite the table row payload at the same rowid with NEW.\n/// 7. Emit CDC rows and RETURNING output if requested.\n/// 8. Jump to `row_done_label`.\n///\n/// Semantics reference: https://sqlite.org/lang_upsert.html\n/// Column references in the DO UPDATE expressions refer to the original\n/// (unchanged) row. To refer to would-be inserted values, use `excluded.x`.\n#[allow(clippy::too_many_arguments)]\npub fn emit_upsert(\n    program: &mut ProgramBuilder,\n    table: &Table,\n    ctx: &InsertEmitCtx,\n    insertion: &Insertion,\n    set_pairs: &mut [(usize, Box<ast::Expr>)],\n    where_clause: &mut Option<Box<ast::Expr>>,\n    resolver: &mut Resolver,\n    returning: &mut [ResultSetColumn],\n    connection: &Arc<Connection>,\n    table_references: &mut TableReferences,\n) -> crate::Result<()> {\n    // Seek & snapshot CURRENT\n    program.emit_insn(Insn::SeekRowid {\n        cursor_id: ctx.cursor_id,\n        src_reg: ctx.conflict_rowid_reg,\n        target_pc: ctx.loop_labels.row_done,\n    });\n    let num_cols = ctx.table.columns.len();\n    let current_start = program.alloc_registers(num_cols);\n    for (i, col) in ctx.table.columns.iter().enumerate() {\n        if col.is_rowid_alias() {\n            program.emit_insn(Insn::RowId {\n                cursor_id: ctx.cursor_id,\n                dest: current_start + i,\n            });\n        } else {\n            program.emit_insn(Insn::Column {\n                cursor_id: ctx.cursor_id,\n                column: i,\n                dest: current_start + i,\n                default: None,\n            });\n        }\n    }\n\n    // BEFORE for index maintenance / CDC\n    let before_start = if ctx.cdc_table.is_some() || !ctx.idx_cursors.is_empty() {\n        let s = program.alloc_registers(num_cols);\n        program.emit_insn(Insn::Copy {\n            src_reg: current_start,\n            dst_reg: s,\n            extra_amount: num_cols - 1,\n        });\n        Some(s)\n    } else {\n        None\n    };\n\n    // NEW = CURRENT, then apply SET\n    let new_start = program.alloc_registers(num_cols);\n    program.emit_insn(Insn::Copy {\n        src_reg: current_start,\n        dst_reg: new_start,\n        extra_amount: num_cols - 1,\n    });\n\n    // For STRICT tables with custom types, values loaded from disk (current_start)\n    // are in encoded form. We need decoded copies so that:\n    // - WHERE clause expressions see user-facing values (Bug 13)\n    // - SET expressions referencing t1.column see user-facing values\n    // - excluded.column references also see decoded values (Bug 7)\n    // current_start itself stays encoded for trigger OLD registers and before_start.\n    // After SET evaluation, we encode ALL columns in new_start before writing to disk.\n    let (decoded_current_start, excluded_decoded_start) = if let Some(bt) = table.btree() {\n        if bt.is_strict {\n            // Create decoded copy of current_start for WHERE/SET expressions\n            let decoded_current = program.alloc_registers(num_cols);\n            program.emit_insn(Insn::Copy {\n                src_reg: current_start,\n                dst_reg: decoded_current,\n                extra_amount: num_cols - 1,\n            });\n            crate::translate::expr::emit_custom_type_decode_columns(\n                program,\n                resolver,\n                &bt.columns,\n                decoded_current,\n                None,\n            )?;\n            // Decode new_start in-place (was copied from encoded current_start;\n            // after SET applies decoded values, we encode ALL columns)\n            crate::translate::expr::emit_custom_type_decode_columns(\n                program,\n                resolver,\n                &bt.columns,\n                new_start,\n                None,\n            )?;\n            // Create decoded copies of excluded (insertion) registers so that\n            // excluded.column references see user-facing values\n            let decoded_excluded = program.alloc_registers(num_cols);\n            program.emit_insn(Insn::Copy {\n                src_reg: insertion.first_col_register(),\n                dst_reg: decoded_excluded,\n                extra_amount: num_cols - 1,\n            });\n            crate::translate::expr::emit_custom_type_decode_columns(\n                program,\n                resolver,\n                &bt.columns,\n                decoded_excluded,\n                None,\n            )?;\n            (Some(decoded_current), Some(decoded_excluded))\n        } else {\n            (None, None)\n        }\n    } else {\n        (None, None)\n    };\n\n    // For WHERE and SET, use decoded_current_start if available (STRICT with custom types),\n    // otherwise fall back to current_start (already decoded or non-custom-type).\n    let expr_current_start = decoded_current_start.unwrap_or(current_start);\n\n    // WHERE on target row\n    if let Some(pred) = where_clause.as_mut() {\n        rewrite_expr_to_registers(\n            pred,\n            table,\n            expr_current_start,\n            ctx.conflict_rowid_reg,\n            Some(table.get_name()),\n            Some(insertion),\n            true,\n            excluded_decoded_start,\n        )?;\n        let pr = program.alloc_register();\n        translate_expr(program, None, pred, pr, resolver)?;\n        program.emit_insn(Insn::IfNot {\n            reg: pr,\n            target_pc: ctx.loop_labels.row_done,\n            jump_if_null: true,\n        });\n    }\n\n    // Apply SET; capture rowid change if any\n    let mut new_rowid_reg: Option<usize> = None;\n    for (col_idx, expr) in set_pairs.iter_mut() {\n        rewrite_expr_to_registers(\n            expr,\n            table,\n            expr_current_start,\n            ctx.conflict_rowid_reg,\n            Some(table.get_name()),\n            Some(insertion),\n            true,\n            excluded_decoded_start,\n        )?;\n        translate_expr_no_constant_opt(\n            program,\n            None,\n            expr,\n            new_start + *col_idx,\n            resolver,\n            NoConstantOptReason::RegisterReuse,\n        )?;\n        let col = &table.columns()[*col_idx];\n        if col.notnull() && !col.is_rowid_alias() {\n            program.emit_insn(Insn::HaltIfNull {\n                target_reg: new_start + *col_idx,\n                err_code: SQLITE_CONSTRAINT_NOTNULL,\n                description: String::from(table.get_name()) + \".\" + col.name.as_ref().unwrap(),\n            });\n        }\n        if col.is_rowid_alias() {\n            // Must be integer; remember the NEW rowid value\n            let r = program.alloc_register();\n            program.emit_insn(Insn::Copy {\n                src_reg: new_start + *col_idx,\n                dst_reg: r,\n                extra_amount: 0,\n            });\n            program.emit_insn(Insn::MustBeInt { reg: r });\n            new_rowid_reg = Some(r);\n        }\n    }\n\n    if let Some(bt) = table.btree() {\n        if bt.is_strict {\n            // Pre-encode TypeCheck: all columns are decoded (user-facing) at this point.\n            program.emit_insn(Insn::TypeCheck {\n                start_reg: new_start,\n                count: num_cols,\n                check_generated: true,\n                table_reference: BTreeTable::input_type_check_table_ref(\n                    &bt,\n                    resolver.schema(),\n                    None,\n                ),\n            });\n\n            // Encode ALL columns. Both non-SET columns (decoded from disk above)\n            // and SET columns (user-facing values from expressions) need encoding\n            // before being written to disk.\n            crate::translate::expr::emit_custom_type_encode_columns(\n                program,\n                resolver,\n                &bt.columns,\n                new_start,\n                None,\n                &bt.name,\n            )?;\n\n            // Post-encode TypeCheck: validate encoded values match storage type.\n            program.emit_insn(Insn::TypeCheck {\n                start_reg: new_start,\n                count: num_cols,\n                check_generated: true,\n                table_reference: BTreeTable::type_check_table_ref(&bt, resolver.schema()),\n            });\n        } else {\n            // For non-STRICT tables, apply column affinity to the values.\n            // This must happen early so that both index records and the table record\n            // use the converted values.\n            let affinity = bt.columns.iter().map(|c| c.affinity());\n\n            // Only emit Affinity if there's meaningful affinity to apply\n            if affinity.clone().any(|a| a != Affinity::Blob) {\n                if let Ok(count) = std::num::NonZeroUsize::try_from(num_cols) {\n                    program.emit_insn(Insn::Affinity {\n                        start_reg: new_start,\n                        count,\n                        affinities: affinity.map(|a| a.aff_mask()).collect(),\n                    });\n                }\n            }\n        }\n\n        // Evaluate CHECK constraints on the new values\n        emit_check_constraints(\n            program,\n            &bt.check_constraints,\n            resolver,\n            &bt.name,\n            new_rowid_reg.unwrap_or(ctx.conflict_rowid_reg),\n            bt.columns\n                .iter()\n                .enumerate()\n                .filter_map(|(idx, col)| col.name.as_deref().map(|n| (n, new_start + idx))),\n            connection,\n            ast::ResolveType::Abort,\n            ctx.loop_labels.row_done,\n            Some(table_references),\n        )?;\n    }\n\n    let (changed_cols, rowid_changed) = collect_changed_cols(table, set_pairs);\n\n    // Fire BEFORE UPDATE triggers\n    let upsert_database_id = ctx.database_id;\n    let preserved_old_registers: Option<Vec<usize>> = if let Some(btree_table) = table.btree() {\n        let updated_column_indices: HashSet<usize> =\n            set_pairs.iter().map(|(col_idx, _)| *col_idx).collect();\n        let relevant_before_update_triggers: Vec<_> =\n            resolver.with_schema(upsert_database_id, |s| {\n                get_relevant_triggers_type_and_time(\n                    s,\n                    TriggerEvent::Update,\n                    TriggerTime::Before,\n                    Some(updated_column_indices.clone()),\n                    &btree_table,\n                )\n                .collect()\n            });\n        // OLD row values are in current_start registers\n        let old_registers: Vec<usize> = (0..num_cols)\n            .map(|i| current_start + i)\n            .chain(std::iter::once(ctx.conflict_rowid_reg))\n            .collect();\n        if !relevant_before_update_triggers.is_empty() {\n            // NEW row values are in new_start registers. At this point they are\n            // encoded (post-encode for STRICT custom types). Mark new_encoded=true\n            // so fire_trigger's decode_trigger_registers will decode them.\n            let new_rowid_for_trigger = new_rowid_reg.unwrap_or(ctx.conflict_rowid_reg);\n            let new_registers: Vec<usize> = (0..num_cols)\n                .map(|i| new_start + i)\n                .chain(std::iter::once(new_rowid_for_trigger))\n                .collect();\n\n            // In UPSERT DO UPDATE context, trigger's INSERT/UPDATE OR IGNORE/REPLACE\n            // clauses should not suppress errors. Override conflict resolution to Abort.\n            // Use new_after variant because NEW values are encoded at this point.\n            let trigger_ctx = TriggerContext::new_after_with_override_conflict(\n                btree_table.clone(),\n                Some(new_registers),\n                Some(old_registers.clone()),\n                ast::ResolveType::Abort,\n            );\n\n            for trigger in relevant_before_update_triggers {\n                fire_trigger(\n                    program,\n                    resolver,\n                    trigger,\n                    &trigger_ctx,\n                    connection,\n                    upsert_database_id,\n                    ctx.loop_labels.row_done,\n                )?;\n            }\n\n            // BEFORE UPDATE triggers may have altered the btree, need to re-seek\n            program.emit_insn(Insn::NotExists {\n                cursor: ctx.cursor_id,\n                rowid_reg: ctx.conflict_rowid_reg,\n                target_pc: ctx.loop_labels.row_done,\n            });\n\n            let has_relevant_after_triggers = resolver.with_schema(upsert_database_id, |s| {\n                get_relevant_triggers_type_and_time(\n                    s,\n                    TriggerEvent::Update,\n                    TriggerTime::After,\n                    Some(updated_column_indices),\n                    &btree_table,\n                )\n                .count()\n                    > 0\n            });\n            if has_relevant_after_triggers {\n                // Preserve OLD registers for AFTER triggers\n                let preserved: Vec<usize> = old_registers\n                    .iter()\n                    .map(|old_reg| {\n                        let preserved_reg = program.alloc_register();\n                        program.emit_insn(Insn::Copy {\n                            src_reg: *old_reg,\n                            dst_reg: preserved_reg,\n                            extra_amount: 0,\n                        });\n                        preserved_reg\n                    })\n                    .collect();\n                Some(preserved)\n            } else {\n                None\n            }\n        } else {\n            // Check if we need to preserve for AFTER triggers\n            let has_relevant_after_triggers = resolver.with_schema(upsert_database_id, |s| {\n                get_relevant_triggers_type_and_time(\n                    s,\n                    TriggerEvent::Update,\n                    TriggerTime::After,\n                    Some(updated_column_indices),\n                    &btree_table,\n                )\n                .count()\n                    > 0\n            });\n            if has_relevant_after_triggers {\n                Some(old_registers)\n            } else {\n                None\n            }\n        }\n    } else {\n        None\n    };\n    let rowid_alias_idx = table.columns().iter().position(|c| c.is_rowid_alias());\n    let has_direct_rowid_update = set_pairs\n        .iter()\n        .any(|(idx, _)| *idx == rowid_alias_idx.unwrap_or(ROWID_SENTINEL));\n    let has_user_provided_rowid = if let Some(i) = rowid_alias_idx {\n        set_pairs.iter().any(|(idx, _)| *idx == i) || has_direct_rowid_update\n    } else {\n        has_direct_rowid_update\n    };\n\n    let rowid_set_clause_reg = if has_user_provided_rowid {\n        Some(new_rowid_reg.unwrap_or(ctx.conflict_rowid_reg))\n    } else {\n        None\n    };\n    if let Some(bt) = table.btree() {\n        if connection.foreign_keys_enabled() {\n            let rowid_new_reg = new_rowid_reg.unwrap_or(ctx.conflict_rowid_reg);\n\n            // Child-side checks\n            if resolver.with_schema(upsert_database_id, |s| s.has_child_fks(bt.name.as_str())) {\n                emit_fk_child_update_counters(\n                    program,\n                    &bt,\n                    table.get_name(),\n                    ctx.cursor_id,\n                    new_start,\n                    rowid_new_reg,\n                    &changed_cols,\n                    upsert_database_id,\n                    resolver,\n                )?;\n            }\n            let upsert_indices: Vec<_> = resolver.with_schema(upsert_database_id, |s| {\n                s.get_indices(table.get_name()).cloned().collect()\n            });\n            emit_parent_key_change_checks(\n                program,\n                &bt,\n                upsert_indices.iter().filter(|idx| {\n                    upsert_index_is_affected(table, idx, &changed_cols, rowid_changed)\n                }),\n                ctx.cursor_id,\n                ctx.conflict_rowid_reg,\n                new_start,\n                new_rowid_reg.unwrap_or(ctx.conflict_rowid_reg),\n                rowid_set_clause_reg,\n                set_pairs,\n                upsert_database_id,\n                resolver,\n            )?;\n        }\n    }\n\n    // Index rebuild (DELETE old, INSERT new), honoring partial-index WHEREs\n    if let Some(before) = before_start {\n        for (idx_name, _root, idx_cid) in &ctx.idx_cursors {\n            let idx_meta = resolver\n                .with_schema(ctx.database_id, |s| {\n                    s.get_index(table.get_name(), idx_name).cloned()\n                })\n                .expect(\"index exists\");\n\n            if !upsert_index_is_affected(table, &idx_meta, &changed_cols, rowid_changed) {\n                continue; // skip untouched index completely\n            }\n            let k = idx_meta.columns.len();\n\n            let before_pred_reg = eval_partial_pred_for_row_image(\n                program,\n                table,\n                &idx_meta,\n                before,\n                ctx.conflict_rowid_reg,\n                resolver,\n            );\n            let new_rowid = new_rowid_reg.unwrap_or(ctx.conflict_rowid_reg);\n            let new_pred_reg = eval_partial_pred_for_row_image(\n                program, table, &idx_meta, new_start, new_rowid, resolver,\n            );\n\n            // Skip delete if BEFORE predicate false/NULL\n            let maybe_skip_del = before_pred_reg.map(|r| {\n                let lbl = program.allocate_label();\n                program.emit_insn(Insn::IfNot {\n                    reg: r,\n                    target_pc: lbl,\n                    jump_if_null: true,\n                });\n                lbl\n            });\n\n            // DELETE old key\n            let del = program.alloc_registers(k + 1);\n            for (i, ic) in idx_meta.columns.iter().enumerate() {\n                if let Some(expr) = &ic.expr {\n                    let mut e = expr.as_ref().clone();\n                    rewrite_expr_to_registers(\n                        &mut e,\n                        table,\n                        before,\n                        ctx.conflict_rowid_reg,\n                        Some(table.get_name()),\n                        None,\n                        false,\n                        None,\n                    )?;\n                    translate_expr_no_constant_opt(\n                        program,\n                        None,\n                        &e,\n                        del + i,\n                        resolver,\n                        NoConstantOptReason::RegisterReuse,\n                    )?;\n                } else {\n                    let (ci, _) = table.get_column_by_name(&ic.name).unwrap();\n                    program.emit_insn(Insn::Copy {\n                        src_reg: before + ci,\n                        dst_reg: del + i,\n                        extra_amount: 0,\n                    });\n                }\n            }\n            program.emit_insn(Insn::Copy {\n                src_reg: ctx.conflict_rowid_reg,\n                dst_reg: del + k,\n                extra_amount: 0,\n            });\n            program.emit_insn(Insn::IdxDelete {\n                start_reg: del,\n                num_regs: k + 1,\n                cursor_id: *idx_cid,\n                raise_error_if_no_matching_entry: false,\n            });\n            if let Some(label) = maybe_skip_del {\n                program.resolve_label(label, program.offset());\n            }\n\n            // Skip insert if NEW predicate false/NULL\n            let maybe_skip_ins = new_pred_reg.map(|r| {\n                let lbl = program.allocate_label();\n                program.emit_insn(Insn::IfNot {\n                    reg: r,\n                    target_pc: lbl,\n                    jump_if_null: true,\n                });\n                lbl\n            });\n\n            // INSERT new key (use NEW rowid if present)\n            let ins = program.alloc_registers(k + 1);\n            for (i, ic) in idx_meta.columns.iter().enumerate() {\n                if let Some(expr) = &ic.expr {\n                    let mut e = expr.as_ref().clone();\n                    rewrite_expr_to_registers(\n                        &mut e,\n                        table,\n                        new_start,\n                        new_rowid,\n                        Some(table.get_name()),\n                        None,\n                        false,\n                        None,\n                    )?;\n                    translate_expr_no_constant_opt(\n                        program,\n                        None,\n                        &e,\n                        ins + i,\n                        resolver,\n                        NoConstantOptReason::RegisterReuse,\n                    )?;\n                } else {\n                    let (ci, _) = table.get_column_by_name(&ic.name).unwrap();\n                    program.emit_insn(Insn::Copy {\n                        src_reg: new_start + ci,\n                        dst_reg: ins + i,\n                        extra_amount: 0,\n                    });\n                }\n            }\n            program.emit_insn(Insn::Copy {\n                src_reg: new_rowid,\n                dst_reg: ins + k,\n                extra_amount: 0,\n            });\n\n            let rec = program.alloc_register();\n            program.emit_insn(Insn::MakeRecord {\n                start_reg: to_u16(ins),\n                count: to_u16(k + 1),\n                dest_reg: to_u16(rec),\n                index_name: Some((*idx_name).clone()),\n                affinity_str: None,\n            });\n\n            if idx_meta.unique {\n                // Affinity on the key columns for the NoConflict probe\n                let ok = program.allocate_label();\n                let aff: String = idx_meta\n                    .columns\n                    .iter()\n                    .map(|c| {\n                        c.expr.as_ref().map_or_else(\n                            || {\n                                table\n                                    .get_column_by_name(&c.name)\n                                    .map(|(_, col)| {\n                                        let is_strict =\n                                            table.btree().is_some_and(|btree| btree.is_strict);\n                                        col.affinity_with_strict(is_strict).aff_mask()\n                                    })\n                                    .unwrap_or('B')\n                            },\n                            |_| crate::vdbe::affinity::Affinity::Blob.aff_mask(),\n                        )\n                    })\n                    .collect();\n\n                program.emit_insn(Insn::Affinity {\n                    start_reg: ins,\n                    count: NonZeroUsize::new(k).unwrap(),\n                    affinities: aff,\n                });\n                program.emit_insn(Insn::NoConflict {\n                    cursor_id: *idx_cid,\n                    target_pc: ok,\n                    record_reg: ins,\n                    num_regs: k,\n                });\n                let hit = program.alloc_register();\n                program.emit_insn(Insn::IdxRowId {\n                    cursor_id: *idx_cid,\n                    dest: hit,\n                });\n                program.emit_insn(Insn::Eq {\n                    lhs: new_rowid,\n                    rhs: hit,\n                    target_pc: ok,\n                    flags: CmpInsFlags::default(),\n                    collation: program.curr_collation(),\n                });\n                let description = format_unique_violation_desc(table.get_name(), &idx_meta);\n                program.emit_insn(Insn::Halt {\n                    err_code: SQLITE_CONSTRAINT_PRIMARYKEY,\n                    description,\n                    on_error: None,\n                    description_reg: None,\n                });\n                program.preassign_label_to_next_insn(ok);\n            }\n\n            program.emit_insn(Insn::IdxInsert {\n                cursor_id: *idx_cid,\n                record_reg: rec,\n                unpacked_start: Some(ins),\n                unpacked_count: Some((k + 1) as u16),\n                flags: IdxInsertFlags::new().nchange(true),\n            });\n\n            if let Some(lbl) = maybe_skip_ins {\n                program.resolve_label(lbl, program.offset());\n            }\n        }\n    }\n\n    // Build NEW table payload\n    let rec = program.alloc_register();\n    let is_strict = table.btree().is_some_and(|btree| btree.is_strict);\n    let affinity_str = table\n        .columns()\n        .iter()\n        .map(|c| c.affinity_with_strict(is_strict).aff_mask())\n        .collect::<String>();\n    program.emit_insn(Insn::MakeRecord {\n        start_reg: to_u16(new_start),\n        count: to_u16(num_cols),\n        dest_reg: to_u16(rec),\n        index_name: None,\n        affinity_str: Some(affinity_str),\n    });\n\n    // If rowid changed, first ensure no other row owns it, then delete+insert\n    if let Some(rnew) = new_rowid_reg {\n        let ok = program.allocate_label();\n\n        // If equal to old rowid, skip uniqueness probe\n        program.emit_insn(Insn::Eq {\n            lhs: rnew,\n            rhs: ctx.conflict_rowid_reg,\n            target_pc: ok,\n            flags: CmpInsFlags::default(),\n            collation: program.curr_collation(),\n        });\n\n        // If another row already has rnew -> constraint\n        program.emit_insn(Insn::NotExists {\n            cursor: ctx.cursor_id,\n            rowid_reg: rnew,\n            target_pc: ok,\n        });\n        program.emit_insn(Insn::Halt {\n            err_code: SQLITE_CONSTRAINT_PRIMARYKEY,\n            description: format!(\n                \"{}.{}\",\n                table.get_name(),\n                table\n                    .columns()\n                    .iter()\n                    .find(|c| c.is_rowid_alias())\n                    .and_then(|c| c.name.as_deref())\n                    .unwrap_or(\"rowid\")\n            ),\n            on_error: None,\n            description_reg: None,\n        });\n        program.preassign_label_to_next_insn(ok);\n\n        // important: the cursor was repositioned in the previous conflict check via NotExists,\n        // so if we didn't conflict+halt above, we need to re-seek to the row under update.\n        program.emit_insn(Insn::SeekRowid {\n            cursor_id: ctx.cursor_id,\n            src_reg: ctx.conflict_rowid_reg,\n            target_pc: ctx.loop_labels.row_done,\n        });\n\n        // Now replace the row\n        program.emit_insn(Insn::Delete {\n            cursor_id: ctx.cursor_id,\n            table_name: table.get_name().to_string(),\n            is_part_of_update: true,\n        });\n        program.emit_insn(Insn::Insert {\n            cursor: ctx.cursor_id,\n            key_reg: rnew,\n            record_reg: rec,\n            flag: InsertFlags::new()\n                .require_seek()\n                .update_rowid_change()\n                .skip_last_rowid(),\n            table_name: table.get_name().to_string(),\n        });\n    } else {\n        program.emit_insn(Insn::Insert {\n            cursor: ctx.cursor_id,\n            key_reg: ctx.conflict_rowid_reg,\n            record_reg: rec,\n            flag: InsertFlags::new().skip_last_rowid(),\n            table_name: table.get_name().to_string(),\n        });\n    }\n\n    // Fire FK actions (CASCADE, SET NULL, SET DEFAULT) for parent-side updates.\n    // This must be done after the update is complete but before AFTER triggers.\n    if let Some(bt) = table.btree() {\n        if connection.foreign_keys_enabled()\n            && resolver.with_schema(upsert_database_id, |s| {\n                s.any_resolved_fks_referencing(bt.name.as_str())\n            })\n        {\n            fire_fk_update_actions(\n                program,\n                resolver,\n                bt.name.as_str(),\n                ctx.conflict_rowid_reg, // old_rowid_reg\n                current_start,          // old_values_start\n                new_start,              // new_values_start\n                new_rowid_reg.unwrap_or(ctx.conflict_rowid_reg), // new_rowid_reg\n                connection,\n                upsert_database_id,\n            )?;\n        }\n    }\n\n    // emit CDC instructions\n    if let Some((cdc_id, _)) = ctx.cdc_table {\n        let new_rowid = new_rowid_reg.unwrap_or(ctx.conflict_rowid_reg);\n        if new_rowid_reg.is_some() {\n            // DELETE (before)\n            let before_rec = if program.capture_data_changes_info().has_before() {\n                Some(emit_cdc_full_record(\n                    program,\n                    table.columns(),\n                    ctx.cursor_id,\n                    ctx.conflict_rowid_reg,\n                    table.btree().is_some_and(|btree| btree.is_strict),\n                ))\n            } else {\n                None\n            };\n            emit_cdc_insns(\n                program,\n                resolver,\n                OperationMode::DELETE,\n                cdc_id,\n                ctx.conflict_rowid_reg,\n                before_rec,\n                None,\n                None,\n                table.get_name(),\n            )?;\n\n            // INSERT (after)\n            let after_rec = if program.capture_data_changes_info().has_after() {\n                Some(emit_cdc_patch_record(\n                    program, table, new_start, rec, new_rowid,\n                ))\n            } else {\n                None\n            };\n            emit_cdc_insns(\n                program,\n                resolver,\n                OperationMode::INSERT,\n                cdc_id,\n                new_rowid,\n                None,\n                after_rec,\n                None,\n                table.get_name(),\n            )?;\n        } else {\n            let after_rec = if program.capture_data_changes_info().has_after() {\n                Some(emit_cdc_patch_record(\n                    program,\n                    table,\n                    new_start,\n                    rec,\n                    ctx.conflict_rowid_reg,\n                ))\n            } else {\n                None\n            };\n            let before_rec = if program.capture_data_changes_info().has_before() {\n                Some(emit_cdc_full_record(\n                    program,\n                    table.columns(),\n                    ctx.cursor_id,\n                    ctx.conflict_rowid_reg,\n                    table.btree().is_some_and(|btree| btree.is_strict),\n                ))\n            } else {\n                None\n            };\n            emit_cdc_insns(\n                program,\n                resolver,\n                OperationMode::UPDATE(UpdateRowSource::Normal),\n                cdc_id,\n                ctx.conflict_rowid_reg,\n                before_rec,\n                after_rec,\n                None,\n                table.get_name(),\n            )?;\n        }\n    }\n\n    // Fire AFTER UPDATE triggers\n    if let (Some(btree_table), Some(old_regs)) = (table.btree(), preserved_old_registers) {\n        let updated_column_indices: HashSet<usize> =\n            set_pairs.iter().map(|(col_idx, _)| *col_idx).collect();\n        let relevant_triggers: Vec<_> = resolver.with_schema(upsert_database_id, |s| {\n            get_relevant_triggers_type_and_time(\n                s,\n                TriggerEvent::Update,\n                TriggerTime::After,\n                Some(updated_column_indices),\n                &btree_table,\n            )\n            .collect()\n        });\n        if !relevant_triggers.is_empty() {\n            let new_rowid_for_trigger = new_rowid_reg.unwrap_or(ctx.conflict_rowid_reg);\n            let new_registers_after: Vec<usize> = (0..num_cols)\n                .map(|i| new_start + i)\n                .chain(std::iter::once(new_rowid_for_trigger))\n                .collect();\n\n            // In UPSERT DO UPDATE context, trigger's INSERT/UPDATE OR IGNORE/REPLACE\n            // clauses should not suppress errors. Override conflict resolution to Abort.\n            // NEW values are encoded at this point; fire_trigger will decode them.\n            let trigger_ctx_after = TriggerContext::new_after_with_override_conflict(\n                btree_table,\n                Some(new_registers_after),\n                Some(old_regs),\n                ast::ResolveType::Abort,\n            );\n\n            // RAISE(IGNORE) in an AFTER trigger should only abort the trigger body,\n            // not skip post-row work (RETURNING).\n            let after_trigger_done = program.allocate_label();\n            for trigger in relevant_triggers {\n                fire_trigger(\n                    program,\n                    resolver,\n                    trigger,\n                    &trigger_ctx_after,\n                    connection,\n                    upsert_database_id,\n                    after_trigger_done,\n                )?;\n            }\n            program.preassign_label_to_next_insn(after_trigger_done);\n        }\n    }\n\n    // RETURNING from NEW image + final rowid\n    if !returning.is_empty() {\n        emit_returning_results(\n            program,\n            table_references,\n            returning,\n            new_start,\n            new_rowid_reg.unwrap_or(ctx.conflict_rowid_reg),\n            resolver,\n            ctx.returning_buffer.as_ref(),\n        )?;\n    }\n\n    program.emit_insn(Insn::Goto {\n        target_pc: ctx.loop_labels.row_done,\n    });\n    Ok(())\n}\n\n/// Normalize the `SET` clause into `(column_index, Expr)` pairs using table layout.\n///\n/// Supports multi-target row-value SETs: `SET (a, b) = (expr1, expr2)`.\n/// Enforces same number of column names and RHS values.\n/// If the same column is assigned multiple times, the last assignment wins.\npub fn collect_set_clauses_for_upsert(\n    table: &Table,\n    set_items: &mut [ast::Set],\n) -> crate::Result<Vec<(usize, Box<ast::Expr>)>> {\n    let lookup: HashMap<String, usize> = table\n        .columns()\n        .iter()\n        .enumerate()\n        .filter_map(|(i, c)| c.name.as_ref().map(|n| (n.to_lowercase(), i)))\n        .collect();\n\n    let mut out: Vec<(usize, Box<ast::Expr>)> = vec![];\n\n    for set in set_items {\n        let values: Vec<Box<ast::Expr>> = match set.expr.as_ref() {\n            ast::Expr::Parenthesized(v) => v.clone(),\n            e => vec![e.clone().into()],\n        };\n        if set.col_names.len() != values.len() {\n            bail_parse_error!(\n                \"{} columns assigned {} values\",\n                set.col_names.len(),\n                values.len()\n            );\n        }\n        for (cn, e) in set.col_names.iter().zip(values.into_iter()) {\n            let Some(idx) = lookup.get(&normalize_ident(cn.as_str())) else {\n                bail_parse_error!(\"no such column: {}\", cn);\n            };\n            if let Some(existing) = out.iter_mut().find(|(i, _)| *i == *idx) {\n                existing.1 = e;\n            } else {\n                out.push((*idx, e));\n            }\n        }\n    }\n    Ok(out)\n}\n\nfn eval_partial_pred_for_row_image(\n    prg: &mut ProgramBuilder,\n    table: &Table,\n    idx: &Index,\n    row_start: usize, // base of CURRENT or NEW image\n    rowid_reg: usize, // rowid for that image\n    resolver: &Resolver,\n) -> Option<usize> {\n    let Some(where_expr) = &idx.where_clause else {\n        return None;\n    };\n    let mut e = where_expr.as_ref().clone();\n    rewrite_expr_to_registers(\n        &mut e, table, row_start, rowid_reg, None,  // table_name\n        None,  // insertion\n        false, // dont allow EXCLUDED\n        None,  // no decoded excluded\n    )\n    .ok()?;\n    let r = prg.alloc_register();\n    translate_expr_no_constant_opt(\n        prg,\n        None,\n        &e,\n        r,\n        resolver,\n        NoConstantOptReason::RegisterReuse,\n    )\n    .ok()?;\n    Some(r)\n}\n\n/// Generic rewriter that maps column references to registers for a given row image.\n///\n/// - Id/Qualified refs to the *target table* (when `table_name` is provided) resolve\n///   to the CURRENT/NEW row image starting at `base_start`, with `rowid` (or the\n///   rowid-alias) mapped to `rowid_reg`.\n/// - If `allow_excluded` and `insertion` are provided, `EXCLUDED.x` resolves to the\n///   insertion registers (and `EXCLUDED.rowid` resolves to `insertion.key_register()`).\n///   When `excluded_decoded_start` is provided, excluded column references resolve to\n///   decoded registers at `excluded_decoded_start + col_idx` instead of the raw\n///   (encoded) insertion registers. This prevents double-encoding in UPSERT.\n/// - If `table_name` is `None`, qualified refs never match\n/// - Leaves names from other tables/namespaces untouched.\n#[allow(clippy::too_many_arguments)]\nfn rewrite_expr_to_registers(\n    e: &mut ast::Expr,\n    table: &Table,\n    base_start: usize,\n    rowid_reg: usize,\n    table_name: Option<&str>,\n    insertion: Option<&Insertion>,\n    allow_excluded: bool,\n    excluded_decoded_start: Option<usize>,\n) -> crate::Result<WalkControl> {\n    use ast::Expr;\n    let table_name_norm = table_name.map(normalize_ident);\n\n    // Map a column name to a register within the row image at `base_start`.\n    let col_reg_from_row_image = |name: &str| -> Option<usize> {\n        if ROWID_STRS.iter().any(|s| s.eq_ignore_ascii_case(name)) {\n            return Some(rowid_reg);\n        }\n        let (idx, c) = table.get_column_by_name(name)?;\n        if c.is_rowid_alias() {\n            Some(rowid_reg)\n        } else {\n            Some(base_start + idx)\n        }\n    };\n\n    walk_expr_mut(\n        e,\n        &mut |expr: &mut ast::Expr| -> crate::Result<WalkControl> {\n            match expr {\n                Expr::Qualified(ns, c) | Expr::DoublyQualified(_, ns, c) => {\n                    let ns = normalize_ident(ns.as_str());\n                    let c = normalize_ident(c.as_str());\n                    // Handle EXCLUDED.* if enabled\n                    if allow_excluded && ns.eq_ignore_ascii_case(\"excluded\") {\n                        if let Some(ins) = insertion {\n                            if ROWID_STRS.iter().any(|s| s.eq_ignore_ascii_case(&c)) {\n                                *expr = Expr::Register(ins.key_register());\n                            } else if let Some(cm) = ins.get_col_mapping_by_name(&c) {\n                                // Use decoded excluded registers when available\n                                // to prevent double-encoding of custom type values\n                                if let Some(decoded_start) = excluded_decoded_start {\n                                    let (col_idx, _) =\n                                        table.get_column_by_name(&c).expect(\"column exists\");\n                                    *expr = Expr::Register(decoded_start + col_idx);\n                                } else {\n                                    *expr = Expr::Register(cm.register);\n                                }\n                            } else {\n                                bail_parse_error!(\"no such column in EXCLUDED: {}\", c);\n                            }\n                        }\n                        // If insertion is None, leave EXCLUDED.* untouched.\n                        return Ok(WalkControl::Continue);\n                    }\n\n                    // Match the target table namespace if provided\n                    if let Some(ref tn) = table_name_norm {\n                        if ns.eq_ignore_ascii_case(tn) {\n                            if let Some(r) = col_reg_from_row_image(&c) {\n                                *expr = Expr::Register(r);\n                            } else {\n                                bail_parse_error!(\"no such column: {}.{}\", ns, c);\n                            }\n                            return Ok(WalkControl::Continue);\n                        }\n                    }\n\n                    // In UPSERT DO UPDATE context (allow_excluded=true), a qualified\n                    // reference that doesn't match the target table or EXCLUDED is\n                    // invalid. Return a graceful error instead of leaving it\n                    // unresolved (which would panic later in translate_expr).\n                    if allow_excluded {\n                        bail_parse_error!(\"no such column: {}.{}\", ns, c);\n                    }\n                }\n                // Unqualified id -> row image (CURRENT/NEW depending on caller)\n                Expr::Id(name) => {\n                    if let Some(r) = col_reg_from_row_image(&normalize_ident(name.as_str())) {\n                        *expr = Expr::Register(r);\n                    }\n                }\n                _ => {}\n            }\n            Ok(WalkControl::Continue)\n        },\n    )\n}\n"
  },
  {
    "path": "core/translate/vacuum.rs",
    "content": "//! Translation of VACUUM statements to VDBE bytecode.\n\nuse crate::vdbe::builder::ProgramBuilder;\nuse crate::vdbe::insn::Insn;\nuse crate::{bail_parse_error, Result};\nuse turso_parser::ast::{Expr, Literal, Name};\n\n/// Translate a VACUUM statement into VDBE bytecode.\n///\n/// Currently only VACUUM INTO is supported. Plain VACUUM (which compacts\n/// the database in place) is not yet implemented.\n///\n/// # Arguments\n/// * `program` - The program builder to emit instructions to\n/// * `schema_name` - Optional schema/database name (not yet supported)\n/// * `into` - Optional destination path for VACUUM INTO\n///\n/// # Returns\n/// The modified program builder on success\npub fn translate_vacuum(\n    program: &mut ProgramBuilder,\n    schema_name: Option<&Name>,\n    into: Option<&Expr>,\n) -> Result<()> {\n    // Schema name support (for attached databases) is not yet implemented\n    if schema_name.is_some() {\n        bail_parse_error!(\"VACUUM with schema name is not supported yet\");\n    }\n\n    match into {\n        Some(dest_expr) => {\n            // VACUUM INTO 'path' - create compacted copy at destination\n            let dest_path = extract_path_from_expr(dest_expr)?;\n            program.emit_insn(Insn::VacuumInto { dest_path });\n            Ok(())\n        }\n        None => {\n            // Plain VACUUM - not yet supported\n            bail_parse_error!(\n                \"VACUUM is not supported yet. Use VACUUM INTO 'filename' to create a compacted copy.\"\n            );\n        }\n    }\n}\n\n/// Extract a file path string from an expression.\n///\n/// The expression can be either:\n/// - A string literal: `VACUUM INTO 'path/to/file.db'`\n/// - An identifier (variable name, though not commonly used)\nfn extract_path_from_expr(expr: &Expr) -> Result<String> {\n    match expr {\n        Expr::Literal(Literal::String(s)) => {\n            // Remove surrounding quotes if present\n            let path = s.trim_matches('\\'').trim_matches('\"');\n            if path.is_empty() {\n                bail_parse_error!(\"VACUUM INTO path cannot be empty\");\n            }\n            Ok(path.to_string())\n        }\n        Expr::Id(name) => {\n            // Allow identifier as path (unusual but valid)\n            Ok(name.as_str().to_string())\n        }\n        _ => {\n            bail_parse_error!(\"VACUUM INTO requires a string literal path\");\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_extract_path_from_string_literal() {\n        let expr = Expr::Literal(Literal::String(\"'test.db'\".to_string()));\n        let path = extract_path_from_expr(&expr).unwrap();\n        assert_eq!(path, \"test.db\");\n    }\n\n    #[test]\n    fn test_extract_path_from_string_literal_double_quotes() {\n        let expr = Expr::Literal(Literal::String(\"\\\"test.db\\\"\".to_string()));\n        let path = extract_path_from_expr(&expr).unwrap();\n        assert_eq!(path, \"test.db\");\n    }\n\n    #[test]\n    fn test_extract_path_from_identifier() {\n        let expr = Expr::Id(Name::exact(\"myfile\".to_string()));\n        let path = extract_path_from_expr(&expr).unwrap();\n        assert_eq!(path, \"myfile\");\n    }\n\n    #[test]\n    fn test_extract_path_empty_fails() {\n        let expr = Expr::Literal(Literal::String(\"''\".to_string()));\n        assert!(extract_path_from_expr(&expr).is_err());\n    }\n}\n"
  },
  {
    "path": "core/translate/values.rs",
    "content": "use crate::translate::emitter::TranslateCtx;\nuse crate::translate::expr::{translate_expr_no_constant_opt, NoConstantOptReason};\nuse crate::translate::plan::{QueryDestination, SelectPlan};\nuse crate::translate::result_row::emit_offset;\nuse crate::turso_assert_eq;\nuse crate::vdbe::builder::ProgramBuilder;\nuse crate::vdbe::insn::{to_u16, IdxInsertFlags, InsertFlags, Insn};\nuse crate::vdbe::BranchOffset;\nuse crate::Result;\n\npub fn emit_values(\n    program: &mut ProgramBuilder,\n    plan: &SelectPlan,\n    t_ctx: &mut TranslateCtx,\n) -> Result<usize> {\n    if plan.values.len() == 1 {\n        let start_reg = emit_values_when_single_row(program, plan, t_ctx)?;\n        return Ok(start_reg);\n    }\n\n    let reg_result_cols_start = match plan.query_destination {\n        QueryDestination::ResultRows => emit_toplevel_values(program, plan, t_ctx)?,\n        QueryDestination::CoroutineYield { yield_reg, .. } => {\n            emit_values_in_subquery(program, plan, t_ctx, yield_reg)?\n        }\n        QueryDestination::EphemeralIndex { .. } => emit_toplevel_values(program, plan, t_ctx)?,\n        QueryDestination::EphemeralTable { .. } => emit_toplevel_values(program, plan, t_ctx)?,\n        QueryDestination::ExistsSubqueryResult { result_reg } => {\n            program.emit_insn(Insn::Integer {\n                value: 1,\n                dest: result_reg,\n            });\n            result_reg\n        }\n        QueryDestination::RowValueSubqueryResult { .. } => {\n            emit_toplevel_values(program, plan, t_ctx)?\n        }\n        QueryDestination::RowSet { .. } => {\n            unreachable!(\"RowSet query destination should not be used in values emission\")\n        }\n        QueryDestination::Unset => unreachable!(\"Unset query destination should not be reached\"),\n    };\n    Ok(reg_result_cols_start)\n}\n\nfn emit_values_when_single_row(\n    program: &mut ProgramBuilder,\n    plan: &SelectPlan,\n    t_ctx: &mut TranslateCtx,\n) -> Result<usize> {\n    let end_label = program.allocate_label();\n    emit_offset(program, end_label, t_ctx.reg_offset);\n    let first_row = &plan.values[0];\n    let row_len = first_row.len();\n    let start_reg = if let Some(reg) = t_ctx.reg_result_cols_start {\n        program.reg_result_cols_start = Some(reg);\n        reg\n    } else {\n        let reg = program.alloc_registers(row_len);\n        t_ctx.reg_result_cols_start = Some(reg);\n        program.reg_result_cols_start = Some(reg);\n        reg\n    };\n    for (i, v) in first_row.iter().enumerate() {\n        translate_expr_no_constant_opt(\n            program,\n            Some(&plan.table_references),\n            v,\n            start_reg + i,\n            &t_ctx.resolver,\n            NoConstantOptReason::RegisterReuse,\n        )?;\n    }\n    emit_values_to_destination(program, plan, t_ctx, start_reg, row_len, end_label)?;\n    program.preassign_label_to_next_insn(end_label);\n    Ok(start_reg)\n}\n\nfn emit_toplevel_values(\n    program: &mut ProgramBuilder,\n    plan: &SelectPlan,\n    t_ctx: &mut TranslateCtx,\n) -> Result<usize> {\n    let yield_reg = program.alloc_register();\n    let definition_label = program.allocate_label();\n    let start_offset_label = program.allocate_label();\n    program.emit_insn(Insn::InitCoroutine {\n        yield_reg,\n        jump_on_definition: definition_label,\n        start_offset: start_offset_label,\n    });\n    program.preassign_label_to_next_insn(start_offset_label);\n\n    let start_reg = emit_values_in_subquery(program, plan, t_ctx, yield_reg)?;\n\n    program.emit_insn(Insn::EndCoroutine { yield_reg });\n    program.preassign_label_to_next_insn(definition_label);\n\n    program.emit_insn(Insn::InitCoroutine {\n        yield_reg,\n        jump_on_definition: BranchOffset::Offset(0),\n        start_offset: start_offset_label,\n    });\n    let end_label = program.allocate_label();\n    let yield_label = program.allocate_label();\n    program.preassign_label_to_next_insn(yield_label);\n    program.emit_insn(Insn::Yield {\n        yield_reg,\n        end_offset: end_label,\n        subtype_clear_start_reg: 0,\n        subtype_clear_count: 0,\n    });\n\n    let goto_label = program.allocate_label();\n    emit_offset(program, goto_label, t_ctx.reg_offset);\n    let row_len = plan.values[0].len();\n    let copy_start_reg = program.alloc_registers(row_len);\n    for i in 0..row_len {\n        program.emit_insn(Insn::Copy {\n            src_reg: start_reg + i,\n            dst_reg: copy_start_reg + i,\n            extra_amount: 0,\n        });\n    }\n\n    emit_values_to_destination(program, plan, t_ctx, copy_start_reg, row_len, end_label)?;\n\n    program.preassign_label_to_next_insn(goto_label);\n    program.emit_insn(Insn::Goto {\n        target_pc: yield_label,\n    });\n    program.preassign_label_to_next_insn(end_label);\n\n    Ok(copy_start_reg)\n}\n\nfn emit_values_in_subquery(\n    program: &mut ProgramBuilder,\n    plan: &SelectPlan,\n    t_ctx: &mut TranslateCtx,\n    yield_reg: usize,\n) -> Result<usize> {\n    let row_len = plan.values[0].len();\n    let start_reg = if let Some(reg) = t_ctx.reg_result_cols_start {\n        program.reg_result_cols_start = Some(reg);\n        reg\n    } else {\n        let reg = program.alloc_registers(row_len);\n        t_ctx.reg_result_cols_start = Some(reg);\n        program.reg_result_cols_start = Some(reg);\n        reg\n    };\n    for value in &plan.values {\n        for (i, v) in value.iter().enumerate() {\n            translate_expr_no_constant_opt(\n                program,\n                None,\n                v,\n                start_reg + i,\n                &t_ctx.resolver,\n                NoConstantOptReason::RegisterReuse,\n            )?;\n        }\n        program.emit_insn(Insn::Yield {\n            yield_reg,\n            end_offset: BranchOffset::Offset(0),\n            subtype_clear_start_reg: start_reg,\n            subtype_clear_count: row_len,\n        });\n    }\n\n    Ok(start_reg)\n}\n\nfn emit_values_to_destination(\n    program: &mut ProgramBuilder,\n    plan: &SelectPlan,\n    t_ctx: &TranslateCtx,\n    start_reg: usize,\n    row_len: usize,\n    end_label: BranchOffset,\n) -> Result<()> {\n    match &plan.query_destination {\n        QueryDestination::ResultRows => {\n            program.emit_insn(Insn::ResultRow {\n                start_reg,\n                count: row_len,\n            });\n            if let Some(limit_ctx) = t_ctx.limit_ctx {\n                program.emit_insn(Insn::DecrJumpZero {\n                    reg: limit_ctx.reg_limit,\n                    target_pc: end_label,\n                });\n            }\n        }\n        QueryDestination::CoroutineYield { yield_reg, .. } => {\n            program.emit_insn(Insn::Yield {\n                yield_reg: *yield_reg,\n                end_offset: BranchOffset::Offset(0),\n                subtype_clear_start_reg: start_reg,\n                subtype_clear_count: row_len,\n            });\n        }\n        QueryDestination::EphemeralIndex { .. } => {\n            emit_values_to_index(program, plan, start_reg, row_len)?;\n        }\n        QueryDestination::EphemeralTable { .. } => {\n            emit_values_to_table(program, plan, start_reg, row_len);\n        }\n        QueryDestination::ExistsSubqueryResult { result_reg } => {\n            program.emit_insn(Insn::Integer {\n                value: 1,\n                dest: *result_reg,\n            });\n        }\n        QueryDestination::RowValueSubqueryResult {\n            result_reg_start,\n            num_regs,\n        } => {\n            turso_assert_eq!(\n                row_len,\n                *num_regs,\n                \"row value subqueries must have matching result columns and registers\"\n            );\n            program.emit_insn(Insn::Copy {\n                src_reg: start_reg,\n                dst_reg: *result_reg_start,\n                extra_amount: num_regs - 1,\n            });\n        }\n        QueryDestination::RowSet { .. } => {\n            unreachable!(\"RowSet query destination should not be used in values emission\")\n        }\n        QueryDestination::Unset => unreachable!(\"Unset query destination should not be reached\"),\n    }\n    Ok(())\n}\n\nfn emit_values_to_index(\n    program: &mut ProgramBuilder,\n    plan: &SelectPlan,\n    start_reg: usize,\n    row_len: usize,\n) -> Result<()> {\n    let (cursor_id, index, affinity_str, is_delete) = match &plan.query_destination {\n        QueryDestination::EphemeralIndex {\n            cursor_id,\n            index,\n            affinity_str,\n            is_delete,\n        } => (cursor_id, index, affinity_str, is_delete),\n        _ => unreachable!(),\n    };\n\n    if row_len != index.columns.len() {\n        crate::bail_corrupt_error!(\n            \"VALUES column count {} does not match index column count {}\",\n            row_len,\n            index.columns.len()\n        );\n    }\n\n    if *is_delete {\n        program.emit_insn(Insn::IdxDelete {\n            start_reg,\n            num_regs: row_len,\n            cursor_id: *cursor_id,\n            raise_error_if_no_matching_entry: false,\n        });\n    } else {\n        let record_reg = program.alloc_register();\n\n        // Seek indexes may reorder key columns relative to the subquery result\n        // column layout, so materialized VALUES rows must be reordered to match\n        // the index key definition before insertion.\n        let needs_reorder = index\n            .columns\n            .iter()\n            .enumerate()\n            .any(|(idx_pos, col)| col.pos_in_table != idx_pos);\n        let extra_for_rowid = usize::from(index.ephemeral && index.has_rowid);\n\n        let (record_start, record_count) = if needs_reorder || extra_for_rowid != 0 {\n            let record_count = row_len + extra_for_rowid;\n            let record_start = program.alloc_registers(record_count);\n\n            if needs_reorder {\n                for (idx_pos, idx_col) in index.columns.iter().enumerate() {\n                    program.emit_insn(Insn::Copy {\n                        src_reg: start_reg + idx_col.pos_in_table,\n                        dst_reg: record_start + idx_pos,\n                        extra_amount: 0,\n                    });\n                }\n            } else {\n                for i in 0..row_len {\n                    program.emit_insn(Insn::Copy {\n                        src_reg: start_reg + i,\n                        dst_reg: record_start + i,\n                        extra_amount: 0,\n                    });\n                }\n            }\n\n            if extra_for_rowid != 0 {\n                program.emit_insn(Insn::Sequence {\n                    cursor_id: *cursor_id,\n                    target_reg: record_start + row_len,\n                });\n            }\n\n            (record_start, record_count)\n        } else {\n            (start_reg, row_len)\n        };\n\n        program.emit_insn(Insn::MakeRecord {\n            start_reg: to_u16(record_start),\n            count: to_u16(record_count),\n            dest_reg: to_u16(record_reg),\n            index_name: Some(index.name.clone()),\n            affinity_str: affinity_str.as_ref().map(|s| (**s).clone()),\n        });\n        program.emit_insn(Insn::IdxInsert {\n            cursor_id: *cursor_id,\n            record_reg,\n            unpacked_start: None,\n            unpacked_count: None,\n            flags: IdxInsertFlags::new().no_op_duplicate(),\n        });\n    }\n    Ok(())\n}\n\nfn emit_values_to_table(\n    program: &mut ProgramBuilder,\n    plan: &SelectPlan,\n    start_reg: usize,\n    row_len: usize,\n) {\n    let (cursor_id, table) = match &plan.query_destination {\n        QueryDestination::EphemeralTable {\n            cursor_id, table, ..\n        } => (cursor_id, table),\n        _ => unreachable!(),\n    };\n    let record_reg = program.alloc_register();\n    let rowid_reg = program.alloc_register();\n    program.emit_insn(Insn::MakeRecord {\n        start_reg: to_u16(start_reg),\n        count: to_u16(row_len),\n        dest_reg: to_u16(record_reg),\n        index_name: Some(table.name.clone()),\n        affinity_str: None,\n    });\n    program.emit_insn(Insn::NewRowid {\n        cursor: *cursor_id,\n        rowid_reg,\n        prev_largest_reg: 0,\n    });\n    program.emit_insn(Insn::Insert {\n        cursor: *cursor_id,\n        key_reg: rowid_reg,\n        record_reg,\n        flag: InsertFlags::new().is_ephemeral_table_insert(),\n        table_name: table.name.clone(),\n    });\n}\n"
  },
  {
    "path": "core/translate/view.rs",
    "content": "use crate::schema::{SchemaObjectType, DBSP_TABLE_PREFIX, RESERVED_TABLE_PREFIXES};\nuse crate::storage::pager::CreateBTreeFlags;\nuse crate::sync::Arc;\nuse crate::translate::emitter::Resolver;\nuse crate::translate::schema::{emit_schema_entry, SchemaEntryType, SQLITE_TABLEID};\nuse crate::util::{\n    escape_sql_string_literal, normalize_ident, PRIMARY_KEY_AUTOMATIC_INDEX_NAME_PREFIX,\n};\nuse crate::vdbe::builder::{CursorType, ProgramBuilder};\nuse crate::vdbe::insn::{CmpInsFlags, Cookie, Insn, RegisterOrLiteral};\nuse crate::{bail_parse_error, Connection, Result};\nuse turso_parser::ast;\n\npub fn translate_create_materialized_view(\n    view_name: &ast::QualifiedName,\n    resolver: &Resolver,\n    select_stmt: &ast::Select,\n    connection: Arc<Connection>,\n    program: &mut ProgramBuilder,\n) -> Result<()> {\n    // Check if experimental views are enabled\n    if !connection.experimental_views_enabled() {\n        return Err(crate::LimboError::ParseError(\n            \"CREATE MATERIALIZED VIEW is an experimental feature. Enable with --experimental-views flag\"\n                .to_string(),\n        ));\n    }\n\n    let database_id = resolver.resolve_database_id(view_name)?;\n    // The DBSP incremental maintenance runtime (populate_from_table, etc.) assumes\n    // the main database pager/schema. Block attached databases until that is fixed.\n    if database_id != crate::MAIN_DB_ID {\n        crate::bail_parse_error!(\"materialized views are not supported on attached databases\");\n    }\n    if crate::is_attached_db(database_id) {\n        let schema_cookie = resolver.with_schema(database_id, |s| s.schema_version);\n        program.begin_write_on_database(database_id, schema_cookie);\n    }\n\n    let normalized_view_name = normalize_ident(view_name.name.as_str());\n    if RESERVED_TABLE_PREFIXES\n        .iter()\n        .any(|prefix| normalized_view_name.starts_with(prefix))\n    {\n        bail_parse_error!(\n            \"Object name reserved for internal use: {}\",\n            view_name.name.as_str()\n        );\n    }\n\n    // Check if view already exists\n    if resolver.with_schema(database_id, |s| {\n        s.get_materialized_view(&normalized_view_name).is_some()\n    }) {\n        return Err(crate::LimboError::ParseError(format!(\n            \"View {normalized_view_name} already exists\"\n        )));\n    }\n\n    // Validate the view can be created and extract its columns\n    // This validation happens before updating sqlite_master to prevent\n    // storing invalid view definitions\n\n    // Check for cross-database table references first\n    crate::util::validate_select_for_views(select_stmt, view_name.db_name.as_ref())?;\n\n    use crate::incremental::view::IncrementalView;\n    use crate::schema::BTreeTable;\n    let view_column_schema = resolver.with_schema(database_id, |s| {\n        IncrementalView::validate_and_extract_columns(select_stmt, s)\n    })?;\n    let view_columns = view_column_schema.flat_columns();\n\n    // Reconstruct the SQL string for storage\n    let sql = create_materialized_view_to_str(&view_name.name.as_ident(), select_stmt);\n\n    // Create a btree for storing the materialized view state\n    // This btree will hold the materialized rows (row_id -> values)\n    let view_root_reg = program.alloc_register();\n\n    program.emit_insn(Insn::CreateBtree {\n        db: database_id,\n        root: view_root_reg,\n        flags: CreateBTreeFlags::new_table(),\n    });\n\n    // Create a second btree for DBSP operator state (e.g., aggregate state)\n    // This is stored as a hidden table: __turso_internal_dbsp_state_<view_name>\n    let dbsp_state_root_reg = program.alloc_register();\n\n    program.emit_insn(Insn::CreateBtree {\n        db: database_id,\n        root: dbsp_state_root_reg,\n        flags: CreateBTreeFlags::new_table(),\n    });\n\n    // Create a proper BTreeTable for the cursor with the actual view columns\n    let view_table = Arc::new(BTreeTable {\n        root_page: 0, // Will be set to actual root page after creation\n        name: normalized_view_name.clone(),\n        columns: view_columns,\n        primary_key_columns: vec![], // Materialized views use implicit rowid\n        has_rowid: true,\n        is_strict: false,\n        has_autoincrement: false,\n\n        unique_sets: vec![],\n        foreign_keys: vec![],\n        check_constraints: vec![],\n        rowid_alias_conflict_clause: None,\n    });\n\n    // Allocate a cursor for writing to the view's btree during population\n    let view_cursor_id =\n        program.alloc_cursor_id(crate::vdbe::builder::CursorType::BTreeTable(view_table));\n\n    // Open the cursor to the view's btree\n    program.emit_insn(Insn::OpenWrite {\n        cursor_id: view_cursor_id,\n        root_page: RegisterOrLiteral::Register(view_root_reg),\n        db: database_id,\n    });\n\n    // Clear any existing data in the btree\n    // This is important because if we're reusing a page that previously held\n    // a materialized view, there might be old data still there\n    // We need to start with a clean slate\n    let clear_loop_label = program.allocate_label();\n    let clear_done_label = program.allocate_label();\n\n    // Rewind to the beginning of the btree\n    program.emit_insn(Insn::Rewind {\n        cursor_id: view_cursor_id,\n        pc_if_empty: clear_done_label,\n    });\n\n    // Loop to delete all rows\n    program.preassign_label_to_next_insn(clear_loop_label);\n    program.emit_insn(Insn::Delete {\n        cursor_id: view_cursor_id,\n        table_name: normalized_view_name.clone(),\n        is_part_of_update: false,\n    });\n    program.emit_insn(Insn::Next {\n        cursor_id: view_cursor_id,\n        pc_if_next: clear_loop_label,\n    });\n\n    program.preassign_label_to_next_insn(clear_done_label);\n\n    // Open cursor to sqlite_schema table\n    let table = resolver.with_schema(database_id, |s| s.get_btree_table(SQLITE_TABLEID).unwrap());\n    let sqlite_schema_cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(table));\n    program.emit_insn(Insn::OpenWrite {\n        cursor_id: sqlite_schema_cursor_id,\n        root_page: 1i64.into(),\n        db: database_id,\n    });\n\n    // Add the materialized view entry to sqlite_schema\n    emit_schema_entry(\n        program,\n        resolver,\n        sqlite_schema_cursor_id,\n        None, // cdc_table_cursor_id, no cdc for views\n        SchemaEntryType::View,\n        &normalized_view_name,\n        &normalized_view_name,\n        view_root_reg, // btree root for materialized view data\n        Some(sql),\n    )?;\n\n    // Add the DBSP state table to sqlite_master (required for materialized views)\n    // Include the version number in the table name\n    use crate::incremental::compiler::DBSP_CIRCUIT_VERSION;\n    let dbsp_table_name = ast::Name::exact(format!(\n        \"{DBSP_TABLE_PREFIX}{DBSP_CIRCUIT_VERSION}_{normalized_view_name}\"\n    ));\n    let dbsp_table_ident = dbsp_table_name.as_ident();\n    // The element_id column uses SQLite's dynamic typing system to store different value types:\n    // - For hash-based operators (joins, filters): stores INTEGER hash values or rowids\n    // - For future MIN/MAX operators: stores the actual values being compared (INTEGER, REAL, TEXT, BLOB)\n    // SQLite's type affinity and sorting rules ensure correct ordering within each operator's data\n    let dbsp_sql = format!(\n        \"CREATE TABLE {dbsp_table_ident} (\\\n         operator_id INTEGER NOT NULL, \\\n         zset_id BLOB NOT NULL, \\\n         element_id BLOB NOT NULL, \\\n         value BLOB, \\\n         weight INTEGER NOT NULL, \\\n         PRIMARY KEY (operator_id, zset_id, element_id)\\\n        )\"\n    );\n\n    emit_schema_entry(\n        program,\n        resolver,\n        sqlite_schema_cursor_id,\n        None, // cdc_table_cursor_id\n        SchemaEntryType::Table,\n        dbsp_table_name.as_str(),\n        dbsp_table_name.as_str(),\n        dbsp_state_root_reg, // Root for DBSP state table\n        Some(dbsp_sql),\n    )?;\n\n    // Create automatic primary key index for the DBSP table\n    // Since the table has PRIMARY KEY (operator_id, zset_id, element_id), we need an index\n    let dbsp_index_root_reg = program.alloc_register();\n    program.emit_insn(Insn::CreateBtree {\n        db: database_id,\n        root: dbsp_index_root_reg,\n        flags: CreateBTreeFlags::new_index(),\n    });\n\n    // Register the index in sqlite_schema\n    let dbsp_index_name = format!(\n        \"{}{}_1\",\n        PRIMARY_KEY_AUTOMATIC_INDEX_NAME_PREFIX,\n        &dbsp_table_name.as_str()\n    );\n    emit_schema_entry(\n        program,\n        resolver,\n        sqlite_schema_cursor_id,\n        None, // cdc_table_cursor_id\n        SchemaEntryType::Index,\n        &dbsp_index_name,\n        dbsp_table_name.as_str(),\n        dbsp_index_root_reg,\n        None, // Automatic indexes don't store SQL\n    )?;\n\n    // Parse schema to load the new view and DBSP state table\n    let escaped_view_name = escape_sql_string_literal(&normalized_view_name);\n    let escaped_dbsp_table_name = escape_sql_string_literal(dbsp_table_name.as_str());\n    let escaped_dbsp_index_name = escape_sql_string_literal(&dbsp_index_name);\n    program.emit_insn(Insn::ParseSchema {\n        db: database_id,\n        where_clause: Some(format!(\n            \"name = '{escaped_view_name}' OR name = '{escaped_dbsp_table_name}' OR name = '{escaped_dbsp_index_name}'\"\n        )),\n    });\n\n    let schema_version = resolver.with_schema(database_id, |s| s.schema_version);\n    program.emit_insn(Insn::SetCookie {\n        db: database_id,\n        cookie: Cookie::SchemaVersion,\n        value: (schema_version + 1) as i32,\n        p5: 0,\n    });\n\n    // Populate the materialized view\n    let cursor_info = vec![(normalized_view_name.clone(), view_cursor_id)];\n    program.emit_insn(Insn::PopulateMaterializedViews {\n        cursors: cursor_info,\n    });\n\n    program.epilogue(resolver.schema());\n    Ok(())\n}\n\nfn create_materialized_view_to_str(view_name: &str, select_stmt: &ast::Select) -> String {\n    format!(\"CREATE MATERIALIZED VIEW {view_name} AS {select_stmt}\")\n}\n\npub fn translate_create_view(\n    view_name: &ast::QualifiedName,\n    resolver: &Resolver,\n    select_stmt: &ast::Select,\n    columns: &[ast::IndexedColumn],\n    program: &mut ProgramBuilder,\n) -> Result<()> {\n    let database_id = resolver.resolve_database_id(view_name)?;\n    if crate::is_attached_db(database_id) {\n        let schema_cookie = resolver.with_schema(database_id, |s| s.schema_version);\n        program.begin_write_on_database(database_id, schema_cookie);\n    }\n    let normalized_view_name = normalize_ident(view_name.name.as_str());\n\n    if RESERVED_TABLE_PREFIXES\n        .iter()\n        .any(|prefix| normalized_view_name.starts_with(prefix))\n    {\n        bail_parse_error!(\n            \"Object name reserved for internal use: {}\",\n            view_name.name.as_str()\n        );\n    }\n\n    // Check for name conflicts with existing schema objects\n    if let Some(object_type) =\n        resolver.with_schema(database_id, |s| s.get_object_type(&normalized_view_name))\n    {\n        let type_str = match object_type {\n            SchemaObjectType::Table => \"table\",\n            SchemaObjectType::View => \"view\",\n            SchemaObjectType::Index => \"index\",\n        };\n        return Err(crate::LimboError::ParseError(format!(\n            \"{type_str} {normalized_view_name} already exists\"\n        )));\n    }\n\n    // Also check materialized views (not in get_object_type since they're stored differently)\n    if resolver\n        .with_schema(database_id, |s| {\n            s.get_materialized_view(&normalized_view_name)\n        })\n        .is_some()\n    {\n        return Err(crate::LimboError::ParseError(format!(\n            \"view {normalized_view_name} already exists\"\n        )));\n    }\n\n    crate::util::validate_select_for_views(select_stmt, view_name.db_name.as_ref())?;\n\n    // Reconstruct the SQL string\n    let sql = create_view_to_str(&view_name.name.as_ident(), columns, select_stmt);\n\n    // Open cursor to sqlite_schema table\n    let table = resolver.schema().get_btree_table(SQLITE_TABLEID).unwrap();\n    let sqlite_schema_cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(table));\n    program.emit_insn(Insn::OpenWrite {\n        cursor_id: sqlite_schema_cursor_id,\n        root_page: 1i64.into(),\n        db: database_id,\n    });\n\n    // Add the view entry to sqlite_schema\n    emit_schema_entry(\n        program,\n        resolver,\n        sqlite_schema_cursor_id,\n        None, // cdc_table_cursor_id, no cdc for views\n        SchemaEntryType::View,\n        &normalized_view_name,\n        &normalized_view_name,\n        0, // Regular views don't have a btree\n        Some(sql),\n    )?;\n\n    // Parse schema to load the new view\n    let escaped_view_name = escape_sql_string_literal(&normalized_view_name);\n    program.emit_insn(Insn::ParseSchema {\n        db: database_id,\n        where_clause: Some(format!(\"name = '{escaped_view_name}'\")),\n    });\n\n    let schema_version = resolver.with_schema(database_id, |s| s.schema_version);\n    program.emit_insn(Insn::SetCookie {\n        db: database_id,\n        cookie: Cookie::SchemaVersion,\n        value: (schema_version + 1) as i32,\n        p5: 0,\n    });\n\n    Ok(())\n}\n\nfn create_view_to_str(\n    view_name: &str,\n    columns: &[ast::IndexedColumn],\n    select_stmt: &ast::Select,\n) -> String {\n    let columns_str = columns\n        .iter()\n        .map(|col| col.col_name.as_str())\n        .collect::<Vec<&str>>()\n        .join(\", \");\n    if !columns_str.is_empty() {\n        return format!(\"CREATE VIEW {view_name} ({columns_str}) AS {select_stmt}\");\n    }\n    format!(\"CREATE VIEW {view_name} AS {select_stmt}\")\n}\n\npub fn translate_drop_view(\n    resolver: &Resolver,\n    view_name: &ast::QualifiedName,\n    if_exists: bool,\n    program: &mut ProgramBuilder,\n) -> Result<()> {\n    let database_id = resolver.resolve_database_id(view_name)?;\n    if crate::is_attached_db(database_id) {\n        let schema_cookie = resolver.with_schema(database_id, |s| s.schema_version);\n        program.begin_write_on_database(database_id, schema_cookie);\n    }\n    let normalized_view_name = normalize_ident(view_name.name.as_str());\n\n    // Check if view exists (either regular or materialized)\n    let (is_regular_view, is_materialized_view) = resolver.with_schema(database_id, |s| {\n        (\n            s.get_view(&normalized_view_name).is_some(),\n            s.is_materialized_view(&normalized_view_name),\n        )\n    });\n    let view_exists = is_regular_view || is_materialized_view;\n\n    if !view_exists && !if_exists {\n        return Err(crate::LimboError::ParseError(format!(\n            \"no such view: {normalized_view_name}\"\n        )));\n    }\n\n    if !view_exists && if_exists {\n        // View doesn't exist but IF EXISTS was specified, nothing to do\n        return Ok(());\n    }\n\n    // If this is a materialized view, we need to destroy its btree as well\n    // and also clean up the associated DBSP state table and index\n    let dbsp_table_name = if is_materialized_view {\n        if let Some(table) =\n            resolver.with_schema(database_id, |s| s.get_table(&normalized_view_name))\n        {\n            if let Some(btree_table) = table.btree() {\n                // Destroy the btree for the materialized view\n                program.emit_insn(Insn::Destroy {\n                    db: database_id,\n                    root: btree_table.root_page,\n                    former_root_reg: 0, // No autovacuum\n                    is_temp: 0,\n                });\n            }\n        }\n\n        // Construct the DBSP state table name\n        use crate::incremental::compiler::DBSP_CIRCUIT_VERSION;\n        Some(format!(\n            \"{DBSP_TABLE_PREFIX}{DBSP_CIRCUIT_VERSION}_{normalized_view_name}\"\n        ))\n    } else {\n        None\n    };\n\n    // Destroy DBSP state table and index btrees if this is a materialized view\n    if let Some(ref dbsp_table_name) = dbsp_table_name {\n        // Destroy DBSP indexes first\n        let dbsp_indexes: Vec<_> = resolver.with_schema(database_id, |s| {\n            s.get_indices(dbsp_table_name).cloned().collect()\n        });\n        for index in &dbsp_indexes {\n            program.emit_insn(Insn::Destroy {\n                db: database_id,\n                root: index.root_page,\n                former_root_reg: 0, // No autovacuum\n                is_temp: 0,\n            });\n        }\n\n        // Destroy DBSP state table btree\n        if let Some(dbsp_table) =\n            resolver.with_schema(database_id, |s| s.get_table(dbsp_table_name))\n        {\n            if let Some(dbsp_btree_table) = dbsp_table.btree() {\n                program.emit_insn(Insn::Destroy {\n                    db: database_id,\n                    root: dbsp_btree_table.root_page,\n                    former_root_reg: 0, // No autovacuum\n                    is_temp: 0,\n                });\n            }\n        }\n    }\n\n    // Open cursor to sqlite_schema table (structure is the same for all databases)\n    let schema_table = resolver.with_schema(0, |s| s.get_btree_table(SQLITE_TABLEID).unwrap());\n    let sqlite_schema_cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(schema_table));\n    program.emit_insn(Insn::OpenWrite {\n        cursor_id: sqlite_schema_cursor_id,\n        root_page: 1i64.into(),\n        db: database_id,\n    });\n\n    // Allocate registers for searching\n    let view_name_reg = program.alloc_register();\n    let type_reg = program.alloc_register();\n    let rowid_reg = program.alloc_register();\n\n    // Set the view name and type we're looking for\n    program.emit_insn(Insn::String8 {\n        dest: view_name_reg,\n        value: normalized_view_name.clone(),\n    });\n    program.emit_insn(Insn::String8 {\n        dest: type_reg,\n        value: \"view\".to_string(),\n    });\n\n    // Start scanning from the beginning\n    let end_loop_label = program.allocate_label();\n    let loop_start_label = program.allocate_label();\n\n    program.emit_insn(Insn::Rewind {\n        cursor_id: sqlite_schema_cursor_id,\n        pc_if_empty: end_loop_label,\n    });\n    program.preassign_label_to_next_insn(loop_start_label);\n\n    // Check if this row should be deleted\n    // Column 0 is type, Column 1 is name, Column 2 is tbl_name\n    let col0_reg = program.alloc_register();\n    let col1_reg = program.alloc_register();\n\n    program.emit_column_or_rowid(sqlite_schema_cursor_id, 0, col0_reg);\n    program.emit_column_or_rowid(sqlite_schema_cursor_id, 1, col1_reg);\n\n    // Check if this row matches the view, DBSP table, or DBSP index\n    let skip_delete_label = program.allocate_label();\n\n    // Check if this is the view entry (type='view' and name=view_name)\n    program.emit_insn(Insn::Ne {\n        lhs: col0_reg,\n        rhs: type_reg,\n        target_pc: skip_delete_label,\n        flags: CmpInsFlags::default(),\n        collation: program.curr_collation(),\n    });\n    program.emit_insn(Insn::Ne {\n        lhs: col1_reg,\n        rhs: view_name_reg,\n        target_pc: skip_delete_label,\n        flags: CmpInsFlags::default(),\n        collation: program.curr_collation(),\n    });\n    // Matches view - delete it\n    program.emit_insn(Insn::RowId {\n        cursor_id: sqlite_schema_cursor_id,\n        dest: rowid_reg,\n    });\n    program.emit_insn(Insn::Delete {\n        cursor_id: sqlite_schema_cursor_id,\n        table_name: \"sqlite_schema\".to_string(),\n        is_part_of_update: false,\n    });\n\n    program.resolve_label(skip_delete_label, program.offset());\n\n    // Move to next row\n    program.emit_insn(Insn::Next {\n        cursor_id: sqlite_schema_cursor_id,\n        pc_if_next: loop_start_label,\n    });\n\n    program.preassign_label_to_next_insn(end_loop_label);\n\n    // If this is a materialized view, delete DBSP table and index entries in a second pass\n    // We do this in a separate loop to ensure we catch all entries even if they come\n    // in different orders in sqlite_schema\n    if let Some(ref dbsp_table_name) = dbsp_table_name {\n        // Set up registers for DBSP table name and types (outside the loop for efficiency)\n        let dbsp_table_name_reg_2 = program.alloc_register();\n        program.emit_insn(Insn::String8 {\n            dest: dbsp_table_name_reg_2,\n            value: dbsp_table_name.clone(),\n        });\n        let table_type_reg_2 = program.alloc_register();\n        program.emit_insn(Insn::String8 {\n            dest: table_type_reg_2,\n            value: \"table\".to_string(),\n        });\n        let index_type_reg_2 = program.alloc_register();\n        program.emit_insn(Insn::String8 {\n            dest: index_type_reg_2,\n            value: \"index\".to_string(),\n        });\n        let dbsp_index_name_reg_2 = program.alloc_register();\n        let dbsp_index_name_2 =\n            format!(\"{PRIMARY_KEY_AUTOMATIC_INDEX_NAME_PREFIX}{dbsp_table_name}_1\");\n        program.emit_insn(Insn::String8 {\n            dest: dbsp_index_name_reg_2,\n            value: dbsp_index_name_2,\n        });\n\n        // Allocate column registers once (outside the loop)\n        let dbsp_col0_reg = program.alloc_register();\n        let dbsp_col1_reg = program.alloc_register();\n\n        // Second pass: delete DBSP table and index entries\n        let dbsp_end_loop_label = program.allocate_label();\n        let dbsp_loop_start_label = program.allocate_label();\n\n        program.emit_insn(Insn::Rewind {\n            cursor_id: sqlite_schema_cursor_id,\n            pc_if_empty: dbsp_end_loop_label,\n        });\n        program.preassign_label_to_next_insn(dbsp_loop_start_label);\n\n        // Read columns for this row (reusing the same registers)\n        program.emit_column_or_rowid(sqlite_schema_cursor_id, 0, dbsp_col0_reg);\n        program.emit_column_or_rowid(sqlite_schema_cursor_id, 1, dbsp_col1_reg);\n\n        let dbsp_skip_delete_label = program.allocate_label();\n\n        // Check if this is the DBSP table entry (type='table' and name=dbsp_table_name)\n        let check_dbsp_index_label = program.allocate_label();\n        program.emit_insn(Insn::Ne {\n            lhs: dbsp_col0_reg,\n            rhs: table_type_reg_2,\n            target_pc: check_dbsp_index_label,\n            flags: CmpInsFlags::default(),\n            collation: program.curr_collation(),\n        });\n        program.emit_insn(Insn::Ne {\n            lhs: dbsp_col1_reg,\n            rhs: dbsp_table_name_reg_2,\n            target_pc: check_dbsp_index_label,\n            flags: CmpInsFlags::default(),\n            collation: program.curr_collation(),\n        });\n        // Matches DBSP table - delete it\n        program.emit_insn(Insn::RowId {\n            cursor_id: sqlite_schema_cursor_id,\n            dest: rowid_reg,\n        });\n        program.emit_insn(Insn::Delete {\n            cursor_id: sqlite_schema_cursor_id,\n            table_name: \"sqlite_schema\".to_string(),\n            is_part_of_update: false,\n        });\n        program.emit_insn(Insn::Goto {\n            target_pc: dbsp_skip_delete_label,\n        });\n\n        // Check if this is the DBSP index entry (type='index' and name=dbsp_index_name)\n        program.preassign_label_to_next_insn(check_dbsp_index_label);\n        program.emit_insn(Insn::Ne {\n            lhs: dbsp_col0_reg,\n            rhs: index_type_reg_2,\n            target_pc: dbsp_skip_delete_label,\n            flags: CmpInsFlags::default(),\n            collation: program.curr_collation(),\n        });\n        program.emit_insn(Insn::Ne {\n            lhs: dbsp_col1_reg,\n            rhs: dbsp_index_name_reg_2,\n            target_pc: dbsp_skip_delete_label,\n            flags: CmpInsFlags::default(),\n            collation: program.curr_collation(),\n        });\n        // Matches DBSP index - delete it\n        program.emit_insn(Insn::RowId {\n            cursor_id: sqlite_schema_cursor_id,\n            dest: rowid_reg,\n        });\n        program.emit_insn(Insn::Delete {\n            cursor_id: sqlite_schema_cursor_id,\n            table_name: \"sqlite_schema\".to_string(),\n            is_part_of_update: false,\n        });\n\n        program.resolve_label(dbsp_skip_delete_label, program.offset());\n\n        // Move to next row\n        program.emit_insn(Insn::Next {\n            cursor_id: sqlite_schema_cursor_id,\n            pc_if_next: dbsp_loop_start_label,\n        });\n\n        program.preassign_label_to_next_insn(dbsp_end_loop_label);\n    }\n\n    // Remove the view from the in-memory schema\n    program.emit_insn(Insn::DropView {\n        db: database_id,\n        view_name: normalized_view_name,\n    });\n\n    // Update schema version (increment schema cookie)\n    let schema_version = resolver.with_schema(database_id, |s| s.schema_version);\n    let schema_version_reg = program.alloc_register();\n    program.emit_insn(Insn::Integer {\n        dest: schema_version_reg,\n        value: (schema_version + 1) as i64,\n    });\n    program.emit_insn(Insn::SetCookie {\n        db: database_id,\n        cookie: Cookie::SchemaVersion,\n        value: (schema_version + 1) as i32,\n        p5: 1, // update version\n    });\n\n    program.epilogue(resolver.schema());\n    Ok(())\n}\n"
  },
  {
    "path": "core/translate/window.rs",
    "content": "use crate::function::WindowFunc;\nuse crate::schema::{BTreeTable, Table};\nuse crate::sync::Arc;\nuse crate::translate::aggregation::{translate_aggregation_step, AggArgumentSource};\nuse crate::translate::collate::{get_collseq_from_expr, CollationSeq};\nuse crate::translate::emitter::{Resolver, TranslateCtx};\nuse crate::translate::expr::{walk_expr, walk_expr_mut, WalkControl};\nuse crate::translate::order_by::EmitOrderBy;\nuse crate::translate::plan::{\n    Aggregate, Distinctness, JoinOrderMember, JoinedTable, QueryDestination, ResultSetColumn,\n    SelectPlan, TableReferences, Window, WindowFunctionKind,\n};\nuse crate::translate::planner::resolve_window_and_aggregate_functions;\nuse crate::translate::result_row::emit_select_result;\nuse crate::types::KeyInfo;\nuse crate::util::exprs_are_equivalent;\nuse crate::vdbe::builder::{CursorType, ProgramBuilder, TableRefIdCounter};\nuse crate::vdbe::insn::{\n    to_u16, {InsertFlags, Insn},\n};\nuse crate::vdbe::{BranchOffset, CursorID};\nuse crate::Result;\nuse crate::{turso_assert, turso_assert_eq};\nuse std::mem;\nuse turso_parser::ast::Name;\nuse turso_parser::ast::{Expr, FunctionTail, Literal, Over, SortOrder, TableInternalId};\n\nconst SUBQUERY_DATABASE_ID: usize = 0;\n\nstruct WindowSubqueryContext<'a> {\n    resolver: &'a Resolver<'a>,\n    subquery_order_by: &'a mut Vec<(Box<Expr>, SortOrder)>,\n    subquery_result_columns: &'a mut Vec<ResultSetColumn>,\n    subquery_id: &'a TableInternalId,\n}\n\n/// Rewrite a `SELECT` plan for window function processing.\n///\n/// A `SELECT` may reference multiple window definitions, but internally, each `SELECT` plan\n/// operates on **exactly one** window. Multiple window functions may reference the same window.\n///\n/// The original plan is rewritten into a series of nested subqueries, each  bound to a single\n/// window definition. Each subquery produces rows in the order determined by its parent window\n/// definition. The innermost subquery does not have any window assigned to it; instead,\n/// the FROM, WHERE, GROUP BY, and HAVING clauses from the original query are pushed down to it.\n/// The outermost query retains ORDER BY, LIMIT, and OFFSET.\n///\n/// # Examples\n/// ```sql\n/// -- Example 1: Query with one window\n/// SELECT\n///     a+1,\n///     max(b) OVER (PARTITION BY c ORDER BY d),\n///     min(c) OVER (PARTITION BY c ORDER BY d)\n/// FROM t1\n/// ORDER BY e;\n///\n/// -- Rewritten form\n/// SELECT\n///     a+1,\n///     max(b) OVER (PARTITION BY c ORDER BY d),\n///     min(c) OVER (PARTITION BY c ORDER BY d)\n/// FROM (SELECT a, b, c, d, e FROM t1 ORDER BY c, d)\n/// ORDER BY e;\n///\n/// -- Example 2: Query with multiple windows\n/// SELECT\n///     a,\n///     max(b) OVER (PARTITION BY c ORDER BY d),\n///     min(c) OVER (PARTITION BY e ORDER BY f)\n/// FROM t1;\n///\n/// -- Rewritten form\n/// SELECT\n///     a,\n///     max(b) OVER (PARTITION BY c ORDER BY d) AS w1,\n///     w2\n/// FROM (\n///     SELECT\n///         a,\n///         b,\n///         c,\n///         d,\n///         min(c) OVER (PARTITION BY e ORDER BY f) AS w2\n///     FROM (SELECT a, b, c, d, e, f FROM t1 ORDER BY e, f)\n///     ORDER BY c, d\n/// );\n/// ```\npub fn plan_windows(\n    plan: &mut SelectPlan,\n    resolver: &Resolver,\n    table_ref_counter: &mut TableRefIdCounter,\n    windows: &mut Vec<Window>,\n) -> crate::Result<()> {\n    // Remove named windows that are not referenced by any function, as they can be ignored.\n    windows.retain(|w| !w.functions.is_empty());\n\n    if !windows.is_empty() {\n        // Sanity check: this should never happen because the syntax disallows combining VALUES with windows\n        turso_assert!(\n            plan.values.is_empty(),\n            \"VALUES clause with windows is not supported\"\n        );\n    }\n\n    prepare_window_subquery(plan, resolver, table_ref_counter, windows, 0)\n}\n\nfn prepare_window_subquery(\n    outer_plan: &mut SelectPlan,\n    resolver: &Resolver,\n    table_ref_counter: &mut TableRefIdCounter,\n    windows: &mut Vec<Window>,\n    processed_window_count: usize,\n) -> crate::Result<()> {\n    if windows.is_empty() {\n        return Ok(());\n    }\n\n    let mut current_window = windows.swap_remove(0);\n    let mut subquery_result_columns = Vec::new();\n    let mut subquery_order_by = Vec::new();\n    let subquery_id = table_ref_counter.next();\n\n    if current_window.name.is_none() {\n        // This is part of normalizing the window definition. The remaining logic lives in\n        // `rewrite_expr_referencing_current_window`, which replaces inline window definitions\n        // with references by name.\n        //\n        // The goal is to always work with named windows instead of a mix of named and\n        // inline ones. This way, we don’t need to rewrite expressions embedded in inline\n        // definitions (there might be many equivalent definitions per subquery). Instead,\n        // we rewrite the named definition once, and all associated window functions\n        // require no additional processing.\n        //\n        // At this stage, window definitions and window functions are already bound,\n        // so this normalization is purely to keep the plan valid.\n        //\n        // If the generated name is not unique across the entire query, that’s acceptable —\n        // the final plan always associates exactly one window with one subquery.\n        current_window.name = Some(format!(\"window_{processed_window_count}\"));\n    }\n\n    let mut ctx = WindowSubqueryContext {\n        resolver,\n        subquery_order_by: &mut subquery_order_by,\n        subquery_result_columns: &mut subquery_result_columns,\n        subquery_id: &subquery_id,\n    };\n\n    // Build the ORDER BY clause for the subquery by concatenating the window’s PARTITION BY\n    // columns with its ORDER BY columns.This ensures that rows in the subquery are returned\n    // in the correct order for partitioning and window function evaluation.\n    for expr in current_window.partition_by.iter_mut() {\n        append_order_by(outer_plan, expr, &SortOrder::Asc, &mut ctx)?;\n        current_window.deduplicated_partition_by_len = Some(ctx.subquery_result_columns.len())\n    }\n    for (expr, order) in current_window.order_by.iter_mut() {\n        append_order_by(outer_plan, expr, order, &mut ctx)?;\n    }\n\n    // Rewrite expressions from the outer query’s result columns and ORDER BY clause so that\n    // they reference the subquery instead. The original expressions are included in the\n    // subquery’s result columns.\n    for col in outer_plan.result_columns.iter_mut() {\n        rewrite_terminal_expr(\n            &mut outer_plan.aggregates,\n            &mut col.expr,\n            &mut current_window,\n            &mut ctx,\n        )?;\n    }\n    for (expr, _) in outer_plan.order_by.iter_mut() {\n        rewrite_terminal_expr(\n            &mut outer_plan.aggregates,\n            expr,\n            &mut current_window,\n            &mut ctx,\n        )?;\n    }\n\n    // When there is no ORDER BY or PARTITION BY clause, the window function takes zero arguments,\n    // and no other columns are selected (e.g., \"SELECT count() OVER () FROM products\"),\n    // `subquery_result_columns` may be empty. Add a constant expression to keep the query valid.\n    if subquery_result_columns.is_empty() {\n        subquery_result_columns.push(ResultSetColumn {\n            expr: Expr::Literal(Literal::Numeric(\"0\".to_string())),\n            alias: None,\n            contains_aggregates: false,\n        });\n    }\n\n    let new_join_order = vec![JoinOrderMember {\n        table_id: subquery_id,\n        original_idx: 0,\n        is_outer: false,\n    }];\n    let new_table_references = TableReferences::new(\n        vec![],\n        outer_plan.table_references.outer_query_refs().to_vec(),\n    );\n\n    let mut inner_plan = SelectPlan {\n        join_order: mem::replace(&mut outer_plan.join_order, new_join_order),\n        table_references: mem::replace(&mut outer_plan.table_references, new_table_references),\n        result_columns: subquery_result_columns,\n        where_clause: mem::take(&mut outer_plan.where_clause),\n        group_by: mem::take(&mut outer_plan.group_by),\n        order_by: subquery_order_by,\n        aggregates: mem::take(&mut outer_plan.aggregates),\n        limit: None,\n        offset: None,\n        contains_constant_false_condition: false,\n        query_destination: QueryDestination::placeholder_for_subquery(),\n        distinctness: Distinctness::NonDistinct,\n        values: vec![],\n        window: None,\n        non_from_clause_subqueries: vec![],\n        input_cardinality_hint: None,\n        estimated_output_rows: None,\n        simple_aggregate: None,\n    };\n\n    prepare_window_subquery(\n        &mut inner_plan,\n        resolver,\n        table_ref_counter,\n        windows,\n        processed_window_count + 1,\n    )?;\n\n    let subquery = JoinedTable::new_subquery(\n        format!(\"window_subquery_{processed_window_count}\"),\n        inner_plan,\n        None,\n        subquery_id,\n    )?;\n\n    // Verify that the subquery has the expected database ID.\n    // This is required to ensure that assumptions in `rewrite_terminal_expr` are valid.\n    turso_assert_eq!(\n        subquery.database_id,\n        SUBQUERY_DATABASE_ID,\n        \"subquery database id must be SUBQUERY_DATABASE_ID\",\n        {\"SUBQUERY_DATABASE_ID\": SUBQUERY_DATABASE_ID}\n    );\n\n    outer_plan.window = Some(current_window);\n    outer_plan.table_references.add_joined_table(subquery);\n\n    Ok(())\n}\n\nfn append_order_by(\n    plan: &mut SelectPlan,\n    expr: &mut Expr,\n    sort_order: &SortOrder,\n    ctx: &mut WindowSubqueryContext,\n) -> crate::Result<()> {\n    // Deduplicate: if an equivalent expression already exists in the subquery ORDER BY,\n    // skip adding it again. This can happen when the same column appears in both\n    // PARTITION BY and ORDER BY (e.g. OVER (PARTITION BY a ORDER BY a)), and prevents\n    // the optimizer assertion group_by.exprs.len() >= order_by.len() from being violated.\n    let already_exists = ctx\n        .subquery_order_by\n        .iter()\n        .any(|(existing, _)| exprs_are_equivalent(existing, expr));\n    if !already_exists {\n        ctx.subquery_order_by\n            .push((Box::new(expr.clone()), *sort_order));\n    }\n\n    let contains_aggregates =\n        resolve_window_and_aggregate_functions(expr, ctx.resolver, &mut plan.aggregates, None)?;\n    rewrite_expr_as_subquery_column(expr, ctx, contains_aggregates);\n    Ok(())\n}\n\nfn rewrite_terminal_expr(\n    aggregates: &mut Vec<Aggregate>,\n    top_level_expr: &mut Expr,\n    current_window: &mut Window,\n    ctx: &mut WindowSubqueryContext,\n) -> crate::Result<WalkControl> {\n    walk_expr_mut(\n        top_level_expr,\n        &mut |expr: &mut Expr| -> crate::Result<WalkControl> {\n            match expr {\n                Expr::FunctionCall { filter_over, .. }\n                | Expr::FunctionCallStar { filter_over, .. } => {\n                    if filter_over.over_clause.is_none() {\n                        // If the expression is a standard aggregate (non-window), push it down\n                        // to the subquery.\n                        if aggregates\n                            .iter()\n                            .any(|a| exprs_are_equivalent(&a.original_expr, expr))\n                        {\n                            rewrite_expr_as_subquery_column(expr, ctx, true);\n                        }\n                    } else if let Some(window_function) = current_window\n                        .functions\n                        .iter_mut()\n                        .find(|f| exprs_are_equivalent(&f.original_expr, expr))\n                    {\n                        // If the expression is a window function tied to the current window,\n                        // do not push it to the subquery. Instead, rewrite it so its child\n                        // expressions reference the subquery where needed.\n                        rewrite_expr_referencing_current_window(\n                            aggregates,\n                            current_window\n                                .name\n                                .clone()\n                                .expect(\"current_window must always have a name here\"),\n                            ctx,\n                            expr,\n                        )?;\n                        window_function.original_expr = expr.clone();\n\n                        // At this point, the expression and all its children now reference the subquery,\n                        // so further traversal is unnecessary.\n                        return Ok(WalkControl::SkipChildren);\n                    } else {\n                        // This is a window function referencing a different window (not the current one).\n                        // Push the entire expression to the subquery; it will be rewritten later.\n                        rewrite_expr_as_subquery_column(expr, ctx, false);\n                    }\n                }\n                Expr::RowId { .. } | Expr::Column { .. } => {\n                    rewrite_expr_as_subquery_column(expr, ctx, false);\n                }\n                _ => {}\n            }\n\n            Ok(WalkControl::Continue)\n        },\n    )\n}\n\nfn rewrite_expr_referencing_current_window(\n    aggregates: &mut Vec<Aggregate>,\n    window_name: String,\n    ctx: &mut WindowSubqueryContext,\n    expr: &mut Expr,\n) -> crate::Result<()> {\n    fn normalize_over_clause(filter_over: &mut FunctionTail, window_name: &str) {\n        // FILTER clause is not supported yet. Proper checks elsewhere return appropriate\n        // error messages, and this ensures that nothing slips through unnoticed.\n        turso_assert!(\n            filter_over.filter_clause.is_none(),\n            \"FILTER in window functions is not supported\"\n        );\n\n        // Replace inline OVER clause with a reference to the named window.\n        // The window name may be user-provided or planner-generated.\n        *filter_over = FunctionTail {\n            filter_clause: None,\n            over_clause: Some(Over::Name(Name::exact(window_name.to_string()))),\n        };\n    }\n\n    match expr {\n        Expr::FunctionCall {\n            name: _,\n            distinctness: _,\n            args,\n            order_by,\n            filter_over,\n        } => {\n            for arg in args.iter_mut() {\n                let contains_aggregates =\n                    resolve_window_and_aggregate_functions(arg, ctx.resolver, aggregates, None)?;\n                rewrite_expr_as_subquery_column(arg, ctx, contains_aggregates);\n            }\n            turso_assert!(\n                order_by.is_empty(),\n                \"ORDER BY in window functions is not supported\"\n            );\n            normalize_over_clause(filter_over, &window_name);\n        }\n        Expr::FunctionCallStar {\n            filter_over,\n            name: _,\n        } => {\n            normalize_over_clause(filter_over, &window_name);\n        }\n        _ => unreachable!(\"only functions can reference windows\"),\n    }\n    Ok(())\n}\n\n/// Rewrites an expression into a reference to a subquery column.\n/// If the expression was already pushed down, reuses the existing column index.\n/// Otherwise, adds it as a new column in the subquery's result set.\nfn rewrite_expr_as_subquery_column(\n    expr: &mut Expr,\n    ctx: &mut WindowSubqueryContext,\n    contains_aggregates: bool,\n) {\n    let (column_idx, existing) = match ctx\n        .subquery_result_columns\n        .iter()\n        .position(|col| exprs_are_equivalent(&col.expr, expr))\n    {\n        Some(pos) => (pos, true),\n        None => (ctx.subquery_result_columns.len(), false),\n    };\n\n    let subquery_ref = Expr::Column {\n        database: Some(SUBQUERY_DATABASE_ID),\n        table: *ctx.subquery_id,\n        column: column_idx,\n        is_rowid_alias: false,\n    };\n\n    if existing {\n        *expr = subquery_ref;\n    } else {\n        let subquery_expr = mem::replace(expr, subquery_ref);\n        ctx.subquery_result_columns.push(ResultSetColumn {\n            expr: subquery_expr,\n            alias: None,\n            contains_aggregates,\n        });\n    }\n}\n\n#[derive(Debug)]\npub struct WindowMetadata<'a> {\n    pub labels: WindowLabels,\n    pub registers: WindowRegisters,\n    pub cursors: WindowCursors,\n    /// Number of input columns in the source subquery.\n    pub src_column_count: usize,\n    /// Maps expressions in the current query that reference subquery columns\n    /// to their corresponding column indexes in the subquery’s result.\n    pub expressions_referencing_subquery: Vec<(&'a Expr, usize)>,\n    pub buffer_table_name: String,\n}\n\n#[derive(Debug)]\npub struct WindowLabels {\n    /// Address of the subroutine for flushing buffered rows\n    pub flush_buffer: BranchOffset,\n    /// Address of the end of window processing\n    pub window_processing_end: BranchOffset,\n}\n\n#[derive(Debug)]\npub struct WindowRegisters {\n    /// Stores the ROWID of the last row inserted into the buffer table.\n    /// If NULL, we are before inserting the first row of a new partition.\n    pub rowid: usize,\n    /// Start of the register array storing partition key values for the current partition.\n    pub partition_start: Option<usize>,\n    /// Start of the register array storing per-function state for window functions.\n    /// Aggregates use `AggStep` to populate their state.\n    pub acc_start: usize,\n    /// Start of the register array storing per-function outputs. Aggregate windows\n    /// populate these via `AggValue`; window-only functions like ROW_NUMBER()\n    /// keep their running state here.\n    pub acc_result_start: usize,\n    /// Stores the address to which control returns after all buffered rows are flushed.\n    pub flush_buffer_return_offset: usize,\n    /// Start of consecutive registers containing column values for the current row\n    /// read from the subquery.\n    pub src_columns_start: usize,\n    /// Start of the register array storing column values that need to be propagated\n    /// from the subquery to the parent query.\n    pub result_columns_start: usize,\n    /// Start of the register array holding ORDER BY column values for the current row.\n    /// These registers are used to detect whether the current row is a \"peer\"\n    /// (i.e., has identical ORDER BY values to the previous row).\n    pub new_order_by_columns_start: Option<usize>,\n    /// Start of the register array holding ORDER BY column values from the previous row.\n    /// These are used to compare against the current row to determine peer relationships.\n    pub prev_order_by_columns_start: Option<usize>,\n}\n\n#[derive(Debug)]\npub struct WindowCursors {\n    /// Cursor used to read from the ephemeral buffer table\n    pub buffer_read: CursorID,\n    /// Cursor used to write to the ephemeral buffer table\n    pub buffer_write: CursorID,\n}\n\npub struct EmitWindow;\nimpl EmitWindow {\n    pub fn init<'a>(\n        program: &mut ProgramBuilder,\n        t_ctx: &mut TranslateCtx<'a>,\n        window: &'a Window,\n        plan: &SelectPlan,\n        result_columns: &'a [ResultSetColumn],\n        order_by: &'a [(Box<Expr>, SortOrder)],\n    ) -> crate::Result<()> {\n        let joined_tables = &plan.joined_tables();\n        turso_assert_eq!(joined_tables.len(), 1, \"expected only one joined table\");\n\n        let src_table = &joined_tables[0];\n        let reg_src_columns_start =\n            if let Table::FromClauseSubquery(from_clause_subquery) = &src_table.table {\n                from_clause_subquery\n                    .result_columns_start_reg\n                    .expect(\"Subquery result_columns_start_reg must be set\")\n            } else {\n                panic!(\n                    \"expected source table to be a FromClauseSubquery, but got: {:?}\",\n                    src_table.table\n                );\n            };\n        let src_columns = src_table.columns().to_vec();\n        let src_column_count = src_columns.len();\n        let window_name = window.name.clone().expect(\"window name is missing\");\n        let partition_by_len = window\n            .deduplicated_partition_by_len\n            .unwrap_or(window.partition_by.len());\n        let order_by_len = window.order_by.len();\n        let window_function_count = window.functions.len();\n\n        // An ephemeral table used to buffer rows for the current frame\n        let buffer_table = Arc::new(BTreeTable {\n            root_page: 0,\n            // TODO: Generating the name this way may cause collisions with real tables in the\n            //  attached database. Other ephemeral tables are created similarly, so it’s left\n            //  as-is for now. Ideally, there should be a way to mark tables as ephemeral so\n            //  they can be handled differently from regular tables.\n            name: format!(\"buffer_table_{window_name}\"),\n            has_rowid: true,\n            primary_key_columns: vec![],\n            columns: src_columns,\n            is_strict: false,\n            unique_sets: vec![],\n            has_autoincrement: false,\n            foreign_keys: vec![],\n            check_constraints: vec![],\n            rowid_alias_conflict_clause: None,\n        });\n        let cursor_buffer_read =\n            program.alloc_cursor_id(CursorType::BTreeTable(buffer_table.clone()));\n        let cursor_buffer_write =\n            program.alloc_cursor_id(CursorType::BTreeTable(buffer_table.clone()));\n        program.emit_insn(Insn::OpenEphemeral {\n            cursor_id: cursor_buffer_read,\n            is_table: true,\n        });\n        program.emit_insn(Insn::OpenDup {\n            original_cursor_id: cursor_buffer_read,\n            new_cursor_id: cursor_buffer_write,\n        });\n\n        // Window function processing is similar to aggregation processing in how results are mapped\n        // to registers. Each function expression is stored in `expr_to_reg_cache` along with its\n        // result register. Later, when bytecode generation encounters the expression, the value is\n        // copied from the result register instead of generating code to evaluate the expression.\n        let reg_acc_start = program.alloc_registers(window_function_count);\n        let reg_acc_result_start = program.alloc_registers(window_function_count);\n        for (i, func) in window.functions.iter().enumerate() {\n            t_ctx.resolver.cache_expr_reg(\n                std::borrow::Cow::Borrowed(&func.original_expr),\n                reg_acc_result_start + i,\n                false,\n                None,\n            );\n        }\n\n        // The same approach applies to expressions referencing the subquery (columns).\n        // Instead of reading directly from the subquery, we redirect them to the corresponding\n        // result registers. This is necessary because rows are buffered in an ephemeral table and\n        // returned according to the rules of the window definition.\n        let expressions_referencing_subquery = collect_expressions_referencing_subquery(\n            result_columns,\n            order_by,\n            &src_table.internal_id,\n        )?;\n        let reg_col_start = program.alloc_registers(expressions_referencing_subquery.len());\n        for (i, (expr, _)) in expressions_referencing_subquery.iter().enumerate() {\n            t_ctx.resolver.cache_scalar_expr_reg(\n                std::borrow::Cow::Borrowed(expr),\n                reg_col_start + i,\n                false,\n                &plan.table_references,\n            )?;\n        }\n\n        t_ctx.meta_window = Some(WindowMetadata {\n            labels: WindowLabels {\n                flush_buffer: program.allocate_label(),\n                window_processing_end: program.allocate_label(),\n            },\n            registers: WindowRegisters {\n                rowid: program.alloc_registers_and_init_w_null(1),\n                partition_start: if partition_by_len > 0 {\n                    Some(program.alloc_registers_and_init_w_null(partition_by_len))\n                } else {\n                    None\n                },\n                acc_start: reg_acc_start,\n                acc_result_start: reg_acc_result_start,\n                flush_buffer_return_offset: program.alloc_register(),\n                src_columns_start: reg_src_columns_start,\n                result_columns_start: reg_col_start,\n                prev_order_by_columns_start: alloc_optional_registers(program, order_by_len),\n                new_order_by_columns_start: alloc_optional_registers(program, order_by_len),\n            },\n            cursors: WindowCursors {\n                buffer_read: cursor_buffer_read,\n                buffer_write: cursor_buffer_write,\n            },\n            src_column_count,\n            expressions_referencing_subquery,\n            buffer_table_name: buffer_table.name.clone(),\n        });\n\n        Ok(())\n    }\n    /// Emits bytecode to process a single row of the window’s input (always a subquery).\n    ///\n    /// Note:\n    /// The **buffer table** mentioned below is an ephemeral B-tree that temporarily\n    /// stores rows for the current window frame.\n    ///\n    /// High-level overview:\n    /// - Each row from the subquery is read, and its ORDER BY columns are loaded into\n    ///   dedicated registers for comparison and partitioning purposes.\n    /// - If the row starts a new partition (based on PARTITION BY columns), the buffer\n    ///   and accumulators are flushed or reset as needed.\n    /// - Rows are compared against the previous row to determine if they are \"peers\"\n    ///   (i.e., have the same ORDER BY values). Non-peer rows may trigger flushing\n    ///   of intermediate results.\n    /// - The row is then inserted into the window’s buffer table.\n    /// - Aggregate steps for any window functions are executed.\n    pub fn emit_window_loop_source(\n        program: &mut ProgramBuilder,\n        t_ctx: &mut TranslateCtx,\n        plan: &SelectPlan,\n    ) -> crate::Result<()> {\n        let WindowMetadata {\n            labels,\n            registers,\n            cursors,\n            src_column_count: input_column_count,\n            buffer_table_name,\n            ..\n        } = t_ctx.meta_window.as_ref().expect(\"missing window metadata\");\n        let window = plan.window.as_ref().expect(\"missing window\");\n\n        emit_load_order_by_columns(program, window, registers);\n        emit_flush_buffer_if_new_partition(program, labels, registers, window, plan)?;\n        emit_reset_state_if_new_partition(program, registers, window);\n        emit_flush_buffer_if_not_peer(program, labels, registers, window, plan)?;\n        emit_insert_row_into_buffer(\n            program,\n            registers,\n            cursors,\n            input_column_count,\n            buffer_table_name,\n        );\n        emit_aggregation_step(program, window, &t_ctx.resolver, plan, registers)?;\n\n        Ok(())\n    }\n}\n\nfn alloc_optional_registers(program: &mut ProgramBuilder, count: usize) -> Option<usize> {\n    if count > 0 {\n        Some(program.alloc_registers(count))\n    } else {\n        None\n    }\n}\n\nfn collect_expressions_referencing_subquery<'a>(\n    result_columns: &'a [ResultSetColumn],\n    order_by: &'a [(Box<Expr>, SortOrder)],\n    subquery_id: &TableInternalId,\n) -> crate::Result<Vec<(&'a Expr, usize)>> {\n    let mut expressions_referencing_subquery: Vec<(&'a Expr, usize)> = Vec::new();\n\n    for root_expr in result_columns\n        .iter()\n        .map(|col| &col.expr)\n        .chain(order_by.iter().map(|(e, _)| e.as_ref()))\n    {\n        walk_expr(\n            root_expr,\n            &mut |expr: &Expr| -> crate::Result<WalkControl> {\n                match expr {\n                    Expr::FunctionCall { filter_over, .. }\n                    | Expr::FunctionCallStar { filter_over, .. } => {\n                        if filter_over.over_clause.is_some() {\n                            return Ok(WalkControl::SkipChildren);\n                        }\n                    }\n                    Expr::Column { column, table, .. } => {\n                        turso_assert_eq!(\n                            table,\n                            subquery_id,\n                            \"only subquery columns can be referenced\"\n                        );\n                        if expressions_referencing_subquery\n                            .iter()\n                            .all(|(_, existing_column)| column != existing_column)\n                        {\n                            expressions_referencing_subquery.push((expr, *column));\n                        }\n                    }\n                    _ => {}\n                };\n                Ok(WalkControl::Continue)\n            },\n        )?;\n    }\n\n    Ok(expressions_referencing_subquery)\n}\n\nfn emit_flush_buffer_if_new_partition(\n    program: &mut ProgramBuilder,\n    labels: &WindowLabels,\n    registers: &WindowRegisters,\n    window: &Window,\n    plan: &SelectPlan,\n) -> Result<()> {\n    if let Some(reg_partition_start) = registers.partition_start {\n        let same_partition_label = program.allocate_label();\n        let new_partition_label = program.allocate_label();\n\n        // Compare the first `deduplicated_partition_by_len` source columns with the saved\n        // partition keys. If they differ, this row starts a new partition and we flush the buffer.\n        let partition_by_len = window\n            .deduplicated_partition_by_len\n            .expect(\"deduplicated_partition_by_len must exist\");\n\n        program.add_comment(\n            program.offset(),\n            \"compare partition keys to detect new partition\",\n        );\n        let mut compare_key_info = (0..partition_by_len)\n            .map(|_| KeyInfo {\n                sort_order: SortOrder::Asc,\n                collation: CollationSeq::default(),\n            })\n            .collect::<Vec<_>>();\n        for (i, c) in compare_key_info\n            .iter_mut()\n            .enumerate()\n            .take(partition_by_len)\n        {\n            // After rewriting, partition_by entries are Expr::Column references to the\n            // subquery. Duplicates reference the same column index, so we find the entry\n            // that references column i (the i-th unique partition column) to get the\n            // correct collation.\n            let expr = window\n                .partition_by\n                .iter()\n                .find(|e| matches!(e, Expr::Column { column, .. } if *column == i))\n                .unwrap_or(&window.partition_by[i]);\n            let maybe_collation = get_collseq_from_expr(expr, &plan.table_references)?;\n            c.collation = maybe_collation.unwrap_or_default();\n        }\n        program.emit_insn(Insn::Compare {\n            start_reg_a: registers.src_columns_start,\n            start_reg_b: reg_partition_start,\n            count: partition_by_len,\n            key_info: compare_key_info,\n        });\n        program.emit_insn(Insn::Jump {\n            target_pc_lt: new_partition_label,\n            target_pc_eq: same_partition_label,\n            target_pc_gt: new_partition_label,\n        });\n\n        program.resolve_label(new_partition_label, program.offset());\n        program.add_comment(program.offset(), \"detected new partition\");\n        program.emit_insn(Insn::Gosub {\n            target_pc: labels.flush_buffer,\n            return_reg: registers.flush_buffer_return_offset,\n        });\n        // Reset rowid to signal the start of processing a new partition.\n        program.emit_insn(Insn::Null {\n            dest: registers.rowid,\n            dest_end: None,\n        });\n        program.emit_insn(Insn::Copy {\n            src_reg: registers.src_columns_start,\n            dst_reg: reg_partition_start,\n            extra_amount: partition_by_len - 1,\n        });\n\n        program.resolve_label(same_partition_label, program.offset());\n    }\n\n    Ok(())\n}\n\nfn emit_reset_state_if_new_partition(\n    program: &mut ProgramBuilder,\n    registers: &WindowRegisters,\n    window: &Window,\n) {\n    let label_skip_reset_state = program.allocate_label();\n\n    // If rowid is null, it means we are starting a new partition. It was either set by the code\n    // initializing window processing or by code detecting the start of a new partition.\n    program.emit_insn(Insn::NotNull {\n        reg: registers.rowid,\n        target_pc: label_skip_reset_state,\n    });\n    if let Some(dst_reg_start) = registers.new_order_by_columns_start {\n        // Initialize previous ORDER BY values for the new partition. The first row of the\n        // partition is compared to itself, not to the row from the previous partition.\n        program.add_comment(\n            program.offset(),\n            \"initialize previous peer register for new partition\",\n        );\n        program.emit_insn(Insn::Copy {\n            src_reg: dst_reg_start,\n            dst_reg: registers\n                .prev_order_by_columns_start\n                .expect(\"prev_order_by_columns_start must exist\"),\n            extra_amount: window.order_by.len() - 1,\n        });\n    }\n    // Since this is a new partition, we must reset accumulator registers.\n    program.add_comment(program.offset(), \"reset accumulator registers\");\n    program.emit_insn(Insn::Null {\n        dest: registers.acc_start,\n        dest_end: Some(registers.acc_start + window.functions.len() - 1),\n    });\n    for (i, func) in window.functions.iter().enumerate() {\n        if matches!(func.func, WindowFunctionKind::Window(WindowFunc::RowNumber)) {\n            program.emit_int(0, registers.acc_result_start + i);\n        }\n    }\n\n    program.preassign_label_to_next_insn(label_skip_reset_state);\n}\n\nfn emit_flush_buffer_if_not_peer(\n    program: &mut ProgramBuilder,\n    labels: &WindowLabels,\n    registers: &WindowRegisters,\n    window: &Window,\n    plan: &SelectPlan,\n) -> Result<()> {\n    if let Some(reg_new_order_by_columns_start) = registers.new_order_by_columns_start {\n        let label_peer = program.allocate_label();\n        let label_not_peer = program.allocate_label();\n        let order_by_len = window.order_by.len();\n        let reg_prev_order_by_columns_start = registers\n            .prev_order_by_columns_start\n            .expect(\"prev_order_by_columns_start must exist\");\n\n        program.add_comment(program.offset(), \"compare ORDER BY columns to detect peer\");\n        let mut compare_key_info = (0..window.order_by.len())\n            .map(|_| KeyInfo {\n                sort_order: SortOrder::Asc,\n                collation: CollationSeq::default(),\n            })\n            .collect::<Vec<_>>();\n        for (i, c) in compare_key_info\n            .iter_mut()\n            .enumerate()\n            .take(window.order_by.len())\n        {\n            let maybe_collation =\n                get_collseq_from_expr(&window.order_by[i].0, &plan.table_references)?;\n            c.collation = maybe_collation.unwrap_or_default();\n        }\n        program.emit_insn(Insn::Compare {\n            start_reg_a: reg_prev_order_by_columns_start,\n            start_reg_b: reg_new_order_by_columns_start,\n            count: order_by_len,\n            key_info: compare_key_info,\n        });\n        program.emit_insn(Insn::Jump {\n            target_pc_lt: label_not_peer,\n            target_pc_eq: label_peer,\n            target_pc_gt: label_not_peer,\n        });\n\n        program.resolve_label(label_not_peer, program.offset());\n        program.add_comment(program.offset(), \"detected non-peer row\");\n        program.emit_insn(Insn::Gosub {\n            target_pc: labels.flush_buffer,\n            return_reg: registers.flush_buffer_return_offset,\n        });\n        program.emit_insn(Insn::Copy {\n            src_reg: reg_new_order_by_columns_start,\n            dst_reg: reg_prev_order_by_columns_start,\n            extra_amount: order_by_len - 1,\n        });\n\n        program.resolve_label(label_peer, program.offset());\n    }\n\n    Ok(())\n}\n\nfn emit_load_order_by_columns(\n    program: &mut ProgramBuilder,\n    window: &Window,\n    registers: &WindowRegisters,\n) {\n    if let Some(reg_new_order_by_columns_start) = registers.new_order_by_columns_start {\n        // Source columns are deduplicated and may appear in a different order than\n        // the ORDER BY terms. Therefore, we must restore the original ORDER BY layout\n        // here by copying the values into an array of registers.\n        for (i, (expr, _)) in window.order_by.iter().enumerate() {\n            match expr {\n                Expr::Column { column, .. } => {\n                    program.emit_insn(Insn::Copy {\n                        src_reg: registers.src_columns_start + column,\n                        dst_reg: reg_new_order_by_columns_start + i,\n                        extra_amount: 0,\n                    });\n                }\n                _ => unreachable!(\"expected Column, got {:?}\", expr),\n            }\n        }\n    }\n}\n\nfn emit_insert_row_into_buffer(\n    program: &mut ProgramBuilder,\n    registers: &WindowRegisters,\n    cursors: &WindowCursors,\n    input_column_count: &usize,\n    table_name: &str,\n) {\n    let reg_record = program.alloc_register();\n\n    program.emit_insn(Insn::MakeRecord {\n        start_reg: to_u16(registers.src_columns_start),\n        count: to_u16(*input_column_count),\n        dest_reg: to_u16(reg_record),\n        index_name: None,\n        affinity_str: None,\n    });\n    program.emit_insn(Insn::NewRowid {\n        cursor: cursors.buffer_write,\n        rowid_reg: registers.rowid,\n        prev_largest_reg: 0,\n    });\n    program.emit_insn(Insn::Insert {\n        cursor: cursors.buffer_write,\n        key_reg: registers.rowid,\n        record_reg: reg_record,\n        flag: InsertFlags::new(),\n        table_name: table_name.to_string(),\n    });\n}\n\nfn emit_aggregation_step(\n    program: &mut ProgramBuilder,\n    window: &Window,\n    resolver: &Resolver,\n    plan: &SelectPlan,\n    registers: &WindowRegisters,\n) -> crate::Result<()> {\n    for (i, func) in window.functions.iter().enumerate() {\n        let WindowFunctionKind::Agg(agg_func) = &func.func else {\n            continue;\n        };\n        // The aggregation step is performed incrementally as each row from the subquery is\n        // processed. Therefore, we don’t need to access the buffer table and can obtain argument\n        // values directly by evaluating the expressions that reference the subquery result columns.\n        let args = match &func.original_expr {\n            Expr::FunctionCall { args, .. } => args.iter().map(|a| (**a).clone()).collect(),\n            Expr::FunctionCallStar { .. } => vec![],\n            _ => unreachable!(\n                \"All window functions should be either FunctionCall or FunctionCallStar expressions\"\n            ),\n        };\n\n        let reg_acc_start = registers.acc_start + i;\n        translate_aggregation_step(\n            program,\n            &plan.table_references,\n            AggArgumentSource::new_from_expression(agg_func, &args, &Distinctness::NonDistinct),\n            reg_acc_start,\n            resolver,\n        )?;\n    }\n\n    Ok(())\n}\n\n/// Emits bytecode to output all buffered rows produced by window processing.\n///\n/// The generated code has two possible entry points:\n/// * **Fallthrough mode** (normal flow): After all source rows have been processed,\n///   this code executes inline to flush any remaining buffered rows, then continues execution.\n/// * **Subroutine mode** (jump into `labels.flush_buffer`): In this case the code\n///   returns control to the address stored in `registers.flush_buffer_return_offset`\n///   once all buffered rows are processed.\npub fn emit_window_results(\n    program: &mut ProgramBuilder,\n    t_ctx: &mut TranslateCtx,\n    plan: &SelectPlan,\n) -> crate::Result<()> {\n    let WindowMetadata {\n        labels,\n        registers,\n        cursors,\n        ..\n    } = t_ctx.meta_window.as_ref().expect(\"missing window metadata\");\n    let window = plan.window.as_ref().expect(\"missing window\");\n\n    let label_empty = program.allocate_label();\n    let label_window_processing_end = labels.window_processing_end;\n    let reg_flush_buffer_return_offset = registers.flush_buffer_return_offset;\n    let cursor_buffer_read = cursors.buffer_read;\n\n    // All source rows have already been processed at this point.\n    // In fallthrough mode, we are not returning to a caller — we just flush\n    // the buffered rows and continue execution.\n    program.add_comment(program.offset(), \"return remaining buffered rows\");\n    program.emit_insn(Insn::Null {\n        dest: registers.flush_buffer_return_offset,\n        dest_end: None,\n    });\n\n    // If control jumps here (labels.flush_buffer), we are in subroutine mode.\n    // In that case, after flushing the buffer, execution will return to the\n    // address stored in `flush_buffer_return_offset`.\n    program.preassign_label_to_next_insn(labels.flush_buffer);\n\n    program.emit_insn(Insn::Rewind {\n        cursor_id: cursor_buffer_read,\n        pc_if_empty: label_empty,\n    });\n\n    emit_return_buffered_rows(program, window, t_ctx, plan)?;\n\n    program.resolve_label(label_empty, program.offset());\n\n    program.emit_insn(Insn::ResetSorter {\n        cursor_id: cursor_buffer_read,\n    });\n    program.emit_insn(Insn::Return {\n        return_reg: reg_flush_buffer_return_offset,\n        can_fallthrough: true,\n    });\n\n    program.preassign_label_to_next_insn(label_window_processing_end);\n\n    Ok(())\n}\n\nfn emit_return_buffered_rows(\n    program: &mut ProgramBuilder,\n    window: &Window,\n    t_ctx: &mut TranslateCtx,\n    plan: &SelectPlan,\n) -> crate::Result<()> {\n    let WindowMetadata {\n        labels,\n        registers,\n        cursors,\n        expressions_referencing_subquery,\n        ..\n    } = t_ctx.meta_window.as_ref().expect(\"missing window metadata\");\n\n    for (i, func) in window.functions.iter().enumerate() {\n        if let WindowFunctionKind::Agg(agg_func) = &func.func {\n            program.emit_insn(Insn::AggValue {\n                acc_reg: registers.acc_start + i,\n                dest_reg: registers.acc_result_start + i,\n                func: agg_func.clone(),\n            });\n        }\n    }\n\n    let label_skip_returning_row = program.allocate_label();\n    let label_loop_start = program.allocate_label();\n    let reg_one = window\n        .functions\n        .iter()\n        .any(|func| matches!(func.func, WindowFunctionKind::Window(WindowFunc::RowNumber)))\n        .then(|| {\n            let reg = program.alloc_register();\n            program.emit_int(1, reg);\n            reg\n        });\n    program.preassign_label_to_next_insn(label_loop_start);\n\n    // Propagate subquery result column values to the outer query (if any) or directly to\n    // the final output that will be returned to the user, by copying them from the buffer table\n    // into the dedicated registers.\n    for (i, (_, col_idx)) in expressions_referencing_subquery.iter().enumerate() {\n        let reg_result = registers.result_columns_start + i;\n        program.emit_column_or_rowid(cursors.buffer_read, *col_idx, reg_result);\n    }\n    for (i, func) in window.functions.iter().enumerate() {\n        if let WindowFunctionKind::Window(WindowFunc::RowNumber) = &func.func {\n            let reg_one = reg_one.expect(\"row_number must allocate reg_one\");\n            let reg_row_number = registers.acc_result_start + i;\n            program.emit_insn(Insn::Add {\n                lhs: reg_row_number,\n                rhs: reg_one,\n                dest: reg_row_number,\n            });\n        }\n    }\n    t_ctx.resolver.enable_expr_to_reg_cache();\n\n    match plan.order_by.is_empty() {\n        true => {\n            emit_select_result(\n                program,\n                &t_ctx.resolver,\n                plan,\n                Some(labels.window_processing_end),\n                Some(label_skip_returning_row),\n                t_ctx.reg_nonagg_emit_once_flag,\n                t_ctx.reg_offset,\n                t_ctx.reg_result_cols_start.unwrap(),\n                t_ctx.limit_ctx,\n            )?;\n        }\n        false => {\n            EmitOrderBy::sorter_insert(program, t_ctx, plan)?;\n        }\n    }\n\n    program.resolve_label(label_skip_returning_row, program.offset());\n\n    if let Distinctness::Distinct { ctx } = &plan.distinctness {\n        let distinct_ctx = ctx.as_ref().expect(\"distinct context must exist\");\n        program.preassign_label_to_next_insn(distinct_ctx.label_on_conflict);\n    }\n\n    program.emit_insn(Insn::Next {\n        cursor_id: cursors.buffer_read,\n        pc_if_next: label_loop_start,\n    });\n\n    Ok(())\n}\n"
  },
  {
    "path": "core/turso_types_vtab.rs",
    "content": "use crate::sync::Arc;\nuse crate::sync::RwLock;\nuse crate::vtab::{InternalVirtualTable, InternalVirtualTableCursor};\nuse crate::{Connection, Result, Value};\nuse turso_ext::{ConstraintInfo, ConstraintUsage, IndexInfo, OrderByInfo, ResultCode};\n\n#[derive(Debug)]\npub struct TursoTypesTable;\n\nimpl Default for TursoTypesTable {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl TursoTypesTable {\n    pub fn new() -> Self {\n        Self\n    }\n}\n\nimpl InternalVirtualTable for TursoTypesTable {\n    fn name(&self) -> String {\n        \"sqlite_turso_types\".to_string()\n    }\n\n    fn sql(&self) -> String {\n        \"CREATE TABLE sqlite_turso_types(name TEXT, sql TEXT)\".to_string()\n    }\n\n    fn open(&self, conn: Arc<Connection>) -> Result<Arc<RwLock<dyn InternalVirtualTableCursor>>> {\n        let cursor = TursoTypesCursor::new(conn);\n        Ok(Arc::new(RwLock::new(cursor)))\n    }\n\n    fn best_index(\n        &self,\n        constraints: &[ConstraintInfo],\n        _order_by: &[OrderByInfo],\n    ) -> std::result::Result<IndexInfo, ResultCode> {\n        let constraint_usages = constraints\n            .iter()\n            .map(|_| ConstraintUsage {\n                argv_index: None,\n                omit: false,\n            })\n            .collect();\n\n        Ok(IndexInfo {\n            idx_num: 0,\n            idx_str: None,\n            order_by_consumed: false,\n            estimated_cost: 10.0,\n            estimated_rows: 20,\n            constraint_usages,\n        })\n    }\n}\n\npub struct TursoTypesCursor {\n    conn: Arc<Connection>,\n    /// Snapshot of type entries: (display_name, sql_string)\n    entries: Vec<(String, String)>,\n    index: usize,\n}\n\nimpl TursoTypesCursor {\n    fn new(conn: Arc<Connection>) -> Self {\n        Self {\n            conn,\n            entries: Vec::new(),\n            index: 0,\n        }\n    }\n\n    fn snapshot_types(&mut self) {\n        self.entries.clear();\n        self.conn.with_schema(0, |schema| {\n            let mut names: Vec<_> = schema\n                .type_registry\n                .iter()\n                .filter(|(key, td)| *key == &td.name.to_lowercase())\n                .map(|(key, _)| key.clone())\n                .collect();\n            names.sort();\n            for name in names {\n                let td = &schema.type_registry[&name];\n                let display_name = if td.params.is_empty() {\n                    td.name.clone()\n                } else {\n                    let params: Vec<String> = td\n                        .params\n                        .iter()\n                        .map(|p| match &p.ty {\n                            Some(ty) => format!(\"{} {}\", p.name, ty),\n                            None => p.name.clone(),\n                        })\n                        .collect();\n                    format!(\"{}({})\", td.name, params.join(\", \"))\n                };\n                self.entries.push((display_name, td.to_sql()));\n            }\n        });\n    }\n}\n\nimpl InternalVirtualTableCursor for TursoTypesCursor {\n    fn filter(&mut self, _args: &[Value], _idx_str: Option<String>, _idx_num: i32) -> Result<bool> {\n        self.snapshot_types();\n        self.index = 0;\n        Ok(!self.entries.is_empty())\n    }\n\n    fn next(&mut self) -> Result<bool> {\n        self.index += 1;\n        Ok(self.index < self.entries.len())\n    }\n\n    fn column(&self, column: usize) -> Result<Value> {\n        if self.index >= self.entries.len() {\n            return Ok(Value::Null);\n        }\n        let (ref name, ref sql) = self.entries[self.index];\n        match column {\n            0 => Ok(Value::from_text(name.clone())),\n            1 => Ok(Value::from_text(sql.clone())),\n            _ => Ok(Value::Null),\n        }\n    }\n\n    fn rowid(&self) -> i64 {\n        self.index as i64 + 1\n    }\n}\n"
  },
  {
    "path": "core/types.rs",
    "content": "use crate::turso_debug_assert;\nuse branches::{mark_unlikely, unlikely};\nuse either::Either;\nuse turso_ext::{AggCtx, FinalizeFunction, StepFunction};\nuse turso_parser::ast::SortOrder;\n\nuse crate::error::LimboError;\nuse crate::ext::{ExtValue, ExtValueType};\nuse crate::index_method::IndexMethodCursor;\nuse crate::numeric::format_float;\nuse crate::numeric::nonnan::NonNan;\nuse crate::numeric::Numeric;\nuse crate::pseudo::PseudoCursor;\nuse crate::schema::Index;\nuse crate::storage::btree::CursorTrait;\nuse crate::storage::sqlite3_ondisk::{read_integer, read_value, read_varint, write_varint};\nuse crate::translate::collate::CollationSeq;\nuse crate::translate::plan::IterationDirection;\nuse crate::vdbe::sorter::Sorter;\nuse crate::vdbe::Register;\nuse crate::vtab::VirtualTableCursor;\nuse crate::{Completion, CompletionError, Result, IO};\nuse std::borrow::{Borrow, Cow};\nuse std::cell::Cell;\nuse std::fmt::{Debug, Display};\nuse std::future::Future;\nuse std::iter::{FusedIterator, Peekable};\nuse std::ops::Deref;\nuse std::task::{Poll, Waker};\n\n/// SQLite by default uses 2000 as maximum numbers in a row.\n/// It controlld by the constant called SQLITE_MAX_COLUMN\n/// But the hard limit of number of columns is 32,767 columns i16::MAX\n/// const MAX_COLUMN: usize = 2000;\n\n#[derive(Debug, Clone, Copy, PartialEq)]\npub enum ValueType {\n    Null,\n    Integer,\n    Float,\n    Text,\n    Blob,\n    Error,\n}\n\nimpl Display for ValueType {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let value = match self {\n            Self::Null => \"NULL\",\n            Self::Integer => \"INT\",\n            Self::Float => \"REAL\",\n            Self::Blob => \"BLOB\",\n            Self::Text => \"TEXT\",\n            Self::Error => \"ERROR\",\n        };\n        write!(f, \"{value}\")\n    }\n}\n\n#[derive(Debug, Clone, Copy, PartialEq)]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\npub enum TextSubtype {\n    Text,\n    #[cfg(feature = \"json\")]\n    Json,\n}\n\n#[derive(Debug, Clone)]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\npub struct Text {\n    pub value: Cow<'static, str>,\n    pub subtype: TextSubtype,\n}\n\nimpl Display for Text {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{}\", self.as_str())\n    }\n}\n\nimpl Text {\n    pub fn new(value: impl Into<Cow<'static, str>>) -> Self {\n        Self {\n            value: value.into(),\n            subtype: TextSubtype::Text,\n        }\n    }\n    #[cfg(feature = \"json\")]\n    pub fn json(value: String) -> Self {\n        Self {\n            value: value.into(),\n            subtype: TextSubtype::Json,\n        }\n    }\n\n    pub fn as_str(&self) -> &str {\n        &self.value\n    }\n}\n\n#[derive(Debug, Clone, Copy)]\npub struct TextRef<'a> {\n    pub value: &'a str,\n    pub subtype: TextSubtype,\n}\n\nimpl<'a> TextRef<'a> {\n    pub fn new(value: &'a str, subtype: TextSubtype) -> Self {\n        Self { value, subtype }\n    }\n\n    #[inline]\n    pub fn as_str(&self) -> &'a str {\n        self.value\n    }\n}\n\nimpl<'a> Borrow<str> for TextRef<'a> {\n    #[inline]\n    fn borrow(&self) -> &str {\n        self.as_str()\n    }\n}\n\nimpl<'a> Deref for TextRef<'a> {\n    type Target = str;\n\n    #[inline]\n    fn deref(&self) -> &Self::Target {\n        self.as_str()\n    }\n}\n\npub trait Extendable<T> {\n    fn do_extend(&mut self, other: &T);\n}\n\nimpl<T: AnyText> Extendable<T> for Text {\n    #[inline(always)]\n    fn do_extend(&mut self, other: &T) {\n        let other_str = other.as_ref();\n        match &mut self.value {\n            Cow::Owned(s) => {\n                let needed = other_str.len();\n                if s.capacity() >= needed {\n                    // SAFETY: capacity >= needed, source is valid UTF-8\n                    turso_debug_assert!(\n                        s.as_ptr().wrapping_add(s.len()) <= other_str.as_ptr()\n                            || other_str.as_ptr().wrapping_add(other_str.len()) <= s.as_ptr(),\n                        \"source and destination ranges must not overlap\"\n                    );\n                    unsafe {\n                        std::ptr::copy_nonoverlapping(other_str.as_ptr(), s.as_mut_ptr(), needed);\n                        s.as_mut_vec().set_len(needed);\n                    }\n                } else {\n                    other_str.clone_into(s);\n                }\n            }\n            Cow::Borrowed(_) => {\n                self.value = Cow::Owned(other_str.to_owned());\n            }\n        }\n        self.subtype = other.subtype();\n    }\n}\n\nimpl<T: AnyBlob> Extendable<T> for Vec<u8> {\n    #[inline(always)]\n    fn do_extend(&mut self, other: &T) {\n        let other_slice = other.as_slice();\n        let needed = other_slice.len();\n        if self.capacity() >= needed {\n            // SAFETY: capacity >= needed\n            turso_debug_assert!(\n                self.as_ptr().wrapping_add(self.len()) <= other_slice.as_ptr()\n                    || other_slice.as_ptr().wrapping_add(other_slice.len()) <= self.as_ptr(),\n                \"source and destination ranges must not overlap\"\n            );\n            unsafe {\n                std::ptr::copy_nonoverlapping(other_slice.as_ptr(), self.as_mut_ptr(), needed);\n                self.set_len(needed);\n            }\n        } else {\n            self.clear();\n            self.extend_from_slice(other_slice);\n        }\n    }\n}\n\npub trait AnyText: AsRef<str> {\n    fn subtype(&self) -> TextSubtype;\n}\n\nimpl AnyText for Text {\n    fn subtype(&self) -> TextSubtype {\n        self.subtype\n    }\n}\n\nimpl AnyText for &str {\n    fn subtype(&self) -> TextSubtype {\n        TextSubtype::Text\n    }\n}\n\npub trait AnyBlob {\n    fn as_slice(&self) -> &[u8];\n}\n\nimpl AnyBlob for Vec<u8> {\n    fn as_slice(&self) -> &[u8] {\n        self.as_slice()\n    }\n}\n\nimpl AnyBlob for &[u8] {\n    fn as_slice(&self) -> &[u8] {\n        self\n    }\n}\n\nimpl AsRef<str> for Text {\n    fn as_ref(&self) -> &str {\n        self.as_str()\n    }\n}\n\nimpl From<&str> for Text {\n    fn from(value: &str) -> Self {\n        Text {\n            value: value.to_owned().into(),\n            subtype: TextSubtype::Text,\n        }\n    }\n}\n\nimpl From<String> for Text {\n    fn from(value: String) -> Self {\n        Text {\n            value: Cow::from(value),\n            subtype: TextSubtype::Text,\n        }\n    }\n}\n\nimpl From<Text> for String {\n    fn from(value: Text) -> Self {\n        value.value.into_owned()\n    }\n}\n\n#[derive(Debug, Clone)]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\npub enum Value {\n    Null,\n    Numeric(Numeric),\n    Text(Text),\n    Blob(Vec<u8>),\n}\n\n#[derive(Clone, Copy)]\npub enum ValueRef<'a> {\n    Null,\n    Numeric(Numeric),\n    Text(TextRef<'a>),\n    Blob(&'a [u8]),\n}\n\nimpl Debug for ValueRef<'_> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            ValueRef::Null => write!(f, \"Null\"),\n            ValueRef::Numeric(Numeric::Integer(i)) => f.debug_tuple(\"Integer\").field(i).finish(),\n            ValueRef::Numeric(Numeric::Float(float)) => {\n                let fval: f64 = (*float).into();\n                f.debug_tuple(\"Float\").field(&fval).finish()\n            }\n            ValueRef::Text(text_ref) => {\n                // truncate string to at most 256 chars\n                let text = text_ref.as_str();\n                let max_len = text.len().min(256);\n                f.debug_struct(\"Text\")\n                    .field(\"data\", &&text[0..max_len])\n                    // Indicates to the developer debugging that the data is truncated for printing\n                    .field(\"truncated\", &(text.len() > max_len))\n                    .finish()\n            }\n            ValueRef::Blob(blob) => {\n                // truncate blob_slice to at most 32 bytes\n                let max_len = blob.len().min(32);\n                f.debug_struct(\"Blob\")\n                    .field(\"data\", &&blob[0..max_len])\n                    // Indicates to the developer debugging that the data is truncated for printing\n                    .field(\"truncated\", &(blob.len() > max_len))\n                    .finish()\n            }\n        }\n    }\n}\n\npub trait AsValueRef {\n    fn as_value_ref<'a>(&'a self) -> ValueRef<'a>;\n}\n\nimpl<'b> AsValueRef for ValueRef<'b> {\n    #[inline]\n    fn as_value_ref<'a>(&'a self) -> ValueRef<'a> {\n        *self\n    }\n}\n\nimpl AsValueRef for Value {\n    #[inline]\n    fn as_value_ref<'a>(&'a self) -> ValueRef<'a> {\n        self.as_ref()\n    }\n}\n\nimpl AsValueRef for &mut Value {\n    #[inline]\n    fn as_value_ref<'a>(&'a self) -> ValueRef<'a> {\n        self.as_ref()\n    }\n}\n\nimpl<V1, V2> AsValueRef for Either<V1, V2>\nwhere\n    V1: AsValueRef,\n    V2: AsValueRef,\n{\n    #[inline]\n    fn as_value_ref<'a>(&'a self) -> ValueRef<'a> {\n        match self {\n            Either::Left(left) => left.as_value_ref(),\n            Either::Right(right) => right.as_value_ref(),\n        }\n    }\n}\n\nimpl<V: AsValueRef> AsValueRef for &V {\n    fn as_value_ref<'a>(&'a self) -> ValueRef<'a> {\n        (*self).as_value_ref()\n    }\n}\n\nimpl Value {\n    pub fn from_f64(f: f64) -> Self {\n        match NonNan::new(f) {\n            Some(nn) => Self::Numeric(Numeric::Float(nn)),\n            None => Self::Null,\n        }\n    }\n\n    pub fn from_i64(i: i64) -> Self {\n        Self::Numeric(Numeric::Integer(i))\n    }\n\n    pub fn as_ref<'a>(&'a self) -> ValueRef<'a> {\n        match self {\n            Value::Null => ValueRef::Null,\n            Value::Numeric(n) => ValueRef::Numeric(*n),\n            Value::Text(v) => ValueRef::Text(TextRef {\n                value: &v.value,\n                subtype: v.subtype,\n            }),\n            Value::Blob(v) => ValueRef::Blob(v.as_slice()),\n        }\n    }\n\n    // A helper function that makes building a text Value easier.\n    pub fn build_text(text: impl Into<Cow<'static, str>>) -> Self {\n        Self::Text(Text::new(text))\n    }\n\n    pub fn to_blob(&self) -> Option<&[u8]> {\n        match self {\n            Self::Blob(blob) => Some(blob),\n            _ => None,\n        }\n    }\n\n    pub fn from_blob(data: Vec<u8>) -> Self {\n        Value::Blob(data)\n    }\n\n    pub fn to_text(&self) -> Option<&str> {\n        match self {\n            Value::Text(t) => Some(t.as_str()),\n            _ => None,\n        }\n    }\n\n    pub fn as_blob(&self) -> &Vec<u8> {\n        match self {\n            Value::Blob(b) => b,\n            _ => panic!(\"as_blob must be called only for Value::Blob\"),\n        }\n    }\n\n    pub fn as_blob_mut(&mut self) -> &mut Vec<u8> {\n        match self {\n            Value::Blob(b) => b,\n            _ => panic!(\"as_blob must be called only for Value::Blob\"),\n        }\n    }\n    pub fn as_float(&self) -> f64 {\n        match self {\n            Value::Numeric(Numeric::Float(f)) => f64::from(*f),\n            Value::Numeric(Numeric::Integer(i)) => *i as f64,\n            _ => panic!(\"as_float must be called only for Value::Numeric\"),\n        }\n    }\n\n    pub fn to_float_or_zero(&self) -> f64 {\n        match self {\n            Value::Numeric(Numeric::Float(f)) => f64::from(*f),\n            Value::Numeric(Numeric::Integer(i)) => *i as f64,\n            _ => 0.0,\n        }\n    }\n\n    pub fn as_int(&self) -> Option<i64> {\n        match self {\n            Value::Numeric(Numeric::Integer(i)) => Some(*i),\n            _ => None,\n        }\n    }\n\n    pub fn as_uint(&self) -> u64 {\n        match self {\n            Value::Numeric(Numeric::Integer(i)) => (*i).cast_unsigned(),\n            _ => 0,\n        }\n    }\n\n    pub fn from_text(text: impl Into<Cow<'static, str>>) -> Self {\n        Value::Text(Text::new(text))\n    }\n\n    pub fn value_type(&self) -> ValueType {\n        match self {\n            Value::Null => ValueType::Null,\n            Value::Numeric(Numeric::Integer(_)) => ValueType::Integer,\n            Value::Numeric(Numeric::Float(_)) => ValueType::Float,\n            Value::Text(_) => ValueType::Text,\n            Value::Blob(_) => ValueType::Blob,\n        }\n    }\n    pub fn serialize_serial(&self, out: &mut Vec<u8>) {\n        match self {\n            Value::Null => {}\n            Value::Numeric(Numeric::Integer(i)) => {\n                let serial_type = SerialType::from(self);\n                match serial_type.kind() {\n                    SerialTypeKind::I8 => out.extend_from_slice(&(*i as i8).to_be_bytes()),\n                    SerialTypeKind::I16 => out.extend_from_slice(&(*i as i16).to_be_bytes()),\n                    SerialTypeKind::I24 => out.extend_from_slice(&(*i as i32).to_be_bytes()[1..]), // remove most significant byte\n                    SerialTypeKind::I32 => out.extend_from_slice(&(*i as i32).to_be_bytes()),\n                    SerialTypeKind::I48 => out.extend_from_slice(&i.to_be_bytes()[2..]), // remove 2 most significant bytes\n                    SerialTypeKind::I64 => out.extend_from_slice(&i.to_be_bytes()),\n                    _ => unreachable!(),\n                }\n            }\n            Value::Numeric(Numeric::Float(f)) => {\n                let fval: f64 = (*f).into();\n                out.extend_from_slice(&fval.to_be_bytes());\n            }\n            Value::Text(t) => out.extend_from_slice(t.value.as_bytes()),\n            Value::Blob(b) => out.extend_from_slice(b),\n        };\n    }\n\n    /// Cast Value to String, if Value is NULL returns None\n    pub fn cast_text(&self) -> Option<String> {\n        Some(match self {\n            Value::Null => return None,\n            v => v.to_string(),\n        })\n    }\n}\n\n#[derive(Debug, Clone, PartialEq)]\npub struct ExternalAggState {\n    pub state: *mut AggCtx,\n    pub argc: usize,\n    pub step_fn: StepFunction,\n    pub finalize_fn: FinalizeFunction,\n}\n\n/// Please use Display trait for all limbo output so we have single origin of truth\n/// When you need value as string:\n/// ---GOOD---\n/// format!(\"{}\", value);\n/// ---BAD---\n/// match value {\n///   Value::Numeric(Numeric::Integer(i)) => i.to_string(),\n///   Value::Numeric(Numeric::Float(f)) => f64::from(*f).to_string(),\n///   ....\n/// }\nimpl Display for Value {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Self::Null => write!(f, \"\"),\n            Self::Numeric(Numeric::Integer(i)) => write!(f, \"{i}\"),\n            Self::Numeric(Numeric::Float(fl)) => f.write_str(&format_float(f64::from(*fl))),\n            Self::Text(s) => write!(f, \"{}\", s.as_str()),\n            Self::Blob(b) => write!(f, \"{}\", String::from_utf8_lossy(b)),\n        }\n    }\n}\n\nimpl Value {\n    pub fn to_ffi(&self) -> ExtValue {\n        match self {\n            Self::Null => ExtValue::null(),\n            Self::Numeric(Numeric::Integer(i)) => ExtValue::from_integer(*i),\n            Self::Numeric(Numeric::Float(fl)) => ExtValue::from_float(f64::from(*fl)),\n            Self::Text(text) => ExtValue::from_text(text.as_str().to_string()),\n            Self::Blob(blob) => ExtValue::from_blob(blob.to_vec()),\n        }\n    }\n\n    pub fn from_ffi(v: ExtValue) -> Result<Self> {\n        let res = match v.value_type() {\n            ExtValueType::Null => Ok(Value::Null),\n            ExtValueType::Integer => {\n                let Some(int) = v.to_integer() else {\n                    return Ok(Value::Null);\n                };\n                Ok(Value::from_i64(int))\n            }\n            ExtValueType::Float => {\n                let Some(float) = v.to_float() else {\n                    return Ok(Value::Null);\n                };\n                Ok(Value::from_f64(float))\n            }\n            ExtValueType::Text => {\n                let Some(text) = v.to_text() else {\n                    return Ok(Value::Null);\n                };\n                #[cfg(feature = \"json\")]\n                if v.is_json() {\n                    return Ok(Value::Text(Text::json(text.to_string())));\n                }\n                Ok(Value::build_text(text.to_string()))\n            }\n            ExtValueType::Blob => {\n                let Some(blob) = v.to_blob() else {\n                    return Ok(Value::Null);\n                };\n                Ok(Value::Blob(blob))\n            }\n            ExtValueType::Error => {\n                let Some(err) = v.to_error_details() else {\n                    return Ok(Value::Null);\n                };\n                match err {\n                    (_, Some(msg)) => Err(LimboError::ExtensionError(msg)),\n                    (code, None) => Err(LimboError::ExtensionError(code.to_string())),\n                }\n            }\n        };\n        unsafe { v.__free_internal_type() };\n        res\n    }\n}\n\n/// Convert a `Value` into the implementors type.\npub trait FromValue: Sealed {\n    fn from_sql(val: Value) -> Result<Self>\n    where\n        Self: Sized;\n}\n\nimpl FromValue for Value {\n    fn from_sql(val: Value) -> Result<Self> {\n        Ok(val)\n    }\n}\nimpl Sealed for crate::Value {}\n\nmacro_rules! impl_int_from_value {\n    ($ty:ty, $cast:expr) => {\n        impl FromValue for $ty {\n            fn from_sql(val: Value) -> Result<Self> {\n                match val {\n                    Value::Null => Err(LimboError::NullValue),\n                    Value::Numeric(Numeric::Integer(i)) => Ok($cast(i)),\n                    _ => unreachable!(\"invalid value type\"),\n                }\n            }\n        }\n\n        impl Sealed for $ty {}\n    };\n}\n\nimpl_int_from_value!(i32, |i| i as i32);\nimpl_int_from_value!(u32, |i| i as u32);\nimpl_int_from_value!(i64, |i| i);\nimpl_int_from_value!(u64, |i| i as u64);\n\nimpl FromValue for f64 {\n    fn from_sql(val: Value) -> Result<Self> {\n        match val {\n            Value::Null => Err(LimboError::NullValue),\n            Value::Numeric(Numeric::Float(f)) => Ok(f64::from(f)),\n            _ => unreachable!(\"invalid value type\"),\n        }\n    }\n}\nimpl Sealed for f64 {}\n\nimpl FromValue for Vec<u8> {\n    fn from_sql(val: Value) -> Result<Self> {\n        match val {\n            Value::Null => Err(LimboError::NullValue),\n            Value::Blob(blob) => Ok(blob),\n            _ => unreachable!(\"invalid value type\"),\n        }\n    }\n}\nimpl Sealed for Vec<u8> {}\n\nimpl<const N: usize> FromValue for [u8; N] {\n    fn from_sql(val: Value) -> Result<Self> {\n        match val {\n            Value::Null => Err(LimboError::NullValue),\n            Value::Blob(blob) => blob.try_into().map_err(|_| LimboError::InvalidBlobSize(N)),\n            _ => unreachable!(\"invalid value type\"),\n        }\n    }\n}\nimpl<const N: usize> Sealed for [u8; N] {}\n\nimpl FromValue for String {\n    fn from_sql(val: Value) -> Result<Self> {\n        match val {\n            Value::Null => Err(LimboError::NullValue),\n            Value::Text(s) => Ok(s.to_string()),\n            _ => unreachable!(\"invalid value type\"),\n        }\n    }\n}\nimpl Sealed for String {}\n\nimpl FromValue for bool {\n    fn from_sql(val: Value) -> Result<Self> {\n        match val {\n            Value::Null => Err(LimboError::NullValue),\n            Value::Numeric(Numeric::Integer(i)) => match i {\n                0 => Ok(false),\n                1 => Ok(true),\n                _ => Err(LimboError::InvalidColumnType),\n            },\n            _ => unreachable!(\"invalid value type\"),\n        }\n    }\n}\nimpl Sealed for bool {}\n\nimpl<T> FromValue for Option<T>\nwhere\n    T: FromValue,\n{\n    fn from_sql(val: Value) -> Result<Self> {\n        match val {\n            Value::Null => Ok(None),\n            _ => T::from_sql(val).map(Some),\n        }\n    }\n}\nimpl<T> Sealed for Option<T> {}\n\nmod sealed {\n    pub trait Sealed {}\n}\nuse sealed::Sealed;\n\n#[derive(Debug, Clone, PartialEq)]\npub struct SumAggState {\n    pub r_err: f64,   // Error term for Kahan-Babushka-Neumaier summation\n    pub approx: bool, // True if any non-integer value was input to the sum\n    pub ovrfl: bool,  // Integer overflow seen\n}\nimpl Default for SumAggState {\n    fn default() -> Self {\n        Self {\n            r_err: 0.0,\n            approx: false,\n            ovrfl: false,\n        }\n    }\n}\n\n/// Aggregate context for accumulating values during GROUP BY.\n/// Built-in aggregates use a flat payload representation for efficiency and\n/// to share code between register-based and hash-based aggregation (future enhancement).\n#[derive(Debug, Clone, PartialEq)]\npub enum AggContext {\n    /// Built-in aggregates store state as a flat Vec<Value> payload.\n    /// The layout depends on the aggregate function (see init_agg_payload).\n    Builtin(Vec<Value>),\n    /// External (extension) aggregates need FFI state that can't be serialized.\n    External(ExternalAggState),\n}\n\nimpl AggContext {\n    pub fn compute_external(&self) -> Result<Value> {\n        if let Self::External(ext_state) = self {\n            let final_value = unsafe { (ext_state.finalize_fn)(ext_state.state) };\n            Value::from_ffi(final_value)\n        } else {\n            panic!(\"AggContext::compute_external() expected External, found {self:?}\");\n        }\n    }\n\n    /// Get a mutable reference to the builtin payload as a slice\n    pub fn payload_mut(&mut self) -> &mut [Value] {\n        match self {\n            Self::Builtin(payload) => payload,\n            Self::External(_) => panic!(\"payload_mut() called on External aggregate\"),\n        }\n    }\n\n    /// Get a mutable reference to the builtin payload Vec (for aggregates that\n    /// grow the payload, e.g. array_agg).\n    pub fn payload_vec_mut(&mut self) -> &mut Vec<Value> {\n        match self {\n            Self::Builtin(payload) => payload,\n            Self::External(_) => panic!(\"payload_vec_mut() called on External aggregate\"),\n        }\n    }\n\n    /// Get an immutable reference to the builtin payload\n    pub fn payload(&self) -> &[Value] {\n        match self {\n            Self::Builtin(payload) => payload,\n            Self::External(_) => panic!(\"payload() called on External aggregate\"),\n        }\n    }\n}\n\nimpl PartialEq<Value> for Value {\n    fn eq(&self, other: &Value) -> bool {\n        let (left, right) = (self.as_value_ref(), other.as_value_ref());\n        left.eq(&right)\n    }\n}\n\nimpl PartialOrd<Value> for Value {\n    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {\n        Some(self.cmp(other))\n    }\n}\n\nimpl PartialOrd<AggContext> for AggContext {\n    fn partial_cmp(&self, other: &AggContext) -> Option<std::cmp::Ordering> {\n        match (self, other) {\n            (Self::Builtin(a), Self::Builtin(b)) => {\n                // Compare by first element (the accumulator) if present\n                match (a.first(), b.first()) {\n                    (Some(a), Some(b)) => a.partial_cmp(b),\n                    _ => None,\n                }\n            }\n            _ => None,\n        }\n    }\n}\n\nimpl Eq for Value {}\n\nimpl Ord for Value {\n    fn cmp(&self, other: &Self) -> std::cmp::Ordering {\n        let (left, right) = (self.as_value_ref(), other.as_value_ref());\n        left.cmp(&right)\n    }\n}\n\nimpl std::ops::Add<Value> for Value {\n    type Output = Value;\n\n    fn add(mut self, rhs: Self) -> Self::Output {\n        self += rhs;\n        self\n    }\n}\n\nimpl std::ops::Add<f64> for Value {\n    type Output = Value;\n\n    fn add(mut self, rhs: f64) -> Self::Output {\n        self += rhs;\n        self\n    }\n}\n\nimpl std::ops::Add<i64> for Value {\n    type Output = Value;\n\n    fn add(mut self, rhs: i64) -> Self::Output {\n        self += rhs;\n        self\n    }\n}\n\nimpl std::ops::AddAssign for Value {\n    fn add_assign(mut self: &mut Self, rhs: Self) {\n        match (&mut self, &rhs) {\n            (Self::Numeric(_), Self::Numeric(_)) => {\n                let sum = (|| {\n                    let lhs_num = Numeric::from_value(&self)?;\n                    let rhs_num = Numeric::from_value(&rhs)?;\n                    lhs_num.checked_add(rhs_num)\n                })();\n                *self = sum.into();\n            }\n            (Self::Text(string_left), Self::Text(string_right)) => {\n                string_left.value.to_mut().push_str(&string_right.value);\n                string_left.subtype = TextSubtype::Text;\n            }\n            (Self::Text(string_left), Self::Numeric(Numeric::Integer(int_right))) => {\n                let string_right = int_right.to_string();\n                string_left.value.to_mut().push_str(&string_right);\n                string_left.subtype = TextSubtype::Text;\n            }\n            (Self::Numeric(Numeric::Integer(int_left)), Self::Text(string_right)) => {\n                let string_left = int_left.to_string();\n                *self = Self::build_text(string_left + string_right.as_str());\n            }\n            (Self::Text(string_left), Self::Numeric(Numeric::Float(_))) => {\n                let string_right = rhs.to_string();\n                string_left.value.to_mut().push_str(&string_right);\n                string_left.subtype = TextSubtype::Text;\n            }\n            (Self::Numeric(Numeric::Float(_)), Self::Text(string_right)) => {\n                let string_left = self.to_string();\n                *self = Self::build_text(string_left + string_right.as_str());\n            }\n            (_, Self::Null) => {}\n            (Self::Null, _) => *self = rhs,\n            _ => *self = Self::from_f64(0.0),\n        }\n    }\n}\n\nimpl std::ops::AddAssign<i64> for Value {\n    fn add_assign(&mut self, rhs: i64) {\n        let sum = (|| {\n            let lhs_num = Numeric::from_value(&self)?;\n            let rhs_num = Numeric::Integer(rhs);\n            lhs_num.checked_add(rhs_num)\n        })();\n        *self = sum.into();\n    }\n}\n\nimpl std::ops::AddAssign<f64> for Value {\n    fn add_assign(&mut self, rhs: f64) {\n        let sum = (|| {\n            let lhs_num = Numeric::from_value(&self)?;\n            let rhs_num = NonNan::new(rhs).map(Numeric::Float)?;\n            lhs_num.checked_add(rhs_num)\n        })();\n\n        *self = sum.into();\n    }\n}\n\nimpl std::ops::Div<Value> for Value {\n    type Output = Value;\n\n    fn div(self, rhs: Value) -> Self::Output {\n        let div = (|| {\n            let lhs_num = Numeric::from_value(self)?;\n            let rhs_num = Numeric::from_value(rhs)?;\n            lhs_num.checked_div(rhs_num)\n        })();\n        div.into()\n    }\n}\n\nimpl std::ops::DivAssign<Value> for Value {\n    fn div_assign(&mut self, rhs: Value) {\n        *self = self.clone() / rhs;\n    }\n}\n\nimpl TryFrom<ValueRef<'_>> for i64 {\n    type Error = LimboError;\n\n    fn try_from(value: ValueRef<'_>) -> Result<Self, Self::Error> {\n        match value {\n            ValueRef::Numeric(Numeric::Integer(i)) => Ok(i),\n            _ => Err(LimboError::ConversionError(\"Expected integer value\".into())),\n        }\n    }\n}\n\nimpl TryFrom<ValueRef<'_>> for String {\n    type Error = LimboError;\n\n    #[inline]\n    fn try_from(value: ValueRef<'_>) -> Result<Self, Self::Error> {\n        Ok(<&str>::try_from(value)?.to_string())\n    }\n}\n\nimpl<'a> TryFrom<ValueRef<'a>> for &'a str {\n    type Error = LimboError;\n\n    #[inline]\n    fn try_from(value: ValueRef<'a>) -> Result<Self, Self::Error> {\n        match value {\n            ValueRef::Text(s) => Ok(s.as_str()),\n            _ => Err(LimboError::ConversionError(\"Expected text value\".into())),\n        }\n    }\n}\n\n/// This struct serves the purpose of not allocating multiple vectors of bytes if not needed.\n/// A value in a record that has already been serialized can stay serialized and what this struct offsers\n/// is easy acces to each value which point to the payload.\n/// The name might be contradictory as it is immutable in the sense that you cannot modify the values without modifying the payload.\npub struct ImmutableRecord {\n    // We have to be super careful with this buffer since we make values point to the payload we need to take care reallocations\n    // happen in a controlled manner. If we realocate with values that should be correct, they will now point to undefined data.\n    // We don't use pin here because it would make it imposible to reuse the buffer if we need to push a new record in the same struct.\n    //\n    // payload is the Vec<u8> but in order to use Register which holds ImmutableRecord as a Value - we store Vec<u8> as Value::Blob\n    payload: Value,\n}\n\n// SAFETY: all ImmutableRecord instances are intended to be used in a single thread\n// by a single connection.\nunsafe impl Send for ImmutableRecord {}\nunsafe impl Sync for ImmutableRecord {}\n\nimpl Clone for ImmutableRecord {\n    fn clone(&self) -> Self {\n        Self {\n            payload: self.payload.clone(),\n        }\n    }\n}\n\nimpl PartialEq for ImmutableRecord {\n    fn eq(&self, other: &Self) -> bool {\n        self.payload == other.payload // Only compare payload, ignore cursor state\n    }\n}\n\nimpl Eq for ImmutableRecord {}\n\nimpl PartialOrd for ImmutableRecord {\n    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {\n        Some(self.cmp(other))\n    }\n}\n\nimpl Ord for ImmutableRecord {\n    fn cmp(&self, other: &Self) -> std::cmp::Ordering {\n        self.payload.cmp(&other.payload) // Only compare payload, ignore cursor state\n    }\n}\n\nimpl std::fmt::Debug for ImmutableRecord {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match &self.payload {\n            Value::Blob(bytes) => {\n                let preview = if bytes.len() > 20 {\n                    format!(\"{:?} ... ({} bytes total)\", &bytes[..20], bytes.len())\n                } else {\n                    format!(\"{bytes:?}\")\n                };\n                write!(f, \"ImmutableRecord {{ payload: {preview} }}\")\n            }\n            Value::Text(s) => {\n                let string = s.as_str();\n                let preview = if string.len() > 20 {\n                    format!(\"{:?} ... ({} chars total)\", &string[..20], string.len())\n                } else {\n                    format!(\"{string:?}\")\n                };\n                write!(f, \"ImmutableRecord {{ payload: {preview} }}\")\n            }\n            other => write!(f, \"ImmutableRecord {{ payload: {other:?} }}\"),\n        }\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]\npub struct Record {\n    values: Vec<Value>,\n}\n\nimpl Record {\n    // pub fn get<'a, T: FromValue<'a> + 'a>(&'a self, idx: usize) -> Result<T> {\n    //     let value = &self.values[idx];\n    //     T::from_value(value)\n    // }\n\n    pub fn count(&self) -> usize {\n        self.values.len()\n    }\n\n    pub fn last_value(&self) -> Option<&Value> {\n        self.values.last()\n    }\n\n    pub fn get_values(&self) -> &Vec<Value> {\n        &self.values\n    }\n\n    pub fn get_value(&self, idx: usize) -> &Value {\n        &self.values[idx]\n    }\n\n    pub fn len(&self) -> usize {\n        self.values.len()\n    }\n\n    pub fn is_empty(&self) -> bool {\n        self.values.is_empty()\n    }\n}\nstruct AppendWriter<'a> {\n    buf: &'a mut Vec<u8>,\n    pos: usize,\n    buf_capacity_start: usize,\n    buf_ptr_start: *const u8,\n}\n\nimpl<'a> AppendWriter<'a> {\n    pub fn new(buf: &'a mut Vec<u8>, pos: usize) -> Self {\n        let buf_ptr_start = buf.as_ptr();\n        let buf_capacity_start = buf.capacity();\n        Self {\n            buf,\n            pos,\n            buf_capacity_start,\n            buf_ptr_start,\n        }\n    }\n\n    #[inline]\n    pub fn extend_from_slice(&mut self, slice: &[u8]) {\n        self.buf[self.pos..self.pos + slice.len()].copy_from_slice(slice);\n        self.pos += slice.len();\n    }\n\n    fn assert_finish_capacity(&self) {\n        // let's make sure we didn't reallocate anywhere else\n        assert_eq!(self.buf_capacity_start, self.buf.capacity());\n        assert_eq!(self.buf_ptr_start, self.buf.as_ptr());\n    }\n}\n\nimpl ImmutableRecord {\n    pub fn new(payload_capacity: usize) -> Self {\n        Self {\n            payload: Value::Blob(Vec::with_capacity(payload_capacity)),\n        }\n    }\n\n    pub fn from_bin_record(payload: Vec<u8>) -> Self {\n        Self {\n            payload: Value::Blob(payload),\n        }\n    }\n\n    // Don't use this in performance critical paths, prefer using `iter()` instead\n    pub fn get_values(&self) -> Result<Vec<ValueRef<'_>>> {\n        let iter = self.iter()?;\n        let mut values = Vec::with_capacity(iter.size_hint().0);\n        for value in iter {\n            values.push(value?);\n        }\n        Ok(values)\n    }\n\n    // Don't use this in performance critical paths, prefer using `iter()` instead\n    pub fn get_values_range(&self, range: std::ops::Range<usize>) -> Result<Vec<ValueRef<'_>>> {\n        let mut iter = self.iter()?;\n        let mut values = Vec::with_capacity(range.end - range.start);\n        // advance to start\n        if let Some(value) = iter.nth(range.start) {\n            values.push(value?);\n        } else {\n            return Ok(values);\n        }\n        // collect rest\n        for _ in range.start + 1..range.end {\n            if let Some(value) = iter.next() {\n                values.push(value?);\n            } else {\n                break;\n            }\n        }\n        Ok(values)\n    }\n\n    // Idx values must be sorted ascending\n    pub fn get_two_values(&self, idx1: usize, idx2: usize) -> Result<(ValueRef<'_>, ValueRef<'_>)> {\n        let mut iter = self.iter()?;\n        let val1 = iter.nth(idx1);\n        let val2 = iter.nth(idx2 - idx1 - 1); // idx2 - idx1 - 1 because we already advanced to idx1\n        match (val1, val2) {\n            (Some(v1), Some(v2)) => Ok((v1?, v2?)),\n            _ => Err(LimboError::InternalError(\"index out of bound\".to_string())),\n        }\n    }\n\n    // Idx values must be sorted ascending\n    pub fn get_three_values(\n        &self,\n        idx1: usize,\n        idx2: usize,\n        idx3: usize,\n    ) -> Result<(ValueRef<'_>, ValueRef<'_>, ValueRef<'_>)> {\n        let mut iter = self.iter()?;\n        let val1 = iter.nth(idx1);\n        let val2 = iter.nth(idx2 - idx1 - 1); // idx2 - idx1 - 1 because we already advanced to idx1\n        let val3 = iter.nth(idx3 - idx2 - 1); // idx3 - idx2 - 1 because we already advanced to idx2\n        match (val1, val2, val3) {\n            (Some(v1), Some(v2), Some(v3)) => Ok((v1?, v2?, v3?)),\n            _ => Err(LimboError::InternalError(\"index out of bound\".to_string())),\n        }\n    }\n\n    // Idx values must be sorted ascending\n    pub fn get_four_values(\n        &self,\n        idx1: usize,\n        idx2: usize,\n        idx3: usize,\n        idx4: usize,\n    ) -> Result<(ValueRef<'_>, ValueRef<'_>, ValueRef<'_>, ValueRef<'_>)> {\n        let mut iter = self.iter()?;\n        let val1 = iter.nth(idx1);\n        let val2 = iter.nth(idx2 - idx1 - 1); // idx2 - idx1 - 1 because we already advanced to idx1\n        let val3 = iter.nth(idx3 - idx2 - 1); // idx3 - idx2 - 1 because we already advanced to idx2\n        let val4 = iter.nth(idx4 - idx3 - 1); // idx4 - idx3 - 1 because we already advanced to idx3\n        match (val1, val2, val3, val4) {\n            (Some(v1), Some(v2), Some(v3), Some(v4)) => Ok((v1?, v2?, v3?, v4?)),\n            _ => Err(LimboError::InternalError(\"index out of bound\".to_string())),\n        }\n    }\n\n    // Don't use this in performance critical paths, prefer using `iter()` instead\n    pub fn get_values_owned(&self) -> Result<Vec<Value>> {\n        let iter = self.iter().expect(\"Failed to create payload iterator\");\n        let mut values = Vec::with_capacity(iter.size_hint().0);\n        for value in iter {\n            values.push(value?.to_owned());\n        }\n        Ok(values)\n    }\n\n    // Don't use this in performance critical paths, prefer using `iter()` instead\n    pub fn get_values_owned_range(&self, range: std::ops::Range<usize>) -> Result<Vec<Value>> {\n        let mut iter = self.iter().expect(\"Failed to create payload iterator\");\n        let mut values = Vec::with_capacity(range.end - range.start);\n        // advance to start\n        if let Some(value) = iter.nth(range.start) {\n            values.push(value?.to_owned());\n        } else {\n            return Ok(values);\n        }\n        // collect rest\n        for _ in range.start + 1..range.end {\n            if let Some(value) = iter.next() {\n                values.push(value?.to_owned());\n            } else {\n                break;\n            }\n        }\n        Ok(values)\n    }\n\n    pub fn from_registers<'a, I: Iterator<Item = &'a Register> + Clone>(\n        // we need to accept both &[Register] and &[&Register] values - that's why non-trivial signature\n        //\n        // std::slice::Iter under the hood just stores pointer and length of slice and also implements a Clone which just copy those meta-values\n        // (without copying the data itself)\n        registers: impl IntoIterator<Item = &'a Register, IntoIter = I>,\n        len: usize,\n    ) -> Self {\n        Self::from_values(registers.into_iter().map(|x| x.get_value()), len)\n    }\n\n    pub fn from_values<'a>(\n        values: impl IntoIterator<Item = impl AsValueRef + 'a> + Clone,\n        len: usize,\n    ) -> Self {\n        let mut serials = Vec::with_capacity(len);\n        let mut size_header = 0;\n        let mut size_values = 0;\n\n        let mut serial_type_buf = [0; 9];\n        // write serial types\n        for value in values.clone() {\n            let serial_type = SerialType::from(value.as_value_ref());\n            let n = write_varint(&mut serial_type_buf[0..], serial_type.into());\n            serials.push((serial_type_buf, n));\n\n            let value_size = serial_type.size();\n\n            size_header += n;\n            size_values += value_size;\n        }\n\n        let header_size = Record::calc_header_size(size_header);\n\n        // 1. write header size\n        let mut buf = Vec::new();\n        buf.reserve_exact(header_size + size_values);\n        assert_eq!(buf.capacity(), header_size + size_values);\n        let n = write_varint(&mut serial_type_buf, header_size as u64);\n\n        buf.resize(buf.capacity(), 0);\n        let mut writer = AppendWriter::new(&mut buf, 0);\n        writer.extend_from_slice(&serial_type_buf[..n]);\n\n        // 2. Write serial\n        for (value, n) in serials {\n            writer.extend_from_slice(&value[..n]);\n        }\n\n        // write content\n        for value in values {\n            let value = value.as_value_ref();\n            match value {\n                ValueRef::Null => {}\n                ValueRef::Numeric(Numeric::Integer(i)) => {\n                    let serial_type = SerialType::from(value);\n                    match serial_type.kind() {\n                        SerialTypeKind::ConstInt0 | SerialTypeKind::ConstInt1 => {}\n                        SerialTypeKind::I8 => writer.extend_from_slice(&(i as i8).to_be_bytes()),\n                        SerialTypeKind::I16 => writer.extend_from_slice(&(i as i16).to_be_bytes()),\n                        SerialTypeKind::I24 => {\n                            writer.extend_from_slice(&(i as i32).to_be_bytes()[1..])\n                        } // remove most significant byte\n                        SerialTypeKind::I32 => writer.extend_from_slice(&(i as i32).to_be_bytes()),\n                        SerialTypeKind::I48 => writer.extend_from_slice(&i.to_be_bytes()[2..]), // remove 2 most significant bytes\n                        SerialTypeKind::I64 => writer.extend_from_slice(&i.to_be_bytes()),\n                        other => panic!(\"Serial type is not an integer: {other:?}\"),\n                    }\n                }\n                ValueRef::Numeric(Numeric::Float(f)) => {\n                    let fval: f64 = f.into();\n                    writer.extend_from_slice(&fval.to_be_bytes());\n                }\n                ValueRef::Text(t) => {\n                    writer.extend_from_slice(t.value.as_bytes());\n                }\n                ValueRef::Blob(b) => {\n                    writer.extend_from_slice(b);\n                }\n            };\n        }\n\n        writer.assert_finish_capacity();\n        Self {\n            payload: Value::Blob(buf),\n        }\n    }\n\n    #[inline]\n    pub fn into_payload(self) -> Vec<u8> {\n        match self.payload {\n            Value::Blob(b) => b,\n            _ => panic!(\"payload must be a blob\"),\n        }\n    }\n\n    #[inline]\n    pub fn as_blob(&self) -> &Vec<u8> {\n        match &self.payload {\n            Value::Blob(b) => b,\n            _ => panic!(\"payload must be a blob\"),\n        }\n    }\n\n    #[inline]\n    pub fn as_blob_mut(&mut self) -> &mut Vec<u8> {\n        match &mut self.payload {\n            Value::Blob(b) => b,\n            _ => panic!(\"payload must be a blob\"),\n        }\n    }\n\n    #[inline]\n    pub fn as_blob_value(&self) -> &Value {\n        &self.payload\n    }\n\n    #[inline]\n    pub fn start_serialization(&mut self, payload: &[u8]) {\n        self.as_blob_mut().extend_from_slice(payload);\n    }\n\n    #[inline]\n    pub fn invalidate(&mut self) {\n        self.as_blob_mut().clear();\n    }\n\n    #[inline]\n    pub fn is_invalidated(&self) -> bool {\n        self.as_blob().is_empty()\n    }\n\n    #[inline]\n    pub fn get_payload(&self) -> &[u8] {\n        self.as_blob()\n    }\n\n    #[inline(always)]\n    pub fn iter(&self) -> Result<ValueIterator<'_>, LimboError> {\n        ValueIterator::new(self.get_payload())\n    }\n\n    #[inline]\n    /// Returns true if the record contains any NULL values.\n    /// This is an optimization that only examines the header (serial types)\n    /// without deserializing the data section.\n    pub fn contains_null(&self) -> Result<bool> {\n        let payload = self.get_payload();\n        let (header_size, header_varint_len) = read_varint(payload)?;\n        let header_size = header_size as usize;\n\n        if header_size > payload.len() || header_varint_len > payload.len() {\n            return Err(LimboError::Corrupt(\n                \"Payload too small for indicated header size\".into(),\n            ));\n        }\n\n        let mut header = &payload[header_varint_len..header_size];\n\n        while !header.is_empty() {\n            let (serial_type, bytes_read) = read_varint(header)?;\n            if serial_type == 0 {\n                return Ok(true);\n            }\n            header = &header[bytes_read..];\n        }\n\n        Ok(false)\n    }\n\n    #[inline]\n    pub fn last_value(&self) -> Option<Result<ValueRef<'_>>> {\n        if unlikely(self.is_invalidated()) {\n            return Some(Err(LimboError::InternalError(\n                \"Record is invalidated\".into(),\n            )));\n        }\n        let iter = match self.iter() {\n            Ok(it) => it,\n            Err(e) => return Some(Err(e)),\n        };\n        iter.last()\n    }\n\n    #[inline]\n    pub fn first_value(&self) -> Result<ValueRef<'_>> {\n        if unlikely(self.is_invalidated()) {\n            return Err(LimboError::InternalError(\"Record is invalidated\".into()));\n        }\n        match self.iter()?.next() {\n            Some(v) => v,\n            None => Err(LimboError::InternalError(\"Record has no columns\".into())),\n        }\n    }\n\n    #[inline]\n    pub fn get_value(&self, idx: usize) -> Result<ValueRef<'_>> {\n        if unlikely(self.is_invalidated()) {\n            return Err(LimboError::InternalError(\"Record is invalidated\".into()));\n        }\n        let mut iter = self.iter()?;\n        iter.nth(idx)\n            .transpose()?\n            .ok_or_else(|| LimboError::InternalError(\"Index out of bounds\".into()))\n    }\n\n    #[inline]\n    pub fn get_value_opt(&self, idx: usize) -> Option<ValueRef<'_>> {\n        let mut iter = match self.iter() {\n            Ok(it) => it,\n            Err(_) => {\n                mark_unlikely();\n                return None;\n            }\n        };\n        match iter.nth(idx) {\n            Some(Ok(v)) => Some(v),\n            _ => {\n                mark_unlikely();\n                None\n            }\n        }\n    }\n\n    pub fn column_count(&self) -> usize {\n        self.iter().map(|it| it.count()).unwrap_or_default()\n    }\n}\n\n/// A zero-allocation iterator over SQLite record payload data.\n///\n/// This iterator provides efficient, lazy parsing of SQLite records without\n/// any heap allocation. It processes record data on-the-fly, returning `ValueRef`\n/// instances that borrow directly from the underlying payload.\n///\n/// # Memory Layout\n///\n/// SQLite records follow this binary format:\n/// ```text\n/// [header_size: varint][serial_type1: varint][serial_type2: varint]...\n/// [data1][data2][data3]...\n/// ```\n///\n/// - **header_size**: Total bytes in the header section (including this varint)\n/// - **serial_typeN**: Encodes the type and size of column N's data\n/// - **dataN**: The actual data for column N (length determined by serial_typeN)\npub struct ValueIterator<'a> {\n    /// Reference to header section up to data offset\n    header_section: Cell<&'a [u8]>,\n    /// Reference to data section only\n    data_section: Cell<&'a [u8]>,\n}\n\nimpl<'a> ValueIterator<'a> {\n    /// Creates a new payload iterator from a raw payload slice.\n    ///\n    /// # Arguments\n    ///\n    /// * `payload` - The serialized SQLite record payload\n    ///\n    /// # Returns\n    ///\n    /// Returns `Ok(Self)` if the header can be parsed, or an error if the\n    /// payload is malformed.\n    #[inline(always)]\n    pub fn new(payload: &'a [u8]) -> Result<Self> {\n        let (header_size, header_varint_len) = read_varint(payload)?;\n        let header_size = header_size as usize;\n\n        if header_size > payload.len()\n            || header_varint_len > payload.len()\n            || header_varint_len > header_size\n        {\n            return Err(LimboError::Corrupt(\n                \"Payload too small for indicated header size\".into(),\n            ));\n        }\n\n        Ok(Self {\n            header_section: Cell::new(&payload[header_varint_len..header_size]),\n            data_section: Cell::new(&payload[header_size..]),\n        })\n    }\n\n    /// Returns `true` if the payload is empty or the record has no columns.\n    pub fn is_empty(&self) -> bool {\n        self.header_section.get().is_empty()\n    }\n\n    /// Returns a reference to the current header section.\n    #[inline(always)]\n    pub fn header_section_ref(&self) -> &'a [u8] {\n        self.header_section.get()\n    }\n\n    /// Returns a reference to the current data section.\n    #[inline(always)]\n    pub fn data_section_ref(&self) -> &'a [u8] {\n        self.data_section.get()\n    }\n\n    /// Sets the header section to a new slice.\n    #[inline(always)]\n    pub fn set_header_section(&self, header: &'a [u8]) {\n        self.header_section.set(header);\n    }\n\n    /// Sets the data section to a new slice.\n    #[inline(always)]\n    pub fn set_data_section(&self, data: &'a [u8]) {\n        self.data_section.set(data);\n    }\n}\n\nimpl<'a> Iterator for ValueIterator<'a> {\n    type Item = Result<ValueRef<'a>, LimboError>;\n\n    #[inline(always)]\n    fn count(self) -> usize\n    where\n        Self: Sized,\n    {\n        let mut count = 0;\n        let mut header = self.header_section.get();\n        while !header.is_empty() {\n            match read_varint(header) {\n                Ok((_, bytes_read)) => {\n                    count += 1;\n                    header = &header[bytes_read..];\n                }\n                Err(_) => break,\n            }\n        }\n        count\n    }\n\n    #[inline(always)]\n    fn size_hint(&self) -> (usize, Option<usize>) {\n        let mut count = 0;\n        let mut header = self.header_section.get();\n        while !header.is_empty() {\n            match read_varint(header) {\n                Ok((_, bytes_read)) => {\n                    count += 1;\n                    header = &header[bytes_read..];\n                }\n                Err(_) => break,\n            }\n        }\n        (count, Some(count))\n    }\n\n    fn fold<B, F>(self, init: B, mut f: F) -> B\n    where\n        F: FnMut(B, Self::Item) -> B,\n    {\n        let mut acc = init;\n        for item in self {\n            acc = f(acc, item);\n        }\n        acc\n    }\n\n    /// Returns the nth element of the iterator.\n    #[inline(always)]\n    fn nth(&mut self, n: usize) -> Option<Self::Item> {\n        let mut header = self.header_section.get();\n        let mut data = self.data_section.get();\n\n        let mut data_sum = 0;\n        for _ in 0..n {\n            if unlikely(header.is_empty()) {\n                return None;\n            }\n\n            let (serial_type, bytes_read) = match read_varint(header) {\n                Ok(v) => v,\n                Err(e) => {\n                    mark_unlikely();\n                    return Some(Err(e));\n                }\n            };\n            header = &header[bytes_read..];\n\n            data_sum += match get_serial_type_size(serial_type) {\n                Ok(size) => size,\n                Err(e) => {\n                    mark_unlikely();\n                    return Some(Err(e));\n                }\n            };\n        }\n\n        if unlikely(data_sum > data.len()) {\n            return Some(Err(LimboError::Corrupt(\n                \"Data section too small for indicated serial type size\".into(),\n            )));\n        }\n        data = &data[data_sum..];\n\n        // Update iterator state\n        self.header_section.set(header);\n        self.data_section.set(data);\n\n        // Return the nth value\n        self.next()\n    }\n\n    #[inline(always)]\n    fn next(&mut self) -> Option<Self::Item> {\n        let header = self.header_section.get();\n        if unlikely(header.is_empty()) {\n            return None;\n        }\n\n        // Read next serial type\n        let (serial_type, bytes_read) = match read_varint(header) {\n            Ok(v) => v,\n            Err(e) => {\n                mark_unlikely();\n                return Some(Err(e));\n            }\n        };\n\n        // Update header section to remove the consumed serial type\n        self.header_section.set(&header[bytes_read..]);\n\n        let data_section = self.data_section.get();\n\n        match crate::storage::sqlite3_ondisk::read_value_serial_type(data_section, serial_type) {\n            Ok((value, n)) => {\n                self.data_section.set(&data_section[n..]);\n                Some(Ok(value))\n            }\n            Err(e) => {\n                mark_unlikely();\n                Some(Err(e))\n            }\n        }\n    }\n}\n\n// Optimization: indicate that once the iterator is exhausted, it will always return None.\nimpl<'a> FusedIterator for ValueIterator<'a> {}\n\nimpl<'a> Clone for ValueIterator<'a> {\n    fn clone(&self) -> Self {\n        Self {\n            header_section: Cell::new(self.header_section.get()),\n            data_section: Cell::new(self.data_section.get()),\n        }\n    }\n}\n\nimpl<'a> ValueRef<'a> {\n    pub fn from_f64(f: f64) -> Self {\n        match NonNan::new(f) {\n            Some(nn) => Self::Numeric(Numeric::Float(nn)),\n            None => Self::Null,\n        }\n    }\n\n    pub fn from_i64(i: i64) -> Self {\n        Self::Numeric(Numeric::Integer(i))\n    }\n\n    pub fn to_ffi(&self) -> ExtValue {\n        match self {\n            Self::Null => ExtValue::null(),\n            Self::Numeric(Numeric::Integer(i)) => ExtValue::from_integer(*i),\n            Self::Numeric(Numeric::Float(fl)) => ExtValue::from_float(f64::from(*fl)),\n            Self::Text(text) => ExtValue::from_text(text.as_str().to_string()),\n            Self::Blob(blob) => ExtValue::from_blob(blob.to_vec()),\n        }\n    }\n\n    pub fn to_blob(&self) -> Option<&'a [u8]> {\n        match self {\n            Self::Blob(blob) => Some(*blob),\n            _ => None,\n        }\n    }\n\n    pub fn to_text(&self) -> Option<&'a str> {\n        match self {\n            Self::Text(t) => Some(t.as_str()),\n            _ => None,\n        }\n    }\n\n    pub fn as_blob(&self) -> &'a [u8] {\n        match self {\n            Self::Blob(b) => b,\n            _ => panic!(\"as_blob must be called only for Value::Blob\"),\n        }\n    }\n\n    pub fn as_float(&self) -> f64 {\n        match self {\n            Self::Numeric(Numeric::Float(f)) => f64::from(*f),\n            Self::Numeric(Numeric::Integer(i)) => *i as f64,\n            _ => panic!(\"as_float must be called only for ValueRef::Numeric\"),\n        }\n    }\n\n    pub fn as_int(&self) -> Option<i64> {\n        match self {\n            Self::Numeric(Numeric::Integer(i)) => Some(*i),\n            _ => None,\n        }\n    }\n\n    pub fn as_uint(&self) -> u64 {\n        match self {\n            Self::Numeric(Numeric::Integer(i)) => (*i).cast_unsigned(),\n            _ => 0,\n        }\n    }\n\n    #[inline]\n    pub fn to_owned(&self) -> Value {\n        match self {\n            ValueRef::Null => Value::Null,\n            ValueRef::Numeric(n) => Value::from(*n),\n            ValueRef::Text(text) => Value::Text(Text {\n                value: text.value.to_string().into(),\n                subtype: text.subtype,\n            }),\n            ValueRef::Blob(b) => Value::Blob(b.to_vec()),\n        }\n    }\n\n    pub fn value_type(&self) -> ValueType {\n        match self {\n            Self::Null => ValueType::Null,\n            Self::Numeric(Numeric::Integer(_)) => ValueType::Integer,\n            Self::Numeric(Numeric::Float(_)) => ValueType::Float,\n            Self::Text(_) => ValueType::Text,\n            Self::Blob(_) => ValueType::Blob,\n        }\n    }\n}\n\nimpl Display for ValueRef<'_> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Self::Null => write!(f, \"NULL\"),\n            Self::Numeric(Numeric::Integer(i)) => write!(f, \"{i}\"),\n            Self::Numeric(Numeric::Float(fl)) => {\n                let fval: f64 = (*fl).into();\n                write!(f, \"{fval:?}\")\n            }\n            Self::Text(s) => write!(f, \"{}\", s.as_str()),\n            Self::Blob(b) => write!(f, \"{}\", String::from_utf8_lossy(b)),\n        }\n    }\n}\n\nimpl<'a> PartialEq<ValueRef<'a>> for ValueRef<'a> {\n    fn eq(&self, other: &ValueRef<'a>) -> bool {\n        match (self, other) {\n            (Self::Null, Self::Null) => true,\n            (Self::Numeric(a), Self::Numeric(b)) => a == b,\n            (Self::Text(text_left), Self::Text(text_right)) => {\n                text_left.value.as_bytes() == text_right.value.as_bytes()\n            }\n            (Self::Blob(blob_left), Self::Blob(blob_right)) => blob_left.eq(blob_right),\n            _ => false,\n        }\n    }\n}\n\nimpl<'a> PartialEq<Value> for ValueRef<'a> {\n    fn eq(&self, other: &Value) -> bool {\n        let other = other.as_value_ref();\n        self.eq(&other)\n    }\n}\n\nimpl<'a> Eq for ValueRef<'a> {}\n\nimpl<'a> PartialOrd<ValueRef<'a>> for ValueRef<'a> {\n    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {\n        Some(self.cmp(other))\n    }\n}\n\nimpl<'a> Ord for ValueRef<'a> {\n    fn cmp(&self, other: &Self) -> std::cmp::Ordering {\n        match (self, other) {\n            (Self::Null, Self::Null) => std::cmp::Ordering::Equal,\n            (Self::Null, _) => std::cmp::Ordering::Less,\n            (_, Self::Null) => std::cmp::Ordering::Greater,\n\n            (Self::Numeric(a), Self::Numeric(b)) => a.cmp(b),\n\n            // Numeric < Text < Blob\n            (Self::Numeric(_), _) => std::cmp::Ordering::Less,\n            (_, Self::Numeric(_)) => std::cmp::Ordering::Greater,\n\n            (Self::Text(text_left), Self::Text(text_right)) => {\n                text_left.value.as_bytes().cmp(text_right.value.as_bytes())\n            }\n            (Self::Text(_), Self::Blob(_)) => std::cmp::Ordering::Less,\n            (Self::Blob(_), Self::Text(_)) => std::cmp::Ordering::Greater,\n\n            (Self::Blob(blob_left), Self::Blob(blob_right)) => blob_left.cmp(blob_right),\n        }\n    }\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub struct KeyInfo {\n    pub sort_order: SortOrder,\n    pub collation: CollationSeq,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Metadata about an index, used for handling and comparing index keys.\n///\n/// This struct provides information about the sorting order of columns,\n/// whether the index includes a row ID, and the total number of columns\n/// in the index.\npub struct IndexInfo {\n    /// Specifies the sorting order (ascending or descending) for each column in the index.\n    pub key_info: Vec<KeyInfo>,\n    /// Indicates whether the index includes a row ID column.\n    pub has_rowid: bool,\n    /// The total number of columns in the index, including the row ID column if present.\n    pub num_cols: usize,\n    /// Indicates whether index rows should be unique.\n    pub is_unique: bool,\n}\n\nimpl Default for IndexInfo {\n    fn default() -> Self {\n        Self {\n            key_info: vec![],\n            has_rowid: true,\n            num_cols: 1,\n            is_unique: false,\n        }\n    }\n}\n\nimpl IndexInfo {\n    pub fn new_from_index(index: &Index) -> Self {\n        Self {\n            key_info: {\n                let mut key_info: Vec<KeyInfo> = index\n                    .columns\n                    .iter()\n                    .map(|c| KeyInfo {\n                        sort_order: c.order,\n                        collation: c.collation.unwrap_or_default(),\n                    })\n                    .collect();\n                if index.has_rowid {\n                    key_info.push(KeyInfo {\n                        sort_order: SortOrder::Asc,\n                        collation: CollationSeq::Binary,\n                    });\n                }\n                key_info\n            },\n            has_rowid: index.has_rowid,\n            num_cols: index.columns.len() + (index.has_rowid as usize),\n            is_unique: index.unique,\n        }\n    }\n}\n\npub fn compare_immutable<V1, V2, E1, E2, I1, I2>(\n    l: I1,\n    r: I2,\n    column_info: &[KeyInfo],\n) -> std::cmp::Ordering\nwhere\n    V1: AsValueRef,\n    V2: AsValueRef,\n    E1: ExactSizeIterator<Item = V1>,\n    E2: ExactSizeIterator<Item = V2>,\n    I1: IntoIterator<IntoIter = E1, Item = E1::Item>,\n    I2: IntoIterator<IntoIter = E2, Item = E2::Item>,\n{\n    let (l, r): (E1, E2) = (l.into_iter(), r.into_iter());\n    assert!(\n        l.len() >= column_info.len(),\n        \"{} < {}\",\n        l.len(),\n        column_info.len()\n    );\n    assert!(\n        r.len() >= column_info.len(),\n        \"{} < {}\",\n        r.len(),\n        column_info.len()\n    );\n    let (l, r) = (l.take(column_info.len()), r.take(column_info.len()));\n    for (i, (l, r)) in l.zip(r).enumerate() {\n        let column_order = column_info[i].sort_order;\n        let collation = column_info[i].collation;\n        let cmp = compare_immutable_single(l, r, collation);\n        if !cmp.is_eq() {\n            return match column_order {\n                SortOrder::Asc => cmp,\n                SortOrder::Desc => cmp.reverse(),\n            };\n        }\n    }\n    std::cmp::Ordering::Equal\n}\n\npub fn compare_immutable_iter<V, E1, E2>(\n    mut l: E1,\n    mut r: E2,\n    column_info: &[KeyInfo],\n) -> Result<std::cmp::Ordering>\nwhere\n    V: AsValueRef,\n    E1: Iterator<Item = Result<V>>,\n    E2: Iterator<Item = Result<V>>,\n{\n    for col_info in column_info.iter() {\n        let l = match l.next() {\n            Some(v) => v,\n            None => break,\n        };\n        let r = match r.next() {\n            Some(v) => v,\n            None => break,\n        };\n        let column_order = col_info.sort_order;\n        let collation = col_info.collation;\n        let cmp = compare_immutable_single(l?, r?, collation);\n        if !cmp.is_eq() {\n            return match column_order {\n                SortOrder::Asc => Ok(cmp),\n                SortOrder::Desc => Ok(cmp.reverse()),\n            };\n        }\n    }\n    Ok(std::cmp::Ordering::Equal)\n}\n\npub fn compare_immutable_single<V1, V2>(l: V1, r: V2, collation: CollationSeq) -> std::cmp::Ordering\nwhere\n    V1: AsValueRef,\n    V2: AsValueRef,\n{\n    let l = l.as_value_ref();\n    let r = r.as_value_ref();\n    match (l, r) {\n        (ValueRef::Text(left), ValueRef::Text(right)) => collation.compare_strings(&left, &right),\n        _ => l.cmp(&r),\n    }\n}\n\n#[derive(Debug, Clone, Copy)]\npub enum RecordCompare {\n    Int,\n    String,\n    Generic,\n}\n\nimpl RecordCompare {\n    pub fn compare<V, E, I>(\n        &self,\n        serialized: &ImmutableRecord,\n        unpacked: I,\n        index_info: &IndexInfo,\n        skip: usize,\n        tie_breaker: std::cmp::Ordering,\n    ) -> Result<std::cmp::Ordering>\n    where\n        V: AsValueRef,\n        E: ExactSizeIterator<Item = V>,\n        I: IntoIterator<IntoIter = E, Item = E::Item>,\n    {\n        let unpacked = unpacked.into_iter();\n        match self {\n            RecordCompare::Int => {\n                compare_records_int(serialized, unpacked, index_info, tie_breaker)\n            }\n            RecordCompare::String => {\n                compare_records_string(serialized, unpacked, index_info, tie_breaker)\n            }\n            RecordCompare::Generic => {\n                compare_records_generic(serialized, unpacked, index_info, skip, tie_breaker)\n            }\n        }\n    }\n}\n\npub fn find_compare<I, E, V>(unpacked: I, index_info: &IndexInfo) -> RecordCompare\nwhere\n    V: AsValueRef,\n    E: ExactSizeIterator<Item = V>,\n    I: IntoIterator<IntoIter = Peekable<E>, Item = V>,\n{\n    let mut unpacked = unpacked.into_iter();\n    if unpacked.len() != 0 && index_info.num_cols <= 13 {\n        let val = unpacked.peek().unwrap();\n        match val.as_value_ref() {\n            ValueRef::Numeric(Numeric::Integer(_)) => RecordCompare::Int,\n            ValueRef::Text(_) if index_info.key_info[0].collation == CollationSeq::Binary => {\n                RecordCompare::String\n            }\n            _ => RecordCompare::Generic,\n        }\n    } else {\n        RecordCompare::Generic\n    }\n}\n\npub fn get_tie_breaker_from_seek_op(seek_op: SeekOp) -> std::cmp::Ordering {\n    match seek_op {\n        // exact‐match “key == X” opcodes\n        SeekOp::GE { eq_only: true } | SeekOp::LE { eq_only: true } => std::cmp::Ordering::Equal,\n\n        // forward search – want the *first* ≥ / > key\n        SeekOp::GE { eq_only: false } => std::cmp::Ordering::Greater,\n        SeekOp::GT => std::cmp::Ordering::Less,\n\n        // backward search – want the *last* ≤ / < key\n        SeekOp::LE { eq_only: false } => std::cmp::Ordering::Less,\n        SeekOp::LT => std::cmp::Ordering::Greater,\n    }\n}\n\n/// Optimized integer-first record comparison function.\n///\n/// This function is an optimized version of `compare_records_generic()` for the\n/// common case where:\n/// - (a) The first field of the unpacked record is an integer\n/// - (b) The serialized record's first field is also an integer\n/// - (c) The header size varint fits in a single byte and is ≤ 63 bytes\n///\n/// The 63-byte header limit prevents buffer overreads and ensures safe direct\n/// memory access patterns. This optimization avoids generic parsing overhead\n/// by directly extracting and comparing integer values using known layouts.\n///\n/// # Fast Path Conditions\n///\n/// The function uses the optimized path when ALL of these conditions are met:\n/// - Payload is at least 2 bytes (header size + first serial type)\n/// - First serial type indicates integer (`1-6`, `8`, or `9`)\n/// - First unpacked field is a `ValueRef::Numeric(Numeric::Integer)`\n///\n/// If any condition fails, it falls back to `compare_records_generic()`.\n///\n/// # Arguments\n///\n/// * `serialized` - The left-hand side record in serialized format\n/// * `unpacked` - The right-hand side record as an array of parsed values\n/// * `index_info` - Contains sort order information for each field\n/// * `collations` - Array of collation sequences (unused for integers)\n/// * `tie_breaker` - Result to return when all compared fields are equal\n///\n/// /// # Comparison Logic\n///\n/// The function follows optimized integer comparison semantics:\n///\n/// 1. **Type validation**: Ensures both sides are integers, otherwise falls back\n/// 2. **Direct extraction**: Reads integer value using specialized decoder\n/// 3. **Native comparison**: Uses Rust's built-in `i64::cmp()` for speed\n/// 4. **Sort order**: Applies ascending/descending order to comparison result\n/// 5. **Remaining fields**: If first field is equal and more fields exist,\n///    delegates to `compare_records_generic()` with `skip=1`\nfn compare_records_int<V, I>(\n    serialized: &ImmutableRecord,\n    unpacked: I,\n    index_info: &IndexInfo,\n    tie_breaker: std::cmp::Ordering,\n) -> Result<std::cmp::Ordering>\nwhere\n    V: AsValueRef,\n    I: ExactSizeIterator<Item = V>,\n{\n    let payload = serialized.get_payload();\n    if payload.len() < 2 {\n        return compare_records_generic(serialized, unpacked, index_info, 0, tie_breaker);\n    }\n\n    let (header_size, offset_1st_serialtype) = read_varint(payload)?;\n    let header_size = header_size as usize;\n\n    if payload.len() < header_size {\n        return Err(LimboError::Corrupt(format!(\n            \"Record payload too short: claimed header size {} but payload only {} bytes\",\n            header_size,\n            payload.len()\n        )));\n    }\n\n    let (first_serial_type, _) = read_varint(&payload[offset_1st_serialtype..])?;\n\n    let serialtype_is_integer = matches!(first_serial_type, 1..=6 | 8 | 9);\n    if !serialtype_is_integer {\n        return compare_records_generic(serialized, unpacked, index_info, 0, tie_breaker);\n    }\n\n    let data_start = header_size;\n\n    let lhs_int = read_integer(&payload[data_start..], first_serial_type as u8)?;\n    let mut unpacked = unpacked.peekable();\n    // Do not consume iterator here\n    let ValueRef::Numeric(Numeric::Integer(rhs_int)) = unpacked.peek().unwrap().as_value_ref()\n    else {\n        return compare_records_generic(serialized, unpacked, index_info, 0, tie_breaker);\n    };\n    let comparison = match index_info.key_info[0].sort_order {\n        SortOrder::Asc => lhs_int.cmp(&rhs_int),\n        SortOrder::Desc => lhs_int.cmp(&rhs_int).reverse(),\n    };\n    match comparison {\n        std::cmp::Ordering::Equal => {\n            // First fields equal, compare remaining fields if any\n            if unpacked.len() > 1 {\n                return compare_records_generic(serialized, unpacked, index_info, 1, tie_breaker);\n            }\n            Ok(tie_breaker)\n        }\n        other => Ok(other),\n    }\n}\n\n/// This function is an optimized version of `compare_records_generic()` for the\n/// common case where:\n/// - (a) The first field of the unpacked record is a string\n/// - (b) The serialized record's first field is also a string\n/// - (c) The header size varint fits in a single byte (most records)\n///\n/// This optimization avoids the overhead of generic field parsing by directly\n/// accessing the first string field using known offsets, then falling back to\n/// the generic comparison for remaining fields if needed.\n///\n/// # Fast Path Conditions\n///\n/// The function uses the optimized path when ALL of these conditions are met:\n/// - Payload is at least 2 bytes (header size + first serial type)\n/// - Header size fits in single byte (`payload[0] < 0x80`)\n/// - First serial type indicates string (`>= 13` and odd number)\n/// - First unpacked field is a `RefValue::Text`\n///\n/// If any condition fails, it falls back to `compare_records_generic()`.\n///\n/// # Arguments\n///\n/// * `serialized` - The left-hand side record in serialized format\n/// * `unpacked` - The right-hand side record as an array of parsed values\n/// * `index_info` - Contains sort order information for each field\n/// * `collations` - Array of collation sequences for string comparisons\n/// * `tie_breaker` - Result to return when all compared fields are equal\n///\n/// # Comparison Logic\n///\n/// The function follows SQLite's string comparison semantics:\n///\n/// 1. **Type checking**: Ensures both sides are strings, otherwise falls back\n/// 2. **String comparison**: Uses collation if provided, binary otherwise\n/// 3. **Sort order**: Applies ascending/descending order to comparison result\n/// 4. **Length comparison**: If strings are equal, compares lengths\n/// 5. **Remaining fields**: If first field is equal and more fields exist,\n///    delegates to `compare_records_generic()` with `skip=1`\nfn compare_records_string<V, I>(\n    serialized: &ImmutableRecord,\n    unpacked: I,\n    index_info: &IndexInfo,\n    tie_breaker: std::cmp::Ordering,\n) -> Result<std::cmp::Ordering>\nwhere\n    V: AsValueRef,\n    I: ExactSizeIterator<Item = V>,\n{\n    let payload = serialized.get_payload();\n    if payload.len() < 2 {\n        return compare_records_generic(serialized, unpacked, index_info, 0, tie_breaker);\n    }\n\n    let (header_size, offset_1st_serialtype) = read_varint(payload)?;\n    let header_size = header_size as usize;\n\n    if payload.len() < header_size {\n        return Err(LimboError::Corrupt(format!(\n            \"Record payload too short: claimed header size {} but payload only {} bytes\",\n            header_size,\n            payload.len()\n        )));\n    }\n\n    let (first_serial_type, _) = read_varint(&payload[offset_1st_serialtype..])?;\n\n    let serialtype_is_string = first_serial_type >= 13 && (first_serial_type & 1) == 1;\n    if !serialtype_is_string {\n        return compare_records_generic(serialized, unpacked, index_info, 0, tie_breaker);\n    }\n\n    let mut unpacked = unpacked.peekable();\n\n    let ValueRef::Text(rhs_text) = unpacked.peek().unwrap().as_value_ref() else {\n        return compare_records_generic(serialized, unpacked, index_info, 0, tie_breaker);\n    };\n\n    let string_len = (first_serial_type as usize - 13) / 2;\n    let data_start = header_size;\n\n    turso_debug_assert!(data_start + string_len <= payload.len());\n\n    let serial_type = SerialType::try_from(first_serial_type)?;\n    let (lhs_value, _) = read_value(&payload[data_start..], serial_type)?;\n\n    let ValueRef::Text(lhs_text) = lhs_value else {\n        return compare_records_generic(serialized, unpacked, index_info, 0, tie_breaker);\n    };\n\n    let collation = index_info.key_info[0].collation;\n    let comparison = collation.compare_strings(&lhs_text, &rhs_text);\n\n    let final_comparison = match index_info.key_info[0].sort_order {\n        SortOrder::Asc => comparison,\n        SortOrder::Desc => comparison.reverse(),\n    };\n\n    match final_comparison {\n        std::cmp::Ordering::Equal => {\n            let len_cmp = lhs_text.len().cmp(&rhs_text.len());\n            if len_cmp != std::cmp::Ordering::Equal {\n                let adjusted = match index_info.key_info[0].sort_order {\n                    SortOrder::Asc => len_cmp,\n                    SortOrder::Desc => len_cmp.reverse(),\n                };\n                return Ok(adjusted);\n            }\n\n            if unpacked.len() > 1 {\n                return compare_records_generic(serialized, unpacked, index_info, 1, tie_breaker);\n            }\n            Ok(tie_breaker)\n        }\n        other => Ok(other),\n    }\n}\n\n/// Compare two table rows or index records.\n///\n/// This function compares a serialized record (`serialized`) with an unpacked\n/// record (`unpacked`) and returns a comparison result. It returns `Less`, `Equal`,\n/// or `Greater` if the serialized record is less than, equal to, or greater than\n/// the unpacked record.\n///\n/// The `serialized` record must be a blob created by the record serialization\n/// process (equivalent to SQLite's OP_MakeRecord opcode). The `unpacked` record\n/// must be a parsed key array of `RefValue` objects.\n///\n/// # Arguments\n///\n/// * `serialized` - The left-hand side record in serialized format\n/// * `unpacked` - The right-hand side record as an array of parsed values\n/// * `index_info` - Contains sort order information for each field\n/// * `collations` - Array of collation sequences for string comparisons\n/// * `skip` - Number of initial fields to skip (assumes caller verified equality)\n/// * `tie_breaker` - Result to return when all compared fields are equal\n///\n/// # Skipping Fields\n///\n/// If `skip` is non-zero, it is assumed that the caller has already determined\n/// that the first `skip` fields of the records are equal. This function will\n/// begin comparing at field index `skip`, skipping over the header and data\n/// portions of the already-verified fields.\n///\n/// # Field Count Differences\n///\n/// The serialized and unpacked records do not have to contain the same number\n/// of fields. If all fields that appear in both records are equal, then\n/// `tie_breaker` is returned.\npub fn compare_records_generic<V, I>(\n    serialized: &ImmutableRecord,\n    unpacked: I,\n    index_info: &IndexInfo,\n    skip: usize,\n    tie_breaker: std::cmp::Ordering,\n) -> Result<std::cmp::Ordering>\nwhere\n    V: AsValueRef,\n    I: ExactSizeIterator<Item = V>,\n{\n    let payload = serialized.get_payload();\n    if payload.is_empty() {\n        return Ok(std::cmp::Ordering::Less);\n    }\n\n    let (header_size, mut header_pos) = read_varint(payload)?;\n    let header_end = header_size as usize;\n    turso_debug_assert!(header_end <= payload.len());\n\n    let mut data_pos = header_size as usize;\n\n    // Skip over `skip` number of fields\n    for _ in 0..skip {\n        if header_pos >= header_end {\n            break;\n        }\n\n        let (serial_type_raw, bytes_read) = read_varint(&payload[header_pos..])?;\n        header_pos += bytes_read;\n\n        let serial_type = SerialType::try_from(serial_type_raw)?;\n        if !matches!(\n            serial_type.kind(),\n            SerialTypeKind::ConstInt0 | SerialTypeKind::ConstInt1 | SerialTypeKind::Null\n        ) {\n            data_pos += serial_type.size();\n        }\n    }\n\n    let mut field_idx = skip;\n    let field_limit = unpacked.len().min(index_info.key_info.len());\n\n    // assumes that that the `unpacked' iterator was not skipped outside this function call`\n    for rhs_value in unpacked.skip(skip) {\n        let rhs_value = &rhs_value.as_value_ref();\n        if field_idx >= field_limit || header_pos >= header_end {\n            break;\n        }\n        let (serial_type_raw, bytes_read) = read_varint(&payload[header_pos..])?;\n        header_pos += bytes_read;\n\n        let serial_type = SerialType::try_from(serial_type_raw)?;\n\n        let lhs_value = match serial_type.kind() {\n            SerialTypeKind::ConstInt0 => ValueRef::Numeric(Numeric::Integer(0)),\n            SerialTypeKind::ConstInt1 => ValueRef::Numeric(Numeric::Integer(1)),\n            SerialTypeKind::Null => ValueRef::Null,\n            _ => {\n                let (value, field_size) = read_value(&payload[data_pos..], serial_type)?;\n                data_pos += field_size;\n                value\n            }\n        };\n\n        let comparison = match (&lhs_value, rhs_value) {\n            (ValueRef::Text(lhs_text), ValueRef::Text(rhs_text)) => index_info.key_info[field_idx]\n                .collation\n                .compare_strings(lhs_text, rhs_text),\n\n            _ => lhs_value.cmp(rhs_value),\n        };\n\n        let final_comparison = match index_info.key_info[field_idx].sort_order {\n            SortOrder::Asc => comparison,\n            SortOrder::Desc => comparison.reverse(),\n        };\n\n        if final_comparison != std::cmp::Ordering::Equal {\n            return Ok(final_comparison);\n        }\n\n        field_idx += 1;\n    }\n\n    Ok(tie_breaker)\n}\n\nconst I8_LOW: i64 = -128;\nconst I8_HIGH: i64 = 127;\nconst I16_LOW: i64 = -32768;\nconst I16_HIGH: i64 = 32767;\nconst I24_LOW: i64 = -8388608;\nconst I24_HIGH: i64 = 8388607;\nconst I32_LOW: i64 = -2147483648;\nconst I32_HIGH: i64 = 2147483647;\nconst I48_LOW: i64 = -140737488355328;\nconst I48_HIGH: i64 = 140737488355327;\n\n/// Sqlite Serial Types\n/// https://www.sqlite.org/fileformat.html#record_format\n#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]\n#[repr(transparent)]\npub struct SerialType(u64);\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]\npub enum SerialTypeKind {\n    Null,\n    I8,\n    I16,\n    I24,\n    I32,\n    I48,\n    I64,\n    F64,\n    ConstInt0,\n    ConstInt1,\n    Text,\n    Blob,\n}\n\nimpl SerialType {\n    #[inline(always)]\n    pub fn u64_is_valid_serial_type(n: u64) -> bool {\n        n != 10 && n != 11\n    }\n\n    const NULL: Self = Self(0);\n    const I8: Self = Self(1);\n    const I16: Self = Self(2);\n    const I24: Self = Self(3);\n    const I32: Self = Self(4);\n    const I48: Self = Self(5);\n    const I64: Self = Self(6);\n    const F64: Self = Self(7);\n    const CONST_INT0: Self = Self(8);\n    const CONST_INT1: Self = Self(9);\n\n    pub const fn null() -> Self {\n        Self::NULL\n    }\n\n    pub const fn i8() -> Self {\n        Self::I8\n    }\n\n    pub const fn i16() -> Self {\n        Self::I16\n    }\n\n    pub const fn i24() -> Self {\n        Self::I24\n    }\n\n    pub const fn i32() -> Self {\n        Self::I32\n    }\n\n    pub const fn i48() -> Self {\n        Self::I48\n    }\n\n    pub const fn i64() -> Self {\n        Self::I64\n    }\n\n    pub const fn f64() -> Self {\n        Self::F64\n    }\n\n    pub const fn const_int0() -> Self {\n        Self::CONST_INT0\n    }\n\n    pub const fn const_int1() -> Self {\n        Self::CONST_INT1\n    }\n\n    pub const fn blob(size: u64) -> Self {\n        Self(12 + size * 2)\n    }\n\n    pub const fn text(size: u64) -> Self {\n        Self(13 + size * 2)\n    }\n\n    #[inline(always)]\n    pub const fn kind(&self) -> SerialTypeKind {\n        match self.0 {\n            0 => SerialTypeKind::Null,\n            1 => SerialTypeKind::I8,\n            2 => SerialTypeKind::I16,\n            3 => SerialTypeKind::I24,\n            4 => SerialTypeKind::I32,\n            5 => SerialTypeKind::I48,\n            6 => SerialTypeKind::I64,\n            7 => SerialTypeKind::F64,\n            8 => SerialTypeKind::ConstInt0,\n            9 => SerialTypeKind::ConstInt1,\n            n if n >= 12 => match n % 2 {\n                0 => SerialTypeKind::Blob,\n                1 => SerialTypeKind::Text,\n                _ => {\n                    mark_unlikely();\n                    unreachable!();\n                }\n            },\n            _ => {\n                mark_unlikely();\n                unreachable!();\n            }\n        }\n    }\n\n    pub const fn size(&self) -> usize {\n        match self.kind() {\n            SerialTypeKind::Null => 0,\n            SerialTypeKind::I8 => 1,\n            SerialTypeKind::I16 => 2,\n            SerialTypeKind::I24 => 3,\n            SerialTypeKind::I32 => 4,\n            SerialTypeKind::I48 => 6,\n            SerialTypeKind::I64 => 8,\n            SerialTypeKind::F64 => 8,\n            SerialTypeKind::ConstInt0 => 0,\n            SerialTypeKind::ConstInt1 => 0,\n            SerialTypeKind::Text => (self.0 as usize - 13) / 2,\n            SerialTypeKind::Blob => (self.0 as usize - 12) / 2,\n        }\n    }\n}\n\n#[inline(always)]\npub fn get_serial_type_size(serial: u64) -> Result<usize> {\n    match serial {\n        0 | 8 | 9 => Ok(0),\n        1 => Ok(1),\n        2 => Ok(2),\n        3 => Ok(3),\n        4 => Ok(4),\n        5 => Ok(6),\n        6 | 7 => Ok(8),\n        n if n >= 12 => match n % 2 {\n            0 => Ok(((n - 12) / 2) as usize), // Blob\n            1 => Ok(((n - 13) / 2) as usize), // Text\n            _ => {\n                mark_unlikely();\n                unreachable!();\n            }\n        },\n        _ => {\n            mark_unlikely();\n            Err(LimboError::Corrupt(format!(\n                \"Invalid serial type: {serial}\"\n            )))\n        }\n    }\n}\n\nimpl<T: AsValueRef> From<T> for SerialType {\n    fn from(value: T) -> Self {\n        let value = value.as_value_ref();\n        match value {\n            ValueRef::Null => SerialType::null(),\n            ValueRef::Numeric(Numeric::Integer(i)) => match i {\n                0 => SerialType::const_int0(),\n                1 => SerialType::const_int1(),\n                i if (I8_LOW..=I8_HIGH).contains(&i) => SerialType::i8(),\n                i if (I16_LOW..=I16_HIGH).contains(&i) => SerialType::i16(),\n                i if (I24_LOW..=I24_HIGH).contains(&i) => SerialType::i24(),\n                i if (I32_LOW..=I32_HIGH).contains(&i) => SerialType::i32(),\n                i if (I48_LOW..=I48_HIGH).contains(&i) => SerialType::i48(),\n                _ => SerialType::i64(),\n            },\n            ValueRef::Numeric(Numeric::Float(_)) => SerialType::f64(),\n            ValueRef::Text(t) => SerialType::text(t.value.len() as u64),\n            ValueRef::Blob(b) => SerialType::blob(b.len() as u64),\n        }\n    }\n}\n\nimpl From<SerialType> for u64 {\n    fn from(serial_type: SerialType) -> Self {\n        serial_type.0\n    }\n}\n\nimpl TryFrom<u64> for SerialType {\n    type Error = LimboError;\n\n    #[inline(always)]\n    fn try_from(uint: u64) -> Result<Self> {\n        if unlikely(uint == 10 || uint == 11) {\n            return Err(LimboError::Corrupt(format!(\"Invalid serial type: {uint}\")));\n        }\n        Ok(SerialType(uint))\n    }\n}\n\nimpl Record {\n    pub fn new(values: Vec<Value>) -> Self {\n        Self { values }\n    }\n\n    /// Calculates the total size needed for a SQLite record header.\n    ///\n    /// The record header consists of:\n    /// 1. A varint encoding the total header size (self-referentially, e.g. a 100 byte header literally has the number '100' in the header suffix)\n    /// 2. A sequence of varints encoding the serial types\n    ///\n    /// For small headers (<=126 bytes), we only need 1 byte to encode the header size, because 127 fits in 7 bits (varint uses 7 bits for the value and 1 continuation bit)\n    /// For larger headers, we need to account for the variable length of the header size varint.\n    pub fn calc_header_size(sizeof_serial_types: usize) -> usize {\n        if sizeof_serial_types < i8::MAX as usize {\n            return sizeof_serial_types + 1;\n        }\n\n        let mut header_size = sizeof_serial_types;\n        // For larger headers, calculate how many bytes we need for the header size varint\n        let mut temp_buf = [0u8; 9];\n        let mut prev_header_size;\n\n        loop {\n            prev_header_size = header_size;\n            let varint_len = write_varint(&mut temp_buf, header_size as u64);\n            header_size = sizeof_serial_types + varint_len;\n\n            if header_size == prev_header_size {\n                break;\n            }\n        }\n\n        header_size\n    }\n\n    pub fn serialize(&self, buf: &mut Vec<u8>) {\n        let initial_i = buf.len();\n\n        // write serial types\n        for value in &self.values {\n            let serial_type = SerialType::from(value);\n            buf.resize(buf.len() + 9, 0); // Ensure space for varint (1-9 bytes in length)\n            let len = buf.len();\n            let n = write_varint(&mut buf[len - 9..], serial_type.into());\n            buf.truncate(buf.len() - 9 + n); // Remove unused bytes\n        }\n\n        let mut header_size = buf.len() - initial_i;\n        // write content\n        for value in &self.values {\n            match value {\n                Value::Null => {}\n                Value::Numeric(Numeric::Integer(i)) => {\n                    let serial_type = SerialType::from(value);\n                    match serial_type.kind() {\n                        SerialTypeKind::ConstInt0 | SerialTypeKind::ConstInt1 => {}\n                        SerialTypeKind::I8 => buf.extend_from_slice(&(*i as i8).to_be_bytes()),\n                        SerialTypeKind::I16 => buf.extend_from_slice(&(*i as i16).to_be_bytes()),\n                        SerialTypeKind::I24 => {\n                            buf.extend_from_slice(&(*i as i32).to_be_bytes()[1..])\n                        } // remove most significant byte\n                        SerialTypeKind::I32 => buf.extend_from_slice(&(*i as i32).to_be_bytes()),\n                        SerialTypeKind::I48 => buf.extend_from_slice(&i.to_be_bytes()[2..]), // remove 2 most significant bytes\n                        SerialTypeKind::I64 => buf.extend_from_slice(&i.to_be_bytes()),\n                        _ => {\n                            mark_unlikely();\n                            unreachable!();\n                        }\n                    }\n                }\n                Value::Numeric(Numeric::Float(f)) => {\n                    buf.extend_from_slice(&f64::from(*f).to_be_bytes())\n                }\n                Value::Text(t) => buf.extend_from_slice(t.value.as_bytes()),\n                Value::Blob(b) => buf.extend_from_slice(b),\n            };\n        }\n\n        let mut header_bytes_buf: Vec<u8> = Vec::new();\n        header_size = Record::calc_header_size(header_size);\n        header_bytes_buf.extend(std::iter::repeat_n(0, 9));\n        let n = write_varint(header_bytes_buf.as_mut_slice(), header_size as u64);\n        header_bytes_buf.truncate(n);\n        buf.splice(initial_i..initial_i, header_bytes_buf.iter().cloned());\n    }\n}\n\npub enum Cursor {\n    BTree(Box<dyn CursorTrait>),\n    IndexMethod(Box<dyn IndexMethodCursor>),\n    Pseudo(Box<PseudoCursor>),\n    Sorter(Box<Sorter>),\n    Virtual(VirtualTableCursor),\n    MaterializedView(Box<crate::incremental::cursor::MaterializedViewCursor>),\n}\n\nimpl Debug for Cursor {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Self::BTree(..) => f.debug_tuple(\"BTree\").finish(),\n            Self::IndexMethod(..) => f.debug_tuple(\"IndexMethod\").finish(),\n            Self::Pseudo(..) => f.debug_tuple(\"Pseudo\").finish(),\n            Self::Sorter(..) => f.debug_tuple(\"Sorter\").finish(),\n            Self::Virtual(..) => f.debug_tuple(\"Virtual\").finish(),\n            Self::MaterializedView(..) => f.debug_tuple(\"MaterializedView\").finish(),\n        }\n    }\n}\n\nimpl Cursor {\n    pub fn new_btree(cursor: Box<dyn CursorTrait>) -> Self {\n        Self::BTree(cursor)\n    }\n\n    pub fn new_pseudo(cursor: PseudoCursor) -> Self {\n        Self::Pseudo(Box::new(cursor))\n    }\n\n    pub fn new_sorter(cursor: Sorter) -> Self {\n        Self::Sorter(Box::new(cursor))\n    }\n\n    pub fn new_materialized_view(\n        cursor: crate::incremental::cursor::MaterializedViewCursor,\n    ) -> Self {\n        Self::MaterializedView(Box::new(cursor))\n    }\n\n    pub fn as_btree_mut(&mut self) -> &mut dyn CursorTrait {\n        match self {\n            Self::BTree(cursor) => cursor.as_mut(),\n            _ => {\n                mark_unlikely();\n                panic!(\"Cursor is not a btree cursor\");\n            }\n        }\n    }\n\n    pub fn as_pseudo_mut(&mut self) -> &mut PseudoCursor {\n        match self {\n            Self::Pseudo(cursor) => cursor,\n            _ => {\n                mark_unlikely();\n                panic!(\"Cursor is not a pseudo cursor\");\n            }\n        }\n    }\n\n    pub fn as_sorter_mut(&mut self) -> &mut Sorter {\n        match self {\n            Self::Sorter(cursor) => cursor,\n            _ => {\n                mark_unlikely();\n                panic!(\"Cursor is not a sorter cursor\")\n            }\n        }\n    }\n\n    pub fn as_virtual_mut(&mut self) -> &mut VirtualTableCursor {\n        match self {\n            Self::Virtual(cursor) => cursor,\n            _ => {\n                mark_unlikely();\n                panic!(\"Cursor is not a virtual cursor\")\n            }\n        }\n    }\n\n    pub fn as_materialized_view_mut(\n        &mut self,\n    ) -> &mut crate::incremental::cursor::MaterializedViewCursor {\n        match self {\n            Self::MaterializedView(cursor) => cursor,\n            _ => {\n                mark_unlikely();\n                panic!(\"Cursor is not a materialized view cursor\");\n            }\n        }\n    }\n\n    pub fn as_index_method_mut(&mut self) -> &mut dyn IndexMethodCursor {\n        match self {\n            Self::IndexMethod(cursor) => cursor.as_mut(),\n            _ => {\n                mark_unlikely();\n                panic!(\"Cursor is not an IndexMethod cursor\");\n            }\n        }\n    }\n\n    pub fn set_null_flag(&mut self, flag: bool) {\n        match self {\n            Self::BTree(cursor) => cursor.set_null_flag(flag),\n            Self::Virtual(cursor) => cursor.set_null_flag(flag),\n            _ => {\n                mark_unlikely();\n                panic!(\"set_null_flag on unexpected cursor type\");\n            }\n        }\n    }\n}\n\n#[derive(Debug)]\n#[must_use]\npub enum IOCompletions {\n    Single(Completion),\n}\n\npub struct IOCompletionAsync<'a, I: ?Sized + IO> {\n    io: &'a I,\n    completion: Completion,\n}\n\nimpl<'a, I: ?Sized + IO> Future for IOCompletionAsync<'a, I> {\n    type Output = Result<()>;\n\n    fn poll(\n        mut self: std::pin::Pin<&mut Self>,\n        cx: &mut std::task::Context<'_>,\n    ) -> std::task::Poll<Self::Output> {\n        let completion = std::pin::pin!(&mut self.as_mut().completion);\n        match completion.poll(cx) {\n            Poll::Pending => {\n                self.io.step()?;\n                Poll::Pending\n            }\n            res => res,\n        }\n    }\n}\n\nimpl IOCompletions {\n    /// Wais for the Completions to complete\n    pub fn wait<I: ?Sized + IO>(self, io: &I) -> Result<()> {\n        match self {\n            IOCompletions::Single(c) => io.wait_for_completion(c),\n        }\n    }\n\n    /// Waits for Completion to complete and `steps` IO. Ideally the user should do the stepping,\n    /// but we do not have yet a good api for this\n    pub async fn wait_async<I: ?Sized + IO>(self, io: &I) -> Result<()> {\n        match self {\n            IOCompletions::Single(c) => IOCompletionAsync { io, completion: c }.await,\n        }\n    }\n\n    pub fn finished(&self) -> bool {\n        match self {\n            IOCompletions::Single(c) => c.finished(),\n        }\n    }\n\n    /// Returns true if this is an explicit yield — a signal to return control\n    /// to the cooperative scheduler so other fibers can make progress.\n    pub fn is_explicit_yield(&self) -> bool {\n        match self {\n            IOCompletions::Single(c) => c.is_explicit_yield(),\n        }\n    }\n\n    /// Send abort signal to completions\n    pub fn abort(&self) {\n        match self {\n            IOCompletions::Single(c) => c.abort(),\n        }\n    }\n\n    pub fn get_error(&self) -> Option<CompletionError> {\n        match self {\n            IOCompletions::Single(c) => c.get_error(),\n        }\n    }\n\n    pub fn set_waker(&self, waker: Option<&Waker>) {\n        if let Some(waker) = waker {\n            match self {\n                IOCompletions::Single(c) => c.set_waker(waker),\n            }\n        }\n    }\n}\n\n#[derive(Debug)]\n#[must_use]\npub enum IOResult<T> {\n    Done(T),\n    IO(IOCompletions),\n}\n\nimpl<T> IOResult<T> {\n    #[inline]\n    pub fn is_io(&self) -> bool {\n        matches!(self, IOResult::IO(..))\n    }\n\n    #[inline]\n    pub fn io(self) -> Option<IOCompletions> {\n        match self {\n            IOResult::Done(_) => None,\n            IOResult::IO(io) => Some(io),\n        }\n    }\n\n    #[inline]\n    pub fn map<U>(self, func: impl FnOnce(T) -> U) -> IOResult<U> {\n        match self {\n            IOResult::Done(t) => IOResult::Done(func(t)),\n            IOResult::IO(io) => IOResult::IO(io),\n        }\n    }\n}\n\n/// Evaluate a Result<IOResult<T>>, if IO return IO.\n#[macro_export]\nmacro_rules! return_if_io {\n    ($expr:expr) => {\n        match $expr {\n            Ok(IOResult::Done(v)) => v,\n            Ok(IOResult::IO(io)) => return Ok(IOResult::IO(io)),\n            Err(err) => {\n                branches::mark_unlikely();\n                return Err(err);\n            }\n        }\n    };\n}\n\n#[macro_export]\nmacro_rules! return_and_restore_if_io {\n    ($field:expr, $saved_state:expr, $e:expr) => {\n        match $e {\n            Ok(IOResult::Done(v)) => v,\n            Ok(IOResult::IO(io)) => {\n                let _ = std::mem::replace($field, $saved_state);\n                return Ok(IOResult::IO(io));\n            }\n            Err(e) => {\n                let _ = std::mem::replace($field, $saved_state);\n                return Err(e);\n            }\n        }\n    };\n}\n\n#[derive(Debug, PartialEq, Clone, Copy)]\npub enum SeekResult {\n    /// Record matching the [SeekOp] found in the B-tree and cursor was positioned to point onto that record\n    Found,\n    /// Record matching the [SeekOp] doesn't exists in the B-tree\n    NotFound,\n    /// This result can happen only if eq_only for [SeekOp] is false\n    /// In this case Seek can position cursor to the leaf page boundaries (before the start, after the end)\n    /// (e.g. if leaf page holds rows with keys from range [1..10], key 10 is absent and [SeekOp] is >= 10)\n    ///\n    /// turso-db has this extra [SeekResult] in order to make [BTreeCursor::seek] method to position cursor at\n    /// the leaf of potential insertion, but also communicate to caller the fact that current cursor position\n    /// doesn't hold a matching entry\n    /// (necessary for Seek{XX} VM op-codes, so these op-codes will try to advance cursor in order to move it to matching entry)\n    TryAdvance,\n}\n\n#[derive(Clone, Copy, PartialEq, Eq, Debug)]\n/// The match condition of a table/index seek.\npub enum SeekOp {\n    /// If eq_only is true, this means in practice:\n    /// We are iterating forwards, but we are really looking for an exact match on the seek key.\n    GE {\n        eq_only: bool,\n    },\n    GT,\n    /// If eq_only is true, this means in practice:\n    /// We are iterating backwards, but we are really looking for an exact match on the seek key.\n    LE {\n        eq_only: bool,\n    },\n    LT,\n}\n\nimpl SeekOp {\n    /// A given seek op implies an iteration direction.\n    ///\n    /// For example, a seek with SeekOp::GT implies:\n    /// Find the first table/index key that compares greater than the seek key\n    /// -> used in forwards iteration.\n    ///\n    /// A seek with SeekOp::LE implies:\n    /// Find the last table/index key that compares less than or equal to the seek key\n    /// -> used in backwards iteration.\n    #[inline(always)]\n    pub fn iteration_direction(&self) -> IterationDirection {\n        match self {\n            SeekOp::GE { .. } | SeekOp::GT => IterationDirection::Forwards,\n            SeekOp::LE { .. } | SeekOp::LT => IterationDirection::Backwards,\n        }\n    }\n\n    pub fn eq_only(&self) -> bool {\n        match self {\n            SeekOp::GE { eq_only } | SeekOp::LE { eq_only } => *eq_only,\n            _ => false,\n        }\n    }\n\n    pub fn reverse(&self) -> Self {\n        match self {\n            SeekOp::GE { eq_only } => SeekOp::LE { eq_only: *eq_only },\n            SeekOp::GT => SeekOp::LT,\n            SeekOp::LE { eq_only } => SeekOp::GE { eq_only: *eq_only },\n            SeekOp::LT => SeekOp::GT,\n        }\n    }\n}\n\n#[derive(Clone, PartialEq, Debug)]\npub enum SeekKey<'a> {\n    TableRowId(i64),\n    IndexKey(&'a ImmutableRecord),\n}\n\n#[derive(Debug)]\npub enum DatabaseChangeType {\n    Delete,\n    Update { bin_record: Vec<u8> },\n    Insert { bin_record: Vec<u8> },\n}\n\n#[derive(Debug)]\npub struct DatabaseChange {\n    pub change_id: i64,\n    pub change_time: u64,\n    pub change: DatabaseChangeType,\n    pub table_name: String,\n    pub id: i64,\n}\n\n#[derive(Debug)]\npub struct WalFrameInfo {\n    pub page_no: u32,\n    pub db_size: u32,\n}\n\n#[derive(Debug, PartialEq)]\npub struct WalState {\n    pub checkpoint_seq_no: u32,\n    pub max_frame: u64,\n}\n\nimpl WalFrameInfo {\n    pub fn is_commit_frame(&self) -> bool {\n        self.db_size > 0\n    }\n    pub fn from_frame_header(frame: &[u8]) -> Self {\n        let page_no = u32::from_be_bytes(frame[0..4].try_into().unwrap());\n        let db_size = u32::from_be_bytes(frame[4..8].try_into().unwrap());\n        Self { page_no, db_size }\n    }\n    pub fn put_to_frame_header(&self, frame: &mut [u8]) {\n        frame[0..4].copy_from_slice(&self.page_no.to_be_bytes());\n        frame[4..8].copy_from_slice(&self.db_size.to_be_bytes());\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::translate::collate::CollationSeq;\n\n    #[test]\n    fn test_value_iterator_simple() {\n        let mut buf = Vec::new();\n        let record = Record::new(vec![Value::from_i64(42), Value::Text(Text::new(\"hello\"))]);\n        record.serialize(&mut buf);\n\n        let iter = ValueIterator::new(&buf).unwrap();\n        assert!(!iter.is_empty());\n        assert_eq!(iter.clone().count(), 2);\n\n        let mut iter = ValueIterator::new(&buf).unwrap();\n\n        let val = iter.next().unwrap().unwrap();\n        assert_eq!(val, ValueRef::from_i64(42));\n\n        let val = iter.next().unwrap().unwrap();\n        assert_eq!(\n            val,\n            ValueRef::Text(TextRef::new(\"hello\", TextSubtype::Text))\n        );\n\n        assert!(iter.next().is_none());\n    }\n\n    #[test]\n    fn test_value_iterator_nulls() {\n        let mut buf = Vec::new();\n        let record = Record::new(vec![Value::Null, Value::Null, Value::Null]);\n        record.serialize(&mut buf);\n\n        let iter = ValueIterator::new(&buf).unwrap();\n\n        for val in iter {\n            assert_eq!(val.unwrap(), ValueRef::Null);\n        }\n    }\n\n    #[test]\n    fn test_value_iterator_mixed_types() {\n        let mut buf = Vec::new();\n        let record = Record::new(vec![\n            Value::Null,\n            Value::from_i64(100),\n            Value::from_f64(std::f64::consts::PI),\n            Value::Text(Text::new(\"test\")),\n            Value::Blob(vec![1, 2, 3]),\n            Value::from_i64(0),\n            Value::from_i64(1),\n        ]);\n        record.serialize(&mut buf);\n\n        let iter = ValueIterator::new(&buf).unwrap();\n        let values: Vec<_> = iter.collect::<Result<Vec<_>>>().unwrap();\n\n        assert_eq!(values[0], ValueRef::Null);\n        assert_eq!(values[1], ValueRef::from_i64(100));\n        assert_eq!(values[2], ValueRef::from_f64(std::f64::consts::PI));\n        assert_eq!(\n            values[3],\n            ValueRef::Text(TextRef::new(\"test\", TextSubtype::Text))\n        );\n        assert_eq!(values[4], ValueRef::Blob(&[1, 2, 3]));\n        assert_eq!(values[5], ValueRef::from_i64(0));\n        assert_eq!(values[6], ValueRef::from_i64(1));\n    }\n\n    #[test]\n    fn test_value_iterator_large_record() {\n        let mut buf = Vec::new();\n        let values: Vec<Value> = (0..20).map(|i| Value::from_i64(i as i64)).collect();\n        let record = Record::new(values);\n        record.serialize(&mut buf);\n\n        let iter = ValueIterator::new(&buf).unwrap();\n        assert_eq!(iter.count(), 20);\n\n        let iter = ValueIterator::new(&buf).unwrap();\n        for (i, val) in iter.enumerate() {\n            assert_eq!(val.unwrap(), ValueRef::from_i64(i as i64));\n        }\n    }\n\n    #[test]\n    fn test_value_iterator_zero_allocation() {\n        let mut buf = Vec::new();\n        let values: Vec<Value> = (0..5).map(|i| Value::from_i64(i as i64)).collect();\n        let record = Record::new(values);\n        record.serialize(&mut buf);\n\n        let mut iter = ValueIterator::new(&buf).unwrap();\n        let _ = iter.next();\n        let _ = iter.next();\n    }\n\n    pub fn compare_immutable_for_testing(\n        l: &[ValueRef],\n        r: &[ValueRef],\n        index_key_info: &[KeyInfo],\n        tie_breaker: std::cmp::Ordering,\n    ) -> std::cmp::Ordering {\n        let min_len = l.len().min(r.len());\n\n        for i in 0..min_len {\n            let column_order = index_key_info[i].sort_order;\n            let collation = index_key_info[i].collation;\n\n            let cmp = match (&l[i], &r[i]) {\n                (ValueRef::Text(left), ValueRef::Text(right)) => {\n                    collation.compare_strings(left, right)\n                }\n                _ => l[i].partial_cmp(&r[i]).unwrap_or(std::cmp::Ordering::Equal),\n            };\n\n            if cmp != std::cmp::Ordering::Equal {\n                return match column_order {\n                    SortOrder::Asc => cmp,\n                    SortOrder::Desc => cmp.reverse(),\n                };\n            }\n        }\n\n        tie_breaker\n    }\n\n    fn create_record(values: Vec<Value>) -> ImmutableRecord {\n        let registers: Vec<Register> = values.into_iter().map(Register::Value).collect();\n        ImmutableRecord::from_registers(&registers, registers.len())\n    }\n\n    fn create_index_info(\n        num_cols: usize,\n        sort_orders: Vec<SortOrder>,\n        collations: Vec<CollationSeq>,\n    ) -> IndexInfo {\n        IndexInfo {\n            key_info: sort_orders\n                .into_iter()\n                .zip(collations)\n                .map(|(sort_order, collation)| KeyInfo {\n                    sort_order,\n                    collation,\n                })\n                .collect(),\n            has_rowid: false,\n            num_cols,\n            is_unique: false,\n        }\n    }\n\n    fn assert_compare_matches_full_comparison(\n        serialized_values: Vec<Value>,\n        unpacked_values: Vec<ValueRef>,\n        index_info: &IndexInfo,\n        test_name: &str,\n    ) {\n        let serialized = create_record(serialized_values.clone());\n\n        let serialized_ref_values: Vec<ValueRef> =\n            serialized_values.iter().map(Value::as_ref).collect();\n\n        let tie_breaker = std::cmp::Ordering::Equal;\n\n        let gold_result = compare_immutable_for_testing(\n            &serialized_ref_values,\n            &unpacked_values,\n            &index_info.key_info,\n            tie_breaker,\n        );\n\n        let comparer = find_compare(unpacked_values.iter().peekable(), index_info);\n        let optimized_result = comparer\n            .compare(&serialized, &unpacked_values, index_info, 0, tie_breaker)\n            .unwrap();\n\n        assert_eq!(\n            gold_result, optimized_result,\n            \"Test '{test_name}' failed: Full Comparison: {gold_result:?}, Optimized: {optimized_result:?}, Strategy: {comparer:?}\"\n        );\n\n        let generic_result = compare_records_generic(\n            &serialized,\n            unpacked_values.iter(),\n            index_info,\n            0,\n            tie_breaker,\n        )\n        .unwrap();\n        assert_eq!(\n            gold_result, generic_result,\n            \"Test '{test_name}' failed with generic: Full Comparison: {gold_result:?}, Generic: {generic_result:?}\\n LHS: {serialized_values:?}\\n RHS: {unpacked_values:?}\"\n        );\n    }\n\n    #[test]\n    fn test_calc_header_size() {\n        // Test 1-byte header size (serial type sizes 0 to 126)\n        const MIN_SERIALTYPES_SIZE_FOR_1_BYTE_HEADER: usize = 0;\n        assert_eq!(\n            Record::calc_header_size(MIN_SERIALTYPES_SIZE_FOR_1_BYTE_HEADER),\n            MIN_SERIALTYPES_SIZE_FOR_1_BYTE_HEADER + 1\n        );\n        const BITS_7_MAX: usize = (1 << 7) - 1; // varints use 7 bits for the value and 1 continuation bit\n        const MAX_SERIALTYPES_SIZE_FOR_1_BYTE_HEADER: usize = BITS_7_MAX - 1;\n        assert_eq!(\n            Record::calc_header_size(MAX_SERIALTYPES_SIZE_FOR_1_BYTE_HEADER),\n            MAX_SERIALTYPES_SIZE_FOR_1_BYTE_HEADER + 1\n        );\n\n        // Test 2-byte header size (serial type sizes 127 to 16381)\n        const MIN_SERIALTYPES_SIZE_FOR_2_BYTE_HEADER: usize =\n            MAX_SERIALTYPES_SIZE_FOR_1_BYTE_HEADER + 1;\n        assert_eq!(\n            Record::calc_header_size(MIN_SERIALTYPES_SIZE_FOR_2_BYTE_HEADER),\n            MIN_SERIALTYPES_SIZE_FOR_2_BYTE_HEADER + 2\n        );\n        const BITS_14_MAX: usize = (1 << 14) - 1;\n        const MAX_SERIALTYPES_SIZE_FOR_2_BYTE_HEADER: usize = BITS_14_MAX - 2;\n        assert_eq!(\n            Record::calc_header_size(MAX_SERIALTYPES_SIZE_FOR_2_BYTE_HEADER),\n            MAX_SERIALTYPES_SIZE_FOR_2_BYTE_HEADER + 2\n        );\n\n        // Test 3-byte header size (serial type sizes 16382 to 2097148)\n        const MIN_SERIALTYPES_SIZE_FOR_3_BYTE_HEADER: usize =\n            MAX_SERIALTYPES_SIZE_FOR_2_BYTE_HEADER + 1;\n        assert_eq!(\n            Record::calc_header_size(MIN_SERIALTYPES_SIZE_FOR_3_BYTE_HEADER),\n            MIN_SERIALTYPES_SIZE_FOR_3_BYTE_HEADER + 3\n        );\n        const BITS_21_MAX: usize = (1 << 21) - 1;\n        const MAX_SERIALTYPES_SIZE_FOR_3_BYTE_HEADER: usize = BITS_21_MAX - 3;\n        assert_eq!(\n            Record::calc_header_size(MAX_SERIALTYPES_SIZE_FOR_3_BYTE_HEADER),\n            MAX_SERIALTYPES_SIZE_FOR_3_BYTE_HEADER + 3\n        );\n\n        // Test 4-byte header size (serial type sizes 2097149 to 268435451)\n        const MIN_SERIALTYPES_SIZE_FOR_4_BYTE_HEADER: usize =\n            MAX_SERIALTYPES_SIZE_FOR_3_BYTE_HEADER + 1;\n        assert_eq!(\n            Record::calc_header_size(MIN_SERIALTYPES_SIZE_FOR_4_BYTE_HEADER),\n            MIN_SERIALTYPES_SIZE_FOR_4_BYTE_HEADER + 4\n        );\n        const BITS_28_MAX: usize = (1 << 28) - 1;\n        const MAX_SERIALTYPES_SIZE_FOR_4_BYTE_HEADER: usize = BITS_28_MAX - 4;\n        assert_eq!(\n            Record::calc_header_size(MAX_SERIALTYPES_SIZE_FOR_4_BYTE_HEADER),\n            MAX_SERIALTYPES_SIZE_FOR_4_BYTE_HEADER + 4\n        );\n    }\n\n    #[test]\n    fn test_integer_fast_path() {\n        let index_info = create_index_info(\n            2,\n            vec![SortOrder::Asc, SortOrder::Asc],\n            vec![CollationSeq::Binary; 2],\n        );\n\n        let test_cases = vec![\n            (\n                vec![Value::from_i64(42)],\n                vec![ValueRef::from_i64(42)],\n                \"equal_integers\",\n            ),\n            (\n                vec![Value::from_i64(10)],\n                vec![ValueRef::from_i64(20)],\n                \"less_than_integers\",\n            ),\n            (\n                vec![Value::from_i64(30)],\n                vec![ValueRef::from_i64(20)],\n                \"greater_than_integers\",\n            ),\n            (\n                vec![Value::from_i64(0)],\n                vec![ValueRef::from_i64(0)],\n                \"zero_integers\",\n            ),\n            (\n                vec![Value::from_i64(-5)],\n                vec![ValueRef::from_i64(-5)],\n                \"negative_integers\",\n            ),\n            (\n                vec![Value::from_i64(i64::MAX)],\n                vec![ValueRef::from_i64(i64::MAX)],\n                \"max_integers\",\n            ),\n            (\n                vec![Value::from_i64(i64::MIN)],\n                vec![ValueRef::from_i64(i64::MIN)],\n                \"min_integers\",\n            ),\n            (\n                vec![Value::from_i64(42), Value::Text(Text::new(\"hello\"))],\n                vec![\n                    ValueRef::from_i64(42),\n                    ValueRef::Text(TextRef::new(\"hello\", TextSubtype::Text)),\n                ],\n                \"integer_text_equal\",\n            ),\n            (\n                vec![Value::from_i64(42), Value::Text(Text::new(\"hello\"))],\n                vec![\n                    ValueRef::from_i64(42),\n                    ValueRef::Text(TextRef::new(\"world\", TextSubtype::Text)),\n                ],\n                \"integer_equal_text_different\",\n            ),\n        ];\n\n        for (serialized_values, unpacked_values, test_name) in test_cases {\n            println!(\n                \"Testing integer fast path `{test_name}`\\nLHS: {serialized_values:?}\\nRHS: {unpacked_values:?}\"\n            );\n            assert_compare_matches_full_comparison(\n                serialized_values,\n                unpacked_values,\n                &index_info,\n                test_name,\n            );\n        }\n    }\n\n    #[test]\n    fn test_string_fast_path() {\n        let index_info = create_index_info(\n            2,\n            vec![SortOrder::Asc, SortOrder::Asc],\n            vec![CollationSeq::Binary; 2],\n        );\n\n        let test_cases = vec![\n            (\n                vec![Value::Text(Text::new(\"hello\"))],\n                vec![ValueRef::Text(TextRef::new(\"hello\", TextSubtype::Text))],\n                \"equal_strings\",\n            ),\n            (\n                vec![Value::Text(Text::new(\"abc\"))],\n                vec![ValueRef::Text(TextRef::new(\"def\", TextSubtype::Text))],\n                \"less_than_strings\",\n            ),\n            (\n                vec![Value::Text(Text::new(\"xyz\"))],\n                vec![ValueRef::Text(TextRef::new(\"abc\", TextSubtype::Text))],\n                \"greater_than_strings\",\n            ),\n            (\n                vec![Value::Text(Text::new(\"\"))],\n                vec![ValueRef::Text(TextRef::new(\"\", TextSubtype::Text))],\n                \"empty_strings\",\n            ),\n            (\n                vec![Value::Text(Text::new(\"a\"))],\n                vec![ValueRef::Text(TextRef::new(\"aa\", TextSubtype::Text))],\n                \"prefix_strings\",\n            ),\n            // Multi-field with string first\n            (\n                vec![Value::Text(Text::new(\"hello\")), Value::from_i64(42)],\n                vec![\n                    ValueRef::Text(TextRef::new(\"hello\", TextSubtype::Text)),\n                    ValueRef::from_i64(42),\n                ],\n                \"string_integer_equal\",\n            ),\n            (\n                vec![Value::Text(Text::new(\"hello\")), Value::from_i64(42)],\n                vec![\n                    ValueRef::Text(TextRef::new(\"hello\", TextSubtype::Text)),\n                    ValueRef::from_i64(99),\n                ],\n                \"string_equal_integer_different\",\n            ),\n        ];\n\n        for (serialized_values, unpacked_values, test_name) in test_cases {\n            assert_compare_matches_full_comparison(\n                serialized_values,\n                unpacked_values,\n                &index_info,\n                test_name,\n            );\n        }\n    }\n\n    #[test]\n    fn test_type_precedence() {\n        let index_info = create_index_info(1, vec![SortOrder::Asc], vec![CollationSeq::Binary]);\n\n        // Test SQLite type precedence: NULL < Numbers < Text < Blob\n        let test_cases = vec![\n            // NULL vs others\n            (\n                vec![Value::Null],\n                vec![ValueRef::from_i64(42)],\n                \"null_vs_integer\",\n            ),\n            (\n                vec![Value::Null],\n                vec![ValueRef::from_f64(64.4)],\n                \"null_vs_float\",\n            ),\n            (\n                vec![Value::Null],\n                vec![ValueRef::Text(TextRef::new(\"hello\", TextSubtype::Text))],\n                \"null_vs_text\",\n            ),\n            (\n                vec![Value::Null],\n                vec![ValueRef::Blob(b\"blob\")],\n                \"null_vs_blob\",\n            ),\n            // Numbers vs Text/Blob\n            (\n                vec![Value::from_i64(42)],\n                vec![ValueRef::Text(TextRef::new(\"hello\", TextSubtype::Text))],\n                \"integer_vs_text\",\n            ),\n            (\n                vec![Value::from_f64(64.4)],\n                vec![ValueRef::Text(TextRef::new(\"hello\", TextSubtype::Text))],\n                \"float_vs_text\",\n            ),\n            (\n                vec![Value::from_i64(42)],\n                vec![ValueRef::Blob(b\"blob\")],\n                \"integer_vs_blob\",\n            ),\n            (\n                vec![Value::from_f64(64.4)],\n                vec![ValueRef::Blob(b\"blob\")],\n                \"float_vs_blob\",\n            ),\n            // Text vs Blob\n            (\n                vec![Value::Text(Text::new(\"hello\"))],\n                vec![ValueRef::Blob(b\"blob\")],\n                \"text_vs_blob\",\n            ),\n            // Integer vs Float (affinity conversion)\n            (\n                vec![Value::from_i64(42)],\n                vec![ValueRef::from_f64(42.0)],\n                \"integer_vs_equal_float\",\n            ),\n            (\n                vec![Value::from_i64(42)],\n                vec![ValueRef::from_f64(42.5)],\n                \"integer_vs_different_float\",\n            ),\n            (\n                vec![Value::from_f64(42.5)],\n                vec![ValueRef::from_i64(42)],\n                \"float_vs_integer\",\n            ),\n        ];\n\n        for (serialized_values, unpacked_values, test_name) in test_cases {\n            assert_compare_matches_full_comparison(\n                serialized_values,\n                unpacked_values,\n                &index_info,\n                test_name,\n            );\n        }\n    }\n\n    #[test]\n    fn test_sort_order_desc() {\n        let index_info = create_index_info(\n            2,\n            vec![SortOrder::Desc, SortOrder::Asc],\n            vec![CollationSeq::Binary; 2],\n        );\n\n        let test_cases = vec![\n            // DESC order should reverse first field comparison\n            (\n                vec![Value::from_i64(10)],\n                vec![ValueRef::from_i64(20)],\n                \"desc_integer_reversed\",\n            ),\n            (\n                vec![Value::Text(Text::new(\"abc\"))],\n                vec![ValueRef::Text(TextRef::new(\"def\", TextSubtype::Text))],\n                \"desc_string_reversed\",\n            ),\n            // Mixed sort orders\n            (\n                vec![Value::from_i64(10), Value::Text(Text::new(\"hello\"))],\n                vec![\n                    ValueRef::from_i64(20),\n                    ValueRef::Text(TextRef::new(\"hello\", TextSubtype::Text)),\n                ],\n                \"desc_first_asc_second\",\n            ),\n        ];\n\n        for (serialized_values, unpacked_values, test_name) in test_cases {\n            assert_compare_matches_full_comparison(\n                serialized_values,\n                unpacked_values,\n                &index_info,\n                test_name,\n            );\n        }\n    }\n\n    #[test]\n    fn test_edge_cases() {\n        let index_info =\n            create_index_info(15, vec![SortOrder::Asc; 15], vec![CollationSeq::Binary; 15]);\n\n        let test_cases = vec![\n            (\n                vec![Value::from_i64(42)],\n                vec![\n                    ValueRef::from_i64(42),\n                    ValueRef::Text(TextRef::new(\"extra\", TextSubtype::Text)),\n                ],\n                \"fewer_serialized_fields\",\n            ),\n            (\n                vec![Value::from_i64(42), Value::Text(Text::new(\"extra\"))],\n                vec![ValueRef::from_i64(42)],\n                \"fewer_unpacked_fields\",\n            ),\n            (vec![], vec![], \"both_empty\"),\n            (vec![], vec![ValueRef::from_i64(42)], \"empty_serialized\"),\n            (\n                (0..15).map(Value::from_i64).collect(),\n                (0..15).map(ValueRef::from_i64).collect(),\n                \"large_field_count\",\n            ),\n            (\n                vec![Value::Blob(vec![1, 2, 3])],\n                vec![ValueRef::Blob(&[1, 2, 3])],\n                \"blob_first_field\",\n            ),\n            (\n                vec![Value::Text(Text::new(\"hello\")), Value::from_i64(5)],\n                vec![ValueRef::Text(TextRef::new(\"hello\", TextSubtype::Text))],\n                \"equal_text_prefix_but_more_serialized_fields\",\n            ),\n            (\n                vec![Value::Text(Text::new(\"same\")), Value::from_i64(5)],\n                vec![\n                    ValueRef::Text(TextRef::new(\"same\", TextSubtype::Text)),\n                    ValueRef::from_i64(5),\n                ],\n                \"equal_text_then_equal_int\",\n            ),\n        ];\n\n        for (serialized_values, unpacked_values, test_name) in test_cases {\n            assert_compare_matches_full_comparison(\n                serialized_values,\n                unpacked_values,\n                &index_info,\n                test_name,\n            );\n        }\n    }\n\n    #[test]\n    fn test_skip_parameter() {\n        let index_info = create_index_info(\n            3,\n            vec![SortOrder::Asc, SortOrder::Asc, SortOrder::Asc],\n            vec![CollationSeq::Binary; 3],\n        );\n\n        let serialized = create_record(vec![\n            Value::from_i64(1),\n            Value::from_i64(2),\n            Value::from_i64(3),\n        ]);\n        let unpacked = [\n            ValueRef::from_i64(1),\n            ValueRef::from_i64(99),\n            ValueRef::from_i64(3),\n        ];\n\n        let tie_breaker = std::cmp::Ordering::Equal;\n        let result_skip_0 =\n            compare_records_generic(&serialized, unpacked.iter(), &index_info, 0, tie_breaker)\n                .unwrap();\n        let result_skip_1 =\n            compare_records_generic(&serialized, unpacked.iter(), &index_info, 1, tie_breaker)\n                .unwrap();\n\n        assert_eq!(result_skip_0, std::cmp::Ordering::Less);\n\n        assert_eq!(result_skip_1, std::cmp::Ordering::Less);\n    }\n\n    #[test]\n    fn test_strategy_selection() {\n        let collations_small = vec![CollationSeq::Binary; 3];\n        let collations_large = vec![CollationSeq::Binary; 15];\n        let index_info_small = create_index_info(\n            3,\n            vec![SortOrder::Asc, SortOrder::Asc, SortOrder::Asc],\n            collations_small,\n        );\n        let index_info_large = create_index_info(15, vec![SortOrder::Asc; 15], collations_large);\n\n        let int_values = [\n            ValueRef::from_i64(42),\n            ValueRef::Text(TextRef::new(\"hello\", TextSubtype::Text)),\n        ];\n        assert!(matches!(\n            find_compare(int_values.iter().peekable(), &index_info_small),\n            RecordCompare::Int\n        ));\n\n        let string_values = [\n            ValueRef::Text(TextRef::new(\"hello\", TextSubtype::Text)),\n            ValueRef::from_i64(42),\n        ];\n        assert!(matches!(\n            find_compare(string_values.iter().peekable(), &index_info_small),\n            RecordCompare::String\n        ));\n\n        let large_values: Vec<ValueRef> = (0..15).map(ValueRef::from_i64).collect();\n        assert!(matches!(\n            find_compare(large_values.iter().peekable(), &index_info_large),\n            RecordCompare::Generic\n        ));\n\n        let blob_values = [ValueRef::Blob(&[1, 2, 3])];\n        assert!(matches!(\n            find_compare(blob_values.iter().peekable(), &index_info_small),\n            RecordCompare::Generic\n        ));\n    }\n\n    #[test]\n    fn test_serialize_null() {\n        let record = Record::new(vec![Value::Null]);\n        let mut buf = Vec::new();\n        record.serialize(&mut buf);\n\n        let header_length = record.values.len() + 1;\n        let header = &buf[0..header_length];\n        // First byte should be header size\n        assert_eq!(header[0], header_length as u8);\n        // Second byte should be serial type for NULL\n        assert_eq!(header[1] as u64, u64::from(SerialType::null()));\n        // Check that the buffer is empty after the header\n        assert_eq!(buf.len(), header_length);\n    }\n\n    #[test]\n    fn test_serialize_integers() {\n        let record = Record::new(vec![\n            Value::from_i64(0),                 // Should use ConstInt0\n            Value::from_i64(1),                 // Should use ConstInt1\n            Value::from_i64(42),                // Should use SERIAL_TYPE_I8\n            Value::from_i64(1000),              // Should use SERIAL_TYPE_I16\n            Value::from_i64(1_000_000),         // Should use SERIAL_TYPE_I24\n            Value::from_i64(1_000_000_000),     // Should use SERIAL_TYPE_I32\n            Value::from_i64(1_000_000_000_000), // Should use SERIAL_TYPE_I48\n            Value::from_i64(i64::MAX),          // Should use SERIAL_TYPE_I64\n        ]);\n        let mut buf = Vec::new();\n        record.serialize(&mut buf);\n\n        let header_length = record.values.len() + 1;\n        let header = &buf[0..header_length];\n        // First byte should be header size\n        assert_eq!(header[0], header_length as u8); // Header should be larger than number of values\n\n        // Check that correct serial types were chosen\n        assert_eq!(header[1] as u64, u64::from(SerialType::const_int0())); // 8\n        assert_eq!(header[2] as u64, u64::from(SerialType::const_int1())); // 9\n        assert_eq!(header[3] as u64, u64::from(SerialType::i8())); // 1\n        assert_eq!(header[4] as u64, u64::from(SerialType::i16())); // 2\n        assert_eq!(header[5] as u64, u64::from(SerialType::i24())); // 3\n        assert_eq!(header[6] as u64, u64::from(SerialType::i32())); // 4\n        assert_eq!(header[7] as u64, u64::from(SerialType::i48())); // 5\n        assert_eq!(header[8] as u64, u64::from(SerialType::i64())); // 6\n\n        // test that the bytes after the header can be interpreted as the correct values\n        let mut cur_offset = header_length;\n\n        // Value::from_i64(0) - ConstInt0: NO PAYLOAD BYTES\n        // Value::from_i64(1) - ConstInt1: NO PAYLOAD BYTES\n\n        // Value::from_i64(42) - I8: 1 byte\n        let i8_bytes = &buf[cur_offset..cur_offset + size_of::<i8>()];\n        cur_offset += size_of::<i8>();\n\n        // Value::from_i64(1000) - I16: 2 bytes\n        let i16_bytes = &buf[cur_offset..cur_offset + size_of::<i16>()];\n        cur_offset += size_of::<i16>();\n\n        // Value::from_i64(1_000_000) - I24: 3 bytes\n        let i24_bytes = &buf[cur_offset..cur_offset + 3];\n        cur_offset += 3;\n\n        // Value::from_i64(1_000_000_000) - I32: 4 bytes\n        let i32_bytes = &buf[cur_offset..cur_offset + size_of::<i32>()];\n        cur_offset += size_of::<i32>();\n\n        // Value::from_i64(1_000_000_000_000) - I48: 6 bytes\n        let i48_bytes = &buf[cur_offset..cur_offset + 6];\n        cur_offset += 6;\n\n        // Value::from_i64(i64::MAX) - I64: 8 bytes\n        let i64_bytes = &buf[cur_offset..cur_offset + size_of::<i64>()];\n\n        // Verify the payload values\n        let val_int8 = i8::from_be_bytes(i8_bytes.try_into().unwrap());\n        let val_int16 = i16::from_be_bytes(i16_bytes.try_into().unwrap());\n\n        let mut i24_with_padding = vec![0];\n        i24_with_padding.extend(i24_bytes);\n        let val_int24 = i32::from_be_bytes(i24_with_padding.try_into().unwrap());\n\n        let val_int32 = i32::from_be_bytes(i32_bytes.try_into().unwrap());\n\n        let mut i48_with_padding = vec![0, 0];\n        i48_with_padding.extend(i48_bytes);\n        let val_int48 = i64::from_be_bytes(i48_with_padding.try_into().unwrap());\n\n        let val_int64 = i64::from_be_bytes(i64_bytes.try_into().unwrap());\n\n        assert_eq!(val_int8, 42);\n        assert_eq!(val_int16, 1000);\n        assert_eq!(val_int24, 1_000_000);\n        assert_eq!(val_int32, 1_000_000_000);\n        assert_eq!(val_int48, 1_000_000_000_000);\n        assert_eq!(val_int64, i64::MAX);\n\n        //Size of buffer = header + payload bytes\n        // ConstInt0 and ConstInt1 contribute 0 bytes to payload\n        assert_eq!(\n            buf.len(),\n            header_length  // 9 bytes (header size + 8 serial types)\n                + size_of::<i8>()        // I8: 1 byte\n                + size_of::<i16>()        // I16: 2 bytes\n                + (size_of::<i32>() - 1)        // I24: 3 bytes\n                + size_of::<i32>()        // I32: 4 bytes\n                + (size_of::<i64>() - 2)        // I48: 6 bytes\n                + size_of::<i64>() // I64: 8 bytes\n        );\n    }\n\n    #[test]\n    fn test_serialize_const_integers() {\n        let record = Record::new(vec![Value::from_i64(0), Value::from_i64(1)]);\n        let mut buf = Vec::new();\n        record.serialize(&mut buf);\n\n        // [header_size, serial_type_0, serial_type_1] + no payload bytes\n        let expected_header_size = 3; // 1 byte for header size + 2 bytes for serial types\n\n        assert_eq!(buf.len(), expected_header_size);\n\n        // Check header size\n        assert_eq!(buf[0], expected_header_size as u8);\n\n        assert_eq!(buf[1] as u64, u64::from(SerialType::const_int0())); // Should be 8\n        assert_eq!(buf[2] as u64, u64::from(SerialType::const_int1())); // Should be 9\n\n        assert_eq!(buf[1], 8); // ConstInt0 serial type\n        assert_eq!(buf[2], 9); // ConstInt1 serial type\n    }\n\n    #[test]\n    fn test_serialize_single_const_int0() {\n        let record = Record::new(vec![Value::from_i64(0)]);\n        let mut buf = Vec::new();\n        record.serialize(&mut buf);\n\n        // Expected: [header_size=2, serial_type=8]\n        assert_eq!(buf.len(), 2);\n        assert_eq!(buf[0], 2); // Header size\n        assert_eq!(buf[1], 8); // ConstInt0 serial type\n    }\n\n    #[test]\n    fn test_serialize_float() {\n        #[warn(clippy::approx_constant)]\n        let record = Record::new(vec![Value::from_f64(3.15555)]);\n        let mut buf = Vec::new();\n        record.serialize(&mut buf);\n\n        let header_length = record.values.len() + 1;\n        let header = &buf[0..header_length];\n        assert_eq!(header[0], header_length as u8);\n        // Second byte should be serial type for FLOAT\n        assert_eq!(header[1] as u64, u64::from(SerialType::f64()));\n        // Check that the bytes after the header can be interpreted as the float\n        let float_bytes = &buf[header_length..header_length + size_of::<f64>()];\n        let float = f64::from_be_bytes(float_bytes.try_into().unwrap());\n        assert_eq!(float, 3.15555);\n        // Check that buffer length is correct\n        assert_eq!(buf.len(), header_length + size_of::<f64>());\n    }\n\n    #[test]\n    fn test_serialize_text() {\n        let text = \"hello\";\n        let record = Record::new(vec![Value::Text(Text::new(text))]);\n        let mut buf = Vec::new();\n        record.serialize(&mut buf);\n\n        let header_length = record.values.len() + 1;\n        let header = &buf[0..header_length];\n        // First byte should be header size\n        assert_eq!(header[0], header_length as u8);\n        // Second byte should be serial type for TEXT, which is (len * 2 + 13)\n        assert_eq!(header[1], (5 * 2 + 13) as u8);\n        // Check the actual text bytes\n        assert_eq!(&buf[2..7], b\"hello\");\n        // Check that buffer length is correct\n        assert_eq!(buf.len(), header_length + text.len());\n    }\n\n    #[test]\n    fn test_serialize_blob() {\n        let blob = vec![1, 2, 3, 4, 5];\n        let record = Record::new(vec![Value::Blob(blob.clone())]);\n        let mut buf = Vec::new();\n        record.serialize(&mut buf);\n\n        let header_length = record.values.len() + 1;\n        let header = &buf[0..header_length];\n        // First byte should be header size\n        assert_eq!(header[0], header_length as u8);\n        // Second byte should be serial type for BLOB, which is (len * 2 + 12)\n        assert_eq!(header[1], (5 * 2 + 12) as u8);\n        // Check the actual blob bytes\n        assert_eq!(&buf[2..7], &[1, 2, 3, 4, 5]);\n        // Check that buffer length is correct\n        assert_eq!(buf.len(), header_length + blob.len());\n    }\n\n    #[test]\n    fn test_serialize_mixed_types() {\n        let text = \"test\";\n        let record = Record::new(vec![\n            Value::Null,\n            Value::from_i64(42),\n            Value::from_f64(3.15),\n            Value::Text(Text::new(text)),\n        ]);\n        let mut buf = Vec::new();\n        record.serialize(&mut buf);\n\n        let header_length = record.values.len() + 1;\n        let header = &buf[0..header_length];\n        // First byte should be header size\n        assert_eq!(header[0], header_length as u8);\n        // Second byte should be serial type for NULL\n        assert_eq!(header[1] as u64, u64::from(SerialType::null()));\n        // Third byte should be serial type for I8\n        assert_eq!(header[2] as u64, u64::from(SerialType::i8()));\n        // Fourth byte should be serial type for F64\n        assert_eq!(header[3] as u64, u64::from(SerialType::f64()));\n        // Fifth byte should be serial type for TEXT, which is (len * 2 + 13)\n        assert_eq!(header[4] as u64, (4 * 2 + 13) as u64);\n\n        // Check that the bytes after the header can be interpreted as the correct values\n        let mut cur_offset = header_length;\n        let i8_bytes = &buf[cur_offset..cur_offset + size_of::<i8>()];\n        cur_offset += size_of::<i8>();\n        let f64_bytes = &buf[cur_offset..cur_offset + size_of::<f64>()];\n        cur_offset += size_of::<f64>();\n        let text_bytes = &buf[cur_offset..cur_offset + text.len()];\n\n        let val_int8 = i8::from_be_bytes(i8_bytes.try_into().unwrap());\n        let val_float = f64::from_be_bytes(f64_bytes.try_into().unwrap());\n        let val_text = String::from_utf8(text_bytes.to_vec()).unwrap();\n\n        assert_eq!(val_int8, 42);\n        assert_eq!(val_float, 3.15);\n        assert_eq!(val_text, \"test\");\n\n        // Check that buffer length is correct\n        assert_eq!(\n            buf.len(),\n            header_length + size_of::<i8>() + size_of::<f64>() + text.len()\n        );\n    }\n\n    /// Before the Numeric refactor, ValueRef had separate Float(f64) and Integer(i64)\n    /// variants. A raw f64::NAN could be stored in Float, and comparing two NaN floats\n    /// via partial_cmp returned None. The .unwrap() in Ord::cmp and\n    /// compare_immutable_single would then panic.\n    ///\n    /// Now Numeric::Float wraps NonNan, which rejects NaN at construction time.\n    /// This makes it impossible to represent NaN in a ValueRef, so partial_cmp\n    /// is total and can never return None for any representable value.\n    #[test]\n    fn test_valueref_partial_cmp_no_panic_on_nan() {\n        use crate::numeric::nonnan::NonNan;\n\n        // NonNan::new rejects NaN — this is the type-level guarantee that\n        // prevents the old panic. No ValueRef::Float(NAN) can be constructed.\n        assert!(NonNan::new(f64::NAN).is_none());\n\n        // from_f64(NAN) falls back to Null instead of storing a NaN float.\n        assert_eq!(ValueRef::from_f64(f64::NAN), ValueRef::Null);\n\n        // Exercise every representable float edge case through partial_cmp,\n        // Ord::cmp, and compare_immutable_single — none of these can panic now.\n        let values: Vec<ValueRef> = vec![\n            ValueRef::Null,\n            ValueRef::from_i64(0),\n            ValueRef::from_i64(-1),\n            ValueRef::from_i64(i64::MAX),\n            ValueRef::from_i64(i64::MIN),\n            ValueRef::from_f64(0.0),\n            ValueRef::from_f64(-0.0),\n            ValueRef::from_f64(1.5),\n            ValueRef::from_f64(-1.5),\n            ValueRef::from_f64(f64::MAX),\n            ValueRef::from_f64(f64::MIN),\n            ValueRef::from_f64(f64::MIN_POSITIVE),\n            ValueRef::from_f64(f64::INFINITY),\n            ValueRef::from_f64(f64::NEG_INFINITY),\n            ValueRef::from_f64(f64::NAN), // becomes Null\n            ValueRef::Text(TextRef::new(\"hello\", TextSubtype::Text)),\n            ValueRef::Text(TextRef::new(\"\", TextSubtype::Text)),\n            ValueRef::Blob(&[1, 2, 3]),\n            ValueRef::Blob(&[]),\n        ];\n\n        // partial_cmp must return Some for every pair — the old code panicked\n        // here when either side was Float(NAN).\n        for (i, a) in values.iter().enumerate() {\n            for (j, b) in values.iter().enumerate() {\n                let result = a.partial_cmp(b);\n                assert!(\n                    result.is_some(),\n                    \"partial_cmp returned None for values[{i}]={a:?} vs values[{j}]={b:?}\"\n                );\n                // Ord::cmp (which previously called partial_cmp().unwrap()) must agree.\n                assert_eq!(result.unwrap(), a.cmp(b));\n            }\n        }\n\n        // compare_immutable_single is where the unwrap panic originally surfaced.\n        for a in &values {\n            for b in &values {\n                let _ = compare_immutable_single(*a, *b, CollationSeq::Binary);\n            }\n        }\n\n        // Antisymmetry holds for all pairs.\n        for a in &values {\n            for b in &values {\n                let ab = a.cmp(b);\n                let ba = b.cmp(a);\n                assert_eq!(ab, ba.reverse(), \"antisymmetry failed for {a:?} vs {b:?}\");\n            }\n        }\n    }\n\n    #[test]\n    fn test_column_count_matches_values_written() {\n        // Test with different numbers of values\n        for num_values in 1..=10 {\n            let values: Vec<Value> = (0..num_values).map(|i| Value::from_i64(i as i64)).collect();\n\n            let record = ImmutableRecord::from_values(&values, values.len());\n            let cnt = record.column_count();\n            assert_eq!(\n                cnt, num_values,\n                \"column_count should be {num_values}, not {cnt}\"\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "core/util.rs",
    "content": "use crate::incremental::view::IncrementalView;\nuse crate::numeric::StrToF64;\nuse crate::schema::ColDef;\nuse crate::sync::Mutex;\nuse crate::translate::emitter::TransactionMode;\nuse crate::translate::expr::{walk_expr, walk_expr_mut, WalkControl};\nuse crate::translate::plan::{JoinedTable, TableReferences};\nuse crate::translate::planner::{parse_row_id, TableMask};\nuse crate::types::IOResult;\nuse crate::IO;\nuse crate::{\n    schema::{Column, Schema, Table, Type},\n    types::{Value, ValueType},\n    LimboError, OpenFlags, Result, Statement, SymbolTable,\n};\nuse either::Either;\nuse rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};\nuse std::future::Future;\nuse std::sync::Arc;\nuse tracing::{instrument, Level};\nuse turso_macros::match_ignore_ascii_case;\nuse turso_parser::ast::{self, CreateTableBody, Expr, Literal, UnaryOperator};\nuse turso_parser::parser::Parser;\n\n#[macro_export]\nmacro_rules! io_yield_one {\n    ($c:expr) => {\n        return Ok(IOResult::IO(IOCompletions::Single($c)));\n    };\n}\n\n#[macro_export]\nmacro_rules! eq_ignore_ascii_case {\n    ( $var:expr, $value:literal ) => {{\n        match_ignore_ascii_case!(match $var {\n            $value => true,\n            _ => false,\n        })\n    }};\n}\n\n#[macro_export]\nmacro_rules! contains_ignore_ascii_case {\n    ( $var:expr, $value:literal ) => {{\n        let compare_to_idx = $var.len().saturating_sub($value.len());\n        if $var.len() < $value.len() {\n            false\n        } else {\n            let mut result = false;\n            for i in 0..=compare_to_idx {\n                if eq_ignore_ascii_case!(&$var[i..i + $value.len()], $value) {\n                    result = true;\n                    break;\n                }\n            }\n\n            result\n        }\n    }};\n}\n\n#[macro_export]\nmacro_rules! starts_with_ignore_ascii_case {\n    ( $var:expr, $value:literal ) => {{\n        if $var.len() < $value.len() {\n            false\n        } else {\n            eq_ignore_ascii_case!(&$var[..$value.len()], $value)\n        }\n    }};\n}\n\n#[macro_export]\nmacro_rules! ends_with_ignore_ascii_case {\n    ( $var:expr, $value:literal ) => {{\n        if $var.len() < $value.len() {\n            false\n        } else {\n            eq_ignore_ascii_case!(&$var[$var.len() - $value.len()..], $value)\n        }\n    }};\n}\n\npub trait IOExt {\n    fn block<T>(&self, f: impl FnMut() -> Result<IOResult<T>>) -> Result<T>;\n    fn wait<T, F>(&self, f: F) -> impl Future<Output = Result<T>> + Send\n    where\n        F: FnMut() -> Result<IOResult<T>> + Send,\n        T: Send;\n}\n\nimpl<I: ?Sized + IO> IOExt for I {\n    fn block<T>(&self, mut f: impl FnMut() -> Result<IOResult<T>>) -> Result<T> {\n        Ok(loop {\n            match f()? {\n                IOResult::Done(v) => break v,\n                IOResult::IO(io) => io.wait(self)?,\n            }\n        })\n    }\n\n    async fn wait<T, F>(&self, mut f: F) -> Result<T>\n    where\n        F: FnMut() -> Result<IOResult<T>> + Send,\n        T: Send,\n    {\n        Ok(loop {\n            match f()? {\n                IOResult::Done(v) => break v,\n                IOResult::IO(io) => io.wait_async(self).await?,\n            }\n        })\n    }\n}\n\n// https://sqlite.org/lang_keywords.html\nconst QUOTE_PAIRS: &[(char, char)] = &[\n    ('\"', '\"'),\n    ('[', ']'),\n    ('`', '`'),\n    ('\\'', '\\''), // string sometimes used as identifier quoting\n];\n\npub fn normalize_ident(identifier: &str) -> String {\n    // quotes normalization already happened in the parser layer (see Name ast node implementation)\n    // so, we only need to convert identifier string to lowercase\n    identifier.to_lowercase()\n}\n\n/// Escape a SQL string literal payload for safe interpolation inside single quotes.\npub fn escape_sql_string_literal(literal: &str) -> String {\n    literal.replace('\\'', \"''\")\n}\n\npub const PRIMARY_KEY_AUTOMATIC_INDEX_NAME_PREFIX: &str = \"sqlite_autoindex_\";\n\n/// Unparsed index that comes from a sql query, i.e not an automatic index\n///\n/// CREATE INDEX idx ON table_name(sql)\npub struct UnparsedFromSqlIndex {\n    pub table_name: String,\n    pub root_page: i64,\n    pub sql: String,\n}\n\n#[instrument(skip_all, level = Level::INFO)]\npub fn parse_schema_rows(\n    mut rows: Statement,\n    schema: &mut Schema,\n    syms: &SymbolTable,\n    mv_tx: Option<(u64, TransactionMode)>,\n    _existing_views: HashMap<String, Arc<Mutex<IncrementalView>>>,\n) -> Result<()> {\n    rows.set_mv_tx(mv_tx);\n    let mv_store = rows.mv_store().clone();\n    // TODO: if we IO, this unparsed indexes is lost. Will probably need some state between\n    // IO runs\n    let mut from_sql_indexes = Vec::with_capacity(10);\n    let mut automatic_indices = HashMap::with_capacity_and_hasher(10, Default::default());\n\n    // Store DBSP state table root pages: view_name -> dbsp_state_root_page\n    let mut dbsp_state_roots: HashMap<String, i64> = HashMap::default();\n    // Store DBSP state table index root pages: view_name -> dbsp_state_index_root_page\n    let mut dbsp_state_index_roots: HashMap<String, i64> = HashMap::default();\n    // Store materialized view info (SQL and root page) for later creation\n    let mut materialized_view_info: HashMap<String, (String, i64)> = HashMap::default();\n\n    // TODO: How do we ensure that the I/O we submitted to\n    // read the schema is actually complete?\n    rows.run_with_row_callback(|row| {\n        let ty = row.get::<&str>(0)?;\n        let name = row.get::<&str>(1)?;\n        let table_name = row.get::<&str>(2)?;\n        let root_page = row.get::<i64>(3)?;\n        let sql = row.get::<&str>(4).ok();\n        schema.handle_schema_row(\n            ty,\n            name,\n            table_name,\n            root_page,\n            sql,\n            syms,\n            &mut from_sql_indexes,\n            &mut automatic_indices,\n            &mut dbsp_state_roots,\n            &mut dbsp_state_index_roots,\n            &mut materialized_view_info,\n        )\n    })?;\n\n    schema.populate_indices(\n        syms,\n        from_sql_indexes,\n        automatic_indices,\n        mv_store.is_some(),\n    )?;\n    schema.populate_materialized_views(\n        materialized_view_info,\n        dbsp_state_roots,\n        dbsp_state_index_roots,\n    )?;\n\n    Ok(())\n}\n\nfn cmp_numeric_strings(num_str: &str, other: &str) -> bool {\n    fn parse(s: &str) -> Option<Either<i64, f64>> {\n        if let Ok(i) = s.parse::<i64>() {\n            Some(Either::Left(i))\n        } else if let Ok(f) = s.parse::<f64>() {\n            Some(Either::Right(f))\n        } else {\n            None\n        }\n    }\n\n    match (parse(num_str), parse(other)) {\n        (Some(Either::Left(i1)), Some(Either::Left(i2))) => i1 == i2,\n        (Some(Either::Right(f1)), Some(Either::Right(f2))) => f1 == f2,\n        // Integer and Float are NOT equivalent even if values match,\n        // because result type of operations depends on operand types\n        (Some(Either::Left(_)), Some(Either::Right(_)))\n        | (Some(Either::Right(_)), Some(Either::Left(_))) => false,\n        _ => num_str == other,\n    }\n}\n\npub fn check_ident_equivalency(ident1: &str, ident2: &str) -> bool {\n    fn strip_quotes(identifier: &str) -> &str {\n        for &(start, end) in QUOTE_PAIRS {\n            if identifier.starts_with(start) && identifier.ends_with(end) {\n                return &identifier[1..identifier.len() - 1];\n            }\n        }\n        identifier\n    }\n    strip_quotes(ident1).eq_ignore_ascii_case(strip_quotes(ident2))\n}\n\npub fn module_name_from_sql(sql: &str) -> Result<&str> {\n    if let Some(start) = sql.find(\"USING\") {\n        let start = start + 6;\n        // stop at the first space, semicolon, or parenthesis\n        let end = sql[start..]\n            .find(|c: char| c.is_whitespace() || c == ';' || c == '(')\n            .unwrap_or(sql.len() - start)\n            + start;\n        Ok(sql[start..end].trim())\n    } else {\n        Err(LimboError::InvalidArgument(\n            \"Expected 'USING' in module name\".to_string(),\n        ))\n    }\n}\n\n// CREATE VIRTUAL TABLE table_name USING module_name(arg1, arg2, ...);\n// CREATE VIRTUAL TABLE table_name USING module_name;\npub fn module_args_from_sql(sql: &str) -> Result<Vec<turso_ext::Value>> {\n    if !sql.contains('(') {\n        return Ok(vec![]);\n    }\n    let start = sql.find('(').ok_or_else(|| {\n        LimboError::InvalidArgument(\"Expected '(' in module argument list\".to_string())\n    })? + 1;\n    let end = sql.rfind(')').ok_or_else(|| {\n        LimboError::InvalidArgument(\"Expected ')' in module argument list\".to_string())\n    })?;\n\n    let mut args = Vec::new();\n    let mut current_arg = String::new();\n    let mut chars = sql[start..end].chars().peekable();\n    let mut in_quotes = false;\n\n    while let Some(c) = chars.next() {\n        match c {\n            '\\'' => {\n                if in_quotes {\n                    if chars.peek() == Some(&'\\'') {\n                        // Escaped quote\n                        current_arg.push('\\'');\n                        chars.next();\n                    } else {\n                        in_quotes = false;\n                        args.push(turso_ext::Value::from_text(current_arg.trim().to_string()));\n                        current_arg.clear();\n                        // Skip until comma or end\n                        while let Some(&nc) = chars.peek() {\n                            if nc == ',' {\n                                chars.next(); // Consume comma\n                                break;\n                            } else if nc.is_whitespace() {\n                                chars.next();\n                            } else {\n                                return Err(LimboError::InvalidArgument(\n                                    \"Unexpected characters after quoted argument\".to_string(),\n                                ));\n                            }\n                        }\n                    }\n                } else {\n                    in_quotes = true;\n                }\n            }\n            ',' => {\n                if !in_quotes {\n                    if !current_arg.trim().is_empty() {\n                        args.push(turso_ext::Value::from_text(current_arg.trim().to_string()));\n                        current_arg.clear();\n                    }\n                } else {\n                    current_arg.push(c);\n                }\n            }\n            _ => {\n                current_arg.push(c);\n            }\n        }\n    }\n\n    if !current_arg.trim().is_empty() && !in_quotes {\n        args.push(turso_ext::Value::from_text(current_arg.trim().to_string()));\n    }\n\n    if in_quotes {\n        return Err(LimboError::InvalidArgument(\n            \"Unterminated string literal in module arguments\".to_string(),\n        ));\n    }\n\n    Ok(args)\n}\n\npub fn check_literal_equivalency(lhs: &Literal, rhs: &Literal) -> bool {\n    match (lhs, rhs) {\n        (Literal::Numeric(n1), Literal::Numeric(n2)) => cmp_numeric_strings(n1, n2),\n        (Literal::String(s1), Literal::String(s2)) => s1 == s2,\n        (Literal::Blob(b1), Literal::Blob(b2)) => b1 == b2,\n        (Literal::Keyword(k1), Literal::Keyword(k2)) => check_ident_equivalency(k1, k2),\n        (Literal::Null, Literal::Null) => true,\n        (Literal::CurrentDate, Literal::CurrentDate) => true,\n        (Literal::CurrentTime, Literal::CurrentTime) => true,\n        (Literal::CurrentTimestamp, Literal::CurrentTimestamp) => true,\n        _ => false,\n    }\n}\n\n/// Returns true if every Column/RowId table reference in `expr` is contained\n/// in `allowed`. Constants (no table refs) pass.\npub(crate) fn expr_tables_subset_of(\n    expr: &Expr,\n    table_references: &TableReferences,\n    allowed: &TableMask,\n) -> bool {\n    let mut ok = true;\n    let _ = walk_expr(expr, &mut |e: &Expr| -> Result<WalkControl> {\n        match e {\n            Expr::Column { table, .. } | Expr::RowId { table, .. } => {\n                if let Some(idx) = table_references\n                    .joined_tables()\n                    .iter()\n                    .position(|t| t.internal_id == *table)\n                {\n                    if !allowed.contains_table(idx) {\n                        ok = false;\n                        return Ok(WalkControl::SkipChildren);\n                    }\n                }\n                // Outer query references are already in scope — allow them.\n            }\n            _ => {}\n        }\n        Ok(WalkControl::Continue)\n    });\n    ok\n}\n\n/// bind AST identifiers to either Column or Rowid if possible\npub fn simple_bind_expr(\n    joined_table: &JoinedTable,\n    result_columns: &[ast::ResultColumn],\n    expr: &mut ast::Expr,\n) -> Result<()> {\n    let internal_id = joined_table.internal_id;\n    walk_expr_mut(expr, &mut |expr: &mut ast::Expr| -> Result<WalkControl> {\n        #[allow(clippy::single_match)]\n        match expr {\n            Expr::Id(id) => {\n                let normalized_id = normalize_ident(id.as_str());\n\n                for result_column in result_columns.iter() {\n                    if let ast::ResultColumn::Expr(result, Some(ast::As::As(alias))) = result_column\n                    {\n                        if alias.as_str().eq_ignore_ascii_case(&normalized_id) {\n                            *expr = *result.clone();\n                            return Ok(WalkControl::Continue);\n                        }\n                    }\n                }\n                let col_idx = joined_table.columns().iter().position(|c| {\n                    c.name\n                        .as_ref()\n                        .is_some_and(|name| name.eq_ignore_ascii_case(&normalized_id))\n                });\n                if let Some(col_idx) = col_idx {\n                    let col = joined_table.table.columns().get(col_idx).unwrap();\n                    *expr = ast::Expr::Column {\n                        database: None,\n                        table: internal_id,\n                        column: col_idx,\n                        is_rowid_alias: col.is_rowid_alias(),\n                    };\n                } else {\n                    // only if we haven't found a match, check for explicit rowid reference\n                    let is_btree_table = matches!(joined_table.table, Table::BTree(_));\n                    if is_btree_table {\n                        if let Some(rowid) = parse_row_id(&normalized_id, internal_id, || false)? {\n                            *expr = rowid;\n                        }\n                    }\n                }\n            }\n            _ => {}\n        }\n        Ok(WalkControl::Continue)\n    })?;\n    Ok(())\n}\n\npub fn try_substitute_parameters(\n    pattern: &Expr,\n    parameters: &HashMap<i32, Expr>,\n) -> Option<Box<Expr>> {\n    match pattern {\n        Expr::FunctionCall {\n            name,\n            distinctness,\n            args,\n            order_by,\n            filter_over,\n        } => {\n            let mut substituted = Vec::new();\n            for arg in args {\n                substituted.push(try_substitute_parameters(arg, parameters)?);\n            }\n            Some(Box::new(Expr::FunctionCall {\n                args: substituted,\n                distinctness: *distinctness,\n                name: name.clone(),\n                order_by: order_by.clone(),\n                filter_over: filter_over.clone(),\n            }))\n        }\n        Expr::Variable(var) => {\n            if var.name.is_some() {\n                return None;\n            }\n            let Ok(var) = i32::try_from(var.index.get()) else {\n                return None;\n            };\n            Some(Box::new(parameters.get(&var)?.clone()))\n        }\n        _ => Some(Box::new(pattern.clone())),\n    }\n}\n\npub fn try_capture_parameters(pattern: &Expr, query: &Expr) -> Option<HashMap<i32, Expr>> {\n    let mut captured = HashMap::default();\n    match (pattern, query) {\n        (\n            Expr::FunctionCall {\n                name: name1,\n                distinctness: distinct1,\n                args: args1,\n                order_by: order1,\n                filter_over: filter1,\n            },\n            Expr::FunctionCall {\n                name: name2,\n                distinctness: distinct2,\n                args: args2,\n                order_by: order2,\n                filter_over: filter2,\n            },\n        ) => {\n            if !name1.as_str().eq_ignore_ascii_case(name2.as_str()) {\n                return None;\n            }\n            if distinct1.is_some() || distinct2.is_some() {\n                return None;\n            }\n            if !order1.is_empty() || !order2.is_empty() {\n                return None;\n            }\n            if filter1.filter_clause.is_some() || filter1.over_clause.is_some() {\n                return None;\n            }\n            if filter2.filter_clause.is_some() || filter2.over_clause.is_some() {\n                return None;\n            }\n            for (arg1, arg2) in args1.iter().zip(args2.iter()) {\n                let result = try_capture_parameters(arg1, arg2)?;\n                captured.extend(result);\n            }\n            Some(captured)\n        }\n        (Expr::Variable(var), expr) => {\n            if var.name.is_some() {\n                return None;\n            }\n            let Ok(var) = i32::try_from(var.index.get()) else {\n                return None;\n            };\n            captured.insert(var, expr.clone());\n            Some(captured)\n        }\n        (\n            Expr::Id(_) | Expr::Name(_) | Expr::Column { .. },\n            Expr::Id(_) | Expr::Name(_) | Expr::Column { .. },\n        ) => {\n            if pattern == query {\n                Some(captured)\n            } else {\n                None\n            }\n        }\n        (_, _) => None,\n    }\n}\n\n/// Returns the number of column arguments for FTS functions.\n/// FTS functions have column arguments followed by non-column arguments:\n/// - fts_match(col1, col2, ..., query_string) -> columns = args.len() - 1\n/// - fts_score(col1, col2, ..., query_string) -> columns = args.len() - 1\n/// - fts_highlight(col1, col2, ..., before_tag, after_tag, query_string) -> columns = args.len() - 3\n///\n/// Returns 0 for non-FTS functions.\n/// Specific for FTS but cannot gate behind feature = \"fts\" so it must\n/// live in util.rs :/\npub fn count_fts_column_args(expr: &Expr) -> usize {\n    match expr {\n        Expr::FunctionCall { name, args, .. } => {\n            let name_lower = name.as_str().to_lowercase();\n            match name_lower.as_str() {\n                \"fts_match\" | \"fts_score\" => args.len().saturating_sub(1),\n                \"fts_highlight\" => args.len().saturating_sub(3),\n                _ => 0,\n            }\n        }\n        _ => 0,\n    }\n}\n\n/// Match FTS function calls where column arguments can appear in any order.\n///\n/// FTS functions like `fts_match(col1, col2, 'query')` should match\n/// `fts_match(col2, col1, 'query')` as long as the same columns are used.\n///\n/// Semi-specific for FTS but cannot gate behind feature = \"fts\" so it must\n/// live in util.rs :/\npub fn try_capture_parameters_column_agnostic(\n    pattern: &Expr,         // pattern expression from index definition\n    query: &Expr,           // the actual query expression\n    num_column_args: usize, // number of leading column arguments\n) -> Option<HashMap<i32, Expr>> {\n    // If not a function call or no column args, fall back to standard matching\n    if num_column_args == 0 {\n        return try_capture_parameters(pattern, query);\n    }\n\n    let (\n        Expr::FunctionCall {\n            name: pattern_name,\n            distinctness: pattern_distinct,\n            args: pattern_args,\n            order_by: pattern_order,\n            filter_over: pattern_filter,\n        },\n        Expr::FunctionCall {\n            name: query_name,\n            distinctness: query_distinct,\n            args: query_args,\n            order_by: query_order,\n            filter_over: query_filter,\n        },\n    ) = (pattern, query)\n    else {\n        return try_capture_parameters(pattern, query);\n    };\n    // Function names must match\n    if !pattern_name\n        .as_str()\n        .eq_ignore_ascii_case(query_name.as_str())\n    {\n        return None;\n    }\n\n    // Argument counts must match\n    if pattern_args.len() != query_args.len() {\n        return None;\n    }\n    // Distinctness must match (we don't support it)\n    if pattern_distinct.is_some() || query_distinct.is_some() {\n        return None;\n    }\n    // ORDER BY within function not supported\n    if !pattern_order.is_empty() || !query_order.is_empty() {\n        return None;\n    }\n\n    // Filter/over clause not supported\n    if pattern_filter.filter_clause.is_some() || pattern_filter.over_clause.is_some() {\n        return None;\n    }\n    if query_filter.filter_clause.is_some() || query_filter.over_clause.is_some() {\n        return None;\n    }\n\n    let mut captured = HashMap::default();\n\n    // Split args into column args (reorderable) and remaining args (positional)\n    let pattern_col_args = &pattern_args[..num_column_args];\n    let query_col_args = &query_args[..num_column_args];\n    let pattern_rest = &pattern_args[num_column_args..];\n    let query_rest = &query_args[num_column_args..];\n\n    // For column arguments: check that the same set of columns is used (order-independent)\n    // We use a greedy matching approach: for each query column, find a matching pattern column\n    let mut matched_pattern_indices: HashSet<usize> = HashSet::default();\n\n    for query_col in query_col_args {\n        let mut found_match = false;\n        for (i, pattern_col) in pattern_col_args.iter().enumerate() {\n            if matched_pattern_indices.contains(&i) {\n                continue;\n            }\n            if exprs_are_equivalent(pattern_col, query_col) {\n                matched_pattern_indices.insert(i);\n                found_match = true;\n                break;\n            }\n        }\n        if !found_match {\n            return None;\n        }\n    }\n    // All pattern columns must be matched\n    if matched_pattern_indices.len() != pattern_col_args.len() {\n        return None;\n    }\n    // Remaining args must match positionally (includes the query string parameter)\n    for (pattern_arg, query_arg) in pattern_rest.iter().zip(query_rest.iter()) {\n        let result = try_capture_parameters(pattern_arg, query_arg)?;\n        captured.extend(result);\n    }\n\n    Some(captured)\n}\n\n/// This function is used to determine whether two expressions are logically\n/// equivalent in the context of queries, even if their representations\n/// differ. e.g.: `SUM(x)` and `sum(x)`, `x + y` and `y + x`\npub fn exprs_are_equivalent(expr1: &Expr, expr2: &Expr) -> bool {\n    match (expr1, expr2) {\n        (\n            Expr::Between {\n                lhs: lhs1,\n                not: not1,\n                start: start1,\n                end: end1,\n            },\n            Expr::Between {\n                lhs: lhs2,\n                not: not2,\n                start: start2,\n                end: end2,\n            },\n        ) => {\n            not1 == not2\n                && exprs_are_equivalent(lhs1, lhs2)\n                && exprs_are_equivalent(start1, start2)\n                && exprs_are_equivalent(end1, end2)\n        }\n        (Expr::Binary(lhs1, op1, rhs1), Expr::Binary(lhs2, op2, rhs2)) => {\n            op1 == op2\n                && ((exprs_are_equivalent(lhs1, lhs2) && exprs_are_equivalent(rhs1, rhs2))\n                    || (op1.is_commutative()\n                        && exprs_are_equivalent(lhs1, rhs2)\n                        && exprs_are_equivalent(rhs1, lhs2)))\n        }\n        (\n            Expr::Case {\n                base: base1,\n                when_then_pairs: pairs1,\n                else_expr: else1,\n            },\n            Expr::Case {\n                base: base2,\n                when_then_pairs: pairs2,\n                else_expr: else2,\n            },\n        ) => {\n            base1 == base2\n                && pairs1.len() == pairs2.len()\n                && pairs1.iter().zip(pairs2).all(|((w1, t1), (w2, t2))| {\n                    exprs_are_equivalent(w1, w2) && exprs_are_equivalent(t1, t2)\n                })\n                && else1 == else2\n        }\n        (\n            Expr::Cast {\n                expr: expr1,\n                type_name: type1,\n            },\n            Expr::Cast {\n                expr: expr2,\n                type_name: type2,\n            },\n        ) => {\n            exprs_are_equivalent(expr1, expr2)\n                && match (type1, type2) {\n                    (Some(t1), Some(t2)) => t1.name.eq_ignore_ascii_case(&t2.name),\n                    _ => false,\n                }\n        }\n        (Expr::Collate(expr1, collation1), Expr::Collate(expr2, collation2)) => {\n            // TODO: check correctness of comparing colation as strings\n            exprs_are_equivalent(expr1, expr2)\n                && collation1\n                    .as_str()\n                    .eq_ignore_ascii_case(collation2.as_str())\n        }\n        (\n            Expr::FunctionCall {\n                name: name1,\n                distinctness: distinct1,\n                args: args1,\n                order_by: order1,\n                filter_over: filter1,\n            },\n            Expr::FunctionCall {\n                name: name2,\n                distinctness: distinct2,\n                args: args2,\n                order_by: order2,\n                filter_over: filter2,\n            },\n        ) => {\n            name1.as_str().eq_ignore_ascii_case(name2.as_str())\n                && distinct1 == distinct2\n                && args1 == args2\n                && order1 == order2\n                && filter1 == filter2\n        }\n        (\n            Expr::FunctionCallStar {\n                name: name1,\n                filter_over: filter1,\n            },\n            Expr::FunctionCallStar {\n                name: name2,\n                filter_over: filter2,\n            },\n        ) => {\n            name1.as_str().eq_ignore_ascii_case(name2.as_str())\n                && match (&filter1.filter_clause, &filter2.filter_clause) {\n                    (Some(expr1), Some(expr2)) => exprs_are_equivalent(expr1, expr2),\n                    (None, None) => true,\n                    _ => false,\n                }\n                && filter1.over_clause == filter2.over_clause\n        }\n        (Expr::NotNull(expr1), Expr::NotNull(expr2)) => exprs_are_equivalent(expr1, expr2),\n        (Expr::IsNull(expr1), Expr::IsNull(expr2)) => exprs_are_equivalent(expr1, expr2),\n        (Expr::Literal(lit1), Expr::Literal(lit2)) => check_literal_equivalency(lit1, lit2),\n        (Expr::Id(id1), Expr::Id(id2)) => check_ident_equivalency(id1.as_str(), id2.as_str()),\n        (Expr::Unary(op1, expr1), Expr::Unary(op2, expr2)) => {\n            op1 == op2 && exprs_are_equivalent(expr1, expr2)\n        }\n        (Expr::Variable(val), Expr::Variable(val2)) => val == val2,\n        (Expr::Parenthesized(exprs1), Expr::Parenthesized(exprs2)) => {\n            exprs1.len() == exprs2.len()\n                && exprs1\n                    .iter()\n                    .zip(exprs2)\n                    .all(|(e1, e2)| exprs_are_equivalent(e1, e2))\n        }\n        (Expr::Parenthesized(exprs1), exprs2) | (exprs2, Expr::Parenthesized(exprs1)) => {\n            exprs1.len() == 1 && exprs_are_equivalent(&exprs1[0], exprs2)\n        }\n        (Expr::Qualified(tn1, cn1), Expr::Qualified(tn2, cn2)) => {\n            check_ident_equivalency(tn1.as_str(), tn2.as_str())\n                && check_ident_equivalency(cn1.as_str(), cn2.as_str())\n        }\n        (Expr::DoublyQualified(sn1, tn1, cn1), Expr::DoublyQualified(sn2, tn2, cn2)) => {\n            check_ident_equivalency(sn1.as_str(), sn2.as_str())\n                && check_ident_equivalency(tn1.as_str(), tn2.as_str())\n                && check_ident_equivalency(cn1.as_str(), cn2.as_str())\n        }\n        (\n            Expr::InList {\n                lhs: lhs1,\n                not: not1,\n                rhs: rhs1,\n            },\n            Expr::InList {\n                lhs: lhs2,\n                not: not2,\n                rhs: rhs2,\n            },\n        ) => {\n            *not1 == *not2\n                && exprs_are_equivalent(lhs1, lhs2)\n                && rhs1.len() == rhs2.len()\n                && rhs1\n                    .iter()\n                    .zip(rhs2.iter())\n                    .all(|(a, b)| exprs_are_equivalent(a, b))\n        }\n        (\n            Expr::Column {\n                database: db1,\n                is_rowid_alias: r1,\n                table: tbl_1,\n                column: col_1,\n            },\n            Expr::Column {\n                database: db2,\n                is_rowid_alias: r2,\n                table: tbl_2,\n                column: col_2,\n            },\n        ) => tbl_1 == tbl_2 && col_1 == col_2 && db1 == db2 && r1 == r2,\n        // fall back to naive equality check\n        _ => expr1 == expr2,\n    }\n}\n\n/// \"evaluate\" an expression to determine if it contains a poisonous NULL\n/// which will propagate through most expressions and result in it's evaluation\n/// into NULL. This is used to prevent things like the following:\n/// `ALTER TABLE t ADD COLUMN (a NOT NULL DEFAULT (NULL + 5)`\npub(crate) fn expr_contains_null(expr: &ast::Expr) -> bool {\n    let mut contains_null = false;\n    let _ = walk_expr(expr, &mut |expr: &ast::Expr| -> Result<WalkControl> {\n        if let ast::Expr::Literal(ast::Literal::Null) = expr {\n            contains_null = true;\n            return Ok(WalkControl::SkipChildren);\n        }\n        Ok(WalkControl::Continue)\n    }); // infallible\n    contains_null\n}\n\n// this function returns the affinity type and whether the type name was exactly \"INTEGER\"\n// https://www.sqlite.org/datatype3.html\npub(crate) fn type_from_name(type_name: &str) -> (Type, bool) {\n    let type_name = type_name.as_bytes();\n    if type_name.is_empty() {\n        return (Type::Blob, false);\n    }\n\n    if eq_ignore_ascii_case!(type_name, b\"INTEGER\") {\n        return (Type::Integer, true);\n    }\n\n    if contains_ignore_ascii_case!(type_name, b\"INT\") {\n        return (Type::Integer, false);\n    }\n\n    if let Some(ty) = type_name.windows(4).find_map(|s| {\n        match_ignore_ascii_case!(match s {\n            b\"CHAR\" | b\"CLOB\" | b\"TEXT\" => Some(Type::Text),\n            b\"BLOB\" => Some(Type::Blob),\n            b\"REAL\" | b\"FLOA\" | b\"DOUB\" => Some(Type::Real),\n            _ => None,\n        })\n    }) {\n        return (ty, false);\n    }\n\n    (Type::Numeric, false)\n}\n\npub fn columns_from_create_table_body(\n    body: &turso_parser::ast::CreateTableBody,\n) -> crate::Result<Vec<Column>> {\n    let CreateTableBody::ColumnsAndConstraints { columns, .. } = body else {\n        return Err(crate::LimboError::ParseError(\n            \"CREATE TABLE body must contain columns and constraints\".to_string(),\n        ));\n    };\n\n    columns\n        .iter()\n        .map(Column::try_from)\n        .collect::<crate::Result<Vec<Column>>>()\n}\n\n#[derive(Debug, Default, PartialEq)]\npub struct OpenOptions<'a> {\n    /// The authority component of the URI. may be 'localhost' or empty\n    pub authority: Option<&'a str>,\n    /// The normalized path to the database file\n    pub path: String,\n    /// The vfs query parameter causes the database connection to be opened using the VFS called NAME\n    pub vfs: Option<String>,\n    /// read-only, read-write, read-write and created if it does not exist, or pure in-memory database that never interacts with disk\n    pub mode: OpenMode,\n    /// Attempt to set the permissions of the new database file to match the existing file \"filename\".\n    pub modeof: Option<String>,\n    /// Specifies Cache mode shared | private\n    pub cache: CacheMode,\n    /// immutable=1|0 specifies that the database is stored on read-only media\n    pub immutable: bool,\n    // The encryption cipher\n    pub cipher: Option<String>,\n    // The encryption key in hex format\n    pub hexkey: Option<String>,\n}\n\npub const MEMORY_PATH: &str = \":memory:\";\n\n#[derive(Clone, Default, Debug, Copy, PartialEq)]\npub enum OpenMode {\n    ReadOnly,\n    ReadWrite,\n    Memory,\n    #[default]\n    ReadWriteCreate,\n}\n\n#[derive(Debug, Default, Clone, Copy, PartialEq)]\npub enum CacheMode {\n    #[default]\n    Private,\n    Shared,\n}\n\nimpl From<&str> for CacheMode {\n    fn from(s: &str) -> Self {\n        match s {\n            \"private\" => CacheMode::Private,\n            \"shared\" => CacheMode::Shared,\n            _ => CacheMode::Private,\n        }\n    }\n}\n\nimpl OpenMode {\n    pub fn from_str(s: &str) -> Result<Self> {\n        let s_bytes = s.trim().as_bytes();\n        match_ignore_ascii_case!(match s_bytes {\n            b\"ro\" => Ok(OpenMode::ReadOnly),\n            b\"rw\" => Ok(OpenMode::ReadWrite),\n            b\"memory\" => Ok(OpenMode::Memory),\n            b\"rwc\" => Ok(OpenMode::ReadWriteCreate),\n            _ => Err(LimboError::InvalidArgument(format!(\n                \"Invalid mode: '{s}'. Expected one of 'ro', 'rw', 'memory', 'rwc'\"\n            ))),\n        })\n    }\n}\n\nfn is_windows_path(path: &str) -> bool {\n    path.len() >= 3\n        && path.chars().nth(1) == Some(':')\n        && (path.chars().nth(2) == Some('/') || path.chars().nth(2) == Some('\\\\'))\n}\n\n/// converts windows-style paths to forward slashes, per SQLite spec.\nfn normalize_windows_path(path: &str) -> String {\n    let mut normalized = path.replace(\"\\\\\", \"/\");\n\n    // remove duplicate slashes (`//` → `/`)\n    while normalized.contains(\"//\") {\n        normalized = normalized.replace(\"//\", \"/\");\n    }\n\n    // if absolute windows path (`C:/...`), ensure it starts with `/`\n    if normalized.len() >= 3\n        && !normalized.starts_with('/')\n        && normalized.chars().nth(1) == Some(':')\n        && normalized.chars().nth(2) == Some('/')\n    {\n        normalized.insert(0, '/');\n    }\n    normalized\n}\n\nimpl<'a> OpenOptions<'a> {\n    /// Parses a SQLite URI, handling Windows and Unix paths separately.\n    pub fn parse(uri: &'a str) -> Result<OpenOptions<'a>> {\n        if !uri.starts_with(\"file:\") {\n            return Ok(OpenOptions {\n                path: uri.to_string(),\n                ..Default::default()\n            });\n        }\n\n        let mut opts = OpenOptions::default();\n        let without_scheme = &uri[5..];\n\n        let (without_fragment, _) = without_scheme\n            .split_once('#')\n            .unwrap_or((without_scheme, \"\"));\n\n        let (without_query, query) = without_fragment\n            .split_once('?')\n            .unwrap_or((without_fragment, \"\"));\n        parse_query_params(query, &mut opts)?;\n\n        // handle authority + path separately\n        if let Some(after_slashes) = without_query.strip_prefix(\"//\") {\n            let (authority, path) = after_slashes.split_once('/').unwrap_or((after_slashes, \"\"));\n\n            // sqlite allows only `localhost` or empty authority.\n            if !(authority.is_empty() || authority == \"localhost\") {\n                return Err(LimboError::InvalidArgument(format!(\n                    \"Invalid authority '{authority}'. Only '' or 'localhost' allowed.\"\n                )));\n            }\n            opts.authority = if authority.is_empty() {\n                None\n            } else {\n                Some(authority)\n            };\n\n            if is_windows_path(path) {\n                opts.path = normalize_windows_path(&decode_percent(path));\n            } else if !path.is_empty() {\n                opts.path = format!(\"/{}\", decode_percent(path));\n            } else {\n                opts.path = String::new();\n            }\n        } else {\n            // no authority, must be a normal absolute or relative path.\n            opts.path = decode_percent(without_query);\n        }\n\n        Ok(opts)\n    }\n\n    pub fn get_flags(&self) -> Result<OpenFlags> {\n        // Only use modeof if we're in a mode that can create files\n        if self.mode != OpenMode::ReadWriteCreate && self.modeof.is_some() {\n            return Err(LimboError::InvalidArgument(\n                \"modeof is not applicable without mode=rwc\".to_string(),\n            ));\n        }\n        // If modeof is not applicable or file doesn't exist, use default flags\n        Ok(match self.mode {\n            OpenMode::ReadWriteCreate => OpenFlags::Create,\n            OpenMode::ReadOnly => OpenFlags::ReadOnly,\n            _ => OpenFlags::default(),\n        })\n    }\n}\n\n// parses query parameters and updates OpenOptions\nfn parse_query_params(query: &str, opts: &mut OpenOptions) -> Result<()> {\n    for param in query.split('&') {\n        if let Some((key, value)) = param.split_once('=') {\n            let decoded_value = decode_percent(value);\n            match key {\n                \"mode\" => opts.mode = OpenMode::from_str(value)?,\n                \"modeof\" => opts.modeof = Some(decoded_value),\n                \"cache\" => opts.cache = decoded_value.as_str().into(),\n                \"immutable\" => opts.immutable = decoded_value == \"1\",\n                \"vfs\" => opts.vfs = Some(decoded_value),\n                \"cipher\" => opts.cipher = Some(decoded_value),\n                \"hexkey\" => opts.hexkey = Some(decoded_value),\n                _ => {}\n            }\n        }\n    }\n    Ok(())\n}\n\n/// Decodes percent-encoded characters\n/// this function was adapted from the 'urlencoding' crate. MIT\npub fn decode_percent(uri: &str) -> String {\n    let from_hex_digit = |digit: u8| -> Option<u8> {\n        match digit {\n            b'0'..=b'9' => Some(digit - b'0'),\n            b'A'..=b'F' => Some(digit - b'A' + 10),\n            b'a'..=b'f' => Some(digit - b'a' + 10),\n            _ => None,\n        }\n    };\n\n    let offset = uri.chars().take_while(|&c| c != '%').count();\n\n    if offset >= uri.len() {\n        return uri.to_string();\n    }\n\n    let mut decoded: Vec<u8> = Vec::with_capacity(uri.len());\n    let (ascii, mut data) = uri.as_bytes().split_at(offset);\n    decoded.extend_from_slice(ascii);\n\n    loop {\n        let mut parts = data.splitn(2, |&c| c == b'%');\n        let non_escaped_part = parts.next().unwrap();\n        let rest = parts.next();\n        if rest.is_none() && decoded.is_empty() {\n            return String::from_utf8_lossy(data).to_string();\n        }\n        decoded.extend_from_slice(non_escaped_part);\n        match rest {\n            Some(rest) => match rest.get(0..2) {\n                Some([first, second]) => match from_hex_digit(*first) {\n                    Some(first_val) => match from_hex_digit(*second) {\n                        Some(second_val) => {\n                            decoded.push((first_val << 4) | second_val);\n                            data = &rest[2..];\n                        }\n                        None => {\n                            decoded.extend_from_slice(&[b'%', *first]);\n                            data = &rest[1..];\n                        }\n                    },\n                    None => {\n                        decoded.push(b'%');\n                        data = rest;\n                    }\n                },\n                _ => {\n                    decoded.push(b'%');\n                    decoded.extend_from_slice(rest);\n                    break;\n                }\n            },\n            None => break,\n        }\n    }\n    String::from_utf8_lossy(&decoded).to_string()\n}\n\npub fn trim_ascii_whitespace(s: &str) -> &str {\n    let bytes = s.as_bytes();\n    let start = bytes\n        .iter()\n        .position(|&b| !b.is_ascii_whitespace())\n        .unwrap_or(bytes.len());\n    let end = bytes\n        .iter()\n        .rposition(|&b| !b.is_ascii_whitespace())\n        .map(|i| i + 1)\n        .unwrap_or(0);\n    if start <= end {\n        &s[start..end]\n    } else {\n        \"\"\n    }\n}\n\n/// NUMERIC Casting a TEXT or BLOB value into NUMERIC yields either an INTEGER or a REAL result.\n/// If the input text looks like an integer (there is no decimal point nor exponent) and the value\n/// is small enough to fit in a 64-bit signed integer, then the result will be INTEGER.\n/// Input text that looks like floating point (there is a decimal point and/or an exponent)\n/// and the text describes a value that can be losslessly converted back and forth between IEEE 754\n/// 64-bit float and a 51-bit signed integer, then the result is INTEGER. (In the previous sentence,\n/// a 51-bit integer is specified since that is one bit less than the length of the mantissa of an\n/// IEEE 754 64-bit float and thus provides a 1-bit of margin for the text-to-float conversion operation.)\n/// Any text input that describes a value outside the range of a 64-bit signed integer yields a REAL result.\n/// Casting a REAL or INTEGER value to NUMERIC is a no-op, even if a real value could be losslessly converted to an integer.\n///\n/// `lossless`: If `true`, rejects the input if any characters remain after the numeric prefix (strict / exact conversion).\npub fn checked_cast_text_to_numeric(text: &str, lossless: bool) -> std::result::Result<Value, ()> {\n    // sqlite will parse the first N digits of a string to numeric value, then determine\n    // whether _that_ value is more likely a real or integer value. e.g.\n    // '-100234-2344.23e14' evaluates to -100234 instead of -100234.0\n    let original_len = text.trim().len();\n    let (kind, text) = parse_numeric_str(text)?;\n\n    if original_len != text.len() && lossless {\n        return Err(());\n    }\n\n    match kind {\n        ValueType::Integer => match text.parse::<i64>() {\n            Ok(i) => Ok(Value::from_i64(i)),\n            Err(e) => {\n                if matches!(\n                    e.kind(),\n                    std::num::IntErrorKind::PosOverflow | std::num::IntErrorKind::NegOverflow\n                ) {\n                    // if overflow, we return the representation as a real.\n                    // we have to match sqlite exactly here, so we match sqlite3AtoF\n                    let value = text.parse::<f64>().unwrap_or_default();\n                    let factor = 10f64.powi(15 - value.abs().log10().ceil() as i32);\n                    Ok(Value::from_f64((value * factor).round() / factor))\n                } else {\n                    Err(())\n                }\n            }\n        },\n        ValueType::Float => Ok(text\n            .parse::<f64>()\n            .map_or(Value::from_f64(0.0), Value::from_f64)),\n        _ => unreachable!(),\n    }\n}\n\nfn parse_numeric_str(text: &str) -> Result<(ValueType, &str), ()> {\n    let text = text.trim();\n    let bytes = text.as_bytes();\n\n    if matches!(\n        bytes,\n        [] | [b'e', ..] | [b'E', ..] | [b'.', b'e' | b'E', ..]\n    ) {\n        return Err(());\n    }\n\n    let mut end = 0;\n    let mut has_decimal = false;\n    let mut has_exponent = false;\n    if bytes[0] == b'-' || bytes[0] == b'+' {\n        end = 1;\n    }\n    while end < bytes.len() {\n        match bytes[end] {\n            b'0'..=b'9' => end += 1,\n            b'.' if !has_decimal && !has_exponent => {\n                has_decimal = true;\n                end += 1;\n            }\n            b'e' | b'E' if !has_exponent => {\n                has_exponent = true;\n                end += 1;\n                // allow exponent sign\n                if end < bytes.len() && (bytes[end] == b'+' || bytes[end] == b'-') {\n                    end += 1;\n                }\n            }\n            _ => break,\n        }\n    }\n    if end == 0 || (end == 1 && (bytes[0] == b'-' || bytes[0] == b'+')) {\n        return Err(());\n    }\n    // edge case: if it ends with exponent, strip and cast valid digits as float\n    let last = bytes[end - 1];\n    if last.eq_ignore_ascii_case(&b'e') {\n        return Ok((ValueType::Float, &text[0..end - 1]));\n    // edge case: ends with extponent / sign\n    } else if has_exponent && (last == b'-' || last == b'+') {\n        return Ok((ValueType::Float, &text[0..end - 2]));\n    }\n    Ok((\n        if !has_decimal && !has_exponent {\n            ValueType::Integer\n        } else {\n            ValueType::Float\n        },\n        &text[..end],\n    ))\n}\n\n// Check if float can be converted to integer for INTEGER PRIMARY KEY columns.\n// SQLite uses sqlite3VdbeIntegerAffinity which requires:\n// 1. The float must round-trip correctly (float -> int -> float gives same value)\n// 2. The integer must be strictly between i64::MIN and i64::MAX (exclusive)\n//\n// This matches SQLite's check: ix > SMALLEST_INT64 && ix < LARGEST_INT64\npub fn cast_real_to_integer(float: f64) -> std::result::Result<i64, ()> {\n    // Must be finite and a whole number (no fractional part)\n    if !float.is_finite() || float.trunc() != float {\n        return Err(());\n    }\n\n    // Convert to i64, clamping to i64 range if necessary\n    // Note: Rust's f64 as i64 saturates to i64::MIN/MAX for out-of-range values\n    let int_val = float as i64;\n\n    // SQLite requires the value to be STRICTLY between i64::MIN and i64::MAX\n    // (i.e., ix > SMALLEST_INT64 && ix < LARGEST_INT64)\n    if int_val == i64::MIN || int_val == i64::MAX {\n        return Err(());\n    }\n\n    // Verify round-trip: converting back to f64 must give the same value\n    // This matches SQLite's check: pMem->u.r == ix\n    if (int_val as f64) != float {\n        return Err(());\n    }\n\n    Ok(int_val)\n}\n\n// we don't need to verify the numeric literal here, as it is already verified by the parser\npub fn parse_numeric_literal(text: &str) -> Result<Value> {\n    // a single extra underscore (\"_\") character can exist between any two digits\n    let text = if text.contains('_') {\n        std::borrow::Cow::Owned(text.replace('_', \"\"))\n    } else {\n        std::borrow::Cow::Borrowed(text)\n    };\n\n    if text.starts_with(\"0x\") || text.starts_with(\"0X\") {\n        let value = u64::from_str_radix(&text[2..], 16)? as i64;\n        return Ok(Value::from_i64(value));\n    } else if text.starts_with(\"-0x\") || text.starts_with(\"-0X\") {\n        let value = u64::from_str_radix(&text[3..], 16)? as i64;\n        if value == i64::MIN {\n            return Err(LimboError::IntegerOverflow);\n        }\n        return Ok(Value::from_i64(-value));\n    }\n\n    if let Ok(int_value) = text.parse::<i64>() {\n        return Ok(Value::from_i64(int_value));\n    }\n\n    let Some(StrToF64::Fractional(float) | StrToF64::Decimal(float)) =\n        crate::numeric::str_to_f64(text)\n    else {\n        unreachable!();\n    };\n    Ok(Value::Numeric(crate::numeric::Numeric::Float(float)))\n}\n\npub fn parse_signed_number(expr: &Expr) -> Result<Value> {\n    match expr {\n        Expr::Literal(Literal::Numeric(num)) => parse_numeric_literal(num),\n        Expr::Unary(op, expr) => match (op, expr.as_ref()) {\n            (UnaryOperator::Negative, Expr::Literal(Literal::Numeric(num))) => {\n                let data = \"-\".to_owned() + &num.to_string();\n                parse_numeric_literal(&data)\n            }\n            (UnaryOperator::Positive, Expr::Literal(Literal::Numeric(num))) => {\n                parse_numeric_literal(num)\n            }\n            _ => Err(LimboError::InvalidArgument(\n                \"signed-number must follow the format: ([+|-] numeric-literal)\".to_string(),\n            )),\n        },\n        _ => Err(LimboError::InvalidArgument(\n            \"signed-number must follow the format: ([+|-] numeric-literal)\".to_string(),\n        )),\n    }\n}\n\npub fn parse_string(expr: &Expr) -> Result<String> {\n    match expr {\n        Expr::Name(name) if name.quoted_with('\\'') => Ok(name.as_str().to_string()),\n        _ => Err(LimboError::InvalidArgument(format!(\n            \"string parameter expected, got {expr:?} instead\"\n        ))),\n    }\n}\n\n#[allow(unused)]\npub fn parse_pragma_bool(expr: &Expr) -> Result<bool> {\n    const TRUE_VALUES: &[&str] = &[\"yes\", \"true\", \"on\"];\n    const FALSE_VALUES: &[&str] = &[\"no\", \"false\", \"off\"];\n    if let Ok(number) = parse_signed_number(expr) {\n        if let Value::Numeric(crate::numeric::Numeric::Integer(x @ (0 | 1))) = number {\n            return Ok(x != 0);\n        }\n    } else if let Expr::Name(name) = expr {\n        let ident = normalize_ident(name.as_str());\n        if TRUE_VALUES.contains(&ident.as_str()) {\n            return Ok(true);\n        }\n        if FALSE_VALUES.contains(&ident.as_str()) {\n            return Ok(false);\n        }\n    }\n    Err(LimboError::InvalidArgument(\n        \"boolean pragma value must be either 0|1 integer or yes|true|on|no|false|off token\"\n            .to_string(),\n    ))\n}\n\n/// Extract column name from an expression (e.g., for SELECT clauses)\npub fn extract_column_name_from_expr(expr: impl AsRef<ast::Expr>) -> Option<String> {\n    match expr.as_ref() {\n        ast::Expr::Id(name) => Some(name.as_str().to_string()),\n        ast::Expr::DoublyQualified(_, _, name) | ast::Expr::Qualified(_, name) => {\n            Some(normalize_ident(name.as_str()))\n        }\n        _ => None,\n    }\n}\n\n/// Information about a table referenced in a view\n#[derive(Debug, Clone)]\npub struct ViewTable {\n    /// Unqualified table name, normalized.\n    pub name: String,\n    /// Database qualifier if present, normalized.\n    pub db_name: Option<String>,\n    /// Optional alias (e.g., \"c\" in \"FROM customers c\")\n    pub alias: Option<String>,\n}\n\n/// Information about a column in the view's output\n#[derive(Debug, Clone)]\npub struct ViewColumn {\n    /// Index into ViewColumnSchema.tables indicating which table this column comes from\n    /// For computed columns or constants, this will be usize::MAX\n    pub table_index: usize,\n    /// The actual column definition\n    pub column: Column,\n}\n\n/// Schema information for a view, tracking which columns come from which tables\n#[derive(Debug, Clone)]\npub struct ViewColumnSchema {\n    /// All tables referenced by the view (in order of appearance)\n    pub tables: Vec<ViewTable>,\n    /// The view's output columns with their table associations\n    pub columns: Vec<ViewColumn>,\n}\n\nimpl ViewColumnSchema {\n    /// Get all columns as a flat vector (without table association info)\n    pub fn flat_columns(&self) -> Vec<Column> {\n        self.columns.iter().map(|vc| vc.column.clone()).collect()\n    }\n\n    /// Get columns that belong to a specific table\n    pub fn table_columns(&self, table_index: usize) -> Vec<Column> {\n        self.columns\n            .iter()\n            .filter(|vc| vc.table_index == table_index)\n            .map(|vc| vc.column.clone())\n            .collect()\n    }\n}\n\n/// Walk all expressions in a SELECT statement, including subqueries.\npub fn walk_select_expressions<F>(select: &ast::Select, func: &mut F) -> Result<()>\nwhere\n    F: FnMut(&ast::Expr) -> Result<WalkControl>,\n{\n    walk_select_expressions_inner(select, func)\n}\n\npub fn validate_aggregate_function_tail(\n    filter_over: &ast::FunctionTail,\n    order_by: &[ast::SortedColumn],\n) -> Result<()> {\n    if filter_over.filter_clause.is_some() {\n        crate::bail_parse_error!(\"FILTER clause is not supported yet in aggregate functions\");\n    }\n\n    if !order_by.is_empty() {\n        crate::bail_parse_error!(\"ORDER BY clause is not supported yet in aggregate functions\");\n    }\n\n    Ok(())\n}\n\nfn walk_select_expressions_inner<F>(select: &ast::Select, func: &mut F) -> Result<()>\nwhere\n    F: FnMut(&ast::Expr) -> Result<WalkControl>,\n{\n    if let Some(with_clause) = &select.with {\n        for cte in &with_clause.ctes {\n            walk_select_expressions_inner(&cte.select, func)?;\n        }\n    }\n\n    walk_one_select_expressions(&select.body.select, func)?;\n    for compound in &select.body.compounds {\n        walk_one_select_expressions(&compound.select, func)?;\n    }\n\n    for sorted_col in &select.order_by {\n        walk_expr_with_subqueries(&sorted_col.expr, func)?;\n    }\n\n    if let Some(limit) = &select.limit {\n        walk_expr_with_subqueries(&limit.expr, func)?;\n        if let Some(offset) = &limit.offset {\n            walk_expr_with_subqueries(offset, func)?;\n        }\n    }\n\n    Ok(())\n}\n\nfn walk_one_select_expressions<F>(one_select: &ast::OneSelect, func: &mut F) -> Result<()>\nwhere\n    F: FnMut(&ast::Expr) -> Result<WalkControl>,\n{\n    match one_select {\n        ast::OneSelect::Select {\n            columns,\n            from,\n            where_clause,\n            group_by,\n            window_clause,\n            ..\n        } => {\n            for col in columns {\n                if let ast::ResultColumn::Expr(expr, _) = col {\n                    walk_expr_with_subqueries(expr, func)?;\n                }\n            }\n\n            if let Some(from_clause) = from {\n                walk_from_clause_expressions(from_clause, func)?;\n            }\n\n            if let Some(where_expr) = where_clause {\n                walk_expr_with_subqueries(where_expr, func)?;\n            }\n\n            if let Some(group_by) = group_by {\n                for expr in &group_by.exprs {\n                    walk_expr_with_subqueries(expr, func)?;\n                }\n                if let Some(having_expr) = &group_by.having {\n                    walk_expr_with_subqueries(having_expr, func)?;\n                }\n            }\n\n            for window_def in window_clause {\n                walk_window_expressions(&window_def.window, func)?;\n            }\n        }\n        ast::OneSelect::Values(values) => {\n            for row in values {\n                for expr in row {\n                    walk_expr_with_subqueries(expr, func)?;\n                }\n            }\n        }\n    }\n\n    Ok(())\n}\n\nfn walk_from_clause_expressions<F>(from_clause: &ast::FromClause, func: &mut F) -> Result<()>\nwhere\n    F: FnMut(&ast::Expr) -> Result<WalkControl>,\n{\n    walk_select_table_expressions(&from_clause.select, func)?;\n\n    for join in &from_clause.joins {\n        walk_select_table_expressions(&join.table, func)?;\n\n        if let Some(ast::JoinConstraint::On(expr)) = &join.constraint {\n            walk_expr_with_subqueries(expr, func)?;\n        }\n    }\n\n    Ok(())\n}\n\nfn walk_select_table_expressions<F>(select_table: &ast::SelectTable, func: &mut F) -> Result<()>\nwhere\n    F: FnMut(&ast::Expr) -> Result<WalkControl>,\n{\n    match select_table {\n        ast::SelectTable::Select(select, _) => walk_select_expressions_inner(select, func),\n        ast::SelectTable::Sub(from_clause, _) => walk_from_clause_expressions(from_clause, func),\n        ast::SelectTable::TableCall(_, args, _) => {\n            for arg in args {\n                walk_expr_with_subqueries(arg, func)?;\n            }\n            Ok(())\n        }\n        ast::SelectTable::Table(_, _, _) => Ok(()),\n    }\n}\n\nfn walk_window_expressions<F>(window: &ast::Window, func: &mut F) -> Result<()>\nwhere\n    F: FnMut(&ast::Expr) -> Result<WalkControl>,\n{\n    for expr in &window.partition_by {\n        walk_expr_with_subqueries(expr, func)?;\n    }\n\n    for sorted_col in &window.order_by {\n        walk_expr_with_subqueries(&sorted_col.expr, func)?;\n    }\n\n    if let Some(frame_clause) = &window.frame_clause {\n        walk_frame_bound_expressions(&frame_clause.start, func)?;\n        if let Some(end_bound) = &frame_clause.end {\n            walk_frame_bound_expressions(end_bound, func)?;\n        }\n    }\n\n    Ok(())\n}\n\nfn walk_frame_bound_expressions<F>(bound: &ast::FrameBound, func: &mut F) -> Result<()>\nwhere\n    F: FnMut(&ast::Expr) -> Result<WalkControl>,\n{\n    match bound {\n        ast::FrameBound::Following(expr) | ast::FrameBound::Preceding(expr) => {\n            walk_expr_with_subqueries(expr, func)\n        }\n        ast::FrameBound::CurrentRow\n        | ast::FrameBound::UnboundedFollowing\n        | ast::FrameBound::UnboundedPreceding => Ok(()),\n    }\n}\n\npub fn walk_expr_with_subqueries<F>(expr: &ast::Expr, func: &mut F) -> Result<()>\nwhere\n    F: FnMut(&ast::Expr) -> Result<WalkControl>,\n{\n    walk_expr(expr, &mut |e| {\n        let control = func(e)?;\n        if matches!(control, WalkControl::Continue) {\n            match e {\n                ast::Expr::Subquery(select) | ast::Expr::Exists(select) => {\n                    walk_select_expressions_inner(select, func)?;\n                }\n                ast::Expr::InSelect { rhs, .. } => {\n                    walk_select_expressions_inner(rhs, func)?;\n                }\n                _ => {}\n            }\n        }\n        Ok(control)\n    })?;\n    Ok(())\n}\n\nfn validate_no_cross_db_references(\n    select_stmt: &ast::Select,\n    view_db_name: Option<&ast::Name>,\n) -> Result<()> {\n    if let Some(with_clause) = &select_stmt.with {\n        for cte in &with_clause.ctes {\n            validate_no_cross_db_references(&cte.select, view_db_name)?;\n        }\n    }\n\n    validate_one_select_no_cross_db(&select_stmt.body.select, view_db_name)?;\n\n    for compound in &select_stmt.body.compounds {\n        validate_one_select_no_cross_db(&compound.select, view_db_name)?;\n    }\n\n    Ok(())\n}\n\nfn validate_one_select_no_cross_db(\n    one_select: &ast::OneSelect,\n    view_db_name: Option<&ast::Name>,\n) -> Result<()> {\n    match one_select {\n        ast::OneSelect::Select { from, .. } => {\n            if let Some(from_clause) = from {\n                validate_from_clause_no_cross_db(from_clause, view_db_name)?;\n            }\n        }\n        ast::OneSelect::Values(_) => {}\n    }\n    Ok(())\n}\n\nfn validate_from_clause_no_cross_db(\n    from_clause: &ast::FromClause,\n    view_db_name: Option<&ast::Name>,\n) -> Result<()> {\n    validate_select_table_no_cross_db(&from_clause.select, view_db_name)?;\n    for join in &from_clause.joins {\n        validate_select_table_no_cross_db(&join.table, view_db_name)?;\n    }\n    Ok(())\n}\n\nfn reject_cross_db_qualified_name(\n    qualified_name: &ast::QualifiedName,\n    view_db_name: Option<&ast::Name>,\n) -> Result<()> {\n    if let Some(table_db_name) = &qualified_name.db_name {\n        let is_cross_db = match view_db_name {\n            Some(view_db) => {\n                normalize_ident(view_db.as_str()) != normalize_ident(table_db_name.as_str())\n            }\n            None => !table_db_name.as_str().eq_ignore_ascii_case(\"main\"),\n        };\n        if is_cross_db {\n            return Err(crate::LimboError::ParseError(format!(\n                \"view cannot reference table in attached database: {qualified_name}\"\n            )));\n        }\n    }\n    Ok(())\n}\n\nfn validate_select_table_no_cross_db(\n    select_table: &ast::SelectTable,\n    view_db_name: Option<&ast::Name>,\n) -> Result<()> {\n    match select_table {\n        ast::SelectTable::Table(name, _, _) | ast::SelectTable::TableCall(name, _, _) => {\n            reject_cross_db_qualified_name(name, view_db_name)?;\n        }\n        ast::SelectTable::Select(select, _) => {\n            validate_no_cross_db_references(select, view_db_name)?;\n        }\n        ast::SelectTable::Sub(from_clause, _) => {\n            validate_from_clause_no_cross_db(from_clause, view_db_name)?;\n        }\n    }\n    Ok(())\n}\n\npub fn validate_select_for_unsupported_features(select_stmt: &ast::Select) -> Result<()> {\n    walk_select_expressions(select_stmt, &mut |expr| {\n        match expr {\n            ast::Expr::FunctionCall {\n                filter_over,\n                order_by,\n                ..\n            } => {\n                validate_aggregate_function_tail(filter_over, order_by)?;\n            }\n            ast::Expr::FunctionCallStar { filter_over, .. } => {\n                validate_aggregate_function_tail(filter_over, &[])?;\n            }\n            _ => {}\n        }\n\n        Ok(WalkControl::Continue)\n    })\n}\n\npub fn validate_select_for_views(\n    select_stmt: &ast::Select,\n    view_db_name: Option<&ast::Name>,\n) -> Result<()> {\n    validate_select_for_unsupported_features(select_stmt)?;\n\n    validate_no_cross_db_references(select_stmt, view_db_name)?;\n\n    walk_select_expressions(select_stmt, &mut |expr| {\n        match expr {\n            ast::Expr::Subquery(subquery_select) | ast::Expr::Exists(subquery_select) => {\n                validate_no_cross_db_references(subquery_select, view_db_name)?;\n            }\n            ast::Expr::InSelect { rhs, .. } => {\n                validate_no_cross_db_references(rhs, view_db_name)?;\n            }\n            _ => {}\n        }\n\n        Ok(WalkControl::Continue)\n    })?;\n\n    Ok(())\n}\n\n/// Extract column information from a SELECT statement for view creation\npub fn extract_view_columns(\n    select_stmt: &ast::Select,\n    schema: &Schema,\n) -> Result<ViewColumnSchema> {\n    let mut tables = Vec::new();\n    let mut columns = Vec::new();\n    let mut column_name_counts: HashMap<String, usize> = HashMap::default();\n\n    // Navigate to the first SELECT in the statement\n    if let ast::OneSelect::Select {\n        ref from,\n        columns: select_columns,\n        ..\n    } = &select_stmt.body.select\n    {\n        // First, extract all tables (from FROM clause and JOINs)\n        if let Some(from) = from {\n            // Add the main table from FROM clause\n            match from.select.as_ref() {\n                ast::SelectTable::Table(qualified_name, alias, _) => {\n                    let table_name = normalize_ident(qualified_name.name.as_str());\n                    let db_name = qualified_name\n                        .db_name\n                        .as_ref()\n                        .map(|db| normalize_ident(db.as_str()));\n                    tables.push(ViewTable {\n                        name: table_name,\n                        db_name,\n                        alias: alias.as_ref().map(|a| match a {\n                            ast::As::As(name) => normalize_ident(name.as_str()),\n                            ast::As::Elided(name) => normalize_ident(name.as_str()),\n                        }),\n                    });\n                }\n                _ => {\n                    // Handle other types like subqueries if needed\n                }\n            }\n\n            // Add tables from JOINs\n            for join in &from.joins {\n                match join.table.as_ref() {\n                    ast::SelectTable::Table(qualified_name, alias, _) => {\n                        let table_name = normalize_ident(qualified_name.name.as_str());\n                        let db_name = qualified_name\n                            .db_name\n                            .as_ref()\n                            .map(|db| normalize_ident(db.as_str()));\n                        tables.push(ViewTable {\n                            name: table_name,\n                            db_name,\n                            alias: alias.as_ref().map(|a| match a {\n                                ast::As::As(name) => normalize_ident(name.as_str()),\n                                ast::As::Elided(name) => normalize_ident(name.as_str()),\n                            }),\n                        });\n                    }\n                    _ => {\n                        // Handle other types like subqueries if needed\n                    }\n                }\n            }\n        }\n\n        // Helper function to find table index by name or alias\n        let find_table_index = |name: &str| -> Option<usize> {\n            let name_norm = normalize_ident(name);\n            tables.iter().position(|t| {\n                t.name == name_norm || t.alias.as_ref().is_some_and(|a| a == &name_norm)\n            })\n        };\n\n        // Process each column in the SELECT list\n        for result_col in select_columns.iter() {\n            match result_col {\n                ast::ResultColumn::Expr(expr, alias) => {\n                    // Figure out which table this expression comes from\n                    let table_index = match expr.as_ref() {\n                        ast::Expr::Qualified(table_ref, _col_name) => {\n                            // Column qualified with table name\n                            find_table_index(table_ref.as_str())\n                        }\n                        ast::Expr::Id(_col_name) => {\n                            // Unqualified column - would need to resolve based on schema\n                            // For now, assume it's from the first table if there is one\n                            if !tables.is_empty() {\n                                Some(0)\n                            } else {\n                                None\n                            }\n                        }\n                        _ => None, // Expression, literal, etc.\n                    };\n\n                    let col_name = alias\n                        .as_ref()\n                        .map(|a| match a {\n                            ast::As::Elided(name) => name.as_str().to_string(),\n                            ast::As::As(name) => name.as_str().to_string(),\n                        })\n                        .or_else(|| extract_column_name_from_expr(expr))\n                        .unwrap_or_else(|| {\n                            // If we can't extract a simple column name, use the expression itself\n                            expr.to_string()\n                        });\n\n                    columns.push(ViewColumn {\n                        table_index: table_index.unwrap_or(usize::MAX),\n                        column: Column::new_default_text(Some(col_name), \"TEXT\".to_string(), None),\n                    });\n                }\n                ast::ResultColumn::Star => {\n                    // For SELECT *, expand to all columns from all tables\n                    for (table_idx, table) in tables.iter().enumerate() {\n                        if let Some(table_obj) = schema.get_table(&table.name) {\n                            for table_column in table_obj.columns() {\n                                let col_name =\n                                    table_column.name.clone().unwrap_or_else(|| \"?\".to_string());\n\n                                // Handle duplicate column names by adding suffix\n                                let final_name =\n                                    if let Some(count) = column_name_counts.get_mut(&col_name) {\n                                        *count += 1;\n                                        format!(\"{}:{}\", col_name, *count - 1)\n                                    } else {\n                                        column_name_counts.insert(col_name.clone(), 1);\n                                        col_name.clone()\n                                    };\n\n                                columns.push(ViewColumn {\n                                    table_index: table_idx,\n                                    column: Column::new(\n                                        Some(final_name),\n                                        table_column.ty_str.clone(),\n                                        None,\n                                        None,\n                                        table_column.ty(),\n                                        table_column.collation_opt(),\n                                        ColDef::default(),\n                                    ),\n                                });\n                            }\n                        }\n                    }\n\n                    // If no tables, create a placeholder\n                    if tables.is_empty() {\n                        columns.push(ViewColumn {\n                            table_index: usize::MAX,\n                            column: Column::new_default_text(\n                                Some(\"*\".to_string()),\n                                \"TEXT\".to_string(),\n                                None,\n                            ),\n                        });\n                    }\n                }\n                ast::ResultColumn::TableStar(table_ref) => {\n                    // For table.*, expand to all columns from the specified table\n                    let table_name_str = normalize_ident(table_ref.as_str());\n                    if let Some(table_idx) = find_table_index(&table_name_str) {\n                        if let Some(table) = schema.get_table(&tables[table_idx].name) {\n                            for table_column in table.columns() {\n                                let col_name =\n                                    table_column.name.clone().unwrap_or_else(|| \"?\".to_string());\n\n                                // Handle duplicate column names by adding suffix\n                                let final_name =\n                                    if let Some(count) = column_name_counts.get_mut(&col_name) {\n                                        *count += 1;\n                                        format!(\"{}:{}\", col_name, *count - 1)\n                                    } else {\n                                        column_name_counts.insert(col_name.clone(), 1);\n                                        col_name.clone()\n                                    };\n\n                                columns.push(ViewColumn {\n                                    table_index: table_idx,\n                                    column: Column::new(\n                                        Some(final_name),\n                                        table_column.ty_str.clone(),\n                                        None,\n                                        None,\n                                        table_column.ty(),\n                                        table_column.collation_opt(),\n                                        ColDef::default(),\n                                    ),\n                                });\n                            }\n                        } else {\n                            // Table not found, create placeholder\n                            columns.push(ViewColumn {\n                                table_index: usize::MAX,\n                                column: Column::new_default_text(\n                                    Some(format!(\"{table_name_str}.*\")),\n                                    \"TEXT\".to_string(),\n                                    None,\n                                ),\n                            });\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    Ok(ViewColumnSchema { tables, columns })\n}\n\npub fn rewrite_fk_parent_cols_if_self_ref(\n    clause: &mut ast::ForeignKeyClause,\n    table: &str,\n    from: &str,\n    to: &str,\n) {\n    if normalize_ident(clause.tbl_name.as_str()) == normalize_ident(table) {\n        for c in &mut clause.columns {\n            if normalize_ident(c.col_name.as_str()) == normalize_ident(from) {\n                c.col_name = ast::Name::exact(to.to_owned());\n            }\n        }\n    }\n}\n\n/// Returns true if the expression tree references a column whose normalized\n/// name equals `col_name_normalized`.\npub fn check_expr_references_column(expr: &ast::Expr, col_name_normalized: &str) -> bool {\n    let mut found = false;\n    // The closure is infallible, so walk_expr cannot fail.\n    let _ = walk_expr(expr, &mut |e| {\n        if found {\n            return Ok(WalkControl::SkipChildren);\n        }\n        match e {\n            ast::Expr::Id(name) | ast::Expr::Name(name) => {\n                if normalize_ident(name.as_str()) == col_name_normalized {\n                    found = true;\n                    return Ok(WalkControl::SkipChildren);\n                }\n            }\n            ast::Expr::Qualified(_, col) | ast::Expr::DoublyQualified(_, _, col) => {\n                if normalize_ident(col.as_str()) == col_name_normalized {\n                    found = true;\n                    return Ok(WalkControl::SkipChildren);\n                }\n            }\n            _ => {}\n        }\n        Ok(WalkControl::Continue)\n    });\n    found\n}\n\n/// Rewrite column name references; used in e.g. ALTER TABLE RENAME COLUMN\n/// to rewrite references to the old column name to the new column name.\n/// Replaces `Id(old)` and `Name(old)` with `Id(new)`, and updates the\n/// column name in `Qualified(tbl, old)` references.\npub fn rename_identifiers(expr: &mut ast::Expr, from: &str, to: &str) {\n    let from_normalized = normalize_ident(from);\n    // The closure is infallible, so walk_expr_mut cannot fail.\n    let _ = walk_expr_mut(\n        expr,\n        &mut |e: &mut ast::Expr| -> crate::Result<WalkControl> {\n            match e {\n                ast::Expr::Id(ref name) | ast::Expr::Name(ref name)\n                    if normalize_ident(name.as_str()) == from_normalized =>\n                {\n                    *e = ast::Expr::Id(ast::Name::exact(to.to_owned()));\n                }\n                ast::Expr::Qualified(ref tbl, ref col_name)\n                    if normalize_ident(col_name.as_str()) == from_normalized =>\n                {\n                    let tbl = tbl.clone();\n                    *e = ast::Expr::Qualified(tbl, ast::Name::exact(to.to_owned()));\n                }\n                _ => {}\n            }\n            Ok(WalkControl::Continue)\n        },\n    );\n}\n\n/// Like `rename_identifiers` but scope-aware: only renames qualified refs\n/// (e.g. `t1.b`) when the qualifier matches the target table or is NEW/OLD\n/// (which always refer to the trigger's owning table). Unqualified refs\n/// are renamed unconditionally (caller must ensure they're in the right scope).\n/// Also enters Subquery/Exists/InSelect expressions that walk_expr_mut skips.\npub fn rename_identifiers_scoped(\n    expr: &mut ast::Expr,\n    target_table: &str,\n    trigger_table: &str,\n    from: &str,\n    to: &str,\n) {\n    rename_identifiers_scoped_inner(expr, target_table, trigger_table, from, to, true);\n}\n\n/// Rename column references in a trigger WHEN clause.\n/// Only renames qualified NEW.col / OLD.col references — bare column names\n/// are invalid in WHEN clauses per SQLite semantics and must not be renamed.\npub fn rename_identifiers_scoped_when_clause(\n    expr: &mut ast::Expr,\n    target_table: &str,\n    trigger_table: &str,\n    from: &str,\n    to: &str,\n) {\n    rename_identifiers_scoped_inner(expr, target_table, trigger_table, from, to, false);\n}\n\n/// Inner implementation with `rename_unqualified` flag controlling whether bare `Expr::Id`\n/// references should be renamed. When `false`, only qualified refs (table.col, NEW.col, OLD.col)\n/// are renamed — used when the enclosing SELECT's FROM clause does NOT reference the target table.\nfn rename_identifiers_scoped_inner(\n    expr: &mut ast::Expr,\n    target_table: &str,\n    trigger_table: &str,\n    from: &str,\n    to: &str,\n    rename_unqualified: bool,\n) {\n    let from_normalized = normalize_ident(from);\n    let target_normalized = normalize_ident(target_table);\n    let trigger_normalized = normalize_ident(trigger_table);\n    let is_renaming_trigger_table = target_normalized == trigger_normalized;\n\n    let _ = walk_expr_mut(\n        expr,\n        &mut |e: &mut ast::Expr| -> crate::Result<WalkControl> {\n            match e {\n                ast::Expr::Subquery(select) | ast::Expr::Exists(select) => {\n                    rewrite_select_column_refs_scoped(\n                        select,\n                        target_table,\n                        trigger_table,\n                        from,\n                        to,\n                    );\n                }\n                ast::Expr::InSelect { rhs, .. } => {\n                    rewrite_select_column_refs_scoped(rhs, target_table, trigger_table, from, to);\n                    // lhs will be walked by walk_expr_mut\n                }\n                ast::Expr::Id(ref name) | ast::Expr::Name(ref name)\n                    if rename_unqualified && normalize_ident(name.as_str()) == from_normalized =>\n                {\n                    *e = ast::Expr::Id(ast::Name::exact(to.to_owned()));\n                }\n                ast::Expr::Qualified(ref tbl, ref col_name)\n                    if normalize_ident(col_name.as_str()) == from_normalized =>\n                {\n                    let tbl_norm = normalize_ident(tbl.as_str());\n                    let should_rename = if tbl_norm == \"new\" || tbl_norm == \"old\" {\n                        is_renaming_trigger_table\n                    } else {\n                        tbl_norm == target_normalized\n                    };\n                    if should_rename {\n                        let tbl = tbl.clone();\n                        *e = ast::Expr::Qualified(tbl, ast::Name::exact(to.to_owned()));\n                    }\n                }\n                _ => {}\n            }\n            Ok(WalkControl::Continue)\n        },\n    );\n}\n\nmod rename_column_view {\n    use super::*;\n\n    #[derive(Debug, Clone)]\n    pub struct RewrittenView {\n        pub sql: String,\n        pub select_stmt: ast::Select,\n        pub columns: Vec<Column>,\n    }\n\n    pub fn rewrite_view_sql_for_column_rename(\n        view_sql: &str,\n        schema: &Schema,\n        target_table: &str,\n        target_db_name: &str,\n        old_column: &str,\n        new_column: &str,\n    ) -> Result<Option<RewrittenView>> {\n        let mut visiting_views = HashSet::default();\n        rewrite_view_sql_for_column_rename_inner(\n            view_sql,\n            schema,\n            target_table,\n            target_db_name,\n            old_column,\n            new_column,\n            &mut visiting_views,\n        )\n    }\n\n    fn rewrite_view_sql_for_column_rename_inner(\n        view_sql: &str,\n        schema: &Schema,\n        target_table: &str,\n        target_db_name: &str,\n        old_column: &str,\n        new_column: &str,\n        visiting_views: &mut HashSet<String>,\n    ) -> Result<Option<RewrittenView>> {\n        let mut parser = Parser::new(view_sql.as_bytes());\n        let cmd = parser\n            .next_cmd()\n            .map_err(|e| LimboError::ParseError(format!(\"failed to parse view SQL: {e}\")))?;\n        let Some(ast::Cmd::Stmt(ast::Stmt::CreateView {\n            temporary,\n            if_not_exists,\n            view_name,\n            columns: view_columns,\n            mut select,\n        })) = cmd\n        else {\n            return Ok(None);\n        };\n\n        let current_view_name = normalize_ident(view_name.name.as_str());\n        if !visiting_views.insert(current_view_name.clone()) {\n            return Err(LimboError::ParseError(format!(\n                \"view {current_view_name} is circularly defined\"\n            )));\n        }\n\n        let rewrite_result = (|| -> Result<Option<RewrittenView>> {\n            let original_select = select.clone();\n            let original_columns =\n                view_columns_from_select(&original_select, schema, &view_columns)?;\n\n            let ctx =\n                ViewRewriteCtx::new(schema, target_table, target_db_name, old_column, new_column);\n            let sql_changed =\n                rewrite_view_select_for_column_rename(&mut select, &ctx, &[], visiting_views)?;\n\n            let view_column_schema = extract_view_columns(&select, schema)?;\n            let mut final_columns = apply_view_column_rename(view_column_schema, &ctx);\n\n            for (i, indexed_col) in view_columns.iter().enumerate() {\n                if let Some(col) = final_columns.get_mut(i) {\n                    col.name = Some(indexed_col.col_name.to_string());\n                }\n            }\n\n            let columns_changed = !columns_equivalent(&original_columns, &final_columns);\n\n            if !sql_changed && !columns_changed {\n                return Ok(None);\n            }\n\n            let new_sql = if sql_changed {\n                let new_stmt = ast::Stmt::CreateView {\n                    temporary,\n                    if_not_exists,\n                    view_name,\n                    columns: view_columns,\n                    select: select.clone(),\n                };\n                new_stmt.to_string()\n            } else {\n                view_sql.to_string()\n            };\n\n            Ok(Some(RewrittenView {\n                sql: new_sql,\n                select_stmt: select,\n                columns: final_columns,\n            }))\n        })();\n        visiting_views.remove(&current_view_name);\n        rewrite_result\n    }\n\n    fn apply_view_column_rename(\n        view_columns: ViewColumnSchema,\n        ctx: &ViewRewriteCtx,\n    ) -> Vec<Column> {\n        let target_norm = ctx.target_table_norm.as_str();\n        let mut columns = view_columns.columns;\n\n        for view_column in &mut columns {\n            if view_column.table_index == usize::MAX {\n                continue;\n            }\n            let table = &view_columns.tables[view_column.table_index];\n            if table_name_matches_target(\n                &table.name,\n                table.db_name.as_deref(),\n                target_norm,\n                &ctx.target_db_norm,\n            ) {\n                if let Some(ref mut name) = view_column.column.name {\n                    if name.as_str().eq_ignore_ascii_case(ctx.old_column) {\n                        *name = ctx.new_column.to_string();\n                    }\n                }\n            }\n        }\n\n        columns.into_iter().map(|vc| vc.column).collect()\n    }\n\n    fn view_columns_from_select(\n        select: &ast::Select,\n        schema: &Schema,\n        explicit: &[ast::IndexedColumn],\n    ) -> Result<Vec<Column>> {\n        let view_column_schema = extract_view_columns(select, schema)?;\n        let mut columns = view_column_schema.flat_columns();\n        for (i, indexed_col) in explicit.iter().enumerate() {\n            if let Some(col) = columns.get_mut(i) {\n                col.name = Some(indexed_col.col_name.to_string());\n            }\n        }\n        Ok(columns)\n    }\n\n    fn columns_equivalent(left: &[Column], right: &[Column]) -> bool {\n        if left.len() != right.len() {\n            return false;\n        }\n        left.iter().zip(right.iter()).all(|(l, r)| {\n            let l_name = l.name.as_deref().unwrap_or(\"\");\n            let r_name = r.name.as_deref().unwrap_or(\"\");\n            l_name.eq_ignore_ascii_case(r_name)\n        })\n    }\n\n    #[derive(Clone)]\n    struct ViewSourceInfo {\n        qualifiers: Vec<String>,\n        columns_before: HashSet<String>,\n        rename_map: HashMap<String, String>,\n        is_target_table: bool,\n        db_name: Option<String>,\n    }\n\n    impl ViewSourceInfo {\n        fn matches_qualifier(&self, qualifier: &str) -> bool {\n            self.qualifiers.iter().any(|q| q == qualifier)\n        }\n    }\n\n    fn alias_name(alias: &ast::As) -> &str {\n        match alias {\n            ast::As::As(name) | ast::As::Elided(name) => name.as_str(),\n        }\n    }\n\n    #[derive(Clone)]\n    struct CteInfo {\n        columns_before: HashSet<String>,\n        rename_map: HashMap<String, String>,\n    }\n\n    struct ViewRewriteCtx<'a> {\n        schema: &'a Schema,\n        target_table: &'a str,\n        target_table_norm: String,\n        target_db_norm: String,\n        old_column: &'a str,\n        old_column_norm: String,\n        new_column: &'a str,\n    }\n\n    impl<'a> ViewRewriteCtx<'a> {\n        fn new(\n            schema: &'a Schema,\n            target_table: &'a str,\n            target_db_name: &'a str,\n            old_column: &'a str,\n            new_column: &'a str,\n        ) -> Self {\n            Self {\n                schema,\n                target_table,\n                target_table_norm: normalize_ident(target_table),\n                target_db_norm: normalize_ident(target_db_name),\n                old_column,\n                old_column_norm: normalize_ident(old_column),\n                new_column,\n            }\n        }\n    }\n\n    fn rewrite_view_select_for_column_rename(\n        select: &mut ast::Select,\n        ctx: &ViewRewriteCtx,\n        outer_scopes: &[&[ViewSourceInfo]],\n        visiting_views: &mut HashSet<String>,\n    ) -> Result<bool> {\n        let mut changed = false;\n\n        let mut ctes: HashMap<String, CteInfo> = HashMap::default();\n        if let Some(ref mut with_clause) = select.with {\n            for cte in &mut with_clause.ctes {\n                let mut before_cols = select_output_columns(&cte.select, ctx, false)?;\n                apply_explicit_column_names(&mut before_cols, &cte.columns);\n                let cte_changed = rewrite_view_select_for_column_rename(\n                    &mut cte.select,\n                    ctx,\n                    &[],\n                    visiting_views,\n                )?;\n                changed |= cte_changed;\n                let mut after_cols = select_output_columns(&cte.select, ctx, true)?;\n                apply_explicit_column_names(&mut after_cols, &cte.columns);\n                let rename_map = build_rename_map(&before_cols, &after_cols, &ctx.old_column_norm);\n                ctes.insert(\n                    normalize_ident(cte.tbl_name.as_str()),\n                    CteInfo {\n                        columns_before: before_cols\n                            .into_iter()\n                            .map(|c| normalize_ident(&c))\n                            .collect(),\n                        rename_map,\n                    },\n                );\n            }\n        }\n\n        let mut scope_sources = rewrite_one_select_for_column_rename(\n            &mut select.body.select,\n            ctx,\n            &ctes,\n            outer_scopes,\n            &mut changed,\n            visiting_views,\n        )?;\n\n        for compound in &mut select.body.compounds {\n            let compound_sources = rewrite_one_select_for_column_rename(\n                &mut compound.select,\n                ctx,\n                &ctes,\n                outer_scopes,\n                &mut changed,\n                visiting_views,\n            )?;\n            if scope_sources.is_none() {\n                scope_sources = compound_sources;\n            }\n        }\n\n        if let Some(ref sources) = scope_sources {\n            for sorted_col in &mut select.order_by {\n                rewrite_expr_in_scope(\n                    &mut sorted_col.expr,\n                    sources,\n                    outer_scopes,\n                    ctx,\n                    &mut changed,\n                    visiting_views,\n                )?;\n            }\n            if let Some(ref mut limit) = select.limit {\n                rewrite_expr_in_scope(\n                    &mut limit.expr,\n                    sources,\n                    outer_scopes,\n                    ctx,\n                    &mut changed,\n                    visiting_views,\n                )?;\n                if let Some(ref mut offset) = limit.offset {\n                    rewrite_expr_in_scope(\n                        offset,\n                        sources,\n                        outer_scopes,\n                        ctx,\n                        &mut changed,\n                        visiting_views,\n                    )?;\n                }\n            }\n        }\n\n        Ok(changed)\n    }\n\n    fn rewrite_one_select_for_column_rename(\n        one_select: &mut ast::OneSelect,\n        ctx: &ViewRewriteCtx,\n        ctes: &HashMap<String, CteInfo>,\n        outer_scopes: &[&[ViewSourceInfo]],\n        changed: &mut bool,\n        visiting_views: &mut HashSet<String>,\n    ) -> Result<Option<Vec<ViewSourceInfo>>> {\n        match one_select {\n            ast::OneSelect::Select {\n                columns,\n                from,\n                where_clause,\n                group_by,\n                window_clause,\n                ..\n            } => {\n                let sources = if let Some(ref mut from_clause) = from {\n                    rewrite_from_clause_for_column_rename(\n                        from_clause,\n                        ctx,\n                        ctes,\n                        outer_scopes,\n                        changed,\n                        visiting_views,\n                    )?\n                } else {\n                    Vec::new()\n                };\n\n                for col in columns {\n                    if let ast::ResultColumn::Expr(expr, _) = col {\n                        rewrite_expr_in_scope(\n                            expr,\n                            &sources,\n                            outer_scopes,\n                            ctx,\n                            changed,\n                            visiting_views,\n                        )?;\n                    }\n                }\n\n                if let Some(ref mut where_expr) = where_clause {\n                    rewrite_expr_in_scope(\n                        where_expr,\n                        &sources,\n                        outer_scopes,\n                        ctx,\n                        changed,\n                        visiting_views,\n                    )?;\n                }\n\n                if let Some(ref mut group_by) = group_by {\n                    for expr in &mut group_by.exprs {\n                        rewrite_expr_in_scope(\n                            expr,\n                            &sources,\n                            outer_scopes,\n                            ctx,\n                            changed,\n                            visiting_views,\n                        )?;\n                    }\n                    if let Some(ref mut having_expr) = group_by.having {\n                        rewrite_expr_in_scope(\n                            having_expr,\n                            &sources,\n                            outer_scopes,\n                            ctx,\n                            changed,\n                            visiting_views,\n                        )?;\n                    }\n                }\n\n                for window_def in window_clause {\n                    for expr in &mut window_def.window.partition_by {\n                        rewrite_expr_in_scope(\n                            expr,\n                            &sources,\n                            outer_scopes,\n                            ctx,\n                            changed,\n                            visiting_views,\n                        )?;\n                    }\n                    for sorted in &mut window_def.window.order_by {\n                        rewrite_expr_in_scope(\n                            &mut sorted.expr,\n                            &sources,\n                            outer_scopes,\n                            ctx,\n                            changed,\n                            visiting_views,\n                        )?;\n                    }\n                }\n\n                Ok(Some(sources))\n            }\n            ast::OneSelect::Values(values) => {\n                for row in values {\n                    for expr in row {\n                        rewrite_expr_in_scope(\n                            expr,\n                            &[],\n                            outer_scopes,\n                            ctx,\n                            changed,\n                            visiting_views,\n                        )?;\n                    }\n                }\n                Ok(None)\n            }\n        }\n    }\n\n    fn rewrite_from_clause_for_column_rename(\n        from_clause: &mut ast::FromClause,\n        ctx: &ViewRewriteCtx,\n        ctes: &HashMap<String, CteInfo>,\n        outer_scopes: &[&[ViewSourceInfo]],\n        changed: &mut bool,\n        visiting_views: &mut HashSet<String>,\n    ) -> Result<Vec<ViewSourceInfo>> {\n        let mut sources = Vec::new();\n        let first_source = rewrite_select_table_for_column_rename(\n            &mut from_clause.select,\n            &[],\n            ctx,\n            ctes,\n            outer_scopes,\n            changed,\n            visiting_views,\n        )?;\n        sources.push(first_source);\n\n        for join in &mut from_clause.joins {\n            let right_source = rewrite_select_table_for_column_rename(\n                &mut join.table,\n                &sources,\n                ctx,\n                ctes,\n                outer_scopes,\n                changed,\n                visiting_views,\n            )?;\n            sources.push(right_source);\n            let (right_source, left_sources) = sources\n                .split_last()\n                .expect(\"sources should include the right-hand side join source\");\n            if let Some(ref mut constraint) = join.constraint {\n                match constraint {\n                    ast::JoinConstraint::On(expr) => {\n                        rewrite_expr_in_scope(\n                            expr,\n                            &sources,\n                            outer_scopes,\n                            ctx,\n                            changed,\n                            visiting_views,\n                        )?;\n                    }\n                    ast::JoinConstraint::Using(cols) => {\n                        *changed |= rewrite_using_columns(\n                            cols,\n                            left_sources,\n                            right_source,\n                            &ctx.old_column_norm,\n                            ctx.new_column,\n                        );\n                    }\n                }\n            }\n        }\n\n        Ok(sources)\n    }\n\n    fn rewrite_select_table_for_column_rename(\n        select_table: &mut ast::SelectTable,\n        visible_sources: &[ViewSourceInfo],\n        ctx: &ViewRewriteCtx,\n        ctes: &HashMap<String, CteInfo>,\n        outer_scopes: &[&[ViewSourceInfo]],\n        changed: &mut bool,\n        visiting_views: &mut HashSet<String>,\n    ) -> Result<ViewSourceInfo> {\n        match select_table {\n            ast::SelectTable::Table(tbl_name, alias, _) => {\n                let table_name_norm = normalize_ident(tbl_name.name.as_str());\n                let table_db_norm = tbl_name\n                    .db_name\n                    .as_ref()\n                    .map(|db| normalize_ident(db.as_str()));\n                let mut qualifiers = Vec::new();\n                qualifiers.push(table_name_norm.clone());\n                if let Some(ref alias) = alias {\n                    qualifiers.push(normalize_ident(alias_name(alias)));\n                }\n                if table_db_norm.is_none() {\n                    if let Some(cte) = ctes.get(&table_name_norm) {\n                        return Ok(ViewSourceInfo {\n                            qualifiers,\n                            columns_before: cte.columns_before.clone(),\n                            rename_map: cte.rename_map.clone(),\n                            is_target_table: false,\n                            db_name: None,\n                        });\n                    }\n                }\n\n                let is_local = table_db_norm\n                    .as_deref()\n                    .is_none_or(|db| db == ctx.target_db_norm);\n\n                if is_local {\n                    if let Some(view) = ctx.schema.views.get(&table_name_norm) {\n                        let columns_before = view\n                            .columns\n                            .iter()\n                            .filter_map(|col| col.name.clone())\n                            .map(|name| normalize_ident(&name))\n                            .collect();\n\n                        let mut rename_map = HashMap::default();\n                        if let Some(rewritten) = rewrite_view_sql_for_column_rename_inner(\n                            &view.sql,\n                            ctx.schema,\n                            ctx.target_table,\n                            &ctx.target_db_norm,\n                            ctx.old_column,\n                            ctx.new_column,\n                            visiting_views,\n                        )? {\n                            rename_map = build_rename_map_from_columns(\n                                &view.columns,\n                                &rewritten.columns,\n                                &ctx.old_column_norm,\n                            );\n                        }\n\n                        return Ok(ViewSourceInfo {\n                            qualifiers,\n                            columns_before,\n                            rename_map,\n                            is_target_table: false,\n                            db_name: table_db_norm,\n                        });\n                    }\n                }\n                let is_target = table_name_matches_target(\n                    &table_name_norm,\n                    table_db_norm.as_deref(),\n                    &ctx.target_table_norm,\n                    &ctx.target_db_norm,\n                );\n                let columns_before = if is_local {\n                    table_source_columns(ctx.schema, &table_name_norm)\n                        .unwrap_or_default()\n                        .into_iter()\n                        .map(|c| normalize_ident(&c))\n                        .collect()\n                } else {\n                    HashSet::default()\n                };\n\n                Ok(ViewSourceInfo {\n                    qualifiers,\n                    columns_before,\n                    rename_map: HashMap::default(),\n                    is_target_table: is_target,\n                    db_name: table_db_norm,\n                })\n            }\n            ast::SelectTable::Select(select, alias) => {\n                let before_cols = select_output_columns(select, ctx, false)?;\n                *changed |=\n                    rewrite_view_select_for_column_rename(select, ctx, &[], visiting_views)?;\n                let after_cols = select_output_columns(select, ctx, true)?;\n                let rename_map = build_rename_map(&before_cols, &after_cols, &ctx.old_column_norm);\n                let qualifiers = alias\n                    .as_ref()\n                    .map(|alias| vec![normalize_ident(alias_name(alias))])\n                    .unwrap_or_default();\n                Ok(ViewSourceInfo {\n                    qualifiers,\n                    columns_before: before_cols\n                        .into_iter()\n                        .map(|c| normalize_ident(&c))\n                        .collect(),\n                    rename_map,\n                    is_target_table: false,\n                    db_name: None,\n                })\n            }\n            ast::SelectTable::Sub(from_clause, alias) => {\n                let before_cols = from_clause_output_columns(from_clause, ctx, false)?;\n                let _ = rewrite_from_clause_for_column_rename(\n                    from_clause,\n                    ctx,\n                    ctes,\n                    outer_scopes,\n                    changed,\n                    visiting_views,\n                )?;\n                let after_cols = from_clause_output_columns(from_clause, ctx, true)?;\n                let rename_map = build_rename_map(&before_cols, &after_cols, &ctx.old_column_norm);\n                let qualifiers = alias\n                    .as_ref()\n                    .map(|alias| vec![normalize_ident(alias_name(alias))])\n                    .unwrap_or_default();\n                Ok(ViewSourceInfo {\n                    qualifiers,\n                    columns_before: before_cols\n                        .into_iter()\n                        .map(|c| normalize_ident(&c))\n                        .collect(),\n                    rename_map,\n                    is_target_table: false,\n                    db_name: None,\n                })\n            }\n            ast::SelectTable::TableCall(_, args, alias) => {\n                for arg in args {\n                    rewrite_expr_in_scope(\n                        arg,\n                        visible_sources,\n                        outer_scopes,\n                        ctx,\n                        changed,\n                        visiting_views,\n                    )?;\n                }\n                let qualifiers = alias\n                    .as_ref()\n                    .map(|alias| vec![normalize_ident(alias_name(alias))])\n                    .unwrap_or_default();\n                Ok(ViewSourceInfo {\n                    qualifiers,\n                    columns_before: HashSet::default(),\n                    rename_map: HashMap::default(),\n                    is_target_table: false,\n                    db_name: None,\n                })\n            }\n        }\n    }\n\n    fn rewrite_expr_in_scope(\n        expr: &mut ast::Expr,\n        sources: &[ViewSourceInfo],\n        outer_scopes: &[&[ViewSourceInfo]],\n        ctx: &ViewRewriteCtx,\n        changed: &mut bool,\n        visiting_views: &mut HashSet<String>,\n    ) -> Result<()> {\n        let mut outer_scopes_for_subqueries: Vec<&[ViewSourceInfo]> =\n            Vec::with_capacity(outer_scopes.len() + 1);\n        if !sources.is_empty() {\n            outer_scopes_for_subqueries.push(sources);\n        }\n        outer_scopes_for_subqueries.extend_from_slice(outer_scopes);\n        walk_expr_mut(expr, &mut |e: &mut ast::Expr| -> Result<WalkControl> {\n            if rewrite_expr_column_ref_view(\n                e,\n                sources,\n                outer_scopes,\n                &ctx.target_db_norm,\n                &ctx.old_column_norm,\n                ctx.new_column,\n            ) {\n                *changed = true;\n            }\n            match e {\n                ast::Expr::Subquery(select) | ast::Expr::Exists(select) => {\n                    if rewrite_view_select_for_column_rename(\n                        select,\n                        ctx,\n                        outer_scopes_for_subqueries.as_slice(),\n                        visiting_views,\n                    )? {\n                        *changed = true;\n                    }\n                }\n                ast::Expr::InSelect { rhs, .. } => {\n                    if rewrite_view_select_for_column_rename(\n                        rhs,\n                        ctx,\n                        outer_scopes_for_subqueries.as_slice(),\n                        visiting_views,\n                    )? {\n                        *changed = true;\n                    }\n                }\n                _ => {}\n            }\n            Ok(WalkControl::Continue)\n        })?;\n        Ok(())\n    }\n\n    fn rewrite_expr_column_ref_view(\n        expr: &mut ast::Expr,\n        sources: &[ViewSourceInfo],\n        outer_scopes: &[&[ViewSourceInfo]],\n        target_db_norm: &str,\n        old_column_norm: &str,\n        new_column: &str,\n    ) -> bool {\n        let apply_rename = |source: &ViewSourceInfo, set_name: &mut dyn FnMut(String)| {\n            if source.is_target_table {\n                set_name(new_column.to_string());\n                return true;\n            }\n            if let Some(mapped) = source.rename_map.get(old_column_norm) {\n                set_name(mapped.to_string());\n                return true;\n            }\n            false\n        };\n\n        match expr {\n            ast::Expr::Qualified(ns, col) => {\n                let ns_norm = normalize_ident(ns.as_str());\n                if !col.as_str().eq_ignore_ascii_case(old_column_norm) {\n                    return false;\n                }\n                let (source, local_ambiguous) =\n                    resolve_qualified(sources, &ns_norm, target_db_norm);\n                if let Some(source) = source {\n                    return apply_rename(source, &mut |name| {\n                        *col = ast::Name::exact(name);\n                    });\n                }\n                if local_ambiguous {\n                    return false;\n                }\n                for scope in outer_scopes {\n                    let (source, ambiguous) = resolve_qualified(scope, &ns_norm, target_db_norm);\n                    if let Some(source) = source {\n                        return apply_rename(source, &mut |name| {\n                            *col = ast::Name::exact(name);\n                        });\n                    }\n                    if ambiguous {\n                        return false;\n                    }\n                }\n            }\n            ast::Expr::DoublyQualified(schema, ns, col) => {\n                let schema_norm = normalize_ident(schema.as_str());\n                if schema_norm != target_db_norm {\n                    return false;\n                }\n                let ns_norm = normalize_ident(ns.as_str());\n                if !col.as_str().eq_ignore_ascii_case(old_column_norm) {\n                    return false;\n                }\n                let (source, local_ambiguous) = resolve_qualified(sources, &ns_norm, &schema_norm);\n                if let Some(source) = source {\n                    return apply_rename(source, &mut |name| {\n                        *col = ast::Name::exact(name);\n                    });\n                }\n                if local_ambiguous {\n                    return false;\n                }\n                for scope in outer_scopes {\n                    let (source, ambiguous) = resolve_qualified(scope, &ns_norm, &schema_norm);\n                    if let Some(source) = source {\n                        return apply_rename(source, &mut |name| {\n                            *col = ast::Name::exact(name);\n                        });\n                    }\n                    if ambiguous {\n                        return false;\n                    }\n                }\n            }\n            ast::Expr::Id(col) | ast::Expr::Name(col) => {\n                if !col.as_str().eq_ignore_ascii_case(old_column_norm) {\n                    return false;\n                }\n                let col_norm = normalize_ident(col.as_str());\n                let (source, local_ambiguous) = resolve_unqualified(sources, &col_norm);\n                if let Some(source) = source {\n                    return apply_rename(source, &mut |name| {\n                        *expr = ast::Expr::Id(ast::Name::exact(name));\n                    });\n                }\n                if local_ambiguous {\n                    return false;\n                }\n                for scope in outer_scopes {\n                    let (source, ambiguous) = resolve_unqualified(scope, &col_norm);\n                    if let Some(source) = source {\n                        return apply_rename(source, &mut |name| {\n                            *expr = ast::Expr::Id(ast::Name::exact(name));\n                        });\n                    }\n                    if ambiguous {\n                        return false;\n                    }\n                }\n            }\n            _ => {}\n        }\n        false\n    }\n\n    fn resolve_unqualified<'a>(\n        candidates: &'a [ViewSourceInfo],\n        old_column_norm: &str,\n    ) -> (Option<&'a ViewSourceInfo>, bool) {\n        let mut matches = candidates\n            .iter()\n            .filter(|s| s.columns_before.contains(old_column_norm));\n        let Some(first) = matches.next() else {\n            return (None, false);\n        };\n        if matches.next().is_some() {\n            return (None, true);\n        }\n        (Some(first), false)\n    }\n\n    fn resolve_qualified<'a>(\n        candidates: &'a [ViewSourceInfo],\n        qualifier: &str,\n        target_db_norm: &str,\n    ) -> (Option<&'a ViewSourceInfo>, bool) {\n        let mut matches = candidates.iter().filter(|s| {\n            s.matches_qualifier(qualifier)\n                && s.db_name.as_deref().is_none_or(|db| db == target_db_norm)\n        });\n        let Some(first) = matches.next() else {\n            return (None, false);\n        };\n        if matches.next().is_some() {\n            return (None, true);\n        }\n        (Some(first), false)\n    }\n\n    fn rewrite_using_columns(\n        cols: &mut [ast::Name],\n        left_sources: &[ViewSourceInfo],\n        right: &ViewSourceInfo,\n        old_column_norm: &str,\n        new_column: &str,\n    ) -> bool {\n        let mut changed = false;\n        let left_map = left_sources\n            .iter()\n            .find_map(|source| source.rename_map.get(old_column_norm));\n        let left_has_target = left_sources.iter().any(|source| source.is_target_table);\n        let right_map = right.rename_map.get(old_column_norm);\n        let should_rename =\n            left_has_target || right.is_target_table || left_map.is_some() || right_map.is_some();\n        if !should_rename {\n            return false;\n        }\n        let replacement = left_map\n            .or(right_map)\n            .map(|s| s.as_str())\n            .unwrap_or(new_column);\n\n        for col in cols {\n            if col.as_str().eq_ignore_ascii_case(old_column_norm) {\n                *col = ast::Name::exact(replacement.to_string());\n                changed = true;\n            }\n        }\n        changed\n    }\n\n    fn select_output_columns(\n        select: &ast::Select,\n        ctx: &ViewRewriteCtx,\n        apply_rename: bool,\n    ) -> Result<Vec<String>> {\n        let view_columns = extract_view_columns(select, ctx.schema)?;\n        let mut columns = view_columns.columns;\n        if apply_rename {\n            let target_norm = ctx.target_table_norm.as_str();\n            for view_column in &mut columns {\n                if view_column.table_index == usize::MAX {\n                    continue;\n                }\n                let table = &view_columns.tables[view_column.table_index];\n                if table_name_matches_target(\n                    &table.name,\n                    table.db_name.as_deref(),\n                    target_norm,\n                    &ctx.target_db_norm,\n                ) {\n                    if let Some(ref mut name) = view_column.column.name {\n                        if name.as_str().eq_ignore_ascii_case(ctx.old_column) {\n                            *name = ctx.new_column.to_string();\n                        }\n                    }\n                }\n            }\n        }\n\n        Ok(columns\n            .into_iter()\n            .map(|vc| vc.column.name.unwrap_or_else(|| \"?\".to_string()))\n            .collect())\n    }\n\n    fn apply_explicit_column_names(columns: &mut [String], explicit: &[ast::IndexedColumn]) {\n        for (i, indexed_col) in explicit.iter().enumerate() {\n            if let Some(col) = columns.get_mut(i) {\n                *col = indexed_col.col_name.to_string();\n            }\n        }\n    }\n\n    fn from_clause_output_columns(\n        from_clause: &ast::FromClause,\n        ctx: &ViewRewriteCtx,\n        apply_rename: bool,\n    ) -> Result<Vec<String>> {\n        let dummy_select = ast::Select {\n            with: None,\n            body: ast::SelectBody {\n                select: ast::OneSelect::Select {\n                    distinctness: None,\n                    columns: vec![ast::ResultColumn::Star],\n                    from: Some(from_clause.clone()),\n                    where_clause: None,\n                    group_by: None,\n                    window_clause: Vec::new(),\n                },\n                compounds: Vec::new(),\n            },\n            order_by: Vec::new(),\n            limit: None,\n        };\n        select_output_columns(&dummy_select, ctx, apply_rename)\n    }\n\n    fn build_rename_map(\n        before_cols: &[String],\n        after_cols: &[String],\n        old_column_norm: &str,\n    ) -> HashMap<String, String> {\n        let mut map = HashMap::default();\n        for (before, after) in before_cols.iter().zip(after_cols.iter()) {\n            if before.as_str().eq_ignore_ascii_case(old_column_norm)\n                && !after.as_str().eq_ignore_ascii_case(before.as_str())\n            {\n                map.insert(old_column_norm.to_string(), after.to_string());\n            }\n        }\n        map\n    }\n\n    fn build_rename_map_from_columns(\n        before_cols: &[Column],\n        after_cols: &[Column],\n        old_column_norm: &str,\n    ) -> HashMap<String, String> {\n        if before_cols.len() != after_cols.len() {\n            return HashMap::default();\n        }\n        let mut map = HashMap::default();\n        for (before, after) in before_cols.iter().zip(after_cols.iter()) {\n            let Some(before_name) = before.name.as_ref() else {\n                continue;\n            };\n            let Some(after_name) = after.name.as_ref() else {\n                continue;\n            };\n            if before_name.as_str().eq_ignore_ascii_case(old_column_norm)\n                && !after_name\n                    .as_str()\n                    .eq_ignore_ascii_case(before_name.as_str())\n            {\n                map.insert(old_column_norm.to_string(), after_name.to_string());\n            }\n        }\n        map\n    }\n\n    fn table_name_matches_target(\n        table_name: &str,\n        table_db: Option<&str>,\n        target_table_norm: &str,\n        target_db_norm: &str,\n    ) -> bool {\n        if !table_name.eq_ignore_ascii_case(target_table_norm) {\n            return false;\n        }\n        match table_db {\n            None => true,\n            Some(db) => db.eq_ignore_ascii_case(target_db_norm),\n        }\n    }\n\n    fn table_source_columns(schema: &Schema, table_name: &str) -> Option<Vec<String>> {\n        if let Some(table) = schema.get_table(table_name) {\n            return Some(\n                table\n                    .columns()\n                    .iter()\n                    .filter_map(|col| col.name.clone())\n                    .collect(),\n            );\n        }\n        let table_norm = normalize_ident(table_name);\n        if let Some(view) = schema.views.get(&table_norm) {\n            return Some(\n                view.columns\n                    .iter()\n                    .filter_map(|col| col.name.clone())\n                    .collect(),\n            );\n        }\n        None\n    }\n}\n\npub use rename_column_view::{rewrite_view_sql_for_column_rename, RewrittenView};\n\n/// Rewrite table-qualified column references in a CHECK constraint expression,\n/// replacing the table name from `from` to `to`. For example, `t1.a > 0` becomes\n/// `t2.a > 0` when renaming t1 to t2. This matches SQLite 3.49.1+ behavior which\n/// rewrites qualified refs during ALTER TABLE RENAME instead of rejecting them.\npub fn rewrite_check_expr_table_refs(expr: &mut ast::Expr, from: &str, to: &str) {\n    let from_normalized = normalize_ident(from);\n    let _ = walk_expr_mut(\n        expr,\n        &mut |e: &mut ast::Expr| -> crate::Result<WalkControl> {\n            if let ast::Expr::Qualified(ref tbl, ref col) = *e {\n                if normalize_ident(tbl.as_str()) == from_normalized {\n                    let col = col.clone();\n                    *e = ast::Expr::Qualified(ast::Name::exact(to.to_owned()), col);\n                }\n            }\n            Ok(WalkControl::Continue)\n        },\n    );\n}\n\n/// Update a column-level REFERENCES <tbl>(col,...) constraint\npub fn rewrite_column_references_if_needed(\n    col: &mut ast::ColumnDefinition,\n    table: &str,\n    from: &str,\n    to: &str,\n) {\n    for cc in &mut col.constraints {\n        match &mut cc.constraint {\n            ast::ColumnConstraint::ForeignKey { clause, .. } => {\n                rewrite_fk_parent_cols_if_self_ref(clause, table, from, to);\n            }\n            ast::ColumnConstraint::Check(expr) => {\n                rename_identifiers(expr, from, to);\n            }\n            _ => {}\n        }\n    }\n}\n\n/// If a FK REFERENCES targets `old_tbl`, change it to `new_tbl`\npub fn rewrite_fk_parent_table_if_needed(\n    clause: &mut ast::ForeignKeyClause,\n    old_tbl: &str,\n    new_tbl: &str,\n) -> bool {\n    if normalize_ident(clause.tbl_name.as_str()) == normalize_ident(old_tbl) {\n        clause.tbl_name = ast::Name::exact(new_tbl.to_owned());\n        return true;\n    }\n    false\n}\n\n/// For inline REFERENCES tbl in a column definition.\npub fn rewrite_inline_col_fk_target_if_needed(\n    col: &mut ast::ColumnDefinition,\n    old_tbl: &str,\n    new_tbl: &str,\n) -> bool {\n    let mut changed = false;\n    for cc in &mut col.constraints {\n        if let ast::NamedColumnConstraint {\n            constraint: ast::ColumnConstraint::ForeignKey { clause, .. },\n            ..\n        } = cc\n        {\n            changed |= rewrite_fk_parent_table_if_needed(clause, old_tbl, new_tbl);\n        }\n    }\n    changed\n}\n\n/// Rewrite table name references inside a trigger's body commands for ALTER TABLE RENAME.\n/// Updates tbl_name fields in INSERT/UPDATE/DELETE commands and table references\n/// in FROM clauses and qualified expressions throughout the trigger body.\npub fn rewrite_trigger_cmd_table_refs(cmd: &mut ast::TriggerCmd, old_tbl: &str, new_tbl: &str) {\n    let old_normalized = normalize_ident(old_tbl);\n    match cmd {\n        ast::TriggerCmd::Update {\n            tbl_name,\n            sets,\n            from,\n            where_clause,\n            ..\n        } => {\n            if normalize_ident(tbl_name.as_str()) == old_normalized {\n                *tbl_name = ast::Name::exact(new_tbl.to_owned());\n            }\n            for set in sets {\n                rewrite_check_expr_table_refs(&mut set.expr, old_tbl, new_tbl);\n            }\n            if let Some(ref mut from) = from {\n                rewrite_from_clause_table_refs(from, old_tbl, new_tbl);\n            }\n            if let Some(ref mut wc) = where_clause {\n                rewrite_check_expr_table_refs(wc, old_tbl, new_tbl);\n            }\n        }\n        ast::TriggerCmd::Insert {\n            tbl_name,\n            select,\n            upsert,\n            ..\n        } => {\n            if normalize_ident(tbl_name.as_str()) == old_normalized {\n                *tbl_name = ast::Name::exact(new_tbl.to_owned());\n            }\n            rewrite_select_table_refs(select, old_tbl, new_tbl);\n            if let Some(ref mut upsert) = upsert {\n                rewrite_upsert_table_refs(upsert, old_tbl, new_tbl);\n            }\n        }\n        ast::TriggerCmd::Delete {\n            tbl_name,\n            where_clause,\n        } => {\n            if normalize_ident(tbl_name.as_str()) == old_normalized {\n                *tbl_name = ast::Name::exact(new_tbl.to_owned());\n            }\n            if let Some(ref mut wc) = where_clause {\n                rewrite_check_expr_table_refs(wc, old_tbl, new_tbl);\n            }\n        }\n        ast::TriggerCmd::Select(select) => {\n            rewrite_select_table_refs(select, old_tbl, new_tbl);\n        }\n    }\n}\n\n/// Scope-aware version of `rewrite_select_column_refs` that checks table qualifiers.\nfn rewrite_select_column_refs_scoped(\n    select: &mut ast::Select,\n    target_table: &str,\n    trigger_table: &str,\n    old_col: &str,\n    new_col: &str,\n) {\n    rewrite_one_select_column_refs_scoped(\n        &mut select.body.select,\n        target_table,\n        trigger_table,\n        old_col,\n        new_col,\n    );\n    for compound in &mut select.body.compounds {\n        rewrite_one_select_column_refs_scoped(\n            &mut compound.select,\n            target_table,\n            trigger_table,\n            old_col,\n            new_col,\n        );\n    }\n    // ORDER BY is in the same scope as the body's FROM\n    let body_from_has_target = match &select.body.select {\n        ast::OneSelect::Select { from, .. } => from_clause_has_target(from, target_table),\n        _ => false,\n    };\n    let target_normalized = normalize_ident(target_table);\n    let trigger_normalized = normalize_ident(trigger_table);\n    let rename_unqualified = body_from_has_target || target_normalized == trigger_normalized;\n    for col in &mut select.order_by {\n        rename_identifiers_scoped_inner(\n            &mut col.expr,\n            target_table,\n            trigger_table,\n            old_col,\n            new_col,\n            rename_unqualified,\n        );\n    }\n}\n\n/// Check if a FROM clause contains a reference to the given table name.\nfn from_clause_has_target(from: &Option<ast::FromClause>, target_table: &str) -> bool {\n    let Some(from_clause) = from else {\n        return false;\n    };\n    let target_normalized = normalize_ident(target_table);\n    let check_table = |st: &ast::SelectTable| -> bool {\n        matches!(\n            st,\n            ast::SelectTable::Table(name, _, _)\n                if normalize_ident(name.name.as_str()) == target_normalized\n        )\n    };\n    if check_table(&from_clause.select) {\n        return true;\n    }\n    from_clause.joins.iter().any(|j| check_table(&j.table))\n}\n\nfn rewrite_one_select_column_refs_scoped(\n    one: &mut ast::OneSelect,\n    target_table: &str,\n    trigger_table: &str,\n    old_col: &str,\n    new_col: &str,\n) {\n    match one {\n        ast::OneSelect::Select {\n            from,\n            where_clause,\n            columns,\n            group_by,\n            ..\n        } => {\n            // Check if FROM clause references the target table to determine\n            // whether unqualified Expr::Id should be renamed in this scope\n            let from_has_target = from_clause_has_target(from, target_table);\n            let target_normalized = normalize_ident(target_table);\n            let trigger_normalized = normalize_ident(trigger_table);\n            let rename_unqualified = from_has_target || target_normalized == trigger_normalized;\n\n            if let Some(ref mut from) = from {\n                rewrite_from_clause_column_refs_scoped(\n                    from,\n                    target_table,\n                    trigger_table,\n                    old_col,\n                    new_col,\n                );\n            }\n            if let Some(ref mut wc) = where_clause {\n                rename_identifiers_scoped_inner(\n                    wc,\n                    target_table,\n                    trigger_table,\n                    old_col,\n                    new_col,\n                    rename_unqualified,\n                );\n            }\n            for col in columns {\n                if let ast::ResultColumn::Expr(ref mut expr, _) = col {\n                    rename_identifiers_scoped_inner(\n                        expr,\n                        target_table,\n                        trigger_table,\n                        old_col,\n                        new_col,\n                        rename_unqualified,\n                    );\n                }\n            }\n            if let Some(ref mut gb) = group_by {\n                for expr in &mut gb.exprs {\n                    rename_identifiers_scoped_inner(\n                        expr,\n                        target_table,\n                        trigger_table,\n                        old_col,\n                        new_col,\n                        rename_unqualified,\n                    );\n                }\n                if let Some(ref mut having) = gb.having {\n                    rename_identifiers_scoped_inner(\n                        having,\n                        target_table,\n                        trigger_table,\n                        old_col,\n                        new_col,\n                        rename_unqualified,\n                    );\n                }\n            }\n        }\n        ast::OneSelect::Values(rows) => {\n            for row in rows {\n                for expr in row {\n                    rename_identifiers_scoped(expr, target_table, trigger_table, old_col, new_col);\n                }\n            }\n        }\n    }\n}\n\nfn rewrite_from_clause_column_refs_scoped(\n    from: &mut ast::FromClause,\n    target_table: &str,\n    trigger_table: &str,\n    old_col: &str,\n    new_col: &str,\n) {\n    // Check if this FROM clause references the target table for JOIN ON expressions\n    let from_has_target = {\n        let target_normalized = normalize_ident(target_table);\n        let check_table = |st: &ast::SelectTable| -> bool {\n            matches!(\n                st,\n                ast::SelectTable::Table(name, _, _)\n                    if normalize_ident(name.name.as_str()) == target_normalized\n            )\n        };\n        check_table(&from.select) || from.joins.iter().any(|j| check_table(&j.table))\n    };\n    let target_normalized = normalize_ident(target_table);\n    let trigger_normalized = normalize_ident(trigger_table);\n    let rename_unqualified = from_has_target || target_normalized == trigger_normalized;\n\n    rewrite_select_table_entry_column_refs_scoped(\n        &mut from.select,\n        target_table,\n        trigger_table,\n        old_col,\n        new_col,\n    );\n    for join in &mut from.joins {\n        rewrite_select_table_entry_column_refs_scoped(\n            &mut join.table,\n            target_table,\n            trigger_table,\n            old_col,\n            new_col,\n        );\n        if let Some(ast::JoinConstraint::On(ref mut expr)) = join.constraint {\n            rename_identifiers_scoped_inner(\n                expr,\n                target_table,\n                trigger_table,\n                old_col,\n                new_col,\n                rename_unqualified,\n            );\n        }\n    }\n}\n\nfn rewrite_select_table_entry_column_refs_scoped(\n    st: &mut ast::SelectTable,\n    target_table: &str,\n    trigger_table: &str,\n    old_col: &str,\n    new_col: &str,\n) {\n    match st {\n        ast::SelectTable::TableCall(_, ref mut args, _) => {\n            for arg in args {\n                rename_identifiers_scoped(arg, target_table, trigger_table, old_col, new_col);\n            }\n        }\n        ast::SelectTable::Select(ref mut select, _) => {\n            rewrite_select_column_refs_scoped(\n                select,\n                target_table,\n                trigger_table,\n                old_col,\n                new_col,\n            );\n        }\n        ast::SelectTable::Sub(ref mut from, _) => {\n            rewrite_from_clause_column_refs_scoped(\n                from,\n                target_table,\n                trigger_table,\n                old_col,\n                new_col,\n            );\n        }\n        ast::SelectTable::Table(..) => {}\n    }\n}\n\nfn rename_excluded_column_refs(expr: &mut ast::Expr, old_col: &str, new_col: &str) {\n    let old_col_normalized = normalize_ident(old_col);\n    let _ = walk_expr_mut(\n        expr,\n        &mut |e: &mut ast::Expr| -> crate::Result<WalkControl> {\n            if let ast::Expr::Qualified(ns, col) | ast::Expr::DoublyQualified(_, ns, col) = e {\n                if normalize_ident(ns.as_str()) == \"excluded\"\n                    && normalize_ident(col.as_str()) == old_col_normalized\n                {\n                    *col = ast::Name::exact(new_col.to_owned());\n                }\n            }\n            Ok(WalkControl::Continue)\n        },\n    );\n}\n\nfn rewrite_upsert_column_refs_scoped(\n    upsert: &mut ast::Upsert,\n    table: &str,\n    trigger_table: &str,\n    insert_table: &str,\n    old_col: &str,\n    new_col: &str,\n) {\n    let insert_targets_renamed_table = normalize_ident(insert_table) == normalize_ident(table);\n    let rewrite_expr = |expr: &mut ast::Expr| {\n        if insert_targets_renamed_table {\n            rename_identifiers_scoped(expr, table, trigger_table, old_col, new_col);\n            rename_excluded_column_refs(expr, old_col, new_col);\n        } else {\n            rename_identifiers_scoped_when_clause(expr, table, trigger_table, old_col, new_col);\n        }\n    };\n\n    if let Some(ref mut index) = upsert.index {\n        for target in &mut index.targets {\n            rewrite_expr(&mut target.expr);\n        }\n        if let Some(ref mut wc) = index.where_clause {\n            rewrite_expr(wc);\n        }\n    }\n    if let ast::UpsertDo::Set {\n        ref mut sets,\n        ref mut where_clause,\n    } = upsert.do_clause\n    {\n        for set in sets {\n            if insert_targets_renamed_table {\n                for col_name in &mut set.col_names {\n                    if normalize_ident(col_name.as_str()) == normalize_ident(old_col) {\n                        *col_name = ast::Name::exact(new_col.to_owned());\n                    }\n                }\n            }\n            rewrite_expr(&mut set.expr);\n        }\n        if let Some(ref mut wc) = where_clause {\n            rewrite_expr(wc);\n        }\n    }\n    if let Some(ref mut next) = upsert.next {\n        rewrite_upsert_column_refs_scoped(\n            next,\n            table,\n            trigger_table,\n            insert_table,\n            old_col,\n            new_col,\n        );\n    }\n}\n\n/// Rewrite column references inside a trigger's body commands for ALTER TABLE RENAME COLUMN.\n/// Uses scope-aware renaming: only renames qualified refs when the qualifier matches\n/// the target table (or NEW/OLD for the trigger's owning table).\npub fn rewrite_trigger_cmd_column_refs(\n    cmd: &mut ast::TriggerCmd,\n    table: &str,\n    trigger_table: &str,\n    old_col: &str,\n    new_col: &str,\n) {\n    let table_normalized = normalize_ident(table);\n    match cmd {\n        ast::TriggerCmd::Update {\n            tbl_name,\n            sets,\n            from,\n            where_clause,\n            ..\n        } => {\n            let cmd_tbl_norm = normalize_ident(tbl_name.as_str());\n            let targets_renamed_table = cmd_tbl_norm == table_normalized;\n            if targets_renamed_table {\n                for set in sets {\n                    for col_name in &mut set.col_names {\n                        if normalize_ident(col_name.as_str()) == normalize_ident(old_col) {\n                            *col_name = ast::Name::exact(new_col.to_owned());\n                        }\n                    }\n                    rename_identifiers_scoped(\n                        &mut set.expr,\n                        table,\n                        trigger_table,\n                        old_col,\n                        new_col,\n                    );\n                }\n                if let Some(ref mut wc) = where_clause {\n                    rename_identifiers_scoped(wc, table, trigger_table, old_col, new_col);\n                }\n            } else {\n                for set in sets {\n                    rename_identifiers_scoped_when_clause(\n                        &mut set.expr,\n                        table,\n                        trigger_table,\n                        old_col,\n                        new_col,\n                    );\n                }\n                if let Some(ref mut wc) = where_clause {\n                    rename_identifiers_scoped_when_clause(\n                        wc,\n                        table,\n                        trigger_table,\n                        old_col,\n                        new_col,\n                    );\n                }\n            }\n            if let Some(ref mut from) = from {\n                rewrite_from_clause_column_refs_scoped(\n                    from,\n                    table,\n                    trigger_table,\n                    old_col,\n                    new_col,\n                );\n            }\n        }\n        ast::TriggerCmd::Insert {\n            tbl_name,\n            col_names,\n            select,\n            upsert,\n            ..\n        } => {\n            let cmd_tbl_norm = normalize_ident(tbl_name.as_str());\n            let targets_renamed_table = cmd_tbl_norm == table_normalized;\n            if targets_renamed_table {\n                for col_name in col_names {\n                    if normalize_ident(col_name.as_str()) == normalize_ident(old_col) {\n                        *col_name = ast::Name::exact(new_col.to_owned());\n                    }\n                }\n            }\n            rewrite_select_column_refs_scoped(select, table, trigger_table, old_col, new_col);\n            if let Some(ref mut upsert) = upsert {\n                rewrite_upsert_column_refs_scoped(\n                    upsert,\n                    table,\n                    trigger_table,\n                    tbl_name.as_str(),\n                    old_col,\n                    new_col,\n                );\n            }\n        }\n        ast::TriggerCmd::Delete {\n            tbl_name,\n            where_clause,\n        } => {\n            let cmd_tbl_norm = normalize_ident(tbl_name.as_str());\n            let targets_renamed_table = cmd_tbl_norm == table_normalized;\n            if targets_renamed_table {\n                if let Some(ref mut wc) = where_clause {\n                    rename_identifiers_scoped(wc, table, trigger_table, old_col, new_col);\n                }\n            } else if let Some(ref mut wc) = where_clause {\n                rename_identifiers_scoped_when_clause(wc, table, trigger_table, old_col, new_col);\n            }\n        }\n        ast::TriggerCmd::Select(select) => {\n            rewrite_select_column_refs_scoped(select, table, trigger_table, old_col, new_col);\n        }\n    }\n}\n\nfn rewrite_select_table_refs(select: &mut ast::Select, old_tbl: &str, new_tbl: &str) {\n    rewrite_one_select_table_refs(&mut select.body.select, old_tbl, new_tbl);\n    for compound in &mut select.body.compounds {\n        rewrite_one_select_table_refs(&mut compound.select, old_tbl, new_tbl);\n    }\n    for col in &mut select.order_by {\n        rewrite_check_expr_table_refs(&mut col.expr, old_tbl, new_tbl);\n    }\n}\n\nfn rewrite_one_select_table_refs(one: &mut ast::OneSelect, old_tbl: &str, new_tbl: &str) {\n    match one {\n        ast::OneSelect::Select {\n            from,\n            where_clause,\n            columns,\n            group_by,\n            ..\n        } => {\n            if let Some(ref mut from) = from {\n                rewrite_from_clause_table_refs(from, old_tbl, new_tbl);\n            }\n            if let Some(ref mut wc) = where_clause {\n                rewrite_check_expr_table_refs(wc, old_tbl, new_tbl);\n            }\n            for col in columns {\n                match col {\n                    ast::ResultColumn::Expr(ref mut expr, _) => {\n                        rewrite_check_expr_table_refs(expr, old_tbl, new_tbl);\n                    }\n                    ast::ResultColumn::TableStar(ref mut name) => {\n                        if normalize_ident(name.as_str()) == normalize_ident(old_tbl) {\n                            *name = ast::Name::exact(new_tbl.to_owned());\n                        }\n                    }\n                    ast::ResultColumn::Star => {}\n                }\n            }\n            if let Some(ref mut gb) = group_by {\n                for expr in &mut gb.exprs {\n                    rewrite_check_expr_table_refs(expr, old_tbl, new_tbl);\n                }\n                if let Some(ref mut having) = gb.having {\n                    rewrite_check_expr_table_refs(having, old_tbl, new_tbl);\n                }\n            }\n        }\n        ast::OneSelect::Values(rows) => {\n            for row in rows {\n                for expr in row {\n                    rewrite_check_expr_table_refs(expr, old_tbl, new_tbl);\n                }\n            }\n        }\n    }\n}\n\nfn rewrite_from_clause_table_refs(from: &mut ast::FromClause, old_tbl: &str, new_tbl: &str) {\n    rewrite_select_table_entry_table_refs(&mut from.select, old_tbl, new_tbl);\n    for join in &mut from.joins {\n        rewrite_select_table_entry_table_refs(&mut join.table, old_tbl, new_tbl);\n        if let Some(ast::JoinConstraint::On(ref mut expr)) = join.constraint {\n            rewrite_check_expr_table_refs(expr, old_tbl, new_tbl);\n        }\n    }\n}\n\nfn rewrite_select_table_entry_table_refs(st: &mut ast::SelectTable, old_tbl: &str, new_tbl: &str) {\n    let old_normalized = normalize_ident(old_tbl);\n    match st {\n        ast::SelectTable::Table(ref mut name, _, _) => {\n            if normalize_ident(name.name.as_str()) == old_normalized {\n                name.name = ast::Name::exact(new_tbl.to_owned());\n            }\n        }\n        ast::SelectTable::TableCall(ref mut name, ref mut args, _) => {\n            if normalize_ident(name.name.as_str()) == old_normalized {\n                name.name = ast::Name::exact(new_tbl.to_owned());\n            }\n            for arg in args {\n                rewrite_check_expr_table_refs(arg, old_tbl, new_tbl);\n            }\n        }\n        ast::SelectTable::Select(ref mut select, _) => {\n            rewrite_select_table_refs(select, old_tbl, new_tbl);\n        }\n        ast::SelectTable::Sub(ref mut from, _) => {\n            rewrite_from_clause_table_refs(from, old_tbl, new_tbl);\n        }\n    }\n}\n\nfn rewrite_upsert_table_refs(upsert: &mut ast::Upsert, old_tbl: &str, new_tbl: &str) {\n    if let Some(ref mut index) = upsert.index {\n        if let Some(ref mut wc) = index.where_clause {\n            rewrite_check_expr_table_refs(wc, old_tbl, new_tbl);\n        }\n    }\n    if let ast::UpsertDo::Set {\n        ref mut sets,\n        ref mut where_clause,\n    } = upsert.do_clause\n    {\n        for set in sets {\n            rewrite_check_expr_table_refs(&mut set.expr, old_tbl, new_tbl);\n        }\n        if let Some(ref mut wc) = where_clause {\n            rewrite_check_expr_table_refs(wc, old_tbl, new_tbl);\n        }\n    }\n    if let Some(ref mut next) = upsert.next {\n        rewrite_upsert_table_refs(next, old_tbl, new_tbl);\n    }\n}\n\n#[cfg(test)]\npub mod tests {\n    use super::*;\n    use crate::schema::{BTreeTable, Type as SchemaValueType};\n    use turso_parser::ast::{self, Expr, FunctionTail, Literal, Name, Operator::*, Type, Variable};\n\n    #[test]\n    fn test_normalize_ident() {\n        assert_eq!(normalize_ident(\"foo\"), \"foo\");\n        assert_eq!(normalize_ident(\"FOO\"), \"foo\");\n        assert_eq!(normalize_ident(\"ὈΔΥΣΣΕΎΣ\"), \"ὀδυσσεύς\");\n    }\n\n    fn schema_with_tables(create_table_sqls: &[&str]) -> Schema {\n        let mut schema = Schema::new();\n        for (index, create_table_sql) in create_table_sqls.iter().enumerate() {\n            let root_page = i64::try_from(index).expect(\"test table index should fit in i64\") + 2;\n            let table = BTreeTable::from_sql(create_table_sql, root_page)\n                .expect(\"test CREATE TABLE should parse\");\n            schema\n                .add_btree_table(std::sync::Arc::new(table))\n                .expect(\"test table should be added to schema\");\n        }\n\n        schema\n    }\n\n    fn schema_with_table(create_table_sql: &str) -> Schema {\n        schema_with_tables(&[create_table_sql])\n    }\n\n    #[test]\n    fn test_rewrite_view_sql_select_table_branch() {\n        let schema = schema_with_table(\"CREATE TABLE t (a, b)\");\n        let view_sql = \"CREATE VIEW v AS SELECT s.x FROM (SELECT b AS x FROM t) AS s\";\n\n        let rewritten =\n            rewrite_view_sql_for_column_rename(view_sql, &schema, \"t\", \"main\", \"b\", \"c\")\n                .unwrap()\n                .expect(\"view should be rewritten\");\n\n        assert!(rewritten.sql.contains(\"SELECT c AS x FROM t\"));\n    }\n\n    #[test]\n    fn test_rewrite_view_sql_sub_branch() {\n        let schema = schema_with_table(\"CREATE TABLE t (a, b)\");\n        let view_sql = \"CREATE VIEW v AS SELECT s.b FROM (t) AS s\";\n\n        let rewritten =\n            rewrite_view_sql_for_column_rename(view_sql, &schema, \"t\", \"main\", \"b\", \"c\")\n                .unwrap()\n                .expect(\"view should be rewritten\");\n\n        assert!(!rewritten.sql.contains(\"s.b\"), \"{}\", rewritten.sql);\n    }\n\n    #[test]\n    fn test_rewrite_view_sql_table_call_branch() {\n        let schema = schema_with_table(\"CREATE TABLE t (a, b)\");\n        let view_sql =\n            \"CREATE VIEW v AS SELECT j.value FROM t JOIN json_each(json_array(t.b)) AS j\";\n\n        let rewritten =\n            rewrite_view_sql_for_column_rename(view_sql, &schema, \"t\", \"main\", \"b\", \"c\")\n                .unwrap()\n                .expect(\"view should be rewritten\");\n\n        assert!(!rewritten.sql.contains(\"t.b\"), \"{}\", rewritten.sql);\n    }\n\n    #[test]\n    fn test_rewrite_view_sql_compound_branch() {\n        let schema = schema_with_table(\"CREATE TABLE t (a, b)\");\n        let view_sql = \"CREATE VIEW v AS SELECT b FROM t UNION ALL SELECT b FROM t ORDER BY b\";\n\n        let rewritten =\n            rewrite_view_sql_for_column_rename(view_sql, &schema, \"t\", \"main\", \"b\", \"c\")\n                .unwrap()\n                .expect(\"view should be rewritten\");\n\n        assert_eq!(rewritten.sql.matches(\"SELECT c FROM t\").count(), 2);\n        assert!(!rewritten.sql.contains(\"ORDER BY b\"), \"{}\", rewritten.sql);\n        assert!(rewritten.sql.contains(\"ORDER BY c\"), \"{}\", rewritten.sql);\n    }\n\n    #[test]\n    fn test_rewrite_view_sql_cte_branch() {\n        let schema = schema_with_table(\"CREATE TABLE t (a, b)\");\n        let view_sql = \"CREATE VIEW v AS WITH cte AS (SELECT b FROM t) SELECT b FROM cte\";\n\n        let rewritten =\n            rewrite_view_sql_for_column_rename(view_sql, &schema, \"t\", \"main\", \"b\", \"c\")\n                .unwrap()\n                .expect(\"view should be rewritten\");\n\n        assert!(\n            rewritten.sql.contains(\"WITH cte AS (SELECT c FROM t)\"),\n            \"{}\",\n            rewritten.sql\n        );\n        assert!(\n            rewritten.sql.contains(\"SELECT c FROM cte\"),\n            \"{}\",\n            rewritten.sql\n        );\n    }\n\n    #[test]\n    fn test_rewrite_view_sql_cte_branch_with_explicit_columns() {\n        let schema = schema_with_table(\"CREATE TABLE t (a, b)\");\n        let view_sql = \"CREATE VIEW v AS WITH cte(x) AS (SELECT b FROM t) SELECT x FROM cte\";\n\n        let rewritten =\n            rewrite_view_sql_for_column_rename(view_sql, &schema, \"t\", \"main\", \"b\", \"c\")\n                .unwrap()\n                .expect(\"view should be rewritten\");\n\n        assert!(\n            rewritten.sql.contains(\"WITH cte(x)\") || rewritten.sql.contains(\"WITH cte (x)\"),\n            \"{}\",\n            rewritten.sql\n        );\n        assert!(\n            rewritten.sql.contains(\"AS (SELECT c FROM t)\"),\n            \"{}\",\n            rewritten.sql\n        );\n        assert!(\n            rewritten.sql.contains(\"SELECT x FROM cte\"),\n            \"{}\",\n            rewritten.sql\n        );\n    }\n\n    #[test]\n    fn test_rewrite_view_sql_join_on_branch() {\n        let schema = schema_with_tables(&[\"CREATE TABLE t (a, b)\", \"CREATE TABLE u (b)\"]);\n        let view_sql = \"CREATE VIEW v AS SELECT t.a FROM t JOIN u ON t.b = u.b\";\n\n        let rewritten =\n            rewrite_view_sql_for_column_rename(view_sql, &schema, \"t\", \"main\", \"b\", \"c\")\n                .unwrap()\n                .expect(\"view should be rewritten\");\n\n        assert!(!rewritten.sql.contains(\"t.b\"), \"{}\", rewritten.sql);\n        assert!(rewritten.sql.contains(\"t.c = u.b\"), \"{}\", rewritten.sql);\n    }\n\n    #[test]\n    fn test_rewrite_view_sql_join_using_branch() {\n        let schema = schema_with_tables(&[\"CREATE TABLE t (a, b)\", \"CREATE TABLE u (b)\"]);\n        let view_sql = \"CREATE VIEW v AS SELECT t.a FROM t JOIN u USING (b)\";\n\n        let rewritten =\n            rewrite_view_sql_for_column_rename(view_sql, &schema, \"t\", \"main\", \"b\", \"c\")\n                .unwrap()\n                .expect(\"view should be rewritten\");\n\n        assert!(\n            !rewritten.sql.contains(\"USING (b)\") && !rewritten.sql.contains(\"USING(b)\"),\n            \"{}\",\n            rewritten.sql\n        );\n        assert!(\n            rewritten.sql.contains(\"USING (c)\") || rewritten.sql.contains(\"USING(c)\"),\n            \"{}\",\n            rewritten.sql\n        );\n    }\n\n    #[test]\n    fn test_rewrite_view_sql_group_by_having_branch() {\n        let schema = schema_with_table(\"CREATE TABLE t (a, b)\");\n        let view_sql = \"CREATE VIEW v AS SELECT b FROM t GROUP BY b HAVING b > 0\";\n\n        let rewritten =\n            rewrite_view_sql_for_column_rename(view_sql, &schema, \"t\", \"main\", \"b\", \"c\")\n                .unwrap()\n                .expect(\"view should be rewritten\");\n\n        assert!(\n            rewritten\n                .sql\n                .contains(\"SELECT c FROM t GROUP BY c HAVING c > 0\"),\n            \"{}\",\n            rewritten.sql\n        );\n    }\n\n    #[test]\n    fn test_rewrite_view_sql_window_clause_branch() {\n        let schema = schema_with_table(\"CREATE TABLE t (a, b)\");\n        let view_sql = \"CREATE VIEW v AS SELECT sum(a) OVER (PARTITION BY b ORDER BY b) FROM t\";\n\n        let rewritten =\n            rewrite_view_sql_for_column_rename(view_sql, &schema, \"t\", \"main\", \"b\", \"c\")\n                .unwrap()\n                .expect(\"view should be rewritten\");\n\n        assert!(\n            !rewritten.sql.contains(\"PARTITION BY b\"),\n            \"{}\",\n            rewritten.sql\n        );\n        assert!(!rewritten.sql.contains(\"ORDER BY b\"), \"{}\", rewritten.sql);\n        assert!(\n            rewritten.sql.contains(\"PARTITION BY c\"),\n            \"{}\",\n            rewritten.sql\n        );\n        assert!(rewritten.sql.contains(\"ORDER BY c\"), \"{}\", rewritten.sql);\n    }\n\n    #[test]\n    fn test_rewrite_view_sql_limit_offset_branch() {\n        let schema = schema_with_table(\"CREATE TABLE t (a, b)\");\n        let view_sql = \"CREATE VIEW v AS SELECT a FROM t LIMIT b OFFSET b\";\n\n        let rewritten =\n            rewrite_view_sql_for_column_rename(view_sql, &schema, \"t\", \"main\", \"b\", \"c\")\n                .unwrap()\n                .expect(\"view should be rewritten\");\n\n        assert!(!rewritten.sql.contains(\"LIMIT b\"), \"{}\", rewritten.sql);\n        assert!(!rewritten.sql.contains(\"OFFSET b\"), \"{}\", rewritten.sql);\n        assert!(rewritten.sql.contains(\"LIMIT c\"), \"{}\", rewritten.sql);\n        assert!(rewritten.sql.contains(\"OFFSET c\"), \"{}\", rewritten.sql);\n    }\n\n    #[test]\n    fn test_rewrite_view_sql_values_branch() {\n        let schema = schema_with_table(\"CREATE TABLE t (a, b)\");\n        let view_sql = \"CREATE VIEW v AS VALUES ((SELECT b FROM t LIMIT 1))\";\n\n        let rewritten =\n            rewrite_view_sql_for_column_rename(view_sql, &schema, \"t\", \"main\", \"b\", \"c\")\n                .unwrap()\n                .expect(\"view should be rewritten\");\n\n        assert!(\n            rewritten.sql.contains(\"VALUES ((SELECT c FROM t LIMIT 1))\"),\n            \"{}\",\n            rewritten.sql\n        );\n    }\n\n    #[test]\n    fn test_indexed_variable_comparison() {\n        let expr1 = Expr::Variable(Variable::indexed(1u32.try_into().unwrap()));\n        let expr2 = Expr::Variable(Variable::indexed(1u32.try_into().unwrap()));\n        assert!(exprs_are_equivalent(&expr1, &expr2));\n    }\n\n    #[test]\n    fn test_named_variable_comparison() {\n        let expr1 = Expr::Variable(Variable::named(\":a\".to_string(), 1u32.try_into().unwrap()));\n        let expr2 = Expr::Variable(Variable::named(\":a\".to_string(), 1u32.try_into().unwrap()));\n        assert!(exprs_are_equivalent(&expr1, &expr2));\n\n        let expr1 = Expr::Variable(Variable::named(\":a\".to_string(), 1u32.try_into().unwrap()));\n        let expr2 = Expr::Variable(Variable::named(\":b\".to_string(), 2u32.try_into().unwrap()));\n        assert!(!exprs_are_equivalent(&expr1, &expr2));\n    }\n\n    #[test]\n    fn test_basic_addition_exprs_are_equivalent() {\n        let expr1 = Expr::Binary(\n            Box::new(Expr::Literal(Literal::Numeric(\"826\".to_string()))),\n            Add,\n            Box::new(Expr::Literal(Literal::Numeric(\"389\".to_string()))),\n        );\n        let expr2 = Expr::Binary(\n            Box::new(Expr::Literal(Literal::Numeric(\"389\".to_string()))),\n            Add,\n            Box::new(Expr::Literal(Literal::Numeric(\"826\".to_string()))),\n        );\n        assert!(exprs_are_equivalent(&expr1, &expr2));\n    }\n\n    #[test]\n    fn test_addition_expressions_equivalent_normalized() {\n        // Same types: 123.0 + 243.0 == 243.0 + 123.0 (commutative)\n        let expr1 = Expr::Binary(\n            Box::new(Expr::Literal(Literal::Numeric(\"123.0\".to_string()))),\n            Add,\n            Box::new(Expr::Literal(Literal::Numeric(\"243.0\".to_string()))),\n        );\n        let expr2 = Expr::Binary(\n            Box::new(Expr::Literal(Literal::Numeric(\"243.0\".to_string()))),\n            Add,\n            Box::new(Expr::Literal(Literal::Numeric(\"123.0\".to_string()))),\n        );\n        assert!(exprs_are_equivalent(&expr1, &expr2));\n\n        // Mixed types are NOT equivalent (different result types)\n        let expr3 = Expr::Binary(\n            Box::new(Expr::Literal(Literal::Numeric(\"123.0\".to_string()))),\n            Add,\n            Box::new(Expr::Literal(Literal::Numeric(\"243\".to_string()))),\n        );\n        let expr4 = Expr::Binary(\n            Box::new(Expr::Literal(Literal::Numeric(\"243.0\".to_string()))),\n            Add,\n            Box::new(Expr::Literal(Literal::Numeric(\"123\".to_string()))),\n        );\n        assert!(!exprs_are_equivalent(&expr3, &expr4));\n    }\n\n    #[test]\n    fn test_subtraction_expressions_not_equivalent() {\n        let expr3 = Expr::Binary(\n            Box::new(Expr::Literal(Literal::Numeric(\"364\".to_string()))),\n            Subtract,\n            Box::new(Expr::Literal(Literal::Numeric(\"22.0\".to_string()))),\n        );\n        let expr4 = Expr::Binary(\n            Box::new(Expr::Literal(Literal::Numeric(\"22.0\".to_string()))),\n            Subtract,\n            Box::new(Expr::Literal(Literal::Numeric(\"364\".to_string()))),\n        );\n        assert!(!exprs_are_equivalent(&expr3, &expr4));\n    }\n\n    #[test]\n    fn test_subtraction_expressions_normalized() {\n        // Same types: 66.0 - 22.0 == 66.0 - 22.0\n        let expr3 = Expr::Binary(\n            Box::new(Expr::Literal(Literal::Numeric(\"66.0\".to_string()))),\n            Subtract,\n            Box::new(Expr::Literal(Literal::Numeric(\"22.0\".to_string()))),\n        );\n        let expr4 = Expr::Binary(\n            Box::new(Expr::Literal(Literal::Numeric(\"66.0\".to_string()))),\n            Subtract,\n            Box::new(Expr::Literal(Literal::Numeric(\"22.0\".to_string()))),\n        );\n        assert!(exprs_are_equivalent(&expr3, &expr4));\n\n        // Mixed types are NOT equivalent\n        let expr5 = Expr::Binary(\n            Box::new(Expr::Literal(Literal::Numeric(\"66.0\".to_string()))),\n            Subtract,\n            Box::new(Expr::Literal(Literal::Numeric(\"22\".to_string()))),\n        );\n        let expr6 = Expr::Binary(\n            Box::new(Expr::Literal(Literal::Numeric(\"66\".to_string()))),\n            Subtract,\n            Box::new(Expr::Literal(Literal::Numeric(\"22.0\".to_string()))),\n        );\n        assert!(!exprs_are_equivalent(&expr5, &expr6));\n    }\n\n    #[test]\n    fn test_expressions_equivalent_case_insensitive_functioncalls() {\n        let func1 = Expr::FunctionCall {\n            name: Name::exact(\"SUM\".to_string()),\n            distinctness: None,\n            args: vec![Expr::Id(Name::exact(\"x\".to_string())).into()],\n            order_by: vec![],\n            filter_over: FunctionTail {\n                filter_clause: None,\n                over_clause: None,\n            },\n        };\n        let func2 = Expr::FunctionCall {\n            name: Name::exact(\"sum\".to_string()),\n            distinctness: None,\n            args: vec![Expr::Id(Name::exact(\"x\".to_string())).into()],\n            order_by: vec![],\n            filter_over: FunctionTail {\n                filter_clause: None,\n                over_clause: None,\n            },\n        };\n        assert!(exprs_are_equivalent(&func1, &func2));\n\n        let func3 = Expr::FunctionCall {\n            name: Name::exact(\"SUM\".to_string()),\n            distinctness: Some(ast::Distinctness::Distinct),\n            args: vec![Expr::Id(Name::exact(\"x\".to_string())).into()],\n            order_by: vec![],\n            filter_over: FunctionTail {\n                filter_clause: None,\n                over_clause: None,\n            },\n        };\n        assert!(!exprs_are_equivalent(&func1, &func3));\n    }\n\n    #[test]\n    fn test_expressions_equivalent_identical_fn_with_distinct() {\n        let sum = Expr::FunctionCall {\n            name: Name::exact(\"SUM\".to_string()),\n            distinctness: None,\n            args: vec![Expr::Id(Name::exact(\"x\".to_string())).into()],\n            order_by: vec![],\n            filter_over: FunctionTail {\n                filter_clause: None,\n                over_clause: None,\n            },\n        };\n        let sum_distinct = Expr::FunctionCall {\n            name: Name::exact(\"SUM\".to_string()),\n            distinctness: Some(ast::Distinctness::Distinct),\n            args: vec![Expr::Id(Name::exact(\"x\".to_string())).into()],\n            order_by: vec![],\n            filter_over: FunctionTail {\n                filter_clause: None,\n                over_clause: None,\n            },\n        };\n        assert!(!exprs_are_equivalent(&sum, &sum_distinct));\n    }\n\n    #[test]\n    fn test_expressions_equivalent_multiplication() {\n        // Same types: 42.0 * 38.0 == 38.0 * 42.0 (commutative)\n        let expr1 = Expr::Binary(\n            Box::new(Expr::Literal(Literal::Numeric(\"42.0\".to_string()))),\n            Multiply,\n            Box::new(Expr::Literal(Literal::Numeric(\"38.0\".to_string()))),\n        );\n        let expr2 = Expr::Binary(\n            Box::new(Expr::Literal(Literal::Numeric(\"38.0\".to_string()))),\n            Multiply,\n            Box::new(Expr::Literal(Literal::Numeric(\"42.0\".to_string()))),\n        );\n        assert!(exprs_are_equivalent(&expr1, &expr2));\n    }\n\n    #[test]\n    fn test_expressions_both_parenthesized_equivalent() {\n        // Same types: (683 + 799) == 799 + 683 (commutative, integers only)\n        let expr1 = Expr::Parenthesized(vec![Expr::Binary(\n            Box::new(Expr::Literal(Literal::Numeric(\"683\".to_string()))),\n            Add,\n            Box::new(Expr::Literal(Literal::Numeric(\"799\".to_string()))),\n        )\n        .into()]);\n        let expr2 = Expr::Binary(\n            Box::new(Expr::Literal(Literal::Numeric(\"799\".to_string()))),\n            Add,\n            Box::new(Expr::Literal(Literal::Numeric(\"683\".to_string()))),\n        );\n        assert!(exprs_are_equivalent(&expr1, &expr2));\n    }\n    #[test]\n    fn test_expressions_parenthesized_equivalent() {\n        let expr7 = Expr::Parenthesized(vec![Expr::Binary(\n            Box::new(Expr::Literal(Literal::Numeric(\"6\".to_string()))),\n            Add,\n            Box::new(Expr::Literal(Literal::Numeric(\"7\".to_string()))),\n        )\n        .into()]);\n        let expr8 = Expr::Binary(\n            Box::new(Expr::Literal(Literal::Numeric(\"6\".to_string()))),\n            Add,\n            Box::new(Expr::Literal(Literal::Numeric(\"7\".to_string()))),\n        );\n        assert!(exprs_are_equivalent(&expr7, &expr8));\n    }\n\n    #[test]\n    fn test_like_expressions_equivalent() {\n        let expr1 = Expr::Like {\n            lhs: Box::new(Expr::Id(Name::exact(\"name\".to_string()))),\n            not: false,\n            op: ast::LikeOperator::Like,\n            rhs: Box::new(Expr::Literal(Literal::String(\"%john%\".to_string()))),\n            escape: Some(Box::new(Expr::Literal(Literal::String(\"\\\\\".to_string())))),\n        };\n        let expr2 = Expr::Like {\n            lhs: Box::new(Expr::Id(Name::exact(\"name\".to_string()))),\n            not: false,\n            op: ast::LikeOperator::Like,\n            rhs: Box::new(Expr::Literal(Literal::String(\"%john%\".to_string()))),\n            escape: Some(Box::new(Expr::Literal(Literal::String(\"\\\\\".to_string())))),\n        };\n        assert!(exprs_are_equivalent(&expr1, &expr2));\n    }\n\n    #[test]\n    fn test_expressions_equivalent_like_escaped() {\n        let expr1 = Expr::Like {\n            lhs: Box::new(Expr::Id(Name::exact(\"name\".to_string()))),\n            not: false,\n            op: ast::LikeOperator::Like,\n            rhs: Box::new(Expr::Literal(Literal::String(\"%john%\".to_string()))),\n            escape: Some(Box::new(Expr::Literal(Literal::String(\"\\\\\".to_string())))),\n        };\n        let expr2 = Expr::Like {\n            lhs: Box::new(Expr::Id(Name::exact(\"name\".to_string()))),\n            not: false,\n            op: ast::LikeOperator::Like,\n            rhs: Box::new(Expr::Literal(Literal::String(\"%john%\".to_string()))),\n            escape: Some(Box::new(Expr::Literal(Literal::String(\"#\".to_string())))),\n        };\n        assert!(!exprs_are_equivalent(&expr1, &expr2));\n    }\n    #[test]\n    fn test_expressions_equivalent_between() {\n        let expr1 = Expr::Between {\n            lhs: Box::new(Expr::Id(Name::exact(\"age\".to_string()))),\n            not: false,\n            start: Box::new(Expr::Literal(Literal::Numeric(\"18\".to_string()))),\n            end: Box::new(Expr::Literal(Literal::Numeric(\"65\".to_string()))),\n        };\n        let expr2 = Expr::Between {\n            lhs: Box::new(Expr::Id(Name::exact(\"age\".to_string()))),\n            not: false,\n            start: Box::new(Expr::Literal(Literal::Numeric(\"18\".to_string()))),\n            end: Box::new(Expr::Literal(Literal::Numeric(\"65\".to_string()))),\n        };\n        assert!(exprs_are_equivalent(&expr1, &expr2));\n\n        // differing BETWEEN bounds\n        let expr3 = Expr::Between {\n            lhs: Box::new(Expr::Id(Name::exact(\"age\".to_string()))),\n            not: false,\n            start: Box::new(Expr::Literal(Literal::Numeric(\"20\".to_string()))),\n            end: Box::new(Expr::Literal(Literal::Numeric(\"65\".to_string()))),\n        };\n        assert!(!exprs_are_equivalent(&expr1, &expr3));\n    }\n    #[test]\n    fn test_cast_exprs_equivalent() {\n        let cast1 = Expr::Cast {\n            expr: Box::new(Expr::Literal(Literal::Numeric(\"123\".to_string()))),\n            type_name: Some(Type {\n                name: \"INTEGER\".to_string(),\n                size: None,\n                array_dimensions: 0,\n            }),\n        };\n\n        let cast2 = Expr::Cast {\n            expr: Box::new(Expr::Literal(Literal::Numeric(\"123\".to_string()))),\n            type_name: Some(Type {\n                name: \"integer\".to_string(),\n                size: None,\n                array_dimensions: 0,\n            }),\n        };\n        assert!(exprs_are_equivalent(&cast1, &cast2));\n    }\n\n    #[test]\n    fn test_ident_equivalency() {\n        assert!(check_ident_equivalency(\"\\\"foo\\\"\", \"foo\"));\n        assert!(check_ident_equivalency(\"[foo]\", \"foo\"));\n        assert!(check_ident_equivalency(\"`FOO`\", \"foo\"));\n        assert!(check_ident_equivalency(\"\\\"foo\\\"\", \"`FOO`\"));\n        assert!(!check_ident_equivalency(\"\\\"foo\\\"\", \"[bar]\"));\n        assert!(!check_ident_equivalency(\"foo\", \"\\\"bar\\\"\"));\n    }\n\n    #[test]\n    fn test_simple_uri() {\n        let uri = \"file:/home/user/db.sqlite\";\n        let opts = OpenOptions::parse(uri).unwrap();\n        assert_eq!(opts.path, \"/home/user/db.sqlite\");\n        assert_eq!(opts.authority, None);\n    }\n\n    #[test]\n    fn test_uri_with_authority() {\n        let uri = \"file://localhost/home/user/db.sqlite\";\n        let opts = OpenOptions::parse(uri).unwrap();\n        assert_eq!(opts.path, \"/home/user/db.sqlite\");\n        assert_eq!(opts.authority, Some(\"localhost\"));\n    }\n\n    #[test]\n    fn test_uri_with_invalid_authority() {\n        let uri = \"file://example.com/home/user/db.sqlite\";\n        let result = OpenOptions::parse(uri);\n        assert!(result.is_err());\n    }\n\n    #[test]\n    fn test_uri_with_query_params() {\n        let uri = \"file:/home/user/db.sqlite?vfs=unix&mode=ro&immutable=1\";\n        let opts = OpenOptions::parse(uri).unwrap();\n        assert_eq!(opts.path, \"/home/user/db.sqlite\");\n        assert_eq!(opts.vfs, Some(\"unix\".to_string()));\n        assert_eq!(opts.mode, OpenMode::ReadOnly);\n        assert!(opts.immutable);\n    }\n\n    #[test]\n    fn test_uri_with_fragment() {\n        let uri = \"file:/home/user/db.sqlite#section1\";\n        let opts = OpenOptions::parse(uri).unwrap();\n        assert_eq!(opts.path, \"/home/user/db.sqlite\");\n    }\n\n    #[test]\n    fn test_uri_with_percent_encoding() {\n        let uri = \"file:/home/user/db%20with%20spaces.sqlite?vfs=unix\";\n        let opts = OpenOptions::parse(uri).unwrap();\n        assert_eq!(opts.path, \"/home/user/db with spaces.sqlite\");\n        assert_eq!(opts.vfs, Some(\"unix\".to_string()));\n    }\n\n    #[test]\n    fn test_uri_without_scheme() {\n        let uri = \"/home/user/db.sqlite\";\n        let result = OpenOptions::parse(uri);\n        assert!(result.is_ok());\n        assert_eq!(result.unwrap().path, \"/home/user/db.sqlite\");\n    }\n\n    #[test]\n    fn test_uri_with_empty_query() {\n        let uri = \"file:/home/user/db.sqlite?\";\n        let opts = OpenOptions::parse(uri).unwrap();\n        assert_eq!(opts.path, \"/home/user/db.sqlite\");\n        assert_eq!(opts.vfs, None);\n    }\n\n    #[test]\n    fn test_uri_with_partial_query() {\n        let uri = \"file:/home/user/db.sqlite?mode=rw\";\n        let opts = OpenOptions::parse(uri).unwrap();\n        assert_eq!(opts.path, \"/home/user/db.sqlite\");\n        assert_eq!(opts.mode, OpenMode::ReadWrite);\n        assert_eq!(opts.vfs, None);\n    }\n\n    #[test]\n    fn test_uri_windows_style_path() {\n        let uri = \"file:///C:/Users/test/db.sqlite\";\n        let opts = OpenOptions::parse(uri).unwrap();\n        assert_eq!(opts.path, \"/C:/Users/test/db.sqlite\");\n    }\n\n    #[test]\n    fn test_uri_with_only_query_params() {\n        let uri = \"file:?mode=memory&cache=shared\";\n        let opts = OpenOptions::parse(uri).unwrap();\n        assert_eq!(opts.path, \"\");\n        assert_eq!(opts.mode, OpenMode::Memory);\n        assert_eq!(opts.cache, CacheMode::Shared);\n    }\n\n    #[test]\n    fn test_uri_with_only_fragment() {\n        let uri = \"file:#fragment\";\n        let opts = OpenOptions::parse(uri).unwrap();\n        assert_eq!(opts.path, \"\");\n    }\n\n    #[test]\n    fn test_uri_with_invalid_scheme() {\n        let uri = \"http:/home/user/db.sqlite\";\n        let result = OpenOptions::parse(uri);\n        assert!(result.is_ok());\n        assert_eq!(result.unwrap().path, \"http:/home/user/db.sqlite\");\n    }\n\n    #[test]\n    fn test_uri_with_multiple_query_params() {\n        let uri = \"file:/home/user/db.sqlite?vfs=unix&mode=rw&cache=private&immutable=0\";\n        let opts = OpenOptions::parse(uri).unwrap();\n        assert_eq!(opts.path, \"/home/user/db.sqlite\");\n        assert_eq!(opts.vfs, Some(\"unix\".to_string()));\n        assert_eq!(opts.mode, OpenMode::ReadWrite);\n        assert_eq!(opts.cache, CacheMode::Private);\n        assert!(!opts.immutable);\n    }\n\n    #[test]\n    fn test_uri_with_unknown_query_param() {\n        let uri = \"file:/home/user/db.sqlite?unknown=param\";\n        let opts = OpenOptions::parse(uri).unwrap();\n        assert_eq!(opts.path, \"/home/user/db.sqlite\");\n        assert_eq!(opts.vfs, None);\n    }\n\n    #[test]\n    fn test_uri_with_multiple_equal_signs() {\n        let uri = \"file:/home/user/db.sqlite?vfs=unix=custom\";\n        let opts = OpenOptions::parse(uri).unwrap();\n        assert_eq!(opts.path, \"/home/user/db.sqlite\");\n        assert_eq!(opts.vfs, Some(\"unix=custom\".to_string()));\n    }\n\n    #[test]\n    fn test_uri_with_trailing_slash() {\n        let uri = \"file:/home/user/db.sqlite/\";\n        let opts = OpenOptions::parse(uri).unwrap();\n        assert_eq!(opts.path, \"/home/user/db.sqlite/\");\n    }\n\n    #[test]\n    fn test_uri_with_encoded_characters_in_query() {\n        let uri = \"file:/home/user/db.sqlite?vfs=unix%20mode\";\n        let opts = OpenOptions::parse(uri).unwrap();\n        assert_eq!(opts.path, \"/home/user/db.sqlite\");\n        assert_eq!(opts.vfs, Some(\"unix mode\".to_string()));\n    }\n\n    #[test]\n    fn test_uri_windows_network_path() {\n        let uri = \"file://server/share/db.sqlite\";\n        let result = OpenOptions::parse(uri);\n        assert!(result.is_err()); // non-localhost authority should fail\n    }\n\n    #[test]\n    fn test_uri_windows_drive_letter_with_slash() {\n        let uri = \"file:///C:/database.sqlite\";\n        let opts = OpenOptions::parse(uri).unwrap();\n        assert_eq!(opts.path, \"/C:/database.sqlite\");\n    }\n\n    #[test]\n    fn test_localhost_with_double_slash_and_no_path() {\n        let uri = \"file://localhost\";\n        let opts = OpenOptions::parse(uri).unwrap();\n        assert_eq!(opts.path, \"\");\n        assert_eq!(opts.authority, Some(\"localhost\"));\n    }\n\n    #[test]\n    fn test_uri_windows_drive_letter_without_slash() {\n        let uri = \"file:///C:/database.sqlite\";\n        let opts = OpenOptions::parse(uri).unwrap();\n        assert_eq!(opts.path, \"/C:/database.sqlite\");\n    }\n\n    #[test]\n    fn test_improper_mode() {\n        // any other mode but ro, rwc, rw, memory should fail per sqlite\n\n        let uri = \"file:data.db?mode=readonly\";\n        let res = OpenOptions::parse(uri);\n        assert!(res.is_err());\n        // including empty\n        let uri = \"file:/home/user/db.sqlite?vfs=&mode=\";\n        let res = OpenOptions::parse(uri);\n        assert!(res.is_err());\n    }\n\n    // Some examples from https://www.sqlite.org/c3ref/open.html#urifilenameexamples\n    #[test]\n    fn test_simple_file_current_dir() {\n        let uri = \"file:data.db\";\n        let opts = OpenOptions::parse(uri).unwrap();\n        assert_eq!(opts.path, \"data.db\");\n        assert_eq!(opts.authority, None);\n        assert_eq!(opts.vfs, None);\n        assert_eq!(opts.mode, OpenMode::ReadWriteCreate);\n    }\n\n    #[test]\n    fn test_simple_file_three_slash() {\n        let uri = \"file:///home/data/data.db\";\n        let opts = OpenOptions::parse(uri).unwrap();\n        assert_eq!(opts.path, \"/home/data/data.db\");\n        assert_eq!(opts.authority, None);\n        assert_eq!(opts.vfs, None);\n        assert_eq!(opts.mode, OpenMode::ReadWriteCreate);\n    }\n\n    #[test]\n    fn test_simple_file_two_slash_localhost() {\n        let uri = \"file://localhost/home/fred/data.db\";\n        let opts = OpenOptions::parse(uri).unwrap();\n        assert_eq!(opts.path, \"/home/fred/data.db\");\n        assert_eq!(opts.authority, Some(\"localhost\"));\n        assert_eq!(opts.vfs, None);\n    }\n\n    #[test]\n    fn test_windows_double_invalid() {\n        let uri = \"file://C:/home/fred/data.db?mode=ro\";\n        let opts = OpenOptions::parse(uri);\n        assert!(opts.is_err());\n    }\n\n    #[test]\n    fn test_simple_file_two_slash() {\n        let uri = \"file:///C:/Documents%20and%20Settings/fred/Desktop/data.db\";\n        let opts = OpenOptions::parse(uri).unwrap();\n        assert_eq!(opts.path, \"/C:/Documents and Settings/fred/Desktop/data.db\");\n        assert_eq!(opts.vfs, None);\n    }\n\n    #[test]\n    fn test_decode_percent_basic() {\n        assert_eq!(decode_percent(\"hello%20world\"), \"hello world\");\n        assert_eq!(decode_percent(\"file%3Adata.db\"), \"file:data.db\");\n        assert_eq!(decode_percent(\"path%2Fto%2Ffile\"), \"path/to/file\");\n    }\n\n    #[test]\n    fn test_decode_percent_edge_cases() {\n        assert_eq!(decode_percent(\"\"), \"\");\n        assert_eq!(decode_percent(\"plain_text\"), \"plain_text\");\n        assert_eq!(\n            decode_percent(\"%2Fhome%2Fuser%2Fdb.sqlite\"),\n            \"/home/user/db.sqlite\"\n        );\n        // multiple percent-encoded characters in sequence\n        assert_eq!(decode_percent(\"%41%42%43\"), \"ABC\");\n        assert_eq!(decode_percent(\"%61%62%63\"), \"abc\");\n    }\n\n    #[test]\n    fn test_decode_percent_invalid_sequences() {\n        // invalid percent encoding (single % without two hex digits)\n        assert_eq!(decode_percent(\"hello%\"), \"hello%\");\n        // only one hex digit after %\n        assert_eq!(decode_percent(\"file%2\"), \"file%2\");\n        // invalid hex digits (not 0-9, A-F, a-f)\n        assert_eq!(decode_percent(\"file%2X.db\"), \"file%2X.db\");\n\n        // Incomplete sequence at the end, leave untouched\n        assert_eq!(decode_percent(\"path%2Fto%2\"), \"path/to%2\");\n    }\n\n    #[test]\n    fn test_decode_percent_mixed_valid_invalid() {\n        assert_eq!(decode_percent(\"hello%20world%\"), \"hello world%\");\n        assert_eq!(decode_percent(\"%2Fpath%2Xto%2Ffile\"), \"/path%2Xto/file\");\n        assert_eq!(decode_percent(\"file%3Adata.db%2\"), \"file:data.db%2\");\n    }\n\n    #[test]\n    fn test_decode_percent_special_characters() {\n        assert_eq!(\n            decode_percent(\"%21%40%23%24%25%5E%26%2A%28%29\"),\n            \"!@#$%^&*()\"\n        );\n        assert_eq!(decode_percent(\"%5B%5D%7B%7D%7C%5C%3A\"), \"[]{}|\\\\:\");\n    }\n\n    #[test]\n    fn test_decode_percent_unmodified_valid_text() {\n        // ensure already valid text remains unchanged\n        assert_eq!(\n            decode_percent(\"C:/Users/Example/Database.sqlite\"),\n            \"C:/Users/Example/Database.sqlite\"\n        );\n        assert_eq!(\n            decode_percent(\"/home/user/db.sqlite\"),\n            \"/home/user/db.sqlite\"\n        );\n    }\n\n    #[test]\n    fn test_text_to_integer() {\n        assert_eq!(\n            checked_cast_text_to_numeric(\"1\", false).unwrap(),\n            Value::from_i64(1)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"-1\", false).unwrap(),\n            Value::from_i64(-1)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"1823400-00000\", false).unwrap(),\n            Value::from_i64(1823400)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"-10000000\", false).unwrap(),\n            Value::from_i64(-10000000)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"123xxx\", false).unwrap(),\n            Value::from_i64(123)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"9223372036854775807\", false).unwrap(),\n            Value::from_i64(i64::MAX)\n        );\n        // Overflow becomes Float (different from cast_text_to_integer which returned 0)\n        assert_eq!(\n            checked_cast_text_to_numeric(\"9223372036854775808\", false).unwrap(),\n            Value::from_f64(9.22337203685478e18)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"-9223372036854775808\", false).unwrap(),\n            Value::from_i64(i64::MIN)\n        );\n        // Overflow becomes Float (different from cast_text_to_integer which returned 0)\n        assert_eq!(\n            checked_cast_text_to_numeric(\"-9223372036854775809\", false).unwrap(),\n            Value::from_f64(-9.22337203685478e18)\n        );\n        assert!(checked_cast_text_to_numeric(\"-\", false).is_err());\n    }\n\n    #[test]\n    fn test_text_to_real() {\n        assert_eq!(\n            checked_cast_text_to_numeric(\"1\", false).unwrap(),\n            Value::from_i64(1)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"-1\", false).unwrap(),\n            Value::from_i64(-1)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"1.0\", false).unwrap(),\n            Value::from_f64(1.0)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"-1.0\", false).unwrap(),\n            Value::from_f64(-1.0)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"1e10\", false).unwrap(),\n            Value::from_f64(1e10)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"-1e10\", false).unwrap(),\n            Value::from_f64(-1e10)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"1e-10\", false).unwrap(),\n            Value::from_f64(1e-10)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"-1e-10\", false).unwrap(),\n            Value::from_f64(-1e-10)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"1.123e10\", false).unwrap(),\n            Value::from_f64(1.123e10)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"-1.123e10\", false).unwrap(),\n            Value::from_f64(-1.123e10)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"1.123e-10\", false).unwrap(),\n            Value::from_f64(1.123e-10)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"-1.123-e-10\", false).unwrap(),\n            Value::from_f64(-1.123)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"1-282584294928\", false).unwrap(),\n            Value::from_i64(1)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"1.7976931348623157e309\", false).unwrap(),\n            Value::from_f64(f64::INFINITY),\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"-1.7976931348623157e308\", false).unwrap(),\n            Value::from_f64(f64::MIN),\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"-1.7976931348623157e309\", false).unwrap(),\n            Value::from_f64(f64::NEG_INFINITY),\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"1E\", false).unwrap(),\n            Value::from_f64(1.0)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"1EE\", false).unwrap(),\n            Value::from_f64(1.0)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"-1E\", false).unwrap(),\n            Value::from_f64(-1.0)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"1.\", false).unwrap(),\n            Value::from_f64(1.0)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"-1.\", false).unwrap(),\n            Value::from_f64(-1.0)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"1.23E\", false).unwrap(),\n            Value::from_f64(1.23)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\".1.23E-\", false).unwrap(),\n            Value::from_f64(0.1)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"0\", false).unwrap(),\n            Value::from_i64(0)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"-0\", false).unwrap(),\n            Value::from_i64(0)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"-0\", false).unwrap(),\n            Value::from_i64(0)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"-0.0\", false).unwrap(),\n            Value::from_f64(0.0)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"0.0\", false).unwrap(),\n            Value::from_f64(0.0)\n        );\n        assert!(checked_cast_text_to_numeric(\"-\", false).is_err());\n    }\n\n    #[test]\n    fn test_text_to_numeric() {\n        assert_eq!(\n            checked_cast_text_to_numeric(\"1\", false).unwrap(),\n            Value::from_i64(1)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"-1\", false).unwrap(),\n            Value::from_i64(-1)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"1823400-00000\", false).unwrap(),\n            Value::from_i64(1823400)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"-10000000\", false).unwrap(),\n            Value::from_i64(-10000000)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"123xxx\", false).unwrap(),\n            Value::from_i64(123)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"9223372036854775807\", false).unwrap(),\n            Value::from_i64(i64::MAX)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"9223372036854775808\", false).unwrap(),\n            Value::from_f64(9.22337203685478e18)\n        ); // Exceeds i64, becomes float\n        assert_eq!(\n            checked_cast_text_to_numeric(\"-9223372036854775808\", false).unwrap(),\n            Value::from_i64(i64::MIN)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"-9223372036854775809\", false).unwrap(),\n            Value::from_f64(-9.22337203685478e18)\n        ); // Exceeds i64, becomes float\n\n        assert_eq!(\n            checked_cast_text_to_numeric(\"1.0\", false).unwrap(),\n            Value::from_f64(1.0)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"-1.0\", false).unwrap(),\n            Value::from_f64(-1.0)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"1e10\", false).unwrap(),\n            Value::from_f64(1e10)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"-1e10\", false).unwrap(),\n            Value::from_f64(-1e10)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"1e-10\", false).unwrap(),\n            Value::from_f64(1e-10)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"-1e-10\", false).unwrap(),\n            Value::from_f64(-1e-10)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"1.123e10\", false).unwrap(),\n            Value::from_f64(1.123e10)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"-1.123e10\", false).unwrap(),\n            Value::from_f64(-1.123e10)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"1.123e-10\", false).unwrap(),\n            Value::from_f64(1.123e-10)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"-1.123-e-10\", false).unwrap(),\n            Value::from_f64(-1.123)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"1-282584294928\", false).unwrap(),\n            Value::from_i64(1)\n        );\n        assert!(checked_cast_text_to_numeric(\"xxx\", false).is_err());\n        assert_eq!(\n            checked_cast_text_to_numeric(\"1.7976931348623157e309\", false).unwrap(),\n            Value::from_f64(f64::INFINITY)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"-1.7976931348623157e308\", false).unwrap(),\n            Value::from_f64(f64::MIN)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"-1.7976931348623157e309\", false).unwrap(),\n            Value::from_f64(f64::NEG_INFINITY)\n        );\n\n        assert_eq!(\n            checked_cast_text_to_numeric(\"1E\", false).unwrap(),\n            Value::from_f64(1.0)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"1EE\", false).unwrap(),\n            Value::from_f64(1.0)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"-1E\", false).unwrap(),\n            Value::from_f64(-1.0)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"1.\", false).unwrap(),\n            Value::from_f64(1.0)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"-1.\", false).unwrap(),\n            Value::from_f64(-1.0)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"1.23E\", false).unwrap(),\n            Value::from_f64(1.23)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"1.23E-\", false).unwrap(),\n            Value::from_f64(1.23)\n        );\n\n        assert_eq!(\n            checked_cast_text_to_numeric(\"0\", false).unwrap(),\n            Value::from_i64(0)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"-0\", false).unwrap(),\n            Value::from_i64(0)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"-0.0\", false).unwrap(),\n            Value::from_f64(0.0)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"0.0\", false).unwrap(),\n            Value::from_f64(0.0)\n        );\n        assert!(checked_cast_text_to_numeric(\"-\", false).is_err());\n        assert_eq!(\n            checked_cast_text_to_numeric(\"-e\", false).unwrap(),\n            Value::from_f64(0.0)\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"-E\", false).unwrap(),\n            Value::from_f64(0.0)\n        );\n    }\n\n    #[test]\n    fn test_parse_numeric_str_valid_integer() {\n        assert_eq!(parse_numeric_str(\"123\"), Ok((ValueType::Integer, \"123\")));\n        assert_eq!(parse_numeric_str(\"-456\"), Ok((ValueType::Integer, \"-456\")));\n        assert_eq!(parse_numeric_str(\"+789\"), Ok((ValueType::Integer, \"+789\")));\n        assert_eq!(\n            parse_numeric_str(\"000789\"),\n            Ok((ValueType::Integer, \"000789\"))\n        );\n    }\n\n    #[test]\n    fn test_parse_numeric_str_valid_float() {\n        assert_eq!(\n            parse_numeric_str(\"123.456\"),\n            Ok((ValueType::Float, \"123.456\"))\n        );\n        assert_eq!(\n            parse_numeric_str(\"-0.789\"),\n            Ok((ValueType::Float, \"-0.789\"))\n        );\n        assert_eq!(\n            parse_numeric_str(\"+0.789\"),\n            Ok((ValueType::Float, \"+0.789\"))\n        );\n        assert_eq!(parse_numeric_str(\"1e10\"), Ok((ValueType::Float, \"1e10\")));\n        assert_eq!(parse_numeric_str(\"+1e10\"), Ok((ValueType::Float, \"+1e10\")));\n        assert_eq!(\n            parse_numeric_str(\"-1.23e-4\"),\n            Ok((ValueType::Float, \"-1.23e-4\"))\n        );\n        assert_eq!(\n            parse_numeric_str(\"1.23E+4\"),\n            Ok((ValueType::Float, \"1.23E+4\"))\n        );\n        assert_eq!(parse_numeric_str(\"1.2.3\"), Ok((ValueType::Float, \"1.2\")))\n    }\n\n    #[test]\n    fn test_parse_numeric_str_edge_cases() {\n        assert_eq!(parse_numeric_str(\"1e\"), Ok((ValueType::Float, \"1\")));\n        assert_eq!(parse_numeric_str(\"1e-\"), Ok((ValueType::Float, \"1\")));\n        assert_eq!(parse_numeric_str(\"1e+\"), Ok((ValueType::Float, \"1\")));\n        assert_eq!(parse_numeric_str(\"-1e\"), Ok((ValueType::Float, \"-1\")));\n        assert_eq!(parse_numeric_str(\"-1e-\"), Ok((ValueType::Float, \"-1\")));\n    }\n\n    #[test]\n    fn test_parse_numeric_str_invalid() {\n        assert_eq!(parse_numeric_str(\"\"), Err(()));\n        assert_eq!(parse_numeric_str(\"abc\"), Err(()));\n        assert_eq!(parse_numeric_str(\"-\"), Err(()));\n        assert_eq!(parse_numeric_str(\"+\"), Err(()));\n        assert_eq!(parse_numeric_str(\"e10\"), Err(()));\n        assert_eq!(parse_numeric_str(\".e10\"), Err(()));\n    }\n\n    #[test]\n    fn test_parse_numeric_str_with_whitespace() {\n        assert_eq!(parse_numeric_str(\"   123\"), Ok((ValueType::Integer, \"123\")));\n        assert_eq!(\n            parse_numeric_str(\"  -456.78  \"),\n            Ok((ValueType::Float, \"-456.78\"))\n        );\n        assert_eq!(\n            parse_numeric_str(\"  1.23e4  \"),\n            Ok((ValueType::Float, \"1.23e4\"))\n        );\n    }\n\n    #[test]\n    fn test_parse_numeric_str_leading_zeros() {\n        assert_eq!(\n            parse_numeric_str(\"000123\"),\n            Ok((ValueType::Integer, \"000123\"))\n        );\n        assert_eq!(\n            parse_numeric_str(\"000.456\"),\n            Ok((ValueType::Float, \"000.456\"))\n        );\n        assert_eq!(\n            parse_numeric_str(\"0001e3\"),\n            Ok((ValueType::Float, \"0001e3\"))\n        );\n    }\n\n    #[test]\n    fn test_parse_numeric_str_trailing_characters() {\n        assert_eq!(parse_numeric_str(\"123abc\"), Ok((ValueType::Integer, \"123\")));\n        assert_eq!(\n            parse_numeric_str(\"456.78xyz\"),\n            Ok((ValueType::Float, \"456.78\"))\n        );\n        assert_eq!(\n            parse_numeric_str(\"1.23e4extra\"),\n            Ok((ValueType::Float, \"1.23e4\"))\n        );\n    }\n\n    #[test]\n    fn test_module_name_basic() {\n        let sql = \"CREATE VIRTUAL TABLE x USING y;\";\n        assert_eq!(module_name_from_sql(sql).unwrap(), \"y\");\n    }\n\n    #[test]\n    fn test_module_name_with_args() {\n        let sql = \"CREATE VIRTUAL TABLE x USING modname('a', 'b');\";\n        assert_eq!(module_name_from_sql(sql).unwrap(), \"modname\");\n    }\n\n    #[test]\n    fn test_module_name_missing_using() {\n        let sql = \"CREATE VIRTUAL TABLE x (a, b);\";\n        assert!(module_name_from_sql(sql).is_err());\n    }\n\n    #[test]\n    fn test_module_name_no_semicolon() {\n        let sql = \"CREATE VIRTUAL TABLE x USING limbo(a, b)\";\n        assert_eq!(module_name_from_sql(sql).unwrap(), \"limbo\");\n    }\n\n    #[test]\n    fn test_module_name_no_semicolon_or_args() {\n        let sql = \"CREATE VIRTUAL TABLE x USING limbo\";\n        assert_eq!(module_name_from_sql(sql).unwrap(), \"limbo\");\n    }\n\n    #[test]\n    fn test_module_args_none() {\n        let sql = \"CREATE VIRTUAL TABLE x USING modname;\";\n        let args = module_args_from_sql(sql).unwrap();\n        assert_eq!(args.len(), 0);\n    }\n\n    #[test]\n    fn test_module_args_basic() {\n        let sql = \"CREATE VIRTUAL TABLE x USING modname('arg1', 'arg2');\";\n        let args = module_args_from_sql(sql).unwrap();\n        assert_eq!(args.len(), 2);\n        assert_eq!(\"arg1\", args[0].to_text().unwrap());\n        assert_eq!(\"arg2\", args[1].to_text().unwrap());\n        for arg in args {\n            unsafe { arg.__free_internal_type() }\n        }\n    }\n\n    #[test]\n    fn test_module_args_with_escaped_quote() {\n        let sql = \"CREATE VIRTUAL TABLE x USING modname('a''b', 'c');\";\n        let args = module_args_from_sql(sql).unwrap();\n        assert_eq!(args.len(), 2);\n        assert_eq!(args[0].to_text().unwrap(), \"a'b\");\n        assert_eq!(args[1].to_text().unwrap(), \"c\");\n        for arg in args {\n            unsafe { arg.__free_internal_type() }\n        }\n    }\n\n    #[test]\n    fn test_module_args_unterminated_string() {\n        let sql = \"CREATE VIRTUAL TABLE x USING modname('arg1, 'arg2');\";\n        assert!(module_args_from_sql(sql).is_err());\n    }\n\n    #[test]\n    fn test_module_args_extra_garbage_after_quote() {\n        let sql = \"CREATE VIRTUAL TABLE x USING modname('arg1'x);\";\n        assert!(module_args_from_sql(sql).is_err());\n    }\n\n    #[test]\n    fn test_module_args_trailing_comma() {\n        let sql = \"CREATE VIRTUAL TABLE x USING modname('arg1',);\";\n        let args = module_args_from_sql(sql).unwrap();\n        assert_eq!(args.len(), 1);\n        assert_eq!(\"arg1\", args[0].to_text().unwrap());\n        for arg in args {\n            unsafe { arg.__free_internal_type() }\n        }\n    }\n\n    #[test]\n    fn test_parse_numeric_literal_hex() {\n        assert_eq!(\n            parse_numeric_literal(\"0x1234\").unwrap(),\n            Value::from_i64(4660)\n        );\n        assert_eq!(\n            parse_numeric_literal(\"0xFFFFFFFF\").unwrap(),\n            Value::from_i64(4294967295)\n        );\n        assert_eq!(\n            parse_numeric_literal(\"0x7FFFFFFF\").unwrap(),\n            Value::from_i64(2147483647)\n        );\n        assert_eq!(\n            parse_numeric_literal(\"0x7FFFFFFFFFFFFFFF\").unwrap(),\n            Value::from_i64(9223372036854775807)\n        );\n        assert_eq!(\n            parse_numeric_literal(\"0xFFFFFFFFFFFFFFFF\").unwrap(),\n            Value::from_i64(-1)\n        );\n        assert_eq!(\n            parse_numeric_literal(\"0x8000000000000000\").unwrap(),\n            Value::from_i64(-9223372036854775808)\n        );\n\n        assert_eq!(\n            parse_numeric_literal(\"-0x1234\").unwrap(),\n            Value::from_i64(-4660)\n        );\n        // too big hex\n        assert!(parse_numeric_literal(\"-0x8000000000000000\").is_err());\n    }\n\n    #[test]\n    fn test_parse_numeric_literal_integer() {\n        assert_eq!(parse_numeric_literal(\"123\").unwrap(), Value::from_i64(123));\n        assert_eq!(\n            parse_numeric_literal(\"9_223_372_036_854_775_807\").unwrap(),\n            Value::from_i64(9223372036854775807)\n        );\n    }\n\n    #[test]\n    fn test_parse_numeric_literal_float() {\n        assert_eq!(\n            parse_numeric_literal(\"123.456\").unwrap(),\n            Value::from_f64(123.456)\n        );\n        assert_eq!(\n            parse_numeric_literal(\".123\").unwrap(),\n            Value::from_f64(0.123)\n        );\n        assert_eq!(\n            parse_numeric_literal(\"1.23e10\").unwrap(),\n            Value::from_f64(1.23e10)\n        );\n        assert_eq!(\n            parse_numeric_literal(\"1e-10\").unwrap(),\n            Value::from_f64(1e-10)\n        );\n        assert_eq!(\n            parse_numeric_literal(\"1.23E+10\").unwrap(),\n            Value::from_f64(1.23e10)\n        );\n        assert_eq!(\n            parse_numeric_literal(\"1.1_1\").unwrap(),\n            Value::from_f64(1.11)\n        );\n\n        // > i64::MAX, convert to float\n        assert_eq!(\n            parse_numeric_literal(\"9223372036854775808\").unwrap(),\n            Value::from_f64(9.223_372_036_854_776e18)\n        );\n        // < i64::MIN, convert to float\n        assert_eq!(\n            parse_numeric_literal(\"-9223372036854775809\").unwrap(),\n            Value::from_f64(-9.223_372_036_854_776e18)\n        );\n    }\n\n    #[test]\n    fn test_parse_pragma_bool() {\n        assert!(parse_pragma_bool(&Expr::Literal(Literal::Numeric(\"1\".into()))).unwrap(),);\n        assert!(parse_pragma_bool(&Expr::Name(Name::exact(\"true\".into()))).unwrap(),);\n        assert!(parse_pragma_bool(&Expr::Name(Name::exact(\"on\".into()))).unwrap(),);\n        assert!(parse_pragma_bool(&Expr::Name(Name::exact(\"yes\".into()))).unwrap(),);\n\n        assert!(!parse_pragma_bool(&Expr::Literal(Literal::Numeric(\"0\".into()))).unwrap(),);\n        assert!(!parse_pragma_bool(&Expr::Name(Name::exact(\"false\".into()))).unwrap(),);\n        assert!(!parse_pragma_bool(&Expr::Name(Name::exact(\"off\".into()))).unwrap(),);\n        assert!(!parse_pragma_bool(&Expr::Name(Name::exact(\"no\".into()))).unwrap(),);\n\n        assert!(parse_pragma_bool(&Expr::Name(Name::exact(\"nono\".into()))).is_err());\n        assert!(parse_pragma_bool(&Expr::Name(Name::exact(\"10\".into()))).is_err());\n        assert!(parse_pragma_bool(&Expr::Name(Name::exact(\"-1\".into()))).is_err());\n    }\n\n    #[test]\n    fn test_type_from_name() {\n        let tc = vec![\n            (\"\", (SchemaValueType::Blob, false)),\n            (\"INTEGER\", (SchemaValueType::Integer, true)),\n            (\"INT\", (SchemaValueType::Integer, false)),\n            (\"CHAR\", (SchemaValueType::Text, false)),\n            (\"CLOB\", (SchemaValueType::Text, false)),\n            (\"TEXT\", (SchemaValueType::Text, false)),\n            (\"BLOB\", (SchemaValueType::Blob, false)),\n            (\"REAL\", (SchemaValueType::Real, false)),\n            (\"FLOAT\", (SchemaValueType::Real, false)),\n            (\"DOUBLE\", (SchemaValueType::Real, false)),\n            (\"U128\", (SchemaValueType::Numeric, false)),\n        ];\n\n        for (input, expected) in tc {\n            let result = type_from_name(input);\n            assert_eq!(result, expected, \"Failed for input: {input}\");\n        }\n    }\n\n    #[test]\n    fn test_checked_cast_text_to_numeric_lossless_property() {\n        assert_eq!(checked_cast_text_to_numeric(\"1.xx\", true), Err(()));\n        assert_eq!(checked_cast_text_to_numeric(\"abc\", true), Err(()));\n        assert_eq!(checked_cast_text_to_numeric(\"--5\", true), Err(()));\n        assert_eq!(checked_cast_text_to_numeric(\"12.34.56\", true), Err(()));\n        assert_eq!(checked_cast_text_to_numeric(\"\", true), Err(()));\n        assert_eq!(checked_cast_text_to_numeric(\" \", true), Err(()));\n        assert_eq!(\n            checked_cast_text_to_numeric(\"0\", true),\n            Ok(Value::from_i64(0))\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"42\", true),\n            Ok(Value::from_i64(42))\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"-42\", true),\n            Ok(Value::from_i64(-42))\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"999999999999\", true),\n            Ok(Value::from_i64(999_999_999_999))\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"1.0\", true),\n            Ok(Value::from_f64(1.0))\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"-3.22\", true),\n            Ok(Value::from_f64(-3.22))\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"0.001\", true),\n            Ok(Value::from_f64(0.001))\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"2e3\", true),\n            Ok(Value::from_f64(2000.0))\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"-5.5e-2\", true),\n            Ok(Value::from_f64(-0.055))\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\" 123 \", true),\n            Ok(Value::from_i64(123))\n        );\n        assert_eq!(\n            checked_cast_text_to_numeric(\"\\t-3.22\\n\", true),\n            Ok(Value::from_f64(-3.22))\n        );\n    }\n\n    #[test]\n    fn test_trim_ascii_whitespace_helper() {\n        assert_eq!(trim_ascii_whitespace(\"  hello  \"), \"hello\");\n        assert_eq!(trim_ascii_whitespace(\"\\t\\nhello\\r\\n\"), \"hello\");\n        assert_eq!(trim_ascii_whitespace(\"hello\"), \"hello\");\n        assert_eq!(trim_ascii_whitespace(\"   \"), \"\");\n        assert_eq!(trim_ascii_whitespace(\"\"), \"\");\n\n        // non-breaking space should NOT be trimmed\n        assert_eq!(\n            trim_ascii_whitespace(\"\\u{00A0}hello\\u{00A0}\"),\n            \"\\u{00A0}hello\\u{00A0}\"\n        );\n        assert_eq!(\n            trim_ascii_whitespace(\"  \\u{00A0}hello\\u{00A0}  \"),\n            \"\\u{00A0}hello\\u{00A0}\"\n        );\n    }\n\n    #[test]\n    fn test_cast_real_to_integer_limits() {\n        // Values that are exactly representable in f64 and strictly within i64 range\n        let max_exact = ((1i64 << 51) - 1) as f64;\n        assert_eq!(cast_real_to_integer(max_exact), Ok((1i64 << 51) - 1));\n        assert_eq!(cast_real_to_integer(-max_exact), Ok(-((1i64 << 51) - 1)));\n\n        // Values beyond 2^51 are valid if they round-trip correctly and are strictly within bounds\n        assert_eq!(cast_real_to_integer((1i64 << 51) as f64), Ok(1i64 << 51));\n        assert_eq!(cast_real_to_integer((1i64 << 52) as f64), Ok(1i64 << 52));\n\n        // 2^62 round-trips correctly and is strictly between i64::MIN and i64::MAX\n        assert_eq!(cast_real_to_integer((1i64 << 62) as f64), Ok(1i64 << 62));\n\n        // The original bug's value: 426601719749026560 should work\n        assert_eq!(\n            cast_real_to_integer(426601719749026560.0),\n            Ok(426601719749026560)\n        );\n\n        // SQLite rejects boundary values: i64::MIN and i64::MAX exactly\n        // (ix > SMALLEST_INT64 && ix < LARGEST_INT64 requires STRICT inequality)\n        assert_eq!(cast_real_to_integer(i64::MIN as f64), Err(()));\n        assert_eq!(cast_real_to_integer(i64::MAX as f64), Err(()));\n\n        // Values at or beyond i64::MAX + 1 (2^63) should fail\n        assert_eq!(cast_real_to_integer(9223372036854775808.0), Err(()));\n\n        // Values below i64::MIN should fail\n        assert_eq!(cast_real_to_integer(-9223372036854777856.0), Err(()));\n\n        // Non-whole numbers should fail\n        assert_eq!(cast_real_to_integer(1.5), Err(()));\n        assert_eq!(cast_real_to_integer(-1.5), Err(()));\n\n        // Non-finite values should fail\n        assert_eq!(cast_real_to_integer(f64::INFINITY), Err(()));\n        assert_eq!(cast_real_to_integer(f64::NEG_INFINITY), Err(()));\n        assert_eq!(cast_real_to_integer(f64::NAN), Err(()));\n    }\n}\n"
  },
  {
    "path": "core/uuid.rs",
    "content": "use crate::ext::register_scalar_function;\nuse turso_ext::{scalar, ExtensionApi, ResultCode, Value, ValueType};\n\npub fn register_extension(ext_api: &mut ExtensionApi) {\n    // FIXME: Add macro magic to register functions automatically.\n    unsafe {\n        register_scalar_function(ext_api.ctx, c\"uuid4_str\".as_ptr(), uuid4_str);\n        register_scalar_function(ext_api.ctx, c\"gen_random_uuid\".as_ptr(), uuid4_str);\n        register_scalar_function(ext_api.ctx, c\"uuid4\".as_ptr(), uuid4_blob);\n        register_scalar_function(ext_api.ctx, c\"uuid7_str\".as_ptr(), uuid7_str);\n        register_scalar_function(ext_api.ctx, c\"uuid7\".as_ptr(), uuid7);\n        register_scalar_function(ext_api.ctx, c\"uuid7_timestamp_ms\".as_ptr(), uuid7_ts);\n        register_scalar_function(ext_api.ctx, c\"uuid_str\".as_ptr(), uuid_str);\n        register_scalar_function(ext_api.ctx, c\"uuid_blob\".as_ptr(), uuid_blob);\n    }\n}\n\n#[scalar(name = \"uuid4_str\", alias = \"gen_random_uuid\")]\nfn uuid4_str(_args: &[Value]) -> Value {\n    let uuid = uuid::Uuid::new_v4().to_string();\n    Value::from_text(uuid)\n}\n\n#[scalar(name = \"uuid4\")]\nfn uuid4_blob(_args: &[Value]) -> Value {\n    let uuid = uuid::Uuid::new_v4();\n    let bytes = uuid.as_bytes();\n    Value::from_blob(bytes.to_vec())\n}\n\n#[scalar(name = \"uuid7_str\")]\nfn uuid7_str(args: &[Value]) -> Value {\n    let timestamp = if args.is_empty() {\n        let ctx = uuid::ContextV7::new();\n        uuid::Timestamp::now(ctx)\n    } else {\n        match args[0].value_type() {\n            ValueType::Integer => {\n                let ctx = uuid::ContextV7::new();\n                let Some(int) = args[0].to_integer() else {\n                    return Value::error(ResultCode::InvalidArgs);\n                };\n                uuid::Timestamp::from_unix(ctx, int as u64, 0)\n            }\n            ValueType::Text => {\n                let Some(text) = args[0].to_text() else {\n                    return Value::error(ResultCode::InvalidArgs);\n                };\n                match text.parse::<i64>() {\n                    Ok(unix) => {\n                        if unix <= 0 {\n                            return Value::error_with_message(\"Invalid timestamp\".to_string());\n                        }\n                        uuid::Timestamp::from_unix(uuid::ContextV7::new(), unix as u64, 0)\n                    }\n                    Err(_) => return Value::error(ResultCode::InvalidArgs),\n                }\n            }\n            _ => return Value::error(ResultCode::InvalidArgs),\n        }\n    };\n    let uuid = uuid::Uuid::new_v7(timestamp);\n    Value::from_text(uuid.to_string())\n}\n\n#[scalar(name = \"uuid7\")]\nfn uuid7(&self, args: &[Value]) -> Value {\n    let timestamp = if args.is_empty() {\n        let ctx = uuid::ContextV7::new();\n        uuid::Timestamp::now(ctx)\n    } else {\n        match args[0].value_type() {\n            ValueType::Integer => {\n                let ctx = uuid::ContextV7::new();\n                let Some(int) = args[0].to_integer() else {\n                    return Value::null();\n                };\n                uuid::Timestamp::from_unix(ctx, int as u64, 0)\n            }\n            _ => return Value::null(),\n        }\n    };\n    let uuid = uuid::Uuid::new_v7(timestamp);\n    let bytes = uuid.as_bytes();\n    Value::from_blob(bytes.to_vec())\n}\n\n#[scalar(name = \"uuid7_timestamp_ms\")]\nfn uuid7_ts(args: &[Value]) -> Value {\n    match args.first().map(|a| a.value_type()) {\n        Some(ValueType::Blob) => {\n            let Some(blob) = &args[0].to_blob() else {\n                return Value::null();\n            };\n            let Ok(uuid) = uuid::Uuid::from_slice(blob.as_slice()) else {\n                return Value::null();\n            };\n            let unix = uuid_to_unix(uuid.as_bytes());\n            Value::from_integer(unix as i64)\n        }\n        Some(ValueType::Text) => {\n            let Some(text) = args[0].to_text() else {\n                return Value::null();\n            };\n            let Ok(uuid) = uuid::Uuid::parse_str(text) else {\n                return Value::null();\n            };\n            let unix = uuid_to_unix(uuid.as_bytes());\n            Value::from_integer(unix as i64)\n        }\n        None => Value::error_with_message(\n            \"wrong number of arguments to function uuid7_timestamp_ms()\".into(),\n        ),\n        _ => Value::null(),\n    }\n}\n\n#[scalar(name = \"uuid_str\")]\nfn uuid_str(args: &[Value]) -> Value {\n    let Some(blob) = args.first().and_then(|a| a.to_blob()) else {\n        return Value::error_with_message(\n            \"wrong number of arguments to function uuid_str()\".into(),\n        );\n    };\n    let parsed = uuid::Uuid::from_slice(blob.as_slice())\n        .ok()\n        .map(|u| u.to_string());\n    match parsed {\n        Some(s) => Value::from_text(s),\n        None => Value::null(),\n    }\n}\n\n#[scalar(name = \"uuid_blob\")]\nfn uuid_blob(&self, args: &[Value]) -> Value {\n    let Some(text) = args.first().and_then(|a| a.to_text()) else {\n        return Value::error_with_message(\n            \"wrong number of arguments to function uuid_blob()\".into(),\n        );\n    };\n    match uuid::Uuid::parse_str(text) {\n        Ok(uuid) => Value::from_blob(uuid.as_bytes().to_vec()),\n        Err(_) => Value::null(),\n    }\n}\n\n#[inline(always)]\nfn uuid_to_unix(uuid: &[u8; 16]) -> u64 {\n    ((uuid[0] as u64) << 40)\n        | ((uuid[1] as u64) << 32)\n        | ((uuid[2] as u64) << 24)\n        | ((uuid[3] as u64) << 16)\n        | ((uuid[4] as u64) << 8)\n        | (uuid[5] as u64)\n}\n"
  },
  {
    "path": "core/vdbe/affinity.rs",
    "content": "use either::Either;\nuse turso_parser::ast::{Expr, Literal};\n\nuse crate::{\n    numeric::{format_float, DoubleDouble, Numeric},\n    types::AsValueRef,\n    Value, ValueRef,\n};\n\n/// # SQLite Column Type Affinities\n///\n/// Each column in an SQLite 3 database is assigned one of the following type affinities:\n///\n/// - **TEXT**\n/// - **NUMERIC**\n/// - **INTEGER**\n/// - **REAL**\n/// - **BLOB**\n///\n/// > **Note:** Historically, the \"BLOB\" type affinity was called \"NONE\". However, this term was renamed to avoid confusion with \"no affinity\".\n///\n/// ## Affinity Descriptions\n///\n/// ### **TEXT**\n/// - Stores data using the NULL, TEXT, or BLOB storage classes.\n/// - Numerical data inserted into a column with TEXT affinity is converted into text form before being stored.\n/// - **Example:**\n///   ```sql\n///   CREATE TABLE example (col TEXT);\n///   INSERT INTO example (col) VALUES (123); -- Stored as '123' (text)\n///   SELECT typeof(col) FROM example; -- Returns 'text'\n///   ```\n///\n/// ### **NUMERIC**\n/// - Can store values using all five storage classes.\n/// - Text data is converted to INTEGER or REAL (in that order of preference) if it is a well-formed integer or real literal.\n/// - If the text represents an integer too large for a 64-bit signed integer, it is converted to REAL.\n/// - If the text is not a well-formed literal, it is stored as TEXT.\n/// - Hexadecimal integer literals are stored as TEXT for historical compatibility.\n/// - Floating-point values that can be exactly represented as integers are converted to integers.\n/// - **Example:**\n///   ```sql\n///   CREATE TABLE example (col NUMERIC);\n///   INSERT INTO example (col) VALUES ('3.0e+5'); -- Stored as 300000 (integer)\n///   SELECT typeof(col) FROM example; -- Returns 'integer'\n///   ```\n///\n/// ### **INTEGER**\n/// - Behaves like NUMERIC affinity but differs in `CAST` expressions.\n/// - **Example:**\n///   ```sql\n///   CREATE TABLE example (col INTEGER);\n///   INSERT INTO example (col) VALUES (4.0); -- Stored as 4 (integer)\n///   SELECT typeof(col) FROM example; -- Returns 'integer'\n///   ```\n///\n/// ### **REAL**\n/// - Similar to NUMERIC affinity but forces integer values into floating-point representation.\n/// - **Optimization:** Small floating-point values with no fractional component may be stored as integers on disk to save space. This is invisible at the SQL level.\n/// - **Example:**\n///   ```sql\n///   CREATE TABLE example (col REAL);\n///   INSERT INTO example (col) VALUES (4); -- Stored as 4.0 (real)\n///   SELECT typeof(col) FROM example; -- Returns 'real'\n///   ```\n///\n/// ### **BLOB**\n/// - Does not prefer any storage class.\n/// - No coercion is performed between storage classes.\n/// - **Example:**\n///   ```sql\n///   CREATE TABLE example (col BLOB);\n///   INSERT INTO example (col) VALUES (x'1234'); -- Stored as a binary blob\n///   SELECT typeof(col) FROM example; -- Returns 'blob'\n///   ```\n#[derive(Debug, Clone, Copy, PartialEq)]\npub enum Affinity {\n    Blob = 0,\n    Text = 1,\n    Numeric = 2,\n    Integer = 3,\n    Real = 4,\n}\n\npub const SQLITE_AFF_NONE: char = 'A'; // Historically called NONE, but it's the same as BLOB\npub const SQLITE_AFF_TEXT: char = 'B';\npub const SQLITE_AFF_NUMERIC: char = 'C';\npub const SQLITE_AFF_INTEGER: char = 'D';\npub const SQLITE_AFF_REAL: char = 'E';\n\nimpl Affinity {\n    /// This is meant to be used in opcodes like Eq, which state:\n    ///\n    /// \"The SQLITE_AFF_MASK portion of P5 must be an affinity character - SQLITE_AFF_TEXT, SQLITE_AFF_INTEGER, and so forth.\n    /// An attempt is made to coerce both inputs according to this affinity before the comparison is made.\n    /// If the SQLITE_AFF_MASK is 0x00, then numeric affinity is used.\n    /// Note that the affinity conversions are stored back into the input registers P1 and P3.\n    /// So this opcode can cause persistent changes to registers P1 and P3.\"\"\n    pub fn aff_mask(&self) -> char {\n        match self {\n            Affinity::Integer => SQLITE_AFF_INTEGER,\n            Affinity::Text => SQLITE_AFF_TEXT,\n            Affinity::Blob => SQLITE_AFF_NONE,\n            Affinity::Real => SQLITE_AFF_REAL,\n            Affinity::Numeric => SQLITE_AFF_NUMERIC,\n        }\n    }\n\n    pub fn from_char(char: char) -> Self {\n        match char {\n            SQLITE_AFF_INTEGER => Affinity::Integer,\n            SQLITE_AFF_TEXT => Affinity::Text,\n            SQLITE_AFF_NONE => Affinity::Blob,\n            SQLITE_AFF_REAL => Affinity::Real,\n            SQLITE_AFF_NUMERIC => Affinity::Numeric,\n            _ => Affinity::Blob,\n        }\n    }\n\n    pub fn as_char_code(&self) -> u8 {\n        self.aff_mask() as u8\n    }\n\n    pub fn from_char_code(code: u8) -> Self {\n        Self::from_char(code as char)\n    }\n\n    pub fn is_numeric(&self) -> bool {\n        matches!(self, Affinity::Integer | Affinity::Real | Affinity::Numeric)\n    }\n\n    pub fn has_affinity(&self) -> bool {\n        !matches!(self, Affinity::Blob)\n    }\n\n    /// 3.1. Determination Of Column Affinity\n    /// For tables not declared as STRICT, the affinity of a column is determined by the declared type of the column, according to the following rules in the order shown:\n    ///\n    /// If the declared type contains the string \"INT\" then it is assigned INTEGER affinity.\n    ///\n    /// If the declared type of the column contains any of the strings \"CHAR\", \"CLOB\", or \"TEXT\" then that column has TEXT affinity. Notice that the type VARCHAR contains the string \"CHAR\" and is thus assigned TEXT affinity.\n    ///\n    /// If the declared type for a column contains the string \"BLOB\" or if no type is specified then the column has affinity BLOB.\n    ///\n    /// If the declared type for a column contains any of the strings \"REAL\", \"FLOA\", or \"DOUB\" then the column has REAL affinity.\n    ///\n    /// Otherwise, the affinity is NUMERIC.\n    ///\n    /// Note that the order of the rules for determining column affinity is important. A column whose declared type is \"CHARINT\" will match both rules 1 and 2 but the first rule takes precedence and so the column affinity will be INTEGER.\n    #[expect(clippy::self_named_constructors)]\n    pub fn affinity(datatype: &str) -> Self {\n        let datatype = datatype.to_ascii_uppercase();\n\n        // Rule 1: INT -> INTEGER affinity\n        if datatype.contains(\"INT\") {\n            return Affinity::Integer;\n        }\n\n        // Rule 2: CHAR/CLOB/TEXT -> TEXT affinity\n        if datatype.contains(\"CHAR\") || datatype.contains(\"CLOB\") || datatype.contains(\"TEXT\") {\n            return Affinity::Text;\n        }\n\n        // Rule 3: BLOB or empty -> BLOB affinity (historically called NONE)\n        if datatype.contains(\"BLOB\") || datatype.is_empty() {\n            return Affinity::Blob;\n        }\n\n        // Rule 4: REAL/FLOA/DOUB -> REAL affinity\n        if datatype.contains(\"REAL\") || datatype.contains(\"FLOA\") || datatype.contains(\"DOUB\") {\n            return Affinity::Real;\n        }\n\n        // Rule 5: Otherwise -> NUMERIC affinity\n        Affinity::Numeric\n    }\n\n    pub fn convert<'a>(&self, val: &'a impl AsValueRef) -> Option<Either<ValueRef<'a>, Value>> {\n        let val = val.as_value_ref();\n        let is_text = matches!(val, ValueRef::Text(_));\n        // Apply affinity conversions\n        match self {\n            Affinity::Numeric | Affinity::Integer => is_text\n                .then(|| apply_numeric_affinity(val, false))\n                .flatten()\n                .map(Either::Left),\n\n            Affinity::Text => {\n                // TEXT affinity: Convert numeric values to their text representation\n                match val {\n                    ValueRef::Numeric(Numeric::Integer(i)) => {\n                        Some(Either::Right(Value::Text(i.to_string().into())))\n                    }\n                    ValueRef::Numeric(Numeric::Float(f)) => Some(Either::Right(Value::Text(\n                        format_float(f64::from(f)).into(),\n                    ))),\n                    ValueRef::Text(_) => {\n                        // If it's already text but looks numeric, ensure it's in canonical text form\n                        if is_numeric_value(val) {\n                            stringify_register(val).map(Either::Right)\n                        } else {\n                            None // Already text, no conversion needed\n                        }\n                    }\n                    _ => None, // Blob and Null are not converted\n                }\n            }\n\n            Affinity::Real => {\n                let mut left = is_text\n                    .then(|| apply_numeric_affinity(val, false))\n                    .flatten();\n\n                if let ValueRef::Numeric(Numeric::Integer(i)) = left.unwrap_or(val) {\n                    left = Some(ValueRef::from_f64(i as f64));\n                }\n\n                left.map(Either::Left)\n            }\n\n            Affinity::Blob => None, // Do nothing for blob affinity.\n        }\n    }\n\n    /// Return TRUE if the given expression is a constant which would be\n    /// unchanged by OP_Affinity with the affinity given in the second\n    /// argument.\n    ///\n    /// This routine is used to determine if the OP_Affinity operation\n    /// can be omitted.  When in doubt return FALSE.  A false negative\n    /// is harmless.  A false positive, however, can result in the wrong\n    /// answer.\n    ///\n    /// reference https://github.com/sqlite/sqlite/blob/master/src/expr.c#L3000\n    pub fn expr_needs_no_affinity_change(&self, expr: &Expr) -> bool {\n        if !self.has_affinity() {\n            return true;\n        }\n        // TODO: check for unary minus in the expr, as it may be an additional optimization.\n        // This involves mostly likely walking the expression\n        match expr {\n            Expr::Literal(literal) => match literal {\n                Literal::Numeric(_) => self.is_numeric(),\n                Literal::String(_) => matches!(self, Affinity::Text),\n                Literal::Blob(_) => true,\n                _ => false,\n            },\n            Expr::Column {\n                is_rowid_alias: true,\n                ..\n            } => self.is_numeric(),\n            _ => false,\n        }\n    }\n}\n\n#[derive(Debug, PartialEq)]\npub enum NumericParseResult {\n    NotNumeric,      // not a valid number\n    PureInteger,     // pure integer (entire string)\n    HasDecimalOrExp, // has decimal point or exponent (entire string)\n    ValidPrefixOnly, // valid prefix but not entire string\n}\n\n#[derive(Debug)]\npub enum ParsedNumber {\n    None,\n    Integer(i64),\n    Float(f64),\n}\n\nimpl ParsedNumber {\n    fn as_integer(&self) -> Option<i64> {\n        match self {\n            ParsedNumber::Integer(i) => Some(*i),\n            _ => None,\n        }\n    }\n\n    fn as_float(&self) -> Option<f64> {\n        match self {\n            ParsedNumber::Float(f) => Some(*f),\n            _ => None,\n        }\n    }\n}\n\npub fn try_for_float(bytes: &[u8]) -> (NumericParseResult, ParsedNumber) {\n    if bytes.is_empty() {\n        return (NumericParseResult::NotNumeric, ParsedNumber::None);\n    }\n\n    let mut pos = 0;\n    let len = bytes.len();\n\n    while pos < len && is_space(bytes[pos]) {\n        pos += 1;\n    }\n\n    if pos >= len {\n        return (NumericParseResult::NotNumeric, ParsedNumber::None);\n    }\n\n    let mut sign = 1i64;\n\n    if bytes[pos] == b'-' {\n        sign = -1;\n        pos += 1;\n    } else if bytes[pos] == b'+' {\n        pos += 1;\n    }\n\n    if pos >= len {\n        return (NumericParseResult::NotNumeric, ParsedNumber::None);\n    }\n\n    let mut significand = 0u64;\n    let mut decimal_adjust = 0i32;\n    let mut has_digits = false;\n\n    // Parse digits before decimal point\n    while pos < len && bytes[pos].is_ascii_digit() {\n        has_digits = true;\n        let digit = (bytes[pos] - b'0') as u64;\n\n        if significand <= (u64::MAX - 9) / 10 {\n            significand = significand * 10 + digit;\n        } else {\n            // Skip overflow digits but adjust exponent\n            decimal_adjust += 1;\n        }\n        pos += 1;\n    }\n\n    let mut has_decimal = false;\n    let mut has_exponent = false;\n\n    // Check for decimal point\n    if pos < len && bytes[pos] == b'.' {\n        has_decimal = true;\n        pos += 1;\n\n        // Parse fractional digits\n        while pos < len && bytes[pos].is_ascii_digit() {\n            has_digits = true;\n            let digit = (bytes[pos] - b'0') as u64;\n\n            if significand <= (u64::MAX - 9) / 10 {\n                significand = significand * 10 + digit;\n                decimal_adjust -= 1;\n            }\n            pos += 1;\n        }\n    }\n\n    if !has_digits {\n        return (NumericParseResult::NotNumeric, ParsedNumber::None);\n    }\n\n    // Check for exponent\n    let mut exponent = 0i32;\n    if pos < len && (bytes[pos] == b'e' || bytes[pos] == b'E') {\n        has_exponent = true;\n        pos += 1;\n\n        if pos >= len {\n            // Incomplete exponent, but we have valid digits before\n            return create_result_from_significand(\n                significand,\n                sign,\n                decimal_adjust,\n                has_decimal,\n                has_exponent,\n                NumericParseResult::ValidPrefixOnly,\n            );\n        }\n\n        let mut exp_sign = 1i32;\n        if bytes[pos] == b'-' {\n            exp_sign = -1;\n            pos += 1;\n        } else if bytes[pos] == b'+' {\n            pos += 1;\n        }\n\n        if pos >= len || !bytes[pos].is_ascii_digit() {\n            // Incomplete exponent\n            return create_result_from_significand(\n                significand,\n                sign,\n                decimal_adjust,\n                has_decimal,\n                false,\n                NumericParseResult::ValidPrefixOnly,\n            );\n        }\n\n        // Parse exponent digits\n        while pos < len && bytes[pos].is_ascii_digit() {\n            let digit = (bytes[pos] - b'0') as i32;\n            if exponent < 10000 {\n                exponent = exponent * 10 + digit;\n            } else {\n                exponent = 10000; // Cap at large value\n            }\n            pos += 1;\n        }\n        exponent *= exp_sign;\n    }\n\n    // Skip trailing whitespace\n    while pos < len && is_space(bytes[pos]) {\n        pos += 1;\n    }\n\n    // Determine if we consumed the entire string\n    let consumed_all = pos >= len;\n    let final_exponent = decimal_adjust + exponent;\n\n    let parse_result = if !consumed_all {\n        NumericParseResult::ValidPrefixOnly\n    } else if has_decimal || has_exponent {\n        NumericParseResult::HasDecimalOrExp\n    } else {\n        NumericParseResult::PureInteger\n    };\n\n    create_result_from_significand(\n        significand,\n        sign,\n        final_exponent,\n        has_decimal,\n        has_exponent,\n        parse_result,\n    )\n}\n\nfn create_result_from_significand(\n    significand: u64,\n    sign: i64,\n    exponent: i32,\n    has_decimal: bool,\n    has_exponent: bool,\n    parse_result: NumericParseResult,\n) -> (NumericParseResult, ParsedNumber) {\n    if significand == 0 {\n        match parse_result {\n            NumericParseResult::PureInteger => {\n                return (parse_result, ParsedNumber::Integer(0));\n            }\n            _ => {\n                return (parse_result, ParsedNumber::Float(0.0));\n            }\n        }\n    }\n\n    // For pure integers without exponent, try to return as integer\n    if !has_decimal && !has_exponent && exponent == 0 && significand <= i64::MAX as u64 {\n        let signed_val = (significand as i64).wrapping_mul(sign);\n        return (parse_result, ParsedNumber::Integer(signed_val));\n    }\n\n    // Convert to float using Dekker double-double arithmetic for precision\n    // This matches SQLite's sqlite3AtoF implementation\n    let mut result = DoubleDouble::from(significand);\n\n    let mut exp = exponent;\n    match exp.cmp(&0) {\n        std::cmp::Ordering::Greater => {\n            while exp >= 100 {\n                result *= DoubleDouble::E100;\n                exp -= 100;\n            }\n            while exp >= 10 {\n                result *= DoubleDouble::E10;\n                exp -= 10;\n            }\n            while exp >= 1 {\n                result *= DoubleDouble::E1;\n                exp -= 1;\n            }\n        }\n        std::cmp::Ordering::Less => {\n            while exp <= -100 {\n                result *= DoubleDouble::NEG_E100;\n                exp += 100;\n            }\n            while exp <= -10 {\n                result *= DoubleDouble::NEG_E10;\n                exp += 10;\n            }\n            while exp <= -1 {\n                result *= DoubleDouble::NEG_E1;\n                exp += 1;\n            }\n        }\n        std::cmp::Ordering::Equal => {}\n    }\n\n    let mut final_result: f64 = result.into();\n    if final_result.is_nan() {\n        final_result = f64::INFINITY;\n    }\n    if sign < 0 {\n        final_result = -final_result;\n    }\n\n    (parse_result, ParsedNumber::Float(final_result))\n}\n\npub fn is_space(byte: u8) -> bool {\n    matches!(byte, b' ' | b'\\t' | b'\\n' | b'\\r' | b'\\x0c')\n}\n\npub(crate) fn real_to_i64(r: f64) -> i64 {\n    if r < -9223372036854774784.0 {\n        i64::MIN\n    } else if r > 9223372036854774784.0 {\n        i64::MAX\n    } else {\n        r as i64\n    }\n}\n\nfn apply_integer_affinity(val: ValueRef) -> Option<ValueRef> {\n    let ValueRef::Numeric(Numeric::Float(nn)) = val else {\n        return None;\n    };\n\n    let f: f64 = nn.into();\n    let ix = real_to_i64(f);\n\n    // Only convert if round-trip is exact and not at extreme values\n    if f == (ix as f64) && ix > i64::MIN && ix < i64::MAX {\n        Some(ValueRef::Numeric(Numeric::Integer(ix)))\n    } else {\n        None\n    }\n}\n\n/// Try to convert a value into a numeric representation if we can\n/// do so without loss of information. In other words, if the string\n/// looks like a number, convert it into a number. If it does not\n/// look like a number, leave it alone.\npub fn apply_numeric_affinity(val: ValueRef, try_for_int: bool) -> Option<ValueRef> {\n    let ValueRef::Text(text) = val else {\n        return None; // Only apply to text values\n    };\n\n    let text_str = text.as_str();\n    let (parse_result, parsed_value) = try_for_float(text_str.as_bytes());\n\n    // Only convert if we have a complete valid number (not just a prefix)\n    match parse_result {\n        NumericParseResult::NotNumeric | NumericParseResult::ValidPrefixOnly => {\n            None // Leave as text\n        }\n        NumericParseResult::PureInteger => {\n            if let Some(int_val) = parsed_value.as_integer() {\n                Some(ValueRef::Numeric(Numeric::Integer(int_val)))\n            } else if let Some(float_val) = parsed_value.as_float() {\n                let res = ValueRef::from_f64(float_val);\n                if try_for_int {\n                    apply_integer_affinity(res)\n                } else {\n                    Some(res)\n                }\n            } else {\n                None\n            }\n        }\n        NumericParseResult::HasDecimalOrExp => {\n            if let Some(float_val) = parsed_value.as_float() {\n                // Failed parses can occasionally surface as NaN. Treat those as\n                // non-convertible so we keep the original text value instead of\n                // coercing to NULL during comparison affinity conversion.\n                if float_val.is_nan() {\n                    return None;\n                }\n\n                let res = ValueRef::from_f64(float_val);\n                // If try_for_int is true, try to convert float to int if exact\n                if try_for_int {\n                    apply_integer_affinity(res)\n                } else {\n                    Some(res)\n                }\n            } else {\n                None\n            }\n        }\n    }\n}\n\nfn is_numeric_value(val: ValueRef) -> bool {\n    matches!(val, ValueRef::Numeric(_))\n}\n\nfn stringify_register(val: ValueRef) -> Option<Value> {\n    match val {\n        ValueRef::Numeric(Numeric::Integer(i)) => Some(Value::build_text(i.to_string())),\n        ValueRef::Numeric(Numeric::Float(f)) => Some(Value::build_text(f64::from(f).to_string())),\n        _ => None,\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_apply_numeric_affinity_partial_numbers() {\n        let val = Value::Text(\"123abc\".into());\n        let res = apply_numeric_affinity(val.as_value_ref(), false);\n        assert!(res.is_none());\n\n        let val = Value::Text(\"-53093015420544-15062897\".into());\n        let res = apply_numeric_affinity(val.as_value_ref(), false);\n        assert!(res.is_none());\n\n        let val = Value::Text(\"123.45xyz\".into());\n        let res = apply_numeric_affinity(val.as_value_ref(), false);\n        assert!(res.is_none());\n    }\n\n    #[test]\n    fn test_apply_numeric_affinity_complete_numbers() {\n        let val = Value::Text(\"123\".into());\n        let res = apply_numeric_affinity(val.as_value_ref(), false);\n        assert_eq!(res, Some(ValueRef::Numeric(Numeric::Integer(123))));\n\n        let val = Value::Text(\"123.45\".into());\n        let res = apply_numeric_affinity(val.as_value_ref(), false);\n        assert_eq!(res, Some(ValueRef::from_f64(123.45)));\n\n        let val = Value::Text(\"  -456  \".into());\n        let res = apply_numeric_affinity(val.as_value_ref(), false);\n        assert_eq!(res, Some(ValueRef::Numeric(Numeric::Integer(-456))));\n\n        let val = Value::Text(\"0\".into());\n        let res = apply_numeric_affinity(val.as_value_ref(), false);\n        assert_eq!(res, Some(ValueRef::Numeric(Numeric::Integer(0))));\n    }\n\n    #[test]\n    fn test_apply_numeric_affinity_extreme_exponent_gives_infinity() {\n        let val = Value::Text(\"3139353734372E383932303939343135\".into());\n        let res = apply_numeric_affinity(val.as_value_ref(), false);\n        assert!(res.is_some());\n        match res.unwrap() {\n            ValueRef::Numeric(Numeric::Float(f)) => assert!(f64::from(f).is_infinite()),\n            other => panic!(\"expected Float, got {other:?}\"),\n        }\n    }\n\n    #[test]\n    fn test_try_for_float_precision() {\n        // This test verifies that try_for_float uses high-precision arithmetic\n        // to avoid rounding errors when computing significand * 10^exponent.\n        // Naive f64 multiplication accumulates errors; Dekker double-double fixes this.\n        let (_, parsed) = try_for_float(b\"12345678901234567e-5\");\n        let expected: f64 = \"12345678901234567e-5\".parse().unwrap();\n        assert_eq!(\n            parsed.as_float().unwrap().to_bits(),\n            expected.to_bits(),\n            \"try_for_float precision mismatch: got {}, expected {expected}\",\n            parsed.as_float().unwrap(),\n        );\n    }\n}\n"
  },
  {
    "path": "core/vdbe/array.rs",
    "content": "use std::collections::BTreeSet;\nuse std::fmt::Write;\n\nuse crate::numeric::Numeric;\nuse crate::types::{ImmutableRecord, Value, ValueIterator};\nuse crate::Result;\n\n/// Extract values from a record-format array blob.\n/// Returns Err if the blob is not a valid record.\n/// Uses zero-copy iteration over the blob bytes — no Vec<u8> allocation.\npub(crate) fn array_values_from_blob(blob: &[u8]) -> Result<Vec<Value>> {\n    let iter = ValueIterator::new(blob)?;\n    let mut values = Vec::with_capacity(iter.size_hint().0);\n    for value in iter {\n        values.push(value?.to_owned());\n    }\n    Ok(values)\n}\n\n/// Extract elements from any Value that represents an array.\n/// Handles record blobs, JSON text input, and NULL (empty array).\n/// Returns None if the value cannot be interpreted as an array.\npub(crate) fn array_values_from_any(arr: &Value) -> Option<Vec<Value>> {\n    match arr {\n        Value::Blob(blob) => array_values_from_blob(blob).ok(),\n        Value::Text(text) => parse_text_array(text.as_str()),\n        Value::Null => Some(Vec::new()),\n        _ => None,\n    }\n}\n\n/// Parse a text array literal in PG format `{1, hello, NULL}` into a Vec<Value>.\n/// Handles integers, floats, strings (quoted and unquoted), and NULL.\npub(crate) fn parse_text_array(text: &str) -> Option<Vec<Value>> {\n    let text = text.trim();\n    if text.starts_with('{') && text.ends_with('}') {\n        return parse_pg_text_array(text);\n    }\n    None\n}\n\n/// Parse a PG-style text array like `{1, hello, NULL, 3.14}` into a Vec<Value>.\n/// Unquoted `NULL` (case-insensitive) → Value::Null.\n/// Quoted strings use `\"...\"` with `\\\"` and `\\\\` escapes.\n/// Unquoted tokens are parsed as integer, then float, then text.\nfn parse_pg_text_array(text: &str) -> Option<Vec<Value>> {\n    let inner = text[1..text.len() - 1].trim();\n    if inner.is_empty() {\n        return Some(Vec::new());\n    }\n    let bytes = inner.as_bytes();\n    let mut pos = 0;\n    let mut elements = Vec::new();\n\n    loop {\n        // Skip whitespace\n        while pos < bytes.len() && bytes[pos].is_ascii_whitespace() {\n            pos += 1;\n        }\n        if pos >= bytes.len() {\n            break;\n        }\n\n        if bytes[pos] == b'\"' {\n            // Quoted string\n            pos += 1;\n            let mut s = String::new();\n            loop {\n                if pos >= bytes.len() {\n                    return None;\n                }\n                match bytes[pos] {\n                    b'\\\\' => {\n                        pos += 1;\n                        if pos >= bytes.len() {\n                            return None;\n                        }\n                        match bytes[pos] {\n                            b'n' => s.push('\\n'),\n                            b't' => s.push('\\t'),\n                            b'r' => s.push('\\r'),\n                            other => s.push(other as char),\n                        }\n                    }\n                    b'\"' => {\n                        pos += 1;\n                        break;\n                    }\n                    _ => {\n                        let remaining = &inner[pos..];\n                        let ch = remaining.chars().next().unwrap_or('\\u{FFFD}');\n                        s.push(ch);\n                        pos += ch.len_utf8();\n                        continue;\n                    }\n                }\n                pos += 1;\n            }\n            elements.push(Value::build_text(s));\n        } else {\n            // Unquoted token: read until comma, whitespace, or end\n            let start = pos;\n            while pos < bytes.len() && bytes[pos] != b',' && !bytes[pos].is_ascii_whitespace() {\n                pos += 1;\n            }\n            let token = &inner[start..pos];\n            if token.eq_ignore_ascii_case(\"null\") {\n                elements.push(Value::Null);\n            } else if let Ok(i) = token.parse::<i64>() {\n                elements.push(Value::from_i64(i));\n            } else if let Ok(f) = token.parse::<f64>() {\n                if !f.is_finite() {\n                    return None; // reject Infinity and NaN\n                }\n                elements.push(Value::from_f64(f));\n            } else {\n                elements.push(Value::build_text(token.to_string()));\n            }\n        }\n\n        // Skip whitespace\n        while pos < bytes.len() && bytes[pos].is_ascii_whitespace() {\n            pos += 1;\n        }\n        if pos >= bytes.len() {\n            break;\n        }\n        if bytes[pos] == b',' {\n            pos += 1;\n            // Reject trailing commas: after consuming ',' there must be another element\n            let mut peek = pos;\n            while peek < bytes.len() && bytes[peek].is_ascii_whitespace() {\n                peek += 1;\n            }\n            if peek >= bytes.len() {\n                return None; // trailing comma\n            }\n        } else if pos < bytes.len() {\n            return None;\n        }\n    }\n\n    Some(elements)\n}\n\n/// Pack values into a record-format array blob.\npub(crate) fn values_to_record_blob(values: &[Value]) -> Value {\n    Value::Blob(ImmutableRecord::from_values(values, values.len()).into_payload())\n}\n\n/// Serialize a record-format array blob to PostgreSQL text representation.\n/// Uses `{...}` delimiters and PG quoting rules:\n/// - NULL elements → uppercase `NULL` (unquoted)\n/// - Text elements → double-quoted if they contain special chars, unquoted otherwise\n/// - Numeric elements → unquoted\npub(crate) fn serialize_array_from_blob(blob: &[u8]) -> Result<String> {\n    let iter = ValueIterator::new(blob)?;\n    let mut result = String::from(\"{\");\n    let mut first = true;\n    for vref in iter {\n        let vref = vref?;\n        if !first {\n            result.push(',');\n        }\n        first = false;\n        write_value_ref_pg(&mut result, &vref);\n    }\n    result.push('}');\n    Ok(result)\n}\n\nfn write_value_ref_pg(result: &mut String, val: &crate::ValueRef<'_>) {\n    match val {\n        crate::ValueRef::Null => result.push_str(\"NULL\"),\n        crate::ValueRef::Numeric(Numeric::Integer(n)) => {\n            let _ = write!(result, \"{n}\");\n        }\n        crate::ValueRef::Numeric(Numeric::Float(f)) => {\n            let fval: f64 = (*f).into();\n            // Normalize -0.0 to 0.0 for display\n            let fval = if fval == 0.0 { 0.0 } else { fval };\n            if fval.fract() == 0.0 && fval.is_finite() {\n                let _ = write!(result, \"{fval:.1}\");\n            } else {\n                let _ = write!(result, \"{fval}\");\n            }\n        }\n        crate::ValueRef::Text(t) => {\n            write_pg_text_element(result, t.as_str());\n        }\n        crate::ValueRef::Blob(b) => {\n            result.push_str(\"\\\"X'\");\n            for byte in *b {\n                let _ = write!(result, \"{byte:02X}\");\n            }\n            result.push_str(\"'\\\"\");\n        }\n    }\n}\n\n/// Write a text element in PG array format.\n/// Simple values are unquoted; values with special chars are double-quoted.\nfn write_pg_text_element(result: &mut String, s: &str) {\n    let needs_quoting = s.is_empty()\n        || s.eq_ignore_ascii_case(\"null\")\n        || s.contains(|c: char| {\n            c == ','\n                || c == '{'\n                || c == '}'\n                || c == '\"'\n                || c == '\\\\'\n                || c.is_whitespace()\n                || c.is_control()\n        });\n    if needs_quoting {\n        result.push('\"');\n        for ch in s.chars() {\n            match ch {\n                '\"' => result.push_str(\"\\\\\\\"\"),\n                '\\\\' => result.push_str(\"\\\\\\\\\"),\n                '\\n' => result.push_str(\"\\\\n\"),\n                '\\r' => result.push_str(\"\\\\r\"),\n                '\\t' => result.push_str(\"\\\\t\"),\n                c if c.is_control() => {\n                    let _ = write!(result, \"\\\\u{:04x}\", c as u32);\n                }\n                c => result.push(c),\n            }\n        }\n        result.push('\"');\n    } else {\n        result.push_str(s);\n    }\n}\n\n/// Compute the number of elements in an array value. Shared by\n/// op_array_length (instruction) and ScalarFunc::ArrayLength (function).\n/// Returns None for NULL or non-blob input (maps to SQL NULL).\npub(crate) fn compute_array_length(val: &Value) -> Option<i64> {\n    match val {\n        Value::Null => None,\n        Value::Blob(b) => match ValueIterator::new(b) {\n            Ok(iter) => Some(iter.count() as i64),\n            Err(_) => None,\n        },\n        Value::Text(t) => parse_text_array(t.as_str()).map(|v| v.len() as i64),\n        _ => None,\n    }\n}\n\npub(crate) fn exec_array_append(arr: &Value, elem: &Value) -> Value {\n    let Some(mut elements) = array_values_from_any(arr) else {\n        return Value::Null;\n    };\n    elements.push(elem.clone());\n    values_to_record_blob(&elements)\n}\n\npub(crate) fn exec_array_prepend(arr: &Value, elem: &Value) -> Value {\n    let Some(elements) = array_values_from_any(arr) else {\n        return Value::Null;\n    };\n    // Build new vec with elem first — avoids O(n) shift from Vec::insert(0, ...)\n    let mut result = Vec::with_capacity(elements.len() + 1);\n    result.push(elem.clone());\n    result.extend(elements);\n    values_to_record_blob(&result)\n}\n\npub(crate) fn exec_array_cat(a: &Value, b: &Value) -> Value {\n    if matches!(a, Value::Null) || matches!(b, Value::Null) {\n        return Value::Null;\n    }\n    let Some(mut elems_a) = array_values_from_any(a) else {\n        return Value::Null;\n    };\n    let Some(elems_b) = array_values_from_any(b) else {\n        return Value::Null;\n    };\n    elems_a.extend(elems_b);\n    values_to_record_blob(&elems_a)\n}\n\npub(crate) fn exec_array_remove(arr: &Value, target: &Value) -> Value {\n    if matches!(arr, Value::Null) {\n        return Value::Null;\n    }\n    let Some(elements) = array_values_from_any(arr) else {\n        return Value::Null;\n    };\n    let result: Vec<Value> = elements.into_iter().filter(|e| e != target).collect();\n    values_to_record_blob(&result)\n}\n\npub(crate) fn exec_array_contains(arr: &Value, target: &Value) -> Value {\n    if matches!(arr, Value::Null) {\n        return Value::Null;\n    }\n    if let Value::Blob(blob) = arr {\n        return array_find_streaming(blob, |vref| vref == *target)\n            .map(|_| Value::from_i64(1))\n            .unwrap_or_else(|| Value::from_i64(0));\n    }\n    let Some(elements) = array_values_from_any(arr) else {\n        return Value::Null;\n    };\n    let found = elements.iter().any(|e| e == target);\n    Value::from_i64(found as i64)\n}\n\npub(crate) fn exec_array_position(arr: &Value, target: &Value) -> Value {\n    if matches!(arr, Value::Null) {\n        return Value::Null;\n    }\n    if let Value::Blob(blob) = arr {\n        return array_find_streaming(blob, |vref| vref == *target)\n            .map(|i| Value::from_i64(i as i64 + 1)) // 1-based (PG convention)\n            .unwrap_or(Value::Null);\n    }\n    let Some(elements) = array_values_from_any(arr) else {\n        return Value::Null;\n    };\n    for (i, elem) in elements.iter().enumerate() {\n        if elem == target {\n            return Value::from_i64(i as i64 + 1); // 1-based (PG convention)\n        }\n    }\n    Value::Null\n}\n\n/// Stream through a record-format blob, calling `predicate` on each element.\n/// Returns Some(index) for the first element where the predicate returns true,\n/// or None if no match or on error.\nfn array_find_streaming(\n    blob: &[u8],\n    predicate: impl Fn(crate::ValueRef<'_>) -> bool,\n) -> Option<usize> {\n    let iter = ValueIterator::new(blob).ok()?;\n    for (i, vref) in iter.enumerate() {\n        let vref = vref.ok()?;\n        if predicate(vref) {\n            return Some(i);\n        }\n    }\n    None\n}\n\npub(crate) fn exec_array_slice(arr: &Value, start: &Value, end: &Value) -> Value {\n    if matches!(arr, Value::Null) {\n        return Value::Null;\n    }\n    let Some(elements) = array_values_from_any(arr) else {\n        return Value::Null;\n    };\n    // PG convention: 1-based inclusive bounds\n    let start_idx = match start {\n        Value::Numeric(Numeric::Integer(i)) if *i >= 1 => (*i - 1) as usize,\n        _ => 0,\n    };\n    let end_idx = match end {\n        Value::Numeric(Numeric::Integer(i)) if *i >= 1 => *i as usize, // inclusive → exclusive\n        _ => 0,\n    };\n    let end = end_idx.min(elements.len());\n    let start = start_idx.min(end);\n    values_to_record_blob(&elements[start..end])\n}\n\n/// Split a string into an array using a delimiter.\n/// string_to_array(text, delimiter [, null_string])\n/// If text is NULL, returns NULL.\n/// If delimiter is NULL, splits into individual characters (PostgreSQL behavior).\n/// If null_string is provided, any element matching it becomes NULL.\npub(crate) fn exec_string_to_array(\n    text: &Value,\n    delimiter: &Value,\n    null_str: Option<&Value>,\n) -> Value {\n    let text_str = match text {\n        Value::Text(t) => t.as_str().to_string(),\n        Value::Null => return Value::Null,\n        other => other.to_string(),\n    };\n\n    let null_match: Option<String> = match null_str {\n        Some(Value::Text(t)) => Some(t.as_str().to_string()),\n        Some(Value::Null) | None => None,\n        Some(other) => Some(other.to_string()),\n    };\n\n    // NULL delimiter: split into individual characters (PostgreSQL behavior)\n    if matches!(delimiter, Value::Null) {\n        let values: Vec<Value> = text_str\n            .chars()\n            .map(|c| {\n                let s = c.to_string();\n                if let Some(ref nm) = null_match {\n                    if s == *nm {\n                        return Value::Null;\n                    }\n                }\n                Value::build_text(s)\n            })\n            .collect();\n        return values_to_record_blob(&values);\n    }\n\n    let delim_str = match delimiter {\n        Value::Text(d) => d.as_str().to_string(),\n        other => other.to_string(),\n    };\n\n    let parts: Vec<&str> = if delim_str.is_empty() {\n        // Empty delimiter: return single-element array with the whole string\n        vec![&text_str]\n    } else {\n        text_str.split(&delim_str).collect()\n    };\n\n    let values: Vec<Value> = parts\n        .into_iter()\n        .map(|p| {\n            if let Some(ref nm) = null_match {\n                if p == nm.as_str() {\n                    return Value::Null;\n                }\n            }\n            Value::build_text(p.to_string())\n        })\n        .collect();\n\n    values_to_record_blob(&values)\n}\n\n/// Join array elements into a string with a delimiter.\n/// array_to_string(array, delimiter [, null_string])\n/// NULL elements are omitted unless null_string is provided.\npub(crate) fn exec_array_to_string(\n    arr: &Value,\n    delimiter: &Value,\n    null_str: Option<&Value>,\n) -> Value {\n    if matches!(arr, Value::Null) {\n        return Value::Null;\n    }\n\n    let delim = match delimiter {\n        Value::Text(t) => t.as_str().to_string(),\n        Value::Null => return Value::Null,\n        other => other.to_string(),\n    };\n\n    let null_replacement: Option<String> = match null_str {\n        Some(Value::Text(t)) => Some(t.as_str().to_string()),\n        Some(Value::Null) | None => None,\n        Some(other) => Some(other.to_string()),\n    };\n\n    // Fast path: stream from blob without materializing Vec<Value>\n    if let Value::Blob(blob) = arr {\n        if let Ok(iter) = ValueIterator::new(blob) {\n            let mut result = String::new();\n            let mut first = true;\n            for vref in iter {\n                let Ok(vref) = vref else {\n                    return Value::Null;\n                };\n                let part = match &vref {\n                    crate::ValueRef::Null => {\n                        if let Some(ref replacement) = null_replacement {\n                            replacement.clone()\n                        } else {\n                            continue;\n                        }\n                    }\n                    crate::ValueRef::Text(t) => t.as_str().to_string(),\n                    other => format!(\"{other}\"),\n                };\n                if !first {\n                    result.push_str(&delim);\n                }\n                result.push_str(&part);\n                first = false;\n            }\n            return Value::build_text(result);\n        }\n    }\n\n    let Some(elements) = array_values_from_any(arr) else {\n        return Value::Null;\n    };\n\n    let mut result = String::new();\n    let mut first = true;\n    for elem in &elements {\n        let part = match elem {\n            Value::Null => {\n                if let Some(ref replacement) = null_replacement {\n                    replacement.clone()\n                } else {\n                    continue;\n                }\n            }\n            Value::Text(t) => t.as_str().to_string(),\n            other => other.to_string(),\n        };\n        if !first {\n            result.push_str(&delim);\n        }\n        result.push_str(&part);\n        first = false;\n    }\n\n    Value::build_text(result)\n}\n\n/// Check if two arrays have any elements in common.\n/// Returns 1 if they share at least one element, 0 otherwise.\n/// NULL if either input is not a valid array.\npub(crate) fn exec_array_overlap(a: &Value, b: &Value) -> Value {\n    if matches!(a, Value::Null) || matches!(b, Value::Null) {\n        return Value::Null;\n    }\n    let Some(elems_a) = array_values_from_any(a) else {\n        return Value::Null;\n    };\n    let Some(elems_b) = array_values_from_any(b) else {\n        return Value::Null;\n    };\n    // O(n log n + m log n) via BTreeSet instead of O(n*m)\n    let set: BTreeSet<&Value> = elems_a.iter().collect();\n    let found = elems_b.iter().any(|eb| set.contains(eb));\n    Value::from_i64(found as i64)\n}\n\n/// Check if array `a` contains all elements of array `b` (@> operator).\n/// Returns 1 if every element in `b` appears in `a`, 0 otherwise.\n/// NULL if either input is not a valid array.\npub(crate) fn exec_array_contains_all(a: &Value, b: &Value) -> Value {\n    if matches!(a, Value::Null) || matches!(b, Value::Null) {\n        return Value::Null;\n    }\n    let Some(elems_a) = array_values_from_any(a) else {\n        return Value::Null;\n    };\n    let Some(elems_b) = array_values_from_any(b) else {\n        return Value::Null;\n    };\n    // O(n log n + m log n) via BTreeSet instead of O(n*m)\n    let set: BTreeSet<&Value> = elems_a.iter().collect();\n    let all_found = elems_b.iter().all(|eb| set.contains(eb));\n    Value::from_i64(all_found as i64)\n}\n\n/// Collect values from contiguous registers into a record-format array blob.\npub(crate) fn make_array_from_registers(\n    registers: &[super::Register],\n    start_reg: usize,\n    count: usize,\n) -> Value {\n    let values: Vec<Value> = (0..count)\n        .map(|i| registers[start_reg + i].get_value().clone())\n        .collect();\n    let record = ImmutableRecord::from_values(&values, count);\n    Value::Blob(record.into_payload())\n}\n\n/// Element-wise comparison of two record-format array blobs.\n/// Compares corresponding elements using ValueRef ordering.\n/// If all common elements are equal, the shorter array is less.\n/// Returns Err if either blob is not a valid record.\npub(crate) fn compare_arrays(a: &[u8], b: &[u8]) -> Result<std::cmp::Ordering> {\n    let iter_a = ValueIterator::new(a)?;\n    let iter_b = ValueIterator::new(b)?;\n    let mut count_a = 0usize;\n    let mut count_b = 0usize;\n    for (va, vb) in iter_a.zip(iter_b) {\n        count_a += 1;\n        count_b += 1;\n        let (va, vb) = (va?, vb?);\n        let ord = va.cmp(&vb);\n        if !ord.is_eq() {\n            return Ok(ord);\n        }\n    }\n    // Count remaining elements in the longer array\n    let len_a = count_a + ValueIterator::new(a)?.skip(count_a).count();\n    let len_b = count_b + ValueIterator::new(b)?.skip(count_b).count();\n    Ok(len_a.cmp(&len_b))\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_parse_text_array_multibyte_utf8() {\n        let input = r#\"{\"café\",\"naïve\",\"über\"}\"#;\n        let result = parse_text_array(input).unwrap();\n        assert_eq!(result.len(), 3);\n        assert_eq!(result[0], Value::build_text(\"café\"));\n        assert_eq!(result[1], Value::build_text(\"naïve\"));\n        assert_eq!(result[2], Value::build_text(\"über\"));\n    }\n\n    #[test]\n    fn test_parse_text_array_emoji() {\n        let input = r#\"{\"hello 🌍\",\"test 🚀\"}\"#;\n        let result = parse_text_array(input).unwrap();\n        assert_eq!(result.len(), 2);\n        assert_eq!(result[0], Value::build_text(\"hello 🌍\"));\n        assert_eq!(result[1], Value::build_text(\"test 🚀\"));\n    }\n\n    #[test]\n    fn test_parse_text_array_cjk() {\n        let input = r#\"{\"你好\",\"世界\"}\"#;\n        let result = parse_text_array(input).unwrap();\n        assert_eq!(result.len(), 2);\n        assert_eq!(result[0], Value::build_text(\"你好\"));\n        assert_eq!(result[1], Value::build_text(\"世界\"));\n    }\n\n    #[test]\n    fn test_compute_array_length_null_returns_none() {\n        assert_eq!(compute_array_length(&Value::Null), None);\n    }\n\n    #[test]\n    fn test_compute_array_length_valid_array() {\n        let blob = values_to_record_blob(&[Value::from_i64(1), Value::from_i64(2)]);\n        assert_eq!(compute_array_length(&blob), Some(2));\n    }\n\n    #[test]\n    fn test_compute_array_length_non_blob_returns_none() {\n        assert_eq!(compute_array_length(&Value::from_i64(42)), None,);\n    }\n\n    #[test]\n    fn test_array_remove_all_occurrences() {\n        let arr = values_to_record_blob(&[\n            Value::from_i64(1),\n            Value::from_i64(2),\n            Value::from_i64(3),\n            Value::from_i64(2),\n            Value::from_i64(1),\n        ]);\n        let result = exec_array_remove(&arr, &Value::from_i64(2));\n        let Value::Blob(blob) = &result else {\n            panic!(\"Expected Blob\");\n        };\n        let elements = array_values_from_blob(blob).unwrap();\n        assert_eq!(elements.len(), 3);\n        assert_eq!(elements[0], Value::from_i64(1));\n        assert_eq!(elements[1], Value::from_i64(3));\n        assert_eq!(elements[2], Value::from_i64(1));\n    }\n\n    #[test]\n    fn test_array_contains_null_array_returns_null() {\n        assert_eq!(\n            exec_array_contains(&Value::Null, &Value::from_i64(1)),\n            Value::Null,\n        );\n    }\n\n    #[test]\n    fn test_array_position_null_array_returns_null() {\n        assert_eq!(\n            exec_array_position(&Value::Null, &Value::from_i64(1)),\n            Value::Null,\n        );\n    }\n\n    #[test]\n    fn test_compute_array_length_invalid_blob_returns_none() {\n        // A random blob that is not a valid record should return None\n        let invalid = Value::Blob(vec![0xFF, 0xFE, 0xFD]);\n        assert_eq!(compute_array_length(&invalid), None);\n    }\n\n    #[test]\n    fn test_parse_text_array_rejects_json_format() {\n        // JSON [1,2,3] format is no longer accepted — only PG {1,2,3}\n        assert!(parse_text_array(\"[1,2,3]\").is_none());\n        assert!(parse_text_array(r#\"[\"hello\"]\"#).is_none());\n    }\n\n    #[test]\n    fn test_parse_text_array_rejects_trailing_comma() {\n        assert!(parse_text_array(\"{1,2,}\").is_none());\n        assert!(parse_text_array(\"{1, 2, }\").is_none());\n    }\n\n    #[test]\n    fn test_parse_text_array_rejects_infinity() {\n        assert!(parse_text_array(\"{1e309}\").is_none());\n        assert!(parse_text_array(\"{-1e309}\").is_none());\n    }\n\n    #[test]\n    fn test_string_to_array_null_delimiter_splits_chars() {\n        let result = exec_string_to_array(&Value::build_text(\"hello\"), &Value::Null, None);\n        let Value::Blob(blob) = &result else {\n            panic!(\"Expected Blob, got {result:?}\");\n        };\n        let elements = array_values_from_blob(blob).unwrap();\n        assert_eq!(elements.len(), 5);\n        assert_eq!(elements[0], Value::build_text(\"h\"));\n        assert_eq!(elements[1], Value::build_text(\"e\"));\n        assert_eq!(elements[4], Value::build_text(\"o\"));\n    }\n\n    #[test]\n    fn test_exec_array_contains_streaming() {\n        let arr = values_to_record_blob(&[\n            Value::from_i64(10),\n            Value::from_i64(20),\n            Value::from_i64(30),\n        ]);\n        assert_eq!(\n            exec_array_contains(&arr, &Value::from_i64(20)),\n            Value::from_i64(1)\n        );\n        assert_eq!(\n            exec_array_contains(&arr, &Value::from_i64(99)),\n            Value::from_i64(0)\n        );\n    }\n\n    #[test]\n    fn test_exec_array_position_streaming() {\n        let arr = values_to_record_blob(&[\n            Value::from_i64(10),\n            Value::from_i64(20),\n            Value::from_i64(30),\n        ]);\n        // 1-based: element 20 is at position 2\n        assert_eq!(\n            exec_array_position(&arr, &Value::from_i64(20)),\n            Value::from_i64(2)\n        );\n        assert_eq!(exec_array_position(&arr, &Value::from_i64(99)), Value::Null);\n    }\n\n    #[test]\n    fn test_dc1_negative_index_preserves_array() {\n        let arr = values_to_record_blob(&[\n            Value::from_i64(10),\n            Value::from_i64(20),\n            Value::from_i64(30),\n        ]);\n        // array_find_streaming with impossible predicate should return None\n        let Value::Blob(blob) = &arr else {\n            panic!(\"Expected Blob\");\n        };\n        assert!(array_find_streaming(blob, |_| false).is_none());\n    }\n\n    #[test]\n    fn test_dc4_array_remove_null_returns_null() {\n        assert_eq!(\n            exec_array_remove(&Value::Null, &Value::from_i64(1)),\n            Value::Null\n        );\n    }\n\n    #[test]\n    fn test_dc4_array_slice_null_returns_null() {\n        assert_eq!(\n            exec_array_slice(&Value::Null, &Value::from_i64(0), &Value::from_i64(2)),\n            Value::Null,\n        );\n    }\n\n    #[test]\n    fn test_dc4_array_cat_null_returns_null() {\n        assert_eq!(exec_array_cat(&Value::Null, &Value::Null), Value::Null);\n        assert_eq!(\n            exec_array_cat(&Value::Null, &Value::from_i64(1)),\n            Value::Null,\n        );\n    }\n\n    #[test]\n    fn test_serialize_array_from_blob() {\n        let arr =\n            values_to_record_blob(&[Value::from_i64(1), Value::build_text(\"hello\"), Value::Null]);\n        let Value::Blob(blob) = &arr else {\n            panic!(\"Expected Blob\");\n        };\n        let text = serialize_array_from_blob(blob).unwrap();\n        assert_eq!(text, \"{1,hello,NULL}\");\n    }\n\n    #[test]\n    fn test_make_array_from_registers() {\n        use super::super::Register;\n        let registers = vec![\n            Register::Value(Value::from_i64(1)),\n            Register::Value(Value::build_text(\"two\")),\n            Register::Value(Value::from_i64(3)),\n        ];\n        let result = make_array_from_registers(&registers, 0, 3);\n        let Value::Blob(blob) = &result else {\n            panic!(\"Expected Blob\");\n        };\n        let elements = array_values_from_blob(blob).unwrap();\n        assert_eq!(elements.len(), 3);\n        assert_eq!(elements[0], Value::from_i64(1));\n        assert_eq!(elements[1], Value::build_text(\"two\"));\n        assert_eq!(elements[2], Value::from_i64(3));\n    }\n}\n"
  },
  {
    "path": "core/vdbe/bloom_filter.rs",
    "content": "use fastbloom::BloomFilter as BloomFilterInner;\nuse std::fmt;\nuse std::hash::{Hash, Hasher};\n\nuse crate::numeric::Numeric;\nuse crate::types::{Value, ValueRef};\n\n/// Default number of expected items for bloom filter sizing.\n/// This is used when the expected count is not known ahead of time.\nconst DEFAULT_EXPECTED_ITEMS: u32 = 1024;\n\n/// Default false positive rate (1%).\nconst DEFAULT_FALSE_POSITIVE_RATE: f32 = 0.01;\n\n/// A bloom filter for fast probabilistic set membership testing.\n///\n/// Each bloom filter is associated with a cursor or operation that builds it\n/// during ephemeral index/hash table construction.\npub struct BloomFilter {\n    inner: BloomFilterInner,\n    /// Number of items inserted into the filter\n    count: usize,\n}\n\nimpl fmt::Debug for BloomFilter {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"BloomFilter\")\n            .field(\"count\", &self.count)\n            .finish_non_exhaustive()\n    }\n}\n\nimpl BloomFilter {\n    /// Creates a new bloom filter with default parameters.\n    pub fn new() -> Self {\n        Self::with_capacity(DEFAULT_EXPECTED_ITEMS, DEFAULT_FALSE_POSITIVE_RATE)\n    }\n\n    /// Creates a new bloom filter with the specified expected item count and false positive rate.\n    pub fn with_capacity(expected_items: u32, false_positive_rate: f32) -> Self {\n        Self {\n            inner: BloomFilterInner::with_false_pos(false_positive_rate as f64)\n                .expected_items(expected_items as usize),\n            count: 0,\n        }\n    }\n\n    #[inline]\n    /// Inserts an i64 value into the bloom filter.\n    pub fn insert_i64(&mut self, value: i64) {\n        self.inner.insert(&value);\n        self.count += 1;\n    }\n\n    #[inline]\n    /// Checks if an i64 value might be in the bloom filter.\n    pub fn contains_i64(&self, value: i64) -> bool {\n        self.inner.contains(&value)\n    }\n\n    #[inline]\n    /// Inserts a byte slice into the bloom filter.\n    pub fn insert_bytes(&mut self, value: &[u8]) {\n        self.inner.insert(&value);\n        self.count += 1;\n    }\n\n    #[inline]\n    /// Checks if a byte slice might be in the bloom filter.\n    pub fn contains_bytes(&self, value: &[u8]) -> bool {\n        self.inner.contains(&value)\n    }\n\n    /// Inserts a Value into the bloom filter.\n    /// Safety NOTE: does not accept NULL values.\n    pub fn insert_value(&mut self, value: &Value) {\n        if !matches!(value, Value::Null) {\n            let mut hasher = rapidhash::fast::RapidHasher::default();\n            hash_value(&mut hasher, &value.as_ref());\n            let hash = hasher.finish();\n            self.inner.insert(&hash);\n        }\n        self.count += 1;\n    }\n\n    /// Checks if a Value might be in the bloom filter.\n    pub fn contains_value(&self, value: &Value) -> bool {\n        if matches!(value, Value::Null) {\n            return false;\n        }\n        let mut hasher = rapidhash::fast::RapidHasher::default();\n        hash_value(&mut hasher, &value.as_ref());\n        let hash = hasher.finish();\n        self.inner.contains(&hash)\n    }\n\n    /// Inserts multiple owned Values as a composite key into the bloom filter.\n    /// This is because bloom filters only support a single value insertion, so to handle multi\n    /// join-key situations we hash the composite key into a single u64 and then insert that\n    pub fn insert_values(&mut self, values: &[&Value]) {\n        let mut hasher = rapidhash::fast::RapidHasher::default();\n        for value in values {\n            hash_value(&mut hasher, &value.as_ref());\n        }\n        let hash = hasher.finish();\n        self.inner.insert(&hash);\n        self.count += 1;\n    }\n\n    /// Checks if multiple owned Values as a composite key might be in the bloom filter.\n    pub fn contains_values(&self, values: &[&Value]) -> bool {\n        let mut hasher = rapidhash::fast::RapidHasher::default();\n        for value in values {\n            if matches!(value, Value::Null) {\n                // if any value is NULL, we can never have a match\n                return false;\n            }\n            hash_value(&mut hasher, &value.as_ref());\n        }\n        let hash = hasher.finish();\n        self.inner.contains(&hash)\n    }\n\n    pub fn count(&self) -> usize {\n        self.count\n    }\n    pub fn is_empty(&self) -> bool {\n        self.count == 0\n    }\n    pub fn clear(&mut self) {\n        self.inner.clear();\n        self.count = 0;\n    }\n}\n\nimpl Default for BloomFilter {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\n/// Hashes an owned Value into the provided hasher.\nfn hash_value<H: Hasher>(hasher: &mut H, value: &ValueRef) {\n    match value {\n        // do nothing for NULLs as we will always return false for set membership\n        ValueRef::Null => {}\n        ValueRef::Numeric(Numeric::Integer(i)) => {\n            // Hash integers in the same bucket as numerically equivalent REALs so\n            // bloom-filter membership can never return a false-negative for e.g. 10 vs 10.0.\n            let f = *i as f64;\n            if (f as i64) == *i && f.is_finite() {\n                hash_numeric(hasher, f);\n            } else {\n                // Fallback to the integer domain when the float representation would lose precision.\n                1u8.hash(hasher);\n                i.hash(hasher);\n            }\n        }\n        ValueRef::Numeric(Numeric::Float(f)) => {\n            hash_numeric(hasher, f64::from(*f));\n        }\n        ValueRef::Text(s) => {\n            3u8.hash(hasher);\n            s.as_str().hash(hasher);\n        }\n        ValueRef::Blob(b) => {\n            4u8.hash(hasher);\n            b.hash(hasher);\n        }\n    }\n}\n\n/// Hashes numeric values (both INTEGER and REAL) into the same domain to mirror SQLite's\n/// numeric comparison semantics (e.g. 10 == 10.0, -0.0 == 0.0).\nfn hash_numeric<H: Hasher>(hasher: &mut H, f: f64) {\n    const NUMERIC_TAG: u8 = 2;\n    let bits = normalized_f64_bits(f);\n    NUMERIC_TAG.hash(hasher);\n    bits.hash(hasher);\n}\n\n/// Normalize signed zero so 0.0 and -0.0 hash the same.\n#[inline]\nfn normalized_f64_bits(f: f64) -> u64 {\n    if f == 0.0 {\n        0.0f64.to_bits()\n    } else {\n        f.to_bits()\n    }\n}\n\n#[cfg(test)]\nmod bloomtests {\n    use super::*;\n    use crate::types::Text;\n\n    #[test]\n    fn test_bloom_filter_i64() {\n        let mut bf = BloomFilter::new();\n        bf.insert_i64(1);\n        bf.insert_i64(2);\n        bf.insert_i64(3);\n\n        // These should definitely be found (no false negatives)\n        assert!(bf.contains_i64(1));\n        assert!(bf.contains_i64(2));\n        assert!(bf.contains_i64(3));\n    }\n\n    #[test]\n    fn test_bloom_filter_values() {\n        let mut bf = BloomFilter::new();\n\n        let int_val = Value::from_i64(42);\n        let text_val = Value::Text(Text::new(\"hello\".to_string()));\n        let null_val = Value::Null;\n\n        bf.insert_value(&int_val);\n        bf.insert_value(&text_val);\n        bf.insert_value(&null_val);\n\n        assert!(bf.contains_value(&int_val));\n        assert!(bf.contains_value(&text_val));\n        // NULLs are not hashed into the filter, so membership should be false.\n        assert!(!bf.contains_value(&null_val));\n    }\n\n    #[test]\n    fn test_bloom_filter_false_positive_rate() {\n        // Test that false positive rate is roughly as expected\n        let mut bf = BloomFilter::with_capacity(1000, 0.01);\n\n        // Insert 1000 values\n        for i in 0..1000 {\n            bf.insert_i64(i);\n        }\n\n        // Check false positive rate on non-inserted values\n        let mut false_positives = 0;\n        let test_count = 10000;\n        for i in 1000..(1000 + test_count) {\n            if bf.contains_i64(i) {\n                false_positives += 1;\n            }\n        }\n\n        // False positive rate should be around 1% (allow some variance)\n        let rate = false_positives as f64 / test_count as f64;\n        assert!(rate < 0.05, \"False positive rate {rate} is too high\");\n    }\n\n    #[test]\n    fn test_bloom_filter_numeric_equivalence() {\n        let mut bf = BloomFilter::new();\n\n        // Zero variants should all be found regardless of sign or int/float representation\n        let zero_float = Value::from_f64(0.0);\n        let zero_neg_float = Value::from_f64(-0.0);\n        let zero_int = Value::from_i64(0);\n        bf.insert_value(&zero_float);\n        assert!(bf.contains_value(&zero_float));\n        assert!(bf.contains_value(&zero_neg_float));\n        assert!(bf.contains_value(&zero_int));\n\n        // Integer/float representations of the same numeric value should match\n        let ten_int = Value::from_i64(10);\n        let ten_float = Value::from_f64(10.0);\n        bf.insert_value(&ten_int);\n        assert!(bf.contains_value(&ten_int));\n        assert!(bf.contains_value(&ten_float));\n\n        let neg_ten_float = Value::from_f64(-10.0);\n        let neg_ten_int = Value::from_i64(-10);\n        bf.insert_value(&neg_ten_float);\n        assert!(bf.contains_value(&neg_ten_float));\n        assert!(bf.contains_value(&neg_ten_int));\n    }\n}\n"
  },
  {
    "path": "core/vdbe/builder.rs",
    "content": "use crate::{turso_assert, turso_assert_eq, turso_debug_assert};\nuse rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};\nuse tracing::{instrument, Level};\nuse turso_parser::ast::{self, ResolveType, SortOrder, TableInternalId};\n\nuse crate::{\n    index_method::IndexMethodAttachment,\n    parameters::Parameters,\n    schema::{BTreeTable, Index, PseudoCursorType, Schema, Table, Trigger},\n    translate::{\n        collate::CollationSeq,\n        emitter::{MaterializedColumnRef, TransactionMode},\n        plan::{ResultSetColumn, TableReferences},\n    },\n    vdbe::affinity::Affinity,\n    Arc, CaptureDataChangesInfo, Connection, VirtualTable,\n};\n\n// Keep distinct hash-table ids far from table internal ids to avoid collisions.\nconst HASH_TABLE_ID_BASE: usize = 1 << 30;\n\n#[derive(Default)]\npub struct TableRefIdCounter {\n    next_free: ast::TableInternalId,\n}\n\nimpl TableRefIdCounter {\n    pub fn new() -> Self {\n        Self {\n            next_free: TableInternalId::default(),\n        }\n    }\n\n    #[allow(clippy::should_implement_trait)]\n    pub fn next(&mut self) -> ast::TableInternalId {\n        let id = self.next_free;\n        self.next_free += 1;\n        id\n    }\n}\n\nuse super::{\n    BranchOffset, CursorID, Insn, InsnReference, JumpTarget, PrepareContext, PreparedProgram,\n    Program,\n};\n\n/// A key that uniquely identifies a cursor.\n/// The key is a pair of table reference id and index.\n/// The index is only provided when the cursor is an index cursor.\n#[derive(Debug, Clone)]\npub struct CursorKey {\n    /// The table reference that the cursor is associated with.\n    /// We cannot use e.g. the table query identifier (e.g. 'users' or 'u')\n    /// because it might be ambiguous, e.g. this silly example:\n    /// `SELECT * FROM t WHERE EXISTS (SELECT * from t)` <-- two different cursors, which 't' should we use as key?\n    ///  TableInternalIds are unique within a program, since there is one id per table reference.\n    pub table_reference_id: TableInternalId,\n    /// The index, in case of an index cursor.\n    /// The combination of table internal id and index is enough to disambiguate.\n    pub index: Option<Arc<Index>>,\n    /// Whether this cursor is an special case build cursor.\n    pub is_build: bool,\n}\n\nimpl CursorKey {\n    pub fn table(table_reference_id: TableInternalId) -> Self {\n        Self {\n            table_reference_id,\n            index: None,\n            is_build: false,\n        }\n    }\n\n    pub fn index(table_reference_id: TableInternalId, index: Arc<Index>) -> Self {\n        Self {\n            table_reference_id,\n            index: Some(index),\n            is_build: false,\n        }\n    }\n\n    /// Create a cursor key for hash join build operations.\n    /// This creates a separate cursor from the regular table cursor.\n    pub fn hash_build(table_reference_id: TableInternalId) -> Self {\n        Self {\n            table_reference_id,\n            index: None,\n            is_build: true,\n        }\n    }\n\n    pub fn equals(&self, other: &CursorKey) -> bool {\n        if self.table_reference_id != other.table_reference_id {\n            return false;\n        }\n        if self.is_build != other.is_build {\n            return false;\n        }\n        match (self.index.as_ref(), other.index.as_ref()) {\n            (Some(self_index), Some(other_index)) => self_index.name == other_index.name,\n            (None, None) => true,\n            _ => false,\n        }\n    }\n}\n\n#[allow(dead_code)]\npub struct ProgramBuilder {\n    pub table_reference_counter: TableRefIdCounter,\n    next_free_register: usize,\n    next_free_cursor_id: usize,\n    next_hash_table_id: usize,\n    /// Instruction, the function to execute it with, and its original index in the vector.\n    pub insns: Vec<(Insn, usize)>,\n    /// A span of instructions from (offset_start_inclusive, offset_end_exclusive),\n    /// that are deemed to be compile-time constant and can be hoisted out of loops\n    /// so that they get evaluated only once at the start of the program.\n    pub constant_spans: Vec<(usize, usize)>,\n    /// Cursors that are referenced by the program. Indexed by [CursorKey].\n    /// Certain types of cursors do not need a [CursorKey] (e.g. temp tables, sorter),\n    /// because they never need to use [ProgramBuilder::resolve_cursor_id] to find it\n    /// again. Hence, the key is optional.\n    pub cursor_ref: Vec<(Option<CursorKey>, CursorType)>,\n    /// A vector where index=label number, value=resolved offset. Resolved in build().\n    label_to_resolved_offset: Vec<Option<(InsnReference, JumpTarget)>>,\n    // Bitmask of cursors that have emitted a SeekRowid instruction.\n    seekrowid_emitted_bitmask: u64,\n    // map of instruction index to manual comment (used in EXPLAIN only)\n    comments: Vec<(InsnReference, &'static str)>,\n    pub parameters: Parameters,\n    pub result_columns: Vec<ResultSetColumn>,\n    pub table_references: TableReferences,\n    /// Curr collation sequence. Bool indicates whether it was set by a COLLATE expr\n    collation: Option<(CollationSeq, bool)>,\n    /// Current parsing nesting level\n    nested_level: usize,\n    init_label: BranchOffset,\n    start_offset: BranchOffset,\n    capture_data_changes_info: Option<CaptureDataChangesInfo>,\n    // TODO: when we support multiple dbs, this should be a write mask to track which DBs need to be written\n    txn_mode: TransactionMode,\n    /// Set of database IDs that need write transactions (for attached databases).\n    write_databases: HashSet<usize>,\n    /// Set of attached database IDs that need read transactions.\n    read_databases: HashSet<usize>,\n    /// Schema cookies for attached databases at prepare time.\n    write_database_cookies: HashMap<usize, u32>,\n    /// Schema cookies for attached databases opened for reading.\n    read_database_cookies: HashMap<usize, u32>,\n    rollback: bool,\n    /// The mode in which the query is being executed.\n    query_mode: QueryMode,\n    /// Current parent explain address, if any.\n    current_parent_explain_idx: Option<usize>,\n    pub(crate) reg_result_cols_start: Option<usize>,\n    /// Mirrors SQLite's isMultiWrite: true if the statement may modify/insert multiple rows.\n    /// If a non-autocommit transaction can modify multiple rows, statement subjournaling is always\n    /// required for proper cleanup on abort. If only one row can be modified, then journaling is not\n    /// necessary because on abort there is nothing to clean up.\n    /// Defaults to true for safety; specific translate paths (e.g., single-row INSERT) set false.\n    is_multi_write: bool,\n    /// Mirrors SQLite's mayAbort: true if the statement may throw an ABORT exception.\n    /// This flag is used in combination with is_multi_write to determine if statement subjournaling is required.\n    /// Defaults to true for safety; specific translate paths (e.g., INSERT with no constraints) set false.\n    may_abort: bool,\n    /// If this ProgramBuilder is building trigger subprogram, a ref to the trigger is stored here.\n    pub trigger: Option<Arc<Trigger>>,\n    /// Whether this is a subprogram (trigger or FK action). Subprograms skip Transaction instructions.\n    pub is_subprogram: bool,\n    pub resolve_type: ResolveType,\n    /// Whether the resolve_type was explicitly set from a statement-level OR clause.\n    /// When false, per-constraint ON CONFLICT clauses from CREATE TABLE should be used.\n    pub has_statement_conflict: bool,\n    /// When set, all triggers fired from this program should use this conflict resolution.\n    /// This is used in UPSERT DO UPDATE context to ensure nested trigger's OR IGNORE/REPLACE\n    /// clauses don't suppress errors.\n    pub trigger_conflict_override: Option<ResolveType>,\n    /// Temporary cursor overrides maps table internal IDs to cursor IDs that should be used instead of the normal resolution.\n    /// This allows for things like hash build to use a separate cursor for iterating the same table.\n    cursor_overrides: HashMap<usize, CursorID>,\n    /// Maps identifier names to registers for custom type encode/decode expressions.\n    /// When set, `Expr::Id(\"value\")` resolves to the register holding the input value,\n    /// and type parameter names resolve to registers holding their concrete values.\n    pub id_register_overrides: HashMap<String, usize>,\n    /// When set, translate_expr will skip custom type decode for Expr::Column.\n    /// This is used when building ORDER BY sort keys so the sorter compares\n    /// encoded (on-disk) values. Decode is presentation-only.\n    pub suppress_custom_type_decode: bool,\n    /// When true, the next `emit_column` call will not bake the default value\n    /// into the Column instruction. Used for custom type columns where the default\n    /// needs to be encoded before use.\n    pub suppress_column_default: bool,\n    /// Hash join build signatures keyed by hash table id.\n    hash_build_signatures: HashMap<usize, HashBuildSignature>,\n    /// Hash tables to keep open across subplans (e.g. materialization).\n    hash_tables_to_keep_open: HashSet<usize>,\n    /// Maps table internal_id to result_columns_start_reg for FROM clause subqueries.\n    /// Used when nested subqueries need to reference columns from outer query subqueries.\n    subquery_result_regs: HashMap<TableInternalId, usize>,\n    /// Counter for CTE identity tracking. Each CTE definition gets a unique ID\n    /// so that multiple references to the same CTE can share materialized data.\n    next_cte_id: usize,\n    /// Registry of materialized CTEs, keyed by cte_id.\n    /// Used to share materialized data across multiple CTE references via OpenDup.\n    materialized_ctes: HashMap<usize, MaterializedCteInfo>,\n    /// Global count of references to each CTE across the entire query.\n    /// Used to determine whether a CTE should be materialized (multi-ref) or use coroutine (single-ref).\n    cte_reference_counts: HashMap<usize, usize>,\n    /// Stack of CTE names currently being planned. Used to detect circular\n    /// references in non-recursive CTEs and to prevent fallthrough to schema\n    /// resolution for same-named tables/views.\n    ctes_being_defined: Vec<String>,\n    /// Counter for subquery numbering in EXPLAIN QUERY PLAN output.\n    next_subquery_eqp_id: usize,\n}\n\n#[derive(Debug, Clone, Copy, Eq, PartialEq)]\npub enum MaterializedBuildInputModeTag {\n    RowidOnly,\n    Payload,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Signature of a hash build to allow reuse when inputs are unchanged.\n/// TODO: this is very heavy... we might consider hashing instead of storing full data.\npub struct HashBuildSignature {\n    /// WHERE term indices used as hash join keys.\n    pub join_key_indices: Vec<usize>,\n    /// Build-table columns stored as payload.\n    pub payload_refs: Vec<MaterializedColumnRef>,\n    /// Affinity string applied to join keys.\n    pub key_affinities: String,\n    /// Whether a bloom filter is enabled for this build.\n    pub use_bloom_filter: bool,\n    /// Rowid input cursor when the build side is materialized.\n    pub materialized_input_cursor: Option<CursorID>,\n    /// RowidOnly vs KeyPayload\n    pub materialized_mode: Option<MaterializedBuildInputModeTag>,\n}\n\n/// Information about a materialized CTE, used for sharing data across multiple references.\n#[derive(Debug, Clone)]\npub struct MaterializedCteInfo {\n    /// The ephemeral table cursor holding materialized CTE data.\n    pub cursor_id: CursorID,\n    /// The table definition, needed for allocating dup cursors with the same CursorType.\n    pub table: Arc<BTreeTable>,\n    /// Number of result columns.\n    pub num_columns: usize,\n}\n\n#[derive(Debug, Clone)]\npub enum CursorType {\n    BTreeTable(Arc<BTreeTable>),\n    BTreeIndex(Arc<Index>),\n    IndexMethod(Arc<dyn IndexMethodAttachment>),\n    Pseudo(PseudoCursorType),\n    Sorter,\n    VirtualTable(Arc<VirtualTable>),\n    MaterializedView(\n        Arc<BTreeTable>,\n        Arc<crate::sync::Mutex<crate::incremental::view::IncrementalView>>,\n    ),\n}\n\nimpl CursorType {\n    pub fn is_index(&self) -> bool {\n        matches!(self, CursorType::BTreeIndex(_))\n    }\n\n    pub fn get_explain_description(&self) -> String {\n        let out = match self {\n            CursorType::BTreeTable(btree_table) => {\n                let mut col_count = btree_table.columns.len();\n                if btree_table.get_rowid_alias_column().is_none() {\n                    col_count += 1;\n                }\n                Some((\n                    col_count,\n                    btree_table\n                        .columns\n                        .iter()\n                        .map(|col| {\n                            if let Some(coll) = col.collation_opt() {\n                                format!(\"{coll}\")\n                            } else {\n                                \"B\".to_string()\n                            }\n                        })\n                        .collect::<Vec<_>>()\n                        .join(\",\"),\n                ))\n            }\n            CursorType::BTreeIndex(index) => {\n                let mut col_count = index.columns.len();\n                if index.has_rowid {\n                    col_count += 1;\n                }\n                Some((\n                    col_count,\n                    index\n                        .columns\n                        .iter()\n                        .map(|col| {\n                            let sign = match col.order {\n                                SortOrder::Asc => \"\",\n                                SortOrder::Desc => \"-\",\n                            };\n                            if let Some(coll) = col.collation {\n                                format!(\"{sign}{coll}\")\n                            } else {\n                                format!(\"{sign}B\")\n                            }\n                        })\n                        .collect::<Vec<_>>()\n                        .join(\",\"),\n                ))\n            }\n            _ => None,\n        };\n\n        out.map_or(String::new(), |(col_count, collations)| {\n            format!(\"k({col_count},{collations})\")\n        })\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Eq, Copy)]\npub enum QueryMode {\n    Normal,\n    Explain,\n    ExplainQueryPlan,\n}\n\nimpl QueryMode {\n    pub fn new(cmd: &ast::Cmd) -> Self {\n        match cmd {\n            ast::Cmd::ExplainQueryPlan(_) => QueryMode::ExplainQueryPlan,\n            ast::Cmd::Explain(_) => QueryMode::Explain,\n            ast::Cmd::Stmt(_) => QueryMode::Normal,\n        }\n    }\n}\n\npub struct ProgramBuilderOpts {\n    pub num_cursors: usize,\n    pub approx_num_insns: usize,\n    pub approx_num_labels: usize,\n}\n\n/// Use this macro to emit an OP_Explain instruction.\n/// Please use this macro instead of calling emit_explain() directly,\n/// because we want to avoid allocating a String if we are not in explain mode.\n#[macro_export]\nmacro_rules! emit_explain {\n    ($builder:expr, $push:expr, $detail:expr) => {\n        if let $crate::QueryMode::ExplainQueryPlan = $builder.get_query_mode() {\n            $builder.emit_explain($push, $detail);\n        }\n    };\n}\n\nimpl ProgramBuilder {\n    /// Run a nested emission scope without leaking its result-column register base\n    /// into the surrounding builder state.\n    pub fn with_scoped_result_cols_start<T>(\n        &mut self,\n        f: impl FnOnce(&mut Self) -> crate::Result<T>,\n    ) -> crate::Result<T> {\n        let saved = self.reg_result_cols_start;\n        let result = f(self);\n        self.reg_result_cols_start = saved;\n        result\n    }\n\n    pub fn new(\n        query_mode: QueryMode,\n        capture_data_changes_info: Option<CaptureDataChangesInfo>,\n        opts: ProgramBuilderOpts,\n    ) -> Self {\n        ProgramBuilder::_new(query_mode, capture_data_changes_info, opts, None, false)\n    }\n    pub fn new_for_trigger(\n        query_mode: QueryMode,\n        capture_data_changes_info: Option<CaptureDataChangesInfo>,\n        opts: ProgramBuilderOpts,\n        trigger: Arc<Trigger>,\n    ) -> Self {\n        ProgramBuilder::_new(\n            query_mode,\n            capture_data_changes_info,\n            opts,\n            Some(trigger),\n            true,\n        )\n    }\n    /// Create a ProgramBuilder for a subprogram (FK actions, etc.) that runs within\n    /// an existing transaction and doesn't emit Transaction instructions.\n    pub fn new_for_subprogram(\n        query_mode: QueryMode,\n        capture_data_changes_info: Option<CaptureDataChangesInfo>,\n        opts: ProgramBuilderOpts,\n    ) -> Self {\n        ProgramBuilder::_new(query_mode, capture_data_changes_info, opts, None, true)\n    }\n    fn _new(\n        query_mode: QueryMode,\n        capture_data_changes_info: Option<CaptureDataChangesInfo>,\n        opts: ProgramBuilderOpts,\n        trigger: Option<Arc<Trigger>>,\n        is_subprogram: bool,\n    ) -> Self {\n        Self {\n            table_reference_counter: TableRefIdCounter::new(),\n            next_free_register: 1,\n            next_free_cursor_id: 0,\n            next_hash_table_id: HASH_TABLE_ID_BASE,\n            insns: Vec::with_capacity(opts.approx_num_insns),\n            cursor_ref: Vec::with_capacity(opts.num_cursors),\n            constant_spans: Vec::new(),\n            label_to_resolved_offset: Vec::with_capacity(opts.approx_num_labels),\n            seekrowid_emitted_bitmask: 0,\n            comments: Vec::new(),\n            parameters: Parameters::new(),\n            result_columns: Vec::new(),\n            table_references: TableReferences::new(vec![], vec![]),\n            collation: None,\n            nested_level: 0,\n            // These labels will be filled when `prologue()` is called\n            init_label: BranchOffset::Placeholder,\n            start_offset: BranchOffset::Placeholder,\n            capture_data_changes_info,\n            txn_mode: TransactionMode::None,\n            write_databases: HashSet::default(),\n            read_databases: HashSet::default(),\n            write_database_cookies: HashMap::default(),\n            read_database_cookies: HashMap::default(),\n            rollback: false,\n            query_mode,\n            current_parent_explain_idx: None,\n            reg_result_cols_start: None,\n            is_multi_write: true,\n            may_abort: true,\n            trigger,\n            is_subprogram,\n            resolve_type: ResolveType::Abort,\n            has_statement_conflict: false,\n            trigger_conflict_override: None,\n            cursor_overrides: HashMap::default(),\n            id_register_overrides: HashMap::default(),\n            suppress_custom_type_decode: false,\n            suppress_column_default: false,\n            hash_build_signatures: HashMap::default(),\n            hash_tables_to_keep_open: HashSet::default(),\n            subquery_result_regs: HashMap::default(),\n            next_cte_id: 0,\n            materialized_ctes: HashMap::default(),\n            cte_reference_counts: HashMap::default(),\n            ctes_being_defined: Vec::new(),\n            next_subquery_eqp_id: 1,\n        }\n    }\n\n    pub fn next_subquery_eqp_id(&mut self) -> usize {\n        let id = self.next_subquery_eqp_id;\n        self.next_subquery_eqp_id += 1;\n        id\n    }\n\n    pub fn alloc_hash_table_id(&mut self) -> usize {\n        let id = self.next_hash_table_id;\n        self.next_hash_table_id = self\n            .next_hash_table_id\n            .checked_add(1)\n            .expect(\"hash table id overflow\");\n        id\n    }\n\n    /// Allocate a unique CTE identity. Each CTE definition in a query gets a unique ID\n    /// so that multiple references to the same CTE can share materialized data via OpenDup.\n    pub fn alloc_cte_id(&mut self) -> usize {\n        let id = self.next_cte_id;\n        self.next_cte_id += 1;\n        id\n    }\n\n    /// Check if a CTE has already been materialized.\n    /// Returns the materialization info if the CTE cursor can be shared via OpenDup.\n    pub fn get_materialized_cte(&self, cte_id: usize) -> Option<&MaterializedCteInfo> {\n        self.materialized_ctes.get(&cte_id)\n    }\n\n    /// Register a materialized CTE so that subsequent references can share it via OpenDup.\n    pub fn register_materialized_cte(&mut self, cte_id: usize, info: MaterializedCteInfo) {\n        self.materialized_ctes.insert(cte_id, info);\n    }\n\n    /// Increment the global reference count for a CTE.\n    /// Called during planning when a CTE reference is created.\n    pub fn increment_cte_reference(&mut self, cte_id: usize) {\n        *self.cte_reference_counts.entry(cte_id).or_insert(0) += 1;\n    }\n\n    /// Get the global reference count for a CTE.\n    /// Used during emission to decide whether to materialize (multi-ref) or use coroutine (single-ref).\n    pub fn get_cte_reference_count(&self, cte_id: usize) -> usize {\n        self.cte_reference_counts.get(&cte_id).copied().unwrap_or(0)\n    }\n\n    /// Mark a CTE name as currently being planned. While on the stack,\n    /// `parse_table` will reject references to this name with \"circular\n    /// reference\" instead of falling through to schema resolution.\n    pub fn push_cte_being_defined(&mut self, name: String) {\n        self.ctes_being_defined.push(name);\n    }\n\n    /// Remove the most recently pushed CTE name after planning completes.\n    pub fn pop_cte_being_defined(&mut self) {\n        self.ctes_being_defined.pop();\n    }\n\n    /// Check whether a name refers to a CTE currently being planned.\n    pub fn is_cte_being_defined(&self, name: &str) -> bool {\n        self.ctes_being_defined.iter().any(|n| n == name)\n    }\n\n    /// Temporarily take the CTE-being-defined stack (e.g. during view\n    /// expansion, which should not see CTE context from the caller).\n    pub fn take_ctes_being_defined(&mut self) -> Vec<String> {\n        std::mem::take(&mut self.ctes_being_defined)\n    }\n\n    /// Restore the CTE-being-defined stack after a context-isolated expansion.\n    pub fn restore_ctes_being_defined(&mut self, saved: Vec<String>) {\n        self.ctes_being_defined = saved;\n    }\n\n    pub fn set_resolve_type(&mut self, resolve_type: ResolveType) {\n        self.resolve_type = resolve_type;\n    }\n\n    /// Set the trigger conflict override. When set, all triggers fired from this program\n    /// should use this conflict resolution instead of their own OR clauses.\n    pub fn set_trigger_conflict_override(&mut self, resolve_type: ResolveType) {\n        self.trigger_conflict_override = Some(resolve_type);\n    }\n\n    /// Returns true if the given hash table id should be kept open across subplans.\n    pub fn should_keep_hash_table_open(&self, hash_table_id: usize) -> bool {\n        self.hash_tables_to_keep_open.contains(&hash_table_id)\n    }\n\n    /// Set the set of hash tables to keep open across subplans.\n    pub fn set_hash_tables_to_keep_open(&mut self, tables: &HashSet<usize>) {\n        self.hash_tables_to_keep_open.clone_from(tables);\n    }\n\n    /// Reset the set of hash tables to keep open.\n    pub fn clear_hash_tables_to_keep_open(&mut self) {\n        self.hash_tables_to_keep_open.clear();\n    }\n\n    /// Returns true if the given hash build signature matches the recorded one for the given hash table id.\n    pub fn hash_build_signature_matches(\n        &self,\n        hash_table_id: usize,\n        signature: &HashBuildSignature,\n    ) -> bool {\n        self.hash_build_signatures\n            .get(&hash_table_id)\n            .is_some_and(|existing| existing == signature)\n    }\n\n    /// Returns true if there is a recorded hash build signature for the given hash table id.\n    pub fn has_hash_build_signature(&self, hash_table_id: usize) -> bool {\n        self.hash_build_signatures.contains_key(&hash_table_id)\n    }\n\n    /// Insert or update the hash build signature for the given hash table id.\n    pub fn record_hash_build_signature(\n        &mut self,\n        hash_table_id: usize,\n        signature: HashBuildSignature,\n    ) {\n        self.hash_build_signatures.insert(hash_table_id, signature);\n    }\n\n    /// Clear the hash build signature for the given hash table id.\n    pub fn clear_hash_build_signature(&mut self, hash_table_id: usize) {\n        self.hash_build_signatures.remove(&hash_table_id);\n    }\n\n    /// Store the result_columns_start_reg for a FROM clause subquery by its internal_id.\n    /// Used so nested subqueries can access columns from outer query subqueries.\n    pub fn set_subquery_result_reg(&mut self, internal_id: TableInternalId, result_reg: usize) {\n        self.subquery_result_regs.insert(internal_id, result_reg);\n    }\n\n    /// Look up the result_columns_start_reg for a FROM clause subquery by its internal_id.\n    /// Returns None if the subquery hasn't been emitted yet.\n    pub fn get_subquery_result_reg(&self, internal_id: TableInternalId) -> Option<usize> {\n        self.subquery_result_regs.get(&internal_id).copied()\n    }\n\n    /// Mark that this statement may modify/insert multiple rows (mirrors SQLite's sqlite3MultiWrite).\n    /// When false, statement journals are skipped since single-write statements are atomic.\n    pub fn set_multi_write(&mut self, is_multi_write: bool) {\n        self.is_multi_write = is_multi_write;\n    }\n\n    /// Mark that this statement may throw an ABORT exception (mirrors SQLite's sqlite3MayAbort).\n    pub fn set_may_abort(&mut self, may_abort: bool) {\n        self.may_abort = may_abort;\n    }\n\n    pub fn capture_data_changes_info(&self) -> &Option<CaptureDataChangesInfo> {\n        &self.capture_data_changes_info\n    }\n\n    pub fn extend(&mut self, opts: &ProgramBuilderOpts) {\n        self.insns.reserve(opts.approx_num_insns);\n        self.cursor_ref.reserve(opts.num_cursors);\n        self.label_to_resolved_offset\n            .reserve(opts.approx_num_labels);\n    }\n\n    /// Start a new constant span. The next instruction to be emitted will be the first\n    /// instruction in the span.\n    pub fn constant_span_start(&mut self) -> usize {\n        let span = self.constant_spans.len();\n        let start = self.insns.len();\n        self.constant_spans.push((start, usize::MAX));\n        span\n    }\n\n    /// End the current constant span. The last instruction that was emitted is the last\n    /// instruction in the span.\n    pub fn constant_span_end(&mut self, span_idx: usize) {\n        let span = &mut self.constant_spans[span_idx];\n        if span.1 == usize::MAX {\n            span.1 = self.insns.len().saturating_sub(1);\n        }\n    }\n\n    /// End all constant spans that are currently open. This is used to handle edge cases\n    /// where we think a parent expression is constant, but we decide during the evaluation\n    /// of one of its children that it is not.\n    pub fn constant_span_end_all(&mut self) {\n        for span in self.constant_spans.iter_mut() {\n            if span.1 == usize::MAX {\n                span.1 = self.insns.len().saturating_sub(1);\n            }\n        }\n    }\n\n    /// Check if there is a constant span that is currently open.\n    pub fn constant_span_is_open(&self) -> bool {\n        self.constant_spans\n            .last()\n            .is_some_and(|(_, end)| *end == usize::MAX)\n    }\n\n    /// Get the index of the next constant span.\n    /// Used in [crate::translate::expr::translate_expr_no_constant_opt()] to invalidate\n    /// all constant spans after the given index.\n    pub fn constant_spans_next_idx(&self) -> usize {\n        self.constant_spans.len()\n    }\n\n    /// Invalidate all constant spans after the given index. This is used when we want to\n    /// be sure that constant optimization is never used for translating a given expression.\n    /// See [crate::translate::expr::translate_expr_no_constant_opt()] for more details.\n    pub fn constant_spans_invalidate_after(&mut self, idx: usize) {\n        self.constant_spans.truncate(idx);\n    }\n\n    pub fn alloc_register(&mut self) -> usize {\n        let reg = self.next_free_register;\n        self.next_free_register += 1;\n        reg\n    }\n\n    pub fn alloc_registers(&mut self, amount: usize) -> usize {\n        let reg = self.next_free_register;\n        self.next_free_register += amount;\n        reg\n    }\n\n    /// Returns the next register that will be allocated by alloc_register/alloc_registers.\n    pub fn peek_next_register(&self) -> usize {\n        self.next_free_register\n    }\n\n    pub fn alloc_registers_and_init_w_null(&mut self, amount: usize) -> usize {\n        let reg = self.alloc_registers(amount);\n        self.emit_insn(Insn::Null {\n            dest: reg,\n            dest_end: if amount == 1 {\n                None\n            } else {\n                Some(reg + amount - 1)\n            },\n        });\n        reg\n    }\n\n    pub fn alloc_cursor_id_keyed(&mut self, key: CursorKey, cursor_type: CursorType) -> usize {\n        turso_assert!(\n            !self\n                .cursor_ref\n                .iter()\n                .any(|(k, _)| k.as_ref().is_some_and(|k| k.equals(&key))),\n            \"duplicate cursor key\"\n        );\n        self._alloc_cursor_id(Some(key), cursor_type)\n    }\n\n    pub fn alloc_cursor_id_keyed_if_not_exists(\n        &mut self,\n        key: CursorKey,\n        cursor_type: CursorType,\n    ) -> usize {\n        if let Some(cursor_id) = self.resolve_cursor_id_safe(&key) {\n            cursor_id\n        } else {\n            self._alloc_cursor_id(Some(key), cursor_type)\n        }\n    }\n\n    /// allocate proper cursor for the given index (either [CursorType::BTreeIndex] or [CursorType::IndexMethod])\n    pub fn alloc_cursor_index(\n        &mut self,\n        key: Option<CursorKey>,\n        index: &Arc<Index>,\n    ) -> crate::Result<usize> {\n        tracing::debug!(\"alloc cursor: {:?} {:?}\", key, index.index_method.is_some());\n        let module = index.index_method.as_ref();\n        if let Some(m) = module {\n            if !m.definition().backing_btree {\n                return Ok(self._alloc_cursor_id(key, CursorType::IndexMethod(m.clone())));\n            }\n        }\n        Ok(self._alloc_cursor_id(key, CursorType::BTreeIndex(index.clone())))\n    }\n\n    pub fn alloc_cursor_index_if_not_exists(\n        &mut self,\n        key: CursorKey,\n        index: &Arc<Index>,\n    ) -> crate::Result<usize> {\n        if let Some(cursor_id) = self.resolve_cursor_id_safe(&key) {\n            Ok(cursor_id)\n        } else {\n            self.alloc_cursor_index(Some(key), index)\n        }\n    }\n\n    pub fn alloc_cursor_id(&mut self, cursor_type: CursorType) -> usize {\n        self._alloc_cursor_id(None, cursor_type)\n    }\n\n    fn _alloc_cursor_id(&mut self, key: Option<CursorKey>, cursor_type: CursorType) -> usize {\n        let cursor = self.next_free_cursor_id;\n        self.next_free_cursor_id += 1;\n        self.cursor_ref.push((key, cursor_type));\n        turso_assert_eq!(self.cursor_ref.len(), self.next_free_cursor_id);\n        cursor\n    }\n\n    pub fn add_pragma_result_column(&mut self, col_name: String) {\n        // TODO figure out a better type definition for ResultSetColumn\n        // or invent another way to set pragma result columns\n        let expr = ast::Expr::Id(ast::Name::exact(\"\".to_string()));\n        self.result_columns.push(ResultSetColumn {\n            expr,\n            alias: Some(col_name),\n            contains_aggregates: false,\n        });\n    }\n\n    #[instrument(skip(self), level = Level::DEBUG)]\n    pub fn emit_insn(&mut self, insn: Insn) {\n        // This seemingly empty trace here is needed so that a function span is emmited with it\n        tracing::trace!(\"\");\n        self.insns.push((insn, self.insns.len()));\n    }\n\n    /// Emit an instruction that should not start or extend a constant span on its own.\n    /// If a parent constant span is already open, the instruction is emitted normally\n    /// within that span (the parent's `is_constant` classification takes precedence).\n    #[instrument(skip(self), level = Level::DEBUG)]\n    pub fn emit_no_constant_insn(&mut self, insn: Insn) {\n        if !self.constant_span_is_open() {\n            self.constant_span_end_all();\n        }\n        self.emit_insn(insn);\n    }\n\n    pub fn close_cursors(&mut self, cursors: &[CursorID]) {\n        for cursor in cursors {\n            self.emit_insn(Insn::Close { cursor_id: *cursor });\n        }\n    }\n\n    pub fn emit_string8(&mut self, value: String, dest: usize) {\n        self.emit_insn(Insn::String8 { value, dest });\n    }\n\n    pub fn emit_string8_new_reg(&mut self, value: String) -> usize {\n        let dest = self.alloc_register();\n        self.emit_insn(Insn::String8 { value, dest });\n        dest\n    }\n\n    pub fn emit_int(&mut self, value: i64, dest: usize) {\n        self.emit_insn(Insn::Integer { value, dest });\n    }\n\n    pub fn emit_bool(&mut self, value: bool, dest: usize) {\n        self.emit_insn(Insn::Integer {\n            value: if value { 1 } else { 0 },\n            dest,\n        });\n    }\n\n    pub fn emit_null(&mut self, dest: usize, dest_end: Option<usize>) {\n        self.emit_insn(Insn::Null { dest, dest_end });\n    }\n\n    pub fn emit_result_row(&mut self, start_reg: usize, count: usize) {\n        self.emit_insn(Insn::ResultRow { start_reg, count });\n    }\n\n    fn emit_halt(&mut self, rollback: bool) {\n        self.emit_insn(Insn::Halt {\n            err_code: 0,\n            description: if rollback {\n                \"rollback\".to_string()\n            } else {\n                String::new()\n            },\n            on_error: None,\n            description_reg: None,\n        });\n    }\n\n    // no users yet, but I want to avoid someone else in the future\n    // just adding parameters to emit_halt! If you use this, remove the\n    // clippy warning please.\n    #[allow(dead_code)]\n    pub fn emit_halt_err(&mut self, err_code: usize, description: String) {\n        self.emit_insn(Insn::Halt {\n            err_code,\n            description,\n            on_error: None,\n            description_reg: None,\n        });\n    }\n\n    pub fn add_comment(&mut self, insn_index: BranchOffset, comment: &'static str) {\n        if let QueryMode::Explain | QueryMode::ExplainQueryPlan = self.query_mode {\n            self.comments.push((insn_index.as_offset_int(), comment));\n        }\n    }\n\n    pub fn get_query_mode(&self) -> QueryMode {\n        self.query_mode\n    }\n\n    /// use emit_explain macro instead, because we don't want to allocate\n    /// String if we are not in explain mode\n    pub fn emit_explain(&mut self, push: bool, detail: String) {\n        if let QueryMode::ExplainQueryPlan = self.query_mode {\n            self.emit_insn(Insn::Explain {\n                p1: self.insns.len(),\n                p2: self.current_parent_explain_idx,\n                detail,\n            });\n            if push {\n                self.current_parent_explain_idx = Some(self.insns.len() - 1);\n            }\n        }\n    }\n\n    pub fn pop_current_parent_explain(&mut self) {\n        if let QueryMode::ExplainQueryPlan = self.query_mode {\n            if let Some(current) = self.current_parent_explain_idx {\n                let (Insn::Explain { p2, .. }, _) = &self.insns[current] else {\n                    unreachable!(\"current_parent_explain_idx must point to an Explain insn\");\n                };\n                self.current_parent_explain_idx = *p2;\n            }\n        } else {\n            turso_debug_assert!(self.current_parent_explain_idx.is_none());\n        }\n    }\n\n    pub fn mark_last_insn_constant(&mut self) {\n        if self.constant_span_is_open() {\n            // no need to mark this insn as constant as the surrounding parent expression is already constant\n            return;\n        }\n\n        let prev = self.insns.len().saturating_sub(1);\n        self.constant_spans.push((prev, prev));\n    }\n\n    fn emit_constant_insns(&mut self) {\n        // Move compile-time constant instructions to the end of the program,\n        // where they are executed once after Init jumps to it.\n\n        // Stable partition: non-constant instructions first, then constant.\n        // Since spans are sorted and non-overlapping, we track our position\n        // in the span list and never look back - O(n + m) total, where\n        // n = number of instructions, m = number of constant spans.\n        let mut non_constant = Vec::with_capacity(self.insns.len());\n        let mut constant = Vec::new();\n        let mut span_idx = 0;\n\n        for item in self.insns.drain(..) {\n            let idx = item.1;\n\n            // Advance past spans we've completely passed\n            while span_idx < self.constant_spans.len() && self.constant_spans[span_idx].1 < idx {\n                span_idx += 1;\n            }\n\n            // Check if current span contains this index\n            let is_constant =\n                span_idx < self.constant_spans.len() && self.constant_spans[span_idx].0 <= idx;\n\n            if is_constant {\n                constant.push(item);\n            } else {\n                non_constant.push(item);\n            }\n        }\n\n        self.insns = non_constant;\n        self.insns.extend(constant);\n\n        // Build old index -> new position mapping\n        let mut old_to_new = vec![0usize; self.insns.len()];\n        for (new_pos, (_, old_idx)) in self.insns.iter().enumerate() {\n            old_to_new[*old_idx] = new_pos;\n        }\n\n        for resolved_offset in self.label_to_resolved_offset.iter_mut() {\n            if let Some((old_offset, target)) = resolved_offset {\n                *resolved_offset = Some((old_to_new[*old_offset as usize] as u32, *target));\n            }\n        }\n\n        for (offset, _) in self.comments.iter_mut() {\n            *offset = old_to_new[*offset as usize] as u32;\n        }\n\n        if let QueryMode::ExplainQueryPlan = self.query_mode {\n            self.current_parent_explain_idx =\n                self.current_parent_explain_idx.map(|old| old_to_new[old]);\n\n            for i in 0..self.insns.len() {\n                let (Insn::Explain { p2, .. }, _) = &self.insns[i] else {\n                    continue;\n                };\n\n                let new_p2 = p2.map(|old| old_to_new[old]);\n\n                let (Insn::Explain { p1, p2, .. }, _) = &mut self.insns[i] else {\n                    unreachable!();\n                };\n\n                *p1 = i;\n                *p2 = new_p2;\n            }\n        }\n    }\n\n    pub fn offset(&self) -> BranchOffset {\n        BranchOffset::Offset(self.insns.len() as InsnReference)\n    }\n\n    pub fn allocate_label(&mut self) -> BranchOffset {\n        let label_n = self.label_to_resolved_offset.len();\n        self.label_to_resolved_offset.push(None);\n        BranchOffset::Label(label_n as u32)\n    }\n\n    /// Resolve a label to whatever instruction follows the one that was\n    /// last emitted.\n    ///\n    /// Use this when your use case is: \"the program should jump to whatever instruction\n    /// follows the one that was previously emitted\", and you don't care exactly\n    /// which instruction that is. Examples include \"the start of a loop\", or\n    /// \"after the loop ends\".\n    ///\n    /// It is important to handle those cases this way, because the precise\n    /// instruction that follows any given instruction might change due to\n    /// reordering the emitted instructions.\n    #[inline]\n    pub fn preassign_label_to_next_insn(&mut self, label: BranchOffset) {\n        turso_assert!(label.is_label(), \"BranchOffset should be a label\", { \"label\": label });\n        self._resolve_label(label, self.offset().sub(1u32), JumpTarget::AfterThisInsn);\n    }\n\n    /// Resolve a label to exactly the instruction that was last emitted.\n    ///\n    /// Use this when your use case is: \"the program should jump to the exact instruction\n    /// that was last emitted\", and you don't care WHERE exactly that ends up being\n    /// once the order of the bytecode of the program is finalized. Examples include\n    /// \"jump to the Halt instruction\", or \"jump to the Next instruction of a loop\".\n    #[inline]\n    pub fn resolve_label(&mut self, label: BranchOffset, to_offset: BranchOffset) {\n        self._resolve_label(label, to_offset, JumpTarget::ExactlyThisInsn);\n    }\n\n    fn _resolve_label(&mut self, label: BranchOffset, to_offset: BranchOffset, target: JumpTarget) {\n        turso_assert!(matches!(label, BranchOffset::Label(_)));\n        turso_assert!(matches!(to_offset, BranchOffset::Offset(_)));\n        let BranchOffset::Label(label_number) = label else {\n            unreachable!(\"Label is not a label\");\n        };\n        self.label_to_resolved_offset[label_number as usize] =\n            Some((to_offset.as_offset_int(), target));\n    }\n\n    /// Resolve unresolved labels to a specific offset in the instruction list.\n    ///\n    /// This function scans all instructions and resolves any labels to their corresponding offsets.\n    /// It ensures that all labels are resolved correctly and updates the target program counter (PC)\n    /// of each instruction that references a label.\n    pub fn resolve_labels(&mut self) -> crate::Result<()> {\n        let resolve = |pc: &mut BranchOffset, insn_name: &str| -> crate::Result<()> {\n            if let BranchOffset::Label(label) = pc {\n                let Some(Some((to_offset, target))) =\n                    self.label_to_resolved_offset.get(*label as usize)\n                else {\n                    crate::bail_corrupt_error!(\n                        \"Reference to undefined or unresolved label in {insn_name}: {label}\"\n                    );\n                };\n                *pc = BranchOffset::Offset(\n                    to_offset\n                        + if *target == JumpTarget::ExactlyThisInsn {\n                            0\n                        } else {\n                            1\n                        },\n                );\n            }\n            Ok(())\n        };\n        for (insn, _) in self.insns.iter_mut() {\n            match insn {\n                Insn::Init { target_pc } => {\n                    resolve(target_pc, \"Init\")?;\n                }\n                Insn::Eq {\n                    lhs: _lhs,\n                    rhs: _rhs,\n                    target_pc,\n                    ..\n                } => {\n                    resolve(target_pc, \"Eq\")?;\n                }\n                Insn::Ne {\n                    lhs: _lhs,\n                    rhs: _rhs,\n                    target_pc,\n                    ..\n                } => {\n                    resolve(target_pc, \"Ne\")?;\n                }\n                Insn::Lt {\n                    lhs: _lhs,\n                    rhs: _rhs,\n                    target_pc,\n                    ..\n                } => {\n                    resolve(target_pc, \"Lt\")?;\n                }\n                Insn::Le {\n                    lhs: _lhs,\n                    rhs: _rhs,\n                    target_pc,\n                    ..\n                } => {\n                    resolve(target_pc, \"Le\")?;\n                }\n                Insn::Gt {\n                    lhs: _lhs,\n                    rhs: _rhs,\n                    target_pc,\n                    ..\n                } => {\n                    resolve(target_pc, \"Gt\")?;\n                }\n                Insn::Ge {\n                    lhs: _lhs,\n                    rhs: _rhs,\n                    target_pc,\n                    ..\n                } => {\n                    resolve(target_pc, \"Ge\")?;\n                }\n                Insn::If {\n                    reg: _reg,\n                    target_pc,\n                    jump_if_null: _,\n                } => {\n                    resolve(target_pc, \"If\")?;\n                }\n                Insn::IfNot {\n                    reg: _reg,\n                    target_pc,\n                    jump_if_null: _,\n                } => {\n                    resolve(target_pc, \"IfNot\")?;\n                }\n                Insn::Rewind { pc_if_empty, .. } => {\n                    resolve(pc_if_empty, \"Rewind\")?;\n                }\n                Insn::Last { pc_if_empty, .. } => {\n                    resolve(pc_if_empty, \"Last\")?;\n                }\n                Insn::Goto { target_pc } => {\n                    resolve(target_pc, \"Goto\")?;\n                }\n                Insn::DecrJumpZero {\n                    reg: _reg,\n                    target_pc,\n                } => {\n                    resolve(target_pc, \"DecrJumpZero\")?;\n                }\n                Insn::SorterNext {\n                    cursor_id: _cursor_id,\n                    pc_if_next,\n                } => {\n                    resolve(pc_if_next, \"SorterNext\")?;\n                }\n                Insn::SorterSort { pc_if_empty, .. } => {\n                    resolve(pc_if_empty, \"SorterSort\")?;\n                }\n                Insn::SorterCompare {\n                    pc_when_nonequal: target_pc,\n                    ..\n                } => {\n                    resolve(target_pc, \"SorterCompare\")?;\n                }\n                Insn::NotNull {\n                    reg: _reg,\n                    target_pc,\n                } => {\n                    resolve(target_pc, \"NotNull\")?;\n                }\n                Insn::IfPos { target_pc, .. } => {\n                    resolve(target_pc, \"IfPos\")?;\n                }\n                Insn::Next { pc_if_next, .. } => {\n                    resolve(pc_if_next, \"Next\")?;\n                }\n                Insn::Once {\n                    target_pc_when_reentered,\n                    ..\n                } => {\n                    resolve(target_pc_when_reentered, \"Once\")?;\n                }\n                Insn::Prev { pc_if_prev, .. } => {\n                    resolve(pc_if_prev, \"Prev\")?;\n                }\n                Insn::InitCoroutine {\n                    yield_reg: _,\n                    jump_on_definition,\n                    start_offset,\n                } => {\n                    resolve(jump_on_definition, \"InitCoroutine\")?;\n                    resolve(start_offset, \"InitCoroutine\")?;\n                }\n                Insn::NotExists {\n                    cursor: _,\n                    rowid_reg: _,\n                    target_pc,\n                } => {\n                    resolve(target_pc, \"NotExists\")?;\n                }\n                Insn::Yield {\n                    yield_reg: _,\n                    end_offset,\n                    subtype_clear_start_reg: _,\n                    subtype_clear_count: _,\n                } => {\n                    resolve(end_offset, \"Yield\")?;\n                }\n                Insn::SeekRowid { target_pc, .. } => {\n                    resolve(target_pc, \"SeekRowid\")?;\n                }\n                Insn::Gosub { target_pc, .. } => {\n                    resolve(target_pc, \"Gosub\")?;\n                }\n                Insn::Jump {\n                    target_pc_eq,\n                    target_pc_lt,\n                    target_pc_gt,\n                } => {\n                    resolve(target_pc_eq, \"Jump\")?;\n                    resolve(target_pc_lt, \"Jump\")?;\n                    resolve(target_pc_gt, \"Jump\")?;\n                }\n                Insn::SeekGE { target_pc, .. } => resolve(target_pc, \"SeekGE\")?,\n                Insn::SeekGT { target_pc, .. } => resolve(target_pc, \"SeekGT\")?,\n                Insn::SeekLE { target_pc, .. } => resolve(target_pc, \"SeekLE\")?,\n                Insn::SeekLT { target_pc, .. } => resolve(target_pc, \"SeekLT\")?,\n                Insn::IdxGE { target_pc, .. } => resolve(target_pc, \"IdxGE\")?,\n                Insn::IdxLE { target_pc, .. } => resolve(target_pc, \"IdxLE\")?,\n                Insn::IdxGT { target_pc, .. } => resolve(target_pc, \"IdxGT\")?,\n                Insn::IdxLT { target_pc, .. } => resolve(target_pc, \"IdxLT\")?,\n                Insn::IndexMethodQuery { pc_if_empty, .. } => {\n                    resolve(pc_if_empty, \"IndexMethodQuery\")?;\n                }\n                Insn::IsNull { reg: _, target_pc } => resolve(target_pc, \"IsNull\")?,\n                Insn::VNext { pc_if_next, .. } => resolve(pc_if_next, \"VNext\")?,\n                Insn::VFilter { pc_if_empty, .. } => resolve(pc_if_empty, \"VFilter\")?,\n                Insn::RowSetRead { pc_if_empty, .. } => resolve(pc_if_empty, \"RowSetRead\")?,\n                Insn::RowSetTest { pc_if_found, .. } => resolve(pc_if_found, \"RowSetTest\")?,\n                Insn::NoConflict { target_pc, .. } => resolve(target_pc, \"NoConflict\")?,\n                Insn::Found { target_pc, .. } => resolve(target_pc, \"Found\")?,\n                Insn::NotFound { target_pc, .. } => resolve(target_pc, \"NotFound\")?,\n                Insn::FkIfZero { target_pc, .. } => resolve(target_pc, \"FkIfZero\")?,\n                Insn::Filter { target_pc, .. } => resolve(target_pc, \"Filter\")?,\n                Insn::HashProbe { target_pc, .. } => resolve(target_pc, \"HashProbe\")?,\n                Insn::HashNext { target_pc, .. } => resolve(target_pc, \"HashNext\")?,\n                Insn::HashDistinct { data } => resolve(&mut data.target_pc, \"HashDistinct\")?,\n                Insn::HashScanUnmatched { target_pc, .. } => {\n                    resolve(target_pc, \"HashScanUnmatched\")?\n                }\n                Insn::HashNextUnmatched { target_pc, .. } => {\n                    resolve(target_pc, \"HashNextUnmatched\")?\n                }\n                Insn::HashGraceInit { target_pc, .. } => resolve(target_pc, \"HashGraceInit\")?,\n                Insn::HashGraceLoadPartition { target_pc, .. } => {\n                    resolve(target_pc, \"HashGraceLoadPartition\")?\n                }\n                Insn::HashGraceNextProbe { target_pc, .. } => {\n                    resolve(target_pc, \"HashGraceNextProbe\")?\n                }\n                Insn::HashGraceAdvancePartition { target_pc, .. } => {\n                    resolve(target_pc, \"HashGraceAdvancePartition\")?\n                }\n                Insn::Program {\n                    ignore_jump_target, ..\n                } => resolve(ignore_jump_target, \"Program\")?,\n                _ => {}\n            }\n        }\n        self.label_to_resolved_offset.clear();\n        Ok(())\n    }\n\n    /// Set a cursor override for a table. When resolving a table cursor for this table,\n    /// the override cursor will be used instead of the normal resolution.\n    pub fn set_cursor_override(&mut self, table_ref_id: TableInternalId, cursor_id: CursorID) {\n        self.cursor_overrides.insert(table_ref_id.into(), cursor_id);\n    }\n\n    /// Clear the cursor override for a table.\n    pub fn clear_cursor_override(&mut self, table_ref_id: TableInternalId) {\n        self.cursor_overrides.remove(&table_ref_id.into());\n    }\n\n    /// Clear all cursor overrides.\n    pub fn clear_all_cursor_overrides(&mut self) {\n        self.cursor_overrides.clear();\n    }\n\n    /// Check if a cursor override is active for a given table.\n    pub fn has_cursor_override(&self, table_ref_id: TableInternalId) -> bool {\n        self.cursor_overrides.contains_key(&table_ref_id.into())\n    }\n\n    // translate [CursorKey] to cursor id\n    pub fn resolve_cursor_id_safe(&self, key: &CursorKey) -> Option<CursorID> {\n        // Check cursor overrides first, only apply override for table cursors.\n        // Index cursor lookups are not overridden because when a cursor override is active,\n        // the calling code (translate_expr) should skip index logic entirely.\n        if key.index.is_none() && !key.is_build {\n            let table_id: usize = key.table_reference_id.into();\n            if let Some(&cursor_id) = self.cursor_overrides.get(&table_id) {\n                return Some(cursor_id);\n            }\n        }\n        self.cursor_ref\n            .iter()\n            .position(|(k, _)| k.as_ref().is_some_and(|k| k.equals(key)))\n    }\n\n    pub fn resolve_cursor_id(&self, key: &CursorKey) -> CursorID {\n        self.resolve_cursor_id_safe(key)\n            .unwrap_or_else(|| panic!(\"Cursor not found: {key:?}\"))\n    }\n\n    /// Resolve the first allocated index cursor for a given table reference.\n    /// This method exists due to a limitation of our translation system where\n    /// a subquery that references an outer query table cannot know whether a\n    /// table cursor, index cursor, or both were opened for that table reference.\n    /// Hence: currently we first try to resolve a table cursor, and if that fails,\n    /// we resolve an index cursor via this method.\n    pub fn resolve_any_index_cursor_id_for_table(&self, table_ref_id: TableInternalId) -> CursorID {\n        self.resolve_any_index_cursor_id_for_table_safe(table_ref_id)\n            .unwrap_or_else(|| panic!(\"No index cursor found for table {table_ref_id}\"))\n    }\n\n    pub fn resolve_any_index_cursor_id_for_table_safe(\n        &self,\n        table_ref_id: TableInternalId,\n    ) -> Option<CursorID> {\n        self.cursor_ref.iter().position(|(k, _)| {\n            k.as_ref()\n                .is_some_and(|k| k.table_reference_id == table_ref_id && k.index.is_some())\n        })\n    }\n\n    /// Resolve the [Index] that a given cursor is associated with.\n    pub fn resolve_index_for_cursor_id(&self, cursor_id: CursorID) -> Arc<Index> {\n        let cursor_ref = &self\n            .cursor_ref\n            .get(cursor_id)\n            .unwrap_or_else(|| panic!(\"Cursor not found: {cursor_id}\"))\n            .1;\n        let CursorType::BTreeIndex(index) = cursor_ref else {\n            panic!(\"Cursor is not an index: {cursor_id}\");\n        };\n        index.clone()\n    }\n\n    /// Get the [CursorType] of a given cursor.\n    pub fn get_cursor_type(&self, cursor_id: CursorID) -> Option<&CursorType> {\n        self.cursor_ref\n            .get(cursor_id)\n            .map(|(_, cursor_type)| cursor_type)\n    }\n\n    pub fn set_collation(&mut self, c: Option<(CollationSeq, bool)>) {\n        self.collation = c\n    }\n\n    pub fn curr_collation_ctx(&self) -> Option<(CollationSeq, bool)> {\n        self.collation\n    }\n\n    pub fn curr_collation(&self) -> Option<CollationSeq> {\n        self.collation.map(|c| c.0)\n    }\n\n    pub fn reset_collation(&mut self) {\n        self.collation = None;\n    }\n\n    #[inline]\n    pub fn nested<T>(&mut self, body: impl FnOnce(&mut Self) -> T) -> T {\n        self.incr_nesting();\n        let res = body(self);\n        self.decr_nesting();\n        res\n    }\n\n    #[inline]\n    fn incr_nesting(&mut self) {\n        self.nested_level += 1;\n    }\n\n    #[inline]\n    fn decr_nesting(&mut self) {\n        self.nested_level -= 1;\n    }\n\n    /// Returns true if we are inside a nested subquery context.\n    #[inline]\n    pub fn is_nested(&self) -> bool {\n        self.nested_level > 0\n    }\n\n    /// Initialize the program with basic setup and return initial metadata and labels\n    pub fn prologue(&mut self) {\n        if self.is_subprogram {\n            // Subprograms (triggers, FK actions) don't need Transaction - they run within parent's tx\n            self.init_label = self.allocate_label();\n            self.emit_insn(Insn::Init {\n                target_pc: self.init_label,\n            });\n            self.preassign_label_to_next_insn(self.init_label);\n            self.start_offset = self.offset();\n            return;\n        }\n        if self.nested_level == 0 {\n            self.init_label = self.allocate_label();\n\n            self.emit_insn(Insn::Init {\n                target_pc: self.init_label,\n            });\n\n            self.start_offset = self.offset();\n        }\n    }\n\n    /// Tries to mirror: https://github.com/sqlite/sqlite/blob/e77e589a35862f6ac9c4141cfd1beb2844b84c61/src/build.c#L5379\n    pub fn begin_write_operation(&mut self) {\n        self.txn_mode = TransactionMode::Write;\n        self.write_databases.insert(0);\n    }\n\n    /// Begin a write operation on a specific database (for attached databases).\n    pub fn begin_write_on_database(&mut self, database_id: usize, schema_cookie: u32) {\n        self.txn_mode = TransactionMode::Write;\n        self.write_databases.insert(database_id);\n        self.write_database_cookies\n            .insert(database_id, schema_cookie);\n    }\n\n    pub fn begin_read_operation(&mut self) {\n        // Just override the transaction mode when it is None\n        if matches!(self.txn_mode, TransactionMode::None) {\n            self.txn_mode = TransactionMode::Read;\n        }\n    }\n\n    /// Begin a read operation on a specific attached database.\n    /// This ensures a Transaction instruction is emitted for the attached pager\n    /// so that a WAL read lock is acquired.\n    pub fn begin_read_on_database(&mut self, database_id: usize, schema_cookie: u32) {\n        self.begin_read_operation();\n        if crate::is_attached_db(database_id) {\n            self.read_databases.insert(database_id);\n            self.read_database_cookies\n                .insert(database_id, schema_cookie);\n        }\n    }\n\n    pub fn begin_concurrent_operation(&mut self) {\n        self.txn_mode = TransactionMode::Concurrent;\n    }\n\n    /// Indicates the rollback behvaiour for the halt instruction in epilogue\n    pub fn rollback(&mut self) {\n        self.rollback = true;\n    }\n\n    /// Clean up and finalize the program, resolving any remaining labels\n    /// Note that although these are the final instructions, typically an SQLite\n    /// query will jump to the Transaction instruction via init_label.\n    pub fn epilogue(&mut self, schema: &Schema) {\n        if self.is_subprogram {\n            // Subprograms (triggers, FK actions) just emit Halt without Transaction\n            let description = if self.trigger.is_some() {\n                \"trigger\"\n            } else {\n                \"fk action\"\n            };\n            self.emit_insn(Insn::Halt {\n                err_code: 0,\n                description: description.to_string(),\n                on_error: None,\n                description_reg: None,\n            });\n            return;\n        }\n        if self.nested_level == 0 {\n            // \"rollback\" flag is used to determine if halt should rollback the transaction.\n            self.emit_halt(self.rollback);\n            self.preassign_label_to_next_insn(self.init_label);\n\n            if !matches!(self.txn_mode, TransactionMode::None) {\n                // Emit Transaction for main database always\n                self.emit_insn(Insn::Transaction {\n                    db: crate::MAIN_DB_ID,\n                    tx_mode: self.txn_mode,\n                    schema_cookie: schema.schema_version,\n                });\n                // Emit Transaction for each attached database that needs a write\n                for &db_id in &self.write_databases.clone() {\n                    if crate::is_attached_db(db_id) {\n                        let cookie = self\n                            .write_database_cookies\n                            .get(&db_id)\n                            .copied()\n                            .unwrap_or(0);\n                        self.emit_insn(Insn::Transaction {\n                            db: db_id,\n                            tx_mode: self.txn_mode,\n                            schema_cookie: cookie,\n                        });\n                    }\n                }\n                // Emit Transaction for each attached database that only needs a read\n                // (skip databases already covered by write_databases)\n                for &db_id in &self.read_databases.clone() {\n                    if !self.write_databases.contains(&db_id) {\n                        let cookie = self.read_database_cookies.get(&db_id).copied().unwrap_or(0);\n                        self.emit_insn(Insn::Transaction {\n                            db: db_id,\n                            tx_mode: TransactionMode::Read,\n                            schema_cookie: cookie,\n                        });\n                    }\n                }\n            }\n\n            if !self.constant_spans.is_empty() {\n                self.emit_constant_insns();\n            }\n            self.emit_insn(Insn::Goto {\n                target_pc: self.start_offset,\n            });\n        }\n    }\n\n    /// Checks whether `table` or any of its indices has been opened in the program\n    pub fn is_table_open(&self, table: &Table) -> bool {\n        self.table_references.contains_table(table)\n    }\n\n    /// Returns true if the cursor is a BTreeTable cursor.\n    pub fn cursor_is_btree(&self, cursor_id: CursorID) -> bool {\n        matches!(self.cursor_ref[cursor_id].1, CursorType::BTreeTable(_))\n    }\n\n    #[inline]\n    pub fn cursor_loop(&mut self, cursor_id: CursorID, f: impl Fn(&mut ProgramBuilder, usize)) {\n        let loop_start = self.allocate_label();\n        let loop_end = self.allocate_label();\n\n        self.emit_insn(Insn::Rewind {\n            cursor_id,\n            pc_if_empty: loop_end,\n        });\n        self.preassign_label_to_next_insn(loop_start);\n\n        let rowid = self.alloc_register();\n\n        self.emit_insn(Insn::RowId {\n            cursor_id,\n            dest: rowid,\n        });\n\n        self.emit_insn(Insn::IsNull {\n            reg: rowid,\n            target_pc: loop_end,\n        });\n\n        f(self, rowid);\n\n        self.emit_insn(Insn::Next {\n            cursor_id,\n            pc_if_next: loop_start,\n        });\n        self.preassign_label_to_next_insn(loop_end);\n    }\n\n    pub fn emit_column_or_rowid(&mut self, cursor_id: CursorID, column: usize, out: usize) {\n        let (_, cursor_type) = self.cursor_ref.get(cursor_id).expect(\"cursor_id is valid\");\n        if let CursorType::BTreeTable(btree) = cursor_type {\n            let column_def = btree\n                .columns\n                .get(column)\n                .expect(\"column index out of bounds\");\n            if column_def.is_rowid_alias() {\n                // Consume the suppress_column_default flag so it doesn't\n                // leak to the next column (emit_column normally consumes it).\n                self.suppress_column_default = false;\n                self.emit_insn(Insn::RowId {\n                    cursor_id,\n                    dest: out,\n                });\n            } else {\n                self.emit_column(cursor_id, column, out);\n            }\n        } else {\n            self.emit_column(cursor_id, column, out);\n        }\n    }\n\n    fn emit_column(&mut self, cursor_id: CursorID, column: usize, out: usize) {\n        let (_, cursor_type) = self.cursor_ref.get(cursor_id).expect(\"cursor_id is valid\");\n\n        let default = 'value: {\n            let default = match cursor_type {\n                CursorType::BTreeTable(btree) => &btree.columns[column].default,\n                CursorType::BTreeIndex(index) => &index.columns[column].default,\n                CursorType::MaterializedView(btree, _) => &btree.columns[column].default,\n                _ => break 'value None,\n            };\n\n            let Some(ref default_expr) = default else {\n                break 'value None;\n            };\n\n            // Try to constant-fold the default expression into a Value for the\n            // Column instruction. Non-constant defaults (e.g. DEFAULT (ABS(-5)))\n            // can't be folded and yield None here — that's correct: they are\n            // evaluated at INSERT time via translate_expr. The Column default\n            // only matters for pre-existing rows after ALTER TABLE ADD COLUMN,\n            // and ALTER TABLE already validates that the default is constant.\n            let mut value = match crate::translate::alter::eval_constant_default_value(default_expr)\n            {\n                Ok(v) => v,\n                Err(_) => break 'value None,\n            };\n\n            // Apply column affinity to the default value, matching SQLite's\n            // sqlite3ColumnDefault which calls sqlite3ValueFromExpr with\n            // pCol->affinity. This ensures e.g. ALTER TABLE ADD COLUMN c TEXT\n            // DEFAULT 0 returns text \"0\" rather than integer 0 for pre-existing rows.\n            let affinity = match cursor_type {\n                CursorType::BTreeTable(btree) => btree.columns[column].affinity(),\n                CursorType::MaterializedView(btree, _) => btree.columns[column].affinity(),\n                _ => Affinity::Blob,\n            };\n            if let Some(converted) = affinity.convert(&value) {\n                value = match converted {\n                    either::Either::Left(val_ref) => val_ref.to_owned(),\n                    either::Either::Right(val) => val,\n                };\n            }\n\n            Some(value)\n        };\n\n        let default = if self.suppress_column_default {\n            self.suppress_column_default = false;\n            None\n        } else {\n            default\n        };\n\n        self.emit_insn(Insn::Column {\n            cursor_id,\n            column,\n            dest: out,\n            default,\n        });\n    }\n\n    pub fn build_prepared_program(\n        mut self,\n        prepare_context: PrepareContext,\n        change_cnt_on: bool,\n        sql: &str,\n    ) -> crate::Result<PreparedProgram> {\n        self.resolve_labels()?;\n\n        self.parameters.list.dedup();\n\n        // Mirrors SQLite's: usesStmtJournal = isMultiWrite && mayAbort\n        // Statement journals are only needed when a statement writes multiple rows AND could\n        // abort midway (e.g. constraint violation). Single-row writes are atomic and don't\n        // need statement-level rollback. Both flags default to true; specific translate paths\n        // (e.g., single-row INSERT) set is_multi_write=false to opt out.\n        let needs_stmt_subtransactions = matches!(self.txn_mode, TransactionMode::Write)\n            && self.is_multi_write\n            && self.may_abort;\n\n        let contains_trigger_subprograms = self\n            .insns\n            .iter()\n            .any(|(insn, _)| matches!(insn, Insn::Program { .. }));\n\n        let prepared = PreparedProgram {\n            max_registers: self.next_free_register,\n            insns: self.insns,\n            cursor_ref: self.cursor_ref,\n            comments: self.comments,\n            parameters: self.parameters,\n            change_cnt_on,\n            result_columns: self.result_columns,\n            table_references: self.table_references,\n            sql: sql.to_string(),\n            needs_stmt_subtransactions: crate::Arc::new(crate::AtomicBool::new(\n                needs_stmt_subtransactions,\n            )),\n            trigger: self.trigger.take(),\n            is_subprogram: self.is_subprogram,\n            contains_trigger_subprograms,\n            resolve_type: self.resolve_type,\n            prepare_context,\n            write_databases: self.write_databases,\n            read_databases: self.read_databases,\n        };\n        Ok(prepared)\n    }\n\n    pub fn build(\n        self,\n        connection: Arc<Connection>,\n        change_cnt_on: bool,\n        sql: &str,\n    ) -> crate::Result<Program> {\n        let prepare_context = PrepareContext::from_connection(&connection);\n        let prepared = self.build_prepared_program(prepare_context, change_cnt_on, sql)?;\n        Ok(Program::from_prepared(Arc::new(prepared), connection))\n    }\n}\n"
  },
  {
    "path": "core/vdbe/execute.rs",
    "content": "use crate::error::SQLITE_CONSTRAINT_UNIQUE;\nuse crate::function::AlterTableFunc;\nuse crate::mvcc::cursor::{MvccCursorType, NextRowidResult};\nuse crate::mvcc::database::CheckpointStateMachine;\nuse crate::mvcc::MvccClock;\nuse crate::numeric::Numeric;\nuse crate::schema::{Schema, Table, SQLITE_SEQUENCE_TABLE_NAME};\nuse crate::state_machine::StateMachine;\nuse crate::storage::btree::{\n    integrity_check, CursorTrait, IntegrityCheckError, IntegrityCheckState, PageCategory,\n};\nuse crate::storage::database::DatabaseFile;\nuse crate::storage::journal_mode;\nuse crate::storage::page_cache::PageCache;\nuse crate::storage::pager::{default_page1, CreateBTreeFlags, PageRef, SavepointResult};\nuse crate::storage::sqlite3_ondisk::{DatabaseHeader, PageSize, RawVersion};\nuse crate::translate::collate::CollationSeq;\nuse crate::translate::pragma::TURSO_CDC_VERSION_TABLE_NAME;\nuse crate::types::{\n    compare_immutable, compare_records_generic, AsValueRef, Extendable, IOCompletions, IOResult,\n    ImmutableRecord, IndexInfo, SeekResult, Text, ValueIterator,\n};\nuse crate::util::{\n    escape_sql_string_literal, normalize_ident, rename_identifiers,\n    rename_identifiers_scoped_when_clause, rewrite_check_expr_table_refs,\n    rewrite_column_references_if_needed, rewrite_fk_parent_cols_if_self_ref,\n    rewrite_fk_parent_table_if_needed, rewrite_inline_col_fk_target_if_needed,\n    rewrite_trigger_cmd_column_refs, rewrite_trigger_cmd_table_refs,\n    rewrite_view_sql_for_column_rename, trim_ascii_whitespace, RewrittenView,\n};\nuse crate::vdbe::affinity::{\n    apply_numeric_affinity, try_for_float, Affinity, NumericParseResult, ParsedNumber,\n};\nuse crate::vdbe::hash_table::{\n    HashEntry, HashInsertResult, HashTable, HashTableConfig, PendingHashInsert, DEFAULT_MEM_BUDGET,\n};\nuse crate::vdbe::insn::InsertFlags;\nuse crate::vdbe::metrics::HashJoinMetrics;\nuse crate::vdbe::value::ComparisonOp;\nuse crate::vdbe::{\n    registers_to_ref_values, DeferredSeekState, EndStatement, OpHashBuildState, OpHashProbeState,\n    StepResult, TxnCleanup,\n};\nuse crate::vector::{\n    vector1bit, vector32, vector32_sparse, vector64, vector8, vector_concat, vector_distance_cos,\n    vector_distance_dot, vector_distance_jaccard, vector_distance_l2, vector_extract, vector_slice,\n};\nuse crate::{\n    error::{\n        LimboError, SQLITE_CONSTRAINT, SQLITE_CONSTRAINT_CHECK, SQLITE_CONSTRAINT_FOREIGNKEY,\n        SQLITE_CONSTRAINT_NOTNULL, SQLITE_CONSTRAINT_PRIMARYKEY, SQLITE_CONSTRAINT_TRIGGER,\n        SQLITE_ERROR,\n    },\n    ext::ExtValue,\n    function::{AggFunc, ExtFunc, MathFunc, MathFuncArity, ScalarFunc, VectorFunc},\n    functions::{\n        datetime::{\n            exec_date, exec_datetime_full, exec_julianday, exec_strftime, exec_time, exec_unixepoch,\n        },\n        printf::exec_printf,\n    },\n    stats::StatAccum,\n    translate::emitter::TransactionMode,\n};\nuse crate::{\n    get_cursor, CaptureDataChangesInfo, CheckpointMode, Completion, Connection, DatabaseStorage,\n    IOExt, MvCursor, NonNan, QueryMode,\n};\nuse crate::{CdcVersion, Statement};\nuse branches::{mark_unlikely, unlikely};\nuse either::Either;\nuse smallvec::SmallVec;\nuse std::any::Any;\nuse std::env::temp_dir;\nuse std::str::FromStr;\nuse std::{\n    borrow::BorrowMut,\n    num::NonZero,\n    sync::{atomic::Ordering, Arc},\n};\nuse turso_macros::match_ignore_ascii_case;\n\nuse crate::pseudo::PseudoCursor;\n\nuse crate::storage::btree::{BTreeCursor, BTreeKey};\n\nuse crate::{\n    storage::wal::CheckpointResult,\n    types::{AggContext, Cursor, ExternalAggState, SeekKey, SeekOp, SumAggState, Value, ValueType},\n    util::{cast_real_to_integer, checked_cast_text_to_numeric, parse_schema_rows},\n    vdbe::{\n        builder::CursorType,\n        insn::{IdxInsertFlags, Insn, SavepointOp},\n    },\n};\n\nuse crate::{connection::Row, info, turso_assert, OpenFlags, TransactionState, ValueRef};\n\nuse super::{\n    array::{\n        array_values_from_blob, compare_arrays, compute_array_length, exec_array_append,\n        exec_array_cat, exec_array_contains, exec_array_contains_all, exec_array_overlap,\n        exec_array_position, exec_array_prepend, exec_array_remove, exec_array_slice,\n        exec_array_to_string, exec_string_to_array, make_array_from_registers, parse_text_array,\n        serialize_array_from_blob, values_to_record_blob,\n    },\n    insn::{Cookie, RegisterOrLiteral},\n    CommitState,\n};\nuse crate::sync::{Mutex, RwLock};\nuse turso_parser::ast::{self, ForeignKeyClause, Name, ResolveType};\nuse turso_parser::parser::Parser;\n\nuse super::sorter::Sorter;\n\n#[cfg(feature = \"json\")]\nuse crate::{\n    function::JsonFunc, json, json::convert_dbtype_to_raw_jsonb, json::get_json,\n    json::is_json_valid, json::json_array, json::json_array_length, json::json_arrow_extract,\n    json::json_arrow_shift_extract, json::json_error_position, json::json_extract,\n    json::json_from_raw_bytes_agg, json::json_insert, json::json_object, json::json_patch,\n    json::json_quote, json::json_remove, json::json_replace, json::json_set, json::json_type,\n    json::jsonb, json::jsonb_array, json::jsonb_extract, json::jsonb_insert, json::jsonb_object,\n    json::jsonb_patch, json::jsonb_remove, json::jsonb_replace, json::jsonb_set,\n};\n\nuse super::{make_record, Program, ProgramState, Register};\n\n#[cfg(feature = \"fs\")]\nuse crate::connection::resolve_ext_path;\nuse crate::{bail_constraint_error, must_be_btree_cursor, MvStore, Pager, Result};\n\n/// Macro to destructure an Insn enum variant, only to be used when it\n/// is *impossible* to be another variant.\nmacro_rules! load_insn {\n    ($variant:ident { $($field:tt $(: $binding:pat)?),* $(,)? }, $insn:expr) => {\n        #[cfg(debug_assertions)]\n        let Insn::$variant { $($field $(: $binding)?),* } = $insn else {\n            panic!(\"Expected Insn::{}, got {:?}\", stringify!($variant), $insn);\n        };\n        #[cfg(not(debug_assertions))]\n        let Insn::$variant { $($field $(: $binding)?),*} = $insn else {\n             // this will optimize away the branch\n            unsafe { std::hint::unreachable_unchecked() };\n        };\n    };\n}\n\nmacro_rules! return_if_io {\n    ($expr:expr) => {\n        match $expr {\n            Ok(IOResult::Done(v)) => v,\n            Ok(IOResult::IO(io)) => return Ok(InsnFunctionStepResult::IO(io)),\n            Err(err) => {\n                mark_unlikely();\n                return Err(err);\n            }\n        }\n    };\n}\n\nmacro_rules! check_arg_count {\n    ($actual:expr, $expected:expr) => {\n        if unlikely($actual != $expected) {\n            return Err(LimboError::InternalError(format!(\n                \"expected {} argument(s), got {}\",\n                $expected, $actual\n            )));\n        }\n    };\n}\n\npub type InsnFunction =\n    fn(&Program, &mut ProgramState, &Insn, &Arc<Pager>) -> Result<InsnFunctionStepResult>;\n\n/// Parse a Value (text, int, float, or blob) into a BigDecimal.\nfn value_to_bigdecimal(val: &Value) -> Result<bigdecimal::BigDecimal> {\n    use bigdecimal::BigDecimal;\n    use std::str::FromStr;\n    match val {\n        Value::Numeric(Numeric::Integer(i)) => Ok(BigDecimal::from(*i)),\n        Value::Numeric(Numeric::Float(f)) => BigDecimal::from_str(&f.to_string())\n            .map_err(|_| LimboError::Constraint(format!(\"invalid numeric value: {f}\"))),\n        Value::Text(t) => BigDecimal::from_str(&t.value)\n            .map_err(|_| LimboError::Constraint(format!(\"invalid numeric value: \\\"{}\\\"\", t.value))),\n        Value::Blob(b) => crate::numeric::decimal::blob_to_bigdecimal(b),\n        _ => Err(LimboError::Constraint(format!(\n            \"cannot convert to numeric: \\\"{val}\\\"\"\n        ))),\n    }\n}\n\n/// Create a sort comparator closure from a SortComparatorType enum.\nfn make_sort_comparator(\n    cmp_type: &crate::vdbe::insn::SortComparatorType,\n) -> crate::vdbe::sorter::SortComparator {\n    use crate::types::ValueRef;\n    use crate::vdbe::insn::SortComparatorType;\n    use std::cmp::Ordering;\n    match cmp_type {\n        SortComparatorType::NumericLt => {\n            std::sync::Arc::new(|a: &ValueRef, b: &ValueRef| -> Ordering {\n                match (a, b) {\n                    (ValueRef::Null, ValueRef::Null) => Ordering::Equal,\n                    (ValueRef::Null, _) => Ordering::Less,\n                    (_, ValueRef::Null) => Ordering::Greater,\n                    _ => {\n                        // Decode from ValueRef to Value for value_to_bigdecimal\n                        let a_val = a.to_owned();\n                        let b_val = b.to_owned();\n                        match (value_to_bigdecimal(&a_val), value_to_bigdecimal(&b_val)) {\n                            (Ok(a_dec), Ok(b_dec)) => a_dec.cmp(&b_dec),\n                            _ => a.partial_cmp(b).unwrap_or(Ordering::Equal),\n                        }\n                    }\n                }\n            })\n        }\n        SortComparatorType::StringReverse => {\n            std::sync::Arc::new(|a: &ValueRef, b: &ValueRef| -> Ordering {\n                fn reverse_str(v: &ValueRef) -> String {\n                    match v {\n                        ValueRef::Text(t) => t.to_string().chars().rev().collect(),\n                        _ => String::new(),\n                    }\n                }\n                match (a, b) {\n                    (ValueRef::Null, ValueRef::Null) => Ordering::Equal,\n                    (ValueRef::Null, _) => Ordering::Less,\n                    (_, ValueRef::Null) => Ordering::Greater,\n                    _ => reverse_str(a).cmp(&reverse_str(b)),\n                }\n            })\n        }\n        SortComparatorType::TestUintLt => {\n            std::sync::Arc::new(|a: &ValueRef, b: &ValueRef| -> Ordering {\n                fn to_u64(v: &ValueRef) -> Option<u64> {\n                    match v {\n                        ValueRef::Null => None,\n                        ValueRef::Numeric(Numeric::Integer(i)) => {\n                            if *i >= 0 {\n                                Some(*i as u64)\n                            } else {\n                                None\n                            }\n                        }\n                        ValueRef::Text(t) => t.to_string().parse::<u64>().ok(),\n                        _ => None,\n                    }\n                }\n                match (a, b) {\n                    (ValueRef::Null, ValueRef::Null) => Ordering::Equal,\n                    (ValueRef::Null, _) => Ordering::Less,\n                    (_, ValueRef::Null) => Ordering::Greater,\n                    _ => match (to_u64(a), to_u64(b)) {\n                        (Some(a), Some(b)) => a.cmp(&b),\n                        _ => a.partial_cmp(b).unwrap_or(Ordering::Equal),\n                    },\n                }\n            })\n        }\n        SortComparatorType::ArrayLt => {\n            std::sync::Arc::new(|a: &ValueRef, b: &ValueRef| -> Ordering {\n                match (a, b) {\n                    (ValueRef::Null, ValueRef::Null) => Ordering::Equal,\n                    (ValueRef::Null, _) => Ordering::Less,\n                    (_, ValueRef::Null) => Ordering::Greater,\n                    (ValueRef::Blob(a_blob), ValueRef::Blob(b_blob)) => {\n                        crate::vdbe::array::compare_arrays(a_blob, b_blob)\n                            .unwrap_or(Ordering::Equal)\n                    }\n                    (ValueRef::Text(a_text), ValueRef::Text(b_text)) => {\n                        let a_vals = crate::vdbe::array::parse_text_array(a_text);\n                        let b_vals = crate::vdbe::array::parse_text_array(b_text);\n                        match (a_vals, b_vals) {\n                            (Some(av), Some(bv)) => {\n                                let a_blob = crate::vdbe::array::values_to_record_blob(&av);\n                                let b_blob = crate::vdbe::array::values_to_record_blob(&bv);\n                                if let (Value::Blob(ab), Value::Blob(bb)) = (&a_blob, &b_blob) {\n                                    crate::vdbe::array::compare_arrays(ab, bb)\n                                        .unwrap_or(Ordering::Equal)\n                                } else {\n                                    Ordering::Equal\n                                }\n                            }\n                            _ => a.partial_cmp(b).unwrap_or(Ordering::Equal),\n                        }\n                    }\n                    _ => a.partial_cmp(b).unwrap_or(Ordering::Equal),\n                }\n            })\n        }\n    }\n}\n\n/// Compare two values using the specified collation for text values.\n/// Non-text values are compared using their natural ordering.\nfn compare_with_collation(\n    lhs: &Value,\n    rhs: &Value,\n    collation: Option<CollationSeq>,\n) -> std::cmp::Ordering {\n    match (lhs, rhs) {\n        (Value::Text(lhs_text), Value::Text(rhs_text)) => {\n            if let Some(coll) = collation {\n                coll.compare_strings(lhs_text.as_str(), rhs_text.as_str())\n            } else {\n                lhs.cmp(rhs)\n            }\n        }\n        _ => lhs.cmp(rhs),\n    }\n}\n\npub enum InsnFunctionStepResult {\n    Done,\n    IO(IOCompletions),\n    Row,\n    Step,\n}\n\nimpl<T> From<IOResult<T>> for InsnFunctionStepResult {\n    fn from(value: IOResult<T>) -> Self {\n        match value {\n            IOResult::Done(_) => InsnFunctionStepResult::Done,\n            IOResult::IO(io) => InsnFunctionStepResult::IO(io),\n        }\n    }\n}\n\npub fn op_init(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(Init { target_pc }, insn);\n    if unlikely(!target_pc.is_offset()) {\n        crate::bail_corrupt_error!(\"Unresolved label: {target_pc:?}\");\n    }\n    state.pc = target_pc.as_offset_int();\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_add(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(Add { lhs, rhs, dest }, insn);\n    state.registers[*dest].set_value(\n        state.registers[*lhs]\n            .get_value()\n            .exec_add(state.registers[*rhs].get_value()),\n    );\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_subtract(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(Subtract { lhs, rhs, dest }, insn);\n    state.registers[*dest].set_value(\n        state.registers[*lhs]\n            .get_value()\n            .exec_subtract(state.registers[*rhs].get_value()),\n    );\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_multiply(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(Multiply { lhs, rhs, dest }, insn);\n    state.registers[*dest].set_value(\n        state.registers[*lhs]\n            .get_value()\n            .exec_multiply(state.registers[*rhs].get_value()),\n    );\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_divide(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(Divide { lhs, rhs, dest }, insn);\n    state.registers[*dest].set_value(\n        state.registers[*lhs]\n            .get_value()\n            .exec_divide(state.registers[*rhs].get_value()),\n    );\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_drop_index(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(DropIndex { index, db }, insn);\n    let conn = program.connection.clone();\n    let is_mvcc = conn.mv_store_for_db(*db).is_some();\n    conn.with_database_schema_mut(*db, |schema| {\n        // In MVCC mode, track dropped index root pages so integrity_check knows about them.\n        // The btree pages won't be freed until checkpoint, so integrity_check needs to\n        // include them to avoid \"page never used\" false positives.\n        if is_mvcc && index.root_page > 0 {\n            schema.dropped_root_pages.insert(index.root_page);\n        }\n        schema.remove_index(index);\n    });\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_remainder(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(Remainder { lhs, rhs, dest }, insn);\n    state.registers[*dest].set_value(\n        state.registers[*lhs]\n            .get_value()\n            .exec_remainder(state.registers[*rhs].get_value()),\n    );\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_bit_and(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(BitAnd { lhs, rhs, dest }, insn);\n    state.registers[*dest].set_value(\n        state.registers[*lhs]\n            .get_value()\n            .exec_bit_and(state.registers[*rhs].get_value()),\n    );\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_bit_or(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(BitOr { lhs, rhs, dest }, insn);\n    state.registers[*dest].set_value(\n        state.registers[*lhs]\n            .get_value()\n            .exec_bit_or(state.registers[*rhs].get_value()),\n    );\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_bit_not(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(BitNot { reg, dest }, insn);\n    state.registers[*dest].set_value(state.registers[*reg].get_value().exec_bit_not());\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_checkpoint(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        Checkpoint {\n            database,\n            checkpoint_mode,\n            dest,\n        },\n        insn\n    );\n    if !program.connection.auto_commit.load(Ordering::SeqCst) {\n        // TODO: sqlite returns \"Runtime error: database table is locked (6)\" when a table is in use\n        // when a checkpoint is attempted. We don't have table locks, so return TableLocked for any\n        // attempt to checkpoint in an interactive transaction. This does not end the transaction,\n        // however.\n        return Err(LimboError::TableLocked);\n    }\n    let pager = program.get_pager_from_database_index(database);\n    // In autocommit mode, this statement can still hold an implicit read tx.\n    // RESTART/TRUNCATE checkpoint needs to restart WAL and may fail with Busy\n    // if we keep our own statement read slot while checkpointing.\n    if matches!(\n        checkpoint_mode,\n        CheckpointMode::Restart | CheckpointMode::Truncate { .. }\n    ) && pager.holds_read_lock()\n    {\n        pager.end_read_tx();\n    }\n    // Re-fetch mv_store from connection to get the latest value.\n    // This is necessary because the mv_store may have been set by a preceding JournalMode instruction\n    // (e.g., when switching from WAL to MVCC mode via `PRAGMA journal_mode = \"mvcc\"`).\n    let mv_store = program.connection.mv_store_for_db(*database);\n    if let Some(mv_store) = mv_store.as_ref() {\n        if !matches!(checkpoint_mode, CheckpointMode::Truncate { .. }) {\n            return Err(LimboError::InvalidArgument(\n                \"Only TRUNCATE checkpoint mode is supported for MVCC\".to_string(),\n            ));\n        }\n        use crate::state_machine::{StateTransition, TransitionResult};\n        let mut ckpt_sm = CheckpointStateMachine::new(\n            pager.clone(),\n            mv_store.clone(),\n            program.connection.clone(),\n            true,\n            program.connection.get_sync_mode(),\n        );\n        let CheckpointResult {\n            wal_max_frame,\n            wal_total_backfilled,\n            ..\n        } = loop {\n            match ckpt_sm.step(&()) {\n                Ok(TransitionResult::Continue) => {}\n                Ok(TransitionResult::Done(result)) => break result,\n                Ok(TransitionResult::Io(iocompletions)) => {\n                    if let Err(err) = iocompletions.wait(pager.io.as_ref()) {\n                        ckpt_sm.cleanup_after_external_io_error();\n                        return Err(err);\n                    }\n                }\n                Err(err) => return Err(err),\n            }\n        };\n        // https://sqlite.org/pragma.html#pragma_wal_checkpoint\n        // 1st col: 1 (checkpoint SQLITE_BUSY) or 0 (not busy).\n        state.registers[*dest].set_int(0);\n        // 2nd col: # modified pages written to wal file\n        state.registers[*dest + 1].set_int(wal_max_frame as i64);\n        // 3rd col: # pages moved to db after checkpoint\n        state.registers[*dest + 2].set_int(wal_total_backfilled as i64);\n\n        state.pc += 1;\n        return Ok(InsnFunctionStepResult::Step);\n    }\n    let step_result = pager.checkpoint(*checkpoint_mode, program.connection.get_sync_mode(), true);\n    match step_result {\n        Ok(IOResult::Done(CheckpointResult {\n            wal_max_frame,\n            wal_total_backfilled,\n            ..\n        })) => {\n            // https://sqlite.org/pragma.html#pragma_wal_checkpoint\n            // 1st col: 1 (checkpoint SQLITE_BUSY) or 0 (not busy).\n            state.registers[*dest].set_int(0);\n            // 2nd col: # modified pages written to wal file\n            state.registers[*dest + 1].set_int(wal_max_frame as i64);\n            // 3rd col: # pages moved to db after checkpoint\n            state.registers[*dest + 2].set_int(wal_total_backfilled as i64);\n\n            state.pc += 1;\n            Ok(InsnFunctionStepResult::Step)\n        }\n        Ok(IOResult::IO(io)) => Ok(InsnFunctionStepResult::IO(io)),\n        Err(err) => {\n            tracing::debug!(\"PRAGMA wal_checkpoint failed: {err:?}\");\n            pager.clear_checkpoint_state();\n            state.registers[*dest].set_int(1);\n            state.pc += 1;\n            Ok(InsnFunctionStepResult::Step)\n        }\n    }\n}\n\npub fn op_null(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    match insn {\n        Insn::Null { dest, dest_end } | Insn::BeginSubrtn { dest, dest_end } => {\n            if let Some(dest_end) = dest_end {\n                for i in *dest..=*dest_end {\n                    state.registers[i].set_null();\n                    // Clear any associated RowSet so it can be reused in a fresh\n                    // state.  In SQLite the RowSet lives inside the register and\n                    // is destroyed by OP_Null; we keep RowSets in a side map, so\n                    // we must remove them explicitly.\n                    state.rowsets.remove(&i);\n                }\n            } else {\n                state.registers[*dest].set_null();\n                state.rowsets.remove(dest);\n            }\n        }\n        _ => {\n            mark_unlikely();\n            unreachable!(\"unexpected Insn {:?}\", insn)\n        }\n    }\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_null_row(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(NullRow { cursor_id }, insn);\n    state.get_cursor(*cursor_id).set_null_flag(true);\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_compare(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        Compare {\n            start_reg_a,\n            start_reg_b,\n            count,\n            key_info,\n        },\n        insn\n    );\n    let start_reg_a = *start_reg_a;\n    let start_reg_b = *start_reg_b;\n    let count = *count;\n\n    if unlikely(start_reg_a + count > start_reg_b) {\n        return Err(LimboError::InternalError(\n            \"Compare registers overlap\".to_string(),\n        ));\n    }\n\n    // (https://github.com/tursodatabase/turso/issues/2304): reusing logic from compare_immutable().\n    // TODO: There are tons of cases like this where we could reuse this in a similar vein\n    let a_range =\n        (start_reg_a..start_reg_a + count + 1).map(|idx| state.registers[idx].get_value());\n    let b_range =\n        (start_reg_b..start_reg_b + count + 1).map(|idx| state.registers[idx].get_value());\n    let cmp = compare_immutable(a_range, b_range, key_info);\n\n    state.last_compare = Some(cmp);\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_jump(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        Jump {\n            target_pc_lt,\n            target_pc_eq,\n            target_pc_gt,\n        },\n        insn\n    );\n    assert!(target_pc_lt.is_offset());\n    assert!(target_pc_eq.is_offset());\n    assert!(target_pc_gt.is_offset());\n    let cmp = state.last_compare.take();\n    if unlikely(cmp.is_none()) {\n        return Err(LimboError::InternalError(\n            \"Jump without compare\".to_string(),\n        ));\n    }\n    let target_pc = match cmp.expect(\"comparison should succeed for valid operands\") {\n        std::cmp::Ordering::Less => *target_pc_lt,\n        std::cmp::Ordering::Equal => *target_pc_eq,\n        std::cmp::Ordering::Greater => *target_pc_gt,\n    };\n    state.pc = target_pc.as_offset_int();\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_move(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        Move {\n            source_reg,\n            dest_reg,\n            count,\n        },\n        insn\n    );\n    let source_reg = *source_reg;\n    let dest_reg = *dest_reg;\n    let count = *count;\n    for i in 0..count {\n        state.registers[dest_reg + i] = std::mem::replace(\n            &mut state.registers[source_reg + i],\n            Register::Value(Value::Null),\n        );\n    }\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_if_pos(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        IfPos {\n            reg,\n            target_pc,\n            decrement_by,\n        },\n        insn\n    );\n    if !target_pc.is_offset() {\n        crate::bail_corrupt_error!(\"Unresolved label: {target_pc:?}\");\n    }\n    let reg = *reg;\n    let target_pc = *target_pc;\n    match state.registers[reg].get_value() {\n        Value::Numeric(Numeric::Integer(n)) if *n > 0 => {\n            state.pc = target_pc.as_offset_int();\n            state.registers[reg].set_int(*n - *decrement_by as i64);\n        }\n        Value::Numeric(Numeric::Integer(_)) => {\n            state.pc += 1;\n        }\n        _ => {\n            mark_unlikely();\n            return Err(LimboError::InternalError(\n                \"IfPos: the value in the register is not an integer\".into(),\n            ));\n        }\n    }\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_not_null(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(NotNull { reg, target_pc }, insn);\n    if !target_pc.is_offset() {\n        crate::bail_corrupt_error!(\"Unresolved label: {target_pc:?}\");\n    }\n    let reg = *reg;\n    let target_pc = *target_pc;\n    match &state.registers[reg].get_value() {\n        Value::Null => {\n            state.pc += 1;\n        }\n        _ => {\n            state.pc = target_pc.as_offset_int();\n        }\n    }\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_comparison(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    let (lhs, rhs, target_pc, flags, collation, op) = match insn {\n        Insn::Eq {\n            lhs,\n            rhs,\n            target_pc,\n            flags,\n            collation,\n        } => (\n            *lhs,\n            *rhs,\n            *target_pc,\n            *flags,\n            collation.unwrap_or_default(),\n            ComparisonOp::Eq,\n        ),\n        Insn::Ne {\n            lhs,\n            rhs,\n            target_pc,\n            flags,\n            collation,\n        } => (\n            *lhs,\n            *rhs,\n            *target_pc,\n            *flags,\n            collation.unwrap_or_default(),\n            ComparisonOp::Ne,\n        ),\n        Insn::Lt {\n            lhs,\n            rhs,\n            target_pc,\n            flags,\n            collation,\n        } => (\n            *lhs,\n            *rhs,\n            *target_pc,\n            *flags,\n            collation.unwrap_or_default(),\n            ComparisonOp::Lt,\n        ),\n        Insn::Le {\n            lhs,\n            rhs,\n            target_pc,\n            flags,\n            collation,\n        } => (\n            *lhs,\n            *rhs,\n            *target_pc,\n            *flags,\n            collation.unwrap_or_default(),\n            ComparisonOp::Le,\n        ),\n        Insn::Gt {\n            lhs,\n            rhs,\n            target_pc,\n            flags,\n            collation,\n        } => (\n            *lhs,\n            *rhs,\n            *target_pc,\n            *flags,\n            collation.unwrap_or_default(),\n            ComparisonOp::Gt,\n        ),\n        Insn::Ge {\n            lhs,\n            rhs,\n            target_pc,\n            flags,\n            collation,\n        } => (\n            *lhs,\n            *rhs,\n            *target_pc,\n            *flags,\n            collation.unwrap_or_default(),\n            ComparisonOp::Ge,\n        ),\n        _ => unreachable!(\"unexpected Insn {:?}\", insn),\n    };\n\n    if !target_pc.is_offset() {\n        crate::bail_corrupt_error!(\"Unresolved label: {target_pc:?}\");\n    }\n\n    let null_eq = flags.has_nulleq();\n    let jump_if_null = flags.has_jump_if_null();\n    let affinity = flags.get_affinity();\n\n    let lhs_value = state.registers[lhs].get_value();\n    let rhs_value = state.registers[rhs].get_value();\n\n    // Fast path for integers\n    if matches!(lhs_value, Value::Numeric(Numeric::Integer(_)))\n        && matches!(rhs_value, Value::Numeric(Numeric::Integer(_)))\n    {\n        if op.compare(lhs_value, rhs_value, collation) {\n            state.pc = target_pc.as_offset_int();\n        } else {\n            state.pc += 1;\n        }\n        return Ok(InsnFunctionStepResult::Step);\n    }\n\n    // Handle NULL values\n    if matches!(lhs_value, Value::Null) || matches!(rhs_value, Value::Null) {\n        let cmp_res = op.compare_nulls(lhs_value, rhs_value, null_eq);\n        let jump = match op {\n            ComparisonOp::Eq => cmp_res || (!null_eq && jump_if_null),\n            ComparisonOp::Ne => cmp_res || (!null_eq && jump_if_null),\n            ComparisonOp::Lt | ComparisonOp::Le | ComparisonOp::Gt | ComparisonOp::Ge => {\n                jump_if_null\n            }\n        };\n        if jump {\n            state.pc = target_pc.as_offset_int();\n        } else {\n            state.pc += 1;\n        }\n        return Ok(InsnFunctionStepResult::Step);\n    }\n\n    // Element-wise array comparison when ARRAY_CMP flag is set\n    if flags.has_array_cmp() {\n        if let (Value::Blob(lb), Value::Blob(rb)) = (lhs_value, rhs_value) {\n            if let Ok(ord) = compare_arrays(lb, rb) {\n                let should_jump = match op {\n                    ComparisonOp::Eq => ord.is_eq(),\n                    ComparisonOp::Ne => !ord.is_eq(),\n                    ComparisonOp::Lt => ord.is_lt(),\n                    ComparisonOp::Le => ord.is_le(),\n                    ComparisonOp::Gt => ord.is_gt(),\n                    ComparisonOp::Ge => ord.is_ge(),\n                };\n                if should_jump {\n                    state.pc = target_pc.as_offset_int();\n                } else {\n                    state.pc += 1;\n                }\n                return Ok(InsnFunctionStepResult::Step);\n            }\n        }\n    }\n\n    let (new_lhs, new_rhs) = (affinity.convert(lhs_value), affinity.convert(rhs_value));\n\n    let should_jump = op.compare(\n        new_lhs\n            .as_ref()\n            .map_or(Either::Left(lhs_value), Either::Right),\n        new_rhs\n            .as_ref()\n            .map_or(Either::Left(rhs_value), Either::Right),\n        collation,\n    );\n\n    match (new_lhs, new_rhs) {\n        (Some(new_lhs), None) => {\n            state.registers[lhs].set_value(new_lhs.as_value_ref().to_owned());\n        }\n        (None, Some(new_rhs)) => {\n            state.registers[rhs].set_value(new_rhs.as_value_ref().to_owned());\n        }\n        (Some(new_lhs), Some(new_rhs)) => {\n            let (new_lhs, new_rhs) = (\n                new_lhs.as_value_ref().to_owned(),\n                new_rhs.as_value_ref().to_owned(),\n            );\n            state.registers[lhs].set_value(new_lhs);\n            state.registers[rhs].set_value(new_rhs);\n        }\n        (None, None) => {}\n    }\n\n    if should_jump {\n        state.pc = target_pc.as_offset_int();\n    } else {\n        state.pc += 1;\n    }\n\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_if(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        If {\n            reg,\n            target_pc,\n            jump_if_null,\n        },\n        insn\n    );\n    if !target_pc.is_offset() {\n        crate::bail_corrupt_error!(\"Unresolved label: {target_pc:?}\");\n    }\n    if state.registers[*reg]\n        .get_value()\n        .exec_if(*jump_if_null, false)\n    {\n        state.pc = target_pc.as_offset_int();\n    } else {\n        state.pc += 1;\n    }\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_if_not(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        IfNot {\n            reg,\n            target_pc,\n            jump_if_null,\n        },\n        insn\n    );\n    if !target_pc.is_offset() {\n        crate::bail_corrupt_error!(\"Unresolved label: {target_pc:?}\");\n    }\n    if state.registers[*reg]\n        .get_value()\n        .exec_if(*jump_if_null, true)\n    {\n        state.pc = target_pc.as_offset_int();\n    } else {\n        state.pc += 1;\n    }\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_open_read(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        OpenRead {\n            cursor_id,\n            root_page,\n            db,\n        },\n        insn\n    );\n\n    let pager = program.get_pager_from_database_index(db);\n    let mv_store = program.connection.mv_store_for_db(*db);\n\n    if let (_, CursorType::IndexMethod(module)) = &program.cursor_ref[*cursor_id] {\n        if state.cursors[*cursor_id].is_none() {\n            let cursor = module.init()?;\n            let cursor_ref = &mut state.cursors[*cursor_id];\n            *cursor_ref = Some(Cursor::IndexMethod(cursor));\n        }\n\n        let cursor = state.cursors[*cursor_id]\n            .as_mut()\n            .expect(\"cursor should exist after initialization\");\n        let cursor = cursor.as_index_method_mut();\n        return_if_io!(cursor.open_read(&program.connection));\n        state.pc += 1;\n        return Ok(InsnFunctionStepResult::Step);\n    }\n\n    let (_, cursor_type) = program\n        .cursor_ref\n        .get(*cursor_id)\n        .expect(\"cursor_id should exist in cursor_ref\");\n    if program.connection.get_mv_tx_id_for_db(*db).is_none() {\n        assert!(\n            *root_page >= 0,\n            \"root page should be non negative when we are not in a MVCC transaction\"\n        );\n    }\n    let cursors = &mut state.cursors;\n    let num_columns = match cursor_type {\n        CursorType::BTreeTable(table_rc) => table_rc.columns.len(),\n        CursorType::BTreeIndex(index_arc) => index_arc.columns.len(),\n        CursorType::MaterializedView(table_rc, _) => table_rc.columns.len(),\n        _ => unreachable!(\"This should not have happened\"),\n    };\n\n    let maybe_promote_to_mvcc_cursor = |btree_cursor: Box<dyn CursorTrait>,\n                                        mv_cursor_type: MvccCursorType|\n     -> Result<Box<dyn CursorTrait>> {\n        if let Some(tx_id) = program.connection.get_mv_tx_id_for_db(*db) {\n            let mv_store = mv_store\n                .as_ref()\n                .expect(\"mv_store should be Some when MVCC transaction is active\")\n                .clone();\n            Ok(Box::new(MvCursor::new(\n                mv_store,\n                tx_id,\n                *root_page,\n                mv_cursor_type,\n                btree_cursor,\n            )?))\n        } else {\n            Ok(btree_cursor)\n        }\n    };\n\n    match cursor_type {\n        CursorType::MaterializedView(_, view_mutex) => {\n            // This is a materialized view with storage\n            // Create btree cursor for reading the persistent data\n\n            let btree_cursor = Box::new(BTreeCursor::new_table(\n                pager.clone(),\n                maybe_transform_root_page_to_positive(mv_store.as_ref(), *root_page),\n                num_columns,\n            ));\n            let cursor = maybe_promote_to_mvcc_cursor(btree_cursor, MvccCursorType::Table)?;\n\n            // Get the view name and look up or create its transaction state\n            let view_name = view_mutex.lock().name().to_string();\n            let tx_state = program\n                .connection\n                .view_transaction_states\n                .get_or_create(&view_name);\n\n            // Create materialized view cursor with this view's transaction state\n            let mv_cursor = crate::incremental::cursor::MaterializedViewCursor::new(\n                cursor,\n                view_mutex.clone(),\n                pager,\n                tx_state,\n            )?;\n\n            cursors\n                .get_mut(*cursor_id)\n                .expect(\"cursor_id should be valid\")\n                .replace(Cursor::new_materialized_view(mv_cursor));\n        }\n        CursorType::BTreeTable(_) => {\n            // Regular table\n            let btree_cursor = Box::new(BTreeCursor::new_table(\n                pager,\n                maybe_transform_root_page_to_positive(mv_store.as_ref(), *root_page),\n                num_columns,\n            ));\n            let cursor = maybe_promote_to_mvcc_cursor(btree_cursor, MvccCursorType::Table)?;\n            cursors\n                .get_mut(*cursor_id)\n                .expect(\"cursor_id should be valid\")\n                .replace(Cursor::new_btree(cursor));\n        }\n        CursorType::BTreeIndex(index) => {\n            let btree_cursor = Box::new(BTreeCursor::new_index(\n                pager,\n                *root_page,\n                index.as_ref(),\n                num_columns,\n            ));\n            let index_info = Arc::new(IndexInfo::new_from_index(index));\n            let cursor =\n                maybe_promote_to_mvcc_cursor(btree_cursor, MvccCursorType::Index(index_info))?;\n            cursors\n                .get_mut(*cursor_id)\n                .expect(\"cursor_id should be valid\")\n                .replace(Cursor::new_btree(cursor));\n        }\n        CursorType::Pseudo(_) => {\n            panic!(\"OpenRead on pseudo cursor\");\n        }\n        CursorType::Sorter => {\n            panic!(\"OpenRead on sorter cursor\");\n        }\n        CursorType::IndexMethod(..) => {\n            unreachable!(\"IndexMethod handled above\")\n        }\n        CursorType::VirtualTable(_) => {\n            panic!(\"OpenRead on virtual table cursor, use Insn:VOpen instead\");\n        }\n    }\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_vopen(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(VOpen { cursor_id }, insn);\n    let (_, cursor_type) = program\n        .cursor_ref\n        .get(*cursor_id)\n        .expect(\"cursor_id should exist in cursor_ref\");\n    let CursorType::VirtualTable(virtual_table) = cursor_type else {\n        panic!(\"VOpen on non-virtual table cursor\");\n    };\n    let cursor = virtual_table.open(program.connection.clone())?;\n    state\n        .cursors\n        .get_mut(*cursor_id)\n        .unwrap_or_else(|| panic!(\"cursor id {} out of bounds\", *cursor_id))\n        .replace(Cursor::Virtual(cursor));\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_vcreate(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        VCreate {\n            module_name,\n            table_name,\n            args_reg,\n        },\n        insn\n    );\n    let module_name = state.registers[*module_name].get_value().to_string();\n    let table_name = state.registers[*table_name].get_value().to_string();\n    let args = if let Some(args_reg) = args_reg {\n        if let Register::Record(rec) = &state.registers[*args_reg] {\n            rec.iter()?\n                .map(|v| v.map(|v| v.to_ffi()))\n                .collect::<Result<_, _>>()?\n        } else {\n            mark_unlikely();\n            return Err(LimboError::InternalError(\n                \"VCreate: args_reg is not a record\".to_string(),\n            ));\n        }\n    } else {\n        vec![]\n    };\n    let conn = program.connection.clone();\n    let table =\n        crate::VirtualTable::table(Some(&table_name), &module_name, args, &conn.syms.read())?;\n    {\n        conn.syms.write().vtabs.insert(table_name, table);\n    }\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_vfilter(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        VFilter {\n            cursor_id,\n            pc_if_empty,\n            arg_count,\n            args_reg,\n            idx_str,\n            idx_num,\n        },\n        insn\n    );\n    let has_rows = {\n        let cursor = get_cursor!(state, *cursor_id);\n        let cursor = cursor.as_virtual_mut();\n        let mut args = Vec::with_capacity(*arg_count);\n        for i in 0..*arg_count {\n            args.push(state.registers[args_reg + i].get_value().clone());\n        }\n        let idx_str = if let Some(idx_str) = idx_str {\n            Some(state.registers[*idx_str].get_value().to_string())\n        } else {\n            None\n        };\n        cursor.filter(*idx_num as i32, idx_str, *arg_count, args)?\n    };\n    // Increment filter_operations metric for virtual table filter\n    state.metrics.filter_operations = state.metrics.filter_operations.saturating_add(1);\n    if !has_rows {\n        state.pc = pc_if_empty.as_offset_int();\n    } else {\n        // VFilter positions to the first row if any exist, which counts as a read\n        state.metrics.rows_read = state.metrics.rows_read.saturating_add(1);\n        state.pc += 1;\n    }\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_vcolumn(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        VColumn {\n            cursor_id,\n            column,\n            dest,\n        },\n        insn\n    );\n    let value = {\n        let cursor = state.get_cursor(*cursor_id);\n        let cursor = cursor.as_virtual_mut();\n        cursor.column(*column)?\n    };\n    state.registers[*dest].set_value(value);\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_vupdate(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    #[cfg(not(feature = \"cli_only\"))]\n    let _ = pager;\n    load_insn!(\n        VUpdate {\n            cursor_id,\n            arg_count,\n            start_reg,\n            conflict_action,\n            ..\n        },\n        insn\n    );\n    let (_, cursor_type) = program\n        .cursor_ref\n        .get(*cursor_id)\n        .expect(\"cursor_id should exist in cursor_ref\");\n    let CursorType::VirtualTable(virtual_table) = cursor_type else {\n        panic!(\"VUpdate on non-virtual table cursor\");\n    };\n    let allow_dbpage_write = {\n        #[cfg(feature = \"cli_only\")]\n        {\n            virtual_table.name == crate::dbpage::DBPAGE_TABLE_NAME\n                && program.connection.db.opts.unsafe_testing\n        }\n        #[cfg(not(feature = \"cli_only\"))]\n        {\n            false\n        }\n    };\n    if virtual_table.readonly() && !allow_dbpage_write {\n        return Err(LimboError::ReadOnly);\n    }\n\n    if unlikely(*arg_count < 2) {\n        return Err(LimboError::InternalError(\n            \"VUpdate: arg_count must be at least 2 (rowid and insert_rowid)\".to_string(),\n        ));\n    }\n    let mut argv = Vec::with_capacity(*arg_count);\n    for i in 0..*arg_count {\n        if let Some(value) = state.registers.get(*start_reg + i) {\n            argv.push(value.get_value().clone());\n        } else {\n            mark_unlikely();\n            return Err(LimboError::InternalError(format!(\n                \"VUpdate: register out of bounds at {}\",\n                *start_reg + i\n            )));\n        }\n    }\n    let result = if allow_dbpage_write {\n        #[cfg(feature = \"cli_only\")]\n        {\n            crate::dbpage::update_dbpage(pager, &argv)\n        }\n        #[cfg(not(feature = \"cli_only\"))]\n        {\n            unreachable!(\"sqlite_dbpage writes require cli_only feature\");\n        }\n    } else {\n        virtual_table.update(&argv)\n    };\n    match result {\n        Ok(Some(new_rowid)) => {\n            if *conflict_action == 5 {\n                // ResolveType::Replace\n                program.connection.update_last_rowid(new_rowid);\n            }\n            state.pc += 1;\n        }\n        Ok(None) => {\n            // no-op or successful update without rowid return\n            state.pc += 1;\n        }\n        Err(e) => {\n            // virtual table update failed\n            mark_unlikely();\n            return Err(LimboError::ExtensionError(format!(\n                \"Virtual table update failed: {e}\"\n            )));\n        }\n    }\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_vnext(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        VNext {\n            cursor_id,\n            pc_if_next,\n        },\n        insn\n    );\n    let has_more = {\n        let cursor = state.get_cursor(*cursor_id);\n        let cursor = cursor.as_virtual_mut();\n        cursor.next()?\n    };\n    if has_more {\n        // Increment metrics for row read from virtual table (including materialized views)\n        state.metrics.rows_read = state.metrics.rows_read.saturating_add(1);\n        state.pc = pc_if_next.as_offset_int();\n    } else {\n        state.pc += 1;\n    }\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_vdestroy(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(VDestroy { db: _, table_name }, insn);\n    let conn = program.connection.clone();\n    {\n        let Some(vtab) = conn.syms.write().vtabs.remove(table_name) else {\n            mark_unlikely();\n            return Err(crate::LimboError::InternalError(\n                \"Could not find Virtual Table to Destroy\".to_string(),\n            ));\n        };\n        vtab.destroy()?;\n    }\n\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_vbegin(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(VBegin { cursor_id }, insn);\n    let cursor = state.get_cursor(*cursor_id);\n    let cursor = cursor.as_virtual_mut();\n    let vtab_id = cursor\n        .vtab_id()\n        .expect(\"VBegin on non ext-virtual table cursor\");\n    let mut states = program.connection.vtab_txn_states.write();\n    if states.insert(vtab_id) {\n        // Only begin a new transaction if one is not already active for this virtual table module\n        let vtabs = &program.connection.syms.read().vtabs;\n        let vtab = vtabs\n            .iter()\n            .find(|p| p.1.id().eq(&vtab_id))\n            .expect(\"Could not find virtual table for VBegin\");\n        vtab.1.begin()?;\n    }\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_vrename(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        VRename {\n            cursor_id,\n            new_name_reg\n        },\n        insn\n    );\n    let name = state.registers[*new_name_reg].get_value().to_string();\n    let cursor = state.get_cursor(*cursor_id);\n    let cursor = cursor.as_virtual_mut();\n    let vtabs = &program.connection.syms.read().vtabs;\n    let vtab = vtabs\n        .iter()\n        .find(|p| {\n            p.1.id().eq(&cursor\n                .vtab_id()\n                .expect(\"non ext-virtual table used in VRollback\"))\n        })\n        .expect(\"Could not find virtual table for VRollback\");\n    vtab.1.rename(&name)?;\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_open_pseudo(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        OpenPseudo {\n            cursor_id,\n            content_reg: _,\n            num_fields: _,\n        },\n        insn\n    );\n    {\n        let cursors = &mut state.cursors;\n        let cursor = PseudoCursor::default();\n        cursors\n            .get_mut(*cursor_id)\n            .expect(\"cursor_id should be valid\")\n            .replace(Cursor::new_pseudo(cursor));\n    }\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_rewind(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        Rewind {\n            cursor_id,\n            pc_if_empty,\n        },\n        insn\n    );\n    assert!(pc_if_empty.is_offset());\n    // Clear any bloom filter associated with this cursor so stale filter data\n    // does not incorrectly reject valid matches in subsequent iterations.\n    if let Some(filter) = state.get_bloom_filter_mut(*cursor_id) {\n        filter.clear();\n    }\n    let is_empty = {\n        let cursor = state.get_cursor(*cursor_id);\n        match cursor {\n            Cursor::BTree(btree_cursor) => {\n                return_if_io!(btree_cursor.rewind());\n                btree_cursor.is_empty()\n            }\n            Cursor::MaterializedView(mv_cursor) => {\n                return_if_io!(mv_cursor.rewind());\n                !mv_cursor.is_valid()?\n            }\n            _ => panic!(\"Rewind on non-btree/materialized-view cursor\"),\n        }\n    };\n    if is_empty {\n        state.pc = pc_if_empty.as_offset_int();\n    } else {\n        // Rewind positions to the first row, which is effectively a read\n        state.metrics.rows_read = state.metrics.rows_read.saturating_add(1);\n        state.pc += 1;\n    }\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_last(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        Last {\n            cursor_id,\n            pc_if_empty,\n        },\n        insn\n    );\n    assert!(pc_if_empty.is_offset());\n    let is_empty = {\n        let cursor = must_be_btree_cursor!(*cursor_id, program.cursor_ref, state, \"Last\");\n        let cursor = cursor.as_btree_mut();\n        return_if_io!(cursor.last());\n        cursor.is_empty()\n    };\n    if is_empty {\n        state.pc = pc_if_empty.as_offset_int();\n    } else {\n        // Last positions to the last row, which is effectively a read\n        state.metrics.rows_read = state.metrics.rows_read.saturating_add(1);\n        state.pc += 1;\n    }\n    Ok(InsnFunctionStepResult::Step)\n}\n\n#[derive(Debug, Clone, Copy)]\npub enum OpColumnState {\n    Start,\n    Rowid {\n        index_cursor_id: usize,\n        table_cursor_id: usize,\n    },\n    Seek {\n        rowid: i64,\n        table_cursor_id: usize,\n    },\n    GetColumn,\n}\n\npub fn op_column(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        Column {\n            cursor_id,\n            column,\n            dest,\n            default,\n        },\n        insn\n    );\n    'outer: loop {\n        match state.op_column_state {\n            OpColumnState::Start => {\n                if let Some(deferred) = state.deferred_seeks[*cursor_id].take() {\n                    state.op_column_state = OpColumnState::Rowid {\n                        index_cursor_id: deferred.index_cursor_id,\n                        table_cursor_id: deferred.table_cursor_id,\n                    };\n                } else {\n                    state.op_column_state = OpColumnState::GetColumn;\n                }\n            }\n            OpColumnState::Rowid {\n                index_cursor_id,\n                table_cursor_id,\n            } => {\n                let Some(rowid) = ({\n                    let index_cursor = state.get_cursor(index_cursor_id);\n                    match index_cursor {\n                        Cursor::BTree(cursor) => return_if_io!(cursor.rowid()),\n                        Cursor::IndexMethod(cursor) => return_if_io!(cursor.query_rowid()),\n                        _ => panic!(\"unexpected cursor type\"),\n                    }\n                }) else {\n                    state.registers[*dest].set_null();\n                    break 'outer;\n                };\n                state.op_column_state = OpColumnState::Seek {\n                    rowid,\n                    table_cursor_id,\n                };\n            }\n            OpColumnState::Seek {\n                rowid,\n                table_cursor_id,\n            } => {\n                {\n                    let table_cursor = state.get_cursor(table_cursor_id);\n                    // MaterializedView cursors shouldn't go through deferred seek logic\n                    // but if we somehow get here, handle it appropriately\n                    match table_cursor {\n                        Cursor::MaterializedView(mv_cursor) => {\n                            // Seek to the rowid in the materialized view\n                            return_if_io!(mv_cursor\n                                .seek(SeekKey::TableRowId(rowid), SeekOp::GE { eq_only: true }));\n                        }\n                        _ => {\n                            // Regular btree cursor\n                            let table_cursor = table_cursor.as_btree_mut();\n                            return_if_io!(table_cursor\n                                .seek(SeekKey::TableRowId(rowid), SeekOp::GE { eq_only: true }));\n                        }\n                    }\n                }\n                state.op_column_state = OpColumnState::GetColumn;\n            }\n            OpColumnState::GetColumn => {\n                let (active_cursor_id, active_column) = (*cursor_id, *column);\n                // First check if this is a MaterializedViewCursor\n                {\n                    let cursor = state.get_cursor(active_cursor_id);\n                    if let Cursor::MaterializedView(mv_cursor) = cursor {\n                        // Handle materialized view column access\n                        let value = return_if_io!(mv_cursor.column(active_column));\n                        state.registers[*dest].set_value(value);\n                        break 'outer;\n                    }\n                    // Fall back to normal handling\n                }\n\n                let (_, cursor_type) = program\n                    .cursor_ref\n                    .get(active_cursor_id)\n                    .expect(\"cursor_id should exist in cursor_ref\");\n                match cursor_type {\n                    CursorType::BTreeTable(_)\n                    | CursorType::BTreeIndex(_)\n                    | CursorType::MaterializedView(_, _) => {\n                        {\n                            let cursor_ref = must_be_btree_cursor!(\n                                active_cursor_id,\n                                program.cursor_ref,\n                                state,\n                                \"Column\"\n                            );\n                            let cursor = cursor_ref.as_btree_mut();\n\n                            if cursor.get_null_flag() {\n                                tracing::trace!(\"op_column(null_flag)\");\n                                state.registers[*dest].set_null();\n                                break 'outer;\n                            }\n\n                            let record_result = return_if_io!(cursor.record());\n                            let Some(record) = record_result else {\n                                // Cursor is not positioned on a valid row (e.g., empty table).\n                                // Return NULL, not the column's default value.\n                                // DEFAULT handling below is for when record exists\n                                // but has fewer columns than expected.\n                                state.registers[*dest].set_null();\n                                break 'outer;\n                            };\n\n                            let mut payload_iterator = record.iter()?;\n\n                            // Parse the header for serial types incrementally until we have the target column\n                            // Use nth_into_register to write directly to the register without\n                            // creating intermediate ValueRef allocations\n                            use crate::vdbe::ValueIteratorExt;\n                            match payload_iterator\n                                .nth_into_register(*column, &mut state.registers[*dest])\n                            {\n                                Some(result) => {\n                                    result?;\n                                    break 'outer;\n                                }\n                                None => {\n                                    branches::mark_unlikely();\n                                    // record has fewer columns than expected\n                                }\n                            };\n\n                            //break;\n                        };\n\n                        // DEFAULT handling\n                        let Some(ref default) = default else {\n                            state.registers[*dest].set_null();\n                            break;\n                        };\n                        match (default, &mut state.registers[*dest]) {\n                            (\n                                Value::Text(new_text),\n                                Register::Value(Value::Text(existing_text)),\n                            ) => {\n                                existing_text.do_extend(new_text);\n                            }\n                            (\n                                Value::Blob(new_blob),\n                                Register::Value(Value::Blob(existing_blob)),\n                            ) => {\n                                existing_blob.do_extend(new_blob);\n                            }\n                            _ => {\n                                state.registers[*dest].set_value(default.clone());\n                            }\n                        }\n                        break;\n                    }\n                    CursorType::Sorter => {\n                        let record = {\n                            let cursor = state.get_cursor(*cursor_id);\n                            let cursor = cursor.as_sorter_mut();\n                            cursor.record().cloned()\n                        };\n                        if let Some(record) = record {\n                            state.registers[*dest].set_value(match record.get_value_opt(*column) {\n                                Some(val) => val.to_owned(),\n                                None => default.clone().unwrap_or(Value::Null),\n                            });\n                        } else {\n                            state.registers[*dest].set_null();\n                        }\n                    }\n                    CursorType::Pseudo(_) => {\n                        let value = {\n                            let cursor = state.get_cursor(*cursor_id);\n                            let cursor = cursor.as_pseudo_mut();\n                            cursor.get_value(*column)?\n                        };\n                        state.registers[*dest].set_value(value);\n                    }\n                    CursorType::IndexMethod(..) => {\n                        let cursor = state.cursors[*cursor_id]\n                            .as_mut()\n                            .expect(\"cursor should exist\");\n                        let cursor = cursor.as_index_method_mut();\n                        let value = return_if_io!(cursor.query_column(*column));\n                        state.registers[*dest].set_value(value);\n                    }\n                    CursorType::VirtualTable(_) => {\n                        panic!(\"Insn:Column on virtual table cursor, use Insn:VColumn instead\");\n                    }\n                }\n                break;\n            }\n        }\n    }\n\n    state.op_column_state = OpColumnState::Start;\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_type_check(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        TypeCheck {\n            start_reg,\n            count,\n            check_generated: _,\n            table_reference,\n        },\n        insn\n    );\n    assert!(table_reference.is_strict);\n    state.registers[*start_reg..*start_reg + *count]\n        .iter_mut()\n        .zip(table_reference.columns.iter())\n        .try_for_each(|(reg, col)| {\n            // INT PRIMARY KEY is not row_id_alias so we throw error if this col is NULL\n            if !col.is_rowid_alias() && col.primary_key() && matches!(reg.get_value(), Value::Null)\n            {\n                bail_constraint_error!(\n                    \"NOT NULL constraint failed: {}.{} ({})\",\n                    &table_reference.name,\n                    col.name.as_deref().unwrap_or(\"\"),\n                    SQLITE_CONSTRAINT\n                )\n            } else if col.is_rowid_alias() && matches!(reg.get_value(), Value::Null) {\n                // Handle INTEGER PRIMARY KEY for null as usual (Rowid will be auto-assigned)\n                return Ok(());\n            } else if matches!(reg.get_value(), Value::Null) {\n                // STRICT only enforces type affinity on non-NULL values.\n                // NULL is valid in any column without NOT NULL constraint.\n                return Ok(());\n            }\n            let ty_str = &col.ty_str;\n            let ty_bytes = ty_str.as_bytes();\n            let is_builtin_type = turso_macros::match_ignore_ascii_case!(match ty_bytes {\n                b\"ANY\" | b\"INTEGER\" | b\"INT\" | b\"REAL\" | b\"BLOB\" | b\"TEXT\" => true,\n                _ => false,\n            });\n            if is_builtin_type {\n                match_ignore_ascii_case!(match ty_bytes {\n                    b\"ANY\" => {}\n                    _ => {\n                        let col_affinity = col.affinity();\n                        let _applied = apply_affinity_char(reg, col_affinity);\n                        let value_type = reg.get_value().value_type();\n                        match_ignore_ascii_case!(match ty_bytes {\n                            b\"INTEGER\" | b\"INT\" if value_type == ValueType::Integer => {}\n                            b\"REAL\" if value_type == ValueType::Float => {}\n                            b\"BLOB\" if value_type == ValueType::Blob => {}\n                            b\"TEXT\" if value_type == ValueType::Text => {}\n                            _ => bail_constraint_error!(\n                                \"cannot store {} value in {} column {}.{} ({})\",\n                                value_type,\n                                ty_str,\n                                &table_reference.name,\n                                col.name.as_deref().unwrap_or(\"\"),\n                                SQLITE_CONSTRAINT\n                            ),\n                        });\n                    }\n                });\n            }\n            // Custom types: skip type check — encode function validates\n            Ok(())\n        })?;\n\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\n/// Parse an array input (JSON text or record blob), validate/coerce each element\n/// against the declared element type using STRICT type-checking logic, then\n/// serialize to a native record-format BLOB for storage.\npub fn op_array_encode(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        ArrayEncode {\n            reg,\n            element_affinity,\n            element_type,\n            table_name,\n            col_name,\n        },\n        insn\n    );\n\n    let val = state.registers[*reg].get_value();\n    if matches!(val, Value::Null) {\n        state.pc += 1;\n        return Ok(InsnFunctionStepResult::Step);\n    }\n\n    // Fast path: blob input with ANY type — validate it is a well-formed record\n    // before accepting. This avoids the redundant extract→reserialize when MakeArray\n    // output feeds directly into ArrayEncode with ANY affinity, but rejects raw blobs\n    // (e.g. zeroblob, X'DEADBEEF') that would crash on read.\n    if let Value::Blob(b) = val {\n        if element_type.eq_ignore_ascii_case(\"ANY\") {\n            // Validate blob is a well-formed record using streaming iterator\n            // (no Vec<Value> allocation). Rejects empty blobs and invalid records.\n            let valid = !b.is_empty()\n                && ValueIterator::new(b)\n                    .map(|iter| iter.into_iter().all(|r| r.is_ok()))\n                    .unwrap_or(false);\n            if !valid {\n                bail_constraint_error!(\n                    \"cannot store non-array value in {} column {}.{} ({})\",\n                    element_type,\n                    table_name,\n                    col_name,\n                    SQLITE_CONSTRAINT\n                );\n            }\n            state.pc += 1;\n            return Ok(InsnFunctionStepResult::Step);\n        }\n    }\n\n    // Extract elements from either blob (MakeArray) or text (JSON literal)\n    let raw_elements = match val {\n        Value::Blob(b) => array_values_from_blob(b).ok(),\n        Value::Text(text) => parse_text_array(text.as_str()),\n        _ => None,\n    };\n    let Some(raw_elements) = raw_elements else {\n        bail_constraint_error!(\n            \"cannot store non-array value in {} column {}.{} ({})\",\n            element_type,\n            table_name,\n            col_name,\n            SQLITE_CONSTRAINT\n        );\n    };\n\n    const MAX_ARRAY_ELEMENTS: usize = 100_000;\n    if raw_elements.len() > MAX_ARRAY_ELEMENTS {\n        bail_constraint_error!(\n            \"array exceeds maximum element count ({MAX_ARRAY_ELEMENTS}) for column {table_name}.{col_name} ({SQLITE_CONSTRAINT})\"\n        );\n    }\n\n    let mut coerced_elements: Vec<Value> = Vec::with_capacity(raw_elements.len());\n    for elem in raw_elements {\n        // NULL elements are allowed — same as STRICT allows NULL in columns\n        if matches!(elem, Value::Null) {\n            coerced_elements.push(elem);\n            continue;\n        }\n\n        // Apply affinity coercion — same as STRICT's TypeCheck\n        let mut reg_tmp = Register::Value(elem);\n        apply_affinity_char(&mut reg_tmp, *element_affinity);\n        let coerced = reg_tmp.get_value().clone();\n\n        // Check value type matches the declared element type — same as STRICT's TypeCheck\n        let value_type = coerced.value_type();\n        let ty_bytes = element_type.as_bytes();\n        let type_ok = turso_macros::match_ignore_ascii_case!(match ty_bytes {\n            b\"ANY\" => true,\n            b\"INTEGER\" | b\"INT\" => value_type == ValueType::Integer,\n            b\"REAL\" => value_type == ValueType::Float,\n            b\"TEXT\" => value_type == ValueType::Text,\n            b\"BLOB\" => value_type == ValueType::Blob,\n            _ => true, // custom types validated by their own encode\n        });\n\n        if !type_ok {\n            bail_constraint_error!(\n                \"cannot store {} value in {} ({})\",\n                value_type,\n                element_type,\n                SQLITE_CONSTRAINT\n            );\n        }\n\n        coerced_elements.push(coerced);\n    }\n\n    // Serialize coerced elements as a native record-format BLOB\n    let record = ImmutableRecord::from_values(&coerced_elements, coerced_elements.len());\n    state.registers[*reg].set_blob(record.into_payload());\n\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\n/// Convert a native record-format array BLOB to PG text representation for display.\npub fn op_array_decode(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(ArrayDecode { reg }, insn);\n\n    let val = state.registers[*reg].get_value();\n    if matches!(val, Value::Null) {\n        state.pc += 1;\n        return Ok(InsnFunctionStepResult::Step);\n    }\n\n    let text = match val {\n        Value::Blob(b) if b.is_empty() => \"{}\".to_string(),\n        Value::Blob(b) => serialize_array_from_blob(b)?,\n        _ => {\n            // Not a blob — leave as-is (might be text from a function result)\n            state.pc += 1;\n            return Ok(InsnFunctionStepResult::Step);\n        }\n    };\n    state.registers[*reg].set_text(Text::new(text));\n\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\n/// Access element at index from a record-format array BLOB.\npub fn op_array_element(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        ArrayElement {\n            array_reg,\n            index_reg,\n            dest,\n        },\n        insn\n    );\n\n    let arr_val = state.registers[*array_reg].get_value();\n    if matches!(arr_val, Value::Null) {\n        state.registers[*dest].set_null();\n        state.pc += 1;\n        return Ok(InsnFunctionStepResult::Step);\n    }\n\n    let idx = match state.registers[*index_reg].get_value() {\n        Value::Numeric(Numeric::Integer(i)) if *i >= 1 => (*i - 1) as usize,\n        _ => {\n            // Non-positive, non-integer, or NULL index → NULL result (PG convention: 1-based)\n            state.registers[*dest].set_null();\n            state.pc += 1;\n            return Ok(InsnFunctionStepResult::Step);\n        }\n    };\n\n    let result = match arr_val {\n        Value::Blob(blob) => match ValueIterator::new(blob) {\n            Ok(mut iter) => iter\n                .nth(idx)\n                .and_then(|r| r.ok())\n                .map(|vref| {\n                    // The blob may not be a real record — text fields could\n                    // contain invalid UTF-8 (from_utf8_unchecked in the\n                    // record decoder). Validate and demote to blob if needed.\n                    if let ValueRef::Text(t) = &vref {\n                        if t.value.as_bytes().iter().any(|&b| b > 0x7F)\n                            && std::str::from_utf8(t.value.as_bytes()).is_err()\n                        {\n                            return Value::Blob(t.value.as_bytes().to_vec());\n                        }\n                    }\n                    vref.to_owned()\n                })\n                .unwrap_or(Value::Null),\n            Err(_) => Value::Null,\n        },\n        _ => Value::Null,\n    };\n\n    state.registers[*dest].set_value(result);\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\n/// Get the number of elements in a record-format array BLOB.\npub fn op_array_length(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(ArrayLength { reg, dest }, insn);\n\n    let val = state.registers[*reg].get_value();\n    match compute_array_length(val) {\n        Some(count) => state.registers[*dest].set_int(count),\n        None => state.registers[*dest].set_null(),\n    };\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\n/// Create an array from contiguous registers as a record-format BLOB.\npub fn op_make_array(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        MakeArray {\n            start_reg,\n            count,\n            dest\n        },\n        insn\n    );\n\n    let end = start_reg\n        .checked_add(*count)\n        .ok_or_else(|| LimboError::InternalError(\"MakeArray: register range overflow\".into()))?;\n    if end > state.registers.len() {\n        return Err(LimboError::InternalError(format!(\n            \"MakeArray: register range {}..{} exceeds register file size {}\",\n            start_reg,\n            end,\n            state.registers.len()\n        )));\n    }\n    state.registers[*dest].set_value(make_array_from_registers(\n        &state.registers,\n        *start_reg,\n        *count,\n    ));\n\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\n/// Create an array from contiguous registers with dynamic count.\npub fn op_make_array_dynamic(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        MakeArrayDynamic {\n            start_reg,\n            count_reg,\n            dest\n        },\n        insn\n    );\n\n    let count = match state.registers[*count_reg].get_value() {\n        Value::Numeric(Numeric::Integer(n)) if *n >= 0 => *n as usize,\n        _ => 0,\n    };\n\n    let end = start_reg.checked_add(count).ok_or_else(|| {\n        LimboError::InternalError(\"MakeArrayDynamic: register range overflow\".into())\n    })?;\n    if end > state.registers.len() {\n        return Err(LimboError::InternalError(format!(\n            \"MakeArrayDynamic: register range {}..{} exceeds register file size {}\",\n            start_reg,\n            end,\n            state.registers.len()\n        )));\n    }\n\n    state.registers[*dest].set_value(make_array_from_registers(\n        &state.registers,\n        *start_reg,\n        count,\n    ));\n\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\n/// Copy a register value to a dynamically-computed destination register.\npub fn op_reg_copy_offset(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        RegCopyOffset {\n            src,\n            base,\n            offset_reg\n        },\n        insn\n    );\n\n    let offset = match state.registers[*offset_reg].get_value() {\n        Value::Numeric(Numeric::Integer(n)) if *n >= 0 => *n as usize,\n        _ => 0,\n    };\n    let dest = *base + offset;\n    if dest >= state.registers.len() {\n        return Err(LimboError::InternalError(format!(\n            \"RegCopyOffset: destination register {} out of bounds (max {})\",\n            dest,\n            state.registers.len()\n        )));\n    }\n    state.registers[dest] = state.registers[*src].clone();\n\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\n/// Concatenate/append/prepend arrays. Runtime dispatch based on operand types.\npub fn op_array_concat(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(ArrayConcat { lhs, rhs, dest }, insn);\n\n    // Check NULL before cloning to avoid unnecessary allocation\n    let lhs_ref = state.registers[*lhs].get_value();\n    let rhs_ref = state.registers[*rhs].get_value();\n\n    // PG-compatible NULL handling for arrays:\n    // array || NULL = array, NULL || array = array, NULL || NULL = NULL\n    if matches!(lhs_ref, Value::Null) && matches!(rhs_ref, Value::Null) {\n        state.registers[*dest].set_null();\n        state.pc += 1;\n        return Ok(InsnFunctionStepResult::Step);\n    }\n    if matches!(lhs_ref, Value::Null) {\n        state.registers[*dest].set_value(rhs_ref.clone());\n        state.pc += 1;\n        return Ok(InsnFunctionStepResult::Step);\n    }\n    if matches!(rhs_ref, Value::Null) {\n        state.registers[*dest].set_value(lhs_ref.clone());\n        state.pc += 1;\n        return Ok(InsnFunctionStepResult::Step);\n    }\n\n    let result = match (lhs_ref, rhs_ref) {\n        (Value::Blob(lb), Value::Blob(rb)) => {\n            let mut elems_a = array_values_from_blob(lb)?;\n            let elems_b = array_values_from_blob(rb)?;\n            elems_a.extend(elems_b);\n            values_to_record_blob(&elems_a)\n        }\n        (Value::Blob(lb), _) => {\n            let mut elems = array_values_from_blob(lb)?;\n            elems.push(rhs_ref.clone());\n            values_to_record_blob(&elems)\n        }\n        (_, Value::Blob(rb)) => {\n            let mut elems = array_values_from_blob(rb)?;\n            elems.insert(0, lhs_ref.clone());\n            values_to_record_blob(&elems)\n        }\n        _ => {\n            // Neither is an array blob — fall back to string concat\n            Value::build_text(format!(\"{lhs_ref}{rhs_ref}\"))\n        }\n    };\n\n    state.registers[*dest].set_value(result);\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\n/// Set element at index in a record-format array BLOB.\npub fn op_array_set_element(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        ArraySetElement {\n            array_reg,\n            index_reg,\n            value_reg,\n            dest,\n        },\n        insn\n    );\n\n    let arr_val = state.registers[*array_reg].get_value();\n    if matches!(arr_val, Value::Null) {\n        state.registers[*dest].set_null();\n        state.pc += 1;\n        return Ok(InsnFunctionStepResult::Step);\n    }\n\n    let idx = match state.registers[*index_reg].get_value() {\n        Value::Numeric(Numeric::Integer(i)) if *i >= 1 => (*i - 1) as usize,\n        _ => {\n            // Invalid index (non-positive, non-integer): preserve original array (PG: 1-based)\n            state.registers[*dest].set_value(arr_val.clone());\n            state.pc += 1;\n            return Ok(InsnFunctionStepResult::Step);\n        }\n    };\n\n    let new_val = state.registers[*value_reg].get_value().clone();\n\n    let Value::Blob(blob) = arr_val else {\n        return Err(LimboError::InternalError(\n            \"ArraySetElement: expected blob array\".into(),\n        ));\n    };\n    let mut elements = array_values_from_blob(blob)?;\n    if idx >= elements.len() {\n        // Out-of-bounds: preserve original array unchanged\n        state.registers[*dest].set_blob(blob.clone());\n    } else {\n        elements[idx] = new_val;\n        state.registers[*dest].set_value(values_to_record_blob(&elements));\n    }\n\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\n/// Extract a subslice of elements from a record-format array BLOB.\npub fn op_array_slice(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        ArraySlice {\n            array_reg,\n            start_reg,\n            end_reg,\n            dest,\n        },\n        insn\n    );\n\n    let arr_val = state.registers[*array_reg].get_value().clone();\n    let start_val = state.registers[*start_reg].get_value().clone();\n    let end_val = state.registers[*end_reg].get_value().clone();\n\n    let result = exec_array_slice(&arr_val, &start_val, &end_val);\n    state.registers[*dest].set_value(result);\n\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_make_record(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        MakeRecord {\n            start_reg,\n            count,\n            dest_reg,\n            affinity_str,\n            ..\n        },\n        insn\n    );\n\n    let start_reg = *start_reg as usize;\n    let count = *count as usize;\n    let dest_reg = *dest_reg as usize;\n\n    if let Some(affinity_str) = affinity_str {\n        if unlikely(affinity_str.len() != count) {\n            return Err(LimboError::InternalError(format!(\n                \"MakeRecord: the length of affinity string ({}) does not match the count ({})\",\n                affinity_str.len(),\n                count\n            )));\n        }\n        for (i, affinity_ch) in affinity_str.chars().enumerate().take(count) {\n            let reg_index = start_reg + i;\n            let affinity = Affinity::from_char(affinity_ch);\n            apply_affinity_char(&mut state.registers[reg_index], affinity);\n        }\n    }\n\n    let record = make_record(&state.registers, &start_reg, &count);\n    state.registers[dest_reg] = Register::Record(record);\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_mem_max(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(MemMax { dest_reg, src_reg }, insn);\n\n    let dest_val = state.registers[*dest_reg].get_value();\n    let src_val = state.registers[*src_reg].get_value();\n\n    let dest_int = extract_int_value(dest_val);\n    let src_int = extract_int_value(src_val);\n\n    if dest_int < src_int {\n        state.registers[*dest_reg].set_int(src_int);\n    }\n\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_result_row(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(ResultRow { start_reg, count }, insn);\n    let row = Row {\n        values: &state.registers[*start_reg] as *const Register,\n        count: *count,\n    };\n    state.result_row = Some(row);\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Row)\n}\n\npub fn op_next(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        Next {\n            cursor_id,\n            pc_if_next,\n        },\n        insn\n    );\n    assert!(pc_if_next.is_offset());\n    let is_empty = {\n        let cursor = state.get_cursor(*cursor_id);\n        match cursor {\n            Cursor::BTree(btree_cursor) => {\n                // If cursor is in NullRow state, don't advance - just return empty.\n                // This matches SQLite's OP_Next behavior: btreeNext() returns\n                // SQLITE_DONE when eState==CURSOR_INVALID (NullRow calls\n                // sqlite3BtreeClearCursor which sets CURSOR_INVALID).\n                let is_null_row = btree_cursor.get_null_flag();\n                btree_cursor.set_null_flag(false);\n                if is_null_row {\n                    true // is_empty = true\n                } else {\n                    return_if_io!(btree_cursor.next());\n                    btree_cursor.is_empty()\n                }\n            }\n            Cursor::MaterializedView(mv_cursor) => {\n                let has_more = return_if_io!(mv_cursor.next());\n                !has_more\n            }\n            Cursor::IndexMethod(_) => {\n                let cursor = cursor.as_index_method_mut();\n                let has_more = return_if_io!(cursor.query_next());\n                !has_more\n            }\n            _ => panic!(\"Next on non-btree/materialized-view cursor\"),\n        }\n    };\n    if !is_empty {\n        // Increment metrics for row read\n        state.metrics.rows_read = state.metrics.rows_read.saturating_add(1);\n        state.metrics.btree_next = state.metrics.btree_next.saturating_add(1);\n        // Track if this is a full table scan or index scan\n        if let Some((_, cursor_type)) = program.cursor_ref.get(*cursor_id) {\n            if cursor_type.is_index() {\n                state.metrics.index_steps = state.metrics.index_steps.saturating_add(1);\n            } else if matches!(cursor_type, CursorType::BTreeTable(_)) {\n                state.metrics.fullscan_steps = state.metrics.fullscan_steps.saturating_add(1);\n            }\n        }\n        state.pc = pc_if_next.as_offset_int();\n    } else {\n        state.pc += 1;\n    }\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_prev(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        Prev {\n            cursor_id,\n            pc_if_prev,\n        },\n        insn\n    );\n    assert!(pc_if_prev.is_offset());\n    let is_empty = {\n        let cursor = must_be_btree_cursor!(*cursor_id, program.cursor_ref, state, \"Prev\");\n        let cursor = cursor.as_btree_mut();\n        // If cursor is in NullRow state, don't advance - just return empty.\n        // This matches SQLite's OP_Prev behavior which checks nullRow first.\n        let is_null_row = cursor.get_null_flag();\n        cursor.set_null_flag(false);\n        if is_null_row {\n            true // is_empty = true\n        } else {\n            return_if_io!(cursor.prev());\n            cursor.is_empty()\n        }\n    };\n    if !is_empty {\n        // Increment metrics for row read\n        state.metrics.rows_read = state.metrics.rows_read.saturating_add(1);\n        state.metrics.btree_prev = state.metrics.btree_prev.saturating_add(1);\n        // Track if this is a full table scan or index scan\n        if let Some((_, cursor_type)) = program.cursor_ref.get(*cursor_id) {\n            if cursor_type.is_index() {\n                state.metrics.index_steps = state.metrics.index_steps.saturating_add(1);\n            } else if matches!(cursor_type, CursorType::BTreeTable(_)) {\n                state.metrics.fullscan_steps = state.metrics.fullscan_steps.saturating_add(1);\n            }\n        }\n        state.pc = pc_if_prev.as_offset_int();\n    } else {\n        state.pc += 1;\n    }\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn halt(\n    program: &Program,\n    state: &mut ProgramState,\n    pager: &Arc<Pager>,\n    err_code: usize,\n    description: &str,\n    on_error: Option<ResolveType>,\n) -> Result<InsnFunctionStepResult> {\n    let mv_store = program.connection.mv_store();\n    let auto_commit = program.connection.auto_commit.load(Ordering::SeqCst);\n\n    // Check if we're resuming from a FAIL commit I/O wait.\n    // If pending_fail_error is set, we were in the middle of committing partial changes\n    // for FAIL mode and need to continue the commit, then return the stored error.\n    if let Some(pending_error) = state.pending_fail_error.take() {\n        match program.commit_txn(pager.clone(), state, mv_store.as_ref(), false)? {\n            IOResult::Done(_) => return Err(pending_error),\n            IOResult::IO(io) => {\n                state.pending_fail_error = Some(pending_error); // put it back and wait\n                return Ok(InsnFunctionStepResult::IO(io));\n            }\n        }\n    }\n\n    if err_code > 0 {\n        vtab_rollback_all(&program.connection)?;\n    }\n\n    // Handle RAISE errors - these carry their own resolve type\n    if let Some(resolve_type) = on_error {\n        // RAISE(IGNORE) signals the parent to skip the current row\n        if resolve_type == ResolveType::Ignore {\n            return Err(LimboError::RaiseIgnore);\n        }\n        if err_code > 0 {\n            let error = match resolve_type {\n                ResolveType::Abort | ResolveType::Rollback | ResolveType::Fail => {\n                    LimboError::Raise(resolve_type, description.to_string())\n                }\n                ResolveType::Ignore => unreachable!(\"handled above\"),\n                ResolveType::Replace => unreachable!(\"Replace not valid for RAISE\"),\n            };\n\n            // Trigger subprograms must not commit — just propagate the error.\n            // The parent program's abort() handles transaction state.\n            if program.is_trigger_subprogram() {\n                return Err(error);\n            }\n\n            return Err(error);\n        }\n    }\n\n    // Determine the constraint error (if any) based on error code\n    let constraint_error = match err_code {\n        0 => None,\n        SQLITE_CONSTRAINT_PRIMARYKEY => Some(LimboError::Constraint(format!(\n            \"UNIQUE constraint failed: {description} (19)\"\n        ))),\n        SQLITE_CONSTRAINT_CHECK => Some(LimboError::Constraint(format!(\n            \"CHECK constraint failed: {description} (19)\"\n        ))),\n        SQLITE_CONSTRAINT_NOTNULL => Some(LimboError::Constraint(format!(\n            \"NOT NULL constraint failed: {description} (19)\"\n        ))),\n        SQLITE_CONSTRAINT_UNIQUE => Some(LimboError::Constraint(format!(\n            \"UNIQUE constraint failed: {description} (19)\"\n        ))),\n        SQLITE_CONSTRAINT_FOREIGNKEY => {\n            Some(LimboError::ForeignKeyConstraint(description.to_string()))\n        }\n        SQLITE_CONSTRAINT_TRIGGER => Some(LimboError::Constraint(description.to_string())),\n        // SQLITE_ERROR is a generic error (e.g. ALTER TABLE validation), not a constraint.\n        // Use InternalError so abort() doesn't apply ON CONFLICT resolution to it.\n        SQLITE_ERROR => Some(LimboError::InternalError(description.to_string())),\n        _ => Some(LimboError::Constraint(format!(\n            \"undocumented halt error code {description}\"\n        ))),\n    };\n\n    // Handle constraint errors\n    if let Some(error) = constraint_error {\n        // For FAIL mode with autocommit, commit partial changes before returning error.\n        // This matches SQLite behavior where FAIL keeps changes made before the error.\n        // Note: ON CONFLICT FAIL does NOT apply to FK violations, so we check for those first.\n        if program.resolve_type == ResolveType::Fail && auto_commit {\n            // Check for immediate FK violations - FK errors don't respect ON CONFLICT\n            if program.connection.foreign_keys_enabled()\n                && state.get_fk_immediate_violations_during_stmt() > 0\n            {\n                return Err(LimboError::ForeignKeyConstraint(\n                    \"immediate foreign key constraint failed\".to_string(),\n                ));\n            }\n\n            // Release savepoint to preserve partial changes, then commit\n            state.end_statement(&program.connection, pager, EndStatement::ReleaseSavepoint)?;\n            vtab_commit_all(&program.connection)?;\n            index_method_pre_commit_all(state, pager)?;\n\n            // Commit the transaction with partial changes\n            match program.commit_txn(pager.clone(), state, mv_store.as_ref(), false)? {\n                IOResult::Done(_) => return Err(error),\n                IOResult::IO(io) => {\n                    // store the error for reentrancy\n                    state.pending_fail_error = Some(error);\n                    return Ok(InsnFunctionStepResult::IO(io));\n                }\n            }\n        }\n\n        // For non-FAIL modes (or non-autocommit), just return the error.\n        // abort() will handle rollback based on resolve_type.\n        return Err(error);\n    }\n\n    tracing::trace!(\"halt(auto_commit={})\", auto_commit);\n\n    // Check for immediate foreign key violations.\n    // Any immediate violation causes the statement subtransaction to roll back.\n    if program.connection.foreign_keys_enabled()\n        && state.get_fk_immediate_violations_during_stmt() > 0\n    {\n        return Err(LimboError::ForeignKeyConstraint(\n            \"immediate foreign key constraint failed\".to_string(),\n        ));\n    }\n\n    if program.is_trigger_subprogram() {\n        return Ok(InsnFunctionStepResult::Done);\n    }\n\n    if auto_commit {\n        // In autocommit mode, a statement that leaves deferred violations must fail here,\n        // and it also ends the transaction.\n        if program.connection.foreign_keys_enabled() {\n            let deferred_violations = program\n                .connection\n                .fk_deferred_violations\n                .swap(0, Ordering::AcqRel);\n            if deferred_violations > 0 {\n                vtab_rollback_all(&program.connection)?;\n                if let Some(mv_store) = mv_store.as_ref() {\n                    if let Some(tx_id) = program.connection.get_mv_tx_id() {\n                        mv_store.rollback_tx(\n                            tx_id,\n                            pager.clone(),\n                            &program.connection,\n                            crate::MAIN_DB_ID,\n                        );\n                    }\n                    pager.end_read_tx();\n                } else {\n                    pager.rollback_tx(&program.connection);\n                }\n                program.connection.set_tx_state(TransactionState::None);\n                return Err(LimboError::ForeignKeyConstraint(\n                    \"deferred foreign key constraint failed\".to_string(),\n                ));\n            }\n        }\n        state.end_statement(&program.connection, pager, EndStatement::ReleaseSavepoint)?;\n        vtab_commit_all(&program.connection)?;\n        index_method_pre_commit_all(state, pager)?;\n        let result = program\n            .commit_txn(pager.clone(), state, mv_store.as_ref(), false)\n            .map(Into::into);\n        // Apply deferred CDC state and reset CDC txn ID after successful commit\n        if matches!(result, Ok(InsnFunctionStepResult::Done)) {\n            if let Some(cdc_info) = state.pending_cdc_info.take() {\n                program.connection.set_capture_data_changes_info(cdc_info);\n            }\n            program.connection.set_cdc_transaction_id(-1);\n        }\n        result\n    } else {\n        // Even if deferred violations are present, the statement subtransaction completes successfully when\n        // it is part of an interactive transaction.\n        state.end_statement(&program.connection, pager, EndStatement::ReleaseSavepoint)?;\n        // Apply deferred CDC state after successful statement completion\n        if let Some(cdc_info) = state.pending_cdc_info.take() {\n            program.connection.set_capture_data_changes_info(cdc_info);\n        }\n        if program.change_cnt_on {\n            program\n                .connection\n                .set_changes(state.n_change.load(Ordering::SeqCst));\n        }\n        Ok(InsnFunctionStepResult::Done)\n    }\n}\n\n/// Call xCommit on all virtual tables that participated in the current transaction.\npub(crate) fn vtab_commit_all(conn: &Connection) -> crate::Result<()> {\n    let mut set = conn.vtab_txn_states.write();\n    if set.is_empty() {\n        return Ok(());\n    }\n    let reg = &conn.syms.read().vtabs;\n    for id in set.drain() {\n        let vtab = reg\n            .iter()\n            .find(|(_, vtab)| vtab.id() == id)\n            .expect(\"vtab must exist\");\n        vtab.1.commit()?;\n    }\n    Ok(())\n}\n\n/// Flush pending writes on all index method cursors before transaction commit.\n/// This ensures index method writes are persisted as part of the transaction.\npub(crate) fn index_method_pre_commit_all(\n    state: &mut ProgramState,\n    pager: &Arc<Pager>,\n) -> crate::Result<()> {\n    for cursor_opt in state.cursors.iter_mut().flatten() {\n        let Cursor::IndexMethod(cursor) = cursor_opt else {\n            continue;\n        };\n        loop {\n            match cursor.pre_commit()? {\n                IOResult::Done(()) => break,\n                IOResult::IO(io) => {\n                    while !io.finished() {\n                        pager.io.step()?;\n                    }\n                }\n            }\n        }\n    }\n    Ok(())\n}\n\n/// Rollback all virtual tables that are part of the current transaction.\nfn vtab_rollback_all(conn: &Connection) -> crate::Result<()> {\n    let mut set = conn.vtab_txn_states.write();\n    if set.is_empty() {\n        return Ok(());\n    }\n    let reg = &conn.syms.read().vtabs;\n    for id in set.drain() {\n        let vtab = reg\n            .iter()\n            .find(|(_, vtab)| vtab.id() == id)\n            .expect(\"vtab must exist\");\n        vtab.1.rollback()?;\n    }\n    Ok(())\n}\n\npub fn op_halt(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        Halt {\n            err_code,\n            description,\n            on_error,\n            description_reg,\n        },\n        insn\n    );\n    // If description_reg is set, read the error message from that register at runtime\n    // (used by RAISE with expression-based error messages).\n    let desc = if let Some(reg) = description_reg {\n        state.registers[*reg].get_value().to_string()\n    } else {\n        description.to_string()\n    };\n    halt(program, state, pager, *err_code, &desc, *on_error)\n}\n\npub fn op_halt_if_null(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        HaltIfNull {\n            target_reg,\n            err_code,\n            description,\n        },\n        insn\n    );\n    if state.registers[*target_reg].get_value() == &Value::Null {\n        halt(program, state, pager, *err_code, description, None)\n    } else {\n        state.pc += 1;\n        Ok(InsnFunctionStepResult::Step)\n    }\n}\n\n#[derive(Debug, Clone, Copy)]\npub enum OpTransactionState {\n    Start,\n    AttachedBeginWriteTx,\n    CheckSchemaCookie,\n    BeginStatement,\n}\n\npub fn op_transaction(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    let result = op_transaction_inner(program, state, insn, pager);\n    tracing::debug!(\n        \"op_transaction: end: state={:?}, tx_state={:?}\",\n        state.op_transaction_state,\n        program.connection.get_tx_state()\n    );\n    match result {\n        Ok(result) => Ok(result),\n        Err(err) => {\n            state.op_transaction_state = OpTransactionState::Start;\n            Err(err)\n        }\n    }\n}\n\n/// Begin an MVCC transaction on the given MvStore using the specified mode.\n/// When `existing_tx_id` is `Some`, upgrades an existing transaction to exclusive.\nfn begin_mvcc_tx(\n    mv_store: &MvStore,\n    pager: &Arc<Pager>,\n    mode: &TransactionMode,\n    existing_tx_id: Option<u64>,\n) -> Result<u64> {\n    match mode {\n        TransactionMode::None | TransactionMode::Read | TransactionMode::Concurrent => {\n            mv_store.begin_tx(pager.clone())\n        }\n        TransactionMode::Write => mv_store.begin_exclusive_tx(pager.clone(), existing_tx_id),\n    }\n}\n\npub fn op_transaction_inner(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        Transaction {\n            db,\n            tx_mode,\n            schema_cookie,\n        },\n        insn\n    );\n    if program.is_trigger_subprogram() {\n        crate::bail_parse_error!(\n            \"Transaction instruction should not be used in trigger subprograms\"\n        );\n    }\n    let pager = program.get_pager_from_database_index(db);\n    // Get the MvStore for the specific database (main or attached).\n    let mv_store = program.connection.mv_store_for_db(*db);\n    loop {\n        match state.op_transaction_state {\n            OpTransactionState::Start => {\n                let conn = program.connection.clone();\n                let write = matches!(tx_mode, TransactionMode::Write);\n                if write && conn.is_readonly(*db) {\n                    return Err(LimboError::ReadOnly);\n                }\n\n                // 1. We try to upgrade current version\n                let current_state = conn.get_tx_state();\n                let is_attached = crate::is_attached_db(*db);\n                let (new_transaction_state, updated) = if conn.is_nested_stmt() {\n                    (current_state, false)\n                } else if is_attached {\n                    // For attached databases, don't modify the connection-level\n                    // transaction state — it tracks the main database's state.\n                    // Attached pager locks are managed independently below.\n                    (current_state, false)\n                } else {\n                    match (current_state, write) {\n                        // pending state means that we tried beginning a tx and the method returned IO.\n                        // instead of ending the read tx, just update the state to pending.\n                        (TransactionState::PendingUpgrade { .. }, write) => {\n                            turso_assert!(\n                                write,\n                                \"pending upgrade should only be set for write transactions\"\n                            );\n                            (\n                                TransactionState::Write {\n                                    schema_did_change: false,\n                                },\n                                true,\n                            )\n                        }\n                        (TransactionState::Write { schema_did_change }, true) => {\n                            (TransactionState::Write { schema_did_change }, false)\n                        }\n                        (TransactionState::Write { schema_did_change }, false) => {\n                            (TransactionState::Write { schema_did_change }, false)\n                        }\n                        (TransactionState::Read, true) => (\n                            TransactionState::Write {\n                                schema_did_change: false,\n                            },\n                            true,\n                        ),\n                        (TransactionState::Read, false) => (TransactionState::Read, false),\n                        (TransactionState::None, true) => (\n                            TransactionState::Write {\n                                schema_did_change: false,\n                            },\n                            true,\n                        ),\n                        (TransactionState::None, false) => (TransactionState::Read, true),\n                    }\n                };\n\n                // 2. Start transaction if needed\n                if let Some(mv_store) = mv_store.as_ref() {\n                    if is_attached {\n                        // Attached databases don't participate in the connection-level\n                        // transaction state machine above (phase 1), so the pager read\n                        // tx that the main DB path starts on None→Read isn't triggered\n                        // for them. We need it here to pin a consistent WAL snapshot\n                        // for the attached pager's B-tree page reads.\n                        if !pager.holds_read_lock() {\n                            pager.begin_read_tx()?;\n                        }\n                        pager.mvcc_refresh_if_db_changed();\n\n                        let current_mv_tx = conn.get_mv_tx_for_db(*db);\n                        if current_mv_tx.is_none() {\n                            // Reject CONCURRENT on an attached DB if the main\n                            // DB already started with BEGIN DEFERRED.\n                            let conn_has_executed_begin_deferred =\n                                !conn.auto_commit.load(Ordering::SeqCst)\n                                    && conn.get_mv_tx().is_none();\n                            if conn_has_executed_begin_deferred\n                                && *tx_mode == TransactionMode::Concurrent\n                            {\n                                mark_unlikely();\n                                return Err(LimboError::TxError(\n                                    \"Cannot start CONCURRENT transaction after BEGIN DEFERRED\"\n                                        .to_string(),\n                                ));\n                            }\n                            // Use the same tx_mode as the main DB's active\n                            // transaction when available, so BEGIN CONCURRENT\n                            // applies to all databases uniformly.\n                            let effective_mode =\n                                conn.get_mv_tx().map(|(_, mode)| mode).unwrap_or(*tx_mode);\n                            match begin_mvcc_tx(mv_store, &pager, &effective_mode, None) {\n                                Ok(tx_id) => {\n                                    conn.set_mv_tx_for_db(*db, Some((tx_id, effective_mode)));\n                                }\n                                Err(err) => {\n                                    pager.end_read_tx();\n                                    return Err(err);\n                                }\n                            }\n                        } else if write {\n                            // Upgrade: attached DB has a Read/Concurrent tx but the\n                            // statement needs write access. Mirror the main DB's\n                            // upgrade logic so that exclusive locks are acquired.\n                            let (tx_id, current_mode) = current_mv_tx.unwrap();\n                            if matches!(current_mode, TransactionMode::None | TransactionMode::Read)\n                                && matches!(tx_mode, TransactionMode::Write)\n                            {\n                                if let Err(err) =\n                                    begin_mvcc_tx(mv_store, &pager, tx_mode, Some(tx_id))\n                                {\n                                    pager.end_read_tx();\n                                    return Err(err);\n                                }\n                                conn.set_mv_tx_for_db(*db, Some((tx_id, *tx_mode)));\n                            }\n                        }\n                    } else {\n                        // Main database MVCC path (unchanged logic)\n                        let started_read_tx =\n                            updated && matches!(current_state, TransactionState::None);\n                        if started_read_tx {\n                            turso_assert!(\n                                !conn.is_nested_stmt(),\n                                \"nested stmt should not begin a new read transaction\"\n                            );\n                            pager.begin_read_tx()?;\n                            state.auto_txn_cleanup = TxnCleanup::RollbackTxn;\n                        }\n                        // MVCC reads must refresh WAL change counters to avoid stale page-cache reads.\n                        pager.mvcc_refresh_if_db_changed();\n                        // In MVCC we don't have write exclusivity, therefore we just need to start a transaction if needed.\n                        // Programs can run Transaction twice, first with read flag and then with write flag. So a single txid is enough\n                        // for both.\n                        let current_mv_tx = program.connection.get_mv_tx_for_db(*db);\n                        let has_existing_mv_tx = current_mv_tx.is_some();\n\n                        let conn_has_executed_begin_deferred = !has_existing_mv_tx\n                            && !program.connection.auto_commit.load(Ordering::SeqCst);\n                        if conn_has_executed_begin_deferred\n                            && *tx_mode == TransactionMode::Concurrent\n                        {\n                            mark_unlikely();\n                            return Err(LimboError::TxError(\n                                \"Cannot start CONCURRENT transaction after BEGIN DEFERRED\"\n                                    .to_string(),\n                            ));\n                        }\n\n                        if !has_existing_mv_tx {\n                            match begin_mvcc_tx(mv_store, &pager, tx_mode, None) {\n                                Ok(tx_id) => {\n                                    program\n                                        .connection\n                                        .set_mv_tx_for_db(*db, Some((tx_id, *tx_mode)));\n                                }\n                                Err(err) => {\n                                    if started_read_tx {\n                                        pager.end_read_tx();\n                                        conn.set_tx_state(TransactionState::None);\n                                        state.auto_txn_cleanup = TxnCleanup::None;\n                                    }\n                                    return Err(err);\n                                }\n                            }\n                        } else if updated {\n                            // TODO: fix tx_mode in Insn::Transaction, now each statement overrides it even if there's already a CONCURRENT Tx in progress, for example\n                            let (tx_id, mv_tx_mode) = current_mv_tx\n                                .expect(\"current_mv_tx should be Some when updated is true\");\n                            let actual_tx_mode = if mv_tx_mode == TransactionMode::Concurrent {\n                                TransactionMode::Concurrent\n                            } else {\n                                *tx_mode\n                            };\n                            if matches!(new_transaction_state, TransactionState::Write { .. })\n                                && matches!(actual_tx_mode, TransactionMode::Write)\n                            {\n                                if let Err(err) =\n                                    begin_mvcc_tx(mv_store, &pager, &actual_tx_mode, Some(tx_id))\n                                {\n                                    if started_read_tx {\n                                        pager.end_read_tx();\n                                        conn.set_tx_state(TransactionState::None);\n                                        state.auto_txn_cleanup = TxnCleanup::None;\n                                    }\n                                    return Err(err);\n                                }\n                            }\n                        }\n                    }\n                } else {\n                    if matches!(tx_mode, TransactionMode::Concurrent) {\n                        mark_unlikely();\n                        return Err(LimboError::TxError(\n                            \"Concurrent transaction mode is only supported when MVCC is enabled\"\n                                .to_string(),\n                        ));\n                    }\n                    // For attached databases without MVCC, always start read/write\n                    // transactions on the attached pager, since the connection-level\n                    // transaction state may already be Read/Write from the main database.\n                    let is_attached = crate::is_attached_db(*db);\n                    if is_attached {\n                        // If the pager already holds a read lock (e.g., after\n                        // SchemaUpdated reprepare or prior write tx), skip\n                        // locks that are already held.\n                        if pager.holds_read_lock() {\n                            if matches!(tx_mode, TransactionMode::Write)\n                                && !pager.holds_write_lock()\n                            {\n                                state.op_transaction_state =\n                                    OpTransactionState::AttachedBeginWriteTx;\n                                continue;\n                            }\n                            state.op_transaction_state = OpTransactionState::CheckSchemaCookie;\n                            continue;\n                        }\n                        pager.begin_read_tx()?;\n                        if matches!(tx_mode, TransactionMode::Write) {\n                            // Transition to AttachedBeginWriteTx to handle begin_write_tx\n                            // separately, so if it returns IO we don't re-call begin_read_tx\n                            // on re-entry.\n                            state.op_transaction_state = OpTransactionState::AttachedBeginWriteTx;\n                            continue;\n                        }\n                    } else if updated && matches!(current_state, TransactionState::None) {\n                        turso_assert!(\n                            !conn.is_nested_stmt(),\n                            \"nested stmt should not begin a new read transaction\"\n                        );\n                        pager.begin_read_tx()?;\n                        state.auto_txn_cleanup = TxnCleanup::RollbackTxn;\n                    }\n\n                    if !is_attached\n                        && updated\n                        && matches!(new_transaction_state, TransactionState::Write { .. })\n                    {\n                        turso_assert!(\n                            !conn.is_nested_stmt(),\n                            \"nested stmt should not begin a new write transaction\"\n                        );\n                        let begin_w_tx_res = pager.begin_write_tx();\n                        if matches!(\n                            begin_w_tx_res,\n                            Err(LimboError::Busy | LimboError::BusySnapshot)\n                        ) {\n                            // We failed to upgrade to write transaction so put the transaction into its original state.\n                            // That is, if the transaction had not started, end the read transaction so that next time we\n                            // start a new one.\n                            match current_state {\n                                TransactionState::None\n                                | TransactionState::PendingUpgrade {\n                                    has_read_txn: false,\n                                } => {\n                                    pager.end_read_tx();\n                                    conn.set_tx_state(TransactionState::None);\n                                    state.auto_txn_cleanup = TxnCleanup::None;\n                                }\n                                TransactionState::Read\n                                | TransactionState::PendingUpgrade { has_read_txn: true } => {\n                                    conn.set_tx_state(TransactionState::Read);\n                                }\n                                TransactionState::Write { .. } => {\n                                    panic!(\"impossible state: {current_state:?}\")\n                                }\n                            }\n                            return Err(begin_w_tx_res.unwrap_err());\n                        }\n                        if let IOResult::IO(io) = begin_w_tx_res? {\n                            // set the transaction state to pending so we don't have to\n                            // end the read transaction.\n                            conn.set_tx_state(TransactionState::PendingUpgrade {\n                                has_read_txn: matches!(current_state, TransactionState::Read),\n                            });\n                            return Ok(InsnFunctionStepResult::IO(io));\n                        }\n                    }\n                }\n\n                // 3. Transaction state should be updated before checking for Schema cookie so that the tx is ended properly on error\n                if updated {\n                    conn.set_tx_state(new_transaction_state);\n                }\n                state.op_transaction_state = OpTransactionState::CheckSchemaCookie;\n                continue;\n            }\n            // 3b. For attached databases, begin the write transaction after\n            // begin_read_tx has already completed in the Start state.\n            OpTransactionState::AttachedBeginWriteTx => {\n                let res = pager.begin_write_tx()?;\n                if let IOResult::IO(io) = res {\n                    return Ok(InsnFunctionStepResult::IO(io));\n                }\n                state.op_transaction_state = OpTransactionState::CheckSchemaCookie;\n                continue;\n            }\n            // 4. Check whether schema has changed if we are actually going to access the database.\n            // Can only read header if page 1 has been allocated already\n            // begin_write_tx that happens, but not begin_read_tx\n            OpTransactionState::CheckSchemaCookie => {\n                let res = get_schema_cookie(&pager, mv_store.as_ref(), program, *db);\n                match res {\n                    Ok(IOResult::Done(header_schema_cookie)) => {\n                        if header_schema_cookie != *schema_cookie {\n                            tracing::debug!(\n                                \"schema changed, force reprepare: {} != {}\",\n                                header_schema_cookie,\n                                *schema_cookie\n                            );\n                            return Err(LimboError::SchemaUpdated);\n                        }\n                    }\n                    Ok(IOResult::IO(io)) => return Ok(InsnFunctionStepResult::IO(io)),\n                    // This means we are starting a read_tx and we do not have a page 1 yet, so we just continue execution\n                    Err(LimboError::Page1NotAlloc) => {}\n                    Err(err) => {\n                        return Err(err);\n                    }\n                }\n\n                state.op_transaction_state = OpTransactionState::BeginStatement;\n            }\n            OpTransactionState::BeginStatement => {\n                let needs_stmt_journal = program.needs_stmt_subtransactions.load(Ordering::Relaxed);\n                if *db == crate::MAIN_DB_ID && needs_stmt_journal {\n                    let write = matches!(tx_mode, TransactionMode::Write);\n                    let res = state.begin_statement(&program.connection, &pager, write)?;\n                    if let IOResult::IO(io) = res {\n                        return Ok(InsnFunctionStepResult::IO(io));\n                    }\n                } else if crate::is_attached_db(*db)\n                    && matches!(tx_mode, TransactionMode::Write)\n                    && needs_stmt_journal\n                {\n                    if let Some(mv_store) = program.connection.mv_store_for_db(*db) {\n                        // Attached MVCC DB: open an MvStore savepoint.\n                        if let Some(tx_id) = program.connection.get_mv_tx_id_for_db(*db) {\n                            mv_store.begin_savepoint(tx_id);\n                        }\n                    } else {\n                        // Attached WAL DB: open a pager savepoint for statement rollback.\n                        let db_size =\n                            return_if_io!(pager.with_header(|header| header.database_size.get()));\n                        pager.open_subjournal()?;\n                        pager.try_use_subjournal()?;\n                        let result = pager.open_savepoint(db_size);\n                        if result.is_err() {\n                            pager.stop_use_subjournal();\n                        }\n                        result?;\n                        state.attached_savepoint_pagers.push(pager.clone());\n                    }\n                }\n\n                if *db == crate::MAIN_DB_ID\n                    && matches!(tx_mode, TransactionMode::Write)\n                    && !program.connection.auto_commit.load(Ordering::SeqCst)\n                {\n                    program\n                        .connection\n                        .n_active_writes\n                        .fetch_add(1, Ordering::SeqCst);\n                    state.is_active_write = true;\n                }\n                state.pc += 1;\n                state.op_transaction_state = OpTransactionState::Start;\n                return Ok(InsnFunctionStepResult::Step);\n            }\n        }\n    }\n}\n\npub fn op_auto_commit(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        AutoCommit {\n            auto_commit,\n            rollback\n        },\n        insn\n    );\n\n    // Main DB's MvStore drives the commit/rollback routing.  The attach-time\n    // journal-mode compatibility check ensures all attached DBs match, so\n    // checking the main DB is sufficient to choose the MVCC vs WAL path.\n    let mv_store = program.connection.mv_store();\n    let conn = program.connection.clone();\n    let fk_on = conn.foreign_keys_enabled();\n    let had_autocommit = conn.auto_commit.load(Ordering::SeqCst); // true, not in tx\n\n    // Drive any multi-step commit/rollback that's already in progress.\n    // This handles main DB commits (Committing), attached DB commits\n    // (CommittingAttached), MVCC commits (CommittingMvcc), and attached\n    // MVCC commits (CommittingAttachedMvcc) that yielded on IO and need re-entry.\n    if !matches!(state.commit_state, CommitState::Ready) {\n        let res = program\n            .commit_txn(pager.clone(), state, mv_store.as_ref(), *rollback)\n            .map(Into::into);\n        // Only clear after a final, successful non-rollback COMMIT.\n        if fk_on\n            && !*rollback\n            && matches!(\n                res,\n                Ok(InsnFunctionStepResult::Step | InsnFunctionStepResult::Done)\n            )\n        {\n            conn.clear_deferred_foreign_key_violations();\n        }\n        return res;\n    }\n\n    if program.is_trigger_subprogram() {\n        // Trigger subprograms never commit or rollback.\n        state.pc += 1;\n        return Ok(InsnFunctionStepResult::Step);\n    }\n\n    // The logic in this opcode can be a bit confusing, so to make things a bit clearer lets be\n    // very explicit about the currently existing and requested state.\n    let requested_autocommit = *auto_commit;\n    let requested_rollback = *rollback;\n    let changed = requested_autocommit != had_autocommit;\n    let is_txn_end_eq = changed && requested_autocommit;\n    // what the requested operation is\n    let is_begin_req = had_autocommit && !requested_autocommit && !requested_rollback;\n    let is_commit_req = !had_autocommit && requested_autocommit && !requested_rollback;\n    let is_rollback_req = !had_autocommit && requested_autocommit && requested_rollback;\n\n    if is_txn_end_eq && conn.n_active_writes.load(Ordering::SeqCst) > 0 {\n        return Err(LimboError::Busy);\n    }\n\n    if changed {\n        if requested_rollback {\n            // ROLLBACK transition\n            if let Some(mv_store) = mv_store.as_ref() {\n                if let Some(tx_id) = conn.get_mv_tx_id() {\n                    mv_store.rollback_tx(tx_id, pager.clone(), &conn, crate::MAIN_DB_ID);\n                }\n                pager.end_read_tx();\n                conn.rollback_attached_mvcc_txs(true);\n            } else {\n                pager.rollback_tx(&conn);\n            }\n            conn.rollback_attached_wal_txns();\n            conn.set_tx_state(TransactionState::None);\n            conn.auto_commit.store(true, Ordering::SeqCst);\n            conn.set_cdc_transaction_id(-1);\n        } else {\n            // BEGIN (true->false) or COMMIT (false->true)\n            if is_commit_req {\n                // Pre-check deferred FKs; leave tx open and do NOT clear violations\n                check_deferred_fk_on_commit(&conn)?;\n            }\n            conn.auto_commit\n                .store(requested_autocommit, Ordering::SeqCst);\n        }\n    } else {\n        // No autocommit flip.\n        let mvcc_tx_active = conn.get_mv_tx().is_some();\n        if !mvcc_tx_active {\n            if !requested_autocommit {\n                return Err(LimboError::TxError(\n                    \"cannot start a transaction within a transaction\".to_string(),\n                ));\n            } else if requested_rollback {\n                return Err(LimboError::TxError(\n                    \"cannot rollback - no transaction is active\".to_string(),\n                ));\n            } else {\n                return Err(LimboError::TxError(\n                    \"cannot commit - no transaction is active\".to_string(),\n                ));\n            }\n        } else if is_begin_req {\n            return Err(LimboError::TxError(\n                \"cannot use BEGIN after BEGIN CONCURRENT\".to_string(),\n            ));\n        }\n    }\n\n    // For explicit COMMIT, flush any pending index method writes first\n    if is_commit_req {\n        index_method_pre_commit_all(state, pager)?;\n    }\n\n    let res = program\n        .commit_txn(pager.clone(), state, mv_store.as_ref(), requested_rollback)\n        .map(Into::into);\n\n    if mv_store.is_none()\n        && matches!(\n            res,\n            Ok(InsnFunctionStepResult::Step | InsnFunctionStepResult::Done)\n        )\n        && (is_rollback_req || is_commit_req)\n    {\n        pager.clear_savepoints()?;\n    }\n\n    // Clear deferred FK counters only after FINAL success of COMMIT/ROLLBACK.\n    if fk_on\n        && matches!(\n            res,\n            Ok(InsnFunctionStepResult::Step | InsnFunctionStepResult::Done)\n        )\n        && (is_rollback_req || is_commit_req)\n    {\n        conn.clear_deferred_foreign_key_violations();\n    }\n\n    // Reset CDC transaction ID after successful COMMIT or ROLLBACK.\n    if matches!(\n        res,\n        Ok(InsnFunctionStepResult::Step | InsnFunctionStepResult::Done)\n    ) && (is_rollback_req || is_commit_req)\n    {\n        conn.set_cdc_transaction_id(-1);\n    }\n\n    res\n}\n\npub fn op_savepoint(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(Savepoint { op, name }, insn);\n    let conn = program.connection.clone();\n    let mv_store = conn.mv_store();\n\n    match *op {\n        SavepointOp::Begin => {\n            let starts_transaction = conn.auto_commit.load(Ordering::SeqCst);\n            let deferred_fk_violations = conn.get_deferred_foreign_key_violations();\n\n            if let Some(mv_store) = mv_store.as_ref() {\n                let tx_id = if let Some(tx_id) = conn.get_mv_tx_id() {\n                    tx_id\n                } else {\n                    let tx_id = mv_store.begin_tx(pager.clone())?;\n                    conn.set_mv_tx(Some((tx_id, TransactionMode::Read)));\n                    if matches!(conn.get_tx_state(), TransactionState::None) {\n                        conn.set_tx_state(TransactionState::Read);\n                    }\n                    tx_id\n                };\n                mv_store.begin_named_savepoint(\n                    tx_id,\n                    name.clone(),\n                    starts_transaction,\n                    deferred_fk_violations,\n                );\n                // Open matching named savepoints on attached MVCC databases.\n                conn.for_each_attached_mv_tx(|db_id, att_tx_id| {\n                    if let Some(att_mv) = conn.mv_store_for_db(db_id) {\n                        att_mv.begin_named_savepoint(\n                            att_tx_id,\n                            name.clone(),\n                            false,\n                            deferred_fk_violations,\n                        );\n                    }\n                });\n            } else {\n                pager.open_subjournal()?;\n                let db_size = return_if_io!(pager.with_header(|header| header.database_size.get()));\n                pager.open_named_savepoint(\n                    name.clone(),\n                    db_size,\n                    starts_transaction,\n                    deferred_fk_violations,\n                )?;\n            }\n\n            if starts_transaction {\n                conn.auto_commit.store(false, Ordering::SeqCst);\n            }\n\n            state.pc += 1;\n            Ok(InsnFunctionStepResult::Step)\n        }\n        SavepointOp::Release => {\n            let release_result = if let Some(mv_store) = mv_store.as_ref() {\n                match conn.get_mv_tx_id() {\n                    Some(tx_id) => mv_store.release_named_savepoint(tx_id, name)?,\n                    None => SavepointResult::NotFound,\n                }\n            } else {\n                pager.release_named_savepoint(name)?\n            };\n            // Release matching named savepoints on attached MVCC databases.\n            if mv_store.is_some() {\n                conn.for_each_attached_mv_tx(|db_id, att_tx_id| {\n                    if let Some(att_mv) = conn.mv_store_for_db(db_id) {\n                        let _ = att_mv.release_named_savepoint(att_tx_id, name);\n                    }\n                });\n            }\n            match release_result {\n                SavepointResult::NotFound => {\n                    mark_unlikely();\n                    return Err(LimboError::TxError(format!(\"no such savepoint: {name}\")));\n                }\n                SavepointResult::Release => {\n                    // Savepoint released successfully, just continue\n                }\n                SavepointResult::Commit => {\n                    // This means that releasing the savepoint caused the transaction to commit, so we need to auto-commit here.\n                    let auto_commit = Insn::AutoCommit {\n                        auto_commit: true,\n                        rollback: false,\n                    };\n                    return op_auto_commit(program, state, &auto_commit, pager);\n                }\n            }\n\n            state.pc += 1;\n            Ok(InsnFunctionStepResult::Step)\n        }\n        SavepointOp::RollbackTo => {\n            let deferred_fk_snapshot = if let Some(mv_store) = mv_store.as_ref() {\n                // Rollback named savepoints on attached MVCC databases.\n                conn.for_each_attached_mv_tx(|db_id, att_tx_id| {\n                    if let Some(att_mv) = conn.mv_store_for_db(db_id) {\n                        let _ = att_mv.rollback_to_named_savepoint(att_tx_id, name);\n                    }\n                });\n                match conn.get_mv_tx_id() {\n                    Some(tx_id) => mv_store.rollback_to_named_savepoint(tx_id, name)?,\n                    None => None,\n                }\n            } else {\n                pager.rollback_to_named_savepoint(name)?\n            };\n\n            let Some(deferred_fk_snapshot) = deferred_fk_snapshot else {\n                mark_unlikely();\n                return Err(LimboError::TxError(format!(\"no such savepoint: {name}\")));\n            };\n            conn.fk_deferred_violations\n                .store(deferred_fk_snapshot, Ordering::SeqCst);\n\n            // After rolling back pages, the in-memory schema cache may be stale\n            // if DDL was executed within the savepoint. Invalidate the pager's\n            // cached schema cookie and check if a schema reparse is needed.\n            pager.set_schema_cookie(None);\n            let in_memory_version = conn.schema.read().schema_version;\n            let pager_ref = conn.pager.load().clone();\n            match pager_ref\n                .io\n                .block(|| pager.with_header(|h| h.schema_cookie.get()))\n            {\n                Ok(on_disk_cookie) if in_memory_version != on_disk_cookie => {\n                    // Schema was modified during the savepoint. Try to reparse\n                    // from the restored database pages. If that fails (e.g. the\n                    // database was empty at the savepoint), use an empty schema.\n                    if conn.reparse_schema().is_err() {\n                        conn.with_schema_mut(|schema| {\n                            *schema = Schema::new();\n                        });\n                    }\n                }\n                Err(_) => {\n                    // Header page is not readable (database empty after rollback).\n                    // Reset to an empty schema.\n                    conn.with_schema_mut(|schema| {\n                        *schema = Schema::new();\n                    });\n                }\n                _ => {} // Schema unchanged, nothing to do.\n            }\n\n            state.pc += 1;\n            Ok(InsnFunctionStepResult::Step)\n        }\n    }\n}\n\nfn check_deferred_fk_on_commit(conn: &Connection) -> Result<()> {\n    if !conn.foreign_keys_enabled() {\n        return Ok(());\n    }\n    if conn.get_deferred_foreign_key_violations() > 0 {\n        return Err(LimboError::ForeignKeyConstraint(\n            \"deferred foreign key constraint failed on commit\".into(),\n        ));\n    }\n    Ok(())\n}\n\npub fn op_goto(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(Goto { target_pc }, insn);\n    if !target_pc.is_offset() {\n        crate::bail_corrupt_error!(\"Unresolved label: {target_pc:?}\");\n    }\n    state.pc = target_pc.as_offset_int();\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_gosub(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        Gosub {\n            target_pc,\n            return_reg,\n        },\n        insn\n    );\n    if !target_pc.is_offset() {\n        crate::bail_corrupt_error!(\"Unresolved label: {target_pc:?}\");\n    }\n    state.registers[*return_reg].set_int((state.pc + 1) as i64);\n    state.pc = target_pc.as_offset_int();\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_return(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        Return {\n            return_reg,\n            can_fallthrough,\n        },\n        insn\n    );\n    if let Value::Numeric(Numeric::Integer(pc)) = state.registers[*return_reg].get_value() {\n        let pc: u32 = (*pc)\n            .try_into()\n            .unwrap_or_else(|_| panic!(\"Return register is negative: {pc}\"));\n        state.pc = pc;\n    } else {\n        if unlikely(!*can_fallthrough) {\n            return Err(LimboError::InternalError(\n                \"Return register is not an integer\".to_string(),\n            ));\n        }\n        state.pc += 1;\n    }\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_integer(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(Integer { value, dest }, insn);\n    state.registers[*dest].set_int(*value);\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub enum OpProgramState {\n    Start,\n    /// Step state tracks whether we're executing a trigger subprogram (vs FK action subprogram)\n    Step {\n        is_trigger: bool,\n        statement: Box<Statement>,\n        /// Saved last_insert_rowid to restore after trigger subprogram completes.\n        /// Per SQLite docs, trigger-body INSERTs must not overwrite the top-level rowid.\n        saved_last_insert_rowid: Option<i64>,\n    },\n}\n\n/// Execute a subprogram (Program opcode).\n/// Used for both triggers and FK actions (CASCADE, SET NULL, etc.)\npub fn op_program(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        Program {\n            params,\n            program: subprogram,\n            ignore_jump_target,\n        },\n        insn\n    );\n    loop {\n        match &mut state.op_program_state {\n            OpProgramState::Start => {\n                let mut statement = Statement::new(\n                    Program::from_prepared(subprogram.clone(), program.connection.clone()),\n                    pager.clone(),\n                    QueryMode::Normal,\n                    0,\n                );\n                statement.reset()?;\n\n                // Check if this is a trigger subprogram - if so, track execution\n                // and save last_insert_rowid so it can be restored after the trigger finishes.\n                let (is_trigger, saved_last_insert_rowid) =\n                    if let Some(ref trigger) = statement.get_trigger() {\n                        program.connection.start_trigger_execution(trigger.clone());\n                        (true, Some(program.connection.last_insert_rowid()))\n                    } else {\n                        (false, None)\n                    };\n\n                // Extract register values from params (which contain register indices encoded as negative integers)\n                // and bind them to the subprogram's parameters\n                for (param_idx, param_value) in params.iter().enumerate() {\n                    if let Value::Numeric(Numeric::Integer(reg_idx)) = param_value {\n                        let reg_idx = *reg_idx as usize;\n                        if reg_idx < state.registers.len() {\n                            let value = state.registers[reg_idx].get_value().clone();\n                            let param_index = NonZero::<usize>::new(param_idx + 1)\n                                .expect(\"param_idx + 1 should be non-zero\");\n                            statement.bind_at(param_index, value);\n                        } else {\n                            crate::bail_corrupt_error!(\n                                \"Register index {} out of bounds (len={})\",\n                                reg_idx,\n                                state.registers.len()\n                            );\n                        }\n                    } else {\n                        crate::bail_parse_error!(\n                            \"Subprogram parameters should be integers, got {:?}\",\n                            param_value\n                        );\n                    }\n                }\n\n                state.op_program_state = OpProgramState::Step {\n                    is_trigger,\n                    statement: Box::new(statement),\n                    saved_last_insert_rowid,\n                };\n            }\n            OpProgramState::Step {\n                is_trigger,\n                statement,\n                saved_last_insert_rowid,\n            } => {\n                let is_trigger = *is_trigger;\n                let saved_last_insert_rowid = *saved_last_insert_rowid;\n                let mut raise_ignore = false;\n                // Track whether the subprogram aborted with an error. When abort()\n                // runs inside the subprogram, it already calls end_trigger_execution(),\n                // so we must not call it again after the loop.\n                let mut subprogram_aborted = false;\n                loop {\n                    let res = statement.step();\n                    match res {\n                        Ok(step_result) => match step_result {\n                            StepResult::Done => break,\n                            StepResult::IO => {\n                                let io = statement.take_io_completions().unwrap_or_else(|| {\n                                    IOCompletions::Single(Completion::new_yield())\n                                });\n                                return Ok(InsnFunctionStepResult::IO(io));\n                            }\n                            StepResult::Row => continue,\n                            StepResult::Interrupt | StepResult::Busy => {\n                                return Err(LimboError::Busy);\n                            }\n                        },\n                        Err(LimboError::Constraint(constraint_err)) => {\n                            if program.resolve_type != ResolveType::Ignore {\n                                return Err(LimboError::Constraint(constraint_err));\n                            }\n                            subprogram_aborted = true;\n                            break;\n                        }\n                        Err(LimboError::RaiseIgnore) => {\n                            raise_ignore = true;\n                            subprogram_aborted = true;\n                            break;\n                        }\n                        Err(err) => {\n                            return Err(err);\n                        }\n                    }\n                }\n\n                // Only end trigger execution for normal completion. Error paths\n                // already called end_trigger_execution() via abort() in the subprogram.\n                if is_trigger && !subprogram_aborted {\n                    program.connection.end_trigger_execution();\n                }\n\n                // Restore last_insert_rowid after trigger execution, per SQLite semantics:\n                // trigger-body INSERTs must not overwrite the top-level rowid.\n                if let Some(rowid) = saved_last_insert_rowid {\n                    program.connection.update_last_rowid(rowid);\n                }\n\n                state.op_program_state = OpProgramState::Start;\n                if raise_ignore {\n                    // RAISE(IGNORE) — skip the current row by jumping to ignore_jump_target\n                    state.pc = ignore_jump_target.as_offset_int();\n                } else {\n                    state.pc += 1;\n                }\n                return Ok(InsnFunctionStepResult::Step);\n            }\n        }\n    }\n}\n\npub fn op_real(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(Real { value, dest }, insn);\n    state.registers[*dest]\n        .set_float(NonNan::new(*value).expect(\"f64 passed to op_real should be a valid NonNan\"));\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_real_affinity(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(RealAffinity { register }, insn);\n    if let Value::Numeric(Numeric::Integer(i)) = &state.registers[*register].get_value() {\n        state.registers[*register].set_float(\n            NonNan::new(*i as f64)\n                .expect(\"i64 passed to op_real_affinity should be a valid NonNan\"),\n        );\n    };\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_string8(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(String8 { value, dest }, insn);\n    state.registers[*dest].set_text(Text::new(value.clone()));\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_blob(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(Blob { value, dest }, insn);\n    state.registers[*dest].set_blob(value.clone());\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_row_data(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(RowData { cursor_id, dest }, insn);\n\n    let record = {\n        let cursor_ref = must_be_btree_cursor!(*cursor_id, program.cursor_ref, state, \"RowData\");\n        let cursor = cursor_ref.as_btree_mut();\n        let record_option = return_if_io!(cursor.record());\n\n        let record = record_option.ok_or_else(|| {\n            mark_unlikely();\n            LimboError::InternalError(\"RowData: cursor has no record\".to_string())\n        })?;\n\n        record.clone()\n    };\n\n    let reg = &mut state.registers[*dest];\n    *reg = Register::Record(record);\n\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\n#[derive(Debug, Clone, Copy)]\npub enum OpRowIdState {\n    Start,\n    Record {\n        index_cursor_id: usize,\n        table_cursor_id: usize,\n    },\n    Seek {\n        rowid: i64,\n        table_cursor_id: usize,\n    },\n    GetRowid,\n}\n\npub fn op_row_id(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(RowId { cursor_id, dest }, insn);\n    loop {\n        match state.op_row_id_state {\n            OpRowIdState::Start => {\n                if let Some(deferred) = state.deferred_seeks[*cursor_id].take() {\n                    state.op_row_id_state = OpRowIdState::Record {\n                        index_cursor_id: deferred.index_cursor_id,\n                        table_cursor_id: deferred.table_cursor_id,\n                    };\n                } else {\n                    state.op_row_id_state = OpRowIdState::GetRowid;\n                }\n            }\n            OpRowIdState::Record {\n                index_cursor_id,\n                table_cursor_id,\n            } => {\n                let rowid = {\n                    let index_cursor = state.get_cursor(index_cursor_id);\n                    match index_cursor {\n                        Cursor::BTree(index_cursor) => {\n                            let record = return_if_io!(index_cursor.record());\n                            let record =\n                                record.as_ref().expect(\"index cursor should have a record\");\n                            let rowid = record\n                                .last_value()\n                                .expect(\"record should have a last value\");\n                            match rowid {\n                                Ok(ValueRef::Numeric(Numeric::Integer(rowid))) => rowid,\n                                _ => unreachable!(),\n                            }\n                        }\n                        Cursor::IndexMethod(index_cursor) => {\n                            return_if_io!(index_cursor.query_rowid())\n                                .expect(\"index cursor should have a rowid\")\n                        }\n                        _ => panic!(\"unexpected cursor type\"),\n                    }\n                };\n                state.op_row_id_state = OpRowIdState::Seek {\n                    rowid,\n                    table_cursor_id,\n                }\n            }\n            OpRowIdState::Seek {\n                rowid,\n                table_cursor_id,\n            } => {\n                {\n                    let table_cursor = state.get_cursor(table_cursor_id);\n                    let table_cursor = table_cursor.as_btree_mut();\n                    return_if_io!(\n                        table_cursor.seek(SeekKey::TableRowId(rowid), SeekOp::GE { eq_only: true })\n                    );\n                }\n                state.op_row_id_state = OpRowIdState::GetRowid;\n            }\n            OpRowIdState::GetRowid => {\n                let cursors = &mut state.cursors;\n                if let Some(Cursor::BTree(btree_cursor)) = cursors\n                    .get_mut(*cursor_id)\n                    .expect(\"cursor_id should be valid\")\n                {\n                    if btree_cursor.get_null_flag() {\n                        state.registers[*dest].set_null();\n                        break;\n                    }\n                    if let Some(ref rowid) = return_if_io!(btree_cursor.rowid()) {\n                        state.registers[*dest].set_int(*rowid);\n                    } else {\n                        state.registers[*dest].set_null();\n                    }\n                } else if let Some(Cursor::Virtual(virtual_cursor)) = cursors\n                    .get_mut(*cursor_id)\n                    .expect(\"cursor_id should be valid\")\n                {\n                    let rowid = virtual_cursor.rowid();\n                    if rowid != 0 {\n                        state.registers[*dest].set_int(rowid);\n                    } else {\n                        state.registers[*dest].set_null();\n                    }\n                } else if let Some(Cursor::MaterializedView(mv_cursor)) = cursors\n                    .get_mut(*cursor_id)\n                    .expect(\"cursor_id should be valid\")\n                {\n                    if let Some(rowid) = return_if_io!(mv_cursor.rowid()) {\n                        state.registers[*dest].set_int(rowid);\n                    } else {\n                        state.registers[*dest].set_null();\n                    }\n                } else if let Some(Cursor::IndexMethod(cursor)) = cursors\n                    .get_mut(*cursor_id)\n                    .expect(\"cursor_id should be valid\")\n                {\n                    if let Some(rowid) = return_if_io!(cursor.query_rowid()) {\n                        state.registers[*dest].set_int(rowid);\n                    } else {\n                        state.registers[*dest].set_null();\n                    }\n                } else {\n                    mark_unlikely();\n                    return Err(LimboError::InternalError(\n                        \"RowId: cursor is not a table, virtual, or materialized view cursor\"\n                            .to_string(),\n                    ));\n                }\n                break;\n            }\n        }\n    }\n\n    state.op_row_id_state = OpRowIdState::Start;\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_idx_row_id(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(IdxRowId { cursor_id, dest }, insn);\n    let cursors = &mut state.cursors;\n    let cursor = cursors\n        .get_mut(*cursor_id)\n        .expect(\"cursor_id should be valid\")\n        .as_mut()\n        .expect(\"cursor should exist\");\n\n    let rowid = match cursor {\n        Cursor::BTree(cursor) => return_if_io!(cursor.rowid()),\n        Cursor::IndexMethod(cursor) => return_if_io!(cursor.query_rowid()),\n        _ => panic!(\"unexpected cursor type\"),\n    };\n    match rowid {\n        Some(rowid) => state.registers[*dest].set_int(rowid),\n        None => state.registers[*dest].set_null(),\n    };\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_seek_rowid(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        SeekRowid {\n            cursor_id,\n            src_reg,\n            target_pc,\n        },\n        insn\n    );\n    if !target_pc.is_offset() {\n        crate::bail_corrupt_error!(\"Unresolved label: {target_pc:?}\");\n    }\n    let (pc, did_seek) = {\n        let cursor = get_cursor!(state, *cursor_id);\n\n        // Handle MaterializedView cursor\n        let (pc, did_seek) = match cursor {\n            Cursor::MaterializedView(mv_cursor) => {\n                let rowid = match state.registers[*src_reg].get_value() {\n                    Value::Numeric(Numeric::Integer(rowid)) => Some(*rowid),\n                    Value::Null => None,\n                    _ => None,\n                };\n\n                match rowid {\n                    Some(rowid) => {\n                        let seek_result = return_if_io!(mv_cursor\n                            .seek(SeekKey::TableRowId(rowid), SeekOp::GE { eq_only: true }));\n                        let pc = if !matches!(seek_result, SeekResult::Found) {\n                            target_pc.as_offset_int()\n                        } else {\n                            state.pc + 1\n                        };\n                        (pc, true)\n                    }\n                    None => (target_pc.as_offset_int(), false),\n                }\n            }\n            Cursor::BTree(btree_cursor) => {\n                let rowid = match state.registers[*src_reg].get_value() {\n                    Value::Numeric(Numeric::Integer(rowid)) => Some(*rowid),\n                    Value::Null => None,\n                    // For non-integer values try to apply affinity and convert them to integer.\n                    other => {\n                        let mut temp_reg = Register::Value(other.clone());\n                        let converted = apply_affinity_char(&mut temp_reg, Affinity::Numeric);\n                        if converted {\n                            match temp_reg.get_value() {\n                                Value::Numeric(Numeric::Integer(i)) => Some(*i),\n                                Value::Numeric(Numeric::Float(f)) => Some(f64::from(*f) as i64),\n                                _ => None,\n                            }\n                        } else {\n                            None\n                        }\n                    }\n                };\n\n                match rowid {\n                    Some(rowid) => {\n                        let seek_result = return_if_io!(btree_cursor\n                            .seek(SeekKey::TableRowId(rowid), SeekOp::GE { eq_only: true }));\n                        let pc = if !matches!(seek_result, SeekResult::Found) {\n                            target_pc.as_offset_int()\n                        } else {\n                            state.pc + 1\n                        };\n                        (pc, true)\n                    }\n                    None => (target_pc.as_offset_int(), false),\n                }\n            }\n            _ => panic!(\"SeekRowid on non-btree/materialized-view cursor\"),\n        };\n        (pc, did_seek)\n    };\n    // Increment btree_seeks metric for SeekRowid operation after cursor is dropped\n    if did_seek {\n        state.metrics.btree_seeks = state.metrics.btree_seeks.saturating_add(1);\n    }\n    state.pc = pc;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_deferred_seek(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        DeferredSeek {\n            index_cursor_id,\n            table_cursor_id,\n        },\n        insn\n    );\n    state.deferred_seeks[*table_cursor_id] = Some(DeferredSeekState {\n        index_cursor_id: *index_cursor_id,\n        table_cursor_id: *table_cursor_id,\n    });\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\n/// Separate enum for seek key to avoid lifetime issues\n/// with using [SeekKey] - OpSeekState always owns the key,\n/// unless it's [OpSeekKey::IndexKeyFromRegister] in which case the record\n/// is owned by the program state's registers and we store the register number.\n#[derive(Debug)]\npub enum OpSeekKey {\n    TableRowId(i64),\n    IndexKeyFromRegister(usize),\n    IndexKeyUnpacked { start_reg: usize, num_regs: usize },\n}\n\n#[derive(Debug)]\npub enum OpSeekState {\n    /// Initial state\n    Start,\n    /// Position cursor with seek operation with (rowid, op) search parameters\n    Seek { key: OpSeekKey, op: SeekOp },\n    /// Advance cursor (with [BTreeCursor::next]/[BTreeCursor::prev] methods) which was\n    /// positioned after [OpSeekState::Seek] state if [BTreeCursor::seek] returned [SeekResult::TryAdvance]\n    Advance { op: SeekOp },\n    /// Move cursor to the last BTree row if DB knows that comparison result will be fixed (due to type ordering, e.g. NUMBER always <= TEXT)\n    MoveLast,\n}\n\npub fn op_seek(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    let (cursor_id, is_index, record_source, target_pc) = match insn {\n        Insn::SeekGE {\n            cursor_id,\n            is_index,\n            start_reg,\n            num_regs,\n            target_pc,\n            ..\n        }\n        | Insn::SeekLE {\n            cursor_id,\n            is_index,\n            start_reg,\n            num_regs,\n            target_pc,\n            ..\n        }\n        | Insn::SeekGT {\n            cursor_id,\n            is_index,\n            start_reg,\n            num_regs,\n            target_pc,\n            ..\n        }\n        | Insn::SeekLT {\n            cursor_id,\n            is_index,\n            start_reg,\n            num_regs,\n            target_pc,\n            ..\n        } => (\n            cursor_id,\n            *is_index,\n            RecordSource::Unpacked {\n                start_reg: *start_reg,\n                num_regs: *num_regs,\n            },\n            target_pc,\n        ),\n        _ => unreachable!(\"unexpected Insn {:?}\", insn),\n    };\n    assert!(\n        target_pc.is_offset(),\n        \"op_seek: target_pc should be an offset, is: {target_pc:?}\"\n    );\n    let op = match insn {\n        Insn::SeekGE { eq_only, .. } => SeekOp::GE { eq_only: *eq_only },\n        Insn::SeekGT { .. } => SeekOp::GT,\n        Insn::SeekLE { eq_only, .. } => SeekOp::LE { eq_only: *eq_only },\n        Insn::SeekLT { .. } => SeekOp::LT,\n        _ => unreachable!(\"unexpected Insn {:?}\", insn),\n    };\n    match seek_internal(\n        program,\n        state,\n        pager,\n        record_source,\n        *cursor_id,\n        is_index,\n        op,\n    ) {\n        Ok(SeekInternalResult::Found) => {\n            state.pc += 1;\n            Ok(InsnFunctionStepResult::Step)\n        }\n        Ok(SeekInternalResult::NotFound) => {\n            state.pc = target_pc.as_offset_int();\n            Ok(InsnFunctionStepResult::Step)\n        }\n        Ok(SeekInternalResult::IO(io)) => Ok(InsnFunctionStepResult::IO(io)),\n        Err(e) => Err(e),\n    }\n}\n\n#[derive(Debug)]\npub enum SeekInternalResult {\n    Found,\n    NotFound,\n    IO(IOCompletions),\n}\n#[derive(Clone, Copy)]\npub enum RecordSource {\n    Unpacked { start_reg: usize, num_regs: usize },\n    Packed { record_reg: usize },\n}\n\n/// Internal function used by many VDBE instructions that need to perform a seek operation.\n///\n/// Explanation for some of the arguments:\n/// - `record_source`: whether the seek key record is already a record (packed) or it will be constructed from registers (unpacked)\n/// - `cursor_id`: the cursor id\n/// - `is_index`: true if the cursor is an index, false if it is a table\n/// - `op`: the [SeekOp] to perform\n#[allow(clippy::too_many_arguments)]\npub fn seek_internal(\n    program: &Program,\n    state: &mut ProgramState,\n    pager: &Arc<Pager>,\n    record_source: RecordSource,\n    cursor_id: usize,\n    is_index: bool,\n    op: SeekOp,\n) -> Result<SeekInternalResult> {\n    let mv_store = program.connection.mv_store();\n    /// wrapper so we can use the ? operator and handle errors correctly in this outer function\n    fn inner(\n        _program: &Program,\n        state: &mut ProgramState,\n        _pager: &Arc<Pager>,\n        _mv_store: Option<&Arc<MvStore>>,\n        record_source: RecordSource,\n        cursor_id: usize,\n        is_index: bool,\n        op: SeekOp,\n    ) -> Result<SeekInternalResult> {\n        loop {\n            match &state.seek_state {\n                OpSeekState::Start => {\n                    if is_index {\n                        // FIXME: text-to-numeric conversion should also happen here when applicable (when index column is numeric)\n                        // See below for the table-btree implementation of this\n                        match record_source {\n                            RecordSource::Unpacked {\n                                start_reg,\n                                num_regs,\n                            } => {\n                                state.seek_state = OpSeekState::Seek {\n                                    key: OpSeekKey::IndexKeyUnpacked {\n                                        start_reg,\n                                        num_regs,\n                                    },\n                                    op,\n                                };\n                            }\n                            RecordSource::Packed { record_reg } => {\n                                state.seek_state = OpSeekState::Seek {\n                                    key: OpSeekKey::IndexKeyFromRegister(record_reg),\n                                    op,\n                                };\n                            }\n                        };\n                        continue;\n                    }\n                    let RecordSource::Unpacked {\n                        start_reg,\n                        num_regs,\n                    } = record_source\n                    else {\n                        unreachable!(\"op_seek: record_source should be Unpacked for table-btree\");\n                    };\n                    assert_eq!(num_regs, 1, \"op_seek: num_regs should be 1 for table-btree\");\n                    let original_value = state.registers[start_reg].get_value();\n                    let mut temp_value = original_value.clone();\n\n                    let conversion_successful = if matches!(temp_value, Value::Text(_)) {\n                        let new_val = apply_numeric_affinity(temp_value.as_value_ref(), false);\n                        let converted = new_val.is_some();\n                        if let Some(new_val) = new_val {\n                            temp_value = new_val.to_owned();\n                        }\n                        converted\n                    } else {\n                        true // Non-text values don't need conversion\n                    };\n                    let int_key = extract_int_value(&temp_value);\n                    let lost_precision = !conversion_successful\n                        || !matches!(temp_value, Value::Numeric(Numeric::Integer(_)));\n                    let actual_op = if lost_precision {\n                        match &temp_value {\n                            Value::Numeric(Numeric::Float(f)) => {\n                                let f_val = f64::from(*f);\n                                // When extract_int_value clamped to i64::MAX/MIN,\n                                // the cast `int_key as f64` loses the fact that the\n                                // float is outside the i64 range. Detect this and\n                                // set the comparison result directly.\n                                //\n                                // For i64::MAX: any float > 9223372036854774784.0\n                                // is >= 9223372036854775808.0 (the next f64), which\n                                // exceeds i64::MAX (9223372036854775807). i64::MAX\n                                // is not exactly representable as f64, so the float\n                                // is always strictly greater.\n                                //\n                                // For i64::MIN: -2^63 IS exactly representable as\n                                // f64, so we must distinguish the exact match from\n                                // floats that are strictly less.\n                                let c = if int_key == i64::MAX && f_val > 9223372036854774784.0 {\n                                    // Float exceeds i64::MAX, so int_key < float\n                                    -1\n                                } else if int_key == i64::MIN && f_val < -9223372036854774784.0 {\n                                    if f_val == (i64::MIN as f64) {\n                                        // Float is exactly i64::MIN\n                                        0\n                                    } else {\n                                        // Float is below i64::MIN, so int_key > float\n                                        1\n                                    }\n                                } else {\n                                    let int_key_as_float = int_key as f64;\n                                    if int_key_as_float > f_val {\n                                        1\n                                    } else if int_key_as_float < f_val {\n                                        -1\n                                    } else {\n                                        0\n                                    }\n                                };\n\n                                match c.cmp(&0) {\n                                    std::cmp::Ordering::Less => match op {\n                                        SeekOp::LT => SeekOp::LE { eq_only: false }, // (x < 5.1) -> (x <= 5)\n                                        SeekOp::GE { .. } => SeekOp::GT, // (x >= 5.1) -> (x > 5)\n                                        other => other,\n                                    },\n                                    std::cmp::Ordering::Greater => match op {\n                                        SeekOp::GT => SeekOp::GE { eq_only: false }, // (x > 4.9) -> (x >= 5)\n                                        SeekOp::LE { .. } => SeekOp::LT, // (x <= 4.9) -> (x < 5)\n                                        other => other,\n                                    },\n                                    std::cmp::Ordering::Equal => op,\n                                }\n                            }\n                            Value::Text(_) | Value::Blob(_) => {\n                                match op {\n                                    SeekOp::GT | SeekOp::GE { .. } => {\n                                        // No integers are > or >= non-numeric text\n                                        return Ok(SeekInternalResult::NotFound);\n                                    }\n                                    SeekOp::LT | SeekOp::LE { .. } => {\n                                        // All integers are < or <= non-numeric text\n                                        // Move to last position and then use the normal seek logic\n                                        state.seek_state = OpSeekState::MoveLast;\n                                        continue;\n                                    }\n                                }\n                            }\n                            _ => op,\n                        }\n                    } else {\n                        op\n                    };\n                    let rowid = if matches!(original_value, Value::Null) {\n                        match actual_op {\n                            SeekOp::GE { .. } | SeekOp::GT => {\n                                return Ok(SeekInternalResult::NotFound);\n                            }\n                            SeekOp::LE { .. } | SeekOp::LT => {\n                                // No integers are < NULL\n                                return Ok(SeekInternalResult::NotFound);\n                            }\n                        }\n                    } else {\n                        int_key\n                    };\n                    state.seek_state = OpSeekState::Seek {\n                        key: OpSeekKey::TableRowId(rowid),\n                        op: actual_op,\n                    };\n                    continue;\n                }\n                OpSeekState::Seek { key, op } => {\n                    let seek_result = match key {\n                        OpSeekKey::TableRowId(rowid) => {\n                            let cursor = get_cursor!(state, cursor_id).as_btree_mut();\n                            match cursor.seek(SeekKey::TableRowId(*rowid), *op)? {\n                                IOResult::Done(seek_result) => seek_result,\n                                IOResult::IO(io) => return Ok(SeekInternalResult::IO(io)),\n                            }\n                        }\n                        OpSeekKey::IndexKeyFromRegister(record_reg) => {\n                            let (cursor, record) = {\n                                let (cursors, registers) = (&mut state.cursors, &state.registers);\n                                let cursor = cursors\n                                    .get_mut(cursor_id)\n                                    .and_then(|c| c.as_mut())\n                                    .expect(\"op_seek: cursor should be allocated\")\n                                    .as_btree_mut();\n                                let record = match &registers[*record_reg] {\n                                    Register::Record(ref record) => record,\n                                    _ => unreachable!(\n                                        \"op_seek: record_reg should be a Record register when OpSeekKey::IndexKeyFromRegister is used\"\n                                    ),\n                                };\n                                (cursor, record)\n                            };\n                            match cursor.seek(SeekKey::IndexKey(record), *op)? {\n                                IOResult::Done(seek_result) => seek_result,\n                                IOResult::IO(io) => return Ok(SeekInternalResult::IO(io)),\n                            }\n                        }\n                        OpSeekKey::IndexKeyUnpacked {\n                            start_reg,\n                            num_regs,\n                        } => {\n                            let start_reg = *start_reg;\n                            let num_regs = *num_regs;\n                            let cursor = get_cursor!(state, cursor_id).as_btree_mut();\n                            let registers = &state.registers[start_reg..start_reg + num_regs];\n                            match cursor.seek_unpacked(registers, *op)? {\n                                IOResult::Done(seek_result) => seek_result,\n                                IOResult::IO(io) => return Ok(SeekInternalResult::IO(io)),\n                            }\n                        }\n                    };\n                    // Increment btree_seeks metric after seek operation and cursor is dropped\n                    state.metrics.btree_seeks = state.metrics.btree_seeks.saturating_add(1);\n                    let found = match seek_result {\n                        SeekResult::Found => true,\n                        SeekResult::NotFound => false,\n                        SeekResult::TryAdvance => {\n                            state.seek_state = OpSeekState::Advance { op: *op };\n                            continue;\n                        }\n                    };\n                    return Ok(if found {\n                        SeekInternalResult::Found\n                    } else {\n                        SeekInternalResult::NotFound\n                    });\n                }\n                OpSeekState::Advance { op } => {\n                    let found = {\n                        let cursor = get_cursor!(state, cursor_id);\n                        let cursor = cursor.as_btree_mut();\n                        // Seek operation has anchor number which equals to the closed boundary of the range\n                        // (e.g. for >= x - anchor is x, for > x - anchor is x + 1)\n                        //\n                        // Before Advance state, cursor was positioned to the leaf page which should hold the anchor.\n                        // Sometimes this leaf page can have no matching rows, and in this case\n                        // we need to move cursor in the direction of Seek to find record which matches the seek filter\n                        //\n                        // Consider following scenario: Seek [> 666]\n                        // interior page dividers:       I1: [ .. 667 .. ]\n                        //                                       /   \\\n                        //             leaf pages:    P1[661,665]   P2[anything here is GT 666]\n                        // After the initial Seek, cursor will be positioned after the end of leaf page P1 [661, 665]\n                        // because this is potential position for insertion of value 666.\n                        // But as P1 has no row matching Seek criteria - we need to move it to the right\n                        // (and as we at the page boundary, we will move cursor to the next neighbor leaf, which guaranteed to have\n                        // row keys greater than divider, which is greater or equal than anchor)\n\n                        // this same logic applies for indexes, but the next/prev record is expected to be found in the parent page's\n                        // divider cell.\n                        turso_assert!(\n                            !cursor.get_skip_advance(),\n                            \"skip_advance should not be true in the middle of a seek operation\"\n                        );\n                        let result = match op {\n                            // deliberately call get_next_record() instead of next() to avoid skip_advance triggering unwantedly\n                            SeekOp::GT | SeekOp::GE { .. } => cursor.next()?,\n                            SeekOp::LT | SeekOp::LE { .. } => cursor.prev()?,\n                        };\n                        match result {\n                            IOResult::Done(()) => cursor.has_record(),\n                            IOResult::IO(io) => return Ok(SeekInternalResult::IO(io)),\n                        }\n                    };\n                    return Ok(if found {\n                        SeekInternalResult::Found\n                    } else {\n                        SeekInternalResult::NotFound\n                    });\n                }\n                OpSeekState::MoveLast => {\n                    let cursor = state.get_cursor(cursor_id);\n                    let cursor = cursor.as_btree_mut();\n                    match cursor.last()? {\n                        IOResult::Done(()) => {}\n                        IOResult::IO(io) => return Ok(SeekInternalResult::IO(io)),\n                    }\n                    // the MoveLast variant is only used for SeekOp::LT and SeekOp::LE when the seek condition is always true,\n                    // so we have always found what we were looking for.\n                    return Ok(SeekInternalResult::Found);\n                }\n            }\n        }\n    }\n\n    let result = inner(\n        program,\n        state,\n        pager,\n        mv_store.as_ref(),\n        record_source,\n        cursor_id,\n        is_index,\n        op,\n    );\n    if !matches!(result, Ok(SeekInternalResult::IO(..))) {\n        state.seek_state = OpSeekState::Start;\n    }\n    result\n}\n\n/// Returns the tie-breaker ordering for SQLite index comparison opcodes.\n///\n/// When comparing index keys that omit the PRIMARY KEY/ROWID, SQLite uses a\n/// tie-breaker value (`default_rc` in the C code) to determine the result when\n/// the non-primary-key portions of the keys are equal.\n///\n/// This function extracts the appropriate tie-breaker based on the comparison opcode:\n///\n/// ## Tie-breaker Logic\n///\n/// - **`IdxLE` and `IdxGT`**: Return `Ordering::Less` (equivalent to `default_rc = -1`)\n///   - When keys are equal, these operations should favor the \"less than\" result\n///   - `IdxLE`: \"less than or equal\" - equality should be treated as \"less\"\n///   - `IdxGT`: \"greater than\" - equality should be treated as \"less\" (so condition fails)\n///\n/// - **`IdxGE` and `IdxLT`**: Return `Ordering::Equal` (equivalent to `default_rc = 0`)\n///   - When keys are equal, these operations should treat it as true equality\n///   - `IdxGE`: \"greater than or equal\" - equality should be treated as \"equal\"\n///   - `IdxLT`: \"less than\" - equality should be treated as \"equal\" (so condition fails)\n///\n/// ## SQLite Implementation Details\n///\n/// In SQLite's C implementation, this corresponds to:\n/// ```c\n/// if( pOp->opcode<OP_IdxLT ){\n///     assert( pOp->opcode==OP_IdxLE || pOp->opcode==OP_IdxGT );\n///     r.default_rc = -1;  // Ordering::Less\n/// }else{\n///     assert( pOp->opcode==OP_IdxGE || pOp->opcode==OP_IdxLT );\n///     r.default_rc = 0;   // Ordering::Equal\n/// }\n/// ```\n#[inline(always)]\nfn get_tie_breaker_from_idx_comp_op(insn: &Insn) -> std::cmp::Ordering {\n    match insn {\n        Insn::IdxLE { .. } | Insn::IdxGT { .. } => std::cmp::Ordering::Less,\n        Insn::IdxGE { .. } | Insn::IdxLT { .. } => std::cmp::Ordering::Equal,\n        _ => panic!(\"Invalid instruction for index comparison\"),\n    }\n}\n\n#[allow(clippy::let_and_return)]\npub fn op_idx_ge(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        IdxGE {\n            cursor_id,\n            start_reg,\n            num_regs,\n            target_pc,\n        },\n        insn\n    );\n    if !target_pc.is_offset() {\n        crate::bail_corrupt_error!(\"Unresolved label: {target_pc:?}\");\n    }\n\n    let pc = {\n        let cursor = get_cursor!(state, *cursor_id);\n        let cursor = cursor.as_btree_mut();\n        let index_info = cursor.get_index_info().clone();\n\n        let pc = if let Some(idx_record) = return_if_io!(cursor.record()) {\n            // Create the comparison record from registers\n            let values =\n                registers_to_ref_values(&state.registers[*start_reg..*start_reg + *num_regs]);\n            let tie_breaker = get_tie_breaker_from_idx_comp_op(insn);\n            let ord = compare_records_generic(\n                idx_record,  // The serialized record from the index\n                values,      // The record built from registers\n                &index_info, // Sort order flags\n                0,\n                tie_breaker,\n            )?;\n\n            if ord.is_ge() {\n                target_pc.as_offset_int()\n            } else {\n                state.pc + 1\n            }\n        } else {\n            // No record at cursor position, jump to target\n            target_pc.as_offset_int()\n        };\n        pc\n    };\n\n    state.pc = pc;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_seek_end(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(SeekEnd { cursor_id }, *insn);\n    {\n        let cursor = state.get_cursor(cursor_id);\n        let cursor = cursor.as_btree_mut();\n        return_if_io!(cursor.seek_end());\n    }\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\n#[allow(clippy::let_and_return)]\npub fn op_idx_le(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        IdxLE {\n            cursor_id,\n            start_reg,\n            num_regs,\n            target_pc,\n        },\n        insn\n    );\n    if !target_pc.is_offset() {\n        crate::bail_corrupt_error!(\"Unresolved label: {target_pc:?}\");\n    }\n\n    let pc = {\n        let cursor = get_cursor!(state, *cursor_id);\n        let cursor = cursor.as_btree_mut();\n        let index_info = cursor.get_index_info().clone();\n\n        let pc = if let Some(idx_record) = return_if_io!(cursor.record()) {\n            let values =\n                registers_to_ref_values(&state.registers[*start_reg..*start_reg + *num_regs]);\n            let tie_breaker = get_tie_breaker_from_idx_comp_op(insn);\n            let ord = compare_records_generic(idx_record, values, &index_info, 0, tie_breaker)?;\n\n            if ord.is_le() {\n                target_pc.as_offset_int()\n            } else {\n                state.pc + 1\n            }\n        } else {\n            // No record at cursor position, jump to target\n            target_pc.as_offset_int()\n        };\n        pc\n    };\n\n    state.pc = pc;\n    Ok(InsnFunctionStepResult::Step)\n}\n\n#[allow(clippy::let_and_return)]\npub fn op_idx_gt(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        IdxGT {\n            cursor_id,\n            start_reg,\n            num_regs,\n            target_pc,\n        },\n        insn\n    );\n    if !target_pc.is_offset() {\n        crate::bail_corrupt_error!(\"Unresolved label: {target_pc:?}\");\n    }\n\n    let pc = {\n        let cursor = get_cursor!(state, *cursor_id);\n        let cursor = cursor.as_btree_mut();\n        let index_info = cursor.get_index_info().clone();\n\n        let pc = if let Some(idx_record) = return_if_io!(cursor.record()) {\n            let values =\n                registers_to_ref_values(&state.registers[*start_reg..*start_reg + *num_regs]);\n            let tie_breaker = get_tie_breaker_from_idx_comp_op(insn);\n            let ord = compare_records_generic(idx_record, values, &index_info, 0, tie_breaker)?;\n\n            if ord.is_gt() {\n                target_pc.as_offset_int()\n            } else {\n                state.pc + 1\n            }\n        } else {\n            // No record at cursor position, jump to target\n            target_pc.as_offset_int()\n        };\n        pc\n    };\n\n    state.pc = pc;\n    Ok(InsnFunctionStepResult::Step)\n}\n\n#[allow(clippy::let_and_return)]\npub fn op_idx_lt(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        IdxLT {\n            cursor_id,\n            start_reg,\n            num_regs,\n            target_pc,\n        },\n        insn\n    );\n    if !target_pc.is_offset() {\n        crate::bail_corrupt_error!(\"Unresolved label: {target_pc:?}\");\n    }\n\n    let pc = {\n        let cursor = get_cursor!(state, *cursor_id);\n        let cursor = cursor.as_btree_mut();\n        let index_info = cursor.get_index_info().clone();\n\n        let pc = if let Some(idx_record) = return_if_io!(cursor.record()) {\n            let values =\n                registers_to_ref_values(&state.registers[*start_reg..*start_reg + *num_regs]);\n\n            let tie_breaker = get_tie_breaker_from_idx_comp_op(insn);\n            let ord = compare_records_generic(idx_record, values, &index_info, 0, tie_breaker)?;\n\n            if ord.is_lt() {\n                target_pc.as_offset_int()\n            } else {\n                state.pc + 1\n            }\n        } else {\n            // No record at cursor position, jump to target\n            target_pc.as_offset_int()\n        };\n        pc\n    };\n\n    state.pc = pc;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_decr_jump_zero(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(DecrJumpZero { reg, target_pc }, insn);\n    if !target_pc.is_offset() {\n        crate::bail_corrupt_error!(\"Unresolved label: {target_pc:?}\");\n    }\n    match &mut state.registers[*reg] {\n        Register::Value(Value::Numeric(Numeric::Integer(n))) => {\n            *n = n.saturating_sub(1);\n            if *n == 0 {\n                state.pc = target_pc.as_offset_int();\n            } else {\n                state.pc += 1;\n            }\n        }\n        Register::Value(_) | Register::Record(_) => {\n            bail_constraint_error!(\"datatype mismatch\");\n        }\n        Register::Aggregate(_) => {\n            mark_unlikely();\n            return Err(LimboError::InternalError(\n                \"DecrJumpZero: unexpected aggregate register\".into(),\n            ));\n        }\n    }\n    Ok(InsnFunctionStepResult::Step)\n}\n\nfn apply_kbn_step(acc: &mut Value, r: f64, state: &mut SumAggState) {\n    // NaN from Inf + (-Inf) is sticky: once acc is Null, it stays Null.\n    // See https://sqlite.org/lang_aggfunc.html (\"result is NULL\").\n    if matches!(acc, Value::Null) {\n        return;\n    }\n    let s = acc.to_float_or_zero();\n    let t = s + r;\n    if t.is_nan() {\n        *acc = Value::Null;\n        return;\n    }\n    // When t is infinite, the KBN correction computes inf - inf = NaN,\n    // which is meaningless. Skip compensation in that case.\n    if t.is_finite() {\n        let correction = if s.abs() > r.abs() {\n            (s - t) + r\n        } else {\n            (r - t) + s\n        };\n        state.r_err += correction;\n    }\n    *acc = Value::from_f64(t);\n}\n\n// Add a (possibly large) integer to the running sum.\nfn apply_kbn_step_int(acc: &mut Value, i: i64, state: &mut SumAggState) {\n    const THRESHOLD: i64 = 4503599627370496; // 2^52\n\n    if i <= -THRESHOLD || i >= THRESHOLD {\n        let i_sm = i % 16384;\n        let i_big = i - i_sm;\n\n        apply_kbn_step(acc, i_big as f64, state);\n        apply_kbn_step(acc, i_sm as f64, state);\n    } else {\n        apply_kbn_step(acc, i as f64, state);\n    }\n}\n\n/// Initialize aggregate payload with default values.\n/// Payload layout by aggregate type:\n/// - Count/Count0: [Integer(0)]\n/// - Sum: [Null, Float(0.0), Integer(0), Integer(0)]  // acc, r_err, approx, ovrfl\n/// - Total: [Float(0.0), Float(0.0), Integer(0), Integer(0)]  // same but starts at 0.0\n/// - Avg: [Float(0.0), Float(0.0), Integer(0)]  // sum, r_err, count - uses KBN like SUM\n/// - Min/Max: [Null]\n/// - GroupConcat/StringAgg: [Null] (becomes Text on first non-null value)\n/// - JsonGroupObject/JsonbGroupObject: [Blob([])]\n/// - JsonGroupArray/JsonbGroupArray: [Blob([])]\nfn init_agg_payload(func: &AggFunc, payload: &mut Vec<Value>) -> Result<()> {\n    match func {\n        AggFunc::Count | AggFunc::Count0 => payload.push(Value::from_i64(0)),\n        AggFunc::Sum | AggFunc::Total => {\n            let acc = if matches!(func, AggFunc::Total) {\n                Value::from_f64(0.0)\n            } else {\n                Value::Null\n            };\n            payload.push(acc);\n            payload.push(Value::from_f64(0.0));\n            payload.push(Value::from_i64(0));\n            payload.push(Value::from_i64(0));\n        }\n        AggFunc::Avg => {\n            payload.push(Value::from_f64(0.0));\n            payload.push(Value::from_f64(0.0));\n            payload.push(Value::from_i64(0));\n        }\n        AggFunc::Min | AggFunc::Max => payload.push(Value::Null),\n        AggFunc::GroupConcat | AggFunc::StringAgg => {\n            // Use Null as sentinel to distinguish \"no values yet\" from \"accumulated empty string\"\n            payload.push(Value::Null);\n        }\n        AggFunc::External(_) => {\n            mark_unlikely();\n            // External aggregates use ExternalAggState, not flat payload\n            return Err(LimboError::InternalError(\n                \"External aggregate not supported in init_agg_payload\".to_string(),\n            ));\n        }\n        AggFunc::ArrayAgg => {\n            // payload[0] = element count (Integer), remaining slots = accumulated values.\n            // We serialize to a record blob only in finalize, avoiding O(n²) re-serialization.\n            payload.push(Value::from_i64(0));\n        }\n        #[cfg(feature = \"json\")]\n        AggFunc::JsonGroupObject | AggFunc::JsonbGroupObject => {\n            payload.push(Value::Blob(vec![]));\n        }\n        #[cfg(feature = \"json\")]\n        AggFunc::JsonGroupArray | AggFunc::JsonbGroupArray => {\n            payload.push(Value::Blob(vec![]));\n        }\n    };\n    Ok(())\n}\n\n/// Process a single input row and update the aggregate state in the payload.\n///\n/// This is the core aggregation logic shared between both aggregation strategies:\n/// - **Register-based (sort-stream)**: Called from `op_agg_step` (AggStep instruction).\n///   The payload lives in `AggContext::Builtin` stored in a register.\n/// - **Hash-based (future enhancement)**: Called from `step_aggregate` during HashAggStep. The payload lives\n///   in hash table entries keyed by GROUP BY values.\n///\n/// The payload slice contains the intermediate aggregate state (initialized by\n/// `init_agg_payload`), and this function incorporates the new row's values.\n///\n/// # Payload layouts (see `init_agg_payload` for initial values):\n/// - **Count**: `[count: Integer]` - increments if arg is not NULL\n/// - **Count0**: `[count: Integer]` - always increments (COUNT(*))\n/// - **Avg**: `[sum: Float, r_err: Float, count: Integer]` - uses KBN compensation like SUM\n/// - **Sum/Total**: `[acc, r_err: Float, approx: Integer, ovrfl: Integer]`\n///   - `acc`: running sum (Null/Integer/Float depending on inputs)\n///   - `r_err`: Kahan-Babuška-Neumaier compensation term for floating-point precision\n///   - `approx`: 1 if result is approximate (float arithmetic used)\n///   - `ovrfl`: 1 if integer overflow occurred (Total promotes to float, Sum errors)\n/// - **Min/Max**: `[current_extreme: Value]` - tracks min/max seen so far\n/// - **GroupConcat/StringAgg**: `[accumulated: Null|Text]` - Null until first value, then Text\n/// - **JsonGroup***: `[raw_jsonb: Blob]` - accumulated raw JSONB bytes\nfn update_agg_payload(\n    func: &AggFunc,\n    arg: Value,                // most agg functions take one argument\n    maybe_arg2: Option<Value>, // for GroupConcat/StringAgg, JsonGroupObject/JsonbGroupObject,\n    payload: &mut [Value],\n    collation: CollationSeq,\n    comparator: &Option<crate::vdbe::sorter::SortComparator>,\n) -> Result<()> {\n    match func {\n        AggFunc::Count => {\n            // COUNT(column) increments only when arg is not NULL. Empty args treated as non-NULL\n            // (would indicate a bug in query translation, but matches SQLite behavior of counting).\n            if !matches!(arg, Value::Null) {\n                // invariant as per init_agg_payload: payload[0] is always an integer\n                let Value::Numeric(Numeric::Integer(i)) = &mut payload[0] else {\n                    mark_unlikely();\n                    return Err(LimboError::InternalError(\n                        \"Count: payload is not an integer\".to_string(),\n                    ));\n                };\n                *i = i.checked_add(1).ok_or(LimboError::IntegerOverflow)?;\n            }\n        }\n        AggFunc::Count0 => {\n            // invariant as per init_agg_payload: payload[0] is always an integer\n            let Value::Numeric(Numeric::Integer(i)) = &mut payload[0] else {\n                mark_unlikely();\n                return Err(LimboError::InternalError(\n                    \"Count0: payload is not an integer\".to_string(),\n                ));\n            };\n            *i = i.checked_add(1).ok_or(LimboError::IntegerOverflow)?;\n        }\n        AggFunc::Avg => {\n            if matches!(arg, Value::Null) {\n                return Ok(());\n            }\n            // invariant as per init_agg_payload: payload[0] is Float (sum), payload[1] is Float (r_err), payload[2] is Integer (count)\n            let [sum_val, r_err_val, count_val, ..] = payload else {\n                mark_unlikely();\n                return Err(LimboError::InternalError(\n                    \"Avg: payload too short\".to_string(),\n                ));\n            };\n            if matches!(*sum_val, Value::Null) {\n                return Ok(());\n            }\n            let r_err = r_err_val.to_float_or_zero();\n            let Value::Numeric(Numeric::Integer(count)) = count_val else {\n                mark_unlikely();\n                return Err(LimboError::InternalError(\n                    \"Avg: payload[2] is not an integer\".to_string(),\n                ));\n            };\n            let val = match arg {\n                Value::Numeric(Numeric::Integer(i)) => i as f64,\n                Value::Numeric(Numeric::Float(f)) => f64::from(f),\n                Value::Text(t) => match try_for_float(t.as_str().as_bytes()).1 {\n                    ParsedNumber::Integer(i) => i as f64,\n                    ParsedNumber::Float(f) => f,\n                    ParsedNumber::None => 0.0,\n                },\n                Value::Blob(b) => match try_for_float(&b).1 {\n                    ParsedNumber::Integer(i) => i as f64,\n                    ParsedNumber::Float(f) => f,\n                    ParsedNumber::None => 0.0,\n                },\n                _ => unreachable!(),\n            };\n            // Use Kahan-Babuška-Neumaier compensation for better floating-point precision\n            let s = sum_val.to_float_or_zero();\n            let t = s + val;\n            // When t is infinite, the KBN correction computes inf - inf = NaN,\n            // which is meaningless. Skip compensation in that case.\n            if t.is_finite() {\n                let correction = if s.abs() > val.abs() {\n                    (s - t) + val\n                } else {\n                    (val - t) + s\n                };\n                *r_err_val = Value::from_f64(r_err + correction);\n            }\n            *sum_val = Value::from_f64(t);\n            *count = count.checked_add(1).ok_or(LimboError::IntegerOverflow)?;\n        }\n        AggFunc::Sum | AggFunc::Total => {\n            // invariant as per init_agg_payload: payload[0] is acc (Null/Integer/Float),\n            // payload[1] is Float (r_err), payload[2] is Integer (approx), payload[3] is Integer (ovrfl)\n            let [acc, r_err_val, approx_val, ovrfl_val, ..] = payload else {\n                return Err(LimboError::InternalError(\n                    \"Sum/Total: payload too short\".to_string(),\n                ));\n            };\n            let r_err_f = r_err_val.to_float_or_zero();\n            let Value::Numeric(Numeric::Integer(approx_i)) = approx_val else {\n                mark_unlikely();\n                return Err(LimboError::InternalError(\n                    \"Sum/Total: payload[2] is not an integer\".to_string(),\n                ));\n            };\n            let Value::Numeric(Numeric::Integer(ovrfl_i)) = ovrfl_val else {\n                mark_unlikely();\n                return Err(LimboError::InternalError(\n                    \"Sum/Total: payload[3] is not an integer\".to_string(),\n                ));\n            };\n            let mut sum_state = SumAggState {\n                r_err: r_err_f,\n                approx: *approx_i != 0,\n                ovrfl: *ovrfl_i != 0,\n            };\n            if matches!(*acc, Value::Null) && sum_state.approx {\n                return Ok(());\n            }\n            match arg {\n                Value::Null => {}\n                Value::Numeric(Numeric::Integer(i)) => match acc {\n                    Value::Null => {\n                        *acc = Value::from_i64(i);\n                    }\n                    Value::Numeric(Numeric::Integer(acc_i)) => match acc_i.checked_add(i) {\n                        Some(sum) => *acc_i = sum,\n                        None => {\n                            if matches!(func, AggFunc::Total) {\n                                let acc_f = *acc_i as f64;\n                                *acc = Value::from_f64(acc_f);\n                                sum_state.approx = true;\n                                sum_state.ovrfl = true;\n                                apply_kbn_step_int(acc, i, &mut sum_state);\n                            } else {\n                                mark_unlikely();\n                                return Err(LimboError::IntegerOverflow);\n                            }\n                        }\n                    },\n                    Value::Numeric(Numeric::Float(_)) => {\n                        apply_kbn_step_int(acc, i, &mut sum_state);\n                    }\n                    _ => unreachable!(\"Sum/Total accumulator initialized to Null/Integer/Float\"),\n                },\n                Value::Numeric(Numeric::Float(f)) => match acc {\n                    Value::Null => {\n                        *acc = Value::Numeric(Numeric::Float(f));\n                        sum_state.approx = true;\n                    }\n                    Value::Numeric(Numeric::Integer(i)) => {\n                        *acc = Value::from_f64(*i as f64);\n                        sum_state.approx = true;\n                        apply_kbn_step(acc, f64::from(f), &mut sum_state);\n                    }\n                    Value::Numeric(Numeric::Float(_)) => {\n                        sum_state.approx = true;\n                        apply_kbn_step(acc, f64::from(f), &mut sum_state);\n                    }\n                    _ => unreachable!(\"Sum/Total accumulator initialized to Null/Integer/Float\"),\n                },\n                Value::Text(t) => {\n                    let (parse_result, parsed_number) = try_for_float(t.as_str().as_bytes());\n                    handle_text_sum(acc, &mut sum_state, parsed_number, parse_result, false);\n                }\n                Value::Blob(b) => {\n                    let (parse_result, parsed_number) = try_for_float(&b);\n                    handle_text_sum(acc, &mut sum_state, parsed_number, parse_result, true);\n                }\n            }\n            *r_err_val = Value::from_f64(sum_state.r_err);\n            *approx_i = sum_state.approx as i64;\n            *ovrfl_i = sum_state.ovrfl as i64;\n        }\n        AggFunc::Min | AggFunc::Max => {\n            if matches!(arg, Value::Null) {\n                return Ok(());\n            }\n            if matches!(payload[0], Value::Null) {\n                payload[0] = arg;\n                return Ok(());\n            }\n            use std::cmp::Ordering;\n            // Use custom type comparator if available, otherwise fall back to collation\n            let cmp = if let Some(ref cmp_fn) = comparator {\n                let arg_ref = arg.as_ref();\n                let payload_ref = payload[0].as_ref();\n                cmp_fn(&arg_ref, &payload_ref)\n            } else {\n                compare_with_collation(&arg, &payload[0], Some(collation))\n            };\n            let should_update = match func {\n                AggFunc::Max => cmp == Ordering::Greater,\n                AggFunc::Min => cmp == Ordering::Less,\n                _ => false,\n            };\n            if should_update {\n                payload[0] = arg;\n            }\n        }\n        AggFunc::GroupConcat | AggFunc::StringAgg => {\n            if matches!(arg, Value::Null) {\n                return Ok(());\n            }\n            let delimiter = maybe_arg2.unwrap_or_else(|| Value::build_text(\",\"));\n            let acc = &mut payload[0];\n            if matches!(acc, Value::Null) {\n                // First non-null value: convert to Text\n                *acc = Value::build_text(arg.to_string());\n            } else {\n                acc.exec_group_concat(&delimiter);\n                acc.exec_group_concat(&arg);\n            }\n        }\n        AggFunc::External(_) => {\n            mark_unlikely();\n            return Err(LimboError::InternalError(\n                \"External aggregate not supported in update_agg_payload\".to_string(),\n            ));\n        }\n        #[cfg(feature = \"json\")]\n        AggFunc::JsonGroupObject | AggFunc::JsonbGroupObject => {\n            // arg = key, maybe_arg2 = value\n            let Some(value) = maybe_arg2 else {\n                mark_unlikely();\n                return Err(LimboError::InternalError(\n                    \"JsonGroupObject/JsonbGroupObject: no value provided\".to_string(),\n                ));\n            };\n            let mut key_vec = convert_dbtype_to_raw_jsonb(&arg)?;\n            let mut val_vec = convert_dbtype_to_raw_jsonb(&value)?;\n            let Value::Blob(vec) = &mut payload[0] else {\n                mark_unlikely();\n                return Err(LimboError::InternalError(\n                    \"JsonGroupObject: payload[0] is not a blob\".to_string(),\n                ));\n            };\n            if vec.is_empty() {\n                // bits for obj header\n                vec.push(12);\n            }\n            vec.append(&mut key_vec);\n            vec.append(&mut val_vec);\n        }\n        AggFunc::ArrayAgg => {\n            // ArrayAgg accumulation is handled directly in the AggStep caller\n            // via payload_vec_mut() to grow the Vec (O(1) per row).\n            return Err(LimboError::InternalError(\n                \"ArrayAgg should be handled directly in op_agg_step, not update_agg_payload\".into(),\n            ));\n        }\n        #[cfg(feature = \"json\")]\n        AggFunc::JsonGroupArray | AggFunc::JsonbGroupArray => {\n            // arg = value\n            let mut data = convert_dbtype_to_raw_jsonb(&arg)?;\n            let Value::Blob(vec) = &mut payload[0] else {\n                mark_unlikely();\n                return Err(LimboError::InternalError(\n                    \"JsonGroupArray: payload[0] is not a blob\".to_string(),\n                ));\n            };\n            if vec.is_empty() {\n                vec.push(11); // bits for array header\n            }\n            vec.append(&mut data);\n        }\n    }\n    Ok(())\n}\n\n/// Convert the intermediate aggregate state in `payload` into the final result value.\n///\n/// This finalization logic is shared between both aggregation strategies:\n/// - **Register-based (sort-stream)**: Called from `op_agg_final` (AggFinal/AggValue\n///   instructions) when a group boundary is crossed.\n/// - **Hash-based (future enhancement)**: Called during the emit phase of HashAggNext after all rows have\n///   been processed. The payload may have been merged via `merge_agg_payload` if\n///   spilling occurred.\n///\n/// # Finalization logic by aggregate type:\n/// - **Count/Count0**: Returns the count directly\n/// - **Avg**: Computes `sum / count`, returns NULL if count is 0\n/// - **Sum**: Returns the accumulated value, applying Kahan compensation if approximate.\n///   Returns NULL if no non-NULL values were seen (unless float arithmetic was used).\n/// - **Total**: Like Sum but always returns Float, defaulting to 0.0 for empty groups\n/// - **Min/Max**: Returns the tracked extreme value directly\n/// - **GroupConcat/StringAgg**: Returns the accumulated string\n/// - **JsonGroup***: Parses accumulated raw JSONB bytes into proper JSON output\nfn finalize_agg_payload(func: &AggFunc, payload: &[Value]) -> Result<Value> {\n    let val = match func {\n        AggFunc::Count | AggFunc::Count0 => payload[0].clone(),\n        AggFunc::Avg => {\n            // Payload: [sum, r_err, count]\n            let count = payload[2].as_int().unwrap_or(0);\n            if count == 0 || matches!(&payload[0], Value::Null) {\n                Value::Null\n            } else {\n                let sum = payload[0].to_float_or_zero();\n                let r_err = payload[1].to_float_or_zero();\n                // Apply KBN compensation before dividing\n                Value::from_f64((sum + r_err) / count as f64)\n            }\n        }\n        AggFunc::Sum => {\n            let acc = &payload[0];\n            let approx = payload[2].as_int().unwrap_or(0) != 0;\n            let ovrfl = payload[3].as_int().unwrap_or(0) != 0;\n            let r_err = payload[1].to_float_or_zero();\n            match acc {\n                Value::Null => Value::Null,\n                Value::Numeric(Numeric::Integer(i)) if !approx && !ovrfl => Value::from_i64(*i),\n                Value::Numeric(Numeric::Float(f)) => Value::from_f64(f64::from(*f) + r_err),\n                _ => Value::from_f64(acc.to_float_or_zero() + r_err),\n            }\n        }\n        AggFunc::Total => {\n            // Payload: [acc, r_err, approx, ovrfl]\n            let acc = &payload[0];\n            let approx = payload[2].as_int().unwrap_or(0) != 0;\n            let r_err = payload[1].to_float_or_zero();\n            match acc {\n                Value::Null if approx => Value::Null,\n                Value::Null => Value::from_f64(0.0),\n                Value::Numeric(Numeric::Integer(i)) => Value::from_f64(*i as f64 + r_err),\n                Value::Numeric(Numeric::Float(f)) => Value::from_f64(f64::from(*f) + r_err),\n                _ => unreachable!(\"Total accumulator initialized to Null/Integer/Float\"),\n            }\n        }\n        AggFunc::Min | AggFunc::Max => payload[0].clone(),\n        AggFunc::GroupConcat | AggFunc::StringAgg => payload[0].clone(),\n        AggFunc::ArrayAgg => {\n            // payload[0] = count, payload[1..] = accumulated values.\n            // Serialize to a record blob only once at finalization.\n            let count = payload[0].as_int().unwrap_or(0) as usize;\n            if count == 0 {\n                Value::Null\n            } else if 1 + count > payload.len() {\n                return Err(LimboError::InternalError(format!(\n                    \"ArrayAgg: count ({count}) exceeds payload length ({})\",\n                    payload.len() - 1\n                )));\n            } else {\n                let elements = &payload[1..1 + count];\n                Value::Blob(ImmutableRecord::from_values(elements, count).into_payload())\n            }\n        }\n        AggFunc::External(_) => {\n            mark_unlikely();\n            // External aggregates are finalized via AggContext::compute_external()\n            return Err(LimboError::InternalError(\n                \"finalize_agg_payload called for External aggregate\".to_string(),\n            ));\n        }\n        #[cfg(feature = \"json\")]\n        AggFunc::JsonGroupObject => {\n            let data = payload[0].to_blob().expect(\"Should be blob\");\n            json_from_raw_bytes_agg(data, false)?\n        }\n        #[cfg(feature = \"json\")]\n        AggFunc::JsonbGroupObject => {\n            let data = payload[0].to_blob().expect(\"Should be blob\");\n            json_from_raw_bytes_agg(data, true)?\n        }\n        #[cfg(feature = \"json\")]\n        AggFunc::JsonGroupArray => {\n            let data = payload[0].to_blob().expect(\"Should be blob\");\n            json_from_raw_bytes_agg(data, false)?\n        }\n        #[cfg(feature = \"json\")]\n        AggFunc::JsonbGroupArray => {\n            let data = payload[0].to_blob().expect(\"Should be blob\");\n            json_from_raw_bytes_agg(data, true)?\n        }\n    };\n\n    Ok(val)\n}\n\npub fn op_agg_step(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        AggStep {\n            acc_reg,\n            col,\n            delimiter,\n            func,\n            comparator,\n        },\n        insn\n    );\n\n    // Initialize aggregate state if not already done\n    if let Register::Value(Value::Null) = state.registers[*acc_reg] {\n        state.registers[*acc_reg] = match func {\n            AggFunc::External(ext_func) => match ext_func.as_ref() {\n                ExtFunc::Aggregate {\n                    init,\n                    step,\n                    finalize,\n                    argc,\n                } => Register::Aggregate(AggContext::External(ExternalAggState {\n                    state: unsafe { (init)() },\n                    argc: *argc,\n                    step_fn: *step,\n                    finalize_fn: *finalize,\n                })),\n                _ => unreachable!(\"scalar function called in aggregate context\"),\n            },\n            _ => {\n                // Built-in aggregates use flat payload\n                let mut payload = Vec::new();\n                init_agg_payload(func, &mut payload)?;\n                Register::Aggregate(AggContext::Builtin(payload))\n            }\n        };\n    }\n\n    // Resolve custom type comparator for MIN/MAX if provided\n    let comparator = comparator.as_ref().map(make_sort_comparator);\n\n    // Step the aggregate\n    match func {\n        AggFunc::External(_) => {\n            // External aggregates use FFI and need special handling\n            let (step_fn, state_ptr, argc) = {\n                let Register::Aggregate(agg) = &state.registers[*acc_reg] else {\n                    unreachable!();\n                };\n                let AggContext::External(agg_state) = agg else {\n                    unreachable!();\n                };\n                (agg_state.step_fn, agg_state.state, agg_state.argc)\n            };\n            if argc == 0 {\n                unsafe { step_fn(state_ptr, 0, std::ptr::null()) };\n            } else {\n                let register_slice = &state.registers[*col..*col + argc];\n                let mut ext_values: Vec<ExtValue> = Vec::with_capacity(argc);\n                for ov in register_slice.iter() {\n                    ext_values.push(ov.get_value().to_ffi());\n                }\n                let argv_ptr = ext_values.as_ptr();\n                unsafe { step_fn(state_ptr, argc as i32, argv_ptr) };\n                for ext_value in ext_values {\n                    unsafe { ext_value.__free_internal_type() };\n                }\n            }\n        }\n        _ => {\n            let arg = state.registers[*col].get_value().clone();\n\n            if matches!(func, AggFunc::ArrayAgg) {\n                // ArrayAgg grows the payload Vec directly (O(1) per row).\n                let Register::Aggregate(agg) = &mut state.registers[*acc_reg] else {\n                    panic!(\n                        \"Unexpected value {:?} in AggStep at register {}\",\n                        state.registers[*acc_reg], *acc_reg\n                    );\n                };\n                let payload = agg.payload_vec_mut();\n                let count = payload[0]\n                    .as_int()\n                    .expect(\"array_agg count must be an integer\")\n                    as usize;\n                payload[0] = Value::from_i64((count + 1) as i64);\n                payload.push(arg);\n            } else {\n                // Only a subset of aggregate functions take two arguments\n                let maybe_arg2 = match func {\n                    AggFunc::GroupConcat | AggFunc::StringAgg => {\n                        Some(state.registers[*delimiter].get_value().clone())\n                    }\n                    #[cfg(feature = \"json\")]\n                    AggFunc::JsonGroupObject | AggFunc::JsonbGroupObject => {\n                        Some(state.registers[*delimiter].get_value().clone())\n                    }\n                    _ => None,\n                };\n                let collation = state.current_collation.unwrap_or(CollationSeq::Binary);\n\n                // Now get mutable borrow on payload\n                let Register::Aggregate(agg) = &mut state.registers[*acc_reg] else {\n                    panic!(\n                        \"Unexpected value {:?} in AggStep at register {}\",\n                        state.registers[*acc_reg], *acc_reg\n                    );\n                };\n                let payload = agg.payload_mut();\n                update_agg_payload(func, arg, maybe_arg2, payload, collation, &comparator)?;\n            }\n        }\n    };\n\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_agg_final(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    let (acc_reg, dest_reg, func) = match insn {\n        Insn::AggFinal { register, func } => (*register, *register, func),\n        Insn::AggValue {\n            acc_reg,\n            dest_reg,\n            func,\n        } => (*acc_reg, *dest_reg, func),\n        _ => unreachable!(\"unexpected Insn {:?}\", insn),\n    };\n\n    match &state.registers[acc_reg] {\n        Register::Aggregate(agg) => {\n            let value = match agg {\n                AggContext::External(_) => {\n                    // External aggregates use FFI finalization\n                    agg.compute_external()?\n                }\n                AggContext::Builtin(payload) => {\n                    // Built-in aggregates use shared finalization\n                    finalize_agg_payload(func, payload)?\n                }\n            };\n            state.registers[dest_reg].set_value(value);\n        }\n        Register::Value(Value::Null) => {\n            // When the set is empty, return appropriate default\n            match func {\n                AggFunc::Total => {\n                    state.registers[dest_reg]\n                        .set_float(NonNan::new(0.0).expect(\"0.0 is a valid NonNan\"));\n                }\n                AggFunc::Count | AggFunc::Count0 => {\n                    state.registers[dest_reg].set_int(0);\n                }\n                #[cfg(feature = \"json\")]\n                AggFunc::JsonGroupArray => {\n                    state.registers[dest_reg].set_text(Text::json(\"[]\".to_string()));\n                }\n                #[cfg(feature = \"json\")]\n                AggFunc::JsonbGroupArray => {\n                    state.registers[dest_reg]\n                        .set_blob(json::jsonb::Jsonb::make_empty_array(1).data());\n                }\n                #[cfg(feature = \"json\")]\n                AggFunc::JsonGroupObject => {\n                    state.registers[dest_reg].set_text(Text::json(\"{}\".to_string()));\n                }\n                #[cfg(feature = \"json\")]\n                AggFunc::JsonbGroupObject => {\n                    state.registers[dest_reg]\n                        .set_blob(json::jsonb::Jsonb::make_empty_obj(1).data());\n                }\n                _ => {}\n            }\n        }\n        other => {\n            panic!(\"Unexpected value {other:?} in AggFinal\");\n        }\n    };\n\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_sorter_open(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        SorterOpen {\n            cursor_id,\n            columns: _,\n            order_and_collations,\n            comparators,\n        },\n        insn\n    );\n    // be careful here - we must not use any async operations after pager.with_header because this op-code has no proper state-machine\n    let page_size = match pager.with_header(|header| header.page_size) {\n        Ok(IOResult::Done(page_size)) => page_size,\n        Err(_) => PageSize::default(),\n        Ok(IOResult::IO(io)) => return Ok(InsnFunctionStepResult::IO(io)),\n    };\n    let page_size = page_size.get() as usize;\n\n    let cache_size = program.connection.get_cache_size();\n\n    // Set the buffer size threshold to be roughly the same as the limit configured for the page-cache.\n    let max_buffer_size_bytes = if cache_size < 0 {\n        (cache_size.abs() * 1024) as usize\n    } else {\n        (cache_size as usize) * page_size\n    };\n    let (order, collations): (Vec<_>, Vec<_>) = order_and_collations\n        .iter()\n        .map(|(ord, coll)| (*ord, coll.unwrap_or_default()))\n        .unzip();\n    let comparators = comparators\n        .iter()\n        .map(|c| c.as_ref().map(make_sort_comparator))\n        .collect();\n    let temp_store = program.connection.get_temp_store();\n    let cursor = Sorter::new(\n        &order,\n        collations,\n        comparators,\n        max_buffer_size_bytes,\n        page_size,\n        pager.io.clone(),\n        temp_store,\n    );\n    let cursors = &mut state.cursors;\n    cursors\n        .get_mut(*cursor_id)\n        .expect(\"cursor_id should be valid\")\n        .replace(Cursor::new_sorter(cursor));\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_sorter_data(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        SorterData {\n            cursor_id,\n            dest_reg,\n            pseudo_cursor,\n        },\n        insn\n    );\n    let record = {\n        let cursor = state.get_cursor(*cursor_id);\n        let cursor = cursor.as_sorter_mut();\n        cursor.record().cloned()\n    };\n    let record = match record {\n        Some(record) => record,\n        None => {\n            state.pc += 1;\n            return Ok(InsnFunctionStepResult::Step);\n        }\n    };\n    state.registers[*dest_reg] = Register::Record(record.clone());\n    {\n        let pseudo_cursor = state.get_cursor(*pseudo_cursor);\n        pseudo_cursor.as_pseudo_mut().insert(record);\n    }\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_sorter_insert(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        SorterInsert {\n            cursor_id,\n            record_reg,\n        },\n        insn\n    );\n    {\n        let cursor = get_cursor!(state, *cursor_id);\n        let cursor = cursor.as_sorter_mut();\n        let record = match &state.registers[*record_reg] {\n            Register::Record(record) => record,\n            _ => unreachable!(\"SorterInsert on non-record register\"),\n        };\n        return_if_io!(cursor.insert(record));\n    }\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_sorter_sort(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        SorterSort {\n            cursor_id,\n            pc_if_empty,\n        },\n        insn\n    );\n    let (is_empty, did_sort) = {\n        let cursor = state.get_cursor(*cursor_id);\n        let cursor = cursor.as_sorter_mut();\n        let is_empty = cursor.is_empty();\n        if !is_empty {\n            return_if_io!(cursor.sort());\n        }\n        (is_empty, !is_empty)\n    };\n    // Increment metrics for sort operation after cursor is dropped\n    if did_sort {\n        state.metrics.sort_operations = state.metrics.sort_operations.saturating_add(1);\n    }\n    if is_empty {\n        state.pc = pc_if_empty.as_offset_int();\n    } else {\n        state.pc += 1;\n    }\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_sorter_next(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        SorterNext {\n            cursor_id,\n            pc_if_next,\n        },\n        insn\n    );\n    assert!(pc_if_next.is_offset());\n    let has_more = {\n        let cursor = state.get_cursor(*cursor_id);\n        let cursor = cursor.as_sorter_mut();\n        return_if_io!(cursor.next());\n        cursor.has_more()\n    };\n    if has_more {\n        state.pc = pc_if_next.as_offset_int();\n    } else {\n        state.pc += 1;\n    }\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_sorter_compare(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        SorterCompare {\n            cursor_id,\n            sorted_record_reg,\n            num_regs,\n            pc_when_nonequal,\n        },\n        insn\n    );\n\n    let previous_sorter_values = {\n        let Register::Record(record) = &state.registers[*sorted_record_reg] else {\n            mark_unlikely();\n            return Err(LimboError::InternalError(\n                \"Sorted record must be a record\".to_string(),\n            ));\n        };\n        &record.get_values_range(0..*num_regs)?\n    };\n\n    // Inlined `state.get_cursor` to prevent borrowing conflit with `state.registers`\n    let cursor = state\n        .cursors\n        .get_mut(*cursor_id)\n        .unwrap_or_else(|| panic!(\"cursor id {cursor_id} out of bounds\"))\n        .as_mut()\n        .unwrap_or_else(|| panic!(\"cursor id {cursor_id} is None\"));\n\n    let cursor = cursor.as_sorter_mut();\n    let Some(current_sorter_record) = cursor.record() else {\n        mark_unlikely();\n        return Err(LimboError::InternalError(\n            \"Sorter must have a record\".to_string(),\n        ));\n    };\n\n    let current_sorter_values = &current_sorter_record.get_values_range(0..*num_regs)?;\n    // If the current sorter record has a NULL in any of the significant fields, the comparison is not equal.\n    let is_equal = current_sorter_values\n        .iter()\n        .all(|v| !matches!(v, ValueRef::Null))\n        && compare_immutable(\n            previous_sorter_values,\n            current_sorter_values,\n            &cursor.index_key_info,\n        )\n        .is_eq();\n    if is_equal {\n        state.pc += 1;\n    } else {\n        state.pc = pc_when_nonequal.as_offset_int();\n    }\n    Ok(InsnFunctionStepResult::Step)\n}\n\n/// Insert the integer value held by register P2 into a RowSet object held in register P1.\n///\n/// An assertion fails if P2 is not an integer.\npub fn op_rowset_add(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        RowSetAdd {\n            rowset_reg,\n            value_reg,\n        },\n        insn\n    );\n\n    let value = state.registers[*value_reg].get_value();\n    let rowid = match value {\n        Value::Numeric(Numeric::Integer(i)) => *i,\n        _ => {\n            mark_unlikely();\n            return Err(LimboError::InternalError(\n                \"RowSetAdd: P2 must be an integer\".to_string(),\n            ));\n        }\n    };\n\n    let rowset = state.rowsets.entry(*rowset_reg).or_default();\n\n    rowset.insert(rowid);\n\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\n/// Extract the smallest value from the RowSet object in P1 and put that value into register P3.\n/// Or, if RowSet object P1 is initially empty, leave P3 unchanged and jump to instruction P2.\npub fn op_rowset_read(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        RowSetRead {\n            rowset_reg,\n            pc_if_empty,\n            dest_reg,\n        },\n        insn\n    );\n    assert!(pc_if_empty.is_offset());\n\n    let rowset = state.rowsets.get_mut(rowset_reg);\n\n    match rowset {\n        Some(rowset) => {\n            if rowset.is_empty() {\n                state.pc = pc_if_empty.as_offset_int();\n            } else if let Some(smallest) = rowset.smallest() {\n                state.registers[*dest_reg].set_int(smallest);\n                state.pc += 1;\n            } else {\n                state.pc = pc_if_empty.as_offset_int();\n            }\n        }\n        None => {\n            state.pc = pc_if_empty.as_offset_int();\n        }\n    }\n\n    Ok(InsnFunctionStepResult::Step)\n}\n\n/// Register P3 is assumed to hold a 64-bit integer value. If register P1 contains a RowSet object\n/// and that RowSet object contains the value held in P3, jump to register P2. Otherwise, insert\n/// the integer in P3 into the RowSet and continue on to the next opcode.\n///\n/// The RowSet object is optimized for the case where sets of integers are inserted in distinct\n/// phases, which each set contains no duplicates. Each set is identified by a unique P4 value.\n/// The first set must have P4==0, the final set must have P4==-1, and for all other sets must\n/// have P4>0.\n///\n/// This allows optimizations: (a) when P4==0 there is no need to test the RowSet object for P3,\n/// as it is guaranteed not to contain it, (b) when P4==-1 there is no need to insert the value,\n/// as it will never be tested for, and (c) when a value that is part of set X is inserted, there\n/// is no need to search to see if the same value was previously inserted as part of set X (only\n/// if it was previously inserted as part of some other set).\npub fn op_rowset_test(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        RowSetTest {\n            rowset_reg,\n            pc_if_found,\n            value_reg,\n            batch,\n        },\n        insn\n    );\n    assert!(pc_if_found.is_offset());\n\n    let value = state.registers[*value_reg].get_value();\n    let rowid = match value {\n        Value::Numeric(Numeric::Integer(i)) => *i,\n        _ => {\n            mark_unlikely();\n            return Err(LimboError::InternalError(\n                \"RowSetTest: P3 must be an integer\".to_string(),\n            ));\n        }\n    };\n\n    let rowset = state.rowsets.entry(*rowset_reg).or_default();\n\n    let found = if *batch == 0 {\n        // SQLite rowsets assume that in each batch, the caller makes sure no\n        // duplicates are inserted. Hence if batch==0, we can return false without\n        // checking.\n        false\n    } else {\n        rowset.test(rowid, *batch)\n    };\n\n    if found {\n        state.pc = pc_if_found.as_offset_int();\n    } else {\n        if *batch != -1 {\n            rowset.insert(rowid);\n        }\n        state.pc += 1;\n    }\n\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_function(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        Function {\n            constant_mask: _,\n            func,\n            start_reg,\n            dest,\n        },\n        insn\n    );\n    let arg_count = func.arg_count;\n\n    match &func.func {\n        #[cfg(feature = \"json\")]\n        crate::function::Func::Json(json_func) => match json_func {\n            JsonFunc::Json => {\n                let json_value = &state.registers[*start_reg];\n                let json_str = get_json(json_value.get_value(), None);\n                match json_str {\n                    Ok(json) => state.registers[*dest].set_value(json),\n                    Err(e) => return Err(e),\n                }\n            }\n\n            JsonFunc::Jsonb => {\n                let json_value = &state.registers[*start_reg];\n                let json_blob = jsonb(json_value.get_value(), &state.json_cache);\n                match json_blob {\n                    Ok(json) => state.registers[*dest].set_value(json),\n                    Err(e) => return Err(e),\n                }\n            }\n\n            JsonFunc::JsonArray\n            | JsonFunc::JsonObject\n            | JsonFunc::JsonbArray\n            | JsonFunc::JsonbObject => {\n                let reg_values =\n                    registers_to_ref_values(&state.registers[*start_reg..*start_reg + arg_count]);\n\n                let json_func = match json_func {\n                    JsonFunc::JsonArray => json_array,\n                    JsonFunc::JsonObject => json_object,\n                    JsonFunc::JsonbArray => jsonb_array,\n                    JsonFunc::JsonbObject => jsonb_object,\n                    _ => unreachable!(),\n                };\n                let json_result = json_func(reg_values);\n\n                match json_result {\n                    Ok(json) => state.registers[*dest].set_value(json),\n                    Err(e) => return Err(e),\n                }\n            }\n            JsonFunc::JsonExtract => {\n                let result = match arg_count {\n                    0 => Ok(Value::Null),\n                    _ => {\n                        let val = &state.registers[*start_reg];\n                        let reg_values = registers_to_ref_values(\n                            &state.registers[*start_reg + 1..*start_reg + arg_count],\n                        );\n\n                        json_extract(val.get_value(), reg_values, &state.json_cache)\n                    }\n                };\n\n                match result {\n                    Ok(json) => state.registers[*dest].set_value(json),\n                    Err(e) => return Err(e),\n                }\n            }\n            JsonFunc::JsonbExtract => {\n                let result = match arg_count {\n                    0 => Ok(Value::Null),\n                    _ => {\n                        let val = &state.registers[*start_reg];\n                        let reg_values = registers_to_ref_values(\n                            &state.registers[*start_reg + 1..*start_reg + arg_count],\n                        );\n\n                        jsonb_extract(val.get_value(), reg_values, &state.json_cache)\n                    }\n                };\n\n                match result {\n                    Ok(json) => state.registers[*dest].set_value(json),\n                    Err(e) => return Err(e),\n                }\n            }\n\n            JsonFunc::JsonArrowExtract | JsonFunc::JsonArrowShiftExtract => {\n                assert_eq!(arg_count, 2);\n                let json = &state.registers[*start_reg];\n                let path = &state.registers[*start_reg + 1];\n                let json_func = match json_func {\n                    JsonFunc::JsonArrowExtract => json_arrow_extract,\n                    JsonFunc::JsonArrowShiftExtract => json_arrow_shift_extract,\n                    _ => unreachable!(),\n                };\n                let json_str = json_func(json.get_value(), path.get_value(), &state.json_cache);\n                match json_str {\n                    Ok(json) => state.registers[*dest].set_value(json),\n                    Err(e) => return Err(e),\n                }\n            }\n            JsonFunc::JsonArrayLength | JsonFunc::JsonType => {\n                let json_value = &state.registers[*start_reg];\n                let path_value = if arg_count > 1 {\n                    Some(&state.registers[*start_reg + 1])\n                } else {\n                    None\n                };\n                let func_result = match json_func {\n                    JsonFunc::JsonArrayLength => json_array_length(\n                        json_value.get_value(),\n                        path_value.map(|x| x.get_value()),\n                        &state.json_cache,\n                    ),\n                    JsonFunc::JsonType => {\n                        json_type(json_value.get_value(), path_value.map(|x| x.get_value()))\n                    }\n                    _ => unreachable!(),\n                };\n\n                match func_result {\n                    Ok(result) => state.registers[*dest].set_value(result),\n                    Err(e) => return Err(e),\n                }\n            }\n            JsonFunc::JsonErrorPosition => {\n                let json_value = &state.registers[*start_reg];\n                match json_error_position(json_value.get_value()) {\n                    Ok(pos) => state.registers[*dest].set_value(pos),\n                    Err(e) => return Err(e),\n                }\n            }\n            JsonFunc::JsonValid => {\n                let json_value = &state.registers[*start_reg];\n                state.registers[*dest].set_value(is_json_valid(json_value.get_value()));\n            }\n            JsonFunc::JsonPatch => {\n                assert_eq!(arg_count, 2);\n                assert!(*start_reg + 1 < state.registers.len());\n                let target = &state.registers[*start_reg];\n                let patch = &state.registers[*start_reg + 1];\n                state.registers[*dest].set_value(json_patch(\n                    target.get_value(),\n                    patch.get_value(),\n                    &state.json_cache,\n                )?);\n            }\n            JsonFunc::JsonbPatch => {\n                assert_eq!(arg_count, 2);\n                assert!(*start_reg + 1 < state.registers.len());\n                let target = &state.registers[*start_reg];\n                let patch = &state.registers[*start_reg + 1];\n                state.registers[*dest].set_value(jsonb_patch(\n                    target.get_value(),\n                    patch.get_value(),\n                    &state.json_cache,\n                )?);\n            }\n            JsonFunc::JsonRemove => {\n                if let Ok(json) = json_remove(\n                    registers_to_ref_values(&state.registers[*start_reg..*start_reg + arg_count]),\n                    &state.json_cache,\n                ) {\n                    state.registers[*dest].set_value(json);\n                } else {\n                    state.registers[*dest].set_null();\n                }\n            }\n            JsonFunc::JsonbRemove => {\n                if let Ok(json) = jsonb_remove(\n                    registers_to_ref_values(&state.registers[*start_reg..*start_reg + arg_count]),\n                    &state.json_cache,\n                ) {\n                    state.registers[*dest].set_value(json);\n                } else {\n                    state.registers[*dest].set_null();\n                }\n            }\n            JsonFunc::JsonReplace => {\n                if let Ok(json) = json_replace(\n                    registers_to_ref_values(&state.registers[*start_reg..*start_reg + arg_count]),\n                    &state.json_cache,\n                ) {\n                    state.registers[*dest].set_value(json);\n                } else {\n                    state.registers[*dest].set_null();\n                }\n            }\n            JsonFunc::JsonbReplace => {\n                if let Ok(json) = jsonb_replace(\n                    registers_to_ref_values(&state.registers[*start_reg..*start_reg + arg_count]),\n                    &state.json_cache,\n                ) {\n                    state.registers[*dest].set_value(json);\n                } else {\n                    state.registers[*dest].set_null();\n                }\n            }\n            JsonFunc::JsonInsert => {\n                if let Ok(json) = json_insert(\n                    registers_to_ref_values(&state.registers[*start_reg..*start_reg + arg_count]),\n                    &state.json_cache,\n                ) {\n                    state.registers[*dest].set_value(json);\n                } else {\n                    state.registers[*dest].set_null();\n                }\n            }\n            JsonFunc::JsonbInsert => {\n                if let Ok(json) = jsonb_insert(\n                    registers_to_ref_values(&state.registers[*start_reg..*start_reg + arg_count]),\n                    &state.json_cache,\n                ) {\n                    state.registers[*dest].set_value(json);\n                } else {\n                    state.registers[*dest].set_null();\n                }\n            }\n            JsonFunc::JsonPretty => {\n                let json_value = &state.registers[*start_reg];\n                let indent = if arg_count > 1 {\n                    Some(&state.registers[*start_reg + 1])\n                } else {\n                    None\n                };\n\n                // Blob should be converted to Ascii in a lossy way\n                // However, Rust strings uses utf-8\n                // so the behavior at the moment is slightly different\n                // To the way blobs are parsed here in SQLite.\n                let indent = match indent {\n                    Some(value) => match value.get_value() {\n                        Value::Text(text) => text.as_str(),\n                        Value::Numeric(Numeric::Integer(val)) => &val.to_string(),\n                        Value::Numeric(Numeric::Float(val)) => &f64::from(*val).to_string(),\n                        Value::Blob(val) => &String::from_utf8_lossy(val),\n                        _ => \"    \",\n                    },\n                    // If the second argument is omitted or is NULL, then indentation is four spaces per level\n                    None => \"    \",\n                };\n\n                let json_str = get_json(json_value.get_value(), Some(indent))?;\n                state.registers[*dest].set_value(json_str);\n            }\n            JsonFunc::JsonSet => {\n                if arg_count % 2 == 0 {\n                    bail_constraint_error!(\"json_set() needs an odd number of arguments\")\n                }\n                let reg_values =\n                    registers_to_ref_values(&state.registers[*start_reg..*start_reg + arg_count]);\n\n                let json_result = json_set(reg_values, &state.json_cache);\n\n                match json_result {\n                    Ok(json) => state.registers[*dest].set_value(json),\n                    Err(e) => return Err(e),\n                }\n            }\n            JsonFunc::JsonbSet => {\n                if arg_count % 2 == 0 {\n                    bail_constraint_error!(\"json_set() needs an odd number of arguments\")\n                }\n                let reg_values =\n                    registers_to_ref_values(&state.registers[*start_reg..*start_reg + arg_count]);\n\n                let json_result = jsonb_set(reg_values, &state.json_cache);\n\n                match json_result {\n                    Ok(json) => state.registers[*dest].set_value(json),\n                    Err(e) => return Err(e),\n                }\n            }\n            JsonFunc::JsonQuote => {\n                let json_value = &state.registers[*start_reg];\n\n                match json_quote(json_value.get_value()) {\n                    Ok(result) => state.registers[*dest].set_value(result),\n                    Err(e) => return Err(e),\n                }\n            }\n        },\n        crate::function::Func::Scalar(scalar_func) => match scalar_func {\n            ScalarFunc::Array | ScalarFunc::ArrayElement | ScalarFunc::ArraySetElement => {\n                unreachable!(\"desugared to dedicated instructions, not Function\")\n            }\n            ScalarFunc::Cast => {\n                assert_eq!(arg_count, 2);\n                assert!(*start_reg + 1 < state.registers.len());\n                let reg_value_argument = state.registers[*start_reg].clone();\n                let Value::Text(reg_value_type) =\n                    state.registers[*start_reg + 1].get_value().clone()\n                else {\n                    unreachable!(\"Cast with non-text type\");\n                };\n                let result = reg_value_argument\n                    .get_value()\n                    .exec_cast(reg_value_type.as_str());\n                state.registers[*dest].set_value(result);\n            }\n            ScalarFunc::Changes => {\n                let res = &program.connection.last_change;\n                let changes = res.load(Ordering::SeqCst);\n                state.registers[*dest].set_int(changes);\n            }\n            ScalarFunc::Char => {\n                let reg_values = &state.registers[*start_reg..*start_reg + arg_count];\n                state.registers[*dest].set_value(Value::exec_char(\n                    reg_values.iter().map(|reg| reg.get_value()),\n                ));\n            }\n            ScalarFunc::Coalesce => {}\n            ScalarFunc::Concat => {\n                let reg_values = &state.registers[*start_reg..*start_reg + arg_count];\n                let result =\n                    Value::exec_concat_strings(reg_values.iter().map(|reg| reg.get_value()));\n                state.registers[*dest].set_value(result);\n            }\n            ScalarFunc::ConcatWs => {\n                let reg_values = &state.registers[*start_reg..*start_reg + arg_count];\n                let result = Value::exec_concat_ws(reg_values.iter().map(|reg| reg.get_value()));\n                state.registers[*dest].set_value(result);\n            }\n            ScalarFunc::Glob => {\n                if arg_count != 2 {\n                    mark_unlikely();\n                    return Err(LimboError::ParseError(\n                        \"wrong number of arguments to function GLOB()\".to_string(),\n                    ));\n                }\n                let pattern_reg = &state.registers[*start_reg];\n                let match_reg = &state.registers[*start_reg + 1];\n\n                let pattern_value = pattern_reg.get_value();\n                let match_value = match_reg.get_value();\n\n                if pattern_value == &Value::Null || match_value == &Value::Null {\n                    state.registers[*dest].set_null();\n                } else {\n                    let pattern_cow = match pattern_value {\n                        Value::Text(s) => std::borrow::Cow::Borrowed(s.as_str()),\n                        v => match v.exec_cast(\"TEXT\") {\n                            Value::Text(s) => std::borrow::Cow::Owned(s.to_string()),\n                            _ => unreachable!(\"Cast to TEXT should yield Text\"),\n                        },\n                    };\n\n                    let match_cow = match match_value {\n                        Value::Text(s) => std::borrow::Cow::Borrowed(s.as_str()),\n                        v => match v.exec_cast(\"TEXT\") {\n                            Value::Text(s) => std::borrow::Cow::Owned(s.to_string()),\n                            _ => unreachable!(\"Cast to TEXT should yield Text\"),\n                        },\n                    };\n\n                    let matches = Value::exec_glob(&pattern_cow, &match_cow)?;\n                    state.registers[*dest].set_int(matches as i64);\n                }\n            }\n            ScalarFunc::IfNull => {}\n            ScalarFunc::Iif => {}\n            ScalarFunc::Instr => {\n                let reg_value = &state.registers[*start_reg];\n                let pattern_value = &state.registers[*start_reg + 1];\n                match reg_value.get_value().exec_instr(pattern_value.get_value()) {\n                    Value::Numeric(Numeric::Integer(i)) => state.registers[*dest].set_int(i),\n                    _ => state.registers[*dest].set_null(),\n                };\n            }\n            ScalarFunc::LastInsertRowid => {\n                state.registers[*dest].set_int(program.connection.last_insert_rowid());\n            }\n            ScalarFunc::Like => {\n                let pattern_reg = &state.registers[*start_reg];\n                let match_reg = &state.registers[*start_reg + 1];\n\n                let pattern_value = pattern_reg.get_value();\n                let match_value = match_reg.get_value();\n\n                // 1. Check for NULL inputs\n                if pattern_value == &Value::Null || match_value == &Value::Null {\n                    state.registers[*dest].set_null();\n                } else {\n                    // 2. Resolve Escape Character (if 3rd arg exists)\n                    let mut escape_char = None;\n                    let mut is_null_result = false;\n\n                    if arg_count == 3 {\n                        let escape_value = state.registers[*start_reg + 2].get_value();\n                        match escape_value {\n                            Value::Null => {\n                                is_null_result = true;\n                            }\n                            _ => {\n                                let escape_cow = match escape_value {\n                                    Value::Text(s) => std::borrow::Cow::Borrowed(s.as_str()),\n                                    v => match v.exec_cast(\"TEXT\") {\n                                        Value::Text(s) => std::borrow::Cow::Owned(s.to_string()),\n                                        _ => unreachable!(\"Cast to TEXT should yield Text\"),\n                                    },\n                                };\n\n                                let mut chars = escape_cow.chars();\n                                let c = chars.next();\n                                if c.is_none() || chars.next().is_some() {\n                                    return Err(LimboError::Constraint(\n                                        \"ESCAPE expression must be a single character\".to_string(),\n                                    ));\n                                }\n                                escape_char = c;\n                            }\n                        }\n                    }\n\n                    if is_null_result {\n                        state.registers[*dest].set_null();\n                    } else {\n                        // 3. Prepare Pattern and Text\n                        let pattern_cow = match pattern_value {\n                            Value::Text(s) => std::borrow::Cow::Borrowed(s.as_str()),\n                            v => match v.exec_cast(\"TEXT\") {\n                                Value::Text(s) => std::borrow::Cow::Owned(s.to_string()),\n                                _ => unreachable!(\"Cast to TEXT should yield Text\"),\n                            },\n                        };\n\n                        let match_cow = match match_value {\n                            Value::Text(s) => std::borrow::Cow::Borrowed(s.as_str()),\n                            v => match v.exec_cast(\"TEXT\") {\n                                Value::Text(s) => std::borrow::Cow::Owned(s.to_string()),\n                                _ => unreachable!(\"Cast to TEXT should yield Text\"),\n                            },\n                        };\n\n                        // 4. Execute Like\n                        let matches = Value::exec_like(&pattern_cow, &match_cow, escape_char)?;\n                        state.registers[*dest].set_int(matches as i64);\n                    }\n                }\n            }\n            ScalarFunc::Abs\n            | ScalarFunc::Lower\n            | ScalarFunc::Upper\n            | ScalarFunc::Length\n            | ScalarFunc::OctetLength\n            | ScalarFunc::Typeof\n            | ScalarFunc::Unicode\n            | ScalarFunc::Quote\n            | ScalarFunc::RandomBlob\n            | ScalarFunc::Sign\n            | ScalarFunc::Soundex\n            | ScalarFunc::ZeroBlob => {\n                let reg_value = state.registers[*start_reg].borrow_mut().get_value();\n                let result = match scalar_func {\n                    ScalarFunc::Sign => reg_value.exec_sign(),\n                    ScalarFunc::Abs => Some(reg_value.exec_abs()?),\n                    ScalarFunc::Lower => reg_value.exec_lower(),\n                    ScalarFunc::Upper => reg_value.exec_upper(),\n                    ScalarFunc::Length => Some(reg_value.exec_length()),\n                    ScalarFunc::OctetLength => Some(reg_value.exec_octet_length()),\n                    ScalarFunc::Typeof => Some(reg_value.exec_typeof()),\n                    ScalarFunc::Unicode => Some(reg_value.exec_unicode()),\n                    ScalarFunc::Quote => Some(reg_value.exec_quote()),\n                    ScalarFunc::RandomBlob => {\n                        Some(reg_value.exec_randomblob(|dest| pager.io.fill_bytes(dest))?)\n                    }\n                    ScalarFunc::ZeroBlob => Some(reg_value.exec_zeroblob()?),\n                    ScalarFunc::Soundex => Some(reg_value.exec_soundex()),\n                    _ => unreachable!(),\n                };\n                state.registers[*dest].set_value(result.unwrap_or(Value::Null));\n            }\n            ScalarFunc::Hex => {\n                let reg_value = state.registers[*start_reg].borrow_mut();\n                let result = reg_value.get_value().exec_hex();\n                state.registers[*dest].set_value(result);\n            }\n            ScalarFunc::Unhex => {\n                let reg_value = &state.registers[*start_reg];\n                let ignored_chars = if func.arg_count == 2 {\n                    state.registers.get(*start_reg + 1)\n                } else {\n                    None\n                };\n                let result = reg_value\n                    .get_value()\n                    .exec_unhex(ignored_chars.map(|x| x.get_value()));\n                state.registers[*dest].set_value(result);\n            }\n            ScalarFunc::Random => {\n                state.registers[*dest].set_int(pager.io.generate_random_number());\n            }\n            ScalarFunc::Trim => {\n                let reg_value = &state.registers[*start_reg];\n                let pattern_value = if func.arg_count == 2 {\n                    state.registers.get(*start_reg + 1)\n                } else {\n                    None\n                };\n                let result = reg_value\n                    .get_value()\n                    .exec_trim(pattern_value.map(|x| x.get_value()));\n                state.registers[*dest].set_value(result);\n            }\n            ScalarFunc::LTrim => {\n                let reg_value = &state.registers[*start_reg];\n                let pattern_value = if func.arg_count == 2 {\n                    state.registers.get(*start_reg + 1)\n                } else {\n                    None\n                };\n                let result = reg_value\n                    .get_value()\n                    .exec_ltrim(pattern_value.map(|x| x.get_value()));\n                state.registers[*dest].set_value(result);\n            }\n            ScalarFunc::RTrim => {\n                let reg_value = &state.registers[*start_reg];\n                let pattern_value = if func.arg_count == 2 {\n                    state.registers.get(*start_reg + 1)\n                } else {\n                    None\n                };\n                let result = reg_value\n                    .get_value()\n                    .exec_rtrim(pattern_value.map(|x| x.get_value()));\n                state.registers[*dest].set_value(result);\n            }\n            ScalarFunc::Round => {\n                let reg_value = &state.registers[*start_reg];\n                assert!(arg_count == 1 || arg_count == 2);\n                let precision_value = if arg_count > 1 {\n                    state.registers.get(*start_reg + 1)\n                } else {\n                    None\n                };\n                let result = reg_value\n                    .get_value()\n                    .exec_round(precision_value.map(|x| x.get_value()));\n                state.registers[*dest].set_value(result);\n            }\n            ScalarFunc::Min => {\n                let reg_values = &state.registers[*start_reg..*start_reg + arg_count];\n                state.registers[*dest]\n                    .set_value(Value::exec_min(reg_values.iter().map(|v| v.get_value())));\n            }\n            ScalarFunc::Max => {\n                let reg_values = &state.registers[*start_reg..*start_reg + arg_count];\n                state.registers[*dest]\n                    .set_value(Value::exec_max(reg_values.iter().map(|v| v.get_value())));\n            }\n            ScalarFunc::Nullif => {\n                let first_value = &state.registers[*start_reg];\n                let second_value = &state.registers[*start_reg + 1];\n                state.registers[*dest].set_value(Value::exec_nullif(\n                    first_value.get_value(),\n                    second_value.get_value(),\n                ));\n            }\n            ScalarFunc::Substr | ScalarFunc::Substring => {\n                let str_value = &state.registers[*start_reg];\n                let start_value = &state.registers[*start_reg + 1];\n                let length_value = if func.arg_count == 3 {\n                    Some(&state.registers[*start_reg + 2])\n                } else {\n                    None\n                };\n                let result = Value::exec_substring(\n                    str_value.get_value(),\n                    start_value.get_value(),\n                    length_value.map(|x| x.get_value()),\n                );\n                state.registers[*dest].set_value(result);\n            }\n            ScalarFunc::Date => {\n                let values =\n                    registers_to_ref_values(&state.registers[*start_reg..*start_reg + arg_count]);\n                let result = exec_date(values);\n                state.registers[*dest].set_value(result);\n            }\n            ScalarFunc::Time => {\n                let values =\n                    registers_to_ref_values(&state.registers[*start_reg..*start_reg + arg_count]);\n                let result = exec_time(values);\n                state.registers[*dest].set_value(result);\n            }\n            ScalarFunc::TimeDiff => {\n                if arg_count != 2 {\n                    state.registers[*dest].set_null();\n                } else {\n                    let start = state.registers[*start_reg].get_value();\n                    let end = state.registers[*start_reg + 1].get_value();\n\n                    let result = crate::functions::datetime::exec_timediff([start, end]);\n\n                    state.registers[*dest].set_value(result);\n                }\n            }\n            ScalarFunc::TotalChanges => {\n                let res = &program.connection.total_changes;\n                let total_changes = res.load(Ordering::SeqCst);\n                state.registers[*dest].set_int(total_changes);\n            }\n            ScalarFunc::DateTime => {\n                let values =\n                    registers_to_ref_values(&state.registers[*start_reg..*start_reg + arg_count]);\n                let result = exec_datetime_full(values);\n                state.registers[*dest].set_value(result);\n            }\n            ScalarFunc::JulianDay => {\n                let values =\n                    registers_to_ref_values(&state.registers[*start_reg..*start_reg + arg_count]);\n                let result = exec_julianday(values);\n                state.registers[*dest].set_value(result);\n            }\n            ScalarFunc::UnixEpoch => {\n                let values =\n                    registers_to_ref_values(&state.registers[*start_reg..*start_reg + arg_count]);\n                let result = exec_unixepoch(values);\n                state.registers[*dest].set_value(result);\n            }\n            ScalarFunc::TursoVersion => {\n                if !program.connection.is_db_initialized() {\n                    state.registers[*dest].set_text(Text::new(info::build::PKG_VERSION));\n                } else {\n                    let version_integer =\n                        return_if_io!(pager.with_header(|header| header.version_number)).get()\n                            as i64;\n                    let version = execute_turso_version(version_integer);\n                    state.registers[*dest].set_text(Text::new(version));\n                }\n            }\n            ScalarFunc::SqliteVersion => {\n                let version = execute_sqlite_version();\n                state.registers[*dest].set_text(Text::new(version));\n            }\n            ScalarFunc::SqliteSourceId => {\n                let src_id = format!(\n                    \"{} {}\",\n                    info::build::BUILT_TIME_SQLITE,\n                    info::build::GIT_COMMIT_HASH.unwrap_or(\"unknown\")\n                );\n                state.registers[*dest].set_text(Text::new(src_id));\n            }\n            ScalarFunc::Replace => {\n                assert_eq!(arg_count, 3);\n                let source = &state.registers[*start_reg];\n                let pattern = &state.registers[*start_reg + 1];\n                let replacement = &state.registers[*start_reg + 2];\n                state.registers[*dest].set_value(Value::exec_replace(\n                    source.get_value(),\n                    pattern.get_value(),\n                    replacement.get_value(),\n                ));\n            }\n            #[cfg(feature = \"fs\")]\n            #[cfg(not(target_family = \"wasm\"))]\n            ScalarFunc::LoadExtension => {\n                if !program.connection.db.can_load_extensions() {\n                    crate::bail_parse_error!(\"runtime extension loading is disabled\");\n                }\n                let extension = &state.registers[*start_reg];\n                let ext = resolve_ext_path(&extension.get_value().to_string())?;\n                program.connection.load_extension(ext)?;\n            }\n            ScalarFunc::StrfTime => {\n                let values =\n                    registers_to_ref_values(&state.registers[*start_reg..*start_reg + arg_count]);\n                let result = exec_strftime(values);\n                state.registers[*dest].set_value(result);\n            }\n            ScalarFunc::Printf => {\n                let result = exec_printf(&state.registers[*start_reg..*start_reg + arg_count])?;\n                state.registers[*dest].set_value(result);\n            }\n            ScalarFunc::TableColumnsJsonArray => {\n                assert_eq!(arg_count, 1);\n                #[cfg(not(feature = \"json\"))]\n                {\n                    return Err(LimboError::InvalidArgument(\n                        \"table_columns_json_array: turso must be compiled with JSON support\"\n                            .to_string(),\n                    ));\n                }\n                #[cfg(feature = \"json\")]\n                {\n                    use crate::types::TextSubtype;\n\n                    let table = state.registers[*start_reg].get_value();\n                    let Value::Text(table) = table else {\n                        return Err(LimboError::InvalidArgument(\n                            \"table_columns_json_array: function argument must be of type TEXT\"\n                                .to_string(),\n                        ));\n                    };\n                    let table = {\n                        let schema = program.connection.schema.read();\n                        match schema.get_table(table.as_str()) {\n                            Some(table) => table,\n                            None => {\n                                return Err(LimboError::InvalidArgument(format!(\n                                    \"table_columns_json_array: table {table} doesn't exists\"\n                                )));\n                            }\n                        }\n                    };\n\n                    let mut json = json::jsonb::Jsonb::make_empty_array(table.columns().len() * 10);\n                    for column in table.columns() {\n                        use crate::types::TextRef;\n\n                        let name = column.name.as_ref().expect(\"column should have a name\");\n                        let name_json = json::convert_ref_dbtype_to_jsonb(\n                            ValueRef::Text(TextRef::new(name, TextSubtype::Text)),\n                            json::Conv::ToString,\n                        )?;\n                        json.append_jsonb_to_end(name_json.data());\n                    }\n                    json.finalize_unsafe(json::jsonb::ElementType::ARRAY)?;\n                    state.registers[*dest].set_value(json::json_string_to_db_type(\n                        json,\n                        json::jsonb::ElementType::ARRAY,\n                        json::OutputVariant::String,\n                    )?);\n                }\n            }\n            ScalarFunc::BinRecordJsonObject => {\n                assert_eq!(arg_count, 2);\n                #[cfg(not(feature = \"json\"))]\n                {\n                    return Err(LimboError::InvalidArgument(\n                        \"bin_record_json_object: turso must be compiled with JSON support\"\n                            .to_string(),\n                    ));\n                }\n                #[cfg(feature = \"json\")]\n                'outer: {\n                    use crate::types::ValueIterator;\n                    use std::str::FromStr;\n\n                    let columns_str = state.registers[*start_reg].get_value();\n                    let bin_record = state.registers[*start_reg + 1].get_value();\n                    let Value::Text(columns_str) = columns_str else {\n                        return Err(LimboError::InvalidArgument(\n                            \"bin_record_json_object: function arguments must be of type TEXT and BLOB correspondingly\".to_string()\n                        ));\n                    };\n\n                    if let Value::Null = bin_record {\n                        state.registers[*dest].set_null();\n                        break 'outer;\n                    }\n\n                    let Value::Blob(bin_record) = bin_record else {\n                        return Err(LimboError::InvalidArgument(\n                            \"bin_record_json_object: function arguments must be of type TEXT and BLOB correspondingly\".to_string()\n                        ));\n                    };\n                    let mut columns_json_array =\n                        json::jsonb::Jsonb::from_str(columns_str.as_str())?;\n                    let columns_len = columns_json_array.array_len()?;\n\n                    let mut payload_iterator = ValueIterator::new(bin_record.as_slice())?;\n\n                    let mut json = json::jsonb::Jsonb::make_empty_obj(columns_len);\n                    for i in 0..columns_len {\n                        let mut op = json::jsonb::SearchOperation::new(0);\n                        let path = json::path::JsonPath {\n                            elements: vec![\n                                json::path::PathElement::Root(),\n                                json::path::PathElement::ArrayLocator(Some(i as i32)),\n                            ],\n                        };\n\n                        columns_json_array.operate_on_path(&path, &mut op)?;\n                        let column_name = op.result();\n                        json.append_jsonb_to_end(column_name.data());\n\n                        let val = match payload_iterator.next() {\n                            Some(Ok(v)) => v,\n                            Some(Err(e)) => return Err(e),\n                            None => {\n                                return Err(LimboError::InvalidArgument(\n                                    \"bin_record_json_object: binary record has fewer columns than specified in the columns argument\".to_string()\n                                ));\n                            }\n                        };\n\n                        if let ValueRef::Blob(..) = val {\n                            return Err(LimboError::InvalidArgument(\n                                \"bin_record_json_object: formatting of BLOB values stored in binary record is not supported\".to_string()\n                            ));\n                        }\n                        let val_json =\n                            json::convert_ref_dbtype_to_jsonb(val, json::Conv::NotStrict)?;\n                        json.append_jsonb_to_end(val_json.data());\n                    }\n                    json.finalize_unsafe(json::jsonb::ElementType::OBJECT)?;\n\n                    state.registers[*dest].set_value(json::json_string_to_db_type(\n                        json,\n                        json::jsonb::ElementType::OBJECT,\n                        json::OutputVariant::String,\n                    )?);\n                }\n            }\n            ScalarFunc::Attach => {\n                assert_eq!(arg_count, 3);\n                let filename = state.registers[*start_reg].get_value();\n                let dbname = state.registers[*start_reg + 1].get_value();\n                let _key = state.registers[*start_reg + 2].get_value(); // Not used in read-only implementation\n\n                let Value::Text(filename_str) = filename else {\n                    return Err(LimboError::InvalidArgument(\n                        \"attach: filename argument must be text\".to_string(),\n                    ));\n                };\n\n                let Value::Text(dbname_str) = dbname else {\n                    return Err(LimboError::InvalidArgument(\n                        \"attach: database name argument must be text\".to_string(),\n                    ));\n                };\n\n                program\n                    .connection\n                    .attach_database(filename_str.as_str(), dbname_str.as_str())?;\n\n                state.registers[*dest].set_null();\n            }\n            ScalarFunc::Detach => {\n                assert_eq!(arg_count, 1);\n                let dbname = state.registers[*start_reg].get_value();\n\n                let Value::Text(dbname_str) = dbname else {\n                    return Err(LimboError::InvalidArgument(\n                        \"detach: database name argument must be text\".to_string(),\n                    ));\n                };\n\n                // Call the detach_database method on the connection\n                program.connection.detach_database(dbname_str.as_str())?;\n\n                // Set result to NULL (detach doesn't return a value)\n                state.registers[*dest].set_null();\n            }\n            ScalarFunc::Unlikely | ScalarFunc::Likely | ScalarFunc::Likelihood => {\n                panic!(\n                    \"{scalar_func:?} should be stripped during expression translation and never reach VDBE\",\n                );\n            }\n            ScalarFunc::StatInit => {\n                // stat_init(n_col): Initialize a statistics accumulator\n                // Returns a blob containing the serialized StatAccum\n                assert!(arg_count >= 1);\n                let n_col = match state.registers[*start_reg].get_value() {\n                    Value::Numeric(Numeric::Integer(n)) => *n as usize,\n                    _ => 0,\n                };\n                let accum = StatAccum::new(n_col);\n                state.registers[*dest].set_blob(accum.to_bytes());\n            }\n            ScalarFunc::StatPush => {\n                // stat_push(accum_blob, i_chng): Push a row into the accumulator\n                // i_chng is the index of the leftmost column that changed from the previous row\n                // Returns the updated accumulator blob\n                assert!(arg_count >= 2);\n                let accum_blob = state.registers[*start_reg].get_value();\n                let i_chng = match state.registers[*start_reg + 1].get_value() {\n                    Value::Numeric(Numeric::Integer(n)) => *n as usize,\n                    _ => 0,\n                };\n                let result = match accum_blob {\n                    Value::Blob(bytes) => {\n                        if let Some(mut accum) = StatAccum::from_bytes(bytes) {\n                            accum.push(i_chng);\n                            Value::Blob(accum.to_bytes())\n                        } else {\n                            Value::Null\n                        }\n                    }\n                    _ => Value::Null,\n                };\n                state.registers[*dest].set_value(result);\n            }\n            ScalarFunc::StatGet => {\n                // stat_get(accum_blob): Get the stat1 string from the accumulator\n                // Returns the stat string \"total avg1 avg2 ...\"\n                assert!(arg_count >= 1);\n                let accum_blob = state.registers[*start_reg].get_value();\n                let result = match accum_blob {\n                    Value::Blob(bytes) => {\n                        if let Some(accum) = StatAccum::from_bytes(bytes) {\n                            let stat_str = accum.get_stat1();\n                            if stat_str.is_empty() {\n                                Value::Null\n                            } else {\n                                Value::build_text(stat_str)\n                            }\n                        } else {\n                            Value::Null\n                        }\n                    }\n                    _ => Value::Null,\n                };\n                state.registers[*dest].set_value(result);\n            }\n            ScalarFunc::ConnTxnId => {\n                // conn_txn_id(candidate): get-or-set semantics for CDC transaction ID.\n                // If unset (-1), store the candidate and return it.\n                // If already set, return the existing value, ignoring the candidate.\n                assert_eq!(arg_count, 1);\n                let candidate = match state.registers[*start_reg].get_value() {\n                    Value::Numeric(Numeric::Integer(n)) => *n,\n                    _ => -1,\n                };\n                let current = program.connection.get_cdc_transaction_id();\n                if current == -1 {\n                    program.connection.set_cdc_transaction_id(candidate);\n                    state.registers[*dest].set_int(candidate);\n                } else {\n                    state.registers[*dest].set_int(current);\n                }\n            }\n            ScalarFunc::IsAutocommit => {\n                // is_autocommit(): returns 1 if autocommit, 0 otherwise.\n                let auto_commit = program.connection.auto_commit.load(Ordering::SeqCst);\n                state.registers[*dest].set_int(if auto_commit { 1 } else { 0 });\n            }\n            ScalarFunc::TestUintEncode => {\n                check_arg_count!(arg_count, 1);\n                let val = &state.registers[*start_reg];\n                let result = match val.get_value() {\n                    Value::Null => Value::Null,\n                    Value::Numeric(Numeric::Integer(i)) => {\n                        if *i < 0 {\n                            return Err(LimboError::InternalError(\n                                \"test_uint_encode: negative value\".to_string(),\n                            ));\n                        }\n                        Value::build_text(i.to_string())\n                    }\n                    Value::Numeric(Numeric::Float(f)) => {\n                        if *f < 0.0 || f.fract() != 0.0 {\n                            return Err(LimboError::InternalError(\n                                \"test_uint_encode: not a non-negative integer\".to_string(),\n                            ));\n                        }\n                        Value::build_text((f64::from(*f) as u64).to_string())\n                    }\n                    Value::Text(t) => {\n                        let s = t.to_string();\n                        s.parse::<u64>().map_err(|_| {\n                            LimboError::InternalError(format!(\n                                \"test_uint_encode: invalid uint: {s}\"\n                            ))\n                        })?;\n                        Value::build_text(s)\n                    }\n                    _ => {\n                        return Err(LimboError::InternalError(\n                            \"test_uint_encode: unsupported type\".to_string(),\n                        ));\n                    }\n                };\n                state.registers[*dest].set_value(result);\n            }\n            ScalarFunc::TestUintDecode => {\n                check_arg_count!(arg_count, 1);\n                let val = &state.registers[*start_reg];\n                let result = match val.get_value() {\n                    Value::Null => Value::Null,\n                    other => other.clone(),\n                };\n                state.registers[*dest].set_value(result);\n            }\n            ScalarFunc::TestUintAdd\n            | ScalarFunc::TestUintSub\n            | ScalarFunc::TestUintMul\n            | ScalarFunc::TestUintDiv => {\n                check_arg_count!(arg_count, 2);\n                let a = parse_test_uint(&state.registers[*start_reg])?;\n                let b = parse_test_uint(&state.registers[*start_reg + 1])?;\n                let result = match (a, b) {\n                    (Some(a), Some(b)) => {\n                        let r = match scalar_func {\n                            ScalarFunc::TestUintAdd => a.checked_add(b),\n                            ScalarFunc::TestUintSub => a.checked_sub(b),\n                            ScalarFunc::TestUintMul => a.checked_mul(b),\n                            ScalarFunc::TestUintDiv => {\n                                if b == 0 {\n                                    None\n                                } else {\n                                    Some(a / b)\n                                }\n                            }\n                            _ => unreachable!(),\n                        };\n                        match r {\n                            Some(v) => Value::build_text(v.to_string()),\n                            None => {\n                                return Err(LimboError::InternalError(\n                                    \"test_uint arithmetic overflow/underflow\".to_string(),\n                                ));\n                            }\n                        }\n                    }\n                    _ => Value::Null,\n                };\n                state.registers[*dest].set_value(result);\n            }\n            ScalarFunc::TestUintLt | ScalarFunc::TestUintEq => {\n                check_arg_count!(arg_count, 2);\n                let a = parse_test_uint(&state.registers[*start_reg])?;\n                let b = parse_test_uint(&state.registers[*start_reg + 1])?;\n                let result = match (a, b) {\n                    (Some(a), Some(b)) => {\n                        let cmp = match scalar_func {\n                            ScalarFunc::TestUintLt => a < b,\n                            ScalarFunc::TestUintEq => a == b,\n                            _ => unreachable!(),\n                        };\n                        Value::from_i64(if cmp { 1 } else { 0 })\n                    }\n                    _ => Value::Null,\n                };\n                state.registers[*dest].set_value(result);\n            }\n            ScalarFunc::StringReverse => {\n                check_arg_count!(arg_count, 1);\n                let val = &state.registers[*start_reg];\n                let result = match val.get_value() {\n                    Value::Null => Value::Null,\n                    Value::Text(t) => {\n                        let reversed: String = t.to_string().chars().rev().collect();\n                        Value::build_text(reversed)\n                    }\n                    other => {\n                        let s = other.to_string();\n                        let reversed: String = s.chars().rev().collect();\n                        Value::build_text(reversed)\n                    }\n                };\n                state.registers[*dest].set_value(result);\n            }\n            ScalarFunc::BooleanToInt => {\n                check_arg_count!(arg_count, 1);\n                let val = &state.registers[*start_reg];\n                let result = match val.get_value() {\n                    Value::Null => Value::Null,\n                    Value::Numeric(Numeric::Integer(i)) => match *i {\n                        0 => Value::from_i64(0),\n                        1 => Value::from_i64(1),\n                        _ => {\n                            return Err(LimboError::Constraint(format!(\n                                \"invalid input for type boolean: \\\"{i}\\\"\"\n                            )));\n                        }\n                    },\n                    Value::Text(t) => {\n                        let v = &t.value;\n                        match v.to_ascii_lowercase().as_str() {\n                            \"true\" | \"t\" | \"yes\" | \"on\" | \"1\" => Value::from_i64(1),\n                            \"false\" | \"f\" | \"no\" | \"off\" | \"0\" => Value::from_i64(0),\n                            _ => {\n                                return Err(LimboError::Constraint(format!(\n                                    \"invalid input for type boolean: \\\"{v}\\\"\"\n                                )));\n                            }\n                        }\n                    }\n                    other => {\n                        return Err(LimboError::Constraint(format!(\n                            \"invalid input for type boolean: \\\"{other}\\\"\"\n                        )));\n                    }\n                };\n                state.registers[*dest].set_value(result);\n            }\n            ScalarFunc::IntToBoolean => {\n                check_arg_count!(arg_count, 1);\n                let val = &state.registers[*start_reg];\n                let result = match val.get_value() {\n                    Value::Null => Value::Null,\n                    Value::Numeric(Numeric::Integer(0)) => Value::build_text(\"false\".to_string()),\n                    _ => Value::build_text(\"true\".to_string()),\n                };\n                state.registers[*dest].set_value(result);\n            }\n            ScalarFunc::ValidateIpAddr => {\n                check_arg_count!(arg_count, 1);\n                let val = &state.registers[*start_reg];\n                let result = match val.get_value() {\n                    Value::Null => Value::Null,\n                    Value::Text(t) => {\n                        let v = &t.value;\n                        v.parse::<std::net::IpAddr>().map_err(|_| {\n                            LimboError::Constraint(format!(\"invalid input for type inet: \\\"{v}\\\"\"))\n                        })?;\n                        val.get_value().clone()\n                    }\n                    other => {\n                        return Err(LimboError::Constraint(format!(\n                            \"invalid input for type inet: \\\"{other}\\\"\"\n                        )));\n                    }\n                };\n                state.registers[*dest].set_value(result);\n            }\n            ScalarFunc::NumericEncode => {\n                check_arg_count!(arg_count, 3);\n                let val = &state.registers[*start_reg];\n                let precision_reg = &state.registers[*start_reg + 1];\n                let scale_reg = &state.registers[*start_reg + 2];\n                let result = match val.get_value() {\n                    Value::Null => Value::Null,\n                    other => {\n                        use crate::numeric::decimal::{\n                            bigdecimal_to_blob, validate_precision_scale,\n                        };\n                        use bigdecimal::BigDecimal;\n                        use std::str::FromStr;\n\n                        let precision = match precision_reg.get_value() {\n                            Value::Numeric(Numeric::Integer(i)) => *i,\n                            _ => {\n                                return Err(LimboError::Constraint(\n                                    \"numeric_encode: precision must be an integer\".to_string(),\n                                ));\n                            }\n                        };\n                        let scale = match scale_reg.get_value() {\n                            Value::Numeric(Numeric::Integer(i)) => *i,\n                            _ => {\n                                return Err(LimboError::Constraint(\n                                    \"numeric_encode: scale must be an integer\".to_string(),\n                                ));\n                            }\n                        };\n                        let text = match other {\n                            Value::Numeric(Numeric::Integer(i)) => i.to_string(),\n                            Value::Numeric(Numeric::Float(f)) => f.to_string(),\n                            Value::Text(t) => t.value.to_string(),\n                            _ => {\n                                return Err(LimboError::Constraint(format!(\n                                    \"invalid input for type numeric: \\\"{other}\\\"\"\n                                )));\n                            }\n                        };\n                        let bd = BigDecimal::from_str(&text).map_err(|_| {\n                            LimboError::Constraint(format!(\n                                \"invalid input for type numeric: \\\"{text}\\\"\"\n                            ))\n                        })?;\n                        let validated = validate_precision_scale(&bd, precision, scale)?;\n                        Value::from_blob(bigdecimal_to_blob(&validated))\n                    }\n                };\n                state.registers[*dest].set_value(result);\n            }\n            ScalarFunc::NumericDecode => {\n                check_arg_count!(arg_count, 1);\n                let val = &state.registers[*start_reg];\n                let result = match val.get_value() {\n                    Value::Null => Value::Null,\n                    Value::Blob(b) => {\n                        let bd = crate::numeric::decimal::blob_to_bigdecimal(b)?;\n                        Value::build_text(crate::numeric::decimal::format_numeric(&bd))\n                    }\n                    other => {\n                        return Err(LimboError::Constraint(format!(\n                            \"numeric_decode: expected blob, got \\\"{other}\\\"\"\n                        )));\n                    }\n                };\n                state.registers[*dest].set_value(result);\n            }\n            ScalarFunc::NumericAdd\n            | ScalarFunc::NumericSub\n            | ScalarFunc::NumericMul\n            | ScalarFunc::NumericDiv => {\n                check_arg_count!(arg_count, 2);\n                let lhs_val = state.registers[*start_reg].get_value().clone();\n                let rhs_val = state.registers[*start_reg + 1].get_value().clone();\n                let result = match (&lhs_val, &rhs_val) {\n                    (Value::Null, _) | (_, Value::Null) => Value::Null,\n                    _ => {\n                        let a = value_to_bigdecimal(&lhs_val)?;\n                        let b = value_to_bigdecimal(&rhs_val)?;\n                        let res = match scalar_func {\n                            ScalarFunc::NumericAdd => a + b,\n                            ScalarFunc::NumericSub => a - b,\n                            ScalarFunc::NumericMul => a * b,\n                            ScalarFunc::NumericDiv => {\n                                use bigdecimal::Zero;\n                                if b.is_zero() {\n                                    return Err(LimboError::Constraint(\n                                        \"division by zero\".to_string(),\n                                    ));\n                                }\n                                a / b\n                            }\n                            _ => unreachable!(),\n                        };\n                        Value::build_text(crate::numeric::decimal::format_numeric(&res))\n                    }\n                };\n                state.registers[*dest].set_value(result);\n            }\n            ScalarFunc::NumericLt | ScalarFunc::NumericEq => {\n                check_arg_count!(arg_count, 2);\n                let lhs_val = state.registers[*start_reg].get_value().clone();\n                let rhs_val = state.registers[*start_reg + 1].get_value().clone();\n                match (&lhs_val, &rhs_val) {\n                    (Value::Null, _) | (_, Value::Null) => state.registers[*dest].set_null(),\n                    _ => {\n                        let a = value_to_bigdecimal(&lhs_val)?;\n                        let b = value_to_bigdecimal(&rhs_val)?;\n                        let cmp_result = match scalar_func {\n                            ScalarFunc::NumericLt => a < b,\n                            ScalarFunc::NumericEq => a == b,\n                            _ => unreachable!(),\n                        };\n                        state.registers[*dest].set_int(cmp_result as i64)\n                    }\n                };\n            }\n            ScalarFunc::ArrayAppend => {\n                check_arg_count!(arg_count, 2);\n                let arr_val = state.registers[*start_reg].get_value().clone();\n                let elem_val = state.registers[*start_reg + 1].get_value().clone();\n                state.registers[*dest].set_value(exec_array_append(&arr_val, &elem_val));\n            }\n            ScalarFunc::ArrayPrepend => {\n                check_arg_count!(arg_count, 2);\n                let elem_val = state.registers[*start_reg].get_value().clone();\n                let arr_val = state.registers[*start_reg + 1].get_value().clone();\n                state.registers[*dest].set_value(exec_array_prepend(&arr_val, &elem_val));\n            }\n            ScalarFunc::ArrayCat => {\n                check_arg_count!(arg_count, 2);\n                let a_val = state.registers[*start_reg].get_value().clone();\n                let b_val = state.registers[*start_reg + 1].get_value().clone();\n                state.registers[*dest].set_value(exec_array_cat(&a_val, &b_val));\n            }\n            ScalarFunc::ArrayRemove => {\n                check_arg_count!(arg_count, 2);\n                let arr_val = state.registers[*start_reg].get_value().clone();\n                let target = state.registers[*start_reg + 1].get_value().clone();\n                state.registers[*dest].set_value(exec_array_remove(&arr_val, &target));\n            }\n            ScalarFunc::ArrayContains => {\n                check_arg_count!(arg_count, 2);\n                let arr_val = state.registers[*start_reg].get_value().clone();\n                let target = state.registers[*start_reg + 1].get_value().clone();\n                state.registers[*dest].set_value(exec_array_contains(&arr_val, &target));\n            }\n            ScalarFunc::ArrayPosition => {\n                check_arg_count!(arg_count, 2);\n                let arr_val = state.registers[*start_reg].get_value().clone();\n                let target = state.registers[*start_reg + 1].get_value().clone();\n                state.registers[*dest].set_value(exec_array_position(&arr_val, &target));\n            }\n            ScalarFunc::ArrayLength => {\n                // Accept 1 or 2 args; dimension arg (PG compat) ignored for 1D arrays\n                let arr_val = state.registers[*start_reg].get_value();\n                match compute_array_length(arr_val) {\n                    Some(count) => state.registers[*dest].set_int(count),\n                    None => state.registers[*dest].set_null(),\n                };\n            }\n            ScalarFunc::ArraySlice => {\n                check_arg_count!(arg_count, 3);\n                let arr_val = state.registers[*start_reg].get_value().clone();\n                let start_idx = state.registers[*start_reg + 1].get_value().clone();\n                let end_idx = state.registers[*start_reg + 2].get_value().clone();\n                let result = exec_array_slice(&arr_val, &start_idx, &end_idx);\n                state.registers[*dest].set_value(result);\n            }\n            ScalarFunc::StringToArray => {\n                let text = state.registers[*start_reg].get_value().clone();\n                let delimiter = state.registers[*start_reg + 1].get_value().clone();\n                let null_str = if arg_count >= 3 {\n                    Some(state.registers[*start_reg + 2].get_value().clone())\n                } else {\n                    None\n                };\n                state.registers[*dest].set_value(exec_string_to_array(\n                    &text,\n                    &delimiter,\n                    null_str.as_ref(),\n                ));\n            }\n            ScalarFunc::ArrayToString => {\n                let arr_val = state.registers[*start_reg].get_value().clone();\n                let delimiter = state.registers[*start_reg + 1].get_value().clone();\n                let null_str = if arg_count >= 3 {\n                    Some(state.registers[*start_reg + 2].get_value().clone())\n                } else {\n                    None\n                };\n                state.registers[*dest].set_value(exec_array_to_string(\n                    &arr_val,\n                    &delimiter,\n                    null_str.as_ref(),\n                ));\n            }\n            ScalarFunc::ArrayOverlap => {\n                check_arg_count!(arg_count, 2);\n                let a_val = state.registers[*start_reg].get_value().clone();\n                let b_val = state.registers[*start_reg + 1].get_value().clone();\n                state.registers[*dest].set_value(exec_array_overlap(&a_val, &b_val));\n            }\n            ScalarFunc::ArrayContainsAll => {\n                check_arg_count!(arg_count, 2);\n                let a_val = state.registers[*start_reg].get_value().clone();\n                let b_val = state.registers[*start_reg + 1].get_value().clone();\n                state.registers[*dest].set_value(exec_array_contains_all(&a_val, &b_val));\n            }\n        },\n        crate::function::Func::Vector(vector_func) => {\n            let args = &state.registers[*start_reg..*start_reg + arg_count];\n            match vector_func {\n                VectorFunc::Vector => {\n                    let result = vector32(args)?;\n                    state.registers[*dest].set_value(result);\n                }\n                VectorFunc::Vector32 => {\n                    let result = vector32(args)?;\n                    state.registers[*dest].set_value(result);\n                }\n                VectorFunc::Vector32Sparse => {\n                    let result = vector32_sparse(args)?;\n                    state.registers[*dest].set_value(result);\n                }\n                VectorFunc::Vector64 => {\n                    let result = vector64(args)?;\n                    state.registers[*dest].set_value(result);\n                }\n                VectorFunc::Vector8 => {\n                    let result = vector8(args)?;\n                    state.registers[*dest].set_value(result);\n                }\n                VectorFunc::Vector1Bit => {\n                    let result = vector1bit(args)?;\n                    state.registers[*dest].set_value(result);\n                }\n                VectorFunc::VectorExtract => {\n                    let result = vector_extract(args)?;\n                    state.registers[*dest].set_value(result);\n                }\n                VectorFunc::VectorDistanceCos => {\n                    let result = vector_distance_cos(args)?;\n                    state.registers[*dest].set_value(result);\n                }\n                VectorFunc::VectorDistanceDot => {\n                    let result = vector_distance_dot(args)?;\n                    state.registers[*dest].set_value(result);\n                }\n                VectorFunc::VectorDistanceL2 => {\n                    let result = vector_distance_l2(args)?;\n                    state.registers[*dest].set_value(result);\n                }\n                VectorFunc::VectorDistanceJaccard => {\n                    let result = vector_distance_jaccard(args)?;\n                    state.registers[*dest].set_value(result);\n                }\n                VectorFunc::VectorConcat => {\n                    let result = vector_concat(args)?;\n                    state.registers[*dest].set_value(result);\n                }\n                VectorFunc::VectorSlice => {\n                    let result = vector_slice(args)?;\n                    state.registers[*dest].set_value(result)\n                }\n            }\n        }\n        crate::function::Func::External(f) => match f.func {\n            ExtFunc::Scalar(f) => {\n                if arg_count == 0 {\n                    let result_c_value: ExtValue = unsafe { (f)(0, std::ptr::null()) };\n                    match Value::from_ffi(result_c_value) {\n                        Ok(result_ov) => {\n                            state.registers[*dest].set_value(result_ov);\n                        }\n                        Err(e) => {\n                            return Err(e);\n                        }\n                    }\n                } else {\n                    let register_slice = &state.registers[*start_reg..*start_reg + arg_count];\n                    let mut ext_values: Vec<ExtValue> = Vec::with_capacity(arg_count);\n                    for ov in register_slice.iter() {\n                        let val = ov.get_value().to_ffi();\n                        ext_values.push(val);\n                    }\n                    let argv_ptr = ext_values.as_ptr();\n                    let result_c_value: ExtValue = unsafe { (f)(arg_count as i32, argv_ptr) };\n                    match Value::from_ffi(result_c_value) {\n                        Ok(result_ov) => {\n                            state.registers[*dest].set_value(result_ov);\n                        }\n                        Err(e) => {\n                            return Err(e);\n                        }\n                    }\n                }\n            }\n            _ => unreachable!(\"aggregate called in scalar context\"),\n        },\n        crate::function::Func::Math(math_func) => match math_func.arity() {\n            MathFuncArity::Nullary => match math_func {\n                MathFunc::Pi => {\n                    state.registers[*dest].set_float(\n                        NonNan::new(std::f64::consts::PI).expect(\"PI is a valid NonNan\"),\n                    );\n                }\n                _ => {\n                    unreachable!(\"Unexpected mathematical Nullary function {:?}\", math_func);\n                }\n            },\n\n            MathFuncArity::Unary => {\n                let reg_value = &state.registers[*start_reg];\n                let result = reg_value.get_value().exec_math_unary(math_func);\n                state.registers[*dest].set_value(result);\n            }\n\n            MathFuncArity::Binary => {\n                let lhs = &state.registers[*start_reg];\n                let rhs = &state.registers[*start_reg + 1];\n                let result = lhs.get_value().exec_math_binary(rhs.get_value(), math_func);\n                state.registers[*dest].set_value(result);\n            }\n\n            MathFuncArity::UnaryOrBinary => match math_func {\n                MathFunc::Log => {\n                    let result = match arg_count {\n                        1 => {\n                            let arg = &state.registers[*start_reg];\n                            arg.get_value().exec_math_log(None)\n                        }\n                        2 => {\n                            let base = &state.registers[*start_reg];\n                            let arg = &state.registers[*start_reg + 1];\n                            arg.get_value().exec_math_log(Some(base.get_value()))\n                        }\n                        _ => unreachable!(\n                            \"{:?} function with unexpected number of arguments\",\n                            math_func\n                        ),\n                    };\n                    state.registers[*dest].set_value(result);\n                }\n                _ => unreachable!(\n                    \"Unexpected mathematical UnaryOrBinary function {:?}\",\n                    math_func\n                ),\n            },\n        },\n        crate::function::Func::AlterTable(alter_func) => {\n            let r#type = &state.registers[*start_reg].get_value().clone();\n\n            let Value::Text(name) = &state.registers[*start_reg + 1].get_value() else {\n                panic!(\"sqlite_schema.name should be TEXT\")\n            };\n            let name = name.to_string();\n\n            let Value::Text(tbl_name) = &state.registers[*start_reg + 2].get_value() else {\n                panic!(\"sqlite_schema.tbl_name should be TEXT\")\n            };\n            let tbl_name = tbl_name.to_string();\n\n            let Value::Numeric(Numeric::Integer(root_page)) =\n                &state.registers[*start_reg + 3].get_value().clone()\n            else {\n                panic!(\"sqlite_schema.root_page should be INTEGER\")\n            };\n\n            let sql = &state.registers[*start_reg + 4].get_value().clone();\n\n            let (new_name, new_tbl_name, new_sql) = match alter_func {\n                AlterTableFunc::RenameTable => {\n                    let rename_from = {\n                        match &state.registers[*start_reg + 5].get_value() {\n                            Value::Text(rename_from) => normalize_ident(rename_from.as_str()),\n                            _ => panic!(\"rename_from parameter should be TEXT\"),\n                        }\n                    };\n\n                    let original_rename_to = {\n                        match &state.registers[*start_reg + 6].get_value() {\n                            Value::Text(rename_to) => rename_to,\n                            _ => panic!(\"rename_to parameter should be TEXT\"),\n                        }\n                    };\n                    let rename_to = normalize_ident(original_rename_to.as_str());\n\n                    let new_name = if let Some(column) =\n                        &name.strip_prefix(&format!(\"sqlite_autoindex_{rename_from}_\"))\n                    {\n                        format!(\"sqlite_autoindex_{rename_to}_{column}\")\n                    } else if name == rename_from {\n                        rename_to.clone()\n                    } else {\n                        name\n                    };\n\n                    let new_tbl_name = if tbl_name == rename_from {\n                        rename_to.clone()\n                    } else {\n                        tbl_name\n                    };\n\n                    let new_sql = 'sql: {\n                        let Value::Text(sql) = sql else {\n                            break 'sql None;\n                        };\n\n                        let mut parser = Parser::new(sql.as_str().as_bytes());\n                        let ast::Cmd::Stmt(stmt) =\n                            parser.next().expect(\"parser should have next item\")?\n                        else {\n                            return Err(LimboError::InternalError(\n                                \"Unexpected command during ALTER TABLE RENAME processing\"\n                                    .to_string(),\n                            ));\n                        };\n\n                        match stmt {\n                            ast::Stmt::CreateIndex {\n                                tbl_name,\n                                unique,\n                                if_not_exists,\n                                idx_name,\n                                columns,\n                                where_clause,\n                                using,\n                                with_clause,\n                            } => {\n                                let table_name = normalize_ident(tbl_name.as_str());\n\n                                if rename_from != table_name {\n                                    break 'sql None;\n                                }\n\n                                Some(\n                                    ast::Stmt::CreateIndex {\n                                        tbl_name: ast::Name::exact(original_rename_to.to_string()),\n                                        unique,\n                                        if_not_exists,\n                                        idx_name,\n                                        columns,\n                                        where_clause,\n                                        using,\n                                        with_clause,\n                                    }\n                                    .to_string(),\n                                )\n                            }\n                            ast::Stmt::CreateTable {\n                                tbl_name,\n                                temporary,\n                                if_not_exists,\n                                body,\n                            } => {\n                                let this_table = normalize_ident(tbl_name.name.as_str());\n\n                                let ast::CreateTableBody::ColumnsAndConstraints {\n                                    mut columns,\n                                    mut constraints,\n                                    options,\n                                } = body\n                                else {\n                                    return Err(LimboError::InternalError(\n                                        \"CREATE TABLE AS SELECT schemas cannot be altered\"\n                                            .to_string(),\n                                    ));\n                                };\n\n                                let mut any_change = false;\n\n                                // Rewrite FK targets in both paths\n                                for c in &mut constraints {\n                                    if let ast::TableConstraint::ForeignKey { clause, .. } =\n                                        &mut c.constraint\n                                    {\n                                        any_change |= rewrite_fk_parent_table_if_needed(\n                                            clause,\n                                            &rename_from,\n                                            original_rename_to.as_str(),\n                                        );\n                                    }\n                                }\n                                for col in &mut columns {\n                                    any_change |= rewrite_inline_col_fk_target_if_needed(\n                                        col,\n                                        &rename_from,\n                                        original_rename_to.as_str(),\n                                    );\n                                }\n\n                                // Rewrite table-qualified refs in CHECK constraints\n                                // (e.g. t1.a > 0 → t2.a > 0)\n                                if this_table == rename_from {\n                                    for c in &mut constraints {\n                                        if let ast::TableConstraint::Check(ref mut expr) =\n                                            c.constraint\n                                        {\n                                            rewrite_check_expr_table_refs(\n                                                expr,\n                                                &rename_from,\n                                                &rename_to,\n                                            );\n                                        }\n                                    }\n                                    for col in &mut columns {\n                                        for cc in &mut col.constraints {\n                                            if let ast::ColumnConstraint::Check(ref mut expr) =\n                                                cc.constraint\n                                            {\n                                                rewrite_check_expr_table_refs(\n                                                    expr,\n                                                    &rename_from,\n                                                    &rename_to,\n                                                );\n                                            }\n                                        }\n                                    }\n                                }\n\n                                if this_table == rename_from {\n                                    // Rebuild with new table identifier so SQL persists the new name.\n                                    let new_stmt = ast::Stmt::CreateTable {\n                                        tbl_name: ast::QualifiedName {\n                                            db_name: None,\n                                            name: ast::Name::exact(original_rename_to.to_string()),\n                                            alias: None,\n                                        },\n                                        temporary,\n                                        if_not_exists,\n                                        body: ast::CreateTableBody::ColumnsAndConstraints {\n                                            columns,\n                                            constraints,\n                                            options,\n                                        },\n                                    };\n                                    Some(new_stmt.to_string())\n                                } else {\n                                    // Other tables: only emit if we actually changed their FK targets.\n                                    if !any_change {\n                                        break 'sql None;\n                                    }\n                                    Some(\n                                        ast::Stmt::CreateTable {\n                                            tbl_name,\n                                            temporary,\n                                            if_not_exists,\n                                            body: ast::CreateTableBody::ColumnsAndConstraints {\n                                                columns,\n                                                constraints,\n                                                options,\n                                            },\n                                        }\n                                        .to_string(),\n                                    )\n                                }\n                            }\n                            ast::Stmt::CreateVirtualTable(ast::CreateVirtualTable {\n                                tbl_name,\n                                if_not_exists,\n                                module_name,\n                                args,\n                            }) => {\n                                let this_table = normalize_ident(tbl_name.name.as_str());\n                                if this_table != rename_from {\n                                    None\n                                } else {\n                                    let new_stmt =\n                                        ast::Stmt::CreateVirtualTable(ast::CreateVirtualTable {\n                                            tbl_name: ast::QualifiedName {\n                                                db_name: tbl_name.db_name,\n                                                name: ast::Name::exact(\n                                                    original_rename_to.to_string(),\n                                                ),\n                                                alias: None,\n                                            },\n                                            if_not_exists,\n                                            module_name,\n                                            args,\n                                        });\n                                    Some(new_stmt.to_string())\n                                }\n                            }\n                            ast::Stmt::CreateTrigger {\n                                temporary,\n                                if_not_exists,\n                                trigger_name,\n                                time,\n                                event,\n                                tbl_name: trigger_tbl_name,\n                                for_each_row,\n                                mut when_clause,\n                                mut commands,\n                            } => {\n                                let trigger_tbl = normalize_ident(trigger_tbl_name.name.as_str());\n\n                                // Rewrite ON table name if it matches the renamed table\n                                let new_trigger_tbl_name = if trigger_tbl == rename_from {\n                                    ast::QualifiedName {\n                                        db_name: trigger_tbl_name.db_name,\n                                        name: ast::Name::exact(original_rename_to.to_string()),\n                                        alias: None,\n                                    }\n                                } else {\n                                    trigger_tbl_name\n                                };\n\n                                // Rewrite WHEN clause qualified refs\n                                if let Some(ref mut when) = when_clause {\n                                    rewrite_check_expr_table_refs(\n                                        when,\n                                        &rename_from,\n                                        original_rename_to.as_str(),\n                                    );\n                                }\n\n                                // Rewrite table references in trigger body commands\n                                for cmd in &mut commands {\n                                    rewrite_trigger_cmd_table_refs(\n                                        cmd,\n                                        &rename_from,\n                                        original_rename_to.as_str(),\n                                    );\n                                }\n\n                                Some(\n                                    ast::Stmt::CreateTrigger {\n                                        temporary,\n                                        if_not_exists,\n                                        trigger_name,\n                                        time,\n                                        event,\n                                        tbl_name: new_trigger_tbl_name,\n                                        for_each_row,\n                                        when_clause,\n                                        commands,\n                                    }\n                                    .to_string(),\n                                )\n                            }\n                            _ => None,\n                        }\n                    };\n\n                    (new_name, new_tbl_name, new_sql)\n                }\n                AlterTableFunc::AlterColumn | AlterTableFunc::RenameColumn => {\n                    let table = {\n                        match &state.registers[*start_reg + 5].get_value() {\n                            Value::Text(rename_to) => normalize_ident(rename_to.as_str()),\n                            _ => panic!(\"table parameter should be TEXT\"),\n                        }\n                    };\n\n                    let original_rename_from = {\n                        match &state.registers[*start_reg + 6].get_value() {\n                            Value::Text(rename_from) => rename_from,\n                            _ => panic!(\"rename_from parameter should be TEXT\"),\n                        }\n                    };\n                    let rename_from = normalize_ident(original_rename_from.as_str());\n\n                    let column_def = {\n                        match &state.registers[*start_reg + 7].get_value() {\n                            Value::Text(column_def) => column_def.as_str(),\n                            _ => panic!(\"rename_to parameter should be TEXT\"),\n                        }\n                    };\n\n                    let column_def =\n                        Parser::new(column_def.as_bytes()).parse_column_definition(true)?;\n\n                    let _rename_to = normalize_ident(column_def.col_name.as_str());\n\n                    let new_sql = 'sql: {\n                        let Value::Text(sql) = sql else {\n                            break 'sql None;\n                        };\n\n                        let mut parser = Parser::new(sql.as_str().as_bytes());\n                        let ast::Cmd::Stmt(stmt) =\n                            parser.next().expect(\"parser should have next item\")?\n                        else {\n                            return Err(LimboError::InternalError(\n                                \"Unexpected command during ALTER TABLE RENAME COLUMN processing\"\n                                    .to_string(),\n                            ));\n                        };\n\n                        match stmt {\n                            ast::Stmt::CreateIndex {\n                                tbl_name,\n                                mut columns,\n                                unique,\n                                if_not_exists,\n                                idx_name,\n                                mut where_clause,\n                                using,\n                                with_clause,\n                            } => {\n                                if table != normalize_ident(tbl_name.as_str()) {\n                                    break 'sql None;\n                                }\n\n                                for column in &mut columns {\n                                    match column.expr.as_mut() {\n                                        ast::Expr::Id(id)\n                                            if normalize_ident(id.as_str()) == rename_from =>\n                                        {\n                                            *id = Name::exact(\n                                                column_def.col_name.as_str().to_owned(),\n                                            );\n                                        }\n                                        _ => {}\n                                    }\n                                }\n\n                                if let Some(ref mut wc) = where_clause {\n                                    rename_identifiers(\n                                        wc,\n                                        &rename_from,\n                                        column_def.col_name.as_str(),\n                                    );\n                                }\n\n                                Some(\n                                    ast::Stmt::CreateIndex {\n                                        tbl_name,\n                                        columns,\n                                        unique,\n                                        if_not_exists,\n                                        idx_name,\n                                        where_clause,\n                                        using,\n                                        with_clause,\n                                    }\n                                    .to_string(),\n                                )\n                            }\n                            ast::Stmt::CreateTable {\n                                tbl_name,\n                                body,\n                                temporary,\n                                if_not_exists,\n                            } => {\n                                let ast::CreateTableBody::ColumnsAndConstraints {\n                                    mut columns,\n                                    mut constraints,\n                                    options,\n                                } = body\n                                else {\n                                    return Err(LimboError::InternalError(\n                                        \"CREATE TABLE AS SELECT schemas cannot be altered\"\n                                            .to_string(),\n                                    ));\n                                };\n\n                                let normalized_tbl_name = normalize_ident(tbl_name.name.as_str());\n\n                                if normalized_tbl_name == table {\n                                    // This is the table being altered - update its column\n                                    let column = columns\n                                        .iter_mut()\n                                        .find(|column| {\n                                            normalize_ident(column.col_name.as_str()) == rename_from\n                                        })\n                                        .expect(\"column being renamed should be present\");\n\n                                    match alter_func {\n                                        AlterTableFunc::AlterColumn => *column = column_def.clone(),\n                                        AlterTableFunc::RenameColumn => {\n                                            column.col_name = column_def.col_name.clone()\n                                        }\n                                        _ => unreachable!(),\n                                    }\n\n                                    // Update table-level constraints (PRIMARY KEY, UNIQUE, FOREIGN KEY)\n                                    for constraint in &mut constraints {\n                                        match &mut constraint.constraint {\n                                            ast::TableConstraint::PrimaryKey {\n                                                columns: pk_cols,\n                                                ..\n                                            } => {\n                                                for col in pk_cols {\n                                                    let (ast::Expr::Name(ref name)\n                                                    | ast::Expr::Id(ref name)) = *col.expr\n                                                    else {\n                                                        return Err(LimboError::ParseError(\"Unexpected expression in PRIMARY KEY constraint\".to_string()));\n                                                    };\n                                                    if normalize_ident(name.as_str()) == rename_from\n                                                    {\n                                                        *col.expr = ast::Expr::Name(Name::exact(\n                                                            column_def.col_name.as_str().to_owned(),\n                                                        ));\n                                                    }\n                                                }\n                                            }\n                                            ast::TableConstraint::Unique {\n                                                columns: uniq_cols,\n                                                ..\n                                            } => {\n                                                for col in uniq_cols {\n                                                    let (ast::Expr::Name(ref name)\n                                                    | ast::Expr::Id(ref name)) = *col.expr\n                                                    else {\n                                                        return Err(LimboError::ParseError(\"Unexpected expression in UNIQUE constraint\".to_string()));\n                                                    };\n                                                    if normalize_ident(name.as_str()) == rename_from\n                                                    {\n                                                        *col.expr = ast::Expr::Name(Name::exact(\n                                                            column_def.col_name.as_str().to_owned(),\n                                                        ));\n                                                    }\n                                                }\n                                            }\n                                            ast::TableConstraint::ForeignKey {\n                                                columns: child_cols,\n                                                clause,\n                                                ..\n                                            } => {\n                                                // Update child columns in this table's FK definitions\n                                                for child_col in child_cols {\n                                                    if normalize_ident(child_col.col_name.as_str())\n                                                        == rename_from\n                                                    {\n                                                        child_col.col_name = Name::exact(\n                                                            column_def.col_name.as_str().to_owned(),\n                                                        );\n                                                    }\n                                                }\n                                                rewrite_fk_parent_cols_if_self_ref(\n                                                    clause,\n                                                    &normalized_tbl_name,\n                                                    &rename_from,\n                                                    column_def.col_name.as_str(),\n                                                );\n                                            }\n                                            ast::TableConstraint::Check(ref mut expr) => {\n                                                rename_identifiers(\n                                                    expr,\n                                                    &rename_from,\n                                                    column_def.col_name.as_str(),\n                                                );\n                                            }\n                                        }\n                                    }\n\n                                    for col in &mut columns {\n                                        rewrite_column_references_if_needed(\n                                            col,\n                                            &normalized_tbl_name,\n                                            &rename_from,\n                                            column_def.col_name.as_str(),\n                                        );\n                                    }\n                                } else {\n                                    // This is a different table, check if it has FKs referencing the renamed column\n                                    let mut fk_updated = false;\n\n                                    for constraint in &mut constraints {\n                                        if let ast::TableConstraint::ForeignKey {\n                                            columns: _,\n                                            clause:\n                                                ForeignKeyClause {\n                                                    tbl_name,\n                                                    columns: parent_cols,\n                                                    ..\n                                                },\n                                            ..\n                                        } = &mut constraint.constraint\n                                        {\n                                            // Check if this FK references the table being altered\n                                            if normalize_ident(tbl_name.as_str()) == table {\n                                                // Update parent column references if they match the renamed column\n                                                for parent_col in parent_cols {\n                                                    if normalize_ident(parent_col.col_name.as_str())\n                                                        == rename_from\n                                                    {\n                                                        parent_col.col_name = Name::exact(\n                                                            column_def.col_name.as_str().to_owned(),\n                                                        );\n                                                        fk_updated = true;\n                                                    }\n                                                }\n                                            }\n                                        }\n                                    }\n                                    for col in &mut columns {\n                                        let _before = fk_updated;\n                                        let mut local_col = col.clone();\n                                        rewrite_column_references_if_needed(\n                                            &mut local_col,\n                                            &table,\n                                            &rename_from,\n                                            column_def.col_name.as_str(),\n                                        );\n                                        if local_col != *col {\n                                            *col = local_col;\n                                            fk_updated = true;\n                                        }\n                                    }\n\n                                    // Only return updated SQL if we actually changed something\n                                    if !fk_updated {\n                                        break 'sql None;\n                                    }\n                                }\n                                Some(\n                                    ast::Stmt::CreateTable {\n                                        tbl_name,\n                                        body: ast::CreateTableBody::ColumnsAndConstraints {\n                                            columns,\n                                            constraints,\n                                            options,\n                                        },\n                                        temporary,\n                                        if_not_exists,\n                                    }\n                                    .to_string(),\n                                )\n                            }\n                            // Trigger SQL is rewritten by separate UPDATE statements\n                            // generated by alter.rs (via rewrite_trigger_sql_for_column_rename),\n                            // so we skip triggers here to avoid redundant work.\n                            _ => None,\n                        }\n                    };\n\n                    (name, tbl_name, new_sql)\n                }\n            };\n\n            state.registers[*dest].set_value(r#type.clone());\n            state.registers[*dest + 1].set_text(Text::from(new_name));\n            state.registers[*dest + 2].set_text(Text::from(new_tbl_name));\n            state.registers[*dest + 3].set_int(*root_page);\n\n            if let Some(new_sql) = new_sql {\n                state.registers[*dest + 4].set_text(Text::from(new_sql));\n            } else {\n                state.registers[*dest + 4].set_value(sql.clone());\n            }\n        }\n        #[cfg(all(feature = \"fts\", not(target_family = \"wasm\")))]\n        crate::function::Func::Fts(fts_func) => {\n            // FTS functions are typically handled via index method pattern matching.\n            // If we reach here, just return a fallback since no FTS index matched.\n            use crate::function::FtsFunc;\n            match fts_func {\n                FtsFunc::Score => {\n                    // Without an FTS index match, return 0.0 as a default score\n                    state.registers[*dest]\n                        .set_float(NonNan::new(0.0).expect(\"0.0 is a valid NonNan\"));\n                }\n                FtsFunc::Match => {\n                    // fts_match(col1, col2, ..., query): returns 1 if any column matches query\n                    // Minimum: fts_match(text, query) = 2 args\n                    if arg_count < 2 {\n                        return Err(LimboError::InvalidArgument(\n                            \"fts_match requires at least 2 arguments: text, query\".to_string(),\n                        ));\n                    }\n\n                    // Last arg is the query, first N-1 args are text columns\n                    let num_text_cols = arg_count - 1;\n                    let query = state.registers[*start_reg + num_text_cols].get_value();\n\n                    if matches!(query, Value::Null) {\n                        state.registers[*dest].set_int(0);\n                    } else {\n                        let query_str = query.to_string();\n\n                        // Concatenate all text columns with space separator\n                        let est_len = 16;\n                        let mut combined_text = String::with_capacity(num_text_cols * est_len);\n                        for i in 0..num_text_cols {\n                            let text = state.registers[*start_reg + i].get_value();\n                            if !matches!(text, Value::Null) {\n                                if !combined_text.is_empty() {\n                                    combined_text.push(' ');\n                                }\n                                combined_text.push_str(&text.to_string());\n                            }\n                        }\n\n                        let matches =\n                            crate::index_method::fts::fts_match(&combined_text, &query_str);\n                        state.registers[*dest].set_int(matches.into());\n                    }\n                }\n                FtsFunc::Highlight => {\n                    // fts_highlight(col1, col2, ..., before_tag, after_tag, query)\n                    // Variable number of text columns, followed by before_tag, after_tag, query\n                    // Minimum: fts_highlight(text, before_tag, after_tag, query) = 4 args\n                    if arg_count < 4 {\n                        return Err(LimboError::InvalidArgument(\n                            \"fts_highlight requires at least 4 arguments: text, before_tag, after_tag, query\"\n                                .to_string(),\n                        ));\n                    }\n\n                    // Last 3 args are: before_tag, after_tag, query\n                    // First N-3 args are text columns\n                    let num_text_cols = arg_count - 3;\n                    let before_tag = state.registers[*start_reg + num_text_cols].get_value();\n                    let after_tag = state.registers[*start_reg + num_text_cols + 1].get_value();\n                    let query = state.registers[*start_reg + num_text_cols + 2].get_value();\n\n                    // Handle NULL values in tags or query\n                    if matches!(query, Value::Null)\n                        || matches!(before_tag, Value::Null)\n                        || matches!(after_tag, Value::Null)\n                    {\n                        state.registers[*dest].set_null();\n                    } else {\n                        let query_str = query.to_string();\n                        let before_str = before_tag.to_string();\n                        let after_str = after_tag.to_string();\n\n                        // Concatenate all text columns with space separator\n                        let mut combined_text = String::new();\n                        for i in 0..num_text_cols {\n                            let text = state.registers[*start_reg + i].get_value();\n                            if !matches!(text, Value::Null) {\n                                if !combined_text.is_empty() {\n                                    combined_text.push(' ');\n                                }\n                                combined_text.push_str(&text.to_string());\n                            }\n                        }\n\n                        let highlighted = crate::index_method::fts::fts_highlight(\n                            &combined_text,\n                            &query_str,\n                            &before_str,\n                            &after_str,\n                        );\n                        state.registers[*dest].set_text(Text::new(highlighted));\n                    }\n                }\n            }\n        }\n        crate::function::Func::Agg(_) => {\n            unreachable!(\"Aggregate functions should not be handled here\")\n        }\n        crate::function::Func::Window(_) => {\n            unreachable!(\"Window functions should not be handled here\")\n        }\n    }\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_sequence(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        Sequence {\n            cursor_id,\n            target_reg\n        },\n        insn\n    );\n    let cursor_seq = state\n        .cursor_seqs\n        .get_mut(*cursor_id)\n        .expect(\"cursor_id should be valid\");\n    let seq_num = *cursor_seq;\n    *cursor_seq += 1;\n    state.registers[*target_reg].set_int(seq_num);\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_sequence_test(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        SequenceTest {\n            cursor_id,\n            target_pc,\n            value_reg: _\n        },\n        insn\n    );\n    let cursor_seq = state\n        .cursor_seqs\n        .get_mut(*cursor_id)\n        .expect(\"cursor_id should be valid\");\n    let was_zero = *cursor_seq == 0;\n    *cursor_seq += 1;\n    state.pc = if was_zero {\n        target_pc.as_offset_int()\n    } else {\n        state.pc + 1\n    };\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_init_coroutine(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        InitCoroutine {\n            yield_reg,\n            jump_on_definition,\n            start_offset,\n        },\n        insn\n    );\n    assert!(jump_on_definition.is_offset());\n    let start_offset = start_offset.as_offset_int();\n    state.registers[*yield_reg].set_int(start_offset as i64);\n    state.ended_coroutine.retain(|n| *n != *yield_reg as u32);\n    let jump_on_definition = jump_on_definition.as_offset_int();\n    state.pc = if jump_on_definition == 0 {\n        state.pc + 1\n    } else {\n        jump_on_definition\n    };\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_end_coroutine(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(EndCoroutine { yield_reg }, insn);\n\n    if let Value::Numeric(Numeric::Integer(pc)) = state.registers[*yield_reg].get_value() {\n        state.ended_coroutine.push(*yield_reg as u32);\n        let pc: u32 = (*pc)\n            .try_into()\n            .unwrap_or_else(|_| panic!(\"EndCoroutine: pc overflow: {pc}\"));\n        state.pc = pc - 1; // yield jump is always next to yield. Here we subtract 1 to go back to yield instruction\n    } else {\n        unreachable!();\n    }\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_yield(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        Yield {\n            yield_reg,\n            end_offset,\n            subtype_clear_start_reg,\n            subtype_clear_count,\n        },\n        insn\n    );\n    if let Value::Numeric(Numeric::Integer(pc)) = state.registers[*yield_reg].get_value() {\n        if state.ended_coroutine.contains(&(*yield_reg as u32)) {\n            state.pc = end_offset.as_offset_int();\n        } else {\n            let pc: u32 = (*pc)\n                .try_into()\n                .unwrap_or_else(|_| panic!(\"Yield: pc overflow: {pc}\"));\n            // swap the program counter with the value in the yield register\n            // this is the mechanism that allows jumping back and forth between the coroutine and the caller\n            state.registers[*yield_reg].set_int((state.pc + 1) as i64);\n            state.pc = pc;\n\n            // Strip JSON subtypes from co-routine output columns so they do not\n            // survive the subquery boundary, matching SQLite's OP_Copy P5=0x0002.\n            // subtype_clear_count > 0 only for coroutine body yields.\n            #[cfg(feature = \"json\")]\n            if *subtype_clear_count > 0 {\n                use crate::types::TextSubtype;\n                for reg in &mut state.registers\n                    [*subtype_clear_start_reg..*subtype_clear_start_reg + *subtype_clear_count]\n                {\n                    if let Register::Value(Value::Text(text)) = reg {\n                        if text.subtype == TextSubtype::Json {\n                            text.subtype = TextSubtype::Text;\n                        }\n                    }\n                }\n            }\n        }\n    } else {\n        unreachable!(\n            \"yield_reg {} contains non-integer value: {:?}\",\n            *yield_reg, state.registers[*yield_reg]\n        );\n    }\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub struct OpInsertState {\n    pub sub_state: OpInsertSubState,\n    pub old_record: Option<(i64, Vec<Value>)>,\n    /// Set by the NoopCheck sub-state to indicate the row already has the exact\n    /// same payload, so the physical write can be skipped.\n    pub is_noop_update: bool,\n}\n\n#[derive(Debug, PartialEq)]\npub enum OpInsertSubState {\n    /// If this insert overwrites a record, capture the old record for incremental view maintenance.\n    /// If cursor is already positioned (no REQUIRE_SEEK), capture directly.\n    /// If REQUIRE_SEEK is set, transition to Seek first.\n    MaybeCaptureRecord,\n    /// Seek to the correct position if needed.\n    /// In a table insert, if the caller does not pass InsertFlags::REQUIRE_SEEK, they must ensure that a seek has already happened to the correct location.\n    /// This typically happens by invoking either Insn::NewRowid or Insn::NotExists, because:\n    /// 1. op_new_rowid() seeks to the end of the table, which is the correct insertion position.\n    /// 2. op_not_exists() seeks to the position in the table where the target rowid would be inserted.\n    Seek,\n    /// Capture the old record at the current cursor position for IVM.\n    /// The cursor must already be positioned (by a prior seek or by NotExists/NewRowid).\n    CaptureRecord,\n    /// Check whether the update is a no-op (existing record matches new record).\n    /// Must complete before Insert so that cursor.rowid()/record() are never\n    /// interleaved with a partially-completed cursor.insert().\n    NoopCheck,\n    /// Insert the row into the table.\n    Insert,\n    /// Updating last_insert_rowid may return IO, so we need a separate state for it so that we don't\n    /// start inserting the same row multiple times.\n    UpdateLastRowid,\n    /// If there are dependent incremental views, apply the change.\n    ApplyViewChange,\n}\n\npub fn op_insert(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        Insert {\n            cursor: cursor_id,\n            key_reg,\n            record_reg,\n            flag,\n            table_name,\n        },\n        insn\n    );\n\n    loop {\n        match &state.op_insert_state.sub_state {\n            OpInsertSubState::MaybeCaptureRecord => {\n                let has_dependent_views = {\n                    let schema = program.connection.schema.read();\n                    !schema\n                        .get_dependent_materialized_views(table_name)\n                        .is_empty()\n                };\n                // If there are no dependent views, we don't need to capture the old record.\n                // We also don't need to do it if the rowid of the UPDATEd row was changed, because\n                // op_delete already captured the deletion for IVM, and this insert only needs to\n                // record the new row (which ApplyViewChange handles without old_record).\n                let needs_capture =\n                    has_dependent_views && !flag.has(InsertFlags::UPDATE_ROWID_CHANGE);\n\n                if flag.has(InsertFlags::REQUIRE_SEEK) {\n                    state.op_insert_state.sub_state = OpInsertSubState::Seek;\n                } else if needs_capture {\n                    state.op_insert_state.sub_state = OpInsertSubState::CaptureRecord;\n                } else {\n                    state.op_insert_state.sub_state = OpInsertSubState::NoopCheck;\n                }\n                continue;\n            }\n            OpInsertSubState::Seek => {\n                if let SeekInternalResult::IO(io) = seek_internal(\n                    program,\n                    state,\n                    pager,\n                    RecordSource::Unpacked {\n                        start_reg: *key_reg,\n                        num_regs: 1,\n                    },\n                    *cursor_id,\n                    false,\n                    SeekOp::GE { eq_only: true },\n                )? {\n                    return Ok(InsnFunctionStepResult::IO(io));\n                }\n                let has_dependent_views = {\n                    let schema = program.connection.schema.read();\n                    !schema\n                        .get_dependent_materialized_views(table_name)\n                        .is_empty()\n                };\n                let needs_capture =\n                    has_dependent_views && !flag.has(InsertFlags::UPDATE_ROWID_CHANGE);\n                if needs_capture {\n                    state.op_insert_state.sub_state = OpInsertSubState::CaptureRecord;\n                } else {\n                    state.op_insert_state.sub_state = OpInsertSubState::NoopCheck;\n                }\n                continue;\n            }\n            OpInsertSubState::CaptureRecord => {\n                let insert_key = match &state.registers[*key_reg].get_value() {\n                    Value::Numeric(Numeric::Integer(i)) => *i,\n                    _ => unreachable!(\"expected integer key in insert\"),\n                };\n\n                let cursor = state.get_cursor(*cursor_id);\n                let cursor = cursor.as_btree_mut();\n                let maybe_key = return_if_io!(cursor.rowid());\n                let old_record = if let Some(key) = maybe_key {\n                    if key == insert_key {\n                        let maybe_record = return_if_io!(cursor.record());\n                        if let Some(record) = maybe_record {\n                            let mut values = record.get_values_owned()?;\n                            let schema = program.connection.schema.read();\n                            if let Some(table) = schema.get_table(table_name) {\n                                for (i, col) in table.columns().iter().enumerate() {\n                                    if col.is_rowid_alias() && i < values.len() {\n                                        values[i] = Value::from_i64(key);\n                                    }\n                                }\n                            }\n                            Some((key, values))\n                        } else {\n                            None\n                        }\n                    } else {\n                        None\n                    }\n                } else {\n                    None\n                };\n                state.op_insert_state.old_record = old_record;\n                state.op_insert_state.sub_state = OpInsertSubState::NoopCheck;\n                continue;\n            }\n            // TODO: add some InsertFlags that allows us to skip this check when we know for\n            // certain that the update is not a no-op to avoid the branch.\n            OpInsertSubState::NoopCheck => {\n                // UPDATE fast path: skip the physical write if the target row already\n                // has the exact same record payload. This check is isolated in its own\n                // sub-state so that cursor.rowid()/record() IO yields never interleave\n                // with a partially-completed cursor.insert().\n                //\n                // In MVCC mode this check must be skipped: after Delete, cursor.record()\n                // cannot reliably return the pre-delete payload (btree-resident rows\n                // still appear physically intact, MVCC-store rows become invisible).\n                // The noop check is fundamentally incompatible with the MVCC\n                // Delete+Insert update pattern.\n                state.op_insert_state.is_noop_update = false;\n                let is_mvcc = {\n                    let cursor_ref = get_cursor!(state, *cursor_id);\n                    cursor_ref.as_btree_mut().is_mvcc()\n                };\n                if !is_mvcc\n                    && flag.has(InsertFlags::SKIP_LAST_ROWID)\n                    && !flag.has(InsertFlags::UPDATE_ROWID_CHANGE)\n                {\n                    let key = match &state.registers[*key_reg].get_value() {\n                        Value::Numeric(Numeric::Integer(i)) => *i,\n                        _ => unreachable!(\"expected integer key\"),\n                    };\n                    let cursor = get_cursor!(state, *cursor_id);\n                    let cursor = cursor.as_btree_mut();\n                    let existing_key = return_if_io!(cursor.rowid());\n                    if existing_key == Some(key) {\n                        let record = match &state.registers[*record_reg] {\n                            Register::Record(r) => std::borrow::Cow::Borrowed(r),\n                            Register::Value(value) => {\n                                let values = [value];\n                                let record = ImmutableRecord::from_values(values, values.len());\n                                std::borrow::Cow::Owned(record)\n                            }\n                            Register::Aggregate(..) => {\n                                unreachable!(\"Cannot insert an aggregate value.\")\n                            }\n                        };\n                        let existing_record = return_if_io!(cursor.record());\n                        if existing_record.is_some_and(|r| r == record.as_ref()) {\n                            state.op_insert_state.is_noop_update = true;\n                        }\n                    }\n                }\n                state.op_insert_state.sub_state = OpInsertSubState::Insert;\n                continue;\n            }\n            OpInsertSubState::Insert => {\n                if !state.op_insert_state.is_noop_update {\n                    let key = match &state.registers[*key_reg].get_value() {\n                        Value::Numeric(Numeric::Integer(i)) => *i,\n                        _ => unreachable!(\"expected integer key\"),\n                    };\n                    let record = match &state.registers[*record_reg] {\n                        Register::Record(r) => std::borrow::Cow::Borrowed(r),\n                        Register::Value(value) => {\n                            let values = [value];\n                            let record = ImmutableRecord::from_values(values, values.len());\n                            std::borrow::Cow::Owned(record)\n                        }\n                        Register::Aggregate(..) => {\n                            unreachable!(\"Cannot insert an aggregate value.\")\n                        }\n                    };\n                    let cursor = get_cursor!(state, *cursor_id);\n                    let cursor = cursor.as_btree_mut();\n                    return_if_io!(cursor.insert(&BTreeKey::new_table_rowid(key, Some(&record))));\n                    state.metrics.rows_written = state.metrics.rows_written.saturating_add(1);\n                }\n                // Only update last_insert_rowid for regular table inserts, not schema modifications\n                let root_page = {\n                    let cursor = state.get_cursor(*cursor_id);\n                    let cursor = cursor.as_btree_mut();\n                    cursor.root_page()\n                };\n                if root_page != 1\n                    && table_name != SQLITE_SEQUENCE_TABLE_NAME\n                    && !flag.has(InsertFlags::EPHEMERAL_TABLE_INSERT)\n                {\n                    state.op_insert_state.sub_state = OpInsertSubState::UpdateLastRowid;\n                } else {\n                    // Schema table writes (sqlite_master, sqlite_sequence, ephemeral)\n                    // must not produce view deltas. The p4 table_name on these inserts\n                    // refers to the *target* table (for the update hook), not the table\n                    // actually being written to. Tracking deltas here would feed the\n                    // sqlite_master record into the DBSP circuit as if it were data\n                    // from the named table, corrupting the materialized view.\n                    state.op_insert_state.old_record = None;\n                    break;\n                }\n            }\n            OpInsertSubState::UpdateLastRowid => {\n                let maybe_rowid = {\n                    let cursor = state.get_cursor(*cursor_id);\n                    let cursor = cursor.as_btree_mut();\n                    return_if_io!(cursor.rowid())\n                };\n                if let Some(rowid) = maybe_rowid {\n                    if !flag.has(InsertFlags::SKIP_LAST_ROWID) {\n                        program.connection.update_last_rowid(rowid);\n                    }\n                    state\n                        .n_change\n                        .fetch_add(1, crate::sync::atomic::Ordering::SeqCst);\n                }\n                let schema = program.connection.schema.read();\n                let dependent_views = schema.get_dependent_materialized_views(table_name);\n                if !dependent_views.is_empty() {\n                    state.op_insert_state.sub_state = OpInsertSubState::ApplyViewChange;\n                    continue;\n                }\n                break;\n            }\n            OpInsertSubState::ApplyViewChange => {\n                let schema = program.connection.schema.read();\n                let dependent_views = schema.get_dependent_materialized_views(table_name);\n                assert!(!dependent_views.is_empty());\n\n                let (key, values) = {\n                    let key = match &state.registers[*key_reg].get_value() {\n                        Value::Numeric(Numeric::Integer(i)) => *i,\n                        _ => unreachable!(\"expected integer key\"),\n                    };\n\n                    let record = match &state.registers[*record_reg] {\n                        Register::Record(r) => std::borrow::Cow::Borrowed(r),\n                        Register::Value(value) => {\n                            let values = [value];\n                            let record = ImmutableRecord::from_values(values, values.len());\n                            std::borrow::Cow::Owned(record)\n                        }\n                        Register::Aggregate(..) => {\n                            unreachable!(\"Cannot insert an aggregate value.\")\n                        }\n                    };\n\n                    // Add insertion of new row to view deltas\n                    let mut new_values = record.get_values_owned()?;\n\n                    // Fix rowid alias columns: replace Null with actual rowid value\n                    let schema = program.connection.schema.read();\n                    if let Some(table) = schema.get_table(table_name) {\n                        for (i, col) in table.columns().iter().enumerate() {\n                            if col.is_rowid_alias() && i < new_values.len() {\n                                new_values[i] = Value::from_i64(key);\n                            }\n                        }\n                    }\n\n                    (key, new_values)\n                };\n\n                if let Some((key, values)) = state.op_insert_state.old_record.take() {\n                    for view_name in dependent_views.iter() {\n                        let tx_state = program\n                            .connection\n                            .view_transaction_states\n                            .get_or_create(view_name);\n                        tx_state.delete(table_name, key, values.clone());\n                    }\n                }\n                for view_name in dependent_views.iter() {\n                    let tx_state = program\n                        .connection\n                        .view_transaction_states\n                        .get_or_create(view_name);\n\n                    tx_state.insert(table_name, key, values.clone());\n                }\n\n                break;\n            }\n        }\n    }\n\n    state.op_insert_state.sub_state = OpInsertSubState::MaybeCaptureRecord;\n    state.op_insert_state.is_noop_update = false;\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_int_64(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        Int64 {\n            _p1,\n            out_reg,\n            _p3,\n            value,\n        },\n        insn\n    );\n    state.registers[*out_reg].set_int(*value);\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub struct OpDeleteState {\n    pub sub_state: OpDeleteSubState,\n    pub deleted_record: Option<(i64, Vec<Value>)>,\n}\n\npub enum OpDeleteSubState {\n    /// Capture the record before deletion, if the are dependent views.\n    MaybeCaptureRecord,\n    /// Delete the record.\n    Delete,\n    /// Apply the change to the dependent views.\n    ApplyViewChange,\n}\n\npub fn op_delete(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        Delete {\n            cursor_id,\n            table_name,\n            is_part_of_update,\n        },\n        insn\n    );\n\n    loop {\n        match &state.op_delete_state.sub_state {\n            OpDeleteSubState::MaybeCaptureRecord => {\n                let schema = program.connection.schema.read();\n                let dependent_views = schema.get_dependent_materialized_views(table_name);\n                if dependent_views.is_empty() {\n                    state.op_delete_state.sub_state = OpDeleteSubState::Delete;\n                    continue;\n                }\n\n                let deleted_record = {\n                    let cursor = state.get_cursor(*cursor_id);\n                    let cursor = cursor.as_btree_mut();\n                    // Get the current key\n                    let maybe_key = return_if_io!(cursor.rowid());\n                    let key = maybe_key.ok_or_else(|| {\n                        LimboError::InternalError(\"Cannot delete: no current row\".to_string())\n                    })?;\n                    // Get the current record before deletion and extract values\n                    let maybe_record = return_if_io!(cursor.record());\n                    if let Some(record) = maybe_record {\n                        let mut values = record.get_values_owned()?;\n\n                        // Fix rowid alias columns: replace Null with actual rowid value\n                        if let Some(table) = schema.get_table(table_name) {\n                            for (i, col) in table.columns().iter().enumerate() {\n                                if col.is_rowid_alias() && i < values.len() {\n                                    values[i] = Value::from_i64(key);\n                                }\n                            }\n                        }\n                        Some((key, values))\n                    } else {\n                        None\n                    }\n                };\n                state.op_delete_state.deleted_record = deleted_record;\n                state.op_delete_state.sub_state = OpDeleteSubState::Delete;\n                continue;\n            }\n            OpDeleteSubState::Delete => {\n                {\n                    let cursor = state.get_cursor(*cursor_id);\n                    let cursor = cursor.as_btree_mut();\n                    return_if_io!(cursor.delete());\n                }\n                // Increment metrics for row write (DELETE is a write operation)\n                state.metrics.rows_written = state.metrics.rows_written.saturating_add(1);\n                let schema = program.connection.schema.read();\n                let dependent_views = schema.get_dependent_materialized_views(table_name);\n                if dependent_views.is_empty() {\n                    break;\n                }\n                state.op_delete_state.sub_state = OpDeleteSubState::ApplyViewChange;\n                continue;\n            }\n            OpDeleteSubState::ApplyViewChange => {\n                let schema = program.connection.schema.read();\n                let dependent_views = schema.get_dependent_materialized_views(table_name);\n                assert!(!dependent_views.is_empty());\n                let maybe_deleted_record = state.op_delete_state.deleted_record.take();\n                if let Some((key, values)) = maybe_deleted_record {\n                    for view_name in dependent_views {\n                        let tx_state = program\n                            .connection\n                            .view_transaction_states\n                            .get_or_create(&view_name);\n                        tx_state.delete(table_name, key, values.clone());\n                    }\n                }\n                break;\n            }\n        }\n    }\n\n    state.op_delete_state.sub_state = OpDeleteSubState::MaybeCaptureRecord;\n    if !is_part_of_update {\n        // DELETEs do not count towards the total changes if they are part of an UPDATE statement,\n        // i.e. the DELETE and subsequent INSERT of a row are the same \"change\".\n        state\n            .n_change\n            .fetch_add(1, crate::sync::atomic::Ordering::SeqCst);\n    }\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\n#[derive(Debug)]\npub enum OpIdxDeleteState {\n    Seeking,\n    Verifying,\n    Deleting,\n}\npub fn op_idx_delete(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        IdxDelete {\n            cursor_id,\n            start_reg,\n            num_regs,\n            raise_error_if_no_matching_entry,\n        },\n        insn\n    );\n\n    if let Some(Cursor::IndexMethod(cursor)) = &mut state.cursors[*cursor_id] {\n        return_if_io!(cursor.delete(&state.registers[*start_reg..*start_reg + *num_regs]));\n        state.pc += 1;\n        return Ok(InsnFunctionStepResult::Step);\n    }\n\n    loop {\n        #[cfg(debug_assertions)]\n        tracing::debug!(\n            \"op_idx_delete(cursor_id={}, start_reg={}, num_regs={}, rootpage={}, state={:?})\",\n            cursor_id,\n            start_reg,\n            num_regs,\n            state.get_cursor(*cursor_id).as_btree_mut().root_page(),\n            state.op_idx_delete_state\n        );\n        match &state.op_idx_delete_state {\n            Some(OpIdxDeleteState::Seeking) => {\n                let found = match seek_internal(\n                    program,\n                    state,\n                    pager,\n                    RecordSource::Unpacked {\n                        start_reg: *start_reg,\n                        num_regs: *num_regs,\n                    },\n                    *cursor_id,\n                    true,\n                    SeekOp::GE { eq_only: true },\n                ) {\n                    Ok(SeekInternalResult::Found) => true,\n                    Ok(SeekInternalResult::NotFound) => false,\n                    Ok(SeekInternalResult::IO(io)) => return Ok(InsnFunctionStepResult::IO(io)),\n                    Err(e) => return Err(e),\n                };\n\n                if !found {\n                    // If P5 is not zero, then raise an SQLITE_CORRUPT_INDEX error if no matching index entry is found\n                    // Also, do not raise this (self-correcting and non-critical) error if in writable_schema mode.\n                    if *raise_error_if_no_matching_entry {\n                        let reg_values = (*start_reg..*start_reg + *num_regs)\n                            .map(|i| &state.registers[i])\n                            .collect::<Vec<_>>();\n                        return Err(LimboError::Corrupt(format!(\n                            \"IdxDelete: no matching index entry found for key {reg_values:?} while seeking\"\n                        )));\n                    }\n                    state.pc += 1;\n                    state.op_idx_delete_state = None;\n                    return Ok(InsnFunctionStepResult::Step);\n                }\n                state.op_idx_delete_state = Some(OpIdxDeleteState::Verifying);\n            }\n            Some(OpIdxDeleteState::Verifying) => {\n                let rowid = {\n                    let cursor = state.get_cursor(*cursor_id);\n                    let cursor = cursor.as_btree_mut();\n                    return_if_io!(cursor.rowid())\n                };\n\n                if rowid.is_none() && *raise_error_if_no_matching_entry {\n                    let reg_values = (*start_reg..*start_reg + *num_regs)\n                        .map(|i| &state.registers[i])\n                        .collect::<Vec<_>>();\n                    return Err(LimboError::Corrupt(format!(\n                        \"IdxDelete: no matching index entry found for key while verifying: {reg_values:?}\"\n                    )));\n                }\n                state.op_idx_delete_state = Some(OpIdxDeleteState::Deleting);\n            }\n            Some(OpIdxDeleteState::Deleting) => {\n                {\n                    let cursor = state.get_cursor(*cursor_id);\n                    let cursor = cursor.as_btree_mut();\n                    return_if_io!(cursor.delete());\n                }\n                // Increment metrics for index write (delete is a write operation)\n                state.metrics.rows_written = state.metrics.rows_written.saturating_add(1);\n                state.pc += 1;\n                state.op_idx_delete_state = None;\n                return Ok(InsnFunctionStepResult::Step);\n            }\n            None => {\n                state.op_idx_delete_state = Some(OpIdxDeleteState::Seeking);\n            }\n        }\n    }\n}\n\n#[derive(Debug, PartialEq, Copy, Clone)]\npub enum OpIdxInsertState {\n    /// Optional seek step done before an unique constraint check or if the caller indicates a seek is required.\n    MaybeSeek,\n    /// Optional unique constraint check done before an insert.\n    UniqueConstraintCheck,\n    /// Main insert step. This is always performed.\n    Insert,\n}\n\npub fn op_idx_insert(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        IdxInsert {\n            cursor_id,\n            record_reg,\n            flags,\n            unpacked_start,\n            unpacked_count,\n            ..\n        },\n        *insn\n    );\n\n    if let Some(Cursor::IndexMethod(cursor)) = &mut state.cursors[cursor_id] {\n        let Some(start) = unpacked_start else {\n            return Err(LimboError::InternalError(\n                \"IndexMethod must receive unpacked values\".to_string(),\n            ));\n        };\n        let Some(count) = unpacked_count else {\n            return Err(LimboError::InternalError(\n                \"IndexMethod must receive unpacked values\".to_string(),\n            ));\n        };\n        return_if_io!(cursor.insert(&state.registers[start..start + count as usize]));\n        state.pc += 1;\n        return Ok(InsnFunctionStepResult::Step);\n    }\n\n    let record_to_insert = match &state.registers[record_reg] {\n        Register::Record(ref r) => r,\n        o => {\n            return Err(LimboError::InternalError(format!(\n                \"expected record, got {o:?}\"\n            )));\n        }\n    };\n\n    match state.op_idx_insert_state {\n        OpIdxInsertState::MaybeSeek => {\n            let (_, cursor_type) = program\n                .cursor_ref\n                .get(cursor_id)\n                .expect(\"cursor_id should exist in cursor_ref\");\n            let CursorType::BTreeIndex(index_meta) = cursor_type else {\n                panic!(\"IdxInsert: not a BTreeIndex cursor\");\n            };\n\n            // USE_SEEK: cursor was already positioned by a preceding NoConflict operation.\n            // Skip the redundant seek and go directly to insert.\n            // For unique indexes, this also skips UniqueConstraintCheck since NoConflict already verified uniqueness.\n            //\n            // HOWEVER: If the record contains NULLs, NoConflict skips the seek entirely\n            // (since NULLs can't conflict), so we must fall back to seeking here.\n            if flags.has(IdxInsertFlags::USE_SEEK) && !record_to_insert.contains_null()? {\n                state.op_idx_insert_state = OpIdxInsertState::Insert;\n                return Ok(InsnFunctionStepResult::Step);\n                // Fall through to do the seek since NoConflict skipped it due to NULLs\n            }\n\n            match seek_internal(\n                program,\n                state,\n                pager,\n                RecordSource::Packed { record_reg },\n                cursor_id,\n                true,\n                SeekOp::GE { eq_only: true },\n            )? {\n                SeekInternalResult::Found => {\n                    state.op_idx_insert_state = if index_meta.unique {\n                        OpIdxInsertState::UniqueConstraintCheck\n                    } else {\n                        OpIdxInsertState::Insert\n                    };\n                    Ok(InsnFunctionStepResult::Step)\n                }\n                SeekInternalResult::NotFound => {\n                    state.op_idx_insert_state = OpIdxInsertState::Insert;\n                    Ok(InsnFunctionStepResult::Step)\n                }\n                SeekInternalResult::IO(io) => Ok(InsnFunctionStepResult::IO(io)),\n            }\n        }\n        OpIdxInsertState::UniqueConstraintCheck => {\n            let ignore_conflict = 'i: {\n                let cursor = get_cursor!(state, cursor_id);\n                let cursor = cursor.as_btree_mut();\n                let has_rowid = cursor.has_rowid();\n                let index_info = cursor.get_index_info().clone();\n                let record_opt = return_if_io!(cursor.record());\n                let Some(record) = record_opt.as_ref() else {\n                    // Cursor not pointing at a record — table is empty or past last\n                    break 'i false;\n                };\n                // Cursor is pointing at a record; if the index has a rowid, exclude it from the comparison since it's a pointer to the table row;\n                // UNIQUE indexes disallow duplicates like (a=1,b=2,rowid=1) and (a=1,b=2,rowid=2).\n                let existing_key = if has_rowid {\n                    let count = record.column_count();\n                    &record.get_values_range(0..count.saturating_sub(1))?\n                } else {\n                    &record.get_values()?[..]\n                };\n                let inserted_key_vals = &record_to_insert.get_values()?;\n                if existing_key.len() != inserted_key_vals.len() {\n                    break 'i false;\n                }\n\n                let conflict =\n                    compare_immutable(existing_key, inserted_key_vals, &index_info.key_info)\n                        == std::cmp::Ordering::Equal;\n                if conflict {\n                    if flags.has(IdxInsertFlags::NO_OP_DUPLICATE) {\n                        break 'i true;\n                    }\n                    return Err(LimboError::Constraint(\n                        \"UNIQUE constraint failed: duplicate key\".into(),\n                    ));\n                }\n\n                false\n            };\n            state.op_idx_insert_state = if ignore_conflict {\n                state.pc += 1;\n                OpIdxInsertState::MaybeSeek\n            } else {\n                OpIdxInsertState::Insert\n            };\n            Ok(InsnFunctionStepResult::Step)\n        }\n        OpIdxInsertState::Insert => {\n            {\n                let cursor = get_cursor!(state, cursor_id);\n                let cursor = cursor.as_btree_mut();\n                return_if_io!(cursor.insert(&BTreeKey::new_index_key(record_to_insert)));\n            }\n            // Increment metrics for index write\n            if flags.has(IdxInsertFlags::NCHANGE) {\n                state.metrics.rows_written = state.metrics.rows_written.saturating_add(1);\n            }\n            state.op_idx_insert_state = OpIdxInsertState::MaybeSeek;\n            state.pc += 1;\n            Ok(InsnFunctionStepResult::Step)\n        }\n    }\n}\n\n#[derive(Debug, Clone, Copy)]\npub enum OpNewRowidState {\n    Start,\n    SeekingToLast {\n        mvcc_already_initialized: bool,\n    },\n    ReadingMaxRowid,\n    GeneratingRandom {\n        attempts: u32,\n    },\n    VerifyingCandidate {\n        attempts: u32,\n        candidate: i64,\n    },\n    /// In case a rowid was generated and not provided by the user, we need to call next() on the cursor\n    /// after generating the rowid. This is because the rowid was generated by seeking to the last row in the\n    /// table, and we need to insert _after_ that row.\n    GoNext,\n}\n\npub fn op_new_rowid(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    new_rowid_inner(program, state, insn, pager).inspect_err(|_| {\n        // In case of error we need to unlock rowid lock from mvcc cursor\n        load_insn!(\n            NewRowid {\n                cursor,\n                rowid_reg: _,\n                prev_largest_reg: _,\n            },\n            insn\n        );\n        let mv_store = program.connection.mv_store();\n        if mv_store.is_some() {\n            let cursor = state.get_cursor(*cursor);\n            let cursor = cursor.as_btree_mut() as &mut dyn Any;\n            if let Some(mvcc_cursor) = cursor.downcast_mut::<MvCursor>() {\n                mvcc_cursor.end_new_rowid();\n            }\n        }\n    })\n}\n\nfn new_rowid_inner(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        NewRowid {\n            cursor,\n            rowid_reg,\n            prev_largest_reg,\n        },\n        insn\n    );\n\n    const MAX_ROWID: i64 = i64::MAX;\n    const MAX_ATTEMPTS: u32 = 100;\n    let mv_store = program.connection.mv_store();\n    loop {\n        match state.op_new_rowid_state {\n            OpNewRowidState::Start => {\n                if mv_store.is_some() {\n                    let cursor = state.get_cursor(*cursor);\n                    let cursor = cursor.as_btree_mut() as &mut dyn Any;\n                    if let Some(mvcc_cursor) = cursor.downcast_mut::<MvCursor>() {\n                        match return_if_io!(mvcc_cursor.start_new_rowid()) {\n                            NextRowidResult::Uninitialized => {\n                                state.op_new_rowid_state = OpNewRowidState::SeekingToLast {\n                                    mvcc_already_initialized: false,\n                                };\n                            }\n                            NextRowidResult::Next {\n                                new_rowid,\n                                prev_rowid,\n                            } => {\n                                // Allocator already initialized — release lock immediately\n                                mvcc_cursor.end_new_rowid();\n                                state.registers[*rowid_reg].set_int(new_rowid);\n                                if *prev_largest_reg > 0 {\n                                    state.registers[*prev_largest_reg]\n                                        .set_int(prev_rowid.unwrap_or(0));\n                                }\n                                state.op_new_rowid_state = OpNewRowidState::SeekingToLast {\n                                    mvcc_already_initialized: true,\n                                };\n                            }\n                            NextRowidResult::FindRandom => {\n                                mvcc_cursor.end_new_rowid();\n                                state.op_new_rowid_state =\n                                    OpNewRowidState::GeneratingRandom { attempts: 0 };\n                            }\n                        }\n                    } else {\n                        // Not an MvCursor — must be an ephemeral cursor or an attached\n                        // DB cursor without MVCC (e.g., :memory: attached DBs skip MVCC).\n                        // Keep the downcast check as a safety net against unexpected cursor types.\n                        assert!(\n                            cursor.downcast_ref::<BTreeCursor>().is_some(),\n                            \"Expected MvCursor or BTreeCursor in op_new_rowid\"\n                        );\n                        state.op_new_rowid_state = OpNewRowidState::SeekingToLast {\n                            mvcc_already_initialized: false,\n                        };\n                    }\n                } else {\n                    state.op_new_rowid_state = OpNewRowidState::SeekingToLast {\n                        mvcc_already_initialized: false,\n                    };\n                }\n            }\n\n            OpNewRowidState::SeekingToLast {\n                mvcc_already_initialized,\n            } => {\n                {\n                    let cursor = state.get_cursor(*cursor);\n                    let cursor = cursor.as_btree_mut();\n                    // We have an optimization in the btree cursor to not seek if we know the rightmost page and are already on it.\n                    // However, this optimization should NOT never performed in cases where we cannot be sure that the btree wasn't modified from under us\n                    // e.g. by a trigger subprogram.\n                    let always_seek = program.contains_trigger_subprograms;\n                    return_if_io!(cursor.seek_to_last(always_seek));\n                }\n                if mvcc_already_initialized {\n                    state.op_new_rowid_state = OpNewRowidState::GoNext;\n                } else {\n                    state.op_new_rowid_state = OpNewRowidState::ReadingMaxRowid;\n                }\n            }\n\n            OpNewRowidState::ReadingMaxRowid => {\n                let current_max = {\n                    let cursor = state.get_cursor(*cursor);\n                    let cursor = cursor.as_btree_mut();\n                    return_if_io!(cursor.rowid())\n                };\n\n                if mv_store.is_some() {\n                    let cursor = state.get_cursor(*cursor);\n                    let cursor = cursor.as_btree_mut() as &mut dyn Any;\n                    if let Some(mvcc_cursor) = cursor.downcast_mut::<MvCursor>() {\n                        // Initialize the monotonic counter from the btree max.\n                        // The allocator lock is held, so no other thread can\n                        // race between this read and initialize.\n                        mvcc_cursor.initialize_max_rowid(current_max)?;\n                        // Allocate the first rowid from the freshly initialized counter.\n                        match mvcc_cursor.allocate_next_rowid() {\n                            Some((new_rowid, prev_rowid)) => {\n                                state.registers[*rowid_reg].set_int(new_rowid);\n                                if *prev_largest_reg > 0 {\n                                    state.registers[*prev_largest_reg]\n                                        .set_int(prev_rowid.unwrap_or(0));\n                                }\n                                tracing::trace!(\"new_rowid={}\", new_rowid);\n                                state.op_new_rowid_state = OpNewRowidState::GoNext;\n                                continue;\n                            }\n                            None => {\n                                // At i64::MAX — fall back to random\n                                state.op_new_rowid_state =\n                                    OpNewRowidState::GeneratingRandom { attempts: 0 };\n                                continue;\n                            }\n                        }\n                    }\n                }\n\n                // Non-MVCC path (or ephemeral cursor in MVCC mode)\n                if *prev_largest_reg > 0 {\n                    state.registers[*prev_largest_reg].set_int(current_max.unwrap_or(0));\n                }\n                match current_max {\n                    Some(rowid) if rowid < MAX_ROWID => {\n                        state.registers[*rowid_reg].set_int(rowid + 1);\n                        tracing::trace!(\"new_rowid={}\", rowid + 1);\n                        state.op_new_rowid_state = OpNewRowidState::GoNext;\n                        continue;\n                    }\n                    Some(_) => {\n                        state.op_new_rowid_state =\n                            OpNewRowidState::GeneratingRandom { attempts: 0 };\n                    }\n                    None => {\n                        tracing::trace!(\"new_rowid=1\");\n                        state.registers[*rowid_reg].set_int(1);\n                        state.op_new_rowid_state = OpNewRowidState::GoNext;\n                        continue;\n                    }\n                }\n            }\n\n            OpNewRowidState::GeneratingRandom { attempts } => {\n                if attempts >= MAX_ATTEMPTS {\n                    return Err(LimboError::DatabaseFull(\"Unable to find an unused rowid after 100 attempts - database is probably full\".to_string()));\n                }\n\n                // Generate a random i64 and constrain it to the lower half of the rowid range.\n                // We use the lower half (1 to MAX_ROWID/2) because we're in random mode only\n                // when sequential allocation reached MAX_ROWID, meaning the upper range is full.\n                let mut random_rowid: i64 = pager.io.generate_random_number();\n                random_rowid &= MAX_ROWID >> 1; // Mask to keep value in range [0, MAX_ROWID/2]\n                random_rowid += 1; // Ensure positive\n\n                state.op_new_rowid_state = OpNewRowidState::VerifyingCandidate {\n                    attempts,\n                    candidate: random_rowid,\n                };\n            }\n\n            OpNewRowidState::VerifyingCandidate {\n                attempts,\n                candidate,\n            } => {\n                let exists = {\n                    let cursor = state.get_cursor(*cursor);\n                    let cursor = cursor.as_btree_mut();\n                    let seek_result =\n                        return_if_io!(cursor\n                            .seek(SeekKey::TableRowId(candidate), SeekOp::GE { eq_only: true }));\n                    matches!(seek_result, SeekResult::Found)\n                };\n\n                if !exists {\n                    // Found unused rowid!\n                    state.registers[*rowid_reg].set_int(candidate);\n                    state.op_new_rowid_state = OpNewRowidState::Start;\n                    state.pc += 1;\n\n                    if mv_store.is_some() {\n                        let cursor = state.get_cursor(*cursor);\n                        let cursor = cursor.as_btree_mut() as &mut dyn Any;\n                        if let Some(mvcc_cursor) = cursor.downcast_mut::<MvCursor>() {\n                            mvcc_cursor.end_new_rowid();\n                        }\n                    }\n\n                    return Ok(InsnFunctionStepResult::Step);\n                } else {\n                    // Collision, try again\n                    state.op_new_rowid_state = OpNewRowidState::GeneratingRandom {\n                        attempts: attempts + 1,\n                    };\n                }\n            }\n            OpNewRowidState::GoNext => {\n                {\n                    let cursor = state.get_cursor(*cursor);\n                    let cursor = cursor.as_btree_mut();\n                    return_if_io!(cursor.next());\n                }\n                state.op_new_rowid_state = OpNewRowidState::Start;\n                state.pc += 1;\n\n                if mv_store.is_some() {\n                    let cursor = state.get_cursor(*cursor);\n                    let cursor = cursor.as_btree_mut() as &mut dyn Any;\n                    if let Some(mvcc_cursor) = cursor.downcast_mut::<MvCursor>() {\n                        mvcc_cursor.end_new_rowid();\n                    }\n                }\n\n                return Ok(InsnFunctionStepResult::Step);\n            }\n        }\n    }\n}\n\npub fn op_must_be_int(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(MustBeInt { reg }, insn);\n    match &state.registers[*reg].get_value() {\n        Value::Numeric(Numeric::Integer(_)) => {}\n        Value::Numeric(Numeric::Float(f)) => match cast_real_to_integer(f64::from(*f)) {\n            Ok(i) => state.registers[*reg].set_int(i),\n            Err(_) => bail_constraint_error!(\"datatype mismatch\"),\n        },\n        Value::Text(text) => match checked_cast_text_to_numeric(text.as_str(), true) {\n            Ok(Value::Numeric(Numeric::Integer(i))) => state.registers[*reg].set_int(i),\n            Ok(Value::Numeric(Numeric::Float(f))) => match cast_real_to_integer(f64::from(f)) {\n                Ok(i) => state.registers[*reg].set_int(i),\n                Err(_) => bail_constraint_error!(\"datatype mismatch\"),\n            },\n            _ => bail_constraint_error!(\"datatype mismatch\"),\n        },\n        _ => {\n            bail_constraint_error!(\"datatype mismatch\");\n        }\n    };\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_soft_null(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(SoftNull { reg }, insn);\n    state.registers[*reg].set_null();\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\n#[derive(Clone, Copy)]\npub enum OpNoConflictState {\n    Start,\n    Seeking(RecordSource),\n}\n\n/// If a matching record is not found in the btree (\"no conflict\"), jump to the target PC.\n/// Otherwise, continue execution.\npub fn op_no_conflict(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        NoConflict {\n            cursor_id,\n            target_pc,\n            record_reg,\n            num_regs,\n        },\n        insn\n    );\n\n    loop {\n        match state.op_no_conflict_state {\n            OpNoConflictState::Start => {\n                let record_source = if *num_regs == 0 {\n                    RecordSource::Packed {\n                        record_reg: *record_reg,\n                    }\n                } else {\n                    RecordSource::Unpacked {\n                        start_reg: *record_reg,\n                        num_regs: *num_regs,\n                    }\n                };\n\n                // If there is at least one NULL in the index record, there cannot be a conflict so we can immediately jump.\n                let contains_nulls = match &record_source {\n                    RecordSource::Packed { record_reg } => {\n                        let Register::Record(record) = &state.registers[*record_reg] else {\n                            return Err(LimboError::InternalError(\n                                \"NoConflict: expected a record in the register\".into(),\n                            ));\n                        };\n                        record.iter()?.any(|val| matches!(val, Ok(ValueRef::Null)))\n                    }\n                    RecordSource::Unpacked {\n                        start_reg,\n                        num_regs,\n                    } => (0..*num_regs).any(|i| {\n                        matches!(\n                            &state.registers[start_reg + i],\n                            Register::Value(Value::Null)\n                        )\n                    }),\n                };\n\n                if contains_nulls {\n                    state.pc = target_pc.as_offset_int();\n                    state.op_no_conflict_state = OpNoConflictState::Start;\n                    return Ok(InsnFunctionStepResult::Step);\n                } else {\n                    state.op_no_conflict_state = OpNoConflictState::Seeking(record_source);\n                }\n            }\n            OpNoConflictState::Seeking(record_source) => {\n                return match seek_internal(\n                    program,\n                    state,\n                    pager,\n                    record_source,\n                    *cursor_id,\n                    true,\n                    SeekOp::GE { eq_only: true },\n                )? {\n                    SeekInternalResult::Found => {\n                        state.pc += 1;\n                        state.op_no_conflict_state = OpNoConflictState::Start;\n                        Ok(InsnFunctionStepResult::Step)\n                    }\n                    SeekInternalResult::NotFound => {\n                        state.pc = target_pc.as_offset_int();\n                        state.op_no_conflict_state = OpNoConflictState::Start;\n                        Ok(InsnFunctionStepResult::Step)\n                    }\n                    SeekInternalResult::IO(io) => Ok(InsnFunctionStepResult::IO(io)),\n                };\n            }\n        }\n    }\n}\n\npub fn op_not_exists(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        NotExists {\n            cursor,\n            rowid_reg,\n            target_pc,\n        },\n        insn\n    );\n    let cursor = must_be_btree_cursor!(*cursor, program.cursor_ref, state, \"NotExists\");\n    let cursor = cursor.as_btree_mut();\n    let exists = return_if_io!(cursor.exists(state.registers[*rowid_reg].get_value()));\n\n    if exists {\n        state.pc += 1;\n    } else {\n        state.pc = target_pc.as_offset_int();\n    }\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_offset_limit(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        OffsetLimit {\n            limit_reg,\n            combined_reg,\n            offset_reg,\n        },\n        insn\n    );\n    let limit_val = match state.registers[*limit_reg].get_value() {\n        Value::Numeric(Numeric::Integer(val)) => val,\n        _ => {\n            return Err(LimboError::InternalError(\n                \"OffsetLimit: the value in limit_reg is not an integer\".into(),\n            ));\n        }\n    };\n    let offset_val = match state.registers[*offset_reg].get_value() {\n        Value::Numeric(Numeric::Integer(val)) if *val < 0 => 0,\n        Value::Numeric(Numeric::Integer(val)) if *val >= 0 => *val,\n        _ => {\n            return Err(LimboError::InternalError(\n                \"OffsetLimit: the value in offset_reg is not an integer\".into(),\n            ));\n        }\n    };\n\n    let offset_limit_sum = limit_val.overflowing_add(offset_val);\n    if *limit_val <= 0 || offset_limit_sum.1 {\n        state.registers[*combined_reg].set_int(-1);\n    } else {\n        state.registers[*combined_reg].set_int(offset_limit_sum.0);\n    }\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n// this cursor may be reused for next insert\n// Update: tablemoveto is used to travers on not exists, on insert depending on flags if nonseek it traverses again.\n// If not there might be some optimizations obviously.\npub fn op_open_write(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        OpenWrite {\n            cursor_id,\n            root_page,\n            db,\n        },\n        insn\n    );\n    if program.connection.is_readonly(*db) {\n        return Err(LimboError::ReadOnly);\n    }\n    let pager = program.get_pager_from_database_index(db);\n    let mv_store = program.connection.mv_store_for_db(*db);\n\n    if let (_, CursorType::IndexMethod(module)) = &program.cursor_ref[*cursor_id] {\n        if state.cursors[*cursor_id].is_none() {\n            let cursor = module.init()?;\n            let cursor_ref = &mut state.cursors[*cursor_id];\n            *cursor_ref = Some(Cursor::IndexMethod(cursor));\n        }\n\n        let cursor = state.cursors[*cursor_id]\n            .as_mut()\n            .expect(\"cursor should exist\");\n        let cursor = cursor.as_index_method_mut();\n        return_if_io!(cursor.open_write(&program.connection));\n        state.pc += 1;\n        return Ok(InsnFunctionStepResult::Step);\n    }\n\n    let root_page = match root_page {\n        RegisterOrLiteral::Literal(lit) => *lit,\n        RegisterOrLiteral::Register(reg) => match &state.registers[*reg].get_value() {\n            Value::Numeric(Numeric::Integer(val)) => *val,\n            _ => {\n                return Err(LimboError::InternalError(\n                    \"OpenWrite: the value in root_page is not an integer\".into(),\n                ));\n            }\n        },\n    };\n\n    const SQLITE_SCHEMA_ROOT_PAGE: i64 = 1;\n\n    if root_page == SQLITE_SCHEMA_ROOT_PAGE {\n        if let Some(mv_store) = mv_store.as_ref() {\n            let Some(tx_id) = program.connection.get_mv_tx_id_for_db(*db) else {\n                return Err(LimboError::InternalError(\n                    \"Schema changes in MVCC mode require an exclusive MVCC transaction\".to_string(),\n                ));\n            };\n            if !mv_store.is_exclusive_tx(&tx_id) {\n                return Err(LimboError::TxError(\n                    \"DDL statements require an exclusive transaction (use BEGIN instead of BEGIN CONCURRENT)\".to_string(),\n                ));\n            }\n        }\n    }\n\n    let (_, cursor_type) = program\n        .cursor_ref\n        .get(*cursor_id)\n        .expect(\"cursor_id should exist in cursor_ref\");\n    let cursors = &mut state.cursors;\n    let maybe_index = match cursor_type {\n        CursorType::BTreeIndex(index) => Some(index),\n        _ => None,\n    };\n\n    // Check if we can reuse the existing cursor\n    let can_reuse_cursor = if let Some(Some(Cursor::BTree(btree_cursor))) = cursors.get(*cursor_id)\n    {\n        // Reuse if the root_page matches (same table/index)\n        btree_cursor.root_page() == root_page\n    } else {\n        false\n    };\n\n    if !can_reuse_cursor {\n        let maybe_promote_to_mvcc_cursor = |btree_cursor: Box<dyn CursorTrait>,\n                                            mv_cursor_type: MvccCursorType|\n         -> Result<Box<dyn CursorTrait>> {\n            if let Some(tx_id) = program.connection.get_mv_tx_id_for_db(*db) {\n                let mv_store = mv_store\n                    .as_ref()\n                    .expect(\"mv_store should be Some when MVCC transaction is active\")\n                    .clone();\n                Ok(Box::new(MvCursor::new(\n                    mv_store,\n                    tx_id,\n                    root_page,\n                    mv_cursor_type,\n                    btree_cursor,\n                )?))\n            } else {\n                Ok(btree_cursor)\n            }\n        };\n        if let Some(index) = maybe_index {\n            let num_columns = index.columns.len();\n            let btree_cursor = Box::new(BTreeCursor::new_index(\n                pager,\n                maybe_transform_root_page_to_positive(mv_store.as_ref(), root_page),\n                index.as_ref(),\n                num_columns,\n            ));\n            let index_info = Arc::new(IndexInfo::new_from_index(index));\n            let cursor =\n                maybe_promote_to_mvcc_cursor(btree_cursor, MvccCursorType::Index(index_info))?;\n            cursors\n                .get_mut(*cursor_id)\n                .expect(\"cursor_id should be valid\")\n                .replace(Cursor::new_btree(cursor));\n        } else {\n            let num_columns = match cursor_type {\n                CursorType::BTreeTable(table_rc) => table_rc.columns.len(),\n                CursorType::MaterializedView(table_rc, _) => table_rc.columns.len(),\n                _ => unreachable!(\n                    \"Expected BTreeTable or MaterializedView. This should not have happened.\"\n                ),\n            };\n\n            let btree_cursor = Box::new(BTreeCursor::new_table(\n                pager,\n                maybe_transform_root_page_to_positive(mv_store.as_ref(), root_page),\n                num_columns,\n            ));\n            let cursor = maybe_promote_to_mvcc_cursor(btree_cursor, MvccCursorType::Table)?;\n            cursors\n                .get_mut(*cursor_id)\n                .expect(\"cursor_id should be valid\")\n                .replace(Cursor::new_btree(cursor));\n        }\n    }\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_copy(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        Copy {\n            src_reg,\n            dst_reg,\n            extra_amount,\n        },\n        insn\n    );\n    for i in 0..=*extra_amount {\n        state.registers[*dst_reg + i] = state.registers[*src_reg + i].clone();\n    }\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_create_btree(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(CreateBtree { db, root, flags }, insn);\n\n    if program.connection.is_readonly(*db) {\n        return Err(LimboError::ReadOnly);\n    }\n    let mv_store = program.connection.mv_store_for_db(*db);\n\n    if let Some(mv_store) = mv_store.as_ref() {\n        let root_page = mv_store.get_next_table_id();\n        state.registers[*root].set_int(root_page);\n        state.pc += 1;\n        return Ok(InsnFunctionStepResult::Step);\n    }\n    let pager = program.get_pager_from_database_index(db);\n    // FIXME: handle page cache is full\n    let root_page = return_if_io!(pager.btree_create(flags));\n    state.registers[*root].set_int(root_page as i64);\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_index_method_create(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(IndexMethodCreate { db, cursor_id }, insn);\n    if program.connection.is_readonly(*db) {\n        return Err(LimboError::ReadOnly);\n    }\n    let mv_store = program.connection.mv_store_for_db(*db);\n    if let Some(_mv_store) = mv_store.as_ref() {\n        todo!(\"MVCC is not supported yet\");\n    }\n    if let (_, CursorType::IndexMethod(module)) = &program.cursor_ref[*cursor_id] {\n        if state.cursors[*cursor_id].is_none() {\n            let cursor = module.init()?;\n            let cursor_ref = &mut state.cursors[*cursor_id];\n            *cursor_ref = Some(Cursor::IndexMethod(cursor));\n        }\n    }\n    let cursor = state.cursors[*cursor_id]\n        .as_mut()\n        .expect(\"cursor should exist\");\n    let cursor = cursor.as_index_method_mut();\n    return_if_io!(cursor.create(&program.connection));\n\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_index_method_destroy(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(IndexMethodDestroy { db, cursor_id }, insn);\n    if program.connection.is_readonly(*db) {\n        return Err(LimboError::ReadOnly);\n    }\n    let mv_store = program.connection.mv_store_for_db(*db);\n    if let Some(_mv_store) = mv_store.as_ref() {\n        todo!(\"MVCC is not supported yet\");\n    }\n    if let Some((_, CursorType::IndexMethod(module))) = program.cursor_ref.get(*cursor_id) {\n        if state.cursors[*cursor_id].is_none() {\n            let cursor = module.init()?;\n            let cursor_ref = &mut state.cursors[*cursor_id];\n            *cursor_ref = Some(Cursor::IndexMethod(cursor));\n        }\n    }\n    let cursor = state.cursors[*cursor_id]\n        .as_mut()\n        .expect(\"cursor should exist\");\n    let cursor = cursor.as_index_method_mut();\n    return_if_io!(cursor.destroy(&program.connection));\n\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_index_method_optimize(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(IndexMethodOptimize { db, cursor_id }, insn);\n    if program.connection.is_readonly(*db) {\n        return Err(LimboError::ReadOnly);\n    }\n    let mv_store = program.connection.mv_store_for_db(*db);\n    if let Some(_mv_store) = mv_store.as_ref() {\n        todo!(\"MVCC is not supported yet\");\n    }\n    if let Some((_, CursorType::IndexMethod(module))) = program.cursor_ref.get(*cursor_id) {\n        if state.cursors[*cursor_id].is_none() {\n            let cursor = module.init()?;\n            let cursor_ref = &mut state.cursors[*cursor_id];\n            *cursor_ref = Some(Cursor::IndexMethod(cursor));\n        }\n    }\n    let cursor = state.cursors[*cursor_id]\n        .as_mut()\n        .expect(\"cursor should exist\");\n    let cursor = cursor.as_index_method_mut();\n    return_if_io!(cursor.optimize(&program.connection));\n\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_index_method_query(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        IndexMethodQuery {\n            db: _,\n            cursor_id,\n            start_reg,\n            count_reg,\n            pc_if_empty,\n        },\n        insn\n    );\n    let mv_store = program.connection.mv_store();\n    if let Some(_mv_store) = mv_store.as_ref() {\n        todo!(\"MVCC is not supported yet\");\n    }\n    let cursor = state.cursors[*cursor_id]\n        .as_mut()\n        .expect(\"cursor should exist\");\n    let cursor = cursor.as_index_method_mut();\n    let has_rows =\n        return_if_io!(cursor.query_start(&state.registers[*start_reg..*start_reg + *count_reg]));\n    if !has_rows {\n        state.pc = pc_if_empty.as_offset_int();\n    } else {\n        state.pc += 1;\n    }\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub enum OpDestroyState {\n    CreateCursor,\n    DestroyBtree(Arc<RwLock<BTreeCursor>>),\n}\n\npub fn op_destroy(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        Destroy {\n            db,\n            root,\n            former_root_reg,\n            is_temp,\n        },\n        insn\n    );\n    if *is_temp == 1 {\n        todo!(\"temp databases not implemented yet.\");\n    }\n    let mv_store = program.connection.mv_store_for_db(*db);\n    if mv_store.is_some() {\n        // MVCC only does pager operations in checkpoint\n        state.pc += 1;\n        return Ok(InsnFunctionStepResult::Step);\n    }\n\n    let destroy_pager = if *db != crate::MAIN_DB_ID {\n        program.get_pager_from_database_index(db)\n    } else {\n        pager.clone()\n    };\n\n    loop {\n        match state.op_destroy_state {\n            OpDestroyState::CreateCursor => {\n                // Destroy doesn't do anything meaningful with the table/index distinction so we can just use a\n                // table btree cursor for both.\n                let cursor = BTreeCursor::new(destroy_pager.clone(), *root, 0);\n                state.op_destroy_state =\n                    OpDestroyState::DestroyBtree(Arc::new(RwLock::new(cursor)));\n            }\n            OpDestroyState::DestroyBtree(ref mut cursor) => {\n                let maybe_former_root_page = return_if_io!(cursor.write().btree_destroy());\n                state.registers[*former_root_reg]\n                    .set_int(maybe_former_root_page.unwrap_or(0) as i64);\n                state.op_destroy_state = OpDestroyState::CreateCursor;\n                state.pc += 1;\n                return Ok(InsnFunctionStepResult::Step);\n            }\n        }\n    }\n}\n\npub fn op_reset_sorter(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(ResetSorter { cursor_id }, insn);\n\n    let (_, cursor_type) = program\n        .cursor_ref\n        .get(*cursor_id)\n        .expect(\"cursor_id should exist in cursor_ref\");\n    let cursor = state.get_cursor(*cursor_id);\n\n    match cursor_type {\n        CursorType::BTreeTable(_) => {\n            let cursor = cursor.as_btree_mut();\n            return_if_io!(cursor.clear_btree());\n            // FIXME: cuurently we don't have a good way to identify cursors that are\n            // iterating in the same underlying BTree\n\n            // After clearing the btree, invalidate cached navigation state on all\n            // other cursors that share the same underlying btree (e.g. OpenDup cursors).\n            // Without this, dup cursors may use stale cached rightmost-page info and\n            // attempt to insert into freed pages, causing corruption.\n            let cleared_pager = {\n                let cursor = state.get_cursor(*cursor_id);\n                cursor.as_btree_mut().get_pager()\n            };\n            for (i, other_cursor_opt) in state.cursors.iter_mut().enumerate() {\n                if i == *cursor_id {\n                    continue;\n                }\n                if let Some(Cursor::BTree(ref mut btree_cursor)) = other_cursor_opt {\n                    if Arc::ptr_eq(&btree_cursor.get_pager(), &cleared_pager) {\n                        btree_cursor.invalidate_btree_cache();\n                    }\n                }\n            }\n        }\n        CursorType::Sorter => {\n            return Err(LimboError::InternalError(\n                \"ResetSorter is not supported for sorter cursors\".to_string(),\n            ));\n        }\n        _ => {\n            return Err(LimboError::InternalError(format!(\n                \"ResetSorter is not supported for {cursor_type:?}\"\n            )));\n        }\n    }\n\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_drop_table(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(DropTable { db, table_name, .. }, insn);\n    let conn = program.connection.clone();\n    let is_mvcc = conn.mv_store_for_db(*db).is_some();\n    {\n        conn.with_database_schema_mut(*db, |schema| {\n            // In MVCC mode, track dropped root pages so integrity_check knows about them.\n            // The btree pages won't be freed until checkpoint, so integrity_check needs\n            // to include them to avoid \"page never used\" false positives.\n            if is_mvcc {\n                let table = schema\n                    .get_table(table_name)\n                    .expect(\"DROP TABLE: table must exist in schema\");\n                if let Some(btree) = table.btree() {\n                    // Only track positive root pages (checkpointed tables).\n                    // Negative root pages are non-checkpointed and don't exist in btree file.\n                    if btree.root_page > 0 {\n                        schema.dropped_root_pages.insert(btree.root_page);\n                    }\n                }\n                // Capture index root pages (table may not have indexes)\n                if let Some(indexes) = schema.indexes.get(table_name) {\n                    for index in indexes.iter() {\n                        if index.root_page > 0 {\n                            schema.dropped_root_pages.insert(index.root_page);\n                        }\n                    }\n                }\n            }\n            schema.remove_indices_for_table(table_name);\n            schema.remove_triggers_for_table(table_name);\n            schema.remove_table(table_name);\n        });\n    }\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_drop_view(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(DropView { db, view_name }, insn);\n    let conn = program.connection.clone();\n    conn.with_database_schema_mut(*db, |schema| {\n        schema.remove_view(view_name).ok();\n    });\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_drop_type(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(DropType { db, type_name }, insn);\n    if *db > 0 {\n        todo!(\"temp databases not implemented yet\");\n    }\n    let conn = program.connection.clone();\n    conn.with_schema_mut(|schema| {\n        schema.remove_type(type_name);\n        Ok::<(), crate::LimboError>(())\n    })?;\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_add_type(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(AddType { db, sql }, insn);\n    if *db > 0 {\n        todo!(\"temp databases not implemented yet\");\n    }\n    let conn = program.connection.clone();\n    conn.with_schema_mut(|schema| schema.add_type_from_sql(sql))?;\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_drop_trigger(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(DropTrigger { db, trigger_name }, insn);\n\n    let conn = program.connection.clone();\n    conn.with_database_schema_mut(*db, |schema| {\n        schema.remove_trigger(trigger_name).ok();\n    });\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_close(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(Close { cursor_id }, insn);\n    let cursors = &mut state.cursors;\n    cursors\n        .get_mut(*cursor_id)\n        .expect(\"cursor_id should be valid\")\n        .take();\n    if let Some(deferred_seek) = state.deferred_seeks.get_mut(*cursor_id) {\n        deferred_seek.take();\n    }\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_is_null(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(IsNull { reg, target_pc }, insn);\n    if matches!(state.registers[*reg], Register::Value(Value::Null)) {\n        state.pc = target_pc.as_offset_int();\n    } else {\n        state.pc += 1;\n    }\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_coll_seq(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    let Insn::CollSeq { reg, collation } = insn else {\n        unreachable!(\"unexpected Insn {:?}\", insn)\n    };\n\n    // Set the current collation sequence for use by subsequent functions\n    state.current_collation = Some(*collation);\n\n    // If P1 is not zero, initialize that register to 0\n    if let Some(reg_idx) = reg {\n        state.registers[*reg_idx].set_int(0);\n    }\n\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_page_count(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(PageCount { db, dest }, insn);\n    let pager = program.get_pager_from_database_index(db);\n    let mv_store = program.connection.mv_store_for_db(*db);\n    let count = match with_header(&pager, mv_store.as_ref(), program, *db, |header| {\n        header.database_size.get()\n    }) {\n        Err(_) => 0.into(),\n        Ok(IOResult::Done(v)) => v.into(),\n        Ok(IOResult::IO(io)) => return Ok(InsnFunctionStepResult::IO(io)),\n    };\n    state.registers[*dest].set_int(count);\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_parse_schema(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(ParseSchema { db, where_clause }, insn);\n\n    let conn = program.connection.clone();\n    // set auto commit to false in order for parse schema to not commit changes as transaction state is stored in connection,\n    // and we use the same connection for nested query.\n    let previous_auto_commit = conn.auto_commit.load(Ordering::SeqCst);\n    conn.auto_commit.store(false, Ordering::SeqCst);\n\n    // For attached databases, qualify the sqlite_schema table with the database name\n    let schema_table = if crate::is_attached_db(*db) {\n        let db_name = conn\n            .get_database_name_by_index(*db)\n            .unwrap_or_else(|| \"main\".to_string());\n        format!(\"{db_name}.sqlite_schema\")\n    } else {\n        \"sqlite_schema\".to_string()\n    };\n    let sql = if let Some(where_clause) = where_clause {\n        format!(\"SELECT * FROM {schema_table} WHERE {where_clause}\")\n    } else {\n        format!(\"SELECT * FROM {schema_table}\")\n    };\n    let stmt = conn.prepare(sql)?;\n\n    // Get a mutable schema clone *without* holding the schema lock during\n    // nested statement execution.  The nested Statement may call reprepare()\n    // which also acquires the schema / database_schemas write lock, so holding\n    // it here would deadlock on the same thread (parking_lot RwLock is not\n    // re-entrant).\n    let mut schema_arc = if crate::is_attached_db(*db) {\n        // Fetch the fallback schema from attached_databases BEFORE acquiring\n        // database_schemas.write() to avoid a nested lock ordering dependency\n        // (database_schemas.write -> attached_databases.read).\n        let fallback_schema = {\n            let attached_dbs = conn.attached_databases.read();\n            attached_dbs\n                .index_to_data\n                .get(db)\n                .map(|(db_inst, _pager)| db_inst.schema.lock().clone())\n        };\n        let Some(fallback_schema) = fallback_schema else {\n            // The db index refers to a database that was detached after this\n            // program was compiled. The schema cookie check should have caught\n            // this, but defensively return an error instead of panicking.\n            return Err(LimboError::InternalError(format!(\n                \"stale reference to detached database (index {db})\"\n            )));\n        };\n        let mut schemas = conn.database_schemas().write();\n        schemas\n            .entry(*db)\n            .or_insert_with(|| fallback_schema)\n            .clone() // cheap Arc clone; write lock released at end of block\n    } else {\n        conn.schema.read().clone()\n    };\n\n    let schema = Arc::make_mut(&mut schema_arc);\n    // TODO: This function below is synchronous, make it async\n    let existing_views = schema.incremental_views.clone();\n    conn.start_nested();\n    let maybe_nested_stmt_err = parse_schema_rows(\n        stmt,\n        schema,\n        &conn.syms.read(),\n        // NOTE: We always pass the main DB's mv_tx here because\n        // Statement::set_mv_tx() writes to connection.mv_tx (main DB field).\n        // Passing an attached DB's tx would corrupt the main DB's transaction\n        // state.  The nested statement's opcodes use get_mv_tx_id_for_db(db)\n        // to read the correct per-database tx, so the attached DB case works\n        // correctly without setting mv_tx.\n        program.connection.get_mv_tx(),\n        existing_views,\n    );\n\n    // Store the modified schema back\n    if crate::is_attached_db(*db) {\n        conn.database_schemas().write().insert(*db, schema_arc);\n    } else {\n        *conn.schema.write() = schema_arc;\n    }\n    conn.end_nested();\n    conn.auto_commit\n        .store(previous_auto_commit, Ordering::SeqCst);\n    maybe_nested_stmt_err?;\n\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_init_cdc_version(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        InitCdcVersion {\n            cdc_table_name,\n            version,\n            cdc_mode,\n        },\n        insn\n    );\n\n    let conn = program.connection.clone();\n    let escaped_cdc_table_name = escape_sql_string_literal(cdc_table_name);\n\n    // \"off\" — disable CDC (table and version entry are preserved)\n    if CaptureDataChangesInfo::parse(cdc_mode, None)?.is_none() {\n        state.pending_cdc_info = Some(None);\n        state.pc += 1;\n        return Ok(InsnFunctionStepResult::Step);\n    }\n\n    // If CDC is already enabled, re-parse with current version and exit early.\n    // This makes the operation idempotent and avoids CDC capturing its own\n    // table creation when the pragma is called multiple times.\n    {\n        let current = conn.get_capture_data_changes_info();\n        if let Some(info) = current.as_ref() {\n            let opts = CaptureDataChangesInfo::parse(cdc_mode, info.version)?;\n            state.pending_cdc_info = Some(opts);\n            state.pc += 1;\n            return Ok(InsnFunctionStepResult::Step);\n        }\n    }\n\n    // Step 0: Check if the CDC table already exists but has no version row.\n    // If so, it's a legacy v1 table that pre-dates version tracking.\n    let cdc_table_exists = {\n        conn.start_nested();\n        let mut stmt = conn.prepare(format!(\n            \"SELECT 1 FROM sqlite_schema WHERE type='table' AND name='{escaped_cdc_table_name}'\",\n        ))?;\n        stmt.program\n            .prepared\n            .needs_stmt_subtransactions\n            .store(false, Ordering::Relaxed);\n        let rows = stmt.run_collect_rows();\n        conn.end_nested();\n        !rows?.is_empty()\n    };\n\n    // Step 1: Create CDC table if needed\n    {\n        conn.start_nested();\n        let create_sql = match version {\n            CdcVersion::V1 => format!(\n                \"CREATE TABLE IF NOT EXISTS {cdc_table_name} (change_id INTEGER PRIMARY KEY AUTOINCREMENT, change_time INTEGER, change_type INTEGER, table_name TEXT, id, before BLOB, after BLOB, updates BLOB)\",\n            ),\n            CdcVersion::V2 => format!(\n                \"CREATE TABLE IF NOT EXISTS {cdc_table_name} (change_id INTEGER PRIMARY KEY AUTOINCREMENT, change_time INTEGER, change_txn_id INTEGER, change_type INTEGER, table_name TEXT, id, before BLOB, after BLOB, updates BLOB)\",\n            ),\n        };\n        let mut stmt = conn.prepare(create_sql)?;\n        stmt.program\n            .prepared\n            .needs_stmt_subtransactions\n            .store(false, Ordering::Relaxed);\n        let res = stmt.run_ignore_rows();\n        conn.end_nested();\n        res?;\n    }\n\n    // Step 2: Create version table if needed\n    {\n        conn.start_nested();\n        let mut stmt = conn.prepare(format!(\n            \"CREATE TABLE IF NOT EXISTS {TURSO_CDC_VERSION_TABLE_NAME} (table_name TEXT PRIMARY KEY, version TEXT NOT NULL)\",\n        ))?;\n        stmt.program\n            .prepared\n            .needs_stmt_subtransactions\n            .store(false, Ordering::Relaxed);\n        let res = stmt.run_ignore_rows();\n        conn.end_nested();\n        res?;\n    }\n\n    // Step 3: Insert version row only if one doesn't already exist.\n    // If the CDC table pre-existed without a version row, it's a legacy v1 table.\n    let version_to_insert = if cdc_table_exists {\n        CdcVersion::V1\n    } else {\n        *version\n    };\n    {\n        conn.start_nested();\n        let mut stmt = conn.prepare(format!(\n            \"INSERT OR IGNORE INTO {TURSO_CDC_VERSION_TABLE_NAME} (table_name, version) VALUES ('{escaped_cdc_table_name}', '{version_to_insert}')\",\n        ))?;\n        stmt.program\n            .prepared\n            .needs_stmt_subtransactions\n            .store(false, Ordering::Relaxed);\n        let res = stmt.run_ignore_rows();\n        conn.end_nested();\n        res?;\n    }\n\n    // Step 4: Read back the actual version from the table (may differ from\n    // `version` if the row already existed with an older version).\n    let actual_version = {\n        conn.start_nested();\n        let mut stmt = conn.prepare(format!(\n            \"SELECT version FROM {TURSO_CDC_VERSION_TABLE_NAME} WHERE table_name = '{escaped_cdc_table_name}'\",\n        ))?;\n        stmt.program\n            .prepared\n            .needs_stmt_subtransactions\n            .store(false, Ordering::Relaxed);\n        let rows = stmt.run_collect_rows();\n        conn.end_nested();\n        let rows = rows?;\n        match rows.first().and_then(|r| r.first()) {\n            Some(crate::Value::Text(text)) => text.to_string().parse::<CdcVersion>()?,\n            _ => *version,\n        }\n    };\n\n    // Defer enabling CDC until the program completes successfully (Halt).\n    // This ensures that if the transaction rolls back, the connection's\n    // CDC state remains unchanged.\n    let opts = CaptureDataChangesInfo::parse(cdc_mode, Some(actual_version))?;\n    state.pending_cdc_info = Some(opts);\n\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_populate_materialized_views(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(PopulateMaterializedViews { cursors }, insn);\n\n    let conn = program.connection.clone();\n\n    // For each view, get its cursor and root page\n    let mut view_info = Vec::new();\n    {\n        let cursors_ref = &state.cursors;\n        for (view_name, cursor_id) in cursors {\n            // Get the cursor to find the root page\n            let cursor = cursors_ref\n                .get(*cursor_id)\n                .and_then(|c| c.as_ref())\n                .ok_or_else(|| {\n                    LimboError::InternalError(format!(\"Cursor {cursor_id} not found\"))\n                })?;\n\n            let root_page = match cursor {\n                crate::types::Cursor::BTree(btree_cursor) => btree_cursor.root_page(),\n                _ => {\n                    return Err(LimboError::InternalError(\n                        \"Expected BTree cursor for materialized view\".into(),\n                    ));\n                }\n            };\n\n            view_info.push((view_name.clone(), root_page, *cursor_id));\n        }\n    }\n\n    // Now populate the views (after releasing the schema borrow)\n    for (view_name, _root_page, cursor_id) in view_info {\n        let schema = conn.schema.read();\n        if let Some(view) = schema.get_materialized_view(&view_name) {\n            let mut view = view.lock();\n            // Drop the schema borrow before calling populate_from_table\n            drop(schema);\n\n            // Get the cursor for writing\n            // Get a mutable reference to the cursor\n            let cursors_ref = &mut state.cursors;\n            let cursor = cursors_ref\n                .get_mut(cursor_id)\n                .and_then(|c| c.as_mut())\n                .ok_or_else(|| {\n                    LimboError::InternalError(format!(\n                        \"Cursor {cursor_id} not found for population\"\n                    ))\n                })?;\n\n            // Extract the BTreeCursor\n            let btree_cursor = match cursor {\n                crate::types::Cursor::BTree(btree_cursor) => btree_cursor,\n                _ => {\n                    return Err(LimboError::InternalError(\n                        \"Expected BTree cursor for materialized view population\".into(),\n                    ));\n                }\n            };\n\n            // Now populate it with the cursor for writing\n            return_if_io!(view.populate_from_table(&conn, pager, btree_cursor.as_mut()));\n        }\n    }\n\n    // All views populated, advance to next instruction\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_read_cookie(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(ReadCookie { db, dest, cookie }, insn);\n    let pager = program.get_pager_from_database_index(db);\n    let mv_store = program.connection.mv_store_for_db(*db);\n\n    let cookie_value =\n        match with_header(\n            &pager,\n            mv_store.as_ref(),\n            program,\n            *db,\n            |header| match cookie {\n                Cookie::ApplicationId => header.application_id.get().into(),\n                Cookie::UserVersion => header.user_version.get().into(),\n                Cookie::SchemaVersion => header.schema_cookie.get().into(),\n                Cookie::LargestRootPageNumber => header.vacuum_mode_largest_root_page.get().into(),\n                cookie => todo!(\"{cookie:?} is not yet implement for ReadCookie\"),\n            },\n        ) {\n            Err(_) => 0.into(),\n            Ok(IOResult::Done(v)) => v,\n            Ok(IOResult::IO(io)) => return Ok(InsnFunctionStepResult::IO(io)),\n        };\n\n    state.registers[*dest].set_int(cookie_value);\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_set_cookie(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        SetCookie {\n            db,\n            cookie,\n            value,\n            p5: _,\n        },\n        insn\n    );\n    let pager = program.get_pager_from_database_index(db);\n    let mv_store = program.connection.mv_store_for_db(*db);\n    if let Some(mv_store) = mv_store.as_ref() {\n        let Some(tx_id) = program.connection.get_mv_tx_id_for_db(*db) else {\n            return Err(LimboError::InternalError(\n                \"Header updates in MVCC mode require an active transaction\".to_string(),\n            ));\n        };\n        if !mv_store.is_exclusive_tx(&tx_id) {\n            // Header cookies are global metadata with no row-level conflict keys; require\n            // SQLite-style single-writer semantics (same policy as DDL in MVCC).\n            return Err(LimboError::TxError(\n                \"Header updates require an exclusive transaction (use BEGIN instead of BEGIN CONCURRENT)\".to_string(),\n            ));\n        }\n    }\n\n    return_if_io!(with_header_mut(\n        &pager,\n        mv_store.as_ref(),\n        program,\n        *db,\n        |header| {\n            match cookie {\n                Cookie::ApplicationId => header.application_id = (*value).into(),\n                Cookie::UserVersion => header.user_version = (*value).into(),\n                Cookie::LargestRootPageNumber => {\n                    header.vacuum_mode_largest_root_page = (*value as u32).into();\n                }\n                Cookie::IncrementalVacuum => {\n                    header.incremental_vacuum_enabled = (*value as u32).into()\n                }\n                Cookie::SchemaVersion => {\n                    // Only mark schema_did_change on connection for main database (db 0).\n                    // Attached databases track their schema independently.\n                    if *db == crate::MAIN_DB_ID {\n                        match program.connection.get_tx_state() {\n                            TransactionState::Write { .. } => {\n                                program.connection.set_tx_state(TransactionState::Write { schema_did_change: true });\n                            },\n                            TransactionState::Read => unreachable!(\"invalid transaction state for SetCookie: TransactionState::Read, should be write\"),\n                            TransactionState::None => unreachable!(\"invalid transaction state for SetCookie: TransactionState::None, should be write\"),\n                            TransactionState::PendingUpgrade { .. } => unreachable!(\"invalid transaction state for SetCookie: TransactionState::PendingUpgrade, should be write\"),\n                        }\n                    }\n                    program.connection.with_database_schema_mut(*db, |schema| {\n                        schema.schema_version = *value as u32\n                    });\n                    header.schema_cookie = (*value as u32).into();\n                }\n                cookie => todo!(\"{cookie:?} is not yet implement for SetCookie\"),\n            };\n        }\n    ));\n\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_shift_right(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(ShiftRight { lhs, rhs, dest }, insn);\n    state.registers[*dest].set_value(\n        state.registers[*lhs]\n            .get_value()\n            .exec_shift_right(state.registers[*rhs].get_value()),\n    );\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_shift_left(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(ShiftLeft { lhs, rhs, dest }, insn);\n    state.registers[*dest].set_value(\n        state.registers[*lhs]\n            .get_value()\n            .exec_shift_left(state.registers[*rhs].get_value()),\n    );\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_add_imm(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(AddImm { register, value }, insn);\n\n    let current = &state.registers[*register];\n    let current_value = match current {\n        Register::Value(val) => val,\n        Register::Aggregate(_) => &Value::Null,\n        Register::Record(_) => &Value::Null,\n    };\n\n    let int_val = match current_value {\n        Value::Numeric(Numeric::Integer(i)) => i + value,\n        Value::Numeric(Numeric::Float(f)) => (f64::from(*f) as i64) + value,\n        Value::Text(s) => s.as_str().parse::<i64>().unwrap_or(0) + value,\n        Value::Blob(_) => *value, // BLOB becomes the added value\n        Value::Null => *value,    // NULL becomes the added value\n    };\n\n    state.registers[*register].set_int(int_val);\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_variable(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(Variable { index, dest }, insn);\n    state.registers[*dest].set_value(state.get_parameter(*index));\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_zero_or_null(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(ZeroOrNull { rg1, rg2, dest }, insn);\n    if state.registers[*rg1].is_null() || state.registers[*rg2].is_null() {\n        state.registers[*dest].set_null()\n    } else {\n        state.registers[*dest].set_int(0);\n    }\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_not(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(Not { reg, dest }, insn);\n    match state.registers[*reg].get_value().exec_boolean_not() {\n        Value::Numeric(Numeric::Integer(i)) => state.registers[*dest].set_int(i),\n        _ => state.registers[*dest].set_null(),\n    };\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\n/// Implements IS TRUE, IS FALSE, IS NOT TRUE, IS NOT FALSE.\n/// A value is \"true\" only if it's a non-zero number.\n/// Text and blobs are parsed as numbers; if not parseable, treated as 0 (falsy).\n/// NULL is handled specially with the null_value parameter.\npub fn op_is_true(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        IsTrue {\n            reg,\n            dest,\n            null_value,\n            invert\n        },\n        insn\n    );\n    let value = state.registers[*reg].get_value();\n    // Use Numeric::try_into_bool which handles the conversion of text/blob to numbers\n    let final_result = match Numeric::from_value(value).map(|val| val.to_bool()) {\n        // For NULL, store null_value directly (no inversion)\n        None => {\n            if *null_value {\n                1\n            } else {\n                0\n            }\n        }\n        // For non-NULL, optionally invert the boolean result\n        Some(is_truthy) => {\n            let result = if is_truthy { 1 } else { 0 };\n            if *invert {\n                1 - result\n            } else {\n                result\n            }\n        }\n    };\n    state.registers[*dest].set_int(final_result);\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_concat(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(Concat { lhs, rhs, dest }, insn);\n    state.registers[*dest].set_value(\n        state.registers[*lhs]\n            .get_value()\n            .exec_concat(state.registers[*rhs].get_value()),\n    );\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_and(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(And { lhs, rhs, dest }, insn);\n    state.registers[*dest].set_value(\n        state.registers[*lhs]\n            .get_value()\n            .exec_and(state.registers[*rhs].get_value()),\n    );\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_or(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(Or { lhs, rhs, dest }, insn);\n    state.registers[*dest].set_value(\n        state.registers[*lhs]\n            .get_value()\n            .exec_or(state.registers[*rhs].get_value()),\n    );\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_noop(\n    _program: &Program,\n    state: &mut ProgramState,\n    _insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    // Do nothing\n    // Advance the program counter for the next opcode\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\n#[derive(Default)]\npub enum OpOpenEphemeralState {\n    #[default]\n    Start,\n    // Fast path states for reusing existing ephemeral cursor\n    ClearExisting,\n    RewindExisting,\n    // Slow path states for creating new ephemeral cursor\n    StartingTxn {\n        pager: Arc<Pager>,\n    },\n    CreateBtree {\n        pager: Arc<Pager>,\n    },\n    // clippy complains this variant is too big when compared to the rest of the variants\n    // so it says we need to box it here\n    Rewind {\n        cursor: Box<dyn CursorTrait>,\n    },\n}\npub fn op_open_ephemeral(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    let (cursor_id, is_table) = match insn {\n        Insn::OpenEphemeral {\n            cursor_id,\n            is_table,\n        } => (*cursor_id, *is_table),\n        Insn::OpenAutoindex { cursor_id } => (*cursor_id, false),\n        _ => unreachable!(\"unexpected Insn {:?}\", insn),\n    };\n    let mv_store = program.connection.mv_store();\n    match &mut state.op_open_ephemeral_state {\n        OpOpenEphemeralState::Start => {\n            tracing::trace!(\"Start\");\n            // Fast path: if cursor already has an ephemeral btree, just clear it instead of\n            // recreating the entire pager/file/btree. This is important for performance when\n            // OpenEphemeral is called repeatedly during statement execution.\n            if state.cursors[cursor_id].is_some() {\n                state.op_open_ephemeral_state = OpOpenEphemeralState::ClearExisting;\n                return Ok(InsnFunctionStepResult::Step);\n            }\n            // Ephemeral tables always use the main DB's page size (db index 0)\n            // regardless of which database triggered the ephemeral allocation.\n            let page_size = return_if_io!(with_header(\n                pager,\n                mv_store.as_ref(),\n                program,\n                0,\n                |header| { header.page_size }\n            ));\n            let conn = program.connection.clone();\n            let io = conn.pager.load().io.clone();\n            let rand_num = io.generate_random_number();\n            let db_file: Arc<dyn DatabaseStorage>;\n            let db_file_io: Arc<dyn crate::IO>;\n\n            // we support OPFS in WASM - but it require files to be pre-opened in the browser before use\n            // we can fix this if we will make open_file interface async\n            // but for now for simplicity we use MemoryIO for all intermediate calculations\n            #[cfg(target_family = \"wasm\")]\n            {\n                use crate::MemoryIO;\n\n                db_file_io = Arc::new(MemoryIO::new());\n                let file = db_file_io.open_file(\"temp-file\", OpenFlags::Create, false)?;\n                db_file = Arc::new(DatabaseFile::new(file));\n            }\n            #[cfg(not(target_family = \"wasm\"))]\n            {\n                let temp_store = conn.get_temp_store();\n                if matches!(temp_store, crate::TempStore::Memory) {\n                    // When temp_store=memory, use in-memory storage for ephemeral tables\n                    use crate::MemoryIO;\n                    db_file_io = Arc::new(MemoryIO::new());\n                    let file = db_file_io.open_file(\"temp-file\", OpenFlags::Create, false)?;\n                    db_file = Arc::new(DatabaseFile::new(file));\n                } else {\n                    let temp_dir = temp_dir();\n                    let rand_path = std::path::Path::new(&temp_dir)\n                        .join(format!(\"tursodb-ephemeral-{rand_num}\"));\n                    let Some(rand_path_str) = rand_path.to_str() else {\n                        return Err(LimboError::InternalError(\n                            \"Failed to convert path to string\".to_string(),\n                        ));\n                    };\n                    let file = io.open_file(rand_path_str, OpenFlags::Create, false)?;\n                    db_file = Arc::new(DatabaseFile::new(file));\n                    db_file_io = io;\n                }\n            }\n\n            let buffer_pool = program.connection.db.buffer_pool.clone();\n\n            // Ephemeral databases always start empty, so create their own init_page_1\n            let ephemeral_init_page_1 =\n                Arc::new(arc_swap::ArcSwapOption::new(Some(default_page1(None))));\n\n            let pager = Arc::new(Pager::new(\n                db_file,\n                None,\n                db_file_io,\n                PageCache::default(),\n                buffer_pool,\n                Arc::new(Mutex::new(())),\n                ephemeral_init_page_1,\n            )?);\n\n            pager.set_page_size(page_size);\n\n            state.op_open_ephemeral_state = OpOpenEphemeralState::StartingTxn { pager };\n        }\n        OpOpenEphemeralState::ClearExisting => {\n            tracing::trace!(\"ClearExisting\");\n            let cursor = state.cursors[cursor_id]\n                .as_mut()\n                .expect(\"cursor should exist in ClearExisting state\");\n            let btree_cursor = cursor.as_btree_mut();\n            btree_cursor.set_null_flag(false);\n            return_if_io!(btree_cursor.clear_btree());\n            // iterate over existing deferred seeks and clear them as well,\n            // as any deferred seek on this cursor is now invalid.\n            for deferred_seek in &mut state.deferred_seeks {\n                if let Some(ds) = deferred_seek {\n                    if ds.index_cursor_id == cursor_id || ds.table_cursor_id == cursor_id {\n                        *deferred_seek = None;\n                    }\n                }\n            }\n            state.op_open_ephemeral_state = OpOpenEphemeralState::RewindExisting;\n        }\n        OpOpenEphemeralState::RewindExisting => {\n            tracing::trace!(\"RewindExisting\");\n            let cursor = state.cursors[cursor_id]\n                .as_mut()\n                .expect(\"cursor should exist in RewindExisting state\");\n            let btree_cursor = cursor.as_btree_mut();\n            return_if_io!(btree_cursor.rewind());\n            state.pc += 1;\n            state.op_open_ephemeral_state = OpOpenEphemeralState::Start;\n        }\n        OpOpenEphemeralState::StartingTxn { pager } => {\n            tracing::trace!(\"StartingTxn\");\n            pager\n                .begin_read_tx() // we have to begin a read tx before beginning a write\n                .expect(\"Failed to start read transaction\");\n            return_if_io!(pager.begin_write_tx());\n            state.op_open_ephemeral_state = OpOpenEphemeralState::CreateBtree {\n                pager: pager.clone(),\n            };\n        }\n        OpOpenEphemeralState::CreateBtree { pager } => {\n            tracing::trace!(\"CreateBtree\");\n            // FIXME: handle page cache is full\n            let flag = if is_table {\n                &CreateBTreeFlags::new_table()\n            } else {\n                &CreateBTreeFlags::new_index()\n            };\n            let root_page = return_if_io!(pager.btree_create(flag)) as i64;\n\n            let (_, cursor_type) = program\n                .cursor_ref\n                .get(cursor_id)\n                .expect(\"cursor_id should exist in cursor_ref\");\n\n            let num_columns = match cursor_type {\n                CursorType::BTreeTable(table_rc) => table_rc.columns.len(),\n                CursorType::BTreeIndex(index_arc) => index_arc.columns.len(),\n                _ => unreachable!(\"This should not have happened\"),\n            };\n\n            let cursor = if let CursorType::BTreeIndex(index) = cursor_type {\n                BTreeCursor::new_index(pager.clone(), root_page, index, num_columns)\n            } else {\n                BTreeCursor::new_table(pager.clone(), root_page, num_columns)\n            };\n            state.op_open_ephemeral_state = OpOpenEphemeralState::Rewind {\n                cursor: Box::new(cursor),\n            };\n        }\n        OpOpenEphemeralState::Rewind { cursor } => {\n            return_if_io!(cursor.rewind());\n\n            let cursors = &mut state.cursors;\n\n            let (_, cursor_type) = program\n                .cursor_ref\n                .get(cursor_id)\n                .expect(\"cursor_id should exist in cursor_ref\");\n\n            let OpOpenEphemeralState::Rewind { cursor } =\n                std::mem::take(&mut state.op_open_ephemeral_state)\n            else {\n                unreachable!()\n            };\n\n            // Table content is erased if the cursor already exists\n            match cursor_type {\n                CursorType::BTreeTable(_) => {\n                    cursors\n                        .get_mut(cursor_id)\n                        .expect(\"cursor_id should be valid\")\n                        .replace(Cursor::new_btree(cursor));\n                }\n                CursorType::BTreeIndex(_) => {\n                    cursors\n                        .get_mut(cursor_id)\n                        .expect(\"cursor_id should be valid\")\n                        .replace(Cursor::new_btree(cursor));\n                }\n                CursorType::Pseudo(_) => {\n                    panic!(\"OpenEphemeral on pseudo cursor\");\n                }\n                CursorType::Sorter => {\n                    panic!(\"OpenEphemeral on sorter cursor\");\n                }\n                CursorType::VirtualTable(_) => {\n                    panic!(\"OpenEphemeral on virtual table cursor, use Insn::VOpen instead\");\n                }\n                CursorType::IndexMethod(..) => {\n                    panic!(\"OpenEphemeral on index method cursor\")\n                }\n                CursorType::MaterializedView(_, _) => {\n                    panic!(\"OpenEphemeral on materialized view cursor\");\n                }\n            }\n\n            state.pc += 1;\n            state.op_open_ephemeral_state = OpOpenEphemeralState::Start;\n        }\n    }\n\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_open_dup(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        OpenDup {\n            new_cursor_id,\n            original_cursor_id,\n        },\n        insn\n    );\n    let mv_store = program.connection.mv_store();\n\n    let original_cursor = state.get_cursor(*original_cursor_id);\n    let original_cursor = original_cursor.as_btree_mut();\n\n    let root_page = original_cursor.root_page();\n    // We use the pager from the original cursor instead of the one attached to\n    // the connection because each ephemeral table creates its own pager (and\n    // a separate database file).\n    let pager = original_cursor.get_pager();\n\n    // Ephemeral tables have their own pager, so we need to check if this is an\n    // ephemeral cursor by comparing pagers. Ephemeral cursors should NOT be wrapped\n    // in MvCursor because the mv_store doesn't have mappings for ephemeral table root pages.\n    let is_ephemeral = pager.wal.is_none();\n\n    let (_, cursor_type) = program\n        .cursor_ref\n        .get(*original_cursor_id)\n        .expect(\"cursor_id should exist in cursor_ref\");\n    match cursor_type {\n        CursorType::BTreeTable(table) => {\n            let cursor = Box::new(BTreeCursor::new_table(\n                pager,\n                maybe_transform_root_page_to_positive(mv_store.as_ref(), root_page),\n                table.columns.len(),\n            ));\n            let cursor: Box<dyn CursorTrait> = if !is_ephemeral {\n                if let Some(tx_id) = program.connection.get_mv_tx_id() {\n                    let mv_store = mv_store\n                        .as_ref()\n                        .expect(\"mv_store should be Some when MVCC transaction is active\")\n                        .clone();\n                    Box::new(MvCursor::new(\n                        mv_store,\n                        tx_id,\n                        root_page,\n                        MvccCursorType::Table,\n                        cursor,\n                    )?)\n                } else {\n                    cursor\n                }\n            } else {\n                cursor\n            };\n            let cursors = &mut state.cursors;\n            cursors\n                .get_mut(*new_cursor_id)\n                .expect(\"cursor_id should be valid\")\n                .replace(Cursor::new_btree(cursor));\n        }\n        CursorType::BTreeIndex(_) => {\n            return Err(LimboError::InternalError(\n                \"OpenDup is not supported for BTreeIndex\".to_string(),\n            ));\n        }\n        _ => {\n            return Err(LimboError::InternalError(format!(\n                \"OpenDup is not supported for {cursor_type:?}\"\n            )));\n        }\n    }\n\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\n/// Execute the [Insn::Once] instruction.\n///\n/// This instruction is used to execute a block of code only once.\n/// If the instruction is executed again, it will jump to the target program counter.\npub fn op_once(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        Once {\n            target_pc_when_reentered,\n        },\n        insn\n    );\n    assert!(target_pc_when_reentered.is_offset());\n    let offset = state.pc;\n    if state.once.contains(&offset) {\n        state.pc = target_pc_when_reentered.as_offset_int();\n        return Ok(InsnFunctionStepResult::Step);\n    }\n    state.once.push(offset);\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_found(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    let (cursor_id, target_pc, record_reg, num_regs) = match insn {\n        Insn::NotFound {\n            cursor_id,\n            target_pc,\n            record_reg,\n            num_regs,\n        } => (cursor_id, target_pc, record_reg, num_regs),\n        Insn::Found {\n            cursor_id,\n            target_pc,\n            record_reg,\n            num_regs,\n        } => (cursor_id, target_pc, record_reg, num_regs),\n        _ => unreachable!(\"unexpected Insn {:?}\", insn),\n    };\n\n    let not = matches!(insn, Insn::NotFound { .. });\n\n    let record_source = if *num_regs == 0 {\n        RecordSource::Packed {\n            record_reg: *record_reg,\n        }\n    } else {\n        RecordSource::Unpacked {\n            start_reg: *record_reg,\n            num_regs: *num_regs,\n        }\n    };\n    let seek_result = match seek_internal(\n        program,\n        state,\n        pager,\n        record_source,\n        *cursor_id,\n        true,\n        SeekOp::GE { eq_only: true },\n    ) {\n        Ok(SeekInternalResult::Found) => SeekResult::Found,\n        Ok(SeekInternalResult::NotFound) => SeekResult::NotFound,\n        Ok(SeekInternalResult::IO(io)) => return Ok(InsnFunctionStepResult::IO(io)),\n        Err(e) => return Err(e),\n    };\n\n    let found = matches!(seek_result, SeekResult::Found);\n    let do_jump = (!found && not) || (found && !not);\n    if do_jump {\n        state.pc = target_pc.as_offset_int();\n    } else {\n        state.pc += 1;\n    }\n\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_affinity(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        Affinity {\n            start_reg,\n            count,\n            affinities,\n        },\n        insn\n    );\n\n    if affinities.len() != count.get() {\n        return Err(LimboError::InternalError(\n            \"Affinity: the length of affinities does not match the count\".into(),\n        ));\n    }\n\n    for (i, affinity_char) in affinities.chars().enumerate().take(count.get()) {\n        let reg_index = *start_reg + i;\n\n        let affinity = Affinity::from_char(affinity_char);\n\n        apply_affinity_char(&mut state.registers[reg_index], affinity);\n    }\n\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_count(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        Count {\n            cursor_id,\n            target_reg,\n            exact,\n        },\n        insn\n    );\n\n    let count = {\n        let cursor = must_be_btree_cursor!(*cursor_id, program.cursor_ref, state, \"Count\");\n        let cursor = cursor.as_btree_mut();\n        return_if_io!(cursor.count())\n    };\n\n    state.registers[*target_reg].set_int(count as i64);\n\n    // For optimized COUNT(*) queries, the count represents rows that would be read\n    // SQLite tracks this differently (as pages read), but for consistency we track as rows\n    if *exact {\n        state.metrics.rows_read = state.metrics.rows_read.saturating_add(count as u64);\n    }\n\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\n/// Format integrity check errors into a result string.\n/// Returns NULL when no errors were found.\nfn format_integrity_check_result(errors: &[IntegrityCheckError]) -> Option<String> {\n    if errors.is_empty() {\n        None\n    } else {\n        Some(\n            errors\n                .iter()\n                .map(|e| e.to_string())\n                .collect::<Vec<String>>()\n                .join(\"\\n\"),\n        )\n    }\n}\n\nfn has_freelist_error(errors: &[IntegrityCheckError]) -> bool {\n    errors.iter().any(|err| match err {\n        IntegrityCheckError::FreelistTrunkCorrupt { .. }\n        | IntegrityCheckError::FreelistPointerOutOfRange { .. } => true,\n        IntegrityCheckError::PageReferencedMultipleTimes { page_category, .. } => {\n            matches!(\n                page_category,\n                PageCategory::FreeListTrunk | PageCategory::FreePage\n            )\n        }\n        _ => false,\n    })\n}\n\npub enum OpIntegrityCheckState {\n    Start,\n    CheckingBTreeStructure {\n        errors: Vec<IntegrityCheckError>,\n        current_root_idx: usize,\n        state: IntegrityCheckState,\n    },\n}\n\npub fn op_integrity_check(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        IntegrityCk {\n            db,\n            max_errors,\n            roots,\n            message_register,\n        },\n        insn\n    );\n\n    let mv_store = program.connection.mv_store_for_db(*db);\n    // Use the correct pager for the target database (main or attached)\n    let target_pager = if *db == crate::MAIN_DB_ID {\n        pager.clone()\n    } else {\n        program.get_pager_from_database_index(db)\n    };\n    match &mut state.op_integrity_check_state {\n        OpIntegrityCheckState::Start => {\n            let (freelist_trunk_page, db_size) = return_if_io!(with_header(\n                &target_pager,\n                mv_store.as_ref(),\n                program,\n                *db,\n                |header| (header.freelist_trunk_page.get(), header.database_size.get())\n            ));\n            let mut errors = Vec::new();\n            let mut integrity_check_state = IntegrityCheckState::new(db_size as usize);\n            let mut current_root_idx = 0;\n\n            if freelist_trunk_page > 0 {\n                let expected_freelist_count = return_if_io!(with_header(\n                    &target_pager,\n                    mv_store.as_ref(),\n                    program,\n                    *db,\n                    |header| { header.freelist_pages.get() }\n                ));\n                integrity_check_state.set_expected_freelist_count(expected_freelist_count as usize);\n                integrity_check_state.start(\n                    freelist_trunk_page as i64,\n                    PageCategory::FreeListTrunk,\n                    &mut errors,\n                );\n            } else if !roots.is_empty() {\n                integrity_check_state.start(roots[0], PageCategory::Normal, &mut errors);\n                current_root_idx += 1;\n            }\n\n            state.op_integrity_check_state = OpIntegrityCheckState::CheckingBTreeStructure {\n                errors,\n                state: integrity_check_state,\n                current_root_idx,\n            };\n        }\n        OpIntegrityCheckState::CheckingBTreeStructure {\n            errors,\n            current_root_idx,\n            state: integrity_check_state,\n        } => {\n            return_if_io!(integrity_check(\n                integrity_check_state,\n                errors,\n                &target_pager,\n                mv_store.as_ref()\n            ));\n\n            if errors.len() >= *max_errors {\n                errors.truncate(*max_errors);\n                let message = format_integrity_check_result(errors);\n                match message {\n                    Some(msg) => state.registers[*message_register].set_text(Text::new(msg)),\n                    None => state.registers[*message_register].set_null(),\n                };\n                state.op_integrity_check_state = OpIntegrityCheckState::Start;\n                state.pc += 1;\n                return Ok(InsnFunctionStepResult::Step);\n            }\n\n            if *current_root_idx < roots.len() {\n                integrity_check_state.start(roots[*current_root_idx], PageCategory::Normal, errors);\n                *current_root_idx += 1;\n                return Ok(InsnFunctionStepResult::Step);\n            }\n\n            if !has_freelist_error(errors)\n                && integrity_check_state.freelist_count.actual_count\n                    != integrity_check_state.freelist_count.expected_count\n            {\n                errors.push(IntegrityCheckError::FreelistCountMismatch {\n                    actual_count: integrity_check_state.freelist_count.actual_count,\n                    expected_count: integrity_check_state.freelist_count.expected_count,\n                });\n            }\n\n            #[cfg(not(feature = \"omit_autovacuum\"))]\n            let skip_page_never_used = !matches!(\n                target_pager.get_auto_vacuum_mode(),\n                crate::storage::pager::AutoVacuumMode::None\n            );\n            #[cfg(feature = \"omit_autovacuum\")]\n            let skip_page_never_used = false;\n\n            if !skip_page_never_used {\n                for page_number in 2..=integrity_check_state.db_size {\n                    if !integrity_check_state\n                        .page_reference\n                        .contains_key(&(page_number as i64))\n                    {\n                        if target_pager.pending_byte_page_id() != Some(page_number as u32) {\n                            errors.push(IntegrityCheckError::PageNeverUsed {\n                                page_id: page_number as i64,\n                            });\n                        }\n                    } else if target_pager.pending_byte_page_id() == Some(page_number as u32) {\n                        errors.push(IntegrityCheckError::PendingBytePageUsed {\n                            page_id: page_number as i64,\n                        })\n                    }\n\n                    if errors.len() >= *max_errors {\n                        break;\n                    }\n                }\n            }\n\n            errors.truncate(*max_errors);\n            let message = format_integrity_check_result(errors);\n            match message {\n                Some(msg) => state.registers[*message_register].set_text(Text::new(msg)),\n                None => state.registers[*message_register].set_null(),\n            };\n            state.op_integrity_check_state = OpIntegrityCheckState::Start;\n            state.pc += 1;\n        }\n    }\n\n    Ok(InsnFunctionStepResult::Step)\n}\npub fn op_cast(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(Cast { reg, affinity }, insn);\n\n    let value = state.registers[*reg].get_value().clone();\n    let result = match affinity {\n        Affinity::Blob => value.exec_cast(\"BLOB\"),\n        Affinity::Text => value.exec_cast(\"TEXT\"),\n        Affinity::Numeric => value.exec_cast(\"NUMERIC\"),\n        Affinity::Integer => value.exec_cast(\"INTEGER\"),\n        Affinity::Real => value.exec_cast(\"REAL\"),\n    };\n\n    state.registers[*reg].set_value(result);\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_rename_table(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(RenameTable { db, from, to }, insn);\n\n    let normalized_from = normalize_ident(from.as_str());\n    let normalized_to = normalize_ident(to.as_str());\n\n    let conn = program.connection.clone();\n\n    conn.with_database_schema_mut(*db, |schema| -> crate::Result<()> {\n        if let Some(mut indexes) = schema.indexes.remove(&normalized_from) {\n            let autoindex_prefix = format!(\"sqlite_autoindex_{normalized_from}_\");\n            indexes.iter_mut().for_each(|index| {\n                let index = Arc::make_mut(index);\n                normalized_to.clone_into(&mut index.table_name);\n                // Rename autoindexes to match the new table name\n                if let Some(suffix) = index.name.strip_prefix(&autoindex_prefix) {\n                    index.name = format!(\"sqlite_autoindex_{normalized_to}_{suffix}\");\n                }\n            });\n\n            schema.indexes.insert(normalized_to.to_owned(), indexes);\n        };\n\n        let mut table = schema\n            .tables\n            .remove(&normalized_from)\n            .expect(\"table being renamed should be in schema\");\n        match Arc::make_mut(&mut table) {\n            Table::BTree(btree) => {\n                let btree = Arc::make_mut(btree);\n                // update this table's own foreign keys\n                for fk_arc in &mut btree.foreign_keys {\n                    let fk = Arc::make_mut(fk_arc);\n                    if normalize_ident(&fk.parent_table) == normalized_from {\n                        fk.parent_table.clone_from(&normalized_to);\n                    }\n                }\n\n                // Rewrite table-qualified refs in CHECK constraints\n                for check in &mut btree.check_constraints {\n                    rewrite_check_expr_table_refs(\n                        &mut check.expr,\n                        &normalized_from,\n                        &normalized_to,\n                    );\n                }\n\n                normalized_to.clone_into(&mut btree.name);\n            }\n            Table::Virtual(vtab) => {\n                Arc::make_mut(vtab).name.clone_from(&normalized_to);\n            }\n            _ => panic!(\"only btree and virtual tables can be renamed\"),\n        }\n\n        schema.tables.insert(normalized_to.to_owned(), table);\n\n        for (tname, t_arc) in schema.tables.iter_mut() {\n            // skip the table we just renamed\n            if normalize_ident(tname) == normalized_to {\n                continue;\n            }\n            if let Table::BTree(ref mut child_btree_arc) = Arc::make_mut(t_arc) {\n                let child_btree = Arc::make_mut(child_btree_arc);\n                for fk_arc in &mut child_btree.foreign_keys {\n                    if normalize_ident(&fk_arc.parent_table) == normalized_from {\n                        let fk = Arc::make_mut(fk_arc);\n                        fk.parent_table.clone_from(&normalized_to);\n                    }\n                }\n            }\n        }\n\n        // Update triggers: move from old table name key to new, and update\n        // each trigger's table_name field and body commands.\n        if let Some(mut triggers) = schema.triggers.remove(&normalized_from) {\n            for trigger_arc in &mut triggers {\n                let trigger = Arc::make_mut(trigger_arc);\n                normalized_to.clone_into(&mut trigger.table_name);\n                // Rewrite table references in trigger body commands\n                for cmd in &mut trigger.commands {\n                    rewrite_trigger_cmd_table_refs(cmd, &normalized_from, &normalized_to);\n                }\n                // Rewrite WHEN clause qualified refs\n                if let Some(ref mut when) = trigger.when_clause {\n                    rewrite_check_expr_table_refs(when, &normalized_from, &normalized_to);\n                }\n            }\n            schema.triggers.insert(normalized_to.to_owned(), triggers);\n        }\n\n        // Also update triggers on OTHER tables that reference the renamed table\n        // in their body commands (e.g., INSERT INTO old_name in a trigger on another table)\n        for (_, triggers) in schema.triggers.iter_mut() {\n            for trigger_arc in triggers.iter_mut() {\n                let trigger = Arc::make_mut(trigger_arc);\n                for cmd in &mut trigger.commands {\n                    rewrite_trigger_cmd_table_refs(cmd, &normalized_from, &normalized_to);\n                }\n                if let Some(ref mut when) = trigger.when_clause {\n                    rewrite_check_expr_table_refs(when, &normalized_from, &normalized_to);\n                }\n            }\n        }\n\n        Ok(())\n    })?;\n\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_drop_column(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        DropColumn {\n            db,\n            table,\n            column_index\n        },\n        insn\n    );\n\n    let conn = program.connection.clone();\n\n    let normalized_table_name = normalize_ident(table.as_str());\n\n    let column_name = conn.with_schema(*db, |schema| {\n        let table = schema\n            .tables\n            .get(&normalized_table_name)\n            .expect(\"table being ALTERed should be in schema\");\n        table\n            .get_column_at(*column_index)\n            .expect(\"column being ALTERed should be in schema\")\n            .name\n            .as_ref()\n            .expect(\"column being ALTERed should be named\")\n            .clone()\n    });\n\n    conn.with_database_schema_mut(*db, |schema| {\n        let table = schema\n            .tables\n            .get_mut(&normalized_table_name)\n            .expect(\"table being renamed should be in schema\");\n\n        let table = Arc::get_mut(table).expect(\"this should be the only strong reference\");\n\n        let Table::BTree(btree) = table else {\n            panic!(\"only btree tables can be renamed\");\n        };\n\n        let btree = Arc::make_mut(btree);\n        btree.columns.remove(*column_index);\n        // Remove column-level CHECK constraints for the dropped column\n        let col_name = column_name.clone();\n        btree.check_constraints.retain(|c| {\n            c.column\n                .as_ref()\n                .is_none_or(|col| normalize_ident(col) != normalize_ident(&col_name))\n        });\n    });\n\n    conn.with_schema(*db, |schema| -> crate::Result<()> {\n        if let Some(indexes) = schema.indexes.get(&normalized_table_name) {\n            for index in indexes {\n                if index\n                    .columns\n                    .iter()\n                    .any(|column| column.pos_in_table == *column_index)\n                {\n                    return Err(LimboError::ParseError(format!(\n                        \"cannot drop column \\\"{column_name}\\\": indexed\"\n                    )));\n                }\n            }\n        }\n        Ok(())\n    })?;\n\n    // Update index.pos_in_table for all indexes.\n    // For example, if the dropped column had index 2, then anything that was indexed on column 3 or higher should be decremented by 1.\n    conn.with_database_schema_mut(*db, |schema| {\n        if let Some(indexes) = schema.indexes.get_mut(&normalized_table_name) {\n            for index in indexes {\n                let index = Arc::get_mut(index).expect(\"this should be the only strong reference\");\n                for index_column in index.columns.iter_mut() {\n                    if index_column.pos_in_table > *column_index {\n                        index_column.pos_in_table -= 1;\n                    }\n                }\n            }\n        }\n    });\n\n    conn.with_schema(*db, |schema| -> crate::Result<()> {\n        for (view_name, view) in schema.views.iter() {\n            let view_select_sql = format!(\"SELECT * FROM {view_name}\");\n            let _ = conn.prepare(view_select_sql.as_str()).map_err(|e| {\n                LimboError::ParseError(format!(\n                    \"cannot drop column \\\"{}\\\": referenced in VIEW {view_name}: {}. {e}\",\n                    column_name, view.sql,\n                ))\n            })?;\n        }\n        Ok(())\n    })?;\n\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_add_column(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        AddColumn {\n            db,\n            table,\n            column,\n            check_constraints\n        },\n        insn\n    );\n\n    let conn = program.connection.clone();\n    let normalized_table_name = normalize_ident(table.as_str());\n\n    conn.with_database_schema_mut(*db, |schema| {\n        let table_ref = schema\n            .tables\n            .get_mut(&normalized_table_name)\n            .expect(\"table being altered should be in schema\");\n\n        let table_ref = Arc::make_mut(table_ref);\n\n        let crate::schema::Table::BTree(btree) = table_ref else {\n            panic!(\"only btree tables can have columns added\");\n        };\n\n        let btree = Arc::make_mut(btree);\n        btree.columns.push((**column).clone());\n        // Update CHECK constraints to include any constraints from the new column\n        btree.check_constraints.clone_from(check_constraints);\n    });\n\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_alter_column(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        AlterColumn {\n            db,\n            table: table_name,\n            column_index,\n            definition,\n            rename,\n        },\n        insn\n    );\n\n    let conn = program.connection.clone();\n\n    let normalized_table_name = normalize_ident(table_name.as_str());\n    let old_column_name = conn.with_schema(*db, |schema| {\n        let table = schema\n            .tables\n            .get(&normalized_table_name)\n            .expect(\"table being ALTERed should be in schema\");\n        table\n            .get_column_at(*column_index)\n            .expect(\"column being ALTERed should be in schema\")\n            .name\n            .as_ref()\n            .expect(\"column being ALTERed should be named\")\n            .clone()\n    });\n    let new_column = crate::schema::Column::try_from(definition.as_ref())?;\n    let new_name = definition.col_name.as_str().to_owned();\n\n    let view_rewrites = if *rename {\n        let target_db_name = conn.get_database_name_by_index(*db).ok_or_else(|| {\n            LimboError::InternalError(format!(\"unknown database id {} during ALTER TABLE\", *db))\n        })?;\n        conn.with_schema(\n            *db,\n            |schema| -> crate::Result<Vec<(String, RewrittenView)>> {\n                let mut rewrites = Vec::new();\n                for (view_name, view) in schema.views.iter() {\n                    if let Some(rewritten) = rewrite_view_sql_for_column_rename(\n                        &view.sql,\n                        schema,\n                        table_name.as_str(),\n                        &target_db_name,\n                        &old_column_name,\n                        &new_name,\n                    )? {\n                        rewrites.push((view_name.clone(), rewritten));\n                    }\n                }\n                Ok(rewrites)\n            },\n        )?\n    } else {\n        Vec::new()\n    };\n\n    conn.with_database_schema_mut(*db, |schema| {\n        let table_arc = schema\n            .tables\n            .get_mut(&normalized_table_name)\n            .expect(\"table being ALTERed should be in schema\");\n        let table = Arc::make_mut(table_arc);\n\n        let Table::BTree(ref mut btree_arc) = table else {\n            panic!(\"only btree tables can be altered\");\n        };\n        let btree = Arc::make_mut(btree_arc);\n        let col = btree\n            .columns\n            .get_mut(*column_index)\n            .expect(\"column being ALTERed should be in schema\");\n\n        // Update indexes on THIS table that name the old column (you already had this)\n        if let Some(idxs) = schema.indexes.get_mut(&normalized_table_name) {\n            for idx in idxs {\n                let idx = Arc::make_mut(idx);\n                for ic in &mut idx.columns {\n                    if ic.name.eq_ignore_ascii_case(\n                        col.name.as_ref().expect(\"btree column should be named\"),\n                    ) {\n                        ic.name.clone_from(&new_name);\n                    }\n                }\n                // Update partial index WHERE clause column references\n                if let Some(ref mut wc) = idx.where_clause {\n                    rename_identifiers(wc, &old_column_name, &new_name);\n                }\n            }\n        }\n        if *rename {\n            col.name = Some(new_name.clone());\n        } else {\n            *col = new_column.clone();\n        }\n\n        // Keep primary_key_columns consistent (names may change on rename)\n        for (pk_name, _ord) in &mut btree.primary_key_columns {\n            if pk_name.eq_ignore_ascii_case(&old_column_name) {\n                pk_name.clone_from(&new_name);\n            }\n        }\n\n        // Update unique_sets to reflect the renamed column\n        for unique_set in &mut btree.unique_sets {\n            for (col_name, _) in &mut unique_set.columns {\n                if col_name.eq_ignore_ascii_case(&old_column_name) {\n                    col_name.clone_from(&new_name);\n                }\n            }\n        }\n\n        // Update CHECK constraint expressions to reference the new column name\n        let old_col_normalized = normalize_ident(&old_column_name);\n        for check in &mut btree.check_constraints {\n            rename_identifiers(&mut check.expr, &old_col_normalized, &new_name);\n            if let Some(ref mut col) = check.column {\n                if col.eq_ignore_ascii_case(&old_column_name) {\n                    col.clone_from(&new_name);\n                }\n            }\n        }\n\n        // Maintain rowid-alias bit after change/rename (INTEGER PRIMARY KEY)\n        if !*rename {\n            // recompute alias from `new_column`\n            btree.columns[*column_index].set_rowid_alias(new_column.is_rowid_alias());\n        }\n\n        // Update this table's OWN foreign keys\n        for fk_arc in &mut btree.foreign_keys {\n            let fk = Arc::make_mut(fk_arc);\n            // child side: rename child column if it matches\n            for cc in &mut fk.child_columns {\n                if cc.eq_ignore_ascii_case(&old_column_name) {\n                    cc.clone_from(&new_name);\n                }\n            }\n            // parent side: if self-referencing, rename parent column too\n            if normalize_ident(&fk.parent_table) == normalized_table_name {\n                for pc in &mut fk.parent_columns {\n                    if pc.eq_ignore_ascii_case(&old_column_name) {\n                        pc.clone_from(&new_name);\n                    }\n                }\n            }\n        }\n\n        // fix OTHER tables that reference this table as parent\n        for (tname, t_arc) in schema.tables.iter_mut() {\n            if normalize_ident(tname) == normalized_table_name {\n                continue;\n            }\n            if let Table::BTree(ref mut child_btree_arc) = Arc::make_mut(t_arc) {\n                let child_btree = Arc::make_mut(child_btree_arc);\n                for fk_arc in &mut child_btree.foreign_keys {\n                    if normalize_ident(&fk_arc.parent_table) != normalized_table_name {\n                        continue;\n                    }\n                    let fk = Arc::make_mut(fk_arc);\n                    for pc in &mut fk.parent_columns {\n                        if pc.eq_ignore_ascii_case(&old_column_name) {\n                            pc.clone_from(&new_name);\n                        }\n                    }\n                }\n            }\n        }\n    });\n\n    if *rename {\n        let old_col = old_column_name.clone();\n        let new_col = new_name;\n        let tbl_name = normalized_table_name.clone();\n        // Update in-memory trigger objects for the renamed column\n        conn.with_database_schema_mut(*db, move |schema| {\n            for (_, triggers) in schema.triggers.iter_mut() {\n                for trigger_arc in triggers.iter_mut() {\n                    let trigger_tbl = normalize_ident(&trigger_arc.table_name);\n                    let trigger = Arc::make_mut(trigger_arc);\n                    // Rewrite WHEN clause: only rename NEW.col / OLD.col qualified refs.\n                    // Bare column names in WHEN clauses are invalid per SQLite semantics,\n                    // so we must not rename them (SQLite would error on such triggers).\n                    if let Some(ref mut when) = trigger.when_clause {\n                        rename_identifiers_scoped_when_clause(\n                            when,\n                            &tbl_name,\n                            &trigger_tbl,\n                            &old_col,\n                            &new_col,\n                        );\n                    }\n                    // Rewrite UPDATE OF columns if trigger is on the renamed table\n                    if trigger_tbl == tbl_name {\n                        if let ast::TriggerEvent::UpdateOf(ref mut cols) = trigger.event {\n                            for col in cols {\n                                if normalize_ident(col.as_str()) == normalize_ident(&old_col) {\n                                    *col = ast::Name::exact(new_col.clone());\n                                }\n                            }\n                        }\n                    }\n                    // Rewrite trigger body commands\n                    for cmd in &mut trigger.commands {\n                        rewrite_trigger_cmd_column_refs(\n                            cmd,\n                            &tbl_name,\n                            &trigger_tbl,\n                            &old_col,\n                            &new_col,\n                        );\n                    }\n                }\n            }\n        });\n\n        let rewrites = view_rewrites;\n        conn.with_database_schema_mut(*db, move |schema| -> crate::Result<()> {\n            for (view_name, rewritten) in rewrites {\n                if let Some(view_arc) = schema.views.get_mut(&view_name) {\n                    let view = Arc::make_mut(view_arc);\n                    view.sql = rewritten.sql;\n                    view.select_stmt = rewritten.select_stmt;\n                    view.columns = rewritten.columns;\n                }\n            }\n            Ok(())\n        })?;\n\n        if !crate::is_attached_db(*db) {\n            conn.with_schema(*db, |schema| -> crate::Result<()> {\n                let table = schema\n                    .tables\n                    .get(&normalized_table_name)\n                    .expect(\"table being ALTERed should be in schema\");\n                let _column = table\n                    .get_column_at(*column_index)\n                    .expect(\"column being ALTERed should be in schema\");\n                for (view_name, view) in schema.views.iter() {\n                    let view_select_sql = format!(\"SELECT * FROM {view_name}\");\n                    let _ = conn.prepare(view_select_sql.as_str()).map_err(|e| {\n                        LimboError::ParseError(format!(\n                            \"cannot rename column \\\"{}\\\": referenced in VIEW {view_name}: {}. {e}\",\n                            old_column_name, view.sql,\n                        ))\n                    })?;\n                }\n                Ok(())\n            })?;\n        }\n    }\n\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_if_neg(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(IfNeg { reg, target_pc }, insn);\n\n    match &state.registers[*reg] {\n        Register::Value(Value::Numeric(Numeric::Integer(i))) if *i < 0 => {\n            state.pc = target_pc.as_offset_int();\n        }\n        Register::Value(Value::Numeric(Numeric::Float(f))) if f64::from(*f) < 0.0 => {\n            state.pc = target_pc.as_offset_int();\n        }\n        Register::Value(Value::Null) => {\n            state.pc += 1;\n        }\n        _ => {\n            state.pc += 1;\n        }\n    }\n\n    Ok(InsnFunctionStepResult::Step)\n}\n\nfn handle_text_sum(\n    acc: &mut Value,\n    sum_state: &mut SumAggState,\n    parsed_number: ParsedNumber,\n    parse_result: NumericParseResult,\n    force_approx: bool,\n) {\n    // SQLite treats text that only partially parses as numeric (ValidPrefixOnly)\n    // as approximate, so SUM returns real instead of integer. Non-integer inputs\n    // (e.g. BLOB) should also force approximate results.\n    let is_approx = force_approx || matches!(parse_result, NumericParseResult::ValidPrefixOnly);\n    match parsed_number {\n        ParsedNumber::Integer(i) => {\n            if is_approx {\n                sum_state.approx = true;\n                match acc {\n                    Value::Null => {\n                        *acc = Value::from_f64(i as f64);\n                    }\n                    Value::Numeric(Numeric::Integer(acc_i)) => {\n                        *acc = Value::from_f64(*acc_i as f64);\n                        apply_kbn_step_int(acc, i, sum_state);\n                    }\n                    Value::Numeric(Numeric::Float(_)) => {\n                        apply_kbn_step_int(acc, i, sum_state);\n                    }\n                    _ => unreachable!(),\n                }\n            } else {\n                match acc {\n                    Value::Null => {\n                        *acc = Value::from_i64(i);\n                    }\n                    Value::Numeric(Numeric::Integer(acc_i)) => match acc_i.checked_add(i) {\n                        Some(sum) => *acc = Value::from_i64(sum),\n                        None => {\n                            let acc_f = *acc_i as f64;\n                            *acc = Value::from_f64(acc_f);\n                            sum_state.approx = true;\n                            sum_state.ovrfl = true;\n                            apply_kbn_step_int(acc, i, sum_state);\n                        }\n                    },\n                    Value::Numeric(Numeric::Float(_)) => {\n                        apply_kbn_step_int(acc, i, sum_state);\n                    }\n                    _ => unreachable!(),\n                }\n            }\n        }\n        ParsedNumber::Float(f) => {\n            if !sum_state.approx {\n                if let Value::Numeric(Numeric::Integer(current_sum)) = *acc {\n                    *acc = Value::from_f64(current_sum as f64);\n                } else if matches!(*acc, Value::Null) {\n                    *acc = Value::from_f64(0.0);\n                }\n                sum_state.approx = true;\n            }\n            apply_kbn_step(acc, f, sum_state);\n        }\n        ParsedNumber::None => {\n            if !sum_state.approx {\n                if let Value::Numeric(Numeric::Integer(current_sum)) = *acc {\n                    *acc = Value::from_f64(current_sum as f64);\n                } else if matches!(*acc, Value::Null) {\n                    *acc = Value::from_f64(0.0);\n                }\n                sum_state.approx = true;\n            }\n        }\n    }\n}\n\npub fn op_fk_counter(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        FkCounter {\n            increment_value,\n            deferred,\n        },\n        insn\n    );\n    if !*deferred {\n        state.increment_fk_immediate_violations_during_stmt(*increment_value);\n    } else {\n        // Transaction-level counter: add/subtract for deferred FKs.\n        program\n            .connection\n            .increment_deferred_foreign_key_violations(*increment_value);\n    }\n\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_fk_if_zero(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        FkIfZero {\n            deferred,\n            target_pc,\n        },\n        insn\n    );\n    let fk_enabled = program.connection.foreign_keys_enabled();\n\n    // Jump if any:\n    // Foreign keys are disabled globally\n    // p1 is true AND deferred constraint counter is zero\n    // p1 is false AND deferred constraint counter is non-zero\n    if !fk_enabled {\n        state.pc = target_pc.as_offset_int();\n        return Ok(InsnFunctionStepResult::Step);\n    }\n    let v = if *deferred {\n        program.connection.get_deferred_foreign_key_violations()\n    } else {\n        state.get_fk_immediate_violations_during_stmt()\n    };\n\n    state.pc = if v == 0 {\n        target_pc.as_offset_int()\n    } else {\n        state.pc + 1\n    };\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_fk_check(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(FkCheck { deferred }, insn);\n    if !program.connection.foreign_keys_enabled() {\n        state.pc += 1;\n        return Ok(InsnFunctionStepResult::Step);\n    }\n    let v = if *deferred {\n        program.connection.get_deferred_foreign_key_violations()\n    } else {\n        state.get_fk_immediate_violations_during_stmt()\n    };\n    if v > 0 {\n        return Err(LimboError::ForeignKeyConstraint(\n            \"immediate foreign key constraint failed\".to_string(),\n        ));\n    }\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_hash_build(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(HashBuild { data }, insn);\n\n    let mut op_state = state\n        .op_hash_build_state\n        .take()\n        .filter(|s| {\n            s.hash_table_id == data.hash_table_id\n                && s.cursor_id == data.cursor_id\n                && s.key_start_reg == data.key_start_reg\n                && s.num_keys == data.num_keys\n        })\n        .unwrap_or_else(|| OpHashBuildState {\n            key_values: Vec::with_capacity(data.num_keys),\n            key_idx: 0,\n            payload_values: Vec::with_capacity(data.num_payload),\n            payload_idx: 0,\n            rowid: None,\n            cursor_id: data.cursor_id,\n            hash_table_id: data.hash_table_id,\n            key_start_reg: data.key_start_reg,\n            num_keys: data.num_keys,\n        });\n\n    // Create hash table if it doesn't exist yet\n    let temp_store = program.connection.get_temp_store();\n    // When temp_store=memory, disable the memory limit entirely to avoid spilling.\n    // Spilling to an in-memory file has serialization overhead - simpler to never spill.\n    let mem_budget = if matches!(temp_store, crate::TempStore::Memory) {\n        usize::MAX\n    } else {\n        data.mem_budget\n    };\n    state\n        .hash_tables\n        .entry(data.hash_table_id)\n        .or_insert_with(|| {\n            let config = HashTableConfig {\n                initial_buckets: 1024,\n                mem_budget,\n                num_keys: data.num_keys,\n                collations: data.collations.clone(),\n                temp_store,\n                track_matched: data.track_matched,\n                partition_count: None,\n            };\n            HashTable::new(config, pager.io.clone())\n        });\n\n    // Read pre-computed key values directly from registers\n    while op_state.key_idx < data.num_keys {\n        let i = op_state.key_idx;\n        let reg = &state.registers[data.key_start_reg + i];\n        let value = reg.get_value().clone();\n        op_state.key_values.push(value);\n        op_state.key_idx += 1;\n    }\n\n    // Read payload values from registers if provided\n    if let Some(payload_reg) = data.payload_start_reg {\n        while op_state.payload_idx < data.num_payload {\n            let i = op_state.payload_idx;\n            let reg = &state.registers[payload_reg + i];\n            let value = reg.get_value().clone();\n            op_state.payload_values.push(value);\n            op_state.payload_idx += 1;\n        }\n    }\n\n    // Get the rowid from the cursor\n    if op_state.rowid.is_none() {\n        let cursor = state.get_cursor(data.cursor_id);\n        let rowid_val = match cursor {\n            Cursor::BTree(btree_cursor) => {\n                let rowid_opt = match btree_cursor.rowid() {\n                    Ok(IOResult::Done(v)) => v,\n                    Ok(IOResult::IO(io)) => {\n                        state.op_hash_build_state = Some(op_state);\n                        return Ok(InsnFunctionStepResult::IO(io));\n                    }\n                    Err(e) => {\n                        state.op_hash_build_state = Some(op_state);\n                        return Err(e);\n                    }\n                };\n                rowid_opt.ok_or_else(|| {\n                    LimboError::InternalError(\"HashBuild: cursor has no rowid\".to_string())\n                })?\n            }\n            _ => {\n                return Err(LimboError::InternalError(\n                    \"HashBuild: unsupported cursor type\".to_string(),\n                ));\n            }\n        };\n        op_state.rowid = Some(rowid_val);\n    }\n\n    // Insert the rowid into the hash table\n    if let Some(ht) = state.hash_tables.get_mut(&data.hash_table_id) {\n        let rowid = op_state.rowid.expect(\"rowid set\");\n        let pending = PendingHashInsert {\n            key_values: std::mem::take(&mut op_state.key_values),\n            rowid,\n            payload_values: std::mem::take(&mut op_state.payload_values),\n        };\n        match ht.insert_pending(pending, Some(&mut state.metrics.hash_join))? {\n            HashInsertResult::Done => {}\n            HashInsertResult::IO { io, pending } => {\n                op_state.key_values = pending.key_values;\n                op_state.payload_values = pending.payload_values;\n                state.op_hash_build_state = Some(op_state);\n                return Ok(InsnFunctionStepResult::IO(io));\n            }\n        }\n    }\n\n    state.op_hash_build_state = None;\n    state.metrics.rows_read = state.metrics.rows_read.saturating_add(1);\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_hash_distinct(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(HashDistinct { data }, insn);\n\n    let temp_store = program.connection.get_temp_store();\n    // When temp_store=memory, disable the memory limit entirely to avoid spilling.\n    let mem_budget = if matches!(temp_store, crate::TempStore::Memory) {\n        usize::MAX\n    } else {\n        DEFAULT_MEM_BUDGET\n    };\n    let hash_table = state\n        .hash_tables\n        .entry(data.hash_table_id)\n        .or_insert_with(|| {\n            let config = HashTableConfig {\n                initial_buckets: 1024,\n                mem_budget,\n                num_keys: data.num_keys,\n                collations: data.collations.clone(),\n                temp_store,\n                track_matched: false,\n                partition_count: None,\n            };\n            HashTable::new(config, pager.io.clone())\n        });\n\n    let key_values = &mut state.distinct_key_values;\n    key_values.clear();\n    for i in 0..data.num_keys {\n        let reg = &state.registers[data.key_start_reg + i];\n        key_values.push(reg.get_value().clone());\n    }\n\n    let mut key_refs: SmallVec<[ValueRef; 2]> = SmallVec::with_capacity(data.num_keys);\n    key_refs.extend(key_values.iter().map(|v| v.as_ref()));\n    match hash_table.insert_distinct(key_values, &key_refs, Some(&mut state.metrics.hash_join))? {\n        IOResult::Done(inserted) => {\n            state.pc = if inserted {\n                state.pc + 1\n            } else {\n                data.target_pc.as_offset_int()\n            };\n            Ok(InsnFunctionStepResult::Step)\n        }\n        IOResult::IO(io) => Ok(InsnFunctionStepResult::IO(io)),\n    }\n}\n\npub fn op_hash_build_finalize(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(HashBuildFinalize { hash_table_id }, insn);\n    if let Some(ht) = state.hash_tables.get_mut(hash_table_id) {\n        // Finalize the build phase, may flush remaining partitions to disk if spilled\n        match ht.finalize_build(Some(&mut state.metrics.hash_join))? {\n            crate::types::IOResult::Done(()) => {}\n            crate::types::IOResult::IO(io) => {\n                return Ok(InsnFunctionStepResult::IO(io));\n            }\n        }\n    }\n\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\n/// Write payload values from a hash entry to registers.\nfn write_hash_payload_to_registers(\n    registers: &mut [Register],\n    entry: &HashEntry,\n    payload_dest_reg: Option<usize>,\n    num_payload: usize,\n) {\n    if let Some(dest_reg) = payload_dest_reg {\n        for (i, value) in entry.payload_values.iter().take(num_payload).enumerate() {\n            registers[dest_reg + i].set_value(value.clone());\n        }\n    }\n}\n\npub fn op_hash_probe(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        HashProbe {\n            hash_table_id,\n            key_start_reg,\n            num_keys,\n            dest_reg,\n            target_pc,\n            payload_dest_reg,\n            num_payload,\n            probe_rowid_reg,\n        },\n        insn\n    );\n    let hash_table_id = *hash_table_id as usize;\n    let key_start_reg = *key_start_reg as usize;\n    let num_keys = *num_keys as usize;\n    let dest_reg = *dest_reg as usize;\n    let payload_dest_reg = payload_dest_reg.map(|r| r as usize);\n    let num_payload = *num_payload as usize;\n    let probe_rowid_reg = probe_rowid_reg.map(|r| r as usize);\n    let (probe_keys, partition_idx, probe_buffered) =\n        if let Some(op_state) = state.op_hash_probe_state.take() {\n            if op_state.hash_table_id == hash_table_id {\n                (\n                    op_state.probe_keys,\n                    Some(op_state.partition_idx),\n                    op_state.probe_buffered,\n                )\n            } else {\n                // Different hash table, read fresh keys\n                let mut keys = Vec::with_capacity(num_keys);\n                for i in 0..num_keys {\n                    let reg = &state.registers[key_start_reg + i];\n                    keys.push(reg.get_value().clone());\n                }\n                (keys, None, false)\n            }\n        } else {\n            // First entry, read probe keys from registers\n            let mut keys = Vec::with_capacity(num_keys);\n            for i in 0..num_keys {\n                let reg = &state.registers[key_start_reg + i];\n                keys.push(reg.get_value().clone());\n            }\n            (keys, None, false)\n        };\n\n    let Some(hash_table) = state.hash_tables.get_mut(&hash_table_id) else {\n        // Empty build side: treat as no match and jump to target.\n        state.op_hash_probe_state = None;\n        state.pc = target_pc.as_offset_int();\n        return Ok(InsnFunctionStepResult::Step);\n    };\n\n    // For spilled hash tables, either buffer main-loop probe rows for grace\n    // processing or probe a partition that grace logic already loaded.\n    if hash_table.has_spilled() {\n        let partition_idx =\n            partition_idx.unwrap_or_else(|| hash_table.partition_for_keys(&probe_keys));\n\n        // Main probe loop: buffer probe rows targeting spilled build partitions.\n        if let Some(rowid_reg) = probe_rowid_reg {\n            if probe_buffered {\n                state.pc = target_pc.as_offset_int();\n                return Ok(InsnFunctionStepResult::Step);\n            }\n            if !hash_table.is_partition_loaded(partition_idx) {\n                // Partition is on disk: buffer this probe row for grace processing\n                let probe_rowid = match state.registers[rowid_reg].get_value() {\n                    Value::Numeric(Numeric::Integer(i)) => *i,\n                    _ => 0,\n                };\n                match hash_table.buffer_probe_row(\n                    probe_keys,\n                    probe_rowid,\n                    Some(&mut state.metrics.hash_join),\n                )? {\n                    IOResult::Done(()) => {}\n                    IOResult::IO(io) => {\n                        state.op_hash_probe_state = Some(OpHashProbeState {\n                            probe_keys: Vec::new(), // keys consumed\n                            hash_table_id,\n                            partition_idx,\n                            probe_buffered: true,\n                        });\n                        return Ok(InsnFunctionStepResult::IO(io));\n                    }\n                }\n                // Jump to target_pc: this row is deferred to grace processing.\n                state.pc = target_pc.as_offset_int();\n                return Ok(InsnFunctionStepResult::Step);\n            }\n            // Partition is in memory: probe immediately (fast path)\n            state.metrics.hash_join.grace_probe_rows_streamed = state\n                .metrics\n                .hash_join\n                .grace_probe_rows_streamed\n                .saturating_add(1);\n        } else if unlikely(!hash_table.is_partition_loaded(partition_idx)) {\n            return Err(LimboError::InternalError(format!(\n                \"HashProbe reached spilled partition {partition_idx} without a preloaded build partition; probe_rowid_reg=None is grace-only\"\n            )));\n        }\n\n        // Probe the loaded partition\n        match hash_table.probe_partition(\n            partition_idx,\n            &probe_keys,\n            Some(&mut state.metrics.hash_join),\n        ) {\n            Some(entry) => {\n                state.registers[dest_reg].set_int(entry.rowid);\n                write_hash_payload_to_registers(\n                    &mut state.registers,\n                    entry,\n                    payload_dest_reg,\n                    num_payload,\n                );\n                state.pc += 1;\n                Ok(InsnFunctionStepResult::Step)\n            }\n            None => {\n                state.pc = target_pc.as_offset_int();\n                Ok(InsnFunctionStepResult::Step)\n            }\n        }\n    } else {\n        // Non-spilled hash table, use normal probe\n        match hash_table.probe(probe_keys, Some(&mut state.metrics.hash_join)) {\n            Some(entry) => {\n                state.registers[dest_reg].set_int(entry.rowid);\n                write_hash_payload_to_registers(\n                    &mut state.registers,\n                    entry,\n                    payload_dest_reg,\n                    num_payload,\n                );\n                state.pc += 1;\n                Ok(InsnFunctionStepResult::Step)\n            }\n            None => {\n                state.pc = target_pc.as_offset_int();\n                Ok(InsnFunctionStepResult::Step)\n            }\n        }\n    }\n}\n\npub fn op_hash_next(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        HashNext {\n            hash_table_id,\n            dest_reg,\n            target_pc,\n            payload_dest_reg,\n            num_payload,\n        },\n        insn\n    );\n\n    let hash_table = state.hash_tables.get_mut(hash_table_id).ok_or_else(|| {\n        LimboError::InternalError(format!(\"Hash table not found with ID: {hash_table_id}\"))\n    })?;\n    match hash_table.next_match() {\n        Some(entry) => {\n            state.registers[*dest_reg].set_int(entry.rowid);\n            write_hash_payload_to_registers(\n                &mut state.registers,\n                entry,\n                *payload_dest_reg,\n                *num_payload,\n            );\n            state.pc += 1;\n            Ok(InsnFunctionStepResult::Step)\n        }\n        None => {\n            state.pc = target_pc.as_offset_int();\n            Ok(InsnFunctionStepResult::Step)\n        }\n    }\n}\n\npub fn op_hash_close(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(HashClose { hash_table_id }, insn);\n    if let Some(mut hash_table) = state.hash_tables.remove(hash_table_id) {\n        hash_table.close();\n    }\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_hash_clear(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(HashClear { hash_table_id }, insn);\n    if let Some(hash_table) = state.hash_tables.get_mut(hash_table_id) {\n        hash_table.clear();\n    }\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_hash_reset_matched(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(HashResetMatched { hash_table_id }, insn);\n    if let Some(hash_table) = state.hash_tables.get_mut(hash_table_id) {\n        hash_table.reset_matched_bits();\n    }\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_hash_mark_matched(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(HashMarkMatched { hash_table_id }, insn);\n    if let Some(hash_table) = state.hash_tables.get_mut(hash_table_id) {\n        hash_table.mark_current_matched();\n    }\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_hash_scan_unmatched(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        HashScanUnmatched {\n            hash_table_id,\n            dest_reg,\n            target_pc,\n            payload_dest_reg,\n            num_payload,\n        },\n        insn\n    );\n\n    let Some(hash_table) = state.hash_tables.get_mut(hash_table_id) else {\n        state.pc = target_pc.as_offset_int();\n        return Ok(InsnFunctionStepResult::Step);\n    };\n\n    hash_table.begin_unmatched_scan();\n    advance_unmatched_scan(\n        hash_table,\n        &mut state.registers,\n        &mut state.pc,\n        *dest_reg,\n        target_pc.as_offset_int(),\n        *payload_dest_reg,\n        *num_payload,\n        &mut state.metrics.hash_join,\n    )\n}\n\npub fn op_hash_next_unmatched(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        HashNextUnmatched {\n            hash_table_id,\n            dest_reg,\n            target_pc,\n            payload_dest_reg,\n            num_payload,\n        },\n        insn\n    );\n\n    let hash_table = state.hash_tables.get_mut(hash_table_id).ok_or_else(|| {\n        LimboError::InternalError(format!(\"Hash table not found with ID: {hash_table_id}\"))\n    })?;\n\n    advance_unmatched_scan(\n        hash_table,\n        &mut state.registers,\n        &mut state.pc,\n        *dest_reg,\n        target_pc.as_offset_int(),\n        *payload_dest_reg,\n        *num_payload,\n        &mut state.metrics.hash_join,\n    )\n}\n\n/// Shared logic for HashScanUnmatched/HashNextUnmatched: find the next unmatched\n/// entry, loading spilled partitions as needed.\n#[allow(clippy::too_many_arguments)]\nfn advance_unmatched_scan(\n    hash_table: &mut HashTable,\n    registers: &mut [Register],\n    pc: &mut u32,\n    dest_reg: usize,\n    target_pc: u32,\n    payload_dest_reg: Option<usize>,\n    num_payload: usize,\n    metrics: &mut HashJoinMetrics,\n) -> Result<InsnFunctionStepResult> {\n    if hash_table.has_spilled() {\n        if let Some(partition_idx) = hash_table.unmatched_scan_current_partition() {\n            if !hash_table.is_partition_loaded(partition_idx) {\n                match hash_table.load_spilled_partition(partition_idx, Some(metrics))? {\n                    crate::types::IOResult::Done(()) => {}\n                    crate::types::IOResult::IO(io) => {\n                        return Ok(InsnFunctionStepResult::IO(io));\n                    }\n                }\n            }\n        }\n    }\n\n    loop {\n        match hash_table.next_unmatched() {\n            Some(entry) => {\n                registers[dest_reg].set_int(entry.rowid);\n                write_hash_payload_to_registers(registers, entry, payload_dest_reg, num_payload);\n                *pc += 1;\n                return Ok(InsnFunctionStepResult::Step);\n            }\n            None => {\n                if hash_table.has_spilled() {\n                    if let Some(partition_idx) = hash_table.unmatched_scan_current_partition() {\n                        if !hash_table.is_partition_loaded(partition_idx) {\n                            match hash_table.load_spilled_partition(partition_idx, Some(metrics))? {\n                                crate::types::IOResult::Done(()) => continue,\n                                crate::types::IOResult::IO(io) => {\n                                    return Ok(InsnFunctionStepResult::IO(io));\n                                }\n                            }\n                        }\n                    }\n                }\n                *pc = target_pc;\n                return Ok(InsnFunctionStepResult::Step);\n            }\n        }\n    }\n}\n\npub fn op_hash_grace_init(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        HashGraceInit {\n            hash_table_id,\n            target_pc,\n        },\n        insn\n    );\n    let hash_table_id = *hash_table_id as usize;\n\n    let Some(hash_table) = state.hash_tables.get_mut(&hash_table_id) else {\n        state.pc = target_pc.as_offset_int();\n        return Ok(InsnFunctionStepResult::Step);\n    };\n\n    // If no spilling occurred, skip grace processing\n    if !hash_table.has_grace_partitions() {\n        state.pc = target_pc.as_offset_int();\n        return Ok(InsnFunctionStepResult::Step);\n    }\n\n    // Finalize probe spill\n    match hash_table.finalize_probe_spill(Some(&mut state.metrics.hash_join))? {\n        IOResult::Done(()) => {}\n        IOResult::IO(io) => {\n            return Ok(InsnFunctionStepResult::IO(io));\n        }\n    }\n\n    // Initialize grace processing\n    if !hash_table.grace_begin() {\n        state.pc = target_pc.as_offset_int();\n        return Ok(InsnFunctionStepResult::Step);\n    }\n\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_hash_grace_load_partition(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        HashGraceLoadPartition {\n            hash_table_id,\n            target_pc,\n        },\n        insn\n    );\n    let hash_table_id = *hash_table_id as usize;\n\n    let hash_table = state.hash_tables.get_mut(&hash_table_id).ok_or_else(|| {\n        LimboError::InternalError(format!(\"Hash table not found with ID: {hash_table_id}\"))\n    })?;\n\n    match hash_table.grace_load_current_partition(Some(&mut state.metrics.hash_join))? {\n        IOResult::Done(true) => {\n            state.pc += 1;\n            Ok(InsnFunctionStepResult::Step)\n        }\n        IOResult::Done(false) => {\n            state.pc = target_pc.as_offset_int();\n            Ok(InsnFunctionStepResult::Step)\n        }\n        IOResult::IO(io) => Ok(InsnFunctionStepResult::IO(io)),\n    }\n}\n\npub fn op_hash_grace_next_probe(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        HashGraceNextProbe {\n            hash_table_id,\n            key_start_reg,\n            num_keys,\n            probe_rowid_dest,\n            target_pc,\n        },\n        insn\n    );\n    let hash_table_id = *hash_table_id as usize;\n    let key_start_reg = *key_start_reg as usize;\n    let num_keys = *num_keys as usize;\n    let probe_rowid_dest = *probe_rowid_dest as usize;\n\n    let hash_table = state.hash_tables.get_mut(&hash_table_id).ok_or_else(|| {\n        mark_unlikely();\n        LimboError::InternalError(format!(\"Hash table not found with ID: {hash_table_id}\"))\n    })?;\n\n    match hash_table.grace_next_probe_entry()? {\n        IOResult::Done(Some(entry)) => {\n            // Write probe keys to registers\n            for (i, value) in entry.key_values.into_iter().enumerate() {\n                if i < num_keys {\n                    state.registers[key_start_reg + i].set_value(value);\n                }\n            }\n            // Write probe rowid\n            state.registers[probe_rowid_dest].set_int(entry.probe_rowid);\n            state.pc += 1;\n            Ok(InsnFunctionStepResult::Step)\n        }\n        IOResult::Done(None) => {\n            state.pc = target_pc.as_offset_int();\n            Ok(InsnFunctionStepResult::Step)\n        }\n        IOResult::IO(io) => Ok(InsnFunctionStepResult::IO(io)),\n    }\n}\n\npub fn op_hash_grace_advance_partition(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        HashGraceAdvancePartition {\n            hash_table_id,\n            target_pc,\n        },\n        insn\n    );\n    let hash_table_id = *hash_table_id as usize;\n\n    let hash_table = state.hash_tables.get_mut(&hash_table_id).ok_or_else(|| {\n        mark_unlikely();\n        LimboError::InternalError(format!(\"Hash table not found with ID: {hash_table_id}\"))\n    })?;\n\n    if hash_table.grace_advance_partition() {\n        state.pc += 1;\n    } else {\n        state.pc = target_pc.as_offset_int();\n    }\n    Ok(InsnFunctionStepResult::Step)\n}\n\nfn apply_affinity_char(target: &mut Register, affinity: Affinity) -> bool {\n    if let Register::Value(value) = target {\n        if matches!(value, Value::Blob(_)) {\n            return true;\n        }\n\n        match affinity {\n            Affinity::Blob => return true,\n\n            Affinity::Text => {\n                if matches!(value, Value::Text(_) | Value::Null) {\n                    return true;\n                }\n                let text = value.to_string();\n                *value = Value::Text(text.into());\n                return true;\n            }\n\n            Affinity::Integer | Affinity::Numeric => {\n                if matches!(value, Value::Numeric(Numeric::Integer(_))) {\n                    return true;\n                }\n                if !matches!(value, Value::Text(_) | Value::Numeric(Numeric::Float(_))) {\n                    return true;\n                }\n\n                if let Value::Numeric(Numeric::Float(fl)) = *value {\n                    // For floats, try to convert to integer if it's exact\n                    // This is similar to sqlite3VdbeIntegerAffinity\n                    return try_float_to_integer_affinity(value, f64::from(fl));\n                }\n\n                if let Value::Text(t) = value {\n                    let text = trim_ascii_whitespace(t.as_str());\n\n                    // Handle hex numbers - they shouldn't be converted\n                    if text.starts_with(\"0x\") {\n                        return false;\n                    }\n\n                    let (parse_result, parsed_value) = try_for_float(text.as_bytes());\n                    let num = match parse_result {\n                        NumericParseResult::NotNumeric | NumericParseResult::ValidPrefixOnly => {\n                            return false;\n                        }\n                        NumericParseResult::PureInteger | NumericParseResult::HasDecimalOrExp => {\n                            match parsed_value {\n                                ParsedNumber::Integer(i) => Value::from_i64(i),\n                                ParsedNumber::Float(f) => Value::from_f64(f),\n                                ParsedNumber::None => return false,\n                            }\n                        }\n                    };\n\n                    match num {\n                        Value::Numeric(Numeric::Integer(i)) => {\n                            *value = Value::from_i64(i);\n                            return true;\n                        }\n                        Value::Numeric(Numeric::Float(fl)) => {\n                            // For both Numeric and Integer affinity, try to convert\n                            // float to int if exact. SQLite treats INTEGER identically\n                            // to NUMERIC here: both enter applyNumericAffinity() with\n                            // bTryForInt=1 (sqlite/src/vdbe.c:403-408).\n                            return try_float_to_integer_affinity(value, f64::from(fl));\n                        }\n                        other => {\n                            *value = other;\n                            return true;\n                        }\n                    }\n                }\n\n                return false;\n            }\n\n            Affinity::Real => match value {\n                Value::Numeric(Numeric::Integer(i)) => {\n                    *value = Value::from_f64(*i as f64);\n                    return true;\n                }\n                Value::Numeric(Numeric::Float(_)) | Value::Null => {\n                    return true;\n                }\n                Value::Text(t) => {\n                    let text = trim_ascii_whitespace(t.as_str());\n                    if text.starts_with(\"0x\") {\n                        return false;\n                    }\n\n                    let (parse_result, parsed_value) = try_for_float(text.as_bytes());\n                    let coerced = match parse_result {\n                        NumericParseResult::NotNumeric | NumericParseResult::ValidPrefixOnly => {\n                            return false;\n                        }\n                        NumericParseResult::PureInteger | NumericParseResult::HasDecimalOrExp => {\n                            match parsed_value {\n                                ParsedNumber::Integer(i) => Value::from_f64(i as f64),\n                                ParsedNumber::Float(f) => Value::from_f64(f),\n                                ParsedNumber::None => return false,\n                            }\n                        }\n                    };\n\n                    *value = coerced;\n                    return true;\n                }\n                _ => return true,\n            },\n        }\n    }\n\n    true\n}\n\nfn try_float_to_integer_affinity(value: &mut Value, fl: f64) -> bool {\n    // Check if the float can be exactly represented as an integer\n    if let Ok(int_val) = cast_real_to_integer(fl) {\n        // Additional check: ensure round-trip conversion is exact\n        // and value is within safe bounds (similar to SQLite's checks)\n        if (int_val as f64) == fl && int_val > i64::MIN + 1 && int_val < i64::MAX - 1 {\n            *value = Value::from_i64(int_val);\n            return true;\n        }\n    }\n\n    // If we can't convert to exact integer, keep as float for Numeric affinity\n    // but return false to indicate the conversion wasn't \"complete\"\n    *value = Value::from_f64(fl);\n    false\n}\n\nfn parse_test_uint(reg: &Register) -> Result<Option<u64>> {\n    match reg.get_value() {\n        Value::Null => Ok(None),\n        Value::Numeric(Numeric::Integer(i)) => {\n            if *i < 0 {\n                Err(LimboError::InternalError(\n                    \"test_uint: negative value\".to_string(),\n                ))\n            } else {\n                Ok(Some(*i as u64))\n            }\n        }\n        Value::Text(t) => {\n            let s = t.to_string();\n            let v = s\n                .parse::<u64>()\n                .map_err(|_| LimboError::InternalError(format!(\"test_uint: invalid uint: {s}\")))?;\n            Ok(Some(v))\n        }\n        _ => Err(LimboError::InternalError(\n            \"test_uint: unsupported type\".to_string(),\n        )),\n    }\n}\n\n// Compat for applications that test for SQLite.\nfn execute_sqlite_version() -> String {\n    \"3.50.4\".to_string()\n}\n\nfn execute_turso_version(version_integer: i64) -> String {\n    let major = version_integer / 1_000_000;\n    let minor = (version_integer % 1_000_000) / 1_000;\n    let release = version_integer % 1_000;\n\n    format!(\"{major}.{minor}.{release}\")\n}\n\npub fn extract_int_value<V: AsValueRef>(value: V) -> i64 {\n    let value = value.as_value_ref();\n    match value {\n        ValueRef::Numeric(Numeric::Integer(i)) => i,\n        ValueRef::Numeric(Numeric::Float(f)) => {\n            let f = f64::from(f);\n            // Use sqlite3RealToI64 equivalent\n            if f < -9223372036854774784.0 {\n                i64::MIN\n            } else if f > 9223372036854774784.0 {\n                i64::MAX\n            } else {\n                f as i64\n            }\n        }\n        ValueRef::Text(t) => {\n            // Try to parse as integer, return 0 if failed\n            t.as_str().parse::<i64>().unwrap_or(0)\n        }\n        ValueRef::Blob(b) => {\n            // Try to parse blob as string then as integer\n            if let Ok(s) = std::str::from_utf8(b) {\n                s.parse::<i64>().unwrap_or(0)\n            } else {\n                0\n            }\n        }\n        ValueRef::Null => 0,\n    }\n}\n\npub fn op_max_pgcnt(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(MaxPgcnt { db, dest, new_max }, insn);\n\n    let pager = program.get_pager_from_database_index(db);\n    let result_value = if *new_max == 0 {\n        // If new_max is 0, just return current maximum without changing it\n        pager.get_max_page_count()\n    } else {\n        // Set new maximum page count (will be clamped to current database size)\n        return_if_io!(pager.set_max_page_count(*new_max as u32))\n    };\n\n    state.registers[*dest].set_int(result_value.into());\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\n/// State machine for PRAGMA journal_mode changes\n#[derive(Debug, Clone, Copy, Default)]\npub enum OpJournalModeSubState {\n    /// Initial state - read header to get current mode\n    #[default]\n    Start,\n    /// Checkpointing WAL/MVCC before mode change\n    Checkpoint,\n    /// Update the header with new version and get page reference\n    UpdateHeader,\n    /// Write page 1 to disk\n    WritePage,\n    /// Finalize - clear cache and setup new mode\n    Finalize,\n}\n\n/// Holds the state for the journal mode change operation\n#[derive(Default)]\npub struct OpJournalModeState {\n    pub sub_state: OpJournalModeSubState,\n    /// The previous journal mode (before the change)\n    pub prev_mode: Option<journal_mode::JournalMode>,\n    /// The new journal mode we're changing to\n    pub new_mode: Option<journal_mode::JournalMode>,\n    /// Checkpoint state machine for MVCC mode\n    pub checkpoint_sm: Option<StateMachine<CheckpointStateMachine<MvccClock>>>,\n    /// Page reference for writing header\n    pub page_ref: Option<PageRef>,\n}\n\npub fn op_journal_mode(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    match op_journal_mode_inner(program, state, insn, pager) {\n        Ok(result) => {\n            if !matches!(result, InsnFunctionStepResult::IO(_)) {\n                // Reset state if we are done with this instruction\n                state.op_journal_mode_state = Default::default();\n            }\n            Ok(result)\n        }\n        Err(err) => {\n            // Reset state on error\n            state.op_journal_mode_state = Default::default();\n            Err(err)\n        }\n    }\n}\n\nfn op_journal_mode_inner(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    use crate::storage::sqlite3_ondisk::begin_write_btree_page;\n\n    load_insn!(JournalMode { db, dest, new_mode }, insn);\n    let pager = program.get_pager_from_database_index(db);\n    let pager = &pager;\n\n    loop {\n        match state.op_journal_mode_state.sub_state {\n            OpJournalModeSubState::Start => {\n                // Read header to get current mode\n                let mv_store = program.connection.mv_store_for_db(*db);\n                let header_result = with_header(pager, mv_store.as_ref(), program, *db, |header| {\n                    header.read_version\n                });\n\n                let prev_mode_raw = return_if_io!(header_result);\n\n                let prev_mode_version = prev_mode_raw\n                    .to_version()\n                    .map_err(|val| LimboError::Corrupt(format!(\"Invalid read_version: {val}\")))?;\n\n                let prev_mode = journal_mode::JournalMode::from(prev_mode_version);\n                state.op_journal_mode_state.prev_mode = Some(prev_mode);\n\n                // If no new mode specified, just return current mode\n                let Some(mode_str) = new_mode else {\n                    let ret: &'static str = prev_mode.into();\n                    state.registers[*dest].set_text(Text::new(ret));\n                    state.pc += 1;\n                    return Ok(InsnFunctionStepResult::Step);\n                };\n\n                // Parse the new mode. If unknown or unsupported, silently return\n                // current mode (matches SQLite behavior).\n                let new_mode = match journal_mode::JournalMode::from_str(mode_str.as_str()) {\n                    Ok(mode) if mode.supported() => mode,\n                    _ => {\n                        let ret: &'static str = prev_mode.into();\n                        state.registers[*dest].set_text(Text::new(ret));\n                        state.pc += 1;\n                        return Ok(InsnFunctionStepResult::Step);\n                    }\n                };\n\n                // If same mode, just return\n                if prev_mode == new_mode {\n                    let ret: &'static str = new_mode.into();\n                    state.registers[*dest].set_text(Text::new(ret));\n                    state.pc += 1;\n                    return Ok(InsnFunctionStepResult::Step);\n                }\n\n                // Check if database is readonly - cannot change journal mode on readonly databases\n                if program.connection.is_readonly(*db) {\n                    return Err(LimboError::ReadOnly);\n                }\n\n                state.op_journal_mode_state.new_mode = Some(new_mode);\n                state.op_journal_mode_state.sub_state = OpJournalModeSubState::Checkpoint;\n            }\n\n            OpJournalModeSubState::Checkpoint => {\n                // Checkpoint WAL or MVCC before changing mode\n                let mv_store = program.connection.mv_store_for_db(*db);\n                if let Some(mv_store) = mv_store.as_ref() {\n                    // MVCC checkpoint using state machine\n                    if state.op_journal_mode_state.checkpoint_sm.is_none() {\n                        state.op_journal_mode_state.checkpoint_sm =\n                            Some(StateMachine::new(CheckpointStateMachine::new(\n                                pager.clone(),\n                                mv_store.clone(),\n                                program.connection.clone(),\n                                true,\n                                program.connection.get_sync_mode(),\n                            )));\n                    }\n\n                    let ckpt_sm = state.op_journal_mode_state.checkpoint_sm.as_mut().unwrap();\n                    return_if_io!(ckpt_sm.step(&()));\n                    state.op_journal_mode_state.checkpoint_sm = None;\n                    state.op_journal_mode_state.sub_state = OpJournalModeSubState::UpdateHeader;\n                } else {\n                    // WAL checkpoint\n                    let checkpoint_result = pager.checkpoint(\n                        CheckpointMode::Truncate {\n                            upper_bound_inclusive: None,\n                        },\n                        program.connection.get_sync_mode(),\n                        false, // Don't clear cache yet, we'll do it in Finalize\n                    );\n                    return_if_io!(checkpoint_result);\n                    state.op_journal_mode_state.sub_state = OpJournalModeSubState::UpdateHeader;\n                }\n            }\n\n            OpJournalModeSubState::UpdateHeader => {\n                let new_mode = state\n                    .op_journal_mode_state\n                    .new_mode\n                    .expect(\"new_mode should be set\");\n                let new_version = new_mode\n                    .as_version()\n                    .expect(\"Should be a supported Journal Mode\");\n                let raw_version = RawVersion::from(new_version);\n\n                // Get the header page reference (handles both initialized and uninitialized databases)\n                // This uses the pager's cache and won't fail for empty database files\n                let header_ref =\n                    return_if_io!(crate::storage::pager::HeaderRefMut::from_pager(pager));\n\n                // Update the header version\n                {\n                    let header = header_ref.borrow_mut();\n                    header.read_version = raw_version;\n                    header.write_version = raw_version;\n                }\n\n                // Save the page reference for writing\n                state.op_journal_mode_state.page_ref = Some(header_ref.page().clone());\n                // Skip ReadPage and go directly to WritePage\n                state.op_journal_mode_state.sub_state = OpJournalModeSubState::WritePage;\n            }\n\n            OpJournalModeSubState::WritePage => {\n                // Write page 1 to disk to flush the header\n                let page = state\n                    .op_journal_mode_state\n                    .page_ref\n                    .as_ref()\n                    .expect(\"page_ref should be set\");\n                let completion = begin_write_btree_page(pager, page)?;\n                state.op_journal_mode_state.sub_state = OpJournalModeSubState::Finalize;\n                return Ok(InsnFunctionStepResult::IO(IOCompletions::Single(\n                    completion,\n                )));\n            }\n\n            OpJournalModeSubState::Finalize => {\n                let new_mode = state\n                    .op_journal_mode_state\n                    .new_mode\n                    .expect(\"new_mode should be set\");\n\n                // Clear page cache\n                pager.clear_page_cache(true);\n\n                // Setup new mode\n                if matches!(new_mode, journal_mode::JournalMode::Mvcc) {\n                    if program.connection.get_capture_data_changes_info().is_some() {\n                        return Err(LimboError::InternalError(\n                            \"cannot enable MVCC while CDC is active\".to_string(),\n                        ));\n                    }\n                    let db_path = program.connection.get_database_canonical_path();\n                    // todo(v): pass required encryption ctx to enable encryption with mvcc\n                    let mv_store = journal_mode::open_mv_store(\n                        pager.io.clone(),\n                        &db_path,\n                        program.connection.db.open_flags,\n                        program.connection.db.durable_storage.clone(),\n                        None,\n                    )?;\n                    program.connection.db.mv_store.store(Some(mv_store.clone()));\n                    program.connection.demote_to_mvcc_connection();\n                    mv_store.bootstrap(program.connection.clone())?;\n                }\n\n                if matches!(new_mode, journal_mode::JournalMode::Wal) {\n                    program.connection.db.mv_store.store(None);\n                }\n\n                // Return result\n                let ret: &'static str = new_mode.into();\n                state.registers[*dest].set_text(Text::new(ret));\n                state.pc += 1;\n\n                return Ok(InsnFunctionStepResult::Step);\n            }\n        }\n    }\n}\n\npub fn op_filter(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        Filter {\n            cursor_id,\n            target_pc,\n            key_reg,\n            num_keys\n        },\n        insn\n    );\n    let Some(filter) = state.get_bloom_filter(*cursor_id) else {\n        // always safe to fall though, no filter present\n        state.pc += 1;\n        return Ok(InsnFunctionStepResult::Step);\n    };\n    let contains = if *num_keys == 1 {\n        // Single key optimization, avoid allocating a Vec\n        let value = state.registers[*key_reg].get_value();\n        if matches!(value, Value::Null) {\n            // its always safe to fall through, so this *should* be `true` but\n            // since it's always an equality predicate and we have a NULL value,\n            // we can just short-circuit to false here.\n            false\n        } else {\n            filter.contains_value(value)\n        }\n    } else {\n        let values: Vec<&Value> = (0..*num_keys)\n            .map(|i| state.registers[*key_reg + i].get_value())\n            .collect();\n        if values.iter().any(|v| matches!(*v, Value::Null)) {\n            false\n        } else {\n            filter.contains_values(&values)\n        }\n    };\n\n    if !contains {\n        state.pc = target_pc.as_offset_int();\n    } else {\n        state.pc += 1;\n    }\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_filter_add(\n    _program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(\n        FilterAdd {\n            cursor_id,\n            key_reg,\n            num_keys\n        },\n        insn\n    );\n\n    if *num_keys == 1 {\n        let reg: *const Register = &state.registers[*key_reg];\n        let value = unsafe { &*reg }.get_value();\n        let filter = state.get_or_create_bloom_filter(*cursor_id);\n        filter.insert_value(value);\n    } else {\n        let values: Vec<Value> = (0..*num_keys)\n            .map(|i| state.registers[*key_reg + i].get_value().clone())\n            .collect();\n        let filter = state.get_or_create_bloom_filter(*cursor_id);\n        let value_refs: Vec<&Value> = values.iter().collect();\n        filter.insert_values(&value_refs);\n    }\n\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\nfn extract_pragma_int<T>(rows: &[Vec<Value>], pragma_name: &str) -> Result<T>\nwhere\n    T: TryFrom<i64>,\n{\n    rows.first()\n        .and_then(|row| row.first())\n        .and_then(|v| match v {\n            Value::Numeric(Numeric::Integer(i)) => T::try_from(*i).ok(),\n            _ => None,\n        })\n        .ok_or_else(|| {\n            LimboError::InternalError(format!(\"failed to read {pragma_name} from source\"))\n        })\n}\n\n/// Sub-states for the VACUUM INTO operation state machine.\n#[derive(Default)]\npub(crate) enum OpVacuumIntoSubState {\n    /// Initial state - validate preconditions and create destination database\n    #[default]\n    Init,\n    /// Step through schema query to collect rows\n    CollectSchemaRows {\n        dest_conn: Arc<Connection>,\n        schema_stmt: Box<crate::Statement>,\n    },\n    /// Prepare CREATE statement on destination (idx into schema_rows)\n    PrepareDestSchema {\n        dest_conn: Arc<Connection>,\n        idx: usize,\n    },\n    /// Step through CREATE statement on destination (async)\n    StepDestSchema {\n        dest_conn: Arc<Connection>,\n        dest_schema_stmt: Box<crate::Statement>,\n        idx: usize,\n    },\n    /// Start copying a table - prepare column info query\n    StartCopyTable {\n        dest_conn: Arc<Connection>,\n        table_idx: usize,\n    },\n    /// Collect column info for current table\n    CollectColumnInfo {\n        dest_conn: Arc<Connection>,\n        column_stmt: Box<crate::Statement>,\n        table_idx: usize,\n    },\n    /// Select rows from source table and insert into destination\n    CopyRows {\n        dest_conn: Arc<Connection>,\n        select_stmt: Box<crate::Statement>,\n        dest_insert_stmt: Box<crate::Statement>,\n        table_idx: usize,\n    },\n    /// Step through INSERT statement on destination (async)\n    StepDestInsert {\n        dest_conn: Arc<Connection>,\n        select_stmt: Box<crate::Statement>,\n        dest_insert_stmt: Box<crate::Statement>,\n        table_idx: usize,\n    },\n    /// Copy meta values (user_version, application_id) from source to destination\n    CopyMetaValues { dest_conn: Arc<Connection> },\n    /// Create triggers and views after data copy (to avoid triggers firing during copy)\n    PrepareTriggersViews {\n        dest_conn: Arc<Connection>,\n        idx: usize,\n    },\n    /// Step through CREATE TRIGGER/VIEW statement on destination\n    StepTriggersViews {\n        dest_conn: Arc<Connection>,\n        dest_schema_stmt: Box<crate::Statement>,\n        idx: usize,\n    },\n    /// Operation complete\n    Done { dest_conn: Arc<Connection> },\n}\n\n/// Holds the state for the VACUUM INTO operation.\n#[derive(Default)]\npub(crate) struct OpVacuumIntoState {\n    sub_state: OpVacuumIntoSubState,\n    /// Keep dest_db alive while vacuum is in progress.\n    #[allow(dead_code)]\n    dest_db: Option<Arc<crate::Database>>,\n    /// Schema rows: [(type, name, tbl_name, sql), ...]\n    schema_rows: Vec<Vec<Value>>,\n    /// Names of tables to copy data for\n    table_names: Vec<String>,\n    /// Column names for the current table being copied\n    current_table_columns: Vec<String>,\n    /// Meta values read from source database header\n    source_user_version: i32,\n    source_application_id: i32,\n}\n\n/// VACUUM INTO - create a compacted copy of the database at the specified path.\n///\n/// This is an async state machine implementation that yields on I/O operations.\n/// It:\n/// 1. Creates a new database at the destination path with matching page_size\n/// 2. Queries sqlite_schema for all schema objects (tables, indexes, triggers, views)\n/// 3. Creates tables and indexes in destination (skipping sqlite_sequence - it's\n///    auto-created when AUTOINCREMENT tables are created, see translate/schema.rs)\n/// 4. Copies data for each table, including sqlite_sequence to preserve AUTOINCREMENT counters\n/// 5. Copies meta values (user_version, application_id) from source to destination\n/// 6. Creates triggers and views last (after data copy to avoid triggers firing during copy)\npub fn op_vacuum_into(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> Result<InsnFunctionStepResult> {\n    match op_vacuum_into_inner(program, state, insn) {\n        Ok(InsnFunctionStepResult::Step) => {\n            // Instruction complete, reset state\n            state.op_vacuum_into_state = None;\n            Ok(InsnFunctionStepResult::Step)\n        }\n        Ok(InsnFunctionStepResult::IO(io)) => {\n            // Waiting for I/O, keep state for resumption\n            Ok(InsnFunctionStepResult::IO(io))\n        }\n        Ok(InsnFunctionStepResult::Done | InsnFunctionStepResult::Row) => {\n            unreachable!(\"op_vacuum_into_inner only returns Step or IO\")\n        }\n        Err(err) => {\n            // Reset state on error\n            state.op_vacuum_into_state = None;\n            Err(err)\n        }\n    }\n}\n\nfn op_vacuum_into_inner(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n) -> Result<InsnFunctionStepResult> {\n    load_insn!(VacuumInto { dest_path }, insn);\n\n    let vacuum_state = state\n        .op_vacuum_into_state\n        .get_or_insert_with(OpVacuumIntoState::default);\n\n    loop {\n        let current_sub_state = std::mem::take(&mut vacuum_state.sub_state);\n\n        match current_sub_state {\n            OpVacuumIntoSubState::Init => {\n                // Check if we're in a transaction\n                // as vacuum cannot be run inside a transaction\n                if !program.connection.auto_commit.load(Ordering::SeqCst) {\n                    return Err(LimboError::TxError(\n                        \"cannot VACUUM INTO from within a transaction\".to_string(),\n                    ));\n                }\n\n                // we always vacuum into a new file, so check if it exists\n                if std::path::Path::new(dest_path).exists() {\n                    return Err(LimboError::ParseError(format!(\n                        \"output file already exists: {dest_path}\"\n                    )));\n                }\n\n                // make sure to create destination database with same experimental features as source\n                // Always use PlatformIO for the destination file, even if source is in-memory.\n                // This ensures VACUUM INTO actually writes to disk.\n                let io: Arc<dyn crate::IO> = Arc::new(crate::io::PlatformIO::new()?);\n                let source_db = &program.connection.db;\n                let dest_opts = crate::DatabaseOpts::new()\n                    .with_views(source_db.experimental_views_enabled())\n                    .with_index_method(source_db.experimental_index_method_enabled());\n\n                program.connection.execute(\"BEGIN\")?;\n                // lets set the same meta values as source db\n                let user_version: i32 = extract_pragma_int(\n                    &program.connection.pragma_query(\"user_version\")?,\n                    \"user_version\",\n                )?;\n                let application_id: i32 = extract_pragma_int(\n                    &program.connection.pragma_query(\"application_id\")?,\n                    \"application_id\",\n                )?;\n                let page_size: u32 = extract_pragma_int(\n                    &program.connection.pragma_query(\"page_size\")?,\n                    \"page_size\",\n                )?;\n\n                let reserved_space = {\n                    let pager = program.connection.pager.load();\n                    let reserved_space: u8 = match program.connection.get_reserved_bytes() {\n                        Some(val) => val,\n                        None => io.block(|| pager.with_header(|header| header.reserved_space))?,\n                    };\n                    reserved_space\n                };\n\n                let dest_db = crate::Database::open_file_with_flags(\n                    io,\n                    dest_path,\n                    OpenFlags::Create,\n                    dest_opts,\n                    None,\n                )?;\n                let dest_conn = dest_db.connect()?;\n                dest_conn.reset_page_size(page_size)?;\n                // set reserved_space on destination to match source\n                // this is important for databases using encryption or checksums\n                // must be set before page 1 is allocated (before any schema operations)\n                dest_conn.set_reserved_bytes(reserved_space)?;\n\n                // Enable MVCC on destination if source has it enabled\n                // Must be done before any schema operations to ensure the log file is created\n                if program.connection.db.mvcc_enabled() {\n                    dest_conn.execute(\"PRAGMA journal_mode = 'mvcc'\")?;\n                }\n\n                // Performance optimizations for destination database:\n                // 1. Disable fsync - destination is a new file, if crash occurs we just delete it\n                // 2. Disable foreign key checks - source data is already consistent\n                // These match SQLite's vacuum.c optimizations (PAGER_SYNCHRONOUS_OFF, ~SQLITE_ForeignKeys)\n                dest_conn.execute(\"PRAGMA synchronous = OFF\")?;\n                dest_conn.execute(\"PRAGMA foreign_keys = OFF\")?;\n\n                // Wrap all operations in a single transaction for atomicity and performance.\n                // This batches all writes and ensures destination is either empty or complete.\n                dest_conn.execute(\"BEGIN\")?;\n\n                // Exclude the MVCC metadata table from the vacuum destination — it is an\n                // internal artifact of mvcc mode and must not appear in a\n                // standalone SQLite file produced by VACUUM INTO.\n                let schema_sql = format!(\n                    \"SELECT type, name, tbl_name, sql FROM sqlite_schema WHERE sql IS NOT NULL AND name <> '{}' ORDER BY CASE type WHEN 'table' THEN 1 WHEN 'index' THEN 2 WHEN 'trigger' THEN 3 WHEN 'view' THEN 4 ELSE 5 END\",\n                    crate::mvcc::database::MVCC_META_TABLE_NAME\n                );\n                let schema_stmt = program.connection.prepare(schema_sql.as_str())?;\n\n                vacuum_state.dest_db = Some(dest_db);\n                vacuum_state.source_user_version = user_version;\n                vacuum_state.source_application_id = application_id;\n\n                vacuum_state.sub_state = OpVacuumIntoSubState::CollectSchemaRows {\n                    dest_conn,\n                    schema_stmt: Box::new(schema_stmt),\n                };\n                continue;\n            }\n\n            OpVacuumIntoSubState::CollectSchemaRows {\n                dest_conn,\n                mut schema_stmt,\n            } => {\n                // Collect rows from sqlite_schema query: (type, name, tbl_name, sql)\n                // These define all tables, indexes, triggers, and views to recreate in destination\n                match schema_stmt.step()? {\n                    crate::StepResult::Row => {\n                        let row = schema_stmt\n                            .row()\n                            .expect(\"StepResult::Row but row() returned None\");\n                        let values: Vec<Value> = row.get_values().cloned().collect();\n                        vacuum_state.schema_rows.push(values);\n                        vacuum_state.sub_state = OpVacuumIntoSubState::CollectSchemaRows {\n                            dest_conn,\n                            schema_stmt,\n                        };\n                        continue;\n                    }\n                    crate::StepResult::Done => {\n                        // Extract table names for data copy phase\n                        // Include sqlite_sequence for AUTOINCREMENT counters, but not other sqlite_ tables\n                        vacuum_state.table_names = vacuum_state\n                            .schema_rows\n                            .iter()\n                            .filter_map(|row| {\n                                if row.len() >= 2 {\n                                    if let (Value::Text(type_val), Value::Text(name_val)) =\n                                        (&row[0], &row[1])\n                                    {\n                                        let name = name_val.as_str();\n                                        if type_val.as_str() == \"table\"\n                                            && (!name.starts_with(\"sqlite_\")\n                                                || name == \"sqlite_sequence\")\n                                            && name != crate::mvcc::database::MVCC_META_TABLE_NAME\n                                        {\n                                            return Some(name.to_string());\n                                        }\n                                    }\n                                }\n                                None\n                            })\n                            .collect();\n\n                        vacuum_state.sub_state =\n                            OpVacuumIntoSubState::PrepareDestSchema { dest_conn, idx: 0 };\n                        continue;\n                    }\n                    crate::StepResult::IO => {\n                        let io = schema_stmt\n                            .take_io_completions()\n                            .expect(\"StepResult::IO returned but no completions available\");\n                        vacuum_state.sub_state = OpVacuumIntoSubState::CollectSchemaRows {\n                            dest_conn,\n                            schema_stmt,\n                        };\n                        return Ok(InsnFunctionStepResult::IO(io));\n                    }\n                    crate::StepResult::Busy | crate::StepResult::Interrupt => {\n                        return Err(LimboError::Busy);\n                    }\n                }\n            }\n\n            OpVacuumIntoSubState::PrepareDestSchema { dest_conn, idx } => {\n                let schema_rows_len = vacuum_state.schema_rows.len();\n                turso_assert!(\n                    idx <= schema_rows_len,\n                    \"idx incremented past end of schema_rows\",\n                    { \"idx\": idx, \"schema_rows_len\": schema_rows_len }\n                );\n                if idx == schema_rows_len {\n                    // Done creating schema, start copying data\n                    vacuum_state.sub_state = OpVacuumIntoSubState::StartCopyTable {\n                        dest_conn,\n                        table_idx: 0,\n                    };\n                    continue;\n                }\n\n                let row = &vacuum_state.schema_rows[idx];\n                turso_assert!(\n                    row.len() == 4,\n                    \"schema row should have exactly 4 columns (type, name, tbl_name, sql)\",\n                    { \"row_len\": row.len() }\n                );\n\n                // Skip triggers and views - they'll be created after data copy\n                // to avoid triggers firing during data copy\n                if let Value::Text(type_val) = &row[0] {\n                    let type_str = type_val.as_str();\n                    if type_str == \"trigger\" || type_str == \"view\" {\n                        vacuum_state.sub_state = OpVacuumIntoSubState::PrepareDestSchema {\n                            dest_conn,\n                            idx: idx + 1,\n                        };\n                        continue;\n                    }\n                }\n\n                // Skip sqlite_sequence in schema creation phase. When we create an AUTOINCREMENT\n                // table, Turso automatically creates sqlite_sequence if it doesn't exist (see\n                // translate/schema.rs). Since schema_rows order depends on sqlite_schema rowids,\n                // an AUTOINCREMENT table may appear before sqlite_sequence. If we create that\n                // table first (which auto-creates sqlite_sequence), then later try to run\n                // \"CREATE TABLE sqlite_sequence(name,seq)\", it fails with \"table already exists\".\n                // We still copy sqlite_sequence data in StartCopyTable to preserve counters.\n                if let Value::Text(name_val) = &row[1] {\n                    if name_val.as_str() == \"sqlite_sequence\" {\n                        vacuum_state.sub_state = OpVacuumIntoSubState::PrepareDestSchema {\n                            dest_conn,\n                            idx: idx + 1,\n                        };\n                        continue;\n                    }\n                }\n\n                // Query filters WHERE sql IS NOT NULL, so sql column must be text\n                let Value::Text(sql) = &row[3] else {\n                    unreachable!(\"sql column should be text (query has WHERE sql IS NOT NULL)\");\n                };\n                let sql_str = sql.as_str();\n\n                // Internal tables (e.g. __turso_internal_types) have a reserved\n                // name prefix that translate_create_table rejects for user SQL.\n                // Temporarily mark the dest connection as nested during prepare()\n                // so the reserved-name check is bypassed at compile time. We must\n                // NOT keep it nested during step() because that would prevent\n                // sub-statements from upgrading to write transactions.\n                let is_internal = matches!(&row[1], Value::Text(n) if n.as_str().starts_with(crate::schema::TURSO_INTERNAL_PREFIX));\n                if is_internal {\n                    dest_conn.start_nested();\n                }\n                let dest_stmt = dest_conn.prepare(sql_str);\n                if is_internal {\n                    dest_conn.end_nested();\n                }\n                let dest_stmt = dest_stmt?;\n                vacuum_state.sub_state = OpVacuumIntoSubState::StepDestSchema {\n                    dest_conn,\n                    dest_schema_stmt: Box::new(dest_stmt),\n                    idx,\n                };\n                continue;\n            }\n\n            OpVacuumIntoSubState::StepDestSchema {\n                dest_conn,\n                mut dest_schema_stmt,\n                idx,\n            } => match dest_schema_stmt.step()? {\n                crate::StepResult::Row => {\n                    unreachable!(\"CREATE statement unexpectedly returned a row\");\n                }\n                crate::StepResult::Done => {\n                    // After creating __turso_internal_types in the dest, load\n                    // custom type definitions from the source so that subsequent\n                    // CREATE TABLE statements for STRICT tables with custom type\n                    // columns can resolve those types.\n                    let row = &vacuum_state.schema_rows[idx];\n                    if matches!(&row[1], Value::Text(n) if n.as_str() == crate::schema::TURSO_TYPES_TABLE_NAME)\n                    {\n                        let source_types: Vec<(String, std::sync::Arc<crate::schema::TypeDef>)> = {\n                            let source_schema = program.connection.schema.read();\n                            source_schema\n                                .type_registry\n                                .iter()\n                                .filter(|(_, td)| !td.is_builtin)\n                                .map(|(name, td)| (name.clone(), td.clone()))\n                                .collect()\n                        };\n                        dest_conn.with_schema_mut(|dest_schema| {\n                            for (name, td) in source_types {\n                                dest_schema.type_registry.insert(name, td);\n                            }\n                        });\n                    }\n\n                    vacuum_state.sub_state = OpVacuumIntoSubState::PrepareDestSchema {\n                        dest_conn,\n                        idx: idx + 1,\n                    };\n                    continue;\n                }\n                crate::StepResult::IO => {\n                    let io = dest_schema_stmt\n                        .take_io_completions()\n                        .expect(\"StepResult::IO returned but no completions available\");\n                    vacuum_state.sub_state = OpVacuumIntoSubState::StepDestSchema {\n                        dest_conn,\n                        dest_schema_stmt,\n                        idx,\n                    };\n                    return Ok(InsnFunctionStepResult::IO(io));\n                }\n                crate::StepResult::Busy | crate::StepResult::Interrupt => {\n                    return Err(LimboError::Busy);\n                }\n            },\n\n            OpVacuumIntoSubState::StartCopyTable {\n                dest_conn,\n                table_idx,\n            } => {\n                let table_names_len = vacuum_state.table_names.len();\n                turso_assert!(\n                    table_idx <= table_names_len,\n                    \"table_idx incremented past end of table_names\",\n                    { \"table_idx\": table_idx, \"table_names_len\": table_names_len }\n                );\n                if table_idx == table_names_len {\n                    // Done copying all tables, now copy meta values\n                    vacuum_state.sub_state = OpVacuumIntoSubState::CopyMetaValues { dest_conn };\n                    continue;\n                }\n\n                let table_name = &vacuum_state.table_names[table_idx];\n                // Escape double quotes in table name for safe SQL\n                let escaped_table_name = table_name.replace('\"', \"\\\"\\\"\");\n                let pragma_sql = format!(\"PRAGMA table_info(\\\"{escaped_table_name}\\\")\");\n                let column_stmt = program.connection.prepare(&pragma_sql)?;\n                vacuum_state.current_table_columns.clear();\n                vacuum_state.sub_state = OpVacuumIntoSubState::CollectColumnInfo {\n                    dest_conn,\n                    column_stmt: Box::new(column_stmt),\n                    table_idx,\n                };\n                continue;\n            }\n\n            OpVacuumIntoSubState::CollectColumnInfo {\n                dest_conn,\n                mut column_stmt,\n                table_idx,\n            } => {\n                match column_stmt.step()? {\n                    crate::StepResult::Row => {\n                        let row = column_stmt\n                            .row()\n                            .expect(\"StepResult::Row but row() returned None\");\n                        // Column name is at index 1\n                        if let Value::Text(name) = row.get_value(1) {\n                            // Escape double quotes in column name for safe SQL\n                            let escaped_name = name.as_str().replace('\"', \"\\\"\\\"\");\n                            let col_name = format!(\"\\\"{escaped_name}\\\"\");\n                            vacuum_state.current_table_columns.push(col_name);\n                        }\n                        vacuum_state.sub_state = OpVacuumIntoSubState::CollectColumnInfo {\n                            dest_conn,\n                            column_stmt,\n                            table_idx,\n                        };\n                        continue;\n                    }\n                    crate::StepResult::Done => {\n                        if vacuum_state.current_table_columns.is_empty() {\n                            // if no columns, then db is corrupt\n                            return Err(LimboError::Corrupt(\n                                \"found a table without any columns\".to_string(),\n                            ));\n                        }\n\n                        // Prepare SELECT and INSERT statements for this table\n                        let table_name = &vacuum_state.table_names[table_idx];\n                        let escaped_table_name = table_name.replace('\"', \"\\\"\\\"\");\n                        let source_btree_table =\n                            program.connection.schema.read().get_btree_table(table_name);\n                        let rowid_alias = source_btree_table\n                            .as_ref()\n                            .filter(|table| table.has_rowid)\n                            .and_then(|table| {\n                                [\"rowid\", \"_rowid_\", \"oid\"]\n                                    .iter()\n                                    .copied()\n                                    .find(|alias| table.get_column(alias).is_none())\n                            });\n                        let rowid_alias_column_index = source_btree_table\n                            .as_ref()\n                            .and_then(|table| table.get_rowid_alias_column().map(|(idx, _)| idx));\n\n                        let mut data_columns: Vec<&str> = vacuum_state\n                            .current_table_columns\n                            .iter()\n                            .map(String::as_str)\n                            .collect();\n                        let mut excluded_rowid_alias_column = false;\n                        if rowid_alias.is_some() {\n                            if let Some(idx) = rowid_alias_column_index {\n                                turso_assert!(\n                                    idx < data_columns.len(),\n                                    \"rowid alias column index out of bounds for table columns\",\n                                    { \"idx\": idx, \"columns_len\": data_columns.len() }\n                                );\n                                data_columns.remove(idx);\n                                excluded_rowid_alias_column = true;\n                            }\n                        }\n                        let column_names = data_columns.join(\", \");\n\n                        let select_sql = match rowid_alias {\n                            Some(alias)\n                                if excluded_rowid_alias_column && column_names.is_empty() =>\n                            {\n                                format!(\"SELECT {alias} FROM \\\"{escaped_table_name}\\\"\")\n                            }\n                            Some(alias) if excluded_rowid_alias_column => {\n                                format!(\n                                    \"SELECT {alias}, {column_names} FROM \\\"{escaped_table_name}\\\"\"\n                                )\n                            }\n                            Some(alias) => {\n                                format!(\"SELECT {alias}, * FROM \\\"{escaped_table_name}\\\"\")\n                            }\n                            None => format!(\"SELECT * FROM \\\"{escaped_table_name}\\\"\"),\n                        };\n                        let select_stmt = program.connection.prepare(&select_sql)?;\n\n                        // Prepare INSERT statement once per table (reused for all rows)\n                        let bind_count = if rowid_alias.is_some() {\n                            data_columns.len() + 1\n                        } else {\n                            data_columns.len()\n                        };\n                        let placeholders: String =\n                            (0..bind_count).map(|_| \"?\").collect::<Vec<_>>().join(\", \");\n                        let insert_columns = if let Some(alias) = rowid_alias {\n                            if column_names.is_empty() {\n                                alias.to_string()\n                            } else {\n                                format!(\"{alias}, {column_names}\")\n                            }\n                        } else {\n                            column_names\n                        };\n                        let insert_sql = format!(\n                            \"INSERT INTO \\\"{escaped_table_name}\\\" ({insert_columns}) VALUES ({placeholders})\"\n                        );\n\n                        // Internal tables need nested mode to bypass \"may not\n                        // be modified\" checks during prepare (compile time).\n                        let is_internal =\n                            table_name.starts_with(crate::schema::TURSO_INTERNAL_PREFIX);\n                        if is_internal {\n                            dest_conn.start_nested();\n                        }\n                        let dest_insert_stmt = dest_conn.prepare(&insert_sql);\n                        if is_internal {\n                            dest_conn.end_nested();\n                        }\n                        let dest_insert_stmt = dest_insert_stmt?;\n\n                        vacuum_state.sub_state = OpVacuumIntoSubState::CopyRows {\n                            dest_conn,\n                            select_stmt: Box::new(select_stmt),\n                            dest_insert_stmt: Box::new(dest_insert_stmt),\n                            table_idx,\n                        };\n                        continue;\n                    }\n                    crate::StepResult::IO => {\n                        let io = column_stmt\n                            .take_io_completions()\n                            .expect(\"StepResult::IO returned but no completions available\");\n                        vacuum_state.sub_state = OpVacuumIntoSubState::CollectColumnInfo {\n                            dest_conn,\n                            column_stmt,\n                            table_idx,\n                        };\n                        return Ok(InsnFunctionStepResult::IO(io));\n                    }\n                    crate::StepResult::Busy | crate::StepResult::Interrupt => {\n                        return Err(LimboError::Busy);\n                    }\n                }\n            }\n\n            OpVacuumIntoSubState::CopyRows {\n                dest_conn,\n                mut select_stmt,\n                mut dest_insert_stmt,\n                table_idx,\n            } => match select_stmt.step()? {\n                crate::StepResult::Row => {\n                    let row = select_stmt\n                        .row()\n                        .expect(\"StepResult::Row but row() returned None\");\n\n                    let values: Vec<Value> = row.get_values().cloned().collect();\n\n                    dest_insert_stmt.reset()?;\n                    dest_insert_stmt.clear_bindings();\n                    for (i, value) in values.iter().enumerate() {\n                        let index =\n                            std::num::NonZero::new(i + 1).expect(\"i + 1 is always non-zero\");\n                        dest_insert_stmt.bind_at(index, value.clone());\n                    }\n\n                    vacuum_state.sub_state = OpVacuumIntoSubState::StepDestInsert {\n                        dest_conn,\n                        select_stmt,\n                        dest_insert_stmt,\n                        table_idx,\n                    };\n                    continue;\n                }\n                crate::StepResult::Done => {\n                    // Move to next table\n                    vacuum_state.sub_state = OpVacuumIntoSubState::StartCopyTable {\n                        dest_conn,\n                        table_idx: table_idx + 1,\n                    };\n                    continue;\n                }\n                crate::StepResult::IO => {\n                    let io = select_stmt\n                        .take_io_completions()\n                        .expect(\"StepResult::IO returned but no completions available\");\n                    vacuum_state.sub_state = OpVacuumIntoSubState::CopyRows {\n                        dest_conn,\n                        select_stmt,\n                        dest_insert_stmt,\n                        table_idx,\n                    };\n                    return Ok(InsnFunctionStepResult::IO(io));\n                }\n                crate::StepResult::Busy | crate::StepResult::Interrupt => {\n                    return Err(LimboError::Busy);\n                }\n            },\n\n            OpVacuumIntoSubState::StepDestInsert {\n                dest_conn,\n                select_stmt,\n                mut dest_insert_stmt,\n                table_idx,\n            } => match dest_insert_stmt.step()? {\n                crate::StepResult::Row => {\n                    unreachable!(\"INSERT statement unexpectedly returned a row\");\n                }\n                crate::StepResult::Done => {\n                    // Go back to get next row from source\n                    vacuum_state.sub_state = OpVacuumIntoSubState::CopyRows {\n                        dest_conn,\n                        select_stmt,\n                        dest_insert_stmt,\n                        table_idx,\n                    };\n                    continue;\n                }\n                crate::StepResult::IO => {\n                    let io = dest_insert_stmt\n                        .take_io_completions()\n                        .expect(\"StepResult::IO returned but no completions available\");\n                    vacuum_state.sub_state = OpVacuumIntoSubState::StepDestInsert {\n                        dest_conn,\n                        select_stmt,\n                        dest_insert_stmt,\n                        table_idx,\n                    };\n                    return Ok(InsnFunctionStepResult::IO(io));\n                }\n                crate::StepResult::Busy | crate::StepResult::Interrupt => {\n                    return Err(LimboError::Busy);\n                }\n            },\n\n            OpVacuumIntoSubState::CopyMetaValues { dest_conn } => {\n                // Copy meta values to destination database\n                // Use pragma_update to set user_version and application_id\n                // Note: schema_version is not copied - VACUUM INTO creates a new file so\n                // there's no cache to invalidate. The destination will have its own\n                // schema_version based on the schema operations performed.\n                dest_conn\n                    .pragma_update(\"user_version\", vacuum_state.source_user_version.to_string())?;\n                dest_conn.pragma_update(\n                    \"application_id\",\n                    vacuum_state.source_application_id.to_string(),\n                )?;\n\n                // Now create triggers and views (after data copy to avoid triggers firing)\n                vacuum_state.sub_state =\n                    OpVacuumIntoSubState::PrepareTriggersViews { dest_conn, idx: 0 };\n                continue;\n            }\n\n            OpVacuumIntoSubState::PrepareTriggersViews { dest_conn, idx } => {\n                let schema_rows_len = vacuum_state.schema_rows.len();\n                turso_assert!(\n                    idx <= schema_rows_len,\n                    \"idx incremented past end of schema_rows\",\n                    { \"idx\": idx, \"schema_rows_len\": schema_rows_len }\n                );\n                if idx == schema_rows_len {\n                    // Done creating triggers and views\n                    vacuum_state.sub_state = OpVacuumIntoSubState::Done { dest_conn };\n                    continue;\n                }\n\n                // We validated row.len() == 4 in PrepareDestSchema\n                let row = &vacuum_state.schema_rows[idx];\n\n                // Only process triggers and views in this phase\n                if let Value::Text(type_val) = &row[0] {\n                    let type_str = type_val.as_str();\n                    if type_str == \"trigger\" || type_str == \"view\" {\n                        if let Value::Text(sql) = &row[3] {\n                            let sql_str = sql.as_str();\n                            let dest_stmt = dest_conn.prepare(sql_str)?;\n                            vacuum_state.sub_state = OpVacuumIntoSubState::StepTriggersViews {\n                                dest_conn,\n                                dest_schema_stmt: Box::new(dest_stmt),\n                                idx,\n                            };\n                            continue;\n                        }\n                    }\n                }\n\n                // Skip non-trigger/view entries\n                vacuum_state.sub_state = OpVacuumIntoSubState::PrepareTriggersViews {\n                    dest_conn,\n                    idx: idx + 1,\n                };\n            }\n\n            OpVacuumIntoSubState::StepTriggersViews {\n                dest_conn,\n                mut dest_schema_stmt,\n                idx,\n            } => match dest_schema_stmt.step()? {\n                crate::StepResult::Row => {\n                    unreachable!(\"CREATE TRIGGER/VIEW statement unexpectedly returned a row\");\n                }\n                crate::StepResult::Done => {\n                    vacuum_state.sub_state = OpVacuumIntoSubState::PrepareTriggersViews {\n                        dest_conn,\n                        idx: idx + 1,\n                    };\n                    continue;\n                }\n                crate::StepResult::IO => {\n                    let io = dest_schema_stmt\n                        .take_io_completions()\n                        .expect(\"StepResult::IO returned but no completions available\");\n                    vacuum_state.sub_state = OpVacuumIntoSubState::StepTriggersViews {\n                        dest_conn,\n                        dest_schema_stmt,\n                        idx,\n                    };\n                    return Ok(InsnFunctionStepResult::IO(io));\n                }\n                crate::StepResult::Busy | crate::StepResult::Interrupt => {\n                    return Err(LimboError::Busy);\n                }\n            },\n\n            OpVacuumIntoSubState::Done { dest_conn } => {\n                // Commit the transaction that was started in Init state\n                dest_conn.execute(\"COMMIT\")?;\n                program.connection.execute(\"COMMIT\")?;\n\n                state.pc += 1;\n                return Ok(InsnFunctionStepResult::Step);\n            }\n        }\n    }\n}\n\nfn with_header<T, F>(\n    pager: &Pager,\n    mv_store: Option<&Arc<MvStore>>,\n    program: &Program,\n    db: usize,\n    f: F,\n) -> Result<IOResult<T>>\nwhere\n    F: Fn(&DatabaseHeader) -> T,\n{\n    if let Some(mv_store) = mv_store {\n        let tx_id = program.connection.get_mv_tx_id_for_db(db);\n        mv_store.with_header(f, tx_id.as_ref()).map(IOResult::Done)\n    } else {\n        pager.with_header(&f)\n    }\n}\n\npub fn with_header_mut<T, F>(\n    pager: &Pager,\n    mv_store: Option<&Arc<MvStore>>,\n    program: &Program,\n    db: usize,\n    f: F,\n) -> Result<IOResult<T>>\nwhere\n    F: Fn(&mut DatabaseHeader) -> T,\n{\n    if let Some(mv_store) = mv_store {\n        let tx_id = program.connection.get_mv_tx_id_for_db(db);\n        mv_store\n            .with_header_mut(f, tx_id.as_ref())\n            .map(IOResult::Done)\n    } else {\n        pager.with_header_mut(&f)\n    }\n}\n\nfn get_schema_cookie(\n    pager: &Arc<Pager>,\n    mv_store: Option<&Arc<MvStore>>,\n    program: &Program,\n    db: usize,\n) -> Result<IOResult<u32>> {\n    if let Some(mv_store) = mv_store {\n        let tx_id = program.connection.get_mv_tx_id_for_db(db);\n        mv_store\n            .with_header(|header| header.schema_cookie.get(), tx_id.as_ref())\n            .map(IOResult::Done)\n    } else {\n        pager.get_schema_cookie()\n    }\n}\n\n/// A root page in MVCC might still be marked as negative in schema. On restart it is automatically transformed to positive but in other cases\n/// we need to map it to positive if we can in case checkpoint happened.\nfn maybe_transform_root_page_to_positive(mvcc_store: Option<&Arc<MvStore>>, root_page: i64) -> i64 {\n    if let Some(mvcc_store) = mvcc_store {\n        if root_page < 0 {\n            mvcc_store.get_real_table_id(root_page)\n        } else {\n            root_page\n        }\n    } else {\n        root_page\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::translate::collate::CollationSeq;\n    use crate::vdbe::BranchOffset;\n    use crate::{Database, DatabaseOpts, MemoryIO, IO};\n\n    fn prepare_test_statement() -> Statement {\n        let io: Arc<dyn IO> = Arc::new(MemoryIO::new());\n        let db = Database::open_file_with_flags(\n            io,\n            \":memory:\",\n            OpenFlags::Create,\n            DatabaseOpts::new(),\n            None,\n        )\n        .unwrap();\n        let conn = db.connect().unwrap();\n        conn.prepare(\"SELECT 1;\").unwrap()\n    }\n\n    fn make_spilled_hash_table() -> (HashTable, Vec<Value>, usize) {\n        let io: Arc<dyn IO> = Arc::new(MemoryIO::new());\n        let config = HashTableConfig {\n            initial_buckets: 4,\n            mem_budget: 1024,\n            num_keys: 1,\n            collations: vec![CollationSeq::Binary],\n            temp_store: crate::TempStore::Default,\n            track_matched: false,\n            ..Default::default()\n        };\n        let mut ht = HashTable::new(config, io);\n\n        for i in 0..1024 {\n            match ht\n                .insert(vec![Value::from_i64(i)], i, vec![], None)\n                .unwrap()\n            {\n                IOResult::Done(()) => {}\n                IOResult::IO(_) => panic!(\"memory IO should complete synchronously\"),\n            }\n        }\n\n        match ht.finalize_build(None).unwrap() {\n            IOResult::Done(()) => {}\n            IOResult::IO(_) => panic!(\"memory IO should complete synchronously\"),\n        }\n        assert!(ht.has_spilled(), \"test requires spilled hash table\");\n\n        let probe_key = (0..1024)\n            .map(|i| vec![Value::from_i64(i)])\n            .find(|key| {\n                let partition_idx = ht.partition_for_keys(key);\n                !ht.is_partition_loaded(partition_idx)\n            })\n            .expect(\"expected an unloaded spilled partition\");\n        let partition_idx = ht.partition_for_keys(&probe_key);\n\n        (ht, probe_key, partition_idx)\n    }\n\n    #[test]\n    fn test_hash_probe_rejects_unloaded_spilled_partition_without_probe_rowid() {\n        let stmt = prepare_test_statement();\n        let (ht, probe_key, _) = make_spilled_hash_table();\n\n        let mut state = ProgramState::new(2, 0);\n        state.hash_tables.insert(7, ht);\n        state.set_register(0, Register::Value(probe_key[0].clone()));\n\n        let insn = Insn::HashProbe {\n            hash_table_id: 7,\n            key_start_reg: 0,\n            num_keys: 1,\n            dest_reg: 1,\n            target_pc: BranchOffset::Offset(99),\n            payload_dest_reg: None,\n            num_payload: 0,\n            probe_rowid_reg: None,\n        };\n\n        let err = match op_hash_probe(stmt.get_program(), &mut state, &insn, stmt.get_pager()) {\n            Ok(_) => {\n                panic!(\"HashProbe should reject grace-only probing without a loaded partition\")\n            }\n            Err(err) => err,\n        };\n\n        assert!(\n            matches!(err, LimboError::InternalError(ref message) if message.contains(\"probe_rowid_reg=None is grace-only\")),\n            \"unexpected error: {err:?}\"\n        );\n        assert_eq!(state.pc, 0, \"pc should not advance on invariant violation\");\n        assert!(\n            state.op_hash_probe_state.is_none(),\n            \"HashProbe should not stash resumable state for the removed fallback path\"\n        );\n    }\n\n    #[test]\n    fn test_hash_probe_allows_grace_style_probe_after_partition_preload() {\n        let stmt = prepare_test_statement();\n        let (mut ht, probe_key, partition_idx) = make_spilled_hash_table();\n\n        loop {\n            match ht.load_spilled_partition(partition_idx, None).unwrap() {\n                IOResult::Done(()) => break,\n                IOResult::IO(_) => continue,\n            }\n        }\n        assert!(\n            ht.is_partition_loaded(partition_idx),\n            \"grace-style probe requires the build partition to be resident\"\n        );\n\n        let expected_rowid = match &probe_key[0] {\n            Value::Numeric(Numeric::Integer(i)) => *i,\n            ref other => panic!(\"expected integer probe key, got {other:?}\"),\n        };\n\n        let mut state = ProgramState::new(2, 0);\n        state.hash_tables.insert(7, ht);\n        state.set_register(0, Register::Value(probe_key[0].clone()));\n\n        let insn = Insn::HashProbe {\n            hash_table_id: 7,\n            key_start_reg: 0,\n            num_keys: 1,\n            dest_reg: 1,\n            target_pc: BranchOffset::Offset(99),\n            payload_dest_reg: None,\n            num_payload: 0,\n            probe_rowid_reg: None,\n        };\n\n        let step = op_hash_probe(stmt.get_program(), &mut state, &insn, stmt.get_pager())\n            .expect(\"preloaded grace probe should succeed\");\n        assert!(matches!(step, InsnFunctionStepResult::Step));\n        assert_eq!(state.pc, 1, \"matching probe should fall through\");\n        assert_eq!(\n            state.get_register(1).get_value(),\n            &Value::from_i64(expected_rowid),\n            \"HashProbe should return the matching build rowid\"\n        );\n    }\n\n    #[test]\n    fn test_decr_jump_zero_non_integer_register_returns_error() {\n        let stmt = prepare_test_statement();\n\n        let mut state = ProgramState::new(1, 0);\n        state.set_register(0, Register::Value(Value::Text(\"not-an-int\".into())));\n\n        let insn = Insn::DecrJumpZero {\n            reg: 0,\n            target_pc: crate::vdbe::BranchOffset::Offset(1),\n        };\n\n        let err = match op_decr_jump_zero(stmt.get_program(), &mut state, &insn, stmt.get_pager()) {\n            Ok(_) => panic!(\"non-integer register must fail\"),\n            Err(err) => err,\n        };\n        assert!(matches!(err, LimboError::Constraint(message) if message == \"datatype mismatch\"));\n        assert_eq!(state.pc, 0);\n    }\n\n    #[test]\n    fn test_execute_sqlite_version() {\n        let version_integer = 3046001;\n        let expected = \"3.46.1\";\n        assert_eq!(execute_turso_version(version_integer), expected);\n    }\n\n    #[test]\n    fn test_ascii_whitespace_is_trimmed() {\n        // Regular ASCII whitespace SHOULD be trimmed\n        let ascii_whitespace_cases = vec![\n            (\" 12\", 12i64),            // space\n            (\"12 \", 12i64),            // trailing space\n            (\" 12 \", 12i64),           // both sides\n            (\"\\t42\\t\", 42i64),         // tab\n            (\"\\n99\\n\", 99i64),         // newline\n            (\" \\t\\n123\\r\\n \", 123i64), // mixed ASCII whitespace\n        ];\n\n        for (input, expected_int) in ascii_whitespace_cases {\n            let mut register = Register::Value(Value::Text(input.into()));\n            apply_affinity_char(&mut register, Affinity::Integer);\n\n            match register {\n                Register::Value(Value::Numeric(Numeric::Integer(i))) => {\n                    assert_eq!(\n                        i, expected_int,\n                        \"String '{input}' should convert to {expected_int}, got {i}\"\n                    );\n                }\n                other => {\n                    panic!(\n                        \"String '{input}' should be converted to integer {expected_int}, got {other:?}\"\n                    );\n                }\n            }\n        }\n    }\n\n    #[test]\n    fn test_non_breaking_space_not_trimmed() {\n        let test_strings = vec![\n            (\"12\\u{00A0}\", \"text\", 3),   // '12' + non-breaking space (3 chars, 4 bytes)\n            (\"\\u{00A0}12\", \"text\", 3),   // non-breaking space + '12' (3 chars, 4 bytes)\n            (\"12\\u{00A0}34\", \"text\", 5), // '12' + nbsp + '34' (5 chars, 6 bytes)\n        ];\n\n        for (input, _expected_type, expected_len) in test_strings {\n            let mut register = Register::Value(Value::Text(input.into()));\n            apply_affinity_char(&mut register, Affinity::Integer);\n\n            match register {\n                Register::Value(Value::Text(t)) => {\n                    assert_eq!(\n                        t.as_str().chars().count(),\n                        expected_len,\n                        \"String '{input}' should have {expected_len} characters\",\n                    );\n                }\n                Register::Value(Value::Numeric(Numeric::Integer(_))) => {\n                    panic!(\"String '{input}' should NOT be converted to integer\");\n                }\n                other => panic!(\"Unexpected value type: {other:?}\"),\n            }\n        }\n    }\n\n    #[test]\n    fn test_affinity_keeps_nan_inf_text() {\n        let cases = [\"nan\", \"inf\"];\n\n        for input in cases {\n            let mut register = Register::Value(Value::Text(input.into()));\n            apply_affinity_char(&mut register, Affinity::Integer);\n            match register {\n                Register::Value(Value::Text(t)) => {\n                    assert_eq!(t.as_str(), input, \"Unexpected conversion for '{input}'\");\n                }\n                other => {\n                    panic!(\"'{input}' should remain text, got {other:?}\");\n                }\n            }\n\n            let mut register = Register::Value(Value::Text(input.into()));\n            apply_affinity_char(&mut register, Affinity::Numeric);\n            match register {\n                Register::Value(Value::Text(t)) => {\n                    assert_eq!(t.as_str(), input, \"Unexpected conversion for '{input}'\");\n                }\n                other => {\n                    panic!(\"'{input}' should remain text, got {other:?}\");\n                }\n            }\n        }\n    }\n\n    #[test]\n    fn test_init_agg_payload_count() {\n        let mut payload = Vec::new();\n        init_agg_payload(&AggFunc::Count, &mut payload).unwrap();\n        assert_eq!(payload.len(), 1);\n        assert_eq!(payload[0], Value::from_i64(0));\n    }\n\n    #[test]\n    fn test_init_agg_payload_sum() {\n        let mut payload = Vec::new();\n        init_agg_payload(&AggFunc::Sum, &mut payload).unwrap();\n        assert_eq!(payload.len(), 4);\n        assert_eq!(payload[0], Value::Null); // acc\n        assert_eq!(payload[1], Value::from_f64(0.0)); // r_err\n        assert_eq!(payload[2], Value::from_i64(0)); // approx\n        assert_eq!(payload[3], Value::from_i64(0)); // ovrfl\n    }\n\n    #[test]\n    fn test_init_agg_payload_avg() {\n        let mut payload = Vec::new();\n        init_agg_payload(&AggFunc::Avg, &mut payload).unwrap();\n        assert_eq!(payload.len(), 3);\n        assert_eq!(payload[0], Value::from_f64(0.0)); // sum\n        assert_eq!(payload[1], Value::from_f64(0.0)); // r_err\n        assert_eq!(payload[2], Value::from_i64(0)); // count\n    }\n\n    #[test]\n    fn test_update_count_skips_null() {\n        let mut payload = vec![Value::from_i64(5)];\n        update_agg_payload(\n            &AggFunc::Count,\n            Value::Null,\n            None,\n            &mut payload,\n            CollationSeq::Binary,\n            &None,\n        )\n        .unwrap();\n        assert_eq!(payload[0], Value::from_i64(5)); // unchanged\n    }\n\n    #[test]\n    fn test_update_count_increments() {\n        let mut payload = vec![Value::from_i64(5)];\n        update_agg_payload(\n            &AggFunc::Count,\n            Value::from_i64(42),\n            None,\n            &mut payload,\n            CollationSeq::Binary,\n            &None,\n        )\n        .unwrap();\n        assert_eq!(payload[0], Value::from_i64(6));\n    }\n\n    #[test]\n    fn test_update_sum_integers() {\n        let mut payload = vec![\n            Value::Null,\n            Value::from_f64(0.0),\n            Value::from_i64(0),\n            Value::from_i64(0),\n        ];\n        update_agg_payload(\n            &AggFunc::Sum,\n            Value::from_i64(10),\n            None,\n            &mut payload,\n            CollationSeq::Binary,\n            &None,\n        )\n        .unwrap();\n        assert_eq!(payload[0], Value::from_i64(10));\n\n        update_agg_payload(\n            &AggFunc::Sum,\n            Value::from_i64(5),\n            None,\n            &mut payload,\n            CollationSeq::Binary,\n            &None,\n        )\n        .unwrap();\n        assert_eq!(payload[0], Value::from_i64(15));\n    }\n\n    #[test]\n    fn test_update_sum_null_is_skipped() {\n        let mut payload = vec![\n            Value::from_i64(10),\n            Value::from_f64(0.0),\n            Value::from_i64(0),\n            Value::from_i64(0),\n        ];\n        update_agg_payload(\n            &AggFunc::Sum,\n            Value::Null,\n            None,\n            &mut payload,\n            CollationSeq::Binary,\n            &None,\n        )\n        .unwrap();\n        assert_eq!(payload[0], Value::from_i64(10)); // unchanged\n    }\n\n    #[test]\n    fn test_update_min_max() {\n        let mut payload = vec![Value::Null];\n        // First value sets the min/max\n        update_agg_payload(\n            &AggFunc::Min,\n            Value::from_i64(5),\n            None,\n            &mut payload,\n            CollationSeq::Binary,\n            &None,\n        )\n        .unwrap();\n        assert_eq!(payload[0], Value::from_i64(5));\n\n        // Smaller value updates min\n        update_agg_payload(\n            &AggFunc::Min,\n            Value::from_i64(3),\n            None,\n            &mut payload,\n            CollationSeq::Binary,\n            &None,\n        )\n        .unwrap();\n        assert_eq!(payload[0], Value::from_i64(3));\n\n        // Larger value doesn't update min\n        update_agg_payload(\n            &AggFunc::Min,\n            Value::from_i64(10),\n            None,\n            &mut payload,\n            CollationSeq::Binary,\n            &None,\n        )\n        .unwrap();\n        assert_eq!(payload[0], Value::from_i64(3));\n    }\n\n    #[test]\n    fn test_update_avg() {\n        // Payload: [sum, r_err, count]\n        let mut payload = vec![\n            Value::from_f64(0.0),\n            Value::from_f64(0.0),\n            Value::from_i64(0),\n        ];\n        update_agg_payload(\n            &AggFunc::Avg,\n            Value::from_i64(10),\n            None,\n            &mut payload,\n            CollationSeq::Binary,\n            &None,\n        )\n        .unwrap();\n        assert_eq!(payload[0], Value::from_f64(10.0));\n        assert_eq!(payload[2], Value::from_i64(1));\n\n        update_agg_payload(\n            &AggFunc::Avg,\n            Value::from_i64(20),\n            None,\n            &mut payload,\n            CollationSeq::Binary,\n            &None,\n        )\n        .unwrap();\n        assert_eq!(payload[0], Value::from_f64(30.0));\n        assert_eq!(payload[2], Value::from_i64(2));\n    }\n\n    #[test]\n    fn test_finalize_count() {\n        let payload = vec![Value::from_i64(42)];\n        let result = finalize_agg_payload(&AggFunc::Count, &payload).unwrap();\n        assert_eq!(result, Value::from_i64(42));\n    }\n\n    #[test]\n    fn test_finalize_avg() {\n        // Payload: [sum, r_err, count]\n        let payload = vec![\n            Value::from_f64(30.0),\n            Value::from_f64(0.0),\n            Value::from_i64(3),\n        ];\n        let result = finalize_agg_payload(&AggFunc::Avg, &payload).unwrap();\n        assert_eq!(result, Value::from_f64(10.0));\n    }\n\n    #[test]\n    fn test_finalize_avg_empty() {\n        // Payload: [sum, r_err, count]\n        let payload = vec![\n            Value::from_f64(0.0),\n            Value::from_f64(0.0),\n            Value::from_i64(0),\n        ];\n        let result = finalize_agg_payload(&AggFunc::Avg, &payload).unwrap();\n        assert_eq!(result, Value::Null);\n    }\n\n    #[test]\n    fn test_array_agg_accumulates_correctly() {\n        // Verify that array_agg produces correct results when accumulating\n        // multiple values. Uses the direct payload approach (O(1) per row).\n        let mut payload = Vec::new();\n        init_agg_payload(&AggFunc::ArrayAgg, &mut payload).unwrap();\n\n        // Simulate how AggStep accumulates values directly into the payload Vec.\n        for i in 0..100 {\n            let count = payload[0].as_int().unwrap_or(0) as usize;\n            payload[0] = Value::from_i64((count + 1) as i64);\n            payload.push(Value::from_i64(i));\n        }\n\n        let result = finalize_agg_payload(&AggFunc::ArrayAgg, &payload).unwrap();\n        let blob = match &result {\n            Value::Blob(b) => b,\n            _ => panic!(\"Expected Blob, got {result:?}\"),\n        };\n        let elements = array_values_from_blob(blob).unwrap();\n        assert_eq!(elements.len(), 100);\n        for (i, elem) in elements.iter().enumerate() {\n            assert_eq!(*elem, Value::from_i64(i as i64));\n        }\n    }\n\n    #[test]\n    fn test_array_agg_zero_rows_produces_valid_result() {\n        // array_agg with zero rows should return NULL, matching PostgreSQL.\n        // The result must not be an invalid empty blob that crashes on decode.\n        let mut payload = Vec::new();\n        init_agg_payload(&AggFunc::ArrayAgg, &mut payload).unwrap();\n        // No values accumulated — count stays 0.\n        let result = finalize_agg_payload(&AggFunc::ArrayAgg, &payload).unwrap();\n        assert_eq!(result, Value::Null);\n    }\n\n    #[test]\n    fn test_array_agg_finalize_bounds_check() {\n        // If payload[0] count is larger than the actual payload length,\n        // finalize should return an error rather than panicking.\n        let payload = vec![Value::from_i64(999)]; // claims 999 elements but has none\n        let result = finalize_agg_payload(&AggFunc::ArrayAgg, &payload);\n        assert!(\n            result.is_err(),\n            \"Should error on count exceeding payload length\"\n        );\n    }\n\n    #[test]\n    fn test_negate_blob_subscript_invalid_utf8_no_panic() {\n        // Negating a blob subscript that extracts a \"text\" value containing\n        // invalid UTF-8 bytes must not panic. The record decoder uses\n        // from_utf8_unchecked, so ArrayElement must validate extracted text.\n        //\n        // Reproduces fuzzer bug at seed 27035.\n        let io: Arc<dyn IO> = Arc::new(MemoryIO::new());\n        let db = Database::open_file_with_flags(\n            io,\n            \":memory:\",\n            OpenFlags::Create,\n            DatabaseOpts::new(),\n            None,\n        )\n        .unwrap();\n        let conn = db.connect().unwrap();\n\n        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {\n            conn.execute(\"SELECT -X'18530E218A8D2D8D8F7456733370E68357745AFE13FC1B94751B77FCB00D0CAD971017936278BFF49BB4C8BD47F874ECA5226D3A433B7DFCD18661673598CED1FDB30A795F6F25'[2]\")\n        }));\n        assert!(\n            result.is_ok(),\n            \"Negating a blob subscript with invalid UTF-8 text should not panic\"\n        );\n    }\n}\n"
  },
  {
    "path": "core/vdbe/explain.rs",
    "content": "use crate::vdbe::{builder::CursorType, insn::RegisterOrLiteral};\nuse crate::HashSet;\nuse turso_parser::ast::{ResolveType, SortOrder};\n\nuse super::{Insn, InsnReference, PreparedProgram, Value};\nuse crate::function::{Func, ScalarFunc};\n\npub const EXPLAIN_COLUMNS: [&str; 8] = [\"addr\", \"opcode\", \"p1\", \"p2\", \"p3\", \"p4\", \"p5\", \"comment\"];\npub const EXPLAIN_COLUMNS_TYPE: [&str; 8] = [\n    \"INTEGER\", \"TEXT\", \"INTEGER\", \"INTEGER\", \"INTEGER\", \"TEXT\", \"INTEGER\", \"TEXT\",\n];\npub const EXPLAIN_QUERY_PLAN_COLUMNS: [&str; 4] = [\"id\", \"parent\", \"notused\", \"detail\"];\npub const EXPLAIN_QUERY_PLAN_COLUMNS_TYPE: [&str; 4] = [\"INTEGER\", \"INTEGER\", \"INTEGER\", \"TEXT\"];\n\npub fn insn_to_row(\n    program: &PreparedProgram,\n    insn: &Insn,\n) -> (&'static str, i64, i64, i64, Value, i64, String) {\n    let mut ephemeral_cursors = HashSet::default();\n    for (insn, _) in &program.insns {\n        match insn {\n            Insn::OpenEphemeral { cursor_id, .. } => {\n                ephemeral_cursors.insert(*cursor_id);\n            }\n            Insn::OpenAutoindex { cursor_id } => {\n                ephemeral_cursors.insert(*cursor_id);\n            }\n            Insn::OpenDup { new_cursor_id, .. } => {\n                // Note: relies on invariant that OpenDup is only for ephemeral cursors\n                ephemeral_cursors.insert(*new_cursor_id);\n            }\n            _ => {}\n        }\n    }\n\n    let get_table_or_index_name = |cursor_id: usize| -> String {\n        let cursor_type = &program.cursor_ref[cursor_id].1;\n        let name = match cursor_type {\n            CursorType::BTreeTable(table) => table.name.as_str(),\n            CursorType::BTreeIndex(index) => index.name.as_str(),\n            CursorType::IndexMethod(descriptor) => descriptor.definition().index_name,\n            CursorType::Pseudo(_) => \"pseudo\",\n            CursorType::VirtualTable(virtual_table) => virtual_table.name.as_str(),\n            CursorType::MaterializedView(table, _) => table.name.as_str(),\n            CursorType::Sorter => \"sorter\",\n        };\n        if ephemeral_cursors.contains(&cursor_id) {\n            format!(\"ephemeral({name})\")\n        } else {\n            name.to_string()\n        }\n    };\n    match insn {\n            Insn::Init { target_pc } => (\n                \"Init\",\n                0,\n                target_pc.as_debug_int() as i64,\n                0,\n                Value::build_text(\"\"),\n                0,\n                format!(\"Start at {}\", target_pc.as_debug_int()),\n            ),\n            Insn::Add { lhs, rhs, dest } => (\n                \"Add\",\n                *lhs as i64,\n                *rhs as i64,\n                *dest as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\"r[{dest}]=r[{lhs}]+r[{rhs}]\"),\n            ),\n            Insn::Subtract { lhs, rhs, dest } => (\n                \"Subtract\",\n                *lhs as i64,\n                *rhs as i64,\n                *dest as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\"r[{dest}]=r[{lhs}]-r[{rhs}]\"),\n            ),\n            Insn::Multiply { lhs, rhs, dest } => (\n                \"Multiply\",\n                *lhs as i64,\n                *rhs as i64,\n                *dest as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\"r[{dest}]=r[{lhs}]*r[{rhs}]\"),\n            ),\n            Insn::Divide { lhs, rhs, dest } => (\n                \"Divide\",\n                *lhs as i64,\n                *rhs as i64,\n                *dest as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\"r[{dest}]=r[{lhs}]/r[{rhs}]\"),\n            ),\n            Insn::BitAnd { lhs, rhs, dest } => (\n                \"BitAnd\",\n                *lhs as i64,\n                *rhs as i64,\n                *dest as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\"r[{dest}]=r[{lhs}]&r[{rhs}]\"),\n            ),\n            Insn::BitOr { lhs, rhs, dest } => (\n                \"BitOr\",\n                *lhs as i64,\n                *rhs as i64,\n                *dest as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\"r[{dest}]=r[{lhs}]|r[{rhs}]\"),\n            ),\n            Insn::BitNot { reg, dest } => (\n                \"BitNot\",\n                *reg as i64,\n                *dest as i64,\n                0,\n                Value::build_text(\"\"),\n                0,\n                format!(\"r[{dest}]=~r[{reg}]\"),\n            ),\n            Insn::Checkpoint {\n                database,\n                checkpoint_mode: _,\n                dest,\n            } => (\n                \"Checkpoint\",\n                *database as i64,\n                *dest as i64,\n                0,\n                Value::build_text(\"\"),\n                0,\n                format!(\"r[{dest}]=~r[{database}]\"),\n            ),\n            Insn::Remainder { lhs, rhs, dest } => (\n                \"Remainder\",\n                *lhs as i64,\n                *rhs as i64,\n                *dest as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\"r[{dest}]=r[{lhs}]%r[{rhs}]\"),\n            ),\n            Insn::Null { dest, dest_end } => (\n                \"Null\",\n                0,\n                *dest as i64,\n                dest_end.map_or(0, |end| end as i64),\n                Value::build_text(\"\"),\n                0,\n                dest_end.map_or(format!(\"r[{dest}]=NULL\"), |end| {\n                    format!(\"r[{dest}..{end}]=NULL\")\n                }),\n            ),\n            Insn::NullRow { cursor_id } => (\n                \"NullRow\",\n                *cursor_id as i64,\n                0,\n                0,\n                Value::build_text(\"\"),\n                0,\n                format!(\"Set cursor {cursor_id} to a (pseudo) NULL row\"),\n            ),\n            Insn::NotNull { reg, target_pc } => (\n                \"NotNull\",\n                *reg as i64,\n                target_pc.as_debug_int() as i64,\n                0,\n                Value::build_text(\"\"),\n                0,\n                format!(\"r[{}]!=NULL -> goto {}\", reg, target_pc.as_debug_int()),\n            ),\n            Insn::Compare {\n                start_reg_a,\n                start_reg_b,\n                count,\n                key_info,\n            } => (\n                \"Compare\",\n                *start_reg_a as i64,\n                *start_reg_b as i64,\n                *count as i64,\n                Value::build_text(format!(\"k({count}, {})\", key_info.iter().map(|k| k.collation.to_string()).collect::<Vec<_>>().join(\", \"))),\n                0,\n                format!(\n                    \"r[{}..{}]==r[{}..{}]\",\n                    start_reg_a,\n                    start_reg_a + (count - 1),\n                    start_reg_b,\n                    start_reg_b + (count - 1)\n                ),\n            ),\n            Insn::Jump {\n                target_pc_lt,\n                target_pc_eq,\n                target_pc_gt,\n            } => (\n                \"Jump\",\n                target_pc_lt.as_debug_int() as i64,\n                target_pc_eq.as_debug_int() as i64,\n                target_pc_gt.as_debug_int() as i64,\n                Value::build_text(\"\"),\n                0,\n                \"\".to_string(),\n            ),\n            Insn::Move {\n                source_reg,\n                dest_reg,\n                count,\n            } => (\n                \"Move\",\n                *source_reg as i64,\n                *dest_reg as i64,\n                *count as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\n                    \"r[{}..{}]=r[{}..{}]\",\n                    dest_reg,\n                    dest_reg + (count - 1),\n                    source_reg,\n                    source_reg + (count - 1)\n                ),\n            ),\n            Insn::IfPos {\n                reg,\n                target_pc,\n                decrement_by,\n            } => (\n                \"IfPos\",\n                *reg as i64,\n                target_pc.as_debug_int() as i64,\n                *decrement_by as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\n                    \"r[{}]>0 -> r[{}]-={}, goto {}\",\n                    reg,\n                    reg,\n                    decrement_by,\n                    target_pc.as_debug_int()\n                ),\n            ),\n            Insn::Eq {\n                lhs,\n                rhs,\n                target_pc,\n                collation,\n                ..\n            } => (\n                \"Eq\",\n                *lhs as i64,\n                *rhs as i64,\n                target_pc.as_debug_int() as i64,\n                Value::build_text(collation.map_or(\"\".to_string(), |c| c.to_string())),\n                0,\n                format!(\n                    \"if r[{}]==r[{}] goto {}\",\n                    lhs,\n                    rhs,\n                    target_pc.as_debug_int()\n                ),\n            ),\n            Insn::Ne {\n                lhs,\n                rhs,\n                target_pc,\n                collation,\n                ..\n            } => (\n                \"Ne\",\n                *lhs as i64,\n                *rhs as i64,\n                target_pc.as_debug_int() as i64,\n                Value::build_text(collation.map_or(\"\".to_string(), |c| c.to_string())),\n                0,\n                format!(\n                    \"if r[{}]!=r[{}] goto {}\",\n                    lhs,\n                    rhs,\n                    target_pc.as_debug_int()\n                ),\n            ),\n            Insn::Lt {\n                lhs,\n                rhs,\n                target_pc,\n                collation,\n                ..\n            } => (\n                \"Lt\",\n                *lhs as i64,\n                *rhs as i64,\n                target_pc.as_debug_int() as i64,\n                Value::build_text(collation.map_or(\"\".to_string(), |c| c.to_string())),\n                0,\n                format!(\"if r[{}]<r[{}] goto {}\", lhs, rhs, target_pc.as_debug_int()),\n            ),\n            Insn::Le {\n                lhs,\n                rhs,\n                target_pc,\n                collation,\n                ..\n            } => (\n                \"Le\",\n                *lhs as i64,\n                *rhs as i64,\n                target_pc.as_debug_int() as i64,\n                Value::build_text(collation.map_or(\"\".to_string(), |c| c.to_string())),\n                0,\n                format!(\n                    \"if r[{}]<=r[{}] goto {}\",\n                    lhs,\n                    rhs,\n                    target_pc.as_debug_int()\n                ),\n            ),\n            Insn::Gt {\n                lhs,\n                rhs,\n                target_pc,\n                collation,\n                ..\n            } => (\n                \"Gt\",\n                *lhs as i64,\n                *rhs as i64,\n                target_pc.as_debug_int() as i64,\n                Value::build_text(collation.map_or(\"\".to_string(), |c| c.to_string())),\n                0,\n                format!(\"if r[{}]>r[{}] goto {}\", lhs, rhs, target_pc.as_debug_int()),\n            ),\n            Insn::Ge {\n                lhs,\n                rhs,\n                target_pc,\n                collation,\n                ..\n            } => (\n                \"Ge\",\n                *lhs as i64,\n                *rhs as i64,\n                target_pc.as_debug_int() as i64,\n                Value::build_text(collation.map_or(\"\".to_string(), |c| c.to_string())),\n                0,\n                format!(\n                    \"if r[{}]>=r[{}] goto {}\",\n                    lhs,\n                    rhs,\n                    target_pc.as_debug_int()\n                ),\n            ),\n            Insn::If {\n                reg,\n                target_pc,\n                jump_if_null,\n            } => (\n                \"If\",\n                *reg as i64,\n                target_pc.as_debug_int() as i64,\n                *jump_if_null as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\"if r[{}] goto {}\", reg, target_pc.as_debug_int()),\n            ),\n            Insn::IfNot {\n                reg,\n                target_pc,\n                jump_if_null,\n            } => (\n                \"IfNot\",\n                *reg as i64,\n                target_pc.as_debug_int() as i64,\n                *jump_if_null as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\"if !r[{}] goto {}\", reg, target_pc.as_debug_int()),\n            ),\n            Insn::OpenRead {\n                cursor_id,\n                root_page,\n                db,\n            } => (\n                \"OpenRead\",\n                *cursor_id as i64,\n                *root_page,\n                *db as i64,\n                Value::build_text(program.cursor_ref[*cursor_id]\n                            .1.get_explain_description()),\n                0,\n                {\n                    let cursor_type =\n                        program.cursor_ref[*cursor_id]\n                            .0\n                            .as_ref()\n                            .map_or(\"\", |cursor_key| {\n                                if cursor_key.index.is_some() {\n                                    \"index\"\n                                } else {\n                                    \"table\"\n                                }\n                            });\n                    format!(\n                        \"{}={}, root={}, iDb={}\",\n                        cursor_type,\n                        get_table_or_index_name(*cursor_id),\n                        root_page,\n                        db\n                    )\n                },\n            ),\n            Insn::VOpen { cursor_id } => (\n                \"VOpen\",\n                *cursor_id as i64,\n                0,\n                0,\n                Value::build_text(\"\"),\n                0,\n                {\n                    let cursor_type =\n                        program.cursor_ref[*cursor_id]\n                            .0\n                            .as_ref()\n                            .map_or(\"\", |cursor_key| {\n                                if cursor_key.index.is_some() {\n                                    \"index\"\n                                } else {\n                                    \"table\"\n                                }\n                            });\n                    format!(\"{} {}\", cursor_type, get_table_or_index_name(*cursor_id),)\n                },\n            ),\n            Insn::VCreate {\n                table_name,\n                module_name,\n                args_reg,\n            } => (\n                \"VCreate\",\n                *table_name as i64,\n                *module_name as i64,\n                args_reg.unwrap_or(0) as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\"table={table_name}, module={module_name}\"),\n            ),\n            Insn::VFilter {\n                cursor_id,\n                pc_if_empty,\n                arg_count,\n                ..\n            } => (\n                \"VFilter\",\n                *cursor_id as i64,\n                pc_if_empty.as_debug_int() as i64,\n                *arg_count as i64,\n                Value::build_text(\"\"),\n                0,\n                \"\".to_string(),\n            ),\n            Insn::VColumn {\n                cursor_id,\n                column,\n                dest,\n            } => (\n                \"VColumn\",\n                *cursor_id as i64,\n                *column as i64,\n                *dest as i64,\n                Value::build_text(\"\"),\n                0,\n                \"\".to_string(),\n            ),\n            Insn::VUpdate {\n                cursor_id,\n                arg_count,       // P2: Number of arguments in argv[]\n                start_reg,       // P3: Start register for argv[]\n                conflict_action, // P4: Conflict resolution flags\n            } => (\n                \"VUpdate\",\n                *cursor_id as i64,\n                *arg_count as i64,\n                *start_reg as i64,\n                Value::build_text(\"\"),\n                *conflict_action as i64,\n                format!(\"args=r[{}..{}]\", start_reg, start_reg + arg_count - 1),\n            ),\n            Insn::VNext {\n                cursor_id,\n                pc_if_next,\n            } => (\n                \"VNext\",\n                *cursor_id as i64,\n                pc_if_next.as_debug_int() as i64,\n                0,\n                Value::build_text(\"\"),\n                0,\n                \"\".to_string(),\n            ),\n            Insn::VDestroy { db, table_name } => (\n                \"VDestroy\",\n                *db as i64,\n                0,\n                0,\n                Value::build_text(table_name.clone()),\n                0,\n                \"\".to_string(),\n            ),\n            Insn::VBegin{cursor_id} => (\n                \"VBegin\",\n                *cursor_id as i64,\n                0,\n                0,\n                Value::build_text(\"\"),\n                0,\n                \"\".into()\n              ),\n            Insn::VRename{cursor_id, new_name_reg} => (\n               \"VRename\",\n                *cursor_id as i64,\n                 *new_name_reg as i64,\n                 0,\n                Value::build_text(\"\"),\n                 0,\n                 \"\".into(),\n            ),\n            Insn::OpenPseudo {\n                cursor_id,\n                content_reg,\n                num_fields,\n            } => (\n                \"OpenPseudo\",\n                *cursor_id as i64,\n                *content_reg as i64,\n                *num_fields as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\"{num_fields} columns in r[{content_reg}]\"),\n            ),\n            Insn::Rewind {\n                cursor_id,\n                pc_if_empty,\n            } => (\n                \"Rewind\",\n                *cursor_id as i64,\n                pc_if_empty.as_debug_int() as i64,\n                0,\n                Value::build_text(\"\"),\n                0,\n                {\n                    let cursor_type =\n                        program.cursor_ref[*cursor_id]\n                            .0\n                            .as_ref()\n                            .map_or(\"\", |cursor_key| {\n                                if cursor_key.index.is_some() {\n                                    \"index\"\n                                } else {\n                                    \"table\"\n                                }\n                            });\n                    format!(\n                        \"Rewind {} {}\",\n                        cursor_type,\n                        get_table_or_index_name(*cursor_id),\n                    )\n                },\n            ),\n            Insn::Column {\n                cursor_id,\n                column,\n                dest,\n                default,\n            } => {\n                let cursor_type = &program.cursor_ref[*cursor_id].1;\n                let column_name: Option<&String> = match cursor_type {\n                    CursorType::BTreeTable(table) => {\n                        let name = table.columns.get(*column).and_then(|v| v.name.as_ref());\n                        name\n                    }\n                    CursorType::BTreeIndex(index) => {\n                        let name = &index.columns.get(*column).expect(\"column index out of bounds\").name;\n                        Some(name)\n                    }\n                    CursorType::MaterializedView(table, _) => {\n                        let name = table.columns.get(*column).and_then(|v| v.name.as_ref());\n                        name\n                    }\n                    CursorType::Pseudo(_) => None,\n                    CursorType::Sorter => None,\n                    CursorType::IndexMethod(..) => None,\n                    CursorType::VirtualTable(v) => v.columns.get(*column).expect(\"column index out of bounds\").name.as_ref(),\n                };\n                (\n                    \"Column\",\n                    *cursor_id as i64,\n                    *column as i64,\n                    *dest as i64,\n                    default.clone().unwrap_or_else(|| Value::build_text(\"\")),\n                    0,\n                    format!(\n                        \"r[{}]={}.{}\",\n                        dest,\n                        get_table_or_index_name(*cursor_id),\n                        &column_name.map_or_else(|| format!(\"column {}\", *column), |name| name.to_string())\n                    ),\n                )\n            }\n            Insn::TypeCheck {\n                start_reg,\n                count,\n                check_generated,\n                ..\n            } => (\n                \"TypeCheck\",\n                *start_reg as i64,\n                *count as i64,\n                *check_generated as i64,\n                Value::build_text(\"\"),\n                0,\n                String::from(\"\"),\n            ),\n            Insn::ArrayEncode {\n                reg,\n                element_type,\n                table_name,\n                col_name,\n                ..\n            } => (\n                \"ArrayEncode\",\n                *reg as i64,\n                0,\n                0,\n                Value::build_text(\"\"),\n                0,\n                format!(\"{table_name}.{col_name} ({element_type})\"),\n            ),\n            Insn::ArrayDecode { reg } => (\n                \"ArrayDecode\",\n                *reg as i64,\n                0,\n                0,\n                Value::build_text(\"\"),\n                0,\n                String::new(),\n            ),\n            Insn::ArrayElement {\n                array_reg,\n                index_reg,\n                dest,\n            } => (\n                \"ArrayElement\",\n                *array_reg as i64,\n                *index_reg as i64,\n                *dest as i64,\n                Value::build_text(\"\"),\n                0,\n                String::new(),\n            ),\n            Insn::ArrayLength { reg, dest } => (\n                \"ArrayLength\",\n                *reg as i64,\n                *dest as i64,\n                0,\n                Value::build_text(\"\"),\n                0,\n                String::new(),\n            ),\n            Insn::MakeArray {\n                start_reg,\n                count,\n                dest,\n            } => (\n                \"MakeArray\",\n                *start_reg as i64,\n                *count as i64,\n                *dest as i64,\n                Value::build_text(\"\"),\n                0,\n                String::new(),\n            ),\n            Insn::MakeArrayDynamic {\n                start_reg,\n                count_reg,\n                dest,\n            } => (\n                \"MakeArrayDynamic\",\n                *start_reg as i64,\n                *count_reg as i64,\n                *dest as i64,\n                Value::build_text(\"\"),\n                0,\n                String::new(),\n            ),\n            Insn::RegCopyOffset {\n                src,\n                base,\n                offset_reg,\n            } => (\n                \"RegCopyOffset\",\n                *src as i64,\n                *base as i64,\n                *offset_reg as i64,\n                Value::build_text(\"\"),\n                0,\n                String::new(),\n            ),\n            Insn::ArrayConcat { lhs, rhs, dest } => (\n                \"ArrayConcat\",\n                *lhs as i64,\n                *rhs as i64,\n                *dest as i64,\n                Value::build_text(\"\"),\n                0,\n                String::new(),\n            ),\n            Insn::ArraySetElement {\n                array_reg,\n                index_reg,\n                value_reg,\n                dest,\n            } => (\n                \"ArraySetElement\",\n                *array_reg as i64,\n                *index_reg as i64,\n                *dest as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\"r[{value_reg}]\"),\n            ),\n            Insn::ArraySlice {\n                array_reg,\n                start_reg,\n                end_reg,\n                dest,\n            } => (\n                \"ArraySlice\",\n                *array_reg as i64,\n                *start_reg as i64,\n                *dest as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\"end_reg=r[{end_reg}]\"),\n            ),\n            Insn::MakeRecord {\n                start_reg,\n                count,\n                dest_reg,\n                index_name,\n                affinity_str: _,\n            } => {\n                let for_index = index_name.as_ref().map(|name| format!(\"; for {name}\"));\n                (\n                    \"MakeRecord\",\n                    *start_reg as i64,\n                    *count as i64,\n                    *dest_reg as i64,\n                    Value::build_text(\"\"),\n                    0,\n                    format!(\n                        \"r[{}]=mkrec(r[{}..{}]){}\",\n                        dest_reg,\n                        start_reg,\n                        start_reg + count - 1,\n                        for_index.unwrap_or_else(|| \"\".to_string())\n                    ),\n                )\n            }\n            Insn::ResultRow { start_reg, count } => (\n                \"ResultRow\",\n                *start_reg as i64,\n                *count as i64,\n                0,\n                Value::build_text(\"\"),\n                0,\n                if *count == 1 {\n                    format!(\"output=r[{start_reg}]\")\n                } else {\n                    format!(\"output=r[{}..{}]\", start_reg, start_reg + count - 1)\n                },\n            ),\n            Insn::Next {\n                cursor_id,\n                pc_if_next,\n            } => (\n                \"Next\",\n                *cursor_id as i64,\n                pc_if_next.as_debug_int() as i64,\n                0,\n                Value::build_text(\"\"),\n                0,\n                \"\".to_string(),\n            ),\n            Insn::Halt {\n                err_code,\n                description,\n                on_error,\n                description_reg,\n            } => {\n                let p2 = match on_error {\n                    Some(ResolveType::Rollback) => 1,\n                    Some(ResolveType::Abort) => 2,\n                    Some(ResolveType::Fail) => 3,\n                    Some(ResolveType::Ignore) => 4,\n                    Some(ResolveType::Replace) => 5,\n                    None => 0,\n                };\n                let p3 = description_reg.unwrap_or(0) as i64;\n                (\n                    \"Halt\",\n                    *err_code as i64,\n                    p2,\n                    p3,\n                    Value::build_text(description.clone()),\n                    0,\n                    \"\".to_string(),\n                )\n            }\n            Insn::HaltIfNull {\n                err_code,\n                target_reg,\n                description,\n            } => (\n                \"HaltIfNull\",\n                *err_code as i64,\n                0,\n                *target_reg as i64,\n                Value::build_text(description.clone()),\n                0,\n                \"\".to_string(),\n            ),\n            Insn::Transaction { db, tx_mode, schema_cookie} => (\n                \"Transaction\",\n                *db as i64,\n                *tx_mode as i64,\n                *schema_cookie as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\"iDb={db} tx_mode={tx_mode:?}\"),\n            ),\n            Insn::Goto { target_pc } => (\n                \"Goto\",\n                0,\n                target_pc.as_debug_int() as i64,\n                0,\n                Value::build_text(\"\"),\n                0,\n                \"\".to_string(),\n            ),\n            Insn::Gosub {\n                target_pc,\n                return_reg,\n            } => (\n                \"Gosub\",\n                *return_reg as i64,\n                target_pc.as_debug_int() as i64,\n                0,\n                Value::build_text(\"\"),\n                0,\n                \"\".to_string(),\n            ),\n            Insn::Return {\n                return_reg,\n                can_fallthrough,\n            } => (\n                \"Return\",\n                *return_reg as i64,\n                0,\n                *can_fallthrough as i64,\n                Value::build_text(\"\"),\n                0,\n                \"\".to_string(),\n            ),\n            Insn::Integer { value, dest } => (\n                \"Integer\",\n                *value,\n                *dest as i64,\n                0,\n                Value::build_text(\"\"),\n                0,\n                format!(\"r[{dest}]={value}\"),\n            ),\n            Insn::Program {\n                params,\n                ignore_jump_target,\n                ..\n            } => (\n                \"Program\",\n                // P1: first register that contains a param\n                params.first().map(|v| match v {\n                    crate::types::Value::Numeric(crate::numeric::Numeric::Integer(i)) if *i < 0 => -i - 1,\n                    _ => 0,\n                }).unwrap_or(0),\n                // P2: ignore jump target (for RAISE(IGNORE))\n                ignore_jump_target.as_debug_int() as i64,\n                // P3: number of registers that contain params\n                params.len() as i64,\n                Value::build_text(program.sql.clone()),\n                0,\n                format!(\"subprogram={}\", program.sql),\n            ),\n            Insn::Real { value, dest } => (\n                \"Real\",\n                0,\n                *dest as i64,\n                0,\n                Value::from_f64(*value),\n                0,\n                format!(\"r[{dest}]={value}\"),\n            ),\n            Insn::RealAffinity { register } => (\n                \"RealAffinity\",\n                *register as i64,\n                0,\n                0,\n                Value::build_text(\"\"),\n                0,\n                \"\".to_string(),\n            ),\n            Insn::String8 { value, dest } => (\n                \"String8\",\n                0,\n                *dest as i64,\n                0,\n                Value::build_text(value.clone()),\n                0,\n                format!(\"r[{dest}]='{value}'\"),\n            ),\n            Insn::Blob { value, dest } => (\n                \"Blob\",\n                0,\n                *dest as i64,\n                0,\n                Value::Blob(value.clone()),\n                0,\n                format!(\n                    \"r[{}]={} (len={})\",\n                    dest,\n                    String::from_utf8_lossy(value),\n                    value.len()\n                ),\n            ),\n            Insn::RowId { cursor_id, dest } => (\n                \"RowId\",\n                *cursor_id as i64,\n                *dest as i64,\n                0,\n                Value::build_text(\"\"),\n                0,\n                format!(\"r[{}]={}.rowid\", dest, get_table_or_index_name(*cursor_id)),\n            ),\n            Insn::IdxRowId { cursor_id, dest } => (\n                \"IdxRowId\",\n                *cursor_id as i64,\n                *dest as i64,\n                0,\n                Value::build_text(\"\"),\n                0,\n                format!(\n                    \"r[{}]={}.rowid\",\n                    dest,\n                    program.cursor_ref[*cursor_id]\n                        .0\n                        .as_ref()\n                        .map(|k| format!(\n                            \"cursor {} for {} {}\",\n                            cursor_id,\n                            if k.index.is_some() { \"index\" } else { \"table\" },\n                            get_table_or_index_name(*cursor_id),\n                        ))\n                        .unwrap_or_else(|| format!(\"cursor {cursor_id}\"))\n                ),\n            ),\n            Insn::SeekRowid {\n                cursor_id,\n                src_reg,\n                target_pc,\n            } => (\n                \"SeekRowid\",\n                *cursor_id as i64,\n                *src_reg as i64,\n                target_pc.as_debug_int() as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\n                    \"if (r[{}]!={}.rowid) goto {}\",\n                    src_reg,\n                    &program.cursor_ref[*cursor_id]\n                        .0\n                        .as_ref()\n                        .map(|k| format!(\n                            \"cursor {} for {} {}\",\n                            cursor_id,\n                            if k.index.is_some() { \"index\" } else { \"table\" },\n                            get_table_or_index_name(*cursor_id),\n                        ))\n                        .unwrap_or_else(|| format!(\"cursor {cursor_id}\")),\n                    target_pc.as_debug_int()\n                ),\n            ),\n            Insn::DeferredSeek {\n                index_cursor_id,\n                table_cursor_id,\n            } => (\n                \"DeferredSeek\",\n                *index_cursor_id as i64,\n                *table_cursor_id as i64,\n                0,\n                Value::build_text(\"\"),\n                0,\n                \"\".to_string(),\n            ),\n            Insn::SeekGT {\n                is_index: _,\n                cursor_id,\n                start_reg,\n                num_regs,\n                target_pc,\n            }\n            | Insn::SeekGE {\n                is_index: _,\n                cursor_id,\n                start_reg,\n                num_regs,\n                target_pc,\n                ..\n            }\n            | Insn::SeekLE {\n                is_index: _,\n                cursor_id,\n                start_reg,\n                num_regs,\n                target_pc,\n                ..\n            }\n            | Insn::SeekLT {\n                is_index: _,\n                cursor_id,\n                start_reg,\n                num_regs,\n                target_pc,\n            } => (\n                match insn {\n                    Insn::SeekGT { .. } => \"SeekGT\",\n                    Insn::SeekGE { .. } => \"SeekGE\",\n                    Insn::SeekLE { .. } => \"SeekLE\",\n                    Insn::SeekLT { .. } => \"SeekLT\",\n                    _ => unreachable!(),\n                },\n                *cursor_id as i64,\n                target_pc.as_debug_int() as i64,\n                *start_reg as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\"key=[{}..{}]\", start_reg, start_reg + num_regs - 1),\n            ),\n            Insn::SeekEnd { cursor_id } => (\n                \"SeekEnd\",\n                *cursor_id as i64,\n                0,\n                0,\n                Value::build_text(\"\"),\n                0,\n                \"\".to_string(),\n            ),\n            Insn::IdxInsert {\n                cursor_id,\n                record_reg,\n                unpacked_start,\n                flags,\n                ..\n            } => (\n                \"IdxInsert\",\n                *cursor_id as i64,\n                *record_reg as i64,\n                unpacked_start.unwrap_or(0) as i64,\n                Value::build_text(\"\"),\n                flags.0 as i64,\n                format!(\"key=r[{record_reg}]\"),\n            ),\n            Insn::IdxGT {\n                cursor_id,\n                start_reg,\n                num_regs,\n                target_pc,\n            }\n            | Insn::IdxGE {\n                cursor_id,\n                start_reg,\n                num_regs,\n                target_pc,\n            }\n            | Insn::IdxLE {\n                cursor_id,\n                start_reg,\n                num_regs,\n                target_pc,\n            }\n            | Insn::IdxLT {\n                cursor_id,\n                start_reg,\n                num_regs,\n                target_pc,\n            } => (\n                match insn {\n                    Insn::IdxGT { .. } => \"IdxGT\",\n                    Insn::IdxGE { .. } => \"IdxGE\",\n                    Insn::IdxLE { .. } => \"IdxLE\",\n                    Insn::IdxLT { .. } => \"IdxLT\",\n                    _ => unreachable!(),\n                },\n                *cursor_id as i64,\n                target_pc.as_debug_int() as i64,\n                *start_reg as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\"key=[{}..{}]\", start_reg, start_reg + num_regs - 1),\n            ),\n            Insn::DecrJumpZero { reg, target_pc } => (\n                \"DecrJumpZero\",\n                *reg as i64,\n                target_pc.as_debug_int() as i64,\n                0,\n                Value::build_text(\"\"),\n                0,\n                format!(\"if (--r[{}]==0) goto {}\", reg, target_pc.as_debug_int()),\n            ),\n            Insn::AggStep {\n                func,\n                acc_reg,\n                delimiter: _,\n                col,\n                comparator: _,\n            } => (\n                \"AggStep\",\n                0,\n                *col as i64,\n                *acc_reg as i64,\n                Value::build_text(func.as_str()),\n                0,\n                format!(\"accum=r[{}] step(r[{}])\", *acc_reg, *col),\n            ),\n            Insn::AggFinal { register, func } => (\n                \"AggFinal\",\n                0,\n                *register as i64,\n                0,\n                Value::build_text(func.as_str()),\n                0,\n                format!(\"accum=r[{}]\", *register),\n            ),\n            Insn::AggValue { acc_reg, dest_reg, func } => (\n                \"AggValue\",\n                0,\n                *acc_reg as i64,\n                *dest_reg as i64,\n                Value::build_text(func.as_str()),\n                0,\n                format!(\"accum=r[{}] dest=r[{}]\", *acc_reg, *dest_reg),\n            ),\n            Insn::SorterOpen {\n                cursor_id,\n                columns,\n                order_and_collations,\n                ..\n            } => {\n                let to_print: Vec<String> = order_and_collations\n                    .iter()\n                    .map(|(order, collation)| {\n                        let sign = match order {\n                            SortOrder::Asc => \"\",\n                            SortOrder::Desc => \"-\",\n                        };\n                        if let Some(coll) = collation {\n                            format!(\"{sign}{coll}\")\n                        } else {\n                            format!(\"{sign}B\")\n                        }\n                    })\n                    .collect();\n                (\n                    \"SorterOpen\",\n                    *cursor_id as i64,\n                    *columns as i64,\n                    0,\n                    Value::build_text(format!(\"k({},{})\", order_and_collations.len(), to_print.join(\",\"))),\n                    0,\n                    format!(\"cursor={cursor_id}\"),\n                )\n            }\n            Insn::SorterData {\n                cursor_id,\n                dest_reg,\n                pseudo_cursor,\n            } => (\n                \"SorterData\",\n                *cursor_id as i64,\n                *dest_reg as i64,\n                *pseudo_cursor as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\"r[{dest_reg}]=data\"),\n            ),\n            Insn::SorterInsert {\n                cursor_id,\n                record_reg,\n            } => (\n                \"SorterInsert\",\n                *cursor_id as i64,\n                *record_reg as i64,\n                0,\n                Value::from_i64(0),\n                0,\n                format!(\"key=r[{record_reg}]\"),\n            ),\n            Insn::SorterSort {\n                cursor_id,\n                pc_if_empty,\n            } => (\n                \"SorterSort\",\n                *cursor_id as i64,\n                pc_if_empty.as_debug_int() as i64,\n                0,\n                Value::build_text(\"\"),\n                0,\n                \"\".to_string(),\n            ),\n            Insn::SorterNext {\n                cursor_id,\n                pc_if_next,\n            } => (\n                \"SorterNext\",\n                *cursor_id as i64,\n                pc_if_next.as_debug_int() as i64,\n                0,\n                Value::build_text(\"\"),\n                0,\n                \"\".to_string(),\n            ),\n            Insn::SorterCompare {\n                cursor_id,\n                pc_when_nonequal,\n                sorted_record_reg,\n                num_regs,\n            } => (\n                \"SorterCompare\",\n                *cursor_id as i64,\n                pc_when_nonequal.as_debug_int() as i64,\n                *sorted_record_reg as i64,\n                Value::build_text(num_regs.to_string()),\n                0,\n                \"\".to_string(),\n            ),\n            Insn::RowSetAdd {\n                rowset_reg,\n                value_reg,\n            } => (\n                \"RowSetAdd\",\n                *rowset_reg as i64,\n                *value_reg as i64,\n                0,\n                Value::build_text(\"\"),\n                0,\n                \"\".to_string(),\n            ),\n            Insn::RowSetRead {\n                rowset_reg,\n                pc_if_empty,\n                dest_reg,\n            } => (\n                \"RowSetRead\",\n                *rowset_reg as i64,\n                pc_if_empty.as_debug_int() as i64,\n                *dest_reg as i64,\n                Value::build_text(\"\"),\n                0,\n                \"\".to_string(),\n            ),\n            Insn::RowSetTest {\n                rowset_reg,\n                pc_if_found,\n                value_reg,\n                batch,\n            } => (\n                \"RowSetTest\",\n                *rowset_reg as i64,\n                pc_if_found.as_debug_int() as i64,\n                *value_reg as i64,\n                Value::build_text(batch.to_string()),\n                0,\n                \"\".to_string(),\n            ),\n            Insn::Function {\n                constant_mask,\n                start_reg,\n                dest,\n                func,\n            } => (\n                \"Function\",\n                *constant_mask as i64,\n                *start_reg as i64,\n                *dest as i64,\n                {\n                    let s = if matches!(&func.func, Func::Scalar(ScalarFunc::Like)) {\n                        format!(\"like({})\", func.arg_count)\n                    } else {\n                        func.func.to_string()\n                    };\n                    Value::build_text(s)\n                },\n                0,\n                if func.arg_count == 0 {\n                    format!(\"r[{dest}]=func()\")\n                } else if *start_reg == *start_reg + func.arg_count - 1 {\n                    format!(\"r[{dest}]=func(r[{start_reg}])\")\n                } else {\n                    format!(\n                        \"r[{}]=func(r[{}..{}])\",\n                        dest,\n                        start_reg,\n                        start_reg + func.arg_count - 1\n                    )\n                },\n            ),\n            Insn::InitCoroutine {\n                yield_reg,\n                jump_on_definition,\n                start_offset,\n            } => (\n                \"InitCoroutine\",\n                *yield_reg as i64,\n                jump_on_definition.as_debug_int() as i64,\n                start_offset.as_debug_int() as i64,\n                Value::build_text(\"\"),\n                0,\n                \"\".to_string(),\n            ),\n            Insn::EndCoroutine { yield_reg } => (\n                \"EndCoroutine\",\n                *yield_reg as i64,\n                0,\n                0,\n                Value::build_text(\"\"),\n                0,\n                \"\".to_string(),\n            ),\n            Insn::Yield {\n                yield_reg,\n                end_offset,\n                ..\n            } => (\n                \"Yield\",\n                *yield_reg as i64,\n                end_offset.as_debug_int() as i64,\n                0,\n                Value::build_text(\"\"),\n                0,\n                \"\".to_string(),\n            ),\n            Insn::Insert {\n                cursor,\n                key_reg,\n                record_reg,\n                flag,\n                table_name,\n            } => (\n                \"Insert\",\n                *cursor as i64,\n                *record_reg as i64,\n                *key_reg as i64,\n                Value::build_text(table_name.clone()),\n                flag.0 as i64,\n                format!(\"intkey=r[{key_reg}] data=r[{record_reg}]\"),\n            ),\n            Insn::Delete { cursor_id, table_name, .. } => (\n                \"Delete\",\n                *cursor_id as i64,\n                0,\n                0,\n                Value::build_text(table_name.clone()),\n                0,\n                \"\".to_string(),\n            ),\n            Insn::IdxDelete {\n                cursor_id,\n                start_reg,\n                num_regs,\n                raise_error_if_no_matching_entry,\n            } => (\n                \"IdxDelete\",\n                *cursor_id as i64,\n                *start_reg as i64,\n                *num_regs as i64,\n                Value::build_text(\"\"),\n                *raise_error_if_no_matching_entry as i64,\n                \"\".to_string(),\n            ),\n            Insn::NewRowid {\n                cursor,\n                rowid_reg,\n                prev_largest_reg,\n            } => (\n                \"NewRowid\",\n                *cursor as i64,\n                *rowid_reg as i64,\n                *prev_largest_reg as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\"r[{rowid_reg}]=rowid\"),\n            ),\n            Insn::MustBeInt { reg } => (\n                \"MustBeInt\",\n                *reg as i64,\n                0,\n                0,\n                Value::build_text(\"\"),\n                0,\n                \"\".to_string(),\n            ),\n            Insn::SoftNull { reg } => (\n                \"SoftNull\",\n                *reg as i64,\n                0,\n                0,\n                Value::build_text(\"\"),\n                0,\n                \"\".to_string(),\n            ),\n            Insn::NoConflict {\n                cursor_id,\n                target_pc,\n                record_reg,\n                num_regs,\n            } => {\n                let key = if *num_regs > 0 {\n                    format!(\"key=r[{}..{}]\", record_reg, record_reg + num_regs - 1)\n                } else {\n                    format!(\"key=r[{record_reg}]\")\n                };\n                (\n                    \"NoConflict\",\n                    *cursor_id as i64,\n                    target_pc.as_debug_int() as i64,\n                    *record_reg as i64,\n                    Value::build_text(format!(\"{num_regs}\")),\n                    0,\n                    key,\n                )\n            }\n            Insn::NotExists {\n                cursor,\n                rowid_reg,\n                target_pc,\n            } => (\n                \"NotExists\",\n                *cursor as i64,\n                target_pc.as_debug_int() as i64,\n                *rowid_reg as i64,\n                Value::build_text(\"\"),\n                0,\n                \"\".to_string(),\n            ),\n            Insn::OffsetLimit {\n                limit_reg,\n                combined_reg,\n                offset_reg,\n            } => (\n                \"OffsetLimit\",\n                *limit_reg as i64,\n                *combined_reg as i64,\n                *offset_reg as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\n                    \"if r[{limit_reg}]>0 then r[{combined_reg}]=r[{limit_reg}]+max(0,r[{offset_reg}]) else r[{combined_reg}]=(-1)\"\n                ),\n            ),\n            Insn::OpenWrite {\n                cursor_id,\n                root_page,\n                db,\n                ..\n            } => (\n                \"OpenWrite\",\n                *cursor_id as i64,\n                match root_page {\n                    RegisterOrLiteral::Literal(i) => *i as _,\n                    RegisterOrLiteral::Register(i) => *i as _,\n                },\n                *db as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\"root={root_page}; iDb={db}\"),\n            ),\n            Insn::Copy {\n                src_reg,\n                dst_reg,\n                extra_amount,\n            } => (\n                \"Copy\",\n                *src_reg as i64,\n                *dst_reg as i64,\n                *extra_amount as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\"r[{dst_reg}]=r[{src_reg}]\"),\n            ),\n            Insn::CreateBtree { db, root, flags } => (\n                \"CreateBtree\",\n                *db as i64,\n                *root as i64,\n                flags.get_flags() as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\"r[{}]=root iDb={} flags={}\", root, db, flags.get_flags()),\n            ),\n            Insn::IndexMethodCreate { db, cursor_id } => (\n                \"IndexMethodCreate\",\n                *db as i64,\n                *cursor_id as i64,\n                0,\n                Value::build_text(\"\"),\n                0,\n                \"\".to_string()\n            ),\n            Insn::IndexMethodDestroy { db, cursor_id } => (\n                \"IndexMethodDestroy\",\n                *db as i64,\n                *cursor_id as i64,\n                0,\n                Value::build_text(\"\"),\n                0,\n                \"\".to_string()\n            ),\n            Insn::IndexMethodOptimize { db, cursor_id } => (\n                \"IndexMethodOptimize\",\n                *db as i64,\n                *cursor_id as i64,\n                0,\n                Value::build_text(\"\"),\n                0,\n                \"\".to_string()\n            ),\n            Insn::IndexMethodQuery { db, cursor_id, start_reg, .. } => (\n                \"IndexMethodQuery\",\n                *db as i64,\n                *cursor_id as i64,\n                *start_reg as i64,\n                Value::build_text(\"\"),\n                0,\n                \"\".to_string()\n            ),\n            Insn::Destroy {\n                db,\n                root,\n                former_root_reg,\n                is_temp,\n            } => (\n                \"Destroy\",\n                *root,\n                *former_root_reg as i64,\n                *is_temp as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\n                    \"root iDb={db} former_root={former_root_reg} is_temp={is_temp}\"\n                ),\n            ),\n            Insn::ResetSorter { cursor_id } => (\n                \"ResetSorter\",\n                *cursor_id as i64,\n                0,\n                0,\n                Value::build_text(\"\"),\n                0,\n                format!(\"cursor={cursor_id}\"),\n            ),\n            Insn::DropTable {\n                db,\n                _p2,\n                _p3,\n                table_name,\n            } => (\n                \"DropTable\",\n                *db as i64,\n                0,\n                0,\n                Value::build_text(table_name.clone()),\n                0,\n                format!(\"DROP TABLE {table_name}\"),\n            ),\n            Insn::DropTrigger { db, trigger_name } => (\n                \"DropTrigger\",\n                *db as i64,\n                0,\n                0,\n                Value::build_text(trigger_name.clone()),\n                0,\n                format!(\"DROP TRIGGER {trigger_name}\"),\n            ),\n            Insn::DropType { db, type_name } => (\n                \"DropType\",\n                *db as i64,\n                0,\n                0,\n                Value::build_text(type_name.clone()),\n                0,\n                format!(\"DROP TYPE {type_name}\"),\n            ),\n            Insn::AddType { db, sql } => (\n                \"AddType\",\n                *db as i64,\n                0,\n                0,\n                Value::build_text(sql.clone()),\n                0,\n                \"ADD TYPE\".to_string(),\n            ),\n            Insn::DropView { db, view_name } => (\n                \"DropView\",\n                *db as i64,\n                0,\n                0,\n                Value::build_text(view_name.clone()),\n                0,\n                format!(\"DROP VIEW {view_name}\"),\n            ),\n            Insn::DropIndex { db: _, index } => (\n                \"DropIndex\",\n                0,\n                0,\n                0,\n                Value::build_text(index.name.clone()),\n                0,\n                format!(\"DROP INDEX {}\", index.name),\n            ),\n            Insn::Close { cursor_id } => (\n                \"Close\",\n                *cursor_id as i64,\n                0,\n                0,\n                Value::build_text(\"\"),\n                0,\n                \"\".to_string(),\n            ),\n            Insn::Last {\n                cursor_id,\n                pc_if_empty,\n            } => (\n                \"Last\",\n                *cursor_id as i64,\n                pc_if_empty.as_debug_int() as i64,\n                0,\n                Value::build_text(\"\"),\n                0,\n                \"\".to_string(),\n            ),\n            Insn::IsNull { reg, target_pc } => (\n                \"IsNull\",\n                *reg as i64,\n                target_pc.as_debug_int() as i64,\n                0,\n                Value::build_text(\"\"),\n                0,\n                format!(\"if (r[{}]==NULL) goto {}\", reg, target_pc.as_debug_int()),\n            ),\n            Insn::ParseSchema { db, where_clause } => (\n                \"ParseSchema\",\n                *db as i64,\n                0,\n                0,\n                Value::build_text(where_clause.clone().unwrap_or_else(|| \"NULL\".to_string())),\n                0,\n                where_clause.clone().unwrap_or_else(|| \"NULL\".to_string()),\n            ),\n            Insn::PopulateMaterializedViews { cursors } => (\n                \"PopulateMaterializedViews\",\n                0,\n                0,\n                0,\n                Value::Null,\n                cursors.len() as i64,\n                \"\".to_string(),\n            ),\n            Insn::Prev {\n                cursor_id,\n                pc_if_prev,\n            } => (\n                \"Prev\",\n                *cursor_id as i64,\n                pc_if_prev.as_debug_int() as i64,\n                0,\n                Value::build_text(\"\"),\n                0,\n                \"\".to_string(),\n            ),\n            Insn::ShiftRight { lhs, rhs, dest } => (\n                \"ShiftRight\",\n                *rhs as i64,\n                *lhs as i64,\n                *dest as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\"r[{dest}]=r[{lhs}] >> r[{rhs}]\"),\n            ),\n            Insn::ShiftLeft { lhs, rhs, dest } => (\n                \"ShiftLeft\",\n                *rhs as i64,\n                *lhs as i64,\n                *dest as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\"r[{dest}]=r[{lhs}] << r[{rhs}]\"),\n            ),\n            Insn::AddImm { register, value } => (\n                \"AddImm\",\n                *register as i64,\n                *value,\n                0,\n                Value::build_text(\"\"),\n                0,\n                format!(\"r[{register}]=r[{register}]+{value}\"),\n            ),\n            Insn::Variable { index, dest } => (\n                \"Variable\",\n                usize::from(*index) as i64,\n                *dest as i64,\n                0,\n                Value::build_text(\"\"),\n                0,\n                format!(\"r[{}]=parameter({})\", *dest, *index),\n            ),\n            Insn::ZeroOrNull { rg1, rg2, dest } => (\n                \"ZeroOrNull\",\n                *rg1 as i64,\n                *dest as i64,\n                *rg2 as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\n                    \"((r[{rg1}]=NULL)|(r[{rg2}]=NULL)) ? r[{dest}]=NULL : r[{dest}]=0\"\n                ),\n            ),\n            Insn::Not { reg, dest } => (\n                \"Not\",\n                *reg as i64,\n                *dest as i64,\n                0,\n                Value::build_text(\"\"),\n                0,\n                format!(\"r[{dest}]=!r[{reg}]\"),\n            ),\n            Insn::IsTrue { reg, dest, null_value, invert } => (\n                \"IsTrue\",\n                *reg as i64,\n                *dest as i64,\n                if *null_value { 1 } else { 0 },\n                Value::build_text(\"\"),\n                if *invert { 1 } else { 0 },\n                format!(\"r[{dest}] = IsTrue(r[{reg}], null={}, invert={})\", *null_value as i64, *invert as i64),\n            ),\n            Insn::Concat { lhs, rhs, dest } => (\n                \"Concat\",\n                *rhs as i64,\n                *lhs as i64,\n                *dest as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\"r[{dest}]=r[{lhs}] + r[{rhs}]\"),\n            ),\n            Insn::And { lhs, rhs, dest } => (\n                \"And\",\n                *rhs as i64,\n                *lhs as i64,\n                *dest as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\"r[{dest}]=(r[{lhs}] && r[{rhs}])\"),\n            ),\n            Insn::Or { lhs, rhs, dest } => (\n                \"Or\",\n                *rhs as i64,\n                *lhs as i64,\n                *dest as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\"r[{dest}]=(r[{lhs}] || r[{rhs}])\"),\n            ),\n            Insn::Noop => (\"Noop\", 0, 0, 0, Value::build_text(\"\"), 0, String::new()),\n            Insn::PageCount { db, dest } => (\n                \"Pagecount\",\n                *db as i64,\n                *dest as i64,\n                0,\n                Value::build_text(\"\"),\n                0,\n                \"\".to_string(),\n            ),\n            Insn::ReadCookie { db, dest, cookie } => (\n                \"ReadCookie\",\n                *db as i64,\n                *dest as i64,\n                *cookie as i64,\n                Value::build_text(\"\"),\n                0,\n                \"\".to_string(),\n            ),\n            Insn::Filter{cursor_id, target_pc, key_reg, num_keys} => (\n                \"Filter\",\n                *cursor_id as i64,\n                target_pc.as_debug_int() as i64,\n                *key_reg as i64,\n                Value::build_text(\"\"),\n                *num_keys as i64,\n                format!(\"if !bloom_filter(r[{}..{}]) goto {}\", key_reg, key_reg + num_keys, target_pc.as_debug_int()),\n            ),\n            Insn::FilterAdd{cursor_id, key_reg, num_keys} => (\n                \"FilterAdd\",\n                *cursor_id as i64,\n                *key_reg as i64,\n                *num_keys as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\"bloom_filter_add(r[{}..{}])\", key_reg, key_reg + num_keys),\n            ),\n            Insn::SetCookie {\n                db,\n                cookie,\n                value,\n                p5,\n            } => (\n                \"SetCookie\",\n                *db as i64,\n                *cookie as i64,\n                *value as i64,\n                Value::build_text(\"\"),\n                *p5 as i64,\n                \"\".to_string(),\n            ),\n            Insn::AutoCommit {\n                auto_commit,\n                rollback,\n            } => (\n                \"AutoCommit\",\n                *auto_commit as i64,\n                *rollback as i64,\n                0,\n                Value::build_text(\"\"),\n                0,\n                format!(\"auto_commit={auto_commit}, rollback={rollback}\"),\n            ),\n            Insn::Savepoint { op, name } => (\n                \"Savepoint\",\n                0,\n                0,\n                0,\n                Value::build_text(name.clone()),\n                0,\n                format!(\"op={op:?}, name={name}\"),\n            ),\n            Insn::OpenEphemeral {\n                cursor_id,\n                is_table,\n            } => (\n                \"OpenEphemeral\",\n                *cursor_id as i64,\n                *is_table as i64,\n                0,\n                Value::build_text(\"\"),\n                0,\n                format!(\n                    \"cursor={} is_table={}\",\n                    cursor_id,\n                    if *is_table { \"true\" } else { \"false\" }\n                ),\n            ),\n            Insn::OpenAutoindex { cursor_id } => (\n                \"OpenAutoindex\",\n                *cursor_id as i64,\n                0,\n                0,\n                Value::build_text(\"\"),\n                0,\n                format!(\"cursor={cursor_id}\"),\n            ),\n            Insn::OpenDup { new_cursor_id, original_cursor_id } => (\n                \"OpenDup\",\n                *new_cursor_id as i64,\n                *original_cursor_id as i64,\n                0,\n                Value::build_text(\"\"),\n                0,\n                format!(\"new_cursor={new_cursor_id}, original_cursor={original_cursor_id}\"),\n            ),\n            Insn::Once {\n                target_pc_when_reentered,\n            } => (\n                \"Once\",\n                target_pc_when_reentered.as_debug_int() as i64,\n                0,\n                0,\n                Value::build_text(\"\"),\n                0,\n                format!(\"goto {}\", target_pc_when_reentered.as_debug_int()),\n            ),\n            Insn::BeginSubrtn { dest, dest_end } => (\n                \"BeginSubrtn\",\n                *dest as i64,\n                dest_end.map_or(0, |end| end as i64),\n                0,\n                Value::build_text(\"\"),\n                0,\n                dest_end.map_or(format!(\"r[{dest}]=NULL\"), |end| {\n                    format!(\"r[{dest}..{end}]=NULL\")\n                }),\n            ),\n            Insn::NotFound {\n                cursor_id,\n                target_pc,\n                record_reg,\n                ..\n            }\n            | Insn::Found {\n                cursor_id,\n                target_pc,\n                record_reg,\n                ..\n            } => (\n                if matches!(insn, Insn::NotFound { .. }) {\n                    \"NotFound\"\n                } else {\n                    \"Found\"\n                },\n                *cursor_id as i64,\n                target_pc.as_debug_int() as i64,\n                *record_reg as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\n                    \"if {}found goto {}\",\n                    if matches!(insn, Insn::NotFound { .. }) {\n                        \"not \"\n                    } else {\n                        \"\"\n                    },\n                    target_pc.as_debug_int()\n                ),\n            ),\n            Insn::Affinity {\n                start_reg,\n                count,\n                affinities,\n            } => (\n                \"Affinity\",\n                *start_reg as i64,\n                count.get() as i64,\n                0,\n                Value::build_text(\"\"),\n                0,\n                format!(\n                    \"r[{}..{}] = {}\",\n                    start_reg,\n                    start_reg + count.get(),\n                    affinities\n                        .chars()\n                        .map(|a| a.to_string())\n                        .collect::<Vec<_>>()\n                        .join(\", \")\n                ),\n            ),\n            Insn::Count {\n                cursor_id,\n                target_reg,\n                exact,\n            } => (\n                \"Count\",\n                *cursor_id as i64,\n                *target_reg as i64,\n                if *exact { 0 } else { 1 },\n                Value::build_text(\"\"),\n                0,\n                \"\".to_string(),\n            ),\n            Insn::Int64 {\n                _p1,\n                out_reg,\n                _p3,\n                value,\n            } => (\n                \"Int64\",\n                0,\n                *out_reg as i64,\n                0,\n                Value::from_i64(*value),\n                0,\n                format!(\"r[{}]={}\", *out_reg, *value),\n            ),\n            Insn::IntegrityCk {\n                db,\n                max_errors,\n                roots,\n                message_register,\n            } => (\n                \"IntegrityCk\",\n                *max_errors as i64,\n                0,\n                0,\n                Value::build_text(\"\"),\n                0,\n                format!(\"db={db} roots={roots:?} message_register={message_register}\"),\n            ),\n            Insn::RowData { cursor_id, dest } => (\n                \"RowData\",\n                *cursor_id as i64,\n                *dest as i64,\n                0,\n                Value::build_text(\"\"),\n                0,\n                format!(\"r[{}] = data\", *dest),\n            ),\n            Insn::Cast { reg, affinity } => (\n                \"Cast\",\n                *reg as i64,\n                0,\n                0,\n                Value::build_text(\"\"),\n                0,\n                format!(\"affinity(r[{}]={:?})\", *reg, affinity),\n            ),\n            Insn::RenameTable { db: _, from, to } => (\n                \"RenameTable\",\n                0,\n                0,\n                0,\n                Value::build_text(\"\"),\n                0,\n                format!(\"rename_table({from}, {to})\"),\n            ),\n            Insn::DropColumn { db: _, table, column_index } => (\n                \"DropColumn\",\n                0,\n                0,\n                0,\n                Value::build_text(\"\"),\n                0,\n                format!(\"drop_column({table}, {column_index})\"),\n            ),\n            Insn::AddColumn { db: _, table, column, .. } => (\n                \"AddColumn\",\n                0,\n                0,\n                0,\n                Value::build_text(\"\"),\n                0,\n                format!(\"add_column({table}, {column:?})\"),\n            ),\n            Insn::AlterColumn { db: _, table, column_index, definition: column, rename } => (\n                \"AlterColumn\",\n                0,\n                0,\n                0,\n                Value::build_text(\"\"),\n                0,\n                format!(\"alter_column({table}, {column_index}, {column:?}, {rename:?})\"),\n            ),\n            Insn::MaxPgcnt { db, dest, new_max } => (\n                \"MaxPgcnt\",\n                *db as i64,\n                *dest as i64,\n                *new_max as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\"r[{dest}]=max_page_count(db[{db}],{new_max})\"),\n            ),\n            Insn::JournalMode { db, dest, new_mode } => (\n                \"JournalMode\",\n                *db as i64,\n                *dest as i64,\n                0,\n                Value::build_text(new_mode.clone().unwrap_or(String::new())),\n                0,\n                format!(\"r[{dest}]=journal_mode(db[{db}]{})\",\n                    new_mode.as_ref().map_or(String::new(), |m| format!(\",'{m}'\"))),\n            ),\n            Insn::CollSeq { reg, collation } => (\n                \"CollSeq\",\n                reg.unwrap_or(0) as i64,\n                0,\n                0,\n                Value::build_text(collation.to_string()),\n                0,\n                format!(\"collation={collation}\"),\n            ),\n            Insn::IfNeg { reg, target_pc } => (\n                \"IfNeg\",\n                *reg as i64,\n                target_pc.as_debug_int() as i64,\n                0,\n                Value::build_text(\"\"),\n                0,\n                format!(\"if (r[{}] < 0) goto {}\", reg, target_pc.as_debug_int()),\n            ),\n            Insn::Explain { p1, p2, detail } => (\n                \"Explain\",\n                *p1 as i64,\n                p2.as_ref().map(|p| *p).unwrap_or(0) as i64,\n                0,\n                Value::build_text(detail.clone()),\n                0,\n                String::new(),\n            ),\n            Insn::MemMax { dest_reg, src_reg } => (\n                \"MemMax\",\n                *dest_reg as i64,\n                *src_reg as i64,\n                0,\n                Value::build_text(\"\"),\n                0,\n                format!(\"r[{dest_reg}]=Max(r[{dest_reg}],r[{src_reg}])\"),\n            ),\n        Insn::Sequence{ cursor_id, target_reg} => (\n                \"Sequence\",\n                *cursor_id as i64,\n                *target_reg as i64,\n                0,\n                Value::build_text(\"\"),\n                0,\n                String::new(),\n          ),\n        Insn::SequenceTest{ cursor_id, target_pc, value_reg } => (\n            \"SequenceTest\",\n              *cursor_id as i64,\n            target_pc.as_debug_int() as i64,\n            *value_reg as i64,\n            Value::build_text(\"\"),\n            0,\n            String::new(),\n        ),\n        Insn::FkCounter{increment_value, deferred } => (\n        \"FkCounter\",\n            *increment_value as i64,\n            *deferred as i64,\n            0,\n            Value::build_text(\"\"),\n            0,\n            String::new(),\n        ),\n        Insn::FkIfZero{target_pc, deferred } => (\n        \"FkIfZero\",\n            target_pc.as_debug_int() as i64,\n            *deferred as i64,\n            0,\n            Value::build_text(\"\"),\n            0,\n            String::new(),\n        ),\n        Insn::FkCheck{ deferred } => (\n        \"FkCheck\",\n            *deferred as i64,\n            0,\n            0,\n            Value::build_text(\"\"),\n            0,\n            String::new(),\n        ),\n        Insn::HashBuild { data } => {\n            let payload_info = if let Some(p_reg) = data.payload_start_reg {\n                format!(\" payload=r[{}]..r[{}]\", p_reg, p_reg + data.num_payload - 1)\n            } else {\n                String::new()\n            };\n            (\n                \"HashBuild\",\n                data.cursor_id as i64,\n                data.key_start_reg as i64,\n                data.num_keys as i64,\n                Value::build_text(format!(\"r=[{}] budget={}{payload_info}\", data.hash_table_id, data.mem_budget)),\n                0,\n                String::new(),\n            )\n        }\n        Insn::HashBuildFinalize{hash_table_id: hash_table_reg} => (\n            \"HashBuildFinalize\",\n            *hash_table_reg as i64,\n            0,\n            0,\n            Value::build_text(\"\"),\n            0,\n            String::new(),\n        ),\n        Insn::HashProbe{hash_table_id: hash_table_reg, key_start_reg, num_keys, dest_reg, target_pc, payload_dest_reg, num_payload, probe_rowid_reg: _} => {\n            let payload_info = if let Some(p_reg) = payload_dest_reg {\n                format!(\" payload=r[{}]..r[{}]\", p_reg, p_reg + num_payload - 1)\n            } else {\n                String::new()\n            };\n            (\n                \"HashProbe\",\n                *hash_table_reg as i64,\n                *key_start_reg as i64,\n                *num_keys as i64,\n                Value::build_text(format!(\"r[{}]={}{}\", dest_reg, target_pc.as_debug_int(), payload_info)),\n                0,\n                String::new(),\n            )\n        }\n        Insn::HashNext{hash_table_id: hash_table_reg, dest_reg, target_pc, payload_dest_reg, num_payload} => {\n            let payload_info = if let Some(p_reg) = payload_dest_reg {\n                format!(\" payload=r[{}]..r[{}]\", p_reg, p_reg + num_payload - 1)\n            } else {\n                String::new()\n            };\n            (\n                \"HashNext\",\n                *hash_table_reg as i64,\n                *dest_reg as i64,\n                target_pc.as_debug_int() as i64,\n                Value::build_text(payload_info),\n                0,\n                String::new(),\n            )\n        }\n        Insn::HashDistinct { data } => (\n            \"HashDistinct\",\n            data.hash_table_id as i64,\n            data.key_start_reg as i64,\n            data.num_keys as i64,\n            Value::build_text(format!(\"jmp={}\", data.target_pc.as_debug_int())),\n            0,\n            String::new(),\n        ),\n        Insn::HashClose{hash_table_id: hash_table_reg} => (\n            \"HashClose\",\n            *hash_table_reg as i64,\n            0,\n            0,\n            Value::build_text(\"\"),\n            0,\n            String::new(),\n        ),\n        Insn::HashClear { hash_table_id: hash_table_reg } => (\n            \"HashClear\",\n            *hash_table_reg as i64,\n            0,\n            0,\n            Value::build_text(\"\"),\n            0,\n            String::new(),\n        ),\n        Insn::HashMarkMatched { hash_table_id } => (\n            \"HashMarkMatched\",\n            *hash_table_id as i64,\n            0,\n            0,\n            Value::build_text(\"\"),\n            0,\n            String::new(),\n        ),\n        Insn::HashResetMatched { hash_table_id } => (\n            \"HashResetMatched\",\n            *hash_table_id as i64,\n            0,\n            0,\n            Value::build_text(\"\"),\n            0,\n            String::new(),\n        ),\n        Insn::HashScanUnmatched { hash_table_id, dest_reg, target_pc, payload_dest_reg, num_payload } => {\n            let payload_info = if let Some(p_reg) = payload_dest_reg {\n                format!(\" payload=r[{}]..r[{}]\", p_reg, p_reg + num_payload - 1)\n            } else {\n                String::new()\n            };\n            (\n                \"HashScanUnmatched\",\n                *hash_table_id as i64,\n                *dest_reg as i64,\n                target_pc.as_debug_int() as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\"hash_table_id={hash_table_id}{payload_info}\"),\n            )\n        },\n        Insn::HashNextUnmatched { hash_table_id, dest_reg, target_pc, payload_dest_reg, num_payload } => {\n            let payload_info = if let Some(p_reg) = payload_dest_reg {\n                format!(\" payload=r[{}]..r[{}]\", p_reg, p_reg + num_payload - 1)\n            } else {\n                String::new()\n            };\n            (\n                \"HashNextUnmatched\",\n                *hash_table_id as i64,\n                *dest_reg as i64,\n                target_pc.as_debug_int() as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\"hash_table_id={hash_table_id}{payload_info}\"),\n            )\n        },\n        Insn::HashGraceInit { hash_table_id, target_pc } => {\n            (\n                \"HashGraceInit\",\n                *hash_table_id as i64,\n                0,\n                target_pc.as_debug_int() as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\"hash_table_id={hash_table_id}\"),\n            )\n        },\n        Insn::HashGraceLoadPartition { hash_table_id, target_pc } => {\n            (\n                \"HashGraceLoadPart\",\n                *hash_table_id as i64,\n                0,\n                target_pc.as_debug_int() as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\"hash_table_id={hash_table_id}\"),\n            )\n        },\n        Insn::HashGraceNextProbe { hash_table_id, key_start_reg, num_keys, probe_rowid_dest, target_pc } => {\n            (\n                \"HashGraceNextProbe\",\n                *hash_table_id as i64,\n                *key_start_reg as i64,\n                target_pc.as_debug_int() as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\"hash_table_id={hash_table_id} keys=r[{}]..r[{}] probe_rowid_dest=r[{probe_rowid_dest}]\", key_start_reg, *key_start_reg + *num_keys - 1),\n            )\n        },\n        Insn::HashGraceAdvancePartition { hash_table_id, target_pc } => {\n            (\n                \"HashGraceAdvPart\",\n                *hash_table_id as i64,\n                0,\n                target_pc.as_debug_int() as i64,\n                Value::build_text(\"\"),\n                0,\n                format!(\"hash_table_id={hash_table_id}\"),\n            )\n        },\n        Insn::VacuumInto { dest_path } => (\n            \"VacuumInto\",\n            0,\n            0,\n            0,\n            Value::build_text(dest_path.to_string()),\n            0,\n            format!(\"dest={dest_path}\"),\n        ),\n        Insn::InitCdcVersion { cdc_table_name, version, cdc_mode } => (\n            \"InitCdcVersion\",\n            0,\n            0,\n            0,\n            Value::build_text(format!(\"{cdc_table_name}={version}\")),\n            0,\n            format!(\"ensure turso_cdc_version({cdc_table_name}, {version}); set cdc={cdc_mode}\"),\n        ),\n    }\n}\n\npub fn insn_to_row_with_comment(\n    program: &PreparedProgram,\n    insn: &Insn,\n    manual_comment: Option<&str>,\n) -> (&'static str, i64, i64, i64, Value, i64, String) {\n    let (opcode, p1, p2, p3, p4, p5, comment) = insn_to_row(program, insn);\n    (\n        opcode,\n        p1,\n        p2,\n        p3,\n        p4,\n        p5,\n        manual_comment.map_or(comment.to_string(), |mc| format!(\"{comment}; {mc}\")),\n    )\n}\n\npub fn insn_to_str(\n    program: &PreparedProgram,\n    addr: InsnReference,\n    insn: &Insn,\n    indent: String,\n    manual_comment: Option<&str>,\n) -> String {\n    let (opcode, p1, p2, p3, p4, p5, comment) = insn_to_row(program, insn);\n    format!(\n        \"{:<4}  {:<17}  {:<4}  {:<4}  {:<4}  {:<13}  {:<2}  {}\",\n        addr,\n        &(indent + opcode),\n        p1,\n        p2,\n        p3,\n        p4.to_string(),\n        p5,\n        manual_comment.map_or(comment.to_string(), |mc| format!(\"{comment}; {mc}\"))\n    )\n}\n"
  },
  {
    "path": "core/vdbe/hash_table.rs",
    "content": "use crate::turso_assert;\nuse crate::{\n    error::LimboError,\n    io::{Buffer, Completion, TempFile, IO},\n    io_yield_one, return_if_io,\n    storage::sqlite3_ondisk::{read_varint, read_varint_partial, varint_len, write_varint},\n    sync::{\n        atomic::{self, AtomicUsize},\n        Arc, RwLock,\n    },\n    translate::collate::CollationSeq,\n    types::{IOCompletions, IOResult, Value, ValueRef},\n    vdbe::metrics::HashJoinMetrics,\n    CompletionError, Numeric, Result,\n};\nuse branches::{mark_unlikely, unlikely};\nuse rapidhash::fast::RapidHasher;\nuse std::cmp::Ordering;\nuse std::hash::Hasher;\nuse std::{cell::RefCell, collections::VecDeque};\nuse turso_macros::{turso_assert_eq, AtomicEnum};\n\nconst DEFAULT_SEED: u64 = 1337;\n\n// set to a *very* small 32KB, intentionally to trigger frequent spilling during tests\n#[cfg(debug_assertions)]\npub const DEFAULT_MEM_BUDGET: usize = 32 * 1024;\n\n/// 64MB default memory budget for hash joins.\n/// TODO: make configurable via PRAGMA\n#[cfg(not(debug_assertions))]\npub const DEFAULT_MEM_BUDGET: usize = 64 * 1024 * 1024;\nconst DEFAULT_BUCKETS: usize = 1024;\n/// Minimum number of partitions for grace hash join.\npub const MIN_PARTITIONS: usize = 16;\n/// Maximum number of partitions for adaptive partitioning.\npub const MAX_PARTITIONS: usize = 128;\nconst NULL_HASH: u8 = 0;\nconst INT_HASH: u8 = 1;\nconst FLOAT_HASH: u8 = 2;\nconst TEXT_HASH: u8 = 3;\nconst BLOB_HASH: u8 = 4;\n\n#[inline]\n/// Hash text case-insensitively without allocation (ASCII-only for SQLite NOCASE).\n/// SQLite's NOCASE collation only considers ASCII case, so to_ascii_lowercase() is correct.\nfn hash_text_nocase(hasher: &mut impl Hasher, text: &str) {\n    for byte in text.bytes() {\n        hasher.write_u8(byte.to_ascii_lowercase());\n    }\n}\n\n/// Hash function for join keys using rapidhash\n/// Takes collation into account when hashing text values\nfn hash_join_key(key_values: &[ValueRef], collations: &[CollationSeq]) -> u64 {\n    let mut hasher = RapidHasher::new(DEFAULT_SEED);\n\n    for (idx, value) in key_values.iter().enumerate() {\n        match value {\n            ValueRef::Null => {\n                hasher.write_u8(NULL_HASH);\n            }\n            ValueRef::Numeric(Numeric::Integer(i)) => {\n                // Hash integers in the same bucket as numerically equivalent REALs so e.g. 10 and 10.0 have the same hash.\n                let f = *i as f64;\n                if (f as i64) == *i && f.is_finite() {\n                    hasher.write_u8(FLOAT_HASH);\n                    let bits = normalized_f64_bits(f);\n                    hasher.write(&bits.to_le_bytes());\n                } else {\n                    // Fallback to the integer domain when the float representation would lose precision.\n                    hasher.write_u8(INT_HASH);\n                    hasher.write_i64(*i);\n                }\n            }\n            ValueRef::Numeric(Numeric::Float(f)) => {\n                hasher.write_u8(FLOAT_HASH);\n                let bits = normalized_f64_bits(f64::from(*f));\n                hasher.write(&bits.to_le_bytes());\n            }\n            ValueRef::Text(text) => {\n                let collation = collations.get(idx).unwrap_or(&CollationSeq::Binary);\n                hasher.write_u8(TEXT_HASH);\n                match collation {\n                    CollationSeq::NoCase => {\n                        hash_text_nocase(&mut hasher, text.as_str());\n                    }\n                    CollationSeq::Rtrim => {\n                        let trimmed = text.as_str().trim_end();\n                        hasher.write(trimmed.as_bytes());\n                    }\n                    CollationSeq::Binary | CollationSeq::Unset => {\n                        hasher.write(text.as_bytes());\n                    }\n                }\n            }\n            ValueRef::Blob(blob) => {\n                hasher.write_u8(BLOB_HASH);\n                hasher.write(blob);\n            }\n        }\n    }\n    hasher.finish()\n}\n\n/// Normalize signed zero so 0.0 and -0.0 hash the same.\n#[inline]\nfn normalized_f64_bits(f: f64) -> u64 {\n    if f == 0.0 {\n        0.0f64.to_bits()\n    } else {\n        f.to_bits()\n    }\n}\n\n/// Check if any of the key values is NULL.\n/// Rows with NULL join keys should be skipped in hash joins since NULL != NULL in SQL.\nfn has_null_key(key_values: &[Value]) -> bool {\n    key_values.iter().any(|v| matches!(v, Value::Null))\n}\n\n/// Check if any of the key value refs is NULL.\nfn has_null_key_ref(key_values: &[ValueRef]) -> bool {\n    key_values.iter().any(|v| matches!(v, ValueRef::Null))\n}\n\n/// Check if two key value arrays are equal, taking collation into account.\nfn keys_equal(key1: &[Value], key2: &[ValueRef], collations: &[CollationSeq]) -> bool {\n    if key1.len() != key2.len() {\n        return false;\n    }\n    for (idx, (v1, v2)) in key1.iter().zip(key2.iter()).enumerate() {\n        let collation = collations.get(idx).copied().unwrap_or(CollationSeq::Binary);\n        if !values_equal(v1.as_ref(), *v2, collation) {\n            return false;\n        }\n    }\n    true\n}\n\n/// Check if two values are equal, using the specified collation for text comparison.\n/// NOTE: In SQL, NULL = NULL evaluates to NULL (falsy), so this returns false for NULL comparisons.\nfn values_equal(v1: ValueRef, v2: ValueRef, collation: CollationSeq) -> bool {\n    match (v1, v2) {\n        // NULL = NULL is false in SQL (actually NULL, which is falsy)\n        (ValueRef::Null, _) | (_, ValueRef::Null) => false,\n        (ValueRef::Numeric(n1), ValueRef::Numeric(n2)) => {\n            ValueRef::Numeric(n1) == ValueRef::Numeric(n2)\n        }\n        (ValueRef::Blob(b1), ValueRef::Blob(b2)) => b1 == b2,\n        (ValueRef::Text(t1), ValueRef::Text(t2)) => {\n            // Use collation for text comparison\n            collation.compare_strings(t1.as_str(), t2.as_str()) == Ordering::Equal\n        }\n        _ => false,\n    }\n}\n\n/// DISTINCT equality: NULLs compare equal to NULL.\nfn values_equal_distinct(v1: ValueRef, v2: ValueRef, collation: CollationSeq) -> bool {\n    match (v1, v2) {\n        (ValueRef::Null, ValueRef::Null) => true,\n        (ValueRef::Null, _) | (_, ValueRef::Null) => false,\n        _ => values_equal(v1, v2, collation),\n    }\n}\n\nfn keys_equal_distinct(key1: &[Value], key2: &[ValueRef], collations: &[CollationSeq]) -> bool {\n    if key1.len() != key2.len() {\n        return false;\n    }\n    for (idx, (v1, v2)) in key1.iter().zip(key2.iter()).enumerate() {\n        let collation = collations.get(idx).copied().unwrap_or(CollationSeq::Binary);\n        if !values_equal_distinct(v1.as_ref(), *v2, collation) {\n            return false;\n        }\n    }\n    true\n}\n\n/// State machine states for hash table operations.\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum HashTableState {\n    Building,\n    Probing,\n    Spilled,\n    GraceProcessing,\n    Closed,\n}\n\n/// A probe entry returned by `grace_next_probe_entry()`.\n/// The VDBE writes these to registers for HashProbe to use.\n#[derive(Debug)]\npub struct GraceProbeEntry {\n    pub key_values: Vec<Value>,\n    pub probe_rowid: i64,\n}\n\n/// A single entry in a hash table bucket.\n#[derive(Debug, Clone)]\npub struct HashEntry {\n    /// Hash value of the join keys.\n    pub hash: u64,\n    /// The join key values.\n    pub key_values: Vec<Value>,\n    /// The rowid of the row in the build table.\n    /// During probe phase, we'll use SeekRowid to fetch the full row\n    /// (unless payload_values contains all needed columns).\n    pub rowid: i64,\n    /// Optional payload values - columns from the build table that are stored\n    /// directly in the hash entry to avoid SeekRowid during probe phase.\n    /// When populated, these are the result columns needed from the build table,\n    /// stored in column index order as specified during hash table construction.\n    pub payload_values: Vec<Value>,\n}\n\n#[derive(Debug)]\npub(crate) struct PendingHashInsert {\n    pub(crate) key_values: Vec<Value>,\n    pub(crate) rowid: i64,\n    pub(crate) payload_values: Vec<Value>,\n}\n\n#[derive(Debug)]\npub(crate) enum HashInsertResult {\n    Done,\n    IO {\n        io: IOCompletions,\n        pending: PendingHashInsert,\n    },\n}\n\nimpl HashEntry {\n    fn new(hash: u64, key_values: Vec<Value>, rowid: i64) -> Self {\n        Self {\n            hash,\n            key_values,\n            rowid,\n            payload_values: Vec::new(),\n        }\n    }\n\n    fn new_with_payload(\n        hash: u64,\n        key_values: Vec<Value>,\n        rowid: i64,\n        payload_values: Vec<Value>,\n    ) -> Self {\n        Self {\n            hash,\n            key_values,\n            rowid,\n            payload_values,\n        }\n    }\n\n    /// Returns true if this entry has payload values stored.\n    pub fn has_payload(&self) -> bool {\n        !self.payload_values.is_empty()\n    }\n\n    /// Get the size of this entry in bytes (approximate).\n    /// This is a lightweight estimate for memory budgeting, not a precise measurement.\n    fn size_bytes(&self) -> usize {\n        Self::size_from_values(&self.key_values, &self.payload_values)\n    }\n\n    fn size_from_values(key_values: &[Value], payload_values: &[Value]) -> usize {\n        let value_size = |v: &Value| match v {\n            Value::Null => 1,\n            Value::Numeric(_) => 8,\n            Value::Text(t) => t.as_str().len(),\n            Value::Blob(b) => b.len(),\n        };\n        let key_size: usize = key_values.iter().map(value_size).sum();\n        let payload_size: usize = payload_values.iter().map(value_size).sum();\n        key_size + payload_size + 8 + 8 // +8 for hash, +8 for rowid\n    }\n\n    /// Calculate the serialized size of a single Value.\n    #[inline]\n    fn value_serialized_size(v: &Value) -> usize {\n        1 + match v {\n            Value::Null => 0,\n            Value::Numeric(_) => 8,\n            Value::Text(t) => {\n                let len = t.as_str().len();\n                varint_len(len as u64) + len\n            }\n            Value::Blob(b) => varint_len(b.len() as u64) + b.len(),\n        }\n    }\n\n    /// Calculate the exact serialized size of this entry.\n    fn serialized_size(&self) -> usize {\n        8 + 8 // hash + rowid\n            + varint_len(self.key_values.len() as u64)\n            + self.key_values.iter().map(Self::value_serialized_size).sum::<usize>()\n            + varint_len(self.payload_values.len() as u64)\n            + self.payload_values.iter().map(Self::value_serialized_size).sum::<usize>()\n    }\n\n    /// Serialize this entry directly to a slice, returns bytes written.\n    /// The caller must ensure the slice is large enough (use serialized_size()).\n    fn serialize_to_slice(&self, buf: &mut [u8]) -> usize {\n        let mut offset = 0;\n\n        // Write hash and rowid\n        buf[offset..offset + 8].copy_from_slice(&self.hash.to_le_bytes());\n        offset += 8;\n        buf[offset..offset + 8].copy_from_slice(&self.rowid.to_le_bytes());\n        offset += 8;\n\n        // Write number of keys and key values\n        offset += write_varint(&mut buf[offset..], self.key_values.len() as u64);\n        for value in &self.key_values {\n            offset += Self::serialize_value_to_slice(value, &mut buf[offset..]);\n        }\n\n        // Write number of payload values and payload values\n        offset += write_varint(&mut buf[offset..], self.payload_values.len() as u64);\n        for value in &self.payload_values {\n            offset += Self::serialize_value_to_slice(value, &mut buf[offset..]);\n        }\n\n        offset\n    }\n\n    /// Helper to serialize a single Value directly to a slice. Returns bytes written.\n    #[inline]\n    fn serialize_value_to_slice(value: &Value, buf: &mut [u8]) -> usize {\n        let mut offset = 0;\n        match value {\n            Value::Null => {\n                buf[offset] = NULL_HASH;\n                offset += 1;\n            }\n            Value::Numeric(Numeric::Integer(i)) => {\n                buf[offset] = INT_HASH;\n                offset += 1;\n                buf[offset..offset + 8].copy_from_slice(&i.to_le_bytes());\n                offset += 8;\n            }\n            Value::Numeric(Numeric::Float(f)) => {\n                buf[offset] = FLOAT_HASH;\n                offset += 1;\n                buf[offset..offset + 8].copy_from_slice(&f64::from(*f).to_le_bytes());\n                offset += 8;\n            }\n            Value::Text(t) => {\n                buf[offset] = TEXT_HASH;\n                offset += 1;\n                let bytes = t.as_str().as_bytes();\n                offset += write_varint(&mut buf[offset..], bytes.len() as u64);\n                buf[offset..offset + bytes.len()].copy_from_slice(bytes);\n                offset += bytes.len();\n            }\n            Value::Blob(b) => {\n                buf[offset] = BLOB_HASH;\n                offset += 1;\n                offset += write_varint(&mut buf[offset..], b.len() as u64);\n                buf[offset..offset + b.len()].copy_from_slice(b);\n                offset += b.len();\n            }\n        }\n        offset\n    }\n\n    /// Serialize this entry to bytes for disk storage.\n    /// Format: [hash:8][rowid:8][num_keys:varint][keys...][num_payload:varint][payload...]\n    /// Each value is: [type:1][len:varint (for text/blob)][data]\n    fn serialize(&self, buf: &mut Vec<u8>) {\n        buf.extend_from_slice(&self.hash.to_le_bytes());\n        buf.extend_from_slice(&self.rowid.to_le_bytes());\n\n        // Write number of keys and key values\n        let varint_buf = &mut [0u8; 9];\n        let len = write_varint(varint_buf, self.key_values.len() as u64);\n        buf.extend_from_slice(&varint_buf[..len]);\n        for value in &self.key_values {\n            Self::serialize_value(value, buf, varint_buf);\n        }\n\n        // Write number of payload values and payload values\n        let len = write_varint(varint_buf, self.payload_values.len() as u64);\n        buf.extend_from_slice(&varint_buf[..len]);\n        for value in &self.payload_values {\n            Self::serialize_value(value, buf, varint_buf);\n        }\n    }\n\n    /// Helper to serialize a single Value to bytes.\n    fn serialize_value(value: &Value, buf: &mut Vec<u8>, varint_buf: &mut [u8; 9]) {\n        match value {\n            Value::Null => {\n                buf.push(NULL_HASH);\n            }\n            Value::Numeric(Numeric::Integer(i)) => {\n                buf.push(INT_HASH);\n                buf.extend_from_slice(&i.to_le_bytes());\n            }\n            Value::Numeric(Numeric::Float(f)) => {\n                buf.push(FLOAT_HASH);\n                buf.extend_from_slice(&f64::from(*f).to_le_bytes());\n            }\n            Value::Text(t) => {\n                buf.push(TEXT_HASH);\n                let bytes = t.as_str().as_bytes();\n                let len = write_varint(varint_buf, bytes.len() as u64);\n                buf.extend_from_slice(&varint_buf[..len]);\n                buf.extend_from_slice(bytes);\n            }\n            Value::Blob(b) => {\n                buf.push(BLOB_HASH);\n                let len = write_varint(varint_buf, b.len() as u64);\n                buf.extend_from_slice(&varint_buf[..len]);\n                buf.extend_from_slice(b);\n            }\n        }\n    }\n\n    /// Deserialize an entry from bytes, returning (entry, bytes_consumed) or error.\n    fn deserialize(buf: &[u8]) -> Result<(Self, usize)> {\n        if unlikely(buf.len() < 16) {\n            return Err(LimboError::Corrupt(\n                \"HashEntry: buffer too small for header\".to_string(),\n            ));\n        }\n\n        // buffer len checked above\n        let hash = u64::from_le_bytes(buf[0..8].try_into().expect(\"expect 8 bytes\"));\n        let rowid = i64::from_le_bytes(buf[8..16].try_into().expect(\"expect 8 bytes\"));\n        let mut offset = 16;\n\n        // Read number of keys and key values\n        let (num_keys, varint_len) = read_varint(&buf[offset..])?;\n        offset += varint_len;\n\n        let mut key_values = Vec::with_capacity(num_keys as usize);\n        for _ in 0..num_keys {\n            let (value, consumed) = Self::deserialize_value(&buf[offset..])?;\n            key_values.push(value);\n            offset += consumed;\n        }\n\n        // Read number of payload values and payload values\n        let (num_payload, varint_len) = read_varint(&buf[offset..])?;\n        offset += varint_len;\n\n        let mut payload_values = Vec::with_capacity(num_payload as usize);\n        for _ in 0..num_payload {\n            let (value, consumed) = Self::deserialize_value(&buf[offset..])?;\n            payload_values.push(value);\n            offset += consumed;\n        }\n\n        Ok((\n            Self {\n                hash,\n                key_values,\n                rowid,\n                payload_values,\n            },\n            offset,\n        ))\n    }\n\n    /// Helper to deserialize a single Value from bytes.\n    /// Returns (Value, bytes_consumed).\n    fn deserialize_value(buf: &[u8]) -> Result<(Value, usize)> {\n        if unlikely(buf.is_empty()) {\n            return Err(LimboError::Corrupt(\n                \"HashEntry: unexpected end of buffer\".to_string(),\n            ));\n        }\n        let value_type = buf[0];\n        let mut offset = 1;\n\n        let value = match value_type {\n            NULL_HASH => Value::Null,\n            INT_HASH => {\n                if unlikely(offset + 8 > buf.len()) {\n                    return Err(LimboError::Corrupt(\n                        \"HashEntry: buffer too small for integer\".to_string(),\n                    ));\n                }\n                let i =\n                    i64::from_le_bytes(buf[offset..offset + 8].try_into().expect(\"expect 8 bytes\"));\n                offset += 8;\n                Value::from_i64(i)\n            }\n            FLOAT_HASH => {\n                if unlikely(offset + 8 > buf.len()) {\n                    return Err(LimboError::Corrupt(\n                        \"HashEntry: buffer too small for float\".to_string(),\n                    ));\n                }\n                let f =\n                    f64::from_le_bytes(buf[offset..offset + 8].try_into().expect(\"expect 8 bytes\"));\n                offset += 8;\n                Value::from_f64(f)\n            }\n            TEXT_HASH => {\n                let (str_len, varint_len) = read_varint(&buf[offset..])?;\n                offset += varint_len;\n                if unlikely(offset + str_len as usize > buf.len()) {\n                    return Err(LimboError::Corrupt(\n                        \"HashEntry: buffer too small for text\".to_string(),\n                    ));\n                }\n                // SAFETY: We serialized this data ourselves, so it should be valid UTF-8.\n                // Skipping validation here for performance in the spill/reload path.\n                // Doing checked utf8 construction here is a massive performance hit.\n                let s = unsafe {\n                    String::from_utf8_unchecked(buf[offset..offset + str_len as usize].to_vec())\n                };\n                offset += str_len as usize;\n                Value::Text(s.into())\n            }\n            BLOB_HASH => {\n                let (blob_len, varint_len) = read_varint(&buf[offset..])?;\n                offset += varint_len;\n                if unlikely(offset + blob_len as usize > buf.len()) {\n                    return Err(LimboError::Corrupt(\n                        \"HashEntry: buffer too small for blob\".to_string(),\n                    ));\n                }\n                let b = buf[offset..offset + blob_len as usize].to_vec();\n                offset += blob_len as usize;\n                Value::Blob(b)\n            }\n            _ => {\n                mark_unlikely();\n                return Err(LimboError::Corrupt(format!(\n                    \"HashEntry: unknown value type {value_type}\",\n                )));\n            }\n        };\n        Ok((value, offset))\n    }\n}\n\n#[derive(Debug, Clone, Copy)]\nstruct Partitioning {\n    count: usize,\n    mask: usize,\n    shift: u32,\n}\n\nimpl Partitioning {\n    fn new(count: usize) -> Self {\n        turso_assert!(\n            count.is_power_of_two(),\n            \"partition count must be a power of two\"\n        );\n        let bits = count.trailing_zeros();\n        Self {\n            count,\n            mask: count - 1,\n            shift: 64 - bits,\n        }\n    }\n\n    #[inline(always)]\n    fn index(&self, hash: u64) -> usize {\n        ((hash >> self.shift) as usize) & self.mask\n    }\n}\n\n/// A bucket in the hash table. Uses chaining for collision resolution.\n#[derive(Debug, Clone)]\npub struct HashBucket {\n    entries: Vec<HashEntry>,\n}\n\nimpl HashBucket {\n    fn new() -> Self {\n        Self {\n            entries: Vec::new(),\n        }\n    }\n\n    fn insert(&mut self, entry: HashEntry) {\n        self.entries.push(entry);\n    }\n\n    fn find_matches<'a>(\n        &'a self,\n        hash: u64,\n        probe_keys: &[ValueRef],\n        collations: &[CollationSeq],\n    ) -> Vec<&'a HashEntry> {\n        self.entries\n            .iter()\n            .filter(|entry| {\n                entry.hash == hash && keys_equal(&entry.key_values, probe_keys, collations)\n            })\n            .collect()\n    }\n\n    fn is_empty(&self) -> bool {\n        self.entries.is_empty()\n    }\n\n    fn size_bytes(&self) -> usize {\n        self.entries.iter().map(|e| e.size_bytes()).sum()\n    }\n}\n\n/// I/O state for spilled partition operations\n#[derive(Debug, AtomicEnum, Clone, Copy, PartialEq, Eq)]\npub enum SpillIOState {\n    None,\n    WaitingForWrite,\n    WriteComplete,\n    WaitingForRead,\n    ReadComplete,\n    Error,\n}\n\n/// State of a partition in a spilled hash table\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum PartitionState {\n    /// data is in partition_buffers\n    InMemory,\n    /// Has been written to disk, not yet loaded\n    OnDisk,\n    /// Is being loaded from disk (I/O in progress)\n    Loading,\n    /// Has been loaded from disk and is ready for probing\n    Loaded,\n}\n\n/// A chunk of partition data spilled to disk.\n/// A partition may be spilled multiple times, creating multiple chunks.\n#[derive(Debug)]\nstruct SpillChunk {\n    /// File offset where this chunk's data starts\n    file_offset: u64,\n    /// Size in bytes of this chunk on disk\n    size_bytes: usize,\n    /// Number of entries in this chunk\n    num_entries: usize,\n}\n\n/// Tracks a partition that has been spilled to disk during grace hash join.\npub struct SpilledPartition {\n    /// Partition index (0 to partition_count - 1)\n    pub partition_idx: usize,\n    /// Chunks of data belonging to this partition (may have multiple spills)\n    chunks: Vec<SpillChunk>,\n    /// Current state of the partition\n    state: PartitionState,\n    /// I/O state for async operations\n    io_state: Arc<AtomicSpillIOState>,\n    /// Read buffer for loading partition back\n    read_buffer: Arc<RwLock<Vec<u8>>>,\n    /// Length of data in read buffer\n    buffer_len: Arc<AtomicUsize>,\n    /// Hash buckets for this partition (populated after loading)\n    buckets: Vec<HashBucket>,\n    /// Current chunk being loaded (for multi-chunk reads)\n    current_chunk_idx: usize,\n    /// Approximate memory used by the resident buckets for this partition\n    resident_mem: usize,\n    /// Parallel to `buckets`: tracks which entries have been matched (for FULL OUTER JOIN).\n    matched_bits: Vec<Vec<bool>>,\n    /// Partial entry bytes spanning chunk boundaries\n    partial_entry: Vec<u8>,\n    /// Parsed entries for validation\n    parsed_entries: usize,\n}\n\nimpl SpilledPartition {\n    fn new(partition_idx: usize) -> Self {\n        Self {\n            partition_idx,\n            chunks: Vec::new(),\n            state: PartitionState::OnDisk,\n            io_state: Arc::new(AtomicSpillIOState::new(SpillIOState::None)),\n            read_buffer: Arc::new(RwLock::new(Vec::new())),\n            buffer_len: Arc::new(atomic::AtomicUsize::new(0)),\n            buckets: Vec::new(),\n            current_chunk_idx: 0,\n            resident_mem: 0,\n            matched_bits: Vec::new(),\n            partial_entry: Vec::new(),\n            parsed_entries: 0,\n        }\n    }\n\n    /// Add a new chunk of data to this partition\n    fn add_chunk(&mut self, file_offset: u64, size_bytes: usize, num_entries: usize) {\n        self.chunks.push(SpillChunk {\n            file_offset,\n            size_bytes,\n            num_entries,\n        });\n    }\n\n    /// Get total size in bytes across all chunks\n    fn total_size_bytes(&self) -> usize {\n        self.chunks.iter().map(|c| c.size_bytes).sum()\n    }\n\n    /// Get total number of entries across all chunks\n    fn total_num_entries(&self) -> usize {\n        self.chunks.iter().map(|c| c.num_entries).sum()\n    }\n\n    fn buffer_len(&self) -> usize {\n        self.buffer_len.load(atomic::Ordering::Acquire)\n    }\n\n    /// Check if partition is ready for probing\n    pub fn is_loaded(&self) -> bool {\n        matches!(\n            self.state,\n            PartitionState::Loaded | PartitionState::InMemory\n        )\n    }\n\n    /// Check if there are more chunks to load\n    fn has_more_chunks(&self) -> bool {\n        self.current_chunk_idx < self.chunks.len()\n    }\n\n    /// Get the current chunk to load, if any\n    fn current_chunk(&self) -> Option<&SpillChunk> {\n        self.chunks.get(self.current_chunk_idx)\n    }\n}\n\n/// In-memory partition buffer for grace hash join.\n/// During build phase, entries are first accumulated here before spilling.\nstruct PartitionBuffer {\n    /// Entries in this partition\n    entries: Vec<HashEntry>,\n    /// Total memory used by entries in this partition\n    mem_used: usize,\n}\n\nimpl PartitionBuffer {\n    fn new() -> Self {\n        Self {\n            entries: Vec::new(),\n            mem_used: 0,\n        }\n    }\n\n    fn insert(&mut self, entry: HashEntry) {\n        self.mem_used += entry.size_bytes();\n        self.entries.push(entry);\n    }\n\n    fn clear(&mut self) {\n        self.entries.clear();\n        self.mem_used = 0;\n    }\n\n    fn is_empty(&self) -> bool {\n        self.entries.is_empty()\n    }\n}\n\n/// Configuration for the hash table.\n#[derive(Debug, Clone)]\npub struct HashTableConfig {\n    /// Initial number of buckets (must be power of 2).\n    pub initial_buckets: usize,\n    /// Maximum memory budget in bytes.\n    pub mem_budget: usize,\n    /// Number of keys in the join condition.\n    pub num_keys: usize,\n    /// Collation sequences for each join key.\n    pub collations: Vec<CollationSeq>,\n    /// Only spill to a file when != TempStore::Memory\n    pub temp_store: crate::TempStore,\n    /// Whether to track which entries have been matched during probing (for FULL OUTER JOIN).\n    pub track_matched: bool,\n    /// Optional override for the number of partitions (must be power of two).\n    pub partition_count: Option<usize>,\n}\n\nimpl Default for HashTableConfig {\n    fn default() -> Self {\n        Self {\n            initial_buckets: DEFAULT_BUCKETS,\n            mem_budget: DEFAULT_MEM_BUDGET,\n            num_keys: 1,\n            collations: vec![CollationSeq::Binary],\n            temp_store: crate::TempStore::Default,\n            track_matched: false,\n            partition_count: None,\n        }\n    }\n}\n\nstruct SpillState {\n    /// In-memory partition buffers for grace hash join.\n    /// When spilling is triggered, entries are partitioned by hash before writing.\n    partition_buffers: Vec<PartitionBuffer>,\n    /// Spilled partitions metadata, tracks what's on disk\n    partitions: Vec<SpilledPartition>,\n    /// Current file offset for next spill write\n    next_spill_offset: u64,\n    /// Temporary file for spilled data.\n    temp_file: TempFile,\n    /// Partitioning strategy for this spill.\n    partitioning: Partitioning,\n}\n\nimpl SpillState {\n    fn new(\n        io: &Arc<dyn IO>,\n        temp_store: crate::TempStore,\n        partitioning: Partitioning,\n    ) -> Result<Self> {\n        Ok(SpillState {\n            partition_buffers: (0..partitioning.count)\n                .map(|_| PartitionBuffer::new())\n                .collect(),\n            partitions: Vec::new(),\n            next_spill_offset: 0,\n            temp_file: TempFile::with_temp_store(io, temp_store)?,\n            partitioning,\n        })\n    }\n\n    fn find_partition_mut(&mut self, logical_idx: usize) -> Option<&mut SpilledPartition> {\n        self.partitions\n            .iter_mut()\n            .find(|p| p.partition_idx == logical_idx)\n    }\n\n    fn find_partition(&self, logical_idx: usize) -> Option<&SpilledPartition> {\n        self.partitions\n            .iter()\n            .find(|p| p.partition_idx == logical_idx)\n    }\n}\n\n/// Probe-side buffering/spilling state for grace hash join.\n/// Reuses the same serialization format and types as build-side spilling.\nstruct ProbeSpillState {\n    /// In-memory partition buffers for probe rows targeting spilled build partitions.\n    partition_buffers: Vec<PartitionBuffer>,\n    /// Spilled probe partition metadata.\n    partitions: Vec<SpilledPartition>,\n    /// Current file offset for next probe spill write.\n    next_spill_offset: u64,\n    /// Separate temp file for probe-side spills.\n    temp_file: TempFile,\n    /// Same partitioning as build side, so partition indices correspond.\n    partitioning: Partitioning,\n    /// Current memory used by probe buffers.\n    mem_used: usize,\n    /// Memory budget for probe-side buffers.\n    mem_budget: usize,\n}\n\nimpl ProbeSpillState {\n    fn new(\n        io: &Arc<dyn IO>,\n        temp_store: crate::TempStore,\n        partitioning: Partitioning,\n        mem_budget: usize,\n    ) -> Result<Self> {\n        Ok(Self {\n            partition_buffers: (0..partitioning.count)\n                .map(|_| PartitionBuffer::new())\n                .collect(),\n            partitions: Vec::new(),\n            next_spill_offset: 0,\n            temp_file: TempFile::with_temp_store(io, temp_store)?,\n            partitioning,\n            mem_used: 0,\n            mem_budget,\n        })\n    }\n\n    fn find_partition_mut(&mut self, logical_idx: usize) -> Option<&mut SpilledPartition> {\n        self.partitions\n            .iter_mut()\n            .find(|p| p.partition_idx == logical_idx)\n    }\n\n    fn find_partition(&self, logical_idx: usize) -> Option<&SpilledPartition> {\n        self.partitions\n            .iter()\n            .find(|p| p.partition_idx == logical_idx)\n    }\n}\n\n/// State for grace hash join partition-by-partition processing.\n/// The VDBE drives the loop; this tracks partition iteration and probe chunk loading.\nstruct GraceState {\n    /// Current probe entries loaded from disk (one chunk at a time).\n    probe_entries: Vec<HashEntry>,\n    /// Cursor into probe_entries.\n    probe_entry_cursor: usize,\n    /// Ordered list of partition indices to process (only spilled ones).\n    partitions_to_process: Vec<usize>,\n    /// Index into partitions_to_process.\n    partition_list_idx: usize,\n    /// Current load state for the active grace partition.\n    load_state: GracePartitionLoadState,\n}\n\nimpl GraceState {\n    fn current_partition_idx(&self) -> Option<usize> {\n        self.partitions_to_process\n            .get(self.partition_list_idx)\n            .copied()\n    }\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\nenum GracePartitionLoadState {\n    NeedBuildLoad,\n    NeedProbeLoad,\n    Ready,\n}\n\n/// HashTable is the build-side data structure used for hash joins and DISTINCT. It behaves like a\n/// standard in-memory hash table until a configurable memory budget is exceeded, at\n/// which point it transparently switches to a grace-hash-join style layout and spills\n/// partitions to disk.\n///\n/// # Overview\n///\n/// The table is keyed by an N-column join key. Keys are hashed using a stable\n/// rapidhash hasher that is aware of SQLite-style collations for text values.\n/// Each entry stores:\n///\n/// - the precomputed hash value,\n/// - a owned copy of the join key values, and\n/// - the rowid of the build-side row, used later to SeekRowid into the build table.\n///\n/// Collisions within a hash bucket are resolved using simple chaining (a `Vec<HashEntry>`),\n/// and equality is determined by comparing the stored key values against probe keys using\n/// the same collation-aware comparison logic that was used when hashing.\npub struct HashTable {\n    /// Initial bucket count used to reinitialize after spills.\n    initial_buckets: usize,\n    /// The hash buckets (used when not spilled).\n    buckets: Vec<HashBucket>,\n    /// Number of entries in the table.\n    num_entries: usize,\n    /// Current memory usage in bytes.\n    mem_used: usize,\n    /// Memory budget in bytes.\n    mem_budget: usize,\n    /// Number of join keys.\n    num_keys: usize,\n    /// Collation sequences for each join key.\n    collations: Vec<CollationSeq>,\n    /// Current state of the hash table.\n    state: HashTableState,\n    /// IO object for disk operations.\n    io: Arc<dyn IO>,\n    /// Current probe position bucket index.\n    probe_bucket_idx: usize,\n    /// Current probe entry index within bucket.\n    probe_entry_idx: usize,\n    /// Cached hash of current probe keys (to avoid recomputing)\n    current_probe_hash: Option<u64>,\n    /// Current probe key values being searched.\n    current_probe_keys: Option<Vec<Value>>,\n    spill_state: Option<SpillState>,\n    /// Index of current spilled partition being probed\n    current_spill_partition_idx: usize,\n    /// Track non-empty buckets for fast clear in distinct/group-by usage.\n    non_empty_buckets: Vec<usize>,\n    /// LRU of resident spilled partitions to cap memory for DISTINCT, grace,\n    /// and unmatched-scan partition loads.\n    loaded_partitions_lru: RefCell<VecDeque<usize>>,\n    /// Memory used by resident (loaded or in-memory) partitions\n    loaded_partitions_mem: usize,\n    /// Temp storage mode (memory vs file) for spilled data\n    temp_store: crate::TempStore,\n    /// Whether to track matched entries (for FULL OUTER JOIN).\n    track_matched: bool,\n    /// Parallel to `buckets`: one Vec<bool> per bucket tracking which entries were matched.\n    matched_bits: Vec<Vec<bool>>,\n    /// Bucket index for iterating unmatched entries.\n    unmatched_scan_bucket: usize,\n    /// Entry index within bucket for iterating unmatched entries.\n    unmatched_scan_entry: usize,\n    /// Partition index for iterating unmatched entries in spilled mode.\n    unmatched_scan_partition: usize,\n    /// Optional override for partition count selection\n    partition_count_override: Option<usize>,\n    /// Probe-side spill state for grace hash join.\n    probe_spill_state: Option<ProbeSpillState>,\n    /// Grace processing state machine.\n    grace_state: Option<GraceState>,\n}\n\ncrate::assert::assert_send!(HashTable);\n\nenum SpillAction {\n    AlreadyLoaded,\n    ParseChunk {\n        partition_idx: usize,\n    },\n    WaitingForIO,\n    NoChunks,\n    LoadChunk {\n        read_size: usize,\n        file_offset: u64,\n        io_state: Arc<AtomicSpillIOState>,\n        buffer_len: Arc<AtomicUsize>,\n        read_buffer_ref: Arc<RwLock<Vec<u8>>>,\n    },\n    Restart,\n    NotFound,\n}\n\nenum GraceProbeChunkAction {\n    WaitingForIO,\n    ParseChunk {\n        partition_idx: usize,\n    },\n    LoadChunk {\n        read_size: usize,\n        file_offset: u64,\n        io_state: Arc<AtomicSpillIOState>,\n        buffer_len: Arc<AtomicUsize>,\n        read_buffer_ref: Arc<RwLock<Vec<u8>>>,\n    },\n    Restart,\n    NoMoreChunks,\n}\n\nenum ParseChunkResult {\n    MoreChunks,\n    Done { resident_mem: usize },\n}\n\nimpl HashTable {\n    /// Create a new hash table.\n    pub fn new(config: HashTableConfig, io: Arc<dyn IO>) -> Self {\n        let num_buckets = config.initial_buckets;\n        let buckets = (0..num_buckets).map(|_| HashBucket::new()).collect();\n        let matched_bits = if config.track_matched {\n            (0..num_buckets).map(|_| Vec::new()).collect()\n        } else {\n            Vec::new()\n        };\n        Self {\n            initial_buckets: config.initial_buckets,\n            buckets,\n            num_entries: 0,\n            mem_used: 0,\n            mem_budget: config.mem_budget,\n            num_keys: config.num_keys,\n            collations: config.collations,\n            state: HashTableState::Building,\n            io,\n            probe_bucket_idx: 0,\n            probe_entry_idx: 0,\n            current_probe_keys: None,\n            current_probe_hash: None,\n            spill_state: None,\n            current_spill_partition_idx: 0,\n            loaded_partitions_lru: VecDeque::new().into(),\n            loaded_partitions_mem: 0,\n            non_empty_buckets: Vec::new(),\n            temp_store: config.temp_store,\n            track_matched: config.track_matched,\n            matched_bits,\n            unmatched_scan_bucket: 0,\n            unmatched_scan_entry: 0,\n            unmatched_scan_partition: 0,\n            partition_count_override: config.partition_count,\n            probe_spill_state: None,\n            grace_state: None,\n        }\n    }\n\n    /// Get the current state of the hash table.\n    pub fn get_state(&self) -> &HashTableState {\n        &self.state\n    }\n\n    /// Based on average entry size and number of entries,\n    /// determine the number of partitions to use for spilling.\n    fn choose_partition_count(&self, entry_size: usize) -> usize {\n        if let Some(count) = self.partition_count_override {\n            turso_assert!(\n                count.is_power_of_two(),\n                \"partition count override must be a power of two\"\n            );\n            return count;\n        }\n\n        let avg_entry_size = if self.num_entries > 0 {\n            (self.mem_used / self.num_entries).max(entry_size)\n        } else {\n            entry_size.max(1)\n        };\n        let target_partition_bytes = (self.mem_budget / 2).max(avg_entry_size);\n        let target_entries_per_partition = (target_partition_bytes / avg_entry_size).max(1);\n        let estimated_total_entries = self.num_entries.saturating_add(1);\n        let mut partitions = estimated_total_entries.div_ceil(target_entries_per_partition);\n        partitions = partitions.clamp(MIN_PARTITIONS, MAX_PARTITIONS);\n        partitions.next_power_of_two()\n    }\n\n    /// For a given hash value, get the partition index.\n    /// SAFETY: only call this when spill_state is Some.\n    fn partition_index(&self, hash: u64) -> usize {\n        let spill_state = self.spill_state.as_ref().expect(\"spill state must exist\");\n        spill_state.partitioning.index(hash)\n    }\n\n    fn record_probe_call(&mut self, metrics: Option<&mut HashJoinMetrics>) {\n        if let Some(metrics) = metrics {\n            metrics.probe_calls = metrics.probe_calls.saturating_add(1);\n        }\n    }\n\n    /// Insert a row into the hash table, returns IOResult because this may spill to disk.\n    /// When memory budget is exceeded, triggers grace hash join by partitioning and spilling.\n    /// Rows with NULL join keys are skipped since NULL != NULL in SQL.\n    /// (This is specific to hash join semantics, not DISTINCT.)\n    pub fn insert(\n        &mut self,\n        key_values: Vec<Value>,\n        rowid: i64,\n        payload_values: Vec<Value>,\n        metrics: Option<&mut HashJoinMetrics>,\n    ) -> Result<IOResult<()>> {\n        let pending = PendingHashInsert {\n            key_values,\n            rowid,\n            payload_values,\n        };\n        match self.insert_pending(pending, metrics)? {\n            HashInsertResult::Done => Ok(IOResult::Done(())),\n            HashInsertResult::IO { io, .. } => Ok(IOResult::IO(io)),\n        }\n    }\n\n    pub(crate) fn insert_pending(\n        &mut self,\n        pending: PendingHashInsert,\n        metrics: Option<&mut HashJoinMetrics>,\n    ) -> Result<HashInsertResult> {\n        turso_assert!(\n            matches!(self.state,  HashTableState::Building | HashTableState::Spilled),\n            \"Cannot insert into hash table in unexpected state\",\n            { \"state\": format!(\"{:?}\", self.state) }\n        );\n\n        // Skip rows with NULL join keys - they can never match anything since NULL != NULL in SQL.\n        // However, when track_matched is enabled (outer joins), we must keep NULL-key entries\n        // so they appear as unmatched in the unmatched scan.\n        if has_null_key(&pending.key_values) && !self.track_matched {\n            return Ok(HashInsertResult::Done);\n        }\n\n        // Compute hash of the join keys using collations\n        let key_refs: Vec<ValueRef> = pending.key_values.iter().map(|v| v.as_ref()).collect();\n        let hash = hash_join_key(&key_refs, &self.collations);\n        let entry_size = HashEntry::size_from_values(&pending.key_values, &pending.payload_values);\n\n        // Check if we would exceed memory budget\n        if self.mem_used + entry_size > self.mem_budget {\n            if self.spill_state.is_none() {\n                tracing::debug!(\n                    \"Hash table memory budget exceeded (used: {}, budget: {}), spilling to disk\",\n                    self.mem_used,\n                    self.mem_budget\n                );\n                // First time exceeding budget, trigger spill\n                // Move all existing bucket entries into partition buffers\n                let partition_count = self.choose_partition_count(entry_size);\n                let partitioning = Partitioning::new(partition_count);\n                self.spill_state = Some(SpillState::new(&self.io, self.temp_store, partitioning)?);\n                self.redistribute_to_partitions();\n                self.state = HashTableState::Spilled;\n            };\n\n            // Spill whole partitions until the new entry fits\n            if let Some(c) = self.spill_partitions_for_entry(entry_size, metrics)? {\n                // I/O pending, caller will re-enter after completion and retry the insert.\n                if !c.finished() {\n                    return Ok(HashInsertResult::IO {\n                        io: IOCompletions::Single(c),\n                        pending,\n                    });\n                }\n            }\n        }\n\n        let PendingHashInsert {\n            key_values,\n            rowid,\n            payload_values,\n        } = pending;\n        let entry = if payload_values.is_empty() {\n            HashEntry::new(hash, key_values, rowid)\n        } else {\n            HashEntry::new_with_payload(hash, key_values, rowid, payload_values)\n        };\n\n        if self.spill_state.is_some() {\n            let partition_idx = {\n                let spill_state = self.spill_state.as_ref().expect(\"spill state must exist\");\n                spill_state.partitioning.index(hash)\n            };\n            let spill_state = self.spill_state.as_mut().expect(\"spill state must exist\");\n            // In spilled mode, insert into partition buffer\n            spill_state.partition_buffers[partition_idx].insert(entry);\n        } else {\n            // Normal mode, insert into hash bucket\n            let bucket_idx = (hash as usize) % self.buckets.len();\n            if self.buckets[bucket_idx].entries.is_empty() {\n                self.non_empty_buckets.push(bucket_idx);\n            }\n            self.buckets[bucket_idx].insert(entry);\n            if self.track_matched {\n                self.matched_bits[bucket_idx].push(false);\n            }\n        }\n\n        self.num_entries += 1;\n        self.mem_used += entry_size;\n\n        Ok(HashInsertResult::Done)\n    }\n\n    /// Insert keys into the hash table if not already present.\n    /// Returns true if inserted, false if duplicate found.\n    /// Unlike hash join inserts, DISTINCT keeps NULLs and treats NULL==NULL.\n    pub fn insert_distinct(\n        &mut self,\n        key_values: &[Value],\n        key_refs: &[ValueRef],\n        mut metrics: Option<&mut HashJoinMetrics>,\n    ) -> Result<IOResult<bool>> {\n        turso_assert!(\n            self.state == HashTableState::Building || self.state == HashTableState::Spilled,\n            \"Cannot insert_distinct into hash table in unexpected state\",\n            { \"state\": format!(\"{:?}\", self.state) }\n        );\n\n        let hash = hash_join_key(key_refs, &self.collations);\n\n        if self.spill_state.is_some() {\n            let partition_idx = self.partition_index(hash);\n            // Check partition buffer for duplicates\n            let has_buffer_dup = {\n                let spill_state = self.spill_state.as_ref().expect(\"spill state exists\");\n                let buffer = &spill_state.partition_buffers[partition_idx];\n                buffer.entries.iter().any(|entry| {\n                    entry.hash == hash\n                        && keys_equal_distinct(&entry.key_values, key_refs, &self.collations)\n                })\n            };\n            if has_buffer_dup {\n                return Ok(IOResult::Done(false));\n            }\n\n            // Ensure spilled partition is loaded before checking\n            let has_partition = {\n                let spill_state = self.spill_state.as_ref().expect(\"spill state exists\");\n                spill_state.find_partition(partition_idx).is_some()\n            };\n            if has_partition && !self.is_partition_loaded(partition_idx) {\n                return_if_io!(self.load_spilled_partition(partition_idx, metrics.as_deref_mut()));\n            }\n\n            // Check loaded partition for duplicates\n            let has_spilled_dup = 'has_spilled_dup: {\n                let spill_state = self.spill_state.as_ref().expect(\"spill state exists\");\n                let Some(partition) = spill_state.find_partition(partition_idx) else {\n                    break 'has_spilled_dup false;\n                };\n                if partition.buckets.is_empty() {\n                    break 'has_spilled_dup false;\n                }\n                let bucket_idx = (hash as usize) % partition.buckets.len();\n                let bucket = &partition.buckets[bucket_idx];\n                bucket.entries.iter().any(|entry| {\n                    entry.hash == hash\n                        && keys_equal_distinct(&entry.key_values, key_refs, &self.collations)\n                })\n            };\n            if has_spilled_dup {\n                return Ok(IOResult::Done(false));\n            }\n\n            let entry_size = HashEntry::size_from_values(key_values, &[]);\n            if let Some(c) = self.spill_partitions_for_entry(entry_size, metrics.as_deref_mut())? {\n                if !c.succeeded() {\n                    return Ok(IOResult::IO(IOCompletions::Single(c)));\n                }\n            }\n\n            {\n                let spill_state = self.spill_state.as_mut().expect(\"spill state exists\");\n                spill_state.partition_buffers[partition_idx].insert(HashEntry::new(\n                    hash,\n                    key_values.to_vec(),\n                    0,\n                ));\n            }\n            self.num_entries += 1;\n            self.mem_used += entry_size;\n            return Ok(IOResult::Done(true));\n        }\n\n        // Non-spilled mode: check main buckets\n        let bucket_idx = (hash as usize) % self.buckets.len();\n        let bucket = &self.buckets[bucket_idx];\n        for entry in &bucket.entries {\n            if entry.hash == hash\n                && keys_equal_distinct(&entry.key_values, key_refs, &self.collations)\n            {\n                return Ok(IOResult::Done(false));\n            }\n        }\n\n        let entry_size = HashEntry::size_from_values(key_values, &[]);\n        if self.mem_used + entry_size > self.mem_budget {\n            if self.spill_state.is_none() {\n                let partition_count = self.choose_partition_count(entry_size);\n                let partitioning = Partitioning::new(partition_count);\n                self.spill_state = Some(SpillState::new(&self.io, self.temp_store, partitioning)?);\n                self.redistribute_to_partitions();\n                self.state = HashTableState::Spilled;\n            }\n            return self.insert_distinct(key_values, key_refs, metrics);\n        }\n\n        if self.buckets[bucket_idx].entries.is_empty() {\n            self.non_empty_buckets.push(bucket_idx);\n        }\n        self.buckets[bucket_idx].insert(HashEntry::new(hash, key_values.to_vec(), 0));\n        self.num_entries += 1;\n        self.mem_used += entry_size;\n        Ok(IOResult::Done(true))\n    }\n\n    /// Clear all entries and reset spill state.\n    pub fn clear(&mut self) {\n        if self.num_entries == 0 && self.spill_state.is_none() {\n            self.state = HashTableState::Building;\n            self.current_probe_keys = None;\n            self.current_probe_hash = None;\n            self.probe_bucket_idx = 0;\n            self.probe_entry_idx = 0;\n            self.current_spill_partition_idx = 0;\n            self.loaded_partitions_lru.borrow_mut().clear();\n            self.loaded_partitions_mem = 0;\n            self.non_empty_buckets.clear();\n            self.probe_spill_state = None;\n            self.grace_state = None;\n            return;\n        }\n\n        if self.spill_state.is_some() {\n            // Drop spilled partitions and reset buckets.\n            self.spill_state = None;\n            let bucket_count = self.initial_buckets.max(1);\n            self.buckets = (0..bucket_count).map(|_| HashBucket::new()).collect();\n            self.non_empty_buckets.clear();\n        } else {\n            for &idx in &self.non_empty_buckets {\n                self.buckets[idx].entries.clear();\n            }\n            self.non_empty_buckets.clear();\n        }\n\n        self.num_entries = 0;\n        self.mem_used = 0;\n        self.state = HashTableState::Building;\n        self.current_probe_keys = None;\n        self.current_probe_hash = None;\n        self.probe_bucket_idx = 0;\n        self.probe_entry_idx = 0;\n        self.current_spill_partition_idx = 0;\n        self.loaded_partitions_lru.borrow_mut().clear();\n        self.loaded_partitions_mem = 0;\n    }\n\n    /// Redistribute existing bucket entries into partition buffers for grace hash join.\n    fn redistribute_to_partitions(&mut self) {\n        let partitioning = {\n            let spill_state = self.spill_state.as_ref().expect(\"spill state must exist\");\n            spill_state.partitioning\n        };\n        for bucket in self.buckets.drain(..) {\n            for entry in bucket.entries {\n                let partition_idx = partitioning.index(entry.hash);\n                self.spill_state\n                    .as_mut()\n                    .expect(\"spill state must exist\")\n                    .partition_buffers[partition_idx]\n                    .insert(entry);\n            }\n        }\n        // Clear in-memory matched bits; spilled partitions will have their own.\n        self.matched_bits.clear();\n    }\n\n    /// Return the next partition which should be spilled to disk, for simplicity,\n    /// we always select the largest non-empty partition buffer.\n    fn next_partition_to_spill(&self, _required_free: usize) -> Option<usize> {\n        let spill_state = self.spill_state.as_ref()?;\n        spill_state\n            .partition_buffers\n            .iter()\n            .enumerate()\n            .filter(|(_, p)| !p.is_empty())\n            .max_by_key(|(_, p)| p.mem_used)\n            .map(|(idx, _)| idx)\n    }\n\n    /// Spill the given partition buffer to disk and return the pending completion.\n    /// Uses single-pass serialization directly into the I/O buffer to avoid intermediate copies.\n    fn spill_partition(\n        &mut self,\n        partition_idx: usize,\n        metrics: Option<&mut HashJoinMetrics>,\n    ) -> Result<Option<Completion>> {\n        let mut metrics = metrics;\n        let spill_state = self.spill_state.as_mut().expect(\"Spill state must exist\");\n        let partition = &spill_state.partition_buffers[partition_idx];\n        if partition.is_empty() {\n            return Ok(None);\n        }\n\n        // Phase 1: Calculate sizes and cache them to avoid recomputation\n        let num_entries = partition.entries.len();\n        let mut entry_sizes = Vec::with_capacity(num_entries);\n        let mut total_size = 0usize;\n        for entry in &partition.entries {\n            let entry_size = entry.serialized_size();\n            entry_sizes.push(entry_size);\n            total_size += varint_len(entry_size as u64) + entry_size;\n        }\n\n        // Allocate I/O buffer and serialize using cached sizes\n        let buffer = Buffer::new_temporary(total_size);\n        let buf = buffer.as_mut_slice();\n        let mut offset = 0;\n\n        for (entry, &entry_size) in partition.entries.iter().zip(entry_sizes.iter()) {\n            offset += write_varint(&mut buf[offset..], entry_size as u64);\n            offset += entry.serialize_to_slice(&mut buf[offset..]);\n        }\n\n        turso_assert!(offset == total_size, \"serialized size mismatch\");\n\n        let file_offset = spill_state.next_spill_offset;\n        let data_size = total_size;\n        let num_entries = spill_state.partition_buffers[partition_idx].entries.len();\n        let mem_freed = spill_state.partition_buffers[partition_idx].mem_used;\n\n        spill_state.partition_buffers[partition_idx].clear();\n\n        // Find existing partition or create new one\n        let io_state = if let Some(existing) = spill_state.find_partition_mut(partition_idx) {\n            existing.add_chunk(file_offset, data_size, num_entries);\n            if let Some(metrics) = metrics.as_deref_mut() {\n                metrics.spill_bytes_written =\n                    metrics.spill_bytes_written.saturating_add(data_size as u64);\n                metrics.spill_chunks = metrics.spill_chunks.saturating_add(1);\n                metrics.spill_max_chunks_per_partition = metrics\n                    .spill_max_chunks_per_partition\n                    .max(existing.chunks.len() as u64);\n                metrics.spill_max_partition_bytes = metrics\n                    .spill_max_partition_bytes\n                    .max(existing.total_size_bytes() as u64);\n            }\n            existing.io_state.clone()\n        } else {\n            let mut new_partition = SpilledPartition::new(partition_idx);\n            new_partition.add_chunk(file_offset, data_size, num_entries);\n            if let Some(metrics) = metrics {\n                metrics.spill_bytes_written =\n                    metrics.spill_bytes_written.saturating_add(data_size as u64);\n                metrics.spill_chunks = metrics.spill_chunks.saturating_add(1);\n                metrics.spill_max_chunks_per_partition = metrics\n                    .spill_max_chunks_per_partition\n                    .max(new_partition.chunks.len() as u64);\n                metrics.spill_max_partition_bytes = metrics\n                    .spill_max_partition_bytes\n                    .max(new_partition.total_size_bytes() as u64);\n            }\n            let io_state = new_partition.io_state.clone();\n            spill_state.partitions.push(new_partition);\n            io_state\n        };\n\n        io_state.set(SpillIOState::WaitingForWrite);\n\n        let buffer_ref = Arc::new(buffer);\n        let write_complete = Box::new(move |res: Result<i32, crate::CompletionError>| match res {\n            Ok(_) => {\n                tracing::trace!(\"Successfully wrote spilled partition to disk\");\n                io_state.set(SpillIOState::WriteComplete);\n            }\n            Err(e) => {\n                tracing::error!(\"Error writing spilled partition to disk: {e:?}\");\n                io_state.set(SpillIOState::Error);\n            }\n        });\n\n        let completion = Completion::new_write(write_complete);\n        let file = spill_state.temp_file.file.clone();\n        let completion = file.pwrite(file_offset, buffer_ref, completion)?;\n\n        // Update state\n        self.mem_used -= mem_freed;\n        spill_state.next_spill_offset += data_size as u64;\n        Ok(Some(completion))\n    }\n\n    /// Spill multiple partitions in a single I/O operation.\n    /// This batches the work to reduce syscall overhead when freeing large amounts of memory.\n    fn spill_multiple_partitions(\n        &mut self,\n        partition_indices: &[usize],\n        metrics: Option<&mut HashJoinMetrics>,\n    ) -> Result<Option<Completion>> {\n        let mut metrics = metrics;\n        if partition_indices.is_empty() {\n            return Ok(None);\n        }\n\n        // If only one partition, use the simpler single-partition path\n        if partition_indices.len() == 1 {\n            return self.spill_partition(partition_indices[0], metrics);\n        }\n\n        let spill_state = self.spill_state.as_mut().expect(\"Spill state must exist\");\n\n        // Phase 1: Calculate total size and per-partition metadata\n        struct PartitionMeta {\n            idx: usize,\n            num_entries: usize,\n            data_size: usize,\n            mem_freed: usize,\n            entry_sizes: Vec<usize>,\n        }\n\n        let mut metas = Vec::with_capacity(partition_indices.len());\n        let mut total_size = 0usize;\n\n        for &partition_idx in partition_indices {\n            let partition = &spill_state.partition_buffers[partition_idx];\n            if partition.is_empty() {\n                continue;\n            }\n\n            let mut entry_sizes = Vec::with_capacity(partition.entries.len());\n            let mut partition_size = 0usize;\n            for entry in &partition.entries {\n                let entry_size = entry.serialized_size();\n                entry_sizes.push(entry_size);\n                partition_size += varint_len(entry_size as u64) + entry_size;\n            }\n\n            metas.push(PartitionMeta {\n                idx: partition_idx,\n                num_entries: partition.entries.len(),\n                data_size: partition_size,\n                mem_freed: partition.mem_used,\n                entry_sizes,\n            });\n            total_size += partition_size;\n        }\n\n        if metas.is_empty() {\n            return Ok(None);\n        }\n\n        // Allocate single I/O buffer and serialize all partitions\n        let buffer = Buffer::new_temporary(total_size);\n        let buf = buffer.as_mut_slice();\n        let mut offset = 0;\n        let base_file_offset = spill_state.next_spill_offset;\n\n        // Track where each partition's data starts in the buffer\n        let mut partition_offsets = Vec::with_capacity(metas.len());\n\n        for meta in &metas {\n            partition_offsets.push(offset);\n            let partition = &spill_state.partition_buffers[meta.idx];\n\n            for (entry, &entry_size) in partition.entries.iter().zip(meta.entry_sizes.iter()) {\n                offset += write_varint(&mut buf[offset..], entry_size as u64);\n                offset += entry.serialize_to_slice(&mut buf[offset..]);\n            }\n        }\n\n        turso_assert!(offset == total_size, \"serialized size mismatch\");\n\n        // Update partition metadata and clear buffers\n        let mut total_mem_freed = 0usize;\n        let mut io_states = Vec::with_capacity(metas.len());\n\n        for (meta, &partition_offset) in metas.iter().zip(partition_offsets.iter()) {\n            let file_offset = base_file_offset + partition_offset as u64;\n\n            spill_state.partition_buffers[meta.idx].clear();\n            total_mem_freed += meta.mem_freed;\n\n            // Find existing partition or create new one\n            let io_state = if let Some(existing) = spill_state.find_partition_mut(meta.idx) {\n                existing.add_chunk(file_offset, meta.data_size, meta.num_entries);\n                if let Some(metrics) = metrics.as_deref_mut() {\n                    metrics.spill_bytes_written = metrics\n                        .spill_bytes_written\n                        .saturating_add(meta.data_size as u64);\n                    metrics.spill_chunks = metrics.spill_chunks.saturating_add(1);\n                    metrics.spill_max_chunks_per_partition = metrics\n                        .spill_max_chunks_per_partition\n                        .max(existing.chunks.len() as u64);\n                    metrics.spill_max_partition_bytes = metrics\n                        .spill_max_partition_bytes\n                        .max(existing.total_size_bytes() as u64);\n                }\n                existing.io_state.clone()\n            } else {\n                let mut new_partition = SpilledPartition::new(meta.idx);\n                new_partition.add_chunk(file_offset, meta.data_size, meta.num_entries);\n                if let Some(metrics) = metrics.as_deref_mut() {\n                    metrics.spill_bytes_written = metrics\n                        .spill_bytes_written\n                        .saturating_add(meta.data_size as u64);\n                    metrics.spill_chunks = metrics.spill_chunks.saturating_add(1);\n                    metrics.spill_max_chunks_per_partition = metrics\n                        .spill_max_chunks_per_partition\n                        .max(new_partition.chunks.len() as u64);\n                    metrics.spill_max_partition_bytes = metrics\n                        .spill_max_partition_bytes\n                        .max(new_partition.total_size_bytes() as u64);\n                }\n                let io_state = new_partition.io_state.clone();\n                spill_state.partitions.push(new_partition);\n                io_state\n            };\n\n            io_state.set(SpillIOState::WaitingForWrite);\n            io_states.push(io_state);\n        }\n\n        // Submit single I/O write\n        let buffer_ref = Arc::new(buffer);\n        let _buffer_ref_clone = buffer_ref.clone();\n        let write_complete = Box::new(move |res: Result<i32, crate::CompletionError>| match res {\n            Ok(_) => {\n                let _buf = _buffer_ref_clone.clone();\n                tracing::trace!(\n                    \"Successfully wrote {} batched partitions to disk\",\n                    io_states.len()\n                );\n                for io_state in &io_states {\n                    io_state.set(SpillIOState::WriteComplete);\n                }\n            }\n            Err(e) => {\n                tracing::error!(\"Error writing batched partitions to disk: {e:?}\");\n                for io_state in &io_states {\n                    io_state.set(SpillIOState::Error);\n                }\n            }\n        });\n\n        let completion = Completion::new_write(write_complete);\n        let file = spill_state.temp_file.file.clone();\n        let completion = file.pwrite(base_file_offset, buffer_ref, completion)?;\n\n        // Update state\n        self.mem_used -= total_mem_freed;\n        spill_state.next_spill_offset += total_size as u64;\n        Ok(Some(completion))\n    }\n\n    /// Spill as many whole partitions as needed to keep the incoming entry within budget.\n    /// Uses batch spilling to combine multiple partitions into a single I/O operation.\n    fn spill_partitions_for_entry(\n        &mut self,\n        entry_size: usize,\n        metrics: Option<&mut HashJoinMetrics>,\n    ) -> Result<Option<Completion>> {\n        if self.mem_used + entry_size <= self.mem_budget {\n            return Ok(None);\n        }\n\n        // Collect all partitions that need to be spilled\n        let mut partitions_to_spill = Vec::new();\n        let mut projected_mem_used = self.mem_used;\n\n        let spill_state = self.spill_state.as_ref().expect(\"spill state must exist\");\n\n        // Sort partitions by size (largest first) to minimize number of spills\n        let mut candidates: Vec<(usize, usize)> = spill_state\n            .partition_buffers\n            .iter()\n            .enumerate()\n            .filter(|(_, p)| !p.is_empty())\n            .map(|(idx, p)| (idx, p.mem_used))\n            .collect();\n        candidates.sort_by(|a, b| b.1.cmp(&a.1)); // Sort descending by mem_used\n\n        for (partition_idx, mem_used) in candidates {\n            if projected_mem_used + entry_size <= self.mem_budget {\n                break;\n            }\n            partitions_to_spill.push(partition_idx);\n            projected_mem_used -= mem_used;\n        }\n\n        if partitions_to_spill.is_empty() {\n            return Ok(None);\n        }\n\n        self.spill_multiple_partitions(&partitions_to_spill, metrics)\n    }\n\n    /// Convert a never-spilled partition buffer into in-memory buckets for probing.\n    fn materialize_partition_in_memory(&mut self, partition_idx: usize) {\n        let spill_state = self.spill_state.as_mut().expect(\"spill state must exist\");\n        if spill_state.find_partition(partition_idx).is_some() {\n            return;\n        }\n\n        let partition_buffer = &mut spill_state.partition_buffers[partition_idx];\n        if partition_buffer.is_empty() {\n            return;\n        }\n\n        let entries = std::mem::take(&mut partition_buffer.entries);\n        // we don't change self.mem_used here, as these entries\n        // were always in memory. we’re just changing their layout\n        partition_buffer.mem_used = 0;\n\n        let bucket_count = entries.len().next_power_of_two().max(64);\n        let mut buckets = (0..bucket_count)\n            .map(|_| HashBucket::new())\n            .collect::<Vec<_>>();\n        for entry in entries {\n            let bucket_idx = (entry.hash as usize) % bucket_count;\n            buckets[bucket_idx].insert(entry);\n        }\n\n        let matched_bits = if self.track_matched {\n            buckets\n                .iter()\n                .map(|b| vec![false; b.entries.len()])\n                .collect()\n        } else {\n            Vec::new()\n        };\n        let mut partition = SpilledPartition::new(partition_idx);\n        partition.state = PartitionState::InMemory;\n        partition.buckets = buckets;\n        partition.matched_bits = matched_bits;\n        partition.resident_mem = 0;\n        spill_state.partitions.push(partition);\n    }\n\n    /// Finalize the build phase and prepare for probing.\n    /// If spilled, flushes remaining in-memory partition entries to disk.\n    pub fn finalize_build(\n        &mut self,\n        metrics: Option<&mut HashJoinMetrics>,\n    ) -> Result<IOResult<()>> {\n        let mut metrics = metrics;\n        turso_assert!(\n            self.state == HashTableState::Building || self.state == HashTableState::Spilled,\n            \"Cannot finalize build in unexpected state\",\n            { \"state\": format!(\"{:?}\", self.state) }\n        );\n\n        if self.spill_state.is_some() {\n            {\n                // Check for pending writes from previous call\n                let spill_state = self.spill_state.as_ref().expect(\"spill state must exist\");\n                for spilled in &spill_state.partitions {\n                    if matches!(spilled.io_state.get(), SpillIOState::WaitingForWrite) {\n                        io_yield_one!(Completion::new_yield());\n                    }\n                }\n            }\n            // Determine which partitions need to spill vs stay in memory without holding\n            // a mutable borrow across the spill/materialize calls.\n            let mut spill_targets = Vec::new();\n            let mut materialize_targets = Vec::new();\n            {\n                let spill_state = self.spill_state.as_ref().expect(\"spill state must exist\");\n                for partition_idx in 0..spill_state.partitioning.count {\n                    let partition = &spill_state.partition_buffers[partition_idx];\n                    if partition.is_empty() {\n                        continue;\n                    }\n                    if spill_state.find_partition(partition_idx).is_some() {\n                        spill_targets.push(partition_idx);\n                    } else {\n                        materialize_targets.push(partition_idx);\n                    }\n                }\n            }\n            for partition_idx in spill_targets {\n                if let Some(completion) =\n                    self.spill_partition(partition_idx, metrics.as_deref_mut())?\n                {\n                    // Return I/O completion to caller, they will re-enter after completion\n                    if !completion.finished() {\n                        io_yield_one!(completion);\n                    }\n                }\n            }\n            for partition_idx in materialize_targets {\n                self.materialize_partition_in_memory(partition_idx);\n            }\n        }\n        self.current_spill_partition_idx = 0;\n        self.state = HashTableState::Probing;\n        Ok(IOResult::Done(()))\n    }\n\n    /// Probe the hash table with the given keys, returns the first matching entry if found.\n    /// NOTE: Calling `probe` on a spilled table requires the relevant partition to be loaded.\n    /// Returns None immediately if any probe key is NULL since NULL != NULL in SQL.\n    pub fn probe(\n        &mut self,\n        probe_keys: Vec<Value>,\n        metrics: Option<&mut HashJoinMetrics>,\n    ) -> Option<&HashEntry> {\n        turso_assert!(\n            self.state == HashTableState::Probing,\n            \"Cannot probe hash table in unexpected state\",\n            { \"state\": format!(\"{:?}\", self.state) }\n        );\n\n        // Skip probing if any key is NULL - NULL can never match anything in SQL\n        if has_null_key(&probe_keys) {\n            self.current_probe_keys = Some(probe_keys);\n            self.current_probe_hash = None;\n            return None;\n        }\n\n        // Compute hash of probe keys using collations\n        let hash = {\n            let key_refs: Vec<ValueRef> = probe_keys.iter().map(|v| v.as_ref()).collect();\n            hash_join_key(&key_refs, &self.collations)\n        };\n        self.current_probe_keys = Some(probe_keys);\n        self.current_probe_hash = Some(hash);\n\n        // Reset probe state\n        self.probe_entry_idx = 0;\n\n        if self.spill_state.is_some() {\n            // In spilled mode, search through loaded entries from spilled partitions\n            // that match this probe key's partition\n            let partitioning = {\n                let spill_state = self.spill_state.as_ref().expect(\"spill state must exist\");\n                spill_state.partitioning\n            };\n            let target_partition = partitioning.index(hash);\n            self.record_probe_call(metrics);\n            self.touch_partition_lru(target_partition);\n\n            let bucket_idx = {\n                let spill_state = self.spill_state.as_ref().expect(\"spill state must exist\");\n                let partition = spill_state.find_partition(target_partition)?;\n                if partition.buckets.is_empty() {\n                    return None;\n                }\n                (hash as usize) % partition.buckets.len()\n            };\n\n            self.probe_bucket_idx = bucket_idx;\n            self.current_spill_partition_idx = target_partition;\n\n            let match_idx = {\n                let key_refs: Vec<ValueRef> = self\n                    .current_probe_keys\n                    .as_ref()\n                    .expect(\"probe keys were set\")\n                    .iter()\n                    .map(|v| v.as_ref())\n                    .collect();\n                let spill_state = self.spill_state.as_ref().expect(\"spill state must exist\");\n                let partition = spill_state.find_partition(target_partition)?;\n                let bucket = &partition.buckets[bucket_idx];\n                let mut found = None;\n                for (idx, entry) in bucket.entries.iter().enumerate() {\n                    if entry.hash == hash\n                        && keys_equal(&entry.key_values, &key_refs, &self.collations)\n                    {\n                        found = Some(idx);\n                        break;\n                    }\n                }\n                found\n            };\n\n            if let Some(idx) = match_idx {\n                self.probe_entry_idx = idx + 1;\n                let spill_state = self.spill_state.as_ref().expect(\"spill state must exist\");\n                let partition = spill_state.find_partition(target_partition)?;\n                let bucket = &partition.buckets[bucket_idx];\n                return bucket.entries.get(idx);\n            }\n            None\n        } else {\n            // Normal mode - search in hash buckets\n            let bucket_idx = (hash as usize) % self.buckets.len();\n            self.probe_bucket_idx = bucket_idx;\n            let match_idx = {\n                let key_refs: Vec<ValueRef> = self\n                    .current_probe_keys\n                    .as_ref()\n                    .expect(\"probe keys were set\")\n                    .iter()\n                    .map(|v| v.as_ref())\n                    .collect();\n                let bucket = &self.buckets[bucket_idx];\n                let mut found = None;\n                for (idx, entry) in bucket.entries.iter().enumerate() {\n                    if entry.hash == hash\n                        && keys_equal(&entry.key_values, &key_refs, &self.collations)\n                    {\n                        found = Some(idx);\n                        break;\n                    }\n                }\n                found\n            };\n\n            if let Some(idx) = match_idx {\n                self.probe_entry_idx = idx + 1;\n                return self.buckets[bucket_idx].entries.get(idx);\n            }\n            None\n        }\n    }\n\n    /// Get the next matching entry for the current probe keys.\n    pub fn next_match(&mut self) -> Option<&HashEntry> {\n        turso_assert!(\n            self.state == HashTableState::Probing || self.state == HashTableState::GraceProcessing,\n            \"Cannot get next match in unexpected state\",\n            { \"state\": format!(\"{:?}\", self.state) }\n        );\n\n        turso_assert!(self.current_probe_keys.is_some(), \"probe keys must be set\");\n        let probe_keys = self.current_probe_keys.as_ref()?;\n        let key_refs: Vec<ValueRef> = probe_keys.iter().map(|v| v.as_ref()).collect();\n        let hash = match self.current_probe_hash {\n            Some(h) => h,\n            None => {\n                let h = hash_join_key(&key_refs, &self.collations);\n                self.current_probe_hash = Some(h);\n                h\n            }\n        };\n\n        if let Some(spill_state) = self.spill_state.as_ref() {\n            let partition_idx = self.current_spill_partition_idx;\n\n            turso_assert_eq!(partition_idx, self.partition_index(hash));\n            let partition = spill_state.find_partition(partition_idx)?;\n            if partition.buckets.is_empty() {\n                return None;\n            }\n\n            let bucket = &partition.buckets[self.probe_bucket_idx];\n            // Continue from where we left off\n            for idx in self.probe_entry_idx..bucket.entries.len() {\n                let entry = &bucket.entries[idx];\n                if entry.hash == hash && keys_equal(&entry.key_values, &key_refs, &self.collations)\n                {\n                    self.probe_entry_idx = idx + 1;\n                    return Some(entry);\n                }\n            }\n            None\n        } else {\n            // non-spilled case, seach in main buckets\n            let bucket = &self.buckets[self.probe_bucket_idx];\n            for idx in self.probe_entry_idx..bucket.entries.len() {\n                let entry = &bucket.entries[idx];\n                if entry.hash == hash && keys_equal(&entry.key_values, &key_refs, &self.collations)\n                {\n                    // update probe entry index for next call\n                    self.probe_entry_idx = idx + 1;\n                    return Some(entry);\n                }\n            }\n            None\n        }\n    }\n\n    /// Mark the current matched entry as \"matched\" for outer join tracking.\n    /// Must be called after a successful probe/next_match.\n    pub fn mark_current_matched(&mut self) {\n        if !self.track_matched {\n            return;\n        }\n        let entry_idx = self\n            .probe_entry_idx\n            .checked_sub(1)\n            .expect(\"mark_current_matched called without prior probe match\");\n        let bucket_idx = self.probe_bucket_idx;\n        if let Some(spill_state) = &mut self.spill_state {\n            let partition_idx = self.current_spill_partition_idx;\n            let partition = spill_state\n                .find_partition_mut(partition_idx)\n                .expect(\"spilled partition missing during mark_current_matched\");\n            partition.matched_bits[bucket_idx][entry_idx] = true;\n        } else {\n            self.matched_bits[bucket_idx][entry_idx] = true;\n        }\n    }\n\n    /// Reset all matched_bits to false. Called at the start of each outer-loop\n    /// iteration so that marks from a previous iteration don't suppress NULL-fill\n    /// rows in the current one.\n    pub fn reset_matched_bits(&mut self) {\n        if let Some(spill_state) = self.spill_state.as_mut() {\n            for partition in &mut spill_state.partitions {\n                for bits in &mut partition.matched_bits {\n                    bits.fill(false);\n                }\n            }\n        }\n        for bits in &mut self.matched_bits {\n            bits.fill(false);\n        }\n    }\n\n    /// Reset the unmatched scan state to the beginning.\n    pub fn begin_unmatched_scan(&mut self) {\n        self.unmatched_scan_bucket = 0;\n        self.unmatched_scan_entry = 0;\n        self.unmatched_scan_partition = 0;\n    }\n\n    /// Advance to the next unmatched entry in the hash table.\n    /// Returns the entry if found, or None when the scan is complete\n    /// (or a spilled partition needs loading).\n    pub fn next_unmatched(&mut self) -> Option<&HashEntry> {\n        let has_spill_state = self.spill_state.is_some();\n        let in_grace = self.grace_state.is_some();\n        let has_pending_grace = !in_grace && self.has_grace_partitions();\n\n        if has_spill_state {\n            if in_grace {\n                // During grace: scan only the partition currently owned by grace.\n                return self.next_unmatched_current_grace_partition();\n            }\n            if has_pending_grace {\n                // Before grace starts, emit unmatched rows from never-spilled in-memory\n                // partitions here. Grace will handle only partitions that actually have\n                // disk chunks, so skipping these would lose unmatched build rows.\n                return self.next_unmatched_spilled_in_memory_only();\n            }\n            return self.next_unmatched_spilled();\n        }\n        self.next_unmatched_main_buckets()\n    }\n\n    fn next_unmatched_main_buckets(&mut self) -> Option<&HashEntry> {\n        while self.unmatched_scan_bucket < self.buckets.len() {\n            let bucket = &self.buckets[self.unmatched_scan_bucket];\n            let matched = &self.matched_bits[self.unmatched_scan_bucket];\n            while self.unmatched_scan_entry < bucket.entries.len() {\n                let idx = self.unmatched_scan_entry;\n                self.unmatched_scan_entry += 1;\n                if !matched[idx] {\n                    return Some(&bucket.entries[idx]);\n                }\n            }\n            self.unmatched_scan_bucket += 1;\n            self.unmatched_scan_entry = 0;\n        }\n        None\n    }\n\n    /// Advance to the next unmatched entry across spilled partitions.\n    /// Returns None when a partition needs loading (caller must load and retry).\n    fn next_unmatched_spilled(&mut self) -> Option<&HashEntry> {\n        let spill_state = self.spill_state.as_ref()?;\n        while self.unmatched_scan_partition < spill_state.partitions.len() {\n            let partition = &spill_state.partitions[self.unmatched_scan_partition];\n            if !partition.is_loaded() {\n                // Partition not loaded yet — caller needs to load it.\n                // Return None to signal we need I/O; caller will re-enter.\n                return None;\n            }\n            while self.unmatched_scan_bucket < partition.buckets.len() {\n                let bucket = &partition.buckets[self.unmatched_scan_bucket];\n                let matched = &partition.matched_bits[self.unmatched_scan_bucket];\n                while self.unmatched_scan_entry < bucket.entries.len() {\n                    let idx = self.unmatched_scan_entry;\n                    self.unmatched_scan_entry += 1;\n                    if !matched[idx] {\n                        return Some(&bucket.entries[idx]);\n                    }\n                }\n                self.unmatched_scan_bucket += 1;\n                self.unmatched_scan_entry = 0;\n            }\n            self.unmatched_scan_partition += 1;\n            self.unmatched_scan_bucket = 0;\n            self.unmatched_scan_entry = 0;\n        }\n        None\n    }\n\n    /// Scan unmatched rows from partitions that stayed resident in memory after spilling.\n    /// These partitions have no disk chunks, so grace never revisits them.\n    fn next_unmatched_spilled_in_memory_only(&mut self) -> Option<&HashEntry> {\n        let spill_state = self.spill_state.as_ref()?;\n        while self.unmatched_scan_partition < spill_state.partitions.len() {\n            let partition = &spill_state.partitions[self.unmatched_scan_partition];\n            if !partition.chunks.is_empty() {\n                self.unmatched_scan_partition += 1;\n                self.unmatched_scan_bucket = 0;\n                self.unmatched_scan_entry = 0;\n                continue;\n            }\n\n            turso_assert!(\n                partition.is_loaded(),\n                \"in-memory partition unexpectedly unavailable during unmatched scan\",\n                { \"partition_idx\": partition.partition_idx }\n            );\n\n            while self.unmatched_scan_bucket < partition.buckets.len() {\n                let bucket = &partition.buckets[self.unmatched_scan_bucket];\n                let matched = &partition.matched_bits[self.unmatched_scan_bucket];\n                while self.unmatched_scan_entry < bucket.entries.len() {\n                    let idx = self.unmatched_scan_entry;\n                    self.unmatched_scan_entry += 1;\n                    if !matched[idx] {\n                        return Some(&bucket.entries[idx]);\n                    }\n                }\n                self.unmatched_scan_bucket += 1;\n                self.unmatched_scan_entry = 0;\n            }\n\n            self.unmatched_scan_partition += 1;\n            self.unmatched_scan_bucket = 0;\n            self.unmatched_scan_entry = 0;\n        }\n        None\n    }\n\n    /// Scan unmatched entries only for the active grace partition.\n    /// Grace unmatched emission happens partition-by-partition before eviction, so\n    /// scanning any other loaded partition can duplicate or suppress rows.\n    fn next_unmatched_current_grace_partition(&mut self) -> Option<&HashEntry> {\n        let partition_idx = self.grace_state.as_ref()?.current_partition_idx()?;\n        let spill_state = self.spill_state.as_ref()?;\n        let partition = spill_state\n            .find_partition(partition_idx)\n            .expect(\"current grace partition missing from spill state\");\n\n        if !partition.is_loaded() {\n            return None;\n        }\n\n        while self.unmatched_scan_bucket < partition.buckets.len() {\n            let bucket = &partition.buckets[self.unmatched_scan_bucket];\n            let matched = &partition.matched_bits[self.unmatched_scan_bucket];\n            while self.unmatched_scan_entry < bucket.entries.len() {\n                let idx = self.unmatched_scan_entry;\n                self.unmatched_scan_entry += 1;\n                if !matched[idx] {\n                    return Some(&bucket.entries[idx]);\n                }\n            }\n            self.unmatched_scan_bucket += 1;\n            self.unmatched_scan_entry = 0;\n        }\n        None\n    }\n\n    /// Get the current partition index for unmatched scan (for spilled partition loading).\n    pub fn unmatched_scan_current_partition(&self) -> Option<usize> {\n        if let Some(grace_state) = self.grace_state.as_ref() {\n            return grace_state.current_partition_idx();\n        }\n        if self.has_grace_partitions() {\n            return None;\n        }\n        let spill_state = self.spill_state.as_ref()?;\n        if self.unmatched_scan_partition < spill_state.partitions.len() {\n            Some(spill_state.partitions[self.unmatched_scan_partition].partition_idx)\n        } else {\n            None\n        }\n    }\n\n    /// Get the number of spilled partitions.\n    pub fn num_partitions(&self) -> usize {\n        self.spill_state\n            .as_ref()\n            .map(|s| s.partitions.len())\n            .unwrap_or(0)\n    }\n\n    /// Check if a specific partition is loaded and ready for probing.\n    pub fn is_partition_loaded(&self, partition_idx: usize) -> bool {\n        self.spill_state\n            .as_ref()\n            .and_then(|s| s.find_partition(partition_idx))\n            .is_some_and(|p| p.is_loaded())\n    }\n\n    /// Re-entrantly load spilled partitions from disk\n    pub fn load_spilled_partition(\n        &mut self,\n        partition_idx: usize,\n        mut metrics: Option<&mut HashJoinMetrics>,\n    ) -> Result<IOResult<()>> {\n        loop {\n            // to avoid holding mut borrows, split this into two phases.\n            let action = {\n                let spill_state = match &mut self.spill_state {\n                    Some(s) => s,\n                    None => return Ok(IOResult::Done(())),\n                };\n\n                let spilled = match spill_state.find_partition_mut(partition_idx) {\n                    Some(p) => p,\n                    None => return Ok(IOResult::Done(())),\n                };\n                let io_state = spilled.io_state.get();\n\n                if unlikely(matches!(io_state, SpillIOState::Error)) {\n                    return Err(LimboError::InternalError(\n                        \"hash join spill I/O failure\".into(),\n                    ));\n                }\n                // Already fully loaded\n                if spilled.is_loaded() {\n                    SpillAction::AlreadyLoaded\n                } else if matches!(io_state, SpillIOState::WaitingForRead) {\n                    // We've scheduled a read, caller must wait for completion.\n                    SpillAction::WaitingForIO\n                } else if matches!(io_state, SpillIOState::ReadComplete) {\n                    SpillAction::ParseChunk { partition_idx }\n                } else {\n                    match spilled.current_chunk() {\n                        Some(chunk) => {\n                            let read_size = chunk.size_bytes;\n                            let file_offset = chunk.file_offset;\n                            let is_first_load = matches!(spilled.state, PartitionState::OnDisk)\n                                && spilled.current_chunk_idx == 0;\n                            if is_first_load {\n                                let total_entries = spilled.total_num_entries();\n                                let bucket_count = total_entries.next_power_of_two().max(64);\n                                spilled.buckets =\n                                    (0..bucket_count).map(|_| HashBucket::new()).collect();\n                                spilled.parsed_entries = 0;\n                                spilled.partial_entry.clear();\n                            }\n\n                            if read_size == 0 {\n                                // Empty chunk: skip it and move to the next.\n                                spilled.current_chunk_idx += 1;\n                                if spilled.has_more_chunks() {\n                                    SpillAction::Restart\n                                } else {\n                                    // No more chunks, but nothing to read; mark loaded.\n                                    spilled.state = PartitionState::Loaded;\n                                    SpillAction::NoChunks\n                                }\n                            } else {\n                                // Non-empty chunk, schedule a read for it.\n                                let buffer_len = spilled.buffer_len.clone();\n                                let read_buffer_ref = spilled.read_buffer.clone();\n\n                                spilled.io_state.set(SpillIOState::WaitingForRead);\n                                spilled.state = PartitionState::Loading;\n\n                                SpillAction::LoadChunk {\n                                    read_size,\n                                    file_offset,\n                                    io_state: spilled.io_state.clone(),\n                                    buffer_len,\n                                    read_buffer_ref,\n                                }\n                            }\n                        }\n                        None => {\n                            // No chunks at all: partition is logically empty, mark as loaded.\n                            spilled.state = PartitionState::Loaded;\n                            SpillAction::NoChunks\n                        }\n                    }\n                }\n            };\n\n            match action {\n                SpillAction::AlreadyLoaded => {\n                    self.touch_partition_lru(partition_idx);\n                    return Ok(IOResult::Done(()));\n                }\n                SpillAction::NoChunks => {\n                    self.evict_partitions_to_fit(0, partition_idx);\n                    self.record_partition_resident(partition_idx, 0);\n                    return Ok(IOResult::Done(()));\n                }\n                SpillAction::NotFound => {\n                    return Ok(IOResult::Done(()));\n                }\n                SpillAction::ParseChunk { partition_idx } => {\n                    match self.parse_partition_chunk(partition_idx, metrics.as_deref_mut())? {\n                        ParseChunkResult::MoreChunks => continue,\n                        ParseChunkResult::Done { resident_mem } => {\n                            self.evict_partitions_to_fit(resident_mem, partition_idx);\n                            self.record_partition_resident(partition_idx, resident_mem);\n                            return Ok(IOResult::Done(()));\n                        }\n                    }\n                }\n                SpillAction::WaitingForIO => {\n                    io_yield_one!(Completion::new_yield());\n                }\n                SpillAction::Restart => {\n                    // We advanced state (e.g., moved past an empty chunk or completed a chunk),\n                    // so just loop again and recompute the next action.\n                    continue;\n                }\n                SpillAction::LoadChunk {\n                    read_size,\n                    file_offset,\n                    io_state,\n                    buffer_len,\n                    read_buffer_ref,\n                } => {\n                    let read_buffer = Arc::new(Buffer::new_temporary(read_size));\n                    let read_complete = Box::new(\n                        move |res: Result<(Arc<Buffer>, i32), CompletionError>| match res {\n                            Ok((buf, bytes_read)) => {\n                                tracing::trace!(\n                                    \"Completed read of spilled partition chunk: bytes_read={}\",\n                                    bytes_read\n                                );\n                                let mut persistent_buf = read_buffer_ref.write();\n                                persistent_buf.clear();\n                                persistent_buf\n                                    .extend_from_slice(&buf.as_slice()[..bytes_read as usize]);\n                                buffer_len.store(bytes_read as usize, atomic::Ordering::Release);\n                                io_state.set(SpillIOState::ReadComplete);\n                                None\n                            }\n                            Err(e) => {\n                                tracing::error!(\"Error reading spilled partition chunk: {e:?}\");\n                                io_state.set(SpillIOState::Error);\n                                None\n                            }\n                        },\n                    );\n                    let completion = Completion::new_read(read_buffer, read_complete);\n                    let spill_state = self.spill_state.as_ref().expect(\"spill state must exist\");\n                    let c = spill_state.temp_file.file.pread(file_offset, completion)?;\n                    if !c.finished() {\n                        io_yield_one!(c);\n                    }\n                }\n            }\n        }\n    }\n\n    /// Parse entries from the current chunk buffer into buckets for a partition.\n    fn parse_partition_chunk(\n        &mut self,\n        partition_idx: usize,\n        mut metrics: Option<&mut HashJoinMetrics>,\n    ) -> Result<ParseChunkResult> {\n        let (has_more_chunks, resident_mem) = {\n            let spill_state = self.spill_state.as_mut().expect(\"spill state must exist\");\n            let partition = spill_state\n                .find_partition_mut(partition_idx)\n                .expect(\"partition must exist for parsing\");\n\n            let data_len = partition.buffer_len();\n            if let Some(metrics) = metrics.as_mut() {\n                metrics.load_bytes_read = metrics.load_bytes_read.saturating_add(data_len as u64);\n            }\n\n            let data_guard = partition.read_buffer.read();\n            let data = &data_guard[..data_len];\n            let parse_buf = if partition.partial_entry.is_empty() {\n                data.to_vec()\n            } else {\n                let mut combined = Vec::with_capacity(partition.partial_entry.len() + data.len());\n                combined.extend_from_slice(&partition.partial_entry);\n                combined.extend_from_slice(data);\n                combined\n            };\n            drop(data_guard);\n\n            partition.partial_entry.clear();\n            partition.buffer_len.store(0, atomic::Ordering::Release);\n            partition.read_buffer.write().clear();\n            partition.io_state.set(SpillIOState::None);\n\n            let mut offset = 0;\n            while offset < parse_buf.len() {\n                let Some((entry_len, varint_size)) = read_varint_partial(&parse_buf[offset..])?\n                else {\n                    partition\n                        .partial_entry\n                        .extend_from_slice(&parse_buf[offset..]);\n                    break;\n                };\n\n                let total_needed = varint_size + entry_len as usize;\n                if offset + total_needed > parse_buf.len() {\n                    partition\n                        .partial_entry\n                        .extend_from_slice(&parse_buf[offset..]);\n                    break;\n                }\n\n                let start = offset + varint_size;\n                let end = start + entry_len as usize;\n                let (entry, consumed) = HashEntry::deserialize(&parse_buf[start..end])?;\n                turso_assert!(\n                    consumed == entry_len as usize,\n                    \"expected to consume entire entry\"\n                );\n\n                let bucket_idx = (entry.hash as usize) % partition.buckets.len();\n                partition.buckets[bucket_idx].insert(entry);\n                partition.parsed_entries += 1;\n                offset += total_needed;\n            }\n\n            partition.current_chunk_idx += 1;\n\n            if partition.has_more_chunks() {\n                (true, 0)\n            } else {\n                if unlikely(!partition.partial_entry.is_empty()) {\n                    return Err(LimboError::Corrupt(\"HashEntry: truncated entry\".into()));\n                }\n                let total_num_entries = partition.total_num_entries();\n                turso_assert!(\n                    partition.parsed_entries == total_num_entries,\n                    \"parsed entry count mismatch\"\n                );\n                if self.track_matched && partition.matched_bits.is_empty() {\n                    // Only initialize matched_bits on the first load. On subsequent\n                    // reloads (after eviction), the existing bits are preserved so that\n                    // probe marks set during earlier passes are not lost.\n                    partition.matched_bits = partition\n                        .buckets\n                        .iter()\n                        .map(|b| vec![false; b.entries.len()])\n                        .collect();\n                }\n                partition.state = PartitionState::Loaded;\n                partition.resident_mem = Self::partition_bucket_mem(&partition.buckets);\n                // Release staging buffer to free memory now that buckets are built.\n                partition.buffer_len.store(0, atomic::Ordering::SeqCst);\n                partition.read_buffer.write().clear();\n                (false, partition.resident_mem)\n            }\n        };\n\n        if has_more_chunks {\n            Ok(ParseChunkResult::MoreChunks)\n        } else {\n            Ok(ParseChunkResult::Done { resident_mem })\n        }\n    }\n\n    /// Probe a specific partition with the given keys. The partition must be loaded first via `load_spilled_partition`.\n    /// VDBE *must* call load_spilled_partition(partition_idx) and get IOResult::Done before calling probe.\n    /// Returns None immediately if any probe key is NULL since NULL != NULL in SQL.\n    pub fn probe_partition(\n        &mut self,\n        partition_idx: usize,\n        probe_keys: &[Value],\n        metrics: Option<&mut HashJoinMetrics>,\n    ) -> Option<&HashEntry> {\n        // Skip probing if any key is NULL - NULL can never match anything in SQL\n        if has_null_key(probe_keys) {\n            self.current_probe_keys = Some(probe_keys.to_vec());\n            self.current_probe_hash = None;\n            return None;\n        }\n\n        let key_refs: Vec<ValueRef> = probe_keys.iter().map(|v| v.as_ref()).collect();\n        let hash = hash_join_key(&key_refs, &self.collations);\n\n        // Store probe keys for subsequent next_match calls\n        self.current_probe_keys = Some(probe_keys.to_vec());\n        self.current_probe_hash = Some(hash);\n\n        self.record_probe_call(metrics);\n        self.touch_partition_lru(partition_idx);\n        let spill_state = self.spill_state.as_ref()?;\n        let partition = spill_state.find_partition(partition_idx)?;\n\n        if !partition.is_loaded() || partition.buckets.is_empty() {\n            return None;\n        }\n\n        let bucket_idx = (hash as usize) % partition.buckets.len();\n        let bucket = &partition.buckets[bucket_idx];\n\n        self.probe_bucket_idx = bucket_idx;\n        self.current_spill_partition_idx = partition_idx;\n\n        for (idx, entry) in bucket.entries.iter().enumerate() {\n            if entry.hash == hash && keys_equal(&entry.key_values, &key_refs, &self.collations) {\n                self.probe_entry_idx = idx + 1;\n                return Some(entry);\n            }\n        }\n        None\n    }\n\n    /// Get the partition index for a given probe key hash.\n    pub fn partition_for_keys(&self, probe_keys: &[Value]) -> usize {\n        turso_assert!(\n            self.spill_state.is_some(),\n            \"partition_for_keys requires spill state\"\n        );\n        let key_refs: Vec<ValueRef> = probe_keys.iter().map(|v| v.as_ref()).collect();\n        let hash = hash_join_key(&key_refs, &self.collations);\n        self.partition_index(hash)\n    }\n\n    /// Returns true if the hash table has spilled to disk.\n    pub fn has_spilled(&self) -> bool {\n        self.spill_state.is_some()\n    }\n\n    /// Approximate memory used by a partition's buckets.\n    fn partition_bucket_mem(buckets: &[HashBucket]) -> usize {\n        buckets.iter().map(|b| b.size_bytes()).sum()\n    }\n\n    /// Touch a resident spilled partition for LRU ordering without changing its\n    /// accounted memory.\n    fn touch_partition_lru(&self, partition_idx: usize) {\n        let mut lru = self.loaded_partitions_lru.borrow_mut();\n        if let Some(pos) = lru.iter().position(|p| *p == partition_idx) {\n            lru.remove(pos);\n        }\n        lru.push_back(partition_idx);\n    }\n\n    /// Record that a spilled partition is resident with the given memory\n    /// footprint and update LRU ordering.\n    fn record_partition_resident(&mut self, partition_idx: usize, mem_used: usize) {\n        if let Some(spill_state) = self.spill_state.as_mut() {\n            if let Some(partition) = spill_state.find_partition_mut(partition_idx) {\n                self.loaded_partitions_mem = self\n                    .loaded_partitions_mem\n                    .saturating_sub(partition.resident_mem);\n                partition.resident_mem = mem_used;\n                self.loaded_partitions_mem += mem_used;\n                self.touch_partition_lru(partition_idx);\n            }\n        }\n    }\n\n    fn evict_partitions_to_fit(&mut self, incoming_mem: usize, protect_idx: usize) {\n        while self.mem_used + self.loaded_partitions_mem + incoming_mem > self.mem_budget {\n            let Some(victim_idx) = self.next_evictable(protect_idx) else {\n                break;\n            };\n\n            let mut freed = 0;\n            if let Some(spill_state) = self.spill_state.as_mut() {\n                if let Some(victim) = spill_state.find_partition_mut(victim_idx) {\n                    if matches!(victim.state, PartitionState::Loaded) {\n                        freed = victim.resident_mem;\n                        victim.buckets.clear();\n                        victim.state = PartitionState::OnDisk;\n                        victim.resident_mem = 0;\n                        victim.current_chunk_idx = 0;\n                        victim.buffer_len.store(0, atomic::Ordering::Release);\n                        victim.read_buffer.write().clear();\n                        victim.partial_entry.clear();\n                        victim.parsed_entries = 0;\n                        victim.io_state.set(SpillIOState::None);\n                    }\n                }\n            }\n\n            self.loaded_partitions_mem = self.loaded_partitions_mem.saturating_sub(freed);\n        }\n    }\n\n    /// Find the next evictable resident spilled partition (LRU) that is not\n    /// protected and has backing spill data.\n    fn next_evictable(&mut self, protect_idx: usize) -> Option<usize> {\n        let spill_state = self.spill_state.as_ref()?;\n\n        let len = self.loaded_partitions_lru.borrow().len();\n        for i in 0..len {\n            let lru = self.loaded_partitions_lru.borrow();\n            let candidate = lru[i];\n            if candidate == protect_idx {\n                continue;\n            }\n            if let Some(p) = spill_state.find_partition(candidate) {\n                let has_disk = !p.chunks.is_empty();\n                drop(lru);\n                if matches!(p.state, PartitionState::Loaded) && has_disk {\n                    self.loaded_partitions_lru.borrow_mut().remove(i);\n                    return Some(candidate);\n                }\n            }\n        }\n        None\n    }\n\n    /// Buffer a probe row whose target build partition is on disk.\n    /// Called from op_hash_probe when `probe_rowid_reg` is Some and the\n    /// partition is OnDisk.\n    pub fn buffer_probe_row(\n        &mut self,\n        key_values: Vec<Value>,\n        probe_rowid: i64,\n        metrics: Option<&mut HashJoinMetrics>,\n    ) -> Result<IOResult<()>> {\n        let spill_state = self\n            .spill_state\n            .as_ref()\n            .expect(\"buffer_probe_row requires build-side spill state\");\n        let partitioning = spill_state.partitioning;\n\n        // Lazily initialize probe spill state on first call\n        if self.probe_spill_state.is_none() {\n            self.probe_spill_state = Some(ProbeSpillState::new(\n                &self.io,\n                self.temp_store,\n                partitioning,\n                self.mem_budget / 2,\n            )?);\n        }\n\n        let key_refs: Vec<ValueRef> = key_values.iter().map(|v| v.as_ref()).collect();\n        let hash = hash_join_key(&key_refs, &self.collations);\n        let partition_idx = partitioning.index(hash);\n\n        let entry = HashEntry::new(hash, key_values, probe_rowid);\n        let entry_size = entry.size_bytes();\n\n        let probe_state = self\n            .probe_spill_state\n            .as_mut()\n            .expect(\"probe spill state just initialized\");\n\n        probe_state.partition_buffers[partition_idx].insert(entry);\n        probe_state.mem_used += entry_size;\n\n        if let Some(metrics) = metrics {\n            metrics.grace_probe_rows_buffered = metrics.grace_probe_rows_buffered.saturating_add(1);\n        }\n\n        // If probe buffers exceed budget, spill the largest one\n        if probe_state.mem_used > probe_state.mem_budget {\n            if let Some(c) = self.spill_largest_probe_partition(None)? {\n                if !c.finished() {\n                    return Ok(IOResult::IO(IOCompletions::Single(c)));\n                }\n            }\n        }\n\n        Ok(IOResult::Done(()))\n    }\n\n    /// Spill the largest probe partition buffer to disk.\n    fn spill_largest_probe_partition(\n        &mut self,\n        metrics: Option<&mut HashJoinMetrics>,\n    ) -> Result<Option<Completion>> {\n        let probe_state = self\n            .probe_spill_state\n            .as_mut()\n            .expect(\"probe spill state must exist\");\n\n        let largest_idx = probe_state\n            .partition_buffers\n            .iter()\n            .enumerate()\n            .filter(|(_, p)| !p.is_empty())\n            .max_by_key(|(_, p)| p.mem_used)\n            .map(|(idx, _)| idx);\n\n        let Some(partition_idx) = largest_idx else {\n            return Ok(None);\n        };\n\n        Self::spill_probe_partition(probe_state, partition_idx, &self.io, metrics)\n    }\n\n    /// Spill a probe partition buffer to its temp file.\n    fn spill_probe_partition(\n        probe_state: &mut ProbeSpillState,\n        partition_idx: usize,\n        io: &Arc<dyn IO>,\n        metrics: Option<&mut HashJoinMetrics>,\n    ) -> Result<Option<Completion>> {\n        let partition = &probe_state.partition_buffers[partition_idx];\n        if partition.is_empty() {\n            return Ok(None);\n        }\n\n        // Calculate total serialized size\n        let mut total_size = 0usize;\n        let mut entry_sizes = Vec::with_capacity(partition.entries.len());\n        for entry in &partition.entries {\n            let s = entry.serialized_size();\n            entry_sizes.push(s);\n            total_size += varint_len(s as u64) + s;\n        }\n\n        // Serialize into I/O buffer\n        let buffer = Buffer::new_temporary(total_size);\n        let buf = buffer.as_mut_slice();\n        let mut offset = 0;\n        for (entry, &entry_size) in partition.entries.iter().zip(entry_sizes.iter()) {\n            offset += write_varint(&mut buf[offset..], entry_size as u64);\n            offset += entry.serialize_to_slice(&mut buf[offset..]);\n        }\n        turso_assert!(offset == total_size, \"serialized size mismatch\");\n\n        let file_offset = probe_state.next_spill_offset;\n        let num_entries = partition.entries.len();\n        let mem_freed = partition.mem_used;\n\n        probe_state.partition_buffers[partition_idx].clear();\n\n        // Record chunk\n        let io_state = if let Some(existing) = probe_state.find_partition_mut(partition_idx) {\n            existing.add_chunk(file_offset, total_size, num_entries);\n            existing.io_state.clone()\n        } else {\n            let mut new_partition = SpilledPartition::new(partition_idx);\n            new_partition.add_chunk(file_offset, total_size, num_entries);\n            let io_state = new_partition.io_state.clone();\n            probe_state.partitions.push(new_partition);\n            io_state\n        };\n\n        if let Some(metrics) = metrics {\n            metrics.probe_spill_bytes_written = metrics\n                .probe_spill_bytes_written\n                .saturating_add(total_size as u64);\n            metrics.probe_spill_chunks = metrics.probe_spill_chunks.saturating_add(1);\n        }\n\n        io_state.set(SpillIOState::WaitingForWrite);\n        let buffer_ref = Arc::new(buffer);\n        let write_complete = Box::new(move |res: Result<i32, crate::CompletionError>| match res {\n            Ok(_) => io_state.set(SpillIOState::WriteComplete),\n            Err(e) => {\n                tracing::error!(\"Error writing probe partition to disk: {e:?}\");\n                io_state.set(SpillIOState::Error);\n            }\n        });\n\n        let completion = Completion::new_write(write_complete);\n        let file = probe_state.temp_file.file.clone();\n        let completion = file.pwrite(file_offset, buffer_ref, completion)?;\n\n        probe_state.mem_used -= mem_freed;\n        probe_state.next_spill_offset += total_size as u64;\n        let _ = io;\n        Ok(Some(completion))\n    }\n\n    /// Finalize probe-side spilling after the probe cursor is exhausted.\n    /// Flushes remaining in-memory probe buffers for partitions that have spill\n    /// chunks, keeps purely in-memory probe buffers as-is.\n    pub fn finalize_probe_spill(\n        &mut self,\n        metrics: Option<&mut HashJoinMetrics>,\n    ) -> Result<IOResult<()>> {\n        let mut metrics = metrics;\n        let Some(probe_state) = self.probe_spill_state.as_ref() else {\n            return Ok(IOResult::Done(()));\n        };\n\n        // Wait for any pending probe writes\n        for spilled in &probe_state.partitions {\n            if matches!(spilled.io_state.get(), SpillIOState::WaitingForWrite) {\n                io_yield_one!(Completion::new_yield());\n            }\n        }\n\n        // Collect partition indices that need flushing\n        let partition_count = probe_state.partitioning.count;\n        let mut flush_targets = Vec::new();\n        for partition_idx in 0..partition_count {\n            if probe_state.partition_buffers[partition_idx].is_empty() {\n                continue;\n            }\n            if probe_state.find_partition(partition_idx).is_some() {\n                flush_targets.push(partition_idx);\n            }\n        }\n\n        for partition_idx in flush_targets {\n            if let Some(c) = Self::spill_probe_partition(\n                self.probe_spill_state.as_mut().expect(\"probe state exists\"),\n                partition_idx,\n                &self.io,\n                metrics.as_deref_mut(),\n            )? {\n                if !c.finished() {\n                    io_yield_one!(c);\n                }\n            }\n        }\n\n        Ok(IOResult::Done(()))\n    }\n\n    /// Initialize grace processing. Builds partition list, frees in-memory build partitions.\n    /// Returns true if there are partitions to process. No IO.\n    pub fn grace_begin(&mut self) -> bool {\n        if self.probe_spill_state.is_none() || self.spill_state.is_none() {\n            return false;\n        }\n\n        // Build list of partitions that were spilled on the build side\n        let partitions_to_process: Vec<usize> = {\n            let spill_state = self.spill_state.as_ref().expect(\"spill state exists\");\n            spill_state\n                .partitions\n                .iter()\n                .filter(|p| !p.chunks.is_empty())\n                .map(|p| p.partition_idx)\n                .collect()\n        };\n\n        if partitions_to_process.is_empty() {\n            return false;\n        }\n\n        // Free in-memory build partitions -- initial probe is done with them\n        self.free_in_memory_build_partitions();\n\n        self.grace_state = Some(GraceState {\n            probe_entries: Vec::new(),\n            probe_entry_cursor: 0,\n            partitions_to_process,\n            partition_list_idx: 0,\n            load_state: GracePartitionLoadState::NeedBuildLoad,\n        });\n\n        if let Some(probe_state) = self.probe_spill_state.as_mut() {\n            for partition in &mut probe_state.partitions {\n                partition.current_chunk_idx = 0;\n                partition.buffer_len.store(0, atomic::Ordering::Release);\n                partition.read_buffer.write().clear();\n                partition.partial_entry.clear();\n                partition.parsed_entries = 0;\n                partition.io_state.set(SpillIOState::None);\n            }\n        }\n\n        self.state = HashTableState::GraceProcessing;\n        true\n    }\n\n    /// Load build partition at current index + first probe chunk. IO-blocking.\n    /// Returns true if loaded, false if partition list exhausted.\n    pub fn grace_load_current_partition(\n        &mut self,\n        mut metrics: Option<&mut HashJoinMetrics>,\n    ) -> Result<IOResult<bool>> {\n        loop {\n            let grace = self.grace_state.as_ref().expect(\"grace state must exist\");\n            if grace.partition_list_idx >= grace.partitions_to_process.len() {\n                return Ok(IOResult::Done(false));\n            }\n\n            let partition_idx = grace.partitions_to_process[grace.partition_list_idx];\n            match grace.load_state {\n                GracePartitionLoadState::NeedBuildLoad => {\n                    self.evict_all_loaded_partitions();\n                    return_if_io!(\n                        self.load_spilled_partition(partition_idx, metrics.as_deref_mut())\n                    );\n\n                    let grace = self.grace_state.as_mut().expect(\"grace state\");\n                    grace.probe_entries.clear();\n                    grace.probe_entry_cursor = 0;\n                    grace.load_state = GracePartitionLoadState::NeedProbeLoad;\n                }\n                GracePartitionLoadState::NeedProbeLoad => {\n                    return_if_io!(self.grace_load_probe_entries(partition_idx));\n\n                    let grace = self.grace_state.as_mut().expect(\"grace state\");\n                    grace.load_state = GracePartitionLoadState::Ready;\n                    if let Some(m) = metrics.as_mut() {\n                        m.grace_partitions_processed =\n                            m.grace_partitions_processed.saturating_add(1);\n                    }\n                    return Ok(IOResult::Done(true));\n                }\n                GracePartitionLoadState::Ready => return Ok(IOResult::Done(true)),\n            }\n        }\n    }\n\n    /// Advance to next probe entry. Returns keys+rowid or None when exhausted.\n    pub fn grace_next_probe_entry(&mut self) -> Result<IOResult<Option<GraceProbeEntry>>> {\n        loop {\n            let grace = self.grace_state.as_ref().expect(\"grace state must exist\");\n            if grace.probe_entry_cursor < grace.probe_entries.len() {\n                let entry = &grace.probe_entries[grace.probe_entry_cursor];\n                let result = GraceProbeEntry {\n                    key_values: entry.key_values.clone(),\n                    probe_rowid: entry.rowid,\n                };\n                let grace = self.grace_state.as_mut().expect(\"grace state\");\n                grace.probe_entry_cursor += 1;\n                return Ok(IOResult::Done(Some(result)));\n            }\n\n            // Current probe entries exhausted, try loading more\n            let grace = self.grace_state.as_ref().expect(\"grace state\");\n            let partition_idx = match grace.current_partition_idx() {\n                Some(idx) => idx,\n                None => return Ok(IOResult::Done(None)),\n            };\n            match self.grace_try_load_next_probe_chunk(partition_idx)? {\n                IOResult::Done(true) => continue,\n                IOResult::Done(false) => return Ok(IOResult::Done(None)),\n                IOResult::IO(io) => return Ok(IOResult::IO(io)),\n            }\n        }\n    }\n\n    /// Try to load the next probe chunk for the given partition.\n    /// Returns true if more probe entries were loaded, false if exhausted.\n    fn grace_try_load_next_probe_chunk(&mut self, partition_idx: usize) -> Result<IOResult<bool>> {\n        loop {\n            let Some(probe_state) = self.probe_spill_state.as_ref() else {\n                return Ok(IOResult::Done(false));\n            };\n\n            let Some(spilled) = probe_state.find_partition(partition_idx) else {\n                return Ok(IOResult::Done(false));\n            };\n\n            if spilled.current_chunk_idx >= spilled.chunks.len() {\n                return Ok(IOResult::Done(false));\n            }\n\n            match self.grace_load_next_probe_chunk(partition_idx)? {\n                IOResult::Done(true) => return Ok(IOResult::Done(true)),\n                IOResult::Done(false) => continue,\n                IOResult::IO(io) => return Ok(IOResult::IO(io)),\n            }\n        }\n    }\n\n    /// Evict current partition, advance to next. Returns true if more partitions. No IO.\n    pub fn grace_advance_partition(&mut self) -> bool {\n        self.evict_all_loaded_partitions();\n        let grace = self.grace_state.as_mut().expect(\"grace state must exist\");\n        grace.partition_list_idx += 1;\n        grace.probe_entries.clear();\n        grace.probe_entry_cursor = 0;\n        grace.load_state = GracePartitionLoadState::NeedBuildLoad;\n        grace.partition_list_idx < grace.partitions_to_process.len()\n    }\n\n    /// Free all in-memory build partitions (InMemory state).\n    fn free_in_memory_build_partitions(&mut self) {\n        if let Some(spill_state) = self.spill_state.as_mut() {\n            for partition in &mut spill_state.partitions {\n                if matches!(partition.state, PartitionState::InMemory) {\n                    partition.buckets.clear();\n                    partition.state = PartitionState::OnDisk;\n                    partition.resident_mem = 0;\n                }\n            }\n        }\n        // Also free the main buckets\n        self.buckets.clear();\n        self.loaded_partitions_lru.borrow_mut().clear();\n        self.loaded_partitions_mem = 0;\n    }\n\n    /// Evict all currently loaded build partitions.\n    fn evict_all_loaded_partitions(&mut self) {\n        if let Some(spill_state) = self.spill_state.as_mut() {\n            for partition in &mut spill_state.partitions {\n                if matches!(partition.state, PartitionState::Loaded) && !partition.chunks.is_empty()\n                {\n                    partition.buckets.clear();\n                    partition.state = PartitionState::OnDisk;\n                    partition.resident_mem = 0;\n                    partition.current_chunk_idx = 0;\n                    partition.buffer_len.store(0, atomic::Ordering::Release);\n                    partition.read_buffer.write().clear();\n                    partition.partial_entry.clear();\n                    partition.parsed_entries = 0;\n                    partition.io_state.set(SpillIOState::None);\n                }\n            }\n        }\n        self.loaded_partitions_lru.borrow_mut().clear();\n        self.loaded_partitions_mem = 0;\n    }\n\n    /// Load probe entries for a given partition into grace_state.probe_entries.\n    /// Loads from in-memory buffers or from the first spill chunk.\n    fn grace_load_probe_entries(&mut self, partition_idx: usize) -> Result<IOResult<()>> {\n        {\n            let grace = self.grace_state.as_mut().expect(\"grace state\");\n            grace.probe_entries.clear();\n            grace.probe_entry_cursor = 0;\n        }\n\n        let Some(probe_state) = self.probe_spill_state.as_ref() else {\n            return Ok(IOResult::Done(()));\n        };\n\n        // First: check if there are in-memory entries for this partition\n        let buffer = &probe_state.partition_buffers[partition_idx];\n        if !buffer.is_empty() {\n            let grace = self.grace_state.as_mut().expect(\"grace state\");\n            grace.probe_entries.clone_from(&buffer.entries);\n            return Ok(IOResult::Done(()));\n        }\n\n        // Check if there are spill chunks\n        let Some(spilled) = probe_state.find_partition(partition_idx) else {\n            return Ok(IOResult::Done(()));\n        };\n        if spilled.chunks.is_empty() {\n            return Ok(IOResult::Done(()));\n        }\n\n        self.grace_load_next_probe_chunk(partition_idx)\n            .map(|result| result.map(|_| ()))\n    }\n\n    /// Load the next probe spill chunk into grace_state.probe_entries.\n    fn grace_load_next_probe_chunk(&mut self, partition_idx: usize) -> Result<IOResult<bool>> {\n        loop {\n            let action = {\n                let probe_state = self.probe_spill_state.as_mut().expect(\"probe spill state\");\n                let spilled = probe_state\n                    .find_partition_mut(partition_idx)\n                    .expect(\"probe partition must exist\");\n                let io_state = spilled.io_state.get();\n\n                if unlikely(matches!(io_state, SpillIOState::Error)) {\n                    return Err(LimboError::InternalError(\n                        \"grace probe spill I/O failure\".into(),\n                    ));\n                }\n\n                if matches!(io_state, SpillIOState::WaitingForRead) {\n                    GraceProbeChunkAction::WaitingForIO\n                } else if matches!(io_state, SpillIOState::ReadComplete) {\n                    GraceProbeChunkAction::ParseChunk { partition_idx }\n                } else {\n                    match spilled.current_chunk() {\n                        Some(chunk) if chunk.size_bytes == 0 => {\n                            spilled.current_chunk_idx += 1;\n                            GraceProbeChunkAction::Restart\n                        }\n                        Some(chunk) => {\n                            spilled.io_state.set(SpillIOState::WaitingForRead);\n                            GraceProbeChunkAction::LoadChunk {\n                                read_size: chunk.size_bytes,\n                                file_offset: chunk.file_offset,\n                                io_state: spilled.io_state.clone(),\n                                buffer_len: spilled.buffer_len.clone(),\n                                read_buffer_ref: spilled.read_buffer.clone(),\n                            }\n                        }\n                        None => GraceProbeChunkAction::NoMoreChunks,\n                    }\n                }\n            };\n\n            match action {\n                GraceProbeChunkAction::WaitingForIO => {\n                    io_yield_one!(Completion::new_yield());\n                }\n                GraceProbeChunkAction::ParseChunk { partition_idx } => {\n                    return Ok(IOResult::Done(self.parse_grace_probe_chunk(partition_idx)?));\n                }\n                GraceProbeChunkAction::LoadChunk {\n                    read_size,\n                    file_offset,\n                    io_state,\n                    buffer_len,\n                    read_buffer_ref,\n                } => {\n                    let read_buffer = Arc::new(Buffer::new_temporary(read_size));\n                    let read_complete = Box::new(\n                        move |res: Result<(Arc<Buffer>, i32), CompletionError>| match res {\n                            Ok((buf, bytes_read)) => {\n                                let mut persistent_buf = read_buffer_ref.write();\n                                persistent_buf.clear();\n                                persistent_buf\n                                    .extend_from_slice(&buf.as_slice()[..bytes_read as usize]);\n                                buffer_len.store(bytes_read as usize, atomic::Ordering::Release);\n                                io_state.set(SpillIOState::ReadComplete);\n                                None\n                            }\n                            Err(e) => {\n                                mark_unlikely();\n                                tracing::error!(\"Error reading probe chunk: {e:?}\");\n                                io_state.set(SpillIOState::Error);\n                                None\n                            }\n                        },\n                    );\n\n                    let completion = Completion::new_read(read_buffer, read_complete);\n                    let probe_state = self.probe_spill_state.as_ref().expect(\"probe spill state\");\n                    let c = probe_state.temp_file.file.pread(file_offset, completion)?;\n                    if !c.finished() {\n                        io_yield_one!(c);\n                    }\n                }\n                GraceProbeChunkAction::Restart => continue,\n                GraceProbeChunkAction::NoMoreChunks => return Ok(IOResult::Done(false)),\n            }\n        }\n    }\n\n    fn parse_grace_probe_chunk(&mut self, partition_idx: usize) -> Result<bool> {\n        let entries = {\n            let probe_state = self.probe_spill_state.as_mut().expect(\"probe spill state\");\n            let partition = probe_state\n                .find_partition_mut(partition_idx)\n                .expect(\"probe partition must exist for parsing\");\n            let chunk = partition\n                .current_chunk()\n                .expect(\"probe chunk must exist while parsing\");\n            let expected_entries = chunk.num_entries;\n            let data_len = partition.buffer_len();\n\n            let data_guard = partition.read_buffer.read();\n            let data = &data_guard[..data_len];\n            let mut entries = Vec::with_capacity(expected_entries);\n            let mut offset = 0;\n            while offset < data_len {\n                let Some((entry_len, varint_size)) = read_varint_partial(&data[offset..])? else {\n                    return Err(LimboError::InternalError(\n                        \"truncated grace probe spill chunk header\".into(),\n                    ));\n                };\n                let total_needed = varint_size + entry_len as usize;\n                if offset + total_needed > data_len {\n                    return Err(LimboError::InternalError(\n                        \"truncated grace probe spill chunk payload\".into(),\n                    ));\n                }\n                let start = offset + varint_size;\n                let end = start + entry_len as usize;\n                let (entry, _consumed) = HashEntry::deserialize(&data[start..end])?;\n                entries.push(entry);\n                offset += total_needed;\n            }\n            drop(data_guard);\n\n            if unlikely(entries.len() != expected_entries) {\n                return Err(LimboError::InternalError(format!(\n                    \"grace probe spill chunk entry count mismatch: expected {expected_entries}, got {}\",\n                    entries.len()\n                )));\n            }\n\n            partition.buffer_len.store(0, atomic::Ordering::Release);\n            partition.read_buffer.write().clear();\n            partition.io_state.set(SpillIOState::None);\n            partition.current_chunk_idx += 1;\n            entries\n        };\n\n        let grace = self.grace_state.as_mut().expect(\"grace state\");\n        grace.probe_entries = entries;\n        grace.probe_entry_cursor = 0;\n        Ok(!grace.probe_entries.is_empty())\n    }\n\n    /// Returns true if grace processing has any spilled partitions to process.\n    pub fn has_grace_partitions(&self) -> bool {\n        self.probe_spill_state.is_some()\n    }\n\n    /// Close the hash table and free resources.\n    pub fn close(&mut self) {\n        self.state = HashTableState::Closed;\n        self.buckets.clear();\n        self.num_entries = 0;\n        self.mem_used = 0;\n        self.loaded_partitions_lru.borrow_mut().clear();\n        self.loaded_partitions_mem = 0;\n        let _ = self.spill_state.take();\n        self.probe_spill_state = None;\n        self.grace_state = None;\n    }\n}\n\n#[cfg(test)]\nmod hashtests {\n    use super::*;\n    use crate::io::Buffer;\n    use crate::MemoryIO;\n\n    #[test]\n    fn test_hash_function_consistency() {\n        // Test that the same keys produce the same hash\n        let keys1 = vec![\n            ValueRef::from_i64(42),\n            ValueRef::Text(crate::types::TextRef::new(\n                \"hello\",\n                crate::types::TextSubtype::Text,\n            )),\n        ];\n        let keys2 = vec![\n            ValueRef::from_i64(42),\n            ValueRef::Text(crate::types::TextRef::new(\n                \"hello\",\n                crate::types::TextSubtype::Text,\n            )),\n        ];\n        let keys3 = vec![\n            ValueRef::from_i64(43),\n            ValueRef::Text(crate::types::TextRef::new(\n                \"hello\",\n                crate::types::TextSubtype::Text,\n            )),\n        ];\n\n        let collations = vec![CollationSeq::Binary, CollationSeq::Binary];\n        let hash1 = hash_join_key(&keys1, &collations);\n        let hash2 = hash_join_key(&keys2, &collations);\n        let hash3 = hash_join_key(&keys3, &collations);\n\n        assert_eq!(hash1, hash2);\n        assert_ne!(hash1, hash3);\n    }\n\n    #[test]\n    fn test_hash_function_numeric_equivalence() {\n        let collations = vec![CollationSeq::Binary];\n\n        // Zero variants should hash identically\n        let h_zero = hash_join_key(&[ValueRef::from_f64(0.0)], &collations);\n        let h_neg_zero = hash_join_key(&[ValueRef::from_f64(-0.0)], &collations);\n        let h_int_zero = hash_join_key(&[ValueRef::from_i64(0)], &collations);\n        assert_eq!(h_zero, h_neg_zero);\n        assert_eq!(h_zero, h_int_zero);\n\n        // Integer/float representations of the same numeric value should match\n        let h_ten_int = hash_join_key(&[ValueRef::from_i64(10)], &collations);\n        let h_ten_float = hash_join_key(&[ValueRef::from_f64(10.0)], &collations);\n        assert_eq!(h_ten_int, h_ten_float);\n\n        let h_neg_ten_int = hash_join_key(&[ValueRef::from_i64(-10)], &collations);\n        let h_neg_ten_float = hash_join_key(&[ValueRef::from_f64(-10.0)], &collations);\n        assert_eq!(h_neg_ten_int, h_neg_ten_float);\n\n        // Positive/negative values should still differ\n        assert_ne!(h_ten_int, h_neg_ten_int);\n    }\n\n    #[test]\n    fn test_keys_equal() {\n        let key1 = vec![Value::from_i64(42), Value::Text(\"hello\".to_string().into())];\n        let key2 = vec![\n            ValueRef::from_i64(42),\n            ValueRef::Text(crate::types::TextRef::new(\n                \"hello\",\n                crate::types::TextSubtype::Text,\n            )),\n        ];\n        let key3 = vec![\n            ValueRef::from_i64(43),\n            ValueRef::Text(crate::types::TextRef::new(\n                \"hello\",\n                crate::types::TextSubtype::Text,\n            )),\n        ];\n\n        let collations = vec![CollationSeq::Binary, CollationSeq::Binary];\n        assert!(keys_equal(&key1, &key2, &collations));\n        assert!(!keys_equal(&key1, &key3, &collations));\n    }\n\n    #[test]\n    fn test_hash_table_basic() {\n        let io = Arc::new(MemoryIO::new());\n        let config = HashTableConfig {\n            initial_buckets: 4,\n            mem_budget: 1024 * 1024,\n            num_keys: 1,\n            collations: vec![CollationSeq::Binary],\n            temp_store: crate::TempStore::Default,\n            track_matched: false,\n            partition_count: None,\n        };\n        let mut ht = HashTable::new(config, io);\n\n        // Insert some entries (late materialization - only store rowids)\n        let key1 = vec![Value::from_i64(1)];\n        let _ = ht.insert(key1.clone(), 100, vec![], None).unwrap();\n\n        let key2 = vec![Value::from_i64(2)];\n        let _ = ht.insert(key2.clone(), 200, vec![], None).unwrap();\n\n        let _ = ht.finalize_build(None);\n\n        // Probe for key1\n        let result = ht.probe(key1, None);\n        assert!(result.is_some());\n        let entry1 = result.unwrap();\n        assert_eq!(entry1.key_values[0].as_ref(), ValueRef::from_i64(1));\n        assert_eq!(entry1.rowid, 100);\n\n        // Probe for key2\n        let result = ht.probe(key2, None);\n        assert!(result.is_some());\n        let entry2 = result.unwrap();\n        assert_eq!(entry2.key_values[0].as_ref(), ValueRef::from_i64(2));\n        assert_eq!(entry2.rowid, 200);\n\n        // Probe for non-existent key\n        let result = ht.probe(vec![Value::from_i64(999)], None);\n        assert!(result.is_none());\n    }\n\n    #[test]\n    fn test_hash_table_collisions() {\n        let io = Arc::new(MemoryIO::new());\n        let config = HashTableConfig {\n            initial_buckets: 2, // Small number to force collisions\n            mem_budget: 1024 * 1024,\n            num_keys: 1,\n            collations: vec![CollationSeq::Binary],\n            temp_store: crate::TempStore::Default,\n            track_matched: false,\n            partition_count: None,\n        };\n        let mut ht = HashTable::new(config, io);\n\n        // Insert multiple entries (late materialization - only store rowids)\n        for i in 0..10 {\n            let key = vec![Value::from_i64(i)];\n            let _ = ht.insert(key, i * 100, vec![], None).unwrap();\n        }\n\n        let _ = ht.finalize_build(None);\n\n        // Verify all entries can be found\n        for i in 0..10 {\n            let result = ht.probe(vec![Value::from_i64(i)], None);\n            assert!(result.is_some());\n            let entry = result.unwrap();\n            assert_eq!(entry.key_values[0].as_ref(), ValueRef::from_i64(i));\n            assert_eq!(entry.rowid, i * 100);\n        }\n    }\n\n    #[test]\n    fn test_hash_table_duplicate_keys() {\n        let io = Arc::new(MemoryIO::new());\n        let config = HashTableConfig {\n            initial_buckets: 4,\n            mem_budget: 1024 * 1024,\n            num_keys: 1,\n            collations: vec![CollationSeq::Binary],\n            temp_store: crate::TempStore::Default,\n            track_matched: false,\n            partition_count: None,\n        };\n        let mut ht = HashTable::new(config, io);\n\n        // Insert multiple entries with the same key\n        let key = vec![Value::from_i64(42)];\n        for i in 0..3 {\n            let _ = ht.insert(key.clone(), 1000 + i, vec![], None).unwrap();\n        }\n\n        let _ = ht.finalize_build(None);\n\n        // Probe should return first match\n        let result = ht.probe(key, None);\n        assert!(result.is_some());\n        assert_eq!(result.unwrap().rowid, 1000);\n\n        // next_match should return additional matches\n        let result2 = ht.next_match();\n        assert!(result2.is_some());\n        assert_eq!(result2.unwrap().rowid, 1001);\n\n        let result3 = ht.next_match();\n        assert!(result3.is_some());\n        assert_eq!(result3.unwrap().rowid, 1002);\n\n        // No more matches\n        let result4 = ht.next_match();\n        assert!(result4.is_none());\n    }\n\n    #[test]\n    fn test_hash_entry_serialization() {\n        // Test that entries serialize and deserialize correctly\n        let entry = HashEntry::new(\n            12345,\n            vec![\n                Value::from_i64(42),\n                Value::Text(\"hello\".to_string().into()),\n                Value::Null,\n                Value::from_f64(std::f64::consts::PI),\n            ],\n            100,\n        );\n\n        let mut buf = Vec::new();\n        entry.serialize(&mut buf);\n\n        let (deserialized, consumed) = HashEntry::deserialize(&buf).unwrap();\n        assert_eq!(consumed, buf.len());\n        assert_eq!(deserialized.hash, entry.hash);\n        assert_eq!(deserialized.rowid, entry.rowid);\n        assert_eq!(deserialized.key_values.len(), entry.key_values.len());\n\n        for (v1, v2) in deserialized.key_values.iter().zip(entry.key_values.iter()) {\n            match (v1, v2) {\n                (Value::Numeric(Numeric::Integer(i1)), Value::Numeric(Numeric::Integer(i2))) => {\n                    assert_eq!(i1, i2)\n                }\n                (Value::Text(t1), Value::Text(t2)) => assert_eq!(t1.as_str(), t2.as_str()),\n                (Value::Numeric(Numeric::Float(f1)), Value::Numeric(Numeric::Float(f2))) => {\n                    assert!((f64::from(*f1) - f64::from(*f2)).abs() < 1e-10)\n                }\n                (Value::Null, Value::Null) => {}\n                _ => panic!(\"Value type mismatch\"),\n            }\n        }\n    }\n\n    #[test]\n    fn test_serialize_to_slice_matches_serialize() {\n        // Test that serialize_to_slice produces identical output to serialize\n        let entry = HashEntry::new_with_payload(\n            12345,\n            vec![\n                Value::from_i64(42),\n                Value::Text(\"hello world\".to_string().into()),\n                Value::Null,\n                Value::from_f64(std::f64::consts::PI),\n            ],\n            100,\n            vec![Value::Blob(vec![1, 2, 3, 4, 5]), Value::from_i64(-999)],\n        );\n\n        // Serialize using the Vec-based method\n        let mut vec_buf = Vec::new();\n        entry.serialize(&mut vec_buf);\n\n        // Serialize using the slice-based method\n        let size = entry.serialized_size();\n        assert_eq!(\n            size,\n            vec_buf.len(),\n            \"serialized_size must match actual size\"\n        );\n\n        let mut slice_buf = vec![0u8; size];\n        let written = entry.serialize_to_slice(&mut slice_buf);\n        assert_eq!(written, size, \"bytes written must match serialized_size\");\n\n        // Both methods must produce identical output\n        assert_eq!(\n            vec_buf, slice_buf,\n            \"serialize and serialize_to_slice must produce identical output\"\n        );\n\n        // Verify the output is valid by deserializing\n        let (deserialized, consumed) = HashEntry::deserialize(&slice_buf).unwrap();\n        assert_eq!(consumed, size);\n        assert_eq!(deserialized.hash, entry.hash);\n        assert_eq!(deserialized.rowid, entry.rowid);\n        assert_eq!(deserialized.key_values.len(), entry.key_values.len());\n        assert_eq!(\n            deserialized.payload_values.len(),\n            entry.payload_values.len()\n        );\n    }\n\n    #[test]\n    fn test_partition_from_hash() {\n        // Test partition distribution\n        let partitioning = Partitioning::new(16);\n        let mut counts = [0usize; 16];\n        for i in 0u64..10000 {\n            let hash = i.wrapping_mul(0x9E3779B97F4A7C15); // Simple hash spreading\n            let partition = partitioning.index(hash);\n            assert!(partition < counts.len());\n            counts[partition] += 1;\n        }\n\n        // Check reasonable distribution (each partition should have some entries)\n        for count in counts {\n            assert!(count > 0, \"Each partition should have some entries\");\n        }\n    }\n\n    #[test]\n    fn test_spill_chunk_tracking() {\n        // Test that SpilledPartition can track multiple chunks\n        let mut partition = SpilledPartition::new(5);\n        assert_eq!(partition.partition_idx, 5);\n        assert!(partition.chunks.is_empty());\n        assert_eq!(partition.total_size_bytes(), 0);\n        assert_eq!(partition.total_num_entries(), 0);\n\n        // Add first chunk\n        partition.add_chunk(0, 1000, 50);\n        assert_eq!(partition.chunks.len(), 1);\n        assert_eq!(partition.total_size_bytes(), 1000);\n        assert_eq!(partition.total_num_entries(), 50);\n\n        // Add second chunk\n        partition.add_chunk(1000, 500, 25);\n        assert_eq!(partition.chunks.len(), 2);\n        assert_eq!(partition.total_size_bytes(), 1500);\n        assert_eq!(partition.total_num_entries(), 75);\n\n        // Check individual chunks\n        assert_eq!(partition.chunks[0].file_offset, 0);\n        assert_eq!(partition.chunks[0].size_bytes, 1000);\n        assert_eq!(partition.chunks[1].file_offset, 1000);\n        assert_eq!(partition.chunks[1].size_bytes, 500);\n    }\n\n    #[test]\n    fn test_partition_count_override() {\n        let io = Arc::new(MemoryIO::new());\n        let config = HashTableConfig {\n            initial_buckets: 4,\n            mem_budget: 1024,\n            num_keys: 1,\n            collations: vec![CollationSeq::Binary],\n            temp_store: crate::TempStore::Default,\n            track_matched: false,\n            partition_count: Some(64),\n        };\n        let mut ht = HashTable::new(config, io);\n        insert_many_force_spill(&mut ht, 0, 1024);\n        let _ = ht.finalize_build(None).unwrap();\n        assert!(ht.has_spilled());\n\n        let spill_state = ht.spill_state.as_ref().expect(\"spill state exists\");\n        assert_eq!(spill_state.partitioning.count, 64);\n    }\n\n    #[test]\n    fn test_adaptive_partition_count_bounds() {\n        let io = Arc::new(MemoryIO::new());\n        let config = HashTableConfig {\n            initial_buckets: 4,\n            mem_budget: 1024,\n            num_keys: 1,\n            collations: vec![CollationSeq::Binary],\n            temp_store: crate::TempStore::Default,\n            track_matched: false,\n            partition_count: None,\n        };\n        let mut ht = HashTable::new(config, io);\n        insert_many_force_spill(&mut ht, 0, 1024);\n        let _ = ht.finalize_build(None).unwrap();\n        assert!(ht.has_spilled());\n\n        let spill_state = ht.spill_state.as_ref().expect(\"spill state exists\");\n        let count = spill_state.partitioning.count;\n        assert!(count.is_power_of_two());\n        assert!(count >= MIN_PARTITIONS);\n        assert!(count <= MAX_PARTITIONS);\n    }\n\n    #[test]\n    fn test_spill_streaming_parse_multiple_chunks() {\n        let io = Arc::new(MemoryIO::new());\n        let config = HashTableConfig {\n            initial_buckets: 4,\n            mem_budget: 1024,\n            num_keys: 1,\n            collations: vec![CollationSeq::Binary],\n            temp_store: crate::TempStore::Default,\n            track_matched: false,\n            partition_count: Some(16),\n        };\n        let mut ht = HashTable::new(config, io);\n\n        let key = vec![Value::from_i64(1)];\n        for i in 0..2048 {\n            match ht.insert(key.clone(), i, vec![], None).unwrap() {\n                IOResult::Done(()) => {}\n                IOResult::IO(_) => panic!(\"memory IO\"),\n            }\n        }\n\n        match ht.finalize_build(None).unwrap() {\n            IOResult::Done(()) => {}\n            IOResult::IO(_) => panic!(\"memory IO\"),\n        }\n        assert!(ht.has_spilled());\n\n        let partition_idx = ht.partition_for_keys(&key);\n        {\n            let spill_state = ht.spill_state.as_ref().expect(\"spill state exists\");\n            let partition = spill_state\n                .find_partition(partition_idx)\n                .expect(\"partition exists\");\n            assert!(partition.chunks.len() > 1, \"expected multiple spill chunks\");\n        }\n\n        while let IOResult::IO(_) = ht.load_spilled_partition(partition_idx, None).unwrap() {}\n        assert!(ht.is_partition_loaded(partition_idx));\n\n        let entry = ht.probe_partition(partition_idx, &key, None).unwrap();\n        assert_eq!(entry.rowid, 0);\n\n        let mut matches = 1usize;\n        while ht.next_match().is_some() {\n            matches += 1;\n        }\n        assert_eq!(matches, 2048);\n    }\n\n    #[test]\n    fn test_load_partition_empty_chunk() {\n        let io: Arc<dyn IO> = Arc::new(MemoryIO::new());\n        let config = HashTableConfig {\n            initial_buckets: 4,\n            mem_budget: 1024,\n            num_keys: 1,\n            collations: vec![CollationSeq::Binary],\n            temp_store: crate::TempStore::Default,\n            track_matched: false,\n            partition_count: Some(16),\n        };\n        let mut ht = HashTable::new(config, io.clone());\n        let partitioning = Partitioning::new(16);\n        let temp_file = TempFile::with_temp_store(&io, crate::TempStore::Default).unwrap();\n\n        let mut partition = SpilledPartition::new(0);\n        partition.add_chunk(0, 0, 0);\n\n        let spill_state = SpillState {\n            partition_buffers: (0..partitioning.count)\n                .map(|_| PartitionBuffer::new())\n                .collect(),\n            partitions: vec![partition],\n            next_spill_offset: 0,\n            temp_file,\n            partitioning,\n        };\n        ht.spill_state = Some(spill_state);\n        ht.state = HashTableState::Probing;\n\n        while let IOResult::IO(_) = ht.load_spilled_partition(0, None).unwrap() {}\n        assert!(ht.is_partition_loaded(0));\n    }\n\n    #[test]\n    fn test_load_partition_truncated_chunk() {\n        let io: Arc<dyn IO> = Arc::new(MemoryIO::new());\n        let config = HashTableConfig {\n            initial_buckets: 4,\n            mem_budget: 1024,\n            num_keys: 1,\n            collations: vec![CollationSeq::Binary],\n            temp_store: crate::TempStore::Default,\n            track_matched: false,\n            partition_count: Some(16),\n        };\n        let mut ht = HashTable::new(config, io.clone());\n\n        let entry = HashEntry::new(1, vec![Value::from_i64(1)], 7);\n        let mut buf = Vec::new();\n        entry.serialize(&mut buf);\n        let truncated = &buf[..buf.len() - 1];\n\n        let temp_file = TempFile::with_temp_store(&io, crate::TempStore::Default).unwrap();\n        let write_buf = Buffer::new_temporary(truncated.len());\n        write_buf.as_mut_slice().copy_from_slice(truncated);\n        let write_buf = Arc::new(write_buf);\n        let completion = temp_file\n            .file\n            .pwrite(0, write_buf, Completion::new_write(|_| {}))\n            .unwrap();\n        assert!(completion.finished(), \"memory write should complete\");\n\n        let partitioning = Partitioning::new(16);\n        let mut partition = SpilledPartition::new(0);\n        partition.add_chunk(0, truncated.len(), 1);\n\n        let spill_state = SpillState {\n            partition_buffers: (0..partitioning.count)\n                .map(|_| PartitionBuffer::new())\n                .collect(),\n            partitions: vec![partition],\n            next_spill_offset: truncated.len() as u64,\n            temp_file,\n            partitioning,\n        };\n        ht.spill_state = Some(spill_state);\n        ht.state = HashTableState::Probing;\n\n        let mut saw_err = false;\n        loop {\n            match ht.load_spilled_partition(0, None) {\n                Ok(IOResult::Done(())) => break,\n                Ok(IOResult::IO(_)) => continue,\n                Err(_) => {\n                    saw_err = true;\n                    break;\n                }\n            }\n        }\n\n        assert!(saw_err, \"truncated chunk should return an error\");\n    }\n\n    #[test]\n    fn test_hash_function_respects_collation_nocase() {\n        use crate::types::{TextRef, TextSubtype};\n\n        let keys1 = vec![ValueRef::Text(TextRef::new(\"Hello\", TextSubtype::Text))];\n        let keys2 = vec![ValueRef::Text(TextRef::new(\"hello\", TextSubtype::Text))];\n\n        // Under BINARY: hashes must differ\n        let bin_coll = vec![CollationSeq::Binary];\n        let h1_bin = hash_join_key(&keys1, &bin_coll);\n        let h2_bin = hash_join_key(&keys2, &bin_coll);\n        assert_ne!(h1_bin, h2_bin);\n\n        // Under NOCASE: hashes should be equal\n        let nocase_coll = vec![CollationSeq::NoCase];\n        let h1_nc = hash_join_key(&keys1, &nocase_coll);\n        let h2_nc = hash_join_key(&keys2, &nocase_coll);\n        assert_eq!(h1_nc, h2_nc);\n    }\n\n    #[test]\n    fn test_hash_nocase_preserves_non_ascii() {\n        use crate::types::{TextRef, TextSubtype};\n\n        // SQLite NOCASE only affects ASCII a-z/A-Z.\n        // Non-ASCII characters like ü should hash identically regardless of case conversion.\n        let keys1 = vec![ValueRef::Text(TextRef::new(\"über\", TextSubtype::Text))];\n        let keys2 = vec![ValueRef::Text(TextRef::new(\"ÜBER\", TextSubtype::Text))];\n\n        // Under NOCASE: ASCII portion differs (b/B), so hashes should differ\n        // (because SQLite NOCASE doesn't handle Unicode case folding)\n        let nocase_coll = vec![CollationSeq::NoCase];\n        let h1 = hash_join_key(&keys1, &nocase_coll);\n        let h2 = hash_join_key(&keys2, &nocase_coll);\n\n        // The 'b' and 'B' will be lowercased to 'b', but the 'ü' and 'Ü' are not\n        // ASCII so they remain as-is. Since ü != Ü at byte level, hashes will differ.\n        // This is correct SQLite NOCASE behavior (ASCII-only case folding).\n        assert_ne!(\n            h1, h2,\n            \"non-ASCII chars should not be case-folded by NOCASE\"\n        );\n    }\n\n    #[test]\n    fn test_values_equal_with_collations() {\n        use crate::types::{TextRef, TextSubtype};\n\n        let h1 = ValueRef::Text(TextRef::new(\"Hello  \", TextSubtype::Text));\n        let h2 = ValueRef::Text(TextRef::new(\"hello\", TextSubtype::Text));\n\n        // Binary: case / trailing spaces matter\n        assert!(!values_equal(h1, h2, CollationSeq::Binary));\n\n        // NOCASE: case-insensitive but trailing spaces still matter -> likely false\n        assert!(!values_equal(h1, h2, CollationSeq::NoCase));\n\n        // RTRIM: ignore trailing spaces, but case is still significant\n        let h3 = ValueRef::Text(TextRef::new(\"Hello\", TextSubtype::Text));\n        assert!(values_equal(h1, h3, CollationSeq::Rtrim));\n    }\n\n    #[test]\n    fn test_keys_equal_with_collations() {\n        use crate::types::{TextRef, TextSubtype};\n\n        let key1 = vec![Value::Text(\"Hello\".into())];\n        let key2 = vec![ValueRef::Text(TextRef::new(\"hello\", TextSubtype::Text))];\n\n        // Binary: not equal\n        assert!(!keys_equal(&key1, &key2, &[CollationSeq::Binary]));\n\n        // NOCASE: equal\n        assert!(keys_equal(&key1, &key2, &[CollationSeq::NoCase]));\n    }\n\n    #[test]\n    fn test_hash_entry_deserialization_truncated() {\n        let entry = HashEntry::new(123, vec![Value::from_i64(1), Value::Text(\"abc\".into())], 42);\n\n        let mut buf = Vec::new();\n        entry.serialize(&mut buf);\n\n        // Cut off the buffer mid-entry\n        let truncated = &buf[..buf.len() - 2];\n\n        let res = HashEntry::deserialize(truncated);\n        assert!(\n            res.is_err(),\n            \"truncated buffer should be rejected as corrupt\"\n        );\n    }\n\n    #[test]\n    fn test_hash_entry_deserialization_garbage_type_tag() {\n        let entry = HashEntry::new(1, vec![Value::from_i64(10)], 7);\n        let mut buf = Vec::new();\n        entry.serialize(&mut buf);\n\n        // Compute the exact offset of the *first* type tag.\n        // Layout: [0..8] hash | [8..16] rowid | varint(num_keys) | type | payload...\n        let mut corrupted = buf.clone();\n\n        let mut offset = 16;\n        let (_num_keys, varint_len) = read_varint(&corrupted[offset..]).unwrap();\n        offset += varint_len;\n        corrupted[offset] = 0xFF;\n\n        let res = HashEntry::deserialize(&corrupted);\n        assert!(\n            res.is_err(),\n            \"invalid type tag should be rejected as corrupt\"\n        );\n    }\n\n    fn insert_many_force_spill(ht: &mut HashTable, start: i64, count: i64) {\n        for i in 0..count {\n            let rowid = start + i;\n            let key = vec![Value::from_i64(rowid)];\n            let _ = ht.insert(key, rowid, vec![], None);\n        }\n    }\n\n    #[test]\n    fn test_hash_table_spill_and_load_partition_round_trip() {\n        let io = Arc::new(MemoryIO::new());\n        let config = HashTableConfig {\n            initial_buckets: 4,\n            // very small budget to force spill\n            mem_budget: 1024,\n            num_keys: 1,\n            collations: vec![CollationSeq::Binary],\n            temp_store: crate::TempStore::Default,\n            track_matched: false,\n            ..Default::default()\n        };\n        let mut ht = HashTable::new(config, io);\n\n        // Insert enough fat rows to exceed budget and force spills\n        insert_many_force_spill(&mut ht, 0, 1024);\n\n        let _ = ht.finalize_build(None).unwrap();\n        assert!(ht.has_spilled(), \"hash table should have spilled\");\n\n        // Pick a key and find its partition\n        let probe_key = vec![Value::from_i64(10)];\n        let partition_idx = ht.partition_for_keys(&probe_key);\n\n        // Load that partition into memory\n        match ht.load_spilled_partition(partition_idx, None).unwrap() {\n            IOResult::Done(()) => {}\n            IOResult::IO(_) => panic!(\"test harness must drive IO completions here\"),\n        }\n\n        assert!(\n            ht.is_partition_loaded(partition_idx),\n            \"partition must be resident after load_spilled_partition\"\n        );\n\n        // Probe via partition API\n        let entry = ht.probe_partition(partition_idx, &probe_key, None);\n        assert!(entry.is_some()); // here\n        assert_eq!(entry.unwrap().rowid, 10);\n    }\n\n    #[test]\n    fn test_partition_lru_eviction() {\n        let io = Arc::new(MemoryIO::new());\n        // tiny mem_budget so only ~1 partition can stay resident\n        let config = HashTableConfig {\n            initial_buckets: 4,\n            mem_budget: 8 * 1024,\n            num_keys: 1,\n            collations: vec![CollationSeq::Binary],\n            temp_store: crate::TempStore::Default,\n            track_matched: false,\n            partition_count: None,\n        };\n        let mut ht = HashTable::new(config, io);\n\n        // Insert two disjoint key ranges that will hash to different partitions\n        insert_many_force_spill(&mut ht, 0, 256);\n        insert_many_force_spill(&mut ht, 256, 1024);\n\n        let _ = ht.finalize_build(None).unwrap();\n        assert!(ht.has_spilled());\n\n        let key_a = vec![Value::from_i64(1)];\n        let key_b = vec![Value::from_i64(10_001)];\n        let pa = ht.partition_for_keys(&key_a);\n        let pb = ht.partition_for_keys(&key_b);\n        assert_ne!(pa, pb);\n\n        // Load partition A\n        while let IOResult::IO(_) = ht.load_spilled_partition(pa, None).unwrap() {}\n        assert!(ht.is_partition_loaded(pa));\n\n        // Now load partition B, this should (under tight memory) evict A\n        let _ = ht.load_spilled_partition(pb, None).unwrap();\n        assert!(ht.is_partition_loaded(pb));\n\n        // Depending on mem_budget and actual entry sizes, A should now be evicted\n        // We can't *guarantee* that without knowing exact sizes, but in practice\n        // this test will detect regressions in the LRU bookkeeping.\n        assert!(\n            !ht.is_partition_loaded(pa) || ht.loaded_partitions_mem <= ht.mem_budget,\n            \"either partition A is evicted, or loaded memory is within budget\"\n        );\n    }\n\n    #[test]\n    fn test_probe_partition_with_duplicate_keys() {\n        let io = Arc::new(MemoryIO::new());\n        let config = HashTableConfig {\n            initial_buckets: 4,\n            mem_budget: 8 * 1024,\n            num_keys: 1,\n            collations: vec![CollationSeq::Binary],\n            temp_store: crate::TempStore::Default,\n            track_matched: false,\n            partition_count: None,\n        };\n        let mut ht = HashTable::new(config, io);\n\n        let key = vec![Value::from_i64(42)];\n        for i in 0..1024 {\n            match ht.insert(key.clone(), 1000 + i, vec![], None).unwrap() {\n                IOResult::Done(()) => {}\n                IOResult::IO(_) => panic!(\"memory IO\"),\n            }\n        }\n        match ht.finalize_build(None).unwrap() {\n            IOResult::Done(()) => {}\n            IOResult::IO(_) => panic!(\"memory IO\"),\n        }\n\n        assert!(ht.has_spilled());\n        let partition_idx = ht.partition_for_keys(&key);\n\n        match ht.load_spilled_partition(partition_idx, None).unwrap() {\n            IOResult::Done(()) => {}\n            IOResult::IO(_) => panic!(\"memory IO\"),\n        }\n        assert!(ht.is_partition_loaded(partition_idx));\n\n        // First probe should give us the first rowid\n        let entry1 = ht.probe_partition(partition_idx, &key, None).unwrap();\n        assert_eq!(entry1.rowid, 1000);\n\n        // Then iterate through the rest with next_match\n        for i in 0..1023 {\n            let next = ht.next_match().unwrap();\n            assert_eq!(next.rowid, 1001 + i);\n        }\n        assert!(ht.next_match().is_none());\n    }\n\n    #[test]\n    fn test_hash_table_with_payload() {\n        let io = Arc::new(MemoryIO::new());\n        let config = HashTableConfig {\n            initial_buckets: 4,\n            mem_budget: 1024 * 1024,\n            num_keys: 1,\n            collations: vec![CollationSeq::Binary],\n            temp_store: crate::TempStore::Default,\n            track_matched: false,\n            partition_count: None,\n        };\n        let mut ht = HashTable::new(config, io);\n\n        // Insert entries with payload values (simulating cached result columns)\n        let key1 = vec![Value::from_i64(1)];\n        let payload1 = vec![\n            Value::Text(\"Alice\".into()),\n            Value::from_i64(30),\n            Value::from_f64(1000.50),\n        ];\n        let _ = ht.insert(key1.clone(), 100, payload1, None).unwrap();\n\n        let key2 = vec![Value::from_i64(2)];\n        let payload2 = vec![\n            Value::Text(\"Bob\".into()),\n            Value::from_i64(25),\n            Value::from_f64(2000.75),\n        ];\n        let _ = ht.insert(key2.clone(), 200, payload2, None).unwrap();\n\n        let _ = ht.finalize_build(None);\n\n        // Probe and verify payload is returned correctly\n        let result = ht.probe(key1, None);\n        assert!(result.is_some());\n        let entry1 = result.unwrap();\n        assert_eq!(entry1.rowid, 100);\n        assert!(entry1.has_payload());\n        assert_eq!(entry1.payload_values.len(), 3);\n        assert_eq!(entry1.payload_values[0], Value::Text(\"Alice\".into()));\n        assert_eq!(entry1.payload_values[1], Value::from_i64(30));\n        assert_eq!(entry1.payload_values[2], Value::from_f64(1000.50));\n\n        let result = ht.probe(key2, None);\n        assert!(result.is_some());\n        let entry2 = result.unwrap();\n        assert_eq!(entry2.rowid, 200);\n        assert!(entry2.has_payload());\n        assert_eq!(entry2.payload_values[0], Value::Text(\"Bob\".into()));\n        assert_eq!(entry2.payload_values[1], Value::from_i64(25));\n        assert_eq!(entry2.payload_values[2], Value::from_f64(2000.75));\n    }\n\n    #[test]\n    fn test_hash_table_payload_with_nulls() {\n        let io = Arc::new(MemoryIO::new());\n        let config = HashTableConfig {\n            initial_buckets: 4,\n            mem_budget: 1024 * 1024,\n            num_keys: 1,\n            collations: vec![CollationSeq::Binary],\n            temp_store: crate::TempStore::Default,\n            track_matched: false,\n            partition_count: None,\n        };\n        let mut ht = HashTable::new(config, io);\n\n        // Insert entry with NULL values in payload\n        let key = vec![Value::from_i64(1)];\n        let payload = vec![Value::Null, Value::Text(\"test\".into()), Value::Null];\n        let _ = ht.insert(key.clone(), 100, payload, None).unwrap();\n\n        let _ = ht.finalize_build(None);\n\n        let result = ht.probe(key, None);\n        assert!(result.is_some());\n        let entry = result.unwrap();\n        assert_eq!(entry.payload_values.len(), 3);\n        assert_eq!(entry.payload_values[0], Value::Null);\n        assert_eq!(entry.payload_values[1], Value::Text(\"test\".into()));\n        assert_eq!(entry.payload_values[2], Value::Null);\n    }\n\n    #[test]\n    fn test_null_keys_are_skipped() {\n        // In SQL, NULL = NULL is false (actually NULL which is falsy).\n        // Hash joins should skip rows with NULL keys during both insert and probe.\n        let io = Arc::new(MemoryIO::new());\n        let config = HashTableConfig {\n            initial_buckets: 4,\n            mem_budget: 1024 * 1024,\n            num_keys: 2,\n            collations: vec![CollationSeq::Binary, CollationSeq::Binary],\n            temp_store: crate::TempStore::Default,\n            track_matched: false,\n            partition_count: None,\n        };\n        let mut ht = HashTable::new(config, io);\n\n        // Insert entry with NULL key - should be silently skipped\n        let null_key = vec![Value::Null, Value::from_i64(1)];\n        let _ = ht.insert(null_key.clone(), 100, vec![], None).unwrap();\n\n        // Insert entry with non-NULL keys\n        let valid_key = vec![Value::from_i64(1), Value::from_i64(2)];\n        let _ = ht.insert(valid_key.clone(), 200, vec![], None).unwrap();\n\n        // Insert another entry where second key is NULL\n        let null_key2 = vec![Value::from_i64(1), Value::Null];\n        let _ = ht.insert(null_key2.clone(), 300, vec![], None).unwrap();\n\n        let _ = ht.finalize_build(None);\n\n        // Only one entry should be in the table (the one with valid keys)\n        assert_eq!(ht.num_entries, 1);\n\n        // Probing with NULL key should return None\n        let result = ht.probe(null_key, None);\n        assert!(result.is_none());\n\n        // Probing with valid key should return the entry\n        let result = ht.probe(valid_key, None);\n        assert!(result.is_some());\n        assert_eq!(result.unwrap().rowid, 200);\n\n        // Probing with NULL in second position should also return None\n        let result = ht.probe(null_key2, None);\n        assert!(result.is_none());\n    }\n\n    #[test]\n    fn test_hash_table_payload_with_blobs() {\n        let io = Arc::new(MemoryIO::new());\n        let config = HashTableConfig {\n            initial_buckets: 4,\n            mem_budget: 1024 * 1024,\n            num_keys: 1,\n            collations: vec![CollationSeq::Binary],\n            temp_store: crate::TempStore::Default,\n            track_matched: false,\n            partition_count: None,\n        };\n        let mut ht = HashTable::new(config, io);\n\n        // Insert entry with blob payload\n        let key = vec![Value::from_i64(1)];\n        let blob_data = vec![0xDE, 0xAD, 0xBE, 0xEF];\n        let payload = vec![Value::Blob(blob_data.clone()), Value::from_i64(42)];\n        let _ = ht.insert(key.clone(), 100, payload, None).unwrap();\n\n        let _ = ht.finalize_build(None);\n\n        let result = ht.probe(key, None);\n        assert!(result.is_some());\n        let entry = result.unwrap();\n        assert_eq!(entry.payload_values.len(), 2);\n        assert_eq!(entry.payload_values[0], Value::Blob(blob_data));\n        assert_eq!(entry.payload_values[1], Value::from_i64(42));\n    }\n\n    #[test]\n    fn test_hash_table_payload_duplicate_keys() {\n        let io = Arc::new(MemoryIO::new());\n        let config = HashTableConfig {\n            initial_buckets: 4,\n            mem_budget: 1024 * 1024,\n            num_keys: 1,\n            collations: vec![CollationSeq::Binary],\n            temp_store: crate::TempStore::Default,\n            track_matched: false,\n            partition_count: None,\n        };\n        let mut ht = HashTable::new(config, io);\n\n        // Insert multiple entries with the same key but different payloads\n        let key = vec![Value::from_i64(42)];\n        let _ = ht\n            .insert(\n                key.clone(),\n                100,\n                vec![Value::Text(\"first\".into()), Value::from_i64(1)],\n                None,\n            )\n            .unwrap();\n        let _ = ht\n            .insert(\n                key.clone(),\n                200,\n                vec![Value::Text(\"second\".into()), Value::from_i64(2)],\n                None,\n            )\n            .unwrap();\n        let _ = ht\n            .insert(\n                key.clone(),\n                300,\n                vec![Value::Text(\"third\".into()), Value::from_i64(3)],\n                None,\n            )\n            .unwrap();\n\n        let _ = ht.finalize_build(None);\n\n        // First probe should return first match\n        let result = ht.probe(key, None);\n        assert!(result.is_some());\n        let entry1 = result.unwrap();\n        assert_eq!(entry1.rowid, 100);\n        assert_eq!(entry1.payload_values[0], Value::Text(\"first\".into()));\n        assert_eq!(entry1.payload_values[1], Value::from_i64(1));\n\n        // next_match should return subsequent matches with their payloads\n        let entry2 = ht.next_match().unwrap();\n        assert_eq!(entry2.rowid, 200);\n        assert_eq!(entry2.payload_values[0], Value::Text(\"second\".into()));\n        assert_eq!(entry2.payload_values[1], Value::from_i64(2));\n\n        let entry3 = ht.next_match().unwrap();\n        assert_eq!(entry3.rowid, 300);\n        assert_eq!(entry3.payload_values[0], Value::Text(\"third\".into()));\n        assert_eq!(entry3.payload_values[1], Value::from_i64(3));\n\n        // No more matches\n        assert!(ht.next_match().is_none());\n    }\n\n    #[test]\n    fn test_hash_entry_payload_serialization() {\n        // Test that payload values survive serialization/deserialization\n        let entry = HashEntry::new_with_payload(\n            12345,\n            vec![Value::from_i64(1), Value::Text(\"key\".into())],\n            100,\n            vec![\n                Value::Text(\"payload_text\".into()),\n                Value::from_i64(999),\n                Value::from_f64(std::f64::consts::PI),\n                Value::Null,\n                Value::Blob(vec![1, 2, 3, 4]),\n            ],\n        );\n\n        let mut buf = Vec::new();\n        entry.serialize(&mut buf);\n\n        let (deserialized, bytes_consumed) = HashEntry::deserialize(&buf).unwrap();\n        assert_eq!(bytes_consumed, buf.len());\n\n        // Verify key values\n        assert_eq!(deserialized.hash, entry.hash);\n        assert_eq!(deserialized.rowid, entry.rowid);\n        assert_eq!(deserialized.key_values.len(), 2);\n        assert_eq!(deserialized.key_values[0], Value::from_i64(1));\n        assert_eq!(deserialized.key_values[1], Value::Text(\"key\".into()));\n\n        // Verify payload values\n        assert_eq!(deserialized.payload_values.len(), 5);\n        assert_eq!(\n            deserialized.payload_values[0],\n            Value::Text(\"payload_text\".into())\n        );\n        assert_eq!(deserialized.payload_values[1], Value::from_i64(999));\n        assert_eq!(\n            deserialized.payload_values[2],\n            Value::from_f64(std::f64::consts::PI)\n        );\n        assert_eq!(deserialized.payload_values[3], Value::Null);\n        assert_eq!(\n            deserialized.payload_values[4],\n            Value::Blob(vec![1, 2, 3, 4])\n        );\n    }\n\n    #[test]\n    fn test_hash_entry_empty_payload() {\n        // Test that entries without payload work correctly\n        let entry = HashEntry::new(12345, vec![Value::from_i64(1)], 100);\n\n        assert!(!entry.has_payload());\n        assert!(entry.payload_values.is_empty());\n\n        // Serialization should still work\n        let mut buf = Vec::new();\n        entry.serialize(&mut buf);\n\n        let (deserialized, _) = HashEntry::deserialize(&buf).unwrap();\n        assert!(!deserialized.has_payload());\n        assert!(deserialized.payload_values.is_empty());\n        assert_eq!(deserialized.rowid, 100);\n    }\n\n    #[test]\n    fn test_hash_entry_size_includes_payload() {\n        let entry_no_payload = HashEntry::new(12345, vec![Value::from_i64(1)], 100);\n\n        let entry_with_payload = HashEntry::new_with_payload(\n            12345,\n            vec![Value::from_i64(1)],\n            100,\n            vec![\n                Value::Text(\"a]long payload string\".into()),\n                Value::from_i64(42),\n            ],\n        );\n\n        // Entry with payload should have larger size\n        assert!(entry_with_payload.size_bytes() > entry_no_payload.size_bytes());\n    }\n\n    // ── Grace hash join tests ──────────────────────────────────────\n\n    /// Helper: build a spilled hash table with given keys and payloads\n    fn make_spilled_ht_with_payload(\n        io: Arc<dyn IO>,\n        build_keys: &[(i64, Vec<Value>)], // (rowid, key_values)\n        payload: bool,\n    ) -> HashTable {\n        let config = HashTableConfig {\n            initial_buckets: 4,\n            mem_budget: 1024, // tiny, forces spill\n            num_keys: 1,\n            collations: vec![CollationSeq::Binary],\n            temp_store: crate::TempStore::Default,\n            track_matched: false,\n            ..Default::default()\n        };\n        let mut ht = HashTable::new(config, io);\n        for (rowid, keys) in build_keys {\n            let payload_values = if payload {\n                vec![Value::Text(format!(\"payload_{rowid}\").into())]\n            } else {\n                vec![]\n            };\n            let _ = ht.insert(keys.clone(), *rowid, payload_values, None);\n        }\n        let _ = ht.finalize_build(None).unwrap();\n        ht\n    }\n\n    /// Helper: run grace processing with the new fine-grained API.\n    /// Returns (build_rowid, probe_rowid) pairs for all matches found.\n    fn run_grace_processing(ht: &mut HashTable) -> Vec<(i64, i64)> {\n        let _ = ht.finalize_probe_spill(None).unwrap();\n        let mut matches = Vec::new();\n\n        if !ht.grace_begin() {\n            return matches;\n        }\n\n        // Partition loop\n        loop {\n            match ht.grace_load_current_partition(None).unwrap() {\n                IOResult::Done(true) => {}\n                IOResult::Done(false) => break,\n                _ => panic!(\"unexpected IO\"),\n            }\n\n            // Probe entry loop\n            loop {\n                let entry = match ht.grace_next_probe_entry().unwrap() {\n                    IOResult::Done(entry) => entry,\n                    IOResult::IO(_) => panic!(\"unexpected IO\"),\n                };\n                let Some(entry) = entry else {\n                    break;\n                };\n\n                // Use probe_partition + next_match to find build matches\n                let key_values = entry.key_values;\n                let probe_rowid = entry.probe_rowid;\n                let partition_idx = ht.partition_for_keys(&key_values);\n\n                if ht\n                    .probe_partition(partition_idx, &key_values, None)\n                    .is_some()\n                {\n                    // First match from probe_partition\n                    let build_entry = ht.probe_partition(partition_idx, &key_values, None);\n                    // Re-probe to get entry again\n                    if let Some(build_entry) = build_entry {\n                        matches.push((build_entry.rowid, probe_rowid));\n                    }\n                    // Get additional matches via next_match\n                    while let Some(build_entry) = ht.next_match() {\n                        matches.push((build_entry.rowid, probe_rowid));\n                    }\n                }\n            }\n\n            if !ht.grace_advance_partition() {\n                break;\n            }\n        }\n        matches\n    }\n\n    #[test]\n    fn test_grace_basic() {\n        let io = Arc::new(MemoryIO::new());\n        let build_keys: Vec<(i64, Vec<Value>)> =\n            (0..200).map(|i| (i, vec![Value::from_i64(i)])).collect();\n        let mut ht = make_spilled_ht_with_payload(io, &build_keys, true);\n        assert!(ht.has_spilled(), \"should have spilled\");\n\n        // Buffer probe rows for keys that map to spilled partitions\n        let mut buffered = 0;\n        for i in 0..200 {\n            let key = vec![Value::from_i64(i)];\n            let partition_idx = ht.partition_for_keys(&key);\n            if !ht.is_partition_loaded(partition_idx) {\n                let _ = ht.buffer_probe_row(key, i + 1000, None).unwrap();\n                buffered += 1;\n            }\n        }\n        assert!(buffered > 0, \"should have buffered some probe rows\");\n\n        let matches = run_grace_processing(&mut ht);\n\n        // Every buffered probe row should have found a match\n        assert_eq!(\n            matches.len(),\n            buffered,\n            \"each buffered probe row should match exactly one build row\"\n        );\n        // Verify correctness: build_rowid should equal probe_rowid - 1000\n        for (build_rowid, probe_rowid) in &matches {\n            assert_eq!(\n                *build_rowid,\n                probe_rowid - 1000,\n                \"build_rowid should match probe key\"\n            );\n        }\n    }\n\n    #[test]\n    fn test_grace_no_spill_noop() {\n        let io = Arc::new(MemoryIO::new());\n        // Use large budget so nothing spills\n        let config = HashTableConfig {\n            initial_buckets: 4,\n            mem_budget: 1024 * 1024,\n            num_keys: 1,\n            collations: vec![CollationSeq::Binary],\n            temp_store: crate::TempStore::Default,\n            track_matched: false,\n            ..Default::default()\n        };\n        let mut ht = HashTable::new(config, io);\n        for i in 0..10 {\n            let _ = ht.insert(vec![Value::from_i64(i)], i, vec![], None);\n        }\n        let _ = ht.finalize_build(None).unwrap();\n        assert!(!ht.has_spilled(), \"should NOT have spilled\");\n\n        // grace_begin should return false since nothing was spilled\n        assert!(\n            !ht.grace_begin(),\n            \"grace_begin should return false when nothing spilled\"\n        );\n    }\n\n    #[test]\n    fn test_grace_duplicate_keys() {\n        let io = Arc::new(MemoryIO::new());\n        // Insert multiple build rows with same key\n        let mut build_keys: Vec<(i64, Vec<Value>)> = Vec::new();\n        for i in 0..100 {\n            // 3 build rows per key value\n            build_keys.push((i * 3, vec![Value::from_i64(i)]));\n            build_keys.push((i * 3 + 1, vec![Value::from_i64(i)]));\n            build_keys.push((i * 3 + 2, vec![Value::from_i64(i)]));\n        }\n        let mut ht = make_spilled_ht_with_payload(io, &build_keys, false);\n        assert!(ht.has_spilled());\n\n        // Buffer probe rows\n        let mut buffered_keys = Vec::new();\n        for i in 0..100 {\n            let key = vec![Value::from_i64(i)];\n            let partition_idx = ht.partition_for_keys(&key);\n            if !ht.is_partition_loaded(partition_idx) {\n                let _ = ht.buffer_probe_row(key, i + 500, None).unwrap();\n                buffered_keys.push(i);\n            }\n        }\n\n        let matches = run_grace_processing(&mut ht);\n\n        // Each buffered probe key should match 3 build rows\n        assert_eq!(\n            matches.len(),\n            buffered_keys.len() * 3,\n            \"each probe key should find 3 matches\"\n        );\n    }\n\n    #[test]\n    fn test_grace_empty_partitions() {\n        let io = Arc::new(MemoryIO::new());\n        // Build with keys 0..100, probe with keys 200..300 (no overlap)\n        let build_keys: Vec<(i64, Vec<Value>)> =\n            (0..200).map(|i| (i, vec![Value::from_i64(i)])).collect();\n        let mut ht = make_spilled_ht_with_payload(io, &build_keys, false);\n        assert!(ht.has_spilled());\n\n        // Buffer probe rows with non-matching keys\n        for i in 1000..1050 {\n            let key = vec![Value::from_i64(i)];\n            let partition_idx = ht.partition_for_keys(&key);\n            if !ht.is_partition_loaded(partition_idx) {\n                let _ = ht.buffer_probe_row(key, i, None).unwrap();\n            }\n        }\n\n        let matches = run_grace_processing(&mut ht);\n        assert_eq!(\n            matches.len(),\n            0,\n            \"non-matching keys should produce no matches\"\n        );\n    }\n\n    #[test]\n    fn test_grace_null_keys() {\n        let io = Arc::new(MemoryIO::new());\n        let build_keys: Vec<(i64, Vec<Value>)> =\n            (0..200).map(|i| (i, vec![Value::from_i64(i)])).collect();\n        let mut ht = make_spilled_ht_with_payload(io, &build_keys, false);\n        assert!(ht.has_spilled());\n\n        // Buffer a probe row with NULL key - should be skipped\n        let null_key = vec![Value::Null];\n        // NULL keys can't match, so we just verify no crash\n        let partition_idx = ht.partition_for_keys(&[Value::from_i64(0)]);\n        if !ht.is_partition_loaded(partition_idx) {\n            // Buffer with a valid key to ensure grace processing runs\n            let _ = ht\n                .buffer_probe_row(vec![Value::from_i64(0)], 999, None)\n                .unwrap();\n        }\n        // Buffer a null key row to the same partition\n        let _ = ht.buffer_probe_row(null_key, 888, None).unwrap();\n\n        let _ = ht.finalize_probe_spill(None).unwrap();\n        // Should not crash; NULL key entries should return from grace_next_probe_entry\n        assert!(ht.grace_begin(), \"should have partitions to process\");\n        loop {\n            match ht.grace_load_current_partition(None).unwrap() {\n                IOResult::Done(true) => {}\n                IOResult::Done(false) => break,\n                _ => panic!(\"unexpected IO\"),\n            }\n            loop {\n                let entry = match ht.grace_next_probe_entry().unwrap() {\n                    IOResult::Done(entry) => entry,\n                    IOResult::IO(_) => panic!(\"unexpected IO\"),\n                };\n                if entry.is_none() {\n                    break;\n                }\n                // NULL key probe entries are still returned; the VDBE's HashProbe\n                // handles NULL skip. Just verify no crash.\n            }\n            if !ht.grace_advance_partition() {\n                break;\n            }\n        }\n    }\n\n    #[test]\n    fn test_grace_with_payload() {\n        let io = Arc::new(MemoryIO::new());\n        let build_keys: Vec<(i64, Vec<Value>)> =\n            (0..200).map(|i| (i, vec![Value::from_i64(i)])).collect();\n        let mut ht = make_spilled_ht_with_payload(io, &build_keys, true);\n        assert!(ht.has_spilled());\n\n        // Buffer some probe rows\n        let mut buffered = 0;\n        for i in 0..200 {\n            let key = vec![Value::from_i64(i)];\n            let partition_idx = ht.partition_for_keys(&key);\n            if !ht.is_partition_loaded(partition_idx) {\n                let _ = ht.buffer_probe_row(key, i + 1000, None).unwrap();\n                buffered += 1;\n            }\n        }\n\n        let _ = ht.finalize_probe_spill(None).unwrap();\n        assert!(ht.grace_begin(), \"should have partitions to process\");\n\n        let mut match_count = 0;\n        loop {\n            match ht.grace_load_current_partition(None).unwrap() {\n                IOResult::Done(true) => {}\n                IOResult::Done(false) => break,\n                _ => panic!(\"unexpected IO\"),\n            }\n            loop {\n                let entry = match ht.grace_next_probe_entry().unwrap() {\n                    IOResult::Done(entry) => entry,\n                    IOResult::IO(_) => panic!(\"unexpected IO\"),\n                };\n                let Some(entry) = entry else {\n                    break;\n                };\n                let key_values = entry.key_values;\n                let partition_idx = ht.partition_for_keys(&key_values);\n                if let Some(build_entry) = ht.probe_partition(partition_idx, &key_values, None) {\n                    // Check payload was correctly round-tripped\n                    let expected_payload = format!(\"payload_{}\", build_entry.rowid);\n                    assert_eq!(build_entry.payload_values.len(), 1);\n                    match &build_entry.payload_values[0] {\n                        Value::Text(t) => assert_eq!(t.as_str(), expected_payload.as_str()),\n                        other => panic!(\"expected text payload, got {other:?}\"),\n                    }\n                    match_count += 1;\n                }\n            }\n            if !ht.grace_advance_partition() {\n                break;\n            }\n        }\n        assert_eq!(match_count, buffered);\n    }\n\n    #[test]\n    fn test_grace_unmatched_scan_uses_current_partition() {\n        let io = Arc::new(MemoryIO::new());\n        let config = HashTableConfig {\n            initial_buckets: 4,\n            mem_budget: 4096,\n            num_keys: 1,\n            collations: vec![CollationSeq::Binary],\n            temp_store: crate::TempStore::Default,\n            track_matched: true,\n            partition_count: Some(4),\n        };\n        let mut ht = HashTable::new(config, io);\n\n        for i in 0..400 {\n            let key = vec![Value::from_i64(i)];\n            let _ = ht.insert(key, i, vec![], None);\n        }\n        let _ = ht.finalize_build(None).unwrap();\n        assert!(ht.has_spilled(), \"should have spilled\");\n\n        let mut keys_by_partition = std::collections::BTreeMap::<usize, Vec<i64>>::new();\n        for i in 0..400 {\n            let partition_idx = ht.partition_for_keys(&[Value::from_i64(i)]);\n            keys_by_partition.entry(partition_idx).or_default().push(i);\n        }\n\n        let partitions_to_process: Vec<usize> = ht\n            .spill_state\n            .as_ref()\n            .expect(\"spill state\")\n            .partitions\n            .iter()\n            .filter(|partition| !partition.chunks.is_empty())\n            .map(|partition| partition.partition_idx)\n            .collect();\n        assert!(\n            partitions_to_process.len() >= 2,\n            \"test requires at least two spilled partitions\"\n        );\n\n        let first_partition = partitions_to_process[0];\n        let second_partition = partitions_to_process[1];\n        let first_keys = keys_by_partition\n            .get(&first_partition)\n            .cloned()\n            .expect(\"first partition keys\");\n        let second_keys = keys_by_partition\n            .get(&second_partition)\n            .cloned()\n            .expect(\"second partition keys\");\n\n        for key in &first_keys {\n            let _ = ht\n                .buffer_probe_row(vec![Value::from_i64(*key)], key + 10_000, None)\n                .unwrap();\n        }\n        let _ = ht.finalize_probe_spill(None).unwrap();\n        assert!(ht.grace_begin(), \"should enter grace\");\n\n        match ht.grace_load_current_partition(None).unwrap() {\n            IOResult::Done(true) => {}\n            other => panic!(\"unexpected grace load result: {other:?}\"),\n        }\n        assert_eq!(\n            ht.grace_state\n                .as_ref()\n                .expect(\"grace state\")\n                .current_partition_idx(),\n            Some(first_partition)\n        );\n\n        loop {\n            let entry = match ht.grace_next_probe_entry().unwrap() {\n                IOResult::Done(entry) => entry,\n                IOResult::IO(_) => panic!(\"unexpected IO\"),\n            };\n            let Some(entry) = entry else {\n                break;\n            };\n            let partition_idx = ht.partition_for_keys(&entry.key_values);\n            if ht\n                .probe_partition(partition_idx, &entry.key_values, None)\n                .is_some()\n            {\n                ht.mark_current_matched();\n                while ht.next_match().is_some() {\n                    ht.mark_current_matched();\n                }\n            }\n        }\n\n        ht.begin_unmatched_scan();\n        assert!(\n            ht.next_unmatched().is_none(),\n            \"all rows in the first partition were matched\"\n        );\n\n        assert!(\n            ht.grace_advance_partition(),\n            \"should have another partition\"\n        );\n        match ht.grace_load_current_partition(None).unwrap() {\n            IOResult::Done(true) => {}\n            other => panic!(\"unexpected grace load result: {other:?}\"),\n        }\n        assert_eq!(\n            ht.grace_state\n                .as_ref()\n                .expect(\"grace state\")\n                .current_partition_idx(),\n            Some(second_partition)\n        );\n\n        match ht.load_spilled_partition(first_partition, None).unwrap() {\n            IOResult::Done(()) => {}\n            other => panic!(\"unexpected spill load result: {other:?}\"),\n        }\n\n        ht.begin_unmatched_scan();\n        assert_eq!(\n            ht.unmatched_scan_current_partition(),\n            Some(second_partition),\n            \"grace unmatched scan must target the active grace partition\"\n        );\n        match ht.load_spilled_partition(second_partition, None).unwrap() {\n            IOResult::Done(()) => {}\n            other => panic!(\"unexpected spill load result: {other:?}\"),\n        }\n\n        let mut unmatched_rowids = Vec::new();\n        while let Some(entry) = ht.next_unmatched() {\n            unmatched_rowids.push(entry.rowid);\n        }\n        unmatched_rowids.sort_unstable();\n\n        let mut expected = second_keys;\n        expected.sort_unstable();\n        assert_eq!(unmatched_rowids, expected);\n    }\n\n    #[test]\n    fn test_unmatched_scan_preserves_in_memory_partitions_before_grace() {\n        let io = Arc::new(MemoryIO::new());\n        let config = HashTableConfig {\n            initial_buckets: 4,\n            mem_budget: 1024,\n            num_keys: 1,\n            collations: vec![CollationSeq::Binary],\n            temp_store: crate::TempStore::Default,\n            track_matched: true,\n            partition_count: Some(16),\n        };\n        let mut ht = HashTable::new(config, io);\n\n        let mut next_rowid = 0i64;\n        while !ht.has_spilled() {\n            let _ = ht\n                .insert(vec![Value::from_i64(next_rowid)], next_rowid, vec![], None)\n                .unwrap();\n            next_rowid += 1;\n        }\n\n        let partition_keys: std::collections::BTreeMap<usize, Vec<i64>> = (0..4096)\n            .map(|i| (ht.partition_for_keys(&[Value::from_i64(i)]), i))\n            .fold(\n                std::collections::BTreeMap::new(),\n                |mut acc, (partition, key)| {\n                    acc.entry(partition).or_default().push(key);\n                    acc\n                },\n            );\n\n        let hot_partition = ht\n            .spill_state\n            .as_ref()\n            .expect(\"spill state\")\n            .partitions\n            .first()\n            .map(|partition| partition.partition_idx)\n            .expect(\"expected at least one spilled partition\");\n        let cold_partition = partition_keys\n            .keys()\n            .copied()\n            .find(|partition_idx| {\n                ht.spill_state\n                    .as_ref()\n                    .expect(\"spill state\")\n                    .find_partition(*partition_idx)\n                    .is_none()\n            })\n            .expect(\"expected at least one partition without spill chunks yet\");\n        let hot_key = partition_keys\n            .get(&hot_partition)\n            .and_then(|keys| keys.first())\n            .copied()\n            .expect(\"hot partition key\");\n        let cold_key = partition_keys\n            .get(&cold_partition)\n            .and_then(|keys| keys.first())\n            .copied()\n            .expect(\"cold partition key\");\n\n        for _ in 0..160 {\n            let _ = ht\n                .insert(vec![Value::from_i64(hot_key)], next_rowid, vec![], None)\n                .unwrap();\n            next_rowid += 1;\n        }\n        for _ in 0..6 {\n            let _ = ht\n                .insert(vec![Value::from_i64(cold_key)], next_rowid, vec![], None)\n                .unwrap();\n            next_rowid += 1;\n        }\n\n        let _ = ht.finalize_build(None).unwrap();\n        assert!(ht.has_spilled(), \"should have spilled\");\n\n        let (spilled_partition, mut expected_unmatched) = {\n            let spill_state = ht.spill_state.as_ref().expect(\"spill state\");\n            let spilled_partition = spill_state\n                .partitions\n                .iter()\n                .find(|partition| !partition.chunks.is_empty())\n                .map(|partition| partition.partition_idx)\n                .expect(\"expected at least one spilled partition\");\n            let expected_unmatched: Vec<i64> = spill_state\n                .partitions\n                .iter()\n                .filter(|partition| partition.chunks.is_empty())\n                .flat_map(|partition| {\n                    partition\n                        .buckets\n                        .iter()\n                        .flat_map(|bucket| bucket.entries.iter().map(|entry| entry.rowid))\n                })\n                .collect();\n            (spilled_partition, expected_unmatched)\n        };\n        assert!(\n            !expected_unmatched.is_empty(),\n            \"expected at least one resident in-memory partition\"\n        );\n\n        let probe_key = (0..400)\n            .map(|i| vec![Value::from_i64(i)])\n            .find(|key| ht.partition_for_keys(key) == spilled_partition)\n            .expect(\"spilled partition should have at least one key\");\n        let _ = ht.buffer_probe_row(probe_key, 10_000, None).unwrap();\n        assert!(\n            ht.has_grace_partitions(),\n            \"probe buffering should enable grace\"\n        );\n\n        ht.begin_unmatched_scan();\n        let mut actual_unmatched = Vec::new();\n        while let Some(entry) = ht.next_unmatched() {\n            actual_unmatched.push(entry.rowid);\n        }\n\n        expected_unmatched.sort_unstable();\n        actual_unmatched.sort_unstable();\n        assert_eq!(actual_unmatched, expected_unmatched);\n    }\n}\n"
  },
  {
    "path": "core/vdbe/insn.rs",
    "content": "use std::{\n    num::{NonZero, NonZeroUsize},\n    sync::Arc,\n};\n\n/// Convert a usize to u16 for instruction fields (registers, counts).\n/// Panics if the value exceeds u16::MAX.\n#[inline]\npub fn to_u16(v: usize) -> u16 {\n    v.try_into().expect(\"value exceeds u16::MAX\")\n}\n\nuse super::{execute, AggFunc, BranchOffset, CursorID, FuncCtx, InsnFunction, PageIdx};\nuse crate::{\n    schema::{BTreeTable, CheckConstraint, Column, Index},\n    storage::{pager::CreateBTreeFlags, wal::CheckpointMode},\n    translate::{collate::CollationSeq, emitter::TransactionMode},\n    types::KeyInfo,\n    vdbe::affinity::Affinity,\n    PreparedProgram, Value,\n};\nuse strum::EnumCount;\nuse strum_macros::{EnumDiscriminants, FromRepr, VariantArray};\nuse turso_macros::Description;\nuse turso_parser::ast::{ResolveType, SortOrder};\n\n/// Known custom type comparator functions for sorting and MIN/MAX aggregates.\n/// These replace heap-allocated String names with a compact enum.\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub enum SortComparatorType {\n    NumericLt,\n    StringReverse,\n    TestUintLt,\n    ArrayLt,\n}\n\n/// Flags provided to comparison instructions (e.g. Eq, Ne) which determine behavior related to NULL values.\n#[derive(Clone, Copy, Debug, Default)]\npub struct CmpInsFlags(usize);\n\nimpl CmpInsFlags {\n    const NULL_EQ: usize = 0x80;\n    const JUMP_IF_NULL: usize = 0x10;\n    const AFFINITY_MASK: usize = 0x47;\n    const ARRAY_CMP: usize = 0x100;\n\n    fn has(&self, flag: usize) -> bool {\n        (self.0 & flag) != 0\n    }\n\n    pub fn null_eq(mut self) -> Self {\n        self.0 |= CmpInsFlags::NULL_EQ;\n        self\n    }\n\n    pub fn jump_if_null(mut self) -> Self {\n        self.0 |= CmpInsFlags::JUMP_IF_NULL;\n        self\n    }\n\n    pub fn has_jump_if_null(&self) -> bool {\n        self.has(CmpInsFlags::JUMP_IF_NULL)\n    }\n\n    pub fn has_nulleq(&self) -> bool {\n        self.has(CmpInsFlags::NULL_EQ)\n    }\n\n    pub fn with_affinity(mut self, affinity: Affinity) -> Self {\n        let aff_code = affinity.as_char_code() as usize;\n        self.0 = (self.0 & !Self::AFFINITY_MASK) | aff_code;\n        self\n    }\n\n    pub fn get_affinity(&self) -> Affinity {\n        let aff_code = (self.0 & Self::AFFINITY_MASK) as u8;\n        Affinity::from_char_code(aff_code)\n    }\n\n    pub fn array_cmp(mut self) -> Self {\n        self.0 |= Self::ARRAY_CMP;\n        self\n    }\n\n    pub fn has_array_cmp(&self) -> bool {\n        self.has(Self::ARRAY_CMP)\n    }\n}\n\n#[derive(Clone, Copy, Debug, Default)]\npub struct IdxInsertFlags(pub u8);\nimpl IdxInsertFlags {\n    pub const APPEND: u8 = 0x01; // Hint: insert likely at the end\n    pub const NCHANGE: u8 = 0x02; // Increment the change counter\n    pub const USE_SEEK: u8 = 0x04; // Skip seek if last one was same key\n    pub const NO_OP_DUPLICATE: u8 = 0x08; // Do not error on duplicate key\n    pub fn new() -> Self {\n        IdxInsertFlags(0)\n    }\n    pub fn has(&self, flag: u8) -> bool {\n        (self.0 & flag) != 0\n    }\n    pub fn append(mut self, append: bool) -> Self {\n        if append {\n            self.0 |= IdxInsertFlags::APPEND;\n        } else {\n            self.0 &= !IdxInsertFlags::APPEND;\n        }\n        self\n    }\n    pub fn use_seek(mut self, seek: bool) -> Self {\n        if seek {\n            self.0 |= IdxInsertFlags::USE_SEEK;\n        } else {\n            self.0 &= !IdxInsertFlags::USE_SEEK;\n        }\n        self\n    }\n    pub fn nchange(mut self, change: bool) -> Self {\n        if change {\n            self.0 |= IdxInsertFlags::NCHANGE;\n        } else {\n            self.0 &= !IdxInsertFlags::NCHANGE;\n        }\n        self\n    }\n    /// If this is set, we will not error on duplicate key.\n    /// This is a bit of a hack we use to make ephemeral indexes for UNION work --\n    /// instead we should allow overwriting index interior cells, which we currently don't;\n    /// this should (and will) be fixed in a future PR.\n    pub fn no_op_duplicate(mut self) -> Self {\n        self.0 |= IdxInsertFlags::NO_OP_DUPLICATE;\n        self\n    }\n}\n\n#[derive(Clone, Copy, Debug, Default)]\npub struct InsertFlags(pub u8);\n\nimpl InsertFlags {\n    pub const UPDATE_ROWID_CHANGE: u8 = 0x01; // Flag indicating this is part of an UPDATE statement where the row's rowid is changed\n    pub const REQUIRE_SEEK: u8 = 0x02; // Flag indicating that a seek is required to insert the row\n    pub const EPHEMERAL_TABLE_INSERT: u8 = 0x04; // Flag indicating that this is an insert into an ephemeral table\n    pub const SKIP_LAST_ROWID: u8 = 0x08; // Flag indicating that last_insert_rowid() must not be updated\n\n    pub fn new() -> Self {\n        InsertFlags(0)\n    }\n\n    pub fn has(&self, flag: u8) -> bool {\n        (self.0 & flag) != 0\n    }\n\n    pub fn require_seek(mut self) -> Self {\n        self.0 |= InsertFlags::REQUIRE_SEEK;\n        self\n    }\n\n    pub fn update_rowid_change(mut self) -> Self {\n        self.0 |= InsertFlags::UPDATE_ROWID_CHANGE;\n        self\n    }\n\n    pub fn is_ephemeral_table_insert(mut self) -> Self {\n        self.0 |= InsertFlags::EPHEMERAL_TABLE_INSERT;\n        self\n    }\n\n    pub fn skip_last_rowid(mut self) -> Self {\n        self.0 |= InsertFlags::SKIP_LAST_ROWID;\n        self\n    }\n}\n\n#[derive(Clone, Copy, Debug)]\npub enum RegisterOrLiteral<T: Copy + std::fmt::Display> {\n    Register(usize),\n    Literal(T),\n}\n\n#[derive(Debug, Clone, Copy)]\npub enum SavepointOp {\n    Begin,\n    Release,\n    RollbackTo,\n}\n\nimpl From<PageIdx> for RegisterOrLiteral<PageIdx> {\n    fn from(value: PageIdx) -> Self {\n        RegisterOrLiteral::Literal(value)\n    }\n}\n\nimpl<T: Copy + std::fmt::Display> std::fmt::Display for RegisterOrLiteral<T> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Self::Literal(lit) => lit.fmt(f),\n            Self::Register(reg) => reg.fmt(f),\n        }\n    }\n}\n\n/// Data for HashBuild instruction (boxed to keep Insn small).\n#[derive(Debug, Clone)]\npub struct HashBuildData {\n    pub cursor_id: CursorID,\n    pub key_start_reg: usize,\n    pub num_keys: usize,\n    pub hash_table_id: usize,\n    pub mem_budget: usize,\n    pub collations: Vec<CollationSeq>,\n    /// Starting register for payload columns to store in the hash entry.\n    /// When Some: payload_start_reg..payload_start_reg+num_payload-1 contain values to cache.\n    pub payload_start_reg: Option<usize>,\n    /// Number of payload columns to read\n    pub num_payload: usize,\n    /// Whether to track which entries are matched (for FULL OUTER JOIN).\n    pub track_matched: bool,\n}\n\n/// Data for HashDistinct instruction (boxed to keep Insn small).\n#[derive(Debug, Clone)]\npub struct HashDistinctData {\n    pub hash_table_id: usize,\n    pub key_start_reg: usize,\n    pub num_keys: usize,\n    pub collations: Vec<CollationSeq>,\n    pub target_pc: BranchOffset,\n}\n\n// There are currently 190 opcodes in sqlite\n#[repr(u8)]\n#[derive(Description, Debug, Clone, EnumDiscriminants)]\n#[strum_discriminants(vis(pub(crate)))]\n#[strum_discriminants(derive(VariantArray, EnumCount, FromRepr))]\n#[strum_discriminants(name(InsnVariants))]\npub enum Insn {\n    /// Initialize the program state and jump to the given PC.\n    Init {\n        target_pc: BranchOffset,\n    },\n    /// Write a NULL into register dest. If dest_end is Some, then also write NULL into register dest_end and every register in between dest and dest_end. If dest_end is not set, then only register dest is set to NULL.\n    Null {\n        dest: usize,\n        dest_end: Option<usize>,\n    },\n    /// Mark the beginning of a subroutine tha can be entered in-line. This opcode is identical to Null\n    /// it has a different name only to make the byte code easier to read and verify\n    BeginSubrtn {\n        dest: usize,\n        dest_end: Option<usize>,\n    },\n    /// Move the cursor P1 to a null row. Any Column operations that occur while the cursor is on the null row will always write a NULL.\n    NullRow {\n        cursor_id: CursorID,\n    },\n    /// Add two registers and store the result in a third register.\n    Add {\n        lhs: usize,\n        rhs: usize,\n        dest: usize,\n    },\n    /// Subtract rhs from lhs and store in dest\n    Subtract {\n        lhs: usize,\n        rhs: usize,\n        dest: usize,\n    },\n    /// Multiply two registers and store the result in a third register.\n    Multiply {\n        lhs: usize,\n        rhs: usize,\n        dest: usize,\n    },\n    /// Updates the value of register dest_reg to the maximum of its current\n    /// value and the value in src_reg.\n    ///\n    ///    - dest_reg = max(int(dest_reg), int(src_reg))\n    ///\n    /// Both registers are converted to integers before the comparison.\n    MemMax {\n        dest_reg: usize, // P1\n        src_reg: usize,  // P2\n    },\n    /// Divide lhs by rhs and store the result in a third register.\n    Divide {\n        lhs: usize,\n        rhs: usize,\n        dest: usize,\n    },\n    /// Compare two vectors of registers in reg(P1)..reg(P1+P3-1) (call this vector \"A\") and in reg(P2)..reg(P2+P3-1) (\"B\"). Save the result of the comparison for use by the next Jump instruct.\n    Compare {\n        start_reg_a: usize,\n        start_reg_b: usize,\n        count: usize,\n        key_info: Vec<KeyInfo>,\n    },\n    /// Place the result of rhs bitwise AND lhs in third register.\n    BitAnd {\n        lhs: usize,\n        rhs: usize,\n        dest: usize,\n    },\n    /// Place the result of rhs bitwise OR lhs in third register.\n    BitOr {\n        lhs: usize,\n        rhs: usize,\n        dest: usize,\n    },\n    /// Place the result of bitwise NOT register P1 in dest register.\n    BitNot {\n        reg: usize,\n        dest: usize,\n    },\n    /// Checkpoint the database (applying wal file content to database file).\n    Checkpoint {\n        database: usize,                 // checkpoint database P1\n        checkpoint_mode: CheckpointMode, // P2 checkpoint mode\n        dest: usize,                     // P3 checkpoint result\n    },\n    /// Divide lhs by rhs and place the remainder in dest register.\n    Remainder {\n        lhs: usize,\n        rhs: usize,\n        dest: usize,\n    },\n    /// Jump to the instruction at address P1, P2, or P3 depending on whether in the most recent Compare instruction the P1 vector was less than, equal to, or greater than the P2 vector, respectively.\n    Jump {\n        target_pc_lt: BranchOffset,\n        target_pc_eq: BranchOffset,\n        target_pc_gt: BranchOffset,\n    },\n    /// Move the P3 values in register P1..P1+P3-1 over into registers P2..P2+P3-1. Registers P1..P1+P3-1 are left holding a NULL. It is an error for register ranges P1..P1+P3-1 and P2..P2+P3-1 to overlap. It is an error for P3 to be less than 1.\n    Move {\n        source_reg: usize,\n        dest_reg: usize,\n        count: usize,\n    },\n    /// If the given register is a positive integer, decrement it by decrement_by and jump to the given PC.\n    IfPos {\n        reg: usize,\n        target_pc: BranchOffset,\n        decrement_by: usize,\n    },\n    /// If the given register is not NULL, jump to the given PC.\n    NotNull {\n        reg: usize,\n        target_pc: BranchOffset,\n    },\n    /// Compare two registers and jump to the given PC if they are equal.\n    Eq {\n        lhs: usize,\n        rhs: usize,\n        target_pc: BranchOffset,\n        /// CmpInsFlags are nulleq (null = null) or jump_if_null.\n        ///\n        /// jump_if_null jumps if either of the operands is null. Used for \"jump when false\" logic.\n        /// Eg. \"SELECT * FROM users WHERE id = NULL\" becomes:\n        /// <JUMP TO NEXT ROW IF id != NULL>\n        /// Without the jump_if_null flag it would not jump because the logical comparison \"id != NULL\" is never true.\n        /// This flag indicates that if either is null we should still jump.\n        flags: CmpInsFlags,\n        collation: Option<CollationSeq>,\n    },\n    /// Compute a hash on num_keys registers starting with r[key_reg]. Check to see if that hash\n    /// is found in the bloom filter associated with the cursor/hash_table. If it is not present\n    /// then jump to target_pc. Otherwise fall through.\n    /// False negatives are harmless. It is always safe to fall through, even if the value is\n    /// in the bloom filter. A false negative causes more CPU cycles to be used, but it should\n    /// still yield the correct answer. However, an incorrect answer may well arise from a\n    /// false positive - if the jump is taken when it should fall through.\n    Filter {\n        cursor_id: CursorID,\n        /// Jump target if bloom filter says \"definitely not present\"\n        target_pc: BranchOffset,\n        /// Start register containing the key(s) to check\n        key_reg: usize,\n        /// Number of key registers to hash together\n        num_keys: usize,\n    },\n    /// Compute a hash on num_keys registers starting with r[key_reg] and add that hash to\n    /// the bloom filter associated with the cursor/hash_table.\n    FilterAdd {\n        cursor_id: CursorID,\n        key_reg: usize,\n        num_keys: usize,\n    },\n    /// Compare two registers and jump to the given PC if they are not equal.\n    Ne {\n        lhs: usize,\n        rhs: usize,\n        target_pc: BranchOffset,\n        /// CmpInsFlags are nulleq (null = null) or jump_if_null.\n        ///\n        /// jump_if_null jumps if either of the operands is null. Used for \"jump when false\" logic.\n        flags: CmpInsFlags,\n        collation: Option<CollationSeq>,\n    },\n    /// Compare two registers and jump to the given PC if the left-hand side is less than the right-hand side.\n    Lt {\n        lhs: usize,\n        rhs: usize,\n        target_pc: BranchOffset,\n        /// jump_if_null: Jump if either of the operands is null. Used for \"jump when false\" logic.\n        flags: CmpInsFlags,\n        collation: Option<CollationSeq>,\n    },\n    // Compare two registers and jump to the given PC if the left-hand side is less than or equal to the right-hand side.\n    Le {\n        lhs: usize,\n        rhs: usize,\n        target_pc: BranchOffset,\n        /// jump_if_null: Jump if either of the operands is null. Used for \"jump when false\" logic.\n        flags: CmpInsFlags,\n        collation: Option<CollationSeq>,\n    },\n    /// Compare two registers and jump to the given PC if the left-hand side is greater than the right-hand side.\n    Gt {\n        lhs: usize,\n        rhs: usize,\n        target_pc: BranchOffset,\n        /// jump_if_null: Jump if either of the operands is null. Used for \"jump when false\" logic.\n        flags: CmpInsFlags,\n        collation: Option<CollationSeq>,\n    },\n    /// Compare two registers and jump to the given PC if the left-hand side is greater than or equal to the right-hand side.\n    Ge {\n        lhs: usize,\n        rhs: usize,\n        target_pc: BranchOffset,\n        /// jump_if_null: Jump if either of the operands is null. Used for \"jump when false\" logic.\n        flags: CmpInsFlags,\n        collation: Option<CollationSeq>,\n    },\n    /// Jump to target_pc if r\\[reg\\] != 0 or (r\\[reg\\] == NULL && r\\[jump_if_null\\] != 0)\n    If {\n        reg: usize,              // P1\n        target_pc: BranchOffset, // P2\n        /// P3. If r\\[reg\\] is null, jump iff r\\[jump_if_null\\] != 0\n        jump_if_null: bool,\n    },\n    /// Jump to target_pc if r\\[reg\\] != 0 or (r\\[reg\\] == NULL && r\\[jump_if_null\\] != 0)\n    IfNot {\n        reg: usize,              // P1\n        target_pc: BranchOffset, // P2\n        /// P3. If r\\[reg\\] is null, jump iff r\\[jump_if_null\\] != 0\n        jump_if_null: bool,\n    },\n    /// Open a cursor for reading.\n    OpenRead {\n        cursor_id: CursorID,\n        root_page: PageIdx,\n        db: usize,\n    },\n\n    /// Open a cursor for a virtual table.\n    VOpen {\n        cursor_id: CursorID,\n    },\n\n    /// Create a new virtual table.\n    VCreate {\n        module_name: usize, // P1: Name of the module that contains the virtual table implementation\n        table_name: usize,  // P2: Name of the virtual table\n        args_reg: Option<usize>,\n    },\n\n    /// Initialize the position of the virtual table cursor.\n    VFilter {\n        cursor_id: CursorID,\n        pc_if_empty: BranchOffset,\n        arg_count: usize,\n        args_reg: usize,\n        idx_str: Option<usize>,\n        idx_num: usize,\n    },\n\n    /// Read a column from the current row of the virtual table cursor.\n    VColumn {\n        cursor_id: CursorID,\n        column: usize,\n        dest: usize,\n    },\n\n    /// `VUpdate`: Virtual Table Insert/Update/Delete Instruction\n    VUpdate {\n        cursor_id: usize,     // P1: Virtual table cursor number\n        arg_count: usize,     // P2: Number of arguments in argv[]\n        start_reg: usize,     // P3: Start register for argv[]\n        conflict_action: u16, // P4: Conflict resolution flags\n    },\n\n    /// Advance the virtual table cursor to the next row.\n    /// TODO: async\n    VNext {\n        cursor_id: CursorID,\n        pc_if_next: BranchOffset,\n    },\n\n    /// P4 is the name of a virtual table in database P1. Call the xDestroy method of that table.\n    VDestroy {\n        /// Name of a virtual table being destroyed\n        table_name: String,\n        ///  The database within which this virtual table needs to be destroyed (P1).\n        db: usize,\n    },\n    VBegin {\n        /// The database within which this virtual table transaction needs to begin (P1).\n        cursor_id: CursorID,\n    },\n    VRename {\n        /// The database within which this virtual table needs to be renamed (P1).\n        cursor_id: CursorID,\n        /// New name of the virtual table (P2).\n        new_name_reg: usize,\n    },\n\n    /// Open a cursor for a pseudo-table that contains a single row.\n    OpenPseudo {\n        cursor_id: CursorID,\n        content_reg: usize,\n        num_fields: usize,\n    },\n\n    /// Rewind the cursor to the beginning of the B-Tree.\n    Rewind {\n        cursor_id: CursorID,\n        pc_if_empty: BranchOffset,\n    },\n\n    Last {\n        cursor_id: CursorID,\n        pc_if_empty: BranchOffset,\n    },\n\n    /// Read a column from the current row of the cursor.\n    Column {\n        cursor_id: CursorID,\n        column: usize,\n        dest: usize,\n        default: Option<Value>,\n    },\n\n    TypeCheck {\n        start_reg: usize, // P1\n        count: usize,     // P2\n        /// GENERATED ALWAYS AS ... STATIC columns are only checked if P3 is zero.\n        /// When P3 is non-zero, no type checking occurs for static generated columns.\n        check_generated: bool, // P3\n        table_reference: Arc<BTreeTable>, // P4\n    },\n\n    /// Parse a JSON text array into a native record-format BLOB, validating\n    /// and coercing each element against the declared type using STRICT\n    /// type-checking logic (apply_affinity_char + value_type check).\n    /// Input: reg = JSON text like '[1,2,3]'. Output: reg = record-format BLOB.\n    /// Raises SQLITE_CONSTRAINT on type mismatch.\n    ArrayEncode {\n        reg: usize,\n        element_affinity: Affinity,\n        element_type: Arc<str>,\n        table_name: Arc<str>,\n        col_name: Arc<str>,\n    },\n\n    /// Convert a native record-format BLOB back to JSON text for display.\n    /// Input: reg = record-format BLOB. Output: reg = JSON text '[1,2,3]'.\n    ArrayDecode {\n        reg: usize,\n    },\n\n    /// Access element at index from a record-format array BLOB.\n    /// If array is NULL or index out of bounds, dest = NULL.\n    ArrayElement {\n        array_reg: usize,\n        index_reg: usize,\n        dest: usize,\n    },\n\n    /// Get the number of elements in a record-format array BLOB.\n    /// If input is NULL, dest = 0.\n    ArrayLength {\n        reg: usize,\n        dest: usize,\n    },\n\n    /// Create an array from contiguous registers (static count).\n    /// Reads `count` values from start_reg..start_reg+count,\n    /// serializes via ImmutableRecord, stores Value::Blob in dest.\n    MakeArray {\n        start_reg: usize,\n        count: usize,\n        dest: usize,\n    },\n\n    /// Create an array from contiguous registers (dynamic count).\n    /// Like MakeArray but count is read from count_reg at runtime.\n    MakeArrayDynamic {\n        start_reg: usize,\n        count_reg: usize,\n        dest: usize,\n    },\n\n    /// Copy a register value to a dynamically-computed destination.\n    /// dest = registers[base + registers[offset_reg]]\n    /// registers[base + registers[offset_reg]] = registers[src]\n    RegCopyOffset {\n        src: usize,\n        base: usize,\n        offset_reg: usize,\n    },\n\n    /// Concatenate/append/prepend arrays. PostgreSQL-compatible semantics:\n    /// - blob || blob → array_cat\n    /// - blob || scalar → array_append\n    /// - scalar || blob → array_prepend\n    ///\n    /// Falls back to string Concat for non-array operands.\n    ArrayConcat {\n        lhs: usize,\n        rhs: usize,\n        dest: usize,\n    },\n\n    /// Set element at index in a record-format array BLOB.\n    /// Extracts all elements, replaces element at index, rebuilds blob.\n    ArraySetElement {\n        array_reg: usize,\n        index_reg: usize,\n        value_reg: usize,\n        dest: usize,\n    },\n\n    /// Extract a subslice of elements from a record-format array BLOB.\n    /// Creates a new array blob from elements[start..end].\n    ArraySlice {\n        array_reg: usize,\n        start_reg: usize,\n        end_reg: usize,\n        dest: usize,\n    },\n\n    // Make a record and write it to destination register.\n    MakeRecord {\n        start_reg: u16, // P1\n        count: u16,     // P2\n        dest_reg: u16,  // P3\n        index_name: Option<String>,\n        affinity_str: Option<String>,\n    },\n\n    /// Emit a row of results.\n    ResultRow {\n        start_reg: usize, // P1\n        count: usize,     // P2\n    },\n\n    /// Advance the cursor to the next row.\n    Next {\n        cursor_id: CursorID,\n        pc_if_next: BranchOffset,\n    },\n\n    Prev {\n        cursor_id: CursorID,\n        pc_if_prev: BranchOffset,\n    },\n\n    /// Halt the program.\n    Halt {\n        err_code: usize,\n        description: String,\n        /// Override the program's resolve_type for error handling (used by RAISE).\n        on_error: Option<ResolveType>,\n        /// If set, read the error description from this register instead of\n        /// the static `description` field (used by RAISE with expression messages).\n        description_reg: Option<usize>,\n    },\n\n    /// Halt the program if P3 is null.\n    HaltIfNull {\n        target_reg: usize,   // P3\n        description: String, // p4\n        err_code: usize,     // p1\n    },\n\n    /// Start a transaction.\n    Transaction {\n        db: usize,                // p1\n        tx_mode: TransactionMode, // p2\n        schema_cookie: u32,       // p3\n    },\n\n    /// Set database auto-commit mode and potentially rollback.\n    AutoCommit {\n        auto_commit: bool,\n        rollback: bool,\n    },\n\n    /// Execute a named savepoint operation.\n    Savepoint {\n        op: SavepointOp,\n        name: String,\n    },\n\n    /// Branch to the given PC.\n    Goto {\n        target_pc: BranchOffset,\n    },\n\n    /// Stores the current program counter into register 'return_reg' then jumps to address target_pc.\n    Gosub {\n        target_pc: BranchOffset,\n        return_reg: usize,\n    },\n\n    /// Returns to the program counter stored in register 'return_reg'.\n    /// If can_fallthrough is true, fall through to the next instruction\n    /// if return_reg does not contain an integer value. Otherwise raise an error.\n    Return {\n        return_reg: usize,\n        can_fallthrough: bool,\n    },\n\n    /// Invoke a trigger subprogram.\n    ///\n    /// According to SQLite documentation (https://sqlite.org/opcode.html):\n    /// \"The Program opcode invokes the trigger subprogram. The Program instruction\n    /// allocates and initializes a fresh register set for each invocation of the\n    /// subprogram, so subprograms can be reentrant and recursive. The Param opcode\n    /// is used by subprograms to access content in registers of the calling bytecode program.\"\n    Program {\n        params: Vec<Value>,\n        program: Arc<PreparedProgram>,\n        /// Jump target when RAISE(IGNORE) fires in the subprogram.\n        /// Points to the \"skip this row\" address in the parent program.\n        ignore_jump_target: BranchOffset,\n    },\n\n    /// Write an integer value into a register.\n    Integer {\n        value: i64,\n        dest: usize,\n    },\n\n    /// Write a float value into a register\n    Real {\n        value: f64,\n        dest: usize,\n    },\n\n    /// If register holds an integer, transform it to a float\n    RealAffinity {\n        register: usize,\n    },\n\n    // Write a string value into a register.\n    String8 {\n        value: String,\n        dest: usize,\n    },\n\n    /// Write a blob value into a register.\n    Blob {\n        value: Vec<u8>,\n        dest: usize,\n    },\n\n    /// Read a complete row of data from the current cursor and write it to the destination register.\n    RowData {\n        cursor_id: CursorID,\n        dest: usize,\n    },\n\n    /// Read the rowid of the current row.\n    RowId {\n        cursor_id: CursorID,\n        dest: usize,\n    },\n    /// Read the rowid of the current row from an index cursor.\n    IdxRowId {\n        cursor_id: CursorID,\n        dest: usize,\n    },\n\n    /// Seek to a rowid in the cursor. If not found, jump to the given PC. Otherwise, continue to the next instruction.\n    SeekRowid {\n        cursor_id: CursorID,\n        src_reg: usize,\n        target_pc: BranchOffset,\n    },\n    SeekEnd {\n        cursor_id: CursorID,\n    },\n\n    /// P1 is an open index cursor and P3 is a cursor on the corresponding table. This opcode does a deferred seek of the P3 table cursor to the row that corresponds to the current row of P1.\n    /// This is a deferred seek. Nothing actually happens until the cursor is used to read a record. That way, if no reads occur, no unnecessary I/O happens.\n    DeferredSeek {\n        index_cursor_id: CursorID,\n        table_cursor_id: CursorID,\n    },\n\n    /// If cursor_id refers to an SQL table (B-Tree that uses integer keys), use the value in start_reg as the key.\n    /// If cursor_id refers to an SQL index, then start_reg is the first in an array of num_regs registers that are used as an unpacked index key.\n    /// Seek to the first index entry that is greater than or equal to the given key. If not found, jump to the given PC. Otherwise, continue to the next instruction.\n    SeekGE {\n        is_index: bool,\n        cursor_id: CursorID,\n        start_reg: usize,\n        num_regs: usize,\n        target_pc: BranchOffset,\n        eq_only: bool,\n    },\n\n    /// If cursor_id refers to an SQL table (B-Tree that uses integer keys), use the value in start_reg as the key.\n    /// If cursor_id refers to an SQL index, then start_reg is the first in an array of num_regs registers that are used as an unpacked index key.\n    /// Seek to the first index entry that is greater than the given key. If not found, jump to the given PC. Otherwise, continue to the next instruction.\n    SeekGT {\n        is_index: bool,\n        cursor_id: CursorID,\n        start_reg: usize,\n        num_regs: usize,\n        target_pc: BranchOffset,\n    },\n\n    /// cursor_id is a cursor pointing to a B-Tree index that uses integer keys, this op writes the value obtained from MakeRecord into the index.\n    /// P3 + P4 are for the original column values that make up that key in unpacked (pre-serialized) form.\n    /// If P5 has the OPFLAG_APPEND bit set, that is a hint to the b-tree layer that this insert is likely to be an append.\n    /// OPFLAG_NCHANGE bit set, then the change counter is incremented by this instruction. If the OPFLAG_NCHANGE bit is clear, then the change counter is unchanged\n    IdxInsert {\n        cursor_id: CursorID,\n        record_reg: usize, // P2 the register containing the record to insert\n        unpacked_start: Option<usize>, // P3 the index of the first register for the unpacked key\n        unpacked_count: Option<u16>, // P4 # of unpacked values in the key in P2\n        flags: IdxInsertFlags, // TODO: optimization\n    },\n\n    /// The P4 register values beginning with P3 form an unpacked index key that omits the PRIMARY KEY. Compare this key value against the index that P1 is currently pointing to, ignoring the PRIMARY KEY or ROWID fields at the end.\n    /// If the P1 index entry is greater or equal than the key value then jump to P2. Otherwise fall through to the next instruction.\n    // If cursor_id refers to an SQL table (B-Tree that uses integer keys), use the value in start_reg as the key.\n    // If cursor_id refers to an SQL index, then start_reg is the first in an array of num_regs registers that are used as an unpacked index key.\n    // Seek to the first index entry that is less than or equal to the given key. If not found, jump to the given PC. Otherwise, continue to the next instruction.\n    SeekLE {\n        is_index: bool,\n        cursor_id: CursorID,\n        start_reg: usize,\n        num_regs: usize,\n        target_pc: BranchOffset,\n        eq_only: bool,\n    },\n\n    // If cursor_id refers to an SQL table (B-Tree that uses integer keys), use the value in start_reg as the key.\n    // If cursor_id refers to an SQL index, then start_reg is the first in an array of num_regs registers that are used as an unpacked index key.\n    // Seek to the first index entry that is less than the given key. If not found, jump to the given PC. Otherwise, continue to the next instruction.\n    SeekLT {\n        is_index: bool,\n        cursor_id: CursorID,\n        start_reg: usize,\n        num_regs: usize,\n        target_pc: BranchOffset,\n    },\n\n    // The P4 register values beginning with P3 form an unpacked index key that omits the PRIMARY KEY. Compare this key value against the index that P1 is currently pointing to, ignoring the PRIMARY KEY or ROWID fields at the end.\n    // If the P1 index entry is greater or equal than the key value then jump to P2. Otherwise fall through to the next instruction.\n    IdxGE {\n        cursor_id: CursorID,\n        start_reg: usize,\n        num_regs: usize,\n        target_pc: BranchOffset,\n    },\n\n    /// The P4 register values beginning with P3 form an unpacked index key that omits the PRIMARY KEY. Compare this key value against the index that P1 is currently pointing to, ignoring the PRIMARY KEY or ROWID fields at the end.\n    /// If the P1 index entry is greater than the key value then jump to P2. Otherwise fall through to the next instruction.\n    IdxGT {\n        cursor_id: CursorID,\n        start_reg: usize,\n        num_regs: usize,\n        target_pc: BranchOffset,\n    },\n\n    /// The P4 register values beginning with P3 form an unpacked index key that omits the PRIMARY KEY. Compare this key value against the index that P1 is currently pointing to, ignoring the PRIMARY KEY or ROWID fields at the end.\n    /// If the P1 index entry is lesser or equal than the key value then jump to P2. Otherwise fall through to the next instruction.\n    IdxLE {\n        cursor_id: CursorID,\n        start_reg: usize,\n        num_regs: usize,\n        target_pc: BranchOffset,\n    },\n\n    /// The P4 register values beginning with P3 form an unpacked index key that omits the PRIMARY KEY. Compare this key value against the index that P1 is currently pointing to, ignoring the PRIMARY KEY or ROWID fields at the end.\n    /// If the P1 index entry is lesser than the key value then jump to P2. Otherwise fall through to the next instruction.\n    IdxLT {\n        cursor_id: CursorID,\n        start_reg: usize,\n        num_regs: usize,\n        target_pc: BranchOffset,\n    },\n\n    /// Decrement the given register and jump to the given PC if the result is zero.\n    DecrJumpZero {\n        reg: usize,\n        target_pc: BranchOffset,\n    },\n\n    AggStep {\n        acc_reg: usize,\n        col: usize,\n        delimiter: usize,\n        func: AggFunc,\n        /// Optional custom type comparator for MIN/MAX aggregates.\n        comparator: Option<SortComparatorType>,\n    },\n\n    AggFinal {\n        register: usize,\n        func: AggFunc,\n    },\n\n    /// Similar to AggFinal, but instead of writing the result back into the\n    /// accumulator register, it stores the result in a separate destination\n    /// register.\n    AggValue {\n        acc_reg: usize,\n        dest_reg: usize,\n        func: AggFunc,\n    },\n\n    /// Open a sorter.\n    SorterOpen {\n        cursor_id: CursorID, // P1\n        columns: usize,      // P2\n        /// Combined order and collation per column (keeps Insn small, and order+collations are always the same length).\n        order_and_collations: Vec<(SortOrder, Option<CollationSeq>)>,\n        /// Per-column custom type comparators for ORDER BY sorting.\n        /// When present, the comparator is used instead of standard value comparison.\n        comparators: Vec<Option<SortComparatorType>>,\n    },\n\n    /// Insert a row into the sorter.\n    SorterInsert {\n        cursor_id: CursorID,\n        record_reg: usize,\n    },\n\n    /// `cursor_id` is a sorter cursor. This instruction compares a prefix of the record blob in register `sorted_record_reg`\n    /// against a prefix of the entry that the sorter cursor currently points to.\n    /// Only the first `num_regs` fields of `sorted_record_reg` and the sorter record are compared.\n    /// Fall through to next instruction if the two records compare equal to each other.\n    /// Jump to `pc_when_nonequal` if they are different.\n    SorterCompare {\n        cursor_id: CursorID,\n        pc_when_nonequal: BranchOffset,\n        sorted_record_reg: usize,\n        num_regs: usize,\n    },\n\n    /// Sort the rows in the sorter.\n    SorterSort {\n        cursor_id: CursorID,\n        pc_if_empty: BranchOffset,\n    },\n\n    /// Retrieve the next row from the sorter.\n    SorterData {\n        cursor_id: CursorID,  // P1\n        dest_reg: usize,      // P2\n        pseudo_cursor: usize, // P3\n    },\n\n    /// Advance to the next row in the sorter.\n    SorterNext {\n        cursor_id: CursorID,\n        pc_if_next: BranchOffset,\n    },\n\n    /// Insert the integer value held by register P2 into a RowSet object held in register P1.\n    /// An assertion fails if P2 is not an integer.\n    RowSetAdd {\n        rowset_reg: usize, // P1 - register holding RowSet\n        value_reg: usize,  // P2 - register holding integer value to add\n    },\n\n    /// Extract the smallest value from the RowSet object in P1 and put that value into register P3.\n    /// Or, if RowSet object P1 is initially empty, leave P3 unchanged and jump to instruction P2.\n    RowSetRead {\n        rowset_reg: usize,         // P1 - register holding RowSet\n        pc_if_empty: BranchOffset, // P2 - jump target if empty\n        dest_reg: usize,           // P3 - register to store smallest value\n    },\n\n    /// Register P3 is assumed to hold a 64-bit integer value. If register P1 contains a RowSet object\n    /// and that RowSet object contains the value held in P3, jump to register P2. Otherwise, insert\n    /// the integer in P3 into the RowSet and continue on to the next opcode.\n    /// P4 is the batch identifier (0 for first set, -1 for final set, >0 for other sets).\n    RowSetTest {\n        rowset_reg: usize,         // P1 - register holding RowSet\n        pc_if_found: BranchOffset, // P2 - jump target if value found\n        value_reg: usize,          // P3 - register holding integer value to test/insert\n        batch: i32,                // P4 - batch identifier\n    },\n\n    /// Function\n    Function {\n        constant_mask: i32, // P1\n        start_reg: usize,   // P2, start of argument registers\n        dest: usize,        // P3\n        func: FuncCtx,      // P4\n    },\n\n    /// Cast register P1 to affinity P2 and store in register P1\n    Cast {\n        reg: usize,\n        affinity: Affinity,\n    },\n\n    InitCoroutine {\n        yield_reg: usize,\n        jump_on_definition: BranchOffset,\n        start_offset: BranchOffset,\n    },\n\n    EndCoroutine {\n        yield_reg: usize,\n    },\n\n    Yield {\n        yield_reg: usize,\n        end_offset: BranchOffset,\n        /// For coroutine body yields (end_offset == 0): the start register of the\n        /// output columns and how many there are.  op_yield uses these to strip\n        /// the JSON subtype so that it does not survive the subquery boundary,\n        /// mirroring SQLite's OP_Copy P5=0x0002 behaviour.\n        /// Set to 0/0 for parent-side (non-body) yields.\n        subtype_clear_start_reg: usize,\n        subtype_clear_count: usize,\n    },\n\n    Insert {\n        cursor: CursorID,\n        key_reg: usize,    // Must be int.\n        record_reg: usize, // Blob of record data.\n        flag: InsertFlags, // Flags used by insert, for now not used.\n        table_name: String,\n    },\n\n    Int64 {\n        _p1: usize,     //  unused\n        out_reg: usize, // the output register\n        _p3: usize,     // unused\n        value: i64,     //  the value being written into the output register\n    },\n\n    Delete {\n        cursor_id: CursorID,\n        table_name: String,\n        /// Whether the DELETE is part of an UPDATE statement. If so, it doesn't count towards the change counter.\n        is_part_of_update: bool,\n    },\n\n    /// If P5 is not zero, then raise an SQLITE_CORRUPT_INDEX error if no matching index entry\n    /// is found. This happens when running an UPDATE or DELETE statement and the index entry to\n    /// be updated or deleted is not found. For some uses of IdxDelete (example: the EXCEPT operator)\n    /// it does not matter that no matching entry is found. For those cases, P5 is zero.\n    IdxDelete {\n        start_reg: usize,\n        num_regs: usize,\n        cursor_id: CursorID,\n        raise_error_if_no_matching_entry: bool, // P5\n    },\n\n    NewRowid {\n        cursor: CursorID,        // P1\n        rowid_reg: usize,        // P2  Destination register to store the new rowid\n        prev_largest_reg: usize, // P3 Previous largest rowid in the table (Not used for now)\n    },\n\n    MustBeInt {\n        reg: usize,\n    },\n\n    SoftNull {\n        reg: usize,\n    },\n\n    /// If P4==0 then register P3 holds a blob constructed by [MakeRecord](https://sqlite.org/opcode.html#MakeRecord).\n    /// If P4>0 then register P3 is the first of P4 registers that form an unpacked record.\n    ///\n    /// Cursor P1 is on an index btree. If the record identified by P3 and P4 contains any NULL value, jump immediately\n    /// to P2. If all terms of the record are not-NULL then a check is done to determine if any row in the P1 index\n    /// btree has a matching key prefix. If there are no matches, jump immediately to P2. If there is a match, fall\n    /// through and leave the P1 cursor pointing to the matching row.\\\n    ///\n    /// This opcode is similar to [NotFound](https://sqlite.org/opcode.html#NotFound) with the exceptions that the\n    /// branch is always taken if any part of the search key input is NULL.\n    NoConflict {\n        cursor_id: CursorID,     // P1 index cursor\n        target_pc: BranchOffset, // P2 jump target\n        record_reg: usize,\n        num_regs: usize,\n    },\n\n    NotExists {\n        cursor: CursorID,\n        rowid_reg: usize,\n        target_pc: BranchOffset,\n    },\n\n    OffsetLimit {\n        limit_reg: usize,\n        combined_reg: usize,\n        offset_reg: usize,\n    },\n\n    OpenWrite {\n        cursor_id: CursorID,\n        root_page: RegisterOrLiteral<PageIdx>,\n        db: usize,\n    },\n\n    /// Make a copy of register src..src+extra_amount into dst..dst+extra_amount.\n    Copy {\n        src_reg: usize,\n        dst_reg: usize,\n        /// 0 extra_amount means we include src_reg, dst_reg..=dst_reg+amount = src_reg..=src_reg+amount\n        extra_amount: usize,\n    },\n\n    /// Allocate a new b-tree.\n    CreateBtree {\n        /// Allocate b-tree in main database if zero or in temp database if non-zero (P1).\n        db: usize,\n        /// The root page of the new b-tree (P2).\n        root: usize,\n        /// Flags (P3).\n        flags: CreateBTreeFlags,\n    },\n\n    /// Create custom index method (calls [crate::index_method::IndexMethodCursor::create] under the hood)\n    IndexMethodCreate {\n        db: usize,\n        cursor_id: CursorID,\n    },\n    /// Destroy custom index method (calls [crate::index_method::IndexMethodCursor::destroy] under the hood)\n    IndexMethodDestroy {\n        db: usize,\n        cursor_id: CursorID,\n    },\n    /// Optimize custom index method (calls [crate::index_method::IndexMethodCursor::optimize] under the hood)\n    IndexMethodOptimize {\n        db: usize,\n        cursor_id: CursorID,\n    },\n    /// Query custom index method (call [crate::index_method::IndexMethodCursor::query_start] under the hood)\n    IndexMethodQuery {\n        db: usize,\n        cursor_id: CursorID,\n        start_reg: usize,\n        count_reg: usize,\n        pc_if_empty: BranchOffset,\n    },\n\n    /// Deletes an entire database table or index whose root page in the database file is given by P1.\n    Destroy {\n        /// The database index (0 = main, 1 = temp, 2+ = attached)\n        db: usize,\n        /// The root page of the table/index to destroy\n        root: i64,\n        /// Register to store the former value of any moved root page (for AUTOVACUUM)\n        former_root_reg: usize,\n        /// Whether this is a temporary table (1) or main database table (0)\n        is_temp: usize,\n    },\n\n    /// Deletes all contents from the ephemeral table that the cursor points to.\n    ///\n    /// In Turso, we do not currently distinguish strictly between ephemeral\n    /// and standard tables at the type level. Therefore, it is the caller’s\n    /// responsibility to ensure that `ResetSorter` is applied only to ephemeral\n    /// tables.\n    ///\n    /// SQLite also supports sorter cursors, but this is not yet implemented in Turso.\n    ResetSorter {\n        cursor_id: CursorID,\n    },\n\n    ///  Drop a table\n    DropTable {\n        ///  The database within which this b-tree needs to be dropped (P1).\n        db: usize,\n        ///  unused register p2\n        _p2: usize,\n        ///  unused register p3\n        _p3: usize,\n        //  The name of the table being dropped\n        table_name: String,\n    },\n    DropView {\n        /// The database within which this view needs to be dropped\n        db: usize,\n        /// The name of the view being dropped\n        view_name: String,\n    },\n    DropIndex {\n        ///  The database within which this index needs to be dropped (P1).\n        db: usize,\n        //  The name of the index being dropped\n        index: Arc<Index>,\n    },\n    /// Drop a trigger\n    DropTrigger {\n        /// The database within which this trigger needs to be dropped (P1).\n        db: usize,\n        /// The name of the trigger being dropped\n        trigger_name: String,\n    },\n    /// Drop a custom type from the in-memory schema\n    DropType {\n        /// The database within which this type needs to be dropped\n        db: usize,\n        /// The name of the type being dropped\n        type_name: String,\n    },\n    /// Add a custom type to the in-memory schema by parsing its CREATE TYPE SQL\n    AddType {\n        /// The database within which this type needs to be added\n        db: usize,\n        /// The full CREATE TYPE SQL string\n        sql: String,\n    },\n\n    /// Close a cursor.\n    Close {\n        cursor_id: CursorID,\n    },\n\n    /// Check if the register is null.\n    IsNull {\n        /// Source register (P1).\n        reg: usize,\n\n        /// Jump to this PC if the register is null (P2).\n        target_pc: BranchOffset,\n    },\n\n    /// Set the collation sequence for the next function call.\n    /// P4 is a pointer to a CollationSeq. If the next call to a user function\n    /// or aggregate calls sqlite3GetFuncCollSeq(), this collation sequence will\n    /// be returned. This is used by the built-in min(), max() and nullif()\n    /// functions.\n    ///\n    /// If P1 is not zero, then it is a register that a subsequent min() or\n    /// max() aggregate will set to 1 if the current row is not the minimum or\n    /// maximum.  The P1 register is initialized to 0 by this instruction.\n    CollSeq {\n        /// Optional register to initialize to 0 (P1).\n        reg: Option<usize>,\n        /// The collation sequence to set (P4).\n        collation: CollationSeq,\n    },\n    ParseSchema {\n        db: usize,\n        where_clause: Option<String>,\n    },\n\n    /// Populate all materialized views after schema parsing\n    /// The cursors parameter contains a mapping of view names to cursor IDs that have been\n    /// opened to the view's btree for writing the materialized data\n    PopulateMaterializedViews {\n        /// Mapping of view name to cursor_id for writing to the view's btree\n        cursors: Vec<(String, usize)>,\n    },\n\n    /// Place the result of lhs >> rhs in dest register.\n    ShiftRight {\n        lhs: usize,\n        rhs: usize,\n        dest: usize,\n    },\n\n    /// Place the result of lhs << rhs in dest register.\n    ShiftLeft {\n        lhs: usize,\n        rhs: usize,\n        dest: usize,\n    },\n\n    /// Add immediate value to register and force integer conversion.\n    /// Add the constant P2 to the value in register P1. The result is always an integer.\n    /// To force any register to be an integer, just add 0.\n    AddImm {\n        register: usize, // P1: target register\n        value: i64,      // P2: immediate value to add\n    },\n\n    /// Get parameter variable.\n    Variable {\n        index: NonZero<usize>,\n        dest: usize,\n    },\n    /// If either register is null put null else put 0\n    ZeroOrNull {\n        /// Source register (P1).\n        rg1: usize,\n        rg2: usize,\n        dest: usize,\n    },\n    /// Interpret the value in reg as boolean and store its compliment in destination\n    Not {\n        reg: usize,\n        dest: usize,\n    },\n    /// Interpret the value in register `reg` as a boolean and store in `dest`.\n    /// Used to implement IS TRUE, IS FALSE, IS NOT TRUE, IS NOT FALSE.\n    ///\n    /// A value is considered \"true\" if it is a non-zero number.\n    /// Strings, blobs, and zero are \"false\". NULL is handled specially.\n    ///\n    /// - If reg is NULL, store `null_value` in dest\n    /// - Otherwise, store 1 if the value is a non-zero number, 0 otherwise\n    /// - If `invert` is true, invert the result (0↔1)\n    IsTrue {\n        reg: usize,\n        dest: usize,\n        /// Value to store if input is NULL (0 or 1)\n        null_value: bool,\n        /// Whether to invert the result\n        invert: bool,\n    },\n    /// Concatenates the `rhs` and `lhs` values and stores the result in the third register.\n    Concat {\n        lhs: usize,\n        rhs: usize,\n        dest: usize,\n    },\n    /// Take the logical AND of the values in registers P1 and P2 and write the result into register P3.\n    And {\n        lhs: usize,\n        rhs: usize,\n        dest: usize,\n    },\n    /// Take the logical OR of the values in register P1 and P2 and store the answer in register P3.\n    Or {\n        lhs: usize,\n        rhs: usize,\n        dest: usize,\n    },\n    /// Do nothing. Continue downward to the next opcode.\n    Noop,\n    /// Write the current number of pages in database P1 to memory cell P2.\n    PageCount {\n        db: usize,\n        dest: usize,\n    },\n    /// Read cookie number P3 from database P1 and write it into register P2\n    ReadCookie {\n        db: usize,\n        dest: usize,\n        cookie: Cookie,\n    },\n    /// Write the value in register P3 into cookie number P2 of database P1.\n    /// If P2 is the SCHEMA_VERSION cookie (cookie number 1) then the internal schema version is set to P3-P5\n    SetCookie {\n        db: usize,\n        cookie: Cookie,\n        value: i32,\n        p5: u16,\n    },\n    /// Open a new cursor P1 to a transient table.\n    OpenEphemeral {\n        cursor_id: usize,\n        is_table: bool,\n    },\n    /// Works the same as OpenEphemeral, name just distinguishes its use; used for transient indexes in joins.\n    OpenAutoindex {\n        cursor_id: usize,\n    },\n    /// Opens a new cursor that points to the same table as the original.\n    /// In SQLite, this is restricted to cursors opened by `OpenEphemeral`\n    /// (i.e., ephemeral tables), and only ephemeral cursors may be duplicated.\n    /// In Turso, we currently do not strictly distinguish between ephemeral\n    /// and standard tables at the type level. Therefore, it is the caller’s\n    /// responsibility to ensure that `OpenDup` is applied only to ephemeral\n    /// cursors.\n    OpenDup {\n        new_cursor_id: CursorID,\n        original_cursor_id: CursorID,\n    },\n    /// Fall through to the next instruction on the first invocation, otherwise jump to target_pc\n    Once {\n        target_pc_when_reentered: BranchOffset,\n    },\n    /// Search for a record in the index cursor.\n    /// If any entry for which the key is a prefix exists, jump to target_pc.\n    /// Otherwise, continue to the next instruction.\n    Found {\n        cursor_id: CursorID,\n        target_pc: BranchOffset,\n        record_reg: usize,\n        num_regs: usize,\n    },\n    /// Search for record in the index cusor, if any entry for which the key is a prefix exists\n    /// is a no-op, otherwise go to target_pc\n    /// Example =>\n    /// For a index key (1,2,3):\n    /// NotFound((1,2,3)) => No-op\n    /// NotFound((1,2)) => No-op\n    /// NotFound((2,2, 1)) => Jump\n    NotFound {\n        cursor_id: CursorID,\n        target_pc: BranchOffset,\n        record_reg: usize,\n        num_regs: usize,\n    },\n    /// Apply affinities to a range of registers. Affinities must have the same size of count\n    Affinity {\n        start_reg: usize,\n        count: NonZeroUsize,\n        affinities: String,\n    },\n\n    /// Store the number of entries (an integer value) in the table or index opened by cursor P1 in register P2.\n    ///\n    /// If P3==0, then an exact count is obtained, which involves visiting every btree page of the table.\n    /// But if P3 is non-zero, an estimate is returned based on the current cursor position.\n    Count {\n        cursor_id: CursorID,\n        target_reg: usize,\n        exact: bool,\n    },\n\n    /// Perform low-level btree/freelist structural integrity checks.\n    /// Writes NULL to `message_register` when no structural problem is found,\n    /// otherwise writes a textual error summary.\n    /// Higher-level semantic checks (row/index consistency, constraints, etc.)\n    /// are emitted as normal VDBE bytecode in translation.\n    IntegrityCk {\n        db: usize,\n        max_errors: usize,\n        roots: Vec<i64>,\n        message_register: usize,\n    },\n    RenameTable {\n        db: usize,\n        from: String,\n        to: String,\n    },\n    DropColumn {\n        db: usize,\n        table: String,\n        column_index: usize,\n    },\n    AddColumn {\n        db: usize,\n        table: String,\n        column: Box<Column>,\n        check_constraints: Vec<CheckConstraint>,\n    },\n    AlterColumn {\n        db: usize,\n        table: String,\n        column_index: usize,\n        definition: Box<turso_parser::ast::ColumnDefinition>,\n        rename: bool,\n    },\n    /// Try to set the maximum page count for database P1 to the value in P3.\n    /// Do not let the maximum page count fall below the current page count and\n    /// do not change the maximum page count value if P3==0.\n    /// Store the maximum page count after the change in register P2.\n    MaxPgcnt {\n        db: usize,      // P1: database index\n        dest: usize,    // P2: output register\n        new_max: usize, // P3: new maximum page count (0 = just return current)\n    },\n    /// Get or set the journal mode for database P1.\n    /// If P3 is not null, it contains the new journal mode string.\n    /// Store the resulting journal mode in register P2.\n    JournalMode {\n        db: usize,                // P1: database index\n        dest: usize,              // P2: output register for result\n        new_mode: Option<String>, // P3: new journal mode (if setting)\n    },\n    IfNeg {\n        reg: usize,\n        target_pc: BranchOffset,\n    },\n\n    /// Find the next available sequence number for cursor P1. Write the sequence number into register P2.\n    /// The sequence number on the cursor is incremented after this instruction.\n    Sequence {\n        cursor_id: CursorID,\n        target_reg: usize,\n    },\n\n    /// P1 is a sorter cursor. If the sequence counter is currently zero, jump to P2. Regardless of whether or not the jump is taken, increment the the sequence value.\n    SequenceTest {\n        cursor_id: CursorID,\n        target_pc: BranchOffset,\n        value_reg: usize,\n    },\n\n    // OP_Explain\n    Explain {\n        p1: usize,         // P1: address of instruction\n        p2: Option<usize>, // P2: address of parent explain instruction\n        detail: String,    // P4: detail text\n    },\n    // Increment a \"constraint counter\" by P2 (P2 may be negative or positive).\n    // If P1 is non-zero, the database constraint counter is incremented (deferred foreign key constraints).\n    // Otherwise, if P1 is zero, the statement counter is incremented (immediate foreign key constraints).\n    FkCounter {\n        increment_value: isize,\n        deferred: bool,\n    },\n    // This opcode tests if a foreign key constraint-counter is currently zero. If so, jump to instruction P2. Otherwise, fall through to the next instruction.\n    // If P1 is non-zero, then the jump is taken if the database constraint-counter is zero (the one that counts deferred constraint violations).\n    // If P1 is zero, the jump is taken if the statement constraint-counter is zero (immediate foreign key constraint violations).\n    FkIfZero {\n        deferred: bool,\n        target_pc: BranchOffset,\n    },\n    // Check if there are any unresolved foreign key constraint violations.\n    // If P1 is zero, check the statement constraint-counter (immediate FK violations).\n    // If P1 is non-zero, check the database constraint-counter (deferred FK violations).\n    // If violations exist, throw SQLITE_CONSTRAINT_FOREIGNKEY.\n    FkCheck {\n        deferred: bool,\n    },\n\n    /// Build a hash table from a cursor for hash join.\n    HashBuild {\n        data: Box<HashBuildData>,\n    },\n\n    /// Deduplicate using a hash table. Jumps to target_pc if duplicate found.\n    HashDistinct {\n        data: Box<HashDistinctData>,\n    },\n\n    /// Finalize the hash table build phase. Transitions the hash table from Building to Probing state.\n    /// Should be called after the HashBuild loop completes.\n    HashBuildFinalize {\n        hash_table_id: usize,\n    },\n\n    /// Probe a hash table for matches.\n    /// Extract probe keys from registers key_start_reg..key_start_reg+num_keys-1,\n    /// hash them, and look up matches in the hash table stored in hash_table_reg.\n    /// For each match, load the build-side rowid into dest_reg and continue.\n    /// If payload columns were stored during build, they are written to\n    /// payload_dest_reg..payload_dest_reg+num_payload-1.\n    /// If no matches, jump to target_pc.\n    HashProbe {\n        hash_table_id: u16,\n        key_start_reg: u16,\n        num_keys: u16,\n        dest_reg: u16,\n        target_pc: BranchOffset,\n        /// Starting register to write payload columns from hash entry.\n        payload_dest_reg: Option<u16>,\n        /// Number of payload columns expected\n        num_payload: u16,\n        /// Register containing probe-side rowid for grace hash join buffering.\n        /// When Some and target partition is on disk, buffer the probe row\n        /// instead of loading the partition on demand.\n        /// When None, this instruction is running inside grace processing and\n        /// the build partition must already be loaded.\n        probe_rowid_reg: Option<u16>,\n    },\n\n    /// Advance to next matching row in hash table bucket.\n    /// Used for handling hash collisions and duplicate keys.\n    /// If another match is found, store rowid in dest_reg (and payload in payload_dest_reg if set).\n    /// If no more matches, jump to target_pc.\n    HashNext {\n        hash_table_id: usize,\n        dest_reg: usize,\n        target_pc: BranchOffset,\n        /// Starting register to write payload columns from hash entry, if we are caching payload.\n        payload_dest_reg: Option<usize>,\n        /// Number of payload columns expected\n        num_payload: usize,\n    },\n\n    /// Free hash table resources.\n    /// Closes the hash table referenced by hash_table_id and releases memory.\n    HashClose {\n        hash_table_id: usize,\n    },\n\n    /// Clear hash table entries without releasing the table itself.\n    HashClear {\n        hash_table_id: usize,\n    },\n\n    /// Mark the current hash table match entry as \"matched\" (for FULL OUTER JOIN).\n    HashMarkMatched {\n        hash_table_id: usize,\n    },\n\n    /// Reset all matched_bits in a hash table to false.\n    /// Emitted at the start of each outer-loop iteration so that marks from\n    /// a previous probe pass don't suppress NULL-fill rows in the current one.\n    HashResetMatched {\n        hash_table_id: usize,\n    },\n\n    /// Begin scanning unmatched entries in the hash table (for FULL OUTER JOIN).\n    /// Writes the first unmatched entry's rowid to dest_reg and payload to payload_dest_reg.\n    /// If no unmatched entries exist, jumps to target_pc.\n    HashScanUnmatched {\n        hash_table_id: usize,\n        dest_reg: usize,\n        target_pc: BranchOffset,\n        payload_dest_reg: Option<usize>,\n        num_payload: usize,\n    },\n\n    /// Advance to the next unmatched entry in the hash table (for FULL OUTER JOIN).\n    /// If another unmatched entry is found, writes rowid to dest_reg and payload to payload_dest_reg.\n    /// If no more unmatched entries, jumps to target_pc.\n    HashNextUnmatched {\n        hash_table_id: usize,\n        dest_reg: usize,\n        target_pc: BranchOffset,\n        payload_dest_reg: Option<usize>,\n        num_payload: usize,\n    },\n\n    /// Initialize grace hash join processing after the probe cursor is exhausted.\n    /// Finalizes probe-side spills and calls grace_begin.\n    /// Jumps to target_pc if no spilling occurred or no partitions to process.\n    HashGraceInit {\n        hash_table_id: u16,\n        target_pc: BranchOffset,\n    },\n\n    /// Load the current grace partition's build side from disk.\n    /// Also loads the first probe chunk. Jumps to target_pc when all partitions done.\n    HashGraceLoadPartition {\n        hash_table_id: u16,\n        target_pc: BranchOffset,\n    },\n\n    /// Advance to next probe entry in the current grace partition.\n    /// Writes probe keys to key_start_reg..key_start_reg+num_keys-1 and probe rowid to probe_rowid_dest.\n    /// Jumps to target_pc when probe entries exhausted.\n    HashGraceNextProbe {\n        hash_table_id: u16,\n        key_start_reg: u16,\n        num_keys: u16,\n        probe_rowid_dest: u16,\n        target_pc: BranchOffset,\n    },\n\n    /// Evict current grace partition and advance to the next one.\n    /// Jumps to target_pc when all partitions are processed.\n    HashGraceAdvancePartition {\n        hash_table_id: u16,\n        target_pc: BranchOffset,\n    },\n\n    /// VACUUM INTO - create a compacted copy of the database at the specified path.\n    /// This copies all schema and data from the current database to a new file.\n    VacuumInto {\n        /// Destination file path for the vacuumed database\n        dest_path: String,\n    },\n\n    /// Ensure turso_cdc_version table exists and insert/replace a version row,\n    /// then enable CDC on the connection. Runs nested SQL at VDBE execution time\n    /// (same pattern as ParseSchema). CDC is enabled after version table operations\n    /// so those operations are not captured.\n    ///\n    /// A dedicated opcode is needed because the PRAGMA SET handler may create the\n    /// CDC table (via translate_create_table) and then needs to insert data into\n    /// turso_cdc_version — which requires a schema change followed by DML against\n    /// the new table. This is hard to express in a single translation plan since\n    /// plans are compiled against a fixed schema, so the version table operations\n    /// are deferred to execution time via this opcode.\n    InitCdcVersion {\n        cdc_table_name: String,\n        version: crate::CdcVersion,\n        cdc_mode: String,\n    },\n}\n\nconst fn get_insn_virtual_table() -> [InsnFunction; InsnVariants::COUNT] {\n    let mut result: [InsnFunction; InsnVariants::COUNT] = [execute::op_init; InsnVariants::COUNT];\n\n    let mut insn = 0;\n    while insn < InsnVariants::COUNT {\n        result[insn] = InsnVariants::from_repr(insn as u8)\n            .expect(\"insn index should be valid within COUNT\")\n            .to_function();\n        insn += 1;\n    }\n\n    result\n}\n\nconst INSN_VTABLE: [InsnFunction; InsnVariants::COUNT] = get_insn_virtual_table();\n\nimpl InsnVariants {\n    // This function is used for testing\n    #[allow(dead_code)]\n    #[inline(always)]\n    pub(crate) const fn to_function_fast(self) -> InsnFunction {\n        INSN_VTABLE[self as usize]\n    }\n\n    // This function is used for generating `INSN_VTABLE`.\n    // We need to keep this function to make sure we implement all opcodes\n    pub(crate) const fn to_function(self) -> InsnFunction {\n        match self {\n            InsnVariants::Init => execute::op_init,\n            InsnVariants::Null => execute::op_null,\n            InsnVariants::BeginSubrtn => execute::op_null,\n            InsnVariants::NullRow => execute::op_null_row,\n            InsnVariants::Add => execute::op_add,\n            InsnVariants::Subtract => execute::op_subtract,\n            InsnVariants::Multiply => execute::op_multiply,\n            InsnVariants::Divide => execute::op_divide,\n            InsnVariants::DropIndex => execute::op_drop_index,\n            InsnVariants::Compare => execute::op_compare,\n            InsnVariants::BitAnd => execute::op_bit_and,\n            InsnVariants::BitOr => execute::op_bit_or,\n            InsnVariants::BitNot => execute::op_bit_not,\n            InsnVariants::Checkpoint => execute::op_checkpoint,\n            InsnVariants::Remainder => execute::op_remainder,\n            InsnVariants::Jump => execute::op_jump,\n            InsnVariants::Move => execute::op_move,\n            InsnVariants::IfPos => execute::op_if_pos,\n            InsnVariants::NotNull => execute::op_not_null,\n            InsnVariants::Eq\n            | InsnVariants::Ne\n            | InsnVariants::Lt\n            | InsnVariants::Le\n            | InsnVariants::Gt\n            | InsnVariants::Ge => execute::op_comparison,\n            InsnVariants::If => execute::op_if,\n            InsnVariants::IfNot => execute::op_if_not,\n            InsnVariants::OpenRead => execute::op_open_read,\n            InsnVariants::VOpen => execute::op_vopen,\n            InsnVariants::VCreate => execute::op_vcreate,\n            InsnVariants::VFilter => execute::op_vfilter,\n            InsnVariants::VColumn => execute::op_vcolumn,\n            InsnVariants::VUpdate => execute::op_vupdate,\n            InsnVariants::VNext => execute::op_vnext,\n            InsnVariants::VDestroy => execute::op_vdestroy,\n            InsnVariants::OpenPseudo => execute::op_open_pseudo,\n            InsnVariants::Rewind => execute::op_rewind,\n            InsnVariants::Last => execute::op_last,\n            InsnVariants::Column => execute::op_column,\n            InsnVariants::TypeCheck => execute::op_type_check,\n            InsnVariants::ArrayEncode => execute::op_array_encode,\n            InsnVariants::ArrayDecode => execute::op_array_decode,\n            InsnVariants::ArrayElement => execute::op_array_element,\n            InsnVariants::ArrayLength => execute::op_array_length,\n            InsnVariants::MakeArray => execute::op_make_array,\n            InsnVariants::MakeArrayDynamic => execute::op_make_array_dynamic,\n            InsnVariants::RegCopyOffset => execute::op_reg_copy_offset,\n            InsnVariants::ArrayConcat => execute::op_array_concat,\n            InsnVariants::ArraySetElement => execute::op_array_set_element,\n            InsnVariants::ArraySlice => execute::op_array_slice,\n            InsnVariants::MakeRecord => execute::op_make_record,\n            InsnVariants::ResultRow => execute::op_result_row,\n            InsnVariants::Next => execute::op_next,\n            InsnVariants::Prev => execute::op_prev,\n            InsnVariants::Halt => execute::op_halt,\n            InsnVariants::HaltIfNull => execute::op_halt_if_null,\n            InsnVariants::Transaction => execute::op_transaction,\n            InsnVariants::AutoCommit => execute::op_auto_commit,\n            InsnVariants::Savepoint => execute::op_savepoint,\n            InsnVariants::Goto => execute::op_goto,\n            InsnVariants::Gosub => execute::op_gosub,\n            InsnVariants::Return => execute::op_return,\n            InsnVariants::Integer => execute::op_integer,\n            InsnVariants::Program => execute::op_program,\n            InsnVariants::Real => execute::op_real,\n            InsnVariants::RealAffinity => execute::op_real_affinity,\n            InsnVariants::String8 => execute::op_string8,\n            InsnVariants::Blob => execute::op_blob,\n            InsnVariants::RowData => execute::op_row_data,\n            InsnVariants::RowId => execute::op_row_id,\n            InsnVariants::IdxRowId => execute::op_idx_row_id,\n            InsnVariants::SeekRowid => execute::op_seek_rowid,\n            InsnVariants::DeferredSeek => execute::op_deferred_seek,\n            InsnVariants::SeekGE\n            | InsnVariants::SeekGT\n            | InsnVariants::SeekLE\n            | InsnVariants::SeekLT => execute::op_seek,\n            InsnVariants::SeekEnd => execute::op_seek_end,\n            InsnVariants::IdxGE => execute::op_idx_ge,\n            InsnVariants::IdxGT => execute::op_idx_gt,\n            InsnVariants::IdxLE => execute::op_idx_le,\n            InsnVariants::IdxLT => execute::op_idx_lt,\n            InsnVariants::DecrJumpZero => execute::op_decr_jump_zero,\n            InsnVariants::AggStep => execute::op_agg_step,\n            InsnVariants::AggFinal | InsnVariants::AggValue => execute::op_agg_final,\n            InsnVariants::SorterOpen => execute::op_sorter_open,\n            InsnVariants::SorterInsert => execute::op_sorter_insert,\n            InsnVariants::SorterSort => execute::op_sorter_sort,\n            InsnVariants::SorterData => execute::op_sorter_data,\n            InsnVariants::SorterNext => execute::op_sorter_next,\n            InsnVariants::SorterCompare => execute::op_sorter_compare,\n            InsnVariants::RowSetAdd => execute::op_rowset_add,\n            InsnVariants::RowSetRead => execute::op_rowset_read,\n            InsnVariants::RowSetTest => execute::op_rowset_test,\n            InsnVariants::Function => execute::op_function,\n            InsnVariants::Cast => execute::op_cast,\n            InsnVariants::InitCoroutine => execute::op_init_coroutine,\n            InsnVariants::EndCoroutine => execute::op_end_coroutine,\n            InsnVariants::Yield => execute::op_yield,\n            InsnVariants::Insert => execute::op_insert,\n            InsnVariants::Int64 => execute::op_int_64,\n            InsnVariants::IdxInsert => execute::op_idx_insert,\n            InsnVariants::Delete => execute::op_delete,\n            InsnVariants::NewRowid => execute::op_new_rowid,\n            InsnVariants::MustBeInt => execute::op_must_be_int,\n            InsnVariants::SoftNull => execute::op_soft_null,\n            InsnVariants::NoConflict => execute::op_no_conflict,\n            InsnVariants::NotExists => execute::op_not_exists,\n            InsnVariants::OffsetLimit => execute::op_offset_limit,\n            InsnVariants::OpenWrite => execute::op_open_write,\n            InsnVariants::Copy => execute::op_copy,\n            InsnVariants::CreateBtree => execute::op_create_btree,\n            InsnVariants::IndexMethodCreate => execute::op_index_method_create,\n            InsnVariants::IndexMethodDestroy => execute::op_index_method_destroy,\n            InsnVariants::IndexMethodOptimize => execute::op_index_method_optimize,\n            InsnVariants::IndexMethodQuery => execute::op_index_method_query,\n            InsnVariants::Destroy => execute::op_destroy,\n            InsnVariants::ResetSorter => execute::op_reset_sorter,\n            InsnVariants::DropTable => execute::op_drop_table,\n            InsnVariants::DropTrigger => execute::op_drop_trigger,\n            InsnVariants::DropType => execute::op_drop_type,\n            InsnVariants::AddType => execute::op_add_type,\n            InsnVariants::DropView => execute::op_drop_view,\n            InsnVariants::Close => execute::op_close,\n            InsnVariants::IsNull => execute::op_is_null,\n            InsnVariants::CollSeq => execute::op_coll_seq,\n            InsnVariants::ParseSchema => execute::op_parse_schema,\n            InsnVariants::PopulateMaterializedViews => execute::op_populate_materialized_views,\n            InsnVariants::ShiftRight => execute::op_shift_right,\n            InsnVariants::ShiftLeft => execute::op_shift_left,\n            InsnVariants::AddImm => execute::op_add_imm,\n            InsnVariants::Variable => execute::op_variable,\n            InsnVariants::ZeroOrNull => execute::op_zero_or_null,\n            InsnVariants::Not => execute::op_not,\n            InsnVariants::IsTrue => execute::op_is_true,\n            InsnVariants::Concat => execute::op_concat,\n            InsnVariants::And => execute::op_and,\n            InsnVariants::Or => execute::op_or,\n            InsnVariants::Noop => execute::op_noop,\n            InsnVariants::PageCount => execute::op_page_count,\n            InsnVariants::ReadCookie => execute::op_read_cookie,\n            InsnVariants::SetCookie => execute::op_set_cookie,\n            InsnVariants::OpenEphemeral | InsnVariants::OpenAutoindex => execute::op_open_ephemeral,\n            InsnVariants::Once => execute::op_once,\n            InsnVariants::Found | InsnVariants::NotFound => execute::op_found,\n            InsnVariants::Affinity => execute::op_affinity,\n            InsnVariants::IdxDelete => execute::op_idx_delete,\n            InsnVariants::Count => execute::op_count,\n            InsnVariants::IntegrityCk => execute::op_integrity_check,\n            InsnVariants::RenameTable => execute::op_rename_table,\n            InsnVariants::DropColumn => execute::op_drop_column,\n            InsnVariants::AddColumn => execute::op_add_column,\n            InsnVariants::AlterColumn => execute::op_alter_column,\n            InsnVariants::MaxPgcnt => execute::op_max_pgcnt,\n            InsnVariants::JournalMode => execute::op_journal_mode,\n            InsnVariants::IfNeg => execute::op_if_neg,\n            InsnVariants::Explain => execute::op_noop,\n            InsnVariants::OpenDup => execute::op_open_dup,\n            InsnVariants::MemMax => execute::op_mem_max,\n            InsnVariants::Sequence => execute::op_sequence,\n            InsnVariants::SequenceTest => execute::op_sequence_test,\n            InsnVariants::FkCounter => execute::op_fk_counter,\n            InsnVariants::FkIfZero => execute::op_fk_if_zero,\n            InsnVariants::FkCheck => execute::op_fk_check,\n            InsnVariants::VBegin => execute::op_vbegin,\n            InsnVariants::VRename => execute::op_vrename,\n            InsnVariants::FilterAdd => execute::op_filter_add,\n            InsnVariants::Filter => execute::op_filter,\n            InsnVariants::HashBuild => execute::op_hash_build,\n            InsnVariants::HashDistinct => execute::op_hash_distinct,\n            InsnVariants::HashBuildFinalize => execute::op_hash_build_finalize,\n            InsnVariants::HashProbe => execute::op_hash_probe,\n            InsnVariants::HashNext => execute::op_hash_next,\n            InsnVariants::HashClose => execute::op_hash_close,\n            InsnVariants::HashClear => execute::op_hash_clear,\n            InsnVariants::HashMarkMatched => execute::op_hash_mark_matched,\n            InsnVariants::HashResetMatched => execute::op_hash_reset_matched,\n            InsnVariants::HashScanUnmatched => execute::op_hash_scan_unmatched,\n            InsnVariants::HashNextUnmatched => execute::op_hash_next_unmatched,\n            InsnVariants::HashGraceInit => execute::op_hash_grace_init,\n            InsnVariants::HashGraceLoadPartition => execute::op_hash_grace_load_partition,\n            InsnVariants::HashGraceNextProbe => execute::op_hash_grace_next_probe,\n            InsnVariants::HashGraceAdvancePartition => execute::op_hash_grace_advance_partition,\n            InsnVariants::VacuumInto => execute::op_vacuum_into,\n            InsnVariants::InitCdcVersion => execute::op_init_cdc_version,\n        }\n    }\n}\n\nimpl Insn {\n    // SAFETY: If the enumeration specifies a primitive representation,\n    // then the discriminant may be reliably accessed via unsafe pointer casting\n    #[inline(always)]\n    fn discriminant(&self) -> u8 {\n        unsafe { *(self as *const Self as *const u8) }\n    }\n\n    #[inline(always)]\n    pub fn to_function(&self) -> InsnFunction {\n        // dont use this because its still using match\n        // InsnVariants::from(self).to_function_fast()\n        INSN_VTABLE[self.discriminant() as usize]\n    }\n}\n\n// TODO: Add remaining cookies.\n#[derive(Description, Debug, Clone, Copy)]\npub enum Cookie {\n    /// The schema cookie.\n    SchemaVersion = 1,\n    /// The schema format number. Supported schema formats are 1, 2, 3, and 4.\n    DatabaseFormat = 2,\n    /// Default page cache size.\n    DefaultPageCacheSize = 3,\n    /// The page number of the largest root b-tree page when in auto-vacuum or incremental-vacuum modes, or zero otherwise.\n    LargestRootPageNumber = 4,\n    /// The database text encoding. A value of 1 means UTF-8. A value of 2 means UTF-16le. A value of 3 means UTF-16be.\n    DatabaseTextEncoding = 5,\n    /// The \"user version\" as read and set by the user_version pragma.\n    UserVersion = 6,\n    /// The auto-vacuum mode setting.\n    IncrementalVacuum = 7,\n    /// The application ID as set by the application_id pragma.\n    ApplicationId = 8,\n}\n\n#[cfg(test)]\nmod tests {\n    use strum::VariantArray;\n\n    #[test]\n    fn test_make_sure_correct_insn_table() {\n        for variant in super::InsnVariants::VARIANTS {\n            let func1 = variant.to_function();\n            let func2 = variant.to_function_fast();\n            assert_eq!(\n                func1 as usize, func2 as usize,\n                \"Variant {:?} does not match in fast table at index {}\",\n                variant, *variant as usize\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "core/vdbe/metrics.rs",
    "content": "use std::fmt;\n\n/// Hash join spill/probe metrics.\n#[derive(Debug, Default, Clone)]\npub struct HashJoinMetrics {\n    // Spill metrics\n    pub spill_bytes_written: u64,\n    pub spill_chunks: u64,\n    pub spill_max_chunks_per_partition: u64,\n    pub spill_max_partition_bytes: u64,\n\n    // Load metrics\n    pub load_bytes_read: u64,\n\n    // Probe metrics\n    pub probe_calls: u64,\n\n    // Grace hash join metrics\n    pub probe_spill_bytes_written: u64,\n    pub probe_spill_chunks: u64,\n    pub grace_partitions_processed: u64,\n    pub grace_probe_rows_streamed: u64,\n    pub grace_probe_rows_buffered: u64,\n    pub grace_matches: u64,\n}\n\nimpl HashJoinMetrics {\n    pub fn merge(&mut self, other: &HashJoinMetrics) {\n        self.spill_bytes_written = self\n            .spill_bytes_written\n            .saturating_add(other.spill_bytes_written);\n        self.spill_chunks = self.spill_chunks.saturating_add(other.spill_chunks);\n        self.spill_max_chunks_per_partition = self\n            .spill_max_chunks_per_partition\n            .max(other.spill_max_chunks_per_partition);\n        self.spill_max_partition_bytes = self\n            .spill_max_partition_bytes\n            .max(other.spill_max_partition_bytes);\n        self.load_bytes_read = self.load_bytes_read.saturating_add(other.load_bytes_read);\n        self.probe_calls = self.probe_calls.saturating_add(other.probe_calls);\n        self.probe_spill_bytes_written = self\n            .probe_spill_bytes_written\n            .saturating_add(other.probe_spill_bytes_written);\n        self.probe_spill_chunks = self\n            .probe_spill_chunks\n            .saturating_add(other.probe_spill_chunks);\n        self.grace_partitions_processed = self\n            .grace_partitions_processed\n            .saturating_add(other.grace_partitions_processed);\n        self.grace_probe_rows_streamed = self\n            .grace_probe_rows_streamed\n            .saturating_add(other.grace_probe_rows_streamed);\n        self.grace_probe_rows_buffered = self\n            .grace_probe_rows_buffered\n            .saturating_add(other.grace_probe_rows_buffered);\n        self.grace_matches = self.grace_matches.saturating_add(other.grace_matches);\n    }\n\n    pub fn reset(&mut self) {\n        *self = Self::default();\n    }\n}\n\n/// Statement-level execution metrics\n///\n/// These metrics are collected unconditionally during statement execution\n/// with minimal overhead (simple counter increments). The cost of incrementing\n/// these counters is negligible compared to the actual work being measured.\n#[derive(Debug, Default, Clone)]\npub struct StatementMetrics {\n    // Row operations\n    pub rows_read: u64,\n    pub rows_written: u64,\n\n    // Execution statistics\n    pub vm_steps: u64,\n    pub insn_executed: u64,\n\n    // Table scan metrics\n    pub fullscan_steps: u64,\n    pub index_steps: u64,\n\n    // Sort and filter operations\n    pub sort_operations: u64,\n    pub filter_operations: u64,\n\n    // B-tree operations\n    pub btree_seeks: u64,\n    pub btree_next: u64,\n    pub btree_prev: u64,\n\n    // Hash join spill/probe metrics\n    pub hash_join: HashJoinMetrics,\n}\n\nimpl StatementMetrics {\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    /// Get total row operations\n    pub fn total_row_ops(&self) -> u64 {\n        self.rows_read + self.rows_written\n    }\n\n    /// Merge another metrics instance into this one (for aggregation)\n    pub fn merge(&mut self, other: &StatementMetrics) {\n        self.rows_read = self.rows_read.saturating_add(other.rows_read);\n        self.rows_written = self.rows_written.saturating_add(other.rows_written);\n        self.vm_steps = self.vm_steps.saturating_add(other.vm_steps);\n        self.insn_executed = self.insn_executed.saturating_add(other.insn_executed);\n        self.fullscan_steps = self.fullscan_steps.saturating_add(other.fullscan_steps);\n        self.index_steps = self.index_steps.saturating_add(other.index_steps);\n        self.sort_operations = self.sort_operations.saturating_add(other.sort_operations);\n        self.filter_operations = self\n            .filter_operations\n            .saturating_add(other.filter_operations);\n        self.btree_seeks = self.btree_seeks.saturating_add(other.btree_seeks);\n        self.btree_next = self.btree_next.saturating_add(other.btree_next);\n        self.btree_prev = self.btree_prev.saturating_add(other.btree_prev);\n        self.hash_join.merge(&other.hash_join);\n    }\n\n    /// Reset all counters to zero\n    pub fn reset(&mut self) {\n        *self = Self::default();\n    }\n}\n\nimpl fmt::Display for StatementMetrics {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        writeln!(f, \"Statement Metrics:\")?;\n        writeln!(f, \"  Row Operations:\")?;\n        writeln!(f, \"    Rows read:        {}\", self.rows_read)?;\n        writeln!(f, \"    Rows written:     {}\", self.rows_written)?;\n        writeln!(f, \"  Execution:\")?;\n        writeln!(f, \"    VM steps:         {}\", self.vm_steps)?;\n        writeln!(f, \"    Instructions:     {}\", self.insn_executed)?;\n        writeln!(f, \"  Table Access:\")?;\n        writeln!(f, \"    Full scan steps:  {}\", self.fullscan_steps)?;\n        writeln!(f, \"    Index steps:      {}\", self.index_steps)?;\n        writeln!(f, \"  Operations:\")?;\n        writeln!(f, \"    Sort operations:  {}\", self.sort_operations)?;\n        writeln!(f, \"    Filter operations:{}\", self.filter_operations)?;\n        writeln!(f, \"  B-tree Operations:\")?;\n        writeln!(f, \"    Seeks:            {}\", self.btree_seeks)?;\n        writeln!(f, \"    Next:             {}\", self.btree_next)?;\n        writeln!(f, \"    Prev:             {}\", self.btree_prev)?;\n        writeln!(f, \"  Hash Join:\")?;\n        writeln!(\n            f,\n            \"    Spill bytes:      {}\",\n            self.hash_join.spill_bytes_written\n        )?;\n        writeln!(f, \"    Spill chunks:     {}\", self.hash_join.spill_chunks)?;\n        writeln!(\n            f,\n            \"    Max chunks/part:  {}\",\n            self.hash_join.spill_max_chunks_per_partition\n        )?;\n        writeln!(\n            f,\n            \"    Max part bytes:   {}\",\n            self.hash_join.spill_max_partition_bytes\n        )?;\n        writeln!(\n            f,\n            \"    Load bytes:       {}\",\n            self.hash_join.load_bytes_read\n        )?;\n        writeln!(f, \"    Probes:           {}\", self.hash_join.probe_calls)?;\n        writeln!(\n            f,\n            \"    Probe spill bytes: {}\",\n            self.hash_join.probe_spill_bytes_written\n        )?;\n        writeln!(\n            f,\n            \"    Probe spill chunks: {}\",\n            self.hash_join.probe_spill_chunks\n        )?;\n        writeln!(\n            f,\n            \"    Grace partitions:  {}\",\n            self.hash_join.grace_partitions_processed\n        )?;\n        writeln!(\n            f,\n            \"    Grace streamed:    {}\",\n            self.hash_join.grace_probe_rows_streamed\n        )?;\n        writeln!(\n            f,\n            \"    Grace buffered:    {}\",\n            self.hash_join.grace_probe_rows_buffered\n        )?;\n        writeln!(f, \"    Grace matches:     {}\", self.hash_join.grace_matches)?;\n        Ok(())\n    }\n}\n\n/// Connection-level metrics aggregation\n#[derive(Debug, Default, Clone)]\npub struct ConnectionMetrics {\n    /// Total number of statements executed\n    pub total_statements: u64,\n\n    /// Aggregate metrics from all statements\n    pub aggregate: StatementMetrics,\n\n    /// High-water marks for monitoring\n    pub max_vm_steps_per_statement: u64,\n    pub max_rows_read_per_statement: u64,\n}\n\nimpl ConnectionMetrics {\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    /// Record a completed statement's metrics (borrows, no clone).\n    pub fn record_statement(&mut self, metrics: &StatementMetrics) {\n        self.total_statements = self.total_statements.saturating_add(1);\n\n        // Update high-water marks\n        self.max_vm_steps_per_statement = self.max_vm_steps_per_statement.max(metrics.vm_steps);\n        self.max_rows_read_per_statement = self.max_rows_read_per_statement.max(metrics.rows_read);\n\n        // Aggregate into total\n        self.aggregate.merge(metrics);\n    }\n\n    /// Reset connection metrics\n    pub fn reset(&mut self) {\n        *self = Self::default();\n    }\n}\n\nimpl fmt::Display for ConnectionMetrics {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        writeln!(f, \"Connection Metrics:\")?;\n        writeln!(f, \"  Total statements:     {}\", self.total_statements)?;\n        writeln!(f, \"  High-water marks:\")?;\n        writeln!(\n            f,\n            \"    Max VM steps:       {}\",\n            self.max_vm_steps_per_statement\n        )?;\n        writeln!(\n            f,\n            \"    Max rows read:      {}\",\n            self.max_rows_read_per_statement\n        )?;\n        writeln!(f)?;\n        writeln!(f, \"Aggregate Statistics:\")?;\n        write!(f, \"{}\", self.aggregate)?;\n        Ok(())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_metrics_merge() {\n        let mut m1 = StatementMetrics::new();\n        m1.rows_read = 100;\n        m1.vm_steps = 50;\n        m1.hash_join.spill_bytes_written = 42;\n\n        let mut m2 = StatementMetrics::new();\n        m2.rows_read = 200;\n        m2.vm_steps = 75;\n        m2.hash_join.spill_bytes_written = 8;\n        m2.hash_join.spill_max_partition_bytes = 1024;\n\n        m1.merge(&m2);\n        assert_eq!(m1.rows_read, 300);\n        assert_eq!(m1.vm_steps, 125);\n        assert_eq!(m1.hash_join.spill_bytes_written, 50);\n        assert_eq!(m1.hash_join.spill_max_partition_bytes, 1024);\n    }\n\n    #[test]\n    fn test_connection_metrics_high_water() {\n        let mut conn_metrics = ConnectionMetrics::new();\n\n        let mut stmt1 = StatementMetrics::new();\n        stmt1.vm_steps = 100;\n        stmt1.rows_read = 50;\n        conn_metrics.record_statement(&stmt1);\n\n        let mut stmt2 = StatementMetrics::new();\n        stmt2.vm_steps = 75;\n        stmt2.rows_read = 100;\n        conn_metrics.record_statement(&stmt2);\n\n        assert_eq!(conn_metrics.max_vm_steps_per_statement, 100);\n        assert_eq!(conn_metrics.max_rows_read_per_statement, 100);\n        assert_eq!(conn_metrics.total_statements, 2);\n        assert_eq!(conn_metrics.aggregate.vm_steps, 175);\n        assert_eq!(conn_metrics.aggregate.rows_read, 150);\n    }\n}\n"
  },
  {
    "path": "core/vdbe/mod.rs",
    "content": "//! The virtual database engine (VDBE).\n//!\n//! The VDBE is a register-based virtual machine that execute bytecode\n//! instructions that represent SQL statements. When an application prepares\n//! an SQL statement, the statement is compiled into a sequence of bytecode\n//! instructions that perform the needed operations, such as reading or\n//! writing to a b-tree, sorting, or aggregating data.\n//!\n//! The instruction set of the VDBE is similar to SQLite's instruction set,\n//! but with the exception that bytecodes that perform I/O operations are\n//! return execution back to the caller instead of blocking. This is because\n//! Turso is designed for applications that need high concurrency such as\n//! serverless runtimes. In addition, asynchronous I/O makes storage\n//! disaggregation easier.\n//!\n//! You can find a full list of SQLite opcodes at:\n//!\n//! https://www.sqlite.org/opcode.html\n\nuse crate::types::{Extendable, Text};\nuse crate::{turso_assert, turso_assert_ne, turso_debug_assert, HashSet, NonNan};\npub mod affinity;\npub mod array;\npub mod bloom_filter;\npub mod builder;\npub mod execute;\npub mod explain;\n#[allow(dead_code)]\npub mod hash_table;\npub mod insn;\npub mod metrics;\npub mod rowset;\npub mod sorter;\npub mod value;\n// for benchmarks\npub use crate::translate::collate::CollationSeq;\nuse crate::{\n    error::LimboError,\n    function::{AggFunc, FuncCtx},\n    mvcc::{database::CommitStateMachine, MvccClock},\n    numeric::Numeric,\n    return_if_io,\n    schema::Trigger,\n    state_machine::StateMachine,\n    translate::plan::TableReferences,\n    types::{IOCompletions, IOResult},\n    vdbe::{\n        execute::{\n            OpColumnState, OpDeleteState, OpDeleteSubState, OpDestroyState, OpIdxInsertState,\n            OpInsertState, OpInsertSubState, OpJournalModeState, OpNewRowidState,\n            OpNoConflictState, OpProgramState, OpRowIdState, OpSeekState, OpTransactionState,\n            OpVacuumIntoState,\n        },\n        hash_table::HashTable,\n        metrics::StatementMetrics,\n    },\n    ValueRef,\n};\nuse smallvec::SmallVec;\n\n#[cfg(feature = \"json\")]\nuse crate::json::JsonCacheCell;\nuse crate::sync::RwLock;\nuse crate::{\n    storage::pager::Pager,\n    translate::plan::ResultSetColumn,\n    types::{AggContext, Cursor, ImmutableRecord, Value},\n    vdbe::{builder::CursorType, insn::Insn},\n};\nuse crate::{\n    AtomicBool, CaptureDataChangesInfo, Connection, MvStore, Result, SyncMode, TransactionState,\n};\nuse branches::{mark_unlikely, unlikely};\nuse builder::{CursorKey, QueryMode};\nuse execute::{\n    InsnFunction, InsnFunctionStepResult, OpIdxDeleteState, OpIntegrityCheckState,\n    OpOpenEphemeralState,\n};\nuse turso_parser::ast::ResolveType;\n\nuse crate::vdbe::bloom_filter::BloomFilter;\nuse crate::vdbe::rowset::RowSet;\nuse explain::{insn_to_row_with_comment, EXPLAIN_COLUMNS, EXPLAIN_QUERY_PLAN_COLUMNS};\nuse std::{\n    collections::HashMap,\n    num::NonZero,\n    ops::Deref,\n    sync::{\n        atomic::{AtomicI64, AtomicIsize, Ordering},\n        Arc,\n    },\n    task::Waker,\n};\nuse tracing::{instrument, Level};\n\n/// State machine for committing view deltas with I/O handling\n#[derive(Debug, Clone)]\npub enum ViewDeltaCommitState {\n    NotStarted,\n    Processing {\n        views: Vec<String>, // view names (all materialized views have storage)\n        current_index: usize,\n    },\n    Done,\n}\n\n/// We use labels to indicate that we want to jump to whatever the instruction offset\n/// will be at runtime, because the offset cannot always be determined when the jump\n/// instruction is created.\n///\n/// In some cases, we want to jump to EXACTLY a specific instruction.\n/// - Example: a condition is not met, so we want to jump to wherever Halt is.\n///\n/// In other cases, we don't care what the exact instruction is, but we know that we\n/// want to jump to whatever comes AFTER a certain instruction.\n/// - Example: a Next instruction will want to jump to \"whatever the start of the loop is\",\n///   but it doesn't care what instruction that is.\n///\n/// The reason this distinction is important is that we might reorder instructions that are\n/// constant at compile time, and when we do that, we need to change the offsets of any impacted\n/// jump instructions, so the instruction that comes immediately after \"next Insn\" might have changed during the reordering.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub enum JumpTarget {\n    ExactlyThisInsn,\n    AfterThisInsn,\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\n/// Represents a target for a jump instruction.\n/// Stores 32-bit ints to keep the enum word-sized.\npub enum BranchOffset {\n    /// A label is a named location in the program.\n    /// If there are references to it, it must always be resolved to an Offset\n    /// via program.resolve_label().\n    Label(u32),\n    /// An offset is a direct index into the instruction list.\n    Offset(InsnReference),\n    /// A placeholder is a temporary value to satisfy the compiler.\n    /// It must be set later.\n    Placeholder,\n}\n\nimpl BranchOffset {\n    /// Returns true if the branch offset is a label.\n    pub fn is_label(&self) -> bool {\n        matches!(self, BranchOffset::Label(_))\n    }\n\n    /// Returns true if the branch offset is an offset.\n    pub fn is_offset(&self) -> bool {\n        matches!(self, BranchOffset::Offset(_))\n    }\n\n    /// Returns the offset value. Panics if the branch offset is a label or placeholder.\n    pub fn as_offset_int(&self) -> InsnReference {\n        match self {\n            BranchOffset::Label(v) => unreachable!(\"Unresolved label: {}\", v),\n            BranchOffset::Offset(v) => *v,\n            BranchOffset::Placeholder => unreachable!(\"Unresolved placeholder\"),\n        }\n    }\n\n    /// Returns the branch offset as a signed integer.\n    /// Used in explain output, where we don't want to panic in case we have an unresolved\n    /// label or placeholder.\n    pub fn as_debug_int(&self) -> i32 {\n        match self {\n            BranchOffset::Label(v) => *v as i32,\n            BranchOffset::Offset(v) => *v as i32,\n            BranchOffset::Placeholder => i32::MAX,\n        }\n    }\n\n    /// Adds an integer value to the branch offset.\n    /// Returns a new branch offset.\n    /// Panics if the branch offset is a label or placeholder.\n    #[allow(clippy::should_implement_trait)]\n    pub fn add<N: Into<u32>>(self, n: N) -> BranchOffset {\n        BranchOffset::Offset(self.as_offset_int() + n.into())\n    }\n\n    #[allow(clippy::should_implement_trait)]\n    pub fn sub<N: Into<u32>>(self, n: N) -> BranchOffset {\n        BranchOffset::Offset(self.as_offset_int() - n.into())\n    }\n}\n\npub type CursorID = usize;\n\npub type PageIdx = i64;\n\n// Index of insn in list of insns\ntype InsnReference = u32;\n\n#[derive(Debug)]\npub enum StepResult {\n    Done,\n    IO,\n    Row,\n    Interrupt,\n    Busy,\n}\n\n#[derive(Debug)]\n#[allow(clippy::large_enum_variant)]\n/// The commit state of the program.\n/// There are two states:\n/// - Ready: The program is ready to run the next instruction, or has shut down after\n///   the last instruction.\n/// - Committing: The program is committing a write transaction. It is waiting for the pager to finish flushing the cache to disk,\n///   primarily to the WAL, but also possibly checkpointing the WAL to the database file.\nenum CommitState {\n    Ready,\n    Committing,\n    /// Committing attached database pagers after main pager commit is done.\n    CommittingAttached,\n    CommittingMvcc {\n        state_machine: StateMachine<CommitStateMachine<MvccClock>>,\n    },\n    /// Committing MVCC transactions on attached databases after main MVCC commit is done.\n    CommittingAttachedMvcc {\n        state_machine: StateMachine<CommitStateMachine<MvccClock>>,\n        db_id: usize,\n        mv_store: Arc<MvStore>,\n    },\n}\n\n#[derive(Debug, Clone)]\npub enum Register {\n    Value(Value),\n    Aggregate(AggContext),\n    Record(ImmutableRecord),\n}\n\nimpl Register {\n    #[inline]\n    pub fn is_null(&self) -> bool {\n        matches!(self, Register::Value(Value::Null))\n    }\n\n    #[inline(always)]\n    /// Sets the value of the register to an integer,\n    /// reusing the existing Register::Value(Value::Numeric(Numeric::Integer(_))) if possible,\n    /// which is faster than always creating a new one.\n    pub fn set_int(&mut self, val: i64) {\n        match self {\n            Register::Value(Value::Numeric(Numeric::Integer(existing))) => {\n                *existing = val;\n            }\n            Register::Value(Value::Numeric(float)) => {\n                *float = Numeric::Integer(val);\n            }\n            Register::Value(other_value_kind) => {\n                *other_value_kind = Value::from_i64(val);\n            }\n            _ => {\n                *self = Register::Value(Value::from_i64(val));\n            }\n        }\n    }\n    /// Set the value of the register to a floating point,\n    /// reusing Register::Value(Value::Numeric(Numeric::Float(_))) if possible.\n    #[inline(always)]\n    pub fn set_float(&mut self, val: NonNan) {\n        match self {\n            Register::Value(Value::Numeric(Numeric::Float(existing))) => {\n                *existing = val;\n            }\n            Register::Value(Value::Numeric(integer)) => {\n                *integer = Numeric::Float(val);\n            }\n            Register::Value(other_value_kind) => {\n                *other_value_kind = Value::Numeric(Numeric::Float(val));\n            }\n            _ => {\n                *self = Register::Value(Value::Numeric(Numeric::Float(val)));\n            }\n        }\n    }\n\n    /// Set the value of the register to a Text,\n    /// reusing Register::Value(Value::Text(_)) buffer if possible.\n    #[inline]\n    pub fn set_text(&mut self, val: Text) {\n        match self {\n            Register::Value(Value::Text(existing)) => {\n                existing.do_extend(&val);\n            }\n            Register::Value(other_value_kind) => {\n                *other_value_kind = Value::Text(val);\n            }\n            _ => {\n                *self = Register::Value(Value::Text(val));\n            }\n        }\n    }\n\n    /// Set the value of the register to a blob,\n    /// reusing Register::Value(Value::Blob(_)) buffer if possible.\n    #[inline]\n    pub fn set_blob(&mut self, val: Vec<u8>) {\n        match self {\n            Register::Value(Value::Blob(existing)) => {\n                existing.do_extend(&val);\n            }\n            Register::Value(other_value_kind) => {\n                *other_value_kind = Value::Blob(val);\n            }\n            _ => {\n                *self = Register::Value(Value::Blob(val));\n            }\n        }\n    }\n\n    // Set the value of the register to NULL,\n    // reusing the existing Register::Value(Value::Null) if possible.\n    pub fn set_null(&mut self) {\n        match self {\n            Register::Value(Value::Null) => {}\n            Register::Value(other_value_kind) => {\n                *other_value_kind = Value::Null;\n            }\n            _ => {\n                *self = Register::Value(Value::Null);\n            }\n        }\n    }\n\n    /// Set the register to a generic Value, attempting to reuse backing allocation if compatible.\n    pub fn set_value(&mut self, val: Value) {\n        match self {\n            Register::Value(v) => {\n                *v = val;\n            }\n            _ => {\n                *self = Register::Value(val);\n            }\n        }\n    }\n}\n\n/// A row is a the list of registers that hold the values for a filtered row. This row is a pointer, therefore\n/// after stepping again, row will be invalidated to be sure it doesn't point to somewhere unexpected.\n#[derive(Debug)]\npub struct Row {\n    values: *const Register,\n    count: usize,\n}\n\n#[derive(Debug, Clone, Copy, PartialEq)]\npub enum TxnCleanup {\n    None,\n    RollbackTxn,\n}\n\n#[derive(Debug, Clone, Copy, PartialEq)]\npub enum ProgramExecutionState {\n    /// No steps of the program was executed\n    Init,\n    /// Program started execution but didn't reach any terminal state\n    Running,\n    /// Interrupt requested for the program\n    Interrupting,\n    /// Terminal state: program interrupted\n    Interrupted,\n    /// Terminal state: program finished successfully\n    Done,\n    /// Terminal state: program failed with error\n    Failed,\n}\n\nimpl ProgramExecutionState {\n    pub fn is_running(&self) -> bool {\n        matches!(\n            self,\n            ProgramExecutionState::Interrupting | ProgramExecutionState::Running\n        )\n    }\n    pub fn is_terminal(&self) -> bool {\n        matches!(\n            self,\n            ProgramExecutionState::Interrupted\n                | ProgramExecutionState::Failed\n                | ProgramExecutionState::Done\n        )\n    }\n}\n\n/// Re-entrant state for [Insn::HashBuild].\n/// Allows HashBuild to resume cleanly after async I/O without re-reading the row.\n#[derive(Debug, Default)]\npub struct OpHashBuildState {\n    pub key_values: Vec<Value>,\n    pub key_idx: usize,\n    pub payload_values: Vec<Value>,\n    pub payload_idx: usize,\n    pub rowid: Option<i64>,\n    pub cursor_id: CursorID,\n    pub hash_table_id: usize,\n    pub key_start_reg: usize,\n    pub num_keys: usize,\n}\n\n/// Re-entrant state for [Insn::HashProbe].\n/// Allows HashProbe to resume cleanly after async probe-row buffering I/O.\n#[derive(Debug, Default)]\npub struct OpHashProbeState {\n    /// Cached probe key values to avoid re-reading from registers\n    pub probe_keys: Vec<Value>,\n    /// Hash table register being probed\n    pub hash_table_id: usize,\n    /// Partition index being loaded (if any)\n    pub partition_idx: usize,\n    /// Whether the probe row was already buffered for grace processing.\n    pub probe_buffered: bool,\n}\n\n#[derive(Debug, Clone)]\npub(crate) struct DeferredSeekState {\n    pub index_cursor_id: CursorID,\n    pub table_cursor_id: CursorID,\n}\n\n/// The program state describes the environment in which the program executes.\npub struct ProgramState {\n    pub io_completions: Option<IOCompletions>,\n    pub pc: InsnReference,\n    pub(crate) cursors: Vec<Option<Cursor>>,\n    cursor_seqs: Vec<i64>,\n    registers: Box<[Register]>,\n    pub(crate) result_row: Option<Row>,\n    last_compare: Option<std::cmp::Ordering>,\n    deferred_seeks: Vec<Option<DeferredSeekState>>,\n    /// Indicate whether a coroutine has ended for a given yield register.\n    /// If an element is present, it means the coroutine with the given register number has ended.\n    ended_coroutine: Vec<u32>,\n    /// Indicate whether an [Insn::Once] instruction at a given program counter position has already been executed, well, once.\n    once: SmallVec<[u32; 4]>,\n    pub execution_state: ProgramExecutionState,\n    pub parameters: Vec<Value>,\n    commit_state: CommitState,\n    #[cfg(feature = \"json\")]\n    json_cache: JsonCacheCell,\n    op_delete_state: OpDeleteState,\n    op_destroy_state: OpDestroyState,\n    op_idx_delete_state: Option<OpIdxDeleteState>,\n    op_integrity_check_state: OpIntegrityCheckState,\n    /// Metrics collected during statement execution\n    pub metrics: StatementMetrics,\n    op_open_ephemeral_state: OpOpenEphemeralState,\n    op_program_state: OpProgramState,\n    op_new_rowid_state: OpNewRowidState,\n    op_idx_insert_state: OpIdxInsertState,\n    op_insert_state: OpInsertState,\n    op_no_conflict_state: OpNoConflictState,\n    seek_state: OpSeekState,\n    /// Current collation sequence set by OP_CollSeq instruction\n    current_collation: Option<CollationSeq>,\n    op_column_state: OpColumnState,\n    op_row_id_state: OpRowIdState,\n    op_transaction_state: OpTransactionState,\n    op_journal_mode_state: OpJournalModeState,\n    op_vacuum_into_state: Option<OpVacuumIntoState>,\n    /// State machine for committing view deltas with I/O handling\n    view_delta_state: ViewDeltaCommitState,\n    /// Marker which tells about auto transaction cleanup necessary for that connection in case of reset\n    /// This is used when statement in auto-commit mode reseted after previous uncomplete execution - in which case we may need to rollback transaction started on previous attempt\n    pub(crate) auto_txn_cleanup: TxnCleanup,\n    /// Number of deferred foreign key violations when the statement started.\n    /// When a statement subtransaction rolls back, the connection's deferred foreign key violations counter\n    /// is reset to this value.\n    fk_deferred_violations_when_stmt_started: AtomicIsize,\n    /// Number of immediate foreign key violations that occurred during the active statement. If nonzero,\n    /// the statement subtransactionwill roll back.\n    fk_immediate_violations_during_stmt: AtomicIsize,\n    /// RowSet objects stored by register index\n    rowsets: HashMap<usize, RowSet>,\n    /// Bloom filters stored by cursor ID for probabilistic set membership testing\n    /// Used to avoid unnecessary seeks on ephemeral indexes and hash tables\n    pub(crate) bloom_filters: HashMap<usize, BloomFilter>,\n    op_hash_build_state: Option<OpHashBuildState>,\n    op_hash_probe_state: Option<OpHashProbeState>,\n    /// Scratch buffer for [Insn::HashDistinct] to avoid per-row allocations.\n    distinct_key_values: Vec<Value>,\n    hash_tables: HashMap<usize, HashTable>,\n    uses_subjournal: bool,\n    /// Whether this statement is an active write inside an explicit transaction.\n    pub(crate) is_active_write: bool,\n    /// Whether begin_statement was called (savepoint + FK bookkeeping active).\n    has_stmt_transaction: bool,\n    /// Attached pagers that have open savepoints for statement rollback.\n    attached_savepoint_pagers: Vec<Arc<Pager>>,\n    pub n_change: AtomicI64,\n    pub explain_state: RwLock<ExplainState>,\n    /// Pending error to return after FAIL mode commit completes.\n    /// When a constraint error occurs with FAIL resolve type in autocommit mode,\n    /// we need to commit partial changes before returning the error.\n    pub(crate) pending_fail_error: Option<LimboError>,\n    /// Pending CDC info to apply after the program completes successfully.\n    /// Set by InitCdcVersion opcode, applied at Halt/Done so that if the\n    /// transaction rolls back, the connection's CDC state remains unchanged.\n    ///\n    /// capture_data_changes has type Option<CaptureDataChangesInfo> (off mode is None)\n    /// so, for pending_cdc_info we wrap it in one more Option<...> layer to represent if mode changed during program execution\n    pub(crate) pending_cdc_info: Option<Option<CaptureDataChangesInfo>>,\n}\n\nimpl std::fmt::Debug for Program {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"Program\").finish()\n    }\n}\n\n// See: https://github.com/tursodatabase/turso/issues/1552\n// SAFETY: Rust cannot derive Send + Sync automatically mainly because of `Row` struct\n// as it contains a `*const Register`.\n// Program + Program State upholds Rust aliasing rules with `Row` by only giving out immutable references to\n// the internal `result_row` and by invalidating the result row whenever the program is stepped.\nunsafe impl Send for ProgramState {}\nunsafe impl Sync for ProgramState {}\ncrate::assert::assert_send_sync!(ProgramState);\n\nimpl ProgramState {\n    pub fn new(max_registers: usize, max_cursors: usize) -> Self {\n        let cursors: Vec<Option<Cursor>> = (0..max_cursors).map(|_| None).collect();\n        let cursor_seqs = vec![0i64; max_cursors];\n        let registers = vec![Register::Value(Value::Null); max_registers].into_boxed_slice();\n        Self {\n            io_completions: None,\n            pc: 0,\n            cursors,\n            cursor_seqs,\n            registers,\n            result_row: None,\n            last_compare: None,\n            deferred_seeks: vec![None; max_cursors],\n            ended_coroutine: vec![],\n            once: SmallVec::<[u32; 4]>::new(),\n            execution_state: ProgramExecutionState::Init,\n            parameters: Vec::new(),\n            commit_state: CommitState::Ready,\n            #[cfg(feature = \"json\")]\n            json_cache: JsonCacheCell::new(),\n            op_delete_state: OpDeleteState {\n                sub_state: OpDeleteSubState::MaybeCaptureRecord,\n                deleted_record: None,\n            },\n            op_destroy_state: OpDestroyState::CreateCursor,\n            op_idx_delete_state: None,\n            op_integrity_check_state: OpIntegrityCheckState::Start,\n            metrics: StatementMetrics::new(),\n            op_open_ephemeral_state: OpOpenEphemeralState::Start,\n            op_program_state: OpProgramState::Start,\n            op_new_rowid_state: OpNewRowidState::Start,\n            op_idx_insert_state: OpIdxInsertState::MaybeSeek,\n            op_insert_state: OpInsertState {\n                sub_state: OpInsertSubState::MaybeCaptureRecord,\n                old_record: None,\n                is_noop_update: false,\n            },\n            op_no_conflict_state: OpNoConflictState::Start,\n            op_hash_build_state: None,\n            op_hash_probe_state: None,\n            distinct_key_values: Vec::new(),\n            seek_state: OpSeekState::Start,\n            current_collation: None,\n            op_column_state: OpColumnState::Start,\n            op_row_id_state: OpRowIdState::Start,\n            op_transaction_state: OpTransactionState::Start,\n            op_journal_mode_state: OpJournalModeState::default(),\n            op_vacuum_into_state: None,\n            view_delta_state: ViewDeltaCommitState::NotStarted,\n            auto_txn_cleanup: TxnCleanup::None,\n            fk_deferred_violations_when_stmt_started: AtomicIsize::new(0),\n            fk_immediate_violations_during_stmt: AtomicIsize::new(0),\n            rowsets: HashMap::default(),\n            bloom_filters: HashMap::default(),\n            hash_tables: HashMap::default(),\n            uses_subjournal: false,\n            is_active_write: false,\n            has_stmt_transaction: false,\n            attached_savepoint_pagers: Vec::new(),\n            n_change: AtomicI64::new(0),\n            explain_state: RwLock::new(ExplainState::default()),\n            pending_fail_error: None,\n            pending_cdc_info: None,\n        }\n    }\n\n    pub fn set_register(&mut self, idx: usize, value: Register) {\n        self.registers[idx] = value;\n    }\n\n    pub fn get_register(&self, idx: usize) -> &Register {\n        &self.registers[idx]\n    }\n\n    pub fn column_count(&self) -> usize {\n        self.registers.len()\n    }\n\n    pub fn column(&self, i: usize) -> Option<String> {\n        Some(format!(\"{:?}\", self.registers[i]))\n    }\n\n    pub fn interrupt(&mut self) {\n        self.execution_state = ProgramExecutionState::Interrupting;\n    }\n\n    pub fn bind_at(&mut self, index: NonZero<usize>, value: Value) {\n        let i = index.get() - 1;\n        if i >= self.parameters.len() {\n            self.parameters.resize(i + 1, Value::Null);\n        }\n        let slot = &mut self.parameters[i];\n        match (slot, value) {\n            (Value::Null, Value::Null) => {}\n            (Value::Numeric(Numeric::Integer(existing)), Value::Numeric(Numeric::Integer(new))) => {\n                *existing = new\n            }\n            (Value::Numeric(Numeric::Float(existing)), Value::Numeric(Numeric::Float(new))) => {\n                *existing = new\n            }\n            (Value::Text(existing), Value::Text(new)) => existing.do_extend(&new),\n            (Value::Blob(existing), Value::Blob(new)) => existing.do_extend(&new),\n            (slot, value) => *slot = value,\n        }\n    }\n\n    pub fn clear_bindings(&mut self) {\n        self.parameters.clear();\n    }\n\n    pub fn get_parameter(&self, index: NonZero<usize>) -> Value {\n        let i = index.get() - 1;\n        self.parameters.get(i).cloned().unwrap_or(Value::Null)\n    }\n\n    pub fn reset(&mut self, max_registers: Option<usize>, max_cursors: Option<usize>) {\n        self.io_completions = None;\n        self.pc = 0;\n\n        if let Some(max_cursors) = max_cursors {\n            self.cursors.resize_with(max_cursors, || None);\n            self.cursor_seqs.resize(max_cursors, 0);\n            self.deferred_seeks.resize(max_cursors, None);\n        }\n        self.result_row = None;\n        if let Some(max_registers) = max_registers {\n            // into_vec and into_boxed_slice do not allocate\n            let mut registers = std::mem::take(&mut self.registers).into_vec();\n            // As we are dropping whatever is in the result row, we can be sure that no one is referencing values from `*const Register` inside `Row`.\n            registers.resize_with(max_registers, || Register::Value(Value::Null));\n            self.registers = registers.into_boxed_slice();\n        }\n        // reset cursors as they can have cached information which will be no longer relevant on next program execution\n        self.cursors.iter_mut().for_each(|c| {\n            let _ = c.take();\n        });\n        for r in self.registers.iter_mut() {\n            match r {\n                Register::Value(v) => *v = Value::Null,\n                _ => r.set_null(),\n            }\n        }\n        self.last_compare = None;\n        self.deferred_seeks.iter_mut().for_each(|s| *s = None);\n        self.ended_coroutine.clear();\n        self.once.clear();\n        self.execution_state = ProgramExecutionState::Init;\n        self.current_collation = None;\n        #[cfg(feature = \"json\")]\n        self.json_cache.clear();\n\n        // Reset state machines\n        self.op_delete_state = OpDeleteState {\n            sub_state: OpDeleteSubState::MaybeCaptureRecord,\n            deleted_record: None,\n        };\n        self.op_idx_delete_state = None;\n        self.op_integrity_check_state = OpIntegrityCheckState::Start;\n        self.metrics = StatementMetrics::new();\n        self.op_open_ephemeral_state = OpOpenEphemeralState::Start;\n        self.op_new_rowid_state = OpNewRowidState::Start;\n        self.op_idx_insert_state = OpIdxInsertState::MaybeSeek;\n        self.op_insert_state = OpInsertState {\n            sub_state: OpInsertSubState::MaybeCaptureRecord,\n            old_record: None,\n            is_noop_update: false,\n        };\n        self.op_no_conflict_state = OpNoConflictState::Start;\n        self.seek_state = OpSeekState::Start;\n        self.current_collation = None;\n        self.op_column_state = OpColumnState::Start;\n        self.op_row_id_state = OpRowIdState::Start;\n        self.commit_state = CommitState::Ready;\n        self.op_destroy_state = OpDestroyState::CreateCursor;\n        self.op_program_state = OpProgramState::Start;\n        self.op_transaction_state = OpTransactionState::Start;\n        self.op_journal_mode_state = OpJournalModeState::default();\n        self.op_vacuum_into_state = None;\n        self.view_delta_state = ViewDeltaCommitState::NotStarted;\n        self.auto_txn_cleanup = TxnCleanup::None;\n        self.fk_immediate_violations_during_stmt\n            .store(0, Ordering::SeqCst);\n        self.fk_deferred_violations_when_stmt_started\n            .store(0, Ordering::SeqCst);\n        self.rowsets.clear();\n        self.bloom_filters.clear();\n        self.hash_tables.clear();\n        self.op_hash_build_state = None;\n        self.op_hash_probe_state = None;\n        self.uses_subjournal = false;\n        self.is_active_write = false;\n        self.has_stmt_transaction = false;\n        self.distinct_key_values.clear();\n        self.attached_savepoint_pagers.clear();\n        self.n_change.store(0, Ordering::SeqCst);\n        *self.explain_state.write() = ExplainState::default();\n        self.pending_fail_error = None;\n        self.pending_cdc_info = None;\n    }\n\n    pub fn get_cursor(&mut self, cursor_id: CursorID) -> &mut Cursor {\n        self.cursors\n            .get_mut(cursor_id)\n            .unwrap_or_else(|| panic!(\"cursor id {cursor_id} out of bounds\"))\n            .as_mut()\n            .unwrap_or_else(|| panic!(\"cursor id {cursor_id} is None\"))\n    }\n\n    /// Begin a statement subtransaction.\n    ///\n    /// Creates a savepoint on the main DB's MvStore (or pager for WAL mode),\n    /// and snapshots FK violation counters for potential statement rollback.\n    /// Attached DB savepoints are opened per-DB in `op_transaction_inner`\n    /// when each DB's Transaction opcode is executed.\n    ///\n    /// Pager/MVCC savepoints are only opened for write statements inside an\n    /// explicit transaction. In autocommit mode, a statement abort is a\n    /// transaction abort, so savepoints are unnecessary.\n    pub fn begin_statement(\n        &mut self,\n        connection: &Connection,\n        pager: &Arc<Pager>,\n        write: bool,\n    ) -> Result<IOResult<()>> {\n        let in_explicit_txn = !connection.auto_commit.load(Ordering::SeqCst);\n        if write && in_explicit_txn {\n            // Check if MVCC is active - if so, use MVCC savepoints instead of pager savepoints\n            if let Some(mv_store) = connection.mv_store().as_ref() {\n                if let Some(tx_id) = connection.get_mv_tx_id() {\n                    mv_store.begin_savepoint(tx_id);\n                }\n            } else {\n                // Non-MVCC mode: use pager savepoints\n                let db_size = return_if_io!(pager.with_header(|header| header.database_size.get()));\n                pager.open_subjournal()?;\n                pager.try_use_subjournal()?;\n                let result = pager.open_savepoint(db_size);\n                if result.is_err() {\n                    pager.stop_use_subjournal();\n                }\n                result?;\n                self.uses_subjournal = true;\n            }\n        }\n\n        self.has_stmt_transaction = true;\n\n        // Store the deferred foreign key violations counter at the start of the statement.\n        // This is used to ensure that if an interactive transaction had deferred FK violations and a statement subtransaction rolls back,\n        // the deferred FK violations are not lost.\n        self.fk_deferred_violations_when_stmt_started.store(\n            connection.fk_deferred_violations.load(Ordering::Acquire),\n            Ordering::SeqCst,\n        );\n        // Reset the immediate foreign key violations counter to 0. If this is nonzero when the statement completes, the statement subtransaction will roll back.\n        self.fk_immediate_violations_during_stmt\n            .store(0, Ordering::Release);\n        Ok(IOResult::Done(()))\n    }\n\n    /// End a statement subtransaction.\n    ///\n    /// Mirrors SQLite's vdbeCloseStatement (vdbeaux.c:3203-3248). Pager/MVCC\n    /// savepoint management and FK violation counter restoration are independent\n    /// concerns: pager savepoints may be skipped (e.g. autocommit optimization)\n    /// while FK bookkeeping still needs cleanup.\n    pub fn end_statement(\n        &mut self,\n        connection: &Connection,\n        pager: &Arc<Pager>,\n        end_statement: EndStatement,\n    ) -> Result<()> {\n        if self.is_active_write {\n            connection.n_active_writes.fetch_sub(1, Ordering::SeqCst);\n            self.is_active_write = false;\n        }\n        // If begin_statement was never called, no savepoint/FK cleanup needed.\n        if !self.has_stmt_transaction {\n            return Ok(());\n        }\n        self.has_stmt_transaction = false;\n\n        // Drain attached pagers upfront so we can clean them up regardless of path.\n        let attached_pagers: Vec<Arc<Pager>> = self.attached_savepoint_pagers.drain(..).collect();\n        let result = match end_statement {\n            EndStatement::ReleaseSavepoint => {\n                if let Some(mv_store) = connection.mv_store().as_ref() {\n                    if let Some(tx_id) = connection.get_mv_tx_id() {\n                        mv_store.release_savepoint(tx_id);\n                    }\n                    connection.for_each_attached_mv_tx(|db_id, tx_id| {\n                        if let Some(attached_mv) = connection.mv_store_for_db(db_id) {\n                            attached_mv.release_savepoint(tx_id);\n                        }\n                    });\n                    Ok(())\n                } else if self.uses_subjournal {\n                    pager.release_savepoint()?;\n                    for p in &attached_pagers {\n                        p.release_savepoint()?;\n                    }\n                    Ok(())\n                } else {\n                    Ok(())\n                }\n            }\n            EndStatement::RollbackSavepoint => {\n                // Rollback pager/MVCC savepoint if one was opened.\n                let pager_err = if let Some(mv_store) = connection.mv_store().as_ref() {\n                    let mut err = None;\n                    if let Some(tx_id) = connection.get_mv_tx_id() {\n                        if let Err(e) = mv_store.rollback_first_savepoint(tx_id) {\n                            err = Some(e);\n                        }\n                    }\n                    connection.for_each_attached_mv_tx(|db_id, tx_id| {\n                        if let Some(attached_mv) = connection.mv_store_for_db(db_id) {\n                            if let Err(e) = attached_mv.rollback_first_savepoint(tx_id) {\n                                if err.is_none() {\n                                    err = Some(e);\n                                }\n                            }\n                        }\n                    });\n                    err\n                } else if self.uses_subjournal {\n                    match pager.rollback_to_newest_savepoint() {\n                        Ok(_) => {\n                            let mut err = None;\n                            for p in &attached_pagers {\n                                if let Err(e) = p.rollback_to_newest_savepoint() {\n                                    err = Some(e);\n                                    break;\n                                }\n                            }\n                            err\n                        }\n                        Err(e) => Some(e),\n                    }\n                } else {\n                    None\n                };\n\n                // Always restore FK violation counters on statement rollback,\n                // regardless of whether a pager savepoint was opened.\n                // Mirrors SQLite's vdbeCloseStatement (vdbeaux.c:3243-3246).\n                connection.fk_deferred_violations.store(\n                    self.fk_deferred_violations_when_stmt_started\n                        .load(Ordering::Acquire),\n                    Ordering::SeqCst,\n                );\n\n                match pager_err {\n                    Some(e) => Err(e),\n                    None => Ok(()),\n                }\n            }\n        };\n        if self.uses_subjournal {\n            pager.stop_use_subjournal();\n            self.uses_subjournal = false;\n        }\n        for p in &attached_pagers {\n            p.stop_use_subjournal();\n        }\n        result\n    }\n\n    /// Gets or creates a bloom filter for the given cursor ID.\n    pub fn get_or_create_bloom_filter(&mut self, cursor_id: usize) -> &mut BloomFilter {\n        self.bloom_filters.entry(cursor_id).or_default()\n    }\n\n    /// Gets or creates a bloom filter with a specific capacity for the given cursor ID.\n    pub fn get_or_create_bloom_filter_with_capacity(\n        &mut self,\n        cursor_id: usize,\n        expected_items: u32,\n        false_positive_rate: f32,\n    ) -> &mut BloomFilter {\n        self.bloom_filters\n            .entry(cursor_id)\n            .or_insert_with(|| BloomFilter::with_capacity(expected_items, false_positive_rate))\n    }\n\n    /// Gets an existing bloom filter for the given cursor ID.\n    pub fn get_bloom_filter(&self, cursor_id: usize) -> Option<&BloomFilter> {\n        self.bloom_filters.get(&cursor_id)\n    }\n\n    /// Gets a mutable reference to an existing bloom filter for the given cursor ID.\n    pub fn get_bloom_filter_mut(&mut self, cursor_id: usize) -> Option<&mut BloomFilter> {\n        self.bloom_filters.get_mut(&cursor_id)\n    }\n\n    /// Removes and drops the bloom filter for the given cursor ID.\n    pub fn remove_bloom_filter(&mut self, cursor_id: usize) {\n        self.bloom_filters.remove(&cursor_id);\n    }\n\n    /// Checks if a bloom filter exists for the given cursor ID.\n    pub fn has_bloom_filter(&self, cursor_id: usize) -> bool {\n        self.bloom_filters.contains_key(&cursor_id)\n    }\n\n    pub fn get_fk_immediate_violations_during_stmt(&self) -> isize {\n        self.fk_immediate_violations_during_stmt\n            .load(Ordering::Acquire)\n    }\n\n    pub fn increment_fk_immediate_violations_during_stmt(&self, v: isize) {\n        self.fk_immediate_violations_during_stmt\n            .fetch_add(v, Ordering::AcqRel);\n    }\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\n/// Action to take at the end of a statement subtransaction.\npub enum EndStatement {\n    /// Release (commit) the savepoint -- effectively removing the savepoint as it is no longer needed for undo purposes.\n    ReleaseSavepoint,\n    /// Rollback (abort) to the newest savepoint: read pages from the subjournal and restore them to the page cache.\n    /// This is used to undo the changes made by the statement.\n    RollbackSavepoint,\n}\n\nimpl Register {\n    pub fn get_value(&self) -> &Value {\n        match self {\n            Register::Value(v) => v,\n            Register::Record(r) => {\n                turso_assert!(!r.is_invalidated());\n                r.as_blob_value()\n            }\n            _ => panic!(\"register holds unexpected value: {self:?}\"),\n        }\n    }\n}\n\n#[macro_export]\nmacro_rules! must_be_btree_cursor {\n    ($cursor_id:expr, $cursor_ref:expr, $state:expr, $insn_name:expr) => {{\n        let (_, cursor_type) = $cursor_ref.get($cursor_id).unwrap();\n        if matches!(\n            cursor_type,\n            CursorType::BTreeTable(_)\n                | CursorType::BTreeIndex(_)\n                | CursorType::MaterializedView(_, _)\n        ) {\n            $crate::get_cursor!($state, $cursor_id)\n        } else {\n            panic!(\"{} on unexpected cursor\", $insn_name)\n        }\n    }};\n}\n\n/// Macro is necessary to help the borrow checker see we are only accessing state.cursor field\n/// and nothing else\n#[macro_export]\nmacro_rules! get_cursor {\n    ($state:expr, $cursor_id:expr) => {\n        $state\n            .cursors\n            .get_mut($cursor_id)\n            .unwrap_or_else(|| panic!(\"cursor id {} out of bounds\", $cursor_id))\n            .as_mut()\n            .unwrap_or_else(|| panic!(\"cursor id {} is None\", $cursor_id))\n    };\n}\n\n/// Tracks the state of explain mode execution, including which subprograms need to be processed.\n#[derive(Default)]\npub struct ExplainState {\n    /// Subprograms queued for explain output, processed after the parent program finishes.\n    pending: std::collections::VecDeque<Arc<PreparedProgram>>,\n    /// The subprogram currently being explained, if any.\n    current: Option<Arc<PreparedProgram>>,\n}\n\n#[derive(Debug, Clone)]\npub struct PreparedProgram {\n    pub max_registers: usize,\n    // we store original indices because we don't want to create new vec from\n    // ProgramBuilder\n    pub insns: Vec<(Insn, usize)>,\n    pub cursor_ref: Vec<(Option<CursorKey>, CursorType)>,\n    pub comments: Vec<(InsnReference, &'static str)>,\n    pub parameters: crate::parameters::Parameters,\n    pub change_cnt_on: bool,\n    pub result_columns: Vec<ResultSetColumn>,\n    pub table_references: TableReferences,\n    pub sql: String,\n    /// Whether the statement needs to be wrapped in a statement subtransaction\n    /// when run as part of an interactive (non-autocommit) transaction.\n    /// See [crate::vdbe::builder::ProgramBuilder::is_multi_write] and [crate::vdbe::builder::ProgramBuilder::may_abort] for more details.\n    pub needs_stmt_subtransactions: Arc<AtomicBool>,\n    /// If this Program is a trigger subprogram, a ref to the trigger is stored here.\n    pub trigger: Option<Arc<Trigger>>,\n    /// Whether this program is a subprogram (trigger or FK action) that runs within a parent statement.\n    pub is_subprogram: bool,\n    /// Whether the program contains any trigger subprograms.\n    pub contains_trigger_subprograms: bool,\n    pub resolve_type: ResolveType,\n    pub prepare_context: PrepareContext,\n    /// Set of attached database indices that need write transactions.\n    pub write_databases: HashSet<usize>,\n    /// Set of attached database indices that need read transactions.\n    pub read_databases: HashSet<usize>,\n}\n\n#[derive(Clone)]\npub struct Program {\n    pub(crate) prepared: Arc<PreparedProgram>,\n    pub connection: Arc<Connection>,\n}\n\n/// Captures connection settings at statement preparation time for cache invalidation.\n///\n/// This struct is used to detect when a cached prepared statement needs to be recompiled\n/// because relevant connection settings have changed. When `matches_connection()` returns\n/// false, the statement will be automatically reprepared before execution.\n///\n/// # Adding New Fields\n///\n/// If you add a new setting to `Connection` that affects statement compilation or execution,\n/// When adding a new connection setting that affects query compilation, you MUST call\n/// `bump_prepare_context_generation()` in its setter so that prepared statements know\n/// they need to be reprepared.\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct PrepareContext {\n    /// Identity check: the prepared statement must belong to the same database.\n    database_ptr: usize,\n    /// Generation counter snapshot taken at prepare time. Compared against the\n    /// connection's current generation to detect setting changes (pragmas,\n    /// attach/detach, extension registration, etc.) without rebuilding the full\n    /// context on every step.\n    generation: u64,\n}\n\nimpl PrepareContext {\n    pub fn from_connection(connection: &Connection) -> Self {\n        Self {\n            database_ptr: connection.database_ptr(),\n            generation: connection.prepare_context_generation(),\n        }\n    }\n\n    #[inline]\n    pub fn matches_connection(&self, connection: &Connection) -> bool {\n        self.database_ptr == connection.database_ptr()\n            && self.generation == connection.prepare_context_generation()\n    }\n}\n\nimpl PreparedProgram {\n    pub fn bind(self: Arc<Self>, connection: Arc<Connection>) -> Program {\n        Program {\n            prepared: self,\n            connection,\n        }\n    }\n\n    pub fn is_compatible_with(&self, connection: &Connection) -> bool {\n        self.prepare_context.matches_connection(connection)\n    }\n}\n\nimpl Program {\n    pub fn prepared(&self) -> &Arc<PreparedProgram> {\n        &self.prepared\n    }\n\n    pub fn from_prepared(prepared: Arc<PreparedProgram>, connection: Arc<Connection>) -> Self {\n        Self {\n            prepared,\n            connection,\n        }\n    }\n}\n\nimpl Program {\n    fn get_pager_from_database_index(&self, idx: &usize) -> Arc<Pager> {\n        self.connection.get_pager_from_database_index(idx)\n    }\n\n    pub fn step(\n        &self,\n        state: &mut ProgramState,\n        pager: &Arc<Pager>,\n        query_mode: QueryMode,\n        waker: Option<&Waker>,\n    ) -> Result<StepResult> {\n        state.execution_state = ProgramExecutionState::Running;\n        let result = match query_mode {\n            QueryMode::Normal => self.normal_step(state, pager, waker),\n            QueryMode::Explain => self.explain_step(state, pager),\n            QueryMode::ExplainQueryPlan => self.explain_query_plan_step(state, pager),\n        };\n        match &result {\n            Ok(StepResult::Done) => {\n                state.execution_state = ProgramExecutionState::Done;\n            }\n            Ok(StepResult::Interrupt) => {\n                state.execution_state = ProgramExecutionState::Interrupted;\n            }\n            Err(_) => {\n                state.execution_state = ProgramExecutionState::Failed;\n            }\n            _ => {}\n        }\n        result\n    }\n\n    fn explain_step(&self, state: &mut ProgramState, pager: &Arc<Pager>) -> Result<StepResult> {\n        turso_debug_assert!(state.column_count() == EXPLAIN_COLUMNS.len());\n        if self.connection.is_closed() {\n            let tx_state = self.connection.get_tx_state();\n            if let TransactionState::Write { .. } = tx_state {\n                pager.rollback_tx(&self.connection);\n            }\n            return Err(LimboError::InternalError(\"Connection closed\".to_string()));\n        }\n\n        if matches!(state.execution_state, ProgramExecutionState::Interrupting) {\n            return Ok(StepResult::Interrupt);\n        }\n\n        state.metrics.vm_steps = state.metrics.vm_steps.saturating_add(1);\n\n        let mut explain_state = state.explain_state.write();\n\n        // Advance to the next subprogram if the current one is finished\n        loop {\n            if let Some(ref current) = explain_state.current {\n                if (state.pc as usize) < current.insns.len() {\n                    break;\n                }\n            } else if (state.pc as usize) < self.insns.len() {\n                break;\n            }\n            // Current program is done, pop next subprogram from queue\n            if let Some(next) = explain_state.pending.pop_front() {\n                explain_state.current = Some(next);\n                state.pc = 0;\n            } else {\n                explain_state.current = None;\n                return Ok(StepResult::Done);\n            }\n        }\n\n        let pc = state.pc as usize;\n\n        // Explain the current instruction from the active program.\n        // We collect subprograms separately to avoid borrow conflicts with explain_state.\n        let (row, subprogram) = if let Some(ref current) = explain_state.current {\n            let (insn, _) = &current.insns[pc];\n            let sub = if let Insn::Program {\n                program: prepared, ..\n            } = insn\n            {\n                Some(prepared.clone())\n            } else {\n                None\n            };\n            let comment = current\n                .comments\n                .iter()\n                .find(|(offset, _)| *offset == state.pc)\n                .map(|(_, c)| *c);\n            (insn_to_row_with_comment(current, insn, comment), sub)\n        } else {\n            let (insn, _) = &self.insns[pc];\n            let sub = if let Insn::Program {\n                program: prepared, ..\n            } = insn\n            {\n                Some(prepared.clone())\n            } else {\n                None\n            };\n            let comment = self\n                .comments\n                .iter()\n                .find(|(offset, _)| *offset == state.pc)\n                .map(|(_, c)| *c);\n            (insn_to_row_with_comment(self, insn, comment), sub)\n        };\n        if let Some(sub) = subprogram {\n            explain_state.pending.push_back(sub);\n        }\n        let (opcode, p1, p2, p3, p4, p5, comment) = row;\n\n        state.registers[0].set_int(state.pc as i64);\n        state.registers[1].set_value(Value::from_text(opcode));\n        state.registers[2].set_int(p1);\n        state.registers[3].set_int(p2);\n        state.registers[4].set_int(p3);\n        state.registers[5].set_value(p4);\n        state.registers[6].set_int(p5);\n        state.registers[7].set_value(Value::from_text(comment));\n        state.result_row = Some(Row {\n            values: &state.registers[0] as *const Register,\n            count: EXPLAIN_COLUMNS.len(),\n        });\n        state.pc += 1;\n        Ok(StepResult::Row)\n    }\n\n    fn explain_query_plan_step(\n        &self,\n        state: &mut ProgramState,\n        pager: &Arc<Pager>,\n    ) -> Result<StepResult> {\n        turso_debug_assert!(state.column_count() == EXPLAIN_QUERY_PLAN_COLUMNS.len());\n        loop {\n            if self.connection.is_closed() {\n                // Connection is closed for whatever reason, rollback the transaction.\n                let state = self.connection.get_tx_state();\n                if let TransactionState::Write { .. } = state {\n                    pager.rollback_tx(&self.connection);\n                }\n                return Err(LimboError::InternalError(\"Connection closed\".to_string()));\n            }\n\n            if matches!(state.execution_state, ProgramExecutionState::Interrupting) {\n                return Ok(StepResult::Interrupt);\n            }\n\n            // FIXME: do we need this?\n            state.metrics.vm_steps = state.metrics.vm_steps.saturating_add(1);\n\n            if state.pc as usize >= self.insns.len() {\n                return Ok(StepResult::Done);\n            }\n\n            let Insn::Explain { p1, p2, detail } = &self.insns[state.pc as usize].0 else {\n                state.pc += 1;\n                continue;\n            };\n\n            state.registers[0].set_int(*p1 as i64);\n            state.registers[1] =\n                Register::Value(Value::from_i64(p2.as_ref().map(|p| *p).unwrap_or(0) as i64));\n            state.registers[2].set_int(0);\n            state.registers[3].set_value(Value::from_text(detail.clone()));\n            state.result_row = Some(Row {\n                values: &state.registers[0] as *const Register,\n                count: EXPLAIN_QUERY_PLAN_COLUMNS.len(),\n            });\n            state.pc += 1;\n            return Ok(StepResult::Row);\n        }\n    }\n\n    #[instrument(skip_all, level = Level::DEBUG)]\n    fn normal_step(\n        &self,\n        state: &mut ProgramState,\n        pager: &Arc<Pager>,\n        waker: Option<&Waker>,\n    ) -> Result<StepResult> {\n        let enable_tracing = tracing::enabled!(tracing::Level::TRACE);\n        loop {\n            if self.connection.is_closed() {\n                // Connection is closed for whatever reason, rollback the transaction.\n                let state = self.connection.get_tx_state();\n                if let TransactionState::Write { .. } = state {\n                    pager.rollback_tx(&self.connection);\n                }\n                return Err(LimboError::InternalError(\"Connection closed\".to_string()));\n            }\n            if matches!(state.execution_state, ProgramExecutionState::Interrupting) {\n                self.abort(pager, None, state)?;\n                return Ok(StepResult::Interrupt);\n            }\n\n            if let Some(io) = &state.io_completions {\n                if !io.finished() {\n                    io.set_waker(waker);\n                    return Ok(StepResult::IO);\n                }\n                if let Some(err) = io.get_error() {\n                    if pager.is_checkpointing() {\n                        // Wrap IO errors that occurred during checkpointing in CheckpointFailed error,\n                        // so that abort() knows not to try to rollback the transaction, because the transaction\n                        // is already durable in the WAL and hence committed.\n                        // This also lets the simulator know that it should shadow the results of the query because\n                        // the write itself succeeded.\n                        let checkpoint_err = LimboError::CheckpointFailed(err.to_string());\n                        tracing::error!(\"Checkpoint failed: {checkpoint_err}\");\n                        if let Err(abort_err) = self.abort(pager, Some(&checkpoint_err), state) {\n                            tracing::error!(\n                                \"Abort also failed during checkpoint error handling: {abort_err}\"\n                            );\n                        }\n                        return Err(checkpoint_err);\n                    }\n                    let err = err.into();\n                    if let Err(abort_err) = self.abort(pager, Some(&err), state) {\n                        tracing::error!(\"Abort failed during error handling: {abort_err}\");\n                    }\n                    return Err(err);\n                }\n                state.io_completions = None;\n            }\n            // invalidate row\n            let _ = state.result_row.take();\n            let (insn, _) = &self.insns[state.pc as usize];\n            let insn_function = insn.to_function();\n            if enable_tracing {\n                trace_insn(self, state.pc as InsnReference, insn);\n            }\n            // Always increment VM steps for every loop iteration\n            state.metrics.vm_steps = state.metrics.vm_steps.saturating_add(1);\n\n            match insn_function(self, state, insn, pager) {\n                Ok(InsnFunctionStepResult::Step) => {\n                    // Instruction completed, moving to next\n                    state.metrics.insn_executed = state.metrics.insn_executed.saturating_add(1);\n                }\n                Ok(InsnFunctionStepResult::Done) => {\n                    // Instruction completed execution\n                    state.metrics.insn_executed = state.metrics.insn_executed.saturating_add(1);\n                    state.auto_txn_cleanup = TxnCleanup::None;\n                    return Ok(StepResult::Done);\n                }\n                Ok(InsnFunctionStepResult::IO(io)) => {\n                    // Instruction not complete - waiting for I/O, will resume at same PC\n                    io.set_waker(waker);\n                    let is_yield = io.is_explicit_yield();\n                    if is_yield {\n                        // Yield: return control to the cooperative scheduler so\n                        // other connections can make progress (e.g. release a\n                        // contended lock). Don't store in io_completions —\n                        // yields aren't pending I/O, so the instruction will\n                        // simply re-execute on the next step.\n                        return Ok(StepResult::IO);\n                    }\n                    let finished = io.finished();\n                    state.io_completions = Some(io);\n                    if !finished {\n                        return Ok(StepResult::IO);\n                    }\n                    // just continue the outer loop if IO is finished so db will continue execution immediately\n                }\n                Ok(InsnFunctionStepResult::Row) => {\n                    // Instruction completed (ResultRow already incremented PC)\n                    state.metrics.insn_executed = state.metrics.insn_executed.saturating_add(1);\n                    return Ok(StepResult::Row);\n                }\n                Err(LimboError::Busy) => {\n                    // Instruction blocked - will retry at same PC\n                    return Ok(StepResult::Busy);\n                }\n                Err(LimboError::BusySnapshot)\n                    if self.connection.transaction_state.get() == TransactionState::None =>\n                {\n                    // For interactive transactions that are already in a read transaction, retrying BusySnapshot is pointless\n                    // because the snapshot will continue to be stale no matter how many times we retry.\n                    // However, for auto-commits or BEGIN IMMEDIATE, failing to promote to write transaction means it was rolled\n                    // back, so auto-retrying can be useful.\n                    return Ok(StepResult::Busy);\n                }\n                Err(err) => {\n                    if let Err(abort_err) = self.abort(pager, Some(&err), state) {\n                        tracing::error!(\"Abort failed during error handling: {abort_err}\");\n                    }\n                    return Err(err);\n                }\n            }\n        }\n    }\n\n    #[instrument(skip_all, level = Level::DEBUG)]\n    fn apply_view_deltas(\n        &self,\n        state: &mut ProgramState,\n        rollback: bool,\n        pager: &Arc<Pager>,\n    ) -> Result<IOResult<()>> {\n        use crate::types::IOResult;\n\n        loop {\n            match &state.view_delta_state {\n                ViewDeltaCommitState::NotStarted => {\n                    if self.connection.view_transaction_states.is_empty() {\n                        return Ok(IOResult::Done(()));\n                    }\n\n                    if rollback {\n                        // On rollback, just clear and done\n                        self.connection.view_transaction_states.clear();\n                        return Ok(IOResult::Done(()));\n                    }\n\n                    // Not a rollback - proceed with processing\n                    let schema = self.connection.schema.read();\n\n                    // Collect materialized views - they should all have storage\n                    let mut views = Vec::new();\n                    for view_name in self.connection.view_transaction_states.get_view_names() {\n                        if let Some(view_mutex) = schema.get_materialized_view(&view_name) {\n                            let view = view_mutex.lock();\n                            let root_page = view.get_root_page();\n\n                            // Materialized views should always have storage (root_page != 0)\n                            turso_assert_ne!(\n                                root_page, 0,\n                                \"Materialized view should have a root page\",\n                                { \"view_name\": view_name }\n                            );\n\n                            views.push(view_name);\n                        }\n                    }\n\n                    state.view_delta_state = ViewDeltaCommitState::Processing {\n                        views,\n                        current_index: 0,\n                    };\n                }\n\n                ViewDeltaCommitState::Processing {\n                    views,\n                    current_index,\n                } => {\n                    // At this point we know it's not a rollback\n                    if *current_index >= views.len() {\n                        // All done, clear the transaction states\n                        self.connection.view_transaction_states.clear();\n                        state.view_delta_state = ViewDeltaCommitState::Done;\n                        return Ok(IOResult::Done(()));\n                    }\n\n                    let view_name = &views[*current_index];\n\n                    let table_deltas = self\n                        .connection\n                        .view_transaction_states\n                        .get(view_name)\n                        .expect(\"view should have transaction state\")\n                        .get_table_deltas();\n\n                    let schema = self.connection.schema.read();\n                    if let Some(view_mutex) = schema.get_materialized_view(view_name) {\n                        let mut view = view_mutex.lock();\n\n                        // Create a DeltaSet from the per-table deltas\n                        let mut delta_set = crate::incremental::compiler::DeltaSet::new();\n                        for (table_name, delta) in table_deltas {\n                            delta_set.insert(table_name, delta);\n                        }\n\n                        // Handle I/O from merge_delta - pass pager, circuit will create its own cursor\n                        match view.merge_delta(delta_set, pager.clone())? {\n                            IOResult::Done(_) => {\n                                // Move to next view\n                                state.view_delta_state = ViewDeltaCommitState::Processing {\n                                    views: views.clone(),\n                                    current_index: current_index + 1,\n                                };\n                            }\n                            IOResult::IO(io) => {\n                                // Return I/O, will resume at same index\n                                return Ok(IOResult::IO(io));\n                            }\n                        }\n                    }\n                }\n\n                ViewDeltaCommitState::Done => {\n                    return Ok(IOResult::Done(()));\n                }\n            }\n        }\n    }\n\n    pub fn commit_txn(\n        &self,\n        pager: Arc<Pager>,\n        program_state: &mut ProgramState,\n        mv_store: Option<&Arc<MvStore>>,\n        rollback: bool,\n    ) -> Result<IOResult<()>> {\n        // Apply view deltas with I/O handling\n        match self.apply_view_deltas(program_state, rollback, &pager)? {\n            IOResult::IO(io) => return Ok(IOResult::IO(io)),\n            IOResult::Done(_) => {}\n        }\n\n        // Reset state for next use\n        program_state.view_delta_state = ViewDeltaCommitState::NotStarted;\n        let tx_state = self.connection.get_tx_state();\n        if tx_state == TransactionState::None\n            && matches!(program_state.commit_state, CommitState::Ready)\n        {\n            // No active transaction and no in-progress commit — nothing to do.\n            // Attached MVCC transactions are only started after the main DB's\n            // Transaction opcode runs, so tx_state==None implies no attached\n            // MVCC txs either.\n            return Ok(IOResult::Done(()));\n        }\n        if self.connection.is_nested_stmt() {\n            // We don't want to commit on nested statements. Let parent handle it.\n            return Ok(IOResult::Done(()));\n        }\n        let res = if let Some(mv_store) = mv_store {\n            self.commit_txn_mvcc(pager, program_state, mv_store, rollback)\n        } else {\n            self.commit_txn_wal(pager, program_state, rollback)\n        }?;\n        if !res.is_io() && self.change_cnt_on {\n            self.connection\n                .set_changes(program_state.n_change.load(Ordering::SeqCst));\n        }\n        Ok(res)\n    }\n\n    fn commit_txn_wal(\n        &self,\n        pager: Arc<Pager>,\n        program_state: &mut ProgramState,\n        rollback: bool,\n    ) -> Result<IOResult<()>> {\n        let connection = self.connection.clone();\n        let auto_commit = connection.auto_commit.load(Ordering::SeqCst);\n        let tx_state = connection.get_tx_state();\n        tracing::debug!(\n            \"Halt auto_commit {}, commit_state={:?}, tx_state={:?}\",\n            auto_commit,\n            program_state.commit_state,\n            tx_state,\n        );\n        if matches!(program_state.commit_state, CommitState::Committing) {\n            let TransactionState::Write { .. } = tx_state else {\n                unreachable!(\"invalid state for write commit step\")\n            };\n            self.step_end_write_txn(&pager, &connection, program_state, rollback)\n        } else if matches!(program_state.commit_state, CommitState::CommittingAttached) {\n            // Re-entry after IO yield from attached pager commit.\n            match self.end_attached_write_txns(&connection, rollback)? {\n                IOResult::Done(_) => {\n                    program_state.commit_state = CommitState::Ready;\n                    if pager.holds_read_lock() {\n                        pager.end_read_tx();\n                    }\n                    self.end_attached_read_txns(&connection);\n                    Ok(IOResult::Done(()))\n                }\n                IOResult::IO(io) => Ok(IOResult::IO(io)),\n            }\n        } else if auto_commit {\n            match tx_state {\n                TransactionState::Write { .. } => {\n                    self.step_end_write_txn(&pager, &connection, program_state, rollback)\n                }\n                TransactionState::Read => {\n                    connection.set_tx_state(TransactionState::None);\n                    // Commit any attached write transactions that were opened\n                    // independently of the main connection's transaction state.\n                    // (e.g., UPDATE aux0.t SET ... only needs Read on main DB\n                    // but holds a write lock on the attached pager.)\n                    match self.end_attached_write_txns(&connection, rollback)? {\n                        IOResult::Done(_) => {}\n                        IOResult::IO(io) => {\n                            program_state.commit_state = CommitState::CommittingAttached;\n                            return Ok(IOResult::IO(io));\n                        }\n                    }\n                    pager.end_read_tx();\n                    self.end_attached_read_txns(&connection);\n                    Ok(IOResult::Done(()))\n                }\n                TransactionState::None => Ok(IOResult::Done(())),\n                TransactionState::PendingUpgrade { .. } => {\n                    panic!(\"Unexpected transaction state: {tx_state:?} during auto-commit\",)\n                }\n            }\n        } else {\n            Ok(IOResult::Done(()))\n        }\n    }\n\n    /// Commit MVCC transactions across all databases in a multi-phase protocol:\n    ///\n    /// 1. **Main DB MVCC** — commit the main database's MvStore transaction.\n    /// 2. **Attached MVCC** — commit each attached database's MvStore transaction.\n    /// 3. **Attached WAL** — flush dirty pages on attached databases that use WAL\n    ///    (e.g. :memory: attached while main is MVCC).\n    ///\n    /// **IMPORTANT**: This multi-phase commit is NOT atomic across databases.\n    /// A crash between phases can leave the main and attached databases in\n    /// inconsistent states (main committed, some attached DBs not committed).\n    /// This matches SQLite's WAL mode behavior — cross-file atomicity only\n    /// exists in legacy rollback journal mode, which we do not support.\n    fn commit_txn_mvcc(\n        &self,\n        pager: Arc<Pager>,\n        program_state: &mut ProgramState,\n        mv_store: &Arc<MvStore>,\n        rollback: bool,\n    ) -> Result<IOResult<()>> {\n        let conn = self.connection.clone();\n        let auto_commit = conn.auto_commit.load(Ordering::SeqCst);\n        if !auto_commit {\n            return Ok(IOResult::Done(()));\n        }\n\n        // Phase 1: Commit main DB MVCC transaction\n        if matches!(program_state.commit_state, CommitState::Ready) {\n            if let Some(tx_id) = conn.get_mv_tx_id() {\n                let state_machine = mv_store.commit_tx(tx_id, &conn)?;\n                program_state.commit_state = CommitState::CommittingMvcc { state_machine };\n            }\n            // If no main MVCC tx, commit_state stays Ready and we fall\n            // through directly to phase 2 (the CommittingMvcc and\n            // CommittingAttachedMvcc checks will both miss).\n        }\n\n        if matches!(\n            program_state.commit_state,\n            CommitState::CommittingMvcc { .. }\n        ) {\n            let CommitState::CommittingMvcc { state_machine } = &mut program_state.commit_state\n            else {\n                unreachable!()\n            };\n            match self.step_end_mvcc_txn(state_machine, mv_store)? {\n                IOResult::Done(_) => {\n                    assert!(state_machine.is_finalized());\n                    conn.set_mv_tx(None);\n                    conn.set_tx_state(TransactionState::None);\n                    pager.end_read_tx();\n                    program_state.commit_state = CommitState::Ready;\n                    // Fall through to attached phase\n                }\n                IOResult::IO(io) => return Ok(IOResult::IO(io)),\n            }\n        }\n\n        // Phase 2: Commit MVCC transactions on attached databases\n        // Resume an in-progress attached MVCC commit\n        if matches!(\n            program_state.commit_state,\n            CommitState::CommittingAttachedMvcc { .. }\n        ) {\n            let (step_result, db_id) = {\n                let CommitState::CommittingAttachedMvcc {\n                    state_machine,\n                    db_id,\n                    mv_store: ref attached_mv,\n                } = &mut program_state.commit_state\n                else {\n                    unreachable!()\n                };\n                (state_machine.step(attached_mv)?, *db_id)\n            };\n            match step_result {\n                IOResult::Done(_) => {\n                    let attached_pager = conn.get_pager_from_database_index(&db_id);\n                    conn.publish_attached_schema(db_id);\n                    conn.set_mv_tx_for_db(db_id, None);\n                    attached_pager.end_read_tx();\n                    // Fall through to look for more\n                }\n                IOResult::IO(io) => return Ok(IOResult::IO(io)),\n            }\n        }\n\n        // Start/continue committing remaining attached MVCC transactions\n        loop {\n            let Some((db_id, tx_id, _mode)) = conn.next_attached_mv_tx() else {\n                break;\n            };\n            let Some(attached_mv_store) = conn.mv_store_for_db(db_id) else {\n                conn.set_mv_tx_for_db(db_id, None);\n                continue;\n            };\n            let mut state_machine = match attached_mv_store.commit_tx(tx_id, &conn) {\n                Ok(sm) => sm,\n                Err(e) => {\n                    tracing::error!(\n                        db_id,\n                        \"attached DB commit failed after main DB already committed; \\\n                         cross-database state is inconsistent: {e}\"\n                    );\n                    // Rollback remaining uncommitted attached MVCC transactions\n                    // so they don't block checkpointing until connection close.\n                    conn.rollback_attached_mvcc_txs(true);\n                    return Err(e);\n                }\n            };\n            match state_machine.step(&attached_mv_store)? {\n                IOResult::Done(_) => {\n                    let attached_pager = conn.get_pager_from_database_index(&db_id);\n                    conn.publish_attached_schema(db_id);\n                    conn.set_mv_tx_for_db(db_id, None);\n                    attached_pager.end_read_tx();\n                    continue;\n                }\n                IOResult::IO(io) => {\n                    program_state.commit_state = CommitState::CommittingAttachedMvcc {\n                        state_machine,\n                        db_id,\n                        mv_store: attached_mv_store,\n                    };\n                    return Ok(IOResult::IO(io));\n                }\n            }\n        }\n\n        // Phase 3: Commit WAL transactions on attached databases that don't use MVCC.\n        // When the main DB uses MVCC, we route through commit_txn_mvcc, but attached\n        // DBs may use WAL mode and need their dirty pages committed via the WAL path.\n        if matches!(program_state.commit_state, CommitState::CommittingAttached) {\n            // Re-entry after IO yield from attached WAL pager commit.\n            match self.end_attached_write_txns(&conn, rollback)? {\n                IOResult::Done(_) => {\n                    program_state.commit_state = CommitState::Ready;\n                    self.end_attached_read_txns(&conn);\n                    return Ok(IOResult::Done(()));\n                }\n                IOResult::IO(io) => return Ok(IOResult::IO(io)),\n            }\n        }\n\n        match self.end_attached_write_txns(&conn, rollback)? {\n            IOResult::Done(_) => {}\n            IOResult::IO(io) => {\n                program_state.commit_state = CommitState::CommittingAttached;\n                return Ok(IOResult::IO(io));\n            }\n        }\n        self.end_attached_read_txns(&conn);\n\n        program_state.commit_state = CommitState::Ready;\n        Ok(IOResult::Done(()))\n    }\n\n    #[instrument(skip(self, pager, connection, program_state), level = Level::DEBUG)]\n    fn step_end_write_txn(\n        &self,\n        pager: &Arc<Pager>,\n        connection: &Connection,\n        program_state: &mut ProgramState,\n        rollback: bool,\n    ) -> Result<IOResult<()>> {\n        let commit_state = &mut program_state.commit_state;\n        if matches!(commit_state, CommitState::CommittingAttached) {\n            // Resume committing attached pagers after IO yield.\n            match self.end_attached_write_txns(connection, rollback)? {\n                IOResult::Done(_) => {\n                    *commit_state = CommitState::Ready;\n                }\n                IOResult::IO(io) => {\n                    return Ok(IOResult::IO(io));\n                }\n            }\n            // Release read locks on attached pagers that only had read transactions\n            // (end_attached_write_txns only handles pagers with write locks).\n            self.end_attached_read_txns(connection);\n            return Ok(IOResult::Done(()));\n        }\n        let txn_finish_result = if !rollback {\n            pager.commit_tx(connection, true)\n        } else {\n            pager.rollback_tx(connection);\n            Ok(IOResult::Done(()))\n        };\n        tracing::debug!(\"txn_finish_result: {:?}\", txn_finish_result);\n        match txn_finish_result? {\n            IOResult::Done(_) => {\n                // Main pager commit done, now commit attached database pagers\n                match self.end_attached_write_txns(connection, rollback)? {\n                    IOResult::Done(_) => {\n                        *commit_state = CommitState::Ready;\n                    }\n                    IOResult::IO(io) => {\n                        *commit_state = CommitState::CommittingAttached;\n                        return Ok(IOResult::IO(io));\n                    }\n                }\n            }\n            IOResult::IO(io) => {\n                tracing::trace!(\"Cacheflush IO\");\n                *commit_state = CommitState::Committing;\n                return Ok(IOResult::IO(io));\n            }\n        }\n        // Release read locks on attached pagers that only had read transactions\n        // (end_attached_write_txns only handles pagers with write locks).\n        self.end_attached_read_txns(connection);\n        Ok(IOResult::Done(()))\n    }\n\n    /// End write transactions on all attached databases that hold write locks.\n    /// Iterates ALL attached pagers (not just the current program's write_databases)\n    /// because in explicit transactions, the COMMIT statement's program may differ\n    /// from the statement that acquired the attached write lock.\n    /// On IO yield, already-committed pagers are skipped on re-entry via holds_write_lock().\n    fn end_attached_write_txns(\n        &self,\n        connection: &Connection,\n        rollback: bool,\n    ) -> Result<IOResult<()>> {\n        let pagers = connection.get_all_attached_pagers_with_index();\n        for (db_id, attached_pager) in pagers {\n            // MVCC-enabled attached DBs are committed in commit_txn_mvcc phase 2\n            if connection.mv_store_for_db(db_id).is_some() {\n                continue;\n            }\n            if !attached_pager.holds_write_lock() {\n                continue;\n            }\n            if !rollback {\n                // Commit dirty pages to WAL, then end write+read transactions.\n                // We disable auto-checkpoint and avoid pager.commit_tx() since\n                // the checkpoint logic can leave read locks held.\n                match attached_pager.commit_dirty_pages(true, SyncMode::Normal, false) {\n                    Ok(IOResult::Done(_)) => {}\n                    Ok(IOResult::IO(io)) => {\n                        // IO pending — return so the caller can yield and re-enter.\n                        // commit_dirty_pages tracks its own internal state, so calling\n                        // it again on re-entry will resume correctly.\n                        return Ok(IOResult::IO(io));\n                    }\n                    Err(e) => return Err(e),\n                }\n                // WAL commit succeeded — publish the connection-local schema\n                // changes to the shared Database so other connections can see them.\n                connection.publish_attached_schema(db_id);\n                attached_pager.end_write_tx();\n                attached_pager.end_read_tx();\n                attached_pager.commit_dirty_pages_end();\n            } else {\n                // Discard any local schema changes on rollback\n                connection.database_schemas().write().remove(&db_id);\n                attached_pager.rollback_attached();\n            }\n        }\n        Ok(IOResult::Done(()))\n    }\n\n    /// End read transactions on all attached databases that had transactions started.\n    fn end_attached_read_txns(&self, connection: &Connection) {\n        for attached_pager in connection.get_all_attached_pagers() {\n            if attached_pager.holds_read_lock() {\n                attached_pager.end_read_tx();\n            }\n        }\n    }\n\n    #[instrument(skip(self, commit_state, mv_store), level = Level::DEBUG)]\n    fn step_end_mvcc_txn(\n        &self,\n        commit_state: &mut StateMachine<CommitStateMachine<MvccClock>>,\n        mv_store: &Arc<MvStore>,\n    ) -> Result<IOResult<()>> {\n        commit_state.step(mv_store)\n    }\n\n    /// Aborts the program due to various conditions (explicit error, interrupt or reset of unfinished statement) by rolling back the transaction\n    /// This method is no-op if program was already finished (either aborted or executed to completion)\n    /// Returns an error if cleanup operations (savepoint rollback/release) fail.\n    pub fn abort(\n        &self,\n        pager: &Arc<Pager>,\n        err: Option<&LimboError>,\n        state: &mut ProgramState,\n    ) -> Result<()> {\n        fn capture_abort_error(\n            abort_error: &mut Option<LimboError>,\n            err: LimboError,\n            context: &str,\n        ) {\n            tracing::error!(\"{context}: {err}\");\n            if abort_error.is_none() {\n                *abort_error = Some(err);\n            }\n        }\n\n        let mut abort_error: Option<LimboError> = None;\n\n        if self.is_trigger_subprogram() {\n            self.connection.end_trigger_execution();\n        }\n        // Errors from nested statements are handled by the parent statement.\n        if !self.connection.is_nested_stmt() && !self.is_trigger_subprogram() {\n            if err.is_some() && !pager.is_checkpointing() {\n                // For ON CONFLICT FAIL, do NOT rollback the statement savepoint —\n                // changes made before the error should persist.\n                // For all other resolve types (ABORT, ROLLBACK, etc.), rollback the statement.\n                let is_fail_constraint = (matches!(err, Some(LimboError::Constraint(_)))\n                    && self.resolve_type == ResolveType::Fail)\n                    || matches!(err, Some(LimboError::Raise(ResolveType::Fail, _)));\n                if !is_fail_constraint {\n                    if let Err(end_stmt_err) = state.end_statement(\n                        &self.connection,\n                        pager,\n                        EndStatement::RollbackSavepoint,\n                    ) {\n                        capture_abort_error(\n                            &mut abort_error,\n                            end_stmt_err,\n                            \"Failed to rollback statement savepoint during abort\",\n                        );\n                    }\n                }\n            }\n            match err {\n                // Transaction errors, e.g. trying to start a nested transaction, do not cause a rollback.\n                Some(LimboError::TxError(_)) => {}\n                // Table locked errors, e.g. trying to checkpoint in an interactive transaction, do not cause a rollback.\n                Some(LimboError::TableLocked) => {}\n                // Busy errors do not cause a rollback.\n                Some(LimboError::Busy) => {}\n                // BusySnapshot errors do not cause a rollback either - user must rollback explicitly.\n                // BusySnapshot is distinct from Busy in that a busy_timeout or handler should not be\n                // used because it will not help - the snapshot is permanently stale and rollback is\n                // the only way out for this poor transaction.\n                Some(LimboError::BusySnapshot) => {}\n                // Schema updated errors do not cause a rollback; the statement will be reprepared and retried,\n                // and the caller is expected to handle transaction cleanup explicitly if needed.\n                Some(LimboError::SchemaUpdated) => {}\n                // Foreign key constraint errors: ON CONFLICT does NOT apply to FK violations.\n                // FK errors always behave like ABORT: rollback statement,\n                // rollback transaction in autocommit mode.\n                Some(LimboError::ForeignKeyConstraint(_)) => {\n                    if self.connection.get_auto_commit() {\n                        self.rollback_current_txn(pager);\n                    }\n                }\n                // Constraint and RAISE errors: behavior depends on the effective resolve type.\n                // For normal constraints, the resolve type comes from the statement (ON CONFLICT).\n                // For RAISE errors, the resolve type is embedded in the error variant itself.\n                // - ROLLBACK: rollback the entire transaction regardless of autocommit mode\n                // - FAIL: don't rollback anything - changes persist, transaction stays active\n                // - ABORT (default): rollback statement, rollback txn if autocommit\n                Some(LimboError::Constraint(_)) | Some(LimboError::Raise(_, _)) => {\n                    let effective_resolve = match err {\n                        Some(LimboError::Raise(rt, _)) => *rt,\n                        _ => self.resolve_type,\n                    };\n                    match effective_resolve {\n                        ResolveType::Rollback => {\n                            self.rollback_current_txn(pager);\n                            // All deferred FK violations are undone by the full rollback.\n                            self.connection.clear_deferred_foreign_key_violations();\n                        }\n                        ResolveType::Fail => {\n                            // FAIL: Don't rollback the transaction.\n                            // Changes made before the error persist.\n                            if let Err(end_stmt_err) = state.end_statement(\n                                &self.connection,\n                                pager,\n                                EndStatement::ReleaseSavepoint,\n                            ) {\n                                capture_abort_error(\n                                    &mut abort_error,\n                                    end_stmt_err,\n                                    \"Failed to release statement savepoint during abort\",\n                                );\n                            }\n                            if self.connection.get_auto_commit() {\n                                // Autocommit FAIL: commit partial changes.\n                                // This matches halt()'s FAIL+autocommit path.\n                                let mv_store = self.connection.mv_store();\n                                if let Err(e) = execute::vtab_commit_all(&self.connection) {\n                                    capture_abort_error(\n                                        &mut abort_error,\n                                        e,\n                                        \"vtab_commit_all failed during FAIL abort\",\n                                    );\n                                }\n                                if let Err(e) = execute::index_method_pre_commit_all(state, pager) {\n                                    capture_abort_error(\n                                        &mut abort_error,\n                                        e,\n                                        \"index_method_pre_commit_all failed during FAIL abort\",\n                                    );\n                                }\n                                loop {\n                                    match self.commit_txn(\n                                        pager.clone(),\n                                        state,\n                                        mv_store.as_ref(),\n                                        false,\n                                    ) {\n                                        Ok(IOResult::Done(_)) => break,\n                                        Ok(IOResult::IO(io)) => {\n                                            if let Err(e) = io.wait(pager.io.as_ref()) {\n                                                capture_abort_error(\n                                                    &mut abort_error,\n                                                    e,\n                                                    \"IO error during FAIL commit in abort\",\n                                                );\n                                                break;\n                                            }\n                                        }\n                                        Err(e) => {\n                                            capture_abort_error(\n                                                &mut abort_error,\n                                                e,\n                                                \"commit_txn failed during FAIL abort\",\n                                            );\n                                            break;\n                                        }\n                                    }\n                                }\n                            }\n                        }\n                        _ => {\n                            if self.connection.get_auto_commit() {\n                                self.rollback_current_txn(pager);\n                            }\n                        }\n                    }\n                }\n                Some(LimboError::RaiseIgnore) => {\n                    tracing::error!(\n                        \"BUG: RaiseIgnore reached abort() - should be caught by op_program\"\n                    );\n                    debug_assert!(\n                        false,\n                        \"RaiseIgnore should be caught by op_program, not reach abort\"\n                    );\n                }\n                _ => {\n                    if state.auto_txn_cleanup != TxnCleanup::None || err.is_some() {\n                        self.rollback_current_txn(pager);\n                    }\n                }\n            }\n        }\n        if state.uses_subjournal {\n            pager.stop_use_subjournal();\n            state.uses_subjournal = false;\n        }\n        state.auto_txn_cleanup = TxnCleanup::None;\n        if let Some(err) = abort_error {\n            return Err(err);\n        }\n        Ok(())\n    }\n\n    fn rollback_current_txn(&self, pager: &Arc<Pager>) {\n        if let Some(mv_store) = self.connection.mv_store().as_ref() {\n            if let Some(tx_id) = self.connection.get_mv_tx_id() {\n                self.connection.auto_commit.store(true, Ordering::SeqCst);\n                mv_store.rollback_tx(tx_id, pager.clone(), &self.connection, crate::MAIN_DB_ID);\n            }\n            pager.end_read_tx();\n            self.connection.rollback_attached_mvcc_txs(true);\n        } else {\n            pager.rollback_tx(&self.connection);\n            self.connection.auto_commit.store(true, Ordering::SeqCst);\n        }\n        self.connection.rollback_attached_wal_txns();\n        self.connection.set_tx_state(TransactionState::None);\n    }\n\n    pub fn is_trigger_subprogram(&self) -> bool {\n        self.trigger.is_some() || self.is_subprogram\n    }\n}\n\nimpl Deref for Program {\n    type Target = PreparedProgram;\n\n    fn deref(&self) -> &PreparedProgram {\n        &self.prepared\n    }\n}\n\npub(crate) fn make_record(\n    registers: &[Register],\n    start_reg: &usize,\n    count: &usize,\n) -> ImmutableRecord {\n    let regs = &registers[*start_reg..*start_reg + *count];\n    ImmutableRecord::from_registers(regs, regs.len())\n}\n\npub fn registers_to_ref_values<'a>(\n    registers: &'a [Register],\n) -> impl ExactSizeIterator<Item = ValueRef<'a>> {\n    registers.iter().map(|reg| reg.get_value().as_ref())\n}\n\n#[instrument(skip(program), level = Level::DEBUG)]\nfn trace_insn(program: &Program, addr: InsnReference, insn: &Insn) {\n    tracing::trace!(\n        \"\\n{}\",\n        explain::insn_to_str(\n            program,\n            addr,\n            insn,\n            String::new(),\n            program\n                .comments\n                .iter()\n                .find(|(offset, _)| *offset == addr)\n                .map(|(_, comment)| comment)\n                .copied()\n        )\n    );\n}\n\npub trait FromValueRow<'a> {\n    fn from_value(value: &'a Value) -> Result<Self>\n    where\n        Self: Sized + 'a;\n}\n\nimpl<'a> FromValueRow<'a> for i64 {\n    fn from_value(value: &'a Value) -> Result<Self> {\n        match value {\n            Value::Numeric(Numeric::Integer(i)) => Ok(*i),\n            _ => Err(LimboError::ConversionError(\"Expected integer value\".into())),\n        }\n    }\n}\n\nimpl<'a> FromValueRow<'a> for f64 {\n    fn from_value(value: &'a Value) -> Result<Self> {\n        match value {\n            Value::Numeric(Numeric::Float(f)) => Ok(f64::from(*f)),\n            _ => Err(LimboError::ConversionError(\"Expected integer value\".into())),\n        }\n    }\n}\n\nimpl<'a> FromValueRow<'a> for String {\n    fn from_value(value: &'a Value) -> Result<Self> {\n        match value {\n            Value::Text(s) => Ok(s.as_str().to_string()),\n            _ => Err(LimboError::ConversionError(\"Expected text value\".into())),\n        }\n    }\n}\n\nimpl<'a> FromValueRow<'a> for &'a str {\n    fn from_value(value: &'a Value) -> Result<Self> {\n        match value {\n            Value::Text(s) => Ok(s.as_str()),\n            _ => Err(LimboError::ConversionError(\"Expected text value\".into())),\n        }\n    }\n}\n\nimpl<'a> FromValueRow<'a> for &'a Value {\n    fn from_value(value: &'a Value) -> Result<Self> {\n        Ok(value)\n    }\n}\n\nimpl Row {\n    pub fn get<'a, T: FromValueRow<'a> + 'a>(&'a self, idx: usize) -> Result<T> {\n        let value = unsafe {\n            self.values\n                .add(idx)\n                .as_ref()\n                .expect(\"row value pointer should be valid\")\n        };\n        let value = match value {\n            Register::Value(value) => value,\n            _ => unreachable!(\"a row should be formed of values only\"),\n        };\n        T::from_value(value)\n    }\n\n    pub fn get_value(&self, idx: usize) -> &Value {\n        let value = unsafe {\n            self.values\n                .add(idx)\n                .as_ref()\n                .expect(\"row value pointer should be valid\")\n        };\n        match value {\n            Register::Value(value) => value,\n            _ => unreachable!(\"a row should be formed of values only\"),\n        }\n    }\n\n    pub fn get_values(&self) -> impl Iterator<Item = &Value> {\n        let values = unsafe { std::slice::from_raw_parts(self.values, self.count) };\n        // This should be ownedvalues\n        // TODO: add check for this\n        values.iter().map(|v| v.get_value())\n    }\n\n    pub fn len(&self) -> usize {\n        self.count\n    }\n\n    pub fn is_empty(&self) -> bool {\n        self.count == 0\n    }\n}\n\n/// Extension trait for `ValueIterator` that allows writing directly to a `Register`\n/// without allocating intermediate `ValueRef` values.\npub trait ValueIteratorExt {\n    /// Skips `n` elements and writes the value directly to the register.\n    /// Returns `Some(Ok(()))` on success, `Some(Err(...))` on parse error,\n    /// or `None` if there are fewer than `n+1` elements.\n    fn nth_into_register(&mut self, n: usize, dest: &mut Register) -> Option<Result<()>>;\n}\n\nimpl<'a> ValueIteratorExt for crate::types::ValueIterator<'a> {\n    #[inline(always)]\n    fn nth_into_register(&mut self, n: usize, dest: &mut Register) -> Option<Result<()>> {\n        use crate::storage::sqlite3_ondisk::read_varint;\n        use crate::types::{get_serial_type_size, Extendable, Text};\n\n        let mut header = self.header_section_ref();\n        let mut data = self.data_section_ref();\n\n        // Skip n elements\n        let mut data_sum = 0;\n        for _ in 0..n {\n            if header.is_empty() {\n                return None;\n            }\n\n            let (serial_type, bytes_read) = match read_varint(header) {\n                Ok(v) => v,\n                Err(e) => return Some(Err(e)),\n            };\n            header = &header[bytes_read..];\n\n            data_sum += match get_serial_type_size(serial_type) {\n                Ok(size) => size,\n                Err(e) => return Some(Err(e)),\n            };\n        }\n\n        if data_sum > data.len() {\n            return Some(Err(LimboError::Corrupt(\n                \"Data section too small for indicated serial type size\".into(),\n            )));\n        }\n        data = &data[data_sum..];\n\n        // Read the serial type for the target element\n        if header.is_empty() {\n            return None;\n        }\n\n        let (serial_type, bytes_read) = match read_varint(header) {\n            Ok(v) => v,\n            Err(e) => return Some(Err(e)),\n        };\n\n        // Update iterator state\n        self.set_header_section(&header[bytes_read..]);\n\n        // Decode directly into register based on serial type\n        match serial_type {\n            // NULL\n            0 => {\n                self.set_data_section(data);\n                dest.set_null();\n            }\n            // I8\n            1 => {\n                if unlikely(data.is_empty()) {\n                    return Some(Err(LimboError::Corrupt(\"Invalid 1-byte int\".into())));\n                }\n                self.set_data_section(&data[1..]);\n                dest.set_int(data[0] as i8 as i64);\n            }\n            // I16\n            2 => {\n                if unlikely(data.len() < 2) {\n                    return Some(Err(LimboError::Corrupt(\"Invalid 2-byte int\".into())));\n                }\n                self.set_data_section(&data[2..]);\n                dest.set_int(i16::from_be_bytes([data[0], data[1]]) as i64);\n            }\n            // I24\n            3 => {\n                if unlikely(data.len() < 3) {\n                    return Some(Err(LimboError::Corrupt(\"Invalid 3-byte int\".into())));\n                }\n                self.set_data_section(&data[3..]);\n                let sign_extension = if data[0] <= 0x7F { 0 } else { 0xFF };\n                dest.set_int(\n                    i32::from_be_bytes([sign_extension, data[0], data[1], data[2]]) as i64,\n                );\n            }\n            // I32\n            4 => {\n                if unlikely(data.len() < 4) {\n                    return Some(Err(LimboError::Corrupt(\"Invalid 4-byte int\".into())));\n                }\n                self.set_data_section(&data[4..]);\n                dest.set_int(i32::from_be_bytes([data[0], data[1], data[2], data[3]]) as i64);\n            }\n            // I48\n            5 => {\n                if unlikely(data.len() < 6) {\n                    return Some(Err(LimboError::Corrupt(\"Invalid 6-byte int\".into())));\n                }\n                self.set_data_section(&data[6..]);\n                let sign_extension = if data[0] <= 0x7F { 0 } else { 0xFF };\n                dest.set_int(i64::from_be_bytes([\n                    sign_extension,\n                    sign_extension,\n                    data[0],\n                    data[1],\n                    data[2],\n                    data[3],\n                    data[4],\n                    data[5],\n                ]));\n            }\n            // I64\n            6 => {\n                if unlikely(data.len() < 8) {\n                    return Some(Err(LimboError::Corrupt(\"Invalid 8-byte int\".into())));\n                }\n                self.set_data_section(&data[8..]);\n                dest.set_int(i64::from_be_bytes([\n                    data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],\n                ]));\n            }\n            // F64\n            7 => {\n                if unlikely(data.len() < 8) {\n                    return Some(Err(LimboError::Corrupt(\"Invalid 8-byte float\".into())));\n                }\n                self.set_data_section(&data[8..]);\n                let val = f64::from_be_bytes([\n                    data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],\n                ]);\n                if let Some(nn) = NonNan::new(val) {\n                    dest.set_float(nn);\n                } else {\n                    dest.set_null();\n                }\n            }\n            // CONST_INT0\n            8 => {\n                self.set_data_section(data);\n                dest.set_int(0);\n            }\n            // CONST_INT1\n            9 => {\n                self.set_data_section(data);\n                dest.set_int(1);\n            }\n            // Reserved\n            10 | 11 => {\n                mark_unlikely();\n                return Some(Err(LimboError::Corrupt(format!(\n                    \"Reserved serial type: {serial_type}\"\n                ))));\n            }\n            // BLOB (n >= 12 && n & 1 == 0)\n            n if n >= 12 && n & 1 == 0 => {\n                let content_size = ((n - 12) / 2) as usize;\n                if unlikely(data.len() < content_size) {\n                    return Some(Err(LimboError::Corrupt(\"Invalid Blob value\".into())));\n                }\n                self.set_data_section(&data[content_size..]);\n                let blob_data = &data[..content_size];\n                match dest {\n                    Register::Value(Value::Blob(existing_blob)) => {\n                        existing_blob.do_extend(&blob_data);\n                    }\n                    _ => {\n                        dest.set_blob(blob_data.to_vec());\n                    }\n                }\n            }\n            // TEXT (n >= 13 && n & 1 == 1)\n            n if n >= 13 && n & 1 == 1 => {\n                let content_size = ((n - 13) / 2) as usize;\n                if unlikely(data.len() < content_size) {\n                    return Some(Err(LimboError::Corrupt(\"Invalid Text value\".into())));\n                }\n                self.set_data_section(&data[content_size..]);\n                let text_data = &data[..content_size];\n                // SAFETY: TEXT serial type contains valid UTF-8\n                let text_str = if cfg!(debug_assertions) {\n                    match std::str::from_utf8(text_data) {\n                        Ok(s) => s,\n                        Err(e) => {\n                            return Some(Err(LimboError::InternalError(format!(\n                                \"Invalid UTF-8 in TEXT serial type: {e}\"\n                            ))));\n                        }\n                    }\n                } else {\n                    unsafe { std::str::from_utf8_unchecked(text_data) }\n                };\n                match dest {\n                    Register::Value(Value::Text(existing_text)) => {\n                        existing_text.do_extend(&text_str);\n                    }\n                    _ => {\n                        dest.set_text(Text::new(text_str.to_string()));\n                    }\n                }\n            }\n            _ => {\n                mark_unlikely();\n                return Some(Err(LimboError::Corrupt(format!(\n                    \"Invalid serial type: {serial_type}\"\n                ))));\n            }\n        }\n\n        Some(Ok(()))\n    }\n}\n\n/// Shuttle tests for validating the `unsafe impl Send + Sync for ProgramState` safety claims.\n///\n/// The safety claims are:\n/// 1. `Row` contains a `*const Register` pointing into `ProgramState.registers`\n/// 2. Only immutable references (`&Row`) are given out via `result_row.as_ref()`\n/// 3. `result_row` is invalidated (via `.take()`) at the start of each step iteration\n///\n/// These tests verify that the implementation correctly upholds these invariants\n/// under concurrent access patterns.\n#[cfg(all(shuttle, test))]\nmod shuttle_tests {\n    use super::*;\n    use crate::sync::Arc;\n    use crate::thread;\n    use crate::types::Value;\n\n    /// Creates a minimal ProgramState for testing.\n    fn create_test_state(num_registers: usize, num_cursors: usize) -> ProgramState {\n        ProgramState::new(num_registers, num_cursors)\n    }\n\n    /// Test that ProgramState can be safely sent between threads.\n    /// This validates the `unsafe impl Send for ProgramState` claim.\n    #[test]\n    fn shuttle_program_state_send() {\n        shuttle::check_random(\n            || {\n                let mut state = create_test_state(10, 2);\n\n                // Write some data to registers\n                state.registers[0].set_int(42);\n                state.registers[1].set_text(Text::new(\"test\".to_string()));\n\n                // Send state to another thread\n                let handle = thread::spawn(move || {\n                    // Verify data is intact after send\n                    assert!(matches!(\n                        &state.registers[0],\n                        Register::Value(Value::Numeric(Numeric::Integer(42)))\n                    ));\n                    if let Register::Value(Value::Text(t)) = &state.registers[1] {\n                        assert_eq!(t.as_str(), \"test\");\n                    } else {\n                        panic!(\"Expected text value\");\n                    }\n\n                    // Modify in new thread\n                    state.registers[2].set_int(100);\n                    state\n                });\n\n                let state = handle.join().unwrap();\n                assert!(matches!(\n                    &state.registers[2],\n                    Register::Value(Value::Numeric(Numeric::Integer(100)))\n                ));\n            },\n            1000,\n        );\n    }\n\n    /// Test that ProgramState with a set result_row can be safely sent.\n    /// The Row contains a raw pointer that must remain valid after the send.\n    #[test]\n    fn shuttle_program_state_send_with_row() {\n        shuttle::check_random(\n            || {\n                let mut state = create_test_state(10, 2);\n\n                // Set up registers with test data\n                state.registers[0].set_int(1);\n                state.registers[1].set_int(2);\n                state.registers[2].set_int(3);\n\n                // Create a result_row pointing to registers\n                state.result_row = Some(Row {\n                    values: &state.registers[0] as *const Register,\n                    count: 3,\n                });\n\n                // Send to another thread - the pointer must remain valid\n                // because it points to memory owned by state (the registers Vec)\n                let handle = thread::spawn(move || {\n                    // The row pointer should still be valid because registers moved with state\n                    if let Some(row) = &state.result_row {\n                        assert_eq!(row.len(), 3);\n                        // Read through the pointer - this validates the pointer is still valid\n                        let val = row.get::<i64>(0).unwrap();\n                        assert_eq!(val, 1);\n                        let val = row.get::<i64>(1).unwrap();\n                        assert_eq!(val, 2);\n                        let val = row.get::<i64>(2).unwrap();\n                        assert_eq!(val, 3);\n                    } else {\n                        panic!(\"Expected result_row to be set\");\n                    }\n                    state\n                });\n\n                let _ = handle.join().unwrap();\n            },\n            1000,\n        );\n    }\n\n    /// Test concurrent reads of result_row through shared reference.\n    /// This validates the `unsafe impl Sync for ProgramState` claim for read access.\n    #[test]\n    fn shuttle_program_state_sync_concurrent_reads() {\n        shuttle::check_random(\n            || {\n                let mut state = create_test_state(10, 2);\n\n                // Set up registers\n                state.registers[0].set_int(42);\n                state.registers[1].set_int(43);\n\n                // Create result_row\n                state.result_row = Some(Row {\n                    values: &state.registers[0] as *const Register,\n                    count: 2,\n                });\n\n                let state = Arc::new(state);\n                let state2 = Arc::clone(&state);\n                let state3 = Arc::clone(&state);\n\n                // Multiple threads reading concurrently\n                let h1 = thread::spawn(move || {\n                    if let Some(row) = &state.result_row {\n                        let val = row.get::<i64>(0).unwrap();\n                        assert_eq!(val, 42);\n                    }\n                });\n\n                let h2 = thread::spawn(move || {\n                    if let Some(row) = &state2.result_row {\n                        let val = row.get::<i64>(1).unwrap();\n                        assert_eq!(val, 43);\n                    }\n                });\n\n                let h3 = thread::spawn(move || {\n                    if let Some(row) = &state3.result_row {\n                        assert_eq!(row.len(), 2);\n                    }\n                });\n\n                h1.join().unwrap();\n                h2.join().unwrap();\n                h3.join().unwrap();\n            },\n            1000,\n        );\n    }\n\n    /// Test that Row values read through the pointer are consistent.\n    /// Multiple threads reading the same row values should see the same data.\n    #[test]\n    fn shuttle_row_pointer_consistency() {\n        shuttle::check_random(\n            || {\n                let mut state = create_test_state(10, 2);\n\n                // Set up registers with distinct values\n                for i in 0..5 {\n                    state.registers[i].set_int(i as i64 * 10);\n                }\n\n                state.result_row = Some(Row {\n                    values: &state.registers[0] as *const Register,\n                    count: 5,\n                });\n\n                let state = Arc::new(state);\n                let mut handles = vec![];\n\n                for _ in 0..4 {\n                    let state_clone = Arc::clone(&state);\n                    let h = thread::spawn(move || {\n                        if let Some(row) = &state_clone.result_row {\n                            // All threads should see the same values\n                            for i in 0..5 {\n                                let val = row.get::<i64>(i).unwrap();\n                                assert_eq!(val, i as i64 * 10);\n                            }\n                        }\n                    });\n                    handles.push(h);\n                }\n\n                for h in handles {\n                    h.join().unwrap();\n                }\n            },\n            1000,\n        );\n    }\n\n    /// Test the result_row invalidation pattern.\n    /// When result_row is taken (invalidated), concurrent reads should not see stale data.\n    /// This simulates the pattern used in `normal_step()` where `result_row.take()` is called.\n    #[test]\n    fn shuttle_result_row_invalidation() {\n        shuttle::check_random(\n            || {\n                let mut state = create_test_state(10, 2);\n\n                state.registers[0].set_int(100);\n                state.result_row = Some(Row {\n                    values: &state.registers[0] as *const Register,\n                    count: 1,\n                });\n\n                // Simulate the invalidation pattern from normal_step\n                // In real code, this requires &mut self, so there's no concurrent access\n                let taken_row = state.result_row.take();\n\n                // After take(), result_row should be None\n                assert!(state.result_row.is_none());\n\n                // The taken row still holds valid data (until dropped)\n                if let Some(row) = taken_row {\n                    let val = row.get::<i64>(0).unwrap();\n                    assert_eq!(val, 100);\n                }\n            },\n            1000,\n        );\n    }\n\n    /// Test register modification after row invalidation.\n    /// This validates that modifying registers after take() is safe.\n    #[test]\n    fn shuttle_register_modification_after_invalidation() {\n        shuttle::check_random(\n            || {\n                let mut state = create_test_state(10, 2);\n\n                state.registers[0].set_int(1);\n                state.result_row = Some(Row {\n                    values: &state.registers[0] as *const Register,\n                    count: 1,\n                });\n\n                // Invalidate row (simulating what normal_step does)\n                let _ = state.result_row.take();\n\n                // Now safe to modify registers\n                state.registers[0].set_int(999);\n\n                // Create new row pointing to modified registers\n                state.result_row = Some(Row {\n                    values: &state.registers[0] as *const Register,\n                    count: 1,\n                });\n\n                // New row should see new value\n                if let Some(row) = &state.result_row {\n                    let val = row.get::<i64>(0).unwrap();\n                    assert_eq!(val, 999);\n                }\n            },\n            1000,\n        );\n    }\n\n    /// Test sequential send-receive pattern (simulating async task scheduling).\n    /// ProgramState is moved between threads in a producer-consumer pattern.\n    #[test]\n    fn shuttle_sequential_thread_transfer() {\n        shuttle::check_random(\n            || {\n                let mut state = create_test_state(10, 2);\n                state.registers[0].set_int(0);\n\n                // Thread 1: increment\n                let h1 = thread::spawn(move || {\n                    if let Register::Value(Value::Numeric(Numeric::Integer(v))) =\n                        &state.registers[0]\n                    {\n                        state.registers[0].set_int(v + 1);\n                    }\n                    state\n                });\n\n                let mut state = h1.join().unwrap();\n\n                // Thread 2: increment\n                let h2 = thread::spawn(move || {\n                    if let Register::Value(Value::Numeric(Numeric::Integer(v))) =\n                        &state.registers[0]\n                    {\n                        state.registers[0].set_int(v + 1);\n                    }\n                    state\n                });\n\n                let mut state = h2.join().unwrap();\n\n                // Thread 3: increment\n                let h3 = thread::spawn(move || {\n                    if let Register::Value(Value::Numeric(Numeric::Integer(v))) =\n                        &state.registers[0]\n                    {\n                        state.registers[0].set_int(v + 1);\n                    }\n                    state\n                });\n\n                let state = h3.join().unwrap();\n\n                // Final value should be 3\n                assert!(matches!(\n                    &state.registers[0],\n                    Register::Value(Value::Numeric(Numeric::Integer(3)))\n                ));\n            },\n            1000,\n        );\n    }\n\n    /// Test that ProgramState can be wrapped in Arc for shared ownership.\n    /// This is the typical pattern for concurrent database operations.\n    #[test]\n    fn shuttle_arc_wrapped_state() {\n        shuttle::check_random(\n            || {\n                let mut state = create_test_state(10, 2);\n\n                // Initialize with test data\n                for i in 0..5 {\n                    state.registers[i].set_int(i as i64);\n                }\n\n                let state = Arc::new(state);\n                let mut handles = vec![];\n\n                // Multiple threads reading registers through Arc\n                for thread_id in 0u8..4 {\n                    let state_clone = Arc::clone(&state);\n                    let h = thread::spawn(move || {\n                        // Each thread reads all registers\n                        for i in 0..5 {\n                            if let Register::Value(Value::Numeric(Numeric::Integer(v))) =\n                                &state_clone.registers[i]\n                            {\n                                assert_eq!(*v, i as i64);\n                            }\n                        }\n                        thread_id\n                    });\n                    handles.push(h);\n                }\n\n                for h in handles {\n                    h.join().unwrap();\n                }\n            },\n            1000,\n        );\n    }\n\n    /// Test Row::get_values iterator under concurrent access.\n    #[test]\n    fn shuttle_row_get_values_concurrent() {\n        shuttle::check_random(\n            || {\n                let mut state = create_test_state(10, 2);\n\n                state.registers[0].set_int(10);\n                state.registers[1].set_int(20);\n                state.registers[2].set_int(30);\n\n                state.result_row = Some(Row {\n                    values: &state.registers[0] as *const Register,\n                    count: 3,\n                });\n\n                let state = Arc::new(state);\n                let state2 = Arc::clone(&state);\n\n                let h1 = thread::spawn(move || {\n                    if let Some(row) = &state.result_row {\n                        let values: Vec<_> = row.get_values().collect();\n                        assert_eq!(values.len(), 3);\n                    }\n                });\n\n                let h2 = thread::spawn(move || {\n                    if let Some(row) = &state2.result_row {\n                        let mut sum = 0i64;\n                        for val in row.get_values() {\n                            if let Value::Numeric(Numeric::Integer(i)) = val {\n                                sum += i;\n                            }\n                        }\n                        assert_eq!(sum, 60); // 10 + 20 + 30\n                    }\n                });\n\n                h1.join().unwrap();\n                h2.join().unwrap();\n            },\n            1000,\n        );\n    }\n\n    /// Stress test: Many threads reading from shared ProgramState.\n    #[test]\n    fn shuttle_stress_concurrent_reads() {\n        shuttle::check_random(\n            || {\n                let mut state = create_test_state(20, 2);\n\n                // Fill registers with identifiable data\n                for i in 0..20 {\n                    state.registers[i].set_int(i as i64 * 100);\n                }\n\n                state.result_row = Some(Row {\n                    values: &state.registers[0] as *const Register,\n                    count: 20,\n                });\n\n                let state = Arc::new(state);\n                let mut handles = vec![];\n\n                for thread_id in 0..6u8 {\n                    let state_clone = Arc::clone(&state);\n                    let h = thread::spawn(move || {\n                        // Each thread reads different parts\n                        let start = (thread_id as usize * 3) % 20;\n                        if let Some(row) = &state_clone.result_row {\n                            for i in 0..3 {\n                                let idx = (start + i) % row.len();\n                                let val = row.get::<i64>(idx).unwrap();\n                                assert_eq!(val, idx as i64 * 100);\n                            }\n                        }\n                        thread_id\n                    });\n                    handles.push(h);\n                }\n\n                for h in handles {\n                    h.join().unwrap();\n                }\n            },\n            1000,\n        );\n    }\n}\n"
  },
  {
    "path": "core/vdbe/rowset.rs",
    "content": "//! RowSet data structure for efficient set operations on integer rowids.\n//!\n//! RowSet is optimized for batch-oriented insertions where sets of integers are inserted\n//! in distinct phases, with each phase containing no duplicates. Operations are optimized\n//! for two distinct use cases:\n//!\n//! 1. **TEST mode**: Check membership and insert values with batch-based consolidation.\n//!    Values are inserted into a fresh list and consolidated into a BTreeSet when the batch\n//!    number changes, enabling efficient membership tests.\n//!\n//! 2. **SMALLEST mode**: Extract values in sorted order. Once extraction begins, a sorted\n//!    buffer is built and values are extracted one at a time.\n//!\n//! **Critical constraint**: TEST and SMALLEST operations are mutually exclusive. Once `test()`\n//! has been called, `smallest()` cannot be used. Once `smallest()` has been called, `test()`\n//! cannot be used. This matches SQLite's RowSet semantics.\n//!\n//! ## Batch Semantics\n//!\n//! Batches identify distinct phases of insertion:\n//! - `batch == 0`: First set (guaranteed not to contain values, so no test needed)\n//! - `batch > 0`: Intermediate sets (consolidation happens when batch changes)\n//! - `batch == -1`: Final set (no insertion needed, only testing)\n//!\n//! When `test()` is called with a different batch number than the current `i_batch`, all\n//! values in the fresh list are consolidated into the consolidated set.\n\nuse branches::mark_unlikely;\n\nuse crate::turso_assert;\nuse std::collections::BTreeSet;\n\n/// The mode of usage for a RowSet.\n/// Test: the rowset will be used for set membership tests.\n/// Smallest: the rowset will be used to extract the smallest value in sorted order.\n#[derive(Debug)]\npub enum RowSetMode {\n    Test {\n        /// Set of distinct rowids.\n        set: BTreeSet<i64>,\n        /// Batch number of the last test.\n        batch_number: i32,\n    },\n    Smallest {\n        sorted_vec: Vec<i64>,\n    },\n    Unset,\n}\n\n/// A set of integer rowids optimized for batch-oriented operations.\n#[derive(Debug)]\npub struct RowSet {\n    /// Fresh inserts since last consolidation\n    fresh: Vec<i64>,\n    /// The mode of usage for the RowSet.\n    mode: RowSetMode,\n}\n\nimpl Default for RowSet {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl RowSet {\n    /// Creates a new empty RowSet.\n    pub fn new() -> Self {\n        Self {\n            fresh: Vec::new(),\n            mode: RowSetMode::Unset,\n        }\n    }\n\n    /// Inserts a rowid into the set.\n    ///\n    /// Values are added to the fresh list and will be consolidated when `test()` is called\n    /// with a different batch number.\n    ///\n    /// # Panics\n    ///\n    /// Panics if `smallest()` extraction has already started.\n    pub fn insert(&mut self, rowid: i64) {\n        turso_assert!(\n            !matches!(self.mode, RowSetMode::Smallest { .. }),\n            \"cannot insert after smallest() has been used\"\n        );\n        self.fresh.push(rowid);\n    }\n\n    /// Tests if the rowid exists in the set, with batch-based consolidation.\n    ///\n    /// If `batch` differs from the current batch, consolidates fresh values into the\n    /// consolidated set. Returns `true` if the rowid is found.\n    ///\n    /// # Panics\n    ///\n    /// Panics if `smallest()` extraction has already started, because rowsets have two\n    /// mutually exclusive uses: set membership tests (test()) and in-order iteration (smallest()).\n    pub fn test(&mut self, rowid: i64, batch: i32) -> bool {\n        turso_assert!(\n            !matches!(self.mode, RowSetMode::Smallest { .. }),\n            \"cannot call test() after smallest() has started\"\n        );\n        if matches!(self.mode, RowSetMode::Unset) {\n            self.mode = RowSetMode::Test {\n                set: BTreeSet::new(),\n                batch_number: 0,\n            };\n        }\n        let RowSetMode::Test { set, batch_number } = &mut self.mode else {\n            mark_unlikely();\n            unreachable!()\n        };\n\n        // If a new batch has started, fold the fresh vector into the set.\n        if batch != *batch_number {\n            for v in self.fresh.drain(..) {\n                set.insert(v);\n            }\n            *batch_number = batch;\n        }\n        // Note: If the batch number has not changed, we only check whether any previous batch inserted this value,\n        // since the rowset implementation expects that any single batch does not insert any duplicates nor\n        // test for duplicates wrt the current batch.\n        set.contains(&rowid)\n    }\n\n    /// Extracts and returns the smallest rowid from the set.\n    ///\n    /// On the first call, builds a sorted buffer from all values (O(N log N)).\n    /// Subsequent calls are O(1). Returns `None` if the set is empty.\n    ///\n    /// # Panics\n    ///\n    /// Panics if `test()` has been called on this RowSet, because rowsets have two\n    /// mutually exclusive uses: set membership tests (test()) and in-order iteration (smallest()).\n    pub fn smallest(&mut self) -> Option<i64> {\n        turso_assert!(\n            !matches!(self.mode, RowSetMode::Test { .. }),\n            \"cannot call smallest() after test() has been used\"\n        );\n        if matches!(self.mode, RowSetMode::Unset) {\n            let mut v = Vec::with_capacity(self.fresh.len());\n            v.append(&mut self.fresh);\n            v.sort_unstable();\n            v.dedup();\n            v.reverse();\n            self.mode = RowSetMode::Smallest { sorted_vec: v };\n        }\n        let RowSetMode::Smallest { sorted_vec } = &mut self.mode else {\n            mark_unlikely();\n            unreachable!()\n        };\n\n        sorted_vec.pop()\n    }\n\n    /// Returns `true` if the RowSet contains no values.\n    pub fn is_empty(&self) -> bool {\n        if !self.fresh.is_empty() {\n            return false;\n        }\n        match &self.mode {\n            RowSetMode::Test { set, .. } => set.is_empty(),\n            RowSetMode::Smallest { sorted_vec, .. } => sorted_vec.is_empty(),\n            RowSetMode::Unset => true,\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use rand_chacha::{\n        rand_core::{RngCore, SeedableRng},\n        ChaCha8Rng,\n    };\n\n    fn get_seed() -> u64 {\n        std::env::var(\"SEED\").map_or(\n            std::time::SystemTime::now()\n                .duration_since(std::time::UNIX_EPOCH)\n                .unwrap()\n                .as_millis(),\n            |v| {\n                v.parse()\n                    .expect(\"Failed to parse SEED environment variable as u64\")\n            },\n        ) as u64\n    }\n\n    #[test]\n    fn test_empty_rowset() {\n        let rowset = RowSet::new();\n        assert!(rowset.is_empty());\n    }\n\n    #[test]\n    fn test_insert_and_test() {\n        let mut rowset = RowSet::new();\n\n        rowset.insert(10);\n        rowset.insert(20);\n        rowset.insert(30);\n\n        assert!(!rowset.test(10, 0));\n        assert!(!rowset.test(20, 0));\n        assert!(!rowset.test(30, 0));\n\n        assert!(rowset.test(10, 1));\n        assert!(rowset.test(20, 1));\n        assert!(rowset.test(30, 1));\n        assert!(!rowset.test(40, 1));\n    }\n\n    #[test]\n    fn test_batch_consolidation() {\n        let mut rowset = RowSet::new();\n\n        // Insert values into batch 0 (first set)\n        rowset.insert(10);\n        rowset.insert(20);\n        // Batch 0 doesn't test for membership (guaranteed not to contain values)\n        assert!(!rowset.test(10, 0));\n\n        // Insert more values (still in fresh, not consolidated yet)\n        rowset.insert(30);\n        rowset.insert(40);\n\n        // Test with batch 1: triggers consolidation of fresh values (10, 20, 30, 40)\n        // All should be found after consolidation\n        assert!(rowset.test(10, 1));\n        assert!(rowset.test(20, 1));\n        assert!(rowset.test(30, 1));\n        assert!(rowset.test(40, 1));\n        assert!(!rowset.test(50, 1));\n\n        // Insert value 50 (goes to fresh, not consolidated yet)\n        rowset.insert(50);\n\n        // Test with same batch (1): no new consolidation, so 50 not found yet\n        assert!(rowset.test(10, 1));\n        assert!(!rowset.test(50, 1));\n\n        // Test with new batch (2): triggers consolidation, now 50 is found\n        assert!(rowset.test(50, 2));\n    }\n\n    #[test]\n    fn test_smallest_extraction() {\n        let mut rowset = RowSet::new();\n\n        rowset.insert(30);\n        rowset.insert(10);\n        rowset.insert(50);\n        rowset.insert(20);\n        rowset.insert(40);\n\n        assert_eq!(rowset.smallest(), Some(10));\n        assert_eq!(rowset.smallest(), Some(20));\n        assert_eq!(rowset.smallest(), Some(30));\n        assert_eq!(rowset.smallest(), Some(40));\n        assert_eq!(rowset.smallest(), Some(50));\n        assert_eq!(rowset.smallest(), None);\n        assert!(rowset.is_empty());\n    }\n\n    #[test]\n    fn test_smallest_with_duplicates() {\n        let mut rowset = RowSet::new();\n\n        rowset.insert(10);\n        rowset.insert(20);\n        rowset.insert(10);\n        rowset.insert(30);\n        rowset.insert(20);\n\n        assert_eq!(rowset.smallest(), Some(10));\n        assert_eq!(rowset.smallest(), Some(20));\n        assert_eq!(rowset.smallest(), Some(30));\n        assert_eq!(rowset.smallest(), None);\n    }\n\n    #[test]\n    fn test_insert_after_smallest_panics() {\n        let mut rowset = RowSet::new();\n        rowset.insert(10);\n        rowset.smallest();\n\n        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {\n            rowset.insert(20);\n        }));\n        assert!(result.is_err());\n    }\n\n    #[test]\n    fn test_test_after_smallest_panics() {\n        let mut rowset = RowSet::new();\n        rowset.insert(10);\n        rowset.smallest();\n\n        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {\n            rowset.test(10, 1);\n        }));\n        assert!(result.is_err());\n    }\n\n    #[test]\n    fn test_smallest_after_test_panics() {\n        let mut rowset = RowSet::new();\n        rowset.insert(10);\n        rowset.test(10, 1);\n\n        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {\n            rowset.smallest();\n        }));\n        assert!(result.is_err());\n    }\n\n    #[test]\n    fn test_batch_zero_allows_smallest() {\n        let mut rowset = RowSet::new();\n        rowset.insert(10);\n        rowset.insert(20);\n        rowset.insert(30);\n        rowset.insert(5);\n        rowset.insert(15);\n\n        assert_eq!(rowset.smallest(), Some(5));\n        assert_eq!(rowset.smallest(), Some(10));\n        assert_eq!(rowset.smallest(), Some(15));\n        assert_eq!(rowset.smallest(), Some(20));\n        assert_eq!(rowset.smallest(), Some(30));\n        assert_eq!(rowset.smallest(), None);\n    }\n\n    #[test]\n    fn test_empty_smallest() {\n        let mut rowset = RowSet::new();\n        assert_eq!(rowset.smallest(), None);\n        assert!(rowset.is_empty());\n    }\n\n    #[test]\n    fn test_batch_zero_semantics() {\n        let mut rowset = RowSet::new();\n\n        rowset.insert(10);\n        rowset.insert(20);\n\n        assert!(!rowset.test(10, 0));\n        assert!(!rowset.test(20, 0));\n\n        assert!(rowset.test(10, 1));\n        assert!(rowset.test(20, 1));\n    }\n\n    #[test]\n    fn test_batch_final_semantics() {\n        let mut rowset = RowSet::new();\n\n        // Insert and consolidate with batch 1\n        rowset.insert(10);\n        assert!(rowset.test(10, 1));\n\n        // Insert more (goes to fresh)\n        rowset.insert(20);\n\n        // Test with batch -1 (final set): consolidates fresh values\n        // Both 10 (already consolidated) and 20 (now consolidated) should be found\n        assert!(rowset.test(10, -1));\n        assert!(rowset.test(20, -1));\n\n        // Test non-existent value: should not insert (batch == -1 means no insertion)\n        // Verify it's still not found on second test\n        assert!(!rowset.test(30, -1));\n        assert!(!rowset.test(30, -1));\n    }\n\n    #[test]\n    fn test_negative_values() {\n        let mut rowset = RowSet::new();\n\n        rowset.insert(-10);\n        rowset.insert(-5);\n        rowset.insert(0);\n        rowset.insert(5);\n        rowset.insert(10);\n\n        assert!(rowset.test(-10, 1));\n        assert!(rowset.test(-5, 1));\n        assert!(rowset.test(0, 1));\n        assert!(rowset.test(5, 1));\n        assert!(rowset.test(10, 1));\n\n        assert!(rowset.test(-10, 2));\n        assert!(rowset.test(-5, 2));\n        assert!(rowset.test(0, 2));\n        assert!(rowset.test(5, 2));\n        assert!(rowset.test(10, 2));\n    }\n\n    #[test]\n    fn test_large_values() {\n        let mut rowset = RowSet::new();\n\n        let large1 = i64::MAX;\n        let large2 = i64::MAX - 1;\n        let large3 = i64::MIN;\n        let large4 = i64::MIN + 1;\n\n        rowset.insert(large1);\n        rowset.insert(large2);\n        rowset.insert(large3);\n        rowset.insert(large4);\n\n        assert!(rowset.test(large1, 1));\n        assert!(rowset.test(large2, 1));\n        assert!(rowset.test(large3, 1));\n        assert!(rowset.test(large4, 1));\n\n        assert!(rowset.test(large1, 2));\n        assert!(rowset.test(large2, 2));\n        assert!(rowset.test(large3, 2));\n        assert!(rowset.test(large4, 2));\n    }\n\n    #[test]\n    fn fuzz_basic_operations() {\n        // Fuzz test for smallest() extraction: insert random values and verify\n        // they are extracted in sorted order without duplicates.\n        let seed = get_seed();\n        let mut rng = ChaCha8Rng::seed_from_u64(seed);\n\n        let attempts = 10;\n        for _ in 0..attempts {\n            let mut rowset = RowSet::new();\n            let mut inserted = std::collections::BTreeSet::new();\n\n            // Insert random values (may include duplicates)\n            let num_inserts = 100 + (rng.next_u64() % 900) as usize;\n            for _ in 0..num_inserts {\n                let value = rng.next_u64() as i64;\n                rowset.insert(value);\n                inserted.insert(value);\n            }\n\n            // Extract all values using smallest()\n            let mut extracted = Vec::new();\n            while let Some(value) = rowset.smallest() {\n                extracted.push(value);\n            }\n\n            // Verify all unique values were extracted exactly once\n            assert_eq!(extracted.len(), inserted.len());\n\n            // Verify they're in sorted order\n            let mut sorted_inserted: Vec<i64> = inserted.iter().copied().collect();\n            sorted_inserted.sort_unstable();\n            assert_eq!(extracted, sorted_inserted);\n        }\n    }\n\n    #[test]\n    fn fuzz_batch_operations() {\n        // Fuzz test for batch-based consolidation: insert values in distinct batches\n        // and verify they can be found after consolidation.\n        let seed = get_seed();\n        let mut rng = ChaCha8Rng::seed_from_u64(seed);\n\n        let attempts = 10;\n        for _ in 0..attempts {\n            let mut rowset = RowSet::new();\n            let mut batches: Vec<(i32, Vec<i64>)> = Vec::new();\n\n            // Create multiple batches: batch 0 (first), intermediate batches (>0), and batch -1 (final)\n            let num_batches = 5 + (rng.next_u64() % 10) as usize;\n            for batch_idx in 0..num_batches {\n                let batch = if batch_idx == 0 {\n                    0\n                } else if batch_idx == num_batches - 1 {\n                    -1\n                } else {\n                    batch_idx as i32\n                };\n\n                // Insert values for this batch\n                let mut batch_values = Vec::new();\n                let num_values = 10 + (rng.next_u64() % 90) as usize;\n\n                for _ in 0..num_values {\n                    let value = rng.next_u64() as i64;\n                    rowset.insert(value);\n                    batch_values.push(value);\n                }\n\n                batches.push((batch, batch_values));\n            }\n\n            // Verify all values can be found when testing with their batch\n            for (batch, values) in &batches {\n                for &value in values {\n                    if *batch == 0 {\n                        // Batch 0: guaranteed not to contain values\n                        assert!(!rowset.test(value, *batch));\n                    } else {\n                        // Other batches: should find values after consolidation\n                        assert!(\n                            rowset.test(value, *batch),\n                            \"Value {value} should be found in batch {batch}\",\n                        );\n                    }\n                }\n            }\n        }\n    }\n\n    #[test]\n    fn fuzz_mixed_operations() {\n        // Fuzz test mixing insertions and tests: randomly insert values and test them\n        // with incrementing batch numbers to verify consolidation works correctly.\n        let seed = get_seed();\n        let mut rng = ChaCha8Rng::seed_from_u64(seed);\n\n        let attempts = 3;\n        for _ in 0..attempts {\n            let mut rowset = RowSet::new();\n            let mut all_values = std::collections::BTreeSet::new();\n            let mut next_batch = 1;\n\n            let num_ops = 20 + (rng.next_u64() % 30) as usize;\n\n            // Randomly interleave insertions and tests\n            for _ in 0..num_ops {\n                let op = rng.next_u64() % 2;\n\n                match op {\n                    0 => {\n                        // Insert a random value\n                        let value = rng.next_u64() as i64;\n                        rowset.insert(value);\n                        all_values.insert(value);\n                    }\n                    _ => {\n                        // Test a previously inserted value with a new batch number\n                        // This triggers consolidation of all fresh values\n                        if !all_values.is_empty() {\n                            let values_vec: Vec<i64> = all_values.iter().copied().collect();\n                            let idx = (rng.next_u64() % values_vec.len() as u64) as usize;\n                            let value = values_vec[idx];\n\n                            let found = rowset.test(value, next_batch);\n                            assert!(found, \"Value {value} should be found in batch {next_batch}\",);\n                            next_batch += 1;\n                        }\n                    }\n                }\n            }\n\n            // Verify all inserted values can be found after final consolidation\n            if !all_values.is_empty() {\n                let final_batch = next_batch;\n                for &value in &all_values {\n                    assert!(\n                        rowset.test(value, final_batch),\n                        \"Value {value} should be found in batch {final_batch}\",\n                    );\n                }\n            }\n        }\n    }\n\n    #[test]\n    fn fuzz_long() {\n        // Long-running fuzz test: insert values in batches and verify batch consolidation.\n        // This tests the core RowSet behavior where values are inserted in distinct phases\n        // and consolidated when the batch number changes.\n        let seed = get_seed();\n        let mut rng = ChaCha8Rng::seed_from_u64(seed);\n\n        println!(\"Fuzz seed: {seed}\");\n\n        let attempts = 2;\n        for attempt in 0..attempts {\n            let mut rowset = RowSet::new();\n            let mut reference = std::collections::BTreeSet::new();\n            let mut batches: Vec<(i32, Vec<i64>)> = Vec::new();\n\n            // Generate random number of batches and total inserts\n            let num_batches = 10 + (rng.next_u64() % 40) as usize;\n            let total_inserts = 1000 + (rng.next_u64() % 9000) as usize;\n            let inserts_per_batch = (total_inserts / num_batches).max(1);\n\n            // Insert values in batches\n            for batch_idx in 0..num_batches {\n                // Determine batch number: 0 for first, -1 for last, sequential for others\n                let batch = if batch_idx == 0 {\n                    0\n                } else if batch_idx == num_batches - 1 {\n                    -1\n                } else {\n                    batch_idx as i32\n                };\n\n                // Calculate how many values to insert in this batch\n                // (distribute total_inserts across batches with some randomness)\n                let mut batch_values = Vec::new();\n                let already_inserted = batches.iter().map(|(_, v)| v.len()).sum::<usize>();\n                let batch_inserts = if batch_idx == num_batches - 1 {\n                    // Last batch gets remaining values\n                    total_inserts.saturating_sub(already_inserted)\n                } else {\n                    // Other batches get inserts_per_batch plus some random variation\n                    let remaining = total_inserts.saturating_sub(already_inserted);\n                    let max_for_this_batch = remaining.min(inserts_per_batch * 2);\n                    inserts_per_batch\n                        + (rng.next_u64()\n                            % (max_for_this_batch.saturating_sub(inserts_per_batch) + 1) as u64)\n                            as usize\n                };\n\n                // Insert values for this batch\n                for _ in 0..batch_inserts {\n                    let value = rng.next_u64() as i64;\n                    rowset.insert(value);\n                    reference.insert(value);\n                    batch_values.push(value);\n                }\n\n                // For batches > 0, test some values to verify consolidation works\n                if batch > 0 {\n                    let test_count = (batch_values.len() / 10).max(1);\n                    for _ in 0..test_count {\n                        let idx = (rng.next_u64() % batch_values.len() as u64) as usize;\n                        let value = batch_values[idx];\n\n                        let found = rowset.test(value, batch);\n                        assert!(\n                            found,\n                            \"Attempt {attempt}, batch {batch}, value {value} should be found\",\n                        );\n                    }\n                }\n\n                batches.push((batch, batch_values));\n            }\n\n            // Final verification: ensure all values can be found after consolidation\n            if !reference.is_empty() && !batches.is_empty() {\n                let last_batch = batches.last().unwrap().0;\n                // Use a new batch number to force final consolidation\n                let final_batch = if last_batch == -1 { -1 } else { last_batch + 1 };\n\n                for &value in &reference {\n                    assert!(\n                        rowset.test(value, final_batch),\n                        \"Attempt {attempt}, value {value} should be found in batch {final_batch}\",\n                    );\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "core/vdbe/sorter.rs",
    "content": "use crate::{turso_assert, turso_assert_eq};\nuse branches::mark_unlikely;\nuse turso_parser::ast::SortOrder;\n\nuse crate::sync::RwLock;\nuse crate::sync::{atomic, Arc};\nuse bumpalo::Bump;\nuse std::cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd, Reverse};\nuse std::collections::BinaryHeap;\nuse std::ptr::NonNull;\nuse std::rc::Rc;\n\nuse crate::io::TempFile;\nuse crate::types::{IOCompletions, ValueIterator};\nuse crate::{\n    error::LimboError,\n    io::{Buffer, Completion, CompletionGroup, File, IO},\n    storage::sqlite3_ondisk::{read_varint, varint_len, write_varint},\n    translate::collate::CollationSeq,\n    types::{IOResult, ImmutableRecord, KeyInfo, ValueRef},\n    Result,\n};\nuse crate::{io_yield_one, return_if_io, CompletionError};\n\n/// A custom comparison function for sorting custom type columns.\n/// Takes two value references and returns an Ordering.\n/// Used when a custom type defines a `<` operator for correct sort behavior.\npub type SortComparator = Arc<dyn Fn(&ValueRef, &ValueRef) -> Ordering + Send + Sync>;\n\n#[derive(Debug, Clone, Copy)]\nenum SortState {\n    Start,\n    Flush,\n    InitHeap,\n    Next,\n}\n\n#[derive(Debug, Clone, Copy)]\nenum InsertState {\n    Start,\n    Insert,\n}\n\n#[derive(Debug, Clone, Copy)]\nenum InitChunkHeapState {\n    Start,\n    PushChunk,\n}\n\npub struct Sorter {\n    /// Arena allocator for records - provides fast bump allocation and bulk deallocation.\n    /// All record data (payload bytes, key_values) is stored here for in-memory sorting.\n    arena: Bump,\n    /// Pointers to records allocated in the arena. Sorting moves only 8-byte pointers,\n    /// which prevents high memmove costs during sorting.\n    /// SAFETY: These pointers are valid as long as the arena hasn't been reset.\n    records: Vec<NonNull<ArenaSortableRecord>>,\n    /// The current record.\n    current: Option<ImmutableRecord>,\n    /// The number of values in the key.\n    key_len: usize,\n    /// The key info.\n    pub index_key_info: Rc<Vec<KeyInfo>>,\n    /// Per-column custom comparators for custom type ordering.\n    /// When present, used instead of standard ValueRef comparison for that column.\n    comparators: Rc<Vec<Option<SortComparator>>>,\n    /// Sorted chunks stored on disk.\n    chunks: Vec<SortedChunk>,\n    /// The heap of records consumed from the chunks and their corresponding chunk index.\n    chunk_heap: BinaryHeap<(Reverse<Box<BoxedSortableRecord>>, usize)>,\n    /// The maximum size of the in-memory buffer in bytes before the records are flushed to a chunk file.\n    max_buffer_size: usize,\n    /// The current size of the in-memory buffer in bytes.\n    current_buffer_size: usize,\n    /// The minimum size of a chunk read buffer in bytes. The actual buffer size can be larger if the largest\n    /// record in the buffer is larger than this value.\n    min_chunk_read_buffer_size: usize,\n    /// The maximum record payload size in the in-memory buffer.\n    max_payload_size_in_buffer: usize,\n    /// The IO object.\n    io: Arc<dyn IO>,\n    /// The temporary file for chunks.\n    temp_file: Option<TempFile>,\n    /// Offset where the next chunk will be placed in the `temp_file`\n    next_chunk_offset: usize,\n    /// State machine for [Sorter::sort]\n    sort_state: SortState,\n    /// State machine for [Sorter::insert]\n    insert_state: InsertState,\n    /// State machine for [Sorter::init_chunk_heap]\n    init_chunk_heap_state: InitChunkHeapState,\n    /// Pending IO completion along with the chunk index that needs to be retried after IO completes.\n    pending_completion: Option<(Completion, usize)>,\n    /// Temp storage mode (memory vs file) for spilled data\n    temp_store: crate::TempStore,\n}\n\nimpl Sorter {\n    pub fn new(\n        order: &[SortOrder],\n        collations: Vec<CollationSeq>,\n        comparators: Vec<Option<SortComparator>>,\n        max_buffer_size_bytes: usize,\n        min_chunk_read_buffer_size_bytes: usize,\n        io: Arc<dyn IO>,\n        temp_store: crate::TempStore,\n    ) -> Self {\n        turso_assert_eq!(order.len(), collations.len());\n        Self {\n            arena: Bump::new(),\n            records: Vec::new(),\n            current: None,\n            key_len: order.len(),\n            index_key_info: Rc::new(\n                order\n                    .iter()\n                    .zip(collations)\n                    .map(|(order, collation)| KeyInfo {\n                        sort_order: *order,\n                        collation,\n                    })\n                    .collect(),\n            ),\n            comparators: Rc::new(comparators),\n            chunks: Vec::new(),\n            chunk_heap: BinaryHeap::new(),\n            max_buffer_size: max_buffer_size_bytes,\n            current_buffer_size: 0,\n            min_chunk_read_buffer_size: min_chunk_read_buffer_size_bytes,\n            max_payload_size_in_buffer: 0,\n            io,\n            temp_file: None,\n            next_chunk_offset: 0,\n            sort_state: SortState::Start,\n            insert_state: InsertState::Start,\n            init_chunk_heap_state: InitChunkHeapState::Start,\n            pending_completion: None,\n            temp_store,\n        }\n    }\n\n    pub fn is_empty(&self) -> bool {\n        self.records.is_empty() && self.chunks.is_empty()\n    }\n\n    pub fn has_more(&self) -> bool {\n        self.current.is_some()\n    }\n\n    // We do the sorting here since this is what is called by the SorterSort instruction\n    pub fn sort(&mut self) -> Result<IOResult<()>> {\n        loop {\n            match self.sort_state {\n                SortState::Start => {\n                    if self.chunks.is_empty() {\n                        // Sort ascending then reverse - we pop from end so this gives ascending output.\n                        // NOTE: We can't just sort descending because stable sort preserves insertion\n                        // order for equal elements, and descending sort doesn't reverse equal elements.\n                        // SAFETY: All pointers in records are valid (arena hasn't been reset).\n                        self.records\n                            .sort_by(|a, b| unsafe { a.as_ref().cmp(b.as_ref()) });\n                        self.records.reverse();\n                        self.sort_state = SortState::Next;\n                    } else {\n                        self.sort_state = SortState::Flush;\n                    }\n                }\n                SortState::Flush => {\n                    self.sort_state = SortState::InitHeap;\n                    if let Some(c) = self.flush()? {\n                        io_yield_one!(c);\n                    }\n                }\n                SortState::InitHeap => {\n                    // Check for write errors before proceeding\n                    if self.chunks.iter().any(|chunk| {\n                        matches!(*chunk.io_state.read(), SortedChunkIOState::WriteError)\n                    }) {\n                        return Err(CompletionError::IOError(\n                            std::io::ErrorKind::WriteZero,\n                            \"sorter write\",\n                        )\n                        .into());\n                    }\n                    turso_assert!(\n                        !self.chunks.iter().any(|chunk| {\n                            matches!(*chunk.io_state.read(), SortedChunkIOState::WaitingForWrite)\n                        }),\n                        \"chunks should been written\"\n                    );\n                    return_if_io!(self.init_chunk_heap());\n                    self.sort_state = SortState::Next;\n                }\n                SortState::Next => {\n                    return_if_io!(self.next());\n                    self.sort_state = SortState::Start;\n                    return Ok(IOResult::Done(()));\n                }\n            }\n        }\n    }\n\n    #[allow(clippy::should_implement_trait)]\n    pub fn next(&mut self) -> Result<IOResult<()>> {\n        if self.chunks.is_empty() {\n            match self.records.pop() {\n                Some(ptr) => {\n                    // SAFETY: ptr is valid - arena hasn't been reset yet.\n                    let arena_record = unsafe { ptr.as_ref() };\n                    let payload = arena_record.payload();\n\n                    match &mut self.current {\n                        Some(record) => {\n                            record.invalidate();\n                            record.start_serialization(payload);\n                        }\n                        None => {\n                            self.current = Some(arena_record.to_immutable_record());\n                        }\n                    }\n\n                    if self.records.is_empty() {\n                        self.arena.reset();\n                    }\n                }\n                None => self.current = None,\n            }\n        } else {\n            // Serve from sorted chunk files\n            match return_if_io!(self.next_from_chunk_heap()) {\n                Some(boxed_record) => {\n                    if let Some(ref error) = boxed_record.deserialization_error {\n                        return Err(error.clone());\n                    }\n                    let payload = boxed_record.record.get_payload();\n                    match &mut self.current {\n                        Some(record) => {\n                            record.invalidate();\n                            record.start_serialization(payload);\n                        }\n                        None => {\n                            self.current = Some(boxed_record.record);\n                        }\n                    }\n                }\n                None => self.current = None,\n            }\n        }\n        Ok(IOResult::Done(()))\n    }\n\n    pub fn record(&self) -> Option<&ImmutableRecord> {\n        self.current.as_ref()\n    }\n\n    pub fn insert(&mut self, record: &ImmutableRecord) -> Result<IOResult<()>> {\n        let payload_size = record.get_payload().len();\n        loop {\n            match self.insert_state {\n                InsertState::Start => {\n                    self.insert_state = InsertState::Insert;\n                    if self.current_buffer_size + payload_size > self.max_buffer_size {\n                        if let Some(c) = self.flush()? {\n                            if !c.succeeded() {\n                                io_yield_one!(c);\n                            }\n                        }\n                        // Check for write errors immediately after flush completes\n                        if self.chunks.iter().any(|chunk| {\n                            matches!(*chunk.io_state.read(), SortedChunkIOState::WriteError)\n                        }) {\n                            return Err(CompletionError::IOError(\n                                std::io::ErrorKind::WriteZero,\n                                \"sorter write\",\n                            )\n                            .into());\n                        }\n                    }\n                }\n                InsertState::Insert => {\n                    let sortable_record = ArenaSortableRecord::new(\n                        &self.arena,\n                        record,\n                        self.key_len,\n                        &self.index_key_info,\n                        &self.comparators,\n                    )?;\n                    let record_ref = self.arena.alloc(sortable_record);\n                    // SAFETY: arena.alloc returns a valid, aligned, non-null pointer\n                    self.records.push(NonNull::from(record_ref));\n                    self.current_buffer_size += payload_size;\n                    self.max_payload_size_in_buffer =\n                        self.max_payload_size_in_buffer.max(payload_size);\n                    self.insert_state = InsertState::Start;\n                    return Ok(IOResult::Done(()));\n                }\n            }\n        }\n    }\n\n    fn init_chunk_heap(&mut self) -> Result<IOResult<()>> {\n        match self.init_chunk_heap_state {\n            InitChunkHeapState::Start => {\n                let mut group = CompletionGroup::new(|_| {});\n                for chunk in self.chunks.iter_mut() {\n                    match chunk.read() {\n                        Err(e) => {\n                            tracing::error!(\"Failed to read chunk: {e}\");\n                            group.cancel();\n                            self.io.drain()?;\n                            return Err(e);\n                        }\n                        Ok(Some(c)) => group.add(&c),\n                        Ok(None) => {}\n                    };\n                }\n                self.init_chunk_heap_state = InitChunkHeapState::PushChunk;\n                let completion = group.build();\n                io_yield_one!(completion);\n            }\n            InitChunkHeapState::PushChunk => {\n                // Make sure all chunks read at least one record into their buffer.\n                turso_assert!(\n                    !self.chunks.iter().any(|chunk| matches!(\n                        *chunk.io_state.read(),\n                        SortedChunkIOState::WaitingForRead\n                    )),\n                    \"chunks should have been read\"\n                );\n                self.chunk_heap.reserve(self.chunks.len());\n                // TODO: blocking will be unnecessary here with IO completions\n                let mut group = CompletionGroup::new(|_| {});\n                for chunk_idx in 0..self.chunks.len() {\n                    if let Some(c) = self.push_to_chunk_heap(chunk_idx)? {\n                        group.add(&c);\n                    };\n                }\n                self.init_chunk_heap_state = InitChunkHeapState::Start;\n                let completion = group.build();\n                if completion.finished() {\n                    Ok(IOResult::Done(()))\n                } else {\n                    io_yield_one!(completion);\n                }\n            }\n        }\n    }\n\n    /// Returns the next record from the chunk heap in sorted order.\n    ///\n    /// The heap contains at most one record per chunk. When we pop a record, we try to refill\n    /// from that chunk. If IO is needed, we store it in `pending_completion` and wait for it\n    /// on the next call before popping again - this ensures all non-exhausted chunks have\n    /// a record in the heap before we decide which is smallest.\n    fn next_from_chunk_heap(&mut self) -> Result<IOResult<Option<Box<BoxedSortableRecord>>>> {\n        // If there is a pending IO, we must wait for it before popping from the heap,\n        // otherwise we might return records out of order.\n        while let Some((completion, chunk_idx)) = self.pending_completion.take() {\n            if !completion.succeeded() {\n                // IO not complete - put it back and yield\n                self.pending_completion = Some((completion.clone(), chunk_idx));\n                return Ok(IOResult::IO(IOCompletions::Single(completion)));\n            }\n            // IO completed - push result to heap and retry\n            if let Some(c) = self.push_to_chunk_heap(chunk_idx)? {\n                self.pending_completion = Some((c, chunk_idx));\n            }\n        }\n\n        // No pending IO - safe to pop from heap\n        if let Some((next_record, chunk_idx)) = self.chunk_heap.pop() {\n            if let Some(c) = self.push_to_chunk_heap(chunk_idx)? {\n                self.pending_completion = Some((c, chunk_idx));\n            }\n            return Ok(IOResult::Done(Some(next_record.0)));\n        }\n\n        // Heap empty and no pending IO - sorter exhausted\n        Ok(IOResult::Done(None))\n    }\n\n    fn push_to_chunk_heap(&mut self, chunk_idx: usize) -> Result<Option<Completion>> {\n        let chunk = &mut self.chunks[chunk_idx];\n\n        match chunk.next()? {\n            ChunkNextResult::Done(Some(record)) => {\n                self.chunk_heap.push((\n                    Reverse(Box::new(BoxedSortableRecord::new(\n                        record,\n                        self.key_len,\n                        self.index_key_info.clone(),\n                        self.comparators.clone(),\n                    )?)),\n                    chunk_idx,\n                ));\n                Ok(None)\n            }\n            ChunkNextResult::Done(None) => Ok(None),\n            ChunkNextResult::IO(io) => Ok(Some(io)),\n        }\n    }\n\n    fn flush(&mut self) -> Result<Option<Completion>> {\n        if self.records.is_empty() {\n            // Dummy completion to not complicate logic handling\n            return Ok(None);\n        }\n\n        // SAFETY: All pointers are valid (arena not reset).\n        self.records\n            .sort_by(|a, b| unsafe { a.as_ref().cmp(b.as_ref()) });\n\n        let chunk_file = match &self.temp_file {\n            Some(temp_file) => temp_file.file.clone(),\n            None => {\n                let temp_file = TempFile::with_temp_store(&self.io, self.temp_store)?;\n                let chunk_file = temp_file.file.clone();\n                self.temp_file = Some(temp_file);\n                chunk_file\n            }\n        };\n\n        // Make sure the chunk buffer size can fit the largest record and its size varint.\n        let chunk_buffer_size = self\n            .min_chunk_read_buffer_size\n            .max(self.max_payload_size_in_buffer + 9);\n\n        let mut chunk_size = 0;\n        // Pre-compute varint lengths for record sizes to determine the total buffer size.\n        // SAFETY: All pointers are valid because they are allocated in the arena,\n        // and the arena hasn't been reset.\n        let mut record_size_lengths = Vec::with_capacity(self.records.len());\n        for ptr in self.records.iter() {\n            let record_size = unsafe { ptr.as_ref().payload().len() };\n            let size_len = varint_len(record_size as u64);\n            record_size_lengths.push(size_len);\n            chunk_size += size_len + record_size;\n        }\n\n        let mut chunk = SortedChunk::new(chunk_file, self.next_chunk_offset, chunk_buffer_size);\n        let c = chunk.write(&self.records, record_size_lengths, chunk_size)?;\n        self.chunks.push(chunk);\n\n        self.records.clear();\n        self.arena.reset();\n\n        self.current_buffer_size = 0;\n        self.max_payload_size_in_buffer = 0;\n        // increase offset start for next chunk\n        self.next_chunk_offset += chunk_size;\n\n        Ok(Some(c))\n    }\n}\n\n#[derive(Debug, Clone, Copy)]\nenum NextState {\n    Start,\n    Finish,\n}\n\n/// A sorted chunk represents a portion of sorted data that has been written to disk\n/// during external merge sort. When the in-memory buffer fills up, records are sorted\n/// and flushed to a chunk file. During the merge phase, chunks are read back and merged\n/// using a heap to produce the final sorted output.\n///\n/// # Buffer management\n///\n/// The chunk uses a fixed-size read buffer (`buffer`) to read data from disk. The buffer\n/// has two relevant sizes:\n/// - `buffer.len()` (capacity): The total allocated size of the buffer (fixed at creation)\n/// - `buffer_len`: The amount of valid data currently in the buffer (0 to capacity)\n///\n/// The difference `buffer.len() - buffer_len` is the free space available for reading\n/// more data from disk.\n///\n/// # Reading progress\n///\n/// - `chunk_size`: Total bytes of this chunk on disk (set when chunk is written)\n/// - `total_bytes_read`: Cumulative bytes read from disk so far (0 to chunk_size)\n///\n/// The difference `chunk_size - total_bytes_read` is the remaining data on disk that\n/// hasn't been read yet. When `total_bytes_read == chunk_size`, we've read all data.\n///\n/// # Record parsing\n///\n/// Data flows: disk -> buffer -> records -> caller\n///\n/// 1. `read()` fills `buffer` from disk, updates `total_bytes_read`\n/// 2. `next()` parses records from `buffer` into `records` vec, updates `buffer_len`\n/// 3. `next()` returns records one at a time from `records`\n///\n/// Incomplete records at the end of the buffer are kept (buffer compacted) until\n/// more data is read to complete them.\nstruct SortedChunk {\n    /// The file containing the chunk data.\n    file: Arc<dyn File>,\n    /// Byte offset where this chunk starts in the file.\n    start_offset: u64,\n    /// Total size of this chunk in bytes (set during write, used to detect EOF during read).\n    chunk_size: usize,\n    /// Fixed-size buffer for reading data from disk. The capacity (`buffer.len()`) is\n    /// constant; use `buffer_len` for the amount of valid data.\n    buffer: Arc<RwLock<Vec<u8>>>,\n    /// Amount of valid (unparsed) data in `buffer`, from index 0 to buffer_len.\n    /// This is separate from buffer.len() because we reuse the same allocation.\n    buffer_len: Arc<atomic::AtomicUsize>,\n    /// Records parsed from the buffer, waiting to be returned by `next()`.\n    /// Stored in reverse order so we can efficiently pop from the end.\n    records: Vec<ImmutableRecord>,\n    /// Current async IO state (None, WaitingForRead, ReadComplete, ReadEOF, etc).\n    io_state: Arc<RwLock<SortedChunkIOState>>,\n    /// Cumulative bytes read from disk. When this equals `chunk_size`, we've read everything.\n    total_bytes_read: Arc<atomic::AtomicUsize>,\n    /// State machine for the `next()` method.\n    next_state: NextState,\n}\n\nenum ChunkNextResult {\n    Done(Option<ImmutableRecord>),\n    IO(Completion),\n}\n\nimpl SortedChunk {\n    fn new(file: Arc<dyn File>, start_offset: usize, buffer_size: usize) -> Self {\n        Self {\n            file,\n            start_offset: start_offset as u64,\n            chunk_size: 0,\n            buffer: Arc::new(RwLock::new(vec![0; buffer_size])),\n            buffer_len: Arc::new(atomic::AtomicUsize::new(0)),\n            records: Vec::new(),\n            io_state: Arc::new(RwLock::new(SortedChunkIOState::None)),\n            total_bytes_read: Arc::new(atomic::AtomicUsize::new(0)),\n            next_state: NextState::Start,\n        }\n    }\n\n    fn buffer_len(&self) -> usize {\n        self.buffer_len.load(atomic::Ordering::SeqCst)\n    }\n\n    fn set_buffer_len(&self, len: usize) {\n        self.buffer_len.store(len, atomic::Ordering::SeqCst);\n    }\n\n    /// Returns the next record from this chunk, or None if exhausted.\n    ///\n    /// May return `ChunkNextResult::IO` if async IO is needed, in which case\n    /// the caller should wait for the completion and call `next()` again.\n    ///\n    /// Internally manages a two-phase state machine:\n    /// - `Start`: Parse records from buffer, issue prefetch read if needed\n    /// - `Finish`: Return the next parsed record\n    fn next(&mut self) -> Result<ChunkNextResult> {\n        loop {\n            match self.next_state {\n                NextState::Start => {\n                    let mut buffer_len = self.buffer_len();\n                    if self.records.is_empty() && buffer_len == 0 {\n                        return Ok(ChunkNextResult::Done(None));\n                    }\n\n                    if self.records.is_empty() {\n                        let mut buffer_ref = self.buffer.write();\n                        let buffer = buffer_ref.as_mut_slice();\n                        let mut buffer_offset = 0;\n                        while buffer_offset < buffer_len {\n                            // Extract records from the buffer until we run out of the buffer or we hit an incomplete record.\n                            let (record_size, bytes_read) =\n                                match read_varint(&buffer[buffer_offset..buffer_len]) {\n                                    Ok((record_size, bytes_read)) => {\n                                        (record_size as usize, bytes_read)\n                                    }\n                                    Err(LimboError::Corrupt(_))\n                                        if *self.io_state.read() != SortedChunkIOState::ReadEOF =>\n                                    {\n                                        // Failed to decode a partial varint.\n                                        break;\n                                    }\n                                    Err(e) => {\n                                        return Err(e);\n                                    }\n                                };\n                            if record_size > buffer_len - (buffer_offset + bytes_read) {\n                                if *self.io_state.read() == SortedChunkIOState::ReadEOF {\n                                    crate::bail_corrupt_error!(\"Incomplete record\");\n                                }\n                                break;\n                            }\n                            buffer_offset += bytes_read;\n\n                            let mut record = ImmutableRecord::new(record_size);\n                            record.start_serialization(\n                                &buffer[buffer_offset..buffer_offset + record_size],\n                            );\n                            buffer_offset += record_size;\n\n                            self.records.push(record);\n                        }\n                        if buffer_offset < buffer_len {\n                            buffer.copy_within(buffer_offset..buffer_len, 0);\n                            buffer_len -= buffer_offset;\n                        } else {\n                            buffer_len = 0;\n                        }\n                        self.set_buffer_len(buffer_len);\n\n                        self.records.reverse();\n                    }\n\n                    self.next_state = NextState::Finish;\n                    // Prefetch: if down to last record, try to read more data into the buffer.\n                    if self.records.len() == 1\n                        && *self.io_state.read() != SortedChunkIOState::ReadEOF\n                    {\n                        if let Some(c) = self.read()? {\n                            if !c.succeeded() {\n                                return Ok(ChunkNextResult::IO(c));\n                            }\n                        }\n                    }\n                }\n                NextState::Finish => {\n                    self.next_state = NextState::Start;\n                    return Ok(ChunkNextResult::Done(self.records.pop()));\n                }\n            }\n        }\n    }\n\n    /// Issues an async read to fill the buffer with more data from the chunk file.\n    ///\n    /// Reads up to `min(free_buffer_space, remaining_chunk_bytes)` bytes. Returns `None`\n    /// if there's no room in the buffer or no data left to read (no IO issued).\n    ///\n    /// On completion, appends data to `buffer` and updates `buffer_len` and `total_bytes_read`.\n    fn read(&mut self) -> Result<Option<Completion>> {\n        let free_buffer_space = self.buffer.read().len() - self.buffer_len();\n        let remaining_chunk_bytes =\n            self.chunk_size - self.total_bytes_read.load(atomic::Ordering::SeqCst);\n        let read_buffer_size = free_buffer_space.min(remaining_chunk_bytes);\n\n        // If there's no room in the buffer or nothing left to read, skip the read.\n        if read_buffer_size == 0 {\n            if remaining_chunk_bytes == 0 {\n                // No more data in the chunk file.\n                *self.io_state.write() = SortedChunkIOState::ReadEOF;\n            }\n            return Ok(None);\n        }\n\n        *self.io_state.write() = SortedChunkIOState::WaitingForRead;\n\n        let read_buffer = Buffer::new_temporary(read_buffer_size);\n        let read_buffer_ref = Arc::new(read_buffer);\n\n        let chunk_io_state_copy = self.io_state.clone();\n        let stored_buffer_copy = self.buffer.clone();\n        let stored_buffer_len_copy = self.buffer_len.clone();\n        let total_bytes_read_copy = self.total_bytes_read.clone();\n        let read_complete = Box::new(move |res: Result<(Arc<Buffer>, i32), CompletionError>| {\n            let Ok((buf, bytes_read)) = res else {\n                return None;\n            };\n            let read_buf = buf.as_slice();\n\n            let bytes_read = bytes_read as usize;\n            if bytes_read == 0 {\n                *chunk_io_state_copy.write() = SortedChunkIOState::ReadEOF;\n                return None;\n            }\n            *chunk_io_state_copy.write() = SortedChunkIOState::ReadComplete;\n\n            let mut stored_buf_ref = stored_buffer_copy.write();\n            let stored_buf = stored_buf_ref.as_mut_slice();\n            let mut stored_buf_len = stored_buffer_len_copy.load(atomic::Ordering::SeqCst);\n\n            stored_buf[stored_buf_len..stored_buf_len + bytes_read]\n                .copy_from_slice(&read_buf[..bytes_read]);\n            stored_buf_len += bytes_read;\n\n            stored_buffer_len_copy.store(stored_buf_len, atomic::Ordering::SeqCst);\n            total_bytes_read_copy.fetch_add(bytes_read, atomic::Ordering::SeqCst);\n            None\n        });\n\n        let c = Completion::new_read(read_buffer_ref, read_complete);\n        let c = self.file.pread(\n            self.start_offset + self.total_bytes_read.load(atomic::Ordering::SeqCst) as u64,\n            c,\n        )?;\n        Ok(Some(c))\n    }\n\n    fn write(\n        &mut self,\n        records: &[NonNull<ArenaSortableRecord>],\n        record_size_lengths: Vec<usize>,\n        chunk_size: usize,\n    ) -> Result<Completion> {\n        turso_assert_eq!(*self.io_state.read(), SortedChunkIOState::None);\n        *self.io_state.write() = SortedChunkIOState::WaitingForWrite;\n        self.chunk_size = chunk_size;\n\n        let buffer = Buffer::new_temporary(self.chunk_size);\n\n        let mut buf_pos = 0;\n        let buf = buffer.as_mut_slice();\n        for (ptr, size_len) in records.iter().zip(record_size_lengths) {\n            // SAFETY: All pointers are valid (arena not reset).\n            let payload = unsafe { ptr.as_ref().payload() };\n            // Write the record size varint.\n            write_varint(&mut buf[buf_pos..buf_pos + size_len], payload.len() as u64);\n            buf_pos += size_len;\n            // Write the record payload.\n            buf[buf_pos..buf_pos + payload.len()].copy_from_slice(payload);\n            buf_pos += payload.len();\n        }\n\n        let buffer_ref = Arc::new(buffer);\n\n        let buffer_ref_copy = buffer_ref.clone();\n        let chunk_io_state_copy = self.io_state.clone();\n        let write_complete = Box::new(move |res: Result<i32, CompletionError>| {\n            let Ok(bytes_written) = res else {\n                *chunk_io_state_copy.write() = SortedChunkIOState::WriteError;\n                return;\n            };\n            let buf_len = buffer_ref_copy.len();\n            if bytes_written < buf_len as i32 {\n                tracing::error!(\"wrote({bytes_written}) less than expected({buf_len})\");\n                *chunk_io_state_copy.write() = SortedChunkIOState::WriteError;\n            } else {\n                *chunk_io_state_copy.write() = SortedChunkIOState::WriteComplete;\n            }\n        });\n\n        let c = Completion::new_write(write_complete);\n        let c = self.file.pwrite(self.start_offset, buffer_ref, c)?;\n        Ok(c)\n    }\n}\n\n/// Record for in-memory sorting. All data lives in the arena, so no Drop is needed.\nstruct ArenaSortableRecord {\n    /// Payload bytes in arena. Using NonNull avoids lifetime issues with\n    /// self-referential struct (key_values points into this payload).\n    payload: NonNull<[u8]>,\n    /// Pre-computed key values in arena. Points into `payload`.\n    key_values: NonNull<[ValueRef<'static>]>,\n    /// Shared KeyInfo owned by Sorter. Avoids Rc refcount overhead that would\n    /// leak when arena.reset() skips Drop.\n    index_key_info: NonNull<[KeyInfo]>,\n    /// Shared comparators owned by Sorter. Same safety model as index_key_info.\n    comparators: NonNull<[Option<SortComparator>]>,\n}\n\nimpl ArenaSortableRecord {\n    fn new(\n        arena: &Bump,\n        record: &ImmutableRecord,\n        key_len: usize,\n        index_key_info: &[KeyInfo],\n        comparators: &[Option<SortComparator>],\n    ) -> Result<Self> {\n        let payload = arena.alloc_slice_copy(record.get_payload());\n\n        let mut payload_iter = ValueIterator::new(payload)?;\n\n        let mut key_values =\n            bumpalo::collections::Vec::with_capacity_in(payload_iter.clone().count(), arena);\n        for _ in 0..key_len {\n            let value = match payload_iter.next() {\n                Some(Ok(v)) => v,\n                Some(Err(e)) => return Err(e),\n                None => crate::bail_corrupt_error!(\"Not enough columns in record\"),\n            };\n            // SAFETY: value borrows from payload which is in the arena and outlives this struct.\n            let value: ValueRef<'static> = unsafe { std::mem::transmute(value) };\n            key_values.push(value);\n        }\n\n        Ok(Self {\n            payload: NonNull::from(payload),\n            key_values: NonNull::from(key_values.into_bump_slice()),\n            index_key_info: NonNull::from(index_key_info),\n            comparators: NonNull::from(comparators),\n        })\n    }\n\n    #[inline]\n    fn key_values(&self) -> &[ValueRef<'static>] {\n        // SAFETY: valid from construction, arena not reset\n        unsafe { self.key_values.as_ref() }\n    }\n\n    #[inline]\n    fn payload(&self) -> &[u8] {\n        // SAFETY: valid from construction, arena not reset\n        unsafe { self.payload.as_ref() }\n    }\n\n    /// Create an ImmutableRecord by copying payload bytes out of the arena.\n    fn to_immutable_record(&self) -> ImmutableRecord {\n        let payload = self.payload();\n        let mut record = ImmutableRecord::new(payload.len());\n        record.start_serialization(payload);\n        record\n    }\n}\n\nimpl Ord for ArenaSortableRecord {\n    #[inline]\n    fn cmp(&self, other: &Self) -> Ordering {\n        let self_values = self.key_values();\n        let other_values = other.key_values();\n        // SAFETY: index_key_info and comparators point to Sorter-owned data that outlives all records.\n        let index_key_info = unsafe { self.index_key_info.as_ref() };\n        let comparators = unsafe { self.comparators.as_ref() };\n\n        for (i, ((&self_val, &other_val), key_info)) in self_values\n            .iter()\n            .zip(other_values.iter())\n            .zip(index_key_info.iter())\n            .enumerate()\n        {\n            let cmp = if let Some(Some(comparator)) = comparators.get(i) {\n                comparator(&self_val, &other_val)\n            } else {\n                match (self_val, other_val) {\n                    (ValueRef::Text(left), ValueRef::Text(right)) => {\n                        key_info.collation.compare_strings(&left, &right)\n                    }\n                    _ => self_val.partial_cmp(&other_val).unwrap_or(Ordering::Equal),\n                }\n            };\n            if cmp != Ordering::Equal {\n                return match key_info.sort_order {\n                    SortOrder::Asc => cmp,\n                    SortOrder::Desc => cmp.reverse(),\n                };\n            }\n        }\n        Ordering::Equal\n    }\n}\n\nimpl PartialOrd for ArenaSortableRecord {\n    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {\n        Some(self.cmp(other))\n    }\n}\n\nimpl PartialEq for ArenaSortableRecord {\n    fn eq(&self, other: &Self) -> bool {\n        self.cmp(other) == Ordering::Equal\n    }\n}\n\nimpl Eq for ArenaSortableRecord {}\n\n/// Heap-allocated record for external merge sort. Used when records are read\n/// back from chunk files. Normal Drop semantics apply.\nstruct BoxedSortableRecord {\n    record: ImmutableRecord,\n    key_values: Vec<ValueRef<'static>>,\n    index_key_info: Rc<Vec<KeyInfo>>,\n    comparators: Rc<Vec<Option<SortComparator>>>,\n    deserialization_error: Option<LimboError>,\n}\n\nimpl BoxedSortableRecord {\n    fn new(\n        record: ImmutableRecord,\n        key_len: usize,\n        index_key_info: Rc<Vec<KeyInfo>>,\n        comparators: Rc<Vec<Option<SortComparator>>>,\n    ) -> Result<Self> {\n        let mut value_iterator = record.iter()?;\n        let mut key_values = Vec::with_capacity(key_len);\n        let mut deserialization_error = None;\n\n        for _ in 0..key_len {\n            match value_iterator.next() {\n                Some(Ok(value)) => {\n                    // SAFETY: value points into record which lives as long as this struct\n                    let value: ValueRef<'static> = unsafe { std::mem::transmute(value) };\n                    key_values.push(value);\n                }\n                Some(Err(err)) => {\n                    mark_unlikely();\n                    deserialization_error = Some(err);\n                    break;\n                }\n                None => {\n                    mark_unlikely();\n                    deserialization_error = Some(LimboError::Corrupt(\n                        \"Not enough columns in record\".to_string(),\n                    ));\n                    break;\n                }\n            }\n        }\n\n        Ok(Self {\n            record,\n            key_values,\n            index_key_info,\n            comparators,\n            deserialization_error,\n        })\n    }\n}\n\nimpl Ord for BoxedSortableRecord {\n    #[inline]\n    fn cmp(&self, other: &Self) -> Ordering {\n        if self.deserialization_error.is_some() || other.deserialization_error.is_some() {\n            return Ordering::Equal;\n        }\n\n        for (i, ((&self_val, &other_val), key_info)) in self\n            .key_values\n            .iter()\n            .zip(other.key_values.iter())\n            .zip(self.index_key_info.iter())\n            .enumerate()\n        {\n            let cmp = if let Some(Some(comparator)) = self.comparators.get(i) {\n                comparator(&self_val, &other_val)\n            } else {\n                match (self_val, other_val) {\n                    (ValueRef::Text(left), ValueRef::Text(right)) => {\n                        key_info.collation.compare_strings(&left, &right)\n                    }\n                    _ => self_val.partial_cmp(&other_val).unwrap_or(Ordering::Equal),\n                }\n            };\n            if cmp != Ordering::Equal {\n                return match key_info.sort_order {\n                    SortOrder::Asc => cmp,\n                    SortOrder::Desc => cmp.reverse(),\n                };\n            }\n        }\n        Ordering::Equal\n    }\n}\n\nimpl PartialOrd for BoxedSortableRecord {\n    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {\n        Some(self.cmp(other))\n    }\n}\n\nimpl PartialEq for BoxedSortableRecord {\n    fn eq(&self, other: &Self) -> bool {\n        self.cmp(other) == Ordering::Equal\n    }\n}\n\nimpl Eq for BoxedSortableRecord {}\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq)]\nenum SortedChunkIOState {\n    WaitingForRead,\n    ReadComplete,\n    WaitingForWrite,\n    WriteComplete,\n    WriteError,\n    ReadEOF,\n    None,\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::translate::collate::CollationSeq;\n    use crate::types::{ImmutableRecord, Value, ValueRef, ValueType};\n    use crate::util::IOExt;\n    use crate::PlatformIO;\n    use rand_chacha::{\n        rand_core::{RngCore, SeedableRng},\n        ChaCha8Rng,\n    };\n\n    fn get_seed() -> u64 {\n        std::env::var(\"SEED\").map_or(\n            std::time::SystemTime::now()\n                .duration_since(std::time::UNIX_EPOCH)\n                .unwrap()\n                .as_millis(),\n            |v| {\n                v.parse()\n                    .expect(\"Failed to parse SEED environment variable as u64\")\n            },\n        ) as u64\n    }\n\n    #[test]\n    fn fuzz_external_sort() {\n        let seed = get_seed();\n        let mut rng = ChaCha8Rng::seed_from_u64(seed);\n\n        let io = Arc::new(PlatformIO::new().unwrap());\n\n        let attempts = 8;\n        for _ in 0..attempts {\n            let mut sorter = Sorter::new(\n                &[SortOrder::Asc],\n                vec![CollationSeq::Binary],\n                vec![None],\n                256,\n                64,\n                io.clone(),\n                crate::TempStore::Default,\n            );\n\n            let num_records = 1000 + rng.next_u64() % 2000;\n            let num_records = num_records as i64;\n\n            let num_values = 1 + rng.next_u64() % 4;\n            let value_types = generate_value_types(&mut rng, num_values as usize);\n\n            let mut initial_records = Vec::with_capacity(num_records as usize);\n            for i in (0..num_records).rev() {\n                let mut values = vec![Value::from_i64(i)];\n                values.append(&mut generate_values(&mut rng, &value_types));\n                let record = ImmutableRecord::from_values(&values, values.len());\n\n                io.block(|| sorter.insert(&record))\n                    .expect(\"Failed to insert the record\");\n                initial_records.push(record);\n            }\n\n            io.block(|| sorter.sort())\n                .expect(\"Failed to sort the records\");\n\n            assert!(!sorter.is_empty());\n            assert!(!sorter.chunks.is_empty());\n\n            for i in 0..num_records {\n                assert!(sorter.has_more());\n                let record = sorter.record().unwrap();\n                assert_eq!(record.get_values().unwrap()[0], ValueRef::from_i64(i));\n                // Check that the record remained unchanged after sorting.\n                assert_eq!(record, &initial_records[(num_records - i - 1) as usize]);\n\n                io.block(|| sorter.next())\n                    .expect(\"Failed to get the next record\");\n            }\n            assert!(!sorter.has_more());\n        }\n    }\n\n    fn generate_value_types<R: RngCore>(rng: &mut R, num_values: usize) -> Vec<ValueType> {\n        let mut value_types = Vec::with_capacity(num_values);\n\n        for _ in 0..num_values {\n            let value_type: ValueType = match rng.next_u64() % 4 {\n                0 => ValueType::Integer,\n                1 => ValueType::Float,\n                2 => ValueType::Blob,\n                3 => ValueType::Null,\n                _ => unreachable!(),\n            };\n            value_types.push(value_type);\n        }\n\n        value_types\n    }\n\n    fn generate_values<R: RngCore>(rng: &mut R, value_types: &[ValueType]) -> Vec<Value> {\n        let mut values = Vec::with_capacity(value_types.len());\n        for value_type in value_types {\n            let value = match value_type {\n                ValueType::Integer => Value::from_i64(rng.next_u64() as i64),\n                ValueType::Float => {\n                    let numerator = rng.next_u64() as f64;\n                    let denominator = rng.next_u64() as f64;\n                    Value::from_f64(numerator / denominator)\n                }\n                ValueType::Blob => {\n                    let mut blob = Vec::with_capacity((rng.next_u64() % 2047 + 1) as usize);\n                    rng.fill_bytes(&mut blob);\n                    Value::Blob(blob)\n                }\n                ValueType::Null => Value::Null,\n                _ => unreachable!(),\n            };\n            values.push(value);\n        }\n        values\n    }\n}\n"
  },
  {
    "path": "core/vdbe/value.rs",
    "content": "use crate::turso_assert;\nuse crate::{\n    function::MathFunc,\n    numeric::{format_float, format_float_for_quote, NullableInteger, Numeric},\n    translate::collate::CollationSeq,\n    types::{compare_immutable_single, AsValueRef, SeekOp},\n    vdbe::affinity::{real_to_i64, Affinity},\n    LimboError, Result, Value, ValueRef,\n};\n\n// we use math functions from Rust stdlib in order to be as portable as possible for the production version of the tursodb\n#[cfg(not(test))]\nmod cmath {\n    pub fn exp(x: f64) -> f64 {\n        x.exp()\n    }\n    pub fn log(x: f64) -> f64 {\n        x.ln()\n    }\n    pub fn log10(x: f64) -> f64 {\n        x.log(10.)\n    }\n    pub fn log2(x: f64) -> f64 {\n        x.log(2.)\n    }\n    pub fn pow(x: f64, y: f64) -> f64 {\n        x.powf(y)\n    }\n    pub fn sin(x: f64) -> f64 {\n        x.sin()\n    }\n    pub fn sinh(x: f64) -> f64 {\n        x.sinh()\n    }\n    pub fn asin(x: f64) -> f64 {\n        x.asin()\n    }\n    pub fn asinh(x: f64) -> f64 {\n        x.asinh()\n    }\n    pub fn cos(x: f64) -> f64 {\n        x.cos()\n    }\n    pub fn cosh(x: f64) -> f64 {\n        x.cosh()\n    }\n    pub fn acos(x: f64) -> f64 {\n        x.acos()\n    }\n    pub fn acosh(x: f64) -> f64 {\n        x.acosh()\n    }\n    pub fn tan(x: f64) -> f64 {\n        x.tan()\n    }\n    pub fn tanh(x: f64) -> f64 {\n        x.tanh()\n    }\n    pub fn atan(x: f64) -> f64 {\n        x.atan()\n    }\n    pub fn atanh(x: f64) -> f64 {\n        x.atanh()\n    }\n    pub fn atan2(x: f64, y: f64) -> f64 {\n        x.atan2(y)\n    }\n    pub fn degrees(x: f64) -> f64 {\n        x.to_degrees()\n    }\n    pub fn radians(x: f64) -> f64 {\n        x.to_radians()\n    }\n}\n\n// we use exactly same math function as SQLite in tests in order to avoid mismatch in the differential tests due to floating-point precision issues\n#[cfg(test)]\nmod cmath {\n    extern \"C\" {\n        pub fn exp(x: f64) -> f64;\n        pub fn log(x: f64) -> f64;\n        pub fn log10(x: f64) -> f64;\n        pub fn log2(x: f64) -> f64;\n        pub fn pow(x: f64, y: f64) -> f64;\n\n        pub fn sin(x: f64) -> f64;\n        pub fn sinh(x: f64) -> f64;\n        pub fn asin(x: f64) -> f64;\n        pub fn asinh(x: f64) -> f64;\n\n        pub fn cos(x: f64) -> f64;\n        pub fn cosh(x: f64) -> f64;\n        pub fn acos(x: f64) -> f64;\n        pub fn acosh(x: f64) -> f64;\n\n        pub fn tan(x: f64) -> f64;\n        pub fn tanh(x: f64) -> f64;\n        pub fn atan(x: f64) -> f64;\n        pub fn atanh(x: f64) -> f64;\n        pub fn atan2(x: f64, y: f64) -> f64;\n    }\n\n    // SQLite's M_PI constant (same value as SQLite's func.c)\n    #[allow(clippy::excessive_precision)]\n    const M_PI: f64 = 3.141592653589793238462643383279502884;\n\n    pub fn degrees(x: f64) -> f64 {\n        x * 180.0 / M_PI\n    }\n    pub fn radians(x: f64) -> f64 {\n        x * M_PI / 180.0\n    }\n}\n\n#[derive(Debug, Clone, Copy, PartialEq)]\npub(super) enum ComparisonOp {\n    Eq,\n    Ne,\n    Lt,\n    Le,\n    Gt,\n    Ge,\n}\n\nimpl ComparisonOp {\n    pub(super) fn compare<V1: AsValueRef, V2: AsValueRef>(\n        &self,\n        lhs: V1,\n        rhs: V2,\n        collation: CollationSeq,\n    ) -> bool {\n        let order = compare_immutable_single(lhs, rhs, collation);\n        match self {\n            ComparisonOp::Eq => order.is_eq(),\n            ComparisonOp::Ne => order.is_ne(),\n            ComparisonOp::Lt => order.is_lt(),\n            ComparisonOp::Le => order.is_le(),\n            ComparisonOp::Gt => order.is_gt(),\n            ComparisonOp::Ge => order.is_ge(),\n        }\n    }\n\n    pub(super) fn compare_nulls<V1: AsValueRef, V2: AsValueRef>(\n        &self,\n        lhs: V1,\n        rhs: V2,\n        null_eq: bool,\n    ) -> bool {\n        let (lhs, rhs) = (lhs.as_value_ref(), rhs.as_value_ref());\n        turso_assert!(matches!(lhs, ValueRef::Null) || matches!(rhs, ValueRef::Null));\n\n        match self {\n            ComparisonOp::Eq => {\n                let both_null = lhs == rhs;\n                null_eq && both_null\n            }\n            ComparisonOp::Ne => {\n                let at_least_one_null = lhs != rhs;\n                null_eq && at_least_one_null\n            }\n            ComparisonOp::Lt | ComparisonOp::Le | ComparisonOp::Gt | ComparisonOp::Ge => false,\n        }\n    }\n}\n\nimpl From<SeekOp> for ComparisonOp {\n    fn from(value: SeekOp) -> Self {\n        match value {\n            SeekOp::GE { eq_only: true } | SeekOp::LE { eq_only: true } => ComparisonOp::Eq,\n            SeekOp::GE { eq_only: false } => ComparisonOp::Ge,\n            SeekOp::GT => ComparisonOp::Gt,\n            SeekOp::LE { eq_only: false } => ComparisonOp::Le,\n            SeekOp::LT => ComparisonOp::Lt,\n        }\n    }\n}\n\nenum TrimType {\n    All,\n    Left,\n    Right,\n}\n\nimpl Value {\n    pub fn exec_lower(&self) -> Option<Self> {\n        self.cast_text()\n            .map(|s| Value::build_text(s.to_ascii_lowercase()))\n    }\n\n    pub fn exec_length(&self) -> Self {\n        match self {\n            Value::Text(t) => {\n                let s = t.as_str();\n                let len_before_null = s.find('\\0').map_or_else(\n                    || s.chars().count(),\n                    |null_pos| s[..null_pos].chars().count(),\n                );\n                Value::from_i64(len_before_null as i64)\n            }\n            Value::Numeric(_) => {\n                // For numbers, SQLite returns the length of the string representation\n                Value::from_i64(self.to_string().chars().count() as i64)\n            }\n            Value::Blob(blob) => Value::from_i64(blob.len() as i64),\n            _ => self.to_owned(),\n        }\n    }\n\n    pub fn exec_octet_length(&self) -> Self {\n        match self {\n            Value::Text(s) => Value::from_i64(s.as_str().len() as i64),\n            Value::Blob(blob) => Value::from_i64(blob.len() as i64),\n            Value::Numeric(_) => Value::from_i64(self.to_string().len() as i64),\n            _ => self.to_owned(),\n        }\n    }\n\n    pub fn exec_upper(&self) -> Option<Self> {\n        self.cast_text()\n            .map(|s| Value::build_text(s.to_ascii_uppercase()))\n    }\n\n    pub fn exec_sign(&self) -> Option<Value> {\n        let v = Numeric::from_value_strict(self).map(|value| value.to_f64())?;\n\n        Some(Value::from_i64(if v > 0.0 {\n            1\n        } else if v < 0.0 {\n            -1\n        } else {\n            0\n        }))\n    }\n\n    /// Generates the Soundex code for a given word\n    pub fn exec_soundex(&self) -> Value {\n        let s = match self {\n            Value::Text(s) => s.as_str(),\n            Value::Null => return Value::build_text(\"?000\"),\n            _ => return Value::build_text(\"?000\"),\n        };\n\n        if s.bytes().any(|b| !b.is_ascii_alphabetic()) {\n            return Value::build_text(\"?000\");\n        }\n\n        let mut bytes = s.bytes();\n        let Some(first_char) = bytes.next() else {\n            return Value::build_text(\"?000\");\n        };\n\n        let first_upper = first_char.to_ascii_uppercase();\n        let mut result = String::with_capacity(4);\n        result.push(first_upper as char);\n        let get_code = |b: u8| -> Option<char> {\n            match b.to_ascii_lowercase() {\n                b'b' | b'f' | b'p' | b'v' => Some('1'),\n                b'c' | b'g' | b'j' | b'k' | b'q' | b's' | b'x' | b'z' => Some('2'),\n                b'd' | b't' => Some('3'),\n                b'l' => Some('4'),\n                b'm' | b'n' => Some('5'),\n                b'r' => Some('6'),\n                _ => None, // a, e, i, o, u, y, h, w\n            }\n        };\n\n        let mut prev_code = get_code(first_char);\n\n        for b in bytes {\n            if result.len() >= 4 {\n                break;\n            }\n\n            // H and W are ignored completely in this step for continuity checks\n            let lower = b.to_ascii_lowercase();\n            if lower == b'h' || lower == b'w' {\n                continue;\n            }\n\n            let code = get_code(b);\n            if code.is_some() && code != prev_code {\n                result.push(code.unwrap());\n                prev_code = code;\n            } else if code.is_none() {\n                // Reset previous code for vowels/separators (a,e,i,o,u,y)\n                prev_code = None;\n            }\n        }\n\n        while result.len() < 4 {\n            result.push('0');\n        }\n\n        Value::build_text(result)\n    }\n\n    pub fn exec_abs(&self) -> Result<Self> {\n        Ok(match self {\n            Value::Null => Value::Null,\n            Value::Numeric(Numeric::Integer(v)) => {\n                Value::from_i64(v.checked_abs().ok_or(LimboError::IntegerOverflow)?)\n            }\n            Value::Numeric(Numeric::Float(non_nan)) => Value::from_f64(f64::from(*non_nan).abs()),\n            _ => {\n                let s = match self {\n                    Value::Text(text) => std::borrow::Cow::Borrowed(text.as_str()),\n                    Value::Blob(blob) => String::from_utf8_lossy(blob),\n                    _ => unreachable!(),\n                };\n\n                crate::numeric::str_to_f64(s)\n                    .map(|v| Value::from_f64(f64::from(v).abs()))\n                    .unwrap_or_else(|| Value::from_f64(0.0))\n            }\n        })\n    }\n\n    pub fn exec_random<F>(generate_random_number: F) -> Self\n    where\n        F: Fn() -> i64,\n    {\n        Value::from_i64(generate_random_number())\n    }\n\n    /// SQLite default max blob/string size (1GB)\n    pub const MAX_BLOB_LENGTH: i64 = 1_000_000_000;\n\n    pub fn exec_randomblob<F>(&self, fill_bytes: F) -> Result<Value>\n    where\n        F: Fn(&mut [u8]),\n    {\n        let length = match self {\n            Value::Numeric(Numeric::Integer(i)) => *i,\n            Value::Numeric(Numeric::Float(f)) => f64::from(*f) as i64,\n            Value::Text(t) => t.as_str().parse().unwrap_or(1),\n            _ => 1,\n        }\n        .max(1);\n\n        if length > Self::MAX_BLOB_LENGTH {\n            return Err(LimboError::TooBig);\n        }\n\n        let mut blob: Vec<u8> = vec![0; length as usize];\n        fill_bytes(&mut blob);\n        Ok(Value::Blob(blob))\n    }\n\n    pub fn exec_quote(&self) -> Self {\n        use std::fmt::Write;\n        match self {\n            Value::Null => Value::build_text(\"NULL\"),\n            Value::Numeric(Numeric::Integer(i)) => Value::build_text(i.to_string()),\n            Value::Numeric(Numeric::Float(f)) => {\n                Value::build_text(format_float_for_quote(f64::from(*f)))\n            }\n            Value::Blob(b) => {\n                // SQLite returns X'hexdigits' for blobs\n                let mut quoted = String::with_capacity(3 + b.len() * 2);\n                quoted.push_str(\"X'\");\n                for byte in b.iter() {\n                    write!(&mut quoted, \"{byte:02X}\").expect(\"unable to write hex bytes\");\n                }\n                quoted.push('\\'');\n                Value::build_text(quoted)\n            }\n            Value::Text(s) => {\n                let mut quoted = String::with_capacity(s.as_str().len() + 2);\n                quoted.push('\\'');\n                for c in s.as_str().chars() {\n                    if c == '\\0' {\n                        break;\n                    } else if c == '\\'' {\n                        quoted.push('\\'');\n                        quoted.push(c);\n                    } else {\n                        quoted.push(c);\n                    }\n                }\n                quoted.push('\\'');\n                Value::build_text(quoted)\n            }\n        }\n    }\n\n    pub fn exec_nullif(&self, second_value: &Self) -> Self {\n        if self != second_value {\n            self.clone()\n        } else {\n            Value::Null\n        }\n    }\n\n    pub fn exec_substring(\n        value: &Value,\n        start_value: &Value,\n        length_value: Option<&Value>,\n    ) -> Value {\n        /// Function is stabilized but not released for version 1.88 \\\n        /// https://doc.rust-lang.org/src/core/str/mod.rs.html#453\n        const fn ceil_char_boundary(s: &str, index: usize) -> usize {\n            const fn is_utf8_char_boundary(c: u8) -> bool {\n                // This is bit magic equivalent to: b < 128 || b >= 192\n                (c as i8) >= -0x40\n            }\n\n            if index >= s.len() {\n                s.len()\n            } else {\n                let mut i = index;\n                while i < s.len() {\n                    if is_utf8_char_boundary(s.as_bytes()[i]) {\n                        break;\n                    }\n                    i += 1;\n                }\n\n                //  The character boundary will be within four bytes of the index\n                debug_assert!(i <= index + 3);\n\n                i\n            }\n        }\n\n        // Match SQLite's substr algorithm exactly (func.c substrFunc)\n        // Uses wrapping arithmetic to match C overflow behavior\n        fn calculate_postions(\n            mut p1: i64,\n            len: usize,\n            length_value: Option<&Value>,\n        ) -> (usize, usize) {\n            let len = len as i64;\n            let mut p2 = match length_value {\n                Some(Value::Numeric(Numeric::Integer(length))) => *length,\n                // SQLite uses SQLITE_LIMIT_LENGTH (default 1 billion) when no explicit length.\n                // Using len causes wrong results when p1 is large negative number.\n                _ => Value::MAX_BLOB_LENGTH,\n            };\n\n            // Track if length was explicitly provided\n            let explicit_length = length_value.is_some();\n\n            // Handle negative start position (count from end)\n            if p1 < 0 {\n                p1 = p1.wrapping_add(len);\n                if p1 < 0 {\n                    p2 = p2.wrapping_add(p1);\n                    p1 = 0;\n                }\n            } else if p1 > 0 {\n                p1 -= 1; // Convert 1-indexed to 0-indexed\n            } else if p2 > 0 && explicit_length {\n                // SQLite quirk: when p1==0, p2>0, and explicit length, decrement p2\n                // This means substr('x', 0, 3) returns 2 chars, not 3\n                // But substr('x', 0) with no length returns whole string\n                p2 -= 1;\n            }\n\n            // Handle negative length (characters preceding position)\n            if p2 < 0 {\n                if p2 < -p1 {\n                    p2 = p1;\n                } else {\n                    p2 = -p2;\n                }\n                p1 -= p2;\n            }\n\n            // Clamp to valid range\n            let start = p1.max(0).min(len) as usize;\n            let end = p1.saturating_add(p2).max(0).min(len) as usize;\n            (start, end)\n        }\n\n        let start_value = start_value.exec_cast(\"INT\");\n        let length_value = length_value.map(|value| value.exec_cast(\"INT\"));\n\n        // If length is explicitly NULL, return NULL (SQLite behavior)\n        if matches!(length_value, Some(Value::Null)) {\n            return Value::Null;\n        }\n\n        match (value, start_value) {\n            (Value::Blob(b), Value::Numeric(Numeric::Integer(start))) => {\n                let (start, end) = calculate_postions(start, b.len(), length_value.as_ref());\n                Value::from_blob(b[start..end].to_vec())\n            }\n            (value, Value::Numeric(Numeric::Integer(start))) => {\n                if let Some(text) = value.cast_text() {\n                    // Use character count to accurately resolve negative offsets in UTF-8 strings\n                    let char_count = text.chars().count();\n                    let (mut start, mut end) =\n                        calculate_postions(start, char_count, length_value.as_ref());\n\n                    // https://github.com/sqlite/sqlite/blob/a248d84f/src/func.c#L417\n                    let s = text.as_str();\n                    let mut start_byte_idx = 0;\n                    end -= start;\n                    while start > 0 {\n                        start_byte_idx = ceil_char_boundary(s, start_byte_idx + 1);\n                        start -= 1;\n                    }\n                    let mut end_byte_idx = start_byte_idx;\n                    while end > 0 {\n                        end_byte_idx = ceil_char_boundary(s, end_byte_idx + 1);\n                        end -= 1;\n                    }\n                    Value::build_text(s[start_byte_idx..end_byte_idx].to_string())\n                } else {\n                    Value::Null\n                }\n            }\n            _ => Value::Null,\n        }\n    }\n\n    pub fn exec_instr(&self, pattern: &Value) -> Value {\n        if self == &Value::Null || pattern == &Value::Null {\n            return Value::Null;\n        }\n\n        if let (Value::Blob(reg), Value::Blob(pattern)) = (self, pattern) {\n            // SQLite returns 1 for empty pattern (found at position 1)\n            if pattern.is_empty() {\n                return Value::from_i64(1);\n            }\n            let result = reg\n                .windows(pattern.len())\n                .position(|window| window == *pattern)\n                .map_or(0, |i| i + 1);\n            return Value::from_i64(result as i64);\n        }\n\n        let reg_str;\n        let reg = match self {\n            Value::Text(s) => s.as_str(),\n            _ => {\n                reg_str = self.to_string();\n                reg_str.as_str()\n            }\n        };\n\n        let pattern_str;\n        let pattern = match pattern {\n            Value::Text(s) => s.as_str(),\n            _ => {\n                pattern_str = pattern.to_string();\n                pattern_str.as_str()\n            }\n        };\n\n        match reg.find(pattern) {\n            Some(byte_pos) => {\n                // Convert byte position to character position (1-indexed)\n                let char_pos = reg[..byte_pos].chars().count() + 1;\n                Value::from_i64(char_pos as i64)\n            }\n            None => Value::from_i64(0),\n        }\n    }\n\n    pub fn exec_typeof(&self) -> Value {\n        match self {\n            Value::Null => Value::build_text(\"null\"),\n            Value::Numeric(Numeric::Integer(_)) => Value::build_text(\"integer\"),\n            Value::Numeric(Numeric::Float(_)) => Value::build_text(\"real\"),\n            Value::Text(_) => Value::build_text(\"text\"),\n            Value::Blob(_) => Value::build_text(\"blob\"),\n        }\n    }\n\n    pub fn exec_hex(&self) -> Value {\n        match self {\n            Value::Text(_) | Value::Numeric(_) => {\n                let text = self.to_string();\n                Value::build_text(hex::encode_upper(text))\n            }\n            Value::Blob(blob_bytes) => Value::build_text(hex::encode_upper(blob_bytes)),\n            Value::Null => Value::build_text(\"\"),\n        }\n    }\n\n    pub fn exec_unhex(&self, ignored_chars: Option<&Value>) -> Value {\n        match self {\n            Value::Null => Value::Null,\n            _ => match ignored_chars {\n                None => match self\n                    .cast_text()\n                    .map(|s| hex::decode(&s[0..s.find('\\0').unwrap_or(s.len())]))\n                {\n                    Some(Ok(bytes)) => Value::Blob(bytes),\n                    _ => Value::Null,\n                },\n                Some(ignore) => match ignore {\n                    Value::Text(_) => {\n                        let pat = ignore.to_string();\n                        let trimmed = self\n                            .to_string()\n                            .trim_start_matches(|x| pat.contains(x))\n                            .trim_end_matches(|x| pat.contains(x))\n                            .to_string();\n                        match hex::decode(trimmed) {\n                            Ok(bytes) => Value::Blob(bytes),\n                            _ => Value::Null,\n                        }\n                    }\n                    _ => Value::Null,\n                },\n            },\n        }\n    }\n\n    pub fn exec_unicode(&self) -> Value {\n        match self {\n            Value::Text(_) | Value::Numeric(_) | Value::Blob(_) => {\n                let text = self.to_string();\n                if let Some(first_char) = text.chars().next() {\n                    if first_char == '\\0' {\n                        return Value::Null;\n                    }\n                    Value::from_i64(first_char as u32 as i64)\n                } else {\n                    Value::Null\n                }\n            }\n            _ => Value::Null,\n        }\n    }\n\n    pub fn exec_round(&self, precision: Option<&Value>) -> Value {\n        let Some(f) = Numeric::from_value(self).map(|v| v.to_f64()) else {\n            return Value::Null;\n        };\n\n        let precision = match precision.map(|v| Numeric::from_value(v).map(|v| v.to_f64())) {\n            None => 0.0,\n            Some(Some(v)) => v,\n            Some(None) => return Value::Null,\n        };\n\n        if !(-4503599627370496.0..=4503599627370496.0).contains(&f) {\n            return Value::from_f64(f);\n        }\n\n        let precision = if precision < 1.0 { 0.0 } else { precision };\n        let precision = precision.clamp(0.0, 30.0) as usize;\n\n        if precision == 0 {\n            return Value::from_f64(((f + if f < 0.0 { -0.5 } else { 0.5 }) as i64) as f64);\n        }\n\n        let f: f64 = crate::numeric::str_to_f64(format!(\"{f:.precision$}\"))\n            .expect(\"formatted float should always parse successfully\")\n            .into();\n\n        Value::from_f64(f)\n    }\n\n    fn _exec_trim(&self, pattern: Option<&Value>, trim_type: TrimType) -> Value {\n        let text_cow = match self {\n            Value::Text(s) => std::borrow::Cow::Borrowed(s.as_str()),\n            Value::Null => return Value::Null,\n            _ => std::borrow::Cow::Owned(self.to_string()),\n        };\n        let trimmed = match pattern {\n            Some(p) => {\n                if matches!(p, Value::Null) {\n                    return Value::Null;\n                }\n                let pat_cow = match p {\n                    Value::Text(s) => std::borrow::Cow::Borrowed(s.as_str()),\n                    _ => std::borrow::Cow::Owned(p.to_string()),\n                };\n                let p_str = pat_cow.as_ref();\n                match trim_type {\n                    TrimType::All => text_cow.trim_matches(|c| p_str.contains(c)),\n                    TrimType::Left => text_cow.trim_start_matches(|c| p_str.contains(c)),\n                    TrimType::Right => text_cow.trim_end_matches(|c| p_str.contains(c)),\n                }\n            }\n            None => match trim_type {\n                TrimType::All => text_cow.trim_matches(' '),\n                TrimType::Left => text_cow.trim_start_matches(' '),\n                TrimType::Right => text_cow.trim_end_matches(' '),\n            },\n        };\n        Value::build_text(trimmed.to_string())\n    }\n\n    // Implements TRIM pattern matching.\n    pub fn exec_trim(&self, pattern: Option<&Value>) -> Value {\n        self._exec_trim(pattern, TrimType::All)\n    }\n    // Implements RTRIM pattern matching.\n    pub fn exec_rtrim(&self, pattern: Option<&Value>) -> Value {\n        self._exec_trim(pattern, TrimType::Right)\n    }\n\n    // Implements LTRIM pattern matching.\n    pub fn exec_ltrim(&self, pattern: Option<&Value>) -> Value {\n        self._exec_trim(pattern, TrimType::Left)\n    }\n\n    pub fn exec_zeroblob(&self) -> Result<Value> {\n        let length: i64 = match self {\n            Value::Numeric(Numeric::Integer(i)) => *i,\n            Value::Numeric(Numeric::Float(f)) => f64::from(*f) as i64,\n            Value::Text(s) => s.as_str().parse().unwrap_or(0),\n            _ => 0,\n        }\n        .max(0);\n\n        if length > Self::MAX_BLOB_LENGTH {\n            return Err(LimboError::TooBig);\n        }\n\n        Ok(Value::Blob(vec![0; length as usize]))\n    }\n\n    // exec_if returns whether you should jump\n    pub fn exec_if(&self, jump_if_null: bool, not: bool) -> bool {\n        Numeric::from_value(self)\n            .map(|v| v.to_bool())\n            .map(|jump| if not { !jump } else { jump })\n            .unwrap_or(jump_if_null)\n    }\n\n    pub fn exec_cast(&self, datatype: &str) -> Value {\n        if matches!(self, Value::Null) {\n            return Value::Null;\n        }\n        match Affinity::affinity(datatype) {\n            // NONE\tCasting a value to a type-name with no affinity causes the value to be converted into a BLOB. Casting to a BLOB consists of first casting the value to TEXT in the encoding of the database connection, then interpreting the resulting byte sequence as a BLOB instead of as TEXT.\n            // Historically called NONE, but it's the same as BLOB\n            Affinity::Blob => {\n                // Convert to TEXT first, then interpret as BLOB\n                // TODO: handle encoding\n                let text = self.to_string();\n                Value::Blob(text.into_bytes())\n            }\n            // TEXT To cast a BLOB value to TEXT, the sequence of bytes that make up the BLOB is interpreted as text encoded using the database encoding.\n            // Casting an INTEGER or REAL value into TEXT renders the value as if via sqlite3_snprintf() except that the resulting TEXT uses the encoding of the database connection.\n            Affinity::Text => {\n                // Convert everything to text representation\n                // TODO: handle encoding and whatever sqlite3_snprintf does\n                Value::build_text(self.to_string())\n            }\n            Affinity::Real => match self {\n                Value::Blob(b) => {\n                    let text = String::from_utf8_lossy(b);\n                    Value::from_f64(\n                        crate::numeric::str_to_f64(&text)\n                            .map(f64::from)\n                            .unwrap_or(0.0),\n                    )\n                }\n                Value::Text(t) => {\n                    Value::from_f64(crate::numeric::str_to_f64(t).map(f64::from).unwrap_or(0.0))\n                }\n                Value::Numeric(Numeric::Integer(i)) => Value::from_f64(*i as f64),\n                Value::Numeric(Numeric::Float(f)) => Value::Numeric(Numeric::Float(*f)),\n                _ => Value::from_f64(0.0),\n            },\n            Affinity::Integer => match self {\n                Value::Blob(b) => {\n                    // Convert BLOB to TEXT first\n                    let text = String::from_utf8_lossy(b);\n                    Value::from_i64(crate::numeric::str_to_i64(&text).unwrap_or(0))\n                }\n                Value::Text(t) => Value::from_i64(crate::numeric::str_to_i64(t).unwrap_or(0)),\n                Value::Numeric(Numeric::Integer(i)) => Value::from_i64(*i),\n                // A cast of a REAL value into an INTEGER follows SQLite's sqlite3RealToI64:\n                // truncate toward zero and clamp to i64::MIN/MAX if outside the safe range.\n                Value::Numeric(Numeric::Float(f)) => Value::from_i64(real_to_i64(f64::from(*f))),\n                _ => Value::from_i64(0),\n            },\n            Affinity::Numeric => match self {\n                Value::Null => Value::Null,\n                Value::Numeric(Numeric::Integer(v)) => Value::from_i64(*v),\n                Value::Numeric(Numeric::Float(v)) => Value::Numeric(Numeric::Float(*v)),\n                _ => {\n                    let s = match self {\n                        Value::Text(text) => text.as_str().into(),\n                        Value::Blob(blob) => String::from_utf8_lossy(blob.as_slice()),\n                        _ => unreachable!(),\n                    };\n                    crate::util::checked_cast_text_to_numeric(&s, false)\n                        .ok()\n                        .unwrap_or_else(|| Value::from_i64(0))\n                }\n            },\n        }\n    }\n\n    pub fn exec_replace(source: &Value, pattern: &Value, replacement: &Value) -> Value {\n        // The replace(X,Y,Z) function returns a string formed by substituting string Z for every occurrence of\n        // string Y in string X. The BINARY collating sequence is used for comparisons. If Y is an empty string\n        // then return X unchanged. If Z is not initially a string, it is cast to a UTF-8 string prior to processing.\n\n        // If any of the arguments is NULL, the result is NULL.\n        if matches!(source, Value::Null)\n            || matches!(pattern, Value::Null)\n            || matches!(replacement, Value::Null)\n        {\n            return Value::Null;\n        }\n\n        let source = source.exec_cast(\"TEXT\");\n        let pattern = pattern.exec_cast(\"TEXT\");\n        let replacement = replacement.exec_cast(\"TEXT\");\n\n        // If any of the casts failed, panic as text casting is not expected to fail.\n        match (&source, &pattern, &replacement) {\n            (Value::Text(source), Value::Text(pattern), Value::Text(replacement)) => {\n                if pattern.as_str().is_empty() || pattern.as_str().starts_with('\\0') {\n                    return Value::Text(source.clone());\n                }\n\n                let result = source\n                    .as_str()\n                    .replace(pattern.as_str(), replacement.as_str());\n                Value::build_text(result)\n            }\n            _ => unreachable!(\"text cast should never fail\"),\n        }\n    }\n\n    pub fn exec_math_unary(&self, function: &MathFunc) -> Value {\n        let v = Numeric::from_value_strict(self);\n\n        // In case of some functions and integer input, return the input as is\n        if let Some(Numeric::Integer(i)) = v {\n            if matches! { function, MathFunc::Ceil | MathFunc::Ceiling | MathFunc::Floor | MathFunc::Trunc }\n            {\n                return Value::from_i64(i);\n            }\n        }\n\n        let Some(f) = v.map(|v| v.to_f64()) else {\n            return Value::Null;\n        };\n\n        if matches! { function, MathFunc::Ln | MathFunc::Log10 | MathFunc::Log2 } && f <= 0.0 {\n            return Value::Null;\n        }\n\n        #[allow(unused_unsafe)]\n        let result = match function {\n            MathFunc::Acos => unsafe { cmath::acos(f) },\n            MathFunc::Acosh => unsafe { cmath::acosh(f) },\n            MathFunc::Asin => unsafe { cmath::asin(f) },\n            MathFunc::Asinh => unsafe { cmath::asinh(f) },\n            MathFunc::Atan => unsafe { cmath::atan(f) },\n            MathFunc::Atanh => unsafe { cmath::atanh(f) },\n            MathFunc::Ceil | MathFunc::Ceiling => libm::ceil(f),\n            MathFunc::Cos => unsafe { cmath::cos(f) },\n            MathFunc::Cosh => unsafe { cmath::cosh(f) },\n            MathFunc::Degrees => cmath::degrees(f),\n            MathFunc::Exp => unsafe { cmath::exp(f) },\n            MathFunc::Floor => libm::floor(f),\n            MathFunc::Ln => unsafe { cmath::log(f) },\n            MathFunc::Log10 => unsafe { cmath::log10(f) },\n            MathFunc::Log2 => unsafe { cmath::log2(f) },\n            MathFunc::Radians => cmath::radians(f),\n            MathFunc::Sin => unsafe { cmath::sin(f) },\n            MathFunc::Sinh => unsafe { cmath::sinh(f) },\n            MathFunc::Sqrt => libm::sqrt(f),\n            MathFunc::Tan => unsafe { cmath::tan(f) },\n            MathFunc::Tanh => unsafe { cmath::tanh(f) },\n            MathFunc::Trunc => libm::trunc(f),\n            _ => unreachable!(\"Unexpected mathematical unary function {:?}\", function),\n        };\n\n        if result.is_nan() {\n            Value::Null\n        } else {\n            Value::from_f64(result)\n        }\n    }\n\n    pub fn exec_math_binary(&self, rhs: &Value, function: &MathFunc) -> Value {\n        let Some(lhs) = Numeric::from_value_strict(self).map(|v| v.to_f64()) else {\n            return Value::Null;\n        };\n\n        let Some(rhs) = Numeric::from_value_strict(rhs).map(|v| v.to_f64()) else {\n            return Value::Null;\n        };\n\n        #[allow(unused_unsafe)]\n        let result = match function {\n            MathFunc::Atan2 => unsafe { cmath::atan2(lhs, rhs) },\n            MathFunc::Mod => libm::fmod(lhs, rhs),\n            MathFunc::Pow | MathFunc::Power => unsafe { cmath::pow(lhs, rhs) },\n            _ => unreachable!(\"Unexpected mathematical binary function {:?}\", function),\n        };\n\n        if result.is_nan() {\n            Value::Null\n        } else {\n            Value::from_f64(result)\n        }\n    }\n\n    pub fn exec_math_log(&self, base: Option<&Value>) -> Value {\n        let Some(f) = Numeric::from_value_strict(self).map(|v| v.to_f64()) else {\n            return Value::Null;\n        };\n\n        let base = match base.map(|value| Numeric::from_value_strict(value).map(|v| v.to_f64())) {\n            Some(Some(f)) => f,\n            Some(None) => return Value::Null,\n            None => 10.0,\n        };\n\n        if f <= 0.0 || base <= 0.0 || base == 1.0 {\n            return Value::Null;\n        }\n\n        if base == 2.0 {\n            return Value::from_f64(libm::log2(f));\n        } else if base == 10.0 {\n            return Value::from_f64(libm::log10(f));\n        };\n\n        let log_x = libm::log(f);\n        let log_base = libm::log(base);\n\n        if log_base <= 0.0 {\n            return Value::Null;\n        }\n\n        let result = log_x / log_base;\n        Value::from_f64(result)\n    }\n\n    pub fn exec_add(&self, rhs: &Value) -> Value {\n        (|| Numeric::from_value(self)?.checked_add(Numeric::from_value(rhs)?))().into()\n    }\n\n    pub fn exec_subtract(&self, rhs: &Value) -> Value {\n        (|| Numeric::from_value(self)?.checked_sub(Numeric::from_value(rhs)?))().into()\n    }\n\n    pub fn exec_multiply(&self, rhs: &Value) -> Value {\n        (|| Numeric::from_value(self)?.checked_mul(Numeric::from_value(rhs)?))().into()\n    }\n\n    pub fn exec_divide(&self, rhs: &Value) -> Value {\n        (|| Numeric::from_value(self)?.checked_div(Numeric::from_value(rhs)?))().into()\n    }\n\n    pub fn exec_bit_and(&self, rhs: &Value) -> Value {\n        (NullableInteger::from(self) & NullableInteger::from(rhs)).into()\n    }\n\n    pub fn exec_bit_or(&self, rhs: &Value) -> Value {\n        (NullableInteger::from(self) | NullableInteger::from(rhs)).into()\n    }\n\n    pub fn exec_remainder(&self, rhs: &Value) -> Value {\n        let convert_to_float = matches!(Numeric::from_value(self), Some(Numeric::Float(_)))\n            || matches!(Numeric::from_value(rhs), Some(Numeric::Float(_)));\n\n        match NullableInteger::from(self) % NullableInteger::from(rhs) {\n            NullableInteger::Null => Value::Null,\n            NullableInteger::Integer(v) => {\n                if convert_to_float {\n                    Value::from_f64(v as f64)\n                } else {\n                    Value::from_i64(v)\n                }\n            }\n        }\n    }\n\n    pub fn exec_bit_not(&self) -> Value {\n        (!NullableInteger::from(self)).into()\n    }\n\n    pub fn exec_shift_left(&self, rhs: &Value) -> Value {\n        (NullableInteger::from(self) << NullableInteger::from(rhs)).into()\n    }\n\n    pub fn exec_shift_right(&self, rhs: &Value) -> Value {\n        (NullableInteger::from(self) >> NullableInteger::from(rhs)).into()\n    }\n\n    pub fn exec_boolean_not(&self) -> Value {\n        match Numeric::from_value(self).map(|v| v.to_bool()) {\n            None => Value::Null,\n            Some(v) => Value::from_i64(!v as i64),\n        }\n    }\n\n    pub fn exec_concat(&self, rhs: &Value) -> Value {\n        if let (Value::Blob(lhs), Value::Blob(rhs)) = (self, rhs) {\n            return Value::Blob([lhs.as_slice(), rhs.as_slice()].concat().to_vec());\n        }\n\n        let Some(lhs) = self.cast_text() else {\n            return Value::Null;\n        };\n\n        let Some(rhs) = rhs.cast_text() else {\n            return Value::Null;\n        };\n\n        Value::build_text(lhs + &rhs)\n    }\n\n    pub fn exec_and(&self, rhs: &Value) -> Value {\n        match (\n            Numeric::from_value(self).map(|v| v.to_bool()),\n            Numeric::from_value(rhs).map(|v| v.to_bool()),\n        ) {\n            (Some(false), _) | (_, Some(false)) => Value::from_i64(0),\n            (None, _) | (_, None) => Value::Null,\n            _ => Value::from_i64(1),\n        }\n    }\n\n    pub fn exec_or(&self, rhs: &Value) -> Value {\n        match (\n            Numeric::from_value(self).map(|v| v.to_bool()),\n            Numeric::from_value(rhs).map(|v| v.to_bool()),\n        ) {\n            (Some(true), _) | (_, Some(true)) => Value::from_i64(1),\n            (None, _) | (_, None) => Value::Null,\n            _ => Value::from_i64(0),\n        }\n    }\n\n    pub fn exec_like(pattern: &str, text: &str, escape: Option<char>) -> Result<bool, LimboError> {\n        const MAX_LIKE_PATTERN_LENGTH: usize = 50000;\n        if pattern.len() > MAX_LIKE_PATTERN_LENGTH {\n            return Err(LimboError::Constraint(\n                \"LIKE or GLOB pattern too complex\".to_string(),\n            ));\n        }\n\n        let has_escape = escape.is_some_and(|e| pattern.contains(e));\n\n        // 1. Exact match (no wildcards)\n        if !has_escape && !pattern.contains(['%', '_']) {\n            return Ok(pattern.eq_ignore_ascii_case(text));\n        }\n\n        // 2. Fast Path: 'abc%' (Prefix)\n        if !has_escape\n            && pattern.ends_with('%')\n            && !pattern[..pattern.len() - 1].contains(['%', '_'])\n        {\n            let prefix = &pattern[..pattern.len() - 1];\n            if text.len() >= prefix.len() && text.is_char_boundary(prefix.len()) {\n                return Ok(text[..prefix.len()].eq_ignore_ascii_case(prefix));\n            }\n            // Fall through to pattern_compare if boundary check fails (multi-byte UTF-8)\n        }\n\n        // 3. Fast Path: '%abc' (Suffix)\n        if !has_escape && pattern.starts_with('%') && !pattern[1..].contains(['%', '_']) {\n            let suffix = &pattern[1..];\n            let start = text.len().wrapping_sub(suffix.len());\n            if text.len() >= suffix.len() && text.is_char_boundary(start) {\n                return Ok(text[start..].eq_ignore_ascii_case(suffix));\n            }\n            // Fall through to pattern_compare if boundary check fails (multi-byte UTF-8)\n        }\n\n        Ok(pattern_compare(pattern, text, &LIKE_INFO, escape) == CompareResult::Match)\n    }\n\n    pub fn exec_glob(pattern: &str, text: &str) -> Result<bool, LimboError> {\n        const MAX_GLOB_PATTERN_LENGTH: usize = 50000;\n        const GLOB_CHARS: [char; 3] = ['*', '?', '['];\n\n        if pattern.len() > MAX_GLOB_PATTERN_LENGTH {\n            return Err(LimboError::Constraint(\n                \"GLOB pattern too complex\".to_string(),\n            ));\n        }\n\n        // 1. Exact match (no wildcards)\n        if !pattern.contains(GLOB_CHARS) {\n            return Ok(pattern == text);\n        }\n\n        // 2. Fast Path: 'abc*' (Prefix)\n        if pattern.ends_with('*') && !pattern[..pattern.len() - 1].contains(GLOB_CHARS) {\n            let prefix = &pattern[..pattern.len() - 1];\n            if text.len() >= prefix.len() && text.is_char_boundary(prefix.len()) {\n                return Ok(&text[..prefix.len()] == prefix);\n            }\n            // Fall through to pattern_compare if boundary check fails (multi-byte UTF-8)\n        }\n\n        // 3. Fast Path: '*abc' (Suffix)\n        if pattern.starts_with('*') && !pattern[1..].contains(GLOB_CHARS) {\n            let suffix = &pattern[1..];\n            let start = text.len().wrapping_sub(suffix.len());\n            if text.len() >= suffix.len() && text.is_char_boundary(start) {\n                return Ok(&text[start..] == suffix);\n            }\n            // Fall through to pattern_compare if boundary check fails (multi-byte UTF-8)\n        }\n\n        Ok(pattern_compare(pattern, text, &GLOB_INFO, None) == CompareResult::Match)\n    }\n\n    pub fn exec_min<'a, T: Iterator<Item = &'a Value>>(regs: T) -> Value {\n        // SQLite: multi-arg min() returns NULL if ANY argument is NULL\n        let mut result: Option<&Value> = None;\n        for v in regs {\n            if matches!(v, Value::Null) {\n                return Value::Null;\n            }\n            result = Some(match result {\n                None => v,\n                Some(cur) if v < cur => v,\n                Some(cur) => cur,\n            });\n        }\n        result.map(|v| v.to_owned()).unwrap_or(Value::Null)\n    }\n\n    pub fn exec_max<'a, T: Iterator<Item = &'a Value>>(regs: T) -> Value {\n        // SQLite: multi-arg max() returns NULL if ANY argument is NULL\n        let mut result: Option<&Value> = None;\n        for v in regs {\n            if matches!(v, Value::Null) {\n                return Value::Null;\n            }\n            result = Some(match result {\n                None => v,\n                Some(cur) if v > cur => v,\n                Some(cur) => cur,\n            });\n        }\n        result.map(|v| v.to_owned()).unwrap_or(Value::Null)\n    }\n\n    /// Concatenate another value onto this Text value, converting both to strings.\n    /// Used by GROUP_CONCAT/STRING_AGG to properly handle all value types.\n    /// Panics if self is not a Text value.\n    pub fn exec_group_concat(&mut self, other: &Value) {\n        let Value::Text(text) = self else {\n            panic!(\"concat_to_text must be called only on Value::Text\");\n        };\n        text.value.to_mut().push_str(&other.to_string());\n    }\n\n    pub fn exec_concat_strings<'a, T: Iterator<Item = &'a Self>>(registers: T) -> Self {\n        let mut result = String::new();\n        for val in registers {\n            match val {\n                Value::Null => continue,\n                Value::Text(s) => result.push_str(s.as_str()),\n                Value::Blob(b) => result.push_str(&String::from_utf8_lossy(b)),\n                Value::Numeric(Numeric::Integer(i)) => result.push_str(&i.to_string()),\n                Value::Numeric(Numeric::Float(f)) => result.push_str(&format_float(f64::from(*f))),\n            }\n        }\n        Value::build_text(result)\n    }\n\n    pub fn exec_concat_ws<'a, T: ExactSizeIterator<Item = &'a Self>>(mut registers: T) -> Self {\n        if registers.len() == 0 {\n            return Value::Null;\n        }\n\n        let separator = match registers\n            .next()\n            .expect(\"registers should have at least one element after length check\")\n        {\n            Value::Null | Value::Blob(_) => return Value::Null,\n            v => format!(\"{v}\"),\n        };\n\n        let parts = registers.filter_map(|val| match val {\n            Value::Text(_) | Value::Numeric(_) => Some(format!(\"{val}\")),\n            _ => None,\n        });\n\n        let result = parts.collect::<Vec<_>>().join(&separator);\n        Value::build_text(result)\n    }\n\n    pub fn exec_char<'a, T: Iterator<Item = &'a Self>>(values: T) -> Self {\n        let result: String = values\n            .filter_map(|x| match x {\n                Value::Numeric(Numeric::Integer(i)) => {\n                    // Convert integer to Unicode codepoint.\n                    // For invalid codepoints (negative, surrogates, or > U+10FFFF),\n                    // output U+FFFD (replacement character) to match SQLite behavior.\n                    if *i >= 0 {\n                        Some(char::from_u32(*i as u32).unwrap_or('\\u{FFFD}'))\n                    } else {\n                        Some('\\u{FFFD}')\n                    }\n                }\n                // NULL arguments produce NUL characters to match SQLite behavior.\n                Value::Null => Some('\\0'),\n                _ => None,\n            })\n            .collect();\n        Value::build_text(result)\n    }\n}\n\n/// Result of LIKE pattern comparison.\n/// `NoWildcardMatch` signals an early abort when a literal after `%` cannot be found,\n/// allowing the algorithm to skip unnecessary backtracking.\n#[derive(PartialEq)]\nenum CompareResult {\n    Match,\n    NoMatch,\n    NoWildcardMatch,\n}\n\nstruct PatternInfo {\n    match_all: char,\n    match_one: char,\n    match_set: Option<char>,\n    no_case: bool,\n}\n\nconst LIKE_INFO: PatternInfo = PatternInfo {\n    match_all: '%',\n    match_one: '_',\n    match_set: None,\n    no_case: true,\n};\n\nconst GLOB_INFO: PatternInfo = PatternInfo {\n    match_all: '*',\n    match_one: '?',\n    match_set: Some('['),\n    no_case: false,\n};\n\n/// LIKE and GLOB pattern matching based on SQLite's patternCompare algorithm (src/func.c).\n/// Uses recursive descent with early termination via `NoWildcardMatch` to avoid\n/// exponential backtracking on patterns like `%a%a%a%...%b`.\n/// Ref: https://github.com/sqlite/sqlite/blob/master/src/func.c#L728\nfn pattern_compare(\n    pattern: &str,\n    text: &str,\n    info: &PatternInfo,\n    escape: Option<char>,\n) -> CompareResult {\n    let mut p_indices = pattern.char_indices();\n    let mut t_indices = text.char_indices();\n\n    let mut p_curr = p_indices.next();\n    let mut t_curr = t_indices.next();\n\n    // Checkpoints for backtracking\n    let mut wildcard_p_iter: Option<std::str::CharIndices> = None;\n    let mut wildcard_t_iter: Option<std::str::CharIndices> = None;\n\n    loop {\n        match (p_curr, t_curr) {\n            (Some((_, p_char)), Some((_, t_char))) => {\n                if p_char == info.match_all && Some(p_char) != escape {\n                    // Consume consecutive match_alls\n                    let mut next_p = p_indices.clone();\n                    while let Some((_, c)) = next_p.clone().next() {\n                        if c == info.match_all && Some(c) != escape {\n                            next_p.next();\n                        } else {\n                            break;\n                        }\n                    }\n\n                    let mut lookahead_p = next_p.clone();\n                    if let Some((_, next_char)) = lookahead_p.next() {\n                        let is_wildcard = (next_char == info.match_all\n                            && Some(next_char) != escape)\n                            || (next_char == info.match_one && Some(next_char) != escape)\n                            || (info.match_set == Some(next_char));\n\n                        let is_escaped_next = Some(next_char) == escape;\n\n                        if !is_wildcard && !is_escaped_next {\n                            let mut found = false;\n\n                            // Check current text char\n                            if compare_chars(next_char, t_char, info.no_case) {\n                                found = true;\n                            } else {\n                                // Scan remaining text\n                                let lookahead_t = t_indices.clone();\n                                for (_, t_c) in lookahead_t {\n                                    if compare_chars(next_char, t_c, info.no_case) {\n                                        found = true;\n                                        break;\n                                    }\n                                }\n                            }\n\n                            if !found {\n                                return CompareResult::NoWildcardMatch;\n                            }\n                        }\n                    }\n\n                    p_indices = next_p;\n                    wildcard_p_iter = Some(p_indices.clone());\n                    p_curr = p_indices.next();\n\n                    if p_curr.is_none() {\n                        return CompareResult::Match;\n                    }\n\n                    wildcard_t_iter = Some(t_indices.clone());\n                    continue;\n                }\n\n                if p_char == info.match_one && Some(p_char) != escape {\n                    p_curr = p_indices.next();\n                    t_curr = t_indices.next();\n                    continue;\n                }\n\n                // Handle Set (GLOB only)\n                if info.match_set == Some(p_char) {\n                    let mut seen = false;\n                    let mut invert = false;\n                    let c = t_char;\n\n                    let mut next_c_opt = p_indices.next();\n\n                    if let Some((_, c2)) = next_c_opt {\n                        if c2 == '^' {\n                            invert = true;\n                            next_c_opt = p_indices.next();\n                        }\n                    }\n\n                    let mut c2_opt = next_c_opt;\n                    if let Some((_, c2)) = c2_opt {\n                        if c2 == ']' {\n                            if c == ']' {\n                                seen = true;\n                            }\n                            c2_opt = p_indices.next();\n                        }\n                    }\n\n                    let mut prior_c: Option<char> = None;\n\n                    while let Some((_, c2)) = c2_opt {\n                        if c2 == ']' {\n                            break;\n                        }\n\n                        let mut is_range = false;\n                        if c2 == '-' && prior_c.is_some() {\n                            let lookahead = p_indices.clone().next();\n                            if let Some((_, c3)) = lookahead {\n                                if c3 != ']' {\n                                    is_range = true;\n                                    let start = prior_c.unwrap();\n                                    let end = c3;\n                                    if c >= start && c <= end {\n                                        seen = true;\n                                    }\n                                    p_indices.next();\n                                    prior_c = None;\n                                }\n                            }\n                        }\n\n                        if !is_range {\n                            if c == c2 {\n                                seen = true;\n                            }\n                            prior_c = Some(c2);\n                        }\n\n                        c2_opt = p_indices.next();\n                    }\n\n                    if c2_opt.is_none() || !(seen ^ invert) {\n                        // Fallthrough to backtracking\n                    } else {\n                        p_curr = p_indices.next();\n                        t_curr = t_indices.next();\n                        continue;\n                    }\n                } else {\n                    let (expected_char, next_p_iter) = if Some(p_char) == escape {\n                        if let Some((_, literal)) = p_indices.next() {\n                            (literal, p_indices.clone())\n                        } else {\n                            return CompareResult::NoMatch;\n                        }\n                    } else {\n                        (p_char, p_indices.clone())\n                    };\n\n                    if compare_chars(expected_char, t_char, info.no_case) {\n                        p_indices = next_p_iter;\n                        p_curr = p_indices.next();\n                        t_curr = t_indices.next();\n                        continue;\n                    }\n                }\n            }\n            (None, None) => return CompareResult::Match,\n            (Some((_, p_char)), None) if p_char == info.match_all && Some(p_char) != escape => {\n                let mut temp = p_indices.clone();\n                loop {\n                    match temp.next() {\n                        Some((_, c)) if c == info.match_all && Some(c) != escape => continue,\n                        None => return CompareResult::Match,\n                        _ => break,\n                    }\n                }\n            }\n            _ => {}\n        }\n\n        // Backtracking\n        if let (Some(wp), Some(wt)) = (wildcard_p_iter.clone(), wildcard_t_iter.clone()) {\n            p_indices = wp;\n            p_curr = p_indices.next();\n            t_indices = wt.clone();\n            t_curr = t_indices.next();\n\n            if t_curr.is_some() {\n                wildcard_t_iter = Some(t_indices.clone());\n                continue;\n            }\n        }\n\n        return CompareResult::NoMatch;\n    }\n}\n\nfn compare_chars(p: char, t: char, no_case: bool) -> bool {\n    if no_case {\n        p.eq_ignore_ascii_case(&t)\n    } else {\n        p == t\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::numeric::Numeric;\n    use crate::types::Value;\n    use crate::vdbe::Register;\n\n    use rand::{Rng, RngCore};\n\n    #[test]\n    fn test_exec_add() {\n        let inputs = vec![\n            (Value::from_i64(3), Value::from_i64(1)),\n            (Value::from_f64(3.0), Value::from_f64(1.0)),\n            (Value::from_f64(3.0), Value::from_i64(1)),\n            (Value::from_i64(3), Value::from_f64(1.0)),\n            (Value::Null, Value::Null),\n            (Value::Null, Value::from_i64(1)),\n            (Value::Null, Value::from_f64(1.0)),\n            (Value::Null, Value::Text(\"2\".into())),\n            (Value::from_i64(1), Value::Null),\n            (Value::from_f64(1.0), Value::Null),\n            (Value::Text(\"1\".into()), Value::Null),\n            (Value::Text(\"1\".into()), Value::Text(\"3\".into())),\n            (Value::Text(\"1.0\".into()), Value::Text(\"3.0\".into())),\n            (Value::Text(\"1.0\".into()), Value::from_f64(3.0)),\n            (Value::Text(\"1.0\".into()), Value::from_i64(3)),\n            (Value::from_f64(1.0), Value::Text(\"3.0\".into())),\n            (Value::from_i64(1), Value::Text(\"3\".into())),\n        ];\n\n        let outputs = [\n            Value::from_i64(4),\n            Value::from_f64(4.0),\n            Value::from_f64(4.0),\n            Value::from_f64(4.0),\n            Value::Null,\n            Value::Null,\n            Value::Null,\n            Value::Null,\n            Value::Null,\n            Value::Null,\n            Value::Null,\n            Value::from_i64(4),\n            Value::from_f64(4.0),\n            Value::from_f64(4.0),\n            Value::from_f64(4.0),\n            Value::from_f64(4.0),\n            Value::from_f64(4.0),\n        ];\n\n        assert_eq!(\n            inputs.len(),\n            outputs.len(),\n            \"Inputs and Outputs should have same size\"\n        );\n        for (i, (lhs, rhs)) in inputs.iter().enumerate() {\n            assert_eq!(\n                lhs.exec_add(rhs),\n                outputs[i],\n                \"Wrong ADD for lhs: {lhs}, rhs: {rhs}\"\n            );\n        }\n    }\n\n    #[test]\n    fn test_exec_subtract() {\n        let inputs = vec![\n            (Value::from_i64(3), Value::from_i64(1)),\n            (Value::from_f64(3.0), Value::from_f64(1.0)),\n            (Value::from_f64(3.0), Value::from_i64(1)),\n            (Value::from_i64(3), Value::from_f64(1.0)),\n            (Value::Null, Value::Null),\n            (Value::Null, Value::from_i64(1)),\n            (Value::Null, Value::from_f64(1.0)),\n            (Value::Null, Value::Text(\"1\".into())),\n            (Value::from_i64(1), Value::Null),\n            (Value::from_f64(1.0), Value::Null),\n            (Value::Text(\"4\".into()), Value::Null),\n            (Value::Text(\"1\".into()), Value::Text(\"3\".into())),\n            (Value::Text(\"1.0\".into()), Value::Text(\"3.0\".into())),\n            (Value::Text(\"1.0\".into()), Value::from_f64(3.0)),\n            (Value::Text(\"1.0\".into()), Value::from_i64(3)),\n            (Value::from_f64(1.0), Value::Text(\"3.0\".into())),\n            (Value::from_i64(1), Value::Text(\"3\".into())),\n        ];\n\n        let outputs = [\n            Value::from_i64(2),\n            Value::from_f64(2.0),\n            Value::from_f64(2.0),\n            Value::from_f64(2.0),\n            Value::Null,\n            Value::Null,\n            Value::Null,\n            Value::Null,\n            Value::Null,\n            Value::Null,\n            Value::Null,\n            Value::from_i64(-2),\n            Value::from_f64(-2.0),\n            Value::from_f64(-2.0),\n            Value::from_f64(-2.0),\n            Value::from_f64(-2.0),\n            Value::from_f64(-2.0),\n        ];\n\n        assert_eq!(\n            inputs.len(),\n            outputs.len(),\n            \"Inputs and Outputs should have same size\"\n        );\n        for (i, (lhs, rhs)) in inputs.iter().enumerate() {\n            assert_eq!(\n                lhs.exec_subtract(rhs),\n                outputs[i],\n                \"Wrong subtract for lhs: {lhs}, rhs: {rhs}\"\n            );\n        }\n    }\n\n    #[test]\n    fn test_exec_multiply() {\n        let inputs = vec![\n            (Value::from_i64(3), Value::from_i64(2)),\n            (Value::from_f64(3.0), Value::from_f64(2.0)),\n            (Value::from_f64(3.0), Value::from_i64(2)),\n            (Value::from_i64(3), Value::from_f64(2.0)),\n            (Value::Null, Value::Null),\n            (Value::Null, Value::from_i64(1)),\n            (Value::Null, Value::from_f64(1.0)),\n            (Value::Null, Value::Text(\"1\".into())),\n            (Value::from_i64(1), Value::Null),\n            (Value::from_f64(1.0), Value::Null),\n            (Value::Text(\"4\".into()), Value::Null),\n            (Value::Text(\"2\".into()), Value::Text(\"3\".into())),\n            (Value::Text(\"2.0\".into()), Value::Text(\"3.0\".into())),\n            (Value::Text(\"2.0\".into()), Value::from_f64(3.0)),\n            (Value::Text(\"2.0\".into()), Value::from_i64(3)),\n            (Value::from_f64(2.0), Value::Text(\"3.0\".into())),\n            (Value::from_i64(2), Value::Text(\"3.0\".into())),\n        ];\n\n        let outputs = [\n            Value::from_i64(6),\n            Value::from_f64(6.0),\n            Value::from_f64(6.0),\n            Value::from_f64(6.0),\n            Value::Null,\n            Value::Null,\n            Value::Null,\n            Value::Null,\n            Value::Null,\n            Value::Null,\n            Value::Null,\n            Value::from_i64(6),\n            Value::from_f64(6.0),\n            Value::from_f64(6.0),\n            Value::from_f64(6.0),\n            Value::from_f64(6.0),\n            Value::from_f64(6.0),\n        ];\n\n        assert_eq!(\n            inputs.len(),\n            outputs.len(),\n            \"Inputs and Outputs should have same size\"\n        );\n        for (i, (lhs, rhs)) in inputs.iter().enumerate() {\n            assert_eq!(\n                lhs.exec_multiply(rhs),\n                outputs[i],\n                \"Wrong multiply for lhs: {lhs}, rhs: {rhs}\"\n            );\n        }\n    }\n\n    #[test]\n    fn test_exec_divide() {\n        let inputs = vec![\n            (Value::from_i64(1), Value::from_i64(0)),\n            (Value::from_f64(1.0), Value::from_f64(0.0)),\n            (Value::from_i64(i64::MIN), Value::from_i64(-1)),\n            (Value::from_f64(6.0), Value::from_f64(2.0)),\n            (Value::from_f64(6.0), Value::from_i64(2)),\n            (Value::from_i64(6), Value::from_i64(2)),\n            (Value::Null, Value::from_i64(2)),\n            (Value::from_i64(2), Value::Null),\n            (Value::Null, Value::Null),\n            (Value::Text(\"6\".into()), Value::Text(\"2\".into())),\n            (Value::Text(\"6\".into()), Value::from_i64(2)),\n        ];\n\n        let outputs = [\n            Value::Null,\n            Value::Null,\n            Value::from_f64(9.223372036854776e18),\n            Value::from_f64(3.0),\n            Value::from_f64(3.0),\n            Value::from_f64(3.0),\n            Value::Null,\n            Value::Null,\n            Value::Null,\n            Value::from_f64(3.0),\n            Value::from_f64(3.0),\n        ];\n\n        assert_eq!(\n            inputs.len(),\n            outputs.len(),\n            \"Inputs and Outputs should have same size\"\n        );\n        for (i, (lhs, rhs)) in inputs.iter().enumerate() {\n            assert_eq!(\n                lhs.exec_divide(rhs),\n                outputs[i],\n                \"Wrong divide for lhs: {lhs}, rhs: {rhs}\"\n            );\n        }\n    }\n\n    #[test]\n    fn test_exec_remainder() {\n        let inputs = vec![\n            (Value::Null, Value::Null),\n            (Value::Null, Value::from_f64(1.0)),\n            (Value::Null, Value::from_i64(1)),\n            (Value::Null, Value::Text(\"1\".into())),\n            (Value::from_f64(1.0), Value::Null),\n            (Value::from_i64(1), Value::Null),\n            (Value::from_i64(12), Value::from_i64(0)),\n            (Value::from_f64(12.0), Value::from_f64(0.0)),\n            (Value::from_f64(12.0), Value::from_i64(0)),\n            (Value::from_i64(12), Value::from_f64(0.0)),\n            (Value::from_i64(i64::MIN), Value::from_i64(-1)),\n            (Value::from_i64(12), Value::from_i64(3)),\n            (Value::from_f64(12.0), Value::from_f64(3.0)),\n            (Value::from_f64(12.0), Value::from_i64(3)),\n            (Value::from_i64(12), Value::from_f64(3.0)),\n            (Value::from_i64(12), Value::from_i64(-3)),\n            (Value::from_f64(12.0), Value::from_f64(-3.0)),\n            (Value::from_f64(12.0), Value::from_i64(-3)),\n            (Value::from_i64(12), Value::from_f64(-3.0)),\n            (Value::Text(\"12.0\".into()), Value::Text(\"3.0\".into())),\n            (Value::Text(\"12.0\".into()), Value::from_f64(3.0)),\n            (Value::from_f64(12.0), Value::Text(\"3.0\".into())),\n        ];\n        let outputs = vec![\n            Value::Null,\n            Value::Null,\n            Value::Null,\n            Value::Null,\n            Value::Null,\n            Value::Null,\n            Value::Null,\n            Value::Null,\n            Value::Null,\n            Value::Null,\n            Value::from_f64(0.0),\n            Value::from_i64(0),\n            Value::from_f64(0.0),\n            Value::from_f64(0.0),\n            Value::from_f64(0.0),\n            Value::from_i64(0),\n            Value::from_f64(0.0),\n            Value::from_f64(0.0),\n            Value::from_f64(0.0),\n            Value::from_f64(0.0),\n            Value::from_f64(0.0),\n            Value::from_f64(0.0),\n        ];\n\n        assert_eq!(\n            inputs.len(),\n            outputs.len(),\n            \"Inputs and Outputs should have same size\"\n        );\n\n        for (i, (lhs, rhs)) in inputs.iter().enumerate() {\n            assert_eq!(\n                lhs.exec_remainder(rhs),\n                outputs[i],\n                \"Wrong remainder for lhs: {lhs}, rhs: {rhs}\"\n            );\n        }\n    }\n\n    #[test]\n    fn test_exec_and() {\n        let inputs = vec![\n            (Value::from_i64(0), Value::Null),\n            (Value::Null, Value::from_i64(1)),\n            (Value::Null, Value::Null),\n            (Value::from_f64(0.0), Value::Null),\n            (Value::from_i64(1), Value::from_f64(2.2)),\n            (Value::from_i64(0), Value::Text(\"string\".into())),\n            (Value::from_i64(0), Value::Text(\"1\".into())),\n            (Value::from_i64(1), Value::Text(\"1\".into())),\n        ];\n        let outputs = [\n            Value::from_i64(0),\n            Value::Null,\n            Value::Null,\n            Value::from_i64(0),\n            Value::from_i64(1),\n            Value::from_i64(0),\n            Value::from_i64(0),\n            Value::from_i64(1),\n        ];\n\n        assert_eq!(\n            inputs.len(),\n            outputs.len(),\n            \"Inputs and Outputs should have same size\"\n        );\n        for (i, (lhs, rhs)) in inputs.iter().enumerate() {\n            assert_eq!(\n                lhs.exec_and(rhs),\n                outputs[i],\n                \"Wrong AND for lhs: {lhs}, rhs: {rhs}\"\n            );\n        }\n    }\n\n    #[test]\n    fn test_exec_or() {\n        let inputs = vec![\n            (Value::from_i64(0), Value::Null),\n            (Value::Null, Value::from_i64(1)),\n            (Value::Null, Value::Null),\n            (Value::from_f64(0.0), Value::Null),\n            (Value::from_i64(1), Value::from_f64(2.2)),\n            (Value::from_f64(0.0), Value::from_i64(0)),\n            (Value::from_i64(0), Value::Text(\"string\".into())),\n            (Value::from_i64(0), Value::Text(\"1\".into())),\n            (Value::from_i64(0), Value::Text(\"\".into())),\n        ];\n        let outputs = [\n            Value::Null,\n            Value::from_i64(1),\n            Value::Null,\n            Value::Null,\n            Value::from_i64(1),\n            Value::from_i64(0),\n            Value::from_i64(0),\n            Value::from_i64(1),\n            Value::from_i64(0),\n        ];\n\n        assert_eq!(\n            inputs.len(),\n            outputs.len(),\n            \"Inputs and Outputs should have same size\"\n        );\n        for (i, (lhs, rhs)) in inputs.iter().enumerate() {\n            assert_eq!(\n                lhs.exec_or(rhs),\n                outputs[i],\n                \"Wrong OR for lhs: {lhs}, rhs: {rhs}\"\n            );\n        }\n    }\n\n    #[test]\n    fn test_length() {\n        let input_str = Value::build_text(\"bob\");\n        let expected_len = Value::from_i64(3);\n        assert_eq!(input_str.exec_length(), expected_len);\n\n        let input_integer = Value::from_i64(123);\n        let expected_len = Value::from_i64(3);\n        assert_eq!(input_integer.exec_length(), expected_len);\n\n        let input_float = Value::from_f64(123.456);\n        let expected_len = Value::from_i64(7);\n        assert_eq!(input_float.exec_length(), expected_len);\n\n        let expected_blob = Value::Blob(\"example\".as_bytes().to_vec());\n        let expected_len = Value::from_i64(7);\n        assert_eq!(expected_blob.exec_length(), expected_len);\n    }\n\n    #[test]\n    fn test_quote() {\n        let input = Value::build_text(\"abc\\0edf\");\n        let expected = Value::build_text(\"'abc'\");\n        assert_eq!(input.exec_quote(), expected);\n\n        let input = Value::from_i64(123);\n        let expected = Value::build_text(\"123\");\n        assert_eq!(input.exec_quote(), expected);\n\n        let input = Value::from_f64(12.34);\n        let expected = Value::build_text(\"12.34\");\n        assert_eq!(input.exec_quote(), expected);\n\n        let input = Value::build_text(\"hello''world\");\n        let expected = Value::build_text(\"'hello''''world'\");\n        assert_eq!(input.exec_quote(), expected);\n\n        let input = Value::from_f64(\n            crate::numeric::str_to_f64(\"2.042747795102219097e+05\")\n                .map(f64::from)\n                .unwrap(),\n        );\n        let expected = Value::build_text(\"2.042747795102219097e+05\");\n        assert_eq!(input.exec_quote(), expected);\n    }\n\n    #[test]\n    fn test_typeof() {\n        let input = Value::Null;\n        let expected: Value = Value::build_text(\"null\");\n        assert_eq!(input.exec_typeof(), expected);\n\n        let input = Value::from_i64(123);\n        let expected: Value = Value::build_text(\"integer\");\n        assert_eq!(input.exec_typeof(), expected);\n\n        let input = Value::from_f64(123.456);\n        let expected: Value = Value::build_text(\"real\");\n        assert_eq!(input.exec_typeof(), expected);\n\n        let input = Value::build_text(\"hello\");\n        let expected: Value = Value::build_text(\"text\");\n        assert_eq!(input.exec_typeof(), expected);\n\n        let input = Value::Blob(\"limbo\".as_bytes().to_vec());\n        let expected: Value = Value::build_text(\"blob\");\n        assert_eq!(input.exec_typeof(), expected);\n    }\n\n    #[test]\n    fn test_unicode() {\n        assert_eq!(Value::build_text(\"a\").exec_unicode(), Value::from_i64(97));\n        assert_eq!(\n            Value::build_text(\"😊\").exec_unicode(),\n            Value::from_i64(128522)\n        );\n        assert_eq!(Value::build_text(\"\").exec_unicode(), Value::Null);\n        assert_eq!(Value::build_text(\"\\0\").exec_unicode(), Value::Null);\n        assert_eq!(Value::from_i64(23).exec_unicode(), Value::from_i64(50));\n        assert_eq!(Value::from_i64(0).exec_unicode(), Value::from_i64(48));\n        assert_eq!(Value::from_f64(0.0).exec_unicode(), Value::from_i64(48));\n        assert_eq!(Value::from_f64(23.45).exec_unicode(), Value::from_i64(50));\n        assert_eq!(Value::Null.exec_unicode(), Value::Null);\n        assert_eq!(\n            Value::Blob(\"example\".as_bytes().to_vec()).exec_unicode(),\n            Value::from_i64(101)\n        );\n    }\n\n    #[test]\n    fn test_min_max() {\n        let input_int_vec = [\n            Register::Value(Value::from_i64(-1)),\n            Register::Value(Value::from_i64(10)),\n        ];\n        assert_eq!(\n            Value::exec_min(input_int_vec.iter().map(|v| v.get_value())),\n            Value::from_i64(-1)\n        );\n        assert_eq!(\n            Value::exec_max(input_int_vec.iter().map(|v| v.get_value())),\n            Value::from_i64(10)\n        );\n\n        let str1 = Register::Value(Value::build_text(\"A\"));\n        let str2 = Register::Value(Value::build_text(\"z\"));\n        let input_str_vec = [str2, str1.clone()];\n        assert_eq!(\n            Value::exec_min(input_str_vec.iter().map(|v| v.get_value())),\n            Value::build_text(\"A\")\n        );\n        assert_eq!(\n            Value::exec_max(input_str_vec.iter().map(|v| v.get_value())),\n            Value::build_text(\"z\")\n        );\n\n        let input_null_vec = [Register::Value(Value::Null), Register::Value(Value::Null)];\n        assert_eq!(\n            Value::exec_min(input_null_vec.iter().map(|v| v.get_value())),\n            Value::Null\n        );\n        assert_eq!(\n            Value::exec_max(input_null_vec.iter().map(|v| v.get_value())),\n            Value::Null\n        );\n\n        let input_mixed_vec = [Register::Value(Value::from_i64(10)), str1];\n        assert_eq!(\n            Value::exec_min(input_mixed_vec.iter().map(|v| v.get_value())),\n            Value::from_i64(10)\n        );\n        assert_eq!(\n            Value::exec_max(input_mixed_vec.iter().map(|v| v.get_value())),\n            Value::build_text(\"A\")\n        );\n\n        // SQLite: multi-arg min/max returns NULL if ANY argument is NULL\n        let input_with_null = [\n            Register::Value(Value::from_i64(1)),\n            Register::Value(Value::Null),\n        ];\n        assert_eq!(\n            Value::exec_min(input_with_null.iter().map(|v| v.get_value())),\n            Value::Null\n        );\n        assert_eq!(\n            Value::exec_max(input_with_null.iter().map(|v| v.get_value())),\n            Value::Null\n        );\n    }\n\n    #[test]\n    fn test_trim() {\n        let input_str = Value::build_text(\"     Bob and Alice     \");\n        let expected_str = Value::build_text(\"Bob and Alice\");\n        assert_eq!(input_str.exec_trim(None), expected_str);\n\n        let input_str = Value::build_text(\"     Bob and Alice     \");\n        let pattern_str = Value::build_text(\"Bob and\");\n        let expected_str = Value::build_text(\"Alice\");\n        assert_eq!(input_str.exec_trim(Some(&pattern_str)), expected_str);\n\n        let input_str = Value::build_text(\"\\ta\");\n        let expected_str = Value::build_text(\"\\ta\");\n        assert_eq!(input_str.exec_trim(None), expected_str);\n\n        let input_str = Value::build_text(\"\\na\");\n        let expected_str = Value::build_text(\"\\na\");\n        assert_eq!(input_str.exec_trim(None), expected_str);\n\n        // TRIM on Integer should return TEXT (SQLite compatibility)\n        let input_int = Value::from_i64(12345);\n        let expected_text = Value::build_text(\"12345\");\n        assert_eq!(input_int.exec_trim(None), expected_text);\n\n        // TRIM on Float should return TEXT (SQLite compatibility)\n        let input_float = Value::from_f64(123.5);\n        let expected_text = Value::build_text(\"123.5\");\n        assert_eq!(input_float.exec_trim(None), expected_text);\n    }\n\n    #[test]\n    fn test_ltrim() {\n        let input_str = Value::build_text(\"     Bob and Alice     \");\n        let expected_str = Value::build_text(\"Bob and Alice     \");\n        assert_eq!(input_str.exec_ltrim(None), expected_str);\n\n        let input_str = Value::build_text(\"     Bob and Alice     \");\n        let pattern_str = Value::build_text(\"Bob and\");\n        let expected_str = Value::build_text(\"Alice     \");\n        assert_eq!(input_str.exec_ltrim(Some(&pattern_str)), expected_str);\n    }\n\n    #[test]\n    fn test_rtrim() {\n        let input_str = Value::build_text(\"     Bob and Alice     \");\n        let expected_str = Value::build_text(\"     Bob and Alice\");\n        assert_eq!(input_str.exec_rtrim(None), expected_str);\n\n        let input_str = Value::build_text(\"     Bob and Alice     \");\n        let pattern_str = Value::build_text(\"Bob and\");\n        let expected_str = Value::build_text(\"     Bob and Alice\");\n        assert_eq!(input_str.exec_rtrim(Some(&pattern_str)), expected_str);\n\n        let input_str = Value::build_text(\"     Bob and Alice     \");\n        let pattern_str = Value::build_text(\"and Alice\");\n        let expected_str = Value::build_text(\"     Bob\");\n        assert_eq!(input_str.exec_rtrim(Some(&pattern_str)), expected_str);\n    }\n\n    #[test]\n    fn test_soundex() {\n        let input_str = Value::build_text(\"Pfister\");\n        let expected_str = Value::build_text(\"P236\");\n        assert_eq!(input_str.exec_soundex(), expected_str);\n\n        let input_str = Value::build_text(\"husobee\");\n        let expected_str = Value::build_text(\"H210\");\n        assert_eq!(input_str.exec_soundex(), expected_str);\n\n        let input_str = Value::build_text(\"Tymczak\");\n        let expected_str = Value::build_text(\"T522\");\n        assert_eq!(input_str.exec_soundex(), expected_str);\n\n        let input_str = Value::build_text(\"Ashcraft\");\n        let expected_str = Value::build_text(\"A261\");\n        assert_eq!(input_str.exec_soundex(), expected_str);\n\n        let input_str = Value::build_text(\"Robert\");\n        let expected_str = Value::build_text(\"R163\");\n        assert_eq!(input_str.exec_soundex(), expected_str);\n\n        let input_str = Value::build_text(\"Rupert\");\n        let expected_str = Value::build_text(\"R163\");\n        assert_eq!(input_str.exec_soundex(), expected_str);\n\n        let input_str = Value::build_text(\"Rubin\");\n        let expected_str = Value::build_text(\"R150\");\n        assert_eq!(input_str.exec_soundex(), expected_str);\n\n        let input_str = Value::build_text(\"Kant\");\n        let expected_str = Value::build_text(\"K530\");\n        assert_eq!(input_str.exec_soundex(), expected_str);\n\n        let input_str = Value::build_text(\"Knuth\");\n        let expected_str = Value::build_text(\"K530\");\n        assert_eq!(input_str.exec_soundex(), expected_str);\n\n        let input_str = Value::build_text(\"x\");\n        let expected_str = Value::build_text(\"X000\");\n        assert_eq!(input_str.exec_soundex(), expected_str);\n\n        let input_str = Value::build_text(\"闪电五连鞭\");\n        let expected_str = Value::build_text(\"?000\");\n        assert_eq!(input_str.exec_soundex(), expected_str);\n    }\n\n    #[test]\n    fn test_upper_case() {\n        let input_str = Value::build_text(\"Limbo\");\n        let expected_str = Value::build_text(\"LIMBO\");\n        assert_eq!(input_str.exec_upper().unwrap(), expected_str);\n\n        let input_int = Value::from_i64(10);\n        assert_eq!(input_int.exec_upper().unwrap(), Value::build_text(\"10\"));\n        assert_eq!(Value::Null.exec_upper(), None)\n    }\n\n    #[test]\n    fn test_lower_case() {\n        let input_str = Value::build_text(\"Limbo\");\n        let expected_str = Value::build_text(\"limbo\");\n        assert_eq!(input_str.exec_lower().unwrap(), expected_str);\n\n        let input_int = Value::from_i64(10);\n        assert_eq!(input_int.exec_lower().unwrap(), Value::build_text(\"10\"));\n        assert_eq!(Value::Null.exec_lower(), None)\n    }\n\n    #[test]\n    fn test_hex() {\n        let input_str = Value::build_text(\"limbo\");\n        let expected_val = Value::build_text(\"6C696D626F\");\n        assert_eq!(input_str.exec_hex(), expected_val);\n\n        let input_int = Value::from_i64(100);\n        let expected_val = Value::build_text(\"313030\");\n        assert_eq!(input_int.exec_hex(), expected_val);\n\n        let input_float = Value::from_f64(12.34);\n        let expected_val = Value::build_text(\"31322E3334\");\n        assert_eq!(input_float.exec_hex(), expected_val);\n\n        let input_blob = Value::Blob(vec![0xff]);\n        let expected_val = Value::build_text(\"FF\");\n        assert_eq!(input_blob.exec_hex(), expected_val);\n    }\n\n    #[test]\n    fn test_unhex() {\n        let input = Value::build_text(\"6f\");\n        let expected = Value::Blob(vec![0x6f]);\n        assert_eq!(input.exec_unhex(None), expected);\n\n        let input = Value::build_text(\"6f\");\n        let expected = Value::Blob(vec![0x6f]);\n        assert_eq!(input.exec_unhex(None), expected);\n\n        let input = Value::build_text(\"611\");\n        let expected = Value::Null;\n        assert_eq!(input.exec_unhex(None), expected);\n\n        let input = Value::build_text(\"\");\n        let expected = Value::Blob(vec![]);\n        assert_eq!(input.exec_unhex(None), expected);\n\n        let input = Value::build_text(\"61x\");\n        let expected = Value::Null;\n        assert_eq!(input.exec_unhex(None), expected);\n\n        let input = Value::Null;\n        let expected = Value::Null;\n        assert_eq!(input.exec_unhex(None), expected);\n    }\n\n    #[test]\n    fn test_abs() {\n        let int_positive_reg = Value::from_i64(10);\n        let int_negative_reg = Value::from_i64(-10);\n        assert_eq!(int_positive_reg.exec_abs().unwrap(), int_positive_reg);\n        assert_eq!(int_negative_reg.exec_abs().unwrap(), int_positive_reg);\n\n        let float_positive_reg = Value::from_i64(10);\n        let float_negative_reg = Value::from_i64(-10);\n        assert_eq!(float_positive_reg.exec_abs().unwrap(), float_positive_reg);\n        assert_eq!(float_negative_reg.exec_abs().unwrap(), float_positive_reg);\n\n        assert_eq!(\n            Value::build_text(\"a\").exec_abs().unwrap(),\n            Value::from_f64(0.0)\n        );\n        assert_eq!(Value::Null.exec_abs().unwrap(), Value::Null);\n\n        // ABS(i64::MIN) should return RuntimeError\n        assert!(Value::from_i64(i64::MIN).exec_abs().is_err());\n    }\n\n    #[test]\n    fn test_char() {\n        assert_eq!(\n            Value::exec_char(\n                [\n                    Register::Value(Value::from_i64(108)),\n                    Register::Value(Value::from_i64(105))\n                ]\n                .iter()\n                .map(|reg| reg.get_value())\n            ),\n            Value::build_text(\"li\")\n        );\n        assert_eq!(Value::exec_char(std::iter::empty()), Value::build_text(\"\"));\n        assert_eq!(\n            Value::exec_char(\n                [Register::Value(Value::Null)]\n                    .iter()\n                    .map(|reg| reg.get_value())\n            ),\n            Value::build_text(\"\\0\")\n        );\n        assert_eq!(\n            Value::exec_char(\n                [Register::Value(Value::build_text(\"a\"))]\n                    .iter()\n                    .map(|reg| reg.get_value())\n            ),\n            Value::build_text(\"\")\n        );\n    }\n\n    #[test]\n    fn test_like_with_escape_or_regexmeta_chars() {\n        assert!(Value::exec_like(r#\"\\%A\"#, r#\"\\A\"#, None).unwrap());\n        assert!(Value::exec_like(\"%a%a\", \"aaaa\", None).unwrap());\n    }\n\n    #[test]\n    fn test_like_without_escape() {\n        assert!(Value::exec_like(\"a%\", \"aaaa\", None).unwrap());\n        assert!(Value::exec_like(\"%a%a\", \"aaaa\", None).unwrap());\n        assert!(!Value::exec_like(\"%a.a\", \"aaaa\", None).unwrap());\n        assert!(!Value::exec_like(\"a.a%\", \"aaaa\", None).unwrap());\n        assert!(!Value::exec_like(\"%a.ab\", \"aaaa\", None).unwrap());\n    }\n\n    #[test]\n    fn test_exec_like_with_escape() {\n        assert!(Value::exec_like(\"abcX%\", \"abc%\", Some('X')).unwrap());\n        assert!(!Value::exec_like(\"abcX%\", \"abc5\", Some('X')).unwrap());\n        assert!(!Value::exec_like(\"abcX%\", \"abc\", Some('X')).unwrap());\n        assert!(!Value::exec_like(\"abcX%\", \"abcX%\", Some('X')).unwrap());\n        assert!(!Value::exec_like(\"abcX%\", \"abc%%\", Some('X')).unwrap());\n\n        assert!(Value::exec_like(\"abcX_\", \"abc_\", Some('X')).unwrap());\n        assert!(!Value::exec_like(\"abcX_\", \"abc5\", Some('X')).unwrap());\n        assert!(!Value::exec_like(\"abcX_\", \"abc\", Some('X')).unwrap());\n        assert!(!Value::exec_like(\"abcX_\", \"abcX_\", Some('X')).unwrap());\n        assert!(!Value::exec_like(\"abcX_\", \"abc__\", Some('X')).unwrap());\n\n        assert!(Value::exec_like(\"abcXX\", \"abcX\", Some('X')).unwrap());\n        assert!(!Value::exec_like(\"abcXX\", \"abc5\", Some('X')).unwrap());\n        assert!(!Value::exec_like(\"abcXX\", \"abc\", Some('X')).unwrap());\n        assert!(!Value::exec_like(\"abcXX\", \"abcXX\", Some('X')).unwrap());\n    }\n\n    #[test]\n    fn test_glob() {\n        assert!(Value::exec_glob(r#\"?*/abc/?*\"#, r#\"x//a/ab/abc/y\"#).unwrap());\n        assert!(Value::exec_glob(r#\"a[1^]\"#, r#\"a1\"#).unwrap());\n        assert!(Value::exec_glob(r#\"a[1^]*\"#, r#\"a^\"#).unwrap());\n        assert!(!Value::exec_glob(r#\"a[a*\"#, r#\"a[\"#).unwrap());\n        assert!(!Value::exec_glob(r#\"a[a\"#, r#\"a[a\"#).unwrap());\n        assert!(Value::exec_glob(r#\"a[[]\"#, r#\"a[\"#).unwrap());\n        assert!(Value::exec_glob(r#\"abc[^][*?]efg\"#, r#\"abcdefg\"#).unwrap());\n        assert!(!Value::exec_glob(r#\"abc[^][*?]efg\"#, r#\"abc]efg\"#).unwrap());\n    }\n\n    #[test]\n    fn test_random() {\n        match Value::exec_random(|| rand::rng().random()) {\n            Value::Numeric(Numeric::Integer(value)) => {\n                // Check that the value is within the range of i64\n                assert!(\n                    (i64::MIN..=i64::MAX).contains(&value),\n                    \"Random number out of range\"\n                );\n            }\n            _ => panic!(\"exec_random did not return an Integer variant\"),\n        }\n    }\n\n    #[test]\n    fn test_exec_randomblob() {\n        struct TestCase {\n            input: Value,\n            expected_len: usize,\n        }\n\n        let test_cases = vec![\n            TestCase {\n                input: Value::from_i64(5),\n                expected_len: 5,\n            },\n            TestCase {\n                input: Value::from_i64(0),\n                expected_len: 1,\n            },\n            TestCase {\n                input: Value::from_i64(-1),\n                expected_len: 1,\n            },\n            TestCase {\n                input: Value::build_text(\"\"),\n                expected_len: 1,\n            },\n            TestCase {\n                input: Value::build_text(\"5\"),\n                expected_len: 5,\n            },\n            TestCase {\n                input: Value::build_text(\"0\"),\n                expected_len: 1,\n            },\n            TestCase {\n                input: Value::build_text(\"-1\"),\n                expected_len: 1,\n            },\n            TestCase {\n                input: Value::from_f64(2.9),\n                expected_len: 2,\n            },\n            TestCase {\n                input: Value::from_f64(-3.15),\n                expected_len: 1,\n            },\n            TestCase {\n                input: Value::Null,\n                expected_len: 1,\n            },\n        ];\n\n        for test_case in &test_cases {\n            let result = test_case\n                .input\n                .exec_randomblob(|dest| {\n                    rand::rng().fill_bytes(dest);\n                })\n                .unwrap();\n            match result {\n                Value::Blob(blob) => {\n                    assert_eq!(blob.len(), test_case.expected_len);\n                }\n                _ => panic!(\"exec_randomblob did not return a Blob variant\"),\n            }\n        }\n\n        // Test TooBig error\n        let input = Value::from_i64(Value::MAX_BLOB_LENGTH + 1);\n        assert!(input.exec_randomblob(|_| {}).is_err());\n    }\n\n    #[test]\n    fn test_exec_round() {\n        let input_val = Value::from_f64(123.456);\n        let expected_val = Value::from_f64(123.0);\n        assert_eq!(input_val.exec_round(None), expected_val);\n\n        let input_val = Value::from_f64(123.456);\n        let precision_val = Value::from_i64(2);\n        let expected_val = Value::from_f64(123.46);\n        assert_eq!(input_val.exec_round(Some(&precision_val)), expected_val);\n\n        let input_val = Value::from_f64(123.456);\n        let precision_val = Value::build_text(\"1\");\n        let expected_val = Value::from_f64(123.5);\n        assert_eq!(input_val.exec_round(Some(&precision_val)), expected_val);\n\n        let input_val = Value::build_text(\"123.456\");\n        let precision_val = Value::from_i64(2);\n        let expected_val = Value::from_f64(123.46);\n        assert_eq!(input_val.exec_round(Some(&precision_val)), expected_val);\n\n        let input_val = Value::from_i64(123);\n        let precision_val = Value::from_i64(1);\n        let expected_val = Value::from_f64(123.0);\n        assert_eq!(input_val.exec_round(Some(&precision_val)), expected_val);\n\n        let input_val = Value::from_f64(100.123);\n        let expected_val = Value::from_f64(100.0);\n        assert_eq!(input_val.exec_round(None), expected_val);\n\n        let input_val = Value::from_f64(100.123);\n        let expected_val = Value::Null;\n        assert_eq!(input_val.exec_round(Some(&Value::Null)), expected_val);\n    }\n\n    #[test]\n    fn test_exec_if() {\n        let reg = Value::from_i64(0);\n        assert!(!reg.exec_if(false, false));\n        assert!(reg.exec_if(false, true));\n\n        let reg = Value::from_i64(1);\n        assert!(reg.exec_if(false, false));\n        assert!(!reg.exec_if(false, true));\n\n        let reg = Value::Null;\n        assert!(!reg.exec_if(false, false));\n        assert!(!reg.exec_if(false, true));\n\n        let reg = Value::Null;\n        assert!(reg.exec_if(true, false));\n        assert!(reg.exec_if(true, true));\n\n        let reg = Value::Null;\n        assert!(!reg.exec_if(false, false));\n        assert!(!reg.exec_if(false, true));\n    }\n\n    #[test]\n    fn test_nullif() {\n        assert_eq!(\n            Value::from_i64(1).exec_nullif(&Value::from_i64(1)),\n            Value::Null\n        );\n        assert_eq!(\n            Value::from_f64(1.1).exec_nullif(&Value::from_f64(1.1)),\n            Value::Null\n        );\n        assert_eq!(\n            Value::build_text(\"limbo\").exec_nullif(&Value::build_text(\"limbo\")),\n            Value::Null\n        );\n\n        assert_eq!(\n            Value::from_i64(1).exec_nullif(&Value::from_i64(2)),\n            Value::from_i64(1)\n        );\n        assert_eq!(\n            Value::from_f64(1.1).exec_nullif(&Value::from_f64(1.2)),\n            Value::from_f64(1.1)\n        );\n        assert_eq!(\n            Value::build_text(\"limbo\").exec_nullif(&Value::build_text(\"limb\")),\n            Value::build_text(\"limbo\")\n        );\n    }\n\n    #[test]\n    fn test_substring() {\n        let str_value = Value::build_text(\"limbo\");\n        let start_value = Value::from_i64(1);\n        let length_value = Value::from_i64(3);\n        let expected_val = Value::build_text(\"lim\");\n        assert_eq!(\n            Value::exec_substring(&str_value, &start_value, Some(&length_value)),\n            expected_val\n        );\n\n        let str_value = Value::build_text(\"limbo\");\n        let start_value = Value::from_i64(1);\n        let length_value = Value::from_i64(10);\n        let expected_val = Value::build_text(\"limbo\");\n        assert_eq!(\n            Value::exec_substring(&str_value, &start_value, Some(&length_value)),\n            expected_val\n        );\n\n        let str_value = Value::build_text(\"limbo\");\n        let start_value = Value::from_i64(10);\n        let length_value = Value::from_i64(3);\n        let expected_val = Value::build_text(\"\");\n        assert_eq!(\n            Value::exec_substring(&str_value, &start_value, Some(&length_value)),\n            expected_val\n        );\n\n        let str_value = Value::build_text(\"limbo\");\n        let start_value = Value::from_i64(3);\n        let length_value = Value::Null;\n        let expected_val = Value::Null;\n        assert_eq!(\n            Value::exec_substring(&str_value, &start_value, Some(&length_value)),\n            expected_val\n        );\n\n        let str_value = Value::build_text(\"limbo\");\n        let start_value = Value::from_i64(10);\n        let length_value = Value::Null;\n        let expected_val = Value::Null;\n        assert_eq!(\n            Value::exec_substring(&str_value, &start_value, Some(&length_value)),\n            expected_val\n        );\n    }\n\n    #[test]\n    fn test_exec_instr() {\n        let input = Value::build_text(\"limbo\");\n        let pattern = Value::build_text(\"im\");\n        let expected = Value::from_i64(2);\n        assert_eq!(input.exec_instr(&pattern), expected);\n\n        let input = Value::build_text(\"limbo\");\n        let pattern = Value::build_text(\"limbo\");\n        let expected = Value::from_i64(1);\n        assert_eq!(input.exec_instr(&pattern), expected);\n\n        let input = Value::build_text(\"limbo\");\n        let pattern = Value::build_text(\"o\");\n        let expected = Value::from_i64(5);\n        assert_eq!(input.exec_instr(&pattern), expected);\n\n        let input = Value::build_text(\"liiiiimbo\");\n        let pattern = Value::build_text(\"ii\");\n        let expected = Value::from_i64(2);\n        assert_eq!(input.exec_instr(&pattern), expected);\n\n        let input = Value::build_text(\"limbo\");\n        let pattern = Value::build_text(\"limboX\");\n        let expected = Value::from_i64(0);\n        assert_eq!(input.exec_instr(&pattern), expected);\n\n        let input = Value::build_text(\"limbo\");\n        let pattern = Value::build_text(\"\");\n        let expected = Value::from_i64(1);\n        assert_eq!(input.exec_instr(&pattern), expected);\n\n        let input = Value::build_text(\"\");\n        let pattern = Value::build_text(\"limbo\");\n        let expected = Value::from_i64(0);\n        assert_eq!(input.exec_instr(&pattern), expected);\n\n        let input = Value::build_text(\"\");\n        let pattern = Value::build_text(\"\");\n        let expected = Value::from_i64(1);\n        assert_eq!(input.exec_instr(&pattern), expected);\n\n        let input = Value::Null;\n        let pattern = Value::Null;\n        let expected = Value::Null;\n        assert_eq!(input.exec_instr(&pattern), expected);\n\n        let input = Value::build_text(\"limbo\");\n        let pattern = Value::Null;\n        let expected = Value::Null;\n        assert_eq!(input.exec_instr(&pattern), expected);\n\n        let input = Value::Null;\n        let pattern = Value::build_text(\"limbo\");\n        let expected = Value::Null;\n        assert_eq!(input.exec_instr(&pattern), expected);\n\n        let input = Value::from_i64(123);\n        let pattern = Value::from_i64(2);\n        let expected = Value::from_i64(2);\n        assert_eq!(input.exec_instr(&pattern), expected);\n\n        let input = Value::from_i64(123);\n        let pattern = Value::from_i64(5);\n        let expected = Value::from_i64(0);\n        assert_eq!(input.exec_instr(&pattern), expected);\n\n        let input = Value::from_f64(12.34);\n        let pattern = Value::from_f64(2.3);\n        let expected = Value::from_i64(2);\n        assert_eq!(input.exec_instr(&pattern), expected);\n\n        let input = Value::from_f64(12.34);\n        let pattern = Value::from_f64(5.6);\n        let expected = Value::from_i64(0);\n        assert_eq!(input.exec_instr(&pattern), expected);\n\n        let input = Value::from_f64(12.34);\n        let pattern = Value::build_text(\".\");\n        let expected = Value::from_i64(3);\n        assert_eq!(input.exec_instr(&pattern), expected);\n\n        let input = Value::Blob(vec![1, 2, 3, 4, 5]);\n        let pattern = Value::Blob(vec![3, 4]);\n        let expected = Value::from_i64(3);\n        assert_eq!(input.exec_instr(&pattern), expected);\n\n        let input = Value::Blob(vec![1, 2, 3, 4, 5]);\n        let pattern = Value::Blob(vec![3, 2]);\n        let expected = Value::from_i64(0);\n        assert_eq!(input.exec_instr(&pattern), expected);\n\n        let input = Value::Blob(vec![0x61, 0x62, 0x63, 0x64, 0x65]);\n        let pattern = Value::build_text(\"cd\");\n        let expected = Value::from_i64(3);\n        assert_eq!(input.exec_instr(&pattern), expected);\n\n        let input = Value::build_text(\"abcde\");\n        let pattern = Value::Blob(vec![0x63, 0x64]);\n        let expected = Value::from_i64(3);\n        assert_eq!(input.exec_instr(&pattern), expected);\n\n        let input = Value::build_text(\"abcde\");\n        let pattern = Value::build_text(\"\");\n        let expected = Value::from_i64(1);\n        assert_eq!(input.exec_instr(&pattern), expected);\n    }\n\n    #[test]\n    fn test_exec_sign() {\n        let input = Value::from_i64(42);\n        let expected = Some(Value::from_i64(1));\n        assert_eq!(input.exec_sign(), expected);\n\n        let input = Value::from_i64(-42);\n        let expected = Some(Value::from_i64(-1));\n        assert_eq!(input.exec_sign(), expected);\n\n        let input = Value::from_i64(0);\n        let expected = Some(Value::from_i64(0));\n        assert_eq!(input.exec_sign(), expected);\n\n        let input = Value::from_f64(0.0);\n        let expected = Some(Value::from_i64(0));\n        assert_eq!(input.exec_sign(), expected);\n\n        let input = Value::from_f64(0.1);\n        let expected = Some(Value::from_i64(1));\n        assert_eq!(input.exec_sign(), expected);\n\n        let input = Value::from_f64(42.0);\n        let expected = Some(Value::from_i64(1));\n        assert_eq!(input.exec_sign(), expected);\n\n        let input = Value::from_f64(-42.0);\n        let expected = Some(Value::from_i64(-1));\n        assert_eq!(input.exec_sign(), expected);\n\n        let input = Value::build_text(\"abc\");\n        let expected = None;\n        assert_eq!(input.exec_sign(), expected);\n\n        let input = Value::build_text(\"42\");\n        let expected = Some(Value::from_i64(1));\n        assert_eq!(input.exec_sign(), expected);\n\n        let input = Value::build_text(\"-42\");\n        let expected = Some(Value::from_i64(-1));\n        assert_eq!(input.exec_sign(), expected);\n\n        let input = Value::build_text(\"0\");\n        let expected = Some(Value::from_i64(0));\n        assert_eq!(input.exec_sign(), expected);\n\n        let input = Value::Blob(b\"abc\".to_vec());\n        let expected = None;\n        assert_eq!(input.exec_sign(), expected);\n\n        let input = Value::Blob(b\"42\".to_vec());\n        let expected = None;\n        assert_eq!(input.exec_sign(), expected);\n\n        let input = Value::Blob(b\"-42\".to_vec());\n        let expected = None;\n        assert_eq!(input.exec_sign(), expected);\n\n        let input = Value::Blob(b\"0\".to_vec());\n        let expected = None;\n        assert_eq!(input.exec_sign(), expected);\n\n        let input = Value::Null;\n        let expected = None;\n        assert_eq!(input.exec_sign(), expected);\n    }\n\n    #[test]\n    fn test_exec_zeroblob() {\n        let input = Value::from_i64(0);\n        let expected = Value::Blob(vec![]);\n        assert_eq!(input.exec_zeroblob().unwrap(), expected);\n\n        let input = Value::Null;\n        let expected = Value::Blob(vec![]);\n        assert_eq!(input.exec_zeroblob().unwrap(), expected);\n\n        let input = Value::from_i64(4);\n        let expected = Value::Blob(vec![0; 4]);\n        assert_eq!(input.exec_zeroblob().unwrap(), expected);\n\n        let input = Value::from_i64(-1);\n        let expected = Value::Blob(vec![]);\n        assert_eq!(input.exec_zeroblob().unwrap(), expected);\n\n        let input = Value::build_text(\"5\");\n        let expected = Value::Blob(vec![0; 5]);\n        assert_eq!(input.exec_zeroblob().unwrap(), expected);\n\n        let input = Value::build_text(\"-5\");\n        let expected = Value::Blob(vec![]);\n        assert_eq!(input.exec_zeroblob().unwrap(), expected);\n\n        let input = Value::build_text(\"text\");\n        let expected = Value::Blob(vec![]);\n        assert_eq!(input.exec_zeroblob().unwrap(), expected);\n\n        let input = Value::from_f64(2.6);\n        let expected = Value::Blob(vec![0; 2]);\n        assert_eq!(input.exec_zeroblob().unwrap(), expected);\n\n        let input = Value::Blob(vec![1]);\n        let expected = Value::Blob(vec![]);\n        assert_eq!(input.exec_zeroblob().unwrap(), expected);\n\n        // Test TooBig error\n        let input = Value::from_i64(Value::MAX_BLOB_LENGTH + 1);\n        assert!(input.exec_zeroblob().is_err());\n    }\n\n    #[test]\n    fn test_replace() {\n        let input_str = Value::build_text(\"bob\");\n        let pattern_str = Value::build_text(\"b\");\n        let replace_str = Value::build_text(\"a\");\n        let expected_str = Value::build_text(\"aoa\");\n        assert_eq!(\n            Value::exec_replace(&input_str, &pattern_str, &replace_str),\n            expected_str\n        );\n\n        let input_str = Value::build_text(\"bob\");\n        let pattern_str = Value::build_text(\"b\");\n        let replace_str = Value::build_text(\"\");\n        let expected_str = Value::build_text(\"o\");\n        assert_eq!(\n            Value::exec_replace(&input_str, &pattern_str, &replace_str),\n            expected_str\n        );\n\n        let input_str = Value::build_text(\"bob\");\n        let pattern_str = Value::build_text(\"b\");\n        let replace_str = Value::build_text(\"abc\");\n        let expected_str = Value::build_text(\"abcoabc\");\n        assert_eq!(\n            Value::exec_replace(&input_str, &pattern_str, &replace_str),\n            expected_str\n        );\n\n        let input_str = Value::build_text(\"bob\");\n        let pattern_str = Value::build_text(\"a\");\n        let replace_str = Value::build_text(\"b\");\n        let expected_str = Value::build_text(\"bob\");\n        assert_eq!(\n            Value::exec_replace(&input_str, &pattern_str, &replace_str),\n            expected_str\n        );\n\n        let input_str = Value::build_text(\"bob\");\n        let pattern_str = Value::build_text(\"\");\n        let replace_str = Value::build_text(\"a\");\n        let expected_str = Value::build_text(\"bob\");\n        assert_eq!(\n            Value::exec_replace(&input_str, &pattern_str, &replace_str),\n            expected_str\n        );\n\n        let input_str = Value::build_text(\"bob\");\n        let pattern_str = Value::Null;\n        let replace_str = Value::build_text(\"a\");\n        let expected_str = Value::Null;\n        assert_eq!(\n            Value::exec_replace(&input_str, &pattern_str, &replace_str),\n            expected_str\n        );\n\n        let input_str = Value::build_text(\"bo5\");\n        let pattern_str = Value::from_i64(5);\n        let replace_str = Value::build_text(\"a\");\n        let expected_str = Value::build_text(\"boa\");\n        assert_eq!(\n            Value::exec_replace(&input_str, &pattern_str, &replace_str),\n            expected_str\n        );\n\n        let input_str = Value::build_text(\"bo5.0\");\n        let pattern_str = Value::from_f64(5.0);\n        let replace_str = Value::build_text(\"a\");\n        let expected_str = Value::build_text(\"boa\");\n        assert_eq!(\n            Value::exec_replace(&input_str, &pattern_str, &replace_str),\n            expected_str\n        );\n\n        let input_str = Value::build_text(\"bo5\");\n        let pattern_str = Value::from_f64(5.0);\n        let replace_str = Value::build_text(\"a\");\n        let expected_str = Value::build_text(\"bo5\");\n        assert_eq!(\n            Value::exec_replace(&input_str, &pattern_str, &replace_str),\n            expected_str\n        );\n\n        let input_str = Value::build_text(\"bo5.0\");\n        let pattern_str = Value::from_f64(5.0);\n        let replace_str = Value::from_f64(6.0);\n        let expected_str = Value::build_text(\"bo6.0\");\n        assert_eq!(\n            Value::exec_replace(&input_str, &pattern_str, &replace_str),\n            expected_str\n        );\n\n        // todo: change this test to use (0.1 + 0.2) instead of 0.3 when decimals are implemented.\n        let input_str = Value::build_text(\"tes3\");\n        let pattern_str = Value::from_i64(3);\n        let replace_str = Value::from_f64(0.3);\n        let expected_str = Value::build_text(\"tes0.3\");\n        assert_eq!(\n            Value::exec_replace(&input_str, &pattern_str, &replace_str),\n            expected_str\n        );\n    }\n}\n"
  },
  {
    "path": "core/vector/mod.rs",
    "content": "use crate::types::AsValueRef;\nuse crate::types::Value;\nuse crate::types::ValueType;\nuse crate::vdbe::Register;\nuse crate::LimboError;\nuse crate::Result;\nuse crate::ValueRef;\n\npub mod operations;\npub mod vector_types;\nuse vector_types::*;\n\npub fn parse_vector<'a>(\n    value: &'a (impl AsValueRef + 'a),\n    type_hint: Option<VectorType>,\n) -> Result<Vector<'a>> {\n    let value = value.as_value_ref();\n    match value.value_type() {\n        ValueType::Text => operations::text::vector_from_text(\n            type_hint.unwrap_or(VectorType::Float32Dense),\n            value.to_text().expect(\"value must be text\"),\n        ),\n        ValueType::Blob => {\n            let Some(blob) = value.to_blob() else {\n                return Err(LimboError::ConversionError(\n                    \"Invalid vector value\".to_string(),\n                ));\n            };\n            Vector::from_slice(blob)\n        }\n        _ => Err(LimboError::ConversionError(\n            \"Invalid vector type\".to_string(),\n        )),\n    }\n}\n\npub fn vector32(args: &[Register]) -> Result<Value> {\n    if args.len() != 1 {\n        return Err(LimboError::ConversionError(\n            \"vector32 requires exactly one argument\".to_string(),\n        ));\n    }\n    let value = args[0].get_value();\n    let vector = parse_vector(value, Some(VectorType::Float32Dense))?;\n    let vector = operations::convert::vector_convert(vector, VectorType::Float32Dense)?;\n    Ok(operations::serialize::vector_serialize(vector))\n}\n\npub fn vector32_sparse(args: &[Register]) -> Result<Value> {\n    if args.len() != 1 {\n        return Err(LimboError::ConversionError(\n            \"vector32_sparse requires exactly one argument\".to_string(),\n        ));\n    }\n    let value = args[0].get_value();\n    let vector = parse_vector(value, Some(VectorType::Float32Sparse))?;\n    let vector = operations::convert::vector_convert(vector, VectorType::Float32Sparse)?;\n    Ok(operations::serialize::vector_serialize(vector))\n}\n\npub fn vector64(args: &[Register]) -> Result<Value> {\n    if args.len() != 1 {\n        return Err(LimboError::ConversionError(\n            \"vector64 requires exactly one argument\".to_string(),\n        ));\n    }\n    let value = args[0].get_value();\n    let vector = parse_vector(value, Some(VectorType::Float64Dense))?;\n    let vector = operations::convert::vector_convert(vector, VectorType::Float64Dense)?;\n    Ok(operations::serialize::vector_serialize(vector))\n}\n\npub fn vector8(args: &[Register]) -> Result<Value> {\n    if args.len() != 1 {\n        return Err(LimboError::ConversionError(\n            \"vector8 requires exactly one argument\".to_string(),\n        ));\n    }\n    let value = args[0].get_value();\n    let vector = parse_vector(value, Some(VectorType::Float8))?;\n    let vector = operations::convert::vector_convert(vector, VectorType::Float8)?;\n    Ok(operations::serialize::vector_serialize(vector))\n}\n\npub fn vector1bit(args: &[Register]) -> Result<Value> {\n    if args.len() != 1 {\n        return Err(LimboError::ConversionError(\n            \"vector1bit requires exactly one argument\".to_string(),\n        ));\n    }\n    let value = args[0].get_value();\n    let vector = parse_vector(value, Some(VectorType::Float1Bit))?;\n    let vector = operations::convert::vector_convert(vector, VectorType::Float1Bit)?;\n    Ok(operations::serialize::vector_serialize(vector))\n}\n\npub fn vector_extract(args: &[Register]) -> Result<Value> {\n    if args.len() != 1 {\n        return Err(LimboError::ConversionError(\n            \"vector_extract requires exactly one argument\".to_string(),\n        ));\n    }\n\n    let value = args[0].get_value().as_value_ref();\n    let blob = match value {\n        ValueRef::Blob(b) => b,\n        _ => {\n            return Err(LimboError::ConversionError(\n                \"Expected blob value\".to_string(),\n            ))\n        }\n    };\n\n    if blob.is_empty() {\n        return Ok(Value::build_text(\"[]\"));\n    }\n\n    let vector = Vector::from_slice(blob)?;\n    Ok(Value::build_text(operations::text::vector_to_text(&vector)))\n}\n\npub fn vector_distance_cos(args: &[Register]) -> Result<Value> {\n    if args.len() != 2 {\n        return Err(LimboError::ConversionError(\n            \"vector_distance_cos requires exactly two arguments\".to_string(),\n        ));\n    }\n\n    let value_0 = args[0].get_value();\n    let value_1 = args[1].get_value();\n    let x = parse_vector(value_0, None)?;\n    let y = parse_vector(value_1, None)?;\n    let dist = operations::distance_cos::vector_distance_cos(&x, &y)?;\n    Ok(Value::from_f64(dist))\n}\n\npub fn vector_distance_l2(args: &[Register]) -> Result<Value> {\n    if args.len() != 2 {\n        return Err(LimboError::ConversionError(\n            \"distance_l2 requires exactly two arguments\".to_string(),\n        ));\n    }\n\n    let value_0 = args[0].get_value();\n    let value_1 = args[1].get_value();\n    let x = parse_vector(value_0, None)?;\n    let y = parse_vector(value_1, None)?;\n    let dist = operations::distance_l2::vector_distance_l2(&x, &y)?;\n    Ok(Value::from_f64(dist))\n}\n\npub fn vector_distance_jaccard(args: &[Register]) -> Result<Value> {\n    if args.len() != 2 {\n        return Err(LimboError::ConversionError(\n            \"distance_jaccard requires exactly two arguments\".to_string(),\n        ));\n    }\n\n    let value_0 = args[0].get_value();\n    let value_1 = args[1].get_value();\n    let x = parse_vector(value_0, None)?;\n    let y = parse_vector(value_1, None)?;\n    let dist = operations::jaccard::vector_distance_jaccard(&x, &y)?;\n    Ok(Value::from_f64(dist))\n}\n\npub fn vector_distance_dot(args: &[Register]) -> Result<Value> {\n    if args.len() != 2 {\n        return Err(LimboError::ConversionError(\n            \"distance_dot requires exactly two arguments\".to_string(),\n        ));\n    }\n\n    let value_0 = args[0].get_value();\n    let value_1 = args[1].get_value();\n    let x = parse_vector(value_0, None)?;\n    let y = parse_vector(value_1, None)?;\n    let dist = operations::distance_dot::vector_distance_dot(&x, &y)?;\n    Ok(Value::from_f64(dist))\n}\n\npub fn vector_concat(args: &[Register]) -> Result<Value> {\n    if args.len() != 2 {\n        return Err(LimboError::InvalidArgument(\n            \"concat requires exactly two arguments\".into(),\n        ));\n    }\n\n    let value_0 = args[0].get_value();\n    let value_1 = args[1].get_value();\n    let x = parse_vector(value_0, None)?;\n    let y = parse_vector(value_1, None)?;\n    let vector = operations::concat::vector_concat(&x, &y)?;\n    Ok(operations::serialize::vector_serialize(vector))\n}\n\npub fn vector_slice(args: &[Register]) -> Result<Value> {\n    if args.len() != 3 {\n        return Err(LimboError::InvalidArgument(\n            \"vector_slice requires exactly three arguments\".into(),\n        ));\n    }\n    let value_0 = args[0].get_value();\n    let value_1 = args[1].get_value().as_value_ref();\n    let value_2 = args[2].get_value().as_value_ref();\n\n    let vector = parse_vector(value_0, None)?;\n\n    let start_index = value_1\n        .as_int()\n        .ok_or_else(|| LimboError::InvalidArgument(\"start index must be an integer\".into()))?;\n\n    let end_index = value_2\n        .as_int()\n        .ok_or_else(|| LimboError::InvalidArgument(\"end_index must be an integer\".into()))?;\n\n    if start_index < 0 || end_index < 0 {\n        return Err(LimboError::InvalidArgument(\n            \"start index and end_index must be non-negative\".into(),\n        ));\n    }\n\n    let result =\n        operations::slice::vector_slice(&vector, start_index as usize, end_index as usize)?;\n\n    Ok(operations::serialize::vector_serialize(result))\n}\n"
  },
  {
    "path": "core/vector/operations/concat.rs",
    "content": "use crate::{\n    vector::vector_types::{Vector, VectorType},\n    LimboError, Result,\n};\n\npub fn vector_concat(v1: &Vector, v2: &Vector) -> Result<Vector<'static>> {\n    if v1.vector_type != v2.vector_type {\n        return Err(LimboError::ConversionError(\n            \"Mismatched vector types\".into(),\n        ));\n    }\n\n    let data = match v1.vector_type {\n        VectorType::Float32Dense | VectorType::Float64Dense => {\n            let mut data = Vec::with_capacity(v1.bin_len() + v2.bin_len());\n            data.extend_from_slice(v1.bin_data());\n            data.extend_from_slice(v2.bin_data());\n            data\n        }\n        VectorType::Float32Sparse => {\n            let mut data = Vec::with_capacity(v1.bin_len() + v2.bin_len());\n            data.extend_from_slice(&v1.bin_data()[..v1.bin_len() / 2]);\n            data.extend_from_slice(&v2.bin_data()[..v2.bin_len() / 2]);\n            data.extend_from_slice(&v1.bin_data()[v1.bin_len() / 2..]);\n            data.extend_from_slice(&v2.bin_data()[v2.bin_len() / 2..]);\n            data\n        }\n        VectorType::Float1Bit | VectorType::Float8 => {\n            return Err(LimboError::ConversionError(\n                \"vector_concat is not supported for float1bit/float8 vectors\".to_string(),\n            ));\n        }\n    };\n\n    Ok(Vector {\n        vector_type: v1.vector_type,\n        dims: v1.dims + v2.dims,\n        owned: Some(data),\n        refer: None,\n    })\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::vector::{\n        operations::concat::vector_concat,\n        vector_types::{Vector, VectorType},\n    };\n\n    fn float32_vec_from(slice: &[f32]) -> Vector<'static> {\n        let mut data = Vec::new();\n        for &v in slice {\n            data.extend_from_slice(&v.to_le_bytes());\n        }\n\n        Vector {\n            vector_type: VectorType::Float32Dense,\n            dims: slice.len(),\n            owned: Some(data),\n            refer: None,\n        }\n    }\n\n    fn f32_slice_from_vector(vector: &Vector) -> Vec<f32> {\n        vector.as_f32_slice().to_vec()\n    }\n\n    #[test]\n    fn test_vector_concat_normal_case() {\n        let v1 = float32_vec_from(&[1.0, 2.0, 3.0]);\n        let v2 = float32_vec_from(&[4.0, 5.0, 6.0]);\n\n        let result = vector_concat(&v1, &v2).unwrap();\n\n        assert_eq!(result.dims, 6);\n        assert_eq!(result.vector_type, VectorType::Float32Dense);\n        assert_eq!(\n            f32_slice_from_vector(&result),\n            vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]\n        );\n    }\n\n    #[test]\n    fn test_vector_concat_empty_left() {\n        let v1 = float32_vec_from(&[]);\n        let v2 = float32_vec_from(&[4.0, 5.0]);\n\n        let result = vector_concat(&v1, &v2).unwrap();\n\n        assert_eq!(result.dims, 2);\n        assert_eq!(f32_slice_from_vector(&result), vec![4.0, 5.0]);\n    }\n\n    #[test]\n    fn test_vector_concat_empty_right() {\n        let v1 = float32_vec_from(&[1.0, 2.0]);\n        let v2 = float32_vec_from(&[]);\n\n        let result = vector_concat(&v1, &v2).unwrap();\n\n        assert_eq!(result.dims, 2);\n        assert_eq!(f32_slice_from_vector(&result), vec![1.0, 2.0]);\n    }\n\n    #[test]\n    fn test_vector_concat_both_empty() {\n        let v1 = float32_vec_from(&[]);\n        let v2 = float32_vec_from(&[]);\n        let result = vector_concat(&v1, &v2).unwrap();\n        assert_eq!(result.dims, 0);\n        assert_eq!(f32_slice_from_vector(&result), Vec::<f32>::new());\n    }\n\n    #[test]\n    fn test_vector_concat_different_lengths() {\n        let v1 = float32_vec_from(&[1.0]);\n        let v2 = float32_vec_from(&[2.0, 3.0, 4.0]);\n\n        let result = vector_concat(&v1, &v2).unwrap();\n\n        assert_eq!(result.dims, 4);\n        assert_eq!(f32_slice_from_vector(&result), vec![1.0, 2.0, 3.0, 4.0]);\n    }\n}\n"
  },
  {
    "path": "core/vector/operations/convert.rs",
    "content": "use crate::{\n    vector::vector_types::{Vector, VectorType},\n    Result,\n};\n\npub fn vector_convert(v: Vector, target_type: VectorType) -> Result<Vector> {\n    if v.vector_type == target_type {\n        return Ok(v);\n    }\n    match (v.vector_type, target_type) {\n        (VectorType::Float32Dense, VectorType::Float64Dense) => Ok(Vector::from_f64(\n            v.as_f32_slice().iter().map(|&x| x as f64).collect(),\n        )),\n        (VectorType::Float64Dense, VectorType::Float32Dense) => Ok(Vector::from_f32(\n            v.as_f64_slice().iter().map(|&x| x as f32).collect(),\n        )),\n        (VectorType::Float32Dense, VectorType::Float32Sparse) => {\n            let (mut idx, mut values) = (Vec::new(), Vec::new());\n            for (i, &value) in v.as_f32_slice().iter().enumerate() {\n                if value == 0.0 {\n                    continue;\n                }\n                idx.push(i as u32);\n                values.push(value);\n            }\n            Ok(Vector::from_f32_sparse(v.dims, values, idx))\n        }\n        (VectorType::Float64Dense, VectorType::Float32Sparse) => {\n            let (mut idx, mut values) = (Vec::new(), Vec::new());\n            for (i, &value) in v.as_f64_slice().iter().enumerate() {\n                if value == 0.0 {\n                    continue;\n                }\n                idx.push(i as u32);\n                values.push(value as f32);\n            }\n            Ok(Vector::from_f32_sparse(v.dims, values, idx))\n        }\n        (VectorType::Float32Sparse, VectorType::Float32Dense) => {\n            let sparse = v.as_f32_sparse();\n            let mut data = vec![0f32; v.dims];\n            for (&i, &value) in sparse.idx.iter().zip(sparse.values.iter()) {\n                data[i as usize] = value;\n            }\n            Ok(Vector::from_f32(data))\n        }\n        (VectorType::Float32Sparse, VectorType::Float64Dense) => {\n            let sparse = v.as_f32_sparse();\n            let mut data = vec![0f64; v.dims];\n            for (&i, &value) in sparse.idx.iter().zip(sparse.values.iter()) {\n                data[i as usize] = value as f64;\n            }\n            Ok(Vector::from_f64(data))\n        }\n        // Float1Bit conversions\n        (VectorType::Float32Dense, VectorType::Float1Bit) => {\n            let dims = v.dims;\n            let byte_count = dims.div_ceil(8);\n            let mut bits = vec![0u8; byte_count];\n            for (i, &val) in v.as_f32_slice().iter().enumerate() {\n                if val > 0.0 {\n                    bits[i / 8] |= 1 << (i & 7);\n                }\n            }\n            Ok(Vector::from_1bit(dims, bits))\n        }\n        (VectorType::Float64Dense, VectorType::Float1Bit) => {\n            let dims = v.dims;\n            let byte_count = dims.div_ceil(8);\n            let mut bits = vec![0u8; byte_count];\n            for (i, &val) in v.as_f64_slice().iter().enumerate() {\n                if val > 0.0 {\n                    bits[i / 8] |= 1 << (i & 7);\n                }\n            }\n            Ok(Vector::from_1bit(dims, bits))\n        }\n        (VectorType::Float1Bit, VectorType::Float32Dense) => {\n            let data = v.as_1bit_data();\n            let floats: Vec<f32> = (0..v.dims)\n                .map(|i| {\n                    if (data[i / 8] >> (i & 7)) & 1 == 1 {\n                        1.0\n                    } else {\n                        -1.0\n                    }\n                })\n                .collect();\n            Ok(Vector::from_f32(floats))\n        }\n        (VectorType::Float1Bit, VectorType::Float64Dense) => {\n            let data = v.as_1bit_data();\n            let floats: Vec<f64> = (0..v.dims)\n                .map(|i| {\n                    if (data[i / 8] >> (i & 7)) & 1 == 1 {\n                        1.0\n                    } else {\n                        -1.0\n                    }\n                })\n                .collect();\n            Ok(Vector::from_f64(floats))\n        }\n        // Float8 conversions\n        (VectorType::Float32Dense, VectorType::Float8) => {\n            convert_floats_to_f8(v.as_f32_slice().iter().copied(), v.dims)\n        }\n        (VectorType::Float64Dense, VectorType::Float8) => {\n            convert_floats_to_f8(v.as_f64_slice().iter().map(|&x| x as f32), v.dims)\n        }\n        (VectorType::Float8, VectorType::Float32Dense) => {\n            let (quantized, alpha, shift) = v.as_f8_data();\n            let floats: Vec<f32> = quantized\n                .iter()\n                .map(|&q| alpha * q as f32 + shift)\n                .collect();\n            Ok(Vector::from_f32(floats))\n        }\n        (VectorType::Float8, VectorType::Float64Dense) => {\n            let (quantized, alpha, shift) = v.as_f8_data();\n            let floats: Vec<f64> = quantized\n                .iter()\n                .map(|&q| alpha as f64 * q as f64 + shift as f64)\n                .collect();\n            Ok(Vector::from_f64(floats))\n        }\n        // Cross-conversions via intermediate\n        (VectorType::Float1Bit, VectorType::Float8) => {\n            let f32_vec = vector_convert(v, VectorType::Float32Dense)?;\n            vector_convert(f32_vec, VectorType::Float8)\n        }\n        (VectorType::Float8, VectorType::Float1Bit) => {\n            let f32_vec = vector_convert(v, VectorType::Float32Dense)?;\n            vector_convert(f32_vec, VectorType::Float1Bit)\n        }\n        (VectorType::Float1Bit, VectorType::Float32Sparse)\n        | (VectorType::Float8, VectorType::Float32Sparse)\n        | (VectorType::Float32Sparse, VectorType::Float1Bit)\n        | (VectorType::Float32Sparse, VectorType::Float8) => {\n            let f32_vec = vector_convert(v, VectorType::Float32Dense)?;\n            vector_convert(f32_vec, target_type)\n        }\n        _ => unreachable!(\n            \"unexpected conversion: {:?} -> {:?}\",\n            v.vector_type, target_type\n        ),\n    }\n}\n\nfn convert_floats_to_f8(\n    values: impl Iterator<Item = f32> + Clone,\n    dims: usize,\n) -> Result<Vector<'static>> {\n    if dims == 0 {\n        return Ok(Vector::from_f8(0, Vec::new(), 0.0, 0.0));\n    }\n    let mut min_val = f32::INFINITY;\n    let mut max_val = f32::NEG_INFINITY;\n    for val in values.clone() {\n        if val < min_val {\n            min_val = val;\n        }\n        if val > max_val {\n            max_val = val;\n        }\n    }\n    let alpha = (max_val - min_val) / 255.0;\n    let shift = min_val;\n    let mut quantized = Vec::with_capacity(dims);\n    for val in values {\n        let q = if alpha == 0.0 {\n            0u8\n        } else {\n            let v = (val - shift) / alpha + 0.5;\n            (v as i32).clamp(0, 255) as u8\n        };\n        quantized.push(q);\n    }\n    Ok(Vector::from_f8(dims, quantized, alpha, shift))\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::vector::{\n        operations::convert::vector_convert,\n        vector_types::{tests::ArbitraryVector, Vector, VectorType},\n    };\n    use quickcheck_macros::quickcheck;\n\n    fn concat<const N: usize>(data: &[[u8; N]]) -> Vec<u8> {\n        data.iter().flatten().cloned().collect::<Vec<u8>>()\n    }\n\n    fn assert_vectors(v1: &Vector, v2: &Vector) {\n        assert_eq!(v1.vector_type, v2.vector_type);\n        assert_eq!(v1.dims, v2.dims);\n        assert_eq!(v1.bin_data(), v2.bin_data());\n    }\n\n    fn clone_vector(v: &Vector) -> Vector<'static> {\n        Vector {\n            vector_type: v.vector_type,\n            dims: v.dims,\n            owned: Some(v.bin_data().to_vec()),\n            refer: None,\n        }\n    }\n\n    #[test]\n    pub fn test_vector_convert() {\n        let vf32 = Vector {\n            vector_type: VectorType::Float32Dense,\n            dims: 3,\n            owned: Some(concat(&[\n                1.0f32.to_le_bytes(),\n                0.0f32.to_le_bytes(),\n                2.0f32.to_le_bytes(),\n            ])),\n            refer: None,\n        };\n        let vf64 = Vector {\n            vector_type: VectorType::Float64Dense,\n            dims: 3,\n            owned: Some(concat(&[\n                1.0f64.to_le_bytes(),\n                0.0f64.to_le_bytes(),\n                2.0f64.to_le_bytes(),\n            ])),\n            refer: None,\n        };\n        let vf32_sparse = Vector {\n            vector_type: VectorType::Float32Sparse,\n            dims: 3,\n            owned: Some(concat(&[\n                1.0f32.to_le_bytes(),\n                2.0f32.to_le_bytes(),\n                0u32.to_le_bytes(),\n                2u32.to_le_bytes(),\n            ])),\n            refer: None,\n        };\n\n        let vectors = [vf32, vf64, vf32_sparse];\n        for v1 in &vectors {\n            for v2 in &vectors {\n                println!(\"{:?} -> {:?}\", v1.vector_type, v2.vector_type);\n                let v_copy = Vector {\n                    vector_type: v1.vector_type,\n                    dims: v1.dims,\n                    owned: v1.owned.clone(),\n                    refer: None,\n                };\n                assert_vectors(&vector_convert(v_copy, v2.vector_type).unwrap(), v2);\n            }\n        }\n    }\n\n    /// Test that all 5x5 type conversions succeed and produce correct type/dims.\n    #[test]\n    pub fn test_vector_convert_all_types() {\n        let source = Vector::from_f32(vec![1.0, 0.5, 2.0]);\n\n        let all_types = [\n            VectorType::Float32Dense,\n            VectorType::Float64Dense,\n            VectorType::Float32Sparse,\n            VectorType::Float1Bit,\n            VectorType::Float8,\n        ];\n\n        for &src_type in &all_types {\n            let src = vector_convert(clone_vector(&source), src_type).unwrap();\n            for &dst_type in &all_types {\n                let result = vector_convert(clone_vector(&src), dst_type);\n                assert!(\n                    result.is_ok(),\n                    \"conversion {:?} -> {:?} failed: {:?}\",\n                    src_type,\n                    dst_type,\n                    result.err()\n                );\n                let converted = result.unwrap();\n                assert_eq!(converted.vector_type, dst_type);\n                assert_eq!(converted.dims, 3);\n            }\n        }\n    }\n\n    /// Lossless conversions roundtrip exactly.\n    #[test]\n    pub fn test_vector_convert_lossless_roundtrip() {\n        let vf32 = Vector::from_f32(vec![1.0, 0.0, 2.0]);\n\n        // f32 -> f64 -> f32 is exact\n        let via_f64 = vector_convert(\n            vector_convert(clone_vector(&vf32), VectorType::Float64Dense).unwrap(),\n            VectorType::Float32Dense,\n        )\n        .unwrap();\n        assert_eq!(vf32.bin_data(), via_f64.bin_data());\n\n        // f32 -> sparse -> f32 is exact\n        let via_sparse = vector_convert(\n            vector_convert(clone_vector(&vf32), VectorType::Float32Sparse).unwrap(),\n            VectorType::Float32Dense,\n        )\n        .unwrap();\n        assert_eq!(vf32.bin_data(), via_sparse.bin_data());\n    }\n\n    /// Float1Bit roundtrip preserves sign information: positive → 1, non-positive → -1.\n    #[quickcheck]\n    fn prop_vector_convert_1bit_roundtrip(v: ArbitraryVector<100>) -> bool {\n        let v_f32 = vector_convert(v.into(), VectorType::Float32Dense).unwrap();\n        let orig_slice = v_f32.as_f32_slice().to_vec();\n        let v_1bit = vector_convert(v_f32, VectorType::Float1Bit).unwrap();\n        let v_back = vector_convert(v_1bit, VectorType::Float32Dense).unwrap();\n        let back_slice = v_back.as_f32_slice();\n\n        for i in 0..100 {\n            let expected = if orig_slice[i] > 0.0 { 1.0f32 } else { -1.0f32 };\n            if back_slice[i] != expected {\n                return false;\n            }\n        }\n        true\n    }\n\n    /// Float8 roundtrip approximately preserves values within one quantization step.\n    #[quickcheck]\n    fn prop_vector_convert_f8_roundtrip(v: ArbitraryVector<100>) -> bool {\n        let v_f32 = vector_convert(v.into(), VectorType::Float32Dense).unwrap();\n        let orig_slice = v_f32.as_f32_slice().to_vec();\n        let v_f8 = vector_convert(v_f32, VectorType::Float8).unwrap();\n        let v_back = vector_convert(v_f8, VectorType::Float32Dense).unwrap();\n        let back_slice = v_back.as_f32_slice();\n\n        let min_val = orig_slice.iter().cloned().fold(f32::INFINITY, f32::min);\n        let max_val = orig_slice.iter().cloned().fold(f32::NEG_INFINITY, f32::max);\n        let alpha = (max_val - min_val) / 255.0;\n        let tolerance = alpha + 1e-6;\n\n        for i in 0..100 {\n            if (orig_slice[i] - back_slice[i]).abs() > tolerance {\n                return false;\n            }\n        }\n        true\n    }\n\n    /// All type-pair conversions succeed for arbitrary vectors.\n    #[quickcheck]\n    fn prop_vector_convert_all_pairs(v: ArbitraryVector<16>) -> bool {\n        let v: Vector = v.into();\n        let all_types = [\n            VectorType::Float32Dense,\n            VectorType::Float64Dense,\n            VectorType::Float32Sparse,\n            VectorType::Float1Bit,\n            VectorType::Float8,\n        ];\n\n        for &target_type in &all_types {\n            if vector_convert(clone_vector(&v), target_type).is_err() {\n                return false;\n            }\n        }\n        true\n    }\n\n    #[test]\n    fn test_vector_convert_empty_to_f8() {\n        let empty_f32 = Vector::from_f32(vec![]);\n        let f8 = vector_convert(empty_f32, VectorType::Float8).unwrap();\n        assert_eq!(f8.dims, 0);\n        assert_eq!(f8.vector_type, VectorType::Float8);\n        let (quantized, alpha, shift) = f8.as_f8_data();\n        assert!(quantized.is_empty());\n        assert_eq!(alpha, 0.0);\n        assert_eq!(shift, 0.0);\n    }\n\n    #[test]\n    fn test_vector_convert_empty_f8_to_f32() {\n        let empty_f8 = Vector::from_f8(0, Vec::new(), 0.0, 0.0);\n        let f32_vec = vector_convert(empty_f8, VectorType::Float32Dense).unwrap();\n        assert_eq!(f32_vec.dims, 0);\n        assert!(f32_vec.as_f32_slice().is_empty());\n    }\n}\n"
  },
  {
    "path": "core/vector/operations/distance_cos.rs",
    "content": "use crate::{\n    vector::vector_types::{Vector, VectorSparse, VectorType},\n    LimboError, Result,\n};\n#[cfg(not(any(\n    target_family = \"wasm\",\n    all(target_os = \"windows\", target_arch = \"aarch64\")\n)))]\nuse simsimd::SpatialSimilarity;\n\npub fn vector_distance_cos(v1: &Vector, v2: &Vector) -> Result<f64> {\n    if v1.dims != v2.dims {\n        return Err(LimboError::ConversionError(\n            \"Vectors must have the same dimensions\".to_string(),\n        ));\n    }\n    if v1.vector_type != v2.vector_type {\n        return Err(LimboError::ConversionError(\n            \"Vectors must be of the same type\".to_string(),\n        ));\n    }\n    match v1.vector_type {\n        VectorType::Float32Dense => Ok(vector_f32_distance_cos_simsimd(\n            v1.as_f32_slice(),\n            v2.as_f32_slice(),\n        )),\n        VectorType::Float64Dense => Ok(vector_f64_distance_cos_simsimd(\n            v1.as_f64_slice(),\n            v2.as_f64_slice(),\n        )),\n        VectorType::Float32Sparse => Ok(vector_f32_sparse_distance_cos(\n            v1.as_f32_sparse(),\n            v2.as_f32_sparse(),\n        )),\n        VectorType::Float1Bit => Ok(vector_1bit_distance_cos(v1, v2)),\n        VectorType::Float8 => Ok(vector_f8_distance_cos(v1, v2)),\n    }\n}\n\nfn vector_1bit_distance_cos(v1: &Vector, v2: &Vector) -> f64 {\n    // For 1-bit vectors, cosine distance returns hamming distance (matching libsql)\n    let d1 = v1.as_1bit_data();\n    let d2 = v2.as_1bit_data();\n    let mut hamming = 0u32;\n    for (&a, &b) in d1.iter().zip(d2.iter()) {\n        hamming += (a ^ b).count_ones();\n    }\n    hamming as f64\n}\n\nfn vector_f8_distance_cos(v1: &Vector, v2: &Vector) -> f64 {\n    let (data1, alpha1, shift1) = v1.as_f8_data();\n    let (data2, alpha2, shift2) = v2.as_f8_data();\n    let dims = v1.dims;\n\n    let (mut sum1, mut sum2, mut sumsq1, mut sumsq2, mut doti) = (0u64, 0u64, 0u64, 0u64, 0u64);\n    for i in 0..dims {\n        let q1 = data1[i] as u64;\n        let q2 = data2[i] as u64;\n        sum1 += q1;\n        sum2 += q2;\n        sumsq1 += q1 * q1;\n        sumsq2 += q2 * q2;\n        doti += q1 * q2;\n    }\n\n    let a1 = alpha1 as f64;\n    let a2 = alpha2 as f64;\n    let s1 = shift1 as f64;\n    let s2 = shift2 as f64;\n    let d = dims as f64;\n\n    let dot = a1 * a2 * doti as f64 + a1 * s2 * sum1 as f64 + a2 * s1 * sum2 as f64 + s1 * s2 * d;\n    let norm1 = a1 * a1 * sumsq1 as f64 + 2.0 * a1 * s1 * sum1 as f64 + s1 * s1 * d;\n    let norm2 = a2 * a2 * sumsq2 as f64 + 2.0 * a2 * s2 * sum2 as f64 + s2 * s2 * d;\n\n    1.0 - dot / (norm1 * norm2).sqrt()\n}\n\n#[allow(dead_code)]\n#[cfg(not(any(\n    target_family = \"wasm\",\n    all(target_os = \"windows\", target_arch = \"aarch64\")\n)))]\nfn vector_f32_distance_cos_simsimd(v1: &[f32], v2: &[f32]) -> f64 {\n    f32::cosine(v1, v2).unwrap_or(f64::NAN)\n}\n\n// SimSIMD does not support WASM, and Windows AArch64 has linker issues with simsimd.lib.\n#[allow(dead_code)]\nfn vector_f32_distance_cos_rust(v1: &[f32], v2: &[f32]) -> f64 {\n    let (mut dot, mut norm1, mut norm2) = (0.0, 0.0, 0.0);\n    for (a, b) in v1.iter().zip(v2.iter()) {\n        dot += a * b;\n        norm1 += a * a;\n        norm2 += b * b;\n    }\n    if norm1 == 0.0 || norm2 == 0.0 {\n        return 0.0;\n    }\n    (1.0 - dot / (norm1 * norm2).sqrt()) as f64\n}\n\n#[allow(dead_code)]\n#[cfg(any(\n    target_family = \"wasm\",\n    all(target_os = \"windows\", target_arch = \"aarch64\")\n))]\nfn vector_f32_distance_cos_simsimd(v1: &[f32], v2: &[f32]) -> f64 {\n    vector_f32_distance_cos_rust(v1, v2)\n}\n\n#[allow(dead_code)]\n#[cfg(not(any(\n    target_family = \"wasm\",\n    all(target_os = \"windows\", target_arch = \"aarch64\")\n)))]\nfn vector_f64_distance_cos_simsimd(v1: &[f64], v2: &[f64]) -> f64 {\n    f64::cosine(v1, v2).unwrap_or(f64::NAN)\n}\n\n// SimSIMD does not support WASM, and Windows AArch64 has linker issues with simsimd.lib.\n#[allow(dead_code)]\nfn vector_f64_distance_cos_rust(v1: &[f64], v2: &[f64]) -> f64 {\n    let (mut dot, mut norm1, mut norm2) = (0.0, 0.0, 0.0);\n    for (a, b) in v1.iter().zip(v2.iter()) {\n        dot += a * b;\n        norm1 += a * a;\n        norm2 += b * b;\n    }\n    if norm1 == 0.0 || norm2 == 0.0 {\n        return 0.0;\n    }\n    1.0 - dot / (norm1 * norm2).sqrt()\n}\n\n#[allow(dead_code)]\n#[cfg(any(\n    target_family = \"wasm\",\n    all(target_os = \"windows\", target_arch = \"aarch64\")\n))]\nfn vector_f64_distance_cos_simsimd(v1: &[f64], v2: &[f64]) -> f64 {\n    vector_f64_distance_cos_rust(v1, v2)\n}\n\nfn vector_f32_sparse_distance_cos(v1: VectorSparse<f32>, v2: VectorSparse<f32>) -> f64 {\n    let mut v1_pos = 0;\n    let mut v2_pos = 0;\n    let (mut dot, mut norm1, mut norm2) = (0.0, 0.0, 0.0);\n    while v1_pos < v1.idx.len() && v2_pos < v2.idx.len() {\n        let e1 = v1.values[v1_pos];\n        let e2 = v2.values[v2_pos];\n        if v1.idx[v1_pos] == v2.idx[v2_pos] {\n            dot += e1 * e2;\n            norm1 += e1 * e1;\n            norm2 += e2 * e2;\n            v1_pos += 1;\n            v2_pos += 1;\n        } else if v1.idx[v1_pos] < v2.idx[v2_pos] {\n            norm1 += e1 * e1;\n            v1_pos += 1;\n        } else {\n            norm2 += e2 * e2;\n            v2_pos += 1;\n        }\n    }\n\n    while v1_pos < v1.idx.len() {\n        norm1 += v1.values[v1_pos] * v1.values[v1_pos];\n        v1_pos += 1;\n    }\n    while v2_pos < v2.idx.len() {\n        norm2 += v2.values[v2_pos] * v2.values[v2_pos];\n        v2_pos += 1;\n    }\n\n    // Check for zero norms\n    if norm1 == 0.0f32 || norm2 == 0.0f32 {\n        return f64::NAN;\n    }\n\n    (1.0f32 - (dot / (norm1 * norm2).sqrt())) as f64\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::vector::{\n        operations::convert::vector_convert, vector_types::tests::ArbitraryVector,\n    };\n\n    use super::*;\n    use quickcheck_macros::quickcheck;\n\n    #[test]\n    fn test_vector_distance_cos_f32() {\n        assert_eq!(vector_f32_distance_cos_simsimd(&[], &[]), 0.0);\n        assert_eq!(\n            vector_f32_distance_cos_simsimd(&[1.0, 2.0], &[0.0, 0.0]),\n            1.0\n        );\n        assert!(vector_f32_distance_cos_simsimd(&[1.0, 2.0], &[1.0, 2.0]).abs() < 1e-6);\n        assert!((vector_f32_distance_cos_simsimd(&[1.0, 2.0], &[-1.0, -2.0]) - 2.0).abs() < 1e-6);\n        assert!((vector_f32_distance_cos_simsimd(&[1.0, 2.0], &[-2.0, 1.0]) - 1.0).abs() < 1e-6);\n    }\n\n    #[test]\n    fn test_vector_distance_cos_f64() {\n        assert_eq!(vector_f64_distance_cos_simsimd(&[], &[]), 0.0);\n        assert_eq!(\n            vector_f64_distance_cos_simsimd(&[1.0, 2.0], &[0.0, 0.0]),\n            1.0\n        );\n        assert!(vector_f64_distance_cos_simsimd(&[1.0, 2.0], &[1.0, 2.0]).abs() < 1e-6);\n        assert!((vector_f64_distance_cos_simsimd(&[1.0, 2.0], &[-1.0, -2.0]) - 2.0).abs() < 1e-6);\n        assert!((vector_f64_distance_cos_simsimd(&[1.0, 2.0], &[-2.0, 1.0]) - 1.0).abs() < 1e-6);\n    }\n\n    #[test]\n    fn test_vector_distance_cos_f32_sparse() {\n        assert!(\n            (vector_f32_sparse_distance_cos(\n                VectorSparse {\n                    idx: &[0, 1],\n                    values: &[1.0, 2.0]\n                },\n                VectorSparse {\n                    idx: &[1, 2],\n                    values: &[1.0, 3.0]\n                },\n            ) - vector_f32_distance_cos_simsimd(&[1.0, 2.0, 0.0], &[0.0, 1.0, 3.0]))\n            .abs()\n                < 1e-7\n        );\n    }\n\n    #[quickcheck]\n    fn prop_vector_distance_cos_dense_vs_sparse(\n        v1: ArbitraryVector<100>,\n        v2: ArbitraryVector<100>,\n    ) -> bool {\n        let v1 = vector_convert(v1.into(), VectorType::Float32Dense).unwrap();\n        let v2 = vector_convert(v2.into(), VectorType::Float32Dense).unwrap();\n        let d1 = vector_distance_cos(&v1, &v2).unwrap();\n\n        let sparse1 = vector_convert(v1, VectorType::Float32Sparse).unwrap();\n        let sparse2 = vector_convert(v2, VectorType::Float32Sparse).unwrap();\n        let d2 = vector_f32_sparse_distance_cos(sparse1.as_f32_sparse(), sparse2.as_f32_sparse());\n\n        (d1.is_nan() && d2.is_nan()) || (d1 - d2).abs() < 1e-6\n    }\n\n    #[quickcheck]\n    fn prop_vector_distance_cos_rust_vs_simsimd_f32(\n        v1: ArbitraryVector<100>,\n        v2: ArbitraryVector<100>,\n    ) -> bool {\n        let v1 = vector_convert(v1.into(), VectorType::Float32Dense).unwrap();\n        let v2 = vector_convert(v2.into(), VectorType::Float32Dense).unwrap();\n        let d1 = vector_f32_distance_cos_rust(v1.as_f32_slice(), v2.as_f32_slice());\n        let d2 = vector_f32_distance_cos_simsimd(v1.as_f32_slice(), v2.as_f32_slice());\n        println!(\"d1 vs d2: {d1} vs {d2}\");\n        (d1.is_nan() && d2.is_nan()) || (d1 - d2).abs() < 1e-4\n    }\n\n    #[quickcheck]\n    fn prop_vector_distance_cos_rust_vs_simsimd_f64(\n        v1: ArbitraryVector<100>,\n        v2: ArbitraryVector<100>,\n    ) -> bool {\n        let v1 = vector_convert(v1.into(), VectorType::Float64Dense).unwrap();\n        let v2 = vector_convert(v2.into(), VectorType::Float64Dense).unwrap();\n        let d1 = vector_f64_distance_cos_rust(v1.as_f64_slice(), v2.as_f64_slice());\n        let d2 = vector_f64_distance_cos_simsimd(v1.as_f64_slice(), v2.as_f64_slice());\n        println!(\"d1 vs d2: {d1} vs {d2}\");\n        (d1.is_nan() && d2.is_nan()) || (d1 - d2).abs() < 1e-6\n    }\n\n    /// Float8 optimized cosine distance matches dequantized Float32 cosine distance.\n    #[quickcheck]\n    fn prop_vector_distance_cos_f8_vs_dequantized(\n        v1: ArbitraryVector<100>,\n        v2: ArbitraryVector<100>,\n    ) -> bool {\n        let v1 = vector_convert(v1.into(), VectorType::Float32Dense).unwrap();\n        let v2 = vector_convert(v2.into(), VectorType::Float32Dense).unwrap();\n        let v1_f8 = vector_convert(v1, VectorType::Float8).unwrap();\n        let v2_f8 = vector_convert(v2, VectorType::Float8).unwrap();\n        let d_f8 = vector_distance_cos(&v1_f8, &v2_f8).unwrap();\n        let v1_deq = vector_convert(v1_f8, VectorType::Float32Dense).unwrap();\n        let v2_deq = vector_convert(v2_f8, VectorType::Float32Dense).unwrap();\n        let d_deq = vector_distance_cos(&v1_deq, &v2_deq).unwrap();\n        (d_f8.is_nan() && d_deq.is_nan()) || (d_f8 - d_deq).abs() < 1e-4\n    }\n\n    /// Float1Bit cosine distance (hamming) matches dot-product relationship:\n    /// hamming = (dims + dot_distance) / 2\n    #[quickcheck]\n    fn prop_vector_distance_cos_1bit_dot_relationship(\n        v1: ArbitraryVector<100>,\n        v2: ArbitraryVector<100>,\n    ) -> bool {\n        use crate::vector::operations::distance_dot::vector_distance_dot;\n        let v1 = vector_convert(v1.into(), VectorType::Float1Bit).unwrap();\n        let v2 = vector_convert(v2.into(), VectorType::Float1Bit).unwrap();\n        let cos = vector_distance_cos(&v1, &v2).unwrap();\n        let dot = vector_distance_dot(&v1, &v2).unwrap();\n        // hamming = cos, dot = -(dims - 2*hamming), so cos = (dims + dot) / 2\n        (cos - (100.0 + dot) / 2.0).abs() < 1e-10\n    }\n}\n"
  },
  {
    "path": "core/vector/operations/distance_dot.rs",
    "content": "use crate::{\n    vector::vector_types::{Vector, VectorSparse, VectorType},\n    LimboError, Result,\n};\n#[cfg(not(any(\n    target_family = \"wasm\",\n    all(target_os = \"windows\", target_arch = \"aarch64\")\n)))]\nuse simsimd::SpatialSimilarity;\n\npub fn vector_distance_dot(v1: &Vector, v2: &Vector) -> Result<f64> {\n    if v1.dims != v2.dims {\n        return Err(LimboError::ConversionError(\n            \"Vectors must have the same dimensions\".to_string(),\n        ));\n    }\n    if v1.vector_type != v2.vector_type {\n        return Err(LimboError::ConversionError(\n            \"Vectors must be of the same type\".to_string(),\n        ));\n    }\n    match v1.vector_type {\n        VectorType::Float32Dense => Ok(vector_f32_distance_dot_simsimd(\n            v1.as_f32_slice(),\n            v2.as_f32_slice(),\n        )),\n        VectorType::Float64Dense => Ok(vector_f64_distance_dot_simsimd(\n            v1.as_f64_slice(),\n            v2.as_f64_slice(),\n        )),\n        VectorType::Float32Sparse => Ok(vector_f32_sparse_distance_dot(\n            v1.as_f32_sparse(),\n            v2.as_f32_sparse(),\n        )),\n        VectorType::Float1Bit => Ok(vector_1bit_distance_dot(v1, v2)),\n        VectorType::Float8 => Ok(vector_f8_distance_dot(v1, v2)),\n    }\n}\n\nfn vector_1bit_distance_dot(v1: &Vector, v2: &Vector) -> f64 {\n    // 1-bit values represent +1/-1.\n    // Dot product = dims - 2 * hamming_distance\n    // Return negated (consistent with existing dot distance convention).\n    let d1 = v1.as_1bit_data();\n    let d2 = v2.as_1bit_data();\n    let mut hamming = 0u32;\n    for (&a, &b) in d1.iter().zip(d2.iter()) {\n        hamming += (a ^ b).count_ones();\n    }\n    let dot = v1.dims as f64 - 2.0 * hamming as f64;\n    -dot\n}\n\nfn vector_f8_distance_dot(v1: &Vector, v2: &Vector) -> f64 {\n    let (data1, alpha1, shift1) = v1.as_f8_data();\n    let (data2, alpha2, shift2) = v2.as_f8_data();\n    let dims = v1.dims;\n\n    let (mut sum1, mut sum2, mut doti) = (0u64, 0u64, 0u64);\n    for i in 0..dims {\n        let q1 = data1[i] as u64;\n        let q2 = data2[i] as u64;\n        sum1 += q1;\n        sum2 += q2;\n        doti += q1 * q2;\n    }\n\n    let a1 = alpha1 as f64;\n    let a2 = alpha2 as f64;\n    let s1 = shift1 as f64;\n    let s2 = shift2 as f64;\n    let d = dims as f64;\n\n    let dot = a1 * a2 * doti as f64 + a1 * s2 * sum1 as f64 + a2 * s1 * sum2 as f64 + s1 * s2 * d;\n    -dot\n}\n\n#[allow(dead_code)]\n#[cfg(not(any(\n    target_family = \"wasm\",\n    all(target_os = \"windows\", target_arch = \"aarch64\")\n)))]\nfn vector_f32_distance_dot_simsimd(v1: &[f32], v2: &[f32]) -> f64 {\n    -f32::dot(v1, v2).unwrap_or(f64::NAN)\n}\n\n// SimSIMD does not support WASM, and Windows AArch64 has linker issues with simsimd.lib.\n#[allow(dead_code)]\nfn vector_f32_distance_dot_rust(v1: &[f32], v2: &[f32]) -> f64 {\n    let mut dot = 0.0;\n    for (a, b) in v1.iter().zip(v2.iter()) {\n        dot += (*a as f64) * (*b as f64);\n    }\n    -dot\n}\n\n#[allow(dead_code)]\n#[cfg(any(\n    target_family = \"wasm\",\n    all(target_os = \"windows\", target_arch = \"aarch64\")\n))]\nfn vector_f32_distance_dot_simsimd(v1: &[f32], v2: &[f32]) -> f64 {\n    vector_f32_distance_dot_rust(v1, v2)\n}\n\n#[allow(dead_code)]\n#[cfg(not(any(\n    target_family = \"wasm\",\n    all(target_os = \"windows\", target_arch = \"aarch64\")\n)))]\nfn vector_f64_distance_dot_simsimd(v1: &[f64], v2: &[f64]) -> f64 {\n    -f64::dot(v1, v2).unwrap_or(f64::NAN)\n}\n\n// SimSIMD does not support WASM, and Windows AArch64 has linker issues with simsimd.lib.\n#[allow(dead_code)]\nfn vector_f64_distance_dot_rust(v1: &[f64], v2: &[f64]) -> f64 {\n    let mut dot = 0.0;\n    for (a, b) in v1.iter().zip(v2.iter()) {\n        dot += *a * *b;\n    }\n    -dot\n}\n\n#[allow(dead_code)]\n#[cfg(any(\n    target_family = \"wasm\",\n    all(target_os = \"windows\", target_arch = \"aarch64\")\n))]\nfn vector_f64_distance_dot_simsimd(v1: &[f64], v2: &[f64]) -> f64 {\n    vector_f64_distance_dot_rust(v1, v2)\n}\n\nfn vector_f32_sparse_distance_dot(v1: VectorSparse<f32>, v2: VectorSparse<f32>) -> f64 {\n    let mut v1_pos = 0;\n    let mut v2_pos = 0;\n    let mut dot = 0.0;\n    while v1_pos < v1.idx.len() && v2_pos < v2.idx.len() {\n        let idx1 = v1.idx[v1_pos];\n        let idx2 = v2.idx[v2_pos];\n        if idx1 == idx2 {\n            let e1 = v1.values[v1_pos];\n            let e2 = v2.values[v2_pos];\n            dot += (e1 as f64) * (e2 as f64);\n            v1_pos += 1;\n            v2_pos += 1;\n        } else if idx1 < idx2 {\n            v1_pos += 1;\n        } else {\n            v2_pos += 1;\n        }\n    }\n    -dot\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::vector::{\n        operations::convert::vector_convert, vector_types::tests::ArbitraryVector,\n    };\n\n    use super::*;\n    use quickcheck_macros::quickcheck;\n\n    #[test]\n    fn test_vector_distance_dot_f32() {\n        assert_eq!(vector_f32_distance_dot_simsimd(&[], &[]), 0.0);\n        assert_eq!(\n            vector_f32_distance_dot_simsimd(&[1.0, 0.0], &[0.0, 1.0]),\n            0.0\n        );\n        assert!((vector_f32_distance_dot_simsimd(&[1.0, 2.0], &[1.0, 2.0]) - (-5.0)).abs() < 1e-6);\n        assert!((vector_f32_distance_dot_simsimd(&[1.0, 2.0], &[2.0, 4.0]) - (-10.0)).abs() < 1e-6);\n        assert!((vector_f32_distance_dot_simsimd(&[1.0, 2.0], &[-1.0, -2.0]) - 5.0).abs() < 1e-6);\n    }\n\n    #[test]\n    fn test_vector_distance_dot_f64() {\n        assert_eq!(vector_f64_distance_dot_simsimd(&[], &[]), 0.0);\n        assert_eq!(\n            vector_f64_distance_dot_simsimd(&[1.0, 0.0], &[0.0, 1.0]),\n            0.0\n        );\n        assert!((vector_f64_distance_dot_simsimd(&[1.0, 2.0], &[1.0, 2.0]) - (-5.0)).abs() < 1e-6);\n        assert!((vector_f64_distance_dot_simsimd(&[1.0, 2.0], &[-1.0, -2.0]) - 5.0).abs() < 1e-6);\n    }\n\n    #[test]\n    fn test_vector_distance_dot_f32_sparse() {\n        let v1_sparse = VectorSparse {\n            idx: &[1, 2],\n            values: &[1.0, 2.0],\n        };\n        let v2_sparse = VectorSparse {\n            idx: &[1, 3],\n            values: &[2.0, 3.0],\n        };\n        let v1_dense = &[0.0, 1.0, 2.0, 0.0];\n        let v2_dense = &[0.0, 2.0, 0.0, 3.0];\n        let sparse_dist = vector_f32_sparse_distance_dot(v1_sparse, v2_sparse);\n        let dense_dist = vector_f32_distance_dot_simsimd(v1_dense, v2_dense);\n        assert!((sparse_dist - dense_dist).abs() < 1e-7);\n        assert!((sparse_dist - (-2.0)).abs() < 1e-7);\n    }\n\n    #[quickcheck]\n    fn prop_vector_distance_dot_dense_vs_sparse(\n        v1: ArbitraryVector<100>,\n        v2: ArbitraryVector<100>,\n    ) -> bool {\n        let v1_dense = vector_convert(v1.into(), VectorType::Float32Dense).unwrap();\n        let v2_dense = vector_convert(v2.into(), VectorType::Float32Dense).unwrap();\n        let d_dense =\n            vector_f32_distance_dot_rust(v1_dense.as_f32_slice(), v2_dense.as_f32_slice());\n        let v1_sparse = vector_convert(v1_dense, VectorType::Float32Sparse).unwrap();\n        let v2_sparse = vector_convert(v2_dense, VectorType::Float32Sparse).unwrap();\n        let d_sparse =\n            vector_f32_sparse_distance_dot(v1_sparse.as_f32_sparse(), v2_sparse.as_f32_sparse());\n        (d_dense.is_nan() && d_sparse.is_nan()) || (d_dense - d_sparse).abs() < 1e-5\n    }\n\n    #[quickcheck]\n    fn prop_vector_distance_dot_rust_vs_simsimd_f32(\n        v1: ArbitraryVector<100>,\n        v2: ArbitraryVector<100>,\n    ) -> bool {\n        let v1 = vector_convert(v1.into(), VectorType::Float32Dense).unwrap();\n        let v2 = vector_convert(v2.into(), VectorType::Float32Dense).unwrap();\n        let d_rust = vector_f32_distance_dot_rust(v1.as_f32_slice(), v2.as_f32_slice());\n        let d_simd = vector_f32_distance_dot_simsimd(v1.as_f32_slice(), v2.as_f32_slice());\n        (d_rust.is_nan() && d_simd.is_nan()) || (d_rust - d_simd).abs() < 1e-4\n    }\n\n    #[quickcheck]\n    fn prop_vector_distance_dot_rust_vs_simsimd_f64(\n        v1: ArbitraryVector<100>,\n        v2: ArbitraryVector<100>,\n    ) -> bool {\n        let v1 = vector_convert(v1.into(), VectorType::Float64Dense).unwrap();\n        let v2 = vector_convert(v2.into(), VectorType::Float64Dense).unwrap();\n        let d_rust = vector_f64_distance_dot_rust(v1.as_f64_slice(), v2.as_f64_slice());\n        let d_simd = vector_f64_distance_dot_simsimd(v1.as_f64_slice(), v2.as_f64_slice());\n        (d_rust.is_nan() && d_simd.is_nan()) || (d_rust - d_simd).abs() < 1e-6\n    }\n\n    /// Float8 optimized dot distance matches dequantized Float32 dot distance.\n    #[quickcheck]\n    fn prop_vector_distance_dot_f8_vs_dequantized(\n        v1: ArbitraryVector<100>,\n        v2: ArbitraryVector<100>,\n    ) -> bool {\n        let v1 = vector_convert(v1.into(), VectorType::Float32Dense).unwrap();\n        let v2 = vector_convert(v2.into(), VectorType::Float32Dense).unwrap();\n        let v1_f8 = vector_convert(v1, VectorType::Float8).unwrap();\n        let v2_f8 = vector_convert(v2, VectorType::Float8).unwrap();\n        let d_f8 = vector_distance_dot(&v1_f8, &v2_f8).unwrap();\n        let v1_deq = vector_convert(v1_f8, VectorType::Float32Dense).unwrap();\n        let v2_deq = vector_convert(v2_f8, VectorType::Float32Dense).unwrap();\n        let d_deq = vector_distance_dot(&v1_deq, &v2_deq).unwrap();\n        (d_f8.is_nan() && d_deq.is_nan()) || (d_f8 - d_deq).abs() < 1e-3\n    }\n\n    /// Float1Bit dot distance matches dequantized ±1 Float32 dot distance.\n    #[quickcheck]\n    fn prop_vector_distance_dot_1bit_vs_dequantized(\n        v1: ArbitraryVector<100>,\n        v2: ArbitraryVector<100>,\n    ) -> bool {\n        let v1 = vector_convert(v1.into(), VectorType::Float1Bit).unwrap();\n        let v2 = vector_convert(v2.into(), VectorType::Float1Bit).unwrap();\n        let d_1bit = vector_distance_dot(&v1, &v2).unwrap();\n        let v1_f32 = vector_convert(v1, VectorType::Float32Dense).unwrap();\n        let v2_f32 = vector_convert(v2, VectorType::Float32Dense).unwrap();\n        let d_f32 = vector_distance_dot(&v1_f32, &v2_f32).unwrap();\n        (d_1bit - d_f32).abs() < 1e-6\n    }\n}\n"
  },
  {
    "path": "core/vector/operations/distance_l2.rs",
    "content": "use crate::{\n    vector::vector_types::{Vector, VectorSparse, VectorType},\n    LimboError, Result,\n};\n#[cfg(not(any(\n    target_family = \"wasm\",\n    all(target_os = \"windows\", target_arch = \"aarch64\")\n)))]\nuse simsimd::SpatialSimilarity;\n\npub fn vector_distance_l2(v1: &Vector, v2: &Vector) -> Result<f64> {\n    if v1.dims != v2.dims {\n        return Err(LimboError::ConversionError(\n            \"Vectors must have the same dimensions\".to_string(),\n        ));\n    }\n    if v1.vector_type != v2.vector_type {\n        return Err(LimboError::ConversionError(\n            \"Vectors must be of the same type\".to_string(),\n        ));\n    }\n    match v1.vector_type {\n        VectorType::Float32Dense => Ok(vector_f32_distance_l2_simsimd(\n            v1.as_f32_slice(),\n            v2.as_f32_slice(),\n        )),\n        VectorType::Float64Dense => Ok(vector_f64_distance_l2_simsimd(\n            v1.as_f64_slice(),\n            v2.as_f64_slice(),\n        )),\n        VectorType::Float32Sparse => Ok(vector_f32_sparse_distance_l2(\n            v1.as_f32_sparse(),\n            v2.as_f32_sparse(),\n        )),\n        VectorType::Float1Bit => Err(LimboError::ConversionError(\n            \"L2 distance is not supported for float1bit vectors\".to_string(),\n        )),\n        VectorType::Float8 => Ok(vector_f8_distance_l2(v1, v2)),\n    }\n}\n\nfn vector_f8_distance_l2(v1: &Vector, v2: &Vector) -> f64 {\n    let (data1, alpha1, shift1) = v1.as_f8_data();\n    let (data2, alpha2, shift2) = v2.as_f8_data();\n    let mut sum = 0.0f64;\n    for i in 0..v1.dims {\n        let f1 = alpha1 as f64 * data1[i] as f64 + shift1 as f64;\n        let f2 = alpha2 as f64 * data2[i] as f64 + shift2 as f64;\n        let d = f1 - f2;\n        sum += d * d;\n    }\n    sum.sqrt()\n}\n\n#[allow(dead_code)]\n#[cfg(not(any(\n    target_family = \"wasm\",\n    all(target_os = \"windows\", target_arch = \"aarch64\")\n)))]\nfn vector_f32_distance_l2_simsimd(v1: &[f32], v2: &[f32]) -> f64 {\n    f32::euclidean(v1, v2).unwrap_or(f64::NAN)\n}\n\n// SimSIMD does not support WASM, and Windows AArch64 has linker issues with simsimd.lib.\n#[allow(dead_code)]\nfn vector_f32_distance_l2_rust(v1: &[f32], v2: &[f32]) -> f64 {\n    let sum = v1\n        .iter()\n        .zip(v2.iter())\n        .map(|(a, b)| (a - b).powi(2))\n        .sum::<f32>() as f64;\n    sum.sqrt()\n}\n\n#[allow(dead_code)]\n#[cfg(any(\n    target_family = \"wasm\",\n    all(target_os = \"windows\", target_arch = \"aarch64\")\n))]\nfn vector_f32_distance_l2_simsimd(v1: &[f32], v2: &[f32]) -> f64 {\n    vector_f32_distance_l2_rust(v1, v2)\n}\n\n#[allow(dead_code)]\n#[cfg(not(any(\n    target_family = \"wasm\",\n    all(target_os = \"windows\", target_arch = \"aarch64\")\n)))]\nfn vector_f64_distance_l2_simsimd(v1: &[f64], v2: &[f64]) -> f64 {\n    f64::euclidean(v1, v2).unwrap_or(f64::NAN)\n}\n\n// SimSIMD does not support WASM, and Windows AArch64 has linker issues with simsimd.lib.\n#[allow(dead_code)]\nfn vector_f64_distance_l2_rust(v1: &[f64], v2: &[f64]) -> f64 {\n    let sum = v1\n        .iter()\n        .zip(v2.iter())\n        .map(|(a, b)| (a - b).powi(2))\n        .sum::<f64>();\n    sum.sqrt()\n}\n\n#[allow(dead_code)]\n#[cfg(any(\n    target_family = \"wasm\",\n    all(target_os = \"windows\", target_arch = \"aarch64\")\n))]\nfn vector_f64_distance_l2_simsimd(v1: &[f64], v2: &[f64]) -> f64 {\n    vector_f64_distance_l2_rust(v1, v2)\n}\n\nfn vector_f32_sparse_distance_l2(v1: VectorSparse<f32>, v2: VectorSparse<f32>) -> f64 {\n    let mut v1_pos = 0;\n    let mut v2_pos = 0;\n    let mut sum = 0.0;\n    while v1_pos < v1.idx.len() && v2_pos < v2.idx.len() {\n        if v1.idx[v1_pos] == v2.idx[v2_pos] {\n            sum += (v1.values[v1_pos] - v2.values[v2_pos]).powi(2);\n            v1_pos += 1;\n            v2_pos += 1;\n        } else if v1.idx[v1_pos] < v2.idx[v2_pos] {\n            sum += v1.values[v1_pos].powi(2);\n            v1_pos += 1;\n        } else {\n            sum += v2.values[v2_pos].powi(2);\n            v2_pos += 1;\n        }\n    }\n    while v1_pos < v1.idx.len() {\n        sum += v1.values[v1_pos].powi(2);\n        v1_pos += 1;\n    }\n    while v2_pos < v2.idx.len() {\n        sum += v2.values[v2_pos].powi(2);\n        v2_pos += 1;\n    }\n    (sum as f64).sqrt()\n}\n\n#[cfg(test)]\nmod tests {\n    use quickcheck_macros::quickcheck;\n\n    use crate::vector::{\n        operations::convert::vector_convert, vector_types::tests::ArbitraryVector,\n    };\n\n    use super::*;\n\n    #[test]\n    fn test_vector_distance_l2_f32_another() {\n        let vectors = [\n            (0..8).map(|x| x as f32).collect::<Vec<f32>>(),\n            (1..9).map(|x| x as f32).collect::<Vec<f32>>(),\n            (2..10).map(|x| x as f32).collect::<Vec<f32>>(),\n            (3..11).map(|x| x as f32).collect::<Vec<f32>>(),\n        ];\n        let query = (2..10).map(|x| x as f32).collect::<Vec<f32>>();\n\n        let expected: Vec<f64> = vec![\n            32.0_f64.sqrt(),\n            8.0_f64.sqrt(),\n            0.0_f64.sqrt(),\n            8.0_f64.sqrt(),\n        ];\n        let results = vectors\n            .iter()\n            .map(|v| vector_f32_distance_l2_rust(&query, v))\n            .collect::<Vec<f64>>();\n        assert_eq!(results, expected);\n    }\n\n    #[test]\n    fn test_vector_distance_l2_odd_len() {\n        let v = (0..5).map(|x| x as f32).collect::<Vec<f32>>();\n        let query = (2..7).map(|x| x as f32).collect::<Vec<f32>>();\n        assert_eq!(vector_f32_distance_l2_rust(&v, &query), 20.0_f64.sqrt());\n    }\n\n    #[test]\n    fn test_vector_distance_l2_f32() {\n        assert_eq!(vector_f32_distance_l2_rust(&[], &[]), 0.0);\n        assert_eq!(\n            vector_f32_distance_l2_rust(&[1.0, 2.0], &[0.0, 0.0]),\n            (1f64 + 2f64 * 2f64).sqrt()\n        );\n        assert_eq!(vector_f32_distance_l2_rust(&[1.0, 2.0], &[1.0, 2.0]), 0.0);\n        assert_eq!(\n            vector_f32_distance_l2_rust(&[1.0, 2.0], &[-1.0, -2.0]),\n            (2f64 * 2f64 + 4f64 * 4f64).sqrt()\n        );\n        assert_eq!(\n            vector_f32_distance_l2_rust(&[1.0, 2.0], &[-2.0, 1.0]),\n            (3f64 * 3f64 + 1f64 * 1f64).sqrt()\n        );\n    }\n\n    #[test]\n    fn test_vector_distance_l2_f64() {\n        assert_eq!(vector_f64_distance_l2_rust(&[], &[]), 0.0);\n        assert_eq!(\n            vector_f64_distance_l2_rust(&[1.0, 2.0], &[0.0, 0.0]),\n            (1f64 + 2f64 * 2f64).sqrt()\n        );\n        assert_eq!(vector_f64_distance_l2_rust(&[1.0, 2.0], &[1.0, 2.0]), 0.0);\n        assert_eq!(\n            vector_f64_distance_l2_rust(&[1.0, 2.0], &[-1.0, -2.0]),\n            (2f64 * 2f64 + 4f64 * 4f64).sqrt()\n        );\n        assert_eq!(\n            vector_f64_distance_l2_rust(&[1.0, 2.0], &[-2.0, 1.0]),\n            (3f64 * 3f64 + 1f64 * 1f64).sqrt()\n        );\n    }\n\n    #[test]\n    fn test_vector_distance_l2_f32_sparse() {\n        assert!(\n            (vector_f32_sparse_distance_l2(\n                VectorSparse {\n                    idx: &[0, 1],\n                    values: &[1.0, 2.0]\n                },\n                VectorSparse {\n                    idx: &[1, 2],\n                    values: &[1.0, 3.0]\n                },\n            ) - vector_f32_distance_l2_rust(&[1.0, 2.0, 0.0], &[0.0, 1.0, 3.0]))\n            .abs()\n                < 1e-7\n        );\n    }\n\n    #[quickcheck]\n    fn prop_vector_distance_l2_dense_vs_sparse(\n        v1: ArbitraryVector<100>,\n        v2: ArbitraryVector<100>,\n    ) -> bool {\n        // Dense uses simsimd, sparse uses rust impl. These can differ by up to 1e-4\n        // (as demonstrated by prop_vector_distance_l2_rust_vs_simsimd_f32).\n        let tolerance = 1e-4;\n        let v1 = vector_convert(v1.into(), VectorType::Float32Dense).unwrap();\n        let v2 = vector_convert(v2.into(), VectorType::Float32Dense).unwrap();\n        let d1 = vector_distance_l2(&v1, &v2).unwrap();\n\n        let sparse1 = vector_convert(v1, VectorType::Float32Sparse).unwrap();\n        let sparse2 = vector_convert(v2, VectorType::Float32Sparse).unwrap();\n        let d2 = vector_f32_sparse_distance_l2(sparse1.as_f32_sparse(), sparse2.as_f32_sparse());\n\n        (d1.is_nan() && d2.is_nan()) || (d1 - d2).abs() < tolerance\n    }\n\n    #[quickcheck]\n    fn prop_vector_distance_l2_rust_vs_simsimd_f32(\n        v1: ArbitraryVector<100>,\n        v2: ArbitraryVector<100>,\n    ) -> bool {\n        let v1 = vector_convert(v1.into(), VectorType::Float32Dense).unwrap();\n        let v2 = vector_convert(v2.into(), VectorType::Float32Dense).unwrap();\n        let d1 = vector_f32_distance_l2_rust(v1.as_f32_slice(), v2.as_f32_slice());\n        let d2 = vector_f32_distance_l2_simsimd(v1.as_f32_slice(), v2.as_f32_slice());\n        (d1.is_nan() && d2.is_nan()) || (d1 - d2).abs() < 1e-4\n    }\n\n    #[quickcheck]\n    fn prop_vector_distance_l2_rust_vs_simsimd_f64(\n        v1: ArbitraryVector<100>,\n        v2: ArbitraryVector<100>,\n    ) -> bool {\n        let v1 = vector_convert(v1.into(), VectorType::Float64Dense).unwrap();\n        let v2 = vector_convert(v2.into(), VectorType::Float64Dense).unwrap();\n        let d1 = vector_f64_distance_l2_rust(v1.as_f64_slice(), v2.as_f64_slice());\n        let d2 = vector_f64_distance_l2_simsimd(v1.as_f64_slice(), v2.as_f64_slice());\n        (d1.is_nan() && d2.is_nan()) || (d1 - d2).abs() < 1e-6\n    }\n\n    /// Float8 L2 distance matches dequantized Float32 L2 distance.\n    #[quickcheck]\n    fn prop_vector_distance_l2_f8_vs_dequantized(\n        v1: ArbitraryVector<100>,\n        v2: ArbitraryVector<100>,\n    ) -> bool {\n        let v1 = vector_convert(v1.into(), VectorType::Float32Dense).unwrap();\n        let v2 = vector_convert(v2.into(), VectorType::Float32Dense).unwrap();\n        let v1_f8 = vector_convert(v1, VectorType::Float8).unwrap();\n        let v2_f8 = vector_convert(v2, VectorType::Float8).unwrap();\n        let d_f8 = vector_distance_l2(&v1_f8, &v2_f8).unwrap();\n        let v1_deq = vector_convert(v1_f8, VectorType::Float32Dense).unwrap();\n        let v2_deq = vector_convert(v2_f8, VectorType::Float32Dense).unwrap();\n        let d_deq = vector_distance_l2(&v1_deq, &v2_deq).unwrap();\n        (d_f8.is_nan() && d_deq.is_nan()) || (d_f8 - d_deq).abs() < 1e-4\n    }\n\n    /// Float1Bit L2 distance returns an error.\n    #[test]\n    fn test_vector_distance_l2_1bit_error() {\n        let v1 = Vector::from_1bit(4, vec![0b1010]);\n        let v2 = Vector::from_1bit(4, vec![0b0101]);\n        assert!(vector_distance_l2(&v1, &v2).is_err());\n    }\n}\n"
  },
  {
    "path": "core/vector/operations/jaccard.rs",
    "content": "use crate::{\n    vector::vector_types::{Vector, VectorSparse, VectorType},\n    LimboError, Result,\n};\n\npub fn vector_distance_jaccard(v1: &Vector, v2: &Vector) -> Result<f64> {\n    if v1.dims != v2.dims {\n        return Err(LimboError::ConversionError(\n            \"Vectors must have the same dimensions\".to_string(),\n        ));\n    }\n    if v1.vector_type != v2.vector_type {\n        return Err(LimboError::ConversionError(\n            \"Vectors must be of the same type\".to_string(),\n        ));\n    }\n    match v1.vector_type {\n        VectorType::Float32Dense => Ok(vector_f32_distance_jaccard(\n            v1.as_f32_slice(),\n            v2.as_f32_slice(),\n        )),\n        VectorType::Float64Dense => Ok(vector_f64_distance_jaccard(\n            v1.as_f64_slice(),\n            v2.as_f64_slice(),\n        )),\n        VectorType::Float32Sparse => Ok(vector_f32_sparse_distance_jaccard(\n            v1.as_f32_sparse(),\n            v2.as_f32_sparse(),\n        )),\n        VectorType::Float1Bit => Ok(vector_1bit_distance_jaccard(v1, v2)),\n        VectorType::Float8 => Ok(vector_f8_distance_jaccard(v1, v2)),\n    }\n}\n\nfn vector_1bit_distance_jaccard(v1: &Vector, v2: &Vector) -> f64 {\n    // Binary Jaccard: 1.0 - popcount(a & b) / popcount(a | b)\n    let d1 = v1.as_1bit_data();\n    let d2 = v2.as_1bit_data();\n    let mut intersection = 0u32;\n    let mut union = 0u32;\n    for (&a, &b) in d1.iter().zip(d2.iter()) {\n        intersection += (a & b).count_ones();\n        union += (a | b).count_ones();\n    }\n    if union == 0 {\n        return f64::NAN;\n    }\n    1.0 - intersection as f64 / union as f64\n}\n\nfn vector_f8_distance_jaccard(v1: &Vector, v2: &Vector) -> f64 {\n    let (data1, alpha1, shift1) = v1.as_f8_data();\n    let (data2, alpha2, shift2) = v2.as_f8_data();\n    let (mut min_sum, mut max_sum) = (0.0f64, 0.0f64);\n    for i in 0..v1.dims {\n        let f1 = alpha1 as f64 * data1[i] as f64 + shift1 as f64;\n        let f2 = alpha2 as f64 * data2[i] as f64 + shift2 as f64;\n        min_sum += f1.min(f2);\n        max_sum += f1.max(f2);\n    }\n    if max_sum == 0.0 {\n        return f64::NAN;\n    }\n    1.0 - min_sum / max_sum\n}\n\nfn vector_f32_distance_jaccard(v1: &[f32], v2: &[f32]) -> f64 {\n    let (mut min_sum, mut max_sum) = (0.0, 0.0);\n    for (&a, &b) in v1.iter().zip(v2.iter()) {\n        min_sum += a.min(b);\n        max_sum += a.max(b);\n    }\n    if max_sum == 0.0 {\n        return f64::NAN;\n    }\n    1. - (min_sum / max_sum) as f64\n}\n\nfn vector_f64_distance_jaccard(v1: &[f64], v2: &[f64]) -> f64 {\n    let (mut min_sum, mut max_sum) = (0.0, 0.0);\n    for (&a, &b) in v1.iter().zip(v2.iter()) {\n        min_sum += a.min(b);\n        max_sum += a.max(b);\n    }\n    if max_sum == 0.0 {\n        return f64::NAN;\n    }\n    1. - min_sum / max_sum\n}\n\nfn vector_f32_sparse_distance_jaccard(v1: VectorSparse<f32>, v2: VectorSparse<f32>) -> f64 {\n    let mut v1_pos = 0;\n    let mut v2_pos = 0;\n    let (mut min_sum, mut max_sum) = (0.0, 0.0);\n    while v1_pos < v1.idx.len() && v2_pos < v2.idx.len() {\n        if v1.idx[v1_pos] == v2.idx[v2_pos] {\n            min_sum += v1.values[v1_pos].min(v2.values[v2_pos]);\n            max_sum += v1.values[v1_pos].max(v2.values[v2_pos]);\n            v1_pos += 1;\n            v2_pos += 1;\n        } else if v1.idx[v1_pos] < v2.idx[v2_pos] {\n            min_sum += v1.values[v1_pos].min(0.);\n            max_sum += v1.values[v1_pos].max(0.);\n            v1_pos += 1;\n        } else {\n            min_sum += v2.values[v2_pos].min(0.);\n            max_sum += v2.values[v2_pos].max(0.);\n            v2_pos += 1;\n        }\n    }\n    while v1_pos < v1.idx.len() {\n        min_sum += v1.values[v1_pos].min(0.);\n        max_sum += v1.values[v1_pos].max(0.);\n        v1_pos += 1;\n    }\n    while v2_pos < v2.idx.len() {\n        min_sum += v2.values[v2_pos].min(0.);\n        max_sum += v2.values[v2_pos].max(0.);\n        v2_pos += 1;\n    }\n    if max_sum == 0.0 {\n        return f64::NAN;\n    }\n    1. - (min_sum / max_sum) as f64\n}\n\n#[cfg(test)]\nmod tests {\n    use quickcheck_macros::quickcheck;\n\n    use crate::vector::{\n        operations::convert::vector_convert, vector_types::tests::ArbitraryVector,\n    };\n\n    use super::*;\n\n    #[test]\n    fn test_vector_distance_jaccard_f32() {\n        assert!(vector_f32_distance_jaccard(&[0.0, 0.0, 0.0], &[0.0, 0.0, 0.0]).is_nan());\n        assert_eq!(vector_f32_distance_jaccard(&[1.0, 2.0], &[0.0, 0.0]), 1.0);\n        assert_eq!(vector_f32_distance_jaccard(&[1.0, 2.0], &[1.0, 2.0]), 0.0);\n        assert_eq!(\n            vector_f32_distance_jaccard(&[1.0, 2.0], &[2.0, 1.0]),\n            1. - (1.0 + 1.0) / (2.0 + 2.0)\n        );\n    }\n\n    #[test]\n    fn test_vector_distance_jaccard_f64() {\n        assert!(vector_f64_distance_jaccard(&[], &[]).is_nan());\n        assert!(vector_f64_distance_jaccard(&[0.0, 0.0, 0.0], &[0.0, 0.0, 0.0]).is_nan());\n        assert_eq!(vector_f64_distance_jaccard(&[1.0, 2.0], &[0.0, 0.0]), 1.0);\n        assert_eq!(vector_f64_distance_jaccard(&[1.0, 2.0], &[1.0, 2.0]), 0.0);\n        assert_eq!(\n            vector_f64_distance_jaccard(&[1.0, 2.0], &[2.0, 1.0]),\n            1. - (1.0 + 1.0) / (2.0 + 2.0)\n        );\n    }\n\n    #[test]\n    fn test_vector_distance_jaccard_f32_sparse() {\n        assert!(\n            (vector_f32_sparse_distance_jaccard(\n                VectorSparse {\n                    idx: &[0, 1],\n                    values: &[1.0, 2.0]\n                },\n                VectorSparse {\n                    idx: &[1, 2],\n                    values: &[1.0, 3.0]\n                },\n            ) - vector_f32_distance_jaccard(&[1.0, 2.0, 0.0], &[0.0, 1.0, 3.0]))\n            .abs()\n                < 1e-7\n        );\n    }\n\n    #[quickcheck]\n    fn prop_vector_distance_jaccard_dense_vs_sparse(\n        v1: ArbitraryVector<100>,\n        v2: ArbitraryVector<100>,\n    ) -> bool {\n        let v1 = vector_convert(v1.into(), VectorType::Float32Dense).unwrap();\n        let v2 = vector_convert(v2.into(), VectorType::Float32Dense).unwrap();\n        let d1 = vector_distance_jaccard(&v1, &v2).unwrap();\n        println!(\"v1: {:?}, v2: {:?}\", v1.as_f32_slice(), v2.as_f32_slice());\n\n        let sparse1 = vector_convert(v1, VectorType::Float32Sparse).unwrap();\n        let sparse2 = vector_convert(v2, VectorType::Float32Sparse).unwrap();\n        let d2 =\n            vector_f32_sparse_distance_jaccard(sparse1.as_f32_sparse(), sparse2.as_f32_sparse());\n\n        println!(\"d1: {}, d2: {}, delta: {}\", d1, d2, (d1 - d2).abs());\n        (d1.is_nan() && d2.is_nan()) || (d1 - d2).abs() < 1e-6\n    }\n\n    // FIXME: flaky\n    // Float8 optimized Jaccard distance matches dequantized Float32 Jaccard distance.\n    // Tolerance is looser here because the Float8 path computes in f64 precision\n    // while the dequantized path accumulates in f32, causing precision differences\n    // that are amplified by Jaccard's min/max ratio when values are close to zero.\n    // #[quickcheck]\n    // fn prop_vector_distance_jaccard_f8_vs_dequantized(\n    //     v1: ArbitraryVector<100>,\n    //     v2: ArbitraryVector<100>,\n    // ) -> bool {\n    //     let v1 = vector_convert(v1.into(), VectorType::Float32Dense).unwrap();\n    //     let v2 = vector_convert(v2.into(), VectorType::Float32Dense).unwrap();\n    //     let v1_f8 = vector_convert(v1, VectorType::Float8).unwrap();\n    //     let v2_f8 = vector_convert(v2, VectorType::Float8).unwrap();\n    //     let d_f8 = vector_distance_jaccard(&v1_f8, &v2_f8).unwrap();\n    //     let v1_deq = vector_convert(v1_f8, VectorType::Float32Dense).unwrap();\n    //     let v2_deq = vector_convert(v2_f8, VectorType::Float32Dense).unwrap();\n    //     let d_deq = vector_distance_jaccard(&v1_deq, &v2_deq).unwrap();\n    //     (d_f8.is_nan() && d_deq.is_nan()) || (d_f8 - d_deq).abs() < 0.01\n    // }\n\n    /// Float1Bit binary Jaccard matches manual computation from dequantized ±1 set bits.\n    #[quickcheck]\n    fn prop_vector_distance_jaccard_1bit_vs_manual(\n        v1: ArbitraryVector<100>,\n        v2: ArbitraryVector<100>,\n    ) -> bool {\n        let v1 = vector_convert(v1.into(), VectorType::Float1Bit).unwrap();\n        let v2 = vector_convert(v2.into(), VectorType::Float1Bit).unwrap();\n        let d = vector_distance_jaccard(&v1, &v2).unwrap();\n        // Manual: binary Jaccard = 1 - |intersection| / |union| over set bits\n        let d1 = v1.as_1bit_data();\n        let d2 = v2.as_1bit_data();\n        let mut intersection = 0u32;\n        let mut union = 0u32;\n        for (&a, &b) in d1.iter().zip(d2.iter()) {\n            intersection += (a & b).count_ones();\n            union += (a | b).count_ones();\n        }\n        if union == 0 {\n            return d.is_nan();\n        }\n        let expected = 1.0 - intersection as f64 / union as f64;\n        (d - expected).abs() < 1e-10\n    }\n}\n"
  },
  {
    "path": "core/vector/operations/mod.rs",
    "content": "pub mod concat;\npub mod convert;\npub mod distance_cos;\npub mod distance_dot;\npub mod distance_l2;\npub mod jaccard;\npub mod serialize;\npub mod slice;\npub mod text;\n"
  },
  {
    "path": "core/vector/operations/serialize.rs",
    "content": "use crate::{\n    vector::vector_types::{Vector, VectorType},\n    Value,\n};\n\npub fn vector_serialize(x: Vector) -> Value {\n    match x.vector_type {\n        VectorType::Float32Dense => Value::from_blob(x.bin_eject()),\n        VectorType::Float64Dense => {\n            let mut data = x.bin_eject();\n            data.push(2);\n            Value::from_blob(data)\n        }\n        VectorType::Float32Sparse => {\n            let dims = x.dims;\n            let mut data = x.bin_eject();\n            data.extend_from_slice(&(dims as u32).to_le_bytes());\n            data.push(9);\n            Value::from_blob(data)\n        }\n        VectorType::Float1Bit => {\n            // Format: [data bytes][optional padding][trailing_bits][0x03]\n            let dims = x.dims;\n            let data_size = dims.div_ceil(8);\n            let needs_padding = data_size % 2 == 0;\n            let meta_size = if needs_padding { 3 } else { 2 };\n            let blob_size = data_size + meta_size;\n            let mut blob = Vec::with_capacity(blob_size);\n            let raw = x.bin_eject();\n            blob.extend_from_slice(&raw[..data_size]);\n            if needs_padding {\n                blob.push(0); // padding\n            }\n            let trailing_bits = (blob_size - 1) * 8 - dims;\n            blob.push(trailing_bits as u8);\n            blob.push(3); // type byte\n            Value::from_blob(blob)\n        }\n        VectorType::Float8 => {\n            // Format: [quantized bytes][alignment padding][alpha f32][shift f32][padding 0x00][trailing_bytes][0x04]\n            let dims = x.dims;\n            let data = x.bin_eject(); // ALIGN(dims, 4) + 8 bytes\n            let trailing_bytes = dims.div_ceil(4) * 4 - dims;\n            let mut blob = Vec::with_capacity(data.len() + 3);\n            blob.extend_from_slice(&data);\n            blob.push(0); // padding\n            blob.push(trailing_bytes as u8);\n            blob.push(4); // type byte\n            Value::from_blob(blob)\n        }\n    }\n}\n"
  },
  {
    "path": "core/vector/operations/slice.rs",
    "content": "use crate::{\n    vector::vector_types::{Vector, VectorType},\n    LimboError, Result,\n};\n\npub fn vector_slice(vector: &Vector, start: usize, end: usize) -> Result<Vector<'static>> {\n    if start > end {\n        return Err(LimboError::InvalidArgument(\n            \"start index must not be greater than end index\".into(),\n        ));\n    }\n    if end > vector.dims || end < start {\n        return Err(LimboError::ConversionError(\n            \"vector_slice range out of bounds\".into(),\n        ));\n    }\n    match vector.vector_type {\n        VectorType::Float32Dense => Ok(Vector {\n            vector_type: vector.vector_type,\n            dims: end - start,\n            owned: Some(vector.bin_data()[start * 4..end * 4].to_vec()),\n            refer: None,\n        }),\n        VectorType::Float64Dense => Ok(Vector {\n            vector_type: vector.vector_type,\n            dims: end - start,\n            owned: Some(vector.bin_data()[start * 8..end * 8].to_vec()),\n            refer: None,\n        }),\n        VectorType::Float32Sparse => {\n            let mut values = Vec::new();\n            let mut idx = Vec::new();\n            let sparse = vector.as_f32_sparse();\n            for (&i, &value) in sparse.idx.iter().zip(sparse.values.iter()) {\n                let i = i as usize;\n                if i < start || i >= end {\n                    continue;\n                }\n                values.extend_from_slice(&value.to_le_bytes());\n                idx.extend_from_slice(&((i - start) as u32).to_le_bytes());\n            }\n            values.extend_from_slice(&idx);\n            Ok(Vector {\n                vector_type: vector.vector_type,\n                dims: end - start,\n                owned: Some(values),\n                refer: None,\n            })\n        }\n        VectorType::Float1Bit | VectorType::Float8 => Err(LimboError::ConversionError(\n            \"vector_slice is not supported for float1bit/float8 vectors\".to_string(),\n        )),\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::vector::{\n        operations::slice::vector_slice,\n        vector_types::{Vector, VectorType},\n    };\n\n    fn float32_vec_from(slice: &[f32]) -> Vector {\n        let mut data = Vec::new();\n        for &v in slice {\n            data.extend_from_slice(&v.to_le_bytes());\n        }\n\n        Vector {\n            vector_type: VectorType::Float32Dense,\n            dims: slice.len(),\n            owned: Some(data),\n            refer: None,\n        }\n    }\n\n    fn f32_slice_from_vector(vector: &Vector) -> Vec<f32> {\n        vector.as_f32_slice().to_vec()\n    }\n\n    #[test]\n    fn test_vector_slice_normal_case() {\n        let input_vec = float32_vec_from(&[1.0, 2.0, 3.0, 4.0, 5.0]);\n        let result = vector_slice(&input_vec, 1, 4).unwrap();\n\n        assert_eq!(result.dims, 3);\n        assert_eq!(f32_slice_from_vector(&result), vec![2.0, 3.0, 4.0]);\n    }\n\n    #[test]\n    fn test_vector_slice_full_range() {\n        let input_vec = float32_vec_from(&[10.0, 20.0, 30.0]);\n        let result = vector_slice(&input_vec, 0, 3).unwrap();\n\n        assert_eq!(result.dims, 3);\n        assert_eq!(f32_slice_from_vector(&result), vec![10.0, 20.0, 30.0]);\n    }\n\n    #[test]\n    fn test_vector_slice_single_element() {\n        let input_vec = float32_vec_from(&[4.40, 2.71]);\n        let result = vector_slice(&input_vec, 1, 2).unwrap();\n\n        assert_eq!(result.dims, 1);\n        assert_eq!(f32_slice_from_vector(&result), vec![2.71]);\n    }\n\n    #[test]\n    fn test_vector_slice_empty_list() {\n        let input_vec = float32_vec_from(&[1.0, 2.0]);\n        let result = vector_slice(&input_vec, 2, 2).unwrap();\n\n        assert_eq!(result.dims, 0);\n    }\n\n    #[test]\n    fn test_vector_slice_zero_length() {\n        let input_vec = float32_vec_from(&[1.0, 2.0, 3.0]);\n        let err = vector_slice(&input_vec, 2, 1);\n        assert!(err.is_err(), \"Expected error on zero-length range\");\n    }\n\n    #[test]\n    fn test_vector_slice_out_of_bounds() {\n        let input_vec = float32_vec_from(&[1.0, 2.0]);\n        let err = vector_slice(&input_vec, 0, 5);\n        assert!(err.is_err());\n    }\n\n    #[test]\n    fn test_vector_slice_start_out_of_bounds() {\n        let input_vec = float32_vec_from(&[1.0, 2.0]);\n        let err = vector_slice(&input_vec, 5, 5);\n        assert!(err.is_err());\n    }\n\n    #[test]\n    fn test_vector_slice_end_out_of_bounds() {\n        let input_vec = float32_vec_from(&[1.0, 2.0]);\n        let err = vector_slice(&input_vec, 1, 3);\n        assert!(err.is_err());\n    }\n}\n"
  },
  {
    "path": "core/vector/operations/text.rs",
    "content": "use crate::{\n    vector::vector_types::{Vector, VectorType},\n    LimboError, Result,\n};\n\npub fn vector_to_text(vector: &Vector) -> String {\n    match vector.vector_type {\n        VectorType::Float32Dense => format_text(vector.as_f32_slice().iter()),\n        VectorType::Float64Dense => format_text(vector.as_f64_slice().iter()),\n        VectorType::Float32Sparse => {\n            let mut dense = vec![0.0f32; vector.dims];\n            let sparse = vector.as_f32_sparse();\n            tracing::info!(\"{:?}\", sparse);\n            for (&idx, &value) in sparse.idx.iter().zip(sparse.values.iter()) {\n                dense[idx as usize] = value;\n            }\n            format_text(dense.iter())\n        }\n        VectorType::Float1Bit => {\n            // Each bit → +1 or -1\n            let data = vector.as_1bit_data();\n            let values: Vec<i8> = (0..vector.dims)\n                .map(|i| {\n                    if (data[i / 8] >> (i & 7)) & 1 == 1 {\n                        1\n                    } else {\n                        -1\n                    }\n                })\n                .collect();\n            format_text(values.iter())\n        }\n        VectorType::Float8 => {\n            // Dequantize: f_i = alpha * q_i + shift\n            let (quantized, alpha, shift) = vector.as_f8_data();\n            let values: Vec<f32> = quantized\n                .iter()\n                .map(|&q| alpha * (q as f32) + shift)\n                .collect();\n            format_text(values.iter())\n        }\n    }\n}\n\nfn format_text<T: std::string::ToString>(values: impl Iterator<Item = T>) -> String {\n    let mut text = String::new();\n    text.push('[');\n    let mut first = true;\n    for value in values {\n        if !first {\n            text.push(',');\n        }\n        first = false;\n        text.push_str(&value.to_string());\n    }\n    text.push(']');\n    text\n}\n\n/// Parse a vector in text representation into a Vector.\n///\n/// The format of a vector in text representation looks as follows:\n///\n/// ```console\n/// [1.0, 2.0, 3.0]\n/// ```\npub fn vector_from_text(vector_type: VectorType, text: &str) -> Result<Vector> {\n    let text = text.trim();\n    let mut chars = text.chars();\n    if chars.next() != Some('[') || chars.last() != Some(']') {\n        return Err(LimboError::ConversionError(\n            \"Invalid vector value\".to_string(),\n        ));\n    }\n    let text = &text[1..text.len() - 1];\n    if text.trim().is_empty() {\n        return Ok(match vector_type {\n            VectorType::Float1Bit => {\n                return Err(LimboError::ConversionError(\n                    \"empty vector not supported for this type\".to_string(),\n                ));\n            }\n            VectorType::Float8 => {\n                return Ok(Vector::from_f8(0, Vec::new(), 0.0, 0.0));\n            }\n            _ => Vector {\n                vector_type,\n                dims: 0,\n                owned: Some(Vec::new()),\n                refer: None,\n            },\n        });\n    }\n    let tokens = text.split(',').map(|x| x.trim());\n    match vector_type {\n        VectorType::Float32Dense => vector32_from_text(tokens),\n        VectorType::Float64Dense => vector64_from_text(tokens),\n        VectorType::Float32Sparse => vector32_sparse_from_text(tokens),\n        VectorType::Float1Bit => vector_1bit_from_text(tokens),\n        VectorType::Float8 => vector_f8_from_text(tokens),\n    }\n}\n\nfn vector32_from_text<'a>(tokens: impl Iterator<Item = &'a str>) -> Result<Vector<'static>> {\n    let mut data = Vec::new();\n    for token in tokens {\n        let value = token\n            .parse::<f32>()\n            .map_err(|_| LimboError::ConversionError(\"Invalid vector value\".to_string()))?;\n        if !value.is_finite() {\n            return Err(LimboError::ConversionError(\n                \"Invalid vector value\".to_string(),\n            ));\n        }\n        data.extend_from_slice(&value.to_le_bytes());\n    }\n    Ok(Vector {\n        vector_type: VectorType::Float32Dense,\n        dims: data.len() / 4,\n        owned: Some(data),\n        refer: None,\n    })\n}\n\nfn vector64_from_text<'a>(tokens: impl Iterator<Item = &'a str>) -> Result<Vector<'static>> {\n    let mut data = Vec::new();\n    for token in tokens {\n        let value = token\n            .parse::<f64>()\n            .map_err(|_| LimboError::ConversionError(\"Invalid vector value\".to_string()))?;\n        if !value.is_finite() {\n            return Err(LimboError::ConversionError(\n                \"Invalid vector value\".to_string(),\n            ));\n        }\n        data.extend_from_slice(&value.to_le_bytes());\n    }\n    Ok(Vector {\n        vector_type: VectorType::Float64Dense,\n        dims: data.len() / 8,\n        owned: Some(data),\n        refer: None,\n    })\n}\n\nfn vector32_sparse_from_text<'a>(tokens: impl Iterator<Item = &'a str>) -> Result<Vector<'static>> {\n    let mut idx = Vec::new();\n    let mut values = Vec::new();\n    let mut dims = 0u32;\n    for token in tokens {\n        let value = token\n            .parse::<f32>()\n            .map_err(|_| LimboError::ConversionError(\"Invalid vector value\".to_string()))?;\n        if !value.is_finite() {\n            return Err(LimboError::ConversionError(\n                \"Invalid vector value\".to_string(),\n            ));\n        }\n\n        dims += 1;\n        if value == 0.0 {\n            continue;\n        }\n        idx.extend_from_slice(&(dims - 1).to_le_bytes());\n        values.extend_from_slice(&value.to_le_bytes());\n    }\n\n    values.extend_from_slice(&idx);\n    Ok(Vector {\n        vector_type: VectorType::Float32Sparse,\n        dims: dims as usize,\n        owned: Some(values),\n        refer: None,\n    })\n}\n\nfn vector_1bit_from_text<'a>(tokens: impl Iterator<Item = &'a str>) -> Result<Vector<'static>> {\n    let mut floats = Vec::new();\n    for token in tokens {\n        let value = token\n            .parse::<f32>()\n            .map_err(|_| LimboError::ConversionError(\"Invalid vector value\".to_string()))?;\n        if !value.is_finite() {\n            return Err(LimboError::ConversionError(\n                \"Invalid vector value\".to_string(),\n            ));\n        }\n        floats.push(value);\n    }\n    let dims = floats.len();\n    let byte_count = dims.div_ceil(8);\n    let mut bits = vec![0u8; byte_count];\n    for (i, &f) in floats.iter().enumerate() {\n        if f > 0.0 {\n            bits[i / 8] |= 1 << (i & 7);\n        }\n    }\n    Ok(Vector::from_1bit(dims, bits))\n}\n\nfn vector_f8_from_text<'a>(tokens: impl Iterator<Item = &'a str>) -> Result<Vector<'static>> {\n    let mut floats = Vec::new();\n    for token in tokens {\n        let value = token\n            .parse::<f32>()\n            .map_err(|_| LimboError::ConversionError(\"Invalid vector value\".to_string()))?;\n        if !value.is_finite() {\n            return Err(LimboError::ConversionError(\n                \"Invalid vector value\".to_string(),\n            ));\n        }\n        floats.push(value);\n    }\n    let dims = floats.len();\n    let min_val = floats.iter().cloned().fold(f32::INFINITY, f32::min);\n    let max_val = floats.iter().cloned().fold(f32::NEG_INFINITY, f32::max);\n    let alpha = (max_val - min_val) / 255.0;\n    let shift = min_val;\n    let mut quantized = Vec::with_capacity(dims);\n    for &f in &floats {\n        let q = if alpha == 0.0 {\n            0u8\n        } else {\n            let v = (f - shift) / alpha + 0.5;\n            (v as i32).clamp(0, 255) as u8\n        };\n        quantized.push(q);\n    }\n    Ok(Vector::from_f8(dims, quantized, alpha, shift))\n}\n"
  },
  {
    "path": "core/vector/vector_types.rs",
    "content": "use crate::turso_debug_assert;\nuse crate::{LimboError, Result};\n\n#[derive(Debug, Clone, PartialEq, Copy)]\npub enum VectorType {\n    Float32Dense,\n    Float64Dense,\n    Float32Sparse,\n    Float1Bit,\n    Float8,\n}\n\n#[derive(Debug)]\npub struct Vector<'a> {\n    pub vector_type: VectorType,\n    pub dims: usize,\n    pub owned: Option<Vec<u8>>,\n    pub refer: Option<&'a [u8]>,\n}\n\n#[derive(Debug)]\npub struct VectorSparse<'a, T: std::fmt::Debug> {\n    pub idx: &'a [u32],\n    pub values: &'a [T],\n}\n\nimpl<'a> Vector<'a> {\n    /// Returns (VectorType, data_length, dims) from a serialized blob.\n    /// `data_length` is the number of bytes of actual vector data (before meta/type bytes).\n    /// `dims` is the number of vector dimensions (only meaningful for Float1Bit/Float8 where\n    /// it can't be inferred from data length alone; for other types it's set to 0 and\n    /// computed later in from_data).\n    pub fn vector_type(blob: &[u8]) -> Result<(VectorType, usize, usize)> {\n        // Even-sized blobs are always float32.\n        if blob.len() % 2 == 0 {\n            return Ok((VectorType::Float32Dense, blob.len(), 0));\n        }\n        // Odd-sized blobs have type byte at the end\n        let vector_type = blob[blob.len() - 1];\n        /*\n        vector types used by LibSQL:\n        (see https://github.com/tursodatabase/libsql/blob/a55bf61192bdb89e97568de593c4af5b70d24bde/libsql-sqlite3/src/vectorInt.h#L52)\n            #define VECTOR_TYPE_FLOAT32   1\n            #define VECTOR_TYPE_FLOAT64   2\n            #define VECTOR_TYPE_FLOAT1BIT 3\n            #define VECTOR_TYPE_FLOAT8    4\n            #define VECTOR_TYPE_FLOAT16   5\n            #define VECTOR_TYPE_FLOATB16  6\n        */\n        match vector_type {\n            1 => Ok((VectorType::Float32Dense, blob.len() - 1, 0)),\n            2 => Ok((VectorType::Float64Dense, blob.len() - 1, 0)),\n            3 => {\n                // Float1Bit: [data bytes][optional padding][trailing_bits][0x03]\n                let n_blob_size = blob.len() - 1; // without type byte\n                if n_blob_size == 0 || n_blob_size % 2 != 0 {\n                    return Err(LimboError::ConversionError(\n                        \"float1bit vector blob length must be even and non-empty\".to_string(),\n                    ));\n                }\n                let trailing_bits = blob[n_blob_size - 1] as usize;\n                let dims = n_blob_size * 8 - trailing_bits;\n                let data_size = dims.div_ceil(8);\n                Ok((VectorType::Float1Bit, data_size, dims))\n            }\n            4 => {\n                // Float8: [quantized bytes][alignment padding][alpha f32][shift f32][padding 0x00][trailing_bytes][0x04]\n                let n_blob_size = blob.len() - 1; // without type byte\n                if n_blob_size < 2 || n_blob_size % 2 != 0 {\n                    return Err(LimboError::ConversionError(\n                        \"float8 vector blob must have even length >= 2 (excluding type byte)\"\n                            .to_string(),\n                    ));\n                }\n                let trailing_bytes = blob[n_blob_size - 1] as usize;\n                let dims = (n_blob_size - 2) - 8 - trailing_bytes;\n                // data_size = ALIGN(dims, 4) + 8\n                let data_size = n_blob_size - 2;\n                Ok((VectorType::Float8, data_size, dims))\n            }\n            5..=6 => Err(LimboError::ConversionError(\n                \"unsupported vector type from LibSQL\".to_string(),\n            )),\n            9 => Ok((VectorType::Float32Sparse, blob.len() - 1, 0)),\n            _ => Err(LimboError::ConversionError(format!(\n                \"unknown vector type: {vector_type}\"\n            ))),\n        }\n    }\n    pub fn from_f32(mut values_f32: Vec<f32>) -> Self {\n        let dims = values_f32.len();\n        let values = unsafe {\n            Vec::from_raw_parts(\n                values_f32.as_mut_ptr() as *mut u8,\n                values_f32.len() * 4,\n                values_f32.capacity() * 4,\n            )\n        };\n        std::mem::forget(values_f32);\n        Self {\n            vector_type: VectorType::Float32Dense,\n            dims,\n            owned: Some(values),\n            refer: None,\n        }\n    }\n    pub fn from_f64(mut values_f64: Vec<f64>) -> Self {\n        let dims = values_f64.len();\n        let values = unsafe {\n            Vec::from_raw_parts(\n                values_f64.as_mut_ptr() as *mut u8,\n                values_f64.len() * 8,\n                values_f64.capacity() * 8,\n            )\n        };\n        std::mem::forget(values_f64);\n        Self {\n            vector_type: VectorType::Float64Dense,\n            dims,\n            owned: Some(values),\n            refer: None,\n        }\n    }\n    pub fn from_f32_sparse(dims: usize, mut values_f32: Vec<f32>, mut idx_u32: Vec<u32>) -> Self {\n        let mut values = unsafe {\n            Vec::from_raw_parts(\n                values_f32.as_mut_ptr() as *mut u8,\n                values_f32.len() * 4,\n                values_f32.capacity() * 4,\n            )\n        };\n        std::mem::forget(values_f32);\n\n        let idx = unsafe {\n            Vec::from_raw_parts(\n                idx_u32.as_mut_ptr() as *mut u8,\n                idx_u32.len() * 4,\n                idx_u32.capacity() * 4,\n            )\n        };\n        std::mem::forget(idx_u32);\n\n        values.extend_from_slice(&idx);\n        Self {\n            vector_type: VectorType::Float32Sparse,\n            dims,\n            owned: Some(values),\n            refer: None,\n        }\n    }\n    fn align4(n: usize) -> usize {\n        n.div_ceil(4) * 4\n    }\n\n    pub fn from_1bit(dims: usize, bits: Vec<u8>) -> Self {\n        debug_assert!(bits.len() == dims.div_ceil(8));\n        Self {\n            vector_type: VectorType::Float1Bit,\n            dims,\n            owned: Some(bits),\n            refer: None,\n        }\n    }\n\n    pub fn from_f8(dims: usize, quantized: Vec<u8>, alpha: f32, shift: f32) -> Self {\n        let aligned = Self::align4(dims);\n        let mut data = Vec::with_capacity(aligned + 8);\n        data.extend_from_slice(&quantized);\n        data.resize(aligned, 0); // alignment padding\n        data.extend_from_slice(&alpha.to_le_bytes());\n        data.extend_from_slice(&shift.to_le_bytes());\n        debug_assert!(data.len() == aligned + 8);\n        Self {\n            vector_type: VectorType::Float8,\n            dims,\n            owned: Some(data),\n            refer: None,\n        }\n    }\n\n    pub fn from_vec(mut blob: Vec<u8>) -> Result<Self> {\n        let (vector_type, len, explicit_dims) = Self::vector_type(&blob)?;\n        blob.truncate(len);\n        Self::from_data_with_dims(vector_type, Some(blob), None, explicit_dims)\n    }\n    pub fn from_slice(blob: &'a [u8]) -> Result<Self> {\n        let (vector_type, len, explicit_dims) = Self::vector_type(blob)?;\n        Self::from_data_with_dims(vector_type, None, Some(&blob[..len]), explicit_dims)\n    }\n    pub fn from_data(\n        vector_type: VectorType,\n        owned: Option<Vec<u8>>,\n        refer: Option<&'a [u8]>,\n    ) -> Result<Self> {\n        Self::from_data_with_dims(vector_type, owned, refer, 0)\n    }\n\n    fn from_data_with_dims(\n        vector_type: VectorType,\n        owned: Option<Vec<u8>>,\n        refer: Option<&'a [u8]>,\n        explicit_dims: usize,\n    ) -> Result<Self> {\n        let owned_slice = owned.as_deref();\n        let refer_slice = refer.as_ref().map(|&x| x);\n        let data = owned_slice.or(refer_slice).ok_or_else(|| {\n            LimboError::InternalError(\"Vector must have either owned or refer data\".to_string())\n        })?;\n        match vector_type {\n            VectorType::Float32Dense => {\n                if data.len() % 4 != 0 {\n                    return Err(LimboError::InvalidArgument(format!(\n                        \"f32 dense vector unexpected data length: {}\",\n                        data.len(),\n                    )));\n                }\n                Ok(Vector {\n                    vector_type,\n                    dims: data.len() / 4,\n                    owned,\n                    refer,\n                })\n            }\n            VectorType::Float64Dense => {\n                if data.len() % 8 != 0 {\n                    return Err(LimboError::InvalidArgument(format!(\n                        \"f64 dense vector unexpected data length: {}\",\n                        data.len(),\n                    )));\n                }\n                Ok(Vector {\n                    vector_type,\n                    dims: data.len() / 8,\n                    owned,\n                    refer,\n                })\n            }\n            VectorType::Float32Sparse => {\n                if data.is_empty() || data.len() % 4 != 0 || (data.len() - 4) % 8 != 0 {\n                    return Err(LimboError::InvalidArgument(format!(\n                        \"f32 sparse vector unexpected data length: {}\",\n                        data.len(),\n                    )));\n                }\n                let original_len = data.len();\n                let dims_bytes = &data[original_len - 4..];\n                let dims = u32::from_le_bytes([\n                    dims_bytes[0],\n                    dims_bytes[1],\n                    dims_bytes[2],\n                    dims_bytes[3],\n                ]) as usize;\n                let owned = owned.map(|mut x| {\n                    x.truncate(original_len - 4);\n                    x\n                });\n                let refer = refer.map(|x| &x[0..original_len - 4]);\n                Ok(Vector {\n                    vector_type,\n                    dims,\n                    owned,\n                    refer,\n                })\n            }\n            VectorType::Float1Bit => {\n                let expected_len = explicit_dims.div_ceil(8);\n                if explicit_dims == 0 || data.len() != expected_len {\n                    return Err(LimboError::InvalidArgument(format!(\n                        \"f1bit vector data length mismatch: got {} expected {} for {} dims\",\n                        data.len(),\n                        expected_len,\n                        explicit_dims,\n                    )));\n                }\n                Ok(Vector {\n                    vector_type,\n                    dims: explicit_dims,\n                    owned,\n                    refer,\n                })\n            }\n            VectorType::Float8 => {\n                if data.len() < 8 {\n                    return Err(LimboError::InvalidArgument(format!(\n                        \"f8 vector data too short: {}\",\n                        data.len(),\n                    )));\n                }\n                let expected_len = Self::align4(explicit_dims) + 8;\n                if explicit_dims == 0 || data.len() != expected_len {\n                    return Err(LimboError::InvalidArgument(format!(\n                        \"f8 vector data length mismatch: got {} expected {} for {} dims\",\n                        data.len(),\n                        expected_len,\n                        explicit_dims,\n                    )));\n                }\n                Ok(Vector {\n                    vector_type,\n                    dims: explicit_dims,\n                    owned,\n                    refer,\n                })\n            }\n        }\n    }\n\n    pub fn bin_len(&self) -> usize {\n        let owned = self.owned.as_ref().map(|x| x.len());\n        let refer = self.refer.as_ref().map(|x| x.len());\n        owned\n            .or(refer)\n            .expect(\"Vector invariant: exactly one of owned or refer must be Some\")\n    }\n\n    pub fn bin_data(&'a self) -> &'a [u8] {\n        let owned = self.owned.as_deref();\n        let refer = self.refer.as_ref().map(|&x| x);\n        owned\n            .or(refer)\n            .expect(\"Vector invariant: exactly one of owned or refer must be Some\")\n    }\n\n    pub fn bin_eject(self) -> Vec<u8> {\n        self.owned.unwrap_or_else(|| {\n            self.refer\n                .expect(\"Vector invariant: exactly one of owned or refer must be Some\")\n                .to_vec()\n        })\n    }\n\n    /// # Safety\n    ///\n    /// This method is used to reinterpret the underlying `Vec<u8>` data\n    /// as a `&[f32]` slice. This is only valid if:\n    /// - The buffer is correctly aligned for `f32`\n    /// - The length of the buffer is exactly `dims * size_of::<f32>()`\n    pub fn as_f32_slice(&self) -> &[f32] {\n        turso_debug_assert!(self.vector_type == VectorType::Float32Dense);\n        if self.dims == 0 {\n            return &[];\n        }\n\n        assert_eq!(\n            self.bin_len(),\n            self.dims * std::mem::size_of::<f32>(),\n            \"data length must equal dims * size_of::<f32>()\"\n        );\n\n        let ptr = self.bin_data().as_ptr();\n        let align = std::mem::align_of::<f32>();\n        assert_eq!(\n            ptr.align_offset(align),\n            0,\n            \"data pointer must be aligned to {align} bytes for f32 access\"\n        );\n\n        unsafe { std::slice::from_raw_parts(ptr as *const f32, self.dims) }\n    }\n\n    /// # Safety\n    ///\n    /// This method is used to reinterpret the underlying `Vec<u8>` data\n    /// as a `&[f64]` slice. This is only valid if:\n    /// - The buffer is correctly aligned for `f64`\n    /// - The length of the buffer is exactly `dims * size_of::<f64>()`\n    pub fn as_f64_slice(&self) -> &[f64] {\n        turso_debug_assert!(self.vector_type == VectorType::Float64Dense);\n        if self.dims == 0 {\n            return &[];\n        }\n\n        assert_eq!(\n            self.bin_len(),\n            self.dims * std::mem::size_of::<f64>(),\n            \"data length must equal dims * size_of::<f64>()\"\n        );\n\n        let ptr = self.bin_data().as_ptr();\n        let align = std::mem::align_of::<f64>();\n        assert_eq!(\n            ptr.align_offset(align),\n            0,\n            \"data pointer must be aligned to {align} bytes for f64 access\"\n        );\n\n        unsafe { std::slice::from_raw_parts(ptr as *const f64, self.dims) }\n    }\n\n    pub fn as_f32_sparse(&self) -> VectorSparse<'_, f32> {\n        turso_debug_assert!(self.vector_type == VectorType::Float32Sparse);\n        let ptr = self.bin_data().as_ptr();\n        let align = std::mem::align_of::<f32>();\n        assert_eq!(\n            ptr.align_offset(align),\n            0,\n            \"data pointer must be aligned to {align} bytes for f32 access\"\n        );\n        let length = self.bin_data().len() / 4 / 2;\n        let values = unsafe { std::slice::from_raw_parts(ptr as *const f32, length) };\n        let idx = unsafe { std::slice::from_raw_parts((ptr as *const u32).add(length), length) };\n        turso_debug_assert!(idx.is_sorted());\n        VectorSparse { idx, values }\n    }\n\n    /// Returns the raw bit-packed bytes for a Float1Bit vector.\n    /// Bit `i` is at byte `i/8`, position `i & 7`.\n    pub fn as_1bit_data(&self) -> &[u8] {\n        debug_assert!(self.vector_type == VectorType::Float1Bit);\n        let data = self.bin_data();\n        &data[..self.dims.div_ceil(8)]\n    }\n\n    /// Returns (quantized_bytes, alpha, shift) for a Float8 vector.\n    /// Dequantization: `f_i = alpha * q_i + shift`\n    pub fn as_f8_data(&self) -> (&[u8], f32, f32) {\n        debug_assert!(self.vector_type == VectorType::Float8);\n        let data = self.bin_data();\n        let aligned = Self::align4(self.dims);\n        let alpha = f32::from_le_bytes([\n            data[aligned],\n            data[aligned + 1],\n            data[aligned + 2],\n            data[aligned + 3],\n        ]);\n        let shift = f32::from_le_bytes([\n            data[aligned + 4],\n            data[aligned + 5],\n            data[aligned + 6],\n            data[aligned + 7],\n        ]);\n        (&data[..self.dims], alpha, shift)\n    }\n}\n\n#[cfg(test)]\npub(crate) mod tests {\n    use crate::vector::operations;\n\n    use super::*;\n    use quickcheck::{Arbitrary, Gen};\n    use quickcheck_macros::quickcheck;\n\n    // Helper to generate arbitrary vectors of specific type and dimensions\n    #[derive(Debug, Clone)]\n    pub struct ArbitraryVector<const DIMS: usize> {\n        vector_type: VectorType,\n        data: Vec<u8>,\n    }\n\n    /// How to create an arbitrary vector of DIMS dims.\n    impl<const DIMS: usize> ArbitraryVector<DIMS> {\n        fn generate_f32_vector(g: &mut Gen) -> Vec<f32> {\n            (0..DIMS)\n                .map(|_| {\n                    loop {\n                        // generate zeroes with some probability since we have support for sparse vectors\n                        if bool::arbitrary(g) {\n                            return 0.0;\n                        }\n                        let f = f32::arbitrary(g);\n                        // f32::arbitrary() can generate \"problem values\" like NaN, infinity, and very small values\n                        // Skip these values\n                        if f.is_finite() && f.abs() >= 1e-6 {\n                            // Scale to [-1, 1] range\n                            return f % 2.0 - 1.0;\n                        }\n                    }\n                })\n                .collect()\n        }\n\n        fn generate_f64_vector(g: &mut Gen) -> Vec<f64> {\n            (0..DIMS)\n                .map(|_| {\n                    loop {\n                        // generate zeroes with some probability since we have support for sparse vectors\n                        if bool::arbitrary(g) {\n                            return 0.0;\n                        }\n                        let f = f64::arbitrary(g);\n                        // f64::arbitrary() can generate \"problem values\" like NaN, infinity, and very small values\n                        // Skip these values\n                        if f.is_finite() && f.abs() >= 1e-6 {\n                            // Scale to [-1, 1] range\n                            return f % 2.0 - 1.0;\n                        }\n                    }\n                })\n                .collect()\n        }\n    }\n\n    /// Convert an ArbitraryVector to a Vector.\n    impl<const DIMS: usize> From<ArbitraryVector<DIMS>> for Vector<'static> {\n        fn from(v: ArbitraryVector<DIMS>) -> Self {\n            Vector {\n                vector_type: v.vector_type,\n                dims: DIMS,\n                owned: Some(v.data),\n                refer: None,\n            }\n        }\n    }\n\n    /// Implement the quickcheck Arbitrary trait for ArbitraryVector.\n    impl<const DIMS: usize> Arbitrary for ArbitraryVector<DIMS> {\n        fn arbitrary(g: &mut Gen) -> Self {\n            let choice = u8::arbitrary(g) % 4;\n            let vector_type = match choice {\n                0 => VectorType::Float32Dense,\n                1 => VectorType::Float64Dense,\n                2 => VectorType::Float1Bit,\n                _ => VectorType::Float8,\n            };\n\n            let data = match vector_type {\n                VectorType::Float32Dense => {\n                    let floats = Self::generate_f32_vector(g);\n                    floats.iter().flat_map(|f| f.to_le_bytes()).collect()\n                }\n                VectorType::Float64Dense => {\n                    let floats = Self::generate_f64_vector(g);\n                    floats.iter().flat_map(|f| f.to_le_bytes()).collect()\n                }\n                VectorType::Float1Bit => {\n                    // Generate random bits\n                    let byte_count = DIMS.div_ceil(8);\n                    let mut bits = vec![0u8; byte_count];\n                    for b in bits.iter_mut() {\n                        *b = u8::arbitrary(g);\n                    }\n                    // Mask off unused bits in the last byte\n                    if DIMS % 8 != 0 {\n                        let mask = (1u8 << (DIMS % 8)) - 1;\n                        if let Some(last) = bits.last_mut() {\n                            *last &= mask;\n                        }\n                    }\n                    bits\n                }\n                VectorType::Float8 => {\n                    // Generate random quantized values + alpha/shift\n                    let floats = Self::generate_f32_vector(g);\n                    let min_val = floats.iter().cloned().fold(f32::INFINITY, f32::min);\n                    let max_val = floats.iter().cloned().fold(f32::NEG_INFINITY, f32::max);\n                    let alpha = (max_val - min_val) / 255.0;\n                    let shift = min_val;\n                    let aligned = DIMS.div_ceil(4) * 4;\n                    let mut data = Vec::with_capacity(aligned + 8);\n                    for &f in &floats {\n                        let q = if alpha == 0.0 {\n                            0u8\n                        } else {\n                            ((f - shift) / alpha + 0.5) as u8\n                        };\n                        data.push(q);\n                    }\n                    data.resize(aligned, 0); // alignment padding\n                    data.extend_from_slice(&alpha.to_le_bytes());\n                    data.extend_from_slice(&shift.to_le_bytes());\n                    data\n                }\n                _ => unreachable!(),\n            };\n\n            ArbitraryVector { vector_type, data }\n        }\n    }\n\n    #[quickcheck]\n    fn prop_vector_type_identification_2d(v: ArbitraryVector<2>) -> bool {\n        test_vector_type::<2>(v.into())\n    }\n\n    #[quickcheck]\n    fn prop_vector_type_identification_3d(v: ArbitraryVector<3>) -> bool {\n        test_vector_type::<3>(v.into())\n    }\n\n    #[quickcheck]\n    fn prop_vector_type_identification_4d(v: ArbitraryVector<4>) -> bool {\n        test_vector_type::<4>(v.into())\n    }\n\n    #[quickcheck]\n    fn prop_vector_type_identification_100d(v: ArbitraryVector<100>) -> bool {\n        test_vector_type::<100>(v.into())\n    }\n\n    #[quickcheck]\n    fn prop_vector_type_identification_1536d(v: ArbitraryVector<1536>) -> bool {\n        test_vector_type::<1536>(v.into())\n    }\n\n    /// Test if the vector type identification is correct for a given vector.\n    fn test_vector_type<const DIMS: usize>(v: Vector) -> bool {\n        let vtype = v.vector_type;\n        let value = operations::serialize::vector_serialize(v);\n        let blob = value.to_blob().unwrap().to_vec();\n        match Vector::vector_type(&blob) {\n            Ok((detected_type, _, _)) => detected_type == vtype,\n            Err(_) => false,\n        }\n    }\n\n    #[quickcheck]\n    fn prop_slice_conversion_safety_2d(v: ArbitraryVector<2>) -> bool {\n        test_slice_conversion::<2>(v.into())\n    }\n\n    #[quickcheck]\n    fn prop_slice_conversion_safety_3d(v: ArbitraryVector<3>) -> bool {\n        test_slice_conversion::<3>(v.into())\n    }\n\n    #[quickcheck]\n    fn prop_slice_conversion_safety_4d(v: ArbitraryVector<4>) -> bool {\n        test_slice_conversion::<4>(v.into())\n    }\n\n    #[quickcheck]\n    fn prop_slice_conversion_safety_100d(v: ArbitraryVector<100>) -> bool {\n        test_slice_conversion::<100>(v.into())\n    }\n\n    #[quickcheck]\n    fn prop_slice_conversion_safety_1536d(v: ArbitraryVector<1536>) -> bool {\n        test_slice_conversion::<1536>(v.into())\n    }\n\n    /// Test if the slice conversion is safe for a given vector:\n    /// - The slice length matches the dimensions\n    /// - The data length is correct (4 bytes per float for f32, 8 bytes per float for f64)\n    fn test_slice_conversion<const DIMS: usize>(v: Vector) -> bool {\n        match v.vector_type {\n            VectorType::Float32Dense => {\n                let slice = v.as_f32_slice();\n                slice.len() == DIMS && (slice.len() * 4 == v.bin_len())\n            }\n            VectorType::Float64Dense => {\n                let slice = v.as_f64_slice();\n                slice.len() == DIMS && (slice.len() * 8 == v.bin_len())\n            }\n            VectorType::Float1Bit => {\n                let data = v.as_1bit_data();\n                data.len() == DIMS.div_ceil(8) && v.dims == DIMS\n            }\n            VectorType::Float8 => {\n                let (quantized, _alpha, _shift) = v.as_f8_data();\n                quantized.len() == DIMS && v.dims == DIMS\n            }\n            _ => true,\n        }\n    }\n\n    #[quickcheck]\n    fn prop_vector_distance_safety_2d(v1: ArbitraryVector<2>, v2: ArbitraryVector<2>) -> bool {\n        test_vector_distance::<2>(&v1.into(), &v2.into())\n    }\n\n    #[quickcheck]\n    fn prop_vector_distance_safety_3d(v1: ArbitraryVector<3>, v2: ArbitraryVector<3>) -> bool {\n        test_vector_distance::<3>(&v1.into(), &v2.into())\n    }\n\n    #[quickcheck]\n    fn prop_vector_distance_safety_4d(v1: ArbitraryVector<4>, v2: ArbitraryVector<4>) -> bool {\n        test_vector_distance::<4>(&v1.into(), &v2.into())\n    }\n\n    #[quickcheck]\n    fn prop_vector_distance_safety_100d(\n        v1: ArbitraryVector<100>,\n        v2: ArbitraryVector<100>,\n    ) -> bool {\n        test_vector_distance::<100>(&v1.into(), &v2.into())\n    }\n\n    #[quickcheck]\n    fn prop_vector_distance_safety_1536d(\n        v1: ArbitraryVector<1536>,\n        v2: ArbitraryVector<1536>,\n    ) -> bool {\n        test_vector_distance::<1536>(&v1.into(), &v2.into())\n    }\n\n    /// Test if the vector distance calculation is correct for a given pair of vectors:\n    /// - Skips cases with invalid input vectors.\n    /// - Assumes vectors are well-formed (same type and dimension)\n    fn test_vector_distance<const DIMS: usize>(v1: &Vector, v2: &Vector) -> bool {\n        match operations::distance_cos::vector_distance_cos(v1, v2) {\n            Ok(distance) => {\n                if distance.is_nan() {\n                    return true;\n                }\n                match v1.vector_type {\n                    // Float1Bit cosine distance returns hamming distance [0, dims]\n                    VectorType::Float1Bit => (0.0 - 1e-6..=DIMS as f64 + 1e-6).contains(&distance),\n                    // Normal cosine distance [0, 2]\n                    _ => (0.0 - 1e-6..=2.0 + 1e-6).contains(&distance),\n                }\n            }\n            Err(_) => true,\n        }\n    }\n\n    #[test]\n    fn test_vector_some_cosine_dist() {\n        let a = Vector {\n            vector_type: VectorType::Float32Dense,\n            dims: 2,\n            owned: Some(vec![0, 0, 0, 0, 52, 208, 106, 63]),\n            refer: None,\n        };\n        let b = Vector {\n            vector_type: VectorType::Float32Dense,\n            dims: 2,\n            owned: Some(vec![0, 0, 0, 0, 58, 100, 45, 192]),\n            refer: None,\n        };\n        assert!(\n            (operations::distance_cos::vector_distance_cos(&a, &b).unwrap() - 2.0).abs() <= 1e-6\n        );\n    }\n\n    #[test]\n    fn parse_string_vector_zero_length() {\n        let vector = operations::text::vector_from_text(VectorType::Float32Dense, \"[]\").unwrap();\n        assert_eq!(vector.dims, 0);\n        assert_eq!(vector.vector_type, VectorType::Float32Dense);\n    }\n\n    #[test]\n    fn parse_string_vector_f8_zero_length() {\n        let vector = operations::text::vector_from_text(VectorType::Float8, \"[]\").unwrap();\n        assert_eq!(vector.dims, 0);\n        assert_eq!(vector.vector_type, VectorType::Float8);\n        let (quantized, alpha, shift) = vector.as_f8_data();\n        assert!(quantized.is_empty());\n        assert_eq!(alpha, 0.0);\n        assert_eq!(shift, 0.0);\n    }\n\n    #[test]\n    fn test_parse_string_vector_valid_whitespace() {\n        let vector = operations::text::vector_from_text(\n            VectorType::Float32Dense,\n            \"  [  1.0  ,  2.0  ,  3.0  ]  \",\n        )\n        .unwrap();\n        assert_eq!(vector.dims, 3);\n        assert_eq!(vector.vector_type, VectorType::Float32Dense);\n    }\n\n    #[test]\n    fn test_parse_string_vector_valid() {\n        let vector =\n            operations::text::vector_from_text(VectorType::Float32Dense, \"[1.0, 2.0, 3.0]\")\n                .unwrap();\n        assert_eq!(vector.dims, 3);\n        assert_eq!(vector.vector_type, VectorType::Float32Dense);\n    }\n\n    #[quickcheck]\n    fn prop_vector_text_roundtrip_2d(v: ArbitraryVector<2>) -> bool {\n        test_vector_text_roundtrip(v.into())\n    }\n\n    #[quickcheck]\n    fn prop_vector_text_roundtrip_3d(v: ArbitraryVector<3>) -> bool {\n        test_vector_text_roundtrip(v.into())\n    }\n\n    #[quickcheck]\n    fn prop_vector_text_roundtrip_4d(v: ArbitraryVector<4>) -> bool {\n        test_vector_text_roundtrip(v.into())\n    }\n\n    #[quickcheck]\n    fn prop_vector_text_roundtrip_100d(v: ArbitraryVector<100>) -> bool {\n        test_vector_text_roundtrip(v.into())\n    }\n\n    #[quickcheck]\n    fn prop_vector_text_roundtrip_1536d(v: ArbitraryVector<1536>) -> bool {\n        test_vector_text_roundtrip(v.into())\n    }\n\n    /// Test that a vector can be converted to text and back without loss of precision\n    fn test_vector_text_roundtrip(v: Vector) -> bool {\n        // Convert to text\n        let text = operations::text::vector_to_text(&v);\n\n        // Parse back from text\n        let parsed = operations::text::vector_from_text(v.vector_type, &text);\n\n        match parsed {\n            Ok(parsed_vector) => {\n                // Check dimensions match\n                if v.dims != parsed_vector.dims {\n                    return false;\n                }\n\n                match v.vector_type {\n                    VectorType::Float32Dense => {\n                        let original = v.as_f32_slice();\n                        let parsed = parsed_vector.as_f32_slice();\n                        original.iter().zip(parsed.iter()).all(|(a, b)| a == b)\n                    }\n                    VectorType::Float64Dense => {\n                        let original = v.as_f64_slice();\n                        let parsed = parsed_vector.as_f64_slice();\n                        original.iter().zip(parsed.iter()).all(|(a, b)| a == b)\n                    }\n                    VectorType::Float1Bit => {\n                        // 1bit text roundtrip: bits → +1/-1 text → threshold → bits\n                        let original = v.as_1bit_data();\n                        let parsed_data = parsed_vector.as_1bit_data();\n                        original == parsed_data\n                    }\n                    VectorType::Float8 => {\n                        // Float8 text roundtrip: lossy due to quantization\n                        // Just check dims match (re-quantization changes alpha/shift)\n                        true\n                    }\n                    _ => true,\n                }\n            }\n            Err(_) => false,\n        }\n    }\n}\n"
  },
  {
    "path": "core/vtab.rs",
    "content": "use crate::pragma::{PragmaVirtualTable, PragmaVirtualTableCursor};\nuse crate::schema::Column;\nuse crate::sync::atomic::{AtomicPtr, AtomicU64, Ordering};\nuse crate::sync::{Arc, RwLock, Weak};\nuse crate::util::columns_from_create_table_body;\nuse crate::{Connection, LimboError, SymbolTable, Value};\nuse std::ffi::c_void;\nuse std::ptr::NonNull;\nuse turso_ext::{ConstraintInfo, IndexInfo, OrderByInfo, ResultCode, VTabKind, VTabModuleImpl};\nuse turso_parser::{ast, parser::Parser};\n\n#[derive(Debug, Clone)]\npub(crate) enum VirtualTableType {\n    Pragma(PragmaVirtualTable),\n    External(ExtVirtualTable),\n    Internal(Arc<RwLock<dyn InternalVirtualTable>>),\n}\n\n#[derive(Clone, Debug)]\npub struct VirtualTable {\n    pub(crate) name: String,\n    pub(crate) columns: Vec<Column>,\n    pub(crate) kind: VTabKind,\n    vtab_type: VirtualTableType,\n    // identifier to tie a cursor to a specific instantiated virtual table instance\n    vtab_id: u64,\n    // Whether this virtual table is safe to use from within triggers and views.\n    // Corresponds to SQLite's SQLITE_VTAB_INNOCUOUS flag.\n    pub(crate) innocuous: bool,\n}\n\nimpl VirtualTable {\n    pub(crate) fn id(&self) -> u64 {\n        self.vtab_id\n    }\n    pub(crate) fn readonly(&self) -> bool {\n        match &self.vtab_type {\n            VirtualTableType::Pragma(_) => true,\n            VirtualTableType::External(table) => table.readonly(),\n            VirtualTableType::Internal(_) => true,\n        }\n    }\n\n    #[cfg(feature = \"cli_only\")]\n    fn dbpage_virtual_table() -> Arc<VirtualTable> {\n        let dbpage_table = crate::dbpage::DbPageTable::new();\n        let dbpage_vtab = VirtualTable {\n            name: dbpage_table.name(),\n            columns: Self::resolve_columns(dbpage_table.sql())\n                .expect(\"sqlite_dbpage schema resolution should not fail\"),\n            kind: VTabKind::TableValuedFunction,\n            vtab_type: VirtualTableType::Internal(Arc::new(RwLock::new(dbpage_table))),\n            vtab_id: 0,\n            innocuous: true,\n        };\n        Arc::new(dbpage_vtab)\n    }\n\n    #[cfg(feature = \"cli_only\")]\n    fn btree_dump_virtual_table() -> Arc<VirtualTable> {\n        let btree_dump_table = crate::btree_dump::BtreeDumpTable::new();\n        let vtab = VirtualTable {\n            name: btree_dump_table.name(),\n            columns: Self::resolve_columns(btree_dump_table.sql())\n                .expect(\"btree_dump schema resolution should not fail\"),\n            kind: VTabKind::TableValuedFunction,\n            vtab_type: VirtualTableType::Internal(Arc::new(RwLock::new(btree_dump_table))),\n            vtab_id: 0,\n            innocuous: true,\n        };\n        Arc::new(vtab)\n    }\n\n    fn turso_types_virtual_table() -> Arc<VirtualTable> {\n        let table = crate::turso_types_vtab::TursoTypesTable::new();\n        let vtab = VirtualTable {\n            name: table.name(),\n            columns: Self::resolve_columns(table.sql())\n                .expect(\"sqlite_turso_types schema resolution should not fail\"),\n            kind: VTabKind::TableValuedFunction,\n            vtab_type: VirtualTableType::Internal(Arc::new(RwLock::new(table))),\n            vtab_id: 0,\n            innocuous: true,\n        };\n        Arc::new(vtab)\n    }\n\n    pub(crate) fn builtin_functions(enable_custom_types: bool) -> Vec<Arc<VirtualTable>> {\n        let mut vtables: Vec<Arc<VirtualTable>> = PragmaVirtualTable::functions()\n            .into_iter()\n            .map(|(tab, schema)| {\n                let vtab = VirtualTable {\n                    name: format!(\"pragma_{}\", tab.pragma_name),\n                    columns: Self::resolve_columns(schema)\n                        .expect(\"pragma table-valued function schema resolution should not fail\"),\n                    kind: VTabKind::TableValuedFunction,\n                    vtab_type: VirtualTableType::Pragma(tab),\n                    vtab_id: 0,\n                    innocuous: true,\n                };\n                Arc::new(vtab)\n            })\n            .collect();\n\n        #[cfg(feature = \"json\")]\n        vtables.extend(Self::json_virtual_tables());\n\n        #[cfg(feature = \"cli_only\")]\n        vtables.push(Self::dbpage_virtual_table());\n\n        #[cfg(feature = \"cli_only\")]\n        vtables.push(Self::btree_dump_virtual_table());\n\n        if enable_custom_types {\n            vtables.push(Self::turso_types_virtual_table());\n        }\n\n        vtables\n    }\n\n    #[cfg(feature = \"json\")]\n    fn json_virtual_tables() -> Vec<Arc<VirtualTable>> {\n        use crate::json::vtab::JsonVirtualTable;\n\n        let json_each = JsonVirtualTable::json_each();\n\n        let json_each_virtual_table = VirtualTable {\n            name: json_each.name(),\n            columns: Self::resolve_columns(json_each.sql())\n                .expect(\"internal table-valued function schema resolution should not fail\"),\n            kind: VTabKind::TableValuedFunction,\n            vtab_type: VirtualTableType::Internal(Arc::new(RwLock::new(json_each))),\n            vtab_id: 0,\n            innocuous: true,\n        };\n\n        let json_tree = JsonVirtualTable::json_tree();\n\n        let json_tree_virtual_table = VirtualTable {\n            name: json_tree.name(),\n            columns: Self::resolve_columns(json_tree.sql())\n                .expect(\"internal table-valued function schema resolution should not fail\"),\n            kind: VTabKind::TableValuedFunction,\n            vtab_type: VirtualTableType::Internal(Arc::new(RwLock::new(json_tree))),\n            vtab_id: 0,\n            innocuous: true,\n        };\n\n        vec![\n            Arc::new(json_each_virtual_table),\n            Arc::new(json_tree_virtual_table),\n        ]\n    }\n\n    pub(crate) fn function(name: &str, syms: &SymbolTable) -> crate::Result<Arc<VirtualTable>> {\n        let module = syms.vtab_modules.get(name);\n        let (vtab_type, schema) = if module.is_some() {\n            ExtVirtualTable::create(name, module, Vec::new(), VTabKind::TableValuedFunction)\n                .map(|(vtab, columns)| (VirtualTableType::External(vtab), columns))?\n        } else {\n            return Err(LimboError::ParseError(format!(\n                \"No such table-valued function: {name}\"\n            )));\n        };\n\n        let vtab = VirtualTable {\n            name: name.to_owned(),\n            columns: Self::resolve_columns(schema)?,\n            kind: VTabKind::TableValuedFunction,\n            vtab_type,\n            vtab_id: 0,\n            innocuous: false,\n        };\n        Ok(Arc::new(vtab))\n    }\n\n    pub fn table(\n        tbl_name: Option<&str>,\n        module_name: &str,\n        args: Vec<turso_ext::Value>,\n        syms: &SymbolTable,\n    ) -> crate::Result<Arc<VirtualTable>> {\n        let module = syms.vtab_modules.get(module_name);\n        let (table, schema) =\n            ExtVirtualTable::create(module_name, module, args, VTabKind::VirtualTable)?;\n        let vtab = VirtualTable {\n            name: tbl_name.unwrap_or(module_name).to_owned(),\n            columns: Self::resolve_columns(schema)?,\n            kind: VTabKind::VirtualTable,\n            vtab_type: VirtualTableType::External(table),\n            vtab_id: VTAB_ID_COUNTER.fetch_add(1, Ordering::Acquire),\n            innocuous: false,\n        };\n        Ok(Arc::new(vtab))\n    }\n\n    fn resolve_columns(schema: String) -> crate::Result<Vec<Column>> {\n        let mut parser = Parser::new(schema.as_bytes());\n        if let ast::Cmd::Stmt(ast::Stmt::CreateTable { body, .. }) =\n            parser.next_cmd()?.ok_or_else(|| {\n                LimboError::ParseError(\n                    \"Failed to parse schema from virtual table module\".to_string(),\n                )\n            })?\n        {\n            columns_from_create_table_body(&body)\n        } else {\n            Err(LimboError::ParseError(\n                \"Failed to parse schema from virtual table module\".to_string(),\n            ))\n        }\n    }\n\n    pub(crate) fn open(&self, conn: Arc<Connection>) -> crate::Result<VirtualTableCursor> {\n        match &self.vtab_type {\n            VirtualTableType::Pragma(table) => {\n                Ok(VirtualTableCursor::new_pragma(table.open(conn)?))\n            }\n            VirtualTableType::External(table) => Ok(VirtualTableCursor::new_external(\n                table.open(conn, self.vtab_id)?,\n            )),\n            VirtualTableType::Internal(table) => {\n                Ok(VirtualTableCursor::new_internal(table.read().open(conn)?))\n            }\n        }\n    }\n\n    pub(crate) fn update(&self, args: &[Value]) -> crate::Result<Option<i64>> {\n        match &self.vtab_type {\n            VirtualTableType::Pragma(_) => Err(LimboError::ReadOnly),\n            VirtualTableType::External(table) => table.update(args),\n            VirtualTableType::Internal(_) => Err(LimboError::ReadOnly),\n        }\n    }\n\n    pub(crate) fn destroy(&self) -> crate::Result<()> {\n        match &self.vtab_type {\n            VirtualTableType::Pragma(_) => Ok(()),\n            VirtualTableType::External(table) => table.destroy(),\n            VirtualTableType::Internal(_) => Ok(()),\n        }\n    }\n\n    pub(crate) fn best_index(\n        &self,\n        constraints: &[ConstraintInfo],\n        order_by: &[OrderByInfo],\n    ) -> Result<IndexInfo, ResultCode> {\n        match &self.vtab_type {\n            VirtualTableType::Pragma(table) => table.best_index(constraints),\n            VirtualTableType::External(table) => table.best_index(constraints, order_by),\n            VirtualTableType::Internal(table) => table.read().best_index(constraints, order_by),\n        }\n    }\n\n    pub(crate) fn begin(&self) -> crate::Result<()> {\n        match &self.vtab_type {\n            VirtualTableType::Pragma(_) => Err(LimboError::ExtensionError(\n                \"Pragma virtual tables do not support transactions\".to_string(),\n            )),\n            VirtualTableType::External(table) => table.begin(),\n            VirtualTableType::Internal(_) => Err(LimboError::ExtensionError(\n                \"Internal virtual tables currently do not support transactions\".to_string(),\n            )),\n        }\n    }\n\n    pub(crate) fn commit(&self) -> crate::Result<()> {\n        match &self.vtab_type {\n            VirtualTableType::Pragma(_) => Err(LimboError::ExtensionError(\n                \"Pragma virtual tables do not support transactions\".to_string(),\n            )),\n            VirtualTableType::External(table) => table.commit(),\n            VirtualTableType::Internal(_) => Err(LimboError::ExtensionError(\n                \"Internal virtual tables currently do not support transactions\".to_string(),\n            )),\n        }\n    }\n\n    pub(crate) fn rollback(&self) -> crate::Result<()> {\n        match &self.vtab_type {\n            VirtualTableType::Pragma(_) => Err(LimboError::ExtensionError(\n                \"Pragma virtual tables do not support transactions\".to_string(),\n            )),\n            VirtualTableType::External(table) => table.rollback(),\n            VirtualTableType::Internal(_) => Err(LimboError::ExtensionError(\n                \"Internal virtual tables currently do not support transactions\".to_string(),\n            )),\n        }\n    }\n\n    pub(crate) fn rename(&self, new_name: &str) -> crate::Result<()> {\n        match &self.vtab_type {\n            VirtualTableType::Pragma(_) => Err(LimboError::ExtensionError(\n                \"Pragma virtual tables do not support renaming\".to_string(),\n            )),\n            VirtualTableType::External(table) => table.rename(new_name),\n            VirtualTableType::Internal(_) => Err(LimboError::ExtensionError(\n                \"Internal virtual tables currently do not support renaming\".to_string(),\n            )),\n        }\n    }\n}\n\nenum VirtualTableCursorInner {\n    Pragma(Box<PragmaVirtualTableCursor>),\n    External(ExtVirtualTableCursor),\n    Internal(Arc<RwLock<dyn InternalVirtualTableCursor>>),\n}\n\npub struct VirtualTableCursor {\n    inner: VirtualTableCursorInner,\n    null_flag: bool,\n}\n\ncrate::assert::assert_send_sync!(VirtualTableCursor);\n\nimpl VirtualTableCursor {\n    pub(crate) fn new_pragma(cursor: PragmaVirtualTableCursor) -> Self {\n        Self {\n            inner: VirtualTableCursorInner::Pragma(Box::new(cursor)),\n            null_flag: false,\n        }\n    }\n\n    pub(crate) fn new_external(cursor: ExtVirtualTableCursor) -> Self {\n        Self {\n            inner: VirtualTableCursorInner::External(cursor),\n            null_flag: false,\n        }\n    }\n\n    pub(crate) fn new_internal(cursor: Arc<RwLock<dyn InternalVirtualTableCursor>>) -> Self {\n        Self {\n            inner: VirtualTableCursorInner::Internal(cursor),\n            null_flag: false,\n        }\n    }\n\n    pub(crate) fn set_null_flag(&mut self, flag: bool) {\n        self.null_flag = flag;\n    }\n\n    pub(crate) fn next(&mut self) -> crate::Result<bool> {\n        self.null_flag = false;\n        match &mut self.inner {\n            VirtualTableCursorInner::Pragma(cursor) => cursor.next(),\n            VirtualTableCursorInner::External(cursor) => cursor.next(),\n            VirtualTableCursorInner::Internal(cursor) => cursor.write().next(),\n        }\n    }\n\n    pub(crate) fn rowid(&self) -> i64 {\n        match &self.inner {\n            VirtualTableCursorInner::Pragma(cursor) => cursor.rowid(),\n            VirtualTableCursorInner::External(cursor) => cursor.rowid(),\n            VirtualTableCursorInner::Internal(cursor) => cursor.read().rowid(),\n        }\n    }\n\n    pub(crate) fn column(&self, column: usize) -> crate::Result<Value> {\n        if self.null_flag {\n            return Ok(Value::Null);\n        }\n        match &self.inner {\n            VirtualTableCursorInner::Pragma(cursor) => cursor.column(column),\n            VirtualTableCursorInner::External(cursor) => cursor.column(column),\n            VirtualTableCursorInner::Internal(cursor) => cursor.read().column(column),\n        }\n    }\n\n    pub(crate) fn filter(\n        &mut self,\n        idx_num: i32,\n        idx_str: Option<String>,\n        arg_count: usize,\n        args: Vec<Value>,\n    ) -> crate::Result<bool> {\n        self.null_flag = false;\n        match &mut self.inner {\n            VirtualTableCursorInner::Pragma(cursor) => cursor.filter(args),\n            VirtualTableCursorInner::External(cursor) => {\n                cursor.filter(idx_num, idx_str, arg_count, args)\n            }\n            VirtualTableCursorInner::Internal(cursor) => {\n                cursor.write().filter(&args, idx_str, idx_num)\n            }\n        }\n    }\n\n    pub(crate) fn vtab_id(&self) -> Option<u64> {\n        match &self.inner {\n            VirtualTableCursorInner::Pragma(_) => None,\n            VirtualTableCursorInner::External(cursor) => cursor.vtab_id.into(),\n            VirtualTableCursorInner::Internal(_) => None,\n        }\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct ExtVirtualTable {\n    implementation: Arc<VTabModuleImpl>,\n    table_ptr: AtomicPtr<c_void>,\n}\nstatic VTAB_ID_COUNTER: AtomicU64 = AtomicU64::new(1);\n\nimpl Clone for ExtVirtualTable {\n    fn clone(&self) -> Self {\n        Self {\n            implementation: self.implementation.clone(),\n            table_ptr: AtomicPtr::new(self.table_ptr.load(Ordering::SeqCst)),\n        }\n    }\n}\n\nimpl ExtVirtualTable {\n    pub(crate) fn readonly(&self) -> bool {\n        self.implementation.readonly\n    }\n    fn best_index(\n        &self,\n        constraints: &[ConstraintInfo],\n        order_by: &[OrderByInfo],\n    ) -> Result<IndexInfo, ResultCode> {\n        unsafe {\n            IndexInfo::from_ffi((self.implementation.best_idx)(\n                constraints.as_ptr(),\n                constraints.len() as i32,\n                order_by.as_ptr(),\n                order_by.len() as i32,\n            ))\n        }\n    }\n\n    /// takes ownership of the provided Args\n    fn create(\n        module_name: &str,\n        module: Option<&Arc<crate::ext::VTabImpl>>,\n        args: Vec<turso_ext::Value>,\n        kind: VTabKind,\n    ) -> crate::Result<(Self, String)> {\n        let module = module.ok_or_else(|| {\n            LimboError::ExtensionError(format!(\"Virtual table module not found: {module_name}\"))\n        })?;\n        if kind != module.module_kind {\n            let expected = match kind {\n                VTabKind::VirtualTable => \"virtual table\",\n                VTabKind::TableValuedFunction => \"table-valued function\",\n            };\n            return Err(LimboError::ExtensionError(format!(\n                \"{module_name} is not a {expected} module\"\n            )));\n        }\n        let (schema, table_ptr) = module.implementation.create(args)?;\n        let vtab = ExtVirtualTable {\n            implementation: module.implementation.clone(),\n            table_ptr: AtomicPtr::new(table_ptr as *mut c_void),\n        };\n        Ok((vtab, schema))\n    }\n\n    /// Accepts a pointer connection that owns the VTable, that the module\n    /// can optionally use to query the other tables.\n    fn open(&self, conn: Arc<Connection>, id: u64) -> crate::Result<ExtVirtualTableCursor> {\n        // we need a Weak<Connection> to upgrade and call from the extension.\n        let weak = Arc::downgrade(&conn);\n        let weak_box = Box::into_raw(Box::new(weak));\n        let conn = turso_ext::Conn::new(\n            weak_box as *mut c_void,\n            crate::ext::prepare_stmt,\n            crate::ext::execute,\n        );\n        let ext_conn_ptr = NonNull::new(Box::into_raw(Box::new(conn))).expect(\"null pointer\");\n        // store the leaked connection pointer on the table so it can be freed on drop\n        let Some(cursor) = NonNull::new(unsafe {\n            (self.implementation.open)(\n                self.table_ptr.load(Ordering::SeqCst) as *const c_void,\n                ext_conn_ptr.as_ptr(),\n            ) as *mut c_void\n        }) else {\n            return Err(LimboError::ExtensionError(\"Open returned null\".to_string()));\n        };\n        ExtVirtualTableCursor::new(cursor, ext_conn_ptr, self.implementation.clone(), id)\n    }\n\n    fn update(&self, args: &[Value]) -> crate::Result<Option<i64>> {\n        let arg_count = args.len();\n        let ext_args = args.iter().map(|arg| arg.to_ffi()).collect::<Vec<_>>();\n        let newrowid = 0i64;\n        let rc = unsafe {\n            (self.implementation.update)(\n                self.table_ptr.load(Ordering::SeqCst) as *const c_void,\n                arg_count as i32,\n                ext_args.as_ptr(),\n                &newrowid as *const _ as *mut i64,\n            )\n        };\n        for arg in ext_args {\n            unsafe {\n                arg.__free_internal_type();\n            }\n        }\n        match rc {\n            ResultCode::OK => Ok(None),\n            ResultCode::RowID => Ok(Some(newrowid)),\n            _ => Err(LimboError::ExtensionError(rc.to_string())),\n        }\n    }\n\n    fn destroy(&self) -> crate::Result<()> {\n        let rc = unsafe {\n            (self.implementation.destroy)(self.table_ptr.load(Ordering::SeqCst) as *const c_void)\n        };\n        match rc {\n            ResultCode::OK => Ok(()),\n            _ => Err(LimboError::ExtensionError(rc.to_string())),\n        }\n    }\n\n    fn commit(&self) -> crate::Result<()> {\n        let rc = unsafe { (self.implementation.commit)(self.table_ptr.load(Ordering::SeqCst)) };\n        match rc {\n            ResultCode::OK => Ok(()),\n            _ => Err(LimboError::ExtensionError(\"Commit failed\".to_string())),\n        }\n    }\n\n    fn begin(&self) -> crate::Result<()> {\n        let rc = unsafe { (self.implementation.begin)(self.table_ptr.load(Ordering::SeqCst)) };\n        match rc {\n            ResultCode::OK => Ok(()),\n            _ => Err(LimboError::ExtensionError(\"Begin failed\".to_string())),\n        }\n    }\n\n    fn rollback(&self) -> crate::Result<()> {\n        let rc = unsafe { (self.implementation.rollback)(self.table_ptr.load(Ordering::SeqCst)) };\n        match rc {\n            ResultCode::OK => Ok(()),\n            _ => Err(LimboError::ExtensionError(\"Rollback failed\".to_string())),\n        }\n    }\n\n    fn rename(&self, new_name: &str) -> crate::Result<()> {\n        let c_new_name = std::ffi::CString::new(new_name).unwrap();\n        let rc = unsafe {\n            (self.implementation.rename)(self.table_ptr.load(Ordering::SeqCst), c_new_name.as_ptr())\n        };\n        match rc {\n            ResultCode::OK => Ok(()),\n            _ => Err(LimboError::ExtensionError(\"Rename failed\".to_string())),\n        }\n    }\n}\n\npub struct ExtVirtualTableCursor {\n    cursor: NonNull<c_void>,\n    // the core `[Connection]` pointer the vtab module needs to\n    // query other internal tables.\n    conn_ptr: Option<NonNull<turso_ext::Conn>>,\n    implementation: Arc<VTabModuleImpl>,\n    vtab_id: u64,\n}\n\n// SAFETY: Extension provider must guarantee Send + Sync on their side\n// we cannot properly infer Send + Sync for dynamic libraries\nunsafe impl Send for ExtVirtualTableCursor {}\nunsafe impl Sync for ExtVirtualTableCursor {}\ncrate::assert::assert_send_sync!(ExtVirtualTableCursor);\n\nimpl ExtVirtualTableCursor {\n    fn new(\n        cursor: NonNull<c_void>,\n        conn_ptr: NonNull<turso_ext::Conn>,\n        implementation: Arc<VTabModuleImpl>,\n        id: u64,\n    ) -> crate::Result<Self> {\n        Ok(Self {\n            cursor,\n            conn_ptr: Some(conn_ptr),\n            implementation,\n            vtab_id: id,\n        })\n    }\n\n    fn rowid(&self) -> i64 {\n        unsafe { (self.implementation.rowid)(self.cursor.as_ptr()) }\n    }\n\n    #[tracing::instrument(skip(self))]\n    fn filter(\n        &self,\n        idx_num: i32,\n        idx_str: Option<String>,\n        arg_count: usize,\n        args: Vec<Value>,\n    ) -> crate::Result<bool> {\n        tracing::trace!(\"xFilter\");\n        let ext_args = args.iter().map(|arg| arg.to_ffi()).collect::<Vec<_>>();\n        let idx_str = match idx_str {\n            Some(idx_str) => Some(std::ffi::CString::new(idx_str).map_err(|e| {\n                crate::LimboError::InternalError(format!(\"failed to convert idx_str string: {e}\"))\n            })?),\n            None => None,\n        };\n        let c_idx_str_ptr = idx_str\n            .as_ref()\n            .map(|s| s.as_ptr())\n            .unwrap_or(std::ptr::null_mut());\n        let rc = unsafe {\n            (self.implementation.filter)(\n                self.cursor.as_ptr(),\n                arg_count as i32,\n                ext_args.as_ptr(),\n                c_idx_str_ptr,\n                idx_num,\n            )\n        };\n        for arg in ext_args {\n            unsafe {\n                arg.__free_internal_type();\n            }\n        }\n        match rc {\n            ResultCode::OK => Ok(true),\n            ResultCode::EOF => Ok(false),\n            _ => Err(LimboError::ExtensionError(rc.to_string())),\n        }\n    }\n\n    fn column(&self, column: usize) -> crate::Result<Value> {\n        let val = unsafe { (self.implementation.column)(self.cursor.as_ptr(), column as u32) };\n        Value::from_ffi(val)\n    }\n\n    fn next(&self) -> crate::Result<bool> {\n        let rc = unsafe { (self.implementation.next)(self.cursor.as_ptr()) };\n        match rc {\n            ResultCode::OK => Ok(true),\n            ResultCode::EOF => Ok(false),\n            _ => Err(LimboError::ExtensionError(\"Next failed\".to_string())),\n        }\n    }\n}\n\nimpl Drop for ExtVirtualTableCursor {\n    fn drop(&mut self) {\n        if let Some(ptr) = self.conn_ptr.take() {\n            // first free the boxed turso_ext::Conn pointer itself\n            let conn = unsafe { Box::from_raw(ptr.as_ptr()) };\n            if !conn._ctx.is_null() {\n                // we also leaked the Weak 'ctx' pointer, so free this as well\n                let _ = unsafe { Box::from_raw(conn._ctx as *mut Weak<Connection>) };\n            }\n        }\n        let result = unsafe { (self.implementation.close)(self.cursor.as_ptr()) };\n        if !result.is_ok() {\n            tracing::error!(\"Failed to close virtual table cursor\");\n        }\n    }\n}\n\npub trait InternalVirtualTable: std::fmt::Debug + Send + Sync {\n    fn name(&self) -> String;\n    fn open(\n        &self,\n        conn: Arc<Connection>,\n    ) -> crate::Result<Arc<RwLock<dyn InternalVirtualTableCursor>>>;\n    /// best_index is used by the optimizer. See the comment on `Table::best_index`.\n    fn best_index(\n        &self,\n        constraints: &[turso_ext::ConstraintInfo],\n        order_by: &[turso_ext::OrderByInfo],\n    ) -> Result<turso_ext::IndexInfo, ResultCode>;\n    fn sql(&self) -> String;\n}\n\npub trait InternalVirtualTableCursor: Send + Sync {\n    /// next returns `Ok(true)` if there are more rows, and `Ok(false)` otherwise.\n    fn next(&mut self) -> Result<bool, LimboError>;\n    fn rowid(&self) -> i64;\n    fn column(&self, column: usize) -> Result<Value, LimboError>;\n    fn filter(\n        &mut self,\n        args: &[Value],\n        idx_str: Option<String>,\n        idx_num: i32,\n    ) -> Result<bool, LimboError>;\n}\n"
  },
  {
    "path": "deny.toml",
    "content": "# cargo-deny configuration\n# See: https://embarkstudios.github.io/cargo-deny/\n\n[graph]\n# Include all targets for comprehensive checking\nall-features = true\n# Exclude internal perf/testing crates from license checks\nexclude = [\n    \"encryption-throughput\",\n    \"write-throughput\",\n    \"write-throughput-sqlite\",\n]\n\n[licenses]\n# Use version 2 for modern cargo-deny behavior\nversion = 2\n\n# Confidence threshold for license detection\nconfidence-threshold = 0.95\n\n# List of explicitly allowed licenses\n# All other licenses are denied by default (including all GPL variants)\nallow = [\n    \"MIT\",\n    \"Apache-2.0\",\n    \"Apache-2.0 WITH LLVM-exception\",\n    \"BSD-2-Clause\",\n    \"BSD-3-Clause\",\n    \"BSL-1.0\",\n    \"ISC\",\n    \"Zlib\",\n    \"Unicode-3.0\",\n    \"CC0-1.0\",\n    \"Unlicense\",\n    \"MPL-2.0\",\n]\n\n# Clarification for ring crate which has a complex license\n[[licenses.clarify]]\nname = \"ring\"\nexpression = \"MIT AND ISC AND OpenSSL\"\nlicense-files = [\n    { path = \"LICENSE\", hash = 0xbd0eed23 },\n]\n\n# Private crates (workspace members) are ignored\n[licenses.private]\nignore = true\nregistries = []\n"
  },
  {
    "path": "dist-workspace.toml",
    "content": "[workspace]\nmembers = [\"cargo:.\"]\n\n# Config for 'dist'\n[dist]\n# The preferred dist version to use in CI (Cargo.toml SemVer syntax)\ncargo-dist-version = \"0.30.2\"\n# CI backends to support\nci = \"github\"\n# The installers to generate for each app\ninstallers = [\"shell\", \"powershell\"]\n# Target platforms to build apps for (Rust target-triple syntax)\ntargets = [\"aarch64-apple-darwin\", \"aarch64-unknown-linux-gnu\", \"x86_64-apple-darwin\", \"x86_64-unknown-linux-gnu\", \"x86_64-pc-windows-msvc\"]\n# Which actions to run on pull requests\npr-run-mode = \"plan\"\n# Path that installers should place binaries in\ninstall-path = \"~/.turso\"\n# Whether to install an updater program\ninstall-updater = true\n# Whether to consider the binaries in a package for distribution (defaults true)\ndist = false\n# Whether to enable GitHub Attestations\ngithub-attestations = true\n"
  },
  {
    "path": "docs/CODEOWNERS",
    "content": "* @penberg\n/core/ @pereman2 @jussisaurio @penberg\n/core/storage @pereman2\n/core/translate @jussisaurio\n/extensions/ @PThorpe92\n/core/ext/ @PThorpe92\n/bindings/go @PThorpe92\n\n# These are commented out until they get access from the overlords\n# /bindings/java @seonWKim\n# /simulator/ @alpaylan\n# /core/storage @krishvishal \n"
  },
  {
    "path": "docs/agent-guides/async-io-model.md",
    "content": "---\nname: async-io-model\ndescription: IOResult, state machines, re-entrancy pitfalls, CompletionGroup\n---\n# Async I/O Model Guide\n\nTurso uses cooperative yielding with explicit state machines instead of Rust async/await.\n\n## Core Types\n\n```rust\npub enum IOCompletions {\n    Single(Completion),\n}\n\n#[must_use]\npub enum IOResult<T> {\n    Done(T),      // Operation complete, here's the result\n    IO(IOCompletions),  // Need I/O, call me again after completions finish\n}\n```\n\nFunctions returning `IOResult` must be called repeatedly until `Done`.\n\n## Completion and CompletionGroup\n\nA `Completion` tracks a single I/O operation:\n\n```rust\npub struct Completion { /* ... */ }\n\nimpl Completion {\n    pub fn finished(&self) -> bool;\n    pub fn succeeded(&self) -> bool;\n    pub fn get_error(&self) -> Option<CompletionError>;\n}\n```\n\nTo wait for multiple I/O operations, use `CompletionGroup`:\n\n```rust\nlet mut group = CompletionGroup::new(|_| {});\n\n// Add individual completions\ngroup.add(&completion1);\ngroup.add(&completion2);\n\n// Build into single completion that finishes when all complete\nlet combined = group.build();\nio_yield_one!(combined);\n```\n\n`CompletionGroup` features:\n- Aggregates multiple completions into one\n- Calls callback when all complete (or any errors)\n- Can nest groups (add a group's completion to another group)\n- Cancellable via `group.cancel()`\n\n## Helper Macros\n\n### `return_if_io!`\nUnwraps `IOResult`, propagates IO variant up the call stack:\n```rust\nlet result = return_if_io!(some_io_operation());\n// Only reaches here if operation returned Done\n```\n\n### `io_yield_one!`\nYields a single completion:\n```rust\nio_yield_one!(completion);  // Returns Ok(IOResult::IO(Single(completion)))\n```\n\n## State Machine Pattern\n\nOperations that may yield use explicit state enums:\n\n```rust\nenum MyOperationState {\n    Start,\n    WaitingForRead { page: PageRef },\n    Processing { data: Vec<u8> },\n    Done,\n}\n```\n\nThe function loops, matching on state and transitioning:\n\n```rust\nfn my_operation(&mut self) -> Result<IOResult<Output>> {\n    loop {\n        match &mut self.state {\n            MyOperationState::Start => {\n                let (page, completion) = start_read();\n                self.state = MyOperationState::WaitingForRead { page };\n                io_yield_one!(completion);\n            }\n            MyOperationState::WaitingForRead { page } => {\n                let data = page.get_contents();\n                self.state = MyOperationState::Processing { data: data.to_vec() };\n                // No yield, continue loop\n            }\n            MyOperationState::Processing { data } => {\n                let result = process(data);\n                self.state = MyOperationState::Done;\n                return Ok(IOResult::Done(result));\n            }\n            MyOperationState::Done => unreachable!(),\n        }\n    }\n}\n```\n\n## Re-Entrancy: The Critical Pitfall\n\n**State mutations before yield points cause bugs on re-entry.**\n\n### Wrong\n```rust\nfn bad_example(&mut self) -> Result<IOResult<()>> {\n    self.counter += 1;  // Mutates state\n    return_if_io!(something_that_might_yield());  // If yields, re-entry will increment again!\n    Ok(IOResult::Done(()))\n}\n```\n\nIf `something_that_might_yield()` returns `IO`, caller waits for completion, then calls `bad_example()` again. `counter` gets incremented twice (or more).\n\n### Correct: Mutate After Yield\n```rust\nfn good_example(&mut self) -> Result<IOResult<()>> {\n    return_if_io!(something_that_might_yield());\n    self.counter += 1;  // Only reached once, after IO completes\n    Ok(IOResult::Done(()))\n}\n```\n\n### Correct: Use State Machine\n```rust\nenum State { Start, AfterIO }\n\nfn good_example(&mut self) -> Result<IOResult<()>> {\n    loop {\n        match self.state {\n            State::Start => {\n                // Don't mutate shared state here\n                self.state = State::AfterIO;\n                return_if_io!(something_that_might_yield());\n            }\n            State::AfterIO => {\n                self.counter += 1;  // Safe: only entered once\n                return Ok(IOResult::Done(()));\n            }\n        }\n    }\n}\n```\n\n## Common Re-Entrancy Bugs\n\n| Pattern | Problem |\n|---------|---------|\n| `vec.push(x); return_if_io!(...)` | Vec grows on each re-entry |\n| `idx += 1; return_if_io!(...)` | Index advances multiple times |\n| `map.insert(k,v); return_if_io!(...)` | Duplicate inserts or overwrites |\n| `flag = true; return_if_io!(...)` | Usually ok, but check logic |\n\n## State Enum Design\n\nEncode progress in state variants:\n\n```rust\n// Good: index is part of state, preserved across yields\nenum ProcessState {\n    Start,\n    ProcessingItem { idx: usize, items: Vec<Item> },\n    Done,\n}\n\n// Loop advances idx only when transitioning states\nProcessingItem { idx, items } => {\n    return_if_io!(process_item(&items[idx]));\n    if idx + 1 < items.len() {\n        self.state = ProcessingItem { idx: idx + 1, items };\n    } else {\n        self.state = Done;\n    }\n}\n```\n\n## Turso Implementation\n\nKey files:\n- `core/types.rs` - `IOResult`, `IOCompletions`, `return_if_io!`, `return_and_restore_if_io!`\n- `core/io/completions.rs` - `Completion`, `CompletionGroup`\n- `core/util.rs` - `io_yield_one!` macro\n- `core/state_machine.rs` - Generic `StateMachine` wrapper\n- `core/storage/btree.rs` - Many state machine examples\n- `core/storage/pager.rs` - `CompletionGroup` usage examples\n\n## Testing Async Code\n\nRe-entrancy bugs often only manifest under specific IO timing. Use:\n- Deterministic simulation (`testing/simulator/`)\n- Whopper concurrent DST (`testing/concurrent-simulator/`)\n- Fault injection to force yields at different points\n\n## References\n\n- `docs/manual.md` section on I/O\n"
  },
  {
    "path": "docs/agent-guides/code-quality.md",
    "content": "---\nname: code-quality\ndescription: Correctness rules, Rust patterns, comments, avoiding over-engineering\n---\n# Code Quality Guide\n\n## Core Principle\n\nProduction database. Correctness paramount. Crash > corrupt.\n\n## Correctness Rules\n\n1. **No workarounds or quick hacks.** Handle all errors, check invariants\n2. **Assert often.** Never silently fail or swallow edge cases\n3. **Crash on invalid state** if it risks data integrity. Don't continue in undefined state\n4. **Consider edge cases.** On long enough timeline, all possible bugs will happen\n\n## Rust Patterns\n\n- Make illegal states unrepresentable\n- Exhaustive pattern matching\n- Prefer enums over strings/sentinels\n- Minimize heap allocations\n- Write CPU-friendly code (microsecond = long time)\n\n### Avoid `unwrap()`\n\nNever use bare `unwrap()`. Instead:\n\n**For truly unreachable states** (invariants that should never be violated):\n```rust\n// Good: documents the invariant\nlet value = option.expect(\"value must be set in Init phase\");\n```\n\n**For recoverable errors** (states that could happen at runtime):\n```rust\n// Good: proper error handling\nlet Some(value) = option else {\n    return Err(LimboError::InvalidArgument(\"value not provided\".into()));\n};\n```\n\nThe choice depends on whether the None/Err case represents:\n- A bug in the code (use `expect` with descriptive message)\n- A valid runtime condition (use `let ... else` or `match`)\n\n## If-Statements\n\nWrong:\n```rust\nif condition {\n    // happy path\n} else {\n    // \"shouldn't happen\" - silently ignored\n}\n```\n\nRight:\n```rust\n// If only one branch should ever be hit:\nassert!(condition, \"invariant violated: ...\");\n// OR\nreturn Err(LimboError::InternalError(\"unexpected state\".into()));\n// OR\nunreachable!(\"impossible state: ...\");\n```\n\nUse if-statements only when both branches are expected paths.\n\n## Comments\n\n**Do:**\n- Document WHY, not what\n- Document functions, structs, enums, variants\n- Focus on why something is necessary\n- Preserve explanatory comments when refactoring\n\n**Don't:**\n- Comments that repeat code\n- References to AI conversations (\"This test should trigger the bug\")\n- Temporal markers (\"added\", \"existing code\", \"Phase 1\")\n\n## Avoid Over-Engineering\n\n- Only changes directly requested or clearly necessary\n- Don't add features beyond what's asked\n- Don't add docstrings/comments to unchanged code\n- Don't add error handling for impossible scenarios\n- Don't create abstractions for one-time operations\n- Three similar lines > premature abstraction\n\n## Ensure understanding of IO model\n\n- [Async IO model](./async-io-model.md)\n\n## Cleanup\n\n- Delete unused code completely\n- No backwards-compat hacks (renamed `_vars`, re-exports, `// removed` comments)\n"
  },
  {
    "path": "docs/agent-guides/debugging.md",
    "content": "---\nname: debugging\ndescription: Bytecode comparison, logging, ThreadSanitizer, deterministic simulation\n---\n# Debugging Guide\n\n## Bytecode Comparison Flow\n\nTurso aims for SQLite compatibility. When behavior differs:\n\n```\n1. EXPLAIN query in sqlite3\n2. EXPLAIN query in tursodb\n3. Compare bytecode\n   ├─ Different → bug in code generation\n   └─ Same but results differ → bug in VM or storage layer\n```\n\n### Example\n\n```bash\n# SQLite\nsqlite3 :memory: \"EXPLAIN SELECT 1 + 1;\"\n\n# Turso\ncargo run --bin tursodb :memory: \"EXPLAIN SELECT 1 + 1;\"\n```\n\n## Manual Query Inspection\n\n```bash\ncargo run --bin tursodb :memory: 'SELECT * FROM foo;'\ncargo run --bin tursodb :memory: 'EXPLAIN SELECT * FROM foo;'\n```\n\n## Logging\n\n```bash\n# Trace core during tests\nRUST_LOG=none,turso_core=trace make test\n\n# Output goes to testing/test.log\n# Warning: can be megabytes per test run\n```\n\n## Threading Issues\n\nUse stress tests with ThreadSanitizer:\n\n```bash\nrustup toolchain install nightly\nrustup override set nightly\ncargo run -Zbuild-std --target x86_64-unknown-linux-gnu \\\n  -p turso_stress -- --vfs syscall --nr-threads 4 --nr-iterations 1000\n```\n\n## Deterministic Simulation\n\nReproduce bugs with seed. Note: simulator uses legacy \"limbo\" naming.\n\n```bash\n# Simulator\nRUST_LOG=limbo_sim=debug cargo run --bin limbo_sim -- -s <seed>\n\n# Whopper (concurrent DST)\nSEED=1234 ./testing/concurrent-simulator/bin/run\n```\n\n## Architecture Reference\n\n- **Parser** → AST from SQL strings\n- **Code generator** → bytecode from AST\n- **Virtual machine** → executes SQLite-compatible bytecode\n- **Storage layer** → B-tree operations, paging\n"
  },
  {
    "path": "docs/agent-guides/mvcc.md",
    "content": "---\nname: mvcc\ndescription: MVCC feature - snapshot isolation, versioning, limitations\n---\n# MVCC Guide (Experimental)\n\nMulti-Version Concurrency Control. **Work in progress, not production-ready.**\n\n**CRITICAL**: Ignore MVCC when debugging unless the bug is MVCC-specific.\n\n## Enabling MVCC\n\n```sql\nPRAGMA journal_mode = 'mvcc';\n```\n\nRuntime configuration, not a compile-time feature flag. Per-database setting.\n\n## How It Works\n\nStandard WAL: single version per page, readers see snapshot at read mark time.\n\nMVCC: multiple row versions, snapshot isolation. Each transaction sees consistent snapshot at begin time.\n\n### Key Differences from WAL\n\n| Aspect | WAL | MVCC |\n|--------|-----|------|\n| Write granularity | Every commit writes full pages | Affected rows only\n| Readers/Writers | Don't block each other | Don't block each other |\n| Persistence | `.db-wal` | `.db-log` (logical log) |\n| Isolation | Snapshot (page-level) | Snapshot (row-level) |\n\n### Versioning\n\nEach row version tracks:\n- `begin` - timestamp when visible\n- `end` - timestamp when deleted/replaced\n- `btree_resident` - existed before MVCC enabled\n\n## Architecture\n\n```\nDatabase\n  └─ mv_store: MvStore\n      ├─ rows: SkipMap<RowID, Vec<RowVersion>>\n      ├─ txs: SkipMap<TxID, Transaction>\n      ├─ Storage (.db-log file)\n      └─ CheckpointStateMachine\n```\n\n**Per-connection**: `mv_tx` tracks current MVCC transaction.\n\n**Shared**: `MvStore` with lock-free `crossbeam_skiplist` structures.\n\n## Key Files\n\n- `core/mvcc/mod.rs` - Module overview\n- `core/mvcc/database/mod.rs` - Main implementation (~3000 lines)\n- `core/mvcc/cursor.rs` - Merged MVCC + B-tree cursor\n- `core/mvcc/persistent_storage/logical_log.rs` - Disk format\n- `core/mvcc/database/checkpoint_state_machine.rs` - Checkpoint logic\n\n## Checkpointing\n\nFlushes row versions to B-tree periodically.\n\n```sql\nPRAGMA mvcc_checkpoint_threshold = <pages>;\n```\n\nProcess: acquire lock → begin pager txn → write rows → commit → truncate log → fsync → release.\n\n## Current Limitations\n\n**Not implemented:**\n- Garbage collection (old versions accumulate)\n- Recovery from logical log on restart\n\n**Known issues:**\n- Checkpoint blocks other transactions, even reads!\n- No spilling to disk; memory use concerns\n\n## Testing\n\n```bash\n# Run MVCC-specific tests\ncargo test mvcc\n\n# TCL tests with MVCC\nmake test-mvcc\n```\n\nUse `#[turso_macros::test(mvcc)]` attribute for MVCC-enabled tests.\n\n```rust\n#[turso_macros::test(mvcc)]\nfn test_something() {\n    // runs with MVCC enabled\n}\n```\n\n## References\n\n- `core/mvcc/mod.rs` documents data anomalies (dirty reads, lost updates, etc.)\n- Snapshot isolation vs serializability: MVCC provides the former, not the latter\n"
  },
  {
    "path": "docs/agent-guides/pr-workflow.md",
    "content": "---\nname: pr-workflow\ndescription: Commits, formatting, CI, dependencies, security\n---\n# PR Workflow Guide\n\n## Commit Practices\n\n- **Atomic commits.** Small, focused, single purpose\n- **Don't mix:** logic + formatting, logic + refactoring\n- **Good message** = easy to write short description of intent\n\nLearn `git rebase -i` for clean history.\n\n## PR Guidelines\n\n- Keep PRs focused and small\n- Run relevant tests before submitting\n- Each commit tells part of the story\n\n## CI Environment Notes\n\nIf running as GitHub Action:\n- Max-turns limit in `.github/workflows/claude.yml`\n- OK to commit WIP state and push\n- OK to open WIP PR and continue in another action\n- Don't spiral into rabbit holes. Stay focused on key task\n\n## Security\n\nNever commit:\n- `.env` files\n- Credentials\n- Secrets\n\n## Third-Party Dependencies\n\nWhen adding:\n1. Add license file under `licenses/`\n2. Update `NOTICE.md` with dependency info\n\n## External APIs/Tools\n\n- Never guess API params or CLI args\n- Search official docs first\n- Ask for clarification if ambiguous\n"
  },
  {
    "path": "docs/agent-guides/storage-format.md",
    "content": "---\nname: storage-format\ndescription: SQLite file format, B-trees, pages, cells, overflow, freelist\n---\n# Storage Format Guide\n\n## Database File Structure\n\n```\n┌─────────────────────────────┐\n│ Page 1: Header + Schema     │  ← First 100 bytes = DB header\n├─────────────────────────────┤\n│ Page 2..N: B-tree pages     │  ← Tables and indexes\n│            Overflow pages   │\n│            Freelist pages   │\n└─────────────────────────────┘\n```\n\nPage size: power of 2, 512-65536 bytes. Default 4096.\n\n## Database Header (First 100 Bytes)\n\n| Offset | Size | Field |\n|--------|------|-------|\n| 0 | 16 | Magic: `\"SQLite format 3\\0\"` |\n| 16 | 2 | Page size (big-endian) |\n| 18 | 1 | Write format version (1=rollback, 2=WAL) |\n| 19 | 1 | Read format version |\n| 24 | 4 | Change counter |\n| 28 | 4 | Database size in pages |\n| 32 | 4 | First freelist trunk page |\n| 36 | 4 | Total freelist pages |\n| 40 | 4 | Schema cookie |\n| 56 | 4 | Text encoding (1=UTF8, 2=UTF16LE, 3=UTF16BE) |\n\nAll multi-byte integers: **big-endian**.\n\n## Page Types\n\n| Flag | Type | Purpose |\n|------|------|---------|\n| 0x02 | Interior index | Index B-tree internal node |\n| 0x05 | Interior table | Table B-tree internal node |\n| 0x0a | Leaf index | Index B-tree leaf |\n| 0x0d | Leaf table | Table B-tree leaf |\n| - | Overflow | Payload exceeding cell capacity |\n| - | Freelist | Unused pages (trunk or leaf) |\n\n## B-tree Structure\n\nTwo B-tree types:\n- **Table B-tree**: 64-bit rowid keys, stores row data\n- **Index B-tree**: Arbitrary keys (index columns + rowid)\n\n```\nInterior page:  [ptr0] key1 [ptr1] key2 [ptr2] ...\n                   │         │         │\n                   ▼         ▼         ▼\n               child     child     child\n               pages     pages     pages\n\nLeaf page:     key1:data  key2:data  key3:data ...\n```\n\nPage 1 always root of `sqlite_schema` table.\n\n## Cell Format\n\n### Table Leaf Cell\n```\n[payload_size: varint] [rowid: varint] [payload] [overflow_ptr: u32?]\n```\n\n### Table Interior Cell\n```\n[left_child_page: u32] [rowid: varint]\n```\n\n### Index Cells\nSimilar but key is arbitrary (columns + rowid), not just rowid.\n\n## Record Format (Payload)\n\n```\n[header_size: varint] [type1: varint] [type2: varint] ... [data1] [data2] ...\n```\n\nSerial types:\n| Type | Meaning |\n|------|---------|\n| 0 | NULL |\n| 1-4 | 1/2/3/4 byte signed int |\n| 5 | 6 byte signed int |\n| 6 | 8 byte signed int |\n| 7 | IEEE 754 float |\n| 8 | Integer 0 |\n| 9 | Integer 1 |\n| ≥12 even | BLOB, length=(N-12)/2 |\n| ≥13 odd | Text, length=(N-13)/2 |\n\n## Overflow Pages\n\nWhen payload exceeds threshold, excess stored in overflow chain:\n```\n[next_page: u32] [data...]\n```\nLast page has next_page=0.\n\n## Freelist\n\nLinked list of trunk pages, each containing leaf page numbers:\n```\nTrunk: [next_trunk: u32] [leaf_count: u32] [leaf_pages: u32...]\n```\n\n## Turso Implementation\n\nKey files:\n- `core/storage/sqlite3_ondisk.rs` - On-disk format, `PageType` enum\n- `core/storage/btree.rs` - B-tree operations (large file)\n- `core/storage/pager.rs` - Page management\n- `core/storage/buffer_pool.rs` - Page caching\n\n## Debugging Storage\n\n```bash\n# Integrity check\ncargo run --bin tursodb test.db \"PRAGMA integrity_check;\"\n\n# Page count\ncargo run --bin tursodb test.db \"PRAGMA page_count;\"\n\n# Freelist info\ncargo run --bin tursodb test.db \"PRAGMA freelist_count;\"\n```\n\n## References\n\n- [SQLite File Format](https://sqlite.org/fileformat.html)\n- [SQLite B-Tree Module](https://sqlite.org/btreemodule.html)\n- [SQLite Internals: Pages & B-trees](https://fly.io/blog/sqlite-internals-btree/)\n"
  },
  {
    "path": "docs/agent-guides/testing.md",
    "content": "---\nname: testing\ndescription: Test types, when to use each, how to write and run tests\n---\n# Testing Guide\n\n## Test Types & When to Use\n\n| Type | Location | Use Case |\n|------|----------|----------|\n| `.sqltest` | `testing/sqltests/tests/` | SQL compatibility. **Preferred for new tests** |\n| TCL `.test` | `testing/` | Legacy SQL compat (being phased out) |\n| Rust integration | `tests/integration/` | Regression tests, complex scenarios |\n| Fuzz | `tests/fuzz/` | Complex features, edge case discovery |\n\n**Note:** TCL tests are being phased out in favor of testing/sqltests. The `.sqltest` format allows the same test cases to run against multiple backends (CLI, Rust bindings, etc.).\n\n## Running Tests\n\n```bash\n# Main test suite (TCL compat, sqlite3 compat, Python wrappers)\nmake test\n\n# Single TCL test\nmake test-single TEST=select.test\n\n# SQL test runner\nmake -C testing/sqltests run-cli\n\n# Rust unit/integration tests (full workspace)\ncargo test\n```\n\n## Writing Tests\n\n### .sqltest (Preferred)\n```sql\n@database :memory:\n\n@query\nSELECT 1 + 1;\n@expected\n2\n```\nLocation: `testing/sqltests/tests/*.sqltest`\n\n### TCL\n```tcl\ndo_execsql_test_on_specific_db {:memory:} test-name {\n  SELECT 1 + 1;\n} {2}\n```\nLocation: `testing/*.test`\n\n### Rust Integration\n```rust\n// tests/integration/test_foo.rs\n#[test]\nfn test_something() {\n    let conn = Connection::open_in_memory().unwrap();\n    // ...\n}\n```\n\n## Key Rules\n\n- Every functional change needs a test\n- Test must fail without change, pass with it\n- Prefer in-memory DBs: `:memory:` (sqltest) or `{:memory:}` (TCL)\n- Don't invent new test formats. Follow existing patterns\n- Write tests first when possible\n\n## Test Database Schema\n\n`testing/testing.db` has `users` and `products` tables. See [docs/testing.md](../testing.md) for schema.\n\n## Logging During Tests\n\n```bash\nRUST_LOG=none,turso_core=trace make test\n```\nOutput: `testing/test.log`. Warning: very verbose.\n"
  },
  {
    "path": "docs/agent-guides/transaction-correctness.md",
    "content": "---\nname: transaction-correctness\ndescription: WAL mechanics, checkpointing, concurrency rules, recovery\n---\n# Transaction Correctness Guide\n\nTurso uses WAL (Write-Ahead Logging) mode exclusively.\n\nFiles: `.db`, `.db-wal` (no `.db-shm` - Turso uses in-memory WAL index)\n\n## WAL Mechanics\n\n### Write Path\n1. Writer appends frames (page data) to WAL file (sequential I/O)\n2. COMMIT = frame with non-zero db_size in header (marks transaction end)\n3. Original DB unchanged until checkpoint\n\n### Read Path\n1. Reader acquires read mark (mxFrame = last valid commit frame)\n2. For each page: check WAL up to mxFrame, fall back to main DB\n3. Reader sees consistent snapshot at its read mark\n\n### Checkpointing\nTransfers WAL content back to main DB.\n\n```\nWAL grows → checkpoint triggered (default: 1000 pages) → pages copied to DB → WAL reused\n```\n\nCheckpoint types:\n- **PASSIVE**: Non-blocking, stops at pages needed by active readers\n- **FULL**: Waits for readers, checkpoints everything\n- **RESTART**: Like FULL, also resets WAL to beginning\n- **TRUNCATE**: Like RESTART, also truncates WAL file to zero length\n\n### WAL-Index\nSQLite uses a shared memory file (`-shm`) for WAL index. **Turso does not** - it uses in-memory data structures (`frame_cache` hashmap, atomic read marks) since multi-process access is not supported.\n\n## Concurrency Rules\n\n- One writer at a time\n- Readers don't block writer, writer doesn't block readers\n- Checkpoint must stop at pages needed by active readers\n\n## Recovery\n\nOn crash:\n1. First connection acquires exclusive lock\n2. Replays valid commits from WAL\n3. Releases lock, normal operation resumes\n\n## Turso Implementation\n\nKey files:\n- `core/storage/wal.rs` - WAL implementation\n- `core/storage/pager.rs` - Page management, transactions\n\n### Connection-Private vs Shared\n\n**Per-Connection (private):**\n- `Pager` - page cache, dirty pages, savepoints, commit state\n- `WalFile` - connection's snapshot view:\n  - `max_frame` / `min_frame` - frame range for this connection's snapshot\n  - `max_frame_read_lock_index` - which read lock slot this connection holds\n  - `last_checksum` - rolling checksum state\n\n**Shared across connections:**\n- `WalFileShared` - global WAL state:\n  - `frame_cache` - page-to-frame index (replaces `.shm` file)\n  - `max_frame` / `nbackfills` - global WAL progress\n  - `read_locks[5]` - read mark slots (TursoRwLock with embedded frame values)\n  - `write_lock` - exclusive writer lock\n  - `checkpoint_lock` - checkpoint serialization\n  - `file` - WAL file handle\n- `DatabaseStorage` - main `.db` file\n- `BufferPool` - shared memory allocation\n\n## Correctness Invariants\n\n1. **Durability**: COMMIT record must be fsynced before returning success\n2. **Atomicity**: Partial transactions never visible to readers\n3. **Isolation**: Each reader sees consistent snapshot\n4. **No lost updates**: Checkpoint can't overwrite uncommitted changes\n\n## References\n\n- [SQLite WAL](https://sqlite.org/wal.html)\n- [WAL File Format](https://sqlite.org/walformat.html)\n"
  },
  {
    "path": "docs/contributing/contributing_functions.md",
    "content": "# How to contribute a SQL function implementation?\n\nSteps\n1. Pick a `SQL functions` in [COMPAT.md](../../COMPAT.md) file with a No (not implemented yet) status.\n2. Create an issue for that function.\n3. Implement the function in a feature branch.\n4. Push it as a Merge Request, get it review.\n\nSample Pull Requests of function contributing\n- [partial support for datetime() and julianday()](https://github.com/tursodatabase/turso/pull/600)\n- [support for changes() and total_changes()](https://github.com/tursodatabase/turso/pull/589)\n- [support for unhex(X)](https://github.com/tursodatabase/turso/pull/353)\n\n## An example with function `date(..)`\n\n> Note that the files, code location, steps might be not exactly the same because of refactor but the idea of the changes needed in each layer stays.\n\n[Issue #158](https://github.com/tursodatabase/turso/issues/158) was created for it.\nRefer to commit [4ff7058](https://github.com/tursodatabase/turso/commit/4ff705868a054643f6113cbe009655c32bc5f235).\n\n![limbo_architecture.png](limbo_architecture.png)\n\nTo add a function we generally need to touch at least the following modules\n- SQL Command Processor\n  - The `SQL Command Processor` module is responsible for turning sql function string into a sequence of instructions to be executed by the `Virtual Machine` module.\n  - we need the following things: function definition, how the `bytecode generator` in `core/translate` generates bytecode program for this function to be executed.\n- Virtual Machine `core/vdbe`\n  - we need to add logic of how the `vdbe` should execute the logic of this function in Rust and write result to destination register of the vm.\n  - [more info](https://www.sqlite.org/opcode.html)\n- Tests\n\n```\nSQL function string \n--Tokenizer and Parser--> \nAST (enum Func)\n--Bytecode Generator (core/translate)-->\nBytecode Instructions \n--Virtual Machine--> \nResult\n```\n\nTODO for implementing the function:\n- analysis\n  - read and try out how the function works in SQLite.\n  - compare `explain` output of SQLite and Limbo.\n- add/ update the function definition in `functions.rs`.\n- add/ update how to function is translated from `definition` to `instruction` in virtual machine layer VDBE.\n- add/ update the function Rust execution code and tests in vdbe layer.\n- add/ update how the bytecode `Program` executes when steps into the function.\n- add/ update TCL tests for this function in limbo/testing.\n- update doc for function compatibility.\n\n### Analysis\n\nHow `date` works in SQLite?\n```bash\n> sqlite3\n\nsqlite> explain select date('now');\naddr  opcode         p1    p2    p3    p4             p5  comment\n----  -------------  ----  ----  ----  -------------  --  -------------\n0     Init           0     6     0                    0   Start at 6\n1     Once           0     3     0                    0\n2     Function       0     0     2     date(-1)       0   r[2]=func()\n3     Copy           2     1     0                    0   r[1]=r[2]\n4     ResultRow      1     1     0                    0   output=r[1]\n5     Halt           0     0     0                    0\n6     Goto           0     1     0                    0\n```\n\nComparing that with `Limbo`:\n```bash\n# created a sqlite database file database.db\n# or cargo run to use the memory mode if it is already available.\n> cargo run database.db\n\nEnter \".help\" for usage hints.\nlimbo> explain select date('now');\nParse error: unknown function date\n```\n\nWe can see that the function is not implemented yet so the Parser did not understand it and throw an error `Parse error: unknown function date`.\n- we only need to pay attention to opcode `Function` at addr 2. The rest is already set up in limbo.\n- we have up to 5 registers p1 to p5 for each opcode.\n\n### Function definition\n\nFor limbo to understand the meaning of `date`, we need to define it as a Function somewhere.\nThat place can be found currently in `core/functions.rs`. We need to edit 3 places\n1. add to ScalarFunc as `date` is a scalar function. \n```diff\n// file core/functions.rs\npub enum ScalarFunc {\n  // other funcs...\n  Soundex,\n+ Date,\n  Time,\n  // other funcs...\n}\n```\n2. add to Display to show the function as string in our program.\n```diff\n// file core/functions.rs\nimpl Display for ScalarFunc {\n  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n    let str = match self {\n      // ...\n      ScalarFunc::Soundex => \"soundex\".to_string(),\n+     ScalarFunc::Date => \"date\".to_string(),\n      ScalarFunc::Time => \"time\".to_string(),\n      // ...\n}\n```\n3. add to `fn resolve_function(..)` of `impl Func` to enable parsing from str to this function.\n```diff \n// file core/functions.rs\nimpl Func {\n  pub fn resolve_function(name: &str, arg_count: usize) -> Result<Func, ()> {\n    match name {\n      // ...\n+     \"date\" => Ok(Func::Scalar(ScalarFunc::Date)),\n      // ...\n}\n```\n\n### Function translation\n\nHow to translate the function into bytecode `Instruction`?\n- `date` function can have zero to many arguments.\n- in case there are arguments, we loop through the args and allocate a register `let target_reg = program.alloc_register();`\nfor each argument expression.\n- then we emit the bytecode instruction for Function `program.emit_insn(Insn::Function {...})`\n\nhttps://github.com/tursodatabase/turso/blob/69e3dd28f77e59927da4313e517b2b428ede480d/core/translate/expr.rs#L1235C1-L1256C26\n\n\n```diff\n// file core/translate/expr.rs\npub fn translate_expr(...) -> Result<usize> {\n  // ...\n  match expr {\n    // ..\n    ast::Expr::FunctionCall {\n      // ...\n      match &func_ctx.func {\n        // ...\n        Func::Scalar(srf) => {\n          // ...\n+          ScalarFunc::Date => {\n+              if let Some(args) = args {\n+                  for arg in args.iter() {\n+                      // register containing result of each argument expression\n+                      let target_reg = program.alloc_register();\n+                      _ = translate_expr(\n+                          program,\n+                          referenced_tables,\n+                          arg,\n+                          target_reg,\n+                          precomputed_exprs_to_registers,\n+                      )?;\n+                  }\n+              }\n+              program.emit_insn(Insn::Function {\n+                  constant_mask: 0,\n+                  start_reg: target_register + 1,\n+                  dest: target_register,\n+                  func: func_ctx,\n+              });\n+              Ok(target_register)\n+          }\n// ...\n```\n\n### Function execution\n\nThe function execution code is implemented in `vdbe/datetime.rs` file [here](https://github.com/tursodatabase/turso/commit/9cc965186fecf4ba4dd81c783a841c71575123bf#diff-839435241d4ffb648ad2d162bc6ba6a94f052309865251dc2aff36eaa14fa3c5R11-R30) as we already implemented the datetime features in this file. \nNote that for other functions it might be implemented in other location in vdbe module.\n\n```diff\n// file vdbe/datetime.rs\n// ...\n+ pub fn exec_date(values: &[Value]) -> Value {\n+   // ... implementation\n+ }\n\n// ...\n```\n\n### Program bytecode execution\n\nNext step is to implement how the virtual machine (VDBE layer) executes the bytecode `Program` when the program step into the function instruction `Insn::Function` date `ScalarFunc::Date`.\n\nPer [SQLite spec](https://www.sqlite.org/lang_datefunc.html#time_values) if there is no `time value` (no start register) , we want to execute the function with default param `'now'`.\n>  In all functions other than timediff(), the time-value (and all modifiers) may be omitted, in which case a time value of 'now' is assumed.\n\n```diff\n// file vdbe/mod.rs\nimpl Program {\n  pub fn step<'a>(...) {\n    loop {\n      // ...\n      match isin {\n        // ...\n        Insn::Function {\n          // ...\n+          ScalarFunc::Date => {\n+              let result =\n+                  exec_date(&state.registers[*start_reg..*start_reg + arg_count]);\n+              state.registers[*dest] = result;\n+          }\n          // ...\n```\n\n### Adding tests\n\nThere are 2 kind of tests we need to add\n1. tests for Rust code\n2. TCL tests for executing the sql function\n\nOne test for the Rust code is shown as example below\nhttps://github.com/tursodatabase/turso/blob/69e3dd28f77e59927da4313e517b2b428ede480d/core/vdbe/datetime.rs#L620C1-L661C1\n\nTCL tests for `date` functions can be referenced from SQLite source code which is already very comprehensive. \n- https://github.com/sqlite/sqlite/blob/f2b21a5f57e1a1db1a286c42af40563077635c3d/test/date3.test#L36\n- https://github.com/sqlite/sqlite/blob/f2b21a5f57e1a1db1a286c42af40563077635c3d/test/date.test#L611C1-L652C73\n\n### Updating doc\n\nUpdate the [COMPAT.md](../../COMPAT.md) file to mark this function as implemented. Change Status to \n- `Yes` if it is fully supported, \n- `Partial` if supported but not fully yet compared to SQLite.\n\nAn example:\n```diff\n// file COMPAT.md\n| Function                     | Status  | Comment                      |\n|------------------------------|---------|------------------------------|\n- | date()                     | No      |                              |\n+ | date()                     | Yes     | partially supports modifiers |\n...\n```\n"
  },
  {
    "path": "docs/fts.md",
    "content": "# Full-Text Search in tursodb\n\nThis proposal is to use the https://github.com/quickwit-oss/tantivy library to provide full-text search capabilities in tursodb. \n\n# Tantivy overview\n\n*introduction and terminology*:\n\n**Term:** a normalized token extracted from text during indexing.\n\n**Posting list:** (also referred to as an *inverted list*) is the list of all documents that contain a given term, along with metadata needed for scoring the results.\n\n**Document:** Tantivy’s unit of indexing and retrieval, analogous to a single row in a db table.\n\n**Field:** Analogous to a column in a table\n\n**Position:** The sequential offset/token index of a term within a field.\n\n**Segment:**  self-contained and immutable chunk of the index. Each commit writes one or more new segments to disk, searches query all active segments in parallel.\n\nInside each segment:\n\n- A term dictionary lists every term in that segment.\n- For each term, the posting list maps to documents.\n- Stored fields (original text) are compressed for retrieval.\n- Deletes are stored as a separate bitmap until the next merge. Merges rewrite new segments without the deleted docs.\n\nMore details on everything here: https://fulmicoton.gitbooks.io/tantivy-doc/content/step-by-step.html\n\n### Note: this blog post here is a bit old but is a very interesting read. https://fulmicoton.com/posts/behold-tantivy-part2/\n\nAt a high level, a Tantivy index is a self-contained directory containing immutable “segments” of inverted index data file. \n\n*“Your index is in fact divided into smaller independent indexes called **segments**. The reason for this division is explained in [Incremental Indexing](https://fulmicoton.gitbooks.io/tantivy-doc/content/incremental-indexing.html). The UUID part stands as the segment name, while the extension express which data-structure is stored in the file.*\n\n- ***.info** contains some meta information about the segment*\n- ***.term** contains the term dictionary*\n- ***.idx** contains the inverted lists*\n- ***.fieldnorm** contains the field norms*\n- ***.pos** contains the positions information*\n- ***.store** contains the stored documents*\n- ***.fas**t contains the fast terms*\n\n*The last file, meta.json, contains in a JSON format :*\n\n- *The schema of your index*\n- *The name of the segments that were committed and are available for search.”*\n\nsource: https://fulmicoton.gitbooks.io/tantivy-doc/content/index-files.html\n\n### Writing / indexing\n\n- Applications obtain a single `IndexWriter`, which owns the memory arena and background worker threads.\n- Each call to `add_document()` tokenizes and indexes the text fields into in-memory postings.\n- When the writer’s memory budget is full or `commit()` is called, Tantivy serializes a new segment (posting lists, term dictionary, stored field data) and updates the `meta.json` (all these segments are Write-once, Read-many, with the exception of the per-index meta.json).\n- `commit()` is atomic: either the new segment is visible or not at all.\n- `delete_term()` records tombstones that are applied at merge time (all updates done are just “delete + insert”).\n\n### Storage abstraction\n\nTantivy’s I/O is implemented through the `Directory` trait (https://github.com/quickwit-oss/tantivy/blob/dabcaa58093a3f7f10e98a5a3b06cfe2370482f9/src/directory/directory.rs#L107), which is kinda similar to our own `IO` / `File` traits.\n\nTantivy includes `MmapDirectory` (mmap’d file based segments, the default option), and `RamDirectory` (in-memory, usually for testing). Obviously the default will not work for our use case, as we cannot have a whole directory created for each index with a bunch of files.\n\n### Threading and concurrency\n\n- One write per index at a time, but internally multi-threaded for indexing throughput.\n- Multiple readers can coexist and search concurrently on immutable segments.\n- Writer commits are visible to new readers only after `reader.reload()` or automatic reload.\n\n### Typical workflow\n\n1. Build a `Schema` describing indexed fields.\n2. Create or open an `Index` using a `Directory`.\n3. Acquire an `IndexWriter`, call `add_document()` or `delete_term()`.\n4. Call `commit()` to persist and publish new data.\n5. Use `IndexReader`/`Searcher` + `QueryParser` + `TopDocs` to execute searches.\n6. Fetch stored fields for displayed results.\n\n### ===========================================================\n\n## Usage:\n\nFrom the user perspective, the syntax for using full text search features in tursodb will look like the following:\n\n## DDL:\n\n```sql\nCREATE INDEX idx_posts\nON posts USING fts (title, body);\n```\n\n### Tokenizer Configuration\n\nYou can configure the tokenizer used for text processing via the `WITH` clause:\n\n```sql\n-- Use raw tokenizer for exact-match fields (IDs, tags)\nCREATE INDEX idx_tags ON articles USING fts (tag) WITH (tokenizer = 'raw');\n\n-- Use ngram tokenizer for autocomplete/substring matching\nCREATE INDEX idx_products ON products USING fts (name) WITH (tokenizer = 'ngram');\n```\n\n#### Available Tokenizers\n\n| Tokenizer | Description | Use Case |\n|-----------|-------------|----------|\n| `default` | Lowercase, punctuation split, 40 char limit | General English text |\n| `raw` | No tokenization - exact match only | IDs, UUIDs, tags, categories |\n| `simple` | Basic whitespace/punctuation split | Simple text without lowercase |\n| `whitespace` | Split on whitespace only | Space-separated tokens |\n| `ngram` | 2-3 character n-grams | Autocomplete, substring matching |\n\n### Field Weights\n\nYou can configure relative importance of indexed columns using the `weights` parameter:\n\n```sql\n-- Title matches are 2x more important than body matches\nCREATE INDEX idx_articles ON articles USING fts (title, body)\nWITH (weights = 'title=2.0,body=1.0');\n\n-- Combined with tokenizer\nCREATE INDEX idx_docs ON docs USING fts (name, description)\nWITH (tokenizer = 'simple', weights = 'name=3.0,description=1.0');\n```\n\n**Weight behavior:**\n- Default weight is `1.0` for all fields\n- Weights affect the BM25 relevance score from `fts_score()`\n- Higher weights increase the score contribution from that field\n- Weights must be positive numbers\n\n#### Tokenizer Examples\n\n```sql\n-- Default tokenizer: \"Hello World\" → [\"hello\", \"world\"]\n-- Searches for \"hello\" or \"HELLO\" will match\n\n-- Raw tokenizer: \"user-123\" → [\"user-123\"]\n-- Only exact match \"user-123\" will work, \"user\" won't match\n\n-- Ngram tokenizer: \"iPhone\" → [\"iP\", \"iPh\", \"Ph\", \"Pho\", \"ho\", \"hon\", \"on\", \"one\", \"ne\"]\n-- Search for \"Pho\" will match documents containing \"iPhone\"\n```\n\n## DQL:\n\nThree FTS functions are provided:\n\n- **`fts_score(col1, col2, ..., 'query')`** - Returns the BM25 relevance score for each matching row\n- **`fts_match(col1, col2, ..., 'query')`** - Returns a boolean indicating if the row matches (used in WHERE clause)\n- **`fts_highlight(col1, col2, ..., before_tag, after_tag, 'query')`** - Returns text with matching terms wrapped in tags\n\n`WHERE col MATCH 'query'` is available for use instead of `fts_match(col, 'query')`\n\n### Basic Query Examples\n\n```sql\n-- Get scores for matching documents, ordered by relevance\nSELECT fts_score(title, body, 'database') as score, id, title\nFROM articles\nORDER BY score DESC\nLIMIT 10;\n\n-- Simple match filter\nSELECT id, title FROM articles WHERE fts_match(body, 'science') LIMIT 10;\n```\n\n### Highlighting Search Results\n\nThe `fts_highlight` function wraps matching query terms with custom tags for display:\n\n```sql\n-- Basic highlighting (single column)\nSELECT fts_highlight('Learn about database optimization', '<b>', '</b>', 'database');\n-- Returns: \"Learn about <b>database</b> optimization\"\n\n-- Multiple columns - text is concatenated with spaces\nSELECT fts_highlight(title, body, '<mark>', '</mark>', 'database') as highlighted\nFROM articles\nWHERE fts_match(title, body, 'database');\n-- If title='Database Design' and body='Learn about optimization',\n-- Returns: \"<mark>Database</mark> Design Learn about optimization\"\n\n-- Use with FTS queries to highlight matched content\nSELECT\n    id,\n    title,\n    fts_highlight(body, '<mark>', '</mark>', 'database') as highlighted_body\nFROM articles\nWHERE fts_match(title, body, 'database')\nORDER BY fts_score(title, body, 'database') DESC;\n\n-- Multiple terms are highlighted\nSELECT fts_highlight('The quick brown fox', '<em>', '</em>', 'quick fox');\n-- Returns: \"The <em>quick</em> brown <em>fox</em>\"\n```\n\n**Features:**\n- Supports multiple text columns (concatenated with spaces)\n- Case-insensitive matching (uses the default tokenizer)\n- Highlights all occurrences of matching terms\n- Works as a standalone function (doesn't require an FTS index)\n- Returns original text if no matches found\n- Returns NULL if query, before_tag, or after_tag is NULL\n- NULL text columns are skipped (not returned as NULL)\n\n### Supported Query Patterns\n\nThe FTS index recognizes and optimizes these query patterns:\n\n| Pattern | Description |\n|---------|-------------|\n| `SELECT fts_score(...) as score FROM t ORDER BY score DESC LIMIT ?` | Score with ORDER BY and LIMIT |\n| `SELECT fts_score(...) as score FROM t WHERE fts_match(...) ORDER BY score DESC LIMIT ?` | Combined score+match with ORDER BY and LIMIT |\n| `SELECT fts_score(...) as score FROM t WHERE fts_match(...) ORDER BY score DESC` | Combined without LIMIT |\n| `SELECT fts_score(...) as score FROM t WHERE fts_match(...) LIMIT ?` | Combined with LIMIT only |\n| `SELECT fts_score(...) as score FROM t WHERE fts_match(...)` | Combined without ORDER BY or LIMIT |\n| `SELECT * FROM t WHERE fts_match(...) LIMIT ?` | Match filter with LIMIT |\n| `SELECT * FROM t WHERE fts_match(...)` | Match filter only |\n\n### Function Recognition Mode\n\nQueries that don't exactly match the predefined patterns still work via **function recognition**. When `fts_match()` or `fts_score()` functions are detected in a query, the FTS index is used even with:\n- Additional SELECT columns\n- Extra WHERE conditions (e.g., `AND category = 'tech'`)\n- ORDER BY non-score columns\n- Computed expressions\n\n```sql\n-- Complex query with extra columns and WHERE conditions\nSELECT id, author, title, category, views, fts_score(title, body, 'Rust') as score\nFROM articles\nWHERE fts_match(title, body, 'Rust')\n  AND category = 'tech'\n  AND views > 100\nORDER BY score DESC;\n\n-- ORDER BY non-score column\nSELECT id, title FROM docs WHERE fts_match(title, body, 'Rust') ORDER BY created_at DESC;\n```\n\n### Query Syntax\n\nThe query string passed to `fts_match`/`fts_score` supports Tantivy's QueryParser syntax:\n\n| Syntax | Example | Description |\n|--------|---------|-------------|\n| Single term | `database` | Match documents containing \"database\" |\n| Multiple terms (OR) | `database sql` | Match documents with \"database\" OR \"sql\" |\n| AND operator | `database AND sql` | Match documents with both terms |\n| NOT operator | `database NOT nosql` | Match \"database\" but exclude \"nosql\" |\n| Phrase search | `\"full text search\"` | Match exact phrase |\n| Prefix search | `data*` | Match terms starting with \"data\" |\n| Column filter | `title:database` | Match \"database\" only in title field |\n| Boosting | `title:database^2 body:database` | Boost title matches |\n\nThis syntax can be improved on in the future, and maybe eventually we can support some fancy elasticsearch/paradeDB syntax.\n\n### DML Operations\n\nDML statements (INSERT, UPDATE, DELETE) work automatically with FTS indexes:\n\n- **INSERT**: Documents are indexed immediately but batched for efficiency. Tantivy commits after every 1000 documents (`BATCH_COMMIT_SIZE`).\n- **UPDATE**: Implemented as DELETE + INSERT internally.\n- **DELETE**: Uses term-based tombstones that are cleaned up at merge time.\n\n```sql\n-- These trigger automatic FTS index updates\nINSERT INTO articles VALUES (1, 'Title', 'Body text');\nUPDATE articles SET body = 'New body' WHERE id = 1;\nDELETE FROM articles WHERE id = 1;\n```\n\n# Planning\n\nFor the above SELECT query, we would look up which FTS index handles `t.name` from the in-memory schema representation, construct a Tantivy query for this index with `QueryParser::parse_query(\"tursodb\")`. Tantivy then returns a list of `(score, doc_address)` pairs, which we translate each result into `(rowid, rating)` because we stored the table’s PK or rowid as a field during the indexing. Then an index lookup/SeekRowID for each of those result rows from the FtsQuery internal function fetches the `t.*` columns and no extra sort is required. \n\nIf there are additional filters on `t` (`WHERE t.created_at > ...`) we should probably prefer ‘fts-first’, then filter out results from that. Run the Tantivy query, materialize results into an ephemeral table of some sort and then discard rows failing the post-filter. TODO: expand on this a bit more\n\n# Metadata\n\nCREATE INDEX statements are already stored in the schema table, when we parse the sqlite_schema table which contains the DDL statement that created the index, we build the in-memory schema representation that allows us to send these queries to Tantivy.\n\n## Storage\n\nWe implement Tantivy’s `Directory` over our pager/B-tree but just as a regular table. We do not reinterpret Tantivy’s files, we store them exactly as Tantivy names and writes them. We should probably store the files as chunks of 256-512 kb blobs.\n\nOne table, and one index: per each FTS index\n\n- **Data table:**\n    \n    ```sql\n    CREATE TABLE fts_dir_{idx_id} ( \n      path TEXT NOT NULL, \n      chunk_no INTEGER NOT NULL,\n      bytes BLOB NOT NULL\n    );\n    ```\n    \n- **Index:**\n    \n    ```sql\n    CREATE INDEX IF NOT EXISTS idx_name ON table_name USING backing_btree (path, chunk_no, bytes)\n    ```\n \n\nUse `backing_btree` to create a BTree that stores all columns without rowid indirection\nThis allows direct cursor access with the exact key structure. This way we can use an index cursor to `SeekGE` (path, chunk_no) where chunk_no is just computed from the offset requested by `read_bytes` on the file handle.\n\n# Current Architecture: HybridBTreeDirectory\n\nThe architecture uses a hybrid approach that balances memory usage and performance:\n\n```\n┌─────────────────────────────────────────────────────────────┐\n│                    HybridBTreeDirectory                     │\n├─────────────────────────────────────────────────────────────┤\n│  File Catalog (always in memory)                            │\n│  ├── path -> FileMetadata { size, num_chunks, category }    │\n│                                                             │\n│  Hot Cache (metadata + term dictionaries)                   │\n│  ├── meta.json, .managed.json (always loaded)               │\n│  ├── .term files (loaded on first access)                   │\n│  ├── .fast, .fieldnorm (small, frequently accessed)         │\n│                                                             │\n│  Chunk LRU Cache (lazy-loaded segment data)                 │\n│  ├── .idx, .pos, .store chunks                              │\n│  └── Eviction when over memory budget                       │\n└─────────────────────────────────────────────────────────────┘\n```\n\n### File Categories\n\nFiles are classified based on their role in Tantivy operations:\n\n| Category | Files | Behavior |\n|----------|-------|----------|\n| Metadata | `meta.json`, `.managed.json`, `.lock` | Always in hot cache |\n| TermDictionary | `*.term` | Hot, loaded on first access |\n| FastFields | `*.fast`, `*.fieldnorm` | Hot, small and frequent |\n| SegmentData | `*.idx`, `*.pos`, `*.store` | Lazy-loaded on demand |\n\n### Loading Flow (Catalog-First)\n\nThe FTS cursor uses an async state machine to load the index:\n\n1. **LoadingCatalog**: Scan BTree records, building file metadata (path, max_chunk, size)\n2. **PreloadingEssentials**: Load hot files (metadata, term dicts) into memory\n3. **CreatingIndex**: Open/create Tantivy index using HybridBTreeDirectory\n4. **Ready**: Index is usable; segment data lazy-loaded on query\n\nAdditional states for write operations:\n- **FlushingWrites**: Persisting pending writes to BTree\n- **Querying**: Executing a Tantivy search\n\nThe state machine is driven by `FtsCursor` which handles the async IO integration with our pager.\n\n### Lazy Loading with Blocking Reads\n\nSince blocking reads are acceptable in the query path:\n\n```rust\nimpl FileHandle for LazyFileHandle {\n    fn read_bytes(&self, range: Range<usize>) -> io::Result<OwnedBytes> {\n        // 1. Check hot cache\n        // 2. Calculate required chunks from byte range\n        // 3. For each chunk: check LRU cache, or blocking BTree fetch\n        // 4. Assemble and return result\n    }\n}\n```\n\nThe `get_chunk_blocking` method creates a temporary BTree cursor and loops on `pager.io.step()` until the chunk is fetched.\n\n### Memory Management\n\n```rust\npub const DEFAULT_CHUNK_CACHE_BYTES: usize = 128 * 1024 * 1024; // 128MB\npub const DEFAULT_HOT_CACHE_BYTES: usize = 64 * 1024 * 1024;    // 64MB\n```\n\nThe `ChunkLruCache` evicts least-recently-used chunks when over capacity. Each FTS index has its own cache for isolation.\n\n### Key Components\n\n| Component | Purpose |\n|-----------|---------|\n| `FileCategory` | Classifies files for caching decisions |\n| `FileMetadata` | Stores file size, chunk count, category |\n| `ChunkLruCache` | Memory-bounded LRU cache for segment chunks |\n| `HybridBTreeDirectory` | Implements Tantivy's Directory trait |\n| `LazyFileHandle` | Fetches chunks on demand |\n\n### Memory Usage Comparison\n\n| Index Size | Old (CachedBTreeDirectory) | New (HybridBTreeDirectory) |\n|------------|---------------------------|---------------------------|\n| 100MB | ~100MB | ~25MB |\n| 500MB | ~500MB | ~80MB |\n| 1GB | ~1GB | ~150MB |\n\n### Configuration Constants\n\n```rust\nDEFAULT_MEMORY_BUDGET_BYTES  = 64 MB   // Tantivy IndexWriter memory budget\nDEFAULT_CHUNK_SIZE           = 1 MB    // BTree blob chunk size\nDEFAULT_HOT_CACHE_BYTES      = 64 MB   // Bounded LRU cache for metadata/term dicts\nDEFAULT_CHUNK_CACHE_BYTES    = 128 MB  // Bounded LRU cache for segment chunks\nBATCH_COMMIT_SIZE            = 1000    // Documents per Tantivy commit\n```\n\nBoth the hot cache and chunk cache use approximate LRU eviction to stay within their memory budgets, preventing unbounded memory growth with many FTS indexes.\n\n---\n\n# Index Maintenance\n\n## OPTIMIZE INDEX\n\nThe `OPTIMIZE INDEX` command merges all Tantivy segments into a single segment, which can improve query performance and reduce storage overhead. This is especially useful after bulk inserts that create many small segments.\n\n```sql\n-- Optimize a specific FTS index\nOPTIMIZE INDEX fts_articles;\n\n-- Optimize all FTS indexes in the database\nOPTIMIZE INDEX;\n```\n\n**When to use:**\n- After bulk data imports (many INSERTs)\n- When you notice query performance degradation\n- During maintenance windows\n\n**What it does:**\n- Merges all segments into one optimized segment\n- Flushes any pending documents first\n- Invalidates caches to ensure fresh reads\n- Works within normal transaction semantics\n\n**Note:** Optimization can take time on large indexes. For very large indexes with millions of documents, consider running this during off-peak hours.\n\n---\n\n# Current Limitations\n\nThe FTS implementation has some known limitations that may be addressed in future versions:\n\n| Limitation | Description |\n|------------|-------------|\n| No `snippet()` function | Cannot return context snippets around matches (highlight is available) |\n| No automatic segment merging | Uses `NoMergePolicy` - use `OPTIMIZE INDEX` for manual segment merging |\n| No read-your-writes in transaction | FTS changes within a transaction aren't visible to queries until COMMIT |\n| No MATCH operator syntax | Must use `fts_match()` function instead of `WHERE table MATCH 'query'` |\n\n**Note on transactions:** ROLLBACK works correctly - FTS data is stored in BTrees that participate in the same WAL transaction as table data. When a transaction is rolled back, both table changes and FTS index changes are discarded together.\n\n# Comparison with SQLite FTS5\n\n| Feature | SQLite FTS5 | tursodb FTS |\n|---------|-------------|-------------|\n| MATCH operator | `WHERE t MATCH 'query'` | `WHERE fts_match(col, 'query')` |\n| Ranking | `bm25(t)`, `rank` column | `fts_score(col, 'query')` |\n| Highlighting | `highlight(t, ...)` | `fts_highlight(text, query, before, after)` ✓ |\n| Snippets | `snippet(t, ...)` | Not implemented |\n| Boolean operators | AND, OR, NOT | AND, OR, NOT ✓ |\n| Phrase search | `\"exact phrase\"` | `\"exact phrase\"` ✓ |\n| Prefix search | `word*` | `word*` ✓ |\n| Column filter | `col:term` | `col:term` ✓ |\n| Tokenizers | unicode61, ascii, porter | default, raw, simple, whitespace, ngram ✓ |\n| External content | contentless tables | Not supported |\n"
  },
  {
    "path": "docs/internals/mvcc/DESIGN.md",
    "content": "# Design\n\n## Persistent storage\n\nPersistent storage must implement the `Storage` trait that the MVCC module uses for transaction logging.\n\nFigure 1 shows an example of write-ahead log across three transactions.\nThe first transaction T0 executes a `INSERT (id) VALUES (1)` statement, which results in a log record with `id` set to `1`, begin timestamp to 0 (which is the transaction ID) and end timestamp as infinity (meaning the row version is still visible).\nThe second transaction T1 executes another `INSERT` statement, which adds another log record to the transaction log with `id` set to `2`, begin timesstamp to 1 and end timestamp as infinity, similar to what T0 did.\nFinally, a third transaction T2 executes two statements: `DELETE WHERE id = 1` and `INSERT (id) VALUES (3)`. The first one results in a log record with `id` set to `1` and begin timestamp set to 0 (which is the transaction that created the entry). However, the end timestamp is now set to 2 (the current transaction), which means the entry is now deleted.\nThe second statement results in an entry in the transaction log similar to the `INSERT` statements in T0 and T1.\n\n![Transactions](figures/transactions.png)\n<p align=\"center\">\nFigure 1. Transaction log of three transactions.\n</p>\n\nWhen MVCC bootstraps or recovers, it simply redos the transaction log.\nIf the transaction log grows big, we can checkpoint it it by dropping all entries that are no longer visible after the the latest transaction and create a snapshot.\n"
  },
  {
    "path": "docs/internals/mvcc/GC.md",
    "content": "# MVCC Garbage Collection\n\n## Overview\n\nThe MVCC store keeps every row version in memory: inserts, updates, deletes,\nand rolled-back garbage. Without GC, memory grows monotonically with write\nvolume. GC reclaims versions that no active reader can see and that are\nredundant with the B-tree.\n\nGC is driven by two parameters computed at GC time:\n\n- **LWM (low-water mark)**: `min(tx.begin_ts)` across Active/Preparing\n  transactions, or `u64::MAX` if none. Tells GC which versions are still\n  visible to some reader.\n- **ckpt_max** (`durable_txid_max`): the highest committed timestamp\n  whose data has been written to the B-tree. Tells GC when B-tree fallthrough\n  is safe.\n\nAll GC logic lives in a single function, `gc_version_chain`, shared by both\ncheckpoint-time and background GC. The four rules are applied in order:\n\n1. **Aborted garbage** (`begin=None, end=None`) — remove unconditionally.\n2. **Superseded versions** (`end=Timestamp(e), e ≤ lwm`) — remove, unless\n   doing so would let the dual cursor surface a stale B-tree row (tombstone\n   guard).\n3. **Sole-survivor current version** (`end=None, b ≤ ckpt_max, b < lwm`,\n   chain length = 1) — remove, because the B-tree has the same data.\n4. **TxID references** (`begin=TxID` or `end=TxID`) — keep, the owning\n   transaction hasn't resolved yet.\n\nThe same code works under both blocking checkpoint (`lwm = u64::MAX`, all\nversions reclaimable) and a future non-blocking checkpoint (`lwm` finite,\npinned by the oldest reader).\n\n## When GC Runs\n\nGC is triggered automatically in the `Finalize` stage of checkpoint\n(`checkpoint_state_machine.rs`), in two phases:\n\n1. `gc_checkpointed_versions()` — iterates only the checkpoint write set\n   (rows just written to B-tree). O(checkpointed rows).\n2. `drop_unused_row_versions()` — sweeps all table and index rows. Computes\n   LWM once, then applies `gc_version_chain` to every chain. O(total rows).\n\nBoth run while the checkpoint lock is still held, before it is released.\n\n## The Dual Cursor Invariant\n\nReaders merge B-tree rows with MVCC SkipMap versions via a dual cursor. For\neach B-tree row, the cursor checks `is_btree_invalidating_version` against\nevery version in the SkipMap entry. If any version invalidates, the B-tree row\nis hidden and the visible MVCC version (if any) is returned instead. If the\nSkipMap has **no entry** for the RowID, the B-tree row is returned as-is.\n\nThis means GC must maintain:\n\n> If a row exists in the B-tree, either the SkipMap correctly represents the\n> row's current state for all active readers, **or** the SkipMap has no entry\n> (B-tree fallthrough, only safe when B-tree data is up to date).\n\nTwo hazards follow from this:\n\n- **Removing a tombstone before its deletion is checkpointed** resurrects a\n  deleted row — the dual cursor falls through to the stale B-tree row.\n- **Removing the current version while leaving superseded versions** causes\n  data loss — the superseded version's `end` timestamp still invalidates the\n  B-tree row, but there's no MVCC version to serve reads.\n\nThese are guarded by Rule 2's tombstone guard and Rule 3's sole-survivor\ncondition respectively.\n\n## Rule Details\n\n### Rule 2: Tombstone Guard\n\nWhen removing a superseded version (`e ≤ lwm`), we check whether the chain\nhas a **committed current version** (`end=None, begin=Timestamp(_)`). If it\ndoes, the current version takes over B-tree invalidation and removal is safe.\n\nIf no committed current version exists, the superseded version may be the only\nthing hiding a stale B-tree row. Removal is only safe when:\n\n- `e ≤ ckpt_max` — the deletion has been checkpointed, B-tree no longer has\n  the row.\n- But NOT when `e == 0 && ckpt_max == 0` — recovery tombstones before the\n  first real checkpoint (see Recovery below).\n\nPending inserts (`begin=TxID`) do not count as committed current — they might\nroll back.\n\n### Rule 3: Sole Survivor\n\nA current version is redundant with the B-tree when `b ≤ ckpt_max` and\n`b < lwm`. But we only remove it when it's the **sole** remaining version in\nthe chain. If superseded versions remain, removing the current version would\nleave orphaned invalidators that hide the B-tree row without providing data.\n\nRule 3 also guards recovery versions: `b=0` versions are protected by\nrequiring `ckpt_max > 0` (see Recovery below).\n\n## Recovery Versions\n\nLog recovery stamps versions with `LOGICAL_LOG_RECOVERY_COMMIT_TIMESTAMP = 0`.\nSince `durable_txid_max` is advanced via `NonZeroU64`, it stays at 0\nuntil the first real transaction is checkpointed. This means `ckpt_max == 0`\nacts as a natural \"recovery data not yet checkpointed\" flag:\n\n- **Rule 2**: `e == 0 && ckpt_max == 0` → retain (recovery tombstone, B-tree\n  may still have the row).\n- **Rule 3**: `b == 0 && ckpt_max == 0` → `(b > 0 || ckpt_max > 0)` is false\n  → retain (recovery insert, B-tree may not have the row).\n\nOnce `ckpt_max > 0`, the first real checkpoint has processed recovery data\nalongside it, so recovery versions become collectible by the normal rules.\n\nThe recovery transaction itself is removed from `txs` at the end of\n`commit_load_tx` to prevent pinning LWM to 0 (which would disable Rules 2-3).\n\n## SkipMap Entry Removal\n\nAfter GC empties a version chain, the SkipMap entry is handled differently\ndepending on the GC path:\n\n- **Checkpoint-time GC** (`gc_checkpointed_versions`): removes empty entries\n  using a re-check-under-lock pattern. This is a TOCTOU gap (a writer could\n  insert between the lock release and `remove()`), but safe under the current\n  **blocking** checkpoint — no concurrent writers exist.\n\n- **Background GC** (`gc_table_row_versions`, `gc_index_row_versions`): leaves\n  empty entries in place (lazy removal). This avoids the TOCTOU race entirely.\n  Empty entries are reused by `get_or_insert_with` on subsequent inserts, and\n  cleaned up by the next checkpoint-time GC pass.\n\n## Non-blocking Checkpoint Readiness\n\nThe GC rules are designed to work with both blocking and non-blocking\ncheckpoints — the LWM parameter naturally constrains what can be collected\nwhen readers coexist with the checkpoint.\n\n**What works today**: all four GC rules, LWM computation, recovery version\nprotection, tombstone guard, lazy removal in background GC.\n\n**What needs work for non-blocking checkpoint**:\n\n- **Checkpoint-time entry removal**: the re-check-under-lock pattern in\n  `gc_checkpointed_versions` has a TOCTOU gap. Under non-blocking checkpoint,\n  concurrent writers could lose inserted versions. Fix: either hold the write\n  lock across the emptiness check and `remove()`, or switch to lazy removal\n  (same as background GC).\n\n## Key Files\n\n| File | Contents |\n|------|----------|\n| `core/mvcc/database/mod.rs` | `gc_version_chain`, `compute_lwm`, `drop_unused_row_versions`, `gc_table_row_versions`, `gc_index_row_versions`, recovery tx cleanup in `commit_load_tx` |\n| `core/mvcc/database/checkpoint_state_machine.rs` | `gc_checkpointed_versions`, auto-trigger wiring in `Finalize` |\n| `core/mvcc/database/tests.rs` | 39 GC tests (unit, quickcheck, integration, e2e) |\n"
  },
  {
    "path": "docs/internals/mvcc/RECOVERY_SEMANTICS.md",
    "content": "# MVCC Recovery and Checkpoint Semantics\n\nThis document describes the MVCC recovery and checkpoint behavior as implemented in\n`core/mvcc/database/mod.rs` and `core/mvcc/database/checkpoint_state_machine.rs`.\n\nThe checkpoint model is stop-the-world (blocking checkpoint lock).\n\n## Durable Artifacts\n\nStartup decisions use four durable artifacts:\n- Main database file (`.db`)\n- WAL file (`.db-wal`)\n- MVCC logical log (`.db-log`)\n- MVCC metadata table row: `__turso_internal_mvcc_meta(k='persistent_tx_ts_max')`\n\nThe logical-log header (56 bytes) contains format metadata and a CRC chain seed:\nmagic, version, flags, hdr_len, salt (u64), reserved, hdr_crc32c.\nThe salt is regenerated on each log truncation; frame CRCs are chained\n(`crc32c_append(prev_frame_crc, data)`) with the initial seed derived from the salt.\n\n`persistent_tx_ts_max` in `__turso_internal_mvcc_meta`is the durable replay boundary, stored inside the main database file/WAL.\nRecovery replays logical-log frames only when `commit_ts > persistent_tx_ts_max`.\n\n## Bootstrap Order\n\n`MvStore::bootstrap()` runs in this order:\n1. `maybe_complete_interrupted_checkpoint()`\n2. `reparse_schema()`\n3. Ensure metadata table exists (or fail closed in invalid states)\n4. Build in-memory table-id/root-page mapping from schema\n5. `maybe_recover_logical_log()`\n6. Promote bootstrap connection to regular MVCC connection\n\nAny committed WAL state is reconciled before logical-log replay.\nThe replay boundary comes from the metadata row.\n\n## Startup Case Classification\n\nRecovery classifies startup state using two checks:\n- Does the WAL have committed frames? (`wal.get_max_frame_in_wal() > 0`)\n- What does `try_read_header()` return? (`Valid`, `Invalid`, or `NoLog`)\n\n| Case | Startup artifacts | Recovery behavior |\n|---|---|---|\n| 1 | WAL has committed frames + log header valid | Complete interrupted checkpoint: backfill WAL into DB, sync DB, truncate WAL. Then run logical-log recovery with metadata cutoff. |\n| 2 | WAL has committed frames + log header missing (`NoLog`) | Fail closed with `Corrupt`. |\n| 3 | WAL has committed frames + log header invalid/torn | Fail closed with `Corrupt`. |\n| 4 | WAL has no committed frames | Truncate/discard WAL tail bytes and continue logical-log recovery. |\n| 5 | No WAL + log header invalid/torn | Fail closed with `Corrupt`. |\n| 6 | No WAL + valid header, no frames (size <= `LOG_HDR_SIZE`) | No replay needed; timestamp state comes from metadata row. |\n| 7 | No WAL + empty log (0 bytes / `NoLog`) | Timestamp state loaded from metadata row if present; no replay. |\n\nNotes:\n- After checkpoint, the log is truncated to 0 bytes. On restart this is case 7.\n- Torn tail in log body is treated as EOF (prefix frames remain valid).\n- First invalid frame during forward scan terminates the scan (prefix preserved), matching SQLite WAL availability semantics.\n- Missing or corrupt metadata row is treated as corruption when the metadata table is expected to exist.\n\n## Checkpoint Sequence (Blocking Model)\n\n1. Acquire blocking checkpoint lock.\n2. Begin pager transaction.\n3. Write committed MVCC table/index versions into pager.\n4. Upsert metadata row `persistent_tx_ts_max` in the same pager transaction.\n5. Commit pager transaction. WAL now contains committed frames for both data and the metadata row. In-memory `durable_txid_max` advances on this transition.\n6. Checkpoint WAL (backfill WAL frames into DB file).\n7. Fsync DB file (unless `SyncMode::Off`).\n8. Truncate logical log to 0 (salt regenerated in memory; header written with next frame).\n9. Fsync logical log (unless `SyncMode::Off`).\n10. Truncate WAL.\n11. Finalize: GC checkpointed versions, release lock.\n\nWAL truncation is last. Until the DB file and logical-log cleanup are durable,\nthe WAL remains the authoritative recovery source.\n\n## Correctness Invariants\n\n1. Startup reaches one consistent state or fails closed; no best-effort ambiguity.\n2. Committed WAL state is never ignored.\n3. Invalid logical-log tail frames are never replayed.\n4. Torn or invalid-tail bytes are never interpreted as committed operations.\n5. Replay applies only frames with `commit_ts > persistent_tx_ts_max`.\n6. `persistent_tx_ts_max` is advanced atomically with pager commit during checkpoint.\n7. Same-process checkpoint retries resume from the pager-committed boundary even if later checkpoint phases fail.\n8. Logical clock is reseeded to `max(persistent_tx_ts_max, max_replayed_log_commit_ts) + 1`.\n9. After interrupted-checkpoint reconciliation, WAL is truncated.\n\n## SyncMode::Off\n\n`SyncMode::Off` skips fsync calls. This weakens durability but does not change\nlogical ordering or fail-closed validation behavior.\n\n## Test Coverage\n\nKey tests in `core/mvcc/database/tests.rs`:\n- `test_bootstrap_completes_interrupted_checkpoint_with_committed_wal`\n- `test_bootstrap_rejects_committed_wal_without_log_file`\n- `test_bootstrap_rejects_torn_log_header_with_committed_wal`\n- `test_bootstrap_handles_committed_wal_when_log_truncated`\n- `test_bootstrap_ignores_wal_frames_without_commit_marker`\n- `test_bootstrap_rejects_corrupt_log_header_without_wal`\n- `test_empty_log_recovery_loads_checkpoint_watermark`\n- `test_meta_checkpoint_case_10_metadata_upsert_is_atomic_with_pager_commit`\n- `test_meta_checkpoint_case_11_auto_checkpoint_failure_after_commit_remains_recoverable`\n\nLogical-log corruption and torn-tail tests are in `core/mvcc/persistent_storage/logical_log.rs`.\n"
  },
  {
    "path": "docs/internals/mvcc/figures/transactions.excalidraw",
    "content": "{\n  \"type\": \"excalidraw\",\n  \"version\": 2,\n  \"source\": \"https://excalidraw.com\",\n  \"elements\": [\n    {\n      \"id\": \"tFvpBUMWe3qPFUTQVV14X\",\n      \"type\": \"text\",\n      \"x\": 233.14035848761839,\n      \"y\": 205.73272444200816,\n      \"width\": 278.57781982421875,\n      \"height\": 25,\n      \"angle\": 0,\n      \"strokeColor\": \"#087f5b\",\n      \"backgroundColor\": \"#82c91e\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"roundness\": null,\n      \"seed\": 94988319,\n      \"version\": 510,\n      \"versionNonce\": 1210831775,\n      \"isDeleted\": false,\n      \"boundElements\": null,\n      \"updated\": 1683370319070,\n      \"link\": null,\n      \"locked\": false,\n      \"text\": \"<tx=0, id=1, begin=0, end=∞>\",\n      \"fontSize\": 20,\n      \"fontFamily\": 1,\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\",\n      \"baseline\": 18,\n      \"containerId\": null,\n      \"originalText\": \"<tx=0, id=1, begin=0, end=∞>\",\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 515,\n      \"versionNonce\": 1881893969,\n      \"isDeleted\": false,\n      \"id\": \"7i88n1PIb89NxUbVQmTTi\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 938.4614491858606,\n      \"y\": 311.23272444200813,\n      \"strokeColor\": \"#0b7285\",\n      \"backgroundColor\": \"#82c91e\",\n      \"width\": 279.0400085449219,\n      \"height\": 25,\n      \"seed\": 1123646321,\n      \"groupIds\": [],\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1683370316909,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 1,\n      \"text\": \"<tx=2, id=1, begin=0, end=2>\",\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\",\n      \"containerId\": null,\n      \"originalText\": \"<tx=2, id=1, begin=0, end=2>\",\n      \"lineHeight\": 1.25,\n      \"baseline\": 18\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 556,\n      \"versionNonce\": 153125934,\n      \"isDeleted\": false,\n      \"id\": \"Yh8XLtKqXUUYmcmG4SEXn\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 581.1603475012903,\n      \"y\": 256.23272444200813,\n      \"strokeColor\": \"#e67700\",\n      \"backgroundColor\": \"#82c91e\",\n      \"width\": 270.71783447265625,\n      \"height\": 25,\n      \"seed\": 1685524017,\n      \"groupIds\": [],\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1683371076075,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 1,\n      \"text\": \"<tx=1, id=2, begin=1, end=∞>\",\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\",\n      \"containerId\": null,\n      \"originalText\": \"<tx=1, id=2, begin=1, end=∞>\",\n      \"lineHeight\": 1.25,\n      \"baseline\": 18\n    },\n    {\n      \"id\": \"8l0CCJzCAtOLt_2GRcNpa\",\n      \"type\": \"text\",\n      \"x\": 256.1403584876185,\n      \"y\": 409.73272444200813,\n      \"width\": 234.41998291015625,\n      \"height\": 75,\n      \"angle\": 0,\n      \"strokeColor\": \"#087f5b\",\n      \"backgroundColor\": \"#82c91e\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"roundness\": null,\n      \"seed\": 583129809,\n      \"version\": 570,\n      \"versionNonce\": 561756721,\n      \"isDeleted\": false,\n      \"boundElements\": null,\n      \"updated\": 1683370316909,\n      \"link\": null,\n      \"locked\": false,\n      \"text\": \"BEGIN\\nINSERT (id) VALUEs (1)\\nCOMMIT\",\n      \"fontSize\": 20,\n      \"fontFamily\": 1,\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\",\n      \"baseline\": 68,\n      \"containerId\": null,\n      \"originalText\": \"BEGIN\\nINSERT (id) VALUEs (1)\\nCOMMIT\",\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 628,\n      \"versionNonce\": 282656095,\n      \"isDeleted\": false,\n      \"id\": \"3m7VluAP5tair6-60b_sp\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 962.0903554358606,\n      \"y\": 416.23272444200813,\n      \"strokeColor\": \"#0b7285\",\n      \"backgroundColor\": \"#82c91e\",\n      \"width\": 243.91998291015625,\n      \"height\": 100,\n      \"seed\": 479705617,\n      \"groupIds\": [],\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1683370316909,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 1,\n      \"text\": \"BEGIN\\nDELETE WHERE id =1\\nINSERT (id) VALUES (3)\\nCOMMIT\",\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\",\n      \"containerId\": null,\n      \"originalText\": \"BEGIN\\nDELETE WHERE id =1\\nINSERT (id) VALUES (3)\\nCOMMIT\",\n      \"lineHeight\": 1.25,\n      \"baseline\": 93\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 574,\n      \"versionNonce\": 1128746001,\n      \"isDeleted\": false,\n      \"id\": \"Z-Mh1kti2oC6sIMnuGluo\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 613.0903554358607,\n      \"y\": 417.23272444200813,\n      \"strokeColor\": \"#e67700\",\n      \"backgroundColor\": \"#82c91e\",\n      \"width\": 243.239990234375,\n      \"height\": 75,\n      \"seed\": 580440625,\n      \"groupIds\": [],\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1683370316909,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 1,\n      \"text\": \"BEGIN\\nINSERT (id) VALUEs (2)\\nCOMMIT\",\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\",\n      \"containerId\": null,\n      \"originalText\": \"BEGIN\\nINSERT (id) VALUEs (2)\\nCOMMIT\",\n      \"lineHeight\": 1.25,\n      \"baseline\": 68\n    },\n    {\n      \"type\": \"line\",\n      \"version\": 1502,\n      \"versionNonce\": 1835608607,\n      \"isDeleted\": false,\n      \"id\": \"VuJNZCgz1Y0WEWwug7pGk\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 0,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 226.3083636621349,\n      \"y\": 173.11701218356845,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 20.336010349032712,\n      \"height\": 203.23377930246647,\n      \"seed\": 1879839231,\n      \"groupIds\": [],\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1683370316909,\n      \"link\": null,\n      \"locked\": false,\n      \"startBinding\": null,\n      \"endBinding\": null,\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": null,\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          -20.264781987976257,\n          -0.0011773927935071482\n        ],\n        [\n          -20.336010349032712,\n          203.23260190967298\n        ],\n        [\n          -0.07239358683375485,\n          203.135377672515\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"version\": 1755,\n      \"versionNonce\": 1487752017,\n      \"isDeleted\": false,\n      \"id\": \"GpZg3Rw4Hszxzxf38Q4Hn\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 0,\n      \"opacity\": 100,\n      \"angle\": 3.141592653589793,\n      \"x\": 539.3083636621348,\n      \"y\": 178.11701218356845,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 20.336010349032712,\n      \"height\": 203.23377930246647,\n      \"seed\": 470135121,\n      \"groupIds\": [],\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1683370316909,\n      \"link\": null,\n      \"locked\": false,\n      \"startBinding\": null,\n      \"endBinding\": null,\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": null,\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          -20.264781987976257,\n          -0.0011773927935071482\n        ],\n        [\n          -20.336010349032712,\n          203.23260190967298\n        ],\n        [\n          -0.07239358683375485,\n          203.135377672515\n        ]\n      ]\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 528,\n      \"versionNonce\": 1276939839,\n      \"isDeleted\": false,\n      \"id\": \"AGEyNvBxBm2cwm1WRW8n8\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 576.6403584876185,\n      \"y\": 210.23272444200816,\n      \"strokeColor\": \"#087f5b\",\n      \"backgroundColor\": \"#82c91e\",\n      \"width\": 278.57781982421875,\n      \"height\": 25,\n      \"seed\": 877528401,\n      \"groupIds\": [],\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1683370316909,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 1,\n      \"text\": \"<tx=0, id=1, begin=0, end=∞>\",\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\",\n      \"containerId\": null,\n      \"originalText\": \"<tx=0, id=1, begin=0, end=∞>\",\n      \"lineHeight\": 1.25,\n      \"baseline\": 18\n    },\n    {\n      \"type\": \"line\",\n      \"version\": 1557,\n      \"versionNonce\": 773679889,\n      \"isDeleted\": false,\n      \"id\": \"Q8E0gAcLvq6VXqMDZhLdA\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 0,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 581.8083636621351,\n      \"y\": 177.61701218356845,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 20.336010349032712,\n      \"height\": 203.23377930246647,\n      \"seed\": 153279217,\n      \"groupIds\": [],\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1683370316909,\n      \"link\": null,\n      \"locked\": false,\n      \"startBinding\": null,\n      \"endBinding\": null,\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": null,\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          -20.264781987976257,\n          -0.0011773927935071482\n        ],\n        [\n          -20.336010349032712,\n          203.23260190967298\n        ],\n        [\n          -0.07239358683375485,\n          203.135377672515\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"version\": 1810,\n      \"versionNonce\": 1561283199,\n      \"isDeleted\": false,\n      \"id\": \"uhh3ZkPO6bwwf0-AI8syI\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 0,\n      \"opacity\": 100,\n      \"angle\": 3.141592653589793,\n      \"x\": 894.8083636621349,\n      \"y\": 182.61701218356845,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 20.336010349032712,\n      \"height\": 203.23377930246647,\n      \"seed\": 315380945,\n      \"groupIds\": [],\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1683370316909,\n      \"link\": null,\n      \"locked\": false,\n      \"startBinding\": null,\n      \"endBinding\": null,\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": null,\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          -20.264781987976257,\n          -0.0011773927935071482\n        ],\n        [\n          -20.336010349032712,\n          203.23260190967298\n        ],\n        [\n          -0.07239358683375485,\n          203.135377672515\n        ]\n      ]\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 575,\n      \"versionNonce\": 910156017,\n      \"isDeleted\": false,\n      \"id\": \"jI5YKyaOdGYYKiBWZmCMs\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 929.6403584876182,\n      \"y\": 215.23272444200813,\n      \"strokeColor\": \"#087f5b\",\n      \"backgroundColor\": \"#82c91e\",\n      \"width\": 278.57781982421875,\n      \"height\": 25,\n      \"seed\": 121503167,\n      \"groupIds\": [],\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1683370316909,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 1,\n      \"text\": \"<tx=0, id=1, begin=0, end=∞>\",\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\",\n      \"containerId\": null,\n      \"originalText\": \"<tx=0, id=1, begin=0, end=∞>\",\n      \"lineHeight\": 1.25,\n      \"baseline\": 18\n    },\n    {\n      \"type\": \"line\",\n      \"version\": 1604,\n      \"versionNonce\": 19920575,\n      \"isDeleted\": false,\n      \"id\": \"QqIk7VTnRWYq499wkttvv\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 0,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 934.8083636621348,\n      \"y\": 182.61701218356842,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 20.336010349032712,\n      \"height\": 203.23377930246647,\n      \"seed\": 2012037663,\n      \"groupIds\": [],\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1683370316909,\n      \"link\": null,\n      \"locked\": false,\n      \"startBinding\": null,\n      \"endBinding\": null,\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": null,\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          -20.264781987976257,\n          -0.0011773927935071482\n        ],\n        [\n          -20.336010349032712,\n          203.23260190967298\n        ],\n        [\n          -0.07239358683375485,\n          203.135377672515\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"version\": 1857,\n      \"versionNonce\": 1660885169,\n      \"isDeleted\": false,\n      \"id\": \"gk89VsYpnf9Jby9KEUBd3\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 0,\n      \"opacity\": 100,\n      \"angle\": 3.141592653589793,\n      \"x\": 1247.808363662135,\n      \"y\": 187.61701218356842,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 20.336010349032712,\n      \"height\": 203.23377930246647,\n      \"seed\": 509453887,\n      \"groupIds\": [],\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1683370316909,\n      \"link\": null,\n      \"locked\": false,\n      \"startBinding\": null,\n      \"endBinding\": null,\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": null,\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          -20.264781987976257,\n          -0.0011773927935071482\n        ],\n        [\n          -20.336010349032712,\n          203.23260190967298\n        ],\n        [\n          -0.07239358683375485,\n          203.135377672515\n        ]\n      ]\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 620,\n      \"versionNonce\": 1588681010,\n      \"isDeleted\": false,\n      \"id\": \"a1c-iZI0SafCiy0u4xieZ\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 934.3714375891809,\n      \"y\": 261.23272444200813,\n      \"strokeColor\": \"#e67700\",\n      \"backgroundColor\": \"#82c91e\",\n      \"width\": 270.71783447265625,\n      \"height\": 25,\n      \"seed\": 1742829553,\n      \"groupIds\": [],\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1683371080181,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 1,\n      \"text\": \"<tx=1, id=2, begin=1, end=∞>\",\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\",\n      \"containerId\": null,\n      \"originalText\": \"<tx=1, id=2, begin=1, end=∞>\",\n      \"lineHeight\": 1.25,\n      \"baseline\": 18\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 564,\n      \"versionNonce\": 1968863633,\n      \"isDeleted\": false,\n      \"id\": \"hdhhgp5nA06o5EcSgHQE8\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 937.6203542151575,\n      \"y\": 354.23272444200813,\n      \"strokeColor\": \"#0b7285\",\n      \"backgroundColor\": \"#82c91e\",\n      \"width\": 287.73785400390625,\n      \"height\": 25,\n      \"seed\": 309558367,\n      \"groupIds\": [],\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1683370363648,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 1,\n      \"text\": \"<tx=2, id=3, begin=2, end=∞>\",\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\",\n      \"containerId\": null,\n      \"originalText\": \"<tx=2, id=3, begin=2, end=∞>\",\n      \"lineHeight\": 1.25,\n      \"baseline\": 18\n    }\n  ],\n  \"appState\": {\n    \"gridSize\": null,\n    \"viewBackgroundColor\": \"#ffffff\"\n  },\n  \"files\": {}\n}"
  },
  {
    "path": "docs/javascript-api-reference.md",
    "content": "# JavaScript API reference\n\nThis document describes the JavaScript API for Turso. The API is implemented in two different packages:\n\n- [@tursodatabase/database](https://www.npmjs.com/package/@tursodatabase/database) (`bindings/javascript`) - Native bindings for the Turso database.\n- [@tursodatabase/serverless](https://www.npmjs.com/package/@tursodatabase/serverless) (`packages/turso-serverless`) - Serverless driver for Turso Cloud databases.\n\nThe API is compatible with the libSQL promise API, which is an asynchronous variant of the `better-sqlite3` API.\n\n## Functions\n\n#### connect(path, [options]) ⇒ Database\n\nOpens a new database connection.\n\n| Param   | Type                | Description               |\n| ------- | ------------------- | ------------------------- |\n| path    | <code>string</code> | Path to the database file |\n\nThe `path` parameter points to the SQLite database file to open. If the file pointed to by `path` does not exists, it will be created.\nTo open an in-memory database, please pass `:memory:` as the `path` parameter.\n\nThe function returns a `Database` object.\n\n## class Database\n\nThe `Database` class represents a connection that can prepare and execute SQL statements.\n\n### Methods\n\n#### prepare(sql) ⇒ Statement\n\nPrepares a SQL statement for execution.\n\n| Param  | Type                | Description                          |\n| ------ | ------------------- | ------------------------------------ |\n| sql    | <code>string</code> | The SQL statement string to prepare. |\n\nThe function returns a `Statement` object.\n\n#### transaction(function) ⇒ function\n\nThis function is currently not supported.\n\n#### pragma(string, [options]) ⇒ results\n\nThis function is currently not supported.\n\n#### backup(destination, [options]) ⇒ promise\n\nThis function is currently not supported.\n\n#### serialize([options]) ⇒ Buffer\n\nThis function is currently not supported.\n\n#### function(name, [options], function) ⇒ this\n\nThis function is currently not supported.\n\n#### aggregate(name, options) ⇒ this\n\nThis function is currently not supported.\n\n#### table(name, definition) ⇒ this\n\nThis function is currently not supported.\n\n#### authorizer(rules) ⇒ this\n\nThis function is currently not supported.\n\n#### loadExtension(path, [entryPoint]) ⇒ this\n\nThis function is currently not supported.\n\n#### exec(sql) ⇒ this\n\nExecutes a SQL statement.\n\n| Param  | Type                | Description                          |\n| ------ | ------------------- | ------------------------------------ |\n| sql    | <code>string</code> | The SQL statement string to execute. |\n\n#### interrupt() ⇒ this\n\nThis function is currently not supported.\n\n#### close() ⇒ this\n\nCloses the database connection.\n\n## class Statement\n\n### Methods\n\n#### run([...bindParameters]) ⇒ object\n\nExecutes the SQL statement and returns an info object.\n\n| Param          | Type                          | Description                                      |\n| -------------- | ----------------------------- | ------------------------------------------------ |\n| bindParameters | <code>array of objects</code> | The bind parameters for executing the statement. |\n\nThe returned info object contains two properties: `changes` that describes the number of modified rows and `info.lastInsertRowid` that represents the `rowid` of the last inserted row.\n\n#### get([...bindParameters]) ⇒ row\n\nExecutes the SQL statement and returns the first row.\n\n| Param          | Type                          | Description                                      |\n| -------------- | ----------------------------- | ------------------------------------------------ |\n| bindParameters | <code>array of objects</code> | The bind parameters for executing the statement. |\n\n### all([...bindParameters]) ⇒ array of rows\n\nExecutes the SQL statement and returns an array of the resulting rows.\n\n| Param          | Type                          | Description                                      |\n| -------------- | ----------------------------- | ------------------------------------------------ |\n| bindParameters | <code>array of objects</code> | The bind parameters for executing the statement. |\n\n### iterate([...bindParameters]) ⇒ iterator\n\nExecutes the SQL statement and returns an iterator to the resulting rows.\n\n| Param          | Type                          | Description                                      |\n| -------------- | ----------------------------- | ------------------------------------------------ |\n| bindParameters | <code>array of objects</code> | The bind parameters for executing the statement. |\n\n#### pluck([toggleState]) ⇒ this\n\nThis function is currently not supported.\n\n#### expand([toggleState]) ⇒ this\n\nThis function is currently not supported.\n\n#### raw([rawMode]) ⇒ this\n\nThis function is currently not supported.\n\n#### timed([toggle]) ⇒ this\n\nThis function is currently not supported.\n\n#### columns() ⇒ array of objects\n\nThis function is currently not supported.\n\n#### bind([...bindParameters]) ⇒ this\n\nThis function is currently not supported.\n"
  },
  {
    "path": "docs/language-reference/book/print.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\" class=\"light sidebar-visible\" dir=\"ltr\">\n    <head>\n        <!-- Book generated using mdBook -->\n        <meta charset=\"UTF-8\">\n        <title>Turso SQL Language Reference</title>\n        <meta name=\"robots\" content=\"noindex\">\n\n\n        <!-- Custom HTML head -->\n\n        <meta name=\"description\" content=\"\">\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n        <meta name=\"theme-color\" content=\"#ffffff\">\n\n        <link rel=\"icon\" href=\"favicon-de23e50b.svg\">\n        <link rel=\"shortcut icon\" href=\"favicon-8114d1fc.png\">\n        <link rel=\"stylesheet\" href=\"css/variables-8adf115d.css\">\n        <link rel=\"stylesheet\" href=\"css/general-2459343d.css\">\n        <link rel=\"stylesheet\" href=\"css/chrome-ae938929.css\">\n        <link rel=\"stylesheet\" href=\"css/print-9e4910d8.css\" media=\"print\">\n\n        <!-- Fonts -->\n        <link rel=\"stylesheet\" href=\"fonts/fonts-9644e21d.css\">\n\n        <!-- Highlight.js Stylesheets -->\n        <link rel=\"stylesheet\" id=\"mdbook-highlight-css\" href=\"highlight-493f70e1.css\">\n        <link rel=\"stylesheet\" id=\"mdbook-tomorrow-night-css\" href=\"tomorrow-night-4c0ae647.css\">\n        <link rel=\"stylesheet\" id=\"mdbook-ayu-highlight-css\" href=\"ayu-highlight-3fdfc3ac.css\">\n\n        <!-- Custom theme stylesheets -->\n\n\n        <!-- Provide site root and default themes to javascript -->\n        <script>\n            const path_to_root = \"\";\n            const default_light_theme = \"light\";\n            const default_dark_theme = \"ayu\";\n            window.path_to_searchindex_js = \"searchindex-51b85602.js\";\n        </script>\n        <!-- Start loading toc.js asap -->\n        <script src=\"toc-abb19179.js\"></script>\n    </head>\n    <body>\n    <div id=\"mdbook-help-container\">\n        <div id=\"mdbook-help-popup\">\n            <h2 class=\"mdbook-help-title\">Keyboard shortcuts</h2>\n            <div>\n                <p>Press <kbd>←</kbd> or <kbd>→</kbd> to navigate between chapters</p>\n                <p>Press <kbd>S</kbd> or <kbd>/</kbd> to search in the book</p>\n                <p>Press <kbd>?</kbd> to show this help</p>\n                <p>Press <kbd>Esc</kbd> to hide this help</p>\n            </div>\n        </div>\n    </div>\n    <div id=\"mdbook-body-container\">\n        <!-- Work around some values being stored in localStorage wrapped in quotes -->\n        <script>\n            try {\n                let theme = localStorage.getItem('mdbook-theme');\n                let sidebar = localStorage.getItem('mdbook-sidebar');\n\n                if (theme.startsWith('\"') && theme.endsWith('\"')) {\n                    localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));\n                }\n\n                if (sidebar.startsWith('\"') && sidebar.endsWith('\"')) {\n                    localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));\n                }\n            } catch (e) { }\n        </script>\n\n        <!-- Set the theme before any content is loaded, prevents flash -->\n        <script>\n            const default_theme = window.matchMedia(\"(prefers-color-scheme: dark)\").matches ? default_dark_theme : default_light_theme;\n            let theme;\n            try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }\n            if (theme === null || theme === undefined) { theme = default_theme; }\n            const html = document.documentElement;\n            html.classList.remove('light')\n            html.classList.add(theme);\n            html.classList.add(\"js\");\n        </script>\n\n        <input type=\"checkbox\" id=\"mdbook-sidebar-toggle-anchor\" class=\"hidden\">\n\n        <!-- Hide / unhide sidebar before it is displayed -->\n        <script>\n            let sidebar = null;\n            const sidebar_toggle = document.getElementById(\"mdbook-sidebar-toggle-anchor\");\n            if (document.body.clientWidth >= 1080) {\n                try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }\n                sidebar = sidebar || 'visible';\n            } else {\n                sidebar = 'hidden';\n                sidebar_toggle.checked = false;\n            }\n            if (sidebar === 'visible') {\n                sidebar_toggle.checked = true;\n            } else {\n                html.classList.remove('sidebar-visible');\n            }\n        </script>\n\n        <nav id=\"mdbook-sidebar\" class=\"sidebar\" aria-label=\"Table of contents\">\n            <!-- populated by js -->\n            <mdbook-sidebar-scrollbox class=\"sidebar-scrollbox\"></mdbook-sidebar-scrollbox>\n            <noscript>\n                <iframe class=\"sidebar-iframe-outer\" src=\"toc.html\"></iframe>\n            </noscript>\n            <div id=\"mdbook-sidebar-resize-handle\" class=\"sidebar-resize-handle\">\n                <div class=\"sidebar-resize-indicator\"></div>\n            </div>\n        </nav>\n\n        <div id=\"mdbook-page-wrapper\" class=\"page-wrapper\">\n\n            <div class=\"page\">\n                <div id=\"mdbook-menu-bar-hover-placeholder\"></div>\n                <div id=\"mdbook-menu-bar\" class=\"menu-bar sticky\">\n                    <div class=\"left-buttons\">\n                        <label id=\"mdbook-sidebar-toggle\" class=\"icon-button\" for=\"mdbook-sidebar-toggle-anchor\" title=\"Toggle Table of Contents\" aria-label=\"Toggle Table of Contents\" aria-controls=\"mdbook-sidebar\">\n                            <span class=fa-svg><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d=\"M0 96C0 78.3 14.3 64 32 64H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32C14.3 128 0 113.7 0 96zM0 256c0-17.7 14.3-32 32-32H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32c-17.7 0-32-14.3-32-32zM448 416c0 17.7-14.3 32-32 32H32c-17.7 0-32-14.3-32-32s14.3-32 32-32H416c17.7 0 32 14.3 32 32z\"/></svg></span>\n                        </label>\n                        <button id=\"mdbook-theme-toggle\" class=\"icon-button\" type=\"button\" title=\"Change theme\" aria-label=\"Change theme\" aria-haspopup=\"true\" aria-expanded=\"false\" aria-controls=\"mdbook-theme-list\">\n                            <span class=fa-svg><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 576 512\"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d=\"M371.3 367.1c27.3-3.9 51.9-19.4 67.2-42.9L600.2 74.1c12.6-19.5 9.4-45.3-7.6-61.2S549.7-4.4 531.1 9.6L294.4 187.2c-24 18-38.2 46.1-38.4 76.1L371.3 367.1zm-19.6 25.4l-116-104.4C175.9 290.3 128 339.6 128 400c0 3.9 .2 7.8 .6 11.6c1.8 17.5-10.2 36.4-27.8 36.4H96c-17.7 0-32 14.3-32 32s14.3 32 32 32H240c61.9 0 112-50.1 112-112c0-2.5-.1-5-.2-7.5z\"/></svg></span>\n                        </button>\n                        <ul id=\"mdbook-theme-list\" class=\"theme-popup\" aria-label=\"Themes\" role=\"menu\">\n                            <li role=\"none\"><button role=\"menuitem\" class=\"theme\" id=\"mdbook-theme-default_theme\">Auto</button></li>\n                            <li role=\"none\"><button role=\"menuitem\" class=\"theme\" id=\"mdbook-theme-light\">Light</button></li>\n                            <li role=\"none\"><button role=\"menuitem\" class=\"theme\" id=\"mdbook-theme-rust\">Rust</button></li>\n                            <li role=\"none\"><button role=\"menuitem\" class=\"theme\" id=\"mdbook-theme-coal\">Coal</button></li>\n                            <li role=\"none\"><button role=\"menuitem\" class=\"theme\" id=\"mdbook-theme-navy\">Navy</button></li>\n                            <li role=\"none\"><button role=\"menuitem\" class=\"theme\" id=\"mdbook-theme-ayu\">Ayu</button></li>\n                        </ul>\n                        <button id=\"mdbook-search-toggle\" class=\"icon-button\" type=\"button\" title=\"Search (`/`)\" aria-label=\"Toggle Searchbar\" aria-expanded=\"false\" aria-keyshortcuts=\"/ s\" aria-controls=\"mdbook-searchbar\">\n                            <span class=fa-svg><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d=\"M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352c79.5 0 144-64.5 144-144s-64.5-144-144-144S64 128.5 64 208s64.5 144 144 144z\"/></svg></span>\n                        </button>\n                    </div>\n\n                    <h1 class=\"menu-title\">Turso SQL Language Reference</h1>\n\n                    <div class=\"right-buttons\">\n                        <a href=\"print.html\" title=\"Print this book\" aria-label=\"Print this book\">\n                            <span class=fa-svg id=\"print-button\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d=\"M128 0C92.7 0 64 28.7 64 64v96h64V64H354.7L384 93.3V160h64V93.3c0-17-6.7-33.3-18.7-45.3L400 18.7C388 6.7 371.7 0 354.7 0H128zM384 352v32 64H128V384 368 352H384zm64 32h32c17.7 0 32-14.3 32-32V256c0-35.3-28.7-64-64-64H64c-35.3 0-64 28.7-64 64v96c0 17.7 14.3 32 32 32H64v64c0 35.3 28.7 64 64 64H384c35.3 0 64-28.7 64-64V384zm-16-88c-13.3 0-24-10.7-24-24s10.7-24 24-24s24 10.7 24 24s-10.7 24-24 24z\"/></svg></span>\n                        </a>\n                        <a href=\"https://github.com/tursodatabase/turso\" title=\"Git repository\" aria-label=\"Git repository\">\n                            <span class=fa-svg><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 496 512\"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d=\"M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z\"/></svg></span>\n                        </a>\n\n                    </div>\n                </div>\n\n                <div id=\"mdbook-search-wrapper\" class=\"hidden\">\n                    <form id=\"mdbook-searchbar-outer\" class=\"searchbar-outer\">\n                        <div class=\"search-wrapper\">\n                            <input type=\"search\" id=\"mdbook-searchbar\" name=\"searchbar\" placeholder=\"Search this book ...\" aria-controls=\"mdbook-searchresults-outer\" aria-describedby=\"searchresults-header\">\n                            <div class=\"spinner-wrapper\">\n                                <span class=fa-svg id=\"fa-spin\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d=\"M304 48c0-26.5-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48s48-21.5 48-48zm0 416c0-26.5-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48s48-21.5 48-48zM48 304c26.5 0 48-21.5 48-48s-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48zm464-48c0-26.5-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48s48-21.5 48-48zM142.9 437c18.7-18.7 18.7-49.1 0-67.9s-49.1-18.7-67.9 0s-18.7 49.1 0 67.9s49.1 18.7 67.9 0zm0-294.2c18.7-18.7 18.7-49.1 0-67.9S93.7 56.2 75 75s-18.7 49.1 0 67.9s49.1 18.7 67.9 0zM369.1 437c18.7 18.7 49.1 18.7 67.9 0s18.7-49.1 0-67.9s-49.1-18.7-67.9 0s-18.7 49.1 0 67.9z\"/></svg></span>\n                            </div>\n                        </div>\n                    </form>\n                    <div id=\"mdbook-searchresults-outer\" class=\"searchresults-outer hidden\">\n                        <div id=\"mdbook-searchresults-header\" class=\"searchresults-header\"></div>\n                        <ul id=\"mdbook-searchresults\">\n                        </ul>\n                    </div>\n                </div>\n\n                <!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->\n                <script>\n                    document.getElementById('mdbook-sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');\n                    document.getElementById('mdbook-sidebar').setAttribute('aria-hidden', sidebar !== 'visible');\n                    Array.from(document.querySelectorAll('#mdbook-sidebar a')).forEach(function(link) {\n                        link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);\n                    });\n                </script>\n\n                <div id=\"mdbook-content\" class=\"content\">\n                    <main>\n                        <h1 id=\"turso-sql-language-reference\"><a class=\"header\" href=\"#turso-sql-language-reference\">Turso SQL Language Reference</a></h1>\n<p>Turso is a SQLite-compatible database. This reference documents the SQL language as supported by Turso.</p>\n<p>If you are familiar with SQLite, Turso supports most of the same SQL syntax. This reference covers only what Turso supports — features not listed here are not yet available. For Turso-specific extensions beyond SQLite (custom types, vector search, CDC, materialized views, encryption), see the <a href=\"#custom-types\">Turso Extensions</a> section.</p>\n<h2 id=\"how-to-read-syntax-definitions\"><a class=\"header\" href=\"#how-to-read-syntax-definitions\">How to Read Syntax Definitions</a></h2>\n<p>Every statement page begins with a <strong>Syntax</strong> section showing the grammar using the following notation:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Notation</th><th>Meaning</th></tr>\n</thead>\n<tbody>\n<tr><td><code>KEYWORD</code></td><td>A literal SQL keyword. Keywords are case-insensitive; uppercase is used by convention.</td></tr>\n<tr><td><code>name</code></td><td>A placeholder for a user-supplied identifier (table name, column name, etc.).</td></tr>\n<tr><td><code>expr</code></td><td>A placeholder for any SQL expression.</td></tr>\n<tr><td><code>[X]</code></td><td><code>X</code> is optional.</td></tr>\n<tr><td><code>{A | B}</code></td><td>Choose exactly one of <code>A</code> or <code>B</code>.</td></tr>\n<tr><td><code>[A | B]</code></td><td>Optionally choose one of <code>A</code> or <code>B</code>.</td></tr>\n<tr><td><code>[, ...]</code></td><td>The preceding element may be repeated, separated by commas.</td></tr>\n</tbody>\n</table>\n</div>\n<h3 id=\"example\"><a class=\"header\" href=\"#example\">Example</a></h3>\n<pre><code class=\"language-sql\">INSERT [OR {ROLLBACK | ABORT | FAIL | IGNORE | REPLACE}]\n  INTO table-name [(column-name [, ...])]\n  VALUES (expr [, ...]) [, ...]\n</code></pre>\n<p>This means:</p>\n<ul>\n<li><code>INSERT</code> is required.</li>\n<li><code>OR ROLLBACK</code>, <code>OR ABORT</code>, etc. are optional — pick one if used.</li>\n<li>The column list is optional.</li>\n<li>At least one <code>VALUES</code> row is required, and you may provide more separated by commas.</li>\n<li>Each row contains one or more expressions separated by commas.</li>\n</ul>\n<h2 id=\"identifiers-and-quoting\"><a class=\"header\" href=\"#identifiers-and-quoting\">Identifiers and Quoting</a></h2>\n<p>Identifiers (table names, column names) follow these rules:</p>\n<ul>\n<li>Unquoted identifiers may contain letters, digits, and underscores, and must not start with a digit.</li>\n<li>Identifiers can be quoted with double quotes (<code>\"name\"</code>), square brackets (<code>[name]</code>), or backticks (<code>`name`</code>).</li>\n<li>Quoted identifiers may contain any character, including spaces and reserved words.</li>\n<li>String literals use single quotes (<code>'text'</code>). Double quotes are for identifiers, not strings.</li>\n</ul>\n<h2 id=\"type-affinity\"><a class=\"header\" href=\"#type-affinity\">Type Affinity</a></h2>\n<p>Turso uses SQLite’s dynamic type system. Every value has one of five storage classes:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Storage Class</th><th>Description</th></tr>\n</thead>\n<tbody>\n<tr><td>NULL</td><td>A null value.</td></tr>\n<tr><td>INTEGER</td><td>A signed integer, stored in 1, 2, 3, 4, 6, or 8 bytes.</td></tr>\n<tr><td>REAL</td><td>A floating-point number, stored as an 8-byte IEEE 754 float.</td></tr>\n<tr><td>TEXT</td><td>A UTF-8 string.</td></tr>\n<tr><td>BLOB</td><td>Raw binary data, stored exactly as provided.</td></tr>\n</tbody>\n</table>\n</div>\n<p>Column type names in <code>CREATE TABLE</code> determine the column’s <strong>type affinity</strong>, which influences how values are coerced on insertion. See <a href=\"#type-conversions\">Type Conversions</a> for the full rules.</p>\n<div style=\"break-before: page; page-break-before: always;\"></div>\n<h1 id=\"select\"><a class=\"header\" href=\"#select\">SELECT</a></h1>\n<h2 id=\"syntax\"><a class=\"header\" href=\"#syntax\">Syntax</a></h2>\n<pre><code class=\"language-sql\">SELECT [DISTINCT | ALL] result-column [, ...]\n  [FROM table-or-subquery [, ...]]\n  [WHERE expr]\n  [GROUP BY expr [, ...] [HAVING expr]]\n  [ORDER BY ordering-term [, ...]]\n  [LIMIT expr [OFFSET expr]]\n</code></pre>\n<p>Where <code>result-column</code> is one of:</p>\n<pre><code class=\"language-sql\">*\ntable-name.*\nexpr [[AS] column-alias]\n</code></pre>\n<p>And <code>table-or-subquery</code> is one of:</p>\n<pre><code class=\"language-sql\">table-name [[AS] table-alias]\n(select-statement) [[AS] table-alias]\ntable-or-subquery join-operator table-or-subquery join-constraint\n</code></pre>\n<p>This page covers the basic SELECT statement: the SELECT clause, FROM clause, WHERE clause, and DISTINCT/ALL keywords. For related topics, see <a href=\"#join\">JOINs</a>, <a href=\"#group-by-and-having\">GROUP BY and HAVING</a>, <a href=\"#order-by-limit-offset\">ORDER BY, LIMIT, OFFSET</a>, <a href=\"#set-operations\">Set Operations</a>, <a href=\"#subqueries\">Subqueries</a>, and <a href=\"#common-table-expressions\">Common Table Expressions</a>.</p>\n<h2 id=\"description\"><a class=\"header\" href=\"#description\">Description</a></h2>\n<p>The SELECT statement queries the database and returns zero or more rows of data. Each row has a fixed number of columns determined by the result expression list. A SELECT statement does not modify the database.</p>\n<p>Processing a SELECT statement follows four steps:</p>\n<ol>\n<li><strong>FROM clause</strong> – the input dataset is determined. If there is no FROM clause, the input is implicitly a single row with zero columns.</li>\n<li><strong>WHERE clause</strong> – the input rows are filtered by evaluating the WHERE expression as a boolean for each row; only rows where the expression is true are kept.</li>\n<li><strong>Result expressions</strong> – the result columns are computed by evaluating each expression in the SELECT list against the filtered rows.</li>\n<li><strong>DISTINCT/ALL</strong> – if DISTINCT is specified, duplicate result rows are removed.</li>\n</ol>\n<h2 id=\"clauses\"><a class=\"header\" href=\"#clauses\">Clauses</a></h2>\n<h3 id=\"select-result-expressions\"><a class=\"header\" href=\"#select-result-expressions\">SELECT (Result Expressions)</a></h3>\n<p>The list of expressions between SELECT and FROM is the <strong>result expression list</strong>. Each expression becomes a column in the output. Expressions can be constants, column references, computed values, or function calls.</p>\n<pre><code class=\"language-sql\">-- Selecting literal values (no FROM clause needed)\nSELECT 1 + 2;\n</code></pre>\n<pre><code class=\"language-sql\">-- Selecting with string concatenation\nSELECT 10 * 2 AS doubled, 'hello' || ' ' || 'world' AS combined;\n</code></pre>\n<h4 id=\"column-aliases\"><a class=\"header\" href=\"#column-aliases\">Column Aliases</a></h4>\n<p>Any result expression can be given a name using the AS keyword. This alias becomes the column header in the output and can be referenced in ORDER BY and GROUP BY clauses.</p>\n<pre><code class=\"language-sql\">SELECT name AS user_name, email AS contact\n  FROM users\n  WHERE active = 1;\n</code></pre>\n<p>The AS keyword is optional – <code>SELECT name user_name FROM users</code> is also valid – but including it is recommended for clarity.</p>\n<h4 id=\"the-asterisk-wildcard\"><a class=\"header\" href=\"#the-asterisk-wildcard\">The Asterisk Wildcard</a></h4>\n<p>The special expression <code>*</code> expands to all columns from all tables in the FROM clause.</p>\n<pre><code class=\"language-sql\">-- Return all columns from the users table\nSELECT * FROM users;\n</code></pre>\n<p>To expand all columns from a specific table (useful with multiple tables), use <code>table.*</code> or <code>alias.*</code>:</p>\n<pre><code class=\"language-sql\">SELECT u.* FROM users u WHERE u.active = 1;\n</code></pre>\n<p>The <code>*</code> and <code>table.*</code> forms can only be used in the result expression list of a SELECT that has a FROM clause.</p>\n<h3 id=\"from\"><a class=\"header\" href=\"#from\">FROM</a></h3>\n<p>The FROM clause specifies the input data for the query. If omitted, the input is implicitly a single row with zero columns, which is useful for evaluating expressions.</p>\n<pre><code class=\"language-sql\">-- No FROM clause: evaluate an expression directly\nSELECT typeof(42), typeof(3.14), typeof('text'), typeof(NULL);\n</code></pre>\n<h4 id=\"single-table\"><a class=\"header\" href=\"#single-table\">Single Table</a></h4>\n<p>The simplest FROM clause names a single table. The query operates on all rows of that table.</p>\n<pre><code class=\"language-sql\">SELECT name, email FROM users;\n</code></pre>\n<h4 id=\"table-aliases\"><a class=\"header\" href=\"#table-aliases\">Table Aliases</a></h4>\n<p>A table can be given an alias with the AS keyword (or simply by placing the alias after the table name). The alias can then be used to qualify column names.</p>\n<pre><code class=\"language-sql\">SELECT u.name, u.email\n  FROM users AS u\n  WHERE u.active = 1;\n</code></pre>\n<p>Aliases are required when the same table appears more than once in a query, and they are convenient for shortening long table names.</p>\n<h4 id=\"multiple-tables\"><a class=\"header\" href=\"#multiple-tables\">Multiple Tables</a></h4>\n<p>When multiple tables are listed in the FROM clause separated by commas, Turso computes the <strong>Cartesian product</strong> of all rows from each table. This means every combination of rows is produced. A WHERE clause is typically used to filter the result to only the meaningful combinations.</p>\n<pre><code class=\"language-sql\">-- Comma-separated tables with a WHERE condition (implicit join)\nSELECT u.name, o.amount\n  FROM users AS u, orders AS o\n  WHERE u.id = o.user_id;\n</code></pre>\n<p>A comma between tables is equivalent to INNER JOIN or JOIN with no ON clause. For explicit join syntax with ON or USING clauses, see <a href=\"#join\">JOINs</a>.</p>\n<h4 id=\"subqueries-in-from\"><a class=\"header\" href=\"#subqueries-in-from\">Subqueries in FROM</a></h4>\n<p>A parenthesized SELECT statement can appear in the FROM clause. The subquery is treated as a virtual table containing the data it returns. A subquery in FROM should be given an alias.</p>\n<pre><code class=\"language-sql\">SELECT *\n  FROM (\n    SELECT user_id, sum(amount) AS total\n      FROM orders\n      GROUP BY user_id\n  ) AS user_totals\n  WHERE total &gt; 500;\n</code></pre>\n<p>Each column of the subquery inherits the type affinity and collation of the corresponding expression in the subquery’s result list.</p>\n<h3 id=\"where\"><a class=\"header\" href=\"#where\">WHERE</a></h3>\n<p>The WHERE clause filters the input rows by evaluating its expression as a boolean for each row. Only rows where the expression evaluates to true are included in the result. Rows for which the expression evaluates to false or NULL are excluded.</p>\n<pre><code class=\"language-sql\">-- Simple equality condition\nSELECT name, email FROM users WHERE active = 1;\n</code></pre>\n<pre><code class=\"language-sql\">-- Multiple conditions with AND\nSELECT name, email FROM users WHERE active = 1 AND name &lt;&gt; 'Alice';\n</code></pre>\n<pre><code class=\"language-sql\">-- Using OR to match alternative conditions\nSELECT name, price\n  FROM products\n  WHERE category = 'Electronics' OR price &lt; 250;\n</code></pre>\n<h4 id=\"null-handling\"><a class=\"header\" href=\"#null-handling\">NULL Handling</a></h4>\n<p>Comparisons with NULL using <code>=</code> or <code>&lt;&gt;</code> always evaluate to NULL (not true or false), so rows with NULL values in the compared column are excluded by such conditions. Use IS NULL and IS NOT NULL to test for null values explicitly.</p>\n<pre><code class=\"language-sql\">-- Find rows where email is missing\nSELECT name FROM users WHERE email IS NULL;\n</code></pre>\n<pre><code class=\"language-sql\">-- Find rows where email is present\nSELECT name, email FROM users WHERE email IS NOT NULL;\n</code></pre>\n<h4 id=\"pattern-matching-with-like\"><a class=\"header\" href=\"#pattern-matching-with-like\">Pattern Matching with LIKE</a></h4>\n<p>The LIKE operator performs case-insensitive pattern matching on text values. The <code>%</code> wildcard matches any sequence of characters, and <code>_</code> matches any single character.</p>\n<pre><code class=\"language-sql\">-- Names starting with 'A'\nSELECT name, email FROM users WHERE name LIKE 'A%';\n</code></pre>\n<pre><code class=\"language-sql\">-- Names containing 'a' anywhere (case-insensitive)\nSELECT name FROM products WHERE name LIKE '%a%';\n</code></pre>\n<p>For more details, see <a href=\"#pattern-matching\">Pattern Matching</a>.</p>\n<h4 id=\"between\"><a class=\"header\" href=\"#between\">BETWEEN</a></h4>\n<p>The BETWEEN operator tests whether a value falls within an inclusive range.</p>\n<pre><code class=\"language-sql\">SELECT name, price FROM products WHERE price BETWEEN 200 AND 700;\n</code></pre>\n<p>For more details, see <a href=\"#in-and-between\">IN and BETWEEN</a>.</p>\n<h4 id=\"in\"><a class=\"header\" href=\"#in\">IN</a></h4>\n<p>The IN operator tests whether a value matches any value in a list or subquery result.</p>\n<pre><code class=\"language-sql\">SELECT name, category\n  FROM products\n  WHERE category IN ('Electronics', 'Furniture') AND price &gt; 500;\n</code></pre>\n<p>For more details, see <a href=\"#in-and-between\">IN and BETWEEN</a>.</p>\n<h3 id=\"distinct-and-all\"><a class=\"header\" href=\"#distinct-and-all\">DISTINCT and ALL</a></h3>\n<p>By default (or when ALL is specified explicitly), all result rows are returned, including duplicates. When DISTINCT is specified, duplicate rows are removed from the result set before it is returned.</p>\n<pre><code class=\"language-sql\">-- Without DISTINCT: may contain duplicate categories\nSELECT ALL category FROM products;\n\n-- With DISTINCT: each category appears only once\nSELECT DISTINCT category FROM products;\n</code></pre>\n<p>For the purposes of detecting duplicates, two NULL values are considered equal. An integer is equal to a floating-point number if they represent the same quantity. Text values are compared using the appropriate collation sequence.</p>\n<h2 id=\"examples\"><a class=\"header\" href=\"#examples\">Examples</a></h2>\n<p>The examples below use the following tables:</p>\n<pre><code class=\"language-sql\">CREATE TABLE users (\n  id INTEGER PRIMARY KEY,\n  name TEXT,\n  email TEXT,\n  active INTEGER\n);\nINSERT INTO users VALUES\n  (1, 'Alice', 'alice@example.com', 1),\n  (2, 'Bob', 'bob@example.com', 0),\n  (3, 'Charlie', 'charlie@example.com', 1);\n\nCREATE TABLE products (\n  id INTEGER PRIMARY KEY,\n  name TEXT,\n  category TEXT,\n  price REAL\n);\nINSERT INTO products VALUES\n  (1, 'Laptop', 'Electronics', 999.99),\n  (2, 'Phone', 'Electronics', 699.99),\n  (3, 'Desk', 'Furniture', 299.99),\n  (4, 'Chair', 'Furniture', 199.99),\n  (5, 'Tablet', 'Electronics', 499.99);\n\nCREATE TABLE orders (\n  id INTEGER PRIMARY KEY,\n  user_id INTEGER,\n  product TEXT,\n  amount REAL\n);\nINSERT INTO orders VALUES\n  (1, 1, 'Laptop', 999.99),\n  (2, 1, 'Phone', 699.99),\n  (3, 2, 'Desk', 299.99),\n  (4, 3, 'Chair', 199.99),\n  (5, 1, 'Tablet', 499.99);\n</code></pre>\n<h3 id=\"selecting-all-columns\"><a class=\"header\" href=\"#selecting-all-columns\">Selecting All Columns</a></h3>\n<pre><code class=\"language-sql\">SELECT * FROM users;\n-- id | name    | email               | active\n-- 1  | Alice   | alice@example.com   | 1\n-- 2  | Bob     | bob@example.com     | 0\n-- 3  | Charlie | charlie@example.com | 1\n</code></pre>\n<h3 id=\"selecting-specific-columns\"><a class=\"header\" href=\"#selecting-specific-columns\">Selecting Specific Columns</a></h3>\n<pre><code class=\"language-sql\">SELECT name, email FROM users;\n-- name    | email\n-- Alice   | alice@example.com\n-- Bob     | bob@example.com\n-- Charlie | charlie@example.com\n</code></pre>\n<h3 id=\"filtering-with-where\"><a class=\"header\" href=\"#filtering-with-where\">Filtering with WHERE</a></h3>\n<pre><code class=\"language-sql\">SELECT name, email FROM users WHERE active = 1;\n-- name    | email\n-- Alice   | alice@example.com\n-- Charlie | charlie@example.com\n</code></pre>\n<h3 id=\"column-aliases-and-computed-expressions\"><a class=\"header\" href=\"#column-aliases-and-computed-expressions\">Column Aliases and Computed Expressions</a></h3>\n<pre><code class=\"language-sql\">SELECT\n  name,\n  price,\n  price * 0.9 AS discounted_price\nFROM products\nWHERE price &gt; 300;\n-- name   | price  | discounted_price\n-- Laptop | 999.99 | 899.991\n-- Phone  | 699.99 | 629.991\n-- Tablet | 499.99 | 449.991\n</code></pre>\n<h3 id=\"case-expressions-in-select\"><a class=\"header\" href=\"#case-expressions-in-select\">CASE Expressions in SELECT</a></h3>\n<pre><code class=\"language-sql\">SELECT\n  name,\n  price,\n  CASE\n    WHEN price &gt; 500 THEN 'expensive'\n    WHEN price &gt; 200 THEN 'moderate'\n    ELSE 'affordable'\n  END AS price_tier\nFROM products;\n-- name   | price  | price_tier\n-- Laptop | 999.99 | expensive\n-- Phone  | 699.99 | expensive\n-- Desk   | 299.99 | moderate\n-- Chair  | 199.99 | affordable\n-- Tablet | 499.99 | moderate\n</code></pre>\n<h3 id=\"multiple-tables-with-implicit-join\"><a class=\"header\" href=\"#multiple-tables-with-implicit-join\">Multiple Tables with Implicit Join</a></h3>\n<pre><code class=\"language-sql\">SELECT u.name, o.amount\n  FROM users AS u, orders AS o\n  WHERE u.id = o.user_id;\n-- name  | amount\n-- Alice | 999.99\n-- Alice | 699.99\n-- Alice | 499.99\n-- Bob   | 299.99\n-- Charlie | 199.99\n</code></pre>\n<h3 id=\"subquery-in-from\"><a class=\"header\" href=\"#subquery-in-from\">Subquery in FROM</a></h3>\n<pre><code class=\"language-sql\">SELECT *\n  FROM (\n    SELECT user_id, sum(amount) AS total\n      FROM orders\n      GROUP BY user_id\n  ) AS user_totals\n  WHERE total &gt; 500;\n-- user_id | total\n-- 1       | 2199.97\n</code></pre>\n<h3 id=\"distinct\"><a class=\"header\" href=\"#distinct\">DISTINCT</a></h3>\n<pre><code class=\"language-sql\">SELECT DISTINCT category FROM products;\n-- category\n-- Electronics\n-- Furniture\n</code></pre>\n<h2 id=\"compatibility\"><a class=\"header\" href=\"#compatibility\">Compatibility</a></h2>\n<p>Turso supports the standard SELECT statement with the following note:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Feature</th><th>Status</th></tr>\n</thead>\n<tbody>\n<tr><td><code>schema.table.column</code> (three-part names)</td><td>Not supported. Turso does not support attached databases or schema-qualified table names. Use <code>table.column</code> (two-part names) instead.</td></tr>\n</tbody>\n</table>\n</div>\n<div style=\"break-before: page; page-break-before: always;\"></div>\n<h1 id=\"join\"><a class=\"header\" href=\"#join\">JOIN</a></h1>\n<h2 id=\"syntax-1\"><a class=\"header\" href=\"#syntax-1\">Syntax</a></h2>\n<pre><code class=\"language-sql\">table-or-subquery {[INNER] JOIN | LEFT [OUTER] JOIN | NATURAL [LEFT [OUTER]] JOIN} table-or-subquery [join-constraint]\n</code></pre>\n<p>Where <code>join-constraint</code> is one of:</p>\n<pre><code class=\"language-sql\">ON expr\nUSING (column-name [, ...])\n</code></pre>\n<p>Tables may also be joined implicitly using a comma:</p>\n<pre><code class=\"language-sql\">SELECT ... FROM table1, table2 WHERE expr\n</code></pre>\n<h2 id=\"description-1\"><a class=\"header\" href=\"#description-1\">Description</a></h2>\n<p>A JOIN combines rows from two or more tables based on a related column between them. The result of a join is a new set of rows, where each row contains columns from both tables.</p>\n<p>Conceptually, a join starts with the cartesian product of the left and right datasets – every row from the left table paired with every row from the right table. A join constraint (ON or USING) then filters this cartesian product to only the rows where the constraint is satisfied. Different join types control what happens with rows that have no match.</p>\n<p>Turso supports INNER JOIN, LEFT OUTER JOIN, and NATURAL JOIN. Multiple joins can be chained in a single query and are evaluated left to right.</p>\n<h2 id=\"join-types\"><a class=\"header\" href=\"#join-types\">Join Types</a></h2>\n<h3 id=\"inner-join\"><a class=\"header\" href=\"#inner-join\">INNER JOIN</a></h3>\n<p>An INNER JOIN returns only the rows where the join constraint is satisfied in both tables. Rows from either table that have no matching row in the other table are excluded from the result.</p>\n<p>The keyword <code>INNER</code> is optional – <code>JOIN</code> by itself is equivalent to <code>INNER JOIN</code>.</p>\n<pre><code class=\"language-sql\">-- These are equivalent\nSELECT * FROM users INNER JOIN departments ON users.department_id = departments.id;\nSELECT * FROM users JOIN departments ON users.department_id = departments.id;\n</code></pre>\n<h3 id=\"left-outer-join\"><a class=\"header\" href=\"#left-outer-join\">LEFT OUTER JOIN</a></h3>\n<p>A LEFT JOIN returns all rows from the left table, even if there is no matching row in the right table. When a left-table row has no match, the columns from the right table are filled with NULL.</p>\n<p>The keyword <code>OUTER</code> is optional – <code>LEFT JOIN</code> and <code>LEFT OUTER JOIN</code> are equivalent.</p>\n<pre><code class=\"language-sql\">-- These are equivalent\nSELECT * FROM users LEFT JOIN departments ON users.department_id = departments.id;\nSELECT * FROM users LEFT OUTER JOIN departments ON users.department_id = departments.id;\n</code></pre>\n<h3 id=\"natural-join\"><a class=\"header\" href=\"#natural-join\">NATURAL JOIN</a></h3>\n<p>A NATURAL JOIN automatically joins two tables on all columns that share the same name in both tables. It is equivalent to a join with a USING clause that lists every common column name.</p>\n<p>If the two tables share no column names, a NATURAL JOIN behaves like a cartesian product (every row paired with every row).</p>\n<p>NATURAL can be combined with LEFT to form a NATURAL LEFT JOIN. A NATURAL JOIN cannot have an explicit ON or USING clause.</p>\n<pre><code class=\"language-sql\">-- If both tables have a column named \"id\", this is equivalent to:\n-- SELECT * FROM users JOIN profiles USING(id)\nSELECT * FROM users NATURAL JOIN profiles;\n</code></pre>\n<h3 id=\"comma-separated-tables-implicit-join\"><a class=\"header\" href=\"#comma-separated-tables-implicit-join\">Comma-Separated Tables (Implicit Join)</a></h3>\n<p>Listing tables separated by commas in the FROM clause produces the cartesian product of those tables. A WHERE clause is typically used to filter the result, which is functionally equivalent to an INNER JOIN with an ON clause.</p>\n<pre><code class=\"language-sql\">-- These produce the same result\nSELECT * FROM users, departments WHERE users.department_id = departments.id;\nSELECT * FROM users JOIN departments ON users.department_id = departments.id;\n</code></pre>\n<h2 id=\"join-constraints\"><a class=\"header\" href=\"#join-constraints\">Join Constraints</a></h2>\n<h3 id=\"on-clause\"><a class=\"header\" href=\"#on-clause\">ON Clause</a></h3>\n<p>The ON clause specifies a boolean expression that is evaluated for each row of the cartesian product. Only rows where the expression evaluates to true are included in the result. The expression can reference columns from both tables.</p>\n<pre><code class=\"language-sql\">SELECT users.name, departments.name\nFROM users JOIN departments ON users.department_id = departments.id;\n</code></pre>\n<p>The ON clause can contain compound conditions using AND, OR, and other operators:</p>\n<pre><code class=\"language-sql\">SELECT u.name, o.amount\nFROM users u LEFT JOIN orders o ON u.id = o.user_id AND o.amount &gt; 75.00;\n</code></pre>\n<h3 id=\"using-clause\"><a class=\"header\" href=\"#using-clause\">USING Clause</a></h3>\n<p>The USING clause specifies one or more column names that must exist in both tables. For each named column, the join matches rows where the values are equal. This is equivalent to writing <code>ON left.col = right.col</code> for each column, but more concise.</p>\n<p>An important difference from ON: the USING clause eliminates the duplicate column from the result. Only one copy of each named column appears in the output.</p>\n<pre><code class=\"language-sql\">SELECT users.name, departments.dept_name\nFROM users JOIN departments USING(department_id);\n</code></pre>\n<p>Multiple columns can be specified in a single USING clause:</p>\n<pre><code class=\"language-sql\">SELECT * FROM t1 JOIN t2 USING(a, b);\n</code></pre>\n<h2 id=\"on-vs-where-in-outer-joins\"><a class=\"header\" href=\"#on-vs-where-in-outer-joins\">ON vs WHERE in Outer Joins</a></h2>\n<p>For INNER JOINs, placing a condition in the ON clause or the WHERE clause produces the same result. For LEFT JOINs, however, the distinction matters:</p>\n<ul>\n<li><strong>ON clause</strong>: The condition is applied during the join. Rows from the left table that do not match still appear in the result with NULLs for the right-table columns.</li>\n<li><strong>WHERE clause</strong>: The condition is applied after the join, including after NULL rows have been added. This can filter out the unmatched rows.</li>\n</ul>\n<p>Consider this example where Charlie has no department (<code>department_id</code> is NULL):</p>\n<pre><code class=\"language-sql\">-- ON clause: Charlie appears with NULL department\nSELECT users.name, departments.name\nFROM users LEFT JOIN departments ON users.department_id = departments.id;\n</code></pre>\n<pre><code>┌─────────┬─────────────┐\n│ name    │ name        │\n├─────────┼─────────────┤\n│ Alice   │ Engineering │\n│ Bob     │ Marketing   │\n│ Charlie │             │\n└─────────┴─────────────┘\n</code></pre>\n<pre><code class=\"language-sql\">-- WHERE clause: Charlie is excluded because departments.name IS NOT NULL fails\nSELECT users.name, departments.name\nFROM users LEFT JOIN departments ON users.department_id = departments.id\nWHERE departments.name IS NOT NULL;\n</code></pre>\n<pre><code>┌───────┬─────────────┐\n│ name  │ name        │\n├───────┼─────────────┤\n│ Alice │ Engineering │\n│ Bob   │ Marketing   │\n└───────┴─────────────┘\n</code></pre>\n<h2 id=\"examples-1\"><a class=\"header\" href=\"#examples-1\">Examples</a></h2>\n<h3 id=\"basic-inner-join\"><a class=\"header\" href=\"#basic-inner-join\">Basic Inner Join</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, department_id INTEGER);\nCREATE TABLE departments (id INTEGER PRIMARY KEY, name TEXT);\n\nINSERT INTO users VALUES (1, 'Alice', 1);\nINSERT INTO users VALUES (2, 'Bob', 2);\nINSERT INTO users VALUES (3, 'Charlie', NULL);\n\nINSERT INTO departments VALUES (1, 'Engineering');\nINSERT INTO departments VALUES (2, 'Marketing');\nINSERT INTO departments VALUES (3, 'Sales');\n\n-- Return only users that have a matching department\nSELECT users.name, departments.name\nFROM users INNER JOIN departments ON users.department_id = departments.id;\n</code></pre>\n<pre><code>┌───────┬─────────────┐\n│ name  │ name        │\n├───────┼─────────────┤\n│ Alice │ Engineering │\n│ Bob   │ Marketing   │\n└───────┴─────────────┘\n</code></pre>\n<h3 id=\"left-join-to-find-unmatched-rows\"><a class=\"header\" href=\"#left-join-to-find-unmatched-rows\">Left Join to Find Unmatched Rows</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT);\nCREATE TABLE orders (id INTEGER PRIMARY KEY, user_id INTEGER, amount REAL);\n\nINSERT INTO users VALUES (1, 'Alice');\nINSERT INTO users VALUES (2, 'Bob');\nINSERT INTO users VALUES (3, 'Charlie');\n\nINSERT INTO orders VALUES (1, 1, 99.99);\nINSERT INTO orders VALUES (2, 1, 49.50);\nINSERT INTO orders VALUES (3, 2, 150.00);\n\n-- Find users who have never placed an order\nSELECT u.name\nFROM users u LEFT JOIN orders o ON u.id = o.user_id\nWHERE o.id IS NULL;\n</code></pre>\n<pre><code>┌─────────┐\n│ name    │\n├─────────┤\n│ Charlie │\n└─────────┘\n</code></pre>\n<h3 id=\"self-join\"><a class=\"header\" href=\"#self-join\">Self-Join</a></h3>\n<p>A table can be joined to itself using aliases. This is useful for hierarchical data such as employee-manager relationships.</p>\n<pre><code class=\"language-sql\">CREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT, manager_id INTEGER);\n\nINSERT INTO employees VALUES (1, 'Alice', NULL);\nINSERT INTO employees VALUES (2, 'Bob', 1);\nINSERT INTO employees VALUES (3, 'Charlie', 1);\nINSERT INTO employees VALUES (4, 'Diana', 2);\n\n-- Show each employee alongside their manager's name\nSELECT e.name AS employee, m.name AS manager\nFROM employees e LEFT JOIN employees m ON e.manager_id = m.id;\n</code></pre>\n<pre><code>┌──────────┬─────────┐\n│ employee │ manager │\n├──────────┼─────────┤\n│ Alice    │         │\n│ Bob      │ Alice   │\n│ Charlie  │ Alice   │\n│ Diana    │ Bob     │\n└──────────┴─────────┘\n</code></pre>\n<h3 id=\"multi-table-join-with-aggregation\"><a class=\"header\" href=\"#multi-table-join-with-aggregation\">Multi-Table Join with Aggregation</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, department_id INTEGER);\nCREATE TABLE departments (id INTEGER PRIMARY KEY, name TEXT);\nCREATE TABLE orders (id INTEGER PRIMARY KEY, user_id INTEGER, amount REAL);\n\nINSERT INTO users VALUES (1, 'Alice', 1);\nINSERT INTO users VALUES (2, 'Bob', 2);\nINSERT INTO users VALUES (3, 'Charlie', NULL);\n\nINSERT INTO departments VALUES (1, 'Engineering');\nINSERT INTO departments VALUES (2, 'Marketing');\nINSERT INTO departments VALUES (3, 'Sales');\n\nINSERT INTO orders VALUES (1, 1, 99.99);\nINSERT INTO orders VALUES (2, 1, 49.50);\nINSERT INTO orders VALUES (3, 2, 150.00);\n\n-- Show each user's department and total spending\nSELECT u.name, d.name AS department, SUM(o.amount) AS total_spent\nFROM users u\nJOIN departments d ON u.department_id = d.id\nLEFT JOIN orders o ON u.id = o.user_id\nGROUP BY u.id;\n</code></pre>\n<pre><code>┌───────┬─────────────┬─────────────┐\n│ name  │ department  │ total_spent │\n├───────┼─────────────┼─────────────┤\n│ Alice │ Engineering │      149.49 │\n│ Bob   │ Marketing   │       150.0 │\n└───────┴─────────────┴─────────────┘\n</code></pre>\n<h3 id=\"natural-left-join\"><a class=\"header\" href=\"#natural-left-join\">Natural Left Join</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT);\nCREATE TABLE profiles (id INTEGER PRIMARY KEY, bio TEXT);\n\nINSERT INTO users VALUES (1, 'Alice');\nINSERT INTO users VALUES (2, 'Bob');\nINSERT INTO users VALUES (3, 'Charlie');\n\nINSERT INTO profiles VALUES (1, 'Engineer');\nINSERT INTO profiles VALUES (2, 'Designer');\n\n-- NATURAL LEFT JOIN matches on the shared \"id\" column\n-- and preserves users with no profile\nSELECT name, bio FROM users NATURAL LEFT JOIN profiles;\n</code></pre>\n<pre><code>┌─────────┬──────────┐\n│ name    │ bio      │\n├─────────┼──────────┤\n│ Alice   │ Engineer │\n│ Bob     │ Designer │\n│ Charlie │          │\n└─────────┴──────────┘\n</code></pre>\n<h3 id=\"join-with-a-subquery\"><a class=\"header\" href=\"#join-with-a-subquery\">Join with a Subquery</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT);\nCREATE TABLE orders (id INTEGER PRIMARY KEY, user_id INTEGER, amount REAL);\n\nINSERT INTO users VALUES (1, 'Alice');\nINSERT INTO users VALUES (2, 'Bob');\nINSERT INTO users VALUES (3, 'Charlie');\n\nINSERT INTO orders VALUES (1, 1, 99.99);\nINSERT INTO orders VALUES (2, 1, 49.50);\nINSERT INTO orders VALUES (3, 2, 150.00);\n\n-- Join against an aggregated subquery\nSELECT u.name, totals.total_amount\nFROM users u\nJOIN (SELECT user_id, SUM(amount) AS total_amount FROM orders GROUP BY user_id) AS totals\n  ON u.id = totals.user_id;\n</code></pre>\n<pre><code>┌───────┬──────────────┐\n│ name  │ total_amount │\n├───────┼──────────────┤\n│ Alice │       149.49 │\n│ Bob   │        150.0 │\n└───────┴──────────────┘\n</code></pre>\n<h3 id=\"cartesian-product-with-comma-syntax\"><a class=\"header\" href=\"#cartesian-product-with-comma-syntax\">Cartesian Product with Comma Syntax</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE colors (name TEXT);\nCREATE TABLE sizes (name TEXT);\n\nINSERT INTO colors VALUES ('red');\nINSERT INTO colors VALUES ('blue');\n\nINSERT INTO sizes VALUES ('small');\nINSERT INTO sizes VALUES ('large');\n\n-- Every combination of color and size\nSELECT colors.name, sizes.name FROM colors, sizes;\n</code></pre>\n<pre><code>┌──────┬───────┐\n│ name │ name  │\n├──────┼───────┤\n│ red  │ small │\n│ red  │ large │\n│ blue │ small │\n│ blue │ large │\n└──────┴───────┘\n</code></pre>\n<h2 id=\"compatibility-1\"><a class=\"header\" href=\"#compatibility-1\">Compatibility</a></h2>\n<p>Turso does not support the following join features available in SQLite:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Feature</th><th>Status</th></tr>\n</thead>\n<tbody>\n<tr><td>RIGHT JOIN / RIGHT OUTER JOIN</td><td>Not supported</td></tr>\n<tr><td>FULL JOIN / FULL OUTER JOIN</td><td>Not supported</td></tr>\n<tr><td>CROSS JOIN</td><td>Not supported. In SQLite, <code>CROSS JOIN</code> is semantically identical to <code>INNER JOIN</code> but hints the optimizer not to reorder the join. Turso does not parse this syntax. Use <code>INNER JOIN</code> or comma syntax instead.</td></tr>\n</tbody>\n</table>\n</div>\n<div style=\"break-before: page; page-break-before: always;\"></div>\n<h1 id=\"group-by-and-having\"><a class=\"header\" href=\"#group-by-and-having\">GROUP BY and HAVING</a></h1>\n<h2 id=\"syntax-2\"><a class=\"header\" href=\"#syntax-2\">Syntax</a></h2>\n<pre><code class=\"language-sql\">SELECT result-column [, ...]\nFROM table-or-subquery\n[WHERE where-expr]\n[GROUP BY expr [, ...]]\n[HAVING having-expr]\n</code></pre>\n<h2 id=\"description-2\"><a class=\"header\" href=\"#description-2\">Description</a></h2>\n<p>The <code>GROUP BY</code> clause organizes rows into groups based on one or more expressions. When <code>GROUP BY</code> is present, each unique combination of values in the grouping expressions forms a single group, and the query returns one row per group. Aggregate functions in the result set (such as <code>COUNT()</code>, <code>SUM()</code>, <code>AVG()</code>, <code>MIN()</code>, <code>MAX()</code>, and <code>GROUP_CONCAT()</code>) are evaluated once per group rather than once for the entire result set.</p>\n<p>The <code>HAVING</code> clause filters groups after they have been formed. It works like <code>WHERE</code>, but operates on the grouped results rather than on individual rows. <code>HAVING</code> is evaluated once per group and may reference aggregate functions.</p>\n<p>Together, <code>GROUP BY</code> and <code>HAVING</code> enable summary queries – computing totals, averages, counts, and other statistics across categories of data.</p>\n<h2 id=\"clauses-1\"><a class=\"header\" href=\"#clauses-1\">Clauses</a></h2>\n<h3 id=\"group-by\"><a class=\"header\" href=\"#group-by\">GROUP BY</a></h3>\n<p>The <code>GROUP BY</code> clause accepts one or more expressions, separated by commas. Each expression is evaluated for every row in the input, and rows that produce equal values for all grouping expressions are combined into a single group.</p>\n<p>Key behaviors:</p>\n<ul>\n<li><strong>NULL values are considered equal</strong> for grouping purposes. All rows with NULL in a grouping column belong to the same group.</li>\n<li><strong>Expressions, not just column names</strong>, may be used. You can group by computed values, <code>CASE</code> expressions, or function calls.</li>\n<li><strong>Column position numbers</strong> may be used. <code>GROUP BY 1</code> refers to the first column in the result set.</li>\n<li><strong>Grouping expressions need not appear in the result set.</strong> You can group by a column without selecting it.</li>\n<li><strong>Grouping expressions must not be aggregate expressions.</strong> <code>GROUP BY SUM(x)</code> is an error.</li>\n<li><strong>Collation sequences apply</strong> when comparing TEXT values. The default collation is <code>BINARY</code>.</li>\n</ul>\n<p>When no <code>GROUP BY</code> clause is present but the result set contains aggregate functions, the entire input is treated as a single group and the query returns exactly one row.</p>\n<h3 id=\"having\"><a class=\"header\" href=\"#having\">HAVING</a></h3>\n<p>The <code>HAVING</code> clause contains a boolean expression that is evaluated once per group. Groups for which the expression evaluates to false (or NULL) are excluded from the result set.</p>\n<ul>\n<li><code>HAVING</code> may reference aggregate functions. This is the primary distinction from <code>WHERE</code>, which cannot.</li>\n<li><code>HAVING</code> may reference values that are not in the result set.</li>\n<li>If <code>HAVING</code> contains a non-aggregate expression, it is evaluated against an arbitrarily selected row from the group.</li>\n<li><code>HAVING</code> can be used without <code>GROUP BY</code>. In that case the entire result set is treated as one group, and <code>HAVING</code> determines whether that single group is returned or discarded.</li>\n</ul>\n<h3 id=\"evaluation-order\"><a class=\"header\" href=\"#evaluation-order\">Evaluation Order</a></h3>\n<p>When <code>WHERE</code>, <code>GROUP BY</code>, and <code>HAVING</code> all appear in the same query, they are processed in this order:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Step</th><th>Clause</th><th>Purpose</th></tr>\n</thead>\n<tbody>\n<tr><td>1</td><td><code>WHERE</code></td><td>Filters individual rows before grouping</td></tr>\n<tr><td>2</td><td><code>GROUP BY</code></td><td>Organizes remaining rows into groups</td></tr>\n<tr><td>3</td><td>Aggregate functions</td><td>Computed once per group</td></tr>\n<tr><td>4</td><td><code>HAVING</code></td><td>Filters groups after aggregation</td></tr>\n</tbody>\n</table>\n</div>\n<p>This means <code>WHERE</code> reduces the input before any grouping occurs, while <code>HAVING</code> operates on the already-grouped results. Use <code>WHERE</code> to exclude rows you do not want aggregated. Use <code>HAVING</code> to exclude groups based on aggregate values.</p>\n<h2 id=\"bare-columns-in-aggregate-queries\"><a class=\"header\" href=\"#bare-columns-in-aggregate-queries\">Bare Columns in Aggregate Queries</a></h2>\n<p>A “bare” column is a non-aggregate column that does not appear in the <code>GROUP BY</code> clause. For example:</p>\n<pre><code class=\"language-sql\">SELECT customer, product, SUM(quantity) FROM orders GROUP BY customer;\n</code></pre>\n<p>Here <code>customer</code> is in <code>GROUP BY</code>, <code>SUM(quantity)</code> is an aggregate, but <code>product</code> is a bare column. Since each group may contain multiple distinct values for <code>product</code>, the value of <code>product</code> in the result is selected from an arbitrary row within the group.</p>\n<p><strong>Special behavior with <code>MIN()</code> and <code>MAX()</code>:</strong> When there is exactly one <code>MIN()</code> or <code>MAX()</code> aggregate in the query, bare columns take their values from the row that contains the minimum or maximum value. For example:</p>\n<pre><code class=\"language-sql\">SELECT customer, product, MAX(quantity) AS max_quantity\nFROM orders\nGROUP BY customer;\n</code></pre>\n<p>The <code>product</code> value in each row comes from the input row that has the largest <code>quantity</code> for that customer.</p>\n<p>This behavior is an extension beyond the SQL standard. Most other database engines require every non-aggregate column to appear in the <code>GROUP BY</code> clause.</p>\n<h2 id=\"examples-2\"><a class=\"header\" href=\"#examples-2\">Examples</a></h2>\n<h3 id=\"basic-group-by-with-count\"><a class=\"header\" href=\"#basic-group-by-with-count\">Basic GROUP BY with COUNT</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE orders (\n  id INTEGER PRIMARY KEY,\n  customer TEXT,\n  product TEXT,\n  quantity INTEGER,\n  price REAL,\n  region TEXT\n);\nINSERT INTO orders VALUES (1, 'Alice', 'Widget', 5, 9.99, 'East');\nINSERT INTO orders VALUES (2, 'Bob', 'Gadget', 2, 24.99, 'West');\nINSERT INTO orders VALUES (3, 'Alice', 'Gadget', 1, 24.99, 'East');\nINSERT INTO orders VALUES (4, 'Carol', 'Widget', 10, 9.99, 'East');\nINSERT INTO orders VALUES (5, 'Bob', 'Widget', 3, 9.99, 'West');\nINSERT INTO orders VALUES (6, 'Alice', 'Gizmo', 2, 49.99, 'East');\nINSERT INTO orders VALUES (7, 'Carol', 'Gadget', 4, 24.99, 'West');\nINSERT INTO orders VALUES (8, 'Dave', 'Widget', 7, 9.99, 'South');\n\n-- Count orders per customer\nSELECT customer, COUNT(*) AS order_count\nFROM orders\nGROUP BY customer;\n-- Alice|3\n-- Bob|2\n-- Carol|2\n-- Dave|1\n</code></pre>\n<h3 id=\"group-by-with-sum\"><a class=\"header\" href=\"#group-by-with-sum\">GROUP BY with SUM</a></h3>\n<pre><code class=\"language-sql\">-- Total quantity and revenue per product\nSELECT product,\n       SUM(quantity) AS total_quantity,\n       SUM(quantity * price) AS total_revenue\nFROM orders\nGROUP BY product;\n-- Gadget|7|174.93\n-- Gizmo|2|99.98\n-- Widget|25|249.75\n</code></pre>\n<h3 id=\"group-by-with-having\"><a class=\"header\" href=\"#group-by-with-having\">GROUP BY with HAVING</a></h3>\n<pre><code class=\"language-sql\">-- Customers who spent more than $100\nSELECT customer, SUM(quantity * price) AS total_spent\nFROM orders\nGROUP BY customer\nHAVING SUM(quantity * price) &gt; 100;\n-- Alice|174.92\n-- Carol|199.86\n</code></pre>\n<h3 id=\"multiple-group-by-columns\"><a class=\"header\" href=\"#multiple-group-by-columns\">Multiple GROUP BY Columns</a></h3>\n<pre><code class=\"language-sql\">-- Quantity sold per region and product\nSELECT region, product, SUM(quantity) AS total_quantity\nFROM orders\nGROUP BY region, product;\n-- East|Gadget|1\n-- East|Gizmo|2\n-- East|Widget|15\n-- South|Widget|7\n-- West|Gadget|6\n-- West|Widget|3\n</code></pre>\n<h3 id=\"group-by-with-expression\"><a class=\"header\" href=\"#group-by-with-expression\">GROUP BY with Expression</a></h3>\n<pre><code class=\"language-sql\">-- Classify products into price tiers and count orders per tier\nSELECT CASE WHEN price &lt; 20 THEN 'Budget' ELSE 'Premium' END AS tier,\n       COUNT(*) AS order_count\nFROM orders\nGROUP BY CASE WHEN price &lt; 20 THEN 'Budget' ELSE 'Premium' END;\n-- Budget|4\n-- Premium|4\n</code></pre>\n<h3 id=\"combining-where-group-by-and-having\"><a class=\"header\" href=\"#combining-where-group-by-and-having\">Combining WHERE, GROUP BY, and HAVING</a></h3>\n<pre><code class=\"language-sql\">-- Products in the East region with total quantity above 3\nSELECT product, SUM(quantity) AS total_quantity\nFROM orders\nWHERE region = 'East'\nGROUP BY product\nHAVING SUM(quantity) &gt; 3;\n-- Widget|15\n</code></pre>\n<h3 id=\"multiple-aggregate-functions\"><a class=\"header\" href=\"#multiple-aggregate-functions\">Multiple Aggregate Functions</a></h3>\n<pre><code class=\"language-sql\">-- Summary statistics per product\nSELECT product,\n       AVG(quantity) AS avg_quantity,\n       MIN(quantity) AS min_quantity,\n       MAX(quantity) AS max_quantity\nFROM orders\nGROUP BY product;\n-- Gadget|2.33333333333333|1|4\n-- Gizmo|2.0|2|2\n-- Widget|6.25|3|10\n</code></pre>\n<h3 id=\"countdistinct-with-group-by\"><a class=\"header\" href=\"#countdistinct-with-group-by\">COUNT(DISTINCT) with GROUP BY</a></h3>\n<pre><code class=\"language-sql\">-- Count unique customers and products per region\nSELECT region,\n       COUNT(DISTINCT customer) AS unique_customers,\n       COUNT(DISTINCT product) AS unique_products\nFROM orders\nGROUP BY region;\n-- East|2|3\n-- South|1|1\n-- West|2|2\n</code></pre>\n<h3 id=\"group_concat-with-group-by\"><a class=\"header\" href=\"#group_concat-with-group-by\">GROUP_CONCAT with GROUP BY</a></h3>\n<pre><code class=\"language-sql\">-- List distinct products purchased by each customer\nSELECT customer, GROUP_CONCAT(DISTINCT product) AS products\nFROM orders\nGROUP BY customer;\n-- Alice|Widget,Gadget,Gizmo\n-- Bob|Gadget,Widget\n-- Carol|Widget,Gadget\n-- Dave|Widget\n</code></pre>\n<h3 id=\"aggregate-without-group-by\"><a class=\"header\" href=\"#aggregate-without-group-by\">Aggregate Without GROUP BY</a></h3>\n<p>When aggregate functions appear without a <code>GROUP BY</code> clause, the entire table is treated as one group:</p>\n<pre><code class=\"language-sql\">-- Overall totals across all orders\nSELECT COUNT(*) AS total_orders, SUM(quantity) AS total_items\nFROM orders;\n-- 8|34\n</code></pre>\n<h3 id=\"having-without-group-by\"><a class=\"header\" href=\"#having-without-group-by\">HAVING Without GROUP BY</a></h3>\n<pre><code class=\"language-sql\">-- Return total revenue only if it exceeds $500\nSELECT SUM(quantity * price) AS revenue\nFROM orders\nHAVING SUM(quantity * price) &gt; 500;\n-- 524.66\n</code></pre>\n<h3 id=\"null-grouping\"><a class=\"header\" href=\"#null-grouping\">NULL Grouping</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE survey (\n  id INTEGER PRIMARY KEY,\n  respondent TEXT,\n  rating INTEGER\n);\nINSERT INTO survey VALUES (1, 'Alice', 5);\nINSERT INTO survey VALUES (2, NULL, 3);\nINSERT INTO survey VALUES (3, 'Bob', 4);\nINSERT INTO survey VALUES (4, NULL, 2);\nINSERT INTO survey VALUES (5, 'Alice', 3);\n\n-- NULL respondents are grouped together\nSELECT respondent, COUNT(*) AS responses, AVG(rating) AS avg_rating\nFROM survey\nGROUP BY respondent;\n-- (NULL)|2|2.5\n-- Alice|2|4.0\n-- Bob|1|4.0\n</code></pre>\n<h3 id=\"group-by-with-column-position\"><a class=\"header\" href=\"#group-by-with-column-position\">GROUP BY with Column Position</a></h3>\n<pre><code class=\"language-sql\">-- Group by the first column in the result set\nSELECT customer, COUNT(*) AS order_count\nFROM orders\nGROUP BY 1;\n-- Alice|3\n-- Bob|2\n-- Carol|2\n-- Dave|1\n</code></pre>\n<div style=\"break-before: page; page-break-before: always;\"></div>\n<h1 id=\"order-by-limit-offset\"><a class=\"header\" href=\"#order-by-limit-offset\">ORDER BY, LIMIT, OFFSET</a></h1>\n<h2 id=\"syntax-3\"><a class=\"header\" href=\"#syntax-3\">Syntax</a></h2>\n<pre><code class=\"language-sql\">SELECT result-column [, ...]\nFROM table-or-subquery\n[WHERE expr]\n[GROUP BY expr [, ...]]\n[ORDER BY ordering-term [, ...]]\n[LIMIT expr [{OFFSET expr | , expr}]]\n</code></pre>\n<p>Where each <code>ordering-term</code> is:</p>\n<pre><code class=\"language-sql\">expr [{ASC | DESC}] [COLLATE collation-name]\n</code></pre>\n<h2 id=\"description-3\"><a class=\"header\" href=\"#description-3\">Description</a></h2>\n<p>The <code>ORDER BY</code> clause determines the order in which rows are returned by a <code>SELECT</code> statement. Without an <code>ORDER BY</code> clause, the order of rows in the result set is undefined – the database may return them in any order it chooses, and that order may differ between executions.</p>\n<p>The <code>LIMIT</code> clause places an upper bound on the number of rows returned. The optional <code>OFFSET</code> clause (or the comma syntax) skips a number of rows before returning results. Together, <code>ORDER BY</code>, <code>LIMIT</code>, and <code>OFFSET</code> are the building blocks for sorted output, top-N queries, and pagination.</p>\n<p>These clauses appear at the end of a <code>SELECT</code> statement, after any <code>WHERE</code>, <code>GROUP BY</code>, and <code>HAVING</code> clauses. In a compound <code>SELECT</code> (using <code>UNION</code>, <code>INTERSECT</code>, or <code>EXCEPT</code>), only the final <code>SELECT</code> may include <code>ORDER BY</code> and <code>LIMIT</code>, and they apply to the entire compound result.</p>\n<h2 id=\"clauses-2\"><a class=\"header\" href=\"#clauses-2\">Clauses</a></h2>\n<h3 id=\"order-by\"><a class=\"header\" href=\"#order-by\">ORDER BY</a></h3>\n<p>The <code>ORDER BY</code> clause accepts one or more ordering terms, separated by commas. Each term is an expression that defines a sort key. Rows are sorted by the first term; ties are broken by the second term, and so on.</p>\n<p>Each ordering term is evaluated against every row. The resulting values are compared to determine output order. By default, rows are sorted in ascending order (<code>ASC</code>), where smaller values come first.</p>\n<p><strong>Sort direction:</strong></p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Keyword</th><th>Behavior</th></tr>\n</thead>\n<tbody>\n<tr><td><code>ASC</code></td><td>Ascending order (default). Smaller values first.</td></tr>\n<tr><td><code>DESC</code></td><td>Descending order. Larger values first.</td></tr>\n</tbody>\n</table>\n</div>\n<p><strong>NULL handling:</strong></p>\n<p>Turso considers NULL values to be smaller than any other value for sorting purposes. This means:</p>\n<ul>\n<li>In ascending order (<code>ASC</code>), NULLs appear at the beginning of the result set.</li>\n<li>In descending order (<code>DESC</code>), NULLs appear at the end of the result set.</li>\n</ul>\n<p><strong>Expression resolution:</strong></p>\n<p>Each <code>ORDER BY</code> expression is resolved in the following order of precedence:</p>\n<ol>\n<li><strong>Integer constant K:</strong> Treated as a reference to the K-th column of the result set, numbered from left to right starting at 1.</li>\n<li><strong>Identifier matching a column alias:</strong> If the expression is a simple identifier that matches the alias of an output column, it refers to that column.</li>\n<li><strong>Arbitrary expression:</strong> Otherwise, the expression is evaluated per row and the resulting value determines sort order. Any valid expression may be used, including function calls, arithmetic, and <code>CASE</code> expressions.</li>\n</ol>\n<p><strong>Collation:</strong></p>\n<p>Text values are compared using a collation sequence. The collation used for each ordering term is determined by this precedence:</p>\n<ol>\n<li>If the ordering term includes <code>COLLATE collation-name</code>, that collation is used.</li>\n<li>If the ordering term refers to a column (directly or via alias) that has a default collation, that collation is used.</li>\n<li>Otherwise, the <code>BINARY</code> collation is used.</li>\n</ol>\n<p>The built-in collation sequences are:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Collation</th><th>Behavior</th></tr>\n</thead>\n<tbody>\n<tr><td><code>BINARY</code></td><td>Byte-by-byte comparison. Uppercase letters sort before lowercase. Default.</td></tr>\n<tr><td><code>NOCASE</code></td><td>Case-insensitive comparison for ASCII characters.</td></tr>\n<tr><td><code>RTRIM</code></td><td>Like <code>BINARY</code>, but trailing spaces are ignored.</td></tr>\n</tbody>\n</table>\n</div>\n<h3 id=\"limit\"><a class=\"header\" href=\"#limit\">LIMIT</a></h3>\n<p>The <code>LIMIT</code> clause restricts the number of rows returned. It takes a single expression that must evaluate to an integer (or a value that can be losslessly converted to an integer).</p>\n<ul>\n<li>If the LIMIT value is non-negative, at most that many rows are returned.</li>\n<li>If the LIMIT value is negative, there is no upper bound – all rows are returned.</li>\n<li>If the LIMIT expression evaluates to NULL or to a value that cannot be losslessly converted to an integer, an error is returned.</li>\n</ul>\n<p><code>LIMIT</code> is most useful when combined with <code>ORDER BY</code>. Without <code>ORDER BY</code>, the set of rows returned by <code>LIMIT</code> is arbitrary and unpredictable.</p>\n<h3 id=\"offset\"><a class=\"header\" href=\"#offset\">OFFSET</a></h3>\n<p>The <code>OFFSET</code> clause skips a specified number of rows from the beginning of the result set before returning rows. The expression must evaluate to an integer (or a value that can be losslessly converted to an integer).</p>\n<ul>\n<li>If the OFFSET value is zero or negative, no rows are skipped.</li>\n<li>If the OFFSET value is NULL or cannot be losslessly converted to an integer, an error is returned.</li>\n</ul>\n<p>When both <code>LIMIT</code> and <code>OFFSET</code> are specified, the first M rows are skipped (where M is the OFFSET value), and then the next N rows are returned (where N is the LIMIT value). If the result set contains fewer than M + N rows, all rows after the first M are returned.</p>\n<p><strong>Two syntax forms:</strong></p>\n<p>Turso supports two equivalent ways to write LIMIT with OFFSET:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Form</th><th>Syntax</th><th>Meaning</th></tr>\n</thead>\n<tbody>\n<tr><td>Keyword form</td><td><code>LIMIT N OFFSET M</code></td><td>Skip M rows, return at most N rows</td></tr>\n<tr><td>Comma form</td><td><code>LIMIT M, N</code></td><td>Skip M rows, return at most N rows</td></tr>\n</tbody>\n</table>\n</div>\n<p>Note that the comma form reverses the order of the values: the first value is the offset and the second is the limit. This is a common source of confusion. The keyword form (<code>LIMIT N OFFSET M</code>) is recommended for clarity.</p>\n<h2 id=\"examples-3\"><a class=\"header\" href=\"#examples-3\">Examples</a></h2>\n<h3 id=\"sorting-by-a-single-column\"><a class=\"header\" href=\"#sorting-by-a-single-column\">Sorting by a Single Column</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE products (\n  id INTEGER PRIMARY KEY,\n  name TEXT,\n  price REAL,\n  category TEXT\n);\nINSERT INTO products VALUES (1, 'Keyboard', 49.99, 'Electronics');\nINSERT INTO products VALUES (2, 'Notebook', 5.99, 'Stationery');\nINSERT INTO products VALUES (3, 'Monitor', 299.99, 'Electronics');\nINSERT INTO products VALUES (4, 'Pen', 1.99, 'Stationery');\nINSERT INTO products VALUES (5, 'Mouse', 29.99, 'Electronics');\n\n-- Sort by price, cheapest first (ascending is the default)\nSELECT name, price FROM products ORDER BY price;\n-- Pen|1.99\n-- Notebook|5.99\n-- Mouse|29.99\n-- Keyboard|49.99\n-- Monitor|299.99\n</code></pre>\n<h3 id=\"descending-order\"><a class=\"header\" href=\"#descending-order\">Descending Order</a></h3>\n<pre><code class=\"language-sql\">-- Sort by price, most expensive first\nSELECT name, price FROM products ORDER BY price DESC;\n-- Monitor|299.99\n-- Keyboard|49.99\n-- Mouse|29.99\n-- Notebook|5.99\n-- Pen|1.99\n</code></pre>\n<h3 id=\"sorting-by-multiple-columns\"><a class=\"header\" href=\"#sorting-by-multiple-columns\">Sorting by Multiple Columns</a></h3>\n<pre><code class=\"language-sql\">-- Sort by category ascending, then by price descending within each category\nSELECT name, category, price FROM products ORDER BY category ASC, price DESC;\n-- Monitor|Electronics|299.99\n-- Keyboard|Electronics|49.99\n-- Mouse|Electronics|29.99\n-- Notebook|Stationery|5.99\n-- Pen|Stationery|1.99\n</code></pre>\n<h3 id=\"ordering-by-column-number\"><a class=\"header\" href=\"#ordering-by-column-number\">Ordering by Column Number</a></h3>\n<pre><code class=\"language-sql\">-- Order by the second column in the result set (price)\nSELECT name, price FROM products ORDER BY 2;\n-- Pen|1.99\n-- Notebook|5.99\n-- Mouse|29.99\n-- Keyboard|49.99\n-- Monitor|299.99\n</code></pre>\n<h3 id=\"ordering-by-alias\"><a class=\"header\" href=\"#ordering-by-alias\">Ordering by Alias</a></h3>\n<pre><code class=\"language-sql\">-- Order by a computed column alias\nSELECT name, price * 1.1 AS price_with_tax\nFROM products ORDER BY price_with_tax LIMIT 3;\n-- Pen|2.189\n-- Notebook|6.589\n-- Mouse|32.989\n</code></pre>\n<h3 id=\"ordering-by-expression\"><a class=\"header\" href=\"#ordering-by-expression\">Ordering by Expression</a></h3>\n<pre><code class=\"language-sql\">-- Sort by name length (descending), then alphabetically for ties\nSELECT name, length(name) AS name_len\nFROM products ORDER BY length(name) DESC, name ASC LIMIT 3;\n-- Keyboard|8\n-- Notebook|8\n-- Monitor|7\n</code></pre>\n<h3 id=\"collation-in-order-by\"><a class=\"header\" href=\"#collation-in-order-by\">Collation in ORDER BY</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE words (word TEXT);\nINSERT INTO words VALUES ('banana');\nINSERT INTO words VALUES ('Apple');\nINSERT INTO words VALUES ('cherry');\nINSERT INTO words VALUES ('Blueberry');\n\n-- Default BINARY collation: uppercase letters sort before lowercase\nSELECT word FROM words ORDER BY word;\n-- Apple\n-- Blueberry\n-- banana\n-- cherry\n\n-- NOCASE collation: case-insensitive sorting\nSELECT word FROM words ORDER BY word COLLATE NOCASE;\n-- Apple\n-- banana\n-- Blueberry\n-- cherry\n</code></pre>\n<h3 id=\"null-ordering\"><a class=\"header\" href=\"#null-ordering\">NULL Ordering</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE scores (student TEXT, score INTEGER);\nINSERT INTO scores VALUES ('Alice', 90);\nINSERT INTO scores VALUES ('Bob', NULL);\nINSERT INTO scores VALUES ('Carol', 85);\nINSERT INTO scores VALUES ('Dave', NULL);\n\n-- Ascending: NULLs appear first (NULLs are considered smaller than all other values)\nSELECT student, score FROM scores ORDER BY score ASC;\n-- Bob|\n-- Dave|\n-- Carol|85\n-- Alice|90\n\n-- Descending: NULLs appear last\nSELECT student, score FROM scores ORDER BY score DESC;\n-- Alice|90\n-- Carol|85\n-- Bob|\n-- Dave|\n</code></pre>\n<h3 id=\"basic-limit\"><a class=\"header\" href=\"#basic-limit\">Basic LIMIT</a></h3>\n<pre><code class=\"language-sql\">-- Return the 3 cheapest products\nSELECT name, price FROM products ORDER BY price LIMIT 3;\n-- Pen|1.99\n-- Notebook|5.99\n-- Mouse|29.99\n</code></pre>\n<h3 id=\"limit-with-offset-keyword-form\"><a class=\"header\" href=\"#limit-with-offset-keyword-form\">LIMIT with OFFSET (Keyword Form)</a></h3>\n<pre><code class=\"language-sql\">-- Skip the 2 cheapest products, return the next 2\nSELECT name, price FROM products ORDER BY price LIMIT 2 OFFSET 2;\n-- Mouse|29.99\n-- Keyboard|49.99\n</code></pre>\n<h3 id=\"limit-with-offset-comma-form\"><a class=\"header\" href=\"#limit-with-offset-comma-form\">LIMIT with OFFSET (Comma Form)</a></h3>\n<pre><code class=\"language-sql\">-- Same result as above: LIMIT offset, count\nSELECT name, price FROM products ORDER BY price LIMIT 2, 2;\n-- Mouse|29.99\n-- Keyboard|49.99\n</code></pre>\n<h3 id=\"negative-limit\"><a class=\"header\" href=\"#negative-limit\">Negative LIMIT</a></h3>\n<pre><code class=\"language-sql\">-- A negative LIMIT returns all rows (no upper bound)\nSELECT name FROM products ORDER BY name LIMIT -1;\n-- Keyboard\n-- Monitor\n-- Mouse\n-- Notebook\n-- Pen\n</code></pre>\n<h3 id=\"pagination-pattern\"><a class=\"header\" href=\"#pagination-pattern\">Pagination Pattern</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE employees (\n  id INTEGER PRIMARY KEY,\n  name TEXT,\n  department TEXT,\n  salary REAL\n);\nINSERT INTO employees VALUES (1, 'Alice', 'Engineering', 95000);\nINSERT INTO employees VALUES (2, 'Bob', 'Marketing', 72000);\nINSERT INTO employees VALUES (3, 'Carol', 'Engineering', 98000);\nINSERT INTO employees VALUES (4, 'Dave', 'Sales', 68000);\nINSERT INTO employees VALUES (5, 'Eve', 'Marketing', 75000);\nINSERT INTO employees VALUES (6, 'Frank', 'Engineering', 102000);\nINSERT INTO employees VALUES (7, 'Grace', 'Sales', 71000);\nINSERT INTO employees VALUES (8, 'Heidi', 'Engineering', 91000);\n\n-- Page 1 (first 3 employees by salary descending)\nSELECT name, salary FROM employees ORDER BY salary DESC LIMIT 3 OFFSET 0;\n-- Frank|102000.0\n-- Carol|98000.0\n-- Alice|95000.0\n\n-- Page 2 (next 3)\nSELECT name, salary FROM employees ORDER BY salary DESC LIMIT 3 OFFSET 3;\n-- Heidi|91000.0\n-- Eve|75000.0\n-- Bob|72000.0\n\n-- Page 3 (remaining)\nSELECT name, salary FROM employees ORDER BY salary DESC LIMIT 3 OFFSET 6;\n-- Grace|71000.0\n-- Dave|68000.0\n</code></pre>\n<h2 id=\"compatibility-2\"><a class=\"header\" href=\"#compatibility-2\">Compatibility</a></h2>\n<ul>\n<li><code>NULLS FIRST</code> and <code>NULLS LAST</code> are not yet fully supported. <code>NULLS LAST</code> returns a parse error. <code>NULLS FIRST</code> is accepted by the parser but does not change the sort behavior – NULLs always sort as the smallest values regardless. This means there is currently no way to override the default NULL placement in sort results.</li>\n</ul>\n<div style=\"break-before: page; page-break-before: always;\"></div>\n<h1 id=\"set-operations\"><a class=\"header\" href=\"#set-operations\">Set Operations</a></h1>\n<h2 id=\"syntax-4\"><a class=\"header\" href=\"#syntax-4\">Syntax</a></h2>\n<pre><code class=\"language-sql\">select-statement {UNION | UNION ALL | INTERSECT | EXCEPT} select-statement\n</code></pre>\n<p>A compound SELECT chains two or more simple SELECT statements with a set operator. Multiple operators can be chained:</p>\n<pre><code class=\"language-sql\">select-statement op select-statement [op select-statement ...]\n</code></pre>\n<p>An optional LIMIT clause may appear after the last SELECT:</p>\n<pre><code class=\"language-sql\">select-statement op select-statement [LIMIT expr [OFFSET expr]]\n</code></pre>\n<h2 id=\"description-4\"><a class=\"header\" href=\"#description-4\">Description</a></h2>\n<p>Set operations combine the results of two or more SELECT statements into a single result set. Each constituent SELECT must return the same number of columns. Columns are matched by position (left to right), not by name. The column names in the final result are taken from the leftmost SELECT statement.</p>\n<p>Turso supports four set operators:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Operator</th><th>Duplicates</th><th>Description</th></tr>\n</thead>\n<tbody>\n<tr><td><code>UNION ALL</code></td><td>Keeps all</td><td>Returns every row from both queries, including duplicates.</td></tr>\n<tr><td><code>UNION</code></td><td>Removed</td><td>Returns all rows from both queries, removing duplicate rows.</td></tr>\n<tr><td><code>INTERSECT</code></td><td>Removed</td><td>Returns only rows that appear in both queries.</td></tr>\n<tr><td><code>EXCEPT</code></td><td>Removed</td><td>Returns rows from the left query that do not appear in the right query.</td></tr>\n</tbody>\n</table>\n</div>\n<p>UNION, INTERSECT, and EXCEPT all remove duplicate rows from the final result. UNION ALL is the only operator that preserves duplicates.</p>\n<h2 id=\"clauses-3\"><a class=\"header\" href=\"#clauses-3\">Clauses</a></h2>\n<h3 id=\"union-all\"><a class=\"header\" href=\"#union-all\">UNION ALL</a></h3>\n<p>UNION ALL returns all rows from the left SELECT followed by all rows from the right SELECT. No duplicate detection or removal is performed, making UNION ALL the most efficient set operator.</p>\n<pre><code class=\"language-sql\">-- Combine all employees and contractors, keeping duplicates\nSELECT name, department FROM employees\nUNION ALL\nSELECT name, department FROM contractors;\n</code></pre>\n<p>If a person appears in both tables with the same name and department, that combination appears twice in the result.</p>\n<h3 id=\"union\"><a class=\"header\" href=\"#union\">UNION</a></h3>\n<p>UNION works the same way as UNION ALL but removes duplicate rows from the combined result. Two rows are considered duplicates when every corresponding column value is equal.</p>\n<pre><code class=\"language-sql\">-- Combine employees and contractors, removing duplicates\nSELECT name, department FROM employees\nUNION\nSELECT name, department FROM contractors;\n</code></pre>\n<h3 id=\"intersect\"><a class=\"header\" href=\"#intersect\">INTERSECT</a></h3>\n<p>INTERSECT returns only rows that appear in both the left and right result sets. The output contains no duplicates.</p>\n<pre><code class=\"language-sql\">-- Find people who appear in both tables with the same department\nSELECT name, department FROM employees\nINTERSECT\nSELECT name, department FROM contractors;\n</code></pre>\n<h3 id=\"except\"><a class=\"header\" href=\"#except\">EXCEPT</a></h3>\n<p>EXCEPT returns rows from the left query that are not present in the right query. The output contains no duplicates. The order of the two queries matters: <code>A EXCEPT B</code> is different from <code>B EXCEPT A</code>.</p>\n<pre><code class=\"language-sql\">-- Find employees who are not also contractors (by name and department)\nSELECT name, department FROM employees\nEXCEPT\nSELECT name, department FROM contractors;\n</code></pre>\n<h3 id=\"limit-1\"><a class=\"header\" href=\"#limit-1\">LIMIT</a></h3>\n<p>A LIMIT clause may appear after the final SELECT in a compound statement. The limit applies to the entire combined result, not just the last SELECT.</p>\n<pre><code class=\"language-sql\">-- Get the first 4 rows from the combined result\nSELECT name, department FROM employees\nUNION ALL\nSELECT name, department FROM contractors\nLIMIT 4;\n</code></pre>\n<p>OFFSET is also supported:</p>\n<pre><code class=\"language-sql\">-- Skip the first row and return the next 3\nSELECT name, department FROM employees\nUNION ALL\nSELECT name, department FROM contractors\nLIMIT 3 OFFSET 1;\n</code></pre>\n<h2 id=\"column-matching-rules\"><a class=\"header\" href=\"#column-matching-rules\">Column Matching Rules</a></h2>\n<p>All SELECT statements in a compound query must produce the same number of result columns. If they do not, Turso returns an error:</p>\n<pre><code class=\"language-sql\">-- Error: different number of columns\nSELECT name, department FROM employees\nUNION ALL\nSELECT name FROM contractors;\n-- SELECTs to the left and right of UNION ALL do not have the same number of result columns\n</code></pre>\n<p>Columns are matched by position. The first column of the left SELECT pairs with the first column of the right SELECT, and so on. Column names and types do not need to match – only the count must be equal.</p>\n<p>The result column names are always determined by the leftmost SELECT:</p>\n<pre><code class=\"language-sql\">SELECT 1 AS first_col, 2 AS second_col\nUNION ALL\nSELECT 3, 4;\n-- Column headers are \"first_col\" and \"second_col\"\n</code></pre>\n<h2 id=\"duplicate-detection\"><a class=\"header\" href=\"#duplicate-detection\">Duplicate Detection</a></h2>\n<p>For the purpose of identifying duplicate rows in UNION, INTERSECT, and EXCEPT:</p>\n<ul>\n<li><strong>NULL values are considered equal to each other.</strong> Two rows that both contain NULL in the same column position are treated as matching in that column.</li>\n<li><strong>No type affinity transformations</strong> are applied when comparing rows. Values are compared as-is.</li>\n<li><strong>Text comparisons</strong> use the collation sequence that would apply if the columns from the left and right SELECTs were operands of the <code>=</code> operator.</li>\n</ul>\n<pre><code class=\"language-sql\">-- NULL is treated as equal to NULL for dedup purposes\nSELECT NULL UNION SELECT NULL;\n-- Returns one row (a single NULL)\n</code></pre>\n<h2 id=\"chaining-multiple-operators\"><a class=\"header\" href=\"#chaining-multiple-operators\">Chaining Multiple Operators</a></h2>\n<p>Three or more SELECT statements can be connected with set operators. When chained, they group from left to right. That is, <code>A op1 B op2 C</code> is evaluated as <code>(A op1 B) op2 C</code>.</p>\n<pre><code class=\"language-sql\">-- Three-way UNION ALL: employees, contractors, and interns\nSELECT name, department FROM employees\nUNION ALL\nSELECT name, department FROM contractors\nUNION ALL\nSELECT name, department FROM interns;\n</code></pre>\n<p>Different operators can be mixed in the same compound statement:</p>\n<pre><code class=\"language-sql\">-- First combine and deduplicate, then append without dedup\nSELECT name, department FROM employees\nUNION\nSELECT name, department FROM contractors\nUNION ALL\nSELECT name, department FROM interns;\n</code></pre>\n<h2 id=\"using-set-operations-in-subqueries\"><a class=\"header\" href=\"#using-set-operations-in-subqueries\">Using Set Operations in Subqueries</a></h2>\n<p>A compound SELECT can be used as a subquery in the FROM clause. This is useful for aggregating or filtering the combined result:</p>\n<pre><code class=\"language-sql\">-- Count headcount per department across all worker types\nSELECT department, COUNT(*) AS headcount\nFROM (\n    SELECT name, department FROM employees\n    UNION ALL\n    SELECT name, department FROM contractors\n)\nGROUP BY department;\n</code></pre>\n<pre><code class=\"language-sql\">-- Filter the combined result\nSELECT *\nFROM (\n    SELECT name, department FROM employees\n    UNION ALL\n    SELECT name, department FROM contractors\n)\nWHERE department = 'Engineering';\n</code></pre>\n<h2 id=\"examples-4\"><a class=\"header\" href=\"#examples-4\">Examples</a></h2>\n<p>The examples below use the following tables:</p>\n<pre><code class=\"language-sql\">CREATE TABLE employees (\n  id INTEGER PRIMARY KEY,\n  name TEXT,\n  department TEXT,\n  salary REAL\n);\nINSERT INTO employees VALUES\n  (1, 'Alice', 'Engineering', 95000),\n  (2, 'Bob', 'Engineering', 88000),\n  (3, 'Carol', 'Marketing', 72000),\n  (4, 'Dave', 'Marketing', 68000),\n  (5, 'Eve', 'Sales', 75000);\n\nCREATE TABLE contractors (\n  id INTEGER PRIMARY KEY,\n  name TEXT,\n  department TEXT,\n  rate REAL\n);\nINSERT INTO contractors VALUES\n  (1, 'Frank', 'Engineering', 110000),\n  (2, 'Grace', 'Marketing', 65000),\n  (3, 'Alice', 'Engineering', 95000);\n</code></pre>\n<h3 id=\"union-all--all-people-including-duplicates\"><a class=\"header\" href=\"#union-all--all-people-including-duplicates\">UNION ALL – All People Including Duplicates</a></h3>\n<pre><code class=\"language-sql\">SELECT name, department FROM employees\nUNION ALL\nSELECT name, department FROM contractors;\n-- Alice|Engineering\n-- Bob|Engineering\n-- Carol|Marketing\n-- Dave|Marketing\n-- Eve|Sales\n-- Frank|Engineering\n-- Grace|Marketing\n-- Alice|Engineering\n</code></pre>\n<p>Note that “Alice | Engineering” appears twice because UNION ALL does not remove duplicates.</p>\n<h3 id=\"union--all-people-without-duplicates\"><a class=\"header\" href=\"#union--all-people-without-duplicates\">UNION – All People Without Duplicates</a></h3>\n<pre><code class=\"language-sql\">SELECT name, department FROM employees\nUNION\nSELECT name, department FROM contractors;\n-- Alice|Engineering\n-- Bob|Engineering\n-- Carol|Marketing\n-- Dave|Marketing\n-- Eve|Sales\n-- Frank|Engineering\n-- Grace|Marketing\n</code></pre>\n<p>The duplicate “Alice | Engineering” row has been removed. The result has 7 rows instead of 8.</p>\n<h3 id=\"intersect--people-in-both-tables\"><a class=\"header\" href=\"#intersect--people-in-both-tables\">INTERSECT – People in Both Tables</a></h3>\n<pre><code class=\"language-sql\">SELECT name, department FROM employees\nINTERSECT\nSELECT name, department FROM contractors;\n-- Alice|Engineering\n</code></pre>\n<p>Only “Alice | Engineering” appears in both tables.</p>\n<h3 id=\"except--employees-who-are-not-contractors\"><a class=\"header\" href=\"#except--employees-who-are-not-contractors\">EXCEPT – Employees Who Are Not Contractors</a></h3>\n<pre><code class=\"language-sql\">SELECT name, department FROM employees\nEXCEPT\nSELECT name, department FROM contractors;\n-- Bob|Engineering\n-- Carol|Marketing\n-- Dave|Marketing\n-- Eve|Sales\n</code></pre>\n<p>Alice is excluded because she appears in both tables with the same name and department.</p>\n<h3 id=\"finding-common-departments\"><a class=\"header\" href=\"#finding-common-departments\">Finding Common Departments</a></h3>\n<pre><code class=\"language-sql\">SELECT department FROM employees\nINTERSECT\nSELECT department FROM contractors;\n-- Engineering\n-- Marketing\n</code></pre>\n<h3 id=\"finding-departments-unique-to-employees\"><a class=\"header\" href=\"#finding-departments-unique-to-employees\">Finding Departments Unique to Employees</a></h3>\n<pre><code class=\"language-sql\">SELECT department FROM employees\nEXCEPT\nSELECT department FROM contractors;\n-- Sales\n</code></pre>\n<p>Sales exists only in the employees table.</p>\n<h3 id=\"aggregating-a-combined-result\"><a class=\"header\" href=\"#aggregating-a-combined-result\">Aggregating a Combined Result</a></h3>\n<pre><code class=\"language-sql\">SELECT department, COUNT(*) AS headcount\nFROM (\n    SELECT name, department FROM employees\n    UNION ALL\n    SELECT name, department FROM contractors\n)\nGROUP BY department;\n-- Engineering|4\n-- Marketing|3\n-- Sales|1\n</code></pre>\n<p>Note that UNION ALL is used here so that each person is counted, even if they appear in both tables.</p>\n<h3 id=\"limiting-the-combined-result\"><a class=\"header\" href=\"#limiting-the-combined-result\">Limiting the Combined Result</a></h3>\n<pre><code class=\"language-sql\">SELECT name, department FROM employees\nUNION ALL\nSELECT name, department FROM contractors\nLIMIT 4;\n-- Alice|Engineering\n-- Bob|Engineering\n-- Carol|Marketing\n-- Dave|Marketing\n</code></pre>\n<h3 id=\"using-values-with-set-operations\"><a class=\"header\" href=\"#using-values-with-set-operations\">Using VALUES with Set Operations</a></h3>\n<pre><code class=\"language-sql\">VALUES (1, 'Alice'), (2, 'Bob')\nUNION ALL\nVALUES (3, 'Carol');\n-- 1|Alice\n-- 2|Bob\n-- 3|Carol\n</code></pre>\n<h2 id=\"compatibility-3\"><a class=\"header\" href=\"#compatibility-3\">Compatibility</a></h2>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Feature</th><th>Status</th></tr>\n</thead>\n<tbody>\n<tr><td>ORDER BY on compound SELECT</td><td>Not yet supported. Turso does not currently allow ORDER BY on the final result of a compound SELECT. As a workaround, wrap the compound SELECT in a subquery and apply ORDER BY to the outer query.</td></tr>\n</tbody>\n</table>\n</div>\n<div style=\"break-before: page; page-break-before: always;\"></div>\n<h1 id=\"subqueries\"><a class=\"header\" href=\"#subqueries\">Subqueries</a></h1>\n<h2 id=\"syntax-5\"><a class=\"header\" href=\"#syntax-5\">Syntax</a></h2>\n<p>A subquery is a <code>SELECT</code> statement enclosed in parentheses, used as an expression or table source within another SQL statement.</p>\n<pre><code class=\"language-sql\">-- Scalar subquery (returns a single value)\n(SELECT expr FROM table-name [WHERE ...])\n\n-- EXISTS / NOT EXISTS\n[NOT] EXISTS (SELECT ... FROM table-name [WHERE ...])\n\n-- IN / NOT IN with subquery\nexpr [NOT] IN (SELECT expr FROM table-name [WHERE ...])\n\n-- Derived table (subquery in FROM clause)\nSELECT ... FROM (SELECT ... FROM table-name) AS alias\n</code></pre>\n<h2 id=\"description-5\"><a class=\"header\" href=\"#description-5\">Description</a></h2>\n<p>Subqueries allow you to nest one query inside another. They appear in several forms depending on context: as a single value in an expression (scalar subquery), as a set membership test (<code>IN</code>), as an existence check (<code>EXISTS</code>), or as a virtual table in the <code>FROM</code> clause (derived table).</p>\n<p>A subquery can reference columns from its enclosing query. When it does, it is called a <strong>correlated subquery</strong> and is re-evaluated for each row of the outer query. When a subquery does not reference any outer columns, it is an <strong>uncorrelated subquery</strong> and may be evaluated once and its result reused.</p>\n<h2 id=\"scalar-subqueries\"><a class=\"header\" href=\"#scalar-subqueries\">Scalar Subqueries</a></h2>\n<p>A scalar subquery is a <code>SELECT</code> enclosed in parentheses that returns a single column. It can appear anywhere an expression is allowed: in the <code>SELECT</code> list, <code>WHERE</code> clause, <code>HAVING</code> clause, or even <code>LIMIT</code> and <code>OFFSET</code>.</p>\n<p>The value of a scalar subquery is the value from the first row returned. If the subquery returns no rows, the result is <code>NULL</code>. If the subquery returns more than one column, Turso raises an error.</p>\n<pre><code class=\"language-sql\">-- Scalar subquery in SELECT list\nSELECT expr, (SELECT agg_func(...) FROM table-name) AS alias FROM table-name\n\n-- Scalar subquery in WHERE clause\nSELECT ... FROM table-name WHERE column &gt; (SELECT agg_func(...) FROM table-name)\n</code></pre>\n<h2 id=\"exists-and-not-exists\"><a class=\"header\" href=\"#exists-and-not-exists\">EXISTS and NOT EXISTS</a></h2>\n<p>The <code>EXISTS</code> operator takes a subquery and evaluates to <code>1</code> (true) if the subquery returns at least one row, or <code>0</code> (false) if the subquery returns no rows. <code>NOT EXISTS</code> returns the opposite.</p>\n<p>The number of columns returned by the subquery and their values do not matter. Only the presence or absence of rows is significant. By convention, <code>SELECT 1</code> is often used inside <code>EXISTS</code> subqueries.</p>\n<pre><code class=\"language-sql\">-- Returns rows from outer query where the subquery matches at least one row\nSELECT ... FROM table-name t\nWHERE EXISTS (SELECT 1 FROM other-table o WHERE o.ref_id = t.id)\n\n-- Returns rows where no matching row exists\nSELECT ... FROM table-name t\nWHERE NOT EXISTS (SELECT 1 FROM other-table o WHERE o.ref_id = t.id)\n</code></pre>\n<p><code>EXISTS</code> is commonly used with correlated subqueries to test whether related rows exist in another table.</p>\n<h2 id=\"in-and-not-in-with-subqueries\"><a class=\"header\" href=\"#in-and-not-in-with-subqueries\">IN and NOT IN with Subqueries</a></h2>\n<p>The <code>IN</code> operator tests whether a value is a member of the set of values returned by a subquery. The subquery must return a single column. <code>NOT IN</code> tests the inverse.</p>\n<pre><code class=\"language-sql\">expr [NOT] IN (SELECT column FROM table-name [WHERE ...])\n</code></pre>\n<h3 id=\"null-handling-1\"><a class=\"header\" href=\"#null-handling-1\">NULL Handling</a></h3>\n<p>The behavior of <code>IN</code> and <code>NOT IN</code> with <code>NULL</code> values follows specific rules:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Left Value</th><th>Subquery Contains NULL</th><th>Value Found</th><th><code>IN</code> Result</th><th><code>NOT IN</code> Result</th></tr>\n</thead>\n<tbody>\n<tr><td>non-NULL</td><td>no</td><td>no</td><td>0 (false)</td><td>1 (true)</td></tr>\n<tr><td>non-NULL</td><td>no</td><td>yes</td><td>1 (true)</td><td>0 (false)</td></tr>\n<tr><td>non-NULL</td><td>yes</td><td>no</td><td>NULL</td><td>NULL</td></tr>\n<tr><td>non-NULL</td><td>yes</td><td>yes</td><td>1 (true)</td><td>0 (false)</td></tr>\n<tr><td>NULL</td><td>any</td><td>(empty set)</td><td>0 (false)</td><td>1 (true)</td></tr>\n<tr><td>NULL</td><td>any</td><td>(non-empty)</td><td>NULL</td><td>NULL</td></tr>\n</tbody>\n</table>\n</div>\n<p>Key takeaways:</p>\n<ul>\n<li>When the subquery returns an empty set, <code>IN</code> always returns <code>0</code> and <code>NOT IN</code> always returns <code>1</code>, regardless of <code>NULL</code> values.</li>\n<li>When the left value is found in the set, <code>IN</code> returns <code>1</code> even if the set also contains <code>NULL</code>.</li>\n<li>When the left value is not found and the set contains <code>NULL</code>, the result is <code>NULL</code> (unknown), not <code>0</code>. This is because the value might match the unknown (<code>NULL</code>) element.</li>\n</ul>\n<h2 id=\"correlated-subqueries\"><a class=\"header\" href=\"#correlated-subqueries\">Correlated Subqueries</a></h2>\n<p>A correlated subquery references one or more columns from the outer query. Turso re-evaluates a correlated subquery for each row processed by the outer query.</p>\n<pre><code class=\"language-sql\">-- The inner query references e.department_id from the outer query\nSELECT ... FROM employees e\nWHERE e.salary &gt; (\n    SELECT AVG(e2.salary) FROM employees e2\n    WHERE e2.department_id = e.department_id\n)\n</code></pre>\n<p>Correlated subqueries are supported in the <code>SELECT</code> list, <code>WHERE</code> clause, <code>HAVING</code> clause, and <code>GROUP BY</code> clause. Correlated subqueries can also be used with <code>EXISTS</code> and <code>IN</code>.</p>\n<p><strong>Note:</strong> Correlated subqueries in the <code>ORDER BY</code> clause are not currently supported.</p>\n<h2 id=\"derived-tables\"><a class=\"header\" href=\"#derived-tables\">Derived Tables</a></h2>\n<p>A subquery in the <code>FROM</code> clause creates a derived table (also called an inline view). The subquery result is treated as a temporary table for the duration of the outer query.</p>\n<p>A derived table must be given an alias using the <code>AS</code> keyword.</p>\n<pre><code class=\"language-sql\">SELECT alias.column [, ...]\nFROM (SELECT ... FROM table-name [WHERE ...] [GROUP BY ...]) AS alias\n[JOIN other-table ON ...]\n[WHERE ...]\n</code></pre>\n<p>Each column of the derived table inherits the type affinity and collation sequence of the corresponding expression in the subquery.</p>\n<h2 id=\"examples-5\"><a class=\"header\" href=\"#examples-5\">Examples</a></h2>\n<h3 id=\"scalar-subquery-in-select-list\"><a class=\"header\" href=\"#scalar-subquery-in-select-list\">Scalar Subquery in SELECT List</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE employees (\n    id INTEGER PRIMARY KEY,\n    name TEXT NOT NULL,\n    department_id INTEGER,\n    salary REAL\n);\nINSERT INTO employees VALUES\n    (1, 'Alice', 1, 95000), (2, 'Bob', 1, 88000),\n    (3, 'Carol', 2, 72000), (4, 'Dave', 3, 68000),\n    (5, 'Eve', 1, 105000), (6, 'Frank', 2, 71000);\n\n-- Show each employee's salary alongside the company average\nSELECT name, salary,\n       (SELECT AVG(salary) FROM employees) AS avg_salary\nFROM employees\nORDER BY salary DESC;\n-- Eve   | 105000.0 | 83166.6666666667\n-- Alice |  95000.0 | 83166.6666666667\n-- Bob   |  88000.0 | 83166.6666666667\n-- Carol |  72000.0 | 83166.6666666667\n-- Frank |  71000.0 | 83166.6666666667\n-- Dave  |  68000.0 | 83166.6666666667\n</code></pre>\n<h3 id=\"scalar-subquery-in-where-clause\"><a class=\"header\" href=\"#scalar-subquery-in-where-clause\">Scalar Subquery in WHERE Clause</a></h3>\n<pre><code class=\"language-sql\">-- Find employees earning above the company average\nSELECT name, salary\nFROM employees\nWHERE salary &gt; (SELECT AVG(salary) FROM employees);\n-- Alice |  95000.0\n-- Bob   |  88000.0\n-- Eve   | 105000.0\n</code></pre>\n<h3 id=\"scalar-subquery-returning-null\"><a class=\"header\" href=\"#scalar-subquery-returning-null\">Scalar Subquery Returning NULL</a></h3>\n<p>When a scalar subquery matches no rows, it returns <code>NULL</code>:</p>\n<pre><code class=\"language-sql\">SELECT (SELECT name FROM employees WHERE id = 999) AS result;\n-- (NULL)\n</code></pre>\n<h3 id=\"exists-find-departments-with-employees\"><a class=\"header\" href=\"#exists-find-departments-with-employees\">EXISTS: Find Departments with Employees</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE departments (\n    id INTEGER PRIMARY KEY,\n    name TEXT NOT NULL\n);\nINSERT INTO departments VALUES\n    (1, 'Engineering'), (2, 'Marketing'),\n    (3, 'Sales'), (4, 'HR');\n\n-- Departments that have at least one employee\nSELECT d.name\nFROM departments d\nWHERE EXISTS (\n    SELECT 1 FROM employees e WHERE e.department_id = d.id\n);\n-- Engineering\n-- Marketing\n-- Sales\n</code></pre>\n<h3 id=\"not-exists-find-departments-without-employees\"><a class=\"header\" href=\"#not-exists-find-departments-without-employees\">NOT EXISTS: Find Departments without Employees</a></h3>\n<pre><code class=\"language-sql\">-- Departments with no employees assigned\nSELECT d.name\nFROM departments d\nWHERE NOT EXISTS (\n    SELECT 1 FROM employees e WHERE e.department_id = d.id\n);\n-- HR\n</code></pre>\n<h3 id=\"in-with-subquery\"><a class=\"header\" href=\"#in-with-subquery\">IN with Subquery</a></h3>\n<pre><code class=\"language-sql\">-- Employees in the Engineering department\nSELECT name\nFROM employees\nWHERE department_id IN (\n    SELECT id FROM departments WHERE name = 'Engineering'\n);\n-- Alice\n-- Bob\n-- Eve\n</code></pre>\n<h3 id=\"not-in-with-subquery\"><a class=\"header\" href=\"#not-in-with-subquery\">NOT IN with Subquery</a></h3>\n<pre><code class=\"language-sql\">-- Employees outside the Engineering department\nSELECT name\nFROM employees\nWHERE department_id NOT IN (\n    SELECT id FROM departments WHERE name = 'Engineering'\n);\n-- Carol\n-- Dave\n-- Frank\n</code></pre>\n<h3 id=\"correlated-subquery-look-up-related-data\"><a class=\"header\" href=\"#correlated-subquery-look-up-related-data\">Correlated Subquery: Look Up Related Data</a></h3>\n<pre><code class=\"language-sql\">-- Show each employee with their department name\nSELECT e.name, e.salary,\n       (SELECT d.name FROM departments d WHERE d.id = e.department_id) AS dept_name\nFROM employees e\nORDER BY e.salary DESC;\n-- Eve   | 105000.0 | Engineering\n-- Alice |  95000.0 | Engineering\n-- Bob   |  88000.0 | Engineering\n-- Carol |  72000.0 | Marketing\n-- Frank |  71000.0 | Marketing\n-- Dave  |  68000.0 | Sales\n</code></pre>\n<h3 id=\"correlated-subquery-compare-against-group-average\"><a class=\"header\" href=\"#correlated-subquery-compare-against-group-average\">Correlated Subquery: Compare Against Group Average</a></h3>\n<pre><code class=\"language-sql\">-- Employees earning above their department's average salary\nSELECT e.name, e.salary, e.department_id\nFROM employees e\nWHERE e.salary &gt; (\n    SELECT AVG(e2.salary)\n    FROM employees e2\n    WHERE e2.department_id = e.department_id\n);\n-- Carol | 72000.0 | 2\n-- Eve   | 105000.0 | 1\n</code></pre>\n<h3 id=\"correlated-subquery-with-count\"><a class=\"header\" href=\"#correlated-subquery-with-count\">Correlated Subquery with COUNT</a></h3>\n<pre><code class=\"language-sql\">-- Count employees per department\nSELECT d.name,\n       (SELECT COUNT(*) FROM employees e WHERE e.department_id = d.id) AS emp_count\nFROM departments d;\n-- Engineering | 3\n-- Marketing   | 2\n-- Sales       | 1\n-- HR          | 0\n</code></pre>\n<h3 id=\"derived-table\"><a class=\"header\" href=\"#derived-table\">Derived Table</a></h3>\n<pre><code class=\"language-sql\">-- Average salary per department using a derived table\nSELECT dept_name, avg_salary\nFROM (\n    SELECT d.name AS dept_name, AVG(e.salary) AS avg_salary\n    FROM departments d\n    JOIN employees e ON d.id = e.department_id\n    GROUP BY d.name\n) AS dept_stats\nORDER BY avg_salary DESC;\n-- Engineering | 96000.0\n-- Marketing   | 71500.0\n-- Sales       | 68000.0\n</code></pre>\n<h3 id=\"derived-table-joined-with-another-table\"><a class=\"header\" href=\"#derived-table-joined-with-another-table\">Derived Table Joined with Another Table</a></h3>\n<pre><code class=\"language-sql\">-- Join a derived table of aggregate stats back to the departments table\nSELECT c.name AS department, stats.emp_count, stats.avg_salary\nFROM departments c\nJOIN (\n    SELECT department_id,\n           COUNT(*) AS emp_count,\n           AVG(salary) AS avg_salary\n    FROM employees\n    GROUP BY department_id\n) AS stats ON stats.department_id = c.id;\n-- Engineering | 3 | 96000.0\n-- Marketing   | 2 | 71500.0\n-- Sales       | 1 | 68000.0\n</code></pre>\n<h3 id=\"combining-exists-and-in\"><a class=\"header\" href=\"#combining-exists-and-in\">Combining EXISTS and IN</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE products (\n    id INTEGER PRIMARY KEY, name TEXT,\n    category_id INTEGER, price REAL\n);\nCREATE TABLE categories (id INTEGER PRIMARY KEY, name TEXT);\nCREATE TABLE reviews (id INTEGER PRIMARY KEY, product_id INTEGER, rating INTEGER);\n\nINSERT INTO categories VALUES (1, 'Electronics'), (2, 'Clothing');\nINSERT INTO products VALUES\n    (1, 'Laptop', 1, 999.99),\n    (2, 'T-Shirt', 2, 29.99),\n    (3, 'Headphones', 1, 79.99);\nINSERT INTO reviews VALUES (1, 1, 5), (2, 1, 4), (3, 3, 3);\n\n-- Electronics with at least one review rated 4 or higher\nSELECT p.name, p.price\nFROM products p\nWHERE EXISTS (\n    SELECT 1 FROM reviews r\n    WHERE r.product_id = p.id AND r.rating &gt;= 4\n)\nAND p.category_id IN (\n    SELECT id FROM categories WHERE name = 'Electronics'\n);\n-- Laptop | 999.99\n</code></pre>\n<h2 id=\"compatibility-4\"><a class=\"header\" href=\"#compatibility-4\">Compatibility</a></h2>\n<p>Turso supports scalar subqueries used with comparison operators (e.g. <code>WHERE x &gt; (SELECT ...)</code>), but does not support row value subqueries such as <code>(x, y) = (SELECT a, b FROM ...)</code>. Only single-column subqueries are valid in comparison contexts.</p>\n<p>Correlated subqueries in the <code>ORDER BY</code> clause are not yet supported. Uncorrelated subqueries in <code>ORDER BY</code> work as expected.</p>\n<div style=\"break-before: page; page-break-before: always;\"></div>\n<h1 id=\"common-table-expressions\"><a class=\"header\" href=\"#common-table-expressions\">Common Table Expressions</a></h1>\n<h2 id=\"syntax-6\"><a class=\"header\" href=\"#syntax-6\">Syntax</a></h2>\n<pre><code class=\"language-sql\">WITH cte-name [(column-name [, ...])] AS (select-stmt)\n  [, cte-name [(column-name [, ...])] AS (select-stmt) [, ...]]\n{SELECT | INSERT | UPDATE | DELETE} ...\n</code></pre>\n<h2 id=\"description-6\"><a class=\"header\" href=\"#description-6\">Description</a></h2>\n<p>A Common Table Expression (CTE) is a named temporary result set defined within a <code>WITH</code> clause. CTEs exist only for the duration of the statement they are attached to. They behave like temporary views: you define them once and can reference them by name in the main statement that follows.</p>\n<p>CTEs make complex queries easier to read by breaking them into named, reusable pieces. Instead of deeply nested subqueries, you can define each logical step as a separate CTE and compose them together.</p>\n<p>A single <code>WITH</code> clause can define multiple CTEs, separated by commas. Later CTEs in the list can reference earlier ones, allowing you to build up results incrementally.</p>\n<h2 id=\"clauses-4\"><a class=\"header\" href=\"#clauses-4\">Clauses</a></h2>\n<h3 id=\"with\"><a class=\"header\" href=\"#with\">WITH</a></h3>\n<p>The <code>WITH</code> keyword introduces one or more CTE definitions. It must appear at the beginning of a top-level <code>SELECT</code>, <code>INSERT</code>, <code>UPDATE</code>, or <code>DELETE</code> statement.</p>\n<pre><code class=\"language-sql\">WITH cte-name AS (select-stmt)\n</code></pre>\n<p>Each CTE definition consists of:</p>\n<ul>\n<li><strong>cte-name</strong> – the name used to reference the CTE in the main statement. CTE names must be unique within a single <code>WITH</code> clause; duplicate names produce an error.</li>\n<li><strong>select-stmt</strong> – a <code>SELECT</code> statement (including compound <code>SELECT</code> with <code>UNION</code>, <code>UNION ALL</code>, <code>INTERSECT</code>, or <code>EXCEPT</code>) that defines the CTE’s contents.</li>\n</ul>\n<h3 id=\"column-names\"><a class=\"header\" href=\"#column-names\">Column Names</a></h3>\n<p>You can optionally specify explicit column names for the CTE by listing them in parentheses after the CTE name:</p>\n<pre><code class=\"language-sql\">WITH cte-name (column-name [, ...]) AS (select-stmt)\n</code></pre>\n<p>When column names are provided, they replace whatever column names the <code>select-stmt</code> would otherwise produce. This is useful when the CTE body contains expressions without natural names, or when you want to rename columns for clarity.</p>\n<h3 id=\"multiple-ctes\"><a class=\"header\" href=\"#multiple-ctes\">Multiple CTEs</a></h3>\n<p>Multiple CTEs are separated by commas within a single <code>WITH</code> clause. Each subsequent CTE can reference any CTE defined before it:</p>\n<pre><code class=\"language-sql\">WITH\n  first AS (select-stmt),\n  second AS (select-stmt),   -- can reference 'first'\n  third AS (select-stmt)     -- can reference 'first' and 'second'\nSELECT ... FROM third;\n</code></pre>\n<h3 id=\"cte-body-with-compound-select\"><a class=\"header\" href=\"#cte-body-with-compound-select\">CTE Body with Compound SELECT</a></h3>\n<p>The <code>select-stmt</code> inside a CTE definition can be a compound <code>SELECT</code> using set operators:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Operator</th><th>Behavior</th></tr>\n</thead>\n<tbody>\n<tr><td><code>UNION</code></td><td>Combines results, removing duplicate rows</td></tr>\n<tr><td><code>UNION ALL</code></td><td>Combines results, keeping all rows including duplicates</td></tr>\n<tr><td><code>INTERSECT</code></td><td>Returns only rows present in both result sets</td></tr>\n<tr><td><code>EXCEPT</code></td><td>Returns rows from the first result set that are not in the second</td></tr>\n</tbody>\n</table>\n</div>\n<p>The compound <code>SELECT</code> can also include <code>LIMIT</code> and <code>OFFSET</code> to restrict the CTE’s result set.</p>\n<h2 id=\"using-ctes-with-different-statements\"><a class=\"header\" href=\"#using-ctes-with-different-statements\">Using CTEs with Different Statements</a></h2>\n<h3 id=\"with--select\"><a class=\"header\" href=\"#with--select\">WITH … SELECT</a></h3>\n<p>The most common use of CTEs. The main <code>SELECT</code> can reference any defined CTE by name in its <code>FROM</code> clause, in subqueries, or in <code>WHERE</code> clause conditions. A CTE can be referenced multiple times within the same statement.</p>\n<pre><code class=\"language-sql\">WITH cte AS (select-stmt)\nSELECT ... FROM cte;\n</code></pre>\n<h3 id=\"with--insert\"><a class=\"header\" href=\"#with--insert\">WITH … INSERT</a></h3>\n<p>A <code>WITH</code> clause can precede an <code>INSERT</code> statement. The CTEs are visible in the <code>INSERT ... SELECT</code> source query, in scalar subqueries within a <code>VALUES</code> clause, and in <code>RETURNING</code> clause subqueries.</p>\n<pre><code class=\"language-sql\">WITH cte AS (select-stmt)\nINSERT INTO table-name SELECT ... FROM cte;\n</code></pre>\n<h3 id=\"with--update\"><a class=\"header\" href=\"#with--update\">WITH … UPDATE</a></h3>\n<p>A <code>WITH</code> clause can precede an <code>UPDATE</code> statement. The CTEs are visible in the <code>WHERE</code> clause, <code>SET</code> expressions, and <code>RETURNING</code> clause of the <code>UPDATE</code>.</p>\n<pre><code class=\"language-sql\">WITH cte AS (select-stmt)\nUPDATE table-name SET ... WHERE ... IN (SELECT ... FROM cte);\n</code></pre>\n<h3 id=\"with--delete\"><a class=\"header\" href=\"#with--delete\">WITH … DELETE</a></h3>\n<p>A <code>WITH</code> clause can precede a <code>DELETE</code> statement. The CTEs are visible in the <code>WHERE</code> clause and <code>RETURNING</code> clause of the <code>DELETE</code>.</p>\n<pre><code class=\"language-sql\">WITH cte AS (select-stmt)\nDELETE FROM table-name WHERE ... IN (SELECT ... FROM cte);\n</code></pre>\n<h2 id=\"examples-6\"><a class=\"header\" href=\"#examples-6\">Examples</a></h2>\n<h3 id=\"basic-cte\"><a class=\"header\" href=\"#basic-cte\">Basic CTE</a></h3>\n<pre><code class=\"language-sql\">-- Define a simple CTE and select from it\nWITH recent_cutoff AS (SELECT 30 AS days)\nSELECT days FROM recent_cutoff;\n-- 30\n</code></pre>\n<h3 id=\"cte-with-explicit-column-names\"><a class=\"header\" href=\"#cte-with-explicit-column-names\">CTE with Explicit Column Names</a></h3>\n<pre><code class=\"language-sql\">-- Rename the CTE columns to 'sum' and 'product'\nWITH calculations(sum, product) AS (SELECT 3 + 4, 3 * 4)\nSELECT sum, product FROM calculations;\n-- 7|12\n</code></pre>\n<h3 id=\"multiple-ctes-1\"><a class=\"header\" href=\"#multiple-ctes-1\">Multiple CTEs</a></h3>\n<pre><code class=\"language-sql\">-- Chain CTEs: the second references the first\nWITH\n  base AS (SELECT 10 AS val),\n  doubled AS (SELECT val * 2 AS val FROM base)\nSELECT * FROM doubled;\n-- 20\n</code></pre>\n<h3 id=\"long-cte-chain\"><a class=\"header\" href=\"#long-cte-chain\">Long CTE Chain</a></h3>\n<pre><code class=\"language-sql\">-- Build a pipeline of CTEs, each referencing the previous one\nWITH\n  step1 AS (SELECT 1 AS x),\n  step2 AS (SELECT x FROM step1),\n  step3 AS (SELECT x FROM step2),\n  step4 AS (SELECT x FROM step3)\nSELECT * FROM step4;\n-- 1\n</code></pre>\n<h3 id=\"cte-with-union\"><a class=\"header\" href=\"#cte-with-union\">CTE with UNION</a></h3>\n<pre><code class=\"language-sql\">-- Combine two result sets, removing duplicates\nWITH statuses AS (\n  SELECT 'active' AS status\n  UNION\n  SELECT 'inactive'\n  UNION\n  SELECT 'pending'\n)\nSELECT * FROM statuses ORDER BY 1;\n-- active\n-- inactive\n-- pending\n</code></pre>\n<h3 id=\"cte-with-union-all-and-aggregation\"><a class=\"header\" href=\"#cte-with-union-all-and-aggregation\">CTE with UNION ALL and Aggregation</a></h3>\n<pre><code class=\"language-sql\">-- UNION ALL preserves all rows, enabling accurate counts\nWITH all_scores AS (\n  SELECT 1 AS score\n  UNION ALL\n  SELECT 2\n  UNION ALL\n  SELECT 3\n)\nSELECT COUNT(*) FROM all_scores;\n-- 3\n</code></pre>\n<h3 id=\"cte-with-limit-and-offset\"><a class=\"header\" href=\"#cte-with-limit-and-offset\">CTE with LIMIT and OFFSET</a></h3>\n<pre><code class=\"language-sql\">-- Restrict the CTE result set using LIMIT and OFFSET\nWITH numbers AS (\n  SELECT 1 UNION SELECT 2 UNION SELECT 3\n  LIMIT 2 OFFSET 1\n)\nSELECT * FROM numbers ORDER BY 1;\n-- 2\n-- 3\n</code></pre>\n<h3 id=\"cte-referenced-multiple-times\"><a class=\"header\" href=\"#cte-referenced-multiple-times\">CTE Referenced Multiple Times</a></h3>\n<pre><code class=\"language-sql\">-- Reference the same CTE twice to form a cross join\nWITH codes AS (SELECT 1 AS x UNION SELECT 2)\nSELECT * FROM codes AS a, codes AS b ORDER BY 1, 2;\n-- 1|1\n-- 1|2\n-- 2|1\n-- 2|2\n</code></pre>\n<h3 id=\"cte-visible-in-scalar-subqueries\"><a class=\"header\" href=\"#cte-visible-in-scalar-subqueries\">CTE Visible in Scalar Subqueries</a></h3>\n<pre><code class=\"language-sql\">-- A CTE can be referenced inside scalar subqueries in the SELECT list\nWITH constants AS (SELECT 10 AS x, 20 AS y)\nSELECT (SELECT x FROM constants), (SELECT y FROM constants);\n-- 10|20\n</code></pre>\n<h3 id=\"cte-with-insert\"><a class=\"header\" href=\"#cte-with-insert\">CTE with INSERT</a></h3>\n<pre><code class=\"language-sql\">-- Use a CTE to supply rows for an INSERT ... SELECT\nCREATE TABLE orders(amount);\nWITH new_orders AS (\n  SELECT 100 UNION SELECT 250 UNION SELECT 75\n)\nINSERT INTO orders SELECT * FROM new_orders;\n\nSELECT * FROM orders ORDER BY 1;\n-- 75\n-- 100\n-- 250\n</code></pre>\n<h3 id=\"cte-with-insert-and-returning\"><a class=\"header\" href=\"#cte-with-insert-and-returning\">CTE with INSERT and RETURNING</a></h3>\n<pre><code class=\"language-sql\">-- CTEs work with the RETURNING clause\nCREATE TABLE items(name TEXT);\nWITH new_items AS (SELECT 'widget' AS name)\nINSERT INTO items SELECT * FROM new_items RETURNING name;\n-- widget\n</code></pre>\n<h3 id=\"cte-with-insert-values-subquery\"><a class=\"header\" href=\"#cte-with-insert-values-subquery\">CTE with INSERT VALUES Subquery</a></h3>\n<pre><code class=\"language-sql\">-- Reference a CTE inside a VALUES clause via scalar subquery\nCREATE TABLE settings(key TEXT, value INT);\nWITH defaults AS (SELECT 99 AS x)\nINSERT INTO settings VALUES ('threshold', (SELECT x FROM defaults));\n\nSELECT * FROM settings;\n-- threshold|99\n</code></pre>\n<h3 id=\"cte-with-update\"><a class=\"header\" href=\"#cte-with-update\">CTE with UPDATE</a></h3>\n<pre><code class=\"language-sql\">-- Use a CTE to identify rows to update\nCREATE TABLE products(id INTEGER, price REAL);\nINSERT INTO products VALUES (1, 10.0), (2, 20.0), (3, 30.0);\n\nWITH expensive AS (SELECT id FROM products WHERE price &gt; 15.0)\nUPDATE products SET price = price * 0.9\nWHERE id IN (SELECT id FROM expensive);\n\nSELECT * FROM products ORDER BY id;\n-- 1|10.0\n-- 2|18.0\n-- 3|27.0\n</code></pre>\n<h3 id=\"cte-with-delete\"><a class=\"header\" href=\"#cte-with-delete\">CTE with DELETE</a></h3>\n<pre><code class=\"language-sql\">-- Use a CTE to identify rows to delete\nCREATE TABLE tasks(id INTEGER, status TEXT);\nINSERT INTO tasks VALUES (1, 'done'), (2, 'pending'), (3, 'done');\n\nWITH completed AS (SELECT id FROM tasks WHERE status = 'done')\nDELETE FROM tasks WHERE id IN (SELECT id FROM completed);\n\nSELECT * FROM tasks;\n-- 2|pending\n</code></pre>\n<h3 id=\"cte-with-multiple-ctes-in-delete\"><a class=\"header\" href=\"#cte-with-multiple-ctes-in-delete\">CTE with Multiple CTEs in DELETE</a></h3>\n<pre><code class=\"language-sql\">-- Combine multiple CTEs to build complex conditions\nCREATE TABLE inventory(id INTEGER);\nINSERT INTO inventory VALUES (1),(2),(3),(4),(5);\n\nWITH\n  low AS (SELECT 1 UNION SELECT 2),\n  high AS (SELECT 4 UNION SELECT 5)\nDELETE FROM inventory\nWHERE id IN (SELECT * FROM low) OR id IN (SELECT * FROM high);\n\nSELECT * FROM inventory;\n-- 3\n</code></pre>\n<h3 id=\"cte-feeding-another-cte-for-insert\"><a class=\"header\" href=\"#cte-feeding-another-cte-for-insert\">CTE Feeding Another CTE for INSERT</a></h3>\n<pre><code class=\"language-sql\">-- Chain CTEs: the first provides base data, the second transforms it\nCREATE TABLE results(value INTEGER);\nWITH\n  base AS (SELECT 1 AS x),\n  transformed AS (SELECT x + 10 FROM base)\nINSERT INTO results SELECT * FROM transformed;\n\nSELECT * FROM results;\n-- 11\n</code></pre>\n<h3 id=\"cte-visible-in-returning-subqueries\"><a class=\"header\" href=\"#cte-visible-in-returning-subqueries\">CTE Visible in RETURNING Subqueries</a></h3>\n<pre><code class=\"language-sql\">-- Reference a CTE inside the RETURNING clause\nCREATE TABLE logs(entry TEXT);\nINSERT INTO logs VALUES ('old_entry');\n\nWITH marker AS (SELECT 99 AS code)\nDELETE FROM logs WHERE entry = 'old_entry'\nRETURNING (SELECT code FROM marker);\n-- 99\n</code></pre>\n<h2 id=\"compatibility-5\"><a class=\"header\" href=\"#compatibility-5\">Compatibility</a></h2>\n<p>Turso supports the <code>WITH</code> clause with the following limitations compared to SQLite:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Feature</th><th>Status</th></tr>\n</thead>\n<tbody>\n<tr><td>Ordinary CTEs</td><td>Supported</td></tr>\n<tr><td>Multiple CTEs in one <code>WITH</code> clause</td><td>Supported</td></tr>\n<tr><td>CTE with <code>SELECT</code>, <code>INSERT</code>, <code>UPDATE</code>, <code>DELETE</code></td><td>Supported</td></tr>\n<tr><td>Explicit CTE column names</td><td>Supported</td></tr>\n<tr><td>Compound <code>SELECT</code> in CTE body (<code>UNION</code>, <code>UNION ALL</code>, <code>INTERSECT</code>, <code>EXCEPT</code>)</td><td>Supported</td></tr>\n<tr><td><code>RECURSIVE</code> CTEs</td><td>Not supported</td></tr>\n<tr><td><code>MATERIALIZED</code> / <code>NOT MATERIALIZED</code> hints</td><td>Not supported</td></tr>\n<tr><td>Non-<code>SELECT</code> statements in CTE body (<code>INSERT</code>, <code>UPDATE</code>, <code>DELETE</code> inside the CTE definition)</td><td>Not supported</td></tr>\n</tbody>\n</table>\n</div>\n<p>Only <code>SELECT</code> statements (including compound <code>SELECT</code>) are allowed in the CTE body. The main statement that follows the <code>WITH</code> clause can be <code>SELECT</code>, <code>INSERT</code>, <code>UPDATE</code>, or <code>DELETE</code>.</p>\n<div style=\"break-before: page; page-break-before: always;\"></div>\n<h1 id=\"insert\"><a class=\"header\" href=\"#insert\">INSERT</a></h1>\n<h2 id=\"syntax-7\"><a class=\"header\" href=\"#syntax-7\">Syntax</a></h2>\n<pre><code class=\"language-sql\">{INSERT | REPLACE} [OR {ROLLBACK | ABORT | FAIL | IGNORE | REPLACE}]\n  INTO table-name [AS alias] [(column-name [, ...])]\n  {VALUES (expr [, ...]) [, ...] | SELECT ... | DEFAULT VALUES}\n  [RETURNING expr [AS alias] [, ...]]\n</code></pre>\n<h2 id=\"description-7\"><a class=\"header\" href=\"#description-7\">Description</a></h2>\n<p>The INSERT statement creates new rows in an existing table. There are three\nforms: INSERT … VALUES inserts one or more explicitly specified rows,\nINSERT … SELECT inserts rows produced by a query, and INSERT … DEFAULT\nVALUES inserts a single row where every column takes its default value.</p>\n<p>The keyword <code>REPLACE</code> is shorthand for <code>INSERT OR REPLACE</code>. When a constraint\nviolation occurs, <code>REPLACE</code> deletes the conflicting row and inserts the new\none.</p>\n<h2 id=\"clauses-5\"><a class=\"header\" href=\"#clauses-5\">Clauses</a></h2>\n<h3 id=\"column-list\"><a class=\"header\" href=\"#column-list\">Column List</a></h3>\n<p>An optional parenthesized list of column names may appear after the table name.\nWhen provided, the number of values in each VALUES row (or columns in the\nSELECT result) must match the number of listed columns. Columns not named in\nthe list receive their default value, or NULL if no default is defined.</p>\n<p>When the column list is omitted, the number of values must match the total\nnumber of columns in the table, and values are assigned left-to-right.</p>\n<pre><code class=\"language-sql\">-- With column list: unlisted columns get defaults\nINSERT INTO products (name, price) VALUES ('Widget', 9.99);\n\n-- Without column list: must supply every column\nINSERT INTO products VALUES (1, 'Widget', 9.99, 1);\n</code></pre>\n<h3 id=\"values\"><a class=\"header\" href=\"#values\">VALUES</a></h3>\n<p>The VALUES clause provides one or more rows of literal expressions. Multiple\nrows are separated by commas.</p>\n<pre><code class=\"language-sql\">-- Single row\nINSERT INTO products (name, price) VALUES ('Widget', 9.99);\n\n-- Multiple rows\nINSERT INTO products (name, price)\n  VALUES ('Widget', 9.99), ('Gadget', 24.95), ('Gizmo', 4.50);\n</code></pre>\n<h3 id=\"select-1\"><a class=\"header\" href=\"#select-1\">SELECT</a></h3>\n<p>A SELECT statement may be used instead of VALUES to insert rows produced by a\nquery. Any valid SELECT is allowed, including compound SELECTs (UNION, UNION\nALL, INTERSECT, EXCEPT) and SELECTs with ORDER BY or LIMIT.</p>\n<p>If a column list is specified, the number of columns in the SELECT result must\nmatch the number of listed columns. Otherwise it must match the total number\nof columns in the target table.</p>\n<pre><code class=\"language-sql\">CREATE TABLE products_archive (id INTEGER PRIMARY KEY, name TEXT, price REAL);\nINSERT INTO products_archive SELECT id, name, price FROM products WHERE in_stock = 0;\n</code></pre>\n<h3 id=\"default-values\"><a class=\"header\" href=\"#default-values\">DEFAULT VALUES</a></h3>\n<p>The DEFAULT VALUES form inserts exactly one row. Every column receives its\ndefault value as specified in the CREATE TABLE statement, or NULL if no default\nwas defined.</p>\n<pre><code class=\"language-sql\">CREATE TABLE logs (id INTEGER PRIMARY KEY, created_at TEXT DEFAULT 'now', msg TEXT DEFAULT 'empty');\nINSERT INTO logs DEFAULT VALUES;\n-- Result: id=1, created_at='now', msg='empty'\n</code></pre>\n<h3 id=\"or-conflict-algorithm\"><a class=\"header\" href=\"#or-conflict-algorithm\">OR Conflict Algorithm</a></h3>\n<p>By prefixing the INSERT with <code>OR algorithm</code>, you can control what happens when\nthe insertion would violate a constraint (UNIQUE, NOT NULL, CHECK, or PRIMARY\nKEY). The keyword appears between <code>INSERT</code> and <code>INTO</code>.</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Algorithm</th><th>Behavior</th></tr>\n</thead>\n<tbody>\n<tr><td>ABORT</td><td>Abort the current statement and roll back any changes it made. This is the default behavior when no algorithm is specified.</td></tr>\n<tr><td>ROLLBACK</td><td>Abort the current statement and roll back the entire enclosing transaction.</td></tr>\n<tr><td>FAIL</td><td>Abort the current statement but keep changes made by earlier rows within the same statement.</td></tr>\n<tr><td>IGNORE</td><td>Skip the row that caused the violation and continue processing remaining rows.</td></tr>\n<tr><td>REPLACE</td><td>Delete the existing row that caused the conflict, then insert the new row. If the conflicting column has a NOT NULL constraint with a DEFAULT value, the default is used when NULL is supplied. If there is no default, the statement fails.</td></tr>\n</tbody>\n</table>\n</div>\n<p>Foreign key constraint violations are <strong>not</strong> affected by the conflict\nalgorithm. They always behave like ABORT regardless of which algorithm is\nspecified.</p>\n<pre><code class=\"language-sql\">-- IGNORE: silently skip rows that violate constraints\nCREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT UNIQUE);\nINSERT INTO users VALUES (1, 'alice@example.com');\nINSERT OR IGNORE INTO users VALUES (2, 'alice@example.com');\n-- The second row is silently skipped; the table still contains only row 1.\n\n-- REPLACE: delete the conflicting row and insert the new one\nINSERT OR REPLACE INTO users VALUES (2, 'alice@example.com');\n-- Row 1 is deleted, row 2 with the same email is inserted.\n</code></pre>\n<p>The <code>REPLACE</code> keyword (without <code>INSERT OR</code>) is equivalent to\n<code>INSERT OR REPLACE</code>:</p>\n<pre><code class=\"language-sql\">REPLACE INTO users VALUES (3, 'alice@example.com');\n</code></pre>\n<h3 id=\"returning\"><a class=\"header\" href=\"#returning\">RETURNING</a></h3>\n<p>The RETURNING clause causes the INSERT statement to return values from each\ninserted row, much like a SELECT. It accepts a list of expressions that may\nreference columns of the inserted row, use functions, or contain arbitrary\nexpressions. Use <code>*</code> to return all columns.</p>\n<pre><code class=\"language-sql\">CREATE TABLE orders (id INTEGER PRIMARY KEY, product TEXT, qty INTEGER);\nINSERT INTO orders (product, qty) VALUES ('Widget', 5) RETURNING *;\n-- Returns: 1|Widget|5\n\nINSERT INTO orders (product, qty)\n  VALUES ('Gadget', 3), ('Gizmo', 12)\n  RETURNING id, product;\n-- Returns:\n-- 2|Gadget\n-- 3|Gizmo\n</code></pre>\n<p>RETURNING expressions can include functions and computed values:</p>\n<pre><code class=\"language-sql\">CREATE TABLE line_items (id INTEGER PRIMARY KEY, product TEXT, qty INTEGER, unit_price REAL);\nINSERT INTO line_items (product, qty, unit_price)\n  VALUES ('Widget', 5, 9.99)\n  RETURNING id, product, qty * unit_price AS total;\n-- Returns: 1|Widget|49.95\n</code></pre>\n<p>For full details on the RETURNING clause, see <a href=\"#returning-3\">RETURNING</a>.</p>\n<h3 id=\"table-alias\"><a class=\"header\" href=\"#table-alias\">Table Alias</a></h3>\n<p>The optional <code>AS alias</code> after the table name provides an alternative name for\nthe table. This alias is primarily useful with the UPSERT clause (ON CONFLICT\n… DO UPDATE), which is documented separately.</p>\n<h2 id=\"examples-7\"><a class=\"header\" href=\"#examples-7\">Examples</a></h2>\n<pre><code class=\"language-sql\">-- Create a sample table\nCREATE TABLE employees (\n  id INTEGER PRIMARY KEY,\n  name TEXT NOT NULL,\n  department TEXT,\n  salary REAL DEFAULT 50000.00\n);\n</code></pre>\n<pre><code class=\"language-sql\">-- Insert a single row, specifying all columns\nINSERT INTO employees VALUES (1, 'Alice Johnson', 'Engineering', 95000.00);\n</code></pre>\n<pre><code class=\"language-sql\">-- Insert with a column list; salary gets its default value\nINSERT INTO employees (id, name, department)\n  VALUES (2, 'Bob Smith', 'Marketing');\n</code></pre>\n<pre><code class=\"language-sql\">-- Insert multiple rows at once\nINSERT INTO employees (name, department, salary) VALUES\n  ('Carol White', 'Engineering', 105000.00),\n  ('David Brown', 'Sales', 72000.00),\n  ('Eve Davis', 'Marketing', 68000.00);\n</code></pre>\n<pre><code class=\"language-sql\">-- Insert from a SELECT: copy all engineers into a new table\nCREATE TABLE engineers (id INTEGER PRIMARY KEY, name TEXT, salary REAL);\nINSERT INTO engineers\n  SELECT id, name, salary FROM employees WHERE department = 'Engineering';\n</code></pre>\n<pre><code class=\"language-sql\">-- Insert from a compound SELECT\nCREATE TABLE all_names (name TEXT);\nINSERT INTO all_names\n  SELECT name FROM employees\n  UNION ALL\n  SELECT name FROM engineers;\n</code></pre>\n<pre><code class=\"language-sql\">-- Insert a single row with all defaults\nCREATE TABLE events (id INTEGER PRIMARY KEY, description TEXT DEFAULT 'unknown');\nINSERT INTO events DEFAULT VALUES;\n-- Result: id=1, description='unknown'\n</code></pre>\n<pre><code class=\"language-sql\">-- INSERT OR IGNORE: skip rows that would violate a UNIQUE constraint\nCREATE TABLE tags (id INTEGER PRIMARY KEY, label TEXT UNIQUE);\nINSERT INTO tags (label) VALUES ('urgent'), ('review'), ('done');\nINSERT OR IGNORE INTO tags (label) VALUES ('urgent'), ('new'), ('done');\n-- Only 'new' is inserted; 'urgent' and 'done' are skipped.\nSELECT label FROM tags ORDER BY label;\n-- done\n-- new\n-- review\n-- urgent\n</code></pre>\n<pre><code class=\"language-sql\">-- INSERT OR REPLACE: replace the conflicting row\nCREATE TABLE settings (key TEXT PRIMARY KEY, value TEXT);\nINSERT INTO settings VALUES ('theme', 'light');\nINSERT OR REPLACE INTO settings VALUES ('theme', 'dark');\nSELECT * FROM settings;\n-- theme|dark\n</code></pre>\n<pre><code class=\"language-sql\">-- REPLACE shorthand (equivalent to INSERT OR REPLACE)\nREPLACE INTO settings VALUES ('theme', 'solarized');\n</code></pre>\n<pre><code class=\"language-sql\">-- INSERT with RETURNING to get generated IDs\nCREATE TABLE tickets (id INTEGER PRIMARY KEY, title TEXT, priority INTEGER);\nINSERT INTO tickets (title, priority)\n  VALUES ('Fix login bug', 1), ('Update docs', 3)\n  RETURNING id, title;\n-- 1|Fix login bug\n-- 2|Update docs\n</code></pre>\n<pre><code class=\"language-sql\">-- INSERT ... SELECT with RETURNING\nCREATE TABLE source (name TEXT, amount INTEGER);\nINSERT INTO source VALUES ('Alice', 10), ('Bob', 20);\nCREATE TABLE totals (name TEXT, amount INTEGER);\nINSERT INTO totals SELECT * FROM source RETURNING *;\n-- Alice|10\n-- Bob|20\n</code></pre>\n<pre><code class=\"language-sql\">-- INSERT OR IGNORE with RETURNING: only inserted rows are returned\nCREATE TABLE codes (id INTEGER PRIMARY KEY, code TEXT UNIQUE);\nINSERT INTO codes (code) VALUES ('A');\nINSERT OR IGNORE INTO codes (code) VALUES ('A'), ('B') RETURNING id, code;\n-- 2|B\n-- (The duplicate 'A' is skipped and does not appear in the output.)\n</code></pre>\n<div style=\"break-before: page; page-break-before: always;\"></div>\n<h1 id=\"upsert-on-conflict\"><a class=\"header\" href=\"#upsert-on-conflict\">UPSERT (ON CONFLICT)</a></h1>\n<h2 id=\"syntax-8\"><a class=\"header\" href=\"#syntax-8\">Syntax</a></h2>\n<pre><code class=\"language-sql\">INSERT [OR {ROLLBACK | ABORT | FAIL | IGNORE | REPLACE}]\n  INTO table-name [(column-name [, ...])]\n  {VALUES (expr [, ...]) [, ...] | select-stmt}\n  [ON CONFLICT [(column-name [, ...]) [WHERE expr]]\n    {DO NOTHING | DO UPDATE SET assignment [, ...] [WHERE expr]}\n  ] [, ...]\n</code></pre>\n<p>Where each <code>assignment</code> is:</p>\n<pre><code class=\"language-sql\">column-name = expr\n</code></pre>\n<p>or:</p>\n<pre><code class=\"language-sql\">(column-name [, ...]) = (expr [, ...])\n</code></pre>\n<h2 id=\"description-8\"><a class=\"header\" href=\"#description-8\">Description</a></h2>\n<p>UPSERT is not a standalone statement. It is an optional clause that can be appended to an <code>INSERT</code> statement to control what happens when the insertion would violate a uniqueness constraint (a <code>UNIQUE</code> column, a <code>UNIQUE</code> index, or a <code>PRIMARY KEY</code>). Without an UPSERT clause, a uniqueness violation causes the statement to fail with an error.</p>\n<p>An UPSERT clause begins with <code>ON CONFLICT</code> and specifies one of two actions: <code>DO NOTHING</code>, which silently skips the conflicting row, or <code>DO UPDATE SET</code>, which converts the insert into an update of the existing row. This gives you an atomic “insert or update” operation in a single statement, eliminating the need for separate existence checks.</p>\n<p>Turso follows the PostgreSQL-style UPSERT syntax. UPSERT only applies to uniqueness constraints. It does not intercept <code>NOT NULL</code>, <code>CHECK</code>, or foreign key violations – those always produce an error regardless of any <code>ON CONFLICT</code> clause.</p>\n<h2 id=\"clauses-6\"><a class=\"header\" href=\"#clauses-6\">Clauses</a></h2>\n<h3 id=\"conflict-target\"><a class=\"header\" href=\"#conflict-target\">Conflict Target</a></h3>\n<p>The conflict target appears between <code>ON CONFLICT</code> and <code>DO</code>. It specifies which uniqueness constraint should trigger the upsert behavior.</p>\n<pre><code class=\"language-sql\">ON CONFLICT (column-name [, ...]) [WHERE expr] DO ...\n</code></pre>\n<p>The column list must exactly match the columns of a <code>UNIQUE</code> index or <code>PRIMARY KEY</code>. For a composite unique index, all columns must be listed, though the order does not matter.</p>\n<pre><code class=\"language-sql\">-- Matches a UNIQUE index on (a, b), regardless of column order\nON CONFLICT (b, a) DO UPDATE SET ...\n</code></pre>\n<p>The conflict target is optional on the last (or only) <code>ON CONFLICT</code> clause. When omitted, the clause matches any uniqueness constraint violation that has not already been handled by a preceding <code>ON CONFLICT</code> clause.</p>\n<pre><code class=\"language-sql\">-- Omitted target: matches any uniqueness violation\nON CONFLICT DO NOTHING\n</code></pre>\n<p>If a conflict target includes a <code>WHERE</code> clause, it becomes a partial conflict target. The <code>WHERE</code> expression restricts which rows of a partial unique index are considered when matching. This is relevant when the unique index itself was created with a <code>WHERE</code> clause.</p>\n<h3 id=\"do-nothing\"><a class=\"header\" href=\"#do-nothing\">DO NOTHING</a></h3>\n<pre><code class=\"language-sql\">ON CONFLICT [(conflict-target)] DO NOTHING\n</code></pre>\n<p>When a uniqueness constraint is violated, the conflicting row is silently skipped. No insert or update occurs for that row. If the <code>INSERT</code> statement includes a <code>RETURNING</code> clause, skipped rows produce no output.</p>\n<h3 id=\"do-update-set\"><a class=\"header\" href=\"#do-update-set\">DO UPDATE SET</a></h3>\n<pre><code class=\"language-sql\">ON CONFLICT [(conflict-target)] DO UPDATE SET assignment [, ...] [WHERE expr]\n</code></pre>\n<p>When a uniqueness constraint is violated, Turso updates the existing row instead of inserting a new one. The <code>SET</code> clause works the same as in a regular <code>UPDATE</code> statement. You can set individual columns or use the tuple form to set multiple columns at once:</p>\n<pre><code class=\"language-sql\">-- Individual assignments\nDO UPDATE SET price = excluded.price, quantity = excluded.quantity\n\n-- Tuple assignment\nDO UPDATE SET (price, quantity) = (excluded.price, excluded.quantity)\n</code></pre>\n<p>Within the <code>SET</code> clause and its optional <code>WHERE</code> clause, column references that are unqualified or qualified with the target table name refer to the existing row (before the update). To reference the values that were proposed for insertion, use the special <code>excluded</code> table qualifier.</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Reference</th><th>Meaning</th></tr>\n</thead>\n<tbody>\n<tr><td><code>column-name</code></td><td>Value in the existing row</td></tr>\n<tr><td><code>table-name.column-name</code></td><td>Value in the existing row (explicit)</td></tr>\n<tr><td><code>excluded.column-name</code></td><td>Value from the attempted <code>INSERT</code></td></tr>\n</tbody>\n</table>\n</div>\n<p>The <code>DO UPDATE</code> clause always uses <code>ABORT</code> conflict resolution internally. If the update itself causes a constraint violation (for example, setting a column to a value that duplicates another row’s unique key), the entire <code>INSERT</code> statement is rolled back.</p>\n<h3 id=\"where-on-do-update\"><a class=\"header\" href=\"#where-on-do-update\">WHERE on DO UPDATE</a></h3>\n<p>An optional <code>WHERE</code> clause after <code>DO UPDATE SET</code> controls whether the update actually takes effect. If the condition evaluates to false or NULL, the update is skipped for that row, effectively making the clause behave like <code>DO NOTHING</code> for that particular conflict.</p>\n<pre><code class=\"language-sql\">ON CONFLICT (name) DO UPDATE SET\n  phonenumber = excluded.phonenumber,\n  valid_date = excluded.valid_date\nWHERE excluded.valid_date &gt; table-name.valid_date\n</code></pre>\n<p>This is useful for “only update if the new data is newer” patterns, or for conditional merges.</p>\n<h3 id=\"multiple-on-conflict-clauses\"><a class=\"header\" href=\"#multiple-on-conflict-clauses\">Multiple ON CONFLICT Clauses</a></h3>\n<p>An <code>INSERT</code> statement may include more than one <code>ON CONFLICT</code> clause. Turso evaluates them in order. When a uniqueness violation occurs, the first clause whose conflict target matches the violated constraint is used. Only one clause executes per conflicting row.</p>\n<p>Every <code>ON CONFLICT</code> clause except the last one must include a conflict target. The last clause may omit the conflict target to serve as a catch-all for any remaining uniqueness violations.</p>\n<pre><code class=\"language-sql\">INSERT INTO table-name (...)\n  VALUES (...)\n  ON CONFLICT (x) DO UPDATE SET ...   -- handles conflicts on x\n  ON CONFLICT (y) DO UPDATE SET ...   -- handles conflicts on y\n  ON CONFLICT DO NOTHING;             -- catch-all for any other uniqueness violation\n</code></pre>\n<h3 id=\"multi-row-inserts\"><a class=\"header\" href=\"#multi-row-inserts\">Multi-Row Inserts</a></h3>\n<p>When an <code>INSERT</code> provides multiple rows (either through multiple <code>VALUES</code> rows or a <code>SELECT</code> subquery), the upsert decision is made independently for each row. Some rows may be inserted normally, while others trigger <code>DO UPDATE</code> or <code>DO NOTHING</code>.</p>\n<h2 id=\"examples-8\"><a class=\"header\" href=\"#examples-8\">Examples</a></h2>\n<pre><code class=\"language-sql\">-- Create a table with a unique constraint\nCREATE TABLE products (\n  id INTEGER PRIMARY KEY,\n  name TEXT UNIQUE,\n  price REAL,\n  quantity INTEGER\n);\n\nINSERT INTO products VALUES (1, 'Widget', 9.99, 100);\n</code></pre>\n<pre><code class=\"language-sql\">-- DO NOTHING: silently skip if the name already exists\nINSERT INTO products VALUES (2, 'Widget', 12.99, 200)\n  ON CONFLICT DO NOTHING;\n\nSELECT * FROM products;\n-- 1|Widget|9.99|100\n</code></pre>\n<pre><code class=\"language-sql\">-- DO UPDATE: update price and quantity when name conflicts\nINSERT INTO products VALUES (1, 'Widget', 12.99, 200)\n  ON CONFLICT(name) DO UPDATE SET\n    price = excluded.price,\n    quantity = excluded.quantity;\n\nSELECT * FROM products;\n-- 1|Widget|12.99|200\n</code></pre>\n<pre><code class=\"language-sql\">-- Mix existing row values with excluded values\nCREATE TABLE counters (key TEXT UNIQUE, hits INTEGER, last_seen TEXT);\nINSERT INTO counters VALUES ('page_home', 1, '2024-01-01');\n\n-- Increment hits while updating last_seen from the new row\nINSERT INTO counters VALUES ('page_home', 1, '2024-06-15')\n  ON CONFLICT(key) DO UPDATE SET\n    hits = hits + 1,\n    last_seen = excluded.last_seen;\n\nSELECT * FROM counters;\n-- page_home|2|2024-06-15\n</code></pre>\n<pre><code class=\"language-sql\">-- Conditional update: only apply if the new value is greater\nCREATE TABLE high_scores (player TEXT UNIQUE, score INTEGER);\nINSERT INTO high_scores VALUES ('Alice', 5);\n\nINSERT INTO high_scores VALUES ('Alice', 3)\n  ON CONFLICT(player) DO UPDATE SET score = excluded.score\n  WHERE excluded.score &gt; score;\n\nSELECT * FROM high_scores;\n-- Alice|5  (unchanged because 3 is not greater than 5)\n\nINSERT INTO high_scores VALUES ('Alice', 10)\n  ON CONFLICT(player) DO UPDATE SET score = excluded.score\n  WHERE excluded.score &gt; score;\n\nSELECT * FROM high_scores;\n-- Alice|10  (updated because 10 &gt; 5)\n</code></pre>\n<pre><code class=\"language-sql\">-- Multi-row insert with upsert: each row handled independently\nCREATE TABLE inventory (sku TEXT UNIQUE, name TEXT);\nINSERT INTO inventory VALUES ('A001', 'Original');\n\nINSERT INTO inventory VALUES ('A001', 'Updated'), ('B002', 'New Item')\n  ON CONFLICT(sku) DO UPDATE SET name = excluded.name;\n\nSELECT * FROM inventory ORDER BY sku;\n-- A001|Updated\n-- B002|New Item\n</code></pre>\n<pre><code class=\"language-sql\">-- Multiple ON CONFLICT clauses with different targets\nCREATE TABLE records (\n  id INTEGER PRIMARY KEY,\n  code TEXT UNIQUE,\n  email TEXT UNIQUE,\n  note TEXT DEFAULT NULL\n);\n\nINSERT INTO records VALUES (1, 'x', 'a@test.com', 'original');\nINSERT INTO records VALUES (2, 'y', 'b@test.com', 'original');\n\nINSERT INTO records VALUES (3, 'x', 'c@test.com', 'new')\n  ON CONFLICT(code) DO UPDATE SET note = 'code-conflict'\n  ON CONFLICT(email) DO UPDATE SET note = 'email-conflict'\n  ON CONFLICT DO UPDATE SET note = 'other-conflict';\n\nSELECT * FROM records ORDER BY id;\n-- 1|x|a@test.com|code-conflict\n-- 2|y|b@test.com|original\n</code></pre>\n<pre><code class=\"language-sql\">-- Upsert with RETURNING clause\nCREATE TABLE settings (key TEXT PRIMARY KEY, value TEXT);\nINSERT INTO settings VALUES ('theme', 'light');\n\nINSERT INTO settings VALUES ('theme', 'dark')\n  ON CONFLICT DO UPDATE SET value = excluded.value\n  RETURNING key, value;\n-- theme|dark\n</code></pre>\n<pre><code class=\"language-sql\">-- DO NOTHING with RETURNING produces no output for skipped rows\nCREATE TABLE tags (name TEXT PRIMARY KEY);\nINSERT INTO tags VALUES ('important');\n\nINSERT INTO tags VALUES ('important')\n  ON CONFLICT DO NOTHING\n  RETURNING name;\n-- (no output)\n</code></pre>\n<pre><code class=\"language-sql\">-- Composite unique index: target must list all columns\nCREATE TABLE assignments (project TEXT, employee TEXT, role TEXT);\nCREATE UNIQUE INDEX assignments_pk ON assignments(project, employee);\n\nINSERT INTO assignments VALUES ('Atlas', 'Alice', 'Lead');\n\n-- Column order in the target does not need to match the index\nINSERT INTO assignments VALUES ('Atlas', 'Alice', 'Manager')\n  ON CONFLICT(employee, project) DO UPDATE SET role = excluded.role;\n\nSELECT * FROM assignments;\n-- Atlas|Alice|Manager\n</code></pre>\n<pre><code class=\"language-sql\">-- Using the table-qualified name in the conflict target\nCREATE TABLE metrics (sensor_id INTEGER UNIQUE, reading REAL);\nINSERT INTO metrics VALUES (1, 23.5);\n\nINSERT INTO metrics VALUES (1, 25.0)\n  ON CONFLICT(metrics.sensor_id) DO UPDATE SET\n    reading = metrics.reading + excluded.reading;\n\nSELECT * FROM metrics;\n-- 1|48.5\n</code></pre>\n<h2 id=\"compatibility-6\"><a class=\"header\" href=\"#compatibility-6\">Compatibility</a></h2>\n<p>UPSERT is fully supported in Turso. The syntax and behavior match SQLite, including support for multiple <code>ON CONFLICT</code> clauses, the <code>excluded</code> table, conditional <code>WHERE</code> clauses on <code>DO UPDATE</code>, composite conflict targets, and tuple-form <code>SET</code> assignments.</p>\n<div style=\"break-before: page; page-break-before: always;\"></div>\n<h1 id=\"update\"><a class=\"header\" href=\"#update\">UPDATE</a></h1>\n<h2 id=\"syntax-9\"><a class=\"header\" href=\"#syntax-9\">Syntax</a></h2>\n<pre><code class=\"language-sql\">UPDATE [OR {ROLLBACK | ABORT | FAIL | IGNORE | REPLACE}]\n  table-name [AS alias]\n  SET {column-name = expr | (column-name [, ...]) = (expr [, ...])} [, ...]\n  [WHERE expr]\n  [RETURNING expr [AS alias] [, ...]]\n  [LIMIT expr [OFFSET expr]]\n</code></pre>\n<h2 id=\"description-9\"><a class=\"header\" href=\"#description-9\">Description</a></h2>\n<p>The UPDATE statement modifies the values of columns in zero or more rows of an\nexisting table. Each SET clause assigns a new value to a column. If no WHERE\nclause is provided, every row in the table is updated. When a WHERE clause is\npresent, only rows for which the expression evaluates to true are modified.</p>\n<p>It is not an error if the WHERE clause matches zero rows. The statement\ncompletes successfully and modifies nothing.</p>\n<p>All expressions on the right-hand side of SET assignments are evaluated before\nany assignments are made. This means SET expressions can safely reference the\ncurrent (pre-update) values of any column in the same row, including columns\nthat appear on the left-hand side of another assignment in the same statement.</p>\n<h2 id=\"clauses-7\"><a class=\"header\" href=\"#clauses-7\">Clauses</a></h2>\n<h3 id=\"set\"><a class=\"header\" href=\"#set\">SET</a></h3>\n<p>The SET clause specifies one or more column assignments. Each assignment is\neither a single column name paired with an expression, or a parenthesized list\nof column names paired with a matching parenthesized list of expressions (row\nvalue syntax).</p>\n<pre><code class=\"language-sql\">-- Single column assignment\nUPDATE products SET price = 19.99 WHERE id = 1;\n\n-- Multiple column assignments\nUPDATE products SET price = 19.99, in_stock = 1 WHERE id = 1;\n\n-- Row value syntax (equivalent to individual assignments)\nUPDATE products SET (price, in_stock) = (19.99, 1) WHERE id = 1;\n</code></pre>\n<p>Columns not mentioned in the SET clause retain their existing values. If a\ncolumn name appears more than once in the SET clause, all but the rightmost\noccurrence are ignored.</p>\n<h3 id=\"where-1\"><a class=\"header\" href=\"#where-1\">WHERE</a></h3>\n<p>The WHERE clause limits which rows are updated. Only rows for which the\nexpression evaluates to true are affected. The expression can be any valid SQL\nexpression, including subqueries.</p>\n<pre><code class=\"language-sql\">-- Update rows matching a condition\nUPDATE orders SET status = 'shipped' WHERE status = 'pending';\n\n-- Update using a subquery in WHERE\nUPDATE orders SET status = 'priority'\n  WHERE customer_id IN (SELECT id FROM customers WHERE tier = 'gold');\n</code></pre>\n<h3 id=\"or-conflict-algorithm-1\"><a class=\"header\" href=\"#or-conflict-algorithm-1\">OR Conflict Algorithm</a></h3>\n<p>By prefixing the UPDATE with <code>OR algorithm</code>, you can control what happens when\nan updated value would violate a constraint (UNIQUE, NOT NULL, CHECK, or\nPRIMARY KEY). The keyword appears between <code>UPDATE</code> and the table name.</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Algorithm</th><th>Behavior</th></tr>\n</thead>\n<tbody>\n<tr><td>ABORT</td><td>Abort the current statement and roll back any changes it made. This is the default behavior when no algorithm is specified.</td></tr>\n<tr><td>ROLLBACK</td><td>Abort the current statement and roll back the entire enclosing transaction.</td></tr>\n<tr><td>FAIL</td><td>Abort the current statement but keep changes already made to earlier rows within the same statement.</td></tr>\n<tr><td>IGNORE</td><td>Skip the row that caused the violation and continue processing remaining rows.</td></tr>\n<tr><td>REPLACE</td><td>Delete the existing row that conflicts with the updated value, then apply the update. If the conflicting column has a NOT NULL constraint with a DEFAULT value, the default is used when NULL is supplied. If there is no default, the statement fails.</td></tr>\n</tbody>\n</table>\n</div>\n<p>Foreign key constraint violations are <strong>not</strong> affected by the conflict\nalgorithm. They always behave like ABORT regardless of which algorithm is\nspecified.</p>\n<pre><code class=\"language-sql\">-- IGNORE: skip updates that would violate a UNIQUE constraint\nCREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT UNIQUE);\nINSERT INTO users VALUES (1, 'alice@example.com'), (2, 'bob@example.com');\nUPDATE OR IGNORE users SET email = 'alice@example.com' WHERE id = 2;\n-- Row 2 is unchanged because the update would violate the UNIQUE constraint.\n\n-- REPLACE: delete the conflicting row, then apply the update\nUPDATE OR REPLACE users SET email = 'alice@example.com' WHERE id = 2;\n-- Row 1 is deleted, row 2 now has email 'alice@example.com'.\n</code></pre>\n<h3 id=\"returning-1\"><a class=\"header\" href=\"#returning-1\">RETURNING</a></h3>\n<p>The RETURNING clause causes the UPDATE statement to return values from each\nmodified row, much like a SELECT. It accepts a list of expressions that may\nreference columns of the updated row (with their new, post-update values), use\nfunctions, or contain arbitrary expressions. Use <code>*</code> to return all columns.</p>\n<pre><code class=\"language-sql\">UPDATE employees SET salary = salary * 1.10 WHERE department = 'Engineering'\n  RETURNING id, name, salary;\n</code></pre>\n<p>For full details on the RETURNING clause, see <a href=\"#returning-3\">RETURNING</a>.</p>\n<h3 id=\"limit-and-offset\"><a class=\"header\" href=\"#limit-and-offset\">LIMIT and OFFSET</a></h3>\n<p>The LIMIT clause restricts the maximum number of rows that the UPDATE\nmodifies. A negative value for LIMIT means no limit. When OFFSET is specified,\nthe first N rows that would otherwise be updated are skipped.</p>\n<p>Note that without ORDER BY (which Turso does not currently support for UPDATE),\nthe order in which rows are considered is arbitrary. Therefore, LIMIT and\nOFFSET choose from an unpredictable set of qualifying rows.</p>\n<pre><code class=\"language-sql\">-- Update at most 1 row\nUPDATE products SET featured = 1 WHERE category = 'electronics' LIMIT 1;\n\n-- Update 2 rows, skipping the first 3\nUPDATE logs SET archived = 1 LIMIT 2 OFFSET 3;\n</code></pre>\n<h2 id=\"examples-9\"><a class=\"header\" href=\"#examples-9\">Examples</a></h2>\n<pre><code class=\"language-sql\">-- Update a single row\nCREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT, department TEXT, salary REAL);\nINSERT INTO employees VALUES\n  (1, 'Alice', 'Engineering', 85000.0),\n  (2, 'Bob', 'Marketing', 72000.0),\n  (3, 'Charlie', 'Engineering', 92000.0);\nUPDATE employees SET salary = 90000.0 WHERE id = 1;\nSELECT * FROM employees WHERE id = 1;\n-- 1|Alice|Engineering|90000.0\n</code></pre>\n<pre><code class=\"language-sql\">-- Update multiple columns at once\nCREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT, department TEXT, salary REAL);\nINSERT INTO employees VALUES (2, 'Bob', 'Marketing', 72000.0);\nUPDATE employees SET department = 'Sales', salary = 78000.0 WHERE id = 2;\nSELECT * FROM employees WHERE id = 2;\n-- 2|Bob|Sales|78000.0\n</code></pre>\n<pre><code class=\"language-sql\">-- Update all rows using an expression that references the current value\nCREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT, salary REAL);\nINSERT INTO employees VALUES (1, 'Alice', 100.0), (2, 'Bob', 200.0), (3, 'Charlie', 300.0);\nUPDATE employees SET salary = salary * 2;\nSELECT * FROM employees ORDER BY id;\n-- 1|Alice|200.0\n-- 2|Bob|400.0\n-- 3|Charlie|600.0\n</code></pre>\n<pre><code class=\"language-sql\">-- Self-referencing expression: columns on the right side use pre-update values\nCREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT, salary REAL);\nINSERT INTO employees VALUES (1, 'Alice', 10.0), (2, 'Bob', 20.0);\nUPDATE employees SET salary = salary + 5.0 WHERE salary &lt; 15.0;\nSELECT * FROM employees ORDER BY id;\n-- 1|Alice|15.0\n-- 2|Bob|20.0\n</code></pre>\n<pre><code class=\"language-sql\">-- Update with a subquery in the WHERE clause\nCREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT, department TEXT, salary REAL);\nINSERT INTO employees VALUES (1, 'Alice', 'Engineering', 85000.0), (2, 'Bob', 'Sales', 72000.0);\nCREATE TABLE priority_departments (name TEXT);\nINSERT INTO priority_departments VALUES ('Engineering');\nUPDATE employees SET salary = salary + 10000.0\n  WHERE department IN (SELECT name FROM priority_departments);\nSELECT name, salary FROM employees ORDER BY id;\n-- Alice|95000.0\n-- Bob|72000.0\n</code></pre>\n<pre><code class=\"language-sql\">-- Scalar subquery in SET to assign a computed value\nCREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT, salary REAL);\nINSERT INTO employees VALUES (1, 'Alice', 80000.0), (2, 'Bob', 60000.0);\nUPDATE employees SET salary = (SELECT AVG(salary) FROM employees) WHERE id = 2;\nSELECT * FROM employees ORDER BY id;\n-- 1|Alice|80000.0\n-- 2|Bob|70000.0\n</code></pre>\n<pre><code class=\"language-sql\">-- Update with EXISTS subquery\nCREATE TABLE orders (id INTEGER PRIMARY KEY, customer_id INTEGER, status TEXT);\nINSERT INTO orders VALUES (1, 100, 'pending'), (2, 101, 'pending'), (3, 102, 'pending');\nCREATE TABLE order_items (order_id INTEGER, product TEXT);\nINSERT INTO order_items VALUES (1, 'widget'), (3, 'gadget');\nUPDATE orders SET status = 'has_items'\n  WHERE EXISTS (SELECT 1 FROM order_items WHERE order_items.order_id = orders.id);\nSELECT id, status FROM orders ORDER BY id;\n-- 1|has_items\n-- 2|pending\n-- 3|has_items\n</code></pre>\n<pre><code class=\"language-sql\">-- UPDATE with RETURNING to see new values\nCREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT, salary REAL);\nINSERT INTO employees VALUES (1, 'Alice', 85000.0), (2, 'Bob', 72000.0);\nUPDATE employees SET salary = salary + 10000.0 WHERE id = 1 RETURNING id, name, salary;\n-- 1|Alice|95000.0\n</code></pre>\n<pre><code class=\"language-sql\">-- RETURNING with expressions and functions\nCREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT, salary REAL);\nINSERT INTO employees VALUES (1, 'Alice', 85000.0);\nUPDATE employees SET name = 'alice johnson' WHERE id = 1\n  RETURNING id, upper(name), salary;\n-- 1|ALICE JOHNSON|85000.0\n</code></pre>\n<pre><code class=\"language-sql\">-- RETURNING all columns with *\nCREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT, salary REAL);\nINSERT INTO employees VALUES (1, 'Alice', 85000.0);\nUPDATE employees SET salary = 90000.0 WHERE id = 1 RETURNING *;\n-- 1|Alice|90000.0\n</code></pre>\n<pre><code class=\"language-sql\">-- Row value syntax for multiple assignments\nCREATE TABLE contacts (id INTEGER PRIMARY KEY, first_name TEXT, last_name TEXT);\nINSERT INTO contacts VALUES (1, 'Jane', 'Doe');\nUPDATE contacts SET (first_name, last_name) = ('John', 'Smith') WHERE id = 1\n  RETURNING *;\n-- 1|John|Smith\n</code></pre>\n<pre><code class=\"language-sql\">-- UPDATE OR IGNORE: skip rows that would violate a UNIQUE constraint\nCREATE TABLE tags (id INTEGER PRIMARY KEY, label TEXT UNIQUE);\nINSERT INTO tags VALUES (1, 'urgent'), (2, 'review');\nUPDATE OR IGNORE tags SET label = 'urgent' WHERE id = 2;\nSELECT * FROM tags ORDER BY id;\n-- 1|urgent\n-- 2|review\n</code></pre>\n<pre><code class=\"language-sql\">-- UPDATE OR REPLACE: delete the conflicting row and apply the update\nCREATE TABLE tags (id INTEGER PRIMARY KEY, label TEXT UNIQUE);\nINSERT INTO tags VALUES (1, 'urgent'), (2, 'review');\nUPDATE OR REPLACE tags SET label = 'urgent' WHERE id = 2;\nSELECT * FROM tags ORDER BY id;\n-- 2|urgent\n</code></pre>\n<pre><code class=\"language-sql\">-- UPDATE with LIMIT\nCREATE TABLE tasks (id INTEGER PRIMARY KEY, done INTEGER DEFAULT 0);\nINSERT INTO tasks (id) VALUES (1), (2), (3), (4), (5);\nUPDATE tasks SET done = 1 LIMIT 2;\nSELECT COUNT(*) FROM tasks WHERE done = 1;\n-- 2\n</code></pre>\n<pre><code class=\"language-sql\">-- Update the rowid directly\nCREATE TABLE notes (content TEXT);\nINSERT INTO notes (content) VALUES ('hello');\nUPDATE notes SET rowid = 42;\nSELECT rowid, content FROM notes;\n-- 42|hello\n</code></pre>\n<pre><code class=\"language-sql\">-- Update using a table alias in the WHERE clause\nCREATE TABLE scores (player TEXT, points INTEGER);\nINSERT INTO scores VALUES ('Alice', 10), ('Bob', 20);\nUPDATE scores AS s SET points = 99 WHERE s.player = 'Alice';\nSELECT * FROM scores ORDER BY player;\n-- Alice|99\n-- Bob|20\n</code></pre>\n<h2 id=\"compatibility-7\"><a class=\"header\" href=\"#compatibility-7\">Compatibility</a></h2>\n<p>Turso supports the core UPDATE statement with full compatibility. The following\nfeatures are not yet available:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Feature</th><th>Status</th></tr>\n</thead>\n<tbody>\n<tr><td>UPDATE … FROM</td><td>Not supported. Use subqueries in SET or WHERE instead.</td></tr>\n<tr><td>ORDER BY clause</td><td>Not supported. LIMIT and OFFSET select from an arbitrary set of qualifying rows.</td></tr>\n<tr><td>INDEXED BY / NOT INDEXED</td><td>Not supported. The query planner chooses indexes automatically.</td></tr>\n</tbody>\n</table>\n</div>\n<div style=\"break-before: page; page-break-before: always;\"></div>\n<h1 id=\"delete\"><a class=\"header\" href=\"#delete\">DELETE</a></h1>\n<h2 id=\"syntax-10\"><a class=\"header\" href=\"#syntax-10\">Syntax</a></h2>\n<pre><code class=\"language-sql\">[WITH cte-name AS (SELECT ...) [, ...]]\nDELETE FROM table-name\n  [WHERE expr]\n  [RETURNING expr [AS alias] [, ...]]\n  [LIMIT expr]\n</code></pre>\n<h2 id=\"description-10\"><a class=\"header\" href=\"#description-10\">Description</a></h2>\n<p>The DELETE statement removes rows from a table. If a WHERE clause is provided,\nonly the rows for which the WHERE expression evaluates to true are removed. Rows\nwhere the expression evaluates to false or NULL are retained.</p>\n<p>If the WHERE clause is omitted, all rows in the table are deleted. The table\nitself is not dropped – it remains in the schema with zero rows.</p>\n<p>The optional RETURNING clause causes the DELETE statement to return values from\neach deleted row, behaving much like a SELECT over the rows being removed.\nThe optional LIMIT clause restricts the maximum number of rows deleted.</p>\n<h2 id=\"clauses-8\"><a class=\"header\" href=\"#clauses-8\">Clauses</a></h2>\n<h3 id=\"where-2\"><a class=\"header\" href=\"#where-2\">WHERE</a></h3>\n<p>The WHERE clause specifies which rows to delete. It accepts any SQL expression\nthat evaluates to a boolean result. Only rows where the expression is true are\ndeleted. The expression may reference columns of the target table, use\nsubqueries, and include any supported operators or functions.</p>\n<p>When the WHERE clause is omitted, every row in the table is deleted.</p>\n<pre><code class=\"language-sql\">-- Delete a single row by primary key\nDELETE FROM employees WHERE id = 42;\n\n-- Delete rows matching a compound condition\nDELETE FROM employees WHERE salary &lt; 90000 AND department = 'Marketing';\n\n-- Delete rows using an IN list\nDELETE FROM orders WHERE status IN ('cancelled', 'expired');\n</code></pre>\n<h3 id=\"returning-2\"><a class=\"header\" href=\"#returning-2\">RETURNING</a></h3>\n<p>The RETURNING clause causes the DELETE statement to return values from each\ndeleted row. It accepts a comma-separated list of expressions that may reference\ncolumns of the deleted row, use functions, or contain computed values. Use <code>*</code> to\nreturn all columns.</p>\n<pre><code class=\"language-sql\">DELETE FROM products WHERE quantity = 0 RETURNING id, name;\n\nDELETE FROM employees WHERE department = 'Sales' RETURNING *;\n</code></pre>\n<p>For full details on the RETURNING clause, see <a href=\"#returning-3\">RETURNING</a>.</p>\n<h3 id=\"limit-2\"><a class=\"header\" href=\"#limit-2\">LIMIT</a></h3>\n<p>The LIMIT clause restricts the maximum number of rows deleted. When present, at\nmost <code>expr</code> rows are removed. A negative LIMIT value means no limit.</p>\n<pre><code class=\"language-sql\">-- Delete at most 2 rows from the table\nDELETE FROM employees LIMIT 2;\n</code></pre>\n<h3 id=\"with-common-table-expressions\"><a class=\"header\" href=\"#with-common-table-expressions\">WITH (Common Table Expressions)</a></h3>\n<p>A DELETE statement may be preceded by a WITH clause that defines one or more\ncommon table expressions. CTEs defined this way can be referenced in the WHERE\nclause or in subqueries within the statement.</p>\n<pre><code class=\"language-sql\">WITH low_earners AS (\n  SELECT id FROM employees WHERE salary &lt; 90000\n)\nDELETE FROM employees WHERE id IN (SELECT id FROM low_earners);\n</code></pre>\n<p>For more on CTEs, see <a href=\"#common-table-expressions\">Common Table Expressions</a>.</p>\n<h2 id=\"examples-10\"><a class=\"header\" href=\"#examples-10\">Examples</a></h2>\n<pre><code class=\"language-sql\">-- Create and populate a sample table\nCREATE TABLE employees (\n  id INTEGER PRIMARY KEY,\n  name TEXT NOT NULL,\n  department TEXT,\n  salary REAL\n);\n\nINSERT INTO employees VALUES\n  (1, 'Alice', 'Engineering', 120000.00),\n  (2, 'Bob', 'Marketing', 85000.00),\n  (3, 'Carol', 'Engineering', 110000.00),\n  (4, 'Dave', 'Sales', 90000.00),\n  (5, 'Eve', 'Marketing', 78000.00);\n</code></pre>\n<pre><code class=\"language-sql\">-- Delete a single row by primary key\nDELETE FROM employees WHERE id = 4;\nSELECT * FROM employees;\n-- 1|Alice|Engineering|120000.0\n-- 2|Bob|Marketing|85000.0\n-- 3|Carol|Engineering|110000.0\n-- 5|Eve|Marketing|78000.0\n</code></pre>\n<pre><code class=\"language-sql\">-- Delete all rows matching a condition\nDELETE FROM employees WHERE department = 'Marketing';\nSELECT * FROM employees;\n-- 1|Alice|Engineering|120000.0\n-- 3|Carol|Engineering|110000.0\n-- 4|Dave|Sales|90000.0\n</code></pre>\n<pre><code class=\"language-sql\">-- Delete all rows from a table (table itself remains)\nDELETE FROM employees;\nSELECT * FROM employees;\n-- (no rows returned)\n</code></pre>\n<pre><code class=\"language-sql\">-- Delete with RETURNING to see which rows were removed\nCREATE TABLE products (\n  id INTEGER PRIMARY KEY,\n  name TEXT,\n  price REAL,\n  quantity INTEGER\n);\n\nINSERT INTO products VALUES\n  (1, 'Widget', 9.99, 100),\n  (2, 'Gadget', 24.99, 0),\n  (3, 'Doohickey', 4.99, 0),\n  (4, 'Thingamajig', 14.99, 50);\n\nDELETE FROM products WHERE quantity = 0 RETURNING id, name;\n-- 2|Gadget\n-- 3|Doohickey\n</code></pre>\n<pre><code class=\"language-sql\">-- RETURNING with a computed expression\nDELETE FROM products WHERE quantity = 0\n  RETURNING id, name, price * quantity AS lost_value;\n-- 2|Gadget|0.0\n-- 3|Doohickey|0.0\n</code></pre>\n<pre><code class=\"language-sql\">-- RETURNING * returns all columns of each deleted row\nDELETE FROM products WHERE price &gt; 100.00 RETURNING *;\n</code></pre>\n<pre><code class=\"language-sql\">-- Delete using a subquery in WHERE\nCREATE TABLE employees (\n  id INTEGER PRIMARY KEY,\n  name TEXT NOT NULL,\n  department TEXT,\n  salary REAL\n);\n\nINSERT INTO employees VALUES\n  (1, 'Alice', 'Engineering', 120000.00),\n  (2, 'Bob', 'Marketing', 85000.00),\n  (3, 'Carol', 'Engineering', 110000.00);\n\nDELETE FROM employees\n  WHERE id IN (SELECT id FROM employees WHERE department = 'Engineering');\nSELECT * FROM employees;\n-- 2|Bob|Marketing|85000.0\n</code></pre>\n<pre><code class=\"language-sql\">-- Delete with a CTE\nCREATE TABLE employees (\n  id INTEGER PRIMARY KEY,\n  name TEXT NOT NULL,\n  department TEXT,\n  salary REAL\n);\n\nINSERT INTO employees VALUES\n  (1, 'Alice', 'Engineering', 120000.00),\n  (2, 'Bob', 'Marketing', 85000.00),\n  (3, 'Carol', 'Engineering', 110000.00),\n  (4, 'Dave', 'Sales', 90000.00),\n  (5, 'Eve', 'Marketing', 78000.00);\n\nWITH low_earners AS (\n  SELECT id FROM employees WHERE salary &lt; 90000\n)\nDELETE FROM employees WHERE id IN (SELECT id FROM low_earners);\nSELECT * FROM employees;\n-- 1|Alice|Engineering|120000.0\n-- 3|Carol|Engineering|110000.0\n-- 4|Dave|Sales|90000.0\n</code></pre>\n<pre><code class=\"language-sql\">-- Delete with LIMIT: remove at most 2 rows\nCREATE TABLE employees (\n  id INTEGER PRIMARY KEY,\n  name TEXT NOT NULL,\n  department TEXT,\n  salary REAL\n);\n\nINSERT INTO employees VALUES\n  (1, 'Alice', 'Engineering', 120000.00),\n  (2, 'Bob', 'Marketing', 85000.00),\n  (3, 'Carol', 'Engineering', 110000.00),\n  (4, 'Dave', 'Sales', 90000.00),\n  (5, 'Eve', 'Marketing', 78000.00);\n\nDELETE FROM employees LIMIT 2;\nSELECT * FROM employees;\n-- 3|Carol|Engineering|110000.0\n-- 4|Dave|Sales|90000.0\n-- 5|Eve|Marketing|78000.0\n</code></pre>\n<pre><code class=\"language-sql\">-- Delete with WHERE and LIMIT: remove one matching row\nCREATE TABLE employees (\n  id INTEGER PRIMARY KEY,\n  name TEXT NOT NULL,\n  department TEXT,\n  salary REAL\n);\n\nINSERT INTO employees VALUES\n  (1, 'Alice', 'Engineering', 120000.00),\n  (2, 'Bob', 'Marketing', 85000.00),\n  (3, 'Carol', 'Engineering', 110000.00);\n\nDELETE FROM employees WHERE department = 'Engineering' LIMIT 1;\nSELECT * FROM employees;\n-- 2|Bob|Marketing|85000.0\n-- 3|Carol|Engineering|110000.0\n</code></pre>\n<pre><code class=\"language-sql\">-- Use changes() to check how many rows were deleted\nCREATE TABLE employees (\n  id INTEGER PRIMARY KEY,\n  name TEXT NOT NULL,\n  department TEXT,\n  salary REAL\n);\n\nINSERT INTO employees VALUES\n  (1, 'Alice', 'Engineering', 120000.00),\n  (2, 'Bob', 'Marketing', 85000.00),\n  (3, 'Carol', 'Engineering', 110000.00),\n  (4, 'Dave', 'Sales', 90000.00),\n  (5, 'Eve', 'Marketing', 78000.00);\n\nDELETE FROM employees WHERE department = 'Marketing';\nSELECT changes();\n-- 2\n</code></pre>\n<h2 id=\"compatibility-8\"><a class=\"header\" href=\"#compatibility-8\">Compatibility</a></h2>\n<p>Turso supports the DELETE statement with broad compatibility to SQLite, including\nthe WHERE clause, RETURNING clause, LIMIT, and common table expressions.</p>\n<p>The following differences from SQLite apply:</p>\n<ul>\n<li>\n<p><strong>ORDER BY on DELETE</strong> is not supported. In SQLite, ORDER BY is available\n(when compiled with <code>SQLITE_ENABLE_UPDATE_DELETE_LIMIT</code>) and is used together\nwith LIMIT to control which specific rows are deleted. Turso supports LIMIT on\nDELETE but does not support ORDER BY in this context.</p>\n</li>\n<li>\n<p><strong>OFFSET on DELETE</strong> is parsed but not currently effective. While the syntax is\naccepted, the OFFSET value is ignored and all deletions start from the first\nmatching row. Use a subquery with LIMIT and OFFSET in a SELECT if you need to\nskip rows before deleting.</p>\n</li>\n<li>\n<p><strong>INDEXED BY / NOT INDEXED</strong> hints are not supported on DELETE statements.</p>\n</li>\n</ul>\n<div style=\"break-before: page; page-break-before: always;\"></div>\n<h1 id=\"returning-3\"><a class=\"header\" href=\"#returning-3\">RETURNING</a></h1>\n<h2 id=\"syntax-11\"><a class=\"header\" href=\"#syntax-11\">Syntax</a></h2>\n<pre><code class=\"language-sql\">{INSERT | UPDATE | DELETE} ...\n  RETURNING {expr [[AS] column-alias] | *} [, ...]\n</code></pre>\n<h2 id=\"description-11\"><a class=\"header\" href=\"#description-11\">Description</a></h2>\n<p>The <code>RETURNING</code> clause is an optional clause that can be appended to <code>INSERT</code>, <code>UPDATE</code>, and <code>DELETE</code> statements. It causes the statement to return one result row for each database row that is inserted, updated, or deleted. This eliminates the need for a separate <code>SELECT</code> query to retrieve values that were generated or modified by the statement.</p>\n<p>A common use case is retrieving auto-generated primary keys, computed default values, or confirming which rows were affected by an <code>UPDATE</code> or <code>DELETE</code>.</p>\n<p>The <code>RETURNING</code> clause is not part of the SQL standard. It follows the syntax established by PostgreSQL.</p>\n<h2 id=\"expressions\"><a class=\"header\" href=\"#expressions\">Expressions</a></h2>\n<p>The <code>RETURNING</code> keyword is followed by a comma-separated list of expressions, similar to the expressions that follow <code>SELECT</code> in a query. Each expression may reference columns of the table being modified, use literal values, call scalar functions, or combine these with operators.</p>\n<h3 id=\"column-references\"><a class=\"header\" href=\"#column-references\">Column References</a></h3>\n<p>Expressions in <code>RETURNING</code> can reference any column of the modified table, either unqualified or qualified with the table name:</p>\n<pre><code class=\"language-sql\">RETURNING id, name\nRETURNING orders.id, orders.name\n</code></pre>\n<p>For <code>INSERT</code> and <code>UPDATE</code>, column references reflect the values <strong>after</strong> the change has been applied. For <code>DELETE</code>, column references reflect the values of the row <strong>before</strong> it is removed.</p>\n<h3 id=\"the--operator\"><a class=\"header\" href=\"#the--operator\">The * Operator</a></h3>\n<p>The <code>*</code> expands into all columns of the table being modified:</p>\n<pre><code class=\"language-sql\">INSERT INTO orders (...) VALUES (...) RETURNING *;\n</code></pre>\n<h3 id=\"column-aliases-1\"><a class=\"header\" href=\"#column-aliases-1\">Column Aliases</a></h3>\n<p>Each expression may optionally be followed by <code>AS column-alias</code> (or just <code>column-alias</code> without <code>AS</code>) to set the name of the result column:</p>\n<pre><code class=\"language-sql\">RETURNING quantity * unit_price AS total\n</code></pre>\n<h3 id=\"allowed-expressions\"><a class=\"header\" href=\"#allowed-expressions\">Allowed Expressions</a></h3>\n<p>The following kinds of expressions are allowed in a <code>RETURNING</code> clause:</p>\n<ul>\n<li>Literal values (<code>42</code>, <code>'hello'</code>, <code>NULL</code>)</li>\n<li>Column references (<code>id</code>, <code>table_name.column_name</code>)</li>\n<li>Arithmetic and comparison operators (<code>price * 1.1</code>, <code>value &gt; 0</code>)</li>\n<li>String concatenation (<code>first || ' ' || last</code>)</li>\n<li>Scalar function calls (<code>upper(name)</code>, <code>round(price, 2)</code>, <code>coalesce(a, b)</code>)</li>\n<li><code>CASE</code> expressions</li>\n<li><code>CAST</code> expressions</li>\n<li><code>IN</code>, <code>BETWEEN</code>, <code>LIKE</code>, <code>GLOB</code>, <code>IS NULL</code>, <code>IS NOT NULL</code> operators</li>\n<li>The <code>rowid</code> pseudo-column</li>\n</ul>\n<h3 id=\"disallowed-expressions\"><a class=\"header\" href=\"#disallowed-expressions\">Disallowed Expressions</a></h3>\n<p>Top-level aggregate functions (<code>SUM</code>, <code>COUNT</code>, <code>AVG</code>, etc.) and window functions are not permitted in <code>RETURNING</code>. Using them produces an error:</p>\n<pre><code class=\"language-sql\">-- Error: aggregate functions not allowed in RETURNING\nINSERT INTO t VALUES (1, 42) RETURNING SUM(value);\n</code></pre>\n<h2 id=\"behavior\"><a class=\"header\" href=\"#behavior\">Behavior</a></h2>\n<h3 id=\"insert-returning\"><a class=\"header\" href=\"#insert-returning\">INSERT RETURNING</a></h3>\n<p>Returns one row for each inserted row. When inserting multiple rows, one result row is produced per input row. The returned values reflect the state after insertion, including auto-generated primary keys and evaluated <code>DEFAULT</code> expressions.</p>\n<h3 id=\"update-returning\"><a class=\"header\" href=\"#update-returning\">UPDATE RETURNING</a></h3>\n<p>Returns one row for each row that was actually modified by the <code>UPDATE</code>. Column values in the result reflect the new values after the update. If the <code>WHERE</code> clause matches no rows, no result rows are produced.</p>\n<h3 id=\"delete-returning\"><a class=\"header\" href=\"#delete-returning\">DELETE RETURNING</a></h3>\n<p>Returns one row for each deleted row. Column values in the result reflect the values the row had before deletion. If the <code>WHERE</code> clause matches no rows, no result rows are produced.</p>\n<h3 id=\"upsert-on-conflict-returning\"><a class=\"header\" href=\"#upsert-on-conflict-returning\">UPSERT (ON CONFLICT) RETURNING</a></h3>\n<p>When <code>RETURNING</code> is used with an <code>INSERT ... ON CONFLICT</code> statement, it returns rows for both the insert and update code paths. If the conflict resolution is <code>DO NOTHING</code> and a conflict occurs, no row is returned for that conflicting input row.</p>\n<h3 id=\"output-order\"><a class=\"header\" href=\"#output-order\">Output Order</a></h3>\n<p>The order of rows returned by <code>RETURNING</code> is not guaranteed. It typically matches the order in which rows were processed, but applications should not rely on any particular ordering.</p>\n<h3 id=\"triggers\"><a class=\"header\" href=\"#triggers\">Triggers</a></h3>\n<p>The <code>RETURNING</code> clause reports the direct changes made by the statement. It does not report additional changes caused by triggers or foreign key constraint actions.</p>\n<h2 id=\"examples-11\"><a class=\"header\" href=\"#examples-11\">Examples</a></h2>\n<pre><code class=\"language-sql\">-- Retrieve the auto-generated id after inserting a row\nCREATE TABLE orders (\n  id INTEGER PRIMARY KEY,\n  customer TEXT NOT NULL,\n  product TEXT NOT NULL,\n  quantity INTEGER NOT NULL,\n  unit_price REAL NOT NULL\n);\n\nINSERT INTO orders (customer, product, quantity, unit_price)\n  VALUES ('Alice', 'Widget', 5, 9.99)\n  RETURNING id;\n-- 1\n</code></pre>\n<pre><code class=\"language-sql\">-- Return all columns of newly inserted rows\nINSERT INTO orders (customer, product, quantity, unit_price)\n  VALUES ('Alice', 'Widget', 5, 9.99)\n  RETURNING *;\n-- 1|Alice|Widget|5|9.99\n</code></pre>\n<pre><code class=\"language-sql\">-- Return a computed expression with an alias\nINSERT INTO orders (customer, product, quantity, unit_price)\n  VALUES ('Alice', 'Widget', 5, 9.99)\n  RETURNING id, customer, quantity * unit_price AS total;\n-- 1|Alice|49.95\n</code></pre>\n<pre><code class=\"language-sql\">-- Return multiple rows from a multi-row INSERT\nCREATE TABLE users (\n  id INTEGER PRIMARY KEY,\n  name TEXT NOT NULL,\n  email TEXT NOT NULL,\n  active INTEGER DEFAULT 1\n);\n\nINSERT INTO users (name, email)\n  VALUES ('Alice', 'alice@example.com'),\n         ('Bob', 'bob@example.com'),\n         ('Charlie', 'charlie@example.com')\n  RETURNING id, name;\n-- 1|Alice\n-- 2|Bob\n-- 3|Charlie\n</code></pre>\n<pre><code class=\"language-sql\">-- Retrieve auto-filled DEFAULT values\nCREATE TABLE events (\n  id INTEGER PRIMARY KEY,\n  name TEXT NOT NULL,\n  created_at TEXT DEFAULT (datetime('now'))\n);\n\nINSERT INTO events (name) VALUES ('signup')\n  RETURNING id, name, created_at;\n-- 1|signup|2026-02-11 18:52:48\n</code></pre>\n<pre><code class=\"language-sql\">-- See which rows were updated and their new values\nCREATE TABLE products (\n  id INTEGER PRIMARY KEY,\n  name TEXT NOT NULL,\n  price REAL NOT NULL\n);\n\nINSERT INTO products (name, price)\n  VALUES ('Laptop', 999.99), ('Mouse', 29.99), ('Keyboard', 79.99);\n\nUPDATE products SET price = price * 0.9\n  RETURNING id, name, round(price, 2) AS discounted_price;\n-- 1|Laptop|899.99\n-- 2|Mouse|26.99\n-- 3|Keyboard|71.99\n</code></pre>\n<pre><code class=\"language-sql\">-- Use a CASE expression in RETURNING\nCREATE TABLE tasks (\n  id INTEGER PRIMARY KEY,\n  title TEXT NOT NULL,\n  done INTEGER DEFAULT 0\n);\n\nINSERT INTO tasks (title)\n  VALUES ('Write report'), ('Fix bug'), ('Review PR');\n\nUPDATE tasks SET done = 1 WHERE id = 2\n  RETURNING id, title,\n    CASE WHEN done THEN 'completed' ELSE 'pending' END AS status;\n-- 2|Fix bug|completed\n</code></pre>\n<pre><code class=\"language-sql\">-- Confirm which rows were deleted\nCREATE TABLE users (\n  id INTEGER PRIMARY KEY,\n  name TEXT NOT NULL,\n  email TEXT NOT NULL,\n  active INTEGER DEFAULT 1\n);\n\nINSERT INTO users (name, email)\n  VALUES ('Alice', 'alice@example.com'),\n         ('Bob', 'bob@example.com'),\n         ('Charlie', 'charlie@example.com');\n\nDELETE FROM users WHERE active = 1\n  RETURNING id, name, email;\n-- 1|Alice|alice@example.com\n-- 2|Bob|bob@example.com\n-- 3|Charlie|charlie@example.com\n</code></pre>\n<pre><code class=\"language-sql\">-- Use function expressions in DELETE RETURNING\nCREATE TABLE logs (\n  id INTEGER PRIMARY KEY,\n  message TEXT NOT NULL,\n  level TEXT NOT NULL\n);\n\nINSERT INTO logs (message, level)\n  VALUES ('User login', 'info'), ('Disk full', 'error'), ('Timeout', 'warn');\n\nDELETE FROM logs WHERE level = 'error'\n  RETURNING id, message, upper(level) AS level;\n-- 2|Disk full|ERROR\n</code></pre>\n<pre><code class=\"language-sql\">-- RETURNING with UPSERT: insert path\nCREATE TABLE settings (key TEXT PRIMARY KEY, value TEXT NOT NULL);\n\nINSERT INTO settings (key, value) VALUES ('theme', 'dark')\n  ON CONFLICT(key) DO UPDATE SET value = excluded.value\n  RETURNING key, value;\n-- theme|dark\n</code></pre>\n<pre><code class=\"language-sql\">-- RETURNING with UPSERT: update path (conflict triggers DO UPDATE)\nCREATE TABLE settings (key TEXT PRIMARY KEY, value TEXT NOT NULL);\nINSERT INTO settings (key, value) VALUES ('theme', 'dark');\n\nINSERT INTO settings (key, value) VALUES ('theme', 'light')\n  ON CONFLICT(key) DO UPDATE SET value = excluded.value\n  RETURNING key, value;\n-- theme|light\n</code></pre>\n<pre><code class=\"language-sql\">-- RETURNING with UPSERT: DO NOTHING returns no rows on conflict\nCREATE TABLE settings (key TEXT PRIMARY KEY, value TEXT NOT NULL);\nINSERT INTO settings (key, value) VALUES ('theme', 'dark');\n\nINSERT INTO settings (key, value) VALUES ('theme', 'light')\n  ON CONFLICT DO NOTHING\n  RETURNING key, value;\n-- (no rows returned)\n</code></pre>\n<pre><code class=\"language-sql\">-- RETURNING with INSERT ... SELECT\nCREATE TABLE source (id INTEGER, name TEXT, score INTEGER);\nCREATE TABLE archive (name TEXT, score INTEGER);\n\nINSERT INTO source VALUES (1, 'Alice', 95), (2, 'Bob', 82), (3, 'Charlie', 78);\n\nINSERT INTO archive SELECT name, score FROM source WHERE score &gt;= 80\n  RETURNING *;\n-- Alice|95\n-- Bob|82\n</code></pre>\n<div style=\"break-before: page; page-break-before: always;\"></div>\n<h1 id=\"create-table\"><a class=\"header\" href=\"#create-table\">CREATE TABLE</a></h1>\n<h2 id=\"syntax-12\"><a class=\"header\" href=\"#syntax-12\">Syntax</a></h2>\n<pre><code class=\"language-sql\">CREATE TABLE [IF NOT EXISTS] table-name (\n  column-def [, ...]\n  [, table-constraint [, ...]]\n) [STRICT]\n</code></pre>\n<p>Where <code>column-def</code> is:</p>\n<pre><code class=\"language-sql\">column-name [type-name] [column-constraint ...]\n</code></pre>\n<p>And <code>column-constraint</code> is one of:</p>\n<pre><code class=\"language-sql\">PRIMARY KEY [ASC | DESC] [AUTOINCREMENT] [conflict-clause]\nNOT NULL [conflict-clause]\nUNIQUE [conflict-clause]\nCHECK (expr)\nDEFAULT {value | (expr)}\nCOLLATE {BINARY | NOCASE | RTRIM}\nREFERENCES foreign-table (foreign-column [, ...]) [foreign-key-action ...]\n[CONSTRAINT constraint-name] column-constraint\n</code></pre>\n<p>And <code>table-constraint</code> is one of:</p>\n<pre><code class=\"language-sql\">PRIMARY KEY (column-name [, ...]) [conflict-clause]\nUNIQUE (column-name [, ...]) [conflict-clause]\nCHECK (expr)\nFOREIGN KEY (column-name [, ...]) REFERENCES foreign-table (foreign-column [, ...]) [foreign-key-action ...]\n</code></pre>\n<p>And <code>conflict-clause</code> is:</p>\n<pre><code class=\"language-sql\">ON CONFLICT {ROLLBACK | ABORT | FAIL | IGNORE | REPLACE}\n</code></pre>\n<h2 id=\"description-12\"><a class=\"header\" href=\"#description-12\">Description</a></h2>\n<p>The <code>CREATE TABLE</code> statement creates a new table in the database. Each table has a name, a list of column definitions, and optional table-level constraints. Table names beginning with <code>sqlite_</code> are reserved for internal use and cannot be created by user SQL.</p>\n<p>Every ordinary table in Turso has an implicit 64-bit signed integer key called the <code>rowid</code>. The rowid uniquely identifies each row within the table and provides fast lookup. You can access it using the names <code>rowid</code>, <code>_rowid_</code>, or <code>oid</code>, unless one of those names is used as an explicit column name.</p>\n<h2 id=\"clauses-9\"><a class=\"header\" href=\"#clauses-9\">Clauses</a></h2>\n<h3 id=\"if-not-exists\"><a class=\"header\" href=\"#if-not-exists\">IF NOT EXISTS</a></h3>\n<p>When <code>IF NOT EXISTS</code> is specified, the statement is a no-op if a table with the same name already exists. Without this clause, attempting to create a table with an existing name produces an error.</p>\n<pre><code class=\"language-sql\">CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT);\n</code></pre>\n<h3 id=\"column-definitions\"><a class=\"header\" href=\"#column-definitions\">Column Definitions</a></h3>\n<p>Each column definition specifies a column name and an optional declared type. The declared type determines the column’s <strong>type affinity</strong>, which influences how values are coerced on insertion. See <a href=\"#type-conversions\">Type Conversions</a> for the full affinity rules.</p>\n<p>Turso uses dynamic typing. A column’s declared type does not restrict what values can be stored in it (unless the table uses <code>STRICT</code> mode). Any column can hold any storage class: NULL, INTEGER, REAL, TEXT, or BLOB.</p>\n<h3 id=\"default\"><a class=\"header\" href=\"#default\">DEFAULT</a></h3>\n<p>The <code>DEFAULT</code> clause specifies a value to use when an <code>INSERT</code> statement omits a column. If no <code>DEFAULT</code> clause is specified, the default value is <code>NULL</code>.</p>\n<p>The default value can be:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Form</th><th>Description</th></tr>\n</thead>\n<tbody>\n<tr><td><code>NULL</code></td><td>Null value.</td></tr>\n<tr><td>Signed number</td><td>A literal integer or real number, optionally with a <code>+</code> or <code>-</code> prefix.</td></tr>\n<tr><td>String literal</td><td>A single-quoted string.</td></tr>\n<tr><td><code>TRUE</code> / <code>FALSE</code></td><td>Boolean literals (stored as 1 and 0).</td></tr>\n<tr><td><code>CURRENT_TIME</code></td><td>Current time as <code>HH:MM:SS</code>.</td></tr>\n<tr><td><code>CURRENT_DATE</code></td><td>Current date as <code>YYYY-MM-DD</code>.</td></tr>\n<tr><td><code>CURRENT_TIMESTAMP</code></td><td>Current date and time as <code>YYYY-MM-DD HH:MM:SS</code>.</td></tr>\n<tr><td><code>(expr)</code></td><td>An expression in parentheses, evaluated at insert time.</td></tr>\n</tbody>\n</table>\n</div>\n<pre><code class=\"language-sql\">CREATE TABLE products (\n  id INTEGER PRIMARY KEY,\n  name TEXT NOT NULL DEFAULT 'Unknown',\n  price REAL DEFAULT 0.0,\n  in_stock INTEGER DEFAULT TRUE,\n  created_at TEXT DEFAULT CURRENT_TIMESTAMP\n);\n</code></pre>\n<h3 id=\"primary-key\"><a class=\"header\" href=\"#primary-key\">PRIMARY KEY</a></h3>\n<p>Each table may have at most one primary key. A primary key can be declared as a column constraint (single-column) or as a table constraint (composite).</p>\n<p><strong>Single-column primary key:</strong></p>\n<pre><code class=\"language-sql\">CREATE TABLE users (\n  id INTEGER PRIMARY KEY,\n  name TEXT NOT NULL\n);\n</code></pre>\n<p><strong>Composite primary key (table constraint):</strong></p>\n<pre><code class=\"language-sql\">CREATE TABLE enrollment (\n  student_id INTEGER,\n  course_id INTEGER,\n  grade TEXT,\n  PRIMARY KEY (student_id, course_id)\n);\n</code></pre>\n<p>Attempting to insert a duplicate primary key value produces a constraint violation error.</p>\n<h4 id=\"integer-primary-key-and-the-rowid\"><a class=\"header\" href=\"#integer-primary-key-and-the-rowid\">INTEGER PRIMARY KEY and the Rowid</a></h4>\n<p>When a single column is declared with the exact type name <code>INTEGER</code> and is the <code>PRIMARY KEY</code>, that column becomes an alias for the rowid. This is a special case:</p>\n<ul>\n<li>The type name must be exactly <code>INTEGER</code> – not <code>INT</code>, <code>BIGINT</code>, <code>SMALLINT</code>, or any other variation.</li>\n<li>The column can only contain integer values (or NULL, which is auto-assigned a rowid).</li>\n<li>Inserting <code>NULL</code> into an <code>INTEGER PRIMARY KEY</code> column automatically assigns the next available rowid.</li>\n</ul>\n<pre><code class=\"language-sql\">CREATE TABLE events (\n  id INTEGER PRIMARY KEY,\n  description TEXT\n);\nINSERT INTO events (description) VALUES ('Server started');\n-- id is automatically assigned (e.g., 1)\nINSERT INTO events (id, description) VALUES (NULL, 'User login');\n-- id is automatically assigned (e.g., 2)\n</code></pre>\n<h3 id=\"autoincrement\"><a class=\"header\" href=\"#autoincrement\">AUTOINCREMENT</a></h3>\n<p>The <code>AUTOINCREMENT</code> keyword can only be used with <code>INTEGER PRIMARY KEY</code>. It modifies the automatic rowid assignment to guarantee that automatically-assigned rowids are never reused, even after rows are deleted.</p>\n<p>Without <code>AUTOINCREMENT</code>, Turso reuses deleted rowid values. With <code>AUTOINCREMENT</code>, Turso tracks the largest rowid ever inserted in the internal <code>sqlite_sequence</code> table, and new auto-assigned rowids are always greater than any previously used value.</p>\n<pre><code class=\"language-sql\">CREATE TABLE audit_log (\n  id INTEGER PRIMARY KEY AUTOINCREMENT,\n  action TEXT NOT NULL\n);\nINSERT INTO audit_log (action) VALUES ('create');\nINSERT INTO audit_log (action) VALUES ('update');\nDELETE FROM audit_log WHERE id = 2;\nINSERT INTO audit_log (action) VALUES ('delete');\n-- The new row gets id=3, not id=2\n</code></pre>\n<p><code>AUTOINCREMENT</code> has a small performance cost because it requires reading and writing the <code>sqlite_sequence</code> table on each insert. Use it only when strictly monotonically increasing rowids are required.</p>\n<p>If the maximum rowid value (9223372036854775807) has been used, inserting a new row with <code>AUTOINCREMENT</code> produces an error rather than attempting to find an unused rowid.</p>\n<h3 id=\"not-null\"><a class=\"header\" href=\"#not-null\">NOT NULL</a></h3>\n<p>The <code>NOT NULL</code> constraint prevents a column from containing NULL values. Attempting to insert or update a NULL into a <code>NOT NULL</code> column produces a constraint violation error.</p>\n<pre><code class=\"language-sql\">CREATE TABLE contacts (\n  id INTEGER PRIMARY KEY,\n  name TEXT NOT NULL,\n  email TEXT NOT NULL,\n  phone TEXT\n);\n</code></pre>\n<h3 id=\"unique\"><a class=\"header\" href=\"#unique\">UNIQUE</a></h3>\n<p>The <code>UNIQUE</code> constraint ensures that all values in a column (or combination of columns) are distinct. Multiple <code>UNIQUE</code> constraints can appear on the same table.</p>\n<pre><code class=\"language-sql\">CREATE TABLE accounts (\n  id INTEGER PRIMARY KEY,\n  username TEXT UNIQUE,\n  email TEXT UNIQUE\n);\n</code></pre>\n<p>A <code>UNIQUE</code> table constraint can span multiple columns:</p>\n<pre><code class=\"language-sql\">CREATE TABLE assignments (\n  employee_id INTEGER,\n  project_id INTEGER,\n  role TEXT,\n  UNIQUE (employee_id, project_id)\n);\n</code></pre>\n<p>Note: NULL values are considered distinct from each other for <code>UNIQUE</code> constraint purposes. Multiple rows may have NULL in a <code>UNIQUE</code> column without violating the constraint.</p>\n<h3 id=\"check\"><a class=\"header\" href=\"#check\">CHECK</a></h3>\n<p>The <code>CHECK</code> constraint specifies an expression that must evaluate to a non-zero (true) value for every row. If the expression evaluates to zero, the insert or update is rejected. NULL values pass <code>CHECK</code> constraints (NULL is neither true nor false).</p>\n<p><code>CHECK</code> can be used as a column constraint or a table constraint:</p>\n<pre><code class=\"language-sql\">CREATE TABLE employees (\n  id INTEGER PRIMARY KEY,\n  name TEXT NOT NULL,\n  age INTEGER CHECK (age &gt;= 18),\n  salary INTEGER CHECK (salary &gt; 0),\n  CHECK (age &lt;= 120)\n);\n</code></pre>\n<p><code>CHECK</code> expressions may reference multiple columns, use functions, and include operators like <code>IN</code>, <code>BETWEEN</code>, <code>LIKE</code>, and <code>CASE</code>:</p>\n<pre><code class=\"language-sql\">CREATE TABLE orders (\n  id INTEGER PRIMARY KEY,\n  status TEXT CHECK (status IN ('pending', 'active', 'shipped', 'delivered')),\n  quantity INTEGER,\n  unit_price INTEGER,\n  total INTEGER,\n  CHECK (total = quantity * unit_price)\n);\n</code></pre>\n<p><code>CHECK</code> expressions cannot contain subqueries, aggregate functions, or bind parameters. Referencing a non-existent column in a <code>CHECK</code> expression produces an error at table creation time.</p>\n<h3 id=\"collate\"><a class=\"header\" href=\"#collate\">COLLATE</a></h3>\n<p>The <code>COLLATE</code> clause on a column definition sets the default collation sequence for that column. This affects comparisons, sorting, and <code>UNIQUE</code>/<code>PRIMARY KEY</code> constraints.</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Collation</th><th>Behavior</th></tr>\n</thead>\n<tbody>\n<tr><td><code>BINARY</code></td><td>Compares bytes directly (default).</td></tr>\n<tr><td><code>NOCASE</code></td><td>Case-insensitive comparison for ASCII characters.</td></tr>\n<tr><td><code>RTRIM</code></td><td>Like <code>BINARY</code>, but trailing spaces are ignored.</td></tr>\n</tbody>\n</table>\n</div>\n<pre><code class=\"language-sql\">CREATE TABLE tags (\n  id INTEGER PRIMARY KEY,\n  name TEXT COLLATE NOCASE UNIQUE\n);\n</code></pre>\n<h3 id=\"foreign-key\"><a class=\"header\" href=\"#foreign-key\">FOREIGN KEY</a></h3>\n<p>Foreign key constraints enforce referential integrity between tables. A foreign key in a child table references a column (or columns) in a parent table, ensuring that every value in the child column exists in the parent column.</p>\n<p>Foreign key enforcement must be enabled with <code>PRAGMA foreign_keys = ON</code> (it is off by default).</p>\n<p><strong>Column-level syntax:</strong></p>\n<pre><code class=\"language-sql\">CREATE TABLE orders (\n  id INTEGER PRIMARY KEY,\n  customer_id INTEGER REFERENCES customers (id)\n);\n</code></pre>\n<p><strong>Table-level syntax:</strong></p>\n<pre><code class=\"language-sql\">CREATE TABLE order_items (\n  id INTEGER PRIMARY KEY,\n  order_id INTEGER,\n  product_id INTEGER,\n  FOREIGN KEY (order_id) REFERENCES orders (id),\n  FOREIGN KEY (product_id) REFERENCES products (id)\n);\n</code></pre>\n<p><strong>Foreign key actions</strong> specify what happens when the referenced row in the parent table is deleted or updated:</p>\n<pre><code class=\"language-sql\">FOREIGN KEY (column) REFERENCES parent (column)\n  [ON DELETE {SET NULL | CASCADE | RESTRICT | NO ACTION}]\n  [ON UPDATE {SET NULL | CASCADE | RESTRICT | NO ACTION}]\n</code></pre>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Action</th><th>Behavior</th></tr>\n</thead>\n<tbody>\n<tr><td><code>NO ACTION</code></td><td>Reject the change if child rows exist (default).</td></tr>\n<tr><td><code>RESTRICT</code></td><td>Same as <code>NO ACTION</code>, but checked immediately rather than deferred.</td></tr>\n<tr><td><code>CASCADE</code></td><td>Delete or update the child rows to match the parent change.</td></tr>\n<tr><td><code>SET NULL</code></td><td>Set the child foreign key column(s) to NULL.</td></tr>\n</tbody>\n</table>\n</div>\n<pre><code class=\"language-sql\">PRAGMA foreign_keys = ON;\nCREATE TABLE departments (id INTEGER PRIMARY KEY, name TEXT);\nCREATE TABLE staff (\n  id INTEGER PRIMARY KEY,\n  dept_id INTEGER REFERENCES departments (id) ON DELETE CASCADE\n);\n</code></pre>\n<h3 id=\"conflict-clauses\"><a class=\"header\" href=\"#conflict-clauses\">Conflict Clauses</a></h3>\n<p>The <code>PRIMARY KEY</code>, <code>NOT NULL</code>, and <code>UNIQUE</code> constraints accept an optional <code>ON CONFLICT</code> clause that specifies how constraint violations are handled:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Algorithm</th><th>Behavior</th></tr>\n</thead>\n<tbody>\n<tr><td><code>ABORT</code></td><td>Abort the current statement and roll back its changes (default).</td></tr>\n<tr><td><code>ROLLBACK</code></td><td>Abort the current statement and roll back the entire transaction.</td></tr>\n<tr><td><code>FAIL</code></td><td>Abort the current statement but keep changes from earlier rows.</td></tr>\n<tr><td><code>IGNORE</code></td><td>Skip the row that caused the violation and continue.</td></tr>\n<tr><td><code>REPLACE</code></td><td>For <code>UNIQUE</code>/<code>PRIMARY KEY</code>: delete the conflicting row, then insert. For <code>NOT NULL</code>: replace the NULL with the column’s default value.</td></tr>\n</tbody>\n</table>\n</div>\n<pre><code class=\"language-sql\">CREATE TABLE settings (\n  key TEXT PRIMARY KEY ON CONFLICT REPLACE,\n  value TEXT NOT NULL\n);\n</code></pre>\n<p>Note: <code>CHECK</code> constraints do not accept an <code>ON CONFLICT</code> clause in the column definition. The conflict resolution for <code>CHECK</code> violations is always <code>ABORT</code> by default, but can be overridden per-statement using <code>INSERT OR IGNORE</code>, <code>INSERT OR REPLACE</code>, etc.</p>\n<h3 id=\"strict\"><a class=\"header\" href=\"#strict\">STRICT</a></h3>\n<p>The <code>STRICT</code> keyword at the end of the column list enables strict type checking. In a <code>STRICT</code> table, every column must have a declared type, and the type must be one of: <code>INTEGER</code>, <code>REAL</code>, <code>TEXT</code>, <code>BLOB</code>, or <code>ANY</code>.</p>\n<p>When inserting or updating values, Turso rejects values that do not match the declared type (with some coercion: numeric strings are accepted for <code>INTEGER</code> and <code>REAL</code> columns). Columns without a <code>NOT NULL</code> constraint still accept <code>NULL</code> values.</p>\n<pre><code class=\"language-sql\">CREATE TABLE measurements (\n  id INTEGER PRIMARY KEY,\n  sensor_name TEXT,\n  value REAL,\n  recorded_at TEXT\n) STRICT;\n</code></pre>\n<h2 id=\"examples-12\"><a class=\"header\" href=\"#examples-12\">Examples</a></h2>\n<h3 id=\"basic-table-with-constraints\"><a class=\"header\" href=\"#basic-table-with-constraints\">Basic Table with Constraints</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE users (\n  id INTEGER PRIMARY KEY,\n  username TEXT NOT NULL UNIQUE,\n  email TEXT NOT NULL UNIQUE,\n  display_name TEXT DEFAULT 'Anonymous',\n  age INTEGER CHECK (age &gt;= 13)\n);\n\nINSERT INTO users (id, username, email, age)\nVALUES (1, 'alice', 'alice@example.com', 30);\nSELECT * FROM users;\n-- 1|alice|alice@example.com|Anonymous|30\n</code></pre>\n<h3 id=\"composite-primary-key\"><a class=\"header\" href=\"#composite-primary-key\">Composite Primary Key</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE enrollment (\n  student_id INTEGER,\n  course_id INTEGER,\n  enrolled_at TEXT DEFAULT CURRENT_TIMESTAMP,\n  PRIMARY KEY (student_id, course_id)\n);\n\nINSERT INTO enrollment (student_id, course_id) VALUES (1, 101);\nINSERT INTO enrollment (student_id, course_id) VALUES (1, 102);\nINSERT INTO enrollment (student_id, course_id) VALUES (2, 101);\n\nSELECT student_id, course_id FROM enrollment ORDER BY student_id, course_id;\n-- 1|101\n-- 1|102\n-- 2|101\n</code></pre>\n<h3 id=\"autoincrement-1\"><a class=\"header\" href=\"#autoincrement-1\">AUTOINCREMENT</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE audit_log (\n  id INTEGER PRIMARY KEY AUTOINCREMENT,\n  action TEXT NOT NULL\n);\n\nINSERT INTO audit_log (action) VALUES ('user.login');\nINSERT INTO audit_log (action) VALUES ('user.logout');\nSELECT * FROM audit_log;\n-- 1|user.login\n-- 2|user.logout\n</code></pre>\n<h3 id=\"check-constraints-with-functions\"><a class=\"header\" href=\"#check-constraints-with-functions\">CHECK Constraints with Functions</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE accounts (\n  id INTEGER PRIMARY KEY,\n  username TEXT CHECK (length(username) &gt;= 3 AND length(username) &lt;= 20),\n  email TEXT CHECK (email LIKE '%@%.%')\n);\n\nINSERT INTO accounts VALUES (1, 'alice', 'alice@example.com');\nSELECT * FROM accounts;\n-- 1|alice|alice@example.com\n</code></pre>\n<h3 id=\"foreign-keys-with-cascade\"><a class=\"header\" href=\"#foreign-keys-with-cascade\">Foreign Keys with CASCADE</a></h3>\n<pre><code class=\"language-sql\">PRAGMA foreign_keys = ON;\n\nCREATE TABLE departments (\n  id INTEGER PRIMARY KEY,\n  name TEXT NOT NULL\n);\n\nCREATE TABLE employees (\n  id INTEGER PRIMARY KEY,\n  name TEXT NOT NULL,\n  dept_id INTEGER REFERENCES departments (id) ON DELETE CASCADE\n);\n\nINSERT INTO departments VALUES (1, 'Engineering'), (2, 'Sales');\nINSERT INTO employees VALUES (1, 'Alice', 1), (2, 'Bob', 1), (3, 'Carol', 2);\n\nDELETE FROM departments WHERE id = 1;\nSELECT * FROM employees;\n-- 3|Carol|2\n</code></pre>\n<h3 id=\"strict-table\"><a class=\"header\" href=\"#strict-table\">STRICT Table</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE inventory (\n  id INTEGER PRIMARY KEY,\n  item_name TEXT,\n  quantity INTEGER\n) STRICT;\n\nINSERT INTO inventory VALUES (1, 'Bolts', 500);\nSELECT * FROM inventory;\n-- 1|Bolts|500\n</code></pre>\n<h3 id=\"default-values-with-expressions\"><a class=\"header\" href=\"#default-values-with-expressions\">Default Values with Expressions</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE tasks (\n  id INTEGER PRIMARY KEY,\n  title TEXT NOT NULL,\n  status TEXT DEFAULT 'pending' CHECK (status IN ('pending', 'active', 'done')),\n  priority INTEGER DEFAULT (1 + 1)\n);\n\nINSERT INTO tasks (id, title) VALUES (1, 'Review pull request');\nSELECT * FROM tasks;\n-- 1|Review pull request|pending|2\n</code></pre>\n<h3 id=\"if-not-exists-1\"><a class=\"header\" href=\"#if-not-exists-1\">IF NOT EXISTS</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE config (key TEXT PRIMARY KEY, value TEXT);\nCREATE TABLE IF NOT EXISTS config (key TEXT PRIMARY KEY, value TEXT);\n-- No error on the second statement\n</code></pre>\n<h3 id=\"conflict-resolution\"><a class=\"header\" href=\"#conflict-resolution\">Conflict Resolution</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE kv_store (\n  key TEXT PRIMARY KEY ON CONFLICT REPLACE,\n  value TEXT NOT NULL\n);\n\nINSERT INTO kv_store VALUES ('theme', 'light');\nINSERT INTO kv_store VALUES ('theme', 'dark');\n\nSELECT * FROM kv_store;\n-- dark\n</code></pre>\n<h2 id=\"compatibility-9\"><a class=\"header\" href=\"#compatibility-9\">Compatibility</a></h2>\n<ul>\n<li><strong>CREATE TABLE AS SELECT</strong> is not supported. Use <code>CREATE TABLE</code> followed by <code>INSERT ... SELECT</code> instead.</li>\n<li><strong>TEMPORARY tables</strong> (<code>CREATE TEMP TABLE</code>) are not supported.</li>\n<li><strong>WITHOUT ROWID tables</strong> are not supported.</li>\n<li><strong>STRICT tables</strong> are experimental and partially supported.</li>\n<li><strong>SET DEFAULT</strong> as a foreign key action is not supported; use <code>SET NULL</code> or <code>CASCADE</code> instead.</li>\n</ul>\n<div style=\"break-before: page; page-break-before: always;\"></div>\n<h1 id=\"create-index\"><a class=\"header\" href=\"#create-index\">CREATE INDEX</a></h1>\n<h2 id=\"syntax-13\"><a class=\"header\" href=\"#syntax-13\">Syntax</a></h2>\n<pre><code class=\"language-sql\">CREATE [UNIQUE] INDEX [IF NOT EXISTS] index-name\n  ON table-name (indexed-column [, ...])\n  [WHERE expr]\n</code></pre>\n<p>Where <code>indexed-column</code> is:</p>\n<pre><code class=\"language-sql\">{column-name | (expr)} [COLLATE collation-name] [ASC | DESC]\n</code></pre>\n<p>To remove an index:</p>\n<pre><code class=\"language-sql\">DROP INDEX [IF EXISTS] index-name\n</code></pre>\n<h2 id=\"description-13\"><a class=\"header\" href=\"#description-13\">Description</a></h2>\n<p>The <code>CREATE INDEX</code> statement creates a new index on one or more columns of an existing table. Indexes speed up queries that filter, sort, or join on the indexed columns, at the cost of additional storage and slightly slower writes.</p>\n<p>An index does not change the logical content of a table. Queries produce the same results whether or not an index exists. Turso automatically decides whether to use an available index when executing a query. You can use <a href=\"#explain\">EXPLAIN</a> to see whether a query plan uses a particular index.</p>\n<p>The <code>DROP INDEX</code> statement removes an index from the database. Dropping an index has no effect on the table data.</p>\n<h2 id=\"clauses-10\"><a class=\"header\" href=\"#clauses-10\">Clauses</a></h2>\n<h3 id=\"unique-1\"><a class=\"header\" href=\"#unique-1\">UNIQUE</a></h3>\n<p>When the <code>UNIQUE</code> keyword appears between <code>CREATE</code> and <code>INDEX</code>, the index enforces a uniqueness constraint on the indexed columns. Any attempt to insert or update a row that would create a duplicate entry in the indexed columns produces a <code>UNIQUE constraint failed</code> error.</p>\n<p>NULL values are considered distinct from each other for uniqueness purposes. Multiple rows may contain NULL in a <code>UNIQUE</code> index column without violating the constraint.</p>\n<pre><code class=\"language-sql\">CREATE UNIQUE INDEX idx_users_email ON users(email);\n-- Two rows with email = 'alice@example.com' would be rejected.\n-- Two rows with email = NULL are allowed.\n</code></pre>\n<h3 id=\"if-not-exists-2\"><a class=\"header\" href=\"#if-not-exists-2\">IF NOT EXISTS</a></h3>\n<p>When <code>IF NOT EXISTS</code> is specified, the statement is a no-op if an index with the same name already exists. Without this clause, attempting to create an index whose name is already in use produces an error.</p>\n<pre><code class=\"language-sql\">CREATE INDEX IF NOT EXISTS idx_products_name ON products(name);\n-- Safe to run repeatedly without error.\n</code></pre>\n<h3 id=\"if-exists-drop-index\"><a class=\"header\" href=\"#if-exists-drop-index\">IF EXISTS (DROP INDEX)</a></h3>\n<p>When <code>IF EXISTS</code> is included in a <code>DROP INDEX</code> statement, no error is raised if the named index does not exist. Without this clause, dropping a nonexistent index produces an error.</p>\n<pre><code class=\"language-sql\">DROP INDEX IF EXISTS idx_old_report;\n</code></pre>\n<h3 id=\"indexed-columns\"><a class=\"header\" href=\"#indexed-columns\">Indexed Columns</a></h3>\n<p>Each indexed column entry in the column list is either a column name or a parenthesized expression. Multiple columns or expressions can be listed, separated by commas, to create a composite (multi-column) index.</p>\n<p>The order of columns in a composite index matters. An index on <code>(state, city)</code> is most useful for queries that filter on <code>state</code>, or on both <code>state</code> and <code>city</code>, but provides little benefit for queries that filter only on <code>city</code>.</p>\n<h3 id=\"collate-1\"><a class=\"header\" href=\"#collate-1\">COLLATE</a></h3>\n<p>Each indexed column or expression may include a <code>COLLATE</code> clause specifying the collation sequence used for text comparisons within the index. If omitted, the collation defaults to the one defined on the column in <code>CREATE TABLE</code>, or <code>BINARY</code> if none was specified.</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Collation</th><th>Behavior</th></tr>\n</thead>\n<tbody>\n<tr><td><code>BINARY</code></td><td>Compares bytes directly (default).</td></tr>\n<tr><td><code>NOCASE</code></td><td>Case-insensitive comparison for ASCII characters.</td></tr>\n<tr><td><code>RTRIM</code></td><td>Like <code>BINARY</code>, but trailing spaces are ignored.</td></tr>\n</tbody>\n</table>\n</div>\n<p>For a query to use an index, the collation in the query’s comparison must match the collation of the index. If a column is defined as <code>COLLATE NOCASE</code> but the index uses <code>COLLATE BINARY</code>, queries that rely on case-insensitive matching will not use that index.</p>\n<pre><code class=\"language-sql\">CREATE INDEX idx_contacts_name ON contacts(last_name COLLATE NOCASE);\n</code></pre>\n<h3 id=\"asc--desc\"><a class=\"header\" href=\"#asc--desc\">ASC / DESC</a></h3>\n<p>Each indexed column or expression may be followed by <code>ASC</code> (ascending, the default) or <code>DESC</code> (descending) to specify the sort order stored in the index. Descending indexes are useful for queries that sort in descending order, allowing the index to be scanned in its natural order rather than reversed.</p>\n<pre><code class=\"language-sql\">CREATE INDEX idx_transactions_recent ON transactions(account_id, created_at DESC);\n</code></pre>\n<h3 id=\"where-partial-indexes\"><a class=\"header\" href=\"#where-partial-indexes\">WHERE (Partial Indexes)</a></h3>\n<p>When a <code>WHERE</code> clause is appended to <code>CREATE INDEX</code>, the result is a partial index. Only rows that satisfy the <code>WHERE</code> expression are included in the index. This reduces index size and write overhead for tables where queries consistently filter on a known condition.</p>\n<p>The <code>WHERE</code> expression in a partial index has the following restrictions:</p>\n<ul>\n<li>It may not contain subqueries.</li>\n<li>It may not use functions whose results can change (such as <code>random()</code>).</li>\n<li>It may only reference columns of the indexed table.</li>\n</ul>\n<p>For Turso to use a partial index when executing a query, the query’s <code>WHERE</code> clause must imply the index’s <code>WHERE</code> clause. In the simplest case, the query includes the same condition as the index.</p>\n<pre><code class=\"language-sql\">CREATE INDEX idx_orders_pending ON orders(customer_id)\n  WHERE status = 'pending';\n-- Only rows with status = 'pending' are indexed.\n</code></pre>\n<h3 id=\"expression-indexes\"><a class=\"header\" href=\"#expression-indexes\">Expression Indexes</a></h3>\n<p>Instead of a plain column name, an indexed column entry may be a parenthesized expression. This is useful for indexing computed values, such as the lowercase version of a text column.</p>\n<p>Expression indexes have the following restrictions:</p>\n<ul>\n<li>The expression may not reference other tables.</li>\n<li>The expression may not contain subqueries.</li>\n<li>The expression may only use deterministic functions (not <code>random()</code>, <code>last_insert_rowid()</code>, etc.).</li>\n</ul>\n<pre><code class=\"language-sql\">CREATE INDEX idx_users_email_lower ON users(lower(email));\n-- Speeds up queries like: SELECT * FROM users WHERE lower(email) = 'alice@example.com';\n</code></pre>\n<h2 id=\"examples-13\"><a class=\"header\" href=\"#examples-13\">Examples</a></h2>\n<h3 id=\"basic-single-column-index\"><a class=\"header\" href=\"#basic-single-column-index\">Basic single-column index</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE employees (\n  id INTEGER PRIMARY KEY,\n  name TEXT,\n  department TEXT,\n  salary REAL\n);\n\nCREATE INDEX idx_employees_name ON employees(name);\n</code></pre>\n<h3 id=\"unique-index\"><a class=\"header\" href=\"#unique-index\">Unique index</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE users (\n  id INTEGER PRIMARY KEY,\n  email TEXT NOT NULL\n);\n\nCREATE UNIQUE INDEX idx_users_email ON users(email);\n\nINSERT INTO users VALUES (1, 'alice@example.com');\nINSERT INTO users VALUES (2, 'bob@example.com');\n-- INSERT INTO users VALUES (3, 'alice@example.com');\n-- Error: UNIQUE constraint failed: users.email\n</code></pre>\n<h3 id=\"multi-column-index\"><a class=\"header\" href=\"#multi-column-index\">Multi-column index</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE shipments (\n  id INTEGER PRIMARY KEY,\n  customer_id INTEGER,\n  city TEXT,\n  state TEXT,\n  zip TEXT\n);\n\nCREATE INDEX idx_shipments_location ON shipments(state, city, zip);\n-- Useful for queries that filter by state, or by state + city, or by all three.\n</code></pre>\n<h3 id=\"index-with-asc-and-desc\"><a class=\"header\" href=\"#index-with-asc-and-desc\">Index with ASC and DESC</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE transactions (\n  id INTEGER PRIMARY KEY,\n  account_id INTEGER,\n  created_at TEXT,\n  amount REAL\n);\n\nCREATE INDEX idx_transactions_recent ON transactions(account_id, created_at DESC);\n\nINSERT INTO transactions VALUES (1, 100, '2025-01-15', 250.00);\nINSERT INTO transactions VALUES (2, 100, '2025-03-20', 75.00);\nINSERT INTO transactions VALUES (3, 100, '2025-06-01', 300.00);\n\n-- The index stores rows per account with the most recent date first.\nSELECT created_at, amount FROM transactions\n  WHERE account_id = 100\n  ORDER BY created_at DESC;\n-- 2025-06-01|300.0\n-- 2025-03-20|75.0\n-- 2025-01-15|250.0\n</code></pre>\n<h3 id=\"partial-index\"><a class=\"header\" href=\"#partial-index\">Partial index</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE orders (\n  id INTEGER PRIMARY KEY,\n  customer_id INTEGER,\n  status TEXT,\n  amount REAL\n);\n\nCREATE INDEX idx_orders_pending ON orders(customer_id, amount)\n  WHERE status = 'pending';\n\nINSERT INTO orders VALUES (1, 10, 'pending', 99.99);\nINSERT INTO orders VALUES (2, 10, 'shipped', 149.99);\nINSERT INTO orders VALUES (3, 20, 'pending', 49.99);\n\n-- This query can use the partial index because its WHERE clause\n-- includes the index's condition.\nSELECT id, customer_id, amount FROM orders\n  WHERE status = 'pending'\n  ORDER BY amount DESC;\n-- 1|10|99.99\n-- 3|20|49.99\n</code></pre>\n<h3 id=\"expression-index\"><a class=\"header\" href=\"#expression-index\">Expression index</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE users (\n  id INTEGER PRIMARY KEY,\n  first_name TEXT,\n  last_name TEXT,\n  email TEXT\n);\n\nCREATE INDEX idx_users_email_lower ON users(lower(email));\n\nINSERT INTO users VALUES (1, 'Alice', 'Smith', 'Alice@Example.COM');\nINSERT INTO users VALUES (2, 'Bob', 'Jones', 'bob@example.com');\n\n-- The expression index speeds up case-insensitive email lookups.\nSELECT first_name, email FROM users\n  WHERE lower(email) = 'alice@example.com';\n-- Alice|Alice@Example.COM\n</code></pre>\n<h3 id=\"collation-in-an-index\"><a class=\"header\" href=\"#collation-in-an-index\">Collation in an index</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE contacts (\n  id INTEGER PRIMARY KEY,\n  first_name TEXT,\n  last_name TEXT\n);\n\nCREATE INDEX idx_contacts_fullname\n  ON contacts(first_name COLLATE NOCASE, last_name COLLATE NOCASE);\n-- Queries using case-insensitive comparisons on these columns can use this index.\n</code></pre>\n<h3 id=\"if-not-exists-1-1\"><a class=\"header\" href=\"#if-not-exists-1-1\">IF NOT EXISTS</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE products (\n  id INTEGER PRIMARY KEY,\n  name TEXT,\n  category TEXT,\n  price REAL\n);\n\nCREATE INDEX IF NOT EXISTS idx_products_name ON products(name);\n-- Running the same statement again does not produce an error.\nCREATE INDEX IF NOT EXISTS idx_products_name ON products(name);\n</code></pre>\n<h3 id=\"dropping-an-index\"><a class=\"header\" href=\"#dropping-an-index\">Dropping an index</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE products (\n  id INTEGER PRIMARY KEY,\n  name TEXT,\n  price REAL\n);\n\nCREATE INDEX idx_products_name ON products(name);\n\n-- Remove the index\nDROP INDEX idx_products_name;\n\n-- Safe to run even if the index does not exist\nDROP INDEX IF EXISTS idx_products_name;\n</code></pre>\n<h3 id=\"null-values-in-a-unique-index\"><a class=\"header\" href=\"#null-values-in-a-unique-index\">NULL values in a unique index</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE users (\n  id INTEGER PRIMARY KEY,\n  email TEXT\n);\n\nCREATE UNIQUE INDEX idx_users_email ON users(email);\n\n-- Multiple NULLs are allowed because NULL values are considered distinct.\nINSERT INTO users VALUES (1, NULL);\nINSERT INTO users VALUES (2, NULL);\n\nSELECT * FROM users;\n-- 1|\n-- 2|\n</code></pre>\n<div style=\"break-before: page; page-break-before: always;\"></div>\n<h1 id=\"create-view\"><a class=\"header\" href=\"#create-view\">CREATE VIEW</a></h1>\n<h2 id=\"syntax-14\"><a class=\"header\" href=\"#syntax-14\">Syntax</a></h2>\n<pre><code class=\"language-sql\">CREATE [TEMP | TEMPORARY] VIEW [IF NOT EXISTS] view-name\n  AS select-stmt\n</code></pre>\n<pre><code class=\"language-sql\">DROP VIEW [IF EXISTS] view-name\n</code></pre>\n<h2 id=\"description-14\"><a class=\"header\" href=\"#description-14\">Description</a></h2>\n<p>The CREATE VIEW statement assigns a name to a pre-packaged SELECT statement.\nOnce created, a view can be used anywhere a table name is accepted in the FROM\nclause of a SELECT: in simple queries, JOINs, subqueries, and even in the\ndefinitions of other views.</p>\n<p>Views are read-only. You cannot use INSERT, UPDATE, or DELETE on a view. A view\ndoes not store data; every time it is referenced in a query, Turso expands it\ninto the underlying SELECT and executes it against the current table data. This\nmeans the results of a view always reflect the latest state of the base tables.</p>\n<p>The DROP VIEW statement removes a view definition from the database. It has no\neffect on the underlying tables or their data.</p>\n<h2 id=\"clauses-11\"><a class=\"header\" href=\"#clauses-11\">Clauses</a></h2>\n<h3 id=\"as-select-stmt\"><a class=\"header\" href=\"#as-select-stmt\">AS select-stmt</a></h3>\n<p>The AS keyword is followed by any valid SELECT statement. This SELECT defines\nwhat the view returns when queried. The column names of the view are derived\nfrom the result columns of the SELECT. Use the <code>AS</code> alias syntax in the SELECT\nto give view columns well-defined names, particularly when the result includes\nexpressions or function calls.</p>\n<pre><code class=\"language-sql\">-- Without aliases, computed columns get auto-generated names\nCREATE VIEW order_stats AS\n  SELECT customer_id, COUNT(*), SUM(amount)\n  FROM orders\n  GROUP BY customer_id;\n\n-- With aliases, column names are explicit and predictable\nCREATE VIEW order_stats AS\n  SELECT customer_id,\n         COUNT(*) AS order_count,\n         SUM(amount) AS total_spent\n  FROM orders\n  GROUP BY customer_id;\n</code></pre>\n<h3 id=\"temp--temporary\"><a class=\"header\" href=\"#temp--temporary\">TEMP / TEMPORARY</a></h3>\n<p>If the <code>TEMP</code> or <code>TEMPORARY</code> keyword appears between <code>CREATE</code> and <code>VIEW</code>, the\nview is only visible to the current database connection and is automatically\ndeleted when the connection is closed. Temporary views are useful for\nintermediate results within a session.</p>\n<pre><code class=\"language-sql\">CREATE TEMP VIEW recent_orders AS\n  SELECT * FROM orders WHERE created_at &gt; '2024-01-01';\n</code></pre>\n<h3 id=\"if-not-exists-3\"><a class=\"header\" href=\"#if-not-exists-3\">IF NOT EXISTS</a></h3>\n<p>When <code>IF NOT EXISTS</code> is included, the statement does not produce a fatal error\nif a view with the same name already exists. Without this clause, attempting to\ncreate a view that already exists raises an error.</p>\n<h3 id=\"if-exists-drop-view\"><a class=\"header\" href=\"#if-exists-drop-view\">IF EXISTS (DROP VIEW)</a></h3>\n<p>When <code>IF EXISTS</code> is included in a DROP VIEW statement, no error is raised if\nthe named view does not exist. Without this clause, dropping a nonexistent view\nproduces an error.</p>\n<pre><code class=\"language-sql\">-- Safe to run even if the view does not exist\nDROP VIEW IF EXISTS old_report;\n</code></pre>\n<h2 id=\"examples-14\"><a class=\"header\" href=\"#examples-14\">Examples</a></h2>\n<h3 id=\"basic-view\"><a class=\"header\" href=\"#basic-view\">Basic view</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE products (\n  id INTEGER PRIMARY KEY,\n  name TEXT,\n  price REAL,\n  category TEXT\n);\n\nINSERT INTO products VALUES (1, 'Laptop', 999.99, 'Electronics');\nINSERT INTO products VALUES (2, 'Headphones', 49.99, 'Electronics');\nINSERT INTO products VALUES (3, 'Notebook', 5.99, 'Office');\nINSERT INTO products VALUES (4, 'Pen', 1.99, 'Office');\n\n-- Create a view that filters to one category\nCREATE VIEW electronics AS\n  SELECT id, name, price\n  FROM products\n  WHERE category = 'Electronics';\n\nSELECT * FROM electronics;\n-- 1|Laptop|999.99\n-- 2|Headphones|49.99\n</code></pre>\n<h3 id=\"view-with-computed-columns\"><a class=\"header\" href=\"#view-with-computed-columns\">View with computed columns</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE products (\n  id INTEGER PRIMARY KEY,\n  name TEXT,\n  price REAL,\n  quantity INTEGER\n);\n\nINSERT INTO products VALUES (1, 'Laptop', 999.99, 5);\nINSERT INTO products VALUES (2, 'Mouse', 29.99, 50);\n\nCREATE VIEW inventory_value AS\n  SELECT name, price, quantity, price * quantity AS total_value\n  FROM products;\n\nSELECT * FROM inventory_value;\n-- Laptop|999.99|5|4999.95\n-- Mouse|29.99|50|1499.5\n</code></pre>\n<h3 id=\"view-with-aggregation\"><a class=\"header\" href=\"#view-with-aggregation\">View with aggregation</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE orders (\n  id INTEGER PRIMARY KEY,\n  customer_id INTEGER,\n  product TEXT,\n  amount REAL\n);\n\nINSERT INTO orders VALUES (1, 10, 'Laptop', 999.99);\nINSERT INTO orders VALUES (2, 10, 'Mouse', 29.99);\nINSERT INTO orders VALUES (3, 20, 'Keyboard', 79.99);\nINSERT INTO orders VALUES (4, 20, 'Monitor', 399.99);\nINSERT INTO orders VALUES (5, 20, 'Cable', 9.99);\n\nCREATE VIEW customer_totals AS\n  SELECT customer_id,\n         COUNT(*) AS order_count,\n         SUM(amount) AS total_spent\n  FROM orders\n  GROUP BY customer_id;\n\nSELECT * FROM customer_totals;\n-- 10|2|1029.98\n-- 20|3|489.97\n</code></pre>\n<h3 id=\"view-with-a-join\"><a class=\"header\" href=\"#view-with-a-join\">View with a JOIN</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE customers (id INTEGER PRIMARY KEY, name TEXT);\nCREATE TABLE orders (id INTEGER PRIMARY KEY, customer_id INTEGER, amount REAL);\n\nINSERT INTO customers VALUES (1, 'Alice');\nINSERT INTO customers VALUES (2, 'Bob');\nINSERT INTO orders VALUES (1, 1, 100.00);\nINSERT INTO orders VALUES (2, 1, 200.00);\nINSERT INTO orders VALUES (3, 2, 150.00);\n\nCREATE VIEW order_details AS\n  SELECT c.name, o.amount\n  FROM customers c\n  JOIN orders o ON c.id = o.customer_id;\n\nSELECT * FROM order_details;\n-- Alice|100.0\n-- Alice|200.0\n-- Bob|150.0\n</code></pre>\n<h3 id=\"querying-a-view-with-additional-clauses\"><a class=\"header\" href=\"#querying-a-view-with-additional-clauses\">Querying a view with additional clauses</a></h3>\n<p>A view can be used just like a table in a SELECT. You can apply WHERE, ORDER\nBY, LIMIT, and any other clause on top of it.</p>\n<pre><code class=\"language-sql\">CREATE TABLE products (\n  id INTEGER PRIMARY KEY,\n  name TEXT,\n  price REAL,\n  category TEXT\n);\n\nINSERT INTO products VALUES (1, 'Laptop', 999.99, 'Electronics');\nINSERT INTO products VALUES (2, 'Mouse', 29.99, 'Electronics');\nINSERT INTO products VALUES (3, 'Notebook', 5.99, 'Office');\n\nCREATE VIEW electronics AS\n  SELECT id, name, price\n  FROM products\n  WHERE category = 'Electronics';\n\n-- Filter the view further\nSELECT * FROM electronics WHERE price &gt; 50;\n-- 1|Laptop|999.99\n\n-- Sort the view results\nSELECT * FROM electronics ORDER BY price DESC;\n-- 1|Laptop|999.99\n-- 2|Mouse|29.99\n</code></pre>\n<h3 id=\"joining-a-table-with-a-view\"><a class=\"header\" href=\"#joining-a-table-with-a-view\">Joining a table with a view</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE customers (id INTEGER PRIMARY KEY, name TEXT);\nCREATE TABLE orders (id INTEGER PRIMARY KEY, customer_id INTEGER, amount REAL);\n\nINSERT INTO customers VALUES (1, 'Alice');\nINSERT INTO customers VALUES (2, 'Bob');\nINSERT INTO orders VALUES (1, 1, 100.00);\nINSERT INTO orders VALUES (2, 1, 200.00);\nINSERT INTO orders VALUES (3, 2, 150.00);\n\nCREATE VIEW customer_totals AS\n  SELECT customer_id, SUM(amount) AS total\n  FROM orders\n  GROUP BY customer_id;\n\nSELECT c.name, ct.total\n  FROM customers c\n  JOIN customer_totals ct ON c.id = ct.customer_id\n  ORDER BY ct.total DESC;\n-- Alice|300.0\n-- Bob|150.0\n</code></pre>\n<h3 id=\"view-referencing-another-view\"><a class=\"header\" href=\"#view-referencing-another-view\">View referencing another view</a></h3>\n<p>Views can be built on top of other views.</p>\n<pre><code class=\"language-sql\">CREATE TABLE products (\n  id INTEGER PRIMARY KEY,\n  name TEXT,\n  price REAL,\n  category TEXT\n);\n\nINSERT INTO products VALUES (1, 'Laptop', 999.99, 'Electronics');\nINSERT INTO products VALUES (2, 'Mouse', 29.99, 'Electronics');\nINSERT INTO products VALUES (3, 'Notebook', 5.99, 'Office');\n\nCREATE VIEW electronics AS\n  SELECT id, name, price\n  FROM products\n  WHERE category = 'Electronics';\n\nCREATE VIEW expensive_electronics AS\n  SELECT * FROM electronics WHERE price &gt; 100;\n\nSELECT * FROM expensive_electronics;\n-- 1|Laptop|999.99\n</code></pre>\n<h3 id=\"using-a-view-in-a-subquery\"><a class=\"header\" href=\"#using-a-view-in-a-subquery\">Using a view in a subquery</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE products (\n  id INTEGER PRIMARY KEY,\n  name TEXT,\n  price REAL,\n  category TEXT\n);\n\nINSERT INTO products VALUES (1, 'Laptop', 999.99, 'Electronics');\nINSERT INTO products VALUES (2, 'Mouse', 29.99, 'Electronics');\nINSERT INTO products VALUES (3, 'Notebook', 5.99, 'Office');\n\nCREATE VIEW electronics AS\n  SELECT id, name, price\n  FROM products\n  WHERE category = 'Electronics';\n\nSELECT name FROM products WHERE id IN (SELECT id FROM electronics);\n-- Laptop\n-- Mouse\n</code></pre>\n<h3 id=\"temporary-view\"><a class=\"header\" href=\"#temporary-view\">Temporary view</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, price REAL);\n\nINSERT INTO products VALUES (1, 'Laptop', 999.99);\nINSERT INTO products VALUES (2, 'Pen', 1.99);\n\nCREATE TEMP VIEW cheap_products AS\n  SELECT id, name, price\n  FROM products\n  WHERE price &lt; 100;\n\nSELECT * FROM cheap_products;\n-- 2|Pen|1.99\n</code></pre>\n<h3 id=\"dropping-a-view\"><a class=\"header\" href=\"#dropping-a-view\">Dropping a view</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT);\nCREATE VIEW all_products AS SELECT * FROM products;\n\n-- Drop the view\nDROP VIEW all_products;\n\n-- Safe to run even if the view does not exist\nDROP VIEW IF EXISTS all_products;\n</code></pre>\n<h3 id=\"views-are-read-only\"><a class=\"header\" href=\"#views-are-read-only\">Views are read-only</a></h3>\n<p>Attempting to INSERT, UPDATE, or DELETE against a view produces an error.</p>\n<pre><code class=\"language-sql\">CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, price REAL);\nINSERT INTO products VALUES (1, 'Laptop', 999.99);\n\nCREATE VIEW all_products AS SELECT * FROM products;\n\n-- All of the following produce errors:\n-- INSERT INTO all_products VALUES (2, 'Mouse', 29.99);\n-- UPDATE all_products SET price = 500 WHERE id = 1;\n-- DELETE FROM all_products WHERE id = 1;\n</code></pre>\n<h2 id=\"compatibility-10\"><a class=\"header\" href=\"#compatibility-10\">Compatibility</a></h2>\n<p>Turso supports CREATE VIEW with the same syntax as SQLite, with the following\nnotes:</p>\n<ul>\n<li>\n<p><strong>Column name list</strong>: The parenthesized column name list after the view name\n(e.g., <code>CREATE VIEW v(a, b) AS SELECT ...</code>) is parsed but the specified\ncolumn names are not applied to the view output. Use <code>AS</code> aliases in the\nSELECT statement instead to control column names.</p>\n</li>\n<li>\n<p><strong>IF NOT EXISTS</strong>: The <code>IF NOT EXISTS</code> clause is accepted by the parser. When\na view with the same name already exists, a diagnostic message is emitted but\nexecution continues.</p>\n</li>\n</ul>\n<div style=\"break-before: page; page-break-before: always;\"></div>\n<h1 id=\"create-trigger\"><a class=\"header\" href=\"#create-trigger\">CREATE TRIGGER</a></h1>\n<h2 id=\"syntax-15\"><a class=\"header\" href=\"#syntax-15\">Syntax</a></h2>\n<pre><code class=\"language-sql\">CREATE TRIGGER [IF NOT EXISTS] trigger-name\n  [BEFORE | AFTER] {DELETE | INSERT | UPDATE [OF column-name [, ...]]}\n  ON table-name\n  [FOR EACH ROW]\n  [WHEN expr]\nBEGIN\n  statement; [statement; ...]\nEND;\n</code></pre>\n<h2 id=\"description-15\"><a class=\"header\" href=\"#description-15\">Description</a></h2>\n<p>The <code>CREATE TRIGGER</code> statement defines a trigger – a set of SQL statements that automatically execute in response to <code>INSERT</code>, <code>UPDATE</code>, or <code>DELETE</code> operations on a specified table. Triggers are useful for enforcing business rules, maintaining audit logs, synchronizing related tables, and computing derived values.</p>\n<p>Turso supports <code>BEFORE</code> and <code>AFTER</code> triggers on tables. Each trigger fires once per affected row (FOR EACH ROW semantics). The trigger body can contain one or more <code>INSERT</code>, <code>UPDATE</code>, <code>DELETE</code>, or <code>SELECT</code> statements, which execute as part of the same transaction as the triggering statement.</p>\n<p>Triggers require the <code>--experimental-triggers</code> flag when starting the Turso CLI.</p>\n<h2 id=\"clauses-12\"><a class=\"header\" href=\"#clauses-12\">Clauses</a></h2>\n<h3 id=\"trigger-timing\"><a class=\"header\" href=\"#trigger-timing\">Trigger Timing</a></h3>\n<p>The optional timing keyword controls when the trigger body executes relative to the triggering operation.</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Timing</th><th>Behavior</th></tr>\n</thead>\n<tbody>\n<tr><td><code>BEFORE</code></td><td>Executes before the row is modified. Default if no timing is specified.</td></tr>\n<tr><td><code>AFTER</code></td><td>Executes after the row has been modified.</td></tr>\n</tbody>\n</table>\n</div>\n<p>When no timing keyword is provided, <code>BEFORE</code> is used by default.</p>\n<h3 id=\"trigger-event\"><a class=\"header\" href=\"#trigger-event\">Trigger Event</a></h3>\n<p>The event determines which data modification operation causes the trigger to fire.</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Event</th><th>Description</th></tr>\n</thead>\n<tbody>\n<tr><td><code>INSERT</code></td><td>Fires when a new row is inserted into the table.</td></tr>\n<tr><td><code>UPDATE</code></td><td>Fires when any column of a row is updated.</td></tr>\n<tr><td><code>UPDATE OF column-name [, ...]</code></td><td>Fires only when one of the specified columns appears in the <code>SET</code> clause of an <code>UPDATE</code> statement.</td></tr>\n<tr><td><code>DELETE</code></td><td>Fires when a row is deleted from the table.</td></tr>\n</tbody>\n</table>\n</div>\n<p>For <code>UPDATE OF</code>, the trigger fires based on which columns appear in the <code>SET</code> clause, not on whether the column value actually changes. Column names that do not exist in the table are silently ignored.</p>\n<h3 id=\"for-each-row\"><a class=\"header\" href=\"#for-each-row\">FOR EACH ROW</a></h3>\n<p>Turso supports only row-level triggers. The <code>FOR EACH ROW</code> clause is optional and has no effect – row-level semantics are always used. The trigger body executes once for each row affected by the triggering statement.</p>\n<h3 id=\"when\"><a class=\"header\" href=\"#when\">WHEN</a></h3>\n<p>The optional <code>WHEN</code> clause provides a condition that is evaluated for each row. If the condition evaluates to false or NULL, the trigger body is skipped for that row. The <code>WHEN</code> expression can reference <code>NEW</code> and <code>OLD</code> row values (see below).</p>\n<h3 id=\"new-and-old-references\"><a class=\"header\" href=\"#new-and-old-references\">NEW and OLD References</a></h3>\n<p>Inside a trigger body and <code>WHEN</code> clause, the special table references <code>NEW</code> and <code>OLD</code> provide access to the row values being modified.</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Event</th><th><code>NEW</code></th><th><code>OLD</code></th></tr>\n</thead>\n<tbody>\n<tr><td><code>INSERT</code></td><td>The row being inserted.</td><td>Not available.</td></tr>\n<tr><td><code>UPDATE</code></td><td>The row after the update.</td><td>The row before the update.</td></tr>\n<tr><td><code>DELETE</code></td><td>Not available.</td><td>The row being deleted.</td></tr>\n</tbody>\n</table>\n</div>\n<p>Use dot notation to reference individual columns: <code>NEW.column_name</code> or <code>OLD.column_name</code>. Both <code>NEW.rowid</code> and named rowid aliases (e.g., <code>NEW.id</code> for an <code>INTEGER PRIMARY KEY</code> column) are supported.</p>\n<h3 id=\"if-not-exists-4\"><a class=\"header\" href=\"#if-not-exists-4\">IF NOT EXISTS</a></h3>\n<p>When <code>IF NOT EXISTS</code> is included, Turso silently does nothing if a trigger with the same name already exists. Without this clause, attempting to create a trigger whose name is already in use results in an error.</p>\n<h3 id=\"trigger-body\"><a class=\"header\" href=\"#trigger-body\">Trigger Body</a></h3>\n<p>The trigger body is enclosed between <code>BEGIN</code> and <code>END</code> and contains one or more SQL statements, each terminated by a semicolon. The supported statement types are:</p>\n<ul>\n<li><code>INSERT</code></li>\n<li><code>UPDATE</code></li>\n<li><code>DELETE</code></li>\n<li><code>SELECT</code></li>\n</ul>\n<p>All statements in the trigger body execute within the same transaction as the triggering statement. If any statement in the trigger body fails, the entire triggering operation is rolled back.</p>\n<h2 id=\"trigger-execution-order\"><a class=\"header\" href=\"#trigger-execution-order\">Trigger Execution Order</a></h2>\n<p>When multiple triggers are defined on the same table for the same event and timing, they fire in reverse order of creation (last created fires first).</p>\n<h2 id=\"nested-and-recursive-triggers\"><a class=\"header\" href=\"#nested-and-recursive-triggers\">Nested and Recursive Triggers</a></h2>\n<p>A trigger’s body can cause other triggers to fire. For example, an <code>AFTER INSERT</code> trigger on table A that inserts into table B will fire any insert triggers on table B.</p>\n<p>Triggers can also be recursive – a trigger can modify the same table that caused it to fire. However, recursive triggers do not fire recursively by default; a trigger that fires once will not fire itself again in the same chain. This means a recursive <code>AFTER INSERT</code> trigger that inserts into the same table will execute at most one additional level.</p>\n<h2 id=\"trigger-lifecycle\"><a class=\"header\" href=\"#trigger-lifecycle\">Trigger Lifecycle</a></h2>\n<p>Triggers are automatically dropped when the table they are attached to is dropped. To manually remove a trigger, use <code>DROP TRIGGER</code>:</p>\n<pre><code class=\"language-sql\">DROP TRIGGER [IF EXISTS] trigger-name;\n</code></pre>\n<h2 id=\"examples-15\"><a class=\"header\" href=\"#examples-15\">Examples</a></h2>\n<h3 id=\"audit-log-with-after-insert\"><a class=\"header\" href=\"#audit-log-with-after-insert\">Audit Log with AFTER INSERT</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, price REAL);\nCREATE TABLE audit_log (id INTEGER PRIMARY KEY, action TEXT, product_name TEXT);\n\n-- Log every new product insertion\nCREATE TRIGGER log_product_insert AFTER INSERT ON products\nBEGIN\n  INSERT INTO audit_log (action, product_name) VALUES ('INSERT', NEW.name);\nEND;\n\nINSERT INTO products (name, price) VALUES ('Laptop', 999.99);\nINSERT INTO products (name, price) VALUES ('Mouse', 29.99);\n\nSELECT * FROM audit_log;\n-- 1|INSERT|Laptop\n-- 2|INSERT|Mouse\n</code></pre>\n<h3 id=\"tracking-changes-with-old-and-new\"><a class=\"header\" href=\"#tracking-changes-with-old-and-new\">Tracking Changes with OLD and NEW</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT, salary REAL);\nCREATE TABLE salary_changes (id INTEGER PRIMARY KEY, old_salary REAL, new_salary REAL);\n\nCREATE TRIGGER log_salary_update AFTER UPDATE ON employees\nBEGIN\n  INSERT INTO salary_changes (old_salary, new_salary)\n    VALUES (OLD.salary, NEW.salary);\nEND;\n\nINSERT INTO employees VALUES (1, 'Alice', 75000.0);\nUPDATE employees SET salary = 80000.0 WHERE id = 1;\n\nSELECT * FROM salary_changes;\n-- 1|75000.0|80000.0\n</code></pre>\n<h3 id=\"archiving-deleted-rows-with-before-delete\"><a class=\"header\" href=\"#archiving-deleted-rows-with-before-delete\">Archiving Deleted Rows with BEFORE DELETE</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE orders (id INTEGER PRIMARY KEY, customer TEXT, total REAL);\nCREATE TABLE deleted_orders (id INTEGER PRIMARY KEY, customer TEXT, total REAL);\n\nCREATE TRIGGER archive_deleted_order BEFORE DELETE ON orders\nBEGIN\n  INSERT INTO deleted_orders (id, customer, total) VALUES (OLD.id, OLD.customer, OLD.total);\nEND;\n\nINSERT INTO orders VALUES (1, 'Bob', 150.00);\nINSERT INTO orders VALUES (2, 'Carol', 250.00);\nDELETE FROM orders WHERE customer = 'Bob';\n\nSELECT * FROM deleted_orders;\n-- 1|Bob|150.0\n</code></pre>\n<h3 id=\"conditional-trigger-with-when-clause\"><a class=\"header\" href=\"#conditional-trigger-with-when-clause\">Conditional Trigger with WHEN Clause</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE sensor_readings (id INTEGER PRIMARY KEY, value INTEGER);\nCREATE TABLE alerts (id INTEGER PRIMARY KEY, reading_id INTEGER);\n\n-- Only log an alert when the reading exceeds a threshold\nCREATE TRIGGER high_value_alert AFTER INSERT ON sensor_readings\nWHEN NEW.value &gt; 100\nBEGIN\n  INSERT INTO alerts (reading_id) VALUES (NEW.id);\nEND;\n\nINSERT INTO sensor_readings VALUES (1, 50);\nINSERT INTO sensor_readings VALUES (2, 150);\nINSERT INTO sensor_readings VALUES (3, 75);\n\nSELECT * FROM alerts;\n-- 1|2\n</code></pre>\n<h3 id=\"update-of-specific-columns\"><a class=\"header\" href=\"#update-of-specific-columns\">UPDATE OF Specific Columns</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE accounts (id INTEGER PRIMARY KEY, email TEXT, password_hash TEXT);\nCREATE TABLE security_log (id INTEGER PRIMARY KEY, msg TEXT);\n\n-- Only fire when the password column is updated\nCREATE TRIGGER log_password_change AFTER UPDATE OF password_hash ON accounts\nBEGIN\n  INSERT INTO security_log (msg) VALUES ('password changed for account ' || NEW.id);\nEND;\n\nINSERT INTO accounts VALUES (1, 'alice@example.com', 'hash1');\nUPDATE accounts SET email = 'alice-new@example.com' WHERE id = 1;\nUPDATE accounts SET password_hash = 'hash2' WHERE id = 1;\n\nSELECT * FROM security_log;\n-- 1|password changed for account 1\n</code></pre>\n<h3 id=\"multiple-statements-in-trigger-body\"><a class=\"header\" href=\"#multiple-statements-in-trigger-body\">Multiple Statements in Trigger Body</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE inventory (id INTEGER PRIMARY KEY, product TEXT, quantity INTEGER);\nCREATE TABLE restock_log (id INTEGER PRIMARY KEY, product TEXT);\nCREATE TABLE quantity_log (id INTEGER PRIMARY KEY, product TEXT, qty INTEGER);\n\nCREATE TRIGGER track_inventory AFTER INSERT ON inventory\nBEGIN\n  INSERT INTO restock_log (product) VALUES (NEW.product);\n  INSERT INTO quantity_log (product, qty) VALUES (NEW.product, NEW.quantity);\nEND;\n\nINSERT INTO inventory VALUES (1, 'Widget', 100);\n\nSELECT * FROM restock_log;\n-- 1|Widget\nSELECT * FROM quantity_log;\n-- 1|Widget|100\n</code></pre>\n<h3 id=\"cascading-triggers-across-tables\"><a class=\"header\" href=\"#cascading-triggers-across-tables\">Cascading Triggers Across Tables</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE departments (id INTEGER PRIMARY KEY, budget INTEGER);\nCREATE TABLE projects (id INTEGER PRIMARY KEY, dept_id INTEGER, cost INTEGER);\nCREATE TABLE notifications (id INTEGER PRIMARY KEY, msg TEXT);\n\nINSERT INTO departments VALUES (1, 50000);\n\n-- When a project is added, update department budget\nCREATE TRIGGER deduct_budget AFTER INSERT ON projects\nBEGIN\n  UPDATE departments SET budget = budget - NEW.cost WHERE id = NEW.dept_id;\nEND;\n\n-- When budget changes, log a notification\nCREATE TRIGGER budget_notification AFTER UPDATE ON departments\nBEGIN\n  INSERT INTO notifications (msg) VALUES ('budget updated: ' || NEW.budget);\nEND;\n\nINSERT INTO projects VALUES (1, 1, 10000);\n\nSELECT * FROM departments;\n-- 1|40000\nSELECT * FROM notifications;\n-- 1|budget updated: 40000\n</code></pre>\n<h3 id=\"complex-when-clause\"><a class=\"header\" href=\"#complex-when-clause\">Complex WHEN Clause</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE transactions (id INTEGER PRIMARY KEY, amount INTEGER, status TEXT DEFAULT 'pending');\n\n-- Automatically mark large transactions as requiring review\nCREATE TRIGGER flag_large_transactions AFTER INSERT ON transactions\nWHEN NEW.amount &gt; 500 AND NEW.amount &lt; 10000\nBEGIN\n  UPDATE transactions SET status = 'review' WHERE id = NEW.id;\nEND;\n\nINSERT INTO transactions (id, amount) VALUES (1, 100);\nINSERT INTO transactions (id, amount) VALUES (2, 750);\nINSERT INTO transactions (id, amount) VALUES (3, 50000);\n\nSELECT * FROM transactions ORDER BY id;\n-- 1|100|pending\n-- 2|750|review\n-- 3|50000|pending\n</code></pre>\n<h3 id=\"if-not-exists-1-2\"><a class=\"header\" href=\"#if-not-exists-1-2\">IF NOT EXISTS</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE events (id INTEGER PRIMARY KEY, name TEXT);\n\nCREATE TRIGGER IF NOT EXISTS log_event BEFORE INSERT ON events\nBEGIN\n  SELECT 1;\nEND;\n\n-- This does not produce an error because of IF NOT EXISTS\nCREATE TRIGGER IF NOT EXISTS log_event BEFORE INSERT ON events\nBEGIN\n  SELECT 1;\nEND;\n\nSELECT name FROM sqlite_schema WHERE type = 'trigger' AND name = 'log_event';\n-- log_event\n</code></pre>\n<h2 id=\"compatibility-11\"><a class=\"header\" href=\"#compatibility-11\">Compatibility</a></h2>\n<ul>\n<li>Triggers are an experimental feature and require the <code>--experimental-triggers</code> flag.</li>\n<li><code>INSTEAD OF</code> triggers (used with views) are not yet supported.</li>\n<li><code>TEMPORARY</code> triggers are not yet supported.</li>\n<li>The <code>RAISE()</code> function is not supported within trigger bodies.</li>\n<li>Recursive triggers are limited to a single additional level of recursion by default.</li>\n</ul>\n<div style=\"break-before: page; page-break-before: always;\"></div>\n<h1 id=\"alter-table\"><a class=\"header\" href=\"#alter-table\">ALTER TABLE</a></h1>\n<h2 id=\"syntax-16\"><a class=\"header\" href=\"#syntax-16\">Syntax</a></h2>\n<pre><code class=\"language-sql\">ALTER TABLE table-name RENAME TO new-table-name\n</code></pre>\n<pre><code class=\"language-sql\">ALTER TABLE table-name RENAME [COLUMN] column-name TO new-column-name\n</code></pre>\n<pre><code class=\"language-sql\">ALTER TABLE table-name ADD [COLUMN] column-def\n</code></pre>\n<pre><code class=\"language-sql\">ALTER TABLE table-name DROP [COLUMN] column-name\n</code></pre>\n<pre><code class=\"language-sql\">ALTER TABLE table-name ALTER COLUMN column-name TO column-def\n</code></pre>\n<h2 id=\"description-16\"><a class=\"header\" href=\"#description-16\">Description</a></h2>\n<p>The <code>ALTER TABLE</code> statement modifies the schema of an existing table. Turso supports five operations: renaming a table, renaming a column, adding a column, dropping a column, and altering a column definition. Unlike <code>CREATE TABLE</code>, <code>ALTER TABLE</code> works on tables that already exist and may already contain data.</p>\n<p>Each operation modifies the table’s schema entry in <code>sqlite_schema</code>. Renaming and adding columns are fast operations whose execution time is independent of the number of rows. Dropping a column rewrites the table data and is proportional to table size.</p>\n<p>System tables (those with names beginning with <code>sqlite_</code>) cannot be altered.</p>\n<h2 id=\"operations\"><a class=\"header\" href=\"#operations\">Operations</a></h2>\n<h3 id=\"rename-to\"><a class=\"header\" href=\"#rename-to\">RENAME TO</a></h3>\n<p>Renames the table from <code>table-name</code> to <code>new-table-name</code>. The new name must not collide with any existing table, view, or index name. The table cannot be moved between attached databases – it is only renamed within its current database.</p>\n<p>References to the table in triggers, views, and foreign key constraints are automatically updated to use the new name, including self-referencing foreign keys.</p>\n<pre><code class=\"language-sql\">CREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT);\nALTER TABLE employees RENAME TO staff;\nSELECT name FROM sqlite_schema WHERE type = 'table';\n-- staff\n</code></pre>\n<h3 id=\"rename-column\"><a class=\"header\" href=\"#rename-column\">RENAME COLUMN</a></h3>\n<p>Renames an existing column from <code>column-name</code> to <code>new-column-name</code>. The <code>COLUMN</code> keyword is optional. All references to the column in indexes, triggers, views, and foreign key constraints (both child and parent sides) are automatically updated.</p>\n<p>If the rename would introduce a semantic ambiguity in a trigger or view, the operation fails and no changes are applied.</p>\n<pre><code class=\"language-sql\">CREATE TABLE products (id INTEGER PRIMARY KEY, product_name TEXT NOT NULL, price REAL);\nALTER TABLE products RENAME COLUMN product_name TO name;\nSELECT sql FROM sqlite_schema WHERE name = 'products';\n-- CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT NOT NULL, price REAL)\n</code></pre>\n<p>Renaming a column that participates in a foreign key constraint updates both sides of the relationship:</p>\n<pre><code class=\"language-sql\">PRAGMA foreign_keys = ON;\nCREATE TABLE orders (order_id INTEGER PRIMARY KEY, date TEXT);\nCREATE TABLE items (item_id INTEGER PRIMARY KEY, oid INTEGER,\n  FOREIGN KEY (oid) REFERENCES orders(order_id));\n\nALTER TABLE orders RENAME COLUMN order_id TO ord_id;\n\nSELECT sql FROM sqlite_schema WHERE name = 'items';\n-- CREATE TABLE items (item_id INTEGER PRIMARY KEY, oid INTEGER, FOREIGN KEY (oid) REFERENCES orders (ord_id))\n</code></pre>\n<h3 id=\"add-column\"><a class=\"header\" href=\"#add-column\">ADD COLUMN</a></h3>\n<p>Appends a new column to the end of the table’s column list. The <code>COLUMN</code> keyword is optional. The <code>column-def</code> follows the same syntax as a column definition in <code>CREATE TABLE</code>, including an optional type name, <code>DEFAULT</code>, <code>NOT NULL</code>, <code>CHECK</code>, <code>COLLATE</code>, and <code>REFERENCES</code> clauses.</p>\n<pre><code class=\"language-sql\">CREATE TABLE orders (id INTEGER PRIMARY KEY, customer_id INTEGER);\nALTER TABLE orders ADD COLUMN total_amount REAL DEFAULT 0.0;\nALTER TABLE orders ADD COLUMN status TEXT DEFAULT 'pending';\nSELECT sql FROM sqlite_schema WHERE name = 'orders';\n-- CREATE TABLE orders (id INTEGER PRIMARY KEY, customer_id INTEGER, total_amount REAL DEFAULT 0.0, status TEXT DEFAULT 'pending')\n</code></pre>\n<h4 id=\"restrictions\"><a class=\"header\" href=\"#restrictions\">Restrictions</a></h4>\n<p>The new column may not have:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Restriction</th><th>Reason</th></tr>\n</thead>\n<tbody>\n<tr><td><code>PRIMARY KEY</code> or <code>UNIQUE</code> constraint</td><td>Would require rewriting existing data and indexes.</td></tr>\n<tr><td>A non-constant default expression</td><td>The default must be a literal, a signed literal, or a parenthesized constant. Expressions like <code>(NULL + 5)</code> are rejected.</td></tr>\n<tr><td><code>NOT NULL</code> without a non-null default (when the table has rows)</td><td>Existing rows would have NULL for the new column, violating the constraint.</td></tr>\n<tr><td><code>CURRENT_TIME</code>, <code>CURRENT_DATE</code>, or <code>CURRENT_TIMESTAMP</code> as default (when the table has rows)</td><td>These are non-deterministic and cannot be used to backfill existing rows.</td></tr>\n<tr><td><code>GENERATED ALWAYS ... STORED</code> or <code>AS (expr)</code></td><td>Adding generated columns via <code>ALTER TABLE</code> is not supported.</td></tr>\n</tbody>\n</table>\n</div>\n<p>A <code>NOT NULL</code> column without a default value is permitted if the table is empty:</p>\n<pre><code class=\"language-sql\">CREATE TABLE contacts (id INTEGER PRIMARY KEY);\nALTER TABLE contacts ADD name TEXT NOT NULL;\nSELECT sql FROM sqlite_schema WHERE type = 'table' AND name = 'contacts';\n-- CREATE TABLE contacts (id INTEGER PRIMARY KEY, name TEXT NOT NULL)\n</code></pre>\n<p>A <code>NOT NULL</code> column with a non-null default succeeds even on tables with existing rows:</p>\n<pre><code class=\"language-sql\">CREATE TABLE tasks (id INTEGER PRIMARY KEY);\nINSERT INTO tasks VALUES (1);\nALTER TABLE tasks ADD priority INTEGER NOT NULL DEFAULT 5;\nINSERT INTO tasks (id) VALUES (2);\nSELECT * FROM tasks ORDER BY id;\n-- 1|5\n-- 2|5\n</code></pre>\n<p>Adding a column with a foreign key reference is supported and updates the schema to include a table-level <code>FOREIGN KEY</code> clause:</p>\n<pre><code class=\"language-sql\">CREATE TABLE departments (id INTEGER PRIMARY KEY);\nCREATE TABLE staff (id INTEGER PRIMARY KEY);\nALTER TABLE staff ADD COLUMN dept_id REFERENCES departments(id);\nSELECT sql FROM sqlite_schema WHERE name = 'staff';\n-- CREATE TABLE staff (id INTEGER PRIMARY KEY, dept_id, FOREIGN KEY (dept_id) REFERENCES departments(id))\n</code></pre>\n<p>Duplicate column names are rejected (case-insensitive):</p>\n<pre><code class=\"language-sql\">CREATE TABLE items (name TEXT);\nALTER TABLE items ADD COLUMN name TEXT;\n-- Error: duplicate column name\n</code></pre>\n<h3 id=\"drop-column\"><a class=\"header\" href=\"#drop-column\">DROP COLUMN</a></h3>\n<p>Removes a column from the table and rewrites the table data to exclude the dropped column’s values. The <code>COLUMN</code> keyword is optional.</p>\n<pre><code class=\"language-sql\">CREATE TABLE customers (id INTEGER PRIMARY KEY, name TEXT, email TEXT, phone TEXT);\nINSERT INTO customers VALUES (1, 'Alice', 'alice@example.com', '555-0100');\nALTER TABLE customers DROP COLUMN phone;\nSELECT * FROM customers;\n-- 1|Alice|alice@example.com\n</code></pre>\n<h4 id=\"restrictions-1\"><a class=\"header\" href=\"#restrictions-1\">Restrictions</a></h4>\n<p>A column cannot be dropped if it:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Restriction</th><th>Reason</th></tr>\n</thead>\n<tbody>\n<tr><td>Is a <code>PRIMARY KEY</code> or part of one</td><td>The primary key is essential to the table’s identity.</td></tr>\n<tr><td>Has a <code>UNIQUE</code> constraint</td><td>A unique index depends on the column.</td></tr>\n<tr><td>Is referenced by an index</td><td>The index would become invalid.</td></tr>\n<tr><td>Is referenced by a table-level <code>CHECK</code> constraint</td><td>The CHECK expression would reference a missing column.</td></tr>\n<tr><td>Is used in a generated column expression</td><td>The generated column would become invalid.</td></tr>\n</tbody>\n</table>\n</div>\n<p>A column-level <code>CHECK</code> constraint attached to the dropped column is removed along with the column:</p>\n<pre><code class=\"language-sql\">CREATE TABLE metrics (\n  id INTEGER PRIMARY KEY,\n  value REAL CHECK (value BETWEEN 0.0 AND 1000.0),\n  label TEXT NOT NULL\n);\nINSERT INTO metrics VALUES (1, 500.0, 'temperature');\nALTER TABLE metrics DROP COLUMN value;\nINSERT INTO metrics VALUES (2, 'humidity');\nSELECT * FROM metrics;\n-- 1|temperature\n-- 2|humidity\n</code></pre>\n<h3 id=\"alter-column\"><a class=\"header\" href=\"#alter-column\">ALTER COLUMN</a></h3>\n<p><em>This is a Turso extension not present in SQLite.</em></p>\n<p>The <code>ALTER COLUMN</code> operation changes a column’s name and definition in a single statement. The <code>column-def</code> after <code>TO</code> is a full column definition (name, type, and constraints), replacing the old column definition entirely. Data in existing rows is preserved.</p>\n<pre><code class=\"language-sql\">CREATE TABLE sensors (id INTEGER PRIMARY KEY, reading INTEGER);\nCREATE INDEX idx_reading ON sensors (reading);\nALTER TABLE sensors ALTER COLUMN reading TO measurement BLOB;\nSELECT sql FROM sqlite_schema;\n-- CREATE TABLE sensors (id INTEGER PRIMARY KEY, measurement BLOB)\n-- CREATE INDEX idx_reading ON sensors (measurement)\n</code></pre>\n<p>The new column definition may not include <code>PRIMARY KEY</code> or <code>UNIQUE</code> constraints:</p>\n<pre><code class=\"language-sql\">CREATE TABLE config (key TEXT, value TEXT);\nALTER TABLE config ALTER COLUMN value TO value PRIMARY KEY;\n-- Error: cannot add PRIMARY KEY via ALTER COLUMN\n</code></pre>\n<p>The new definition may specify a generated column expression, as long as at least one non-generated column remains in the table:</p>\n<pre><code class=\"language-sql\">CREATE TABLE items (name TEXT, price REAL);\nALTER TABLE items ALTER COLUMN price TO computed_price AS (123);\nSELECT sql FROM sqlite_schema WHERE name = 'items';\n-- CREATE TABLE items (name TEXT, computed_price AS (123))\n</code></pre>\n<h2 id=\"examples-16\"><a class=\"header\" href=\"#examples-16\">Examples</a></h2>\n<h3 id=\"evolving-a-schema\"><a class=\"header\" href=\"#evolving-a-schema\">Evolving a Schema</a></h3>\n<p>A common workflow is creating a table and then adding columns as requirements change:</p>\n<pre><code class=\"language-sql\">CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT NOT NULL);\nINSERT INTO users VALUES (1, 'alice'), (2, 'bob');\n\nALTER TABLE users ADD email TEXT DEFAULT 'unknown';\nALTER TABLE users ADD created_at TEXT DEFAULT CURRENT_TIMESTAMP;\n\nSELECT id, username, email FROM users ORDER BY id;\n-- 1|alice|unknown\n-- 2|bob|unknown\n</code></pre>\n<h3 id=\"renaming-for-clarity\"><a class=\"header\" href=\"#renaming-for-clarity\">Renaming for Clarity</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE t (a INTEGER, b TEXT, c REAL);\nCREATE INDEX idx_t_a ON t (a);\n\nALTER TABLE t RENAME COLUMN a TO user_id;\nALTER TABLE t RENAME COLUMN b TO user_name;\nALTER TABLE t RENAME COLUMN c TO balance;\nALTER TABLE t RENAME TO accounts;\n\nSELECT sql FROM sqlite_schema ORDER BY type;\n-- CREATE INDEX idx_t_a ON accounts (user_id)\n-- CREATE TABLE accounts (user_id INTEGER, user_name TEXT, balance REAL)\n</code></pre>\n<h3 id=\"dropping-unused-columns\"><a class=\"header\" href=\"#dropping-unused-columns\">Dropping Unused Columns</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE logs (id INTEGER PRIMARY KEY, message TEXT, debug_info TEXT);\nINSERT INTO logs VALUES (1, 'Server started', 'verbose debug data');\nINSERT INTO logs VALUES (2, 'User login', 'session details');\n\nALTER TABLE logs DROP COLUMN debug_info;\nSELECT * FROM logs;\n-- 1|Server started\n-- 2|User login\n</code></pre>\n<h3 id=\"foreign-key-updates-after-rename\"><a class=\"header\" href=\"#foreign-key-updates-after-rename\">Foreign Key Updates After Rename</a></h3>\n<p>When a parent table is renamed, foreign key references in child tables are updated automatically:</p>\n<pre><code class=\"language-sql\">PRAGMA foreign_keys = ON;\nCREATE TABLE categories (id INTEGER PRIMARY KEY);\nCREATE TABLE products (id INTEGER PRIMARY KEY, cat_id INTEGER,\n  FOREIGN KEY (cat_id) REFERENCES categories(id));\n\nALTER TABLE categories RENAME TO product_categories;\n\nSELECT sql FROM sqlite_schema WHERE name = 'products';\n-- CREATE TABLE products (id INTEGER PRIMARY KEY, cat_id INTEGER, FOREIGN KEY (cat_id) REFERENCES product_categories (id))\n</code></pre>\n<h2 id=\"compatibility-12\"><a class=\"header\" href=\"#compatibility-12\">Compatibility</a></h2>\n<ul>\n<li><strong>ALTER COLUMN</strong> is a Turso extension. This operation is not available in SQLite. It allows changing a column’s name, type, and constraints in a single statement.</li>\n<li><strong>Adding generated columns</strong> via <code>ALTER TABLE ADD COLUMN</code> is not supported. Use <code>ALTER COLUMN</code> to convert an existing column to a generated column, or recreate the table.</li>\n</ul>\n<div style=\"break-before: page; page-break-before: always;\"></div>\n<h1 id=\"drop\"><a class=\"header\" href=\"#drop\">DROP</a></h1>\n<h2 id=\"syntax-17\"><a class=\"header\" href=\"#syntax-17\">Syntax</a></h2>\n<pre><code class=\"language-sql\">DROP TABLE [IF EXISTS] table-name\nDROP INDEX [IF EXISTS] index-name\nDROP VIEW [IF EXISTS] view-name\nDROP TRIGGER [IF EXISTS] trigger-name\n</code></pre>\n<h2 id=\"description-17\"><a class=\"header\" href=\"#description-17\">Description</a></h2>\n<p>The DROP statement removes a database object (table, index, view, or trigger) from the database. The object and all its associated data are permanently deleted. This action cannot be undone.</p>\n<p>If the <code>IF EXISTS</code> clause is included, the statement is a no-op when the named object does not exist. Without <code>IF EXISTS</code>, attempting to drop a nonexistent object raises an error.</p>\n<h2 id=\"drop-table\"><a class=\"header\" href=\"#drop-table\">DROP TABLE</a></h2>\n<p>Removes a table and all of its data, indexes, and triggers from the database.</p>\n<pre><code class=\"language-sql\">-- Remove a table\nDROP TABLE users;\n\n-- Remove a table only if it exists (no error if missing)\nDROP TABLE IF EXISTS users;\n</code></pre>\n<p>When a table is dropped:</p>\n<ul>\n<li>All rows in the table are deleted.</li>\n<li>All indexes associated with the table are removed.</li>\n<li>All triggers associated with the table are removed.</li>\n<li>Foreign key references to the table are not automatically updated. If other tables have foreign key constraints pointing to the dropped table, those constraints become orphaned.</li>\n</ul>\n<h2 id=\"drop-index\"><a class=\"header\" href=\"#drop-index\">DROP INDEX</a></h2>\n<p>Removes an index from the database. The underlying table data is not affected.</p>\n<pre><code class=\"language-sql\">-- Remove an index\nDROP INDEX idx_users_email;\n\n-- Remove an index only if it exists\nDROP INDEX IF EXISTS idx_users_email;\n</code></pre>\n<p>Dropping an index does not affect the data in the table — it only removes the index structure. Queries that previously used the index will still work, but may run slower without it.</p>\n<h2 id=\"drop-view\"><a class=\"header\" href=\"#drop-view\">DROP VIEW</a></h2>\n<p>Removes a view definition from the database. Since views do not store data, no data is deleted.</p>\n<pre><code class=\"language-sql\">-- Remove a view\nDROP VIEW active_users;\n\n-- Remove a view only if it exists\nDROP VIEW IF EXISTS active_users;\n</code></pre>\n<h2 id=\"drop-trigger\"><a class=\"header\" href=\"#drop-trigger\">DROP TRIGGER</a></h2>\n<p>Removes a trigger from the database.</p>\n<pre><code class=\"language-sql\">-- Remove a trigger\nDROP TRIGGER audit_insert;\n\n-- Remove a trigger only if it exists\nDROP TRIGGER IF EXISTS audit_insert;\n</code></pre>\n<h2 id=\"examples-17\"><a class=\"header\" href=\"#examples-17\">Examples</a></h2>\n<pre><code class=\"language-sql\">-- Create a table with an index, then drop both\nCREATE TABLE products (\n  id INTEGER PRIMARY KEY,\n  name TEXT NOT NULL,\n  price REAL\n);\nCREATE INDEX idx_products_name ON products(name);\n\n-- Drop the index first, then the table\nDROP INDEX idx_products_name;\nDROP TABLE products;\n</code></pre>\n<pre><code class=\"language-sql\">-- Safely clean up objects that may or may not exist\nDROP VIEW IF EXISTS sales_summary;\nDROP TABLE IF EXISTS orders;\nDROP TABLE IF EXISTS customers;\n</code></pre>\n<pre><code class=\"language-sql\">-- Drop and recreate a table (reset)\nDROP TABLE IF EXISTS temp_results;\nCREATE TABLE temp_results (id INTEGER PRIMARY KEY, value TEXT);\n</code></pre>\n<h2 id=\"compatibility-13\"><a class=\"header\" href=\"#compatibility-13\">Compatibility</a></h2>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Feature</th><th>Status</th></tr>\n</thead>\n<tbody>\n<tr><td>DROP TABLE</td><td>Supported</td></tr>\n<tr><td>DROP INDEX</td><td>Supported</td></tr>\n<tr><td>DROP VIEW</td><td>Supported</td></tr>\n<tr><td>DROP TRIGGER</td><td>Requires <code>--experimental-triggers</code> flag</td></tr>\n<tr><td>IF EXISTS</td><td>Supported</td></tr>\n</tbody>\n</table>\n</div>\n<div style=\"break-before: page; page-break-before: always;\"></div>\n<h1 id=\"transactions\"><a class=\"header\" href=\"#transactions\">Transactions</a></h1>\n<h2 id=\"syntax-18\"><a class=\"header\" href=\"#syntax-18\">Syntax</a></h2>\n<pre><code class=\"language-sql\">BEGIN [DEFERRED | IMMEDIATE | EXCLUSIVE] [TRANSACTION]\n\nCOMMIT [TRANSACTION]\n\nEND [TRANSACTION]\n\nROLLBACK [TRANSACTION]\n</code></pre>\n<h2 id=\"description-18\"><a class=\"header\" href=\"#description-18\">Description</a></h2>\n<p>A transaction is a sequence of SQL statements that are executed as a single\natomic unit. Either all statements in the transaction complete successfully and\ntheir changes are made permanent, or none of them take effect. Transactions\nguarantee that the database moves from one consistent state to another, even in\nthe presence of errors or unexpected termination.</p>\n<p>Turso supports three commands for explicit transaction control: <code>BEGIN</code> starts a\nnew transaction, <code>COMMIT</code> (or its alias <code>END</code>) makes all changes within the\ntransaction permanent, and <code>ROLLBACK</code> discards all changes made since the\ntransaction began.</p>\n<p>Transactions in Turso do not nest. Issuing <code>BEGIN</code> while a transaction is\nalready active will produce an error. For the same reason, issuing <code>COMMIT</code> or\n<code>ROLLBACK</code> outside of a transaction will also produce an error.</p>\n<h2 id=\"autocommit-mode\"><a class=\"header\" href=\"#autocommit-mode\">Autocommit Mode</a></h2>\n<p>When no explicit transaction is active, Turso operates in <strong>autocommit mode</strong>.\nIn this mode every individual SQL statement that reads from or writes to the\ndatabase is automatically wrapped in its own implicit transaction. The implicit\ntransaction is committed as soon as the statement finishes executing.</p>\n<p>This means a single <code>INSERT</code>, <code>UPDATE</code>, or <code>DELETE</code> statement executed outside\nof an explicit transaction is atomic by itself – it either fully succeeds or\nhas no effect. However, if you need multiple statements to succeed or fail\ntogether, you must wrap them in an explicit <code>BEGIN</code> … <code>COMMIT</code> block.</p>\n<pre><code class=\"language-sql\">-- Autocommit mode: each statement is its own transaction\nCREATE TABLE orders (id INTEGER PRIMARY KEY, product TEXT, qty INTEGER);\nINSERT INTO orders VALUES (1, 'Widget', 10);\nINSERT INTO orders VALUES (2, 'Gadget', 5);\n-- Both rows are committed independently\n</code></pre>\n<h2 id=\"transaction-types\"><a class=\"header\" href=\"#transaction-types\">Transaction Types</a></h2>\n<p>The <code>BEGIN</code> statement accepts an optional keyword that controls when the\ntransaction acquires its lock on the database.</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Type</th><th>Behavior</th></tr>\n</thead>\n<tbody>\n<tr><td>DEFERRED</td><td>The default. The transaction does not acquire any lock until the database is first accessed. A read statement starts a read transaction; a write statement starts a write transaction.</td></tr>\n<tr><td>IMMEDIATE</td><td>A write lock is acquired immediately when <code>BEGIN IMMEDIATE</code> is executed, without waiting for the first write statement. This guarantees that no other connection can write to the database while this transaction is open.</td></tr>\n<tr><td>EXCLUSIVE</td><td>Behaves the same as IMMEDIATE under WAL mode, which is the journaling mode used by Turso. A write lock is acquired immediately.</td></tr>\n</tbody>\n</table>\n</div>\n<p>When the transaction type is omitted, <code>DEFERRED</code> is assumed.</p>\n<h3 id=\"deferred\"><a class=\"header\" href=\"#deferred\">DEFERRED</a></h3>\n<p>A deferred transaction does not acquire any database lock when <code>BEGIN</code> is\nexecuted. The lock is acquired lazily the first time the database is actually\nread from or written to within the transaction. If the first operation is a\n<code>SELECT</code>, a read lock is acquired. If the first operation is an <code>INSERT</code>,\n<code>UPDATE</code>, <code>DELETE</code>, or other write statement, a write lock is acquired.</p>\n<pre><code class=\"language-sql\">-- DEFERRED is the default; these two are equivalent\nBEGIN TRANSACTION;\n-- ...\nCOMMIT;\n\nBEGIN DEFERRED TRANSACTION;\n-- ...\nCOMMIT;\n</code></pre>\n<h3 id=\"immediate\"><a class=\"header\" href=\"#immediate\">IMMEDIATE</a></h3>\n<p>An immediate transaction acquires a write lock as soon as <code>BEGIN IMMEDIATE</code> is\nexecuted. This is useful when you know the transaction will perform writes,\nbecause it avoids a potential conflict that could occur if a deferred\ntransaction tries to upgrade from a read lock to a write lock after another\nconnection has already started writing.</p>\n<pre><code class=\"language-sql\">BEGIN IMMEDIATE TRANSACTION;\n-- Write lock is held from this point\nINSERT INTO orders (product, qty) VALUES ('Keyboard', 75);\nCOMMIT;\n</code></pre>\n<h3 id=\"exclusive\"><a class=\"header\" href=\"#exclusive\">EXCLUSIVE</a></h3>\n<p>Under WAL mode – the mode Turso uses – <code>EXCLUSIVE</code> behaves identically to\n<code>IMMEDIATE</code>. Both acquire a write lock immediately. In other journaling modes\n(not used by Turso), <code>EXCLUSIVE</code> would additionally prevent other connections\nfrom reading the database, but this distinction does not apply here.</p>\n<pre><code class=\"language-sql\">BEGIN EXCLUSIVE;\nINSERT INTO orders (product, qty) VALUES ('Mouse', 200);\nEND;\n</code></pre>\n<h2 id=\"commit-and-end\"><a class=\"header\" href=\"#commit-and-end\">COMMIT and END</a></h2>\n<p><code>COMMIT</code> makes all changes performed within the current transaction permanent.\n<code>END</code> is an alias for <code>COMMIT</code>; they are interchangeable.</p>\n<p>The optional <code>TRANSACTION</code> keyword after <code>COMMIT</code> or <code>END</code> is purely\ndecorative and has no effect on behavior.</p>\n<pre><code class=\"language-sql\">-- These are all equivalent\nCOMMIT;\nCOMMIT TRANSACTION;\nEND;\nEND TRANSACTION;\n</code></pre>\n<h2 id=\"rollback\"><a class=\"header\" href=\"#rollback\">ROLLBACK</a></h2>\n<p><code>ROLLBACK</code> discards all changes made within the current transaction. The\ndatabase is restored to the state it was in before <code>BEGIN</code> was executed.</p>\n<p>The optional <code>TRANSACTION</code> keyword after <code>ROLLBACK</code> is purely decorative and\nhas no effect on behavior.</p>\n<pre><code class=\"language-sql\">CREATE TABLE accounts (id INTEGER PRIMARY KEY, name TEXT, balance REAL);\nINSERT INTO accounts VALUES (1, 'Alice', 1000.00);\nINSERT INTO accounts VALUES (2, 'Bob', 500.00);\n\nBEGIN;\nUPDATE accounts SET balance = balance - 200.00 WHERE name = 'Alice';\nUPDATE accounts SET balance = balance + 200.00 WHERE name = 'Bob';\n-- At this point, Alice has 800 and Bob has 700 within the transaction\nROLLBACK;\n\nSELECT * FROM accounts;\n-- Alice still has 1000.00 and Bob still has 500.00\n</code></pre>\n<h2 id=\"examples-18\"><a class=\"header\" href=\"#examples-18\">Examples</a></h2>\n<h3 id=\"atomic-money-transfer\"><a class=\"header\" href=\"#atomic-money-transfer\">Atomic money transfer</a></h3>\n<p>Wrapping related updates in a transaction ensures that a transfer between\naccounts either fully completes or has no effect.</p>\n<pre><code class=\"language-sql\">CREATE TABLE accounts (id INTEGER PRIMARY KEY, name TEXT, balance REAL);\nINSERT INTO accounts VALUES (1, 'Alice', 1000.00);\nINSERT INTO accounts VALUES (2, 'Bob', 500.00);\n\nBEGIN TRANSACTION;\nUPDATE accounts SET balance = balance - 200.00 WHERE name = 'Alice';\nUPDATE accounts SET balance = balance + 200.00 WHERE name = 'Bob';\nCOMMIT;\n\nSELECT * FROM accounts;\n-- 1|Alice|800.0\n-- 2|Bob|700.0\n</code></pre>\n<h3 id=\"batch-inserts-within-a-transaction\"><a class=\"header\" href=\"#batch-inserts-within-a-transaction\">Batch inserts within a transaction</a></h3>\n<p>Grouping multiple inserts into a single transaction is significantly faster\nthan executing each insert in autocommit mode, because the database only needs\nto sync to disk once at <code>COMMIT</code> rather than after every individual statement.</p>\n<pre><code class=\"language-sql\">CREATE TABLE inventory (id INTEGER PRIMARY KEY, product TEXT NOT NULL, qty INTEGER DEFAULT 0);\n\nBEGIN;\nINSERT INTO inventory (product, qty) VALUES ('Laptop', 50);\nINSERT INTO inventory (product, qty) VALUES ('Mouse', 200);\nINSERT INTO inventory (product, qty) VALUES ('Keyboard', 75);\nCOMMIT;\n\nSELECT * FROM inventory;\n-- 1|Laptop|50\n-- 2|Mouse|200\n-- 3|Keyboard|75\n</code></pre>\n<h3 id=\"rolling-back-on-error\"><a class=\"header\" href=\"#rolling-back-on-error\">Rolling back on error</a></h3>\n<p>If something goes wrong during a sequence of operations, <code>ROLLBACK</code> ensures\nthat partially applied changes do not corrupt the database.</p>\n<pre><code class=\"language-sql\">CREATE TABLE orders (id INTEGER PRIMARY KEY, product TEXT, qty INTEGER);\n\nBEGIN;\nINSERT INTO orders VALUES (1, 'Widget', 10);\nINSERT INTO orders VALUES (2, 'Gadget', 5);\n-- Decide to discard these changes\nROLLBACK;\n\nSELECT count(*) FROM orders;\n-- 0\n</code></pre>\n<h3 id=\"using-immediate-for-write-heavy-work\"><a class=\"header\" href=\"#using-immediate-for-write-heavy-work\">Using IMMEDIATE for write-heavy work</a></h3>\n<p>When you know a transaction will write to the database, starting with\n<code>BEGIN IMMEDIATE</code> avoids the overhead and potential failure of upgrading a read\nlock to a write lock mid-transaction.</p>\n<pre><code class=\"language-sql\">CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, price REAL);\n\nBEGIN IMMEDIATE;\nINSERT INTO products VALUES (1, 'Widget', 9.99);\nINSERT INTO products VALUES (2, 'Gadget', 24.95);\nEND;\n\nSELECT * FROM products;\n-- 1|Widget|9.99\n-- 2|Gadget|24.95\n</code></pre>\n<h3 id=\"end-as-an-alias-for-commit\"><a class=\"header\" href=\"#end-as-an-alias-for-commit\">END as an alias for COMMIT</a></h3>\n<p><code>END</code> and <code>COMMIT</code> are interchangeable. Use whichever reads more naturally in\nyour application.</p>\n<pre><code class=\"language-sql\">CREATE TABLE events (id INTEGER PRIMARY KEY, description TEXT);\n\nBEGIN DEFERRED TRANSACTION;\nINSERT INTO events (description) VALUES ('user_login');\nINSERT INTO events (description) VALUES ('page_view');\nEND TRANSACTION;\n\nSELECT * FROM events;\n-- 1|user_login\n-- 2|page_view\n</code></pre>\n<h2 id=\"compatibility-14\"><a class=\"header\" href=\"#compatibility-14\">Compatibility</a></h2>\n<p>Turso supports <code>BEGIN</code>, <code>COMMIT</code> / <code>END</code>, and <code>ROLLBACK</code> with the same syntax\nand semantics as SQLite. The <code>TRANSACTION</code> keyword is optional in all three\ncommands, matching SQLite behavior.</p>\n<p><code>SAVEPOINT</code> and <code>RELEASE SAVEPOINT</code> are not supported. Nested transactions\nusing savepoints are not available.</p>\n<p>Turso operates exclusively in WAL (Write-Ahead Logging) mode. As a result,\n<code>BEGIN EXCLUSIVE</code> and <code>BEGIN IMMEDIATE</code> behave identically – both acquire a\nwrite lock immediately. The distinction between these two modes that exists in\nother SQLite journaling modes does not apply.</p>\n<div style=\"break-before: page; page-break-before: always;\"></div>\n<h1 id=\"explain\"><a class=\"header\" href=\"#explain\">EXPLAIN</a></h1>\n<h2 id=\"syntax-19\"><a class=\"header\" href=\"#syntax-19\">Syntax</a></h2>\n<pre><code class=\"language-sql\">EXPLAIN sql-statement\n\nEXPLAIN QUERY PLAN sql-statement\n</code></pre>\n<h2 id=\"description-19\"><a class=\"header\" href=\"#description-19\">Description</a></h2>\n<p>The <code>EXPLAIN</code> statement is a diagnostic tool for understanding how Turso executes a SQL statement. It does not run the statement itself. Instead, it returns metadata about the execution strategy.</p>\n<p>There are two forms. <code>EXPLAIN</code> returns the full sequence of virtual machine (VDBE) bytecode instructions that Turso would execute for the given statement. <code>EXPLAIN QUERY PLAN</code> returns a high-level summary of the query plan, showing which tables and indexes are accessed and in what order. Both forms are intended for interactive analysis, debugging, and performance tuning.</p>\n<p>The <code>EXPLAIN</code> prefix can be applied to any SQL statement, including <code>SELECT</code>, <code>INSERT</code>, <code>UPDATE</code>, <code>DELETE</code>, <code>CREATE TABLE</code>, and others. The prefixed statement is compiled but never executed, so it has no side effects.</p>\n<h2 id=\"output-columns\"><a class=\"header\" href=\"#output-columns\">Output Columns</a></h2>\n<h3 id=\"explain-1\"><a class=\"header\" href=\"#explain-1\">EXPLAIN</a></h3>\n<p><code>EXPLAIN</code> returns one row per bytecode instruction. Each row has eight columns:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Column</th><th>Type</th><th>Description</th></tr>\n</thead>\n<tbody>\n<tr><td><code>addr</code></td><td>INTEGER</td><td>The instruction address (sequential, starting at 0).</td></tr>\n<tr><td><code>opcode</code></td><td>TEXT</td><td>The name of the VDBE opcode (e.g., <code>Init</code>, <code>OpenRead</code>, <code>Column</code>, <code>ResultRow</code>).</td></tr>\n<tr><td><code>p1</code></td><td>INTEGER</td><td>First operand. Meaning varies by opcode.</td></tr>\n<tr><td><code>p2</code></td><td>INTEGER</td><td>Second operand. Often a jump target or register number.</td></tr>\n<tr><td><code>p3</code></td><td>INTEGER</td><td>Third operand.</td></tr>\n<tr><td><code>p4</code></td><td>TEXT</td><td>Fourth operand. May contain a string constant, function name, key format, or table/index name.</td></tr>\n<tr><td><code>p5</code></td><td>INTEGER</td><td>Fifth operand. Typically contains flags.</td></tr>\n<tr><td><code>comment</code></td><td>TEXT</td><td>A human-readable comment describing what the instruction does.</td></tr>\n</tbody>\n</table>\n</div>\n<p>The bytecode format is an internal implementation detail and may change between Turso releases. Do not write application logic that depends on specific opcodes or instruction sequences.</p>\n<h3 id=\"explain-query-plan\"><a class=\"header\" href=\"#explain-query-plan\">EXPLAIN QUERY PLAN</a></h3>\n<p><code>EXPLAIN QUERY PLAN</code> returns one row per step in the query plan. Each row has four columns:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Column</th><th>Type</th><th>Description</th></tr>\n</thead>\n<tbody>\n<tr><td><code>id</code></td><td>INTEGER</td><td>A unique identifier for this node in the plan tree.</td></tr>\n<tr><td><code>parent</code></td><td>INTEGER</td><td>The <code>id</code> of the parent node (0 for root nodes).</td></tr>\n<tr><td><code>notused</code></td><td>INTEGER</td><td>Reserved. Always 0.</td></tr>\n<tr><td><code>detail</code></td><td>TEXT</td><td>A human-readable description of the operation at this step.</td></tr>\n</tbody>\n</table>\n</div>\n<p>The <code>detail</code> column contains the most useful information. Common values include:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Detail prefix</th><th>Meaning</th></tr>\n</thead>\n<tbody>\n<tr><td><code>SCAN table-name</code></td><td>A full table scan with no index.</td></tr>\n<tr><td><code>SEARCH table-name USING INDEX index-name</code></td><td>An indexed lookup.</td></tr>\n<tr><td><code>SEARCH table-name USING INTEGER PRIMARY KEY (rowid=?)</code></td><td>A direct rowid lookup via the primary key.</td></tr>\n<tr><td><code>USE TEMP B-TREE FOR ORDER BY</code></td><td>A temporary structure is used to sort results.</td></tr>\n<tr><td><code>SCAN CONSTANT ROW</code></td><td>A row is produced from constant values (no table access).</td></tr>\n</tbody>\n</table>\n</div>\n<p>When a query involves multiple tables (joins, subqueries), <code>EXPLAIN QUERY PLAN</code> returns multiple rows showing the access order and method for each table.</p>\n<h2 id=\"interpreting-query-plans\"><a class=\"header\" href=\"#interpreting-query-plans\">Interpreting Query Plans</a></h2>\n<p>The output of <code>EXPLAIN QUERY PLAN</code> is the primary tool for diagnosing slow queries. The key things to look for:</p>\n<ul>\n<li><strong>SCAN vs. SEARCH</strong>: A <code>SCAN</code> reads every row in the table. A <code>SEARCH</code> uses an index to jump directly to matching rows. If a query is slow, look for unexpected <code>SCAN</code> operations on large tables and consider adding an index.</li>\n<li><strong>Index usage</strong>: When an index is used, the detail line names it. Verify that the expected index is being chosen.</li>\n<li><strong>Temporary B-trees</strong>: Lines mentioning <code>USE TEMP B-TREE</code> indicate that Turso must build a temporary data structure (for sorting, grouping, or deduplication). This is normal for <code>ORDER BY</code> on non-indexed columns but can be a performance concern for large result sets.</li>\n<li><strong>Join order</strong>: In multi-table queries, the rows appear in the order that tables are accessed. The first table listed is the outer loop; subsequent tables are inner loops. The optimizer chooses the join order it estimates to be fastest.</li>\n</ul>\n<h2 id=\"examples-19\"><a class=\"header\" href=\"#examples-19\">Examples</a></h2>\n<h3 id=\"bytecode-for-a-simple-query\"><a class=\"header\" href=\"#bytecode-for-a-simple-query\">Bytecode for a Simple Query</a></h3>\n<pre><code class=\"language-sql\">EXPLAIN SELECT 1;\n-- addr  opcode             p1    p2    p3    p4             p5  comment\n-- ----  -----------------  ----  ----  ----  -------------  --  -------\n-- 0     Init               0     3     0                    0   Start at 3\n-- 1     ResultRow          1     1     0                    0   output=r[1]\n-- 2     Halt               0     0     0                    0\n-- 3     Integer            1     1     0                    0   r[1]=1\n-- 4     Goto               0     1     0                    0\n</code></pre>\n<p>The <code>Init</code> instruction jumps to address 3, where the integer <code>1</code> is loaded into register 1. Control then jumps to address 1, which outputs register 1 as a result row. Finally, <code>Halt</code> terminates execution.</p>\n<h3 id=\"query-plan-for-a-full-table-scan\"><a class=\"header\" href=\"#query-plan-for-a-full-table-scan\">Query Plan for a Full Table Scan</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE orders (\n  id INTEGER PRIMARY KEY,\n  customer_id INTEGER,\n  product TEXT,\n  amount REAL\n);\n\nEXPLAIN QUERY PLAN SELECT * FROM orders;\n-- QUERY PLAN\n-- `--SCAN orders\n</code></pre>\n<p>With no <code>WHERE</code> clause and no index needed, Turso performs a full scan of the <code>orders</code> table.</p>\n<h3 id=\"query-plan-with-index-usage\"><a class=\"header\" href=\"#query-plan-with-index-usage\">Query Plan with Index Usage</a></h3>\n<pre><code class=\"language-sql\">CREATE INDEX idx_orders_customer ON orders(customer_id);\n\nEXPLAIN QUERY PLAN SELECT id, product, amount\nFROM orders WHERE customer_id = 42;\n-- QUERY PLAN\n-- `--SEARCH orders USING INDEX idx_orders_customer\n</code></pre>\n<p>The <code>SEARCH</code> line shows that Turso uses the <code>idx_orders_customer</code> index to find rows where <code>customer_id = 42</code>, avoiding a full table scan.</p>\n<h3 id=\"query-plan-with-primary-key-lookup\"><a class=\"header\" href=\"#query-plan-with-primary-key-lookup\">Query Plan with Primary Key Lookup</a></h3>\n<pre><code class=\"language-sql\">EXPLAIN QUERY PLAN SELECT * FROM orders WHERE id = 10;\n-- QUERY PLAN\n-- `--SEARCH orders USING INTEGER PRIMARY KEY (rowid=?)\n</code></pre>\n<p>When filtering by the <code>INTEGER PRIMARY KEY</code>, Turso performs a direct rowid lookup, which is the fastest possible access method.</p>\n<h3 id=\"query-plan-for-a-join\"><a class=\"header\" href=\"#query-plan-for-a-join\">Query Plan for a Join</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE customers (\n  id INTEGER PRIMARY KEY,\n  name TEXT,\n  email TEXT\n);\n\nEXPLAIN QUERY PLAN SELECT c.name, o.product, o.amount\nFROM customers c\nJOIN orders o ON c.id = o.customer_id\nWHERE c.name = 'Alice';\n-- QUERY PLAN\n-- |--SCAN customers AS c\n-- `--SEARCH o USING INDEX idx_orders_customer\n</code></pre>\n<p>Turso scans the <code>customers</code> table (the outer loop), and for each matching customer, uses the index on <code>orders(customer_id)</code> to find their orders (the inner loop).</p>\n<h3 id=\"query-plan-with-group-by-and-order-by\"><a class=\"header\" href=\"#query-plan-with-group-by-and-order-by\">Query Plan with GROUP BY and ORDER BY</a></h3>\n<pre><code class=\"language-sql\">EXPLAIN QUERY PLAN SELECT product, SUM(amount)\nFROM orders\nGROUP BY product\nORDER BY SUM(amount) DESC;\n-- QUERY PLAN\n-- |--SCAN orders\n-- `--USE TEMP B-TREE FOR ORDER BY\n</code></pre>\n<p>The plan shows a full table scan to read and group the data, followed by a temporary B-tree to sort the grouped results by the aggregate value.</p>\n<h3 id=\"bytecode-for-an-insert\"><a class=\"header\" href=\"#bytecode-for-an-insert\">Bytecode for an INSERT</a></h3>\n<pre><code class=\"language-sql\">EXPLAIN INSERT INTO orders VALUES (1, 42, 'Widget', 9.99);\n-- addr  opcode             p1    p2    p3    p4             p5  comment\n-- ----  -----------------  ----  ----  ----  -------------  --  -------\n-- 0     Init               0     20    0                    0   Start at 20\n-- 1     OpenWrite          0     2     0                    0   root=2; iDb=0\n-- 2     Integer            1     2     0                    0   r[2]=1\n-- 3     SoftNull           3     0     0                    0\n-- 4     Integer            42    4     0                    0   r[4]=42\n-- 5     String8            0     5     0     Widget         0   r[5]='Widget'\n-- 6     Real               0     6     0     9.99           0   r[6]=9.99\n-- 7     NotNull            2     9     0                    0   r[2]!=NULL -&gt; goto 9\n-- 8     Goto               0     11    0                    0\n-- 9     MustBeInt          2     0     0                    0\n-- 10    Goto               0     12    0                    0\n-- 11    NewRowid           0     2     0                    0   r[2]=rowid\n-- 12    Affinity           3     4     0                    0   r[3..7] = D, D, B, E\n-- 13    NotExists          0     15    2                    0\n-- 14    Halt               1555  0     0     orders.id      0\n-- 15    MakeRecord         3     4     7                    0   r[7]=mkrec(r[3..6])\n-- 16    Insert             0     7     2     orders         0   intkey=r[2] data=r[7]\n-- 17    Goto               0     18    0                    0\n-- 18    Goto               0     19    0                    0\n-- 19    Halt               0     0     0                    0\n-- 20    Transaction        0     2     1                    0   iDb=0 tx_mode=Write\n-- 21    Goto               0     1     0                    0\n</code></pre>\n<p>The bytecode shows the full instruction sequence: opening the table for writing, loading the values into registers, checking for primary key conflicts, assembling the record, and inserting it.</p>\n<div style=\"break-before: page; page-break-before: always;\"></div>\n<h1 id=\"operators\"><a class=\"header\" href=\"#operators\">Operators</a></h1>\n<p>Operators perform arithmetic, comparison, logical, bitwise, and string operations on values in SQL expressions. Turso supports the full set of SQLite operators, including the standard SQL <code>IS DISTINCT FROM</code> and <code>IS NOT DISTINCT FROM</code> forms.</p>\n<h2 id=\"operator-precedence\"><a class=\"header\" href=\"#operator-precedence\">Operator Precedence</a></h2>\n<p>When an expression contains multiple operators, precedence determines the order of evaluation. Operators with higher precedence bind more tightly. Operators at the same precedence level are left-associative (evaluated left to right). Parentheses override precedence.</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Precedence</th><th>Operators</th><th>Category</th></tr>\n</thead>\n<tbody>\n<tr><td>1 (highest)</td><td><code>~</code> <code>+</code> <code>-</code></td><td>Unary bitwise NOT, unary plus, unary minus</td></tr>\n<tr><td>2</td><td><code>||</code></td><td>String concatenation</td></tr>\n<tr><td>3</td><td><code>*</code> <code>/</code> <code>%</code></td><td>Multiplication, division, modulo</td></tr>\n<tr><td>4</td><td><code>+</code> <code>-</code></td><td>Addition, subtraction</td></tr>\n<tr><td>5</td><td><code>&amp;</code> <code>|</code> <code>&lt;&lt;</code> <code>&gt;&gt;</code></td><td>Bitwise AND, OR, left shift, right shift</td></tr>\n<tr><td>6</td><td><code>&lt;</code> <code>&gt;</code> <code>&lt;=</code> <code>&gt;=</code></td><td>Relational comparison</td></tr>\n<tr><td>7</td><td><code>=</code> <code>==</code> <code>&lt;&gt;</code> <code>IS</code> <code>IS NOT</code> <code>IS DISTINCT FROM</code> <code>IS NOT DISTINCT FROM</code></td><td>Equality and identity</td></tr>\n<tr><td>8</td><td><code>NOT</code></td><td>Logical NOT (unary)</td></tr>\n<tr><td>9</td><td><code>AND</code></td><td>Logical AND</td></tr>\n<tr><td>10 (lowest)</td><td><code>OR</code></td><td>Logical OR</td></tr>\n</tbody>\n</table>\n</div>\n<pre><code class=\"language-sql\">-- Multiplication binds tighter than addition\nSELECT 2 + 3 * 4;    -- 14  (not 20)\nSELECT (2 + 3) * 4;  -- 20  (parentheses override)\n\n-- AND binds tighter than OR\nSELECT 1 OR 0 AND 0;    -- 1  (equivalent to: 1 OR (0 AND 0))\n\n-- NOT binds tighter than AND\nSELECT NOT 1 AND 0;     -- 0  (equivalent to: (NOT 1) AND 0)\n\n-- Left associativity\nSELECT 10 - 2 - 3;      -- 5  (equivalent to: (10 - 2) - 3)\n</code></pre>\n<h2 id=\"unary-operators\"><a class=\"header\" href=\"#unary-operators\">Unary Operators</a></h2>\n<p>Unary operators take a single operand. They have the highest precedence of all operators.</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Operator</th><th>Description</th></tr>\n</thead>\n<tbody>\n<tr><td><code>-expr</code></td><td>Negates the numeric value of <code>expr</code>.</td></tr>\n<tr><td><code>+expr</code></td><td>No-op. Returns the value of <code>expr</code> unchanged.</td></tr>\n<tr><td><code>~expr</code></td><td>Bitwise NOT. Inverts every bit of the integer value of <code>expr</code>.</td></tr>\n<tr><td><code>NOT expr</code></td><td>Logical NOT. Returns 1 if <code>expr</code> is 0, returns 0 if <code>expr</code> is non-zero, returns NULL if <code>expr</code> is NULL.</td></tr>\n</tbody>\n</table>\n</div>\n<pre><code class=\"language-sql\">SELECT -5;       -- -5\nSELECT +5;       -- 5\nSELECT ~5;       -- -6  (inverts all bits of integer 5)\nSELECT NOT 1;    -- 0\nSELECT NOT 0;    -- 1\nSELECT NOT NULL;  -- NULL\n</code></pre>\n<h2 id=\"arithmetic-operators\"><a class=\"header\" href=\"#arithmetic-operators\">Arithmetic Operators</a></h2>\n<p>Arithmetic operators perform numeric calculations. When both operands are integers, the result is an integer. When either operand is a real (floating-point) value, the result is a real.</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Operator</th><th>Description</th></tr>\n</thead>\n<tbody>\n<tr><td><code>expr1 + expr2</code></td><td>Addition.</td></tr>\n<tr><td><code>expr1 - expr2</code></td><td>Subtraction.</td></tr>\n<tr><td><code>expr1 * expr2</code></td><td>Multiplication.</td></tr>\n<tr><td><code>expr1 / expr2</code></td><td>Division. Integer division truncates toward zero.</td></tr>\n<tr><td><code>expr1 % expr2</code></td><td>Modulo. Returns the remainder after integer division. Both operands are cast to integers.</td></tr>\n</tbody>\n</table>\n</div>\n<pre><code class=\"language-sql\">SELECT 5 + 3;      -- 8\nSELECT 10 - 4;     -- 6\nSELECT 6 * 7;      -- 42\nSELECT 15 / 4;     -- 3    (integer division truncates)\nSELECT 7.0 / 2;    -- 3.5  (real division when either operand is real)\nSELECT 2.5 + 1.5;  -- 4.0\nSELECT 17 % 5;     -- 2\nSELECT -7 % 3;     -- -1\n</code></pre>\n<h3 id=\"arithmetic-with-null\"><a class=\"header\" href=\"#arithmetic-with-null\">Arithmetic with NULL</a></h3>\n<p>Any arithmetic operation involving NULL produces NULL:</p>\n<pre><code class=\"language-sql\">SELECT 5 + NULL;  -- NULL\n</code></pre>\n<h3 id=\"realistic-example\"><a class=\"header\" href=\"#realistic-example\">Realistic Example</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE products (name TEXT, price REAL, quantity INTEGER);\nINSERT INTO products VALUES ('Widget', 9.99, 100),\n                            ('Gadget', 24.50, 50),\n                            ('Gizmo', 4.75, 200);\n\nSELECT name, price * quantity AS total_value FROM products;\n-- Widget|999.0\n-- Gadget|1225.0\n-- Gizmo|950.0\n</code></pre>\n<h2 id=\"string-concatenation\"><a class=\"header\" href=\"#string-concatenation\">String Concatenation</a></h2>\n<p>The <code>||</code> operator joins two values into a single text string. Non-text operands are converted to text before concatenation. If either operand is NULL, the result is NULL.</p>\n<pre><code class=\"language-sql\">SELECT 'Hello' || ' ' || 'World';  -- Hello World\nSELECT 'Order #' || 42;            -- Order #42\nSELECT 'text' || NULL;             -- NULL\n</code></pre>\n<h3 id=\"realistic-example-1\"><a class=\"header\" href=\"#realistic-example-1\">Realistic Example</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE users (first_name TEXT, last_name TEXT);\nINSERT INTO users VALUES ('Alice', 'Smith'), ('Bob', 'Jones');\n\nSELECT first_name || ' ' || last_name AS full_name FROM users;\n-- Alice Smith\n-- Bob Jones\n</code></pre>\n<h2 id=\"comparison-operators\"><a class=\"header\" href=\"#comparison-operators\">Comparison Operators</a></h2>\n<p>Comparison operators compare two values and return an integer: 1 for true, 0 for false. When either operand is NULL, standard comparison operators return NULL (with exceptions noted below).</p>\n<h3 id=\"equality\"><a class=\"header\" href=\"#equality\">Equality</a></h3>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Operator</th><th>Description</th></tr>\n</thead>\n<tbody>\n<tr><td><code>expr1 = expr2</code></td><td>True if operands are equal.</td></tr>\n<tr><td><code>expr1 == expr2</code></td><td>Synonym for <code>=</code>.</td></tr>\n<tr><td><code>expr1 &lt;&gt; expr2</code></td><td>True if operands are not equal.</td></tr>\n</tbody>\n</table>\n</div>\n<pre><code class=\"language-sql\">SELECT 1 = 1;    -- 1\nSELECT 1 == 1;   -- 1\nSELECT 1 &lt;&gt; 2;   -- 1\n</code></pre>\n<h3 id=\"relational\"><a class=\"header\" href=\"#relational\">Relational</a></h3>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Operator</th><th>Description</th></tr>\n</thead>\n<tbody>\n<tr><td><code>expr1 &lt; expr2</code></td><td>True if <code>expr1</code> is less than <code>expr2</code>.</td></tr>\n<tr><td><code>expr1 &gt; expr2</code></td><td>True if <code>expr1</code> is greater than <code>expr2</code>.</td></tr>\n<tr><td><code>expr1 &lt;= expr2</code></td><td>True if <code>expr1</code> is less than or equal to <code>expr2</code>.</td></tr>\n<tr><td><code>expr1 &gt;= expr2</code></td><td>True if <code>expr1</code> is greater than or equal to <code>expr2</code>.</td></tr>\n</tbody>\n</table>\n</div>\n<pre><code class=\"language-sql\">SELECT 3 &lt; 5;    -- 1\nSELECT 5 &gt; 3;    -- 1\nSELECT 3 &lt;= 3;   -- 1\nSELECT 3 &gt;= 3;   -- 1\n</code></pre>\n<h3 id=\"null-and-equality\"><a class=\"header\" href=\"#null-and-equality\">NULL and Equality</a></h3>\n<p>Standard comparison operators return NULL when either operand is NULL, because NULL represents an unknown value:</p>\n<pre><code class=\"language-sql\">SELECT NULL = NULL;    -- NULL  (not 1!)\nSELECT NULL &lt;&gt; NULL;   -- NULL  (not 1!)\n</code></pre>\n<p>This is why <code>WHERE column = NULL</code> never matches any rows. Use <code>IS NULL</code> instead.</p>\n<h3 id=\"is-and-is-not\"><a class=\"header\" href=\"#is-and-is-not\">IS and IS NOT</a></h3>\n<p>The <code>IS</code> and <code>IS NOT</code> operators work like <code>=</code> and <code>&lt;&gt;</code> but handle NULL deterministically: they never return NULL.</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Left</th><th>Right</th><th><code>IS</code></th><th><code>IS NOT</code></th></tr>\n</thead>\n<tbody>\n<tr><td>NULL</td><td>NULL</td><td>1</td><td>0</td></tr>\n<tr><td>NULL</td><td>non-NULL</td><td>0</td><td>1</td></tr>\n<tr><td>non-NULL</td><td>NULL</td><td>0</td><td>1</td></tr>\n<tr><td>value</td><td>value</td><td>same as <code>=</code></td><td>same as <code>&lt;&gt;</code></td></tr>\n</tbody>\n</table>\n</div>\n<pre><code class=\"language-sql\">SELECT NULL IS NULL;       -- 1\nSELECT 5 IS NOT NULL;      -- 1\nSELECT 5 IS NULL;          -- 0\nSELECT NULL IS NOT NULL;   -- 0\n</code></pre>\n<h3 id=\"is-distinct-from-and-is-not-distinct-from\"><a class=\"header\" href=\"#is-distinct-from-and-is-not-distinct-from\">IS DISTINCT FROM and IS NOT DISTINCT FROM</a></h3>\n<p>These are standard SQL aliases for <code>IS NOT</code> and <code>IS</code>, respectively. They are useful for writing portable SQL that avoids the compact SQLite-specific <code>IS</code>/<code>IS NOT</code> syntax.</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Expression</th><th>Equivalent To</th></tr>\n</thead>\n<tbody>\n<tr><td><code>a IS DISTINCT FROM b</code></td><td><code>a IS NOT b</code></td></tr>\n<tr><td><code>a IS NOT DISTINCT FROM b</code></td><td><code>a IS b</code></td></tr>\n</tbody>\n</table>\n</div>\n<pre><code class=\"language-sql\">SELECT NULL IS NOT DISTINCT FROM NULL;  -- 1  (equivalent to NULL IS NULL)\nSELECT 5 IS DISTINCT FROM NULL;         -- 1  (equivalent to 5 IS NOT NULL)\nSELECT 5 IS DISTINCT FROM 5;            -- 0  (equivalent to 5 IS NOT 5)\nSELECT NULL IS NOT DISTINCT FROM 5;     -- 0  (equivalent to NULL IS 5)\n</code></pre>\n<h2 id=\"logical-operators\"><a class=\"header\" href=\"#logical-operators\">Logical Operators</a></h2>\n<p>Logical operators evaluate boolean expressions. Turso uses three-valued logic: true (1), false (0), and unknown (NULL).</p>\n<h3 id=\"and\"><a class=\"header\" href=\"#and\">AND</a></h3>\n<p>Returns 1 if both operands are true, 0 if either operand is false, and NULL otherwise.</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Left</th><th>Right</th><th>Result</th></tr>\n</thead>\n<tbody>\n<tr><td>1</td><td>1</td><td>1</td></tr>\n<tr><td>1</td><td>0</td><td>0</td></tr>\n<tr><td>0</td><td>0</td><td>0</td></tr>\n<tr><td>NULL</td><td>1</td><td>NULL</td></tr>\n<tr><td>NULL</td><td>0</td><td>0</td></tr>\n</tbody>\n</table>\n</div>\n<p>The key behavior: <code>NULL AND 0</code> is 0 (not NULL), because regardless of the unknown value, the result must be false when the other operand is false.</p>\n<pre><code class=\"language-sql\">SELECT 1 AND 1;      -- 1\nSELECT 1 AND 0;      -- 0\nSELECT NULL AND 0;   -- 0\nSELECT NULL AND 1;   -- NULL\n</code></pre>\n<h3 id=\"or\"><a class=\"header\" href=\"#or\">OR</a></h3>\n<p>Returns 1 if either operand is true, 0 if both operands are false, and NULL otherwise.</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Left</th><th>Right</th><th>Result</th></tr>\n</thead>\n<tbody>\n<tr><td>1</td><td>0</td><td>1</td></tr>\n<tr><td>0</td><td>0</td><td>0</td></tr>\n<tr><td>1</td><td>1</td><td>1</td></tr>\n<tr><td>NULL</td><td>1</td><td>1</td></tr>\n<tr><td>NULL</td><td>0</td><td>NULL</td></tr>\n</tbody>\n</table>\n</div>\n<p>The key behavior: <code>NULL OR 1</code> is 1 (not NULL), because regardless of the unknown value, the result must be true when the other operand is true.</p>\n<pre><code class=\"language-sql\">SELECT 1 OR 0;      -- 1\nSELECT 0 OR 0;      -- 0\nSELECT NULL OR 1;   -- 1\nSELECT NULL OR 0;   -- NULL\n</code></pre>\n<h3 id=\"not\"><a class=\"header\" href=\"#not\">NOT</a></h3>\n<p>Returns 0 if the operand is true, 1 if the operand is false, and NULL if the operand is NULL.</p>\n<pre><code class=\"language-sql\">SELECT NOT 1;      -- 0\nSELECT NOT 0;      -- 1\nSELECT NOT NULL;   -- NULL\n</code></pre>\n<h3 id=\"realistic-example-2\"><a class=\"header\" href=\"#realistic-example-2\">Realistic Example</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE orders (id INTEGER, amount REAL, status TEXT);\nINSERT INTO orders VALUES (1, 150.00, 'shipped'),\n                          (2, 75.50, 'pending'),\n                          (3, 200.00, 'shipped'),\n                          (4, 30.00, 'cancelled');\n\nSELECT * FROM orders WHERE amount &gt; 100 AND status = 'shipped';\n-- 1|150.0|shipped\n-- 3|200.0|shipped\n</code></pre>\n<h2 id=\"bitwise-operators\"><a class=\"header\" href=\"#bitwise-operators\">Bitwise Operators</a></h2>\n<p>Bitwise operators work on the integer representation of their operands. Non-integer operands are cast to integers before the operation.</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Operator</th><th>Description</th></tr>\n</thead>\n<tbody>\n<tr><td><code>expr1 &amp; expr2</code></td><td>Bitwise AND. Sets each bit to 1 only if both corresponding bits are 1.</td></tr>\n<tr><td><code>expr1 | expr2</code></td><td>Bitwise OR. Sets each bit to 1 if either corresponding bit is 1.</td></tr>\n<tr><td><code>~expr</code></td><td>Bitwise NOT (unary). Inverts every bit.</td></tr>\n<tr><td><code>expr1 &lt;&lt; expr2</code></td><td>Left shift. Shifts bits of <code>expr1</code> left by <code>expr2</code> positions, filling with zeros.</td></tr>\n<tr><td><code>expr1 &gt;&gt; expr2</code></td><td>Right shift. Shifts bits of <code>expr1</code> right by <code>expr2</code> positions.</td></tr>\n</tbody>\n</table>\n</div>\n<pre><code class=\"language-sql\">SELECT 5 &amp; 3;       -- 1    (0101 &amp; 0011 = 0001)\nSELECT 5 | 3;       -- 7    (0101 | 0011 = 0111)\nSELECT ~5;          -- -6   (inverts all 64 bits)\nSELECT 1 &lt;&lt; 4;      -- 16   (shift 1 left by 4 positions)\nSELECT 16 &gt;&gt; 2;     -- 4    (shift 16 right by 2 positions)\nSELECT 0xFF &amp; 0x0F; -- 15   (mask lower nibble)\nSELECT 0xF0 | 0x0F; -- 255  (combine nibbles)\n</code></pre>\n<h2 id=\"null-handling-summary\"><a class=\"header\" href=\"#null-handling-summary\">NULL Handling Summary</a></h2>\n<p>Most operators propagate NULL: if any operand is NULL, the result is NULL. The exceptions are:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Operator</th><th>NULL Behavior</th></tr>\n</thead>\n<tbody>\n<tr><td><code>IS</code> / <code>IS NOT</code></td><td>Never returns NULL. Treats NULL as a comparable value.</td></tr>\n<tr><td><code>IS DISTINCT FROM</code> / <code>IS NOT DISTINCT FROM</code></td><td>Never returns NULL. Same as <code>IS NOT</code> / <code>IS</code>.</td></tr>\n<tr><td><code>AND</code></td><td>Returns 0 if the other operand is 0, even when one operand is NULL.</td></tr>\n<tr><td><code>OR</code></td><td>Returns 1 if the other operand is 1, even when one operand is NULL.</td></tr>\n<tr><td><code>||</code></td><td>Returns NULL if either operand is NULL.</td></tr>\n</tbody>\n</table>\n</div>\n<h2 id=\"compatibility-15\"><a class=\"header\" href=\"#compatibility-15\">Compatibility</a></h2>\n<p>The <code>!&lt;</code> (not less than) and <code>!&gt;</code> (not greater than) operators recognized by some databases are not supported. Use <code>&gt;=</code> and <code>&lt;=</code> instead.</p>\n<div style=\"break-before: page; page-break-before: always;\"></div>\n<h1 id=\"literals\"><a class=\"header\" href=\"#literals\">Literals</a></h1>\n<p>A literal (also called a constant) is a fixed value written directly in a SQL statement. Turso supports six kinds of literal values: integers, reals, strings, blobs, NULL, and booleans.</p>\n<h2 id=\"integer-literals\"><a class=\"header\" href=\"#integer-literals\">Integer Literals</a></h2>\n<p>An integer literal is a sequence of decimal digits with no decimal point and no exponent. Integer values are stored as 64-bit signed integers, supporting the range -9223372036854775808 to 9223372036854775807.</p>\n<pre><code class=\"language-sql\">SELECT 42;       -- 42\nSELECT -100;     -- -100\nSELECT 0;        -- 0\n</code></pre>\n<p>If a numeric literal without a decimal point or exponent exceeds the 64-bit signed integer range, it is automatically treated as a real (floating-point) value:</p>\n<pre><code class=\"language-sql\">SELECT typeof(9223372036854775807);  -- integer (fits in 64-bit)\nSELECT typeof(9223372036854775808);  -- real (exceeds 64-bit range)\n</code></pre>\n<h3 id=\"hexadecimal-integers\"><a class=\"header\" href=\"#hexadecimal-integers\">Hexadecimal Integers</a></h3>\n<p>Integer literals may also be written in hexadecimal using the <code>0x</code> or <code>0X</code> prefix followed by hexadecimal digits (<code>0</code>-<code>9</code>, <code>a</code>-<code>f</code>, <code>A</code>-<code>F</code>). Hexadecimal literals are interpreted as 64-bit two’s-complement integers.</p>\n<pre><code class=\"language-sql\">SELECT 0x1F;     -- 31\nSELECT 0xFF;     -- 255\nSELECT 0x0;      -- 0\nSELECT 0X1F;     -- 31 (uppercase prefix also works)\n</code></pre>\n<p>Hexadecimal notation is recognized only in SQL literal syntax. Runtime string-to-integer conversions (such as <code>CAST('0xFF' AS INTEGER)</code>) do not interpret hex prefixes.</p>\n<h2 id=\"real-literals\"><a class=\"header\" href=\"#real-literals\">Real Literals</a></h2>\n<p>A numeric literal is treated as a real (floating-point) value if it contains a decimal point, an exponent clause, or both. Real values are stored as 8-byte IEEE 754 floating-point numbers.</p>\n<h3 id=\"decimal-point\"><a class=\"header\" href=\"#decimal-point\">Decimal Point</a></h3>\n<pre><code class=\"language-sql\">SELECT 3.14;     -- 3.14\nSELECT .5;       -- 0.5\nSELECT 100.0;    -- 100.0\n</code></pre>\n<h3 id=\"scientific-notation\"><a class=\"header\" href=\"#scientific-notation\">Scientific Notation</a></h3>\n<p>An exponent clause consists of the letter <code>E</code> or <code>e</code>, an optional sign (<code>+</code> or <code>-</code>), and one or more digits:</p>\n<pre><code class=\"language-sql\">SELECT 2.5e3;    -- 2500.0\nSELECT 1.5E-2;   -- 0.015\nSELECT 1e10;     -- 10000000000.0\n</code></pre>\n<h3 id=\"underscore-separators\"><a class=\"header\" href=\"#underscore-separators\">Underscore Separators</a></h3>\n<p>For readability, a single underscore (<code>_</code>) may be placed between any two digits in a numeric literal. Underscores are ignored during parsing and do not affect the value. This works for both integer and real literals.</p>\n<pre><code class=\"language-sql\">SELECT 1_000_000;       -- 1000000\nSELECT 1_000.000_001;   -- 1000.000001\n</code></pre>\n<h2 id=\"string-literals\"><a class=\"header\" href=\"#string-literals\">String Literals</a></h2>\n<p>A string literal is a sequence of characters enclosed in single quotes (<code>'</code>). The value has TEXT storage class.</p>\n<pre><code class=\"language-sql\">SELECT 'Hello, world!';   -- Hello, world!\nSELECT '';                 -- (empty string)\n</code></pre>\n<h3 id=\"escaping-single-quotes\"><a class=\"header\" href=\"#escaping-single-quotes\">Escaping Single Quotes</a></h3>\n<p>To include a literal single-quote character within a string, write two single quotes in a row (<code>''</code>). This is the standard SQL escaping mechanism. C-style backslash escapes (<code>\\'</code>) are not supported.</p>\n<pre><code class=\"language-sql\">SELECT 'It''s a test';    -- It's a test\nSELECT 'She said ''hi'''; -- She said 'hi'\n</code></pre>\n<h2 id=\"blob-literals\"><a class=\"header\" href=\"#blob-literals\">Blob Literals</a></h2>\n<p>A blob literal is a string of hexadecimal digits preceded by <code>X</code> or <code>x</code> and enclosed in single quotes. Each pair of hex digits represents one byte. The number of hex digits must be even. The value has BLOB storage class.</p>\n<pre><code class=\"language-sql\">SELECT X'48656C6C6F';           -- blob containing bytes for \"Hello\"\nSELECT typeof(X'48656C6C6F');   -- blob\nSELECT length(X'48656C6C6F');   -- 5\nSELECT x'48656C6C6F';           -- lowercase x prefix also works\nSELECT X'FF';                   -- single byte: 0xFF\n</code></pre>\n<p>A blob literal with no hex digits (<code>X''</code>) produces a zero-length blob.</p>\n<p>Invalid blob literals are rejected at parse time. The hex digits must be valid (<code>0</code>-<code>9</code>, <code>a</code>-<code>f</code>, <code>A</code>-<code>F</code>) and the total count must be even.</p>\n<h2 id=\"null\"><a class=\"header\" href=\"#null\">NULL</a></h2>\n<p>The keyword <code>NULL</code> represents a missing or unknown value. NULL is its own storage class and is distinct from any other value, including zero, an empty string, or a zero-length blob.</p>\n<pre><code class=\"language-sql\">SELECT NULL;            -- (empty result)\nSELECT typeof(NULL);    -- null\n</code></pre>\n<p>NULL propagates through most operations. An expression involving NULL generally produces NULL:</p>\n<pre><code class=\"language-sql\">SELECT NULL + 5;        -- NULL\n</code></pre>\n<p>Use <code>IS NULL</code> or <code>IS NOT NULL</code> to test for null values. The <code>=</code> operator does not match NULL because NULL is not equal to anything, including itself.</p>\n<h2 id=\"boolean-literals\"><a class=\"header\" href=\"#boolean-literals\">Boolean Literals</a></h2>\n<p>Turso recognizes <code>TRUE</code> and <code>FALSE</code> as boolean literals. They are aliases for the integer values 1 and 0, respectively.</p>\n<pre><code class=\"language-sql\">SELECT TRUE;            -- 1\nSELECT FALSE;           -- 0\nSELECT typeof(TRUE);    -- integer\nSELECT typeof(FALSE);   -- integer\n</code></pre>\n<p>Because <code>TRUE</code> and <code>FALSE</code> are integers, they participate in arithmetic like any other integer:</p>\n<pre><code class=\"language-sql\">SELECT TRUE + TRUE;     -- 2\n</code></pre>\n<h3 id=\"is-true--is-false\"><a class=\"header\" href=\"#is-true--is-false\">IS TRUE / IS FALSE</a></h3>\n<p>When <code>TRUE</code> or <code>FALSE</code> appear on the right-hand side of the <code>IS</code> operator, the expression performs a boolean evaluation of the left operand. Any non-zero, non-NULL value <code>IS TRUE</code>, and zero <code>IS FALSE</code>. NULL is neither true nor false.</p>\n<pre><code class=\"language-sql\">SELECT 5 IS TRUE;       -- 1 (non-zero is true)\nSELECT 0 IS FALSE;      -- 1 (zero is false)\nSELECT NULL IS TRUE;    -- 0 (NULL is not true)\nSELECT NULL IS FALSE;   -- 0 (NULL is not false)\n</code></pre>\n<h2 id=\"determining-literal-type\"><a class=\"header\" href=\"#determining-literal-type\">Determining Literal Type</a></h2>\n<p>Use the <code>typeof()</code> function to inspect the storage class of any literal:</p>\n<pre><code class=\"language-sql\">SELECT typeof(42);              -- integer\nSELECT typeof(3.14);            -- real\nSELECT typeof('hello');         -- text\nSELECT typeof(X'FF');           -- blob\nSELECT typeof(NULL);            -- null\nSELECT typeof(TRUE);            -- integer\n</code></pre>\n<h2 id=\"summary\"><a class=\"header\" href=\"#summary\">Summary</a></h2>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Literal Kind</th><th>Syntax</th><th>Storage Class</th></tr>\n</thead>\n<tbody>\n<tr><td>Integer</td><td><code>42</code>, <code>-7</code>, <code>0xFF</code></td><td>INTEGER</td></tr>\n<tr><td>Real</td><td><code>3.14</code>, <code>2.5e3</code>, <code>.5</code></td><td>REAL</td></tr>\n<tr><td>String</td><td><code>'text'</code></td><td>TEXT</td></tr>\n<tr><td>Blob</td><td><code>X'hex'</code></td><td>BLOB</td></tr>\n<tr><td>NULL</td><td><code>NULL</code></td><td>NULL</td></tr>\n<tr><td>Boolean</td><td><code>TRUE</code>, <code>FALSE</code></td><td>INTEGER</td></tr>\n</tbody>\n</table>\n</div>\n<div style=\"break-before: page; page-break-before: always;\"></div>\n<h1 id=\"conditional-expressions\"><a class=\"header\" href=\"#conditional-expressions\">Conditional Expressions</a></h1>\n<p>Turso provides several ways to express conditional logic within SQL queries: the <code>CASE</code> expression for general-purpose branching, and the built-in functions <code>IIF</code>, <code>COALESCE</code>, <code>NULLIF</code>, and <code>IFNULL</code> for common patterns.</p>\n<h2 id=\"case\"><a class=\"header\" href=\"#case\">CASE</a></h2>\n<h3 id=\"syntax-20\"><a class=\"header\" href=\"#syntax-20\">Syntax</a></h3>\n<p>There are two forms of the <code>CASE</code> expression.</p>\n<p><strong>Searched CASE</strong> (without a base expression):</p>\n<pre><code class=\"language-sql\">CASE\n  WHEN condition THEN result\n  [WHEN condition THEN result ...]\n  [ELSE default]\nEND\n</code></pre>\n<p><strong>Simple CASE</strong> (with a base expression):</p>\n<pre><code class=\"language-sql\">CASE expr\n  WHEN value THEN result\n  [WHEN value THEN result ...]\n  [ELSE default]\nEND\n</code></pre>\n<h3 id=\"description-20\"><a class=\"header\" href=\"#description-20\">Description</a></h3>\n<p>The <code>CASE</code> expression evaluates a series of conditions and returns the result associated with the first condition that is true.</p>\n<p>In the <strong>searched form</strong>, each <code>WHEN</code> clause contains an arbitrary boolean expression. Turso evaluates the <code>WHEN</code> expressions from left to right and returns the <code>THEN</code> result corresponding to the first expression that is true. A <code>WHEN</code> expression is considered true if its result is non-zero and non-NULL.</p>\n<p>In the <strong>simple form</strong>, Turso evaluates the base expression once, then compares it against each <code>WHEN</code> value from left to right using the <code>=</code> operator. The result of the first matching <code>WHEN</code> value is returned. Because <code>NULL = NULL</code> evaluates to <code>NULL</code> (not true), a <code>NULL</code> base expression will never match any <code>WHEN</code> value, and the <code>ELSE</code> branch (or <code>NULL</code>) is returned.</p>\n<p>Both forms use <strong>short-circuit evaluation</strong>: once a matching <code>WHEN</code> is found, the remaining <code>WHEN</code> clauses are not evaluated. If no <code>WHEN</code> clause matches and there is no <code>ELSE</code>, the result is <code>NULL</code>.</p>\n<h3 id=\"examples-20\"><a class=\"header\" href=\"#examples-20\">Examples</a></h3>\n<pre><code class=\"language-sql\">-- Searched CASE: classify a number\nSELECT CASE\n  WHEN 1 &gt; 0 THEN 'positive'\n  ELSE 'non-positive'\nEND;\n-- positive\n</code></pre>\n<pre><code class=\"language-sql\">-- Simple CASE: map a value to a label\nSELECT CASE 2\n  WHEN 1 THEN 'one'\n  WHEN 2 THEN 'two'\n  WHEN 3 THEN 'three'\n  ELSE 'other'\nEND;\n-- two\n</code></pre>\n<pre><code class=\"language-sql\">-- Multiple WHEN clauses; first match wins\nSELECT CASE\n  WHEN 1 = 2 THEN 'first'\n  WHEN 2 = 3 THEN 'second'\n  WHEN 3 = 3 THEN 'third'\n  ELSE 'none'\nEND;\n-- third\n</code></pre>\n<pre><code class=\"language-sql\">-- NULL and 0 are not true in WHEN conditions\nSELECT CASE WHEN NULL THEN 'yes' ELSE 'no' END;\n-- no\n\nSELECT CASE WHEN 0 THEN 'matched' END;\n-- NULL (no ELSE clause, so NULL is returned)\n</code></pre>\n<pre><code class=\"language-sql\">-- NULL base expression never matches\nSELECT CASE NULL\n  WHEN 1    THEN 'one'\n  WHEN NULL THEN 'null'\n  ELSE 'else'\nEND;\n-- else\n</code></pre>\n<pre><code class=\"language-sql\">-- Categorize product availability\nCREATE TABLE products (name TEXT, price REAL, stock INTEGER);\nINSERT INTO products VALUES ('Widget', 25.99, 100);\nINSERT INTO products VALUES ('Gadget', 0, 50);\nINSERT INTO products VALUES ('Doohickey', 15.50, 0);\n\nSELECT name,\n  CASE\n    WHEN stock = 0  THEN 'out of stock'\n    WHEN stock &lt; 20 THEN 'low stock'\n    ELSE 'in stock'\n  END AS availability\nFROM products;\n-- Widget    | in stock\n-- Gadget    | in stock\n-- Doohickey | out of stock\n</code></pre>\n<h2 id=\"iif\"><a class=\"header\" href=\"#iif\">iif</a></h2>\n<p><strong>iif(condition, true_value, false_value) -&gt; value</strong></p>\n<p>Returns <code>true_value</code> if <code>condition</code> is true (non-zero and non-NULL), otherwise returns <code>false_value</code>. This is a shorthand for <code>CASE WHEN condition THEN true_value ELSE false_value END</code>.</p>\n<pre><code class=\"language-sql\">SELECT iif(1, 'true', 'false');\n-- true\n\nSELECT iif(10 &gt; 5, 'big', 'small');\n-- big\n</code></pre>\n<pre><code class=\"language-sql\">-- Label orders based on status\nCREATE TABLE orders (id INTEGER, total REAL, status TEXT);\nINSERT INTO orders VALUES (1, 150.00, 'shipped');\nINSERT INTO orders VALUES (2, 0, 'pending');\nINSERT INTO orders VALUES (3, 75.50, 'delivered');\n\nSELECT id, iif(status = 'shipped', 'in transit', 'other') AS label\nFROM orders;\n-- 1 | in transit\n-- 2 | other\n-- 3 | other\n</code></pre>\n<h2 id=\"coalesce\"><a class=\"header\" href=\"#coalesce\">coalesce</a></h2>\n<p><strong>coalesce(x, y, …) -&gt; value</strong></p>\n<p>Returns the first argument that is not <code>NULL</code>. If all arguments are <code>NULL</code>, returns <code>NULL</code>. Requires at least two arguments.</p>\n<p><code>COALESCE</code> uses short-circuit evaluation: arguments to the right of the first non-NULL value are not evaluated.</p>\n<pre><code class=\"language-sql\">SELECT coalesce(NULL, NULL, 'third');\n-- third\n\nSELECT coalesce(NULL, NULL);\n-- NULL\n</code></pre>\n<pre><code class=\"language-sql\">-- Pick the best available contact method\nCREATE TABLE contacts (name TEXT, phone TEXT, email TEXT);\nINSERT INTO contacts VALUES ('Alice', NULL, 'alice@example.com');\nINSERT INTO contacts VALUES ('Bob', '555-1234', NULL);\nINSERT INTO contacts VALUES ('Charlie', NULL, NULL);\n\nSELECT name, coalesce(phone, email, 'no contact info') AS contact\nFROM contacts;\n-- Alice   | alice@example.com\n-- Bob     | 555-1234\n-- Charlie | no contact info\n</code></pre>\n<h2 id=\"nullif\"><a class=\"header\" href=\"#nullif\">nullif</a></h2>\n<p><strong>nullif(x, y) -&gt; value</strong></p>\n<p>Returns <code>x</code> if <code>x</code> and <code>y</code> are different. Returns <code>NULL</code> if <code>x</code> and <code>y</code> are equal. The comparison uses the same rules as the <code>=</code> operator.</p>\n<p>A common use of <code>NULLIF</code> is to convert a sentinel value (such as zero or an empty string) into <code>NULL</code>, which can then be handled by <code>COALESCE</code>, <code>IFNULL</code>, or aggregate functions that skip <code>NULL</code> values.</p>\n<pre><code class=\"language-sql\">SELECT nullif(5, 5);\n-- NULL\n\nSELECT nullif(5, 8);\n-- 5\n</code></pre>\n<pre><code class=\"language-sql\">-- Prevent division by zero (dividing by NULL yields NULL instead of an error)\nSELECT 100.0 / nullif(0, 0);\n-- NULL\n\nSELECT 100.0 / nullif(5, 0);\n-- 20.0\n</code></pre>\n<pre><code class=\"language-sql\">-- Convert zero prices to NULL\nCREATE TABLE products (name TEXT, price REAL, stock INTEGER);\nINSERT INTO products VALUES ('Widget', 25.99, 100);\nINSERT INTO products VALUES ('Gadget', 0, 50);\nINSERT INTO products VALUES ('Doohickey', 15.50, 0);\n\nSELECT name, nullif(price, 0) AS nonzero_price\nFROM products;\n-- Widget    | 25.99\n-- Gadget    | NULL\n-- Doohickey | 15.5\n</code></pre>\n<h2 id=\"ifnull\"><a class=\"header\" href=\"#ifnull\">ifnull</a></h2>\n<p><strong>ifnull(x, y) -&gt; value</strong></p>\n<p>Returns <code>x</code> if <code>x</code> is not <code>NULL</code>, otherwise returns <code>y</code>. This is equivalent to <code>coalesce(x, y)</code> with exactly two arguments.</p>\n<pre><code class=\"language-sql\">SELECT ifnull(NULL, 'fallback');\n-- fallback\n\nSELECT ifnull('present', 'fallback');\n-- present\n</code></pre>\n<pre><code class=\"language-sql\">-- Provide a default for missing phone numbers\nCREATE TABLE contacts (name TEXT, phone TEXT, email TEXT);\nINSERT INTO contacts VALUES ('Alice', NULL, 'alice@example.com');\nINSERT INTO contacts VALUES ('Bob', '555-1234', NULL);\nINSERT INTO contacts VALUES ('Charlie', NULL, NULL);\n\nSELECT name, ifnull(phone, 'N/A') AS phone\nFROM contacts;\n-- Alice   | N/A\n-- Bob     | 555-1234\n-- Charlie | N/A\n</code></pre>\n<h2 id=\"choosing-the-right-construct\"><a class=\"header\" href=\"#choosing-the-right-construct\">Choosing the Right Construct</a></h2>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Need</th><th>Use</th><th>Example</th></tr>\n</thead>\n<tbody>\n<tr><td>Multi-way branching</td><td><code>CASE WHEN ... THEN ... END</code></td><td>Classify rows into categories</td></tr>\n<tr><td>Match a value against a list</td><td><code>CASE expr WHEN ... THEN ... END</code></td><td>Map status codes to labels</td></tr>\n<tr><td>Simple if/else in one line</td><td><code>iif(cond, a, b)</code></td><td>Toggle between two values</td></tr>\n<tr><td>First non-NULL from a list</td><td><code>coalesce(a, b, c)</code></td><td>Pick best available contact</td></tr>\n<tr><td>Convert a value to NULL</td><td><code>nullif(x, sentinel)</code></td><td>Turn 0 into NULL before division</td></tr>\n<tr><td>Default for a single NULL</td><td><code>ifnull(x, default)</code></td><td>Replace NULL with a placeholder</td></tr>\n</tbody>\n</table>\n</div>\n<div style=\"break-before: page; page-break-before: always;\"></div>\n<h1 id=\"pattern-matching\"><a class=\"header\" href=\"#pattern-matching\">Pattern Matching</a></h1>\n<p>Turso provides three pattern-matching operators for comparing strings against patterns: <code>LIKE</code>, <code>GLOB</code>, and <code>REGEXP</code>. Each uses different wildcard conventions and case-sensitivity rules. All three can be negated with the <code>NOT</code> keyword.</p>\n<h2 id=\"syntax-21\"><a class=\"header\" href=\"#syntax-21\">Syntax</a></h2>\n<pre><code class=\"language-sql\">expr [NOT] LIKE pattern [ESCAPE escape-char]\nexpr [NOT] GLOB pattern\nexpr [NOT] REGEXP pattern\n</code></pre>\n<p>Each operator returns <code>1</code> (true) if the string matches the pattern, <code>0</code> (false) if it does not, or <code>NULL</code> if either operand is <code>NULL</code>.</p>\n<h2 id=\"like\"><a class=\"header\" href=\"#like\">LIKE</a></h2>\n<p>The <code>LIKE</code> operator performs pattern matching using two wildcard characters:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Wildcard</th><th>Meaning</th></tr>\n</thead>\n<tbody>\n<tr><td><code>%</code></td><td>Matches any sequence of zero or more characters.</td></tr>\n<tr><td><code>_</code></td><td>Matches exactly one character.</td></tr>\n</tbody>\n</table>\n</div>\n<p>Any other character in the pattern matches itself. <code>LIKE</code> is <strong>case-insensitive for ASCII characters</strong> by default – <code>'a' LIKE 'A'</code> evaluates to true. For Unicode characters outside the ASCII range, <code>LIKE</code> is case-sensitive: <code>'ä' LIKE 'Ä'</code> evaluates to false.</p>\n<pre><code class=\"language-sql\">-- % matches any sequence of characters\nSELECT 'sweater' LIKE 'sweat%';   -- 1\nSELECT 'sweatshirt' LIKE 'sweat%'; -- 1\nSELECT 'hat' LIKE 'sweat%';       -- 0\n\n-- _ matches exactly one character\nSELECT 'sweater' LIKE 'sweat_r';  -- 1\nSELECT 'sweatshirt' LIKE 'sweat_r'; -- 0\n\n-- Case-insensitive for ASCII\nSELECT 'sweater' LIKE 'SWEAT%';   -- 1\nSELECT 'sweater' LIKE 'SwEaT_R';  -- 1\n</code></pre>\n<p>The <code>%</code> and <code>_</code> wildcards can be combined to build expressive patterns. Use <code>%</code> at the beginning and end of a pattern to search for a substring anywhere in a string:</p>\n<pre><code class=\"language-sql\">SELECT 'hello world' LIKE '%world%'; -- 1\nSELECT 'hello world' LIKE '%xyz%';   -- 0\n</code></pre>\n<h3 id=\"escape-clause\"><a class=\"header\" href=\"#escape-clause\">ESCAPE Clause</a></h3>\n<p>To match a literal <code>%</code> or <code>_</code> character in a pattern, use the <code>ESCAPE</code> clause. The escape character causes the next <code>%</code>, <code>_</code>, or the escape character itself to be treated as a literal rather than a wildcard.</p>\n<pre><code class=\"language-sql\">-- Match a literal % character using \\ as the escape character\nSELECT '10%' LIKE '10\\%' ESCAPE '\\';  -- 1\nSELECT '10x' LIKE '10\\%' ESCAPE '\\';  -- 0\n</code></pre>\n<p>The escape character must be a single character. If the <code>ESCAPE</code> value is <code>NULL</code>, the entire <code>LIKE</code> expression evaluates to <code>NULL</code>.</p>\n<pre><code class=\"language-sql\">-- Any single character can serve as the escape character\nSELECT 'a%bc' LIKE 'a5%%' ESCAPE '5'; -- 1\n</code></pre>\n<h3 id=\"not-like\"><a class=\"header\" href=\"#not-like\">NOT LIKE</a></h3>\n<p>Prefixing with <code>NOT</code> inverts the result:</p>\n<pre><code class=\"language-sql\">SELECT 'sweater' NOT LIKE 'sweat%'; -- 0\nSELECT 'hat' NOT LIKE 'sweat%';     -- 1\n</code></pre>\n<h3 id=\"function-form\"><a class=\"header\" href=\"#function-form\">Function Form</a></h3>\n<p>The <code>LIKE</code> operator can also be invoked as a function. The infix expression <code>X LIKE Y</code> is equivalent to <code>like(Y, X)</code>, and <code>X LIKE Y ESCAPE Z</code> is equivalent to <code>like(Y, X, Z)</code>. Note that the pattern is the <strong>first</strong> argument in function form.</p>\n<pre><code class=\"language-sql\">SELECT like('sweat%', 'sweater');           -- 1\nSELECT like('abcX%', 'abc%', 'X');          -- 1\n</code></pre>\n<h2 id=\"glob\"><a class=\"header\" href=\"#glob\">GLOB</a></h2>\n<p>The <code>GLOB</code> operator is similar to <code>LIKE</code> but uses Unix file-globbing syntax for wildcards and is <strong>case-sensitive</strong>.</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Wildcard</th><th>Meaning</th></tr>\n</thead>\n<tbody>\n<tr><td><code>*</code></td><td>Matches any sequence of zero or more characters (like <code>%</code> in LIKE).</td></tr>\n<tr><td><code>?</code></td><td>Matches exactly one character (like <code>_</code> in LIKE).</td></tr>\n<tr><td><code>[chars]</code></td><td>Matches one character from the set or range inside the brackets.</td></tr>\n<tr><td><code>[^chars]</code></td><td>Matches one character <strong>not</strong> in the set or range inside the brackets.</td></tr>\n</tbody>\n</table>\n</div>\n<p>Because <code>GLOB</code> is case-sensitive, <code>'hello' GLOB 'H*'</code> evaluates to false.</p>\n<pre><code class=\"language-sql\">-- * matches any sequence of characters\nSELECT 'hello' GLOB 'h*';    -- 1\nSELECT 'hello' GLOB 'H*';    -- 0 (case-sensitive)\n\n-- ? matches exactly one character\nSELECT 'hello' GLOB '?ello'; -- 1\nSELECT 'hello' GLOB '??llo'; -- 1\n</code></pre>\n<h3 id=\"character-classes\"><a class=\"header\" href=\"#character-classes\">Character Classes</a></h3>\n<p>Square brackets define a set of characters to match against a single position. A range can be specified with a hyphen. Use <code>^</code> after the opening bracket to negate the set.</p>\n<pre><code class=\"language-sql\">-- [cde] matches one character that is c, d, or e\nSELECT 'abcdefg' GLOB 'abc[cde]efg'; -- 1 (d matches [cde])\nSELECT 'abcbefg' GLOB 'abc[cde]efg'; -- 0 (b not in [cde])\n\n-- [c-e] matches one character in the range c through e\nSELECT 'abcdefg' GLOB 'abc[c-e]efg'; -- 1\nSELECT 'abcfefg' GLOB 'abc[c-e]efg'; -- 0\n\n-- [^cde] matches one character NOT in the set\nSELECT 'abcbefg' GLOB 'abc[^cde]efg'; -- 1\nSELECT 'abccefg' GLOB 'abc[^cde]efg'; -- 0\n</code></pre>\n<p>A literal <code>-</code> can be included in a character class by placing it first or last: <code>[-c]</code> matches either <code>-</code> or <code>c</code>.</p>\n<pre><code class=\"language-sql\">SELECT '-' GLOB '[-c]'; -- 1\nSELECT 'c' GLOB '[-c]'; -- 1\nSELECT 'x' GLOB '[-c]'; -- 0\n</code></pre>\n<h3 id=\"not-glob\"><a class=\"header\" href=\"#not-glob\">NOT GLOB</a></h3>\n<p>Prefixing with <code>NOT</code> inverts the result:</p>\n<pre><code class=\"language-sql\">SELECT 'hello' NOT GLOB 'h*'; -- 0\nSELECT 'hello' NOT GLOB 'H*'; -- 1\n</code></pre>\n<h3 id=\"function-form-1\"><a class=\"header\" href=\"#function-form-1\">Function Form</a></h3>\n<p>The infix expression <code>X GLOB Y</code> is equivalent to <code>glob(Y, X)</code>. The pattern is the <strong>first</strong> argument.</p>\n<pre><code class=\"language-sql\">SELECT glob('h*', 'hello');       -- 1\nSELECT glob('[a-k]*', 'hello');   -- 1\n</code></pre>\n<h2 id=\"regexp\"><a class=\"header\" href=\"#regexp\">REGEXP</a></h2>\n<p>The <code>REGEXP</code> operator tests whether a string matches a regular expression pattern. Turso provides a built-in <code>regexp()</code> function, so <code>REGEXP</code> works without loading an extension.</p>\n<p>The infix expression <code>X REGEXP Y</code> is equivalent to <code>regexp(Y, X)</code>. The pattern is the <strong>first</strong> argument in function form.</p>\n<pre><code class=\"language-sql\">SELECT 'hello' REGEXP 'h.*o';      -- 1\nSELECT 'hello' REGEXP '^world$';   -- 0\n</code></pre>\n<p><code>REGEXP</code> supports standard regular expression syntax, including character classes, anchors, quantifiers, and escape sequences:</p>\n<pre><code class=\"language-sql\">-- Anchors: ^ (start of string) and $ (end of string)\nSELECT 'hello' REGEXP '^hello$'; -- 1\nSELECT 'hello' REGEXP '^ello';   -- 0\n\n-- Character classes and quantifiers\nSELECT 'test@example.com' REGEXP '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$'; -- 1\n\n-- \\d for digits, \\b for word boundaries\nSELECT '2024-01-15' REGEXP '^\\d{4}-\\d{2}-\\d{2}$'; -- 1\nSELECT 'hello world' REGEXP '\\bworld\\b';           -- 1\n</code></pre>\n<h3 id=\"not-regexp\"><a class=\"header\" href=\"#not-regexp\">NOT REGEXP</a></h3>\n<p>Prefixing with <code>NOT</code> inverts the result:</p>\n<pre><code class=\"language-sql\">SELECT 'hello' NOT REGEXP 'h.*o';     -- 0\nSELECT 'hello' NOT REGEXP '^world$';  -- 1\n</code></pre>\n<h3 id=\"using-regexp-in-queries\"><a class=\"header\" href=\"#using-regexp-in-queries\">Using REGEXP in Queries</a></h3>\n<p><code>REGEXP</code> can be used in <code>WHERE</code> clauses, <code>CASE</code> expressions, and subqueries just like any other boolean expression:</p>\n<pre><code class=\"language-sql\">CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, sku TEXT);\nINSERT INTO products VALUES (1, 'Widget A', 'WGT-001'),\n                            (2, 'Gadget B', 'GDT-002'),\n                            (3, 'Widget C', 'WGT-003'),\n                            (4, 'Doohickey', 'DHK-004');\n\n-- Filter rows with REGEXP in WHERE\nSELECT name FROM products WHERE sku REGEXP '^WGT';\n-- Widget A\n-- Widget C\n\n-- Classify rows with REGEXP in CASE\nSELECT name,\n       CASE WHEN name REGEXP '^Widget' THEN 'widget'\n            ELSE 'other'\n       END AS category\nFROM products;\n-- Widget A|widget\n-- Gadget B|other\n-- Widget C|widget\n-- Doohickey|other\n</code></pre>\n<h2 id=\"null-handling-2\"><a class=\"header\" href=\"#null-handling-2\">NULL Handling</a></h2>\n<p>For all three operators, if either operand is <code>NULL</code>, the result is <code>NULL</code>:</p>\n<pre><code class=\"language-sql\">SELECT NULL LIKE 'hello';  -- NULL (empty result)\nSELECT 'hello' LIKE NULL;  -- NULL (empty result)\nSELECT NULL GLOB 'h*';     -- NULL (empty result)\nSELECT NULL REGEXP 'abc';  -- NULL (empty result)\n</code></pre>\n<h2 id=\"comparison-of-operators\"><a class=\"header\" href=\"#comparison-of-operators\">Comparison of Operators</a></h2>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Feature</th><th>LIKE</th><th>GLOB</th><th>REGEXP</th></tr>\n</thead>\n<tbody>\n<tr><td>Case sensitivity</td><td>Case-insensitive (ASCII)</td><td>Case-sensitive</td><td>Depends on pattern</td></tr>\n<tr><td>Zero-or-more wildcard</td><td><code>%</code></td><td><code>*</code></td><td><code>.*</code></td></tr>\n<tr><td>Single-char wildcard</td><td><code>_</code></td><td><code>?</code></td><td><code>.</code></td></tr>\n<tr><td>Character classes</td><td>No</td><td><code>[abc]</code>, <code>[a-z]</code>, <code>[^abc]</code></td><td><code>[abc]</code>, <code>[a-z]</code>, <code>[^abc]</code>, <code>\\d</code>, <code>\\w</code>, etc.</td></tr>\n<tr><td>Escape clause</td><td><code>ESCAPE</code> keyword</td><td>No</td><td><code>\\</code> (backslash)</td></tr>\n<tr><td>Negation</td><td><code>NOT LIKE</code></td><td><code>NOT GLOB</code></td><td><code>NOT REGEXP</code></td></tr>\n</tbody>\n</table>\n</div>\n<div style=\"break-before: page; page-break-before: always;\"></div>\n<h1 id=\"type-conversions\"><a class=\"header\" href=\"#type-conversions\">Type Conversions</a></h1>\n<p>Turso uses a dynamic type system inherited from SQLite. Every value belongs to one of five storage classes – NULL, INTEGER, REAL, TEXT, or BLOB – but columns do not enforce a single type. Instead, each column has a <strong>type affinity</strong> that recommends how values should be stored. This page covers how type affinities are determined, how values are coerced on insertion, how the <code>CAST</code> expression performs explicit conversions, and how types interact during comparisons.</p>\n<h2 id=\"storage-classes\"><a class=\"header\" href=\"#storage-classes\">Storage Classes</a></h2>\n<p>Every value in Turso has exactly one storage class at any given time. Use the <code>typeof()</code> function to inspect it:</p>\n<pre><code class=\"language-sql\">SELECT typeof(42);        -- integer\nSELECT typeof(3.14);      -- real\nSELECT typeof('hello');   -- text\nSELECT typeof(x'ABCD');  -- blob\nSELECT typeof(NULL);      -- null\n</code></pre>\n<p>Arithmetic and string operations produce values whose storage class follows from the operation:</p>\n<pre><code class=\"language-sql\">SELECT typeof(1 + 1);      -- integer\nSELECT typeof(1 + 1.0);    -- real (integer promoted to real)\nSELECT typeof('a' || 'b'); -- text\n</code></pre>\n<h2 id=\"type-affinity-1\"><a class=\"header\" href=\"#type-affinity-1\">Type Affinity</a></h2>\n<p>A column’s type affinity is determined by the declared type name in the <code>CREATE TABLE</code> statement. Affinity is a recommendation, not a constraint – any column can store a value of any storage class.</p>\n<h3 id=\"the-five-affinities\"><a class=\"header\" href=\"#the-five-affinities\">The Five Affinities</a></h3>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Affinity</th><th>Behavior on INSERT</th></tr>\n</thead>\n<tbody>\n<tr><td>TEXT</td><td>Numeric values are converted to their text representation before storage.</td></tr>\n<tr><td>NUMERIC</td><td>Text that looks like an integer or real number is converted to INTEGER or REAL.</td></tr>\n<tr><td>INTEGER</td><td>Behaves identically to NUMERIC on insertion. Differs from NUMERIC only in <code>CAST</code> expressions.</td></tr>\n<tr><td>REAL</td><td>Like NUMERIC, but integer values are stored as floating-point.</td></tr>\n<tr><td>BLOB</td><td>No conversion. Values are stored exactly as provided.</td></tr>\n</tbody>\n</table>\n</div>\n<h3 id=\"affinity-determination-rules\"><a class=\"header\" href=\"#affinity-determination-rules\">Affinity Determination Rules</a></h3>\n<p>The affinity of a column is determined by applying the following rules to the declared type name, in order. The first matching rule wins:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Rule</th><th>Condition</th><th>Resulting Affinity</th></tr>\n</thead>\n<tbody>\n<tr><td>1</td><td>Type name contains <code>\"INT\"</code></td><td>INTEGER</td></tr>\n<tr><td>2</td><td>Type name contains <code>\"CHAR\"</code>, <code>\"CLOB\"</code>, or <code>\"TEXT\"</code></td><td>TEXT</td></tr>\n<tr><td>3</td><td>Type name contains <code>\"BLOB\"</code>, or no type is specified</td><td>BLOB</td></tr>\n<tr><td>4</td><td>Type name contains <code>\"REAL\"</code>, <code>\"FLOA\"</code>, or <code>\"DOUB\"</code></td><td>REAL</td></tr>\n<tr><td>5</td><td>Otherwise</td><td>NUMERIC</td></tr>\n</tbody>\n</table>\n</div>\n<p>The matching is case-insensitive and checks for substrings anywhere in the declared type name. Because rules are applied in order, some type names produce counterintuitive results:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Declared Type</th><th>Matching Rule</th><th>Affinity</th></tr>\n</thead>\n<tbody>\n<tr><td><code>INT</code>, <code>INTEGER</code>, <code>BIGINT</code>, <code>SMALLINT</code></td><td>1 (contains “INT”)</td><td>INTEGER</td></tr>\n<tr><td><code>TEXT</code>, <code>VARCHAR(255)</code>, <code>CLOB</code></td><td>2 (contains “TEXT”, “CHAR”, or “CLOB”)</td><td>TEXT</td></tr>\n<tr><td><code>BLOB</code>, (no type)</td><td>3 (contains “BLOB” or empty)</td><td>BLOB</td></tr>\n<tr><td><code>REAL</code>, <code>DOUBLE</code>, <code>FLOAT</code></td><td>4 (contains “REAL”, “DOUB”, or “FLOA”)</td><td>REAL</td></tr>\n<tr><td><code>NUMERIC</code>, <code>DECIMAL(10,5)</code>, <code>BOOLEAN</code>, <code>DATE</code></td><td>5 (no match above)</td><td>NUMERIC</td></tr>\n<tr><td><code>CHARINT</code></td><td>1 (contains “INT”, rule 1 before rule 2)</td><td>INTEGER</td></tr>\n<tr><td><code>FLOATING POINT</code></td><td>1 (contains “INT” in “POINT”)</td><td>INTEGER</td></tr>\n<tr><td><code>STRING</code></td><td>5 (no match for rules 1-4)</td><td>NUMERIC</td></tr>\n</tbody>\n</table>\n</div>\n<h3 id=\"affinity-in-action\"><a class=\"header\" href=\"#affinity-in-action\">Affinity in Action</a></h3>\n<p>When a value is inserted into a column, the column’s affinity determines whether a type conversion is attempted. Conversions only happen if they are lossless and reversible.</p>\n<pre><code class=\"language-sql\">CREATE TABLE demo (\n  t TEXT,\n  n NUMERIC,\n  i INTEGER,\n  r REAL,\n  b BLOB\n);\n\n-- Insert the string '500' into every column\nINSERT INTO demo VALUES ('500', '500', '500', '500', '500');\nSELECT typeof(t), typeof(n), typeof(i), typeof(r), typeof(b) FROM demo;\n-- text|integer|integer|real|text\n</code></pre>\n<p>Here is what happened to the string <code>'500'</code> in each column:</p>\n<ul>\n<li><strong>t (TEXT)</strong>: Stored as text. No conversion needed.</li>\n<li><strong>n (NUMERIC)</strong>: Converted to integer 500 because <code>'500'</code> is a well-formed integer literal.</li>\n<li><strong>i (INTEGER)</strong>: Converted to integer 500, same as NUMERIC.</li>\n<li><strong>r (REAL)</strong>: Converted to real 500.0 because REAL affinity forces floating-point storage.</li>\n<li><strong>b (BLOB)</strong>: Stored as text. BLOB affinity performs no conversion.</li>\n</ul>\n<pre><code class=\"language-sql\">-- Insert the integer 500 into every column\nDELETE FROM demo;\nINSERT INTO demo VALUES (500, 500, 500, 500, 500);\nSELECT typeof(t), typeof(n), typeof(i), typeof(r), typeof(b) FROM demo;\n-- text|integer|integer|real|integer\n</code></pre>\n<ul>\n<li><strong>t (TEXT)</strong>: Converted to text <code>'500'</code> because TEXT affinity converts numerics to text.</li>\n<li><strong>n (NUMERIC)</strong>: Stored as integer. Already numeric.</li>\n<li><strong>i (INTEGER)</strong>: Stored as integer. Already numeric.</li>\n<li><strong>r (REAL)</strong>: Stored as real 500.0. REAL affinity promotes integers to floating-point.</li>\n<li><strong>b (BLOB)</strong>: Stored as integer. BLOB affinity performs no conversion; the value keeps its original type.</li>\n</ul>\n<h3 id=\"null-and-blob-bypass-affinity\"><a class=\"header\" href=\"#null-and-blob-bypass-affinity\">NULL and BLOB Bypass Affinity</a></h3>\n<p>NULL values and BLOB values are never converted by affinity, regardless of the column’s declared type:</p>\n<pre><code class=\"language-sql\">DELETE FROM demo;\nINSERT INTO demo VALUES (NULL, NULL, NULL, NULL, NULL);\nSELECT typeof(t), typeof(n), typeof(i), typeof(r), typeof(b) FROM demo;\n-- null|null|null|null|null\n</code></pre>\n<pre><code class=\"language-sql\">DELETE FROM demo;\nINSERT INTO demo VALUES (x'01', x'01', x'01', x'01', x'01');\nSELECT typeof(t), typeof(n), typeof(i), typeof(r), typeof(b) FROM demo;\n-- blob|blob|blob|blob|blob\n</code></pre>\n<h2 id=\"cast-expressions\"><a class=\"header\" href=\"#cast-expressions\">CAST Expressions</a></h2>\n<p>The <code>CAST</code> expression explicitly converts a value to a different storage class.</p>\n<h3 id=\"syntax-22\"><a class=\"header\" href=\"#syntax-22\">Syntax</a></h3>\n<pre><code class=\"language-sql\">CAST(expr AS type-name)\n</code></pre>\n<p>The <code>type-name</code> follows the same affinity determination rules as column type names. If <code>expr</code> is NULL, the result is always NULL.</p>\n<h3 id=\"conversion-rules\"><a class=\"header\" href=\"#conversion-rules\">Conversion Rules</a></h3>\n<h4 id=\"cast-to-integer\"><a class=\"header\" href=\"#cast-to-integer\">CAST to INTEGER</a></h4>\n<p>Converts the value to a 64-bit signed integer.</p>\n<ul>\n<li><strong>From REAL</strong>: Truncates toward zero. Values outside the 64-bit integer range are clamped to the minimum or maximum value.</li>\n<li><strong>From TEXT</strong>: Extracts the longest leading substring that is a valid integer. Leading whitespace is ignored. Returns 0 if no valid prefix exists.</li>\n<li><strong>From BLOB</strong>: The blob is first interpreted as text, then the text-to-integer rules apply.</li>\n</ul>\n<pre><code class=\"language-sql\">SELECT CAST(3.14 AS INTEGER);      -- 3\nSELECT CAST(9.99 AS INTEGER);      -- 9\nSELECT CAST(-7.8 AS INTEGER);      -- -7\nSELECT CAST('42' AS INTEGER);      -- 42\nSELECT CAST('123abc' AS INTEGER);  -- 123\nSELECT CAST('abc' AS INTEGER);     -- 0\nSELECT CAST('  42  ' AS INTEGER);  -- 42\nSELECT CAST('' AS INTEGER);        -- 0\nSELECT CAST('99.9' AS INTEGER);    -- 99\n</code></pre>\n<h4 id=\"cast-to-real\"><a class=\"header\" href=\"#cast-to-real\">CAST to REAL</a></h4>\n<p>Converts the value to an 8-byte IEEE 754 floating-point number.</p>\n<ul>\n<li><strong>From TEXT</strong>: Extracts the longest leading substring that is a valid real number. Returns 0.0 if no valid prefix exists.</li>\n<li><strong>From INTEGER</strong>: Converts to the nearest representable floating-point value.</li>\n<li><strong>From BLOB</strong>: The blob is first interpreted as text, then the text-to-real rules apply.</li>\n</ul>\n<pre><code class=\"language-sql\">SELECT CAST(42 AS REAL);              -- 42.0\nSELECT CAST('3.14' AS REAL);          -- 3.14\nSELECT CAST('123.45abc' AS REAL);     -- 123.45\n</code></pre>\n<h4 id=\"cast-to-text\"><a class=\"header\" href=\"#cast-to-text\">CAST to TEXT</a></h4>\n<p>Converts the value to a text string.</p>\n<ul>\n<li><strong>From INTEGER or REAL</strong>: Renders the number as its string representation.</li>\n<li><strong>From BLOB</strong>: Interprets the blob bytes as a UTF-8 text string.</li>\n</ul>\n<pre><code class=\"language-sql\">SELECT CAST(42 AS TEXT);                    -- 42\nSELECT CAST(x'68656C6C6F' AS TEXT);         -- hello\nSELECT typeof(CAST(42 AS TEXT));            -- text\n</code></pre>\n<h4 id=\"cast-to-blob\"><a class=\"header\" href=\"#cast-to-blob\">CAST to BLOB</a></h4>\n<p>Converts the value to a blob.</p>\n<ul>\n<li><strong>From TEXT</strong>: The text is first encoded as UTF-8, then the resulting bytes are treated as a blob.</li>\n<li><strong>From INTEGER or REAL</strong>: The value is first converted to text, then to blob.</li>\n</ul>\n<pre><code class=\"language-sql\">SELECT typeof(CAST('hello' AS BLOB));  -- blob\n</code></pre>\n<h4 id=\"cast-to-numeric\"><a class=\"header\" href=\"#cast-to-numeric\">CAST to NUMERIC</a></h4>\n<p>CAST to NUMERIC returns either an INTEGER or a REAL, depending on the input:</p>\n<ul>\n<li>If the value looks like a well-formed integer and fits in 64 bits, the result is INTEGER.</li>\n<li>If the value looks like a floating-point number, the result is REAL.</li>\n<li>From REAL or INTEGER, the value is returned as-is.</li>\n</ul>\n<p>This is where INTEGER and NUMERIC affinity differ in CAST expressions:</p>\n<pre><code class=\"language-sql\">SELECT CAST(4.0 AS INTEGER), typeof(CAST(4.0 AS INTEGER));\n-- 4|integer\n\nSELECT CAST(4.0 AS NUMERIC), typeof(CAST(4.0 AS NUMERIC));\n-- 4.0|real\n</code></pre>\n<p><code>CAST(4.0 AS INTEGER)</code> truncates the real value to an integer, while <code>CAST(4.0 AS NUMERIC)</code> preserves the real type because the input is already a REAL.</p>\n<h2 id=\"comparison-and-type-affinity\"><a class=\"header\" href=\"#comparison-and-type-affinity\">Comparison and Type Affinity</a></h2>\n<p>When Turso compares two values, it may apply affinity conversions to the operands before performing the comparison. The rules depend on the affinities of the expressions being compared.</p>\n<h3 id=\"affinity-application-during-comparisons\"><a class=\"header\" href=\"#affinity-application-during-comparisons\">Affinity Application During Comparisons</a></h3>\n<p>Before a comparison is performed, the following rules are applied in order:</p>\n<ol>\n<li>\n<p><strong>If one operand has INTEGER, REAL, or NUMERIC affinity and the other has TEXT, BLOB, or no affinity</strong>: NUMERIC affinity is applied to the non-numeric operand. This means text that looks like a number is converted to a number before comparing.</p>\n</li>\n<li>\n<p><strong>If one operand has TEXT affinity and the other has no affinity</strong>: TEXT affinity is applied to the operand with no affinity.</p>\n</li>\n<li>\n<p><strong>Otherwise</strong>: No conversion is applied. Values are compared using their existing storage classes.</p>\n</li>\n</ol>\n<h3 id=\"comparison-sort-order\"><a class=\"header\" href=\"#comparison-sort-order\">Comparison Sort Order</a></h3>\n<p>When comparing values of different storage classes, Turso follows this ordering:</p>\n<ol>\n<li><strong>NULL</strong> is less than any other value. (Note: <code>NULL &lt; x</code> evaluates to NULL, not TRUE. Use <code>IS NULL</code> to test for nulls. This ordering applies to <code>ORDER BY</code> and similar contexts.)</li>\n<li><strong>INTEGER and REAL</strong> values are compared numerically with each other, and are less than any TEXT or BLOB value.</li>\n<li><strong>TEXT</strong> values are less than BLOB values. Text comparisons use the applicable collating sequence (default: BINARY).</li>\n<li><strong>BLOB</strong> values are compared byte-by-byte using <code>memcmp</code> ordering.</li>\n</ol>\n<h3 id=\"affinity-effects-on-comparisons\"><a class=\"header\" href=\"#affinity-effects-on-comparisons\">Affinity Effects on Comparisons</a></h3>\n<p>Column affinity can cause the same literal to compare differently depending on the column’s declared type:</p>\n<pre><code class=\"language-sql\">CREATE TABLE inventory (\n  label TEXT,\n  count NUMERIC\n);\nINSERT INTO inventory VALUES ('500', '500');\n\n-- TEXT column: integer 40 is converted to text '40' for comparison\n-- String comparison: '500' &gt; '40' (compares character by character)\nSELECT label &lt; 40, label &lt; 60, label &lt; 600 FROM inventory;\n-- 0|1|1\n\n-- NUMERIC column: stored as integer 500, numeric comparison\nSELECT count &lt; 40, count &lt; 60, count &lt; 600 FROM inventory;\n-- 0|0|1\n</code></pre>\n<p>In the TEXT column example, the integer literal <code>40</code> is converted to the string <code>'40'</code> before comparing. In string comparison, <code>'500' &lt; '60'</code> is true because <code>'5' &lt; '6'</code> lexicographically. In the NUMERIC column, the value is already stored as integer 500, so the comparison is purely numeric.</p>\n<h2 id=\"expression-affinity\"><a class=\"header\" href=\"#expression-affinity\">Expression Affinity</a></h2>\n<p>Expressions in SQL do not always carry an affinity. The rules for expression affinity are:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Expression</th><th>Affinity</th></tr>\n</thead>\n<tbody>\n<tr><td>Column reference</td><td>Same affinity as the column</td></tr>\n<tr><td><code>CAST(expr AS type)</code></td><td>Affinity determined by the type name</td></tr>\n<tr><td>Unary <code>+</code> applied to a column (e.g., <code>+column</code>)</td><td>No affinity</td></tr>\n<tr><td>Any operator or function result</td><td>No affinity</td></tr>\n</tbody>\n</table>\n</div>\n<p>The unary <code>+</code> operator is a common technique to strip affinity from a column reference, forcing the value to be compared without automatic type coercion.</p>\n<h2 id=\"the-typeof-function\"><a class=\"header\" href=\"#the-typeof-function\">The typeof() Function</a></h2>\n<p>The <code>typeof()</code> function returns a string indicating the storage class of its argument. It is the primary tool for inspecting how Turso stores a value.</p>\n<pre><code class=\"language-sql\">SELECT typeof(42);                          -- integer\nSELECT typeof(3.14);                        -- real\nSELECT typeof('hello');                     -- text\nSELECT typeof(x'FF');                       -- blob\nSELECT typeof(NULL);                        -- null\nSELECT typeof(CAST('42' AS INTEGER));       -- integer\n</code></pre>\n<p>The return value is always one of: <code>'null'</code>, <code>'integer'</code>, <code>'real'</code>, <code>'text'</code>, or <code>'blob'</code>.</p>\n<div style=\"break-before: page; page-break-before: always;\"></div>\n<h1 id=\"in-and-between\"><a class=\"header\" href=\"#in-and-between\">IN and BETWEEN</a></h1>\n<p>Turso supports the <code>IN</code>, <code>NOT IN</code>, <code>BETWEEN</code>, and <code>NOT BETWEEN</code> operators for testing whether a value belongs to a set or falls within a range. These operators are commonly used in <code>WHERE</code> clauses to filter rows, but they can appear anywhere an expression is allowed.</p>\n<h2 id=\"in-1\"><a class=\"header\" href=\"#in-1\">IN</a></h2>\n<h3 id=\"syntax-23\"><a class=\"header\" href=\"#syntax-23\">Syntax</a></h3>\n<pre><code class=\"language-sql\">expr [NOT] IN (value [, ...])\nexpr [NOT] IN (select-stmt)\n</code></pre>\n<h3 id=\"description-21\"><a class=\"header\" href=\"#description-21\">Description</a></h3>\n<p>The <code>IN</code> operator tests whether the left-hand expression is equal to any value in the right-hand set. The set can be a parenthesized list of scalar values or the result of a subquery. <code>IN</code> returns 1 (true) if a match is found and 0 (false) if no match is found, subject to the NULL handling rules described below.</p>\n<p><code>NOT IN</code> is the logical negation of <code>IN</code>. It returns 1 (true) when the left-hand expression does not match any value in the set, and 0 (false) when a match is found.</p>\n<p>When the right-hand side is a subquery, it must return a single column. Each row returned by the subquery is treated as one element in the set.</p>\n<p>Turso also allows an empty parenthesized list <code>()</code>. When the right-hand side is an empty set, <code>IN</code> always returns 0 (false) and <code>NOT IN</code> always returns 1 (true), regardless of the left-hand operand – even if it is NULL.</p>\n<h3 id=\"null-handling-3\"><a class=\"header\" href=\"#null-handling-3\">NULL Handling</a></h3>\n<p>The interaction between <code>IN</code>/<code>NOT IN</code> and NULL values follows three-valued logic. The result depends on whether a match is found, whether the set contains NULL, and whether the left operand is NULL.</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Left operand</th><th>Set contains NULL</th><th>Match found</th><th>Empty set</th><th><code>IN</code> result</th><th><code>NOT IN</code> result</th></tr>\n</thead>\n<tbody>\n<tr><td>non-NULL</td><td>no</td><td>no</td><td>no</td><td>0</td><td>1</td></tr>\n<tr><td>non-NULL</td><td>no</td><td>yes</td><td>–</td><td>1</td><td>0</td></tr>\n<tr><td>non-NULL</td><td>yes</td><td>no</td><td>no</td><td>NULL</td><td>NULL</td></tr>\n<tr><td>non-NULL</td><td>yes</td><td>yes</td><td>–</td><td>1</td><td>0</td></tr>\n<tr><td>NULL</td><td>any</td><td>any</td><td>no</td><td>NULL</td><td>NULL</td></tr>\n<tr><td>any</td><td>any</td><td>–</td><td>yes</td><td>0</td><td>1</td></tr>\n</tbody>\n</table>\n</div>\n<p>Key rules to remember:</p>\n<ul>\n<li><strong>Match found</strong>: When the left operand is found in the set, <code>IN</code> returns 1 and <code>NOT IN</code> returns 0, regardless of any NULLs in the set.</li>\n<li><strong>No match, set contains NULL</strong>: When no match is found but the set contains NULL, the result is NULL for both <code>IN</code> and <code>NOT IN</code>. The NULL represents “unknown” – the value might match the unknown element.</li>\n<li><strong>Left operand is NULL</strong>: When the left operand is NULL and the set is non-empty, the result is always NULL (unknown).</li>\n<li><strong>Empty set</strong>: When the right-hand set is empty, <code>IN</code> returns 0 and <code>NOT IN</code> returns 1 – even if the left operand is NULL. An empty set cannot contain any value.</li>\n</ul>\n<h3 id=\"examples-21\"><a class=\"header\" href=\"#examples-21\">Examples</a></h3>\n<pre><code class=\"language-sql\">-- Basic IN with a value list\nSELECT 1 IN (1, 2, 3);\n-- 1\n\nSELECT 4 IN (1, 2, 3);\n-- 0\n</code></pre>\n<pre><code class=\"language-sql\">-- IN with string values\nSELECT 'apple' IN ('apple', 'banana', 'cherry');\n-- 1\n</code></pre>\n<pre><code class=\"language-sql\">-- NOT IN excludes matching values\nSELECT 'grape' NOT IN ('apple', 'banana', 'cherry');\n-- 1\n</code></pre>\n<pre><code class=\"language-sql\">-- IN with an empty list always returns 0\nSELECT 1 IN ();\n-- 0\n\n-- NOT IN with an empty list always returns 1\nSELECT NULL NOT IN ();\n-- 1\n</code></pre>\n<pre><code class=\"language-sql\">-- NULL handling: left operand is NULL\nSELECT NULL IN (1, 2, 3);\n-- NULL\n\n-- Match found despite NULLs in the set\nSELECT 1 IN (1, 2, NULL);\n-- 1\n\n-- No match and set contains NULL: result is NULL\nSELECT 4 IN (1, 2, NULL);\n-- NULL\n</code></pre>\n<pre><code class=\"language-sql\">-- Filter rows using IN with a value list\nCREATE TABLE products (\n  id INTEGER PRIMARY KEY,\n  name TEXT,\n  category TEXT,\n  price REAL,\n  stock INTEGER\n);\nINSERT INTO products VALUES (1, 'Widget', 'Hardware', 9.99, 100);\nINSERT INTO products VALUES (2, 'Gadget', 'Electronics', 24.99, 50);\nINSERT INTO products VALUES (3, 'Gizmo', 'Electronics', 49.99, 25);\nINSERT INTO products VALUES (4, 'Bolt', 'Hardware', 1.99, 500);\nINSERT INTO products VALUES (5, 'Sensor', 'Electronics', 14.99, 75);\n\nSELECT name, category FROM products\nWHERE category NOT IN ('Electronics');\n-- Widget|Hardware\n-- Bolt|Hardware\n</code></pre>\n<pre><code class=\"language-sql\">-- IN with a subquery\nCREATE TABLE products (\n  id INTEGER PRIMARY KEY,\n  name TEXT,\n  category TEXT,\n  price REAL,\n  stock INTEGER\n);\nINSERT INTO products VALUES (1, 'Widget', 'Hardware', 9.99, 100);\nINSERT INTO products VALUES (2, 'Gadget', 'Electronics', 24.99, 50);\nINSERT INTO products VALUES (3, 'Gizmo', 'Electronics', 49.99, 25);\nINSERT INTO products VALUES (4, 'Bolt', 'Hardware', 1.99, 500);\nINSERT INTO products VALUES (5, 'Sensor', 'Electronics', 14.99, 75);\n\nCREATE TABLE featured_ids (id INTEGER);\nINSERT INTO featured_ids VALUES (2);\nINSERT INTO featured_ids VALUES (4);\n\nSELECT name, price FROM products\nWHERE id IN (SELECT id FROM featured_ids);\n-- Gadget|24.99\n-- Bolt|1.99\n</code></pre>\n<pre><code class=\"language-sql\">-- NOT IN with a subquery to find customers with no pending orders\nCREATE TABLE orders (\n  id INTEGER PRIMARY KEY,\n  customer TEXT,\n  amount REAL,\n  status TEXT\n);\nINSERT INTO orders VALUES (1, 'Alice', 150.00, 'shipped');\nINSERT INTO orders VALUES (2, 'Bob', 75.50, 'pending');\nINSERT INTO orders VALUES (3, 'Alice', 200.00, 'delivered');\nINSERT INTO orders VALUES (4, 'Carol', 50.00, 'shipped');\nINSERT INTO orders VALUES (5, 'Bob', 300.00, 'delivered');\n\nSELECT customer, amount FROM orders\nWHERE customer NOT IN (\n  SELECT customer FROM orders WHERE status = 'pending'\n);\n-- Alice|150.0\n-- Alice|200.0\n-- Carol|50.0\n</code></pre>\n<h2 id=\"between-1\"><a class=\"header\" href=\"#between-1\">BETWEEN</a></h2>\n<h3 id=\"syntax-1-1\"><a class=\"header\" href=\"#syntax-1-1\">Syntax</a></h3>\n<pre><code class=\"language-sql\">expr [NOT] BETWEEN expr AND expr\n</code></pre>\n<h3 id=\"description-1-1\"><a class=\"header\" href=\"#description-1-1\">Description</a></h3>\n<p>The <code>BETWEEN</code> operator tests whether a value falls within an inclusive range. The expression:</p>\n<pre><code class=\"language-sql\">x BETWEEN y AND z\n</code></pre>\n<p>is equivalent to:</p>\n<pre><code class=\"language-sql\">x &gt;= y AND x &lt;= z\n</code></pre>\n<p>except that with <code>BETWEEN</code>, the <code>x</code> expression is evaluated only once. This makes no difference in results but can matter when <code>x</code> is a complex or expensive expression.</p>\n<p><code>NOT BETWEEN</code> inverts the test. The expression <code>x NOT BETWEEN y AND z</code> is equivalent to <code>x &lt; y OR x &gt; z</code>.</p>\n<p><code>BETWEEN</code> works with any data type that supports comparison: integers, reals, text (compared according to the active collation), and blobs.</p>\n<h3 id=\"examples-1-1\"><a class=\"header\" href=\"#examples-1-1\">Examples</a></h3>\n<pre><code class=\"language-sql\">-- Numeric range check\nSELECT 5 BETWEEN 1 AND 10;\n-- 1\n\nSELECT 15 BETWEEN 1 AND 10;\n-- 0\n</code></pre>\n<pre><code class=\"language-sql\">-- NOT BETWEEN\nSELECT 5 NOT BETWEEN 1 AND 10;\n-- 0\n\nSELECT 15 NOT BETWEEN 1 AND 10;\n-- 1\n</code></pre>\n<pre><code class=\"language-sql\">-- BETWEEN with text values (compared lexicographically)\nSELECT 'M' BETWEEN 'A' AND 'Z';\n-- 1\n\nSELECT 'banana' BETWEEN 'apple' AND 'cherry';\n-- 1\n</code></pre>\n<pre><code class=\"language-sql\">-- Filter products by price range\nCREATE TABLE products (\n  id INTEGER PRIMARY KEY,\n  name TEXT,\n  category TEXT,\n  price REAL,\n  stock INTEGER\n);\nINSERT INTO products VALUES (1, 'Widget', 'Hardware', 9.99, 100);\nINSERT INTO products VALUES (2, 'Gadget', 'Electronics', 24.99, 50);\nINSERT INTO products VALUES (3, 'Gizmo', 'Electronics', 49.99, 25);\nINSERT INTO products VALUES (4, 'Bolt', 'Hardware', 1.99, 500);\nINSERT INTO products VALUES (5, 'Sensor', 'Electronics', 14.99, 75);\n\nSELECT name, price FROM products\nWHERE price BETWEEN 10.00 AND 30.00;\n-- Gadget|24.99\n-- Sensor|14.99\n</code></pre>\n<pre><code class=\"language-sql\">-- NOT BETWEEN to find outlier prices\nCREATE TABLE products (\n  id INTEGER PRIMARY KEY,\n  name TEXT,\n  category TEXT,\n  price REAL,\n  stock INTEGER\n);\nINSERT INTO products VALUES (1, 'Widget', 'Hardware', 9.99, 100);\nINSERT INTO products VALUES (2, 'Gadget', 'Electronics', 24.99, 50);\nINSERT INTO products VALUES (3, 'Gizmo', 'Electronics', 49.99, 25);\nINSERT INTO products VALUES (4, 'Bolt', 'Hardware', 1.99, 500);\nINSERT INTO products VALUES (5, 'Sensor', 'Electronics', 14.99, 75);\n\nSELECT name, price FROM products\nWHERE price NOT BETWEEN 10.00 AND 30.00;\n-- Widget|9.99\n-- Gizmo|49.99\n-- Bolt|1.99\n</code></pre>\n<pre><code class=\"language-sql\">-- BETWEEN with date strings (ISO 8601 format sorts correctly)\nCREATE TABLE events (\n  id INTEGER PRIMARY KEY,\n  name TEXT,\n  event_date TEXT\n);\nINSERT INTO events VALUES (1, 'Launch', '2024-01-15');\nINSERT INTO events VALUES (2, 'Review', '2024-03-20');\nINSERT INTO events VALUES (3, 'Release', '2024-06-01');\nINSERT INTO events VALUES (4, 'Summit', '2024-09-10');\n\nSELECT name, event_date FROM events\nWHERE event_date BETWEEN '2024-01-01' AND '2024-06-30';\n-- Launch|2024-01-15\n-- Review|2024-03-20\n-- Release|2024-06-01\n</code></pre>\n<h2 id=\"combining-in-and-between\"><a class=\"header\" href=\"#combining-in-and-between\">Combining IN and BETWEEN</a></h2>\n<p><code>IN</code> and <code>BETWEEN</code> can be used together in the same <code>WHERE</code> clause, combined with <code>AND</code> and <code>OR</code>:</p>\n<pre><code class=\"language-sql\">CREATE TABLE orders (\n  id INTEGER PRIMARY KEY,\n  customer TEXT,\n  amount REAL,\n  status TEXT\n);\nINSERT INTO orders VALUES (1, 'Alice', 150.00, 'shipped');\nINSERT INTO orders VALUES (2, 'Bob', 75.50, 'pending');\nINSERT INTO orders VALUES (3, 'Alice', 200.00, 'delivered');\nINSERT INTO orders VALUES (4, 'Carol', 50.00, 'shipped');\nINSERT INTO orders VALUES (5, 'Bob', 300.00, 'delivered');\n\n-- Orders that are shipped or delivered with amount in a range\nSELECT customer, amount FROM orders\nWHERE status IN ('shipped', 'delivered')\n  AND amount BETWEEN 100.00 AND 250.00;\n-- Alice|150.0\n-- Alice|200.0\n</code></pre>\n<h2 id=\"operator-precedence-1\"><a class=\"header\" href=\"#operator-precedence-1\">Operator Precedence</a></h2>\n<p>Both <code>IN</code> and <code>BETWEEN</code> have defined positions in the operator precedence hierarchy. From highest to lowest among the comparison operators:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Precedence</th><th>Operators</th></tr>\n</thead>\n<tbody>\n<tr><td>Higher</td><td><code>&lt;</code>, <code>&gt;</code>, <code>&lt;=</code>, <code>&gt;=</code></td></tr>\n<tr><td></td><td><code>=</code>, <code>==</code>, <code>&lt;&gt;</code>, <code>IS</code>, <code>IS NOT</code></td></tr>\n<tr><td></td><td><code>BETWEEN ... AND ...</code></td></tr>\n<tr><td></td><td><code>IN</code>, <code>LIKE</code>, <code>GLOB</code>, <code>MATCH</code>, <code>REGEXP</code></td></tr>\n<tr><td>Lower</td><td><code>ISNULL</code>, <code>NOTNULL</code>, <code>NOT NULL</code></td></tr>\n</tbody>\n</table>\n</div>\n<p>The <code>NOT</code> keyword that precedes <code>IN</code> or <code>BETWEEN</code> is part of the operator itself (not a separate prefix operator) and does not change the precedence.</p>\n<div style=\"break-before: page; page-break-before: always;\"></div>\n<h1 id=\"collation\"><a class=\"header\" href=\"#collation\">Collation</a></h1>\n<h2 id=\"syntax-24\"><a class=\"header\" href=\"#syntax-24\">Syntax</a></h2>\n<pre><code class=\"language-sql\">expr COLLATE {BINARY | NOCASE | RTRIM}\n</code></pre>\n<p>In column definitions:</p>\n<pre><code class=\"language-sql\">column-name type-name COLLATE {BINARY | NOCASE | RTRIM}\n</code></pre>\n<p>In <code>ORDER BY</code> clauses:</p>\n<pre><code class=\"language-sql\">ORDER BY expr COLLATE {BINARY | NOCASE | RTRIM} [{ASC | DESC}]\n</code></pre>\n<p>In index definitions:</p>\n<pre><code class=\"language-sql\">CREATE INDEX index-name ON table-name (column-name COLLATE {BINARY | NOCASE | RTRIM})\n</code></pre>\n<h2 id=\"description-22\"><a class=\"header\" href=\"#description-22\">Description</a></h2>\n<p>A collation sequence determines how text values are compared and sorted. The <code>COLLATE</code> operator is a unary postfix operator that assigns a collation sequence to an expression, overriding whatever collation would otherwise apply.</p>\n<p>Collation sequences affect only text comparisons. Numeric values are always compared numerically, and BLOB values are always compared byte-by-byte regardless of any collation setting.</p>\n<h2 id=\"built-in-collation-sequences\"><a class=\"header\" href=\"#built-in-collation-sequences\">Built-in Collation Sequences</a></h2>\n<p>Turso provides three built-in collation sequences:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Collation</th><th>Behavior</th></tr>\n</thead>\n<tbody>\n<tr><td><code>BINARY</code></td><td>Compares text byte-by-byte using raw byte values. This is the default collation for all columns. Uppercase letters sort before lowercase (<code>'A'</code> &lt; <code>'a'</code>).</td></tr>\n<tr><td><code>NOCASE</code></td><td>Same as <code>BINARY</code>, except the 26 uppercase ASCII letters (<code>A</code>-<code>Z</code>) are folded to their lowercase equivalents before comparison. Only ASCII characters are folded; accented or non-Latin characters are not affected.</td></tr>\n<tr><td><code>RTRIM</code></td><td>Same as <code>BINARY</code>, except trailing space characters are ignored. <code>'abc'</code> and <code>'abc   '</code> are considered equal.</td></tr>\n</tbody>\n</table>\n</div>\n<p>Every column has an associated collation sequence. If no <code>COLLATE</code> clause is specified in the column definition, the default is <code>BINARY</code>.</p>\n<h2 id=\"collate-in-column-definitions\"><a class=\"header\" href=\"#collate-in-column-definitions\">COLLATE in Column Definitions</a></h2>\n<p>A <code>COLLATE</code> clause in a column definition sets the default collation for that column. This collation is used whenever the column appears in a comparison or <code>ORDER BY</code> without an explicit <code>COLLATE</code> operator.</p>\n<pre><code class=\"language-sql\">CREATE TABLE users (\n  id INTEGER PRIMARY KEY,\n  email TEXT COLLATE NOCASE,\n  username TEXT\n);\nINSERT INTO users VALUES (1, 'Alice@Example.com', 'alice');\nINSERT INTO users VALUES (2, 'bob@example.com', 'bob');\nINSERT INTO users VALUES (3, 'CAROL@EXAMPLE.COM', 'carol');\n\n-- NOCASE column: matches regardless of case\nSELECT id, email FROM users WHERE email = 'alice@example.com';\n-- 1|Alice@Example.com\n</code></pre>\n<p>Because <code>email</code> is declared with <code>COLLATE NOCASE</code>, the comparison <code>email = 'alice@example.com'</code> matches the stored value <code>'Alice@Example.com'</code>.</p>\n<h2 id=\"collate-operator-in-expressions\"><a class=\"header\" href=\"#collate-operator-in-expressions\">COLLATE Operator in Expressions</a></h2>\n<p>The <code>COLLATE</code> operator can be applied to any expression to control how a comparison is performed. It has very high precedence – higher than any binary operator – so it binds tightly to its operand.</p>\n<pre><code class=\"language-sql\">-- Default BINARY comparison is case-sensitive\nSELECT 'Hello' = 'hello';\n-- 0\n\n-- COLLATE NOCASE makes the comparison case-insensitive\nSELECT 'Hello' = 'hello' COLLATE NOCASE;\n-- 1\n\n-- COLLATE RTRIM ignores trailing spaces\nSELECT 'hello   ' = 'hello' COLLATE RTRIM;\n-- 1\n</code></pre>\n<p>When used in a <code>WHERE</code> clause, the <code>COLLATE</code> operator overrides the column’s default collation:</p>\n<pre><code class=\"language-sql\">CREATE TABLE items (\n  id INTEGER PRIMARY KEY,\n  label TEXT\n);\nINSERT INTO items VALUES (1, 'abc  ');\nINSERT INTO items VALUES (2, 'abc');\nINSERT INTO items VALUES (3, 'ABC');\nINSERT INTO items VALUES (4, 'xyz');\n\n-- Without COLLATE, default BINARY collation is exact\nSELECT id FROM items WHERE label = 'abc';\n-- 2\n\n-- With COLLATE RTRIM, trailing spaces are ignored\nSELECT id FROM items WHERE label = 'abc' COLLATE RTRIM;\n-- 1\n-- 2\n</code></pre>\n<h2 id=\"rules-for-choosing-a-collation-in-comparisons\"><a class=\"header\" href=\"#rules-for-choosing-a-collation-in-comparisons\">Rules for Choosing a Collation in Comparisons</a></h2>\n<p>When two values are compared using a binary operator (<code>=</code>, <code>&lt;</code>, <code>&gt;</code>, <code>&lt;=</code>, <code>&gt;=</code>, <code>&lt;&gt;</code>, <code>IS</code>, <code>IS NOT</code>), the collation sequence is determined by these rules, applied in order:</p>\n<ol>\n<li>If either operand has an explicit <code>COLLATE</code> operator, that collation is used. If both operands have explicit <code>COLLATE</code> operators, the leftmost one wins.</li>\n<li>If either operand is a column with a defined collation (from <code>CREATE TABLE</code>), that column’s collation is used. If both operands are columns, the left operand’s collation takes precedence.</li>\n<li>Otherwise, the <code>BINARY</code> collation is used.</li>\n</ol>\n<pre><code class=\"language-sql\">CREATE TABLE contacts (\n  id INTEGER PRIMARY KEY,\n  name TEXT COLLATE NOCASE,\n  tag TEXT\n);\nINSERT INTO contacts VALUES (1, 'alice', 'alpha');\nINSERT INTO contacts VALUES (2, 'Alice', 'Alpha');\nINSERT INTO contacts VALUES (3, 'BOB', 'beta');\n\n-- Column collation applies: name is NOCASE, so 'alice' matches 'Alice'\nSELECT id FROM contacts WHERE name = 'alice';\n-- 1\n-- 2\n\n-- Default BINARY: tag has no COLLATE, so comparison is case-sensitive\nSELECT id FROM contacts WHERE tag = 'alpha';\n-- 1\n\n-- Explicit COLLATE overrides defaults\nSELECT id FROM contacts WHERE tag = 'alpha' COLLATE NOCASE;\n-- 1\n-- 2\n</code></pre>\n<h2 id=\"collate-in-order-by\"><a class=\"header\" href=\"#collate-in-order-by\">COLLATE in ORDER BY</a></h2>\n<p>The <code>ORDER BY</code> clause uses collation to determine sort order for text values. The collation is chosen as follows:</p>\n<ul>\n<li>If the <code>ORDER BY</code> expression has an explicit <code>COLLATE</code> clause, that collation is used.</li>\n<li>If the <code>ORDER BY</code> expression is a column, the column’s defined collation is used.</li>\n<li>Otherwise, <code>BINARY</code> is used.</li>\n</ul>\n<pre><code class=\"language-sql\">CREATE TABLE contacts (\n  id INTEGER PRIMARY KEY,\n  name TEXT COLLATE NOCASE,\n  tag TEXT\n);\nINSERT INTO contacts VALUES (1, 'alice', 'alpha');\nINSERT INTO contacts VALUES (2, 'Alice', 'Alpha');\nINSERT INTO contacts VALUES (3, 'BOB', 'beta');\nINSERT INTO contacts VALUES (4, 'bob', 'Beta');\nINSERT INTO contacts VALUES (5, 'Carol', 'gamma');\n\n-- ORDER BY tag with default BINARY: uppercase sorts before lowercase\nSELECT id, tag FROM contacts ORDER BY tag COLLATE BINARY;\n-- 2|Alpha\n-- 4|Beta\n-- 1|alpha\n-- 3|beta\n-- 5|gamma\n\n-- ORDER BY tag with NOCASE: case-insensitive sort\nSELECT id, tag FROM contacts ORDER BY tag COLLATE NOCASE;\n-- 1|alpha\n-- 2|Alpha\n-- 3|beta\n-- 4|Beta\n-- 5|gamma\n</code></pre>\n<h2 id=\"collate-in-create-index\"><a class=\"header\" href=\"#collate-in-create-index\">COLLATE in CREATE INDEX</a></h2>\n<p>A <code>COLLATE</code> clause can be specified on indexed columns. This is useful when you want an index to support case-insensitive lookups on a column that does not itself have a <code>NOCASE</code> collation.</p>\n<pre><code class=\"language-sql\">CREATE TABLE users (\n  id INTEGER PRIMARY KEY,\n  email TEXT,\n  username TEXT\n);\nINSERT INTO users VALUES (1, 'Alice@Example.com', 'alice');\nINSERT INTO users VALUES (2, 'bob@example.com', 'bob');\nINSERT INTO users VALUES (3, 'CAROL@EXAMPLE.COM', 'carol');\nCREATE INDEX idx_email ON users(email COLLATE NOCASE);\n\nSELECT id, email FROM users WHERE email = 'alice@example.com' COLLATE NOCASE;\n-- 1|Alice@Example.com\n</code></pre>\n<h2 id=\"collate-with-group-by-and-distinct\"><a class=\"header\" href=\"#collate-with-group-by-and-distinct\">COLLATE with GROUP BY and DISTINCT</a></h2>\n<p>Collation sequences also affect grouping. When <code>COLLATE NOCASE</code> is applied to a grouping expression, values that differ only in case are placed in the same group.</p>\n<pre><code class=\"language-sql\">CREATE TABLE words (id INTEGER PRIMARY KEY, word TEXT);\nINSERT INTO words VALUES (1, 'apple');\nINSERT INTO words VALUES (2, 'Apple');\nINSERT INTO words VALUES (3, 'APPLE');\nINSERT INTO words VALUES (4, 'banana');\n\n-- GROUP BY with NOCASE: all case variants of 'apple' form one group\nSELECT word COLLATE NOCASE, COUNT(*) AS cnt\nFROM words\nGROUP BY word COLLATE NOCASE;\n-- apple|3\n-- banana|1\n\n-- DISTINCT with NOCASE: collapses case variants\nSELECT DISTINCT word COLLATE NOCASE FROM words;\n-- apple\n-- banana\n</code></pre>\n<h2 id=\"collate-with-in\"><a class=\"header\" href=\"#collate-with-in\">COLLATE with IN</a></h2>\n<p>The <code>COLLATE</code> operator can be combined with the <code>IN</code> operator. Attach <code>COLLATE</code> to the left-hand expression to control how membership is tested.</p>\n<pre><code class=\"language-sql\">SELECT 'hello' COLLATE NOCASE IN ('Hello', 'World');\n-- 1\n</code></pre>\n<h2 id=\"compatibility-16\"><a class=\"header\" href=\"#compatibility-16\">Compatibility</a></h2>\n<p>Turso supports the three built-in collation sequences (<code>BINARY</code>, <code>NOCASE</code>, <code>RTRIM</code>) and the <code>COLLATE</code> operator in expressions, column definitions, <code>ORDER BY</code>, and <code>CREATE INDEX</code>. Custom collation sequences registered via the C API (<code>sqlite3_create_collation</code>) are not supported. The <code>PRAGMA collation_list</code> command is not available.</p>\n<div style=\"break-before: page; page-break-before: always;\"></div>\n<h1 id=\"scalar-functions\"><a class=\"header\" href=\"#scalar-functions\">Scalar Functions</a></h1>\n<p>Scalar functions accept zero or more arguments and return a single value. They can be used anywhere an expression is allowed: in SELECT lists, WHERE clauses, ORDER BY, and so on.</p>\n<hr>\n<h2 id=\"abs\"><a class=\"header\" href=\"#abs\">abs</a></h2>\n<p><strong>abs(X) -&gt; numeric</strong></p>\n<p>Returns the absolute value of X. Returns NULL if X is NULL. Returns 0.0 if X is a string that cannot be converted to a number.</p>\n<pre><code class=\"language-sql\">SELECT abs(-42);    -- 42\nSELECT abs(3.14);   -- 3.14\nSELECT abs(NULL);   -- NULL\n</code></pre>\n<h2 id=\"char\"><a class=\"header\" href=\"#char\">char</a></h2>\n<p><strong>char(X1, X2, …, XN) -&gt; text</strong></p>\n<p>Returns a string composed of characters having the Unicode code points given by the integer arguments.</p>\n<pre><code class=\"language-sql\">SELECT char(72, 101, 108, 108, 111);  -- 'Hello'\nSELECT char(9731);                     -- snowman character\n</code></pre>\n<h2 id=\"coalesce-1\"><a class=\"header\" href=\"#coalesce-1\">coalesce</a></h2>\n<p><strong>coalesce(X, Y, …) -&gt; value</strong></p>\n<p>Returns the first non-NULL argument. Requires at least two arguments. Returns NULL only if every argument is NULL.</p>\n<pre><code class=\"language-sql\">SELECT coalesce(NULL, NULL, 'hello');  -- 'hello'\nSELECT coalesce(1, 2, 3);             -- 1\n</code></pre>\n<h2 id=\"concat\"><a class=\"header\" href=\"#concat\">concat</a></h2>\n<p><strong>concat(X, …) -&gt; text</strong></p>\n<p>Returns a string formed by concatenating the text representations of all non-NULL arguments. NULL arguments are silently skipped. Returns an empty string if all arguments are NULL.</p>\n<pre><code class=\"language-sql\">SELECT concat('Hello', ' ', 'World');  -- 'Hello World'\nSELECT concat(NULL, 'hello');          -- 'hello'\n</code></pre>\n<h2 id=\"concat_ws\"><a class=\"header\" href=\"#concat_ws\">concat_ws</a></h2>\n<p><strong>concat_ws(separator, X, …) -&gt; text</strong></p>\n<p>Returns a string formed by concatenating the non-NULL arguments after the first, using the first argument as a separator. Returns NULL if the separator is NULL.</p>\n<pre><code class=\"language-sql\">SELECT concat_ws('-', '2024', '01', '15');  -- '2024-01-15'\nSELECT concat_ws(', ', 'Alice', 'Bob');     -- 'Alice, Bob'\nSELECT concat_ws(NULL, 'a', 'b');           -- NULL\n</code></pre>\n<h2 id=\"format\"><a class=\"header\" href=\"#format\">format</a></h2>\n<p><strong>format(FORMAT, …) -&gt; text</strong></p>\n<p>Returns a string formed by substituting arguments into the format string, following <code>printf</code> conventions. Supports <code>%s</code> (string), <code>%d</code> (integer), <code>%f</code> (floating-point), and other standard format specifiers. Returns NULL if FORMAT is NULL.</p>\n<pre><code class=\"language-sql\">SELECT format('%s has %d items', 'cart', 5);  -- 'cart has 5 items'\nSELECT format('%.2f', 3.14159);               -- '3.14'\n</code></pre>\n<h2 id=\"glob-1\"><a class=\"header\" href=\"#glob-1\">glob</a></h2>\n<p><strong>glob(pattern, string) -&gt; integer</strong></p>\n<p>Tests whether the string matches the glob pattern. Equivalent to the expression <code>string GLOB pattern</code>. Returns 1 for a match, 0 otherwise. Glob patterns use <code>*</code> to match any sequence of characters and <code>?</code> to match any single character. Matching is case-sensitive.</p>\n<pre><code class=\"language-sql\">SELECT glob('*ello', 'Hello');  -- 1\nSELECT glob('H?llo', 'Hello');  -- 1\n</code></pre>\n<h2 id=\"hex\"><a class=\"header\" href=\"#hex\">hex</a></h2>\n<p><strong>hex(X) -&gt; text</strong></p>\n<p>Returns an uppercase hexadecimal string rendering of the content of X. If X is a text string, each character is converted to its UTF-8 byte representation. If X is a blob, the raw bytes are converted.</p>\n<pre><code class=\"language-sql\">SELECT hex('Hello');       -- '48656C6C6F'\nSELECT hex(X'CAFE');       -- 'CAFE'\nSELECT hex(zeroblob(4));   -- '00000000'\n</code></pre>\n<h2 id=\"ifnull-1\"><a class=\"header\" href=\"#ifnull-1\">ifnull</a></h2>\n<p><strong>ifnull(X, Y) -&gt; value</strong></p>\n<p>Returns X if X is not NULL, otherwise returns Y. Equivalent to <code>coalesce(X, Y)</code> but restricted to exactly two arguments.</p>\n<pre><code class=\"language-sql\">SELECT ifnull(NULL, 'backup');  -- 'backup'\nSELECT ifnull('value', 42);    -- 'value'\n</code></pre>\n<h2 id=\"iif-1\"><a class=\"header\" href=\"#iif-1\">iif</a></h2>\n<p><strong>iif(condition, true_value, false_value) -&gt; value</strong></p>\n<p>Returns true_value if condition is true, or false_value if condition is false or NULL. The <code>if</code> keyword is an alias for <code>iif</code>.</p>\n<pre><code class=\"language-sql\">SELECT iif(1, 'yes', 'no');   -- 'yes'\nSELECT iif(0, 'yes', 'no');   -- 'no'\nSELECT if(1 &gt; 0, 'positive', 'non-positive');  -- 'positive'\n</code></pre>\n<h2 id=\"instr\"><a class=\"header\" href=\"#instr\">instr</a></h2>\n<p><strong>instr(X, Y) -&gt; integer</strong></p>\n<p>Returns the 1-based position of the first occurrence of Y within X, or 0 if Y is not found. Returns NULL if either argument is NULL. Both arguments must be the same type (both text or both blob).</p>\n<pre><code class=\"language-sql\">SELECT instr('Hello World', 'World');  -- 7\nSELECT instr('Hello World', 'xyz');    -- 0\nSELECT instr('hello', NULL);           -- NULL\n</code></pre>\n<h2 id=\"last_insert_rowid\"><a class=\"header\" href=\"#last_insert_rowid\">last_insert_rowid</a></h2>\n<p><strong>last_insert_rowid() -&gt; integer</strong></p>\n<p>Returns the ROWID of the most recent successful INSERT into a rowid table from the same database connection. Returns 0 if no INSERT has occurred.</p>\n<pre><code class=\"language-sql\">SELECT last_insert_rowid();  -- 0 (before any inserts)\n</code></pre>\n<h2 id=\"length\"><a class=\"header\" href=\"#length\">length</a></h2>\n<p><strong>length(X) -&gt; integer</strong></p>\n<p>Returns the number of characters in X if X is a text string, or the number of bytes if X is a blob. Returns NULL if X is NULL.</p>\n<pre><code class=\"language-sql\">SELECT length('Hello');           -- 5\nSELECT length(X'0102030405');     -- 5\nSELECT length(NULL);              -- NULL\n</code></pre>\n<h2 id=\"like-1\"><a class=\"header\" href=\"#like-1\">like</a></h2>\n<p><strong>like(pattern, string) -&gt; integer</strong></p>\n<p>Tests whether the string matches the LIKE pattern. Equivalent to <code>string LIKE pattern</code>. The <code>%</code> wildcard matches any sequence of characters and <code>_</code> matches any single character. Matching is case-insensitive for ASCII characters.</p>\n<pre><code class=\"language-sql\">SELECT like('H%', 'Hello');     -- 1\nSELECT like('H_llo', 'Hello');  -- 1\nSELECT like('%world%', 'Hello World');  -- 1\n</code></pre>\n<h2 id=\"likelihood\"><a class=\"header\" href=\"#likelihood\">likelihood</a></h2>\n<p><strong>likelihood(X, P) -&gt; value</strong></p>\n<p>Returns X unchanged. The second argument P is a probability hint (a floating-point number between 0.0 and 1.0) for the query planner, indicating the likelihood that X is true. This function has no effect on the result, only on query optimization.</p>\n<pre><code class=\"language-sql\">SELECT likelihood(1, 0.5);  -- 1\n</code></pre>\n<h2 id=\"likely\"><a class=\"header\" href=\"#likely\">likely</a></h2>\n<p><strong>likely(X) -&gt; value</strong></p>\n<p>Returns X unchanged. Provides a hint to the query planner that X is usually true (equivalent to <code>likelihood(X, 0.9375)</code>). This function has no effect on the result, only on query optimization.</p>\n<pre><code class=\"language-sql\">SELECT likely(1);  -- 1\n</code></pre>\n<h2 id=\"lower\"><a class=\"header\" href=\"#lower\">lower</a></h2>\n<p><strong>lower(X) -&gt; text</strong></p>\n<p>Returns a copy of X with all ASCII uppercase characters converted to lowercase. Non-ASCII characters are unchanged.</p>\n<pre><code class=\"language-sql\">SELECT lower('HELLO');       -- 'hello'\nSELECT lower('Hello World'); -- 'hello world'\n</code></pre>\n<h2 id=\"ltrim-rtrim-trim\"><a class=\"header\" href=\"#ltrim-rtrim-trim\">ltrim, rtrim, trim</a></h2>\n<p><strong>ltrim(X) -&gt; text</strong>\n<strong>ltrim(X, Y) -&gt; text</strong>\n<strong>rtrim(X) -&gt; text</strong>\n<strong>rtrim(X, Y) -&gt; text</strong>\n<strong>trim(X) -&gt; text</strong>\n<strong>trim(X, Y) -&gt; text</strong></p>\n<p><code>ltrim</code> removes characters from the left side of X. <code>rtrim</code> removes from the right side. <code>trim</code> removes from both sides. With one argument, spaces are removed. With two arguments, all characters found in Y are removed from the respective side(s).</p>\n<pre><code class=\"language-sql\">SELECT ltrim('   Hello');          -- 'Hello'\nSELECT ltrim('xxxHello', 'x');     -- 'Hello'\nSELECT rtrim('Hello   ');          -- 'Hello'\nSELECT rtrim('Helloxxxx', 'x');    -- 'Hello'\nSELECT trim('  Hello  ');          -- 'Hello'\nSELECT trim('xxHelloxx', 'x');     -- 'Hello'\n</code></pre>\n<h2 id=\"max-scalar\"><a class=\"header\" href=\"#max-scalar\">max (scalar)</a></h2>\n<p><strong>max(X, Y, …) -&gt; value</strong></p>\n<p>Returns the argument with the maximum value when given two or more arguments. Uses the standard comparison rules to determine ordering. Returns NULL if any argument is NULL. (With a single argument, <code>max</code> acts as an aggregate function instead.)</p>\n<pre><code class=\"language-sql\">SELECT max(1, 5, 3);           -- 5\nSELECT max('alpha', 'beta');   -- 'beta'\n</code></pre>\n<h2 id=\"min-scalar\"><a class=\"header\" href=\"#min-scalar\">min (scalar)</a></h2>\n<p><strong>min(X, Y, …) -&gt; value</strong></p>\n<p>Returns the argument with the minimum value when given two or more arguments. Uses the standard comparison rules to determine ordering. Returns NULL if any argument is NULL. (With a single argument, <code>min</code> acts as an aggregate function instead.)</p>\n<pre><code class=\"language-sql\">SELECT min(1, 5, 3);           -- 1\nSELECT min('alpha', 'beta');   -- 'alpha'\n</code></pre>\n<h2 id=\"nullif-1\"><a class=\"header\" href=\"#nullif-1\">nullif</a></h2>\n<p><strong>nullif(X, Y) -&gt; value</strong></p>\n<p>Returns X if X and Y are different, or NULL if they are equal. Useful for converting sentinel values into NULLs.</p>\n<pre><code class=\"language-sql\">SELECT nullif(5, 5);    -- NULL\nSELECT nullif(5, 3);    -- 5\nSELECT nullif('', '');   -- NULL\n</code></pre>\n<h2 id=\"octet_length\"><a class=\"header\" href=\"#octet_length\">octet_length</a></h2>\n<p><strong>octet_length(X) -&gt; integer</strong></p>\n<p>Returns the number of bytes in X. Unlike <code>length</code>, which counts characters for text values, <code>octet_length</code> always counts bytes. Returns NULL if X is NULL.</p>\n<pre><code class=\"language-sql\">SELECT octet_length('Hello');           -- 5\nSELECT octet_length(X'0102030405');     -- 5\n</code></pre>\n<h2 id=\"printf\"><a class=\"header\" href=\"#printf\">printf</a></h2>\n<p><strong>printf(FORMAT, …) -&gt; text</strong></p>\n<p>Alias for <code>format</code>. Returns a string formed by substituting arguments into the format string using <code>printf</code> conventions. Returns NULL if FORMAT is NULL.</p>\n<pre><code class=\"language-sql\">SELECT printf('%d items', 5);    -- '5 items'\nSELECT printf('%.2f', 3.14159);  -- '3.14'\n</code></pre>\n<h2 id=\"quote\"><a class=\"header\" href=\"#quote\">quote</a></h2>\n<p><strong>quote(X) -&gt; text</strong></p>\n<p>Returns a string that is the SQL literal representation of X. Text strings are surrounded by single quotes with internal quotes doubled. Blobs are returned as hex literals. NULL returns the string <code>NULL</code>. Integers and reals are returned as-is.</p>\n<pre><code class=\"language-sql\">SELECT quote('Hello');          -- 'Hello'  (with enclosing quotes)\nSELECT quote(3.14);             -- 3.14\nSELECT quote(NULL);             -- NULL\nSELECT quote(X'48656C6C6F');    -- X'48656C6C6F'\n</code></pre>\n<h2 id=\"random\"><a class=\"header\" href=\"#random\">random</a></h2>\n<p><strong>random() -&gt; integer</strong></p>\n<p>Returns a pseudo-random integer between -9223372036854775808 and +9223372036854775807. A different value is returned each time the function is called.</p>\n<pre><code class=\"language-sql\">SELECT typeof(random());  -- 'integer'\n</code></pre>\n<h2 id=\"randomblob\"><a class=\"header\" href=\"#randomblob\">randomblob</a></h2>\n<p><strong>randomblob(N) -&gt; blob</strong></p>\n<p>Returns an N-byte blob containing pseudo-random bytes. Useful for generating unique identifiers or random data.</p>\n<pre><code class=\"language-sql\">SELECT length(randomblob(16));  -- 16\nSELECT hex(randomblob(4));      -- (random 8-character hex string)\n</code></pre>\n<h2 id=\"replace\"><a class=\"header\" href=\"#replace\">replace</a></h2>\n<p><strong>replace(X, Y, Z) -&gt; text</strong></p>\n<p>Returns a copy of X with every occurrence of Y replaced by Z. If Y is an empty string, X is returned unchanged.</p>\n<pre><code class=\"language-sql\">SELECT replace('Hello World', 'World', 'Turso');  -- 'Hello Turso'\nSELECT replace('aabbcc', 'bb', 'XX');              -- 'aaXXcc'\n</code></pre>\n<h2 id=\"round\"><a class=\"header\" href=\"#round\">round</a></h2>\n<p><strong>round(X) -&gt; real</strong>\n<strong>round(X, Y) -&gt; real</strong></p>\n<p>Rounds X to Y decimal places. If Y is omitted, it defaults to 0. The result is always a floating-point value.</p>\n<pre><code class=\"language-sql\">SELECT round(3.14159);     -- 3.0\nSELECT round(3.14159, 2);  -- 3.14\nSELECT round(123.5);       -- 124.0\n</code></pre>\n<h2 id=\"sign\"><a class=\"header\" href=\"#sign\">sign</a></h2>\n<p><strong>sign(X) -&gt; integer</strong></p>\n<p>Returns -1 for negative values, 0 for zero, or +1 for positive values. Returns NULL if X is NULL or is a string or blob that cannot be converted to a number.</p>\n<pre><code class=\"language-sql\">SELECT sign(-42);   -- -1\nSELECT sign(0);     -- 0\nSELECT sign(42);    -- 1\nSELECT sign(NULL);  -- NULL\n</code></pre>\n<h2 id=\"soundex\"><a class=\"header\" href=\"#soundex\">soundex</a></h2>\n<p><strong>soundex(X) -&gt; text</strong></p>\n<p>Returns the Soundex encoding of X. The Soundex encoding is a four-character string that represents the phonetic sound of the input. Returns <code>?000</code> if X is NULL or contains no ASCII letters.</p>\n<pre><code class=\"language-sql\">SELECT soundex('Robert');   -- 'R163'\nSELECT soundex('Rupert');   -- 'R163'\n</code></pre>\n<h2 id=\"sqlite_source_id\"><a class=\"header\" href=\"#sqlite_source_id\">sqlite_source_id</a></h2>\n<p><strong>sqlite_source_id() -&gt; text</strong></p>\n<p>Returns a string that identifies the specific version and build of the library. The format includes a date, time, and a SHA hash.</p>\n<pre><code class=\"language-sql\">SELECT sqlite_source_id();\n-- e.g. '2026-02-11 18:26:30 fd3ab2fd48b711aa9bec80562dd1175ce10f4d9a'\n</code></pre>\n<h2 id=\"sqlite_version\"><a class=\"header\" href=\"#sqlite_version\">sqlite_version</a></h2>\n<p><strong>sqlite_version() -&gt; text</strong></p>\n<p>Returns the version string for the SQLite-compatible library.</p>\n<pre><code class=\"language-sql\">SELECT sqlite_version();  -- e.g. '3.50.4'\n</code></pre>\n<h2 id=\"substr--substring\"><a class=\"header\" href=\"#substr--substring\">substr / substring</a></h2>\n<p><strong>substr(X, Y) -&gt; text</strong>\n<strong>substr(X, Y, Z) -&gt; text</strong>\n<strong>substring(X, Y) -&gt; text</strong>\n<strong>substring(X, Y, Z) -&gt; text</strong></p>\n<p>Returns a substring of X starting at position Y (1-based). If Z is given, it specifies the length of the substring; otherwise the substring extends to the end of the string. <code>substring</code> is an alias for <code>substr</code>.</p>\n<p>If Y is negative, the substring starts that many characters from the end. If Z is negative, the substring consists of the Z characters preceding (and including) position Y.</p>\n<pre><code class=\"language-sql\">SELECT substr('Hello World', 7);      -- 'World'\nSELECT substr('Hello World', 1, 5);   -- 'Hello'\nSELECT substring('Hello World', 7);   -- 'World'\n</code></pre>\n<h2 id=\"typeof\"><a class=\"header\" href=\"#typeof\">typeof</a></h2>\n<p><strong>typeof(X) -&gt; text</strong></p>\n<p>Returns the type of X as a string. The possible return values are <code>'null'</code>, <code>'integer'</code>, <code>'real'</code>, <code>'text'</code>, and <code>'blob'</code>.</p>\n<pre><code class=\"language-sql\">SELECT typeof(42);        -- 'integer'\nSELECT typeof(3.14);      -- 'real'\nSELECT typeof('Hello');   -- 'text'\nSELECT typeof(NULL);      -- 'null'\nSELECT typeof(X'CAFE');   -- 'blob'\n</code></pre>\n<h2 id=\"unhex\"><a class=\"header\" href=\"#unhex\">unhex</a></h2>\n<p><strong>unhex(X) -&gt; blob</strong>\n<strong>unhex(X, Y) -&gt; blob</strong></p>\n<p>Converts the hexadecimal string X into a blob. Returns NULL if X contains characters that are not hexadecimal digits, unless those characters appear in Y (the set of characters to ignore). Returns NULL if X or Y is NULL.</p>\n<pre><code class=\"language-sql\">SELECT unhex('48656C6C6F');      -- Hello (as blob)\nSELECT hex(unhex('48656C6C6F')); -- '48656C6C6F'\nSELECT unhex('48GG', 'G');       -- H (as blob, 'G' chars are ignored)\n</code></pre>\n<h2 id=\"unicode\"><a class=\"header\" href=\"#unicode\">unicode</a></h2>\n<p><strong>unicode(X) -&gt; integer</strong></p>\n<p>Returns the numeric Unicode code point of the first character of X. Returns NULL if X is an empty string.</p>\n<pre><code class=\"language-sql\">SELECT unicode('A');     -- 65\nSELECT unicode('Hello'); -- 72\n</code></pre>\n<h2 id=\"unlikely\"><a class=\"header\" href=\"#unlikely\">unlikely</a></h2>\n<p><strong>unlikely(X) -&gt; value</strong></p>\n<p>Returns X unchanged. Provides a hint to the query planner that X is usually false (equivalent to <code>likelihood(X, 0.0625)</code>). This function has no effect on the result, only on query optimization.</p>\n<pre><code class=\"language-sql\">SELECT unlikely(1);  -- 1\n</code></pre>\n<h2 id=\"upper\"><a class=\"header\" href=\"#upper\">upper</a></h2>\n<p><strong>upper(X) -&gt; text</strong></p>\n<p>Returns a copy of X with all ASCII lowercase characters converted to uppercase. Non-ASCII characters are unchanged.</p>\n<pre><code class=\"language-sql\">SELECT upper('hello');        -- 'HELLO'\nSELECT upper('Hello World');  -- 'HELLO WORLD'\n</code></pre>\n<h2 id=\"zeroblob\"><a class=\"header\" href=\"#zeroblob\">zeroblob</a></h2>\n<p><strong>zeroblob(N) -&gt; blob</strong></p>\n<p>Returns a blob consisting of N zero-valued bytes (0x00). Useful as a placeholder for blob values to be filled in later with incremental I/O.</p>\n<pre><code class=\"language-sql\">SELECT length(zeroblob(10));  -- 10\nSELECT hex(zeroblob(4));      -- '00000000'\n</code></pre>\n<div style=\"break-before: page; page-break-before: always;\"></div>\n<h1 id=\"aggregate-functions\"><a class=\"header\" href=\"#aggregate-functions\">Aggregate Functions</a></h1>\n<p>Aggregate functions compute a single result from a set of input values. They are most commonly used with a <code>GROUP BY</code> clause to produce one result per group of rows, but when used without <code>GROUP BY</code>, they treat the entire result set as a single group and return one row.</p>\n<p>All built-in aggregate functions ignore NULL inputs (except <code>count(*)</code>). If every input to an aggregate function is NULL, the result is NULL – with the exceptions of <code>count()</code> (which returns 0) and <code>total()</code> (which returns 0.0). This NULL-skipping behavior is consistent with the SQL standard.</p>\n<p>Aggregate functions can be combined with <code>GROUP BY</code> and <code>HAVING</code> to filter groups after aggregation. See <a href=\"#group-by-and-having\">GROUP BY and HAVING</a> for details on grouping.</p>\n<p>The <code>DISTINCT</code> keyword can precede the argument of any single-argument aggregate to eliminate duplicate values before processing:</p>\n<pre><code class=\"language-sql\">SELECT count(DISTINCT category) FROM products;\n</code></pre>\n<p>The examples below use the following tables:</p>\n<pre><code class=\"language-sql\">CREATE TABLE products (\n  id INTEGER PRIMARY KEY,\n  name TEXT,\n  category TEXT,\n  price REAL\n);\nINSERT INTO products VALUES\n  (1, 'Laptop', 'Electronics', 999.99),\n  (2, 'Phone', 'Electronics', 699.99),\n  (3, 'Desk', 'Furniture', 299.99),\n  (4, 'Chair', 'Furniture', 199.99),\n  (5, 'Tablet', 'Electronics', 499.99);\n\nCREATE TABLE orders (\n  id INTEGER PRIMARY KEY,\n  user_id INTEGER,\n  product TEXT,\n  amount REAL\n);\nINSERT INTO orders VALUES\n  (1, 1, 'Laptop', 999.99),\n  (2, 1, 'Phone', 699.99),\n  (3, 2, 'Desk', 299.99),\n  (4, 3, 'Chair', 199.99),\n  (5, 1, 'Tablet', 499.99);\n</code></pre>\n<hr>\n<h2 id=\"avg\"><a class=\"header\" href=\"#avg\">avg</a></h2>\n<p><strong>avg(X) -&gt; real</strong></p>\n<p>Returns the average of all non-NULL values of X. The result is always a floating-point value when there is at least one non-NULL input. String and BLOB values that do not look like numbers are treated as 0. Returns NULL if all inputs are NULL or if the input set is empty. Internally, <code>avg(X)</code> is equivalent to <code>total(X) / count(X)</code>.</p>\n<pre><code class=\"language-sql\">SELECT avg(amount) FROM orders;\n-- 539.99\n</code></pre>\n<pre><code class=\"language-sql\">SELECT user_id, avg(amount) AS avg_order\n  FROM orders\n  GROUP BY user_id;\n-- 1 | 733.323333333333\n-- 2 | 299.99\n-- 3 | 199.99\n</code></pre>\n<p>The <code>DISTINCT</code> keyword causes duplicate values to be removed before computing the average:</p>\n<pre><code class=\"language-sql\">SELECT avg(DISTINCT x) FROM (SELECT 1 AS x UNION ALL SELECT 2 UNION ALL SELECT 2 UNION ALL SELECT 3);\n-- 2.0\n</code></pre>\n<hr>\n<h2 id=\"count\"><a class=\"header\" href=\"#count\">count</a></h2>\n<p><strong>count(X) -&gt; integer</strong>\n<strong>count(*) -&gt; integer</strong></p>\n<p>The <code>count(X)</code> form returns the number of times X is not NULL. The <code>count(*)</code> form returns the total number of rows in the group, regardless of NULL values.</p>\n<p><code>count()</code> always returns an integer and never returns NULL. For an empty input set, both forms return 0.</p>\n<pre><code class=\"language-sql\">SELECT count(*) FROM orders;\n-- 5\n</code></pre>\n<pre><code class=\"language-sql\">SELECT count(amount) FROM orders;\n-- 5\n</code></pre>\n<pre><code class=\"language-sql\">SELECT count(DISTINCT user_id) FROM orders;\n-- 3\n</code></pre>\n<pre><code class=\"language-sql\">SELECT user_id, count(*) AS order_count\n  FROM orders\n  GROUP BY user_id;\n-- 1 | 3\n-- 2 | 1\n-- 3 | 1\n</code></pre>\n<hr>\n<h2 id=\"group_concat\"><a class=\"header\" href=\"#group_concat\">group_concat</a></h2>\n<p><strong>group_concat(X) -&gt; text</strong>\n<strong>group_concat(X, Y) -&gt; text</strong></p>\n<p>Returns a string formed by concatenating all non-NULL values of X, separated by Y. If Y is omitted, a comma (<code>\",\"</code>) is used as the default separator. Returns NULL if all inputs are NULL.</p>\n<p>The order of concatenated elements is determined by the order in which rows are processed. When used with <code>GROUP BY</code>, the order within each group is not guaranteed unless the query uses a subquery or other mechanism to control row ordering.</p>\n<pre><code class=\"language-sql\">SELECT group_concat(product) FROM orders;\n-- Laptop,Phone,Desk,Chair,Tablet\n</code></pre>\n<pre><code class=\"language-sql\">SELECT group_concat(product, ' | ') FROM orders;\n-- Laptop | Phone | Desk | Chair | Tablet\n</code></pre>\n<pre><code class=\"language-sql\">SELECT user_id, group_concat(product, ', ') AS products\n  FROM orders\n  GROUP BY user_id;\n-- 1 | Laptop, Phone, Tablet\n-- 2 | Desk\n-- 3 | Chair\n</code></pre>\n<hr>\n<h2 id=\"string_agg\"><a class=\"header\" href=\"#string_agg\">string_agg</a></h2>\n<p><strong>string_agg(X, Y) -&gt; text</strong></p>\n<p>An alias for <code>group_concat(X, Y)</code>. Returns a string formed by concatenating all non-NULL values of X, separated by Y. Unlike <code>group_concat</code>, the separator argument Y is required. This form provides compatibility with PostgreSQL and SQL Server, which use <code>string_agg</code> rather than <code>group_concat</code>.</p>\n<pre><code class=\"language-sql\">SELECT string_agg(product, ', ') FROM orders;\n-- Laptop, Phone, Desk, Chair, Tablet\n</code></pre>\n<hr>\n<h2 id=\"max\"><a class=\"header\" href=\"#max\">max</a></h2>\n<p><strong>max(X) -&gt; value</strong></p>\n<p>Returns the maximum value of all non-NULL values of X. The maximum is determined by the sort order that would be used by <code>ORDER BY</code> on the same column. Returns NULL if all inputs are NULL or if the input set is empty.</p>\n<p><code>max()</code> works on any type: integers, reals, text, and BLOBs are compared using the normal comparison rules.</p>\n<pre><code class=\"language-sql\">SELECT max(amount) FROM orders;\n-- 999.99\n</code></pre>\n<pre><code class=\"language-sql\">SELECT max(name) FROM products;\n-- Tablet\n</code></pre>\n<pre><code class=\"language-sql\">SELECT category, max(price) AS most_expensive\n  FROM products\n  GROUP BY category;\n-- Electronics | 999.99\n-- Furniture   | 299.99\n</code></pre>\n<hr>\n<h2 id=\"min\"><a class=\"header\" href=\"#min\">min</a></h2>\n<p><strong>min(X) -&gt; value</strong></p>\n<p>Returns the minimum non-NULL value of X. The minimum is the value that would appear first in an <code>ORDER BY</code> on the same column. Returns NULL if all inputs are NULL or if the input set is empty.</p>\n<p><code>min()</code> works on any type: integers, reals, text, and BLOBs are compared using the normal comparison rules.</p>\n<pre><code class=\"language-sql\">SELECT min(amount) FROM orders;\n-- 199.99\n</code></pre>\n<pre><code class=\"language-sql\">SELECT min(name) FROM products;\n-- Chair\n</code></pre>\n<pre><code class=\"language-sql\">SELECT category, min(price) AS cheapest\n  FROM products\n  GROUP BY category;\n-- Electronics | 499.99\n-- Furniture   | 199.99\n</code></pre>\n<hr>\n<h2 id=\"sum\"><a class=\"header\" href=\"#sum\">sum</a></h2>\n<p><strong>sum(X) -&gt; integer or real</strong></p>\n<p>Returns the sum of all non-NULL values of X. If all inputs are integers, the result is an integer. If any input is a real number, the result is a real. Returns NULL if all inputs are NULL or if the input set is empty.</p>\n<p>An integer overflow error is raised if all inputs are integers and the sum exceeds the integer range.</p>\n<pre><code class=\"language-sql\">SELECT sum(amount) FROM orders;\n-- 2699.95\n</code></pre>\n<pre><code class=\"language-sql\">-- sum returns integer type when all inputs are integers\nSELECT typeof(sum(x)) FROM (SELECT 1 AS x UNION ALL SELECT 2 UNION ALL SELECT 3);\n-- integer\n\n-- sum returns real type when any input is real\nSELECT typeof(sum(x)) FROM (SELECT 1.0 AS x UNION ALL SELECT 2.0 UNION ALL SELECT 3.0);\n-- real\n</code></pre>\n<pre><code class=\"language-sql\">SELECT user_id, sum(amount) AS total_spent\n  FROM orders\n  GROUP BY user_id\n  HAVING total_spent &gt; 500;\n-- 1 | 2199.97\n</code></pre>\n<p>The <code>DISTINCT</code> keyword causes duplicate values to be removed before summing:</p>\n<pre><code class=\"language-sql\">SELECT sum(DISTINCT x) FROM (SELECT 1 AS x UNION ALL SELECT 2 UNION ALL SELECT 2 UNION ALL SELECT 3);\n-- 6\n</code></pre>\n<hr>\n<h2 id=\"total\"><a class=\"header\" href=\"#total\">total</a></h2>\n<p><strong>total(X) -&gt; real</strong></p>\n<p>Returns the sum of all non-NULL values of X, similar to <code>sum()</code>, but with two key differences:</p>\n<ol>\n<li><code>total()</code> always returns a floating-point value (0.0 for an empty set), whereas <code>sum()</code> returns NULL for an empty set.</li>\n<li><code>total()</code> never raises an integer overflow error.</li>\n</ol>\n<p>Use <code>total()</code> when you need a guaranteed numeric result and want to avoid NULL checks.</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Behavior</th><th>sum(X)</th><th>total(X)</th></tr>\n</thead>\n<tbody>\n<tr><td>Empty set result</td><td>NULL</td><td>0.0</td></tr>\n<tr><td>Return type (integer inputs)</td><td>integer</td><td>real</td></tr>\n<tr><td>Return type (real inputs)</td><td>real</td><td>real</td></tr>\n<tr><td>Integer overflow</td><td>raises error</td><td>no error</td></tr>\n</tbody>\n</table>\n</div>\n<pre><code class=\"language-sql\">SELECT total(amount) FROM orders;\n-- 2699.95\n</code></pre>\n<pre><code class=\"language-sql\">-- total returns 0.0 on an empty table; sum returns NULL\nCREATE TABLE empty (x INTEGER);\nSELECT sum(x), total(x) FROM empty;\n-- (NULL) | 0.0\n</code></pre>\n<pre><code class=\"language-sql\">-- total always returns real type, even with integer inputs\nSELECT typeof(total(x)) FROM (SELECT 1 AS x UNION ALL SELECT 2 UNION ALL SELECT 3);\n-- real\n</code></pre>\n<hr>\n<h1 id=\"turso-extension-aggregates\"><a class=\"header\" href=\"#turso-extension-aggregates\">Turso Extension Aggregates</a></h1>\n<p>The following aggregate functions are provided by the <code>percentile</code> extension. They are not built-in and must be loaded explicitly before use. Once loaded, they behave like any other aggregate function and can be used with <code>GROUP BY</code>, <code>HAVING</code>, and <code>DISTINCT</code>.</p>\n<pre><code class=\"language-sql\">.load liblimbo_percentile\n</code></pre>\n<h2 id=\"median\"><a class=\"header\" href=\"#median\">median</a></h2>\n<p><strong>median(X) -&gt; real</strong></p>\n<p>Returns the median value of all non-NULL values of X. For an odd number of values, this is the middle value. For an even number of values, this is the average of the two middle values. Returns NULL if all inputs are NULL.</p>\n<pre><code class=\"language-sql\">.load liblimbo_percentile\n\nCREATE TABLE scores (value REAL);\nINSERT INTO scores VALUES (1.0), (2.0), (3.0), (4.0), (5.0);\n\nSELECT median(value) FROM scores;\n-- 3.0\n</code></pre>\n<h2 id=\"percentile\"><a class=\"header\" href=\"#percentile\">percentile</a></h2>\n<p><strong>percentile(Y, P) -&gt; real</strong></p>\n<p>Returns the value at the P-th percentile among all non-NULL values of Y, using linear interpolation. P is expressed on a 0-to-100 scale (e.g., 50 for the median) and must be the same value for every row in the group. An error is raised if P is outside the range 0 to 100 or if different rows supply different values of P. Returns NULL if all inputs are NULL.</p>\n<pre><code class=\"language-sql\">SELECT percentile(value, 50) FROM scores;\n-- 3.0\n</code></pre>\n<h2 id=\"percentile_cont\"><a class=\"header\" href=\"#percentile_cont\">percentile_cont</a></h2>\n<p><strong>percentile_cont(Y, P) -&gt; real</strong></p>\n<p>Computes a percentile using continuous distribution, following the SQL standard <code>PERCENTILE_CONT</code> semantics. P is expressed on a 0.0-to-1.0 scale (e.g., 0.5 for the median) and must be the same value for every row in the group. Uses linear interpolation between adjacent values when the percentile falls between two data points. Returns NULL if all inputs are NULL.</p>\n<pre><code class=\"language-sql\">SELECT percentile_cont(value, 0.5) FROM scores;\n-- 3.0\n</code></pre>\n<h2 id=\"percentile_disc\"><a class=\"header\" href=\"#percentile_disc\">percentile_disc</a></h2>\n<p><strong>percentile_disc(Y, P) -&gt; real</strong></p>\n<p>Computes a percentile using discrete distribution, following the SQL standard <code>PERCENTILE_DISC</code> semantics. P is expressed on a 0.0-to-1.0 scale and must be the same value for every row in the group. Unlike <code>percentile_cont</code>, this function always returns an actual value from the input set rather than interpolating between values. Returns NULL if all inputs are NULL.</p>\n<pre><code class=\"language-sql\">SELECT percentile_disc(value, 0.5) FROM scores;\n-- 3.0\n</code></pre>\n<h2 id=\"stddev\"><a class=\"header\" href=\"#stddev\">stddev</a></h2>\n<p><strong>stddev(X) -&gt; real</strong></p>\n<p>Returns the population standard deviation of all non-NULL values of X. The standard deviation measures how spread out values are from their mean. Returns NULL if all inputs are NULL or if the input set is empty.</p>\n<pre><code class=\"language-sql\">SELECT stddev(value) FROM scores;\n-- 1.58113883008419\n</code></pre>\n<div style=\"break-before: page; page-break-before: always;\"></div>\n<h1 id=\"date-and-time-functions\"><a class=\"header\" href=\"#date-and-time-functions\">Date and Time Functions</a></h1>\n<p>Turso provides seven date and time functions for computing dates, times, and timestamps. All functions operate on UTC internally and accept an optional list of modifiers that transform the result.</p>\n<h2 id=\"date\"><a class=\"header\" href=\"#date\">date</a></h2>\n<p><strong>date(time-value, modifier, …) -&gt; TEXT</strong></p>\n<p>Returns the date as text in the format <code>YYYY-MM-DD</code>.</p>\n<pre><code class=\"language-sql\">SELECT date('2024-06-15 14:30:00');  -- 2024-06-15\nSELECT date('2024-06-15', '+10 days');  -- 2024-06-25\nSELECT date('2024-06-15', '-30 days');  -- 2024-05-16\n</code></pre>\n<h2 id=\"time\"><a class=\"header\" href=\"#time\">time</a></h2>\n<p><strong>time(time-value, modifier, …) -&gt; TEXT</strong></p>\n<p>Returns the time as text in the format <code>HH:MM:SS</code>.</p>\n<pre><code class=\"language-sql\">SELECT time('2024-06-15 14:30:45');  -- 14:30:45\nSELECT time('14:30:00');  -- 14:30:00\n</code></pre>\n<h2 id=\"datetime\"><a class=\"header\" href=\"#datetime\">datetime</a></h2>\n<p><strong>datetime(time-value, modifier, …) -&gt; TEXT</strong></p>\n<p>Returns the date and time as text in the format <code>YYYY-MM-DD HH:MM:SS</code>. When the <code>subsec</code> modifier is present, the output includes fractional seconds: <code>YYYY-MM-DD HH:MM:SS.SSS</code>.</p>\n<pre><code class=\"language-sql\">SELECT datetime('2024-06-15 14:30:00');  -- 2024-06-15 14:30:00\nSELECT datetime('2024-06-15 14:30:00', '+3 hours');  -- 2024-06-15 17:30:00\nSELECT datetime('2024-06-15 14:30:00', 'subsec');  -- 2024-06-15 14:30:00.000\n</code></pre>\n<p>Converting a Unix timestamp to a human-readable datetime:</p>\n<pre><code class=\"language-sql\">SELECT datetime(1718461800, 'unixepoch');  -- 2024-06-15 14:30:00\n</code></pre>\n<h2 id=\"julianday\"><a class=\"header\" href=\"#julianday\">julianday</a></h2>\n<p><strong>julianday(time-value, modifier, …) -&gt; REAL</strong></p>\n<p>Returns the Julian day number – the fractional number of days since noon in Greenwich on November 24, 4714 B.C. (Proleptic Gregorian calendar).</p>\n<pre><code class=\"language-sql\">SELECT julianday('2024-06-15');  -- 2460476.5\n</code></pre>\n<p>Compute the number of days between two dates:</p>\n<pre><code class=\"language-sql\">SELECT julianday('2024-06-15') - julianday('2024-01-01');  -- 166.0\n</code></pre>\n<h2 id=\"unixepoch\"><a class=\"header\" href=\"#unixepoch\">unixepoch</a></h2>\n<p><strong>unixepoch(time-value, modifier, …) -&gt; INTEGER</strong></p>\n<p>Returns the number of seconds since <code>1970-01-01 00:00:00 UTC</code>. Returns an integer by default; use the <code>subsec</code> modifier for a floating-point result with fractional seconds.</p>\n<pre><code class=\"language-sql\">SELECT unixepoch('2024-06-15 14:30:00');  -- 1718461800\n</code></pre>\n<p>Compute the number of seconds between two timestamps:</p>\n<pre><code class=\"language-sql\">SELECT unixepoch('2024-06-15 14:30:00') - unixepoch('2024-06-15 12:00:00');  -- 9000\n</code></pre>\n<h2 id=\"strftime\"><a class=\"header\" href=\"#strftime\">strftime</a></h2>\n<p><strong>strftime(format, time-value, modifier, …) -&gt; TEXT</strong></p>\n<p>Returns the date formatted according to the format string specified as the first argument. The format string supports the substitutions listed in the <a href=\"#format-specifiers\">Format Specifiers</a> table below.</p>\n<pre><code class=\"language-sql\">SELECT strftime('%Y/%m/%d', '2024-06-15');  -- 2024/06/15\nSELECT strftime('%H:%M', '2024-06-15 14:30:00');  -- 14:30\nSELECT strftime('%j', '2024-06-15');  -- 167\nSELECT strftime('%s', '2024-06-15 14:30:00');  -- 1718461800\nSELECT strftime('%J', '2024-06-15');  -- 2460476.5\n</code></pre>\n<p>The other date/time functions can be expressed as <code>strftime</code> equivalents:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Function</th><th>strftime Equivalent</th></tr>\n</thead>\n<tbody>\n<tr><td><code>date(...)</code></td><td><code>strftime('%F', ...)</code></td></tr>\n<tr><td><code>time(...)</code></td><td><code>strftime('%T', ...)</code></td></tr>\n<tr><td><code>datetime(...)</code></td><td><code>strftime('%F %T', ...)</code></td></tr>\n<tr><td><code>julianday(...)</code></td><td><code>CAST(strftime('%J', ...) AS REAL)</code></td></tr>\n<tr><td><code>unixepoch(...)</code></td><td><code>CAST(strftime('%s', ...) AS INT)</code></td></tr>\n</tbody>\n</table>\n</div>\n<h2 id=\"timediff\"><a class=\"header\" href=\"#timediff\">timediff</a></h2>\n<p><strong>timediff(time-value-A, time-value-B) -&gt; TEXT</strong></p>\n<p>Returns a text string describing the amount of time that must be added to the second argument to reach the first. The result has the format <code>(+|-)YYYY-MM-DD HH:MM:SS.SSS</code>.</p>\n<p>The <code>timediff</code> function does not accept modifiers. It satisfies the invariant that <code>datetime(A)</code> equals <code>datetime(B, timediff(A, B))</code>.</p>\n<pre><code class=\"language-sql\">SELECT timediff('2024-06-15', '2024-01-01');  -- +0000-05-14 00:00:00.000\nSELECT timediff('2024-06-15 14:30:00', '2024-06-15 12:00:00');  -- +0000-00-00 02:30:00.000\n</code></pre>\n<p>Because months and years vary in length, <code>timediff</code> may return the same text for intervals that span different numbers of days. For precise day-level differences, subtract Julian day numbers instead:</p>\n<pre><code class=\"language-sql\">SELECT julianday('2024-06-15') - julianday('2024-01-01');  -- 166.0\n</code></pre>\n<h2 id=\"time-value-formats\"><a class=\"header\" href=\"#time-value-formats\">Time Value Formats</a></h2>\n<p>All date/time functions accept time values in the following formats:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Format</th><th>Example</th></tr>\n</thead>\n<tbody>\n<tr><td><code>YYYY-MM-DD</code></td><td><code>'2024-06-15'</code></td></tr>\n<tr><td><code>YYYY-MM-DD HH:MM</code></td><td><code>'2024-06-15 14:30'</code></td></tr>\n<tr><td><code>YYYY-MM-DD HH:MM:SS</code></td><td><code>'2024-06-15 14:30:00'</code></td></tr>\n<tr><td><code>YYYY-MM-DD HH:MM:SS.SSS</code></td><td><code>'2024-06-15 14:30:00.123'</code></td></tr>\n<tr><td><code>YYYY-MM-DDTHH:MM:SS</code></td><td><code>'2024-06-15T14:30:00'</code></td></tr>\n<tr><td><code>HH:MM:SS</code></td><td><code>'14:30:00'</code> (assumes date 2000-01-01)</td></tr>\n<tr><td><code>'now'</code></td><td>Current date and time in UTC</td></tr>\n<tr><td><code>DDDDDDDDDD</code></td><td>Julian day number as a numeric value</td></tr>\n</tbody>\n</table>\n</div>\n<p>The ISO 8601 <code>T</code> separator between date and time is accepted interchangeably with a space:</p>\n<pre><code class=\"language-sql\">SELECT date('2024-06-15T14:30:00');  -- 2024-06-15\n</code></pre>\n<p>An optional timezone suffix <code>[+-]HH:MM</code> or <code>Z</code> may follow any format that includes a time component. The suffix <code>Z</code> denotes UTC (a no-op). A non-zero offset is subtracted to convert the value to UTC.</p>\n<h2 id=\"modifiers\"><a class=\"header\" href=\"#modifiers\">Modifiers</a></h2>\n<p>All date/time functions except <code>timediff</code> accept zero or more modifiers after the time value. Modifiers are applied from left to right; order matters.</p>\n<h3 id=\"arithmetic-modifiers\"><a class=\"header\" href=\"#arithmetic-modifiers\">Arithmetic Modifiers</a></h3>\n<p>Add or subtract a duration from the time value. The <code>NNN</code> value can be an integer or floating-point number, with an optional <code>+</code> or <code>-</code> prefix. The trailing <code>s</code> is optional (e.g., <code>'+1 day'</code> and <code>'+1 days'</code> are equivalent).</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Modifier</th><th>Effect</th></tr>\n</thead>\n<tbody>\n<tr><td><code>NNN days</code></td><td>Add NNN days</td></tr>\n<tr><td><code>NNN hours</code></td><td>Add NNN hours</td></tr>\n<tr><td><code>NNN minutes</code></td><td>Add NNN minutes</td></tr>\n<tr><td><code>NNN seconds</code></td><td>Add NNN seconds</td></tr>\n<tr><td><code>NNN months</code></td><td>Add NNN months</td></tr>\n<tr><td><code>NNN years</code></td><td>Add NNN years</td></tr>\n</tbody>\n</table>\n</div>\n<pre><code class=\"language-sql\">SELECT date('2024-06-15', '+1 month');  -- 2024-07-15\nSELECT date('2024-06-15', '+1 year');  -- 2025-06-15\nSELECT datetime('2024-06-15 14:30:00', '+90 minutes');  -- 2024-06-15 16:00:00\nSELECT datetime('2024-06-15 14:30:00', '+30 seconds');  -- 2024-06-15 14:30:30\n</code></pre>\n<p>Multiple modifiers chain together from left to right:</p>\n<pre><code class=\"language-sql\">SELECT datetime('2024-06-15 14:30:00', '+1 year', '-2 months');  -- 2025-04-15 14:30:00\n</code></pre>\n<h3 id=\"timedatedatetime-offset-modifiers\"><a class=\"header\" href=\"#timedatedatetime-offset-modifiers\">Time/Date/DateTime Offset Modifiers</a></h3>\n<p>Shift a time value by a compound offset specified in time, date, or full datetime format. A leading <code>+</code> or <code>-</code> is required for date offsets.</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Modifier Format</th><th>Example</th></tr>\n</thead>\n<tbody>\n<tr><td><code>[+-]HH:MM</code></td><td><code>'-05:00'</code></td></tr>\n<tr><td><code>[+-]HH:MM:SS</code></td><td><code>'+01:30:00'</code></td></tr>\n<tr><td><code>[+-]YYYY-MM-DD</code></td><td><code>'+0001-06-00'</code></td></tr>\n<tr><td><code>[+-]YYYY-MM-DD HH:MM:SS</code></td><td><code>'+0000-00-01 02:00:00'</code></td></tr>\n</tbody>\n</table>\n</div>\n<pre><code class=\"language-sql\">SELECT datetime('2024-06-15 14:30:00', '-05:00');  -- 2024-06-15 09:30:00\n</code></pre>\n<h3 id=\"start-of-modifiers\"><a class=\"header\" href=\"#start-of-modifiers\">Start-of Modifiers</a></h3>\n<p>Truncate the time value backward to the beginning of a period.</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Modifier</th><th>Effect</th></tr>\n</thead>\n<tbody>\n<tr><td><code>start of day</code></td><td>Sets time to <code>00:00:00</code></td></tr>\n<tr><td><code>start of month</code></td><td>Sets to first day of the month at <code>00:00:00</code></td></tr>\n<tr><td><code>start of year</code></td><td>Sets to January 1 at <code>00:00:00</code></td></tr>\n</tbody>\n</table>\n</div>\n<pre><code class=\"language-sql\">SELECT datetime('2024-06-15 14:30:00', 'start of day');  -- 2024-06-15 00:00:00\nSELECT date('2024-06-15', 'start of month');  -- 2024-06-01\nSELECT date('2024-06-15', 'start of year');  -- 2024-01-01\n</code></pre>\n<p>Chaining start-of modifiers with arithmetic is useful for computing boundaries:</p>\n<pre><code class=\"language-sql\">-- Last day of the current month\nSELECT date('2024-06-15', 'start of month', '+1 month', '-1 day');  -- 2024-06-30\n</code></pre>\n<h3 id=\"weekday-n\"><a class=\"header\" href=\"#weekday-n\">weekday N</a></h3>\n<p>Advance the date forward to the next occurrence of weekday <code>N</code>, where Sunday is 0, Monday is 1, through Saturday which is 6. If the date already falls on the requested weekday, it is left unchanged.</p>\n<pre><code class=\"language-sql\">-- 2024-06-15 is a Saturday (6); the next Sunday (0) is 2024-06-16\nSELECT date('2024-06-15', 'weekday 0');  -- 2024-06-16\n\n-- First Tuesday in October 2024\nSELECT date('2024-06-15', 'start of year', '+9 months', 'weekday 2');  -- 2024-10-01\n</code></pre>\n<h3 id=\"ceiling-and-floor\"><a class=\"header\" href=\"#ceiling-and-floor\">ceiling and floor</a></h3>\n<p>When adding months or years produces an ambiguous date (for example, one month after January 31), the <code>ceiling</code> and <code>floor</code> modifiers control how the ambiguity is resolved.</p>\n<ul>\n<li><code>ceiling</code> (the default) rounds forward to the next valid date.</li>\n<li><code>floor</code> rounds backward to the last day of the previous month.</li>\n</ul>\n<pre><code class=\"language-sql\">SELECT date('2024-01-31', '+1 month', 'ceiling');  -- 2024-03-02\nSELECT date('2024-01-31', '+1 month', 'floor');  -- 2024-02-29\n</code></pre>\n<h3 id=\"format-interpretation-modifiers\"><a class=\"header\" href=\"#format-interpretation-modifiers\">Format Interpretation Modifiers</a></h3>\n<p>These modifiers control how a bare numeric time value (<code>DDDDDDDDDD</code>) is interpreted. They must appear immediately after the time value.</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Modifier</th><th>Effect</th></tr>\n</thead>\n<tbody>\n<tr><td><code>unixepoch</code></td><td>Interpret the number as seconds since 1970-01-01 00:00:00 UTC</td></tr>\n<tr><td><code>julianday</code></td><td>Interpret the number as a Julian day number (the default)</td></tr>\n<tr><td><code>auto</code></td><td>Automatically choose based on magnitude: small values are Julian day numbers, large values are Unix timestamps</td></tr>\n</tbody>\n</table>\n</div>\n<pre><code class=\"language-sql\">SELECT datetime(1718461800, 'unixepoch');  -- 2024-06-15 14:30:00\nSELECT datetime(2460476.5, 'julianday');  -- 2024-06-15 00:00:00\nSELECT datetime(1718461800, 'auto');  -- 2024-06-15 14:30:00\n</code></pre>\n<h3 id=\"timezone-modifiers\"><a class=\"header\" href=\"#timezone-modifiers\">Timezone Modifiers</a></h3>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Modifier</th><th>Effect</th></tr>\n</thead>\n<tbody>\n<tr><td><code>localtime</code></td><td>Convert from UTC to local time</td></tr>\n<tr><td><code>utc</code></td><td>Convert from local time to UTC</td></tr>\n</tbody>\n</table>\n</div>\n<h3 id=\"subsec\"><a class=\"header\" href=\"#subsec\">subsec</a></h3>\n<p>The <code>subsec</code> (or <code>subsecond</code>) modifier increases output resolution from seconds to milliseconds.</p>\n<ul>\n<li>With <code>datetime</code> or <code>time</code>: appends <code>.SSS</code> to the seconds field.</li>\n<li>With <code>unixepoch</code>: returns a floating-point value instead of an integer.</li>\n</ul>\n<pre><code class=\"language-sql\">SELECT datetime('2024-06-15 14:30:00', 'subsec');  -- 2024-06-15 14:30:00.000\n</code></pre>\n<h2 id=\"format-specifiers\"><a class=\"header\" href=\"#format-specifiers\">Format Specifiers</a></h2>\n<p>The <code>strftime</code> function accepts the following format specifiers:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Specifier</th><th>Description</th><th>Example</th></tr>\n</thead>\n<tbody>\n<tr><td><code>%d</code></td><td>Day of month: 01-31</td><td><code>15</code></td></tr>\n<tr><td><code>%f</code></td><td>Fractional seconds: SS.SSS</td><td><code>00.123</code></td></tr>\n<tr><td><code>%H</code></td><td>Hour: 00-24</td><td><code>14</code></td></tr>\n<tr><td><code>%j</code></td><td>Day of year: 001-366</td><td><code>167</code></td></tr>\n<tr><td><code>%J</code></td><td>Julian day number (fractional)</td><td><code>2460476.5</code></td></tr>\n<tr><td><code>%m</code></td><td>Month: 01-12</td><td><code>06</code></td></tr>\n<tr><td><code>%M</code></td><td>Minute: 00-59</td><td><code>30</code></td></tr>\n<tr><td><code>%s</code></td><td>Seconds since 1970-01-01</td><td><code>1718461800</code></td></tr>\n<tr><td><code>%S</code></td><td>Seconds: 00-59</td><td><code>45</code></td></tr>\n<tr><td><code>%w</code></td><td>Day of week: 0-6 (Sunday=0)</td><td><code>6</code></td></tr>\n<tr><td><code>%W</code></td><td>Week of year: 00-53</td><td><code>24</code></td></tr>\n<tr><td><code>%Y</code></td><td>Year: 0000-9999</td><td><code>2024</code></td></tr>\n<tr><td><code>%%</code></td><td>Literal <code>%</code> character</td><td><code>%</code></td></tr>\n</tbody>\n</table>\n</div>\n<pre><code class=\"language-sql\">SELECT strftime('%f', '2024-06-15 14:30:00.123');  -- 00.123\nSELECT strftime('%w', '2024-06-15');  -- 6\nSELECT strftime('%W', '2024-06-15');  -- 24\nSELECT strftime('%S', '2024-06-15 14:30:45');  -- 45\nSELECT strftime('%m', '2024-06-15');  -- 06\n</code></pre>\n<p>Unrecognized format specifiers return NULL.</p>\n<h2 id=\"null-handling-4\"><a class=\"header\" href=\"#null-handling-4\">NULL Handling</a></h2>\n<p>All date/time functions return NULL when given an invalid input, an out-of-range date, or an unrecognized modifier. The valid date range is <code>0000-01-01 00:00:00</code> through <code>9999-12-31 23:59:59</code>.</p>\n<h2 id=\"calendar-notes\"><a class=\"header\" href=\"#calendar-notes\">Calendar Notes</a></h2>\n<p>All computations use the Proleptic Gregorian calendar. Each day is treated as exactly 86,400 seconds; leap seconds are not incorporated.</p>\n<div style=\"break-before: page; page-break-before: always;\"></div>\n<h1 id=\"math-functions\"><a class=\"header\" href=\"#math-functions\">Math Functions</a></h1>\n<p>Turso provides a full set of mathematical functions for trigonometry, logarithms, exponentiation, and rounding. All math functions accept integers, floating-point numbers, or strings that look like numbers. They return an IEEE 754 double-precision floating-point result, except when the input is an integer and the result can be represented exactly as an integer.</p>\n<p>All math functions return NULL when any argument is NULL, when an argument is a blob or non-numeric string, or when the result would be mathematically undefined (a domain error).</p>\n<h2 id=\"constants\"><a class=\"header\" href=\"#constants\">Constants</a></h2>\n<h3 id=\"pi\"><a class=\"header\" href=\"#pi\">pi</a></h3>\n<p><strong>pi() -&gt; real</strong></p>\n<p>Returns an approximation of the mathematical constant pi.</p>\n<pre><code class=\"language-sql\">SELECT pi();  -- 3.14159265358979\n</code></pre>\n<h2 id=\"trigonometric-functions\"><a class=\"header\" href=\"#trigonometric-functions\">Trigonometric Functions</a></h2>\n<p>All trigonometric functions work in radians. Use <code>radians()</code> and <code>degrees()</code> to convert between degrees and radians.</p>\n<h3 id=\"acos\"><a class=\"header\" href=\"#acos\">acos</a></h3>\n<p><strong>acos(X) -&gt; real</strong></p>\n<p>Returns the arccosine of X, in radians. X must be between -1.0 and 1.0; returns NULL for values outside that range.</p>\n<pre><code class=\"language-sql\">SELECT acos(0.5);  -- 1.0471975511966\n</code></pre>\n<h3 id=\"asin\"><a class=\"header\" href=\"#asin\">asin</a></h3>\n<p><strong>asin(X) -&gt; real</strong></p>\n<p>Returns the arcsine of X, in radians. X must be between -1.0 and 1.0; returns NULL for values outside that range.</p>\n<pre><code class=\"language-sql\">SELECT asin(0.5);  -- 0.523598775598299\n</code></pre>\n<h3 id=\"atan\"><a class=\"header\" href=\"#atan\">atan</a></h3>\n<p><strong>atan(X) -&gt; real</strong></p>\n<p>Returns the arctangent of X, in radians.</p>\n<pre><code class=\"language-sql\">SELECT atan(1.0);  -- 0.785398163397448\n</code></pre>\n<h3 id=\"atan2\"><a class=\"header\" href=\"#atan2\">atan2</a></h3>\n<p><strong>atan2(Y, X) -&gt; real</strong></p>\n<p>Returns the arctangent of Y/X, in radians, using the signs of both arguments to determine the quadrant of the result. Unlike <code>atan(Y/X)</code>, <code>atan2</code> correctly handles cases where X is zero.</p>\n<pre><code class=\"language-sql\">SELECT atan2(1.0, 1.0);  -- 0.785398163397448\n</code></pre>\n<h3 id=\"cos\"><a class=\"header\" href=\"#cos\">cos</a></h3>\n<p><strong>cos(X) -&gt; real</strong></p>\n<p>Returns the cosine of X, where X is in radians.</p>\n<pre><code class=\"language-sql\">SELECT cos(0.0);  -- 1.0\n</code></pre>\n<h3 id=\"sin\"><a class=\"header\" href=\"#sin\">sin</a></h3>\n<p><strong>sin(X) -&gt; real</strong></p>\n<p>Returns the sine of X, where X is in radians.</p>\n<pre><code class=\"language-sql\">SELECT sin(pi() / 6);  -- 0.5\n</code></pre>\n<h3 id=\"tan\"><a class=\"header\" href=\"#tan\">tan</a></h3>\n<p><strong>tan(X) -&gt; real</strong></p>\n<p>Returns the tangent of X, where X is in radians.</p>\n<pre><code class=\"language-sql\">SELECT tan(pi() / 4);  -- 1.0\n</code></pre>\n<h2 id=\"hyperbolic-functions\"><a class=\"header\" href=\"#hyperbolic-functions\">Hyperbolic Functions</a></h2>\n<h3 id=\"acosh\"><a class=\"header\" href=\"#acosh\">acosh</a></h3>\n<p><strong>acosh(X) -&gt; real</strong></p>\n<p>Returns the inverse hyperbolic cosine of X. X must be greater than or equal to 1.0; returns NULL for values less than 1.0.</p>\n<pre><code class=\"language-sql\">SELECT acosh(2.0);  -- 1.31695789692482\n</code></pre>\n<h3 id=\"asinh\"><a class=\"header\" href=\"#asinh\">asinh</a></h3>\n<p><strong>asinh(X) -&gt; real</strong></p>\n<p>Returns the inverse hyperbolic sine of X.</p>\n<pre><code class=\"language-sql\">SELECT asinh(1.0);  -- 0.881373587019543\n</code></pre>\n<h3 id=\"atanh\"><a class=\"header\" href=\"#atanh\">atanh</a></h3>\n<p><strong>atanh(X) -&gt; real</strong></p>\n<p>Returns the inverse hyperbolic tangent of X. X must be between -1.0 and 1.0 (exclusive); returns NULL for values outside that range.</p>\n<pre><code class=\"language-sql\">SELECT atanh(0.5);  -- 0.549306144334055\n</code></pre>\n<h3 id=\"cosh\"><a class=\"header\" href=\"#cosh\">cosh</a></h3>\n<p><strong>cosh(X) -&gt; real</strong></p>\n<p>Returns the hyperbolic cosine of X.</p>\n<pre><code class=\"language-sql\">SELECT cosh(1.0);  -- 1.54308063481524\n</code></pre>\n<h3 id=\"sinh\"><a class=\"header\" href=\"#sinh\">sinh</a></h3>\n<p><strong>sinh(X) -&gt; real</strong></p>\n<p>Returns the hyperbolic sine of X.</p>\n<pre><code class=\"language-sql\">SELECT sinh(1.0);  -- 1.1752011936438\n</code></pre>\n<h3 id=\"tanh\"><a class=\"header\" href=\"#tanh\">tanh</a></h3>\n<p><strong>tanh(X) -&gt; real</strong></p>\n<p>Returns the hyperbolic tangent of X.</p>\n<pre><code class=\"language-sql\">SELECT tanh(1.0);  -- 0.761594155955765\n</code></pre>\n<h2 id=\"angle-conversion\"><a class=\"header\" href=\"#angle-conversion\">Angle Conversion</a></h2>\n<h3 id=\"degrees\"><a class=\"header\" href=\"#degrees\">degrees</a></h3>\n<p><strong>degrees(X) -&gt; real</strong></p>\n<p>Converts X from radians to degrees.</p>\n<pre><code class=\"language-sql\">SELECT degrees(pi());  -- 180.0\n</code></pre>\n<h3 id=\"radians\"><a class=\"header\" href=\"#radians\">radians</a></h3>\n<p><strong>radians(X) -&gt; real</strong></p>\n<p>Converts X from degrees to radians.</p>\n<pre><code class=\"language-sql\">SELECT radians(180.0);  -- 3.14159265358979\n</code></pre>\n<h2 id=\"exponential-and-logarithmic-functions\"><a class=\"header\" href=\"#exponential-and-logarithmic-functions\">Exponential and Logarithmic Functions</a></h2>\n<h3 id=\"exp\"><a class=\"header\" href=\"#exp\">exp</a></h3>\n<p><strong>exp(X) -&gt; real</strong></p>\n<p>Returns the value of e (Euler’s number, approximately 2.71828) raised to the power X.</p>\n<pre><code class=\"language-sql\">SELECT exp(1.0);  -- 2.71828182845905\n</code></pre>\n<h3 id=\"ln\"><a class=\"header\" href=\"#ln\">ln</a></h3>\n<p><strong>ln(X) -&gt; real</strong></p>\n<p>Returns the natural logarithm (base e) of X. Returns NULL if X is less than or equal to zero.</p>\n<pre><code class=\"language-sql\">SELECT ln(exp(1.0));  -- 1.0\n</code></pre>\n<h3 id=\"log\"><a class=\"header\" href=\"#log\">log</a></h3>\n<p><strong>log(X) -&gt; real</strong></p>\n<p>With a single argument, returns the base-10 logarithm of X. Returns NULL if X is less than or equal to zero.</p>\n<pre><code class=\"language-sql\">SELECT log(100.0);  -- 2.0\n</code></pre>\n<p><strong>log(B, X) -&gt; real</strong></p>\n<p>With two arguments, returns the base-B logarithm of X. Returns NULL if either argument is less than or equal to zero.</p>\n<pre><code class=\"language-sql\">SELECT log(2, 8);  -- 3.0\n</code></pre>\n<p>Note: the single-argument form of <code>log()</code> computes base-10, not the natural logarithm. Use <code>ln()</code> for the natural logarithm.</p>\n<h3 id=\"log10\"><a class=\"header\" href=\"#log10\">log10</a></h3>\n<p><strong>log10(X) -&gt; real</strong></p>\n<p>Returns the base-10 logarithm of X. Returns NULL if X is less than or equal to zero. Equivalent to <code>log(X)</code> with a single argument.</p>\n<pre><code class=\"language-sql\">SELECT log10(1000.0);  -- 3.0\n</code></pre>\n<h3 id=\"log2\"><a class=\"header\" href=\"#log2\">log2</a></h3>\n<p><strong>log2(X) -&gt; real</strong></p>\n<p>Returns the base-2 logarithm of X. Returns NULL if X is less than or equal to zero.</p>\n<pre><code class=\"language-sql\">SELECT log2(64);  -- 6.0\n</code></pre>\n<h2 id=\"power-and-root-functions\"><a class=\"header\" href=\"#power-and-root-functions\">Power and Root Functions</a></h2>\n<h3 id=\"pow--power\"><a class=\"header\" href=\"#pow--power\">pow / power</a></h3>\n<p><strong>pow(X, Y) -&gt; real</strong>\n<strong>power(X, Y) -&gt; real</strong></p>\n<p>Returns X raised to the power Y. <code>pow</code> and <code>power</code> are aliases for the same function.</p>\n<pre><code class=\"language-sql\">SELECT pow(2, 10);   -- 1024.0\nSELECT power(3, 4);  -- 81.0\n</code></pre>\n<h3 id=\"sqrt\"><a class=\"header\" href=\"#sqrt\">sqrt</a></h3>\n<p><strong>sqrt(X) -&gt; real</strong></p>\n<p>Returns the square root of X. Returns NULL if X is negative.</p>\n<pre><code class=\"language-sql\">SELECT sqrt(144);  -- 12.0\n</code></pre>\n<h2 id=\"rounding-functions\"><a class=\"header\" href=\"#rounding-functions\">Rounding Functions</a></h2>\n<h3 id=\"ceil--ceiling\"><a class=\"header\" href=\"#ceil--ceiling\">ceil / ceiling</a></h3>\n<p><strong>ceil(X) -&gt; integer/real</strong>\n<strong>ceiling(X) -&gt; integer/real</strong></p>\n<p>Returns the smallest integer not less than X (rounds toward positive infinity). <code>ceil</code> and <code>ceiling</code> are aliases. When the input is an integer, the result is that same integer. When the input is a float, the result is a float with an integer value.</p>\n<pre><code class=\"language-sql\">SELECT ceil(3.2);     -- 4.0\nSELECT ceiling(-2.8); -- -2.0\nSELECT ceil(5);       -- 5\n</code></pre>\n<h3 id=\"floor\"><a class=\"header\" href=\"#floor\">floor</a></h3>\n<p><strong>floor(X) -&gt; integer/real</strong></p>\n<p>Returns the largest integer not greater than X (rounds toward negative infinity). When the input is an integer, the result is that same integer. When the input is a float, the result is a float with an integer value.</p>\n<pre><code class=\"language-sql\">SELECT floor(3.8);   -- 3.0\nSELECT floor(-2.3);  -- -3.0\nSELECT floor(5);     -- 5\n</code></pre>\n<h3 id=\"trunc\"><a class=\"header\" href=\"#trunc\">trunc</a></h3>\n<p><strong>trunc(X) -&gt; integer/real</strong></p>\n<p>Returns the integer part of X by removing any fractional digits (rounds toward zero). When the input is an integer, the result is that same integer. When the input is a float, the result is a float with an integer value.</p>\n<pre><code class=\"language-sql\">SELECT trunc(3.7);   -- 3.0\nSELECT trunc(-3.7);  -- -3.0\nSELECT trunc(5);     -- 5\n</code></pre>\n<p>The difference between these three rounding functions is visible with negative values:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Input</th><th>ceil</th><th>floor</th><th>trunc</th></tr>\n</thead>\n<tbody>\n<tr><td>3.7</td><td>4.0</td><td>3.0</td><td>3.0</td></tr>\n<tr><td>-3.7</td><td>-3.0</td><td>-4.0</td><td>-3.0</td></tr>\n</tbody>\n</table>\n</div>\n<p><code>ceil</code> rounds toward positive infinity, <code>floor</code> rounds toward negative infinity, and <code>trunc</code> rounds toward zero.</p>\n<h2 id=\"remainder-function\"><a class=\"header\" href=\"#remainder-function\">Remainder Function</a></h2>\n<h3 id=\"mod\"><a class=\"header\" href=\"#mod\">mod</a></h3>\n<p><strong>mod(X, Y) -&gt; real</strong></p>\n<p>Returns the remainder after dividing X by Y. Unlike the <code>%</code> operator, <code>mod()</code> works correctly with floating-point arguments. Returns NULL if Y is zero.</p>\n<pre><code class=\"language-sql\">SELECT mod(10, 3);      -- 1.0\nSELECT mod(10.5, 3.0);  -- 1.5\n</code></pre>\n<h2 id=\"null-and-error-handling\"><a class=\"header\" href=\"#null-and-error-handling\">NULL and Error Handling</a></h2>\n<p>All math functions follow these rules:</p>\n<ul>\n<li><strong>NULL input</strong>: any NULL argument produces a NULL result.</li>\n<li><strong>Non-numeric input</strong>: blob values and strings that cannot be converted to a number produce a NULL result.</li>\n<li><strong>Domain errors</strong>: operations with no real-valued result return NULL rather than raising an error. Examples include <code>sqrt(-1)</code>, <code>acos(2.0)</code>, <code>ln(-1)</code>, and <code>log(-5)</code>.</li>\n</ul>\n<pre><code class=\"language-sql\">SELECT sqrt(-1);    -- NULL\nSELECT acos(2.0);   -- NULL\nSELECT acos(NULL);  -- NULL\nSELECT ln(-1);      -- NULL\n</code></pre>\n<div style=\"break-before: page; page-break-before: always;\"></div>\n<h1 id=\"json-functions\"><a class=\"header\" href=\"#json-functions\">JSON Functions</a></h1>\n<p>Turso includes a comprehensive set of JSON functions for creating, querying, modifying, and aggregating JSON data. These functions accept both JSON text and JSONB (binary JSON) as input.</p>\n<h2 id=\"json-path-syntax\"><a class=\"header\" href=\"#json-path-syntax\">JSON Path Syntax</a></h2>\n<p>Many JSON functions take a <code>path</code> argument that identifies a specific element within a JSON structure. A well-formed path begins with <code>$</code> (the root element) followed by zero or more accessors:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Accessor</th><th>Meaning</th></tr>\n</thead>\n<tbody>\n<tr><td><code>$.key</code></td><td>Object member by name</td></tr>\n<tr><td><code>$[N]</code></td><td>Array element at index N (0-based)</td></tr>\n<tr><td><code>$[#-N]</code></td><td>Array element N from the end</td></tr>\n<tr><td><code>$[#]</code></td><td>Position after last array element (for appending)</td></tr>\n</tbody>\n</table>\n</div>\n<p>Examples: <code>$</code> (root), <code>$.name</code> (object field), <code>$[0]</code> (first array element), <code>$.items[2].price</code> (nested access), <code>$[#-1]</code> (last array element).</p>\n<h2 id=\"value-argument-conventions\"><a class=\"header\" href=\"#value-argument-conventions\">Value Argument Conventions</a></h2>\n<p>When a function parameter is labeled “value,” plain text arguments become quoted JSON strings in the result. To embed actual JSON (not a string), wrap the value with <code>json()</code> or another JSON function:</p>\n<pre><code class=\"language-sql\">SELECT json_object('data', '[1,2,3]');           -- {\"data\":\"[1,2,3]\"}\nSELECT json_object('data', json('[1,2,3]'));      -- {\"data\":[1,2,3]}\n</code></pre>\n<hr>\n<h2 id=\"creation-functions\"><a class=\"header\" href=\"#creation-functions\">Creation Functions</a></h2>\n<h3 id=\"json\"><a class=\"header\" href=\"#json\">json</a></h3>\n<p><strong>json(json) -&gt; text</strong></p>\n<p>Validates and minifies a JSON string. Accepts JSON text, JSONB blobs, and JSON5 input. Returns canonical RFC-8259 JSON with whitespace removed. Raises an error if the input is malformed.</p>\n<pre><code class=\"language-sql\">SELECT json(' { \"name\" : \"Alice\", \"scores\": [ 90, 85 ] } ');\n-- {\"name\":\"Alice\",\"scores\":[90,85]}\n</code></pre>\n<p>JSON5 extensions such as unquoted keys, trailing commas, and comments are accepted on input but normalized to standard JSON on output:</p>\n<pre><code class=\"language-sql\">SELECT json('{name: \"Alice\", age: 25}');\n-- {\"name\":\"Alice\",\"age\":25}\n</code></pre>\n<h3 id=\"jsonb\"><a class=\"header\" href=\"#jsonb\">jsonb</a></h3>\n<p><strong>jsonb(json) -&gt; blob</strong></p>\n<p>Returns the JSONB (binary) representation of the input. JSONB is an opaque binary format that can be faster to process than text JSON. All JSON functions accept JSONB as input.</p>\n<pre><code class=\"language-sql\">SELECT json(jsonb('{\"name\":\"John\",\"age\":30}'));\n-- {\"name\":\"John\",\"age\":30}\n</code></pre>\n<h3 id=\"json_array\"><a class=\"header\" href=\"#json_array\">json_array</a></h3>\n<p><strong>json_array(value1, value2, …) -&gt; text</strong></p>\n<p>Creates a JSON array from the given arguments. Text arguments become quoted JSON strings unless they come from another JSON function.</p>\n<pre><code class=\"language-sql\">SELECT json_array(1, 2, '3', 4);              -- [1,2,\"3\",4]\nSELECT json_array(json_array(1, 2), 'text');   -- [[1,2],\"text\"]\nSELECT json_array(1, null, json('[4,5]'));      -- [1,null,[4,5]]\n</code></pre>\n<p><code>jsonb_array(...)</code> returns the same result in JSONB format.</p>\n<h3 id=\"json_object\"><a class=\"header\" href=\"#json_object\">json_object</a></h3>\n<p><strong>json_object(label1, value1, …) -&gt; text</strong></p>\n<p>Creates a JSON object from label/value pairs. Labels must be strings. Text values become quoted JSON strings unless produced by another JSON function.</p>\n<pre><code class=\"language-sql\">SELECT json_object('name', 'Alice', 'age', 25);\n-- {\"name\":\"Alice\",\"age\":25}\n\nSELECT json_object('user', json_object('id', 1, 'role', 'admin'));\n-- {\"user\":{\"id\":1,\"role\":\"admin\"}}\n\nSELECT json_object();\n-- {}\n</code></pre>\n<p><code>jsonb_object(...)</code> returns the same result in JSONB format.</p>\n<h3 id=\"json_quote\"><a class=\"header\" href=\"#json_quote\">json_quote</a></h3>\n<p><strong>json_quote(value) -&gt; text</strong></p>\n<p>Converts an SQL value into its JSON representation. Strings are quoted and interior quotes are escaped. NULL becomes the JSON literal <code>null</code>. If the value is already JSON (from another JSON function), it is returned unchanged.</p>\n<pre><code class=\"language-sql\">SELECT json_quote('hello');     -- \"hello\"\nSELECT json_quote(3.14159);     -- 3.14159\nSELECT json_quote(12345);       -- 12345\nSELECT json_quote(null);        -- null\n</code></pre>\n<hr>\n<h2 id=\"extraction-functions\"><a class=\"header\" href=\"#extraction-functions\">Extraction Functions</a></h2>\n<h3 id=\"json_extract\"><a class=\"header\" href=\"#json_extract\">json_extract</a></h3>\n<p><strong>json_extract(json, path, …) -&gt; value</strong></p>\n<p>Extracts one or more values from a JSON document at the given path(s).</p>\n<p>With a single path, the return type depends on the JSON element: SQL NULL for JSON null, INTEGER for integers and booleans, REAL for floating-point numbers, TEXT for strings, and a text JSON representation for objects and arrays.</p>\n<p>With multiple paths, returns a JSON array containing all extracted values.</p>\n<pre><code class=\"language-sql\">SELECT json_extract('{\"a\":2,\"c\":[4,5,{\"f\":7}]}', '$.c[2].f');\n-- 7\n\nSELECT json_extract('{\"a\":[1,2,3]}', '$.a');\n-- [1,2,3]\n\nSELECT json_extract('{\"a\":[1,2,3]}', '$.a', '$.a[0]', '$.a[1]', '$.a[3]');\n-- [[1,2,3],1,2,null]\n</code></pre>\n<p>Returns NULL if the input JSON is NULL. Raises an error if the JSON is malformed.</p>\n<p><code>jsonb_extract(...)</code> returns JSONB for objects and arrays instead of text JSON.</p>\n<h3 id=\"--extract-as-json\"><a class=\"header\" href=\"#--extract-as-json\">-&gt; (extract as JSON)</a></h3>\n<p><strong>json -&gt; path -&gt; text</strong></p>\n<p>Extracts a value and always returns it as a JSON text representation. For strings, the result includes the surrounding quotes.</p>\n<pre><code class=\"language-sql\">SELECT '{\"a\":2,\"c\":[4,5]}' -&gt; '$.a';    -- 2\nSELECT '{\"a\":\"xyz\"}' -&gt; '$.a';           -- \"xyz\"\nSELECT '[1,2,3]' -&gt; 1;                   -- 2\n</code></pre>\n<p>The right operand can be a full path (<code>'$.field'</code>), a plain text label (interpreted as <code>'$.label'</code>), or an integer array index. Negative integers count from the end.</p>\n<pre><code class=\"language-sql\">SELECT '{\"a\":1}' -&gt; 'a';     -- 1\nSELECT '[1,2,3]' -&gt; -1;      -- 3\n</code></pre>\n<p>The <code>-&gt;</code> operator can be chained:</p>\n<pre><code class=\"language-sql\">SELECT '{\"a\":2,\"c\":[4,5,{\"f\":7}]}' -&gt; 'c' -&gt; 2 -&gt;&gt; 'f';\n-- 7\n</code></pre>\n<h3 id=\"--extract-as-sql-value\"><a class=\"header\" href=\"#--extract-as-sql-value\">-&gt;&gt; (extract as SQL value)</a></h3>\n<p><strong>json -&gt;&gt; path -&gt; value</strong></p>\n<p>Extracts a value and returns it as an SQL type: TEXT for strings (without quotes), INTEGER for integers and booleans, REAL for floats, NULL for JSON null.</p>\n<pre><code class=\"language-sql\">SELECT '{\"a\":\"xyz\"}' -&gt;&gt; '$.a';     -- xyz   (text, no quotes)\nSELECT '{\"a\":2}' -&gt;&gt; '$.a';         -- 2     (integer)\nSELECT 'true' -&gt;&gt; '$';              -- 1     (integer)\nSELECT 'false' -&gt;&gt; '$';             -- 0     (integer)\n</code></pre>\n<hr>\n<h2 id=\"inspection-functions\"><a class=\"header\" href=\"#inspection-functions\">Inspection Functions</a></h2>\n<h3 id=\"json_array_length\"><a class=\"header\" href=\"#json_array_length\">json_array_length</a></h3>\n<p><strong>json_array_length(json [, path]) -&gt; integer</strong></p>\n<p>Returns the number of elements in the JSON array. If the value at the specified path is not an array, returns 0. If the path does not exist, returns NULL.</p>\n<pre><code class=\"language-sql\">SELECT json_array_length('[1,2,3,4]');                       -- 4\nSELECT json_array_length('[]');                               -- 0\nSELECT json_array_length('{\"one\":[1,2,3]}', '$.one');        -- 3\nSELECT json_array_length('{\"one\":[1,2,3]}');                  -- 0\nSELECT json_array_length('{\"one\":[1,2,3]}', '$.two');        -- (NULL)\n</code></pre>\n<h3 id=\"json_type\"><a class=\"header\" href=\"#json_type\">json_type</a></h3>\n<p><strong>json_type(json [, path]) -&gt; text</strong></p>\n<p>Returns a string indicating the type of the JSON element. Possible return values: <code>'object'</code>, <code>'array'</code>, <code>'integer'</code>, <code>'real'</code>, <code>'text'</code>, <code>'true'</code>, <code>'false'</code>, <code>'null'</code>. Returns NULL if the path does not exist.</p>\n<pre><code class=\"language-sql\">SELECT json_type('{\"a\":[2,3.5,true,false,null,\"x\"]}');           -- object\nSELECT json_type('{\"a\":[2,3.5,true,false,null,\"x\"]}', '$.a');    -- array\nSELECT json_type('{\"a\":[2,3.5,true,false,null,\"x\"]}', '$.a[0]'); -- integer\nSELECT json_type('{\"a\":[2,3.5,true,false,null,\"x\"]}', '$.a[1]'); -- real\nSELECT json_type('{\"a\":[2,3.5,true,false,null,\"x\"]}', '$.a[2]'); -- true\nSELECT json_type('{\"a\":[2,3.5,true,false,null,\"x\"]}', '$.a[3]'); -- false\nSELECT json_type('{\"a\":[2,3.5,true,false,null,\"x\"]}', '$.a[4]'); -- null\nSELECT json_type('{\"a\":[2,3.5,true,false,null,\"x\"]}', '$.a[5]'); -- text\n</code></pre>\n<h3 id=\"json_valid\"><a class=\"header\" href=\"#json_valid\">json_valid</a></h3>\n<p><strong>json_valid(json [, flags]) -&gt; integer</strong></p>\n<p>Returns 1 if the input is well-formed JSON, 0 if it is malformed, or NULL if the input is NULL.</p>\n<p>Without flags (or with flags = 1), validates against RFC-8259 canonical JSON. The optional <code>flags</code> parameter is a bitmask that controls which formats are considered valid:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Flag</th><th>Meaning</th></tr>\n</thead>\n<tbody>\n<tr><td>1</td><td>RFC-8259 canonical JSON text</td></tr>\n<tr><td>2</td><td>JSON5 text</td></tr>\n<tr><td>4</td><td>JSONB (fast superficial check)</td></tr>\n<tr><td>8</td><td>JSONB (strict thorough check)</td></tr>\n</tbody>\n</table>\n</div>\n<p>Flags can be combined with bitwise OR. Common combinations:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Value</th><th>Accepts</th></tr>\n</thead>\n<tbody>\n<tr><td>1</td><td>Standard JSON text only (default)</td></tr>\n<tr><td>2</td><td>JSON5 text only</td></tr>\n<tr><td>5</td><td>Standard JSON text or JSONB</td></tr>\n<tr><td>6</td><td>JSON5 text or JSONB</td></tr>\n</tbody>\n</table>\n</div>\n<pre><code class=\"language-sql\">SELECT json_valid('{\"a\":55,\"b\":72}');       -- 1\nSELECT json_valid('not a valid json');       -- 0\nSELECT json_valid(NULL);                     -- (NULL)\nSELECT json_valid(123);                      -- 1\n</code></pre>\n<h3 id=\"json_error_position\"><a class=\"header\" href=\"#json_error_position\">json_error_position</a></h3>\n<p><strong>json_error_position(json) -&gt; integer</strong></p>\n<p>Returns 0 if the input is well-formed JSON or JSON5. If malformed, returns the 1-based character position of the first syntax error. Returns NULL if the input is NULL.</p>\n<pre><code class=\"language-sql\">SELECT json_error_position('{\"a\":55,\"b\":72}');      -- 0\nSELECT json_error_position('{\"a\":55,\"b\":72,,}');     -- 16\nSELECT json_error_position(NULL);                     -- (NULL)\n</code></pre>\n<h3 id=\"json_pretty\"><a class=\"header\" href=\"#json_pretty\">json_pretty</a></h3>\n<p><strong>json_pretty(json [, indent]) -&gt; text</strong></p>\n<p>Returns the input JSON formatted with whitespace for readability. The optional <code>indent</code> argument specifies the indentation string (default: four spaces). If <code>indent</code> is NULL, the default four spaces are used.</p>\n<pre><code class=\"language-sql\">SELECT json_pretty('{\"name\":\"Alice\",\"age\":25}');\n-- {\n--     \"name\": \"Alice\",\n--     \"age\": 25\n-- }\n</code></pre>\n<hr>\n<h2 id=\"modification-functions\"><a class=\"header\" href=\"#modification-functions\">Modification Functions</a></h2>\n<h3 id=\"json_set\"><a class=\"header\" href=\"#json_set\">json_set</a></h3>\n<p><strong>json_set(json, path, value, …) -&gt; text</strong></p>\n<p>Creates or replaces values at the given paths. If the path exists, the value is replaced. If it does not exist, it is created (including intermediate objects and arrays as needed). Accepts multiple path/value pairs.</p>\n<pre><code class=\"language-sql\">SELECT json_set('{\"a\":2,\"c\":4}', '$.a', 99);\n-- {\"a\":99,\"c\":4}\n\nSELECT json_set('{\"a\":2,\"c\":4}', '$.e', 99);\n-- {\"a\":2,\"c\":4,\"e\":99}\n\nSELECT json_set('{}', '$.field', 'value');\n-- {\"field\":\"value\"}\n\nSELECT json_set('[123]', '$[0]', 456, '$[1]', 789);\n-- [456,789]\n</code></pre>\n<p>To set a JSON value (not a string), wrap it with <code>json()</code>:</p>\n<pre><code class=\"language-sql\">SELECT json_set('{\"a\":2,\"c\":4}', '$.c', json('[97,96]'));\n-- {\"a\":2,\"c\":[97,96]}\n</code></pre>\n<p><code>json_set</code> can create deeply nested structures:</p>\n<pre><code class=\"language-sql\">SELECT json_set('{}', '$.object.doesnt.exist', 'value');\n-- {\"object\":{\"doesnt\":{\"exist\":\"value\"}}}\n</code></pre>\n<p><code>jsonb_set(...)</code> returns the same result in JSONB format.</p>\n<h3 id=\"json_insert\"><a class=\"header\" href=\"#json_insert\">json_insert</a></h3>\n<p><strong>json_insert(json, path, value, …) -&gt; text</strong></p>\n<p>Inserts values only where the path does not already exist. Existing values are not modified.</p>\n<pre><code class=\"language-sql\">SELECT json_insert('{\"a\":1}', '$.a', 2);         -- {\"a\":1}  (no change)\nSELECT json_insert('{\"a\":1}', '$.b', 2);          -- {\"a\":1,\"b\":2}\nSELECT json_insert('[1,2,3]', '$[3]', 4);          -- [1,2,3,4]\nSELECT json_insert('{\"a\":1}', '$.b', 2, '$.c', 3);\n-- {\"a\":1,\"b\":2,\"c\":3}\n</code></pre>\n<p><code>jsonb_insert(...)</code> returns the same result in JSONB format.</p>\n<h3 id=\"json_replace\"><a class=\"header\" href=\"#json_replace\">json_replace</a></h3>\n<p><strong>json_replace(json, path, value, …) -&gt; text</strong></p>\n<p>Replaces values only where the path already exists. Does not create new paths.</p>\n<pre><code class=\"language-sql\">SELECT json_replace('{\"a\":1,\"b\":2}', '$.a', 42);     -- {\"a\":42,\"b\":2}\nSELECT json_replace('{\"a\":1,\"b\":2}', '$.c', 3);       -- {\"a\":1,\"b\":2}  (no change)\nSELECT json_replace('[1,2,3,4]', '$[1]', 99);          -- [1,99,3,4]\n</code></pre>\n<p><code>jsonb_replace(...)</code> returns the same result in JSONB format.</p>\n<h3 id=\"json_remove\"><a class=\"header\" href=\"#json_remove\">json_remove</a></h3>\n<p><strong>json_remove(json, path, …) -&gt; text</strong></p>\n<p>Returns a copy of the JSON with elements at the specified paths removed. Paths that do not exist are silently ignored. Removing the root element (<code>$</code>) returns NULL.</p>\n<pre><code class=\"language-sql\">SELECT json_remove('{\"a\":1,\"b\":2,\"c\":3}', '$.b');          -- {\"a\":1,\"c\":3}\nSELECT json_remove('[1,2,3,4]', '$[1]');                     -- [1,3,4]\nSELECT json_remove('{\"a\":1,\"b\":2}', '$.a', '$.b');          -- {}\nSELECT json_remove('{\"a\":1}', '$');                          -- (NULL)\n</code></pre>\n<p>When removing multiple array elements, removals are applied sequentially, and each removal shifts subsequent indices:</p>\n<pre><code class=\"language-sql\">SELECT json_remove('[0,1,2,3,4]', '$[2]', '$[0]');\n-- [1,3,4]\n</code></pre>\n<p><code>jsonb_remove(...)</code> returns the same result in JSONB format.</p>\n<h3 id=\"json_patch\"><a class=\"header\" href=\"#json_patch\">json_patch</a></h3>\n<p><strong>json_patch(json1, json2) -&gt; text</strong></p>\n<p>Applies the RFC-7396 MergePatch algorithm. Object members in <code>json2</code> are merged into <code>json1</code>: new keys are added, existing keys are replaced, and keys with a <code>null</code> value are deleted. Arrays are treated as atomic values and replaced entirely.</p>\n<pre><code class=\"language-sql\">SELECT json_patch('{\"a\":1,\"b\":2}', '{\"c\":3,\"d\":4}');\n-- {\"a\":1,\"b\":2,\"c\":3,\"d\":4}\n\nSELECT json_patch('{\"a\":1,\"b\":2}', '{\"b\":3}');\n-- {\"a\":1,\"b\":3}\n\nSELECT json_patch('{\"a\":1,\"b\":2}', '{\"a\":null}');\n-- {\"b\":2}\n\nSELECT json_patch('{\"user\":{\"name\":\"john\"}}', '{\"user\":{\"age\":30}}');\n-- {\"user\":{\"name\":\"john\",\"age\":30}}\n\nSELECT json_patch('{\"arr\":[1,2,3]}', '{\"arr\":[4,5,6]}');\n-- {\"arr\":[4,5,6]}\n</code></pre>\n<p><code>jsonb_patch(...)</code> returns the same result in JSONB format.</p>\n<hr>\n<h2 id=\"aggregate-functions-1\"><a class=\"header\" href=\"#aggregate-functions-1\">Aggregate Functions</a></h2>\n<h3 id=\"json_group_array\"><a class=\"header\" href=\"#json_group_array\">json_group_array</a></h3>\n<p><strong>json_group_array(value) -&gt; text</strong></p>\n<p>An aggregate function that collects all values from the group into a JSON array.</p>\n<pre><code class=\"language-sql\">CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, price REAL);\nINSERT INTO products VALUES (1, 'hat', 9.99), (2, 'shirt', 24.99), (3, 'coat', 79.99);\n\nSELECT json_group_array(name) FROM products;\n-- [\"hat\",\"shirt\",\"coat\"]\n</code></pre>\n<p>Can be combined with other JSON functions to build complex aggregated structures:</p>\n<pre><code class=\"language-sql\">SELECT json_group_array(json_object('name', name, 'price', price)) FROM products;\n-- [{\"name\":\"hat\",\"price\":9.99},{\"name\":\"shirt\",\"price\":24.99},{\"name\":\"coat\",\"price\":79.99}]\n</code></pre>\n<p><code>jsonb_group_array(value)</code> returns the same result in JSONB format.</p>\n<h3 id=\"json_group_object\"><a class=\"header\" href=\"#json_group_object\">json_group_object</a></h3>\n<p><strong>json_group_object(label, value) -&gt; text</strong></p>\n<p>An aggregate function that collects label/value pairs from the group into a JSON object.</p>\n<pre><code class=\"language-sql\">SELECT json_group_object(name, price) FROM products;\n-- {\"hat\":9.99,\"shirt\":24.99,\"coat\":79.99}\n</code></pre>\n<p><code>jsonb_group_object(label, value)</code> returns the same result in JSONB format.</p>\n<hr>\n<h2 id=\"table-valued-functions\"><a class=\"header\" href=\"#table-valued-functions\">Table-Valued Functions</a></h2>\n<h3 id=\"json_each\"><a class=\"header\" href=\"#json_each\">json_each</a></h3>\n<p><strong>json_each(json [, path])</strong></p>\n<p>A table-valued function that returns one row for each immediate child of a JSON array or object. It does not recurse into nested structures. With an optional <code>path</code> argument, iteration starts at the element identified by that path.</p>\n<p>The returned columns are:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Column</th><th>Type</th><th>Description</th></tr>\n</thead>\n<tbody>\n<tr><td><code>key</code></td><td>any</td><td>Array index (integer) or object key (text)</td></tr>\n<tr><td><code>value</code></td><td>any</td><td>SQL value for primitives; JSON text for objects/arrays</td></tr>\n<tr><td><code>type</code></td><td>text</td><td>One of: <code>object</code>, <code>array</code>, <code>integer</code>, <code>real</code>, <code>text</code>, <code>true</code>, <code>false</code>, <code>null</code></td></tr>\n<tr><td><code>atom</code></td><td>any</td><td>SQL value for primitives; NULL for objects/arrays</td></tr>\n<tr><td><code>id</code></td><td>integer</td><td>Unique identifier for this element</td></tr>\n<tr><td><code>parent</code></td><td>integer</td><td>Always NULL (only populated by <code>json_tree</code>)</td></tr>\n<tr><td><code>fullkey</code></td><td>text</td><td>Full JSON path to this element</td></tr>\n<tr><td><code>path</code></td><td>text</td><td>JSON path to the containing element</td></tr>\n</tbody>\n</table>\n</div>\n<pre><code class=\"language-sql\">SELECT key, value, type FROM json_each('[1, 2.5, \"x\", true, false, null]');\n-- 0|1|integer\n-- 1|2.5|real\n-- 2|x|text\n-- 3|1|true\n-- 4|0|false\n-- 5||null\n</code></pre>\n<pre><code class=\"language-sql\">SELECT key, value, type FROM json_each('{\"name\":\"Alice\",\"age\":30}');\n-- name|Alice|text\n-- age|30|integer\n</code></pre>\n<p>With a path argument:</p>\n<pre><code class=\"language-sql\">SELECT key, value FROM json_each('{\"a\":[10,20,30]}', '$.a');\n-- 0|10\n-- 1|20\n-- 2|30\n</code></pre>\n<p>A common use case is joining <code>json_each</code> against a table column to search within JSON arrays:</p>\n<pre><code class=\"language-sql\">CREATE TABLE contacts (id INTEGER PRIMARY KEY, name TEXT, phones TEXT);\nINSERT INTO contacts VALUES (1, 'Alice', '[\"555-0100\",\"555-0101\"]');\nINSERT INTO contacts VALUES (2, 'Bob', '[\"555-0200\"]');\n\nSELECT DISTINCT contacts.name\nFROM contacts, json_each(contacts.phones)\nWHERE json_each.value LIKE '555-01%';\n-- Alice\n</code></pre>\n<h3 id=\"json_tree\"><a class=\"header\" href=\"#json_tree\">json_tree</a></h3>\n<p><strong>json_tree(json [, path])</strong></p>\n<p>A table-valued function that recursively walks the entire JSON structure, returning one row for every element at every level of nesting. It returns the same columns as <code>json_each</code>, but the <code>parent</code> column is populated with the <code>id</code> of each element’s parent.</p>\n<pre><code class=\"language-sql\">SELECT key, type, fullkey FROM json_tree('{\"a\":1,\"b\":{\"c\":2},\"d\":[3,4]}') ORDER BY id;\n-- |object|$\n-- a|integer|$.a\n-- b|object|$.b\n-- c|integer|$.b.c\n-- d|array|$.d\n-- 0|integer|$.d[0]\n-- 1|integer|$.d[1]\n</code></pre>\n<h2 id=\"compatibility-17\"><a class=\"header\" href=\"#compatibility-17\">Compatibility</a></h2>\n<p>The <code>json_tree</code> function is partially supported. Basic recursive traversal works, but some edge cases involving negative array indices in the path argument have known limitations. See the SQLite documentation for full behavioral details.</p>\n<p>All other JSON functions listed on this page are fully compatible with SQLite.</p>\n<div style=\"break-before: page; page-break-before: always;\"></div>\n<h1 id=\"pragmas\"><a class=\"header\" href=\"#pragmas\">PRAGMAs</a></h1>\n<p>PRAGMAs are special SQL statements that configure the database engine or query internal state. Unlike regular SQL statements, PRAGMAs are specific to the database implementation and do not follow the SQL standard.</p>\n<h2 id=\"syntax-25\"><a class=\"header\" href=\"#syntax-25\">Syntax</a></h2>\n<pre><code class=\"language-sql\">PRAGMA pragma_name;\nPRAGMA pragma_name = value;\nPRAGMA pragma_name(value);\n</code></pre>\n<p>The first form queries the current value. The second and third forms set a new value. Not all PRAGMAs support both forms.</p>\n<hr>\n<h2 id=\"database-information\"><a class=\"header\" href=\"#database-information\">Database Information</a></h2>\n<h3 id=\"application_id\"><a class=\"header\" href=\"#application_id\">application_id</a></h3>\n<p><strong>PRAGMA application_id;</strong>\n<strong>PRAGMA application_id = <em>integer</em>;</strong></p>\n<p>Queries or sets the 32-bit signed integer “Application ID” stored at offset 68 in the database header. Applications that use Turso as a file format can set a unique application ID so that external tools can identify the file type. The default value is <code>0</code>.</p>\n<pre><code class=\"language-sql\">PRAGMA application_id;\n-- 0\n\nPRAGMA application_id = 12345;\nPRAGMA application_id;\n-- 12345\n</code></pre>\n<h3 id=\"database_list\"><a class=\"header\" href=\"#database_list\">database_list</a></h3>\n<p><strong>PRAGMA database_list;</strong></p>\n<p>Returns one row per attached database, with columns <code>seq</code> (sequence number), <code>name</code> (database name), and <code>file</code> (file path). The main database always has sequence number <code>0</code> and the name <code>main</code>.</p>\n<pre><code class=\"language-sql\">PRAGMA database_list;\n-- seq|name|file\n-- 0|main|\n</code></pre>\n<h3 id=\"encoding\"><a class=\"header\" href=\"#encoding\">encoding</a></h3>\n<p><strong>PRAGMA encoding;</strong></p>\n<p>Returns the text encoding used by the database. Turso uses UTF-8.</p>\n<pre><code class=\"language-sql\">PRAGMA encoding;\n-- UTF-8\n</code></pre>\n<h3 id=\"freelist_count\"><a class=\"header\" href=\"#freelist_count\">freelist_count</a></h3>\n<p><strong>PRAGMA freelist_count;</strong></p>\n<p>Returns the number of unused pages in the database file. A high freelist count may indicate the database would benefit from <code>VACUUM</code>.</p>\n<pre><code class=\"language-sql\">PRAGMA freelist_count;\n-- 0\n</code></pre>\n<h3 id=\"page_count\"><a class=\"header\" href=\"#page_count\">page_count</a></h3>\n<p><strong>PRAGMA page_count;</strong></p>\n<p>Returns the total number of pages in the database file. Multiply by the page size to get the approximate database size in bytes.</p>\n<pre><code class=\"language-sql\">PRAGMA page_count;\n-- 0\n</code></pre>\n<h3 id=\"page_size\"><a class=\"header\" href=\"#page_size\">page_size</a></h3>\n<p><strong>PRAGMA page_size;</strong>\n<strong>PRAGMA page_size = <em>bytes</em>;</strong></p>\n<p>Queries or sets the database page size in bytes. The value must be a power of two between 512 and 65536. The default is <code>4096</code>.</p>\n<p>Setting a new page size takes effect when the database is first created or after a <code>VACUUM</code> operation.</p>\n<pre><code class=\"language-sql\">PRAGMA page_size;\n-- 4096\n</code></pre>\n<h3 id=\"schema_version\"><a class=\"header\" href=\"#schema_version\">schema_version</a></h3>\n<p><strong>PRAGMA schema_version;</strong></p>\n<p>Returns the schema version number, an integer that Turso increments automatically whenever the database schema changes (via <code>CREATE TABLE</code>, <code>DROP TABLE</code>, etc.). The default for a new database is <code>0</code>.</p>\n<p>Writing to <code>schema_version</code> is accepted syntactically but treated as a no-op for safety. Modifying the schema version externally can cause prepared statements to use stale schemas and corrupt data.</p>\n<pre><code class=\"language-sql\">PRAGMA schema_version;\n-- 0\n</code></pre>\n<h3 id=\"user_version\"><a class=\"header\" href=\"#user_version\">user_version</a></h3>\n<p><strong>PRAGMA user_version;</strong>\n<strong>PRAGMA user_version = <em>integer</em>;</strong></p>\n<p>Queries or sets a user-defined 32-bit integer stored in the database header at offset 60. Turso does not use this value internally – it is available for applications to track their own schema migration version or any other purpose. The default is <code>0</code>.</p>\n<pre><code class=\"language-sql\">PRAGMA user_version;\n-- 0\n\nPRAGMA user_version = 100;\nPRAGMA user_version;\n-- 100\n</code></pre>\n<h3 id=\"pragma_list\"><a class=\"header\" href=\"#pragma_list\">pragma_list</a></h3>\n<p><strong>PRAGMA pragma_list;</strong></p>\n<p>Returns a list of all PRAGMA names recognized by the current database connection.</p>\n<pre><code class=\"language-sql\">PRAGMA pragma_list;\n-- application_id\n-- busy_timeout\n-- cache_size\n-- ...\n</code></pre>\n<hr>\n<h2 id=\"schema-information\"><a class=\"header\" href=\"#schema-information\">Schema Information</a></h2>\n<h3 id=\"table_info\"><a class=\"header\" href=\"#table_info\">table_info</a></h3>\n<p><strong>PRAGMA table_info(<em>table-name</em>);</strong></p>\n<p>Returns one row per regular column in the named table. Each row contains:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Column</th><th>Description</th></tr>\n</thead>\n<tbody>\n<tr><td><code>cid</code></td><td>Column index (zero-based)</td></tr>\n<tr><td><code>name</code></td><td>Column name</td></tr>\n<tr><td><code>type</code></td><td>Declared type (empty string if none)</td></tr>\n<tr><td><code>notnull</code></td><td><code>1</code> if the column has a NOT NULL constraint, <code>0</code> otherwise</td></tr>\n<tr><td><code>dflt_value</code></td><td>Default value expression, or empty if none</td></tr>\n<tr><td><code>pk</code></td><td><code>0</code> if the column is not part of the primary key; otherwise the 1-based index within the primary key</td></tr>\n</tbody>\n</table>\n</div>\n<p>Generated columns and hidden columns are omitted. Use <code>table_xinfo</code> for a complete listing.</p>\n<pre><code class=\"language-sql\">CREATE TABLE products (\n  id INTEGER PRIMARY KEY,\n  name TEXT NOT NULL,\n  price REAL DEFAULT 0.0,\n  in_stock INTEGER DEFAULT 1\n);\n\nPRAGMA table_info(products);\n-- cid|name|type|notnull|dflt_value|pk\n-- 0|id|INTEGER|0||1\n-- 1|name|TEXT|1||0\n-- 2|price|REAL|0|0.0|0\n-- 3|in_stock|INTEGER|0|1|0\n</code></pre>\n<h3 id=\"table_xinfo\"><a class=\"header\" href=\"#table_xinfo\">table_xinfo</a></h3>\n<p><strong>PRAGMA table_xinfo(<em>table-name</em>);</strong></p>\n<p>Returns the same columns as <code>table_info</code> plus an additional <code>hidden</code> column. This PRAGMA includes all columns – regular, generated, and hidden.</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th><code>hidden</code> Value</th><th>Meaning</th></tr>\n</thead>\n<tbody>\n<tr><td><code>0</code></td><td>Normal column</td></tr>\n<tr><td><code>1</code></td><td>Hidden column in a virtual table</td></tr>\n<tr><td><code>2</code></td><td>Virtual (dynamic) generated column</td></tr>\n<tr><td><code>3</code></td><td>Stored generated column</td></tr>\n</tbody>\n</table>\n</div>\n<pre><code class=\"language-sql\">CREATE TABLE products (\n  id INTEGER PRIMARY KEY,\n  name TEXT NOT NULL,\n  price REAL DEFAULT 0.0,\n  in_stock INTEGER DEFAULT 1\n);\n\nPRAGMA table_xinfo(products);\n-- cid|name|type|notnull|dflt_value|pk|hidden\n-- 0|id|INTEGER|0||1|0\n-- 1|name|TEXT|1||0|0\n-- 2|price|REAL|0|0.0|0|0\n-- 3|in_stock|INTEGER|0|1|0|0\n</code></pre>\n<hr>\n<h2 id=\"performance-and-caching\"><a class=\"header\" href=\"#performance-and-caching\">Performance and Caching</a></h2>\n<h3 id=\"busy_timeout\"><a class=\"header\" href=\"#busy_timeout\">busy_timeout</a></h3>\n<p><strong>PRAGMA busy_timeout;</strong>\n<strong>PRAGMA busy_timeout = <em>milliseconds</em>;</strong></p>\n<p>Queries or sets the busy timeout in milliseconds. When another connection holds a lock, Turso will retry for up to this many milliseconds before returning an error. The default is <code>0</code> (return immediately on lock contention).</p>\n<pre><code class=\"language-sql\">PRAGMA busy_timeout;\n-- 0\n\nPRAGMA busy_timeout = 5000;\nPRAGMA busy_timeout;\n-- 5000\n</code></pre>\n<h3 id=\"cache_size\"><a class=\"header\" href=\"#cache_size\">cache_size</a></h3>\n<p><strong>PRAGMA cache_size;</strong>\n<strong>PRAGMA cache_size = <em>pages</em>;</strong></p>\n<p>Queries or sets the suggested maximum number of database pages held in memory. A positive value specifies pages directly. A negative value specifies the cache size in kibibytes (KiB). The default is <code>-2000</code> (approximately 2 MB).</p>\n<p>This setting applies only to the current session and reverts to the default when the connection closes.</p>\n<pre><code class=\"language-sql\">PRAGMA cache_size;\n-- -2000\n\n-- Set cache to 4000 pages\nPRAGMA cache_size = 4000;\nPRAGMA cache_size;\n-- 4000\n\n-- Set cache to approximately 4 MB\nPRAGMA cache_size = -4000;\nPRAGMA cache_size;\n-- -4000\n</code></pre>\n<h3 id=\"cache_spill\"><a class=\"header\" href=\"#cache_spill\">cache_spill</a></h3>\n<p><strong>PRAGMA cache_spill;</strong>\n<strong>PRAGMA cache_spill = <em>boolean</em>;</strong></p>\n<p>Queries or sets whether the pager is allowed to spill dirty pages to the database file in the middle of a transaction. When enabled (the default, <code>1</code>), the pager may write dirty pages to disk before a transaction commits. When disabled (<code>0</code>), dirty pages are held in memory until commit.</p>\n<p>Disabling cache spill avoids acquiring an exclusive lock on the database file until the transaction commits, which can be useful for long-running transactions.</p>\n<pre><code class=\"language-sql\">PRAGMA cache_spill;\n-- 1\n\nPRAGMA cache_spill = 0;\nPRAGMA cache_spill;\n-- 0\n</code></pre>\n<h3 id=\"max_page_count\"><a class=\"header\" href=\"#max_page_count\">max_page_count</a></h3>\n<p><strong>PRAGMA max_page_count;</strong>\n<strong>PRAGMA max_page_count = <em>N</em>;</strong></p>\n<p>Queries or sets the maximum number of pages allowed in the database file. Both the query and set forms return the current maximum. The maximum cannot be reduced below the current database size. The default is <code>4294967294</code>.</p>\n<pre><code class=\"language-sql\">PRAGMA max_page_count;\n-- 4294967294\n\nPRAGMA max_page_count = 1000;\nPRAGMA max_page_count;\n-- 1000\n</code></pre>\n<h3 id=\"query_only\"><a class=\"header\" href=\"#query_only\">query_only</a></h3>\n<p><strong>PRAGMA query_only;</strong>\n<strong>PRAGMA query_only = <em>boolean</em>;</strong></p>\n<p>When set to <code>1</code>, all write operations (<code>CREATE</code>, <code>INSERT</code>, <code>UPDATE</code>, <code>DELETE</code>, <code>DROP</code>) are rejected with an error. The default is <code>0</code> (writes allowed).</p>\n<pre><code class=\"language-sql\">PRAGMA query_only = 1;\nCREATE TABLE test (id INTEGER);\n-- Error: Cannot execute write statement in query_only mode\n\nPRAGMA query_only = 0;\n</code></pre>\n<h3 id=\"temp_store\"><a class=\"header\" href=\"#temp_store\">temp_store</a></h3>\n<p><strong>PRAGMA temp_store;</strong>\n<strong>PRAGMA temp_store = {0 | 1 | 2};</strong></p>\n<p>Queries or sets the storage location for temporary tables and indices.</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Value</th><th>Name</th><th>Behavior</th></tr>\n</thead>\n<tbody>\n<tr><td><code>0</code></td><td>DEFAULT</td><td>Use the compile-time default</td></tr>\n<tr><td><code>1</code></td><td>FILE</td><td>Store temporary data in a file</td></tr>\n<tr><td><code>2</code></td><td>MEMORY</td><td>Store temporary data in memory</td></tr>\n</tbody>\n</table>\n</div>\n<p>Changing this setting deletes all existing temporary tables, indices, triggers, and views. The default is <code>0</code>.</p>\n<pre><code class=\"language-sql\">PRAGMA temp_store;\n-- 0\n\nPRAGMA temp_store = 2;\nPRAGMA temp_store;\n-- 2\n</code></pre>\n<hr>\n<h2 id=\"journaling-and-durability\"><a class=\"header\" href=\"#journaling-and-durability\">Journaling and Durability</a></h2>\n<h3 id=\"journal_mode\"><a class=\"header\" href=\"#journal_mode\">journal_mode</a></h3>\n<p><strong>PRAGMA journal_mode;</strong>\n<strong>PRAGMA journal_mode = <em>mode</em>;</strong></p>\n<p>Queries or sets the journal mode for the database. The journal mode controls how transactions are logged for crash recovery.</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Mode</th><th>Description</th></tr>\n</thead>\n<tbody>\n<tr><td><code>wal</code></td><td>Write-ahead logging. Allows concurrent readers and a single writer. This is the default in Turso.</td></tr>\n</tbody>\n</table>\n</div>\n<p>Turso defaults to WAL mode. Setting the journal mode returns the active mode.</p>\n<pre><code class=\"language-sql\">PRAGMA journal_mode;\n-- wal\n\nPRAGMA journal_mode = 'wal';\n-- wal\n</code></pre>\n<h3 id=\"synchronous\"><a class=\"header\" href=\"#synchronous\">synchronous</a></h3>\n<p><strong>PRAGMA synchronous;</strong>\n<strong>PRAGMA synchronous = {0 | 2};</strong></p>\n<p>Queries or sets the synchronous flag, which controls how aggressively Turso forces writes to disk. The value is returned as an integer.</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Value</th><th>Name</th><th>Behavior</th></tr>\n</thead>\n<tbody>\n<tr><td><code>0</code></td><td>OFF</td><td>No synchronous writes. Fastest, but the database may corrupt if the operating system crashes or power is lost.</td></tr>\n<tr><td><code>2</code></td><td>FULL</td><td>Synchronous writes at every critical moment. Safe against both application crashes and OS/power failures. This is the default.</td></tr>\n</tbody>\n</table>\n</div>\n<pre><code class=\"language-sql\">PRAGMA synchronous;\n-- 2\n\nPRAGMA synchronous = OFF;\nPRAGMA synchronous;\n-- 0\n\nPRAGMA synchronous = FULL;\nPRAGMA synchronous;\n-- 2\n</code></pre>\n<h3 id=\"wal_checkpoint\"><a class=\"header\" href=\"#wal_checkpoint\">wal_checkpoint</a></h3>\n<p><strong>PRAGMA wal_checkpoint;</strong></p>\n<p>Runs a checkpoint operation on the write-ahead log (WAL). A checkpoint transfers data from the WAL file back into the main database file. Returns three columns:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Column</th><th>Description</th></tr>\n</thead>\n<tbody>\n<tr><td><code>busy</code></td><td><code>0</code> if the checkpoint completed, <code>1</code> if it was blocked by a concurrent reader or writer</td></tr>\n<tr><td><code>log</code></td><td>Total number of frames in the WAL</td></tr>\n<tr><td><code>checkpointed</code></td><td>Number of frames successfully checkpointed</td></tr>\n</tbody>\n</table>\n</div>\n<pre><code class=\"language-sql\">PRAGMA wal_checkpoint;\n-- busy|log|checkpointed\n-- 0|0|0\n</code></pre>\n<hr>\n<h2 id=\"constraint-enforcement\"><a class=\"header\" href=\"#constraint-enforcement\">Constraint Enforcement</a></h2>\n<h3 id=\"foreign_keys\"><a class=\"header\" href=\"#foreign_keys\">foreign_keys</a></h3>\n<p><strong>PRAGMA foreign_keys;</strong>\n<strong>PRAGMA foreign_keys = <em>boolean</em>;</strong></p>\n<p>Queries or sets whether foreign key constraints are enforced. When enabled (<code>1</code>), INSERT, UPDATE, and DELETE operations that violate a foreign key constraint will fail with an error. The default is <code>0</code> (foreign keys not enforced).</p>\n<p>This setting cannot be changed while a transaction is active.</p>\n<pre><code class=\"language-sql\">PRAGMA foreign_keys;\n-- 0\n\nPRAGMA foreign_keys = 1;\n\nCREATE TABLE orders (id INTEGER PRIMARY KEY, total REAL);\nCREATE TABLE items (\n  id INTEGER PRIMARY KEY,\n  order_id INTEGER REFERENCES orders(id)\n);\n\n-- This fails because order 999 does not exist\nINSERT INTO items VALUES (1, 999);\n-- Error: foreign key constraint failed\n</code></pre>\n<h3 id=\"ignore_check_constraints\"><a class=\"header\" href=\"#ignore_check_constraints\">ignore_check_constraints</a></h3>\n<p><strong>PRAGMA ignore_check_constraints;</strong>\n<strong>PRAGMA ignore_check_constraints = <em>boolean</em>;</strong></p>\n<p>Queries or sets whether CHECK constraints are enforced. When set to <code>1</code>, CHECK constraint violations are silently ignored during INSERT and UPDATE operations. The default is <code>0</code> (CHECK constraints enforced).</p>\n<pre><code class=\"language-sql\">CREATE TABLE orders (\n  id INTEGER PRIMARY KEY,\n  customer TEXT NOT NULL,\n  total REAL CHECK(total &gt;= 0)\n);\n\n-- With CHECK constraints enforced (default)\nINSERT INTO orders VALUES (1, 'Alice', -5.0);\n-- Error: CHECK constraint failed: total &gt;= 0\n\n-- Disable CHECK constraints\nPRAGMA ignore_check_constraints = 1;\nINSERT INTO orders VALUES (1, 'Alice', -5.0);\n-- Succeeds despite negative total\n</code></pre>\n<hr>\n<h2 id=\"integrity-checking\"><a class=\"header\" href=\"#integrity-checking\">Integrity Checking</a></h2>\n<h3 id=\"integrity_check\"><a class=\"header\" href=\"#integrity_check\">integrity_check</a></h3>\n<p><strong>PRAGMA integrity_check;</strong></p>\n<p>Performs a thorough consistency check of the entire database. Examines every page, verifies B-tree structure, checks for missing or duplicate index entries, validates UNIQUE and NOT NULL constraints, and confirms freelist integrity. Returns <code>ok</code> if no problems are found, or one or more rows describing any issues detected.</p>\n<pre><code class=\"language-sql\">PRAGMA integrity_check;\n-- ok\n</code></pre>\n<h3 id=\"quick_check\"><a class=\"header\" href=\"#quick_check\">quick_check</a></h3>\n<p><strong>PRAGMA quick_check;</strong></p>\n<p>Performs a faster but less comprehensive consistency check than <code>integrity_check</code>. It verifies B-tree structure and record formatting but does not validate UNIQUE constraints or cross-check index content against table content. Returns <code>ok</code> if no problems are found.</p>\n<pre><code class=\"language-sql\">PRAGMA quick_check;\n-- ok\n</code></pre>\n<hr>\n<h2 id=\"deprecated\"><a class=\"header\" href=\"#deprecated\">Deprecated</a></h2>\n<h3 id=\"legacy_file_format\"><a class=\"header\" href=\"#legacy_file_format\">legacy_file_format</a></h3>\n<p><strong>PRAGMA legacy_file_format;</strong></p>\n<p>This PRAGMA is a no-op. It exists for compatibility but does not return a value or perform any action.</p>\n<hr>\n<h2 id=\"compatibility-18\"><a class=\"header\" href=\"#compatibility-18\">Compatibility</a></h2>\n<p>Turso supports the PRAGMAs listed on this page. The following differences from SQLite are worth noting:</p>\n<ul>\n<li><strong>journal_mode</strong>: Turso defaults to <code>wal</code> mode. Other journal modes (<code>delete</code>, <code>truncate</code>, <code>persist</code>, <code>memory</code>, <code>off</code>) are not supported.</li>\n<li><strong>synchronous</strong>: Only <code>OFF</code> (0) and <code>FULL</code> (2) are supported. <code>NORMAL</code> (1) and <code>EXTRA</code> (3) are not available.</li>\n<li><strong>cache_spill</strong>: Only the boolean enable/disable form is supported. The numeric threshold form (<code>PRAGMA schema.cache_spill = N</code>) is not available.</li>\n<li><strong>schema_version</strong>: Reads work normally. Writes are accepted but silently ignored to prevent accidental database corruption.</li>\n<li><strong>wal_checkpoint</strong>: Only the no-argument form is supported. Checkpoint modes (<code>PASSIVE</code>, <code>FULL</code>, <code>RESTART</code>, <code>TRUNCATE</code>) are not available.</li>\n<li><strong>legacy_file_format</strong>: Accepted for compatibility but is a no-op, matching modern SQLite behavior.</li>\n</ul>\n<div style=\"break-before: page; page-break-before: always;\"></div>\n<h1 id=\"custom-types\"><a class=\"header\" href=\"#custom-types\">Custom Types</a></h1>\n<blockquote>\n<p><strong>Turso Extension.</strong> This feature is not available in SQLite. Custom types require STRICT tables for encode/decode behavior.</p>\n</blockquote>\n<h2 id=\"overview\"><a class=\"header\" href=\"#overview\">Overview</a></h2>\n<p>Turso supports user-defined custom types that extend the type system of STRICT tables. A custom type defines how values are transformed when written to and read from storage using ENCODE and DECODE expressions, allowing you to enforce domain constraints, normalize data, or change the storage representation without modifying application queries.</p>\n<p>Custom types are built on top of one of the four base storage types (<code>TEXT</code>, <code>INTEGER</code>, <code>REAL</code>, <code>BLOB</code>). When a value is inserted into a column with a custom type, the ENCODE expression transforms it before writing. When the value is read back, the DECODE expression reverses the transformation. This is transparent to queries: SELECT statements return decoded values, and WHERE clauses operate on decoded values.</p>\n<p>Type definitions are stored in the <code>sqlite_turso_types</code> system table and are loaded into an in-memory registry when the database is opened. Types persist across sessions.</p>\n<h2 id=\"syntax-26\"><a class=\"header\" href=\"#syntax-26\">Syntax</a></h2>\n<h3 id=\"create-type\"><a class=\"header\" href=\"#create-type\">CREATE TYPE</a></h3>\n<pre><code class=\"language-sql\">CREATE TYPE [IF NOT EXISTS] type_name [(param [, ...])]\n    BASE {TEXT | INTEGER | REAL | BLOB}\n    [ENCODE expr]\n    [DECODE expr]\n    [DEFAULT expr]\n    [OPERATOR 'op' (right_type) -&gt; func_name]*;\n</code></pre>\n<h3 id=\"drop-type\"><a class=\"header\" href=\"#drop-type\">DROP TYPE</a></h3>\n<pre><code class=\"language-sql\">DROP TYPE [IF EXISTS] type_name;\n</code></pre>\n<h2 id=\"description-23\"><a class=\"header\" href=\"#description-23\">Description</a></h2>\n<p><code>CREATE TYPE</code> registers a new custom type in the database. The type definition specifies:</p>\n<ul>\n<li><strong>BASE</strong>: The underlying storage type. All values for this type are stored on disk using this SQLite storage class. Must be one of <code>TEXT</code>, <code>INTEGER</code>, <code>REAL</code>, or <code>BLOB</code>.</li>\n<li><strong>ENCODE</strong>: An expression applied to the input value before writing to storage. The keyword <code>value</code> is a placeholder that refers to the value being inserted. If omitted, values are stored as-is.</li>\n<li><strong>DECODE</strong>: An expression applied to the stored value when reading from the table. The keyword <code>value</code> is a placeholder that refers to the raw stored value. If omitted, values are returned as-is.</li>\n<li><strong>DEFAULT</strong>: A fallback default expression used when a column of this type has no column-level DEFAULT and no explicit value is provided in an INSERT.</li>\n<li><strong>OPERATOR</strong>: Maps an operator symbol to a named function for use with values of this type.</li>\n</ul>\n<p><code>DROP TYPE</code> removes a custom type definition from the database. If the type does not exist and <code>IF EXISTS</code> is not specified, an error is raised.</p>\n<h2 id=\"clauses-13\"><a class=\"header\" href=\"#clauses-13\">Clauses</a></h2>\n<h3 id=\"base\"><a class=\"header\" href=\"#base\">BASE</a></h3>\n<p>The <code>BASE</code> clause is required and specifies the underlying storage type.</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Base Type</th><th>Description</th></tr>\n</thead>\n<tbody>\n<tr><td><code>TEXT</code></td><td>Stored as a UTF-8 text string</td></tr>\n<tr><td><code>INTEGER</code></td><td>Stored as a signed integer (up to 8 bytes)</td></tr>\n<tr><td><code>REAL</code></td><td>Stored as an 8-byte IEEE 754 floating-point number</td></tr>\n<tr><td><code>BLOB</code></td><td>Stored as raw binary data</td></tr>\n</tbody>\n</table>\n</div>\n<p>The base type determines how the encoded value is stored on disk and what type affinity rules apply during storage.</p>\n<h3 id=\"encode\"><a class=\"header\" href=\"#encode\">ENCODE</a></h3>\n<p>The <code>ENCODE</code> clause defines an expression that transforms values on write. The special identifier <code>value</code> represents the input being inserted. The expression can be any valid SQL expression, including function calls, arithmetic, CASE expressions, and nested function calls.</p>\n<p>If the ENCODE expression raises an error (for example, <code>json(value)</code> on invalid JSON), the INSERT statement fails. This makes ENCODE a natural place to add validation logic.</p>\n<h3 id=\"decode\"><a class=\"header\" href=\"#decode\">DECODE</a></h3>\n<p>The <code>DECODE</code> clause defines an expression that transforms values on read. The special identifier <code>value</code> represents the raw stored value. The DECODE expression is applied whenever a column with the custom type is selected.</p>\n<h3 id=\"default-1\"><a class=\"header\" href=\"#default-1\">DEFAULT</a></h3>\n<p>The <code>DEFAULT</code> clause specifies a fallback value for columns of this type when no value is provided during INSERT. If a column also has its own <code>DEFAULT</code> clause in the <code>CREATE TABLE</code> statement, the column-level default takes priority.</p>\n<h3 id=\"operator\"><a class=\"header\" href=\"#operator\">OPERATOR</a></h3>\n<p>The <code>OPERATOR</code> clause maps an operator symbol to a named function. The syntax is:</p>\n<pre><code class=\"language-sql\">OPERATOR 'op' (right_type) -&gt; func_name\n</code></pre>\n<p>Where <code>op</code> is the operator symbol (such as <code>+</code>, <code>-</code>, <code>&lt;</code>, <code>=</code>), <code>right_type</code> is the type of the right-hand operand, and <code>func_name</code> is the function to call when this operator is used between values of the custom type.</p>\n<h3 id=\"if-not-exists--if-exists\"><a class=\"header\" href=\"#if-not-exists--if-exists\">IF NOT EXISTS / IF EXISTS</a></h3>\n<p><code>CREATE TYPE IF NOT EXISTS</code> silently succeeds if a type with the same name already exists. <code>DROP TYPE IF EXISTS</code> silently succeeds if the type does not exist.</p>\n<h2 id=\"parametric-types\"><a class=\"header\" href=\"#parametric-types\">Parametric Types</a></h2>\n<p>Custom types can accept parameters that are substituted into the ENCODE and DECODE expressions. Parameters are declared in parentheses after the type name and referenced by name in the expressions.</p>\n<pre><code class=\"language-sql\">CREATE TYPE type_name(param1, param2)\n    BASE base_type\n    ENCODE expr_using_param1_and_param2\n    DECODE expr_using_param1_and_param2;\n</code></pre>\n<p>When the type is used in a <code>CREATE TABLE</code> statement, the actual parameter values are provided:</p>\n<pre><code class=\"language-sql\">CREATE TABLE t (col type_name(100, 2)) STRICT;\n</code></pre>\n<p>The parameter values are substituted into the ENCODE and DECODE expressions at compile time.</p>\n<h2 id=\"using-custom-types-in-tables\"><a class=\"header\" href=\"#using-custom-types-in-tables\">Using Custom Types in Tables</a></h2>\n<p>Custom types are used as column type names in <code>CREATE TABLE ... STRICT</code> statements. The type name replaces a standard type like <code>TEXT</code> or <code>INTEGER</code>:</p>\n<pre><code class=\"language-sql\">CREATE TYPE cents BASE integer ENCODE value * 100 DECODE value / 100;\nCREATE TABLE products (\n    id INTEGER PRIMARY KEY,\n    name TEXT,\n    price cents\n) STRICT;\n</code></pre>\n<p>Custom types require STRICT tables. On non-STRICT tables, the ENCODE transformation is not applied during insertion, which leads to incorrect values when DECODE runs on read.</p>\n<h2 id=\"cast-with-custom-types\"><a class=\"header\" href=\"#cast-with-custom-types\">CAST with Custom Types</a></h2>\n<p>The <code>CAST</code> expression can target a custom type. When you write <code>CAST(expr AS type_name)</code>, the ENCODE and DECODE expressions are both applied (a round-trip), producing the normalized user-facing form of the value:</p>\n<pre><code class=\"language-sql\">CREATE TYPE normalized BASE text ENCODE lower(value) DECODE value;\nSELECT CAST('Hello World' AS normalized);\n-- Returns 'hello world'\n</code></pre>\n<p>For parametric types, the parameters must be provided:</p>\n<pre><code class=\"language-sql\">SELECT CAST(42 AS numeric(10,2));\n-- Returns '42.00'\n</code></pre>\n<p>This is useful for normalizing values or validating that a value conforms to a custom type's constraints outside of an INSERT context.</p>\n<h2 id=\"inspecting-custom-types\"><a class=\"header\" href=\"#inspecting-custom-types\">Inspecting Custom Types</a></h2>\n<p>Custom type definitions are stored in the <code>sqlite_turso_types</code> system table. You can query them directly:</p>\n<pre><code class=\"language-sql\">SELECT name, sql FROM sqlite_turso_types;\n</code></pre>\n<h2 id=\"null-handling-5\"><a class=\"header\" href=\"#null-handling-5\">NULL Handling</a></h2>\n<p>NULL values pass through ENCODE and DECODE unchanged. If you insert NULL into a column with a custom type, NULL is stored and NULL is returned on read, regardless of the ENCODE and DECODE expressions.</p>\n<h2 id=\"examples-22\"><a class=\"header\" href=\"#examples-22\">Examples</a></h2>\n<h3 id=\"basic-custom-type\"><a class=\"header\" href=\"#basic-custom-type\">Basic Custom Type</a></h3>\n<pre><code class=\"language-sql\">-- Define a type that stores monetary values as cents\nCREATE TYPE cents BASE integer\n    ENCODE value * 100\n    DECODE value / 100;\n\n-- Use it in a STRICT table\nCREATE TABLE products (\n    id INTEGER PRIMARY KEY,\n    name TEXT,\n    price cents\n) STRICT;\n\nINSERT INTO products VALUES (1, 'Coffee', 4);\nINSERT INTO products VALUES (2, 'Tea', 2);\nINSERT INTO products VALUES (3, 'Juice', 3);\n\n-- Values are decoded on read\nSELECT id, name, price FROM products;\n-- 1|Coffee|4\n-- 2|Tea|2\n-- 3|Juice|3\n</code></pre>\n<h3 id=\"json-validation-type\"><a class=\"header\" href=\"#json-validation-type\">JSON Validation Type</a></h3>\n<pre><code class=\"language-sql\">-- Define a type that validates JSON on insert\nCREATE TYPE validated_json BASE text\n    ENCODE json(value)\n    DECODE value;\n\nCREATE TABLE config (\n    id INTEGER PRIMARY KEY,\n    data validated_json\n) STRICT;\n\n-- Valid JSON is accepted and normalized\nINSERT INTO config VALUES (1, '{\"key\": \"val\"}');\nSELECT id, data FROM config;\n-- 1|{\"key\":\"val\"}\n\n-- Invalid JSON is rejected at insert time\nINSERT INTO config VALUES (2, 'not valid json');\n-- Error: malformed JSON\n</code></pre>\n<h3 id=\"text-normalization-type\"><a class=\"header\" href=\"#text-normalization-type\">Text Normalization Type</a></h3>\n<pre><code class=\"language-sql\">-- Define a type that lowercases text on insert\nCREATE TYPE normalized BASE text\n    ENCODE lower(value)\n    DECODE value;\n\nCREATE TABLE tags (label normalized) STRICT;\n\nINSERT INTO tags VALUES ('JavaScript');\nINSERT INTO tags VALUES ('PYTHON');\nINSERT INTO tags VALUES ('Rust');\n\nSELECT label FROM tags ORDER BY label;\n-- javascript\n-- python\n-- rust\n</code></pre>\n<h3 id=\"type-level-default\"><a class=\"header\" href=\"#type-level-default\">Type-Level DEFAULT</a></h3>\n<pre><code class=\"language-sql\">-- Define a type with a default value\nCREATE TYPE cents BASE integer\n    ENCODE value * 100\n    DECODE value / 100\n    DEFAULT 0;\n\nCREATE TABLE products (\n    id INTEGER PRIMARY KEY,\n    price cents\n) STRICT;\n\n-- Omitting price uses the type-level default of 0\nINSERT INTO products(id) VALUES (1);\nINSERT INTO products VALUES (2, 5);\n\nSELECT id, price FROM products;\n-- 1|0\n-- 2|5\n</code></pre>\n<h3 id=\"column-default-overrides-type-default\"><a class=\"header\" href=\"#column-default-overrides-type-default\">Column DEFAULT Overrides Type DEFAULT</a></h3>\n<pre><code class=\"language-sql\">CREATE TYPE cents BASE integer\n    ENCODE value * 100\n    DECODE value / 100\n    DEFAULT 0;\n\n-- Column-level DEFAULT takes priority over type-level DEFAULT\nCREATE TABLE products (\n    id INTEGER PRIMARY KEY,\n    price cents DEFAULT 10\n) STRICT;\n\nINSERT INTO products(id) VALUES (1);\nSELECT id, price FROM products;\n-- 1|10\n</code></pre>\n<h3 id=\"parametric-type\"><a class=\"header\" href=\"#parametric-type\">Parametric Type</a></h3>\n<pre><code class=\"language-sql\">-- Define a clamping type with configurable bounds\nCREATE TYPE clamp(lo, hi) BASE integer\n    ENCODE CASE\n        WHEN value &lt; lo THEN lo\n        WHEN value &gt; hi THEN hi\n        ELSE value\n    END\n    DECODE value;\n\nCREATE TABLE readings (\n    id INTEGER PRIMARY KEY,\n    temperature clamp(0, 100)\n) STRICT;\n\nINSERT INTO readings VALUES (1, 50);\nINSERT INTO readings VALUES (2, 150);\nINSERT INTO readings VALUES (3, -20);\n\nSELECT id, temperature FROM readings;\n-- 1|50\n-- 2|100\n-- 3|0\n</code></pre>\n<h3 id=\"multiple-custom-types-in-one-table\"><a class=\"header\" href=\"#multiple-custom-types-in-one-table\">Multiple Custom Types in One Table</a></h3>\n<pre><code class=\"language-sql\">CREATE TYPE cents BASE integer\n    ENCODE value * 100\n    DECODE value / 100;\n\nCREATE TYPE normalized BASE text\n    ENCODE lower(value)\n    DECODE value;\n\nCREATE TABLE products (\n    id INTEGER PRIMARY KEY,\n    name normalized,\n    price cents\n) STRICT;\n\nINSERT INTO products VALUES (1, 'Coffee', 4);\nINSERT INTO products VALUES (2, 'TEA', 2);\n\nSELECT id, name, price FROM products;\n-- 1|coffee|4\n-- 2|tea|2\n</code></pre>\n<h3 id=\"null-passes-through\"><a class=\"header\" href=\"#null-passes-through\">NULL Passes Through</a></h3>\n<pre><code class=\"language-sql\">CREATE TYPE cents BASE integer\n    ENCODE value * 100\n    DECODE value / 100;\n\nCREATE TABLE products (\n    id INTEGER PRIMARY KEY,\n    price cents\n) STRICT;\n\nINSERT INTO products VALUES (1, NULL);\nSELECT id, COALESCE(price, 'IS_NULL') FROM products;\n-- 1|IS_NULL\n</code></pre>\n<h3 id=\"create-type-if-not-exists\"><a class=\"header\" href=\"#create-type-if-not-exists\">CREATE TYPE IF NOT EXISTS</a></h3>\n<pre><code class=\"language-sql\">CREATE TYPE cents BASE integer\n    ENCODE value * 100\n    DECODE value / 100;\n\n-- Does not raise an error if the type already exists\nCREATE TYPE IF NOT EXISTS cents BASE integer\n    ENCODE value * 100\n    DECODE value / 100;\n\nSELECT count(*) FROM sqlite_turso_types WHERE name = 'cents';\n-- 1\n</code></pre>\n<h3 id=\"drop-type-1\"><a class=\"header\" href=\"#drop-type-1\">DROP TYPE</a></h3>\n<pre><code class=\"language-sql\">CREATE TYPE cents BASE integer\n    ENCODE value * 100\n    DECODE value / 100;\n\nDROP TYPE cents;\n\nSELECT count(*) FROM sqlite_turso_types WHERE name = 'cents';\n-- 0\n\n-- DROP TYPE IF EXISTS does not raise an error for missing types\nDROP TYPE IF EXISTS nonexistent;\n</code></pre>\n<h3 id=\"inspecting-types-via-sqlite_turso_types\"><a class=\"header\" href=\"#inspecting-types-via-sqlite_turso_types\">Inspecting Types via sqlite_turso_types</a></h3>\n<pre><code class=\"language-sql\">CREATE TYPE cents BASE integer\n    ENCODE value * 100\n    DECODE value / 100\n    DEFAULT 0;\n\nSELECT name, sql FROM sqlite_turso_types WHERE name = 'cents';\n-- cents|CREATE TYPE cents BASE integer ENCODE value * 100 DECODE value / 100 DEFAULT 0\n</code></pre>\n<h2 id=\"compatibility-19\"><a class=\"header\" href=\"#compatibility-19\">Compatibility</a></h2>\n<ul>\n<li>Custom types are a Turso extension and are not available in SQLite.</li>\n<li>Custom types require STRICT tables for correct encode/decode behavior. On non-STRICT tables, custom type names are treated as standard type affinity hints and encode/decode expressions are not applied.</li>\n<li>Type definitions are stored in the <code>sqlite_turso_types</code> system table. SQLite-based tools that open a Turso database may not understand this table.</li>\n<li>The OPERATOR clause maps operators to named functions. These functions must be available in the Turso function registry (either built-in or loaded via extensions).</li>\n</ul>\n<div style=\"break-before: page; page-break-before: always;\"></div>\n<h1 id=\"vector-search\"><a class=\"header\" href=\"#vector-search\">Vector Search</a></h1>\n<blockquote>\n<p><strong>Turso extension.</strong> Vector search is a Turso-specific feature and is not available in standard SQLite.</p>\n</blockquote>\n<h2 id=\"overview-1\"><a class=\"header\" href=\"#overview-1\">Overview</a></h2>\n<p>Turso supports vector operations for building similarity search and semantic search applications. Vectors are fixed-length arrays of floating-point numbers that represent data points in a high-dimensional space. They are commonly produced by machine learning embedding models that convert text, images, or other data into numerical representations where similar items have nearby vectors.</p>\n<p>Vectors are stored as compact binary BLOBs and can be compared using built-in distance functions to find the most similar items.</p>\n<p>A typical workflow is:</p>\n<ol>\n<li>Create a table with a BLOB column (or the <code>F32_BLOB</code>/<code>F64_BLOB</code> type hint) to hold embeddings.</li>\n<li>Generate embeddings externally using a model (such as OpenAI, Cohere, or a local model) and insert them using the <code>vector32()</code> or <code>vector64()</code> creation functions. These functions convert a JSON text array into a compact binary representation.</li>\n<li>At query time, convert the search query into an embedding using the same model, then use a distance function (<code>vector_distance_cos</code>, <code>vector_distance_l2</code>, etc.) with <code>ORDER BY</code> and <code>LIMIT</code> to find the nearest neighbors.</li>\n</ol>\n<p>Vector indexes are not yet supported. All vector searches currently use brute-force scanning, which means search time scales linearly with the number of rows. For small to medium datasets (up to hundreds of thousands of rows), brute-force search is often fast enough. For larger datasets, consider partitioning or pre-filtering with WHERE clauses on non-vector columns.</p>\n<h2 id=\"vector-types\"><a class=\"header\" href=\"#vector-types\">Vector Types</a></h2>\n<p>Turso supports three vector formats:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Format</th><th>Function</th><th>Bytes per dimension</th><th>Description</th></tr>\n</thead>\n<tbody>\n<tr><td>Float32 dense</td><td><code>vector32()</code> or <code>vector()</code></td><td>4</td><td>32-bit floating-point. Default format, good balance of precision and size.</td></tr>\n<tr><td>Float64 dense</td><td><code>vector64()</code></td><td>8</td><td>64-bit floating-point. Higher precision, double the storage.</td></tr>\n<tr><td>Float32 sparse</td><td><code>vector32_sparse()</code></td><td>4 (non-zero only)</td><td>32-bit floating-point sparse representation. Only stores non-zero dimensions.</td></tr>\n</tbody>\n</table>\n</div>\n<p>The <code>vector()</code> function is an alias for <code>vector32()</code>.</p>\n<h2 id=\"column-types\"><a class=\"header\" href=\"#column-types\">Column Types</a></h2>\n<p>Vectors are stored on disk as BLOBs. You can use a plain <code>BLOB</code> column or the optional type hints <code>F32_BLOB(n)</code> and <code>F64_BLOB(n)</code>, where <code>n</code> is the number of dimensions. The type hint is purely documentary and does not enforce a dimension constraint at the storage layer.</p>\n<pre><code class=\"language-sql\">CREATE TABLE documents (\n    id INTEGER PRIMARY KEY,\n    content TEXT,\n    embedding F32_BLOB(4)\n);\n</code></pre>\n<h2 id=\"vector-creation-functions\"><a class=\"header\" href=\"#vector-creation-functions\">Vector Creation Functions</a></h2>\n<h3 id=\"vectortext-vector32text\"><a class=\"header\" href=\"#vectortext-vector32text\">vector(text), vector32(text)</a></h3>\n<p><strong>vector(text) -&gt; blob</strong>\n<strong>vector32(text) -&gt; blob</strong></p>\n<p>Parse a JSON array of numbers into a 32-bit floating-point vector BLOB. The input must be a single-quoted string containing a JSON array of numeric values (integers or floats). <code>vector()</code> is an alias for <code>vector32()</code>. Each number in the array becomes one dimension of the vector. The values must be finite (no NaN or infinity).</p>\n<pre><code class=\"language-sql\">SELECT vector_extract(vector32('[1.0, 2.0, 3.0]'));  -- [1,2,3]\n</code></pre>\n<pre><code class=\"language-sql\">SELECT vector_extract(vector('[4.5, 5.5, 6.5]'));  -- [4.5,5.5,6.5]\n</code></pre>\n<h3 id=\"vector64text\"><a class=\"header\" href=\"#vector64text\">vector64(text)</a></h3>\n<p><strong>vector64(text) -&gt; blob</strong></p>\n<p>Parse a JSON array of numbers into a 64-bit floating-point vector BLOB. Use this when you need higher numerical precision than 32-bit floats provide. The trade-off is double the storage per dimension (8 bytes instead of 4 bytes).</p>\n<pre><code class=\"language-sql\">SELECT vector_extract(vector64('[1.0, 2.0, 3.0]'));  -- [1,2,3]\n</code></pre>\n<h3 id=\"vector32_sparsetext\"><a class=\"header\" href=\"#vector32_sparsetext\">vector32_sparse(text)</a></h3>\n<p><strong>vector32_sparse(text) -&gt; blob</strong></p>\n<p>Parse a JSON array into a sparse 32-bit floating-point vector. Zero-valued dimensions are omitted from the binary representation, reducing storage for vectors with many zeros. This is particularly useful for bag-of-words models, TF-IDF representations, or any embedding scheme where most dimensions are zero. The full dimensionality of the vector is preserved – <code>vector_extract</code> will reconstruct the zeros – but only non-zero values are stored on disk.</p>\n<pre><code class=\"language-sql\">SELECT vector_extract(vector32_sparse('[1.0, 0.0, 3.0]'));  -- [1,0,3]\n</code></pre>\n<h2 id=\"distance-functions\"><a class=\"header\" href=\"#distance-functions\">Distance Functions</a></h2>\n<p>All distance functions take two vector BLOBs and return a floating-point number. Both vectors must have the same number of dimensions. Passing vectors with different dimension counts will result in an error.</p>\n<p>When using distance functions for nearest-neighbor search, sort results in ascending order (<code>ORDER BY distance ASC</code> or simply <code>ORDER BY distance</code>) so that the most similar items appear first. All distance functions in Turso follow this convention: smaller values mean more similar vectors.</p>\n<h3 id=\"vector_distance_cosv1-v2\"><a class=\"header\" href=\"#vector_distance_cosv1-v2\">vector_distance_cos(v1, v2)</a></h3>\n<p><strong>vector_distance_cos(v1, v2) -&gt; real</strong></p>\n<p>Compute the cosine distance between two vectors, defined as <code>1 - cosine_similarity</code>. The result ranges from 0 (identical direction) to 2 (opposite direction). A value of 1 means the vectors are orthogonal.</p>\n<p>Cosine distance is typically preferred for text and document embeddings because it measures the angle between vectors rather than their magnitude.</p>\n<pre><code class=\"language-sql\">-- Identical vectors: distance near 0\nSELECT vector_distance_cos(\n    vector32('[1.0, 0.0]'),\n    vector32('[1.0, 0.0]')\n);  -- ~0.0\n\n-- Orthogonal vectors: distance = 1\nSELECT vector_distance_cos(\n    vector32('[1.0, 0.0, 0.0]'),\n    vector32('[0.0, 0.0, 1.0]')\n);  -- 1.0\n\n-- Opposite vectors: distance near 2\nSELECT vector_distance_cos(\n    vector32('[1.0, 0.0]'),\n    vector32('[-1.0, 0.0]')\n);  -- ~2.0\n</code></pre>\n<h3 id=\"vector_distance_l2v1-v2\"><a class=\"header\" href=\"#vector_distance_l2v1-v2\">vector_distance_l2(v1, v2)</a></h3>\n<p><strong>vector_distance_l2(v1, v2) -&gt; real</strong></p>\n<p>Compute the Euclidean (L2) distance between two vectors. This is the straight-line distance between two points in the vector space. The result is always non-negative, with 0 meaning the vectors are identical. L2 distance is sensitive to vector magnitude, so it works best when vectors are on a similar scale.</p>\n<pre><code class=\"language-sql\">SELECT vector_distance_l2(\n    vector32('[0.0, 0.0]'),\n    vector32('[3.0, 4.0]')\n);  -- 5.0\n\nSELECT vector_distance_l2(\n    vector32('[1.0, 2.0, 3.0]'),\n    vector32('[4.0, 5.0, 6.0]')\n);  -- 5.19615242270663\n</code></pre>\n<h3 id=\"vector_distance_dotv1-v2\"><a class=\"header\" href=\"#vector_distance_dotv1-v2\">vector_distance_dot(v1, v2)</a></h3>\n<p><strong>vector_distance_dot(v1, v2) -&gt; real</strong></p>\n<p>Compute the negative dot product between two vectors. A more negative result means the vectors are more similar (larger dot product). The result is negated so that sorting in ascending order returns the most similar vectors first.</p>\n<pre><code class=\"language-sql\">SELECT vector_distance_dot(\n    vector32('[1.0, 2.0]'),\n    vector32('[3.0, 4.0]')\n);  -- -11.0\n\nSELECT vector_distance_dot(\n    vector32('[1.0, 0.0]'),\n    vector32('[0.0, 1.0]')\n);  -- 0.0\n</code></pre>\n<h3 id=\"vector_distance_jaccardv1-v2\"><a class=\"header\" href=\"#vector_distance_jaccardv1-v2\">vector_distance_jaccard(v1, v2)</a></h3>\n<p><strong>vector_distance_jaccard(v1, v2) -&gt; real</strong></p>\n<p>Compute the Jaccard distance between two vectors treated as sets. The Jaccard distance is defined as <code>1 - (intersection / union)</code> where non-zero elements are treated as set members. This is useful when vector dimensions represent binary or categorical features, such as presence/absence of tags or keywords.</p>\n<pre><code class=\"language-sql\">SELECT vector_distance_jaccard(\n    vector32('[1.0, 0.0, 1.0]'),\n    vector32('[0.0, 1.0, 1.0]')\n);  -- 0.666666656732559\n</code></pre>\n<h2 id=\"utility-functions\"><a class=\"header\" href=\"#utility-functions\">Utility Functions</a></h2>\n<h3 id=\"vector_extractblob\"><a class=\"header\" href=\"#vector_extractblob\">vector_extract(blob)</a></h3>\n<p><strong>vector_extract(blob) -&gt; text</strong></p>\n<p>Convert a vector BLOB back into a human-readable JSON array. Useful for inspecting stored vectors.</p>\n<pre><code class=\"language-sql\">SELECT vector_extract(vector32('[1.0, 2.0, 3.0]'));  -- [1,2,3]\n</code></pre>\n<h3 id=\"vector_concatv1-v2\"><a class=\"header\" href=\"#vector_concatv1-v2\">vector_concat(v1, v2)</a></h3>\n<p><strong>vector_concat(v1, v2) -&gt; blob</strong></p>\n<p>Concatenate two vectors into a single vector. Both vectors must be the same type.</p>\n<pre><code class=\"language-sql\">SELECT vector_extract(\n    vector_concat(\n        vector32('[1.0, 2.0]'),\n        vector32('[3.0, 4.0]')\n    )\n);  -- [1,2,3,4]\n</code></pre>\n<h3 id=\"vector_slicev-start-end\"><a class=\"header\" href=\"#vector_slicev-start-end\">vector_slice(v, start, end)</a></h3>\n<p><strong>vector_slice(v, start, end) -&gt; blob</strong></p>\n<p>Extract a contiguous sub-vector from dimension index <code>start</code> (inclusive) to <code>end</code> (exclusive). Indices are zero-based.</p>\n<pre><code class=\"language-sql\">SELECT vector_extract(\n    vector_slice(vector32('[10.0, 20.0, 30.0, 40.0, 50.0]'), 0, 3)\n);  -- [10,20,30]\n\nSELECT vector_extract(\n    vector_slice(vector32('[10.0, 20.0, 30.0, 40.0, 50.0]'), 2, 5)\n);  -- [30,40,50]\n</code></pre>\n<h2 id=\"examples-23\"><a class=\"header\" href=\"#examples-23\">Examples</a></h2>\n<h3 id=\"creating-a-table-and-inserting-vectors\"><a class=\"header\" href=\"#creating-a-table-and-inserting-vectors\">Creating a Table and Inserting Vectors</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE documents (\n    id INTEGER PRIMARY KEY,\n    content TEXT,\n    embedding BLOB\n);\n\nINSERT INTO documents VALUES\n    (1, 'Introduction to databases', vector32('[0.1, 0.2, 0.3, 0.4]'));\nINSERT INTO documents VALUES\n    (2, 'SQL query optimization', vector32('[0.2, 0.1, 0.4, 0.3]'));\nINSERT INTO documents VALUES\n    (3, 'Vector similarity search', vector32('[0.4, 0.3, 0.2, 0.1]'));\n</code></pre>\n<h3 id=\"finding-similar-documents-with-cosine-distance\"><a class=\"header\" href=\"#finding-similar-documents-with-cosine-distance\">Finding Similar Documents with Cosine Distance</a></h3>\n<pre><code class=\"language-sql\">SELECT\n    id,\n    content,\n    vector_distance_cos(embedding, vector32('[0.15, 0.25, 0.35, 0.45]')) AS distance\nFROM documents\nORDER BY distance\nLIMIT 3;\n-- 1|Introduction to databases|0.00203462258422431\n-- 2|SQL query optimization|0.0590611621657705\n-- 3|Vector similarity search|0.287167575420696\n</code></pre>\n<h3 id=\"finding-similar-documents-with-l2-distance\"><a class=\"header\" href=\"#finding-similar-documents-with-l2-distance\">Finding Similar Documents with L2 Distance</a></h3>\n<pre><code class=\"language-sql\">SELECT\n    id,\n    content,\n    vector_distance_l2(embedding, vector32('[0.15, 0.25, 0.35, 0.45]')) AS distance\nFROM documents\nORDER BY distance\nLIMIT 3;\n-- 1|Introduction to databases|0.0999999802559595\n-- 2|SQL query optimization|0.223606791085977\n-- 3|Vector similarity search|0.458257562341844\n</code></pre>\n<h3 id=\"threshold-based-search\"><a class=\"header\" href=\"#threshold-based-search\">Threshold-Based Search</a></h3>\n<p>Find all vectors within a certain distance rather than a fixed number of results:</p>\n<pre><code class=\"language-sql\">SELECT\n    id,\n    content,\n    vector_distance_cos(embedding, vector32('[0.1, 0.2, 0.3, 0.4]')) AS distance\nFROM documents\nWHERE vector_distance_cos(embedding, vector32('[0.1, 0.2, 0.3, 0.4]')) &lt; 0.01\nORDER BY distance;\n</code></pre>\n<h3 id=\"inspecting-stored-vectors\"><a class=\"header\" href=\"#inspecting-stored-vectors\">Inspecting Stored Vectors</a></h3>\n<pre><code class=\"language-sql\">SELECT id, vector_extract(embedding) FROM documents;\n-- 1|[0.1,0.2,0.3,0.4]\n-- 2|[0.2,0.1,0.4,0.3]\n-- 3|[0.4,0.3,0.2,0.1]\n</code></pre>\n<h3 id=\"combining-vector-search-with-relational-filters\"><a class=\"header\" href=\"#combining-vector-search-with-relational-filters\">Combining Vector Search with Relational Filters</a></h3>\n<p>Vector search can be combined with standard SQL WHERE clauses. Non-vector predicates are applied before or alongside the distance calculation, reducing the number of vectors that need to be compared:</p>\n<pre><code class=\"language-sql\">-- Assuming a 'category' column exists alongside the embedding\nCREATE TABLE products (\n    id INTEGER PRIMARY KEY,\n    name TEXT,\n    category TEXT,\n    embedding BLOB\n);\n\nINSERT INTO products VALUES (1, 'Running Shoes', 'footwear', vector32('[0.8, 0.1, 0.3]'));\nINSERT INTO products VALUES (2, 'Hiking Boots', 'footwear', vector32('[0.7, 0.2, 0.5]'));\nINSERT INTO products VALUES (3, 'Cotton T-Shirt', 'clothing', vector32('[0.1, 0.9, 0.2]'));\nINSERT INTO products VALUES (4, 'Trail Sneakers', 'footwear', vector32('[0.75, 0.15, 0.4]'));\n\n-- Search only within the 'footwear' category\nSELECT\n    name,\n    vector_distance_cos(embedding, vector32('[0.8, 0.1, 0.35]')) AS distance\nFROM products\nWHERE category = 'footwear'\nORDER BY distance\nLIMIT 2;\n</code></pre>\n<h3 id=\"semantic-search-application\"><a class=\"header\" href=\"#semantic-search-application\">Semantic Search Application</a></h3>\n<p>A complete example modeling an article recommendation system:</p>\n<pre><code class=\"language-sql\">-- Create schema\nCREATE TABLE articles (\n    id INTEGER PRIMARY KEY,\n    title TEXT,\n    embedding BLOB\n);\n\n-- Insert pre-computed embeddings from an external model\nINSERT INTO articles VALUES\n    (1, 'Database Fundamentals', vector32('[0.12, -0.34, 0.56, 0.78]'));\nINSERT INTO articles VALUES\n    (2, 'Machine Learning Basics', vector32('[0.23, 0.45, -0.67, 0.89]'));\nINSERT INTO articles VALUES\n    (3, 'Web Development Guide', vector32('[0.34, -0.12, 0.78, -0.56]'));\nINSERT INTO articles VALUES\n    (4, 'Data Structures', vector32('[0.11, -0.33, 0.55, 0.77]'));\n\n-- Search: given a query embedding, find the 3 most similar articles\nSELECT\n    a.id,\n    a.title,\n    vector_distance_cos(a.embedding, vector32('[0.10, -0.30, 0.50, 0.70]')) AS distance\nFROM articles a\nORDER BY distance\nLIMIT 3;\n-- 4|Data Structures|0.0\n-- 1|Database Fundamentals|4.52537882702912e-05\n-- 2|Machine Learning Basics|0.843018284332676\n</code></pre>\n<h2 id=\"choosing-a-distance-function\"><a class=\"header\" href=\"#choosing-a-distance-function\">Choosing a Distance Function</a></h2>\n<p>Different distance functions suit different use cases:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Function</th><th>Best for</th><th>Range</th><th>Notes</th></tr>\n</thead>\n<tbody>\n<tr><td><code>vector_distance_cos</code></td><td>Text/document embeddings</td><td>0 to 2</td><td>Ignores vector magnitude; focuses on direction. Most common for NLP embeddings.</td></tr>\n<tr><td><code>vector_distance_l2</code></td><td>Spatial data, image features</td><td>0 to infinity</td><td>Sensitive to magnitude. Good when absolute position matters.</td></tr>\n<tr><td><code>vector_distance_dot</code></td><td>Normalized embeddings, ranking</td><td>negative infinity to positive infinity</td><td>Returns negated dot product so ascending sort gives best matches.</td></tr>\n<tr><td><code>vector_distance_jaccard</code></td><td>Binary/categorical features</td><td>0 to 1</td><td>Treats vectors as sets. Best for presence/absence features.</td></tr>\n</tbody>\n</table>\n</div>\n<p>When in doubt, start with <code>vector_distance_cos</code>. It is the most widely used metric for text embeddings produced by models like OpenAI, Cohere, and Sentence Transformers.</p>\n<h2 id=\"performance-considerations\"><a class=\"header\" href=\"#performance-considerations\">Performance Considerations</a></h2>\n<p>Since vector indexes are not yet implemented, keep the following in mind:</p>\n<ul>\n<li><strong>Linear scan</strong>: every search examines all rows in the table. For large datasets, queries will be slower.</li>\n<li><strong>Use <code>vector32</code> over <code>vector64</code></strong> unless double precision is required. It uses half the storage (4 bytes vs. 8 bytes per dimension).</li>\n<li><strong>Pre-filter with WHERE</strong>: apply non-vector predicates first to reduce the number of distance calculations. For example, filtering by a category column before computing distances avoids unnecessary work.</li>\n<li><strong>Use <code>vector32_sparse</code></strong> when vectors have many zero-valued dimensions to reduce storage.</li>\n<li><strong>Dimension count matters</strong>: storage per row is <code>dimensions * bytes_per_dimension</code>. A 1536-dimensional <code>vector32</code> column uses about 6 KB per row, while a 384-dimensional column uses about 1.5 KB per row. Choose the smallest embedding model that meets your accuracy requirements.</li>\n</ul>\n<h2 id=\"compatibility-20\"><a class=\"header\" href=\"#compatibility-20\">Compatibility</a></h2>\n<p>Vector search is a Turso extension and is not available in standard SQLite. The <code>vector()</code>, <code>vector32()</code>, <code>vector64()</code>, <code>vector32_sparse()</code>, <code>vector_extract()</code>, <code>vector_distance_cos()</code>, <code>vector_distance_l2()</code>, <code>vector_distance_dot()</code>, <code>vector_distance_jaccard()</code>, <code>vector_concat()</code>, and <code>vector_slice()</code> functions are all Turso-specific.</p>\n<div style=\"break-before: page; page-break-before: always;\"></div>\n<h1 id=\"change-data-capture\"><a class=\"header\" href=\"#change-data-capture\">Change Data Capture</a></h1>\n<blockquote>\n<p><strong>Turso extension.</strong> Change Data Capture (CDC) is a Turso-specific feature and is not available in standard SQLite.</p>\n</blockquote>\n<h2 id=\"overview-2\"><a class=\"header\" href=\"#overview-2\">Overview</a></h2>\n<p>Change Data Capture (CDC) records every data modification (insert, update, delete) into a dedicated tracking table. This is useful for building reactive applications, replicating data between systems, maintaining audit logs, and implementing event-driven architectures.</p>\n<p>CDC is enabled per connection using a PRAGMA. Once enabled, every INSERT, UPDATE, and DELETE on user tables automatically generates a corresponding row in the CDC table. The level of detail captured depends on the chosen mode.</p>\n<p><strong>Note:</strong> This feature is currently marked as unstable, meaning the PRAGMA name may change in future versions. The functionality itself is reliable for use.</p>\n<h2 id=\"syntax-27\"><a class=\"header\" href=\"#syntax-27\">Syntax</a></h2>\n<pre><code class=\"language-sql\">PRAGMA unstable_capture_data_changes_conn('mode[,table_name]');\n</code></pre>\n<h3 id=\"parameters\"><a class=\"header\" href=\"#parameters\">Parameters</a></h3>\n<ul>\n<li><strong>mode</strong> – the capture mode (see Capture Modes below).</li>\n<li><strong>table_name</strong> – an optional custom name for the CDC table, separated from the mode by a comma. Defaults to <code>turso_cdc</code> when omitted.</li>\n</ul>\n<h2 id=\"capture-modes\"><a class=\"header\" href=\"#capture-modes\">Capture Modes</a></h2>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Mode</th><th>Description</th></tr>\n</thead>\n<tbody>\n<tr><td><code>off</code></td><td>Disable CDC for this connection. No further changes are recorded.</td></tr>\n<tr><td><code>id</code></td><td>Record only the primary key (or rowid) of every changed row. The <code>before</code>, <code>after</code>, and <code>updates</code> columns are NULL.</td></tr>\n<tr><td><code>before</code></td><td>Record the full row state <strong>before</strong> each change. Populated for updates and deletes. Inserts still record only the id.</td></tr>\n<tr><td><code>after</code></td><td>Record the full row state <strong>after</strong> each change. Populated for inserts and updates. Deletes still record only the id.</td></tr>\n<tr><td><code>full</code></td><td>Record <strong>before</strong> state, <strong>after</strong> state, and an <strong>updates</strong> blob describing which columns changed. The most detailed mode.</td></tr>\n</tbody>\n</table>\n</div>\n<h2 id=\"cdc-table-structure\"><a class=\"header\" href=\"#cdc-table-structure\">CDC Table Structure</a></h2>\n<p>When CDC is first enabled and a DML statement is executed, Turso automatically creates the tracking table (default name <code>turso_cdc</code>) if it does not already exist. The table has the following schema:</p>\n<pre><code>(change_id INTEGER PRIMARY KEY AUTOINCREMENT,\n change_time INTEGER,\n change_type INTEGER,\n table_name TEXT,\n id,\n before BLOB,\n after BLOB,\n updates BLOB)\n</code></pre>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Column</th><th>Type</th><th>Description</th></tr>\n</thead>\n<tbody>\n<tr><td><code>change_id</code></td><td>INTEGER</td><td>Auto-incrementing unique identifier for each change entry.</td></tr>\n<tr><td><code>change_time</code></td><td>INTEGER</td><td>Unix epoch timestamp when the change was recorded.</td></tr>\n<tr><td><code>change_type</code></td><td>INTEGER</td><td><code>1</code> for INSERT, <code>0</code> for UPDATE, <code>-1</code> for DELETE.</td></tr>\n<tr><td><code>table_name</code></td><td>TEXT</td><td>Name of the table that was modified. Schema changes appear as changes to <code>sqlite_schema</code>.</td></tr>\n<tr><td><code>id</code></td><td>(any)</td><td>The primary key or rowid of the affected row.</td></tr>\n<tr><td><code>before</code></td><td>BLOB</td><td>A binary record containing the row state before the change. Populated only in <code>before</code> and <code>full</code> modes, and only for updates and deletes. NULL otherwise.</td></tr>\n<tr><td><code>after</code></td><td>BLOB</td><td>A binary record containing the row state after the change. Populated only in <code>after</code> and <code>full</code> modes, and only for inserts and updates. NULL otherwise.</td></tr>\n<tr><td><code>updates</code></td><td>BLOB</td><td>A binary record describing per-column update details. Populated only in <code>full</code> mode for updates. NULL otherwise.</td></tr>\n</tbody>\n</table>\n</div>\n<h3 id=\"change-type-values\"><a class=\"header\" href=\"#change-type-values\">Change Type Values</a></h3>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Value</th><th>Meaning</th></tr>\n</thead>\n<tbody>\n<tr><td><code>1</code></td><td>INSERT</td></tr>\n<tr><td><code>0</code></td><td>UPDATE</td></tr>\n<tr><td><code>-1</code></td><td>DELETE</td></tr>\n</tbody>\n</table>\n</div>\n<h2 id=\"enabling-cdc\"><a class=\"header\" href=\"#enabling-cdc\">Enabling CDC</a></h2>\n<p>Enable CDC with the desired mode:</p>\n<pre><code class=\"language-sql\">-- Capture only primary keys of changed rows\nPRAGMA unstable_capture_data_changes_conn('id');\n</code></pre>\n<pre><code class=\"language-sql\">-- Capture full before and after state\nPRAGMA unstable_capture_data_changes_conn('full');\n</code></pre>\n<pre><code class=\"language-sql\">-- Store changes in a custom table instead of the default turso_cdc\nPRAGMA unstable_capture_data_changes_conn('full,audit_log');\n</code></pre>\n<h2 id=\"disabling-cdc\"><a class=\"header\" href=\"#disabling-cdc\">Disabling CDC</a></h2>\n<p>Turn off CDC for the current connection. Changes made after this point are not recorded.</p>\n<pre><code class=\"language-sql\">PRAGMA unstable_capture_data_changes_conn('off');\n</code></pre>\n<h2 id=\"querying-changes\"><a class=\"header\" href=\"#querying-changes\">Querying Changes</a></h2>\n<p>The CDC table is a regular table that can be queried with standard SQL.</p>\n<pre><code class=\"language-sql\">-- View all captured changes\nSELECT * FROM turso_cdc;\n\n-- View only inserts\nSELECT * FROM turso_cdc WHERE change_type = 1;\n\n-- View only updates\nSELECT * FROM turso_cdc WHERE change_type = 0;\n\n-- View only deletes\nSELECT * FROM turso_cdc WHERE change_type = -1;\n\n-- View changes for a specific table\nSELECT * FROM turso_cdc WHERE table_name = 'users';\n\n-- View recent changes (last hour)\nSELECT * FROM turso_cdc\nWHERE change_time &gt; unixepoch() - 3600;\n</code></pre>\n<p>You can also delete old entries to keep the table from growing indefinitely. Modifications to the CDC table itself are not captured, so deleting rows from <code>turso_cdc</code> does not generate additional CDC entries.</p>\n<pre><code class=\"language-sql\">-- Purge entries older than 24 hours\nDELETE FROM turso_cdc\nWHERE change_time &lt; unixepoch() - 86400;\n</code></pre>\n<h2 id=\"helper-functions\"><a class=\"header\" href=\"#helper-functions\">Helper Functions</a></h2>\n<p>Turso provides two scalar functions to decode the binary records stored in the <code>before</code>, <code>after</code>, and <code>updates</code> columns.</p>\n<h3 id=\"table_columns_json_arraytable_name\"><a class=\"header\" href=\"#table_columns_json_arraytable_name\">table_columns_json_array(table_name)</a></h3>\n<p><strong>table_columns_json_array(table_name) -&gt; text</strong></p>\n<p>Return a JSON array of column names for the given table. This can be used as the first argument to <code>bin_record_json_object</code> to decode a CDC binary record.</p>\n<pre><code class=\"language-sql\">CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, price REAL);\nSELECT table_columns_json_array('products');\n-- [\"id\",\"name\",\"price\"]\n</code></pre>\n<h3 id=\"bin_record_json_objectcolumns_json-blob\"><a class=\"header\" href=\"#bin_record_json_objectcolumns_json-blob\">bin_record_json_object(columns_json, blob)</a></h3>\n<p><strong>bin_record_json_object(columns_json, blob) -&gt; text</strong></p>\n<p>Decode a binary record blob into a JSON object, using the column names from the first argument to label each field. The first argument should be a JSON array of column name strings (typically from <code>table_columns_json_array</code>).</p>\n<pre><code class=\"language-sql\">-- Decode the \"after\" column of a CDC row\nSELECT bin_record_json_object(\n    table_columns_json_array('products'),\n    \"after\"\n) FROM turso_cdc\nWHERE table_name = 'products' AND change_type = 1;\n</code></pre>\n<h2 id=\"examples-24\"><a class=\"header\" href=\"#examples-24\">Examples</a></h2>\n<h3 id=\"basic-change-tracking\"><a class=\"header\" href=\"#basic-change-tracking\">Basic Change Tracking</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE users (\n    id INTEGER PRIMARY KEY,\n    name TEXT,\n    email TEXT\n);\n\n-- Enable CDC in id mode\nPRAGMA unstable_capture_data_changes_conn('id');\n\n-- Make some changes\nINSERT INTO users VALUES (1, 'Alice', 'alice@example.com');\nINSERT INTO users VALUES (2, 'Bob', 'bob@example.com');\nUPDATE users SET email = 'alice@newdomain.com' WHERE id = 1;\nDELETE FROM users WHERE id = 2;\n\n-- View the captured changes\nSELECT change_id, change_type, table_name, id\nFROM turso_cdc;\n-- 1|1|users|1\n-- 2|1|users|2\n-- 3|0|users|1\n-- 4|-1|users|2\n</code></pre>\n<h3 id=\"full-mode-with-beforeafter-state\"><a class=\"header\" href=\"#full-mode-with-beforeafter-state\">Full Mode with Before/After State</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE inventory (\n    id INTEGER PRIMARY KEY,\n    qty INTEGER\n);\n\nPRAGMA unstable_capture_data_changes_conn('full');\n\nINSERT INTO inventory VALUES (1, 100);\nUPDATE inventory SET qty = 80 WHERE id = 1;\n\n-- The INSERT has an \"after\" record but no \"before\"\n-- The UPDATE has both \"before\" and \"after\" records, plus \"updates\"\nSELECT change_id, change_type, \"before\" IS NOT NULL AS has_before,\n       \"after\" IS NOT NULL AS has_after, updates IS NOT NULL AS has_updates\nFROM turso_cdc;\n-- 1|1|0|1|0\n-- 2|0|1|1|1\n</code></pre>\n<h3 id=\"custom-cdc-table-name\"><a class=\"header\" href=\"#custom-cdc-table-name\">Custom CDC Table Name</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE orders (id INTEGER PRIMARY KEY, total REAL);\n\n-- Store changes in a table named \"order_audit\"\nPRAGMA unstable_capture_data_changes_conn('id,order_audit');\n\nINSERT INTO orders VALUES (1, 99.95);\n\nSELECT change_id, change_type, table_name, id FROM order_audit;\n-- 1|1|orders|1\n</code></pre>\n<h3 id=\"per-connection-isolation\"><a class=\"header\" href=\"#per-connection-isolation\">Per-Connection Isolation</a></h3>\n<p>CDC is configured per connection. Each connection can use a different mode and a different CDC table. Changes made by a connection that does not have CDC enabled are not recorded.</p>\n<pre><code class=\"language-sql\">-- Connection 1 captures to \"audit_log\"\nPRAGMA unstable_capture_data_changes_conn('full,audit_log');\n\n-- Connection 2 captures to \"sync_queue\"\nPRAGMA unstable_capture_data_changes_conn('id,sync_queue');\n\n-- Changes from Connection 1 go to \"audit_log\"\n-- Changes from Connection 2 go to \"sync_queue\"\n</code></pre>\n<p>Only changes executed by the connection that enabled CDC are recorded. If another connection modifies the same table without CDC enabled, those changes do not appear in any CDC table.</p>\n<h3 id=\"schema-changes\"><a class=\"header\" href=\"#schema-changes\">Schema Changes</a></h3>\n<p>In <code>full</code> mode, DDL statements (CREATE TABLE, DROP TABLE, ALTER TABLE, CREATE INDEX, DROP INDEX) are also tracked as changes to <code>sqlite_schema</code>.</p>\n<pre><code class=\"language-sql\">PRAGMA unstable_capture_data_changes_conn('full');\n\nCREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT);\n-- Recorded as an INSERT into sqlite_schema\n\nDROP TABLE products;\n-- Recorded as a DELETE from sqlite_schema\n</code></pre>\n<h3 id=\"transactions-1\"><a class=\"header\" href=\"#transactions-1\">Transactions</a></h3>\n<p>CDC respects transaction boundaries. Changes within a transaction are recorded when the transaction commits. If a transaction is rolled back, no CDC entries are created for those changes.</p>\n<pre><code class=\"language-sql\">CREATE TABLE accounts (id INTEGER PRIMARY KEY, balance REAL);\nPRAGMA unstable_capture_data_changes_conn('id');\n\nBEGIN;\nINSERT INTO accounts VALUES (1, 1000.00);\nINSERT INTO accounts VALUES (2, 2000.00);\nCOMMIT;\n\n-- Both inserts are recorded after COMMIT\nSELECT change_id, change_type, id FROM turso_cdc;\n-- 1|1|1\n-- 2|1|2\n</code></pre>\n<h3 id=\"failed-statements\"><a class=\"header\" href=\"#failed-statements\">Failed Statements</a></h3>\n<p>When a statement fails (for example, due to a constraint violation), neither the data change nor the CDC entry is recorded. Only successful operations appear in the CDC table.</p>\n<pre><code class=\"language-sql\">CREATE TABLE tags (id INTEGER PRIMARY KEY, label TEXT UNIQUE);\nPRAGMA unstable_capture_data_changes_conn('id');\n\nINSERT INTO tags (label) VALUES ('urgent'), ('review');\n-- This fails because 'urgent' already exists:\n-- INSERT INTO tags (label) VALUES ('new'), ('other'), ('urgent');\n\nINSERT INTO tags (label) VALUES ('done');\n\n-- Only the successful inserts are recorded\nSELECT change_id, change_type, table_name FROM turso_cdc;\n-- 1|1|tags\n-- 2|1|tags\n-- 3|1|tags\n</code></pre>\n<h2 id=\"compatibility-21\"><a class=\"header\" href=\"#compatibility-21\">Compatibility</a></h2>\n<p>Change Data Capture is a Turso extension. It is not available in standard SQLite. The PRAGMA <code>unstable_capture_data_changes_conn</code>, the default <code>turso_cdc</code> table, and the helper functions <code>table_columns_json_array()</code> and <code>bin_record_json_object()</code> are all Turso-specific.</p>\n<div style=\"break-before: page; page-break-before: always;\"></div>\n<h1 id=\"materialized-views\"><a class=\"header\" href=\"#materialized-views\">Materialized Views</a></h1>\n<blockquote>\n<p><strong>Turso Extension.</strong> This feature is not available in SQLite. Materialized views must be explicitly enabled with the <code>--experimental-views</code> flag.</p>\n</blockquote>\n<h2 id=\"syntax-28\"><a class=\"header\" href=\"#syntax-28\">Syntax</a></h2>\n<pre><code class=\"language-sql\">CREATE MATERIALIZED VIEW [IF NOT EXISTS] view-name [(column-name [, ...])]\n  AS select-statement\n</code></pre>\n<pre><code class=\"language-sql\">DROP VIEW [IF EXISTS] view-name\n</code></pre>\n<h2 id=\"description-24\"><a class=\"header\" href=\"#description-24\">Description</a></h2>\n<p>Materialized views in Turso are automatically updating database objects that store the results of a query and keep them current in real-time. Unlike traditional materialized views found in other databases that require manual refresh commands, Turso uses Incremental View Maintenance (IVM) to update materialized views as the underlying data changes.</p>\n<p>When you insert, update, or delete rows in a base table, any dependent materialized views are updated within the same transaction. Only the incremental changes are processed – not the entire query – making updates efficient even for complex aggregations over large datasets. Because the view is updated inside the same transaction as the base table modification, materialized views are always consistent and never show stale data.</p>\n<h2 id=\"enabling-materialized-views\"><a class=\"header\" href=\"#enabling-materialized-views\">Enabling Materialized Views</a></h2>\n<p>Materialized views are an experimental feature. You must pass the <code>--experimental-views</code> flag when starting the Turso CLI:</p>\n<pre><code class=\"language-bash\">tursodb --experimental-views database.db\n</code></pre>\n<p>Without this flag, <code>CREATE MATERIALIZED VIEW</code> statements will fail with an error.</p>\n<h2 id=\"how-incremental-view-maintenance-works\"><a class=\"header\" href=\"#how-incremental-view-maintenance-works\">How Incremental View Maintenance Works</a></h2>\n<p>Traditional materialized views store a snapshot of query results that becomes stale as underlying data changes. Re-executing the entire query to refresh the view is costly for large datasets.</p>\n<p>Turso takes a different approach. Instead of re-computing the entire view, IVM tracks what has changed and updates only the affected portions:</p>\n<ul>\n<li><strong>INSERT</strong> – Adds the new row’s contribution to the view.</li>\n<li><strong>DELETE</strong> – Removes the deleted row’s contribution from the view.</li>\n<li><strong>UPDATE</strong> – Treated as a delete of the old value followed by an insert of the new value.</li>\n</ul>\n<p>This is particularly powerful for aggregations. If a view computes <code>SUM</code> over millions of rows, inserting one new row only requires adding that single value to the existing sum – not re-summing all rows.</p>\n<h2 id=\"creating-materialized-views\"><a class=\"header\" href=\"#creating-materialized-views\">Creating Materialized Views</a></h2>\n<p>A materialized view is created with <code>CREATE MATERIALIZED VIEW</code>. The <code>AS</code> clause contains a <code>SELECT</code> statement that defines the view’s contents. When the view is created, the query is executed once to populate the initial data, and then incremental maintenance keeps it up to date.</p>\n<pre><code class=\"language-sql\">CREATE TABLE sales (product_id INTEGER, quantity INTEGER, day INTEGER);\nINSERT INTO sales VALUES\n  (1, 2, 1), (2, 5, 1), (1, 1, 2),\n  (3, 1, 2), (2, 3, 3), (1, 1, 3);\n\nCREATE MATERIALIZED VIEW daily_totals AS\n  SELECT day, SUM(quantity) as total, COUNT(*) as transactions\n  FROM sales\n  GROUP BY day;\n\nSELECT * FROM daily_totals ORDER BY day;\n-- 1|7.0|2\n-- 2|2.0|2\n-- 3|4.0|2\n</code></pre>\n<p>Once created, a materialized view is queried like any regular table.</p>\n<h3 id=\"supported-query-features\"><a class=\"header\" href=\"#supported-query-features\">Supported Query Features</a></h3>\n<p>Materialized views support a wide range of SQL constructs in their defining query:</p>\n<ul>\n<li><strong>WHERE</strong> filters</li>\n<li><strong>GROUP BY</strong> with positional references, aliases, or expressions</li>\n<li><strong>Aggregate functions</strong>: <code>SUM</code>, <code>COUNT</code>, <code>AVG</code>, <code>MIN</code>, <code>MAX</code> (including <code>DISTINCT</code> variants like <code>COUNT(DISTINCT ...)</code>, <code>SUM(DISTINCT ...)</code>)</li>\n<li><strong>JOINs</strong> (two-way and three-way)</li>\n<li><strong>UNION</strong> and <strong>UNION ALL</strong></li>\n<li><strong>DISTINCT</strong></li>\n<li><strong>Scalar expressions and functions</strong> in the select list (e.g., <code>b + a</code>, <code>min(a, b)</code>)</li>\n<li><strong>BETWEEN</strong>, <strong>IN</strong>, and <strong>CAST</strong> in <code>WHERE</code> clauses</li>\n</ul>\n<h2 id=\"automatic-incremental-updates\"><a class=\"header\" href=\"#automatic-incremental-updates\">Automatic Incremental Updates</a></h2>\n<p>Materialized views stay current automatically. Every INSERT, UPDATE, and DELETE on a base table incrementally updates all dependent materialized views.</p>\n<h3 id=\"insert-maintenance\"><a class=\"header\" href=\"#insert-maintenance\">Insert Maintenance</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE orders (\n  order_id INTEGER PRIMARY KEY,\n  customer_id INTEGER,\n  amount INTEGER\n);\nINSERT INTO orders VALUES (1, 100, 50), (2, 200, 75);\n\nCREATE MATERIALIZED VIEW customer_totals AS\n  SELECT customer_id, SUM(amount) as total, COUNT(*) as order_count\n  FROM orders\n  GROUP BY customer_id;\n\nSELECT * FROM customer_totals ORDER BY customer_id;\n-- 100|50.0|1\n-- 200|75.0|1\n\n-- Insert a new order for customer 100\nINSERT INTO orders VALUES (3, 100, 25);\nSELECT * FROM customer_totals ORDER BY customer_id;\n-- 100|75.0|2\n-- 200|75.0|1\n</code></pre>\n<h3 id=\"update-maintenance\"><a class=\"header\" href=\"#update-maintenance\">Update Maintenance</a></h3>\n<pre><code class=\"language-sql\">-- Continuing from above: update the amount of order 2\nUPDATE orders SET amount = 100 WHERE order_id = 2;\nSELECT * FROM customer_totals ORDER BY customer_id;\n-- 100|75.0|2\n-- 200|100.0|1\n</code></pre>\n<h3 id=\"delete-maintenance\"><a class=\"header\" href=\"#delete-maintenance\">Delete Maintenance</a></h3>\n<pre><code class=\"language-sql\">-- Continuing from above: delete an order\nDELETE FROM orders WHERE order_id = 1;\nSELECT * FROM customer_totals ORDER BY customer_id;\n-- 100|25.0|1\n-- 200|100.0|1\n</code></pre>\n<h2 id=\"transactional-consistency\"><a class=\"header\" href=\"#transactional-consistency\">Transactional Consistency</a></h2>\n<p>Materialized views are updated inside the same transaction as the base table modification. This guarantees:</p>\n<ul>\n<li><strong>Atomicity</strong> – View changes are committed or rolled back together with base table changes.</li>\n<li><strong>Consistency</strong> – Views never show partial or inconsistent state.</li>\n<li><strong>Rollback safety</strong> – If a transaction rolls back, all view changes are rolled back too.</li>\n</ul>\n<pre><code class=\"language-sql\">CREATE TABLE sales (product_id INTEGER, amount INTEGER);\nINSERT INTO sales VALUES (1, 100), (1, 200), (2, 150), (2, 250);\n\nCREATE MATERIALIZED VIEW product_totals AS\n  SELECT product_id, SUM(amount) as total, COUNT(*) as cnt\n  FROM sales\n  GROUP BY product_id;\n\nSELECT * FROM product_totals ORDER BY product_id;\n-- 1|300.0|2\n-- 2|400.0|2\n\nBEGIN;\nINSERT INTO sales VALUES (1, 50), (3, 300);\nSELECT * FROM product_totals ORDER BY product_id;\n-- 1|350.0|3\n-- 2|400.0|2\n-- 3|300.0|1\n\nROLLBACK;\n\n-- After rollback, the view returns to its previous state\nSELECT * FROM product_totals ORDER BY product_id;\n-- 1|300.0|2\n-- 2|400.0|2\n</code></pre>\n<h2 id=\"materialized-views-with-joins\"><a class=\"header\" href=\"#materialized-views-with-joins\">Materialized Views with JOINs</a></h2>\n<p>Materialized views can be defined over joins between two or more tables. Incremental maintenance applies to changes on any of the joined tables.</p>\n<pre><code class=\"language-sql\">CREATE TABLE customers (id INTEGER PRIMARY KEY, name TEXT, city TEXT);\nCREATE TABLE orders (id INTEGER PRIMARY KEY, customer_id INTEGER, product_id INTEGER, quantity INTEGER);\nCREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, price INTEGER);\n\nINSERT INTO customers VALUES (1, 'Alice', 'NYC'), (2, 'Bob', 'LA');\nINSERT INTO products VALUES (1, 'Widget', 10), (2, 'Gadget', 20);\nINSERT INTO orders VALUES (1, 1, 1, 5), (2, 1, 2, 3), (3, 2, 1, 2);\n\nCREATE MATERIALIZED VIEW sales_summary AS\n  SELECT c.name AS customer_name, p.name AS product_name, o.quantity\n  FROM customers c\n  JOIN orders o ON c.id = o.customer_id\n  JOIN products p ON o.product_id = p.id;\n\nSELECT * FROM sales_summary ORDER BY customer_name, product_name;\n-- Alice|Gadget|3\n-- Alice|Widget|5\n-- Bob|Widget|2\n</code></pre>\n<p>Inserting into any of the three tables will incrementally update the view.</p>\n<h2 id=\"materialized-views-with-distinct\"><a class=\"header\" href=\"#materialized-views-with-distinct\">Materialized Views with DISTINCT</a></h2>\n<p>The <code>DISTINCT</code> keyword is supported both at the query level and inside aggregate functions.</p>\n<h3 id=\"query-level-distinct\"><a class=\"header\" href=\"#query-level-distinct\">Query-Level DISTINCT</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE events (id INTEGER PRIMARY KEY, category TEXT, status TEXT);\nINSERT INTO events VALUES (1, 'A', 'open'), (2, 'B', 'open'),\n  (3, 'A', 'open'), (4, 'B', 'closed');\n\nCREATE MATERIALIZED VIEW unique_categories AS\n  SELECT DISTINCT category FROM events;\n\nSELECT * FROM unique_categories ORDER BY category;\n-- A\n-- B\n</code></pre>\n<h3 id=\"distinct-aggregates\"><a class=\"header\" href=\"#distinct-aggregates\">DISTINCT Aggregates</a></h3>\n<pre><code class=\"language-sql\">CREATE TABLE orders (id INTEGER PRIMARY KEY, customer TEXT, product TEXT, amount INTEGER);\nINSERT INTO orders VALUES (1, 'Alice', 'Widget', 10), (2, 'Alice', 'Gadget', 20),\n  (3, 'Alice', 'Widget', 15), (4, 'Bob', 'Widget', 30), (5, 'Bob', 'Widget', 25);\n\nCREATE MATERIALIZED VIEW customer_stats AS\n  SELECT customer, COUNT(DISTINCT product) AS unique_products,\n    SUM(amount) AS total_amount\n  FROM orders\n  GROUP BY customer;\n\nSELECT * FROM customer_stats ORDER BY customer;\n-- Alice|2|45.0\n-- Bob|1|55.0\n</code></pre>\n<h2 id=\"materialized-views-with-union\"><a class=\"header\" href=\"#materialized-views-with-union\">Materialized Views with UNION</a></h2>\n<p>Materialized views can use <code>UNION</code> and <code>UNION ALL</code> to combine results from multiple queries:</p>\n<pre><code class=\"language-sql\">CREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT, department TEXT);\nCREATE TABLE contractors (id INTEGER PRIMARY KEY, name TEXT, agency TEXT);\nINSERT INTO employees VALUES (1, 'Alice', 'Engineering'), (2, 'Bob', 'Marketing');\nINSERT INTO contractors VALUES (1, 'Charlie', 'TechCorp'), (2, 'Diana', 'DesignCo');\n\nCREATE MATERIALIZED VIEW all_workers AS\n  SELECT name, department AS affiliation FROM employees\n  UNION ALL\n  SELECT name, agency AS affiliation FROM contractors;\n\nSELECT * FROM all_workers ORDER BY name;\n-- Alice|Engineering\n-- Bob|Marketing\n-- Charlie|TechCorp\n-- Diana|DesignCo\n</code></pre>\n<h2 id=\"dropping-materialized-views\"><a class=\"header\" href=\"#dropping-materialized-views\">Dropping Materialized Views</a></h2>\n<p>Materialized views are dropped using <code>DROP VIEW</code>, the same syntax used for regular views. This removes the view definition and all associated internal state tables.</p>\n<pre><code class=\"language-sql\">DROP VIEW sales_summary;\n</code></pre>\n<p>After dropping, the view can be recreated with the same or a different definition:</p>\n<pre><code class=\"language-sql\">CREATE MATERIALIZED VIEW sales_summary AS\n  SELECT product_id, SUM(amount) AS revenue\n  FROM sales\n  GROUP BY product_id;\n</code></pre>\n<h2 id=\"performance-considerations-1\"><a class=\"header\" href=\"#performance-considerations-1\">Performance Considerations</a></h2>\n<p>Materialized views trade write-time overhead for read-time performance. Each INSERT, UPDATE, or DELETE on a base table must also update any dependent materialized views. Consider these trade-offs when designing your schema:</p>\n<ul>\n<li>Use materialized views for data that is read frequently and written infrequently.</li>\n<li>Avoid creating many materialized views on tables with very high write rates.</li>\n<li>Aggregation views benefit the most: a query that would scan millions of rows becomes a simple table lookup.</li>\n<li>Join-based views avoid re-executing expensive joins on every read.</li>\n</ul>\n<h2 id=\"current-limitations\"><a class=\"header\" href=\"#current-limitations\">Current Limitations</a></h2>\n<p>As an experimental feature, materialized views have some limitations:</p>\n<ul>\n<li>Not all SQL functions are supported in view definitions.</li>\n<li>Views cannot reference other views.</li>\n<li>The <code>--experimental-views</code> flag must be provided at startup.</li>\n</ul>\n<h2 id=\"compatibility-22\"><a class=\"header\" href=\"#compatibility-22\">Compatibility</a></h2>\n<ul>\n<li>This feature is a Turso extension and is not available in SQLite.</li>\n<li>Materialized views are dropped with <code>DROP VIEW</code>, not a separate <code>DROP MATERIALIZED VIEW</code> statement.</li>\n<li>The <code>--experimental-views</code> flag is required. The feature is experimental and behavior may change in future releases.</li>\n</ul>\n<div style=\"break-before: page; page-break-before: always;\"></div>\n<h1 id=\"encryption-at-rest\"><a class=\"header\" href=\"#encryption-at-rest\">Encryption at Rest</a></h1>\n<blockquote>\n<p><strong>Turso Extension.</strong> This feature is not available in SQLite. Encryption must be explicitly enabled with the <code>--experimental-encryption</code> flag.</p>\n</blockquote>\n<h2 id=\"overview-3\"><a class=\"header\" href=\"#overview-3\">Overview</a></h2>\n<p>Turso supports transparent at-rest encryption to protect database files from unauthorized access. When enabled, all data written to disk is automatically encrypted and all data read from disk is automatically decrypted, with no changes required to SQL queries or application logic.</p>\n<p>Encryption operates at the page level: each database page is independently encrypted and authenticated. A random nonce is generated for every page write, and an authentication tag is stored alongside the ciphertext. If a page is corrupted or tampered with, decryption will fail with an error rather than returning garbage data.</p>\n<p>Encrypted databases use a modified file header. The first 16 bytes of a standard SQLite database contain the magic string <code>SQLite format 3\\0</code>. In an encrypted Turso database, these bytes are replaced with a Turso-specific header that identifies the file as encrypted and records the cipher algorithm. The rest of the database header (bytes 16 through 99) remains unencrypted but is protected by authenticated encryption, so any tampering with the header is detected on read.</p>\n<h2 id=\"enabling-encryption\"><a class=\"header\" href=\"#enabling-encryption\">Enabling Encryption</a></h2>\n<p>Encryption is an experimental feature. You must pass the <code>--experimental-encryption</code> flag when starting the Turso CLI:</p>\n<pre><code class=\"language-bash\">tursodb --experimental-encryption database.db\n</code></pre>\n<p>Without this flag, the <code>PRAGMA cipher</code> and <code>PRAGMA hexkey</code> statements are not available.</p>\n<h2 id=\"supported-ciphers\"><a class=\"header\" href=\"#supported-ciphers\">Supported Ciphers</a></h2>\n<p>Turso supports eight authenticated encryption algorithms across two families. All ciphers provide both confidentiality and integrity verification.</p>\n<h3 id=\"aes-gcm-family\"><a class=\"header\" href=\"#aes-gcm-family\">AES-GCM Family</a></h3>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Cipher Name</th><th>Key Size</th><th>Description</th></tr>\n</thead>\n<tbody>\n<tr><td><code>aes128gcm</code></td><td>16 bytes (128-bit)</td><td>AES-128 in Galois/Counter Mode</td></tr>\n<tr><td><code>aes256gcm</code></td><td>32 bytes (256-bit)</td><td>AES-256 in Galois/Counter Mode</td></tr>\n</tbody>\n</table>\n</div>\n<p>AES-GCM is a widely deployed AEAD cipher. It is a solid choice when hardware AES-NI acceleration is available.</p>\n<h3 id=\"aegis-family\"><a class=\"header\" href=\"#aegis-family\">AEGIS Family</a></h3>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Cipher Name</th><th>Key Size</th><th>Description</th></tr>\n</thead>\n<tbody>\n<tr><td><code>aegis256</code></td><td>32 bytes (256-bit)</td><td>AEGIS-256 (recommended)</td></tr>\n<tr><td><code>aegis128l</code></td><td>16 bytes (128-bit)</td><td>AEGIS-128L</td></tr>\n<tr><td><code>aegis128x2</code></td><td>16 bytes (128-bit)</td><td>AEGIS-128 with 2x parallelization</td></tr>\n<tr><td><code>aegis128x4</code></td><td>16 bytes (128-bit)</td><td>AEGIS-128 with 4x parallelization</td></tr>\n<tr><td><code>aegis256x2</code></td><td>32 bytes (256-bit)</td><td>AEGIS-256 with 2x parallelization</td></tr>\n<tr><td><code>aegis256x4</code></td><td>32 bytes (256-bit)</td><td>AEGIS-256 with 4x parallelization</td></tr>\n</tbody>\n</table>\n</div>\n<p>AEGIS ciphers generally offer better performance than AES-GCM while maintaining strong security properties. <strong><code>aegis256</code> is the recommended default</strong> for most use cases. The <code>x2</code> and <code>x4</code> variants exploit instruction-level parallelism and may perform better on CPUs with wide execution pipelines.</p>\n<p>Cipher names are case-insensitive and accept multiple separator styles. For example, <code>aegis256</code>, <code>aegis-256</code>, and <code>aegis_256</code> all refer to the same algorithm. Similarly, <code>aes128gcm</code>, <code>aes-128-gcm</code>, and <code>aes_128_gcm</code> are equivalent.</p>\n<h2 id=\"generating-encryption-keys\"><a class=\"header\" href=\"#generating-encryption-keys\">Generating Encryption Keys</a></h2>\n<p>Keys are provided as hexadecimal strings. Use OpenSSL or any cryptographically secure random number generator:</p>\n<pre><code class=\"language-bash\"># Generate a 256-bit key (32 bytes) -- for aes256gcm, aegis256, aegis256x2, aegis256x4\nopenssl rand -hex 32\n\n# Generate a 128-bit key (16 bytes) -- for aes128gcm, aegis128l, aegis128x2, aegis128x4\nopenssl rand -hex 16\n</code></pre>\n<p>A 256-bit key produces a 64-character hex string. A 128-bit key produces a 32-character hex string.</p>\n<p><strong>Store your encryption key securely.</strong> If the key is lost, the encrypted database cannot be recovered. There is no key recovery mechanism.</p>\n<h2 id=\"creating-an-encrypted-database\"><a class=\"header\" href=\"#creating-an-encrypted-database\">Creating an Encrypted Database</a></h2>\n<p>There are two ways to configure encryption: PRAGMAs in the SQL shell, or URI parameters on the command line.</p>\n<h3 id=\"method-1-pragmas\"><a class=\"header\" href=\"#method-1-pragmas\">Method 1: PRAGMAs</a></h3>\n<p>Start Turso with the encryption flag, then set the cipher and key before creating any tables:</p>\n<pre><code class=\"language-bash\">tursodb --experimental-encryption database.db\n</code></pre>\n<pre><code class=\"language-sql\">PRAGMA cipher = 'aegis256';\nPRAGMA hexkey = '2d7a30108d3eb3e45c90a732041fe54778bdcf707c76749fab7da335d1b39c1d';\n\n-- The database is now encrypted. Use it normally.\nCREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT);\nINSERT INTO users VALUES (1, 'Alice');\nSELECT * FROM users;\n</code></pre>\n<p>Both <code>PRAGMA cipher</code> and <code>PRAGMA hexkey</code> must be set before any other database operations. The encryption context is established when both values are present. Once set, the cipher and key cannot be changed within the same session.</p>\n<h3 id=\"method-2-uri-parameters\"><a class=\"header\" href=\"#method-2-uri-parameters\">Method 2: URI Parameters</a></h3>\n<p>Specify the cipher and key directly in the database URI:</p>\n<pre><code class=\"language-bash\">tursodb --experimental-encryption \\\n  \"file:database.db?cipher=aegis256&amp;hexkey=2d7a30108d3eb3e45c90a732041fe54778bdcf707c76749fab7da335d1b39c1d\"\n</code></pre>\n<p>This is equivalent to setting the PRAGMAs and is the preferred method for scripting and automation.</p>\n<h2 id=\"opening-an-encrypted-database\"><a class=\"header\" href=\"#opening-an-encrypted-database\">Opening an Encrypted Database</a></h2>\n<p>To open an existing encrypted database, you must provide the correct cipher and key. The recommended approach is URI parameters:</p>\n<pre><code class=\"language-bash\">tursodb --experimental-encryption \\\n  \"file:database.db?cipher=aegis256&amp;hexkey=2d7a30108d3eb3e45c90a732041fe54778bdcf707c76749fab7da335d1b39c1d\"\n</code></pre>\n<p>Alternatively, open the file and set PRAGMAs before any queries:</p>\n<pre><code class=\"language-bash\">tursodb --experimental-encryption database.db\n</code></pre>\n<pre><code class=\"language-sql\">PRAGMA cipher = 'aegis256';\nPRAGMA hexkey = '2d7a30108d3eb3e45c90a732041fe54778bdcf707c76749fab7da335d1b39c1d';\n\n-- Database is now accessible\nSELECT * FROM users;\n</code></pre>\n<p>Opening an encrypted database without providing the correct cipher and key will fail. Providing the wrong cipher or the wrong key will also fail. Turso does not silently return corrupted data.</p>\n<h2 id=\"reading-encryption-settings\"><a class=\"header\" href=\"#reading-encryption-settings\">Reading Encryption Settings</a></h2>\n<p>You can query the current cipher with:</p>\n<pre><code class=\"language-sql\">PRAGMA cipher;\n-- Returns the cipher name, e.g. 'aegis256'\n</code></pre>\n<p>This returns the cipher algorithm configured for the current session. If no cipher has been set, nothing is returned.</p>\n<h2 id=\"pragmas-reference\"><a class=\"header\" href=\"#pragmas-reference\">PRAGMAs Reference</a></h2>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>PRAGMA</th><th>Type</th><th>Description</th></tr>\n</thead>\n<tbody>\n<tr><td><code>PRAGMA cipher = 'name'</code></td><td>Write</td><td>Set the encryption cipher for the session. Must be set before any I/O.</td></tr>\n<tr><td><code>PRAGMA cipher</code></td><td>Read</td><td>Return the current cipher name.</td></tr>\n<tr><td><code>PRAGMA hexkey = 'hex_string'</code></td><td>Write</td><td>Set the encryption key as a hex-encoded string. Must match the cipher’s key size.</td></tr>\n</tbody>\n</table>\n</div>\n<p>The key size must match the cipher:</p>\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Key Size</th><th>Hex String Length</th><th>Ciphers</th></tr>\n</thead>\n<tbody>\n<tr><td>16 bytes</td><td>32 characters</td><td><code>aes128gcm</code>, <code>aegis128l</code>, <code>aegis128x2</code>, <code>aegis128x4</code></td></tr>\n<tr><td>32 bytes</td><td>64 characters</td><td><code>aes256gcm</code>, <code>aegis256</code>, <code>aegis256x2</code>, <code>aegis256x4</code></td></tr>\n</tbody>\n</table>\n</div>\n<h2 id=\"examples-25\"><a class=\"header\" href=\"#examples-25\">Examples</a></h2>\n<h3 id=\"create-and-query-an-encrypted-database\"><a class=\"header\" href=\"#create-and-query-an-encrypted-database\">Create and Query an Encrypted Database</a></h3>\n<pre><code class=\"language-bash\"># Generate a key\nKEY=$(openssl rand -hex 32)\n\n# Create an encrypted database\ntursodb --experimental-encryption \\\n  \"file:secret.db?cipher=aegis256&amp;hexkey=$KEY\" &lt;&lt;'SQL'\nCREATE TABLE secrets (id INTEGER PRIMARY KEY, label TEXT, value TEXT);\nINSERT INTO secrets VALUES (1, 'api_key', 'sk-abc123');\nINSERT INTO secrets VALUES (2, 'db_password', 'hunter2');\nSELECT * FROM secrets;\nSQL\n</code></pre>\n<h3 id=\"verify-encryption-prevents-unauthorized-access\"><a class=\"header\" href=\"#verify-encryption-prevents-unauthorized-access\">Verify Encryption Prevents Unauthorized Access</a></h3>\n<pre><code class=\"language-bash\"># Try opening without credentials -- this will fail\ntursodb --experimental-encryption secret.db &lt;&lt;'SQL'\nSELECT * FROM secrets;\nSQL\n# Error: database is encrypted or is not a database\n</code></pre>\n<h3 id=\"using-a-128-bit-cipher\"><a class=\"header\" href=\"#using-a-128-bit-cipher\">Using a 128-bit Cipher</a></h3>\n<pre><code class=\"language-bash\">KEY128=$(openssl rand -hex 16)\n\ntursodb --experimental-encryption \\\n  \"file:fast.db?cipher=aes128gcm&amp;hexkey=$KEY128\" &lt;&lt;'SQL'\nCREATE TABLE logs (id INTEGER PRIMARY KEY, message TEXT);\nINSERT INTO logs VALUES (1, 'System started');\nSELECT * FROM logs;\nSQL\n</code></pre>\n<h2 id=\"troubleshooting\"><a class=\"header\" href=\"#troubleshooting\">Troubleshooting</a></h2>\n<h3 id=\"database-is-encrypted-or-is-not-a-database\"><a class=\"header\" href=\"#database-is-encrypted-or-is-not-a-database\">“Database is encrypted or is not a database”</a></h3>\n<p>This error appears when:</p>\n<ul>\n<li>Opening an encrypted database without providing the cipher and key.</li>\n<li>Providing the wrong cipher algorithm for an existing encrypted database.</li>\n<li>Providing the wrong key.</li>\n<li>The database file is corrupted.</li>\n</ul>\n<h3 id=\"invalid-hex-string\"><a class=\"header\" href=\"#invalid-hex-string\">“Invalid hex string”</a></h3>\n<p>This means the <code>hexkey</code> value is not valid hexadecimal. Ensure the string contains only characters <code>0-9</code> and <code>a-f</code> (case-insensitive) and that its length matches the cipher’s key size (32 hex characters for 16-byte keys, 64 hex characters for 32-byte keys).</p>\n<h3 id=\"cannot-reset-encryption-attributes-if-already-set-in-the-session\"><a class=\"header\" href=\"#cannot-reset-encryption-attributes-if-already-set-in-the-session\">“Cannot reset encryption attributes if already set in the session”</a></h3>\n<p>The cipher and key can only be set once per session. If you need to change encryption parameters, close the connection and open a new one.</p>\n<h2 id=\"compatibility-23\"><a class=\"header\" href=\"#compatibility-23\">Compatibility</a></h2>\n<ul>\n<li>This feature is a Turso extension and is not available in SQLite.</li>\n<li>Encrypted database files are not compatible with SQLite or other SQLite-based tools. They can only be opened by Turso with the correct cipher and key.</li>\n<li>The <code>--experimental-encryption</code> flag is required. The feature is experimental and the on-disk format may change in future releases.</li>\n<li>There is currently no built-in mechanism to change the encryption key of an existing database or to convert between encrypted and unencrypted formats. To re-key a database, create a new encrypted database and copy the data.</li>\n</ul>\n\n                    </main>\n\n                    <nav class=\"nav-wrapper\" aria-label=\"Page navigation\">\n                        <!-- Mobile navigation buttons -->\n\n\n                        <div style=\"clear: both\"></div>\n                    </nav>\n                </div>\n            </div>\n\n            <nav class=\"nav-wide-wrapper\" aria-label=\"Page navigation\">\n\n            </nav>\n\n        </div>\n\n        <template id=fa-eye><span class=fa-svg><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 576 512\"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d=\"M288 32c-80.8 0-145.5 36.8-192.6 80.6C48.6 156 17.3 208 2.5 243.7c-3.3 7.9-3.3 16.7 0 24.6C17.3 304 48.6 356 95.4 399.4C142.5 443.2 207.2 480 288 480s145.5-36.8 192.6-80.6c46.8-43.5 78.1-95.4 93-131.1c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C433.5 68.8 368.8 32 288 32zM432 256c0 79.5-64.5 144-144 144s-144-64.5-144-144s64.5-144 144-144s144 64.5 144 144zM288 192c0 35.3-28.7 64-64 64c-11.5 0-22.3-3-31.6-8.4c-.2 2.8-.4 5.5-.4 8.4c0 53 43 96 96 96s96-43 96-96s-43-96-96-96c-2.8 0-5.6 .1-8.4 .4c5.3 9.3 8.4 20.1 8.4 31.6z\"/></svg></span></template>\n        <template id=fa-eye-slash><span class=fa-svg><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 512\"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d=\"M38.8 5.1C28.4-3.1 13.3-1.2 5.1 9.2S-1.2 34.7 9.2 42.9l592 464c10.4 8.2 25.5 6.3 33.7-4.1s6.3-25.5-4.1-33.7L525.6 386.7c39.6-40.6 66.4-86.1 79.9-118.4c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C465.5 68.8 400.8 32 320 32c-68.2 0-125 26.3-169.3 60.8L38.8 5.1zM223.1 149.5C248.6 126.2 282.7 112 320 112c79.5 0 144 64.5 144 144c0 24.9-6.3 48.3-17.4 68.7L408 294.5c5.2-11.8 8-24.8 8-38.5c0-53-43-96-96-96c-2.8 0-5.6 .1-8.4 .4c5.3 9.3 8.4 20.1 8.4 31.6c0 10.2-2.4 19.8-6.6 28.3l-90.3-70.8zm223.1 298L373 389.9c-16.4 6.5-34.3 10.1-53 10.1c-79.5 0-144-64.5-144-144c0-6.9 .5-13.6 1.4-20.2L83.1 161.5C60.3 191.2 44 220.8 34.5 243.7c-3.3 7.9-3.3 16.7 0 24.6c14.9 35.7 46.2 87.7 93 131.1C174.5 443.2 239.2 480 320 480c47.8 0 89.9-12.9 126.2-32.5z\"/></svg></span></template>\n        <template id=fa-copy><span class=fa-svg><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d=\"M502.6 70.63l-61.25-61.25C435.4 3.371 427.2 0 418.7 0H255.1c-35.35 0-64 28.66-64 64l.0195 256C192 355.4 220.7 384 256 384h192c35.2 0 64-28.8 64-64V93.25C512 84.77 508.6 76.63 502.6 70.63zM464 320c0 8.836-7.164 16-16 16H255.1c-8.838 0-16-7.164-16-16L239.1 64.13c0-8.836 7.164-16 16-16h128L384 96c0 17.67 14.33 32 32 32h47.1V320zM272 448c0 8.836-7.164 16-16 16H63.1c-8.838 0-16-7.164-16-16L47.98 192.1c0-8.836 7.164-16 16-16H160V128H63.99c-35.35 0-64 28.65-64 64l.0098 256C.002 483.3 28.66 512 64 512h192c35.2 0 64-28.8 64-64v-32h-47.1L272 448z\"/></svg></span></template>\n        <template id=fa-play><span class=fa-svg><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 384 512\"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d=\"M73 39c-14.8-9.1-33.4-9.4-48.5-.9S0 62.6 0 80V432c0 17.4 9.4 33.4 24.5 41.9s33.7 8.1 48.5-.9L361 297c14.3-8.7 23-24.2 23-41s-8.7-32.2-23-41L73 39z\"/></svg></span></template>\n        <template id=fa-clock-rotate-left><span class=fa-svg><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d=\"M75 75L41 41C25.9 25.9 0 36.6 0 57.9V168c0 13.3 10.7 24 24 24H134.1c21.4 0 32.1-25.9 17-41l-30.8-30.8C155 85.5 203 64 256 64c106 0 192 86 192 192s-86 192-192 192c-40.8 0-78.6-12.7-109.7-34.4c-14.5-10.1-34.4-6.6-44.6 7.9s-6.6 34.4 7.9 44.6C151.2 495 201.7 512 256 512c141.4 0 256-114.6 256-256S397.4 0 256 0C185.3 0 121.3 28.7 75 75zm181 53c-13.3 0-24 10.7-24 24V256c0 6.4 2.5 12.5 7 17l72 72c9.4 9.4 24.6 9.4 33.9 0s9.4-24.6 0-33.9l-65-65V152c0-13.3-10.7-24-24-24z\"/></svg></span></template>\n\n        <!-- Livereload script (if served using the cli tool) -->\n        <script>\n            const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';\n            const wsAddress = wsProtocol + \"//\" + location.host + \"/\" + \"__livereload\";\n            const socket = new WebSocket(wsAddress);\n            socket.onmessage = function (event) {\n                if (event.data === \"reload\") {\n                    socket.close();\n                    location.reload();\n                }\n            };\n\n            window.onbeforeunload = function() {\n                socket.close();\n            }\n        </script>\n\n\n        <script>\n            window.playground_copyable = true;\n        </script>\n\n\n        <script src=\"elasticlunr-ef4e11c1.min.js\"></script>\n        <script src=\"mark-09e88c2c.min.js\"></script>\n        <script src=\"searcher-c2a407aa.js\"></script>\n\n        <script src=\"clipboard-1626706a.min.js\"></script>\n        <script src=\"highlight-abc7f01d.js\"></script>\n        <script src=\"book-a0b12cfe.js\"></script>\n\n        <!-- Custom JS scripts -->\n\n        <script>\n        window.addEventListener('load', function() {\n            window.setTimeout(window.print, 100);\n        });\n        </script>\n\n\n    </div>\n    </body>\n</html>\n"
  },
  {
    "path": "docs/manual.md",
    "content": "# Turso Database Manual\n\nWelcome to Turso database manual!\n\n## Table of contents\n\n- [Turso Database Manual](#turso-database-manual)\n  - [Table of contents](#table-of-contents)\n  - [Introduction](#introduction)\n    - [Getting Started](#getting-started)\n    - [Limitations](#limitations)\n  - [Transactions](#transactions)\n    - [Deferred transaction lifecycle](#deferred-transaction-lifecycle)\n  - [The SQL shell](#the-sql-shell)\n    - [Shell commands](#shell-commands)\n    - [Command line options](#command-line-options)\n  - [The SQL language](#the-sql-language)\n    - [`ALTER TABLE` — change table definition](#alter-table--change-table-definition)\n    - [`BEGIN TRANSACTION` — start a transaction](#begin-transaction--start-a-transaction)\n    - [`COMMIT TRANSACTION` — commit the current transaction](#commit-transaction--commit-the-current-transaction)\n    - [`CREATE INDEX` — define a new index](#create-index--define-a-new-index)\n    - [`CREATE TABLE` — define a new table](#create-table--define-a-new-table)\n    - [`DELETE` - delete rows from a table](#delete---delete-rows-from-a-table)\n    - [`DROP INDEX` - remove an index](#drop-index---remove-an-index)\n    - [`DROP TABLE` — remove a table](#drop-table--remove-a-table)\n    - [`END TRANSACTION` — commit the current transaction](#end-transaction--commit-the-current-transaction)\n    - [`INSERT` — create new rows in a table](#insert--create-new-rows-in-a-table)\n    - [`ROLLBACK TRANSACTION` — abort the current transaction](#rollback-transaction--abort-the-current-transaction)\n    - [`SELECT` — retrieve rows from a table](#select--retrieve-rows-from-a-table)\n    - [`UPDATE` — update rows of a table](#update--update-rows-of-a-table)\n  - [JavaScript API](#javascript-api)\n    - [Installation](#installation)\n    - [Getting Started](#getting-started-1)\n  - [SQLite C API](#sqlite-c-api)\n    - [Basic operations](#basic-operations)\n      - [`sqlite3_open`](#sqlite3_open)\n      - [`sqlite3_prepare`](#sqlite3_prepare)\n      - [`sqlite3_step`](#sqlite3_step)\n      - [`sqlite3_column`](#sqlite3_column)\n    - [WAL manipulation](#wal-manipulation)\n      - [`libsql_wal_frame_count`](#libsql_wal_frame_count)\n  - [Journal Mode](#journal-mode)\n  - [Encryption](#encryption)\n  - [Vector search](#vector-search)\n  - [Full-Text Search](#full-text-search-experimental)\n  - [CDC](#cdc-early-preview)\n  - [Index Method](#index-method-experimental)\n  - [Appendix A: Turso Internals](#appendix-a-turso-internals)\n    - [Frontend](#frontend)\n      - [Parser](#parser)\n      - [Code generator](#code-generator)\n      - [Query optimizer](#query-optimizer)\n    - [Virtual Machine](#virtual-machine)\n    - [MVCC](#mvcc)\n    - [Pager](#pager)\n    - [I/O](#io)\n    - [Encryption](#encryption-1)\n    - [References](#references)\n\n## Introduction\n\nTurso is an in-process relational database engine, aiming towards full compatibility with SQLite.\n\nUnlike client-server database systems such as PostgreSQL or MySQL, which require applications to communicate over network protocols for SQL execution,\nan in-process database is in your application memory space.\nThis embedded architecture eliminates network communication overhead, allowing for the best case of low read and write latencies in the order of sub-microseconds.\n\n### Getting Started\n\nYou can install Turso on your computer as follows:\n\n```\ncurl --proto '=https' --tlsv1.2 -LsSf \\\n  https://github.com/tursodatabase/turso/releases/latest/download/turso_cli-installer.sh | sh\n```\n\n\n```\nbrew install turso\n```\n\nWhen you have the software installed, you can start a SQL shell as follows:\n\n```console\n$ tursodb\nTurso\nEnter \".help\" for usage hints.\nConnected to a transient in-memory database.\nUse \".open FILENAME\" to reopen on a persistent database\nturso> SELECT 'hello, world';\nhello, world\n```\n\n### Limitations\n\nTurso aims towards full SQLite compatibility but has the following limitations:\n\n* Query result ordering is not guaranteed to be the same (see [#2964](https://github.com/tursodatabase/turso/issues/2964) for more discussion)\n* No multi-process access\n* No multi-threading\n* No savepoints\n* No triggers\n* No views\n* No vacuum\n* UTF-8 is the only supported character encoding\n\nFor more detailed list of SQLite compatibility, please refer to [COMPAT.md](../COMPAT.md).\n\n#### MVCC limitations\n\nThe MVCC implementation is experimental and has the following limitations:\n\n* Indexes cannot be created and databases with indexes cannot be used.\n* All the data is eagerly loaded from disk to memory on first access so using big databases may take a long time to start, and will consume a lot of memory\n* Only `PRAGMA wal_checkpoint(TRUNCATE)` is supported and it blocks both readers and writers\n* `AUTOINCREMENT` is not supported. Tables with `AUTOINCREMENT` cannot be created or inserted into while MVCC is enabled.\n* Many features may not work, work incorrectly, and/or cause a panic.\n* Queries may return incorrect results\n* If a database is written to using MVCC and then opened again without MVCC, the changes are not visible unless first checkpointed\n\n## The SQL shell\n\nThe `tursodb` command provides an interactive SQL shell, similar to `sqlite3`. You can start it in in-memory mode as follows:\n\n```console\n$ tursodb\nTurso\nEnter \".help\" for usage hints.\nConnected to a transient in-memory database.\nUse \".open FILENAME\" to reopen on a persistent database\nturso> SELECT 'hello, world';\nhello, world\n```\n\n### Shell commands\n\nThe shell supports commands in addition to SQL statements. The commands start with a dot (\".\") followed by the command. The supported commands are:\n\n| Command | Description |\n|---------|-------------|\n| `.schema` | Display the database schema |\n| `.dump` | Dump database contents as SQL statements |\n\n### Command line options\n\nThe SQL shell supports the following command line options:\n\n| Option | Description |\n|--------|-------------|\n| `-m`, `--output-mode` `<mode>` | Configure output mode. Supported values for `<mode>`: <ul><li>`pretty` for pretty output (default)</li><li>`list` for minimal SQLite compatible format</li></ul>\n| `-q`, `--quiet` | Don't display program information at startup |\n| `-e`, `--echo` | Print commands before execution |\n| `--readonly` | Open database in read-only mode |\n| `-h`, `--help` | Print help |\n| `-V`, `--version` | Print version |\n| `--mcp` | Start a MCP server instead of the interactive shell |\n| `--experimental-encryption` | Enable experimental encryption at rest feature. **Note:** the feature is not production ready so do not use it for critical data right now. |\n| `--experimental-views` | Enable experimental views feature. **Note**: the feature is not production ready so do not use it for critical data right now. |\n\n## Transactions\n\nA transaction is a sequence of one or more SQL statements that execute as a single, atomic unit of work.\nA transaction ensures **atomicity** and **isolation**, meaning that either all SQL statements are executed or none of them are, and that concurrent transactions don't interfere with other transactions.\nTransactions maintain database integrity in the presence of errors, crashes, and concurrent access.\n\nEach connection can have exactly one active transaction at a time. All statements prepared on a connection belong to the same transaction context. You cannot interleave statement execution across different transactions on a single connection. When you need concurrency (including `BEGIN CONCURRENT`), you need to use *different connections*, not parallel statements within the same connection.\n\nTurso supports three types of transactions: **deferred**, **immediate**, and **concurrent** transactions:\n\n* **Deferred (default)**: The transaction begins in a suspended state and does not acquire locks immediately. It starts a read transaction when the first read SQL statement (e.g., `SELECT`) runs, and upgrades to a write transaction only when the first write SQL statement (e.g., `INSERT`, `UPDATE`, `DELETE`) executes. This mode allows concurrency for reads and delays write locks, which can reduce contention.\n* **Immediate**: The transaction starts immediately with a reserved write lock, preventing other write transactions from starting concurrently but allowing reads. It attempts to acquire the write lock right away on the `BEGIN` statement, which can fail if another write transaction exists. The `EXCLUSIVE` mode is always an alias for `IMMEDIATE` in Turso, just like it is in SQLite in WAL mode.\n* **Concurrent (MVCC only)**: The transaction begins immediately and allows multiple concurrent read and write transactions. When a concurrent transaction commits, the database performs row-level conflict detection and returns a `SQLITE_BUSY` (write-write conflict) error if the transaction attempted to modify a row that was concurrently modified by another transaction. This mode provides the highest level of concurrency at the cost of potential transaction conflicts that must be retried by the application. The transaction isolation level provided by concurrent transactions is snapshot isolation.\n\n### Deferred transaction lifecycle\n\nWhen the `BEGIN DEFERRED TRANSACTION` statement executes, the database acquires no snapshot or locks. Instead, the transaction is in a suspended state until the first read or write SQL statement executes. When the first read statement executes, a read transaction begins. The database allows multiple read transactions to exist concurrently. When the first write statement executes, a read transaction is either upgraded to a write transaction or a write transaction begins. The database allows a single write transaction at a time. Concurrent write transactions fail with `SQLITE_BUSY` error.\n\nIf a deferred transaction remains unused (no reads or writes), it is automatically restarted by the database if another write transaction commits before the transaction is used. However, if the deferred transaction has already performed reads and another concurrent write transaction commits, it cannot automatically restart due to potential snapshot inconsistency. In this case, the deferred transaction must be manually rolled back and restarted by the application.\n\n### Concurrent transaction lifecycle\n\nConcurrent transactions are only available when MVCC (Multi-Version Concurrency Control) is enabled in the database. They use optimistic concurrency control to allow multiple transactions to modify the database simultaneously.\n\nWhen the `BEGIN CONCURRENT TRANSACTION` statement executes, the database:\n\n1. Assigns a unique transaction ID to the transaction\n2. Records a begin timestamp from the logical clock\n3. Creates an empty read set and write set to track accessed rows\n4. Does **not** acquire any locks\n\nUnlike deferred transactions which delay locking, concurrent transactions never acquire locks. Instead, they rely on MVCC's snapshot isolation and conflict detection at commit time.\n\n#### Read snapshot isolation\n\nEach concurrent transaction reads from a consistent snapshot of the database as of its begin timestamp. This means:\n\n- Reads see all data committed before the transaction's begin timestamp\n- Reads do **not** see writes from other transactions that commit after this transaction starts\n- Reads from the same transaction are consistent (repeatable reads within the transaction)\n- Multiple concurrent transactions can read and write simultaneously without blocking each other\n\nAll rows read by the transaction are tracked in the transaction's read set, and all rows written are tracked in the write set.\n\n#### Commit and conflict detection\n\nWhen a concurrent transaction commits, the database performs these steps:\n\n1. **Exclusive transaction check**: If there is an active exclusive transaction (started with `BEGIN IMMEDIATE` or a `BEGIN DEFERRED` that upgraded to a write transaction), the concurrent transaction **cannot commit** and receives a `SQLITE_BUSY` error. Concurrent transactions can read and write concurrently with exclusive transactions, but cannot commit until the exclusive transaction completes.\n\n2. **Write-write conflict detection**: For each row in the transaction's write set, the database checks if the row was modified by another transaction. A write-write conflict occurs when:\n   - The row is currently being modified by another active transaction, or\n   - The row was modified by a transaction that committed after this transaction's begin timestamp\n\n3. **Commit or abort**: If no conflicts are detected, the transaction commits successfully. All row versions in the write set have their begin timestamps updated to the transaction's commit timestamp, making them visible to future transactions. If a conflict is detected, the transaction fails with a `SQLITE_BUSY` error and must be rolled back and retried by the application.\n\n#### Interaction with exclusive transactions\n\nConcurrent transactions can coexist with exclusive transactions (deferred and immediate), but with important restrictions:\n\n- **Concurrent transactions can read and write** while an exclusive transaction is active\n- **Concurrent transactions cannot commit** while an exclusive transaction holds the exclusive lock\n- **Exclusive transactions block concurrent transaction commits**, not their reads or writes\n\nThis design allows concurrent transactions to make progress during an exclusive transaction, but ensures that exclusive transactions truly have exclusive write access when needed (for example, schema changes).\n\n**Best practice**: For maximum concurrency in MVCC mode, use `BEGIN CONCURRENT` for all write transactions. Only use `BEGIN IMMEDIATE` or `BEGIN DEFERRED` when you need exclusive write access that prevents any concurrent commits.\n\n## The SQL language\n\n### `ALTER TABLE` — change table definition\n\n**Synopsis:**\n\n```sql\nALTER TABLE old_name RENAME TO new_name\n\nALTER TABLE table_name ADD COLUMN column_name [ column_type ]\n\nALTER TABLE table_name DROP COLUMN column_name\n```\n\n**Example:**\n\n```console\nturso> CREATE TABLE t(x);\nturso> .schema t;\nCREATE TABLE t (x);\nturso> ALTER TABLE t ADD COLUMN y TEXT;\nturso> .schema t\nCREATE TABLE t ( x , y TEXT );\nturso> ALTER TABLE t DROP COLUMN y;\nturso> .schema t\nCREATE TABLE t ( x  );\n```\n\n### `BEGIN TRANSACTION` — start a transaction\n\n**Synopsis:**\n\n```sql\nBEGIN [ transaction_mode ] [ TRANSACTION ]\n```\n\nwhere `transaction_mode` is one of the following:\n\n* A `DEFERRED` transaction in a suspended state and does not acquire locks immediately. It starts a read transaction when the first read SQL statement (e.g., `SELECT`) runs, and upgrades to a write transaction only when the first write SQL statement (e.g., `INSERT`, `UPDATE`, `DELETE`) executes.\n* An `IMMEDIATE` transaction starts immediately with a reserved write lock, preventing other write transactions from starting concurrently but allowing reads. It attempts to acquire the write lock right away on the `BEGIN` statement, which can fail if another write transaction exists.\n* An `EXCLUSIVE` transaction is always an alias for `IMMEDIATE`.\n* A `CONCURRENT` transaction begins immediately, but allows other concurrent transactions.\n\n**See also:**\n\n* [Transactions](#transactions)\n* [END TRANSACTION](#end-transaction--commit-the-current-transaction)\n\n### `COMMIT TRANSACTION` — commit the current transaction\n\n**Synopsis:**\n\n```sql\nCOMMIT [ TRANSACTION ]\n```\n\n**See also:**\n\n* [END TRANSACTION](#end-transaction--commit-the-current-transaction)\n\n### `CREATE INDEX` — define a new index\n\n> [!NOTE]  \n> Indexes are currently experimental in Turso and not enabled by default.\n\n**Synopsis:**\n\n```sql\nCREATE INDEX [ index_name ] ON table_name ( column_name )\n```\n\n**Example:**\n\n```\nturso> CREATE TABLE t(x);\nturso> CREATE INDEX t_idx ON t(x);\n```\n\n### `CREATE TABLE` — define a new table\n\n**Synopsis:**\n\n```sql\nCREATE TABLE table_name ( column_name [ column_type ], ... )\n```\n\n**Example:**\n\n```console\nturso> DROP TABLE t;\nturso> CREATE TABLE t(x);\nturso> .schema t\nCREATE TABLE t (x);\n```\n\n### `DELETE` - delete rows from a table\n\n**Synopsis:**\n\n```sql\nDELETE FROM table_name [ WHERE expression ]\n```\n\n**Example:**\n\n```console\nturso> DELETE FROM t WHERE x > 1;\n```\n\n### `DROP INDEX` - remove an index\n\n> [!NOTE]  \n> Indexes are currently experimental in Turso and not enabled by default.\n\n**Example:**\n\n```console\nturso> DROP INDEX idx;\n```\n\n### `DROP TABLE` — remove a table\n\n**Example:**\n\n```console\nturso> DROP TABLE t;\n```\n\n### `END TRANSACTION` — commit the current transaction\n\n```sql\nEND [ TRANSACTION ]\n```\n\n**See also:**\n\n* `COMMIT TRANSACTION`\n\n### `INSERT` — create new rows in a table\n\n**Synopsis:**\n\n```sql\nINSERT INTO table_name [ ( column_name, ... ) ] VALUES ( value, ... ) [, ( value, ... ) ...]\n```\n\n**Example:**\n\n```\nturso> INSERT INTO t VALUES (1), (2), (3);\nturso> SELECT * FROM t;\n┌───┐\n│ x │\n├───┤\n│ 1 │\n├───┤\n│ 2 │\n├───┤\n│ 3 │\n└───┘\n```\n\n### `ROLLBACK TRANSACTION` — abort the current transaction\n\n```sql\nROLLBACK [ TRANSACTION ]\n```\n\n### `SELECT` — retrieve rows from a table\n\n**Synopsis:**\n\n```sql\nSELECT expression\n    [ FROM table-or-subquery ]\n    [ WHERE condition ]\n    [ GROUP BY expression ]\n```\n\n**Example:**\n\n```console\nturso> SELECT 1;\n┌───┐\n│ 1 │\n├───┤\n│ 1 │\n└───┘\nturso> CREATE TABLE t(x);\nturso> INSERT INTO t VALUES (1), (2), (3);\nturso> SELECT * FROM t WHERE x >= 2;\n┌───┐\n│ x │\n├───┤\n│ 2 │\n├───┤\n│ 3 │\n└───┘\n```\n\n### `UPDATE` — update rows of a table\n\n**Synopsis:**\n\n```sql\nUPDATE table_name SET column_name = value [WHERE expression]\n```\n\n**Example:**\n\n```console\nturso> CREATE TABLE t(x);\nturso> INSERT INTO t VALUES (1), (2), (3);\nturso> SELECT * FROM t;\n┌───┐\n│ x │\n├───┤\n│ 1 │\n├───┤\n│ 2 │\n├───┤\n│ 3 │\n└───┘\nturso> UPDATE t SET x = 4 WHERE x >= 2;\nturso> SELECT * FROM t;\n┌───┐\n│ x │\n├───┤\n│ 1 │\n├───┤\n│ 4 │\n├───┤\n│ 4 │\n└───┘\n```\n\n## JavaScript API\n\nTurso supports a JavaScript API, both with native and WebAssembly package options.\n\nPlease read the [JavaScript API reference](docs/javascript-api-reference.md) for more information.\n\n### Installation\n\nInstalling the native package:\n\n```console\nnpm i @tursodatabase/database\n```\n\nInstalling the WebAssembly package:\n\n```console\nnpm i @tursodatabase/database --cpu wasm32\n```\n\n### Getting Started\n\nTo use Turso from JavaScript application, you need to import `Database` type from the `@tursodatabase/database` package.\nYou can the prepare a statement with `Database.prepare` method and execute the SQL statement with `Statement.get()` method.\n\n```\nimport { connect } from '@tursodatabase/database';\n\nconst db = await connect('turso.db');\nconst row = db.prepare('SELECT 1').get();\nconsole.log(row);\n```\n\n## SQLite C API\n\nTurso supports a subset of the SQLite C API, including libSQL extensions.\n\n### Basic operations\n\n#### `sqlite3_open` \n\nOpen a connection to a database.\n\n**Synopsis:**\n\n```c\nint sqlite3_open(const char *filename, sqlite3 **db_out);\nint sqlite3_open_v2(const char *filename, sqlite3 **db_out, int _flags, const char *_z_vfs);\n```\n\n#### `sqlite3_prepare`\n\nPrepare a SQL statement for execution.\n\n**Synopsis:**\n\n```c\nint sqlite3_prepare_v2(sqlite3 *db, const char *sql, int _len, sqlite3_stmt **out_stmt, const char **_tail);\n```\n\n#### `sqlite3_step`\n\nEvaluate a prepared statement until it yields the next row or completes.\n\n**Synopsis:**\n\n```c\nint sqlite3_step(sqlite3_stmt *stmt);\n```\n\n#### `sqlite3_column`\n\nReturn the value of a column for the current row of a statement.\n\n**Synopsis:**\n\n```c\nint sqlite3_column_type(sqlite3_stmt *_stmt, int _idx);\nint sqlite3_column_count(sqlite3_stmt *_stmt);\nconst char *sqlite3_column_decltype(sqlite3_stmt *_stmt, int _idx);\nconst char *sqlite3_column_name(sqlite3_stmt *_stmt, int _idx);\nint64_t sqlite3_column_int64(sqlite3_stmt *_stmt, int _idx);\ndouble sqlite3_column_double(sqlite3_stmt *_stmt, int _idx);\nconst void *sqlite3_column_blob(sqlite3_stmt *_stmt, int _idx);\nint sqlite3_column_bytes(sqlite3_stmt *_stmt, int _idx);\nconst unsigned char *sqlite3_column_text(sqlite3_stmt *stmt, int idx);\n```\n\n### WAL manipulation\n\n#### `libsql_wal_frame_count`\n\nGet the number of frames in the WAL.\n\n**Synopsis:**\n\n```c\nint libsql_wal_frame_count(sqlite3 *db, uint32_t *p_frame_count);\n```\n\n**Description:**\n\nThe `libsql_wal_frame_count` function returns the number of frames in the WAL\nin the `p_frame_count` parameter.\n\n**Return Values:**\n\n* `SQLITE_OK` if the number of frames in the WAL file is successfully returned.\n* `SQLITE_MISUSE` if the `db` is NULL.\n* SQLITE_ERROR if an error occurs while getting the number of frames in the WAL\n  file.\n\n**Safety Requirements:**\n\n* The `db` parameter must be a valid pointer to a `sqlite3` database\n  connection.\n* The `p_frame_count` must be a valid pointer to a `u32` that will store the\n* number of frames in the WAL file.\n\n## Journal Mode\n\nTurso supports switching between different journal modes at runtime using the `PRAGMA journal_mode` statement. Journal modes control how the database handles transaction logging and durability.\n\n### Supported Modes\n\n| Mode | Description |\n|------|-------------|\n| `wal` | Write-Ahead Logging mode. The default mode for new databases. Provides good concurrency for readers and writers. |\n| `mvcc` | Multi-Version Concurrency Control mode. Enables concurrent transactions with snapshot isolation. **Note:** the feature is not production ready so do not use it for critical data right now. |\n\n> **Note:** Legacy SQLite journal modes (`delete`, `truncate`, `persist`, `memory`, `off`) are recognized but not currently supported. Attempting to switch to these modes will return an error.\n\n### Usage\n\n**Query the current journal mode:**\n\n```sql\nPRAGMA journal_mode;\n```\n\n**Switch to WAL mode:**\n\n```sql\nPRAGMA journal_mode = wal;\n```\n\n**Switch to MVCC mode:**\n\n```sql\nPRAGMA journal_mode = mvcc;\n```\n\n### Example\n\n```console\nturso> PRAGMA journal_mode;\n┌──────────────┐\n│ journal_mode │\n├──────────────┤\n│ wal          │\n└──────────────┘\nturso> PRAGMA journal_mode = mvcc;\n┌───────────────────┐\n│ journal_mode      │\n├───────────────────┤\n│ mvcc │\n└───────────────────┘\n```\n\n### Important Notes\n\n- Switching journal modes triggers a checkpoint to ensure all pending changes are persisted before the mode change.\n- When switching from MVCC to WAL mode, the MVCC log file is cleared after checkpointing.\n- Legacy SQLite databases are automatically converted to WAL mode when opened.\n\n## Encryption\n\nThe work-in-progress RFC is [here](https://github.com/tursodatabase/turso/issues/2447).\nTo use encryption, you need to enable it via flag `experimental-encryption`.\nTo get started, generate a secure 32 byte key in hex: \n\n```shell\n$ openssl rand -hex 32\n2d7a30108d3eb3e45c90a732041fe54778bdcf707c76749fab7da335d1b39c1d\n```\n\nSpecify the key and cipher at the time of db creation to use encryption. Here is [sample test](https://github.com/tursodatabase/turso/blob/main/tests/integration/query_processing/encryption.rs):\n\n```shell\n$ cargo run -- --experimental-encryption database.db\n\nPRAGMA cipher = 'aegis256'; -- or 'aes256gcm'\nPRAGMA hexkey = '2d7a30108d3eb3e45c90a732041fe54778bdcf707c76749fab7da335d1b39c1d';\n```\nAlternatively you can provide the encryption parameters directly in a **URI**. For example:\n```shell\n$ cargo run -- --experimental-encryption \\\n\"file:database.db?cipher=aegis256&hexkey=2d7a30108d3eb3e45c90a732041fe54778bdcf707c76749fab7da335d1b39c1d\"\n```\n\n\n> **Note:**  To reopen an already *encrypted database*, the file **must** be opened in URI format with the `cipher` and `hexkey` passed as URI parameters. Now, to reopen `database.db` the command below must be run:\n\n```shell\n$ cargo run -- --experimental-encryption \\\n   \"file:database.db?cipher=aegis256hexkey=2d7a30108d3eb3e45c90a732041fe54778bdcf707c76749fab7da335d1b39c1d\"\n```\n\n\n## Vector search\n\nTurso supports vector search for building workloads such as semantic search, recommendation systems, and similarity matching. Vector embeddings can be stored and queried using specialized functions for distance calculations.\n\n### Vector types\n\nTurso supports **dense**, **sparse**, **quantized**, and **binary** vector representations:\n\n#### Dense vectors\n\nDense vectors store a value for every dimension. Turso provides two precision levels:\n\n* **Float32 dense vectors** (`vector32`): 32-bit floating-point values, suitable for most machine learning embeddings (e.g., OpenAI embeddings, sentence transformers). Uses 4 bytes per dimension.\n* **Float64 dense vectors** (`vector64`): 64-bit floating-point values for applications requiring higher precision. Uses 8 bytes per dimension.\n\nDense vectors are ideal for embeddings from neural networks where most dimensions contain non-zero values.\n\n#### Sparse vectors\n\nSparse vectors only store non-zero values and their indices, making them memory-efficient for high-dimensional data with many zero values:\n\n* **Float32 sparse vectors** (`vector32_sparse`): Stores only non-zero 32-bit float values along with their dimension indices.\n\nSparse vectors are ideal for TF-IDF representations, bag-of-words models, and other scenarios where most dimensions are zero.\n\n#### Quantized vectors\n\n* **8-bit quantized vectors** (`vector8`): Linearly quantizes each float value to an 8-bit integer using min/max scaling. Uses 1 byte per dimension plus 8 bytes for quantization parameters (alpha and shift). Dequantization formula: `f_i = alpha * q_i + shift`.\n\nQuantized vectors reduce memory usage by ~4x compared to Float32 with minimal precision loss, ideal for large-scale similarity search where storage is a concern.\n\n#### Binary vectors\n\n* **1-bit binary vectors** (`vector1bit`): Packs each dimension into a single bit (positive values → 1, non-positive → 0). Uses 1 bit per dimension. Extracted values are displayed as +1/-1.\n\nBinary vectors provide extreme compression (~32x vs Float32) and fast distance computation via bitwise operations. Ideal for binary hashing techniques and approximate nearest neighbor search.\n\n### Vector functions\n\n#### Creating and converting vectors\n\n**`vector32(value)`**\n\nConverts a text or blob value into a 32-bit dense vector.\n\n```sql\nSELECT vector32('[1.0, 2.0, 3.0]');\n```\n\n**`vector32_sparse(value)`**\n\nConverts a text or blob value into a 32-bit sparse vector.\n\n```sql\nSELECT vector32_sparse('[0.0, 1.5, 0.0, 2.3, 0.0]');\n```\n\n**`vector64(value)`**\n\nConverts a text or blob value into a 64-bit dense vector.\n\n```sql\nSELECT vector64('[1.0, 2.0, 3.0]');\n```\n\n**`vector8(value)`**\n\nConverts a text or blob value into an 8-bit quantized vector. Float values are linearly quantized to the 0–255 range using the min and max of the input.\n\n```sql\nSELECT vector8('[1.0, 2.0, 3.0, 4.0]');\n```\n\n**`vector1bit(value)`**\n\nConverts a text or blob value into a 1-bit binary vector. Positive values become 1, non-positive values become 0 (displayed as +1/-1 when extracted).\n\n```sql\nSELECT vector_extract(vector1bit('[1, -1, 1, 1, -1, 0, 0.5]'));\n-- Returns: [1,-1,1,1,-1,-1,1]\n```\n\n**`vector_extract(blob)`**\n\nExtracts and displays a vector blob as human-readable text.\n\n```sql\nSELECT vector_extract(embedding) FROM documents;\n```\n\n#### Distance functions\n\nTurso provides three distance metrics for measuring vector similarity. Both vectors must be of the same type and dimension. All distance functions support Float32, Float64, Float32Sparse, Float8, and Float1Bit vectors unless noted otherwise.\n\n**`vector_distance_cos(v1, v2)`**\n\nComputes the cosine distance between two vectors. Returns a value between 0 (identical direction) and 2 (opposite direction). Cosine distance is computed as `1 - cosine_similarity`. For `vector1bit` vectors, returns the Hamming distance (number of differing bits).\n\nCosine distance is ideal for:\n- Text embeddings where magnitude is less important than direction\n- Comparing document similarity\n\n```sql\nSELECT name, vector_distance_cos(embedding, vector32('[0.1, 0.5, 0.3]')) AS distance\nFROM documents\nORDER BY distance\nLIMIT 10;\n```\n\n**`vector_distance_l2(v1, v2)`**\n\nComputes the Euclidean (L2) distance between two vectors. Returns the straight-line distance in n-dimensional space. Not supported for `vector1bit` vectors (returns an error).\n\nL2 distance is ideal for:\n- Image embeddings where absolute differences matter\n- Spatial data and geometric problems\n- When embeddings are not normalized\n\n```sql\nSELECT name, vector_distance_l2(embedding, vector32('[0.1, 0.5, 0.3]')) AS distance\nFROM documents\nORDER BY distance\nLIMIT 10;\n```\n\n**`vector_distance_dot(v1, v2)`**\n\nComputes the negative dot product between two vectors. Returns `-sum(v1[i] * v2[i])`. Lower values indicate more similar vectors.\n\nDot product distance is ideal for:\n- Normalized embeddings (equivalent to cosine distance when vectors are unit-length)\n- Maximum inner product search (MIPS)\n\n```sql\nSELECT name, vector_distance_dot(embedding, vector32('[0.1, 0.5, 0.3]')) AS distance\nFROM documents\nORDER BY distance\nLIMIT 10;\n```\n\n**`vector_distance_jaccard(v1, v2)`**\n\nComputes the weighted Jaccard distance between two vectors, measuring dissimilarity based on the ratio of minimum to maximum values across dimensions. For `vector1bit` vectors, computes binary Jaccard distance: `1 - |intersection| / |union|` over set bits.\n\nJaccard distance is ideal for:\n- Sparse vectors with many zero values\n- Set-like comparisons\n- TF-IDF and bag-of-words representations\n- Binary similarity with `vector1bit`\n\n```sql\nSELECT name, vector_distance_jaccard(sparse_embedding, vector32_sparse('[0.0, 1.0, 0.0, 2.0]')) AS distance\nFROM documents\nORDER BY distance\nLIMIT 10;\n```\n\n#### Utility functions\n\n**`vector_concat(v1, v2)`**\n\nConcatenates two vectors into a single vector. The resulting vector has dimensions equal to the sum of both input vectors.\n\n```sql\nSELECT vector_concat(vector32('[1.0, 2.0]'), vector32('[3.0, 4.0]'));\n-- Results in a 4-dimensional vector: [1.0, 2.0, 3.0, 4.0]\n```\n\n**`vector_slice(vector, start_index, end_index)`**\n\nExtracts a slice of a vector from `start_index` to `end_index` (exclusive).\n\n```sql\nSELECT vector_slice(vector32('[1.0, 2.0, 3.0, 4.0, 5.0]'), 1, 4);\n-- Results in: [2.0, 3.0, 4.0]\n```\n\n### Example: Semantic search\n\nHere's a complete example of building a semantic search system:\n\n```sql\n-- Create a table for documents with embeddings\nCREATE TABLE documents (\n    id INTEGER PRIMARY KEY,\n    name TEXT,\n    content TEXT,\n    embedding BLOB\n);\n\n-- Insert documents with precomputed embeddings\nINSERT INTO documents (name, content, embedding) VALUES\n    ('Doc 1', 'Machine learning basics', vector32('[0.2, 0.5, 0.1, 0.8]')),\n    ('Doc 2', 'Database fundamentals', vector32('[0.1, 0.3, 0.9, 0.2]')),\n    ('Doc 3', 'Neural networks guide', vector32('[0.3, 0.6, 0.2, 0.7]'));\n\n-- Find documents similar to a query embedding\nSELECT\n    name,\n    content,\n    vector_distance_cos(embedding, vector32('[0.25, 0.55, 0.15, 0.75]')) AS similarity\nFROM documents\nORDER BY similarity\nLIMIT 5;\n```\n\n## Full-Text Search (Experimental)\n\nTurso provides full-text search (FTS) capabilities powered by the [Tantivy](https://github.com/quickwit-oss/tantivy) search engine library. FTS enables efficient text search with relevance ranking, boolean queries, phrase matching, and more.\n\n> **Note:** Full-text search is an experimental feature and requires the `fts` feature to be enabled at compile time.\n\n### Creating an FTS Index\n\nCreate an FTS index on text columns using the `USING fts` syntax:\n\n```sql\nCREATE INDEX idx_articles ON articles USING fts (title, body);\n```\n\nYou can index multiple columns in a single FTS index. The index automatically tracks inserts, updates, and deletes to the underlying table.\n\n### Tokenizer Configuration\n\nConfigure how text is tokenized using the `WITH` clause:\n\n```sql\n-- Use ngram tokenizer for autocomplete/substring matching\nCREATE INDEX idx_products ON products USING fts (name) WITH (tokenizer = 'ngram');\n\n-- Use raw tokenizer for exact-match fields\nCREATE INDEX idx_tags ON articles USING fts (tag) WITH (tokenizer = 'raw');\n```\n\n**Available tokenizers:**\n\n| Tokenizer | Description | Use Case |\n|-----------|-------------|----------|\n| `default` | Lowercase, punctuation split, 40 char limit | General English text |\n| `raw` | No tokenization - exact match only | IDs, UUIDs, tags |\n| `simple` | Basic whitespace/punctuation split | Simple text without lowercase |\n| `whitespace` | Split on whitespace only | Space-separated tokens |\n| `ngram` | 2-3 character n-grams | Autocomplete, substring matching |\n\n### Field Weights\n\nConfigure relative importance of indexed columns for relevance scoring:\n\n```sql\n-- Title matches are 2x more important than body matches\nCREATE INDEX idx_articles ON articles USING fts (title, body)\nWITH (weights = 'title=2.0,body=1.0');\n\n-- Combined with tokenizer\nCREATE INDEX idx_docs ON docs USING fts (name, description)\nWITH (tokenizer = 'simple', weights = 'name=3.0,description=1.0');\n```\n\n### Query Functions\n\nTurso provides three FTS functions:\n\n#### `fts_match(col1, col2, ..., 'query')`\n#### or `WHERE col1, col2 MATCH 'query'`\n\nReturns a boolean indicating if the row matches the query. Used in `WHERE` clauses:\n\n```sql\nSELECT id, title FROM articles WHERE fts_match(title, body, 'database');\n```\n\n#### `fts_score(col1, col2, ..., 'query')`\n\nReturns the BM25 relevance score for ranking results:\n\n```sql\nSELECT fts_score(title, body, 'database') as score, id, title\nFROM articles\nWHERE fts_match(title, body, 'database')\nORDER BY score DESC\nLIMIT 10;\n```\n\n#### `fts_highlight(col1, col2, ..., before_tag, after_tag, 'query')`\n\nReturns text with matching terms wrapped in tags for display:\n\n```sql\nSELECT fts_highlight(body, '<mark>', '</mark>', 'database') as highlighted\nFROM articles\nWHERE fts_match(title, body, 'database');\n-- Returns: \"Learn about <mark>database</mark> optimization\"\n```\n\n### Query Syntax\n\nThe query string supports Tantivy's query syntax:\n[docs](https://docs.rs/tantivy/latest/tantivy/query/struct.QueryParser.html)\n\n| Syntax | Example | Description |\n|--------|---------|-------------|\n| Single term | `database` | Match documents containing \"database\" |\n| Multiple terms (OR) | `database sql` | Match documents with \"database\" OR \"sql\" |\n| AND operator | `database AND sql` | Match documents with both terms |\n| NOT operator | `database NOT nosql` | Match \"database\" but exclude \"nosql\" |\n| Phrase search | `\"full text search\"` | Match exact phrase |\n| Prefix search | `data*` | Match terms starting with \"data\" |\n| Column filter | `title:database` | Match \"database\" only in title field |\n| Boosting | `title:database^2` | Boost matches in title field |\n\n### Complex Queries\n\nFTS functions work with additional WHERE conditions:\n\n```sql\nSELECT id, title, fts_score(title, body, 'Rust') as score\nFROM articles\nWHERE fts_match(title, body, 'Rust')\n  AND category = 'tech'\n  AND published = 1\nORDER BY score DESC;\n```\n\n### Index Maintenance\n\nUse `OPTIMIZE INDEX` to merge Tantivy segments for better query performance:\n\n```sql\n-- Optimize a specific FTS index\nOPTIMIZE INDEX idx_articles;\n\n-- Optimize all FTS indexes\nOPTIMIZE INDEX;\n```\n\nRun optimization after bulk inserts or when query performance degrades.\n\n### Example: Building a Search Feature\n\n```sql\n-- Create a documents table\nCREATE TABLE documents (\n    id INTEGER PRIMARY KEY,\n    title TEXT,\n    content TEXT,\n    category TEXT\n);\n\n-- Create FTS index with weighted fields\nCREATE INDEX fts_docs ON documents USING fts (title, content)\nWITH (weights = 'title=2.0,content=1.0');\n\n-- Insert documents\nINSERT INTO documents VALUES\n    (1, 'Introduction to SQL', 'Learn SQL basics and queries', 'tutorial'),\n    (2, 'Advanced SQL Techniques', 'Complex joins and optimization', 'tutorial'),\n    (3, 'Database Design', 'Schema design best practices', 'architecture');\n\n-- Search with relevance ranking\nSELECT\n    id,\n    title,\n    fts_score(title, content, 'SQL') as score,\n    fts_highlight(content, '<b>', '</b>', 'SQL') as snippet\nFROM documents\nWHERE fts_match(title, content, 'SQL')\nORDER BY score DESC;\n```\n\n### Limitations\n\n| Limitation | Description |\n|------------|-------------|\n| No MATCH operator | Use `fts_match()` function instead of `WHERE table MATCH 'query'` |\n| No read-your-writes in transaction | FTS changes visible only after COMMIT |\n\n## CDC (Early Preview)\n\nTurso supports [Change Data Capture](https://en.wikipedia.org/wiki/Change_data_capture), a powerful pattern for tracking and recording changes to your database in real-time. Instead of periodically scanning tables to find what changed, CDC automatically logs every insert, update, and delete as it happens per connection.\n\n### Enabling CDC\n\n```sql\nPRAGMA capture_data_changes_conn('<mode>[,custom_cdc_table]');\n```\n\n### Parameters\n- `<mode>` can be:\n    - `off`: Turn off CDC for the connection\n    - `id`: Logs only the `rowid` (most compact)\n    - `before`: Captures row state before updates and deletes\n    - `after`: Captures row state after inserts and updates\n    - `full`: Captures both before and after states (recommended for complete audit trail)\n\n- `custom_cdc` is optional, It lets you specify a custom table to capture changes.\nIf no table is provided, Turso uses a default `turso_cdc` table.\n\n\nWhen **Change Data Capture (CDC)** is enabled for a connection, Turso automatically logs all modifications from that connection into a dedicated table (default: `turso_cdc`). This table records each change with details about the operation, the affected row or schema object, and its state **before** and **after** the modification.\n\n> **Note:** Currently, the CDC table is a regular table stored explicitly on disk. If you use full CDC mode and update rows frequently, each update of size N bytes will be written three times to disk (once for the before state, once for the after state, and once for the actual value in the WAL). Frequent updates in full mode can therefore significantly increase disk I/O.\n\n\n\n- **`change_id` (INTEGER)**  \n  A monotonically increasing integer uniquely identifying each change record.(guaranteed by turso-db) \n  - Always strictly increasing.  \n  - Serves as the primary key.  \n\n- **`change_time` (INTEGER)**  \n> turso-db guarantee nothing about properties of the change_time sequence \n  Local timestamp (Unix epoch, seconds) when the change was recorded.  \n  - Not guaranteed to be strictly increasing (can drift or repeat).  \n\n- **`change_type` (INTEGER)**  \n  Indicates the type of operation:  \n  - `1` → INSERT  \n  - `0` → UPDATE (also used for ALTER TABLE)  \n  - `-1` → DELETE (also covers DROP TABLE, DROP INDEX)  \n\n- **`table_name` (TEXT)**  \n  Name of the affected table.  \n  - For schema changes (DDL), this is always `\"sqlite_schema\"`.  \n\n- **`id` (INTEGER)**  \n  Rowid of the affected row in the source table.  \n  - For DDL operations: rowid of the `sqlite_schema` entry.  \n  - **Note:** `WITHOUT ROWID` tables are not supported in the tursodb and CDC\n\n- **`before` (BLOB)**  \n  Full state of the row/schema **before** an UPDATE or DELETE\n  - NULL for INSERT.  \n  - For DDL changes, may contain the definition of the object before modification.  \n\n- **`after` (BLOB)**  \n  Full state of the row/schema **after** an INSERT or UPDATE\n  - NULL for DELETE.  \n  - For DDL changes, may contain the definition of the object after modification.  \n\n- **`updates` (BLOB)**  \n  Granular details about the change.  \n  - For UPDATE: shows specific column modifications.  \n\n\n> CDC records are visible even before a transaction commits. \n> Operations that fail (e.g., constraint violations) are not recorded in CDC.\n\n> Changes to the CDC table itself are also logged to CDC table. if CDC is enabled for that connection.\n\n```zsh\nExample:\nturso> PRAGMA capture_data_changes_conn('full');\nturso> .tables\nturso_cdc\nturso> CREATE TABLE users (\n    id INTEGER PRIMARY KEY,\n    name TEXT\n);\nturso> INSERT INTO users VALUES (1, 'John'), (2, 'Jane');\n\nUPDATE users SET name='John Doe' WHERE id=1;\n\nDELETE FROM users WHERE id=2;\n\nSELECT * FROM turso_cdc;\n┌───────────┬─────────────┬─────────────┬───────────────┬────┬──────────┬──────────────────────────────────────────────────────────────────────────────┬───────────────┐\n│ change_id │ change_time │ change_type │ table_name    │ id │ before   │ after                                                                        │ updates       │\n├───────────┼─────────────┼─────────────┼───────────────┼────┼──────────┼──────────────────────────────────────────────────────────────────────────────┼───────────────┤\n│         1 │  1756713161 │           1 │ sqlite_schema │  2 │          │ ytableusersusersCREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT) │               │\n├───────────┼─────────────┼─────────────┼───────────────┼────┼──────────┼──────────────────────────────────────────────────────────────────────────────┼───────────────┤\n│         2 │  1756713176 │           1 │ users         │  1 │          │       John                                                                      │               │\n├───────────┼─────────────┼─────────────┼───────────────┼────┼──────────┼──────────────────────────────────────────────────────────────────────────────┼───────────────┤\n│         3 │  1756713176 │           1 │ users         │  2 │          │ Jane                                                                     │               │\n├───────────┼─────────────┼─────────────┼───────────────┼────┼──────────┼──────────────────────────────────────────────────────────────────────────────┼───────────────┤\n│         4 │  1756713176 │           0 │ users         │  1 │  John  │         John Doe                                                                  │     John Doe │\n├───────────┼─────────────┼─────────────┼───────────────┼────┼──────────┼──────────────────────────────────────────────────────────────────────────────┼───────────────┤\n│         5 │  1756713176 │          -1 │ users         │  2 │ Jane │                                                                              │               │\n└───────────┴─────────────┴─────────────┴───────────────┴────┴──────────┴──────────────────────────────────────────────────────────────────────────────┴───────────────┘\nturso>\n\n```\n\nIf you modify your table schema (adding/dropping columns), the `table_columns_json_array()` function returns the current schema, not the historical one. This can lead to incorrect results when decoding older CDC records. Manually track schema versions by storing the output of `table_columns_json_array()` before making schema changes.\n\n## Index Method (Experimental)\n\n`tursodb` allows developers to implement custom data access methods and integrate them seamlessly with the query planner. This feature is conceptually similar to [VTable](https://www.sqlite.org/vtab.html) but provides greater flexibility and automatic query planner integration. The feature is experimental and currently gated behind the `--experimental-index-method` flag.\n\n### DDL\n\nIndex Methods can be created using standard `CREATE INDEX` statements by specifying a custom module name:\n\n```sql\nCREATE INDEX t_idx ON t USING index_method_name (column1, column2);\n```\n\nIndex Methods can also include optional parameters whose values may be numeric, floating-point, string, or blob literals:\n\n```sql\nCREATE INDEX t_idx ON t USING index_method_name (c) WITH (a = 1, b = 1.2, c = 'text', d = x'deadbeef');\n```\n\nTo remove an index, use the standard `DROP INDEX t_idx` statement.\n\n### DML\n\nData modification operations for Index Methods are executed implicitly for every modification of the base table (similarly to native B-tree indices):\n\n1. Each `INSERT` operation on the table executes an `IdxInsert` opcode for the Index Method, passing the relevant column values and the `rowid` of the inserted row.\n2. Each `DELETE` operation executes an `IdxDelete` opcode with the corresponding column values and the deleted row's `rowid`.\n3. Each `UPDATE` operation is internally translated into a pair of `DELETE` + `INSERT` operations.\n\n### DQL\n\nAt present, Index Methods can only be used implicitly if the query planner decides to apply them. This decision depends on whether the query matches one of the suitable patterns provided by the Index Method implementation. If parts of a query align with a registered pattern, the planner may substitute default table access method with the Index Method.\n\nFor example, an Index Method can define the following query pattern:\n\n```sql\nSELECT vector_distance_jaccard(embedding, ?) AS distance FROM documents ORDER BY distance LIMIT ?;\n```\n\nThis pattern describes the shape of the output (a single `distance` column), the parameter placeholders (query embedding and limit), and the type of query it can optimize (an ordered retrieval by distance).\n\nThe planner can match this pattern against a user query like:\n\n```sql\nSELECT id, content, created_at FROM documents ORDER BY vector_distance_jaccard(embedding, ?) LIMIT 10;\n```\n\nBecause the query is a *superset* of the pattern, the planner can safely apply the Index Method, enriching its output (`distance`) with data from the main table (`id`, `content`, `created_at`), using the `rowid` provided by each row from the Index Method.\n\nThe query planner is conservative and will avoid using an Index Method if doing so would alter the query's semantics. Consider:\n\n```sql\nSELECT id, content, created_at FROM documents WHERE user = ? ORDER BY vector_distance_jaccard(embedding, ?) LIMIT 10;\n```\n\nThe additional filter `WHERE user = ?` does not fit the Index Method's query pattern, so the planner correctly falls back to the default plan.\n\n### Internals\n\nEach Index Method consists of three traits that work together (for details, see the index method module [root](../core/index_method/mod.rs)):\n\n* **`IndexMethod`** — the root trait for all Index Methods, responsible for creating `IndexMethodAttachment` instances for a given table.\n* **`IndexMethodAttachment`** — represents an Index Method instance bound to a specific table. It can create cursors for query execution and defines the metadata needed for integration with the query planner.\n* **`IndexMethodCursor`** — provides methods for accessing and updating data, as well as for managing the underlying storage during `CREATE INDEX` and `DROP INDEX` operations.\n\nWhile Index Methods can implement arbitrary logic internally, it's generally recommended to use a B-tree as the underlying storage mechanism. To support this, `tursodb` provides a special `backing_btree` Index Method that other Index Methods can use to create auxiliary tables for storing supporting data.\n\nFor more details, see [`toy_vector_sparse_ivf`](../core/index_method/toy_vector_sparse_ivf.rs) implementation.\n\n## Appendix A: Turso Internals\n\nTurso's architecture resembles SQLite's but differs primarily in its\nasynchronous I/O model. This asynchronous design enables applications to\nleverage modern I/O interfaces like `io_uring,` maximizing storage device\nperformance. While an in-process database offers significant performance\nadvantages, integration with cloud services remains crucial for operations\nlike backups. Turso's asynchronous I/O model facilitates this by supporting\nnetworked storage capabilities.\n\nThe high-level interface to Turso is the same as in SQLite:\n\n* SQLite query language\n* The `sqlite3_prepare()` function for translating SQL statements to programs\n  (\"prepared statements\")\n* The `sqlite3_step()` function for executing programs\n\nIf we start with the SQLite query language, you can use the `turso`\ncommand, for example, to evaluate SQL statements in the shell:\n\n```\nturso> SELECT 'hello, world';\nhello, world\n```\n\nTo execute this SQL statement, the shell uses the `sqlite3_prepare()`\ninterface to parse the statement and generate a bytecode program, a step\ncalled preparing a statement. When a statement is prepared, it can be executed\nusing the `sqlite3_step()` function.\n\nTo illustrate the different components of Turso, we can look at the sequence\ndiagram of a query from the CLI to the bytecode virtual machine (VDBE):\n\n```mermaid\nsequenceDiagram\n\nparticipant main as cli/main\nparticipant Database as core/lib/Database\nparticipant Connection as core/lib/Connection\nparticipant Parser as sql/mod/Parser\nparticipant translate as translate/mod\nparticipant Statement as core/lib/Statement\nparticipant Program as vdbe/mod/Program\n\nmain->>Database: open_file\nDatabase->>main: Connection\nmain->>Connection: query(sql)\nNote left of Parser: Parser uses vendored sqlite3-parser\nConnection->>Parser: next()\nNote left of Parser: Passes the SQL query to Parser\n\nParser->>Connection: Cmd::Stmt (ast/mod.rs)\n\nNote right of translate: Translates SQL statement into bytecode\nConnection->>translate:translate(stmt)\n\ntranslate->>Connection: Program \n\nConnection->>main: Ok(Some(Rows { Statement }))\n\nnote right of main: a Statement with <br />a reference to Program is returned\n\nmain->>Statement: step()\nStatement->>Program: step()\nNote left of Program: Program executes bytecode instructions<br />See https://www.sqlite.org/opcode.html\nProgram->>Statement: StepResult\nStatement->>main: StepResult\n```\n\nTo drill down into more specifics, we inspect the bytecode program for a SQL\nstatement using the `EXPLAIN` command in the shell. For our example SQL\nstatement, the bytecode looks as follows:\n\n```\nturso> EXPLAIN SELECT 'hello, world';\naddr  opcode             p1    p2    p3    p4             p5  comment\n----  -----------------  ----  ----  ----  -------------  --  -------\n0     Init               0     4     0                    0   Start at 4\n1     String8            0     1     0     hello, world   0   r[1]='hello, world'\n2     ResultRow          1     1     0                    0   output=r[1]\n3     Halt               0     0     0                    0\n4     Transaction        0     0     0                    0\n5     Goto               0     1     0                    0\n```\n\nThe instruction set of the virtual machine consists of domain specific\ninstructions for a database system. Every instruction consists of an\nopcode that describes the operation and up to 5 operands. In the example\nabove, execution starts at offset zero with the `Init` instruction. The\ninstruction sets up the program and branches to a instruction at address\nspecified in operand `p2`. In our example, address 4 has the\n`Transaction` instruction, which begins a transaction. After that, the\n`Goto` instruction then branches to address 1 where we load a string\nconstant `'hello, world'` to register `r[1]`. The `ResultRow` instruction\nproduces a SQL query result using contents of `r[1]`. Finally, the\nprogram terminates with the `Halt` instruction.\n\n### Frontend\n\n#### Parser\n\nThe parser is the module in the front end that processes SQLite query language input data, transforming it into an abstract syntax tree (AST) for further processing. The parser is an in-tree fork of [lemon-rs](https://github.com/gwenn/lemon-rs), which in turn is a port of SQLite parser into Rust. The emitted AST is handed over to the code generation steps to turn the AST into virtual machine programs.\n\n#### Code generator\n\nThe code generator module takes AST as input and produces virtual machine programs representing executable SQL statements. At high-level, code generation works as follows:\n\n1. `JOIN` clauses are transformed into equivalent `WHERE` clauses, which simplifies code generation.\n2. `WHERE` clauses are mapped into bytecode loops\n3. `ORDER BY` causes the bytecode program to pass result rows to a sorter before returned to the application.\n4. `GROUP BY` also causes the bytecode programs to pass result rows to an aggregation function before results are returned to the application.\n  \n#### Query optimizer\n\nTODO\n\n### Virtual Machine\n\nTODO\n\n### MVCC\n\nThe database implements a multi-version concurrency control (MVCC) using a hybrid architecture that combines an in-memory index with persistent storage through WAL (Write-Ahead Logging) and SQLite database files. The implementation draws from the Hekaton approach documented in Larson et al. (2011), with key modifications for durability handling.\n\nThe database maintains a centralized in-memory MVCC index that serves as the primary coordination point for all database connections. This index provides shared access across all active connections and stores the most recent versions of modified data. It implements version visibility rules for concurrent transactions following the Hekaton MVCC design. The architecture employs a three-tier storage hierarchy consisting of the MVCC index in memory as the primary read/write target for active transactions, a page cache in memory serving as an intermediate buffer for data retrieved from persistent storage, and persistent storage comprising WAL files and SQLite database files on disk.\n\n_Read operations_ follow a lazy loading strategy with a specific precedence order. The database first queries the in-memory MVCC index to check if the requested row exists and is visible to the current transaction. If the row is not found in the MVCC index, the system performs a lazy read from the page cache. When necessary, the page cache retrieves data from both the WAL and the underlying SQLite database file.\n\n_Write operations_ are handled entirely within the in-memory MVCC index during transaction execution. This design provides high-performance writes with minimal latency, immediate visibility of changes within the transaction scope, and isolation from other concurrent transactions until the transaction is committed.\n\n_Commit operation_ ensures durability through a two-phase approach: first, the system writes the complete transaction write set from the MVCC index to the page cache, then the page cache contents are flushed to the WAL, ensuring durable storage of the committed transaction. This commit protocol guarantees that once a transaction commits successfully, all changes are persisted to durable storage and will survive system failures.\n\nWhile the implementation follows Hekaton's core MVCC principles, it differs in one significant aspect regarding logical change tracking. Unlike Hekaton, this system does not maintain a record of logical changes after flushing data to the WAL. This design choice simplifies compatibility with the SQLite database file format.\n\n### Pager\n\nTODO\n\n### I/O\n\nEvery I/O operation shall be tracked by a corresponding `Completion`. A `Completion` is just an object that tracks a particular I/O operation. The database `IO` will call it's complete callback to signal that the operation was complete, thus ensuring that every tracker can be poll to see if the operation succeeded.\n\n\nTo advance the Program State Machines, you must first wait for the tracked completions to complete. This can be done either by busy polling (`io.wait_for_completion`) or polling once and then yielding - e.g\n\n  ```rust\n  if !completion.is_completed {\n    return StepResult::IO;\n  }\n  ```\n\nThis allows us to be flexible in places where we do not have the state machines in place to correctly return the Completion. Thus, we can block in certain places to avoid bigger refactorings, which opens up the opportunity for such refactorings in separate PRs.\n\nTo know if a function does any sort of I/O we just have to look at the function signature. If it returns `Completion`, `Vec<Completion>` or `IOResult`, then it does I/O.\n\nThe `IOResult` struct looks as follows:\n  ```rust\n  pub enum IOCompletions {\n    Single(Completion),\n  }\n\n  #[must_use]\n  pub enum IOResult<T> {\n    Done(T),\n    IO(IOCompletions),\n  }\n  ```\n\nTo combine multiple completions, use `CompletionGroup`:\n  ```rust\n  let mut group = CompletionGroup::new(|_| {});\n  group.add(&completion1);\n  group.add(&completion2);\n  let combined = group.build();  // Single completion that waits for all\n  ```\n\nThis implies that when a function returns an `IOResult`, it must be called again until it returns an `IOResult::Done` variant. This works similarly to how `Future`s are polled in rust. When you receive a `Poll::Ready(None)`, it means that the future stopped it's execution. In a similar vein, if we receive `IOResult::Done`, the function/state machine has reached the end of it's execution. `IOCompletions` is here to signal that, if we are executing any I/O operation, that we need to propagate the completions that are generated from it. This design forces us to handle the fact that a function is asynchronous in nature. This is essentially [function coloring](https://www.tedinski.com/2018/11/13/function-coloring.html), but done at the application level instead of the compiler level.\n\n### Encryption\n\n#### Goals\n\n- Per-page encryption as an opt-in feature, so users don't have to compile/load the encryption extension\n- All pages in db and WAL file to be encrypted on disk\n- Least performance overhead as possible\n\n#### Design\n\n1. We do encryption at the page level, i.e., each page is encrypted and decrypted individually.\n2. At db creation, we take key and cipher scheme information. We store the scheme information (also version) in the db file itself.\n3. The key is not stored anywhere. So each connection should carry an encryption key. Trying to open a db with an invalid or empty key should return an error.\n4. We generate a new randomized, cryptographically safe nonce every time we write a page.\n5. We store the authentication tag and the nonce in the page itself.\n6. We can support different cipher algorithms: AES, ChachaPoly, AEGIS, etc.\n7. We can support key rotation. But rekeying would require writing the entire database.\n8. We should also add import/export functionality to the CLI for better DX and compatibility with SQLite.\n\n#### Metadata management\n\nWe store the nonce and tag (or the verification bits) in the page itself. During decryption, we will load these to decrypt and verify the data.\n\nExample: Assume the page size is 4096 bytes and we use AEGIS 256. So we reserve the last 48 bytes\nfor the nonce (32 bytes) and tag (16 bytes).\n\n```ignore\n            Unencrypted Page              Encrypted Page\n            ┌───────────────┐            ┌───────────────┐\n            │               │            │               │\n            │ Page Content  │            │   Encrypted   │\n            │ (4048 bytes)  │  ────────► │    Content    │\n            │               │            │ (4048 bytes)  │\n            ├───────────────┤            ├───────────────┤\n            │   Reserved    │            │    Tag (16)   │\n            │  (48 bytes)   │            ├───────────────┤\n            │   [empty]     │            │   Nonce (32)  │\n            └───────────────┘            └───────────────┘\n               4096 bytes                   4096 bytes\n```\n\nThe above applies to all the pages except Page 1. Page 1 contains the SQLite header (the first 100 bytes). Specifically, bytes 16 to 24 contain metadata which is required to initialize the connection, which happens before we can set up the encryption context. So, we don't encrypt the header but instead use the header data as additional data (AD) for the encryption of the rest of the page. This provides us protection against tampering and corruption for the unencrypted portion.\n\nOn disk, the encrypted page 1 contains special bytes replacing SQLite's magic bytes (the\nfirst 16 bytes):\n\n```ignore\n                   Turso Header (16 bytes)\n       ┌─────────┬───────┬────────┬──────────────────┐\n       │         │       │        │                  │\n       │ \"Turso\" │Version│ Cipher │     Unused       │\n       │  (5)    │ (1)   │  (1)   │    (9 bytes)     │\n       │         │       │        │                  │\n       └─────────┴───────┴────────┴──────────────────┘\n        0-4      5       6        7-15\n\n       Standard SQLite Header: \"SQLite format 3\\0\" (16 bytes)\n                           ↓\n       Turso Encrypted Header: \"Turso\" + Version + Cipher ID + Unused\n```\n\nThe current version is 0x00. The cipher IDs are:\n\n| Algorithm | Cipher ID |\n|-----------|-----------|\n| AES-GCM (128-bit) | 1 |\n| AES-GCM (256-bit) | 2 |\n| AEGIS-256 | 3 |\n| AEGIS-256-X2 | 4 |\n| AEGIS-256-X4 | 5 |\n| AEGIS-128L | 6 |\n| AEGIS-128-X2 | 7 |\n| AEGIS-128-X4 | 8 |\n\n#### Future work\n1. I have omitted the key derivation aspect. We can later add passphrase and key derivation support.\n2. Pages in memory are unencrypted. We can explore memory enclaves and other mechanisms.\n\n#### Other Considerations\n\nYou may check the [RFC discussion](https://github.com/tursodatabase/turso/issues/2447) and also [Checksum RFC discussion](https://github.com/tursodatabase/turso/issues/2178) for the design decisions.\n\n- SQLite has some unused bytes in the header left for future expansion. We considered using this portion to store the cipher information metadata but decided not to because these may get used in the future.\n- Another alternative was to truncate tag bytes of page 1, then store the meta information. Ultimately, it seemed much better to store the metadata in the magic bytes.\n- For per-page metadata, we decided to store it in the reserved space. The reserved space is for extensions; however, I could not find any usage of it other than the official Checksum shim and other encryption extensions.\n\n### References\n\nPer-Åke Larson et al. \"High-Performance Concurrency Control Mechanisms for Main-Memory Databases.\" In _VLDB '11_\n\n[SQLite]: https://www.sqlite.org/\n"
  },
  {
    "path": "docs/sql-reference/.gitignore",
    "content": "src/\nbook/\n"
  },
  {
    "path": "docs/sql-reference/README.md",
    "content": "# Turso Reference\n\nThis directory contains the Turso reference documentation — both the SQL language reference and the CLI reference. The files are `.mdx` (Markdown + JSX components) and render as readable markdown on GitHub.\n\n## Structure\n\n```\nsql-reference/\n├── data-types.mdx              # Storage classes, type affinity, STRICT tables\n├── expressions.mdx             # Literals, operators, CAST, CASE, pattern matching\n├── experimental-features.mdx   # How to enable experimental features (CLI + all SDKs)\n├── statements/                 # DDL and DML statements\n│   ├── select.mdx\n│   ├── insert.mdx\n│   ├── update.mdx\n│   ├── ...\n│   ├── create-type.mdx         # Turso-specific custom types\n│   └── transactions.mdx        # Includes BEGIN CONCURRENT (Turso MVCC)\n├── functions/                  # Built-in functions\n│   ├── scalar.mdx\n│   ├── aggregate.mdx\n│   ├── json.mdx\n│   ├── vector.mdx              # Turso-specific vector search\n│   ├── fts.mdx                 # Turso-specific full-text search\n│   └── ...\n├── pragmas.mdx                 # All supported PRAGMAs (includes CDC, encryption)\n├── extensions.mdx              # UUID, regexp, vector, time, CSV, percentile\n├── compatibility.mdx           # SQLite compatibility notes and known differences\n└── cli/                        # CLI reference\n    ├── getting-started.mdx     # Installation, usage patterns, output modes\n    ├── command-line-options.mdx # CLI flags and arguments\n    └── shell-commands.mdx      # Dot commands (.tables, .schema, etc.)\n```\n\n## Editing guidelines\n\n- Turso-specific features are marked with `<Info>` callouts. Keep this convention.\n- Experimental features should link to `experimental-features.mdx` for enablement instructions.\n- Unsupported SQLite features are documented only in `compatibility.mdx`, not on individual pages.\n- Every function should have a parameter table with types, a return type, and at least one example.\n- Use `sql` language tags on all code blocks.\n- Keep pages self-contained — use explicit cross-references instead of \"as mentioned above\".\n\n## Local preview (optional)\n\nThese docs are integrated into [turso-docs](https://github.com/tursodatabase/turso-docs) for production rendering with Mintlify. To preview locally, use mdBook (OSS, no Node required):\n\n```bash\n# Install mdbook if you don't have it\ncargo install mdbook\n\n# Preview with live reload at http://localhost:3000\ncd docs/sql-reference\n./preview.sh\n```\n\nThe script converts `.mdx` files to markdown, transforms Mintlify callouts to blockquotes, and serves via mdBook. The `<Info>`/`<Warning>` callouts render as blockquotes — not as styled boxes, but all content is readable.\n\nIf you add a new page, update `preview.sh` to include it in the SUMMARY.\n"
  },
  {
    "path": "docs/sql-reference/book.toml",
    "content": "[book]\ntitle = \"Turso Reference\"\nauthors = [\"Turso\"]\nsrc = \"src\"\n\n[output.html]\nno-section-label = true\ngit-repository-url = \"https://github.com/tursodatabase/turso\"\n"
  },
  {
    "path": "docs/sql-reference/cli/command-line-options.mdx",
    "content": "---\ntitle: Command-Line Options\ndescription: CLI flags, arguments, and server modes for the tursodb command\nsidebarTitle: Command-Line Options\n---\n\n# Command-Line Options\n\n```bash\ntursodb [OPTIONS] [DATABASE] [SQL]\n```\n\n## Arguments\n\n| Argument | Default | Description |\n|----------|---------|-------------|\n| `DATABASE` | `:memory:` | Path to a SQLite database file. If omitted, an in-memory database is created |\n| `SQL` | — | SQL statement to execute. If provided, the shell runs the query and exits |\n\n### Examples\n\n```bash\n# In-memory database\ntursodb\n\n# Open an existing file\ntursodb customers.db\n\n# Run a query and exit\ntursodb customers.db \"SELECT count(*) FROM orders;\"\n\n# Pipe SQL from stdin\necho \"SELECT 1 + 1;\" | tursodb -q\n```\n\n## Options\n\n### `-m, --output-mode <MODE>`\n\nSet the output display format.\n\n| Mode | Description |\n|------|-------------|\n| `pretty` | Table with borders (default) |\n| `list` | Pipe-delimited values |\n| `line` | One column per line with column names |\n\n```bash\ntursodb -m list mydata.db \"SELECT * FROM users;\"\n```\n\n### `-o, --output <PATH>`\n\nRedirect query output to a file instead of stdout.\n\n```bash\ntursodb -o results.txt mydata.db \"SELECT * FROM users;\"\n```\n\n### `-q, --quiet`\n\nSuppress the startup banner and version information.\n\n```bash\ntursodb -q mydata.db\n```\n\n### `-e, --echo`\n\nPrint each SQL statement before executing it. Useful for debugging scripts.\n\n```bash\necho \"SELECT 42;\" | tursodb -q -e\n```\n\n```\nSELECT 42;\n42\n```\n\n### `-v, --vfs <VFS>`\n\nSelect the Virtual File System backend.\n\n| VFS | Description |\n|-----|-------------|\n| `syscall` | Standard OS file I/O (default) |\n| `memory` | In-memory storage |\n| `io_uring` | Linux io_uring (requires feature flag) |\n\n```bash\ntursodb -v memory\n```\n\n### `-t, --tracing-output <PATH>`\n\nWrite internal log traces to a file. Useful for debugging and performance analysis.\n\n```bash\ntursodb -t trace.log mydata.db\n```\n\n### `--readonly`\n\nOpen the database in read-only mode. Write operations will return an error.\n\n```bash\ntursodb --readonly production.db \"SELECT count(*) FROM users;\"\n```\n\nAttempting to write in read-only mode:\n\n```bash\ntursodb --readonly production.db \"INSERT INTO users VALUES (1, 'test');\"\n# Error: Resource is read-only\n```\n\n### `--mcp`\n\nStart a Model Context Protocol server instead of the interactive shell. This allows AI assistants and other MCP clients to interact with the database via JSON-RPC.\n\n```bash\ntursodb --mcp mydata.db\n```\n\n### `--sync-server <ADDRESS>`\n\nStart a sync server for database replication, listening at the given address.\n\n```bash\ntursodb --sync-server 0.0.0.0:8080 mydata.db\n```\n\n## Experimental Feature Flags\n\nThese flags enable features that are still under development. They may change or be removed in future releases.\n\n<Warning>\nExperimental features may have incomplete implementations or known limitations. Use them for testing and development only.\n</Warning>\n\n| Flag | Description |\n|------|-------------|\n| `--experimental-views` | Enable views (`CREATE VIEW` / `DROP VIEW`) |\n| `--experimental-custom-types` | Enable custom types (`CREATE TYPE` / `DROP TYPE`) |\n| `--experimental-encryption` | Enable at-rest database encryption |\n| `--experimental-index-method` | Enable custom index methods. Necessary for FTS and Sparse Vector indexes |\n| `--experimental-autovacuum` | Enable automatic database vacuuming |\n| `--experimental-triggers` | Enable triggers (`CREATE TRIGGER` / `DROP TRIGGER`) |\n| `--experimental-attach` | Enable `ATTACH DATABASE` / `DETACH DATABASE` |\n\n```bash\n# Enable views and triggers\ntursodb --experimental-views --experimental-triggers mydata.db\n```\n\n## Other Flags\n\n| Flag | Description |\n|------|-------------|\n| `--unsafe-testing` | Enable unsafe testing features (e.g., `sqlite_dbpage` writes) |\n| `-h, --help` | Print usage help |\n| `-V, --version` | Print version information |\n"
  },
  {
    "path": "docs/sql-reference/cli/getting-started.mdx",
    "content": "---\ntitle: Getting Started\ndescription: Install and use the Turso interactive SQL shell\nsidebarTitle: Getting Started\n---\n\n# Getting Started\n\nThe Turso CLI (`tursodb`) is an interactive SQL shell for working with Turso databases. It supports in-memory and file-based databases, multiple output modes, CSV import, database cloning, and built-in documentation.\n\n## Quick Start\n\n### In-Memory Database\n\nLaunch the shell with a transient in-memory database:\n\n```bash\ntursodb\n```\n\n### Open a Database File\n\n```bash\ntursodb mydata.db\n```\n\n### Run a Query Directly\n\nPass SQL as a command-line argument to run it and exit:\n\n```bash\ntursodb mydata.db \"SELECT * FROM users;\"\n```\n\n### Pipe SQL from stdin\n\n```bash\necho \"SELECT sqlite_version();\" | tursodb -q\n```\n\n## Interactive Shell\n\nWhen launched without a SQL argument, the shell provides an interactive REPL with command history and syntax highlighting.\n\n### Multi-line Input\n\nUnfinished statements (missing semicolons, unbalanced parentheses) automatically continue on the next line. The prompt indicates nesting depth:\n\n```\ntursodb> SELECT *\n   ...>   FROM employees\n   ...>   WHERE department = 'Engineering';\n```\n\n### Command History\n\nCommand history is saved automatically to `~/.limbo_history` and accessible with up/down arrow keys.\n\n### Exiting\n\nUse `.quit` or `.exit` to leave the shell. Pressing Ctrl+C twice also exits.\n\n## Output Modes\n\nTurso supports three output modes, selectable with the `-m` flag or the `.mode` dot command.\n\n### Pretty (Default)\n\nHuman-readable table with borders:\n\n```bash\ntursodb -q -m pretty\n```\n\n```\n┌────┬─────────┬─────────────┬─────────┐\n│ id │ name    │ department  │ salary  │\n├────┼─────────┼─────────────┼─────────┤\n│  1 │ Alice   │ Engineering │ 95000.0 │\n├────┼─────────┼─────────────┼─────────┤\n│  2 │ Bob     │ Marketing   │ 72000.0 │\n└────┴─────────┴─────────────┴─────────┘\n```\n\n### List\n\nPipe-delimited values suitable for scripting:\n\n```bash\ntursodb -q -m list\n```\n\n```\n1|Alice|Engineering|95000.0\n2|Bob|Marketing|72000.0\n```\n\n### Line\n\nOne column per line, with column names:\n\n```bash\ntursodb -q -m line\n```\n\n```\n        id = 1\n      name = Alice\ndepartment = Engineering\n    salary = 95000.0\n```\n\n## Non-Interactive Mode\n\nWhen input is piped (not a terminal), the shell runs in non-interactive mode:\n\n- Output defaults to `list` mode (override with `-m`)\n- No startup banner, history, or syntax highlighting\n- Exits with code 1 on query errors\n\n```bash\necho \"SELECT 1 + 2;\" | tursodb -q\n```\n\n## Server Modes\n\nThe CLI can also run as a server instead of an interactive shell.\n\n### MCP Server\n\nStart a [Model Context Protocol](https://modelcontextprotocol.io/) server, allowing AI assistants to interact with the database:\n\n```bash\ntursodb --mcp mydata.db\n```\n\n### Sync Server\n\nStart a sync server for database replication:\n\n```bash\ntursodb --sync-server 0.0.0.0:8080 mydata.db\n```\n\n"
  },
  {
    "path": "docs/sql-reference/cli/shell-commands.mdx",
    "content": "---\ntitle: Shell Commands\ndescription: Dot commands available in the Turso interactive SQL shell\nsidebarTitle: Shell Commands\n---\n\n# Shell Commands\n\nDot commands are special commands available in the interactive shell. They start with a period (`.`) and do not require a trailing semicolon.\n\n```\ntursodb> .help\n```\n\n## Database & Navigation\n\n### .open\n\nOpen a database file, optionally specifying a VFS backend.\n\n```\n.open <PATH> [VFS]\n```\n\n```\ntursodb> .open mydata.db\ntursodb> .open mydata.db memory\n```\n\n### .quit\n\nExit the shell. Aliases: `.q`, `.qu`, `.qui`.\n\n```\ntursodb> .quit\n```\n\n### .exit\n\nExit the shell with an optional return code. Aliases: `.ex`, `.exi`.\n\n```\n.exit [CODE]\n```\n\n```\ntursodb> .exit\ntursodb> .exit 1\n```\n\n### .cd\n\nChange the current working directory.\n\n```\n.cd <DIRECTORY>\n```\n\n```\ntursodb> .cd /tmp\n```\n\n## Schema Inspection\n\n### .tables\n\nList all tables in the database, optionally filtered by a pattern.\n\n```\n.tables [PATTERN]\n```\n\n```sql\nCREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT);\nCREATE TABLE departments (id INTEGER PRIMARY KEY, name TEXT);\n```\n\n```\ntursodb> .tables\nemployees\ndepartments\n\ntursodb> .tables emp%\nemployees\n```\n\n### .schema\n\nDisplay the CREATE statement for a table, or all tables if no argument is given.\n\n```\n.schema [TABLE]\n```\n\n```\ntursodb> .schema employees\nCREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT);\n\ntursodb> .schema\nCREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT);\nCREATE TABLE departments (id INTEGER PRIMARY KEY, name TEXT);\n```\n\n### .indexes\n\nShow index names, optionally filtered by table.\n\n```\n.indexes [TABLE]\n```\n\n```sql\nCREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT, department TEXT);\nCREATE INDEX idx_dept ON employees(department);\nCREATE INDEX idx_name ON employees(name);\n```\n\n```\ntursodb> .indexes\nidx_dept\nidx_name\n\ntursodb> .indexes employees\nidx_dept\nidx_name\n```\n\n### .databases\n\nList all attached databases.\n\n```\ntursodb> .databases\nmain: /path/to/mydata.db r/w\n```\n\n## Output Control\n\n### .mode\n\nSet the output display mode.\n\n```\n.mode <MODE>\n```\n\n| Mode | Description |\n|------|-------------|\n| `pretty` | Table with borders (default) |\n| `list` | Pipe-delimited values |\n| `line` | One column per line with column names |\n\n```\ntursodb> .mode list\ntursodb> SELECT 1 AS a, 2 AS b;\n1|2\n\ntursodb> .mode line\ntursodb> SELECT 1 AS a, 2 AS b;\na = 1\nb = 2\n\ntursodb> .mode pretty\ntursodb> SELECT 1 AS a, 2 AS b;\n┌───┬───┐\n│ a │ b │\n├───┼───┤\n│ 1 │ 2 │\n└───┴───┘\n```\n\n### .headers\n\nToggle column headers on or off in `list` mode.\n\n```\n.headers <on|off>\n```\n\n```\ntursodb> .mode list\ntursodb> .headers on\ntursodb> SELECT 1 AS x, 2 AS y;\nx|y\n1|2\n\ntursodb> .headers off\ntursodb> SELECT 1 AS x, 2 AS y;\n1|2\n```\n\n### .nullvalue\n\nSet the string displayed for NULL values in `list` mode.\n\n```\n.nullvalue <STRING>\n```\n\n```\ntursodb> .mode list\ntursodb> .nullvalue [NULL]\ntursodb> SELECT 1 AS a, NULL AS b;\n1|[NULL]\n```\n\n### .output\n\nRedirect query output to a file. Call with no argument or `stdout` to restore output to the terminal.\n\n```\n.output [PATH]\n```\n\n```\ntursodb> .output results.txt\ntursodb> SELECT * FROM employees;\ntursodb> .output\ntursodb> -- Output is back to the terminal\n```\n\n### .echo\n\nToggle echo mode. When on, each SQL statement is printed before execution.\n\n```\n.echo <on|off>\n```\n\n```\ntursodb> .echo on\ntursodb> SELECT 42;\nSELECT 42;\n┌────┐\n│ 42 │\n├────┤\n│ 42 │\n└────┘\n```\n\n### .show\n\nDisplay current shell settings.\n\n```\ntursodb> .show\nSettings:\nOutput mode: pretty\nDB: mydata.db\nOutput: STDOUT\nNull value:\nCWD: /home/user\nEcho: off\nHeaders: off\n```\n\n## Performance & Diagnostics\n\n### .timer\n\nToggle query timing. When on, shows execution time and I/O statistics after each query.\n\n```\n.timer <on|off>\n```\n\n```\ntursodb> .timer on\ntursodb> SELECT 42;\n42\nCommand stats:\n----------------------------\ntotal: 442 us (this includes parsing/coloring of cli app)\n\nquery execution stats:\n----------------------------\nExecution: avg=4 us, total=8 us\nI/O: No samples available\n```\n\n### .stats\n\nDisplay database statistics. Use `on`/`off` to toggle automatic display after every query, or `--reset` to clear counters.\n\n```\n.stats [on|off] [--reset]\n```\n\n```\ntursodb> .stats\nConnection Metrics:\n  Total statements:     3\n  High-water marks:\n    Max VM steps:       19\n    Max rows read:      1\n\nAggregate Statistics:\nStatement Metrics:\n  Row Operations:\n    Rows read:        1\n    Rows written:     2\n  Execution:\n    VM steps:         48\n    Instructions:     47\n  ...\n```\n\n### .opcodes\n\nShow VDBE (Virtual Database Engine) opcodes with descriptions. Optionally filter by opcode name.\n\n```\n.opcodes [OPCODE]\n```\n\n```\ntursodb> .opcodes Integer\nInteger\n-------\nThe 64-bit integer value P1 is written into register P2. This is different\nfrom SQLite, where this opcode is used for 32-bit integers\n```\n\n### .vfslist\n\nList available Virtual File System modules.\n\n```\ntursodb> .vfslist\nAvailable VFS modules:\nmemory\nsyscall\n```\n\n## Data Import & Export\n\n### .dump\n\nOutput the entire database as SQL statements that can recreate it.\n\n```\ntursodb> .dump\nPRAGMA foreign_keys=OFF;\nBEGIN TRANSACTION;\nCREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT);\nINSERT INTO \"employees\" VALUES(1,'Alice');\nINSERT INTO \"employees\" VALUES(2,'Bob');\nCOMMIT;\n```\n\n<Info>\nThe output of `.dump` can be piped into another Turso instance to recreate the database:\n\n```bash\ntursodb original.db \".dump\" | tursodb -q clone.db\n```\n</Info>\n\n### .import\n\nImport data from a file into a table.\n\n```\n.import [--csv] [--skip N] [--verbose] <FILE> <TABLE>\n```\n\n| Option | Default | Description |\n|--------|---------|-------------|\n| `--csv` | on | Use comma as field separator and newline as record separator |\n| `--skip N` | 0 | Skip the first N rows (useful for skipping a header row) |\n| `--verbose` | off | Print progress information during import |\n\n```\ntursodb> CREATE TABLE people (name TEXT, age INTEGER);\ntursodb> .import --csv --skip 1 data.csv people\ntursodb> SELECT * FROM people;\n┌─────────┬─────┐\n│ name    │ age │\n├─────────┼─────┤\n│ Alice   │  30 │\n├─────────┼─────┤\n│ Bob     │  25 │\n├─────────┼─────┤\n│ Charlie │  35 │\n└─────────┴─────┘\n```\n\nGiven a CSV file `data.csv`:\n\n```csv\nname,age\nAlice,30\nBob,25\nCharlie,35\n```\n\n### .clone\n\nClone the current database to a new file.\n\n```\n.clone <OUTPUT_FILE>\n```\n\n```\ntursodb> .clone backup.db\nemployees... done\n```\n\n### .read\n\nExecute SQL statements from a file.\n\n```\n.read <PATH>\n```\n\n```\ntursodb> .read setup.sql\n```\n\n### .load\n\nLoad an extension library.\n\n```\n.load <PATH>\n```\n\n```\ntursodb> .load ./liblimbo_regexp\n```\n\n<Info>\nOnly Turso-native extensions can be loaded. SQLite `.so`/`.dll` loadable extensions are not supported. See the [Extensions](/docs/sql-reference/extensions) documentation for available extensions.\n</Info>\n\n## Debugging\n\n### .dbtotxt\n\nDisplay raw database page contents in hex format. Useful for debugging storage-level issues.\n\n```\n.dbtotxt [--page PAGE_NO]\n```\n\n```\ntursodb> .dbtotxt --page 1\n| size 8192 pagesize 4096 filename :memory:\n| page 1 offset 0\n|      0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00   SQLite format 3.\n|     16: 10 00 02 02 00 40 20 20 00 00 00 01 00 00 00 02   .....@  ........\n...\n```\n\n### .dbconfig\n\nPrint or set database configuration flags. Currently a no-op in Turso.\n\n```\n.dbconfig [CONFIG] [on|off]\n```\n\n## Documentation\n\n### .manual\n\nDisplay built-in manual pages for Turso features. Call with no argument to list all available manuals.\n\n```\n.manual [PAGE]\n```\n\nAliases: `.man`.\n\n```\ntursodb> .manual\n# Turso Manual Pages\n\nAvailable manuals:\n  custom-types    Custom Types for STRICT Tables\n  cdc             Change Data Capture\n  encryption      At-Rest Database Encryption\n  vector          Vector Search\n  materialized-views  Live Materialized Views\n  mcp             Model Context Protocol server\n\ntursodb> .manual vector\n```\n"
  },
  {
    "path": "docs/sql-reference/compatibility.mdx",
    "content": "---\ntitle: SQLite Compatibility\ndescription: Turso compatibility with SQLite - supported features, known differences, and limitations\nsidebarTitle: Compatibility\n---\n\n# SQLite Compatibility\n\nThe authoritative compatibility reference is maintained at [`COMPAT.md`](https://github.com/tursodatabase/turso/blob/main/COMPAT.md) in the repository root. It tracks the support status for every SQL statement, expression, function, PRAGMA, C API function, VDBE opcode, and extension.\n\nRefer to that document for the current state of compatibility. It is updated alongside code changes to stay accurate.\n\n## Turso-Specific Extensions\n\nThese features extend Turso beyond SQLite compatibility:\n\n| Feature | Description |\n|---------|-------------|\n| [CREATE TYPE](/docs/sql-reference/statements/create-type) | User-defined types for STRICT tables |\n| [CREATE MATERIALIZED VIEW](/docs/sql-reference/statements/create-materialized-view) | Live materialized views with incremental maintenance |\n| [BEGIN CONCURRENT](/docs/sql-reference/statements/transactions#begin-concurrent) | Optimistic concurrent write transactions (MVCC) |\n| [Vector functions](/docs/sql-reference/functions/vector) | Vector storage, distance calculations, similarity search |\n| [Full-text search](/docs/sql-reference/functions/fts) | Tantivy-powered FTS with BM25 scoring |\n| [CDC](/docs/sql-reference/pragmas#change-data-capture) | Change Data Capture via PRAGMA |\n| [Encryption](/docs/sql-reference/pragmas#encryption) | At-rest database encryption |\n| [Custom index methods](/docs/sql-reference/statements/create-index#using-method) | CREATE INDEX ... USING for FTS and custom access methods |\n| stddev() | Standard deviation aggregate function |\n\n## See Also\n\n- [Experimental Features](/docs/sql-reference/experimental-features) for how to enable experimental features\n- [COMPAT.md](https://github.com/tursodatabase/turso/blob/main/COMPAT.md) for the full compatibility matrix\n"
  },
  {
    "path": "docs/sql-reference/data-types.mdx",
    "content": "---\ntitle: Data Types\ndescription: Storage classes, type affinity, STRICT tables, and custom types in Turso\nsidebarTitle: Data Types\n---\n\n# Data Types\n\nTurso uses the same dynamic type system as SQLite, where values have types but columns do not enforce a single type (unless using STRICT tables). Every value stored in Turso belongs to one of five storage classes.\n\n## Storage Classes\n\n| Storage Class | Description |\n|---------------|-------------|\n| NULL | The NULL value |\n| INTEGER | A signed integer, stored in 1, 2, 3, 4, 6, or 8 bytes depending on magnitude |\n| REAL | An 8-byte IEEE 754 floating-point number |\n| TEXT | A UTF-8 encoded string |\n| BLOB | Raw binary data, stored exactly as input |\n\n```sql\nSELECT typeof(NULL);       -- 'null'\nSELECT typeof(42);         -- 'integer'\nSELECT typeof(3.14);       -- 'real'\nSELECT typeof('hello');    -- 'text'\nSELECT typeof(x'CAFE');    -- 'blob'\n```\n\n## Type Affinity\n\nWhen a column is declared with a type name, Turso assigns a **type affinity** to that column. Type affinity is a recommendation for how to store values, not a strict constraint (unless using STRICT tables). Turso uses the same affinity rules as SQLite.\n\n### Affinity Determination Rules\n\nThe type affinity of a column is determined by the declared type name, using these rules applied in order:\n\n| Rule | Condition | Affinity | Examples |\n|------|-----------|----------|----------|\n| 1 | Type name contains \"INT\" | INTEGER | `INT`, `INTEGER`, `BIGINT`, `SMALLINT`, `TINYINT` |\n| 2 | Type name contains \"CHAR\", \"CLOB\", or \"TEXT\" | TEXT | `TEXT`, `VARCHAR(255)`, `CLOB`, `CHARACTER(20)` |\n| 3 | Type name contains \"BLOB\" or no type specified | BLOB | `BLOB`, (no type) |\n| 4 | Type name contains \"REAL\", \"FLOA\", or \"DOUB\" | REAL | `REAL`, `FLOAT`, `DOUBLE`, `DOUBLE PRECISION` |\n| 5 | Otherwise | NUMERIC | `NUMERIC`, `DECIMAL`, `BOOLEAN`, `DATE` |\n\n```sql\n-- Type affinity is a suggestion, not a constraint\nCREATE TABLE flexible (\n    id INTEGER,\n    name TEXT,\n    data BLOB\n);\n\n-- This works - TEXT value in an INTEGER column\nINSERT INTO flexible VALUES ('not a number', 42, 'text in blob');\nSELECT typeof(id), typeof(name), typeof(data) FROM flexible;\n-- 'text', 'integer', 'text'\n```\n\n### Type Conversions\n\nWhen a value is inserted into a column, Turso attempts to convert the value to the column's affinity:\n\n- **INTEGER affinity**: If the value is TEXT or REAL that looks like an integer, Turso converts the value to INTEGER\n- **REAL affinity**: If the value is TEXT that looks like a number, Turso converts to REAL. If the value is an integer, Turso converts to REAL\n- **NUMERIC affinity**: Turso tries INTEGER first, then REAL, then keeps as TEXT\n- **TEXT affinity**: Integer and REAL values are converted to their text representation\n- **BLOB affinity**: No conversion is attempted\n\n## STRICT Tables\n\nSTRICT tables enforce type checking at the storage layer. Every value inserted into a STRICT table must match the declared column type or be convertible to the column type.\n\n```sql\nCREATE TABLE users (\n    id INTEGER PRIMARY KEY,\n    name TEXT NOT NULL,\n    age INTEGER,\n    score REAL\n) STRICT;\n\n-- This works - values match declared types\nINSERT INTO users VALUES (1, 'Alice', 30, 95.5);\n\n-- This fails - 'thirty' cannot be converted to INTEGER\nINSERT INTO users VALUES (2, 'Bob', 'thirty', 80.0);\n-- Error: cannot store TEXT value in INTEGER column\n```\n\n### Allowed Types in STRICT Tables\n\nSTRICT tables only allow these base type names:\n\n| Type | Description |\n|------|-------------|\n| INTEGER | Signed integer |\n| REAL | Floating-point number |\n| TEXT | UTF-8 string |\n| BLOB | Raw binary data |\n| ANY | Any storage class (disables type checking for this column) |\n\n<Info>\n**Turso Extension**: STRICT tables also accept [custom type](/docs/sql-reference/statements/create-type) names. Custom types extend the type system with user-defined encoding, decoding, validation, and operator overloading.\n</Info>\n\n## Custom Types\n\n<Info>\n**Turso Extension**: Custom types are a Turso-specific feature not available in SQLite. Custom types work only with STRICT tables. This feature is experimental and must be [enabled before use](/docs/sql-reference/experimental-features).\n</Info>\n\nCustom types let you define how values are encoded before storage and decoded when read, enforce domain constraints at the storage layer, attach operators, and provide defaults. Custom types are declared with the `CREATE TYPE` statement.\n\n```sql\n-- A type that stores monetary values as cents\nCREATE TYPE cents BASE integer\n    ENCODE value * 100\n    DECODE value / 100;\n\nCREATE TABLE prices (\n    id INTEGER PRIMARY KEY,\n    amount cents\n) STRICT;\n\nINSERT INTO prices VALUES (1, 42);\nSELECT amount FROM prices;\n-- 42  (stored on disk as 4200)\n```\n\n### Built-in Custom Types\n\nTurso provides several built-in custom types available in STRICT tables:\n\n| Type | Base | Description |\n|------|------|-------------|\n| `date` | TEXT | ISO 8601 date (YYYY-MM-DD) |\n| `time` | TEXT | ISO 8601 time (HH:MM:SS) |\n| `timestamp` | TEXT | ISO 8601 datetime |\n| `varchar(N)` | TEXT | Text with maximum length constraint |\n| `numeric(P,S)` | BLOB | Fixed-point decimal with precision and scale |\n| `smallint` | INTEGER | Integer constrained to -32768..32767 |\n| `boolean` | INTEGER | Integer constrained to 0 or 1 |\n| `uuid` | BLOB | UUID stored as 16-byte blob, displayed as string |\n| `bytea` | BLOB | Binary data (PostgreSQL-compatible alias) |\n| `inet` | TEXT | IP address |\n| `json` | TEXT | Validated JSON text |\n| `jsonb` | BLOB | JSON in binary format |\n\n```sql\nCREATE TABLE events (\n    id uuid PRIMARY KEY,\n    name varchar(100),\n    event_date date,\n    is_active boolean DEFAULT 1,\n    metadata json\n) STRICT;\n\nINSERT INTO events VALUES (\n    uuid4(),\n    'Product Launch',\n    '2025-03-15',\n    1,\n    '{\"venue\": \"online\"}'\n);\n```\n\nFor the full custom types reference including ENCODE/DECODE, operators, parametric types, and validation, see [CREATE TYPE](/docs/sql-reference/statements/create-type).\n\n## Array Types\n\n<Info>\n**Turso Extension**: Array types are a Turso-specific feature not available in SQLite. Array columns work only with STRICT tables.\n</Info>\n\nArray columns store ordered collections of values. Declare them by appending `[]` to any base type name:\n\n```sql\nCREATE TABLE sensors (\n    id INTEGER PRIMARY KEY,\n    readings REAL[],\n    labels TEXT[],\n    flags INTEGER[]\n) STRICT;\n```\n\nMulti-dimensional arrays are supported with multiple bracket pairs:\n\n```sql\nCREATE TABLE matrices (\n    id INTEGER PRIMARY KEY,\n    data INTEGER[][]\n) STRICT;\n```\n\nArrays can be inserted using the `ARRAY[...]` constructor or as JSON text:\n\n```sql\nINSERT INTO sensors VALUES (1, ARRAY[1.5, 2.5, 3.5], '[\"a\",\"b\"]', '[0, 1, 1]');\n```\n\nArrays are displayed as JSON arrays on output. Element types are validated against the declared base type — for example, an `INTEGER[]` column rejects non-numeric text elements.\n\nFor the full set of array functions, operators, and subscript syntax, see [Array Functions](/docs/sql-reference/functions/array).\n\n### Inspecting Types\n\nList all available types (built-in and custom):\n\n```sql\nPRAGMA list_types;\n```\n\nAll types are also available through the `sqlite_turso_types` virtual table:\n\n```sql\nSELECT name, sql FROM sqlite_turso_types;\n```\n\n## Comparison and Sorting\n\nTurso uses the same comparison rules as SQLite. Values of different storage classes are ordered as:\n\n```\nNULL < INTEGER/REAL < TEXT < BLOB\n```\n\n- NULL values are considered less than any other value\n- INTEGER and REAL values are compared numerically\n- TEXT values are compared using the column's collation sequence (default: BINARY)\n- BLOB values are compared using `memcmp()`\n\n```sql\nSELECT 1 < 2;          -- 1 (true)\nSELECT 'abc' < 'abd';  -- 1 (true)\nSELECT 1 < '2';        -- 1 (true, numeric < text)\nSELECT NULL < 1;        -- NULL (any comparison with NULL yields NULL)\nSELECT NULL IS NULL;    -- 1 (use IS to test for NULL)\n```\n\n## See Also\n\n- [CREATE TABLE](/docs/sql-reference/statements/create-table) for column definitions and constraints\n- [CREATE TYPE](/docs/sql-reference/statements/create-type) for custom type definitions\n- [Array Functions](/docs/sql-reference/functions/array) for array construction, manipulation, and operators\n- [Expressions](/docs/sql-reference/expressions) for CAST expressions and type conversions\n- [Compatibility](/docs/sql-reference/compatibility) for differences from SQLite\n"
  },
  {
    "path": "docs/sql-reference/expressions.mdx",
    "content": "---\ntitle: Expressions\ndescription: SQL expression syntax including literals, operators, CAST, CASE, and subqueries\nsidebarTitle: Expressions\n---\n\n# Expressions\n\nExpressions are combinations of values, operators, and functions that Turso evaluates to produce a result. Expressions appear in many SQL clauses including SELECT columns, WHERE conditions, ORDER BY, GROUP BY, HAVING, CHECK constraints, and DEFAULT values.\n\n## Literals\n\n### Numeric Literals\n\n```sql\nSELECT 42;           -- integer literal\nSELECT -17;          -- negative integer\nSELECT 3.14;         -- real (floating-point) literal\nSELECT 2.5e10;       -- scientific notation\nSELECT 0xFF;         -- hexadecimal integer (255)\n```\n\n### String Literals\n\nString literals are enclosed in single quotes. To include a single quote within a string, use two consecutive single quotes:\n\n```sql\nSELECT 'hello world';        -- text literal\nSELECT 'it''s a test';       -- embedded single quote\nSELECT '';                   -- empty string\n```\n\n### Blob Literals\n\nBlob literals are hexadecimal strings preceded by `x` or `X`:\n\n```sql\nSELECT x'CAFEBABE';          -- blob literal\nSELECT X'48454C4C4F';        -- blob literal (case insensitive prefix)\n```\n\n### NULL Literal\n\n```sql\nSELECT NULL;                 -- null value\n```\n\n### Boolean Literals\n\nTurso does not have a separate boolean type. Use integers `0` (false) and `1` (true):\n\n```sql\nSELECT 1;    -- true\nSELECT 0;    -- false\n```\n\n## Operators\n\n### Arithmetic Operators\n\n| Operator | Description | Example | Result |\n|----------|-------------|---------|--------|\n| `+` | Addition | `3 + 4` | `7` |\n| `-` | Subtraction | `10 - 3` | `7` |\n| `*` | Multiplication | `3 * 4` | `12` |\n| `/` | Division | `10 / 3` | `3` (integer division) |\n| `-` (unary) | Negation | `-5` | `-5` |\n| `+` (unary) | No-op | `+5` | `5` |\n\nInteger division truncates toward zero. Use `CAST` or multiply by `1.0` for floating-point division:\n\n```sql\nSELECT 10 / 3;           -- 3 (integer division)\nSELECT 10 / 3.0;         -- 3.333... (real division)\nSELECT CAST(10 AS REAL) / 3;  -- 3.333...\n```\n\n### Comparison Operators\n\n| Operator | Description | Example |\n|----------|-------------|---------|\n| `=` or `==` | Equal | `x = 5` |\n| `!=` or `<>` | Not equal | `x != 5` |\n| `<` | Less than | `x < 5` |\n| `>` | Greater than | `x > 5` |\n| `<=` | Less than or equal | `x <= 5` |\n| `>=` | Greater than or equal | `x >= 5` |\n\nAll comparison operators return `1` (true), `0` (false), or `NULL` (if either operand is NULL).\n\n### Logical Operators\n\n| Operator | Description | Example |\n|----------|-------------|---------|\n| `AND` | Logical AND | `x > 0 AND x < 10` |\n| `OR` | Logical OR | `x = 1 OR x = 2` |\n| `NOT` | Logical NOT | `NOT x = 5` |\n\n### Bitwise Operators\n\n| Operator | Description | Example | Result |\n|----------|-------------|---------|--------|\n| `&` | Bitwise AND | `5 & 3` | `1` |\n| `\\|` | Bitwise OR | `5 \\| 3` | `7` |\n| `~` | Bitwise NOT | `~5` | `-6` |\n| `<<` | Left shift | `1 << 4` | `16` |\n| `>>` | Right shift | `16 >> 2` | `4` |\n\n### String Concatenation\n\n| Operator | Description | Example | Result |\n|----------|-------------|---------|--------|\n| `\\|\\|` | Concatenation | `'hello' \\|\\| ' ' \\|\\| 'world'` | `'hello world'` |\n\nWhen either operand is an array, `||` performs array concatenation instead. See [Array Operators](#array-operators).\n\n### Array Operators\n\n<Info>\n**Turso Extension**: Array operators are a Turso-specific feature not available in SQLite.\n</Info>\n\n| Operator | Description | Example | Result |\n|----------|-------------|---------|--------|\n| `@>` | Contains all | `ARRAY[1,2,3] @> ARRAY[1,2]` | `1` |\n| `&&` | Overlaps | `ARRAY[1,2,3] && ARRAY[3,4,5]` | `1` |\n| `\\|\\|` | Array concat | `ARRAY[1,2] \\|\\| ARRAY[3,4]` | `[1,2,3,4]` |\n| `=`, `!=`, `<`, `>`, `<=`, `>=` | Comparison | `ARRAY[1,2] < ARRAY[1,3]` | `1` |\n\nThe `@>` operator tests if the left array contains all elements of the right array. The `&&` operator tests if two arrays share any common elements. Both return `1` (true) or `0` (false).\n\nArray comparison is element-wise from left to right. If all compared elements are equal, the shorter array is considered less than the longer one.\n\n```sql\nSELECT ARRAY[1,2,3] @> ARRAY[1,3];     -- 1 (contains both 1 and 3)\nSELECT ARRAY[1,2] && ARRAY[3,4];       -- 0 (no common elements)\nSELECT ARRAY[1,2] < ARRAY[1,2,3];      -- 1 (shorter is less)\n```\n\nFor the full reference, see [Array Functions](/docs/sql-reference/functions/array).\n\n## CAST Expression\n\nThe CAST expression converts a value to a specified type.\n\n```sql\nCAST(expression AS type-name)\n```\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| expression | any | The value to convert |\n| type-name | type | The target type name |\n\n```sql\nSELECT CAST(3.7 AS INTEGER);    -- 3 (truncates toward zero)\nSELECT CAST(42 AS TEXT);         -- '42'\nSELECT CAST('123' AS INTEGER);   -- 123\nSELECT CAST('abc' AS INTEGER);   -- 0\nSELECT CAST(NULL AS INTEGER);    -- NULL\n```\n\n<Info>\n**Turso Extension**: In STRICT tables with custom types, `CAST(value AS custom_type)` applies the custom type's ENCODE function. See [CREATE TYPE](/docs/sql-reference/statements/create-type) for details.\n</Info>\n\n## COLLATE Expression\n\nThe COLLATE expression specifies a collation sequence for string comparison.\n\n```sql\nexpression COLLATE collation-name\n```\n\nBuilt-in collation sequences:\n\n| Collation | Description |\n|-----------|-------------|\n| `BINARY` | Byte-by-byte comparison (default) |\n| `NOCASE` | Case-insensitive comparison for ASCII characters |\n| `RTRIM` | Like BINARY but ignores trailing spaces |\n\n```sql\nSELECT 'ABC' = 'abc';                        -- 0 (BINARY comparison)\nSELECT 'ABC' = 'abc' COLLATE NOCASE;          -- 1 (case-insensitive)\nSELECT 'abc ' = 'abc' COLLATE RTRIM;           -- 1 (trailing space ignored)\n\nSELECT name FROM users ORDER BY name COLLATE NOCASE;\n```\n\n## Pattern Matching\n\n### LIKE Operator\n\nThe LIKE operator performs case-insensitive pattern matching (for ASCII characters). The `%` wildcard matches any sequence of characters, and `_` matches any single character.\n\n```sql\nexpression [NOT] LIKE pattern [ESCAPE escape-char]\n```\n\n```sql\nSELECT 'Hello World' LIKE 'hello%';         -- 1 (case-insensitive)\nSELECT 'Hello World' LIKE 'H_llo%';         -- 1 (_ matches 'e')\nSELECT 'Hello World' LIKE '%World';          -- 1\nSELECT '10%' LIKE '10\\%' ESCAPE '\\';        -- 1 (escaped % literal)\n```\n\n### GLOB Operator\n\nThe GLOB operator performs case-sensitive pattern matching using Unix-style wildcards. `*` matches any sequence of characters, and `?` matches any single character.\n\n```sql\nexpression [NOT] GLOB pattern\n```\n\n```sql\nSELECT 'Hello' GLOB 'H*';      -- 1\nSELECT 'Hello' GLOB 'h*';      -- 0 (case-sensitive)\nSELECT 'Hello' GLOB 'H?llo';   -- 1\nSELECT 'Hello' GLOB 'H[a-z]*'; -- 1 (character class)\n```\n\n### REGEXP Operator\n\nThe REGEXP operator performs regular expression matching. Requires the regexp extension (loaded by default).\n\n```sql\nexpression [NOT] REGEXP pattern\n```\n\n```sql\nSELECT 'Hello123' REGEXP '[A-Za-z]+[0-9]+';  -- 1\nSELECT 'test@email.com' REGEXP '^[^@]+@[^@]+\\.[^@]+$';  -- 1\n```\n\n## BETWEEN Expression\n\nThe BETWEEN expression tests whether a value falls within an inclusive range.\n\n```sql\nexpression [NOT] BETWEEN low AND high\n```\n\nThe BETWEEN expression is equivalent to `expression >= low AND expression <= high`:\n\n```sql\nSELECT 5 BETWEEN 1 AND 10;     -- 1\nSELECT 5 NOT BETWEEN 1 AND 3;  -- 1\nSELECT 'b' BETWEEN 'a' AND 'c'; -- 1\n```\n\n## IN Expression\n\nThe IN expression tests whether a value matches any value in a list or subquery result.\n\n```sql\nexpression [NOT] IN (value1, value2, ...)\nexpression [NOT] IN (select-statement)\n```\n\n```sql\nSELECT 3 IN (1, 2, 3, 4, 5);           -- 1\nSELECT 'red' NOT IN ('blue', 'green');   -- 1\n\nSELECT name FROM users\nWHERE id IN (SELECT user_id FROM orders WHERE total > 100);\n```\n\n## EXISTS Expression\n\nThe EXISTS expression returns `1` if the subquery returns at least one row, and `0` otherwise.\n\n```sql\n[NOT] EXISTS (select-statement)\n```\n\n```sql\nSELECT EXISTS (SELECT 1 FROM users WHERE name = 'Alice');  -- 1 if Alice exists\n\nSELECT name FROM departments d\nWHERE EXISTS (\n    SELECT 1 FROM employees e\n    WHERE e.department_id = d.id\n);\n```\n\n## IS NULL / IS NOT NULL\n\nThe IS NULL expression tests whether a value is NULL. Unlike `= NULL`, which always returns NULL, `IS NULL` returns `1` or `0`.\n\n```sql\nexpression IS NULL\nexpression IS NOT NULL\n```\n\n```sql\nSELECT NULL IS NULL;       -- 1\nSELECT NULL = NULL;        -- NULL (not 1!)\nSELECT 42 IS NOT NULL;     -- 1\nSELECT NULL IS NOT NULL;   -- 0\n```\n\n## IS DISTINCT FROM\n\nThe IS DISTINCT FROM expression compares two values, treating NULL as a comparable value.\n\n```sql\nexpression IS [NOT] DISTINCT FROM expression\n```\n\n```sql\nSELECT 1 IS DISTINCT FROM 2;       -- 1 (different values)\nSELECT 1 IS DISTINCT FROM 1;       -- 0 (same value)\nSELECT NULL IS DISTINCT FROM NULL;  -- 0 (both NULL)\nSELECT NULL IS DISTINCT FROM 1;     -- 1 (NULL vs non-NULL)\nSELECT 1 IS NOT DISTINCT FROM 1;    -- 1\n```\n\n## CASE Expression\n\nThe CASE expression provides conditional logic within SQL expressions.\n\n### Simple CASE\n\n```sql\nCASE expression\n    WHEN value1 THEN result1\n    WHEN value2 THEN result2\n    ...\n    [ELSE default_result]\nEND\n```\n\n### Searched CASE\n\n```sql\nCASE\n    WHEN condition1 THEN result1\n    WHEN condition2 THEN result2\n    ...\n    [ELSE default_result]\nEND\n```\n\nIf no WHEN clause matches and there is no ELSE clause, the CASE expression returns NULL.\n\n```sql\n-- Simple CASE\nSELECT name,\n    CASE status\n        WHEN 'A' THEN 'Active'\n        WHEN 'I' THEN 'Inactive'\n        ELSE 'Unknown'\n    END AS status_text\nFROM users;\n\n-- Searched CASE\nSELECT name, score,\n    CASE\n        WHEN score >= 90 THEN 'A'\n        WHEN score >= 80 THEN 'B'\n        WHEN score >= 70 THEN 'C'\n        ELSE 'F'\n    END AS grade\nFROM students;\n```\n\n## Scalar Subqueries\n\nA subquery enclosed in parentheses that returns a single value can be used as an expression.\n\n```sql\n(select-statement)\n```\n\nThe subquery must return exactly one column and at most one row. If the subquery returns no rows, the expression evaluates to NULL.\n\n```sql\nSELECT name,\n    (SELECT COUNT(*) FROM orders WHERE orders.user_id = users.id) AS order_count\nFROM users;\n\nSELECT * FROM products\nWHERE price > (SELECT AVG(price) FROM products);\n```\n\n## RAISE Function\n\nThe RAISE function raises an error condition. In standard SQLite, RAISE can only be used inside triggers. In Turso, `RAISE(ABORT, msg)` can also be used outside triggers — in CHECK constraints, custom type ENCODE expressions, and standalone queries. The other forms (`RAISE(IGNORE)`, `RAISE(ROLLBACK, msg)`, `RAISE(FAIL, msg)`) are only valid inside triggers.\n\n```sql\nRAISE(IGNORE)\nRAISE(ROLLBACK, error-message)\nRAISE(ABORT, error-message)\nRAISE(FAIL, error-message)\n```\n\n| Form | Description |\n|------|-------------|\n| `RAISE(IGNORE)` | Abandon the current trigger action but continue the statement |\n| `RAISE(ROLLBACK, msg)` | Abort the statement and roll back the current transaction |\n| `RAISE(ABORT, msg)` | Abort the current statement; prior changes in the transaction are preserved |\n| `RAISE(FAIL, msg)` | Abort the current statement at the current point; prior row changes are preserved |\n\n```sql\n-- In a trigger\nCREATE TRIGGER validate_age BEFORE INSERT ON users\nBEGIN\n    SELECT CASE\n        WHEN NEW.age < 0 THEN RAISE(ABORT, 'age must be non-negative')\n    END;\nEND;\n\n-- In a custom type ENCODE expression\nCREATE TYPE positive_int BASE integer\n    ENCODE CASE WHEN value > 0 THEN value\n                ELSE RAISE(ABORT, 'value must be positive') END\n    DECODE value;\n```\n\n## Operator Precedence\n\nOperators are evaluated in the following order (highest precedence first):\n\n| Precedence | Operators |\n|------------|-----------|\n| 1 (highest) | `~` (unary NOT), `+` (unary), `-` (unary) |\n| 2 | `\\|\\|` (concatenation) |\n| 3 | `*`, `/` |\n| 4 | `+`, `-` |\n| 5 | `<<`, `>>`, `&`, `\\|` |\n| 6 | `<`, `<=`, `>`, `>=`, `@>`, `&&` |\n| 7 | `=`, `==`, `!=`, `<>`, `IS`, `IS NOT`, `IS DISTINCT FROM`, `IN`, `LIKE`, `GLOB`, `REGEXP`, `BETWEEN` |\n| 8 | `NOT` |\n| 9 | `AND` |\n| 10 (lowest) | `OR` |\n\nUse parentheses to override precedence when needed:\n\n```sql\nSELECT 2 + 3 * 4;       -- 14 (multiplication first)\nSELECT (2 + 3) * 4;     -- 20 (parentheses override)\n```\n\n## See Also\n\n- [Data Types](/docs/sql-reference/data-types) for storage classes and type affinity\n- [Scalar Functions](/docs/sql-reference/functions/scalar) for built-in functions usable in expressions\n- [SELECT](/docs/sql-reference/statements/select) for using expressions in queries\n"
  },
  {
    "path": "docs/sql-reference/functions/date-time.mdx",
    "content": "---\ntitle: Date and Time Functions\ndescription: Functions for creating, formatting, and manipulating dates, times, and timestamps\nsidebarTitle: Date & Time\n---\n\n# Date and Time Functions\n\nTurso provides a complete set of date and time functions compatible with SQLite. These functions accept time values in various formats, apply optional modifiers, and return results as TEXT, REAL, or INTEGER depending on the function.\n\nAll date and time functions use the [proleptic Gregorian calendar](https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar) and assume UTC unless the `localtime` modifier is applied.\n\n## Functions\n\n### date()\n\nReturns the date as TEXT in `YYYY-MM-DD` format.\n\n```sql\ndate(time-value, modifier, modifier, ...)\n```\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `time-value` | TEXT, REAL, or INTEGER | A time value in any [supported format](#time-value-formats) |\n| `modifier` | TEXT | Zero or more [modifiers](#modifiers) applied left to right |\n\n**Returns:** TEXT in `YYYY-MM-DD` format, or NULL if any argument is invalid.\n\n```sql\nSELECT date('now');\n-- '2025-06-15'\n\nSELECT date('2025-06-15 14:30:00');\n-- '2025-06-15'\n\nSELECT date('2025-06-15', '+1 month');\n-- '2025-07-15'\n\nSELECT date('2025-06-15', 'start of year');\n-- '2025-01-01'\n```\n\n---\n\n### time()\n\nReturns the time as TEXT in `HH:MM:SS` format.\n\n```sql\ntime(time-value, modifier, modifier, ...)\n```\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `time-value` | TEXT, REAL, or INTEGER | A time value in any [supported format](#time-value-formats) |\n| `modifier` | TEXT | Zero or more [modifiers](#modifiers) applied left to right |\n\n**Returns:** TEXT in `HH:MM:SS` format, or NULL if any argument is invalid.\n\n```sql\nSELECT time('now');\n-- '14:30:00'\n\nSELECT time('2025-06-15 14:30:00');\n-- '14:30:00'\n\nSELECT time('14:30:00', '+2 hours');\n-- '16:30:00'\n\nSELECT time('14:30:00', '+90 minutes');\n-- '16:00:00'\n```\n\n---\n\n### datetime()\n\nReturns the date and time as TEXT in `YYYY-MM-DD HH:MM:SS` format.\n\n```sql\ndatetime(time-value, modifier, modifier, ...)\n```\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `time-value` | TEXT, REAL, or INTEGER | A time value in any [supported format](#time-value-formats) |\n| `modifier` | TEXT | Zero or more [modifiers](#modifiers) applied left to right |\n\n**Returns:** TEXT in `YYYY-MM-DD HH:MM:SS` format, or NULL if any argument is invalid.\n\n```sql\nSELECT datetime('now');\n-- '2025-06-15 14:30:00'\n\nSELECT datetime('2025-06-15', '+1 day', '+6 hours');\n-- '2025-06-16 06:00:00'\n\nSELECT datetime(1718458200, 'unixepoch');\n-- '2024-06-15 14:30:00'\n```\n\n---\n\n### julianday()\n\nReturns the [Julian day number](https://en.wikipedia.org/wiki/Julian_day) as a REAL. The Julian day number is the number of days since noon on November 24, 4714 B.C. (proleptic Gregorian calendar).\n\n```sql\njulianday(time-value, modifier, modifier, ...)\n```\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `time-value` | TEXT, REAL, or INTEGER | A time value in any [supported format](#time-value-formats) |\n| `modifier` | TEXT | Zero or more [modifiers](#modifiers) applied left to right |\n\n**Returns:** REAL representing the Julian day number, or NULL if any argument is invalid.\n\n```sql\nSELECT julianday('2025-06-15');\n-- 2460811.5\n\nSELECT julianday('now');\n-- 2460811.104166...\n\n-- Compute the number of days between two dates\nSELECT julianday('2025-12-25') - julianday('2025-06-15');\n-- 193.0\n```\n\n---\n\n### unixepoch()\n\nReturns the Unix timestamp as an INTEGER. The Unix timestamp is the number of seconds since `1970-01-01 00:00:00 UTC`.\n\n```sql\nunixepoch(time-value, modifier, modifier, ...)\n```\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `time-value` | TEXT, REAL, or INTEGER | A time value in any [supported format](#time-value-formats) |\n| `modifier` | TEXT | Zero or more [modifiers](#modifiers) applied left to right |\n\n**Returns:** INTEGER representing seconds since the Unix epoch, or NULL if any argument is invalid. If the `subsec` modifier is used, returns REAL with fractional seconds.\n\n```sql\nSELECT unixepoch('now');\n-- 1718458200\n\nSELECT unixepoch('2025-06-15 14:30:00');\n-- 1750001400\n\nSELECT unixepoch('2025-06-15 14:30:00.123', 'subsec');\n-- 1750001400.123\n```\n\n---\n\n### strftime()\n\nReturns a formatted date/time string according to the specified format string.\n\n```sql\nstrftime(format, time-value, modifier, modifier, ...)\n```\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `format` | TEXT | A format string containing [substitution codes](#strftime-format-codes) |\n| `time-value` | TEXT, REAL, or INTEGER | A time value in any [supported format](#time-value-formats) |\n| `modifier` | TEXT | Zero or more [modifiers](#modifiers) applied left to right |\n\n**Returns:** TEXT with format codes replaced by date/time components, or NULL if any argument is invalid.\n\nThe other date/time functions can be expressed as `strftime` calls:\n\n| Function | Equivalent strftime |\n|----------|---------------------|\n| `date(...)` | `strftime('%Y-%m-%d', ...)` |\n| `time(...)` | `strftime('%H:%M:%S', ...)` |\n| `datetime(...)` | `strftime('%Y-%m-%d %H:%M:%S', ...)` |\n| `julianday(...)` | `strftime('%J', ...)` (as TEXT) |\n| `unixepoch(...)` | `strftime('%s', ...)` (as TEXT) |\n\n```sql\nSELECT strftime('%Y', 'now');\n-- '2025'\n\nSELECT strftime('%m/%d/%Y', '2025-06-15');\n-- '06/15/2025'\n\nSELECT strftime('%H:%M', '2025-06-15 14:30:00');\n-- '14:30'\n\nSELECT strftime('%Y-%m-%d %H:%M:%f', '2025-06-15 14:30:00.123');\n-- '2025-06-15 14:30:00.123'\n\nSELECT strftime('%s', '2025-06-15 14:30:00');\n-- '1750001400'\n\nSELECT strftime('%W', '2025-06-15');\n-- '23' (week of year)\n```\n\n#### strftime Format Codes\n\n| Code | Description | Example |\n|------|-------------|---------|\n| `%d` | Day of month (01-31) | `15` |\n| `%e` | Day of month with leading space ( 1-31) | `15` |\n| `%f` | Fractional seconds (SS.SSS) | `00.123` |\n| `%F` | ISO 8601 date (YYYY-MM-DD) | `2025-06-15` |\n| `%H` | Hour (00-23) | `14` |\n| `%I` | Hour (01-12) | `02` |\n| `%j` | Day of year (001-366) | `166` |\n| `%J` | Julian day number | `2460811.5` |\n| `%k` | Hour with leading space ( 0-23) | `14` |\n| `%l` | Hour with leading space ( 1-12) | ` 2` |\n| `%m` | Month (01-12) | `06` |\n| `%M` | Minute (00-59) | `30` |\n| `%p` | AM or PM | `PM` |\n| `%P` | am or pm | `pm` |\n| `%R` | Time as HH:MM | `14:30` |\n| `%s` | Seconds since Unix epoch | `1750001400` |\n| `%S` | Seconds (00-59) | `00` |\n| `%T` | Time as HH:MM:SS | `14:30:00` |\n| `%u` | Day of week (1=Monday, 7=Sunday) | `7` |\n| `%w` | Day of week (0=Sunday, 6=Saturday) | `0` |\n| `%W` | Week of year (00-53) | `23` |\n| `%Y` | Year (0000-9999) | `2025` |\n| `%%` | Literal `%` character | `%` |\n\n---\n\n### timediff()\n\nReturns the difference between two time values as a TEXT string in the format `(+|-)YYYY-MM-DD HH:MM:SS.SSS`. The result represents the time that must be added to `time2` to produce `time1`.\n\n```sql\ntimediff(time1, time2)\n```\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `time1` | TEXT, REAL, or INTEGER | The later time value |\n| `time2` | TEXT, REAL, or INTEGER | The earlier time value |\n\n**Returns:** TEXT in `(+|-)YYYY-MM-DD HH:MM:SS.SSS` format, or NULL if either argument is invalid.\n\n```sql\nSELECT timediff('2025-06-15', '2025-06-10');\n-- '+0000-00-05 00:00:00.000'\n\nSELECT timediff('2025-01-01', '2024-01-01');\n-- '+0001-00-00 00:00:00.000'\n\nSELECT timediff('2025-06-15 14:30:00', '2025-06-15 10:00:00');\n-- '+0000-00-00 04:30:00.000'\n\nSELECT timediff('2024-01-01', '2025-06-15');\n-- '-0001-05-14 00:00:00.000'\n```\n\n## Time Value Formats\n\nAll date and time functions accept time values in the following formats.\n\n| Format | Example | Description |\n|--------|---------|-------------|\n| `YYYY-MM-DD` | `'2025-06-15'` | Date only (time defaults to 00:00:00) |\n| `YYYY-MM-DD HH:MM` | `'2025-06-15 14:30'` | Date and time (seconds default to 00) |\n| `YYYY-MM-DD HH:MM:SS` | `'2025-06-15 14:30:00'` | Date and time with seconds |\n| `YYYY-MM-DD HH:MM:SS.SSS` | `'2025-06-15 14:30:00.123'` | Date and time with fractional seconds |\n| `YYYY-MM-DDTHH:MM` | `'2025-06-15T14:30'` | ISO 8601 with `T` separator |\n| `YYYY-MM-DDTHH:MM:SS` | `'2025-06-15T14:30:00'` | ISO 8601 with `T` separator |\n| `YYYY-MM-DDTHH:MM:SS.SSS` | `'2025-06-15T14:30:00.123'` | ISO 8601 with `T` separator |\n| `HH:MM` | `'14:30'` | Time only (date defaults to 2000-01-01) |\n| `HH:MM:SS` | `'14:30:00'` | Time only with seconds |\n| `HH:MM:SS.SSS` | `'14:30:00.123'` | Time only with fractional seconds |\n| `now` | `'now'` | Current date and time in UTC |\n| Julian day number | `2460811.5` | REAL number representing a Julian day |\n| Unix timestamp | `1718458200` | INTEGER, requires `'unixepoch'` modifier |\n\n<Info>\nWhen passing a Unix timestamp as the time value, you must include the `'unixepoch'` modifier so the function knows to interpret the number as seconds since 1970-01-01, not as a Julian day number.\n</Info>\n\n```sql\n-- Unix timestamp requires the 'unixepoch' modifier\nSELECT datetime(1718458200, 'unixepoch');\n-- '2024-06-15 14:30:00'\n\n-- Without 'unixepoch', the number is treated as a Julian day\nSELECT datetime(1718458200);\n-- NULL (or a very distant date)\n\n-- 'now' returns the current UTC time\nSELECT datetime('now');\n-- '2025-06-15 14:30:00'\n```\n\n## Modifiers\n\nModifiers transform the time value. Multiple modifiers are applied left to right. If any modifier is invalid, the function returns NULL.\n\n### Offset Modifiers\n\nOffset modifiers add or subtract a specified amount from the time value.\n\n| Modifier | Description | Example |\n|----------|-------------|---------|\n| `NNN days` | Add NNN days | `'+7 days'`, `'-1 days'` |\n| `NNN hours` | Add NNN hours | `'+6 hours'` |\n| `NNN minutes` | Add NNN minutes | `'+30 minutes'` |\n| `NNN seconds` | Add NNN seconds | `'+90 seconds'` |\n| `NNN.NNNN seconds` | Add fractional seconds | `'+0.5 seconds'` |\n| `NNN months` | Add NNN months | `'+1 months'` |\n| `NNN years` | Add NNN years | `'+1 years'` |\n\nNNN can be a positive or negative integer (or real number for seconds). The `+` sign is optional for positive values.\n\n```sql\nSELECT date('2025-06-15', '+7 days');\n-- '2025-06-22'\n\nSELECT datetime('2025-06-15 14:30:00', '-6 hours');\n-- '2025-06-15 08:30:00'\n\nSELECT date('2025-01-31', '+1 months');\n-- '2025-03-03' (Jan 31 + 1 month = Mar 3)\n```\n\n### Time Offset Modifier\n\nA time offset in the format `+HH:MM` or `-HH:MM` adds or subtracts the specified hours and minutes.\n\n```sql\nSELECT time('14:30:00', '+05:30');\n-- '20:00:00'\n\nSELECT datetime('2025-06-15 14:30:00', '-08:00');\n-- '2025-06-15 06:30:00'\n```\n\n### Start-of Modifiers\n\nThese modifiers reset the time value to the start of a period.\n\n| Modifier | Description |\n|----------|-------------|\n| `start of day` | Sets time to 00:00:00, keeps date |\n| `start of month` | Sets to first day of the month, time to 00:00:00 |\n| `start of year` | Sets to January 1 of the year, time to 00:00:00 |\n\n```sql\nSELECT datetime('2025-06-15 14:30:00', 'start of day');\n-- '2025-06-15 00:00:00'\n\nSELECT date('2025-06-15', 'start of month');\n-- '2025-06-01'\n\nSELECT date('2025-06-15', 'start of year');\n-- '2025-01-01'\n```\n\n### Weekday Modifier\n\nThe `weekday N` modifier advances the date to the next occurrence of the specified weekday, where 0 = Sunday, 1 = Monday, ..., 6 = Saturday. If the current date already falls on that weekday, it is unchanged.\n\n```sql\n-- Next Sunday (weekday 0)\nSELECT date('2025-06-15', 'weekday 0');\n-- '2025-06-15' (June 15, 2025 is a Sunday)\n\n-- Next Monday\nSELECT date('2025-06-15', 'weekday 1');\n-- '2025-06-16'\n\n-- Next Friday\nSELECT date('2025-06-15', 'weekday 5');\n-- '2025-06-20'\n```\n\n### Interpretation Modifiers\n\n| Modifier | Description |\n|----------|-------------|\n| `unixepoch` | Interpret the time value as a Unix timestamp (seconds since 1970-01-01) |\n| `julianday` | Interpret the time value as a Julian day number |\n| `auto` | Automatically detect whether the value is a Unix timestamp or Julian day |\n\n```sql\nSELECT datetime(1718458200, 'unixepoch');\n-- '2024-06-15 14:30:00'\n\nSELECT datetime(2460811.5, 'julianday');\n-- '2025-06-15 00:00:00'\n\nSELECT datetime(0, 'unixepoch');\n-- '1970-01-01 00:00:00'\n```\n\n### Timezone Modifiers\n\n| Modifier | Description |\n|----------|-------------|\n| `localtime` | Convert from UTC to local time |\n| `utc` | Convert from local time to UTC |\n\n```sql\nSELECT datetime('2025-06-15 14:30:00', 'localtime');\n-- '2025-06-15 07:30:00' (example: UTC-7)\n\nSELECT time('now', 'localtime');\n-- Local current time\n```\n\n### Rounding Modifiers\n\n| Modifier | Description |\n|----------|-------------|\n| `ceiling` | When adding months, if the day overflows, advance to the first day of the next month |\n| `floor` | When adding months, if the day overflows, use the last day of the target month |\n\nThese modifiers affect how month arithmetic handles months with different numbers of days.\n\n```sql\n-- Without floor/ceiling, month overflow rolls forward\nSELECT date('2025-01-31', '+1 months');\n-- '2025-03-03'\n\n-- With floor, clamp to last day of target month\nSELECT date('2025-01-31', 'floor', '+1 months');\n-- '2025-02-28'\n\n-- With ceiling, advance to first of next month\nSELECT date('2025-01-31', 'ceiling', '+1 months');\n-- '2025-03-01'\n```\n\n### Subsec Modifier\n\nThe `subsec` modifier causes `unixepoch()` to return a REAL with fractional seconds instead of truncating to INTEGER.\n\n```sql\nSELECT unixepoch('2025-06-15 14:30:00.456', 'subsec');\n-- 1750001400.456\n```\n\n## Practical Examples\n\n### Get the Current Date and Time\n\n```sql\nSELECT date('now');                    -- Current UTC date\nSELECT time('now');                    -- Current UTC time\nSELECT datetime('now');                -- Current UTC date and time\nSELECT datetime('now', 'localtime');   -- Current local date and time\n```\n\n### Date Arithmetic\n\n```sql\n-- Tomorrow\nSELECT date('now', '+1 day');\n\n-- 90 days from now\nSELECT date('now', '+90 days');\n\n-- Last day of the current month\nSELECT date('now', 'start of month', '+1 month', '-1 day');\n\n-- First Monday of the current month\nSELECT date('now', 'start of month', 'weekday 1');\n```\n\n### Age Calculation\n\n```sql\nSELECT\n    name,\n    birth_date,\n    timediff('now', birth_date) AS age_diff\nFROM users;\n```\n\n### Convert Between Formats\n\n```sql\n-- Unix timestamp to human-readable\nSELECT datetime(1718458200, 'unixepoch');\n\n-- Human-readable to Unix timestamp\nSELECT unixepoch('2025-06-15 14:30:00');\n\n-- Date to Julian day\nSELECT julianday('2025-06-15');\n\n-- Julian day to date\nSELECT date(2460811.5);\n```\n\n### Group Records by Time Period\n\n```sql\n-- Count orders per month\nSELECT\n    strftime('%Y-%m', order_date) AS month,\n    COUNT(*) AS order_count\nFROM orders\nGROUP BY strftime('%Y-%m', order_date)\nORDER BY month;\n\n-- Count events per day of week\nSELECT\n    CASE strftime('%w', event_date)\n        WHEN '0' THEN 'Sunday'\n        WHEN '1' THEN 'Monday'\n        WHEN '2' THEN 'Tuesday'\n        WHEN '3' THEN 'Wednesday'\n        WHEN '4' THEN 'Thursday'\n        WHEN '5' THEN 'Friday'\n        WHEN '6' THEN 'Saturday'\n    END AS day_name,\n    COUNT(*) AS event_count\nFROM events\nGROUP BY strftime('%w', event_date)\nORDER BY strftime('%w', event_date);\n```\n\n### Filter by Date Range\n\n```sql\n-- Records from the last 30 days\nSELECT * FROM logs\nWHERE timestamp >= datetime('now', '-30 days');\n\n-- Records from a specific month\nSELECT * FROM orders\nWHERE order_date >= '2025-06-01'\n  AND order_date < '2025-07-01';\n\n-- Records from this year\nSELECT * FROM sales\nWHERE date(sale_date) >= date('now', 'start of year');\n```\n\n### Compute Elapsed Time\n\n```sql\n-- Days between two dates\nSELECT julianday('2025-12-25') - julianday('now') AS days_until_christmas;\n\n-- Seconds between two timestamps\nSELECT unixepoch('2025-06-15 18:00:00') - unixepoch('2025-06-15 14:30:00') AS seconds_elapsed;\n-- 12600\n```\n\n## See Also\n\n- [Expressions](/docs/sql-reference/expressions) for using functions in expressions\n- [Data Types](/docs/sql-reference/data-types) for TEXT, REAL, and INTEGER storage classes\n- [Scalar Functions](/docs/sql-reference/functions/scalar) for other built-in functions\n"
  },
  {
    "path": "docs/sql-reference/functions/json.mdx",
    "content": "---\ntitle: JSON Functions\ndescription: Create, extract, modify, and aggregate JSON data\nsidebarTitle: JSON\n---\n\n# JSON Functions\n\nTurso provides a full set of JSON functions compatible with SQLite's JSON1 extension. These functions operate on JSON stored as TEXT or in Turso's internal binary JSON (JSONB) format.\n\nMost functions come in pairs: a `json_*` variant that returns TEXT and a `jsonb_*` variant that returns BLOB in the internal binary format. The JSONB variants are more efficient when the result will be stored or passed to another JSON function rather than returned to the application.\n\n## JSON Path Syntax\n\nMany JSON functions accept a **path** argument that identifies a specific element within a JSON document.\n\n| Syntax | Meaning |\n|--------|---------|\n| `$` | The root element |\n| `$.key` | Object member named `key` |\n| `$[N]` | Array element at index `N` (zero-based) |\n| `$.key1.key2` | Nested object member |\n| `$.key[0]` | First element of an array inside an object member |\n| `$[0].key` | Object member inside the first array element |\n\nPath arguments must begin with `$`. If a path does not match any element, functions generally return `NULL`.\n\n```sql\nSELECT json_extract('{\"a\": {\"b\": [10, 20, 30]}}', '$.a.b[1]');\n-- 20\n```\n\n---\n\n## JSON Creation and Validation\n\n### json\n\nValidates a JSON string and returns it in minified form. If the input is not valid JSON, an error is raised.\n\n```sql\njson(json_text)\n```\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `json_text` | TEXT | A JSON string to validate and minify |\n\n**Returns:** TEXT -- the minified JSON string.\n\n```sql\nSELECT json('  { \"name\": \"Alice\" ,  \"age\": 30 } ');\n-- {\"name\":\"Alice\",\"age\":30}\n```\n\n### jsonb\n\nConverts a JSON string to the internal binary JSON format.\n\n```sql\njsonb(json_text)\n```\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `json_text` | TEXT | A JSON string to convert |\n\n**Returns:** BLOB -- the value in binary JSON format.\n\n```sql\nSELECT typeof(jsonb('{\"a\":1}'));\n-- blob\n```\n\n### json_array / jsonb_array\n\nCreates a JSON array from the arguments.\n\n```sql\njson_array(value1, value2, ...)\njsonb_array(value1, value2, ...)\n```\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `value1, value2, ...` | any | Values to include in the array. SQL NULL becomes JSON `null`. |\n\n**Returns:** TEXT (json_array) or BLOB (jsonb_array) -- a JSON array.\n\n```sql\nSELECT json_array(1, 'hello', NULL, 3.14);\n-- [1,\"hello\",null,3.14]\n\nSELECT json_array();\n-- []\n```\n\n### json_object / jsonb_object\n\nCreates a JSON object from alternating label/value pairs. When called with `*`, expands all columns of the row into label/value pairs using column names as keys.\n\n```sql\njson_object(label1, value1, label2, value2, ...)\njsonb_object(label1, value1, label2, value2, ...)\njson_object(*)\n```\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `label1, label2, ...` | TEXT | Keys for the JSON object. Must be strings. |\n| `value1, value2, ...` | any | Corresponding values. SQL NULL becomes JSON `null`. |\n\n**Returns:** TEXT (json_object) or BLOB (jsonb_object) -- a JSON object.\n\n```sql\nSELECT json_object('name', 'Alice', 'age', 30);\n-- {\"name\":\"Alice\",\"age\":30}\n\nSELECT json_object('items', json_array(1, 2, 3));\n-- {\"items\":[1,2,3]}\n```\n\n### json_quote\n\nConverts a SQL value to its JSON representation.\n\n```sql\njson_quote(value)\n```\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `value` | any | A SQL value to quote as JSON |\n\n**Returns:** TEXT -- the JSON representation of the value.\n\n```sql\nSELECT json_quote('hello');\n-- \"hello\"\n\nSELECT json_quote(42);\n-- 42\n\nSELECT json_quote(NULL);\n-- null\n```\n\n### json_valid\n\nReturns 1 if the argument is well-formed JSON, or 0 otherwise.\n\n```sql\njson_valid(json_text)\n```\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `json_text` | TEXT | A string to check for valid JSON |\n\n**Returns:** INTEGER -- 1 if valid JSON, 0 otherwise.\n\n```sql\nSELECT json_valid('{\"name\":\"Alice\"}');\n-- 1\n\nSELECT json_valid('not json');\n-- 0\n\nSELECT json_valid(NULL);\n-- 0\n```\n\n### json_error_position\n\nReturns the character position of the first syntax error in a JSON string, or 0 if the string is valid JSON.\n\n```sql\njson_error_position(json_text)\n```\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `json_text` | TEXT | A string to check for JSON errors |\n\n**Returns:** INTEGER -- character position of the first error (1-based), or 0 if valid.\n\n```sql\nSELECT json_error_position('{\"a\":1}');\n-- 0\n\nSELECT json_error_position('{\"a\":}');\n-- 6\n```\n\n---\n\n## JSON Extraction\n\n### json_extract / jsonb_extract\n\nExtracts one or more values from a JSON document using path arguments.\n\n```sql\njson_extract(json_text, path)\njson_extract(json_text, path1, path2, ...)\njsonb_extract(json_text, path)\n```\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `json_text` | TEXT or BLOB | A JSON document |\n| `path` | TEXT | One or more JSON path expressions |\n\n**Returns:** With a single path, returns the extracted value using its natural SQL type (INTEGER, REAL, TEXT, or NULL). JSON objects and arrays are returned as TEXT. With multiple paths, returns a JSON array of the extracted values. `jsonb_extract` returns BLOB.\n\n```sql\nSELECT json_extract('{\"name\":\"Alice\",\"age\":30}', '$.name');\n-- Alice\n\nSELECT json_extract('{\"name\":\"Alice\",\"age\":30}', '$.age');\n-- 30\n\n-- Multiple paths return a JSON array\nSELECT json_extract('{\"a\":1,\"b\":2,\"c\":3}', '$.a', '$.c');\n-- [1,3]\n```\n\n### -> operator\n\nExtracts a value from JSON and returns it as JSON. This is a shorthand for `json_extract` that always returns JSON text (objects and arrays remain as JSON, strings are JSON-quoted).\n\n```sql\njson_text -> path\n```\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `json_text` | TEXT or BLOB | A JSON document |\n| `path` | TEXT | A JSON path expression |\n\n**Returns:** TEXT -- the extracted value as JSON.\n\n```sql\nSELECT '{\"name\":\"Alice\"}' -> '$.name';\n-- \"Alice\"\n\nSELECT '{\"items\":[1,2,3]}' -> '$.items';\n-- [1,2,3]\n```\n\n### ->> operator\n\nExtracts a value from JSON and returns it as a SQL value. Strings are unquoted, numbers are returned as INTEGER or REAL, and booleans are returned as integers (0 or 1).\n\n```sql\njson_text ->> path\n```\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `json_text` | TEXT or BLOB | A JSON document |\n| `path` | TEXT | A JSON path expression |\n\n**Returns:** The extracted value as its natural SQL type (TEXT, INTEGER, REAL, or NULL).\n\n```sql\nSELECT '{\"name\":\"Alice\"}' ->> '$.name';\n-- Alice\n\nSELECT '{\"count\":42}' ->> '$.count';\n-- 42\n```\n\n### json_type\n\nReturns the type of a JSON value as a string: `\"null\"`, `\"true\"`, `\"false\"`, `\"integer\"`, `\"real\"`, `\"text\"`, `\"array\"`, or `\"object\"`.\n\n```sql\njson_type(json_text)\njson_type(json_text, path)\n```\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `json_text` | TEXT or BLOB | A JSON document |\n| `path` | TEXT | Optional. A JSON path to inspect. If omitted, inspects the root. |\n\n**Returns:** TEXT -- the JSON type name.\n\n```sql\nSELECT json_type('{\"a\":1}');\n-- object\n\nSELECT json_type('[1, 2, 3]');\n-- array\n\nSELECT json_type('{\"a\": 1}', '$.a');\n-- integer\n\nSELECT json_type('{\"a\": \"hello\"}', '$.a');\n-- text\n```\n\n---\n\n## JSON Modification\n\n### json_insert / jsonb_insert\n\nInserts new values into a JSON document. Existing values are **not** overwritten. If the path already exists, the value is left unchanged.\n\n```sql\njson_insert(json_text, path1, value1, path2, value2, ...)\njsonb_insert(json_text, path1, value1, path2, value2, ...)\n```\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `json_text` | TEXT or BLOB | A JSON document |\n| `path` | TEXT | A JSON path where the value should be inserted |\n| `value` | any | The value to insert |\n\n**Returns:** TEXT (json_insert) or BLOB (jsonb_insert) -- the modified JSON.\n\n```sql\nSELECT json_insert('{\"a\":1}', '$.b', 2);\n-- {\"a\":1,\"b\":2}\n\n-- Existing values are NOT overwritten\nSELECT json_insert('{\"a\":1}', '$.a', 99);\n-- {\"a\":1}\n```\n\n### json_replace / jsonb_replace\n\nReplaces existing values in a JSON document. If the path does not exist, no insertion is made.\n\n```sql\njson_replace(json_text, path1, value1, path2, value2, ...)\njsonb_replace(json_text, path1, value1, path2, value2, ...)\n```\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `json_text` | TEXT or BLOB | A JSON document |\n| `path` | TEXT | A JSON path identifying the value to replace |\n| `value` | any | The replacement value |\n\n**Returns:** TEXT (json_replace) or BLOB (jsonb_replace) -- the modified JSON.\n\n```sql\nSELECT json_replace('{\"a\":1,\"b\":2}', '$.a', 99);\n-- {\"a\":99,\"b\":2}\n\n-- Non-existent paths are ignored\nSELECT json_replace('{\"a\":1}', '$.b', 2);\n-- {\"a\":1}\n```\n\n### json_set / jsonb_set\n\nInserts or replaces values in a JSON document. Combines the behavior of `json_insert` and `json_replace`: if the path exists, the value is replaced; if it does not exist, the value is inserted.\n\n```sql\njson_set(json_text, path1, value1, path2, value2, ...)\njsonb_set(json_text, path1, value1, path2, value2, ...)\n```\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `json_text` | TEXT or BLOB | A JSON document |\n| `path` | TEXT | A JSON path for the value |\n| `value` | any | The value to set |\n\n**Returns:** TEXT (json_set) or BLOB (jsonb_set) -- the modified JSON.\n\n```sql\n-- Replace existing\nSELECT json_set('{\"a\":1}', '$.a', 99);\n-- {\"a\":99}\n\n-- Insert new\nSELECT json_set('{\"a\":1}', '$.b', 2);\n-- {\"a\":1,\"b\":2}\n```\n\n### json_remove / jsonb_remove\n\nRemoves one or more elements from a JSON document.\n\n```sql\njson_remove(json_text, path1, path2, ...)\njsonb_remove(json_text, path1, path2, ...)\n```\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `json_text` | TEXT or BLOB | A JSON document |\n| `path` | TEXT | One or more JSON paths to remove |\n\n**Returns:** TEXT (json_remove) or BLOB (jsonb_remove) -- the modified JSON.\n\n```sql\nSELECT json_remove('{\"a\":1,\"b\":2,\"c\":3}', '$.b');\n-- {\"a\":1,\"c\":3}\n\nSELECT json_remove('[1,2,3,4]', '$[1]');\n-- [1,3,4]\n```\n\n### json_patch / jsonb_patch\n\nApplies an RFC 7396 merge patch to a JSON document. Object members in the patch overwrite members in the target. A `null` value in the patch removes the corresponding member.\n\n```sql\njson_patch(json_text, patch)\njsonb_patch(json_text, patch)\n```\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `json_text` | TEXT or BLOB | The target JSON document |\n| `patch` | TEXT or BLOB | The merge patch to apply |\n\n**Returns:** TEXT (json_patch) or BLOB (jsonb_patch) -- the patched JSON.\n\n```sql\nSELECT json_patch('{\"a\":1,\"b\":2}', '{\"b\":3,\"c\":4}');\n-- {\"a\":1,\"b\":3,\"c\":4}\n\n-- null in the patch removes a key\nSELECT json_patch('{\"a\":1,\"b\":2}', '{\"b\":null}');\n-- {\"a\":1}\n```\n\n### json_pretty\n\nReturns a pretty-printed (indented) representation of a JSON document.\n\n```sql\njson_pretty(json_text)\n```\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `json_text` | TEXT or BLOB | A JSON document |\n\n**Returns:** TEXT -- the formatted JSON string with indentation.\n\n```sql\nSELECT json_pretty('{\"name\":\"Alice\",\"scores\":[90,85,92]}');\n/*\n{\n    \"name\": \"Alice\",\n    \"scores\": [\n        90,\n        85,\n        92\n    ]\n}\n*/\n```\n\n---\n\n## JSON Array Functions\n\n### json_array_length\n\nReturns the number of elements in a JSON array. Returns 0 for an empty array and NULL for non-array JSON values.\n\n```sql\njson_array_length(json_text)\njson_array_length(json_text, path)\n```\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `json_text` | TEXT or BLOB | A JSON document |\n| `path` | TEXT | Optional. A JSON path to an array within the document. |\n\n**Returns:** INTEGER -- the number of elements, or NULL if the value at the path is not an array.\n\n```sql\nSELECT json_array_length('[1, 2, 3, 4]');\n-- 4\n\nSELECT json_array_length('{\"items\": [10, 20]}', '$.items');\n-- 2\n\nSELECT json_array_length('{\"a\": 1}');\n-- NULL\n```\n\n---\n\n## JSON Aggregate Functions\n\n### json_group_array / jsonb_group_array\n\nAggregate function that collects values from a group into a JSON array.\n\n```sql\njson_group_array(value)\njsonb_group_array(value)\n```\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `value` | any | The value to aggregate from each row |\n\n**Returns:** TEXT (json_group_array) or BLOB (jsonb_group_array) -- a JSON array of all values in the group.\n\n```sql\nCREATE TABLE items (category TEXT, name TEXT);\nINSERT INTO items VALUES ('fruit', 'apple'), ('fruit', 'banana'), ('veggie', 'carrot');\n\nSELECT category, json_group_array(name) FROM items GROUP BY category;\n-- fruit  | [\"apple\",\"banana\"]\n-- veggie | [\"carrot\"]\n```\n\n### json_group_object / jsonb_group_object\n\nAggregate function that collects label/value pairs from a group into a JSON object.\n\n```sql\njson_group_object(label, value)\njsonb_group_object(label, value)\n```\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `label` | TEXT | The key for each entry |\n| `value` | any | The value for each entry |\n\n**Returns:** TEXT (json_group_object) or BLOB (jsonb_group_object) -- a JSON object.\n\n```sql\nCREATE TABLE settings (key TEXT, value TEXT);\nINSERT INTO settings VALUES ('theme', 'dark'), ('lang', 'en');\n\nSELECT json_group_object(key, value) FROM settings;\n-- {\"theme\":\"dark\",\"lang\":\"en\"}\n```\n\n---\n\n## JSON Table-Valued Functions\n\n### json_each\n\nA table-valued function that walks the top-level elements of a JSON array or object, returning one row per element.\n\n```sql\nSELECT * FROM json_each(json_text);\nSELECT * FROM json_each(json_text, path);\n```\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `json_text` | TEXT or BLOB | A JSON document |\n| `path` | TEXT | Optional. A JSON path to the array or object to iterate. Defaults to `$`. |\n\n**Output columns:**\n\n| Column | Type | Description |\n|--------|------|-------------|\n| `key` | TEXT or INTEGER | Object key (TEXT) or array index (INTEGER) |\n| `value` | any | The value of the element, as JSON text for objects/arrays or as a SQL value for primitives |\n| `type` | TEXT | JSON type: `null`, `true`, `false`, `integer`, `real`, `text`, `array`, or `object` |\n| `atom` | any | The SQL value for primitives (NULL for arrays and objects) |\n| `id` | INTEGER | A sequential identifier for the element |\n| `parent` | INTEGER | The id of the parent element (NULL for top-level) |\n| `fullkey` | TEXT | The full JSON path to this element |\n| `path` | TEXT | The JSON path to the parent of this element |\n\n```sql\nSELECT key, value, type FROM json_each('[10, \"hello\", null]');\n-- 0 | 10    | integer\n-- 1 | hello | text\n-- 2 | null  | null\n\nSELECT key, value FROM json_each('{\"a\":1, \"b\":2}');\n-- a | 1\n-- b | 2\n\n-- With a path\nSELECT key, value FROM json_each('{\"data\": [1, 2, 3]}', '$.data');\n-- 0 | 1\n-- 1 | 2\n-- 2 | 3\n```\n\n### json_tree\n\n<Warning>\n`json_tree` has partial support. Some advanced traversal features may not work as expected.\n</Warning>\n\nA table-valued function that recursively walks a JSON document, returning one row for every element at every level of nesting.\n\n```sql\nSELECT * FROM json_tree(json_text);\nSELECT * FROM json_tree(json_text, path);\n```\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `json_text` | TEXT or BLOB | A JSON document |\n| `path` | TEXT | Optional. A JSON path to the subtree to walk. Defaults to `$`. |\n\n**Output columns:** Same as `json_each`.\n\n```sql\nSELECT key, value, type, path FROM json_tree('{\"a\": [1, 2]}');\n-- NULL | {\"a\":[1,2]} | object  | $\n-- a    | [1,2]       | array   | $\n-- 0    | 1           | integer | $.a\n-- 1    | 2           | integer | $.a\n```\n\n---\n\n## Practical Examples\n\n### Storing and querying JSON data\n\n```sql\nCREATE TABLE events (id INTEGER PRIMARY KEY, data TEXT);\nINSERT INTO events VALUES (1, '{\"type\":\"click\",\"x\":100,\"y\":200}');\nINSERT INTO events VALUES (2, '{\"type\":\"scroll\",\"offset\":500}');\n\n-- Extract specific fields\nSELECT id, data ->> '$.type' AS event_type FROM events;\n-- 1 | click\n-- 2 | scroll\n\n-- Filter by JSON value\nSELECT * FROM events WHERE data ->> '$.type' = 'click';\n```\n\n### Modifying JSON in place\n\n```sql\nUPDATE events\nSET data = json_set(data, '$.timestamp', '2025-01-15T10:30:00Z')\nWHERE id = 1;\n\nSELECT data FROM events WHERE id = 1;\n-- {\"type\":\"click\",\"x\":100,\"y\":200,\"timestamp\":\"2025-01-15T10:30:00Z\"}\n```\n\n### Building JSON from relational data\n\n```sql\nCREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT);\nINSERT INTO users VALUES (1, 'Alice', 'alice@example.com');\nINSERT INTO users VALUES (2, 'Bob', 'bob@example.com');\n\nSELECT json_object('users', json_group_array(\n    json_object('id', id, 'name', name, 'email', email)\n)) FROM users;\n-- {\"users\":[{\"id\":1,\"name\":\"Alice\",\"email\":\"alice@example.com\"},{\"id\":2,\"name\":\"Bob\",\"email\":\"bob@example.com\"}]}\n```\n\n### Flattening JSON arrays with json_each\n\n```sql\nCREATE TABLE orders (id INTEGER PRIMARY KEY, items TEXT);\nINSERT INTO orders VALUES (1, '[\"widget\",\"gadget\",\"gizmo\"]');\nINSERT INTO orders VALUES (2, '[\"sprocket\"]');\n\n-- Expand each order's items into individual rows\nSELECT orders.id, each.value AS item\nFROM orders, json_each(orders.items) AS each;\n-- 1 | widget\n-- 1 | gadget\n-- 1 | gizmo\n-- 2 | sprocket\n```\n\n## See Also\n\n- [Data Types](/docs/sql-reference/data-types) for how JSON values map to SQL types\n- [Expressions](/docs/sql-reference/expressions) for using JSON operators in expressions\n"
  },
  {
    "path": "docs/sql-reference/functions/scalar.mdx",
    "content": "---\ntitle: Scalar Functions\ndescription: Built-in scalar functions for string manipulation, math, type inspection, and more\nsidebarTitle: Scalar Functions\n---\n\n# Scalar Functions\n\nScalar functions accept one or more arguments and return a single value. They can be used anywhere an expression is valid: SELECT columns, WHERE conditions, ORDER BY, GROUP BY, HAVING, CHECK constraints, and DEFAULT values.\n\n## Function Reference\n\n### Math Functions\n\n| Function | Description |\n|----------|-------------|\n| `abs(X)` | Absolute value of X. Returns INTEGER if X is INTEGER, REAL if X is REAL, NULL if X is NULL |\n| `max(X, Y, ...)` | Returns the argument with the maximum value |\n| `min(X, Y, ...)` | Returns the argument with the minimum value |\n| `random()` | Returns a random 64-bit signed integer |\n| `round(X)` | Rounds X to the nearest integer |\n| `round(X, Y)` | Rounds X to Y decimal places |\n| `sign(X)` | Returns -1, 0, or 1 for negative, zero, or positive X |\n\n### String Functions\n\n| Function | Description |\n|----------|-------------|\n| `char(X1, X2, ..., XN)` | Returns a string composed of characters with Unicode code points X1 through XN |\n| `concat(X, ...)` | Concatenates all arguments as strings. NULL arguments are skipped |\n| `concat_ws(SEP, X, ...)` | Concatenates arguments with separator SEP. NULL arguments are skipped |\n| `format(FORMAT, ...)` | Returns a formatted string using printf-style format specifiers |\n| `hex(X)` | Returns the uppercase hexadecimal representation of X |\n| `instr(X, Y)` | Returns the 1-based position of the first occurrence of Y in X, or 0 if not found |\n| `length(X)` | Returns the string length in characters, or blob length in bytes |\n| `lower(X)` | Returns a lowercase copy of string X |\n| `ltrim(X)` | Removes leading whitespace from X |\n| `ltrim(X, Y)` | Removes leading characters found in Y from X |\n| `octet_length(X)` | Returns the length of X in bytes |\n| `printf(FORMAT, ...)` | Alias for `format()`. Returns a formatted string |\n| `quote(X)` | Returns the SQL literal representation of X |\n| `replace(X, Y, Z)` | Returns X with every occurrence of Y replaced by Z |\n| `rtrim(X)` | Removes trailing whitespace from X |\n| `rtrim(X, Y)` | Removes trailing characters found in Y from X |\n| `soundex(X)` | Returns the Soundex encoding of string X |\n| `substr(X, Y)` | Returns the substring of X starting at position Y (1-based) |\n| `substr(X, Y, Z)` | Returns Z characters from X starting at position Y |\n| `substring(X, Y)` | Alias for `substr(X, Y)` |\n| `substring(X, Y, Z)` | Alias for `substr(X, Y, Z)` |\n| `trim(X)` | Removes leading and trailing whitespace from X |\n| `trim(X, Y)` | Removes leading and trailing characters found in Y from X |\n| `unhex(X)` | Converts hexadecimal string X to a blob. Returns NULL if X contains non-hex characters |\n| `unhex(X, Y)` | Like `unhex(X)`, but characters in Y are silently ignored in X |\n| `unicode(X)` | Returns the Unicode code point of the first character of string X |\n| `upper(X)` | Returns an uppercase copy of string X |\n| `zeroblob(N)` | Returns a blob consisting of N zero bytes |\n\n### Conditional Functions\n\n| Function | Description |\n|----------|-------------|\n| `coalesce(X, Y, ...)` | Returns the first non-NULL argument. Returns NULL if all arguments are NULL |\n| `ifnull(X, Y)` | Returns X if X is not NULL, otherwise returns Y. Equivalent to `coalesce(X, Y)` |\n| `iif(X, Y, Z)` | Returns Y if X is true (non-zero), otherwise returns Z |\n| `if(X, Y, Z)` | Alias for `iif(X, Y, Z)` |\n| `nullif(X, Y)` | Returns NULL if X equals Y, otherwise returns X |\n\n### Type Functions\n\n| Function | Description |\n|----------|-------------|\n| `typeof(X)` | Returns the type of X as a string: `\"null\"`, `\"integer\"`, `\"real\"`, `\"text\"`, or `\"blob\"` |\n\n### Pattern Matching Functions\n\n| Function | Description |\n|----------|-------------|\n| `glob(X, Y)` | Returns 1 if string Y matches glob pattern X, 0 otherwise. Case-sensitive |\n| `like(X, Y)` | Returns 1 if string Y matches LIKE pattern X, 0 otherwise. Case-insensitive for ASCII |\n| `like(X, Y, Z)` | Like `like(X, Y)` but uses Z as the escape character |\n\n### Optimizer Hints\n\n| Function | Description |\n|----------|-------------|\n| `likelihood(X, Y)` | Returns X unchanged. Hints to the query planner that X is true with probability Y (0.0 to 1.0) |\n| `likely(X)` | Returns X unchanged. Equivalent to `likelihood(X, 0.9375)` |\n| `unlikely(X)` | Returns X unchanged. Equivalent to `likelihood(X, 0.0625)` |\n\n### Blob Functions\n\n| Function | Description |\n|----------|-------------|\n| `hex(X)` | Returns the uppercase hexadecimal representation of blob or integer X |\n| `randomblob(N)` | Returns a blob of N bytes filled with pseudo-random data |\n| `unhex(X)` | Converts hexadecimal string X to a blob |\n| `zeroblob(N)` | Returns a blob of N zero bytes |\n\n### System Functions\n\n| Function | Description |\n|----------|-------------|\n| `last_insert_rowid()` | Returns the rowid of the most recent successful INSERT on the same database connection |\n| `changes()` | Returns the number of rows modified by the most recent INSERT, UPDATE, or DELETE |\n| `total_changes()` | Returns the total number of rows modified since the database connection was opened |\n| `sqlite_version()` | Returns the version string `\"3.42.0\"` |\n| `sqlite_source_id()` | Returns the source identifier string for the SQLite-compatible engine |\n| `load_extension(X)` | Loads a Turso-native extension from the shared library at path X |\n\n## Detailed Descriptions and Examples\n\n### abs(X)\n\nReturns the absolute value of X. The return type matches the input: INTEGER for integer inputs, REAL for floating-point inputs. Returns NULL if X is NULL. Returns INTEGER for a text value that looks like an integer.\n\n```sql\nSELECT abs(-42);       -- 42\nSELECT abs(3.14);      -- 3.14\nSELECT abs(0);         -- 0\nSELECT abs(NULL);      -- NULL\n```\n\n### char(X1, X2, ..., XN)\n\nReturns a string composed of characters having the Unicode code points X1 through XN. Arguments that are not valid code points are replaced with the Unicode replacement character (U+FFFD).\n\n```sql\nSELECT char(72, 101, 108, 108, 111);   -- 'Hello'\nSELECT char(9731);                       -- snowman character\nSELECT hex(char(0));                     -- '00'\n```\n\n### coalesce(X, Y, ...)\n\nReturns the first argument that is not NULL. If all arguments are NULL, returns NULL. Requires at least two arguments.\n\n```sql\nSELECT coalesce(NULL, NULL, 'fallback');   -- 'fallback'\nSELECT coalesce(1, 2, 3);                  -- 1\nSELECT coalesce(NULL, 42);                 -- 42\n```\n\n### concat(X, ...) and concat_ws(SEP, X, ...)\n\n`concat` joins all arguments as strings, skipping NULLs. `concat_ws` inserts the separator between non-NULL arguments.\n\n```sql\nSELECT concat('Hello', ' ', 'World');          -- 'Hello World'\nSELECT concat('a', NULL, 'b');                 -- 'ab'\nSELECT concat_ws(', ', 'Alice', 'Bob', NULL, 'Carol');  -- 'Alice, Bob, Carol'\nSELECT concat_ws('-', 2024, 1, 15);            -- '2024-1-15'\n```\n\n### format(FORMAT, ...) and printf(FORMAT, ...)\n\nReturns a formatted string using printf-style format specifiers. `printf` is an alias for `format`.\n\n| Specifier | Description |\n|-----------|-------------|\n| `%d` | Signed integer |\n| `%f` | Floating-point |\n| `%s` | String |\n| `%x` | Lowercase hexadecimal integer |\n| `%X` | Uppercase hexadecimal integer |\n| `%o` | Octal integer |\n| `%e` | Scientific notation |\n| `%g` | General floating-point (shortest representation) |\n| `%%` | Literal percent sign |\n\n```sql\nSELECT format('Hello, %s! You are #%d.', 'Alice', 1);\n-- 'Hello, Alice! You are #1.'\n\nSELECT printf('%.2f%%', 99.5);\n-- '99.50%'\n\nSELECT format('0x%08X', 255);\n-- '0x000000FF'\n```\n\n### glob(X, Y)\n\nReturns 1 if string Y matches the glob pattern X, and 0 otherwise. Glob matching is case-sensitive and uses `*` for any sequence of characters, `?` for any single character, and `[...]` for character classes.\n\n```sql\nSELECT glob('*.txt', 'readme.txt');   -- 1\nSELECT glob('*.TXT', 'readme.txt');   -- 0 (case-sensitive)\nSELECT glob('H?llo', 'Hello');        -- 1\n```\n\n<Info>\nThe `glob(X, Y)` function is the functional form of the `Y GLOB X` operator. Note the reversed argument order compared to the operator syntax.\n</Info>\n\n### hex(X) and unhex(X)\n\n`hex` returns the uppercase hexadecimal representation of its argument. For text, it returns the hex encoding of the UTF-8 bytes. For blobs, it encodes each byte. For integers, it returns the hex of the value.\n\n`unhex` converts a hexadecimal string back to a blob. Returns NULL if the input contains non-hex characters, unless a second argument specifies characters to ignore.\n\n```sql\nSELECT hex('ABC');             -- '414243'\nSELECT hex(255);               -- 'FF'\nSELECT hex(x'CAFE');           -- 'CAFE'\n\nSELECT unhex('48454C4C4F');    -- x'48454C4C4F' (blob for 'HELLO')\nSELECT unhex('48-45-4C', '-'); -- x'48454C' (ignoring dashes)\nSELECT unhex('ZZZZ');          -- NULL (invalid hex)\n```\n\n### iif(X, Y, Z) and if(X, Y, Z)\n\nReturns Y if X is true (non-zero and non-NULL), otherwise returns Z. `if` is an alias for `iif`.\n\n```sql\nSELECT iif(1 > 0, 'yes', 'no');       -- 'yes'\nSELECT iif(NULL, 'yes', 'no');         -- 'no'\nSELECT if(10 > 5, 'big', 'small');     -- 'big'\n```\n\n### instr(X, Y)\n\nReturns the 1-based position of the first occurrence of string Y in string X. Returns 0 if Y is not found in X. If either argument is NULL, returns NULL.\n\n```sql\nSELECT instr('Hello World', 'World');   -- 7\nSELECT instr('Hello World', 'xyz');     -- 0\nSELECT instr('abcabc', 'bc');           -- 2\n```\n\n### length(X) and octet_length(X)\n\n`length` returns the number of characters in a text value, or the number of bytes in a blob value. For NULL, returns NULL. For numeric values, returns the length of the text representation.\n\n`octet_length` always returns the length in bytes, regardless of type.\n\n```sql\nSELECT length('Hello');          -- 5\nSELECT length(x'AABBCC');        -- 3 (bytes for blob)\nSELECT length(12345);            -- 5 (text representation)\nSELECT octet_length('Hello');    -- 5 (ASCII, 1 byte per char)\n```\n\n### like(X, Y) and like(X, Y, Z)\n\nReturns 1 if string Y matches LIKE pattern X, and 0 otherwise. `%` matches any sequence of characters, `_` matches any single character. Matching is case-insensitive for ASCII characters. The optional third argument Z specifies an escape character.\n\n```sql\nSELECT like('%ello%', 'Hello World');     -- 1\nSELECT like('H_llo', 'Hello');            -- 1\nSELECT like('10\\%%', '10% discount', '\\'); -- 1 (escaped %)\n```\n\n<Info>\nThe `like(X, Y)` function is the functional form of the `Y LIKE X` operator. Note the reversed argument order compared to the operator syntax.\n</Info>\n\n### lower(X), upper(X)\n\n`lower` returns a copy of string X with all ASCII characters converted to lowercase. `upper` converts to uppercase.\n\n```sql\nSELECT lower('Hello World');   -- 'hello world'\nSELECT upper('Hello World');   -- 'HELLO WORLD'\n```\n\n### ltrim(X), rtrim(X), trim(X)\n\nThese functions remove characters from the ends of a string. Without a second argument, they remove whitespace. With a second argument Y, they remove any characters present in the string Y.\n\n```sql\n-- Whitespace trimming\nSELECT ltrim('   Hello');          -- 'Hello'\nSELECT rtrim('Hello   ');          -- 'Hello'\nSELECT trim('   Hello   ');        -- 'Hello'\n\n-- Character trimming\nSELECT ltrim('xxxHello', 'x');     -- 'Hello'\nSELECT rtrim('Helloyyy', 'y');     -- 'Hello'\nSELECT trim('***Hello***', '*');   -- 'Hello'\nSELECT trim('abcHelloabc', 'abc'); -- 'Hello' (removes any of a, b, or c)\n```\n\n### max(X, Y, ...) and min(X, Y, ...)\n\nThe multi-argument forms of `max` and `min` return the largest or smallest argument, respectively. Arguments are compared using the standard SQLite comparison rules. If any argument is NULL, the result is NULL.\n\n```sql\nSELECT max(1, 5, 3);          -- 5\nSELECT min(1, 5, 3);          -- 1\nSELECT max('apple', 'banana'); -- 'banana'\nSELECT min(10, NULL, 3);      -- NULL\n```\n\n<Info>\nThe multi-argument `max()` and `min()` are scalar functions. When called with a single argument inside an aggregate query (e.g., `SELECT max(salary) FROM employees`), they act as [aggregate functions](/docs/sql-reference/functions/aggregate).\n</Info>\n\n### nullif(X, Y)\n\nReturns NULL if X equals Y, otherwise returns X. This is useful for converting sentinel values to NULL.\n\n```sql\nSELECT nullif(0, 0);        -- NULL\nSELECT nullif(5, 0);        -- 5\nSELECT nullif('N/A', 'N/A'); -- NULL\nSELECT nullif('hello', '');  -- 'hello'\n```\n\n### quote(X)\n\nReturns the text of an SQL literal that represents the value X. Strings are enclosed in single quotes with escaping. BLOBs are encoded as hex literals. NULL returns the string `'NULL'`. Numbers are returned as-is.\n\n```sql\nSELECT quote('it''s');       -- '''it''s'''\nSELECT quote(42);            -- '42'\nSELECT quote(NULL);          -- 'NULL'\nSELECT quote(x'CAFE');       -- 'X''CAFE'''\n```\n\n### random() and randomblob(N)\n\n`random` returns a pseudo-random 64-bit signed integer. `randomblob` returns a blob of N pseudo-random bytes.\n\n```sql\nSELECT random();           -- e.g., -4520312828827489743\nSELECT hex(randomblob(4)); -- e.g., 'A1B2C3D4' (4 random bytes)\nSELECT abs(random()) % 100; -- random number between 0 and 99\n```\n\n### replace(X, Y, Z)\n\nReturns a copy of string X with every occurrence of string Y replaced by string Z. If Y is empty, X is returned unchanged.\n\n```sql\nSELECT replace('Hello World', 'World', 'Turso');   -- 'Hello Turso'\nSELECT replace('aabbcc', 'bb', 'XX');               -- 'aaXXcc'\nSELECT replace('2024-01-15', '-', '/');              -- '2024/01/15'\n```\n\n### round(X) and round(X, Y)\n\nRounds X to Y decimal places. If Y is omitted, it defaults to 0. The return type is always REAL.\n\n```sql\nSELECT round(3.7);         -- 4.0\nSELECT round(3.14159, 2);  -- 3.14\nSELECT round(2.5);         -- 3.0\nSELECT round(-2.5);        -- -3.0\n```\n\n### sign(X)\n\nReturns -1 for negative values, 0 for zero, and 1 for positive values. Returns NULL if X is NULL.\n\n```sql\nSELECT sign(-42);    -- -1\nSELECT sign(0);      -- 0\nSELECT sign(3.14);   -- 1\nSELECT sign(NULL);   -- NULL\n```\n\n### substr(X, Y) and substr(X, Y, Z)\n\nReturns a substring of X starting at the Y-th character (1-based). If Z is provided, the substring is at most Z characters long. Negative Y counts from the end of the string. `substring` is an alias.\n\n```sql\nSELECT substr('Hello World', 7);       -- 'World'\nSELECT substr('Hello World', 1, 5);    -- 'Hello'\nSELECT substr('Hello World', -5);      -- 'World'\nSELECT substring('Hello', 2, 3);       -- 'ell'\n```\n\n### typeof(X)\n\nReturns the storage class of X as a lowercase string: `\"null\"`, `\"integer\"`, `\"real\"`, `\"text\"`, or `\"blob\"`.\n\n```sql\nSELECT typeof(42);         -- 'integer'\nSELECT typeof(3.14);       -- 'real'\nSELECT typeof('hello');    -- 'text'\nSELECT typeof(NULL);       -- 'null'\nSELECT typeof(x'CAFE');    -- 'blob'\nSELECT typeof(1 + 1.0);   -- 'real'\n```\n\n### unicode(X)\n\nReturns the Unicode code point of the first character of string X. Returns NULL if X is NULL or an empty string.\n\n```sql\nSELECT unicode('A');     -- 65\nSELECT unicode('Hello'); -- 72 (code point of 'H')\n```\n\n### soundex(X)\n\nReturns the Soundex encoding of string X as a four-character code. Soundex encodes a string based on how it sounds in English, which is useful for fuzzy name matching.\n\n```sql\nSELECT soundex('Robert');    -- 'R163'\nSELECT soundex('Rupert');    -- 'R163'\nSELECT soundex('Smith');     -- 'S530'\nSELECT soundex('Smythe');    -- 'S530'\n```\n\n### zeroblob(N)\n\nReturns a blob consisting of N zero bytes (0x00). Useful for pre-allocating blob storage.\n\n```sql\nSELECT length(zeroblob(10));    -- 10\nSELECT hex(zeroblob(4));        -- '00000000'\n```\n\n### last_insert_rowid()\n\nReturns the rowid of the most recent successful INSERT on the current database connection. Returns 0 if no INSERT has been performed.\n\n```sql\nCREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT);\nINSERT INTO users (name) VALUES ('Alice');\nSELECT last_insert_rowid();  -- 1\nINSERT INTO users (name) VALUES ('Bob');\nSELECT last_insert_rowid();  -- 2\n```\n\n### changes() and total_changes()\n\n`changes` returns the number of rows modified by the most recent INSERT, UPDATE, or DELETE statement. `total_changes` returns the total number of rows modified since the database connection was opened.\n\n```sql\nCREATE TABLE t (x INTEGER);\nINSERT INTO t VALUES (1), (2), (3);\nSELECT changes();        -- 3\n\nDELETE FROM t WHERE x > 1;\nSELECT changes();        -- 2\nSELECT total_changes();  -- 5 (3 inserted + 2 deleted)\n```\n\n### sqlite_version()\n\nReturns the SQLite-compatible version string.\n\n```sql\nSELECT sqlite_version();  -- '3.42.0'\n```\n\n### load_extension(X)\n\nLoads a Turso-native extension from the shared library at path X.\n\n```sql\nSELECT load_extension('./my_extension');\n```\n\n<Info>\nExtension loading must be enabled on the database connection. See the [Extensions documentation](/docs/extensions) for details on building and loading extensions.\n</Info>\n\n## See Also\n\n- [Aggregate Functions](/docs/sql-reference/functions/aggregate) for functions that operate across groups of rows\n- [Expressions](/docs/sql-reference/expressions) for operator syntax, CAST, CASE, and subqueries\n- [Data Types](/docs/sql-reference/data-types) for storage classes and type affinity\n- [SELECT](/docs/sql-reference/statements/select) for using functions in queries\n"
  },
  {
    "path": "docs/sql-reference/functions/window.mdx",
    "content": "---\ntitle: Window Functions\ndescription: Perform calculations across sets of rows related to the current row without collapsing them\nsidebarTitle: Window\n---\n\n# Window Functions\n\nWindow functions perform calculations across a set of rows that are related to the current row. Unlike aggregate functions with GROUP BY, window functions do not collapse rows into a single output row. Every input row produces a corresponding output row, with the window function result appended.\n\n<Info>\nTurso supports aggregate functions used as window functions with the default frame definition. Custom frame specifications (ROWS, RANGE, or GROUPS with explicit bounds) and dedicated window functions (row_number, rank, dense_rank, ntile, lag, lead, first_value, last_value, nth_value) are not yet supported.\n</Info>\n\n## Syntax\n\n```sql\naggregate_function(expression) OVER (\n    [PARTITION BY expression [, ...]]\n    [ORDER BY expression [ASC | DESC] [, ...]]\n)\n```\n\n| Clause | Description |\n|--------|-------------|\n| `aggregate_function` | Any supported aggregate function: `count`, `sum`, `avg`, `min`, `max`, `total`, `group_concat` |\n| `OVER (...)` | Defines the window over which the function operates |\n| `PARTITION BY` | Divides the result set into partitions. The function is applied independently within each partition. If omitted, the entire result set is one partition |\n| `ORDER BY` | Defines the order of rows within each partition. This determines which rows are included in the frame for each calculation |\n\n### Default Frame\n\nWhen ORDER BY is specified, the default frame is:\n\n```\nRANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW\n```\n\nThis means the function considers all rows from the start of the partition up to and including the current row (and any rows with equal ORDER BY values, since the frame mode is RANGE).\n\nWhen ORDER BY is omitted, the default frame covers the entire partition.\n\n## Supported Aggregate Functions as Window Functions\n\nAny aggregate function can be used as a window function by adding an OVER clause.\n\n| Function | Description |\n|----------|-------------|\n| `count(*)` | Number of rows in the frame |\n| `count(expression)` | Number of non-NULL values in the frame |\n| `sum(expression)` | Sum of non-NULL values in the frame |\n| `avg(expression)` | Average of non-NULL values in the frame |\n| `min(expression)` | Minimum value in the frame |\n| `max(expression)` | Maximum value in the frame |\n| `total(expression)` | Sum as REAL (returns 0.0 for empty frames instead of NULL) |\n| `group_concat(expression, separator)` | Concatenation of values in the frame |\n\n## PARTITION BY\n\nPARTITION BY divides the rows into groups. The window function resets and recalculates independently for each partition.\n\n```sql\nSELECT\n    department,\n    name,\n    salary,\n    SUM(salary) OVER (PARTITION BY department) AS dept_total\nFROM employees;\n```\n\n| department | name | salary | dept_total |\n|------------|------|--------|------------|\n| Engineering | Alice | 90000 | 250000 |\n| Engineering | Bob | 85000 | 250000 |\n| Engineering | Carol | 75000 | 250000 |\n| Marketing | Dave | 70000 | 130000 |\n| Marketing | Eve | 60000 | 130000 |\n\nWithout PARTITION BY, the function treats the entire result set as one partition:\n\n```sql\nSELECT\n    name,\n    salary,\n    SUM(salary) OVER () AS company_total\nFROM employees;\n```\n\n## ORDER BY\n\nORDER BY within the OVER clause determines row ordering within each partition. Combined with the default frame (RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW), this produces running calculations.\n\n```sql\nSELECT\n    name,\n    salary,\n    SUM(salary) OVER (ORDER BY salary) AS running_total\nFROM employees;\n```\n\n| name | salary | running_total |\n|------|--------|---------------|\n| Eve | 60000 | 60000 |\n| Dave | 70000 | 130000 |\n| Carol | 75000 | 205000 |\n| Bob | 85000 | 290000 |\n| Alice | 90000 | 380000 |\n\n### PARTITION BY with ORDER BY\n\nUse both clauses together for running calculations within groups:\n\n```sql\nSELECT\n    department,\n    name,\n    salary,\n    SUM(salary) OVER (\n        PARTITION BY department\n        ORDER BY salary\n    ) AS dept_running_total\nFROM employees;\n```\n\n| department | name | salary | dept_running_total |\n|------------|------|--------|--------------------|\n| Engineering | Carol | 75000 | 75000 |\n| Engineering | Bob | 85000 | 160000 |\n| Engineering | Alice | 90000 | 250000 |\n| Marketing | Eve | 60000 | 60000 |\n| Marketing | Dave | 70000 | 130000 |\n\n## Named Windows\n\nThe WINDOW clause defines a reusable window specification that can be referenced by multiple window functions in the same query. This avoids repeating the same OVER definition.\n\n```sql\nSELECT\n    department,\n    name,\n    salary,\n    SUM(salary) OVER w AS running_total,\n    AVG(salary) OVER w AS running_avg,\n    COUNT(*) OVER w AS running_count\nFROM employees\nWINDOW w AS (PARTITION BY department ORDER BY salary)\nORDER BY department, salary;\n```\n\nMultiple named windows can be defined:\n\n```sql\nSELECT\n    department,\n    name,\n    salary,\n    SUM(salary) OVER dept AS dept_total,\n    SUM(salary) OVER company AS company_total\nFROM employees\nWINDOW\n    dept AS (PARTITION BY department),\n    company AS ()\nORDER BY department, name;\n```\n\n## Examples\n\n### Running Total\n\n```sql\nSELECT\n    order_date,\n    amount,\n    SUM(amount) OVER (ORDER BY order_date) AS running_total\nFROM orders;\n```\n\n### Count per Group\n\n```sql\n-- Show each employee alongside their department headcount\nSELECT\n    name,\n    department,\n    COUNT(*) OVER (PARTITION BY department) AS dept_size\nFROM employees\nORDER BY department, name;\n```\n\n### Running Average\n\n```sql\nSELECT\n    date,\n    temperature,\n    AVG(temperature) OVER (ORDER BY date) AS running_avg_temp\nFROM weather_readings\nORDER BY date;\n```\n\n### Percentage of Total\n\n```sql\nSELECT\n    product_name,\n    revenue,\n    ROUND(100.0 * revenue / SUM(revenue) OVER (), 2) AS pct_of_total\nFROM products\nORDER BY revenue DESC;\n```\n\n### Multiple Window Functions in One Query\n\n```sql\nSELECT\n    department,\n    name,\n    salary,\n    MIN(salary) OVER (PARTITION BY department) AS dept_min,\n    MAX(salary) OVER (PARTITION BY department) AS dept_max,\n    AVG(salary) OVER (PARTITION BY department) AS dept_avg,\n    salary - AVG(salary) OVER (PARTITION BY department) AS diff_from_avg\nFROM employees\nORDER BY department, salary DESC;\n```\n\n### Group Concatenation over a Window\n\n```sql\nSELECT\n    department,\n    name,\n    GROUP_CONCAT(name, ', ') OVER (PARTITION BY department ORDER BY name) AS names_so_far\nFROM employees;\n```\n\n## Limitations\n\nThe following window function features are not yet supported in Turso:\n\n| Feature | Status |\n|---------|--------|\n| `row_number()` | Not supported |\n| `rank()` | Not supported |\n| `dense_rank()` | Not supported |\n| `ntile(N)` | Not supported |\n| `lag(expr, offset, default)` | Not supported |\n| `lead(expr, offset, default)` | Not supported |\n| `first_value(expr)` | Not supported |\n| `last_value(expr)` | Not supported |\n| `nth_value(expr, N)` | Not supported |\n| `cume_dist()` | Not supported |\n| `percent_rank()` | Not supported |\n| Custom frame: `ROWS BETWEEN ...` | Not supported |\n| Custom frame: `RANGE BETWEEN ... AND ...` | Not supported |\n| Custom frame: `GROUPS BETWEEN ...` | Not supported |\n| `EXCLUDE` clause | Not supported |\n| `FILTER (WHERE ...)` on window functions | Not supported |\n\n## See Also\n\n- [SELECT](/docs/sql-reference/statements/select) for the full SELECT syntax including the WINDOW clause\n- [Aggregate Functions](/docs/sql-reference/functions/aggregate) for aggregate function reference\n- [Expressions](/docs/sql-reference/expressions) for using window function results in expressions\n"
  },
  {
    "path": "docs/sql-reference/preview.sh",
    "content": "#!/usr/bin/env bash\n# Builds an mdBook preview from the .mdx source files.\n# Usage: ./preview.sh        (builds and serves at localhost:3000)\n#        ./preview.sh build   (builds only, output in ./book/)\nset -euo pipefail\ncd \"$(dirname \"$0\")\"\n\nif ! command -v mdbook &>/dev/null; then\n  echo \"mdbook not found. Install with: cargo install mdbook\"\n  exit 1\nfi\n\n# Clean and create src directory for mdbook\nrm -rf src\nmkdir -p src/statements src/functions src/cli\n\n# Convert .mdx files to .md:\n#  - Strip YAML frontmatter\n#  - Transform Mintlify callouts to blockquotes\n#  - Rewrite /docs/sql-reference/ links to relative .md paths\nconvert() {\n  local in=\"$1\" out=\"$2\"\n  # Determine depth: files in subdirs need ../ prefix for top-level pages\n  local depth=\"\"\n  case \"$in\" in\n    statements/*|functions/*|cli/*) depth=\"../\" ;;\n  esac\n  sed -E \\\n    -e 's/^<Info>$/> **Note**/g' \\\n    -e 's/^<Warning>$/> **Warning**/g' \\\n    -e 's/^<Note>$/> **Note**/g' \\\n    -e 's/^<\\/(Info|Warning|Note)>$//g' \\\n    -e '/^---$/,/^---$/d' \\\n    -e \"s|\\(/docs/sql-reference/([^)#]+)(#[^)]+)?\\)|(${depth}\\1.md\\2)|g\" \\\n    \"$in\" > \"$out\"\n}\n\nfor f in *.mdx; do\n  convert \"$f\" \"src/${f%.mdx}.md\"\ndone\nfor f in statements/*.mdx; do\n  convert \"$f\" \"src/${f%.mdx}.md\"\ndone\nfor f in functions/*.mdx; do\n  convert \"$f\" \"src/${f%.mdx}.md\"\ndone\nfor f in cli/*.mdx; do\n  convert \"$f\" \"src/${f%.mdx}.md\"\ndone\n\n# Generate SUMMARY.md\ncat > src/SUMMARY.md << 'EOF'\n# Summary\n\n# CLI\n\n- [Getting Started](cli/getting-started.md)\n- [Command-Line Options](cli/command-line-options.md)\n- [Shell Commands](cli/shell-commands.md)\n\n# SQL Language\n\n- [Data Types](data-types.md)\n- [Expressions](expressions.md)\n\n# Statements\n\n- [SELECT](statements/select.md)\n- [INSERT](statements/insert.md)\n- [UPDATE](statements/update.md)\n- [DELETE](statements/delete.md)\n- [REPLACE](statements/replace.md)\n- [UPSERT](statements/upsert.md)\n- [CREATE TABLE](statements/create-table.md)\n- [ALTER TABLE](statements/alter-table.md)\n- [DROP TABLE](statements/drop-table.md)\n- [CREATE INDEX](statements/create-index.md)\n- [DROP INDEX](statements/drop-index.md)\n- [CREATE VIEW](statements/create-view.md)\n- [CREATE MATERIALIZED VIEW](statements/create-materialized-view.md)\n- [DROP VIEW](statements/drop-view.md)\n- [CREATE TRIGGER](statements/create-trigger.md)\n- [DROP TRIGGER](statements/drop-trigger.md)\n- [CREATE VIRTUAL TABLE](statements/create-virtual-table.md)\n- [CREATE TYPE](statements/create-type.md)\n- [DROP TYPE](statements/drop-type.md)\n- [Transactions](statements/transactions.md)\n- [ATTACH DATABASE](statements/attach-database.md)\n- [DETACH DATABASE](statements/detach-database.md)\n- [ANALYZE](statements/analyze.md)\n- [EXPLAIN](statements/explain.md)\n\n# Functions\n\n- [Scalar](functions/scalar.md)\n- [Aggregate](functions/aggregate.md)\n- [Date & Time](functions/date-time.md)\n- [Math](functions/math.md)\n- [JSON](functions/json.md)\n- [Window](functions/window.md)\n- [Array](functions/array.md)\n- [Vector](functions/vector.md)\n- [Full-Text Search](functions/fts.md)\n\n# Reference\n\n- [PRAGMAs](pragmas.md)\n- [Extensions](extensions.md)\n- [Experimental Features](experimental-features.md)\n- [Compatibility](compatibility.md)\nEOF\n\nif [ \"${1:-}\" = \"build\" ]; then\n  mdbook build\n  echo \"Built to ./book/\"\nelse\n  echo \"Starting preview at http://localhost:3000\"\n  echo \"Press Ctrl+C to stop.\"\n  mdbook serve --open\nfi\n"
  }
]